% File: hawkdraw-softpath.code.tex
% Copyright 2026 Jasper Habicht (mail(at)jasperhabicht.de).
%
% This work may be distributed and/or modified under the
% conditions of the LaTeX Project Public License version 1.3c,
% available at http://www.latex-project.org/lppl/.
%
% This file is part of the `hawkdraw' package (The Work in LPPL)
% and all files in that bundle must be distributed together.
%
% This work has the LPPL maintenance status `maintained'.
%
% BOF

% v0.5.0 2026-07-20

% The softpath mechanism detaches the drawing commands from the actual drawing and creates another layer of
% commands that can be used to manipulate the output of the original drawing commands. This enables it to add
% functionality such as shortening or offsetting paths. It also allows for other functions that are unrelated
% to drawing such as measuring the length of a path.

\tl_new:N \g_hawkdraw_softpath_original_tl

% #1 , #2: x and y coordinate of first point of path
\cs_new_protected:Npn \hawkdraw_softpath_close_op:nn #1#2 {
    \draw_path_close:
}

% #1 , #2: x and y coordinate of point
\cs_new_protected:Npn \hawkdraw_softpath_moveto_op:nn #1#2 {
    \draw_path_moveto:n { #1 , #2 }
}

% #1 , #2: x and y coordinate of end point
\cs_new_protected:Npn \hawkdraw_softpath_lineto_op:nn #1#2 {
    \draw_path_lineto:n { #1 , #2 }
}

% #1 , #2: x and y coordinate of control point
% #3     : \hawkdraw_softpath_curveto_opii:nn
% #4 , #5: x and y coordinate of end point (quadratic) or second control point (cubic)
% #6     : \hawkdraw_softpath_curveto_opiii:nn
% #7 , #8: empty (quadratic) or x and y coordinate of end point (cubic)
\cs_new_protected:Npn \hawkdraw_softpath_curveto_op:nnNnnNnn #1#2#3#4#5#6#7#8 {
    \tl_if_empty:nTF {#7} {
        \draw_path_curveto:nn { #1 , #2 } { #4 , #5 }
    } {
        \draw_path_curveto:nnn { #1 , #2 } { #4 , #5 } { #7 , #8 }
    }
}

\cs_new_protected:Npn \hawkdraw_softpath_curveto_opi:nn #1#2 {
    \hawkdraw_softpath_curveto_op:nnNnnNnn {#1} {#2}
}

\cs_new_protected:Npn \hawkdraw_softpath_curveto_opii:nn #1#2 {
    \hawkdraw_softpath_curveto_opii:nn
}

\cs_new_protected:Npn \hawkdraw_softpath_curveto_opiii:nn #1#2 {
    \hawkdraw_softpath_curveto_opiii:nn
}

% #1 , #2: lengths of x and y radius
% #3     : \hawkdraw_softpath_arc_opii:nn
% #4 , #5: start and end angle
\cs_new_protected:Npn \hawkdraw_softpath_arc_op:nnNnn #1#2#3#4#5 {
    \draw_path_arc:nnnn {#1} {#2} {#4} {#5}
}

\cs_new_protected:Npn \hawkdraw_softpath_arc_opi:nn #1#2 {
    \hawkdraw_softpath_arc_op:nnNnn {#1} {#2}
}

\cs_new_protected:Npn \hawkdraw_softpath_arc_opii:nn #1#2 {
    \hawkdraw_softpath_arc_opii:nn
}

% #1 , #2: x and y coordinate of vector a
% #3     : \hawkdraw_softpath_arc_axes_opii:nn
% #4 , #5: x and y coordinate of value b
% #6     : \hawkdraw_softpath_arc_axes_opiii:nn
% #7 , #8: start and end angle
\cs_new_protected:Npn \hawkdraw_softpath_arc_axes_op:nnNnnNnn #1#2#3#4#5#6#7#8 {
    \draw_path_arc_axes:nnnn { #1 , #2 } { #4 , #5 } {#7} {#8}
}

\cs_new_protected:Npn \hawkdraw_softpath_arc_axes_opi:nn #1#2 {
    \hawkdraw_softpath_arc_axes_op:nnNnnNnn {#1} {#2}
}

\cs_new_protected:Npn \hawkdraw_softpath_arc_axes_opii:nn #1#2 {
    \hawkdraw_softpath_arc_axes_opii:nn
}

\cs_new_protected:Npn \hawkdraw_softpath_arc_axes_opiii:nn #1#2 {
    \hawkdraw_softpath_arc_axes_opiii:nn
}

% #1 , #2: x and y coordinate of center
% #3     : \hawkdraw_softpath_circle_opii:nn
% #4     : length of radius
% #5     : empty
\cs_new_protected:Npn \hawkdraw_softpath_circle_op:nnNnn #1#2#3#4#5 {
    \draw_path_circle:nn { #1 , #2 } {#4}
}

\cs_new_protected:Npn \hawkdraw_softpath_circle_opi:nn #1#2 {
    \hawkdraw_softpath_circle_op:nnNnn {#1} {#2}
}

\cs_new_protected:Npn \hawkdraw_softpath_circle_opii:nn #1#2 {
    \hawkdraw_softpath_circle_opii:nn
}

% #1 , #2: x and y coordinate of center
% #3     : \hawkdraw_softpath_ellipse_opii:nn
% #4 , #5: x and y coordinate of vector a
% #6     : \hawkdraw_softpath_ellipse_opiii:nn
% #7 , #8: x and y coordinate of vector b
\cs_new_protected:Npn \hawkdraw_softpath_ellipse_op:nnNnnNnn #1#2#3#4#5#6#7#8 {
    \draw_path_ellipse:nnn { #1 , #2 } { #4 , #5 } { #7 , #8 }
}

\cs_new_protected:Npn \hawkdraw_softpath_ellipse_opi:nn #1#2 {
    \hawkdraw_softpath_ellipse_op:nnNnnNnn {#1} {#2}
}

\cs_new_protected:Npn \hawkdraw_softpath_ellipse_opii:nn #1#2 {
    \hawkdraw_softpath_ellipse_opii:nn
}

\cs_new_protected:Npn \hawkdraw_softpath_ellipse_opiii:nn #1#2 {
    \hawkdraw_softpath_ellipse_opiii:nn
}
% #1 , #2: x and y coordinate of corner a
% #3     : \hawkdraw_softpath_rectangle_opii:nn
% #4 , #5: x and y coordinate of corner b
\cs_new_protected:Npn \hawkdraw_softpath_rectangle_op:nnNnn #1#2#3#4#5 {
    \draw_path_rectangle_corners:nn { #1 , #2 } { #4 , #5 }
}

\cs_new_protected:Npn \hawkdraw_softpath_rectangle_opi:nn #1#2 {
    \hawkdraw_softpath_rectangle_op:nnNnn {#1} {#2}
}

\cs_new_protected:Npn \hawkdraw_softpath_rectangle_opii:nn #1#2 {
    \hawkdraw_softpath_rectangle_opii:nn
}

% #1 , #2: lengths of x and y step
% #3     : \hawkdraw_softpath_grid_opii:nn
% #4 , #5: x and y coordinate of corner a
% #6     : \hawkdraw_softpath_grid_opiii:nn
% #7 , #8: x and y coordinate of corner b
\cs_new_protected:Npn \hawkdraw_softpath_grid_op:nnNnnNnn #1#2#3#4#5#6#7#8 {
    \draw_path_grid:nnnn {#1} {#2} { #4 , #5 } { #7 , #8 }
}

\cs_new_protected:Npn \hawkdraw_softpath_grid_opi:nn #1#2 {
    \hawkdraw_softpath_grid_op:nnNnnNnn {#1} {#2}
}

\cs_new_protected:Npn \hawkdraw_softpath_grid_opii:nn #1#2 {
    \hawkdraw_softpath_grid_opii:nn
}

\cs_new_protected:Npn \hawkdraw_softpath_grid_opiii:nn #1#2 {
    \hawkdraw_softpath_grid_opiii:nn
}

% =====

% Softpath manipulation helper functions

% ===

\msg_new:nnn { hawkdraw } { softpath-unknown } {
    Softpath ~ mechanism ~ `#1` ~ unknown.
}

\cs_new_protected:Npn \hawkdraw_softpath_add:NNnn #1#2#3#4 {
    \tl_build_put_right:Ne #1 { #2 }
    \tl_build_put_right:Ne #1 { {#3} }
    \tl_build_put_right:Ne #1 { {#4} }
}

\cs_new_protected:Npn \hawkdraw_softpath_add_split_tuple:NNn #1#2#3 {
    \tl_build_put_right:Ne #1 { #2 }
    \tl_build_put_right:Ne #1 {
        { \hawkdraw_tuple_use_i:n {#3} }
    }
    \tl_build_put_right:Ne #1 {
        { \hawkdraw_tuple_use_ii:n {#3} }
    }
}

\cs_new_protected:Npn \hawkdraw_softpath_gadd:NNnn #1#2#3#4 {
    \tl_build_gput_right:Ne #1 { #2 }
    \tl_build_gput_right:Ne #1 { {#3} }
    \tl_build_gput_right:Ne #1 { {#4} }
}

\cs_new_protected:Npn \hawkdraw_softpath_gadd_split_tuple:NNn #1#2#3 {
    \tl_build_gput_right:Ne #1 { #2 }
    \tl_build_gput_right:Ne #1 {
        { \hawkdraw_tuple_use_i:n {#3} }
    }
    \tl_build_gput_right:Ne #1 {
        { \hawkdraw_tuple_use_ii:n {#3} }
    }
}

\cs_new_protected:Npn \hawkdraw_softpath_build_close:n #1 {
    \hawkdraw_softpath_gadd_split_tuple:NNn
        \g_hawkdraw_softpath_original_tl
        \hawkdraw_softpath_close_op:nn {#1}
}

\cs_new_protected:Npn \hawkdraw_softpath_build_moveto:n #1 {
    \hawkdraw_softpath_gadd_split_tuple:NNn
        \g_hawkdraw_softpath_original_tl
        \hawkdraw_softpath_moveto_op:nn {#1}
}

\cs_new_protected:Npn \hawkdraw_softpath_build_lineto:n #1 {
    \hawkdraw_softpath_gadd_split_tuple:NNn
        \g_hawkdraw_softpath_original_tl
        \hawkdraw_softpath_lineto_op:nn {#1}
}

\cs_new_protected:Npn \hawkdraw_softpath_build_curveto:nn #1#2 {
    \hawkdraw_softpath_gadd_split_tuple:NNn
        \g_hawkdraw_softpath_original_tl
        \hawkdraw_softpath_curveto_opi:nn {#1}
    \hawkdraw_softpath_gadd_split_tuple:NNn
        \g_hawkdraw_softpath_original_tl
        \hawkdraw_softpath_curveto_opii:nn {#2}
    \hawkdraw_softpath_gadd:NNnn
        \g_hawkdraw_softpath_original_tl
        \hawkdraw_softpath_curveto_opiii:nn { } { }
}

\cs_new_protected:Npn \hawkdraw_softpath_build_curveto:nnn #1#2#3 {
    \hawkdraw_softpath_gadd_split_tuple:NNn
        \g_hawkdraw_softpath_original_tl
        \hawkdraw_softpath_curveto_opi:nn {#1}
    \hawkdraw_softpath_gadd_split_tuple:NNn
        \g_hawkdraw_softpath_original_tl
        \hawkdraw_softpath_curveto_opii:nn {#2}
    \hawkdraw_softpath_gadd_split_tuple:NNn
        \g_hawkdraw_softpath_original_tl
        \hawkdraw_softpath_curveto_opiii:nn {#3}
}

\cs_new_protected:Npn \hawkdraw_softpath_build_arc:nnnn #1#2#3#4 {
    \hawkdraw_softpath_gadd:NNnn
        \g_hawkdraw_softpath_original_tl
        \hawkdraw_softpath_arc_opi:nn {#1} {#2}
    \hawkdraw_softpath_gadd:NNnn
        \g_hawkdraw_softpath_original_tl
        \hawkdraw_softpath_arc_opii:nn {#3} {#4}
}
\cs_generate_variant:Nn \hawkdraw_softpath_build_arc:nnnn { VV }

\cs_new_protected:Npn \hawkdraw_softpath_build_arc_axes:nnnn #1#2#3#4 {
    \hawkdraw_softpath_gadd_split_tuple:NNn
        \g_hawkdraw_softpath_original_tl
        \hawkdraw_softpath_arc_axes_opi:nn {#1}
    \hawkdraw_softpath_gadd_split_tuple:NNn
        \g_hawkdraw_softpath_original_tl
        \hawkdraw_softpath_arc_axes_opii:nn {#2}
    \hawkdraw_softpath_gadd:NNnn
        \g_hawkdraw_softpath_original_tl
        \hawkdraw_softpath_arc_axes_opiii:nn {#3} {#4}
}

\cs_new_protected:Npn \hawkdraw_softpath_build_circle:nn #1#2 {
    \hawkdraw_softpath_gadd_split_tuple:NNn
        \g_hawkdraw_softpath_original_tl
        \hawkdraw_softpath_circle_opi:nn {#1}
    \hawkdraw_softpath_gadd:NNnn
        \g_hawkdraw_softpath_original_tl
        \hawkdraw_softpath_circle_opii:nn {#2} { }
}
\cs_generate_variant:Nn \hawkdraw_softpath_build_circle:nn { nV }

\cs_new_protected:Npn \hawkdraw_softpath_build_ellipse:nnn #1#2#3 {
    \hawkdraw_softpath_gadd_split_tuple:NNn
        \g_hawkdraw_softpath_original_tl
        \hawkdraw_softpath_ellipse_opi:nn {#1}
    \hawkdraw_softpath_gadd_split_tuple:NNn
        \g_hawkdraw_softpath_original_tl
        \hawkdraw_softpath_ellipse_opii:nn {#2}
    \hawkdraw_softpath_gadd_split_tuple:NNn
        \g_hawkdraw_softpath_original_tl
        \hawkdraw_softpath_ellipse_opiii:nn {#3}
}

\cs_new_protected:Npn \hawkdraw_softpath_build_rectangle:nn #1#2 {
    \hawkdraw_softpath_gadd_split_tuple:NNn
        \g_hawkdraw_softpath_original_tl
        \hawkdraw_softpath_rectangle_opi:nn {#1}
    \hawkdraw_softpath_gadd_split_tuple:NNn
        \g_hawkdraw_softpath_original_tl
        \hawkdraw_softpath_rectangle_opii:nn {#2}
}

\cs_new_protected:Npn \hawkdraw_softpath_build_grid:nnnn #1#2#3#4 {
    \hawkdraw_softpath_gadd:NNnn
        \g_hawkdraw_softpath_original_tl
        \hawkdraw_softpath_grid_opi:nn {#1} {#2}
    \hawkdraw_softpath_gadd_split_tuple:NNn
        \g_hawkdraw_softpath_original_tl
        \hawkdraw_softpath_grid_opii:nn {#3}
    \hawkdraw_softpath_gadd_split_tuple:NNn
        \g_hawkdraw_softpath_original_tl
        \hawkdraw_softpath_grid_opiii:nn {#4}
}
\cs_generate_variant:Nn \hawkdraw_softpath_build_grid:nnnn { VV }

% =====

\clist_new:N \l_hawkdraw_softpath_update_clist
\tl_new:N \l__hawkdraw_softpath_updated_tl

\bool_new:N \l_hawkdraw_softpath_keep_original_path_info_bool
\fp_new:N \l__hawkdraw_softpath_point_first_original_fp
\fp_new:N \l__hawkdraw_softpath_point_last_original_fp
\fp_new:N \l__hawkdraw_softpath_slope_first_original_fp
\fp_new:N \l__hawkdraw_softpath_slope_last_original_fp

\scan_new:N \s__hawkdraw_softpath_end

\keys_define:nn { hawkdraw / path } {
    keep ~ original ~ path ~ info
                            .bool_set:N = \l_hawkdraw_softpath_keep_original_path_info_bool ,
    keep ~ original ~ path ~ info
                            .default:n  = { true } ,
    keep ~ original ~ path ~ info
                            .initial:n  = { false } ,
}

\cs_new_protected:Npn \hawkdraw_softpath_update:Nn #1#2 {
    \fp_set_eq:NN \l__hawkdraw_softpath_point_first_original_fp
        \g_hawkdraw_path_point_first_fp
    \fp_set_eq:NN \l__hawkdraw_softpath_point_last_original_fp
        \g_hawkdraw_path_point_last_fp
    \fp_set_eq:NN \l__hawkdraw_softpath_slope_first_original_fp
        \g_hawkdraw_path_slope_first_fp
    \fp_set_eq:NN \l__hawkdraw_softpath_slope_last_original_fp
        \g_hawkdraw_path_slope_last_fp
    \clist_map_inline:nn {#2} {
        \group_begin:
            \tl_build_begin:N \l__hawkdraw_softpath_updated_tl
            \cs_if_exist_use:cF { hawkdraw_softpath_ ##1 _update_fn: } {
                \msg_error:nnn { hawkdraw } { softpath-unknown } {##1}
            }
            \tl_build_put_right:Nn \l__hawkdraw_softpath_updated_tl { \s__hawkdraw_softpath_end }
            \tl_build_end:N \l__hawkdraw_softpath_updated_tl
            \tl_gset_eq:NN #1 \l__hawkdraw_softpath_updated_tl
        \group_end:
    }
    \bool_if:NT \l_hawkdraw_softpath_keep_original_path_info_bool {
        \fp_gset_eq:NN \g_hawkdraw_path_point_first_fp
            \l__hawkdraw_softpath_point_first_original_fp
        \fp_gset_eq:NN \g_hawkdraw_path_point_last_fp
            \l__hawkdraw_softpath_point_last_original_fp
        \fp_gset_eq:NN \g_hawkdraw_path_slope_first_fp
            \l__hawkdraw_softpath_slope_first_original_fp
        \fp_gset_eq:NN \g_hawkdraw_path_slope_last_fp
            \l__hawkdraw_softpath_slope_last_original_fp
    }
}
\cs_generate_variant:Nn \hawkdraw_softpath_update:Nn { NV }

\cs_new_protected:Npn \hawkdraw_softpath_update_close:n #1 {
    \hawkdraw_softpath_add_split_tuple:NNn
        \l__hawkdraw_softpath_updated_tl
        \hawkdraw_softpath_close_op:nn {#1}
}

\cs_new_protected:Npn \hawkdraw_softpath_update_moveto:n #1 {
    \hawkdraw_softpath_add_split_tuple:NNn
        \l__hawkdraw_softpath_updated_tl
        \hawkdraw_softpath_moveto_op:nn {#1}
}

\cs_new_protected:Npn \hawkdraw_softpath_update_lineto:n #1 {
    \hawkdraw_softpath_add_split_tuple:NNn
        \l__hawkdraw_softpath_updated_tl
        \hawkdraw_softpath_lineto_op:nn {#1}
}

\cs_new_protected:Npn \hawkdraw_softpath_update_curveto:nn #1#2 {
    \hawkdraw_softpath_add_split_tuple:NNn
        \l__hawkdraw_softpath_updated_tl
        \hawkdraw_softpath_curveto_opi:nn {#1}
    \hawkdraw_softpath_add_split_tuple:NNn
        \l__hawkdraw_softpath_updated_tl
        \hawkdraw_softpath_curveto_opii:nn {#2}
    \hawkdraw_softpath_add:NNnn
        \l__hawkdraw_softpath_updated_tl
        \hawkdraw_softpath_curveto_opiii:nn { } { }
}

\cs_new_protected:Npn \hawkdraw_softpath_update_curveto:nnn #1#2#3 {
    \hawkdraw_softpath_add_split_tuple:NNn
        \l__hawkdraw_softpath_updated_tl
        \hawkdraw_softpath_curveto_opi:nn {#1}
    \hawkdraw_softpath_add_split_tuple:NNn
        \l__hawkdraw_softpath_updated_tl
        \hawkdraw_softpath_curveto_opii:nn {#2}
    \hawkdraw_softpath_add_split_tuple:NNn
        \l__hawkdraw_softpath_updated_tl
        \hawkdraw_softpath_curveto_opiii:nn {#3}
}

\cs_new_protected:Npn \hawkdraw_softpath_update_arc:nnnn #1#2#3#4 {
    \hawkdraw_softpath_add:NNnn
        \l__hawkdraw_softpath_updated_tl
        \hawkdraw_softpath_arc_opi:nn {#1} {#2}
    \hawkdraw_softpath_add:NNnn
        \l__hawkdraw_softpath_updated_tl
        \hawkdraw_softpath_arc_opii:nn {#3} {#4}
}

\cs_new_protected:Npn \hawkdraw_softpath_update_arc_axes:nnnn #1#2#3#4 {
    \hawkdraw_softpath_add_split_tuple:NNn
        \l__hawkdraw_softpath_updated_tl
        \hawkdraw_softpath_arc_axes_opi:nn {#1}
    \hawkdraw_softpath_add_split_tuple:NNn
        \l__hawkdraw_softpath_updated_tl
        \hawkdraw_softpath_arc_axes_opii:nn {#2}
    \hawkdraw_softpath_add:NNnn
        \l__hawkdraw_softpath_updated_tl
        \hawkdraw_softpath_arc_axes_opiii:nn {#3} {#4}
}

\cs_new_protected:Npn \hawkdraw_softpath_update_circle:nn #1#2 {
    \hawkdraw_softpath_add_split_tuple:NNn
        \l__hawkdraw_softpath_updated_tl
        \hawkdraw_softpath_circle_opi:nn {#1}
    \hawkdraw_softpath_add:NNnn
        \l__hawkdraw_softpath_updated_tl
        \hawkdraw_softpath_circle_opii:nn {#2} { }
}

\cs_new_protected:Npn \hawkdraw_softpath_update_ellipse:nnn #1#2#3 {
    \hawkdraw_softpath_add_split_tuple:NNn
        \l__hawkdraw_softpath_updated_tl
        \hawkdraw_softpath_ellipse_opi:nn {#1}
    \hawkdraw_softpath_add_split_tuple:NNn
        \l__hawkdraw_softpath_updated_tl
        \hawkdraw_softpath_ellipse_opii:nn {#2}
    \hawkdraw_softpath_add_split_tuple:NNn
        \l__hawkdraw_softpath_updated_tl
        \hawkdraw_softpath_ellipse_opiii:nn {#3}
}

\cs_new_protected:Npn \hawkdraw_softpath_update_rectangle:nn #1#2 {
    \hawkdraw_softpath_add_split_tuple:NNn
        \l__hawkdraw_softpath_updated_tl
        \hawkdraw_softpath_rectangle_opi:nn {#1}
    \hawkdraw_softpath_add_split_tuple:NNn
        \l__hawkdraw_softpath_updated_tl
        \hawkdraw_softpath_rectangle_opii:nn {#2}
}

\cs_new_protected:Npn \hawkdraw_softpath_update_grid:nnnn #1#2#3#4 {
    \hawkdraw_softpath_add:NNnn
        \l__hawkdraw_softpath_updated_tl
        \hawkdraw_softpath_grid_opi:nn {#1} {#2}
    \hawkdraw_softpath_add_split_tuple:NNn
        \l__hawkdraw_softpath_updated_tl
        \hawkdraw_softpath_grid_opii:nn {#3}
    \hawkdraw_softpath_add_split_tuple:NNn
        \l__hawkdraw_softpath_updated_tl
        \hawkdraw_softpath_grid_opiii:nn {#4}
}

% =====

% Shorten paths

% ===

\dim_new:N \l_hawkdraw_softpath_shorten_start_dim
\dim_new:N \l_hawkdraw_softpath_shorten_end_dim
\bool_new:N \l__hawkdraw_softpath_shorten_start_bool
\bool_new:N \l__hawkdraw_softpath_shorten_end_bool
\fp_new:N \l_hawkdraw_softpath_shorten_point_previous_fp
\fp_new:N \l_hawkdraw_softpath_shorten_point_last_fp

\keys_define:nn { hawkdraw / path } {
    shorten ~ start         .code:n     = {
        \clist_put_right:Nn \l_hawkdraw_softpath_update_clist { shorten }
        \clist_remove_duplicates:N \l_hawkdraw_softpath_update_clist
        \dim_set:Nn \l_hawkdraw_softpath_shorten_start_dim {#1}
    } ,
    shorten ~ end           .code:n     = {
        \clist_put_right:Nn \l_hawkdraw_softpath_update_clist { shorten }
        \clist_remove_duplicates:N \l_hawkdraw_softpath_update_clist
        \dim_set:Nn \l_hawkdraw_softpath_shorten_end_dim {#1}
    } ,
}

\cs_new_protected:Npn \hawkdraw_softpath_shorten_update_fn: {
    \bool_set_true:N \l__hawkdraw_softpath_shorten_start_bool
    \fp_set:Nn \l_hawkdraw_softpath_shorten_point_last_fp { 0pt , 0pt }
    \clist_map_inline:nn {
        moveto_op:nn , lineto_op:nn ,
        curveto_op:nnNnnNnn , arc_op:nnNnn , arc_axes_op:nnNnnNnn ,
        close:op:nn , circle_op:nnNnn , ellipse_op:nnNnnNnn ,
        rectangle_op:nnNnn , grid_op:nnNnnNnn
    } {
        \cs_set_eq:cc {
            hawkdraw_softpath_ ##1
        } {
            hawkdraw_softpath_shorten_ ##1
        }
    }
    \tl_use:N \g_hawkdraw_softpath_original_tl
}

\cs_new_protected:Npn \__hawkdraaw_softpath_shorten_lookahead:nN #1#2 {
    \token_if_eq_meaning:NNTF #2 \s__hawkdraw_softpath_end {
        \bool_set_true:N \l__hawkdraw_softpath_shorten_end_bool
    } {
        \bool_set_false:N \l__hawkdraw_softpath_shorten_end_bool
    }
    #1#2
}

\cs_new_protected:Npn \hawkdraw_softpath_shorten_moveto_op:nn #1#2 {
    \fp_set_eq:NN \l_hawkdraw_softpath_shorten_point_previous_fp \l_hawkdraw_softpath_shorten_point_last_fp
    \fp_set:Nn \l_hawkdraw_softpath_shorten_point_last_fp { #1 , #2 }
    \bool_lazy_and:nnF {
        \dim_compare_p:nNn { \l_hawkdraw_softpath_shorten_start_dim } > { \c_zero_dim }
    } {
        \l__hawkdraw_softpath_shorten_start_bool
    } {
        \hawkdraw_softpath_update_moveto:n {
            \l_hawkdraw_softpath_shorten_point_previous_fp
        }
    }
}

\fp_new:N \l__hawkdraw_softpath_shorten_start_line_part_fp
\fp_new:N \l__hawkdraw_softpath_shorten_end_line_part_fp

\cs_new_protected:Npn \hawkdraw_softpath_shorten_lineto_op:nn #1#2 {
    \fp_set_eq:NN \l_hawkdraw_softpath_shorten_point_previous_fp \l_hawkdraw_softpath_shorten_point_last_fp
    \__hawkdraaw_softpath_shorten_lookahead:nN {
        \__hawkdraw_softpath_shorten_line:nn {
            \l_hawkdraw_softpath_shorten_point_previous_fp
        } { #1 , #2 }
    }
}

\cs_new_protected:Npn \__hawkdraw_softpath_shorten_line:nn #1#2 {
    \fp_set:Nn \l_hawkdraw_softpath_shorten_point_last_fp {#2}
    \bool_lazy_and:nnT {
        \dim_compare_p:nNn { \l_hawkdraw_softpath_shorten_start_dim } > { \c_zero_dim }
    } {
        \l__hawkdraw_softpath_shorten_start_bool
    } {
        \bool_set_false:N \l__hawkdraw_softpath_shorten_start_bool
        \fp_set:Nn \l__hawkdraw_softpath_shorten_start_line_part_fp {
            \l_hawkdraw_softpath_shorten_start_dim /
            \__hawkdraw_path_length_line:nn {#1} {#2}
        }
        \fp_gset:Nn \g_hawkdraw_path_point_first_fp {
            \__hawkdraw_point_part_line:nnn {
                \l__hawkdraw_softpath_shorten_start_line_part_fp
            } {#1} {#2}
        }
        % line: shorten start
        \hawkdraw_softpath_update_moveto:n {
            \g_hawkdraw_path_point_first_fp
        }
    }
    \bool_lazy_and:nnTF {
        \dim_compare_p:nNn { \l_hawkdraw_softpath_shorten_end_dim } > { \c_zero_dim }
    } {
        \l__hawkdraw_softpath_shorten_end_bool
    } {
        \fp_set:Nn \l__hawkdraw_softpath_shorten_end_line_part_fp {
            1 - (
                \l_hawkdraw_softpath_shorten_end_dim /
                \__hawkdraw_path_length_line:nn {#1} {#2}
            )
        }
        \fp_gset:Nn \g_hawkdraw_path_point_last_fp {
            \__hawkdraw_point_part_line:nnn {
                \l__hawkdraw_softpath_shorten_end_line_part_fp
            } {#1} {#2}
        }
        % line: shorten end
        \hawkdraw_softpath_update_lineto:n {
            \g_hawkdraw_path_point_last_fp
        }
    } {
        \hawkdraw_softpath_update_lineto:n {#2}
    }
}

\fp_new_function:n { hawkdrawlerp }
\fp_set_function:nnn { hawkdrawlerp } { p , q , t } {
    ( 1 - t ) * p + t * q
}

\fp_new_function:n { hawkdrawremapt }
\fp_set_function:nnn { hawkdrawremapt } { t , u } {
    ( u - t ) / ( 1 - t )
}

\fp_new:N \l__hawkdraw_softpath_shorten_start_curve_part_fp
\fp_new:N \l__hawkdraw_softpath_shorten_end_curve_part_fp
\fp_new:N \l__hawkdraw_softpath_shorten_start_end_curve_part_fp

\fp_new:N \l__hawkdraw_softpath_shorten_curve_point_tmpa_fp
\fp_new:N \l__hawkdraw_softpath_shorten_curve_point_tmpb_fp

\cs_new_protected:Npn \hawkdraw_softpath_shorten_curveto_op:nnNnnNnn #1#2#3#4#5#6#7#8 {
    \fp_set_eq:NN \l_hawkdraw_softpath_shorten_point_previous_fp \l_hawkdraw_softpath_shorten_point_last_fp
    \__hawkdraaw_softpath_shorten_lookahead:nN {
        \tl_if_empty:nTF {#7} {
            \__hawkdraw_softpath_shorten_curve_quadratic:nnn {
                \l_hawkdraw_softpath_shorten_point_previous_fp
             } { #1 , #2 } { #4 , #5 }
        } {
            \__hawkdraw_softpath_shorten_curve_cubic:nnnn {
                \l_hawkdraw_softpath_shorten_point_previous_fp
            } { #1 , #2 } { #4 , #5 } { #7 , #8 }
        }
    }
}

\cs_new_protected:Npn \__hawkdraw_softpath_shorten_curve_quadratic:nnn #1#2#3 {
    \fp_set:Nn \l_hawkdraw_softpath_shorten_point_last_fp {#3}
    % quadratic curve
    \bool_lazy_and:nnTF {
        \dim_compare_p:nNn { \l_hawkdraw_softpath_shorten_end_dim } > { \c_zero_dim }
    } {
        \l__hawkdraw_softpath_shorten_end_bool
    } {
        \fp_set:Nn \l__hawkdraw_softpath_shorten_end_curve_part_fp {
            1 - (
                \l_hawkdraw_softpath_shorten_end_dim /
                \__hawkdraw_path_length_curve_quadratic:nnn
                    {#1} {#2} {#3}
            )
        }
        \fp_gset:Nn \g_hawkdraw_path_point_last_fp {
            \__hawkdraw_point_part_curve_quadratic:nnnn {
                \l__hawkdraw_softpath_shorten_end_curve_part_fp
            } {#1} {#2} {#3}
        }
        \bool_lazy_and:nnTF {
            \dim_compare_p:nNn { \l_hawkdraw_softpath_shorten_start_dim } > { \c_zero_dim }
        } {
            \l__hawkdraw_softpath_shorten_start_bool
        } {
            \bool_set_false:N \l__hawkdraw_softpath_shorten_start_bool
            \fp_set:Nn \l__hawkdraw_softpath_shorten_start_curve_part_fp {
                \l_hawkdraw_softpath_shorten_start_dim /
                \__hawkdraw_path_length_curve_quadratic:nnn
                    {#1} {#2} {#3}
            }
            \fp_set:Nn \l__hawkdraw_softpath_shorten_start_end_curve_part_fp {
                hawkdrawremapt (
                    \l__hawkdraw_softpath_shorten_start_curve_part_fp ,
                    \l__hawkdraw_softpath_shorten_end_curve_part_fp
                )
            }
            \fp_gset:Nn \g_hawkdraw_path_point_first_fp {
                \__hawkdraw_point_part_curve_quadratic:nnnn {
                    \l__hawkdraw_softpath_shorten_start_curve_part_fp
                } {#1} {#2} {#3}
            }
            \fp_set:Nn \l__hawkdraw_softpath_shorten_curve_point_tmpa_fp {
                hawkdrawlerp (
                    \g_hawkdraw_path_point_first_fp ,
                    hawkdrawlerp (
                        ( #2 ) ,
                        ( #3 ) ,
                        \l__hawkdraw_softpath_shorten_start_curve_part_fp
                    ) ,
                    \l__hawkdraw_softpath_shorten_start_end_curve_part_fp
                )
            }
            % quadratic curve: shorten start, shorten end
            \hawkdraw_softpath_update_moveto:n {
                \g_hawkdraw_path_point_first_fp
            }
            \hawkdraw_softpath_update_curveto:nn {
                \l__hawkdraw_softpath_shorten_curve_point_tmpa_fp
            } {
                \g_hawkdraw_path_point_last_fp
            }
            \fp_gset:Nn \g_hawkdraw_path_slope_first_fp {
                \hawkdraw_calculate_slope:nn {
                    \g_hawkdraw_path_point_first_fp
                } {
                    \l__hawkdraw_softpath_shorten_curve_point_tmpa_fp
                }
            }
            \fp_gset:Nn \g_hawkdraw_path_slope_last_fp {
                \hawkdraw_calculate_slope:nn {
                    \l__hawkdraw_softpath_shorten_curve_point_tmpa_fp
                } {
                    \g_hawkdraw_path_point_last_fp
                }
            }
        } {
            \fp_set:Nn \l__hawkdraw_softpath_shorten_curve_point_tmpa_fp {
                hawkdrawlerp (
                    ( #1 ) ,
                    ( #2 ) ,
                    \l__hawkdraw_softpath_shorten_end_curve_part_fp
                )
            }
            % quadratic curve: shorten end
            \hawkdraw_softpath_update_curveto:nn {
                \l__hawkdraw_softpath_shorten_curve_point_tmpa_fp
            } {
                \g_hawkdraw_path_point_last_fp
            }
            \fp_gset:Nn \g_hawkdraw_path_slope_last_fp {
                \hawkdraw_calculate_slope:nn {
                    \l__hawkdraw_softpath_shorten_curve_point_tmpa_fp
                } { \g_hawkdraw_path_point_last_fp }
            }
        }
    } {
        \bool_lazy_and:nnTF {
            \dim_compare_p:nNn { \l_hawkdraw_softpath_shorten_start_dim } > { \c_zero_dim }
        } {
            \l__hawkdraw_softpath_shorten_start_bool
        } {
            \bool_set_false:N \l__hawkdraw_softpath_shorten_start_bool
            \fp_set:Nn \l__hawkdraw_softpath_shorten_start_curve_part_fp {
                \l_hawkdraw_softpath_shorten_start_dim /
                \__hawkdraw_path_length_curve_quadratic:nnn
                    {#1} {#2} {#3}
            }
            \fp_gset:Nn \g_hawkdraw_path_point_first_fp {
                \__hawkdraw_point_part_curve_quadratic:nnnn {
                    \l__hawkdraw_softpath_shorten_start_curve_part_fp
                } {#1} {#2} {#3}
            }
            \fp_set:Nn \l__hawkdraw_softpath_shorten_curve_point_tmpa_fp {
                hawkdrawlerp (
                    ( #2 ) ,
                    ( #3 ) ,
                    \l__hawkdraw_softpath_shorten_start_curve_part_fp
                )
            }
            % quadratic curve: shorten start
            \hawkdraw_softpath_update_moveto:n {
                \g_hawkdraw_path_point_first_fp
            }
            \hawkdraw_softpath_update_curveto:nn {
                \l__hawkdraw_softpath_shorten_curve_point_tmpa_fp
            } {#3}
            \fp_gset:Nn \g_hawkdraw_path_slope_first_fp {
                \hawkdraw_calculate_slope:nn {
                    \g_hawkdraw_path_point_first_fp
                } {
                    \l__hawkdraw_softpath_shorten_curve_point_tmpa_fp
                }
            }
        } {
            \hawkdraw_softpath_update_curveto:nn {#2} {#3}
        }
    }
}

\cs_new_protected:Npn \__hawkdraw_softpath_shorten_curve_cubic:nnnn #1#2#3#4 {
    \fp_set:Nn \l_hawkdraw_softpath_shorten_point_last_fp {#4}
    % cubic curve
    \bool_lazy_and:nnTF {
        \dim_compare_p:nNn { \l_hawkdraw_softpath_shorten_end_dim } > { \c_zero_dim }
    } {
        \l__hawkdraw_softpath_shorten_end_bool
    } {
        \fp_set:Nn \l__hawkdraw_softpath_shorten_end_curve_part_fp {
            1 - (
                \l_hawkdraw_softpath_shorten_end_dim /
                \__hawkdraw_path_length_curve_cubic:nnnn
                    {#1} {#2} {#3} {#4}
            )
        }
        \fp_gset:Nn \g_hawkdraw_path_point_last_fp {
            \__hawkdraw_point_part_curve_cubic:nnnnn {
                \l__hawkdraw_softpath_shorten_end_curve_part_fp
            } {#1} {#2} {#3} {#4}
        }
        \bool_lazy_and:nnTF {
            \dim_compare_p:nNn { \l_hawkdraw_softpath_shorten_start_dim } > { \c_zero_dim }
        } {
            \l__hawkdraw_softpath_shorten_start_bool
        } {
            \bool_set_false:N \l__hawkdraw_softpath_shorten_start_bool
            \fp_set:Nn \l__hawkdraw_softpath_shorten_start_curve_part_fp {
                \l_hawkdraw_softpath_shorten_start_dim /
                \__hawkdraw_path_length_curve_cubic:nnnn
                    {#1} {#2} {#3} {#4}
            }
            \fp_set:Nn \l__hawkdraw_softpath_shorten_start_end_curve_part_fp {
                hawkdrawremapt (
                    \l__hawkdraw_softpath_shorten_start_curve_part_fp ,
                    \l__hawkdraw_softpath_shorten_end_curve_part_fp
                )
            }
            \fp_gset:Nn \g_hawkdraw_path_point_first_fp {
                \__hawkdraw_point_part_curve_cubic:nnnnn {
                    \l__hawkdraw_softpath_shorten_start_curve_part_fp
                } {#1} {#2} {#3} {#4}
            }
            \fp_set:Nn \l__hawkdraw_softpath_shorten_curve_point_tmpa_fp {
                % A' = (1-u)F + uE
                hawkdrawlerp (
                    % F = (1-t)D + tE = R0
                    \g_hawkdraw_path_point_first_fp ,
                    % E = (1-t)B + tC
                    hawkdrawlerp (
                        % B = (1-t)P1 + tP2
                        hawkdrawlerp (
                            ( #2 ) ,
                            ( #3 ) ,
                            \l__hawkdraw_softpath_shorten_start_curve_part_fp
                        ) ,
                        % C = (1-t)P2 + tP3
                        hawkdrawlerp (
                            ( #3 ) ,
                            ( #4 ) ,
                            \l__hawkdraw_softpath_shorten_start_curve_part_fp
                        ) ,
                        \l__hawkdraw_softpath_shorten_start_curve_part_fp
                    ) ,
                    \l__hawkdraw_softpath_shorten_start_end_curve_part_fp
                )
            }
            \fp_set:Nn \l__hawkdraw_softpath_shorten_curve_point_tmpb_fp {
                % D' = (1-u)A' + uB'
                hawkdrawlerp (
                    hawkdrawlerp (
                        \g_hawkdraw_path_point_first_fp ,
                        hawkdrawlerp (
                            hawkdrawlerp (
                                ( #2 ) ,
                                ( #3 ) ,
                                \l__hawkdraw_softpath_shorten_start_curve_part_fp
                            ) ,
                            hawkdrawlerp (
                                ( #3 ) ,
                                ( #4 ) ,
                                \l__hawkdraw_softpath_shorten_start_curve_part_fp
                            ) ,
                            \l__hawkdraw_softpath_shorten_start_curve_part_fp
                        ) ,
                        \l__hawkdraw_softpath_shorten_start_end_curve_part_fp
                    ) ,
                    % B' = (1-u)E + uC
                    hawkdrawlerp (
                        hawkdrawlerp (
                            hawkdrawlerp (
                                ( #2 ) ,
                                ( #3 ) ,
                                \l__hawkdraw_softpath_shorten_start_curve_part_fp
                            ) ,
                            hawkdrawlerp (
                                ( #3 ) ,
                                ( #4 ) ,
                                \l__hawkdraw_softpath_shorten_start_curve_part_fp
                            ) ,
                            \l__hawkdraw_softpath_shorten_start_curve_part_fp
                        ) ,
                        hawkdrawlerp (
                            ( #3 ) ,
                            ( #4 ) ,
                            \l__hawkdraw_softpath_shorten_start_curve_part_fp
                        ) ,
                        \l__hawkdraw_softpath_shorten_start_end_curve_part_fp
                    ) ,
                    \l__hawkdraw_softpath_shorten_start_end_curve_part_fp
                )
            }
            % cubic curve: shorten start, shorten end
            \hawkdraw_softpath_update_moveto:n {
                \g_hawkdraw_path_point_first_fp
            }
            \hawkdraw_softpath_update_curveto:nnn {
                \l__hawkdraw_softpath_shorten_curve_point_tmpa_fp
            } {
                \l__hawkdraw_softpath_shorten_curve_point_tmpb_fp
            } {
                \g_hawkdraw_path_point_last_fp
            }
            \fp_gset:Nn \g_hawkdraw_path_slope_first_fp {
                \hawkdraw_calculate_slope:nn {
                    \g_hawkdraw_path_point_first_fp
                } {
                    \l__hawkdraw_softpath_shorten_curve_point_tmpa_fp
                }
            }
            \fp_gset:Nn \g_hawkdraw_path_slope_last_fp {
                \hawkdraw_calculate_slope:nn {
                    \l__hawkdraw_softpath_shorten_curve_point_tmpb_fp
                } {
                    \g_hawkdraw_path_point_last_fp
                }
            }
        } {
            \fp_set:Nn \l__hawkdraw_softpath_shorten_curve_point_tmpa_fp {
                hawkdrawlerp (
                    ( #1 ) ,
                    ( #2 ) ,
                    \l__hawkdraw_softpath_shorten_end_curve_part_fp
                )
            }
            \fp_set:Nn \l__hawkdraw_softpath_shorten_curve_point_tmpb_fp {
                hawkdrawlerp (
                    hawkdrawlerp (
                        ( #1 ) ,
                        ( #2 ) ,
                        \l__hawkdraw_softpath_shorten_end_curve_part_fp
                    ) ,
                    hawkdrawlerp (
                        ( #2 ) ,
                        ( #3 ) ,
                        \l__hawkdraw_softpath_shorten_end_curve_part_fp
                    ) ,
                    \l__hawkdraw_softpath_shorten_end_curve_part_fp
                )
            }
            % cubic curve: shorten end
            \hawkdraw_softpath_update_curveto:nnn {
                \l__hawkdraw_softpath_shorten_curve_point_tmpa_fp
            } {
                \l__hawkdraw_softpath_shorten_curve_point_tmpb_fp
            } {
                \g_hawkdraw_path_point_last_fp
            }
            \fp_gset:Nn \g_hawkdraw_path_slope_last_fp {
                \hawkdraw_calculate_slope:nn {
                    \l__hawkdraw_softpath_shorten_curve_point_tmpb_fp
                } {
                    \g_hawkdraw_path_point_last_fp
                }
            }
        }
    } {
        \bool_lazy_and:nnTF {
            \dim_compare_p:nNn { \l_hawkdraw_softpath_shorten_start_dim } > { \c_zero_dim }
        } {
            \l__hawkdraw_softpath_shorten_start_bool
        } {
            \bool_set_false:N \l__hawkdraw_softpath_shorten_start_bool
            \fp_set:Nn \l__hawkdraw_softpath_shorten_start_curve_part_fp {
                \l_hawkdraw_softpath_shorten_start_dim /
                \__hawkdraw_path_length_curve_cubic:nnnn
                    {#1} {#2} {#3} {#4}
            }
            \fp_gset:Nn \g_hawkdraw_path_point_first_fp {
                \__hawkdraw_point_part_curve_cubic:nnnnn {
                    \l__hawkdraw_softpath_shorten_start_curve_part_fp
                } {#1} {#2} {#3} {#4}
            }
            \fp_set:Nn \l__hawkdraw_softpath_shorten_curve_point_tmpa_fp {
                hawkdrawlerp (
                    hawkdrawlerp (
                        ( #2 ) ,
                        ( #3 ) ,
                        \l__hawkdraw_softpath_shorten_start_curve_part_fp
                    ) ,
                    hawkdrawlerp (
                        ( #3 ) ,
                        ( #4 ) ,
                        \l__hawkdraw_softpath_shorten_start_curve_part_fp
                    ) ,
                    \l__hawkdraw_softpath_shorten_start_curve_part_fp
                )
            }
            \fp_set:Nn \l__hawkdraw_softpath_shorten_curve_point_tmpb_fp {
                hawkdrawlerp (
                    ( #3 ) ,
                    ( #4 ) ,
                    \l__hawkdraw_softpath_shorten_start_curve_part_fp
                )
            }
            % cubic curve: shorten start
            \hawkdraw_softpath_update_moveto:n {
                \g_hawkdraw_path_point_first_fp
            }
            \hawkdraw_softpath_update_curveto:nnn {
                \l__hawkdraw_softpath_shorten_curve_point_tmpa_fp
            } {
                \l__hawkdraw_softpath_shorten_curve_point_tmpb_fp
            } {#4}
            \fp_gset:Nn \g_hawkdraw_path_slope_first_fp {
                \hawkdraw_calculate_slope:nn {
                    \g_hawkdraw_path_point_first_fp
                } {
                    \l__hawkdraw_softpath_shorten_curve_point_tmpa_fp
                }
            }
        } {
            \hawkdraw_softpath_update_curveto:nnn {#2} {#3} {#4}
        }
    }
}

\fp_new:N \l__hawkdraw_softpath_shorten_start_arc_part_fp
\fp_new:N \l__hawkdraw_softpath_shorten_end_arc_part_fp

\cs_new_protected:Npn \hawkdraw_softpath_shorten_arc_op:nnNnn #1#2#3#4#5 {
    \fp_set_eq:NN \l_hawkdraw_softpath_shorten_point_previous_fp \l_hawkdraw_softpath_shorten_point_last_fp
    \__hawkdraaw_softpath_shorten_lookahead:nN {
        \__hawkdraw_softpath_shorten_arc:nnnn
            {#1} {#2} {#4} {#5}
    }
}

\cs_new_protected:Npn \__hawkdraw_softpath_shorten_arc:nnnn #1#2#3#4 {
    \fp_set:Nn \l_hawkdraw_softpath_shorten_point_last_fp {
        \__hawkdraw_point_part_arc:nnnnnn
            { 1 } { \l_hawkdraw_softpath_shorten_point_previous_fp }
            {#1} {#2} {#3} {#4}
    }
    \bool_lazy_and:nnTF {
        \dim_compare_p:nNn { \l_hawkdraw_softpath_shorten_end_dim } > { \c_zero_dim }
    } {
        \l__hawkdraw_softpath_shorten_end_bool
    } {
        \fp_set:Nn \l__hawkdraw_softpath_shorten_end_arc_part_fp {
            \l_hawkdraw_softpath_shorten_end_dim /
            \__hawkdraw_path_length_arc:nnnn
                {#1} {#2} {#3} {#4}
        }
        \fp_gset:Nn \g_hawkdraw_path_point_last_fp {
            \__hawkdraw_point_part_arc:nnnnn {
                \l__hawkdraw_softpath_shorten_end_arc_part_fp
            } {
                \l_hawkdraw_softpath_shorten_point_previous_fp
            } {#1} {#2} {#3} {#4}
        }
        \bool_lazy_and:nnTF {
            \dim_compare_p:nNn { \l_hawkdraw_softpath_shorten_start_dim } > { \c_zero_dim }
        } {
            \l__hawkdraw_softpath_shorten_start_bool
        } {
            \bool_set_false:N \l__hawkdraw_softpath_shorten_start_bool
            \fp_set:Nn \l__hawkdraw_softpath_shorten_start_arc_part_fp {
                \l_hawkdraw_softpath_shorten_start_dim /
                \__hawkdraw_path_length_arc:nnnn
                    {#1} {#2} {#3} {#4}
            }
            \fp_gset:Nn \g_hawkdraw_path_point_first_fp {
                \__hawkdraw_point_part_arc:nnnnn {
                    \l__hawkdraw_softpath_shorten_start_arc_part_fp
                } {
                    \l_hawkdraw_softpath_shorten_point_previous_fp
                } {#1} {#2} {#3} {#4}
            }
            % arc: shorten start, shorten end
            \hawkdraw_softpath_update_moveto:n {
                \g_hawkdraw_path_point_first_fp
            }
            \hawkdraw_softpath_update_arc:nnnn {#1} {#2} {
                #3 + \l__hawkdraw_softpath_shorten_start_arc_part_fp * ( #4 - #3 )
            } {
                #4 - \l__hawkdraw_softpath_shorten_end_arc_part_fp * ( #4 - #3 )
            }
            \fp_gset:Nn \g_hawkdraw_path_slope_first_fp {
                \__hawkdraw_point_part_slope_arc:n {
                    \l__hawkdraw_softpath_shorten_start_arc_part_fp
                }
            }
            \fp_gset:Nn \g_hawkdraw_path_slope_last_fp {
                \__hawkdraw_point_part_slope_arc:n {
                    \l__hawkdraw_softpath_shorten_end_arc_part_fp
                }
            }
        } {
            % arc: shorten end
            \hawkdraw_softpath_update_arc:nnnn {#1} {#2} {#3} {
                #4 - \l__hawkdraw_softpath_shorten_end_arc_part_fp * ( #4 - #3 )
            }
            \fp_gset:Nn \g_hawkdraw_path_slope_last_fp {
                \__hawkdraw_point_part_slope_arc:n {
                    \l__hawkdraw_softpath_shorten_end_arc_part_fp
                }
            }
        }
    } {
        \bool_lazy_and:nnTF {
            \dim_compare_p:nNn { \l_hawkdraw_softpath_shorten_start_dim } > { \c_zero_dim }
        } {
            \l__hawkdraw_softpath_shorten_start_bool
        } {
            \fp_set:Nn \l__hawkdraw_softpath_shorten_start_arc_part_fp {
                \l_hawkdraw_softpath_shorten_start_dim /
                \__hawkdraw_path_length_arc:nnnn
                    {#1} {#2} {#3} {#4}
            }
            \fp_gset:Nn \g_hawkdraw_path_point_first_fp {
                \__hawkdraw_point_part_arc:nnnnn {
                    \l__hawkdraw_softpath_shorten_start_arc_part_fp
                } {
                    \l_hawkdraw_softpath_shorten_point_previous_fp
                } {#1} {#2} {#3} {#4}
            }
            \hawkdraw_softpath_update_moveto:n {
                \g_hawkdraw_path_point_first_fp
            }
            \hawkdraw_softpath_update_arc:nnnn {#1} {#2} {
                #3 + \l__hawkdraw_softpath_shorten_start_arc_part_fp * ( #4 - #3 )
            } {#4}
            \fp_gset:Nn \g_hawkdraw_path_slope_first_fp {
                \__hawkdraw_point_part_slope_arc:n {
                    \l__hawkdraw_softpath_shorten_start_arc_part_fp
                }
            }
        } {
            \hawkdraw_softpath_update_arc:nnnn {#1} {#2} {#3} {#4}
        }
    }
}

\fp_new:N \l__hawkdraw_softpath_shorten_start_arc_axes_part_fp
\fp_new:N \l__hawkdraw_softpath_shorten_end_arc_axes_part_fp

\cs_new_protected:Npn \hawkdraw_softpath_shorten_arc_axes_op:nnNnnNnn #1#2#3#4#5#6#7#8 {
    \fp_set_eq:NN \l_hawkdraw_softpath_shorten_point_previous_fp \l_hawkdraw_softpath_shorten_point_last_fp
    \__hawkdraaw_softpath_shorten_lookahead:nN {
        \__hawkdraw_softpath_shorten_arc_axes:nnnn
            { #1 , #2 } { #4 , #5 } {#7} {#8}
    }
}

\cs_new_protected:Npn \__hawkdraw_softpath_shorten_arc_axes:nnnn #1#2#3#4 {
    \fp_set:Nn \l_hawkdraw_softpath_shorten_point_last_fp {
        \__hawkdraw_point_part_arc:nnnnnn
            { 1 } { \l_hawkdraw_softpath_shorten_point_previous_fp }
            {#1} {#2} {#3} {#4}
    }
    \bool_lazy_and:nnTF {
        \dim_compare_p:nNn { \l_hawkdraw_softpath_shorten_end_dim } > { \c_zero_dim }
    } {
        \l__hawkdraw_softpath_shorten_end_bool
    } {
        \fp_set:Nn \l__hawkdraw_softpath_shorten_end_arc_axes_part_fp {
            \l_hawkdraw_softpath_shorten_end_dim /
            \__hawkdraw_path_length_arc_axes:nnnn
                {#1} {#2} {#3} {#4}
        }
        \fp_gset:Nn \g_hawkdraw_path_point_last_fp {
            \__hawkdraw_point_part_arc_axes:nnnnn {
                \l__hawkdraw_softpath_shorten_end_arc_axes_part_fp
            } {
                \l_hawkdraw_softpath_shorten_point_previous_fp
            } {#1} {#2} {#3} {#4}
        }
        \bool_lazy_and:nnTF {
            \dim_compare_p:nNn { \l_hawkdraw_softpath_shorten_start_dim } > { \c_zero_dim }
        } {
            \l__hawkdraw_softpath_shorten_start_bool
        } {
            \bool_set_false:N \l__hawkdraw_softpath_shorten_start_bool
            \fp_set:Nn \l__hawkdraw_softpath_shorten_start_arc_axes_part_fp {
                \l_hawkdraw_softpath_shorten_start_dim /
                \__hawkdraw_path_length_arc_axes:nnnn
                    {#1} {#2} {#3} {#4}
            }
            \fp_gset:Nn \g_hawkdraw_path_point_first_fp {
                \__hawkdraw_point_part_arc_axes:nnnnn {
                    \l__hawkdraw_softpath_shorten_start_arc_axes_part_fp
                } {
                    \l_hawkdraw_softpath_shorten_point_previous_fp
                } {#1} {#2} {#3} {#4}
            }
            % arc: shorten start, shorten end
            \hawkdraw_softpath_update_moveto:n {
                \g_hawkdraw_path_point_first_fp
            }
            \hawkdraw_softpath_update_arc_axes:nnnn {#1} {#2} {
                #3 + \l__hawkdraw_softpath_shorten_start_arc_axes_part_fp * ( #4 - #3 )
            } {
                #4 - \l__hawkdraw_softpath_shorten_end_arc_axes_part_fp * ( #4 - #3 )
            }
            \fp_gset:Nn \g_hawkdraw_path_slope_first_fp {
                \__hawkdraw_point_part_slope_arc:n {
                    \l__hawkdraw_softpath_shorten_start_arc_axes_part_fp
                }
            }
            \fp_gset:Nn \g_hawkdraw_path_slope_last_fp {
                \__hawkdraw_point_part_slope_arc:n {
                    \l__hawkdraw_softpath_shorten_end_arc_axes_part_fp
                }
            }
        } {
            % arc: shorten end
            \hawkdraw_softpath_update_arc_axes:nnnn {#1} {#2} {#3} {
                #4 - \l__hawkdraw_softpath_shorten_end_arc_axes_part_fp * ( #4 - #3 )
            }
            \fp_gset:Nn \g_hawkdraw_path_slope_last_fp {
                \__hawkdraw_point_part_slope_arc:n {
                    \l__hawkdraw_softpath_shorten_end_arc_axes_part_fp
                }
            }
        }
    } {
        \bool_lazy_and:nnTF {
            \dim_compare_p:nNn { \l_hawkdraw_softpath_shorten_start_dim } > { \c_zero_dim }
        } {
            \l__hawkdraw_softpath_shorten_start_bool
        } {
            \fp_set:Nn \l__hawkdraw_softpath_shorten_start_arc_axes_part_fp {
                \l_hawkdraw_softpath_shorten_start_dim /
                \__hawkdraw_path_length_arc_axes:nnnn
                    {#1} {#2} {#3} {#4}
            }
            \fp_gset:Nn \g_hawkdraw_path_point_first_fp {
                \__hawkdraw_point_part_arc_axes:nnnnn {
                    \l__hawkdraw_softpath_shorten_start_arc_axes_part_fp
                } {
                    \l_hawkdraw_softpath_shorten_point_previous_fp
                } {#1} {#2} {#3} {#4}
            }
            % arc: shorten start
            \hawkdraw_softpath_update_moveto:n {
                \g_hawkdraw_path_point_first_fp
            }
            \hawkdraw_softpath_update_arc_axes:nnnn {#1} {#2} {
                #3 + \l__hawkdraw_softpath_shorten_start_arc_axes_part_fp * ( #4 - #3 )
            } {#4}
            \fp_gset:Nn \g_hawkdraw_path_slope_first_fp {
                \__hawkdraw_point_part_slope_arc:n {
                    \l__hawkdraw_softpath_shorten_start_arc_axes_part_fp
                }
            }
        } {
            \hawkdraw_softpath_update_arc_axes:nnnn {#1} {#2} {#3} {#4}
        }
    }
}

\cs_new_protected:Npn \hawkdraw_softpath_shorten_close_op:nn #1#2 {
    \hawkdraw_softpath_add:NNnn
        \l__hawkdraw_softpath_updated_tl
        \hawkdraw_softpath_close_op:nn {#1} {#2}
}

\cs_new_protected:Npn \hawkdraw_softpath_shorten_circle_op:nnNnn #1#2#3#4#5 {
    \hawkdraw_softpath_add:NNnn
        \l__hawkdraw_softpath_updated_tl
        \hawkdraw_softpath_circle_opi:nn {#1} {#2}
    \hawkdraw_softpath_add:NNnn
        \l__hawkdraw_softpath_updated_tl
        \hawkdraw_softpath_circle_opii:nn {#4} {#5}
}

\cs_new_protected:Npn \hawkdraw_softpath_shorten_ellipse_op:nnNnnNnn #1#2#3#4#5#6#7#8 {
    \hawkdraw_softpath_add:NNnn
        \l__hawkdraw_softpath_updated_tl
        \hawkdraw_softpath_ellipse_opi:nn {#1} {#2}
    \hawkdraw_softpath_add:NNnn
        \l__hawkdraw_softpath_updated_tl
        \hawkdraw_softpath_ellipse_opii:nn {#4} {#5}
    \hawkdraw_softpath_add:NNnn
        \l__hawkdraw_softpath_updated_tl
        \hawkdraw_softpath_ellipse_opiii:nn {#7} {#8}
}

\cs_new_protected:Npn \hawkdraw_softpath_shorten_rectangle_op:nnNnn #1#2#3#4#5 {
    \hawkdraw_softpath_add:NNnn
        \l__hawkdraw_softpath_updated_tl
        \hawkdraw_softpath_rectangle_opi:nn {#1} {#2}
    \hawkdraw_softpath_add:NNnn
        \l__hawkdraw_softpath_updated_tl
        \hawkdraw_softpath_rectangle_opii:nn {#4} {#5}
}

\cs_new_protected:Npn \hawkdraw_softpath_shorten_grid_op:nnNnnNnn #1#2#3#4#5#6#7#8 {
    \hawkdraw_softpath_add:NNnn
        \l__hawkdraw_softpath_updated_tl
        \hawkdraw_softpath_grid_opi:nn {#1} {#2}
    \hawkdraw_softpath_add:NNnn
        \l__hawkdraw_softpath_updated_tl
        \hawkdraw_softpath_grid_opii:nn {#4} {#5}
    \hawkdraw_softpath_add:NNnn
        \l__hawkdraw_softpath_updated_tl
        \hawkdraw_softpath_grid_opiii:nn {#7} {#8}
}

% =====

% Offset paths

% ===

\clist_new:N \l_hawkdraw_softpath_offset_clist
\dim_new:N \l_hawkdraw_softpath_offset_dim
\tl_new:N \l_hawkdraw_softpath_offset_updated_tl
\tl_new:N \l_hawkdraw_softpath_offset_collect_tl
\bool_new:N \l__hawkdraw_softpath_offset_moveto_bool
\fp_new:N \l_hawkdraw_softpath_offset_point_first_fp
\fp_new:N \l_hawkdraw_softpath_offset_point_last_fp
\fp_new:N \l_hawkdraw_softpath_offset_point_previous_fp
\fp_new:N \l_hawkdraw_softpath_offset_slope_next_fp

\keys_define:nn { hawkdraw / path } {
    offset                 .code:n     = {
        \clist_put_right:Nn \l_hawkdraw_softpath_update_clist { offset }
        \clist_remove_duplicates:N \l_hawkdraw_softpath_update_clist
        \clist_set:Nn \l_hawkdraw_softpath_offset_clist {#1}
    } ,
}

\cs_new_protected:Npn \hawkdraw_softpath_offset_update_fn: {
    \clist_map_inline:nn {
        moveto_op:nn , lineto_op:nn ,
        curveto_op:nnNnnNnn , arc_op:nnNnn , arc_axes_op:nnNnnNnn ,
        close:op:nn , circle_op:nnNnn , ellipse_op:nnNnnNnn ,
        rectangle_op:nnNnn , grid_op:nnNnnNnn
    } {
        \cs_set_eq:cc {
            hawkdraw_softpath_ ##1
        } {
            hawkdraw_softpath_offset_ ##1
        }
    }
    \clist_map_inline:Nn \l_hawkdraw_softpath_offset_clist {
        \dim_set:Nn \l_hawkdraw_softpath_offset_dim {##1}
        \tl_use:N \g_hawkdraw_softpath_original_tl
    }
}

\fp_new:N \l__hawkdraw_softpath_offset_corner_offset_fp
\fp_set:Nn \l__hawkdraw_softpath_offset_corner_offset_fp { 0pt , 0pt }

\cs_new_protected:Npn \__hawkdraw_softpath_offset_get_slope_next:nnN #1#2#3 {
    \token_if_eq_meaning:NNTF #3 \s__hawkdraw_softpath_end {
        \fp_set:Nn \l_hawkdraw_softpath_offset_slope_next_fp { nan }
        #2
    } {
        \__hawkdraw_softpath_offset_get_slope_next:nnNnn {#1} {#2}
    }
    #3
}

\cs_new_protected:Npn \__hawkdraw_softpath_offset_get_slope_next:nnNnn #1#2#3#4#5 {
    \token_if_eq_meaning:NNT #3 \hawkdraw_softpath_lineto_op:nn {
        \fp_set:Nn \l_hawkdraw_softpath_offset_slope_next_fp {
            \hawkdraw_calculate_slope:nn {#1} {
                #4 , #5
            }
        }
    }
    \token_if_eq_meaning:NNT #3 \hawkdraw_softpath_curveto_opi:nn {
        \fp_set:Nn \l_hawkdraw_softpath_offset_slope_next_fp {
            \hawkdraw_calculate_slope:nn {#1} {
                #4 , #5
            }
        }
    }
    \bool_lazy_or:nnTF {
        \token_if_eq_meaning_p:NN #3 \hawkdraw_softpath_arc_opi:nn
    } {
        \token_if_eq_meaning_p:NN #3 \hawkdraw_softpath_arc_axes_opi:nn
    } {
        \__hawkdraw_softpath_offset_get_slope_next_arc:nNnnNnn
            {#2} #3 {#4} {#5}
    } {
        #2#3 {#4} {#5}
    }
}

\cs_new_protected:Npn \__hawkdraw_softpath_offset_get_slope_next_arc:nNnnNnn #1#2#3#4#5#6#7 {
    \token_if_eq_meaning:NNTF #2 \hawkdraw_softpath_arc_opi:nn {
        \fp_set:Nn \l_hawkdraw_softpath_offset_slope_next_fp {
            \__hawkdraw_point_part_slope_arc:nnnnn
                { 0 } {#3} {#4} {#6} {#7}
        }
        #1#2 {#3} {#4} #5 {#6} {#7}
    } {
        \__hawkdraw_softpath_offset_get_slope_next_arc_axes:nnNnn
            {#1} { #2 {#3} {#4} #5 {#6} {#7} }
    }
}

\cs_new_protected:Npn \__hawkdraw_softpath_offset_get_slope_next_arc_axes:nnNnn #1#2#3#4#5 {
    \fp_set:Nn \l_hawkdraw_softpath_offset_slope_next_fp {
        \__hawkdraw_point_part_slope_arc_axes:nnnnn
            { 0 } {
                \tl_item:nn {#2} { 2 } , \tl_item:nn {#2} { 3 }
            } {
                \tl_item:nn {#2} { 5 } , \tl_item:nn {#2} { 6 }
            } {#4} {#5}
    }
    #1#2#3 {#4} {#5}
}

\cs_new:Npn \hawkdraw_softpath_offset_calculate_corner_offset:nnn #1#2#3 {
    \fp_if_nan:nTF {#3} {
        ( \draw_point_polar:nn {#1} { #2 + 90 } )
    } {
        \fp_compare:nNnTF {#3} = {#2} {
            ( \draw_point_polar:nn {#1} { #2 + 90 } )
        } {
            (
                \draw_point_polar:nn {
                    #1 /
                    cosd( ( #3 - #2 ) / 2 )
                } {
                    #3 - ( #3 - #2 ) / 2 + 90
                }
            )
        }
    }
}

\cs_new_protected:Npn \hawkdraw_softpath_offset_moveto_op:nn #1#2 {
    \fp_set:Nn \l_hawkdraw_softpath_offset_point_first_fp { #1 , #2 }
    \fp_set:Nn \l_hawkdraw_softpath_offset_point_last_fp { #1 , #2 }
    \fp_set:Nn \l_hawkdraw_softpath_offset_point_previous_fp { #1 , #2 }
    \fp_set:Nn \l_hawkdraw_softpath_offset_slope_next_fp { nan }
    \bool_set_true:N \l__hawkdraw_softpath_offset_moveto_bool
}

\cs_new_protected:Npn \hawkdraw_softpath_offset_lineto_op:nn #1#2 {
    \__hawkdraw_softpath_offset_get_slope_next:nnN {
        #1 , #2
    } {
        \hawkdraw_softpath_offset_lineto:n {
            #1 , #2
        }
    }
}

\cs_new_protected:Npn \hawkdraw_softpath_offset_lineto:n #1 {
    \fp_set:Nn \l__hawkdraw_softpath_offset_corner_offset_fp {
        \hawkdraw_softpath_offset_calculate_corner_offset:nnn {
            \l_hawkdraw_softpath_offset_dim
        } {
            \hawkdraw_calculate_slope:nn {
                \l_hawkdraw_softpath_offset_point_previous_fp
            } {#1}
        } {
            \l_hawkdraw_softpath_offset_slope_next_fp
        }
    }
    \fp_set:Nn \l_hawkdraw_softpath_offset_point_previous_fp {#1}
    \bool_if:NT \l__hawkdraw_softpath_offset_moveto_bool {
        \bool_set_false:N \l__hawkdraw_softpath_offset_moveto_bool
        \fp_gset:Nn \g_hawkdraw_path_point_first_fp {
            \l_hawkdraw_softpath_offset_point_first_fp + (
                \draw_point_polar:nn {
                    \l_hawkdraw_softpath_offset_dim
                } {
                    \g_hawkdraw_path_slope_first_fp + 90
                }
            )
        }
        \hawkdraw_softpath_update_moveto:n {
            \g_hawkdraw_path_point_first_fp
        }
    }
    \fp_set:Nn \l_hawkdraw_softpath_offset_point_last_fp {
        \l_hawkdraw_softpath_offset_point_previous_fp +
        \l__hawkdraw_softpath_offset_corner_offset_fp
    }
    \hawkdraw_softpath_update_lineto:n {
        \l_hawkdraw_softpath_offset_point_last_fp
    }
    \fp_gset_eq:NN \g_hawkdraw_path_point_last_fp
        \l_hawkdraw_softpath_offset_point_last_fp
}

\cs_new_protected:Npn \hawkdraw_softpath_offset_curveto_op:nnNnnNnn #1#2#3#4#5#6#7#8 {
    \__hawkdraw_softpath_offset_get_slope_next:nnN {
        \tl_if_empty:nTF {#7} {
            #4 , #5
        } {
            #7 , #8
        }
    } {
        \tl_if_empty:nTF {#7} {
            \hawkdraw_softpath_offset_curveto:nnn { #1 , #2 } { #4 , #5 } { }
        } {
            \hawkdraw_softpath_offset_curveto:nnn { #1 , #2 } { #4 , #5 } { #7 , #8 }
        }
    }
}

\cs_new_protected:Npn \hawkdraw_softpath_offset_curveto:nnn #1#2#3 {
    \fp_set:Nn \l__hawkdraw_softpath_offset_corner_offset_fp {
        \hawkdraw_softpath_offset_calculate_corner_offset:nnn {
            \l_hawkdraw_softpath_offset_dim
        } {
            \tl_if_empty:nTF {#3} {
                \hawkdraw_calculate_slope:nn {#1} {#2}
            } {
                \hawkdraw_calculate_slope:nn {#2} {#3}
            }
        } {
            \l_hawkdraw_softpath_offset_slope_next_fp
        }
    }
    \tl_if_empty:nTF {#3} {
        \__hawkdraw_softpath_offset_curve_quadratic:nnn {
            \l_hawkdraw_softpath_offset_point_previous_fp
        } {#1} {#2}
        \fp_set:Nn \l_hawkdraw_softpath_offset_point_previous_fp {#2}
    } {
        \__hawkdraw_softpath_offset_curve_cubic:nnnn {
            \l_hawkdraw_softpath_offset_point_previous_fp
        } {#1} {#2} {#3}
        \fp_set:Nn \l_hawkdraw_softpath_offset_point_previous_fp {#3}
    }
    \fp_set:Nn \l_hawkdraw_softpath_offset_point_last_fp {
        \l_hawkdraw_softpath_offset_point_previous_fp +
        \l__hawkdraw_softpath_offset_corner_offset_fp
    }
    \fp_gset_eq:NN \g_hawkdraw_path_point_last_fp
        \l_hawkdraw_softpath_offset_point_last_fp
}

% we use the same split mechanism for cubic and quadratic Béziers, as well as for arcs and allipses
\seq_new:N \l_hawkdraw_softpath_offset_construct_cubic_segments_seq

\tl_new:N \l__hawkdraw_softpath_offset_construct_cubic_previous_tl
\fp_new:N \l__hawkdraw_softpath_offset_construct_cubic_ta_fp
\fp_new:N \l__hawkdraw_softpath_offset_construct_cubic_tb_fp
\fp_new:N \l__hawkdraw_softpath_offset_construct_cubic_tc_fp

% tmpa to tmpg
\int_step_inline:nn { 7 } {
    \fp_new:c {
        l__hawkdraw_softpath_offset_construct_cubic_point_tmp \int_to_alph:n {#1 } _fp
    }
}

\cs_new_protected:Npn \__hawkdraw_softpath_offset_construct_cubic_split:nnnnn #1#2#3#4#5 {
    \fp_set:Nn \l__hawkdraw_softpath_offset_construct_cubic_point_tmpa_fp {
        ( #2 )
    }
    \fp_set:Nn \l__hawkdraw_softpath_offset_construct_cubic_point_tmpb_fp {
        % lerp(p0, p1, t);
        hawkdrawlerp ( ( #2 ) , ( #3 ) , ( #1 ) )
    }
    \fp_set:Nn \l__hawkdraw_softpath_offset_construct_cubic_point_tmpc_fp {
        % lerp( lerp(p0, p1, t) , lerp(p1, p2, t) , t);
        hawkdrawlerp (
            hawkdrawlerp ( ( #2 ) , ( #3 ) , ( #1 ) ) ,
            hawkdrawlerp ( ( #3 ) , ( #4 ) , ( #1 ) ) ,
            ( #1 )
        )
    }
    \fp_set:Nn \l__hawkdraw_softpath_offset_construct_cubic_point_tmpd_fp {
        % lerp(
        %     lerp( lerp(p0, p1, t) , lerp(p1, p2, t) , t) ,
        %     lerp( lerp(p1, p2, t) , lerp(p2, p3, t) , t)
        % t);
        hawkdrawlerp (
            hawkdrawlerp (
                hawkdrawlerp ( ( #2 ) , ( #3 ) , ( #1 ) ) ,
                hawkdrawlerp ( ( #3 ) , ( #4 ) , ( #1 ) ) ,
                ( #1 )
            ) ,
            hawkdrawlerp (
                hawkdrawlerp ( ( #3 ) , ( #4 ) , ( #1 ) ) ,
                hawkdrawlerp ( ( #4 ) , ( #5 ) , ( #1 ) ) ,
                ( #1 )
            ) ,
            ( #1 )
        )
    }
    \fp_set:Nn \l__hawkdraw_softpath_offset_construct_cubic_point_tmpe_fp {
        % lerp( lerp(p1, p2, t) , lerp(p2, p3, t) , t);
        hawkdrawlerp (
            hawkdrawlerp ( ( #3 ) , ( #4 ) , ( #1 ) ) ,
            hawkdrawlerp ( ( #4 ) , ( #5 ) , ( #1 ) ) ,
            ( #1 )
        )
    }
    \fp_set:Nn \l__hawkdraw_softpath_offset_construct_cubic_point_tmpf_fp {
        % lerp(p2, p3, t);
        hawkdrawlerp ( ( #4 ) , ( #5 ) , ( #1 ) )
    }
    \fp_set:Nn \l__hawkdraw_softpath_offset_construct_cubic_point_tmpg_fp {
        ( #5 )
    }
    \tl_set:Nn \l__hawkdraw_softpath_offset_construct_cubic_curve_i_tl {
        { \l__hawkdraw_softpath_offset_construct_cubic_point_tmpa_fp }
        { \l__hawkdraw_softpath_offset_construct_cubic_point_tmpb_fp }
        { \l__hawkdraw_softpath_offset_construct_cubic_point_tmpc_fp }
        { \l__hawkdraw_softpath_offset_construct_cubic_point_tmpd_fp }
    }
    \tl_set:Nn \l__hawkdraw_softpath_offset_construct_cubic_curve_ii_tl {
        { \l__hawkdraw_softpath_offset_construct_cubic_point_tmpd_fp }
        { \l__hawkdraw_softpath_offset_construct_cubic_point_tmpe_fp }
        { \l__hawkdraw_softpath_offset_construct_cubic_point_tmpf_fp }
        { \l__hawkdraw_softpath_offset_construct_cubic_point_tmpg_fp }
    }
}

\cs_new:Npn \__hawkdraw_softpath_offset_construct_cubic_offset:N #1 {
    {
        \fp_eval:n {
            ( \tl_item:Nn #1 { 1 } ) + (
                \draw_point_polar:nn {
                    \l_hawkdraw_softpath_offset_dim
                } {
                    \hawkdraw_calculate_slope:nn {
                        \tl_item:Nn #1 { 1 }
                    } {
                        \tl_item:Nn #1 { 2 }
                    } + 90
                }
            )
        }
    } {
        \fp_eval:n {
            ( \tl_item:Nn #1 { 2 } ) + (
                \draw_point_polar:nn {
                    \l_hawkdraw_softpath_offset_dim
                } {
                    \hawkdraw_calculate_slope:nn {
                        \tl_item:Nn #1 { 2 }
                    } {
                        \tl_item:Nn #1 { 3 }
                    } + 90
                }
            )
        }
    } {
        \fp_eval:n {
            ( \tl_item:Nn #1 { 3 } ) + (
                \draw_point_polar:nn {
                    \l_hawkdraw_softpath_offset_dim
                } {
                    \hawkdraw_calculate_slope:nn {
                        \tl_item:Nn #1 { 2 }
                    } {
                        \tl_item:Nn #1 { 3 }
                    } + 90
                }
            )
        }
    } {
        \fp_eval:n {
            ( \tl_item:Nn #1 { 4 } ) + (
                \draw_point_polar:nn {
                    \l_hawkdraw_softpath_offset_dim
                } {
                    \hawkdraw_calculate_slope:nn {
                        \tl_item:Nn #1 { 3 }
                    } {
                        \tl_item:Nn #1 { 4 }
                    } + 90
                }
            )
        }
    }
}

\tl_new:N \l__hawkdraw_softpath_offset_construct_cubic_curve_i_tl
\tl_new:N \l__hawkdraw_softpath_offset_construct_cubic_curve_ii_tl

\cs_generate_variant:Nn \seq_set_item:Nnn { Nne }

\cs_new_protected:Npn \hawkdraw_softpath_offset_construct_cubic_set_split:Nnnnnn #1#2#3#4#5#6 {
    \seq_clear:N #1
    \tl_set:Nn \l__hawkdraw_softpath_offset_construct_cubic_previous_tl {
        {#3} {#4} {#5} {#6}
    }
    \fp_set:Nn \l__hawkdraw_softpath_offset_construct_cubic_ta_fp { 0 }
    \int_step_inline:nn {#2} {
        \fp_set:Nn \l__hawkdraw_softpath_offset_construct_cubic_tb_fp {
            ##1 / #2
        }
        \fp_set:Nn \l__hawkdraw_softpath_offset_construct_cubic_tc_fp {
            hawkdrawremapt (
                \l__hawkdraw_softpath_offset_construct_cubic_ta_fp ,
                \l__hawkdraw_softpath_offset_construct_cubic_tb_fp
            )
        }
        \exp_last_unbraced:Nno
        \__hawkdraw_softpath_offset_construct_cubic_split:nnnnn {
            \l__hawkdraw_softpath_offset_construct_cubic_tc_fp
        } { \l__hawkdraw_softpath_offset_construct_cubic_previous_tl }
        \seq_put_right:Ne #1 {
            \__hawkdraw_softpath_offset_construct_cubic_offset:N
                \l__hawkdraw_softpath_offset_construct_cubic_curve_i_tl
        }
        \tl_set_eq:NN \l__hawkdraw_softpath_offset_construct_cubic_previous_tl
            \l__hawkdraw_softpath_offset_construct_cubic_curve_ii_tl
        \fp_set_eq:NN \l__hawkdraw_softpath_offset_construct_cubic_ta_fp
            \l__hawkdraw_softpath_offset_construct_cubic_tb_fp
    }
    % smoothen segment convergence
    \int_compare:nNnT { \seq_count:N #1 } > { \c_one_int } {
        \int_step_inline:nnn { 2 } { \seq_count:N #1 } {
            \tl_set:Ne \l__hawkdraw_softpath_offset_construct_cubic_curve_i_tl {
                \seq_item:Nn #1 { ##1 - 1 }
            }
            \tl_set:Ne \l__hawkdraw_softpath_offset_construct_cubic_curve_ii_tl {
                \seq_item:Nn #1 {##1}
            }
            \seq_set_item:Nne #1 { ##1 - 1 } {
                { \tl_item:Nn \l__hawkdraw_softpath_offset_construct_cubic_curve_i_tl { 1 } }
                { \tl_item:Nn \l__hawkdraw_softpath_offset_construct_cubic_curve_i_tl { 2 } }
                { \tl_item:Nn \l__hawkdraw_softpath_offset_construct_cubic_curve_i_tl { 3 } }
                {
                    \fp_eval:n {
                        (
                            \tl_item:Nn \l__hawkdraw_softpath_offset_construct_cubic_curve_i_tl { 3 } +
                            \tl_item:Nn \l__hawkdraw_softpath_offset_construct_cubic_curve_ii_tl { 2 }
                        ) / 2
                    }
                }
            }
            \seq_set_item:Nne #1 {##1} {
                {
                    \fp_eval:n {
                        (
                            \tl_item:Nn \l__hawkdraw_softpath_offset_construct_cubic_curve_i_tl { 3 } +
                            \tl_item:Nn \l__hawkdraw_softpath_offset_construct_cubic_curve_ii_tl { 2 }
                        ) / 2
                    }
                }
                { \tl_item:Nn \l__hawkdraw_softpath_offset_construct_cubic_curve_ii_tl { 2 } }
                { \tl_item:Nn \l__hawkdraw_softpath_offset_construct_cubic_curve_ii_tl { 3 } }
                { \tl_item:Nn \l__hawkdraw_softpath_offset_construct_cubic_curve_ii_tl { 4 } }
            }
        }
    }
}

\int_new:N \l_hawkdraw_softpath_bisection_count_int

\keys_define:nn { hawkdraw / settings } {
    bisection ~ count       .int_set:N  = \l_hawkdraw_softpath_bisection_count_int ,
    bisection ~ count       .initial:n  = { 3 } ,
}

\cs_new_protected:Npn \__hawkdraw_softpath_offset_curve_quadratic:nnn #1#2#3 {
    \hawkdraw_softpath_offset_construct_cubic_set_split:Nnnnnn
        \l_hawkdraw_softpath_offset_construct_cubic_segments_seq
        {
            \l_hawkdraw_softpath_bisection_count_int
        } {
            #1
        } {
            ( #1 ) + 2/3 * ( ( #2 ) - ( #1 ) )
        } {
            ( #3 ) + 2/3 * ( ( #2 ) - ( #3 ) )
        } {
            #3
        }

    \seq_map_indexed_inline:Nn \l_hawkdraw_softpath_offset_construct_cubic_segments_seq {
        \bool_if:NT \l__hawkdraw_softpath_offset_moveto_bool {
            \bool_set_false:N \l__hawkdraw_softpath_offset_moveto_bool
            \fp_gset:Nn \g_hawkdraw_path_point_first_fp {
                \tl_item:nn {##2} { 1 }
            }
            \hawkdraw_softpath_update_moveto:n {
                \g_hawkdraw_path_point_first_fp
            }
        }

        \hawkdraw_softpath_update_curveto:nnn {
            \tl_item:nn {##2} { 2 }
        } {
            \tl_item:nn {##2} { 3 }
        } {
            \int_compare:nNnTF {##1} = { \l_hawkdraw_softpath_bisection_count_int } {
                ( #3 ) +
                \l__hawkdraw_softpath_offset_corner_offset_fp
            } {
                \tl_item:nn {##2} { 4 }
            }
        }
    }
}

\cs_new_protected:Npn \__hawkdraw_softpath_offset_curve_cubic:nnnn #1#2#3#4 {
    \hawkdraw_softpath_offset_construct_cubic_set_split:Nnnnnn
        \l_hawkdraw_softpath_offset_construct_cubic_segments_seq
        {
            \l_hawkdraw_softpath_bisection_count_int
        } {
            #1
        } {
            #2
        } {
            #3
        } {
            #4
        }

    \seq_map_indexed_inline:Nn \l_hawkdraw_softpath_offset_construct_cubic_segments_seq {
        \bool_if:NT \l__hawkdraw_softpath_offset_moveto_bool {
            \bool_set_false:N \l__hawkdraw_softpath_offset_moveto_bool
            \fp_gset:Nn \g_hawkdraw_path_point_first_fp {
                \tl_item:nn {##2} { 1 }
            }
            \hawkdraw_softpath_update_moveto:n {
                \g_hawkdraw_path_point_first_fp
            }
        }

        \hawkdraw_softpath_update_curveto:nnn {
            \tl_item:nn {##2} { 2 }
        } {
            \tl_item:nn {##2} { 3 }
        } {
            \int_compare:nNnTF {##1} = { \l_hawkdraw_softpath_bisection_count_int } {
                ( #4 ) +
                \l__hawkdraw_softpath_offset_corner_offset_fp
            } {
                \tl_item:nn {##2} { 4 }
            }
        }
    }
}

\cs_new_protected:Npn \hawkdraw_softpath_offset_arc_op:nnNnn #1#2#3#4#5 {
    \__hawkdraw_softpath_offset_get_slope_next:nnN {
        \__hawkdraw_point_part_arc:nnnnnn
            { 1 } { \l_hawkdraw_softpath_offset_point_previous_fp }
            {#1} {#2} {#4} {#5}
    } {
        \hawkdraw_softpath_offset_arc_axes:nnnn
            { #1 , 0pt } { 0pt , #2 } {#4} {#5}
    }
}

\cs_new_protected:Npn \hawkdraw_softpath_offset_arc_axes_op:nnNnnNnn #1#2#3#4#5#6#7#8 {
    \__hawkdraw_softpath_offset_get_slope_next:nnN {
        \__hawkdraw_point_part_arc_axes:nnnnnn
            { 1 } { \l_hawkdraw_softpath_offset_point_previous_fp }
            { #1 , #2 } { #4 , #5 } {#7} {#8}
    } {
        \hawkdraw_softpath_offset_arc_axes:nnnn
            { #1 , #2 } { #4 , #5 } {#7} {#8}
    }
}

\cs_new_protected:Npn \hawkdraw_softpath_offset_arc_axes:nnnn #1#2#3#4 {
    \fp_set:Nn \l__hawkdraw_softpath_offset_corner_offset_fp {
        \hawkdraw_softpath_offset_calculate_corner_offset:nnn {
            \l_hawkdraw_softpath_offset_dim
        } {
            \__hawkdraw_point_part_slope_arc_axes:nnnnn
                { 1 } {#1} {#2} {#3} {#4}
        } {
            \l_hawkdraw_softpath_offset_slope_next_fp
        }
    }
    \__hawkdraw_softpath_offset_arc_axes:nnnn
        {#1} {#2} {#3} {#4}
}

\fp_new_function:n { hawkdrawbezierk }
\fp_set_function:nnn { hawkdrawbezierk } { a } {
    4 / 3 * tand( a / 4 )
}

\bool_new:N \l__hawkdraw_softpath_offset_arc_start_bool
\fp_new:N \l__hawkdraw_softpath_offset_arc_point_last_fp

\cs_new_protected:Npn \__hawkdraw_softpath_offset_construct_cubic_from_arc:nnnnn #1#2#3#4#5 {
    \hawkdraw_softpath_offset_construct_cubic_set_split:Nnnnnn
        \l_hawkdraw_softpath_offset_construct_cubic_segments_seq
        {
            \l_hawkdraw_softpath_bisection_count_int
        } {
            \__hawkdraw_point_part_arc_axes:nnnnnn
                { 0 } {#3} {#1} {#2} {#4} {#5}
        } {
            (
                \__hawkdraw_point_part_arc_axes:nnnnnn
                    { 0 } {#3} {#1} {#2} {#4} {#5}
            ) +
            hawkdrawbezierk( #5 - #4 ) * (
                ( #2 ) * cosd( #4 ) - ( #1 ) * sind( #4 )
            )
        } {
            (
                \__hawkdraw_point_part_arc_axes:nnnnnn
                    { 1 } {#3} {#1} {#2} {#4} {#5}
            ) -
            hawkdrawbezierk( #5 - #4 ) * (
                ( #2 ) * cosd( #5 ) - ( #1 ) * sind( #5 )
            )
        } {
            \__hawkdraw_point_part_arc_axes:nnnnnn
                { 1 } {#3} {#1} {#2} {#4} {#5}
        }
    \seq_map_indexed_inline:Nn \l_hawkdraw_softpath_offset_construct_cubic_segments_seq {
        \int_compare:nNnT {##1} = { \c_one_int } {
            \bool_if:NTF \l__hawkdraw_softpath_offset_moveto_bool {
                \bool_set_false:N \l__hawkdraw_softpath_offset_moveto_bool
                \fp_gset:Nn \g_hawkdraw_path_point_first_fp {
                    \tl_item:nn {##2} { 1 }
                }
                \hawkdraw_softpath_update_moveto:n {
                    \g_hawkdraw_path_point_first_fp
                }
            } {
                \bool_if:NT \l__hawkdraw_softpath_offset_arc_start_bool {
                    \bool_set_false:N \l__hawkdraw_softpath_offset_arc_start_bool
                    \hawkdraw_softpath_update_lineto:n {
                        \tl_item:nn {##2} { 1 }
                    }
                }
            }
        }

        \hawkdraw_softpath_update_curveto:nnn {
            \tl_item:nn {##2} { 2 }
        } {
            \tl_item:nn {##2} { 3 }
        } {
            \tl_item:nn {##2} { 4 }
        }
    }
    \fp_set:Nn \l__hawkdraw_softpath_offset_arc_point_last_fp {
        \__hawkdraw_point_part_arc_axes:nnnnnn
            { 1 } {#3} {#1} {#2} {#4} {#5}
    }
}

\cs_new_protected:Npn \__hawkdraw_softpath_offset_arc_axes:nnnn #1#2#3#4 {
    \fp_set:Nn \l__hawkdraw_softpath_offset_arc_point_last_fp {
        \__hawkdraw_point_part_arc_axes:nnnnnn
            { 0 } { \l_hawkdraw_softpath_offset_point_previous_fp } {#1} {#2} {#3} {#4}
    }

    \bool_set_true:N \l__hawkdraw_softpath_offset_arc_start_bool
    \__hawkdraw_softpath_offset_construct_cubic_from_arc:nnnnn {
        #1
    } {
        #2
    } {
        \l__hawkdraw_softpath_offset_arc_point_last_fp
    } {
        \fp_eval:n { #3 }
    } {
        \fp_eval:n { min( #4 , #3 + 90 ) }
    }

    \fp_compare:nNnT { #4 } > { #3 + 90 } {
        \__hawkdraw_softpath_offset_construct_cubic_from_arc:nnnnn {
            #1
        } {
            #2
        } {
            \l__hawkdraw_softpath_offset_arc_point_last_fp
        } {
            \fp_eval:n { #3 + 90 }
        } {
            \fp_eval:n { min( #4 , #3 + 180 ) }
        }
    }

    \fp_compare:nNnT { #4 } > { #3 + 180 } {
        \__hawkdraw_softpath_offset_construct_cubic_from_arc:nnnnn {
            #1
        } {
            #2
        } {
            \l__hawkdraw_softpath_offset_arc_point_last_fp
        } {
            \fp_eval:n { #3 + 180 }
        } {
            \fp_eval:n { min( #4 , #3 + 270 ) }
        }
    }

    \bool_set_true:N \l__hawkdraw_softpath_offset_arc_end_bool
    \fp_compare:nNnT { #4 } > { #3 + 270 } {
        \__hawkdraw_softpath_offset_construct_cubic_from_arc:nnnnn {
            #1
        } {
            #2
        } {
            \l__hawkdraw_softpath_offset_arc_point_last_fp
        } {
            \fp_eval:n { #3 + 270 }
        } {
            \fp_eval:n { #4 }
        }
    }

    \fp_set:Nn \l_hawkdraw_softpath_offset_point_previous_fp {
        \l__hawkdraw_softpath_offset_arc_point_last_fp
    }

    \fp_set:Nn \l_hawkdraw_softpath_offset_point_last_fp {
        \l_hawkdraw_softpath_offset_point_previous_fp +
        \l__hawkdraw_softpath_offset_corner_offset_fp
    }
    \hawkdraw_softpath_update_lineto:n {
        \l_hawkdraw_softpath_offset_point_last_fp
    }

    \fp_gset_eq:NN \g_hawkdraw_path_point_last_fp
        \l_hawkdraw_softpath_offset_point_last_fp
}

\cs_new_protected:Npn \hawkdraw_softpath_offset_circle_op:nnNnn #1#2#3#4#5 {
    \hawkdraw_softpath_update_circle:nn {
        #1 , #2
    } {
        #4 + \l_hawkdraw_softpath_offset_dim
    }
}

\cs_new_protected:Npn \hawkdraw_softpath_offset_ellipse_op:nnNnnNnn #1#2#3#4#5#6#7#8 {
    \hawkdraw_softpath_offset_ellipse:nnn { #1 , #2 } { #4 , #5 } { #7 , #8 }
}

\cs_new_protected:Npn \hawkdraw_softpath_offset_ellipse:nnn #1#2#3 {
    \fp_set:Nn \l__hawkdraw_softpath_offset_arc_point_last_fp {
        \__hawkdraw_point_part_ellipse:nnnn
            { 0 } {#1} {#2} {#3}
    }

    \int_step_inline:nn { 4 } {
        \__hawkdraw_softpath_offset_construct_cubic_from_arc:nnnnn {
            #2
        } {
            #3
        } {
            \l__hawkdraw_softpath_offset_arc_point_last_fp
        } {
            \fp_eval:n { ( ##1 - 1 ) * 90 }
        } {
            \fp_eval:n { ##1 * 90 }
        }
    }

    \hawkdraw_softpath_update_close:n {
        \l__hawkdraw_softpath_offset_arc_point_last_fp
    }
}

\cs_new_protected:Npn \hawkdraw_softpath_offset_rectangle_op:nnNnn #1#2#3#4#5 {
    \hawkdraw_softpath_update_rectangle:nn {
        #1 - \l_hawkdraw_softpath_offset_dim ,
        #2 - \l_hawkdraw_softpath_offset_dim
    } {
        #4 + 2 * \l_hawkdraw_softpath_offset_dim ,
        #5 + 2 * \l_hawkdraw_softpath_offset_dim
    }
}

\cs_new_protected:Npn \hawkdraw_softpath_offset_grid_op:nnNnnNnn #1#2#3#4#5#6#7#8 {
    \hawkdraw_softpath_update_grid:nnnn {
        #1
    } {
        #2
    } {
        #4 - \l_hawkdraw_softpath_offset_dim ,
        #5 - \l_hawkdraw_softpath_offset_dim
    } {
        #7 + 2 * \l_hawkdraw_softpath_offset_dim ,
        #8 + 2 * \l_hawkdraw_softpath_offset_dim
    }
}

\cs_new_protected:Npn \hawkdraw_softpath_offset_close_op:nn #1#2 {
    \hawkdraw_softpath_add:NNnn
        \l__hawkdraw_softpath_updated_tl
        \hawkdraw_softpath_close_op:nn {#1} {#2}
}

% EOF