%%% ---------------------------------------------------------------------------- %%% joinbox: Join figures to same height or width with LaTeX3 %%% Author : Nan Geng <nangeng@nwafu.edu.cn> %%% Repository: https://gitee.com/nwafu_nan/joinfigs %%% License : The LaTeX Project Public License 1.3c %%% ---------------------------------------------------------------------------- \NeedsTeXFormat{LaTeX2e} \RequirePackage{expl3} \ProvidesExplPackage{joinbox}{2024-09-09}{v1.0.3} {Join figures to same height or width with LaTeX3} \RequirePackage{xparse} \RequirePackage{graphicx} %% \tl_if_eq:NnTF 与texlive 2020的兼容性设置 \cs_if_exist:NF \tl_if_eq:NnTF { \tl_new:N \l__tblr_backport_b_tl \prg_new_protected_conditional:Npnn \tl_if_eq:Nn #1 #2 { T, F, TF } { \group_begin: \tl_set:Nn \l__tblr_backport_b_tl {#2} \exp_after:wN \group_end: \if_meaning:w #1 \l__tblr_backport_b_tl \prg_return_true: \else: \prg_return_false: \fi: } \prg_generate_conditional_variant:Nnn \tl_if_eq:Nn { c } { TF, T, F } } \cs_if_exist:NF \seq_map_indexed_function:NN { \cs_set_eq:NN \seq_map_indexed_function:NN \seq_indexed_map_function:NN } \cs_new:Npn \__joinbox_error:n { \msg_error:nn { joinbox } } % 函数å˜ä½“ \cs_generate_variant:Nn \hcoffin_set:Nn { Nx } % 定义å˜é‡ \bool_new:N \l__joinbox_vertical_bool \bool_new:N \l__joinbox_out_scale_bool \bool_new:N \l__joinbox_only_first_bool \bool_new:N \l__joinbox_only_second_bool \int_new:N \l__joinbox_baseline_int \clist_new:N \l__joinbox_name_clist \clist_new:N \l__joinbox_contents_clist \coffin_new:N \l__joinbox_out_coffin \coffin_new:N \l__joinbox_tmpa_coffin \coffin_new:N \l__joinbox_tmpb_coffin \dim_new:N \l__joinbox_out_length_dim \dim_new:N \l__joinbox_sep_dim \dim_new:N \l__joinbox_min_width_dim \dim_new:N \l__joinbox_min_height_dim \dim_new:N \l__joinbox_tmpa_dim \dim_new:N \l__joinbox_tmpb_dim \bool_set_false:N \l__joinbox_only_first_bool \bool_set_false:N \l__joinbox_only_second_bool %% 选项设计 \keys_define:nn { joinbox } { % 输出结果基线ä½ç½® baseline .choice:, baseline .value_required:n = true, baseline .choices:nn = { t, vc, H, b } { \int_set_eq:NN \l__joinbox_baseline_int \l_keys_choice_int }, baseline .default:n = vc, baseline .initial:n = b, % 输出尺寸(垂直拼接:宽度,水平拼接:高度) outlen .code:n = { \dim_compare:nNnTF { \dim_eval:n{ #1 } } < \c_zero_dim { \bool_set_false:N \l__joinbox_out_scale_bool }{ \dim_compare:nNnTF { \dim_eval:n{ #1 } } = \c_zero_dim { \bool_set_false:N \l__joinbox_out_scale_bool }{ \bool_set_true:N \l__joinbox_out_scale_bool \dim_set:Nn \l__joinbox_out_length_dim { \dim_eval:n{ #1 } } } } }, outlen .default:n = 0pt, outlen .initial:n = 0pt, % æ‹¼æŽ¥é—´è· sep .dim_set:N = \l__joinbox_sep_dim, sep .default:n = 0pt, sep .initial:n = 0pt, unknown .code:n = \__joinbox_unknown_key:V \l_keys_key_str, } \cs_new_protected:Npn \__joinbox_unknown_key:n #1 { \str_case:nnF { #1 } { { t } { \int_set:Nn \l__joinbox_baseline_int { 1 } } { vc } { \int_set:Nn \l__joinbox_baseline_int { 2 } } { H } { \int_set:Nn \l__joinbox_baseline_int { 3 } } { b } { \int_set:Nn \l__joinbox_baseline_int { 4 } } }{ % 转æ¢ä¸ºtoken \tl_set_rescan:Nnn \l_tmpa_tl {} {#1} % 计算尺寸 \dim_set:Nn \l_tmpa_dim { \dim_eval:n { \l_tmpa_tl } } \dim_compare:nNnTF \l_tmpa_dim < \c_zero_dim { \bool_set_false:N \l__joinbox_out_scale_bool }{ \dim_compare:nNnTF \l_tmpa_dim = \c_zero_dim { \bool_set_false:N \l__joinbox_out_scale_bool }{ \bool_set_true:N \l__joinbox_out_scale_bool \dim_set_eq:NN \l__joinbox_out_length_dim \l_tmpa_dim } } } } \cs_generate_variant:Nn \__joinbox_unknown_key:n { V } %% 傿•°è®¾ç½®ç”¨æˆ·æŽ¥å£ \NewDocumentCommand \joinset { m } { \keys_set:nn { joinbox } {#1} } % 计算boxç›’å的总高度 % #1---ç›’åå˜é‡ \cs_if_free:NT \box_ht_plus_dp:N { \cs_new_protected:Npn \box_ht_plus_dp:N #1 { \tex_dimexpr:D \box_ht:N #1 + \box_dp:N #1 \scan_stop: } } % 计算coffinç›’å的总高度 % #1---ç›’åå˜é‡ \cs_new_nopar:Npn \__joinbox_coffin_ht_plus_dp:N #1 { \coffin_ht:N #1 + \coffin_dp:N #1 } % 计算两个盒å的最å°å®½åº¦å’Œæœ€å°é«˜åº¦ % (最å°å®½åº¦å’Œæœ€å°å®½åº¦ä¸ä¸€å®šå±žäºŽåŒä¸€ä¸ªç›’å) \cs_new:Npn \__joinbox_calc_min_size:nn #1#2 { % 最å°å€¼æ¸…0 \dim_zero:N \l__joinbox_min_width_dim \dim_zero:N \l__joinbox_min_height_dim % å–得第1个盒å的宽度和高度 \hbox_set:Nn \l_tmpa_box { #1 } \dim_set:Nn \l_tmpa_dim { \box_wd:N \l_tmpa_box } \dim_set_eq:NN \l__joinbox_min_width_dim \l_tmpa_dim \dim_set:Nn \l_tmpb_dim { \box_ht_plus_dp:N \l_tmpa_box } \dim_set_eq:NN \l__joinbox_min_height_dim \l_tmpb_dim % å–得第2个盒å的宽度和高度,并与第1ä¸ªç›’åæ¯”较 \hbox_set:Nn \l_tmpa_box { #2 } \dim_set:Nn \l_tmpa_dim { \box_wd:N \l_tmpa_box } \dim_set:Nn \l_tmpb_dim { \box_ht_plus_dp:N \l_tmpa_box } \bool_if:NT \l__joinbox_only_second_bool { \dim_set_eq:NN \l__joinbox_min_width_dim \l_tmpa_dim \dim_set_eq:NN \l__joinbox_min_height_dim \l_tmpb_dim } \bool_if:nT { !(\l__joinbox_only_first_bool) && !(\l__joinbox_only_second_bool) } { % 比较并记录最å°å®½åº¦ \dim_set:Nn \l__joinbox_min_width_dim { \dim_min:nn { \l__joinbox_min_width_dim }{ \l_tmpa_dim } } % æ¯”è¾ƒå¹¶è®°å½•æœ€å°æ€»é«˜åº¦(高度+深度) \dim_set:Nn \l__joinbox_min_height_dim { \dim_min:nn { \l__joinbox_min_height_dim }{ \l_tmpb_dim } } } } % 输出盒å \cs_new:Npn \__joinbox_typeout_coffin:N #1 { % 输出拼接åŽçš„ç›’å \int_case:nn { \l__joinbox_baseline_int } { { 1 }{% \coffin_typeset:Nnnnn #1 { l } { t } { 0pt } { 0pt } } { 2 }{% \coffin_typeset:Nnnnn #1 { l } { vc } { 0pt } { 0pt } } { 3 }{% \coffin_typeset:Nnnnn #1 { l } { H } { 0pt } { 0pt } } { 4 }{% \coffin_typeset:Nnnnn #1 { l } { b } { 0pt } { 0pt } } } } % ä¸¤ä¸ªç›’åæ‹¼æŽ¥å†…部函数 % 将指定文件å列表ä¸çš„å›¾åƒæ‹¼æŽ¥æˆä¸€ä¸ªç›’å % #1---第1个盒å的内容 % #2---第2个盒å的内容 \cs_new:Npn \__joinbox_handle:nn #1#2 { \group_begin: % 设置第1个盒å \coffin_clear:N \l__joinbox_out_coffin \hcoffin_set:Nn \l__joinbox_out_coffin { #1 } % 设置第2个盒å \coffin_clear:N \l__joinbox_tmpa_coffin \hcoffin_set:Nn \l__joinbox_tmpa_coffin { #2 } \bool_if:NTF \l__joinbox_vertical_bool { % 按最å°å®½åº¦ç¼©æ”¾ç¬¬1个盒å \bool_if:NF \l__joinbox_only_second_bool { \coffin_scale:Nnn \l__joinbox_out_coffin { \dim_ratio:nn { \l__joinbox_min_width_dim } { \coffin_wd:N \l__joinbox_out_coffin } } { \dim_ratio:nn { \l__joinbox_min_width_dim } { \coffin_wd:N \l__joinbox_out_coffin } } } % 按最å°å®½åº¦ç¼©æ”¾ç¬¬2个盒å \bool_if:NF \l__joinbox_only_first_bool { \coffin_scale:Nnn \l__joinbox_tmpa_coffin { \dim_ratio:nn { \l__joinbox_min_width_dim } { \coffin_wd:N \l__joinbox_tmpa_coffin } } { \dim_ratio:nn { \l__joinbox_min_width_dim } { \coffin_wd:N \l__joinbox_tmpa_coffin } } } \bool_if:NT \l__joinbox_only_second_bool { \coffin_set_eq:NN \l__joinbox_out_coffin \l__joinbox_tmpa_coffin } \bool_if:nT { !(\l__joinbox_only_first_bool) && !(\l__joinbox_only_second_bool) } { % 将第2ä¸ªç›’åæ‹¼æŽ¥åˆ°ç¬¬ä¸€ä¸ªç›’å \coffin_join:NnnNnnnn \l__joinbox_out_coffin { hc } { b } \l__joinbox_tmpa_coffin { hc } { t } { 0pt } { -\l__joinbox_sep_dim } } % 按指定输出宽度缩放输出盒å \bool_if:NT \l__joinbox_out_scale_bool { \coffin_scale:Nnn \l__joinbox_out_coffin { \dim_ratio:nn { \l__joinbox_out_length_dim } { \l__joinbox_min_width_dim } } { \dim_ratio:nn { \l__joinbox_out_length_dim } { \l__joinbox_min_width_dim } } } \hcoffin_set:Nn \l__joinbox_out_coffin { \coffin_typeset:Nnnnn \l__joinbox_out_coffin { l }{ b }{ 0pt }{ 0pt } }\hcoffin_set_end: % 输出拼接åŽçš„ç›’å \__joinbox_typeout_coffin:N \l__joinbox_out_coffin }{ % 按最å°é«˜åº¦ç¼©æ”¾ç¬¬1个盒å \coffin_scale:Nnn \l__joinbox_out_coffin { \dim_ratio:nn { \l__joinbox_min_height_dim } { \__joinbox_coffin_ht_plus_dp:N \l__joinbox_out_coffin } } { \dim_ratio:nn { \l__joinbox_min_height_dim } { \__joinbox_coffin_ht_plus_dp:N \l__joinbox_out_coffin } } % 处ç†ç¬¬2个盒å \hcoffin_set:Nn \l__joinbox_tmpa_coffin { #2 } % 按最å°é«˜åº¦ç¼©æ”¾ç¬¬2个盒å \coffin_scale:Nnn \l__joinbox_tmpa_coffin { \dim_ratio:nn { \l__joinbox_min_height_dim } { \__joinbox_coffin_ht_plus_dp:N \l__joinbox_tmpa_coffin } } { \dim_ratio:nn { \l__joinbox_min_height_dim } { \__joinbox_coffin_ht_plus_dp:N \l__joinbox_tmpa_coffin } } % 将第2ä¸ªç›’åæ‹¼æŽ¥åˆ°ç¬¬1个盒å \coffin_join:NnnNnnnn \l__joinbox_out_coffin { vc } { r } \l__joinbox_tmpa_coffin { vc } { l } { \l__joinbox_sep_dim } { 0pt } % 按指定输出高度缩放输出盒å \bool_if:NT \l__joinbox_out_scale_bool { \coffin_scale:Nnn \l__joinbox_out_coffin { \dim_ratio:nn { \l__joinbox_out_length_dim } { \l__joinbox_min_height_dim } } { \dim_ratio:nn { \l__joinbox_out_length_dim } { \l__joinbox_min_height_dim } } } \hcoffin_set:Nn \l__joinbox_out_coffin { \coffin_typeset:Nnnnn \l__joinbox_out_coffin { l }{ b }{ 0pt }{ 0pt } }\hcoffin_set_end: % 输出拼接åŽçš„ç›’å \__joinbox_typeout_coffin:N \l__joinbox_out_coffin } \group_end: } % å¤šä¸ªç›’åæ‹¼æŽ¥å†…部函数 \cs_new:Npn \__joinbox_boxes: { % 设置第1个盒å内容 \clist_pop:NN \l__joinbox_contents_clist \l_tmpa_tl \hcoffin_set:Nn \l__joinbox_tmpa_coffin { \l_tmpa_tl } % 循环处ç†å…¶å®ƒç›’å内容 \clist_map_inline:Nn \l__joinbox_contents_clist { \hcoffin_set:Nn \l__joinbox_tmpb_coffin { ##1 } \__joinbox_calc_min_size:nn { \__joinbox_typeout_coffin:N \l__joinbox_tmpa_coffin } { \__joinbox_typeout_coffin:N \l__joinbox_tmpb_coffin } \hcoffin_set:Nn \l__joinbox_tmpa_coffin { \__joinbox_handle:nn { \coffin_typeset:Nnnnn \l__joinbox_tmpa_coffin { l } { b } { 0pt } { 0pt } } { \coffin_typeset:Nnnnn \l__joinbox_tmpb_coffin { l } { b } { 0pt } { 0pt } } } } % 按指定输出高度缩放输出盒å \bool_if:nT { \l__joinbox_out_scale_bool && \l__joinbox_only_first_bool } { \bool_if:NTF \l__joinbox_vertical_bool { \coffin_scale:Nnn \l__joinbox_tmpa_coffin { \dim_ratio:nn { \l__joinbox_out_length_dim } { \coffin_wd:N \l__joinbox_tmpa_coffin } } { \dim_ratio:nn { \l__joinbox_out_length_dim } { \coffin_wd:N \l__joinbox_tmpa_coffin } } } { \coffin_scale:Nnn \l__joinbox_tmpa_coffin { \dim_ratio:nn { \l__joinbox_out_length_dim } { \__joinbox_coffin_ht_plus_dp:N \l__joinbox_tmpa_coffin } } { \dim_ratio:nn { \l__joinbox_out_length_dim } { \__joinbox_coffin_ht_plus_dp:N \l__joinbox_tmpa_coffin } } } } \__joinbox_typeout_coffin:N \l__joinbox_tmpa_coffin } \cs_new:Npn \__joinbox_figs: { % 设置第1ä¸ªå›¾åƒ \clist_pop:NN \l__joinbox_name_clist \l_tmpa_tl \hcoffin_set:Nn \l__joinbox_tmpa_coffin { \includegraphics{ \l_tmpa_tl } } % 循环处ç†å…¶å®ƒå›¾åƒ \clist_map_inline:Nn \l__joinbox_name_clist { \hcoffin_set:Nn \l__joinbox_tmpb_coffin { \includegraphics{ ##1 } } \__joinbox_calc_min_size:nn { \__joinbox_typeout_coffin:N \l__joinbox_tmpa_coffin } { \__joinbox_typeout_coffin:N \l__joinbox_tmpb_coffin } \hcoffin_set:Nn \l__joinbox_tmpa_coffin { \__joinbox_handle:nn { \coffin_typeset:Nnnnn \l__joinbox_tmpa_coffin { l } { b } { 0pt } { 0pt } } { \coffin_typeset:Nnnnn \l__joinbox_tmpb_coffin { l } { b } { 0pt } { 0pt } } } } % 按指定输出高度缩放输出盒å \bool_if:nT { \l__joinbox_out_scale_bool && \l__joinbox_only_first_bool } { \bool_if:NTF \l__joinbox_vertical_bool { \coffin_scale:Nnn \l__joinbox_tmpa_coffin { \dim_ratio:nn { \l__joinbox_out_length_dim } { \coffin_wd:N \l__joinbox_tmpa_coffin } } { \dim_ratio:nn { \l__joinbox_out_length_dim } { \coffin_wd:N \l__joinbox_tmpa_coffin } } } { \coffin_scale:Nnn \l__joinbox_tmpa_coffin { \dim_ratio:nn { \l__joinbox_out_length_dim } { \__joinbox_coffin_ht_plus_dp:N \l__joinbox_tmpa_coffin } } { \dim_ratio:nn { \l__joinbox_out_length_dim } { \__joinbox_coffin_ht_plus_dp:N \l__joinbox_tmpa_coffin } } } } \__joinbox_typeout_coffin:N \l__joinbox_tmpa_coffin } % ç›’åæ‹¼æŽ¥ç”¨æˆ·æŽ¥å£ % å°†ä¸¤ä¸ªç›’åæŒ‰æŒ‡å®šæ–¹å¼æ‹¼æŽ¥æˆä¸€ä¸ªç›’å并将基线调整为ä¸å¿ƒçº¿åŽè¾“出 % #1---是å¦ä¸º*命令,如有*åˆ™é‡‡ç”¨æ°´å¹³æ‹¼æŽ¥ï¼Œæ— *则采用垂直拼接 % #2---å¯é€‰å‚数,用key-valueé€‰é¡¹æŒ‡å®šæ‹¼æŽ¥å‚æ•° % #3---第1个盒å的内容 % #4---第2个盒å的内容 \NewDocumentCommand{\joinbox}{ s O{} +m +m} { \IfBooleanTF{#1} { \bool_set_false:N \l__joinbox_vertical_bool }{ \bool_set_true:N \l__joinbox_vertical_bool } \group_begin: % è®¾ç½®æ‹¼æŽ¥å‚æ•° \keys_set:nn { joinbox } { #2 } % 判æ–第1个拼接对象是å¦ä¸ºç©º \tl_set:Nn \l_tmpa_tl { #3 } \tl_if_empty:VT \l_tmpa_tl { \bool_set_true:N \l__joinbox_only_second_bool } % 判æ–第2个拼接对象是å¦ä¸ºç©º \tl_set:Nn \l_tmpa_tl { #4 } \tl_if_empty:VT \l_tmpa_tl { \bool_set_true:N \l__joinbox_only_first_bool } % ä¸¤ä¸ªæ‹¼æŽ¥å¯¹è±¡åŒæ—¶ä¸ºç©ºï¼Œæ— 需拼接 \bool_if:nT { \l__joinbox_only_first_bool && \l__joinbox_only_second_bool } { \__joinbox_error:n { empty-objs } } % 计算最å°å®½åº¦å’Œé«˜åº¦ \__joinbox_calc_min_size:nn { #3 } { #4 } % 拼接输出 \__joinbox_handle:nn { #3 }{ #4 } \group_end: } % ä¸¤ä¸ªä»¥ä¸Šç›’åæ‹¼æŽ¥ç”¨æˆ·æŽ¥å£ % 将逗å·åˆ†éš”的内容构æˆçš„å„ä¸ªç›’åæ‹¼æŽ¥æˆä¸€ä¸ªç›’å % #1---是å¦ä¸º*命令,如有*åˆ™é‡‡ç”¨æ°´å¹³æ‹¼æŽ¥ï¼Œæ— *则采用垂直拼接 % #2---å¯é€‰å‚数,用key-valueé€‰é¡¹æŒ‡å®šæ‹¼æŽ¥å‚æ•° % #3---å¿…é€‰å‚æ•°ï¼Œç”¨é€—å·åˆ†éš”çš„ï¼Œéœ€è¦æ‹¼æŽ¥å†…容(å„个内容应该置于大括å·å†…) \NewDocumentCommand{\joinboxes}{ s O{} +m} { \IfBooleanTF{#1} { \bool_set_false:N \l__joinbox_vertical_bool }{ \bool_set_true:N \l__joinbox_vertical_bool } \group_begin: % è®¾ç½®æ‹¼æŽ¥å‚æ•° \keys_set:nn { joinbox } { #2 } % 设置内容列表 \clist_set:Nn \l__joinbox_contents_clist { #3 } % åˆ¤æ–æ˜¯å¦ä¸ºç©º \clist_if_empty:NT \l__joinbox_contents_clist { \__joinbox_error:n { empty-contents } } % åˆ¤æ–æ˜¯å¦åªæœ‰1个图åƒåç§° \int_compare:nNnT { \clist_count:N \l__joinbox_contents_clist } = { 1 } { \bool_set_true:N \l__joinbox_only_first_bool } % 拼接盒å \__joinbox_boxes: \group_end: } % ç©ºå›¾åƒæ–‡ä»¶ååˆ—è¡¨å‡ºé”™ä¿¡æ¯ \msg_new:nnn { joinbox } { empty-contents } { The~contents~list~is~empty. } % è¢«æ‹¼æŽ¥å¯¹è±¡åŒæ—¶ä¸ºç©ºï¼Œæ— 需拼接 \msg_new:nnn { joinbox } { empty-objs } { The~two~objects~which~were~joined~are~empty. } % å›¾åƒæ‹¼æŽ¥ç”¨æˆ·æŽ¥å£ % 将指定文件å列表ä¸çš„å›¾åƒæ‹¼æŽ¥æˆä¸€ä¸ªç›’å % #1---是å¦ä¸º*命令,如有*åˆ™é‡‡ç”¨æ°´å¹³æ‹¼æŽ¥ï¼Œæ— *则采用垂直拼接 % #2---å¯é€‰å‚数,用key-valueé€‰é¡¹æŒ‡å®šæ‹¼æŽ¥å‚æ•° % #3---å¿…é€‰å‚æ•°ï¼Œç”¨é€—å·åˆ†éš”çš„ï¼Œéœ€è¦æ‹¼æŽ¥çš„å›¾åƒæ–‡ä»¶å称(å¯ä»¥å¸¦æœ‰è·¯å¾„) \NewDocumentCommand{\joinfigs}{ s O{} m} { \IfBooleanTF{#1} { \bool_set_false:N \l__joinbox_vertical_bool }{ \bool_set_true:N \l__joinbox_vertical_bool } \group_begin: % è®¾ç½®æ‹¼æŽ¥å‚æ•° \keys_set:nn { joinbox } { #2 } % 设置文件å列表 \clist_set:Nn \l__joinbox_name_clist { #3 } % åˆ¤æ–æ˜¯å¦ä¸ºç©º \clist_if_empty:NT \l__joinbox_name_clist { \__joinbox_error:n { empty-fignames } } % åˆ¤æ–æ˜¯å¦åªæœ‰1个图åƒåç§° \int_compare:nNnT { \clist_count:N \l__joinbox_name_clist } = { 1 } { \bool_set_true:N \l__joinbox_only_first_bool } % æ‹¼æŽ¥å›¾åƒ \__joinbox_figs: \group_end: } % ç©ºå›¾åƒæ–‡ä»¶ååˆ—è¡¨å‡ºé”™ä¿¡æ¯ \msg_new:nnn { joinbox } { empty-fignames } { The~figure~namelist~is~empty. } \endinput