% \iffalse meta-comment
%
%% File: l3draw-paths.dtx
%
% Copyright (C) 2018-2026 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-paths} package\\ Drawing 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 2026-06-18}
%
% \maketitle
%
% \begin{implementation}
%
% \section{\pkg{l3draw-paths} implementation}
%
%    \begin{macrocode}
%<*package>
%    \end{macrocode}
%
%    \begin{macrocode}
%<@@=draw>
%    \end{macrocode}
%
% This sub-module covers more-or-less the same ideas as
% \texttt{pgfcorepathconstruct.code.tex}, though using the expandable FPU
% means that the implementation often varies. At present, equivalents of the
% following are currently absent:
% \begin{itemize}
%   \item \cs{pgfpatharcto}, \cs{pgfpatharctoprecomputed}: These are
%     extremely specialized and are very complex in implementation. If the
%     functionality is required, it is likely that it will be set up from
%     scratch here.
%   \item \cs{pgfpathparabola}: Seems to be unused other than defining
%     a Ti\emph{k}Z interface, which itself is then not used further.
%   \item \cs{pgfpathsine}, \cs{pgfpathcosine}: Need to see exactly how
%     these need to work, in particular whether a wider input range is
%     needed and what approximation to make.
%    \item \cs{pgfpathcurvebetweentime}, \cs{pgfpathcurvebetweentimecontinue}:
%      These don't seem to be used at all.
% \end{itemize}
%
% \begin{variable}
%   {\l_@@_path_tmp_tl, \l_@@_path_tmpa_fp, \l_@@_path_tmpb_fp}
%   Scratch space.
%    \begin{macrocode}
\tl_new:N \l_@@_path_tmp_tl
\fp_new:N \l_@@_path_tmpa_fp
\fp_new:N \l_@@_path_tmpb_fp
%    \end{macrocode}
% \end{variable}
%
% \subsection{Tracking paths}
%
% \begin{variable}{\g_@@_path_lastx_dim, \g_@@_path_lasty_dim}
%   The last point visited on a path.
%    \begin{macrocode}
\dim_new:N \g_@@_path_lastx_dim
\dim_new:N \g_@@_path_lasty_dim
%    \end{macrocode}
% \end{variable}
%
% \begin{variable}
%   {
%     \g_@@_path_xmax_dim,
%     \g_@@_path_xmin_dim,
%     \g_@@_path_ymax_dim,
%     \g_@@_path_ymin_dim
%   }
%   The limiting size of a path.
%    \begin{macrocode}
\dim_new:N \g_@@_path_xmax_dim
\dim_new:N \g_@@_path_xmin_dim
\dim_new:N \g_@@_path_ymax_dim
\dim_new:N \g_@@_path_ymin_dim
%    \end{macrocode}
% \end{variable}
%
% \begin{macro}{\@@_path_update_limits:nn}
% \begin{macro}{\@@_path_reset_limits:}
%   Track the limits of a path and (perhaps) of the picture as a whole.
%   (At present the latter is always true: that will change as more complex
%   functionality is added.)
%    \begin{macrocode}
\cs_new_protected:Npn \@@_path_update_limits:nn #1#2
  {
    \dim_gset:Nn \g_@@_path_xmax_dim
      { \dim_max:nn \g_@@_path_xmax_dim {#1} }
    \dim_gset:Nn \g_@@_path_xmin_dim
      { \dim_min:nn \g_@@_path_xmin_dim {#1} }
    \dim_gset:Nn \g_@@_path_ymax_dim
      { \dim_max:nn \g_@@_path_ymax_dim {#2} }
    \dim_gset:Nn \g_@@_path_ymin_dim
      { \dim_min:nn \g_@@_path_ymin_dim {#2} }
    \bool_if:NT \l_draw_bb_update_bool
      {
        \dim_gset:Nn \g_draw_bb_xmax_dim
          { \dim_max:nn \g_draw_bb_xmax_dim {#1} }
        \dim_gset:Nn \g_draw_bb_xmin_dim
          { \dim_min:nn \g_draw_bb_xmin_dim {#1} }
        \dim_gset:Nn \g_draw_bb_ymax_dim
          { \dim_max:nn \g_draw_bb_ymax_dim {#2} }
        \dim_gset:Nn \g_draw_bb_ymin_dim
          { \dim_min:nn \g_draw_bb_ymin_dim {#2} }
      }
  }
\cs_new_protected:Npn \@@_path_reset_limits:
  {
    \dim_gset:Nn \g_@@_path_xmax_dim { -\c_max_dim }
    \dim_gset:Nn \g_@@_path_xmin_dim {  \c_max_dim }
    \dim_gset:Nn \g_@@_path_ymax_dim { -\c_max_dim }
    \dim_gset:Nn \g_@@_path_ymin_dim {  \c_max_dim }
  }
%    \end{macrocode}
% \end{macro}
% \end{macro}
%
% \begin{macro}{\@@_path_update_last:nn}
%   A simple auxiliary to avoid repetition.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_path_update_last:nn #1#2
  {
    \dim_gset:Nn \g_@@_path_lastx_dim {#1}
    \dim_gset:Nn \g_@@_path_lasty_dim {#2}
  }
%    \end{macrocode}
% \end{macro}
%
% \subsection{Corner arcs}
%
% At the level of path \emph{construction}, rounded corners are handled
% by inserting a marker into the path: that is then picked up once the
% full path is constructed. Thus we need to set up the appropriate
% data structures here, such that this can be applied every time it is
% relevant.
%
% \begin{variable}{\l_@@_corner_xarc_dim, \l_@@_corner_yarc_dim}
%   The two arcs in use.
%    \begin{macrocode}
\dim_new:N \l_@@_corner_xarc_dim
\dim_new:N \l_@@_corner_yarc_dim
%    \end{macrocode}
% \end{variable}
%
% \begin{variable}{\l_@@_corner_arc_bool}
%   A flag to speed up the repeated checks.
%    \begin{macrocode}
\bool_new:N \l_@@_corner_arc_bool
%    \end{macrocode}
% \end{variable}
%
% \begin{macro}{\draw_path_corner_arc:nn}
%   Calculate the arcs, check they are non-zero.
%    \begin{macrocode}
\cs_new_protected:Npn \draw_path_corner_arc:nn #1#2
  {
    \dim_set:Nn \l_@@_corner_xarc_dim { \fp_to_dim:n {#1} }
    \dim_set:Nn \l_@@_corner_yarc_dim { \fp_to_dim:n {#2} }
    \bool_lazy_and:nnTF
      { \dim_compare_p:nNn \l_@@_corner_xarc_dim = { 0pt } }
      { \dim_compare_p:nNn \l_@@_corner_yarc_dim = { 0pt } }
      { \bool_set_false:N \l_@@_corner_arc_bool }
      { \bool_set_true:N \l_@@_corner_arc_bool }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_path_mark_corner:}
%   Mark up corners for arc post-processing.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_path_mark_corner:
  {
    \bool_if:NT \l_@@_corner_arc_bool
      {
        \@@_softpath_roundpoint:VV
          \l_@@_corner_xarc_dim
          \l_@@_corner_yarc_dim
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \subsection{Basic path constructions}
%
% \begin{macro}{\draw_path_moveto:n, \draw_path_lineto:n}
% \begin{macro}{\@@_path_moveto:nn, \@@_path_lineto:nn}
% \begin{macro}{\draw_path_curveto:nnn}
% \begin{macro}{\@@_path_curveto:nnnnnn}
%   At present, stick to purely linear transformation support and skip the
%   soft path business: that will likely need to be revisited later.
%    \begin{macrocode}
\cs_new_protected:Npn \draw_path_moveto:n #1
  {
    \@@_point_process:nn
      { \@@_path_moveto:nn }
      { \draw_point_transform:n {#1} }
  }
\cs_new_protected:Npn \@@_path_moveto:nn #1#2
  {
    \@@_path_update_limits:nn {#1} {#2}
    \@@_softpath_moveto:nn {#1} {#2}
    \@@_path_update_last:nn {#1} {#2}
  }
\cs_new_protected:Npn \draw_path_lineto:n #1
  {
    \@@_point_process:nn
      { \@@_path_lineto:nn }
      { \draw_point_transform:n {#1} }
  }
\cs_new_protected:Npn \@@_path_lineto:nn #1#2
  {
    \@@_path_mark_corner:
    \@@_path_update_limits:nn {#1} {#2}
    \@@_softpath_lineto:nn {#1} {#2}
    \@@_path_update_last:nn {#1} {#2}
  }
\cs_new_protected:Npn \draw_path_curveto:nnn #1#2#3
  {
    \@@_point_process:nnnn
      {
        \@@_path_mark_corner:
        \@@_path_curveto:nnnnnn
      }
      { \draw_point_transform:n {#1} }
      { \draw_point_transform:n {#2} }
      { \draw_point_transform:n {#3} }
  }
\cs_new_protected:Npn \@@_path_curveto:nnnnnn #1#2#3#4#5#6
  {
    \@@_path_update_limits:nn {#1} {#2}
    \@@_path_update_limits:nn {#3} {#4}
    \@@_path_update_limits:nn {#5} {#6}
    \@@_softpath_curveto:nnnnnn {#1} {#2} {#3} {#4} {#5} {#6}
    \@@_path_update_last:nn {#5} {#6}
  }
%    \end{macrocode}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
%
% \begin{macro}{\draw_path_close:}
%   A simple wrapper.
%    \begin{macrocode}
\cs_new_protected:Npn \draw_path_close:
  {
    \@@_path_mark_corner:
    \@@_softpath_closepath:
  }
%    \end{macrocode}
% \end{macro}
%
% \subsection{Accessing path information}
%
% \begin{macro}[EXP]{\draw_path_lastx:, \draw_path_lasty:}
%   Wrappers so the underlying data are read-only for users.
%    \begin{macrocode}
\cs_new:Npn \draw_path_lastx: { \dim_use:N \g_@@_path_lastx_dim }
\cs_new:Npn \draw_path_lasty: { \dim_use:N \g_@@_path_lasty_dim }
%    \end{macrocode}
% \end{macro}
%
% \subsection{Canvas path constructions}
%
% \begin{macro}{\draw_path_canvas_moveto:n, \draw_path_canvas_lineto:n}
% \begin{macro}{\draw_path_canvas_curveto:nnn}
%   Operations with no application of the transformation matrix.
%    \begin{macrocode}
\cs_new_protected:Npn \draw_path_canvas_moveto:n #1
  { \@@_point_process:nn { \@@_path_moveto:nn } { \draw_point:n {#1} } }
\cs_new_protected:Npn \draw_path_canvas_lineto:n #1
  { \@@_point_process:nn { \@@_path_lineto:nn } { \draw_point:n {#1} } }
\cs_new_protected:Npn \draw_path_canvas_curveto:nnn #1#2#3
  {
    \@@_point_process:nnnn
      {
        \@@_path_mark_corner:
        \@@_path_curveto:nnnnnn
      }
      { \draw_point:n {#1} }
      { \draw_point:n {#2} }
      { \draw_point:n {#3} }
  }
%    \end{macrocode}
% \end{macro}
% \end{macro}
%
% \subsection{Computed curves}
%
% More complex operations need some calculations. To assist with those, various
% constants are pre-defined.
%
% \begin{macro}{\draw_path_curveto:nn}
% \begin{macro}{\@@_path_curveto:nnnn}
% \begin{variable}{\c_@@_path_curveto_a_fp, \c_@@_path_curveto_b_fp}
%   A quadratic curve with one control point $(x_{\mathrm{c}},
%   y_{\mathrm{c}})$. The two required control points are then
%   \[
%     x_{1} = \frac{1}{3}x_{\mathrm{s}} + \frac{2}{3}x_{\mathrm{c}}
%     \quad
%     y_{1} = \frac{1}{3}y_{\mathrm{s}} + \frac{2}{3}y_{\mathrm{c}}
%   \]
%   and
%   \[
%     x_{2} = \frac{1}{3}x_{\mathrm{e}} + \frac{2}{3}x_{\mathrm{c}}
%     \quad
%     x_{2} = \frac{1}{3}y_{\mathrm{e}} + \frac{2}{3}y_{\mathrm{c}}
%   \]
%   using the start (last) point $(x_{\mathrm{s}}, y_{\mathrm{s}})$
%   and the end point $(x_{\mathrm{s}}, y_{\mathrm{s}})$.
%    \begin{macrocode}
\cs_new_protected:Npn \draw_path_curveto:nn #1#2
  {
    \@@_point_process:nnn
      { \@@_path_curveto:nnnn }
      { \draw_point_transform:n {#1} }
      { \draw_point_transform:n {#2} }
  }
\cs_new_protected:Npn \@@_path_curveto:nnnn #1#2#3#4
  {
    \fp_set:Nn \l_@@_path_tmpa_fp { \c_@@_path_curveto_b_fp * #1 }
    \fp_set:Nn \l_@@_path_tmpb_fp { \c_@@_path_curveto_b_fp * #2 }
    \use:e
      {
        \@@_path_mark_corner:
        \@@_path_curveto:nnnnnn
          {
            \fp_to_dim:n
              {
                  \c_@@_path_curveto_a_fp * \g_@@_path_lastx_dim
                + \l_@@_path_tmpa_fp
              }
          }
          {
            \fp_to_dim:n
              {
                  \c_@@_path_curveto_a_fp * \g_@@_path_lasty_dim
                + \l_@@_path_tmpb_fp
              }
          }
          {
            \fp_to_dim:n
              { \c_@@_path_curveto_a_fp * #3 + \l_@@_path_tmpa_fp }
          }
          {
            \fp_to_dim:n
              { \c_@@_path_curveto_a_fp * #4 + \l_@@_path_tmpb_fp }
          }
          {#3}
          {#4}
      }
  }
\fp_const:Nn \c_@@_path_curveto_a_fp { 1 / 3 }
\fp_const:Nn \c_@@_path_curveto_b_fp { 2 / 3 }
%    \end{macrocode}
% \end{variable}
% \end{macro}
% \end{macro}
%
% \begin{macro}{\draw_path_arc:nnn}
% \begin{macro}{\draw_path_arc:nnnn}
% \begin{macro}{\@@_path_arc:nnnn}
% \begin{macro}{\@@_path_arc:nnNnn}
% \begin{macro}
%   {
%     \@@_path_arc_auxi:nnnnNnn,
%     \@@_path_arc_auxi:enenNnn,
%     \@@_path_arc_auxi:eennNnn
%   }
% \begin{macro}{\@@_path_arc_auxii:nnnNnnnn}
% \begin{macro}{\@@_path_arc_auxiii:nn}
% \begin{macro}{\@@_path_arc_auxiv:nnnn}
% \begin{macro}{\@@_path_arc_auxv:nn, \@@_path_arc_auxvi:nn}
% \begin{macro}{\@@_path_arc_add:nnnn}
% \begin{variable}{\l_@@_path_arc_delta_fp, \l_@@_path_arc_start_fp}
% \begin{variable}{\c_@@_path_arc_90_fp,\c_@@_path_arc_60_fp}
%   Drawing an arc means dividing the total curve required into sections:
%   using Bézier curves we can cover at most $90^{\circ}$ at once. To allow
%   for later manipulations, we aim to have roughly equal last segments to
%   the line, with the split set at a final part of $115^{\circ}$.
%    \begin{macrocode}
\cs_new_protected:Npn \draw_path_arc:nnn #1#2#3
  { \draw_path_arc:nnnn {#1} {#1} {#2} {#3} }
\cs_new_protected:Npn \draw_path_arc:nnnn #1#2#3#4
  {
    \use:e
      {
        \@@_path_arc:nnnn
          { \fp_eval:n {#3} }
          { \fp_eval:n {#4} }
          { \fp_to_dim:n {#1} }
          { \fp_to_dim:n {#2} }
      }
  }
\cs_new_protected:Npn \@@_path_arc:nnnn #1#2#3#4
  {
    \fp_compare:nNnTF {#1} > {#2}
      { \@@_path_arc:nnNnn {#1} {#2} - {#3} {#4} }
      { \@@_path_arc:nnNnn {#1} {#2} + {#3} {#4} }
  }
\cs_new_protected:Npn \@@_path_arc:nnNnn #1#2#3#4#5
  {
    \fp_set:Nn \l_@@_path_arc_start_fp {#1}
    \fp_set:Nn \l_@@_path_arc_delta_fp { abs( #1 - #2 ) }
    \fp_while_do:nNnn { \l_@@_path_arc_delta_fp } > { 90 }
      {
        \fp_compare:nNnTF \l_@@_path_arc_delta_fp > { 115 }
          {
            \@@_path_arc_auxi:eennNnn
              { \fp_to_decimal:N \l_@@_path_arc_start_fp }
              { \fp_eval:n { \l_@@_path_arc_start_fp #3 90 } }
              { 90 } {#2}
              #3 {#4} {#5}
          }
          {
            \@@_path_arc_auxi:eennNnn
              { \fp_to_decimal:N \l_@@_path_arc_start_fp }
              { \fp_eval:n { \l_@@_path_arc_start_fp #3 60 } }
              { 60 } {#2}
              #3 {#4} {#5}
          }
      }
    \@@_path_mark_corner:
    \@@_path_arc_auxi:enenNnn
      { \fp_to_decimal:N \l_@@_path_arc_start_fp }
      {#2}
      { \fp_eval:n { abs( \l_@@_path_arc_start_fp - #2 ) } }
      {#2}
      #3 {#4} {#5}
  }
%    \end{macrocode}
%  The auxiliary is responsible for calculating the required points.
%  The \enquote{magic} number required to determine the length of the
%  control vectors is well-established for a right-angle:
%  $\frac{4}{3}(\sqrt{2} - 1) = 0.552\,284\,75$. For other cases, we follow
%  the calculation used by \pkg{pgf} but with the second common case of
%  $60^{\circ}$ pre-calculated for speed.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_path_arc_auxi:nnnnNnn #1#2#3#4#5#6#7
  {
    \use:e
      {
        \@@_path_arc_auxii:nnnNnnnn
          {#1} {#2} {#4} #5 {#6} {#7}
          {
            \fp_to_dim:n
              {
                \cs_if_exist_use:cF
                  { c_@@_path_arc_ #3 _fp }
                  { 4/3 * tand( 0.25 * #3 ) }
                  * #6
              }
          }
          {
            \fp_to_dim:n
              {
                \cs_if_exist_use:cF
                  { c_@@_path_arc_ #3 _fp }
                  { 4/3 * tand( 0.25 * #3 ) }
                  * #7
              }
          }
      }
  }
\cs_generate_variant:Nn \@@_path_arc_auxi:nnnnNnn { ene , ee }
%    \end{macrocode}
%   We can now calculate the required points. As everything here is
%   non-expandable, that is best done by using \texttt{e}-type expansion
%   to build up the tokens. The three points are calculated out-of-order,
%   since finding the second control point needs the position of the end
%   point. Once the points are found, fire-off the fundamental path
%   operation and update the record of where we are up to. The final
%   point has to be
%    \begin{macrocode}
\cs_new_protected:Npn \@@_path_arc_auxii:nnnNnnnn #1#2#3#4#5#6#7#8
  {
    \tl_clear:N \l_@@_path_tmp_tl
    \@@_point_process:nn
      { \@@_path_arc_auxiii:nn }
      {
        \@@_point_transform_noshift:n
          { \draw_point_polar:nnn {#7} {#8} { #1 #4 90 } }
      }
    \@@_point_process:nnn
      { \@@_path_arc_auxiv:nnnn }
      {
        \draw_point_transform:n
          { \draw_point_polar:nnn {#5} {#6} {#1} }
      }
      {
        \draw_point_transform:n
          { \draw_point_polar:nnn {#5} {#6} {#2} }
      }
    \@@_point_process:nn
      { \@@_path_arc_auxv:nn }
      {
        \@@_point_transform_noshift:n
          { \draw_point_polar:nnn {#7} {#8} { #2 #4 -90 } }
      }
    \exp_after:wN \@@_path_curveto:nnnnnn \l_@@_path_tmp_tl
    \fp_set:Nn \l_@@_path_arc_delta_fp { abs ( #2 - #3 ) }
    \fp_set:Nn \l_@@_path_arc_start_fp {#2}
  }
%    \end{macrocode}
%   The first control point.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_path_arc_auxiii:nn #1#2
  {
    \@@_path_arc_aux_add:nn
      { \g_@@_path_lastx_dim + #1 }
      { \g_@@_path_lasty_dim + #2 }
  }
%    \end{macrocode}
%   The end point: simple arithmetic.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_path_arc_auxiv:nnnn #1#2#3#4
  {
    \@@_path_arc_aux_add:nn
      { \g_@@_path_lastx_dim - #1 + #3 }
      { \g_@@_path_lasty_dim - #2 + #4 }
  }
%    \end{macrocode}
%   The second control point: extract the last point, do some
%   rearrangement and record.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_path_arc_auxv:nn #1#2
  {
    \exp_after:wN \@@_path_arc_auxvi:nn
      \l_@@_path_tmp_tl {#1} {#2}
  }
\cs_new_protected:Npn \@@_path_arc_auxvi:nn #1#2#3#4#5#6
  {
    \tl_set:Nn \l_@@_path_tmp_tl { {#1} {#2} }
    \@@_path_arc_aux_add:nn
      { #5 + #3 }
      { #6 + #4 }
    \tl_put_right:Nn \l_@@_path_tmp_tl { {#3} {#4} }
  }
\cs_new_protected:Npn \@@_path_arc_aux_add:nn #1#2
  {
    \tl_put_right:Ne \l_@@_path_tmp_tl
      { { \fp_to_dim:n {#1} } { \fp_to_dim:n {#2} } }
  }
\fp_new:N \l_@@_path_arc_delta_fp
\fp_new:N \l_@@_path_arc_start_fp
\fp_const:cn { c_@@_path_arc_90_fp } { 4/3 * (sqrt(2) - 1) }
\fp_const:cn { c_@@_path_arc_60_fp } { 4/3 * tand(15) }
%    \end{macrocode}
% \end{variable}
% \end{variable}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
%
% \begin{macro}{\draw_path_arc_axes:nnnn}
%   A simple wrapper.
%    \begin{macrocode}
\cs_new_protected:Npn \draw_path_arc_axes:nnnn #1#2#3#4
  {
    \group_begin:
      \draw_transform_triangle:nnn { 0cm , 0cm } {#1} {#2}
      \draw_path_arc:nnn { 1pt } {#3} {#4}
    \group_end:
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\draw_path_ellipse:nnn}
% \begin{macro}{\@@_path_ellipse:nnnnnn}
% \begin{macro}[EXP]
%   {
%     \@@_path_ellipse_arci:nnnnnn   ,
%     \@@_path_ellipse_arcii:nnnnnn  ,
%     \@@_path_ellipse_arciii:nnnnnn ,
%     \@@_path_ellipse_arciv:nnnnnn
%   }
%  \begin{variable}{\c_@@_path_ellipse_fp}
%   Drawing an ellipse is an optimized version of drawing an arc, in particular
%   reusing the same constant. We need to deal with the ellipse in four parts
%   and also deal with moving to the right place, closing it and ending up
%   back at the center. That is handled on a per-arc basis, each in a
%   separate auxiliary for readability.
%    \begin{macrocode}
\cs_new_protected:Npn \draw_path_ellipse:nnn #1#2#3
  {
    \@@_point_process:nnnn
      { \@@_path_ellipse:nnnnnn }
      { \draw_point_transform:n {#1} }
      { \@@_point_transform_noshift:n {#2} }
      { \@@_point_transform_noshift:n {#3} }
  }
\cs_new_protected:Npn \@@_path_ellipse:nnnnnn #1#2#3#4#5#6
  {
    \use:e
      {
        \@@_path_moveto:nn
          { \fp_to_dim:n { #1 + #3 } } { \fp_to_dim:n { #2 + #4 } }
        \@@_path_ellipse_arci:nnnnnn   {#1} {#2} {#3} {#4} {#5} {#6}
        \@@_path_ellipse_arcii:nnnnnn  {#1} {#2} {#3} {#4} {#5} {#6}
        \@@_path_ellipse_arciii:nnnnnn {#1} {#2} {#3} {#4} {#5} {#6}
        \@@_path_ellipse_arciv:nnnnnn  {#1} {#2} {#3} {#4} {#5} {#6}
      }
    \@@_softpath_closepath:
    \@@_path_moveto:nn {#1} {#2}
  }
\cs_new:Npn \@@_path_ellipse_arci:nnnnnn #1#2#3#4#5#6
  {
    \@@_path_curveto:nnnnnn
      { \fp_to_dim:n { #1 + #3 + #5 * \c_@@_path_ellipse_fp } }
      { \fp_to_dim:n { #2 + #4 + #6 * \c_@@_path_ellipse_fp } }
      { \fp_to_dim:n { #1 + #3 * \c_@@_path_ellipse_fp + #5 } }
      { \fp_to_dim:n { #2 + #4 * \c_@@_path_ellipse_fp + #6 } }
      { \fp_to_dim:n { #1 + #5 } }
      { \fp_to_dim:n { #2 + #6 } }
  }
\cs_new:Npn \@@_path_ellipse_arcii:nnnnnn #1#2#3#4#5#6
  {
    \@@_path_curveto:nnnnnn
      { \fp_to_dim:n { #1 - #3 * \c_@@_path_ellipse_fp + #5 } }
      { \fp_to_dim:n { #2 - #4 * \c_@@_path_ellipse_fp + #6 } }
      { \fp_to_dim:n { #1 - #3 + #5 * \c_@@_path_ellipse_fp } }
      { \fp_to_dim:n { #2 - #4 + #6 * \c_@@_path_ellipse_fp } }
      { \fp_to_dim:n { #1 - #3 } }
      { \fp_to_dim:n { #2 - #4 } }
  }
\cs_new:Npn \@@_path_ellipse_arciii:nnnnnn #1#2#3#4#5#6
  {
    \@@_path_curveto:nnnnnn
      { \fp_to_dim:n { #1 - #3 - #5 * \c_@@_path_ellipse_fp } }
      { \fp_to_dim:n { #2 - #4 - #6 * \c_@@_path_ellipse_fp } }
      { \fp_to_dim:n { #1 - #3 * \c_@@_path_ellipse_fp - #5 } }
      { \fp_to_dim:n { #2 - #4 * \c_@@_path_ellipse_fp - #6 } }
      { \fp_to_dim:n { #1 - #5 } }
      { \fp_to_dim:n { #2 - #6 } }
  }
\cs_new:Npn \@@_path_ellipse_arciv:nnnnnn #1#2#3#4#5#6
  {
    \@@_path_curveto:nnnnnn
      { \fp_to_dim:n { #1 + #3 * \c_@@_path_ellipse_fp - #5 } }
      { \fp_to_dim:n { #2 + #4 * \c_@@_path_ellipse_fp - #6 } }
      { \fp_to_dim:n { #1 + #3 - #5 * \c_@@_path_ellipse_fp } }
      { \fp_to_dim:n { #2 + #4 - #6 * \c_@@_path_ellipse_fp } }
      { \fp_to_dim:n { #1 + #3 } }
      { \fp_to_dim:n { #2 + #4 } }
  }
\fp_const:Nn \c_@@_path_ellipse_fp { \fp_use:c { c_@@_path_arc_90_fp } }
%    \end{macrocode}
% \end{variable}
% \end{macro}
% \end{macro}
% \end{macro}
%
% \begin{macro}{\draw_path_circle:nn}
%   A shortcut.
%    \begin{macrocode}
\cs_new_protected:Npn \draw_path_circle:nn #1#2
  { \draw_path_ellipse:nnn {#1} { #2 , 0pt } { 0pt , #2 } }
%    \end{macrocode}
% \end{macro}
%
% \subsection{Rectangles}
%
% \begin{macro}{\draw_path_rectangle:nn}
% \begin{macro}{\@@_path_rectangle:nnnn, \@@_path_rectangle_rounded:nnnn}
%   Building a rectangle can be a single operation, or for rounded versions will
%   involve step-by-step construction.
%    \begin{macrocode}
\cs_new_protected:Npn \draw_path_rectangle:nn #1#2
  {
    \bool_lazy_or:nnTF
      { \l_@@_corner_arc_bool }
      { \l_@@_matrix_active_bool }
      {
        \@@_point_process:nnn \@@_path_rectangle_rounded:nnnn
          { \draw_point:n {#1} }
          { \draw_point:n {#2} }
      }
      {
        \@@_point_process:nnn \@@_path_rectangle:nnnn
          { \draw_point:n { (#1) + ( \l_@@_xshift_dim , \l_@@_yshift_dim ) } }
          { \draw_point:n {#2} }
      }
  }
\cs_new_protected:Npn \@@_path_rectangle:nnnn #1#2#3#4
  {
    \@@_path_update_limits:nn {#1} {#2}
    \@@_path_update_limits:nn { #1 + #3 } { #2 + #4 }
    \@@_softpath_rectangle:nnnn {#1} {#2} {#3} {#4}
    \@@_path_update_last:nn {#1} {#2}
  }
\cs_new_protected:Npn \@@_path_rectangle_rounded:nnnn #1#2#3#4
  {
    \draw_path_moveto:n { #1 + #3 , #2 + #4 }
    \draw_path_lineto:n { #1 , #2 + #4 }
    \draw_path_lineto:n { #1 , #2 }
    \draw_path_lineto:n { #1 + #3 , #2 }
    \draw_path_close:
    \draw_path_moveto:n { #1 , #2 }
  }
%    \end{macrocode}
% \end{macro}
% \end{macro}
%
% \begin{macro}{\draw_path_rectangle_corners:nn}
% \begin{macro}{\@@_path_rectangle_corners:nnnn}
%   Another shortcut wrapper.
%    \begin{macrocode}
\cs_new_protected:Npn \draw_path_rectangle_corners:nn #1#2
  {
    \@@_point_process:nnn
      { \@@_path_rectangle_corners:nnnnn {#1} }
      { \draw_point:n {#1} }
      { \draw_point:n {#2} }
  }
\cs_new_protected:Npn \@@_path_rectangle_corners:nnnnn #1#2#3#4#5
  { \draw_path_rectangle:nn {#1} { #4 - #2 , #5 - #3 } }
%    \end{macrocode}
% \end{macro}
% \end{macro}
%
% \subsection{Grids}
%
% \begin{macro}{\draw_path_grid:nnnn}
% \begin{macro}
%   {
%     \@@_path_grid_auxi:nnnnnn, \@@_path_grid_auxi:eennnn,
%     \@@_path_grid_auxii:nnnnnn,
%     \@@_path_grid_auxiii:nnnnnn, \@@_path_grid_auxiiii:eennnn
%   }
% \begin{macro}
%   {\@@_path_grid_auxiv:nnnnnnnn, \@@_path_grid_auxiv:eennnnnn}
%   The main complexity here is lining up the grid correctly.
%   To keep it simple, we tidy up the argument ordering first.
%    \begin{macrocode}
\cs_new_protected:Npn \draw_path_grid:nnnn #1#2#3#4
  {
    \@@_point_process:nnn
      {
        \@@_path_grid_auxi:eennnn
          { \dim_abs:n {#1} }
          { \dim_abs:n {#2} }
      }
      { \draw_point:n {#3} }
      { \draw_point:n {#4} }
  }
\cs_new_protected:Npn \@@_path_grid_auxi:nnnnnn #1#2#3#4#5#6
  {
    \dim_compare:nNnTF {#3} > {#5}
      { \@@_path_grid_auxii:nnnnnn {#1} {#2} {#5} {#4} {#3} {#6} }
      { \@@_path_grid_auxii:nnnnnn {#1} {#2} {#3} {#4} {#5} {#6} }
  }
\cs_generate_variant:Nn \@@_path_grid_auxi:nnnnnn { ee }
\cs_new_protected:Npn \@@_path_grid_auxii:nnnnnn #1#2#3#4#5#6
  {
    \dim_compare:nNnTF {#4} > {#6}
      { \@@_path_grid_auxiii:nnnnnn {#1} {#2} {#3} {#6} {#5} {#4} }
      { \@@_path_grid_auxiii:nnnnnn {#1} {#2} {#3} {#4} {#5} {#6} }
  }
\cs_new_protected:Npn \@@_path_grid_auxiii:nnnnnn #1#2#3#4#5#6
  {
    \@@_path_grid_auxiv:eennnnnn
      { \fp_to_dim:n { #1 * ceil(#3/(#1)) } }
      { \fp_to_dim:n { #2 * ceil(#4/(#2)) } }
      {#1} {#2} {#3} {#4} {#5} {#6}
  }
\cs_new_protected:Npn \@@_path_grid_auxiv:nnnnnnnn #1#2#3#4#5#6#7#8
  {
    \dim_step_inline:nnnn
      {#1}
      {#3}
      {#7}
      {
        \draw_path_moveto:n { ##1 , #6 }
        \draw_path_lineto:n { ##1 , #8 }
      }
    \dim_step_inline:nnnn
      {#2}
      {#4}
      {#8}
      {
        \draw_path_moveto:n { #5 , ##1 }
        \draw_path_lineto:n { #7 , ##1 }
      }
  }
\cs_generate_variant:Nn \@@_path_grid_auxiv:nnnnnnnn { ee }
%    \end{macrocode}
% \end{macro}
% \end{macro}
% \end{macro}
%
% \subsection{Using paths}
%
% \begin{variable}
%   {
%     \l_@@_path_use_clip_bool  ,
%     \l_@@_path_use_fill_bool  ,
%     \l_@@_path_use_stroke_bool
%   }
%   Actions to pass to the driver.
%    \begin{macrocode}
\bool_new:N \l_@@_path_use_clip_bool
\bool_new:N \l_@@_path_use_fill_bool
\bool_new:N \l_@@_path_use_stroke_bool
%    \end{macrocode}
% \end{variable}
%
% \begin{variable}{\l_@@_path_use_clear_bool}
%   Actions handled at the macro layer.
%    \begin{macrocode}
\bool_new:N \l_@@_path_use_clear_bool
%    \end{macrocode}
% \end{variable}
%
% \begin{macro}{\draw_path_use:n, \draw_path_use_clear:n}
% \begin{macro}{\draw_path_replace_bb:}
% \begin{macro}{\@@_path_replace_bb:NnN}
% \begin{macro}{\@@_path_use:n}
% \begin{macro}{\@@_path_use_action_draw:, \@@_path_use_action_fillstroke:}
% \begin{macro}{\@@_path_use_stroke_bb:}  
% \begin{macro}{\@@_path_use_bb:NnN}
%   There are a range of actions which can apply to a path: they are handled
%   in a single function which can carry out several of them. The first step
%   is to deal with the special case of clearing the path.
%    \begin{macrocode}
\cs_new_protected:Npn \draw_path_use:n #1
  {
    \tl_if_blank:nF {#1}
      { \@@_path_use:n {#1} }
  }
\cs_new_protected:Npn \draw_path_use_clear:n #1
  {
    \bool_lazy_or:nnTF
      { \tl_if_blank_p:n {#1} }
      { \str_if_eq_p:nn {#1} { clear } }
      {
        \@@_softpath_clear:
        \@@_path_reset_limits:
      }
      { \@@_path_use:n { #1 , clear } }
  }
\cs_new_protected:Npn \draw_path_replace_bb:
  {
    \@@_path_replace_bb:NnN x { max } +
    \@@_path_replace_bb:NnN y { max } +
    \@@_path_replace_bb:NnN x { min } -
    \@@_path_replace_bb:NnN y { min } -
    \@@_softpath_clear:
    \@@_path_reset_limits:
  }
\cs_new_protected:Npn \@@_path_replace_bb:NnN #1#2#3
  {
    \dim_gset:cn { g_draw_bb_ #1#2 _dim }
      {
          \dim_use:c { g_@@_path_ #1#2 _dim }
        #3 0.5 \g_@@_linewidth_dim
      }
  }
%    \end{macrocode}
%   Map over the actions and set up the data: mainly just booleans,
%   but with the possibility to cover more complex cases. The business end
%   of the function is a series of checks on the various flags, then
%   taking the appropriate action(s).
%    \begin{macrocode}
\cs_new_protected:Npn \@@_path_use:n #1
  {
    \bool_set_false:N \l_@@_path_use_clip_bool
    \bool_set_false:N \l_@@_path_use_fill_bool
    \bool_set_false:N \l_@@_path_use_stroke_bool
    \clist_map_inline:nn {#1}
      {
        \cs_if_exist:cTF { l_@@_path_use_ ##1 _ bool }
          { \bool_set_true:c { l_@@_path_use_ ##1 _ bool } }
          {
            \cs_if_exist_use:cF { @@_path_use_action_ ##1 : }
              { \msg_error:nnn { draw } { invalid-path-action } {##1} }
          }
      }
    \@@_softpath_round_corners:
    \bool_lazy_and:nnT
      { \l_draw_bb_update_bool }
      { \l_@@_path_use_stroke_bool }
      { \@@_path_use_stroke_bb: }
    \@@_softpath_use:
    \bool_if:NT \l_@@_path_use_clip_bool
      {
        \@@_backend_clip:
        \bool_set_false:N \l_draw_bb_update_bool
        \bool_lazy_or:nnF
          { \l_@@_path_use_fill_bool }
          { \l_@@_path_use_stroke_bool }
          { \@@_backend_discardpath: }
      }
    \bool_lazy_or:nnT
      { \l_@@_path_use_fill_bool }
      { \l_@@_path_use_stroke_bool }
      {
        \use:c
          {
            @@_backend_
            \bool_if:NT \l_@@_path_use_fill_bool { fill }
            \bool_if:NT \l_@@_path_use_stroke_bool { stroke }
            :
          }
      }
    \bool_if:NT \l_@@_path_use_clear_bool
      {
        \@@_softpath_clear:
        \@@_path_reset_limits:
      }
  }
\cs_new_protected:Npn \@@_path_use_action_draw:
  {
    \bool_set_true:N \l_@@_path_use_stroke_bool
  }
\cs_new_protected:Npn \@@_path_use_action_fillstroke:
  {
    \bool_set_true:N \l_@@_path_use_fill_bool
    \bool_set_true:N \l_@@_path_use_stroke_bool
  }
%    \end{macrocode}
%   Where the path is relevant to size and is stroked, we need to allow for
%   the part which overlaps the edge of the bounding box.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_path_use_stroke_bb:
  {
    \@@_path_use_bb:NnN x { max } +
    \@@_path_use_bb:NnN y { max } +
    \@@_path_use_bb:NnN x { min } -
    \@@_path_use_bb:NnN y { min } -
  }
\cs_new_protected:Npn \@@_path_use_bb:NnN #1#2#3
  {
    \dim_compare:nNnF { \dim_use:c { g_draw_bb_ #1#2 _dim } } = { #3 -\c_max_dim }
      {
        \dim_gset:cn { g_draw_bb_ #1#2 _dim }
          {
            \use:c { dim_ #2 :nn }
              { \dim_use:c { g_draw_bb_ #1#2 _dim } }
              {
                  \dim_use:c { g_@@_path_ #1#2 _dim }
                #3 0.5 \g_@@_linewidth_dim
              } 
          }
      }
  }
%    \end{macrocode}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
%
% \subsection{Scoping paths}
%
% \begin{variable}
%   {
%     \l_@@_path_lastx_dim, \l_@@_path_lasty_dim,
%     \l_@@_path_xmax_dim, \l_@@_path_xmin_dim,
%     \l_@@_path_ymax_dim, \l_@@_path_ymin_dim
%   }
% \begin{variable}{\l_@@_softpath_corners_bool}
%   Local storage for global data. There is already a
%   \cs{l_@@_softpath_main_tl} for path manipulation, so we can reuse that
%   (it is always grouped when the path is being reconstructed).
%    \begin{macrocode}
\dim_new:N \l_@@_path_lastx_dim
\dim_new:N \l_@@_path_lasty_dim
\dim_new:N \l_@@_path_xmax_dim
\dim_new:N \l_@@_path_xmin_dim
\dim_new:N \l_@@_path_ymax_dim
\dim_new:N \l_@@_path_ymin_dim
\dim_new:N \l_@@_softpath_lastx_dim
\dim_new:N \l_@@_softpath_lasty_dim
\bool_new:N \l_@@_softpath_corners_bool
%    \end{macrocode}
% \end{variable}
% \end{variable}
%
% \begin{macro}{\draw_path_scope_begin:, \draw_path_scope_end:}
%   Scoping a path is a bit more involved, largely as there are a number
%   of variables to keep hold of.
%    \begin{macrocode}
\cs_new_protected:Npn \draw_path_scope_begin:
  {
    \group_begin:
      \dim_set_eq:NN \l_@@_path_lastx_dim \g_@@_path_lastx_dim
      \dim_set_eq:NN \l_@@_path_lasty_dim \g_@@_path_lasty_dim
      \dim_set_eq:NN \l_@@_path_xmax_dim \g_@@_path_xmax_dim
      \dim_set_eq:NN \l_@@_path_xmin_dim \g_@@_path_xmin_dim
      \dim_set_eq:NN \l_@@_path_ymax_dim \g_@@_path_ymax_dim
      \dim_set_eq:NN \l_@@_path_ymin_dim \g_@@_path_ymin_dim
      \dim_set_eq:NN \l_@@_softpath_lastx_dim \g_@@_softpath_lastx_dim
      \dim_set_eq:NN \l_@@_softpath_lasty_dim \g_@@_softpath_lasty_dim
      \@@_path_reset_limits:
      \@@_softpath_save:
  }
\cs_new_protected:Npn \draw_path_scope_end:
  {
      \@@_softpath_restore:
      \dim_gset_eq:NN \g_@@_softpath_lastx_dim \l_@@_softpath_lastx_dim
      \dim_gset_eq:NN \g_@@_softpath_lasty_dim \l_@@_softpath_lasty_dim
      \dim_gset_eq:NN \g_@@_path_xmax_dim \l_@@_path_xmax_dim
      \dim_gset_eq:NN \g_@@_path_xmin_dim \l_@@_path_xmin_dim
      \dim_gset_eq:NN \g_@@_path_ymax_dim \l_@@_path_ymax_dim
      \dim_gset_eq:NN \g_@@_path_ymin_dim \l_@@_path_ymin_dim
      \dim_gset_eq:NN \g_@@_path_lastx_dim \l_@@_path_lastx_dim
      \dim_gset_eq:NN \g_@@_path_lasty_dim \l_@@_path_lasty_dim
    \group_end:
  }
%    \end{macrocode}
% \end{macro}
%
% \subsection{Messages}
%
%    \begin{macrocode}
\msg_new:nnnn { draw } { invalid-path-action }
  { Invalid~action~'#1'~for~path. }
  { Paths~can~be~used~with~actions~'draw',~'clip',~'fill'~or~'stroke'. }
%     \end{macrocode}
%
%    \begin{macrocode}
%</package>
%    \end{macrocode}
%
% \end{implementation}
%
% \PrintIndex
