Skip to main content

hayro_interpret/
types.rs

1use crate::CacheKey;
2use crate::color::Color;
3use crate::pattern::Pattern;
4use crate::util::hash128;
5use crate::x_object::ImageXObject;
6use crate::x_object::soft_mask::SoftMask;
7use hayro_syntax::object::Stream;
8use kurbo::{Affine, BezPath, Cap, Join};
9use smallvec::{SmallVec, smallvec};
10
11/// A clip path.
12#[derive(Debug, Clone)]
13pub struct ClipPath {
14    /// The clipping path.
15    pub path: BezPath,
16    /// The fill rule.
17    pub fill: FillRule,
18}
19
20impl CacheKey for ClipPath {
21    fn cache_key(&self) -> u128 {
22        hash128(&(&self.path.to_svg(), &self.fill))
23    }
24}
25
26/// A stencil image.
27pub struct StencilImage<'a, 'b> {
28    pub(crate) paint: Paint<'a>,
29    pub(crate) image_xobject: ImageXObject<'b>,
30}
31
32impl<'a, 'b> StencilImage<'a, 'b> {
33    /// Perform some operation with the stencil data of the image.
34    ///
35    /// The second argument allows you to give the image decoder a hint for
36    /// what resolution of the image you want to have. Note that this does not
37    /// mean that the resulting image will have that dimension. Instead, it allows
38    /// the image decoder to extract a lower-resolution version of the image in
39    /// certain cases.
40    pub fn with_stencil(
41        &self,
42        func: impl FnOnce(LumaData, &Paint<'a>),
43        target_dimension: Option<(u32, u32)>,
44    ) {
45        if let Some(decoded) = self.image_xobject.decoded_mask(target_dimension) {
46            func(decoded.luma, &self.paint);
47        }
48    }
49
50    // These are hidden since clients are supposed to call get the
51    // width/height from `LumaData` instead.
52    #[doc(hidden)]
53    pub fn width(&self) -> u32 {
54        self.image_xobject.width()
55    }
56
57    #[doc(hidden)]
58    pub fn height(&self) -> u32 {
59        self.image_xobject.height()
60    }
61}
62
63impl CacheKey for StencilImage<'_, '_> {
64    fn cache_key(&self) -> u128 {
65        self.image_xobject.cache_key()
66    }
67}
68
69/// A raster image.
70pub struct RasterImage<'a>(pub(crate) ImageXObject<'a>);
71
72impl RasterImage<'_> {
73    /// Perform some operation with the RGB and alpha channel of the image.
74    ///
75    /// The second argument allows you to give the image decoder a hint for
76    /// what resolution of the image you want to have. Note that this does not
77    /// mean that the resulting image will have that dimension. Instead, it allows
78    /// the image decoder to extract a lower-resolution version of the image in
79    /// certain cases.
80    pub fn with_rgba(
81        &self,
82        func: impl FnOnce(ImageData, Option<LumaData>),
83        target_dimension: Option<(u32, u32)>,
84    ) {
85        if let Some(decoded) = self.0.decoded_image(target_dimension) {
86            func(decoded.image, decoded.alpha);
87        }
88    }
89
90    /// Return the underlying stream object.
91    ///
92    /// This allows you to get access to the raw encoded image data, without doing any decoding.
93    pub fn stream(&self) -> &Stream<'_> {
94        self.0.stream()
95    }
96
97    // These are hidden since clients are supposed to call get the
98    // width/height from `LumaData` instead.
99    #[doc(hidden)]
100    pub fn width(&self) -> u32 {
101        self.0.width()
102    }
103
104    #[doc(hidden)]
105    pub fn height(&self) -> u32 {
106        self.0.height()
107    }
108}
109
110impl CacheKey for RasterImage<'_> {
111    fn cache_key(&self) -> u128 {
112        self.0.cache_key()
113    }
114}
115
116/// A type of image.
117pub enum Image<'a, 'b> {
118    /// A stencil image.
119    Stencil(StencilImage<'a, 'b>),
120    /// A normal raster image.
121    Raster(RasterImage<'b>),
122}
123
124impl Image<'_, '_> {
125    // These are hidden since clients are supposed to call get the
126    // width/height from `LumaData/RgbData` instead.
127    #[doc(hidden)]
128    pub fn width(&self) -> u32 {
129        match self {
130            Image::Stencil(s) => s.width(),
131            Image::Raster(r) => r.width(),
132        }
133    }
134
135    // These are hidden since clients are supposed to call get the
136    // width/height from `LumaData/RgbData` instead.
137    #[doc(hidden)]
138    pub fn height(&self) -> u32 {
139        match self {
140            Image::Stencil(s) => s.height(),
141            Image::Raster(r) => r.height(),
142        }
143    }
144}
145
146impl CacheKey for Image<'_, '_> {
147    fn cache_key(&self) -> u128 {
148        match self {
149            Image::Stencil(i) => i.cache_key(),
150            Image::Raster(i) => i.cache_key(),
151        }
152    }
153}
154
155/// A structure holding 3-channel RGB data.
156#[derive(Clone)]
157pub struct RgbData {
158    /// The actual data. It is guaranteed to have the length width * height * 3.
159    pub data: Vec<u8>,
160    /// The width.
161    pub width: u32,
162    /// The height.
163    pub height: u32,
164    /// Whether the image should be interpolated.
165    pub interpolate: bool,
166    /// Additional scaling factors to apply to the image.
167    ///
168    /// In most cases, those factors will just be 1.0, and you can
169    /// ignore them. There are two situations in which they will not be equal
170    /// to 1:
171    /// 1) The PDF provided wrong metadata about the width/height of the image,
172    ///    which needs to be corrected
173    /// 2) A lower resolution of the image was requested, in which case it needs
174    ///    to be scaled up so that it still covers the same area.
175    ///
176    /// The first number indicates the x scaling factor, the second number the
177    /// y scaling factor.
178    pub scale_factors: (f32, f32),
179}
180
181/// A structure holding 1-channel luma data.
182#[derive(Clone)]
183pub struct LumaData {
184    /// The actual data. It is guaranteed to have the length width * height.
185    pub data: Vec<u8>,
186    /// The width.
187    pub width: u32,
188    /// The height.
189    pub height: u32,
190    /// Whether the image should be interpolated.
191    pub interpolate: bool,
192    /// Additional scaling factors to apply to the image.
193    ///
194    /// In most cases, those factors will just be 1.0, and you can
195    /// ignore them. There are two situations in which they will not be equal
196    /// to 1:
197    /// 1) The PDF provided wrong metadata about the width/height of the image,
198    ///    which needs to be corrected
199    /// 2) A lower resolution of the image was requested, in which case it needs
200    ///    to be scaled up so that it still covers the same area.
201    ///
202    /// The first number indicates the x scaling factor, the second number the
203    /// y scaling factor.
204    pub scale_factors: (f32, f32),
205}
206
207/// The color data of a raster image, either 3-channel RGB or 1-channel luma.
208#[derive(Clone)]
209pub enum ImageData {
210    /// 3-channel RGB data.
211    Rgb(RgbData),
212    /// 1-channel grayscale data.
213    Luma(LumaData),
214}
215
216impl ImageData {
217    /// The width of the image.
218    pub fn width(&self) -> u32 {
219        match self {
220            Self::Rgb(d) => d.width,
221            Self::Luma(d) => d.width,
222        }
223    }
224
225    /// The height of the image.
226    pub fn height(&self) -> u32 {
227        match self {
228            Self::Rgb(d) => d.height,
229            Self::Luma(d) => d.height,
230        }
231    }
232
233    /// Whether the image should be interpolated.
234    pub fn interpolate(&self) -> bool {
235        match self {
236            Self::Rgb(d) => d.interpolate,
237            Self::Luma(d) => d.interpolate,
238        }
239    }
240
241    /// The scaling factors of the image.
242    pub fn scale_factors(&self) -> (f32, f32) {
243        match self {
244            Self::Rgb(d) => d.scale_factors,
245            Self::Luma(d) => d.scale_factors,
246        }
247    }
248}
249
250/// A type of paint.
251#[derive(Clone, Debug)]
252pub enum Paint<'a> {
253    /// A solid RGBA color.
254    Color(Color),
255    /// A PDF pattern.
256    Pattern(Box<Pattern<'a>>),
257}
258
259impl CacheKey for Paint<'_> {
260    fn cache_key(&self) -> u128 {
261        match self {
262            Paint::Color(c) => {
263                // TODO: We should actually cache the color with color space etc., not just the
264                // RGBA8 version.
265                hash128(&c.to_rgba().to_rgba8())
266            }
267            Paint::Pattern(p) => p.cache_key(),
268        }
269    }
270}
271
272/// Properties for a painted drawing operation.
273#[derive(Clone)]
274pub struct DrawProps<'a> {
275    /// The transform.
276    pub transform: Affine,
277    /// The paint.
278    pub paint: Paint<'a>,
279    /// The soft mask.
280    pub soft_mask: Option<SoftMask<'a>>,
281    /// The blend mode.
282    pub blend_mode: BlendMode,
283}
284
285/// Properties for an image drawing operation.
286#[derive(Clone)]
287pub struct ImageDrawProps<'a> {
288    /// The transform.
289    pub transform: Affine,
290    /// The soft mask.
291    pub soft_mask: Option<SoftMask<'a>>,
292    /// The blend mode.
293    pub blend_mode: BlendMode,
294}
295
296/// The draw mode.
297#[derive(Clone, Debug)]
298pub enum DrawMode {
299    /// Draw using a fill.
300    Fill(FillRule),
301    /// Draw using a stroke.
302    Stroke(StrokeProps),
303    /// Draw using both fill and stroke.
304    FillAndStroke(FillRule, StrokeProps),
305    /// Invisible text (for text extraction but not visual rendering).
306    Invisible,
307}
308
309/// Stroke properties.
310#[derive(Clone, Debug)]
311pub struct StrokeProps {
312    /// The line width.
313    pub line_width: f32,
314    /// The line cap.
315    pub line_cap: Cap,
316    /// The line join.
317    pub line_join: Join,
318    /// The miter limit.
319    pub miter_limit: f32,
320    /// The dash array.
321    pub dash_array: SmallVec<[f32; 4]>,
322    /// The dash offset.
323    pub dash_offset: f32,
324}
325
326impl Default for StrokeProps {
327    fn default() -> Self {
328        Self {
329            line_width: 1.0,
330            line_cap: Cap::Butt,
331            line_join: Join::Miter,
332            miter_limit: 10.0,
333            dash_array: smallvec![],
334            dash_offset: 0.0,
335        }
336    }
337}
338
339/// A fill rule.
340#[derive(Clone, Debug, Copy, Hash, PartialEq, Eq)]
341pub enum FillRule {
342    /// Non-zero filling.
343    NonZero,
344    /// Even-odd filling.
345    EvenOdd,
346}
347
348/// A blend mode.
349#[derive(Clone, Debug, Copy, Hash, PartialEq, Eq, Default)]
350pub enum BlendMode {
351    /// Normal blend mode (default).
352    #[default]
353    Normal,
354    /// Multiply blend mode.
355    Multiply,
356    /// Screen blend mode.
357    Screen,
358    /// Overlay blend mode.
359    Overlay,
360    /// Darken blend mode.
361    Darken,
362    /// Lighten blend mode.
363    Lighten,
364    /// `ColorDodge` blend mode.
365    ColorDodge,
366    /// `ColorBurn` blend mode.
367    ColorBurn,
368    /// `HardLight` blend mode.
369    HardLight,
370    /// `SoftLight` blend mode.
371    SoftLight,
372    /// Difference blend mode.
373    Difference,
374    /// Exclusion blend mode.
375    Exclusion,
376    /// Hue blend mode.
377    Hue,
378    /// Saturation blend mode.
379    Saturation,
380    /// Color blend mode.
381    Color,
382    /// Luminosity blend mode.
383    Luminosity,
384}