%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % xsavebox.sty % % Copyright 2016--\today, Alexander Grahn % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % Provides commands for saving and repeating any content (typeset text, external % and inline [TikZ/PGF, PSTricks] graphics) similar to standard LaTeX's % \savebox, \sbox, \usebox commands and the `lrbox' environment, but without % code replication for smaller PDF output size % % content saving: % % (starred `*' variants allow for colour injection [pdflatex/lualatex only]) % % \xsavebox{}[][]{...} % \xsavebox*{}[][]{...} % % \xsbox{}{...} % % \begin{xlrbox}{}...\end{xlrbox} % \begin{xlrbox*}{}...\end{xlrbox*} % % content insertion: % % \xusebox{} % \the %short form of \xusebox{} for composed of [a-zA-Z] % % Supports all known engines and backends including pdflatex, % latex+dvips+ps2pdf, xelatex, latex+dvipdfmx, lualatex, latex+dvisvgm. % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % This work may be distributed and/or modified under the % conditions of the LaTeX Project Public License % % http://mirrors.ctan.org/macros/latex/base/lppl.txt % % This work has the LPPL maintenance status `maintained'. % % The Current Maintainer of this work is A. Grahn. \NeedsTeXFormat{LaTeX2e}[2022-06-01] \def\g@xsb@version@tl{0.18} \def\g@xsb@date@tl{2022/08/04} \ProvidesExplPackage{xsavebox}{\g@xsb@date@tl}{\g@xsb@version@tl} {saveboxes for repeating content without code replication} % ensure that backend code is loaded \cs_if_exist:NF\c_sys_backend_str{\sys_load_backend:n{}} \msg_set:nnnn{xsavebox}{support~outdated}{ Support~package~`#1'~too~old. }{ Please~install~an~up~to~date~version~of~`#1'.\\ Loading~xsavebox~will~abort! } %re-run message \msg_set:nnn{xsavebox}{rerun}{Rerun~to~get~internal~references~right!} \cs_new_protected:Nn\xsb_rerun_msg:{ \cs_if_exist:NF\g_xsb_rerunwarned_tl{ \tl_new:N\g_xsb_rerunwarned_tl \AtEndDocument{\msg_warning:nn{xsavebox}{rerun}} } } %unknown package option error message \msg_set:nnnn{xsavebox}{unknown~package~option}{Unknown~package~option~`#1'.}{ Package option~'#1'~is~unknown;\\ perhaps~it~is~spelled~incorrectly. } % possible values for \c_sys_backend_str: pdftex, luatex, xetex, dvips, dvipdfmx, dvisvgm %package options \tl_gset:Nn\g_xsb_margin_tl{3pt} \keys_define:nn{xsavebox}{ pdftex.code:n = {}, pdftex.value_forbidden:n = true, luatex.code:n = {}, luatex.value_forbidden:n = true, xetex.code:n = {}, xetex.value_forbidden:n = true, dvips.code:n = {}, dvips.value_forbidden:n = true, dvipdfmx.code:n = { \PassOptionsToPackage{dvipdfmx}{pdfbase} }, dvipdfmx.value_forbidden:n = true, dvisvgm.code:n = { \PassOptionsToPackage{dvisvgm}{pdfbase} }, dvisvgm.value_forbidden:n = true, margin .code:n = { \setlength\l_tmpa_dim{#1} \tl_gset:Nx\g_xsb_margin_tl{\dim_use:N\l_tmpa_dim} }, unknown .code:n = { \msg_error:nnx{xsavebox}{unknown~package~option}{\l_keys_key_tl} } } \ProcessKeyOptions[xsavebox] \RequirePackage{pdfbase} \@ifpackagelater{pdfbase}{2022/08/04}{}{ \msg_error:nnn{xsavebox}{support~outdated}{pdfbase.sty} \tex_endinput:D } \cs_gset_eq:NN\xsb_pdfxform:nnnnn\pbs_pdfxform:nnnnn \cs_gset_eq:NN\xsb_pdflastxform:\pbs_pdflastxform: \cs_gset_eq:NN\xsb_pdfrefxform:n\pbs_pdfrefxform:n \bool_if:NTF\g_pbs_dvisvgm_bool{ \tl_gset:Nn\g_xsb_margin_tl{0pt} \cs_new_protected_nopar:Nn\xsb_updatebbox:nnn{ \special{dvisvgm:bbox~#1~#2~#3~transform} } }{ \cs_new_protected_nopar:Nn\xsb_updatebbox:nnn{} } \int_new:N\g_xsb_id_int %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % content insertion (referencing, actually) %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \DeclareDocumentCommand\xusebox{m}{ \tl_if_exist:cF{xsb_name_#1}{\msg_error:nnn{xsavebox}{save-box~undefined}{#1}} \tl_use:c{the#1} } \msg_set:nnnn{xsavebox}{save-box~undefined}{ Save-box~`#1'~undefined~\msg_line_context:. }{ Save-box~`#1'~must~be~defined~/before/~use. } %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % saving content %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \DeclareDocumentCommand\xsavebox{smO{\width}O{c}+m}{ \xsb_check_box_name:n{#2} \group_begin: \xsb_push_props: %new, empty properties dict \xsb_beginLTR: \hbox_set:Nn\l_xsb_raw_box{ %raw content \bool_lazy_and:nnF{ \tl_if_exist_p:c{xsb@\int_use:N\g_xsb_id_int} }{ \bool_if_p:c{c_\tl_use:c{xsb@\int_use:N\g_xsb_id_int}_bool} }{ %prevent unused boxes from creating OCGs/OCMDs \DeclareDocumentEnvironment{ocg}{O{}mmm}{\ignorespaces}{\unskip} \DeclareDocumentEnvironment{ocmd}{O{}m}{\ignorespaces}{\unskip} } #5 } \hbox_set:Nn\l_xsb_box{ %content re-aligned \makebox[#3][#4]{\hbox_unpack:N\l_xsb_raw_box} } %process one of \l_xsb_raw_box or \l_xsb_box \dim_compare:nTF{\box_wd:N\l_xsb_raw_box>\box_wd:N\l_xsb_box}{ \str_if_eq:eeTF{#4}{s}{ %sqeezing content correctly \IfBooleanTF{#1}{ %for colour injection \hbox_set:Nn\l_xsb_box{ \makebox[\box_wd:N\l_xsb_raw_box][l]{ \makebox[#3][s]{\hbox_unpack:N\l_xsb_raw_box} } } }{ %no colour injection \savebox\l_xsb_box[\box_wd:N\l_xsb_raw_box][l]{ \makebox[#3][s]{\hbox_unpack:N\l_xsb_raw_box} } } \xsb_process_box:nnnN{#2}{#3}{#4}\l_xsb_box }{ % raw content \IfBooleanF{#1}{ \sbox\l_xsb_raw_box{\hbox_unpack:N\l_xsb_raw_box} } \xsb_process_box:nnnN{#2}{#3}{#4}\l_xsb_raw_box } }{ \IfBooleanF{#1}{ \savebox\l_xsb_box[#3][#4]{\hbox_unpack:N\l_xsb_raw_box} } \xsb_process_box:nnnN{#2}{\width}{c}\l_xsb_box } \xsb_endLTR: \group_end: } \DeclareDocumentCommand\xsbox{m+m}{\xsavebox{#1}{#2}} \DeclareDocumentEnvironment{xlrbox}{m}{ \xsb_check_box_name:n{#1} \xsb_xlrbox: }{ \xsb_endxlrbox:n{#1} } \DeclareDocumentEnvironment{xlrbox*}{m}{ \xsb_check_box_name:n{#1} \xsb_xlrbox: }{ \xsb_endxlrbox_star:n{#1} } %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \msg_set:nnn{xsavebox}{name~in~use}{ The~name~`#1'~is~already~in~use~\msg_line_context:.\\ Select~a~different~box~name. } \cs_new_protected_nopar:Nn\xsb_check_box_name:n{ \bool_if:nTF{ \cs_if_exist_p:c{the#1} && !\tl_if_exist_p:c{xsb_name_#1} }{ \msg_error:nnx{xsavebox}{name~in~use}{#1} }{ \tl_clear_new:c{xsb_name_#1} } } \cs_new_protected:Nn\xsb_xlrbox:{ \group_begin: \xsb_push_props: %new, empty properties dict \xsb_beginLTR: \hbox_set:Nw\l_xsb_box \bool_lazy_and:nnF{ %prevent unused boxes from creating OCGs/OCMDs \tl_if_exist_p:c{xsb@\int_use:N\g_xsb_id_int} }{ \bool_if_p:c{c_\tl_use:c{xsb@\int_use:N\g_xsb_id_int}_bool} }{ \DeclareDocumentEnvironment{ocg}{O{}mmm}{\ignorespaces}{\unskip} \DeclareDocumentEnvironment{ocmd}{O{}m}{\ignorespaces}{\unskip} } \ignorespaces } \cs_new_protected_nopar:Nn\xsb_endxlrbox:n{ \unskip \hbox_set_end: \sbox\l_xsb_box{\hbox_unpack_drop:N\l_xsb_box} \xsb_process_box:nnnN{#1}{\width}{c}\l_xsb_box \xsb_endLTR: \group_end: } \cs_new_protected_nopar:Nn\xsb_endxlrbox_star:n{ \unskip \hbox_set_end: \xsb_process_box:nnnN{#1}{\width}{c}\l_xsb_box \xsb_endLTR: \group_end: } \cs_new_protected_nopar:Nn\xsb_process_box:nnnN{ %measure natural dimensions \cs_set_nopar:Npn\width {\box_wd:N#4} \cs_set_nopar:Npn\height{\box_ht:N#4} \cs_set_nopar:Npn\depth {\box_dp:N#4} \cs_set_nopar:Npn\totalheight{\dimexpr\height+\depth\relax} \tl_set:Nx\l_xsb_wd_tl{\dim_use:N\width} \tl_set:Nx\l_xsb_ht_tl{\dim_use:N\height} \tl_set:Nx\l_xsb_dp_tl{\dim_use:N\depth} %evaluate width argument (allowing for calc-type expressions) \setlength\l_tmpa_dim{#2} \tl_set:Nx\l_xsb_new_wd_tl{\dim_use:N\l_tmpa_dim} %temporarily (for distilling) push the box bounds somewhat; glyphs tend to %be bigger than their bounding boxes \hbox_set_to_wd:Nnn#4{\width+\g_xsb_margin_tl+\g_xsb_margin_tl}{ \hss\hbox_unpack_drop:N#4\hss } \box_set_ht:Nn#4{\height+\g_xsb_margin_tl} \box_set_dp:Nn#4{\depth+\g_xsb_margin_tl} \sys_if_output_dvi:TF{ \tl_if_exist:cF{xsb@\int_use:N\g_xsb_id_int}{ \tl_gset:cn{xsb@\int_use:N\g_xsb_id_int}{true} \xsb_rerun_msg: } }{ \tl_gset:cn{xsb@\int_use:N\g_xsb_id_int}{true} } \xsb_pop_props_to:N\l_tmpa_tl %distill box to Form XObject, if used (ref'ed) \bool_if:cT{c_\tl_use:c{xsb@\int_use:N\g_xsb_id_int}_bool}{ \xsb_pdfxform:nnnnn{1}{0}{\l_tmpa_tl}{ \cs_if_exist_use:N\ocgbase_insert_oc:}{#4} } %for tracking box usage \iow_now:Nx\@mainaux{ \token_to_str:N\pbs@newkey{xsb@\int_use:N\g_xsb_id_int}{false} } %define command for inserting the m-boxed XObject reference \cs_gset_protected:cpx{the#1}{ \exp_not:N\tl_if_exist:cF{xsb_\int_use:N\g_xsb_id_int}{ %mark box as `used' \exp_not:N\iow_now:Nx\@mainaux{ \token_to_str:N\pbs@newkey{xsb@\int_use:N\g_xsb_id_int}{true} } \exp_not:N\tl_new:c{xsb_\int_use:N\g_xsb_id_int} } \exp_not:N\bool_if:cF{c_\tl_use:c{xsb@\int_use:N\g_xsb_id_int}_bool}{ \exp_not:N\xsb_rerun_msg: } \exp_not:N\xsb_beginLTR: \xsb_updatebbox:nnn{\l_xsb_new_wd_tl}{\l_xsb_ht_tl}{\l_xsb_dp_tl} \exp_not:N\makebox[\l_xsb_new_wd_tl][#3]{ \exp_not:N\hbox_to_wd:nn{\l_xsb_wd_tl}{ \exp_not:N\vrule~width~\c_zero_dim~height~\l_xsb_ht_tl~ depth~\l_xsb_dp_tl \exp_not:N\skip_horizontal:n{-\g_xsb_margin_tl} \bool_if:cT{c_\tl_use:c{xsb@\int_use:N\g_xsb_id_int}_bool}{ \exp_not:N\xsb_pdfrefxform:n{\xsb_pdflastxform:} } \hss } } \exp_not:N\xsb_endLTR: } \int_gincr:N\g_xsb_id_int } \box_new:N\l_xsb_box %for saving the re-aligned content \box_new:N\l_xsb_raw_box %for saving the raw content %environment for setting LTR typesetting direction with e-TeX based engines \cs_new_protected:Nn\xsb_beginLTR:{ \cs_if_exist:NT\TeXXeTstate{ \int_compare:nT{\TeXXeTstate>\c_zero_int}{\beginL} } } \cs_new_protected:Nn\xsb_endLTR:{ \cs_if_exist:NT\TeXXeTstate{ \int_compare:nT{\TeXXeTstate>\c_zero_int}{\endL} } } %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %for nested xsavebox/xlrbox cmds/envs, maintain a stack of /Properties %dictionaries, mostly used for marked-content property lists, e. g. OCGs; %(see PDF Reference) %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \seq_new:N\g_xsb_props_seq %push new, empty properties dictionary on the stack (to be used by THIS pkg) \cs_new_protected:Nn\xsb_push_props:{\seq_gpush:Nn\g_xsb_props_seq{}} %pop current properties dict from stack and place its contents as a %/Properties << >> entry into the macro argument \cs_new_protected:Nn\xsb_pop_props_to:N{ \seq_gpop:NNT\g_xsb_props_seq\l_tmpa_tl{ \tl_trim_spaces:N\l_tmpa_tl \str_if_eq:eeF{\l_tmpa_tl}{}{\tl_set:Nx#1{/Properties<<\l_tmpa_tl>>}} } } %add property (usually /key/val entry) to current properties dict; %this command is meant to be used by OTHER packages \cs_new_protected:Nn\xsb_addto_props:n{ \seq_gpop:NNT\g_xsb_props_seq\l_tmpa_tl{ \tl_put_right:Nn\l_tmpa_tl{#1~} \seq_gpush:Nx\g_xsb_props_seq{\l_tmpa_tl} } } %get stack size; meant to be used by OTHER packages \cs_new:Nn\xsb_count_props:{\seq_count:N\g_xsb_props_seq}