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