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