%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % ocgbase.sty % % low-level macros for OCG creation, marking optional content and % for managment of global (document-wide) OCG related lists; % % (automatic) OCG configuration in the PDF catalog % % Copyright 2015--\today, Alexander Grahn % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % Support package for ocgx2.sty, media9.sty, animate.sty % % Supported workflows: % % pdflatex, lualatex % latex-->dvips-->ps2pdf or Distiller % latex-->dvipdfmx % xelatex % % for `dvipdfmx', set it as document class option % % % Commands defined: % % \ocgbase_new_ocg:nnn % \ocgbase@new@ocg (LaTeX2e version) % #1: name (as shown in the Layers Tab of the Reader GUI) % #2: usage dict (may be empty), see PDF reference: % http://wwwimages.adobe.com/content/dam/Adobe/en/devnet/pdf/pdfs/ % pdf_reference_1-7.pdf#G9.3858276 % #3: initial visibility (1|0|true|false|on|off|visible|invisible) % % \ocgbase_last_ocg: % \ocgbase@last@ocg (LaTeX2e version) % inserts ID of PDF object created during most recent call of % \ocgbase_new_ocg:nnn % % -------- % % \ocgbase_add_to_order_list:n % \ocgbase@add@to@order@list (LaTeX2e version) % #1: PDF object ID of OCG % Only OCGs added to the global `Order' list using this command will show % up in the Layers Tab % % -------- % % \ocgbase_add_to_off_list:n % \ocgbase@add@to@off@list (LaTeX2e version) % #1: PDF object ID of OCG % macro for setting initial visibility to `off' % % -------- % % \ocgbase_del_from_off_list:n % \ocgbase@del@from@off@list (LaTeX2e version) % #1: PDF object ID of OCG % macro for setting initial visibility to `on' % % -------- % % \ocgbase_add_ocg_to_radiobtn_grp:nnn % \ocgbase@add@ocg@to@radiobtn@grp % add OCG #2 (obj ref) to radio button group `#1' (string), % #3: (0|1|false|true) list OCG as part of group `#1' in the Layers Tab) % % -------- % % \ocgbase_oc_bdc:n % \ocgbase@oc@bdc % #1: OCG obj ref % mark begin of optional content belonging to OCG #1 in the current % content stream % % \ocgbase_oc_emc: % \ocgbase@oc@emc % mark end of optional content in the current content stream % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % 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{expl3} \RequirePackage{pdfbase} \def\g@ocgbase@date@tl{2017/03/30} \def\g@ocgbase@version@tl{0.10} \ProvidesExplPackage{ocgbase}{\g@ocgbase@date@tl}{\g@ocgbase@version@tl} {support package for ocgx2.sty} \msg_set:nnnn{ocgbase}{support~outdated}{ Support~package~`#1'~too~old. }{ Get~an~up~to~date~version~of~`#1'.\\ Aborting. } \@ifpackagelater{pdfbase}{2017/03/23}{}{ \msg_error:nnn{ocgbase}{support~outdated}{pdfbase.sty} \tex_endinput:D } \tl_new:N\g_ocgbase_ocgs_tl %takes ocg object refs \seq_new:N\g_ocgbase_ocgorder_seq \seq_new:N\g_ocgbase_offocgs_seq \pbs_at_end_dvi:n{ \tl_if_empty:NF\g_ocgbase_ocgs_tl{ %global OCG array \pbs_pdfobj:nnn{}{array}{\g_ocgbase_ocgs_tl} \tl_set:Nx\l_ocgbase_ocgarray_tl{\pbs_pdflastobj:} \tl_new:N\l_ocgbase_offocgentry_tl %global OFF list \seq_if_empty:NF\g_ocgbase_offocgs_seq{ \pbs_pdfobj:nnn{}{array}{\seq_use:Nn\g_ocgbase_offocgs_seq{~}} \tl_set:Nx\l_ocgbase_offocgentry_tl{/OFF~\pbs_pdflastobj:} } %global Order list \tl_new:N\l_ocgbase_ocgorderentry_tl \seq_if_empty:NF\g_ocgbase_ocgorder_seq{ %process radio button groups in the Order array \seq_map_inline:Nn\g_ocgbase_ocgorder_seq{ \cs_if_exist:cT{g_ocgbase_#1_rbtn_order_seq}{ \int_compare:nTF{\seq_count:c{g_ocgbase_#1_rbtn_groups_seq}>\c_one}{ \int_compare:nT{\seq_count:c{g_ocgbase_#1_rbtn_order_seq}>\c_zero}{ %remove all occurrences in Order array outside current rbtn group \seq_map_inline:cn{g_ocgbase_#1_rbtn_order_seq}{ \seq_gremove_all:Nn\g_ocgbase_ocgorder_seq{##1} } } }{ %ignore single-item radio button groups \seq_gremove_all:Nn\g_ocgbase_ocgorder_seq{#1} } } } %second sweep on Order array to generate the final Order entry \tl_new:N\l_ocgbase_ocgorder_tl \tl_set:Nn\l_ocgbase_order_item_count_tl{0} \seq_map_inline:Nn\g_ocgbase_ocgorder_seq{ \tl_set:Nx\l_ocgbase_order_item_count_tl{ \int_eval:n{\l_ocgbase_order_item_count_tl+\c_one} } \cs_if_exist:cTF{g_ocgbase_#1_rbtn_order_seq}{ \int_compare:nT{\seq_count:c{g_ocgbase_#1_rbtn_order_seq}>\c_zero}{ \tl_put_right:Nx\l_ocgbase_ocgorder_tl{ ~[(#1)~\seq_use:cn{g_ocgbase_#1_rbtn_order_seq}{~}] } } }{ %ordinary (non-radiobutton group) item (ocg obj ref, `[' or `]'), \tl_set:Nx\l_ocgbase_current_order_item_tl{#1} \tl_set:Nx\l_ocgbase_next_order_item_tl{ \seq_item:Nn\g_ocgbase_ocgorder_seq{ \l_ocgbase_order_item_count_tl+\c_one } } \tl_set:Nx\l_ocgbase_prev_order_item_tl{ \seq_item:Nn\g_ocgbase_ocgorder_seq{ \l_ocgbase_order_item_count_tl-\c_one } } \bool_if:nF{ %avoid empty nesting groups \str_if_eq_p:Vn\l_ocgbase_current_order_item_tl{[} && \str_if_eq_p:Vn\l_ocgbase_next_order_item_tl{]} || \str_if_eq_p:Vn\l_ocgbase_current_order_item_tl{]} && \str_if_eq_p:Vn\l_ocgbase_prev_order_item_tl{[} }{\tl_put_right:Nn\l_ocgbase_ocgorder_tl{~#1}} } } \tl_if_empty:NF\l_ocgbase_ocgorder_tl{ \pbs_pdfobj:nnn{}{array}{\l_ocgbase_ocgorder_tl} \tl_set:Nx\l_ocgbase_ocgorderentry_tl{/Order~\pbs_pdflastobj:} } } %generate RBGroups entry (radio button groups) \tl_new:N\l_ocgbase_rbgroups_tl \seq_map_inline:Nn\g_ocgbase_ocgorder_seq{ \cs_if_exist:cT{g_ocgbase_#1_rbtn_groups_seq}{ \int_compare:nT{\seq_count:c{g_ocgbase_#1_rbtn_groups_seq}>\c_one}{ \tl_put_right:Nx\l_ocgbase_rbgroups_tl{ ~[\seq_use:cn{g_ocgbase_#1_rbtn_groups_seq}{~}] } } } } \tl_new:N\l_ocgbase_rbgroupsentry_tl \tl_if_empty:NF\l_ocgbase_rbgroups_tl{ \pbs_pdfobj:nnn{}{array}{\l_ocgbase_rbgroups_tl} \tl_set:Nx\l_ocgbase_rbgroupsentry_tl{/RBGroups~\pbs_pdflastobj:} } \pbs_pdfcatalog:n{ /OCProperties~<< /OCGs~\l_ocgbase_ocgarray_tl /D~<< /AS~[ <> <> <> ] /BaseState/ON~\l_ocgbase_offocgentry_tl \l_ocgbase_ocgorderentry_tl \l_ocgbase_rbgroupsentry_tl /ListMode/VisiblePages >> >> } } } %macro for inserting new OCG object \cs_new_nopar:Nn\ocgbase_new_ocg:nnn{ \pbs_pdfobj:nnn{}{dict}{ /Type/OCG/Name~(#1)~\str_if_eq_x:nnF{#2}{}{/Usage<<#2>>} } \tl_gput_right:Nx\g_ocgbase_ocgs_tl{~\pbs_pdflastobj:} \bool_if:nT{ \str_if_eq_x_p:nn{#3}{0} || \str_if_eq_x_p:nn{#3}{off} || \str_if_eq_x_p:nn{#3}{false} || \str_if_eq_x_p:nn{#3}{invisible} }{ \ocgbase_add_to_off_list:n{\pbs_pdflastobj:} } \tl_gset:Nx\g_ocgbase_last_ocg_tl{\pbs_pdflastobj:} } \cs_new_nopar:Nn\ocgbase_last_ocg:{\g_ocgbase_last_ocg_tl} %macro for appending OCG object to global `Order' list % in Layers Tab of the GUI \cs_new_nopar:Nn\ocgbase_add_to_order_list:n{ \seq_gput_right:Nx\g_ocgbase_ocgorder_seq{#1} } %macro for appending an OCG object to the global `OFF' list %(initial non-visibility) \cs_new_nopar:Nn\ocgbase_add_to_off_list:n{ \seq_if_in:NxF\g_ocgbase_offocgs_seq{#1}{ \seq_gput_right:Nx\g_ocgbase_offocgs_seq{#1} } } %macro for removing an OCG object from global `OFF' list %(initial non-visibility) \cs_new_nopar:Nn\ocgbase_del_from_off_list:n{ \seq_if_in:NxT\g_ocgbase_offocgs_seq{#1}{ \ocgbase_seq_gremove_all:Nx\g_ocgbase_offocgs_seq{#1} } } \cs_set_eq:NN\ocgbase_seq_gremove_all:Nn\seq_gremove_all:Nn \cs_generate_variant:Nn\ocgbase_seq_gremove_all:Nn{Nx} \cs_new_nopar:Nn\ocgbase_add_ocg_to_radiobtn_grp:nnn{ % #1: rbtn group name, % #2: OCG obj ref, % #3: visible in Layers Tab \seq_if_in:NxF\g_ocgbase_ocgorder_seq{#1}{ \seq_gput_right:Nx\g_ocgbase_ocgorder_seq{#1} \seq_new:c{g_ocgbase_#1_rbtn_order_seq} \seq_new:c{g_ocgbase_#1_rbtn_groups_seq} } \seq_if_in:cxTF{g_ocgbase_#1_rbtn_order_seq}{#2}{ \bool_if:nT{ \str_if_eq_x_p:nn{#3}{0} || \str_if_eq_x_p:nn{#3}{false} }{ \ocgbase_seq_gremove_all:cx{g_ocgbase_#1_rbtn_order_seq}{#2} } }{ \bool_if:nF{ \str_if_eq_x_p:nn{#3}{0} || \str_if_eq_x_p:nn{#3}{false} }{ \seq_gput_right:cx{g_ocgbase_#1_rbtn_order_seq}{#2} } } \seq_gput_right:cx{g_ocgbase_#1_rbtn_groups_seq}{#2} } % OC-marked content \cs_new_nopar:Nn\ocgbase_oc_bdc:n{\pbs_pdfbdc:nn{/OC}{#1}} \cs_new_nopar:Nn\ocgbase_oc_emc:{\pbs_pdfemc:} %stack of PDF obj references of currently open OCGs \seq_new:N\g_ocgbase_ocgstack_seq %push OCG to stack \cs_new_nopar:Nn\ocgbase_ocgstack_push:n{ \seq_gpush:Nx\g_ocgbase_ocgstack_seq{#1}} %pop OCG from stack into tl \cs_new_nopar:Nn\ocgbase_ocgstack_pop:N{\seq_gpop:NN\g_ocgbase_ocgstack_seq#1} %get top OCG on stack, without removing it \cs_new_nopar:Nn\ocgbase_ocgstack_top:{ \seq_item:Nn\g_ocgbase_ocgstack_seq{\c_one}} %command that inserts /OC <> entry; %for use within annotation/xobject dicts \cs_new_nopar:Nn\ocgbase_insert_oc:{ \seq_if_empty:NF\g_ocgbase_ocgstack_seq{ /OC~<> } } %l2e versions \cs_gset_eq:NN\ocgbase@new@ocg\ocgbase_new_ocg:nnn \cs_gset_eq:NN\ocgbase@last@ocg\ocgbase_last_ocg: \cs_gset_eq:NN\ocgbase@add@to@order@list\ocgbase_add_to_order_list:n \cs_gset_eq:NN\ocgbase@add@to@off@list\ocgbase_add_to_off_list:n \cs_gset_eq:NN\ocgbase@del@from@off@list\ocgbase_del_from_off_list:n \cs_gset_eq:NN\ocgbase@add@ocg@to@radiobtn@grp\ocgbase_add_ocg_to_radiobtn_grp:nnn \cs_gset_eq:NN\ocgbase@oc@bdc\ocgbase_oc_bdc:n \cs_gset_eq:NN\ocgbase@oc@emc\ocgbase_oc_emc: \cs_gset_eq:NN\ocgbase@insert@oc\ocgbase_insert_oc: \cs_gset_eq:NN\ocgbase@ocgstack@top\ocgbase_ocgstack_top: \cs_gset_eq:NN\ocgbase@ocgstack@pop\ocgbase_ocgstack_pop:N \cs_gset_eq:NN\ocgbase@ocgstack@push\ocgbase_ocgstack_push:n