Skip to main content

pdf_interpret/
pattern.rs

1//! PDF patterns.
2
3use 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/// A PDF pattern.
29#[derive(Debug, Clone)]
30pub enum Pattern<'a> {
31    /// A shading pattern.
32    Shading(ShadingPattern),
33    /// A tiling pattern.
34    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/// A shading pattern.
86#[derive(Clone, Debug)]
87pub struct ShadingPattern {
88    /// The underlying shading of the pattern.
89    pub shading: Arc<Shading>,
90    /// A transformation matrix to apply prior to rendering.
91    pub matrix: Affine,
92    /// An additional opacity to apply to the shading pattern.
93    pub opacity: f32,
94    /// An optional transfer function to apply to the shading's output colors.
95    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/// A tiling pattern.
130#[derive(Clone)]
131pub struct TilingPattern<'a> {
132    cache_key: u128,
133    ctx_bbox: Rect,
134    /// The bbox of the tiling pattern.
135    pub bbox: Rect,
136    /// The step in the x direction.
137    pub x_step: f32,
138    /// The step in the y direction.
139    pub y_step: f32,
140    /// A transformation to apply prior to rendering.
141    pub matrix: Affine,
142    stream: Stream<'a>,
143    /// Whether this is a colored (`PaintType 1`) tiling pattern.
144    pub is_color: bool,
145    /// For colored tiling patterns: the non-stroking opacity from the parent
146    /// graphics state at the time the pattern was set. PDF ยง8.7.3.1 states
147    /// that the current graphics state (including opacity) applies when a
148    /// tiling pattern is painted, so we capture it here for later use.
149    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        // For colored tiling patterns the fill opacity controls transparency
216        // of the whole tile; for uncolored patterns it is already embedded in
217        // the paint colors above, so we record it but use it conditionally.
218        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    /// Interpret the contents of the pattern into the given device.
240    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            // TODO: bbox?
251            (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
310// Only filling, stroking of paths and stencil masks are allowed.
311impl<'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}