Skip to main content

hayro_interpret/
context.rs

1use crate::cache::{Cache, CacheKey};
2use crate::color::ColorSpace;
3use crate::convert::convert_transform;
4use crate::font::{Font, StandardFont};
5use crate::interpret::state::{ClipType, State, TextStateFont};
6use crate::ocg::OcgState;
7use crate::util::{BezPathExt, Float64Ext};
8use crate::{ClipPath, Device, FillRule, InterpreterSettings, StrokeProps};
9use hayro_syntax::content::ops::Transform;
10use hayro_syntax::object::Dict;
11use hayro_syntax::object::Name;
12use hayro_syntax::page::Resources;
13use hayro_syntax::xref::XRef;
14use kurbo::{Affine, BezPath, PathEl, Point, Rect, Shape};
15use std::cell::RefCell;
16use std::collections::HashMap;
17use std::rc::Rc;
18
19/// Maximum nesting depth for interpreting `XObject`'s/patterns/streams.
20pub(crate) const MAX_NESTED_INTERPRETATION_DEPTH: u32 = 50;
21
22/// A cache used by the interpreter.
23///
24/// Ideally, such a cache should be constructed once per PDF and then reused across
25/// multiple interpreter invocations on the same document.
26#[derive(Clone)]
27pub struct InterpreterCache<'a> {
28    pub(crate) font_cache: Rc<RefCell<HashMap<u128, Option<Font<'a>>>>>,
29    pub(crate) object_cache: Cache,
30}
31
32impl<'a> Default for InterpreterCache<'a> {
33    fn default() -> Self {
34        Self::new()
35    }
36}
37
38impl<'a> InterpreterCache<'a> {
39    /// Create a new interpreter cache.
40    pub fn new() -> Self {
41        Self {
42            font_cache: Rc::new(RefCell::new(HashMap::new())),
43            object_cache: Cache::new(),
44        }
45    }
46}
47
48/// A per-page interpretation context that borrows shared data from an [`InterpreterCache`].
49pub struct Context<'a> {
50    states: Vec<State<'a>>,
51    path: BezPath,
52    sub_path_start: Point,
53    last_point: Point,
54    clip: Option<FillRule>,
55    root_transforms: Vec<Affine>,
56    bbox: Vec<Rect>,
57    pub(crate) settings: InterpreterSettings,
58    pub(crate) interpreter_cache: InterpreterCache<'a>,
59    pub(crate) xref: &'a XRef,
60    pub(crate) ocg_state: OcgState,
61    nesting_depth: u32,
62}
63
64impl<'a> Context<'a> {
65    /// Create a new context.
66    pub fn new(
67        initial_transform: Affine,
68        bbox: Rect,
69        cache: &InterpreterCache<'a>,
70        xref: &'a XRef,
71        settings: InterpreterSettings,
72    ) -> Self {
73        let state = State::new(initial_transform);
74
75        Self::new_with(initial_transform, bbox, cache, xref, settings, state, 0)
76    }
77
78    pub(crate) fn new_with(
79        initial_transform: Affine,
80        bbox: Rect,
81        cache: &InterpreterCache<'a>,
82        xref: &'a XRef,
83        settings: InterpreterSettings,
84        state: State<'a>,
85        nesting_depth: u32,
86    ) -> Self {
87        let ocg_state = {
88            let root_ref = xref.root_id();
89            xref.get::<Dict<'_>>(root_ref)
90                .map(|catalog| OcgState::from_catalog(&catalog))
91                .unwrap_or_default()
92        };
93
94        Self {
95            states: vec![state],
96            settings,
97            xref,
98            root_transforms: vec![initial_transform],
99            last_point: Point::default(),
100            sub_path_start: Point::default(),
101            clip: None,
102            bbox: vec![bbox],
103            path: BezPath::new(),
104            interpreter_cache: cache.clone(),
105            ocg_state,
106            nesting_depth,
107        }
108    }
109
110    pub(crate) fn save_state(&mut self) {
111        let Some(cur) = self.states.last().cloned() else {
112            warn!("attempted to save state without existing state");
113            return;
114        };
115
116        self.states.push(cur);
117    }
118
119    pub(crate) fn bbox(&self) -> Rect {
120        self.bbox.last().copied().unwrap_or_else(|| {
121            warn!("failed to get a bbox");
122
123            Rect::new(0.0, 0.0, 1.0, 1.0)
124        })
125    }
126
127    fn push_bbox(&mut self, bbox: Rect) {
128        let new = self.bbox().intersect(bbox);
129        self.bbox.push(new);
130    }
131
132    pub(crate) fn push_clip_path(
133        &mut self,
134        clip_path: BezPath,
135        fill: FillRule,
136        device: &mut impl Device<'a>,
137    ) {
138        if let Some(clip_rect) = path_as_rect(&clip_path) {
139            let cur_bbox = self.bbox();
140
141            // If the clip path is a rect and completely covers the current bbox, don't emit it.
142            if cur_bbox
143                .min_x()
144                .is_nearly_greater_or_equal(clip_rect.min_x())
145                && cur_bbox
146                    .min_y()
147                    .is_nearly_greater_or_equal(clip_rect.min_y())
148                && cur_bbox.max_x().is_nearly_less_or_equal(clip_rect.max_x())
149                && cur_bbox.max_y().is_nearly_less_or_equal(clip_rect.max_y())
150            {
151                self.get_mut().clips.push(ClipType::Dummy);
152                return;
153            }
154        }
155
156        let bbox = clip_path.bounding_box();
157        device.push_clip_path(&ClipPath {
158            path: clip_path,
159            fill,
160        });
161        self.push_bbox(bbox);
162        self.get_mut().clips.push(ClipType::Real);
163    }
164
165    pub(crate) fn pop_clip_path(&mut self, device: &mut impl Device<'a>) {
166        if let Some(ClipType::Real) = self.get_mut().clips.pop() {
167            device.pop_clip_path();
168            self.pop_bbox();
169        }
170    }
171
172    fn pop_bbox(&mut self) {
173        self.bbox.pop();
174    }
175
176    pub(crate) fn push_root_transform(&mut self) {
177        self.root_transforms.push(self.get().ctm);
178    }
179
180    pub(crate) fn pop_root_transform(&mut self) {
181        self.root_transforms.pop();
182    }
183
184    pub(crate) fn root_transform(&self) -> Affine {
185        self.root_transforms
186            .last()
187            .copied()
188            .unwrap_or(Affine::IDENTITY)
189    }
190
191    pub(crate) fn restore_state(&mut self, device: &mut impl Device<'a>) {
192        let Some(target_clips) = self
193            .states
194            .get(self.states.len().saturating_sub(2))
195            .map(|s| s.clips.len())
196        else {
197            warn!("underflowed graphics state");
198            return;
199        };
200
201        while self.get().clips.len() > target_clips {
202            self.pop_clip_path(device);
203        }
204
205        // The first state should never be popped.
206        if self.states.len() > 1 {
207            self.states.pop();
208        }
209
210        device.set_soft_mask(
211            self.states
212                .last()
213                .and_then(|l| l.graphics_state.soft_mask.clone()),
214        );
215    }
216
217    pub(crate) fn path(&self) -> &BezPath {
218        &self.path
219    }
220
221    pub(crate) fn path_mut(&mut self) -> &mut BezPath {
222        &mut self.path
223    }
224
225    pub(crate) fn sub_path_start(&self) -> &Point {
226        &self.sub_path_start
227    }
228
229    pub(crate) fn sub_path_start_mut(&mut self) -> &mut Point {
230        &mut self.sub_path_start
231    }
232
233    pub(crate) fn last_point(&self) -> &Point {
234        &self.last_point
235    }
236
237    pub(crate) fn last_point_mut(&mut self) -> &mut Point {
238        &mut self.last_point
239    }
240
241    pub(crate) fn clip(&self) -> &Option<FillRule> {
242        &self.clip
243    }
244
245    pub(crate) fn clip_mut(&mut self) -> &mut Option<FillRule> {
246        &mut self.clip
247    }
248
249    pub(crate) fn get(&self) -> &State<'a> {
250        self.states.last().unwrap()
251    }
252
253    pub(crate) fn get_mut(&mut self) -> &mut State<'a> {
254        self.states.last_mut().unwrap()
255    }
256
257    pub(crate) fn pre_concat_transform(&mut self, transform: Transform) {
258        self.pre_concat_affine(convert_transform(transform));
259    }
260
261    pub(crate) fn pre_concat_affine(&mut self, transform: Affine) {
262        self.get_mut().ctm *= transform;
263    }
264
265    pub(crate) fn get_color_space(
266        &mut self,
267        resources: &Resources<'_>,
268        name: &Name<'_>,
269    ) -> Option<ColorSpace> {
270        let cs_object = resources.get_color_space(name)?;
271        self.interpreter_cache
272            .object_cache
273            .get_or_insert_with(cs_object.cache_key(), || {
274                ColorSpace::new(cs_object.clone(), &self.interpreter_cache.object_cache)
275            })
276    }
277
278    pub(crate) fn stroke_props(&self) -> StrokeProps {
279        self.get().graphics_state.stroke_props.clone()
280    }
281
282    pub(crate) fn num_states(&self) -> usize {
283        self.states.len()
284    }
285
286    pub(crate) fn nesting_depth(&self) -> u32 {
287        self.nesting_depth
288    }
289
290    pub(crate) fn begin_nested_interpretation(&mut self) -> bool {
291        if self.nesting_depth >= MAX_NESTED_INTERPRETATION_DEPTH {
292            warn!("interpreter nesting depth exceeded");
293
294            return false;
295        }
296
297        self.nesting_depth += 1;
298
299        true
300    }
301
302    pub(crate) fn end_nested_interpretation(&mut self) {
303        self.nesting_depth = self.nesting_depth.saturating_sub(1);
304    }
305    pub(crate) fn resolve_font(&mut self, font_dict: &Dict<'a>) -> Option<TextStateFont<'a>> {
306        let cache_key = font_dict.cache_key();
307
308        let resolved = {
309            let mut font_cache = self.interpreter_cache.font_cache.borrow_mut();
310            font_cache
311                .entry(cache_key)
312                .or_insert_with(|| {
313                    Font::new(
314                        font_dict,
315                        &self.settings.font_resolver,
316                        &self.settings.cmap_resolver,
317                    )
318                })
319                .clone()
320        };
321
322        if let Some(resolved) = resolved {
323            Some(TextStateFont::Font(resolved))
324        } else {
325            Font::new_standard(StandardFont::Helvetica, &self.settings.font_resolver)
326                .map(TextStateFont::Fallback)
327        }
328    }
329}
330
331pub(crate) fn path_as_rect(path: &BezPath) -> Option<Rect> {
332    // One MoveTo, three LineTo, one ClosePath
333    if path.elements().len() != 5 {
334        return None;
335    }
336
337    let bbox = path.fast_bounding_box();
338    let (min_x, min_y, max_x, max_y) = (bbox.min_x(), bbox.min_y(), bbox.max_x(), bbox.max_y());
339    let mut corners = [false; 4];
340
341    let mut check_point = |p: Point| {
342        corners[0] |= p.x.is_nearly_equal(min_x) && p.y.is_nearly_equal(min_y);
343        corners[1] |= p.x.is_nearly_equal(min_x) && p.y.is_nearly_equal(max_y);
344        corners[2] |= p.x.is_nearly_equal(max_x) && p.y.is_nearly_equal(min_y);
345        corners[3] |= p.x.is_nearly_equal(max_x) && p.y.is_nearly_equal(max_y);
346    };
347
348    for (idx, el) in path.elements().iter().enumerate() {
349        match el {
350            PathEl::MoveTo(p) => {
351                if idx != 0 {
352                    return None;
353                }
354
355                check_point(*p);
356            }
357            PathEl::LineTo(l) => check_point(*l),
358            PathEl::QuadTo(_, _) => return None,
359            PathEl::CurveTo(_, _, _) => return None,
360            PathEl::ClosePath => {}
361        }
362    }
363
364    if corners[0] && corners[1] && corners[2] && corners[3] {
365        Some(bbox)
366    } else {
367        None
368    }
369}