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,503 @@
|
|
|
1
|
+
use pdf_writer::{Chunk, Finish, Name, Pdf, Ref, Str, TextStr};
|
|
2
|
+
use std::collections::HashMap;
|
|
3
|
+
use std::sync::OnceLock;
|
|
4
|
+
use xmp_writer::{RenditionClass, XmpWriter};
|
|
5
|
+
|
|
6
|
+
use crate::configure::{PdfVersion, ValidationError};
|
|
7
|
+
use crate::error::KrillaResult;
|
|
8
|
+
use crate::interchange::metadata::Metadata;
|
|
9
|
+
use crate::metadata::PageLayout;
|
|
10
|
+
use crate::serialize::SerializeContext;
|
|
11
|
+
use crate::util::{stable_hash_base64, Deferred};
|
|
12
|
+
|
|
13
|
+
type DChunk = Deferred<Chunk>;
|
|
14
|
+
|
|
15
|
+
/// Collects all chunks that we create while building
|
|
16
|
+
/// the PDF and then writes them out in an orderly manner.
|
|
17
|
+
pub(crate) struct ChunkContainer {
|
|
18
|
+
pub(crate) streams: StreamChunks,
|
|
19
|
+
pub(crate) mixed: MixedChunks,
|
|
20
|
+
pub(crate) metadata: Option<Metadata>,
|
|
21
|
+
pub(crate) non_stream: NonStreamChunks,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
pub(crate) struct StreamChunks {
|
|
25
|
+
pub(crate) fonts: Vec<Chunk>,
|
|
26
|
+
pub(crate) shading_functions: Vec<Chunk>,
|
|
27
|
+
pub(crate) patterns: Vec<Chunk>,
|
|
28
|
+
pub(crate) pages: Vec<DChunk>,
|
|
29
|
+
pub(crate) embedded_files: Vec<Chunk>,
|
|
30
|
+
pub(crate) icc_profiles: Vec<Chunk>,
|
|
31
|
+
pub(crate) x_objects: Vec<Chunk>,
|
|
32
|
+
pub(crate) images: Vec<Deferred<KrillaResult<Chunk>>>,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
pub(crate) struct MixedChunks {
|
|
36
|
+
pub(crate) embedded_pdfs: Vec<Deferred<KrillaResult<EmbeddedPdfChunk>>>,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
pub(crate) struct NonStreamChunks {
|
|
40
|
+
pub(crate) page_tree: Option<(Ref, Chunk)>,
|
|
41
|
+
pub(crate) outline: Option<(Ref, Chunk)>,
|
|
42
|
+
pub(crate) page_label_tree: Option<(Ref, Chunk)>,
|
|
43
|
+
pub(crate) destination_profiles: Option<(Ref, Chunk)>,
|
|
44
|
+
pub(crate) struct_tree_root: Option<(Ref, Chunk)>,
|
|
45
|
+
pub(crate) struct_elements: Option<Chunk>,
|
|
46
|
+
pub(crate) page_labels: Chunk,
|
|
47
|
+
pub(crate) annotations: Chunk,
|
|
48
|
+
pub(crate) color_spaces: Chunk,
|
|
49
|
+
pub(crate) destinations: Chunk,
|
|
50
|
+
pub(crate) ext_g_states: Chunk,
|
|
51
|
+
pub(crate) resource_dictionaries: Chunk,
|
|
52
|
+
pub(crate) masks: Chunk,
|
|
53
|
+
pub(crate) fonts: Chunk,
|
|
54
|
+
pub(crate) shading_functions: Chunk,
|
|
55
|
+
pub(crate) patterns: Chunk,
|
|
56
|
+
pub(crate) pages: Chunk,
|
|
57
|
+
pub(crate) embedded_files: Chunk,
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
impl ChunkContainer {
|
|
61
|
+
pub(crate) fn new(sc: &SerializeContext) -> Self {
|
|
62
|
+
Self {
|
|
63
|
+
streams: StreamChunks {
|
|
64
|
+
fonts: vec![],
|
|
65
|
+
shading_functions: vec![],
|
|
66
|
+
patterns: vec![],
|
|
67
|
+
pages: vec![],
|
|
68
|
+
embedded_files: vec![],
|
|
69
|
+
icc_profiles: vec![],
|
|
70
|
+
x_objects: vec![],
|
|
71
|
+
images: vec![],
|
|
72
|
+
},
|
|
73
|
+
mixed: MixedChunks {
|
|
74
|
+
embedded_pdfs: vec![],
|
|
75
|
+
},
|
|
76
|
+
metadata: None,
|
|
77
|
+
non_stream: NonStreamChunks {
|
|
78
|
+
page_tree: None,
|
|
79
|
+
outline: None,
|
|
80
|
+
page_label_tree: None,
|
|
81
|
+
destination_profiles: None,
|
|
82
|
+
struct_tree_root: None,
|
|
83
|
+
struct_elements: None,
|
|
84
|
+
page_labels: sc.new_chunk(),
|
|
85
|
+
annotations: sc.new_chunk(),
|
|
86
|
+
color_spaces: sc.new_chunk(),
|
|
87
|
+
destinations: sc.new_chunk(),
|
|
88
|
+
ext_g_states: sc.new_chunk(),
|
|
89
|
+
resource_dictionaries: sc.new_chunk(),
|
|
90
|
+
masks: sc.new_chunk(),
|
|
91
|
+
fonts: sc.new_chunk(),
|
|
92
|
+
shading_functions: sc.new_chunk(),
|
|
93
|
+
patterns: sc.new_chunk(),
|
|
94
|
+
pages: sc.new_chunk(),
|
|
95
|
+
embedded_files: sc.new_chunk(),
|
|
96
|
+
},
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
pub(crate) fn finish(self, sc: &mut SerializeContext) -> KrillaResult<Pdf> {
|
|
101
|
+
let mut remapped_ref = Ref::new(1);
|
|
102
|
+
let mut remapper = HashMap::new();
|
|
103
|
+
|
|
104
|
+
// Allows us to estimate the capacity we will need for the new PDF.
|
|
105
|
+
let mut chunks_byte_len = 0;
|
|
106
|
+
|
|
107
|
+
// This traverses the chunks in the order that we will write them to the PDF and assigns new
|
|
108
|
+
// references as we go. This gives us the advantage that the PDF will be numbered with
|
|
109
|
+
// monotonically increasing numbers, which, while it is not a strict requirement for a valid
|
|
110
|
+
// PDF, makes it a lot cleaner and might make implementing features like object streams
|
|
111
|
+
// easier down the road.
|
|
112
|
+
//
|
|
113
|
+
// It also allows us to estimate the capacity we will need for the new PDF.
|
|
114
|
+
self.visit(sc, &mut |chunk| {
|
|
115
|
+
for object_ref in chunk.refs() {
|
|
116
|
+
let existing = remapper.insert(object_ref, remapped_ref.bump());
|
|
117
|
+
debug_assert!(existing.is_none());
|
|
118
|
+
}
|
|
119
|
+
chunks_byte_len += chunk.len();
|
|
120
|
+
})?;
|
|
121
|
+
|
|
122
|
+
// Chunk length is not an exact number because the length might change as we renumber,
|
|
123
|
+
// so we add a bit of a padding by multiplying with 1.1. The 200 is additional padding
|
|
124
|
+
// for the document catalog. This hopefully allows us to avoid re-alloactions in the general
|
|
125
|
+
// case, and thus give us better performance.
|
|
126
|
+
let capacity = (chunks_byte_len as f32 * 1.1 + 200.0) as usize;
|
|
127
|
+
let mut pdf = sc.new_pdf_with_capacity(capacity);
|
|
128
|
+
sc.serialize_settings().pdf_version().set_version(&mut pdf);
|
|
129
|
+
|
|
130
|
+
if sc.serialize_settings().ascii_compatible
|
|
131
|
+
&& !sc
|
|
132
|
+
.serialize_settings()
|
|
133
|
+
.validators()
|
|
134
|
+
.requires_binary_header()
|
|
135
|
+
{
|
|
136
|
+
pdf.set_binary_marker(b"AAAA")
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Write the chunks in all the fields.
|
|
140
|
+
self.visit(sc, &mut |chunk| {
|
|
141
|
+
chunk.renumber_into(&mut pdf, |old| remapper[&old]);
|
|
142
|
+
})?;
|
|
143
|
+
|
|
144
|
+
let missing_title = self.metadata.as_ref().is_none_or(|m| m.title.is_none());
|
|
145
|
+
|
|
146
|
+
if missing_title {
|
|
147
|
+
sc.register_validation_error(ValidationError::NoDocumentTitle);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Write the PDF document info metadata.
|
|
151
|
+
if let Some(metadata) = &self.metadata {
|
|
152
|
+
metadata.serialize_document_info(
|
|
153
|
+
&mut remapped_ref,
|
|
154
|
+
&mut pdf,
|
|
155
|
+
sc.serialize_settings().configuration,
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
let extensions = self
|
|
160
|
+
.metadata
|
|
161
|
+
.as_ref()
|
|
162
|
+
.map_or(&[][..], |m| m.xmp_extensions.as_slice());
|
|
163
|
+
let language = self.metadata.as_ref().and_then(|m| m.language.as_deref());
|
|
164
|
+
let identity = self
|
|
165
|
+
.metadata
|
|
166
|
+
.as_ref()
|
|
167
|
+
.and_then(|m| m.document_id.as_deref());
|
|
168
|
+
let instance_id = if extensions.is_empty() && language.is_none() && identity.is_none() {
|
|
169
|
+
stable_hash_base64(pdf.as_bytes())
|
|
170
|
+
} else {
|
|
171
|
+
// These fields are not present in the Info dictionary hashed above.
|
|
172
|
+
stable_hash_base64(&(pdf.as_bytes(), extensions, language, identity))
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
let document_id = if let Some(metadata) = &self.metadata {
|
|
176
|
+
if let Some(document_id) = &metadata.document_id {
|
|
177
|
+
stable_hash_base64(&(sc.serialize_settings().pdf_version().as_str(), document_id))
|
|
178
|
+
} else if metadata.title.is_some() && metadata.authors.is_some() {
|
|
179
|
+
stable_hash_base64(&(
|
|
180
|
+
sc.serialize_settings().pdf_version().as_str(),
|
|
181
|
+
&metadata.title,
|
|
182
|
+
&metadata.authors,
|
|
183
|
+
))
|
|
184
|
+
} else {
|
|
185
|
+
instance_id.clone()
|
|
186
|
+
}
|
|
187
|
+
} else {
|
|
188
|
+
instance_id.clone()
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
let mut xmp = XmpWriter::new();
|
|
192
|
+
if let Some(metadata) = &self.metadata {
|
|
193
|
+
metadata.serialize_xmp_metadata(&mut xmp, sc, &instance_id);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
let settings = sc.serialize_settings();
|
|
197
|
+
let validators = settings.validators();
|
|
198
|
+
validators.write_xmp(&mut xmp, extensions);
|
|
199
|
+
|
|
200
|
+
xmp.num_pages(sc.page_infos().len() as u32);
|
|
201
|
+
xmp.format("application/pdf");
|
|
202
|
+
xmp.instance_id(&instance_id);
|
|
203
|
+
xmp.document_id(&document_id);
|
|
204
|
+
pdf.set_file_id((
|
|
205
|
+
document_id.as_bytes().to_vec(),
|
|
206
|
+
instance_id.as_bytes().to_vec(),
|
|
207
|
+
));
|
|
208
|
+
|
|
209
|
+
xmp.rendition_class(RenditionClass::Proof);
|
|
210
|
+
sc.serialize_settings().pdf_version().write_xmp(&mut xmp);
|
|
211
|
+
|
|
212
|
+
let named_destinations = sc.global_objects.named_destinations.take();
|
|
213
|
+
let embedded_files = sc.global_objects.embedded_files.take();
|
|
214
|
+
|
|
215
|
+
// We only write a catalog if a page tree exists. Every valid PDF must have one
|
|
216
|
+
// and krilla ensures that there always is one, but for snapshot tests, it can be
|
|
217
|
+
// useful to not write a document catalog if we don't actually need it for the test.
|
|
218
|
+
if self.non_stream.page_tree.is_some()
|
|
219
|
+
|| self.non_stream.outline.is_some()
|
|
220
|
+
|| self.non_stream.page_label_tree.is_some()
|
|
221
|
+
|| self.non_stream.destination_profiles.is_some()
|
|
222
|
+
|| self.non_stream.struct_tree_root.is_some()
|
|
223
|
+
{
|
|
224
|
+
let meta_ref = if sc.serialize_settings().xmp_metadata {
|
|
225
|
+
let meta_ref = remapped_ref.bump();
|
|
226
|
+
let xmp_buf = xmp.finish(None);
|
|
227
|
+
pdf.stream(meta_ref, xmp_buf.as_bytes())
|
|
228
|
+
.pair(Name(b"Type"), Name(b"Metadata"))
|
|
229
|
+
.pair(Name(b"Subtype"), Name(b"XML"));
|
|
230
|
+
Some(meta_ref)
|
|
231
|
+
} else {
|
|
232
|
+
None
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
let catalog_ref = remapped_ref.bump();
|
|
236
|
+
|
|
237
|
+
let mut catalog = pdf.catalog(catalog_ref);
|
|
238
|
+
|
|
239
|
+
if let Some(pt) = &self.non_stream.page_tree {
|
|
240
|
+
catalog.pages(remapper[&pt.0]);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if let Some(meta_ref) = meta_ref {
|
|
244
|
+
catalog.metadata(meta_ref);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if let Some(pl) = &self.non_stream.page_label_tree {
|
|
248
|
+
catalog.pair(Name(b"PageLabels"), remapper[&pl.0]);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if let Some(oi) = &self.non_stream.destination_profiles {
|
|
252
|
+
catalog.pair(Name(b"OutputIntents"), remapper[&oi.0]);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if let Some(lang) = self.metadata.as_ref().and_then(|m| m.language.as_ref()) {
|
|
256
|
+
catalog.lang(TextStr(lang));
|
|
257
|
+
} else {
|
|
258
|
+
sc.register_validation_error(ValidationError::NoDocumentLanguage);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if let Some(st) = &self.non_stream.struct_tree_root {
|
|
262
|
+
catalog.pair(Name(b"StructTreeRoot"), remapper[&st.0]);
|
|
263
|
+
let mut mark_info = catalog.mark_info();
|
|
264
|
+
mark_info.marked(true);
|
|
265
|
+
if sc.serialize_settings().pdf_version() >= PdfVersion::Pdf16
|
|
266
|
+
&& sc.serialize_settings().pdf_version() < PdfVersion::Pdf20
|
|
267
|
+
{
|
|
268
|
+
// We always set suspects to false because it's required by PDF/UA.
|
|
269
|
+
mark_info.suspects(false);
|
|
270
|
+
}
|
|
271
|
+
mark_info.finish();
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
let write_doc_title = sc
|
|
275
|
+
.serialize_settings()
|
|
276
|
+
.validators()
|
|
277
|
+
.requires_display_doc_title();
|
|
278
|
+
let text_direction = self.metadata.as_ref().and_then(|m| m.text_direction);
|
|
279
|
+
|
|
280
|
+
if write_doc_title || text_direction.is_some() {
|
|
281
|
+
let mut vp = catalog.viewer_preferences();
|
|
282
|
+
|
|
283
|
+
if write_doc_title {
|
|
284
|
+
vp.display_doc_title(true);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if let Some(dir) = text_direction {
|
|
288
|
+
vp.direction(dir.to_pdf());
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
let page_layout = self.metadata.as_ref().and_then(|m| m.page_layout);
|
|
293
|
+
if let Some(layout) = page_layout {
|
|
294
|
+
// TwoPageLeft and TwoPageRight are only available PDF 1.5+
|
|
295
|
+
if sc.serialize_settings().pdf_version() >= PdfVersion::Pdf15
|
|
296
|
+
|| !matches!(layout, PageLayout::TwoPageLeft | PageLayout::TwoPageRight)
|
|
297
|
+
{
|
|
298
|
+
catalog.page_layout(layout.to_pdf());
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if let Some(ol) = &self.non_stream.outline {
|
|
303
|
+
catalog.outlines(remapper[&ol.0]);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
let settings = sc.serialize_settings();
|
|
307
|
+
let validators = settings.validators();
|
|
308
|
+
let write_embedded_files = self.non_stream.embedded_files.len() != 0
|
|
309
|
+
|| validators.requires_embedded_files_when_empty();
|
|
310
|
+
|
|
311
|
+
if !named_destinations.is_empty() || write_embedded_files {
|
|
312
|
+
// Cannot use pdf-writer API here because it requires Ref's, while
|
|
313
|
+
// we write our destinations directly into the array.
|
|
314
|
+
let mut names = catalog.names();
|
|
315
|
+
|
|
316
|
+
if !named_destinations.is_empty() {
|
|
317
|
+
let mut dest_name_tree = names.destinations();
|
|
318
|
+
let mut dest_name_entries = dest_name_tree.names();
|
|
319
|
+
|
|
320
|
+
// "The Names entries in the leaf (or root) nodes shall
|
|
321
|
+
// contain the tree’s keys and their associated values,
|
|
322
|
+
// arranged in key-value pairs and shall be sorted lexically
|
|
323
|
+
// in ascending order by key. Shorter keys shall appear
|
|
324
|
+
// before longer ones beginning with the same byte sequence.
|
|
325
|
+
// Any encoding of the keys may be used as long as it is
|
|
326
|
+
// self-consistent; keys shall be compared for equality on
|
|
327
|
+
// a simple byte-by-byte basis."
|
|
328
|
+
let mut sorted = named_destinations.into_iter().collect::<Vec<_>>();
|
|
329
|
+
// Note that named destinations are guaranteed to be unique,
|
|
330
|
+
// hence just comparing by the name is enough.
|
|
331
|
+
sorted.sort_by(|a, b| a.0.as_bytes().cmp(b.0.as_bytes()));
|
|
332
|
+
|
|
333
|
+
for (name, (dest_ref, _)) in sorted {
|
|
334
|
+
dest_name_entries.insert(Str(name.as_bytes()), remapper[&dest_ref]);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
dest_name_entries.finish();
|
|
338
|
+
dest_name_tree.finish();
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if write_embedded_files {
|
|
342
|
+
let mut embedded_files_name_tree = names.embedded_files();
|
|
343
|
+
let mut embedded_name_entries = embedded_files_name_tree.names();
|
|
344
|
+
|
|
345
|
+
for (name, _ref) in &embedded_files {
|
|
346
|
+
embedded_name_entries.insert(Str(name.as_bytes()), remapper[_ref]);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if !embedded_files.is_empty() && settings.supports_associated_files() {
|
|
352
|
+
let mut associated_files = catalog.insert(Name(b"AF")).array().typed();
|
|
353
|
+
for _ref in embedded_files.values() {
|
|
354
|
+
associated_files.item(remapper[_ref]).finish();
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
catalog.finish();
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
Ok(pdf)
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
pub(crate) struct EmbeddedPdfChunk {
|
|
366
|
+
pub(crate) original_chunk: Chunk,
|
|
367
|
+
pub(crate) root_ref_mappings: HashMap<Ref, Ref>,
|
|
368
|
+
pub(crate) new_chunk: OnceLock<Chunk>,
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/// Visits all chunks in a type.
|
|
372
|
+
trait Visit {
|
|
373
|
+
fn visit(&self, sc: &mut SerializeContext, f: &mut impl FnMut(&Chunk)) -> KrillaResult<()>;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
impl Visit for EmbeddedPdfChunk {
|
|
377
|
+
fn visit(&self, sc: &mut SerializeContext, f: &mut impl FnMut(&Chunk)) -> KrillaResult<()> {
|
|
378
|
+
// Now, we have a chunk that contains everything we need to fully embed the PDF, including
|
|
379
|
+
// the pages we wanted to extract into, as well as all their dependencies. The
|
|
380
|
+
// problem is: during the document creation, we already assigned references to the
|
|
381
|
+
// pages (stored in `SerializerContex::page_infos`), but `hayro_write` created new references
|
|
382
|
+
// for those (stored in `result.root_refs`).
|
|
383
|
+
|
|
384
|
+
// Because of this, embedded PDF chunks will be renumbered twice: First, we preprocess the
|
|
385
|
+
// chunk such that page/XObjects are reassigned their original references from the serialize
|
|
386
|
+
// context, and all other objects are assigned new, unique references provided by the
|
|
387
|
+
// serialize context. Then, we renumber them once again by treating them like any other chunk.
|
|
388
|
+
|
|
389
|
+
// Since we are calling `visit` twice, we also cache the renumbered chunk.
|
|
390
|
+
|
|
391
|
+
let renumbered = self.new_chunk.get_or_init(|| {
|
|
392
|
+
let mut remapper = self.root_ref_mappings.clone();
|
|
393
|
+
|
|
394
|
+
self.original_chunk
|
|
395
|
+
.renumber(|old| *remapper.entry(old).or_insert_with(|| sc.new_ref()))
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
renumbered.visit(sc, f)
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
impl Visit for ChunkContainer {
|
|
403
|
+
fn visit(&self, sc: &mut SerializeContext, f: &mut impl FnMut(&Chunk)) -> KrillaResult<()> {
|
|
404
|
+
self.non_stream.visit(sc, f)?;
|
|
405
|
+
self.mixed.visit(sc, f)?;
|
|
406
|
+
self.streams.visit(sc, f)?;
|
|
407
|
+
Ok(())
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
impl Visit for StreamChunks {
|
|
412
|
+
fn visit(&self, sc: &mut SerializeContext, f: &mut impl FnMut(&Chunk)) -> KrillaResult<()> {
|
|
413
|
+
self.fonts.visit(sc, f)?;
|
|
414
|
+
self.shading_functions.visit(sc, f)?;
|
|
415
|
+
self.patterns.visit(sc, f)?;
|
|
416
|
+
self.pages.visit(sc, f)?;
|
|
417
|
+
self.embedded_files.visit(sc, f)?;
|
|
418
|
+
self.icc_profiles.visit(sc, f)?;
|
|
419
|
+
self.x_objects.visit(sc, f)?;
|
|
420
|
+
self.images.visit(sc, f)?;
|
|
421
|
+
|
|
422
|
+
Ok(())
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
impl Visit for MixedChunks {
|
|
427
|
+
fn visit(&self, sc: &mut SerializeContext, f: &mut impl FnMut(&Chunk)) -> KrillaResult<()> {
|
|
428
|
+
self.embedded_pdfs.visit(sc, f)?;
|
|
429
|
+
|
|
430
|
+
Ok(())
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
impl Visit for NonStreamChunks {
|
|
435
|
+
fn visit(&self, sc: &mut SerializeContext, f: &mut impl FnMut(&Chunk)) -> KrillaResult<()> {
|
|
436
|
+
self.page_tree.visit(sc, f)?;
|
|
437
|
+
self.outline.visit(sc, f)?;
|
|
438
|
+
self.page_label_tree.visit(sc, f)?;
|
|
439
|
+
self.destination_profiles.visit(sc, f)?;
|
|
440
|
+
self.struct_tree_root.visit(sc, f)?;
|
|
441
|
+
self.struct_elements.visit(sc, f)?;
|
|
442
|
+
self.page_labels.visit(sc, f)?;
|
|
443
|
+
self.annotations.visit(sc, f)?;
|
|
444
|
+
self.color_spaces.visit(sc, f)?;
|
|
445
|
+
self.destinations.visit(sc, f)?;
|
|
446
|
+
self.ext_g_states.visit(sc, f)?;
|
|
447
|
+
self.resource_dictionaries.visit(sc, f)?;
|
|
448
|
+
self.masks.visit(sc, f)?;
|
|
449
|
+
self.fonts.visit(sc, f)?;
|
|
450
|
+
self.shading_functions.visit(sc, f)?;
|
|
451
|
+
self.patterns.visit(sc, f)?;
|
|
452
|
+
self.pages.visit(sc, f)?;
|
|
453
|
+
self.embedded_files.visit(sc, f)?;
|
|
454
|
+
|
|
455
|
+
Ok(())
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
impl Visit for Chunk {
|
|
460
|
+
fn visit(&self, _: &mut SerializeContext, f: &mut impl FnMut(&Chunk)) -> KrillaResult<()> {
|
|
461
|
+
f(self);
|
|
462
|
+
Ok(())
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
impl Visit for Option<Chunk> {
|
|
467
|
+
fn visit(&self, sc: &mut SerializeContext, f: &mut impl FnMut(&Chunk)) -> KrillaResult<()> {
|
|
468
|
+
if let Some(chunk) = self {
|
|
469
|
+
chunk.visit(sc, f)?;
|
|
470
|
+
}
|
|
471
|
+
Ok(())
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
impl Visit for Option<(Ref, Chunk)> {
|
|
476
|
+
fn visit(&self, sc: &mut SerializeContext, f: &mut impl FnMut(&Chunk)) -> KrillaResult<()> {
|
|
477
|
+
if let Some((_, chunk)) = self {
|
|
478
|
+
chunk.visit(sc, f)?;
|
|
479
|
+
}
|
|
480
|
+
Ok(())
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
impl<T: Visit + Send + Sync + 'static> Visit for Deferred<T> {
|
|
485
|
+
fn visit(&self, sc: &mut SerializeContext, f: &mut impl FnMut(&Chunk)) -> KrillaResult<()> {
|
|
486
|
+
self.wait().visit(sc, f)
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
impl<T: Visit> Visit for KrillaResult<T> {
|
|
491
|
+
fn visit(&self, sc: &mut SerializeContext, f: &mut impl FnMut(&Chunk)) -> KrillaResult<()> {
|
|
492
|
+
self.as_ref().map_err(|e| e.clone())?.visit(sc, f)
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
impl<T: Visit> Visit for Vec<T> {
|
|
497
|
+
fn visit(&self, sc: &mut SerializeContext, f: &mut impl FnMut(&Chunk)) -> KrillaResult<()> {
|
|
498
|
+
for field in self {
|
|
499
|
+
field.visit(sc, f)?;
|
|
500
|
+
}
|
|
501
|
+
Ok(())
|
|
502
|
+
}
|
|
503
|
+
}
|