% File: hawkdraw-nodes.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.0.8 2026-05-30

\msg_new:nnn { hawkdraw } { node-unknown } {
    Node ~ frame ~ `#1` ~ unknown.
}

\bool_new:N \l__hawkdraw_node_vbox_bool
\coffin_new:N \l_hawkdraw_node_coffin
\coffin_new:N \l_hawkdraw_node_frame_coffin

\clist_new:N \l_hawkdraw_node_anchor_clist
\dim_new:N \l_hawkdraw_node_width_dim
\clist_new:N \l_hawkdraw_node_padding_clist
\dim_new:N \l_hawkdraw_node_padding_left_dim
\dim_new:N \l_hawkdraw_node_padding_right_dim
\dim_new:N \l_hawkdraw_node_padding_top_dim
\dim_new:N \l_hawkdraw_node_padding_bottom_dim
\tl_new:N \l_hawkdraw_node_text_color_tl
\tl_new:N \l_hawkdraw_node_frame_tl
\bool_new:N \l_hawkdraw_node_sloped_bool
\bool_new:N \l_hawkdraw_node_flipped_bool

\cs_generate_variant:Nn \keys_set_known:nnN { nV }
\cs_generate_variant:Nn \draw_coffin_use:Nnnn { NeeV }
\cs_generate_variant:Nn \coffin_attach:NnnNnnnn { NnnNnnVV }

\keys_define:nn { hawkdraw / path / node } {
    anchor                  .clist_set:N    = \l_hawkdraw_node_anchor_clist ,
    anchor                  .initial:n      = { hc , vc } ,
    width                   .code:n         = {
        \bool_set_true:N \l__hawkdraw_node_vbox_bool
        \dim_set:Nn \l_hawkdraw_node_width_dim {#1}
    } ,
    padding                 .clist_set:N    = \l_hawkdraw_node_padding_clist ,
    padding                 .initial:n      = { 5pt } ,
    text ~ color            .tl_set:N       = \l_hawkdraw_node_text_color_tl ,
    text ~ color            .initial:n      = { . } ,
    frame                   .tl_set:N       = \l_hawkdraw_node_frame_tl ,
    frame                   .initial:n      = { rectangle } ,
    sloped                  .bool_set:N     = \l_hawkdraw_node_sloped_bool ,
    sloped                  .default:n      = { true } ,
    flipped                 .bool_set:N     = \l_hawkdraw_node_flipped_bool ,
    flipped                 .default:n      = { true } ,
}

\cs_new_protected:cpn { __hawkdraw_path_process_ n :w } node #1#2 \s__hawkdraw_stop {
    \__hawkdraw_cs_if_exist_use_secure:nnTF {#1} {
        __hawkdraw_path_process_node_ \tl_head:n {#1} :w
    } {
        #1#2 \s__hawkdraw_stop
    } {
        \hawkdraw_node_put:nn { } {#1}
        \tl_trim_spaces_apply:nN {#2} \__hawkdraw_path_continue:n
    }
}

\cs_new_protected:cpn { __hawkdraw_path_process_node_ [ :w } [ #1 ] #2#3 \s__hawkdraw_stop {
    \hawkdraw_node_put:nn {#1} {#2}
    \tl_trim_spaces_apply:nN {#3} \__hawkdraw_path_continue:n
}

\cs_new_protected:Npn \hawkdraw_node_put:nn #1#2 {
    \draw_scope_begin:
        \fp_set_eq:NN \l_hawkdraw_point_at_fp \g_hawkdraw_path_last_point_fp
        \fp_set_eq:NN \l_hawkdraw_point_slope_fp \g_hawkdraw_path_last_slope_fp
        \fp_set:Nn \l_hawkdraw_point_at_part_fp { nan }
        \clist_clear:N \l__hawkdraw_keys_unprocessed_clist
        \keys_set_known:nnN { hawkdraw / path / point } {#1}
            \l__hawkdraw_keys_unprocessed_clist
        \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 :V
                } {
                    \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 :V
                } {
                    \l_hawkdraw_point_at_part_fp
                } {
                    0
                }
            }
        }
        \tl_if_blank:VF \l_hawkdraw_point_at_name_tl {
            \prop_if_exist:cT {
                g__hawkdraw_point_ \l_hawkdraw_point_at_name_tl _prop
            } {
                \fp_set:Nn \l_hawkdraw_point_slope_fp {
                    \prop_item:cn {
                        g__hawkdraw_point_ \l_hawkdraw_point_at_name_tl _prop
                    } { slope }
                }
            }
        }
        \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
        }
        \keys_set_known:nVN { hawkdraw / path / node }
            \l__hawkdraw_keys_unprocessed_clist
            \l__hawkdraw_keys_unprocessed_clist
        \bool_if:NTF \l__hawkdraw_node_vbox_bool {
            \vcoffin_set:Nnn \l_hawkdraw_node_coffin {
                \l_hawkdraw_node_width_dim
            } {
                \color_select:e { \l_hawkdraw_node_text_color_tl }
                \normalfont #2
                \skip_horizontal:n { 0pt plus 1fil minus 1fil }
            }
        } {
            \hcoffin_set:Nn \l_hawkdraw_node_coffin {
                \color_select:e { \l_hawkdraw_node_text_color_tl }
                \normalfont #2
                \skip_horizontal:n { 0pt plus 1fil minus 1fil }
            }
        }
        \__hawkdraw_node_set_padding:
        \__hawkdraw_node_set_frame:V \l__hawkdraw_keys_unprocessed_clist
        \coffin_attach:NnnNnnnn
            \l_hawkdraw_node_frame_coffin { hc } { vc }
            \l_hawkdraw_node_coffin { hc } { vc }
            { 0.5 \l_hawkdraw_node_padding_left_dim - 0.5 \l_hawkdraw_node_padding_right_dim }
            { 0.5 \l_hawkdraw_node_padding_bottom_dim - 0.5 \l_hawkdraw_node_padding_top_dim }
        \bool_if:NT \l_hawkdraw_node_sloped_bool {
            \coffin_rotate:Nn \l_hawkdraw_node_frame_coffin {
                \l_hawkdraw_point_slope_fp
                \bool_if:NT \l_hawkdraw_node_flipped_bool { + 180 }
            }
        }
        \tl_if_blank:VF \l_hawkdraw_point_name_tl {
            \clist_map_inline:Nn \c__hawkdraw_node_vpoles_clist {
                \bool_if:NTF \l__hawkdraw_node_vbox_bool {
                    \clist_map_inline:Nn \c__hawkdraw_node_vhpoles_clist {
                        \__hawkdraw_node_anchors_gset:NVnnnN
                            \l_hawkdraw_node_frame_coffin
                            \l_hawkdraw_point_name_tl {##1} {####1}
                            { \l_hawkdraw_point_at_fp }
                            \l_hawkdraw_node_anchor_clist
                    }
                } {
                    \clist_map_inline:Nn \c__hawkdraw_node_hhpoles_clist {
                        \__hawkdraw_node_anchors_gset:NVnnnN
                            \l_hawkdraw_node_frame_coffin
                            \l_hawkdraw_point_name_tl {##1} {####1}
                            { \l_hawkdraw_point_at_fp }
                            \l_hawkdraw_node_anchor_clist
                    }
                }
            }
        }
        \draw_coffin_use:NeeV \l_hawkdraw_node_frame_coffin {
            \clist_item:Nn \l_hawkdraw_node_anchor_clist { 1 }
        } {
            \clist_item:Nn \l_hawkdraw_node_anchor_clist { 2 }
        } \l_hawkdraw_point_at_fp
    \draw_scope_end:
}

% ===

\cs_new_protected:Npn \__hawkdraw_node_anchors_gset:NnnnnN #1#2#3#4#5#6 {
    \prop_if_exist:cF {
        g__hawkdraw_point_ #2 . #3 - #4 _prop
    } {
        \prop_new_linked:c {
            g__hawkdraw_point_ #2 . #3 - #4 _prop
        }
    }
    \prop_if_exist:cF {
        g__hawkdraw_point_ #2 . #4 - #3 _prop
    } {
        \prop_new_linked:c {
            g__hawkdraw_point_ #2 . #4 - #3 _prop
        }
    }
    \prop_gput:cne { g__hawkdraw_point_ #2 . #3 - #4 _prop }
        { point } {
            \fp_eval:n {
                ( #5 ) +
                (
                    \exp_last_unbraced:Ne \use_i:nnnn {
                        \coffin_pole:Nn #1 { #3 }
                    }
                ,
                    \exp_last_unbraced:Ne \use_ii:nnnn {
                        \coffin_pole:Nn #1 { #4 }
                    }
                ) -
                \__hawkdraw_node_point_normalized:NN
                    #1 #6
            }
        }
    \prop_gput:cne { g__hawkdraw_point_ #2 . #4 - #3 _prop }
        { point } {
            \fp_eval:n {
                ( #5 ) +
                (
                    \exp_last_unbraced:Ne \use_i:nnnn {
                        \coffin_pole:Nn #1 { #3 }
                    }
                ,
                    \exp_last_unbraced:Ne \use_ii:nnnn {
                        \coffin_pole:Nn #1 { #4 }
                    }
                ) -
                \__hawkdraw_node_point_normalized:NN
                    #1 #6
            }
        }
}
\cs_generate_variant:Nn \__hawkdraw_node_anchors_gset:NnnnnN { NV }

\cs_new:Npn \__hawkdraw_node_point_normalized:NN #1#2 {
    \fp_eval:n {
        ( 0pt , 0pt ) 
        \clist_map_tokens:Nn #2 {
            \__hawkdraw_node_point_normalize_x:Nn #1
        } 
        \clist_map_tokens:Nn #2 {
            \__hawkdraw_node_point_normalize_y:Nn #1
        } 
    }
}

\cs_new:Npn \__hawkdraw_node_point_normalize_x:Nn #1#2 {
    + (
        \exp_last_unbraced:Ne \use_i:nnnn {
            \coffin_pole:Nn #1 { #2 }
        }
    , 0pt )
}

\cs_new:Npn \__hawkdraw_node_point_normalize_y:Nn #1#2 {
    + ( 0pt ,
        \exp_last_unbraced:Ne \use_ii:nnnn {
            \coffin_pole:Nn #1 { #2 }
        }
    )
}

% ===

\cs_new_protected:Npn \__hawkdraw_node_set_padding: {
    \int_case:nn { \clist_count:N \l_hawkdraw_node_padding_clist } {
        { 1 } {
            \dim_set:Nn \l_hawkdraw_node_padding_left_dim {
                \clist_item:Nn \l_hawkdraw_node_padding_clist { 1 }
            }
            \dim_set:Nn \l_hawkdraw_node_padding_right_dim {
                \clist_item:Nn \l_hawkdraw_node_padding_clist { 1 }
            }
            \dim_set:Nn \l_hawkdraw_node_padding_top_dim {
                \clist_item:Nn \l_hawkdraw_node_padding_clist { 1 }
            }
            \dim_set:Nn \l_hawkdraw_node_padding_bottom_dim {
                \clist_item:Nn \l_hawkdraw_node_padding_clist { 1 }
            }
        }
        { 2 } {
            \dim_set:Nn \l_hawkdraw_node_padding_left_dim {
                \clist_item:Nn \l_hawkdraw_node_padding_clist { 2 }
            }
            \dim_set:Nn \l_hawkdraw_node_padding_right_dim {
                \clist_item:Nn \l_hawkdraw_node_padding_clist { 2 }
            }
            \dim_set:Nn \l_hawkdraw_node_padding_top_dim {
                \clist_item:Nn \l_hawkdraw_node_padding_clist { 1 }
            }
            \dim_set:Nn \l_hawkdraw_node_padding_bottom_dim {
                \clist_item:Nn \l_hawkdraw_node_padding_clist { 1 }
            }
        }
        { 3 } {
            \dim_set:Nn \l_hawkdraw_node_padding_left_dim {
                \clist_item:Nn \l_hawkdraw_node_padding_clist { 2 }
            }
            \dim_set:Nn \l_hawkdraw_node_padding_right_dim {
                \clist_item:Nn \l_hawkdraw_node_padding_clist { 2 }
            }
            \dim_set:Nn \l_hawkdraw_node_padding_top_dim {
                \clist_item:Nn \l_hawkdraw_node_padding_clist { 1 }
            }
            \dim_set:Nn \l_hawkdraw_node_padding_bottom_dim {
                \clist_item:Nn \l_hawkdraw_node_padding_clist { 3 }
            }
        }
        { 4 } {
            \dim_set:Nn \l_hawkdraw_node_padding_left_dim {
                \clist_item:Nn \l_hawkdraw_node_padding_clist { 4 }
            }
            \dim_set:Nn \l_hawkdraw_node_padding_right_dim {
                \clist_item:Nn \l_hawkdraw_node_padding_clist { 2 }
            }
            \dim_set:Nn \l_hawkdraw_node_padding_top_dim {
                \clist_item:Nn \l_hawkdraw_node_padding_clist { 1 }
            }
            \dim_set:Nn \l_hawkdraw_node_padding_bottom_dim {
                \clist_item:Nn \l_hawkdraw_node_padding_clist { 3 }
            }
        }
    }
}

\cs_new_protected:Npn \__hawkdraw_node_set_frame:n #1 {
    \hcoffin_set:Nn \l_hawkdraw_node_frame_coffin {
        \draw_suspend_begin:
            \draw_begin:
                \keys_set:nn { hawkdraw / path } {#1}
                \hawkdraw_path_set_options:
                \cs_if_exist_use:cTF { hawkdraw_node_frame_ \l_hawkdraw_node_frame_tl :NVVVV } {
                    \l_hawkdraw_node_coffin
                    \l_hawkdraw_node_padding_left_dim
                    \l_hawkdraw_node_padding_right_dim
                    \l_hawkdraw_node_padding_top_dim
                    \l_hawkdraw_node_padding_bottom_dim
                } {
                    \msg_warning:nnV { hawkdraw } { node-unknown } \l_hawkdraw_node_frame_tl
                }
                \clist_remove_duplicates:N \l_hawkdraw_path_use_clist
                \draw_path_use_clear:e { \clist_use:N \l_hawkdraw_path_use_clist }
            \draw_end:
        \draw_suspend_end:
    }
}
\cs_generate_variant:Nn \__hawkdraw_node_set_frame:n { V }

\dim_new:N \hawkdraw_node_natural_width_dim
\dim_new:N \hawkdraw_node_natural_height_dim

\cs_new_protected:Npn \hawkdraw_node_create_frame:nn #1#2 {
    \cs_new_protected:cpn { hawkdraw_node_frame_ #1 :Nnnnn } ##1##2##3##4##5 {
        \dim_set:Nn \hawkdraw_node_natural_width_dim {
            \coffin_wd:N ##1 + ##2 + ##3
        }
        \dim_set:Nn \hawkdraw_node_natural_height_dim {
            \coffin_ht_plus_dp:N ##1 + ##4 + ##5
        }
        #2
    }
    \cs_generate_variant:cn { hawkdraw_node_frame_ #1 :Nnnnn } { NVVVV }
}

% ===

\hawkdraw_node_create_frame:nn { none } { }

\hawkdraw_node_create_frame:nn { rectangle } {
    \draw_path_rectangle:nn {
        (
            -0.5 * \hawkdraw_node_natural_width_dim
        ,
            -0.5 * \hawkdraw_node_natural_width_dim
        )
    } {
        (
            \hawkdraw_node_natural_width_dim
        ,
            \hawkdraw_node_natural_height_dim
        )
    }
}

\hawkdraw_node_create_frame:nn { ellipse } {
    \draw_path_ellipse:nnn { 0pt , 0pt } {
        ( 0.5 * \hawkdraw_node_natural_width_dim , 0pt )
    } {
        ( 0pt , 0.5 * \hawkdraw_node_natural_height_dim )
    }
}

\hawkdraw_node_create_frame:nn { circle } {
    \draw_path_circle:nn { 0pt , 0pt } {
        max(
            0.5 * \hawkdraw_node_natural_width_dim
        ,
            0.5 * \hawkdraw_node_natural_height_dim
        )
    }
}

\hawkdraw_node_create_frame:nn { diamond } {
    \draw_path_moveto:n {
        ( 0pt , \hawkdraw_node_natural_height_dim )
    }
    \draw_path_lineto:n {
        ( \hawkdraw_node_natural_width_dim , 0pt )
    }
    \draw_path_lineto:n {
        ( 0pt , -1 * \hawkdraw_node_natural_height_dim )
    }
    \draw_path_lineto:n {
        ( -1 * \hawkdraw_node_natural_width_dim , 0pt )
    }
    \draw_path_close:
}

% EOF