blitz-pdf 0.1.0 → 0.1.2
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.
- package/README.md +300 -10
- package/THIRD_PARTY_NOTICES.md +4 -2
- package/dist/browser.d.ts +1 -1
- package/dist/browser.d.ts.map +1 -1
- package/dist/document-options.d.ts +36 -0
- package/dist/document-options.d.ts.map +1 -0
- package/dist/document-options.js +236 -0
- package/dist/document-options.js.map +1 -0
- package/dist/node.d.ts +1 -1
- package/dist/node.d.ts.map +1 -1
- package/dist/options.d.ts +35 -1
- package/dist/options.d.ts.map +1 -1
- package/dist/options.js +21 -14
- package/dist/options.js.map +1 -1
- package/dist/types.d.ts +71 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/validation.d.ts +2 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +7 -0
- package/dist/validation.js.map +1 -0
- package/examples/zugferd.mjs +41 -0
- package/licenses/RUST_DEPENDENCIES.txt +27 -0
- package/package.json +3 -2
- package/src/browser.ts +1 -1
- package/src/document-options.ts +218 -0
- package/src/node.ts +1 -1
- package/src/options.ts +18 -13
- package/src/types.ts +76 -1
- package/src/validation.ts +6 -0
- package/vendor/README.md +27 -2
- package/vendor/krilla/.cargo-ok +1 -0
- package/vendor/krilla/.cargo_vcs_info.json +6 -0
- package/vendor/krilla/Cargo.lock +1357 -0
- package/vendor/krilla/Cargo.toml +165 -0
- package/vendor/krilla/LICENSE_APACHE +202 -0
- package/vendor/krilla/LICENSE_MIT +21 -0
- package/vendor/krilla/README.md +156 -0
- package/vendor/krilla/examples/basic.rs +117 -0
- package/vendor/krilla/examples/empty_document.rs +25 -0
- package/vendor/krilla/examples/parley.rs +174 -0
- package/vendor/krilla/examples/simple_text.rs +118 -0
- package/vendor/krilla/examples/stream_builder.rs +93 -0
- package/vendor/krilla/icc/LICENSE +116 -0
- package/vendor/krilla/icc/README.md +1 -0
- package/vendor/krilla/icc/sGrey-v2-magic.icc +0 -0
- package/vendor/krilla/icc/sGrey-v4.icc +0 -0
- package/vendor/krilla/icc/sRGB-v2-magic.icc +0 -0
- package/vendor/krilla/icc/sRGB-v4.icc +0 -0
- package/vendor/krilla/src/chunk_container.rs +503 -0
- package/vendor/krilla/src/configure/PDF_A1.md +227 -0
- package/vendor/krilla/src/configure/PDF_A2.md +269 -0
- package/vendor/krilla/src/configure/PDF_A3.md +31 -0
- package/vendor/krilla/src/configure/PDF_A4.md +211 -0
- package/vendor/krilla/src/configure/PDF_UA1.md +202 -0
- package/vendor/krilla/src/configure/README.md +23 -0
- package/vendor/krilla/src/configure/mod.rs +197 -0
- package/vendor/krilla/src/configure/validate.rs +1244 -0
- package/vendor/krilla/src/configure/version.rs +134 -0
- package/vendor/krilla/src/content.rs +1352 -0
- package/vendor/krilla/src/data.rs +49 -0
- package/vendor/krilla/src/document.rs +156 -0
- package/vendor/krilla/src/error.rs +170 -0
- package/vendor/krilla/src/geom.rs +341 -0
- package/vendor/krilla/src/graphics/blend.rs +46 -0
- package/vendor/krilla/src/graphics/color.rs +552 -0
- package/vendor/krilla/src/graphics/graphic.rs +30 -0
- package/vendor/krilla/src/graphics/graphics_state.rs +251 -0
- package/vendor/krilla/src/graphics/icc.rs +218 -0
- package/vendor/krilla/src/graphics/image.rs +835 -0
- package/vendor/krilla/src/graphics/mask.rs +128 -0
- package/vendor/krilla/src/graphics/mod.rs +14 -0
- package/vendor/krilla/src/graphics/paint.rs +434 -0
- package/vendor/krilla/src/graphics/separation.rs +86 -0
- package/vendor/krilla/src/graphics/shading_function.rs +865 -0
- package/vendor/krilla/src/graphics/shading_pattern.rs +60 -0
- package/vendor/krilla/src/graphics/tiling_pattern.rs +119 -0
- package/vendor/krilla/src/graphics/xobject.rs +150 -0
- package/vendor/krilla/src/interactive/action.rs +68 -0
- package/vendor/krilla/src/interactive/annotation.rs +283 -0
- package/vendor/krilla/src/interactive/destination.rs +289 -0
- package/vendor/krilla/src/interactive/mod.rs +3 -0
- package/vendor/krilla/src/interchange/embed.rs +240 -0
- package/vendor/krilla/src/interchange/metadata.rs +564 -0
- package/vendor/krilla/src/interchange/mod.rs +4 -0
- package/vendor/krilla/src/interchange/outline.rs +263 -0
- package/vendor/krilla/src/interchange/tagging/fmt.rs +733 -0
- package/vendor/krilla/src/interchange/tagging/generate.toml +474 -0
- package/vendor/krilla/src/interchange/tagging/generated.rs +4754 -0
- package/vendor/krilla/src/interchange/tagging/mod.rs +1324 -0
- package/vendor/krilla/src/interchange/tagging/tag.rs +686 -0
- package/vendor/krilla/src/lib.rs +179 -0
- package/vendor/krilla/src/num.rs +26 -0
- package/vendor/krilla/src/page.rs +612 -0
- package/vendor/krilla/src/pdf.rs +313 -0
- package/vendor/krilla/src/resource.rs +436 -0
- package/vendor/krilla/src/serialize.rs +1101 -0
- package/vendor/krilla/src/stream.rs +368 -0
- package/vendor/krilla/src/surface.rs +664 -0
- package/vendor/krilla/src/text/cid.rs +488 -0
- package/vendor/krilla/src/text/font.rs +351 -0
- package/vendor/krilla/src/text/glyph/bitmap.rs +81 -0
- package/vendor/krilla/src/text/glyph/colr.rs +581 -0
- package/vendor/krilla/src/text/glyph/mod.rs +205 -0
- package/vendor/krilla/src/text/glyph/outline.rs +72 -0
- package/vendor/krilla/src/text/glyph/svg.rs +34 -0
- package/vendor/krilla/src/text/group.rs +463 -0
- package/vendor/krilla/src/text/mod.rs +232 -0
- package/vendor/krilla/src/text/shape.rs +102 -0
- package/vendor/krilla/src/text/type3.rs +514 -0
- package/vendor/krilla/src/util.rs +286 -0
- package/vendor/stylo/properties/longhands.toml +0 -1
- package/vendor/stylo/stylesheets/rule_parser.rs +1 -1
- package/wasm/blitz_pdf.js +10 -5
- package/wasm/blitz_pdf_bg.wasm +0 -0
|
@@ -0,0 +1,1244 @@
|
|
|
1
|
+
//! Exporting with a specific PDF conformance level.
|
|
2
|
+
//!
|
|
3
|
+
//! PDF defines a number of additional conformance levels that restrict the features of PDF that
|
|
4
|
+
//! can be used to a specific subset.
|
|
5
|
+
//!
|
|
6
|
+
//! You can use a [`Validator`] by creating a corresponding [`Configuration`]
|
|
7
|
+
//! you want to build the document with. There are three important aspects that play into this:
|
|
8
|
+
//! - krilla will internally write the file in a way that conforms to the given standard, i.e.
|
|
9
|
+
//! by settings appropriate metadata. This happens under-the-hood and is completely abstracted
|
|
10
|
+
//! away from the user.
|
|
11
|
+
//! - For aspects that are out of control of krilla and dependent on the input, krilla will perform
|
|
12
|
+
//! a validation that the input is compatible with the standard. krilla will record all violations,
|
|
13
|
+
//! and when calling `document.finish()`, in case there is at least one violation, krilla will
|
|
14
|
+
//! return them as an error, instead of returning the finished document. See [`ValidationError`].
|
|
15
|
+
//! - Finally, some standards have requirements that cannot possibly be validated by krilla, as
|
|
16
|
+
//! they are semantic in nature. It is upon you, as a user of that library, to ensure that those
|
|
17
|
+
//! requirements are fulfilled. Therefore, while krilla tries to make it as easy as possible
|
|
18
|
+
//! to generate compliant PDFs, it is still highly recommended that you familiarize yourself
|
|
19
|
+
//! with the PDF specification as well as the specifications for the substandards. This is
|
|
20
|
+
//! especially true for standards related to universal accessibility.
|
|
21
|
+
//!
|
|
22
|
+
//! You can find some requirements below **Requirements** for each [`Validator`].
|
|
23
|
+
//!
|
|
24
|
+
//! [`Configuration`]: crate::configure::Configuration
|
|
25
|
+
|
|
26
|
+
use std::collections::HashMap;
|
|
27
|
+
use std::fmt::Debug;
|
|
28
|
+
|
|
29
|
+
use pdf_writer::types::OutputIntentSubtype;
|
|
30
|
+
use xmp_writer::pdfa::PdfAExtSchemasWriter;
|
|
31
|
+
use xmp_writer::XmpWriter;
|
|
32
|
+
|
|
33
|
+
use crate::color::separation::SeparationColorant;
|
|
34
|
+
use crate::color::separation::SeparationSpace;
|
|
35
|
+
use crate::color::RegularColor;
|
|
36
|
+
use crate::configure::PdfVersion;
|
|
37
|
+
use crate::interchange::embed::EmbedError;
|
|
38
|
+
use crate::surface::Location;
|
|
39
|
+
use crate::text::Font;
|
|
40
|
+
use crate::text::GlyphId;
|
|
41
|
+
|
|
42
|
+
/// An error that occurred during validation/
|
|
43
|
+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
|
44
|
+
pub enum ValidationError {
|
|
45
|
+
/// There was a string that was longer than the maximum allowed length (32767).
|
|
46
|
+
///
|
|
47
|
+
/// Can for example occur if you set a title or an author that is longer than
|
|
48
|
+
/// the given length.
|
|
49
|
+
TooLongString,
|
|
50
|
+
/// There was a name that was longer than the maximum allowed length (127).
|
|
51
|
+
///
|
|
52
|
+
/// Can for example occur if the font name is too long.
|
|
53
|
+
TooLongName,
|
|
54
|
+
/// There was an array that was longer than the maximum allowed length (8191).
|
|
55
|
+
/// Can only occur for PDF 1.4.
|
|
56
|
+
///
|
|
57
|
+
/// Can for example occur if a text too long was written.
|
|
58
|
+
TooLongArray,
|
|
59
|
+
/// There was a dictionary with more entries than the maximum allowed (4095).
|
|
60
|
+
/// Can only occur for PDF 1.4.
|
|
61
|
+
///
|
|
62
|
+
/// Can for example occur if too many annotations are added to a page.
|
|
63
|
+
TooLongDictionary,
|
|
64
|
+
/// There was a float that is higher than the maximum allowed (32767).
|
|
65
|
+
/// Can only occur for PDF 1.4.
|
|
66
|
+
TooLargeFloat,
|
|
67
|
+
/// The PDF exceeds the upper limit for indirect objects (8388607).
|
|
68
|
+
///
|
|
69
|
+
/// Occurs if the PDF is simply too long.
|
|
70
|
+
TooManyIndirectObjects,
|
|
71
|
+
/// The PDF contains a content stream that exceeds maximum allowed q/Q nesting level (28).
|
|
72
|
+
///
|
|
73
|
+
/// Can only occur if the user stacks many clip paths.
|
|
74
|
+
TooHighQNestingLevel,
|
|
75
|
+
/// The PDF contains PostScript code, which is forbidden by some export formats.
|
|
76
|
+
///
|
|
77
|
+
/// Occurs if a gradient with spread method `Repeat`/`Reflect` or a sweep gradient was used.
|
|
78
|
+
ContainsPostScript(Option<Location>),
|
|
79
|
+
/// No CMYK ICC profile was provided, even though one is necessary.
|
|
80
|
+
///
|
|
81
|
+
/// Occurs if the export format requires a device-independent color representation,
|
|
82
|
+
/// and a CMYK color was used in the document.
|
|
83
|
+
MissingCMYKProfile,
|
|
84
|
+
/// The same Separation colorant was used with multiple different fallback colors.
|
|
85
|
+
///
|
|
86
|
+
/// Occurs if the user specified multiple Separation color spaces with the same colorant but a different fallback color.
|
|
87
|
+
InconsistentSeparationFallback(SeparationColorant),
|
|
88
|
+
/// The `.notdef` glyph was used, which is forbidden by some export formats.
|
|
89
|
+
///
|
|
90
|
+
/// Can occur if a glyph could not be found in the font for a corresponding codepoint
|
|
91
|
+
/// in the input text, or if it was explicitly mapped that way.
|
|
92
|
+
///
|
|
93
|
+
/// The third argument contains the text range of the glyph.
|
|
94
|
+
ContainsNotDefGlyph(Font, Option<Location>, String),
|
|
95
|
+
/// A glyph was mapped to no codepoint at all, which is forbidden by some
|
|
96
|
+
/// standards.
|
|
97
|
+
NoCodepointMapping(Font, GlyphId, Option<Location>),
|
|
98
|
+
/// A glyph was mapped either to the codepoint 0x0, 0xFEFF or 0xFFFE, which
|
|
99
|
+
/// is forbidden by some standards.
|
|
100
|
+
///
|
|
101
|
+
/// Can occur if those codepoints appeared in the input text, or were
|
|
102
|
+
/// explicitly mapped to that glyph.
|
|
103
|
+
InvalidCodepointMapping(Font, GlyphId, char, Option<Location>),
|
|
104
|
+
/// A glyph was mapped to a codepoint in the Unicode private use area, which is forbidden
|
|
105
|
+
/// by some standards, like for example PDF/A-2a.
|
|
106
|
+
// Note that the standard doesn't explicitly forbid it, but instead requires an ActualText
|
|
107
|
+
// attribute to be present. But we just completely forbid it, for simplicity.
|
|
108
|
+
UnicodePrivateArea(Font, GlyphId, char, Option<Location>),
|
|
109
|
+
/// A font has a license that requires explicit permission of the legal owner for embedding
|
|
110
|
+
/// but the standard requires font programs to be legally embeddable for universal rendering.
|
|
111
|
+
RestrictedLicense(Font),
|
|
112
|
+
/// No document language was set via the metadata, even though it is required
|
|
113
|
+
/// by the standard.
|
|
114
|
+
NoDocumentLanguage,
|
|
115
|
+
/// No title was provided for the document, even though it is required by
|
|
116
|
+
/// the standard.
|
|
117
|
+
NoDocumentTitle,
|
|
118
|
+
/// A figure or formula is missing an alt text.
|
|
119
|
+
MissingAltText(Option<Location>),
|
|
120
|
+
/// A heading is missing a title.
|
|
121
|
+
MissingHeadingTitle,
|
|
122
|
+
/// The document does not contain an outline.
|
|
123
|
+
MissingDocumentOutline,
|
|
124
|
+
/// An annotation is missing an alt text.
|
|
125
|
+
MissingAnnotationAltText(Option<Location>),
|
|
126
|
+
/// The date of the document is missing.
|
|
127
|
+
// We need this because for some standards we need to add the
|
|
128
|
+
// xmp:History attribute.
|
|
129
|
+
MissingDocumentDate,
|
|
130
|
+
/// The PDF contains transparency, which is forbidden by some standards (e.g. PDF/A-1).
|
|
131
|
+
Transparency(Option<Location>),
|
|
132
|
+
/// The PDF contains an image with `interpolate` set to `true`.
|
|
133
|
+
ImageInterpolation(Option<Location>),
|
|
134
|
+
/// The PDF contains an embedded file.
|
|
135
|
+
EmbeddedFile(EmbedError, Option<Location>),
|
|
136
|
+
/// The PDF contains no tagging.
|
|
137
|
+
MissingTagging,
|
|
138
|
+
/// The PDF contains another embedded PDF.
|
|
139
|
+
///
|
|
140
|
+
/// This is currently forbidden in validated export because we cannot manually verify
|
|
141
|
+
/// whether the file actually fulfills all the criteria for the export mode.
|
|
142
|
+
EmbeddedPDF(Option<Location>),
|
|
143
|
+
/// A feature only available in a later PDF version was required.
|
|
144
|
+
RequiresNewerPdfVersion(VersionedFeature, Option<Location>),
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/// Features that may require a later PDF version than the current one.
|
|
148
|
+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
|
149
|
+
pub enum VersionedFeature {
|
|
150
|
+
/// Tabbing through the document according to the structure order.
|
|
151
|
+
StructureOrderTabbing,
|
|
152
|
+
/// Header and footer artifact subtypes.
|
|
153
|
+
HeaderFooterArtifactSubtypes,
|
|
154
|
+
/// Scope attribute for table header cells.
|
|
155
|
+
TableHeaderScope,
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
impl VersionedFeature {
|
|
159
|
+
/// Get the minimum PDF version required for this feature.
|
|
160
|
+
pub fn minimum_pdf_version(&self) -> PdfVersion {
|
|
161
|
+
match self {
|
|
162
|
+
VersionedFeature::StructureOrderTabbing => PdfVersion::Pdf15,
|
|
163
|
+
VersionedFeature::HeaderFooterArtifactSubtypes => PdfVersion::Pdf17,
|
|
164
|
+
VersionedFeature::TableHeaderScope => PdfVersion::Pdf15,
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/// Collection of validators with at most one validator for each standard.
|
|
170
|
+
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, Hash)]
|
|
171
|
+
pub struct Validators {
|
|
172
|
+
a: Option<Archival>,
|
|
173
|
+
ua: Option<Accessibility>,
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
impl Validators {
|
|
177
|
+
/// Returns a filtered `Validators` containing only validators that prohibit the given error,
|
|
178
|
+
/// or `None` if no validator prohibits it.
|
|
179
|
+
pub fn prohibits(self, error: &ValidationError) -> Option<Self> {
|
|
180
|
+
let a = self.a.filter(|v| v.prohibits(error));
|
|
181
|
+
let ua = self.ua.filter(|v| v.prohibits(error));
|
|
182
|
+
|
|
183
|
+
let any = a.is_some() || ua.is_some();
|
|
184
|
+
any.then_some(Self { a, ua })
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/// Returns `true` if no validators are set.
|
|
188
|
+
pub fn is_empty(self) -> bool {
|
|
189
|
+
self.a.is_none() && self.ua.is_none()
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/// Returns the number of set validators.
|
|
193
|
+
pub fn len(self) -> usize {
|
|
194
|
+
(if self.a.is_some() { 1 } else { 0 }) + (if self.ua.is_some() { 1 } else { 0 })
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/// Returns the PDF/A validator, if set.
|
|
198
|
+
pub fn archival(self) -> Option<Archival> {
|
|
199
|
+
self.a
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/// Returns the PDF/UA accessibility validator, if set.
|
|
203
|
+
pub fn accessibility(self) -> Option<Accessibility> {
|
|
204
|
+
self.ua
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/// Whether the font must supply valid Unicode code points for each of the
|
|
208
|
+
/// drawn glyphs.
|
|
209
|
+
pub(crate) fn requires_codepoint_mappings(self) -> bool {
|
|
210
|
+
self.into_iter().any(Validator::requires_codepoint_mappings)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/// Force the `DisplayDocTitle` flag set.
|
|
214
|
+
pub(crate) fn requires_display_doc_title(self) -> bool {
|
|
215
|
+
self.ua
|
|
216
|
+
.is_some_and(Accessibility::requires_display_doc_title)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/// Force sRGB profiles for `DeviceGray` and `DeviceRgb` colorspaces.
|
|
220
|
+
pub(crate) fn requires_no_device_cs(self) -> bool {
|
|
221
|
+
self.a.is_some_and(Archival::requires_no_device_cs)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/// Force the `Print` flag set and the `Hidden`, `Invisible`,
|
|
225
|
+
/// `ToggleNoView`, and `NoView` flags unset.
|
|
226
|
+
pub(crate) fn requires_annotation_flags(self) -> bool {
|
|
227
|
+
self.a.is_some_and(Archival::requires_annotation_flags)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/// Whether Tagged PDF must be enabled.
|
|
231
|
+
pub(crate) fn requires_tagging(self) -> bool {
|
|
232
|
+
self.into_iter().any(Validator::requires_tagging)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/// Whether XMP metadata must be written.
|
|
236
|
+
pub(crate) fn requires_xmp_metadata(self) -> bool {
|
|
237
|
+
self.into_iter().any(Validator::requires_xmp_metadata)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/// Whether any extension schemata should be descibed using the "pdfaSchema"
|
|
241
|
+
/// namespace.
|
|
242
|
+
pub(crate) fn requires_xmp_metadata_extension_schema(self) -> bool {
|
|
243
|
+
self.a
|
|
244
|
+
.is_some_and(Archival::requires_xmp_metadata_extension_schema)
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/// Whether the `instanceID` field is allowed in XMP.
|
|
248
|
+
pub(crate) fn prohibits_instance_id_in_xmp_metadata(self) -> bool {
|
|
249
|
+
self.a
|
|
250
|
+
.is_some_and(Archival::prohibits_instance_id_in_xmp_metadata)
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/// Whether the xmpMM:History entry is required.
|
|
254
|
+
pub(crate) fn requires_file_provenance_information(self) -> bool {
|
|
255
|
+
self.a
|
|
256
|
+
.is_some_and(Archival::requires_file_provenance_information)
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/// Whether the `/Info` dictionary is allowed in the file trailer.
|
|
260
|
+
pub(crate) fn prohibits_info_dict(self) -> bool {
|
|
261
|
+
self.a.is_some_and(Archival::prohibits_info_dict)
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/// Whether a non-printable file header is mandatory.
|
|
265
|
+
pub(crate) fn requires_binary_header(self) -> bool {
|
|
266
|
+
self.a.is_some_and(Archival::requires_binary_header)
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/// Whether the `EmbeddedFiles` key in the name dictionary of the document
|
|
270
|
+
/// catalog dictionary should be written even if empty.
|
|
271
|
+
pub(crate) fn requires_embedded_files_when_empty(self) -> bool {
|
|
272
|
+
self.a
|
|
273
|
+
.is_some_and(Archival::requires_embedded_files_when_empty)
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/// Whether any of these standards explicitly specifies the `/AF` key.
|
|
277
|
+
///
|
|
278
|
+
/// The `/AF` key may be supported by the underlying PDF version instead:
|
|
279
|
+
/// Starting at PDF 2.0, the key is specified by ISO 32000 and does not need
|
|
280
|
+
/// to be added by PDF/A.
|
|
281
|
+
pub(crate) fn specifies_associated_files(self) -> bool {
|
|
282
|
+
self.a.is_some_and(Archival::specifies_associated_files)
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
pub(crate) fn output_intent(self) -> Option<OutputIntentSubtype<'static>> {
|
|
286
|
+
self.a.map(Archival::output_intent)
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
pub(crate) fn write_xmp<'a>(
|
|
290
|
+
self,
|
|
291
|
+
xmp: &mut XmpWriter<'a>,
|
|
292
|
+
extensions: &'a [crate::metadata::XmpExtension],
|
|
293
|
+
) {
|
|
294
|
+
if self.requires_xmp_metadata_extension_schema() || !extensions.is_empty() {
|
|
295
|
+
let mut extension_schemas = xmp.extension_schemas();
|
|
296
|
+
if let Some(a) = self.a {
|
|
297
|
+
a.write_xmp_extension_schema_description(&mut extension_schemas);
|
|
298
|
+
}
|
|
299
|
+
if let Some(ua) = self.ua {
|
|
300
|
+
ua.write_xmp_extension_schema_description(&mut extension_schemas);
|
|
301
|
+
}
|
|
302
|
+
for extension in extensions {
|
|
303
|
+
extension.serialize_schema(&mut extension_schemas);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if let Some(a) = self.a {
|
|
308
|
+
a.write_xmp(xmp);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if let Some(ua) = self.ua {
|
|
312
|
+
ua.write_xmp(xmp);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/// Returns the maximum PDF version allowed by all active validators.
|
|
317
|
+
pub fn max(self) -> PdfVersion {
|
|
318
|
+
self.a
|
|
319
|
+
.map_or(PdfVersion::MAX, |v| v.max())
|
|
320
|
+
.min(self.ua.map_or(PdfVersion::MAX, |v| v.max()))
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/// Returns the minimum PDF version required by all active validators, if any.
|
|
324
|
+
pub fn min(self) -> Option<PdfVersion> {
|
|
325
|
+
self.a
|
|
326
|
+
.and_then(|v| v.min())
|
|
327
|
+
.max(self.ua.and_then(|v| v.min()))
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
impl IntoIterator for Validators {
|
|
332
|
+
type Item = Validator;
|
|
333
|
+
type IntoIter = std::iter::Flatten<std::array::IntoIter<Option<Validator>, 2>>;
|
|
334
|
+
|
|
335
|
+
fn into_iter(self) -> Self::IntoIter {
|
|
336
|
+
[self.a.map(Validator::A), self.ua.map(Validator::Ua)]
|
|
337
|
+
.into_iter()
|
|
338
|
+
.flatten()
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/// A builder for constructing a [`Validators`] collection.
|
|
343
|
+
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, Hash)]
|
|
344
|
+
pub struct ValidatorsBuilder(Validators);
|
|
345
|
+
|
|
346
|
+
impl ValidatorsBuilder {
|
|
347
|
+
/// Set a validator, overwriting the current one if the same standard family is already set.
|
|
348
|
+
pub fn set_validator(self, validator: Validator) -> Self {
|
|
349
|
+
match validator {
|
|
350
|
+
Validator::A(a) => self.with_archival_validator(a),
|
|
351
|
+
Validator::Ua(ua) => self.with_accessibility_validator(ua),
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/// Set the PDF/A validator, overwriting the current one if already set.
|
|
356
|
+
pub fn with_archival_validator(mut self, archival: Archival) -> Self {
|
|
357
|
+
self.0.a = Some(archival);
|
|
358
|
+
self
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/// Set the PDF/UA accessibility validator, overwriting the current one if already set.
|
|
362
|
+
pub fn with_accessibility_validator(mut self, accessibility: Accessibility) -> Self {
|
|
363
|
+
self.0.ua = Some(accessibility);
|
|
364
|
+
self
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
pub(crate) fn finish(self) -> Result<Validators, Validators> {
|
|
368
|
+
let min = self.0.min().unwrap_or(PdfVersion::MIN);
|
|
369
|
+
let max = self.0.max();
|
|
370
|
+
|
|
371
|
+
if min > max {
|
|
372
|
+
Err(self.0)
|
|
373
|
+
} else {
|
|
374
|
+
Ok(self.0)
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/// A PDF validator for a specific conformance standard.
|
|
380
|
+
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
|
381
|
+
pub enum Validator {
|
|
382
|
+
/// A PDF/A validator.
|
|
383
|
+
A(Archival),
|
|
384
|
+
/// A PDF/UA accessibility validator.
|
|
385
|
+
Ua(Accessibility),
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
impl Validator {
|
|
389
|
+
fn requires_codepoint_mappings(self) -> bool {
|
|
390
|
+
match self {
|
|
391
|
+
Self::A(a) => a.requires_codepoint_mappings(),
|
|
392
|
+
Self::Ua(ua) => ua.requires_codepoint_mappings(),
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
fn requires_tagging(self) -> bool {
|
|
397
|
+
match self {
|
|
398
|
+
Self::A(a) => a.requires_tagging(),
|
|
399
|
+
Self::Ua(ua) => ua.requires_tagging(),
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
fn requires_xmp_metadata(self) -> bool {
|
|
404
|
+
match self {
|
|
405
|
+
Self::A(a) => a.requires_xmp_metadata(),
|
|
406
|
+
Self::Ua(ua) => ua.requires_xmp_metadata(),
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/// Minimum PDF version required to use this validator, if any.
|
|
411
|
+
pub fn min(self) -> Option<PdfVersion> {
|
|
412
|
+
match self {
|
|
413
|
+
Self::A(a) => a.min(),
|
|
414
|
+
Self::Ua(ua) => ua.min(),
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/// Maximum PDF version this standard can be used with.
|
|
419
|
+
pub fn max(self) -> PdfVersion {
|
|
420
|
+
match self {
|
|
421
|
+
Self::A(a) => a.max(),
|
|
422
|
+
Self::Ua(ua) => ua.max(),
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/// Returns a human-readable string representation of the validator.
|
|
427
|
+
pub fn as_str(self) -> &'static str {
|
|
428
|
+
match self {
|
|
429
|
+
Self::A(a) => a.as_str(),
|
|
430
|
+
Self::Ua(ua) => ua.as_str(),
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
impl From<Archival> for Validator {
|
|
436
|
+
fn from(a: Archival) -> Self {
|
|
437
|
+
Self::A(a)
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
impl From<Accessibility> for Validator {
|
|
442
|
+
fn from(ua: Accessibility) -> Self {
|
|
443
|
+
Self::Ua(ua)
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/// A PDF/A conformance level.
|
|
448
|
+
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
|
449
|
+
#[allow(non_camel_case_types)]
|
|
450
|
+
pub enum Archival {
|
|
451
|
+
/// The validator for the PDF/A-1a standard.
|
|
452
|
+
///
|
|
453
|
+
/// **Requirements**:
|
|
454
|
+
/// - All requirements of PDF/A-1b.
|
|
455
|
+
/// - You need to follow all requirements outlined in the _Other Notes_ section of the
|
|
456
|
+
/// [`tagging`] module.
|
|
457
|
+
/// - You need to follow all best practices when using [tags](`crate::interchange::tagging::Tag`), as outlined in the documentation
|
|
458
|
+
/// of each tag.
|
|
459
|
+
/// - Artifacts such as page numbers, backgrounds, cut marks and color bars should be specified
|
|
460
|
+
/// correspondingly as artifacts.
|
|
461
|
+
/// - Word boundaries need to be explicitly specified with a space. The same applies to words at
|
|
462
|
+
/// the end of a line that are not followed by punctuation.
|
|
463
|
+
/// - To the fullest extent possible, the logical structure of the document should be encoded
|
|
464
|
+
/// correspondingly in the tag tree using appropriate grouping tags.
|
|
465
|
+
/// - Language identifiers used must be valid according to RFC 3066.
|
|
466
|
+
/// - You should provide an alternate text to span content tags, if applicable.
|
|
467
|
+
/// - You should provide the expansion of abbreviations to span content tags, if applicable.
|
|
468
|
+
///
|
|
469
|
+
/// [`tagging`]: crate::interchange::tagging
|
|
470
|
+
A1_A,
|
|
471
|
+
/// The validator for the PDF/A-1b standard.
|
|
472
|
+
///
|
|
473
|
+
/// **Requirements**: -
|
|
474
|
+
A1_B,
|
|
475
|
+
/// The validator for the PDF/A-2a standard.
|
|
476
|
+
///
|
|
477
|
+
/// **Requirements**:
|
|
478
|
+
/// - All requirements of PDF/A-2b.
|
|
479
|
+
/// - You need to follow all requirements outlined in the _Other Notes_ section of the
|
|
480
|
+
/// [`tagging`] module.
|
|
481
|
+
/// - You need to follow all best practices when using [tags](`crate::interchange::tagging::Tag`), as outlined in the documentation
|
|
482
|
+
/// of each tag.
|
|
483
|
+
/// - Artifacts such as page numbers, backgrounds, cut marks and color bars should be specified
|
|
484
|
+
/// correspondingly as artifacts.
|
|
485
|
+
/// - Word boundaries need to be explicitly specified with a space. The same applies to words at
|
|
486
|
+
/// the end of a line that are not followed by punctuation.
|
|
487
|
+
/// - To the fullest extent possible, the logical structure of the document should be encoded
|
|
488
|
+
/// correspondingly in the tag tree using appropriate grouping tags.
|
|
489
|
+
/// - Language identifiers used must be valid according to RFC 3066.
|
|
490
|
+
/// - You should provide an alternate text to span content tags, if applicable.
|
|
491
|
+
/// - You should provide the expansion of abbreviations to span content tags, if applicable.
|
|
492
|
+
///
|
|
493
|
+
/// [`tagging`]: crate::interchange::tagging
|
|
494
|
+
A2_A,
|
|
495
|
+
/// The validator for the PDF/A-2b standard.
|
|
496
|
+
///
|
|
497
|
+
/// **Requirements**:
|
|
498
|
+
/// - You should only use fonts that are legally embeddable in a file for unlimited,
|
|
499
|
+
/// universal rendering.
|
|
500
|
+
A2_B,
|
|
501
|
+
/// The validator for the PDF/A-2u standard.
|
|
502
|
+
///
|
|
503
|
+
/// **Requirements**:
|
|
504
|
+
/// - All requirements of PDF/A-2b
|
|
505
|
+
A2_U,
|
|
506
|
+
/// The validator for the PDF/A-3a standard.
|
|
507
|
+
///
|
|
508
|
+
/// **Requirements**:
|
|
509
|
+
/// - All requirements of PDF/A-2a
|
|
510
|
+
A3_A,
|
|
511
|
+
/// The validator for the PDF/A-3b standard.
|
|
512
|
+
///
|
|
513
|
+
/// **Requirements**:
|
|
514
|
+
/// - All requirements of PDF/A-2b
|
|
515
|
+
A3_B,
|
|
516
|
+
/// The validator for the PDF/A-3u standard.
|
|
517
|
+
///
|
|
518
|
+
/// **Requirements**:
|
|
519
|
+
/// - All requirements of PDF/A-2b
|
|
520
|
+
A3_U,
|
|
521
|
+
/// The validator for the PDF/A-4 standard.
|
|
522
|
+
///
|
|
523
|
+
/// **Requirements**:
|
|
524
|
+
/// - While not required, it's recommended to enable tagging.
|
|
525
|
+
A4,
|
|
526
|
+
/// The validator for the PDF/A-4f standard.
|
|
527
|
+
///
|
|
528
|
+
/// **Requirements**:
|
|
529
|
+
/// - All requirements of PDF/A-4
|
|
530
|
+
A4F,
|
|
531
|
+
/// The validator for the PDF/A-4e standard.
|
|
532
|
+
///
|
|
533
|
+
/// **Requirements**:
|
|
534
|
+
/// - All requirements of PDF/A-4
|
|
535
|
+
A4E,
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
impl Archival {
|
|
539
|
+
fn prohibits(self, error: &ValidationError) -> bool {
|
|
540
|
+
match (self, error) {
|
|
541
|
+
// Forbidden under all PDF/A-1 profiles.
|
|
542
|
+
(
|
|
543
|
+
Self::A1_A | Self::A1_B,
|
|
544
|
+
ValidationError::TooLongString
|
|
545
|
+
| ValidationError::TooLongName
|
|
546
|
+
| ValidationError::TooLongArray
|
|
547
|
+
| ValidationError::TooLongDictionary
|
|
548
|
+
| ValidationError::TooLargeFloat
|
|
549
|
+
| ValidationError::TooManyIndirectObjects
|
|
550
|
+
| ValidationError::TooHighQNestingLevel
|
|
551
|
+
| ValidationError::ContainsPostScript(_)
|
|
552
|
+
| ValidationError::MissingCMYKProfile
|
|
553
|
+
| ValidationError::RestrictedLicense(_)
|
|
554
|
+
| ValidationError::MissingDocumentDate
|
|
555
|
+
| ValidationError::Transparency(_)
|
|
556
|
+
| ValidationError::ImageInterpolation(_)
|
|
557
|
+
| ValidationError::EmbeddedFile(EmbedError::Existence, _)
|
|
558
|
+
| ValidationError::EmbeddedPDF(_),
|
|
559
|
+
) => true,
|
|
560
|
+
// Allowed under all PDF/A-1 profiles.
|
|
561
|
+
(
|
|
562
|
+
Self::A1_A | Self::A1_B,
|
|
563
|
+
ValidationError::InconsistentSeparationFallback(_)
|
|
564
|
+
| ValidationError::InvalidCodepointMapping(_, _, _, _)
|
|
565
|
+
| ValidationError::UnicodePrivateArea(_, _, _, _)
|
|
566
|
+
| ValidationError::NoDocumentTitle
|
|
567
|
+
| ValidationError::MissingHeadingTitle
|
|
568
|
+
| ValidationError::MissingDocumentOutline
|
|
569
|
+
| ValidationError::EmbeddedFile(_, _)
|
|
570
|
+
| ValidationError::RequiresNewerPdfVersion(
|
|
571
|
+
VersionedFeature::HeaderFooterArtifactSubtypes
|
|
572
|
+
| VersionedFeature::StructureOrderTabbing
|
|
573
|
+
| VersionedFeature::TableHeaderScope,
|
|
574
|
+
_,
|
|
575
|
+
),
|
|
576
|
+
) => false,
|
|
577
|
+
// Forbidden under PDF/A-1a but allowed under PDF/A-1b.
|
|
578
|
+
(
|
|
579
|
+
Self::A1_A | Self::A1_B,
|
|
580
|
+
ValidationError::ContainsNotDefGlyph(_, _, _)
|
|
581
|
+
| ValidationError::NoCodepointMapping(_, _, _)
|
|
582
|
+
| ValidationError::NoDocumentLanguage
|
|
583
|
+
| ValidationError::MissingAltText(_)
|
|
584
|
+
| ValidationError::MissingAnnotationAltText(_)
|
|
585
|
+
| ValidationError::MissingTagging,
|
|
586
|
+
) => self == Self::A1_A,
|
|
587
|
+
|
|
588
|
+
// Forbidden under all PDF/A-2 and PDF/A-3 profiles.
|
|
589
|
+
(
|
|
590
|
+
Self::A2_A | Self::A2_B | Self::A2_U | Self::A3_A | Self::A3_B | Self::A3_U,
|
|
591
|
+
ValidationError::TooLongString
|
|
592
|
+
| ValidationError::TooLongName
|
|
593
|
+
| ValidationError::TooManyIndirectObjects
|
|
594
|
+
| ValidationError::TooHighQNestingLevel
|
|
595
|
+
| ValidationError::ContainsPostScript(_)
|
|
596
|
+
| ValidationError::MissingCMYKProfile
|
|
597
|
+
| ValidationError::InconsistentSeparationFallback(_)
|
|
598
|
+
| ValidationError::ContainsNotDefGlyph(_, _, _)
|
|
599
|
+
| ValidationError::RestrictedLicense(_)
|
|
600
|
+
| ValidationError::MissingDocumentDate
|
|
601
|
+
| ValidationError::ImageInterpolation(_)
|
|
602
|
+
| ValidationError::EmbeddedPDF(_),
|
|
603
|
+
) => true,
|
|
604
|
+
// Allowed under all PDF/A-2 and PDF/A-3 profiles.
|
|
605
|
+
(
|
|
606
|
+
Self::A2_A | Self::A2_B | Self::A2_U | Self::A3_A | Self::A3_B | Self::A3_U,
|
|
607
|
+
ValidationError::TooLongArray
|
|
608
|
+
| ValidationError::TooLongDictionary
|
|
609
|
+
| ValidationError::TooLargeFloat
|
|
610
|
+
| ValidationError::NoDocumentTitle
|
|
611
|
+
| ValidationError::Transparency(_)
|
|
612
|
+
| ValidationError::MissingHeadingTitle
|
|
613
|
+
| ValidationError::MissingDocumentOutline
|
|
614
|
+
| ValidationError::RequiresNewerPdfVersion(
|
|
615
|
+
VersionedFeature::HeaderFooterArtifactSubtypes
|
|
616
|
+
| VersionedFeature::StructureOrderTabbing
|
|
617
|
+
| VersionedFeature::TableHeaderScope,
|
|
618
|
+
_,
|
|
619
|
+
),
|
|
620
|
+
) => false,
|
|
621
|
+
// Forbidden under PDF/A-2 but allowed under PDF/A-3.
|
|
622
|
+
(
|
|
623
|
+
Self::A2_A | Self::A2_B | Self::A2_U | Self::A3_A | Self::A3_B | Self::A3_U,
|
|
624
|
+
ValidationError::EmbeddedFile(EmbedError::Existence, _),
|
|
625
|
+
) => self == Self::A2_A || self == Self::A2_B || self == Self::A2_U,
|
|
626
|
+
// Forbidden under PDF/A-3 but allowed under PDF/A-2.
|
|
627
|
+
(
|
|
628
|
+
Self::A2_A | Self::A2_B | Self::A2_U | Self::A3_A | Self::A3_B | Self::A3_U,
|
|
629
|
+
ValidationError::EmbeddedFile(
|
|
630
|
+
EmbedError::MissingDate
|
|
631
|
+
| EmbedError::MissingDescription
|
|
632
|
+
| EmbedError::MissingMimeType,
|
|
633
|
+
_,
|
|
634
|
+
),
|
|
635
|
+
) => self == Self::A3_A || self == Self::A3_B || self == Self::A3_U,
|
|
636
|
+
// Forbidden under PDF/A-2 and PDF/A-3 accessible profiles.
|
|
637
|
+
(
|
|
638
|
+
Self::A2_A | Self::A2_B | Self::A2_U | Self::A3_A | Self::A3_B | Self::A3_U,
|
|
639
|
+
ValidationError::UnicodePrivateArea(_, _, _, _)
|
|
640
|
+
| ValidationError::NoDocumentLanguage
|
|
641
|
+
| ValidationError::MissingAltText(_)
|
|
642
|
+
| ValidationError::MissingAnnotationAltText(_)
|
|
643
|
+
| ValidationError::MissingTagging,
|
|
644
|
+
) => self == Self::A2_A || self == Self::A3_A,
|
|
645
|
+
// Forbidden under PDF/A-2 and PDF/A-3 accessible and Unicode profiles.
|
|
646
|
+
(
|
|
647
|
+
Self::A2_A | Self::A2_B | Self::A2_U | Self::A3_A | Self::A3_B | Self::A3_U,
|
|
648
|
+
ValidationError::NoCodepointMapping(_, _, _)
|
|
649
|
+
| ValidationError::InvalidCodepointMapping(_, _, _, _),
|
|
650
|
+
) => {
|
|
651
|
+
self == Self::A2_A || self == Self::A2_U || self == Self::A3_A || self == Self::A3_U
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
// Forbidden under all PDF/A-4 profiles.
|
|
655
|
+
(
|
|
656
|
+
Self::A4 | Self::A4F | Self::A4E,
|
|
657
|
+
ValidationError::MissingCMYKProfile
|
|
658
|
+
| ValidationError::InconsistentSeparationFallback(_)
|
|
659
|
+
| ValidationError::ContainsNotDefGlyph(_, _, _)
|
|
660
|
+
| ValidationError::NoCodepointMapping(_, _, _)
|
|
661
|
+
| ValidationError::InvalidCodepointMapping(_, _, _, _)
|
|
662
|
+
| ValidationError::UnicodePrivateArea(_, _, _, _)
|
|
663
|
+
| ValidationError::RestrictedLicense(_)
|
|
664
|
+
| ValidationError::MissingDocumentDate
|
|
665
|
+
| ValidationError::ImageInterpolation(_)
|
|
666
|
+
| ValidationError::EmbeddedPDF(_),
|
|
667
|
+
) => true,
|
|
668
|
+
// Allowed under all PDF/A-4 profiles.
|
|
669
|
+
(
|
|
670
|
+
Self::A4 | Self::A4F | Self::A4E,
|
|
671
|
+
ValidationError::TooLongString
|
|
672
|
+
| ValidationError::TooLongName
|
|
673
|
+
| ValidationError::TooLongArray
|
|
674
|
+
| ValidationError::TooLongDictionary
|
|
675
|
+
| ValidationError::TooLargeFloat
|
|
676
|
+
| ValidationError::TooManyIndirectObjects
|
|
677
|
+
| ValidationError::TooHighQNestingLevel
|
|
678
|
+
| ValidationError::ContainsPostScript(_)
|
|
679
|
+
| ValidationError::NoDocumentLanguage
|
|
680
|
+
| ValidationError::NoDocumentTitle
|
|
681
|
+
| ValidationError::MissingAltText(_)
|
|
682
|
+
| ValidationError::MissingHeadingTitle
|
|
683
|
+
| ValidationError::MissingDocumentOutline
|
|
684
|
+
| ValidationError::MissingAnnotationAltText(_)
|
|
685
|
+
| ValidationError::Transparency(_)
|
|
686
|
+
| ValidationError::EmbeddedFile(
|
|
687
|
+
EmbedError::MissingDate | EmbedError::MissingMimeType,
|
|
688
|
+
_,
|
|
689
|
+
)
|
|
690
|
+
| ValidationError::MissingTagging
|
|
691
|
+
| ValidationError::RequiresNewerPdfVersion(
|
|
692
|
+
VersionedFeature::HeaderFooterArtifactSubtypes
|
|
693
|
+
| VersionedFeature::StructureOrderTabbing
|
|
694
|
+
| VersionedFeature::TableHeaderScope,
|
|
695
|
+
_,
|
|
696
|
+
),
|
|
697
|
+
) => false,
|
|
698
|
+
// Forbidden under PDF/A-4 but allowed under other PDF/A-4 profiles.
|
|
699
|
+
(
|
|
700
|
+
Self::A4 | Self::A4F | Self::A4E,
|
|
701
|
+
ValidationError::EmbeddedFile(EmbedError::Existence, _),
|
|
702
|
+
) => self == Self::A4,
|
|
703
|
+
// Allowed under PDF/A-4 but forbidden under other profiles.
|
|
704
|
+
(
|
|
705
|
+
Self::A4 | Self::A4F | Self::A4E,
|
|
706
|
+
ValidationError::EmbeddedFile(EmbedError::MissingDescription, _),
|
|
707
|
+
) => self == Self::A4,
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
fn requires_codepoint_mappings(self) -> bool {
|
|
712
|
+
match self {
|
|
713
|
+
Self::A1_A
|
|
714
|
+
| Self::A2_A
|
|
715
|
+
| Self::A2_U
|
|
716
|
+
| Self::A3_A
|
|
717
|
+
| Self::A3_U
|
|
718
|
+
| Self::A4
|
|
719
|
+
| Self::A4F
|
|
720
|
+
| Self::A4E => true,
|
|
721
|
+
Self::A1_B | Self::A2_B | Self::A3_B => false,
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
fn requires_no_device_cs(self) -> bool {
|
|
726
|
+
match self {
|
|
727
|
+
Self::A1_A
|
|
728
|
+
| Self::A1_B
|
|
729
|
+
| Self::A2_A
|
|
730
|
+
| Self::A2_B
|
|
731
|
+
| Self::A2_U
|
|
732
|
+
| Self::A3_A
|
|
733
|
+
| Self::A3_B
|
|
734
|
+
| Self::A3_U
|
|
735
|
+
| Self::A4
|
|
736
|
+
| Self::A4F
|
|
737
|
+
| Self::A4E => true,
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
fn requires_annotation_flags(self) -> bool {
|
|
742
|
+
match self {
|
|
743
|
+
Self::A1_A
|
|
744
|
+
| Self::A1_B
|
|
745
|
+
| Self::A2_A
|
|
746
|
+
| Self::A2_B
|
|
747
|
+
| Self::A2_U
|
|
748
|
+
| Self::A3_A
|
|
749
|
+
| Self::A3_B
|
|
750
|
+
| Self::A3_U
|
|
751
|
+
| Self::A4
|
|
752
|
+
| Self::A4F
|
|
753
|
+
| Self::A4E => true,
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
fn requires_tagging(self) -> bool {
|
|
758
|
+
match self {
|
|
759
|
+
Self::A1_A | Self::A2_A | Self::A3_A => true,
|
|
760
|
+
Self::A1_B
|
|
761
|
+
| Self::A2_B
|
|
762
|
+
| Self::A2_U
|
|
763
|
+
| Self::A3_B
|
|
764
|
+
| Self::A3_U
|
|
765
|
+
| Self::A4
|
|
766
|
+
| Self::A4F
|
|
767
|
+
| Self::A4E => false,
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
fn requires_xmp_metadata(self) -> bool {
|
|
772
|
+
match self {
|
|
773
|
+
Self::A1_A
|
|
774
|
+
| Self::A1_B
|
|
775
|
+
| Self::A2_A
|
|
776
|
+
| Self::A2_B
|
|
777
|
+
| Self::A2_U
|
|
778
|
+
| Self::A3_A
|
|
779
|
+
| Self::A3_B
|
|
780
|
+
| Self::A3_U
|
|
781
|
+
| Self::A4
|
|
782
|
+
| Self::A4F
|
|
783
|
+
| Self::A4E => true,
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
fn requires_xmp_metadata_extension_schema(self) -> bool {
|
|
788
|
+
match self {
|
|
789
|
+
Self::A1_A
|
|
790
|
+
| Self::A1_B
|
|
791
|
+
| Self::A2_A
|
|
792
|
+
| Self::A2_B
|
|
793
|
+
| Self::A2_U
|
|
794
|
+
| Self::A3_A
|
|
795
|
+
| Self::A3_B
|
|
796
|
+
| Self::A3_U => true,
|
|
797
|
+
// Clause 6.7.2.3 of PDF/A-4 recommends ("should") a RELAX NG
|
|
798
|
+
// definition of its metadata contents to be embedded as an
|
|
799
|
+
// associated file. It no longer uses the inline schema definition
|
|
800
|
+
// using the "pdfaSchema" namespaces for extension schemata.
|
|
801
|
+
Self::A4 | Self::A4F | Self::A4E => false,
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
fn prohibits_instance_id_in_xmp_metadata(self) -> bool {
|
|
806
|
+
match self {
|
|
807
|
+
Self::A1_A | Self::A1_B => true,
|
|
808
|
+
Self::A2_A
|
|
809
|
+
| Self::A2_B
|
|
810
|
+
| Self::A2_U
|
|
811
|
+
| Self::A3_A
|
|
812
|
+
| Self::A3_B
|
|
813
|
+
| Self::A3_U
|
|
814
|
+
| Self::A4
|
|
815
|
+
| Self::A4F
|
|
816
|
+
| Self::A4E => false,
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
fn requires_file_provenance_information(self) -> bool {
|
|
821
|
+
match self {
|
|
822
|
+
Self::A1_A
|
|
823
|
+
| Self::A1_B
|
|
824
|
+
| Self::A2_A
|
|
825
|
+
| Self::A2_B
|
|
826
|
+
| Self::A2_U
|
|
827
|
+
| Self::A3_A
|
|
828
|
+
| Self::A3_B
|
|
829
|
+
| Self::A3_U
|
|
830
|
+
| Self::A4
|
|
831
|
+
| Self::A4F
|
|
832
|
+
| Self::A4E => true,
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
fn prohibits_info_dict(self) -> bool {
|
|
837
|
+
match self {
|
|
838
|
+
Self::A1_A
|
|
839
|
+
| Self::A1_B
|
|
840
|
+
| Self::A2_A
|
|
841
|
+
| Self::A2_B
|
|
842
|
+
| Self::A2_U
|
|
843
|
+
| Self::A3_A
|
|
844
|
+
| Self::A3_B
|
|
845
|
+
| Self::A3_U => false,
|
|
846
|
+
Self::A4 | Self::A4F | Self::A4E => true,
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
fn requires_binary_header(self) -> bool {
|
|
851
|
+
match self {
|
|
852
|
+
Self::A1_A
|
|
853
|
+
| Self::A1_B
|
|
854
|
+
| Self::A2_A
|
|
855
|
+
| Self::A2_B
|
|
856
|
+
| Self::A2_U
|
|
857
|
+
| Self::A3_A
|
|
858
|
+
| Self::A3_B
|
|
859
|
+
| Self::A3_U
|
|
860
|
+
| Self::A4
|
|
861
|
+
| Self::A4F
|
|
862
|
+
| Self::A4E => true,
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
fn requires_embedded_files_when_empty(self) -> bool {
|
|
867
|
+
match self {
|
|
868
|
+
Self::A1_A
|
|
869
|
+
| Self::A1_B
|
|
870
|
+
| Self::A2_A
|
|
871
|
+
| Self::A2_B
|
|
872
|
+
| Self::A2_U
|
|
873
|
+
| Self::A3_A
|
|
874
|
+
| Self::A3_B
|
|
875
|
+
| Self::A3_U
|
|
876
|
+
| Self::A4
|
|
877
|
+
| Self::A4E => false,
|
|
878
|
+
Self::A4F => true,
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
/// Whether this standard explicitly specifies the `/AF` key.
|
|
883
|
+
///
|
|
884
|
+
/// The `/AF` key may be supported by the underlying PDF version instead:
|
|
885
|
+
/// Starting at PDF 2.0, the key is specified by ISO 32000 and does not need
|
|
886
|
+
/// to be added by PDF/A.
|
|
887
|
+
fn specifies_associated_files(self) -> bool {
|
|
888
|
+
match self {
|
|
889
|
+
Self::A3_A | Self::A3_B | Self::A3_U => true,
|
|
890
|
+
Self::A1_A
|
|
891
|
+
| Self::A1_B
|
|
892
|
+
| Self::A2_A
|
|
893
|
+
| Self::A2_B
|
|
894
|
+
| Self::A2_U
|
|
895
|
+
| Self::A4
|
|
896
|
+
| Self::A4F
|
|
897
|
+
| Self::A4E => false,
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
fn output_intent(self) -> OutputIntentSubtype<'static> {
|
|
902
|
+
match self {
|
|
903
|
+
Self::A1_A
|
|
904
|
+
| Self::A1_B
|
|
905
|
+
| Self::A2_A
|
|
906
|
+
| Self::A2_B
|
|
907
|
+
| Self::A2_U
|
|
908
|
+
| Self::A3_A
|
|
909
|
+
| Self::A3_B
|
|
910
|
+
| Self::A3_U
|
|
911
|
+
| Self::A4
|
|
912
|
+
| Self::A4F
|
|
913
|
+
| Self::A4E => OutputIntentSubtype::PDFA,
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
fn write_xmp(self, xmp: &mut XmpWriter) {
|
|
918
|
+
match self {
|
|
919
|
+
Self::A1_A => {
|
|
920
|
+
xmp.pdfa_part(1);
|
|
921
|
+
xmp.pdfa_conformance("A");
|
|
922
|
+
}
|
|
923
|
+
Self::A1_B => {
|
|
924
|
+
xmp.pdfa_part(1);
|
|
925
|
+
xmp.pdfa_conformance("B");
|
|
926
|
+
}
|
|
927
|
+
Self::A2_A => {
|
|
928
|
+
xmp.pdfa_part(2);
|
|
929
|
+
xmp.pdfa_conformance("A");
|
|
930
|
+
}
|
|
931
|
+
Self::A2_B => {
|
|
932
|
+
xmp.pdfa_part(2);
|
|
933
|
+
xmp.pdfa_conformance("B");
|
|
934
|
+
}
|
|
935
|
+
Self::A2_U => {
|
|
936
|
+
xmp.pdfa_part(2);
|
|
937
|
+
xmp.pdfa_conformance("U");
|
|
938
|
+
}
|
|
939
|
+
Self::A3_A => {
|
|
940
|
+
xmp.pdfa_part(3);
|
|
941
|
+
xmp.pdfa_conformance("A");
|
|
942
|
+
}
|
|
943
|
+
Self::A3_B => {
|
|
944
|
+
xmp.pdfa_part(3);
|
|
945
|
+
xmp.pdfa_conformance("B");
|
|
946
|
+
}
|
|
947
|
+
Self::A3_U => {
|
|
948
|
+
xmp.pdfa_part(3);
|
|
949
|
+
xmp.pdfa_conformance("U");
|
|
950
|
+
}
|
|
951
|
+
Self::A4 => {
|
|
952
|
+
xmp.pdfa_part(4);
|
|
953
|
+
xmp.pdfa_rev(2020);
|
|
954
|
+
}
|
|
955
|
+
Self::A4F => {
|
|
956
|
+
xmp.pdfa_part(4);
|
|
957
|
+
xmp.pdfa_rev(2020);
|
|
958
|
+
xmp.pdfa_conformance("F");
|
|
959
|
+
}
|
|
960
|
+
Self::A4E => {
|
|
961
|
+
xmp.pdfa_part(4);
|
|
962
|
+
xmp.pdfa_rev(2020);
|
|
963
|
+
xmp.pdfa_conformance("E");
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
fn write_xmp_extension_schema_description(
|
|
969
|
+
self,
|
|
970
|
+
extension_schemas: &mut PdfAExtSchemasWriter<'_, '_>,
|
|
971
|
+
) {
|
|
972
|
+
if !self.requires_xmp_metadata_extension_schema() {
|
|
973
|
+
return;
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
extension_schemas
|
|
977
|
+
.xmp_media_management()
|
|
978
|
+
.properties()
|
|
979
|
+
.describe_instance_id();
|
|
980
|
+
extension_schemas.pdf().properties().describe_all();
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
/// Returns a human-readable string representation of the conformance level.
|
|
984
|
+
pub fn as_str(self) -> &'static str {
|
|
985
|
+
match self {
|
|
986
|
+
Self::A1_A => "PDF/A-1a",
|
|
987
|
+
Self::A1_B => "PDF/A-1b",
|
|
988
|
+
Self::A2_A => "PDF/A-2a",
|
|
989
|
+
Self::A2_B => "PDF/A-2b",
|
|
990
|
+
Self::A2_U => "PDF/A-2u",
|
|
991
|
+
Self::A3_A => "PDF/A-3a",
|
|
992
|
+
Self::A3_B => "PDF/A-3b",
|
|
993
|
+
Self::A3_U => "PDF/A-3u",
|
|
994
|
+
Self::A4 => "PDF/A-4",
|
|
995
|
+
Self::A4F => "PDF/A-4f",
|
|
996
|
+
Self::A4E => "PDF/A-4e",
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
/// Minimum PDF version required to use this standard, if any.
|
|
1001
|
+
pub const fn min(self) -> Option<PdfVersion> {
|
|
1002
|
+
match self {
|
|
1003
|
+
// PDF/A-1 through 3 require XMP `/Metadata` streams, which require PDF 1.4.
|
|
1004
|
+
Self::A1_A | Self::A1_B => Some(PdfVersion::Pdf14),
|
|
1005
|
+
Self::A2_A | Self::A2_B | Self::A2_U => Some(PdfVersion::Pdf14),
|
|
1006
|
+
Self::A3_A | Self::A3_B | Self::A3_U => Some(PdfVersion::Pdf14),
|
|
1007
|
+
Self::A4 | Self::A4F | Self::A4E => Some(PdfVersion::Pdf20),
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
/// Maximum PDF version this standard can be used with.
|
|
1012
|
+
pub const fn max(self) -> PdfVersion {
|
|
1013
|
+
match self {
|
|
1014
|
+
Self::A1_A | Self::A1_B => PdfVersion::Pdf14,
|
|
1015
|
+
Self::A2_A | Self::A2_B | Self::A2_U | Self::A3_A | Self::A3_B | Self::A3_U => {
|
|
1016
|
+
PdfVersion::Pdf17
|
|
1017
|
+
}
|
|
1018
|
+
Self::A4 | Self::A4F | Self::A4E => PdfVersion::Pdf20,
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
/// A validator for exporting PDF documents to a specific subset of PDF.
|
|
1024
|
+
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
|
1025
|
+
#[allow(non_camel_case_types)]
|
|
1026
|
+
pub enum Accessibility {
|
|
1027
|
+
/// The validator for the PDF/UA-1 standard.
|
|
1028
|
+
///
|
|
1029
|
+
/// **Requirements**:
|
|
1030
|
+
///
|
|
1031
|
+
/// General:
|
|
1032
|
+
/// - All real content should be tagged accordingly.
|
|
1033
|
+
/// - All artifacts should be marked accordingly.
|
|
1034
|
+
/// - The tag tree should reflect the logical reading order of the
|
|
1035
|
+
/// document.
|
|
1036
|
+
/// - Information should not be conveyed by contrast, color, format
|
|
1037
|
+
/// or layout.
|
|
1038
|
+
/// - All "best practice" notes in [`TagKind`] need to be complied with.
|
|
1039
|
+
///
|
|
1040
|
+
/// Text:
|
|
1041
|
+
/// - You should make use of the `Alt`, `ActualText`, `Lang` and `Expansion` attributes
|
|
1042
|
+
/// whenever possible.
|
|
1043
|
+
/// - Usually, you can provide an empty string as `Lang` to indicate that a language is unknown.
|
|
1044
|
+
/// You should not do that in PDF/UA.
|
|
1045
|
+
/// - Stretchable characters (such as brackets, which often consist of several glyphs)
|
|
1046
|
+
/// should be marked accordingly with `ActualText`.
|
|
1047
|
+
///
|
|
1048
|
+
/// Graphics:
|
|
1049
|
+
/// - Graphics should be tagged as figures (unless they are an artifact).
|
|
1050
|
+
/// - Graphics need to be followed by a caption.
|
|
1051
|
+
/// - Graphics that possess semantic values only in combination with other graphics
|
|
1052
|
+
/// should be tagged with a single Figure tag for each figure.
|
|
1053
|
+
/// - If a more accessible representation exists, it should be used over graphics.
|
|
1054
|
+
///
|
|
1055
|
+
/// Headings:
|
|
1056
|
+
/// - Headings should be tagged as such.
|
|
1057
|
+
/// - For not strongly structured documents, H1 should be the first
|
|
1058
|
+
/// heading.
|
|
1059
|
+
///
|
|
1060
|
+
/// Tables:
|
|
1061
|
+
/// - Tables should include headers and be tagged accordingly.
|
|
1062
|
+
/// - Tables should only be used to represent content within logical row/column relationship.
|
|
1063
|
+
///
|
|
1064
|
+
/// Lists:
|
|
1065
|
+
/// - List items should be tagged with Li tags, if necessary also with
|
|
1066
|
+
/// Lbl and LBody tags.
|
|
1067
|
+
/// - Lists should only be used when the content is intended to be read
|
|
1068
|
+
/// as a list.
|
|
1069
|
+
///
|
|
1070
|
+
/// Mathematical expressions:
|
|
1071
|
+
/// - All mathematical expressions should be enclosed with
|
|
1072
|
+
/// a `Formula` tag.
|
|
1073
|
+
///
|
|
1074
|
+
/// Headers and footers:
|
|
1075
|
+
/// - Headers and footers should be marked as corresponding
|
|
1076
|
+
/// artifacts.
|
|
1077
|
+
///
|
|
1078
|
+
/// Notes and references:
|
|
1079
|
+
/// - Footnotes, endnotes, note labels and references should be
|
|
1080
|
+
/// tagged accordingly and use tagged annotations.
|
|
1081
|
+
/// - Footnotes and end notes should use the `Note` tag.
|
|
1082
|
+
///
|
|
1083
|
+
/// Navigation:
|
|
1084
|
+
/// - The document must contain an outline, and it should reflect
|
|
1085
|
+
/// the reading order of the document.
|
|
1086
|
+
/// - Page labels should be semantically appropriate.
|
|
1087
|
+
///
|
|
1088
|
+
/// Annotations:
|
|
1089
|
+
/// - Annotations should be present in the tag tree in the correct
|
|
1090
|
+
/// reading order.
|
|
1091
|
+
///
|
|
1092
|
+
/// Fonts:
|
|
1093
|
+
/// - You should only use fonts that are legally embeddable in a file for unlimited,
|
|
1094
|
+
/// universal rendering.
|
|
1095
|
+
///
|
|
1096
|
+
/// [`TagKind`]: crate::interchange::tagging::TagKind
|
|
1097
|
+
UA1,
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
impl Accessibility {
|
|
1101
|
+
fn prohibits(self, error: &ValidationError) -> bool {
|
|
1102
|
+
match (self, error) {
|
|
1103
|
+
(
|
|
1104
|
+
Self::UA1,
|
|
1105
|
+
ValidationError::ContainsNotDefGlyph(_, _, _)
|
|
1106
|
+
| ValidationError::NoCodepointMapping(_, _, _)
|
|
1107
|
+
| ValidationError::InvalidCodepointMapping(_, _, _, _)
|
|
1108
|
+
| ValidationError::RestrictedLicense(_)
|
|
1109
|
+
| ValidationError::NoDocumentTitle
|
|
1110
|
+
| ValidationError::MissingAltText(_)
|
|
1111
|
+
| ValidationError::MissingHeadingTitle
|
|
1112
|
+
| ValidationError::MissingDocumentOutline
|
|
1113
|
+
| ValidationError::MissingAnnotationAltText(_)
|
|
1114
|
+
| ValidationError::EmbeddedFile(EmbedError::MissingDescription, _)
|
|
1115
|
+
| ValidationError::MissingTagging
|
|
1116
|
+
| ValidationError::EmbeddedPDF(_)
|
|
1117
|
+
| ValidationError::RequiresNewerPdfVersion(
|
|
1118
|
+
VersionedFeature::HeaderFooterArtifactSubtypes
|
|
1119
|
+
| VersionedFeature::StructureOrderTabbing
|
|
1120
|
+
| VersionedFeature::TableHeaderScope,
|
|
1121
|
+
_,
|
|
1122
|
+
),
|
|
1123
|
+
) => true,
|
|
1124
|
+
(
|
|
1125
|
+
Self::UA1,
|
|
1126
|
+
ValidationError::TooLongString
|
|
1127
|
+
| ValidationError::TooLongName
|
|
1128
|
+
| ValidationError::TooLongArray
|
|
1129
|
+
| ValidationError::TooLongDictionary
|
|
1130
|
+
| ValidationError::TooLargeFloat
|
|
1131
|
+
| ValidationError::TooManyIndirectObjects
|
|
1132
|
+
| ValidationError::TooHighQNestingLevel
|
|
1133
|
+
| ValidationError::ContainsPostScript(_)
|
|
1134
|
+
| ValidationError::MissingCMYKProfile
|
|
1135
|
+
| ValidationError::InconsistentSeparationFallback(_)
|
|
1136
|
+
| ValidationError::UnicodePrivateArea(_, _, _, _)
|
|
1137
|
+
| ValidationError::NoDocumentLanguage
|
|
1138
|
+
| ValidationError::Transparency(_)
|
|
1139
|
+
| ValidationError::ImageInterpolation(_)
|
|
1140
|
+
| ValidationError::EmbeddedFile(
|
|
1141
|
+
EmbedError::Existence | EmbedError::MissingDate | EmbedError::MissingMimeType,
|
|
1142
|
+
_,
|
|
1143
|
+
)
|
|
1144
|
+
| ValidationError::MissingDocumentDate,
|
|
1145
|
+
) => false,
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
fn requires_codepoint_mappings(self) -> bool {
|
|
1150
|
+
match self {
|
|
1151
|
+
Self::UA1 => true,
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
fn requires_display_doc_title(self) -> bool {
|
|
1156
|
+
match self {
|
|
1157
|
+
Self::UA1 => true,
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
const fn requires_tagging(self) -> bool {
|
|
1162
|
+
true
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
fn requires_xmp_metadata(self) -> bool {
|
|
1166
|
+
match self {
|
|
1167
|
+
Self::UA1 => true,
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
fn write_xmp(self, xmp: &mut XmpWriter) {
|
|
1172
|
+
match self {
|
|
1173
|
+
Self::UA1 => {
|
|
1174
|
+
xmp.pdfua_part(1);
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
fn write_xmp_extension_schema_description(
|
|
1180
|
+
self,
|
|
1181
|
+
extension_schemas: &mut PdfAExtSchemasWriter<'_, '_>,
|
|
1182
|
+
) {
|
|
1183
|
+
// Needs to be updated if [`Self::write_xmp`] gains more properties.
|
|
1184
|
+
extension_schemas.pdfua_id().properties().describe_part();
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
/// Returns a human-readable string representation of the accessibility level.
|
|
1188
|
+
pub fn as_str(self) -> &'static str {
|
|
1189
|
+
match self {
|
|
1190
|
+
Self::UA1 => "PDF/UA-1",
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
/// Minimum PDF version required to use this standard, if any.
|
|
1195
|
+
pub const fn min(self) -> Option<PdfVersion> {
|
|
1196
|
+
match self {
|
|
1197
|
+
// PDF/UA-1 requires Tagged PDF and XMP `/Metadata` streams, which both require PDF 1.4.
|
|
1198
|
+
Self::UA1 => Some(PdfVersion::Pdf14),
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
/// Maximum PDF version this standard can be used with.
|
|
1203
|
+
pub const fn max(self) -> PdfVersion {
|
|
1204
|
+
match self {
|
|
1205
|
+
// PDF/UA-1 is specified against PDF 1.7.
|
|
1206
|
+
Self::UA1 => PdfVersion::Pdf17,
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
#[derive(Debug, Default, Clone, PartialEq)]
|
|
1212
|
+
pub(crate) struct ValidationStore {
|
|
1213
|
+
/// Maps from the name of a Separation colorant to a hash of its fallback
|
|
1214
|
+
/// color. Used to track that a name is only ever matched with a single
|
|
1215
|
+
/// fallback color. Since Krilla manages the `tintTransform` functions,
|
|
1216
|
+
/// those are always equivalent.
|
|
1217
|
+
separation_fallback_map: HashMap<SeparationColorant, RegularColor>,
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
impl ValidationStore {
|
|
1221
|
+
pub(crate) fn new() -> Self {
|
|
1222
|
+
Default::default()
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
/// Register a colorant and its fallback and raise an error if it already
|
|
1226
|
+
/// exists.
|
|
1227
|
+
pub(crate) fn validate_separation(
|
|
1228
|
+
&mut self,
|
|
1229
|
+
separation: &SeparationSpace,
|
|
1230
|
+
) -> Result<(), ValidationError> {
|
|
1231
|
+
if self
|
|
1232
|
+
.separation_fallback_map
|
|
1233
|
+
.entry(separation.colorant.clone())
|
|
1234
|
+
.or_insert(separation.fallback)
|
|
1235
|
+
== &separation.fallback
|
|
1236
|
+
{
|
|
1237
|
+
Ok(())
|
|
1238
|
+
} else {
|
|
1239
|
+
Err(ValidationError::InconsistentSeparationFallback(
|
|
1240
|
+
separation.colorant.clone(),
|
|
1241
|
+
))
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
}
|