hayro_interpret/
context.rs

1use crate::cache::Cache;
2use crate::color::ColorSpace;
3use crate::convert::convert_transform;
4use crate::font::Font;
5use crate::interpret::state::State;
6use crate::{FillProps, FillRule, InterpreterSettings, StrokeProps};
7use hayro_syntax::content::ops::Transform;
8use hayro_syntax::object::Dict;
9use hayro_syntax::object::Name;
10use hayro_syntax::object::ObjRef;
11use hayro_syntax::object::Object;
12use hayro_syntax::page::Resources;
13use hayro_syntax::xref::XRef;
14use kurbo::{Affine, BezPath, Point};
15use log::warn;
16use std::collections::HashMap;
17
18/// A context for interpreting PDF files.
19pub struct Context<'a> {
20    states: Vec<State<'a>>,
21    path: BezPath,
22    sub_path_start: Point,
23    last_point: Point,
24    clip: Option<FillRule>,
25    font_cache: HashMap<ObjRef, Option<Font<'a>>>,
26    root_transforms: Vec<Affine>,
27    bbox: Vec<kurbo::Rect>,
28    pub(crate) settings: InterpreterSettings,
29    pub(crate) object_cache: Cache,
30    pub(crate) xref: &'a XRef,
31}
32
33impl<'a> Context<'a> {
34    /// Create a new context.
35    pub fn new(
36        initial_transform: Affine,
37        bbox: kurbo::Rect,
38        xref: &'a XRef,
39        settings: InterpreterSettings,
40    ) -> Self {
41        let cache = Cache::new();
42        let state = State::new(initial_transform);
43
44        Self::new_with(initial_transform, bbox, cache, xref, settings, state)
45    }
46
47    pub(crate) fn new_with(
48        initial_transform: Affine,
49        bbox: kurbo::Rect,
50        cache: Cache,
51        xref: &'a XRef,
52        settings: InterpreterSettings,
53        state: State<'a>,
54    ) -> Self {
55        Self {
56            states: vec![state],
57            settings,
58            xref,
59            root_transforms: vec![initial_transform],
60            last_point: Point::default(),
61            sub_path_start: Point::default(),
62            clip: None,
63            bbox: vec![bbox],
64            path: BezPath::new(),
65            font_cache: HashMap::new(),
66            object_cache: cache,
67        }
68    }
69
70    pub(crate) fn save_state(&mut self) {
71        let Some(cur) = self.states.last().cloned() else {
72            warn!("attempted to save state without existing state");
73            return;
74        };
75
76        self.states.push(cur);
77    }
78
79    pub(crate) fn bbox(&self) -> kurbo::Rect {
80        self.bbox.last().copied().unwrap_or_else(|| {
81            warn!("failed to get a bbox");
82
83            kurbo::Rect::new(0.0, 0.0, 1.0, 1.0)
84        })
85    }
86
87    pub(crate) fn push_root_transform(&mut self) {
88        self.root_transforms.push(self.get().ctm);
89    }
90
91    pub(crate) fn pop_root_transform(&mut self) {
92        self.root_transforms.pop();
93    }
94
95    pub(crate) fn root_transform(&self) -> Affine {
96        self.root_transforms
97            .last()
98            .copied()
99            .unwrap_or(Affine::IDENTITY)
100    }
101
102    pub(crate) fn restore_state(&mut self) {
103        if self.states.len() > 1 {
104            self.states.pop();
105        } else {
106            warn!("overflow in `restore_state");
107        }
108    }
109
110    pub(crate) fn path(&self) -> &BezPath {
111        &self.path
112    }
113
114    pub(crate) fn path_mut(&mut self) -> &mut BezPath {
115        &mut self.path
116    }
117
118    pub(crate) fn sub_path_start(&self) -> &Point {
119        &self.sub_path_start
120    }
121
122    pub(crate) fn sub_path_start_mut(&mut self) -> &mut Point {
123        &mut self.sub_path_start
124    }
125
126    pub(crate) fn last_point(&self) -> &Point {
127        &self.last_point
128    }
129
130    pub(crate) fn last_point_mut(&mut self) -> &mut Point {
131        &mut self.last_point
132    }
133
134    pub(crate) fn clip(&self) -> &Option<FillRule> {
135        &self.clip
136    }
137
138    pub(crate) fn clip_mut(&mut self) -> &mut Option<FillRule> {
139        &mut self.clip
140    }
141
142    pub(crate) fn get(&self) -> &State<'a> {
143        self.states.last().unwrap()
144    }
145
146    pub(crate) fn get_mut(&mut self) -> &mut State<'a> {
147        self.states.last_mut().unwrap()
148    }
149
150    pub(crate) fn pre_concat_transform(&mut self, transform: Transform) {
151        self.pre_concat_affine(convert_transform(transform))
152    }
153
154    pub(crate) fn pre_concat_affine(&mut self, transform: Affine) {
155        self.get_mut().ctm *= transform;
156    }
157
158    pub(crate) fn get_font(&mut self, resources: &Resources<'a>, name: Name) -> Option<Font<'a>> {
159        resources.get_font(
160            name,
161            Box::new(|ref_| {
162                self.font_cache
163                    .entry(ref_)
164                    .or_insert_with(|| {
165                        resources.resolve_ref::<Dict>(ref_).and_then(|o| {
166                            Font::new(
167                                &o,
168                                &self.settings.font_resolver,
169                                &self.settings.warning_sink,
170                            )
171                        })
172                    })
173                    .clone()
174            }),
175            Box::new(|c| {
176                Font::new(
177                    &c,
178                    &self.settings.font_resolver,
179                    &self.settings.warning_sink,
180                )
181            }),
182        )
183    }
184
185    pub(crate) fn get_color_space(
186        &mut self,
187        resources: &Resources,
188        name: Name,
189    ) -> Option<ColorSpace> {
190        resources.get_color_space(
191            name,
192            Box::new(|ref_| {
193                self.object_cache.get_or_insert_with(ref_.into(), || {
194                    resources
195                        .resolve_ref::<Object>(ref_)
196                        .map(|o| ColorSpace::new(o))
197                })
198            }),
199            Box::new(|c| Some(ColorSpace::new(c))),
200        )?
201    }
202
203    pub(crate) fn stroke_props(&self) -> StrokeProps {
204        let state = self.get();
205
206        StrokeProps {
207            line_width: state.line_width,
208            line_cap: state.line_cap,
209            line_join: state.line_join,
210            miter_limit: state.miter_limit,
211            dash_array: state.dash_array.clone(),
212            dash_offset: state.dash_offset,
213        }
214    }
215
216    pub(crate) fn num_states(&self) -> usize {
217        self.states.len()
218    }
219
220    pub(crate) fn fill_props(&self) -> FillProps {
221        let state = self.get();
222
223        FillProps {
224            fill_rule: state.fill_rule,
225        }
226    }
227}