% \iffalse meta-comment % %% File: latex-lab-bookmark.dtx (C) Copyright 2026 LaTeX Project % % It may be distributed and/or modified under the conditions of the % LaTeX Project Public License (LPPL), either version 1.3c of this % license or (at your option) any later version. The latest version % of this license is in the file % % https://www.latex-project.org/lppl.txt % % % The development version of the bundle can be found below % % https://github.com/latex3/latex2e/required/latex-lab % % for those people who are interested or want to report an issue. % \def\ltlabbookmarkdate{2026-02-18} \def\ltlabbookmarkversion{0.95} %<*driver> \DocumentMetadata{tagging=on,pdfstandard=ua-2,testphase=bookmark} \documentclass[kernel]{l3in2edoc} \EnableCrossrefs \CodelineIndex \begin{document} \DocInput{latex-lab-bookmark.dtx} \end{document} % % % \fi % % % \title{The \textsf{latex-lab-bookmark} package\\ % Creating bookmarks } % \author{\LaTeX{} Project\thanks{Initial implementation done by Ulrike Fischer}} % \date{v\ltlabbookmarkversion\ \ltlabbookmarkdate} % % \maketitle % % \newcommand{\TODO}[1]{\textbf{[TODO:} #1\textbf{]}} % % \providecommand\hook[1]{\texttt{#1}} % \begin{documentation} % \begin{abstract} % The following code implements a first draft for the creation of bookmarks % with kernel methods. % \end{abstract} % % \section{Introduction} % This package implements support for the PDF outline aka bookmarks based on % the l3pdfoutline module in the PDF management. % It is based on the % \pkg{bookmark} and the \pkg{hyperref} package and mostly implements the same user commands, % most importantly the main commands |\bookmark| and |\bookmarksetup|.% % \footnote{There is no clash here, as documents can use only one bookmark system: mixing % commands from more than one package or using manually the primitives is not % really possible without breaking the tree.} But it does not try to be a full replacement % and to replicate every option in the same way as the other packages. % % \subsection{Usage} % If \cs{DocumentMetadata} is used, the package should (currently) be loaded additionally % with the |testphase| key: % \begin{verbatim} % \DocumentMetadata{testphase=bookmark} % \documentclass{...} % \end{verbatim} % This will also load all the other tagging support code. % % If only the PDF management is loaded the code should be loaded as package: % \begin{verbatim} % \RequirePackage{pdfmanagement} % \RequirePackage{latex-lab-testphase-bookmark} % \end{verbatim} % % If loaded it will replace the code used for bookmarks in the % \pkg{hyperref} and \pkg{bookmark} packages. It is possible to % manually create bookmarks with this package even if \pkg{hyperref} or \pkg{bookmark} % are not loaded, but then the targets must be created manually too, e.g. with % |\pdf_destination:nn|. % % The package assumes that the input files are UTF-8 encoded! % % \subsection{User commands} % % \begin{function}{\bookmark} % \begin{syntax} % \cs{bookmark}\oarg{keyval options}\Arg{title text} % \end{syntax} % % This is the main command. As keyval options it accepts the keys described below (which are % more or less the same as the keys from the \pkg{bookmark} package). % At least one action key is required! % \end{function} % % \begin{function}{\bookmarksetup} % \begin{syntax} % \cs{bookmarksetup}\Arg{keyval options} % \end{syntax} % % This can be used to change the behaviour of following % bookmarks. It can also be used in the hook |bookmark|. % \end{function} % % % \begin{function}{\bookmarksetupnext} % \begin{syntax} % \cs{bookmarksetupnext}\Arg{keyval options} % \end{syntax} % This is a small wrapper around |\AddToHookNext{bookmark}{\bookmarksetup{#1}}| % and provided for compatibility with the package \pkg{bookmark}. The hook it uses % is executed after the optional argument of |\bookmark| has been processed and so allows % to change the settings done there. % \end{function} % % \begin{function}[EXP]{\bookmarkget} % \begin{syntax} % \cs{bookmarkget}\Arg{option name} % \end{syntax} % In the package \pkg{bookmark} this command allows to retrieve the current value of options. % It is meant to be used inside the hook (as various options are set in a group it wouldn't give % a really sensible output in other places). In \pkg{bookmark} the argument \meta{option name} % can take lots of values but in practice only \texttt{level} has been used to, e.g., make % chapter bookmarks bold. So currently only support for \texttt{level} has been implemented and % all other values returns an empty result. % \end{function} % % \begin{function}{\pdfbookmark,\subpdfbookmark,\belowpdfbookmark,\currentpdfbookmark} % \begin{syntax} % \cs{pdfbookmark}\oarg{level}\Arg{text}\Arg{name} % \end{syntax} % % These commands are defined if hyperref is loaded and work as described in the % hyperref documentation. They all not only create a bookmark with a GoTo link % but also a destination \Arg{name} for this bookmark. % \end{function} % \subsection{Configuring bookmarks connected to table of contents entries} % Bookmarks can in a document be added by two methods: manually with commands like % \cs{pdfbookmark} or \cs{bookmark} and automatically through heading commands. % For the second method \pkg{hyperref} hooks into the \cs{addcontentsline} % command which means that only headings which can at least potentially appear % in a table of contents are added to the bookmarks --- the standard starred % headings currently not% % \footnote{This will change with the new template implementation of headings. % In this implementation also starred headings can create bookmarks.} % The following keys allow to % configure the bookmarks added with this second method\footnote{As mentioned above % hyperref is currently required for these bookmarks}. % % \begin{description} % \item[bookmarksnumbered, numbered] This allows to decide if the bookmarks show numbers. % The code internally uses a socket |hyp/outline/numberformat|. The predeclared % plugs of these socket redefine \cs{numberline}, \cs{booknumberline}, % \cs{partnumberline} and \cs{chapternumberline} to gobble their argument (plug |false|), % or to print it with a following space (plug |true|). Other plugs can be defined % by users or packages and be assigned with |numbered=|\meta{plugname}. % % \item[bookmarkstype,lists] By default \pkg{hyperref} and \pkg{bookmark} add only entries % that go into the main table of contents (|toc|) to the outline. With these keys % this can be changed. % Differently to \pkg{hyperref}, the keys here accepts a list of extensions, so e.g. with % |lists={toc,lof,lot}| it is possible to add entries from all three lists. % \end{description} % % % \subsection{Hooks} % Code to create bookmarks is typically hidden inside other commands. This makes it difficult % to override settings done in their optional argument. There is therefore a hook |bookmark| % to add additional options. It can, for example, change keys if you add a |\bookmarksetup{...}| % to it. This hook is executed after the keys in the optional argument has been processed % and so allows to overwrite them. % % The package \pkg{bookmark} has actually two hooks: one which is filled when using |\bookmarksetupnext| % and one which is filled by the |addtohook| key. % This is different here: there is only one hook and there is no |addtohook| key. The hook % should be filled with the standard commands, e.g., |\AddToHook|. % % As an example the following code will make all bookmarks of level 0 (in a book chapters) bold: % \begin{verbatim} % \AddToHook{bookmark} % {\ifnum\bookmarkget{level}=0 \bookmarksetup{bold}\fi} % \end{verbatim} % % \subsection{Level keys} % While bookmarks use relative levels, a user normally wants to % set levels relative to document sectioning but implementing a suitable % interface is not trivial as bookmarks can't skip levels. So e.g. if % you use in this order a section, a chapter, a part, a chapter, a section, % then the first section, chapter and the part will all be in the bookmarks % in level 1, while the second chapter will be a child of the part and so in level 2, % and the next section in level 3. This is different to the printed % table of contents where both chapters and both sections look alike % and so are perceived to be on the same respective level. % % It is therefore not possible to map the level of a sectioning command (as seen by % a user and by the \texttt{tocdepth} and \texttt{secnumdepth} counter) to a specific level % in the bookmarks. % % The code used here (and also in the bookmark package) tries to address this problem, % by storing for every bookmark not only the actual \emph{bookmark level} but also % the (intended) \emph{document level}. The \emph{document level} of the % last bookmark is the \emph{current document level}. If a new bookmark then requests to be set % in a specific document level, the code looks up the levels of the previous bookmarks % and chooses the best fit as parent. For example, if there is a chapter % (document level 0, bookmark level 1) % with a subsection as child (document level 2, bookmark level 2) % and the document now wants to insert a section bookmark (document level 1), it will insert % it as child of the chapter and sibling of the subsection. % % % % \begin{description} % % \item[level] The value is the requested \emph{document level}. It can be given % as integer expression (positive or negative) or with keywords like \texttt{section} % if a corresponding |\toclevel@section| command exists that expands to an integer. % % If the level value $n$ is larger then the current document level, the bookmark is % inserted as child. % If the two values are equal, the bookmark is inserted as sibling. % If $n$ is smaller it looks up the parents until it finds a bookmark % with a document level equal to $n$ or smaller to $n$. In the first % case it is inserted as a sibling, in the second as a child of this bookmark. % % In all cases the current document level is updated to $n$ % \item[bookmarksdepth, depth] The value of these keys % decides if a bookmark is shown at all. The value is a \emph{document level} % and accepts the same values as the |level| key. A bookmark is suppressed if % its document level value is larger than the depth value. % % \item[rellevel] This sets the level of a new bookmark in relation to the previous. % It is in such a tree the natural way to set a level and quite easy to use and implement: % a positive value (the actual value doesn't matter) produces a child, zero a sibling, % a negative value goes up by the number of steps given but at most % the number of steps needed to reach the root level. The value will also be used % to update the current document level. % % \item[startatroot] This goes up to the root, the same can be achieved by using |rellevel| % with a large negative number (and this is also how it is implemented). % By default it will set the current document level to zero, the value can be changed % by using |startatroot=$n$|, the setting is suppressed if |keeplevel| has been % set first. % % \end{description} % % \subsection{Expanding commands in the bookmarks} % % Bookmarks can only show simple text. \LaTeX{} commands must there be converted into % something simple. \pkg{hyperref} uses here |\pdfstringdef|. The code here uses % kernel methods, this can lead to different output, e.g., if math is used. % The conversion code can be switched back to |\pdfstringdef| (if hyperref is loaded) % by reassigning a socket plug (the default plug is called |default|): % \begin{verbatim} % \AssignSocketPlug{hyp/outline/title}{pdfstringdef} % \end{verbatim} % Both methods understand the |\texorpdfstring| command. % % \subsection{Actions} % % The following actions are supported by this package (the list is quite % similar to the \pkg{bookmark} package). % % \begin{description} % \item[dest] The value is a destination name. That is the action normally used % in bookmarks. When creating a tagged PDF it will also link to the relevant structure % destination. % \item[page] The value is a (absolute) page number. It creates a destination to % the relevant page. It can be used together with the |gotor| key and will then create % a link to the page in the external PDF. The |view| key can be used to control the view/zoom % of the page. When creating a tagged PDF one should avoid this key for internal links as % the page destination has no associated structure destination. % \item[gotor] The value is the file name of an external PDF (with extension). % \item[named] The value is one of |FirstPage|, |LastPage|, |NextPage|, |PrevPage|. % \item[uri] The value is url. The value is automatically percent encoded. The hash and % percent chars must be properly escaped: % \begin{verbatim} % \bookmark[uri=https://www.example.com\#abc]{uri with hash} % \bookmark[uri=https://www.example.com\%abc]{uri with percent} % \bookmark[uri=https://www.köln.de]{uri with non-ascii char} % \end{verbatim} % % \item[view] The value is one of |Fit|, |FitB|, |FitH|, |FitV|, % |FitBH|, |FitBV| which can be followed by a positive integer (separated by a space) or the % keyword |null|. Or it can be |XYZ|. This can be followed (separated by spaces) by up to two % positive integers and one decimarl or keywords |null| which are then taken as \textit{top left zoom} % in this order. \textit{zoom} is a factor, so e.g. 0.5 will give a scaling of 50\%. % \item[rawaction] This is simply passed into the |/A| of the action. Check the % PDF reference to find out what is possible \ldots. % \end{description} % \end{documentation} % \begin{implementation} % \begin{macrocode} %<@@=hyp> %<*package> % \end{macrocode} % % \begin{macrocode} \ProvidesExplPackage{latex-lab-testphase-bookmark}{2026-04-15}{0.96z} {PDF bookmarks with kernel methods}% % \end{macrocode} % \section{Implementation} % % \subsection{Variables} % % \begin{variable}{\g_@@_outline_currentlevel_int,\g_@@_outline_level_intarray} % While bookmarks use relative levels, user set levels relative to document % sectioning. % \begin{macrocode} \int_new:N \g_@@_outline_currentlevel_int \int_new:N \l_@@_outline_level_int \intarray_new:Nn \g_@@_outline_level_intarray {2000} %TODO size?? \intarray_gset:Nnn \g_@@_outline_level_intarray {1}{0} \int_new:N \l_@@_outline_depth_int \tl_new:N \l_@@_outline_title_tl \tl_new:N \l_@@_outline_action_data_tl \tl_new:N \l_@@_outline_action_dest_data_tl \tl_new:N \l_@@_outline_action_view_tl \bool_new:N \l_@@_outline_keeplevel_bool \seq_new:N \l_@@_outline_type_seq \bitset_new:Nn \l_@@_outline_action_bitset { goto = 1, gotor = 2, named = 3, uri = 4, raw = 5, % \end{macrocode} % this two are destination types that apply to both goto and gotor % \begin{macrocode} namedest = 6, pagedest = 7 } \bitset_set_true:Nn \l_@@_outline_action_bitset { goto } \int_new:N \l_@@_outline_tmpa_int \int_new:N \l_@@_outline_rellevel_tmpa_int \tl_new:N \l_@@_outline_parent_tmpa_tl \seq_new:N \l_@@_outline_tmpa_seq \tl_new:N \l_@@_outline_tmpa_tl \str_new:N \g_@@_outline_tmpa_str % \end{macrocode} % \end{variable} % % \subsection{Converting text} % The title text of the bookmark and other strings must be converted to strings % suitable for a PDF. With hyperref one can use |\pdfstringdef| but % in l3pdftools we also provide a native version which is used by default. % \begin{macrocode} \socket_new:nn {hyp/outline/title}{2} \socket_new_plug:nnn {hyp/outline/title}{pdfstringdef} { \pdfstringdef#2{#1} } % \end{macrocode} % % This uses the pdfmanagement version of \cs{pdfstringdef} defined % in l3pdftools. We use that socket by default. % Ensure that a text without parentheses is produced by using % |string-raw|! % \begin{macrocode} \socket_new_plug:nnn {hyp/outline/title}{default} { \pdf_purify:nN {#1}#2 \pdf_string_from_unicode:nVN {utf16/string-raw} #2 #2 } \socket_assign_plug:nn {hyp/outline/title}{default} % \end{macrocode} % % \subsection{Regex to check the view value} % A similar regex is used in the generic driver of hyperref and should % perhaps be shared. % \begin{macrocode} \regex_const:Nn \c_@@_outline_dest_startview_regex { \A\ * (?: (?:XYZ (?:\ +(?:(?:\d+|\d*\.\d+)|null)){3}\ ) | (?:Fit\b|FitB\b) | (?:(?:FitH|FitV|FitBH|FitBV)(?:\ +(?:\d+|\d*\.\d+)|\ +null){1}) | (?:FitR (?:\ +\d+|\ +\d*\.\d+){4}\ ) ) } \msg_new:nnn { hyp /outline } { invalid-view-value } { Invalid~value~'#1'~of~'#2' \\ is~replaced~by~'Fit'~\msg_line_context:. } % \end{macrocode} % % \subsection{Messages} % \begin{macrocode} \msg_new:nnn {hyp/outline}{no-action} { bookmark~action~missing!\\ Use~one~of~'dest',~'gotor',~'named',~'uri',~or~'rawaction' } % \end{macrocode} % % \subsection{Formatting the bookmark} % % \begin{macrocode} \socket_new:nn {hyp/outline/numberformat}{0} \socket_new_plug:nnn {hyp/outline/numberformat}{false} { \text_declare_purify_equivalent:Nn\numberline\use_none:n \text_declare_purify_equivalent:Nn\booknumberline\use_none:n \text_declare_purify_equivalent:Nn\partnumberline\use_none:n \text_declare_purify_equivalent:Nn\chapternumberline\use_none:n \cs_set_eq:NN\numberline\use_none:n \cs_set_eq:NN\booknumberline\use_none:n \cs_set_eq:NN\partnumberline\use_none:n \cs_set_eq:NN\chapternumberline\use_none:n } \cs_new:Npn \@@_outline_use_numberline:n #1 {#1\c_space_tl} \socket_new_plug:nnn {hyp/outline/numberformat}{true} { \text_declare_purify_equivalent:Nn\numberline \@@_outline_use_numberline:n \text_declare_purify_equivalent:Nn\booknumberline \@@_outline_use_numberline:n \text_declare_purify_equivalent:Nn\partnumberline \@@_outline_use_numberline:n \text_declare_purify_equivalent:Nn\chapternumberline\@@_outline_use_numberline:n \cs_set_eq:NN \numberline \@@_outline_use_numberline:n \cs_set_eq:NN\booknumberline \@@_outline_use_numberline:n \cs_set_eq:NN\partnumberline \@@_outline_use_numberline:n \cs_set_eq:NN\chapternumberline\@@_outline_use_numberline:n } % \end{macrocode} % % \subsection{Key definitions} % % \begin{macrocode} \keys_define:nn {hyp/outline} { ,bold .choice: ,bold / true .code:n = \bitset_set_true:Nn \l_pdfoutline_F_bitset {Bold} ,bold /false .code:n = \bitset_set_false:Nn\l_pdfoutline_F_bitset {Bold} ,bold .default:n = true ,italic .choice: ,italic / true .code:n = \bitset_set_true:Nn\l_pdfoutline_F_bitset {Italic} ,italic /false .code:n = \bitset_set_false:Nn\l_pdfoutline_F_bitset {Italic} ,italic .default:n = true ,numbered .code:n = { \socket_assign_plug:nn { hyp/outline/numberformat } {#1}} ,numbered .default:n = true ,numbered .initial:n = false ,bookmarksnumbered .meta:n = {numbered} ,color .tl_set:N = \l_pdfoutline_color_tl ,open .bool_set:N = \l_pdfoutline_open_bool % \end{macrocode} % TODO: make this perhaps a bit safer ... % \begin{macrocode} ,depth .code:n = { \cs_if_exist:cTF {toclevel@#1} { \int_set:Nn \l_@@_outline_depth_int { \use:c {toclevel@#1} } } { \int_set:Nn \l_@@_outline_depth_int { #1 } } } ,bookmarksdepth .meta:n = { depth = #1 } ,depth .initial:n = {\int_use:N \c_max_int} % \end{macrocode} % openlevel is relative to the bookmark levels, not some document level! % \begin{macrocode} ,openlevel .int_set:N = \l_pdfoutline_open_int ,bookmarksopenlevel .meta:n = { openlevel = #1 } ,level .code:n = { \cs_if_exist:cTF {toclevel@#1} { \int_set:Nn \l_@@_outline_level_int { \use:c {toclevel@#1} } } { \int_set:Nn \l_@@_outline_level_int { #1 } } } ,rellevel .code:n = { \int_set:Nn \l_@@_outline_level_int { \g_@@_outline_currentlevel_int + #1 } } ,keeplevel .bool_set:N = \l_@@_outline_keeplevel_bool ,startatroot .code:n = { \int_set:Nn \l_@@_outline_level_int { -1000 } \bool_if:NF \l_@@_outline_keeplevel_bool { \int_gset:Nn \g_@@_outline_currentlevel_int {#1} \bool_set_true:NF \l_@@_outline_keeplevel_bool } } ,startatroot .default:n = 0 % \end{macrocode} % dest applies to goto or gotor actions. % \begin{macrocode} ,dest .code:n = { \int_compare:nNnTF { \bitset_item:Nn \l_@@_outline_action_bitset {gotor} } = {1} { \bitset_clear:N \l_@@_outline_action_bitset \bitset_set_true:Nn \l_@@_outline_action_bitset { gotor } } { \bitset_clear:N \l_@@_outline_action_bitset \bitset_set_true:Nn \l_@@_outline_action_bitset { goto } } \bitset_set_true:Nn \l_@@_outline_action_bitset { namedest } \tl_set:Nn\l_@@_outline_action_dest_data_tl {#1} } ,dest .default:n = Doc-Start ,goto .code:n = { \bitset_clear:N \l_@@_outline_action_bitset \bitset_set_true:Nn \l_@@_outline_action_bitset { goto } \bitset_set_true:Nn \l_@@_outline_action_bitset { namedest } \tl_set:Nn\l_@@_outline_action_dest_data_tl {#1} } ,gotor .code:n = { \int_compare:nNnTF { \bitset_item:Nn \l_@@_outline_action_bitset {pagedest} } = {1} { \bitset_clear:N \l_@@_outline_action_bitset \bitset_set_true:Nn \l_@@_outline_action_bitset { pagedest } } { \bitset_clear:N \l_@@_outline_action_bitset \bitset_set_true:Nn \l_@@_outline_action_bitset { namedest } } \bitset_set_true:Nn \l_@@_outline_action_bitset { gotor } \tl_set:Nn\l_@@_outline_action_data_tl {#1} } ,named .choices:nn = {FirstPage, NextPage, PrevPage, LastPage} { \bitset_clear:N \l_@@_outline_action_bitset \bitset_set_true:Nn \l_@@_outline_action_bitset { named } \tl_set:Nn \l_@@_outline_action_data_tl {/#1} } % \end{macrocode} % page like dest applies to goto and gotor actions. % \begin{macrocode} ,page .code:n = { \int_compare:nNnTF { \bitset_item:Nn \l_@@_outline_action_bitset {gotor} } = {1} { \bitset_clear:N \l_@@_outline_action_bitset \bitset_set_true:Nn \l_@@_outline_action_bitset { gotor } } { \bitset_clear:N \l_@@_outline_action_bitset \bitset_set_true:Nn \l_@@_outline_action_bitset { goto } } \bitset_set_true:Nn \l_@@_outline_action_bitset { pagedest } \tl_set:Nn \l_@@_outline_action_dest_data_tl {#1} } ,page .default:n = 1 ,rawaction .code:n = { \bitset_clear:N \l_@@_outline_action_bitset \bitset_set_true:Nn \l_@@_outline_action_bitset { uri } \tl_set:Nn\l_@@_outline_action_data_tl {#1} } ,view .code:n = { \tl_set:Ne \l_@@_outline_tmpa_tl {#1~null~null~null~} \exp_args:NNV \regex_extract_once:NnNTF \c_@@_outline_dest_startview_regex \l_@@_outline_tmpa_tl \l_@@_outline_tmpa_seq { \tl_set:Ne \l_@@_outline_action_view_tl {/\seq_item:Nn \l_@@_outline_tmpa_seq {1}} } { \msg_warning:nnnn {hyp/outline}{invalid-view-value}{#1}{view} \tl_set:Nn \l_@@_outline_action_view_tl {Fit} } } ,uri .code:n = { \bitset_clear:N \l_@@_outline_action_bitset \bitset_set_true:Nn \l_@@_outline_action_bitset { uri } \pdf_purify:nN{#1}\l_@@_outline_action_data_tl \pdf_string_from_unicode:nVN { utf8/URI } \l_@@_outline_action_data_tl \l_@@_outline_action_data_tl } ,lists .code:n = { \seq_set_from_clist:Nn \l_@@_outline_type_seq { #1 } } ,lists .initial:n = toc ,bookmarkstype .meta:n = {lists={#1}} } % \end{macrocode} % \begin{macro}{\bookmarksetup} % \begin{macrocode} \NewDocumentCommand\bookmarksetup{m}{\keys_set:nn{hyp/outline}{#1}} % \end{macrocode} % \end{macro} % % \begin{macrocode} \NewHook{bookmark} % \end{macrocode} % % \begin{macro}{\bookmarksetupnext} % \begin{macrocode} \NewDocumentCommand\bookmarksetupnext{m}{\AddToHookNext{bookmark}{\bookmarksetup{#1}}} % \end{macrocode} % \end{macro} % % \begin{macro}{\bookmarkget} % \begin{macrocode} \cs_new:Npn\bookmarkget #1 {\use:c{@@_bookmarkget_#1:}} \cs_new:Npn\@@_bookmarkget_level:{\int_use:N\l_@@_outline_level_int} % \end{macrocode} % \end{macro} % \begin{macro}{\bookmark} % \begin{macrocode} \NewDocumentCommand\bookmark{O{}m} { \bool_if:NT \l_pdfoutline_active_bool { \group_begin: \keys_set:nn {hyp/outline} { #1 } \UseHook{bookmark} \socket_use:nnn{hyp/outline/title}{#2}\l_@@_outline_title_tl \int_compare:nNnF {\l_@@_outline_level_int} > {\l_@@_outline_depth_int} { \@@_outline_bookmark_aux: } \group_end: } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_outline_bookmark_aux:} % \begin{macrocode} \cs_new_protected:Npn \@@_outline_bookmark_aux: % \end{macrocode} % At first we calculate the rellevel from the requested level. % \begin{macrocode} { \@@_outline_calc_rellevel:NN \l_@@_outline_level_int \l_@@_outline_rellevel_tmpa_int % \end{macrocode} % Update the global current level % \begin{macrocode} \bool_if:NF\l_@@_outline_keeplevel_bool { \int_gset:Nn \g_@@_outline_currentlevel_int { \l_@@_outline_level_int } } \int_compare:nNnTF { \bitset_item:Nn \l_@@_outline_action_bitset {goto} * \bitset_item:Nn \l_@@_outline_action_bitset {namedest} } = {1} { \pdfoutline_goto:nee { \l_@@_outline_rellevel_tmpa_int } %level { \l_@@_outline_action_dest_data_tl } { \l_@@_outline_title_tl } } { \int_case:nnF { \bitset_to_arabic:N \l_@@_outline_action_bitset } { { 65 } % goto (1) + pagedest (65) { \pdfoutline_action:nee { \l_@@_outline_rellevel_tmpa_int } { /S/GoTo~ /D [ \pdf_pageobject_ref:n { \l_@@_outline_action_dest_data_tl }~ \l_@@_outline_action_view_tl ] } { \l_@@_outline_title_tl } } { 34 } % gotor (2) + namedest (32) { \pdfoutline_action:nee { \l_@@_outline_rellevel_tmpa_int } { /S/GoToR~ /F ( \l_@@_outline_action_data_tl ) %Todo check format /D ( \l_@@_outline_action_dest_data_tl ) %Todo check format } { \l_@@_outline_title_tl } } { 66 } % gotor (2) + pagedest (64) { \pdfoutline_action:nee { \l_@@_outline_rellevel_tmpa_int } { /S/GoToR~ /F ( \l_@@_outline_action_data_tl ) %Todo check format /D [ \pdf_pageobject_ref:n { \l_@@_outline_action_dest_data_tl }~ \l_@@_outline_action_view_tl ] } { \l_@@_outline_title_tl } } { 4 } % named (4) { \pdfoutline_action:nee { \l_@@_outline_rellevel_tmpa_int } { /S/Named~ /N \l_@@_outline_action_data_tl %TODO check formt } { \l_@@_outline_title_tl } } { 8 } % uri (8) { \pdfoutline_action:nee { \l_@@_outline_rellevel_tmpa_int } { /S/URI~ /URI \l_@@_outline_action_data_tl %TODO check formt } { \l_@@_outline_title_tl } } { 16 } % raw (16) { \pdfoutline_action:nee { \l_@@_outline_rellevel_tmpa_int } { \l_@@_outline_action_data_tl %TODO check formt } { \l_@@_outline_title_tl } } } { \msg_error:nn {hyp/outline}{no-action} } } \intarray_gset:Nnn \g_@@_outline_level_intarray { \pdfoutline_id_ref_last: } { \g_@@_outline_currentlevel_int } } % % \end{macrocode} % \end{macro} % % % \begin{macro}{\@@_outline_calc_rellevel:NN} % This function computes the relative level. % \begin{macrocode} \cs_new_protected:Npn\@@_outline_calc_rellevel:NN #1 #2 % #1 the requested level, #2 the return value { \int_compare:nNnTF { #1 } < { \g_@@_outline_currentlevel_int } % \end{macrocode} % if the requested level is lower, we must inspect the parents to find the best fit. % starting with the previous bookmark % \begin{macrocode} { \int_set:Nn #2 { 0 } % \end{macrocode} % get the first parent % \begin{macrocode} \tl_set:Ne \l_@@_outline_parent_tmpa_tl { \pdfoutline_parent_ref:n { \pdfoutline_id_ref_last: } } % \end{macrocode} % get the document level of the first parent. % \begin{macrocode} \int_compare:nNnTF { \l_@@_outline_parent_tmpa_tl } > { 0 } { \int_set:Nn \l_@@_outline_tmpa_int { \intarray_item:Nn \g_@@_outline_level_intarray { \l_@@_outline_parent_tmpa_tl } } } { \int_set_eq:NN \l_@@_outline_tmpa_int #1 } % if \l_@@_outline_tmpa_int = #1 % stop, decr rellevel (this sets it as sibling of the parent) % if \l_@@_outline_tmpa_int < #1 % stop (this sets it as child of the parent as rellevel is unchanged) % if \l_@@_outline_tmpa_int > #1 % get next parent and continue. \int_do_while:nNnn { \l_@@_outline_tmpa_int } > { #1 } { \int_case:nn { \int_sign:n { \l_@@_outline_tmpa_int-#1 } } { { 0 } { \int_decr:N #2 } { 1 } { \int_decr:N #2 \tl_set:Ne \l_@@_outline_parent_tmpa_tl { \pdfoutline_parent_ref:n { \l_@@_outline_parent_tmpa_tl } } % \end{macrocode} % We need to check if we reached the root level. % \begin{macrocode} \int_compare:nNnTF { \l_@@_outline_parent_tmpa_tl } > { 1 } { \int_set:Nn \l_@@_outline_tmpa_int { \intarray_item:Nn \g_@@_outline_level_intarray { \l_@@_outline_parent_tmpa_tl } } } { % root \int_set_eq:NN \l_@@_outline_tmpa_int #1 } } } } \int_if_zero:nT {\l_@@_outline_tmpa_int-#1}{\int_decr:N #2} } % \end{macrocode} % requested level >= current level is the easy case: we create either a sibling or % a child: % \begin{macrocode} { \int_set:Nn #2 { #1 - \g_@@_outline_currentlevel_int } } } % \end{macrocode} % \end{macro} % % \subsection{Replacing hyperref commands} % % \begin{macrocode} \disable@package@load{bookmark}{\RequirePackage{hyperref}} \DeclareHookRule{package/hyperref/after}% {latex-lab-testphase-bookmark}{voids}{latex-lab-testphase-sec-template} \DeclareHookRule{package/hyperref/after}% {latex-lab-testphase-bookmark}{voids}{latex-lab-testphase-sec} \DeclareHookRule{package/hyperref/after}% {latex-lab-testphase-bookmark}{voids}{latex-lab-testphase-toc} \AddToHook{package/hyperref/after} { \RemoveFromHook{cmd/addcontentsline/before}[hyp] \AddToHookWithArguments{cmd/addcontentsline/before}[hyp/outline] { \@@_addcontentsline_bookmark:nnn {#1}{#2}{#3} } \providecommand\addcontentslinebookmark{} \providecommand\addcontentslinebookmarkOff{} \RenewCommandCopy\addcontentslinebookmark\@@_addcontentsline_bookmark:nnn \renewcommand\addcontentslinebookmarkOff { \bool_if:NTF \l_pdfoutline_active_bool { \def\addcontentslinebookmarkReset{\bool_set_true:N\l_pdfoutline_active_bool} } { \let\addcontentslinebookmarkReset\relax } \bool_set_false:N\l_pdfoutline_active_bool } \legacy_if:nF{Hy@bookmarks}{\bool_set_false:N\l_pdfoutline_active_bool} \renewcommand*{\pdfbookmark}[3][0]{% \bookmark[level=#1,dest={#3.#1}]{#2}% \hyper@anchorstart{#3.#1}\hyper@anchorend} \def\currentpdfbookmark#1#2#3{% \bookmark[rellevel=0,dest={#3.#1}]{#2}% \hyper@anchorstart{#3.#1}\hyper@anchorend} \def\subpdfbookmark#1#2#3{% \bookmark[rellevel=1,dest={#3.#1}]{#2}% \hyper@anchorstart{#3.#1}\hyper@anchorend} \def\belopdfbookmark#1#2#3{% \bookmark[keeplevel,rellevel=1,dest={#3.#1}]{#2}% \hyper@anchorstart{#3.#1}\hyper@anchorend} % \end{macrocode} % This doesn't yet handle the package options. This must be done in hyperref. % \begin{macrocode} \keys_define:nn{hyp} {bookmarksdepth .meta:nn = {hyp/outline}{depth=#1}, bookmarksopen .meta:nn = {hyp/outline}{open}, bookmarksopenlevel .meta:nn = {hyp/outline}{openlevel=#1}, bookmarksnumbered .meta:nn = {hyp/outline}{numbered}} } \DeclareHookRule{package/hyperref/after}{latex-lab-testphase-bookmark}{after}{latex-lab-testphase-toc} % \end{macrocode} % % \begin{macro}{\@@_addcontentsline_bookmark:nnn} % This is the command used at the begin of \cs{addcontentsline} which creates the bookmark. % \begin{macrocode} \cs_new_protected:Npn \@@_addcontentsline_bookmark:nnn #1 #2 #3 %%#1 toc type, #2 level, #3 content { \seq_if_in:NeT \l_@@_outline_type_seq { #1 } { \tl_if_empty:NT\@currentHref { \MakeLinkTarget[page] {} } \tl_if_exist:cF { toclevel@#2 } { \tl_new:c { toclevel@#2 } \tl_gset:cn { toclevel@#2 } { 0 } % message } \group_begin: \socket_use:n{hyp/outline/numberformat} \bookmark[level=#2,dest={\HyperDestNameFilter{\@currentHref}}]{#3} \group_end: } } % \end{macrocode} % \end{macro} % % \begin{macrocode} % % \end{macrocode} % \begin{macrocode} %<*latex-lab> \ProvidesFile{bookmark-latex-lab-testphase.ltx} [\ltlabbookmarkdate\space v\ltlabbookmarkversion\space latex-lab wrapper bookmark] \RequirePackage{latex-lab-testphase-bookmark} % % \end{macrocode} % \end{implementation}