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 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}