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