% \iffalse meta-comment % %% File: l3draw-paths.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} % % \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 2024-03-14} % % \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 specialised 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_@@_xmax_dim { \dim_max:nn \g_@@_xmax_dim {#1} } \dim_gset:Nn \g_@@_xmin_dim { \dim_min:nn \g_@@_xmin_dim {#1} } \dim_gset:Nn \g_@@_ymax_dim { \dim_max:nn \g_@@_ymax_dim {#2} } \dim_gset:Nn \g_@@_ymin_dim { \dim_min:nn \g_@@_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{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 } {#1} } \cs_new_protected:Npn \draw_path_canvas_lineto:n #1 { \@@_point_process:nn { \@@_path_lineto:nn } {#1} } \cs_new_protected:Npn \draw_path_canvas_curveto:nnn #1#2#3 { \@@_point_process:nnnn { \@@_path_mark_corner: \@@_path_curveto:nnnnnn } {#1} {#2} {#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} {#2} {#3} {#3} } \cs_new_protected:Npn \draw_path_arc:nnnn #1#2#3#4 { \use:e { \@@_path_arc:nnnn { \fp_eval:n {#1} } { \fp_eval:n {#2} } { \fp_to_dim:n {#3} } { \fp_to_dim:n {#4} } } } \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 } {#3} {#4} \draw_path_arc:nnn {#1} {#2} { 1pt } \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 optimised 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 {#1} {#2} } { \@@_point_process:nnn \@@_path_rectangle:nnnn { (#1) + ( \l_@@_xshift_dim , \l_@@_yshift_dim ) } { #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} } {#1} {#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} } } {#3} {#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_@@_ #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_@@_ #1#2 _dim } } = { #3 -\c_max_dim } { \dim_gset:cn { g_@@_ #1#2 _dim } { \use:c { dim_ #2 :nn } { \dim_use:c { g_@@_ #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} % % \end{macrocode} % % \end{implementation} % % \PrintIndex