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::{Font, FontData, FontQuery, StandardFont};
7use crate::interpret::path::{
8 close_path, fill_path, fill_path_impl, fill_stroke_path, stroke_path,
9};
10use crate::interpret::state::{TextStateFont, 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 kurbo::{Affine, Point, Shape};
19use log::warn;
20use pdf_syntax::content::ops::TypedInstruction;
21use pdf_syntax::object::dict::keys::{ANNOTS, AP, F, FT, MCID, N, OC, RECT};
22use pdf_syntax::object::{Array, Dict, Name, Object, Rect, Stream, dict_or_stream};
23use pdf_syntax::page::{Page, Resources};
24use smallvec::smallvec;
25use std::sync::{Arc, OnceLock};
26
27pub(crate) mod path;
28pub(crate) mod state;
29pub(crate) mod text;
30
31pub use state::ActiveTransferFunction;
32
33pub type FontResolverFn = Arc<dyn Fn(&FontQuery) -> Option<(FontData, u32)> + Send + Sync>;
38pub type CMapResolverFn =
40 Arc<dyn Fn(pdf_font::cmap::CMapName<'_>) -> Option<&'static [u8]> + Send + Sync>;
41pub type WarningSinkFn = Arc<dyn Fn(InterpreterWarning) + Send + Sync>;
43
44#[derive(Clone)]
45pub struct InterpreterSettings {
47 pub font_resolver: FontResolverFn,
83 pub cmap_resolver: CMapResolverFn,
96 pub warning_sink: WarningSinkFn,
99 pub render_annotations: bool,
104 pub skip_signature_widgets: bool,
109 pub max_operator_count: Option<u64>,
114}
115
116#[cfg(feature = "embed-fonts")]
119const CJK_FONT_CANDIDATE_PATHS: &[&str] = &[
120 "/System/Library/Fonts/Supplemental/Arial Unicode.ttf",
122 "/usr/share/fonts/noto-cjk/NotoSansCJK-Regular.ttc",
124 "/usr/share/fonts/truetype/noto/NotoSansCJK-Regular.ttc",
125 "/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc",
126 "/usr/share/fonts/noto-cjk/NotoSansCJKsc-Regular.otf",
127 "/usr/share/fonts/truetype/wqy/wqy-microhei.ttc",
129 "/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc",
130 "/usr/share/fonts/truetype/arphic/uming.ttc",
132 "/usr/share/fonts/noto-cjk/NotoSansCJK-Regular.ttc",
134];
135
136#[cfg(feature = "embed-fonts")]
138static SYSTEM_CJK_FONT: OnceLock<Option<Arc<Vec<u8>>>> = OnceLock::new();
139
140#[cfg(feature = "embed-fonts")]
142fn system_cjk_font() -> Option<FontData> {
143 SYSTEM_CJK_FONT
144 .get_or_init(|| {
145 for path in CJK_FONT_CANDIDATE_PATHS {
146 if let Ok(bytes) = std::fs::read(path) {
147 log::debug!("CJK fallback font loaded from {path}");
148 return Some(Arc::new(bytes));
149 }
150 }
151 log::warn!(
152 "no system CJK font found; non-embedded CJK fonts will render with a Latin fallback"
153 );
154 None
155 })
156 .as_ref()
157 .map(|data| -> FontData { data.clone() })
158}
159
160impl Default for InterpreterSettings {
161 fn default() -> Self {
162 Self {
163 #[cfg(not(feature = "embed-fonts"))]
164 font_resolver: Arc::new(|_| None),
165 #[cfg(feature = "embed-fonts")]
166 font_resolver: Arc::new(|query| match query {
167 FontQuery::Standard(s) => Some(s.get_font_data()),
168 FontQuery::Fallback(f) => {
169 if f.character_collection
174 .as_ref()
175 .is_some_and(|cc| cc.family.is_cjk())
176 && let Some(data) = system_cjk_font()
177 {
178 return Some((data, 0));
179 }
180 Some(f.pick_standard_font().get_font_data())
181 }
182 }),
183 #[cfg(feature = "embed-cmaps")]
184 cmap_resolver: Arc::new(pdf_font::cmap::load_embedded),
185 #[cfg(not(feature = "embed-cmaps"))]
186 cmap_resolver: Arc::new(|_| None),
187 warning_sink: Arc::new(|_| {}),
188 render_annotations: true,
189 skip_signature_widgets: true,
190 max_operator_count: None,
191 }
192 }
193}
194
195#[derive(Copy, Clone, Debug)]
196pub enum InterpreterWarning {
198 UnsupportedFont,
202 ImageDecodeFailure,
204 StreamTooLarge {
210 observed: u64,
212 limit: u64,
214 },
215}
216
217pub fn interpret_page<'a>(
219 page: &Page<'a>,
220 context: &mut Context<'a>,
221 device: &mut impl Device<'a>,
222) {
223 let resources = page.resources();
224 interpret(page.typed_operations(), resources, context, device);
225
226 if context.settings.render_annotations
227 && let Some(annot_arr) = page.raw().get::<Array<'_>>(ANNOTS)
228 {
229 for annot in annot_arr.iter::<Dict<'_>>() {
230 let flags = annot.get::<u32>(F).unwrap_or(0);
231
232 if flags & 2 != 0 {
234 continue;
235 }
236
237 if context.settings.skip_signature_widgets
242 && annot
243 .get::<Name>(FT)
244 .as_deref()
245 .is_some_and(|n| n == b"Sig")
246 {
247 continue;
248 }
249
250 if let Some(apx) = annot
251 .get::<Dict<'_>>(AP)
252 .and_then(|ap| ap.get::<Stream<'_>>(N))
253 .and_then(|o| FormXObject::new(&o, &context.settings.warning_sink))
254 {
255 let Some(rect) = annot.get::<Rect>(RECT) else {
256 continue;
257 };
258
259 let annot_rect = rect.to_kurbo();
260 let transformed_rect = (apx.matrix
270 * kurbo::Rect::new(
271 apx.bbox[0] as f64,
272 apx.bbox[1] as f64,
273 apx.bbox[2] as f64,
274 apx.bbox[3] as f64,
275 )
276 .to_path(0.1))
277 .bounding_box();
278
279 let affine = Affine::new([
288 annot_rect.width() / transformed_rect.width(),
289 0.0,
290 0.0,
291 annot_rect.height() / transformed_rect.height(),
292 annot_rect.x0 - transformed_rect.x0,
293 annot_rect.y0 - transformed_rect.y0,
294 ]);
295
296 context.save_state();
300 context.pre_concat_affine(affine);
301 context.push_root_transform();
302
303 draw_form_xobject(resources, &apx, context, device);
304 context.pop_root_transform();
305 context.restore_state(device);
306 }
307 }
308 }
309}
310
311pub fn interpret<'a, 'b>(
313 ops: impl Iterator<Item = TypedInstruction<'b>>,
314 resources: &Resources<'a>,
315 context: &mut Context<'a>,
316 device: &mut impl Device<'a>,
317) {
318 let num_states = context.num_states();
319 let max_operator_count = context.settings.max_operator_count.unwrap_or(u64::MAX);
320 let mut operator_count = 0_u64;
321
322 context.save_state();
323
324 for op in ops {
325 operator_count = operator_count.saturating_add(1);
326 if operator_count > max_operator_count {
327 warn!(
328 "content stream operator count exceeds {max_operator_count}, stopping interpretation"
329 );
330 break;
331 }
332
333 match op {
334 TypedInstruction::SaveState(_) => context.save_state(),
335 TypedInstruction::StrokeColorDeviceRgb(s) => {
336 context.get_mut().graphics_state.stroke_cs = ColorSpace::device_rgb();
337 context.get_mut().graphics_state.stroke_color =
338 smallvec![s.0.as_f32(), s.1.as_f32(), s.2.as_f32()];
339 }
340 TypedInstruction::StrokeColorDeviceGray(s) => {
341 context.get_mut().graphics_state.stroke_cs = ColorSpace::device_gray();
342 context.get_mut().graphics_state.stroke_color = smallvec![s.0.as_f32()];
343 }
344 TypedInstruction::StrokeColorCmyk(s) => {
345 context.get_mut().graphics_state.stroke_cs = ColorSpace::device_cmyk();
346 context.get_mut().graphics_state.stroke_color =
347 smallvec![s.0.as_f32(), s.1.as_f32(), s.2.as_f32(), s.3.as_f32()];
348 }
349 TypedInstruction::LineWidth(w) => {
350 context.get_mut().graphics_state.stroke_props.line_width = w.0.as_f32();
351 }
352 TypedInstruction::LineCap(c) => {
353 context.get_mut().graphics_state.stroke_props.line_cap = convert_line_cap(c);
354 }
355 TypedInstruction::LineJoin(j) => {
356 context.get_mut().graphics_state.stroke_props.line_join = convert_line_join(j);
357 }
358 TypedInstruction::MiterLimit(l) => {
359 context.get_mut().graphics_state.stroke_props.miter_limit = l.0.as_f32();
360 }
361 TypedInstruction::Transform(t) => {
362 context.pre_concat_transform(t);
363 }
364 TypedInstruction::RectPath(r) => {
365 let rect = kurbo::Rect::new(
366 r.0.as_f64(),
367 r.1.as_f64(),
368 r.0.as_f64() + r.2.as_f64(),
369 r.1.as_f64() + r.3.as_f64(),
370 )
371 .to_path(0.1);
372 context.path_mut().extend(rect);
373 }
374 TypedInstruction::MoveTo(m) => {
375 let p = Point::new(m.0.as_f64(), m.1.as_f64());
376 *(context.last_point_mut()) = p;
377 *(context.sub_path_start_mut()) = p;
378 context.path_mut().move_to(p);
379 }
380 TypedInstruction::FillPathEvenOdd(_) => {
381 fill_path(context, device, FillRule::EvenOdd);
382 }
383 TypedInstruction::FillPathNonZero(_) => {
384 fill_path(context, device, FillRule::NonZero);
385 }
386 TypedInstruction::FillPathNonZeroCompatibility(_) => {
387 fill_path(context, device, FillRule::NonZero);
388 }
389 TypedInstruction::FillAndStrokeEvenOdd(_) => {
390 fill_stroke_path(context, device, FillRule::EvenOdd);
391 }
392 TypedInstruction::FillAndStrokeNonZero(_) => {
393 fill_stroke_path(context, device, FillRule::NonZero);
394 }
395 TypedInstruction::CloseAndStrokePath(_) => {
396 close_path(context);
397 stroke_path(context, device);
398 }
399 TypedInstruction::CloseFillAndStrokeEvenOdd(_) => {
400 close_path(context);
401 fill_stroke_path(context, device, FillRule::EvenOdd);
402 }
403 TypedInstruction::CloseFillAndStrokeNonZero(_) => {
404 close_path(context);
405 fill_stroke_path(context, device, FillRule::NonZero);
406 }
407 TypedInstruction::NonStrokeColorDeviceGray(s) => {
408 context.get_mut().graphics_state.none_stroke_cs = ColorSpace::device_gray();
409 context.get_mut().graphics_state.non_stroke_color = smallvec![s.0.as_f32()];
410 }
411 TypedInstruction::NonStrokeColorDeviceRgb(s) => {
412 context.get_mut().graphics_state.none_stroke_cs = ColorSpace::device_rgb();
413 context.get_mut().graphics_state.non_stroke_color =
414 smallvec![s.0.as_f32(), s.1.as_f32(), s.2.as_f32()];
415 }
416 TypedInstruction::NonStrokeColorCmyk(s) => {
417 context.get_mut().graphics_state.none_stroke_cs = ColorSpace::device_cmyk();
418 context.get_mut().graphics_state.non_stroke_color =
419 smallvec![s.0.as_f32(), s.1.as_f32(), s.2.as_f32(), s.3.as_f32()];
420 }
421 TypedInstruction::LineTo(m) => {
422 if !context.path().elements().is_empty() {
423 let last_point = *context.last_point();
424 let mut p = Point::new(m.0.as_f64(), m.1.as_f64());
425 *(context.last_point_mut()) = p;
426 if last_point == p {
427 p.x += 0.0001;
429 }
430
431 context.path_mut().line_to(p);
432 }
433 }
434 TypedInstruction::CubicTo(c) => {
435 if !context.path().elements().is_empty() {
436 let p1 = Point::new(c.0.as_f64(), c.1.as_f64());
437 let p2 = Point::new(c.2.as_f64(), c.3.as_f64());
438 let p3 = Point::new(c.4.as_f64(), c.5.as_f64());
439
440 *(context.last_point_mut()) = p3;
441
442 context.path_mut().curve_to(p1, p2, p3);
443 }
444 }
445 TypedInstruction::CubicStartTo(c) => {
446 if !context.path().elements().is_empty() {
447 let p1 = *context.last_point();
448 let p2 = Point::new(c.0.as_f64(), c.1.as_f64());
449 let p3 = Point::new(c.2.as_f64(), c.3.as_f64());
450
451 *(context.last_point_mut()) = p3;
452
453 context.path_mut().curve_to(p1, p2, p3);
454 }
455 }
456 TypedInstruction::CubicEndTo(c) => {
457 if !context.path().elements().is_empty() {
458 let p2 = Point::new(c.0.as_f64(), c.1.as_f64());
459 let p3 = Point::new(c.2.as_f64(), c.3.as_f64());
460
461 *(context.last_point_mut()) = p3;
462
463 context.path_mut().curve_to(p2, p3, p3);
464 }
465 }
466 TypedInstruction::ClosePath(_) => {
467 close_path(context);
468 }
469 TypedInstruction::SetGraphicsState(gs) => {
470 if let Some(gs) = resources
471 .get_ext_g_state(gs.0.clone())
472 .warn_none(&format!("failed to get extgstate {}", gs.0.as_str()))
473 {
474 handle_gs(&gs, context, resources);
475 }
476 }
477 TypedInstruction::StrokePath(_) => {
478 stroke_path(context, device);
479 }
480 TypedInstruction::EndPath(_) => {
481 if let Some(clip) = *context.clip()
482 && !context.path().elements().is_empty()
483 {
484 let clip_path = context.get().ctm * context.path().clone();
485 context.push_clip_path(clip_path, clip, device);
486
487 *(context.clip_mut()) = None;
488 }
489
490 context.path_mut().truncate(0);
491 }
492 TypedInstruction::NonStrokeColor(c) => {
493 let fill_c = &mut context.get_mut().graphics_state.non_stroke_color;
494 fill_c.truncate(0);
495
496 for e in c.0 {
497 fill_c.push(e.as_f32());
498 }
499 }
500 TypedInstruction::StrokeColor(c) => {
501 let stroke_c = &mut context.get_mut().graphics_state.stroke_color;
502 stroke_c.truncate(0);
503
504 for e in c.0 {
505 stroke_c.push(e.as_f32());
506 }
507 }
508 TypedInstruction::ClipNonZero(_) => {
509 *(context.clip_mut()) = Some(FillRule::NonZero);
510 }
511 TypedInstruction::ClipEvenOdd(_) => {
512 *(context.clip_mut()) = Some(FillRule::EvenOdd);
513 }
514 TypedInstruction::RestoreState(_) => context.restore_state(device),
515 TypedInstruction::FlatnessTolerance(_) => {
516 }
518 TypedInstruction::ColorSpaceStroke(c) => {
519 let cs = if let Some(named) = ColorSpace::new_from_name(c.0.clone()) {
520 named
521 } else {
522 context
523 .get_color_space(resources, c.0)
524 .unwrap_or(ColorSpace::device_gray())
525 };
526
527 context.get_mut().graphics_state.stroke_color = cs.initial_color();
528 context.get_mut().graphics_state.stroke_cs = cs;
529 }
530 TypedInstruction::ColorSpaceNonStroke(c) => {
531 let cs = if let Some(named) = ColorSpace::new_from_name(c.0.clone()) {
532 named
533 } else {
534 context
535 .get_color_space(resources, c.0)
536 .unwrap_or(ColorSpace::device_gray())
537 };
538
539 context.get_mut().graphics_state.non_stroke_color = cs.initial_color();
540 context.get_mut().graphics_state.none_stroke_cs = cs;
541 }
542 TypedInstruction::DashPattern(p) => {
543 context.get_mut().graphics_state.stroke_props.dash_offset = p.1.as_f32();
544 context.get_mut().graphics_state.stroke_props.dash_array =
546 p.0.iter::<f32>()
547 .map(|n| if n == 0.0 { 0.01 } else { n })
548 .collect();
549 }
550 TypedInstruction::RenderingIntent(_) => {
551 }
553 TypedInstruction::NonStrokeColorNamed(n) => {
554 context.get_mut().graphics_state.non_stroke_color =
555 n.0.into_iter().map(|n| n.as_f32()).collect();
556 context.get_mut().graphics_state.non_stroke_pattern = n.1.and_then(|name| {
557 resources
558 .get_pattern(name)
559 .and_then(|d| Pattern::new(d, context, resources))
560 });
561 }
562 TypedInstruction::StrokeColorNamed(n) => {
563 context.get_mut().graphics_state.stroke_color =
564 n.0.into_iter().map(|n| n.as_f32()).collect();
565 context.get_mut().graphics_state.stroke_pattern = n.1.and_then(|name| {
566 resources
567 .get_pattern(name)
568 .and_then(|d| Pattern::new(d, context, resources))
569 });
570 }
571 TypedInstruction::BeginMarkedContentWithProperties(bdc) => {
572 let mcid = dict_or_stream(&bdc.1).and_then(|(props, _)| props.get::<i32>(MCID));
577
578 let oc = bdc
579 .1
580 .clone()
581 .into_name()
582 .and_then(|name| {
583 let r = resources.properties.get_ref(name.clone())?;
584 let d = resources
585 .properties
586 .get::<Dict<'_>>(name)
587 .unwrap_or_default();
588 Some((d, r))
589 })
590 .or_else(|| {
591 let (props, _) = dict_or_stream(&bdc.1)?;
592 let r = props.get_ref(OC)?;
593 let d = props.get::<Dict<'_>>(OC).unwrap_or_default();
594 Some((d, r))
595 });
596
597 if let Some((dict, oc_ref)) = oc {
598 context.ocg_state.begin_ocg(&dict, oc_ref.into());
599 } else {
600 context.ocg_state.begin_marked_content();
601 }
602
603 device.begin_marked_content(&bdc.0, mcid);
604 }
605 TypedInstruction::MarkedContentPointWithProperties(_) => {}
606 TypedInstruction::EndMarkedContent(_) => {
607 context.ocg_state.end_marked_content();
608 device.end_marked_content();
609 }
610 TypedInstruction::MarkedContentPoint(_) => {}
611 TypedInstruction::BeginMarkedContent(bmc) => {
612 context.ocg_state.begin_marked_content();
613 device.begin_marked_content(&bmc.0, None);
614 }
615 TypedInstruction::BeginText(_) => {
616 context.get_mut().text_state.text_matrix = Affine::IDENTITY;
617 context.get_mut().text_state.text_line_matrix = Affine::IDENTITY;
618 }
619 TypedInstruction::SetTextMatrix(m) => {
620 let m = Affine::new([
621 m.0.as_f64(),
622 m.1.as_f64(),
623 m.2.as_f64(),
624 m.3.as_f64(),
625 m.4.as_f64(),
626 m.5.as_f64(),
627 ]);
628 context.get_mut().text_state.text_line_matrix = m;
629 context.get_mut().text_state.text_matrix = m;
630 }
631 TypedInstruction::EndText(_) => {
632 let has_outline = context
633 .get()
634 .text_state
635 .clip_paths
636 .segments()
637 .next()
638 .is_some();
639
640 if has_outline {
641 let clip_path = context.get().ctm * context.get().text_state.clip_paths.clone();
642
643 context.push_clip_path(clip_path, FillRule::NonZero, device);
644 }
645
646 context.get_mut().text_state.clip_paths.truncate(0);
647 }
648 TypedInstruction::TextFont(t) => {
649 let name = t.0;
650
651 let font = if let Some(font_dict) = resources.get_font(name.clone()) {
658 context.resolve_font(&font_dict)
659 } else {
660 Font::new_standard(StandardFont::Helvetica, &context.settings.font_resolver)
661 .map(TextStateFont::Fallback)
662 };
663
664 context.get_mut().text_state.font_size = t.1.as_f32();
665 context.get_mut().text_state.font = font;
666 }
667 TypedInstruction::ShowText(s) => {
668 if context.get().text_state.font.is_none() {
669 context.get_mut().text_state.font = Font::new_standard(
672 StandardFont::Helvetica,
673 &context.settings.font_resolver,
674 )
675 .map(TextStateFont::Fallback);
676 }
677
678 text::show_text_string(context, device, resources, s.0);
679 }
680 TypedInstruction::ShowTexts(s) => {
681 if context.get().text_state.font.is_none() {
682 context.get_mut().text_state.font = Font::new_standard(
685 StandardFont::Helvetica,
686 &context.settings.font_resolver,
687 )
688 .map(TextStateFont::Fallback);
689 }
690
691 for obj in s.0.iter::<Object<'_>>() {
692 if let Some(adjustment) = obj.clone().into_f32() {
693 device.text_adjustment(adjustment);
698 context.get_mut().text_state.apply_adjustment(adjustment);
699 } else if let Some(text) = obj.into_string() {
700 text::show_text_string(context, device, resources, text);
701 }
702 }
703 }
704 TypedInstruction::HorizontalScaling(h) => {
705 context.get_mut().text_state.horizontal_scaling = h.0.as_f32();
706 }
707 TypedInstruction::TextLeading(tl) => {
708 context.get_mut().text_state.leading = tl.0.as_f32();
709 }
710 TypedInstruction::CharacterSpacing(c) => {
711 context.get_mut().text_state.char_space = c.0.as_f32();
712 }
713 TypedInstruction::WordSpacing(w) => {
714 context.get_mut().text_state.word_space = w.0.as_f32();
715 }
716 TypedInstruction::NextLine(n) => {
717 let (tx, ty) = (n.0.as_f64(), n.1.as_f64());
718 text::next_line(context, tx, ty);
719 }
720 TypedInstruction::NextLineUsingLeading(_) => {
721 text::next_line(context, 0.0, -context.get().text_state.leading as f64);
722 }
723 TypedInstruction::NextLineAndShowText(n) => {
724 text::next_line(context, 0.0, -context.get().text_state.leading as f64);
725 text::show_text_string(context, device, resources, n.0);
726 }
727 TypedInstruction::TextRenderingMode(r) => {
728 let mode = match r.0.as_i64() {
729 0 => TextRenderingMode::Fill,
730 1 => TextRenderingMode::Stroke,
731 2 => TextRenderingMode::FillStroke,
732 3 => TextRenderingMode::Invisible,
733 4 => TextRenderingMode::FillAndClip,
734 5 => TextRenderingMode::StrokeAndClip,
735 6 => TextRenderingMode::FillAndStrokeAndClip,
736 7 => TextRenderingMode::Clip,
737 _ => {
738 warn!("unknown text rendering mode {}", r.0.as_i64());
739
740 TextRenderingMode::Fill
741 }
742 };
743
744 context.get_mut().text_state.render_mode = mode;
745 }
746 TypedInstruction::NextLineAndSetLeading(n) => {
747 let (tx, ty) = (n.0.as_f64(), n.1.as_f64());
748 context.get_mut().text_state.leading = -ty as f32;
749 text::next_line(context, tx, ty);
750 }
751 TypedInstruction::ShapeGlyph(_) => {}
758 TypedInstruction::XObject(x) => {
759 let cache = context.object_cache.clone();
760 let transfer_function = context.get().graphics_state.transfer_function.clone();
761 if let Some(x_object) = resources.get_x_object(x.0).and_then(|s| {
762 XObject::new(
763 &s,
764 &context.settings.warning_sink,
765 &cache,
766 transfer_function.clone(),
767 )
768 }) {
769 draw_xobject(&x_object, resources, context, device);
770 }
771 }
772 TypedInstruction::InlineImage(i) => {
773 let warning_sink = context.settings.warning_sink.clone();
774 let transfer_function = context.get().graphics_state.transfer_function.clone();
775 let cache = context.object_cache.clone();
776 if let Some(x_object) = ImageXObject::new(
777 &i.0,
778 |name| context.get_color_space(resources, name.clone()),
779 &warning_sink,
780 &cache,
781 false,
782 transfer_function,
783 ) {
784 draw_image_xobject(&x_object, context, device);
785 }
786 }
787 TypedInstruction::TextRise(t) => {
788 context.get_mut().text_state.rise = t.0.as_f32();
789 }
790 TypedInstruction::Shading(s) => {
791 if !context.ocg_state.is_visible() {
792 continue;
793 }
794
795 let transfer_function = context.get().graphics_state.transfer_function.clone();
796
797 if let Some(sp) = resources
798 .get_shading(s.0)
799 .and_then(|o| dict_or_stream(&o))
800 .and_then(|s| {
801 Shading::new(
802 &s.0,
803 s.1.as_ref(),
804 &context.object_cache,
805 &context.settings.warning_sink,
806 )
807 })
808 .map(|s| {
809 Pattern::Shading(ShadingPattern {
810 shading: Arc::new(s),
811 matrix: Affine::IDENTITY,
812 opacity: context.get().graphics_state.non_stroke_alpha,
813 transfer_function: transfer_function.clone(),
814 })
815 })
816 {
817 context.save_state();
818 context.push_root_transform();
819 let st = context.get_mut();
820 st.graphics_state.non_stroke_pattern = Some(sp);
821 st.graphics_state.none_stroke_cs = ColorSpace::pattern();
822
823 device.set_soft_mask(st.graphics_state.soft_mask.clone());
824 device.set_blend_mode(st.graphics_state.blend_mode);
825
826 let bbox = context.bbox().to_path(0.1);
827 let inverted_bbox = context.get().ctm.inverse() * bbox;
828 fill_path_impl(context, device, FillRule::NonZero, Some(&inverted_bbox));
829
830 context.pop_root_transform();
831 context.restore_state(device);
832 } else {
833 warn!("failed to process shading");
834 }
835 }
836 TypedInstruction::BeginCompatibility(_) => {}
837 TypedInstruction::EndCompatibility(_) => {}
838 TypedInstruction::ColorGlyph(_) => {}
841 TypedInstruction::ShowTextWithParameters(t) => {
842 context.get_mut().text_state.word_space = t.0.as_f32();
843 context.get_mut().text_state.char_space = t.1.as_f32();
844 text::next_line(context, 0.0, -context.get().text_state.leading as f64);
845 text::show_text_string(context, device, resources, t.2);
846 }
847 _ => {
848 warn!("failed to read an operator");
849 }
850 }
851 }
852
853 while context.num_states() > num_states {
854 context.restore_state(device);
855 }
856}