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 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 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 pub(crate) 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 fn into_settings(self) -> InterpreterSettings {
81 self.settings
82 }
83
84 pub(crate) fn save_state(&mut self) {
85 let Some(cur) = self.states.last().cloned() else {
86 warn!("attempted to save state without existing state");
87 return;
88 };
89
90 self.states.push(cur);
91 }
92
93 pub(crate) fn bbox(&self) -> Rect {
94 self.bbox.last().copied().unwrap_or_else(|| {
95 warn!("failed to get a bbox");
96
97 Rect::new(0.0, 0.0, 1.0, 1.0)
98 })
99 }
100
101 fn push_bbox(&mut self, bbox: Rect) {
102 let new = self.bbox().intersect(bbox);
103 self.bbox.push(new);
104 }
105
106 pub(crate) fn push_clip_path(
107 &mut self,
108 clip_path: BezPath,
109 fill: FillRule,
110 device: &mut impl Device<'a>,
111 ) {
112 if let Some(clip_rect) = path_as_rect(&clip_path) {
113 let cur_bbox = self.bbox();
114
115 if cur_bbox
117 .min_x()
118 .is_nearly_greater_or_equal(clip_rect.min_x())
119 && cur_bbox
120 .min_y()
121 .is_nearly_greater_or_equal(clip_rect.min_y())
122 && cur_bbox.max_x().is_nearly_less_or_equal(clip_rect.max_x())
123 && cur_bbox.max_y().is_nearly_less_or_equal(clip_rect.max_y())
124 {
125 self.get_mut().clips.push(ClipType::Dummy);
126 return;
127 }
128 }
129
130 let bbox = clip_path.bounding_box();
131 device.push_clip_path(&ClipPath {
132 path: clip_path,
133 fill,
134 });
135 self.push_bbox(bbox);
136 self.get_mut().clips.push(ClipType::Real);
137 }
138
139 pub(crate) fn pop_clip_path(&mut self, device: &mut impl Device<'a>) {
140 if let Some(ClipType::Real) = self.get_mut().clips.pop() {
141 device.pop_clip_path();
142 self.pop_bbox();
143 }
144 }
145
146 fn pop_bbox(&mut self) {
147 self.bbox.pop();
148 }
149
150 pub(crate) fn push_root_transform(&mut self) {
151 self.root_transforms.push(self.get().ctm);
152 }
153
154 pub(crate) fn pop_root_transform(&mut self) {
155 self.root_transforms.pop();
156 }
157
158 pub(crate) fn root_transform(&self) -> Affine {
159 self.root_transforms
160 .last()
161 .copied()
162 .unwrap_or(Affine::IDENTITY)
163 }
164
165 pub(crate) fn restore_state(&mut self, device: &mut impl Device<'a>) {
166 let Some(target_clips) = self
167 .states
168 .get(self.states.len().saturating_sub(2))
169 .map(|s| s.clips.len())
170 else {
171 warn!("underflowed graphics state");
172 return;
173 };
174
175 while self.get().clips.len() > target_clips {
176 self.pop_clip_path(device);
177 }
178
179 if self.states.len() > 1 {
181 self.states.pop();
182 }
183
184 device.set_soft_mask(
185 self.states
186 .last()
187 .and_then(|l| l.graphics_state.soft_mask.clone()),
188 );
189 }
190
191 pub(crate) fn path(&self) -> &BezPath {
192 &self.path
193 }
194
195 pub(crate) fn path_mut(&mut self) -> &mut BezPath {
196 &mut self.path
197 }
198
199 pub(crate) fn sub_path_start(&self) -> &Point {
200 &self.sub_path_start
201 }
202
203 pub(crate) fn sub_path_start_mut(&mut self) -> &mut Point {
204 &mut self.sub_path_start
205 }
206
207 pub(crate) fn last_point(&self) -> &Point {
208 &self.last_point
209 }
210
211 pub(crate) fn last_point_mut(&mut self) -> &mut Point {
212 &mut self.last_point
213 }
214
215 pub(crate) fn clip(&self) -> &Option<FillRule> {
216 &self.clip
217 }
218
219 pub(crate) fn clip_mut(&mut self) -> &mut Option<FillRule> {
220 &mut self.clip
221 }
222
223 pub(crate) fn get(&self) -> &State<'a> {
224 self.states.last().unwrap()
225 }
226
227 pub(crate) fn get_mut(&mut self) -> &mut State<'a> {
228 self.states.last_mut().unwrap()
229 }
230
231 pub(crate) fn pre_concat_transform(&mut self, transform: Transform) {
232 self.pre_concat_affine(convert_transform(transform));
233 }
234
235 pub(crate) fn pre_concat_affine(&mut self, transform: Affine) {
236 self.get_mut().ctm *= transform;
237 }
238
239 pub(crate) fn get_color_space(
240 &mut self,
241 resources: &Resources<'_>,
242 name: Name,
243 ) -> Option<ColorSpace> {
244 let cs_object = resources.get_color_space(name)?;
245 self.object_cache
246 .get_or_insert_with(cs_object.cache_key(), || {
247 ColorSpace::new(cs_object.clone(), &self.object_cache)
248 })
249 }
250
251 pub(crate) fn stroke_props(&self) -> StrokeProps {
252 self.get().graphics_state.stroke_props.clone()
253 }
254
255 pub(crate) fn num_states(&self) -> usize {
256 self.states.len()
257 }
258
259 pub(crate) fn resolve_font(&mut self, font_dict: &Dict<'a>) -> Option<TextStateFont<'a>> {
260 let cache_key = font_dict.cache_key();
261
262 if let Some(resolved) = self
263 .font_cache
264 .entry(cache_key)
265 .or_insert_with(|| {
266 Font::new(
267 font_dict,
268 &self.settings.font_resolver,
269 &self.settings.cmap_resolver,
270 )
271 })
272 .clone()
273 {
274 Some(TextStateFont::Font(resolved))
275 } else {
276 Font::new_standard(StandardFont::Helvetica, &self.settings.font_resolver)
277 .map(TextStateFont::Fallback)
278 }
279 }
280}
281
282pub(crate) fn path_as_rect(path: &BezPath) -> Option<Rect> {
283 if path.elements().len() != 5 {
285 return None;
286 }
287
288 let bbox = path.fast_bounding_box();
289 let (min_x, min_y, max_x, max_y) = (bbox.min_x(), bbox.min_y(), bbox.max_x(), bbox.max_y());
290 let mut corners = [false; 4];
291
292 let mut check_point = |p: Point| {
293 corners[0] |= p.x.is_nearly_equal(min_x) && p.y.is_nearly_equal(min_y);
294 corners[1] |= p.x.is_nearly_equal(min_x) && p.y.is_nearly_equal(max_y);
295 corners[2] |= p.x.is_nearly_equal(max_x) && p.y.is_nearly_equal(min_y);
296 corners[3] |= p.x.is_nearly_equal(max_x) && p.y.is_nearly_equal(max_y);
297 };
298
299 for (idx, el) in path.elements().iter().enumerate() {
300 match el {
301 PathEl::MoveTo(p) => {
302 if idx != 0 {
303 return None;
304 }
305
306 check_point(*p);
307 }
308 PathEl::LineTo(l) => check_point(*l),
309 PathEl::QuadTo(_, _) => return None,
310 PathEl::CurveTo(_, _, _) => return None,
311 PathEl::ClosePath => {}
312 }
313 }
314
315 if corners[0] && corners[1] && corners[2] && corners[3] {
316 Some(bbox)
317 } else {
318 None
319 }
320}