1use crate::cache::{Cache, CacheKey};
2use crate::color::ColorSpace;
3use crate::convert::convert_transform;
4use crate::font::{Font, MarkPlacement, 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 kurbo::{Affine, BezPath, PathEl, Point, Rect, Shape};
10use log::warn;
11use pdf_syntax::content::ops::Transform;
12use pdf_syntax::object::Dict;
13use pdf_syntax::object::Name;
14use pdf_syntax::page::Resources;
15use pdf_syntax::xref::XRef;
16use std::collections::HashMap;
17
18pub(crate) const MAX_NESTED_INTERPRETATION_DEPTH: u32 = 50;
59
60pub struct Context<'a> {
62 states: Vec<State<'a>>,
63 path: BezPath,
64 sub_path_start: Point,
65 last_point: Point,
66 clip: Option<FillRule>,
67 pub(crate) font_cache: HashMap<u128, Option<Font<'a>>>,
68 root_transforms: Vec<Affine>,
69 bbox: Vec<Rect>,
70 pub(crate) settings: InterpreterSettings,
71 pub(crate) object_cache: Cache,
72 pub(crate) xref: &'a XRef,
73 pub(crate) ocg_state: OcgState,
74 nesting_depth: u32,
77 pub(crate) marks: MarkPlacement,
80}
81
82impl<'a> Context<'a> {
83 pub fn new(
85 initial_transform: Affine,
86 bbox: Rect,
87 xref: &'a XRef,
88 settings: InterpreterSettings,
89 ) -> Self {
90 let cache = settings.shared_cache.clone().unwrap_or_default();
91 let state = State::new(initial_transform);
92
93 Self::new_with(initial_transform, bbox, cache, xref, settings, state, 0)
94 }
95
96 pub(crate) fn new_with(
97 initial_transform: Affine,
98 bbox: Rect,
99 cache: Cache,
100 xref: &'a XRef,
101 settings: InterpreterSettings,
102 state: State<'a>,
103 nesting_depth: u32,
104 ) -> Self {
105 let ocg_state = {
106 let root_ref = xref.root_id();
107 xref.get::<Dict<'_>>(root_ref)
108 .map(|catalog| OcgState::from_catalog(&catalog))
109 .unwrap_or_default()
110 };
111
112 Self {
113 nesting_depth,
114 states: vec![state],
115 settings,
116 xref,
117 root_transforms: vec![initial_transform],
118 last_point: Point::default(),
119 sub_path_start: Point::default(),
120 clip: None,
121 bbox: vec![bbox],
122 path: BezPath::new(),
123 font_cache: HashMap::new(),
124 object_cache: cache,
125 ocg_state,
126 marks: MarkPlacement::default(),
127 }
128 }
129
130 pub fn into_settings(self) -> InterpreterSettings {
132 self.settings
133 }
134
135 pub(crate) fn save_state(&mut self) {
136 let Some(cur) = self.states.last().cloned() else {
137 warn!("attempted to save state without existing state");
138 return;
139 };
140
141 self.states.push(cur);
142 }
143
144 pub(crate) fn bbox(&self) -> Rect {
145 self.bbox.last().copied().unwrap_or_else(|| {
146 warn!("failed to get a bbox");
147
148 Rect::new(0.0, 0.0, 1.0, 1.0)
149 })
150 }
151
152 fn push_bbox(&mut self, bbox: Rect) {
153 let new = self.bbox().intersect(bbox);
154 self.bbox.push(new);
155 }
156
157 pub(crate) fn push_clip_path(
158 &mut self,
159 clip_path: BezPath,
160 fill: FillRule,
161 device: &mut impl Device<'a>,
162 ) {
163 if let Some(clip_rect) = path_as_rect(&clip_path) {
164 let cur_bbox = self.bbox();
165
166 if cur_bbox
168 .min_x()
169 .is_nearly_greater_or_equal(clip_rect.min_x())
170 && cur_bbox
171 .min_y()
172 .is_nearly_greater_or_equal(clip_rect.min_y())
173 && cur_bbox.max_x().is_nearly_less_or_equal(clip_rect.max_x())
174 && cur_bbox.max_y().is_nearly_less_or_equal(clip_rect.max_y())
175 {
176 self.get_mut().clips.push(ClipType::Dummy);
177 return;
178 }
179 }
180
181 let bbox = clip_path.bounding_box();
182 device.push_clip_path(&ClipPath {
183 path: clip_path,
184 fill,
185 });
186 self.push_bbox(bbox);
187 self.get_mut().clips.push(ClipType::Real);
188 }
189
190 pub(crate) fn pop_clip_path(&mut self, device: &mut impl Device<'a>) {
191 if let Some(ClipType::Real) = self.get_mut().clips.pop() {
192 device.pop_clip_path();
193 self.pop_bbox();
194 }
195 }
196
197 fn pop_bbox(&mut self) {
198 self.bbox.pop();
199 }
200
201 pub(crate) fn push_root_transform(&mut self) {
202 self.root_transforms.push(self.get().ctm);
203 }
204
205 pub(crate) fn pop_root_transform(&mut self) {
206 self.root_transforms.pop();
207 }
208
209 pub(crate) fn root_transform(&self) -> Affine {
210 self.root_transforms
211 .last()
212 .copied()
213 .unwrap_or(Affine::IDENTITY)
214 }
215
216 pub(crate) fn restore_state(&mut self, device: &mut impl Device<'a>) {
217 let Some(target_clips) = self
218 .states
219 .get(self.states.len().saturating_sub(2))
220 .map(|s| s.clips.len())
221 else {
222 warn!("underflowed graphics state");
223 return;
224 };
225
226 while self.get().clips.len() > target_clips {
227 self.pop_clip_path(device);
228 }
229
230 if self.states.len() > 1 {
232 self.states.pop();
233 }
234
235 device.set_soft_mask(
236 self.states
237 .last()
238 .and_then(|l| l.graphics_state.soft_mask.clone()),
239 );
240 }
241
242 pub(crate) fn path(&self) -> &BezPath {
243 &self.path
244 }
245
246 pub(crate) fn path_mut(&mut self) -> &mut BezPath {
247 &mut self.path
248 }
249
250 pub(crate) fn sub_path_start(&self) -> &Point {
251 &self.sub_path_start
252 }
253
254 pub(crate) fn sub_path_start_mut(&mut self) -> &mut Point {
255 &mut self.sub_path_start
256 }
257
258 pub(crate) fn last_point(&self) -> &Point {
259 &self.last_point
260 }
261
262 pub(crate) fn last_point_mut(&mut self) -> &mut Point {
263 &mut self.last_point
264 }
265
266 pub(crate) fn clip(&self) -> &Option<FillRule> {
267 &self.clip
268 }
269
270 pub(crate) fn clip_mut(&mut self) -> &mut Option<FillRule> {
271 &mut self.clip
272 }
273
274 pub(crate) fn nesting_depth(&self) -> u32 {
282 self.nesting_depth
283 }
284
285 pub(crate) fn begin_nested_interpretation(&mut self) -> bool {
292 if self.nesting_depth >= MAX_NESTED_INTERPRETATION_DEPTH {
293 return false;
294 }
295
296 self.nesting_depth += 1;
297
298 true
299 }
300
301 pub(crate) fn end_nested_interpretation(&mut self) {
302 self.nesting_depth = self.nesting_depth.saturating_sub(1);
303 }
304
305 pub(crate) fn get(&self) -> &State<'a> {
306 self.states.last().unwrap()
307 }
308
309 pub(crate) fn get_mut(&mut self) -> &mut State<'a> {
310 self.states.last_mut().unwrap()
311 }
312
313 pub(crate) fn pre_concat_transform(&mut self, transform: Transform) {
314 self.pre_concat_affine(convert_transform(transform));
315 }
316
317 pub(crate) fn pre_concat_affine(&mut self, transform: Affine) {
318 self.get_mut().ctm *= transform;
319 }
320
321 pub(crate) fn get_color_space(
322 &mut self,
323 resources: &Resources<'_>,
324 name: Name,
325 ) -> Option<ColorSpace> {
326 let cs_object = resources.get_color_space(name)?;
327 self.object_cache
328 .get_or_insert_with(cs_object.cache_key(), || {
329 ColorSpace::new(
330 cs_object.clone(),
331 &self.object_cache,
332 &self.settings.warning_sink,
333 )
334 })
335 }
336
337 pub(crate) fn stroke_props(&self) -> StrokeProps {
338 self.get().graphics_state.stroke_props.clone()
339 }
340
341 pub(crate) fn num_states(&self) -> usize {
342 self.states.len()
343 }
344
345 pub(crate) fn resolve_font(&mut self, font_dict: &Dict<'a>) -> Option<TextStateFont<'a>> {
346 let cache_key = font_dict.cache_key();
347
348 if let Some(resolved) = self
349 .font_cache
350 .entry(cache_key)
351 .or_insert_with(|| {
352 Font::new(
353 font_dict,
354 &self.settings.font_resolver,
355 &self.settings.cmap_resolver,
356 &self.settings.warning_sink,
357 )
358 })
359 .clone()
360 {
361 Some(TextStateFont::Font(resolved))
362 } else {
363 Font::new_standard(StandardFont::Helvetica, &self.settings.font_resolver)
364 .map(TextStateFont::Fallback)
365 }
366 }
367}
368
369pub(crate) fn path_as_rect(path: &BezPath) -> Option<Rect> {
370 let points = match path.elements() {
371 [
372 PathEl::MoveTo(first),
373 PathEl::LineTo(second),
374 PathEl::LineTo(third),
375 PathEl::LineTo(fourth),
376 PathEl::ClosePath,
377 ] => [*first, *second, *third, *fourth, *first],
378 [
379 PathEl::MoveTo(first),
380 PathEl::LineTo(second),
381 PathEl::LineTo(third),
382 PathEl::LineTo(fourth),
383 PathEl::LineTo(last),
384 ] if first.x.is_nearly_equal(last.x) && first.y.is_nearly_equal(last.y) => {
385 [*first, *second, *third, *fourth, *last]
386 }
387 _ => return None,
388 };
389
390 let mut previous_axis = None;
391 for edge in points.windows(2) {
392 let same_x = edge[0].x.is_nearly_equal(edge[1].x);
393 let same_y = edge[0].y.is_nearly_equal(edge[1].y);
394 if same_x == same_y || previous_axis == Some(same_x) {
395 return None;
396 }
397 previous_axis = Some(same_x);
398 }
399
400 Some(path.fast_bounding_box())
401}
402
403#[cfg(test)]
404mod clip_path_shape {
405 use super::*;
406
407 #[test]
416 fn a_bowtie_is_not_a_rect() {
417 let mut bowtie = BezPath::new();
418 bowtie.move_to((0.0, 0.0));
419 bowtie.line_to((10.0, 10.0));
420 bowtie.line_to((10.0, 0.0));
421 bowtie.line_to((0.0, 10.0));
422 bowtie.close_path();
423
424 assert!(
425 path_as_rect(&bowtie).is_none(),
426 "a self-intersecting quadrilateral was accepted as a rectangle, so a \
427 clip would cover its bounding box instead of its actual shape"
428 );
429 }
430
431 #[test]
437 fn an_ordinary_rectangle_still_is_one() {
438 let mut rect = BezPath::new();
439 rect.move_to((0.0, 0.0));
440 rect.line_to((10.0, 0.0));
441 rect.line_to((10.0, 10.0));
442 rect.line_to((0.0, 10.0));
443 rect.close_path();
444
445 assert!(
446 path_as_rect(&rect).is_some(),
447 "a plain rectangle stopped being recognised as one"
448 );
449 }
450
451 #[test]
460 fn four_lines_that_do_not_return_to_the_start_are_not_a_rect() {
461 let mut open = BezPath::new();
462 open.move_to((0.0, 0.0));
463 open.line_to((10.0, 0.0));
464 open.line_to((10.0, 10.0));
465 open.line_to((0.0, 10.0));
466 open.line_to((0.0, 5.0)); assert!(
469 path_as_rect(&open).is_none(),
470 "an unclosed four-line path was accepted as a rectangle, so a clip \
471 would cover the closed shape the path never drew"
472 );
473 }
474
475 #[test]
480 fn four_lines_that_do_return_to_the_start_are_a_rect() {
481 let mut closed = BezPath::new();
482 closed.move_to((0.0, 0.0));
483 closed.line_to((10.0, 0.0));
484 closed.line_to((10.0, 10.0));
485 closed.line_to((0.0, 10.0));
486 closed.line_to((0.0, 0.0));
487
488 assert!(
489 path_as_rect(&closed).is_some(),
490 "a four-line rectangle that returns to its start stopped being one"
491 );
492 }
493}