1use crate::ClipPath;
4use crate::cache::Cache;
5use crate::color::{Color, ColorSpace};
6use crate::context::Context;
7use crate::device::Device;
8use crate::font::Glyph;
9use crate::interpret::state::State;
10use crate::shading::Shading;
11use crate::soft_mask::SoftMask;
12use crate::{
13 FillProps, FillRule, InterpreterSettings, LumaData, Paint, PaintType, RgbData, StrokeProps,
14 interpret,
15};
16use hayro_syntax::content::TypedIter;
17use hayro_syntax::object::Dict;
18use hayro_syntax::object::Rect;
19use hayro_syntax::object::Stream;
20use hayro_syntax::object::dict::keys::{
21 BBOX, EXT_G_STATE, MATRIX, PAINT_TYPE, RESOURCES, SHADING, X_STEP, Y_STEP,
22};
23use hayro_syntax::object::{Object, dict_or_stream};
24use hayro_syntax::page::Resources;
25use hayro_syntax::xref::XRef;
26use kurbo::{Affine, BezPath, Shape};
27use log::warn;
28use std::fmt::{Debug, Formatter};
29use std::sync::Arc;
30
31#[derive(Debug, Clone)]
33pub enum Pattern<'a> {
34 Shading(ShadingPattern),
36 Tiling(Box<TilingPattern<'a>>),
38}
39
40impl<'a> Pattern<'a> {
41 pub(crate) fn new(
42 object: Object<'a>,
43 ctx: &Context<'a>,
44 resources: &Resources<'a>,
45 ) -> Option<Self> {
46 if let Some(dict) = object.clone().into_dict() {
47 Some(Self::Shading(ShadingPattern::new(&dict)?))
48 } else if let Some(stream) = object.clone().into_stream() {
49 Some(Self::Tiling(Box::new(TilingPattern::new(
50 stream, ctx, resources,
51 )?)))
52 } else {
53 None
54 }
55 }
56}
57
58#[derive(Clone, Debug)]
60pub struct ShadingPattern {
61 pub shading: Arc<Shading>,
63 pub matrix: Affine,
65}
66
67impl ShadingPattern {
68 pub(crate) fn new(dict: &Dict) -> Option<Self> {
69 let shading = dict.get::<Object>(SHADING).and_then(|o| {
70 let (dict, stream) = dict_or_stream(&o)?;
71
72 Shading::new(&dict, stream.as_ref())
73 })?;
74 let matrix = dict
75 .get::<[f64; 6]>(MATRIX)
76 .map(Affine::new)
77 .unwrap_or_default();
78
79 if dict.contains_key(EXT_G_STATE) {
80 warn!("shading patterns with ext_g_state are not supported yet");
81 }
82
83 Some(Self {
84 shading: Arc::new(shading),
85 matrix,
86 })
87 }
88}
89
90#[derive(Clone)]
92pub struct TilingPattern<'a> {
93 pub bbox: Rect,
95 pub x_step: f32,
97 pub y_step: f32,
99 pub matrix: Affine,
101 stream: Stream<'a>,
102 is_color: bool,
103 pub(crate) stroke_paint: Color,
104 pub(crate) non_stroking_paint: Color,
105 pub(crate) state: Box<State<'a>>,
106 pub(crate) parent_resources: Resources<'a>,
107 pub(crate) cache: Cache,
108 pub(crate) settings: InterpreterSettings,
109 pub(crate) xref: &'a XRef,
110}
111
112impl Debug for TilingPattern<'_> {
113 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
114 f.write_str("TilingPattern")
115 }
116}
117
118impl<'a> TilingPattern<'a> {
119 pub(crate) fn new(
120 stream: Stream<'a>,
121 ctx: &Context<'a>,
122 resources: &Resources<'a>,
123 ) -> Option<Self> {
124 let dict = stream.dict();
125
126 let bbox = dict.get::<Rect>(BBOX)?;
127 let x_step = dict.get::<f32>(X_STEP)?;
128 let y_step = dict.get::<f32>(Y_STEP)?;
129 let is_color = dict.get::<u8>(PAINT_TYPE)? == 1;
130 let matrix = dict
131 .get::<[f64; 6]>(MATRIX)
132 .map(Affine::new)
133 .unwrap_or_default();
134
135 let state = ctx.get();
136
137 let fill_cs = ctx
138 .get()
139 .none_stroke_cs
140 .pattern_cs()
141 .unwrap_or(ColorSpace::device_gray());
142 let stroke_cs = ctx
143 .get()
144 .stroke_cs
145 .pattern_cs()
146 .unwrap_or(ColorSpace::device_gray());
147
148 let non_stroking_paint = Color::new(
149 fill_cs,
150 state.non_stroke_color.clone(),
151 state.non_stroke_alpha,
152 );
153 let stroke_paint = Color::new(stroke_cs, state.stroke_color.clone(), state.stroke_alpha);
154
155 Some(Self {
156 bbox,
157 x_step,
158 y_step,
159 matrix,
160 is_color,
161 stream,
162 stroke_paint,
163 non_stroking_paint,
164 state: Box::new(ctx.get().clone()),
165 settings: ctx.settings.clone(),
166 parent_resources: resources.clone(),
167 cache: ctx.object_cache.clone(),
168 xref: ctx.xref,
169 })
170 }
171
172 pub fn interpret(
174 &self,
175 device: &mut impl Device,
176 initial_transform: Affine,
177 is_stroke: bool,
178 ) -> Option<()> {
179 let mut state = (*self.state).clone();
180 state.ctm = initial_transform;
181
182 let mut context = Context::new_with(
183 state.ctm,
184 kurbo::Rect::new(0.0, 0.0, 1.0, 1.0),
186 self.cache.clone(),
187 self.xref,
188 self.settings.clone(),
189 state,
190 );
191
192 let decoded = self.stream.decoded().ok()?;
193 let resources = Resources::from_parent(
194 self.stream.dict().get(RESOURCES).unwrap_or_default(),
195 self.parent_resources.clone(),
196 );
197 let iter = TypedIter::new(decoded.as_ref());
198
199 let clip_path = ClipPath {
200 path: initial_transform * self.bbox.to_path(0.1),
201 fill: FillRule::NonZero,
202 };
203 device.push_clip_path(&clip_path);
204
205 if self.is_color {
206 interpret(iter, &resources, &mut context, device);
207 } else {
208 let paint = if !is_stroke {
209 Paint {
210 paint_transform: Default::default(),
211 paint_type: PaintType::Color(self.non_stroking_paint.clone()),
212 }
213 } else {
214 Paint {
215 paint_transform: Default::default(),
216 paint_type: PaintType::Color(self.stroke_paint.clone()),
217 }
218 };
219
220 let mut device = StencilPatternDevice::new(device, &paint);
221 interpret(iter, &resources, &mut context, &mut device);
222 }
223
224 device.pop_clip_path();
225
226 Some(())
227 }
228}
229
230struct StencilPatternDevice<'a, T: Device> {
231 inner: &'a mut T,
232 paint: &'a Paint<'a>,
233}
234
235impl<'a, T: Device> StencilPatternDevice<'a, T> {
236 pub fn new(device: &'a mut T, paint: &'a Paint<'a>) -> Self {
237 Self {
238 inner: device,
239 paint,
240 }
241 }
242}
243
244impl<T: Device> Device for StencilPatternDevice<'_, T> {
246 fn set_transform(&mut self, affine: Affine) {
247 self.inner.set_transform(affine);
248 }
249
250 fn stroke_path(&mut self, path: &BezPath, _: &Paint) {
251 self.inner.stroke_path(path, self.paint)
252 }
253
254 fn set_stroke_properties(&mut self, stroke_props: &StrokeProps) {
255 self.inner.set_stroke_properties(stroke_props)
256 }
257
258 fn set_soft_mask(&mut self, _: Option<SoftMask>) {}
259
260 fn fill_path(&mut self, path: &BezPath, _: &Paint) {
261 self.inner.fill_path(path, self.paint)
262 }
263
264 fn set_fill_properties(&mut self, fill_props: &FillProps) {
265 self.inner.set_fill_properties(fill_props)
266 }
267
268 fn push_clip_path(&mut self, clip_path: &ClipPath) {
269 self.inner.push_clip_path(clip_path)
270 }
271
272 fn push_transparency_group(&mut self, _: f32, _: Option<SoftMask>) {}
273
274 fn fill_glyph(&mut self, glyph: &Glyph<'_>, _: &Paint) {
275 self.inner.fill_glyph(glyph, self.paint)
276 }
277
278 fn stroke_glyph(&mut self, glyph: &Glyph<'_>, _: &Paint) {
279 self.inner.stroke_glyph(glyph, self.paint)
280 }
281
282 fn draw_rgba_image(&mut self, _: RgbData, _: Option<LumaData>) {}
283
284 fn draw_stencil_image(&mut self, stencil: LumaData, _: &Paint) {
285 self.inner.draw_stencil_image(stencil, self.paint);
286 }
287
288 fn pop_clip_path(&mut self) {
289 self.inner.pop_clip_path();
290 }
291
292 fn pop_transparency_group(&mut self) {}
293}