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