1use crate::cache::Cache;
4use crate::color::{Color, ColorSpace};
5use crate::context::Context;
6use crate::device::Device;
7use crate::font::Glyph;
8use crate::interpret::state::State;
9use crate::shading::Shading;
10use crate::soft_mask::SoftMask;
11use crate::util::{Float32Ext, RectExt, hash128};
12use crate::{BlendMode, CacheKey, ClipPath, GlyphDrawMode, Image, PathDrawMode};
13use crate::{FillRule, InterpreterSettings, Paint, interpret};
14use hayro_syntax::content::TypedIter;
15use hayro_syntax::object::Dict;
16use hayro_syntax::object::Stream;
17use hayro_syntax::object::dict::keys::{
18 BBOX, EXT_G_STATE, MATRIX, PAINT_TYPE, RESOURCES, SHADING, X_STEP, Y_STEP,
19};
20use hayro_syntax::object::{Object, dict_or_stream};
21use hayro_syntax::page::Resources;
22use hayro_syntax::xref::XRef;
23use kurbo::{Affine, BezPath, Rect, Shape};
24use log::warn;
25use std::fmt::{Debug, Formatter};
26use std::sync::Arc;
27
28#[derive(Debug, Clone)]
30pub enum Pattern<'a> {
31 Shading(ShadingPattern),
33 Tiling(Box<TilingPattern<'a>>),
35}
36
37impl<'a> Pattern<'a> {
38 pub(crate) fn new(
39 object: Object<'a>,
40 ctx: &Context<'a>,
41 resources: &Resources<'a>,
42 ) -> Option<Self> {
43 if let Some(dict) = object.clone().into_dict() {
44 Some(Self::Shading(ShadingPattern::new(
45 &dict,
46 &ctx.object_cache,
47 ctx.get().graphics_state.non_stroke_alpha,
48 )?))
49 } else if let Some(stream) = object.clone().into_stream() {
50 Some(Self::Tiling(Box::new(TilingPattern::new(
51 stream, ctx, resources,
52 )?)))
53 } else {
54 None
55 }
56 }
57
58 pub(crate) fn pre_concat_transform(&mut self, transform: Affine) {
59 match self {
60 Self::Shading(p) => {
61 p.matrix = transform * p.matrix;
62 let transformed_clip_path = p.shading.clip_path.clone().map(|r| p.matrix * r);
63 Arc::make_mut(&mut p.shading).clip_path = transformed_clip_path;
64 }
65 Self::Tiling(p) => p.matrix = transform * p.matrix,
66 }
67 }
68}
69
70impl CacheKey for Pattern<'_> {
71 fn cache_key(&self) -> u128 {
72 match self {
73 Self::Shading(p) => p.cache_key(),
74 Self::Tiling(p) => p.cache_key(),
75 }
76 }
77}
78
79#[derive(Clone, Debug)]
81pub struct ShadingPattern {
82 pub shading: Arc<Shading>,
84 pub matrix: Affine,
86 pub opacity: f32,
88}
89
90impl ShadingPattern {
91 pub(crate) fn new(dict: &Dict<'_>, cache: &Cache, opacity: f32) -> Option<Self> {
92 let shading = dict.get::<Object<'_>>(SHADING).and_then(|o| {
93 let (dict, stream) = dict_or_stream(&o)?;
94
95 Shading::new(&dict, stream.as_ref(), cache)
96 })?;
97 let matrix = dict
98 .get::<[f64; 6]>(MATRIX)
99 .map(Affine::new)
100 .unwrap_or_default();
101
102 if dict.contains_key(EXT_G_STATE) {
103 warn!("shading patterns with ext_g_state are not supported yet");
104 }
105
106 Some(Self {
107 shading: Arc::new(shading),
108 opacity,
109 matrix,
110 })
111 }
112}
113
114impl CacheKey for ShadingPattern {
115 fn cache_key(&self) -> u128 {
116 hash128(&(self.shading.cache_key(), self.matrix.cache_key()))
117 }
118}
119
120#[derive(Clone)]
122pub struct TilingPattern<'a> {
123 cache_key: u128,
124 ctx_bbox: Rect,
125 pub bbox: Rect,
127 pub x_step: f32,
129 pub y_step: f32,
131 pub matrix: Affine,
133 stream: Stream<'a>,
134 is_color: bool,
135 pub(crate) stroke_paint: Color,
136 pub(crate) non_stroking_paint: Color,
137 pub(crate) parent_resources: Resources<'a>,
138 pub(crate) cache: Cache,
139 pub(crate) settings: InterpreterSettings,
140 pub(crate) xref: &'a XRef,
141}
142
143impl Debug for TilingPattern<'_> {
144 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
145 f.write_str("TilingPattern")
146 }
147}
148
149impl<'a> TilingPattern<'a> {
150 pub(crate) fn new(
151 stream: Stream<'a>,
152 ctx: &Context<'a>,
153 resources: &Resources<'a>,
154 ) -> Option<Self> {
155 let cache_key = stream.cache_key();
156 let dict = stream.dict();
157
158 let bbox = dict.get::<hayro_syntax::object::Rect>(BBOX)?.to_kurbo();
159 let x_step = dict.get::<f32>(X_STEP)?;
160 let y_step = dict.get::<f32>(Y_STEP)?;
161
162 if x_step.is_nearly_zero() || y_step.is_nearly_zero() || bbox.is_zero_area() {
163 return None;
164 }
165
166 let is_color = dict.get::<u8>(PAINT_TYPE)? == 1;
167 let matrix = dict
168 .get::<[f64; 6]>(MATRIX)
169 .map(Affine::new)
170 .unwrap_or_default();
171
172 let state = ctx.get().clone();
173 let ctx_bbox = ctx.bbox();
174
175 let fill_cs = state
176 .graphics_state
177 .none_stroke_cs
178 .pattern_cs()
179 .unwrap_or(ColorSpace::device_gray());
180 let stroke_cs = state
181 .graphics_state
182 .stroke_cs
183 .pattern_cs()
184 .unwrap_or(ColorSpace::device_gray());
185
186 let non_stroking_paint = Color::new(
187 fill_cs,
188 state.graphics_state.non_stroke_color.clone(),
189 state.graphics_state.non_stroke_alpha,
190 );
191 let stroke_paint = Color::new(
192 stroke_cs,
193 state.graphics_state.stroke_color.clone(),
194 state.graphics_state.stroke_alpha,
195 );
196
197 Some(Self {
198 cache_key,
199 bbox,
200 x_step,
201 y_step,
202 matrix,
203 ctx_bbox,
204 is_color,
205 stream,
206 stroke_paint,
207 non_stroking_paint,
208 settings: ctx.settings.clone(),
209 parent_resources: resources.clone(),
210 cache: ctx.object_cache.clone(),
211 xref: ctx.xref,
212 })
213 }
214
215 pub fn interpret(
217 &self,
218 device: &mut impl Device<'a>,
219 initial_transform: Affine,
220 is_stroke: bool,
221 ) -> Option<()> {
222 let state = State::new(initial_transform);
223
224 let mut context = Context::new_with(
225 state.ctm,
226 (initial_transform * self.ctx_bbox.to_path(0.1)).bounding_box(),
228 self.cache.clone(),
229 self.xref,
230 self.settings.clone(),
231 state,
232 );
233
234 let decoded = self.stream.decoded().ok()?;
235 let resources = Resources::from_parent(
236 self.stream.dict().get(RESOURCES).unwrap_or_default(),
237 self.parent_resources.clone(),
238 );
239 let iter = TypedIter::new(decoded.as_ref());
240
241 let clip_path = ClipPath {
242 path: initial_transform * self.bbox.to_path(0.1),
243 fill: FillRule::NonZero,
244 };
245 device.push_clip_path(&clip_path);
246
247 if self.is_color {
248 interpret(iter, &resources, &mut context, device);
249 } else {
250 let paint = if !is_stroke {
251 Paint::Color(self.non_stroking_paint.clone())
252 } else {
253 Paint::Color(self.stroke_paint.clone())
254 };
255
256 let mut device = StencilPatternDevice::new(device, paint.clone());
257 interpret(iter, &resources, &mut context, &mut device);
258 }
259
260 device.pop_clip_path();
261
262 Some(())
263 }
264}
265
266impl CacheKey for TilingPattern<'_> {
267 fn cache_key(&self) -> u128 {
268 self.cache_key
269 }
270}
271
272struct StencilPatternDevice<'a, 'b, T: Device<'a>> {
273 inner: &'b mut T,
274 paint: Paint<'a>,
275}
276
277impl<'a, 'b, T: Device<'a>> StencilPatternDevice<'a, 'b, T> {
278 pub(crate) fn new(device: &'b mut T, paint: Paint<'a>) -> Self {
279 Self {
280 inner: device,
281 paint,
282 }
283 }
284}
285
286impl<'a, T: Device<'a>> Device<'a> for StencilPatternDevice<'a, '_, T> {
288 fn draw_path(
289 &mut self,
290 path: &BezPath,
291 transform: Affine,
292 _: &Paint<'_>,
293 draw_mode: &PathDrawMode,
294 ) {
295 self.inner
296 .draw_path(path, transform, &self.paint, draw_mode);
297 }
298
299 fn set_soft_mask(&mut self, _: Option<SoftMask<'_>>) {}
300
301 fn push_clip_path(&mut self, clip_path: &ClipPath) {
302 self.inner.push_clip_path(clip_path);
303 }
304
305 fn push_transparency_group(&mut self, _: f32, _: Option<SoftMask<'_>>, _: BlendMode) {}
306
307 fn draw_glyph(
308 &mut self,
309 g: &Glyph<'a>,
310 transform: Affine,
311 glyph_transform: Affine,
312 p: &Paint<'a>,
313 draw_mode: &GlyphDrawMode,
314 ) {
315 self.inner
316 .draw_glyph(g, transform, glyph_transform, p, draw_mode);
317 }
318
319 fn draw_image(&mut self, image: Image<'a, '_>, transform: Affine) {
320 if let Image::Stencil(mut s) = image {
321 s.paint = self.paint.clone();
322 self.inner.draw_image(Image::Stencil(s), transform);
323 }
324 }
325
326 fn pop_clip_path(&mut self) {
327 self.inner.pop_clip_path();
328 }
329
330 fn pop_transparency_group(&mut self) {}
331
332 fn set_blend_mode(&mut self, _: BlendMode) {}
333}