% File: hawkdraw-utils.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.4.1 2026-07-08

\cs_new_protected:Npn \hawkdraw_support_pdfliteral:n #1 {
    \sys_if_engine_luatex:T {
        \tex_pdfextension:D literal {#1}
    }
    \sys_if_engine_pdftex:T {
        \tex_pdfliteral:D {#1}
    }
}
\cs_generate_variant:Nn \hawkdraw_support_pdfliteral:n { e }

\cs_new_protected:Npn \hawkdraw_support_pdflastobj: {
    \sys_if_engine_luatex:T {
        \tex_pdffeedback:D lastobj
    }
    \sys_if_engine_pdftex:T {
        \tex_pdflastobj:D
    }
}

\cs_new_protected:Npn \hawkdraw_support_pdfxform:nnN #1#2#3 {
    \bool_lazy_or:nnT {
        \sys_if_engine_luatex_p:
    } {
        \sys_if_engine_pdftex_p:
    } {
        \tex_immediate:D \tex_pdfxform:D
        \tl_if_empty:nF {#1} {
            attr {#1}
        }
        resources {#2}
        #3
    }
}

\cs_new_protected:Npn \hawkdraw_support_pdflastxform: {
    \bool_lazy_or:nnTF {
        \sys_if_engine_luatex_p:
    } {
        \sys_if_engine_pdftex_p:
    } {
        \tex_pdflastxform:D
    } { 0 }
}

\cs_new_protected:Npn \hawkdraw_support_pdfrefxform:N #1 {
    \bool_lazy_or:nnT {
        \sys_if_engine_luatex_p:
    } {
        \sys_if_engine_pdftex_p:
    } {
        \tex_pdfrefxform:D \int_use:N #1 \scan_stop:
    }
}
\cs_generate_variant:Nn \hawkdraw_support_pdfrefxform:N { c }

\cs_new_protected:Npn \hawkdraw_support_pdfcolorstack:n #1 {
    \sys_if_engine_luatex:T {
        \tex_pdfextension:D colorstack ~ #1
    }
    \sys_if_engine_pdftex:T {
        \tex_pdfcolorstack:D ~ #1
    }
}
\cs_generate_variant:Nn \hawkdraw_support_pdfcolorstack:n { e }

\cs_new_protected:Npn \hawkdraw_support_pdfcolorstackinit:n #1 {
    \bool_lazy_or:nnTF {
        \sys_if_engine_luatex_p:
    } {
        \sys_if_engine_pdftex_p:
    } {
        \sys_if_engine_luatex:T {
            \tex_pdffeedback:D colorstackinit ~ #1
        }
        \sys_if_engine_pdftex:T {
            \tex_pdfcolorstackinit:D ~ #1
        }
    } { 0 }
}

% ===

% #1 : gray
% #2 : rgb
% #3 : cmyk
\cs_new:Npn \hawkdraw_case_color_space:nnn #1#2#3 {
    \str_case:NnF \l_hawkdraw_picture_color_space_tl {
        { gray } {#1}
        { rgb } {#2}
        { cmyk } {#3}
    } {#2}
}

% ===

\cs_new:Npn \hawkdraw_color_pdf_to_hex:n #1 {
    \exp_last_unbraced:Ne
    \__hawkdraw_color_pdf_to_hex_aux:w { #1 \c_space_tl \exp_not:N \q_stop }
}
\cs_generate_variant:Nn \hawkdraw_color_pdf_to_hex:n { e }

\__hawkdraw_exp_last_unbraced:NNe
\cs_new:Npn \__hawkdraw_color_pdf_to_hex_aux:w { #1 \c_space_tl #2 \exp_not:N \q_stop } {
    \__hawkdraw_color_pdf_to_hex_auxi:n {#1}
    \tl_if_empty:nF {#2} {
        \__hawkdraw_color_pdf_to_hex_aux:w #2 \q_stop
    }
}

\cs_new:Npn \__hawkdraw_color_pdf_to_hex_auxi:n #1 {
    \int_format:nn {
        \fp_to_int:n { #1 * 255 }
    } { 0>2X }
}

% ===

\cs_new:Npn \hawkdraw_hex_to_binary:n #1 {
    \__hawkdraw_hex_to_binary:w #1 \q_stop
}
\cs_generate_variant:Nn \hawkdraw_hex_to_binary:n { e }

\cs_new:Npn \__hawkdraw_hex_to_binary:w #1 \q_stop {
     \__hawkdraw_hex_to_binary:NNw #1 \q_stop
}

\cs_new:Npn \__hawkdraw_hex_to_binary:NNw #1#2#3 \q_stop {
    \sys_if_engine_luatex:T {
        \lua_now:e {
            token.put_next (
                token.new (
                    0x110000 + \int_eval:n { " #1#2 } ,
                    12
                )
            )
        }
    }
    \sys_if_engine_pdftex:T {
        \char_generate:nn { " #1#2 } { 12 }
    }
    \tl_if_empty:nF {#3} {
        \__hawkdraw_hex_to_binary:w #3 \q_stop
    }
}

% ===

\cs_new:Npn \__hawkdraw_tuple_use_i:w ( #1 , #2 ) {#1}
\cs_new:Npn \hawkdraw_tuple_use_i:n #1 {
    \fp_to_dim:n {
        \exp_last_unbraced:Ne
            \__hawkdraw_tuple_use_i:w { \fp_eval:n {#1} }
    }
}
\cs_generate_variant:Nn \hawkdraw_tuple_use_i:n { e , V }

\cs_new:Npn \__hawkdraw_tuple_use_ii:w ( #1 , #2 ) {#2}
\cs_new:Npn \hawkdraw_tuple_use_ii:n #1 {
    \fp_to_dim:n {
        \exp_last_unbraced:Ne
            \__hawkdraw_tuple_use_ii:w { \fp_eval:n {#1} }
    }
}
\cs_generate_variant:Nn \hawkdraw_tuple_use_ii:n { e , V }

\cs_new:Npn \__hawkdraw_cs_if_exist_use_secure:nnTF #1#2#3#4 {
    \exp_last_unbraced:Ne
    \token_if_cs:NTF { \tl_head:n {#1} } {#4} {
        \cs_if_exist_use:cTF {#2} {#3} {#4}
    }
}

\cs_new:Npn \__hawkdraw_cs_if_exist_use_secure:nnT #1#2#3 {
    \exp_last_unbraced:Ne
    \token_if_cs:NF { \tl_head:n {#1} } {
        \cs_if_exist_use:cT {#2} {#3}
    }
}

% =====

% Calculation of slopes and points on path segments

% ===

\cs_new:Npn \hawkdraw_calculate_veclen:nn #1#2 {
    \fp_eval:n {
        sqrt (
            (
                \hawkdraw_tuple_use_i:n {#2} -
                \hawkdraw_tuple_use_i:n {#1}
            ) ^ 2 +
            (
                \hawkdraw_tuple_use_ii:n {#2} -
                \hawkdraw_tuple_use_ii:n {#1}
            ) ^ 2
        )
    }
}

\cs_new:Npn \hawkdraw_calculate_slope:nn #1#2 {
    \fp_eval:n {
        atand(
            \hawkdraw_tuple_use_ii:n {#2} -
            \hawkdraw_tuple_use_ii:n {#1}
        ,
            \hawkdraw_tuple_use_i:n {#2} -
            \hawkdraw_tuple_use_i:n {#1}
        )
    }
}

% ===

\cs_set_eq:NN \__hawkdraw_point_part_line:nnn
    \draw_point_interpolate_line:nnn

\cs_new:Npn \__hawkdraw_point_part_line:n #1 {
    \__hawkdraw_point_part_line:nnn {#1}
        { \l__hawkdraw_point_o_fp }
        { \l__hawkdraw_point_a_fp }
}
\cs_generate_variant:Nn \__hawkdraw_point_part_line:n { V }

\cs_new:Npn \__hawkdraw_point_part_slope_line:nnn #1#2#3 {
    \hawkdraw_calculate_slope:nn {#2} {#3}
}

\cs_new:Npn \__hawkdraw_point_part_slope_line:n #1 {
    \__hawkdraw_point_part_slope_line:nnn
        {#1}
        { \l__hawkdraw_point_o_fp }
        { \l__hawkdraw_point_a_fp }
}
\cs_generate_variant:Nn \__hawkdraw_point_part_slope_line:n { V }

% =

\cs_new:Npn \__hawkdraw_point_part_line_orthogonal:nnnn #1#2#3#4 {
    \fp_compare:nNnTF {#1} > { 0.5 } {
        \__hawkdraw_point_part_line:nnn { 2 * ( #1 - 0.5 ) }
            {#3} {#4}
    } {
        \__hawkdraw_point_part_line:nnn { 2 * #1 }
            {#2} {#3}
    }
}

\cs_new:Npn \__hawkdraw_point_part_line_orthogonal:n #1 {
    \__hawkdraw_point_part_line_orthogonal:nnnn
        {#1}
        { \l__hawkdraw_point_o_fp }
        { \l__hawkdraw_point_a_fp }
        { \l__hawkdraw_point_b_fp }
}
\cs_generate_variant:Nn \__hawkdraw_point_part_line_orthogonal:n { V }

\cs_new:Npn \__hawkdraw_point_part_slope_line_orthogonal:nnnn #1#2#3#4 {
    \fp_compare:nNnTF {#1} > { 0.5 } {
        \hawkdraw_calculate_slope:nn {#3} {#4}
    } {
        \hawkdraw_calculate_slope:nn {#2} {#3}
    }
}

\cs_new:Npn \__hawkdraw_point_part_slope_line_orthogonal:n #1 {
    \__hawkdraw_point_part_slope_line_orthogonal:nnnn
        {#1}
        { \l__hawkdraw_point_o_fp }
        { \l__hawkdraw_point_a_fp }
        { \l__hawkdraw_point_b_fp }
}
\cs_generate_variant:Nn \__hawkdraw_point_part_slope_line_orthogonal:n { V }

% =

\cs_set_eq:NN \__hawkdraw_point_part_curve_quadratic:nnnn
    \draw_point_interpolate_curve:nnnn

\cs_new:Npn \__hawkdraw_point_part_curve_quadratic:n #1 {
    \__hawkdraw_point_part_curve_quadratic:nnnn
        {#1}
        { \l__hawkdraw_point_o_fp }
        { \l__hawkdraw_point_a_fp }
        { \l__hawkdraw_point_b_fp }
}
\cs_generate_variant:Nn \__hawkdraw_point_part_curve_quadratic:n { V }

\cs_new:Npn \__hawkdraw_point_part_slope_curve_quadratic:nnnn #1#2#3#4 {
    \fp_eval:n {
        atand(
            (
                (
                    \hawkdraw_tuple_use_ii:n {#3} -
                    \hawkdraw_tuple_use_ii:n {#2}
                ) * ( 1 - #1 ) +
                (
                    \hawkdraw_tuple_use_ii:n {#4} -
                    \hawkdraw_tuple_use_ii:n {#3}
                ) * #1
            ) * 2
        ,
            (
                (
                    \hawkdraw_tuple_use_i:n {#3} -
                    \hawkdraw_tuple_use_i:n {#2}
                ) * ( 1 - #1 ) +
                (
                    \hawkdraw_tuple_use_i:n {#4} -
                    \hawkdraw_tuple_use_i:n {#3}
                ) * #1
            ) * 2
        )
    }
}

\cs_new:Npn \__hawkdraw_point_part_slope_curve_quadratic:n #1 {
    \__hawkdraw_point_part_slope_curve_quadratic:nnnn
        {#1}
        { \l__hawkdraw_point_o_fp }
        { \l__hawkdraw_point_a_fp }
        { \l__hawkdraw_point_b_fp }
}
\cs_generate_variant:Nn \__hawkdraw_point_part_slope_curve_quadratic:n { V }

% =

\cs_set_eq:NN \__hawkdraw_point_part_curve_cubic:nnnnn
    \draw_point_interpolate_curve:nnnnn

\cs_new:Npn \__hawkdraw_point_part_curve_cubic:n #1 {
    \__hawkdraw_point_part_curve_cubic:nnnnn
        {#1}
        { \l__hawkdraw_point_o_fp }
        { \l__hawkdraw_point_a_fp }
        { \l__hawkdraw_point_b_fp }
        { \l__hawkdraw_point_c_fp }
}
\cs_generate_variant:Nn \__hawkdraw_point_part_curve_cubic:n { V }

\cs_new:Npn \__hawkdraw_point_part_slope_curve_cubic:nnnnn #1#2#3#4#5 {
    \fp_eval:n {
        atand(
            (
                (
                    \hawkdraw_tuple_use_ii:n {#3} -
                    \hawkdraw_tuple_use_ii:n {#2}
                ) * ( 1 - #1 ) * ( 1 - #1 ) +
                (
                    \hawkdraw_tuple_use_ii:n {#4} -
                    \hawkdraw_tuple_use_ii:n {#3}
                ) * ( 1 - #1 ) * #1 * 2 +
                (
                    \hawkdraw_tuple_use_ii:n {#5} -
                    \hawkdraw_tuple_use_ii:n {#4}
                ) * #1 * #1
            ) * 3
        ,
            (
                (
                    \hawkdraw_tuple_use_i:n {#3} -
                    \hawkdraw_tuple_use_i:n {#2}
                ) * ( 1 - #1 ) * ( 1 - #1 ) +
                (
                    \hawkdraw_tuple_use_i:n {#4} -
                    \hawkdraw_tuple_use_i:n {#3}
                ) * ( 1 - #1 ) * #1 * 2 +
                (
                    \hawkdraw_tuple_use_i:n {#5} -
                    \hawkdraw_tuple_use_i:n {#4}
                ) * #1 * #1
            ) * 3
        )
    }
}

\cs_new:Npn \__hawkdraw_point_part_slope_curve_cubic:n #1 {
    \__hawkdraw_point_part_slope_curve_cubic:nnnnn
        {#1}
        { \l__hawkdraw_point_o_fp }
        { \l__hawkdraw_point_a_fp }
        { \l__hawkdraw_point_b_fp }
        { \l__hawkdraw_point_c_fp }
}
\cs_generate_variant:Nn \__hawkdraw_point_part_slope_curve_cubic:n { V }

% =

\cs_new:Npn \__hawkdraw_point_part_arc:nnnnnn #1#2#3#4#5#6 {
    \fp_eval:n {
        ( #2 ) +
        (
            \draw_point_polar:nnn {#3} {#4} { #5 + #1 * ( #6 - #5 ) }
        ) -
        (
            \draw_point_polar:nnn {#3} {#4} {#5}
        )
    }
}

\cs_new:Npn \__hawkdraw_point_part_arc_axes:nnnnnn #1#2#3#4#5#6 {
    \draw_point_interpolate_arc_axes:nnnnnn
        {#1}
        {
            ( #2 ) +
            (
                \hawkdraw_tuple_use_i:n {#3} * cosd( #5 ) +
                \hawkdraw_tuple_use_i:n {#4} * sind( #5 )
            ,
                \hawkdraw_tuple_use_ii:n {#3} * cosd( #5 ) +
                \hawkdraw_tuple_use_ii:n {#4} * sind( #5 )
            ) * -1
        }
        {#3} {#4} {#5} {#6}
}

\cs_new:Npn \__hawkdraw_point_part_arc:n #1 {
    \bool_if:NTF \l_hawkdraw_path_arc_axes_bool {
        \__hawkdraw_point_part_arc_axes:nnnnnn
            {#1}
            { \l__hawkdraw_point_o_fp }
            { \l_hawkdraw_path_arc_vector_a_fp }
            { \l_hawkdraw_path_arc_vector_b_fp }
            { \l_hawkdraw_path_arc_angle_start_fp }
            { \l_hawkdraw_path_arc_angle_end_fp }
    } {
        \__hawkdraw_point_part_arc:nnnnnn
            {#1}
            { \l__hawkdraw_point_o_fp }
            { \l_hawkdraw_path_arc_radius_x_dim }
            { \l_hawkdraw_path_arc_radius_y_dim }
            { \l_hawkdraw_path_arc_angle_start_fp }
            { \l_hawkdraw_path_arc_angle_end_fp }
    }
}
\cs_generate_variant:Nn \__hawkdraw_point_part_arc:n { V }

\cs_new:Npn \__hawkdraw_point_part_slope_arc:nnnnn #1#2#3#4#5 {
    \fp_eval:n {
        atand(
            #2 * sind( #4 + #1 * ( #5 - #4 ) )
        ,
            #3 * cosd( #4 + #1 * ( #5 - #4 ) )
        ) + 90
    }
}

\cs_new:Npn \__hawkdraw_point_part_slope_arc_axes:nnnnn #1#2#3#4#5 {
    \fp_eval:n {
        atand(
            \hawkdraw_tuple_use_ii:n {#3} *
            cosd( #4 + #1 * ( #5 - #4 ) ) -
            \hawkdraw_tuple_use_ii:n {#2} *
            sind( #4 + #1 * ( #5 - #4 ) )
        ,
            \hawkdraw_tuple_use_i:n {#3} *
            cosd( #4 + #1 * ( #5 - #4 ) ) -
            \hawkdraw_tuple_use_i:n {#2} *
            sind( #4 + #1 * ( #5 - #4 ) )
        )
    }
}

\cs_new:Npn \__hawkdraw_point_part_slope_arc:n #1 {
    \bool_if:NTF \l_hawkdraw_path_arc_axes_bool {
        \__hawkdraw_point_part_slope_arc_axes:nnnnn
            {#1}
            { \l_hawkdraw_path_arc_vector_a_fp }
            { \l_hawkdraw_path_arc_vector_b_fp }
            { \l_hawkdraw_path_arc_angle_start_fp }
            { \l_hawkdraw_path_arc_angle_end_fp }
    } {
        \__hawkdraw_point_part_slope_arc:nnnnn
            {#1}
            { \l_hawkdraw_path_arc_radius_x_dim }
            { \l_hawkdraw_path_arc_radius_y_dim }
            { \l_hawkdraw_path_arc_angle_start_fp }
            { \l_hawkdraw_path_arc_angle_end_fp }
    }
}
\cs_generate_variant:Nn \__hawkdraw_point_part_slope_arc:n { V }

% =

\cs_new:Npn \__hawkdraw_point_part_circle:nn #1#2 {
    \draw_point_polar:nn {#2} { #1 * 360 }
}

\cs_new:Npn \__hawkdraw_point_part_circle:n #1 {
    \__hawkdraw_point_part_circle:nn { #1 } { \l_hawkdraw_path_circle_radius_dim }
}
\cs_generate_variant:Nn \__hawkdraw_point_part_circle:n { V }

\cs_new:Npn \__hawkdraw_point_part_slope_circle:nn #1#2 {
    \fp_eval:n { #1 * 360 + 90 }
}

\cs_new:Npn \__hawkdraw_point_part_slope_circle:n #1 {
    \__hawkdraw_point_part_slope_circle:nn {#1} { }
}
\cs_generate_variant:Nn \__hawkdraw_point_part_slope_circle:n { V }

% =

\cs_new:Npn \__hawkdraw_point_part_ellipse:nnnn #1#2#3#4 {
    \draw_point_interpolate_arc_axes:nnnnnn
        {#1} {#2} {#3} {#4} { 0 } { 360 }
}

\cs_new:Npn \__hawkdraw_point_part_ellipse:n #1 {
    \__hawkdraw_point_part_ellipse:nnnn
        {#1}
        { \l__hawkdraw_point_o_fp }
        { \l_hawkdraw_path_ellipse_vector_a_fp }
        { \l_hawkdraw_path_ellipse_vector_b_fp }
}
\cs_generate_variant:Nn \__hawkdraw_point_part_ellipse:n { V }

\cs_new:Npn \__hawkdraw_point_part_slope_ellipse:nnnn #1#2#3#4 {
    \fp_eval:n {
        atand(
            \hawkdraw_tuple_use_ii:n {#4} *
            cosd( #1 * 360 ) -
            \hawkdraw_tuple_use_ii:n {#3} *
            sind( #1 * 360 )
        ,
            \hawkdraw_tuple_use_i:n {#4} *
            cosd( #1 * 360 ) -
            \hawkdraw_tuple_use_i:n {#3} *
            sind( #1 * 360 )
        )
    }
}

\cs_new:Npn \__hawkdraw_point_part_slope_ellipse:n #1 {
    \__hawkdraw_point_part_slope_ellipse:nnnn
        {#1}
        { \l__hawkdraw_point_o_fp }
        { \l_hawkdraw_path_ellipse_vector_a_fp }
        { \l_hawkdraw_path_ellipse_vector_b_fp }
}
\cs_generate_variant:Nn \__hawkdraw_point_part_slope_ellipse:n { V }

% =

\cs_new:Npn \__hawkdraw_point_part_rectangle:nnn #1#2#3 {
    \fp_compare:nNnTF {#1} > { 0.25 } {
        \fp_compare:nNnTF {#1} > { 0.5 } {
            \fp_compare:nNnTF {#1} > { 0.75 } {
                \draw_point_interpolate_line:nnn { 4 * ( #1 - 0.75 ) }
                    {
                        \hawkdraw_tuple_use_i:n {#3}
                    ,
                        \hawkdraw_tuple_use_ii:n {#2}
                    }
                    {#2}
            } {
                \draw_point_interpolate_line:nnn { 4 * ( #1 - 0.5 ) }
                    {#3}
                    {
                        \hawkdraw_tuple_use_i:n {#3}
                    ,
                        \hawkdraw_tuple_use_ii:n {#2}
                    }
            }
        } {
            \draw_point_interpolate_line:nnn { 4 * ( #1 - 0.25 ) }
                {
                    \hawkdraw_tuple_use_i:n {#2}
                ,
                    \hawkdraw_tuple_use_ii:n {#3}
                }
                {#3}
        }
    } {
        \draw_point_interpolate_line:nnn { 4 * #1 }
            {#2}
            {
                \hawkdraw_tuple_use_i:n {#2}
            ,
                \hawkdraw_tuple_use_ii:n {#3}
            }
    }
}
\cs_new:Npn \__hawkdraw_point_part_rectangle:n #1 {
    \__hawkdraw_point_part_rectangle:nnn
        {#1}
        { \l__hawkdraw_point_o_fp }
        { \l__hawkdraw_point_a_fp }
}
\cs_generate_variant:Nn \__hawkdraw_point_part_rectangle:n { V }

\cs_new:Npn \__hawkdraw_point_part_slope_rectangle:nnn #1#2#3 {
    \fp_compare:nNnTF {#1} > { 0.25 } {
        \fp_compare:nNnTF {#1} > { 0.5 } {
            \fp_compare:nNnTF {#1} > { 0.75 } {
                \fp_eval:n { 180 }
            } {
                \fp_eval:n { 270 }
            }
        } {
            \fp_eval:n { 0 }
        }
    } {
        \fp_eval:n { 90 }
    }
}

\cs_new:Npn \__hawkdraw_point_part_slope_rectangle:n #1 {
    \__hawkdraw_point_part_rectangle:nnn
        {#1}
        { \l__hawkdraw_point_o_fp }
        { \l__hawkdraw_point_a_fp }
}
\cs_generate_variant:Nn \__hawkdraw_point_part_slope_rectangle:n { V }

% =

\cs_set_eq:NN \__hawkdraw_point_part_grid:nnn \__hawkdraw_point_part_rectangle:nnn
\cs_set_eq:NN \__hawkdraw_point_part_grid:n \__hawkdraw_point_part_rectangle:n
\cs_generate_variant:Nn \__hawkdraw_point_part_grid:n { V }

\cs_set_eq:NN \__hawkdraw_point_part_slope_grid:nnn \__hawkdraw_point_part_slope_rectangle:nnn
\cs_set_eq:NN \__hawkdraw_point_part_slope_grid:n \__hawkdraw_point_part_slope_rectangle:n
\cs_generate_variant:Nn \__hawkdraw_point_part_slope_grid:n { V }

% =====

% Calculation of path length

% ===

\int_new:N \l_hawkdraw_path_utils_length_precision_int

\keys_define:nn { hawkdraw / settings } {
    length ~ precision      .int_set:N  = \l_hawkdraw_path_utils_length_precision_int ,
    length ~ precision      .initial:n  = { 25 } ,
}

\cs_new:Npn \__hawkdraw_path_length_line:nn #1#2 {
    \hawkdraw_calculate_veclen:nn {#1} {#2}
}

\cs_new:Npn \__hawkdraw_path_length_curve_quadratic:nnn #1#2#3 {
    \fp_eval:n {
        0pt
        \int_step_tokens:nn { \l_hawkdraw_path_utils_length_precision_int } {
            \__hawkdraw_path_length_curve_quadratic_aux:nnnn
                {#1} {#2} {#3}
        }
    }
}
\cs_new:Npn \__hawkdraw_path_length_curve_quadratic_aux:nnnn #1#2#3#4 {
    +
    \hawkdraw_calculate_veclen:nn {
        \__hawkdraw_point_part_curve_quadratic:nnnn
            { ( #4 - 1 ) / \l_hawkdraw_path_utils_length_precision_int }
            {#1} {#2} {#3}
    } {
        \__hawkdraw_point_part_curve_quadratic:nnnn
            { #4 / \l_hawkdraw_path_utils_length_precision_int }
            {#1} {#2} {#3}
    }
}

\cs_new:Npn \__hawkdraw_path_length_curve_cubic:nnnn #1#2#3#4 {
    \fp_eval:n {
        0pt
        \int_step_tokens:nn { \l_hawkdraw_path_utils_length_precision_int } {
            \__hawkdraw_path_length_curve_cubic_aux:nnnnn
                {#1} {#2} {#3} {#4}
        }
    }
}
\cs_new:Npn \__hawkdraw_path_length_curve_cubic_aux:nnnnn #1#2#3#4#5 {
    +
    \hawkdraw_calculate_veclen:nn {
        \__hawkdraw_point_part_curve_cubic:nnnnn
            { ( #5 - 1 ) / \l_hawkdraw_path_utils_length_precision_int }
            {#1} {#2} {#3} {#4}
    } {
        \__hawkdraw_point_part_curve_cubic:nnnnn
            { #5 / \l_hawkdraw_path_utils_length_precision_int }
            {#1} {#2} {#3} {#4}
    }
}

% chord = (p3-p0).Length;
% control_net = (p0 - p1).Length + (p2 - p1).Length + (p3 - p2).Length;
% approx_length = (control_net + chord) / 2;
% split for more accuracy

\cs_new:Npn \__hawkdraw_path_length_arc:nnnn #1#2#3#4 {
    \fp_eval:n {
        0pt
        \int_step_tokens:nn { \l_hawkdraw_path_utils_length_precision_int } {
            \__hawkdraw_path_length_arc_aux:nnnnn
                {#1} {#2} {#3} {#4}
        }
    }
}
\cs_new:Npn \__hawkdraw_path_length_arc_aux:nnnnn #1#2#3#4#5 {
    +
    \hawkdraw_calculate_veclen:nn {
        \__hawkdraw_point_part_arc:nnnnn
            { ( #5 - 1 ) / \l_hawkdraw_path_utils_length_precision_int }
            {#1} {#2} {#3} {#4}
    } {
        \__hawkdraw_point_part_arc:nnnnn
            { #5 / \l_hawkdraw_path_utils_length_precision_int }
            {#1} {#2} {#3} {#4}
    }
}

\cs_new:Npn \__hawkdraw_path_length_arc_axes:nnnn #1#2#3#4 {
    \fp_eval:n {
        0pt
        \int_step_tokens:nn { \l_hawkdraw_path_utils_length_precision_int } {
            \__hawkdraw_path_length_arc_axes_aux:nnnnn
                {#1} {#2} {#3} {#4}
        }
    }
}
\cs_new:Npn \__hawkdraw_path_length_arc_axes_aux:nnnnn #1#2#3#4#5 {
    +
    \hawkdraw_calculate_veclen:nn {
        \__hawkdraw_point_part_arc_axes:nnnnn
            { ( #5 - 1 ) / \l_hawkdraw_path_utils_length_precision_int }
            {#1} {#2} {#3} {#4}
    } {
        \__hawkdraw_point_part_arc_axes:nnnnn
            { #5 / \l_hawkdraw_path_utils_length_precision_int }
            {#1} {#2} {#3} {#4}
    }
}

\cs_new:Npn \__hawkdraw_path_length_circle:n #1 {
    \fp_eval:n { 2 * pi * #1 }
}

\cs_new:Npn \__hawkdraw_path_length_ellipse:nnn #1#2#3 {
    \fp_eval:n {
        0pt
        \int_step_tokens:nn { \l_hawkdraw_path_utils_length_precision_int } {
            \__hawkdraw_path_length_ellipse_aux:nnnn
                {#1} {#2} {#3}
        }
    }
}
\cs_new:Npn \__hawkdraw_path_length_ellipse_aux:nnnn #1#2#3#4 {
    +
    \hawkdraw_calculate_veclen:nn {
        \__hawkdraw_point_part_ellipse:nnnn
            { ( #4 - 1 ) / \l_hawkdraw_path_utils_length_precision_int }
            {#1} {#2} {#3}
    } {
        \__hawkdraw_point_part_ellipse:nnnn
            { #4 / \l_hawkdraw_path_utils_length_precision_int }
            {#1} {#2} {#3}
    }
}

\cs_new:Npn \__hawkdraw_path_length_rectangle:nn #1#2 {
    \fp_eval:n {
        2 * (
            \hawkdraw_tuple_use_i:n {#2} -
            \hawkdraw_tuple_use_i:n {#1}
        ) +
        2 * (
            \hawkdraw_tuple_use_ii:n {#2} -
            \hawkdraw_tuple_use_ii:n {#1}
        )
    }
}

\cs_set_eq:NN \__hawkdraw_path_length_grid:nn \__hawkdraw_path_length_rectangle:nn

% ===

\tl_new:N \l_hawkdraw_softpath_intermediate_tl
\fp_new:N \l_hawkdraw_softpath_point_last_fp
\fp_new:N \l_hawkdraw_path_length_fp

\cs_new_protected:Npn \hawkdraw_path_get_length: {
    \group_begin:
        \fp_zero:N \l_hawkdraw_path_length_fp
        \fp_set:Nn \l_hawkdraw_softpath_point_last_fp { 0pt , 0pt }
        \tl_build_get_intermediate:NN \g_hawkdraw_softpath_original_tl
            \l_hawkdraw_softpath_intermediate_tl
        \clist_map_inline:nn {
            close_op:nn , moveto_op:nn , lineto_op:nn ,
            curveto_op:nnNnnNnn , arc_op:nnNnn , arc_axes_op:nnNnnNnn ,
            circle_op:nnNnn , ellipse_op:nnNnnNnn ,
            rectangle_op:nnNnn , grid_op:nnNnnNnn
        } {
            \cs_set_eq:cc {
                hawkdraw_softpath_ ##1
            } {
                __hawkdraw_path_get_length_ ##1
            }
        }
        \tl_use:N \l_hawkdraw_softpath_intermediate_tl
        \fp_gset_eq:NN \g_hawkdraw_path_length_fp \l_hawkdraw_path_length_fp
    \group_end:
}

\cs_new_protected:Npn \__hawkdraw_path_get_length_close_op:nn #1#2 {
    \fp_add:Nn \l_hawkdraw_path_length_fp {
        \__hawkdraw_path_length_line:nn {
            \l_hawkdraw_softpath_point_last_fp
        } { #1 , #2 }
    }
    \fp_set:Nn \l_hawkdraw_softpath_point_last_fp { #1 , #2 }
}

\cs_new_protected:Npn \__hawkdraw_path_get_length_moveto_op:nn #1#2 {
    \fp_set:Nn \l_hawkdraw_softpath_point_last_fp { #1 , #2 }
}

\cs_new_protected:Npn \__hawkdraw_path_get_length_lineto_op:nn #1#2 {
    \fp_add:Nn \l_hawkdraw_path_length_fp {
        \__hawkdraw_path_length_line:nn {
            \l_hawkdraw_softpath_point_last_fp
        } { #1 , #2 }
    }
    \fp_set:Nn \l_hawkdraw_softpath_point_last_fp { #1 , #2 }
}

\cs_new_protected:Npn \__hawkdraw_path_get_length_curveto_op:nnNnnNnn #1#2#3#4#5#6#7#8 {
    \tl_if_empty:nTF {#7} {
        \fp_add:Nn \l_hawkdraw_path_length_fp {
            \__hawkdraw_path_length_curve_quadratic:nnn {
                \l_hawkdraw_softpath_point_last_fp
            } { #1 , #2 } { #4 , #5 }
        }
    } {
        \fp_add:Nn \l_hawkdraw_path_length_fp {
            \__hawkdraw_path_length_curve_cubic:nnnn {
                \l_hawkdraw_softpath_point_last_fp
            } { #1 , #2 } { #4 , #5 } { #7 , #8 }
        }
    }
    \fp_set:Nn \l_hawkdraw_softpath_point_last_fp { #1 , #2 }
}

\cs_new_protected:Npn \__hawkdraw_path_get_length_arc_op:nnNnn #1#2#3#4#5 {
    \fp_add:Nn \l_hawkdraw_path_length_fp {
        \__hawkdraw_path_length_arc:nnnn
            {#1} {#2} {#4} {#5}
    }
    \fp_set:Nn \l_hawkdraw_softpath_point_last_fp {
        \l_hawkdraw_softpath_point_last_fp + (
            \__hawkdraw_point_part_arc:nnnnn
                { 1 } {#1} {#2} {#4} {#5}
        )
    }
}

\cs_new_protected:Npn \__hawkdraw_path_get_length_arc_axes_op:nnNnnNnn #1#2#3#4#5#6#7#8 {
    \fp_add:Nn \l_hawkdraw_path_length_fp {
        \__hawkdraw_path_length_arc_axes:nnnn
            { #1 , #2 } { #4 , #5 } {#7} {#8}
    }
    \fp_set:Nn \l_hawkdraw_softpath_point_last_fp {
        \l_hawkdraw_softpath_point_last_fp + (
            \__hawkdraw_point_part_arc_axes:nnnnn
                { 1 } { #1 , #2 } { #4 , #5 } {#7} {#8}
        )
    }
}

\cs_new_protected:Npn \__hawkdraw_path_get_length_circle_op:nnNnn #1#2#3#4#5 {
    \fp_add:Nn \l_hawkdraw_path_length_fp {
        \__hawkdraw_path_length_circle:n {#4}
    }
}

\cs_new_protected:Npn \__hawkdraw_path_get_length_ellipse_op:nnNnnNnn #1#2#3#4#5#6#7#8 {
    \fp_add:Nn \l_hawkdraw_path_length_fp {
        \__hawkdraw_path_length_ellipse:nnn { #1 , #2 } { #4 , #5 } { #7 , #8 }
    }
}

\cs_new_protected:Npn \__hawkdraw_path_get_length_rectangle_op:nnNnn #1#2#3#4#5 {
    \fp_add:Nn \l_hawkdraw_path_length_fp {
        \__hawkdraw_path_length_rectangle:nn { #1 , #2 } { #4 , #5 }
    }
    \fp_set:Nn \l_hawkdraw_softpath_point_last_fp { #4 , #5 }
}

\cs_new_protected:Npn \__hawkdraw_path_get_length_grid_op:nnNnnNnn #1#2#3#4#5#6#7#8 {
    \fp_add:Nn \l_hawkdraw_path_length_fp {
        \__hawkdraw_path_length_grid:nn { #4 , #5 } { #7 , #8 }
    }
    \fp_set:Nn \l_hawkdraw_softpath_point_last_fp { #7 , #8 }
}

% EOF