summaryrefslogtreecommitdiff
path: root/macros/latex/contrib/tagpdf/tagpdf-tree.dtx
blob: 1f9c12fb35c8ea5dddf44c3775c8e6b668528187 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
% \iffalse meta-comment
%
%% File: tagpdf-tree.dtx
%
% Copyright (C) 2019-2021 Ulrike Fischer
%
% 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
%
% This file is part of the "tagpdf bundle" (The Work in LPPL)
% and all files in that bundle must be distributed together.
%
% -----------------------------------------------------------------------
%
% The development version of the bundle can be found at
%
%    https://github.com/u-fischer/tagpdf
%
% for those people who are interested.
%
%<*driver>
\RequirePackage{pdfmanagement-testphase}
\DeclareDocumentMetadata{}
\makeatletter
\declare@file@substitution{doc.sty}{doc-v3beta.sty}
\makeatother
\documentclass{l3doc}
\usepackage{array,booktabs,caption}
\hypersetup{pdfauthor=Ulrike Fischer,
 pdftitle=tagpdf-tree module (tagpdf)}
\begin{document}
  \DocInput{\jobname.dtx}
\end{document}
%</driver>
% \fi
% \title{^^A
%   The \pkg{tagpdf-tree} module\\ Commands trees and main dictionaries   ^^A
%   \\ Part of the tagpdf package
% }
%
% \author{^^A
%  Ulrike Fischer\thanks
%    {^^A
%      E-mail:
%        \href{mailto:fischer@troubleshooting-tex.de}
%          {fischer@troubleshooting-tex.de}^^A
%    }^^A
% }
%
% \date{Version 0.91, released 2021-07-03}
% \maketitle
% \begin{implementation}
%    \begin{macrocode}
%<@@=tag>
%<*header>
\ProvidesExplPackage {tagpdf-tree-code} {2021-07-03} {0.91}
 {part of tagpdf - code related to writing trees and dictionaries to the pdf}
%</header>
%    \end{macrocode}
% \section{Trees, pdfmanagement and finalization code}
%
% The code to finish the structure is in a hook.
% This will perhaps at the end be a kernel hook.
% TODO check right place for the code
% The pdfmanagement code is the kernel hook after
% shipout/lastpage so all code affecting it should be before.
% Objects can be written later, at least in pdf mode.
%    \begin{macrocode}
%<*package>
\hook_gput_code:nnn{begindocument}{tagpdf}
  {
    \bool_if:NT \g_@@_active_tree_bool
      {
        \sys_if_output_pdf:TF
          {
            \AddToHook{enddocument/end} { \@@_finish_structure: }
          }
          {
            \AddToHook{shipout/lastpage} { \@@_finish_structure: }
          }
      }
  }
%    \end{macrocode}
% \subsection{Catalog: MarkInfo and StructTreeRoot}
% The StructTreeRoot and the MarkInfo entry must be added to the catalog.
% We do it late so that we can win, but before the pdfmanagement hook.
% \begin{macro}{@@/struct/0}
% This is the object for the root object, the StructTreeRoot
%    \begin{macrocode}
\pdf_object_new:nn { @@/struct/0 }{ dict }
%    \end{macrocode}
% \end{macro}
%    \begin{macrocode}
\hook_gput_code:nnn{shipout/lastpage}{tagpdf}
  {
    \bool_if:NT \g_@@_active_tree_bool
      {
        \pdfmanagement_add:nnn { Catalog / MarkInfo } { Marked } { true }
        \pdfmanagement_add:nnx
          { Catalog }
          { StructTreeRoot }
          { \pdf_object_ref:n { @@/struct/0 } }
      }
  }
%    \end{macrocode}
%
% \subsection{Writing structure elements}
% The following commands are needed to write out the structure.
% \begin{macro}{\@@_tree_write_structtreeroot:}
% This writes out the root object.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_tree_write_structtreeroot:
  {
     \@@_prop_gput:cnx
       { g_@@_struct_0_prop }
       { ParentTree }
       { \pdf_object_ref:n { @@/tree/parenttree } }
     \@@_prop_gput:cnx
       { g_@@_struct_0_prop }
       { RoleMap }
       { \pdf_object_ref:n { @@/tree/rolemap } }
     \@@_struct_write_obj:n { 0 }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_tree_write_structelements:}
% This writes out the other struct elems, the absolute number is in the counter
%    \begin{macrocode}
\cs_new_protected:Npn \@@_tree_write_structelements:
  {
    \int_step_inline:nnnn {1}{1}{\c@g_@@_struct_abs_int}
      {
        \@@_struct_write_obj:n { ##1 }
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \subsection{ParentTree}
% \begin{macro}{@@/tree/parenttree}
% The object which will hold the parenttree
%    \begin{macrocode}
\pdf_object_new:nn { @@/tree/parenttree }{ dict }
%    \end{macrocode}
% \end{macro}
% The ParentTree maps numbers to objects or (if the number represents a page)
% to arrays of objects. The numbers refer to two dictinct types of entries:
% page streams and real objects like annotations.
% The numbers must be distinct and ordered.
% So we rely on abspage for the pages and put the real objects at the end.
% We use a counter to have a chance to get the correct number
% if code is processed twice.
%
% \begin{macro}{\c@g_@@_parenttree_obj_int}
%  This is a counter for the real objects. It starts at the absolute last page
%  value. It relies on l3ref.
%    \begin{macrocode}
\newcounter  { g_@@_parenttree_obj_int }
\hook_gput_code:nnn{begindocument}{tagpdf}
  {
    \int_gset:Nn
      \c@g_@@_parenttree_obj_int
      { \@@_ref_value_lastpage:nn{abspage}{100}  }
  }
%    \end{macrocode}
% \end{macro}
% We store the number/object references in a tl-var. If more structure is needed
% one could switch to a seq.
%  \begin{variable}{ \g_@@_parenttree_objr_tl }
%    \begin{macrocode}
\tl_new:N \g_@@_parenttree_objr_tl
%    \end{macrocode}
% \end{variable}
%
%  \begin{macro}{ \@@_parenttree_add_objr:nn }
%  This command stores a StructParent number and a objref into the tl var.
%  This is only for objects like annotations, pages are handled elsewhere.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_parenttree_add_objr:nn #1 #2 %#1 StructParent number, #2 objref
  {
    \tl_gput_right:Nx \g_@@_parenttree_objr_tl
      {
        #1 \c_space_tl #2 ^^J
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{variable}{\l_@@_parenttree_content_tl}
% A tl-var which will get the page related parenttree content.
%    \begin{macrocode}
\tl_new:N \l_@@_parenttree_content_tl
%    \end{macrocode}
% \end{variable}
%
% \begin{macro}{\@@_tree_fill_parenttree:}
% This is the main command to assemble the page related entries of the parent tree.
% It wanders through the pages and the mcid numbers and collects all mcid of one page.
%    \begin{macrocode}

\cs_new_protected:Npn \@@_tree_fill_parenttree:
  {
    \int_step_inline:nnnn{1}{1}{\@@_ref_value_lastpage:nn{abspage}{-1}} %not quite clear if labels are needed. See lua code
      { %page ##1
        \prop_clear:N \l_@@_tmpa_prop
        \int_step_inline:nnnn{1}{1}{\@@_ref_value_lastpage:nn{tagmcabs}{-1}}
          {
            %mcid####1
            \int_compare:nT
              {\@@_ref_value:enn{mcid-####1}{tagabspage}{-1}=##1} %mcid is on current page
              {% yes
                \prop_put:Nxx
                  \l_@@_tmpa_prop
                  {\@@_ref_value:enn{mcid-####1}{tagmcid}{-1}}
                  {\prop_item:Nn \g_@@_mc_parenttree_prop {####1}}
              }
          }
        \tl_put_right:Nx\l_@@_parenttree_content_tl
          {
            \int_eval:n {##1-1}\c_space_tl
            [\c_space_tl %]
          }
        \int_step_inline:nnnn
          {0}
          {1}
          { \prop_count:N \l_@@_tmpa_prop -1 }
          {
            \prop_get:NnNTF \l_@@_tmpa_prop {####1} \l_@@_tmpa_tl
              {% page#1:mcid##1:\l_@@_tmpa_tl :content
                \tl_put_right:Nx \l_@@_parenttree_content_tl
                  {
                    \pdf_object_if_exist:eT { @@/struct/\l_@@_tmpa_tl }
                     {
                       \pdf_object_ref:e { @@/struct/\l_@@_tmpa_tl }
                     }
                    \c_space_tl
                  }
              }
              {
                \msg_warning:nn { tag } {tree-mcid-index-wrong}
              }
          }
        \tl_put_right:Nn
          \l_@@_parenttree_content_tl
          {%[
            ]^^J
          }
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_tree_lua_fill_parenttree:}
% This is a special variant for luatex.
% lua mode must/can do it differently.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_tree_lua_fill_parenttree:
  {
    \tl_set:Nn \l_@@_parenttree_content_tl
      {
        \lua_now:e
          {
            ltx.@@.func.output_parenttree
              (
                \int_use:N\g_shipout_readonly_int
              )
          }
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_tree_write_parenttree:}
% This combines the two parts and writes out the object.
% TODO should the check for lua be moved into the backend code?
%    \begin{macrocode}
\cs_new_protected:Npn \@@_tree_write_parenttree:
  {
    \bool_if:NTF \g_@@_mode_lua_bool
      {
        \@@_tree_lua_fill_parenttree:
      }
      {
        \@@_tree_fill_parenttree:
      }
    \tl_put_right:NV \l_@@_parenttree_content_tl\g_@@_parenttree_objr_tl
    \pdf_object_write:nx  { @@/tree/parenttree }
      {
        /Nums\c_space_tl [\l_@@_parenttree_content_tl]
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \subsection{Rolemap dictionary}
% The Rolemap dictionary describes relations between new tags and standard types.
% The main part here is handled in the role module, here we only define the
% command which writes it to the PDF.
% \begin{variable}{@@/tree/rolemap}
% At first we reserve again an object.
%    \begin{macrocode}
\pdf_object_new:nn { @@/tree/rolemap }{ dict }
%    \end{macrocode}
% \end{variable}
%
% \begin{macro}{\@@_tree_write_rolemap:}
% This writes out the rolemap, basically it simply pushes out
% the dictionary which has been filled in the role module.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_tree_write_rolemap:
  {
    \pdf_object_write:nx  { @@/tree/rolemap }
      {
        \pdfdict_use:n{g_@@_role/RoleMap_dict}
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \subsection{Classmap dictionary}
% Classmap and attributes are setup in the struct module, here is only the
% code to write it out. It should only done if values have been used.
% \begin{macro}{\@@_tree_write_classmap:}
%    \begin{macrocode}
\cs_new_protected:Npn \@@_tree_write_classmap:
  {
    \tl_clear:N \l_@@_tmpa_tl
    \seq_gremove_duplicates:N \g_@@_attr_class_used_seq
    \seq_set_map:NNn \l_@@_tmpa_seq \g_@@_attr_class_used_seq
      {
        /##1\c_space_tl
        <<
          \prop_item:Nn
            \g_@@_attr_entries_prop
            {##1}
        >>
      }
    \tl_set:Nx \l_@@_tmpa_tl
      {
        \seq_use:Nn
          \l_@@_tmpa_seq
          { \iow_newline: }
      }
    \tl_if_empty:NF
      \l_@@_tmpa_tl
      {
        \pdf_object_new:nn { @@/tree/classmap }{ dict }
        \pdf_object_write:nx
          { @@/tree/classmap }
          { \l_@@_tmpa_tl }
        \@@_prop_gput:cnx
          { g_@@_struct_0_prop }
          { ClassMap }
          { \pdf_object_ref:n { @@/tree/classmap }  }
      }
  }
%    \end{macrocode}
% \end{macro}
% \subsection{Namespaces}
% Namespaces are handle in the role module, here is the code to write them out.
% Namespaces are only relevant for pdf2.0 but we don't care, it doesn't harm.
% \begin{variable}{@@/tree/namespaces}
%    \begin{macrocode}
\pdf_object_new:nn{ @@/tree/namespaces }{array}
%    \end{macrocode}
% \end{variable}
% \begin{macro}{\@@_tree_write_namespaces:}
%    \begin{macrocode}
\cs_new_protected:Npn \@@_tree_write_namespaces:
  {
    \prop_map_inline:Nn \g_@@_role_NS_prop
      {
        \pdfdict_if_empty:nF {g_@@_role/RoleMapNS_##1_dict}
          {
            \pdf_object_write:nx {@@/RoleMapNS/##1}
              {
                \pdfdict_use:n {g_@@_role/RoleMapNS_##1_dict}
              }
            \pdfdict_gput:nnx{g_@@_role/Namespace_##1_dict}
              {RoleMapNS}{\pdf_object_ref:n {@@/RoleMapNS/##1}}
          }
        \pdf_object_write:nx{tag/NS/##1}
          {
             \pdfdict_use:n {g_@@_role/Namespace_##1_dict}
          }
      }
    \pdf_object_write:nx {@@/tree/namespaces}
      {
        \prop_map_tokens:Nn \g_@@_role_NS_prop{\use_ii:nn}
      }
  }
%    \end{macrocode}
% \end{macro}
% \subsection{Finishing the structure}
% This assembles the various parts.
% TODO (when tabular are done or if someone requests it): IDTree
%  \begin{macro}{ \@@_finish_structure: }
%    \begin{macrocode}
\cs_new_protected:Npn \@@_finish_structure:
  {
    \bool_if:NT\g_@@_active_tree_bool
      {
        \hook_use:n {tagpdf/finish/before}
        \@@_tree_write_parenttree:
        \@@_tree_write_rolemap:
        \@@_tree_write_classmap:
        \@@_tree_write_namespaces:
        \@@_tree_write_structelements: %this is rather slow!!
        \@@_tree_write_structtreeroot:
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \subsection{StructParents entry for Page}
% We need to add to the Page resources the |StructParents| entry, this is simply the
% absolute page number.
%    \begin{macrocode}
\hook_gput_code:nnn{begindocument}{tagpdf}
  {
    \bool_if:NT\g_@@_active_tree_bool
      {
       \hook_gput_code:nnn{shipout/before} { tagpdf/structparents }
         {
           \pdfmanagement_add:nnx
             { Page }
             { StructParents }
             { \int_eval:n { \g_shipout_readonly_int} }
         }
      }
  }
%</package>
%    \end{macrocode}
% \end{implementation}
% \PrintIndex