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}