texlive[68137] Master: hobby (1sep23)

commits+karl at tug.org commits+karl at tug.org
Fri Sep 1 23:15:20 CEST 2023


Revision: 68137
          http://tug.org/svn/texlive?view=revision&revision=68137
Author:   karl
Date:     2023-09-01 23:15:20 +0200 (Fri, 01 Sep 2023)
Log Message:
-----------
hobby (1sep23)

Modified Paths:
--------------
    trunk/Master/texmf-dist/doc/latex/hobby/hobby.pdf
    trunk/Master/texmf-dist/doc/latex/hobby/hobby_code.pdf
    trunk/Master/texmf-dist/tex/latex/hobby/hobby.code.tex
    trunk/Master/texmf-dist/tex/latex/hobby/pgflibraryhobby.code.tex
    trunk/Master/texmf-dist/tex/latex/hobby/pml3array.sty
    trunk/Master/texmf-dist/tex/latex/hobby/tikzlibraryhobby.code.tex
    trunk/Master/tlpkg/libexec/ctan2tds

Added Paths:
-----------
    trunk/Master/texmf-dist/doc/latex/hobby/README.txt
    trunk/Master/texmf-dist/doc/latex/hobby/hobby.tex
    trunk/Master/texmf-dist/source/latex/hobby/hobby_code.dtx
    trunk/Master/texmf-dist/source/latex/hobby/hobby_code.ins
    trunk/Master/texmf-dist/tex/latex/hobby/hobby-l3draw.sty

Removed Paths:
-------------
    trunk/Master/texmf-dist/doc/latex/hobby/hobby_doc.tex
    trunk/Master/texmf-dist/source/latex/hobby/hobby.dtx
    trunk/Master/texmf-dist/source/latex/hobby/hobby.ins

Added: trunk/Master/texmf-dist/doc/latex/hobby/README.txt
===================================================================
--- trunk/Master/texmf-dist/doc/latex/hobby/README.txt	                        (rev 0)
+++ trunk/Master/texmf-dist/doc/latex/hobby/README.txt	2023-09-01 21:15:20 UTC (rev 68137)
@@ -0,0 +1,14 @@
+----------------------------------------------------------------
+hobby --- a TikZ/PGF library for drawing smooth(ish) curves using
+          Hobby's algorithm (implemented in LaTeX3)
+E-mail: loopspace at mathforge.org
+Released under the LaTeX Project Public License v1.3c or later
+See http://www.latex-project.org/lppl.txt
+----------------------------------------------------------------
+
+This package defines a path generation function for TikZ/PGF
+which implements Hobby's algorithm for a path built out of Bezier
+curves which passes through a given set of points.
+
+The implementation is in LaTeX3.  It can be used as as a TikZ
+`to path`.


Property changes on: trunk/Master/texmf-dist/doc/latex/hobby/README.txt
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Modified: trunk/Master/texmf-dist/doc/latex/hobby/hobby.pdf
===================================================================
(Binary files differ)

Added: trunk/Master/texmf-dist/doc/latex/hobby/hobby.tex
===================================================================
--- trunk/Master/texmf-dist/doc/latex/hobby/hobby.tex	                        (rev 0)
+++ trunk/Master/texmf-dist/doc/latex/hobby/hobby.tex	2023-09-01 21:15:20 UTC (rev 68137)
@@ -0,0 +1,919 @@
+%\immediate\write18{tex hobby.dtx}
+\documentclass{ltxdoc}
+\usepackage[T1]{fontenc}
+\usepackage{csquotes}
+\usepackage{lmodern}
+\usepackage{tikz}
+\usepackage{pgfplots}
+\usepackage{amsmath}
+\usepackage{fancyvrb}
+\usetikzlibrary{hobby,decorations.pathreplacing}
+\usepackage{listings}
+\usepackage{hyperref}
+\pgfplotsset{compat=1.9}
+
+\lstset{
+  breakatwhitespace=true,
+  breaklines=true,
+  language=[LaTeX]TeX,
+  basicstyle=\small\ttfamily,
+  keepspaces=true,
+  columns=fullflexible
+}
+% hobby specific keywords (we can't put keywords with spaces :()
+\lstset{
+  emphstyle={\color{red}},
+  emph={and,angle,blank,blanks,closed,controls,curl,curve,designated,disjoint,excess,finish,Hobby,hobby,in,invert,next,out,path,previous,quick,restore,save,shortcut,show,soft,tension,through,use}
+}
+% tikz keywords used in this documentation
+\lstset{
+  emphstyle={[2]\color{blue!70!black}},
+  emph={[2]addplot,axis,blue,coordinates,distance,double,draw,every,foreach,grid,help,line,lines,plot,postaction,red,rotate,scale smooth,scale,smooth,style,thick,tikz,tikzpicture,to,ultra,white,width,xshift,yellow}
+}
+
+\EnableCrossrefs
+\CodelineIndex
+\RecordChanges
+
+\newenvironment{example}
+  {\VerbatimEnvironment
+   \begin{VerbatimOut}{example.out}}
+  {\end{VerbatimOut}
+   \begin{center}
+   \setlength{\parindent}{0pt}
+   \fbox{\begin{minipage}{.9\linewidth}
+     \lstinputlisting[]{example.out}
+   \end{minipage}}
+   \fbox{\begin{minipage}{.9\linewidth}
+     \centering
+     \input{example.out}
+   \end{minipage}}
+\end{center}
+}
+
+
+
+\tikzset{
+  show curve controls/.style={
+    decoration={
+      show path construction,
+      curveto code={
+  \draw [blue, dashed]
+        (\tikzinputsegmentfirst)    -- (\tikzinputsegmentsupporta)
+        node [at end, draw, solid, red, inner sep=2pt]{};
+  \draw [blue, dashed]
+        (\tikzinputsegmentsupportb) -- (\tikzinputsegmentlast)
+        node [at start, draw, solid, red, inner sep=2pt]{};
+      }
+    },decorate
+  },
+}
+
+%\bibliographystyle{plain}
+
+\renewcommand{\thefootnote}{\fnsymbol{footnote}}
+
+\providecommand*{\url}{\texttt}
+\title{The \textsf{Hobby} package}
+\author{Andrew Stacey \\ \url{loopspace at mathforge.org}}
+\date{\hobbyVersion\ from\ \hobbyDate}
+\begin{document}
+\maketitle
+
+\section{Introduction}
+
+John Hobby's algorithm, \cite{MR834054}, produces a curve through a given set of points.
+The curve is constructed as a list of cubic B\'ezier curves with endpoints at subsequent points in the list.
+The parameters of the curves are chosen so that the joins are ``smooth''.
+The algorithm was devised as part of the MetaPost program.
+
+TikZ/PGF has the ability to draw a curve through a given set of points but its algorithm is somewhat simpler than Hobby's and consequently does not produce as aesthetically pleasing curve as Hobby's algorithm does.
+This package implements Hobby's algorithm in \TeX{} so that TikZ/PGF can make use of it and thus produce nicer curves through a given set of points.
+
+Hobby's algorithm allows for considerable customisation in that it can take into account various parameters.
+These are all allowed in this implementation.
+
+There is also a ``quick'' version presented here.
+This is a modification of Hobby's algorithm with the feature that any point only influences a finite number (in fact, two) of the previous segments (in Hobby's algorithm the influence of a point dies out exponentially but never completely).
+This is achieved by applying Hobby's algorithm to subpaths.
+The resulting path produced with this ``quick'' version is not as ideal as that produced by Hobby's full algorithm, but is still much better than that produced by the \Verb+plot[smooth]+ method in TikZ/PGF, as can be seen in Figure~\ref{fig:comparison}.
+As this is intended as a simpler method, it does not (at present) admit the same level of customisation as the full implementation.
+The ``quick'' algorithm is described in full in Section~\ref{sec:quick}.
+
+The full algorithm is implemented in \LaTeX3 with no reference to TikZ or PGF.
+It makes extensive use of the \Verb+fp+ and \Verb+prop+ libraries for the computation steps.
+The TikZ library is simply a wrapper that takes the user's input, converts it into the right format for the \LaTeX3 code, and then calls that code to generate the path.
+The ``quick'' version does not use \LaTeX3 and relies instead on the \Verb+PGFMath+ library for the computation.
+
+
+Figure~\ref{fig:comparison} is a comparison of the three methods.
+The red curve is drawn using Hobby's algorithm.
+The blue curve is drawn with the \Verb+plot[smooth]+ method from TikZ/PGF.
+The green curve uses the ``quick'' version.
+Figure~\ref{fig:metapost} compares the implementation with that given by MetaPost.
+
+\begin{figure}
+\centering
+\begin{tikzpicture}[scale=.5]
+\draw[red,line width=5pt] (0,0) to[curve through={(6,4) .. (4,9) .. (1,7)}] (3,5);
+\draw[ultra thick,blue] plot[smooth] coordinates {(0,0) (6,4) (4,9) (1,7) (3,5)};
+\draw[green,line width=2pt] (0,0) to[quick curve through={(6,4) (4,9)  (1,7)}] (3,5);
+\end{tikzpicture}
+\caption{Comparison of the three algorithms}
+\label{fig:comparison}
+\end{figure}
+
+
+\begin{figure}
+\centering
+\begin{tikzpicture}[scale=.5]
+  \draw[scale=.1,postaction=show curve controls,line width=1mm,red] (0,0)
+    .. controls (26.76463,-1.84543) and (51.4094,14.58441) .. (60,40)
+    .. controls (67.09875,61.00188) and (59.76253,84.57518) .. (40,90)
+    .. controls (25.35715,94.01947) and (10.48064,84.5022) .. (10,70)
+    .. controls (9.62895,58.80421) and (18.80421,49.62895) .. (30,50);
+  \fill[green] (0,0) circle[radius=2pt]
+    (6,4) circle[radius=2pt]
+    (4,9) circle[radius=2pt]
+    (1,7) circle[radius=2pt]
+    (3,5) circle[radius=2pt];
+  \draw[postaction=show curve controls,thick] (0,0) to[curve through={(6,4) .. (4,9) .. (1,7)}] (3,5);
+  \begin{scope}[xshift=10cm]
+    \draw[scale=.1,postaction=show curve controls,line width=1mm,red] (0,0)
+      .. controls (5.18756,-26.8353) and (60.36073,-18.40036) .. (60,40)
+      .. controls (59.87714,59.889) and (57.33896,81.64203) .. (40,90)
+      .. controls (22.39987,98.48387) and (4.72404,84.46368) .. (10,70)
+      .. controls (13.38637,60.7165) and (26.35591,59.1351) .. (30,50)
+      .. controls (39.19409,26.95198) and (-4.10555,21.23804) .. (0,0); %
+    \fill[green] (0,0) circle[radius=2pt]
+      (6,4) circle[radius=2pt]
+      (4,9) circle[radius=2pt]
+      (1,7) circle[radius=2pt]
+      (3,5) circle[radius=2pt];
+    \draw[postaction=show curve controls,thick] (0,0) to[closed,curve through={(6,4) .. (4,9) .. (1,7)}] (3,5);
+  \end{scope}
+\end{tikzpicture}
+\caption{Hobby's algorithm in TikZ overlaying the output of MetaPost}
+\label{fig:metapost}
+\end{figure}
+
+\section{Usage}
+The package is provided in form of a TikZ library.
+It can be loaded with
+\begin{verbatim}
+\usetikzlibrary{hobby}
+\end{verbatim}
+
+\textbf{Warning}: This package makes extensive use of \LaTeX3.
+On occasion, updates to \LaTeX3 packages have resulted in this package behaving oddly or not working at all.
+The most up to date version of this package can be obtained from my \href{https://github.org/loopspace/hobby}{github page} (download \Verb+hobby.dtx+ and run \Verb+tex hobby.dtx+ to generate the files).
+Often, such issues are reported on the \href{http://tex.stackexchange.com}{TeX-SX} site and workarounds quickly found so it is worth checking there as well.
+
+\bigskip
+
+There are a variety of ways of specifying the data to the algorithm to generate the curve.
+
+\subsection{As a \textsf{to path}.}
+
+The key \Verb+curve through={<points>}+ installs a \Verb+to path+ which draws a smooth curve through the given points.
+The points should be specified as a list which can be optionally separated by dots.
+The purpose of allowing the dots is to make it simpler to switch between the \Verb+to path+ method and the \Verb+shortcut+ method (described in Section~\ref{sec:shortcut}).
+However, note that the two methods are not completely synonymous due to how one can specify options so care must still be taken when switching.
+
+\begin{example}
+\begin{tikzpicture}[scale=.5]
+  \draw (0,0) to[curve through={(6,4) .. (4,9) .. (1,7)}] (3,5);
+\end{tikzpicture}
+\end{example}
+
+\begin{example}
+\begin{tikzpicture}[scale=.5]
+  \draw (0,0) to[curve through={(6,4) (4,9) (1,7)}] (3,5);
+\end{tikzpicture}
+\end{example}
+
+There is a corresponding key \Verb+quick curve through={<points>}+ which uses the ``quick'' algorithm.
+Again, the dots are optional.
+
+\begin{example}
+\begin{tikzpicture}[scale=.5]
+  \draw (0,0) to[quick curve through={(6,4) .. (4,9) .. (1,7)}] (3,5);
+\end{tikzpicture}
+\end{example}
+
+\subsection{The \textsf{shortcut} Method}
+\label{sec:shortcut}
+
+There is also the facility to subvert TikZ's path processor and define curves simply using the \Verb+..+ separator between points.
+Note that this relies on something a little special in TikZ: the syntax \Verb+(0,0) .. (2,3)+ is currently detected and processed but there is no action assigned to that syntax.
+As a later version of TikZ may assign some action to that syntax, this package makes its override optional via the key \Verb+use Hobby shortcut+ (which can be set globally if so desired).
+
+\begin{example}
+\begin{tikzpicture}[scale=.5,use Hobby shortcut]
+  \draw (-3,0) -- (0,0) .. (6,4) .. (4,9) .. (1,7) .. (3,5) -- ++(2,0);
+\end{tikzpicture}
+\end{example}
+
+\begin{example}
+\begin{tikzpicture}[scale=.5,use quick Hobby shortcut]
+  \draw (-3,0) -- (0,0) .. (6,4) .. (4,9) .. (1,7) .. (3,5) -- ++(2,0);
+\end{tikzpicture}
+\end{example}
+
+\subsection{The \textsf{Plot Handler} Method}
+
+The algorithms can also be used via the \Verb+plot handler+ syntax.
+This library registers three plot handlers: \Verb+hobby+, \Verb+closed hobby+, and \Verb+quick hobby+.
+The first is an open curve through the points using the full algorithm, the second is a closed curve, and the third uses the quick algorithm (and is thus an open curve).
+
+\begin{example}
+  \tikz[smooth] \draw plot coordinates {(0,0) (1,1) (2,0) (3,1) (2,1) (10:2cm)};
+
+  \tikz[hobby] \draw plot coordinates {(0,0) (1,1) (2,0) (3,1) (2,1) (10:2cm)};
+
+  \tikz[closed hobby] \draw plot coordinates {(0,0) (1,1) (2,0) (3,1) (2,1) (10:2cm)};
+
+  \tikz[quick hobby] \draw plot coordinates {(0,0) (1,1) (2,0) (3,1) (2,1) (10:2cm)};
+\end{example}
+
+This has the side effect that these can be used with the \Verb+pgfplots+ package.
+However, the Hobby algorithm is designed to draw a curve in 2D-{}space and does not take into account the fact that when plotting a graph then the two dimensions are treated differently.
+
+\begin{example}
+\begin{tikzpicture}
+  \begin{axis}
+    \addplot +[smooth] {rnd};
+    \addplot +[hobby] {rnd};
+  \end{axis}
+\end{tikzpicture}
+\end{example}
+
+\subsection{Basic Level PGF Commands}
+
+(Suggested by the question \href{http://tex.stackexchange.com/q/159896/86}{How to combine Hobby paths with PGF Basic Layer commands?} on TeX-SX.)
+
+In some circumstances, it is convenient to bypass TikZ and use more basic PGF commands for building a path.
+It is possible to add a path built using Hobby's algorithm in this fashion.
+The commands are:
+
+\begin{itemize}
+\item \Verb+\pgfpathhobby+ to initialise the path.
+If this is followed by a braced group then the contents of that are taken as options to the algorithm.
+
+\item \Verb+\pgfpathhobbypt{<pgf point specification>}+ to add a point to the path.
+If this is followed by a braced group then the contents of that are taken as options for that point.
+
+\item \Verb+\pgfpathhobbyend+ finalises the path.
+This applies the algorithm to the set of specified points and adds it to the current path.
+\end{itemize}
+
+\begin{example}
+\begin{tikzpicture}
+  \pgfpathmoveto{\pgfpoint{0}{0}}
+  \pgfpathlineto{\pgfpoint{1cm}{0}}
+  \pgfpathhobby{closed=true}
+  \pgfpathhobbypt{\pgfpoint{1cm}{2cm}}{tension in=2}
+  \pgfpathhobbypt{\pgfpoint{2cm}{1cm}}
+  \pgfpathhobbypt{\pgfpoint{3cm}{0cm}}
+  \pgfpathhobbyend
+  \pgfusepath{stroke}
+\end{tikzpicture}
+\end{example}
+
+\section{Customisation}
+
+There are various ways to customise the path generated by the Hobby algorithms.
+The full algorithm has a variety of parameters which can be varied to produce different paths through the same points.
+These vary from specifying that the path be open or closed, to specifying ``tensions'' at each point to change how the path approaches or leaves it.
+
+\subsection{Algorithm Customisations}
+
+The main algorithm (i.e., not the ``quick'' variant) can deal with open or closed paths, it is possible to vary the ``tensions'' between the specified points of the paths, and for an open path it is possible to specify the incoming and outgoing angles either directly or via certain ``curl'' parameters.
+When using the \Verb+to path+ specification, the parameters can be specified before or after the \Verb+curve through+ key or as options to the coordinates.
+When using the \Verb+shortcut+ specification, the parameters can be given on the path or on coordinates.
+
+On occasion, it is ambiguous which curve an option belongs to.
+This is most likely if a coordinate happens to belong to two curves, or if a coordinate is parsed before TikZ knows that it is constructing a curve using this library.
+The simplest solution is to move the option to a place where there is no ambiguity.
+Other solutions to this problem will be detailed later.
+
+Let us start with the customisations to the algorithm.
+
+\begin{itemize}
+\item Basic curve.
+\begin{example}
+\begin{tikzpicture}
+  \draw[postaction=show curve controls] (0,0) to[curve through={(1,.5) .. (2,0) .. (3,.5)}] (4,0);
+  \draw[xshift=5cm,use Hobby shortcut,postaction=show curve controls] (0,0) .. (1,.5) .. (2,0) .. (3,.5) .. (4,0);
+\end{tikzpicture}
+\end{example}
+
+\item The path can be open, as above, or closed.
+%%
+\begin{example}
+\begin{tikzpicture}[scale=.5]
+  \draw[postaction=show curve controls] (0,0) to[closed,curve through={(1,.5) .. (2,0) .. (3,.5)}] (4,0);
+  \draw[xshift=5cm,use Hobby shortcut,postaction=show curve controls] ([closed]0,0) .. (1,.5) .. (2,0) .. (3,.5) .. (4,0);
+\end{tikzpicture}
+\end{example}
+
+\item Specifying the angle at which the curve goes \emph{out} and at which it comes \emph{in}.
+The angles given are absolute.
+\begin{example}
+\begin{tikzpicture}
+  \draw[postaction=show curve controls] (0,0) to[out angle=0,in angle=180,curve through={(1,.5) .. (2,0) .. (3,.5)}] (4,0);
+  \draw[xshift=5cm,use Hobby shortcut,postaction=show curve controls] ([out angle=0,in angle=180]0,0) .. (1,.5) .. (2,0) .. (3,.5) .. (4,0);
+\end{tikzpicture}
+\end{example}
+
+\item Applying tension as the curve comes in to a point.
+\begin{example}
+\begin{tikzpicture}
+  \draw[postaction=show curve controls] (0,0) to[curve through={(1,.5) .. ([tension in=2]2,0) .. (3,.5)}] (4,0);
+  \draw[xshift=5cm,use Hobby shortcut,postaction=show curve controls] (0,0) .. (1,.5) .. ([tension in=2]2,0) .. (3,.5) .. (4,0);
+\end{tikzpicture}
+\end{example}
+
+\item Applying the same tension as a curve comes in and goes out of a point.
+\begin{example}
+\begin{tikzpicture}
+  \draw[postaction=show curve controls] (0,0) to[curve through={(1,.5) .. ([tension=2]2,0) .. (3,.5)}] (4,0);
+\end{tikzpicture}
+\end{example}
+
+\item Specifying the \emph{curl} parameters.
+\begin{example}
+\begin{tikzpicture}
+  \draw[postaction=show curve controls] (0,0) to[in curl=.1,out curl=3,curve through={(1,.5) .. (2,0) .. (3,.5)}] (4,0);
+  \draw[xshift=5cm,use Hobby shortcut,postaction=show curve controls] (0,0) .. ([in curl=.1,out curl=3]1,.5) .. (2,0) .. (3,.5) .. (4,0);
+\end{tikzpicture}
+\end{example}
+\end{itemize}
+
+\subsection{Edge Cases}
+
+Angles are constrained to lie in the interval \((-\pi,\pi]\).
+This can introduce edge cases as there is a point where we have to compare an angle with \(-\pi\) and if it is equal, add \(2 \pi\).
+This will occur if the path ``doubles back'' on itself as in the next example.
+By nudging the repeated point slightly, the behaviour changes drastically.
+
+\begin{example}
+\begin{tikzpicture}[use Hobby shortcut]
+  \draw (0,0) .. (1,0) .. (0,0) .. (0,-1);
+  \draw[xshift=2cm] (0,0) .. (1,0) .. (0,0.1) .. (0,-1);
+  \draw[xshift=4cm] (0,0) .. (1,0) .. (0,-0.1) .. (0,-1);
+\end{tikzpicture}
+\end{example}
+
+Due to the precision of the computations, it is not possible to always get this test correct.
+The simplest solution is to nudge the repeated point in one direction or the other.
+Experimenting shows that the ``nudge factor'' can be extremely small (note that it will be proportional to the distance between the specified points).
+It is best to nudge it in the direction most normal to the line between the specified points as the goal is to nudge the difference of the angles.
+An alternative solution is to add an additional point for the curve to go through.
+\begin{example}
+\begin{tikzpicture}[use Hobby shortcut]
+  \draw (0,0) .. (1,0) .. (0,0) .. (0,-1);
+  \draw[xshift=2cm] (0,0) .. (1,0) .. (0,0.002) .. (0,-1);
+  \draw[xshift=4cm] (0,0) .. (1,0) .. (0,-0.002) .. (0,-1);
+\end{tikzpicture}
+\end{example}
+
+Lastly, it is possible to add an \Verb+excess angle+ key to a coordinate.
+This will add the corresponding multiple of \(2\pi\) to the angle difference.
+\begin{example}
+\begin{tikzpicture}[use Hobby shortcut]
+  \draw (0,0) .. (1,0) .. (0,0) .. (0,-1);
+  \draw[xshift=2cm] (0,0) .. ([excess angle=1]1,0) .. (0,0) .. (0,-1);
+  \draw[xshift=4cm] (0,0) .. ([excess angle=-1]1,0) .. (0,0) .. (0,-1);
+\end{tikzpicture}
+\end{example}
+
+Although this is intended to be an integer, no check is done and so some quite odd curves can result from changing this parameter.
+
+\subsection{Reusing Paths}
+
+Although the (full) algorithm has good theoretical computation time, using \TeX\ for its implementation does not provide for fast runs.
+The externalisation library of TikZ/PGF can be used to save whole pictures, but it can be useful to save a generated path within a single \Verb+tikzpicture+ for later use within that same picture.
+The implementation allows for this by separating the generation of the path from its use.
+
+\begin{example}
+\begin{tikzpicture}
+  \draw[line width=3mm,red,use Hobby shortcut,save Hobby path={saved}] (0,0) .. (1,1) .. (2,0);
+  \draw[xshift=2cm,ultra thick,yellow] (0,0) [restore and use Hobby path={saved}{}];
+\end{tikzpicture}
+\end{example}
+%
+
+Note that the key \Verb+restore and use Hobby path+ is given \emph{after} the initial \Verb+(0,0)+.
+This is because by default the path generated by the Hobby algorithm does not start with an explicit \Verb+moveto+ since that is the standard behaviour of all of PGF's path construction macros.
+So the \Verb+(0,0)+ ensures that our path is well-{}formed by issuing an initial \Verb+moveto+.
+An alternative would be to use the key \Verb+disjoint+ which does add an initial \Verb+moveto+.
+
+\begin{example}
+\begin{tikzpicture}
+  \draw[line width=3mm,red,use Hobby shortcut,save Hobby path={saved}] (0,0) .. (1,1) .. (2,0);
+  \draw[xshift=2cm,ultra thick,yellow,restore and use Hobby path={saved}{disjoint}];
+\end{tikzpicture}
+\end{example}
+%
+
+An example of where this is useful is in drawing knot diagrams.
+When so doing, it is sometimes convenient to draw a path (or segment of a path) twice in order to get the under/over crossings correct.
+For this situation, it can be useful to designate certain parts of the path as \Verb+blank+, whereby we mean to redraw them later.
+The point of a blank segment of a curve is that it is still taken into account when computing the algorithm but is left blank when it comes to rendering.
+A path can then be redrawn with the blank/non-{}blank segments reversed.
+As it might be desired to have only some blank segments drawn the second time, there are two types of blank.
+Only a \Verb+soft+ blank will be reversed in these circumstances.
+
+\begin{example}
+\begin{tikzpicture}[use Hobby shortcut,line width=1mm,rotate=90]
+  \draw[blue,save Hobby path={left}] ([out angle=90,in angle=-90]1,0) .. (1,1) .. ([blank=soft]0,2) .. (1,3) .. (1,4);
+  \draw[red] ([out angle=90,in angle=-90]0,0) .. (0,1) .. (1,2) .. (0,3) .. (0,4);
+  \draw[blue,restore and use Hobby path={left}{disjoint,invert soft blanks}];
+\end{tikzpicture}
+\end{example}
+%
+
+This can be taken a step further.
+The generated data can be saved to the \Verb+aux+ file and read back in, avoiding the need to regenerate it on each run.
+To engage this facility, the Hobby path has to be named (via \Verb+save Hobby path+) and the key \Verb+Hobby externalise+ (or \Verb+Hobby externalize+) must be given in a context that applies (such as on the path or on the surrounding scope).
+
+\bigskip
+
+
+The relevant keys are the following.
+
+\begin{itemize}
+\item \Verb+use previous Hobby path[=<options>]+.
+This (re)uses the previously generated Hobby path.
+As all the data is globally stored, this can technically be in a different \Verb+tikzpicture+.
+The \Verb+<options>+ will be applied, in so far as they are options that can be applied after the algorithm has run.
+
+\item \Verb+save Hobby path=<name>+.
+Saves a path for later use.
+The path is saved in a global macro so can be reused in another picture.
+
+\item \Verb+restore Hobby path=<name>+.
+This restores the named Hobby path (if it exists).
+It does not \emph{use} it.
+After this key, \Verb+use previous Hobby path+ will use the restored path.
+
+\item \Verb+restore and use Hobby path={<name>}{<options>}+.
+This restores the named path and uses it with \Verb+<options>+ applied.
+
+\item \Verb+Hobby externalise+ or \Verb+Hobby externalize+.
+This puts in place the code for saving the generated data to the \Verb+aux+ file.
+On subsequent runs, it uses the saved data rather than the current data.
+For a curve to make use of this, it has to be named via the \Verb+save Hobby path+ key.
+So to regenerate the data, either delete the \Verb+aux+ file, remove the \Verb+save Hobby path+ key for one compilation run, or issue the command \Verb+\HobbyDisableAux+ which disables writing paths to the \Verb+aux+ file (note that the paths will be regenerated on the run \emph{after} the first run with this command issued).
+\end{itemize}
+
+The options that can be applied are those that affect the rendering of the curve but not its generation.
+When the curve is rendered (or \emph{used}, in the above parlance), \TeX\ steps along the coordinates of the generated curve and carries out an action for each piece.
+This action can be modified after the curve has been generated.
+The action will be one of:
+%
+\begin{itemize}
+\item Move to the end point (ignoring the control points).
+\item Draw a Bezier curve to the end point through the control points.
+\item Draw a Bezier curve to the end point through the control points and then move to the end point.
+\end{itemize}
+%
+The last is subtle: the move doesn't actually go anywhere but it ``breaks'' the curve at the designated point.
+In particular, a later \Verb+cycle+ would return to this point (or a later break) rather than to the start of the curve.
+
+These actions are triggered by the keys \Verb+blank+ and \Verb+break+.
+Each should be specified to the coordinate at the \emph{end} of the segment under consideration.
+The \Verb+blank+ key can be given the argument \Verb+soft+.
+The effect of this is seen when the key \Verb+invert soft blanks+ is used.
+This swaps the drawing action so that non-{}blank segments are skipped and \emph{soft} blanks are drawn.
+Non-{}soft-{}blank segments are still not drawn.
+
+\begin{example}
+\begin{tikzpicture}[use Hobby shortcut]
+  \draw (0,0) .. (1,1) .. ([blank=soft]2,0) .. (3,1) .. ([blank]4,0) .. (5,1);
+  \draw[red,use previous Hobby path={invert soft blanks,disjoint}];
+\end{tikzpicture}
+\end{example}
+%
+
+As a more practical application, consider the following rendering of a trefoil knot.
+
+\begin{example}
+\begin{tikzpicture}[
+  use Hobby shortcut,
+  every path/.style={
+    line width=1mm,
+    white,
+    double=red,
+    double distance=.5mm
+  }
+]
+  \draw ([closed]0,2) .. ([blank=soft]210:.5) .. (-30:2) .. ([blank=soft]0,.5) .. (210:2) .. ([blank=soft]-30:.5);
+  \draw[use previous Hobby path={invert soft blanks,disjoint}];
+\end{tikzpicture}
+\end{example}
+%
+
+This could easily be generalised using the \Verb+\foreach+ command, as demonstrated in the next code.
+
+\begin{example}
+\begin{tikzpicture}[
+  use Hobby shortcut,
+  every path/.style={
+    line width=1mm,
+    white,
+    double=red,
+    double distance=.5mm
+  }
+]
+\def\nfoil{9}
+  \draw ([closed]0,2)
+    foreach \k in {1,...,\nfoil}{
+      .. ([blank=soft]90+360*\k/\nfoil-180/\nfoil:-.5) .. (90+360*\k/\nfoil:2)
+    };
+  \draw[use previous Hobby path={invert soft blanks,disjoint}];
+\end{tikzpicture}
+\end{example}
+%
+
+\subsection{Breaking the Path}
+
+One issue with the shortcut notation is that it is not possible (using this notation) to have two sets of curves following directly on from each other because there is no clear demarcation of the boundary.
+To make this possible, there is a key \Verb+Hobby action+, which installs an action to be taken after the point has been processed.
+The general key \Verb+Hobby action={code}+ can install arbitrary code.
+Probably the more useful variant is \Verb+Hobby finish+ which runs the algorithm on the points gathered so far.
+An example of the use of this is to make it possible to specify tangencies at certain points.
+Technically, once a tangent direction has been specified, the Hobby algorithm splits the set of points there and works on each piece separately.
+The following key implements this, the technicalities are due to the fact that the tangent angle has to be used twice: once to specify the angle of the path coming in to that point and once to specify the angle of the path coming out.
+Note that specifying the tangent vector at every point means that the algorithm is not actually being used.
+However, Hobby's formulae for the lengths of the control points is still being used.
+
+\begin{example}
+\begin{tikzpicture}[
+  use Hobby shortcut,
+  tangent/.style={%
+    in angle={(180+#1)},
+    Hobby finish,
+    designated Hobby path=next,
+    out angle=#1,
+  },
+]
+  \draw[help lines] (-5,-5) grid (5,5);
+  \draw (-5,0) -- (5,0) (0,-5) -- (0,5);
+  \draw[thick] (-5,2) .. ([tangent=0]-3,3) .. (-1,1) .. (0,-1.3) .. ([tangent=0]1,-2) .. ([tangent=45]2,-1.5) .. ([tangent=0]3,-2) .. (5,-4);
+\end{tikzpicture}
+\end{example}
+%
+
+
+
+\section{Implementing Hobby's Algorithm}
+We start with a list of \(n+1\) points, \(z_0, \dotsc, z_n\).
+The base code assumes that these are already stored in two arrays\footnote{Arrays are thinly disguised property lists.}: the \(x\)--coordinates in \Verb+\l_hobby_points_x_array+ and the \(y\)--coordinates in \Verb+\l_hobby_points_y_array+.
+As our arrays are \(0\)--indexed, the actual number of points is one more than this.
+For a closed curve, we have \(z_n = z_0\)\footnote{Note that there is a difference between a closed curve and an open curve whose endpoints happen to overlap.}.
+For closed curves it will be convenient to add an additional point at \(z_1\): thus \(z_{n+1} = z_1\).
+This makes \(z_n\) an internal point and makes the algorithms for closed paths and open paths agree longer than they would otherwise.
+The number of apparent points is stored as \Verb+\l_hobby_npoints_int+.
+Thus for an open path, \Verb+\l_hobby_npoints_int+ is \(n\), whilst for a closed path, it is \(n+1\)\footnote{In fact, we allow for the case where the user specifies a closed path but with \(z_n \ne z_0\).
+In that case, we assume that the user meant to repeat \(z_0\).
+This adds another point to the list.}.
+Following Hobby, let us write \(n'\) for \(n\) if the path is open and \(n+1\) if closed.
+From this we compute the distances and angles between successive points, storing these again as arrays.
+These are \Verb+\l_hobby_distances_array+ and \Verb+\l_hobby_angles_array+.
+The term indexed by \(k\) is the distance (or angle) of the line between the \(k\)th point and the \(k+1\)th point.
+For the internal nodes\footnote{Hobby calls the specified points \emph{knots}.}, we store the difference in the angles in \Verb+\l_hobby_psi_array+.
+The \(k\)th value on this is the angle subtended at the \(k\)th node.
+This is thus indexed from \(1\) to \(n'-1\).
+The bulk of the work consists in setting up a linear system to compute the angles of the control points.
+At a node, say \(z_i\), we have various pieces of information:
+\begin{enumerate}
+\item The angle of the incoming curve, \(\phi_i\), relative to the straight line from \(z_{i-1}\) to \(z_i\)
+\item The angle of the outgoing curve, \(\theta_i\), relative to the straight line from \(z_i\) to \(z_{i+1}\)
+\item The tension of the incoming curve, \(\overline{\tau}_i\)
+\item The tension of the outgoing curve, \(\tau_i\)
+\item The speed of the incoming curve, \(\sigma_i\)
+\item The speed of the outgoing curve, \(\rho_i\)
+\end{enumerate}
+The tensions are known at the start.
+The speeds are computed from the angles.
+Thus the key thing to compute is the angles.
+This is done by imposing a ``mock curvature'' condition.
+The formula for the mock curvature is:
+%%
+\[
+\hat{k}(\theta,\phi,\tau,\overline{\tau}) = \tau^2 \left( \frac{2(\theta + \phi)}{\overline{\tau}} - 6\theta\right)
+\]
+%%
+and the condition that the mock curvatures have to satisfy is that at each \emph{internal} node, the curvatures must match:
+%
+\[
+\hat{k}(\phi_i,\theta_{i-1},\overline{\tau}_i,\tau_{i-1})/d_{i-1} = \hat{k}(\theta_i,\phi_{i+1},\tau_i,\overline{\tau}_{i+1})/d_i.
+\]
+%%
+Substituting in yields:
+%%
+\[
+\frac{\overline{\tau}_i^2}{d_{i-1}} \left( \frac{2(\phi_i + \theta_{i-1})}{\tau_{i-1}} - 6\phi_i\right) = \frac{\tau_i^2}{d_i} \left( \frac{2(\theta_i + \phi_{i+1})}{\overline{\tau}_{i+1}} - 6\theta_i \right).
+\]
+%%
+Let us rearrange that to the following:
+%%
+\begin{align*}
+d_i \overline{\tau}_{i+1} \overline{\tau}_i^2 &\theta_{i-1} \\
+%%
+{}+
+d_i \overline{\tau}_{i+1} \overline{\tau}_i^2 (1 - 3 \tau_{i-1}) &\phi_i \\
+%%
+{}-
+d_{i-1} \tau_{i-1} \tau_i^2 (1 - 3 \overline{\tau}_{i+1}) &\theta_i \\
+%%
+{}-
+d_{i-1} \tau_{i-1} \tau_i^2 &\phi_{i+1} \\
+%%
+&\qquad =
+0
+\end{align*}
+%%
+For both open and closed paths this holds for \(i=1\) to \(i=n' - 1\).
+We also have the condition that \(\theta_i + \phi_i = -\psi_i\) where \(\psi_i\) is the angle subtended at a node by the lines to the adjacent nodes.
+This holds for the internal nodes\footnote{Recall that by dint of repetition, all nodes are effectively internal for a closed path.}.
+Therefore for \(i=1\) to \(n'-1\) the above simplifies to the following:
+%
+\begin{align*}
+d_i \overline{\tau}_{i+1} \overline{\tau}_i^2 &\theta_{i-1} \\
+{}+
+(d_i \overline{\tau}_{i+1} \overline{\tau}_i^2 (3 \tau_{i-1} - 1)
+{}+
+d_{i-1} \tau_{i-1} \tau_i^2 (3 \overline{\tau}_{i+1} - 1)) &\theta_i \\
+{}+
+d_{i-1} \tau_{i-1} \tau_i^2 & \theta_{i+1} \\
+=
+- d_i \overline{\tau}_{i+1} \overline{\tau}_i^2 (3 \tau_{i-1} - 1) &\psi_i \\
+{}- d_{i-1} \tau_{i-1} \tau_i^2& \psi_{i+1}
+\end{align*}
+For an open path we have two more equations.
+One involves \(\theta_0\).
+The other is the above for \(i = n'-1 = n-1\) with additional information regarding \(\psi_n\).
+It may be that one or either of \(\theta_0\) or \(\phi_n\) is specified in advance.
+If so, we shall write the given values with a bar: \(\overline{\theta}_0\) and \(\overline{\phi}_n\).
+In that case, the first equation is simply setting \(\theta_0\) to that value and the last equation involves substituting the value for \(\phi_n\) into the above.
+If not, they are given by formulae involving ``curl'' parameters \(\chi_0\) and \(\chi_n\) and result in the equations:
+%
+\begin{align*}
+\theta_0 &= \frac{\tau_0^3 + \chi_0 \overline{\tau}_1^3(3 \tau_0 - 1)}{\tau_0^3(3 \overline{\tau}_1 - 1) + \chi_0 \overline{\tau}_1^3} \phi_1 \\
+\phi_n &= \frac{\overline{\tau}_n^3 + \chi_n \tau_{n-1}^3(3 \overline{\tau}_n - 1)}{\overline{\tau}_n^3(3 \tau_{n-1} - 1) + \chi_n \tau_{n-1}^3} \theta_{n-1}
+\end{align*}
+%%
+Using \(\phi_1 = - \psi_1 - \theta_1\), the first rearranges to:
+%%
+\[
+(\tau_0^3(3 \overline{\tau}_1 - 1) + \chi_0 \overline{\tau}_1^3) \theta_0 + (\tau_0^3 + \chi_0 \overline{\tau}_1^3(3 \tau_0 - 1)) \theta_1 = - (\tau_0^3 + \chi_0 \overline{\tau}_1^3(3 \tau_0 - 1)) \psi_1.
+\]
+%%
+The second should be substituted in to the general equation with \(i = n-1\).
+This yields:
+%%
+\begin{align*}
+d_{n-1} \overline{\tau}_{n} \overline{\tau}_{n-1}^2 &\theta_{n-2} \\
+{}+
+\big(d_{n-1} \overline{\tau}_{n} \overline{\tau}_{n-1}^2 (3 \tau_{n-2} - 1)
+{}+
+d_{n-2} \tau_{n-2} \tau_{n-1}^2 (3 \overline{\tau}_{n} - 1) \\
+{}- d_{n-2} \tau_{n-2} \tau_{n-1}^2  \frac{\overline{\tau}_n^3 + \chi_n \tau_{n-1}^3(3 \overline{\tau}_n - 1)}{\overline{\tau}_n^3(3 \tau_{n-1} - 1) + \chi_n \tau_{n-1}^3}\big) & \theta_{n-1} \\
+=
+- d_{n-1} \overline{\tau}_{n} \overline{\tau}_{n-1}^2 (3 \tau_{n-2} - 1) &\psi_{n-1}
+\end{align*}
+%%
+This gives \(n'\) equations in \(n'\) unknowns (\(\theta_0\) to \(\theta_{n-1}\)).
+The coefficient matrix is tridiagonal.
+It is more natural to index the entries from \(0\).
+Let us write \(A_i\) for the subdiagonal, \(B_i\) for the main diagonal, and \(C_i\) for the superdiagonal.
+Let us write \(D_i\) for the target vector.
+Then for an open path we have the following formulae:
+%%
+\begin{align*}
+A_i &= d_i \overline{\tau}_{i+1} \overline{\tau}^2_i \\
+B_0 &= \begin{cases}
+1 & \text{if}\; \overline{\theta}_0\; \text{given} \\
+\tau_0^3(3 \overline{\tau}_1 - 1) + \chi_0 \overline{\tau}^3_1 & \text{otherwise}
+\end{cases} \\
+B_i &= d_i \overline{\tau}_{i+1} \overline{\tau}_i^2 (3 \tau_{i-1} -1) + d_{i-1} \tau_{i-1} \tau_i^2(3 \overline{\tau}_{i+1} - 1) \\
+B_{n-1} &= \begin{cases} d_{n-1} \overline{\tau}_{n} \overline{\tau}_{n-1}^2 (3 \tau_{n-2} - 1) + d_{n-2} \tau_{n-2} \tau_{n-1}^2(3 \overline{\tau}_{n} - 1) & \text{if}\; \overline{\phi}_n\; \text{given} \\
+d_{n-1} \overline{\tau}_{n} \overline{\tau}_{n-1}^2 (3 \tau_{n-2} - 1) + d_{n-2} \tau_{n-2} \tau_{n-1}^2(3 \overline{\tau}_{n} - 1)
+\\
+- d_{n-2} \tau_{n-2} \tau_{n-1}^2  \frac{\overline{\tau}_n^3 + \chi_n \tau_{n-1}^3(3 \overline{\tau}_n - 1)}{\overline{\tau}_n^3(3 \tau_{n-1} - 1) + \chi_n \tau_{n-1}^3}) & \text{otherwise}
+\end{cases} \\
+C_0 &= \begin{cases}
+0 & \text{if}\; \overline{\theta}_0\; \text{given} \\
+\tau_0^3 + \chi_0 \overline{\tau}_1^3(3\tau_0 - 1) & \text{otherwise}
+\end{cases} \\
+C_i &= d_{i-1} \tau_{i-1} \tau_i^2 \\
+D_0 &= \begin{cases}
+\overline{\theta}_0 & \text{if}\; \overline{\theta}_0\; \text{given} \\
+- (\tau_0^3 + \chi_0 \overline{\tau}_1^3(3 \tau_0 - 1)) \psi_1 & \text{otherwise}
+\end{cases} \\
+D_i &= - d_i \overline{\tau}_{i+1} \overline{\tau}_i^2 (3 \tau_{i-1} - 1) \psi_i
+- d_{i-1} \tau_{i-1} \tau_i^2 \psi_{i+1} \\
+D_{n-1} &= \begin{cases}
+- d_{n-1} \overline{\tau}_{n} \overline{\tau}_{n-1}^2 (3 \tau_{n-2} - 1) \psi_{n-1} - d_{n-2} \tau_{n-2} \tau_{n-1}^2 \overline{\phi}_n & \text{if}\; \overline{\phi}_n\; \text{given} \\
+- d_{n-1} \overline{\tau}_{n} \overline{\tau}_{n-1}^2 (3 \tau_{n-2} - 1) \psi_{n-1} & \text{otherwise}
+\end{cases}
+\end{align*}
+For a closed path, we have \(n\) equations in \(n+2\) unknowns (\(\theta_0\) to \(\theta_{n+1}\)).
+However, we have not included all the information.
+Since we have repeated points, we need to identify \(\theta_0\) with \(\theta_n\) and \(\theta_1\) with \(\theta_{n+1}\).
+To get a system with \(n'\) equations in \(n'\) unknowns, we add the equation \(\theta_0 - \theta_n = 0\) and substitute in \(\theta_{n+1} = \theta_1\).
+The resulting matrix is not quite tridiagonal but has extra entries on the off-corners.
+However, it can be written in the form \(M + u v^\top\) with \(M\) tridiagonal.
+There is some freedom in choosing \(u\) and \(v\).
+For simplest computation, we take \(u = e_0 + e_{n'-1}\).
+This means that \(v = d_{n'-2} \tau_{n'-2} \tau_{n'-1}^2 e_1 - e_{n'-1}\).
+With the same notation as above, the matrix \(M\) is given by the following formulae:
+%%
+\begin{align*}
+A_i &= d_i \overline{\tau}_{i+1} \overline{\tau}_i^2 \\
+%%
+B_0 &= 1 \\
+%%
+B_i &= d_i \overline{\tau}_{i+1} \overline{\tau}_i^2 (3 \tau_{i-1} -1) + d_{i-1} \tau_{i-1} \tau_i^2(3 \overline{\tau}_{i+1} - 1) \\
+%%
+B_{n'-1} &= d_{n'-1} \overline{\tau}_{n'} \overline{\tau}_{n'-1}^2 (3 \tau_{n'-2} -1) + d_{n'-2} \tau_{n'-2} \tau_{n'-1}^2(3 \overline{\tau}_{n'} - 1) + 1\\
+%%
+C_0 &= - d_{n'-2} \tau_{n'-2} \tau_{n'-1}^2 \\
+%%
+C_i &= d_{i-1} \tau_{i-1} \tau_i^2 \\
+%%
+D_0 &= 0 \\
+%%
+D_i &= - d_i \overline{\tau}_{i+1} \overline{\tau}_i^2 (3 \tau_{i-1} - 1) \psi_i
+- d_{i-1} \tau_{i-1} \tau_i^2 \psi_{i+1} \\
+%%
+D_{n'-1} &= - d_{n'-1} \overline{\tau}_{n'} \overline{\tau}_{n'-1}^2 (3 \tau_{n'-2} - 1) \psi_{n'-1}
+- d_{n'-2} \tau_{n'-2} \tau_{n'-1}^2 \psi_1
+\end{align*}
+The next step in the implementation is to compute these coefficients and store them in appropriate arrays.
+Having done that, we need to solve the resulting tridiagonal system.
+This is done by looping through the arrays doing the following substitutions (starting at \(i = 1\)):
+%
+\begin{align*}
+B_i' &= B_{i-1}' B_i - A_i C_{i-1}' \\
+C_i' &= B_{i-1}' C_i \\
+D_i' &= B_{i-1}' D_i - A_i D_{i-1}'
+\end{align*}
+%%
+followed by back-substitution:
+%%
+\begin{align*}
+\theta_{n-1} &= D_{n-1}'/B_{n-1}' \\
+\theta_i &= (D_i' - C_i' \theta_{i+1})/B_i'
+\end{align*}
+%%
+For a closed path, we run this both with the vector \(D\) and the vector \(u = e_0 + e_{n'-1}\).
+Then to get the real answer, we use the Sherman--{}Morrison formula:
+%%
+\[
+(M + u v^\top)^{-1} D = M^{-1} D - \frac{M^{-1} u v^\top M^{-1} D}{1 + v^\top M^{-1} u}.
+\]
+%%
+This leaves us with the values for \(\theta_i\).
+We now substitute these into Hobby's formulae for the lengths:
+%%
+\begin{align*}
+\rho_i &= \frac{2 + \alpha_i}{1 + (1 - c) \cos \theta_i + c \cos \phi_{i+1}} \\
+\sigma_{i+1} &= \frac{2 - \alpha_i}{1 + (1 - c) \cos \phi_{i+1} + c \cos \theta_i} \\
+\text{where} \;\alpha_i &= a (\sin \theta_i - b \sin \phi_{i+1})(\sin \phi_{i+1} - b \sin \theta_i)(\cos \theta_i - \cos \phi_{i+1})
+\end{align*}
+%%
+and \(a = \sqrt{2}\), \(b = 1/16\), and \(c = (3 - \sqrt{5})/2\).
+These are actually the \emph{relative} lengths so need to be adjusted by a factor of \(d_i/3\).
+Now \(\theta_i\) is the angle relative to the line from \(z_i\) to \(z_{i+1}\), so to get the true angle we need to add back that angle.
+Fortunately, we stored those angles at the start.
+So the control points are:
+%%
+\begin{gather*}
+d_i \rho_i (\cos (\theta_i + \omega_i), \sin (\theta_i + \omega_i))/3 + z_i \\
+- d_i \sigma_{i+1} (\cos(\omega_i - \phi_{i+1}), \sin(\omega_i - \phi_{i+1}))/3 + z_{i+1}
+\end{gather*}
+
+\section{A Piecewise Version of Hobby's Algorithm}
+\label{sec:quick}
+Here we present a variant of Hobby's algorithm.
+One difficulty with Hobby's algorithm is that it works with the path as a whole.
+It is therefore not possible to build up a path piecewise.
+We therefore modify it to correct for this.
+Obviously, the resulting path will be less ``ideal'', but will have the property that adding new points will not affect earlier segments.
+The method we use is to employ Hobby's algorithm on two-{}segment subpaths.
+When applied to a two-{}segment subpath, the algorithm provides two cubic Bezier curves: one from the \(k\)th point to the \(k+1\)st point and the second from the \(k+1\)st to the \(k+2\)nd.
+Of this data, we keep the first segment and use that for the path between the \(k\)th and \(k+1\)st points.
+We also remember the outgoing angle of the first segment and use that as the incoming angle on the next computation (which will involve the \(k+1\)st, \(k+2\)nd, and \(k+3\)rd points).
+The two ends are slightly different to the middle segments.
+On the first segment, we might have no incoming angle.
+On the last segment, we render both pieces.
+This means that for the initial segment, we have a \(2 \times 2\) linear system:
+%%
+\[
+\begin{bmatrix}
+B_0 & C_0 \\
+A_1 & B_1
+\end{bmatrix}
+\Theta = \begin{bmatrix}
+D_0 \\ D_1
+\end{bmatrix}
+\]
+%%
+This has solution:
+%%
+\[
+\Theta = \frac{1}{B_0 B_1 - C_0 A_1} \begin{bmatrix} B_1 & - C_0 \\ -A_1 & B_0 \end{bmatrix} \begin{bmatrix} D_0 \\ D_1 \end{bmatrix} =  \frac{1}{B_0 B_1 - C_0 A_1} \begin{bmatrix} B_1 D_0 - C_0 D_1 \\ B_0 D_1 - A_1 D_0 \end{bmatrix}
+\]
+Now we have the following values for the constants:
+%%
+\begin{align*}
+A_1 &= d_1 \overline{\tau}_2 \overline{\tau}_1^2 \\
+%%
+B_0 &= \tau_0^3 (3 \overline{\tau}_1 - 1) + \chi_0 \overline{\tau}_1^3 \\
+%%
+B_1 &= d_1 \overline{\tau}_2 \overline{\tau}_1^2 (3 \tau_0 - 1) + d_0 \tau_0 \tau_1^2(3 \overline{\tau}_2 - 1) - d_0 \tau_0 \tau_1^2 \frac{\overline{\tau}_2^3 + \chi_2 \tau_1^3 (3 \overline{\tau}_2 - 1)}{\overline{\tau}_2^3 (3 \tau_1 - 1) + \chi_2 \tau_1^3} \\
+%%
+C_0 &= \tau_0^3 + \chi_0 \overline{\tau}_1^3 (3 \tau_0 - 1) \\
+%%
+D_0 &= - (\tau_0^3 + \chi_0 \overline{\tau}_1^3 ( 3 \tau_0 - 1)) \psi_1 \\
+%%
+D_1 &= - d_1 \overline{\tau}_2 \overline{\tau}_1^2 (3 \tau_0 - 1) \psi_1
+\end{align*}
+
+Let us, as we are aiming for simplicity, assume that the tensions and curls are all \(1\).
+Then we have \(A_1 = d_1\), \(B_0 = 3\), \(B_1 = 2 d_1 + 2 d_0 - d_0 = 2 d_1 + d_0\), \(C_0 = 3\), \(D_0 = - 3 \psi_1\), \(D_1 = - 2 d_1 \psi_1\).
+Thus the linear system is:
+%%
+\[
+\begin{bmatrix}
+3 & 3 \\
+d_1 & 2 d_1 + d_0
+\end{bmatrix}
+\Theta = - \psi_1 \begin{bmatrix}
+3 \\ 2 d_1
+\end{bmatrix}
+\]
+%%
+which we can row reduce to:
+%%
+\[
+\begin{bmatrix}
+1 & 1 \\
+0 & d_1 + d_0
+\end{bmatrix}
+\Theta = -\psi_1 \begin{bmatrix}
+1 \\ d_1
+\end{bmatrix}
+\]
+%%
+whence \(\theta_1 = -\psi_1 \frac{d_1}{d_0 + d_1}\) and \(\theta_0 = -\psi_1 - \theta_1 = -\psi_1\frac{d_0 }{d_0 + d_1}\).
+We also compute \(\phi_1 = -\psi_1 - \theta_1 = \theta_0\) and \(\phi_2 = \theta_1\) (in the simple version).
+We use \(\theta_0\) and \(\phi_1\) to compute the bezier curve of the first segment, make a note of \(\theta_1\), and -- assuming there are more segments -- throw away \(\phi_2\).
+
+For the inner segments, we have the system:
+%%
+\[
+\begin{bmatrix}
+1 & 0 \\
+A_1 & B_1
+\end{bmatrix}
+\Theta = \begin{bmatrix}
+\theta_0 \\
+D_1
+\end{bmatrix}
+\]
+%%
+which has the solution \(\theta_1 = (D_1 - A_1 \theta_0)/B_1\).
+The values of the constants in this case are:
+%%
+\begin{align*}
+A_1 &= d_1 \overline{\tau}_2 \overline{\tau}_1^2 \\
+%%
+B_1 &= d_1 \overline{\tau}_2 \overline{\tau}_1^2 (3 \tau_0 - 1) + d_0 \tau_0 \tau_1^2(3 \overline{\tau}_2 - 1) - d_0 \tau_0 \tau_1^2 \frac{\overline{\tau}_2^3 + \chi_2 \tau_1^3 (3 \overline{\tau}_2 - 1)}{\overline{\tau}_2^3 (3 \tau_1 - 1) + \chi_2 \tau_1^3} \\
+%%
+D_1 &= - d_1 \overline{\tau}_2 \overline{\tau}_1^2 (3 \tau_0 - 1) \psi_1
+\end{align*}
+Again, let us consider the simpler case.
+Then \(A_1 = d_1\), \(B_1 = 2 d_1 + d_0\), and \(D_1 = - 2 d_1 \psi_1\).
+Thus \(\theta_1 = (-2 d_1 \psi_1 - d_1 \theta_0)/(2 d_1 + d_0) = - (2 \psi_1 + \theta_0) \frac{d_1}{2 d_1 + d_0}\).
+We compute \(\phi_1 = -\psi_1 - \theta_1 = \frac{- \psi_1 d_0 + \theta_0 d_1}{2 d_1 + d_0}\) and \(\phi_2 = \theta_1\).
+Then we store \(\theta_1\) for the next iteration.
+
+The actual curves are then produced from the angles using the same formulae for the lengths of the control points as in the main algorithm.
+
+At the last stage, we render both segments of the generated curve.
+
+\section{Acknowledgements}
+
+This package began life as an answer to the question \href{http://tex.stackexchange.com/q/54771/86}{Curve through a sequence of points with Metapost and TikZ}.
+Once released upon the unsuspecting world, various questions on the \href{http://tex.stackexchange.com}{TeX-SX} site have prompted new features (and bug-fixes).
+Most of these can be found by looking at the \href{http://tex.stackexchange.com/questions/tagged/hobby}{list of questions tagged ``hobby''} on that site.
+
+
+\begin{thebibliography}{1} \bibitem{MR834054} John~D. Hobby. \newblock Smooth, easy to compute interpolating splines. \newblock {\em Discrete Comput. Geom.}, 1:123--140, 1986. \end{thebibliography}
+
+\end{document}


Property changes on: trunk/Master/texmf-dist/doc/latex/hobby/hobby.tex
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Modified: trunk/Master/texmf-dist/doc/latex/hobby/hobby_code.pdf
===================================================================
(Binary files differ)

Deleted: trunk/Master/texmf-dist/doc/latex/hobby/hobby_doc.tex
===================================================================
--- trunk/Master/texmf-dist/doc/latex/hobby/hobby_doc.tex	2023-09-01 14:59:58 UTC (rev 68136)
+++ trunk/Master/texmf-dist/doc/latex/hobby/hobby_doc.tex	2023-09-01 21:15:20 UTC (rev 68137)
@@ -1,917 +0,0 @@
-\immediate\write18{tex hobby.dtx}
-\documentclass{ltxdoc}
-\usepackage[T1]{fontenc}
-\usepackage{csquotes}
-\usepackage{lmodern}
-\usepackage{tikz}
-\usepackage{pgfplots}
-\usepackage{amsmath}
-\usepackage{fancyvrb}
-\usetikzlibrary{hobby,decorations.pathreplacing}
-\usepackage{listings}
-\usepackage{hyperref}
-\pgfplotsset{compat=1.9}
-\lstloadlanguages{[LaTeX]TeX}
-\lstset{breakatwhitespace=true,breaklines=true,language=TeX}
-
-\EnableCrossrefs
-\CodelineIndex
-\RecordChanges
-
-\newenvironment{example}
-  {\VerbatimEnvironment
-   \begin{VerbatimOut}{example.out}}
-  {\end{VerbatimOut}
-   \begin{center}
-   \setlength{\parindent}{0pt}
-   \fbox{\begin{minipage}{.9\linewidth}
-     \lstset{breakatwhitespace=true,breaklines=true,language=TeX,basicstyle=\small}
-     \lstinputlisting[]{example.out}
-   \end{minipage}}
-   \fbox{\begin{minipage}{.9\linewidth}
-     \centering
-     \input{example.out}
-   \end{minipage}}
-\end{center}
-}
-
-
-
-\tikzset{
-  show curve controls/.style={
-    decoration={
-      show path construction,
-      curveto code={
-        \draw [blue, dashed]
-        (\tikzinputsegmentfirst)    -- (\tikzinputsegmentsupporta)
-        node [at end, draw, solid, red, inner sep=2pt]{};
-        \draw [blue, dashed]
-        (\tikzinputsegmentsupportb) -- (\tikzinputsegmentlast)
-        node [at start, draw, solid, red, inner sep=2pt]{};
-      }
-    },decorate
-  },
-}
-
-%\bibliographystyle{plain}
-
-\renewcommand{\thefootnote}{\fnsymbol{footnote}}
-
-\providecommand*{\url}{\texttt}
-\title{The \textsf{Hobby} package}
-\author{Andrew Stacey \\ \url{loopspace at mathforge.org}}
-\date{\hobbyVersion\ from\ \hobbyDate}
-\begin{document}
-\maketitle
-
-\section{Introduction}
-
-John Hobby's algorithm, \cite{MR834054}, produces a curve through a given set of points.
-The curve is constructed as a list of cubic B\'ezier curves with endpoints at subsequent points in the list.
-The parameters of the curves are chosen so that the joins are ``smooth''.
-The algorithm was devised as part of the MetaPost program.
-
-TikZ/PGF has the ability to draw a curve through a given set of points but its algorithm is somewhat simpler than Hobby's and consequently does not produce as aesthetically pleasing curve as Hobby's algorithm does.
-This package implements Hobby's algorithm in \TeX{} so that TikZ/PGF can make use of it and thus produce nicer curves through a given set of points.
-
-Hobby's algorithm allows for considerable customisation in that it can take into account various parameters.
-These are all allowed in this implementation.
-
-There is also a ``quick'' version presented here.
-This is a modification of Hobby's algorithm with the feature that any point only influences a finite number (in fact, two) of the previous segments (in Hobby's algorithm the influence of a point dies out exponentially but never completely).
-This is achieved by applying Hobby's algorithm to subpaths.
-The resulting path produced with this ``quick'' version is not as ideal as that produced by Hobby's full algorithm, but is still much better than that produced by the \Verb+plot[smooth]+ method in TikZ/PGF, as can be seen in Figure~\ref{fig:comparison}.
-As this is intended as a simpler method, it does not (at present) admit the same level of customisation as the full implementation.
-The ``quick'' algorithm is described in full in Section~\ref{sec:quick}.
-
-The full algorithm is implemented in \LaTeX3 with no reference to TikZ or PGF.
-It makes extensive use of the \Verb+fp+ and \Verb+prop+ libraries for the computation steps.
-The TikZ library is simply a wrapper that takes the user's input, converts it into the right format for the \LaTeX3 code, and then calls that code to generate the path.
-The ``quick'' version does not use \LaTeX3 and relies instead on the \Verb+PGFMath+ library for the computation.
-
-
-Figure~\ref{fig:comparison} is a comparison of the three methods.
-The red curve is drawn using Hobby's algorithm.
-The blue curve is drawn with the \Verb+plot[smooth]+ method from TikZ/PGF.
-The green curve uses the ``quick'' version.
-Figure~\ref{fig:metapost} compares the implementation with that given by MetaPost.
-
-\begin{figure}
-\centering
-\begin{tikzpicture}[scale=.5]
-\draw[red,line width=5pt] (0,0) to[curve through={(6,4) .. (4,9) .. (1,7)}] (3,5);
-\draw[ultra thick,blue] plot[smooth] coordinates {(0,0) (6,4) (4,9) (1,7) (3,5)};
-\draw[green,line width=2pt] (0,0) to[quick curve through={(6,4) (4,9)  (1,7)}] (3,5);
-\end{tikzpicture}
-\caption{Comparison of the three algorithms}
-\label{fig:comparison}
-\end{figure}
-
-
-\begin{figure}
-\centering
-\begin{tikzpicture}[scale=.5]
-\draw[scale=.1,postaction=show curve controls,line width=1mm,red] (0,0)
-.. controls (26.76463,-1.84543) and (51.4094,14.58441) .. (60,40)
-.. controls (67.09875,61.00188) and (59.76253,84.57518) .. (40,90)
-.. controls (25.35715,94.01947) and (10.48064,84.5022) .. (10,70)
-.. controls (9.62895,58.80421) and (18.80421,49.62895) .. (30,50);
-\fill[green] (0,0) circle[radius=2pt]
-(6,4) circle[radius=2pt]
-(4,9) circle[radius=2pt]
-(1,7) circle[radius=2pt]
-(3,5) circle[radius=2pt];
-\draw[postaction=show curve controls,thick] (0,0) to[curve through={(6,4) .. (4,9) .. (1,7)}] (3,5);
-\begin{scope}[xshift=10cm]
-\draw[scale=.1,postaction=show curve controls,line width=1mm,red] (0,0)
-.. controls (5.18756,-26.8353) and (60.36073,-18.40036) .. (60,40)
-.. controls (59.87714,59.889) and (57.33896,81.64203) .. (40,90)
-.. controls (22.39987,98.48387) and (4.72404,84.46368) .. (10,70)
-.. controls (13.38637,60.7165) and (26.35591,59.1351) .. (30,50)
-.. controls (39.19409,26.95198) and (-4.10555,21.23804) .. (0,0); % 
-\fill[green] (0,0) circle[radius=2pt]
-(6,4) circle[radius=2pt]
-(4,9) circle[radius=2pt]
-(1,7) circle[radius=2pt]
-(3,5) circle[radius=2pt];
-\draw[postaction=show curve controls,thick] (0,0) to[closed,curve through={(6,4) .. (4,9) .. (1,7)}] (3,5);
-\end{scope}
-\end{tikzpicture}
-\caption{Hobby's algorithm in TikZ overlaying the output of MetaPost}
-\label{fig:metapost}
-\end{figure}
-
-\section{Usage}
-The package is provided in form of a TikZ library.
-It can be loaded with
-\begin{verbatim}
-\usetikzlibrary{hobby}
-\end{verbatim}
-
-\textbf{Warning}: This package makes extensive use of \LaTeX3.
-On occasion, updates to \LaTeX3 packages have resulted in this package behaving oddly or not working at all.
-The most up to date version of this package can be obtained from the \href{http://bazaar.launchpad.net/~tex-sx/tex-sx/development/files}{TeX-SX Launchpad page} (download \Verb+hobby.dtx+ and run \Verb+tex hobby.dtx+ to generate the files).
-Often, such issues are reported on the \href{http://tex.stackexchange.com}{TeX-SX} site and workarounds quickly found so it is worth checking there as well.
-
-\bigskip
-
-There are a variety of ways of specifying the data to the algorithm to generate the curve.
-
-\subsection{As a \textsf{to path}.}
-
-The key \Verb+curve through={<points>}+ installs a \Verb+to path+ which draws a smooth curve through the given points.
-The points should be specified as a list which can be optionally separated by dots.
-The purpose of allowing the dots is to make it simpler to switch between the \Verb+to path+ method and the \Verb+shortcut+ method (described in Section~\ref{sec:shortcut}).
-However, note that the two methods are not completely synonymous due to how one can specify options so care must still be taken when switching.
-
-\begin{example}
-\begin{tikzpicture}[scale=.5]
-\draw (0,0) to[curve through={(6,4) .. (4,9) .. (1,7)}] (3,5);
-\end{tikzpicture}
-\end{example}
-
-\begin{example}
-\begin{tikzpicture}[scale=.5]
-\draw (0,0) to[curve through={(6,4) (4,9) (1,7)}] (3,5);
-\end{tikzpicture}
-\end{example}
-
-There is a corresponding key \Verb+quick curve through={<points>}+ which uses the ``quick'' algorithm.
-Again, the dots are optional.
-
-\begin{example}
-\begin{tikzpicture}[scale=.5]
-\draw (0,0) to[quick curve through={(6,4) .. (4,9) .. (1,7)}] (3,5);
-\end{tikzpicture}
-\end{example}
-
-\subsection{The \textsf{shortcut} Method}
-\label{sec:shortcut}
-
-There is also the facility to subvert TikZ's path processor and define curves simply using the \Verb+..+ separator between points.
-Note that this relies on something a little special in TikZ: the syntax \Verb+(0,0) .. (2,3)+ is currently detected and processed but there is no action assigned to that syntax.
-As a later version of TikZ may assign some action to that syntax, this package makes its override optional via the key \Verb+use Hobby shortcut+ (which can be set globally if so desired).
-
-\begin{example}
-\begin{tikzpicture}[scale=.5,use Hobby shortcut]
-\draw (-3,0) -- (0,0) .. (6,4) .. (4,9) .. (1,7) .. (3,5) -- ++(2,0);
-\end{tikzpicture}
-\end{example}
-
-\begin{example}
-\begin{tikzpicture}[scale=.5,use quick Hobby shortcut]
-\draw (-3,0) -- (0,0) .. (6,4) .. (4,9) .. (1,7) .. (3,5) -- ++(2,0);
-\end{tikzpicture}
-\end{example}
-
-\subsection{The \textsf{Plot Handler} Method}
-
-The algorithms can also be used via the \Verb+plot handler+ syntax.
-This library registers three plot handlers: \Verb+hobby+, \Verb+closed hobby+, and \Verb+quick hobby+.
-The first is an open curve through the points using the full algorithm, the second is a closed curve, and the third uses the quick algorithm (and is thus an open curve).
-
-\begin{example}
-\tikz[smooth] \draw plot coordinates {(0,0) (1,1) (2,0) (3,1) (2,1) (10:2cm)};
-
-\tikz[hobby] \draw plot coordinates {(0,0) (1,1) (2,0) (3,1) (2,1) (10:2cm)};
-
-\tikz[closed hobby] \draw plot coordinates {(0,0) (1,1) (2,0) (3,1) (2,1) (10:2cm)};
-
-\tikz[quick hobby] \draw plot coordinates {(0,0) (1,1) (2,0) (3,1) (2,1) (10:2cm)};
-\end{example}
-
-This has the side effect that these can be used with the \Verb+pgfplots+ package.
-However, the Hobby algorithm is designed to draw a curve in 2D-{}space and does not take into account the fact that when plotting a graph then the two dimensions are treated differently.
-
-\begin{example}
-\begin{tikzpicture}
-\begin{axis}
-\addplot +[smooth] {rnd};
-\addplot +[hobby] {rnd};
-\end{axis}
-\end{tikzpicture}
-\end{example}
-
-\subsection{Basic Level PGF Commands}
-
-(Suggested by the question \href{http://tex.stackexchange.com/q/159896/86}{How to combine Hobby paths with PGF Basic Layer commands?} on TeX-SX.)
-
-In some circumstances, it is convenient to bypass TikZ and use more basic PGF commands for building a path.
-It is possible to add a path built using Hobby's algorithm in this fashion.
-The commands are:
-
-\begin{itemize}
-\item \Verb+\pgfpathhobby+ to initialise the path.
-If this is followed by a braced group then the contents of that are taken as options to the algorithm.
-
-\item \Verb+\pgfpathhobbypt{<pgf point specification>}+ to add a point to the path.
-If this is followed by a braced group then the contents of that are taken as options for that point.
-
-\item \Verb+\pgfpathhobbyend+ finalises the path.
-This applies the algorithm to the set of specified points and adds it to the current path.
-\end{itemize}
-
-\begin{example}
-\begin{tikzpicture}
-\pgfpathmoveto{\pgfpoint{0}{0}}
-\pgfpathlineto{\pgfpoint{1cm}{0}}
-\pgfpathhobby{closed=true}
-\pgfpathhobbypt{\pgfpoint{1cm}{2cm}}{tension in=2}
-\pgfpathhobbypt{\pgfpoint{2cm}{1cm}}
-\pgfpathhobbypt{\pgfpoint{3cm}{0cm}}
-\pgfpathhobbyend
-\pgfusepath{stroke}
-\end{tikzpicture}
-\end{example}
-
-\section{Customisation}
-
-There are various ways to customise the path generated by the Hobby algorithms.
-The full algorithm has a variety of parameters which can be varied to produce different paths through the same points.
-These vary from specifying that the path be open or closed, to specifying ``tensions'' at each point to change how the path approaches or leaves it.
-
-\subsection{Algorithm Customisations}
-
-The main algorithm (i.e., not the ``quick'' variant) can deal with open or closed paths, it is possible to vary the ``tensions'' between the specified points of the paths, and for an open path it is possible to specify the incoming and outgoing angles either directly or via certain ``curl'' parameters.
-When using the \Verb+to path+ specification, the parameters can be specified before or after the \Verb+curve through+ key or as options to the coordinates.
-When using the \Verb+shortcut+ specification, the parameters can be given on the path or on coordinates.
-
-On occasion, it is ambiguous which curve an option belongs to.
-This is most likely if a coordinate happens to belong to two curves, or if a coordinate is parsed before TikZ knows that it is constructing a curve using this library.
-The simplest solution is to move the option to a place where there is no ambiguity.
-Other solutions to this problem will be detailed later.
-
-Let us start with the customisations to the algorithm.
-
-\begin{itemize}
-\item Basic curve.
-\begin{example}
-\begin{tikzpicture}
-\draw[postaction=show curve controls]
-(0,0) to[curve through={(1,.5) .. (2,0) .. (3,.5)}] (4,0);
-\draw[xshift=5cm,use Hobby shortcut,postaction=show curve controls]
-(0,0) .. (1,.5) .. (2,0) .. (3,.5) .. (4,0);
-\end{tikzpicture}
-\end{example}
-
-\item The path can be open, as above, or closed.
-%%
-\begin{example}
-\begin{tikzpicture}[scale=.5]
-\draw[postaction=show curve controls]
-(0,0) to[closed,curve through={(1,.5) .. (2,0) .. (3,.5)}] (4,0);
-\draw[xshift=5cm,use Hobby shortcut,postaction=show curve controls]
-([closed]0,0) .. (1,.5) .. (2,0) .. (3,.5) .. (4,0);
-\end{tikzpicture}
-\end{example}
-
-\item Specifying the angle at which the curve goes \emph{out} and at which it comes \emph{in}.
-The angles given are absolute.
-\begin{example}
-\begin{tikzpicture}
-\draw[postaction=show curve controls]
-(0,0) to[out angle=0,in angle=180,curve through={(1,.5) .. (2,0) .. (3,.5)}] (4,0);
-\draw[xshift=5cm,use Hobby shortcut,postaction=show curve controls]
-([out angle=0,in angle=180]0,0) .. (1,.5) .. (2,0) .. (3,.5) .. (4,0);
-\end{tikzpicture}
-\end{example}
-
-\item Applying tension as the curve comes in to a point.
-\begin{example}
-\begin{tikzpicture}
-\draw[postaction=show curve controls]
-(0,0) to[curve through={(1,.5) .. ([tension in=2]2,0) .. (3,.5)}] (4,0);
-\draw[xshift=5cm,use Hobby shortcut,postaction=show curve controls]
-(0,0) .. (1,.5) .. ([tension in=2]2,0) .. (3,.5) .. (4,0);
-\end{tikzpicture}
-\end{example}
-
-\item Applying the same tension as a curve comes in and goes out of a point.
-\begin{example}
-\begin{tikzpicture}
-\draw[postaction=show curve controls]
-(0,0) to[curve through={(1,.5) .. ([tension=2]2,0) .. (3,.5)}] (4,0);
-\end{tikzpicture}
-\end{example}
-
-\item Specifying the \emph{curl} parameters.
-\begin{example}
-\begin{tikzpicture}
-\draw[postaction=show curve controls]
-(0,0) to[in curl=.1,out curl=3,curve through={(1,.5) .. (2,0) .. (3,.5)}] (4,0);
-\draw[xshift=5cm,use Hobby shortcut,postaction=show curve controls]
-(0,0) .. ([in curl=.1,out curl=3]1,.5) .. (2,0) .. (3,.5) .. (4,0);
-\end{tikzpicture}
-\end{example}
-\end{itemize}
-
-\subsection{Edge Cases}
-
-Angles are constrained to lie in the interval \((-\pi,\pi]\).
-This can introduce edge cases as there is a point where we have to compare an angle with \(-\pi\) and if it is equal, add \(2 \pi\).
-This will occur if the path ``doubles back'' on itself as in the next example.
-By nudging the repeated point slightly, the behaviour changes drastically.
-
-\begin{example}
-\begin{tikzpicture}[use Hobby shortcut]
-\draw (0,0) .. (1,0) .. (0,0) .. (0,-1);
-\draw[xshift=2cm] (0,0) .. (1,0) .. (0,0.1) .. (0,-1);
-\draw[xshift=4cm] (0,0) .. (1,0) .. (0,-0.1) .. (0,-1);
-\end{tikzpicture}
-\end{example}
-
-Due to the precision of the computations, it is not possible to always get this test correct.
-The simplest solution is to nudge the repeated point in one direction or the other.
-Experimenting shows that the ``nudge factor'' can be extremely small (note that it will be proportional to the distance between the specified points).
-It is best to nudge it in the direction most normal to the line between the specified points as the goal is to nudge the difference of the angles.
-An alternative solution is to add an additional point for the curve to go through.
-\begin{example}
-\begin{tikzpicture}[use Hobby shortcut]
-\draw (0,0) .. (1,0) .. (0,0) .. (0,-1);
-\draw[xshift=2cm] (0,0) .. (1,0) .. (0,0.002) .. (0,-1);
-\draw[xshift=4cm] (0,0) .. (1,0) .. (0,-0.002) .. (0,-1);
-\end{tikzpicture}
-\end{example}
-
-Lastly, it is possible to add an \Verb+excess angle+ key to a coordinate.
-This will add the corresponding multiple of \(2\pi\) to the angle difference.
-\begin{example}
-\begin{tikzpicture}[use Hobby shortcut]
-\draw (0,0) .. (1,0) .. (0,0) .. (0,-1);
-\draw[xshift=2cm] (0,0) .. ([excess angle=1]1,0) .. (0,0) .. (0,-1);
-\draw[xshift=4cm] (0,0) .. ([excess angle=-1]1,0) .. (0,0) .. (0,-1);
-\end{tikzpicture}
-\end{example}
-
-Although this is intended to be an integer, no check is done and so some quite odd curves can result from changing this parameter.
-
-\subsection{Reusing Paths}
-
-Although the (full) algorithm has good theoretical computation time, using \TeX\ for its implementation does not provide for fast runs.
-The externalisation library of TikZ/PGF can be used to save whole pictures, but it can be useful to save a generated path within a single \Verb+tikzpicture+ for later use within that same picture.
-The implementation allows for this by separating the generation of the path from its use.
-
-\begin{example}
-\begin{tikzpicture}
-\draw[line width=3mm,red,use Hobby shortcut,save Hobby path={saved}] (0,0) .. (1,1) .. (2,0);
-\draw[xshift=2cm,ultra thick,yellow] (0,0) [restore and use Hobby path={saved}{}];
-\end{tikzpicture}
-\end{example}
-%
-
-Note that the key \Verb+restore and use Hobby path+ is given \emph{after} the initial \Verb+(0,0)+.
-This is because by default the path generated by the Hobby algorithm does not start with an explicit \Verb+moveto+ since that is the standard behaviour of all of PGF's path construction macros.
-So the \Verb+(0,0)+ ensures that our path is well-{}formed by issuing an initial \Verb+moveto+.
-An alternative would be to use the key \Verb+disjoint+ which does add an initial \Verb+moveto+.
-
-\begin{example}
-\begin{tikzpicture}
-\draw[line width=3mm,red,use Hobby shortcut,save Hobby path={saved}] (0,0) .. (1,1) .. (2,0);
-\draw[xshift=2cm,ultra thick,yellow,restore and use Hobby path={saved}{disjoint}];
-\end{tikzpicture}
-\end{example}
-%
-
-An example of where this is useful is in drawing knot diagrams.
-When so doing, it is sometimes convenient to draw a path (or segment of a path) twice in order to get the under/over crossings correct.
-For this situation, it can be useful to designate certain parts of the path as \Verb+blank+, whereby we mean to redraw them later.
-The point of a blank segment of a curve is that it is still taken into account when computing the algorithm but is left blank when it comes to rendering.
-A path can then be redrawn with the blank/non-{}blank segments reversed.
-As it might be desired to have only some blank segments drawn the second time, there are two types of blank.
-Only a \Verb+soft+ blank will be reversed in these circumstances.
-
-\begin{example}
-\begin{tikzpicture}[use Hobby shortcut,line width=1mm,rotate=90]
-\draw[blue,save Hobby path={left}]
- ([out angle=90,in angle=-90]1,0) .. (1,1) .. ([blank=soft]0,2) .. (1,3) .. (1,4);
-\draw[red] ([out angle=90,in angle=-90]0,0) .. (0,1) .. (1,2) .. (0,3) .. (0,4);
-\draw[blue,restore and use Hobby path={left}{disjoint,invert soft blanks}];
-\end{tikzpicture}
-\end{example}
-%
-
-This can be taken a step further.
-The generated data can be saved to the \Verb+aux+ file and read back in, avoiding the need to regenerate it on each run.
-To engage this facility, the Hobby path has to be named (via \Verb+save Hobby path+) and the key \Verb+Hobby externalise+ (or \Verb+Hobby externalize+) must be given in a context that applies (such as on the path or on the surrounding scope).
-
-\bigskip
-
-
-The relevant keys are the following.
-
-\begin{itemize}
-\item \Verb+use previous Hobby path[=<options>]+.
-This (re)uses the previously generated Hobby path.
-As all the data is globally stored, this can technically be in a different \Verb+tikzpicture+.
-The \Verb+<options>+ will be applied, in so far as they are options that can be applied after the algorithm has run.
-
-\item \Verb+save Hobby path=<name>+.
-Saves a path for later use.
-The path is saved in a global macro so can be reused in another picture.
-
-\item \Verb+restore Hobby path=<name>+.
-This restores the named Hobby path (if it exists).
-It does not \emph{use} it.
-After this key, \Verb+use previous Hobby path+ will use the restored path.
-
-\item \Verb+restore and use Hobby path={<name>}{<options>}+.
-This restores the named path and uses it with \Verb+<options>+ applied.
-
-\item \Verb+Hobby externalise+ or \Verb+Hobby externalize+.
-This puts in place the code for saving the generated data to the \Verb+aux+ file.
-On subsequent runs, it uses the saved data rather than the current data.
-For a curve to make use of this, it has to be named via the \Verb+save Hobby path+ key.
-So to regenerate the data, either delete the \Verb+aux+ file, remove the \Verb+save Hobby path+ key for one compilation run, or issue the command \Verb+\HobbyDisableAux+ which disables writing paths to the \Verb+aux+ file (note that the paths will be regenerated on the run \emph{after} the first run with this command issued).
-\end{itemize}
-
-The options that can be applied are those that affect the rendering of the curve but not its generation.
-When the curve is rendered (or \emph{used}, in the above parlance), \TeX\ steps along the coordinates of the generated curve and carries out an action for each piece.
-This action can be modified after the curve has been generated.
-The action will be one of:
-%
-\begin{itemize}
-\item Move to the end point (ignoring the control points).
-\item Draw a Bezier curve to the end point through the control points.
-\item Draw a Bezier curve to the end point through the control points and then move to the end point.
-\end{itemize}
-%
-The last is subtle: the move doesn't actually go anywhere but it ``breaks'' the curve at the designated point.
-In particular, a later \Verb+cycle+ would return to this point (or a later break) rather than to the start of the curve.
-
-These actions are triggered by the keys \Verb+blank+ and \Verb+break+.
-Each should be specified to the coordinate at the \emph{end} of the segment under consideration.
-The \Verb+blank+ key can be given the argument \Verb+soft+.
-The effect of this is seen when the key \Verb+invert soft blanks+ is used.
-This swaps the drawing action so that non-{}blank segments are skipped and \emph{soft} blanks are drawn.
-Non-{}soft-{}blank segments are still not drawn.
-
-\begin{example}
-\begin{tikzpicture}[use Hobby shortcut]
-\draw (0,0) .. (1,1) .. ([blank=soft]2,0) .. (3,1) .. ([blank]4,0) .. (5,1);
-\draw[red,use previous Hobby path={invert soft blanks,disjoint}];
-\end{tikzpicture}
-\end{example}
-%
-
-As a more practical application, consider the following rendering of a trefoil knot.
-
-\begin{example}
-\begin{tikzpicture}[
-  use Hobby shortcut,
-  every path/.style={
-    line width=1mm,
-    white,
-    double=red,
-    double distance=.5mm
-  }
-]
-\draw ([closed]0,2) .. ([blank=soft]210:.5) .. (-30:2) ..
-([blank=soft]0,.5) .. (210:2) .. ([blank=soft]-30:.5);
-\draw[use previous Hobby path={invert soft blanks,disjoint}];
-\end{tikzpicture}
-\end{example}
-%
-
-This could easily be generalised using the \Verb+\foreach+ command, as demonstrated in the next code.
-
-\begin{example}
-\begin{tikzpicture}[
-  use Hobby shortcut,
-  every path/.style={
-    line width=1mm,
-    white,
-    double=red,
-    double distance=.5mm
-  }
-]
-\def\nfoil{9}
-\draw ([closed]0,2)
-\foreach \k in {1,...,\nfoil} {
-  .. ([blank=soft]90+360*\k/\nfoil-180/\nfoil:-.5) .. (90+360*\k/\nfoil:2)
-};
-\draw[use previous Hobby path={invert soft blanks,disjoint}];
-\end{tikzpicture}
-\end{example}
-%
-
-\subsection{Breaking the Path}
-
-One issue with the shortcut notation is that it is not possible (using this notation) to have two sets of curves following directly on from each other because there is no clear demarcation of the boundary.
-To make this possible, there is a key \Verb+Hobby action+, which installs an action to be taken after the point has been processed.
-The general key \Verb+Hobby action={code}+ can install arbitrary code.
-Probably the more useful variant is \Verb+Hobby finish+ which runs the algorithm on the points gathered so far.
-An example of the use of this is to make it possible to specify tangencies at certain points.
-Technically, once a tangent direction has been specified, the Hobby algorithm splits the set of points there and works on each piece separately.
-The following key implements this, the technicalities are due to the fact that the tangent angle has to be used twice: once to specify the angle of the path coming in to that point and once to specify the angle of the path coming out.
-Note that specifying the tangent vector at every point means that the algorithm is not actually being used.
-However, Hobby's formulae for the lengths of the control points is still being used.
-
-\begin{example}
-\begin{tikzpicture}[
-    use Hobby shortcut,
-  tangent/.style={%
-    in angle={(180+#1)},
-    Hobby finish,
-    designated Hobby path=next,
-    out angle=#1,
-  },
-]
-\draw[help lines] (-5,-5) grid (5,5);
-\draw (-5,0) -- (5,0) (0,-5) -- (0,5);
-\draw[thick] (-5,2) .. ([tangent=0]-3,3) .. (-1,1) .. (0,-1.3) .. %
-([tangent=0]1,-2) .. ([tangent=45]2,-1.5) .. ([tangent=0]3,-2) .. (5,-4);
-\end{tikzpicture}
-\end{example}
-%
-
-
-
-\section{Implementing Hobby's Algorithm}
-We start with a list of \(n+1\) points, \(z_0, \dotsc, z_n\).
-The base code assumes that these are already stored in two arrays\footnote{Arrays are thinly disguised property lists.}: the \(x\)--coordinates in \Verb+\l_hobby_points_x_array+ and the \(y\)--coordinates in \Verb+\l_hobby_points_y_array+.
-As our arrays are \(0\)--indexed, the actual number of points is one more than this.
-For a closed curve, we have \(z_n = z_0\)\footnote{Note that there is a difference between a closed curve and an open curve whose endpoints happen to overlap.}.
-For closed curves it will be convenient to add an additional point at \(z_1\): thus \(z_{n+1} = z_1\).
-This makes \(z_n\) an internal point and makes the algorithms for closed paths and open paths agree longer than they would otherwise.
-The number of apparent points is stored as \Verb+\l_hobby_npoints_int+.
-Thus for an open path, \Verb+\l_hobby_npoints_int+ is \(n\), whilst for a closed path, it is \(n+1\)\footnote{In fact, we allow for the case where the user specifies a closed path but with \(z_n \ne z_0\).
-In that case, we assume that the user meant to repeat \(z_0\).
-This adds another point to the list.}.
-Following Hobby, let us write \(n'\) for \(n\) if the path is open and \(n+1\) if closed.
-From this we compute the distances and angles between successive points, storing these again as arrays.
-These are \Verb+\l_hobby_distances_array+ and \Verb+\l_hobby_angles_array+.
-The term indexed by \(k\) is the distance (or angle) of the line between the \(k\)th point and the \(k+1\)th point.
-For the internal nodes\footnote{Hobby calls the specified points \emph{knots}.}, we store the difference in the angles in \Verb+\l_hobby_psi_array+.
-The \(k\)th value on this is the angle subtended at the \(k\)th node.
-This is thus indexed from \(1\) to \(n'-1\).
-The bulk of the work consists in setting up a linear system to compute the angles of the control points.
-At a node, say \(z_i\), we have various pieces of information:
-\begin{enumerate}
-\item The angle of the incoming curve, \(\phi_i\), relative to the straight line from \(z_{i-1}\) to \(z_i\)
-\item The angle of the outgoing curve, \(\theta_i\), relative to the straight line from \(z_i\) to \(z_{i+1}\)
-\item The tension of the incoming curve, \(\overline{\tau}_i\)
-\item The tension of the outgoing curve, \(\tau_i\)
-\item The speed of the incoming curve, \(\sigma_i\)
-\item The speed of the outgoing curve, \(\rho_i\)
-\end{enumerate}
-The tensions are known at the start.
-The speeds are computed from the angles.
-Thus the key thing to compute is the angles.
-This is done by imposing a ``mock curvature'' condition.
-The formula for the mock curvature is:
-%%
-\[
-\hat{k}(\theta,\phi,\tau,\overline{\tau}) = \tau^2 \left( \frac{2(\theta + \phi)}{\overline{\tau}} - 6\theta\right)
-\]
-%%
-and the condition that the mock curvatures have to satisfy is that at each \emph{internal} node, the curvatures must match:
-%
-\[
-\hat{k}(\phi_i,\theta_{i-1},\overline{\tau}_i,\tau_{i-1})/d_{i-1} = \hat{k}(\theta_i,\phi_{i+1},\tau_i,\overline{\tau}_{i+1})/d_i.
-\]
-%%
-Substituting in yields:
-%%
-\[
-\frac{\overline{\tau}_i^2}{d_{i-1}} \left( \frac{2(\phi_i + \theta_{i-1})}{\tau_{i-1}} - 6\phi_i\right) = \frac{\tau_i^2}{d_i} \left( \frac{2(\theta_i + \phi_{i+1})}{\overline{\tau}_{i+1}} - 6\theta_i \right).
-\]
-%%
-Let us rearrange that to the following:
-%%
-\begin{align*}
-d_i \overline{\tau}_{i+1} \overline{\tau}_i^2 &\theta_{i-1} \\
-%%
-{}+
-d_i \overline{\tau}_{i+1} \overline{\tau}_i^2 (1 - 3 \tau_{i-1}) &\phi_i \\
-%%
-{}-
-d_{i-1} \tau_{i-1} \tau_i^2 (1 - 3 \overline{\tau}_{i+1}) &\theta_i \\
-%%
-{}-
-d_{i-1} \tau_{i-1} \tau_i^2 &\phi_{i+1} \\
-%%
-&\qquad =
-0
-\end{align*}
-%%
-For both open and closed paths this holds for \(i=1\) to \(i=n' - 1\).
-We also have the condition that \(\theta_i + \phi_i = -\psi_i\) where \(\psi_i\) is the angle subtended at a node by the lines to the adjacent nodes.
-This holds for the internal nodes\footnote{Recall that by dint of repetition, all nodes are effectively internal for a closed path.}.
-Therefore for \(i=1\) to \(n'-1\) the above simplifies to the following:
-%
-\begin{align*}
-d_i \overline{\tau}_{i+1} \overline{\tau}_i^2 &\theta_{i-1} \\
-{}+
-(d_i \overline{\tau}_{i+1} \overline{\tau}_i^2 (3 \tau_{i-1} - 1)
-{}+
-d_{i-1} \tau_{i-1} \tau_i^2 (3 \overline{\tau}_{i+1} - 1)) &\theta_i \\
-{}+
-d_{i-1} \tau_{i-1} \tau_i^2 & \theta_{i+1} \\
-=
-- d_i \overline{\tau}_{i+1} \overline{\tau}_i^2 (3 \tau_{i-1} - 1) &\psi_i \\
-{}- d_{i-1} \tau_{i-1} \tau_i^2& \psi_{i+1}
-\end{align*}
-For an open path we have two more equations.
-One involves \(\theta_0\).
-The other is the above for \(i = n'-1 = n-1\) with additional information regarding \(\psi_n\).
-It may be that one or either of \(\theta_0\) or \(\phi_n\) is specified in advance.
-If so, we shall write the given values with a bar: \(\overline{\theta}_0\) and \(\overline{\phi}_n\).
-In that case, the first equation is simply setting \(\theta_0\) to that value and the last equation involves substituting the value for \(\phi_n\) into the above.
-If not, they are given by formulae involving ``curl'' parameters \(\chi_0\) and \(\chi_n\) and result in the equations:
-%
-\begin{align*}
-\theta_0 &= \frac{\tau_0^3 + \chi_0 \overline{\tau}_1^3(3 \tau_0 - 1)}{\tau_0^3(3 \overline{\tau}_1 - 1) + \chi_0 \overline{\tau}_1^3} \phi_1 \\
-\phi_n &= \frac{\overline{\tau}_n^3 + \chi_n \tau_{n-1}^3(3 \overline{\tau}_n - 1)}{\overline{\tau}_n^3(3 \tau_{n-1} - 1) + \chi_n \tau_{n-1}^3} \theta_{n-1}
-\end{align*}
-%%
-Using \(\phi_1 = - \psi_1 - \theta_1\), the first rearranges to:
-%%
-\[
-(\tau_0^3(3 \overline{\tau}_1 - 1) + \chi_0 \overline{\tau}_1^3) \theta_0 + (\tau_0^3 + \chi_0 \overline{\tau}_1^3(3 \tau_0 - 1)) \theta_1 = - (\tau_0^3 + \chi_0 \overline{\tau}_1^3(3 \tau_0 - 1)) \psi_1.
-\]
-%%
-The second should be substituted in to the general equation with \(i = n-1\).
-This yields:
-%%  
-\begin{align*}
-d_{n-1} \overline{\tau}_{n} \overline{\tau}_{n-1}^2 &\theta_{n-2} \\
-{}+
-\big(d_{n-1} \overline{\tau}_{n} \overline{\tau}_{n-1}^2 (3 \tau_{n-2} - 1)
-{}+
-d_{n-2} \tau_{n-2} \tau_{n-1}^2 (3 \overline{\tau}_{n} - 1) \\
-{}- d_{n-2} \tau_{n-2} \tau_{n-1}^2  \frac{\overline{\tau}_n^3 + \chi_n \tau_{n-1}^3(3 \overline{\tau}_n - 1)}{\overline{\tau}_n^3(3 \tau_{n-1} - 1) + \chi_n \tau_{n-1}^3}\big) & \theta_{n-1} \\
-=
-- d_{n-1} \overline{\tau}_{n} \overline{\tau}_{n-1}^2 (3 \tau_{n-2} - 1) &\psi_{n-1}
-\end{align*}
-%%
-This gives \(n'\) equations in \(n'\) unknowns (\(\theta_0\) to \(\theta_{n-1}\)).
-The coefficient matrix is tridiagonal.
-It is more natural to index the entries from \(0\).
-Let us write \(A_i\) for the subdiagonal, \(B_i\) for the main diagonal, and \(C_i\) for the superdiagonal.
-Let us write \(D_i\) for the target vector.
-Then for an open path we have the following formulae:
-%%
-\begin{align*}
-A_i &= d_i \overline{\tau}_{i+1} \overline{\tau}^2_i \\
-B_0 &= \begin{cases}
-1 & \text{if}\; \overline{\theta}_0\; \text{given} \\
-\tau_0^3(3 \overline{\tau}_1 - 1) + \chi_0 \overline{\tau}^3_1 & \text{otherwise}
-\end{cases} \\
-B_i &= d_i \overline{\tau}_{i+1} \overline{\tau}_i^2 (3 \tau_{i-1} -1) + d_{i-1} \tau_{i-1} \tau_i^2(3 \overline{\tau}_{i+1} - 1) \\
-B_{n-1} &= \begin{cases} d_{n-1} \overline{\tau}_{n} \overline{\tau}_{n-1}^2 (3 \tau_{n-2} - 1) + d_{n-2} \tau_{n-2} \tau_{n-1}^2(3 \overline{\tau}_{n} - 1) & \text{if}\; \overline{\phi}_n\; \text{given} \\
-d_{n-1} \overline{\tau}_{n} \overline{\tau}_{n-1}^2 (3 \tau_{n-2} - 1) + d_{n-2} \tau_{n-2} \tau_{n-1}^2(3 \overline{\tau}_{n} - 1)
-\\
-- d_{n-2} \tau_{n-2} \tau_{n-1}^2  \frac{\overline{\tau}_n^3 + \chi_n \tau_{n-1}^3(3 \overline{\tau}_n - 1)}{\overline{\tau}_n^3(3 \tau_{n-1} - 1) + \chi_n \tau_{n-1}^3}) & \text{otherwise}
-\end{cases} \\
-C_0 &= \begin{cases}
-0 & \text{if}\; \overline{\theta}_0\; \text{given} \\
-\tau_0^3 + \chi_0 \overline{\tau}_1^3(3\tau_0 - 1) & \text{otherwise}
-\end{cases} \\
-C_i &= d_{i-1} \tau_{i-1} \tau_i^2 \\
-D_0 &= \begin{cases}
-\overline{\theta}_0 & \text{if}\; \overline{\theta}_0\; \text{given} \\
-- (\tau_0^3 + \chi_0 \overline{\tau}_1^3(3 \tau_0 - 1)) \psi_1 & \text{otherwise}
-\end{cases} \\
-D_i &= - d_i \overline{\tau}_{i+1} \overline{\tau}_i^2 (3 \tau_{i-1} - 1) \psi_i
-- d_{i-1} \tau_{i-1} \tau_i^2 \psi_{i+1} \\
-D_{n-1} &= \begin{cases}
-- d_{n-1} \overline{\tau}_{n} \overline{\tau}_{n-1}^2 (3 \tau_{n-2} - 1) \psi_{n-1} - d_{n-2} \tau_{n-2} \tau_{n-1}^2 \overline{\phi}_n & \text{if}\; \overline{\phi}_n\; \text{given} \\
-- d_{n-1} \overline{\tau}_{n} \overline{\tau}_{n-1}^2 (3 \tau_{n-2} - 1) \psi_{n-1} & \text{otherwise}
-\end{cases}
-\end{align*}
-For a closed path, we have \(n\) equations in \(n+2\) unknowns (\(\theta_0\) to \(\theta_{n+1}\)).
-However, we have not included all the information.
-Since we have repeated points, we need to identify \(\theta_0\) with \(\theta_n\) and \(\theta_1\) with \(\theta_{n+1}\).
-To get a system with \(n'\) equations in \(n'\) unknowns, we add the equation \(\theta_0 - \theta_n = 0\) and substitute in \(\theta_{n+1} = \theta_1\).
-The resulting matrix is not quite tridiagonal but has extra entries on the off-corners.
-However, it can be written in the form \(M + u v^\top\) with \(M\) tridiagonal.
-There is some freedom in choosing \(u\) and \(v\).
-For simplest computation, we take \(u = e_0 + e_{n'-1}\).
-This means that \(v = d_{n'-2} \tau_{n'-2} \tau_{n'-1}^2 e_1 - e_{n'-1}\).
-With the same notation as above, the matrix \(M\) is given by the following formulae:
-%%
-\begin{align*}
-A_i &= d_i \overline{\tau}_{i+1} \overline{\tau}_i^2 \\
-%%
-B_0 &= 1 \\
-%%
-B_i &= d_i \overline{\tau}_{i+1} \overline{\tau}_i^2 (3 \tau_{i-1} -1) + d_{i-1} \tau_{i-1} \tau_i^2(3 \overline{\tau}_{i+1} - 1) \\
-%%
-B_{n'-1} &= d_{n'-1} \overline{\tau}_{n'} \overline{\tau}_{n'-1}^2 (3 \tau_{n'-2} -1) + d_{n'-2} \tau_{n'-2} \tau_{n'-1}^2(3 \overline{\tau}_{n'} - 1) + 1\\
-%%
-C_0 &= - d_{n'-2} \tau_{n'-2} \tau_{n'-1}^2 \\
-%%
-C_i &= d_{i-1} \tau_{i-1} \tau_i^2 \\
-%%
-D_0 &= 0 \\
-%%
-D_i &= - d_i \overline{\tau}_{i+1} \overline{\tau}_i^2 (3 \tau_{i-1} - 1) \psi_i
-- d_{i-1} \tau_{i-1} \tau_i^2 \psi_{i+1} \\
-%%
-D_{n'-1} &= - d_{n'-1} \overline{\tau}_{n'} \overline{\tau}_{n'-1}^2 (3 \tau_{n'-2} - 1) \psi_{n'-1}
-- d_{n'-2} \tau_{n'-2} \tau_{n'-1}^2 \psi_1
-\end{align*}
-The next step in the implementation is to compute these coefficients and store them in appropriate arrays.
-Having done that, we need to solve the resulting tridiagonal system.
-This is done by looping through the arrays doing the following substitutions (starting at \(i = 1\)):
-%
-\begin{align*}
-B_i' &= B_{i-1}' B_i - A_i C_{i-1}' \\
-C_i' &= B_{i-1}' C_i \\
-D_i' &= B_{i-1}' D_i - A_i D_{i-1}'
-\end{align*}
-%%
-followed by back-substitution:
-%%
-\begin{align*}
-\theta_{n-1} &= D_{n-1}'/B_{n-1}' \\
-\theta_i &= (D_i' - C_i' \theta_{i+1})/B_i'
-\end{align*}
-%%
-For a closed path, we run this both with the vector \(D\) and the vector \(u = e_0 + e_{n'-1}\).
-Then to get the real answer, we use the Sherman--{}Morrison formula:
-%%
-\[
-(M + u v^\top)^{-1} D = M^{-1} D - \frac{M^{-1} u v^\top M^{-1} D}{1 + v^\top M^{-1} u}.
-\]
-%%
-This leaves us with the values for \(\theta_i\).
-We now substitute these into Hobby's formulae for the lengths:
-%%
-\begin{align*}
-\rho_i &= \frac{2 + \alpha_i}{1 + (1 - c) \cos \theta_i + c \cos \phi_{i+1}} \\
-\sigma_{i+1} &= \frac{2 - \alpha_i}{1 + (1 - c) \cos \phi_{i+1} + c \cos \theta_i} \\
-\text{where} \;\alpha_i &= a (\sin \theta_i - b \sin \phi_{i+1})(\sin \phi_{i+1} - b \sin \theta_i)(\cos \theta_i - \cos \phi_{i+1})
-\end{align*}
-%%
-and \(a = \sqrt{2}\), \(b = 1/16\), and \(c = (3 - \sqrt{5})/2\).
-These are actually the \emph{relative} lengths so need to be adjusted by a factor of \(d_i/3\).
-Now \(\theta_i\) is the angle relative to the line from \(z_i\) to \(z_{i+1}\), so to get the true angle we need to add back that angle.
-Fortunately, we stored those angles at the start.
-So the control points are:
-%%
-\begin{gather*}
-d_i \rho_i (\cos (\theta_i + \omega_i), \sin (\theta_i + \omega_i))/3 + z_i \\
-- d_i \sigma_{i+1} (\cos(\omega_i - \phi_{i+1}), \sin(\omega_i - \phi_{i+1}))/3 + z_{i+1}
-\end{gather*}
-
-\section{A Piecewise Version of Hobby's Algorithm}
-\label{sec:quick}
-Here we present a variant of Hobby's algorithm.
-One difficulty with Hobby's algorithm is that it works with the path as a whole.
-It is therefore not possible to build up a path piecewise.
-We therefore modify it to correct for this.
-Obviously, the resulting path will be less ``ideal'', but will have the property that adding new points will not affect earlier segments.
-The method we use is to employ Hobby's algorithm on two-{}segment subpaths.
-When applied to a two-{}segment subpath, the algorithm provides two cubic Bezier curves: one from the \(k\)th point to the \(k+1\)st point and the second from the \(k+1\)st to the \(k+2\)nd.
-Of this data, we keep the first segment and use that for the path between the \(k\)th and \(k+1\)st points.
-We also remember the outgoing angle of the first segment and use that as the incoming angle on the next computation (which will involve the \(k+1\)st, \(k+2\)nd, and \(k+3\)rd points).
-The two ends are slightly different to the middle segments.
-On the first segment, we might have no incoming angle.
-On the last segment, we render both pieces.
-This means that for the initial segment, we have a \(2 \times 2\) linear system:
-%%
-\[
-\begin{bmatrix}
-B_0 & C_0 \\
-A_1 & B_1
-\end{bmatrix}
-\Theta = \begin{bmatrix}
-D_0 \\ D_1
-\end{bmatrix}
-\]
-%%
-This has solution:
-%%
-\[
-\Theta = \frac{1}{B_0 B_1 - C_0 A_1} \begin{bmatrix} B_1 & - C_0 \\ -A_1 & B_0 \end{bmatrix} \begin{bmatrix} D_0 \\ D_1 \end{bmatrix} =  \frac{1}{B_0 B_1 - C_0 A_1} \begin{bmatrix} B_1 D_0 - C_0 D_1 \\ B_0 D_1 - A_1 D_0 \end{bmatrix}
-\]
-Now we have the following values for the constants:
-%%
-\begin{align*}
-A_1 &= d_1 \overline{\tau}_2 \overline{\tau}_1^2 \\
-%%
-B_0 &= \tau_0^3 (3 \overline{\tau}_1 - 1) + \chi_0 \overline{\tau}_1^3 \\
-%%
-B_1 &= d_1 \overline{\tau}_2 \overline{\tau}_1^2 (3 \tau_0 - 1) + d_0 \tau_0 \tau_1^2(3 \overline{\tau}_2 - 1) - d_0 \tau_0 \tau_1^2 \frac{\overline{\tau}_2^3 + \chi_2 \tau_1^3 (3 \overline{\tau}_2 - 1)}{\overline{\tau}_2^3 (3 \tau_1 - 1) + \chi_2 \tau_1^3} \\
-%%
-C_0 &= \tau_0^3 + \chi_0 \overline{\tau}_1^3 (3 \tau_0 - 1) \\
-%%
-D_0 &= - (\tau_0^3 + \chi_0 \overline{\tau}_1^3 ( 3 \tau_0 - 1)) \psi_1 \\
-%%
-D_1 &= - d_1 \overline{\tau}_2 \overline{\tau}_1^2 (3 \tau_0 - 1) \psi_1
-\end{align*}
-
-Let us, as we are aiming for simplicity, assume that the tensions and curls are all \(1\).
-Then we have \(A_1 = d_1\), \(B_0 = 3\), \(B_1 = 2 d_1 + 2 d_0 - d_0 = 2 d_1 + d_0\), \(C_0 = 3\), \(D_0 = - 3 \psi_1\), \(D_1 = - 2 d_1 \psi_1\).
-Thus the linear system is:
-%%
-\[
-\begin{bmatrix}
-3 & 3 \\
-d_1 & 2 d_1 + d_0
-\end{bmatrix}
-\Theta = - \psi_1 \begin{bmatrix}
-3 \\ 2 d_1
-\end{bmatrix}
-\]
-%%
-which we can row reduce to:
-%%
-\[
-\begin{bmatrix}
-1 & 1 \\
-0 & d_1 + d_0 
-\end{bmatrix}
-\Theta = -\psi_1 \begin{bmatrix}
-1 \\ d_1
-\end{bmatrix}
-\]
-%%
-whence \(\theta_1 = -\psi_1 \frac{d_1}{d_0 + d_1}\) and \(\theta_0 = -\psi_1 - \theta_1 = -\psi_1\frac{d_0 }{d_0 + d_1}\).
-We also compute \(\phi_1 = -\psi_1 - \theta_1 = \theta_0\) and \(\phi_2 = \theta_1\) (in the simple version).
-We use \(\theta_0\) and \(\phi_1\) to compute the bezier curve of the first segment, make a note of \(\theta_1\), and -- assuming there are more segments -- throw away \(\phi_2\).
-
-For the inner segments, we have the system:
-%%
-\[
-\begin{bmatrix}
-1 & 0 \\
-A_1 & B_1
-\end{bmatrix}
-\Theta = \begin{bmatrix}
-\theta_0 \\
-D_1
-\end{bmatrix}
-\]
-%%
-which has the solution \(\theta_1 = (D_1 - A_1 \theta_0)/B_1\).
-The values of the constants in this case are:
-%%
-\begin{align*}
-A_1 &= d_1 \overline{\tau}_2 \overline{\tau}_1^2 \\
-%%
-B_1 &= d_1 \overline{\tau}_2 \overline{\tau}_1^2 (3 \tau_0 - 1) + d_0 \tau_0 \tau_1^2(3 \overline{\tau}_2 - 1) - d_0 \tau_0 \tau_1^2 \frac{\overline{\tau}_2^3 + \chi_2 \tau_1^3 (3 \overline{\tau}_2 - 1)}{\overline{\tau}_2^3 (3 \tau_1 - 1) + \chi_2 \tau_1^3} \\
-%%
-D_1 &= - d_1 \overline{\tau}_2 \overline{\tau}_1^2 (3 \tau_0 - 1) \psi_1
-\end{align*}
-Again, let us consider the simpler case.
-Then \(A_1 = d_1\), \(B_1 = 2 d_1 + d_0\), and \(D_1 = - 2 d_1 \psi_1\).
-Thus \(\theta_1 = (-2 d_1 \psi_1 - d_1 \theta_0)/(2 d_1 + d_0) = - (2 \psi_1 + \theta_0) \frac{d_1}{2 d_1 + d_0}\).
-We compute \(\phi_1 = -\psi_1 - \theta_1 = \frac{- \psi_1 d_0 + \theta_0 d_1}{2 d_1 + d_0}\) and \(\phi_2 = \theta_1\).
-Then we store \(\theta_1\) for the next iteration.
-
-The actual curves are then produced from the angles using the same formulae for the lengths of the control points as in the main algorithm.
-
-At the last stage, we render both segments of the generated curve.
-
-\section{Acknowledgements}
-
-This package began life as an answer to the question \href{http://tex.stackexchange.com/q/54771/86}{Curve through a sequence of points with Metapost and TikZ}.
-Once released upon the unsuspecting world, various questions on the \href{http://tex.stackexchange.com}{TeX-SX} site have prompted new features (and bug-fixes).
-Most of these can be found by looking at the \href{http://tex.stackexchange.com/questions/tagged/hobby}{list of questions tagged ``hobby''} on that site.
-
-
-\begin{thebibliography}{1} \bibitem{MR834054} John~D. Hobby. \newblock Smooth, easy to compute interpolating splines. \newblock {\em Discrete Comput. Geom.}, 1:123--140, 1986. \end{thebibliography}
-
-\end{document}

Deleted: trunk/Master/texmf-dist/source/latex/hobby/hobby.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/hobby/hobby.dtx	2023-09-01 14:59:58 UTC (rev 68136)
+++ trunk/Master/texmf-dist/source/latex/hobby/hobby.dtx	2023-09-01 21:15:20 UTC (rev 68137)
@@ -1,3500 +0,0 @@
-% \iffalse meta-comment
-%<*internal>
-\iffalse
-%</internal>
-%<*readme>
-----------------------------------------------------------------
-hobby --- a TikZ/PGF library for drawing smooth(ish) curves using
-          Hobby's algorithm (implemented in LaTeX3)
-E-mail: loopspace at mathforge.org
-Released under the LaTeX Project Public License v1.3c or later
-See http://www.latex-project.org/lppl.txt
-----------------------------------------------------------------
-
-This package defines a path generation function for TikZ/PGF
-which implements Hobby's algorithm for a path built out of Bezier
-curves which passes through a given set of points.
-
-The implementation is in LaTeX3.  It can be used as as a TikZ
-`to path`.
-%</readme>
-%<*internal>
-\fi
-\def\nameofplainTeX{plain}
-\ifx\fmtname\nameofplainTeX\else
-  \expandafter\begingroup
-\fi
-%</internal>
-%<*install>
-\input docstrip.tex
-\keepsilent
-\askforoverwritefalse
-\preamble
-----------------------------------------------------------------
-hobby --- a TikZ/PGF library for drawing smooth(ish) curves using
-          Hobby's algorithm (implemented in LaTeX3)
-E-mail: loopspace at mathforge.org
-Released under the LaTeX Project Public License v1.3c or later
-See http://www.latex-project.org/lppl.txt
-----------------------------------------------------------------
-
-\endpreamble
-\postamble
-
-Copyright (C) 2012 by Andrew Stacey <loopspace at mathforge.org>
-
-This file may be distributed and/or modified under the conditions
-of the LaTeX Project Public License, either version 1.3 of this
-license or (at your option) any later version.
-The latest version of this license is in:
-
-   http://www.latex-project.org/lppl.txt
-
-and version 1.3 or later is part of all distributions of LaTeX
-version 2005/12/01 or later.
-
-This work is "maintained" (as per LPPL maintenance status) by
-Andrew Stacey.
-
-This work consists of the files  hobby.dtx
-                                 hobby_doc.tex
-and the derived files            hobby.code.tex
-                                 pgflibraryhobby.code.tex
-                                 tikzlibraryhobby.code.tex
-                                 pml3array.sty
-                                 hobby.ins
-                                 hobby.pdf
-                                 hobby_code.pdf
-                                 README.txt
-
-\endpostamble
-\usedir{tex/latex/hobby}
-\generate{\file{tikzlibraryhobby.code.tex} {\from{hobby.dtx}{tikzlibrary}}}
-\generate{\file{pgflibraryhobby.code.tex} {\from{hobby.dtx}{pgflibrary}}}
-\generate{\file{hobby.code.tex}
-{\from{hobby.dtx}{hobby}}}
-\generate{\file{pml3array.sty}
-{\from{hobby.dtx}{array}}}
-%</install>
-%<install>\endbatchfile
-%<*internal>
-\usedir{source/latex/hobby}
-\generate{
-  \file{\jobname.ins}{\from{\jobname.dtx}{install}}
-}
-\nopreamble\nopostamble
-\generate{
-   \file{README.txt}{\from{\jobname.dtx}{readme}}
-}
-\ifx\fmtname\nameofplainTeX
-  \expandafter\endbatchfile
-\else
-  \expandafter\endgroup
-\fi
-%</internal>
-%<*driver>
-\documentclass[full]{l3doc}
-\usepackage[T1]{fontenc}
-\usepackage{csquotes}
-\usepackage{lmodern}
-\usepackage{tikz}
-\usepackage{amsmath}
-\usetikzlibrary{hobby,decorations.pathreplacing}
-\usepackage[margin=3cm]{geometry}
-\EnableCrossrefs
-\CodelineIndex
-\RecordChanges
-\addtolength{\hoffset}{.4in}
-\begin{document}
-  \DocInput{\jobname.dtx}
-\end{document}
-%</driver>
-% \fi
-%
-% \CheckSum{3382}
-%
-% \CharacterTable
-%  {Upper-case    \A\B\C\D\E\F\G\H\I\J\K\L\M\N\O\P\Q\R\S\T\U\V\W\X\Y\Z
-%   Lower-case    \a\b\c\d\e\f\g\h\i\j\k\l\m\n\o\p\q\r\s\t\u\v\w\x\y\z
-%   Digits        \0\1\2\3\4\5\6\7\8\9
-%   Exclamation   \!     Double quote  \"     Hash (number) \#
-%   Dollar        \$     Percent       \%     Ampersand     \&
-%   Acute accent  \'     Left paren    \(     Right paren   \)
-%   Asterisk      \*     Plus          \+     Comma         \,
-%   Minus         \-     Point         \.     Solidus       \/
-%   Colon         \:     Semicolon     \;     Less than     \<
-%   Equals        \=     Greater than  \>     Question mark \?
-%   Commercial at \@     Left bracket  \[     Backslash     \\
-%   Right bracket \]     Circumflex    \^     Underscore    \_
-%   Grave accent  \`     Left brace    \{     Vertical bar  \|
-%   Right brace   \}     Tilde         \~}
-%
-%
-%
-% \DoNotIndex{\newcommand,\newenvironment}
-%
-% \providecommand*{\url}{\texttt}
-% \title{The \textsf{Hobby} package: code}
-% \author{Andrew Stacey \\ \url{loopspace at mathforge.org}}
-% \date{\hobbyVersion\ from\ \hobbyDate}
-% \maketitle
-%
-%
-% \StopEventually{\PrintChanges}
-% \section{Implementation}
-%
-% \subsection{Main Code}
-%
-% \iffalse
-%<*hobby>
-% \fi
-%
-% We use \LaTeX3 syntax so need to load the requisite packages
-%    \begin{macrocode}
-\RequirePackage{expl3}
-\RequirePackage{xparse}
-\RequirePackage{pml3array}
-\ExplSyntaxOn
-%    \end{macrocode}
-%
-%    \begin{macrocode}
-\cs_generate_variant:Nn \fp_set:Nn {Nx}
-\cs_generate_variant:Nn \tl_if_eq:nnTF {VnTF}
-\cs_generate_variant:Nn \tl_if_eq:nnTF {xnTF}
-%    \end{macrocode}
-%
-% \subsubsection{Initialisation}
-%
-% We declare all our variables.
-%
-% Start with version and date, together with a check to see if we've been loaded twice (fail gracefully if so). 
-%
-%    \begin{macrocode}
-\tl_clear:N \l_tmpa_tl
-\tl_if_exist:NT \g__hobby_version
-{
-  \tl_set:Nn \l_tmpa_tl {
-    \ExplSyntaxOff
-    \tl_clear:N \l_tmpa_tl
-    \endinput
-  }
-}
-\tl_use:N \l_tmpa_tl
-
-\tl_new:N \g__hobby_version
-\tl_new:N \g__hobby_date
-\tl_set:Nn \g__hobby_version {1.8}
-\tl_set:Nn \g__hobby_date {2017-06-01}
-\DeclareDocumentCommand \hobbyVersion {}
-{
-  \tl_use:N \g__hobby_version
-}
-\DeclareDocumentCommand \hobbyDate {}
-{
-  \tl_use:N \g__hobby_date
-}
-%    \end{macrocode}
-%
-% The function for computing the lengths of the control points depends on three parameters.
-% These are set to \(a = \sqrt{2}\), \(b = 1/16\), and \(c = \frac{3 - \sqrt{5}}{2}\).
-%    \begin{macrocode}
-\fp_new:N \g_hobby_parama_fp
-\fp_new:N \g_hobby_paramb_fp
-\fp_new:N \g_hobby_paramc_fp
-\fp_gset:Nn \g_hobby_parama_fp {2^.5}
-\fp_gset:Nn \g_hobby_paramb_fp {1/16}
-\fp_gset:Nn \g_hobby_paramc_fp {(3-5^.5)/2}
-%    \end{macrocode}
-%
-% Now we define our objects for use in generating the path.
-%
-% \begin{macro}{\l_hobby_closed_bool}
-% \Verb+\l_hobby_closed_bool+ is \Verb+true+ if the path is closed.
-%    \begin{macrocode}
-\bool_new:N \l_hobby_closed_bool
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\l_hobby_disjoint_bool}
-% \Verb+\l_hobby_disjoint_bool+ is \Verb+true+ if the path should start with a \Verb+moveto+ command.
-%    \begin{macrocode}
-\bool_new:N \l_hobby_disjoint_bool
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\l_hobby_save_aux_bool}
-% \Verb+\l_hobby_save_aux_bool+ is \Verb+true+ if when saving paths then they should be saved to the \Verb+aux+ file.
-%    \begin{macrocode}
-\bool_new:N \l_hobby_save_aux_bool
-\bool_set_true:N \l_hobby_save_aux_bool
-\DeclareDocumentCommand \HobbyDisableAux {}
-{
-  \bool_set_false:N \l_hobby_save_aux_bool
-}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\l_hobby_points_array}
-% \Verb+\l_hobby_points_array+ is an array holding the specified points on the path.
-% In the \LaTeX3 code, a ``point'' is a token list of the form \Verb+x = <number>, y = <number>+.
-% This gives us the greatest flexibility in passing points back and forth between the \LaTeX3 code and any calling code.
-% The array is indexed by integers beginning with \(0\).
-% In the documentation, we will use the notation \(z_k\) to refer to the \(k\)th point.
-%    \begin{macrocode}
-\array_new:N \l_hobby_points_array
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\l_hobby_points_x_array}
-% \Verb+\l_hobby_points_x_array+ is an array holding the \(x\)--{}coordinates of the specified points.
-%    \begin{macrocode}
-\array_new:N \l_hobby_points_x_array
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\l_hobby_points_y_array}
-% \Verb+\l_hobby_points_y_array+ is an array holding the \(y\)--{}coordinates of the specified points.
-%    \begin{macrocode}
-\array_new:N \l_hobby_points_y_array
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\l_hobby_actions_array}
-% \Verb+\l_hobby_actions_array+ is an array holding the (encoded) action to be taken out on the segment of the path ending at that point.
-%    \begin{macrocode}
-\array_new:N \l_hobby_actions_array
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\l_hobby_angles_array}
-% \Verb+\l_hobby_angles_array+ is an array holding the angles of the lines between the points.
-% Specifically, the angle indexed by \(k\) is the angle in radians of the line from \(z_k\) to \(z_{k+1}\).
-%    \begin{macrocode}
-\array_new:N \l_hobby_angles_array
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\l_hobby_distances_array}
-% \Verb+\l_hobby_distances_array+ is an array holding the distances between the points.
-% Specifically, the distance indexed by \(k\), which we will write as \(d_k\), is the length of the line from \(z_k\) to \(z_{k+1}\).
-%    \begin{macrocode}
-\array_new:N \l_hobby_distances_array
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\l_hobby_tension_out_array}
-% \Verb+\l_hobby_tension_out_array+ is an array holding the tension for the path as it leaves each point.
-% This is a parameter that controls how much the curve ``flexes'' as it leaves the point.
-% In the following, this will be written \(\tau_k\).
-%    \begin{macrocode}
-\array_new:N \l_hobby_tension_out_array
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\l_hobby_tension_in_array}
-% \Verb+\l_hobby_tension_in_array+ is an array holding the tension for the path as it arrives at each point.
-% This is a parameter that controls how much the curve ``flexes'' as it gets to the point.
-% In the following, this will be written \(\overline{\tau}_k\).
-%    \begin{macrocode}
-\array_new:N \l_hobby_tension_in_array
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\l_hobby_matrix_a_array}
-% \Verb+\l_hobby_matrix_a_array+ is an array holding the subdiagonal of the linear system that has to be solved to find the angles of the control points.
-% In the following, this will be denoted by \(A_i\).
-% The first index is \(1\).
-%    \begin{macrocode}
-\array_new:N \l_hobby_matrix_a_array
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\l_hobby_matrix_b_array}
-% \Verb+\l_hobby_matrix_b_array+ is an array holding the diagonal of the linear system that has to be solved to find the angles of the control points.
-% In the following, this will be denoted by \(B_i\).
-% The first index is \(0\).
-%    \begin{macrocode}
-\array_new:N \l_hobby_matrix_b_array
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\l_hobby_matrix_c_array}
-% \Verb+\l_hobby_matrix_c_array+ is an array holding the superdiagonal of the linear system that has to be solved to find the angles of the control points.
-% In the following, this will be denoted by \(C_i\).
-% The first index is \(0\).
-%    \begin{macrocode}
-\array_new:N \l_hobby_matrix_c_array
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\l_hobby_matrix_d_array}
-% \Verb+\l_hobby_matrix_d_array+ is an array holding the target vector of the linear system that has to be solved to find the angles of the control points.
-% In the following, this will be denoted by \(D_i\).
-% The first index is \(1\).
-%    \begin{macrocode}
-\array_new:N \l_hobby_matrix_d_array
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\l_hobby_vector_u_array}
-% \Verb+\l_hobby_vector_u_array+ is an array holding the perturbation of the linear system for closed paths.
-% The coefficient matrix for an \emph{open} path is tridiagonal and that means that Gaussian elimination runs faster than expected (\(O(n)\) instead of \(O(n^3)\)).
-% The matrix for a closed path is not tridiagonal but is not far off.
-% It can be solved by perturbing it to a tridiagonal matrix and then modifying the result.
-% This array represents a utility vector in that perturbation. 
-% In the following, the vector will be denoted by \(u\).
-% The first index is \(1\).
-%    \begin{macrocode}
-\array_new:N \l_hobby_vector_u_array
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\l_hobby_excess_angle_array}
-% \Verb+\l_hobby_excess_angle_array+ is an array that allows the user to say that the algorithm should add a multiple of \(2 \pi\) to the angle differences.
-% This is because these angles are wrapped to the interval \((-\pi,\pi]\) but the wrapping might go wrong near the end points due to computation accuracy.
-% The first index is \(1\).
-%    \begin{macrocode}
-\array_new:N \l_hobby_excess_angle_array
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\l_hobby_psi_array}
-% \Verb+\l_hobby_psi_array+ is an array holding the difference of the angles of the lines entering and exiting a point.
-% That is, \(\psi_k\) is the angle between the lines joining \(z_k\) to \(z_{k-1}\) and \(z_{k+1}\).
-% The first index is \(1\).
-%    \begin{macrocode}
-\array_new:N \l_hobby_psi_array
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\l_hobby_theta_array}
-% \Verb+\l_hobby_theta_array+ is an array holding the angles of the outgoing control points for the generated path.
-% These are measured relative to the line joining the point to the next point on the path.
-% The first index is \(0\).
-%    \begin{macrocode}
-\array_new:N \l_hobby_theta_array
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\l_hobby_phi_array}
-% \Verb+\l_hobby_phi_array+ is an array holding the angles of the incoming control points for the generated path.
-% These are measured relative to the line joining the point to the previous point on the path.
-% The first index is \(1\).
-%    \begin{macrocode}
-\array_new:N \l_hobby_phi_array
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\l_hobby_sigma_array}
-% \Verb+\l_hobby_sigma_array+ is an array holding the lengths of the outgoing control points for the generated path.
-% The units are such that the length of the line to the next specified point is one unit.
-%    \begin{macrocode}
-\array_new:N \l_hobby_sigma_array
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\l_hobby_rho_array}
-% \Verb+\l_hobby_rho_array+ is an array holding the lengths of the incoming control points for the generated path.
-% The units are such that the length of the line to the previous specified point is one unit.
-%    \begin{macrocode}
-\array_new:N \l_hobby_rho_array
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\l_hobby_controla_array}
-% \Verb+\l_hobby_controla_array+ is an array holding the coordinates of the first control points on the curves.
-% The format is the same as for \Verb+\l_hobby_points_array+.
-%    \begin{macrocode}
-\array_new:N \l_hobby_controla_array
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\l_hobby_controlb_array}
-% \Verb+\l_hobby_controlb_array+ is an array holding the coordinates of the second control points on the curves.
-% The format is the same as for \Verb+\l_hobby_points_array+.
-%    \begin{macrocode}
-\array_new:N \l_hobby_controlb_array
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\l_hobby_matrix_v_fp}
-% \Verb+\l_hobby_matrix_v_fp+ is a number which is used when doing the perturbation of the solution of the linear system for a closed curve.
-% There is actually a vector, \(v\), that this corresponds to but that vector only has one component that needs computation.
-%    \begin{macrocode}
-\fp_new:N \l_hobby_matrix_v_fp
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\l_hobby_tempa_fp}
-% \Verb+\l_hobby_tempa_fp+ is a temporary variable of type \Verb+fp+.
-%    \begin{macrocode}
-\fp_new:N \l_hobby_tempa_fp
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\l_hobby_tempb_fp}
-% \Verb+\l_hobby_tempb_fp+ is a temporary variable of type \Verb+fp+.
-%    \begin{macrocode}
-\fp_new:N \l_hobby_tempb_fp
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\l_hobby_tempc_fp}
-% \Verb+\l_hobby_tempc_fp+ is a temporary variable of type \Verb+fp+.
-%    \begin{macrocode}
-\fp_new:N \l_hobby_tempc_fp
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\l_hobby_tempd_fp}
-% \Verb+\l_hobby_tempd_fp+ is a temporary variable of type \Verb+fp+.
-%    \begin{macrocode}
-\fp_new:N \l_hobby_tempd_fp
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\l_hobby_temps_fp}
-% \Verb+\l_hobby_temps_fp+ is a temporary variable of type \Verb+fp+.
-%    \begin{macrocode}
-\fp_new:N \l_hobby_temps_fp
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\l_hobby_in_curl_fp}
-% \Verb+\l_hobby_in_curl_fp+ is the ``curl'' at the end of an open path.
-% This is used if the angle at the end is not specified.
-%    \begin{macrocode}
-\fp_new:N \l_hobby_in_curl_fp
-\fp_set:Nn \l_hobby_in_curl_fp {1}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\l_hobby_out_curl_fp}
-% \Verb+\l_hobby_out_curl_fp+ is the ``curl'' at the start of an open path.
-% This is used if the angle at the start is not specified.
-%    \begin{macrocode}
-\fp_new:N \l_hobby_out_curl_fp
-\fp_set:Nn \l_hobby_out_curl_fp {1}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\l_hobby_in_angle_fp}
-% \Verb+\l_hobby_in_angle_fp+ is the angle at the end of an open path.
-% If this is not specified, it will be computed automatically.
-% It is set to \Verb+\c_inf_fp+ to allow easy detection of when it has been specified.
-%    \begin{macrocode}
-\fp_new:N \l_hobby_in_angle_fp
-\fp_set_eq:NN \l_hobby_in_angle_fp \c_inf_fp
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\l_hobby_out_angle_fp}
-% \Verb+\l_hobby_out_angle_fp+ is the angle at the start of an open path.
-% If this is not specified, it will be computed automatically.
-% It is set to \Verb+\c_inf_fp+ to allow easy detection of when it has been specified.
-%    \begin{macrocode}
-\fp_new:N \l_hobby_out_angle_fp
-\fp_set_eq:NN \l_hobby_out_angle_fp \c_inf_fp
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\l_hobby_npoints_int}
-% \Verb+\l_hobby_npoints_int+ is one less than the number of points on the curve.
-% As our list of points starts at \(0\), this is the index of the last point.
-% In the algorithm for a closed curve, some points are repeated whereupon this is incremented so that it is always the index of the last point. 
-%    \begin{macrocode}
-\int_new:N \l_hobby_npoints_int
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\l_hobby_draw_int}
-%    \begin{macrocode}
-\int_new:N \l_hobby_draw_int
-%    \end{macrocode}
-% \end{macro}
-%
-% A ``point'' is a key-value list setting the x-value, the y-value, and the tensions at that point.
-% Using keys makes it easier to pass points from the algorithm code to the calling code and vice versa without either knowing too much about the other.
-%    \begin{macrocode}
-\keys_define:nn {hobby / read in all} {
-  x .fp_set:N = \l_hobby_tempa_fp,
-  y .fp_set:N = \l_hobby_tempb_fp,
-  tension~out .fp_set:N = \l_hobby_tempc_fp,
-  tension~in .fp_set:N = \l_hobby_tempd_fp,
-  excess~angle .fp_set:N = \l_hobby_temps_fp,
-  break .tl_set:N = \l_tmpb_tl,
-  blank .tl_set:N = \l_tmpa_tl,
-  tension .meta:n = { tension~out=#1, tension~in=#1 },
-  break .default:n = false,
-  blank .default:n = false,
-  invert~soft~blanks .choice:,
-  invert~soft~blanks / true .code:n = {
-    \int_gset:Nn \l_hobby_draw_int {0}
-  },
-  invert~soft~blanks / false .code:n = {
-    \int_gset:Nn \l_hobby_draw_int {1}
-  },
-  invert~soft~blanks .default:n = true,
-  tension~out .default:n = 1,
-  tension~in .default:n = 1,
-  excess~angle .default:n = 0,
-  in~angle .fp_gset:N = \l_hobby_in_angle_fp,
-  out~angle .fp_gset:N = \l_hobby_out_angle_fp,
-  in~curl .fp_gset:N = \l_hobby_in_curl_fp,
-  out~curl .fp_gset:N = \l_hobby_out_curl_fp,
-  closed .bool_gset:N = \l_hobby_closed_bool,
-  closed .default:n = true,
-  disjoint .bool_gset:N = \l_hobby_disjoint_bool,
-  disjoint .default:n = true,
-  break~default .code:n = {
-    \keys_define:nn { hobby / read in all } 
-    {
-      break .default:n = #1
-    }
-  },
-  blank~default .code:n = {
-    \keys_define:nn { hobby / read in all } 
-    {
-      blank .default:n = #1
-    }
-  },
-}
-%    \end{macrocode}
-% There are certain other parameters that can be set for a given curve.
-%    \begin{macrocode}
-\keys_define:nn { hobby / read in params} {
-  in~angle .fp_gset:N = \l_hobby_in_angle_fp,
-  out~angle .fp_gset:N = \l_hobby_out_angle_fp,
-  in~curl .fp_gset:N = \l_hobby_in_curl_fp,
-  out~curl .fp_gset:N = \l_hobby_out_curl_fp,
-  closed .bool_gset:N = \l_hobby_closed_bool,
-  closed .default:n = true,
-  disjoint .bool_gset:N = \l_hobby_disjoint_bool,
-  disjoint .default:n = true,
-  break~default .code:n = {
-    \keys_define:nn { hobby / read in all } 
-    {
-      break .default:n = #1
-    }
-  },
-  blank~default .code:n = {
-    \keys_define:nn { hobby / read in all } 
-    {
-      blank .default:n = #1
-    }
-  },
-  invert~soft~blanks .choice:,
-  invert~soft~blanks / true .code:n = {
-    \int_gset:Nn \l_hobby_draw_int {0}
-  },
-  invert~soft~blanks / false .code:n = {
-    \int_gset:Nn \l_hobby_draw_int {1}
-  },
-  invert~soft~blanks .default:n = true,
-}
-%    \end{macrocode}
-% \begin{macro}{\hobby_distangle:n}
-% Computes the distance and angle between successive points.
-% The argument given is the index of the current point.
-% Assumptions: the points are in \Verb+\l_hobby_points_x_array+ and \Verb+\l_hobby_points_y_array+ and the index of the last point is \Verb+\l_hobby_npoints_int+.
-%    \begin{macrocode}
-\cs_set:Nn \hobby_distangle:n {
-  \fp_set:Nn \l_hobby_tempa_fp {
-    (\array_get:Nn \l_hobby_points_x_array {#1 + 1})
-    - (\array_get:Nn \l_hobby_points_x_array {#1})}
-
-  \fp_set:Nn \l_hobby_tempb_fp {
-    (\array_get:Nn \l_hobby_points_y_array {#1 + 1})
-    - (\array_get:Nn \l_hobby_points_y_array {#1})}
-
-  \fp_set:Nn \l_hobby_tempc_fp { atan ( \l_hobby_tempb_fp, \l_hobby_tempa_fp ) }
-  \fp_veclen:NVV \l_hobby_tempd_fp \l_hobby_tempa_fp \l_hobby_tempb_fp
-
-  \array_push:Nx \l_hobby_angles_array {\fp_to_tl:N \l_hobby_tempc_fp}
-  \array_push:Nx \l_hobby_distances_array {\fp_to_tl:N \l_hobby_tempd_fp}
-  }
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\fp_veclen:NVV}
-% Computes the length of the vector specified by the latter two arguments, storing the answer in the first.
-%    \begin{macrocode}
-\cs_new:Nn \fp_veclen:Nnn {
-  \fp_set:Nn #1 {((#2)^2 + (#3)^2)^.5}
-}
-\cs_generate_variant:Nn \fp_veclen:Nnn {NVV}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\hobby_ctrllen:Nnn}
-% Computes the length of the control point vector from the two angles, storing the answer in the first argument given.
-%    \begin{macrocode}
-\cs_new:Nn \hobby_ctrllen:Nnn {
-  \fp_set:Nn #1 {(2 - \g_hobby_parama_fp
-    * ( sin(#2) - \g_hobby_paramb_fp * sin(#3) )
-    * ( sin(#3) - \g_hobby_paramb_fp * sin(#2) )
-    * ( cos(#2) - cos(#3) ) )
-    / ( 1 + (1 - \g_hobby_paramc_fp) * cos(#3) + \g_hobby_paramc_fp * cos(#2))}
-}
-\cs_generate_variant:Nn \hobby_ctrllen:Nnn {NVV}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\hobby_append_point_copy:n}
-%   This function adds a copy of the point (numbered by its argument) to the end of the list of points, copying all the relevant data  (coordinates, tension, etc.).
-%
-% Originally from Bruno Le Foch on TeX-SX.
-%    \begin{macrocode}
-\cs_new_protected:Npn \hobby_append_point_copy:n #1
-  {
-    \hobby_append_point_copy_aux:Nn \l_hobby_points_array {#1}
-    \hobby_append_point_copy_aux:Nn \l_hobby_points_x_array {#1}
-    \hobby_append_point_copy_aux:Nn \l_hobby_points_y_array {#1}
-    \hobby_append_point_copy_aux:Nn \l_hobby_tension_in_array {#1}
-    \hobby_append_point_copy_aux:Nn \l_hobby_tension_out_array {#1}
-    \hobby_append_point_copy_aux:Nn \l_hobby_excess_angle_array {#1}
-    \hobby_append_point_copy_aux:Nn \l_hobby_actions_array {#1}
-  }
-\cs_new_protected:Npn \hobby_append_point_copy_aux:Nn #1#2
-  { \array_gpush:Nx #1 { \array_get:Nn #1 {#2} } }
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\hobby_gen_path:}
-% This is the curve generation function.
-% We assume at the start that we have an array containing all the points that the curve must go through, and the various curve parameters have been initialised.
-% So these must be set up by a wrapper function which then calls this one.
-% The list of required information is:
-% \begin{enumerate}
-% \item \Verb+\l_hobby_points_x_array+
-% \item \Verb+\l_hobby_points_y_array+
-% \item \Verb+\l_hobby_tension_out_array+
-% \item \Verb+\l_hobby_tension_in_array+
-% \item \Verb+\l_hobby_excess_angle_array+
-% \item \Verb+\l_hobby_in_curl_fp+
-% \item \Verb+\l_hobby_out_curl_fp+
-% \item \Verb+\l_hobby_in_angle_fp+
-% \item \Verb+\l_hobby_out_angle_fp+
-% \item \Verb+\l_hobby_closed_bool+
-% \item \Verb+\l_hobby_actions_array+
-% \end{enumerate}
-%
-%    \begin{macrocode}
-\cs_new:Nn \hobby_gen_path:
-{
-%    \end{macrocode}
-% For much of the time, we can pretend that a closed path is the same as an open path.
-% To do this, we need to make the end node an internal node by repeating the \(z_1\) node as the \(z_{n+1}\)th node.
-% We also check that the last (\(z_n\)) and first (\(z_0\)) nodes are the same, otherwise we repeat the \(z_0\) node as well.
-%    \begin{macrocode}
-\bool_if:NT \l_hobby_closed_bool {
-%    \end{macrocode}
-% Are the \(x\)-values of the first and last points different?
-%    \begin{macrocode}
-  \fp_compare:nTF {(\array_get:Nn \l_hobby_points_x_array {0})
-    =
-    (\array_top:N \l_hobby_points_x_array)}
-  {
-%    \end{macrocode}
-% No, so compare the \(y\)-values.
-% Are the \(y\)-values of the first and last points different?
-%    \begin{macrocode}
-    \fp_compare:nF {
-      \array_get:Nn \l_hobby_points_y_array {0}
-      =
-      \array_top:N \l_hobby_points_y_array
-    }
-  {
-%    \end{macrocode}
-% Yes, so we need to duplicate the first point, with all of its data.
-%    \begin{macrocode}
-    \hobby_append_point_copy:n {0}
-  }
-  }
-  {
-%    \end{macrocode}
-% Yes, so we need to duplicate the first point, with all of its data.
-%    \begin{macrocode}
-    \hobby_append_point_copy:n {0}
-  }
-%    \end{macrocode}
-% Now that we are sure that the first and last points are identical, we need to duplicate the first-but-one point (and all of its data).
-%    \begin{macrocode}
-    \hobby_append_point_copy:n {1}
-}
-%    \end{macrocode}
-%
-% Set \Verb+\l_hobby_npoints_int+ to the number of points (minus one).
-%    \begin{macrocode}
-\int_gset:Nn \l_hobby_npoints_int {\array_length:N \l_hobby_points_y_array}
-%    \end{macrocode}
-% At this point, we need to decide what to do.
-% This will depend on whether we have any intermediate points.
-%    \begin{macrocode}
-\int_compare:nNnTF {\l_hobby_npoints_int} = {0} {
-%    \end{macrocode}
-% Only one point, do nothing
-%    \begin{macrocode}
-}
-{
-  \int_compare:nNnTF {\l_hobby_npoints_int} = {1} {
-%    \end{macrocode}
-% Only two points, skip processing.
-% Just need to set the incoming and outgoing angles
-%    \begin{macrocode}
-\hobby_distangle:n {0}
-\fp_compare:nF { \l_hobby_out_angle_fp == \c_inf_fp }
-{
-  \fp_set:Nn \l_hobby_tempa_fp { \l_hobby_out_angle_fp
-    - \array_get:Nn \l_hobby_angles_array {0}}
-%    \end{macrocode}
-% We want to ensure that these angles lie in the range \((-\pi,\pi]\).
-% So if the angle is bigger than \(\pi\), we subtract \(2 \pi\).
-% (It shouldn't be that we can get bigger than \(3 \pi\) - check this)
-%    \begin{macrocode}
-    \fp_compare:nT {\l_hobby_tempa_fp > \c_pi_fp }
-    {
-      \fp_sub:Nn \l_hobby_tempa_fp {2 * \c_pi_fp}
-    }
-%    \end{macrocode}
-% Similarly, we check to see if the angle is less than \(-\pi\).
-%    \begin{macrocode}
-    \fp_compare:nT {\l_hobby_tempa_fp < -\c_pi_fp }
-    {
-      \fp_add:Nn \l_hobby_tempa_fp {2 * \c_pi_fp}
-    }
-  \array_put:Nnx \l_hobby_theta_array {0} {\fp_to_tl:N \l_hobby_tempa_fp}
-    \fp_compare:nT { \l_hobby_in_angle_fp == \c_inf_fp }
-    {
-%^^A      \fp_mul:Nn \l_hobby_tempa_fp {-1}
-      \array_put:Nnx \l_hobby_phi_array {1}{ \fp_to_tl:N \l_hobby_tempa_fp}
-    }
-   }
-\fp_compare:nTF { \l_hobby_in_angle_fp == \c_inf_fp }
-{
-  \fp_compare:nT { \l_hobby_out_angle_fp == \c_inf_fp }
-  {
-    \array_put:Nnx \l_hobby_phi_array {1} {0}
-    \array_put:Nnx \l_hobby_theta_array {0} {0}
-  }
-}
-{
-  \fp_set:Nn \l_hobby_tempa_fp { - \l_hobby_in_angle_fp + \c_pi_fp
-+ (\array_get:Nn \l_hobby_angles_array {0})}
-  \fp_compare:nT {\l_hobby_tempa_fp > \c_pi_fp }
-  {
-    \fp_sub:Nn \l_hobby_tempa_fp {2 * \c_pi_fp}
-  }
-  \fp_compare:nT {\l_hobby_tempa_fp < -\c_pi_fp }
-  {
-    \fp_add:Nn \l_hobby_tempa_fp {2 * \c_pi_fp}
-  }
-
-  \array_put:Nnx \l_hobby_phi_array {1}
-  {\fp_to_tl:N \l_hobby_tempa_fp}
-  \fp_compare:nT { \l_hobby_out_angle_fp == \c_inf_fp }
-    {
-%^^A      \fp_mul:Nn \l_hobby_tempa_fp {-1}
-      \array_put:Nnx \l_hobby_theta_array {0}{ \fp_to_tl:N \l_hobby_tempa_fp}
-    }
-}
-
-  }
-  {
-%    \end{macrocode}
-% Got enough points, go on with processing
-%    \begin{macrocode}
-    \hobby_compute_path:
-  }
-  \hobby_build_path:
-}
-}
-%    \end{macrocode}
-% \end{macro}
-% 
-%
-% \begin{macro}{\hobby_compute_path:}
-% This is the path builder where we have enough points to run the algorithm.
-%    \begin{macrocode}
-\cs_new:Nn \hobby_compute_path:
-{
-%    \end{macrocode}
-% Our first step is to go through the list of points and compute the distances and angles between successive points.
-% Thus \(d_i\) is the distance from \(z_i\) to \(z_{i+1}\) and the angle is the angle of the line from \(z_i\) to \(z_{i+1}\).
-%    \begin{macrocode}
-\int_step_function:nnnN {0} {1} {\l_hobby_npoints_int - 1} \hobby_distangle:n
-%    \end{macrocode}
-%
-% For the majority of the code, we're only really interested in the differences of the angles.
-% So for each internal point we compute the differences in the angles.
-%    \begin{macrocode}
-  \int_step_inline:nnnn {1} {1} {\l_hobby_npoints_int - 1} {
-    \fp_set:Nx \l_hobby_tempa_fp {
-    \array_get:Nn \l_hobby_angles_array {##1}
-    - \array_get:Nn \l_hobby_angles_array {##1 - 1}}
-%    \end{macrocode}
-% We want to ensure that these angles lie in the range \((-\pi,\pi]\).
-% So if the angle is bigger than \(\pi\), we subtract \(2 \pi\).
-% (It shouldn't be that we can get bigger than \(3 \pi\) - check this.)
-%    \begin{macrocode}
-    \fp_compare:nTF {\l_hobby_tempa_fp > \c_pi_fp }
-    {
-      \fp_sub:Nn \l_hobby_tempa_fp {2 * \c_pi_fp}
-    }
-    {}
-%    \end{macrocode}
-% Similarly, we check to see if the angle is less than \(-\pi\).
-%    \begin{macrocode}
-    \fp_compare:nTF {\l_hobby_tempa_fp <= -\c_pi_fp }
-    {
-      \fp_add:Nn \l_hobby_tempa_fp {2 * \c_pi_fp}
-    }
-    {}
-%    \end{macrocode}
-% The wrapping routine might not get it right at the edges so we add in the override.  
-%    \begin{macrocode}
-\array_get:NnNTF \l_hobby_excess_angle_array {##1} \l_tmpa_tl {
-  \fp_add:Nn \l_hobby_tempa_fp {2 * \c_pi_fp * \l_tmpa_tl}
-  }{}
-%    \end{macrocode}
-%    \begin{macrocode}
-    \array_put:Nnx \l_hobby_psi_array {##1}{\fp_to_tl:N \l_hobby_tempa_fp}
-  }
-%    \end{macrocode}
-%
-% Next, we generate the matrix.
-% We start with the subdiagonal.
-% This is indexed from \(1\) to \(n-1\).
-%    \begin{macrocode}
-  \int_step_inline:nnnn {1} {1} {\l_hobby_npoints_int - 1} {
-    \array_put:Nnx \l_hobby_matrix_a_array {##1} {\fp_to_tl:n {
-       \array_get:Nn \l_hobby_tension_in_array {##1}^2
-      * \array_get:Nn \l_hobby_distances_array {##1}
-      * \array_get:Nn \l_hobby_tension_in_array {##1 + 1}
-  }}
-}
-%    \end{macrocode}
-%
-% Next, we attack main diagonal.
-% We might need to adjust the first and last terms, but we'll do that in a minute.
-%    \begin{macrocode}
-  \int_step_inline:nnnn {1} {1} {\l_hobby_npoints_int - 1} {
-
-  \array_put:Nnx \l_hobby_matrix_b_array {##1} {\fp_to_tl:n
-{(3 * (\array_get:Nn \l_hobby_tension_in_array {##1 + 1}) - 1) * 
- (\array_get:Nn \l_hobby_tension_out_array {##1})^2 * 
-(\array_get:Nn \l_hobby_tension_out_array {##1 - 1})
-* ( \array_get:Nn \l_hobby_distances_array {##1 - 1})
-+
-(3 * (\array_get:Nn \l_hobby_tension_out_array {##1 - 1}) - 1)
-* (\array_get:Nn \l_hobby_tension_in_array {##1})^2
-* (\array_get:Nn \l_hobby_tension_in_array {##1 + 1})
-* (\array_get:Nn \l_hobby_distances_array {##1})}
-}
-}
-%    \end{macrocode}
-%
-% Next, the superdiagonal.
-%    \begin{macrocode}
-  \int_step_inline:nnnn {1} {1} {\l_hobby_npoints_int - 2} {
-
-  \array_put:Nnx \l_hobby_matrix_c_array {##1} {\fp_to_tl:n
-{(\array_get:Nn \l_hobby_tension_in_array {##1})^2
-* (\array_get:Nn \l_hobby_tension_in_array {##1 - 1})
-* (\array_get:Nn \l_hobby_distances_array {##1 - 1})
-}}
-
-}
-%    \end{macrocode}
-%
-% Lastly (before the adjustments), the target vector.
-%    \begin{macrocode}
-  \int_step_inline:nnnn {1} {1} {\l_hobby_npoints_int - 2} {
-
-  \array_put:Nnx \l_hobby_matrix_d_array {##1} {\fp_to_tl:n
-{
-- (\array_get:Nn \l_hobby_psi_array {##1 + 1})
-* (\array_get:Nn \l_hobby_tension_out_array {##1})^2
-* (\array_get:Nn \l_hobby_tension_out_array {##1 - 1})
-* (\array_get:Nn \l_hobby_distances_array {##1 - 1})
-- (3 * (\array_get:Nn \l_hobby_tension_out_array {##1 - 1}) - 1)
-* (\array_get:Nn \l_hobby_psi_array {##1})
-* (\array_get:Nn \l_hobby_tension_in_array {##1})^2
-* (\array_get:Nn \l_hobby_tension_in_array {##1 + 1})
-* (\array_get:Nn \l_hobby_distances_array {##1})
-}
-}
-}
-%    \end{macrocode}
-%
-% Next, there are some adjustments at the ends.
-% These differ depending on whether the path is open or closed.
-%    \begin{macrocode}
-\bool_if:NTF \l_hobby_closed_bool {
-%    \end{macrocode}
-% Closed path
-%    \begin{macrocode}
-\array_put:Nnx \l_hobby_matrix_c_array {0} {\fp_to_tl:n {
-- (\array_get:Nn \l_hobby_distances_array {\l_hobby_npoints_int - 2})
-* (\array_get:Nn \l_hobby_tension_out_array {\l_hobby_npoints_int - 2})
-* (\array_get:Nn \l_hobby_tension_out_array {\l_hobby_npoints_int - 1})^2
-}}
-
-\array_put:Nnn \l_hobby_matrix_b_array {0} {1}
-\array_put:Nnn \l_hobby_matrix_d_array {0} {0}
-
-\array_put:Nnx \l_hobby_matrix_b_array {\l_hobby_npoints_int - 1} {\fp_to_tl:n {
-(\array_get:Nn \l_hobby_matrix_b_array {\l_hobby_npoints_int - 1})
-+ 1 
-}}
-
- \array_put:Nnx \l_hobby_matrix_d_array {\l_hobby_npoints_int - 1} {\fp_to_tl:n {
-- (\array_get:Nn \l_hobby_psi_array {1})
-* (\array_get:Nn \l_hobby_tension_out_array {\l_hobby_npoints_int -1})^2
-* (\array_get:Nn \l_hobby_tension_out_array {\l_hobby_npoints_int -2})
-* (\array_get:Nn \l_hobby_distances_array {\l_hobby_npoints_int - 2})
-- (3 * (\array_get:Nn \l_hobby_tension_out_array {\l_hobby_npoints_int - 2}) - 1)
-* (\array_get:Nn \l_hobby_psi_array {\l_hobby_npoints_int - 1})
-* (\array_get:Nn \l_hobby_tension_in_array {\l_hobby_npoints_int - 1})^2
-* (\array_get:Nn \l_hobby_tension_in_array {\l_hobby_npoints_int})
-* (\array_get:Nn \l_hobby_distances_array {\l_hobby_npoints_int -1})
-}
-}
-%    \end{macrocode}
-% We also need to populate the \(u\)-vector
-%    \begin{macrocode}
-  \array_put:Nnn \l_hobby_vector_u_array {0} {1}
-\array_put:Nnn \l_hobby_vector_u_array {\l_hobby_npoints_int - 1} {1}
-  \int_step_inline:nnnn {1} {1} {\l_hobby_npoints_int - 2} {
-  \array_put:Nnn \l_hobby_vector_u_array {##1} {0}
-  }
-%    \end{macrocode}
-% And define the significant entry in the \(v\)-vector.
-%    \begin{macrocode}
-\fp_set:Nn \l_hobby_matrix_v_fp {
-(\array_get:Nn \l_hobby_tension_out_array {\l_hobby_npoints_int -1})^2
-* (\array_get:Nn \l_hobby_tension_out_array {\l_hobby_npoints_int -2})
-* (\array_get:Nn \l_hobby_distances_array {\l_hobby_npoints_int -2})
-}
-}
-{
-%    \end{macrocode}
-% Open path.
-% First, we test to see if \(\theta_0\) has been specified.
-%    \begin{macrocode}
-\fp_compare:nTF { \l_hobby_out_angle_fp == \c_inf_fp }
-{
-  \array_put:Nnx \l_hobby_matrix_b_array {0}  {\fp_to_tl:n {
-  (\array_get:Nn \l_hobby_tension_in_array {1})^3
-* \l_hobby_in_curl_fp
-+
-(3 * (\array_get:Nn \l_hobby_tension_in_array {1}) - 1)
-* (\array_get:Nn \l_hobby_tension_out_array {0})^3
-}}
-
-  \array_put:Nnx \l_hobby_matrix_c_array {0} {\fp_to_tl:n {
-  (\array_get:Nn \l_hobby_tension_out_array {0})^3
-+
-(3 * (\array_get:Nn \l_hobby_tension_out_array {0}) - 1)
-* (\array_get:Nn \l_hobby_tension_in_array {1})^3
-* \l_hobby_in_curl_fp
-}}
-
-  \array_put:Nnx \l_hobby_matrix_d_array {0} {\fp_to_tl:n {
--(  (\array_get:Nn \l_hobby_tension_out_array {0})^3
-+
-(3 * (\array_get:Nn \l_hobby_tension_out_array {0}) - 1)
-* (\array_get:Nn \l_hobby_tension_in_array {1})^3
-* \l_hobby_in_curl_fp)
-* (\array_get:Nn \l_hobby_psi_array {1})
-}}
-  
-}
-{
-  \array_put:Nnn \l_hobby_matrix_b_array {0} {1}
-  \array_put:Nnn \l_hobby_matrix_c_array {0} {0}
-  \fp_set:Nn \l_hobby_tempa_fp { \l_hobby_out_angle_fp
-    - \array_get:Nn \l_hobby_angles_array {0}}
-%    \end{macrocode}
-% We want to ensure that these angles lie in the range \((-\pi,\pi]\).
-% So if the angle is bigger than \(\pi\), we subtract \(2 \pi\).
-% (It shouldn't be that we can get bigger than \(3 \pi\) - check this)
-%    \begin{macrocode}
-    \fp_compare:nT {\l_hobby_tempa_fp > \c_pi_fp }
-    {
-      \fp_sub:Nn \l_hobby_tempa_fp {2 * \c_pi_fp}
-    }
-%    \end{macrocode}
-% Similarly, we check to see if the angle is less than \(-\pi\).
-%    \begin{macrocode}
-    \fp_compare:nT {\l_hobby_tempa_fp < -\c_pi_fp }
-    {
-      \fp_add:Nn \l_hobby_tempa_fp {2 * \c_pi_fp}
-    }
-  \array_put:Nnx \l_hobby_matrix_d_array {0} {\fp_to_tl:N \l_hobby_tempa_fp}
-}
-%    \end{macrocode}
-%
-% Next, if \(\phi_n\) has been given.
-%    \begin{macrocode}
-\fp_compare:nTF { \l_hobby_in_angle_fp == \c_inf_fp }
-{
-
- \array_put:Nnx \l_hobby_matrix_b_array {\l_hobby_npoints_int - 1} {\fp_to_tl:n {
-\array_get:Nn \l_hobby_matrix_b_array {\l_hobby_npoints_int - 1}
-- (\array_get:Nn \l_hobby_tension_out_array {\l_hobby_npoints_int - 1})^2
-* (\array_get:Nn \l_hobby_tension_out_array {\l_hobby_npoints_int - 2})
-* (\array_get:Nn \l_hobby_distances_array {\l_hobby_npoints_int - 2})
-*
-((3 * (\array_get:Nn \l_hobby_tension_in_array {\l_hobby_npoints_int} ) - 1)
-* (\array_get:Nn \l_hobby_tension_out_array {\l_hobby_npoints_int - 1})^3 \l_tmpa_tl
-* \l_hobby_out_curl_fp
-+
-(\array_get:Nn \l_hobby_tension_in_array {\l_hobby_npoints_int })^3)
-/
-((3 * (\array_get:Nn \l_hobby_tension_out_array {\l_hobby_npoints_int -2}) - 1)
-* (\array_get:Nn \l_hobby_tension_in_array {\l_hobby_npoints_int})^3
-+
-( \array_get:Nn \l_hobby_tension_out_array {\l_hobby_npoints_int - 1})^3
-* \l_hobby_out_curl_fp)
-}}
-
- \array_put:Nnx \l_hobby_matrix_d_array {\l_hobby_npoints_int - 1} {\fp_to_tl:n {
-- (3 * (\array_get:Nn \l_hobby_tension_out_array {\l_hobby_npoints_int - 2}) - 1)
-* (\array_get:Nn \l_hobby_psi_array {\l_hobby_npoints_int - 1})
-* (\array_get:Nn \l_hobby_tension_in_array {\l_hobby_npoints_int - 1})^2
-* (\array_get:Nn \l_hobby_tension_in_array {\l_hobby_npoints_int})
-* (\array_get:Nn \l_hobby_distances_array {\l_hobby_npoints_int - 1})
-}}
-
-}
-{
-  \fp_set:Nn \l_hobby_tempa_fp { - \l_hobby_in_angle_fp + \c_pi_fp
-+ (\array_get:Nn \l_hobby_angles_array {\l_hobby_npoints_int - 1})}
-  \fp_compare:nT {\l_hobby_tempa_fp > \c_pi_fp }
-  {
-    \fp_sub:Nn \l_hobby_tempa_fp {2 * \c_pi_fp}
-  }
-  \fp_compare:nT {\l_hobby_tempa_fp < -\c_pi_fp }
-  {
-    \fp_add:Nn \l_hobby_tempa_fp {2 * \c_pi_fp}
-  }
-
-  \array_put:Nnx \l_hobby_phi_array {\l_hobby_npoints_int}
-  {\fp_to_tl:N \l_hobby_tempa_fp}
-
-   \array_put:Nnx \l_hobby_matrix_d_array  {\l_hobby_npoints_int - 1} {\fp_to_tl:n {
- \l_hobby_tempa_fp
- * (\array_get:Nn \l_hobby_tension_out_array {\l_hobby_npoints_int - 1})^2
-* (\array_get:Nn \l_hobby_tension_out_array {\l_hobby_npoints_int - 2})
-* (\array_get:Nn \l_hobby_distances_array {\l_hobby_npoints_int - 2})
--
-(3 * ( \array_get:Nn \l_hobby_tension_out_array {\l_hobby_npoints_int - 2}) - 1)
-* (\array_get:Nn \l_hobby_psi_array {\l_hobby_npoints_int - 1})
-* (\array_get:Nn \l_hobby_tension_in_array  {\l_hobby_npoints_int - 1})^2
-* (\array_get:Nn \l_hobby_tension_in_array {\l_hobby_npoints_int})
-* (\array_get:Nn \l_hobby_distances_array  {\l_hobby_npoints_int - 1}) }}
-}
-%    \end{macrocode}
-% End of adjustments for open paths.
-%    \begin{macrocode}
-}
-%    \end{macrocode}
-%
-% Now we have the tridiagonal matrix in place, we implement the solution.
-% We start with the forward eliminations.
-%    \begin{macrocode}
-\int_step_inline:nnnn {1} {1} {\l_hobby_npoints_int - 1} {
-
-  \array_put:Nnx \l_hobby_matrix_b_array {##1} {\fp_to_tl:n {
-  (\array_get:Nn \l_hobby_matrix_b_array {##1 - 1})
-* (\array_get:Nn \l_hobby_matrix_b_array {##1})
--
-(\array_get:Nn \l_hobby_matrix_c_array {##1 - 1})
-* (\array_get:Nn \l_hobby_matrix_a_array {##1})
-}}
-%    \end{macrocode}
-% The last time, we don't touch the \(C\)-vector.
-%    \begin{macrocode}
-  \int_compare:nT {##1 < \l_hobby_npoints_int - 1} {
-
-  \array_put:Nnx \l_hobby_matrix_c_array {##1} {\fp_to_tl:n {
-(\array_get:Nn \l_hobby_matrix_b_array {##1 - 1})
-    * (\array_get:Nn \l_hobby_matrix_c_array {##1})
-}}
-  }
-
-  \array_put:Nnx \l_hobby_matrix_d_array {##1} {\fp_to_tl:n {
-(\array_get:Nn \l_hobby_matrix_b_array {##1 - 1})
-  * (\array_get:Nn \l_hobby_matrix_d_array {##1})
--
-  (\array_get:Nn \l_hobby_matrix_d_array {##1 - 1})
-  * (\array_get:Nn \l_hobby_matrix_a_array {##1})
-}}
-%    \end{macrocode}
-% On a closed path, we also want to know \(M^{-1} u\) so need to do the elimination steps on \(u\) as well.
-%    \begin{macrocode}
-  \bool_if:NT \l_hobby_closed_bool {
-  \array_put:Nnx \l_hobby_vector_u_array {##1} {\fp_to_tl:n {
-(\array_get:Nn \l_hobby_matrix_b_array {##1 - 1})
-* (\array_get:Nn \l_hobby_vector_u_array {##1})
--
-(\array_get:Nn \l_hobby_vector_u_array {##1 - 1})
-* (\array_get:Nn \l_hobby_matrix_a_array {##1})
-}}
-}
-}
-%    \end{macrocode}
-% Now we start the back substitution.
-% The first step is slightly different to the general step.
-%    \begin{macrocode}
- \array_put:Nnx \l_hobby_theta_array  {\l_hobby_npoints_int - 1} {\fp_to_tl:n {
-(\array_get:Nn \l_hobby_matrix_d_array  {\l_hobby_npoints_int - 1})
-/ (\array_get:Nn \l_hobby_matrix_b_array  {\l_hobby_npoints_int - 1})
-}}
-%    \end{macrocode}
-% For a closed path, we need to work with \(u\) as well.
-%    \begin{macrocode}
-\bool_if:NT \l_hobby_closed_bool {
- \array_put:Nnx \l_hobby_vector_u_array  {\l_hobby_npoints_int - 1} {\fp_to_tl:n {
-  (\array_get:Nn \l_hobby_vector_u_array  {\l_hobby_npoints_int - 1})
-/ (\array_get:Nn \l_hobby_matrix_b_array  {\l_hobby_npoints_int - 1})
-}}
-}
-%    \end{macrocode}
-% Now we iterate over the vectors, doing the remaining back substitutions.
-%    \begin{macrocode}
-\int_step_inline:nnnn {\l_hobby_npoints_int - 2} {-1} {0} {
-
-  \array_put:Nnx \l_hobby_theta_array {##1} {\fp_to_tl:n {
-( (\array_get:Nn \l_hobby_matrix_d_array {##1})
-  - (\array_get:Nn \l_hobby_theta_array  {##1 + 1})
-  * (\array_get:Nn \l_hobby_matrix_c_array {##1})
-) / (\array_get:Nn \l_hobby_matrix_b_array {##1})
-}}
-}
-\bool_if:NT \l_hobby_closed_bool {
-%    \end{macrocode}
-% On a closed path, we also need to work out \(M^{-1} u\).
-%    \begin{macrocode}
-\int_step_inline:nnnn {\l_hobby_npoints_int - 2} {-1} {0} {
-  \array_put:Nnx \l_hobby_vector_u_array {##1} {\fp_to_tl:n
-{
-    ((\array_get:Nn \l_hobby_vector_u_array {##1})
-    - (\array_get:Nn \l_hobby_vector_u_array  {##1 + 1})
-    * (\array_get:Nn \l_hobby_matrix_c_array {##1})
-    ) / (\array_get:Nn \l_hobby_matrix_b_array {##1})
-}}
-}
-%    \end{macrocode}
-% Then we compute \(v^\top M^{-1}u\) and \(v^\top M^{-1} \theta\).
-% As \(v\) has a particularly simple form, these inner products are easy to compute.
-%    \begin{macrocode}
-
-\fp_set:Nn \l_hobby_tempb_fp {
-((\array_get:Nn \l_hobby_theta_array {1})
-* \l_hobby_matrix_v_fp
-- (\array_get:Nn \l_hobby_theta_array  {\l_hobby_npoints_int - 1})
-) / (
-(\array_get:Nn \l_hobby_vector_u_array {1})
-* \l_hobby_matrix_v_fp
-- (\array_get:Nn \l_hobby_vector_u_array  {\l_hobby_npoints_int - 1})
-+ 1
-)}
-
-\int_step_inline:nnnn {0} {1} {\l_hobby_npoints_int - 1} {
-
-  \array_put:Nnx \l_hobby_theta_array {##1} {\fp_to_tl:n {
-  (\array_get:Nn \l_hobby_theta_array {##1})
-  - (\array_get:Nn \l_hobby_vector_u_array {##1})
-  * \l_hobby_tempb_fp
-}}
-}
-}
-%    \end{macrocode}
-%
-% Now that we have computed the \(\theta_i\)s, we can quickly compute the \(\phi_i\)s.
-%
-%    \begin{macrocode}
-\int_step_inline:nnnn {1} {1} {\l_hobby_npoints_int - 1} {
-
-    \array_put:Nnx \l_hobby_phi_array {##1} {\fp_to_tl:n {
-      - (\array_get:Nn \l_hobby_psi_array {##1})
-      - (\array_get:Nn \l_hobby_theta_array {##1})
-  }}
-  }
-%    \end{macrocode}
-%
-% If the path is open, this works for all except \(\phi_n\).
-% If the path is closed, we can drop our added point.
-% Cheaply, of course.
-%    \begin{macrocode}
-\bool_if:NTF \l_hobby_closed_bool {
-  \int_gdecr:N \l_hobby_npoints_int
-}{
-%    \end{macrocode}
-% If \(\phi_n\) was not given, we compute it from \(\theta_{n-1}\).
-%    \begin{macrocode}
-\fp_compare:nT { \l_hobby_in_angle_fp == \c_inf_fp }
-{
- \array_put:Nnx \l_hobby_phi_array {\l_hobby_npoints_int} {\fp_to_tl:n {
-((3 * (\array_get:Nn \l_hobby_tension_in_array {\l_hobby_npoints_int}) - 1)
-* (\array_get:Nn \l_hobby_tension_out_array {\l_hobby_npoints_int - 1})^3
-* \l_hobby_out_curl_fp
-+
-(\array_get:Nn \l_hobby_tension_in_array {\l_hobby_npoints_int })^3)
-/
-((3 * (\array_get:Nn \l_hobby_tension_out_array  {\l_hobby_npoints_int -2}) - 1)
-* (\array_get:Nn \l_hobby_tension_in_array {\l_hobby_npoints_int})^3 \l_tmpa_tl
-+
-(\array_get:Nn \l_hobby_tension_out_array  {\l_hobby_npoints_int - 1})^3
-* \l_hobby_out_curl_fp)
-*
-(\array_get:Nn \l_hobby_theta_array  {\l_hobby_npoints_int -1})
-}}
-}
-}
-}
-%    \end{macrocode}
-% \end{macro}
-%
-%
-% \begin{macro}{\hobby_build_path:}
-% Once we've computed the angles, we build the actual path.
-%    \begin{macrocode}
-\cs_new:Nn \hobby_build_path:
-{
-%    \end{macrocode}
-% Next task is to compute the \(\rho_i\) and \(\sigma_i\).
-%    \begin{macrocode}
-\int_step_inline:nnnn {0} {1} {\l_hobby_npoints_int - 1} {
-
-  \fp_set:Nn \l_hobby_tempa_fp {\array_get:Nn \l_hobby_theta_array {##1}}
-
-  \fp_set:Nn \l_hobby_tempb_fp {\array_get:Nn \l_hobby_phi_array  {##1 + 1}}
-
-  \hobby_ctrllen:NVV \l_hobby_temps_fp \l_hobby_tempa_fp \l_hobby_tempb_fp
-
-   \array_put:Nnx \l_hobby_sigma_array {##1 + 1} {\fp_to_tl:N \l_hobby_temps_fp}
-
-  \hobby_ctrllen:NVV \l_hobby_temps_fp \l_hobby_tempb_fp \l_hobby_tempa_fp
-
-   \array_put:Nnx \l_hobby_rho_array {##1} {\fp_to_tl:N \l_hobby_temps_fp}
-
-  }
-%    \end{macrocode}
-% Lastly, we generate the coordinates of the control points.
-%    \begin{macrocode}
-\int_step_inline:nnnn {0} {1} {\l_hobby_npoints_int - 1} {
-\array_gput:Nnx \l_hobby_controla_array  {##1 + 1} {x = \fp_eval:n  {
-(\array_get:Nn \l_hobby_points_x_array {##1})
-+
-  (\array_get:Nn \l_hobby_distances_array {##1}) *
-  (\array_get:Nn \l_hobby_rho_array {##1}) *
-cos ( (\array_get:Nn \l_hobby_angles_array {##1})
-+
-  (\array_get:Nn \l_hobby_theta_array {##1}))
-/3
-}, y = \fp_eval:n {
-( \array_get:Nn \l_hobby_points_y_array {##1}) +
-  (\array_get:Nn \l_hobby_distances_array {##1}) *
-  (\array_get:Nn \l_hobby_rho_array {##1}) *
-sin ( (\array_get:Nn \l_hobby_angles_array {##1})
-+
-  (\array_get:Nn \l_hobby_theta_array {##1}))
-/3
-}
-}
-}
-\int_step_inline:nnnn {1} {1} {\l_hobby_npoints_int} {
-  \array_gput:Nnx \l_hobby_controlb_array {##1} {
-    x = \fp_eval:n {\array_get:Nn \l_hobby_points_x_array {##1}
-- (\array_get:Nn \l_hobby_distances_array  {##1 - 1})
-* (\array_get:Nn \l_hobby_sigma_array {##1})
-* cos((\array_get:Nn \l_hobby_angles_array  {##1 - 1})
-- (\array_get:Nn \l_hobby_phi_array {##1}))/3
-}, y = \fp_eval:n {
-  (\array_get:Nn \l_hobby_points_y_array {##1})
-- (\array_get:Nn \l_hobby_distances_array  {##1 - 1})
-* (\array_get:Nn \l_hobby_sigma_array {##1})
-* sin((\array_get:Nn \l_hobby_angles_array  {##1 - 1})
-- (\array_get:Nn \l_hobby_phi_array {##1}))/3
-} }
- }
-}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\hobbyinit}
-% Initialise the settings for Hobby's algorithm
-%    \begin{macrocode}
-\NewDocumentCommand \hobbyinit {m m m} {
-  \hobby_set_cmds:nnn#1#2#3
-  \hobby_clear_path:
-}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\hobbyaddpoint}
-% This adds a point, possibly with tensions, to the current stack.
-%    \begin{macrocode}
-\NewDocumentCommand \hobbyaddpoint { m } {
-    \keys_set:nn { hobby/read in all }
-    {
-      tension~out,
-      tension~in,
-      excess~angle,
-      blank,
-      break,
-      #1
-    }
-    \tl_if_eq:VnTF {\l_tmpa_tl} {true}
-     {\tl_set:Nn \l_tmpa_tl {2}}
-     {
-       \tl_if_eq:VnTF {\l_tmpa_tl} {soft}
-       {\tl_set:Nn \l_tmpa_tl {0}}
-       {\tl_set:Nn \l_tmpa_tl {1}}
-     }
-    \tl_if_eq:VnTF {\l_tmpb_tl} {true}
-     {\tl_put_right:Nn \l_tmpa_tl {1}}
-     {\tl_put_right:Nn \l_tmpa_tl {0}}
-    \array_gpush:Nx \l_hobby_actions_array {\l_tmpa_tl}
-    \array_gpush:Nx \l_hobby_tension_out_array {\fp_to_tl:N \l_hobby_tempc_fp}
-    \array_gpush:Nx \l_hobby_tension_in_array {\fp_to_tl:N \l_hobby_tempd_fp}
-    \array_gpush:Nx \l_hobby_excess_angle_array {\fp_to_tl:N \l_hobby_temps_fp}
-    \array_gpush:Nx \l_hobby_points_array {
-      x = \fp_use:N \l_hobby_tempa_fp,
-      y = \fp_use:N \l_hobby_tempb_fp }
-    \array_gpush:Nx \l_hobby_points_x_array {\fp_to_tl:N \l_hobby_tempa_fp}
-    \array_gpush:Nx \l_hobby_points_y_array {\fp_to_tl:N \l_hobby_tempb_fp}
-}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\hobbysetparams}
-% This sets the parameters for the curve.
-%    \begin{macrocode}
-\NewDocumentCommand \hobbysetparams { m } {
-  \keys_set:nn { hobby / read in params }
-  {
-    #1
-  }
-}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\hobby_set_cmds:nnn}
-% The path-generation code doesn't know what to actually do with the path so the initialisation code will set some macros to do that.
-% This is an auxiliary command that sets these macros.
-%    \begin{macrocode}
-\cs_new:Npn \hobby_moveto:nnn #1#2#3 {}
-\cs_new:Npn \hobby_curveto:nnn #1#2#3 {}
-\cs_new:Npn \hobby_close:n #1 {}
-\cs_generate_variant:Nn \hobby_moveto:nnn {VVV,nnV}
-\cs_generate_variant:Nn \hobby_curveto:nnn {VVV}
-\cs_generate_variant:Nn \hobby_close:n {V}
-\cs_new:Nn \hobby_set_cmds:nnn {
-  \cs_gset_eq:NN \hobby_moveto:nnn #1
-  \cs_gset_eq:NN \hobby_curveto:nnn #2
-  \cs_gset_eq:NN \hobby_close:n #3
-}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\hobbygenpath}
-% This is the user (well, sort of) command that generates the curve.
-%    \begin{macrocode}
-\NewDocumentCommand \hobbygenpath { } {
-  \array_if_empty:NF \l_hobby_points_array {
-    \hobby_gen_path:
-  }
-}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\hobbygenifnecpath}
-% If the named path doesn't exist, it is generated and named.
-% If it does exist, we restore it.
-% Either way, we save it to the aux file.
-%    \begin{macrocode}
-\NewDocumentCommand \hobbygenifnecpath { m } {
-  \tl_if_exist:cTF {g_hobby_#1_path}
-  {
-    \tl_use:c {g_hobby_#1_path}
-  }
-  {
-    \hobby_gen_path:
-  }
-  \hobby_save_path:n {#1}
-  \hobby_save_path_to_aux:x {#1}
-}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\hobbygenifnecusepath}
-% If the named path doesn't exist, it is generated and named.
-% If it does exist, we restore it.
-% Either way, we save it to the aux file.
-%    \begin{macrocode}
-\NewDocumentCommand \hobbygenuseifnecpath { m } {
-  \tl_if_exist:cTF {g_hobby_#1_path}
-  {
-    \tl_use:c {g_hobby_#1_path}
-  }
-  {
-    \hobby_gen_path:
-  }
-  \hobby_save_path:n {#1}
-  \hobby_save_path_to_aux:x {#1}
-  \hobby_use_path:
-}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\hobbyusepath}
-% This is the user (well, sort of) command that uses the last generated curve.
-%    \begin{macrocode}
-\NewDocumentCommand \hobbyusepath { m } {
-  \hobbysetparams{#1}
-  \hobby_use_path:
-}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\hobbysavepath}
-% This is the user (well, sort of) command that uses the last generated curve.
-%    \begin{macrocode}
-\NewDocumentCommand \hobbysavepath { m } {
-  \hobby_save_path:n {#1}
-}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\hobbyrestorepath}
-% This is the user (well, sort of) command that uses the last generated curve.
-%    \begin{macrocode}
-\NewDocumentCommand \hobbyrestorepath { m } {
-  \tl_if_exist:cT {g_hobby_#1_path} {
-    \tl_use:c {g_hobby_#1_path}
-  }
-}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\hobbyshowpath}
-% This is the user (well, sort of) command that uses the last generated curve.
-%    \begin{macrocode}
-\NewDocumentCommand \hobbyshowpath { m } {
-  \tl_if_exist:cT {g_hobby_#1_path} {
-    \tl_show:c {g_hobby_#1_path}
-  }
-}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\hobbygenusepath}
-% This is the user (well, sort of) command that generates a curve and uses it.
-%    \begin{macrocode}
-\NewDocumentCommand \hobbygenusepath { } {
-  \array_if_empty:NF \l_hobby_points_array {
-    \hobby_gen_path:
-    \hobby_use_path:
-  }
-}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\hobbyclearpath}
-% This is the user (well, sort of) command that generates a curve and uses it.
-%    \begin{macrocode}
-\NewDocumentCommand \hobbyclearpath { } {
-  \hobby_clear_path:
-}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\hobby_use_path:}
-% This is the command that uses the curve.
-% As the curve data is stored globally, the same data can be reused by calling this function more than once without calling the generating function.
-%    \begin{macrocode}
-\tl_new:N \l_tmpc_tl
-\cs_new:Nn \hobby_use_path: {
-  \bool_if:NT \l_hobby_disjoint_bool {
-    \array_get:NnN \l_hobby_points_array {0} \l_tmpa_tl
-    \hobby_moveto:nnV {} {} \l_tmpa_tl
-  }
-  \int_step_inline:nnnn {1} {1} {\l_hobby_npoints_int} {
-    \array_get:NnN \l_hobby_controla_array {##1} \l_tmpa_tl
-    \array_get:NnN \l_hobby_controlb_array {##1} \l_tmpb_tl
-    \array_get:NnN \l_hobby_points_array {##1} \l_tmpc_tl
-    \array_get:NnN \l_hobby_actions_array {##1} \l_tmpd_tl
-    \int_compare:nNnTF {\tl_item:Nn \l_tmpd_tl {1}} = {\l_hobby_draw_int} {
-      \hobby_curveto:VVV \l_tmpa_tl \l_tmpb_tl \l_tmpc_tl
-    }{
-      \bool_gset_false:N \l_hobby_closed_bool
-      \hobby_moveto:VVV \l_tmpa_tl \l_tmpb_tl \l_tmpc_tl
-    }
-    \tl_if_eq:xnTF {\tl_item:Nn \l_tmpd_tl {2}} {1} {
-      \bool_gset_false:N \l_hobby_closed_bool
-      \hobby_moveto:VVV \l_tmpa_tl \l_tmpb_tl \l_tmpc_tl
-    }{}
-  }
-  \bool_if:NT \l_hobby_closed_bool {
-    \array_get:NnN \l_hobby_points_array {0} \l_tmpa_tl
-    \hobby_close:V \l_tmpa_tl
-  }
-}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\hobby_save_path:n}
-% This command saves all the data needed to reinvoke the curve in a global token list that can be used to restore it afterwards.
-%    \begin{macrocode}
-\cs_new:Nn \hobby_save_path:n {
-  \tl_clear:N \l_tmpa_tl
-  \tl_put_right:Nn \l_tmpa_tl {\int_gset:Nn \l_hobby_npoints_int}
-  \tl_put_right:Nx \l_tmpa_tl {{\int_use:N \l_hobby_npoints_int}}
-  \bool_if:NTF \l_hobby_disjoint_bool {
-    \tl_put_right:Nn \l_tmpa_tl {\bool_gset_true:N}
-  }{
-    \tl_put_right:Nn \l_tmpa_tl {\bool_gset_false:N}
-  }
-  \tl_put_right:Nn \l_tmpa_tl {\l_hobby_disjoint_bool}
-  \bool_if:NTF \l_hobby_closed_bool {
-    \tl_put_right:Nn \l_tmpa_tl {\bool_gset_true:N}
-  }{
-    \tl_put_right:Nn \l_tmpa_tl {\bool_gset_false:N}
-  }
-  \tl_put_right:Nn \l_tmpa_tl {\l_hobby_closed_bool}
-  \tl_put_right:Nn \l_tmpa_tl {\array_gclear:N \l_hobby_points_array}
-  \array_map_inline:Nn \l_hobby_points_array {
-    \tl_put_right:Nn \l_tmpa_tl {
-      \array_gput:Nnn \l_hobby_points_array {##1} {##2}
-    }
-  }
-  \tl_put_right:Nn \l_tmpa_tl {\array_gclear:N \l_hobby_actions_array}
-  \array_map_inline:Nn \l_hobby_actions_array {
-    \tl_put_right:Nn \l_tmpa_tl {
-      \array_gput:Nnn \l_hobby_actions_array {##1} {##2}
-    }
-  }
-  \tl_put_right:Nn \l_tmpa_tl {\array_gclear:N \l_hobby_controla_array}
-  \array_map_inline:Nn \l_hobby_controla_array {
-    \tl_put_right:Nn \l_tmpa_tl {
-      \array_gput:Nnn \l_hobby_controla_array {##1} {##2}
-    }
-  }
-  \tl_put_right:Nn \l_tmpa_tl {\array_gclear:N \l_hobby_controlb_array}
-  \array_map_inline:Nn \l_hobby_controlb_array {
-    \tl_put_right:Nn \l_tmpa_tl {
-      \array_gput:Nnn \l_hobby_controlb_array {##1} {##2}
-    }
-  }
-  \tl_gclear_new:c {g_hobby_#1_path}
-  \tl_gset_eq:cN {g_hobby_#1_path} \l_tmpa_tl
-}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\hobby_save_path_to_aux:n}
-%    \begin{macrocode}
-\int_set:Nn \l_tmpa_int {\char_value_catcode:n {`@}}
-\char_set_catcode_letter:N @
-\cs_new:Npn \hobby_save_path_to_aux:n #1 {
-  \bool_if:nT {
-    \tl_if_exist_p:c {g_hobby_#1_path}
-    &&
-    ! \tl_if_exist_p:c {g_hobby_#1_path_saved}
-    &&
-    \l_hobby_save_aux_bool
-  }
-  {
-    \tl_clear:N \l_tmpa_tl
-    \tl_put_right:Nn \l_tmpa_tl {
-      \ExplSyntaxOn
-      \tl_gclear_new:c {g_hobby_#1_path}
-      \tl_gput_right:cn {g_hobby_#1_path}
-    }
-    \tl_put_right:Nx \l_tmpa_tl {
-      {\tl_to_str:c {g_hobby_#1_path}}
-    }
-    \tl_put_right:Nn \l_tmpa_tl {
-      \ExplSyntaxOff
-    }
-    \protected at write\@auxout{}{
-      \tl_to_str:N \l_tmpa_tl
-    }
-    \tl_new:c {g_hobby_#1_path_saved}
-  }
-}
-\char_set_catcode:nn {`@} {\l_tmpa_int}
-\cs_generate_variant:Nn \hobby_save_path_to_aux:n {x}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\hobby_clear_path:}
-%    \begin{macrocode}
-\cs_new:Nn \hobby_clear_path:
-{
-\array_gclear:N \l_hobby_points_array
-\array_gclear:N \l_hobby_points_x_array
-\array_gclear:N \l_hobby_points_y_array
-\array_gclear:N \l_hobby_angles_array
-\array_gclear:N \l_hobby_actions_array
-\array_gclear:N \l_hobby_distances_array
-\array_gclear:N \l_hobby_tension_out_array
-\array_gclear:N \l_hobby_tension_in_array
-\array_gclear:N \l_hobby_excess_angle_array
-\array_gclear:N \l_hobby_matrix_a_array
-\array_gclear:N \l_hobby_matrix_b_array
-\array_gclear:N \l_hobby_matrix_c_array
-\array_gclear:N \l_hobby_matrix_d_array
-\array_gclear:N \l_hobby_vector_u_array
-\array_gclear:N \l_hobby_psi_array
-\array_gclear:N \l_hobby_theta_array
-\array_gclear:N \l_hobby_phi_array
-\array_gclear:N \l_hobby_sigma_array
-\array_gclear:N \l_hobby_rho_array
-\array_gclear:N \l_hobby_controla_array
-\array_gclear:N \l_hobby_controlb_array
-\bool_gset_false:N \l_hobby_closed_bool
-\bool_gset_false:N \l_hobby_disjoint_bool
-
-  \int_gset:Nn \l_hobby_npoints_int {-1}
-  \int_gset:Nn \l_hobby_draw_int {1}
-  \fp_gset_eq:NN \l_hobby_in_angle_fp \c_inf_fp
-  \fp_gset_eq:NN \l_hobby_out_angle_fp \c_inf_fp
-  \fp_gset_eq:NN \l_hobby_in_curl_fp \c_one_fp
-  \fp_gset_eq:NN \l_hobby_out_curl_fp \c_one_fp
-}
-%    \end{macrocode}
-% \end{macro}
-%    \begin{macrocode}
-\ExplSyntaxOff
-%    \end{macrocode}
-% \iffalse
-%</hobby>
-% \fi
-%
-% \subsection{PGF Library}
-%
-% \iffalse
-%<*pgflibrary>
-% \fi
-% 
-% The PGF level is very simple.
-% All we do is set up the path-construction commands that get passed to the path-generation function.
-%    \begin{macrocode}
-\input{hobby.code.tex}
-%    \end{macrocode}
-% Points are communicated as key-pairs.
-% These keys translate from the \LaTeX3 style points to PGF points.
-%    \begin{macrocode}
-\pgfkeys{
-  /pgf/hobby/.is family,
-  /pgf/hobby/.cd,
-  x/.code={\pgf at x=#1cm},
-  y/.code={\pgf at y=#1cm}
-}
-%    \end{macrocode}
-%
-% \begin{macro}{hobbyatan2}
-% The original PGF version of \Verb+atan2+ had the arguments the wrong way around.
-% This was fixed in the CVS version in July 2013, but as old versions are likely to be in use for some time, we define a wrapper function that ensures that the arguments are correct.
-%    \begin{macrocode}
-\pgfmathparse{atan2(0,1)}
-\def\hobby at temp{0.0}
-\ifx\pgfmathresult\hobby at temp
-  \pgfmathdeclarefunction{hobbyatan2}{2}{%
-    \pgfmathatantwo@{#1}{#2}%
-  }
-\else
-  \pgfmathdeclarefunction{hobbyatan2}{2}{%
-    \pgfmathatantwo@{#2}{#1}%
-  }
-\fi
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\hobby at curveto}
-% This is passed to the path-generation code to translate the path into a PGF path.
-%    \begin{macrocode}
-\def\hobby at curveto#1#2#3{%
-  \pgfpathcurveto{\hobby at topgf{#1}}{\hobby at topgf{#2}}{\hobby at topgf{#3}}%
-}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\hobby at moveto}
-% This is passed to the path-generation code to translate the path into a PGF path.
-%    \begin{macrocode}
-\def\hobby at moveto#1#2#3{%
-  \pgfpathmoveto{\hobby at topgf{#3}}%
-}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\hobby at topgf}
-% Translates a \LaTeX3 point to a PGF point.
-%    \begin{macrocode}
-\def\hobby at topgf#1{%
-    \pgfqkeys{/pgf/hobby}{#1}%
-}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\hobby at close}
-% Closes a path.
-%    \begin{macrocode}
-\def\hobby at close#1{%
-  \pgfpathclose
-}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\pgfpathhobby}
-% Low-level interface to the hobby construction.
-% This sets up the commands and starts the iterator.
-%    \begin{macrocode}
-\def\pgfpathhobby{%
-  \pgfutil at ifnextchar\bgroup{\pgfpath at hobby}{\pgfpath at hobby{}}}
-\def\pgfpath at hobby#1{%
-  \hobbyinit\hobby at moveto\hobby at curveto\hobby at close
-  \hobbysetparams{#1}%
-  \pgfmathsetmacro\hobby at x{\the\pgf at path@lastx/1cm}%
-  \pgfmathsetmacro\hobby at y{\the\pgf at path@lasty/1cm}%
-  \hobbyaddpoint{x = \hobby at x, y = \hobby at y}%
-}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\pgfpathhobbypt}
-% Adds a point to the construction
-%    \begin{macrocode}
-\def\pgfpathhobbypt#1{%
-  #1%
-  \pgfmathsetmacro\hobby at x{\the\pgf at x/1cm}%
-  \pgfmathsetmacro\hobby at y{\the\pgf at y/1cm}%
-  \pgfutil at ifnextchar\bgroup{\pgfpathhobbyptparams}{\pgfpathhobbyptparams{}}%
-}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\pgfpathhobbyptparams}
-%    \begin{macrocode}
-\def\pgfpathhobbyptparams#1{%
-  \hobbyaddpoint{#1,x = \hobby at x, y = \hobby at y}%
-}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\pgfpathhobbyend}
-%    \begin{macrocode}
-\def\pgfpathhobbyend{%
-  \ifhobby at externalise
-    \ifx\hobby at path@name\pgfutil at empty
-      \hobbygenusepath
-    \else
-      \hobbygenuseifnecpath{\hobby at path@name}%
-    \fi
-  \else
-    \hobbygenusepath
-  \fi
-  \ifx\hobby at path@name\pgfutil at empty
-  \else
-    \hobbysavepath{\hobby at path@name}%
-  \fi
-  \global\let\hobby at path@name=\pgfutil at empty
-}
-%    \end{macrocode}
-% \end{macro}
-%
-%
-% Plot handlers
-%
-% \begin{macro}{\pgfplothanderhobby}
-% Basic plot handler; uses full algorithm but therefore expensive
-%    \begin{macrocode}
-\def\pgfplothandlerhobby{%
-  \def\pgf at plotstreamstart{%
-    \hobbyinit\hobby at moveto\hobby at curveto\hobby at close
-    \global\let\pgf at plotstreampoint=\pgf at plot@hobby at firstpt
-    \global\let\pgf at plotstreamspecial=\pgfutil at gobble
-    \gdef\pgf at plotstreamend{%
-      \ifhobby at externalise
-       \ifx\hobby at path@name\pgfutil at empty
-        \hobbygenusepath
-       \else
-        \hobbygenuseifnecpath{\hobby at path@name}%
-       \fi
-      \else
-       \hobbygenusepath
-      \fi
-      \ifx\hobby at path@name\pgfutil at empty
-      \else
-       \hobbysavepath{\hobby at path@name}%
-      \fi
-      \global\let\hobby at path@name=\pgfutil at empty
-    }%
-    \let\tikz at scan@point at options=\pgfutil at empty
-  }
-}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\pgfplothandlerclosedhobby}
-% Same as above but produces a closed curve
-%    \begin{macrocode}
-\def\pgfplothandlerclosedhobby{%
-  \def\pgf at plotstreamstart{%
-    \hobbyinit\hobby at moveto\hobby at curveto\hobby at close
-    \hobbysetparams{closed=true,disjoint=true}%
-    \global\let\pgf at plotstreampoint=\pgf at plot@hobby at firstpt
-    \global\let\pgf at plotstreamspecial=\pgfutil at gobble
-    \gdef\pgf at plotstreamend{%
-      \ifhobby at externalise
-       \ifx\hobby at path@name\pgfutil at empty
-        \hobbygenusepath
-       \else
-        \hobbygenuseifnecpath{\hobby at path@name}%
-       \fi
-      \else
-       \hobbygenusepath
-      \fi
-      \ifx\hobby at path@name\pgfutil at empty
-      \else
-       \hobbysavepath{\hobby at path@name}%
-      \fi
-      \global\let\hobby at path@name=\pgfutil at empty
-    }%
-  }
-}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\pgf at plot@hobby at firstpt}
-% First point, move or line as appropriate and then start the algorithm.
-%    \begin{macrocode}
-\def\pgf at plot@hobby at firstpt#1{%
-  \pgf at plot@first at action{#1}%
-  \pgf at plot@hobby at handler{#1}%
-  \global\let\pgf at plotstreampoint=\pgf at plot@hobby at handler
-}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\pgf at plot@hobby at handler}
-% Add points to the array for the algorithm to work on.
-%    \begin{macrocode}
-\def\pgf at plot@hobby at handler#1{%
-    #1%
-    \pgfmathsetmacro\hobby at x{\the\pgf at x/1cm}%
-    \pgfmathsetmacro\hobby at y{\the\pgf at y/1cm}%
-    \hobbyaddpoint{x = \hobby at x, y = \hobby at y}%
-  }
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\pgfplothandlerquickhobby}
-% Uses the ``quick'' algorithm.
-%    \begin{macrocode}
-\def\pgfplothandlerquickhobby{%
-  \def\pgf at plotstreamstart{%
-    \global\let\hobby at quick@curveto=\pgfpathcurveto
-    \global\let\pgf at plotstreampoint=\pgf at plot@qhobby at firstpt
-    \global\let\pgf at plotstreamspecial=\pgfutil at gobble
-    \global\let\pgf at plotstreamend=\pgf at plot@qhobby at end
-  }
-}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\pgf at plot@qhobby at firstpt}
-% Carry out first action (move or line) and save point.
-%    \begin{macrocode}
-\def\pgf at plot@qhobby at firstpt#1{%
-  #1%
-  \edef\hobby at temp{\noexpand\pgf at plot@first at action{\noexpand\pgfqpoint{\the\pgf at x}{\the\pgf at y}}}\hobby at temp%
-  \xdef\hobby at qpoints{\noexpand\pgfqpoint{\the\pgf at x}{\the\pgf at y}}%
-  \gdef\hobby at qpointa{}%
-  \gdef\hobby at angle{}%
-  \global\let\pgf at plotstreampoint=\pgf at plot@qhobby at secondpt
-}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\pgf at plot@qhobby at secondpt}
-% Also need to save second point.
-%    \begin{macrocode}
-\def\pgf at plot@qhobby at secondpt#1{%
-  #1%
-  \xdef\hobby at qpointa{\noexpand\pgfqpoint{\the\pgf at x}{\the\pgf at y}}%
-  \global\let\pgf at plotstreampoint=\pgf at plot@qhobby at handler
-}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\pgf at plot@qhobby at handler}
-% Wrapper around the computation macro that saves the variables globally.
-%    \begin{macrocode}
-\def\pgf at plot@qhobby at handler#1{%
-  #1
-  \edef\hobby at temp{\noexpand\hobby at quick@compute{\noexpand\pgfqpoint{\the\pgf at x}{\the\pgf at y}}}\hobby at temp
-  \global\let\hobby at qpointa=\hobby at qpointa
-  \global\let\hobby at qpoints=\hobby at qpoints
-  \global\let\hobby at angle=\hobby at angle
-%    \end{macrocode}
-% Also need to save some data for the last point
-%    \begin{macrocode}
-  \global\let\hobby at thetaone=\hobby at thetaone
-  \global\let\hobby at phitwo=\hobby at phitwo
-  \global\let\hobby at done=\hobby at done
-  \global\let\hobby at omegaone=\hobby at omegaone
-}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\pgf at plot@qhobby at end}
-%    Wrapper around the finalisation step.
-%    \begin{macrocode}
-\def\pgf at plot@qhobby at end{%
-  \hobby at quick@computeend
-}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\hobby at sf}
-% Working with points leads to computations out of range so we scale to get them into the computable arena.
-%    \begin{macrocode}
-\pgfmathsetmacro\hobby at sf{10cm}
-%    \end{macrocode}
-% \end{macro}
-%
-%
-% \begin{macro}{\hobby at quick@compute}
-% This is the macro that does all the work of computing the control points.
-% The argument is the current point, \Verb+\hobby at qpointa+ is the middle point, and \Verb+\hobby at qpoints+ is the first point.
-%    \begin{macrocode}
-\def\hobby at quick@compute#1{%
-%    \end{macrocode}
-% Save the current (second - counting from zero) point in \Verb+\pgf at xb+ and \Verb+\pgf at yb+.
-%    \begin{macrocode}
-  #1%
-  \pgf at xb=\pgf at x
-  \pgf at yb=\pgf at y
-%    \end{macrocode}
-% Save the previous (first) point in \Verb+\pgf at xa+ and \Verb+\pgf at ya+.
-%    \begin{macrocode}
-  \hobby at qpointa
-  \pgf at xa=\pgf at x
-  \pgf at ya=\pgf at y
-%    \end{macrocode}
-% Adjust so that \Verb+(\pgf at xb,\pgf at yb)+ is the vector from second to third.
-% Then compute and store the distance and angle of this vector.
-% We view this as the vector \emph{from} the midpoint and everything to do with that point has the suffix \Verb+one+.
-% Note that we divide by the scale factor here.
-%    \begin{macrocode}
-  \advance\pgf at xb by -\pgf at xa
-  \advance\pgf at yb by -\pgf at ya
-  \pgfmathsetmacro\hobby at done{sqrt((\pgf at xb/\hobby at sf)^2 + (\pgf at yb/\hobby at sf)^2)}%
-  \pgfmathsetmacro\hobby at omegaone{rad(hobbyatan2(\pgf at yb,\pgf at xb))}%
-%    \end{macrocode}
-% Now we do the same with the vector from the zeroth to the first point.
-%    \begin{macrocode}
-  \hobby at qpoints
-  \advance\pgf at xa by -\pgf at x
-  \advance\pgf at ya by -\pgf at y
-  \pgfmathsetmacro\hobby at dzero{sqrt((\pgf at xa/\hobby at sf)^2 + (\pgf at ya/\hobby at sf)^2)}%
-  \pgfmathsetmacro\hobby at omegazero{rad(hobbyatan2(\pgf at ya,\pgf at xa))}%
-%    \end{macrocode}
-% \Verb+\hobby at psi+ is the angle subtended at the midpoint.
-% We adjust to ensure that it is in the right range.
-%    \begin{macrocode}
-  \pgfmathsetmacro\hobby at psi{\hobby at omegaone - \hobby at omegazero}%
-  \pgfmathsetmacro\hobby at psi{\hobby at psi > pi ? \hobby at psi - 2*pi : \hobby at psi}%
-  \pgfmathsetmacro\hobby at psi{\hobby at psi < -pi ? \hobby at psi + 2*pi : \hobby at psi}%
-%    \end{macrocode}
-% Now we test to see if we're on the first run or not.
-% If the first, we have no incoming angle.
-%    \begin{macrocode}
-  \ifx\hobby at angle\pgfutil at empty
-%    \end{macrocode}
-% First.
-%    \begin{macrocode}
-  \pgfmathsetmacro\hobby at thetaone{-\hobby at psi * \hobby at done%
-/(\hobby at done + \hobby at dzero)}%
-  \pgfmathsetmacro\hobby at thetazero{-\hobby at psi - \hobby at thetaone}%
-  \let\hobby at phione=\hobby at thetazero
-  \let\hobby at phitwo=\hobby at thetaone
-  \else
-%    \end{macrocode}
-% Second or later.
-%    \begin{macrocode}
-  \let\hobby at thetazero=\hobby at angle
-  \pgfmathsetmacro\hobby at thetaone{%
-  -(2 * \hobby at psi + \hobby at thetazero) * \hobby at done%
-  / (2 * \hobby at done + \hobby at dzero)}%
-  \pgfmathsetmacro\hobby at phione{-\hobby at psi - \hobby at thetaone}%
-  \let\hobby at phitwo=\hobby at thetaone
-  \fi
-%    \end{macrocode}
-% Save the outgoing angle.
-%    \begin{macrocode}
-  \let\hobby at angle=\hobby at thetaone
-%    \end{macrocode}
-% Compute the control points from the angles.
-%    \begin{macrocode}
-  \hobby at quick@ctrlpts{\hobby at thetazero}{\hobby at phione}{\hobby at qpoints}{\hobby at qpointa}{\hobby at dzero}{\hobby at omegazero}%
-%    \end{macrocode}
-% Now call the call-back function 
-%    \begin{macrocode}
-  \edef\hobby at temp{\noexpand\hobby at quick@curveto{\noexpand\pgfqpoint{\the\pgf at xa}{\the\pgf at ya}}{\noexpand\pgfqpoint{\the\pgf at xb}{\the\pgf at yb}}{\noexpand\pgfqpoint{\the\pgf at x}{\the\pgf at y}}}%
-\hobby at temp
-%    \end{macrocode}
-% Cycle the points round for the next iteration.
-%    \begin{macrocode}
-  \global\let\hobby at qpoints=\hobby at qpointa
-  #1
-  \xdef\hobby at qpointa{\noexpand\pgfqpoint{\the\pgf at x}{\the\pgf at y}}%
-%    \end{macrocode}
-% Save needed values in global macros
-%    \begin{macrocode}
-  \global\let\hobby at angle=\hobby at angle
-  \global\let\hobby at phitwo=\hobby at phitwo
-  \global\let\hobby at thetaone=\hobby at thetaone
-\global\let\hobby at done=\hobby at done
-\global\let\hobby at omegaone=\hobby at omegaone
-}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\hobby at wuick@computeend}
-% This is the additional code for the final run.
-%    \begin{macrocode}
-\def\hobby at quick@computeend{%
-%    \end{macrocode}
-% Compute the control points for the second part of the curve and add that to the path.
-%    \begin{macrocode}
-  \hobby at quick@ctrlpts{\hobby at thetaone}{\hobby at phitwo}{\hobby at qpoints}{\hobby at qpointa}{\hobby at done}{\hobby at omegaone}%
-%    \end{macrocode}
-% Now call the call-back function 
-%    \begin{macrocode}
-  \edef\hobby at temp{\noexpand\hobby at quick@curveto{\noexpand\pgfqpoint{\the\pgf at xa}{\the\pgf at ya}}{\noexpand\pgfqpoint{\the\pgf at xb}{\the\pgf at yb}}{\noexpand\pgfqpoint{\the\pgf at x}{\the\pgf at y}}}%
-\hobby at temp
-}%
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\hobby at quick@ctrlpts}
-% Compute the control points from the angles and points given.
-%    \begin{macrocode}
-\def\hobby at quick@ctrlpts#1#2#3#4#5#6{%
-  \pgfmathsetmacro\hobby at alpha{%
-    sqrt(2) * (sin(#1 r) - 1/16 * sin(#2 r))%
-* (sin(#2 r) - 1/16 * sin(#1 r))%
- * (cos(#1 r) - cos(#2 r))}%
-  \pgfmathsetmacro\hobby at rho{%
-    (2 + \hobby at alpha)/(1 + (1 - (3 - sqrt(5))/2)%
- * cos(#1 r) + (3 - sqrt(5))/2 * cos(#2 r))}%
-  \pgfmathsetmacro\hobby at sigma{%
-    (2 - \hobby at alpha)/(1 + (1 - (3 - sqrt(5))/2)%
-  * cos(#2 r) +  (3 - sqrt(5))/2 * cos(#1 r))}%
-  #3%
-  \pgf at xa=\pgf at x
-  \pgf at ya=\pgf at y
-  \pgfmathsetlength\pgf at xa{%
-    \pgf at xa + #5 * \hobby at rho%
-  * cos((#1 + #6) r)/3*\hobby at sf}%
-  \pgfmathsetlength\pgf at ya{%
-    \pgf at ya + #5 * \hobby at rho%
-  * sin((#1 + #6) r)/3*\hobby at sf}%
-  #4%
-  \pgf at xb=\pgf at x
-  \pgf at yb=\pgf at y
-  \pgfmathsetlength\pgf at xb{%
-    \pgf at xb - #5 * \hobby at sigma%
-  * cos((-#2 + #6) r)/3*\hobby at sf}%
-  \pgfmathsetlength\pgf at yb{%
-    \pgf at yb - #5 * \hobby at sigma%
-  * sin((-#2 + #6) r)/3*\hobby at sf}%
-  #4%
-}
-%    \end{macrocode}
-% \end{macro}
-%
-% \iffalse
-%</pgflibrary>
-% \fi
-%
-% \subsection{TikZ Library}
-%
-% \iffalse
-%<*tikzlibrary>
-% \fi
-% 
-%    \begin{macrocode}
-\usepgflibrary{hobby}
-\let\hobby at this@opts=\pgfutil at empty
-\let\hobby at next@opts=\pgfutil at empty
-\let\hobby at action=\pgfutil at empty
-\let\hobby at path@name=\pgfutil at empty
-\newif\ifhobby at externalise
-%    \end{macrocode}
-%
-% We set various TikZ keys.
-% These include the \Verb+to path+ constructor and all the various parameters that will eventually get passed to the path-generation code.
-%    \begin{macrocode}
-\def\hobby at point@options{}%
-\tikzset{
-  curve through/.style={
-    to path={
-      \pgfextra{
-        \expandafter\curvethrough\expandafter[\hobby at next@opts]{%
-          (\tikztostart) .. #1 .. (\tikztotarget)%
-        }
-      }
-    }
-  },
-  tension in/.code = {%
-    \expandafter\gdef\expandafter\hobby at point@options\expandafter%
-    {\hobby at point@options,tension in=#1}%
-  },
-  tension out/.code = {%
-    \expandafter\gdef\expandafter\hobby at point@options\expandafter%
-    {\hobby at point@options,tension out=#1}%
-  },
-  tension/.code = {%
-    \expandafter\gdef\expandafter\hobby at point@options\expandafter%
-    {\hobby at point@options,tension=#1}%
-  },
-  excess angle/.code = {%
-    \expandafter\gdef\expandafter\hobby at point@options\expandafter%
-    {\hobby at point@options,excess angle=#1}%
-  },
-  break/.code = {%
-    \expandafter\gdef\expandafter\hobby at point@options\expandafter%
-    {\hobby at point@options,break=#1}%
-  },
-  blank/.code = {%
-    \expandafter\gdef\expandafter\hobby at point@options\expandafter%
-    {\hobby at point@options,blank=#1}%
-  },
-  designated Hobby path/.initial={next},
-  clear next Hobby path options/.code={%
-    \gdef\hobby at next@opts{}%
-  },
-  clear this Hobby path options/.code={%
-    \gdef\hobby at this@opts{}%
-  },
-  clear Hobby path options/.style={%
-    clear \pgfkeysvalueof{/tikz/designated Hobby path} Hobby path options
-  },
-  add option to this Hobby path/.code={%
-    \expandafter\gdef\expandafter\hobby at this@opts\expandafter{\hobby at this@opts#1,}%
-  },
-  add option to next Hobby path/.code={%
-    \expandafter\gdef\expandafter\hobby at next@opts\expandafter{\hobby at next@opts#1,}%
-  },
-  add option to Hobby path/.style={%
-    add option to \pgfkeysvalueof{/tikz/designated Hobby path} Hobby path={#1}%
-  },
-  closed/.style = {%
-    add option to Hobby path={closed=#1,disjoint=#1}%
-  },
-  invert blank/.style = {%
-    add option to Hobby path={invert blank=#1}%
-  },
-  closed/.default = true,
-  blank/.default = true,
-  break/.default = true,
-  invert blank/.default = true,
-  in angle/.code = {%
-    \pgfmathparse{(#1)*pi/180}%
-    \edef\@temp{in angle=\pgfmathresult,}%
-    \pgfkeysalso{add option to Hobby path/.expand once=\@temp}%
-  },
-  out angle/.code = {%
-    \pgfmathparse{(#1)*pi/180}%
-    \edef\@temp{out angle=\pgfmathresult,}%
-    \pgfkeysalso{add option to Hobby path/.expand once=\@temp}%
-  },
-  in curl/.style = {%
-    add option to Hobby path={in curl=#1}%
-  },
-  out curl/.code = {%
-    add option to Hobby path={out curl=#1}%
-  },
-  use Hobby shortcut/.code={%
-    \let\tikz at curveto@auto=\hobby at curveto@override
-    \global\let\hobby at curveto@delegate=\hobby at curveto@auto
-  },
-  use quick Hobby shortcut/.code={%
-    \let\tikz at curveto@auto=\hobby at curveto@override
-    \global\let\hobby at curveto@delegate=\hobby at qcurveto@auto
-  },
-  use previous Hobby path/.code={%
-    \pgfextra{\hobbyusepath{#1}}
-  },
-  use previous Hobby path/.default={},%
-  save Hobby path/.code={%
-    \xdef\hobby at path@name{#1}%
-  },
-  restore Hobby path/.code={%
-    \pgfextra{%
-      \hobbyinit\hobby at tikz@moveto\hobby at tikz@curveto\hobby at tikz@close
-      \global\let\hobby at collected@onpath\pgfutil at empty
-      \hobbyrestorepath{#1}}
-  },
-  restore and use Hobby path/.code 2 args={%
-    \pgfextra{%
-      \hobbyinit\hobby at tikz@moveto\hobby at tikz@curveto\hobby at tikz@close
-      \global\let\hobby at collected@onpath\pgfutil at empty
-      \hobbyrestorepath{#1}%
-      \hobbyusepath{#2}%
-    }
-  },
-  show Hobby path/.code={%
-    \pgfextra{\hobbyshowpath{#1}}
-  },
-  Hobby action/.code={%
-    \expandafter\gdef\expandafter\hobby at action\expandafter{\hobby at action#1}%
-  },
-  Hobby finish/.style={%
-    Hobby action=\hobby at finish%
-  },
-  Hobby externalise/.is if=hobby at externalise,
-  Hobby externalize/.is if=hobby at externalise
-}
-%    \end{macrocode}
-%
-% \begin{macro}{\hobby at tikz@curveto}
-% This is passed to the path-generation code to translate the path into a PGF path.
-%    \begin{macrocode}
-\def\hobby at tikz@curveto#1#2#3{%
-  \pgfutil at ifundefined{tikz at timer@start}{%
-    \expandafter\hobby at topgf\expandafter{\hobby at initial@pt}%
-    \edef\tikz at timer@start{\noexpand\pgfqpoint{\the\pgf at x}{\the\pgf at y}}%
-  }{}%
-  \hobby at topgf{#1}%
-  \edef\tikz at timer@cont at one{\noexpand\pgfqpoint{\the\pgf at x}{\the\pgf at y}}%
-  \hobby at topgf{#2}%
-  \edef\tikz at timer@cont at two{\noexpand\pgfqpoint{\the\pgf at x}{\the\pgf at y}}%
-  \hobby at topgf{#3}%
-  \let\tikz at timer=\tikz at timer@curve
-  \edef\tikz at timer@end{\noexpand\pgfqpoint{\the\pgf at x}{\the\pgf at y}}%
-  \ifx\hobby at collected@onpath\pgfutil at empty
-  \else
-  \expandafter\hobby at nodes@onpath\hobby at collected@onpath\relax\relax
-  \fi
-  \pgfpathcurveto{\hobby at topgf{#1}}{\hobby at topgf{#2}}{\hobby at topgf{#3}}%
-  \hobby at topgf{#3}%
-  \edef\tikz at timer@start{\noexpand\pgfqpoint{\the\pgf at x}{\the\pgf at y}}%
-}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\hobby at tikz@moveto}
-% This is passed to the path-generation code to translate the path into a PGF path.
-%    \begin{macrocode}
-\def\hobby at tikz@moveto#1#2#3{%
-  \pgfutil at ifundefined{tikz at timer@start}{%
-    \expandafter\hobby at topgf\expandafter{\hobby at initial@pt}%
-    \edef\tikz at timer@start{\noexpand\pgfqpoint{\the\pgf at x}{\the\pgf at y}}%
-  }{}%
-  \hobby at topgf{#3}%
-  \edef\tikz at timer@end{\noexpand\pgfqpoint{\the\pgf at x}{\the\pgf at y}}%
-  \def\pgf at temp{#1}%
-  \ifx\pgf at temp\pgfutil at empty
-    \let\tikz at timer=\tikz at timer@line
-  \expandafter\def\expandafter\hobby at collected@onpath\expandafter{\expandafter{\expandafter}\hobby at collected@onpath}
-  \else
-    \hobby at topgf{#1}%
-    \edef\tikz at timer@cont at one{\noexpand\pgfqpoint{\the\pgf at x}{\the\pgf at y}}%
-    \hobby at topgf{#2}%
-    \edef\tikz at timer@cont at two{\noexpand\pgfqpoint{\the\pgf at x}{\the\pgf at y}}%
-    \let\tikz at timer=\tikz at timer@curve
-  \fi
-  \ifx\hobby at collected@onpath\pgfutil at empty
-  \else
-  \expandafter\hobby at nodes@onpath\hobby at collected@onpath\relax\relax
-  \fi
-  \pgfpathmoveto{\hobby at topgf{#3}}%
-  \hobby at topgf{#3}%
-  \edef\tikz at timer@start{\noexpand\pgfqpoint{\the\pgf at x}{\the\pgf at y}}%
-}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\hobby at tikz@close}
-% Closes a path.
-%    \begin{macrocode}
-\def\hobby at tikz@close#1{%
-  \hobby at topgf{#1}%
-  \edef\tikz at timer@end{\noexpand\pgfqpoint{\the\pgf at x}{\the\pgf at y}}%
-  \let\tikz at timer=\tikz at timer@line
-  \ifx\hobby at collected@onpath\pgfutil at empty
-  \else
-  \expandafter\hobby at nodes@onpath\hobby at collected@onpath\relax\relax
-  \fi
-  \pgfpathclose
-}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\hobby at nodes@onpath}
-%    \begin{macrocode}
-\def\hobby at nodes@onpath#1#2\relax{%
-  \gdef\hobby at collected@onpath{#2}%
-  \def\pgf at temp{#1}%
-  \ifx\pgf at temp\pgfutil at empty
-  \else
-  \def\@gtempa{\relax}
-  \ifx\pgf at temp\@gtempa
-  \else
-  \tikz at node@is at a@labeltrue
-  \tikz at scan@next at command#1\pgf at stop
-  \tikz at node@is at a@labelfalse
-  \fi
-  \fi
-}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\curvethrough}
-% This is the parent command.
-% We initialise the path-generation code, set any parameters, and then hand over control to the point processing macro.
-%    \begin{macrocode}
-\newcommand\curvethrough[2][]{%
-  \hobbyinit\hobby at tikz@moveto\hobby at tikz@curveto\hobby at tikz@close
-  \global\let\hobby at collected@onpath\pgfutil at empty
-  \let\hobby at initial@pt\pgfutil at empty
-  \hobbysetparams{#1}%
-  \tikzset{designated Hobby path=this}%
-  \global\let\hobby at this@opts=\pgfutil at empty
-  \global\let\hobby at next@opts=\pgfutil at empty
-  \let\tikz at scan@point at options=\pgfutil at empty
-  \def\hobby at point@options{}%
-  \tikz at scan@one at point\hobby at processpt #2 \relax%
-}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\hobby at processpt}
-% This processes a list of points in the format \Verb+(0,0) [..] (1,1)+.
-% Each point is scanned by TikZ and then added to the stack to be built into the path.
-% If there are any remaining points, we call ourself again with them.
-% Otherwise, we hand over control to the path-generation code.
-%    \begin{macrocode}
-\newcommand\hobby at processpt[1]{%
-  #1%
-  \pgfmathsetmacro\hobby at x{\the\pgf at x/1cm}%
-  \pgfmathsetmacro\hobby at y{\the\pgf at y/1cm}%
-  \ifx\hobby at initial@pt\pgfutil at empty
-    \xdef\hobby at initial@pt{x = \hobby at x, y = \hobby at y}%
-  \fi
-  \expandafter\hobbyaddpoint\expandafter{\hobby at point@options,%
-    x = \hobby at x, y = \hobby at y}%
-  \def\hobby at point@options{}%
-  \let\tikz at scan@point at options=\pgfutil at empty
-  \pgfutil at ifnextchar\relax{%
-    \expandafter\hobbysetparams\expandafter{\hobby at this@opts}%
-  \ifhobby at externalise
-    \ifx\hobby at path@name\pgfutil at empty
-      \hobbygenusepath
-    \else
-      \hobbygenuseifnecpath{\hobby at path@name}%
-    \fi
-  \else
-    \hobbygenusepath
-  \fi
-  \ifx\hobby at path@name\pgfutil at empty
-  \else
-    \hobbysavepath{\hobby at path@name}%
-  \fi
-  \global\let\hobby at path@name=\pgfutil at empty
-  }{%
-    \pgfutil at ifnextchar.{%
-      \hobby at swallowdots}{%
-      \tikz at scan@one at point\hobby at processpt}}}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\hobby at swallowdots}
-% Remove dots from the input stream.
-%    \begin{macrocode}
-\def\hobby at swallowdots.{%
-  \pgfutil at ifnextchar.{%
-    \hobby at swallowdots}{%
-    \tikz at scan@one at point\hobby at processpt}}
-%    \end{macrocode}
-% \end{macro}
-%
-% There is a ``spare hook'' in the TikZ path processing code.
-% If TikZ encounters a path of the form \Verb+(0,0) .. (1,1)+ then it calls a macro \Verb+\tikz at curveto@auto+.
-% However, that macro is not defined in the TikZ code.
-% The following code provides a suitable definition.
-% To play nice, we don't install it by default but define a key (defined above) that installs it.
-%
-% \begin{macro}{\hobby at curveto@override}
-%    \begin{macrocode}
-\def\hobby at curveto@override{%
-  \hobby at curveto@delegate}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\hobby at curveto@auto}
-% When we're called by TikZ, we initialise the path generation code and start adding points.
-% To ensure that the generation code is called, we add a lot of hooks to lots of TikZ commands.
-%    \begin{macrocode}
-\def\hobby at curveto@auto{%
-  \hobbyinit\hobby at tikz@moveto\hobby at tikz@curveto\hobby at tikz@close
-  \expandafter\gdef\expandafter\hobby at collected@onpath\expandafter{\expandafter{\tikz at collected@onpath}   }%
-  \let\tikz at collected@onpath=\pgfutil at empty
-  \pgfmathsetmacro\hobby at x{\the\tikz at lastx/1cm}%
-  \pgfmathsetmacro\hobby at y{\the\tikz at lasty/1cm}%
-  \xdef\hobby at initial@pt{x = \hobby at x, y = \hobby at y}%
-  \expandafter\hobbysetparams\expandafter{\hobby at next@opts}%
-  \expandafter\hobbyaddpoint\expandafter{\hobby at point@options,%
-      x = \hobby at x, y = \hobby at y}%
-  \hobby at init@tikz at commands
-  \tikzset{designated Hobby path=this}%
-  \let\tikz at scan@point at options=\pgfutil at empty
-  \global\let\hobby at action=\pgfutil at empty
-  \global\let\hobby at this@opts=\pgfutil at empty
-  \global\let\hobby at next@opts=\pgfutil at empty
-  \global\let\hobby at point@options=\pgfutil at empty
-  \tikz at scan@one at point\hobby at addfromtikz%
-}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\hobby at addfromtikz}
-% This adds our current point to the stack.
-%    \begin{macrocode}
-\def\hobby at addfromtikz#1{%
-  #1%
-  \tikz at make@last at position{#1}%
-  \pgfmathsetmacro\hobby at x{\the\pgf at x/1cm}%
-  \pgfmathsetmacro\hobby at y{\the\pgf at y/1cm}%
-  \expandafter\hobbysetparams\expandafter{\hobby at this@opts}%
-  \expandafter\hobbyaddpoint\expandafter{\hobby at point@options,%
-    x = \hobby at x, y = \hobby at y}%
-  \hobby at action
-  \global\let\hobby at this@opts=\pgfutil at empty
-  \global\let\hobby at action=\pgfutil at empty
-  \global\let\hobby at point@options=\pgfutil at empty
-  \tikz at scan@next at command%
-}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\hobby at init@tikz at commands}
-%    \begin{macrocode}
-\def\hobby at init@tikz at commands{%
-  \hobby at init@tikz at modcmd\tikz at movetoabs
-  \hobby at init@tikz at modcmd\tikz at movetorel
-  \hobby at init@tikz at modcmd\tikz at lineto
-  \hobby at init@tikz at modcmd\tikz at rect
-  \hobby at init@tikz at modcmd\tikz at cchar
-  \hobby at init@tikz at modcmd\tikz at finish
-  \hobby at init@tikz at modcmd\tikz at arcA
-  \hobby at init@tikz at modcmd\tikz at e@char
-  \hobby at init@tikz at modcmd\tikz at g@char
-  \hobby at init@tikz at modcmd\tikz at schar
-  \hobby at init@tikz at modcmd\tikz at vh@lineto
-  \hobby at init@tikz at modcmd\tikz at pchar
-  \hobby at init@tikz at modcmd\tikz at to
-  \hobby at init@tikz at modcmd\pgf at stop
-  \hobby at init@tikz at modcmd\tikz at decoration
-  \global\let\hobby at curveto@delegate=\hobby at midcurveto@auto
-}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\hobby at restore@tikz at commands}
-%    \begin{macrocode}
-\def\hobby at restore@tikz at commands{%
-  \hobby at restore@tikz at modcmd\tikz at movetoabs
-  \hobby at restore@tikz at modcmd\tikz at movetorel
-  \hobby at restore@tikz at modcmd\tikz at lineto
-  \hobby at restore@tikz at modcmd\tikz at rect
-  \hobby at restore@tikz at modcmd\tikz at cchar
-  \hobby at restore@tikz at modcmd\tikz at finish
-  \hobby at restore@tikz at modcmd\tikz at arcA
-  \hobby at restore@tikz at modcmd\tikz at e@char
-  \hobby at restore@tikz at modcmd\tikz at g@char
-  \hobby at restore@tikz at modcmd\tikz at schar
-  \hobby at restore@tikz at modcmd\tikz at vh@lineto
-  \hobby at restore@tikz at modcmd\tikz at pchar
-  \hobby at restore@tikz at modcmd\tikz at to
-  \hobby at restore@tikz at modcmd\pgf at stop
-  \hobby at restore@tikz at modcmd\tikz at decoration
-  \global\let\hobby at curveto@delegate=\hobby at curveto@auto
-}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\hobby at init@tikz at modcmd}
-%    \begin{macrocode}
-\def\hobby at init@tikz at modcmd#1{%
-    \expandafter\global\expandafter\let\csname hobby at orig@\string#1\endcsname=#1%
-    \gdef#1{\hobby at finish#1}%
-}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\hobby at restore@tikz at modcmd}
-%    \begin{macrocode}
-\def\hobby at restore@tikz at modcmd#1{%
-    \expandafter\global\expandafter\let\expandafter#1\csname hobby at orig@\string#1\endcsname%
-}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\hobby at midcurveto@auto}
-%    \begin{macrocode}
-\def\hobby at midcurveto@auto{%
-  \expandafter\expandafter\expandafter\gdef\expandafter\expandafter\expandafter\hobby at collected@onpath\expandafter\expandafter\expandafter{\expandafter\hobby at collected@onpath\expandafter{\tikz at collected@onpath}   }%
-  \let\tikz at collected@onpath=\pgfutil at empty
-  \let\tikz at scan@point at options=\pgfutil at empty
-  \global\let\hobby at action=\pgfutil at empty
-  \global\let\hobby at this@opts=\pgfutil at empty
-  \global\let\hobby at point@options=\pgfutil at empty
-  \tikz at scan@one at point\hobby at addfromtikz%
-}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\hobby at finish}
-%    \begin{macrocode}
-\def\hobby at finish{%
-  \hobby at restore@tikz at commands
-  \ifhobby at externalise
-    \ifx\hobby at path@name\pgfutil at empty
-      \hobbygenusepath
-    \else
-      \hobbygenuseifnecpath{\hobby at path@name}%
-    \fi
-  \else
-    \hobbygenusepath
-  \fi
-  \ifx\hobby at path@name\pgfutil at empty
-  \else
-    \hobbysavepath{\hobby at path@name}%
-  \fi
-  \global\let\hobby at path@name=\pgfutil at empty
-  \tikzset{designated Hobby path=next}%
-}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{quick curve through}
-% The \Verb+quick curve through+ is a \Verb+to path+ which does the ``quick'' version of Hobby's algorithm.
-% The syntax is as with the \Verb+curve through+: to pass the midpoints as the argument to the style.
-% We need to pass three points to the auxiliary macro.
-% These are passed as \Verb+\hobby at qpoints+, \Verb+\hobby at qpointa+, and the current point.
-% Then these get cycled round for the next triple.
-% The path gets built up and stored as \Verb+\hobby at quick@path+.
-% We also have to remember the angle computed for the next round.
-%    \begin{macrocode}
-\tikzset{
-  quick curve through/.style={%
-    to path={%
-      \pgfextra{%
-%    \end{macrocode}
-% Scan the starting point and store the coordinates in \Verb+\hobby at qpointa+
-%    \begin{macrocode}
-        \let\hobby at next@qbreak=\relax
-        \let\hobby at next@qblank=\relax
-      \tikz at scan@one at point\pgfutil at firstofone(\tikztostart)%
-        \tikz at make@last at position{\pgfqpoint{\the\pgf at x}{\the\pgf at y}}%
-        \edef\hobby at qpoints{\noexpand\pgfqpoint{\the\pgf at x}{\the\pgf at y}}%
-%    \end{macrocode}
-% Blank the path and auxiliary macros.
-%    \begin{macrocode}
-        \def\hobby at qpointa{}%
-        \def\hobby at quick@path{}%
-        \def\hobby at angle{}%
-        \let\hobby at quick@curveto=\hobby at quick@makepath
-%    \end{macrocode}
-% Now start parsing the rest of the coordinates.
-%    \begin{macrocode}
-        \tikz at scan@one at point\hobby at quickfirst #1 (\tikztotarget)\relax
-      }
-%    \end{macrocode}
-% Invoke the path
-%    \begin{macrocode}
-      \hobby at quick@path
-    }
-  },
-  quick hobby/blank curve/.is choice,
-  quick hobby/blank curve/true/.code={%
-    \gdef\hobby at next@qblank{%
-      \qhobby at blanktrue
-      \global\let\hobby at next@qblank=\relax
-    }%
-  },
-  quick hobby/blank curve/false/.code={%
-    \gdef\hobby at next@qblank{%
-      \qhobby at blankfalse
-      \global\let\hobby at next@qblank=\relax
-    }%
-  },
-  quick hobby/blank curve/once/.code={%
-    \gdef\hobby at next@qblank{%
-      \qhobby at blanktrue
-      \gdef\hobby at next@qblank{%
-        \qhobby at blankfalse
-        \global\let\hobby at next@qblank=\relax
-      }%
-    }%
-  },
-  quick hobby/blank curve/.default=true,
-  quick hobby/break curve/.is choice,
-  quick hobby/break curve/true/.code={%
-    \gdef\hobby at next@qbreak{%
-      \qhobby at breaktrue
-      \global\let\hobby at next@qbreak=\relax
-    }%
-  },
-  quick hobby/break curve/false/.code={%
-    \gdef\hobby at next@qbreak{%
-      \qhobby at breakfalse
-      \global\let\hobby at next@qbreak=\relax
-    }%
-  },
-  quick hobby/break curve/once/.code={%
-    \gdef\hobby at next@qbreak{%
-      \qhobby at breaktrue
-      \gdef\hobby at next@qbreak{%
-        \qhobby at breakfalse
-        \global\let\hobby at next@qbreak=\relax
-      }%
-    }%
-  },
-  quick hobby/break curve/.default=true,
-}
-\newif\ifqhobby at break
-\newif\ifqhobby at blank
-%    \end{macrocode}
-% \end{macro}
-%
-% Add plot handlers
-%    \begin{macrocode}
-\tikzoption{hobby}[]{\let\tikz at plot@handler=\pgfplothandlerhobby}
-\tikzoption{quick hobby}[]{\let\tikz at plot@handler=\pgfplothandlerquickhobby}
-\tikzoption{closed hobby}[]{\let\tikz at plot@handler=\pgfplothandlerclosedhobby}
-%    \end{macrocode}
-%
-% \begin{macro}{\hobby at quickfirst}
-% The first time around we just set the next point.
-%    \begin{macrocode}
-\def\hobby at quickfirst#1{%
-  #1%
-  \xdef\hobby at qpointa{\noexpand\pgfqpoint{\the\pgf at x}{\the\pgf at y}}%
-  \tikz at make@last at position{\hobby at qpointa}%
-%    \end{macrocode}
-% Now a check to ensure that we have more points.
-%    \begin{macrocode}
-  \pgfutil at ifnextchar\relax{%
-%    \end{macrocode}
-% Ooops, no more points.
-% That's not good.
-% Bail-out.
-%    \begin{macrocode}
-    \xdef\hobby at quick@path{ -- (\the\pgf at x,\the\pgf at y)}%
-  }{%
-%    \end{macrocode}
-% Okay, have more points.
-% Phew.
-% Call the next round.
-% If we have dots, swallow them.
-%    \begin{macrocode}
-    \pgfutil at ifnextchar.{%
-      \hobby at qswallowdots}{%
-    \tikz at scan@one at point\hobby at quick}}}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\hobby at qswallowdots}
-% Remove dots from the input stream.
-%    \begin{macrocode}
-\def\hobby at qswallowdots.{%
-  \pgfutil at ifnextchar.{%
-    \hobby at qswallowdots}{%
-    \tikz at scan@one at point\hobby at quick}}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\hobby at quick}
-% This is our wrapper function that handles the loop.
-%    \begin{macrocode}
-\def\hobby at quick#1{%
-  \hobby at quick@compute{#1}%
-  \tikz at make@last at position{\hobby at qpointa}%
-  \pgfutil at ifnextchar\relax{%
-%    \end{macrocode}
-% End of loop
-%    \begin{macrocode}
-    \hobby at quick@computeend%
-  }{%
-%    \end{macrocode}
-% More to go, scan in the next coordinate and off we go again.
-%    \begin{macrocode}
-    \pgfutil at ifnextchar.{%
-      \hobby at qswallowdots}{%
-      \tikz at scan@one at point\hobby at quick}}}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\hobby at quick@makepath}
-% Path constructor for \Verb+to path+ use.
-%    \begin{macrocode}
-\def\hobby at quick@makepath#1#2#3{%
-  #1%
-  \pgf at xa=\pgf at x\relax
-  \pgf at ya=\pgf at y\relax
-  #2%
-  \pgf at xb=\pgf at x\relax
-  \pgf at yb=\pgf at y\relax
-  #3%
-  \ifqhobby at blank
-  \xdef\hobby at quick@path{\hobby at quick@path (\the\pgf at x,\the\pgf at y)}%
-  \else
-  \xdef\hobby at quick@path{\hobby at quick@path .. controls%
-  (\the\pgf at xa,\the\pgf at ya) and (\the\pgf at xb,\the\pgf at yb) .. (\the\pgf at x,\the\pgf at y) }%
-  \fi
-  \ifqhobby at break
-  \xdef\hobby at quick@path{\hobby at quick@path +(0,0)}%
-  \fi
-  \hobby at next@qbreak
-  \hobby at next@qblank
-}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\hobby at qcurveto@auto}
-% Uses the ``quick'' method for the shortcut syntax.
-%    \begin{macrocode}
-\def\hobby at qcurveto@auto{%
-  \global\let\hobby at next@qbreak=\relax
-  \global\let\hobby at next@qblank=\relax
-  \xdef\hobby at qpoints{\noexpand\pgfqpoint{\the\tikz at lastx}{\the\tikz at lasty}}%
-  \gdef\hobby at qpointa{}%
-  \gdef\hobby at quick@path{}%
-  \gdef\hobby at angle{}%
-  \global\let\hobby at quick@curveto=\hobby at quick@makepathauto
-  \hobby at qinit@tikz at commands
-  \let\tikz at scan@point at options=\pgfutil at empty
-  \global\let\hobby at action=\pgfutil at empty
-  \global\let\hobby at point@options=\pgfutil at empty
-  \tikz at scan@one at point\hobby at qfirst@auto}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\hobby at qmidcurveto@auto}
-%    \begin{macrocode}
-\def\hobby at qmidcurveto@auto{%
-  \let\tikz at scan@point at options=\pgfutil at empty
-  \global\let\hobby at action=\pgfutil at empty
-  \global\let\hobby at point@options=\pgfutil at empty
-  \tikz at scan@one at point\hobby at qaddfromtikz}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\hobby at qfirst@auto}
-%    \begin{macrocode}
-\def\hobby at qfirst@auto#1{%
-  #1%
-  \xdef\hobby at qpointa{\noexpand\pgfqpoint{\the\pgf at x}{\the\pgf at y}}%
-  \tikz at make@last at position{\hobby at qpointa}%
-  \tikz at scan@next at command%
-}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\hobby at quick@makepathauto}
-% Path constructor for shortcut method to use.
-%    \begin{macrocode}
-\def\hobby at quick@makepathauto#1#2#3{%
-  #1%
-  \pgf at xa=\pgf at x\relax
-  \pgf at ya=\pgf at y\relax
-  #2%
-  \pgf at xb=\pgf at x\relax
-  \pgf at yb=\pgf at y\relax
-  #3%
-  \ifqhobby at blank
-  \edef\hobby at temp{%
-    \noexpand\pgfpathmoveto{\noexpand\pgfqpoint{\the\pgf at x}{\the\pgf at y}}%
-  }%
-  \hobby at temp
-  \else
-  \edef\hobby at temp{%
-    \noexpand\pgfpathcurveto{\noexpand\pgfqpoint{\the\pgf at xa}{\the\pgf at ya}}%
-    {\noexpand\pgfqpoint{\the\pgf at xb}{\the\pgf at yb}}%
-    {\noexpand\pgfqpoint{\the\pgf at x}{\the\pgf at y}}%
-  }%
-  \hobby at temp
-  \fi
-  \ifqhobby at break
-  #3%
-  \edef\hobby at temp{%
-    \noexpand\pgfpathmoveto{\noexpand\pgfqpoint{\the\pgf at x}{\the\pgf at y}}%
-  }%
-  \hobby at temp
-  \fi
-  \hobby at next@qbreak
-  \hobby at next@qblank
-}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\hobby at qaddfromtikz}
-% This adds our current point to the stack.
-%    \begin{macrocode}
-\def\hobby at qaddfromtikz#1{%
-  \hobby at quick@compute{#1}%
-  \tikz at make@last at position{\hobby at qpointa}%
-  \tikz at scan@next at command%
-}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\hobby at qinit@tikz at commands}
-%    \begin{macrocode}
-\def\hobby at qinit@tikz at commands{%
-  \hobby at qinit@tikz at modcmd\tikz at movetoabs
-  \hobby at qinit@tikz at modcmd\tikz at movetorel
-  \hobby at qinit@tikz at modcmd\tikz at lineto
-  \hobby at qinit@tikz at modcmd\tikz at rect
-  \hobby at qinit@tikz at modcmd\tikz at cchar
-  \hobby at qinit@tikz at modcmd\tikz at finish
-  \hobby at qinit@tikz at modcmd\tikz at arcA
-  \hobby at qinit@tikz at modcmd\tikz at e@char
-  \hobby at qinit@tikz at modcmd\tikz at g@char
-  \hobby at qinit@tikz at modcmd\tikz at schar
-  \hobby at qinit@tikz at modcmd\tikz at vh@lineto
-  \hobby at qinit@tikz at modcmd\tikz at pchar
-  \hobby at qinit@tikz at modcmd\tikz at to
-  \hobby at qinit@tikz at modcmd\pgf at stop
-  \hobby at qinit@tikz at modcmd\tikz at decoration
-  \hobby at qinit@tikz at modcmd\tikz@@close
-  \global\let\hobby at curveto@delegate=\hobby at qmidcurveto@auto
-}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\hobby at qrestore@tikz at commands}
-%    \begin{macrocode}
-\def\hobby at qrestore@tikz at commands{%
-  \hobby at restore@tikz at modcmd\tikz at movetoabs
-  \hobby at restore@tikz at modcmd\tikz at movetorel
-  \hobby at restore@tikz at modcmd\tikz at lineto
-  \hobby at restore@tikz at modcmd\tikz at rect
-  \hobby at restore@tikz at modcmd\tikz at cchar
-  \hobby at restore@tikz at modcmd\tikz at finish
-  \hobby at restore@tikz at modcmd\tikz at arcA
-  \hobby at restore@tikz at modcmd\tikz at e@char
-  \hobby at restore@tikz at modcmd\tikz at g@char
-  \hobby at restore@tikz at modcmd\tikz at schar
-  \hobby at restore@tikz at modcmd\tikz at vh@lineto
-  \hobby at restore@tikz at modcmd\tikz at pchar
-  \hobby at restore@tikz at modcmd\tikz at to
-  \hobby at restore@tikz at modcmd\pgf at stop
-  \hobby at restore@tikz at modcmd\tikz at decoration
-  \hobby at restore@tikz at modcmd\tikz@@close
-  \global\let\hobby at curveto@delegate=\hobby at qcurveto@auto
-}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\hobby at qinit@tikz at modcmd}
-%    \begin{macrocode}
-\def\hobby at qinit@tikz at modcmd#1{%
-    \expandafter\global\expandafter\let\csname hobby at orig@\string#1\endcsname=#1%
-    \gdef#1{\hobby at qfinish#1}%
-}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\hobby at qfinish}
-%    \begin{macrocode}
-\def\hobby at qfinish{%
-  \hobby at quick@computeend
-  \hobby at qrestore@tikz at commands
-}
-%    \end{macrocode}
-% \end{macro}
-
-% \iffalse
-%</tikzlibrary>
-% \fi
-%
-% \subsection{Arrays}
-%
-% \iffalse
-%<*array>
-% \fi
-% 
-%
-% A lot of our data structures are really arrays.
-% These are implemented as \LaTeX3 ``property lists''.
-% For ease of use, an array is a property list with numeric entries together with entries ``base'' and ``top'' which hold the lowest and highest indices that have been set.
-%
-%    \begin{macrocode}
-\RequirePackage{expl3}
-\ExplSyntaxOn
-%    \end{macrocode}
-% Some auxiliary variables.
-%    \begin{macrocode}
-\tl_new:N \l_array_tmp_tl
-\tl_new:N \l_array_show_tl
-\int_new:N \l_array_base_int
-\int_new:N \l_array_top_int
-\int_new:N \l_array_tmp_int
-%    \end{macrocode}
-% The global variable \Verb+\g_array_base_int+ says what index a blank array should start with when pushed or unshifted.
-%    \begin{macrocode}
-\int_new:N \g_array_base_int
-\int_set:Nn \g_array_base_int {0}
-%    \end{macrocode}
-% \begin{macro}{\array_adjust_ends:Nn}
-% This ensures that the ``base'' and ``top'' are big enough to include the given index.
-%    \begin{macrocode}
-\cs_new:Npn \array_adjust_ends:Nn #1#2 {
-  \prop_get:NnNTF #1 {base} \l_tmpa_tl
-  {
-    \int_compare:nNnTF {\l_tmpa_tl} > {#2}
-    {
-      \prop_put:Nnx #1 {base} {\int_eval:n {#2}}
-    }
-    {}
-  }
-  {
-    \prop_put:Nnx #1 {base} {\int_eval:n {#2}}
-  }
-  \prop_get:NnNTF #1 {top} \l_tmpa_tl
-  {
-    \int_compare:nNnTF {\l_tmpa_tl} < {#2}
-    {
-      \prop_put:Nnx #1 {top} {\int_eval:n {#2}}
-    }
-    {}
-  }
-  {
-    \prop_put:Nnx #1 {top} {\int_eval:n {#2}}
-  }
-}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\array_gadjust_ends:Nn}
-% This ensures that the ``base'' and ``top'' are big enough to include the given index.
-% (Global version)
-%    \begin{macrocode}
-\cs_new:Npn \array_gadjust_ends:Nn #1#2 {
-  \prop_get:NnNTF #1 {base} \l_tmpa_tl
-  {
-    \int_compare:nNnTF {\l_tmpa_tl} > {#2}
-    {
-      \prop_gput:Nnx #1 {base} {\int_eval:n {#2}}
-    }
-    {}
-  }
-  {
-    \prop_gput:Nnx #1 {base} {\int_eval:n {#2}}
-  }
-  \prop_get:NnNTF #1 {top} \l_tmpa_tl
-  {
-    \int_compare:nNnTF {\l_tmpa_tl} < {#2}
-    {
-      \prop_gput:Nnx #1 {top} {\int_eval:n {#2}}
-    }
-    {}
-  }
-  {
-    \prop_gput:Nnx #1 {top} {\int_eval:n {#2}}
-  }
-}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\array_put:Nnn}
-% When adding a value to an array we have to adjust the ends.
-%    \begin{macrocode}
-\cs_new:Npn \array_put:Nnn #1#2#3 {
-  \exp_args:NNx \prop_put:Nnn #1 {\int_eval:n {#2}} {#3}
-  \array_adjust_ends:Nn #1{#2}
-}
-\cs_generate_variant:Nn \array_put:Nnn {Nnx}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\array_gput:Nnn}
-% When adding a value to an array we have to adjust the ends.
-% (Global version)
-%    \begin{macrocode}
-\cs_new:Npn \array_gput:Nnn #1#2#3 {
-  \exp_args:NNx \prop_gput:Nnn #1 {\int_eval:n {#2}} {#3}
-  \array_gadjust_ends:Nn #1{#2}
-}
-\cs_generate_variant:Nn \array_gput:Nnn {Nnx}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\array_get:NnN}
-%    \begin{macrocode}
-\cs_new:Npn \array_get:NnN #1#2#3 {
-  \exp_args:NNx \prop_get:NnN #1 {\int_eval:n {#2}} #3
-}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}[EXP]{\array_get:Nn}
-%    \begin{macrocode}
-\cs_new:Npn \array_get:Nn #1#2 {
-  \exp_args:NNf \prop_item:Nn #1 { \int_eval:n {#2} }
-}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\array_get:NnNTF}
-%    \begin{macrocode}
-\cs_new:Npn \array_get:NnNTF #1#2#3#4#5 {
-  \exp_args:NNx \prop_get:NnNTF #1 {\int_eval:n {#2}} #3 {#4}{#5}
-}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\array_if_empty:NTF}
-%    \begin{macrocode}
-\prg_new_conditional:Npnn \array_if_empty:N #1 { p, T, F, TF }
-{
-  \if_meaning:w #1 \c_empty_prop
-    \prg_return_true:
-  \else:
-    \prg_return_false:
-  \fi:
-}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\array_if_exist:NTF}
-%    \begin{macrocode}
-\prg_new_eq_conditional:NNn \array_if_exist:N \cs_if_exist:N { p, T, F, TF }
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\array_new:N}
-%    \begin{macrocode}
-\cs_new_eq:NN \array_new:N \prop_new:N
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\array_clear:N}
-%    \begin{macrocode}
-\cs_new_eq:NN \array_clear:N \prop_clear:N
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\array_gclear:N}
-%    \begin{macrocode}
-\cs_new_eq:NN \array_gclear:N \prop_gclear:N
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\array_map_function}
-% When stepping through an array, we want to iterate in order so a simple wrapper to \Verb+\prop_map_function+ is not enough.
-% This maps through every value from the base to the top so the function should be prepared to deal with a \Verb+\q_no_value+.
-%    \begin{macrocode}
-\cs_new:Npn \array_map_function:NN #1#2
-{
-  \array_if_empty:NTF #1 {} {
-    \prop_get:NnNTF #1 {base} \l_array_tmp_tl {
-      \int_set:Nn \l_array_base_int {\l_array_tmp_tl}
-    }{
-      \int_set:Nn \l_array_base_int {0}
-    }
-    \prop_get:NnNTF #1 {top} \l_array_tmp_tl {
-      \int_set:Nn \l_array_top_int {\l_array_tmp_tl}
-    }{
-      \int_set:Nn \l_array_top_int {0}
-    }
-    \int_step_inline:nnnn {\l_array_base_int} {1} {\l_array_top_int} {
-  \array_get:NnN #1 {##1} \l_array_tmp_tl
-  \exp_args:NnV #2 {##1} \l_array_tmp_tl
-}
-} {}
-}
-\cs_generate_variant:Nn \array_map_function:NN {     Nc }
-\cs_generate_variant:Nn \array_map_function:NN { c , cc }
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\array_reverse_map_function}
-% This steps through the array in reverse order.
-%    \begin{macrocode}
-\cs_new:Npn \array_reverse_map_function:NN #1#2
-{
-  \array_if_empty:NTF #1 {} {
-    \prop_get:NnNTF #1 {base} \l_array_tmp_tl {
-      \int_set:Nn \l_array_base_int {\l_array_tmp_tl}
-    }{
-      \int_set:Nn \l_array_base_int {0}
-    }
-    \prop_get:NnNTF #1 {top} \l_array_tmp_tl {
-      \int_set:Nn \l_array_top_int {\l_array_tmp_tl}
-    }{
-      \int_set:Nn \l_array_top_int {0}
-    }
-    \int_step_inline:nnnn {\l_array_top_int} {-1} {\l_array_base_int} {
-  \array_get:NnN #1 {##1} \l_array_tmp_tl
-  \exp_args:Nno #2 {##1} \l_array_tmp_tl
-}
-} {}
-}
-\cs_generate_variant:Nn \array_reverse_map_function:NN {     Nc }
-\cs_generate_variant:Nn \array_reverse_map_function:NN { c , cc }
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\array_map_inline:Nn}
-% Inline version of the above.
-%    \begin{macrocode}
-\cs_new_protected:Npn \array_map_inline:Nn #1#2
-  {
-    \int_gincr:N \g__prg_map_int
-    \cs_gset:cpn { array_map_inline_ \int_use:N \g__prg_map_int :nn }
-      ##1##2 {#2}
-    \exp_args:NNc \array_map_function:NN #1
-      { array_map_inline_ \int_use:N \g__prg_map_int :nn }
-    \__prg_break_point:Nn \array_map_break: { \int_gdecr:N \g__prg_map_int }
-  }
-\cs_generate_variant:Nn \array_map_inline:Nn { c }
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\array_reverse_map_inline:Nn}
-% Inline version of the above.
-%    \begin{macrocode}
-\cs_new_protected:Npn \array_reverse_map_inline:Nn #1#2
-  {
-    \int_gincr:N \g__prg_map_int
-    \cs_gset:cpn { array_map_inline_ \int_use:N \g__prg_map_int :nn }
-      ##1##2 {#2}
-    \exp_args:NNc \array_reverse_map_function:NN #1
-      { array_map_inline_ \int_use:N \g__prg_map_int :nn }
-    \__prg_break_point:Nn \array_map_break: { \int_gdecr:N \g__prg_map_int }
-  }
-\cs_generate_variant:Nn \array_reverse_map_inline:Nn { c }
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\array_map_break:}
-%    \begin{macrocode}
-\cs_new_nopar:Npn \array_map_break:
-  { \__prg_map_break:Nn \array_map_break: { } }
-\cs_new_nopar:Npn \array_map_break:n
-  { \__prg_map_break:Nn \array_map_break: }
-%    \end{macrocode}
-% \end{macro}
-%
-% For displaying arrays, we need some messages.
-%    \begin{macrocode}
-\msg_new:nnn { kernel } { show-array }
-  {
-    The~array~\token_to_str:N #1~
-    \array_if_empty:NTF #1
-      { is~empty }
-      { contains~the~items~(without~outer~braces): }
-  }
-%    \end{macrocode}
-%
-% \begin{macro}{\array_show:N}
-% Mapping through an array isn't expandable so we have to set a token list to its contents first before passing it to the message handler.
-%    \begin{macrocode}
-\cs_new_protected:Npn \array_show:N #1
-  {
-    \__msg_show_variable:NNNnn
-    #1
-    \array_if_exist:NTF
-    \array_if_empty:NTF
-      { array }
-    { \array_map_function:NN #1 \__msg_show_item:nn }
-  }
-\cs_generate_variant:Nn \array_show:N { c }
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\array_push:Nn}
-%    \begin{macrocode}
-\cs_new_protected:Npn \array_push:Nn #1#2
-{
-  \prop_get:NnNTF #1 {top} \l_array_tmp_tl
-  {
-    \int_set:Nn \l_array_tmp_int {\l_array_tmp_tl}
-    \int_incr:N \l_array_tmp_int
-    \array_put:Nnn #1 {\l_array_tmp_int} {#2}
-  }
-  {
-    \array_put:Nnn #1 {\g_array_base_int} {#2}
-  }
-}
-\cs_generate_variant:Nn \array_push:Nn {Nx}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\array_gpush:Nn}b
-%    \begin{macrocode}
-\cs_new_protected:Npn \array_gpush:Nn #1#2
-{
-  \prop_get:NnNTF #1 {top} \l_array_tmp_tl
-  {
-    \int_set:Nn \l_array_tmp_int {\l_array_tmp_tl}
-    \int_incr:N \l_array_tmp_int
-    \array_gput:Nnn #1 {\l_array_tmp_int} {#2}
-  }
-  {
-    \array_gput:Nnn #1 {\g_array_base_int} {#2}
-  }
-}
-\cs_generate_variant:Nn \array_gpush:Nn {Nx}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\array_unshift:Nn}
-%    \begin{macrocode}
-\cs_new_protected:Npn \array_unshift:Nn #1#2
-{
-  \prop_get:NnNTF #1 {base} \l_array_tmp_tl
-  {
-    \int_set:Nn \l_array_tmp_int {\l_array_tmp_tl}
-    \int_decr:N \l_array_tmp_int
-    \array_put:Nnn #1 {\l_array_tmp_int} {#2}
-  }
-  {
-    \array_put:Nnn #1 {\g_array_base_int} {#2}
-  }
-}
-\cs_generate_variant:Nn \array_unshift:Nn {Nx}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\array_gunshift:Nn}
-%    \begin{macrocode}
-\cs_new_protected:Npn \array_gunshift:Nn #1#2
-{
-  \prop_get:NnNTF #1 {base} \l_array_tmp_tl
-  {
-    \int_set:Nn \l_array_tmp_int {\l_array_tmp_tl}
-    \int_decr:N \l_array_tmp_int
-    \array_gput:Nnn #1 {\l_array_tmp_int} {#2}
-  }
-  {
-    \array_gput:Nnn #1 {\g_array_base_int} {#2}
-  }
-}
-\cs_generate_variant:Nn \array_gunshift:Nn {Nx}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\array_pop:NN}
-%    \begin{macrocode}
-\cs_new_protected:Npn \array_pop:NN #1#2
-{
-  \prop_get:NnN #1 {top} \l_array_tmp_tl
-  \array_get:NnN #1 {\l_array_tmp_tl} #2
-  \array_del:Nn #1 {\l_array_tmp_tl}
-}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\array_gpop:NN}
-%    \begin{macrocode}
-\cs_new_protected:Npn \array_gpop:NN #1#2
-{
-  \prop_get:NnN #1 {top} \l_array_tmp_tl
-  \array_get:NnN #1 {\l_array_tmp_tl} #2
-  \array_gdel:Nn #1 {\l_array_tmp_tl}
-}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\array_shift:NN}
-%    \begin{macrocode}
-\cs_new_protected:Npn \array_shift:NN #1#2
-{
-  \prop_get:NnN #1 {base} \l_array_tmp_tl
-  \array_get:NnN #1 {\l_array_tmp_tl} #2
-  \array_del:Nn #1 {\l_array_tmp_tl}
-}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\array_gshift:NN}
-%    \begin{macrocode}
-\cs_new_protected:Npn \array_gshift:NN #1#2
-{
-  \prop_get:NnN #1 {base} \l_array_tmp_tl
-  \array_get:NnN #1 {\l_array_tmp_tl} #2
-  \array_gdel:Nn #1 {\l_array_tmp_tl}
-}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\array_top:NN}
-%    \begin{macrocode}
-\cs_new_protected:Npn \array_top:NN #1#2
-{
-  \prop_get:NnN #1 {top} \l_array_tmp_tl
-  \array_get:NnN #1 {\l_array_tmp_tl} #2
-}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\array_base:NN}
-%    \begin{macrocode}
-\cs_new_protected:Npn \array_base:NN #1#2
-{
-  \prop_get:NnN #1 {base} \l_array_tmp_tl
-  \array_get:NnN #1 {\l_array_tmp_tl} #2
-}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\array_top:N}
-%    \begin{macrocode}
-\cs_new:Npn \array_top:N #1
-{
-  \array_get:Nn #1 {\prop_item:Nn #1 {top}}
-}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\array_base:N}
-%    \begin{macrocode}
-\cs_new:Npn \array_base:N #1
-{
-  \array_get:Nn #1 {\prop_item:Nn #1 {base}}
-}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\array_del:Nn}
-%    \begin{macrocode}
-\cs_new_protected:Npn \array_del:Nn #1#2
-{
-  \exp_args:NNx \prop_pop:Nn #1 {\int_eval:n {#2}}
-  \int_set:Nn \l_array_tmp_int {0}
-  \array_map_inline:Nn #1 {
-    \tl_if_eq:NNTF {##2} {\q_no_value} {}
-    {
-      \int_incr:N \l_array_tmp_int
-    }
-  }
-  \int_compare:nNnTF {\l_array_tmp_int} = {0}
-  {
-    \prop_clear:N #1
-  }
-  {
-  \prop_get:NnN #1 {top} \l_array_tmp_tl
-  \int_compare:nNnTF {#2} = {\l_array_tmp_tl} {
-    \prop_get:NnN #1 {base} \l_array_tmp_tl
-    \int_set:Nn \l_array_tmp_int {\l_array_tmp_tl}
-    \array_map_inline:Nn #1 {
-    \tl_if_eq:NNTF {##2} {\q_no_value} {}
-    {
-      \int_compare:nNnTF {\l_array_tmp_int} < {##1} {
-        \int_set:Nn \l_array_tmp_int {##1}
-      }{}
-    }
-      }
-    \prop_put:Nnx #1 {top} {\int_use:N \l_array_tmp_int}
-  }{}
-  \prop_get:NnN #1 {base} \l_array_tmp_tl
-  \int_compare:nNnTF {#2} = {\l_array_tmp_tl} {
-    \prop_get:NnN #1 {top} \l_array_tmp_tl
-    \int_set:Nn \l_array_tmp_int {\l_array_tmp_tl}
-    \array_map_inline:Nn #1 {
-    \tl_if_eq:NNTF {##2} {\q_no_value} {}
-    {
-      \int_compare:nNnTF {\l_array_tmp_int} > {##1} {
-        \int_set:Nn \l_array_tmp_int {##1}
-      }{}
-    }
-      }
-    \prop_put:Nnx #1 {base} {\int_use:N \l_array_tmp_int}
-  }{}
-  }
-}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\array_gdel:Nn}
-%    \begin{macrocode}
-\cs_new_protected:Npn \array_gdel:Nn #1#2
-{
-  \exp_args:NNx \prop_gpop:Nn #1 {\int_eval:n {#2}}
-  \int_set:Nn \l_array_tmp_int {0}
-  \array_map_inline:Nn #1 {
-    \tl_if_eq:NNTF {##2} {\q_no_value} {}
-    {
-      \int_incr:N \l_array_tmp_int
-    }
-  }
-  \int_compare:nNnTF {\l_array_tmp_int} = {0}
-  {
-    \prop_gclear:N #1
-  }
-  {
-  \prop_get:NnN #1 {top} \l_array_tmp_tl
-  \int_compare:nNnTF {#2} = {\l_array_tmp_tl} {
-    \prop_get:NnN #1 {base} \l_array_tmp_tl
-    \int_set:Nn \l_array_tmp_int {\l_array_tmp_tl}
-    \array_map_inline:Nn #1 {
-    \tl_if_eq:NNTF {##2} {\q_no_value} {}
-    {
-      \int_compare:nNnTF {\l_array_tmp_int} < {##1} {
-        \int_set:Nn \l_array_tmp_int {##1}
-      }{}
-    }
-      }
-    \prop_gput:Nnx #1 {top} {\int_use:N \l_array_tmp_int}
-  }{}
-  \prop_get:NnN #1 {base} \l_array_tmp_tl
-  \int_compare:nNnTF {#2} = {\l_array_tmp_tl} {
-    \prop_get:NnN #1 {top} \l_array_tmp_tl
-    \int_set:Nn \l_array_tmp_int {\l_array_tmp_tl}
-    \array_map_inline:Nn #1 {
-    \tl_if_eq:NNTF {##2} {\q_no_value} {}
-    {
-      \int_compare:nNnTF {\l_array_tmp_int} > {##1} {
-        \int_set:Nn \l_array_tmp_int {##1}
-      }{}
-    }
-      }
-    \prop_gput:Nnx #1 {base} {\int_use:N \l_array_tmp_int}
-  }{}
-  }
-}
-%    \end{macrocode}
-% \end{macro}
-%
-% \begin{macro}{\array_length:N}
-%    \begin{macrocode}
-\cs_new_protected:Npn \array_length:N #1
-{
-  \int_eval:n {\prop_item:Nn #1 {top} - \prop_item:Nn #1 {base}}
-}
-%    \end{macrocode}
-% \end{macro}
-%    \begin{macrocode}
-\ExplSyntaxOff
-%    \end{macrocode}
-%
-% \iffalse
-%</array>
-% \fi
-% 
-%\Finale

Deleted: trunk/Master/texmf-dist/source/latex/hobby/hobby.ins
===================================================================
--- trunk/Master/texmf-dist/source/latex/hobby/hobby.ins	2023-09-01 14:59:58 UTC (rev 68136)
+++ trunk/Master/texmf-dist/source/latex/hobby/hobby.ins	2023-09-01 21:15:20 UTC (rev 68137)
@@ -1,94 +0,0 @@
-%%
-%% This is file `hobby.ins',
-%% generated with the docstrip utility.
-%%
-%% The original source files were:
-%%
-%% hobby.dtx  (with options: `install')
-%% ----------------------------------------------------------------
-%% hobby --- a TikZ/PGF library for drawing smooth(ish) curves using
-%%           Hobby's algorithm (implemented in LaTeX3)
-%% E-mail: loopspace at mathforge.org
-%% Released under the LaTeX Project Public License v1.3c or later
-%% See http://www.latex-project.org/lppl.txt
-%% ----------------------------------------------------------------
-%% 
-\input docstrip.tex
-\keepsilent
-\askforoverwritefalse
-\preamble
-----------------------------------------------------------------
-hobby --- a TikZ/PGF library for drawing smooth(ish) curves using
-          Hobby's algorithm (implemented in LaTeX3)
-E-mail: loopspace at mathforge.org
-Released under the LaTeX Project Public License v1.3c or later
-See http://www.latex-project.org/lppl.txt
-----------------------------------------------------------------
-
-\endpreamble
-\postamble
-
-Copyright (C) 2012 by Andrew Stacey <loopspace at mathforge.org>
-
-This file may be distributed and/or modified under the conditions
-of the LaTeX Project Public License, either version 1.3 of this
-license or (at your option) any later version.
-The latest version of this license is in:
-
-   http://www.latex-project.org/lppl.txt
-
-and version 1.3 or later is part of all distributions of LaTeX
-version 2005/12/01 or later.
-
-This work is "maintained" (as per LPPL maintenance status) by
-Andrew Stacey.
-
-This work consists of the files  hobby.dtx
-                                 hobby_doc.tex
-and the derived files            hobby.code.tex
-                                 pgflibraryhobby.code.tex
-                                 tikzlibraryhobby.code.tex
-                                 pml3array.sty
-                                 hobby.ins
-                                 hobby.pdf
-                                 hobby_code.pdf
-                                 README.txt
-
-\endpostamble
-\usedir{tex/latex/hobby}
-\generate{\file{tikzlibraryhobby.code.tex} {\from{hobby.dtx}{tikzlibrary}}}
-\generate{\file{pgflibraryhobby.code.tex} {\from{hobby.dtx}{pgflibrary}}}
-\generate{\file{hobby.code.tex}
-{\from{hobby.dtx}{hobby}}}
-\generate{\file{pml3array.sty}
-{\from{hobby.dtx}{array}}}
-\endbatchfile
-%% 
-%% Copyright (C) 2012 by Andrew Stacey <loopspace at mathforge.org>
-%% 
-%% This file may be distributed and/or modified under the conditions
-%% of the LaTeX Project Public License, either version 1.3 of this
-%% license or (at your option) any later version.
-%% The latest version of this license is in:
-%% 
-%%    http://www.latex-project.org/lppl.txt
-%% 
-%% and version 1.3 or later is part of all distributions of LaTeX
-%% version 2005/12/01 or later.
-%% 
-%% This work is "maintained" (as per LPPL maintenance status) by
-%% Andrew Stacey.
-%% 
-%% This work consists of the files  hobby.dtx
-%%                                  hobby_doc.tex
-%% and the derived files            hobby.code.tex
-%%                                  pgflibraryhobby.code.tex
-%%                                  tikzlibraryhobby.code.tex
-%%                                  pml3array.sty
-%%                                  hobby.ins
-%%                                  hobby.pdf
-%%                                  hobby_code.pdf
-%%                                  README.txt
-%% 
-%%
-%% End of file `hobby.ins'.

Added: trunk/Master/texmf-dist/source/latex/hobby/hobby_code.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/hobby/hobby_code.dtx	                        (rev 0)
+++ trunk/Master/texmf-dist/source/latex/hobby/hobby_code.dtx	2023-09-01 21:15:20 UTC (rev 68137)
@@ -0,0 +1,3614 @@
+% \iffalse meta-comment
+%<*internal>
+\iffalse
+%</internal>
+%<*readme>
+----------------------------------------------------------------
+hobby --- a TikZ/PGF library for drawing smooth(ish) curves using
+          Hobby's algorithm (implemented in LaTeX3)
+E-mail: loopspace at mathforge.org
+Released under the LaTeX Project Public License v1.3c or later
+See http://www.latex-project.org/lppl.txt
+----------------------------------------------------------------
+
+This package defines a path generation function for TikZ/PGF
+which implements Hobby's algorithm for a path built out of Bezier
+curves which passes through a given set of points.
+
+The implementation is in LaTeX3.  It can be used as as a TikZ
+`to path`.
+%</readme>
+%<*internal>
+\fi
+\def\nameofplainTeX{plain}
+\ifx\fmtname\nameofplainTeX\else
+  \expandafter\begingroup
+\fi
+%</internal>
+%<*install>
+\input docstrip.tex
+\keepsilent
+\askforoverwritefalse
+\preamble
+----------------------------------------------------------------
+hobby --- a TikZ/PGF library for drawing smooth(ish) curves using
+          Hobby's algorithm (implemented in LaTeX3)
+E-mail: loopspace at mathforge.org
+Released under the LaTeX Project Public License v1.3c or later
+See http://www.latex-project.org/lppl.txt
+----------------------------------------------------------------
+
+\endpreamble
+\postamble
+
+Copyright (C) 2012-2021 by Andrew Stacey <loopspace at mathforge.org>
+
+This file may be distributed and/or modified under the conditions
+of the LaTeX Project Public License, either version 1.3 of this
+license or (at your option) any later version.
+The latest version of this license is in:
+
+   http://www.latex-project.org/lppl.txt
+
+and version 1.3 or later is part of all distributions of LaTeX
+version 2005/12/01 or later.
+
+This work is "maintained" (as per LPPL maintenance status) by
+Andrew Stacey.
+
+This work consists of the files  hobby_code.dtx
+                                 hobby.tex
+and the derived files            hobby.code.tex
+                                 pgflibraryhobby.code.tex
+                                 tikzlibraryhobby.code.tex
+                                 pml3array.sty
+                                 hobby-l3draw.sty
+                                 hobby.ins
+                                 hobby.pdf
+                                 hobby_code.pdf
+                                 README.txt
+
+\endpostamble
+\usedir{tex/latex/hobby}
+\generate{\file{tikzlibraryhobby.code.tex} {\from{hobby_code.dtx}{tikzlibrary}}}
+\generate{\file{pgflibraryhobby.code.tex} {\from{hobby_code.dtx}{pgflibrary}}}
+\generate{\file{hobby.code.tex} {\from{hobby_code.dtx}{hobby}}}
+\generate{\file{pml3array.sty} {\from{hobby_code.dtx}{array}}}
+\generate{\file{hobby-l3draw.sty} {\from{hobby_code.dtx}{l3hobby}}}
+%</install>
+%<install>\endbatchfile
+%<*internal>
+\usedir{source/latex/hobby}
+\generate{
+  \file{\jobname.ins}{\from{\jobname.dtx}{install}}
+}
+\nopreamble\nopostamble
+\generate{
+   \file{README.txt}{\from{\jobname.dtx}{readme}}
+}
+\ifx\fmtname\nameofplainTeX
+  \expandafter\endbatchfile
+\else
+  \expandafter\endgroup
+\fi
+%</internal>
+%<*driver>
+\documentclass[full]{l3doc}
+\usepackage[T1]{fontenc}
+\usepackage{csquotes}
+\usepackage{lmodern}
+\usepackage{tikz}
+\usepackage{amsmath}
+\usetikzlibrary{hobby,decorations.pathreplacing}
+\usepackage[margin=3cm]{geometry}
+\EnableCrossrefs
+\CodelineIndex
+\RecordChanges
+\addtolength{\hoffset}{.4in}
+\begin{document}
+  \DocInput{\jobname.dtx}
+\end{document}
+%</driver>
+% \fi
+%
+% \CheckSum{3427}
+%
+% \CharacterTable
+%  {Upper-case    \A\B\C\D\E\F\G\H\I\J\K\L\M\N\O\P\Q\R\S\T\U\V\W\X\Y\Z
+%   Lower-case    \a\b\c\d\e\f\g\h\i\j\k\l\m\n\o\p\q\r\s\t\u\v\w\x\y\z
+%   Digits        \0\1\2\3\4\5\6\7\8\9
+%   Exclamation   \!     Double quote  \"     Hash (number) \#
+%   Dollar        \$     Percent       \%     Ampersand     \&
+%   Acute accent  \'     Left paren    \(     Right paren   \)
+%   Asterisk      \*     Plus          \+     Comma         \,
+%   Minus         \-     Point         \.     Solidus       \/
+%   Colon         \:     Semicolon     \;     Less than     \<
+%   Equals        \=     Greater than  \>     Question mark \?
+%   Commercial at \@     Left bracket  \[     Backslash     \\
+%   Right bracket \]     Circumflex    \^     Underscore    \_
+%   Grave accent  \`     Left brace    \{     Vertical bar  \|
+%   Right brace   \}     Tilde         \~}
+%
+%
+%
+% \DoNotIndex{\newcommand,\newenvironment}
+%
+% \providecommand*{\url}{\texttt}
+% \title{The \textsf{Hobby} package: code}
+% \author{Andrew Stacey \\ \url{loopspace at mathforge.org}}
+% \date{\hobbyVersion\ from\ \hobbyDate}
+% \maketitle
+%
+%
+% \StopEventually{\PrintChanges}
+% \section{Implementation}
+%
+% \subsection{Main Code}
+%
+% \iffalse
+%<*hobby>
+% \fi
+%
+% We use \LaTeX3 syntax so need to load the requisite packages
+%    \begin{macrocode}
+\RequirePackage{pml3array}
+\ExplSyntaxOn
+%    \end{macrocode}
+%
+%    \begin{macrocode}
+\cs_generate_variant:Nn \fp_set:Nn {Nx}
+\cs_generate_variant:Nn \tl_if_eq:nnTF {VnTF}
+\cs_generate_variant:Nn \tl_if_eq:nnTF {xnTF}
+%    \end{macrocode}
+%
+% \subsubsection{Initialisation}
+%
+% We declare all our variables.
+%
+% Start with version and date, together with a check to see if we've been loaded twice (fail gracefully if so).
+%
+%    \begin{macrocode}
+\tl_clear:N \l_tmpa_tl
+\tl_if_exist:NT \g__hobby_version
+{
+  \tl_set:Nn \l_tmpa_tl {
+    \ExplSyntaxOff
+    \tl_clear:N \l_tmpa_tl
+    \endinput
+  }
+}
+\tl_use:N \l_tmpa_tl
+
+\tl_new:N \g__hobby_version
+\tl_new:N \g__hobby_date
+\tl_gset:Nn \g__hobby_version {1.12}
+\tl_gset:Nn \g__hobby_date {2023-09-01}
+\DeclareDocumentCommand \hobbyVersion {}
+{
+  \tl_use:N \g__hobby_version
+}
+\DeclareDocumentCommand \hobbyDate {}
+{
+  \tl_use:N \g__hobby_date
+}
+%    \end{macrocode}
+%
+% The function for computing the lengths of the control points depends on three parameters.
+% These are set to \(a = \sqrt{2}\), \(b = 1/16\), and \(c = \frac{3 - \sqrt{5}}{2}\).
+%    \begin{macrocode}
+\fp_new:N \g_hobby_parama_fp
+\fp_new:N \g_hobby_paramb_fp
+\fp_new:N \g_hobby_paramc_fp
+\fp_gset:Nn \g_hobby_parama_fp {2^.5}
+\fp_gset:Nn \g_hobby_paramb_fp {1/16}
+\fp_gset:Nn \g_hobby_paramc_fp {(3-5^.5)/2}
+%    \end{macrocode}
+%
+% Now we define our objects for use in generating the path.
+%
+% \begin{macro}{\l_hobby_closed_bool}
+% \Verb+\l_hobby_closed_bool+ is \Verb+true+ if the path is closed.
+%    \begin{macrocode}
+\bool_new:N \l_hobby_closed_bool
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\l_hobby_disjoint_bool}
+% \Verb+\l_hobby_disjoint_bool+ is \Verb+true+ if the path should start with a \Verb+moveto+ command.
+%    \begin{macrocode}
+\bool_new:N \l_hobby_disjoint_bool
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\l_hobby_save_aux_bool}
+% \Verb+\l_hobby_save_aux_bool+ is \Verb+true+ if when saving paths then they should be saved to the \Verb+aux+ file.
+%    \begin{macrocode}
+\bool_new:N \l_hobby_save_aux_bool
+\bool_set_true:N \l_hobby_save_aux_bool
+\DeclareDocumentCommand \HobbyDisableAux {}
+{
+  \bool_set_false:N \l_hobby_save_aux_bool
+}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\g__hobby_points_array}
+% \Verb+\g__hobby_points_array+ is an array holding the specified points on the path.
+% In the \LaTeX3 code, a ``point'' is a token list of the form \Verb+<number>, <number>+.
+% This gives us the greatest flexibility in passing points back and forth between the \LaTeX3 code and any calling code.
+% The array is indexed by integers beginning with \(0\).
+% In the documentation, we will use the notation \(z_k\) to refer to the \(k\)th point.
+%    \begin{macrocode}
+\array_new:N \g__hobby_points_array
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\g__hobby_points_x_array}
+% \Verb+\g__hobby_points_x_array+ is an array holding the \(x\)--{}coordinates of the specified points.
+%    \begin{macrocode}
+\array_new:N \g__hobby_points_x_array
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\l_hobby_points_y_array}
+% \Verb+\g__hobby_points_y_array+ is an array holding the \(y\)--{}coordinates of the specified points.
+%    \begin{macrocode}
+\array_new:N \g__hobby_points_y_array
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\g__hobby_actions_array}
+% \Verb+\g__hobby_actions_array+ is an array holding the (encoded) action to be taken out on the segment of the path ending at that point.
+%    \begin{macrocode}
+\array_new:N \g__hobby_actions_array
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\g__hobby_angles_array}
+% \Verb+\g__hobby_angles_array+ is an array holding the angles of the lines between the points.
+% Specifically, the angle indexed by \(k\) is the angle in radians of the line from \(z_k\) to \(z_{k+1}\).
+%    \begin{macrocode}
+\array_new:N \g__hobby_angles_array
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\g__hobby_distances_array}
+% \Verb+\g__hobby_distances_array+ is an array holding the distances between the points.
+% Specifically, the distance indexed by \(k\), which we will write as \(d_k\), is the length of the line from \(z_k\) to \(z_{k+1}\).
+%    \begin{macrocode}
+\array_new:N \g__hobby_distances_array
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\g__hobby_tension_out_array}
+% \Verb+\g__hobby_tension_out_array+ is an array holding the tension for the path as it leaves each point.
+% This is a parameter that controls how much the curve ``flexes'' as it leaves the point.
+% In the following, this will be written \(\tau_k\).
+%    \begin{macrocode}
+\array_new:N \g__hobby_tension_out_array
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\g__hobby_tension_in_array}
+% \Verb+\g__hobby_tension_in_array+ is an array holding the tension for the path as it arrives at each point.
+% This is a parameter that controls how much the curve ``flexes'' as it gets to the point.
+% In the following, this will be written \(\overline{\tau}_k\).
+%    \begin{macrocode}
+\array_new:N \g__hobby_tension_in_array
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\g__hobby_matrix_a_array}
+% \Verb+\g__hobby_matrix_a_array+ is an array holding the subdiagonal of the linear system that has to be solved to find the angles of the control points.
+% In the following, this will be denoted by \(A_i\).
+% The first index is \(1\).
+%    \begin{macrocode}
+\array_new:N \g__hobby_matrix_a_array
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\g__hobby_matrix_b_array}
+% \Verb+\g__hobby_matrix_b_array+ is an array holding the diagonal of the linear system that has to be solved to find the angles of the control points.
+% In the following, this will be denoted by \(B_i\).
+% The first index is \(0\).
+%    \begin{macrocode}
+\array_new:N \g__hobby_matrix_b_array
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\g__hobby_matrix_c_array}
+% \Verb+\g__hobby_matrix_c_array+ is an array holding the superdiagonal of the linear system that has to be solved to find the angles of the control points.
+% In the following, this will be denoted by \(C_i\).
+% The first index is \(0\).
+%    \begin{macrocode}
+\array_new:N \g__hobby_matrix_c_array
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\g__hobby_matrix_d_array}
+% \Verb+\g__hobby_matrix_d_array+ is an array holding the target vector of the linear system that has to be solved to find the angles of the control points.
+% In the following, this will be denoted by \(D_i\).
+% The first index is \(1\).
+%    \begin{macrocode}
+\array_new:N \g__hobby_matrix_d_array
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\g__hobby_vector_u_array}
+% \Verb+\g__hobby_vector_u_array+ is an array holding the perturbation of the linear system for closed paths.
+% The coefficient matrix for an \emph{open} path is tridiagonal and that means that Gaussian elimination runs faster than expected (\(O(n)\) instead of \(O(n^3)\)).
+% The matrix for a closed path is not tridiagonal but is not far off.
+% It can be solved by perturbing it to a tridiagonal matrix and then modifying the result.
+% This array represents a utility vector in that perturbation.
+% In the following, the vector will be denoted by \(u\).
+% The first index is \(1\).
+%    \begin{macrocode}
+\array_new:N \g__hobby_vector_u_array
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\g__hobby_excess_angle_array}
+% \Verb+\g__hobby_excess_angle_array+ is an array that allows the user to say that the algorithm should add a multiple of \(2 \pi\) to the angle differences.
+% This is because these angles are wrapped to the interval \((-\pi,\pi]\) but the wrapping might go wrong near the end points due to computation accuracy.
+% The first index is \(1\).
+%    \begin{macrocode}
+\array_new:N \g__hobby_excess_angle_array
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\g__hobby_psi_array}
+% \Verb+\g__hobby_psi_array+ is an array holding the difference of the angles of the lines entering and exiting a point.
+% That is, \(\psi_k\) is the angle between the lines joining \(z_k\) to \(z_{k-1}\) and \(z_{k+1}\).
+% The first index is \(1\).
+%    \begin{macrocode}
+\array_new:N \g__hobby_psi_array
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\g__hobby_theta_array}
+% \Verb+\g__hobby_theta_array+ is an array holding the angles of the outgoing control points for the generated path.
+% These are measured relative to the line joining the point to the next point on the path.
+% The first index is \(0\).
+%    \begin{macrocode}
+\array_new:N \g__hobby_theta_array
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\g__hobby_phi_array}
+% \Verb+\g__hobby_phi_array+ is an array holding the angles of the incoming control points for the generated path.
+% These are measured relative to the line joining the point to the previous point on the path.
+% The first index is \(1\).
+%    \begin{macrocode}
+\array_new:N \g__hobby_phi_array
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\g__hobby_sigma_array}
+% \Verb+\g__hobby_sigma_array+ is an array holding the lengths of the outgoing control points for the generated path.
+% The units are such that the length of the line to the next specified point is one unit.
+%    \begin{macrocode}
+\array_new:N \g__hobby_sigma_array
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\g__hobby_rho_array}
+% \Verb+\g__hobby_rho_array+ is an array holding the lengths of the incoming control points for the generated path.
+% The units are such that the length of the line to the previous specified point is one unit.
+%    \begin{macrocode}
+\array_new:N \g__hobby_rho_array
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\g__hobby_controla_array}
+% \Verb+\g__hobby_controla_array+ is an array holding the coordinates of the first control points on the curves.
+% The format is the same as for \Verb+\g__hobby_points_array+.
+%    \begin{macrocode}
+\array_new:N \g__hobby_controla_array
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\g__hobby_controlb_array}
+% \Verb+\g__hobby_controlb_array+ is an array holding the coordinates of the second control points on the curves.
+% The format is the same as for \Verb+\g__hobby_points_array+.
+%    \begin{macrocode}
+\array_new:N \g__hobby_controlb_array
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\l_hobby_matrix_v_fp}
+% \Verb+\l_hobby_matrix_v_fp+ is a number which is used when doing the perturbation of the solution of the linear system for a closed curve.
+% There is actually a vector, \(v\), that this corresponds to but that vector only has one component that needs computation.
+%    \begin{macrocode}
+\fp_new:N \l_hobby_matrix_v_fp
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\l_hobby_tempa_tl}
+% \Verb+\l_hobby_tempa_tl+ is a temporary variable of type \Verb+tl+.
+%    \begin{macrocode}
+\fp_new:N \l_hobby_tempa_tl
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\l_hobby_tempb_tl}
+% \Verb+\l_hobby_tempb_tl+ is a temporary variable of type \Verb+tl+.
+%    \begin{macrocode}
+\fp_new:N \l_hobby_tempb_tl
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\l_hobby_tempa_fp}
+% \Verb+\l_hobby_tempa_fp+ is a temporary variable of type \Verb+fp+.
+%    \begin{macrocode}
+\fp_new:N \l_hobby_tempa_fp
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\l_hobby_tempb_fp}
+% \Verb+\l_hobby_tempb_fp+ is a temporary variable of type \Verb+fp+.
+%    \begin{macrocode}
+\fp_new:N \l_hobby_tempb_fp
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\l_hobby_tempc_fp}
+% \Verb+\l_hobby_tempc_fp+ is a temporary variable of type \Verb+fp+.
+%    \begin{macrocode}
+\fp_new:N \l_hobby_tempc_fp
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\l_hobby_tempd_fp}
+% \Verb+\l_hobby_tempd_fp+ is a temporary variable of type \Verb+fp+.
+%    \begin{macrocode}
+\fp_new:N \l_hobby_tempd_fp
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\l_hobby_temps_fp}
+% \Verb+\l_hobby_temps_fp+ is a temporary variable of type \Verb+fp+.
+%    \begin{macrocode}
+\fp_new:N \l_hobby_temps_fp
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\g__hobby_in_curl_fp}
+% \Verb+\g__hobby_in_curl_fp+ is the ``curl'' at the end of an open path.
+% This is used if the angle at the end is not specified.
+%    \begin{macrocode}
+\fp_new:N \g__hobby_in_curl_fp
+\fp_gset:Nn \g__hobby_in_curl_fp {1}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\g__hobby_out_curl_fp}
+% \Verb+\g__hobby_out_curl_fp+ is the ``curl'' at the start of an open path.
+% This is used if the angle at the start is not specified.
+%    \begin{macrocode}
+\fp_new:N \g__hobby_out_curl_fp
+\fp_gset:Nn \g__hobby_out_curl_fp {1}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\g__hobby_in_angle_fp}
+% \Verb+\g__hobby_in_angle_fp+ is the angle at the end of an open path.
+% If this is not specified, it will be computed automatically.
+% It is set to \Verb+\c_inf_fp+ to allow easy detection of when it has been specified.
+%    \begin{macrocode}
+\fp_new:N \g__hobby_in_angle_fp
+\fp_gset_eq:NN \g__hobby_in_angle_fp \c_inf_fp
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\g__hobby_out_angle_fp}
+% \Verb+\g__hobby_out_angle_fp+ is the angle at the start of an open path.
+% If this is not specified, it will be computed automatically.
+% It is set to \Verb+\c_inf_fp+ to allow easy detection of when it has been specified.
+%    \begin{macrocode}
+\fp_new:N \g__hobby_out_angle_fp
+\fp_gset_eq:NN \g__hobby_out_angle_fp \c_inf_fp
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\g__hobby_npoints_int}
+% \Verb+\g__hobby_npoints_int+ is one less than the number of points on the curve.
+% As our list of points starts at \(0\), this is the index of the last point.
+% In the algorithm for a closed curve, some points are repeated whereupon this is incremented so that it is always the index of the last point.
+%    \begin{macrocode}
+\int_new:N \g__hobby_npoints_int
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\g__hobby_draw_int}
+%    \begin{macrocode}
+\int_new:N \g__hobby_draw_int
+%    \end{macrocode}
+% \end{macro}
+%
+% A ``point'' is a key-value list setting the x-value, the y-value, and the tensions at that point.
+% Using keys makes it easier to pass points from the algorithm code to the calling code and vice versa without either knowing too much about the other.
+%    \begin{macrocode}
+\keys_define:nn {hobby / read in all} {
+  point .code:n = {
+    \fp_set:Nn \l_hobby_tempa_fp {\clist_item:nn {#1} {1}}
+    \fp_set:Nn \l_hobby_tempb_fp {\clist_item:nn {#1} {2}}
+  },
+  tension~out .fp_set:N = \l_hobby_tempc_fp,
+  tension~in .fp_set:N = \l_hobby_tempd_fp,
+  excess~angle .fp_set:N = \l_hobby_temps_fp,
+  break .tl_set:N = \l_tmpb_tl,
+  blank .tl_set:N = \l_tmpa_tl,
+  tension .meta:n = { tension~out=#1, tension~in=#1 },
+  break .default:n = false,
+  blank .default:n = false,
+  invert~soft~blanks .choice:,
+  invert~soft~blanks / true .code:n = {
+    \int_gset:Nn \g__hobby_draw_int {0}
+  },
+  invert~soft~blanks / false .code:n = {
+    \int_gset:Nn \g__hobby_draw_int {1}
+  },
+  invert~soft~blanks .default:n = true,
+  tension~out .default:n = 1,
+  tension~in .default:n = 1,
+  excess~angle .default:n = 0,
+  in~angle .fp_gset:N = \g__hobby_in_angle_fp,
+  out~angle .fp_gset:N = \g__hobby_out_angle_fp,
+  in~curl .fp_gset:N = \g__hobby_in_curl_fp,
+  out~curl .fp_gset:N = \g__hobby_out_curl_fp,
+  closed .bool_gset:N = \g__hobby_closed_bool,
+  closed .default:n = true,
+  disjoint .bool_gset:N = \g__hobby_disjoint_bool,
+  disjoint .default:n = true,
+  break~default .code:n = {
+    \keys_define:nn { hobby / read in all }
+    {
+      break .default:n = #1
+    }
+  },
+  blank~default .code:n = {
+    \keys_define:nn { hobby / read in all }
+    {
+      blank .default:n = #1
+    }
+  },
+}
+%    \end{macrocode}
+% There are certain other parameters that can be set for a given curve.
+%    \begin{macrocode}
+\keys_define:nn { hobby / read in params} {
+  in~angle .fp_gset:N = \g__hobby_in_angle_fp,
+  out~angle .fp_gset:N = \g__hobby_out_angle_fp,
+  in~curl .fp_gset:N = \g__hobby_in_curl_fp,
+  out~curl .fp_gset:N = \g__hobby_out_curl_fp,
+  closed .bool_gset:N = \g__hobby_closed_bool,
+  closed .default:n = true,
+  disjoint .bool_gset:N = \g__hobby_disjoint_bool,
+  disjoint .default:n = true,
+  break~default .code:n = {
+    \keys_define:nn { hobby / read in all }
+    {
+      break .default:n = #1
+    }
+  },
+  blank~default .code:n = {
+    \keys_define:nn { hobby / read in all }
+    {
+      blank .default:n = #1
+    }
+  },
+  invert~soft~blanks .choice:,
+  invert~soft~blanks / true .code:n = {
+    \int_gset:Nn \g__hobby_draw_int {0}
+  },
+  invert~soft~blanks / false .code:n = {
+    \int_gset:Nn \g__hobby_draw_int {1}
+  },
+  invert~soft~blanks .default:n = true,
+}
+%    \end{macrocode}
+% \begin{macro}{\hobby_distangle:n}
+% Computes the distance and angle between successive points.
+% The argument given is the index of the current point.
+% Assumptions: the points are in \Verb+\g__hobby_points_x_array+ and \Verb+\g__hobby_points_y_array+ and the index of the last point is \Verb+\g__hobby_npoints_int+.
+%    \begin{macrocode}
+\cs_set:Nn \hobby_distangle:n {
+  \fp_set:Nn \l_hobby_tempa_fp {
+    (\array_get:Nn \g__hobby_points_x_array {#1 + 1})
+    - (\array_get:Nn \g__hobby_points_x_array {#1})}
+
+  \fp_set:Nn \l_hobby_tempb_fp {
+    (\array_get:Nn \g__hobby_points_y_array {#1 + 1})
+    - (\array_get:Nn \g__hobby_points_y_array {#1})}
+
+  \fp_set:Nn \l_hobby_tempc_fp { atan ( \l_hobby_tempb_fp, \l_hobby_tempa_fp ) }
+  \fp_veclen:NVV \l_hobby_tempd_fp \l_hobby_tempa_fp \l_hobby_tempb_fp
+
+  \array_gpush:Nx \g__hobby_angles_array {\fp_to_tl:N \l_hobby_tempc_fp}
+  \array_gpush:Nx \g__hobby_distances_array {\fp_to_tl:N \l_hobby_tempd_fp}
+  }
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\fp_veclen:NVV}
+% Computes the length of the vector specified by the latter two arguments, storing the answer in the first.
+%    \begin{macrocode}
+\cs_new:Nn \fp_veclen:Nnn {
+  \fp_set:Nn #1 {((#2)^2 + (#3)^2)^.5}
+}
+\cs_generate_variant:Nn \fp_veclen:Nnn {NVV}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\hobby_ctrllen:Nnn}
+% Computes the length of the control point vector from the two angles, storing the answer in the first argument given.
+%    \begin{macrocode}
+\cs_new:Nn \hobby_ctrllen:Nnn {
+  \fp_set:Nn #1 {(2 - \g_hobby_parama_fp
+    * ( sin(#2) - \g_hobby_paramb_fp * sin(#3) )
+    * ( sin(#3) - \g_hobby_paramb_fp * sin(#2) )
+    * ( cos(#2) - cos(#3) ) )
+    / ( 1 + (1 - \g_hobby_paramc_fp) * cos(#3) + \g_hobby_paramc_fp * cos(#2))}
+}
+\cs_generate_variant:Nn \hobby_ctrllen:Nnn {NVV}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\hobby_append_point_copy:n}
+%   This function adds a copy of the point (numbered by its argument) to the end of the list of points, copying all the relevant data  (coordinates, tension, etc.).
+%
+% Originally from Bruno Le Foch on TeX-SX.
+%    \begin{macrocode}
+\cs_new_protected:Npn \hobby_append_point_copy:n #1
+  {
+    \hobby_append_point_copy_aux:Nn \g__hobby_points_array {#1}
+    \hobby_append_point_copy_aux:Nn \g__hobby_points_x_array {#1}
+    \hobby_append_point_copy_aux:Nn \g__hobby_points_y_array {#1}
+    \hobby_append_point_copy_aux:Nn \g__hobby_tension_in_array {#1}
+    \hobby_append_point_copy_aux:Nn \g__hobby_tension_out_array {#1}
+    \hobby_append_point_copy_aux:Nn \g__hobby_excess_angle_array {#1}
+    \hobby_append_point_copy_aux:Nn \g__hobby_actions_array {#1}
+  }
+\cs_new_protected:Npn \hobby_append_point_copy_aux:Nn #1#2
+  { \array_gpush:Nx #1 { \array_get:Nn #1 {#2} } }
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\hobby_gen_path:}
+% This is the curve generation function.
+% We assume at the start that we have an array containing all the points that the curve must go through, and the various curve parameters have been initialised.
+% So these must be set up by a wrapper function which then calls this one.
+% The list of required information is:
+% \begin{enumerate}
+% \item \Verb+\g__hobby_points_x_array+
+% \item \Verb+\g__hobby_points_y_array+
+% \item \Verb+\g__hobby_tension_out_array+
+% \item \Verb+\g__hobby_tension_in_array+
+% \item \Verb+\g__hobby_excess_angle_array+
+% \item \Verb+\g__hobby_in_curl_fp+
+% \item \Verb+\g__hobby_out_curl_fp+
+% \item \Verb+\g__hobby_in_angle_fp+
+% \item \Verb+\g__hobby_out_angle_fp+
+% \item \Verb+\g__hobby_closed_bool+
+% \item \Verb+\g__hobby_actions_array+
+% \end{enumerate}
+%
+%    \begin{macrocode}
+\cs_new:Nn \hobby_gen_path:
+{
+%    \end{macrocode}
+% For much of the time, we can pretend that a closed path is the same as an open path.
+% To do this, we need to make the end node an internal node by repeating the \(z_1\) node as the \(z_{n+1}\)th node.
+% We also check that the last (\(z_n\)) and first (\(z_0\)) nodes are the same, otherwise we repeat the \(z_0\) node as well.
+%    \begin{macrocode}
+\bool_if:NT \g__hobby_closed_bool {
+%    \end{macrocode}
+% Are the \(x\)-values of the first and last points different?
+%    \begin{macrocode}
+  \fp_compare:nTF {(\array_get:Nn \g__hobby_points_x_array {0})
+    =
+    (\array_top:N \g__hobby_points_x_array)}
+  {
+%    \end{macrocode}
+% No, so compare the \(y\)-values.
+% Are the \(y\)-values of the first and last points different?
+%    \begin{macrocode}
+    \fp_compare:nF {
+      \array_get:Nn \g__hobby_points_y_array {0}
+      =
+      \array_top:N \g__hobby_points_y_array
+    }
+  {
+%    \end{macrocode}
+% Yes, so we need to duplicate the first point, with all of its data.
+%    \begin{macrocode}
+    \hobby_append_point_copy:n {0}
+  }
+  }
+  {
+%    \end{macrocode}
+% Yes, so we need to duplicate the first point, with all of its data.
+%    \begin{macrocode}
+    \hobby_append_point_copy:n {0}
+  }
+%    \end{macrocode}
+% Now that we are sure that the first and last points are identical, we need to duplicate the first-but-one point (and all of its data).
+%    \begin{macrocode}
+    \hobby_append_point_copy:n {1}
+}
+%    \end{macrocode}
+%
+% Set \Verb+\g__hobby_npoints_int+ to the number of points (minus one).
+%    \begin{macrocode}
+\int_gset:Nn \g__hobby_npoints_int {\array_length:N \g__hobby_points_y_array}
+%    \end{macrocode}
+% At this point, we need to decide what to do.
+% This will depend on whether we have any intermediate points.
+%    \begin{macrocode}
+\int_compare:nNnTF {\g__hobby_npoints_int} = {0} {
+%    \end{macrocode}
+% Only one point, do nothing
+%    \begin{macrocode}
+}
+{
+  \int_compare:nNnTF {\g__hobby_npoints_int} = {1} {
+%    \end{macrocode}
+% Only two points, skip processing.
+% Just need to set the incoming and outgoing angles
+%    \begin{macrocode}
+\hobby_distangle:n {0}
+\fp_compare:nF { \g__hobby_out_angle_fp == \c_inf_fp }
+{
+  \fp_set:Nn \l_hobby_tempa_fp { \g__hobby_out_angle_fp
+    - \array_get:Nn \g__hobby_angles_array {0}}
+%    \end{macrocode}
+% We want to ensure that these angles lie in the range \((-\pi,\pi]\).
+% So if the angle is bigger than \(\pi\), we subtract \(2 \pi\).
+% (It shouldn't be that we can get bigger than \(3 \pi\) - check this)
+%    \begin{macrocode}
+    \fp_compare:nT {\l_hobby_tempa_fp > \c_pi_fp }
+    {
+      \fp_sub:Nn \l_hobby_tempa_fp {2 * \c_pi_fp}
+    }
+%    \end{macrocode}
+% Similarly, we check to see if the angle is less than \(-\pi\).
+%    \begin{macrocode}
+    \fp_compare:nT {\l_hobby_tempa_fp < -\c_pi_fp }
+    {
+      \fp_add:Nn \l_hobby_tempa_fp {2 * \c_pi_fp}
+    }
+  \array_gput:Nnx \g__hobby_theta_array {0} {\fp_to_tl:N \l_hobby_tempa_fp}
+    \fp_compare:nT { \g__hobby_in_angle_fp == \c_inf_fp }
+    {
+%^^A      \fp_mul:Nn \l_hobby_tempa_fp {-1}
+      \array_gput:Nnx \g__hobby_phi_array {1}{ \fp_to_tl:N \l_hobby_tempa_fp}
+    }
+   }
+\fp_compare:nTF { \g__hobby_in_angle_fp == \c_inf_fp }
+{
+  \fp_compare:nT { \g__hobby_out_angle_fp == \c_inf_fp }
+  {
+    \array_gput:Nnx \g__hobby_phi_array {1} {0}
+    \array_gput:Nnx \g__hobby_theta_array {0} {0}
+  }
+}
+{
+  \fp_set:Nn \l_hobby_tempa_fp { - \g__hobby_in_angle_fp + \c_pi_fp
++ (\array_get:Nn \g__hobby_angles_array {0})}
+  \fp_compare:nT {\l_hobby_tempa_fp > \c_pi_fp }
+  {
+    \fp_sub:Nn \l_hobby_tempa_fp {2 * \c_pi_fp}
+  }
+  \fp_compare:nT {\l_hobby_tempa_fp < -\c_pi_fp }
+  {
+    \fp_add:Nn \l_hobby_tempa_fp {2 * \c_pi_fp}
+  }
+
+  \array_gput:Nnx \g__hobby_phi_array {1}
+  {\fp_to_tl:N \l_hobby_tempa_fp}
+  \fp_compare:nT { \g__hobby_out_angle_fp == \c_inf_fp }
+    {
+%^^A      \fp_mul:Nn \l_hobby_tempa_fp {-1}
+      \array_gput:Nnx \g__hobby_theta_array {0}{ \fp_to_tl:N \l_hobby_tempa_fp}
+    }
+}
+
+  }
+  {
+%    \end{macrocode}
+% Got enough points, go on with processing
+%    \begin{macrocode}
+    \hobby_compute_path:
+  }
+  \hobby_build_path:
+}
+}
+%    \end{macrocode}
+% \end{macro}
+%
+%
+% \begin{macro}{\hobby_compute_path:}
+% This is the path builder where we have enough points to run the algorithm.
+%    \begin{macrocode}
+\cs_new:Nn \hobby_compute_path:
+{
+%    \end{macrocode}
+% Our first step is to go through the list of points and compute the distances and angles between successive points.
+% Thus \(d_i\) is the distance from \(z_i\) to \(z_{i+1}\) and the angle is the angle of the line from \(z_i\) to \(z_{i+1}\).
+%    \begin{macrocode}
+\int_step_function:nnnN {0} {1} {\g__hobby_npoints_int - 1} \hobby_distangle:n
+%    \end{macrocode}
+%
+% For the majority of the code, we're only really interested in the differences of the angles.
+% So for each internal point we compute the differences in the angles.
+%    \begin{macrocode}
+  \int_step_inline:nnnn {1} {1} {\g__hobby_npoints_int - 1} {
+    \fp_set:Nx \l_hobby_tempa_fp {
+    \array_get:Nn \g__hobby_angles_array {##1}
+    - \array_get:Nn \g__hobby_angles_array {##1 - 1}}
+%    \end{macrocode}
+% We want to ensure that these angles lie in the range \((-\pi,\pi]\).
+% So if the angle is bigger than \(\pi\), we subtract \(2 \pi\).
+% (It shouldn't be that we can get bigger than \(3 \pi\) - check this.)
+%    \begin{macrocode}
+    \fp_compare:nTF {\l_hobby_tempa_fp > \c_pi_fp }
+    {
+      \fp_sub:Nn \l_hobby_tempa_fp {2 * \c_pi_fp}
+    }
+    {}
+%    \end{macrocode}
+% Similarly, we check to see if the angle is less than \(-\pi\).
+%    \begin{macrocode}
+    \fp_compare:nTF {\l_hobby_tempa_fp <= -\c_pi_fp }
+    {
+      \fp_add:Nn \l_hobby_tempa_fp {2 * \c_pi_fp}
+    }
+    {}
+%    \end{macrocode}
+% The wrapping routine might not get it right at the edges so we add in the override.
+%    \begin{macrocode}
+\array_get:NnNTF \g__hobby_excess_angle_array {##1} \l_tmpa_tl {
+  \fp_add:Nn \l_hobby_tempa_fp {2 * \c_pi_fp * \l_tmpa_tl}
+  }{}
+%    \end{macrocode}
+%    \begin{macrocode}
+    \array_gput:Nnx \g__hobby_psi_array {##1}{\fp_to_tl:N \l_hobby_tempa_fp}
+  }
+%    \end{macrocode}
+%
+% Next, we generate the matrix.
+% We start with the subdiagonal.
+% This is indexed from \(1\) to \(n-1\).
+%    \begin{macrocode}
+  \int_step_inline:nnnn {1} {1} {\g__hobby_npoints_int - 1} {
+    \array_gput:Nnx \g__hobby_matrix_a_array {##1} {\fp_to_tl:n {
+       \array_get:Nn \g__hobby_tension_in_array {##1}^2
+      * \array_get:Nn \g__hobby_distances_array {##1}
+      * \array_get:Nn \g__hobby_tension_in_array {##1 + 1}
+  }}
+}
+%    \end{macrocode}
+%
+% Next, we attack main diagonal.
+% We might need to adjust the first and last terms, but we'll do that in a minute.
+%    \begin{macrocode}
+  \int_step_inline:nnnn {1} {1} {\g__hobby_npoints_int - 1} {
+
+  \array_gput:Nnx \g__hobby_matrix_b_array {##1} {\fp_to_tl:n
+{(3 * (\array_get:Nn \g__hobby_tension_in_array {##1 + 1}) - 1) *
+ (\array_get:Nn \g__hobby_tension_out_array {##1})^2 *
+(\array_get:Nn \g__hobby_tension_out_array {##1 - 1})
+* ( \array_get:Nn \g__hobby_distances_array {##1 - 1})
++
+(3 * (\array_get:Nn \g__hobby_tension_out_array {##1 - 1}) - 1)
+* (\array_get:Nn \g__hobby_tension_in_array {##1})^2
+* (\array_get:Nn \g__hobby_tension_in_array {##1 + 1})
+* (\array_get:Nn \g__hobby_distances_array {##1})}
+}
+}
+%    \end{macrocode}
+%
+% Next, the superdiagonal.
+%    \begin{macrocode}
+  \int_step_inline:nnnn {1} {1} {\g__hobby_npoints_int - 2} {
+
+  \array_gput:Nnx \g__hobby_matrix_c_array {##1} {\fp_to_tl:n
+{(\array_get:Nn \g__hobby_tension_in_array {##1})^2
+* (\array_get:Nn \g__hobby_tension_in_array {##1 - 1})
+* (\array_get:Nn \g__hobby_distances_array {##1 - 1})
+}}
+
+}
+%    \end{macrocode}
+%
+% Lastly (before the adjustments), the target vector.
+%    \begin{macrocode}
+  \int_step_inline:nnnn {1} {1} {\g__hobby_npoints_int - 2} {
+
+  \array_gput:Nnx \g__hobby_matrix_d_array {##1} {\fp_to_tl:n
+{
+- (\array_get:Nn \g__hobby_psi_array {##1 + 1})
+* (\array_get:Nn \g__hobby_tension_out_array {##1})^2
+* (\array_get:Nn \g__hobby_tension_out_array {##1 - 1})
+* (\array_get:Nn \g__hobby_distances_array {##1 - 1})
+- (3 * (\array_get:Nn \g__hobby_tension_out_array {##1 - 1}) - 1)
+* (\array_get:Nn \g__hobby_psi_array {##1})
+* (\array_get:Nn \g__hobby_tension_in_array {##1})^2
+* (\array_get:Nn \g__hobby_tension_in_array {##1 + 1})
+* (\array_get:Nn \g__hobby_distances_array {##1})
+}
+}
+}
+%    \end{macrocode}
+%
+% Next, there are some adjustments at the ends.
+% These differ depending on whether the path is open or closed.
+%    \begin{macrocode}
+\bool_if:NTF \g__hobby_closed_bool {
+%    \end{macrocode}
+% Closed path
+%    \begin{macrocode}
+\array_gput:Nnx \g__hobby_matrix_c_array {0} {\fp_to_tl:n {
+- (\array_get:Nn \g__hobby_distances_array {\g__hobby_npoints_int - 2})
+* (\array_get:Nn \g__hobby_tension_out_array {\g__hobby_npoints_int - 2})
+* (\array_get:Nn \g__hobby_tension_out_array {\g__hobby_npoints_int - 1})^2
+}}
+
+\array_gput:Nnn \g__hobby_matrix_b_array {0} {1}
+\array_gput:Nnn \g__hobby_matrix_d_array {0} {0}
+
+\array_gput:Nnx \g__hobby_matrix_b_array {\g__hobby_npoints_int - 1} {\fp_to_tl:n {
+(\array_get:Nn \g__hobby_matrix_b_array {\g__hobby_npoints_int - 1})
++ 1
+}}
+
+ \array_gput:Nnx \g__hobby_matrix_d_array {\g__hobby_npoints_int - 1} {\fp_to_tl:n {
+- (\array_get:Nn \g__hobby_psi_array {1})
+* (\array_get:Nn \g__hobby_tension_out_array {\g__hobby_npoints_int -1})^2
+* (\array_get:Nn \g__hobby_tension_out_array {\g__hobby_npoints_int -2})
+* (\array_get:Nn \g__hobby_distances_array {\g__hobby_npoints_int - 2})
+- (3 * (\array_get:Nn \g__hobby_tension_out_array {\g__hobby_npoints_int - 2}) - 1)
+* (\array_get:Nn \g__hobby_psi_array {\g__hobby_npoints_int - 1})
+* (\array_get:Nn \g__hobby_tension_in_array {\g__hobby_npoints_int - 1})^2
+* (\array_get:Nn \g__hobby_tension_in_array {\g__hobby_npoints_int})
+* (\array_get:Nn \g__hobby_distances_array {\g__hobby_npoints_int -1})
+}
+}
+%    \end{macrocode}
+% We also need to populate the \(u\)-vector
+%    \begin{macrocode}
+  \array_gput:Nnn \g__hobby_vector_u_array {0} {1}
+\array_gput:Nnn \g__hobby_vector_u_array {\g__hobby_npoints_int - 1} {1}
+  \int_step_inline:nnnn {1} {1} {\g__hobby_npoints_int - 2} {
+  \array_gput:Nnn \g__hobby_vector_u_array {##1} {0}
+  }
+%    \end{macrocode}
+% And define the significant entry in the \(v\)-vector.
+%    \begin{macrocode}
+\fp_set:Nn \l_hobby_matrix_v_fp {
+(\array_get:Nn \g__hobby_tension_out_array {\g__hobby_npoints_int -1})^2
+* (\array_get:Nn \g__hobby_tension_out_array {\g__hobby_npoints_int -2})
+* (\array_get:Nn \g__hobby_distances_array {\g__hobby_npoints_int -2})
+}
+}
+{
+%    \end{macrocode}
+% Open path.
+% First, we test to see if \(\theta_0\) has been specified.
+%    \begin{macrocode}
+\fp_compare:nTF { \g__hobby_out_angle_fp == \c_inf_fp }
+{
+  \array_gput:Nnx \g__hobby_matrix_b_array {0}  {\fp_to_tl:n {
+  (\array_get:Nn \g__hobby_tension_in_array {1})^3
+* \g__hobby_in_curl_fp
++
+(3 * (\array_get:Nn \g__hobby_tension_in_array {1}) - 1)
+* (\array_get:Nn \g__hobby_tension_out_array {0})^3
+}}
+
+  \array_gput:Nnx \g__hobby_matrix_c_array {0} {\fp_to_tl:n {
+  (\array_get:Nn \g__hobby_tension_out_array {0})^3
++
+(3 * (\array_get:Nn \g__hobby_tension_out_array {0}) - 1)
+* (\array_get:Nn \g__hobby_tension_in_array {1})^3
+* \g__hobby_in_curl_fp
+}}
+
+  \array_gput:Nnx \g__hobby_matrix_d_array {0} {\fp_to_tl:n {
+-(  (\array_get:Nn \g__hobby_tension_out_array {0})^3
++
+(3 * (\array_get:Nn \g__hobby_tension_out_array {0}) - 1)
+* (\array_get:Nn \g__hobby_tension_in_array {1})^3
+* \g__hobby_in_curl_fp)
+* (\array_get:Nn \g__hobby_psi_array {1})
+}}
+
+}
+{
+  \array_gput:Nnn \g__hobby_matrix_b_array {0} {1}
+  \array_gput:Nnn \g__hobby_matrix_c_array {0} {0}
+  \fp_set:Nn \l_hobby_tempa_fp { \g__hobby_out_angle_fp
+    - \array_get:Nn \g__hobby_angles_array {0}}
+%    \end{macrocode}
+% We want to ensure that these angles lie in the range \((-\pi,\pi]\).
+% So if the angle is bigger than \(\pi\), we subtract \(2 \pi\).
+% (It shouldn't be that we can get bigger than \(3 \pi\) - check this)
+%    \begin{macrocode}
+    \fp_compare:nT {\l_hobby_tempa_fp > \c_pi_fp }
+    {
+      \fp_sub:Nn \l_hobby_tempa_fp {2 * \c_pi_fp}
+    }
+%    \end{macrocode}
+% Similarly, we check to see if the angle is less than \(-\pi\).
+%    \begin{macrocode}
+    \fp_compare:nT {\l_hobby_tempa_fp < -\c_pi_fp }
+    {
+      \fp_add:Nn \l_hobby_tempa_fp {2 * \c_pi_fp}
+    }
+  \array_gput:Nnx \g__hobby_matrix_d_array {0} {\fp_to_tl:N \l_hobby_tempa_fp}
+}
+%    \end{macrocode}
+%
+% Next, if \(\phi_n\) has been given.
+%    \begin{macrocode}
+\fp_compare:nTF { \g__hobby_in_angle_fp == \c_inf_fp }
+{
+
+ \array_gput:Nnx \g__hobby_matrix_b_array {\g__hobby_npoints_int - 1} {\fp_to_tl:n {
+\array_get:Nn \g__hobby_matrix_b_array {\g__hobby_npoints_int - 1}
+- (\array_get:Nn \g__hobby_tension_out_array {\g__hobby_npoints_int - 1})^2
+* (\array_get:Nn \g__hobby_tension_out_array {\g__hobby_npoints_int - 2})
+* (\array_get:Nn \g__hobby_distances_array {\g__hobby_npoints_int - 2})
+*
+((3 * (\array_get:Nn \g__hobby_tension_in_array {\g__hobby_npoints_int} ) - 1)
+* (\array_get:Nn \g__hobby_tension_out_array {\g__hobby_npoints_int - 1})^3 \l_tmpa_tl
+* \g__hobby_out_curl_fp
++
+(\array_get:Nn \g__hobby_tension_in_array {\g__hobby_npoints_int })^3)
+/
+((3 * (\array_get:Nn \g__hobby_tension_out_array {\g__hobby_npoints_int -2}) - 1)
+* (\array_get:Nn \g__hobby_tension_in_array {\g__hobby_npoints_int})^3
++
+( \array_get:Nn \g__hobby_tension_out_array {\g__hobby_npoints_int - 1})^3
+* \g__hobby_out_curl_fp)
+}}
+
+ \array_gput:Nnx \g__hobby_matrix_d_array {\g__hobby_npoints_int - 1} {\fp_to_tl:n {
+- (3 * (\array_get:Nn \g__hobby_tension_out_array {\g__hobby_npoints_int - 2}) - 1)
+* (\array_get:Nn \g__hobby_psi_array {\g__hobby_npoints_int - 1})
+* (\array_get:Nn \g__hobby_tension_in_array {\g__hobby_npoints_int - 1})^2
+* (\array_get:Nn \g__hobby_tension_in_array {\g__hobby_npoints_int})
+* (\array_get:Nn \g__hobby_distances_array {\g__hobby_npoints_int - 1})
+}}
+
+}
+{
+  \fp_set:Nn \l_hobby_tempa_fp { - \g__hobby_in_angle_fp + \c_pi_fp
++ (\array_get:Nn \g__hobby_angles_array {\g__hobby_npoints_int - 1})}
+  \fp_compare:nT {\l_hobby_tempa_fp > \c_pi_fp }
+  {
+    \fp_sub:Nn \l_hobby_tempa_fp {2 * \c_pi_fp}
+  }
+  \fp_compare:nT {\l_hobby_tempa_fp < -\c_pi_fp }
+  {
+    \fp_add:Nn \l_hobby_tempa_fp {2 * \c_pi_fp}
+  }
+
+  \array_gput:Nnx \g__hobby_phi_array {\g__hobby_npoints_int}
+  {\fp_to_tl:N \l_hobby_tempa_fp}
+
+   \array_gput:Nnx \g__hobby_matrix_d_array  {\g__hobby_npoints_int - 1} {\fp_to_tl:n {
+ \l_hobby_tempa_fp
+ * (\array_get:Nn \g__hobby_tension_out_array {\g__hobby_npoints_int - 1})^2
+* (\array_get:Nn \g__hobby_tension_out_array {\g__hobby_npoints_int - 2})
+* (\array_get:Nn \g__hobby_distances_array {\g__hobby_npoints_int - 2})
+-
+(3 * ( \array_get:Nn \g__hobby_tension_out_array {\g__hobby_npoints_int - 2}) - 1)
+* (\array_get:Nn \g__hobby_psi_array {\g__hobby_npoints_int - 1})
+* (\array_get:Nn \g__hobby_tension_in_array  {\g__hobby_npoints_int - 1})^2
+* (\array_get:Nn \g__hobby_tension_in_array {\g__hobby_npoints_int})
+* (\array_get:Nn \g__hobby_distances_array  {\g__hobby_npoints_int - 1}) }}
+}
+%    \end{macrocode}
+% End of adjustments for open paths.
+%    \begin{macrocode}
+}
+%    \end{macrocode}
+%
+% Now we have the tridiagonal matrix in place, we implement the solution.
+% We start with the forward eliminations.
+%    \begin{macrocode}
+\int_step_inline:nnnn {1} {1} {\g__hobby_npoints_int - 1} {
+
+  \array_gput:Nnx \g__hobby_matrix_b_array {##1} {\fp_to_tl:n {
+  (\array_get:Nn \g__hobby_matrix_b_array {##1 - 1})
+* (\array_get:Nn \g__hobby_matrix_b_array {##1})
+-
+(\array_get:Nn \g__hobby_matrix_c_array {##1 - 1})
+* (\array_get:Nn \g__hobby_matrix_a_array {##1})
+}}
+%    \end{macrocode}
+% The last time, we don't touch the \(C\)-vector.
+%    \begin{macrocode}
+  \int_compare:nT {##1 < \g__hobby_npoints_int - 1} {
+
+  \array_gput:Nnx \g__hobby_matrix_c_array {##1} {\fp_to_tl:n {
+(\array_get:Nn \g__hobby_matrix_b_array {##1 - 1})
+    * (\array_get:Nn \g__hobby_matrix_c_array {##1})
+}}
+  }
+
+  \array_gput:Nnx \g__hobby_matrix_d_array {##1} {\fp_to_tl:n {
+(\array_get:Nn \g__hobby_matrix_b_array {##1 - 1})
+  * (\array_get:Nn \g__hobby_matrix_d_array {##1})
+-
+  (\array_get:Nn \g__hobby_matrix_d_array {##1 - 1})
+  * (\array_get:Nn \g__hobby_matrix_a_array {##1})
+}}
+%    \end{macrocode}
+% On a closed path, we also want to know \(M^{-1} u\) so need to do the elimination steps on \(u\) as well.
+%    \begin{macrocode}
+  \bool_if:NT \g__hobby_closed_bool {
+  \array_gput:Nnx \g__hobby_vector_u_array {##1} {\fp_to_tl:n {
+(\array_get:Nn \g__hobby_matrix_b_array {##1 - 1})
+* (\array_get:Nn \g__hobby_vector_u_array {##1})
+-
+(\array_get:Nn \g__hobby_vector_u_array {##1 - 1})
+* (\array_get:Nn \g__hobby_matrix_a_array {##1})
+}}
+}
+}
+%    \end{macrocode}
+% Now we start the back substitution.
+% The first step is slightly different to the general step.
+%    \begin{macrocode}
+ \array_gput:Nnx \g__hobby_theta_array  {\g__hobby_npoints_int - 1} {\fp_to_tl:n {
+(\array_get:Nn \g__hobby_matrix_d_array  {\g__hobby_npoints_int - 1})
+/ (\array_get:Nn \g__hobby_matrix_b_array  {\g__hobby_npoints_int - 1})
+}}
+%    \end{macrocode}
+% For a closed path, we need to work with \(u\) as well.
+%    \begin{macrocode}
+\bool_if:NT \g__hobby_closed_bool {
+ \array_gput:Nnx \g__hobby_vector_u_array  {\g__hobby_npoints_int - 1} {\fp_to_tl:n {
+  (\array_get:Nn \g__hobby_vector_u_array  {\g__hobby_npoints_int - 1})
+/ (\array_get:Nn \g__hobby_matrix_b_array  {\g__hobby_npoints_int - 1})
+}}
+}
+%    \end{macrocode}
+% Now we iterate over the vectors, doing the remaining back substitutions.
+%    \begin{macrocode}
+\int_step_inline:nnnn {\g__hobby_npoints_int - 2} {-1} {0} {
+
+  \array_gput:Nnx \g__hobby_theta_array {##1} {\fp_to_tl:n {
+( (\array_get:Nn \g__hobby_matrix_d_array {##1})
+  - (\array_get:Nn \g__hobby_theta_array  {##1 + 1})
+  * (\array_get:Nn \g__hobby_matrix_c_array {##1})
+) / (\array_get:Nn \g__hobby_matrix_b_array {##1})
+}}
+}
+\bool_if:NT \g__hobby_closed_bool {
+%    \end{macrocode}
+% On a closed path, we also need to work out \(M^{-1} u\).
+%    \begin{macrocode}
+\int_step_inline:nnnn {\g__hobby_npoints_int - 2} {-1} {0} {
+  \array_gput:Nnx \g__hobby_vector_u_array {##1} {\fp_to_tl:n
+{
+    ((\array_get:Nn \g__hobby_vector_u_array {##1})
+    - (\array_get:Nn \g__hobby_vector_u_array  {##1 + 1})
+    * (\array_get:Nn \g__hobby_matrix_c_array {##1})
+    ) / (\array_get:Nn \g__hobby_matrix_b_array {##1})
+}}
+}
+%    \end{macrocode}
+% Then we compute \(v^\top M^{-1}u\) and \(v^\top M^{-1} \theta\).
+% As \(v\) has a particularly simple form, these inner products are easy to compute.
+%    \begin{macrocode}
+
+\fp_set:Nn \l_hobby_tempb_fp {
+((\array_get:Nn \g__hobby_theta_array {1})
+* \l_hobby_matrix_v_fp
+- (\array_get:Nn \g__hobby_theta_array  {\g__hobby_npoints_int - 1})
+) / (
+(\array_get:Nn \g__hobby_vector_u_array {1})
+* \l_hobby_matrix_v_fp
+- (\array_get:Nn \g__hobby_vector_u_array  {\g__hobby_npoints_int - 1})
++ 1
+)}
+
+\int_step_inline:nnnn {0} {1} {\g__hobby_npoints_int - 1} {
+
+  \array_gput:Nnx \g__hobby_theta_array {##1} {\fp_to_tl:n {
+  (\array_get:Nn \g__hobby_theta_array {##1})
+  - (\array_get:Nn \g__hobby_vector_u_array {##1})
+  * \l_hobby_tempb_fp
+}}
+}
+}
+%    \end{macrocode}
+%
+% Now that we have computed the \(\theta_i\)s, we can quickly compute the \(\phi_i\)s.
+%
+%    \begin{macrocode}
+\int_step_inline:nnnn {1} {1} {\g__hobby_npoints_int - 1} {
+
+    \array_gput:Nnx \g__hobby_phi_array {##1} {\fp_to_tl:n {
+      - (\array_get:Nn \g__hobby_psi_array {##1})
+      - (\array_get:Nn \g__hobby_theta_array {##1})
+  }}
+  }
+%    \end{macrocode}
+%
+% If the path is open, this works for all except \(\phi_n\).
+% If the path is closed, we can drop our added point.
+% Cheaply, of course.
+%    \begin{macrocode}
+\bool_if:NTF \g__hobby_closed_bool {
+  \int_gdecr:N \g__hobby_npoints_int
+}{
+%    \end{macrocode}
+% If \(\phi_n\) was not given, we compute it from \(\theta_{n-1}\).
+%    \begin{macrocode}
+\fp_compare:nT { \g__hobby_in_angle_fp == \c_inf_fp }
+{
+ \array_gput:Nnx \g__hobby_phi_array {\g__hobby_npoints_int} {\fp_to_tl:n {
+((3 * (\array_get:Nn \g__hobby_tension_in_array {\g__hobby_npoints_int}) - 1)
+* (\array_get:Nn \g__hobby_tension_out_array {\g__hobby_npoints_int - 1})^3
+* \g__hobby_out_curl_fp
++
+(\array_get:Nn \g__hobby_tension_in_array {\g__hobby_npoints_int })^3)
+/
+((3 * (\array_get:Nn \g__hobby_tension_out_array  {\g__hobby_npoints_int -2}) - 1)
+* (\array_get:Nn \g__hobby_tension_in_array {\g__hobby_npoints_int})^3 \l_tmpa_tl
++
+(\array_get:Nn \g__hobby_tension_out_array  {\g__hobby_npoints_int - 1})^3
+* \g__hobby_out_curl_fp)
+*
+(\array_get:Nn \g__hobby_theta_array  {\g__hobby_npoints_int -1})
+}}
+}
+}
+}
+%    \end{macrocode}
+% \end{macro}
+%
+%
+% \begin{macro}{\hobby_build_path:}
+% Once we've computed the angles, we build the actual path.
+%    \begin{macrocode}
+\cs_new:Nn \hobby_build_path:
+{
+%    \end{macrocode}
+% Next task is to compute the \(\rho_i\) and \(\sigma_i\).
+%    \begin{macrocode}
+\int_step_inline:nnnn {0} {1} {\g__hobby_npoints_int - 1} {
+
+  \fp_set:Nn \l_hobby_tempa_fp {\array_get:Nn \g__hobby_theta_array {##1}}
+
+  \fp_set:Nn \l_hobby_tempb_fp {\array_get:Nn \g__hobby_phi_array  {##1 + 1}}
+
+  \hobby_ctrllen:NVV \l_hobby_temps_fp \l_hobby_tempa_fp \l_hobby_tempb_fp
+
+   \array_gput:Nnx \g__hobby_sigma_array {##1 + 1} {\fp_to_tl:N \l_hobby_temps_fp}
+
+  \hobby_ctrllen:NVV \l_hobby_temps_fp \l_hobby_tempb_fp \l_hobby_tempa_fp
+
+   \array_gput:Nnx \g__hobby_rho_array {##1} {\fp_to_tl:N \l_hobby_temps_fp}
+
+  }
+%    \end{macrocode}
+% Lastly, we generate the coordinates of the control points.
+%    \begin{macrocode}
+\int_step_inline:nnnn {0} {1} {\g__hobby_npoints_int - 1} {
+\array_gput:Nnx \g__hobby_controla_array  {##1 + 1} {\fp_eval:n  {
+(\array_get:Nn \g__hobby_points_x_array {##1})
++
+  (\array_get:Nn \g__hobby_distances_array {##1}) *
+  (\array_get:Nn \g__hobby_rho_array {##1}) *
+cos ( (\array_get:Nn \g__hobby_angles_array {##1})
++
+  (\array_get:Nn \g__hobby_theta_array {##1}))
+/3
+}, \fp_eval:n {
+( \array_get:Nn \g__hobby_points_y_array {##1}) +
+  (\array_get:Nn \g__hobby_distances_array {##1}) *
+  (\array_get:Nn \g__hobby_rho_array {##1}) *
+sin ( (\array_get:Nn \g__hobby_angles_array {##1})
++
+  (\array_get:Nn \g__hobby_theta_array {##1}))
+/3
+}
+}
+}
+\int_step_inline:nnnn {1} {1} {\g__hobby_npoints_int} {
+  \array_gput:Nnx \g__hobby_controlb_array {##1} {
+    \fp_eval:n {\array_get:Nn \g__hobby_points_x_array {##1}
+- (\array_get:Nn \g__hobby_distances_array  {##1 - 1})
+* (\array_get:Nn \g__hobby_sigma_array {##1})
+* cos((\array_get:Nn \g__hobby_angles_array  {##1 - 1})
+- (\array_get:Nn \g__hobby_phi_array {##1}))/3
+}, \fp_eval:n {
+  (\array_get:Nn \g__hobby_points_y_array {##1})
+- (\array_get:Nn \g__hobby_distances_array  {##1 - 1})
+* (\array_get:Nn \g__hobby_sigma_array {##1})
+* sin((\array_get:Nn \g__hobby_angles_array  {##1 - 1})
+- (\array_get:Nn \g__hobby_phi_array {##1}))/3
+} }
+ }
+}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\hobbyinit}
+% Initialise the settings for Hobby's algorithm
+%    \begin{macrocode}
+\NewDocumentCommand \hobbyinit {m m m} {
+  \hobby_set_cmds:NNN #1#2#3
+  \hobby_clear_path:
+}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\hobbyaddpoint}
+% This adds a point, possibly with tensions, to the current stack.
+%    \begin{macrocode}
+\NewDocumentCommand \hobbyaddpoint { m } {
+  \keys_set:nn { hobby/read in all }
+  {
+    tension~out,
+    tension~in,
+    excess~angle,
+    blank,
+    break,
+    #1
+  }
+  \tl_if_eq:VnTF \l_tmpa_tl {true}
+  {\tl_set:Nn \l_tmpa_tl {2}}
+  {
+    \tl_if_eq:VnTF \l_tmpa_tl {soft}
+    {\tl_set:Nn \l_tmpa_tl {0}}
+    {\tl_set:Nn \l_tmpa_tl {1}}
+  }
+  \tl_if_eq:VnTF \l_tmpb_tl {true}
+    {\tl_put_right:Nn \l_tmpa_tl {1}}
+    {\tl_put_right:Nn \l_tmpa_tl {0}}
+  \tl_set:Nx \l_hobby_tempa_tl {\fp_use:N \l_hobby_tempa_fp}
+  \tl_set:Nx \l_hobby_tempb_tl {\fp_use:N \l_hobby_tempb_fp}
+  \hobby_add_point:VVVVVV \l_hobby_tempa_tl \l_hobby_tempb_tl \l_hobby_tempc_fp \l_hobby_tempd_fp \l_hobby_temps_fp \l_tmpa_tl
+}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\hobby_add_point:n}
+%    \begin{macrocode}
+\cs_new_nopar:Npn \hobby_add_point:nnnnnn #1#2#3#4#5#6
+{
+    \array_gpush:Nn \g__hobby_actions_array { #6 }
+    \array_gpush:Nn \g__hobby_tension_out_array { #3 }
+    \array_gpush:Nn \g__hobby_tension_in_array { #4 }
+    \array_gpush:Nn \g__hobby_excess_angle_array { #5 }
+    \array_gpush:Nn \g__hobby_points_array { #1, #2 }
+    \array_gpush:Nn \g__hobby_points_x_array { #1 }
+    \array_gpush:Nn \g__hobby_points_y_array { #2 }
+}
+\cs_generate_variant:Nn \hobby_add_point:nnnnnn {VVVVVV}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\hobbysetparams}
+% This sets the parameters for the curve.
+%    \begin{macrocode}
+\NewDocumentCommand \hobbysetparams { m } {
+  \keys_set:nn { hobby / read in params }
+  {
+    #1
+  }
+}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\hobby_set_cmds:NNN}
+% The path-generation code doesn't know what to actually do with the path so the initialisation code will set some macros to do that.
+% This is an auxiliary command that sets these macros.
+%    \begin{macrocode}
+\cs_new:Npn \hobby_moveto:nnn #1#2#3 {}
+\cs_new:Npn \hobby_curveto:nnn #1#2#3 {}
+\cs_new:Npn \hobby_close:n #1 {}
+\cs_generate_variant:Nn \hobby_moveto:nnn {VVV,nnV}
+\cs_generate_variant:Nn \hobby_curveto:nnn {VVV}
+\cs_generate_variant:Nn \hobby_close:n {V}
+\cs_new:Nn \hobby_set_cmds:NNN {
+  \cs_gset_eq:NN \hobby_moveto:nnn #1
+  \cs_gset_eq:NN \hobby_curveto:nnn #2
+  \cs_gset_eq:NN \hobby_close:n #3
+}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\hobbygenpath}
+% This is the user (well, sort of) command that generates the curve.
+%    \begin{macrocode}
+\NewDocumentCommand \hobbygenpath { } {
+  \array_if_empty:NF \g__hobby_points_array {
+    \hobby_gen_path:
+  }
+}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\hobbygenifnecpath}
+% If the named path doesn't exist, it is generated and named.
+% If it does exist, we restore it.
+% Either way, we save it to the aux file.
+%    \begin{macrocode}
+\NewDocumentCommand \hobbygenifnecpath { m } {
+  \tl_if_exist:cTF {g_hobby_#1_path}
+  {
+    \tl_use:c {g_hobby_#1_path}
+  }
+  {
+    \hobby_gen_path:
+  }
+  \hobby_save_path:n {#1}
+  \hobby_save_path_to_aux:x {#1}
+}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\hobbygenifnecusepath}
+% If the named path doesn't exist, it is generated and named.
+% If it does exist, we restore it.
+% Either way, we save it to the aux file.
+%    \begin{macrocode}
+\NewDocumentCommand \hobbygenuseifnecpath { m } {
+  \tl_if_exist:cTF {g_hobby_#1_path}
+  {
+    \tl_use:c {g_hobby_#1_path}
+  }
+  {
+    \hobby_gen_path:
+  }
+  \hobby_save_path:n {#1}
+  \hobby_save_path_to_aux:x {#1}
+  \hobby_use_path:
+}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\hobbyusepath}
+% This is the user (well, sort of) command that uses the last generated curve.
+%    \begin{macrocode}
+\NewDocumentCommand \hobbyusepath { m } {
+  \hobbysetparams{#1}
+  \hobby_use_path:
+}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\hobbysavepath}
+% This is the user (well, sort of) command that uses the last generated curve.
+%    \begin{macrocode}
+\NewDocumentCommand \hobbysavepath { m } {
+  \hobby_save_path:n {#1}
+}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\hobbyrestorepath}
+% This is the user (well, sort of) command that uses the last generated curve.
+%    \begin{macrocode}
+\NewDocumentCommand \hobbyrestorepath { m } {
+  \tl_if_exist:cT {g_hobby_#1_path} {
+    \tl_use:c {g_hobby_#1_path}
+  }
+}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\hobbyshowpath}
+% This is the user (well, sort of) command that uses the last generated curve.
+%    \begin{macrocode}
+\NewDocumentCommand \hobbyshowpath { m } {
+  \tl_if_exist:cT {g_hobby_#1_path} {
+    \tl_show:c {g_hobby_#1_path}
+  }
+}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\hobbygenusepath}
+% This is the user (well, sort of) command that generates a curve and uses it.
+%    \begin{macrocode}
+\NewDocumentCommand \hobbygenusepath { } {
+  \array_if_empty:NF \g__hobby_points_array {
+    \hobby_gen_path:
+    \hobby_use_path:
+  }
+}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\hobbyclearpath}
+% This is the user (well, sort of) command that generates a curve and uses it.
+%    \begin{macrocode}
+\NewDocumentCommand \hobbyclearpath { } {
+  \hobby_clear_path:
+}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\hobby_use_path:}
+% This is the command that uses the curve.
+% As the curve data is stored globally, the same data can be reused by calling this function more than once without calling the generating function.
+%    \begin{macrocode}
+\tl_new:N \l_tmpc_tl
+\tl_new:N \l_tmpd_tl
+\cs_new:Nn \hobby_use_path: {
+  \bool_if:NT \g__hobby_disjoint_bool {
+    \array_get:NnN \g__hobby_points_array {0} \l_tmpa_tl
+    \hobby_moveto:nnV {} {} \l_tmpa_tl
+  }
+  \int_step_inline:nnnn {1} {1} {\g__hobby_npoints_int} {
+    \array_get:NnN \g__hobby_controla_array {##1} \l_tmpa_tl
+    \array_get:NnN \g__hobby_controlb_array {##1} \l_tmpb_tl
+    \array_get:NnN \g__hobby_points_array {##1} \l_tmpc_tl
+    \array_get:NnN \g__hobby_actions_array {##1} \l_tmpd_tl
+    \int_compare:nNnTF {\tl_item:Nn \l_tmpd_tl {1}} = {\g__hobby_draw_int} {
+      \hobby_curveto:VVV \l_tmpa_tl \l_tmpb_tl \l_tmpc_tl
+    }{
+      \bool_gset_false:N \g__hobby_closed_bool
+      \hobby_moveto:VVV \l_tmpa_tl \l_tmpb_tl \l_tmpc_tl
+    }
+    \tl_if_eq:xnTF {\tl_item:Nn \l_tmpd_tl {2}} {1} {
+      \bool_gset_false:N \g__hobby_closed_bool
+      \hobby_moveto:VVV \l_tmpa_tl \l_tmpb_tl \l_tmpc_tl
+    }{}
+  }
+  \bool_if:NT \g__hobby_closed_bool {
+    \array_get:NnN \g__hobby_points_array {0} \l_tmpa_tl
+    \hobby_close:V \l_tmpa_tl
+  }
+}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\hobby_save_path:n}
+% This command saves all the data needed to reinvoke the curve in a global token list that can be used to restore it afterwards.
+%    \begin{macrocode}
+\cs_new:Nn \hobby_save_path:n {
+  \tl_clear:N \l_tmpa_tl
+  \tl_put_right:Nn \l_tmpa_tl {\int_gset:Nn \g__hobby_npoints_int}
+  \tl_put_right:Nx \l_tmpa_tl {{\int_use:N \g__hobby_npoints_int}}
+  \bool_if:NTF \g__hobby_disjoint_bool {
+    \tl_put_right:Nn \l_tmpa_tl {\bool_gset_true:N}
+  }{
+    \tl_put_right:Nn \l_tmpa_tl {\bool_gset_false:N}
+  }
+  \tl_put_right:Nn \l_tmpa_tl {\g__hobby_disjoint_bool}
+  \bool_if:NTF \g__hobby_closed_bool {
+    \tl_put_right:Nn \l_tmpa_tl {\bool_gset_true:N}
+  }{
+    \tl_put_right:Nn \l_tmpa_tl {\bool_gset_false:N}
+  }
+  \tl_put_right:Nn \l_tmpa_tl {\g__hobby_closed_bool}
+  \tl_put_right:Nn \l_tmpa_tl {\array_gclear:N \g__hobby_points_array}
+  \array_map_inline:Nn \g__hobby_points_array {
+    \tl_put_right:Nn \l_tmpa_tl {
+      \array_gput:Nnn \g__hobby_points_array {##1} {##2}
+    }
+  }
+  \tl_put_right:Nn \l_tmpa_tl {\array_gclear:N \g__hobby_actions_array}
+  \array_map_inline:Nn \g__hobby_actions_array {
+    \tl_put_right:Nn \l_tmpa_tl {
+      \array_gput:Nnn \g__hobby_actions_array {##1} {##2}
+    }
+  }
+  \tl_put_right:Nn \l_tmpa_tl {\array_gclear:N \g__hobby_controla_array}
+  \array_map_inline:Nn \g__hobby_controla_array {
+    \tl_put_right:Nn \l_tmpa_tl {
+      \array_gput:Nnn \g__hobby_controla_array {##1} {##2}
+    }
+  }
+  \tl_put_right:Nn \l_tmpa_tl {\array_gclear:N \g__hobby_controlb_array}
+  \array_map_inline:Nn \g__hobby_controlb_array {
+    \tl_put_right:Nn \l_tmpa_tl {
+      \array_gput:Nnn \g__hobby_controlb_array {##1} {##2}
+    }
+  }
+  \tl_gclear_new:c {g_hobby_#1_path}
+  \tl_gset_eq:cN {g_hobby_#1_path} \l_tmpa_tl
+}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\hobby_save_path_to_aux:n}
+%    \begin{macrocode}
+\int_set:Nn \l_tmpa_int {\char_value_catcode:n {`@}}
+\char_set_catcode_letter:N @
+\cs_new:Npn \hobby_save_path_to_aux:n #1 {
+  \bool_if:nT {
+    \tl_if_exist_p:c {g_hobby_#1_path}
+    &&
+    ! \tl_if_exist_p:c {g_hobby_#1_path_saved}
+    &&
+    \l_hobby_save_aux_bool
+  }
+  {
+    \tl_clear:N \l_tmpa_tl
+    \tl_put_right:Nn \l_tmpa_tl {
+      \ExplSyntaxOn
+      \tl_gclear_new:c {g_hobby_#1_path}
+      \tl_gput_right:cn {g_hobby_#1_path}
+    }
+    \tl_put_right:Nx \l_tmpa_tl {
+      {\tl_to_str:c {g_hobby_#1_path}}
+    }
+    \tl_put_right:Nn \l_tmpa_tl {
+      \ExplSyntaxOff
+    }
+    \protected at write\@auxout{}{
+      \tl_to_str:N \l_tmpa_tl
+    }
+    \tl_new:c {g_hobby_#1_path_saved}
+  }
+}
+\char_set_catcode:nn {`@} {\l_tmpa_int}
+\cs_generate_variant:Nn \hobby_save_path_to_aux:n {x}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\hobby_clear_path:}
+%    \begin{macrocode}
+\cs_new:Nn \hobby_clear_path:
+{
+\array_gclear:N \g__hobby_points_array
+\array_gclear:N \g__hobby_points_x_array
+\array_gclear:N \g__hobby_points_y_array
+\array_gclear:N \g__hobby_angles_array
+\array_gclear:N \g__hobby_actions_array
+\array_gclear:N \g__hobby_distances_array
+\array_gclear:N \g__hobby_tension_out_array
+\array_gclear:N \g__hobby_tension_in_array
+\array_gclear:N \g__hobby_excess_angle_array
+\array_gclear:N \g__hobby_matrix_a_array
+\array_gclear:N \g__hobby_matrix_b_array
+\array_gclear:N \g__hobby_matrix_c_array
+\array_gclear:N \g__hobby_matrix_d_array
+\array_gclear:N \g__hobby_vector_u_array
+\array_gclear:N \g__hobby_psi_array
+\array_gclear:N \g__hobby_theta_array
+\array_gclear:N \g__hobby_phi_array
+\array_gclear:N \g__hobby_sigma_array
+\array_gclear:N \g__hobby_rho_array
+\array_gclear:N \g__hobby_controla_array
+\array_gclear:N \g__hobby_controlb_array
+\bool_gset_false:N \g__hobby_closed_bool
+\bool_gset_false:N \g__hobby_disjoint_bool
+
+  \int_gset:Nn \g__hobby_npoints_int {-1}
+  \int_gset:Nn \g__hobby_draw_int {1}
+  \fp_gset_eq:NN \g__hobby_in_angle_fp \c_inf_fp
+  \fp_gset_eq:NN \g__hobby_out_angle_fp \c_inf_fp
+  \fp_gset_eq:NN \g__hobby_in_curl_fp \c_one_fp
+  \fp_gset_eq:NN \g__hobby_out_curl_fp \c_one_fp
+}
+%    \end{macrocode}
+% \end{macro}
+%    \begin{macrocode}
+\ExplSyntaxOff
+%    \end{macrocode}
+% \iffalse
+%</hobby>
+% \fi
+%
+% \subsection{PGF Library}
+%
+% \iffalse
+%<*pgflibrary>
+% \fi
+%
+% The PGF level is very simple.
+% All we do is set up the path-construction commands that get passed to the path-generation function.
+%    \begin{macrocode}
+\input{hobby.code.tex}
+%    \end{macrocode}
+% Points are communicated as key-pairs.
+% These keys translate from the \LaTeX3 style points to PGF points.
+%    \begin{macrocode}
+\pgfkeys{
+  /pgf/hobby/.is family,
+  /pgf/hobby/.cd,
+  point/.code={%
+    \hobby at parse@pt#1\relax}
+}
+\def\hobby at parse@pt#1,#2\relax{%
+  \pgf at x=#1cm\relax
+  \pgf at y=#2cm\relax
+}
+%    \end{macrocode}
+%
+% \begin{macro}{hobbyatan2}
+% The original PGF version of \Verb+atan2+ had the arguments the wrong way around.
+% This was fixed in the CVS version in July 2013, but as old versions are likely to be in use for some time, we define a wrapper function that ensures that the arguments are correct.
+%    \begin{macrocode}
+\pgfmathparse{atan2(0,1)}
+\def\hobby at temp{0.0}
+\ifx\pgfmathresult\hobby at temp
+  \pgfmathdeclarefunction{hobbyatan2}{2}{%
+    \pgfmathatantwo@{#1}{#2}%
+  }
+\else
+  \pgfmathdeclarefunction{hobbyatan2}{2}{%
+    \pgfmathatantwo@{#2}{#1}%
+  }
+\fi
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\hobby at curveto}
+% This is passed to the path-generation code to translate the path into a PGF path.
+%    \begin{macrocode}
+\def\hobby at curveto#1#2#3{%
+  \pgfpathcurveto{\hobby at topgf{#1}}{\hobby at topgf{#2}}{\hobby at topgf{#3}}%
+}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\hobby at moveto}
+% This is passed to the path-generation code to translate the path into a PGF path.
+%    \begin{macrocode}
+\def\hobby at moveto#1#2#3{%
+  \pgfpathmoveto{\hobby at topgf{#3}}%
+}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\hobby at topgf}
+% Translates a \LaTeX3 point to a PGF point.
+%    \begin{macrocode}
+\def\hobby at topgf#1{%
+    \pgfqkeys{/pgf/hobby}{point={#1}}%
+}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\hobby at close}
+% Closes a path.
+%    \begin{macrocode}
+\def\hobby at close#1{%
+  \pgfpathclose
+}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\pgfpathhobby}
+% Low-level interface to the hobby construction.
+% This sets up the commands and starts the iterator.
+%    \begin{macrocode}
+\def\pgfpathhobby{%
+  \pgfutil at ifnextchar\bgroup{\pgfpath at hobby}{\pgfpath at hobby{}}}
+\def\pgfpath at hobby#1{%
+  \hobbyinit\hobby at moveto\hobby at curveto\hobby at close
+  \hobbysetparams{#1}%
+  \pgfmathsetmacro\hobby at x{\the\pgf at path@lastx/1cm}%
+  \pgfmathsetmacro\hobby at y{\the\pgf at path@lasty/1cm}%
+  \hobbyaddpoint{point={\hobby at x, \hobby at y}}%
+}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\pgfpathhobbypt}
+% Adds a point to the construction
+%    \begin{macrocode}
+\def\pgfpathhobbypt#1{%
+  #1%
+  \pgfmathsetmacro\hobby at x{\the\pgf at x/1cm}%
+  \pgfmathsetmacro\hobby at y{\the\pgf at y/1cm}%
+  \pgfutil at ifnextchar\bgroup{\pgfpathhobbyptparams}{\pgfpathhobbyptparams{}}%
+}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\pgfpathhobbyptparams}
+%    \begin{macrocode}
+\def\pgfpathhobbyptparams#1{%
+  \hobbyaddpoint{#1,point={\hobby at x, \hobby at y}}%
+}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\pgfpathhobbyend}
+%    \begin{macrocode}
+\def\pgfpathhobbyend{%
+  \ifhobby at externalise
+    \ifx\hobby at path@name\pgfutil at empty
+      \hobbygenusepath
+    \else
+      \hobbygenuseifnecpath{\hobby at path@name}%
+    \fi
+  \else
+    \hobbygenusepath
+  \fi
+  \ifx\hobby at path@name\pgfutil at empty
+  \else
+    \hobbysavepath{\hobby at path@name}%
+  \fi
+  \global\let\hobby at path@name=\pgfutil at empty
+}
+%    \end{macrocode}
+% \end{macro}
+%
+%
+% Plot handlers
+%
+% \begin{macro}{\pgfplothanderhobby}
+% Basic plot handler; uses full algorithm but therefore expensive
+%    \begin{macrocode}
+\def\pgfplothandlerhobby{%
+  \def\pgf at plotstreamstart{%
+    \hobbyinit\hobby at moveto\hobby at curveto\hobby at close
+    \global\let\pgf at plotstreampoint=\pgf at plot@hobby at firstpt
+    \global\let\pgf at plotstreamspecial=\pgfutil at gobble
+    \gdef\pgf at plotstreamend{%
+      \ifhobby at externalise
+       \ifx\hobby at path@name\pgfutil at empty
+        \hobbygenusepath
+       \else
+        \hobbygenuseifnecpath{\hobby at path@name}%
+       \fi
+      \else
+       \hobbygenusepath
+      \fi
+      \ifx\hobby at path@name\pgfutil at empty
+      \else
+       \hobbysavepath{\hobby at path@name}%
+      \fi
+      \global\let\hobby at path@name=\pgfutil at empty
+    }%
+    \let\tikz at scan@point at options=\pgfutil at empty
+  }
+}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\pgfplothandlerclosedhobby}
+% Same as above but produces a closed curve
+%    \begin{macrocode}
+\def\pgfplothandlerclosedhobby{%
+  \def\pgf at plotstreamstart{%
+    \hobbyinit\hobby at moveto\hobby at curveto\hobby at close
+    \hobbysetparams{closed=true,disjoint=true}%
+    \global\let\pgf at plotstreampoint=\pgf at plot@hobby at firstpt
+    \global\let\pgf at plotstreamspecial=\pgfutil at gobble
+    \gdef\pgf at plotstreamend{%
+      \ifhobby at externalise
+       \ifx\hobby at path@name\pgfutil at empty
+        \hobbygenusepath
+       \else
+        \hobbygenuseifnecpath{\hobby at path@name}%
+       \fi
+      \else
+       \hobbygenusepath
+      \fi
+      \ifx\hobby at path@name\pgfutil at empty
+      \else
+       \hobbysavepath{\hobby at path@name}%
+      \fi
+      \global\let\hobby at path@name=\pgfutil at empty
+    }%
+  }
+}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\pgf at plot@hobby at firstpt}
+% First point, move or line as appropriate and then start the algorithm.
+%    \begin{macrocode}
+\def\pgf at plot@hobby at firstpt#1{%
+  \pgf at plot@first at action{#1}%
+  \pgf at plot@hobby at handler{#1}%
+  \global\let\pgf at plotstreampoint=\pgf at plot@hobby at handler
+}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\pgf at plot@hobby at handler}
+% Add points to the array for the algorithm to work on.
+%    \begin{macrocode}
+\def\pgf at plot@hobby at handler#1{%
+    #1%
+    \pgfmathsetmacro\hobby at x{\the\pgf at x/1cm}%
+    \pgfmathsetmacro\hobby at y{\the\pgf at y/1cm}%
+    \hobbyaddpoint{point={\hobby at x, \hobby at y}}%
+  }
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\pgfplothandlerquickhobby}
+% Uses the ``quick'' algorithm.
+%    \begin{macrocode}
+\def\pgfplothandlerquickhobby{%
+  \def\pgf at plotstreamstart{%
+    \global\let\hobby at quick@curveto=\pgfpathcurveto
+    \global\let\pgf at plotstreampoint=\pgf at plot@qhobby at firstpt
+    \global\let\pgf at plotstreamspecial=\pgfutil at gobble
+    \global\let\pgf at plotstreamend=\pgf at plot@qhobby at end
+  }
+}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\pgf at plot@qhobby at firstpt}
+% Carry out first action (move or line) and save point.
+%    \begin{macrocode}
+\def\pgf at plot@qhobby at firstpt#1{%
+  #1%
+  \edef\hobby at temp{\noexpand\pgf at plot@first at action{\noexpand\pgfqpoint{\the\pgf at x}{\the\pgf at y}}}\hobby at temp%
+  \xdef\hobby at qpoints{\noexpand\pgfqpoint{\the\pgf at x}{\the\pgf at y}}%
+  \gdef\hobby at qpointa{}%
+  \gdef\hobby at angle{}%
+  \global\let\pgf at plotstreampoint=\pgf at plot@qhobby at secondpt
+}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\pgf at plot@qhobby at secondpt}
+% Also need to save second point.
+%    \begin{macrocode}
+\def\pgf at plot@qhobby at secondpt#1{%
+  #1%
+  \xdef\hobby at qpointa{\noexpand\pgfqpoint{\the\pgf at x}{\the\pgf at y}}%
+  \global\let\pgf at plotstreampoint=\pgf at plot@qhobby at handler
+}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\pgf at plot@qhobby at handler}
+% Wrapper around the computation macro that saves the variables globally.
+%    \begin{macrocode}
+\def\pgf at plot@qhobby at handler#1{%
+  #1
+  \edef\hobby at temp{\noexpand\hobby at quick@compute{\noexpand\pgfqpoint{\the\pgf at x}{\the\pgf at y}}}\hobby at temp
+  \global\let\hobby at qpointa=\hobby at qpointa
+  \global\let\hobby at qpoints=\hobby at qpoints
+  \global\let\hobby at angle=\hobby at angle
+%    \end{macrocode}
+% Also need to save some data for the last point
+%    \begin{macrocode}
+  \global\let\hobby at thetaone=\hobby at thetaone
+  \global\let\hobby at phitwo=\hobby at phitwo
+  \global\let\hobby at done=\hobby at done
+  \global\let\hobby at omegaone=\hobby at omegaone
+}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\pgf at plot@qhobby at end}
+%    Wrapper around the finalisation step.
+%    \begin{macrocode}
+\def\pgf at plot@qhobby at end{%
+  \hobby at quick@computeend
+}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\hobby at sf}
+% Working with points leads to computations out of range so we scale to get them into the computable arena.
+%    \begin{macrocode}
+\pgfmathsetmacro\hobby at sf{10cm}
+%    \end{macrocode}
+% \end{macro}
+%
+%
+% \begin{macro}{\hobby at quick@compute}
+% This is the macro that does all the work of computing the control points.
+% The argument is the current point, \Verb+\hobby at qpointa+ is the middle point, and \Verb+\hobby at qpoints+ is the first point.
+%    \begin{macrocode}
+\def\hobby at quick@compute#1{%
+%    \end{macrocode}
+% Save the current (second - counting from zero) point in \Verb+\pgf at xb+ and \Verb+\pgf at yb+.
+%    \begin{macrocode}
+  #1%
+  \pgf at xb=\pgf at x
+  \pgf at yb=\pgf at y
+%    \end{macrocode}
+% Save the previous (first) point in \Verb+\pgf at xa+ and \Verb+\pgf at ya+.
+%    \begin{macrocode}
+  \hobby at qpointa
+  \pgf at xa=\pgf at x
+  \pgf at ya=\pgf at y
+%    \end{macrocode}
+% Adjust so that \Verb+(\pgf at xb,\pgf at yb)+ is the vector from second to third.
+% Then compute and store the distance and angle of this vector.
+% We view this as the vector \emph{from} the midpoint and everything to do with that point has the suffix \Verb+one+.
+% Note that we divide by the scale factor here.
+%    \begin{macrocode}
+  \advance\pgf at xb by -\pgf at xa
+  \advance\pgf at yb by -\pgf at ya
+  \pgfmathsetmacro\hobby at done{sqrt((\pgf at xb/\hobby at sf)^2 + (\pgf at yb/\hobby at sf)^2)}%
+  \pgfmathsetmacro\hobby at omegaone{rad(hobbyatan2(\pgf at yb,\pgf at xb))}%
+%    \end{macrocode}
+% Now we do the same with the vector from the zeroth to the first point.
+%    \begin{macrocode}
+  \hobby at qpoints
+  \advance\pgf at xa by -\pgf at x
+  \advance\pgf at ya by -\pgf at y
+  \pgfmathsetmacro\hobby at dzero{sqrt((\pgf at xa/\hobby at sf)^2 + (\pgf at ya/\hobby at sf)^2)}%
+  \pgfmathsetmacro\hobby at omegazero{rad(hobbyatan2(\pgf at ya,\pgf at xa))}%
+%    \end{macrocode}
+% \Verb+\hobby at psi+ is the angle subtended at the midpoint.
+% We adjust to ensure that it is in the right range.
+%    \begin{macrocode}
+  \pgfmathsetmacro\hobby at psi{\hobby at omegaone - \hobby at omegazero}%
+  \pgfmathsetmacro\hobby at psi{\hobby at psi > pi ? \hobby at psi - 2*pi : \hobby at psi}%
+  \pgfmathsetmacro\hobby at psi{\hobby at psi < -pi ? \hobby at psi + 2*pi : \hobby at psi}%
+%    \end{macrocode}
+% Now we test to see if we're on the first run or not.
+% If the first, we have no incoming angle.
+%    \begin{macrocode}
+  \ifx\hobby at angle\pgfutil at empty
+%    \end{macrocode}
+% First.
+%    \begin{macrocode}
+  \pgfmathsetmacro\hobby at thetaone{-\hobby at psi * \hobby at done%
+/(\hobby at done + \hobby at dzero)}%
+  \pgfmathsetmacro\hobby at thetazero{-\hobby at psi - \hobby at thetaone}%
+  \let\hobby at phione=\hobby at thetazero
+  \let\hobby at phitwo=\hobby at thetaone
+  \else
+%    \end{macrocode}
+% Second or later.
+%    \begin{macrocode}
+  \let\hobby at thetazero=\hobby at angle
+  \pgfmathsetmacro\hobby at thetaone{%
+  -(2 * \hobby at psi + \hobby at thetazero) * \hobby at done%
+  / (2 * \hobby at done + \hobby at dzero)}%
+  \pgfmathsetmacro\hobby at phione{-\hobby at psi - \hobby at thetaone}%
+  \let\hobby at phitwo=\hobby at thetaone
+  \fi
+%    \end{macrocode}
+% Save the outgoing angle.
+%    \begin{macrocode}
+  \let\hobby at angle=\hobby at thetaone
+%    \end{macrocode}
+% Compute the control points from the angles.
+%    \begin{macrocode}
+  \hobby at quick@ctrlpts{\hobby at thetazero}{\hobby at phione}{\hobby at qpoints}{\hobby at qpointa}{\hobby at dzero}{\hobby at omegazero}%
+%    \end{macrocode}
+% Now call the call-back function
+%    \begin{macrocode}
+  \edef\hobby at temp{\noexpand\hobby at quick@curveto{\noexpand\pgfqpoint{\the\pgf at xa}{\the\pgf at ya}}{\noexpand\pgfqpoint{\the\pgf at xb}{\the\pgf at yb}}{\noexpand\pgfqpoint{\the\pgf at x}{\the\pgf at y}}}%
+\hobby at temp
+%    \end{macrocode}
+% Cycle the points round for the next iteration.
+%    \begin{macrocode}
+  \global\let\hobby at qpoints=\hobby at qpointa
+  #1
+  \xdef\hobby at qpointa{\noexpand\pgfqpoint{\the\pgf at x}{\the\pgf at y}}%
+%    \end{macrocode}
+% Save needed values in global macros
+%    \begin{macrocode}
+  \global\let\hobby at angle=\hobby at angle
+  \global\let\hobby at phitwo=\hobby at phitwo
+  \global\let\hobby at thetaone=\hobby at thetaone
+\global\let\hobby at done=\hobby at done
+\global\let\hobby at omegaone=\hobby at omegaone
+}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\hobby at wuick@computeend}
+% This is the additional code for the final run.
+%    \begin{macrocode}
+\def\hobby at quick@computeend{%
+%    \end{macrocode}
+% Compute the control points for the second part of the curve and add that to the path.
+%    \begin{macrocode}
+  \hobby at quick@ctrlpts{\hobby at thetaone}{\hobby at phitwo}{\hobby at qpoints}{\hobby at qpointa}{\hobby at done}{\hobby at omegaone}%
+%    \end{macrocode}
+% Now call the call-back function
+%    \begin{macrocode}
+  \edef\hobby at temp{\noexpand\hobby at quick@curveto{\noexpand\pgfqpoint{\the\pgf at xa}{\the\pgf at ya}}{\noexpand\pgfqpoint{\the\pgf at xb}{\the\pgf at yb}}{\noexpand\pgfqpoint{\the\pgf at x}{\the\pgf at y}}}%
+\hobby at temp
+}%
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\hobby at quick@ctrlpts}
+% Compute the control points from the angles and points given.
+%    \begin{macrocode}
+\def\hobby at quick@ctrlpts#1#2#3#4#5#6{%
+  \pgfmathsetmacro\hobby at alpha{%
+    sqrt(2) * (sin(#1 r) - 1/16 * sin(#2 r))%
+* (sin(#2 r) - 1/16 * sin(#1 r))%
+ * (cos(#1 r) - cos(#2 r))}%
+  \pgfmathsetmacro\hobby at rho{%
+    (2 + \hobby at alpha)/(1 + (1 - (3 - sqrt(5))/2)%
+ * cos(#1 r) + (3 - sqrt(5))/2 * cos(#2 r))}%
+  \pgfmathsetmacro\hobby at sigma{%
+    (2 - \hobby at alpha)/(1 + (1 - (3 - sqrt(5))/2)%
+  * cos(#2 r) +  (3 - sqrt(5))/2 * cos(#1 r))}%
+  #3%
+  \pgf at xa=\pgf at x
+  \pgf at ya=\pgf at y
+  \pgfmathsetlength\pgf at xa{%
+    \pgf at xa + #5 * \hobby at rho%
+  * cos((#1 + #6) r)/3*\hobby at sf}%
+  \pgfmathsetlength\pgf at ya{%
+    \pgf at ya + #5 * \hobby at rho%
+  * sin((#1 + #6) r)/3*\hobby at sf}%
+  #4%
+  \pgf at xb=\pgf at x
+  \pgf at yb=\pgf at y
+  \pgfmathsetlength\pgf at xb{%
+    \pgf at xb - #5 * \hobby at sigma%
+  * cos((-#2 + #6) r)/3*\hobby at sf}%
+  \pgfmathsetlength\pgf at yb{%
+    \pgf at yb - #5 * \hobby at sigma%
+  * sin((-#2 + #6) r)/3*\hobby at sf}%
+  #4%
+}
+%    \end{macrocode}
+% \end{macro}
+%
+% \iffalse
+%</pgflibrary>
+% \fi
+%
+% \subsection{TikZ Library}
+%
+% \iffalse
+%<*tikzlibrary>
+% \fi
+%
+%    \begin{macrocode}
+\usepgflibrary{hobby}
+\let\hobby at this@opts=\pgfutil at empty
+\let\hobby at next@opts=\pgfutil at empty
+\let\hobby at action=\pgfutil at empty
+\let\hobby at path@name=\pgfutil at empty
+\newif\ifhobby at externalise
+%    \end{macrocode}
+%
+% We set various TikZ keys.
+% These include the \Verb+to path+ constructor and all the various parameters that will eventually get passed to the path-generation code.
+%    \begin{macrocode}
+\def\hobby at point@options{}%
+\tikzset{
+  curve through/.style={
+    to path={
+      \pgfextra{
+        \expandafter\curvethrough\expandafter[\hobby at next@opts]{%
+          (\tikztostart) .. #1 .. (\tikztotarget)%
+        }
+      }
+    }
+  },
+  tension in/.code = {%
+    \expandafter\gdef\expandafter\hobby at point@options\expandafter%
+    {\hobby at point@options,tension in=#1}%
+  },
+  tension out/.code = {%
+    \expandafter\gdef\expandafter\hobby at point@options\expandafter%
+    {\hobby at point@options,tension out=#1}%
+  },
+  tension/.append code = {%
+    \expandafter\gdef\expandafter\hobby at point@options\expandafter%
+    {\hobby at point@options,tension=#1}%
+  },
+  excess angle/.code = {%
+    \expandafter\gdef\expandafter\hobby at point@options\expandafter%
+    {\hobby at point@options,excess angle=#1}%
+  },
+  break/.code = {%
+    \expandafter\gdef\expandafter\hobby at point@options\expandafter%
+    {\hobby at point@options,break=#1}%
+  },
+  blank/.code = {%
+    \expandafter\gdef\expandafter\hobby at point@options\expandafter%
+    {\hobby at point@options,blank=#1}%
+  },
+  designated Hobby path/.initial={next},
+  clear next Hobby path options/.code={%
+    \gdef\hobby at next@opts{}%
+  },
+  clear this Hobby path options/.code={%
+    \gdef\hobby at this@opts{}%
+  },
+  clear Hobby path options/.style={%
+    clear \pgfkeysvalueof{/tikz/designated Hobby path} Hobby path options
+  },
+  add option to this Hobby path/.code={%
+    \expandafter\gdef\expandafter\hobby at this@opts\expandafter{\hobby at this@opts#1,}%
+  },
+  add option to next Hobby path/.code={%
+    \expandafter\gdef\expandafter\hobby at next@opts\expandafter{\hobby at next@opts#1,}%
+  },
+  add option to Hobby path/.style={%
+    add option to \pgfkeysvalueof{/tikz/designated Hobby path} Hobby path={#1}%
+  },
+  closed/.style = {%
+    add option to Hobby path={closed=#1,disjoint=#1}%
+  },
+  invert blank/.style = {%
+    add option to Hobby path={invert blank=#1}%
+  },
+  closed/.default = true,
+  blank/.default = true,
+  break/.default = true,
+  invert blank/.default = true,
+  in angle/.code = {%
+    \pgfmathparse{(#1)*pi/180}%
+    \edef\@temp{in angle=\pgfmathresult,}%
+    \pgfkeysalso{add option to Hobby path/.expand once=\@temp}%
+  },
+  out angle/.code = {%
+    \pgfmathparse{(#1)*pi/180}%
+    \edef\@temp{out angle=\pgfmathresult,}%
+    \pgfkeysalso{add option to Hobby path/.expand once=\@temp}%
+  },
+  in curl/.style = {%
+    add option to Hobby path={in curl=#1}%
+  },
+  out curl/.style = {%
+    add option to Hobby path={out curl=#1}%
+  },
+  use Hobby shortcut/.code={%
+    \let\tikz at curveto@auto=\hobby at curveto@override
+    \global\let\hobby at curveto@delegate=\hobby at curveto@auto
+  },
+  use quick Hobby shortcut/.code={%
+    \let\tikz at curveto@auto=\hobby at curveto@override
+    \global\let\hobby at curveto@delegate=\hobby at qcurveto@auto
+  },
+  use previous Hobby path/.code={%
+    \hobbyusepath{#1}%
+  },
+  use previous Hobby path/.default={},%
+  save Hobby path/.code={%
+    \xdef\hobby at path@name{#1}%
+  },
+  restore Hobby path/.code={%
+    \hobbyinit\hobby at tikz@moveto\hobby at tikz@curveto\hobby at tikz@close
+    \global\let\hobby at collected@onpath\pgfutil at empty
+    \hobbyrestorepath{#1}%
+  },
+  restore and use Hobby path/.code 2 args={%
+    \hobbyinit\hobby at tikz@moveto\hobby at tikz@curveto\hobby at tikz@close
+    \global\let\hobby at collected@onpath\pgfutil at empty
+    \hobbyrestorepath{#1}%
+    \hobbyusepath{#2}%
+  },
+  show Hobby path/.code={%
+    \hobbyshowpath{#1}%
+  },
+  Hobby action/.code={%
+    \expandafter\gdef\expandafter\hobby at action\expandafter{\hobby at action#1}%
+  },
+  Hobby finish/.style={%
+    Hobby action=\hobby at finish%
+  },
+  Hobby externalise/.is if=hobby at externalise,
+  Hobby externalize/.is if=hobby at externalise
+}
+%    \end{macrocode}
+%
+% \begin{macro}{\hobby at tikz@curveto}
+% This is passed to the path-generation code to translate the path into a PGF path.
+%    \begin{macrocode}
+\def\hobby at tikz@curveto#1#2#3{%
+  \pgfutil at ifundefined{tikz at timer@start}{%
+    \expandafter\hobby at topgf\expandafter{\hobby at initial@pt}%
+    \edef\tikz at timer@start{\noexpand\pgfqpoint{\the\pgf at x}{\the\pgf at y}}%
+  }{}%
+  \hobby at topgf{#1}%
+  \edef\tikz at timer@cont at one{\noexpand\pgfqpoint{\the\pgf at x}{\the\pgf at y}}%
+  \hobby at topgf{#2}%
+  \edef\tikz at timer@cont at two{\noexpand\pgfqpoint{\the\pgf at x}{\the\pgf at y}}%
+  \hobby at topgf{#3}%
+  \let\tikz at timer=\tikz at timer@curve
+  \edef\tikz at timer@end{\noexpand\pgfqpoint{\the\pgf at x}{\the\pgf at y}}%
+  \ifx\hobby at collected@onpath\pgfutil at empty
+  \else
+  \expandafter\hobby at nodes@onpath\hobby at collected@onpath\relax\relax
+  \fi
+  \pgfpathcurveto{\hobby at topgf{#1}}{\hobby at topgf{#2}}{\hobby at topgf{#3}}%
+  \hobby at topgf{#3}%
+  \edef\tikz at timer@start{\noexpand\pgfqpoint{\the\pgf at x}{\the\pgf at y}}%
+}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\hobby at tikz@moveto}
+% This is passed to the path-generation code to translate the path into a PGF path.
+%    \begin{macrocode}
+\def\hobby at tikz@moveto#1#2#3{%
+  \pgfutil at ifundefined{tikz at timer@start}{%
+    \expandafter\hobby at topgf\expandafter{\hobby at initial@pt}%
+    \edef\tikz at timer@start{\noexpand\pgfqpoint{\the\pgf at x}{\the\pgf at y}}%
+  }{}%
+  \hobby at topgf{#3}%
+  \edef\tikz at timer@end{\noexpand\pgfqpoint{\the\pgf at x}{\the\pgf at y}}%
+  \def\pgf at temp{#1}%
+  \ifx\pgf at temp\pgfutil at empty
+    \let\tikz at timer=\tikz at timer@line
+  \expandafter\def\expandafter\hobby at collected@onpath\expandafter{\expandafter{\expandafter}\hobby at collected@onpath}
+  \else
+    \hobby at topgf{#1}%
+    \edef\tikz at timer@cont at one{\noexpand\pgfqpoint{\the\pgf at x}{\the\pgf at y}}%
+    \hobby at topgf{#2}%
+    \edef\tikz at timer@cont at two{\noexpand\pgfqpoint{\the\pgf at x}{\the\pgf at y}}%
+    \let\tikz at timer=\tikz at timer@curve
+  \fi
+  \ifx\hobby at collected@onpath\pgfutil at empty
+  \else
+  \expandafter\hobby at nodes@onpath\hobby at collected@onpath\relax\relax
+  \fi
+  \pgfpathmoveto{\hobby at topgf{#3}}%
+  \hobby at topgf{#3}%
+  \edef\tikz at timer@start{\noexpand\pgfqpoint{\the\pgf at x}{\the\pgf at y}}%
+}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\hobby at tikz@close}
+% Closes a path.
+%    \begin{macrocode}
+\def\hobby at tikz@close#1{%
+  \hobby at topgf{#1}%
+  \edef\tikz at timer@end{\noexpand\pgfqpoint{\the\pgf at x}{\the\pgf at y}}%
+  \let\tikz at timer=\tikz at timer@line
+  \ifx\hobby at collected@onpath\pgfutil at empty
+  \else
+  \expandafter\hobby at nodes@onpath\hobby at collected@onpath\relax\relax
+  \fi
+  \pgfpathclose
+}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\hobby at nodes@onpath}
+%    \begin{macrocode}
+\def\hobby at nodes@onpath#1#2\relax{%
+  \gdef\hobby at collected@onpath{#2}%
+  \def\pgf at temp{#1}%
+  \ifx\pgf at temp\pgfutil at empty
+  \else
+  \def\@gtempa{\relax}
+  \ifx\pgf at temp\@gtempa
+  \else
+  \tikz at node@is at a@labeltrue
+  \tikz at scan@next at command#1\pgf at stop
+  \tikz at node@is at a@labelfalse
+  \fi
+  \fi
+}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\curvethrough}
+% This is the parent command.
+% We initialise the path-generation code, set any parameters, and then hand over control to the point processing macro.
+%    \begin{macrocode}
+\newcommand\curvethrough[2][]{%
+  \hobbyinit\hobby at tikz@moveto\hobby at tikz@curveto\hobby at tikz@close
+  \global\let\hobby at collected@onpath\pgfutil at empty
+  \let\hobby at initial@pt\pgfutil at empty
+  \hobbysetparams{#1}%
+  \tikzset{designated Hobby path=this}%
+  \global\let\hobby at this@opts=\pgfutil at empty
+  \global\let\hobby at next@opts=\pgfutil at empty
+  \let\tikz at scan@point at options=\pgfutil at empty
+  \def\hobby at point@options{}%
+  \tikz at scan@one at point\hobby at processpt #2 \relax%
+}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\hobby at processpt}
+% This processes a list of points in the format \Verb+(0,0) [..] (1,1)+.
+% Each point is scanned by TikZ and then added to the stack to be built into the path.
+% If there are any remaining points, we call ourself again with them.
+% Otherwise, we hand over control to the path-generation code.
+%    \begin{macrocode}
+\newcommand\hobby at processpt[1]{%
+  #1%
+  \pgfmathsetmacro\hobby at x{\the\pgf at x/1cm}%
+  \pgfmathsetmacro\hobby at y{\the\pgf at y/1cm}%
+  \ifx\hobby at initial@pt\pgfutil at empty
+    \xdef\hobby at initial@pt{\hobby at x, \hobby at y}%
+  \fi
+  \expandafter\hobbyaddpoint\expandafter{\hobby at point@options,%
+    point={\hobby at x, \hobby at y}}%
+  \def\hobby at point@options{}%
+  \let\tikz at scan@point at options=\pgfutil at empty
+  \pgfutil at ifnextchar\relax{%
+    \expandafter\hobbysetparams\expandafter{\hobby at this@opts}%
+  \ifhobby at externalise
+    \ifx\hobby at path@name\pgfutil at empty
+      \hobbygenusepath
+    \else
+      \hobbygenuseifnecpath{\hobby at path@name}%
+    \fi
+  \else
+    \hobbygenusepath
+  \fi
+  \ifx\hobby at path@name\pgfutil at empty
+  \else
+    \hobbysavepath{\hobby at path@name}%
+  \fi
+  \global\let\hobby at path@name=\pgfutil at empty
+  }{%
+    \pgfutil at ifnextchar.{%
+      \hobby at swallowdots}{%
+      \tikz at scan@one at point\hobby at processpt}}}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\hobby at swallowdots}
+% Remove dots from the input stream.
+%    \begin{macrocode}
+\def\hobby at swallowdots.{%
+  \pgfutil at ifnextchar.{%
+    \hobby at swallowdots}{%
+    \tikz at scan@one at point\hobby at processpt}}
+%    \end{macrocode}
+% \end{macro}
+%
+% There is a ``spare hook'' in the TikZ path processing code.
+% If TikZ encounters a path of the form \Verb+(0,0) .. (1,1)+ then it calls a macro \Verb+\tikz at curveto@auto+.
+% However, that macro is not defined in the TikZ code.
+% The following code provides a suitable definition.
+% To play nice, we don't install it by default but define a key (defined above) that installs it.
+%
+% \begin{macro}{\hobby at curveto@override}
+%    \begin{macrocode}
+\def\hobby at curveto@override{%
+  \hobby at curveto@delegate}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\hobby at curveto@auto}
+% When we're called by TikZ, we initialise the path generation code and start adding points.
+% To ensure that the generation code is called, we add a lot of hooks to lots of TikZ commands.
+%    \begin{macrocode}
+\def\hobby at curveto@auto{%
+  \hobbyinit\hobby at tikz@moveto\hobby at tikz@curveto\hobby at tikz@close
+  \expandafter\gdef\expandafter\hobby at collected@onpath\expandafter{\expandafter{\tikz at collected@onpath}   }%
+  \let\tikz at collected@onpath=\pgfutil at empty
+  \pgfmathsetmacro\hobby at x{\the\tikz at lastx/1cm}%
+  \pgfmathsetmacro\hobby at y{\the\tikz at lasty/1cm}%
+  \xdef\hobby at initial@pt{\hobby at x, \hobby at y}%
+  \expandafter\hobbysetparams\expandafter{\hobby at next@opts}%
+  \expandafter\hobbyaddpoint\expandafter{\hobby at point@options,%
+      point={\hobby at x, \hobby at y} }%
+  \hobby at init@tikz at commands
+  \tikzset{designated Hobby path=this}%
+  \let\tikz at scan@point at options=\pgfutil at empty
+  \global\let\hobby at action=\pgfutil at empty
+  \global\let\hobby at this@opts=\pgfutil at empty
+  \global\let\hobby at next@opts=\pgfutil at empty
+  \global\let\hobby at point@options=\pgfutil at empty
+  \tikz at scan@one at point\hobby at addfromtikz%
+}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\hobby at addfromtikz}
+% This adds our current point to the stack.
+%    \begin{macrocode}
+\def\hobby at addfromtikz#1{%
+  #1%
+  \tikz at make@last at position{#1}%
+  \pgfmathsetmacro\hobby at x{\the\pgf at x/1cm}%
+  \pgfmathsetmacro\hobby at y{\the\pgf at y/1cm}%
+  \expandafter\hobbysetparams\expandafter{\hobby at this@opts}%
+  \expandafter\hobbyaddpoint\expandafter{\hobby at point@options,%
+    point={\hobby at x, \hobby at y}}%
+  \hobby at action
+  \global\let\hobby at this@opts=\pgfutil at empty
+  \global\let\hobby at action=\pgfutil at empty
+  \global\let\hobby at point@options=\pgfutil at empty
+  \tikz at scan@next at command%
+}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\hobby at init@tikz at commands}
+%    \begin{macrocode}
+\def\hobby at init@tikz at commands{%
+  \hobby at init@tikz at modcmd\tikz at movetoabs
+  \hobby at init@tikz at modcmd\tikz at movetorel
+  \hobby at init@tikz at modcmd\tikz at lineto
+  \hobby at init@tikz at modcmd\tikz at rect
+  \hobby at init@tikz at modcmd\tikz at cchar
+  \hobby at init@tikz at modcmd\tikz at finish
+  \hobby at init@tikz at modcmd\tikz at arcA
+  \hobby at init@tikz at modcmd\tikz at e@char
+  \hobby at init@tikz at modcmd\tikz at g@char
+  \hobby at init@tikz at modcmd\tikz at schar
+  \hobby at init@tikz at modcmd\tikz at vh@lineto
+  \hobby at init@tikz at modcmd\tikz at pchar
+  \hobby at init@tikz at modcmd\tikz at to
+  \hobby at init@tikz at modcmd\pgf at stop
+  \hobby at init@tikz at modcmd\tikz at decoration
+  \global\let\hobby at curveto@delegate=\hobby at midcurveto@auto
+}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\hobby at restore@tikz at commands}
+%    \begin{macrocode}
+\def\hobby at restore@tikz at commands{%
+  \hobby at restore@tikz at modcmd\tikz at movetoabs
+  \hobby at restore@tikz at modcmd\tikz at movetorel
+  \hobby at restore@tikz at modcmd\tikz at lineto
+  \hobby at restore@tikz at modcmd\tikz at rect
+  \hobby at restore@tikz at modcmd\tikz at cchar
+  \hobby at restore@tikz at modcmd\tikz at finish
+  \hobby at restore@tikz at modcmd\tikz at arcA
+  \hobby at restore@tikz at modcmd\tikz at e@char
+  \hobby at restore@tikz at modcmd\tikz at g@char
+  \hobby at restore@tikz at modcmd\tikz at schar
+  \hobby at restore@tikz at modcmd\tikz at vh@lineto
+  \hobby at restore@tikz at modcmd\tikz at pchar
+  \hobby at restore@tikz at modcmd\tikz at to
+  \hobby at restore@tikz at modcmd\pgf at stop
+  \hobby at restore@tikz at modcmd\tikz at decoration
+  \global\let\hobby at curveto@delegate=\hobby at curveto@auto
+}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\hobby at init@tikz at modcmd}
+%    \begin{macrocode}
+\def\hobby at init@tikz at modcmd#1{%
+    \expandafter\global\expandafter\let\csname hobby at orig@\string#1\endcsname=#1%
+    \gdef#1{\hobby at finish#1}%
+}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\hobby at restore@tikz at modcmd}
+%    \begin{macrocode}
+\def\hobby at restore@tikz at modcmd#1{%
+    \expandafter\global\expandafter\let\expandafter#1\csname hobby at orig@\string#1\endcsname%
+}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\hobby at midcurveto@auto}
+%    \begin{macrocode}
+\def\hobby at midcurveto@auto{%
+  \expandafter\expandafter\expandafter\gdef\expandafter\expandafter\expandafter\hobby at collected@onpath\expandafter\expandafter\expandafter{\expandafter\hobby at collected@onpath\expandafter{\tikz at collected@onpath}   }%
+  \let\tikz at collected@onpath=\pgfutil at empty
+  \let\tikz at scan@point at options=\pgfutil at empty
+  \global\let\hobby at action=\pgfutil at empty
+  \global\let\hobby at this@opts=\pgfutil at empty
+  \global\let\hobby at point@options=\pgfutil at empty
+  \tikz at scan@one at point\hobby at addfromtikz%
+}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\hobby at finish}
+%    \begin{macrocode}
+\def\hobby at finish{%
+  \hobby at restore@tikz at commands
+  \ifhobby at externalise
+    \ifx\hobby at path@name\pgfutil at empty
+      \hobbygenusepath
+    \else
+      \hobbygenuseifnecpath{\hobby at path@name}%
+    \fi
+  \else
+    \hobbygenusepath
+  \fi
+  \ifx\hobby at path@name\pgfutil at empty
+  \else
+    \hobbysavepath{\hobby at path@name}%
+  \fi
+  \global\let\hobby at path@name=\pgfutil at empty
+  \tikzset{designated Hobby path=next}%
+}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{quick curve through}
+% The \Verb+quick curve through+ is a \Verb+to path+ which does the ``quick'' version of Hobby's algorithm.
+% The syntax is as with the \Verb+curve through+: to pass the midpoints as the argument to the style.
+% We need to pass three points to the auxiliary macro.
+% These are passed as \Verb+\hobby at qpoints+, \Verb+\hobby at qpointa+, and the current point.
+% Then these get cycled round for the next triple.
+% The path gets built up and stored as \Verb+\hobby at quick@path+.
+% We also have to remember the angle computed for the next round.
+%    \begin{macrocode}
+\tikzset{
+  quick curve through/.style={%
+    to path={%
+      \pgfextra{%
+%    \end{macrocode}
+% Scan the starting point and store the coordinates in \Verb+\hobby at qpointa+
+%    \begin{macrocode}
+        \let\hobby at next@qbreak=\relax
+        \let\hobby at next@qblank=\relax
+      \tikz at scan@one at point\pgfutil at firstofone(\tikztostart)%
+        \tikz at make@last at position{\pgfqpoint{\the\pgf at x}{\the\pgf at y}}%
+        \edef\hobby at qpoints{\noexpand\pgfqpoint{\the\pgf at x}{\the\pgf at y}}%
+%    \end{macrocode}
+% Blank the path and auxiliary macros.
+%    \begin{macrocode}
+        \def\hobby at qpointa{}%
+        \def\hobby at quick@path{}%
+        \def\hobby at angle{}%
+        \let\hobby at quick@curveto=\hobby at quick@makepath
+%    \end{macrocode}
+% Now start parsing the rest of the coordinates.
+%    \begin{macrocode}
+        \tikz at scan@one at point\hobby at quickfirst #1 (\tikztotarget)\relax
+      }
+%    \end{macrocode}
+% Invoke the path
+%    \begin{macrocode}
+      \hobby at quick@path
+    }
+  },
+  quick hobby/blank curve/.is choice,
+  quick hobby/blank curve/true/.code={%
+    \gdef\hobby at next@qblank{%
+      \qhobby at blanktrue
+      \global\let\hobby at next@qblank=\relax
+    }%
+  },
+  quick hobby/blank curve/false/.code={%
+    \gdef\hobby at next@qblank{%
+      \qhobby at blankfalse
+      \global\let\hobby at next@qblank=\relax
+    }%
+  },
+  quick hobby/blank curve/once/.code={%
+    \gdef\hobby at next@qblank{%
+      \qhobby at blanktrue
+      \gdef\hobby at next@qblank{%
+        \qhobby at blankfalse
+        \global\let\hobby at next@qblank=\relax
+      }%
+    }%
+  },
+  quick hobby/blank curve/.default=true,
+  quick hobby/break curve/.is choice,
+  quick hobby/break curve/true/.code={%
+    \gdef\hobby at next@qbreak{%
+      \qhobby at breaktrue
+      \global\let\hobby at next@qbreak=\relax
+    }%
+  },
+  quick hobby/break curve/false/.code={%
+    \gdef\hobby at next@qbreak{%
+      \qhobby at breakfalse
+      \global\let\hobby at next@qbreak=\relax
+    }%
+  },
+  quick hobby/break curve/once/.code={%
+    \gdef\hobby at next@qbreak{%
+      \qhobby at breaktrue
+      \gdef\hobby at next@qbreak{%
+        \qhobby at breakfalse
+        \global\let\hobby at next@qbreak=\relax
+      }%
+    }%
+  },
+  quick hobby/break curve/.default=true,
+}
+\newif\ifqhobby at break
+\newif\ifqhobby at blank
+%    \end{macrocode}
+% \end{macro}
+%
+% Add plot handlers
+%    \begin{macrocode}
+\tikzoption{hobby}[]{\let\tikz at plot@handler=\pgfplothandlerhobby}
+\tikzoption{quick hobby}[]{\let\tikz at plot@handler=\pgfplothandlerquickhobby}
+\tikzoption{closed hobby}[]{\let\tikz at plot@handler=\pgfplothandlerclosedhobby}
+%    \end{macrocode}
+%
+% \begin{macro}{\hobby at quickfirst}
+% The first time around we just set the next point.
+%    \begin{macrocode}
+\def\hobby at quickfirst#1{%
+  #1%
+  \xdef\hobby at qpointa{\noexpand\pgfqpoint{\the\pgf at x}{\the\pgf at y}}%
+  \tikz at make@last at position{\hobby at qpointa}%
+%    \end{macrocode}
+% Now a check to ensure that we have more points.
+%    \begin{macrocode}
+  \pgfutil at ifnextchar\relax{%
+%    \end{macrocode}
+% Ooops, no more points.
+% That's not good.
+% Bail-out.
+%    \begin{macrocode}
+    \xdef\hobby at quick@path{ -- (\the\pgf at x,\the\pgf at y)}%
+  }{%
+%    \end{macrocode}
+% Okay, have more points.
+% Phew.
+% Call the next round.
+% If we have dots, swallow them.
+%    \begin{macrocode}
+    \pgfutil at ifnextchar.{%
+      \hobby at qswallowdots}{%
+    \tikz at scan@one at point\hobby at quick}}}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\hobby at qswallowdots}
+% Remove dots from the input stream.
+%    \begin{macrocode}
+\def\hobby at qswallowdots.{%
+  \pgfutil at ifnextchar.{%
+    \hobby at qswallowdots}{%
+    \tikz at scan@one at point\hobby at quick}}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\hobby at quick}
+% This is our wrapper function that handles the loop.
+%    \begin{macrocode}
+\def\hobby at quick#1{%
+  \hobby at quick@compute{#1}%
+  \tikz at make@last at position{\hobby at qpointa}%
+  \pgfutil at ifnextchar\relax{%
+%    \end{macrocode}
+% End of loop
+%    \begin{macrocode}
+    \hobby at quick@computeend%
+  }{%
+%    \end{macrocode}
+% More to go, scan in the next coordinate and off we go again.
+%    \begin{macrocode}
+    \pgfutil at ifnextchar.{%
+      \hobby at qswallowdots}{%
+      \tikz at scan@one at point\hobby at quick}}}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\hobby at quick@makepath}
+% Path constructor for \Verb+to path+ use.
+%    \begin{macrocode}
+\def\hobby at quick@makepath#1#2#3{%
+  #1%
+  \pgf at xa=\pgf at x\relax
+  \pgf at ya=\pgf at y\relax
+  #2%
+  \pgf at xb=\pgf at x\relax
+  \pgf at yb=\pgf at y\relax
+  #3%
+  \ifqhobby at blank
+  \xdef\hobby at quick@path{\hobby at quick@path (\the\pgf at x,\the\pgf at y)}%
+  \else
+  \xdef\hobby at quick@path{\hobby at quick@path .. controls%
+  (\the\pgf at xa,\the\pgf at ya) and (\the\pgf at xb,\the\pgf at yb) .. (\the\pgf at x,\the\pgf at y) }%
+  \fi
+  \ifqhobby at break
+  \xdef\hobby at quick@path{\hobby at quick@path +(0,0)}%
+  \fi
+  \hobby at next@qbreak
+  \hobby at next@qblank
+}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\hobby at qcurveto@auto}
+% Uses the ``quick'' method for the shortcut syntax.
+%    \begin{macrocode}
+\def\hobby at qcurveto@auto{%
+  \global\let\hobby at next@qbreak=\relax
+  \global\let\hobby at next@qblank=\relax
+  \xdef\hobby at qpoints{\noexpand\pgfqpoint{\the\tikz at lastx}{\the\tikz at lasty}}%
+  \gdef\hobby at qpointa{}%
+  \gdef\hobby at quick@path{}%
+  \gdef\hobby at angle{}%
+  \global\let\hobby at quick@curveto=\hobby at quick@makepathauto
+  \hobby at qinit@tikz at commands
+  \let\tikz at scan@point at options=\pgfutil at empty
+  \global\let\hobby at action=\pgfutil at empty
+  \global\let\hobby at point@options=\pgfutil at empty
+  \tikz at scan@one at point\hobby at qfirst@auto}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\hobby at qmidcurveto@auto}
+%    \begin{macrocode}
+\def\hobby at qmidcurveto@auto{%
+  \let\tikz at scan@point at options=\pgfutil at empty
+  \global\let\hobby at action=\pgfutil at empty
+  \global\let\hobby at point@options=\pgfutil at empty
+  \tikz at scan@one at point\hobby at qaddfromtikz}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\hobby at qfirst@auto}
+%    \begin{macrocode}
+\def\hobby at qfirst@auto#1{%
+  #1%
+  \xdef\hobby at qpointa{\noexpand\pgfqpoint{\the\pgf at x}{\the\pgf at y}}%
+  \tikz at make@last at position{\hobby at qpointa}%
+  \tikz at scan@next at command%
+}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\hobby at quick@makepathauto}
+% Path constructor for shortcut method to use.
+%    \begin{macrocode}
+\def\hobby at quick@makepathauto#1#2#3{%
+  #1%
+  \pgf at xa=\pgf at x\relax
+  \pgf at ya=\pgf at y\relax
+  #2%
+  \pgf at xb=\pgf at x\relax
+  \pgf at yb=\pgf at y\relax
+  #3%
+  \ifqhobby at blank
+  \edef\hobby at temp{%
+    \noexpand\pgfpathmoveto{\noexpand\pgfqpoint{\the\pgf at x}{\the\pgf at y}}%
+  }%
+  \hobby at temp
+  \else
+  \edef\hobby at temp{%
+    \noexpand\pgfpathcurveto{\noexpand\pgfqpoint{\the\pgf at xa}{\the\pgf at ya}}%
+    {\noexpand\pgfqpoint{\the\pgf at xb}{\the\pgf at yb}}%
+    {\noexpand\pgfqpoint{\the\pgf at x}{\the\pgf at y}}%
+  }%
+  \hobby at temp
+  \fi
+  \ifqhobby at break
+  #3%
+  \edef\hobby at temp{%
+    \noexpand\pgfpathmoveto{\noexpand\pgfqpoint{\the\pgf at x}{\the\pgf at y}}%
+  }%
+  \hobby at temp
+  \fi
+  \hobby at next@qbreak
+  \hobby at next@qblank
+}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\hobby at qaddfromtikz}
+% This adds our current point to the stack.
+%    \begin{macrocode}
+\def\hobby at qaddfromtikz#1{%
+  \hobby at quick@compute{#1}%
+  \tikz at make@last at position{\hobby at qpointa}%
+  \tikz at scan@next at command%
+}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\hobby at qinit@tikz at commands}
+%    \begin{macrocode}
+\def\hobby at qinit@tikz at commands{%
+  \hobby at qinit@tikz at modcmd\tikz at movetoabs
+  \hobby at qinit@tikz at modcmd\tikz at movetorel
+  \hobby at qinit@tikz at modcmd\tikz at lineto
+  \hobby at qinit@tikz at modcmd\tikz at rect
+  \hobby at qinit@tikz at modcmd\tikz at cchar
+  \hobby at qinit@tikz at modcmd\tikz at finish
+  \hobby at qinit@tikz at modcmd\tikz at arcA
+  \hobby at qinit@tikz at modcmd\tikz at e@char
+  \hobby at qinit@tikz at modcmd\tikz at g@char
+  \hobby at qinit@tikz at modcmd\tikz at schar
+  \hobby at qinit@tikz at modcmd\tikz at vh@lineto
+  \hobby at qinit@tikz at modcmd\tikz at pchar
+  \hobby at qinit@tikz at modcmd\tikz at to
+  \hobby at qinit@tikz at modcmd\pgf at stop
+  \hobby at qinit@tikz at modcmd\tikz at decoration
+  \hobby at qinit@tikz at modcmd\tikz@@close
+  \global\let\hobby at curveto@delegate=\hobby at qmidcurveto@auto
+}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\hobby at qrestore@tikz at commands}
+%    \begin{macrocode}
+\def\hobby at qrestore@tikz at commands{%
+  \hobby at restore@tikz at modcmd\tikz at movetoabs
+  \hobby at restore@tikz at modcmd\tikz at movetorel
+  \hobby at restore@tikz at modcmd\tikz at lineto
+  \hobby at restore@tikz at modcmd\tikz at rect
+  \hobby at restore@tikz at modcmd\tikz at cchar
+  \hobby at restore@tikz at modcmd\tikz at finish
+  \hobby at restore@tikz at modcmd\tikz at arcA
+  \hobby at restore@tikz at modcmd\tikz at e@char
+  \hobby at restore@tikz at modcmd\tikz at g@char
+  \hobby at restore@tikz at modcmd\tikz at schar
+  \hobby at restore@tikz at modcmd\tikz at vh@lineto
+  \hobby at restore@tikz at modcmd\tikz at pchar
+  \hobby at restore@tikz at modcmd\tikz at to
+  \hobby at restore@tikz at modcmd\pgf at stop
+  \hobby at restore@tikz at modcmd\tikz at decoration
+  \hobby at restore@tikz at modcmd\tikz@@close
+  \global\let\hobby at curveto@delegate=\hobby at qcurveto@auto
+}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\hobby at qinit@tikz at modcmd}
+%    \begin{macrocode}
+\def\hobby at qinit@tikz at modcmd#1{%
+    \expandafter\global\expandafter\let\csname hobby at orig@\string#1\endcsname=#1%
+    \gdef#1{\hobby at qfinish#1}%
+}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\hobby at qfinish}
+%    \begin{macrocode}
+\def\hobby at qfinish{%
+  \hobby at quick@computeend
+  \hobby at qrestore@tikz at commands
+}
+%    \end{macrocode}
+% \end{macro}
+
+% \iffalse
+%</tikzlibrary>
+% \fi
+%
+% \subsection{Arrays}
+%
+% \iffalse
+%<*array>
+% \fi
+%
+%
+% A lot of our data structures are really arrays.
+% These are implemented as \LaTeX3 ``property lists''.
+% For ease of use, an array is a property list with numeric entries together with entries ``base'' and ``top'' which hold the lowest and highest indices that have been set.
+%
+%    \begin{macrocode}
+\RequirePackage{expl3}
+\ExplSyntaxOn
+%    \end{macrocode}
+% Some auxiliary variables.
+%    \begin{macrocode}
+\tl_new:N \l_array_tmp_tl
+\tl_new:N \l_array_show_tl
+\int_new:N \l_array_base_int
+\int_new:N \l_array_top_int
+\int_new:N \l_array_tmp_int
+\int_new:N \g_array_map_int
+%    \end{macrocode}
+% The global variable \Verb+\g_array_base_int+ says what index a blank array should start with when pushed or unshifted.
+%    \begin{macrocode}
+\int_new:N \g_array_base_int
+\int_gset:Nn \g_array_base_int {0}
+%    \end{macrocode}
+% \begin{macro}{\array_adjust_ends:Nn}
+% This ensures that the ``base'' and ``top'' are big enough to include the given index.
+%    \begin{macrocode}
+\cs_new:Npn \array_adjust_ends:Nn #1#2 {
+  \prop_get:NnNTF #1 {base} \l_tmpa_tl
+  {
+    \int_compare:nNnTF {\l_tmpa_tl} > {#2}
+    {
+      \prop_put:Nnx #1 {base} {\int_eval:n {#2}}
+    }
+    {}
+  }
+  {
+    \prop_put:Nnx #1 {base} {\int_eval:n {#2}}
+  }
+  \prop_get:NnNTF #1 {top} \l_tmpa_tl
+  {
+    \int_compare:nNnTF {\l_tmpa_tl} < {#2}
+    {
+      \prop_put:Nnx #1 {top} {\int_eval:n {#2}}
+    }
+    {}
+  }
+  {
+    \prop_put:Nnx #1 {top} {\int_eval:n {#2}}
+  }
+}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\array_gadjust_ends:Nn}
+% This ensures that the ``base'' and ``top'' are big enough to include the given index.
+% (Global version)
+%    \begin{macrocode}
+\cs_new:Npn \array_gadjust_ends:Nn #1#2 {
+  \prop_get:NnNTF #1 {base} \l_tmpa_tl
+  {
+    \int_compare:nNnTF {\l_tmpa_tl} > {#2}
+    {
+      \prop_gput:Nnx #1 {base} {\int_eval:n {#2}}
+    }
+    {}
+  }
+  {
+    \prop_gput:Nnx #1 {base} {\int_eval:n {#2}}
+  }
+  \prop_get:NnNTF #1 {top} \l_tmpa_tl
+  {
+    \int_compare:nNnTF {\l_tmpa_tl} < {#2}
+    {
+      \prop_gput:Nnx #1 {top} {\int_eval:n {#2}}
+    }
+    {}
+  }
+  {
+    \prop_gput:Nnx #1 {top} {\int_eval:n {#2}}
+  }
+}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\array_put:Nnn}
+% When adding a value to an array we have to adjust the ends.
+%    \begin{macrocode}
+\cs_new:Npn \array_put:Nnn #1#2#3 {
+  \exp_args:NNx \prop_put:Nnn #1 {\int_eval:n {#2}} {#3}
+  \array_adjust_ends:Nn #1{#2}
+}
+\cs_generate_variant:Nn \array_put:Nnn {Nnx}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\array_gput:Nnn}
+% When adding a value to an array we have to adjust the ends.
+% (Global version)
+%    \begin{macrocode}
+\cs_new:Npn \array_gput:Nnn #1#2#3 {
+  \exp_args:NNx \prop_gput:Nnn #1 {\int_eval:n {#2}} {#3}
+  \array_gadjust_ends:Nn #1{#2}
+}
+\cs_generate_variant:Nn \array_gput:Nnn {Nnx}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\array_get:NnN}
+%    \begin{macrocode}
+\cs_new:Npn \array_get:NnN #1#2#3 {
+  \exp_args:NNx \prop_get:NnN #1 {\int_eval:n {#2}} #3
+}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[EXP]{\array_get:Nn}
+%    \begin{macrocode}
+\cs_new:Npn \array_get:Nn #1#2 {
+  \exp_args:NNf \prop_item:Nn #1 { \int_eval:n {#2} }
+}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\array_get:NnNTF}
+%    \begin{macrocode}
+\cs_new:Npn \array_get:NnNTF #1#2#3#4#5 {
+  \exp_args:NNx \prop_get:NnNTF #1 {\int_eval:n {#2}} #3 {#4}{#5}
+}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\array_if_empty:NTF}
+%    \begin{macrocode}
+\prg_new_conditional:Npnn \array_if_empty:N #1 { p, T, F, TF }
+{
+  \if_meaning:w #1 \c_empty_prop
+    \prg_return_true:
+  \else:
+    \prg_return_false:
+  \fi:
+}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\array_if_exist:NTF}
+%    \begin{macrocode}
+\prg_new_eq_conditional:NNn \array_if_exist:N \cs_if_exist:N { p, T, F, TF }
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\array_new:N}
+%    \begin{macrocode}
+\cs_new_eq:NN \array_new:N \prop_new:N
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\array_clear:N}
+%    \begin{macrocode}
+\cs_new_eq:NN \array_clear:N \prop_clear:N
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\array_gclear:N}
+%    \begin{macrocode}
+\cs_new_eq:NN \array_gclear:N \prop_gclear:N
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\array_map_function}
+% When stepping through an array, we want to iterate in order so a simple wrapper to \Verb+\prop_map_function+ is not enough.
+% This maps through every value from the base to the top so the function should be prepared to deal with a \Verb+\q_no_value+.
+%    \begin{macrocode}
+\cs_new:Npn \array_map_function:NN #1#2
+{
+  \array_if_empty:NTF #1 {} {
+    \prop_get:NnNTF #1 {base} \l_array_tmp_tl {
+      \int_set:Nn \l_array_base_int {\l_array_tmp_tl}
+    }{
+      \int_set:Nn \l_array_base_int {0}
+    }
+    \prop_get:NnNTF #1 {top} \l_array_tmp_tl {
+      \int_set:Nn \l_array_top_int {\l_array_tmp_tl}
+    }{
+      \int_set:Nn \l_array_top_int {0}
+    }
+    \int_step_inline:nnnn {\l_array_base_int} {1} {\l_array_top_int} {
+  \array_get:NnN #1 {##1} \l_array_tmp_tl
+  \exp_args:NnV #2 {##1} \l_array_tmp_tl
+}
+} {}
+}
+\cs_generate_variant:Nn \array_map_function:NN {     Nc }
+\cs_generate_variant:Nn \array_map_function:NN { c , cc }
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\array_reverse_map_function}
+% This steps through the array in reverse order.
+%    \begin{macrocode}
+\cs_new:Npn \array_reverse_map_function:NN #1#2
+{
+  \array_if_empty:NTF #1 {} {
+    \prop_get:NnNTF #1 {base} \l_array_tmp_tl {
+      \int_set:Nn \l_array_base_int {\l_array_tmp_tl}
+    }{
+      \int_set:Nn \l_array_base_int {0}
+    }
+    \prop_get:NnNTF #1 {top} \l_array_tmp_tl {
+      \int_set:Nn \l_array_top_int {\l_array_tmp_tl}
+    }{
+      \int_set:Nn \l_array_top_int {0}
+    }
+    \int_step_inline:nnnn {\l_array_top_int} {-1} {\l_array_base_int} {
+  \array_get:NnN #1 {##1} \l_array_tmp_tl
+  \exp_args:Nno #2 {##1} \l_array_tmp_tl
+}
+} {}
+}
+\cs_generate_variant:Nn \array_reverse_map_function:NN {     Nc }
+\cs_generate_variant:Nn \array_reverse_map_function:NN { c , cc }
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\array_map_inline:Nn}
+% Inline version of the above.
+%    \begin{macrocode}
+\cs_new_protected:Npn \array_map_inline:Nn #1#2
+  {
+    \int_gincr:N \g_array_map_int
+    \cs_gset:cpn { array_map_inline_ \int_use:N \g_array_map_int :nn }
+      ##1##2 {#2}
+    \exp_args:NNc \array_map_function:NN #1
+      { array_map_inline_ \int_use:N \g_array_map_int :nn }
+    \prg_break_point:Nn \array_map_break: { \int_gdecr:N \g_array_map_int }
+  }
+\cs_generate_variant:Nn \array_map_inline:Nn { c }
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\array_reverse_map_inline:Nn}
+% Inline version of the above.
+%    \begin{macrocode}
+\cs_new_protected:Npn \array_reverse_map_inline:Nn #1#2
+  {
+    \int_gincr:N \g_array_map_int
+    \cs_gset:cpn { array_map_inline_ \int_use:N \g_array_map_int :nn }
+      ##1##2 {#2}
+    \exp_args:NNc \array_reverse_map_function:NN #1
+      { array_map_inline_ \int_use:N \g_array_map_int :nn }
+    \prg_break_point:Nn \array_map_break: { \int_gdecr:N \g_array_map_int }
+  }
+\cs_generate_variant:Nn \array_reverse_map_inline:Nn { c }
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\array_map_break:}
+%    \begin{macrocode}
+\cs_new_nopar:Npn \array_map_break:
+  { \prg_map_break:Nn \array_map_break: { } }
+\cs_new_nopar:Npn \array_map_break:n
+  { \prg_map_break:Nn \array_map_break: }
+%    \end{macrocode}
+% \end{macro}
+%
+% For displaying arrays, we need some messages.
+%    \begin{macrocode}
+\msg_new:nnn { kernel } { show-array }
+  {
+    The~array~\token_to_str:N #1~
+    \array_if_empty:NTF #1
+      { is~empty }
+      { contains~the~items~(without~outer~braces): }
+  }
+%    \end{macrocode}
+%
+% \begin{macro}{\array_show:N}
+% Mapping through an array isn't expandable so we have to set a token list to its contents first before passing it to the message handler.
+%    \begin{macrocode}
+\cs_new_protected:Npn \array_show:N #1
+  {
+    \__msg_show_variable:NNNnn
+    #1
+    \array_if_exist:NTF
+    \array_if_empty:NTF
+      { array }
+    { \array_map_function:NN #1 \__msg_show_item:nn }
+  }
+\cs_generate_variant:Nn \array_show:N { c }
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\array_push:Nn}
+%    \begin{macrocode}
+\cs_new_protected:Npn \array_push:Nn #1#2
+{
+  \prop_get:NnNTF #1 {top} \l_array_tmp_tl
+  {
+    \int_set:Nn \l_array_tmp_int {\l_array_tmp_tl}
+    \int_incr:N \l_array_tmp_int
+    \array_put:Nnn #1 {\l_array_tmp_int} {#2}
+  }
+  {
+    \array_put:Nnn #1 {\g_array_base_int} {#2}
+  }
+}
+\cs_generate_variant:Nn \array_push:Nn {Nx}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\array_gpush:Nn}
+%    \begin{macrocode}
+\cs_new_protected:Npn \array_gpush:Nn #1#2
+{
+  \prop_get:NnNTF #1 {top} \l_array_tmp_tl
+  {
+    \int_set:Nn \l_array_tmp_int {\l_array_tmp_tl}
+    \int_incr:N \l_array_tmp_int
+    \array_gput:Nnn #1 {\l_array_tmp_int} {#2}
+  }
+  {
+    \array_gput:Nnn #1 {\g_array_base_int} {#2}
+  }
+}
+\cs_generate_variant:Nn \array_gpush:Nn {Nx}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\array_unshift:Nn}
+%    \begin{macrocode}
+\cs_new_protected:Npn \array_unshift:Nn #1#2
+{
+  \prop_get:NnNTF #1 {base} \l_array_tmp_tl
+  {
+    \int_set:Nn \l_array_tmp_int {\l_array_tmp_tl}
+    \int_decr:N \l_array_tmp_int
+    \array_put:Nnn #1 {\l_array_tmp_int} {#2}
+  }
+  {
+    \array_put:Nnn #1 {\g_array_base_int} {#2}
+  }
+}
+\cs_generate_variant:Nn \array_unshift:Nn {Nx}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\array_gunshift:Nn}
+%    \begin{macrocode}
+\cs_new_protected:Npn \array_gunshift:Nn #1#2
+{
+  \prop_get:NnNTF #1 {base} \l_array_tmp_tl
+  {
+    \int_set:Nn \l_array_tmp_int {\l_array_tmp_tl}
+    \int_decr:N \l_array_tmp_int
+    \array_gput:Nnn #1 {\l_array_tmp_int} {#2}
+  }
+  {
+    \array_gput:Nnn #1 {\g_array_base_int} {#2}
+  }
+}
+\cs_generate_variant:Nn \array_gunshift:Nn {Nx}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\array_pop:NN}
+%    \begin{macrocode}
+\cs_new_protected:Npn \array_pop:NN #1#2
+{
+  \prop_get:NnN #1 {top} \l_array_tmp_tl
+  \array_get:NnN #1 {\l_array_tmp_tl} #2
+  \array_del:Nn #1 {\l_array_tmp_tl}
+}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\array_gpop:NN}
+%    \begin{macrocode}
+\cs_new_protected:Npn \array_gpop:NN #1#2
+{
+  \prop_get:NnN #1 {top} \l_array_tmp_tl
+  \array_get:NnN #1 {\l_array_tmp_tl} #2
+  \array_gdel:Nn #1 {\l_array_tmp_tl}
+}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\array_shift:NN}
+%    \begin{macrocode}
+\cs_new_protected:Npn \array_shift:NN #1#2
+{
+  \prop_get:NnN #1 {base} \l_array_tmp_tl
+  \array_get:NnN #1 {\l_array_tmp_tl} #2
+  \array_del:Nn #1 {\l_array_tmp_tl}
+}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\array_gshift:NN}
+%    \begin{macrocode}
+\cs_new_protected:Npn \array_gshift:NN #1#2
+{
+  \prop_get:NnN #1 {base} \l_array_tmp_tl
+  \array_get:NnN #1 {\l_array_tmp_tl} #2
+  \array_gdel:Nn #1 {\l_array_tmp_tl}
+}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\array_top:NN}
+%    \begin{macrocode}
+\cs_new_protected:Npn \array_top:NN #1#2
+{
+  \prop_get:NnN #1 {top} \l_array_tmp_tl
+  \array_get:NnN #1 {\l_array_tmp_tl} #2
+}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\array_base:NN}
+%    \begin{macrocode}
+\cs_new_protected:Npn \array_base:NN #1#2
+{
+  \prop_get:NnN #1 {base} \l_array_tmp_tl
+  \array_get:NnN #1 {\l_array_tmp_tl} #2
+}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\array_top:N}
+%    \begin{macrocode}
+\cs_new:Npn \array_top:N #1
+{
+  \array_get:Nn #1 {\prop_item:Nn #1 {top}}
+}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\array_base:N}
+%    \begin{macrocode}
+\cs_new:Npn \array_base:N #1
+{
+  \array_get:Nn #1 {\prop_item:Nn #1 {base}}
+}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\array_del:Nn}
+%    \begin{macrocode}
+\cs_new_protected:Npn \array_del:Nn #1#2
+{
+  \exp_args:NNx \prop_pop:Nn #1 {\int_eval:n {#2}}
+  \int_set:Nn \l_array_tmp_int {0}
+  \array_map_inline:Nn #1 {
+    \tl_if_eq:NNTF {##2} {\q_no_value} {}
+    {
+      \int_incr:N \l_array_tmp_int
+    }
+  }
+  \int_compare:nNnTF {\l_array_tmp_int} = {0}
+  {
+    \prop_clear:N #1
+  }
+  {
+  \prop_get:NnN #1 {top} \l_array_tmp_tl
+  \int_compare:nNnTF {#2} = {\l_array_tmp_tl} {
+    \prop_get:NnN #1 {base} \l_array_tmp_tl
+    \int_set:Nn \l_array_tmp_int {\l_array_tmp_tl}
+    \array_map_inline:Nn #1 {
+    \tl_if_eq:NNTF {##2} {\q_no_value} {}
+    {
+      \int_compare:nNnTF {\l_array_tmp_int} < {##1} {
+        \int_set:Nn \l_array_tmp_int {##1}
+      }{}
+    }
+      }
+    \prop_put:Nnx #1 {top} {\int_use:N \l_array_tmp_int}
+  }{}
+  \prop_get:NnN #1 {base} \l_array_tmp_tl
+  \int_compare:nNnTF {#2} = {\l_array_tmp_tl} {
+    \prop_get:NnN #1 {top} \l_array_tmp_tl
+    \int_set:Nn \l_array_tmp_int {\l_array_tmp_tl}
+    \array_map_inline:Nn #1 {
+    \tl_if_eq:NNTF {##2} {\q_no_value} {}
+    {
+      \int_compare:nNnTF {\l_array_tmp_int} > {##1} {
+        \int_set:Nn \l_array_tmp_int {##1}
+      }{}
+    }
+      }
+    \prop_put:Nnx #1 {base} {\int_use:N \l_array_tmp_int}
+  }{}
+  }
+}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\array_gdel:Nn}
+%    \begin{macrocode}
+\cs_new_protected:Npn \array_gdel:Nn #1#2
+{
+  \exp_args:NNx \prop_gremove:Nn #1 {\int_eval:n {#2}}
+  \int_set:Nn \l_array_tmp_int {0}
+  \array_map_inline:Nn #1 {
+    \tl_if_eq:NNTF {##2} {\q_no_value} {}
+    {
+      \int_incr:N \l_array_tmp_int
+    }
+  }
+  \int_compare:nNnTF {\l_array_tmp_int} = {0}
+  {
+    \prop_gclear:N #1
+  }
+  {
+  \prop_get:NnN #1 {top} \l_array_tmp_tl
+  \int_compare:nNnTF {#2} = {\l_array_tmp_tl} {
+    \prop_get:NnN #1 {base} \l_array_tmp_tl
+    \int_set:Nn \l_array_tmp_int {\l_array_tmp_tl}
+    \array_map_inline:Nn #1 {
+    \tl_if_eq:NNTF {##2} {\q_no_value} {}
+    {
+      \int_compare:nNnTF {\l_array_tmp_int} < {##1} {
+        \int_set:Nn \l_array_tmp_int {##1}
+      }{}
+    }
+      }
+    \prop_gput:Nnx #1 {top} {\int_use:N \l_array_tmp_int}
+  }{}
+  \prop_get:NnN #1 {base} \l_array_tmp_tl
+  \int_compare:nNnTF {#2} = {\l_array_tmp_tl} {
+    \prop_get:NnN #1 {top} \l_array_tmp_tl
+    \int_set:Nn \l_array_tmp_int {\l_array_tmp_tl}
+    \array_map_inline:Nn #1 {
+    \tl_if_eq:NNTF {##2} {\q_no_value} {}
+    {
+      \int_compare:nNnTF {\l_array_tmp_int} > {##1} {
+        \int_set:Nn \l_array_tmp_int {##1}
+      }{}
+    }
+      }
+    \prop_gput:Nnx #1 {base} {\int_use:N \l_array_tmp_int}
+  }{}
+  }
+}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\array_length:N}
+%    \begin{macrocode}
+\cs_new_protected:Npn \array_length:N #1
+{
+  \int_eval:n {\prop_item:Nn #1 {top} - \prop_item:Nn #1 {base}}
+}
+%    \end{macrocode}
+% \end{macro}
+%    \begin{macrocode}
+\ExplSyntaxOff
+%    \end{macrocode}
+%
+% \iffalse
+%</array>
+% \fi
+%
+% \iffalse
+%<*l3hobby>
+% \fi
+%
+%    \begin{macrocode}
+\RequirePackage{expl3}
+%    \end{macrocode}
+%
+% Load the hobby core
+%    \begin{macrocode}
+\input{hobby.code.tex}
+%    \end{macrocode}
+%
+% Register as an expl3 package
+%    \begin{macrocode}
+\ProvidesExplPackage {hobby-l3draw} {2018/02/20} {1.0} {Interface for l3draw and hobby}
+%    \end{macrocode}
+%
+% Load the l3draw package
+%    \begin{macrocode}
+\RequirePackage{l3draw}
+%    \end{macrocode}
+%
+%
+% \begin{macro}{\hobby_draw_moveto:nnn}
+% This provides our interface between hobby's moveto and l3draw's moveto
+%    \begin{macrocode}
+\cs_new_protected:Npn \hobby_draw_moveto:nnn #1#2#3
+{
+  \draw_path_canvas_moveto:n {#3}
+}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\hobby_draw_curveto:nnn}
+% This provides our interface between hobby's curveto and l3draw's curveto
+%    \begin{macrocode}
+\cs_set_eq:NN \hobby_draw_curveto:nnn \draw_path_canvas_curveto:nnn
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\hobby_draw_close:n}
+% This provides our interface between hobby's close and l3draw's close
+%    \begin{macrocode}
+\cs_new_protected:Npn \hobby_draw_close:n #1
+{
+  \draw_path_close:
+}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\hobby_draw_addpoint:n}
+% This processes a point and passes it one more step towards the hobby algorithm
+%    \begin{macrocode}
+\cs_new_protected:Npn \hobby_draw_addpoint:n #1
+{
+  \__draw_point_process:nn
+  { \__hobby_draw_addpoint:nn }
+  { \draw_point_transform:n {#1} }
+}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\__hobby_draw_curveto:nn}
+% This provides our interface between l3draw's points and hobby's syntax
+%    \begin{macrocode}
+\cs_new_protected:Npn \__hobby_draw_addpoint:nn #1#2
+{
+  \hobby_add_point:nnnnnn {#1} {#2} {1} {1} {0} {10}
+}
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\hobby_draw_init:}
+% This initialises hobby's algorithm with the l3draw commands
+%    \begin{macrocode}
+\cs_new_protected:Npn \hobby_draw_init:
+{
+  \hobby_set_cmds:NNN \hobby_draw_moveto:nnn \hobby_draw_curveto:nnn \hobby_draw_close:n
+  \hobby_clear_path:
+}
+%    \end{macrocode}
+% \end{macro}
+%
+% \iffalse
+%</l3hobby>
+% \fi
+%\Finale


Property changes on: trunk/Master/texmf-dist/source/latex/hobby/hobby_code.dtx
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: trunk/Master/texmf-dist/source/latex/hobby/hobby_code.ins
===================================================================
--- trunk/Master/texmf-dist/source/latex/hobby/hobby_code.ins	                        (rev 0)
+++ trunk/Master/texmf-dist/source/latex/hobby/hobby_code.ins	2023-09-01 21:15:20 UTC (rev 68137)
@@ -0,0 +1,95 @@
+%%
+%% This is file `hobby_code.ins',
+%% generated with the docstrip utility.
+%%
+%% The original source files were:
+%%
+%% hobby_code.dtx  (with options: `install')
+%% ----------------------------------------------------------------
+%% hobby --- a TikZ/PGF library for drawing smooth(ish) curves using
+%%           Hobby's algorithm (implemented in LaTeX3)
+%% E-mail: loopspace at mathforge.org
+%% Released under the LaTeX Project Public License v1.3c or later
+%% See http://www.latex-project.org/lppl.txt
+%% ----------------------------------------------------------------
+%% 
+\input docstrip.tex
+\keepsilent
+\askforoverwritefalse
+\preamble
+----------------------------------------------------------------
+hobby --- a TikZ/PGF library for drawing smooth(ish) curves using
+          Hobby's algorithm (implemented in LaTeX3)
+E-mail: loopspace at mathforge.org
+Released under the LaTeX Project Public License v1.3c or later
+See http://www.latex-project.org/lppl.txt
+----------------------------------------------------------------
+
+\endpreamble
+\postamble
+
+Copyright (C) 2012-2021 by Andrew Stacey <loopspace at mathforge.org>
+
+This file may be distributed and/or modified under the conditions
+of the LaTeX Project Public License, either version 1.3 of this
+license or (at your option) any later version.
+The latest version of this license is in:
+
+   http://www.latex-project.org/lppl.txt
+
+and version 1.3 or later is part of all distributions of LaTeX
+version 2005/12/01 or later.
+
+This work is "maintained" (as per LPPL maintenance status) by
+Andrew Stacey.
+
+This work consists of the files  hobby_code.dtx
+                                 hobby.tex
+and the derived files            hobby.code.tex
+                                 pgflibraryhobby.code.tex
+                                 tikzlibraryhobby.code.tex
+                                 pml3array.sty
+                                 hobby-l3draw.sty
+                                 hobby.ins
+                                 hobby.pdf
+                                 hobby_code.pdf
+                                 README.txt
+
+\endpostamble
+\usedir{tex/latex/hobby}
+\generate{\file{tikzlibraryhobby.code.tex} {\from{hobby_code.dtx}{tikzlibrary}}}
+\generate{\file{pgflibraryhobby.code.tex} {\from{hobby_code.dtx}{pgflibrary}}}
+\generate{\file{hobby.code.tex} {\from{hobby_code.dtx}{hobby}}}
+\generate{\file{pml3array.sty} {\from{hobby_code.dtx}{array}}}
+\generate{\file{hobby-l3draw.sty} {\from{hobby_code.dtx}{l3hobby}}}
+\endbatchfile
+%% 
+%% Copyright (C) 2012-2021 by Andrew Stacey <loopspace at mathforge.org>
+%% 
+%% This file may be distributed and/or modified under the conditions
+%% of the LaTeX Project Public License, either version 1.3 of this
+%% license or (at your option) any later version.
+%% The latest version of this license is in:
+%% 
+%%    http://www.latex-project.org/lppl.txt
+%% 
+%% and version 1.3 or later is part of all distributions of LaTeX
+%% version 2005/12/01 or later.
+%% 
+%% This work is "maintained" (as per LPPL maintenance status) by
+%% Andrew Stacey.
+%% 
+%% This work consists of the files  hobby_code.dtx
+%%                                  hobby.tex
+%% and the derived files            hobby.code.tex
+%%                                  pgflibraryhobby.code.tex
+%%                                  tikzlibraryhobby.code.tex
+%%                                  pml3array.sty
+%%                                  hobby-l3draw.sty
+%%                                  hobby.ins
+%%                                  hobby.pdf
+%%                                  hobby_code.pdf
+%%                                  README.txt
+%% 
+%%
+%% End of file `hobby_code.ins'.

Added: trunk/Master/texmf-dist/tex/latex/hobby/hobby-l3draw.sty
===================================================================
--- trunk/Master/texmf-dist/tex/latex/hobby/hobby-l3draw.sty	                        (rev 0)
+++ trunk/Master/texmf-dist/tex/latex/hobby/hobby-l3draw.sty	2023-09-01 21:15:20 UTC (rev 68137)
@@ -0,0 +1,73 @@
+%%
+%% This is file `hobby-l3draw.sty',
+%% generated with the docstrip utility.
+%%
+%% The original source files were:
+%%
+%% hobby_code.dtx  (with options: `l3hobby')
+%% ----------------------------------------------------------------
+%% hobby --- a TikZ/PGF library for drawing smooth(ish) curves using
+%%           Hobby's algorithm (implemented in LaTeX3)
+%% E-mail: loopspace at mathforge.org
+%% Released under the LaTeX Project Public License v1.3c or later
+%% See http://www.latex-project.org/lppl.txt
+%% ----------------------------------------------------------------
+%% 
+\RequirePackage{expl3}
+\input{hobby.code.tex}
+\ProvidesExplPackage {hobby-l3draw} {2018/02/20} {1.0} {Interface for l3draw and hobby}
+\RequirePackage{l3draw}
+\cs_new_protected:Npn \hobby_draw_moveto:nnn #1#2#3
+{
+  \draw_path_canvas_moveto:n {#3}
+}
+\cs_set_eq:NN \hobby_draw_curveto:nnn \draw_path_canvas_curveto:nnn
+\cs_new_protected:Npn \hobby_draw_close:n #1
+{
+  \draw_path_close:
+}
+\cs_new_protected:Npn \hobby_draw_addpoint:n #1
+{
+  \__draw_point_process:nn
+  { \__hobby_draw_addpoint:nn }
+  { \draw_point_transform:n {#1} }
+}
+\cs_new_protected:Npn \__hobby_draw_addpoint:nn #1#2
+{
+  \hobby_add_point:nnnnnn {#1} {#2} {1} {1} {0} {10}
+}
+\cs_new_protected:Npn \hobby_draw_init:
+{
+  \hobby_set_cmds:NNN \hobby_draw_moveto:nnn \hobby_draw_curveto:nnn \hobby_draw_close:n
+  \hobby_clear_path:
+}
+%% 
+%% Copyright (C) 2012-2021 by Andrew Stacey <loopspace at mathforge.org>
+%% 
+%% This file may be distributed and/or modified under the conditions
+%% of the LaTeX Project Public License, either version 1.3 of this
+%% license or (at your option) any later version.
+%% The latest version of this license is in:
+%% 
+%%    http://www.latex-project.org/lppl.txt
+%% 
+%% and version 1.3 or later is part of all distributions of LaTeX
+%% version 2005/12/01 or later.
+%% 
+%% This work is "maintained" (as per LPPL maintenance status) by
+%% Andrew Stacey.
+%% 
+%% This work consists of the files  hobby_code.dtx
+%%                                  hobby.tex
+%% and the derived files            hobby.code.tex
+%%                                  pgflibraryhobby.code.tex
+%%                                  tikzlibraryhobby.code.tex
+%%                                  pml3array.sty
+%%                                  hobby-l3draw.sty
+%%                                  hobby.ins
+%%                                  hobby.pdf
+%%                                  hobby_code.pdf
+%%                                  README.txt
+%% 
+%%
+%% End of file `hobby-l3draw.sty'.


Property changes on: trunk/Master/texmf-dist/tex/latex/hobby/hobby-l3draw.sty
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Modified: trunk/Master/texmf-dist/tex/latex/hobby/hobby.code.tex
===================================================================
--- trunk/Master/texmf-dist/tex/latex/hobby/hobby.code.tex	2023-09-01 14:59:58 UTC (rev 68136)
+++ trunk/Master/texmf-dist/tex/latex/hobby/hobby.code.tex	2023-09-01 21:15:20 UTC (rev 68137)
@@ -4,7 +4,7 @@
 %%
 %% The original source files were:
 %%
-%% hobby.dtx  (with options: `hobby')
+%% hobby_code.dtx  (with options: `hobby')
 %% ----------------------------------------------------------------
 %% hobby --- a TikZ/PGF library for drawing smooth(ish) curves using
 %%           Hobby's algorithm (implemented in LaTeX3)
@@ -13,8 +13,6 @@
 %% See http://www.latex-project.org/lppl.txt
 %% ----------------------------------------------------------------
 %% 
-\RequirePackage{expl3}
-\RequirePackage{xparse}
 \RequirePackage{pml3array}
 \ExplSyntaxOn
 \cs_generate_variant:Nn \fp_set:Nn {Nx}
@@ -33,8 +31,8 @@
 
 \tl_new:N \g__hobby_version
 \tl_new:N \g__hobby_date
-\tl_set:Nn \g__hobby_version {1.8}
-\tl_set:Nn \g__hobby_date {2017-06-01}
+\tl_gset:Nn \g__hobby_version {1.12}
+\tl_gset:Nn \g__hobby_date {2023-09-01}
 \DeclareDocumentCommand \hobbyVersion {}
 {
   \tl_use:N \g__hobby_version
@@ -57,46 +55,50 @@
 {
   \bool_set_false:N \l_hobby_save_aux_bool
 }
-\array_new:N \l_hobby_points_array
-\array_new:N \l_hobby_points_x_array
-\array_new:N \l_hobby_points_y_array
-\array_new:N \l_hobby_actions_array
-\array_new:N \l_hobby_angles_array
-\array_new:N \l_hobby_distances_array
-\array_new:N \l_hobby_tension_out_array
-\array_new:N \l_hobby_tension_in_array
-\array_new:N \l_hobby_matrix_a_array
-\array_new:N \l_hobby_matrix_b_array
-\array_new:N \l_hobby_matrix_c_array
-\array_new:N \l_hobby_matrix_d_array
-\array_new:N \l_hobby_vector_u_array
-\array_new:N \l_hobby_excess_angle_array
-\array_new:N \l_hobby_psi_array
-\array_new:N \l_hobby_theta_array
-\array_new:N \l_hobby_phi_array
-\array_new:N \l_hobby_sigma_array
-\array_new:N \l_hobby_rho_array
-\array_new:N \l_hobby_controla_array
-\array_new:N \l_hobby_controlb_array
+\array_new:N \g__hobby_points_array
+\array_new:N \g__hobby_points_x_array
+\array_new:N \g__hobby_points_y_array
+\array_new:N \g__hobby_actions_array
+\array_new:N \g__hobby_angles_array
+\array_new:N \g__hobby_distances_array
+\array_new:N \g__hobby_tension_out_array
+\array_new:N \g__hobby_tension_in_array
+\array_new:N \g__hobby_matrix_a_array
+\array_new:N \g__hobby_matrix_b_array
+\array_new:N \g__hobby_matrix_c_array
+\array_new:N \g__hobby_matrix_d_array
+\array_new:N \g__hobby_vector_u_array
+\array_new:N \g__hobby_excess_angle_array
+\array_new:N \g__hobby_psi_array
+\array_new:N \g__hobby_theta_array
+\array_new:N \g__hobby_phi_array
+\array_new:N \g__hobby_sigma_array
+\array_new:N \g__hobby_rho_array
+\array_new:N \g__hobby_controla_array
+\array_new:N \g__hobby_controlb_array
 \fp_new:N \l_hobby_matrix_v_fp
+\fp_new:N \l_hobby_tempa_tl
+\fp_new:N \l_hobby_tempb_tl
 \fp_new:N \l_hobby_tempa_fp
 \fp_new:N \l_hobby_tempb_fp
 \fp_new:N \l_hobby_tempc_fp
 \fp_new:N \l_hobby_tempd_fp
 \fp_new:N \l_hobby_temps_fp
-\fp_new:N \l_hobby_in_curl_fp
-\fp_set:Nn \l_hobby_in_curl_fp {1}
-\fp_new:N \l_hobby_out_curl_fp
-\fp_set:Nn \l_hobby_out_curl_fp {1}
-\fp_new:N \l_hobby_in_angle_fp
-\fp_set_eq:NN \l_hobby_in_angle_fp \c_inf_fp
-\fp_new:N \l_hobby_out_angle_fp
-\fp_set_eq:NN \l_hobby_out_angle_fp \c_inf_fp
-\int_new:N \l_hobby_npoints_int
-\int_new:N \l_hobby_draw_int
+\fp_new:N \g__hobby_in_curl_fp
+\fp_gset:Nn \g__hobby_in_curl_fp {1}
+\fp_new:N \g__hobby_out_curl_fp
+\fp_gset:Nn \g__hobby_out_curl_fp {1}
+\fp_new:N \g__hobby_in_angle_fp
+\fp_gset_eq:NN \g__hobby_in_angle_fp \c_inf_fp
+\fp_new:N \g__hobby_out_angle_fp
+\fp_gset_eq:NN \g__hobby_out_angle_fp \c_inf_fp
+\int_new:N \g__hobby_npoints_int
+\int_new:N \g__hobby_draw_int
 \keys_define:nn {hobby / read in all} {
-  x .fp_set:N = \l_hobby_tempa_fp,
-  y .fp_set:N = \l_hobby_tempb_fp,
+  point .code:n = {
+    \fp_set:Nn \l_hobby_tempa_fp {\clist_item:nn {#1} {1}}
+    \fp_set:Nn \l_hobby_tempb_fp {\clist_item:nn {#1} {2}}
+  },
   tension~out .fp_set:N = \l_hobby_tempc_fp,
   tension~in .fp_set:N = \l_hobby_tempd_fp,
   excess~angle .fp_set:N = \l_hobby_temps_fp,
@@ -107,22 +109,22 @@
   blank .default:n = false,
   invert~soft~blanks .choice:,
   invert~soft~blanks / true .code:n = {
-    \int_gset:Nn \l_hobby_draw_int {0}
+    \int_gset:Nn \g__hobby_draw_int {0}
   },
   invert~soft~blanks / false .code:n = {
-    \int_gset:Nn \l_hobby_draw_int {1}
+    \int_gset:Nn \g__hobby_draw_int {1}
   },
   invert~soft~blanks .default:n = true,
   tension~out .default:n = 1,
   tension~in .default:n = 1,
   excess~angle .default:n = 0,
-  in~angle .fp_gset:N = \l_hobby_in_angle_fp,
-  out~angle .fp_gset:N = \l_hobby_out_angle_fp,
-  in~curl .fp_gset:N = \l_hobby_in_curl_fp,
-  out~curl .fp_gset:N = \l_hobby_out_curl_fp,
-  closed .bool_gset:N = \l_hobby_closed_bool,
+  in~angle .fp_gset:N = \g__hobby_in_angle_fp,
+  out~angle .fp_gset:N = \g__hobby_out_angle_fp,
+  in~curl .fp_gset:N = \g__hobby_in_curl_fp,
+  out~curl .fp_gset:N = \g__hobby_out_curl_fp,
+  closed .bool_gset:N = \g__hobby_closed_bool,
   closed .default:n = true,
-  disjoint .bool_gset:N = \l_hobby_disjoint_bool,
+  disjoint .bool_gset:N = \g__hobby_disjoint_bool,
   disjoint .default:n = true,
   break~default .code:n = {
     \keys_define:nn { hobby / read in all }
@@ -138,13 +140,13 @@
   },
 }
 \keys_define:nn { hobby / read in params} {
-  in~angle .fp_gset:N = \l_hobby_in_angle_fp,
-  out~angle .fp_gset:N = \l_hobby_out_angle_fp,
-  in~curl .fp_gset:N = \l_hobby_in_curl_fp,
-  out~curl .fp_gset:N = \l_hobby_out_curl_fp,
-  closed .bool_gset:N = \l_hobby_closed_bool,
+  in~angle .fp_gset:N = \g__hobby_in_angle_fp,
+  out~angle .fp_gset:N = \g__hobby_out_angle_fp,
+  in~curl .fp_gset:N = \g__hobby_in_curl_fp,
+  out~curl .fp_gset:N = \g__hobby_out_curl_fp,
+  closed .bool_gset:N = \g__hobby_closed_bool,
   closed .default:n = true,
-  disjoint .bool_gset:N = \l_hobby_disjoint_bool,
+  disjoint .bool_gset:N = \g__hobby_disjoint_bool,
   disjoint .default:n = true,
   break~default .code:n = {
     \keys_define:nn { hobby / read in all }
@@ -160,27 +162,27 @@
   },
   invert~soft~blanks .choice:,
   invert~soft~blanks / true .code:n = {
-    \int_gset:Nn \l_hobby_draw_int {0}
+    \int_gset:Nn \g__hobby_draw_int {0}
   },
   invert~soft~blanks / false .code:n = {
-    \int_gset:Nn \l_hobby_draw_int {1}
+    \int_gset:Nn \g__hobby_draw_int {1}
   },
   invert~soft~blanks .default:n = true,
 }
 \cs_set:Nn \hobby_distangle:n {
   \fp_set:Nn \l_hobby_tempa_fp {
-    (\array_get:Nn \l_hobby_points_x_array {#1 + 1})
-    - (\array_get:Nn \l_hobby_points_x_array {#1})}
+    (\array_get:Nn \g__hobby_points_x_array {#1 + 1})
+    - (\array_get:Nn \g__hobby_points_x_array {#1})}
 
   \fp_set:Nn \l_hobby_tempb_fp {
-    (\array_get:Nn \l_hobby_points_y_array {#1 + 1})
-    - (\array_get:Nn \l_hobby_points_y_array {#1})}
+    (\array_get:Nn \g__hobby_points_y_array {#1 + 1})
+    - (\array_get:Nn \g__hobby_points_y_array {#1})}
 
   \fp_set:Nn \l_hobby_tempc_fp { atan ( \l_hobby_tempb_fp, \l_hobby_tempa_fp ) }
   \fp_veclen:NVV \l_hobby_tempd_fp \l_hobby_tempa_fp \l_hobby_tempb_fp
 
-  \array_push:Nx \l_hobby_angles_array {\fp_to_tl:N \l_hobby_tempc_fp}
-  \array_push:Nx \l_hobby_distances_array {\fp_to_tl:N \l_hobby_tempd_fp}
+  \array_gpush:Nx \g__hobby_angles_array {\fp_to_tl:N \l_hobby_tempc_fp}
+  \array_gpush:Nx \g__hobby_distances_array {\fp_to_tl:N \l_hobby_tempd_fp}
   }
 \cs_new:Nn \fp_veclen:Nnn {
   \fp_set:Nn #1 {((#2)^2 + (#3)^2)^.5}
@@ -196,27 +198,27 @@
 \cs_generate_variant:Nn \hobby_ctrllen:Nnn {NVV}
 \cs_new_protected:Npn \hobby_append_point_copy:n #1
   {
-    \hobby_append_point_copy_aux:Nn \l_hobby_points_array {#1}
-    \hobby_append_point_copy_aux:Nn \l_hobby_points_x_array {#1}
-    \hobby_append_point_copy_aux:Nn \l_hobby_points_y_array {#1}
-    \hobby_append_point_copy_aux:Nn \l_hobby_tension_in_array {#1}
-    \hobby_append_point_copy_aux:Nn \l_hobby_tension_out_array {#1}
-    \hobby_append_point_copy_aux:Nn \l_hobby_excess_angle_array {#1}
-    \hobby_append_point_copy_aux:Nn \l_hobby_actions_array {#1}
+    \hobby_append_point_copy_aux:Nn \g__hobby_points_array {#1}
+    \hobby_append_point_copy_aux:Nn \g__hobby_points_x_array {#1}
+    \hobby_append_point_copy_aux:Nn \g__hobby_points_y_array {#1}
+    \hobby_append_point_copy_aux:Nn \g__hobby_tension_in_array {#1}
+    \hobby_append_point_copy_aux:Nn \g__hobby_tension_out_array {#1}
+    \hobby_append_point_copy_aux:Nn \g__hobby_excess_angle_array {#1}
+    \hobby_append_point_copy_aux:Nn \g__hobby_actions_array {#1}
   }
 \cs_new_protected:Npn \hobby_append_point_copy_aux:Nn #1#2
   { \array_gpush:Nx #1 { \array_get:Nn #1 {#2} } }
 \cs_new:Nn \hobby_gen_path:
 {
-\bool_if:NT \l_hobby_closed_bool {
-  \fp_compare:nTF {(\array_get:Nn \l_hobby_points_x_array {0})
+\bool_if:NT \g__hobby_closed_bool {
+  \fp_compare:nTF {(\array_get:Nn \g__hobby_points_x_array {0})
     =
-    (\array_top:N \l_hobby_points_x_array)}
+    (\array_top:N \g__hobby_points_x_array)}
   {
     \fp_compare:nF {
-      \array_get:Nn \l_hobby_points_y_array {0}
+      \array_get:Nn \g__hobby_points_y_array {0}
       =
-      \array_top:N \l_hobby_points_y_array
+      \array_top:N \g__hobby_points_y_array
     }
   {
     \hobby_append_point_copy:n {0}
@@ -227,16 +229,16 @@
   }
     \hobby_append_point_copy:n {1}
 }
-\int_gset:Nn \l_hobby_npoints_int {\array_length:N \l_hobby_points_y_array}
-\int_compare:nNnTF {\l_hobby_npoints_int} = {0} {
+\int_gset:Nn \g__hobby_npoints_int {\array_length:N \g__hobby_points_y_array}
+\int_compare:nNnTF {\g__hobby_npoints_int} = {0} {
 }
 {
-  \int_compare:nNnTF {\l_hobby_npoints_int} = {1} {
+  \int_compare:nNnTF {\g__hobby_npoints_int} = {1} {
 \hobby_distangle:n {0}
-\fp_compare:nF { \l_hobby_out_angle_fp == \c_inf_fp }
+\fp_compare:nF { \g__hobby_out_angle_fp == \c_inf_fp }
 {
-  \fp_set:Nn \l_hobby_tempa_fp { \l_hobby_out_angle_fp
-    - \array_get:Nn \l_hobby_angles_array {0}}
+  \fp_set:Nn \l_hobby_tempa_fp { \g__hobby_out_angle_fp
+    - \array_get:Nn \g__hobby_angles_array {0}}
     \fp_compare:nT {\l_hobby_tempa_fp > \c_pi_fp }
     {
       \fp_sub:Nn \l_hobby_tempa_fp {2 * \c_pi_fp}
@@ -245,23 +247,23 @@
     {
       \fp_add:Nn \l_hobby_tempa_fp {2 * \c_pi_fp}
     }
-  \array_put:Nnx \l_hobby_theta_array {0} {\fp_to_tl:N \l_hobby_tempa_fp}
-    \fp_compare:nT { \l_hobby_in_angle_fp == \c_inf_fp }
+  \array_gput:Nnx \g__hobby_theta_array {0} {\fp_to_tl:N \l_hobby_tempa_fp}
+    \fp_compare:nT { \g__hobby_in_angle_fp == \c_inf_fp }
     {
-      \array_put:Nnx \l_hobby_phi_array {1}{ \fp_to_tl:N \l_hobby_tempa_fp}
+      \array_gput:Nnx \g__hobby_phi_array {1}{ \fp_to_tl:N \l_hobby_tempa_fp}
     }
    }
-\fp_compare:nTF { \l_hobby_in_angle_fp == \c_inf_fp }
+\fp_compare:nTF { \g__hobby_in_angle_fp == \c_inf_fp }
 {
-  \fp_compare:nT { \l_hobby_out_angle_fp == \c_inf_fp }
+  \fp_compare:nT { \g__hobby_out_angle_fp == \c_inf_fp }
   {
-    \array_put:Nnx \l_hobby_phi_array {1} {0}
-    \array_put:Nnx \l_hobby_theta_array {0} {0}
+    \array_gput:Nnx \g__hobby_phi_array {1} {0}
+    \array_gput:Nnx \g__hobby_theta_array {0} {0}
   }
 }
 {
-  \fp_set:Nn \l_hobby_tempa_fp { - \l_hobby_in_angle_fp + \c_pi_fp
-+ (\array_get:Nn \l_hobby_angles_array {0})}
+  \fp_set:Nn \l_hobby_tempa_fp { - \g__hobby_in_angle_fp + \c_pi_fp
++ (\array_get:Nn \g__hobby_angles_array {0})}
   \fp_compare:nT {\l_hobby_tempa_fp > \c_pi_fp }
   {
     \fp_sub:Nn \l_hobby_tempa_fp {2 * \c_pi_fp}
@@ -271,11 +273,11 @@
     \fp_add:Nn \l_hobby_tempa_fp {2 * \c_pi_fp}
   }
 
-  \array_put:Nnx \l_hobby_phi_array {1}
+  \array_gput:Nnx \g__hobby_phi_array {1}
   {\fp_to_tl:N \l_hobby_tempa_fp}
-  \fp_compare:nT { \l_hobby_out_angle_fp == \c_inf_fp }
+  \fp_compare:nT { \g__hobby_out_angle_fp == \c_inf_fp }
     {
-      \array_put:Nnx \l_hobby_theta_array {0}{ \fp_to_tl:N \l_hobby_tempa_fp}
+      \array_gput:Nnx \g__hobby_theta_array {0}{ \fp_to_tl:N \l_hobby_tempa_fp}
     }
 }
 
@@ -288,11 +290,11 @@
 }
 \cs_new:Nn \hobby_compute_path:
 {
-\int_step_function:nnnN {0} {1} {\l_hobby_npoints_int - 1} \hobby_distangle:n
-  \int_step_inline:nnnn {1} {1} {\l_hobby_npoints_int - 1} {
+\int_step_function:nnnN {0} {1} {\g__hobby_npoints_int - 1} \hobby_distangle:n
+  \int_step_inline:nnnn {1} {1} {\g__hobby_npoints_int - 1} {
     \fp_set:Nx \l_hobby_tempa_fp {
-    \array_get:Nn \l_hobby_angles_array {##1}
-    - \array_get:Nn \l_hobby_angles_array {##1 - 1}}
+    \array_get:Nn \g__hobby_angles_array {##1}
+    - \array_get:Nn \g__hobby_angles_array {##1 - 1}}
     \fp_compare:nTF {\l_hobby_tempa_fp > \c_pi_fp }
     {
       \fp_sub:Nn \l_hobby_tempa_fp {2 * \c_pi_fp}
@@ -303,129 +305,129 @@
       \fp_add:Nn \l_hobby_tempa_fp {2 * \c_pi_fp}
     }
     {}
-\array_get:NnNTF \l_hobby_excess_angle_array {##1} \l_tmpa_tl {
+\array_get:NnNTF \g__hobby_excess_angle_array {##1} \l_tmpa_tl {
   \fp_add:Nn \l_hobby_tempa_fp {2 * \c_pi_fp * \l_tmpa_tl}
   }{}
-    \array_put:Nnx \l_hobby_psi_array {##1}{\fp_to_tl:N \l_hobby_tempa_fp}
+    \array_gput:Nnx \g__hobby_psi_array {##1}{\fp_to_tl:N \l_hobby_tempa_fp}
   }
-  \int_step_inline:nnnn {1} {1} {\l_hobby_npoints_int - 1} {
-    \array_put:Nnx \l_hobby_matrix_a_array {##1} {\fp_to_tl:n {
-       \array_get:Nn \l_hobby_tension_in_array {##1}^2
-      * \array_get:Nn \l_hobby_distances_array {##1}
-      * \array_get:Nn \l_hobby_tension_in_array {##1 + 1}
+  \int_step_inline:nnnn {1} {1} {\g__hobby_npoints_int - 1} {
+    \array_gput:Nnx \g__hobby_matrix_a_array {##1} {\fp_to_tl:n {
+       \array_get:Nn \g__hobby_tension_in_array {##1}^2
+      * \array_get:Nn \g__hobby_distances_array {##1}
+      * \array_get:Nn \g__hobby_tension_in_array {##1 + 1}
   }}
 }
-  \int_step_inline:nnnn {1} {1} {\l_hobby_npoints_int - 1} {
+  \int_step_inline:nnnn {1} {1} {\g__hobby_npoints_int - 1} {
 
-  \array_put:Nnx \l_hobby_matrix_b_array {##1} {\fp_to_tl:n
-{(3 * (\array_get:Nn \l_hobby_tension_in_array {##1 + 1}) - 1) *
- (\array_get:Nn \l_hobby_tension_out_array {##1})^2 *
-(\array_get:Nn \l_hobby_tension_out_array {##1 - 1})
-* ( \array_get:Nn \l_hobby_distances_array {##1 - 1})
+  \array_gput:Nnx \g__hobby_matrix_b_array {##1} {\fp_to_tl:n
+{(3 * (\array_get:Nn \g__hobby_tension_in_array {##1 + 1}) - 1) *
+ (\array_get:Nn \g__hobby_tension_out_array {##1})^2 *
+(\array_get:Nn \g__hobby_tension_out_array {##1 - 1})
+* ( \array_get:Nn \g__hobby_distances_array {##1 - 1})
 +
-(3 * (\array_get:Nn \l_hobby_tension_out_array {##1 - 1}) - 1)
-* (\array_get:Nn \l_hobby_tension_in_array {##1})^2
-* (\array_get:Nn \l_hobby_tension_in_array {##1 + 1})
-* (\array_get:Nn \l_hobby_distances_array {##1})}
+(3 * (\array_get:Nn \g__hobby_tension_out_array {##1 - 1}) - 1)
+* (\array_get:Nn \g__hobby_tension_in_array {##1})^2
+* (\array_get:Nn \g__hobby_tension_in_array {##1 + 1})
+* (\array_get:Nn \g__hobby_distances_array {##1})}
 }
 }
-  \int_step_inline:nnnn {1} {1} {\l_hobby_npoints_int - 2} {
+  \int_step_inline:nnnn {1} {1} {\g__hobby_npoints_int - 2} {
 
-  \array_put:Nnx \l_hobby_matrix_c_array {##1} {\fp_to_tl:n
-{(\array_get:Nn \l_hobby_tension_in_array {##1})^2
-* (\array_get:Nn \l_hobby_tension_in_array {##1 - 1})
-* (\array_get:Nn \l_hobby_distances_array {##1 - 1})
+  \array_gput:Nnx \g__hobby_matrix_c_array {##1} {\fp_to_tl:n
+{(\array_get:Nn \g__hobby_tension_in_array {##1})^2
+* (\array_get:Nn \g__hobby_tension_in_array {##1 - 1})
+* (\array_get:Nn \g__hobby_distances_array {##1 - 1})
 }}
 
 }
-  \int_step_inline:nnnn {1} {1} {\l_hobby_npoints_int - 2} {
+  \int_step_inline:nnnn {1} {1} {\g__hobby_npoints_int - 2} {
 
-  \array_put:Nnx \l_hobby_matrix_d_array {##1} {\fp_to_tl:n
+  \array_gput:Nnx \g__hobby_matrix_d_array {##1} {\fp_to_tl:n
 {
-- (\array_get:Nn \l_hobby_psi_array {##1 + 1})
-* (\array_get:Nn \l_hobby_tension_out_array {##1})^2
-* (\array_get:Nn \l_hobby_tension_out_array {##1 - 1})
-* (\array_get:Nn \l_hobby_distances_array {##1 - 1})
-- (3 * (\array_get:Nn \l_hobby_tension_out_array {##1 - 1}) - 1)
-* (\array_get:Nn \l_hobby_psi_array {##1})
-* (\array_get:Nn \l_hobby_tension_in_array {##1})^2
-* (\array_get:Nn \l_hobby_tension_in_array {##1 + 1})
-* (\array_get:Nn \l_hobby_distances_array {##1})
+- (\array_get:Nn \g__hobby_psi_array {##1 + 1})
+* (\array_get:Nn \g__hobby_tension_out_array {##1})^2
+* (\array_get:Nn \g__hobby_tension_out_array {##1 - 1})
+* (\array_get:Nn \g__hobby_distances_array {##1 - 1})
+- (3 * (\array_get:Nn \g__hobby_tension_out_array {##1 - 1}) - 1)
+* (\array_get:Nn \g__hobby_psi_array {##1})
+* (\array_get:Nn \g__hobby_tension_in_array {##1})^2
+* (\array_get:Nn \g__hobby_tension_in_array {##1 + 1})
+* (\array_get:Nn \g__hobby_distances_array {##1})
 }
 }
 }
-\bool_if:NTF \l_hobby_closed_bool {
-\array_put:Nnx \l_hobby_matrix_c_array {0} {\fp_to_tl:n {
-- (\array_get:Nn \l_hobby_distances_array {\l_hobby_npoints_int - 2})
-* (\array_get:Nn \l_hobby_tension_out_array {\l_hobby_npoints_int - 2})
-* (\array_get:Nn \l_hobby_tension_out_array {\l_hobby_npoints_int - 1})^2
+\bool_if:NTF \g__hobby_closed_bool {
+\array_gput:Nnx \g__hobby_matrix_c_array {0} {\fp_to_tl:n {
+- (\array_get:Nn \g__hobby_distances_array {\g__hobby_npoints_int - 2})
+* (\array_get:Nn \g__hobby_tension_out_array {\g__hobby_npoints_int - 2})
+* (\array_get:Nn \g__hobby_tension_out_array {\g__hobby_npoints_int - 1})^2
 }}
 
-\array_put:Nnn \l_hobby_matrix_b_array {0} {1}
-\array_put:Nnn \l_hobby_matrix_d_array {0} {0}
+\array_gput:Nnn \g__hobby_matrix_b_array {0} {1}
+\array_gput:Nnn \g__hobby_matrix_d_array {0} {0}
 
-\array_put:Nnx \l_hobby_matrix_b_array {\l_hobby_npoints_int - 1} {\fp_to_tl:n {
-(\array_get:Nn \l_hobby_matrix_b_array {\l_hobby_npoints_int - 1})
+\array_gput:Nnx \g__hobby_matrix_b_array {\g__hobby_npoints_int - 1} {\fp_to_tl:n {
+(\array_get:Nn \g__hobby_matrix_b_array {\g__hobby_npoints_int - 1})
 + 1
 }}
 
- \array_put:Nnx \l_hobby_matrix_d_array {\l_hobby_npoints_int - 1} {\fp_to_tl:n {
-- (\array_get:Nn \l_hobby_psi_array {1})
-* (\array_get:Nn \l_hobby_tension_out_array {\l_hobby_npoints_int -1})^2
-* (\array_get:Nn \l_hobby_tension_out_array {\l_hobby_npoints_int -2})
-* (\array_get:Nn \l_hobby_distances_array {\l_hobby_npoints_int - 2})
-- (3 * (\array_get:Nn \l_hobby_tension_out_array {\l_hobby_npoints_int - 2}) - 1)
-* (\array_get:Nn \l_hobby_psi_array {\l_hobby_npoints_int - 1})
-* (\array_get:Nn \l_hobby_tension_in_array {\l_hobby_npoints_int - 1})^2
-* (\array_get:Nn \l_hobby_tension_in_array {\l_hobby_npoints_int})
-* (\array_get:Nn \l_hobby_distances_array {\l_hobby_npoints_int -1})
+ \array_gput:Nnx \g__hobby_matrix_d_array {\g__hobby_npoints_int - 1} {\fp_to_tl:n {
+- (\array_get:Nn \g__hobby_psi_array {1})
+* (\array_get:Nn \g__hobby_tension_out_array {\g__hobby_npoints_int -1})^2
+* (\array_get:Nn \g__hobby_tension_out_array {\g__hobby_npoints_int -2})
+* (\array_get:Nn \g__hobby_distances_array {\g__hobby_npoints_int - 2})
+- (3 * (\array_get:Nn \g__hobby_tension_out_array {\g__hobby_npoints_int - 2}) - 1)
+* (\array_get:Nn \g__hobby_psi_array {\g__hobby_npoints_int - 1})
+* (\array_get:Nn \g__hobby_tension_in_array {\g__hobby_npoints_int - 1})^2
+* (\array_get:Nn \g__hobby_tension_in_array {\g__hobby_npoints_int})
+* (\array_get:Nn \g__hobby_distances_array {\g__hobby_npoints_int -1})
 }
 }
-  \array_put:Nnn \l_hobby_vector_u_array {0} {1}
-\array_put:Nnn \l_hobby_vector_u_array {\l_hobby_npoints_int - 1} {1}
-  \int_step_inline:nnnn {1} {1} {\l_hobby_npoints_int - 2} {
-  \array_put:Nnn \l_hobby_vector_u_array {##1} {0}
+  \array_gput:Nnn \g__hobby_vector_u_array {0} {1}
+\array_gput:Nnn \g__hobby_vector_u_array {\g__hobby_npoints_int - 1} {1}
+  \int_step_inline:nnnn {1} {1} {\g__hobby_npoints_int - 2} {
+  \array_gput:Nnn \g__hobby_vector_u_array {##1} {0}
   }
 \fp_set:Nn \l_hobby_matrix_v_fp {
-(\array_get:Nn \l_hobby_tension_out_array {\l_hobby_npoints_int -1})^2
-* (\array_get:Nn \l_hobby_tension_out_array {\l_hobby_npoints_int -2})
-* (\array_get:Nn \l_hobby_distances_array {\l_hobby_npoints_int -2})
+(\array_get:Nn \g__hobby_tension_out_array {\g__hobby_npoints_int -1})^2
+* (\array_get:Nn \g__hobby_tension_out_array {\g__hobby_npoints_int -2})
+* (\array_get:Nn \g__hobby_distances_array {\g__hobby_npoints_int -2})
 }
 }
 {
-\fp_compare:nTF { \l_hobby_out_angle_fp == \c_inf_fp }
+\fp_compare:nTF { \g__hobby_out_angle_fp == \c_inf_fp }
 {
-  \array_put:Nnx \l_hobby_matrix_b_array {0}  {\fp_to_tl:n {
-  (\array_get:Nn \l_hobby_tension_in_array {1})^3
-* \l_hobby_in_curl_fp
+  \array_gput:Nnx \g__hobby_matrix_b_array {0}  {\fp_to_tl:n {
+  (\array_get:Nn \g__hobby_tension_in_array {1})^3
+* \g__hobby_in_curl_fp
 +
-(3 * (\array_get:Nn \l_hobby_tension_in_array {1}) - 1)
-* (\array_get:Nn \l_hobby_tension_out_array {0})^3
+(3 * (\array_get:Nn \g__hobby_tension_in_array {1}) - 1)
+* (\array_get:Nn \g__hobby_tension_out_array {0})^3
 }}
 
-  \array_put:Nnx \l_hobby_matrix_c_array {0} {\fp_to_tl:n {
-  (\array_get:Nn \l_hobby_tension_out_array {0})^3
+  \array_gput:Nnx \g__hobby_matrix_c_array {0} {\fp_to_tl:n {
+  (\array_get:Nn \g__hobby_tension_out_array {0})^3
 +
-(3 * (\array_get:Nn \l_hobby_tension_out_array {0}) - 1)
-* (\array_get:Nn \l_hobby_tension_in_array {1})^3
-* \l_hobby_in_curl_fp
+(3 * (\array_get:Nn \g__hobby_tension_out_array {0}) - 1)
+* (\array_get:Nn \g__hobby_tension_in_array {1})^3
+* \g__hobby_in_curl_fp
 }}
 
-  \array_put:Nnx \l_hobby_matrix_d_array {0} {\fp_to_tl:n {
--(  (\array_get:Nn \l_hobby_tension_out_array {0})^3
+  \array_gput:Nnx \g__hobby_matrix_d_array {0} {\fp_to_tl:n {
+-(  (\array_get:Nn \g__hobby_tension_out_array {0})^3
 +
-(3 * (\array_get:Nn \l_hobby_tension_out_array {0}) - 1)
-* (\array_get:Nn \l_hobby_tension_in_array {1})^3
-* \l_hobby_in_curl_fp)
-* (\array_get:Nn \l_hobby_psi_array {1})
+(3 * (\array_get:Nn \g__hobby_tension_out_array {0}) - 1)
+* (\array_get:Nn \g__hobby_tension_in_array {1})^3
+* \g__hobby_in_curl_fp)
+* (\array_get:Nn \g__hobby_psi_array {1})
 }}
 
 }
 {
-  \array_put:Nnn \l_hobby_matrix_b_array {0} {1}
-  \array_put:Nnn \l_hobby_matrix_c_array {0} {0}
-  \fp_set:Nn \l_hobby_tempa_fp { \l_hobby_out_angle_fp
-    - \array_get:Nn \l_hobby_angles_array {0}}
+  \array_gput:Nnn \g__hobby_matrix_b_array {0} {1}
+  \array_gput:Nnn \g__hobby_matrix_c_array {0} {0}
+  \fp_set:Nn \l_hobby_tempa_fp { \g__hobby_out_angle_fp
+    - \array_get:Nn \g__hobby_angles_array {0}}
     \fp_compare:nT {\l_hobby_tempa_fp > \c_pi_fp }
     {
       \fp_sub:Nn \l_hobby_tempa_fp {2 * \c_pi_fp}
@@ -434,42 +436,42 @@
     {
       \fp_add:Nn \l_hobby_tempa_fp {2 * \c_pi_fp}
     }
-  \array_put:Nnx \l_hobby_matrix_d_array {0} {\fp_to_tl:N \l_hobby_tempa_fp}
+  \array_gput:Nnx \g__hobby_matrix_d_array {0} {\fp_to_tl:N \l_hobby_tempa_fp}
 }
-\fp_compare:nTF { \l_hobby_in_angle_fp == \c_inf_fp }
+\fp_compare:nTF { \g__hobby_in_angle_fp == \c_inf_fp }
 {
 
- \array_put:Nnx \l_hobby_matrix_b_array {\l_hobby_npoints_int - 1} {\fp_to_tl:n {
-\array_get:Nn \l_hobby_matrix_b_array {\l_hobby_npoints_int - 1}
-- (\array_get:Nn \l_hobby_tension_out_array {\l_hobby_npoints_int - 1})^2
-* (\array_get:Nn \l_hobby_tension_out_array {\l_hobby_npoints_int - 2})
-* (\array_get:Nn \l_hobby_distances_array {\l_hobby_npoints_int - 2})
+ \array_gput:Nnx \g__hobby_matrix_b_array {\g__hobby_npoints_int - 1} {\fp_to_tl:n {
+\array_get:Nn \g__hobby_matrix_b_array {\g__hobby_npoints_int - 1}
+- (\array_get:Nn \g__hobby_tension_out_array {\g__hobby_npoints_int - 1})^2
+* (\array_get:Nn \g__hobby_tension_out_array {\g__hobby_npoints_int - 2})
+* (\array_get:Nn \g__hobby_distances_array {\g__hobby_npoints_int - 2})
 *
-((3 * (\array_get:Nn \l_hobby_tension_in_array {\l_hobby_npoints_int} ) - 1)
-* (\array_get:Nn \l_hobby_tension_out_array {\l_hobby_npoints_int - 1})^3 \l_tmpa_tl
-* \l_hobby_out_curl_fp
+((3 * (\array_get:Nn \g__hobby_tension_in_array {\g__hobby_npoints_int} ) - 1)
+* (\array_get:Nn \g__hobby_tension_out_array {\g__hobby_npoints_int - 1})^3 \l_tmpa_tl
+* \g__hobby_out_curl_fp
 +
-(\array_get:Nn \l_hobby_tension_in_array {\l_hobby_npoints_int })^3)
+(\array_get:Nn \g__hobby_tension_in_array {\g__hobby_npoints_int })^3)
 /
-((3 * (\array_get:Nn \l_hobby_tension_out_array {\l_hobby_npoints_int -2}) - 1)
-* (\array_get:Nn \l_hobby_tension_in_array {\l_hobby_npoints_int})^3
+((3 * (\array_get:Nn \g__hobby_tension_out_array {\g__hobby_npoints_int -2}) - 1)
+* (\array_get:Nn \g__hobby_tension_in_array {\g__hobby_npoints_int})^3
 +
-( \array_get:Nn \l_hobby_tension_out_array {\l_hobby_npoints_int - 1})^3
-* \l_hobby_out_curl_fp)
+( \array_get:Nn \g__hobby_tension_out_array {\g__hobby_npoints_int - 1})^3
+* \g__hobby_out_curl_fp)
 }}
 
- \array_put:Nnx \l_hobby_matrix_d_array {\l_hobby_npoints_int - 1} {\fp_to_tl:n {
-- (3 * (\array_get:Nn \l_hobby_tension_out_array {\l_hobby_npoints_int - 2}) - 1)
-* (\array_get:Nn \l_hobby_psi_array {\l_hobby_npoints_int - 1})
-* (\array_get:Nn \l_hobby_tension_in_array {\l_hobby_npoints_int - 1})^2
-* (\array_get:Nn \l_hobby_tension_in_array {\l_hobby_npoints_int})
-* (\array_get:Nn \l_hobby_distances_array {\l_hobby_npoints_int - 1})
+ \array_gput:Nnx \g__hobby_matrix_d_array {\g__hobby_npoints_int - 1} {\fp_to_tl:n {
+- (3 * (\array_get:Nn \g__hobby_tension_out_array {\g__hobby_npoints_int - 2}) - 1)
+* (\array_get:Nn \g__hobby_psi_array {\g__hobby_npoints_int - 1})
+* (\array_get:Nn \g__hobby_tension_in_array {\g__hobby_npoints_int - 1})^2
+* (\array_get:Nn \g__hobby_tension_in_array {\g__hobby_npoints_int})
+* (\array_get:Nn \g__hobby_distances_array {\g__hobby_npoints_int - 1})
 }}
 
 }
 {
-  \fp_set:Nn \l_hobby_tempa_fp { - \l_hobby_in_angle_fp + \c_pi_fp
-+ (\array_get:Nn \l_hobby_angles_array {\l_hobby_npoints_int - 1})}
+  \fp_set:Nn \l_hobby_tempa_fp { - \g__hobby_in_angle_fp + \c_pi_fp
++ (\array_get:Nn \g__hobby_angles_array {\g__hobby_npoints_int - 1})}
   \fp_compare:nT {\l_hobby_tempa_fp > \c_pi_fp }
   {
     \fp_sub:Nn \l_hobby_tempa_fp {2 * \c_pi_fp}
@@ -479,132 +481,132 @@
     \fp_add:Nn \l_hobby_tempa_fp {2 * \c_pi_fp}
   }
 
-  \array_put:Nnx \l_hobby_phi_array {\l_hobby_npoints_int}
+  \array_gput:Nnx \g__hobby_phi_array {\g__hobby_npoints_int}
   {\fp_to_tl:N \l_hobby_tempa_fp}
 
-   \array_put:Nnx \l_hobby_matrix_d_array  {\l_hobby_npoints_int - 1} {\fp_to_tl:n {
+   \array_gput:Nnx \g__hobby_matrix_d_array  {\g__hobby_npoints_int - 1} {\fp_to_tl:n {
  \l_hobby_tempa_fp
- * (\array_get:Nn \l_hobby_tension_out_array {\l_hobby_npoints_int - 1})^2
-* (\array_get:Nn \l_hobby_tension_out_array {\l_hobby_npoints_int - 2})
-* (\array_get:Nn \l_hobby_distances_array {\l_hobby_npoints_int - 2})
+ * (\array_get:Nn \g__hobby_tension_out_array {\g__hobby_npoints_int - 1})^2
+* (\array_get:Nn \g__hobby_tension_out_array {\g__hobby_npoints_int - 2})
+* (\array_get:Nn \g__hobby_distances_array {\g__hobby_npoints_int - 2})
 -
-(3 * ( \array_get:Nn \l_hobby_tension_out_array {\l_hobby_npoints_int - 2}) - 1)
-* (\array_get:Nn \l_hobby_psi_array {\l_hobby_npoints_int - 1})
-* (\array_get:Nn \l_hobby_tension_in_array  {\l_hobby_npoints_int - 1})^2
-* (\array_get:Nn \l_hobby_tension_in_array {\l_hobby_npoints_int})
-* (\array_get:Nn \l_hobby_distances_array  {\l_hobby_npoints_int - 1}) }}
+(3 * ( \array_get:Nn \g__hobby_tension_out_array {\g__hobby_npoints_int - 2}) - 1)
+* (\array_get:Nn \g__hobby_psi_array {\g__hobby_npoints_int - 1})
+* (\array_get:Nn \g__hobby_tension_in_array  {\g__hobby_npoints_int - 1})^2
+* (\array_get:Nn \g__hobby_tension_in_array {\g__hobby_npoints_int})
+* (\array_get:Nn \g__hobby_distances_array  {\g__hobby_npoints_int - 1}) }}
 }
 }
-\int_step_inline:nnnn {1} {1} {\l_hobby_npoints_int - 1} {
+\int_step_inline:nnnn {1} {1} {\g__hobby_npoints_int - 1} {
 
-  \array_put:Nnx \l_hobby_matrix_b_array {##1} {\fp_to_tl:n {
-  (\array_get:Nn \l_hobby_matrix_b_array {##1 - 1})
-* (\array_get:Nn \l_hobby_matrix_b_array {##1})
+  \array_gput:Nnx \g__hobby_matrix_b_array {##1} {\fp_to_tl:n {
+  (\array_get:Nn \g__hobby_matrix_b_array {##1 - 1})
+* (\array_get:Nn \g__hobby_matrix_b_array {##1})
 -
-(\array_get:Nn \l_hobby_matrix_c_array {##1 - 1})
-* (\array_get:Nn \l_hobby_matrix_a_array {##1})
+(\array_get:Nn \g__hobby_matrix_c_array {##1 - 1})
+* (\array_get:Nn \g__hobby_matrix_a_array {##1})
 }}
-  \int_compare:nT {##1 < \l_hobby_npoints_int - 1} {
+  \int_compare:nT {##1 < \g__hobby_npoints_int - 1} {
 
-  \array_put:Nnx \l_hobby_matrix_c_array {##1} {\fp_to_tl:n {
-(\array_get:Nn \l_hobby_matrix_b_array {##1 - 1})
-    * (\array_get:Nn \l_hobby_matrix_c_array {##1})
+  \array_gput:Nnx \g__hobby_matrix_c_array {##1} {\fp_to_tl:n {
+(\array_get:Nn \g__hobby_matrix_b_array {##1 - 1})
+    * (\array_get:Nn \g__hobby_matrix_c_array {##1})
 }}
   }
 
-  \array_put:Nnx \l_hobby_matrix_d_array {##1} {\fp_to_tl:n {
-(\array_get:Nn \l_hobby_matrix_b_array {##1 - 1})
-  * (\array_get:Nn \l_hobby_matrix_d_array {##1})
+  \array_gput:Nnx \g__hobby_matrix_d_array {##1} {\fp_to_tl:n {
+(\array_get:Nn \g__hobby_matrix_b_array {##1 - 1})
+  * (\array_get:Nn \g__hobby_matrix_d_array {##1})
 -
-  (\array_get:Nn \l_hobby_matrix_d_array {##1 - 1})
-  * (\array_get:Nn \l_hobby_matrix_a_array {##1})
+  (\array_get:Nn \g__hobby_matrix_d_array {##1 - 1})
+  * (\array_get:Nn \g__hobby_matrix_a_array {##1})
 }}
-  \bool_if:NT \l_hobby_closed_bool {
-  \array_put:Nnx \l_hobby_vector_u_array {##1} {\fp_to_tl:n {
-(\array_get:Nn \l_hobby_matrix_b_array {##1 - 1})
-* (\array_get:Nn \l_hobby_vector_u_array {##1})
+  \bool_if:NT \g__hobby_closed_bool {
+  \array_gput:Nnx \g__hobby_vector_u_array {##1} {\fp_to_tl:n {
+(\array_get:Nn \g__hobby_matrix_b_array {##1 - 1})
+* (\array_get:Nn \g__hobby_vector_u_array {##1})
 -
-(\array_get:Nn \l_hobby_vector_u_array {##1 - 1})
-* (\array_get:Nn \l_hobby_matrix_a_array {##1})
+(\array_get:Nn \g__hobby_vector_u_array {##1 - 1})
+* (\array_get:Nn \g__hobby_matrix_a_array {##1})
 }}
 }
 }
- \array_put:Nnx \l_hobby_theta_array  {\l_hobby_npoints_int - 1} {\fp_to_tl:n {
-(\array_get:Nn \l_hobby_matrix_d_array  {\l_hobby_npoints_int - 1})
-/ (\array_get:Nn \l_hobby_matrix_b_array  {\l_hobby_npoints_int - 1})
+ \array_gput:Nnx \g__hobby_theta_array  {\g__hobby_npoints_int - 1} {\fp_to_tl:n {
+(\array_get:Nn \g__hobby_matrix_d_array  {\g__hobby_npoints_int - 1})
+/ (\array_get:Nn \g__hobby_matrix_b_array  {\g__hobby_npoints_int - 1})
 }}
-\bool_if:NT \l_hobby_closed_bool {
- \array_put:Nnx \l_hobby_vector_u_array  {\l_hobby_npoints_int - 1} {\fp_to_tl:n {
-  (\array_get:Nn \l_hobby_vector_u_array  {\l_hobby_npoints_int - 1})
-/ (\array_get:Nn \l_hobby_matrix_b_array  {\l_hobby_npoints_int - 1})
+\bool_if:NT \g__hobby_closed_bool {
+ \array_gput:Nnx \g__hobby_vector_u_array  {\g__hobby_npoints_int - 1} {\fp_to_tl:n {
+  (\array_get:Nn \g__hobby_vector_u_array  {\g__hobby_npoints_int - 1})
+/ (\array_get:Nn \g__hobby_matrix_b_array  {\g__hobby_npoints_int - 1})
 }}
 }
-\int_step_inline:nnnn {\l_hobby_npoints_int - 2} {-1} {0} {
+\int_step_inline:nnnn {\g__hobby_npoints_int - 2} {-1} {0} {
 
-  \array_put:Nnx \l_hobby_theta_array {##1} {\fp_to_tl:n {
-( (\array_get:Nn \l_hobby_matrix_d_array {##1})
-  - (\array_get:Nn \l_hobby_theta_array  {##1 + 1})
-  * (\array_get:Nn \l_hobby_matrix_c_array {##1})
-) / (\array_get:Nn \l_hobby_matrix_b_array {##1})
+  \array_gput:Nnx \g__hobby_theta_array {##1} {\fp_to_tl:n {
+( (\array_get:Nn \g__hobby_matrix_d_array {##1})
+  - (\array_get:Nn \g__hobby_theta_array  {##1 + 1})
+  * (\array_get:Nn \g__hobby_matrix_c_array {##1})
+) / (\array_get:Nn \g__hobby_matrix_b_array {##1})
 }}
 }
-\bool_if:NT \l_hobby_closed_bool {
-\int_step_inline:nnnn {\l_hobby_npoints_int - 2} {-1} {0} {
-  \array_put:Nnx \l_hobby_vector_u_array {##1} {\fp_to_tl:n
+\bool_if:NT \g__hobby_closed_bool {
+\int_step_inline:nnnn {\g__hobby_npoints_int - 2} {-1} {0} {
+  \array_gput:Nnx \g__hobby_vector_u_array {##1} {\fp_to_tl:n
 {
-    ((\array_get:Nn \l_hobby_vector_u_array {##1})
-    - (\array_get:Nn \l_hobby_vector_u_array  {##1 + 1})
-    * (\array_get:Nn \l_hobby_matrix_c_array {##1})
-    ) / (\array_get:Nn \l_hobby_matrix_b_array {##1})
+    ((\array_get:Nn \g__hobby_vector_u_array {##1})
+    - (\array_get:Nn \g__hobby_vector_u_array  {##1 + 1})
+    * (\array_get:Nn \g__hobby_matrix_c_array {##1})
+    ) / (\array_get:Nn \g__hobby_matrix_b_array {##1})
 }}
 }
 
 \fp_set:Nn \l_hobby_tempb_fp {
-((\array_get:Nn \l_hobby_theta_array {1})
+((\array_get:Nn \g__hobby_theta_array {1})
 * \l_hobby_matrix_v_fp
-- (\array_get:Nn \l_hobby_theta_array  {\l_hobby_npoints_int - 1})
+- (\array_get:Nn \g__hobby_theta_array  {\g__hobby_npoints_int - 1})
 ) / (
-(\array_get:Nn \l_hobby_vector_u_array {1})
+(\array_get:Nn \g__hobby_vector_u_array {1})
 * \l_hobby_matrix_v_fp
-- (\array_get:Nn \l_hobby_vector_u_array  {\l_hobby_npoints_int - 1})
+- (\array_get:Nn \g__hobby_vector_u_array  {\g__hobby_npoints_int - 1})
 + 1
 )}
 
-\int_step_inline:nnnn {0} {1} {\l_hobby_npoints_int - 1} {
+\int_step_inline:nnnn {0} {1} {\g__hobby_npoints_int - 1} {
 
-  \array_put:Nnx \l_hobby_theta_array {##1} {\fp_to_tl:n {
-  (\array_get:Nn \l_hobby_theta_array {##1})
-  - (\array_get:Nn \l_hobby_vector_u_array {##1})
+  \array_gput:Nnx \g__hobby_theta_array {##1} {\fp_to_tl:n {
+  (\array_get:Nn \g__hobby_theta_array {##1})
+  - (\array_get:Nn \g__hobby_vector_u_array {##1})
   * \l_hobby_tempb_fp
 }}
 }
 }
-\int_step_inline:nnnn {1} {1} {\l_hobby_npoints_int - 1} {
+\int_step_inline:nnnn {1} {1} {\g__hobby_npoints_int - 1} {
 
-    \array_put:Nnx \l_hobby_phi_array {##1} {\fp_to_tl:n {
-      - (\array_get:Nn \l_hobby_psi_array {##1})
-      - (\array_get:Nn \l_hobby_theta_array {##1})
+    \array_gput:Nnx \g__hobby_phi_array {##1} {\fp_to_tl:n {
+      - (\array_get:Nn \g__hobby_psi_array {##1})
+      - (\array_get:Nn \g__hobby_theta_array {##1})
   }}
   }
-\bool_if:NTF \l_hobby_closed_bool {
-  \int_gdecr:N \l_hobby_npoints_int
+\bool_if:NTF \g__hobby_closed_bool {
+  \int_gdecr:N \g__hobby_npoints_int
 }{
-\fp_compare:nT { \l_hobby_in_angle_fp == \c_inf_fp }
+\fp_compare:nT { \g__hobby_in_angle_fp == \c_inf_fp }
 {
- \array_put:Nnx \l_hobby_phi_array {\l_hobby_npoints_int} {\fp_to_tl:n {
-((3 * (\array_get:Nn \l_hobby_tension_in_array {\l_hobby_npoints_int}) - 1)
-* (\array_get:Nn \l_hobby_tension_out_array {\l_hobby_npoints_int - 1})^3
-* \l_hobby_out_curl_fp
+ \array_gput:Nnx \g__hobby_phi_array {\g__hobby_npoints_int} {\fp_to_tl:n {
+((3 * (\array_get:Nn \g__hobby_tension_in_array {\g__hobby_npoints_int}) - 1)
+* (\array_get:Nn \g__hobby_tension_out_array {\g__hobby_npoints_int - 1})^3
+* \g__hobby_out_curl_fp
 +
-(\array_get:Nn \l_hobby_tension_in_array {\l_hobby_npoints_int })^3)
+(\array_get:Nn \g__hobby_tension_in_array {\g__hobby_npoints_int })^3)
 /
-((3 * (\array_get:Nn \l_hobby_tension_out_array  {\l_hobby_npoints_int -2}) - 1)
-* (\array_get:Nn \l_hobby_tension_in_array {\l_hobby_npoints_int})^3 \l_tmpa_tl
+((3 * (\array_get:Nn \g__hobby_tension_out_array  {\g__hobby_npoints_int -2}) - 1)
+* (\array_get:Nn \g__hobby_tension_in_array {\g__hobby_npoints_int})^3 \l_tmpa_tl
 +
-(\array_get:Nn \l_hobby_tension_out_array  {\l_hobby_npoints_int - 1})^3
-* \l_hobby_out_curl_fp)
+(\array_get:Nn \g__hobby_tension_out_array  {\g__hobby_npoints_int - 1})^3
+* \g__hobby_out_curl_fp)
 *
-(\array_get:Nn \l_hobby_theta_array  {\l_hobby_npoints_int -1})
+(\array_get:Nn \g__hobby_theta_array  {\g__hobby_npoints_int -1})
 }}
 }
 }
@@ -611,92 +613,97 @@
 }
 \cs_new:Nn \hobby_build_path:
 {
-\int_step_inline:nnnn {0} {1} {\l_hobby_npoints_int - 1} {
+\int_step_inline:nnnn {0} {1} {\g__hobby_npoints_int - 1} {
 
-  \fp_set:Nn \l_hobby_tempa_fp {\array_get:Nn \l_hobby_theta_array {##1}}
+  \fp_set:Nn \l_hobby_tempa_fp {\array_get:Nn \g__hobby_theta_array {##1}}
 
-  \fp_set:Nn \l_hobby_tempb_fp {\array_get:Nn \l_hobby_phi_array  {##1 + 1}}
+  \fp_set:Nn \l_hobby_tempb_fp {\array_get:Nn \g__hobby_phi_array  {##1 + 1}}
 
   \hobby_ctrllen:NVV \l_hobby_temps_fp \l_hobby_tempa_fp \l_hobby_tempb_fp
 
-   \array_put:Nnx \l_hobby_sigma_array {##1 + 1} {\fp_to_tl:N \l_hobby_temps_fp}
+   \array_gput:Nnx \g__hobby_sigma_array {##1 + 1} {\fp_to_tl:N \l_hobby_temps_fp}
 
   \hobby_ctrllen:NVV \l_hobby_temps_fp \l_hobby_tempb_fp \l_hobby_tempa_fp
 
-   \array_put:Nnx \l_hobby_rho_array {##1} {\fp_to_tl:N \l_hobby_temps_fp}
+   \array_gput:Nnx \g__hobby_rho_array {##1} {\fp_to_tl:N \l_hobby_temps_fp}
 
   }
-\int_step_inline:nnnn {0} {1} {\l_hobby_npoints_int - 1} {
-\array_gput:Nnx \l_hobby_controla_array  {##1 + 1} {x = \fp_eval:n  {
-(\array_get:Nn \l_hobby_points_x_array {##1})
+\int_step_inline:nnnn {0} {1} {\g__hobby_npoints_int - 1} {
+\array_gput:Nnx \g__hobby_controla_array  {##1 + 1} {\fp_eval:n  {
+(\array_get:Nn \g__hobby_points_x_array {##1})
 +
-  (\array_get:Nn \l_hobby_distances_array {##1}) *
-  (\array_get:Nn \l_hobby_rho_array {##1}) *
-cos ( (\array_get:Nn \l_hobby_angles_array {##1})
+  (\array_get:Nn \g__hobby_distances_array {##1}) *
+  (\array_get:Nn \g__hobby_rho_array {##1}) *
+cos ( (\array_get:Nn \g__hobby_angles_array {##1})
 +
-  (\array_get:Nn \l_hobby_theta_array {##1}))
+  (\array_get:Nn \g__hobby_theta_array {##1}))
 /3
-}, y = \fp_eval:n {
-( \array_get:Nn \l_hobby_points_y_array {##1}) +
-  (\array_get:Nn \l_hobby_distances_array {##1}) *
-  (\array_get:Nn \l_hobby_rho_array {##1}) *
-sin ( (\array_get:Nn \l_hobby_angles_array {##1})
+}, \fp_eval:n {
+( \array_get:Nn \g__hobby_points_y_array {##1}) +
+  (\array_get:Nn \g__hobby_distances_array {##1}) *
+  (\array_get:Nn \g__hobby_rho_array {##1}) *
+sin ( (\array_get:Nn \g__hobby_angles_array {##1})
 +
-  (\array_get:Nn \l_hobby_theta_array {##1}))
+  (\array_get:Nn \g__hobby_theta_array {##1}))
 /3
 }
 }
 }
-\int_step_inline:nnnn {1} {1} {\l_hobby_npoints_int} {
-  \array_gput:Nnx \l_hobby_controlb_array {##1} {
-    x = \fp_eval:n {\array_get:Nn \l_hobby_points_x_array {##1}
-- (\array_get:Nn \l_hobby_distances_array  {##1 - 1})
-* (\array_get:Nn \l_hobby_sigma_array {##1})
-* cos((\array_get:Nn \l_hobby_angles_array  {##1 - 1})
-- (\array_get:Nn \l_hobby_phi_array {##1}))/3
-}, y = \fp_eval:n {
-  (\array_get:Nn \l_hobby_points_y_array {##1})
-- (\array_get:Nn \l_hobby_distances_array  {##1 - 1})
-* (\array_get:Nn \l_hobby_sigma_array {##1})
-* sin((\array_get:Nn \l_hobby_angles_array  {##1 - 1})
-- (\array_get:Nn \l_hobby_phi_array {##1}))/3
+\int_step_inline:nnnn {1} {1} {\g__hobby_npoints_int} {
+  \array_gput:Nnx \g__hobby_controlb_array {##1} {
+    \fp_eval:n {\array_get:Nn \g__hobby_points_x_array {##1}
+- (\array_get:Nn \g__hobby_distances_array  {##1 - 1})
+* (\array_get:Nn \g__hobby_sigma_array {##1})
+* cos((\array_get:Nn \g__hobby_angles_array  {##1 - 1})
+- (\array_get:Nn \g__hobby_phi_array {##1}))/3
+}, \fp_eval:n {
+  (\array_get:Nn \g__hobby_points_y_array {##1})
+- (\array_get:Nn \g__hobby_distances_array  {##1 - 1})
+* (\array_get:Nn \g__hobby_sigma_array {##1})
+* sin((\array_get:Nn \g__hobby_angles_array  {##1 - 1})
+- (\array_get:Nn \g__hobby_phi_array {##1}))/3
 } }
  }
 }
 \NewDocumentCommand \hobbyinit {m m m} {
-  \hobby_set_cmds:nnn#1#2#3
+  \hobby_set_cmds:NNN #1#2#3
   \hobby_clear_path:
 }
 \NewDocumentCommand \hobbyaddpoint { m } {
-    \keys_set:nn { hobby/read in all }
-    {
-      tension~out,
-      tension~in,
-      excess~angle,
-      blank,
-      break,
-      #1
-    }
-    \tl_if_eq:VnTF {\l_tmpa_tl} {true}
-     {\tl_set:Nn \l_tmpa_tl {2}}
-     {
-       \tl_if_eq:VnTF {\l_tmpa_tl} {soft}
-       {\tl_set:Nn \l_tmpa_tl {0}}
-       {\tl_set:Nn \l_tmpa_tl {1}}
-     }
-    \tl_if_eq:VnTF {\l_tmpb_tl} {true}
-     {\tl_put_right:Nn \l_tmpa_tl {1}}
-     {\tl_put_right:Nn \l_tmpa_tl {0}}
-    \array_gpush:Nx \l_hobby_actions_array {\l_tmpa_tl}
-    \array_gpush:Nx \l_hobby_tension_out_array {\fp_to_tl:N \l_hobby_tempc_fp}
-    \array_gpush:Nx \l_hobby_tension_in_array {\fp_to_tl:N \l_hobby_tempd_fp}
-    \array_gpush:Nx \l_hobby_excess_angle_array {\fp_to_tl:N \l_hobby_temps_fp}
-    \array_gpush:Nx \l_hobby_points_array {
-      x = \fp_use:N \l_hobby_tempa_fp,
-      y = \fp_use:N \l_hobby_tempb_fp }
-    \array_gpush:Nx \l_hobby_points_x_array {\fp_to_tl:N \l_hobby_tempa_fp}
-    \array_gpush:Nx \l_hobby_points_y_array {\fp_to_tl:N \l_hobby_tempb_fp}
+  \keys_set:nn { hobby/read in all }
+  {
+    tension~out,
+    tension~in,
+    excess~angle,
+    blank,
+    break,
+    #1
+  }
+  \tl_if_eq:VnTF \l_tmpa_tl {true}
+  {\tl_set:Nn \l_tmpa_tl {2}}
+  {
+    \tl_if_eq:VnTF \l_tmpa_tl {soft}
+    {\tl_set:Nn \l_tmpa_tl {0}}
+    {\tl_set:Nn \l_tmpa_tl {1}}
+  }
+  \tl_if_eq:VnTF \l_tmpb_tl {true}
+    {\tl_put_right:Nn \l_tmpa_tl {1}}
+    {\tl_put_right:Nn \l_tmpa_tl {0}}
+  \tl_set:Nx \l_hobby_tempa_tl {\fp_use:N \l_hobby_tempa_fp}
+  \tl_set:Nx \l_hobby_tempb_tl {\fp_use:N \l_hobby_tempb_fp}
+  \hobby_add_point:VVVVVV \l_hobby_tempa_tl \l_hobby_tempb_tl \l_hobby_tempc_fp \l_hobby_tempd_fp \l_hobby_temps_fp \l_tmpa_tl
 }
+\cs_new_nopar:Npn \hobby_add_point:nnnnnn #1#2#3#4#5#6
+{
+    \array_gpush:Nn \g__hobby_actions_array { #6 }
+    \array_gpush:Nn \g__hobby_tension_out_array { #3 }
+    \array_gpush:Nn \g__hobby_tension_in_array { #4 }
+    \array_gpush:Nn \g__hobby_excess_angle_array { #5 }
+    \array_gpush:Nn \g__hobby_points_array { #1, #2 }
+    \array_gpush:Nn \g__hobby_points_x_array { #1 }
+    \array_gpush:Nn \g__hobby_points_y_array { #2 }
+}
+\cs_generate_variant:Nn \hobby_add_point:nnnnnn {VVVVVV}
 \NewDocumentCommand \hobbysetparams { m } {
   \keys_set:nn { hobby / read in params }
   {
@@ -709,13 +716,13 @@
 \cs_generate_variant:Nn \hobby_moveto:nnn {VVV,nnV}
 \cs_generate_variant:Nn \hobby_curveto:nnn {VVV}
 \cs_generate_variant:Nn \hobby_close:n {V}
-\cs_new:Nn \hobby_set_cmds:nnn {
+\cs_new:Nn \hobby_set_cmds:NNN {
   \cs_gset_eq:NN \hobby_moveto:nnn #1
   \cs_gset_eq:NN \hobby_curveto:nnn #2
   \cs_gset_eq:NN \hobby_close:n #3
 }
 \NewDocumentCommand \hobbygenpath { } {
-  \array_if_empty:NF \l_hobby_points_array {
+  \array_if_empty:NF \g__hobby_points_array {
     \hobby_gen_path:
   }
 }
@@ -760,7 +767,7 @@
   }
 }
 \NewDocumentCommand \hobbygenusepath { } {
-  \array_if_empty:NF \l_hobby_points_array {
+  \array_if_empty:NF \g__hobby_points_array {
     \hobby_gen_path:
     \hobby_use_path:
   }
@@ -769,70 +776,71 @@
   \hobby_clear_path:
 }
 \tl_new:N \l_tmpc_tl
+\tl_new:N \l_tmpd_tl
 \cs_new:Nn \hobby_use_path: {
-  \bool_if:NT \l_hobby_disjoint_bool {
-    \array_get:NnN \l_hobby_points_array {0} \l_tmpa_tl
+  \bool_if:NT \g__hobby_disjoint_bool {
+    \array_get:NnN \g__hobby_points_array {0} \l_tmpa_tl
     \hobby_moveto:nnV {} {} \l_tmpa_tl
   }
-  \int_step_inline:nnnn {1} {1} {\l_hobby_npoints_int} {
-    \array_get:NnN \l_hobby_controla_array {##1} \l_tmpa_tl
-    \array_get:NnN \l_hobby_controlb_array {##1} \l_tmpb_tl
-    \array_get:NnN \l_hobby_points_array {##1} \l_tmpc_tl
-    \array_get:NnN \l_hobby_actions_array {##1} \l_tmpd_tl
-    \int_compare:nNnTF {\tl_item:Nn \l_tmpd_tl {1}} = {\l_hobby_draw_int} {
+  \int_step_inline:nnnn {1} {1} {\g__hobby_npoints_int} {
+    \array_get:NnN \g__hobby_controla_array {##1} \l_tmpa_tl
+    \array_get:NnN \g__hobby_controlb_array {##1} \l_tmpb_tl
+    \array_get:NnN \g__hobby_points_array {##1} \l_tmpc_tl
+    \array_get:NnN \g__hobby_actions_array {##1} \l_tmpd_tl
+    \int_compare:nNnTF {\tl_item:Nn \l_tmpd_tl {1}} = {\g__hobby_draw_int} {
       \hobby_curveto:VVV \l_tmpa_tl \l_tmpb_tl \l_tmpc_tl
     }{
-      \bool_gset_false:N \l_hobby_closed_bool
+      \bool_gset_false:N \g__hobby_closed_bool
       \hobby_moveto:VVV \l_tmpa_tl \l_tmpb_tl \l_tmpc_tl
     }
     \tl_if_eq:xnTF {\tl_item:Nn \l_tmpd_tl {2}} {1} {
-      \bool_gset_false:N \l_hobby_closed_bool
+      \bool_gset_false:N \g__hobby_closed_bool
       \hobby_moveto:VVV \l_tmpa_tl \l_tmpb_tl \l_tmpc_tl
     }{}
   }
-  \bool_if:NT \l_hobby_closed_bool {
-    \array_get:NnN \l_hobby_points_array {0} \l_tmpa_tl
+  \bool_if:NT \g__hobby_closed_bool {
+    \array_get:NnN \g__hobby_points_array {0} \l_tmpa_tl
     \hobby_close:V \l_tmpa_tl
   }
 }
 \cs_new:Nn \hobby_save_path:n {
   \tl_clear:N \l_tmpa_tl
-  \tl_put_right:Nn \l_tmpa_tl {\int_gset:Nn \l_hobby_npoints_int}
-  \tl_put_right:Nx \l_tmpa_tl {{\int_use:N \l_hobby_npoints_int}}
-  \bool_if:NTF \l_hobby_disjoint_bool {
+  \tl_put_right:Nn \l_tmpa_tl {\int_gset:Nn \g__hobby_npoints_int}
+  \tl_put_right:Nx \l_tmpa_tl {{\int_use:N \g__hobby_npoints_int}}
+  \bool_if:NTF \g__hobby_disjoint_bool {
     \tl_put_right:Nn \l_tmpa_tl {\bool_gset_true:N}
   }{
     \tl_put_right:Nn \l_tmpa_tl {\bool_gset_false:N}
   }
-  \tl_put_right:Nn \l_tmpa_tl {\l_hobby_disjoint_bool}
-  \bool_if:NTF \l_hobby_closed_bool {
+  \tl_put_right:Nn \l_tmpa_tl {\g__hobby_disjoint_bool}
+  \bool_if:NTF \g__hobby_closed_bool {
     \tl_put_right:Nn \l_tmpa_tl {\bool_gset_true:N}
   }{
     \tl_put_right:Nn \l_tmpa_tl {\bool_gset_false:N}
   }
-  \tl_put_right:Nn \l_tmpa_tl {\l_hobby_closed_bool}
-  \tl_put_right:Nn \l_tmpa_tl {\array_gclear:N \l_hobby_points_array}
-  \array_map_inline:Nn \l_hobby_points_array {
+  \tl_put_right:Nn \l_tmpa_tl {\g__hobby_closed_bool}
+  \tl_put_right:Nn \l_tmpa_tl {\array_gclear:N \g__hobby_points_array}
+  \array_map_inline:Nn \g__hobby_points_array {
     \tl_put_right:Nn \l_tmpa_tl {
-      \array_gput:Nnn \l_hobby_points_array {##1} {##2}
+      \array_gput:Nnn \g__hobby_points_array {##1} {##2}
     }
   }
-  \tl_put_right:Nn \l_tmpa_tl {\array_gclear:N \l_hobby_actions_array}
-  \array_map_inline:Nn \l_hobby_actions_array {
+  \tl_put_right:Nn \l_tmpa_tl {\array_gclear:N \g__hobby_actions_array}
+  \array_map_inline:Nn \g__hobby_actions_array {
     \tl_put_right:Nn \l_tmpa_tl {
-      \array_gput:Nnn \l_hobby_actions_array {##1} {##2}
+      \array_gput:Nnn \g__hobby_actions_array {##1} {##2}
     }
   }
-  \tl_put_right:Nn \l_tmpa_tl {\array_gclear:N \l_hobby_controla_array}
-  \array_map_inline:Nn \l_hobby_controla_array {
+  \tl_put_right:Nn \l_tmpa_tl {\array_gclear:N \g__hobby_controla_array}
+  \array_map_inline:Nn \g__hobby_controla_array {
     \tl_put_right:Nn \l_tmpa_tl {
-      \array_gput:Nnn \l_hobby_controla_array {##1} {##2}
+      \array_gput:Nnn \g__hobby_controla_array {##1} {##2}
     }
   }
-  \tl_put_right:Nn \l_tmpa_tl {\array_gclear:N \l_hobby_controlb_array}
-  \array_map_inline:Nn \l_hobby_controlb_array {
+  \tl_put_right:Nn \l_tmpa_tl {\array_gclear:N \g__hobby_controlb_array}
+  \array_map_inline:Nn \g__hobby_controlb_array {
     \tl_put_right:Nn \l_tmpa_tl {
-      \array_gput:Nnn \l_hobby_controlb_array {##1} {##2}
+      \array_gput:Nnn \g__hobby_controlb_array {##1} {##2}
     }
   }
   \tl_gclear_new:c {g_hobby_#1_path}
@@ -871,40 +879,40 @@
 \cs_generate_variant:Nn \hobby_save_path_to_aux:n {x}
 \cs_new:Nn \hobby_clear_path:
 {
-\array_gclear:N \l_hobby_points_array
-\array_gclear:N \l_hobby_points_x_array
-\array_gclear:N \l_hobby_points_y_array
-\array_gclear:N \l_hobby_angles_array
-\array_gclear:N \l_hobby_actions_array
-\array_gclear:N \l_hobby_distances_array
-\array_gclear:N \l_hobby_tension_out_array
-\array_gclear:N \l_hobby_tension_in_array
-\array_gclear:N \l_hobby_excess_angle_array
-\array_gclear:N \l_hobby_matrix_a_array
-\array_gclear:N \l_hobby_matrix_b_array
-\array_gclear:N \l_hobby_matrix_c_array
-\array_gclear:N \l_hobby_matrix_d_array
-\array_gclear:N \l_hobby_vector_u_array
-\array_gclear:N \l_hobby_psi_array
-\array_gclear:N \l_hobby_theta_array
-\array_gclear:N \l_hobby_phi_array
-\array_gclear:N \l_hobby_sigma_array
-\array_gclear:N \l_hobby_rho_array
-\array_gclear:N \l_hobby_controla_array
-\array_gclear:N \l_hobby_controlb_array
-\bool_gset_false:N \l_hobby_closed_bool
-\bool_gset_false:N \l_hobby_disjoint_bool
+\array_gclear:N \g__hobby_points_array
+\array_gclear:N \g__hobby_points_x_array
+\array_gclear:N \g__hobby_points_y_array
+\array_gclear:N \g__hobby_angles_array
+\array_gclear:N \g__hobby_actions_array
+\array_gclear:N \g__hobby_distances_array
+\array_gclear:N \g__hobby_tension_out_array
+\array_gclear:N \g__hobby_tension_in_array
+\array_gclear:N \g__hobby_excess_angle_array
+\array_gclear:N \g__hobby_matrix_a_array
+\array_gclear:N \g__hobby_matrix_b_array
+\array_gclear:N \g__hobby_matrix_c_array
+\array_gclear:N \g__hobby_matrix_d_array
+\array_gclear:N \g__hobby_vector_u_array
+\array_gclear:N \g__hobby_psi_array
+\array_gclear:N \g__hobby_theta_array
+\array_gclear:N \g__hobby_phi_array
+\array_gclear:N \g__hobby_sigma_array
+\array_gclear:N \g__hobby_rho_array
+\array_gclear:N \g__hobby_controla_array
+\array_gclear:N \g__hobby_controlb_array
+\bool_gset_false:N \g__hobby_closed_bool
+\bool_gset_false:N \g__hobby_disjoint_bool
 
-  \int_gset:Nn \l_hobby_npoints_int {-1}
-  \int_gset:Nn \l_hobby_draw_int {1}
-  \fp_gset_eq:NN \l_hobby_in_angle_fp \c_inf_fp
-  \fp_gset_eq:NN \l_hobby_out_angle_fp \c_inf_fp
-  \fp_gset_eq:NN \l_hobby_in_curl_fp \c_one_fp
-  \fp_gset_eq:NN \l_hobby_out_curl_fp \c_one_fp
+  \int_gset:Nn \g__hobby_npoints_int {-1}
+  \int_gset:Nn \g__hobby_draw_int {1}
+  \fp_gset_eq:NN \g__hobby_in_angle_fp \c_inf_fp
+  \fp_gset_eq:NN \g__hobby_out_angle_fp \c_inf_fp
+  \fp_gset_eq:NN \g__hobby_in_curl_fp \c_one_fp
+  \fp_gset_eq:NN \g__hobby_out_curl_fp \c_one_fp
 }
 \ExplSyntaxOff
 %% 
-%% Copyright (C) 2012 by Andrew Stacey <loopspace at mathforge.org>
+%% Copyright (C) 2012-2021 by Andrew Stacey <loopspace at mathforge.org>
 %% 
 %% This file may be distributed and/or modified under the conditions
 %% of the LaTeX Project Public License, either version 1.3 of this
@@ -919,12 +927,13 @@
 %% This work is "maintained" (as per LPPL maintenance status) by
 %% Andrew Stacey.
 %% 
-%% This work consists of the files  hobby.dtx
-%%                                  hobby_doc.tex
+%% This work consists of the files  hobby_code.dtx
+%%                                  hobby.tex
 %% and the derived files            hobby.code.tex
 %%                                  pgflibraryhobby.code.tex
 %%                                  tikzlibraryhobby.code.tex
 %%                                  pml3array.sty
+%%                                  hobby-l3draw.sty
 %%                                  hobby.ins
 %%                                  hobby.pdf
 %%                                  hobby_code.pdf

Modified: trunk/Master/texmf-dist/tex/latex/hobby/pgflibraryhobby.code.tex
===================================================================
--- trunk/Master/texmf-dist/tex/latex/hobby/pgflibraryhobby.code.tex	2023-09-01 14:59:58 UTC (rev 68136)
+++ trunk/Master/texmf-dist/tex/latex/hobby/pgflibraryhobby.code.tex	2023-09-01 21:15:20 UTC (rev 68137)
@@ -4,7 +4,7 @@
 %%
 %% The original source files were:
 %%
-%% hobby.dtx  (with options: `pgflibrary')
+%% hobby_code.dtx  (with options: `pgflibrary')
 %% ----------------------------------------------------------------
 %% hobby --- a TikZ/PGF library for drawing smooth(ish) curves using
 %%           Hobby's algorithm (implemented in LaTeX3)
@@ -17,9 +17,13 @@
 \pgfkeys{
   /pgf/hobby/.is family,
   /pgf/hobby/.cd,
-  x/.code={\pgf at x=#1cm},
-  y/.code={\pgf at y=#1cm}
+  point/.code={%
+    \hobby at parse@pt#1\relax}
 }
+\def\hobby at parse@pt#1,#2\relax{%
+  \pgf at x=#1cm\relax
+  \pgf at y=#2cm\relax
+}
 \pgfmathparse{atan2(0,1)}
 \def\hobby at temp{0.0}
 \ifx\pgfmathresult\hobby at temp
@@ -38,7 +42,7 @@
   \pgfpathmoveto{\hobby at topgf{#3}}%
 }
 \def\hobby at topgf#1{%
-    \pgfqkeys{/pgf/hobby}{#1}%
+    \pgfqkeys{/pgf/hobby}{point={#1}}%
 }
 \def\hobby at close#1{%
   \pgfpathclose
@@ -50,7 +54,7 @@
   \hobbysetparams{#1}%
   \pgfmathsetmacro\hobby at x{\the\pgf at path@lastx/1cm}%
   \pgfmathsetmacro\hobby at y{\the\pgf at path@lasty/1cm}%
-  \hobbyaddpoint{x = \hobby at x, y = \hobby at y}%
+  \hobbyaddpoint{point={\hobby at x, \hobby at y}}%
 }
 \def\pgfpathhobbypt#1{%
   #1%
@@ -59,7 +63,7 @@
   \pgfutil at ifnextchar\bgroup{\pgfpathhobbyptparams}{\pgfpathhobbyptparams{}}%
 }
 \def\pgfpathhobbyptparams#1{%
-  \hobbyaddpoint{#1,x = \hobby at x, y = \hobby at y}%
+  \hobbyaddpoint{#1,point={\hobby at x, \hobby at y}}%
 }
 \def\pgfpathhobbyend{%
   \ifhobby at externalise
@@ -134,7 +138,7 @@
     #1%
     \pgfmathsetmacro\hobby at x{\the\pgf at x/1cm}%
     \pgfmathsetmacro\hobby at y{\the\pgf at y/1cm}%
-    \hobbyaddpoint{x = \hobby at x, y = \hobby at y}%
+    \hobbyaddpoint{point={\hobby at x, \hobby at y}}%
   }
 \def\pgfplothandlerquickhobby{%
   \def\pgf at plotstreamstart{%
@@ -255,7 +259,7 @@
   #4%
 }
 %% 
-%% Copyright (C) 2012 by Andrew Stacey <loopspace at mathforge.org>
+%% Copyright (C) 2012-2021 by Andrew Stacey <loopspace at mathforge.org>
 %% 
 %% This file may be distributed and/or modified under the conditions
 %% of the LaTeX Project Public License, either version 1.3 of this
@@ -270,12 +274,13 @@
 %% This work is "maintained" (as per LPPL maintenance status) by
 %% Andrew Stacey.
 %% 
-%% This work consists of the files  hobby.dtx
-%%                                  hobby_doc.tex
+%% This work consists of the files  hobby_code.dtx
+%%                                  hobby.tex
 %% and the derived files            hobby.code.tex
 %%                                  pgflibraryhobby.code.tex
 %%                                  tikzlibraryhobby.code.tex
 %%                                  pml3array.sty
+%%                                  hobby-l3draw.sty
 %%                                  hobby.ins
 %%                                  hobby.pdf
 %%                                  hobby_code.pdf

Modified: trunk/Master/texmf-dist/tex/latex/hobby/pml3array.sty
===================================================================
--- trunk/Master/texmf-dist/tex/latex/hobby/pml3array.sty	2023-09-01 14:59:58 UTC (rev 68136)
+++ trunk/Master/texmf-dist/tex/latex/hobby/pml3array.sty	2023-09-01 21:15:20 UTC (rev 68137)
@@ -4,7 +4,7 @@
 %%
 %% The original source files were:
 %%
-%% hobby.dtx  (with options: `array')
+%% hobby_code.dtx  (with options: `array')
 %% ----------------------------------------------------------------
 %% hobby --- a TikZ/PGF library for drawing smooth(ish) curves using
 %%           Hobby's algorithm (implemented in LaTeX3)
@@ -20,8 +20,9 @@
 \int_new:N \l_array_base_int
 \int_new:N \l_array_top_int
 \int_new:N \l_array_tmp_int
+\int_new:N \g_array_map_int
 \int_new:N \g_array_base_int
-\int_set:Nn \g_array_base_int {0}
+\int_gset:Nn \g_array_base_int {0}
 \cs_new:Npn \array_adjust_ends:Nn #1#2 {
   \prop_get:NnNTF #1 {base} \l_tmpa_tl
   {
@@ -145,28 +146,28 @@
 \cs_generate_variant:Nn \array_reverse_map_function:NN { c , cc }
 \cs_new_protected:Npn \array_map_inline:Nn #1#2
   {
-    \int_gincr:N \g__prg_map_int
-    \cs_gset:cpn { array_map_inline_ \int_use:N \g__prg_map_int :nn }
+    \int_gincr:N \g_array_map_int
+    \cs_gset:cpn { array_map_inline_ \int_use:N \g_array_map_int :nn }
       ##1##2 {#2}
     \exp_args:NNc \array_map_function:NN #1
-      { array_map_inline_ \int_use:N \g__prg_map_int :nn }
-    \__prg_break_point:Nn \array_map_break: { \int_gdecr:N \g__prg_map_int }
+      { array_map_inline_ \int_use:N \g_array_map_int :nn }
+    \prg_break_point:Nn \array_map_break: { \int_gdecr:N \g_array_map_int }
   }
 \cs_generate_variant:Nn \array_map_inline:Nn { c }
 \cs_new_protected:Npn \array_reverse_map_inline:Nn #1#2
   {
-    \int_gincr:N \g__prg_map_int
-    \cs_gset:cpn { array_map_inline_ \int_use:N \g__prg_map_int :nn }
+    \int_gincr:N \g_array_map_int
+    \cs_gset:cpn { array_map_inline_ \int_use:N \g_array_map_int :nn }
       ##1##2 {#2}
     \exp_args:NNc \array_reverse_map_function:NN #1
-      { array_map_inline_ \int_use:N \g__prg_map_int :nn }
-    \__prg_break_point:Nn \array_map_break: { \int_gdecr:N \g__prg_map_int }
+      { array_map_inline_ \int_use:N \g_array_map_int :nn }
+    \prg_break_point:Nn \array_map_break: { \int_gdecr:N \g_array_map_int }
   }
 \cs_generate_variant:Nn \array_reverse_map_inline:Nn { c }
 \cs_new_nopar:Npn \array_map_break:
-  { \__prg_map_break:Nn \array_map_break: { } }
+  { \prg_map_break:Nn \array_map_break: { } }
 \cs_new_nopar:Npn \array_map_break:n
-  { \__prg_map_break:Nn \array_map_break: }
+  { \prg_map_break:Nn \array_map_break: }
 \msg_new:nnn { kernel } { show-array }
   {
     The~array~\token_to_str:N #1~
@@ -325,7 +326,7 @@
 }
 \cs_new_protected:Npn \array_gdel:Nn #1#2
 {
-  \exp_args:NNx \prop_gpop:Nn #1 {\int_eval:n {#2}}
+  \exp_args:NNx \prop_gremove:Nn #1 {\int_eval:n {#2}}
   \int_set:Nn \l_array_tmp_int {0}
   \array_map_inline:Nn #1 {
     \tl_if_eq:NNTF {##2} {\q_no_value} {}
@@ -374,7 +375,7 @@
 }
 \ExplSyntaxOff
 %% 
-%% Copyright (C) 2012 by Andrew Stacey <loopspace at mathforge.org>
+%% Copyright (C) 2012-2021 by Andrew Stacey <loopspace at mathforge.org>
 %% 
 %% This file may be distributed and/or modified under the conditions
 %% of the LaTeX Project Public License, either version 1.3 of this
@@ -389,12 +390,13 @@
 %% This work is "maintained" (as per LPPL maintenance status) by
 %% Andrew Stacey.
 %% 
-%% This work consists of the files  hobby.dtx
-%%                                  hobby_doc.tex
+%% This work consists of the files  hobby_code.dtx
+%%                                  hobby.tex
 %% and the derived files            hobby.code.tex
 %%                                  pgflibraryhobby.code.tex
 %%                                  tikzlibraryhobby.code.tex
 %%                                  pml3array.sty
+%%                                  hobby-l3draw.sty
 %%                                  hobby.ins
 %%                                  hobby.pdf
 %%                                  hobby_code.pdf

Modified: trunk/Master/texmf-dist/tex/latex/hobby/tikzlibraryhobby.code.tex
===================================================================
--- trunk/Master/texmf-dist/tex/latex/hobby/tikzlibraryhobby.code.tex	2023-09-01 14:59:58 UTC (rev 68136)
+++ trunk/Master/texmf-dist/tex/latex/hobby/tikzlibraryhobby.code.tex	2023-09-01 21:15:20 UTC (rev 68137)
@@ -4,7 +4,7 @@
 %%
 %% The original source files were:
 %%
-%% hobby.dtx  (with options: `tikzlibrary')
+%% hobby_code.dtx  (with options: `tikzlibrary')
 %% ----------------------------------------------------------------
 %% hobby --- a TikZ/PGF library for drawing smooth(ish) curves using
 %%           Hobby's algorithm (implemented in LaTeX3)
@@ -38,7 +38,7 @@
     \expandafter\gdef\expandafter\hobby at point@options\expandafter%
     {\hobby at point@options,tension out=#1}%
   },
-  tension/.code = {%
+  tension/.append code = {%
     \expandafter\gdef\expandafter\hobby at point@options\expandafter%
     {\hobby at point@options,tension=#1}%
   },
@@ -96,7 +96,7 @@
   in curl/.style = {%
     add option to Hobby path={in curl=#1}%
   },
-  out curl/.code = {%
+  out curl/.style = {%
     add option to Hobby path={out curl=#1}%
   },
   use Hobby shortcut/.code={%
@@ -108,7 +108,7 @@
     \global\let\hobby at curveto@delegate=\hobby at qcurveto@auto
   },
   use previous Hobby path/.code={%
-    \pgfextra{\hobbyusepath{#1}}
+    \hobbyusepath{#1}%
   },
   use previous Hobby path/.default={},%
   save Hobby path/.code={%
@@ -115,21 +115,18 @@
     \xdef\hobby at path@name{#1}%
   },
   restore Hobby path/.code={%
-    \pgfextra{%
-      \hobbyinit\hobby at tikz@moveto\hobby at tikz@curveto\hobby at tikz@close
-      \global\let\hobby at collected@onpath\pgfutil at empty
-      \hobbyrestorepath{#1}}
+    \hobbyinit\hobby at tikz@moveto\hobby at tikz@curveto\hobby at tikz@close
+    \global\let\hobby at collected@onpath\pgfutil at empty
+    \hobbyrestorepath{#1}%
   },
   restore and use Hobby path/.code 2 args={%
-    \pgfextra{%
-      \hobbyinit\hobby at tikz@moveto\hobby at tikz@curveto\hobby at tikz@close
-      \global\let\hobby at collected@onpath\pgfutil at empty
-      \hobbyrestorepath{#1}%
-      \hobbyusepath{#2}%
-    }
+    \hobbyinit\hobby at tikz@moveto\hobby at tikz@curveto\hobby at tikz@close
+    \global\let\hobby at collected@onpath\pgfutil at empty
+    \hobbyrestorepath{#1}%
+    \hobbyusepath{#2}%
   },
   show Hobby path/.code={%
-    \pgfextra{\hobbyshowpath{#1}}
+    \hobbyshowpath{#1}%
   },
   Hobby action/.code={%
     \expandafter\gdef\expandafter\hobby at action\expandafter{\hobby at action#1}%
@@ -227,10 +224,10 @@
   \pgfmathsetmacro\hobby at x{\the\pgf at x/1cm}%
   \pgfmathsetmacro\hobby at y{\the\pgf at y/1cm}%
   \ifx\hobby at initial@pt\pgfutil at empty
-    \xdef\hobby at initial@pt{x = \hobby at x, y = \hobby at y}%
+    \xdef\hobby at initial@pt{\hobby at x, \hobby at y}%
   \fi
   \expandafter\hobbyaddpoint\expandafter{\hobby at point@options,%
-    x = \hobby at x, y = \hobby at y}%
+    point={\hobby at x, \hobby at y}}%
   \def\hobby at point@options{}%
   \let\tikz at scan@point at options=\pgfutil at empty
   \pgfutil at ifnextchar\relax{%
@@ -265,10 +262,10 @@
   \let\tikz at collected@onpath=\pgfutil at empty
   \pgfmathsetmacro\hobby at x{\the\tikz at lastx/1cm}%
   \pgfmathsetmacro\hobby at y{\the\tikz at lasty/1cm}%
-  \xdef\hobby at initial@pt{x = \hobby at x, y = \hobby at y}%
+  \xdef\hobby at initial@pt{\hobby at x, \hobby at y}%
   \expandafter\hobbysetparams\expandafter{\hobby at next@opts}%
   \expandafter\hobbyaddpoint\expandafter{\hobby at point@options,%
-      x = \hobby at x, y = \hobby at y}%
+      point={\hobby at x, \hobby at y} }%
   \hobby at init@tikz at commands
   \tikzset{designated Hobby path=this}%
   \let\tikz at scan@point at options=\pgfutil at empty
@@ -285,7 +282,7 @@
   \pgfmathsetmacro\hobby at y{\the\pgf at y/1cm}%
   \expandafter\hobbysetparams\expandafter{\hobby at this@opts}%
   \expandafter\hobbyaddpoint\expandafter{\hobby at point@options,%
-    x = \hobby at x, y = \hobby at y}%
+    point={\hobby at x, \hobby at y}}%
   \hobby at action
   \global\let\hobby at this@opts=\pgfutil at empty
   \global\let\hobby at action=\pgfutil at empty
@@ -583,7 +580,7 @@
 }
 
 %% 
-%% Copyright (C) 2012 by Andrew Stacey <loopspace at mathforge.org>
+%% Copyright (C) 2012-2021 by Andrew Stacey <loopspace at mathforge.org>
 %% 
 %% This file may be distributed and/or modified under the conditions
 %% of the LaTeX Project Public License, either version 1.3 of this
@@ -598,12 +595,13 @@
 %% This work is "maintained" (as per LPPL maintenance status) by
 %% Andrew Stacey.
 %% 
-%% This work consists of the files  hobby.dtx
-%%                                  hobby_doc.tex
+%% This work consists of the files  hobby_code.dtx
+%%                                  hobby.tex
 %% and the derived files            hobby.code.tex
 %%                                  pgflibraryhobby.code.tex
 %%                                  tikzlibraryhobby.code.tex
 %%                                  pml3array.sty
+%%                                  hobby-l3draw.sty
 %%                                  hobby.ins
 %%                                  hobby.pdf
 %%                                  hobby_code.pdf

Modified: trunk/Master/tlpkg/libexec/ctan2tds
===================================================================
--- trunk/Master/tlpkg/libexec/ctan2tds	2023-09-01 14:59:58 UTC (rev 68136)
+++ trunk/Master/tlpkg/libexec/ctan2tds	2023-09-01 21:15:20 UTC (rev 68137)
@@ -3229,6 +3229,7 @@
  'hecthese'	=> 'etex',
  'hf-tikz'      => 'tex',
  'hitszbeamer'	=> 'tex',
+ 'hobby'	=> 'tex',
  'hologo'	=> 'tex',
  'hopatch'	=> 'tex',
  'hrlatex'      => 'latex',  # requires interaction



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