blitz-pdf 0.1.1 → 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 +266 -12
- 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,313 @@
|
|
|
1
|
+
//! Including other PDF files.
|
|
2
|
+
|
|
3
|
+
use std::collections::HashMap;
|
|
4
|
+
use std::fmt::{self, Debug, Display, Formatter};
|
|
5
|
+
use std::hash::{Hash, Hasher};
|
|
6
|
+
use std::ops::Deref;
|
|
7
|
+
use std::sync::{Arc, OnceLock};
|
|
8
|
+
|
|
9
|
+
use hayro_write::hayro_syntax::page::Page;
|
|
10
|
+
use hayro_write::hayro_syntax::PdfVersion as HayroPdfVersion;
|
|
11
|
+
use hayro_write::{ExtractionError, ExtractionQuery};
|
|
12
|
+
use pdf_writer::{Name, Ref};
|
|
13
|
+
|
|
14
|
+
pub use hayro_write::hayro_syntax::Pdf;
|
|
15
|
+
|
|
16
|
+
use crate::chunk_container::{ChunkContainer, EmbeddedPdfChunk};
|
|
17
|
+
use crate::configure::{PdfVersion, ValidationError};
|
|
18
|
+
use crate::error::{KrillaError, KrillaResult};
|
|
19
|
+
use crate::graphics::color::rgb;
|
|
20
|
+
use crate::resource::Resource;
|
|
21
|
+
use crate::serialize::{MaybeDeviceColorSpace, SerializeContext};
|
|
22
|
+
use crate::surface::Location;
|
|
23
|
+
use crate::util::{Deferred, Prehashed};
|
|
24
|
+
|
|
25
|
+
/// An error that can occur when embedding a PDF document.
|
|
26
|
+
#[derive(Debug, PartialEq, Eq, Clone)]
|
|
27
|
+
pub enum PdfError {
|
|
28
|
+
/// A page was requested that doesn't exist.
|
|
29
|
+
InvalidPage(usize),
|
|
30
|
+
/// The PDF version of the embedded PDF is not compatible with the version of the PDF
|
|
31
|
+
/// produced by krilla. This happens if the version of the krilla document is lower than
|
|
32
|
+
/// the one of the embedded PDF.
|
|
33
|
+
///
|
|
34
|
+
/// The argument indicates the version of the embedded PDF document.
|
|
35
|
+
VersionMismatch(PdfVersion),
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
impl Display for PdfError {
|
|
39
|
+
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
|
40
|
+
match self {
|
|
41
|
+
PdfError::InvalidPage(index) => write!(f, "invalid page index {index}"),
|
|
42
|
+
PdfError::VersionMismatch(version) => write!(
|
|
43
|
+
f,
|
|
44
|
+
"embedded PDF requires {}, which is newer than the selected PDF version",
|
|
45
|
+
version.as_str()
|
|
46
|
+
),
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
impl std::error::Error for PdfError {}
|
|
52
|
+
|
|
53
|
+
struct PdfDocumentRepr(Arc<Pdf>);
|
|
54
|
+
|
|
55
|
+
impl Debug for PdfDocumentRepr {
|
|
56
|
+
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
57
|
+
write!(f, "PdfDocumentRepr {{ .. }}")
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
impl Hash for PdfDocumentRepr {
|
|
62
|
+
fn hash<H: Hasher>(&self, state: &mut H) {
|
|
63
|
+
self.0.data().as_ref().as_ref().hash(state);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/// An external PDF document.
|
|
68
|
+
#[derive(Clone, Hash, PartialEq, Eq, Debug)]
|
|
69
|
+
pub struct PdfDocument(Arc<Prehashed<PdfDocumentRepr>>);
|
|
70
|
+
|
|
71
|
+
impl PdfDocument {
|
|
72
|
+
/// Load a new PDF document.
|
|
73
|
+
pub fn new(pdf: Arc<Pdf>) -> PdfDocument {
|
|
74
|
+
Self(Arc::new(Prehashed::new(PdfDocumentRepr(pdf))))
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
pub(crate) fn pdf(&self) -> &Pdf {
|
|
78
|
+
&self.0.deref().0
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
pub(crate) fn pages(&self) -> &[Page<'_>] {
|
|
82
|
+
self.0.deref().0.pages()
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
#[derive(Default, Debug)]
|
|
87
|
+
pub(crate) struct PdfDocumentInfo {
|
|
88
|
+
counter: u64,
|
|
89
|
+
query_refs: Vec<Ref>,
|
|
90
|
+
cached_xobjects: HashMap<usize, Ref>,
|
|
91
|
+
queries: Vec<ExtractionQuery>,
|
|
92
|
+
locations: Vec<Option<Location>>,
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
impl PdfDocumentInfo {
|
|
96
|
+
pub(crate) fn new(counter: u64) -> Self {
|
|
97
|
+
Self {
|
|
98
|
+
counter,
|
|
99
|
+
..Self::default()
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
#[derive(Default, Debug)]
|
|
105
|
+
pub(crate) struct PdfSerializerContext {
|
|
106
|
+
infos: HashMap<PdfDocument, PdfDocumentInfo>,
|
|
107
|
+
counter: u64,
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
impl PdfSerializerContext {
|
|
111
|
+
pub(crate) fn add_page(
|
|
112
|
+
&mut self,
|
|
113
|
+
document: &PdfDocument,
|
|
114
|
+
page_index: usize,
|
|
115
|
+
ref_: Ref,
|
|
116
|
+
location: Option<Location>,
|
|
117
|
+
) {
|
|
118
|
+
// Unlike XObject's, we cannot cache page refs since Acrobat doesn't like duplicate page
|
|
119
|
+
// refs in the page tree.
|
|
120
|
+
let info = self.get_info(document);
|
|
121
|
+
|
|
122
|
+
info.query_refs.push(ref_);
|
|
123
|
+
info.queries.push(ExtractionQuery::new_page(page_index));
|
|
124
|
+
info.locations.push(location);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
fn get_info(&mut self, document: &PdfDocument) -> &mut PdfDocumentInfo {
|
|
128
|
+
self.infos.entry(document.clone()).or_insert_with(|| {
|
|
129
|
+
let info = PdfDocumentInfo::new(self.counter);
|
|
130
|
+
self.counter += 1;
|
|
131
|
+
|
|
132
|
+
info
|
|
133
|
+
})
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
#[must_use = "This method might not use the original ref and could return a different one that \
|
|
137
|
+
was previously cached."]
|
|
138
|
+
pub(crate) fn add_xobject(
|
|
139
|
+
&mut self,
|
|
140
|
+
document: &PdfDocument,
|
|
141
|
+
page_index: usize,
|
|
142
|
+
ref_: Ref,
|
|
143
|
+
location: Option<Location>,
|
|
144
|
+
) -> Ref {
|
|
145
|
+
let info = self.get_info(document);
|
|
146
|
+
|
|
147
|
+
if let Some(cached_ref) = info.cached_xobjects.get(&page_index) {
|
|
148
|
+
return *cached_ref;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
info.query_refs.push(ref_);
|
|
152
|
+
info.queries.push(ExtractionQuery::new_xobject(page_index));
|
|
153
|
+
info.locations.push(location);
|
|
154
|
+
|
|
155
|
+
info.cached_xobjects.insert(page_index, ref_);
|
|
156
|
+
|
|
157
|
+
ref_
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
pub(crate) fn serialize(
|
|
161
|
+
self,
|
|
162
|
+
sc: &mut SerializeContext,
|
|
163
|
+
chunk_container: &mut ChunkContainer,
|
|
164
|
+
) -> KrillaResult<()> {
|
|
165
|
+
let page_tree_parent_ref = sc.page_tree_ref();
|
|
166
|
+
let krilla_version = sc.serialize_settings().configuration.version();
|
|
167
|
+
|
|
168
|
+
let mut entries = self.infos.into_iter().collect::<Vec<_>>();
|
|
169
|
+
// Make sure we always process them in the same order.
|
|
170
|
+
entries.sort_by(|d1, d2| d1.1.counter.cmp(&d2.1.counter));
|
|
171
|
+
|
|
172
|
+
for (doc, info) in entries {
|
|
173
|
+
for location in info.locations.iter() {
|
|
174
|
+
sc.register_validation_error(ValidationError::EmbeddedPDF(*location))
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// In theory `hayro-write` only requires this for PDFs that are embedded
|
|
178
|
+
// as XObject's to write the XObject group color space (embedded pages don't need it),
|
|
179
|
+
// but we just always allocate it if we have at least one PDF,
|
|
180
|
+
// regardless of how the files are embedded. We can change this in the
|
|
181
|
+
// future, but it keeps the code simpler.
|
|
182
|
+
let xobject_group_color_space = sc.register_colorspace(
|
|
183
|
+
chunk_container,
|
|
184
|
+
rgb::color_space(sc.serialize_settings().no_device_cs).into(),
|
|
185
|
+
);
|
|
186
|
+
let chunk_settings = sc.chunk_settings();
|
|
187
|
+
|
|
188
|
+
let deferred_chunk = Deferred::new(move || {
|
|
189
|
+
// We can't share the serializer context between threads, so each PDF has it's own
|
|
190
|
+
// reference, and we remap it later in `ChunkContainer`.
|
|
191
|
+
let mut new_ref = Ref::new(1);
|
|
192
|
+
// `local_ref` refers to the Ref that will be assigned to it locally
|
|
193
|
+
// in the extracted PDF chunk. Note that the colorspace itself
|
|
194
|
+
// doesn't actually exist in the extracted PDF, i.e. the Ref
|
|
195
|
+
// points to the null object. It just represents a placeholder Ref.
|
|
196
|
+
// Later on when actually merging the PDF chunk into the final
|
|
197
|
+
// krilla PDF, we replace `local_ref` with `cs_ref` such that it
|
|
198
|
+
// actually points to the colorspace that is embedded in the full PDF.
|
|
199
|
+
let (assigned_cs_ref, should_cs_ref) = match &xobject_group_color_space {
|
|
200
|
+
MaybeDeviceColorSpace::DeviceRgb => (None, None),
|
|
201
|
+
MaybeDeviceColorSpace::ColorSpace(cs) => {
|
|
202
|
+
(Some(new_ref.bump()), Some(cs.get_ref()))
|
|
203
|
+
}
|
|
204
|
+
MaybeDeviceColorSpace::DeviceGray | MaybeDeviceColorSpace::DeviceCMYK => {
|
|
205
|
+
unreachable!()
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
let first_location = info.locations.iter().flatten().next().cloned();
|
|
210
|
+
let pdf = doc.pdf();
|
|
211
|
+
|
|
212
|
+
let pdf_version = convert_pdf_version(pdf.version());
|
|
213
|
+
|
|
214
|
+
if krilla_version < pdf_version {
|
|
215
|
+
return Err(KrillaError::Pdf(
|
|
216
|
+
doc.clone(),
|
|
217
|
+
PdfError::VersionMismatch(pdf_version),
|
|
218
|
+
first_location,
|
|
219
|
+
));
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
let extracted = hayro_write::extract(
|
|
223
|
+
pdf,
|
|
224
|
+
Box::new(|| new_ref.bump()),
|
|
225
|
+
chunk_settings,
|
|
226
|
+
|group| {
|
|
227
|
+
if let Some(local_group_color_space_ref) = assigned_cs_ref {
|
|
228
|
+
group
|
|
229
|
+
.insert(Name(b"CS"))
|
|
230
|
+
.primitive(local_group_color_space_ref);
|
|
231
|
+
} else {
|
|
232
|
+
// If we aren't using an ICC color space, just use
|
|
233
|
+
// the device RGB color space.
|
|
234
|
+
group.color_space().device_rgb();
|
|
235
|
+
}
|
|
236
|
+
},
|
|
237
|
+
&info.queries,
|
|
238
|
+
);
|
|
239
|
+
let result = convert_extraction_result(extracted, &doc, first_location.as_ref())?;
|
|
240
|
+
|
|
241
|
+
debug_assert_eq!(info.query_refs.len(), result.root_refs.len());
|
|
242
|
+
|
|
243
|
+
let mut root_ref_mappings = HashMap::new();
|
|
244
|
+
|
|
245
|
+
root_ref_mappings.insert(result.page_tree_parent_ref, page_tree_parent_ref);
|
|
246
|
+
|
|
247
|
+
if let (Some(local_group_color_space_ref), Some(group_color_space_ref)) =
|
|
248
|
+
(assigned_cs_ref, should_cs_ref)
|
|
249
|
+
{
|
|
250
|
+
root_ref_mappings.insert(local_group_color_space_ref, group_color_space_ref);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
for ((should_ref, extraction_result), location) in info
|
|
254
|
+
.query_refs
|
|
255
|
+
.iter()
|
|
256
|
+
.zip(result.root_refs)
|
|
257
|
+
.zip(&info.locations)
|
|
258
|
+
{
|
|
259
|
+
let assigned_ref = convert_extraction_result(
|
|
260
|
+
extraction_result,
|
|
261
|
+
&doc,
|
|
262
|
+
location.clone().as_ref(),
|
|
263
|
+
)?;
|
|
264
|
+
|
|
265
|
+
root_ref_mappings.insert(assigned_ref, *should_ref);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
Ok(EmbeddedPdfChunk {
|
|
269
|
+
root_ref_mappings,
|
|
270
|
+
original_chunk: result.chunk,
|
|
271
|
+
new_chunk: OnceLock::new(),
|
|
272
|
+
})
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
chunk_container.mixed.embedded_pdfs.push(deferred_chunk);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
Ok(())
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
fn convert_pdf_version(version: HayroPdfVersion) -> PdfVersion {
|
|
283
|
+
match version {
|
|
284
|
+
// Those are obviously not right, but we don't support versions lower than 1.4 in krilla.
|
|
285
|
+
// Since we only need this conversion to detect version mismatches (in which case the
|
|
286
|
+
// version of the embedded PDF has to be higher than 1.4), this hack is sufficient for our
|
|
287
|
+
// purposes.
|
|
288
|
+
HayroPdfVersion::Pdf10 => PdfVersion::Pdf14,
|
|
289
|
+
HayroPdfVersion::Pdf11 => PdfVersion::Pdf14,
|
|
290
|
+
HayroPdfVersion::Pdf12 => PdfVersion::Pdf14,
|
|
291
|
+
HayroPdfVersion::Pdf13 => PdfVersion::Pdf14,
|
|
292
|
+
|
|
293
|
+
HayroPdfVersion::Pdf14 => PdfVersion::Pdf14,
|
|
294
|
+
HayroPdfVersion::Pdf15 => PdfVersion::Pdf15,
|
|
295
|
+
HayroPdfVersion::Pdf16 => PdfVersion::Pdf16,
|
|
296
|
+
HayroPdfVersion::Pdf17 => PdfVersion::Pdf17,
|
|
297
|
+
HayroPdfVersion::Pdf20 => PdfVersion::Pdf20,
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
fn convert_extraction_result<T>(
|
|
302
|
+
result: Result<T, ExtractionError>,
|
|
303
|
+
doc: &PdfDocument,
|
|
304
|
+
location: Option<&Location>,
|
|
305
|
+
) -> KrillaResult<T> {
|
|
306
|
+
result.map_err(|e| {
|
|
307
|
+
let pdf_error = match e {
|
|
308
|
+
ExtractionError::InvalidPageIndex(i) => PdfError::InvalidPage(i),
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
KrillaError::Pdf(doc.clone(), pdf_error, location.cloned())
|
|
312
|
+
})
|
|
313
|
+
}
|