% \iffalse meta-comment
%
%% File: l3draw-softpath.dtx
%
% Copyright (C) 2018-2024 The LaTeX Project
%
% It may be distributed and/or modified under the conditions of the
% LaTeX Project Public License (LPPL), either version 1.3c of this
% license or (at your option) any later version.  The latest version
% of this license is in the file
%
%    http://www.latex-project.org/lppl.txt
%
% This file is part of the "l3experimental bundle" (The Work in LPPL)
% and all files in that bundle must be distributed together.
%
% -----------------------------------------------------------------------
%
% The development version of the bundle can be found at
%
%    https://github.com/latex3/latex3
%
% for those people who are interested.
%
%<*driver>
\RequirePackage{expl3}
\documentclass[full]{l3doc}
\begin{document}
  \DocInput{\jobname.dtx}
\end{document}
%</driver>
% \fi
%
% \title{^^A
%   The \pkg{l3draw-softpath} package\\ Soft paths^^A
% }
%
% \author{^^A
%  The \LaTeX{} Project\thanks
%    {^^A
%      E-mail:
%        \href{mailto:latex-team@latex-project.org}
%          {latex-team@latex-project.org}^^A
%    }^^A
% }
%
% \date{Released 2024-03-14}
%
% \maketitle
%
% \begin{implementation}
%
% \section{\pkg{l3draw-softpath} implementation}
%
%    \begin{macrocode}
%<*package>
%    \end{macrocode}
%
%    \begin{macrocode}
%<@@=draw>
%    \end{macrocode}
%
% \subsection{Managing soft paths}
%
% There are two linked aims in the code here. The most significant is to
% provide a way to modify paths, for example to shorten the ends or round
% the corners.  This means that the path cannot be written piecemeal as
% specials, but rather needs to be held in macros. The second aspect that
% follows from this is performance: simply adding to a single macro a piece
% at a time will have poor performance as the list gets long so we use
% \cs[no-index]{tl_build_\ldots{}} functions.
%
% Each marker (operation) token takes two arguments, which makes processing
% more straight-forward. As such, some operations have dummy arguments, whilst
% others have to be split over several tokens. As the code here is at a low
% level, all dimension arguments are assumed to be explicit and fully-expanded.
%
% \begin{variable}{\g_@@_softpath_main_tl}
%   The soft path itself.
%    \begin{macrocode}
\tl_new:N \g_@@_softpath_main_tl
%    \end{macrocode}
% \end{variable}
%
% \begin{variable}{\l_@@_softpath_tmp_tl}
%   Scratch space.
%    \begin{macrocode}
\tl_new:N \l_@@_softpath_tmp_tl
%    \end{macrocode}
% \end{variable}
%
% \begin{variable}{\g_@@_softpath_corners_bool}
%   Allow for optimised path use.
%    \begin{macrocode}
\bool_new:N \g_@@_softpath_corners_bool
%    \end{macrocode}
% \end{variable}
%
% \begin{macro}{\@@_softpath_add:n, \@@_softpath_add:o, \@@_softpath_add:e}
%    \begin{macrocode}
\cs_new_protected:Npn \@@_softpath_add:n
  { \tl_build_gput_right:Nn \g_@@_softpath_main_tl }
\cs_generate_variant:Nn \@@_softpath_add:n { o, e }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_softpath_use:, \@@_softpath_clear:}
%   Using and clearing is trivial.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_softpath_use:
  {
    \tl_build_get_intermediate:NN
      \g_@@_softpath_main_tl
      \l_@@_softpath_tmp_tl
    \l_@@_softpath_tmp_tl
  }
\cs_new_protected:Npn \@@_softpath_clear:
  {
    \tl_build_gbegin:N \g_@@_softpath_main_tl
    \bool_gset_false:N \g_@@_softpath_corners_bool
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_softpath_save:, \@@_softpath_restore:}
%   Abstracted ideas to keep variables inside this submodule.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_softpath_save:
  {
    \tl_build_gend:N \g_@@_softpath_main_tl
    \tl_set_eq:NN
      \l_@@_softpath_main_tl
      \g_@@_softpath_main_tl
    \bool_set_eq:NN
      \l_@@_softpath_corners_bool
      \g_@@_softpath_corners_bool
    \@@_softpath_clear:
  }
\cs_new_protected:Npn \@@_softpath_restore:
  {
    \@@_softpath_clear:
    \@@_softpath_add:o \l_@@_softpath_main_tl
    \bool_gset_eq:NN
      \g_@@_softpath_corners_bool
      \l_@@_softpath_corners_bool
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{variable}{\g_@@_softpath_lastx_dim, \g_@@_softpath_lasty_dim}
%   For tracking the end of the path (to close it).
%    \begin{macrocode}
\dim_new:N \g_@@_softpath_lastx_dim
\dim_new:N \g_@@_softpath_lasty_dim
%    \end{macrocode}
% \end{variable}
%
% \begin{variable}{\g_@@_softpath_move_bool}
%   Track if moving a point should update the close position.
%    \begin{macrocode}
\bool_new:N \g_@@_softpath_move_bool
\bool_gset_true:N \g_@@_softpath_move_bool
%    \end{macrocode}
% \end{variable}
%
% \begin{macro}{\@@_softpath_closepath:}
% \begin{macro}{\@@_softpath_curveto:nnnnnn}
% \begin{macro}
%   {
%     \@@_softpath_lineto:nn,
%     \@@_softpath_moveto:nn
%   }
% \begin{macro}{\@@_softpath_rectangle:nnnn}
% \begin{macro}{\@@_softpath_roundpoint:nn, \@@_softpath_roundpoint:VV}
%   The various parts of a path expressed as the appropriate soft path
%   functions.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_softpath_closepath:
  {
    \@@_softpath_add:e
      {
        \@@_softpath_close_op:nn
          { \dim_use:N \g_@@_softpath_lastx_dim }
          { \dim_use:N \g_@@_softpath_lasty_dim }
      }
  }
\cs_new_protected:Npn \@@_softpath_curveto:nnnnnn #1#2#3#4#5#6
  {
    \@@_softpath_add:n
      {
        \@@_softpath_curveto_opi:nn {#1} {#2}
        \@@_softpath_curveto_opii:nn {#3} {#4}
        \@@_softpath_curveto_opiii:nn {#5} {#6}
      }
  }
\cs_new_protected:Npn \@@_softpath_lineto:nn #1#2
  {
    \@@_softpath_add:n
      { \@@_softpath_lineto_op:nn {#1} {#2} }
  }
\cs_new_protected:Npn \@@_softpath_moveto:nn #1#2
  {
    \@@_softpath_add:n
      { \@@_softpath_moveto_op:nn {#1} {#2} }
    \bool_if:NT \g_@@_softpath_move_bool
      {
        \dim_gset:Nn \g_@@_softpath_lastx_dim {#1}
        \dim_gset:Nn \g_@@_softpath_lasty_dim {#2}
      }
  }
\cs_new_protected:Npn \@@_softpath_rectangle:nnnn #1#2#3#4
  {
    \@@_softpath_add:n
      {
        \@@_softpath_rectangle_opi:nn {#1} {#2}
        \@@_softpath_rectangle_opii:nn {#3} {#4}
      }
  }
\cs_new_protected:Npn \@@_softpath_roundpoint:nn #1#2
  {
    \@@_softpath_add:n
      { \@@_softpath_roundpoint_op:nn {#1} {#2} }
    \bool_gset_true:N \g_@@_softpath_corners_bool
  }
\cs_generate_variant:Nn \@@_softpath_roundpoint:nn { VV }
%    \end{macrocode}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
%
% \begin{macro}
%   {
%     \@@_softpath_close_op:nn      ,
%     \@@_softpath_curveto_opi:nn   ,
%     \@@_softpath_curveto_opii:nn  ,
%     \@@_softpath_curveto_opiii:nn ,
%     \@@_softpath_lineto_op:nn     ,
%     \@@_softpath_moveto_op:nn     ,
%     \@@_softpath_roundpoint_op:nn ,
%     \@@_softpath_rectangle_opi:nn ,
%     \@@_softpath_rectangle_opii:nn
%   }
% \begin{macro}{\@@_softpath_curveto_opi:nnNnnNnn}
% \begin{macro}{\@@_softpath_rectangle_opi:nnNnn}
%   The markers for operations: all the top-level ones take two arguments.
%   The support tokens for curves have to be different in meaning to a
%   round point, hence being quark-like.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_softpath_close_op:nn #1#2
  { \@@_backend_closepath: }
\cs_new_protected:Npn \@@_softpath_curveto_opi:nn #1#2 
  { \@@_softpath_curveto_opi:nnNnnNnn {#1} {#2} }
\cs_new_protected:Npn \@@_softpath_curveto_opi:nnNnnNnn #1#2#3#4#5#6#7#8
  { \@@_backend_curveto:nnnnnn {#1} {#2} {#4} {#5} {#7} {#8} }
\cs_new_protected:Npn \@@_softpath_curveto_opii:nn #1#2
  { \@@_softpath_curveto_opii:nn }
\cs_new_protected:Npn \@@_softpath_curveto_opiii:nn #1#2
  { \@@_softpath_curveto_opiii:nn }
\cs_new_protected:Npn \@@_softpath_lineto_op:nn #1#2
  { \@@_backend_lineto:nn {#1} {#2} }
\cs_new_protected:Npn \@@_softpath_moveto_op:nn #1#2
  { \@@_backend_moveto:nn {#1} {#2} }
\cs_new_protected:Npn \@@_softpath_roundpoint_op:nn #1#2
  { \@@_softpath_roundpoint_op:nn }
\cs_new_protected:Npn \@@_softpath_rectangle_opi:nn #1#2 
  { \@@_softpath_rectangle_opi:nnNnn {#1} {#2} }
\cs_new_protected:Npn \@@_softpath_rectangle_opi:nnNnn #1#2#3#4#5
  { \@@_backend_rectangle:nnnn {#1} {#2} {#4} {#5} }
\cs_new_protected:Npn \@@_softpath_rectangle_opii:nn #1#2
  { \@@_softpath_rectangle_opii:nn }
%    \end{macrocode}
% \end{macro}
% \end{macro}
% \end{macro}
%
% \subsection{Rounding soft path corners}
%
% The aim here is to find corner rounding points and to replace them with
% arcs of appropriate length. The approach is exactly that in \pkg{pgf}:
% step through, find the corners, find the supporting data, do the rounding.
%
% \begin{variable}{\l_@@_softpath_main_tl}
%   For constructing the updated path.
%    \begin{macrocode}
\tl_new:N \l_@@_softpath_main_tl
%    \end{macrocode}
% \end{variable}
%
% \begin{variable}{\l_@@_softpath_part_tl}
%   Data structures.
%    \begin{macrocode}
\tl_new:N \l_@@_softpath_part_tl
\tl_new:N \l_@@_softpath_curve_end_tl
%    \end{macrocode}
% \end{variable}
%
% \begin{variable}
%   {\l_@@_softpath_lastx_fp, \l_@@_softpath_lasty_fp}
% \begin{variable}
%   {\l_@@_softpath_corneri_dim, \l_@@_softpath_cornerii_dim}
% \begin{variable}{\l_@@_softpath_first_tl, \l_@@_softpath_move_tl}
%   Position tracking: the token list data may be entirely empty or set to
%   a co-ordinate.
%    \begin{macrocode}
\fp_new:N \l_@@_softpath_lastx_fp
\fp_new:N \l_@@_softpath_lasty_fp
\dim_new:N \l_@@_softpath_corneri_dim
\dim_new:N \l_@@_softpath_cornerii_dim
\tl_new:N \l_@@_softpath_first_tl
\tl_new:N \l_@@_softpath_move_tl
%    \end{macrocode}
% \end{variable}
% \end{variable}
% \end{variable}
%
% \begin{variable}{\c_@@_softpath_arc_fp}
%   The magic constant.
%    \begin{macrocode}
\fp_const:Nn \c_@@_softpath_arc_fp { 4/3 * (sqrt(2) - 1) }
%    \end{macrocode}
% \end{variable}
%
% \begin{macro}{\@@_softpath_round_corners:}
% \begin{macro}{\@@_softpath_round_loop:Nnn}
% \begin{macro}{\@@_softpath_round_action:nn}
% \begin{macro}{\@@_softpath_round_action:Nnn}
% \begin{macro}{\@@_softpath_round_action_curveto:NnnNnn}
% \begin{macro}{\@@_softpath_round_action_close:}
% \begin{macro}{\@@_softpath_round_lookahead:NnnNnn}
% \begin{macro}{\@@_softpath_round_roundpoint:NnnNnnNnn}
% \begin{macro}{\@@_softpath_round_calc:NnnNnn}
% \begin{macro}[EXP]
%   {\@@_softpath_round_calc:nnnnnn, \@@_softpath_round_calc:eVnnnn}
% \begin{macro}[EXP]{\@@_softpath_round_calc:nnnnw}
% \begin{macro}{\@@_softpath_round_close:nn}
% \begin{macro}[EXP]{\@@_softpath_round_close:w}
% \begin{macro}{\@@_softpath_round_end:}
%   Rounding corners on a path means going through the entire path and
%   adjusting it. As such, we avoid this entirely if we know there are no
%   corners to deal with. Assuming there is work to do, we recover the existing
%   path and start a loop.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_softpath_round_corners:
  {
    \bool_if:NT \g_@@_softpath_corners_bool
      {
        \group_begin:
          \tl_clear:N \l_@@_softpath_main_tl
          \tl_clear:N \l_@@_softpath_part_tl
          \fp_zero:N \l_@@_softpath_lastx_fp
          \fp_zero:N \l_@@_softpath_lasty_fp
          \tl_clear:N \l_@@_softpath_first_tl
          \tl_clear:N \l_@@_softpath_move_tl
          \tl_build_gend:N \g_@@_softpath_main_tl
          \exp_after:wN \@@_softpath_round_loop:Nnn
            \g_@@_softpath_main_tl
            \q_@@_recursion_tail ? ?
            \q_@@_recursion_stop
        \group_end:
      }
    \bool_gset_false:N \g_@@_softpath_corners_bool
  }
%    \end{macrocode}
%   The loop can take advantage of the fact that all soft path operations are
%   made up of a token followed by two arguments. At this stage, there is
%   a simple split: have we round a round point. If so, is there any actual
%   rounding to be done: if the arcs have come through zero, just ignore it.
%   In cases where we are not at a corner, we simply move along the path,
%   allowing for any new part starting due to a \texttt{moveto}.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_softpath_round_loop:Nnn #1#2#3
  {
    \@@_if_recursion_tail_stop_do:Nn #1 { \@@_softpath_round_end: }
    \token_if_eq_meaning:NNTF #1 \@@_softpath_roundpoint_op:nn
      { \@@_softpath_round_action:nn {#2} {#3} }
      {
        \tl_if_empty:NT \l_@@_softpath_first_tl
          { \tl_set:Nn \l_@@_softpath_first_tl { {#2} {#3} } }
        \fp_set:Nn \l_@@_softpath_lastx_fp {#2}
        \fp_set:Nn \l_@@_softpath_lasty_fp {#3}
        \token_if_eq_meaning:NNTF #1 \@@_softpath_moveto_op:nn
          {
            \tl_put_right:No \l_@@_softpath_main_tl
              \l_@@_softpath_move_tl
            \tl_put_right:No \l_@@_softpath_main_tl
              \l_@@_softpath_part_tl
            \tl_set:Nn \l_@@_softpath_move_tl { #1 {#2} {#3} }
            \tl_clear:N \l_@@_softpath_first_tl
            \tl_clear:N \l_@@_softpath_part_tl
          }
          { \tl_put_right:Nn \l_@@_softpath_part_tl { #1 {#2} {#3} } }
        \@@_softpath_round_loop:Nnn
      }
  }
\cs_new_protected:Npn \@@_softpath_round_action:nn #1#2
  {
    \dim_set:Nn \l_@@_softpath_corneri_dim {#1}
    \dim_set:Nn \l_@@_softpath_cornerii_dim {#2}
    \bool_lazy_and:nnTF
      { \dim_compare_p:nNn \l_@@_softpath_corneri_dim = { 0pt } }
      { \dim_compare_p:nNn \l_@@_softpath_cornerii_dim = { 0pt } }
      { \@@_softpath_round_loop:Nnn }
      { \@@_softpath_round_action:Nnn } 
  }
%    \end{macrocode}
%   We now have a round point to work on and have grabbed the next item in
%   the path. There are only a few cases where we have to do anything. Each of
%   them is picked up by looking for the appropriate action.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_softpath_round_action:Nnn #1#2#3
  {
    \tl_if_empty:NT \l_@@_softpath_first_tl
      { \tl_set:Nn \l_@@_softpath_first_tl { {#2} {#3} } }
    \token_if_eq_meaning:NNTF #1 \@@_softpath_curveto_opi:nn
      { \@@_softpath_round_action_curveto:NnnNnn }
      {
        \token_if_eq_meaning:NNTF #1 \@@_softpath_close_op:nn
          { \@@_softpath_round_action_close: }
          {
            \token_if_eq_meaning:NNTF #1 \@@_softpath_lineto_op:nn
              { \@@_softpath_round_lookahead:NnnNnn }
              { \@@_softpath_round_loop:Nnn }
          }
      }
      #1 {#2} {#3}
  }
%    \end{macrocode}
%   For a curve, we collect the two control points then move on to grab the
%   end point and add the curve there: the second control point becomes our
%   starter.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_softpath_round_action_curveto:NnnNnn
  #1#2#3#4#5#6
  {
    \tl_put_right:Nn \l_@@_softpath_part_tl
      { #1 {#2} {#3} #4 {#5} {#6} }
    \fp_set:Nn \l_@@_softpath_lastx_fp {#5}
    \fp_set:Nn \l_@@_softpath_lasty_fp {#6}
    \@@_softpath_round_lookahead:NnnNnn
  }
\cs_new_protected:Npn \@@_softpath_round_action_close:
  {
    \bool_lazy_and:nnTF
      { ! \tl_if_empty_p:N \l_@@_softpath_first_tl }
      { ! \tl_if_empty_p:N \l_@@_softpath_move_tl }
      {
        \exp_after:wN \@@_softpath_round_close:nn
          \l_@@_softpath_first_tl
      }
      { \@@_softpath_round_loop:Nnn }
  }
%    \end{macrocode}
%   At this stage we have a current (sub)operation (|#1|) and the next
%   operation (|#4|), and can therefore decide whether to round or not.
%   In the case of yet another rounding marker, we have to look a bit
%   further ahead.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_softpath_round_lookahead:NnnNnn #1#2#3#4#5#6
  {
    \bool_lazy_any:nTF
      {
        { \token_if_eq_meaning_p:NN #4 \@@_softpath_lineto_op:nn }
        { \token_if_eq_meaning_p:NN #4 \@@_softpath_curveto_opi:nn }
        { \token_if_eq_meaning_p:NN #4 \@@_softpath_close_op:nn }
      }
      {
        \@@_softpath_round_calc:NnnNnn
          \@@_softpath_round_loop:Nnn
          {#5} {#6}
      }
      {
        \token_if_eq_meaning:NNTF #4 \@@_softpath_roundpoint_op:nn
          { \@@_softpath_round_roundpoint:NnnNnnNnn }
          { \@@_softpath_round_loop:Nnn }
      }
    #1 {#2} {#3}
    #4 {#5} {#6}
  }
\cs_new_protected:Npn \@@_softpath_round_roundpoint:NnnNnnNnn
  #1#2#3#4#5#6#7#8#9
  {
    \@@_softpath_round_calc:NnnNnn
      \@@_softpath_round_loop:Nnn
      {#8} {#9}
      #1 {#2} {#3}
    #4 {#5} {#6} #7 {#8} {#9}
  }
%    \end{macrocode}
%   We now have all of the data needed to construct a rounded corner: all that
%   is left to do is to work out the detail! At this stage, we have details
%   of where the corner itself is (|#5|, |#6|), and where the next point is
%   (|#2|, |#3|). There are two types of calculations to do. First, we
%   need to interpolate from those two points in the direction of the
%   corner, in order to work out where the curve we are adding will start
%   and end. From those, plus the points we already have, we work out where
%   the control points will lie.  All of this is done in an expansion to
%   avoid multiple calls to |\tl_put_right:Ne|. The end point of the line
%   is worked out up-front and saved: we need that if dealing with a
%   close-path operation.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_softpath_round_calc:NnnNnn #1#2#3#4#5#6
  {
    \tl_set:Ne \l_@@_softpath_curve_end_tl
      {
        \draw_point_interpolate_distance:nnn
          \l_@@_softpath_cornerii_dim
          { #5 , #6 } { #2 , #3 }
      }
    \tl_put_right:Ne \l_@@_softpath_part_tl
      {
        \exp_not:N #4
        \@@_softpath_round_calc:eVnnnn
          {
            \draw_point_interpolate_distance:nnn
              \l_@@_softpath_corneri_dim
              { #5 , #6 }
              {
                \l_@@_softpath_lastx_fp ,
                \l_@@_softpath_lasty_fp
              }
          }
          \l_@@_softpath_curve_end_tl
          {#5} {#6} {#2} {#3}
      }
    \fp_set:Nn \l_@@_softpath_lastx_fp {#5}
    \fp_set:Nn \l_@@_softpath_lasty_fp {#6}
    #1
  }
%    \end{macrocode}
%   At this stage we have the two curve end points, but they are in
%   co-ordinate form. So we split them up (with some more reordering).
%    \begin{macrocode}
\cs_new:Npn \@@_softpath_round_calc:nnnnnn #1#2#3#4#5#6
  {
    \@@_softpath_round_calc:nnnnw {#3} {#4} {#5} {#6}
      #1 \s_@@_mark #2 \s_@@_stop
  }
\cs_generate_variant:Nn \@@_softpath_round_calc:nnnnnn { eV }
%    \end{macrocode}
%   The calculations themselves are relatively straight-forward, as we use a
%   quadratic Bézier curve.
%    \begin{macrocode}
\cs_new:Npn \@@_softpath_round_calc:nnnnw
  #1#2#3#4 #5 , #6 \s_@@_mark #7 , #8 \s_@@_stop
  {
    {#5} {#6}
    \exp_not:N \@@_softpath_curveto_opi:nn
      {
        \fp_to_dim:n
          { #5 + \c_@@_softpath_arc_fp * ( #1 - #5 ) }
      }
      {
        \fp_to_dim:n
          { #6 + \c_@@_softpath_arc_fp * ( #2 - #6 ) }
      }
    \exp_not:N \@@_softpath_curveto_opii:nn
      {
        \fp_to_dim:n
          { #7 + \c_@@_softpath_arc_fp * ( #1 - #7 ) }
      }
      {
        \fp_to_dim:n
          { #8 + \c_@@_softpath_arc_fp* ( #2 - #8 ) }
      }
    \exp_not:N \@@_softpath_curveto_opiii:nn
      {#7} {#8}
  }
%    \end{macrocode}
%   To deal with a close-path operation, we need to do some manipulation.
%   It needs to be treated as a line operation for rounding, and then
%   have the close path operation re-added at the point where the curve
%   ends. That means saving the end point in the calculation step (see
%   earlier), and shuffling a lot.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_softpath_round_close:nn #1#2
  {
    \use:e
      {
        \@@_softpath_round_calc:NnnNnn
          {
            \tl_set:Ne \exp_not:N \l_@@_softpath_move_tl
              {
                \@@_softpath_moveto_op:nn
                \exp_not:N \exp_after:wN
                  \exp_not:N \@@_softpath_round_close:w
                  \exp_not:N \l_@@_softpath_curve_end_tl
                    \s_@@_stop
              }
            \use:e
              {
                \exp_not:N \exp_not:N \exp_not:N \use_i:nnnn
                  {
                    \@@_softpath_round_loop:Nnn
                      \@@_softpath_close_op:nn
                      \exp_not:N \exp_after:wN
                        \exp_not:N \@@_softpath_round_close:w
                        \exp_not:N \l_@@_softpath_curve_end_tl
                          \s_@@_stop
                  }
              }
          }
          {#1} {#2}
          \@@_softpath_lineto_op:nn
          \exp_after:wN \use_none:n \l_@@_softpath_move_tl
      }
  }
\cs_new:Npn \@@_softpath_round_close:w #1 , #2 \s_@@_stop { {#1} {#2} }
%    \end{macrocode}
%   Tidy up the parts of the path, complete the built token list and put
%   it back into action. 
%    \begin{macrocode}
\cs_new_protected:Npn \@@_softpath_round_end:
  {
    \tl_put_right:No \l_@@_softpath_main_tl
      \l_@@_softpath_move_tl
    \tl_put_right:No \l_@@_softpath_main_tl
      \l_@@_softpath_part_tl
    \tl_build_gbegin:N \g_@@_softpath_main_tl
    \@@_softpath_add:o \l_@@_softpath_main_tl
  }
%    \end{macrocode}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
%
%    \begin{macrocode}
%</package>
%    \end{macrocode}
%
% \end{implementation}
%
% \PrintIndex