% 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.4.0 2026-07-01 \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 } } \dim_new:N \l__hawkdraw_node_width_dim \dim_new:N \l__hawkdraw_node_height_dim \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 \tl_if_blank:VF \l_hawkdraw_point_name_tl { \dim_set:Nn \l__hawkdraw_node_width_dim { \coffin_wd:N \l_hawkdraw_node_frame_coffin } \dim_set:Nn \l__hawkdraw_node_height_dim { \coffin_ht_plus_dp:N \l_hawkdraw_node_frame_coffin } \prop_gput:cnV { g__hawkdraw_point_ \l_hawkdraw_point_name_tl _prop } { frame } \l_hawkdraw_node_frame_tl \prop_gput:cnV { g__hawkdraw_point_ \l_hawkdraw_point_name_tl _prop } { width } \l__hawkdraw_node_width_dim \prop_gput:cnV { g__hawkdraw_point_ \l_hawkdraw_point_name_tl _prop } { height } \l__hawkdraw_node_height_dim } \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:nnn #1#2#3 { \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 } \cs_new_protected:cpn { hawkdraw_node_frame_point_ #1 :nnnn } ##1##2##3##4 { #3 } } % === \hawkdraw_node_create_frame:nnn { none } { } { ( #1 ) } \hawkdraw_node_create_frame:nnn { 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 ) } } { ( #1 ) + ( \draw_point_polar:nn { min( ( #2 ) / abs( cosd( #4 ) ) , ( #3 ) / abs( sind( #4 ) ) ) } {#4} ) } \hawkdraw_node_create_frame:nnn { 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 ) } } { ( #1 ) + ( \draw_point_polar:nnn {#2} {#3} {#4} ) } \hawkdraw_node_create_frame:nnn { circle } { \draw_path_circle:nn { 0pt , 0pt } { max( 0.5 * \l_hawkdraw_node_natural_width_dim , 0.5 * \l_hawkdraw_node_natural_height_dim ) } } { ( #1 ) + ( \draw_point_polar:nnn {#2} {#3} {#4} ) } \hawkdraw_node_create_frame:nnn { 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: } { ( #1 ) + ( \draw_point_polar:nn { ( #2 ) * ( #3 ) / ( ( #2 ) * abs( sind( #4 ) ) + ( #3 ) * abs( cosd( #4 ) ) ) } {#4} ) } % EOF