% File: hawkdraw.sty
% 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'.
%
\ProvidesExplPackage {hawkdraw} {2026-08-20} {0.7.1}
    {A package for drawing vector graphics with the l3draw package and a TikZ-like syntax.}

\msg_new:nnn { hawkdraw } { old-kernel } {
    LaTeX ~ kernel ~ too ~ old. \iow_newline:
    The ~ hawkdraw ~ package ~ does ~ not ~ support ~ this ~ LaTeX ~ version. \iow_newline:
    Please ~ update ~ to ~ a ~ newer ~ version.
}

\msg_new:nnn { hawkdraw } { documentmetadata-required } {
    \token_to_str:N \DocumentMetadata \c_space_tl ~ must ~ be ~ set ~
    to ~ enable ~ full ~ functionality.
}

\msg_new:nnn { hawkdraw } { feature-unsupported } {
    #1 : ~ Feature ~ not ~ supported ~ with ~ #2.
}

\msg_new:nnn { hawkdraw } { option-unsupported } {
    Option . `#1` ~ only ~ supported ~ with ~ LuaTeX.
}

\cs_new_protected:Npn \__hawkdraw_warning_unsupported:nn #1#2 {
    \bool_lazy_or:nnF {
        \sys_if_engine_luatex_p:
    } {
        \sys_if_engine_pdftex_p:
    } {
        \msg_warning:nnnn { hawkdraw } { feature-unsupported } {#1} {#2}
    }
}

\NeedsTeXFormat { LaTeX2e } [ 2026-05-26 ]

\cs_if_exist:NF \ProcessKeyOptions {
    \msg_critical:nn { hawkdraw } { old-kernel }
}

% ===

\bool_new:N \l__hawkdraw_bidi_bool
\tl_new:N \l__hawkdraw_bidi_bodydir_tl
\tl_new:N \l__hawkdraw_bidi_pardir_tl
\tl_new:N \l__hawkdraw_bidi_textdir_tl

\keys_define:nn { hawkdraw / global } {
    bidi                    .bool_set:N = \l__hawkdraw_bidi_bool ,
    bidi                    .default:n  = { true } ,
    bidi                    .initial:n  = { false }
}

\ProcessKeyOptions [ hawkdraw / global ]

\bool_if:NT \l__hawkdraw_bidi_bool {
    \sys_if_engine_luatex:TF {
        \hook_gput_code:nnn { env / hawkdraw / begin } { . } {
            \tl_set:NV \l__hawkdraw_bidi_bodydir_tl \tex_bodydirection:D
            \tl_set:NV \l__hawkdraw_bidi_pardir_tl \tex_pardirection:D
            \tl_set:NV \l__hawkdraw_bidi_textdir_tl \tex_textdirection:D
            \tex_bodydirection:D 0
            \tex_pardirection:D 0
            \tex_textdirection:D 0
        }
        \hook_gput_code:nnn { hawkdraw / node / begin } { . } {
            \tex_bodydirection:D \l__hawkdraw_bidi_bodydir_tl
            \tex_pardirection:D \l__hawkdraw_bidi_pardir_tl
            \tex_textdirection:D \l__hawkdraw_bidi_textdir_tl
        }
    }
} {
    \msg_warning:nnn { hawkdraw } { option-unsupported } { bidi }
}

\RequirePackage { l3draw }

% ===

\cs_generate_variant:Nn \prg_new_conditional:Npnn { Npne }

\cs_new:Npn \__hawkdraw_exp_last_unbraced:NNe #1#2#3 {
    \use:e {
        \exp_not:N #1 \exp_not:N #2 #3
    }
}

\cs_new:Npn \__hawkdraw_exp_last_unbraced:Nee #1#2#3 {
    \use:e {
        \exp_not:N #1 {#2} #3
    }
}

% ===

\cs_new:Npn \hawkdraw_tag_bb_attribute_use: {
    \tag_struct_gput:ene { \tag_get:n { struct_num } } { attribute } {
        /O /Layout /BBox ~ [
            \dim_to_decimal_in_bp:n {
                \property_ref:een {
                    draw. \int_use:N \g_draw_id_int
                } { xpos } { 0 } sp
                +
                \g_draw_bb_xmin_dim
            }
            \c_space_tl
            \dim_to_decimal_in_bp:n {
                \property_ref:een {
                    draw. \int_use:N \g_draw_id_int
                } { ypos } { 0 } sp
                +
                \g_draw_bb_ymin_dim
            }
            \c_space_tl
            \dim_to_decimal_in_bp:n {
                \property_ref:een {
                    draw. \int_use:N \g_draw_id_int
                } { xpos } { 0 } sp
                +
                \g_draw_bb_xmax_dim
            }
            \c_space_tl
            \dim_to_decimal_in_bp:n {
                \property_ref:een {
                    draw. \int_use:N \g_draw_id_int
                } { ypos } { 0 } sp
                +
                \g_draw_bb_ymax_dim
            }
        ]
    }
}

\tl_new:N \l_hawkdraw_tag_mode_tl
\tl_new:N \l_hawkdraw_tag_role_tl
\tl_new:N \l_hawkdraw_tag_alt_tl
\tl_new:N \l_hawkdraw_tag_actualtext_tl

\cs_generate_variant:Nn \tag_socket_use:nn { nV }

\NewTaggingSocket { hawkdraw / picture / init } { 1 }
\NewTaggingSocket { hawkdraw / picture / begin } { 0 }
\NewTaggingSocket { hawkdraw / picture / end } { 1 }

\NewTaggingSocketPlug { hawkdraw / picture / init } { default } {
    \tl_set:Nn \l_hawkdraw_tag_mode_tl { text }
    \keys_set_known:nn { hawkdraw / tag } {#1}
    \exp_args:NnV \AssignTaggingSocketPlug { hawkdraw / picture / begin }
        \l_hawkdraw_tag_mode_tl
    \exp_args:NnV \AssignTaggingSocketPlug { hawkdraw / picture / end }
        \l_hawkdraw_tag_mode_tl
}
\AssignTaggingSocketPlug { hawkdraw / picture / init } { default }

\NewTaggingSocketPlug { hawkdraw / picture / begin } { alt } {
    \tag_mc_end_push:
    \tag_struct_begin:n {
        tag = \l_hawkdraw_tag_role_tl ,
        alt = \l_hawkdraw_tag_alt_tl
    }
    \tag_mc_begin:n { }
}

\NewTaggingSocketPlug { hawkdraw / picture / end } { alt } {
    #1
    \tag_mc_end:
    \tag_struct_end:
    \tag_mc_begin_pop:n { }
}

\NewTaggingSocketPlug { hawkdraw / picture / begin } { actualtext } {
    \tag_mc_end_push:
    \tag_struct_begin:n {
        tag = \l_hawkdraw_tag_role_tl ,
        actualtext = \l_hawkdraw_tag_actualtext_tl
    }
    \tag_mc_begin:n { }
}

\NewTaggingSocketPlug { hawkdraw / picture / end } { actualtext } {
    \tag_mc_end:
    \tag_struct_end:
    \tag_mc_begin_pop:n { }
}

\NewTaggingSocketPlug { hawkdraw / picture / begin } { artifact } {
    \tag_mc_end_push:
    \tag_mc_begin:n { artifact }
}

\NewTaggingSocketPlug { hawkdraw / picture / end } { artifact } {
    \tag_mc_end:
    \tag_mc_begin_pop:n { }
}

\NewTaggingSocketPlug { hawkdraw / picture / begin } { text } {
    \tag_mc_end_push:
    \tag_mc_begin:n { artifact }
}

\NewTaggingSocketPlug { hawkdraw / picture / end } { text } {
    \tag_mc_end:
    \tag_mc_begin_pop:n { }
}

\NewTaggingSocketPlug { hawkdraw / picture / begin } { off } { }
\NewTaggingSocketPlug { hawkdraw / picture / end } { off } { }

\AssignTaggingSocketPlug { hawkdraw / picture / begin } { text }
\AssignTaggingSocketPlug { hawkdraw / picture / end } { text }

\keys_define:nn { hawkdraw / tag } {
    mode                    .code:n         = {
        \tl_set:Ne \l_tag_graphic_mode_tl {#1}
    } ,
    alt                     .code:n         = {
        \pdf_purify:nN {#1} \l_hawkdraw_tag_alt_tl
        \tl_set:Nn \l_hawkdraw_tag_mode_tl { alt }
        \tl_set:Nn \l_hawkdraw_tag_role_tl { Figure }
    } ,
    artifact                .code:n         = {
        \tl_set:Nn \l_hawkdraw_tag_mode_tl { artifact }
    } ,
    actualtext              .code:n         = {
        \pdf_purify:nN {#1} \l_hawkdraw_tag_actualtext_tl
        \tl_set:Nn \l_hawkdraw_tag_mode_tl { actualtext }
        \tl_set:Nn \l_hawkdraw_tag_role_tl { Span }
    } ,
    text                    .code:n         = {
        \tl_set:Nn \l_hawkdraw_tag_mode_tl { text }
    } ,
    off                    .code:n         = {
        \tl_set:Nn \l_hawkdraw_tag_mode_tl { off }
    } ,
    tagging-setup           .code:n         = {
        \keys_set:nn { hawkdraw / tag } {#1}
    } ,
    tag                     .code:n         = {
        \str_case:nnF {#1} {
            { artifact } {
                \tl_set:Nn \l_hawkdraw_tag_mode_tl { artifact }
            }
            { false } {
                \tag_suspend:n { hawkdraw / picture }
            }
        } {
            \pdf_purify:nN {#1} \l_hawkdraw_tag_role_tl
            \tl_set:Nn \l_hawkdraw_tag_mode_tl { alt }
        }
    } ,
}

\keys_define:nn { hawkdraw / path } {
    mode                    .groups:n       = { tag } ,
    alt                     .groups:n       = { tag } ,
    artifact                .groups:n       = { tag } ,
    actualtext              .groups:n       = { tag } ,
    text                    .groups:n       = { tag } ,
    off                     .groups:n       = { tag } ,
    tagging-setup           .groups:n       = { tag } ,
    tag                     .groups:n       = { tag } ,
}

% ===

\seq_new:N \l__hawkdraw_chars_active_seq

\cctab_const:Nn \c_hawkdraw_environment_cctab {
    \cctab_select:N \c_document_cctab
    \char_set_catcode_other:n { 126 }
}

% ===

\clist_new:N \l__hawkdraw_keys_unprocessed_clist

\int_new:N \g_hawkdraw_picture_id_int

\clist_new:N \l_hawkdraw_picture_bb_clist
\fp_new:N \l_hawkdraw_picture_baseline_fp

\tl_new:N \l_hawkdraw_path_preactions_tl
\tl_new:N \l_hawkdraw_path_postactions_tl

\clist_new:N \l_hawkdraw_path_use_clist
\bool_new:N \g__hawkdraw_path_use_clip_bool

\tl_new:N \l__hawkdraw_path_options_tl

\tl_new:N \l_hawkdraw_path_color_stroke_tl
\tl_new:N \l_hawkdraw_path_color_fill_tl
\fp_new:N \l_hawkdraw_path_opacity_stroke_fp
\fp_new:N \l_hawkdraw_path_opacity_fill_fp
\dim_new:N \l_hawkdraw_path_linewidth_dim

\tl_set:Nn \l_hawkdraw_path_color_stroke_tl { . }
\tl_set:Nn \l_hawkdraw_path_color_fill_tl { . }
\fp_set:Nn \l_hawkdraw_path_opacity_stroke_fp { 1.0 }
\fp_set:Nn \l_hawkdraw_path_opacity_fill_fp { 1.0 }
\dim_set_eq:NN \l_hawkdraw_path_linewidth_dim
    \l_draw_default_linewidth_dim

\tl_new:N \l_hawkdraw_path_cap_tl
\tl_new:N \l_hawkdraw_path_join_tl
\fp_new:N \l_hawkdraw_path_miterlimit_fp
\clist_new:N \l_hawkdraw_path_corner_arc_clist
\tl_new:N \l_hawkdraw_path_fill_rule_tl

\clist_new:N \l_hawkdraw_path_dash_pattern_clist
\dim_new:N \l_hawkdraw_path_dash_phase_dim

\tl_new:N \l__hawkdraw_path_type_tl
\fp_new:N \g_hawkdraw_path_length_fp
\fp_new:N \g_hawkdraw_path_point_first_fp
\fp_new:N \g_hawkdraw_path_point_last_fp
\fp_new:N \g_hawkdraw_path_slope_first_fp
\fp_new:N \g_hawkdraw_path_slope_last_fp
\fp_new:N \l__hawkdraw_point_o_fp
\fp_new:N \l__hawkdraw_point_a_fp
\fp_new:N \l__hawkdraw_point_b_fp
\fp_new:N \l__hawkdraw_point_c_fp

\tl_new:N \l_hawkdraw_scope_layer_tl

\cs_generate_variant:Nn \color_select:n { e }
\cs_generate_variant:Nn \color_fill:n { e }
\cs_generate_variant:Nn \color_stroke:n { e }
\cs_generate_variant:Nn \opacity_select:n { V }
\cs_generate_variant:Nn \opacity_fill:n { V }
\cs_generate_variant:Nn \opacity_stroke:n { V }
\cs_generate_variant:Nn \draw_set_dash_pattern:nn { VV }
\cs_generate_variant:Nn \draw_path_corner_arc:nn { ee }
\cs_generate_variant:Nn \draw_path_use:n { e }
\cs_generate_variant:Nn \draw_path_use_clear:n { e }
\cs_generate_variant:Nn \draw_layer_begin:n { V }

\exp_args_generate:n { NNNe }

\cs_new_protected:Npn \__hawkdraw_style_set:nn #1#2 {
    \clist_if_exist:cF { l__hawkdraw_style_ #1 _value_clist } {
        \clist_new:c { l__hawkdraw_style_ #1 _value_clist }
    }
    \clist_put_right:cn { l__hawkdraw_style_ #1 _value_clist } {#2}
    \keys_define:ne { hawkdraw / path } {
        #1                  .meta:n         = {
            \clist_use:c { l__hawkdraw_style_ #1 _value_clist }
        }
    }
}

\cs_new_protected:Npn \__hawkdraw_style_put_right:nn #1#2 {
    \clist_if_exist:cTF { l__hawkdraw_style_ #1 _value_clist } {
        \clist_put_right:cn { l__hawkdraw_style_ #1 _value_clist } {#2}
        \keys_define:ne { hawkdraw / path } {
            #1              .meta:n         = {
                \clist_use:c { l__hawkdraw_style_ #1 _value_clist }
            }
        }
    } {
        \msg_error:nnn { hawkdraw } { style-undefined } {#1}
    }
}

\keys_define:nn { hawkdraw / path } {
    style ~ set             .code:n         = {
        \keyval_parse:NNn \use:n \__hawkdraw_style_set:nn {#1}
    } ,
    style ~ add             .code:n         = {
        \keyval_parse:NNn \use:n \__hawkdraw_style_put_right:nn {#1}
    } ,
}

\keys_define:nn { hawkdraw / path } {
    stroke                  .code:n         = {
        \tl_if_eq:nnTF {#1} { false } {
            \clist_remove_all:Nn \l_hawkdraw_path_use_clist { stroke }
        } {
            \clist_put_right:Nn \l_hawkdraw_path_use_clist { stroke }
        }
    } ,
    fill                    .code:n         = {
        \tl_if_eq:nnTF {#1} { false } {
            \clist_remove_all:Nn \l_hawkdraw_path_use_clist { fill }
        } {
            \clist_put_right:Nn \l_hawkdraw_path_use_clist { fill }
        }
    } ,
    clip                    .bool_gset:N    = \g__hawkdraw_path_use_clip_bool ,
    clip                    .default:n      = { true } ,
    clip                    .initial:n      = { false } ,
    % ===
    color ~ stroke          .code:n         = {
        \tl_set:Nn \l_hawkdraw_path_color_stroke_tl {#1}
        \tl_put_right:Nn \l__hawkdraw_path_options_tl {
            \color_stroke:e {#1}
        }
    } ,
    color ~ fill            .code:n         = {
        \tl_set:Nn \l_hawkdraw_path_color_fill_tl {#1}
        \tl_put_right:Nn \l__hawkdraw_path_options_tl {
            \color_fill:e {#1}
        }
    } ,
    opacity ~ stroke        .code:n         = {
        \fp_set:Nn \l_hawkdraw_path_opacity_stroke_fp {#1}
        \tl_put_right:Nn \l__hawkdraw_path_options_tl {
            \opacity_stroke:n { \fp_eval:n {#1} }
        }
    } ,
    opacity ~ fill          .code:n         = {
        \fp_set:Nn \l_hawkdraw_path_opacity_fill_fp {#1}
        \tl_put_right:Nn \l__hawkdraw_path_options_tl {
            \opacity_fill:n { \fp_eval:n {#1} }
        }
    } ,
    % ===
    line ~ width            .code:n         = {
        \dim_set:Nn \l_hawkdraw_path_linewidth_dim {#1}
        \tl_put_right:Nn \l__hawkdraw_path_options_tl {
            \draw_set_linewidth:n {#1}
        }
    } ,
    line ~ cap              .choice: ,
    line ~ cap / butt       .code:n         = {
        \tl_set:Nn \l_hawkdraw_path_cap_tl { butt }
        \tl_put_right:Nn \l__hawkdraw_path_options_tl {
            \draw_set_cap_butt:
        }
    } ,
    line ~ cap / round      .code:n         = {
        \tl_set:Nn \l_hawkdraw_path_cap_tl { round }
        \tl_put_right:Nn \l__hawkdraw_path_options_tl {
            \draw_set_cap_round:
        }
    } ,
    line ~ cap / rectangle  .code:n         = {
        \tl_set:Nn \l_hawkdraw_path_cap_tl { rectangle }
        \tl_put_right:Nn \l__hawkdraw_path_options_tl {
            \draw_set_cap_rectangle:
        }
    } ,
    line ~ join             .choice: ,
    line ~ join / miter     .code:n         = {
        \tl_set:Nn \l_hawkdraw_path_join_tl { miter }
        \tl_put_right:Nn \l__hawkdraw_path_options_tl {
            \draw_set_join_miter:
        }
    } ,
    line ~ join / round     .code:n         = {
        \tl_set:Nn \l_hawkdraw_path_join_tl { round }
        \tl_put_right:Nn \l__hawkdraw_path_options_tl {
            \draw_set_join_round:
        }
    } ,
    line ~ join / bevel     .code:n         = {
        \tl_set:Nn \l_hawkdraw_path_join_tl { bevel }
        \tl_put_right:Nn \l__hawkdraw_path_options_tl {
            \draw_set_join_bevel:
        }
    } ,
    miter ~ limit           .code:n         = {
        \fp_set:Nn \l_hawkdraw_path_miterlimit_fp {#1}
        \tl_put_right:Nn \l__hawkdraw_path_options_tl {
            \draw_set_miter_limit:n {#1}
        }
    } ,
    fill ~ rule             .choice: ,
    fill ~ rule / even-odd  .code:n        = {
        \tl_set:Nn \l_hawkdraw_path_fill_rule_tl { evenodd }
        \tl_put_right:Nn \l__hawkdraw_path_options_tl {
            \draw_set_evenodd_rule:
        }
    } ,
    fill ~ rule / non-zero  .code:n         = {
        \tl_set:Nn \l_hawkdraw_path_fill_rule_tl { nonzero }
        \tl_put_right:Nn \l__hawkdraw_path_options_tl {
            \draw_set_nonzero_rule:
        }
    } ,
    dash ~ pattern          .clist_set:N    = \l_hawkdraw_path_dash_pattern_clist ,
    dash ~ pattern          .initial:n      = { } ,
    dash ~ phase            .dim_set:N      = \l_hawkdraw_path_dash_phase_dim ,
    dash ~ phase            .initial:n      = { 0pt } ,
    corner ~ arc            .clist_set:N    = \l_hawkdraw_path_corner_arc_clist ,
    corner ~ arc            .initial:n      = { 0pt } ,
    % ===
    shift                   .code:n         = {
        \draw_transform_shift:n {#1}
    } ,
    rotate                  .code:n         = {
        \draw_transform_rotate:n {#1}
    } ,
    scale                   .code:n         = {
        \int_compare:nNnTF { \clist_count:n {#1} } > { \c_one_int } {
            \draw_transform_xscale:n {
                \clist_item:nn {#1} { 1 }
            }
            \draw_transform_yscale:n {
                \clist_item:nn {#1} { 2 }
            }
        } {
            \draw_transform_scale:n {#1}
        }
    } ,
    slant                   .code:n         = {
        \int_compare:nNnTF { \clist_count:n {#1} } > { \c_one_int } {
            \draw_transform_xslant:n {
                \clist_item:nn {#1} { 1 }
            }
            \draw_transform_yslant:n {
                \clist_item:nn {#1} { 2 }
            }
        } {
            \draw_transform_slant:n {#1}
        }
    } ,
}

\cs_new_protected:Npn \hawkdraw_path_set_options: {
    \hook_use:n { hawkdraw / options / before }
    \tl_use:N \l__hawkdraw_path_options_tl
    \tl_clear:N \l__hawkdraw_path_options_tl
    \clist_if_empty:NF \l_hawkdraw_path_dash_pattern_clist {
        \draw_set_dash_pattern:VV \l_hawkdraw_path_dash_pattern_clist
            \l_hawkdraw_path_dash_phase_dim
    }
    \int_compare:nNnTF { \clist_count:N \l_hawkdraw_path_corner_arc_clist } > { \c_one_int } {
        \draw_path_corner_arc:ee {
            \clist_item:Nn \l_hawkdraw_path_corner_arc_clist { 1 }
        } {
            \clist_item:Nn \l_hawkdraw_path_corner_arc_clist { 2 }
        }
    } {
        \draw_path_corner_arc:ee {
            \clist_item:Nn \l_hawkdraw_path_corner_arc_clist { 1 }
        } {
            \clist_item:Nn \l_hawkdraw_path_corner_arc_clist { 1 }
        }
    }
    \hook_use:n { hawkdraw / options / after }
}

% ===

\cs_new_protected:Npn \hawkdraw_operator_construct:nN #1#2 {
    \cs_new_protected:cpn {
        __hawkdraw_path_process_ #1 _options:w
    } ##1 [ ##2 ] ##3 \s__hawkdraw_stop {
        #2 {##2}
        \tl_trim_spaces_apply:nN {##3} \__hawkdraw_path_process_continue:n
    }
    \cs_new_protected:cpn {
        __hawkdraw_path_process_ \tl_head:n {#1} :w
    } #1 ##1 \s__hawkdraw_stop {
        \tl_if_head_eq_meaning:nNTF {##1} [ {
            \use:c { __hawkdraw_path_process_ #1 _options:w } ##1 \s__hawkdraw_stop
        } {
            #2 { }
            \tl_trim_spaces_apply:nN {##1} \__hawkdraw_path_process_continue:n
        }
    }
}

\cs_new_protected:Npn \hawkdraw_operator_construct_arg:nN #1#2 {
    \cs_new_protected:cpn {
        __hawkdraw_path_process_ #1 _options:w
    } ##1 [ ##2 ] ##3##4 \s__hawkdraw_stop {
        #2 {##2} {##3}
        \tl_trim_spaces_apply:nN {##4} \__hawkdraw_path_process_continue:n
    }
    \cs_new_protected:cpn {
        __hawkdraw_path_process_ \tl_head:n {#1} :w
    } #1 ##1##2 \s__hawkdraw_stop {
        \tl_if_head_eq_meaning:nNTF {##1} [ {
            \use:c { __hawkdraw_path_process_ #1 _options:w } ##1##2 \s__hawkdraw_stop
        } {
            #2 { } {##1}
            \tl_trim_spaces_apply:nN {##2} \__hawkdraw_path_process_continue:n
        }
    }
}

\cs_new_protected:Npn \hawkdraw_operator_prepare_i:n #1 {
    \cs_new_protected:cpn { __hawkdraw_path_process_ #1 :w } #1 ##1 \s__hawkdraw_stop {
        \__hawkdraw_cs_if_exist_use_secure:nnT {##1} {
            __hawkdraw_path_process_ #1 _ \tl_head:n {##1} :w
        } {
            #1 ##1 \s__hawkdraw_stop
        }
    }
}

\cs_new_protected:Npn \hawkdraw_operator_construct_i:nN #1#2 {
    \cs_new_protected:cpn {
        __hawkdraw_path_process_ #1 _options:w
    } ##1 [ ##2 ] ##3 \s__hawkdraw_stop {
        #2 {##2}
        \tl_trim_spaces_apply:nN {##3} \__hawkdraw_path_process_continue:n
    }
    \cs_new_protected:cpn {
        __hawkdraw_path_process_ \tl_head:n {#1} _ \tl_item:nn {#1} { 2 } :w
    } #1 ##1 \s__hawkdraw_stop {
        \tl_if_head_eq_meaning:nNTF {##1} [ {
            \use:c { __hawkdraw_path_process_ #1 _options:w } ##1 \s__hawkdraw_stop
        } {
            #2 { }
            \tl_trim_spaces_apply:nN {##1} \__hawkdraw_path_process_continue:n
        }
    }
}

\cs_new_protected:Npn \hawkdraw_operator_construct_i_arg:nN #1#2 {
    \cs_new_protected:cpn {
        __hawkdraw_path_process_ #1 _options:w
    } ##1 [ ##2 ] ##3##4 \s__hawkdraw_stop {
        #2 {##2} {##3}
        \tl_trim_spaces_apply:nN {##4} \__hawkdraw_path_process_continue:n
    }
    \cs_new_protected:cpn {
        __hawkdraw_path_process_ \tl_head:n {#1} _ \tl_item:nn {#1} { 2 } :w
    } #1 ##1##2 \s__hawkdraw_stop {
        \tl_if_head_eq_meaning:nNTF {##1} [ {
            \use:c { __hawkdraw_path_process_ #1 _options:w } ##1##2 \s__hawkdraw_stop
        } {
            #2 { } {##1}
            \tl_trim_spaces_apply:nN {##2} \__hawkdraw_path_process_continue:n
        }
    }
}

% ===

\msg_new:nnn { hawkdraw } { style-undefined } {
    The ~ style ~ `#1` ~ is ~ undefined.
}
\msg_new:nnn { hawkdraw } { module-missing } {
    The ~ module ~ `#1` ~ is ~ missing.
}

\cs_new_protected:Npn \hawkdraw_modules_load:n #1 {
    \clist_map_inline:nn {#1} {
        \file_if_exist_input:nF { hawkdraw- ##1 .code.tex } {
            \msg_warning:nnn { hawkdraw } { module-missing } {##1}
        }
    }
}

\hawkdraw_modules_load:n { utils , softpath , nodes , arrows , plots }

\IfPDFManagementActiveTF {
    \hawkdraw_modules_load:n { patterns , opacity , shadings }
} {
    \msg_warning:nn { hawkdraw } { documentmetadata-required }
    \keys_define:nn { hawkdraw / path } {
        pattern                 .code:n     = { } ,
        pattern ~ style ~ set   .code:n     = { } ,
        pattern ~ style ~ add   .code:n     = { } ,
        pattern ~ tile ~ size   .code:n     = { } ,
        shading                 .code:n     = { } ,
        shading ~ colors        .code:n     = { } ,
        shading ~ rotate        .code:n     = { } ,
        shading ~ offset        .code:n     = { } ,
        shading ~ circular      .code:n     = { } ,
        shading ~ angle ~ delta .code:n     = { } ,
    }
    \keys_define:nn { hawkdraw / scope } {
        opacity ~ group         .code:n     = { } ,
        blend ~ mode            .code:n     = { } ,
        soft ~ mask ~ use       .code:n     = { } ,
    }
    \NewDocumentEnvironment { hawkdrawsoftmask } { m O { } +b } { } { }
}

% ===

\scan_new:N \s__hawkdraw_stop
\scan_new:N \s__hawkdraw_path_end

\fp_new:N \l_hawkdraw_point_at_fp
\tl_new:N \l_hawkdraw_point_at_name_tl
\tl_new:N \l_hawkdraw_point_name_tl
\fp_new:N \l_hawkdraw_point_at_part_fp
\fp_new:N \l_hawkdraw_point_slope_fp

\keys_define:nn { hawkdraw / path / point } {
    at                      .code:n         = {
        \hawkdraw_point_parse_set:Nn \l_hawkdraw_point_at_fp {#1}
        \tl_set:Nn \l_hawkdraw_point_at_name_tl {#1}
    } ,
    name                    .tl_set:N       = \l_hawkdraw_point_name_tl ,
    at ~ part               .fp_set:N       = \l_hawkdraw_point_at_part_fp ,
    at ~ distance           .code:n         = {
        \hawkdraw_path_get_length:
        \fp_set:Nn \l_hawkdraw_point_at_part_fp {
            #1 / \g_hawkdraw_path_length_fp
        }
    } ,
}

% point , plot
\hawkdraw_operator_prepare_i:n { p }

\hawkdraw_operator_construct_i:nN { point } \hawkdraw_path_point:n

\cs_new_protected:Npn \hawkdraw_path_point:n #1 {
    \fp_set_eq:NN \l_hawkdraw_point_at_fp \g_hawkdraw_path_point_last_fp
    \fp_set_eq:NN \l_hawkdraw_point_slope_fp \g_hawkdraw_path_slope_last_fp
    \fp_set:Nn \l_hawkdraw_point_at_part_fp { nan }
    \tl_clear:N \l_hawkdraw_point_at_name_tl
    \keys_set:nn { hawkdraw / path / point } {#1}
    \fp_if_nan:nF { \l_hawkdraw_point_at_part_fp } {
        \fp_set:Nn \l_hawkdraw_point_at_fp {
            \cs_if_exist_use:cTF {
                __hawkdraw_point_part_ \l__hawkdraw_path_type_tl :n
            } {
                { \l_hawkdraw_point_at_part_fp }
            } {
                0pt , 0pt
            }
        }
        \fp_set:Nn \l_hawkdraw_point_slope_fp {
            \cs_if_exist_use:cTF {
                __hawkdraw_point_part_slope_ \l__hawkdraw_path_type_tl :n
            } {
                { \l_hawkdraw_point_at_part_fp }
            } {
                0
            }
        }
    }
    \tl_if_blank:VF \l_hawkdraw_point_name_tl {
        \prop_if_exist:cF {
            g__hawkdraw_point_ \l_hawkdraw_point_name_tl _prop
        } {
            \prop_new_linked:c {
                g__hawkdraw_point_ \l_hawkdraw_point_name_tl _prop
            }
        }
        \prop_gput:cnV { g__hawkdraw_point_ \l_hawkdraw_point_name_tl _prop }
            { point } \l_hawkdraw_point_at_fp
        \prop_gput:cnV { g__hawkdraw_point_ \l_hawkdraw_point_name_tl _prop }
            { slope } \l_hawkdraw_point_slope_fp
    }
}

% ===

\cs_new_protected:Npn \hawkdraw_point_parse_set:Nn #1#2 {
    \exp_args:NNe \fp_set:Nn #1 { \hawkdraw_point_parse:n {#2} }
}

\cs_new:Npn \hawkdraw_point_parse:n #1 {
    \prop_if_exist:cTF {
        g__hawkdraw_point_
        \__hawkdraw_point_parse_trim:n {#1}
        _prop
    } {
        \fp_eval:n {
            \prop_item:cn {
                g__hawkdraw_point_
                \__hawkdraw_point_parse_trim:n {#1}
                _prop
            } { point }
        }
    } {
        \__hawkdraw_tl_has_left_square_bracket:nTF {#1} {
            \fp_eval:n {
                \__hawkdraw_point_parse_node_anchor_degrees:w #1 \s__hawkdraw_stop
            }
        } {
            \__hawkdraw_tl_has_greater_than_sign:nTF {#1} {
                \fp_eval:n {
                    \__hawkdraw_point_parse_polar:w #1 \s__hawkdraw_stop
                }
            } {
                \__hawkdraw_tl_has_colon:nTF {#1} {
                    \fp_eval:n {
                        \__hawkdraw_point_parse_polar_compat:w #1 \s__hawkdraw_stop
                    }
                } {
                    \fp_eval:n {#1}
                }
            }
        }
    }
}

\prg_new_conditional:Npne \__hawkdraw_tl_has_colon:n #1 { TF } {
    \exp_not:N \__hawkdraw_tl_has_colon:w
    \exp_not:N \q_mark #1 \c_colon_str \exp_not:N \q_nil \c_colon_str \exp_not:N \q_stop
}
\__hawkdraw_exp_last_unbraced:NNe
\cs_new:Npn \__hawkdraw_tl_has_colon:w {
    #1 \c_colon_str #2 \c_colon_str #3 \exp_not:N \q_stop
} {
    \quark_if_nil:nTF {#2} { \prg_return_false: } { \prg_return_true: }
}

\prg_new_conditional:Npnn \__hawkdraw_tl_has_greater_than_sign:n #1 { TF } {
    \__hawkdraw_tl_has_greater_than_sign:w \q_mark #1 > \q_nil > \q_stop
}
\cs_new:Npn \__hawkdraw_tl_has_greater_than_sign:w #1 > #2 > #3 \q_stop {
    \quark_if_nil:nTF {#2} { \prg_return_false: } { \prg_return_true: }
}

\prg_new_conditional:Npnn \__hawkdraw_tl_has_left_square_bracket:n #1 { TF } {
    \__hawkdraw_tl_has_left_square_bracket:w \q_mark #1 [ \q_nil [ \q_stop
}
\cs_new:Npn \__hawkdraw_tl_has_left_square_bracket:w #1 [ #2 [ #3 \q_stop {
    \quark_if_nil:nTF {#2} { \prg_return_false: } { \prg_return_true: }
}

\cs_new:Npn \__hawkdraw_point_parse_trim:n #1 {
    \tl_trim_spaces:e {
        \tl_if_head_eq_meaning:eNTF { \tl_trim_spaces:n {#1} } ( {
            \exp_last_unbraced:Ne
            \__hawkdraw_point_parse_trim:w { \tl_trim_spaces:n {#1} } \s__hawkdraw_stop
        } {
            #1
        }
    }
}

\cs_new:Npn \__hawkdraw_point_parse_trim:w ( #1 ) #2 \s__hawkdraw_stop {
    \tl_if_empty:nTF {#2} {
        #1
    } {
        ( #1 ) #2
    }
}

\cs_new:Npn \__hawkdraw_point_parse_polar:w #1 > #2 \s__hawkdraw_stop {
    \int_compare:nNnTF { \clist_count:n {#2} } > { \c_one_int } {
        \draw_point_polar:nnn {
            \clist_item:nn {#1} { 1 }
        } {
            \clist_item:nn {#1} { 2 }
        } {#2}
    } {
        \draw_point_polar:nn {#1} {#2}
    }
}

\exp_last_unbraced:NNNNo
\cs_new:Npn \__hawkdraw_point_parse_polar_compat:w #1 \c_colon_str #2 \s__hawkdraw_stop {
    \int_compare:nNnTF { \clist_count:n {#2} } > { \c_one_int } {
        \draw_point_polar:nnn {
            \clist_item:nn {#2} { 1 }
        } {
            \clist_item:nn {#2} { 2 }
        } {#1}
    } {
        \draw_point_polar:nn {#2} {#1}
    }
}

\cs_new:Npn \__hawkdraw_point_parse_node_anchor_degrees:w #1 [ #2 ] #3 \s__hawkdraw_stop {
    \use:c {
        hawkdraw_node_frame_point_
        \prop_item:cn {
            g__hawkdraw_point_
            \__hawkdraw_point_parse_trim:n {#1}
            _prop
        } { frame }
        :nnnn
    } {
        \prop_item:cn {
            g__hawkdraw_point_
            \__hawkdraw_point_parse_trim:n {#1}
            _prop
        } { point }
    } {
        \prop_item:cn {
            g__hawkdraw_point_
            \__hawkdraw_point_parse_trim:n {#1}
            _prop
        } { width } / 2
    } {
        \prop_item:cn {
            g__hawkdraw_point_
            \__hawkdraw_point_parse_trim:n {#1}
            _prop
        } { height } / 2
    } { \fp_eval:n {#2} }
}

% ===

\hook_new_pair:nn { hawkdraw / options / before } { hawkdraw / options / after }
\hook_new_pair:nn { hawkdraw / path / begin } { hawkdraw / path / end }
\hook_new_pair:nn { hawkdraw / scope / begin } { hawkdraw / scope / end }

\cs_new_protected:Npn \hawkdraw_path:w #1 ; {
    \fp_set:Nn \l__hawkdraw_point_o_fp { ( 0pt , 0pt ) }
    \fp_set:Nn \l__hawkdraw_point_a_fp { ( 0pt , 0pt ) }
    \fp_set:Nn \l__hawkdraw_point_b_fp { ( 0pt , 0pt ) }
    \fp_set:Nn \l__hawkdraw_point_c_fp { ( 0pt , 0pt ) }
    \fp_gset:Nn \g_hawkdraw_path_point_first_fp { ( 0pt , 0pt ) }
    \fp_gset:Nn \g_hawkdraw_path_point_last_fp { ( 0pt , 0pt ) }
    \fp_gset:Nn \g_hawkdraw_path_slope_first_fp { nan }
    \fp_gset:Nn \g_hawkdraw_path_slope_last_fp { nan }
    \fp_gzero:N \g_hawkdraw_path_length_fp
    \draw_scope_begin:
        \hook_use:n { hawkdraw / path / begin }
        \tl_build_gbegin:N \g_hawkdraw_softpath_original_tl
        \tl_trim_spaces_apply:nN { #1 \s__hawkdraw_path_end } \__hawkdraw_path_process:n
        \tl_build_gput_right:Nn \g_hawkdraw_softpath_original_tl { \s__hawkdraw_softpath_end }
        \tl_build_gend:N \g_hawkdraw_softpath_original_tl
        \hawkdraw_path_sanitize:N \g_hawkdraw_softpath_original_tl
        \hawkdraw_softpath_update:NV \g_hawkdraw_softpath_original_tl \l_hawkdraw_softpath_update_clist
        \tl_use:N \g_hawkdraw_softpath_original_tl
        \clist_remove_duplicates:N \l_hawkdraw_path_use_clist
        \tl_use:N \l_hawkdraw_path_preactions_tl
        \draw_path_use_clear:e { \clist_use:N \l_hawkdraw_path_use_clist }
        \tl_use:N \l_hawkdraw_path_postactions_tl
        \hook_use:n { hawkdraw / path / end }
    \draw_scope_end:
    \bool_if:NT \g__hawkdraw_path_use_clip_bool {
        \tl_use:N \g_hawkdraw_softpath_original_tl
        \draw_path_use_clear:n { clip }
        \bool_gset_false:N \g__hawkdraw_path_use_clip_bool
    }
}

\cs_new_protected:Npn \__hawkdraw_path_process:n #1 {
    \__hawkdraw_cs_if_exist_use_secure:nnT {#1} {
        __hawkdraw_path_process_ \tl_head:n {#1} :w
    } {
        #1 \s__hawkdraw_stop
    }
}
\cs_generate_variant:Nn \__hawkdraw_path_process:n { o }

\cs_new_protected:Npn \__hawkdraw_path_process_continue:n #1 {
    \tl_if_blank:nF {#1} {
        \__hawkdraw_path_process:n {#1}
    }
}

\cs_new_protected:Npn \hawkdraw_path_sanitize:N #1 {
    \bool_lazy_any:nT {
        { \tl_if_head_eq_meaning_p:VN #1 \hawkdraw_softpath_lineto_op:nn }
        { \tl_if_head_eq_meaning_p:VN #1 \hawkdraw_softpath_curveto_opi:nn }
        { \tl_if_head_eq_meaning_p:VN #1 \hawkdraw_softpath_arc_opi:nn }
        { \tl_if_head_eq_meaning_p:VN #1 \hawkdraw_softpath_arc_axes_opi:nn }
    } {
        \tl_if_head_eq_meaning:VNF #1 \hawkdraw_softpath_moveto_op:nn {
            \tl_put_left:Nn #1 {
                \hawkdraw_softpath_moveto_op:nn { 0pt } { 0pt }
            }
        }
    }
}

\cs_new_protected:cpn { __hawkdraw_path_process_ [ :w } [ #1 ] #2 \s__hawkdraw_stop {
    \keys_set:nn { hawkdraw / path } {#1}
    \hawkdraw_path_set_options:
    \tl_trim_spaces_apply:nN {#2} \__hawkdraw_path_process_continue:n
}

\cs_new_protected:cpn { __hawkdraw_path_process_ ( :w } ( #1 ) #2 \s__hawkdraw_stop {
    \hawkdraw_point_parse_set:Nn \l__hawkdraw_point_a_fp {#1}
    \fp_gset_eq:NN \g_hawkdraw_path_point_first_fp \l__hawkdraw_point_a_fp
    \fp_gset_eq:NN \g_hawkdraw_path_point_last_fp \l__hawkdraw_point_a_fp
    \fp_gset:Nn \g_hawkdraw_path_slope_first_fp { nan }
    \fp_gzero:N \g_hawkdraw_path_length_fp
    \hawkdraw_softpath_build_moveto:n { \l__hawkdraw_point_a_fp }
    \tl_trim_spaces_apply:nN {#2} \__hawkdraw_path_process_continue:n
}

% ===

\cs_new:Npn \__hawkdraw_path_process_line:n #1 {
    \__hawkdraw_cs_if_exist_use_secure:nnT {#1} {
        __hawkdraw_path_process_line_ \tl_head:n {#1} :w
    } {
        #1 \s__hawkdraw_stop
    }
}

\cs_new_protected:cpn { __hawkdraw_path_process_ - :w } - #1 \s__hawkdraw_stop {
    \tl_trim_spaces_apply:nN {#1} \__hawkdraw_path_process_line:n
}

\cs_new_protected:cpn { __hawkdraw_path_process_line_ - :w } - #1 ( #2 ) #3 \s__hawkdraw_stop {
    % what do we do with #1?
    \fp_gset_eq:NN \g_hawkdraw_path_previous_slope_fp \g_hawkdraw_path_slope_last_fp
    \str_set:Nn \l__hawkdraw_path_type_tl { line }
    \fp_set_eq:NN \l__hawkdraw_point_o_fp \g_hawkdraw_path_point_last_fp
    \hawkdraw_point_parse_set:Nn \l__hawkdraw_point_a_fp {#2}
    \fp_if_nan:nT { \g_hawkdraw_path_slope_first_fp } {
        \fp_gset:Nn \g_hawkdraw_path_slope_first_fp {
            \hawkdraw_calculate_slope:nn { \l__hawkdraw_point_o_fp } { \l__hawkdraw_point_a_fp }
        }
    }
    \fp_gset:Nn \g_hawkdraw_path_slope_last_fp {
        \hawkdraw_calculate_slope:nn { \l__hawkdraw_point_o_fp } { \l__hawkdraw_point_a_fp }
    }
    \hawkdraw_softpath_build_lineto:n { \l__hawkdraw_point_a_fp }
    \fp_gset_eq:NN \g_hawkdraw_path_point_last_fp \l__hawkdraw_point_a_fp
    \tl_trim_spaces_apply:nN {#3} \__hawkdraw_path_process_continue:n
}

\cs_new_protected:cpn { __hawkdraw_path_process_line_ | :w } | #1 ( #2 ) #3 \s__hawkdraw_stop {
    % what do we do with #1?
    \fp_gset_eq:NN \g_hawkdraw_path_previous_slope_fp \g_hawkdraw_path_slope_last_fp
    \str_set:Nn \l__hawkdraw_path_type_tl { line_orthogonal }
    \fp_set_eq:NN \l__hawkdraw_point_o_fp \g_hawkdraw_path_point_last_fp
    \hawkdraw_point_parse_set:Nn \l__hawkdraw_point_b_fp {#2}
    \hawkdraw_point_parse_set:Nn \l__hawkdraw_point_a_fp {
        (
            \hawkdraw_tuple_use_i:V \l__hawkdraw_point_b_fp
        ,
            \hawkdraw_tuple_use_ii:V \l__hawkdraw_point_o_fp
        )
    }
    \fp_if_nan:nT { \g_hawkdraw_path_slope_first_fp } {
        \fp_gset:Nn \g_hawkdraw_path_slope_first_fp {
            \hawkdraw_calculate_slope:nn { \l__hawkdraw_point_o_fp } { \l__hawkdraw_point_a_fp }
        }
    }
    \fp_gset:Nn \g_hawkdraw_path_slope_last_fp {
        \hawkdraw_calculate_slope:nn { \l__hawkdraw_point_o_fp } { \l__hawkdraw_point_a_fp }
    }
    \hawkdraw_softpath_build_lineto:n { \l__hawkdraw_point_a_fp }
    \hawkdraw_softpath_build_lineto:n { \l__hawkdraw_point_b_fp }
    \fp_gset_eq:NN \g_hawkdraw_path_point_last_fp \l__hawkdraw_point_b_fp
    \tl_trim_spaces_apply:nN {#3} \__hawkdraw_path_process_continue:n
}

\cs_new_protected:cpn { __hawkdraw_path_process_ | :w } |- #1 ( #2 ) #3 \s__hawkdraw_stop {
    % what do we do with #1?
    \fp_gset_eq:NN \g_hawkdraw_path_previous_slope_fp \g_hawkdraw_path_slope_last_fp
    \str_set:Nn \l__hawkdraw_path_type_tl { line_orthogonal }
    \fp_set_eq:NN \l__hawkdraw_point_o_fp \g_hawkdraw_path_point_last_fp
    \hawkdraw_point_parse_set:Nn \l__hawkdraw_point_b_fp {#2}
    \hawkdraw_point_parse_set:Nn \l__hawkdraw_point_a_fp {
        (
            \hawkdraw_tuple_use_i:V \l__hawkdraw_point_o_fp
        ,
            \hawkdraw_tuple_use_ii:V \l__hawkdraw_point_b_fp
        )
    }
    \fp_if_nan:nT { \g_hawkdraw_path_slope_first_fp } {
        \fp_gset:Nn \g_hawkdraw_path_slope_first_fp {
            \hawkdraw_calculate_slope:nn { \l__hawkdraw_point_o_fp } { \l__hawkdraw_point_a_fp }
        }
    }
    \fp_gset:Nn \g_hawkdraw_path_slope_last_fp {
        \hawkdraw_calculate_slope:nn { \l__hawkdraw_point_a_fp } { \l__hawkdraw_point_b_fp }
    }
    \hawkdraw_softpath_build_lineto:n { \l__hawkdraw_point_a_fp }
    \hawkdraw_softpath_build_lineto:n { \l__hawkdraw_point_b_fp }
    \fp_gset_eq:NN \g_hawkdraw_path_point_last_fp \l__hawkdraw_point_b_fp
    \tl_trim_spaces_apply:nN {#3} \__hawkdraw_path_process_continue:n
}

% ===

\cs_new:Npn \__hawkdraw_path_process_curve:n #1 {
    \__hawkdraw_cs_if_exist_use_secure:nnT {#1} {
        __hawkdraw_path_process_curve_ \tl_head:n {#1} :w
    } {
        #1 \s__hawkdraw_stop
    }
}

\cs_new_protected:cpn { __hawkdraw_path_process_ . :w } .. #1 ( #2 ) #3 \s__hawkdraw_stop {
    % what do we do with #1?
    \fp_gset_eq:NN \g_hawkdraw_path_previous_slope_fp \g_hawkdraw_path_slope_last_fp
    \fp_set_eq:NN \l__hawkdraw_point_o_fp \g_hawkdraw_path_point_last_fp
    \hawkdraw_point_parse_set:Nn \l__hawkdraw_point_a_fp {#2}
    \tl_trim_spaces_apply:nN {#3} \__hawkdraw_path_process_curve:n
}

\cs_new_protected:cpn { __hawkdraw_path_process_curve_ . :w } .. #1 ( #2 ) #3 \s__hawkdraw_stop {
    % what do we do with #1?
    \str_set:Nn \l__hawkdraw_path_type_tl { curve_quadratic }
    \hawkdraw_point_parse_set:Nn \l__hawkdraw_point_b_fp {#2}
    \fp_if_nan:nT { \g_hawkdraw_path_slope_first_fp } {
        \fp_gset:Nn \g_hawkdraw_path_slope_first_fp {
            \hawkdraw_calculate_slope:nn { \l__hawkdraw_point_o_fp } { \l__hawkdraw_point_a_fp }
        }
    }
    \fp_gset:Nn \g_hawkdraw_path_slope_last_fp {
        \hawkdraw_calculate_slope:nn { \l__hawkdraw_point_a_fp } { \l__hawkdraw_point_b_fp }
    }
    \hawkdraw_softpath_build_curveto:nn
        { \l__hawkdraw_point_a_fp } { \l__hawkdraw_point_b_fp }
    \fp_gset_eq:NN \g_hawkdraw_path_point_last_fp \l__hawkdraw_point_b_fp
    \tl_trim_spaces_apply:nN {#3} \__hawkdraw_path_process_continue:n
}

\cs_new_protected:cpn { __hawkdraw_path_process_curve_ & :w } & #1 ( #2 ) #3 .. #4 ( #5 ) #6 \s__hawkdraw_stop {
    % what do we do with #1, #3, #4?
    \str_set:Nn \l__hawkdraw_path_type_tl { curve_cubic }
    \hawkdraw_point_parse_set:Nn \l__hawkdraw_point_b_fp {#2}
    \hawkdraw_point_parse_set:Nn \l__hawkdraw_point_c_fp {#5}
    \fp_if_nan:nT { \g_hawkdraw_path_slope_first_fp } {
        \fp_gset:Nn \g_hawkdraw_path_slope_first_fp {
            \hawkdraw_calculate_slope:nn { \l__hawkdraw_point_o_fp } { \l__hawkdraw_point_a_fp }
        }
    }
    \fp_gset:Nn \g_hawkdraw_path_slope_last_fp {
        \hawkdraw_calculate_slope:nn { \l__hawkdraw_point_b_fp } { \l__hawkdraw_point_c_fp }
    }
    \hawkdraw_softpath_build_curveto:nnn
        { \l__hawkdraw_point_a_fp } { \l__hawkdraw_point_b_fp } { \l__hawkdraw_point_c_fp }
    \fp_gset_eq:NN \g_hawkdraw_path_point_last_fp \l__hawkdraw_point_c_fp
    \tl_trim_spaces_apply:nN {#6} \__hawkdraw_path_process_continue:n
}

\cs_new_protected:cpn { __hawkdraw_path_process_ ! :w } ! #1 \s__hawkdraw_stop {
    \hawkdraw_softpath_build_close:n { \g_hawkdraw_path_point_first_fp }
    \fp_gset:Nn \g_hawkdraw_path_slope_first_fp { nan }
    \fp_gset:Nn \g_hawkdraw_path_slope_last_fp { nan }
    \fp_gset_eq:NN \g_hawkdraw_path_point_last_fp \g_hawkdraw_path_point_first_fp
    \tl_trim_spaces_apply:nN {#1} \__hawkdraw_path_process_continue:n
}

% ===

\bool_new:N \l_hawkdraw_path_arc_axes_bool
\dim_new:N \l_hawkdraw_path_arc_radius_x_dim
\dim_new:N \l_hawkdraw_path_arc_radius_y_dim
\fp_new:N \l_hawkdraw_path_arc_vector_a_fp
\fp_new:N \l_hawkdraw_path_arc_vector_b_fp
\fp_new:N \l_hawkdraw_path_arc_angle_start_fp
\fp_new:N \l_hawkdraw_path_arc_angle_end_fp

\keys_define:nn { hawkdraw / path / arc } {
    radius ~ x              .code:n         = {
        \bool_set_false:N \l_hawkdraw_path_arc_axes_bool
        \dim_set:Nn \l_hawkdraw_path_arc_radius_x_dim {#1}
    } ,
    radius ~ x              .initial:n      = { 1cm } ,
    radius ~ y              .code:n         = {
        \bool_set_false:N \l_hawkdraw_path_arc_axes_bool
        \dim_set:Nn \l_hawkdraw_path_arc_radius_y_dim {#1}
    } ,
    radius ~ y              .initial:n      = { 1cm } ,
    radius                  .code:n         = {
        \bool_set_false:N \l_hawkdraw_path_arc_axes_bool
        \int_compare:nNnTF { \clist_count:n {#1} } > { \c_one_int } {
            \dim_set:Nn \l_hawkdraw_path_arc_radius_x_dim {
                \clist_item:nn {#1} { 1 }
            }
            \dim_set:Nn \l_hawkdraw_path_arc_radius_y_dim {
                \clist_item:nn {#1} { 2 }
            }
        } {
            \dim_set:Nn \l_hawkdraw_path_arc_radius_x_dim {#1}
            \dim_set:Nn \l_hawkdraw_path_arc_radius_y_dim {#1}
        }
    } ,
    vector ~ a              .code:n         = {
        \bool_set_true:N \l_hawkdraw_path_arc_axes_bool
        \fp_set:Nn \l_hawkdraw_path_arc_vector_a_fp {#1}
    } ,
    vector ~ b              .code:n         = {
        \bool_set_true:N \l_hawkdraw_path_arc_axes_bool
        \fp_set:Nn \l_hawkdraw_path_arc_vector_b_fp {#1}
    } ,
    angle ~ start           .fp_set:N       = \l_hawkdraw_path_arc_angle_start_fp ,
    angle ~ start           .initial:n      = { 0 } ,
    angle ~ end             .fp_set:N       = \l_hawkdraw_path_arc_angle_end_fp ,
    angle ~ end             .initial:n      = { 90 } ,
    angle ~ delta           .code:n         = {
        \fp_set:Nn \l_hawkdraw_path_arc_angle_end_fp {
            \l_hawkdraw_path_arc_angle_start_fp + ( #1 )
        }
    } ,
}

\hawkdraw_operator_construct:nN { arc } \hawkdraw_path_arc:n

\cs_new_protected:Npn \hawkdraw_path_arc:n #1 {
    \fp_gset_eq:NN \g_hawkdraw_path_previous_slope_fp \g_hawkdraw_path_slope_last_fp
    \str_set:Nn \l__hawkdraw_path_type_tl { arc }
    \keys_set:nn { hawkdraw / path / arc } {#1}
    \fp_set_eq:NN \l__hawkdraw_point_o_fp \g_hawkdraw_path_point_last_fp
    \fp_if_nan:nT { \g_hawkdraw_path_slope_first_fp } {
        \fp_gset:Nn \g_hawkdraw_path_slope_first_fp {
            \__hawkdraw_point_part_slope_arc:n { 0 }
        }
    }
    \fp_gset:Nn \g_hawkdraw_path_slope_last_fp {
        \__hawkdraw_point_part_slope_arc:n { 1 }
    }
    \fp_gset:Nn \g_hawkdraw_path_point_last_fp {
        \__hawkdraw_point_part_arc:n { 1 }
        % ( \draw_path_lastx: , \draw_path_lasty: )
    }
    \bool_if:NTF \l_hawkdraw_path_arc_axes_bool {
        \hawkdraw_softpath_build_arc_axes:nnnn
            { \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_softpath_build_arc:VVnn
            \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 }
    }
}

\dim_new:N \l_hawkdraw_path_circle_radius_dim

\keys_define:nn { hawkdraw / path / circle } {
    radius                  .dim_set:N      = \l_hawkdraw_path_circle_radius_dim ,
    radius                  .initial:n      = { 1cm } ,
}

\hawkdraw_operator_construct:nN { circle } \hawkdraw_path_circle:n

\cs_new_protected:Npn \hawkdraw_path_circle:n #1 {
    \str_set:Nn \l__hawkdraw_path_type_tl { circle }
    \fp_set_eq:NN \l__hawkdraw_point_o_fp \g_hawkdraw_path_point_last_fp
    \keys_set:nn { hawkdraw / path / circle } {#1}
    \hawkdraw_softpath_build_circle:nV
        { \l__hawkdraw_point_o_fp }
        \l_hawkdraw_path_circle_radius_dim
}

\fp_new:N \l_hawkdraw_path_ellipse_vector_a_fp
\fp_new:N \l_hawkdraw_path_ellipse_vector_b_fp

\keys_define:nn { hawkdraw / path / ellipse } {
    vector ~ a              .fp_set:N       = \l_hawkdraw_path_ellipse_vector_a_fp ,
    vector ~ a              .initial:n      = { ( 1cm , 0pt ) } ,
    vector ~ b              .fp_set:N       = \l_hawkdraw_path_ellipse_vector_b_fp ,
    vector ~ b              .initial:n      = { ( 0pt , 1cm ) } ,
    radius ~ x              .code:n         = {
        \fp_set:Nn \l_hawkdraw_path_ellipse_vector_a_fp { ( #1 , 0pt ) }
    } ,
    radius ~ y              .code:n         = {
        \fp_set:Nn \l_hawkdraw_path_ellipse_vector_b_fp { ( 0pt , #1 ) }
    } ,
}

\hawkdraw_operator_construct:nN { ellipse } \hawkdraw_path_ellipse:n

\cs_new_protected:Npn \hawkdraw_path_ellipse:n #1 {
    \str_set:Nn \l__hawkdraw_path_type_tl { ellipse }
    \fp_set_eq:NN \l__hawkdraw_point_o_fp \g_hawkdraw_path_point_last_fp
    \keys_set:nn { hawkdraw / path / ellipse } {#1}
    \hawkdraw_softpath_build_ellipse:nnn
        { \l__hawkdraw_point_o_fp }
        { \l_hawkdraw_path_ellipse_vector_a_fp }
        { \l_hawkdraw_path_ellipse_vector_b_fp }
}

\bool_new:N \l_hawkdraw_path_rectangle_relative_bool
\fp_new:N \l_hawkdraw_path_rectangle_corner_fp

\keys_define:nn { hawkdraw / path / rectangle } {
    relative                .bool_set:N     = \l_hawkdraw_path_rectangle_relative_bool ,
    relative                .default:n      = { true } ,
    relative                .initial:n      = { false } ,
    corner                  .code:n         = {
        \hawkdraw_point_parse_set:Nn \l_hawkdraw_path_rectangle_corner_fp {#1}
    } ,
    corner                  .initial:n      = { ( 1cm , 1cm ) } ,
}

\hawkdraw_operator_construct:nN { rectangle } \hawkdraw_path_rectangle:n

\cs_new_protected:Npn \hawkdraw_path_rectangle:n #1 {
    \str_set:Nn \l__hawkdraw_path_type_tl { rectangle }
    \fp_set_eq:NN \l__hawkdraw_point_o_fp \g_hawkdraw_path_point_last_fp
    \keys_set:nn { hawkdraw / path / rectangle } {#1}
    \bool_if:NTF \l_hawkdraw_path_rectangle_relative_bool {
        \fp_set:Nn \l__hawkdraw_point_a_fp {
            \l__hawkdraw_point_o_fp + \l_hawkdraw_path_rectangle_corner_fp
        }
    } {
        \fp_set_eq:NN \l__hawkdraw_point_a_fp \l_hawkdraw_path_rectangle_corner_fp
    }
    \hawkdraw_softpath_build_rectangle:nn
        { \l__hawkdraw_point_o_fp } { \l__hawkdraw_point_a_fp }
    \fp_gset_eq:NN \g_hawkdraw_path_point_last_fp \l__hawkdraw_point_a_fp
}

\clist_new:N \l_hawkdraw_path_grid_step_clist
\bool_new:N \l_hawkdraw_path_grid_relative_bool
\fp_new:N \l_hawkdraw_path_grid_corner_fp

\dim_new:N \l__hawkdraw_path_grid_xstep_dim
\dim_new:N \l__hawkdraw_path_grid_ystep_dim

\keys_define:nn { hawkdraw / path / grid } {
    step                    .clist_set:N    = \l_hawkdraw_path_grid_step_clist ,
    step                    .initial:n      = { 1cm } ,
    relative                .bool_set:N     = \l_hawkdraw_path_grid_relative_bool ,
    relative                .default:n      = { true } ,
    relative                .initial:n      = { false } ,
    corner                  .code:n         = {
        \hawkdraw_point_parse_set:Nn \l_hawkdraw_path_grid_corner_fp {#1}
    } ,
    corner                  .initial:n      = { ( 1cm , 1cm ) } ,
}

\hawkdraw_operator_construct:nN { grid } \hawkdraw_path_grid:n

\cs_new_protected:Npn \hawkdraw_path_grid:n #1 {
    \str_set:Nn \l__hawkdraw_path_type_tl { grid }
    \fp_set_eq:NN \l__hawkdraw_point_o_fp \g_hawkdraw_path_point_last_fp
    \keys_set:nn { hawkdraw / path / grid } {#1}
    \bool_if:NTF \l_hawkdraw_path_grid_relative_bool {
        \fp_set:Nn \l__hawkdraw_point_a_fp {
            \l__hawkdraw_point_o_fp + \l_hawkdraw_path_grid_corner_fp
        }
    } {
        \fp_set_eq:NN \l__hawkdraw_point_a_fp \l_hawkdraw_path_grid_corner_fp
    }
    \dim_set:Nn \l__hawkdraw_path_grid_xstep_dim {
        \clist_item:Nn \l_hawkdraw_path_grid_step_clist { 1 }
    }
    \dim_set:Nn \l__hawkdraw_path_grid_ystep_dim {
        \int_compare:nNnTF { \clist_count:N \l_hawkdraw_path_grid_step_clist } > { \c_one_int } {
            \clist_item:Nn \l_hawkdraw_path_grid_step_clist { 2 }
        } {
            \clist_item:Nn \l_hawkdraw_path_grid_step_clist { 1 }
        }
    }
    \hawkdraw_softpath_build_grid:VVnn
        \l__hawkdraw_path_grid_xstep_dim
        \l__hawkdraw_path_grid_ystep_dim
        { \l__hawkdraw_point_o_fp } { \l__hawkdraw_point_a_fp }
    \fp_gset_eq:NN \g_hawkdraw_path_point_last_fp \l__hawkdraw_point_a_fp
}

% ===

\clist_new:N \l_hawkdraw_map_list_clist
\fp_new:N \l_hawkdraw_map_start_fp
\fp_new:N \l_hawkdraw_map_step_fp
\fp_new:N \l_hawkdraw_map_end_fp
\int_new:N \l_hawkdraw_map_index_int
\int_new:N \l_hawkdraw_map_level_int

\keys_define:nn { hawkdraw /path / map } {
    list                    .clist_set:N    = \l_hawkdraw_map_list_clist ,
    start                   .fp_set:N       = \l_hawkdraw_map_start_fp ,
    start                   .initial:n      = { 1 } ,
    step                    .fp_set:N       = \l_hawkdraw_map_step_fp ,
    step                    .initial:n      = { 1 } ,
    end                     .fp_set:N       = \l_hawkdraw_map_end_fp ,
    end                     .initial:n      = { 1 } ,
}

\cs_new_protected:Npn \__hawkdraw_map_path_process:nn #1#2 {
    \hawkdraw_map:nn {#1} {
        \tl_trim_spaces_apply:nN {#2} \__hawkdraw_path_process_continue:n
    }
}

\hawkdraw_operator_construct_arg:nN { map } \__hawkdraw_map_path_process:nn

\cs_generate_variant:Nn \cs_generate_variant:Nn { cn }

\cs_new_protected:Npn \hawkdraw_map:nn #1#2 {
    \keys_set:nn { hawkdraw / path / map } {#1}
    \int_zero:N \l_hawkdraw_map_index_int
    \int_incr:N \l_hawkdraw_map_level_int
    \cs_if_exist:cF {
        __hawkdraw_map_function_ \int_use:N \l_hawkdraw_map_level_int :nn
    } {
        \cs_new_protected:cpn {
            __hawkdraw_map_function_ \int_use:N \l_hawkdraw_map_level_int :nn
        } ##1##2 { }
        \cs_generate_variant:cn {
            __hawkdraw_map_function_ \int_use:N \l_hawkdraw_map_level_int :nn
        } { nV }
    }
    \cs_set_protected:cpn {
        __hawkdraw_map_function_ \int_use:N \l_hawkdraw_map_level_int :nn
    } ##1##2 {#2}
    \clist_if_empty:NTF \l_hawkdraw_map_list_clist {
        \fp_step_function:nnnN
            { \l_hawkdraw_map_start_fp }
            { \l_hawkdraw_map_step_fp }
            { \l_hawkdraw_map_end_fp }
            \__hawkdraw_map_function:n
    } {
        \clist_map_function:NN
            \l_hawkdraw_map_list_clist
            \__hawkdraw_map_function:n
    }
    \int_decr:N \l_hawkdraw_map_level_int
}

\cs_new_protected:Npn \__hawkdraw_map_function:n #1 {
    \int_incr:N \l_hawkdraw_map_index_int
    \use:c { __hawkdraw_map_function_ \int_use:N \l_hawkdraw_map_level_int :nV }
        {#1} \l_hawkdraw_map_index_int
}

% ===

\keys_define:nn { hawkdraw / picture } {
    baseline                .fp_set:N       = \l_hawkdraw_picture_baseline_fp ,
    layers                  .code:n         = {
        \clist_set:Nn \l_draw_layers_clist {#1}
    } ,
    bounding ~ box          .code:n         = {
        \str_case:nnF {#1} {
            { auto } {
                \clist_clear:N \l_hawkdraw_picture_bb_clist
            }
            { zero } {
                \clist_set:Nn \l_hawkdraw_picture_bb_clist
                    { 0pt , 0pt , 0pt , 0pt }
            }
        } {
            \clist_set:Nn \l_hawkdraw_picture_bb_clist {#1}
        }
    } ,
    overlay                 .meta:n         = {
        bounding ~ box = { zero }
    } ,
    color ~ space           .choice: ,
    color ~ space / gray    .code:n         = {
        \tl_set:Nn \l_color_fixed_model_tl { gray }
    } ,
    color ~ space / rgb     .code:n         = {
        \tl_set:Nn \l_color_fixed_model_tl { rgb }
    } ,
    color ~ space / cmyk    .code:n         = {
        \tl_set:Nn \l_color_fixed_model_tl { cmyk }
    } ,
}

\cs_new_protected:Npn \hawkdraw_scope_use:n #1 {#1}

\keys_define:nn { hawkdraw / scope } {
    layer                   .tl_set:N       = \l_hawkdraw_scope_layer_tl ,
}

% ===

\NewDocumentCommand \hawkdrawarrayset { m m } {
    \clist_new:c { g__hawkdraw_user_array_ #1 _clist }
    \clist_gset:cn { g__hawkdraw_user_array_ #1 _clist } {#2}
}

\NewExpandableDocumentCommand \hawkdrawarrayitem { m m } {
    \clist_item:cn { g__hawkdraw_user_array_ #1 _clist } {#2}
}

\NewDocumentCommand \hawkdrawmap { O { } m } {
    \hawkdraw_map:nn {#1} {#2}
}

\NewDocumentCommand \hawkdrawsetcolor { m m m } {
    \str_if_eq:nnTF {#2} { named } {
        \color_set:nn {#1} {#3}
    } {
        \color_set:nnn {#1} {#2} {#3}
    }
}

\NewDocumentCommand \hawkdrawset { m } {
    \clist_clear:N \l__hawkdraw_keys_unprocessed_clist
    \keys_set_known:nnnN { hawkdraw / path } {#1} { hawkdraw / path }
        \l__hawkdraw_keys_unprocessed_clist
    \keys_set:nV { hawkdraw }
        \l__hawkdraw_keys_unprocessed_clist
}

\NewDocumentEnvironment { hawkdraw } { O { } } {
    % ensure that relevant characters are not active
    \seq_clear:N \l__hawkdraw_chars_active_seq
    \int_step_inline:nnn { 32 } { 127 } {
        \int_compare:nNnT { \char_value_catcode:n {##1} } = { 13 } {
            \seq_put_right:Nn \l__hawkdraw_chars_active_seq {##1}
        }
    }
    \seq_if_empty:NF \l__hawkdraw_chars_active_seq {
        \keys_set:nn { hawkdraw / path / node } { rescan }
    }
    \cctab_select:N \c_hawkdraw_environment_cctab
    \int_gincr:N \g_hawkdraw_picture_id_int
    \cs_set_eq:NN \path \hawkdraw_path:w
    \cs_set_eq:NN \getpoint \hawkdraw_point_parse:n
    \cs_set_protected:Npn \stroke { \path [ stroke ] }
    \cs_set_protected:Npn \fill { \path [ fill ] }
    \cs_set_protected:Npn \clip { \path [ clip ] }
    \cs_set_protected:Npn \node { \path ( 0pt , 0pt ) node }
    \cs_set_protected:Npn \point { \path ( 0pt , 0pt ) point }
    \cs_set_protected:Npn \map { \path ( 0pt , 0pt ) map }
    \NewExpandableDocumentCommand \getpointx { m } {
        \hawkdraw_tuple_use_i:n { \hawkdraw_point_parse:n {##1} }
    }
    \NewExpandableDocumentCommand \getpointy { m } {
        \hawkdraw_tuple_use_ii:n { \hawkdraw_point_parse:n {##1} }
    }
    \NewExpandableDocumentCommand \getintersectionlines { m m m m } {
        \draw_point_intersect_lines:nnnn
            { \hawkdraw_point_parse:n {##1} } { \hawkdraw_point_parse:n {##2} }
            { \hawkdraw_point_parse:n {##3} } { \hawkdraw_point_parse:n {##4} }
    }
    \NewExpandableDocumentCommand \getintersectioncircles { O { 1 } m m m m } {
        \draw_point_intersect_circles:nnnnn
            { \hawkdraw_point_parse:n {##2} } {##3}
            { \hawkdraw_point_parse:n {##4} } {##5}
            {##1}
    }
    \NewExpandableDocumentCommand \getintersectionlinecircle { O { 1 } m m m m } {
        \draw_point_intersect_line_circle:nnnnn
            { \hawkdraw_point_parse:n {##2} } { \hawkdraw_point_parse:n {##3} }
            { \hawkdraw_point_parse:n {##4} } {##5}
            {##1}
    }
    \NewExpandableDocumentCommand \getfirstpoint { } {
        \fp_use:N \g_hawkdraw_path_point_first_fp
    }
    \NewExpandableDocumentCommand \getlastpoint { } {
        \fp_use:N \g_hawkdraw_path_point_last_fp
    }
    \NewExpandableDocumentCommand \getfirstslope { } {
        \fp_use:N \g_hawkdraw_path_slope_first_fp
    }
    \NewExpandableDocumentCommand \getlastslope { } {
        \fp_use:N \g_hawkdraw_path_slope_last_fp
    }
    \NewDocumentCommand \getpathlength { m } {
        \hawkdraw_path_get_length:
        \dim_set:Nn #1 {
            \fp_to_dim:N \g_hawkdraw_path_length_fp
        }
    }
    \NewDocumentEnvironment { scope } { O { } +b } {
        \draw_scope_begin:
            \hook_use:n { hawkdraw / scope / begin }
            \clist_clear:N \l__hawkdraw_keys_unprocessed_clist
            \keys_set_known:nnN { hawkdraw / scope } {##1}
                \l__hawkdraw_keys_unprocessed_clist
            \keys_set_known:nVN { hawkdraw / tag } \l__hawkdraw_keys_unprocessed_clist
                \l__hawkdraw_keys_unprocessed_clist
            \keys_set_exclude_groups:nnV { hawkdraw / path } { tag }
                \l__hawkdraw_keys_unprocessed_clist
            \tl_if_empty:NF \l_hawkdraw_scope_layer_tl {
                \draw_layer_begin:V \l_hawkdraw_scope_layer_tl
            }
            \hawkdraw_scope_use:n {##2}
    } {
            \tl_if_empty:NF \l_hawkdraw_scope_layer_tl {
                \draw_layer_end:
            }
            \hook_use:n { hawkdraw / scope / end }
        \draw_scope_end:
    }
    \fp_set:Nn \l_hawkdraw_picture_baseline_fp { nan }
    \mode_leave_vertical:
    \tag_socket_use:nn { hawkdraw / picture / init } {#1}
    \tag_socket_use:n { hawkdraw / picture / begin }
    \tag_suspend:n { hawkdraw / picture }
    \nullfont
    \draw_begin:
        \clist_clear:N \l__hawkdraw_keys_unprocessed_clist
        \keys_set_known:nnN { hawkdraw / picture } {#1}
            \l__hawkdraw_keys_unprocessed_clist
        \keys_set_exclude_groups:nnV { hawkdraw / path } { tag }
            \l__hawkdraw_keys_unprocessed_clist
        \fp_if_nan:nF { \l_hawkdraw_picture_baseline_fp } {
            \draw_set_baseline:n {
                \fp_to_dim:n { \l_hawkdraw_picture_baseline_fp }
            }
        }
        \int_compare:nNnT { \clist_count:N \l_hawkdraw_picture_bb_clist } = { 4 } {
            \draw_path_rectangle_corners:nn {
                (
                    \clist_item:Nn \l_hawkdraw_picture_bb_clist { 1 }
                ,
                    \clist_item:Nn \l_hawkdraw_picture_bb_clist { 2 }
                )
            } {
                (
                    \clist_item:Nn \l_hawkdraw_picture_bb_clist { 3 }
                ,
                    \clist_item:Nn \l_hawkdraw_picture_bb_clist { 4 }
                )
            }
            \draw_path_replace_bb:
            \bool_set_false:N \l_draw_bb_update_bool
        }
} {
    \draw_end:
    \tag_resume:n { hawkdraw / picture }
    \tag_socket_use:nn { hawkdraw / picture / end } {
        \hawkdraw_tag_bb_attribute_use:
    }
}

% EOF
