qpdf_ruby 0.1.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,118 @@
1
+ #pragma once
2
+ #define POINTERHOLDER_TRANSITION 1
3
+
4
+ #include <qpdf/QPDF.hh>
5
+
6
+ #include <map>
7
+ #include <optional>
8
+ #include <string>
9
+ #include <vector>
10
+
11
+ // Fixes for the PDF/UA-1 gaps of Chromium's tagged PDF (print-to-PDF with generateTaggedPDF): it
12
+ // writes no /Artifact sections, no LBody, no link descriptions, no XMP identification, and uses
13
+ // PDF 2.0 structure types without a RoleMap. Each step only adds what is missing and leaves
14
+ // everything else in place.
15
+ namespace qpdf_ruby::pdfua {
16
+
17
+ // Painting operators outside any marked content, per kind - what PDF/UA-1 7.1-3 rejects.
18
+ struct UntaggedCounts {
19
+ long paths = 0;
20
+ long texts = 0;
21
+ long xobjects = 0;
22
+ long shadings = 0;
23
+ long inline_images = 0;
24
+ long total() const { return paths + texts + xobjects + shadings + inline_images; }
25
+ };
26
+
27
+ // Wraps every painting operation outside marked content in /Artifact BMC ... EMC: paths (with
28
+ // their construction operators), text shows, XObjects, shadings, inline images. Content inside
29
+ // marked content is never touched, and neither is a Form XObject that carries marked content of
30
+ // its own (wrapping it would nest tagged content in an artifact, 7.1-2). Returns the counts.
31
+ // Covers page content and the Form XObjects it draws - not annotation appearance streams (/AP),
32
+ // tiling-pattern content or Type 3 glyph procedures, which Chromium does not emit today.
33
+ UntaggedCounts mark_untagged_content_as_artifacts(QPDF& pdf);
34
+
35
+ // The same walk without changing anything.
36
+ UntaggedCounts count_untagged_content(QPDF& pdf);
37
+
38
+ // Chromium tags CSS backgrounds and borders as content of the element they belong to - a Table's
39
+ // cell backgrounds, an <article>'s card - and a grouping element (Document, Part, Art, Sect, Div,
40
+ // BlockQuote, TOC, TOCI, Index, the table and list containers; after the RoleMap) must not hold
41
+ // content of its own (PAC: "Content in an inadmissible location", "Invalid use of a TR"). Content
42
+ // held directly by such an element that paints only shapes - no text, XObject or inline image, no
43
+ // nested tagged content - becomes /Artifact BMC ... EMC, and leaves the element's kids and the
44
+ // ParentTree. Content with text stays tagged. Returns how many pieces were turned into artifacts.
45
+ long artifact_tagged_decorations(QPDF& pdf);
46
+
47
+ // Gives each Link annotation without /Contents a description (7.18.1-2, 7.18.5-2): the text given
48
+ // for its URI in `texts`, else its structure element's /Alt or /ActualText, else the URI itself
49
+ // (a mailto: address without the scheme). An internal link uses the text given for "#<name>" of
50
+ // its named destination (a name in the catalog's /Dests, a string in the /Names /Dests name tree),
51
+ // else "Page N". Returns how many were described.
52
+ long describe_links(QPDF& pdf, std::map<std::string, std::string> const& texts);
53
+
54
+ // Moves an LI's content other than Lbl/LBody into a new LBody (7.2-20), structure elements as well
55
+ // as marked-content references, with the ParentTree entries of the moved content updated.
56
+ // Returns how many LBody elements were created.
57
+ long wrap_list_bodies(QPDF& pdf);
58
+
59
+ // Marked content and annotations whose ParentTree entry does not name the structure element that
60
+ // holds them - MCIDs looked up in the stream they are numbered in (a reference's /Stm, else the
61
+ // page), annotations through their /StructParent. Read-only; 0 for a consistent tree.
62
+ long count_parent_tree_mismatches(QPDF& pdf);
63
+
64
+ // Maps PDF 2.0 structure types Chromium emits (Aside, Strong, Em, ...) to PDF 1.7 standard types
65
+ // in the RoleMap, unless the RoleMap already maps them (7.1-5). Returns how many were added.
66
+ long map_nonstandard_roles(QPDF& pdf);
67
+
68
+ // Chromium tags an HTML <figure> as a Figure without alternative text around the image's own
69
+ // Figure (which carries the alt text) and its Caption (7.3-1). Such a grouping Figure - no /Alt or
70
+ // /ActualText of its own, but a Figure below it - becomes a Div; nothing is lost, the caption stays
71
+ // readable. Returns how many were retagged.
72
+ long retag_grouping_figures(QPDF& pdf);
73
+
74
+ // Figures that still have neither /Alt nor /ActualText - only a person can write those (7.3-1).
75
+ long count_figures_without_alt(QPDF& pdf);
76
+
77
+ // Identifies the file as PDF/UA-1 in its XMP metadata (7.1-8) and sets DisplayDocTitle and
78
+ // Marked. An existing metadata stream keeps everything it has: pdfuaid:part and dc:title are each
79
+ // added only if missing (read with namespace scoping, element or attribute form; comments and CDATA
80
+ // do not count); an existing dc:title is never replaced. Without a stream, a new one is written.
81
+ // The title is `title` if given (it also becomes the information dictionary's /Title), else that
82
+ // /Title. Never throws for odd input: without a structure tree only the title and DisplayDocTitle
83
+ // are set (Marked and pdfuaid would claim a tagged file), and an existing packet that is not
84
+ // well-formed XML with an rdf:RDF element is left alone - apply() reports both as
85
+ // unidentified_reason. Returns true if anything changed.
86
+ bool add_pdfua_identification(QPDF& pdf, std::optional<std::string> const& title);
87
+
88
+ // Who made the file: the information dictionary's /Creator and /Producer, and their XMP twins
89
+ // xmp:CreatorTool and pdf:Producer, kept in agreement. A value that is not given, or empty, is left
90
+ // alone. The information dictionary is always set. In XMP a property that already holds exactly
91
+ // that value once stays; every other occurrence of it is removed and the value written in a new
92
+ // rdf:Description - a packet that is not well-formed XML with an rdf:RDF element is left alone and
93
+ // xmp_error says so. Without a packet a new one is written. The values must be valid XML text
94
+ // (xmp::valid_text) - the Ruby binding checks that.
95
+ struct DocumentInfoResult {
96
+ bool changed = false;
97
+ std::optional<std::string> xmp_error; // why the XMP packet was not updated, if it was not
98
+ };
99
+
100
+ DocumentInfoResult set_document_info(QPDF& pdf, std::optional<std::string> const& creator,
101
+ std::optional<std::string> const& producer);
102
+
103
+ struct Report {
104
+ long decorations = 0;
105
+ UntaggedCounts artifacts;
106
+ long links = 0;
107
+ long list_bodies = 0;
108
+ long roles = 0;
109
+ long figure_groups = 0;
110
+ long figures_without_alt = 0;
111
+ bool identified = false;
112
+ std::optional<std::string> unidentified_reason; // why the file is not identified, if it is not
113
+ };
114
+
115
+ // All of the above, in the order that keeps each step's input intact.
116
+ Report apply(QPDF& pdf, std::map<std::string, std::string> const& link_texts, std::optional<std::string> const& title);
117
+
118
+ } // namespace qpdf_ruby::pdfua