1use crate::ClipPath;
2use crate::FillRule;
3use crate::color::ColorSpace;
4use crate::context::Context;
5use crate::convert::{convert_line_cap, convert_line_join};
6use crate::device::Device;
7use crate::font::{FontData, FontQuery};
8use crate::interpret::path::{
9 close_path, fill_path, fill_path_impl, fill_stroke_path, stroke_path,
10};
11use crate::interpret::state::{handle_gs, restore_state, save_sate};
12use crate::interpret::text::TextRenderingMode;
13use crate::pattern::{Pattern, ShadingPattern};
14use crate::shading::Shading;
15use crate::util::OptionLog;
16use crate::x_object::{ImageXObject, XObject, draw_image_xobject, draw_xobject};
17use hayro_syntax::content::ops::TypedInstruction;
18use hayro_syntax::object::dict::keys::OC;
19use hayro_syntax::object::{Dict, Object, dict_or_stream};
20use hayro_syntax::page::{Page, Resources};
21use kurbo::{Affine, Point, Shape};
22use log::warn;
23use smallvec::smallvec;
24use std::sync::Arc;
25
26pub(crate) mod path;
27pub(crate) mod state;
28pub(crate) mod text;
29
30pub type FontResolverFn = Arc<dyn Fn(&FontQuery) -> Option<(FontData, u32)> + Send + Sync>;
35pub type WarningSinkFn = Arc<dyn Fn(InterpreterWarning) + Send + Sync>;
37
38#[derive(Clone)]
39pub struct InterpreterSettings {
41 pub font_resolver: FontResolverFn,
77
78 pub warning_sink: WarningSinkFn,
81}
82
83impl Default for InterpreterSettings {
84 fn default() -> Self {
85 Self {
86 #[cfg(not(feature = "embed-fonts"))]
87 font_resolver: Arc::new(|_| None),
88 #[cfg(feature = "embed-fonts")]
89 font_resolver: Arc::new(|query| match query {
90 FontQuery::Standard(s) => Some(s.get_font_data()),
91 FontQuery::Fallback(f) => Some(f.pick_standard_font().get_font_data()),
92 }),
93 warning_sink: Arc::new(|_| {}),
94 }
95 }
96}
97
98#[derive(Copy, Clone, Debug)]
99pub enum InterpreterWarning {
101 JpxImage,
103 UnsupportedFont,
107 ImageDecodeFailure,
109}
110
111pub fn interpret_page<'a>(
113 page: &Page<'a>,
114 context: &mut Context<'a>,
115 device: &mut impl Device<'a>,
116) {
117 let resources = page.resources();
118 interpret(page.typed_operations(), resources, context, device)
119}
120
121pub fn interpret<'a, 'b>(
123 ops: impl Iterator<Item = TypedInstruction<'b>>,
124 resources: &Resources<'a>,
125 context: &mut Context<'a>,
126 device: &mut impl Device<'a>,
127) {
128 let num_states = context.num_states();
129 let n_clips = context.get().n_clips;
130
131 save_sate(context);
132
133 for op in ops {
134 match op {
135 TypedInstruction::SaveState(_) => save_sate(context),
136 TypedInstruction::StrokeColorDeviceRgb(s) => {
137 context.get_mut().graphics_state.stroke_cs = ColorSpace::device_rgb();
138 context.get_mut().graphics_state.stroke_color =
139 smallvec![s.0.as_f32(), s.1.as_f32(), s.2.as_f32()];
140 }
141 TypedInstruction::StrokeColorDeviceGray(s) => {
142 context.get_mut().graphics_state.stroke_cs = ColorSpace::device_gray();
143 context.get_mut().graphics_state.stroke_color = smallvec![s.0.as_f32()];
144 }
145 TypedInstruction::StrokeColorCmyk(s) => {
146 context.get_mut().graphics_state.stroke_cs = ColorSpace::device_cmyk();
147 context.get_mut().graphics_state.stroke_color =
148 smallvec![s.0.as_f32(), s.1.as_f32(), s.2.as_f32(), s.3.as_f32()];
149 }
150 TypedInstruction::LineWidth(w) => {
151 context.get_mut().graphics_state.stroke_props.line_width = w.0.as_f32();
152 }
153 TypedInstruction::LineCap(c) => {
154 context.get_mut().graphics_state.stroke_props.line_cap = convert_line_cap(c);
155 }
156 TypedInstruction::LineJoin(j) => {
157 context.get_mut().graphics_state.stroke_props.line_join = convert_line_join(j);
158 }
159 TypedInstruction::MiterLimit(l) => {
160 context.get_mut().graphics_state.stroke_props.miter_limit = l.0.as_f32();
161 }
162 TypedInstruction::Transform(t) => {
163 context.pre_concat_transform(t);
164 }
165 TypedInstruction::RectPath(r) => {
166 let rect = kurbo::Rect::new(
167 r.0.as_f64(),
168 r.1.as_f64(),
169 r.0.as_f64() + r.2.as_f64(),
170 r.1.as_f64() + r.3.as_f64(),
171 )
172 .to_path(0.1);
173 context.path_mut().extend(rect);
174 }
175 TypedInstruction::MoveTo(m) => {
176 let p = Point::new(m.0.as_f64(), m.1.as_f64());
177 *(context.last_point_mut()) = p;
178 *(context.sub_path_start_mut()) = p;
179 context.path_mut().move_to(p);
180 }
181 TypedInstruction::FillPathEvenOdd(_) => {
182 fill_path(context, device, FillRule::EvenOdd);
183 }
184 TypedInstruction::FillPathNonZero(_) => {
185 fill_path(context, device, FillRule::NonZero);
186 }
187 TypedInstruction::FillPathNonZeroCompatibility(_) => {
188 fill_path(context, device, FillRule::NonZero);
189 }
190 TypedInstruction::FillAndStrokeEvenOdd(_) => {
191 fill_stroke_path(context, device, FillRule::EvenOdd);
192 }
193 TypedInstruction::FillAndStrokeNonZero(_) => {
194 fill_stroke_path(context, device, FillRule::NonZero);
195 }
196 TypedInstruction::CloseAndStrokePath(_) => {
197 close_path(context);
198 stroke_path(context, device);
199 }
200 TypedInstruction::CloseFillAndStrokeEvenOdd(_) => {
201 close_path(context);
202 fill_stroke_path(context, device, FillRule::EvenOdd);
203 }
204 TypedInstruction::CloseFillAndStrokeNonZero(_) => {
205 close_path(context);
206 fill_stroke_path(context, device, FillRule::NonZero);
207 }
208 TypedInstruction::NonStrokeColorDeviceGray(s) => {
209 context.get_mut().graphics_state.none_stroke_cs = ColorSpace::device_gray();
210 context.get_mut().graphics_state.non_stroke_color = smallvec![s.0.as_f32()];
211 }
212 TypedInstruction::NonStrokeColorDeviceRgb(s) => {
213 context.get_mut().graphics_state.none_stroke_cs = ColorSpace::device_rgb();
214 context.get_mut().graphics_state.non_stroke_color =
215 smallvec![s.0.as_f32(), s.1.as_f32(), s.2.as_f32()];
216 }
217 TypedInstruction::NonStrokeColorCmyk(s) => {
218 context.get_mut().graphics_state.none_stroke_cs = ColorSpace::device_cmyk();
219 context.get_mut().graphics_state.non_stroke_color =
220 smallvec![s.0.as_f32(), s.1.as_f32(), s.2.as_f32(), s.3.as_f32()];
221 }
222 TypedInstruction::LineTo(m) => {
223 if !context.path().elements().is_empty() {
224 let last_point = *context.last_point();
225 let mut p = Point::new(m.0.as_f64(), m.1.as_f64());
226 *(context.last_point_mut()) = p;
227 if last_point == p {
228 p.x += 0.0001;
230 }
231
232 context.path_mut().line_to(p);
233 }
234 }
235 TypedInstruction::CubicTo(c) => {
236 if !context.path().elements().is_empty() {
237 let p1 = Point::new(c.0.as_f64(), c.1.as_f64());
238 let p2 = Point::new(c.2.as_f64(), c.3.as_f64());
239 let p3 = Point::new(c.4.as_f64(), c.5.as_f64());
240
241 *(context.last_point_mut()) = p3;
242
243 context.path_mut().curve_to(p1, p2, p3)
244 }
245 }
246 TypedInstruction::CubicStartTo(c) => {
247 if !context.path().elements().is_empty() {
248 let p1 = *context.last_point();
249 let p2 = Point::new(c.0.as_f64(), c.1.as_f64());
250 let p3 = Point::new(c.2.as_f64(), c.3.as_f64());
251
252 *(context.last_point_mut()) = p3;
253
254 context.path_mut().curve_to(p1, p2, p3)
255 }
256 }
257 TypedInstruction::CubicEndTo(c) => {
258 if !context.path().elements().is_empty() {
259 let p2 = Point::new(c.0.as_f64(), c.1.as_f64());
260 let p3 = Point::new(c.2.as_f64(), c.3.as_f64());
261
262 *(context.last_point_mut()) = p3;
263
264 context.path_mut().curve_to(p2, p3, p3)
265 }
266 }
267 TypedInstruction::ClosePath(_) => {
268 close_path(context);
269 }
270 TypedInstruction::SetGraphicsState(gs) => {
271 if let Some(gs) = resources
272 .get_ext_g_state::<Dict>(gs.0.clone(), Box::new(|_| None), Box::new(Some))
273 .warn_none(&format!("failed to get extgstate {}", gs.0.as_str()))
274 {
275 handle_gs(&gs, context, resources);
276 }
277 }
278 TypedInstruction::StrokePath(_) => {
279 stroke_path(context, device);
280 }
281 TypedInstruction::EndPath(_) => {
282 if let Some(clip) = *context.clip()
283 && !context.path().elements().is_empty()
284 {
285 let clip_path = context.get().ctm * context.path().clone();
286 context.push_bbox(clip_path.bounding_box());
287
288 device.push_clip_path(&ClipPath {
289 path: clip_path,
290 fill: clip,
291 });
292
293 context.get_mut().n_clips += 1;
294
295 *(context.clip_mut()) = None;
296 }
297
298 context.path_mut().truncate(0);
299 }
300 TypedInstruction::NonStrokeColor(c) => {
301 let fill_c = &mut context.get_mut().graphics_state.non_stroke_color;
302 fill_c.truncate(0);
303
304 for e in c.0 {
305 fill_c.push(e.as_f32());
306 }
307 }
308 TypedInstruction::StrokeColor(c) => {
309 let stroke_c = &mut context.get_mut().graphics_state.stroke_color;
310 stroke_c.truncate(0);
311
312 for e in c.0 {
313 stroke_c.push(e.as_f32());
314 }
315 }
316 TypedInstruction::ClipNonZero(_) => {
317 *(context.clip_mut()) = Some(FillRule::NonZero);
318 }
319 TypedInstruction::ClipEvenOdd(_) => {
320 *(context.clip_mut()) = Some(FillRule::EvenOdd);
321 }
322 TypedInstruction::RestoreState(_) => restore_state(context, device),
323 TypedInstruction::FlatnessTolerance(_) => {
324 }
326 TypedInstruction::ColorSpaceStroke(c) => {
327 let cs = if let Some(named) = ColorSpace::new_from_name(c.0.clone()) {
328 named
329 } else {
330 context
331 .get_color_space(resources, c.0)
332 .unwrap_or(ColorSpace::device_gray())
333 };
334
335 context.get_mut().graphics_state.stroke_color = cs.initial_color();
336 context.get_mut().graphics_state.stroke_cs = cs;
337 }
338 TypedInstruction::ColorSpaceNonStroke(c) => {
339 let cs = if let Some(named) = ColorSpace::new_from_name(c.0.clone()) {
340 named
341 } else {
342 context
343 .get_color_space(resources, c.0)
344 .unwrap_or(ColorSpace::device_gray())
345 };
346
347 context.get_mut().graphics_state.non_stroke_color = cs.initial_color();
348 context.get_mut().graphics_state.none_stroke_cs = cs;
349 }
350 TypedInstruction::DashPattern(p) => {
351 context.get_mut().graphics_state.stroke_props.dash_offset = p.1.as_f32();
352 context.get_mut().graphics_state.stroke_props.dash_array =
354 p.0.iter::<f32>()
355 .map(|n| if n == 0.0 { 0.01 } else { n })
356 .collect();
357 }
358 TypedInstruction::RenderingIntent(_) => {
359 }
361 TypedInstruction::NonStrokeColorNamed(n) => {
362 context.get_mut().graphics_state.non_stroke_color =
363 n.0.into_iter().map(|n| n.as_f32()).collect();
364 context.get_mut().graphics_state.non_stroke_pattern = n.1.and_then(|name| {
365 resources.get_pattern(
366 name,
367 Box::new(|_| None),
368 Box::new(|d| Pattern::new(d, context, resources)),
369 )
370 });
371 }
372 TypedInstruction::StrokeColorNamed(n) => {
373 context.get_mut().graphics_state.stroke_color =
374 n.0.into_iter().map(|n| n.as_f32()).collect();
375 context.get_mut().graphics_state.stroke_pattern = n.1.and_then(|name| {
376 resources.get_pattern(
377 name,
378 Box::new(|_| None),
379 Box::new(|d| Pattern::new(d, context, resources)),
380 )
381 });
382 }
383 TypedInstruction::BeginMarkedContentWithProperties(bdc) => {
384 if let Some(name) = bdc.1.clone().into_name()
389 && let Some(ocg_ref) = resources.properties.get_ref(name.clone())
390 {
391 context.ocg_state.begin_ocg(ocg_ref.into());
392 } else if let Some((props, _)) = dict_or_stream(&bdc.1)
393 && let Some(oc_ref) = props.get_ref(OC)
394 {
395 context.ocg_state.begin_ocg(oc_ref.into());
396 } else {
397 context.ocg_state.begin_marked_content();
398 }
399 }
400 TypedInstruction::MarkedContentPointWithProperties(_) => {}
401 TypedInstruction::EndMarkedContent(_) => {
402 context.ocg_state.end_marked_content();
403 }
404 TypedInstruction::MarkedContentPoint(_) => {}
405 TypedInstruction::BeginMarkedContent(_) => {
406 context.ocg_state.begin_marked_content();
407 }
408 TypedInstruction::BeginText(_) => {
409 context.get_mut().text_state.text_matrix = Affine::IDENTITY;
410 context.get_mut().text_state.text_line_matrix = Affine::IDENTITY;
411 }
412 TypedInstruction::SetTextMatrix(m) => {
413 let m = Affine::new([
414 m.0.as_f64(),
415 m.1.as_f64(),
416 m.2.as_f64(),
417 m.3.as_f64(),
418 m.4.as_f64(),
419 m.5.as_f64(),
420 ]);
421 context.get_mut().text_state.text_line_matrix = m;
422 context.get_mut().text_state.text_matrix = m;
423 }
424 TypedInstruction::EndText(_) => {
425 let has_outline = context
426 .get()
427 .text_state
428 .clip_paths
429 .segments()
430 .next()
431 .is_some();
432
433 if has_outline {
434 let clip_path = context.get().ctm * context.get().text_state.clip_paths.clone();
435
436 context.push_bbox(clip_path.bounding_box());
437
438 device.push_clip_path(&ClipPath {
439 path: clip_path,
440 fill: FillRule::NonZero,
441 });
442 context.get_mut().n_clips += 1;
443 }
444
445 context.get_mut().text_state.clip_paths.truncate(0);
446 }
447 TypedInstruction::TextFont(t) => {
448 let font = context.get_font(resources, t.0);
449 context.get_mut().text_state.font_size = t.1.as_f32();
450 context.get_mut().text_state.font = font;
451 }
452 TypedInstruction::ShowText(s) => {
453 text::show_text_string(context, device, resources, s.0);
454 }
455 TypedInstruction::ShowTexts(s) => {
456 for obj in s.0.iter::<Object>() {
457 if let Some(adjustment) = obj.clone().into_f32() {
458 context.get_mut().text_state.apply_adjustment(adjustment);
459 } else if let Some(text) = obj.into_string() {
460 text::show_text_string(context, device, resources, text);
461 }
462 }
463 }
464 TypedInstruction::HorizontalScaling(h) => {
465 context.get_mut().text_state.horizontal_scaling = h.0.as_f32();
466 }
467 TypedInstruction::TextLeading(tl) => {
468 context.get_mut().text_state.leading = tl.0.as_f32();
469 }
470 TypedInstruction::CharacterSpacing(c) => {
471 context.get_mut().text_state.char_space = c.0.as_f32()
472 }
473 TypedInstruction::WordSpacing(w) => {
474 context.get_mut().text_state.word_space = w.0.as_f32();
475 }
476 TypedInstruction::NextLine(n) => {
477 let (tx, ty) = (n.0.as_f64(), n.1.as_f64());
478 text::next_line(context, tx, ty)
479 }
480 TypedInstruction::NextLineUsingLeading(_) => {
481 text::next_line(context, 0.0, -context.get().text_state.leading as f64);
482 }
483 TypedInstruction::NextLineAndShowText(n) => {
484 text::next_line(context, 0.0, -context.get().text_state.leading as f64);
485 text::show_text_string(context, device, resources, n.0)
486 }
487 TypedInstruction::TextRenderingMode(r) => {
488 let mode = match r.0.as_i64() {
489 0 => TextRenderingMode::Fill,
490 1 => TextRenderingMode::Stroke,
491 2 => TextRenderingMode::FillStroke,
492 3 => TextRenderingMode::Invisible,
493 4 => TextRenderingMode::FillAndClip,
494 5 => TextRenderingMode::StrokeAndClip,
495 6 => TextRenderingMode::FillAndStrokeAndClip,
496 7 => TextRenderingMode::Clip,
497 _ => {
498 warn!("unknown text rendering mode {}", r.0.as_i64());
499
500 TextRenderingMode::Fill
501 }
502 };
503
504 context.get_mut().text_state.render_mode = mode;
505 }
506 TypedInstruction::NextLineAndSetLeading(n) => {
507 let (tx, ty) = (n.0.as_f64(), n.1.as_f64());
508 context.get_mut().text_state.leading = -ty as f32;
509 text::next_line(context, tx, ty)
510 }
511 TypedInstruction::ShapeGlyph(_) => {}
512 TypedInstruction::XObject(x) => {
513 let cache = context.object_cache.clone();
514 let transfer_function = context.get().graphics_state.transfer_function.clone();
515 if let Some(x_object) = resources.get_x_object(
516 x.0,
517 Box::new(|_| None),
518 Box::new(|s| {
519 XObject::new(
520 &s,
521 &context.settings.warning_sink,
522 &cache,
523 transfer_function.clone(),
524 )
525 }),
526 ) {
527 draw_xobject(&x_object, resources, context, device);
528 }
529 }
530 TypedInstruction::InlineImage(i) => {
531 let warning_sink = context.settings.warning_sink.clone();
532 let transfer_function = context.get().graphics_state.transfer_function.clone();
533 let cache = context.object_cache.clone();
534 if let Some(x_object) = ImageXObject::new(
535 &i.0,
536 |name| context.get_color_space(resources, name.clone()),
537 &warning_sink,
538 &cache,
539 false,
540 transfer_function,
541 ) {
542 draw_image_xobject(&x_object, context, device);
543 }
544 }
545 TypedInstruction::TextRise(t) => {
546 context.get_mut().text_state.rise = t.0.as_f32();
547 }
548 TypedInstruction::Shading(s) => {
549 if !context.ocg_state.is_visible() {
550 continue;
551 }
552
553 if let Some(sp) = resources
554 .get_shading(s.0, Box::new(|_| None), Box::new(Some))
555 .and_then(|o| dict_or_stream(&o))
556 .and_then(|s| Shading::new(&s.0, s.1.as_ref(), &context.object_cache))
557 .map(|s| {
558 Pattern::Shading(ShadingPattern {
559 shading: Arc::new(s),
560 matrix: Affine::IDENTITY,
561 })
562 })
563 {
564 context.save_state();
565 context.push_root_transform();
566 let st = context.get_mut();
567 st.graphics_state.non_stroke_pattern = Some(sp);
568 st.graphics_state.none_stroke_cs = ColorSpace::pattern();
569
570 device.set_soft_mask(st.graphics_state.soft_mask.clone());
571 device.push_transparency_group(st.graphics_state.non_stroke_alpha, None);
572
573 let bbox = context.bbox().to_path(0.1);
574 let inverted_bbox = context.get().ctm.inverse() * bbox;
575 fill_path_impl(context, device, FillRule::NonZero, Some(&inverted_bbox));
576
577 device.pop_transparency_group();
578
579 context.pop_root_transform();
580 context.restore_state();
581 } else {
582 warn!("failed to process shading");
583 }
584 }
585 TypedInstruction::BeginCompatibility(_) => {}
586 TypedInstruction::EndCompatibility(_) => {}
587 TypedInstruction::ColorGlyph(_) => {}
588 TypedInstruction::ShowTextWithParameters(t) => {
589 context.get_mut().text_state.word_space = t.0.as_f32();
590 context.get_mut().text_state.char_space = t.1.as_f32();
591 text::next_line(context, 0.0, -context.get().text_state.leading as f64);
592 text::show_text_string(context, device, resources, t.2)
593 }
594 _ => {
595 warn!("failed to read an operator");
596 }
597 }
598 }
599
600 while context.num_states() > num_states {
601 restore_state(context, device);
602 }
603
604 while context.get().n_clips > n_clips {
606 device.pop_clip_path();
607 context.pop_bbox();
608 context.get_mut().n_clips -= 1;
609 }
610}