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,289 @@
|
|
|
1
|
+
//! Destinations in a PDF document.
|
|
2
|
+
//!
|
|
3
|
+
//! In some cases, you might want to refer to other locations within the same document, for
|
|
4
|
+
//! example when defining the outline, or when link to a different section in the document
|
|
5
|
+
//! from a link. To achieve this, you can use destinations, which are associated with a page
|
|
6
|
+
//! and a specific location on that page.
|
|
7
|
+
|
|
8
|
+
use std::hash::{Hash, Hasher};
|
|
9
|
+
use std::sync::Arc;
|
|
10
|
+
|
|
11
|
+
use pdf_writer::{Obj, Ref, Str};
|
|
12
|
+
use tiny_skia_path::Transform;
|
|
13
|
+
|
|
14
|
+
use crate::chunk_container::ChunkContainer;
|
|
15
|
+
use crate::error::{KrillaError, KrillaResult};
|
|
16
|
+
use crate::geom::Point;
|
|
17
|
+
use crate::serialize::{PageInfo, SerializeContext};
|
|
18
|
+
|
|
19
|
+
/// The type of destination.
|
|
20
|
+
#[derive(Hash)]
|
|
21
|
+
pub enum Destination {
|
|
22
|
+
/// An XYZ destination.
|
|
23
|
+
Xyz(XyzDestination),
|
|
24
|
+
/// A named destination.
|
|
25
|
+
Named(NamedDestination),
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
impl Destination {
|
|
29
|
+
pub(crate) fn serialize(&self, sc: &mut SerializeContext, buffer: Obj) -> KrillaResult<()> {
|
|
30
|
+
match self {
|
|
31
|
+
Destination::Xyz(xyz) => {
|
|
32
|
+
let ref_ = sc.register_xyz_destination(xyz.clone());
|
|
33
|
+
buffer.primitive(ref_);
|
|
34
|
+
|
|
35
|
+
Ok(())
|
|
36
|
+
}
|
|
37
|
+
Destination::Named(named) => named.serialize(sc, buffer),
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/// A destination associated with a name.
|
|
43
|
+
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
|
|
44
|
+
pub struct NamedDestination {
|
|
45
|
+
pub(crate) name: Arc<String>,
|
|
46
|
+
pub(crate) xyz_dest: Arc<XyzDestination>,
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
impl From<NamedDestination> for Destination {
|
|
50
|
+
fn from(val: NamedDestination) -> Self {
|
|
51
|
+
Destination::Named(val)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
impl NamedDestination {
|
|
56
|
+
/// Create a new named destination.
|
|
57
|
+
///
|
|
58
|
+
/// When used as part of a link annotation, the destination will be automatically registered
|
|
59
|
+
/// with the [`Document`](crate::Document).
|
|
60
|
+
///
|
|
61
|
+
/// That said, you can also manually register a destination without linking to it by calling
|
|
62
|
+
/// [`Document::register_named_destination`](crate::Document::register_named_destination).
|
|
63
|
+
pub fn new(name: String, xyz_dest: XyzDestination) -> Self {
|
|
64
|
+
Self {
|
|
65
|
+
name: Arc::new(name),
|
|
66
|
+
xyz_dest: Arc::new(xyz_dest),
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
pub(crate) fn serialize(
|
|
71
|
+
&self,
|
|
72
|
+
sc: &mut SerializeContext,
|
|
73
|
+
destination: Obj,
|
|
74
|
+
) -> KrillaResult<()> {
|
|
75
|
+
sc.register_named_destination(self.clone())
|
|
76
|
+
.ok_or_else(|| KrillaError::DuplicateNamedDestination(Arc::clone(&self.name)))?;
|
|
77
|
+
destination.primitive(Str(self.name.as_bytes()));
|
|
78
|
+
|
|
79
|
+
Ok(())
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
#[derive(Debug)]
|
|
84
|
+
struct XyzDestRepr {
|
|
85
|
+
page_index: usize,
|
|
86
|
+
point: Point,
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
impl Hash for XyzDestRepr {
|
|
90
|
+
fn hash<H: Hasher>(&self, state: &mut H) {
|
|
91
|
+
self.page_index.hash(state);
|
|
92
|
+
self.point.x.to_bits().hash(state);
|
|
93
|
+
self.point.y.to_bits().hash(state);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
impl PartialEq for XyzDestRepr {
|
|
98
|
+
fn eq(&self, other: &Self) -> bool {
|
|
99
|
+
self.page_index == other.page_index
|
|
100
|
+
&& self.point.x == other.point.x
|
|
101
|
+
&& self.point.y == other.point.y
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
impl Eq for XyzDestRepr {}
|
|
106
|
+
|
|
107
|
+
/// A destination pointing to a specific location at a specific page.
|
|
108
|
+
#[derive(Clone, Hash, PartialEq, Eq, Debug)]
|
|
109
|
+
pub struct XyzDestination(Arc<XyzDestRepr>);
|
|
110
|
+
|
|
111
|
+
impl From<XyzDestination> for Destination {
|
|
112
|
+
fn from(val: XyzDestination) -> Self {
|
|
113
|
+
Destination::Xyz(val)
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
impl XyzDestination {
|
|
118
|
+
/// Create a new XYZ destination. `page_index` should be the index (i.e. number) of the
|
|
119
|
+
/// target page, and point indicates the specific location on that page that should be
|
|
120
|
+
/// targeted. If the `page_index` is out of range, export will panic.
|
|
121
|
+
pub fn new(page_index: usize, point: Point) -> Self {
|
|
122
|
+
Self(Arc::new(XyzDestRepr { page_index, point }))
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
pub(crate) fn serialize(
|
|
126
|
+
&self,
|
|
127
|
+
sc: &mut SerializeContext,
|
|
128
|
+
chunk_container: &mut ChunkContainer,
|
|
129
|
+
root_ref: Ref,
|
|
130
|
+
) {
|
|
131
|
+
let chunk = &mut chunk_container.non_stream.destinations;
|
|
132
|
+
let destination = chunk.destination(root_ref);
|
|
133
|
+
|
|
134
|
+
let page_info = sc.page_infos().get(self.0.page_index).unwrap_or_else(|| {
|
|
135
|
+
panic!(
|
|
136
|
+
"attempted to link to page {}, but document only has {} pages",
|
|
137
|
+
self.0.page_index + 1,
|
|
138
|
+
sc.page_infos().len()
|
|
139
|
+
)
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
let (ref_, surface_size) = match page_info {
|
|
143
|
+
PageInfo::Krilla {
|
|
144
|
+
ref_, surface_size, ..
|
|
145
|
+
} => (ref_, surface_size),
|
|
146
|
+
PageInfo::Pdf { ref_, size, .. } => (ref_, size),
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
let page_ref = *ref_;
|
|
150
|
+
let page_size = surface_size.height();
|
|
151
|
+
|
|
152
|
+
let mut mapped_point = self.0.point.to_tsp();
|
|
153
|
+
// Convert to PDF coordinates
|
|
154
|
+
let invert_transform = Transform::from_row(1.0, 0.0, 0.0, -1.0, 0.0, page_size);
|
|
155
|
+
invert_transform.map_point(&mut mapped_point);
|
|
156
|
+
|
|
157
|
+
destination
|
|
158
|
+
.page(page_ref)
|
|
159
|
+
.xyz(mapped_point.x, mapped_point.y, None);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
#[cfg(test)]
|
|
164
|
+
mod tests {
|
|
165
|
+
use std::sync::Arc;
|
|
166
|
+
|
|
167
|
+
use crate::annotation::{LinkAnnotation, Target};
|
|
168
|
+
use crate::error::KrillaError;
|
|
169
|
+
use crate::geom::{Point, Rect};
|
|
170
|
+
use crate::Document;
|
|
171
|
+
|
|
172
|
+
use super::{NamedDestination, XyzDestination};
|
|
173
|
+
|
|
174
|
+
#[test]
|
|
175
|
+
fn named_duplicate_rejected() {
|
|
176
|
+
let mut document = Document::new();
|
|
177
|
+
assert_eq!(
|
|
178
|
+
document.register_named_destination(NamedDestination::new(
|
|
179
|
+
"same".to_string(),
|
|
180
|
+
XyzDestination::new(0, Point::from_xy(0.0, 0.0)),
|
|
181
|
+
)),
|
|
182
|
+
Some(())
|
|
183
|
+
);
|
|
184
|
+
assert_eq!(
|
|
185
|
+
document.register_named_destination(NamedDestination::new(
|
|
186
|
+
"same".to_string(),
|
|
187
|
+
XyzDestination::new(0, Point::from_xy(100.0, 100.0)),
|
|
188
|
+
)),
|
|
189
|
+
None
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
#[test]
|
|
194
|
+
fn named_duplicate_same_location_allowed() {
|
|
195
|
+
let mut document = Document::new();
|
|
196
|
+
assert_eq!(
|
|
197
|
+
document.register_named_destination(NamedDestination::new(
|
|
198
|
+
"same".to_string(),
|
|
199
|
+
XyzDestination::new(0, Point::from_xy(0.0, 0.0)),
|
|
200
|
+
)),
|
|
201
|
+
Some(())
|
|
202
|
+
);
|
|
203
|
+
assert_eq!(
|
|
204
|
+
document.register_named_destination(NamedDestination::new(
|
|
205
|
+
"same".to_string(),
|
|
206
|
+
XyzDestination::new(0, Point::from_xy(0.0, 0.0)),
|
|
207
|
+
)),
|
|
208
|
+
Some(())
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
assert!(document.finish().is_ok());
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
#[test]
|
|
215
|
+
fn named_duplicate_annotation_rejected() {
|
|
216
|
+
let mut document = Document::new();
|
|
217
|
+
let mut page = document.start_page();
|
|
218
|
+
|
|
219
|
+
page.add_annotation(
|
|
220
|
+
LinkAnnotation::new(
|
|
221
|
+
Rect::from_xywh(0.0, 0.0, 100.0, 100.0).unwrap(),
|
|
222
|
+
Target::Destination(
|
|
223
|
+
NamedDestination::new(
|
|
224
|
+
"same".to_string(),
|
|
225
|
+
XyzDestination::new(0, Point::from_xy(0.0, 0.0)),
|
|
226
|
+
)
|
|
227
|
+
.into(),
|
|
228
|
+
),
|
|
229
|
+
)
|
|
230
|
+
.into(),
|
|
231
|
+
);
|
|
232
|
+
page.add_annotation(
|
|
233
|
+
LinkAnnotation::new(
|
|
234
|
+
Rect::from_xywh(0.0, 100.0, 100.0, 100.0).unwrap(),
|
|
235
|
+
Target::Destination(
|
|
236
|
+
NamedDestination::new(
|
|
237
|
+
"same".to_string(),
|
|
238
|
+
XyzDestination::new(0, Point::from_xy(100.0, 100.0)),
|
|
239
|
+
)
|
|
240
|
+
.into(),
|
|
241
|
+
),
|
|
242
|
+
)
|
|
243
|
+
.into(),
|
|
244
|
+
);
|
|
245
|
+
drop(page);
|
|
246
|
+
|
|
247
|
+
assert_eq!(
|
|
248
|
+
document.finish(),
|
|
249
|
+
Err(KrillaError::DuplicateNamedDestination(Arc::new(
|
|
250
|
+
"same".to_string()
|
|
251
|
+
)))
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
#[test]
|
|
256
|
+
fn named_duplicate_manual_then_annotation_rejected() {
|
|
257
|
+
let mut document = Document::new();
|
|
258
|
+
assert_eq!(
|
|
259
|
+
document.register_named_destination(NamedDestination::new(
|
|
260
|
+
"same".to_string(),
|
|
261
|
+
XyzDestination::new(0, Point::from_xy(0.0, 0.0)),
|
|
262
|
+
)),
|
|
263
|
+
Some(())
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
let mut page = document.start_page();
|
|
267
|
+
page.add_annotation(
|
|
268
|
+
LinkAnnotation::new(
|
|
269
|
+
Rect::from_xywh(0.0, 0.0, 100.0, 100.0).unwrap(),
|
|
270
|
+
Target::Destination(
|
|
271
|
+
NamedDestination::new(
|
|
272
|
+
"same".to_string(),
|
|
273
|
+
XyzDestination::new(0, Point::from_xy(100.0, 100.0)),
|
|
274
|
+
)
|
|
275
|
+
.into(),
|
|
276
|
+
),
|
|
277
|
+
)
|
|
278
|
+
.into(),
|
|
279
|
+
);
|
|
280
|
+
drop(page);
|
|
281
|
+
|
|
282
|
+
assert_eq!(
|
|
283
|
+
document.finish(),
|
|
284
|
+
Err(KrillaError::DuplicateNamedDestination(Arc::new(
|
|
285
|
+
"same".to_string()
|
|
286
|
+
)))
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
//! Embedding attachments to a PDF file.
|
|
2
|
+
|
|
3
|
+
use std::ops::DerefMut;
|
|
4
|
+
|
|
5
|
+
use pdf_writer::{Finish, Name, Ref, Str, TextStr};
|
|
6
|
+
|
|
7
|
+
use crate::chunk_container::ChunkContainer;
|
|
8
|
+
use crate::configure::{PdfVersion, ValidationError};
|
|
9
|
+
use crate::interchange::metadata::pdf_date;
|
|
10
|
+
use crate::metadata::DateTime;
|
|
11
|
+
use crate::serialize::{Cacheable, SerializeContext};
|
|
12
|
+
use crate::stream::FilterStreamBuilder;
|
|
13
|
+
use crate::surface::Location;
|
|
14
|
+
use crate::util::NameExt;
|
|
15
|
+
use crate::Data;
|
|
16
|
+
|
|
17
|
+
/// An error while embedding the file.
|
|
18
|
+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
|
19
|
+
pub enum EmbedError {
|
|
20
|
+
/// The selected standard does not support embedding files.
|
|
21
|
+
Existence,
|
|
22
|
+
/// The document doesn't contain a modification date, which is required for embedded files
|
|
23
|
+
/// in some export modes.
|
|
24
|
+
MissingDate,
|
|
25
|
+
/// The embedded file is missing a human-readable description.
|
|
26
|
+
MissingDescription,
|
|
27
|
+
/// The mime type of the embedded file is missing.
|
|
28
|
+
MissingMimeType,
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/// An embedded file.
|
|
32
|
+
#[derive(Debug, Clone, Hash)]
|
|
33
|
+
pub struct EmbeddedFile {
|
|
34
|
+
/// The name of the embedded file.
|
|
35
|
+
pub path: String,
|
|
36
|
+
/// The mime type of the embedded file.
|
|
37
|
+
pub mime_type: Option<MimeType>,
|
|
38
|
+
/// A description of the embedded file.
|
|
39
|
+
pub description: Option<String>,
|
|
40
|
+
/// The association kind of the embedded file.
|
|
41
|
+
pub association_kind: AssociationKind,
|
|
42
|
+
/// The raw data of the embedded file.
|
|
43
|
+
pub data: Data,
|
|
44
|
+
/// The modification date of the embedded file.
|
|
45
|
+
pub modification_date: Option<DateTime>,
|
|
46
|
+
/// Whether the embedded file should be compressed (recommended to turn off if the
|
|
47
|
+
/// original file already has compression). If `None`, krilla will use its own logic
|
|
48
|
+
/// for determining whether to compress the file or not.
|
|
49
|
+
pub compress: Option<bool>,
|
|
50
|
+
/// The location of the embedded file.
|
|
51
|
+
pub location: Option<Location>,
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
impl Cacheable for EmbeddedFile {
|
|
55
|
+
fn serialize(
|
|
56
|
+
self,
|
|
57
|
+
sc: &mut SerializeContext,
|
|
58
|
+
chunk_container: &mut ChunkContainer,
|
|
59
|
+
root_ref: Ref,
|
|
60
|
+
) {
|
|
61
|
+
sc.register_validation_error(ValidationError::EmbeddedFile(
|
|
62
|
+
EmbedError::Existence,
|
|
63
|
+
self.location,
|
|
64
|
+
));
|
|
65
|
+
|
|
66
|
+
let chunk = &mut chunk_container.non_stream.embedded_files;
|
|
67
|
+
let mut stream_chunk = sc.new_chunk();
|
|
68
|
+
let stream_ref = sc.new_ref();
|
|
69
|
+
|
|
70
|
+
let file_stream = match self.compress {
|
|
71
|
+
Some(true) => FilterStreamBuilder::new_from_binary_data(self.data.as_ref()),
|
|
72
|
+
Some(false) => FilterStreamBuilder::new_from_uncompressed(self.data.as_ref()),
|
|
73
|
+
None => FilterStreamBuilder::new_auto_compressed(self.data.as_ref()),
|
|
74
|
+
}
|
|
75
|
+
.finish(&sc.serialize_settings());
|
|
76
|
+
|
|
77
|
+
let mut embedded_file_stream =
|
|
78
|
+
stream_chunk.embedded_file(stream_ref, file_stream.encoded_data());
|
|
79
|
+
file_stream.write_filters(embedded_file_stream.deref_mut().deref_mut());
|
|
80
|
+
|
|
81
|
+
if let Some(mime_type) = &self.mime_type {
|
|
82
|
+
embedded_file_stream.subtype(mime_type.0.to_pdf_name());
|
|
83
|
+
} else {
|
|
84
|
+
sc.register_validation_error(ValidationError::EmbeddedFile(
|
|
85
|
+
EmbedError::MissingMimeType,
|
|
86
|
+
self.location,
|
|
87
|
+
));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
let mut params = embedded_file_stream.params();
|
|
91
|
+
params.size(self.data.as_ref().len() as i32);
|
|
92
|
+
|
|
93
|
+
if let Some(date_time) = &self.modification_date {
|
|
94
|
+
let date = pdf_date(*date_time);
|
|
95
|
+
params.modification_date(date);
|
|
96
|
+
} else {
|
|
97
|
+
sc.register_validation_error(ValidationError::EmbeddedFile(
|
|
98
|
+
EmbedError::MissingDate,
|
|
99
|
+
self.location,
|
|
100
|
+
));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
params.finish();
|
|
104
|
+
embedded_file_stream.finish();
|
|
105
|
+
|
|
106
|
+
let mut file_spec = chunk.file_spec(root_ref);
|
|
107
|
+
file_spec.path(Str(self.path.as_bytes()));
|
|
108
|
+
|
|
109
|
+
if sc.serialize_settings().pdf_version() >= PdfVersion::Pdf17 {
|
|
110
|
+
file_spec.unic_file(TextStr(&self.path));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
let mut ef = file_spec.insert(Name(b"EF")).dict();
|
|
114
|
+
ef.pair(Name(b"F"), stream_ref);
|
|
115
|
+
|
|
116
|
+
if sc.serialize_settings().pdf_version() >= PdfVersion::Pdf17 {
|
|
117
|
+
ef.pair(Name(b"UF"), stream_ref);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
ef.finish();
|
|
121
|
+
|
|
122
|
+
if sc.serialize_settings().supports_associated_files() {
|
|
123
|
+
file_spec.association_kind(self.association_kind.to_pdf());
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if let Some(description) = self.description {
|
|
127
|
+
file_spec.description(TextStr(&description));
|
|
128
|
+
} else {
|
|
129
|
+
sc.register_validation_error(ValidationError::EmbeddedFile(
|
|
130
|
+
EmbedError::MissingDescription,
|
|
131
|
+
self.location,
|
|
132
|
+
));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
file_spec.finish();
|
|
136
|
+
chunk_container.streams.embedded_files.push(stream_chunk);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/// How an embedded file relates to the PDF document it is embedded in.
|
|
141
|
+
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
|
142
|
+
pub enum AssociationKind {
|
|
143
|
+
/// The PDF document was created from this source file.
|
|
144
|
+
Source,
|
|
145
|
+
/// This file was used to derive a visual presentation in the PDF.
|
|
146
|
+
Data,
|
|
147
|
+
/// An alternative representation of this document.
|
|
148
|
+
Alternative,
|
|
149
|
+
/// Additional resources for this document.
|
|
150
|
+
Supplement,
|
|
151
|
+
/// There is no clear relationship or it is not known.
|
|
152
|
+
Unspecified,
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
impl AssociationKind {
|
|
156
|
+
fn to_pdf(self) -> pdf_writer::types::AssociationKind {
|
|
157
|
+
match self {
|
|
158
|
+
AssociationKind::Source => pdf_writer::types::AssociationKind::Source,
|
|
159
|
+
AssociationKind::Data => pdf_writer::types::AssociationKind::Data,
|
|
160
|
+
AssociationKind::Alternative => pdf_writer::types::AssociationKind::Alternative,
|
|
161
|
+
AssociationKind::Supplement => pdf_writer::types::AssociationKind::Supplement,
|
|
162
|
+
AssociationKind::Unspecified => pdf_writer::types::AssociationKind::Unspecified,
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/// A mime type.
|
|
168
|
+
#[derive(Debug, Clone, Hash)]
|
|
169
|
+
pub struct MimeType(String);
|
|
170
|
+
|
|
171
|
+
impl MimeType {
|
|
172
|
+
/// Create a new mime type.
|
|
173
|
+
///
|
|
174
|
+
/// Returns `None` if the mime type is invalid.
|
|
175
|
+
pub fn new(mime_type: &str) -> Option<Self> {
|
|
176
|
+
if valid_mime_type(mime_type) {
|
|
177
|
+
Some(Self(mime_type.to_string()))
|
|
178
|
+
} else {
|
|
179
|
+
None
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
fn valid_mime_type(mime_type: &str) -> bool {
|
|
185
|
+
let parts: Vec<&str> = mime_type.split('/').collect();
|
|
186
|
+
if parts.len() != 2 {
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
let (type_part, subtype_part) = (parts[0], parts[1]);
|
|
191
|
+
|
|
192
|
+
valid_mime_part(type_part) && valid_mime_part(subtype_part)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
fn valid_mime_part(part: &str) -> bool {
|
|
196
|
+
if part.is_empty() {
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
part.chars()
|
|
201
|
+
.all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '+' || c == '.')
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
#[cfg(test)]
|
|
205
|
+
mod tests {
|
|
206
|
+
use super::*;
|
|
207
|
+
|
|
208
|
+
#[test]
|
|
209
|
+
fn valid_mime_types() {
|
|
210
|
+
assert!(valid_mime_type("text/plain"));
|
|
211
|
+
assert!(valid_mime_type("application/pdf"));
|
|
212
|
+
assert!(valid_mime_type("image/jpeg"));
|
|
213
|
+
assert!(valid_mime_type("application/octet-stream"));
|
|
214
|
+
assert!(valid_mime_type("text/html"));
|
|
215
|
+
assert!(valid_mime_type("application/json"));
|
|
216
|
+
assert!(valid_mime_type("image/png"));
|
|
217
|
+
assert!(valid_mime_type("video/mp4"));
|
|
218
|
+
assert!(valid_mime_type("audio/mpeg"));
|
|
219
|
+
assert!(valid_mime_type("application/vnd.ms-excel"));
|
|
220
|
+
assert!(valid_mime_type("text/css"));
|
|
221
|
+
assert!(valid_mime_type("application/x-www-form-urlencoded"));
|
|
222
|
+
assert!(valid_mime_type("multipart/form-data"));
|
|
223
|
+
assert!(valid_mime_type("application/xml+svg"));
|
|
224
|
+
assert!(valid_mime_type("text/plain+charset"));
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
#[test]
|
|
228
|
+
fn invalid_mime_types() {
|
|
229
|
+
assert!(!valid_mime_type("source"));
|
|
230
|
+
assert!(!valid_mime_type("text/"));
|
|
231
|
+
assert!(!valid_mime_type("/plain"));
|
|
232
|
+
assert!(!valid_mime_type(""));
|
|
233
|
+
assert!(!valid_mime_type("text"));
|
|
234
|
+
assert!(!valid_mime_type("text/plain/extra"));
|
|
235
|
+
assert!(!valid_mime_type("text plain"));
|
|
236
|
+
assert!(!valid_mime_type("text\\plain"));
|
|
237
|
+
assert!(!valid_mime_type("text@plain"));
|
|
238
|
+
assert!(!valid_mime_type("text/plain*"));
|
|
239
|
+
}
|
|
240
|
+
}
|