hayro_interpret/
context.rs1use crate::cache::{Cache, CacheKey};
2use crate::color::ColorSpace;
3use crate::convert::convert_transform;
4use crate::font::Font;
5use crate::interpret::state::{ClipType, State};
6use crate::ocg::OcgState;
7use crate::util::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 log::warn;
16use std::collections::HashMap;
17
18pub 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<u128, Option<Font<'a>>>,
26 root_transforms: Vec<Affine>,
27 bbox: Vec<Rect>,
28 pub(crate) settings: InterpreterSettings,
29 pub(crate) object_cache: Cache,
30 pub(crate) xref: &'a XRef,
31 pub(crate) ocg_state: OcgState,
32}
33
34impl<'a> Context<'a> {
35 pub fn new(
37 initial_transform: Affine,
38 bbox: Rect,
39 xref: &'a XRef,
40 settings: InterpreterSettings,
41 ) -> Self {
42 let cache = Cache::new();
43 let state = State::new(initial_transform);
44
45 Self::new_with(initial_transform, bbox, cache, xref, settings, state)
46 }
47
48 pub(crate) fn new_with(
49 initial_transform: Affine,
50 bbox: Rect,
51 cache: Cache,
52 xref: &'a XRef,
53 settings: InterpreterSettings,
54 state: State<'a>,
55 ) -> Self {
56 let ocg_state = {
57 let root_ref = xref.root_id();
58 xref.get::<Dict<'_>>(root_ref)
59 .map(|catalog| OcgState::from_catalog(&catalog))
60 .unwrap_or_default()
61 };
62
63 Self {
64 states: vec![state],
65 settings,
66 xref,
67 root_transforms: vec![initial_transform],
68 last_point: Point::default(),
69 sub_path_start: Point::default(),
70 clip: None,
71 bbox: vec![bbox],
72 path: BezPath::new(),
73 font_cache: HashMap::new(),
74 object_cache: cache,
75 ocg_state,
76 }
77 }
78
79 pub(crate) fn save_state(&mut self) {
80 let Some(cur) = self.states.last().cloned() else {
81 warn!("attempted to save state without existing state");
82 return;
83 };
84
85 self.states.push(cur);
86 }
87
88 pub(crate) fn bbox(&self) -> Rect {
89 self.bbox.last().copied().unwrap_or_else(|| {
90 warn!("failed to get a bbox");
91
92 Rect::new(0.0, 0.0, 1.0, 1.0)
93 })
94 }
95
96 fn push_bbox(&mut self, bbox: Rect) {
97 let new = self.bbox().intersect(bbox);
98 self.bbox.push(new);
99 }
100
101 pub(crate) fn push_clip_path(
102 &mut self,
103 clip_path: BezPath,
104 fill: FillRule,
105 device: &mut impl Device<'a>,
106 ) {
107 if let Some(clip_rect) = path_as_rect(&clip_path) {
108 let cur_bbox = self.bbox();
109
110 if cur_bbox
112 .min_x()
113 .is_nearly_greater_or_equal(clip_rect.min_x())
114 && cur_bbox
115 .min_y()
116 .is_nearly_greater_or_equal(clip_rect.min_y())
117 && cur_bbox.max_x().is_nearly_less_or_equal(clip_rect.max_x())
118 && cur_bbox.max_y().is_nearly_less_or_equal(clip_rect.max_y())
119 {
120 self.get_mut().clips.push(ClipType::Dummy);
121 return;
122 }
123 }
124
125 let bbox = clip_path.bounding_box();
126 device.push_clip_path(&ClipPath {
127 path: clip_path,
128 fill,
129 });
130 self.push_bbox(bbox);
131 self.get_mut().clips.push(ClipType::Real);
132 }
133
134 pub(crate) fn pop_clip_path(&mut self, device: &mut impl Device<'a>) {
135 if let Some(ClipType::Real) = self.get_mut().clips.pop() {
136 device.pop_clip_path();
137 self.pop_bbox();
138 }
139 }
140
141 fn pop_bbox(&mut self) {
142 self.bbox.pop();
143 }
144
145 pub(crate) fn push_root_transform(&mut self) {
146 self.root_transforms.push(self.get().ctm);
147 }
148
149 pub(crate) fn pop_root_transform(&mut self) {
150 self.root_transforms.pop();
151 }
152
153 pub(crate) fn root_transform(&self) -> Affine {
154 self.root_transforms
155 .last()
156 .copied()
157 .unwrap_or(Affine::IDENTITY)
158 }
159
160 pub(crate) fn restore_state(&mut self, device: &mut impl Device<'a>) {
161 let Some(target_clips) = self
162 .states
163 .get(self.states.len().saturating_sub(2))
164 .map(|s| s.clips.len())
165 else {
166 warn!("underflowed graphics state");
167 return;
168 };
169
170 while self.get().clips.len() > target_clips {
171 self.pop_clip_path(device);
172 }
173
174 if self.states.len() > 1 {
176 self.states.pop();
177 }
178 }
179
180 pub(crate) fn path(&self) -> &BezPath {
181 &self.path
182 }
183
184 pub(crate) fn path_mut(&mut self) -> &mut BezPath {
185 &mut self.path
186 }
187
188 pub(crate) fn sub_path_start(&self) -> &Point {
189 &self.sub_path_start
190 }
191
192 pub(crate) fn sub_path_start_mut(&mut self) -> &mut Point {
193 &mut self.sub_path_start
194 }
195
196 pub(crate) fn last_point(&self) -> &Point {
197 &self.last_point
198 }
199
200 pub(crate) fn last_point_mut(&mut self) -> &mut Point {
201 &mut self.last_point
202 }
203
204 pub(crate) fn clip(&self) -> &Option<FillRule> {
205 &self.clip
206 }
207
208 pub(crate) fn clip_mut(&mut self) -> &mut Option<FillRule> {
209 &mut self.clip
210 }
211
212 pub(crate) fn get(&self) -> &State<'a> {
213 self.states.last().unwrap()
214 }
215
216 pub(crate) fn get_mut(&mut self) -> &mut State<'a> {
217 self.states.last_mut().unwrap()
218 }
219
220 pub(crate) fn pre_concat_transform(&mut self, transform: Transform) {
221 self.pre_concat_affine(convert_transform(transform));
222 }
223
224 pub(crate) fn pre_concat_affine(&mut self, transform: Affine) {
225 self.get_mut().ctm *= transform;
226 }
227
228 pub(crate) fn get_font(
229 &mut self,
230 resources: &Resources<'a>,
231 name: Name<'_>,
232 ) -> Option<Font<'a>> {
233 let font_dict = resources.get_font(name)?;
234 let cache_key = font_dict.cache_key();
235 self.font_cache
236 .entry(cache_key)
237 .or_insert_with(|| Font::new(&font_dict, &self.settings.font_resolver))
238 .clone()
239 }
240
241 pub(crate) fn get_color_space(
242 &mut self,
243 resources: &Resources<'_>,
244 name: Name<'_>,
245 ) -> Option<ColorSpace> {
246 let cs_object = resources.get_color_space(name)?;
247 self.object_cache
248 .get_or_insert_with(cs_object.cache_key(), || {
249 ColorSpace::new(cs_object.clone(), &self.object_cache)
250 })
251 }
252
253 pub(crate) fn stroke_props(&self) -> StrokeProps {
254 self.get().graphics_state.stroke_props.clone()
255 }
256
257 pub(crate) fn num_states(&self) -> usize {
258 self.states.len()
259 }
260}
261
262fn path_as_rect(path: &BezPath) -> Option<Rect> {
263 let bbox = path.bounding_box();
264 let (min_x, min_y, max_x, max_y) = (bbox.min_x(), bbox.min_y(), bbox.max_x(), bbox.max_y());
265 let mut touched = [false; 4];
266
267 if path.elements().len() != 5 {
269 return None;
270 }
271
272 let mut check_point = |p: Point| {
273 touched[0] |= p.x.is_nearly_equal(min_x);
274 touched[1] |= p.y.is_nearly_equal(min_y);
275 touched[2] |= p.x.is_nearly_equal(max_x);
276 touched[3] |= p.y.is_nearly_equal(max_y);
277 };
278
279 for el in path.elements() {
280 match el {
281 PathEl::MoveTo(p) => check_point(*p),
282 PathEl::LineTo(l) => check_point(*l),
283 PathEl::QuadTo(_, _) => {
284 return None;
285 }
286 PathEl::CurveTo(_, _, _) => {
287 return None;
288 }
289 PathEl::ClosePath => {}
290 }
291 }
292
293 if touched[0] && touched[1] && touched[2] && touched[3] {
294 Some(bbox)
295 } else {
296 None
297 }
298}