%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % 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. % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % This work may be distributed and/or modified under the % conditions of the LaTeX Project Public License, either version 1.3 % of this license or (at your option) any later version. % The latest version of this license is in % http://mirrors.ctan.org/help/Catalogue/licenses.lppl.html % and version 1.3 or later is part of all distributions of LaTeX % version 2005/12/01 or later. % % This work has the LPPL maintenance status `maintained'. % % The Current Maintainer of this work is A. Grahn. \RequirePackage{xparse} \RequirePackage{pdfbase} %everything with `pbs_' prefix \RequirePackage{l3keys2e} \def\g@xsb@version@tl{0.5} \def\g@xsb@date@tl{2017/03/16} \ProvidesExplPackage{xsavebox}{\g@xsb@date@tl}{\g@xsb@version@tl} {saveboxes for repeating content without code replication} \msg_set:nnnn{xsavebox}{support~outdated}{ Support~package~`#1'~too~old. }{ Please~install~an~up~to~date~version~of~`#1'.\\ Loading~xsavebox~will~abort! } \@ifpackagelater{pdfbase}{2016/03/07}{}{ \msg_error:nnn{xsavebox}{support~outdated}{pdfbase.sty} \tex_endinput:D } %package options %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. } \tl_gset:Nn\g_xsb_margin_tl{3pt} \keys_define:nn{xsavebox}{ 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} } } \ProcessKeysOptions{xsavebox} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % content insertion (referencing, actually) %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \DeclareDocumentCommand\xusebox{m}{\tl_use:c{the#1}} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % saving content %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \DeclareDocumentCommand\xsavebox{smO{\width}O{c}+m}{ \group_begin: \tl_set:Nn\l_xsb_content_grouped_tl{ \group_begin: \cs_set:Nn\ocgxii_beginocg:nnnn{}\cs_set:Nn\ocgxii_endocg:{} #5 \group_end: } \xsb_push_props: %new, empty properties dict \xsb_beginLTR: \IfBooleanTF{#1}{ %later colour injection possible (pdfTeX, LuaTeX) \hbox_set:Nn\l_xsb_raw_box{ %raw content \color@begingroup\l_xsb_content_grouped_tl\color@endgroup } \hbox_set:Nn\l_xsb_box{ %content re-aligned \makebox[#3][#4]{ \color@begingroup\l_xsb_content_grouped_tl\color@endgroup } } }{ %the LaTeX way (no colour injection) \sbox\l_xsb_raw_box{\l_xsb_content_grouped_tl} %raw content \savebox\l_xsb_box[#3][#4]{\l_xsb_content_grouped_tl} %re-aligned } %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_x:nnTF{#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]{\color@begingroup#5\color@endgroup} } } }{ %no colour injection \savebox\l_xsb_box[\box_wd:N\l_xsb_raw_box][l]{ \makebox[#3][s]{#5} } } \xsb_process_box:nnnN{#2}{#3}{#4}\l_xsb_box }{ \IfBooleanTF{#1}{ \hbox_set:Nn\l_xsb_raw_box{ %raw content \color@begingroup#5\color@endgroup } }{ \sbox\l_xsb_raw_box{#5} %raw content } \xsb_process_box:nnnN{#2}{#3}{#4}\l_xsb_raw_box } }{ \IfBooleanTF{#1}{ \hbox_set:Nn\l_xsb_box{ %content re-aligned \makebox[#3][#4]{ \color@begingroup#5\color@endgroup } } }{ \savebox\l_xsb_box[#3][#4]{#5} %re-aligned } \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_xlrbox: }{ \xsb_endxlrbox:n{#1} } \DeclareDocumentEnvironment{xlrbox*}{m}{ \xsb_xlrbox_star: }{ \xsb_endxlrbox_star:n{#1} } %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \cs_new_nopar:Nn\xsb_xlrbox:{ \group_begin: \xsb_push_props: %new, empty properties dict \xsb_beginLTR: \hbox_set:Nn\l_xsb_box\bgroup\bgroup \ignorespaces } \cs_new_nopar:Nn\xsb_endxlrbox:n{ \unskip \egroup\egroup \sbox\l_xsb_box{\hbox_unpack_clear:N\l_xsb_box} \xsb_process_box:nnnN{#1}{\width}{c}\l_xsb_box \xsb_endLTR: \group_end: } \cs_new_nopar:Nn\xsb_xlrbox_star:{ \group_begin: \xsb_push_props: %new, empty properties dict \xsb_beginLTR: \hbox_set:Nn\l_xsb_box\bgroup\color@begingroup \ignorespaces } \cs_new_nopar:Nn\xsb_endxlrbox_star:n{ \unskip \color@endgroup\egroup \xsb_process_box:nnnN{#1}{\width}{c}\l_xsb_box \xsb_endLTR: \group_end: } \cs_new_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_clear:N#4\hss } \box_set_ht:Nn#4{\height+\g_xsb_margin_tl} \box_set_dp:Nn#4{\depth+\g_xsb_margin_tl} %distill box to Form XObject \xsb_pop_props_to:N\l_tmpa_tl \pbs_pdfxform:nnnnn{1}{0}{\l_tmpa_tl}{ \cs_if_exist_use:N\ocgbase_insert_oc:}{#4} %define command for inserting the m-boxed XObject reference \tl_gset:cx{the#1}{ \exp_not:N\xsb_beginLTR: \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} \exp_not:N\pbs_pdfrefxform:n{\pbs_pdflastxform:}\hss } } \exp_not:N\xsb_endLTR: } } \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:Nn\xsb_beginLTR:{ \cs_if_exist:NT\TeXXeTstate{ \int_compare:nT{\TeXXeTstate>\c_zero}{\beginL} } } \cs_new:Nn\xsb_endLTR:{ \cs_if_exist:NT\TeXXeTstate{ \int_compare:nT{\TeXXeTstate>\c_zero}{\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: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: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_x:nnF{\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: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}