1use super::*;
2use crate::ast::{PdfDocument, PdfVersion};
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub enum PdfALevel {
7 PdfA1a,
8 PdfA1b,
9 PdfA2a,
10 PdfA2b,
11 PdfA2u,
12 PdfA3a,
13 PdfA3b,
14 PdfA3u,
15}
16
17impl PdfALevel {
18 pub fn as_str(&self) -> &'static str {
19 match self {
20 PdfALevel::PdfA1a => "PDF/A-1a",
21 PdfALevel::PdfA1b => "PDF/A-1b",
22 PdfALevel::PdfA2a => "PDF/A-2a",
23 PdfALevel::PdfA2b => "PDF/A-2b",
24 PdfALevel::PdfA2u => "PDF/A-2u",
25 PdfALevel::PdfA3a => "PDF/A-3a",
26 PdfALevel::PdfA3b => "PDF/A-3b",
27 PdfALevel::PdfA3u => "PDF/A-3u",
28 }
29 }
30
31 pub fn requires_tagging(&self) -> bool {
32 matches!(
33 self,
34 PdfALevel::PdfA1a | PdfALevel::PdfA2a | PdfALevel::PdfA3a
35 )
36 }
37
38 pub fn allows_transparency(&self) -> bool {
39 !matches!(self, PdfALevel::PdfA1a | PdfALevel::PdfA1b)
40 }
41
42 pub fn allows_embedded_files(&self) -> bool {
43 matches!(
44 self,
45 PdfALevel::PdfA3a | PdfALevel::PdfA3b | PdfALevel::PdfA3u
46 )
47 }
48}
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52pub enum PdfXLevel {
53 PdfX1a,
54 PdfX3,
55 PdfX4,
56 PdfX4p,
57 PdfX5g,
58 PdfX5n,
59 PdfX5pg,
60}
61
62impl PdfXLevel {
63 pub fn as_str(&self) -> &'static str {
64 match self {
65 PdfXLevel::PdfX1a => "PDF/X-1a",
66 PdfXLevel::PdfX3 => "PDF/X-3",
67 PdfXLevel::PdfX4 => "PDF/X-4",
68 PdfXLevel::PdfX4p => "PDF/X-4p",
69 PdfXLevel::PdfX5g => "PDF/X-5g",
70 PdfXLevel::PdfX5n => "PDF/X-5n",
71 PdfXLevel::PdfX5pg => "PDF/X-5pg",
72 }
73 }
74}
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq)]
78pub enum PdfUALevel {
79 PdfUA1,
80 PdfUA2,
81}
82
83impl PdfUALevel {
84 pub fn as_str(&self) -> &'static str {
85 match self {
86 PdfUALevel::PdfUA1 => "PDF/UA-1",
87 PdfUALevel::PdfUA2 => "PDF/UA-2",
88 }
89 }
90}
91
92pub struct Pdf20Schema;
94
95impl Default for Pdf20Schema {
96 fn default() -> Self {
97 Self::new()
98 }
99}
100
101impl Pdf20Schema {
102 pub fn new() -> Self {
103 Self
104 }
105}
106
107impl PdfSchema for Pdf20Schema {
108 fn name(&self) -> &str {
109 "PDF-2.0"
110 }
111
112 fn version(&self) -> &str {
113 "2.0"
114 }
115
116 fn description(&self) -> &str {
117 "ISO 32000-2 PDF 2.0 standard compliance"
118 }
119
120 fn reference_url(&self) -> Option<&str> {
121 Some("https://www.iso.org/standard/63534.html")
122 }
123
124 fn supports_pdf_version(&self, version: &PdfVersion) -> bool {
125 version.major >= 2 || (version.major == 1 && version.minor >= 4)
126 }
127
128 fn validate(&self, document: &PdfDocument) -> ValidationReport {
129 let mut report = ValidationReport::new(self.name().to_string(), self.version().to_string());
130 let context = ValidationContext::new(document, &mut report);
131
132 for constraint in self.get_constraints() {
134 if let Some(reference) = constraint.iso_reference() {
135 context
136 .report
137 .metadata
138 .insert(format!("iso.{}", constraint.name()), reference.to_string());
139 }
140 constraint.check(document, context.report);
141 }
142
143 context.report.finalize();
144 report
145 }
146
147 fn get_constraints(&self) -> Vec<Box<dyn SchemaConstraint>> {
148 vec![
149 Box::new(HasCatalogConstraint),
150 Box::new(HasPagesTreeConstraint),
151 Box::new(CatalogHasPagesConstraint),
152 Box::new(PageCountConsistencyConstraint),
153 Box::new(ValidStructureConstraint),
154 Box::new(HasTrailerRootConstraint),
155 Box::new(HasTrailerSizeConstraint),
156 Box::new(TrailerIdConstraint),
157 Box::new(CatalogVersionConstraint),
158 Box::new(HasXRefEntriesConstraint),
159 Box::new(TrailerSizeConsistencyConstraint),
160 Box::new(MetadataStreamConstraint),
161 Box::new(FontCMapEncodingConstraint),
162 ]
163 }
164}
165
166pub struct PdfASchema {
168 level: PdfALevel,
169}
170
171impl PdfASchema {
172 pub fn new(level: PdfALevel) -> Self {
173 Self { level }
174 }
175}
176
177impl PdfSchema for PdfASchema {
178 fn name(&self) -> &str {
179 self.level.as_str()
180 }
181
182 fn version(&self) -> &str {
183 match self.level {
184 PdfALevel::PdfA1a | PdfALevel::PdfA1b => "1.4",
185 PdfALevel::PdfA2a | PdfALevel::PdfA2b | PdfALevel::PdfA2u => "1.7",
186 PdfALevel::PdfA3a | PdfALevel::PdfA3b | PdfALevel::PdfA3u => "1.7",
187 }
188 }
189
190 fn description(&self) -> &str {
191 "ISO 19005 PDF/A long-term archival standard"
192 }
193
194 fn reference_url(&self) -> Option<&str> {
195 Some("https://www.iso.org/standard/38920.html")
196 }
197
198 fn supports_pdf_version(&self, version: &PdfVersion) -> bool {
199 let required_version = self.version().parse::<f32>().unwrap_or(1.4);
200 let doc_version = format!("{}.{}", version.major, version.minor)
201 .parse::<f32>()
202 .unwrap_or(0.0);
203 doc_version >= required_version
204 }
205
206 fn validate(&self, document: &PdfDocument) -> ValidationReport {
207 if self.level == PdfALevel::PdfA1b {
208 let mut report = crate::validation::pdfa::PdfA1bValidator::new().validate(document);
209 report.schema_name = self.name().to_string();
210 report.schema_version = self.version().to_string();
211 return report;
212 }
213
214 let mut report = ValidationReport::new(self.name().to_string(), self.version().to_string());
215 let context = ValidationContext::new(document, &mut report);
216
217 for constraint in self.get_constraints() {
219 constraint.check(document, context.report);
220 }
221
222 context.report.finalize();
223 report
224 }
225
226 fn get_constraints(&self) -> Vec<Box<dyn SchemaConstraint>> {
227 let mut constraints: Vec<Box<dyn SchemaConstraint>> = vec![
228 Box::new(HasCatalogConstraint),
229 Box::new(HasPagesTreeConstraint),
230 Box::new(NoEncryptionConstraint),
231 Box::new(NoJavaScriptConstraint),
232 Box::new(NoExternalReferencesConstraint),
233 Box::new(EmbeddedFontsConstraint),
234 ];
235
236 if self.level.requires_tagging() {
237 constraints.push(Box::new(TaggedStructureConstraint));
238 }
239
240 if !self.level.allows_transparency() {
241 constraints.push(Box::new(NoTransparencyConstraint));
242 }
243
244 if !self.level.allows_embedded_files() {
245 constraints.push(Box::new(NoEmbeddedFilesConstraint));
246 }
247
248 constraints
249 }
250}
251
252pub struct PdfXSchema {
254 level: PdfXLevel,
255}
256
257impl PdfXSchema {
258 pub fn new(level: PdfXLevel) -> Self {
259 Self { level }
260 }
261}
262
263impl PdfSchema for PdfXSchema {
264 fn name(&self) -> &str {
265 self.level.as_str()
266 }
267
268 fn version(&self) -> &str {
269 match self.level {
270 PdfXLevel::PdfX1a => "1.3",
271 PdfXLevel::PdfX3 => "1.3",
272 PdfXLevel::PdfX4 | PdfXLevel::PdfX4p => "1.6",
273 PdfXLevel::PdfX5g | PdfXLevel::PdfX5n | PdfXLevel::PdfX5pg => "1.6",
274 }
275 }
276
277 fn description(&self) -> &str {
278 "ISO 15930 PDF/X graphics exchange standard"
279 }
280
281 fn supports_pdf_version(&self, version: &PdfVersion) -> bool {
282 let required_version = self.version().parse::<f32>().unwrap_or(1.3);
283 let doc_version = format!("{}.{}", version.major, version.minor)
284 .parse::<f32>()
285 .unwrap_or(0.0);
286 doc_version >= required_version
287 }
288
289 fn validate(&self, document: &PdfDocument) -> ValidationReport {
290 let mut report = ValidationReport::new(self.name().to_string(), self.version().to_string());
291 let context = ValidationContext::new(document, &mut report);
292
293 for constraint in self.get_constraints() {
294 constraint.check(document, context.report);
295 }
296
297 context.report.finalize();
298 report
299 }
300
301 fn get_constraints(&self) -> Vec<Box<dyn SchemaConstraint>> {
302 vec![
303 Box::new(HasCatalogConstraint),
304 Box::new(HasPagesTreeConstraint),
305 Box::new(NoEncryptionConstraint),
306 Box::new(NoJavaScriptConstraint),
307 Box::new(EmbeddedFontsConstraint),
308 Box::new(ColorSpaceConstraint),
309 Box::new(TrimBoxConstraint),
310 ]
311 }
312}
313
314pub struct PdfUASchema {
316 level: PdfUALevel,
317}
318
319impl PdfUASchema {
320 pub fn new(level: PdfUALevel) -> Self {
321 Self { level }
322 }
323}
324
325impl PdfSchema for PdfUASchema {
326 fn name(&self) -> &str {
327 self.level.as_str()
328 }
329
330 fn version(&self) -> &str {
331 match self.level {
332 PdfUALevel::PdfUA1 => "1.7",
333 PdfUALevel::PdfUA2 => "2.0",
334 }
335 }
336
337 fn description(&self) -> &str {
338 "ISO 14289 PDF/UA universal accessibility standard"
339 }
340
341 fn supports_pdf_version(&self, version: &PdfVersion) -> bool {
342 let required_version = self.version().parse::<f32>().unwrap_or(1.7);
343 let doc_version = format!("{}.{}", version.major, version.minor)
344 .parse::<f32>()
345 .unwrap_or(0.0);
346 doc_version >= required_version
347 }
348
349 fn validate(&self, document: &PdfDocument) -> ValidationReport {
350 let mut report = ValidationReport::new(self.name().to_string(), self.version().to_string());
351 let context = ValidationContext::new(document, &mut report);
352
353 for constraint in self.get_constraints() {
354 constraint.check(document, context.report);
355 }
356
357 context.report.finalize();
358 report
359 }
360
361 fn get_constraints(&self) -> Vec<Box<dyn SchemaConstraint>> {
362 vec![
363 Box::new(HasCatalogConstraint),
364 Box::new(HasPagesTreeConstraint),
365 Box::new(TaggedStructureConstraint),
366 Box::new(AccessibilityMetadataConstraint),
367 Box::new(AltTextConstraint),
368 Box::new(LanguageSpecificationConstraint),
369 Box::new(LogicalReadingOrderConstraint),
370 ]
371 }
372}