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.
Files changed (114) hide show
  1. package/README.md +266 -12
  2. package/THIRD_PARTY_NOTICES.md +4 -2
  3. package/dist/browser.d.ts +1 -1
  4. package/dist/browser.d.ts.map +1 -1
  5. package/dist/document-options.d.ts +36 -0
  6. package/dist/document-options.d.ts.map +1 -0
  7. package/dist/document-options.js +236 -0
  8. package/dist/document-options.js.map +1 -0
  9. package/dist/node.d.ts +1 -1
  10. package/dist/node.d.ts.map +1 -1
  11. package/dist/options.d.ts +35 -1
  12. package/dist/options.d.ts.map +1 -1
  13. package/dist/options.js +21 -14
  14. package/dist/options.js.map +1 -1
  15. package/dist/types.d.ts +71 -1
  16. package/dist/types.d.ts.map +1 -1
  17. package/dist/validation.d.ts +2 -0
  18. package/dist/validation.d.ts.map +1 -0
  19. package/dist/validation.js +7 -0
  20. package/dist/validation.js.map +1 -0
  21. package/examples/zugferd.mjs +41 -0
  22. package/licenses/RUST_DEPENDENCIES.txt +27 -0
  23. package/package.json +3 -2
  24. package/src/browser.ts +1 -1
  25. package/src/document-options.ts +218 -0
  26. package/src/node.ts +1 -1
  27. package/src/options.ts +18 -13
  28. package/src/types.ts +76 -1
  29. package/src/validation.ts +6 -0
  30. package/vendor/README.md +27 -2
  31. package/vendor/krilla/.cargo-ok +1 -0
  32. package/vendor/krilla/.cargo_vcs_info.json +6 -0
  33. package/vendor/krilla/Cargo.lock +1357 -0
  34. package/vendor/krilla/Cargo.toml +165 -0
  35. package/vendor/krilla/LICENSE_APACHE +202 -0
  36. package/vendor/krilla/LICENSE_MIT +21 -0
  37. package/vendor/krilla/README.md +156 -0
  38. package/vendor/krilla/examples/basic.rs +117 -0
  39. package/vendor/krilla/examples/empty_document.rs +25 -0
  40. package/vendor/krilla/examples/parley.rs +174 -0
  41. package/vendor/krilla/examples/simple_text.rs +118 -0
  42. package/vendor/krilla/examples/stream_builder.rs +93 -0
  43. package/vendor/krilla/icc/LICENSE +116 -0
  44. package/vendor/krilla/icc/README.md +1 -0
  45. package/vendor/krilla/icc/sGrey-v2-magic.icc +0 -0
  46. package/vendor/krilla/icc/sGrey-v4.icc +0 -0
  47. package/vendor/krilla/icc/sRGB-v2-magic.icc +0 -0
  48. package/vendor/krilla/icc/sRGB-v4.icc +0 -0
  49. package/vendor/krilla/src/chunk_container.rs +503 -0
  50. package/vendor/krilla/src/configure/PDF_A1.md +227 -0
  51. package/vendor/krilla/src/configure/PDF_A2.md +269 -0
  52. package/vendor/krilla/src/configure/PDF_A3.md +31 -0
  53. package/vendor/krilla/src/configure/PDF_A4.md +211 -0
  54. package/vendor/krilla/src/configure/PDF_UA1.md +202 -0
  55. package/vendor/krilla/src/configure/README.md +23 -0
  56. package/vendor/krilla/src/configure/mod.rs +197 -0
  57. package/vendor/krilla/src/configure/validate.rs +1244 -0
  58. package/vendor/krilla/src/configure/version.rs +134 -0
  59. package/vendor/krilla/src/content.rs +1352 -0
  60. package/vendor/krilla/src/data.rs +49 -0
  61. package/vendor/krilla/src/document.rs +156 -0
  62. package/vendor/krilla/src/error.rs +170 -0
  63. package/vendor/krilla/src/geom.rs +341 -0
  64. package/vendor/krilla/src/graphics/blend.rs +46 -0
  65. package/vendor/krilla/src/graphics/color.rs +552 -0
  66. package/vendor/krilla/src/graphics/graphic.rs +30 -0
  67. package/vendor/krilla/src/graphics/graphics_state.rs +251 -0
  68. package/vendor/krilla/src/graphics/icc.rs +218 -0
  69. package/vendor/krilla/src/graphics/image.rs +835 -0
  70. package/vendor/krilla/src/graphics/mask.rs +128 -0
  71. package/vendor/krilla/src/graphics/mod.rs +14 -0
  72. package/vendor/krilla/src/graphics/paint.rs +434 -0
  73. package/vendor/krilla/src/graphics/separation.rs +86 -0
  74. package/vendor/krilla/src/graphics/shading_function.rs +865 -0
  75. package/vendor/krilla/src/graphics/shading_pattern.rs +60 -0
  76. package/vendor/krilla/src/graphics/tiling_pattern.rs +119 -0
  77. package/vendor/krilla/src/graphics/xobject.rs +150 -0
  78. package/vendor/krilla/src/interactive/action.rs +68 -0
  79. package/vendor/krilla/src/interactive/annotation.rs +283 -0
  80. package/vendor/krilla/src/interactive/destination.rs +289 -0
  81. package/vendor/krilla/src/interactive/mod.rs +3 -0
  82. package/vendor/krilla/src/interchange/embed.rs +240 -0
  83. package/vendor/krilla/src/interchange/metadata.rs +564 -0
  84. package/vendor/krilla/src/interchange/mod.rs +4 -0
  85. package/vendor/krilla/src/interchange/outline.rs +263 -0
  86. package/vendor/krilla/src/interchange/tagging/fmt.rs +733 -0
  87. package/vendor/krilla/src/interchange/tagging/generate.toml +474 -0
  88. package/vendor/krilla/src/interchange/tagging/generated.rs +4754 -0
  89. package/vendor/krilla/src/interchange/tagging/mod.rs +1324 -0
  90. package/vendor/krilla/src/interchange/tagging/tag.rs +686 -0
  91. package/vendor/krilla/src/lib.rs +179 -0
  92. package/vendor/krilla/src/num.rs +26 -0
  93. package/vendor/krilla/src/page.rs +612 -0
  94. package/vendor/krilla/src/pdf.rs +313 -0
  95. package/vendor/krilla/src/resource.rs +436 -0
  96. package/vendor/krilla/src/serialize.rs +1101 -0
  97. package/vendor/krilla/src/stream.rs +368 -0
  98. package/vendor/krilla/src/surface.rs +664 -0
  99. package/vendor/krilla/src/text/cid.rs +488 -0
  100. package/vendor/krilla/src/text/font.rs +351 -0
  101. package/vendor/krilla/src/text/glyph/bitmap.rs +81 -0
  102. package/vendor/krilla/src/text/glyph/colr.rs +581 -0
  103. package/vendor/krilla/src/text/glyph/mod.rs +205 -0
  104. package/vendor/krilla/src/text/glyph/outline.rs +72 -0
  105. package/vendor/krilla/src/text/glyph/svg.rs +34 -0
  106. package/vendor/krilla/src/text/group.rs +463 -0
  107. package/vendor/krilla/src/text/mod.rs +232 -0
  108. package/vendor/krilla/src/text/shape.rs +102 -0
  109. package/vendor/krilla/src/text/type3.rs +514 -0
  110. package/vendor/krilla/src/util.rs +286 -0
  111. package/vendor/stylo/properties/longhands.toml +0 -1
  112. package/vendor/stylo/stylesheets/rule_parser.rs +1 -1
  113. package/wasm/blitz_pdf.js +10 -5
  114. package/wasm/blitz_pdf_bg.wasm +0 -0
@@ -0,0 +1,835 @@
1
+ //! Creating and using bitmap images.
2
+ //!
3
+ //! krilla allows you to add bitmap images to your PDF very easily.
4
+ //! The currently supported formats include
5
+ //! - PNG
6
+ //! - JPG
7
+ //! - GIF
8
+ //! - WEBP
9
+ //! - Custom image formats via [`CustomImage`]
10
+
11
+ use std::fmt::{Debug, Formatter};
12
+ use std::hash::{Hash, Hasher};
13
+ use std::io::Cursor;
14
+ use std::ops::DerefMut;
15
+ use std::sync::Arc;
16
+
17
+ use pdf_writer::{Finish, Name, Ref};
18
+ use png::{BitDepth, ColorType, Transformations};
19
+ use zune_jpeg::zune_core::colorspace::ColorSpace;
20
+ use zune_jpeg::JpegDecoder;
21
+
22
+ use crate::chunk_container::ChunkContainer;
23
+ use crate::configure::ValidationError;
24
+ use crate::error::KrillaError;
25
+ use crate::graphics::color::DEVICE_GRAY;
26
+ use crate::graphics::color::{cmyk, luma, rgb};
27
+ use crate::graphics::icc::{GenericICCProfile, ICCBasedColorSpace, ICCProfile};
28
+ use crate::serialize::SerializeContext;
29
+ use crate::stream::{deflate_encode, FilterStreamBuilder};
30
+ use crate::util::{set_colorspace, Deferred, NameExt, SipHashable};
31
+ use crate::Data;
32
+
33
+ /// The number of bits per color component.
34
+ #[derive(Debug, Hash, Eq, PartialEq, Copy, Clone)]
35
+ pub enum BitsPerComponent {
36
+ /// Eight bits per component.
37
+ Eight,
38
+ /// Sixteen bits per component.
39
+ Sixteen,
40
+ }
41
+
42
+ impl BitsPerComponent {
43
+ fn as_u8(&self) -> u8 {
44
+ match self {
45
+ BitsPerComponent::Eight => 8,
46
+ BitsPerComponent::Sixteen => 16,
47
+ }
48
+ }
49
+ }
50
+
51
+ /// The color space of the image.
52
+ #[derive(Debug, Hash, Eq, PartialEq, Copy, Clone)]
53
+ pub enum ImageColorspace {
54
+ /// The RGB color space.
55
+ Rgb,
56
+ /// The luma color space.
57
+ Luma,
58
+ /// The CMYK color space.
59
+ Cmyk,
60
+ }
61
+
62
+ impl ImageColorspace {
63
+ fn num_components(&self) -> u8 {
64
+ match self {
65
+ ImageColorspace::Luma => 1,
66
+ ImageColorspace::Rgb => 3,
67
+ ImageColorspace::Cmyk => 4,
68
+ }
69
+ }
70
+
71
+ fn matches_icc_profile(&self, profile: &GenericICCProfile) -> bool {
72
+ match self {
73
+ ImageColorspace::Rgb => matches!(profile, GenericICCProfile::Rgb(_)),
74
+ ImageColorspace::Luma => matches!(profile, GenericICCProfile::Luma(_)),
75
+ ImageColorspace::Cmyk => matches!(profile, GenericICCProfile::Cmyk(_)),
76
+ }
77
+ }
78
+ }
79
+
80
+ impl TryFrom<ColorSpace> for ImageColorspace {
81
+ type Error = ();
82
+
83
+ fn try_from(value: ColorSpace) -> Result<Self, Self::Error> {
84
+ match value {
85
+ ColorSpace::RGB => Ok(ImageColorspace::Rgb),
86
+ ColorSpace::RGBA => Ok(ImageColorspace::Rgb),
87
+ ColorSpace::YCbCr => Ok(ImageColorspace::Rgb),
88
+ ColorSpace::Luma => Ok(ImageColorspace::Luma),
89
+ ColorSpace::LumaA => Ok(ImageColorspace::Luma),
90
+ ColorSpace::YCCK => Ok(ImageColorspace::Cmyk),
91
+ ColorSpace::CMYK => Ok(ImageColorspace::Cmyk),
92
+ _ => Err(()),
93
+ }
94
+ }
95
+ }
96
+
97
+ struct SampledRepr {
98
+ color_channel: Vec<u8>,
99
+ alpha_channel: Option<Vec<u8>>,
100
+ bits_per_component: BitsPerComponent,
101
+ }
102
+
103
+ struct JpegRepr {
104
+ data: Data,
105
+ bits_per_component: BitsPerComponent,
106
+ invert_cmyk: bool,
107
+ }
108
+
109
+ enum Repr {
110
+ Sampled(SampledRepr),
111
+ Jpeg(JpegRepr),
112
+ }
113
+
114
+ impl Repr {
115
+ fn bits_per_component(&self) -> BitsPerComponent {
116
+ match self {
117
+ Repr::Sampled(s) => s.bits_per_component,
118
+ Repr::Jpeg(j) => j.bits_per_component,
119
+ }
120
+ }
121
+ }
122
+
123
+ /// A trait for custom images, which you can use if the
124
+ /// current methods provided by krilla (JPEG/PNG/WEBP/GIF) images
125
+ /// are not suitable for your own purpose.
126
+ ///
127
+ /// Note that a struct implementing this trait should be cheap to
128
+ /// hash and clone, otherwise performance might be bad!
129
+ pub trait CustomImage: Hash + Clone + Send + Sync + 'static {
130
+ /// Return the raw bytes of the color channel.
131
+ fn color_channel(&self) -> &[u8];
132
+ /// Return the raw bytes of the alpha channel, if available.
133
+ fn alpha_channel(&self) -> Option<&[u8]>;
134
+ /// Return the bits per component of the image.
135
+ fn bits_per_component(&self) -> BitsPerComponent;
136
+ /// Return the dimensions of the image.
137
+ fn size(&self) -> (u32, u32);
138
+ /// Return the ICC profile of the image, if available.
139
+ fn icc_profile(&self) -> Option<&[u8]>;
140
+ /// Return the color space of the image.
141
+ fn color_space(&self) -> ImageColorspace;
142
+ }
143
+
144
+ struct ImageMetadata {
145
+ size: (u32, u32),
146
+ color_space: ImageColorspace,
147
+ has_alpha: bool,
148
+ bits_per_component: BitsPerComponent,
149
+ icc: Option<GenericICCProfile>,
150
+ }
151
+
152
+ struct ImageRepr {
153
+ inner: Deferred<Result<Repr, String>>,
154
+ metadata: ImageMetadata,
155
+ sip: u128,
156
+ interpolate: bool,
157
+ }
158
+
159
+ impl ImageRepr {
160
+ fn size(&self) -> (u32, u32) {
161
+ self.metadata.size
162
+ }
163
+
164
+ fn icc(&self) -> Option<GenericICCProfile> {
165
+ self.metadata.icc.clone()
166
+ }
167
+
168
+ fn color_space(&self) -> ImageColorspace {
169
+ self.metadata.color_space
170
+ }
171
+ }
172
+
173
+ impl Debug for ImageRepr {
174
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
175
+ write!(f, "ImageRepr {{..}}")
176
+ }
177
+ }
178
+
179
+ impl Hash for ImageRepr {
180
+ fn hash<H: Hasher>(&self, state: &mut H) {
181
+ self.sip.hash(state);
182
+ }
183
+ }
184
+
185
+ impl PartialEq for ImageRepr {
186
+ fn eq(&self, other: &Self) -> bool {
187
+ self.sip == other.sip
188
+ }
189
+ }
190
+
191
+ impl Eq for ImageRepr {}
192
+
193
+ /// A bitmap image.
194
+ ///
195
+ /// This type is cheap to hash and clone, but expensive to create.
196
+ #[derive(Debug, Hash, Eq, PartialEq, Clone)]
197
+ pub struct Image(Arc<ImageRepr>);
198
+
199
+ impl Image {
200
+ /// Create a new bitmap image from a `.png` file.
201
+ pub fn from_png(data: Data, interpolate: bool) -> Result<Image, String> {
202
+ let hash = data.as_ref().sip_hash();
203
+ let metadata = png_metadata(data.as_ref())?;
204
+
205
+ Ok(Self(Arc::new(ImageRepr {
206
+ inner: Deferred::new(move || decode_png(data.as_ref())),
207
+ metadata,
208
+ sip: hash,
209
+ interpolate,
210
+ })))
211
+ }
212
+
213
+ /// Create a new bitmap image from a `.jpg` file.
214
+ pub fn from_jpeg(data: Data, interpolate: bool) -> Result<Image, String> {
215
+ let hash = data.as_ref().sip_hash();
216
+ let metadata = jpeg_metadata(data.as_ref())?;
217
+
218
+ Ok(Self(Arc::new(ImageRepr {
219
+ inner: Deferred::new(move || decode_jpeg(data)),
220
+ metadata,
221
+ sip: hash,
222
+ interpolate,
223
+ })))
224
+ }
225
+
226
+ /// Create a new bitmap image from a `.jpg` file with custom ICC profile.
227
+ #[doc(hidden)]
228
+ pub fn from_jpeg_with_icc(
229
+ data: Data,
230
+ icc_profile: Option<Data>,
231
+ interpolate: bool,
232
+ ) -> Result<Image, String> {
233
+ let hash = data.as_ref().sip_hash();
234
+ let mut metadata = jpeg_metadata(data.as_ref())?;
235
+ let icc_profile =
236
+ icc_profile.and_then(|d| get_icc_profile_type(d.as_ref(), metadata.color_space));
237
+ metadata.icc = icc_profile;
238
+
239
+ Ok(Self(Arc::new(ImageRepr {
240
+ inner: Deferred::new(move || decode_jpeg(data)),
241
+ metadata,
242
+ sip: hash,
243
+ interpolate,
244
+ })))
245
+ }
246
+
247
+ /// Create a new bitmap image from a `.gif` file.
248
+ pub fn from_gif(data: Data, interpolate: bool) -> Result<Image, String> {
249
+ let hash = data.as_ref().sip_hash();
250
+ let metadata = gif_metadata(data.as_ref())?;
251
+
252
+ Ok(Self(Arc::new(ImageRepr {
253
+ inner: Deferred::new(move || decode_gif(data)),
254
+ metadata,
255
+ sip: hash,
256
+ interpolate,
257
+ })))
258
+ }
259
+
260
+ /// Create a new bitmap image from a `.webp` file.
261
+ ///
262
+ /// Returns `None` if krilla was unable to parse the file.
263
+ pub fn from_webp(data: Data, interpolate: bool) -> Result<Image, String> {
264
+ let hash = data.as_ref().sip_hash();
265
+ let metadata = webp_metadata(data.as_ref())?;
266
+
267
+ Ok(Self(Arc::new(ImageRepr {
268
+ inner: Deferred::new(move || decode_webp(data)),
269
+ metadata,
270
+ sip: hash,
271
+ interpolate,
272
+ })))
273
+ }
274
+
275
+ /// Create a new image from a custom image.
276
+ ///
277
+ /// Panics if the dimensions of the image and the length of the
278
+ /// data doesn't match.
279
+ pub fn from_custom<T: CustomImage>(image: T, interpolate: bool) -> Result<Image, String> {
280
+ let hash = (image.clone(), interpolate).sip_hash();
281
+ let metadata = ImageMetadata {
282
+ size: image.size(),
283
+ color_space: image.color_space(),
284
+ has_alpha: image.alpha_channel().is_some(),
285
+ bits_per_component: image.bits_per_component(),
286
+ icc: image
287
+ .icc_profile()
288
+ .and_then(|d| get_icc_profile_type(d, image.color_space())),
289
+ };
290
+
291
+ Ok(Self(Arc::new(ImageRepr {
292
+ inner: Deferred::new(move || {
293
+ let bytes_per_component = (image.bits_per_component().as_u8() / 8) as u32;
294
+ let color_channel_len = bytes_per_component
295
+ * image.color_space().num_components() as u32
296
+ * metadata.size.0
297
+ * metadata.size.1;
298
+ let color_channel = image.color_channel();
299
+ assert_eq!(color_channel.len(), color_channel_len as usize);
300
+
301
+ let alpha_channel_len = bytes_per_component * metadata.size.0 * metadata.size.1;
302
+ let alpha_channel = image.alpha_channel();
303
+ if let Some(alpha_channel) = alpha_channel {
304
+ assert_eq!(alpha_channel.len(), alpha_channel_len as usize);
305
+ }
306
+
307
+ Ok(Repr::Sampled(SampledRepr {
308
+ color_channel: deflate_encode(color_channel),
309
+ alpha_channel: image.alpha_channel().map(deflate_encode),
310
+ bits_per_component: image.bits_per_component(),
311
+ }))
312
+ }),
313
+ metadata,
314
+ sip: hash,
315
+ interpolate,
316
+ })))
317
+ }
318
+
319
+ /// Create a new RGB image from raw RGBA pixels.
320
+ pub fn from_rgba8(data: Vec<u8>, width: u32, height: u32) -> Self {
321
+ let hash = data.sip_hash();
322
+ let metadata = ImageMetadata {
323
+ has_alpha: true,
324
+ size: (width, height),
325
+ bits_per_component: BitsPerComponent::Eight,
326
+ color_space: ImageColorspace::Rgb,
327
+ icc: None,
328
+ };
329
+
330
+ Self(Arc::new(ImageRepr {
331
+ inner: Deferred::new(move || {
332
+ let (color_channel, alpha_channel, bits_per_component) =
333
+ handle_u8_image(&data, ColorSpace::RGBA);
334
+
335
+ Ok(Repr::Sampled(SampledRepr {
336
+ color_channel,
337
+ alpha_channel,
338
+ bits_per_component,
339
+ }))
340
+ }),
341
+ metadata,
342
+ sip: hash,
343
+ interpolate: false,
344
+ }))
345
+ }
346
+
347
+ /// Return the size of the image.
348
+ pub fn size(&self) -> (u32, u32) {
349
+ self.0.size()
350
+ }
351
+
352
+ fn icc(&self) -> Option<GenericICCProfile> {
353
+ self.0.icc()
354
+ }
355
+
356
+ fn color_space(&self) -> ImageColorspace {
357
+ self.0.color_space()
358
+ }
359
+
360
+ pub(crate) fn serialize(
361
+ self,
362
+ sc: &mut SerializeContext,
363
+ chunk_container: &mut ChunkContainer,
364
+ root_ref: Ref,
365
+ ) {
366
+ let soft_mask_id = self.0.metadata.has_alpha.then(|| {
367
+ sc.register_validation_error(ValidationError::Transparency(sc.location));
368
+ sc.new_ref()
369
+ });
370
+
371
+ let icc_ref = self.icc().and_then(|ic| {
372
+ if sc
373
+ .serialize_settings()
374
+ .pdf_version()
375
+ .supports_icc(ic.metadata())
376
+ && self.color_space().matches_icc_profile(&ic)
377
+ {
378
+ let ref_ = match ic {
379
+ GenericICCProfile::Luma(l) => {
380
+ sc.register_cacheable(chunk_container, ICCBasedColorSpace(l))
381
+ }
382
+ GenericICCProfile::Rgb(r) => {
383
+ sc.register_cacheable(chunk_container, ICCBasedColorSpace(r))
384
+ }
385
+ GenericICCProfile::Cmyk(c) => {
386
+ sc.register_cacheable(chunk_container, ICCBasedColorSpace(c))
387
+ }
388
+ };
389
+
390
+ Some(ref_)
391
+ } else {
392
+ // Don't embed ICC profiles from images if the current
393
+ // PDF version does not support it.
394
+ None
395
+ }
396
+ });
397
+
398
+ if self.0.interpolate {
399
+ sc.register_validation_error(ValidationError::ImageInterpolation(sc.location));
400
+ }
401
+
402
+ let serialize_settings = sc.serialize_settings().clone();
403
+
404
+ let cs = {
405
+ let cs = match self.color_space() {
406
+ ImageColorspace::Rgb => {
407
+ rgb::color_space(sc.serialize_settings().no_device_cs).into()
408
+ }
409
+ ImageColorspace::Luma => {
410
+ luma::color_space(sc.serialize_settings().no_device_cs).into()
411
+ }
412
+ ImageColorspace::Cmyk => match cmyk::color_space(&sc.serialize_settings()) {
413
+ None => {
414
+ sc.register_validation_error(ValidationError::MissingCMYKProfile);
415
+ crate::color::DeviceColorSpace::Cmyk.into()
416
+ }
417
+ Some(cs) => cs.into(),
418
+ },
419
+ };
420
+
421
+ sc.register_colorspace(chunk_container, cs)
422
+ };
423
+
424
+ let supports_bit_depth = sc
425
+ .serialize_settings()
426
+ .configuration
427
+ .version()
428
+ .supports_bit_depth(self.0.metadata.bits_per_component);
429
+ let location = sc.location;
430
+ let mut chunk = sc.new_chunk();
431
+
432
+ let chunk = Deferred::new(move || {
433
+ if !supports_bit_depth {
434
+ return Err(KrillaError::SixteenBitImage(self.clone(), location));
435
+ }
436
+
437
+ let repr = self
438
+ .0
439
+ .inner
440
+ .wait()
441
+ .as_ref()
442
+ .map_err(|e| KrillaError::Image(self.clone(), location, e.clone()))?;
443
+
444
+ let alpha_mask = match repr {
445
+ Repr::Sampled(sampled) => sampled.alpha_channel.as_ref().map(|mask_data| {
446
+ let soft_mask_id = soft_mask_id.unwrap();
447
+ let mask_stream = FilterStreamBuilder::new_from_deflated(mask_data)
448
+ .finish(&serialize_settings);
449
+ let mut s_mask = chunk.image_xobject(soft_mask_id, mask_stream.encoded_data());
450
+ mask_stream.write_filters(s_mask.deref_mut().deref_mut());
451
+ s_mask.width(self.size().0 as i32);
452
+ s_mask.height(self.size().1 as i32);
453
+ s_mask.pair(
454
+ Name(b"ColorSpace"),
455
+ // Mask color space must be device gray -- see Table 145.
456
+ DEVICE_GRAY.to_pdf_name(),
457
+ );
458
+
459
+ if self.0.interpolate {
460
+ s_mask.interpolate(true);
461
+ }
462
+
463
+ s_mask.bits_per_component(repr.bits_per_component().as_u8() as i32);
464
+ soft_mask_id
465
+ }),
466
+ Repr::Jpeg(_) => None,
467
+ };
468
+
469
+ let filter_stream = match repr {
470
+ Repr::Sampled(s) => FilterStreamBuilder::new_from_deflated(&s.color_channel)
471
+ .finish(&serialize_settings),
472
+ Repr::Jpeg(j) => FilterStreamBuilder::new_from_jpeg_data(j.data.as_ref())
473
+ .finish(&serialize_settings),
474
+ };
475
+
476
+ let mut image_x_object = chunk.image_xobject(root_ref, filter_stream.encoded_data());
477
+ filter_stream.write_filters(image_x_object.deref_mut().deref_mut());
478
+ image_x_object.width(self.size().0 as i32);
479
+ image_x_object.height(self.size().1 as i32);
480
+
481
+ if let Some(icc_ref) = icc_ref {
482
+ image_x_object.pair(Name(b"ColorSpace"), icc_ref);
483
+ } else {
484
+ set_colorspace(cs, image_x_object.deref_mut());
485
+ }
486
+
487
+ if self.0.interpolate {
488
+ image_x_object.interpolate(true);
489
+ }
490
+
491
+ // Photoshop CMYK images need to be inverted, see
492
+ // https://github.com/sile-typesetter/libtexpdf/blob/1891bee5e0b73165e4a259f910d3ea3fe1df0b42/jpegimage.c#L25-L51
493
+ // I'm not sure if this applies to all JPEG CMYK images out there, but for now we just
494
+ // always do it. In libtexpdf, they only seem to do it if they can find the Adobe APP
495
+ // marker.
496
+ if let Repr::Jpeg(j) = repr {
497
+ if j.invert_cmyk {
498
+ image_x_object
499
+ .decode([1.0, 0.0].repeat(self.color_space().num_components() as usize));
500
+ }
501
+ }
502
+
503
+ image_x_object.bits_per_component(repr.bits_per_component().as_u8() as i32);
504
+ if let Some(soft_mask_id) = alpha_mask {
505
+ image_x_object.s_mask(soft_mask_id);
506
+ }
507
+ image_x_object.finish();
508
+
509
+ Ok(chunk)
510
+ });
511
+
512
+ chunk_container.streams.images.push(chunk);
513
+ }
514
+ }
515
+
516
+ const PNG_TRANSFORMATIONS: Transformations = Transformations::EXPAND;
517
+
518
+ fn png_metadata(data: &[u8]) -> Result<ImageMetadata, String> {
519
+ let cursor = Cursor::new(data);
520
+ let mut decoder = png::Decoder::new(cursor);
521
+ decoder.set_transformations(PNG_TRANSFORMATIONS);
522
+ let reader = decoder
523
+ .read_info()
524
+ .map_err(|e| e.to_string().to_ascii_lowercase())?;
525
+ let info = reader.info();
526
+
527
+ let size = (info.width, info.height);
528
+ let (color_type, bit_depth) = reader.output_color_type();
529
+ let bits_per_component = match bit_depth {
530
+ // We will normalize to 8 when decoding.
531
+ BitDepth::One | BitDepth::Two | BitDepth::Four | BitDepth::Eight => BitsPerComponent::Eight,
532
+ BitDepth::Sixteen => BitsPerComponent::Sixteen,
533
+ };
534
+
535
+ let (image_color_space, has_alpha) = match color_type {
536
+ ColorType::Grayscale => (ImageColorspace::Luma, false),
537
+ ColorType::GrayscaleAlpha => (ImageColorspace::Luma, true),
538
+ ColorType::Rgb => (ImageColorspace::Rgb, false),
539
+ ColorType::Rgba => (ImageColorspace::Rgb, true),
540
+ ColorType::Indexed => {
541
+ return Err("image uses an indexed color space, which is unsupported".to_string())
542
+ }
543
+ };
544
+ let icc = info
545
+ .icc_profile
546
+ .as_ref()
547
+ .and_then(|i| get_icc_profile_type(i, image_color_space));
548
+
549
+ Ok(ImageMetadata {
550
+ has_alpha,
551
+ size,
552
+ bits_per_component,
553
+ color_space: image_color_space,
554
+ icc,
555
+ })
556
+ }
557
+
558
+ fn decode_png(data: &[u8]) -> Result<Repr, String> {
559
+ let cursor = Cursor::new(data);
560
+ let mut decoder = png::Decoder::new(cursor);
561
+ decoder.set_transformations(PNG_TRANSFORMATIONS);
562
+ let mut reader = decoder
563
+ .read_info()
564
+ .map_err(|e| e.to_string().to_ascii_lowercase())?;
565
+ let mut img_data = vec![0; reader.output_buffer_size().ok_or("image is too large")?];
566
+ let _ = reader
567
+ .next_frame(&mut img_data)
568
+ .map_err(|e| e.to_string())?;
569
+ let (color_type, bit_depth) = reader.output_color_type();
570
+
571
+ let color_space = match color_type {
572
+ ColorType::Rgb => ColorSpace::RGB,
573
+ ColorType::Rgba => ColorSpace::RGBA,
574
+ ColorType::Grayscale => ColorSpace::Luma,
575
+ ColorType::GrayscaleAlpha => ColorSpace::LumaA,
576
+ _ => unreachable!(),
577
+ };
578
+
579
+ let (color_channel, alpha_channel, bits_per_component) = match bit_depth {
580
+ BitDepth::Eight => handle_u8_image(&img_data, color_space),
581
+ BitDepth::Sixteen => handle_u16_image(&img_data, color_space),
582
+ _ => return Err("image has an unsupported bit-depth".to_string()),
583
+ };
584
+
585
+ Ok(Repr::Sampled(SampledRepr {
586
+ color_channel,
587
+ alpha_channel,
588
+ bits_per_component,
589
+ }))
590
+ }
591
+
592
+ fn jpeg_metadata(data: &[u8]) -> Result<ImageMetadata, String> {
593
+ let reader = Cursor::new(data);
594
+ let mut decoder = JpegDecoder::new(reader);
595
+ decoder
596
+ .decode_headers()
597
+ .map_err(|e| e.to_string().to_ascii_lowercase())?;
598
+
599
+ let size = {
600
+ let dimensions = decoder
601
+ .dimensions()
602
+ .ok_or("failed to read image dimensions".to_string())?;
603
+ (dimensions.0 as u32, dimensions.1 as u32)
604
+ };
605
+
606
+ let image_color_space = decoder
607
+ .input_colorspace()
608
+ .and_then(|c| c.try_into().ok())
609
+ .ok_or("failed to read image colorspace".to_string())?;
610
+
611
+ let icc = decoder
612
+ .icc_profile()
613
+ .and_then(|d| get_icc_profile_type(&d, image_color_space));
614
+
615
+ Ok(ImageMetadata {
616
+ has_alpha: false,
617
+ size,
618
+ bits_per_component: BitsPerComponent::Eight,
619
+ color_space: image_color_space,
620
+ icc,
621
+ })
622
+ }
623
+
624
+ fn decode_jpeg(data: Data) -> Result<Repr, String> {
625
+ let reader = Cursor::new(data.as_ref());
626
+ let mut decoder = JpegDecoder::new(reader);
627
+ decoder
628
+ .decode_headers()
629
+ .map_err(|e| e.to_string().to_ascii_lowercase())?;
630
+
631
+ let input_color_space = decoder
632
+ .input_colorspace()
633
+ .ok_or("failed to read image colorspace".to_string())?;
634
+
635
+ if matches!(
636
+ input_color_space,
637
+ ColorSpace::Luma
638
+ | ColorSpace::YCbCr
639
+ | ColorSpace::RGB
640
+ | ColorSpace::CMYK
641
+ | ColorSpace::YCCK
642
+ ) {
643
+ Ok(Repr::Jpeg(JpegRepr {
644
+ data,
645
+ bits_per_component: BitsPerComponent::Eight,
646
+ invert_cmyk: matches!(input_color_space, ColorSpace::YCCK | ColorSpace::CMYK),
647
+ }))
648
+ } else {
649
+ // JPEGs shouldn't be able to have a different color space?
650
+ Err("image has an unknown color space".to_string())
651
+ }
652
+ }
653
+
654
+ fn decode_gif(data: Data) -> Result<Repr, String> {
655
+ let mut decoder = gif::DecodeOptions::new();
656
+ decoder.set_color_output(gif::ColorOutput::RGBA);
657
+ let mut decoder = decoder
658
+ .read_info(data.as_ref())
659
+ .map_err(|e| e.to_string().to_ascii_lowercase())?;
660
+ let first_frame = decoder
661
+ .read_next_frame()
662
+ .map_err(|e| e.to_string())?
663
+ .ok_or("GIF image seems to be empty".to_string())?;
664
+
665
+ let (color_channel, alpha_channel, bits_per_component) =
666
+ handle_u8_image(first_frame.buffer.as_ref(), ColorSpace::RGBA);
667
+
668
+ Ok(Repr::Sampled(SampledRepr {
669
+ color_channel,
670
+ alpha_channel,
671
+ bits_per_component,
672
+ }))
673
+ }
674
+
675
+ fn gif_metadata(data: &[u8]) -> Result<ImageMetadata, String> {
676
+ let size = imagesize::blob_size(data).map_err(|e| e.to_string().to_ascii_lowercase())?;
677
+
678
+ Ok(ImageMetadata {
679
+ // We always decode GIFs using RGBA, see `decode_gif`
680
+ has_alpha: true,
681
+ bits_per_component: BitsPerComponent::Eight,
682
+ size: (size.width as u32, size.height as u32),
683
+ color_space: ImageColorspace::Rgb,
684
+ icc: None,
685
+ })
686
+ }
687
+
688
+ fn webp_metadata(data: &[u8]) -> Result<ImageMetadata, String> {
689
+ let mut decoder = image_webp::WebPDecoder::new(std::io::Cursor::new(data))
690
+ .map_err(|e| e.to_string().to_ascii_lowercase())?;
691
+ let size = decoder.dimensions();
692
+ let color_space = ImageColorspace::Rgb;
693
+ let icc = decoder
694
+ .icc_profile()
695
+ .map_err(|e| e.to_string().to_ascii_lowercase())?
696
+ .and_then(|d| get_icc_profile_type(&d, color_space));
697
+
698
+ Ok(ImageMetadata {
699
+ has_alpha: decoder.has_alpha(),
700
+ bits_per_component: BitsPerComponent::Eight,
701
+ size,
702
+ color_space,
703
+ icc,
704
+ })
705
+ }
706
+
707
+ fn decode_webp(data: Data) -> Result<Repr, String> {
708
+ let mut decoder = image_webp::WebPDecoder::new(std::io::Cursor::new(data.as_ref()))
709
+ .map_err(|e| e.to_string().to_ascii_lowercase())?;
710
+ let mut first_frame = vec![0; decoder.output_buffer_size().ok_or("image is too large")?];
711
+ decoder
712
+ .read_image(&mut first_frame)
713
+ .map_err(|e| e.to_string().to_ascii_lowercase())?;
714
+
715
+ let color_space = if decoder.has_alpha() {
716
+ ColorSpace::RGBA
717
+ } else {
718
+ ColorSpace::RGB
719
+ };
720
+
721
+ let (color_channel, alpha_channel, bits_per_component) =
722
+ handle_u8_image(&first_frame, color_space);
723
+
724
+ Ok(Repr::Sampled(SampledRepr {
725
+ color_channel,
726
+ alpha_channel,
727
+ bits_per_component,
728
+ }))
729
+ }
730
+
731
+ fn handle_u8_image(data: &[u8], cs: ColorSpace) -> (Vec<u8>, Option<Vec<u8>>, BitsPerComponent) {
732
+ let mut alphas = if cs.has_alpha() {
733
+ Vec::with_capacity(data.len() / cs.num_components())
734
+ } else {
735
+ Vec::new()
736
+ };
737
+
738
+ let color_channel = match cs {
739
+ ColorSpace::RGB => deflate_encode(data),
740
+ ColorSpace::RGBA => {
741
+ let mut buf = Vec::with_capacity(data.len() * 3 / 4);
742
+ data.chunks_exact(4).for_each(|data| {
743
+ buf.extend_from_slice(&data[0..3]);
744
+ alphas.push(data[3]);
745
+ });
746
+ deflate_encode(&buf)
747
+ }
748
+ ColorSpace::Luma => deflate_encode(data),
749
+ ColorSpace::LumaA => {
750
+ let mut buf = Vec::with_capacity(data.len() / 2);
751
+ data.chunks_exact(2).for_each(|data| {
752
+ buf.push(data[0]);
753
+ alphas.push(data[1]);
754
+ });
755
+ deflate_encode(&buf)
756
+ }
757
+ // PNG/WEBP/GIF only support those three, so should be enough?
758
+ _ => unimplemented!(),
759
+ };
760
+
761
+ let alpha_channel = if !alphas.is_empty() && alphas.iter().any(|v| *v != 255) {
762
+ Some(deflate_encode(&alphas))
763
+ } else {
764
+ None
765
+ };
766
+
767
+ (color_channel, alpha_channel, BitsPerComponent::Eight)
768
+ }
769
+
770
+ fn handle_u16_image(data: &[u8], cs: ColorSpace) -> (Vec<u8>, Option<Vec<u8>>, BitsPerComponent) {
771
+ let mut alphas = if cs.has_alpha() {
772
+ Vec::with_capacity(data.len() / cs.num_components())
773
+ } else {
774
+ Vec::new()
775
+ };
776
+
777
+ let encoded_image = match cs {
778
+ ColorSpace::RGB => deflate_encode(data),
779
+ ColorSpace::RGBA => {
780
+ let mut buf = Vec::with_capacity(data.len() * 3 / 4);
781
+ data.chunks_exact(8).for_each(|data| {
782
+ buf.extend_from_slice(&data[0..6]);
783
+ alphas.extend_from_slice(&data[6..]);
784
+ });
785
+ deflate_encode(&buf)
786
+ }
787
+ ColorSpace::Luma => deflate_encode(data),
788
+ ColorSpace::LumaA => {
789
+ let mut buf = Vec::with_capacity(data.len() / 2);
790
+ data.chunks_exact(4).for_each(|data| {
791
+ buf.extend_from_slice(&data[0..2]);
792
+ alphas.extend_from_slice(&data[2..]);
793
+ });
794
+ deflate_encode(&buf)
795
+ }
796
+ // PNG/WEBP/GIF only support those three, so should be enough?
797
+ _ => unimplemented!(),
798
+ };
799
+
800
+ let encoded_mask = if !alphas.is_empty() {
801
+ Some(deflate_encode(&alphas))
802
+ } else {
803
+ None
804
+ };
805
+
806
+ (encoded_image, encoded_mask, BitsPerComponent::Sixteen)
807
+ }
808
+
809
+ fn get_icc_profile_type(data: &[u8], color_space: ImageColorspace) -> Option<GenericICCProfile> {
810
+ let wrapper = match color_space {
811
+ ImageColorspace::Rgb => GenericICCProfile::Rgb(ICCProfile::new(data)?),
812
+ ImageColorspace::Luma => GenericICCProfile::Luma(ICCProfile::new(data)?),
813
+ ImageColorspace::Cmyk => GenericICCProfile::Cmyk(ICCProfile::new(data)?),
814
+ };
815
+
816
+ Some(wrapper)
817
+ }
818
+
819
+ #[cfg(test)]
820
+ mod tests {
821
+ use crate::image::Image;
822
+ use std::sync::Arc;
823
+
824
+ #[test]
825
+ fn invalid_png_image() {
826
+ let e = Image::from_png(Arc::new(b"dfngiudfg".to_vec()).into(), false);
827
+ assert!(e.is_err());
828
+ }
829
+
830
+ #[test]
831
+ fn invalid_jpeg_image() {
832
+ let e = Image::from_jpeg(Arc::new(b"dfngiudfg".to_vec()).into(), false);
833
+ assert!(e.is_err());
834
+ }
835
+ }