% 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.1.0 2026-06-03 \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 } \NewTaggingSocketPlug { hawkdraw / node / begin } { default } { \tag_mc_end: \tag_mc_begin:n { } } \NewTaggingSocketPlug { hawkdraw / node / end }{ default } { \tag_mc_end: \tag_mc_begin:n { artifact } } \AssignTaggingSocketPlug { hawkdraw / node / begin } { default} \AssignTaggingSocketPlug { hawkdraw / node / 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_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 \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 } , 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 } , rescan .bool_set:N = \l_hawkdraw_node_rescan_bool , rescan .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 } { \bool_if:NF \g__hawkdraw_path_use_clip_bool { \hawkdraw_node_put:nn { } {#1} } \tl_trim_spaces_apply:nN {#2} \__hawkdraw_path_process_continue:n } } \cs_new_protected:cpn { __hawkdraw_path_process_node_ [ :w } [ #1 ] #2#3 \s__hawkdraw_stop { \bool_if:NF \g__hawkdraw_path_use_clip_bool { \hawkdraw_node_put:nn {#1} {#2} } \tl_trim_spaces_apply:nN {#3} \__hawkdraw_path_process_continue:n } \cs_new_protected:Npn \hawkdraw_node_contents:n #1 { \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_text_color_tl } \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 } } \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 / tag } \l__hawkdraw_keys_unprocessed_clist \l__hawkdraw_keys_unprocessed_clist \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 } } } } \str_if_eq:VnT \l_hawkdraw_tag_mode_tl { text } { \tag_resume:n { hawkdraw / picture } } \tag_socket_use:n { hawkdraw / node / begin } \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: \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 } \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 \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