Skip to main content

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 png;
9mod raw;
10
11use pdfrum_object::ObjRef;
12
13use crate::doc::EditDoc;
14use crate::error::Error;
15
16pub use raw::PixelFormat;
17
18/// An image `XObject` this session added, ready to place on a page.
19///
20/// [`EmbeddedImage::object`] is the `/XObject` a page resource names, and what
21/// `ImageBuilder::at` in the facade takes. The dimensions come back because
22/// they are the caller's only statement of the aspect ratio the placement
23/// rectangle should keep — a JPEG's are read out of its header, not supplied.
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub struct EmbeddedImage {
26    image: ObjRef,
27    width: u32,
28    height: u32,
29}
30
31impl EmbeddedImage {
32    /// The `/XObject` to name from a page resource.
33    #[must_use]
34    pub fn object(&self) -> ObjRef {
35        self.image
36    }
37
38    /// Width in samples.
39    #[must_use]
40    pub fn width(&self) -> u32 {
41        self.width
42    }
43
44    /// Height in samples.
45    #[must_use]
46    pub fn height(&self) -> u32 {
47        self.height
48    }
49}
50
51impl EditDoc<'_> {
52    /// Embed a JPEG or JPEG 2000 codestream as a new image `XObject`.
53    ///
54    /// The bytes become the stream verbatim under `/DCTDecode` or
55    /// `/JPXDecode`; nothing is decoded or re-encoded. `/Width`, `/Height`,
56    /// `/ColorSpace` and `/BitsPerComponent` are read from the codestream's
57    /// own header, which is the file's statement of them and overrides any a
58    /// caller could pass.
59    ///
60    /// # Errors
61    ///
62    /// [`Error::UnrecognisedImageData`] when the bytes are neither a JPEG nor
63    /// a JPEG 2000 codestream, or their header cannot be read.
64    pub fn embed_jpeg(&mut self, bytes: &[u8]) -> Result<EmbeddedImage, Error> {
65        jpeg::embed(self, bytes)
66    }
67
68    /// Embed a PNG as a new image `XObject`, its compressed data passed
69    /// through unchanged.
70    ///
71    /// PNG's `IDAT` stream is what `/FlateDecode` with the PNG predictors
72    /// reads, so a greyscale, RGB or palette PNG that is not interlaced is
73    /// stored as it came: no decode, no re-compression, no larger than the
74    /// file. `/Width`, `/Height`, `/ColorSpace` and `/BitsPerComponent` come
75    /// from its `IHDR` (and `PLTE`).
76    ///
77    /// # Errors
78    ///
79    /// [`Error::PngNeedsDecoding`] for a PNG PDF cannot take as stored — one
80    /// with an alpha channel or `tRNS` transparency (PDF keeps alpha in a
81    /// separate soft mask), an interlaced one, or 16 bits deep: decode it and
82    /// use [`EditDoc::embed_image`]. [`Error::UnrecognisedImageData`] when
83    /// the bytes are not a PNG.
84    ///
85    /// ```
86    /// use pdfrum_edit::{EditDoc, Error, Size, blank_document};
87    ///
88    /// let base = blank_document(&[Size::new(100.0, 100.0)])?;
89    /// let mut edit = EditDoc::new(&base);
90    /// assert!(matches!(edit.embed_png(b"not a png"), Err(Error::UnrecognisedImageData)));
91    /// # Ok::<(), pdfrum_edit::Error>(())
92    /// ```
93    pub fn embed_png(&mut self, bytes: &[u8]) -> Result<EmbeddedImage, Error> {
94        png::embed(self, bytes)
95    }
96
97    /// Embed raw interleaved samples as a new image `XObject`.
98    ///
99    /// The samples are stored uncompressed and the stream writer flate-encodes
100    /// them (there is no `/Filter` on the dictionary this writes). An
101    /// [`PixelFormat::Rgba8`] alpha channel is split off into a separate
102    /// `/DeviceGray` `/SMask` image; the colour channels keep their own
103    /// stream.
104    ///
105    /// # Errors
106    ///
107    /// [`Error::EmptyImage`] when either dimension is zero, and
108    /// [`Error::ImageDataLength`] when `pixels` is not exactly the length the
109    /// dimensions and format require.
110    pub fn embed_image(
111        &mut self,
112        pixels: &[u8],
113        width: u32,
114        height: u32,
115        format: PixelFormat,
116    ) -> Result<EmbeddedImage, Error> {
117        raw::embed(self, pixels, width, height, format)
118    }
119}