summaryrefslogtreecommitdiff
path: root/macros/latex/contrib/xsavebox/xsavebox.sty
diff options
context:
space:
mode:
authorNorbert Preining <norbert@preining.info>2019-09-02 13:46:59 +0900
committerNorbert Preining <norbert@preining.info>2019-09-02 13:46:59 +0900
commite0c6872cf40896c7be36b11dcc744620f10adf1d (patch)
tree60335e10d2f4354b0674ec22d7b53f0f8abee672 /macros/latex/contrib/xsavebox/xsavebox.sty
Initial commit
Diffstat (limited to 'macros/latex/contrib/xsavebox/xsavebox.sty')
-rw-r--r--macros/latex/contrib/xsavebox/xsavebox.sty392
1 files changed, 392 insertions, 0 deletions
diff --git a/macros/latex/contrib/xsavebox/xsavebox.sty b/macros/latex/contrib/xsavebox/xsavebox.sty
new file mode 100644
index 0000000000..164dfab829
--- /dev/null
+++ b/macros/latex/contrib/xsavebox/xsavebox.sty
@@ -0,0 +1,392 @@
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% 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.
+%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+% 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.
+
+%process non-detectable dvipdfmx driver for passing to expl3
+\RequirePackage{xkeyval}
+\DeclareOptionX{dvipdfmx}{%
+ \PassOptionsToPackage{driver=dvipdfmx}{expl3}
+}
+\DeclareOptionX*{}\ProcessOptionsX*\relax %allow anything as an option
+
+\RequirePackage{xparse}
+\RequirePackage{l3keys2e}
+
+\def\g@xsb@version@tl{0.14}
+\def\g@xsb@date@tl{2019/08/13}
+\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@newkey\pbs@newkey
+\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}}
+}{
+ \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\xsb@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\xsb@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}