pdfrum_edit/image/mod.rs
1//! Embed caller-supplied pixels, or a compressed codestream, as an image
2//! `XObject`.
3//!
4//! A caller hands over bytes — a JPEG codestream or raw interleaved samples —
5//! and this allocates the `/XObject` a content stream's `Do` can name.
6
7mod jpeg;
8mod raw;
9
10use pdfrum_object::ObjRef;
11
12use crate::doc::EditDoc;
13use crate::error::Error;
14
15pub use raw::PixelFormat;
16
17/// An image `XObject` this session added, ready to place on a page.
18///
19/// [`EmbeddedImage::object`] is the `/XObject` a page resource names, and what
20/// `ImageBuilder::at` in the facade takes. The dimensions come back because
21/// they are the caller's only statement of the aspect ratio the placement
22/// rectangle should keep — a JPEG's are read out of its header, not supplied.
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub struct EmbeddedImage {
25 image: ObjRef,
26 width: u32,
27 height: u32,
28}
29
30impl EmbeddedImage {
31 /// The `/XObject` to name from a page resource.
32 #[must_use]
33 pub fn object(&self) -> ObjRef {
34 self.image
35 }
36
37 /// Width in samples.
38 #[must_use]
39 pub fn width(&self) -> u32 {
40 self.width
41 }
42
43 /// Height in samples.
44 #[must_use]
45 pub fn height(&self) -> u32 {
46 self.height
47 }
48}
49
50impl EditDoc<'_> {
51 /// Embed a JPEG or JPEG 2000 codestream as a new image `XObject`.
52 ///
53 /// The bytes become the stream verbatim under `/DCTDecode` or
54 /// `/JPXDecode`; nothing is decoded or re-encoded. `/Width`, `/Height`,
55 /// `/ColorSpace` and `/BitsPerComponent` are read from the codestream's
56 /// own header, which is the file's statement of them and overrides any a
57 /// caller could pass.
58 ///
59 /// # Errors
60 ///
61 /// [`Error::UnrecognisedImageData`] when the bytes are neither a JPEG nor
62 /// a JPEG 2000 codestream, or their header cannot be read.
63 pub fn embed_jpeg(&mut self, bytes: &[u8]) -> Result<EmbeddedImage, Error> {
64 jpeg::embed(self, bytes)
65 }
66
67 /// Embed raw interleaved samples as a new image `XObject`.
68 ///
69 /// The samples are stored uncompressed and the stream writer flate-encodes
70 /// them (there is no `/Filter` on the dictionary this writes). An
71 /// [`PixelFormat::Rgba8`] alpha channel is split off into a separate
72 /// `/DeviceGray` `/SMask` image; the colour channels keep their own
73 /// stream.
74 ///
75 /// # Errors
76 ///
77 /// [`Error::EmptyImage`] when either dimension is zero, and
78 /// [`Error::ImageDataLength`] when `pixels` is not exactly the length the
79 /// dimensions and format require.
80 pub fn embed_image(
81 &mut self,
82 pixels: &[u8],
83 width: u32,
84 height: u32,
85 format: PixelFormat,
86 ) -> Result<EmbeddedImage, Error> {
87 raw::embed(self, pixels, width, height, format)
88 }
89}