% 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.3.0 2026-06-17

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

\clist_const:Nn \c__hawkdraw_node_vpoles_clist {
    l , hc , r
}
\clist_const:Nn \c__hawkdraw_node_hhpoles_clist {
    t , vc , b , H
}
\clist_const:Nn \c__hawkdraw_node_vhpoles_clist {
    t , vc , b , H , T , B
}

\NewTaggingSocket { hawkdraw / node / begin } { 0 }
\NewTaggingSocket { hawkdraw / node / end } { 0 }
\NewTaggingSocket { hawkdraw / node / contents / begin } { 0 }
\NewTaggingSocket { hawkdraw / node / contents / end } { 0 }
\NewTaggingSocket { hawkdraw / node / frame / begin } { 0 }
\NewTaggingSocket { hawkdraw / node / frame / end } { 0 }

\NewTaggingSocketPlug { hawkdraw / node / begin } { default } {
    \tag_mc_end:
}
\NewTaggingSocketPlug { hawkdraw / node / end }{ default } {
    \tag_mc_begin:n { artifact }
}

\NewTaggingSocketPlug { hawkdraw / node / contents / begin } { default } {
    \tag_mc_begin:n { }
}
\NewTaggingSocketPlug { hawkdraw / node / contents / end }{ default } {
    \tag_mc_end:
}

\NewTaggingSocketPlug { hawkdraw / node / frame / begin } { default } {
    \tag_mc_begin:n { artifact }
}
\NewTaggingSocketPlug { hawkdraw / node / frame / end }{ default } {
    \tag_mc_end:
}

\AssignTaggingSocketPlug { hawkdraw / node / begin } { default}
\AssignTaggingSocketPlug { hawkdraw / node / end } { default}
\AssignTaggingSocketPlug { hawkdraw / node / contents / begin } { default}
\AssignTaggingSocketPlug { hawkdraw / node / contents / end } { default}
\AssignTaggingSocketPlug { hawkdraw / node / frame / begin } { default}
\AssignTaggingSocketPlug { hawkdraw / node / frame / end } { default}

\hook_new_pair:nn { hawkdraw / node / begin } { hawkdraw / node / end }

\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_color_text_tl
\fp_new:N \l_hawkdraw_node_opacity_text_fp
\tl_new:N \l_hawkdraw_node_frame_tl
\bool_new:N \l_hawkdraw_node_sloped_bool
\bool_new:N \l_hawkdraw_node_flipped_bool

\bool_new:N \l_hawkdraw_node_rescan_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 } ,
    color ~ text            .tl_set:N       = \l_hawkdraw_node_color_text_tl ,
    color ~ text            .initial:n      = { . } ,
    opacity ~ text          .fp_set:N       = \l_hawkdraw_node_opacity_text_fp ,
    opacity ~ text          .initial:n      = { 1 } ,
    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 } ,
    rescan                  .bool_set:N     = \l_hawkdraw_node_rescan_bool ,
    rescan                  .default:n      = { true } ,
}

\hawkdraw_operator_construct_arg:nN { node } \hawkdraw_node:nn

\cs_new_protected:Npn \hawkdraw_node_contents:n #1 {
    \tag_socket_use:n { hawkdraw / node / contents / begin }
    \cctab_select:N \c_document_cctab
    \bool_if:NT \l_hawkdraw_node_rescan_bool {
        \seq_map_inline:Nn \l__hawkdraw_chars_active_seq {
            \char_set_catcode_active:n {##1}
        }
    }
    \color_select:e { \l_hawkdraw_node_color_text_tl }
    \opacity_select:V \l_hawkdraw_node_opacity_text_fp
    \normalfont
    \hook_use:n { hawkdraw / node / begin }
    \bool_if:NTF \l_hawkdraw_node_rescan_bool {
        \tl_rescan:nn { } {#1}
    } {
        #1
    }
    \hook_use:n { hawkdraw / node / end }
    \skip_horizontal:n { 0pt plus 1fil minus 1fil }
    \tag_socket_use:n { hawkdraw / node / contents / end }
}

\cs_new_protected:Npn \hawkdraw_node:nn #1#2 {
    \draw_scope_begin:
        \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 }
        \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 / tag }
            \l__hawkdraw_keys_unprocessed_clist
            \l__hawkdraw_keys_unprocessed_clist
        \str_if_eq:VnT \l_hawkdraw_tag_mode_tl { text } {
            \tag_resume:n { hawkdraw / node }
        }
        \tag_socket_use:n { hawkdraw / node / begin }
        \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
            } {
                \hawkdraw_node_contents:n {#2}
            }
        } {
            \hcoffin_set:Nn \l_hawkdraw_node_coffin {
                \hawkdraw_node_contents:n {#2}
            }
        }
        \__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 }
        \coffin_set_horizontal_pole:Nnn
            \l_hawkdraw_node_frame_coffin { H } {
                \l_hawkdraw_node_padding_bottom_dim -
                \exp_last_unbraced:Ne \use_ii:nnnn {
                    \coffin_pole:Nn \l_hawkdraw_node_coffin { b }
                } -
                \coffin_dp:N \l_hawkdraw_node_frame_coffin
            }
        \bool_if:NT \l__hawkdraw_node_vbox_bool {
            \coffin_set_horizontal_pole:Nnn
                \l_hawkdraw_node_frame_coffin { T } {
                    \l_hawkdraw_node_padding_bottom_dim -
                    \exp_last_unbraced:Ne \use_ii:nnnn {
                        \coffin_pole:Nn \l_hawkdraw_node_coffin { b }
                    } +
                    \exp_last_unbraced:Ne \use_ii:nnnn {
                        \coffin_pole:Nn \l_hawkdraw_node_coffin { T }
                    } -
                    \coffin_dp:N \l_hawkdraw_node_frame_coffin
                }
            \coffin_set_horizontal_pole:Nnn
                \l_hawkdraw_node_frame_coffin { B } {
                    \l_hawkdraw_node_padding_bottom_dim -
                    \exp_last_unbraced:Ne \use_ii:nnnn {
                        \coffin_pole:Nn \l_hawkdraw_node_coffin { b }
                    } +
                    \exp_last_unbraced:Ne \use_ii:nnnn {
                        \coffin_pole:Nn \l_hawkdraw_node_coffin { B }
                    } -
                    \coffin_dp:N \l_hawkdraw_node_frame_coffin
                }
        }
        \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
        \tag_socket_use:n { hawkdraw / node / end }
    \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 } {
            \__hawkdraw_node_set_padding:nnnn { 1 } { 1 } { 1 } { 1 }
        }
        { 2 } {
            \__hawkdraw_node_set_padding:nnnn { 2 } { 2 } { 1 } { 1 }
        }
        { 3 } {
            \__hawkdraw_node_set_padding:nnnn { 2 } { 2 } { 1 } { 3 }
        }
        { 4 } {
            \__hawkdraw_node_set_padding:nnnn { 4 } { 2 } { 1 } { 3 }
        }
    }
}

\cs_new_protected:Npn \__hawkdraw_node_set_padding:nnnn #1#2#3#4 {
    \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 {#2}
    }
    \dim_set:Nn \l_hawkdraw_node_padding_top_dim {
        \clist_item:Nn \l_hawkdraw_node_padding_clist {#3}
    }
    \dim_set:Nn \l_hawkdraw_node_padding_bottom_dim {
        \clist_item:Nn \l_hawkdraw_node_padding_clist {#4}
    }
}

\cs_new_protected:Npn \__hawkdraw_node_set_frame:n #1 {
    \hcoffin_set:Nn \l_hawkdraw_node_frame_coffin {
        \draw_suspend_begin:
            \tag_socket_use:n { hawkdraw / node / frame / begin }
            \draw_begin:
                \keys_set_exclude_groups:nnn { hawkdraw / path } { tag }
                    {#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
                }
                \draw_path_use:n { }
                \bool_set_false:N \l_draw_bb_update_bool
                \clist_remove_duplicates:N \l_hawkdraw_path_use_clist
                \draw_path_use_clear:e { \clist_use:N \l_hawkdraw_path_use_clist }
            \draw_end:
            \tag_socket_use:n { hawkdraw / node / frame / end }
        \draw_suspend_end:
    }
}
\cs_generate_variant:Nn \__hawkdraw_node_set_frame:n { V }

\dim_new:N \l_hawkdraw_node_natural_width_dim
\dim_new:N \l_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 \l_hawkdraw_node_natural_width_dim {
            \coffin_wd:N ##1 + ##2 + ##3
        }
        \dim_set:Nn \l_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 * \l_hawkdraw_node_natural_width_dim
        ,
            -0.5 * \l_hawkdraw_node_natural_height_dim
        )
    } {
        (
            \l_hawkdraw_node_natural_width_dim
        ,
            \l_hawkdraw_node_natural_height_dim
        )
    }
}

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

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

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

% EOF