1use crate::WarningSinkFn;
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::{ActiveTransferFunction, State};
10use crate::shading::Shading;
11use crate::soft_mask::SoftMask;
12use crate::util::{Float32Ext, RectExt, decode_or_warn, hash128};
13use crate::{BlendMode, CacheKey, ClipPath, GlyphDrawMode, Image, PathDrawMode};
14use crate::{FillRule, InterpreterSettings, Paint, interpret};
15use kurbo::{Affine, BezPath, Rect, Shape};
16use log::warn;
17use pdf_syntax::content::TypedIter;
18use pdf_syntax::object::Dict;
19use pdf_syntax::object::Stream;
20use pdf_syntax::object::dict::keys::{
21 BBOX, EXT_G_STATE, MATRIX, PAINT_TYPE, RESOURCES, SHADING, X_STEP, Y_STEP,
22};
23use pdf_syntax::object::{Object, dict_or_stream};
24use pdf_syntax::page::Resources;
25use pdf_syntax::xref::XRef;
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 ctx.get().graphics_state.non_stroke_alpha,
49 &ctx.settings.warning_sink,
50 )?))
51 } else if let Some(stream) = object.clone().into_stream() {
52 Some(Self::Tiling(Box::new(TilingPattern::new(
53 stream, ctx, resources,
54 )?)))
55 } else {
56 None
57 }
58 }
59
60 pub(crate) fn pre_concat_transform(&mut self, transform: Affine) {
61 match self {
62 Self::Shading(p) => {
63 p.matrix = transform * p.matrix;
64 let transformed_clip_path = p.shading.clip_path.clone().map(|r| p.matrix * r);
65 Arc::make_mut(&mut p.shading).clip_path = transformed_clip_path;
66 }
67 Self::Tiling(p) => p.matrix = transform * p.matrix,
68 }
69 }
70
71 pub(crate) fn set_transfer_function(&mut self, tf: ActiveTransferFunction) {
72 if let Self::Shading(p) = self {
73 p.transfer_function = Some(tf);
74 }
75 }
76}
77
78impl CacheKey for Pattern<'_> {
79 fn cache_key(&self) -> u128 {
80 match self {
81 Self::Shading(p) => p.cache_key(),
82 Self::Tiling(p) => p.cache_key(),
83 }
84 }
85}
86
87#[derive(Clone, Debug)]
89pub struct ShadingPattern {
90 pub shading: Arc<Shading>,
92 pub matrix: Affine,
94 pub opacity: f32,
96 pub transfer_function: Option<ActiveTransferFunction>,
98}
99
100impl ShadingPattern {
101 pub(crate) fn new(
102 dict: &Dict<'_>,
103 cache: &Cache,
104 opacity: f32,
105 warning_sink: &WarningSinkFn,
106 ) -> Option<Self> {
107 let shading = dict.get::<Object<'_>>(SHADING).and_then(|o| {
108 let (dict, stream) = dict_or_stream(&o)?;
109
110 Shading::new(&dict, stream.as_ref(), cache, warning_sink)
111 })?;
112 let matrix = dict
113 .get::<[f64; 6]>(MATRIX)
114 .map(Affine::new)
115 .unwrap_or_default();
116
117 if dict.contains_key(EXT_G_STATE) {
118 warn!("shading patterns with ext_g_state are not supported yet");
119 }
120
121 Some(Self {
122 shading: Arc::new(shading),
123 opacity,
124 matrix,
125 transfer_function: None,
126 })
127 }
128}
129
130impl CacheKey for ShadingPattern {
131 fn cache_key(&self) -> u128 {
132 hash128(&(self.shading.cache_key(), self.matrix.cache_key()))
133 }
134}
135
136#[derive(Clone)]
138pub struct TilingPattern<'a> {
139 nesting_depth: u32,
143 cache_key: u128,
144 ctx_bbox: Rect,
145 pub bbox: Rect,
147 pub x_step: f32,
149 pub y_step: f32,
151 pub matrix: Affine,
153 stream: Stream<'a>,
154 pub is_color: bool,
156 pub opacity: f32,
161 pub(crate) stroke_paint: Color,
162 pub(crate) non_stroking_paint: Color,
163 pub(crate) parent_resources: Resources<'a>,
164 pub(crate) cache: Cache,
165 pub(crate) settings: InterpreterSettings,
166 pub(crate) xref: &'a XRef,
167}
168
169impl Debug for TilingPattern<'_> {
170 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
171 f.write_str("TilingPattern")
172 }
173}
174
175impl<'a> TilingPattern<'a> {
176 pub(crate) fn new(
177 stream: Stream<'a>,
178 ctx: &Context<'a>,
179 resources: &Resources<'a>,
180 ) -> Option<Self> {
181 let cache_key = stream.cache_key();
182 let dict = stream.dict();
183
184 let bbox = dict.get::<pdf_syntax::object::Rect>(BBOX)?.to_kurbo();
185 let x_step = dict.get::<f32>(X_STEP)?;
186 let y_step = dict.get::<f32>(Y_STEP)?;
187
188 if x_step.is_nearly_zero() || y_step.is_nearly_zero() || bbox.is_zero_area() {
189 return None;
190 }
191
192 let is_color = dict.get::<u8>(PAINT_TYPE)? == 1;
193 let matrix = dict
194 .get::<[f64; 6]>(MATRIX)
195 .map(Affine::new)
196 .unwrap_or_default();
197
198 let state = ctx.get().clone();
199 let ctx_bbox = ctx.bbox();
200
201 let fill_cs = state
202 .graphics_state
203 .none_stroke_cs
204 .pattern_cs()
205 .unwrap_or(ColorSpace::device_gray());
206 let stroke_cs = state
207 .graphics_state
208 .stroke_cs
209 .pattern_cs()
210 .unwrap_or(ColorSpace::device_gray());
211
212 let non_stroke_alpha = state.graphics_state.non_stroke_alpha;
213 let stroke_alpha = state.graphics_state.stroke_alpha;
214
215 let non_stroking_paint = Color::new(
216 fill_cs,
217 state.graphics_state.non_stroke_color.clone(),
218 non_stroke_alpha,
219 );
220 let stroke_paint = Color::new(
221 stroke_cs,
222 state.graphics_state.stroke_color.clone(),
223 stroke_alpha,
224 );
225
226 let opacity = non_stroke_alpha;
230
231 Some(Self {
232 nesting_depth: ctx.nesting_depth(),
233 cache_key,
234 bbox,
235 x_step,
236 y_step,
237 matrix,
238 ctx_bbox,
239 is_color,
240 stream,
241 opacity,
242 stroke_paint,
243 non_stroking_paint,
244 settings: ctx.settings.clone(),
245 parent_resources: resources.clone(),
246 cache: ctx.object_cache.clone(),
247 xref: ctx.xref,
248 })
249 }
250
251 pub fn interpret(
253 &self,
254 device: &mut impl Device<'a>,
255 initial_transform: Affine,
256 is_stroke: bool,
257 ) -> Option<()> {
258 let state = State::new(initial_transform);
259
260 let mut context = Context::new_with(
261 state.ctm,
262 (initial_transform * self.ctx_bbox.to_path(0.1)).bounding_box(),
264 self.cache.clone(),
265 self.xref,
266 self.settings.clone(),
267 state,
268 self.nesting_depth + 1,
273 );
274
275 let decoded = decode_or_warn(&self.stream, &self.settings.warning_sink)?;
276 let resources = Resources::from_parent(
277 self.stream.dict().get(RESOURCES).unwrap_or_default(),
278 self.parent_resources.clone(),
279 );
280 let iter = TypedIter::new(decoded.as_ref());
281
282 let clip_path = ClipPath {
283 path: initial_transform * self.bbox.to_path(0.1),
284 fill: FillRule::NonZero,
285 };
286 device.push_clip_path(&clip_path);
287
288 if self.is_color {
289 interpret(iter, &resources, &mut context, device);
290 } else {
291 let paint = if !is_stroke {
292 Paint::Color(self.non_stroking_paint.clone())
293 } else {
294 Paint::Color(self.stroke_paint.clone())
295 };
296
297 let mut device = StencilPatternDevice::new(device, paint.clone());
298 interpret(iter, &resources, &mut context, &mut device);
299 }
300
301 device.pop_clip_path();
302
303 Some(())
304 }
305}
306
307impl CacheKey for TilingPattern<'_> {
308 fn cache_key(&self) -> u128 {
309 self.cache_key
310 }
311}
312
313struct StencilPatternDevice<'a, 'b, T: Device<'a>> {
314 inner: &'b mut T,
315 paint: Paint<'a>,
316}
317
318impl<'a, 'b, T: Device<'a>> StencilPatternDevice<'a, 'b, T> {
319 pub(crate) fn new(device: &'b mut T, paint: Paint<'a>) -> Self {
320 Self {
321 inner: device,
322 paint,
323 }
324 }
325}
326
327impl<'a, T: Device<'a>> Device<'a> for StencilPatternDevice<'a, '_, T> {
329 fn draw_path(
330 &mut self,
331 path: &BezPath,
332 transform: Affine,
333 _: &Paint<'_>,
334 draw_mode: &PathDrawMode,
335 ) {
336 self.inner
337 .draw_path(path, transform, &self.paint, draw_mode);
338 }
339
340 fn set_soft_mask(&mut self, _: Option<SoftMask<'_>>) {}
341
342 fn push_clip_path(&mut self, clip_path: &ClipPath) {
343 self.inner.push_clip_path(clip_path);
344 }
345
346 fn push_transparency_group(&mut self, _: f32, _: Option<SoftMask<'_>>, _: BlendMode) {}
347
348 fn draw_glyph(
349 &mut self,
350 g: &Glyph<'a>,
351 transform: Affine,
352 glyph_transform: Affine,
353 p: &Paint<'a>,
354 draw_mode: &GlyphDrawMode,
355 ) {
356 self.inner
357 .draw_glyph(g, transform, glyph_transform, p, draw_mode);
358 }
359
360 fn draw_image(&mut self, image: Image<'a, '_>, transform: Affine) {
361 if let Image::Stencil(mut s) = image {
362 s.paint = self.paint.clone();
363 self.inner.draw_image(Image::Stencil(s), transform);
364 }
365 }
366
367 fn pop_clip_path(&mut self) {
368 self.inner.pop_clip_path();
369 }
370
371 fn pop_transparency_group(&mut self) {}
372
373 fn set_blend_mode(&mut self, _: BlendMode) {}
374}