Skip to main content

pdfrum_anyrender/
sources.rs

1//! What the caller knows that anyrender does not carry: the text behind each glyph run and the
2//! encoded bytes behind each decoded image.
3
4use crate::run_text::RunTexts;
5use std::collections::HashMap;
6use std::fmt;
7use std::sync::Arc;
8
9/// Everything a scene may look up while it paints; built once per document, shared by pages.
10#[derive(Debug, Default)]
11pub struct Sources {
12    /// The text behind each glyph run, where the caller could say.
13    pub texts: RunTexts,
14    /// The encoded source of each image the caller could trace, by the id of its decoded blob.
15    pub images: ImageSources,
16}
17
18/// How an image's source is encoded.
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
20pub enum ImageCodec {
21    /// Written to the PDF as it is (DCTDecode): never re-encoded, never larger than the source.
22    Jpeg,
23    /// Decoded and written losslessly (Flate), with its alpha as a soft mask.
24    Png,
25}
26
27impl ImageCodec {
28    /// The codec `bytes` start with, by their signature; `None` for anything else, which is
29    /// then drawn from its decoded pixels.
30    pub fn sniff(bytes: &[u8]) -> Option<Self> {
31        match bytes {
32            [0xFF, 0xD8, 0xFF, ..] => Some(ImageCodec::Jpeg),
33            [0x89, b'P', b'N', b'G', b'\r', b'\n', 0x1A, b'\n', ..] => Some(ImageCodec::Png),
34            _ => None,
35        }
36    }
37}
38
39/// An image's bytes as they arrived, before anything decoded them.
40#[derive(Clone)]
41pub struct EncodedImage {
42    codec: ImageCodec,
43    bytes: Arc<Vec<u8>>,
44}
45
46impl EncodedImage {
47    /// `bytes` if they are a JPEG or a PNG.
48    pub fn sniffed(bytes: Vec<u8>) -> Option<Self> {
49        let codec = ImageCodec::sniff(&bytes)?;
50        Some(EncodedImage {
51            codec,
52            bytes: Arc::new(bytes),
53        })
54    }
55
56    /// How the bytes are encoded.
57    pub fn codec(&self) -> ImageCodec {
58        self.codec
59    }
60
61    /// The bytes themselves.
62    pub fn bytes(&self) -> &Arc<Vec<u8>> {
63        &self.bytes
64    }
65}
66
67impl fmt::Debug for EncodedImage {
68    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
69        f.debug_struct("EncodedImage")
70            .field("codec", &self.codec)
71            .field("len", &self.bytes.len())
72            .finish()
73    }
74}
75
76/// Encoded sources keyed by the id of the decoded blob a brush paints
77/// (`peniko::Blob::id`): a renderer decodes once and paints that blob wherever the image
78/// appears, so the id names the image across every draw.
79#[derive(Debug, Default)]
80pub struct ImageSources {
81    by_blob: HashMap<u64, EncodedImage>,
82}
83
84impl ImageSources {
85    /// Record that the blob `blob` was decoded from `source`.
86    pub fn insert(&mut self, blob: u64, source: EncodedImage) {
87        self.by_blob.insert(blob, source);
88    }
89
90    /// The source of the blob `blob`, if it was recorded.
91    pub fn get(&self, blob: u64) -> Option<&EncodedImage> {
92        self.by_blob.get(&blob)
93    }
94
95    /// How many images have a recorded source.
96    pub fn len(&self) -> usize {
97        self.by_blob.len()
98    }
99
100    /// Whether no image has a recorded source.
101    pub fn is_empty(&self) -> bool {
102        self.by_blob.is_empty()
103    }
104}
105
106#[cfg(test)]
107mod tests {
108    use super::ImageCodec;
109
110    const CASES: &[(&[u8], Option<ImageCodec>)] = &[
111        (&[0xFF, 0xD8, 0xFF, 0xE0], Some(ImageCodec::Jpeg)),
112        (b"\x89PNG\r\n\x1a\nrest", Some(ImageCodec::Png)),
113        (b"GIF89a", None),
114        (b"", None),
115    ];
116
117    #[test]
118    fn codecs_are_sniffed_by_signature() {
119        for &(bytes, want) in CASES {
120            assert_eq!(ImageCodec::sniff(bytes), want, "{bytes:?}");
121        }
122    }
123}