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