%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% 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{<name>}[<width>][<position>]{...}
%    \xsavebox*{<name>}[<width>][<position>]{...}
%
%    \xsbox{<name>}{...}
%
%    \begin{xlrbox}{<name>}...\end{xlrbox}
%    \begin{xlrbox*}{<name>}...\end{xlrbox*}
%
%  content insertion:
%
%    \xusebox{<name>}
%    \the<name> %short form of \xusebox{<name>} for <name> 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, 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/macros/latex/base/lppl.txt
% 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{l3keys2e}

\def\g@xsb@version@tl{0.16}
\def\g@xsb@date@tl{2020/03/04}
\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!
}

%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.
}

%package options
\bool_new:N\g_xsb_dvisvgm_bool %`dvisvgm' driver option
\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}
  },

  xetex .code:n = {}, %dummy, we use engine test instead

  dvipdfmx .choice:,
  dvipdfmx / true .code:n = {
    \bool_gset_false:N\g_xsb_dvisvgm_bool
    \PassOptionsToPackage{dvipdfmx}{pdfbase}
  },
  dvipdfmx / false .code:n = {},
  dvipdfmx .default:n = {true},

  dvisvgm .choice:,
  dvisvgm / true .code:n = {
    \bool_gset_true:N\g_xsb_dvisvgm_bool
    \PassOptionsToPackage{dvisvgm}{pdfbase}
  },
  dvisvgm / false .code:n = {\bool_gset_false:N\g_xsb_dvisvgm_bool},
  dvisvgm .default:n = {true},

  unknown .code:n = {
    \msg_error:nnx{xsavebox}{unknown~package~option}{\l_keys_key_tl}
  }
}
\ProcessKeysOptions{xsavebox}

\sys_if_output_pdf:T{\bool_gset_false:N\g_xsb_dvisvgm_bool}

\RequirePackage{pdfbase}
\@ifpackagelater{pdfbase}{2017/03/23}{}{
  \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_xsb_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:
  \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{\l_xsb_content_grouped_tl} %raw content
    \hbox_set:Nn\l_xsb_box{ %content re-aligned
      \makebox[#3][#4]{\l_xsb_content_grouped_tl}
    }
  }{
    %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: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]{#5}
          }
        }
      }{
        %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
    }{ % raw content
      \IfBooleanTF{#1}{
        \hbox_set:Nn\l_xsb_raw_box{#5}
      }{
        \sbox\l_xsb_raw_box{#5}
      }
      \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]{#5}
      }
    }{
      \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_check_box_name:n{#1}
  \xsb_xlrbox:
}{
  \xsb_endxlrbox:n{#1}
}

\DeclareDocumentEnvironment{xlrbox*}{m}{
  \xsb_check_box_name:n{#1}
  \xsb_xlrbox_star:
}{
  \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\group_begin:
  \ignorespaces
}

\cs_new_protected_nopar:Nn\xsb_endxlrbox:n{
  \unskip
  \group_end:\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:Nn\xsb_xlrbox_star:{
  \group_begin:
  \xsb_push_props: %new, empty properties dict
  \xsb_beginLTR:
  \hbox_set:Nw\l_xsb_box
  \ignorespaces
}

\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 << <prop dict> >> 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}
