1pub mod cid_unicode;
11pub mod cmap;
12pub mod color_space;
13pub mod font;
14mod gid_maps;
15pub mod graphics_state;
16mod standard_fonts;
17
18use crate::error::PdfError;
19use crate::lexer::{Lexer, Token};
20use crate::objects::{PdfDict, PdfObj};
21use crate::resolver::Resolver;
22
23use self::color_space::{
24 ResolvedColorSpace, painted_channels_for_cs, register_icc_profile, resolve_color_space,
25 resolve_color_space_obj, to_image_color_space,
26};
27use self::graphics_state::{ColorSpaceRef, PdfGraphicsState};
28
29use std::sync::{Arc, Mutex};
30
31use self::font::{FontCache, PdfFont};
32use self::graphics_state::{ShadingPatternDL, TilingPattern};
33use crate::FontProvider;
34use stet_fonts::geometry::{Matrix, PathSegment, PsPath};
35use stet_graphics::color::{DashPattern, DeviceColor, FillRule, LineCap, LineJoin};
36use stet_graphics::device::{
37 ClipParams, FillParams, ImageColorSpace, ImageParams, PatternFillParams, StrokeParams,
38 TintLookupTable,
39};
40use stet_graphics::display_list::{
41 DisplayElement, DisplayList, GroupParams, OcgVisibility, SoftMaskParams, SoftMaskSubtype,
42};
43use stet_graphics::icc::IccCache;
44
45enum MarkedContentFrame {
51 Ocg {
54 parent_list: DisplayList,
55 visibility: OcgVisibility,
56 },
57 Other,
59}
60
61fn deref_num_array(resolver: &Resolver, dict: &PdfDict, key: &[u8]) -> Option<Vec<f64>> {
69 let obj = dict.get(key)?;
70 if let Some(arr) = obj.as_array() {
71 return Some(arr.iter().filter_map(|o| o.as_f64()).collect());
72 }
73 let resolved = resolver.deref(obj).ok()?;
74 resolved
75 .as_array()
76 .map(|a| a.iter().filter_map(|o| o.as_f64()).collect())
77}
78
79#[derive(Clone, Debug)]
81pub enum Operand {
82 Int(i64),
83 Real(f64),
84 Name(Vec<u8>),
85 Str(Vec<u8>),
86 Array(Vec<PdfObj>),
87 Dict(PdfDict),
88 Bool(bool),
89}
90
91impl Operand {
92 fn as_f64(&self) -> Option<f64> {
94 match self {
95 Operand::Int(n) => Some(*n as f64),
96 Operand::Real(f) => Some(*f),
97 _ => None,
98 }
99 }
100
101 fn as_name(&self) -> Option<&[u8]> {
103 match self {
104 Operand::Name(n) => Some(n),
105 _ => None,
106 }
107 }
108
109 #[allow(dead_code)]
111 fn as_str(&self) -> Option<&[u8]> {
112 match self {
113 Operand::Str(s) => Some(s),
114 _ => None,
115 }
116 }
117}
118
119#[derive(Clone)]
122struct CachedImage {
123 sample_data: Arc<Vec<u8>>,
124 width: u32,
125 height: u32,
126 color_space: ImageColorSpace,
127 bits_per_component: u8,
128 interpolate: bool,
129 mask_color: Option<Vec<u8>>,
130 painted_channels: u8,
132 smask: Option<(Arc<Vec<u8>>, u32, u32, Option<Vec<f64>>)>,
134 rendering_intent: u8,
138}
139
140struct SoftMaskScope {
142 start_index: usize,
144 mask: graphics_state::SoftMask,
146}
147
148pub struct ContentInterpreter<'a> {
150 resolver: &'a Resolver<'a>,
151 resources: PdfDict,
152 gstate_stack: Vec<PdfGraphicsState>,
153 gstate: PdfGraphicsState,
154 current_path: PsPath,
155 current_point: Option<(f64, f64)>,
156 subpath_start: Option<(f64, f64)>,
157 operand_stack: Vec<Operand>,
158 display_list: DisplayList,
159 in_text: bool,
160 depth: u32,
161 d1_color_suppressed: bool,
164 font_cache: FontCache,
165 current_font: Option<Arc<PdfFont>>,
166 content_stream_ctm: Matrix,
171 initial_ctm: Matrix,
176 icc_cache: IccCache,
178 soft_mask_scope: Option<SoftMaskScope>,
180 nested_mask_flush_count: u32,
184 font_provider: Option<FontProvider>,
186 text_clip_path: Option<PsPath>,
189 page_group_is_cmyk: bool,
192 pdfx_cmyk_intent: bool,
208 in_smask_form: bool,
221 overprint_enabled: bool,
224 pattern_cache: std::collections::HashMap<(u32, u16), TilingPattern>,
228 ocg_off: std::collections::HashSet<u32>,
230 mc_stack: Vec<MarkedContentFrame>,
235 cs_index: Option<std::collections::HashMap<Vec<u8>, PdfObj>>,
238 form_cull_y: Option<(f64, f64)>,
242 bt_culled: bool,
244 image_cache: std::collections::HashMap<u32, CachedImage>,
248 spot_tint_table_cache:
255 std::collections::HashMap<Vec<u8>, Arc<stet_graphics::device::TintLookupTable>>,
256}
257
258impl<'a> ContentInterpreter<'a> {
259 pub fn new(
261 resolver: &'a Resolver<'a>,
262 resources: PdfDict,
263 initial_ctm: Matrix,
264 icc_cache: &IccCache,
265 font_provider: Option<FontProvider>,
266 overprint_enabled: bool,
267 ocg_off: &std::collections::HashSet<u32>,
268 ) -> Self {
269 Self {
270 resolver,
271 resources,
272 gstate_stack: Vec::new(),
273 gstate: PdfGraphicsState::new(initial_ctm),
274 current_path: PsPath::new(),
275 current_point: None,
276 subpath_start: None,
277 operand_stack: Vec::new(),
278 display_list: DisplayList::new(),
279 content_stream_ctm: initial_ctm,
280 initial_ctm,
281 in_text: false,
282 depth: 0,
283 d1_color_suppressed: false,
284 nested_mask_flush_count: 0,
285 font_cache: FontCache::new(),
286 current_font: None,
287 icc_cache: icc_cache.clone(),
288 soft_mask_scope: None,
289 font_provider,
290 text_clip_path: None,
291 page_group_is_cmyk: false,
292 pdfx_cmyk_intent: false,
293 in_smask_form: false,
294 overprint_enabled,
295 pattern_cache: std::collections::HashMap::new(),
296 ocg_off: ocg_off.clone(),
297 mc_stack: Vec::new(),
298 cs_index: None,
299 form_cull_y: None,
300 bt_culled: false,
301 image_cache: std::collections::HashMap::new(),
302 spot_tint_table_cache: std::collections::HashMap::new(),
303 }
304 }
305
306 pub fn set_page_group_cmyk(&mut self) {
310 self.page_group_is_cmyk = true;
311 }
312
313 pub fn set_pdfx_cmyk_intent(&mut self) {
317 self.pdfx_cmyk_intent = true;
318 }
319
320 fn resolve_dict_int(&self, dict: &PdfDict, key: &[u8]) -> Option<i64> {
324 let obj = dict.get(key)?;
325 if let Some(n) = obj.as_int() {
326 return Some(n);
327 }
328 let resolved = self.resolver.deref(obj).ok()?;
330 resolved.as_int()
331 }
332
333 fn resolve_resource_subdict(&self, key: &[u8]) -> Option<PdfDict> {
334 let obj = self.resources.get(key)?;
335 if let Some(d) = obj.as_dict() {
337 return Some(d.clone());
338 }
339 let resolved = self.resolver.deref(obj).ok()?;
341 resolved.as_dict().cloned()
342 }
343
344 pub fn interpret(mut self, data: &[u8]) -> Result<DisplayList, PdfError> {
346 if let Err(e) = self.interpret_stream(data) {
347 eprintln!("warning: content stream error: {}", e);
348 }
349 self.flush_soft_mask();
351 Ok(self.display_list)
354 }
355
356 pub fn interpret_stream_public(&mut self, data: &[u8]) -> Result<(), PdfError> {
358 self.interpret_stream(data)
359 }
360
361 pub fn into_display_list(mut self) -> DisplayList {
363 self.flush_soft_mask();
364 while let Some(frame) = self.mc_stack.pop() {
366 if let MarkedContentFrame::Ocg {
367 parent_list,
368 visibility,
369 } = frame
370 {
371 let ocg_list = std::mem::replace(&mut self.display_list, parent_list);
372 self.display_list.push(DisplayElement::OcgGroup {
373 elements: ocg_list,
374 visibility,
375 });
376 }
377 }
378 self.display_list
379 }
380
381 pub fn unwind_gstate_stack(&mut self) {
385 while let Some(saved) = self.gstate_stack.pop() {
386 let old_clip_version = self.gstate.clip_path_version;
387 self.gstate = saved;
388 if self.gstate.clip_path_version != old_clip_version {
389 self.restore_clip_from_stack();
390 }
391 }
392 }
393
394 pub fn reset_clip_for_annotations(&mut self) {
397 self.display_list.push(DisplayElement::InitClip);
398 self.gstate.clip_path = None;
399 self.gstate.clip_stack.clear();
400 self.gstate.clip_path_version += 1;
401 }
402
403 pub fn render_annotation(&mut self, obj_num: u32, gen_num: u16) -> Result<(), PdfError> {
405 let annot_obj = self.resolver.resolve(obj_num, gen_num)?;
406 let annot_dict = annot_obj
407 .as_dict()
408 .ok_or(PdfError::Other("annotation not a dict".into()))?;
409
410 let subtype = annot_dict.get_name(b"Subtype").unwrap_or(b"");
411
412 let flags = annot_dict.get_int(b"F").unwrap_or(0);
416 if flags & 0x02 != 0 {
417 return Ok(()); }
419
420 let rect = annot_dict
423 .get(b"Rect")
424 .and_then(|obj| {
425 let resolved = self.resolver.deref(obj).ok().unwrap_or(obj.clone());
426 let a = resolved
427 .as_array()
428 .or_else(|| annot_dict.get_array(b"Rect"))?;
429 if a.len() >= 4 {
430 let r0 = a[0].as_f64()?;
431 let r1 = a[1].as_f64()?;
432 let r2 = a[2].as_f64()?;
433 let r3 = a[3].as_f64()?;
434 Some([r0.min(r2), r1.min(r3), r0.max(r2), r1.max(r3)])
435 } else {
436 None
437 }
438 })
439 .ok_or(PdfError::Other("annotation missing Rect".into()))?;
440
441 let ap_obj = match annot_dict.get(b"AP") {
444 Some(ap) => ap,
445 None => {
446 return self.synthesize_annotation(annot_dict, &rect);
447 }
448 };
449 let ap_dict = match self.resolver.deref(ap_obj)? {
450 PdfObj::Dict(d) => d,
451 _ => return Err(PdfError::Other("AP not a dict".into())),
452 };
453
454 let n_ref = ap_dict.get(b"N").ok_or(PdfError::Other("no AP/N".into()))?;
455
456 let n_obj = self.resolver.deref(n_ref)?;
460 let (n_ref, form_dict) = if let Some(d) = n_obj.as_dict() {
461 if d.get(b"BBox").is_some() {
462 (n_ref.clone(), d.clone())
464 } else {
465 let as_name = annot_dict.get_name(b"AS").unwrap_or(b"Off");
471 let state_ref = match d.get(as_name) {
472 Some(r) => r,
473 None if subtype == b"Widget" => {
474 return Ok(());
476 }
477 None => {
478 match d.entries().first().map(|(_, v)| v) {
480 Some(r) => r,
481 None => return Ok(()),
482 }
483 }
484 };
485 let state_obj = self.resolver.deref(state_ref)?;
486 let state_dict = state_obj
487 .as_dict()
488 .ok_or(PdfError::Other("AP/N state not a stream".into()))?;
489 (state_ref.clone(), state_dict.clone())
490 }
491 } else {
492 return Err(PdfError::Other("AP/N not a dict or stream".into()));
493 };
494
495 let bbox = form_dict
499 .get(b"BBox")
500 .and_then(|obj| {
501 let resolved = self.resolver.deref(obj).ok().unwrap_or(obj.clone());
502 let a = resolved.as_array()?;
503 if a.len() >= 4 {
504 Some([
505 a[0].as_f64()?,
506 a[1].as_f64()?,
507 a[2].as_f64()?,
508 a[3].as_f64()?,
509 ])
510 } else {
511 None
512 }
513 })
514 .unwrap_or([rect[0], rect[1], rect[2], rect[3]]);
515
516 let form_matrix = deref_num_array(self.resolver, &form_dict, b"Matrix")
518 .and_then(|v| {
519 if v.len() == 6 {
520 Some(Matrix::new(v[0], v[1], v[2], v[3], v[4], v[5]))
521 } else {
522 None
523 }
524 })
525 .unwrap_or_else(Matrix::identity);
526
527 let (tb0x, tb0y) = form_matrix.transform_point(bbox[0], bbox[1]);
532 let (tb1x, tb1y) = form_matrix.transform_point(bbox[2], bbox[3]);
533 let tbbox_w = (tb1x - tb0x).abs().max(0.001);
534 let tbbox_h = (tb1y - tb0y).abs().max(0.001);
535 let rect_w = (rect[2] - rect[0]).abs();
536 let rect_h = (rect[3] - rect[1]).abs();
537 let sx = rect_w / tbbox_w;
538 let sy = rect_h / tbbox_h;
539 let tx = rect[0] - tb0x.min(tb1x) * sx;
540 let ty = rect[1] - tb0y.min(tb1y) * sy;
541 let bbox_to_rect = Matrix::new(sx, 0.0, 0.0, sy, tx, ty);
542
543 let saved_gstate = self.gstate.clone();
545 let saved_stack_depth = self.gstate_stack.len();
546 let saved_resources = self.resources.clone();
547 let saved_mc_stack = std::mem::take(&mut self.mc_stack);
548 if let Some(res_obj) = form_dict.get(b"Resources")
550 && let Ok(PdfObj::Dict(d)) = self.resolver.deref(res_obj)
551 {
552 self.resources = d;
553 }
554
555 self.gstate.ctm = self.initial_ctm.concat(&bbox_to_rect).concat(&form_matrix);
560
561 let saved_content_stream_ctm = self.content_stream_ctm;
564 self.content_stream_ctm = self.gstate.ctm;
565
566 let form_data = self.resolver.stream_data_from_obj(&n_ref)?;
570 self.depth += 1;
571 let _ = self.interpret_stream(&form_data);
572 self.depth -= 1;
573
574 self.gstate_stack.truncate(saved_stack_depth);
576 self.content_stream_ctm = saved_content_stream_ctm;
577 self.resources = saved_resources;
578 self.mc_stack = saved_mc_stack;
579 self.gstate = saved_gstate;
580
581 self.display_list.push(DisplayElement::InitClip);
583 if let Some(ref clip) = self.gstate.clip_path {
584 self.display_list.push(DisplayElement::Clip {
585 path: clip.clone(),
586 params: ClipParams {
587 fill_rule: FillRule::NonZeroWinding,
588 ctm: Matrix::identity(),
589 stroke_params: None,
590 },
591 });
592 }
593
594 Ok(())
595 }
596
597 fn synthesize_annotation(
600 &mut self,
601 dict: &crate::objects::PdfDict,
602 rect: &[f64; 4],
603 ) -> Result<(), PdfError> {
604 let subtype = dict.get_name(b"Subtype").unwrap_or(b"");
605
606 let color = if let Some(c) = dict.get_array(b"C") {
608 let vals: Vec<f64> = c.iter().filter_map(|o| o.as_f64()).collect();
609 match vals.len() {
610 1 => DeviceColor::from_gray(vals[0]),
611 3 => DeviceColor::from_rgb(vals[0], vals[1], vals[2]),
612 4 => DeviceColor::from_cmyk(vals[0], vals[1], vals[2], vals[3]),
613 _ => DeviceColor::from_gray(0.0),
614 }
615 } else {
616 DeviceColor::from_gray(0.0)
617 };
618
619 let alpha = dict.get(b"CA").and_then(|o| o.as_f64()).unwrap_or(1.0);
621
622 let border_width = dict
624 .get(b"BS")
625 .and_then(|bs| self.resolver.deref(bs).ok())
626 .and_then(|bs| bs.as_dict().and_then(|d| d.get_f64(b"W")))
627 .or_else(|| {
628 dict.get_array(b"Border")
629 .and_then(|arr| arr.get(2).and_then(|o| o.as_f64()))
630 })
631 .unwrap_or(1.0);
632
633 let dash = dict
635 .get(b"BS")
636 .and_then(|bs| self.resolver.deref(bs).ok())
637 .and_then(|bs| {
638 let d = bs.as_dict()?;
639 let style = d.get_name(b"S")?;
640 if style == b"D" {
641 let arr = d
642 .get_array(b"D")
643 .map(|a| a.iter().filter_map(|o| o.as_f64()).collect::<Vec<_>>())
644 .unwrap_or_else(|| vec![3.0]);
645 Some(DashPattern {
646 array: arr,
647 offset: 0.0,
648 })
649 } else {
650 None
651 }
652 })
653 .unwrap_or_default();
654
655 let ctm = self.initial_ctm;
656
657 match subtype {
658 b"Line" => {
659 if let Some(l) = dict.get_array(b"L") {
661 let coords: Vec<f64> = l.iter().filter_map(|o| o.as_f64()).collect();
662 if coords.len() >= 4 {
663 let (x1, y1, x2, y2) = (coords[0], coords[1], coords[2], coords[3]);
664 let path = PsPath {
665 segments: vec![
666 PathSegment::MoveTo(x1, y1),
667 PathSegment::LineTo(x2, y2),
668 ],
669 };
670 self.display_list.push(DisplayElement::Stroke {
671 path,
672 params: StrokeParams {
673 color: color.clone(),
674 line_width: border_width,
675 line_cap: LineCap::Butt,
676 line_join: LineJoin::Miter,
677 miter_limit: 10.0,
678 dash_pattern: dash.clone(),
679 ctm,
680 stroke_adjust: false,
681 is_text_glyph: false,
682 overprint: false,
683 overprint_mode: 0,
684 opm_paired: false,
685 painted_channels: 0,
686 is_device_cmyk: false,
687 spot_color: None,
688 icc_color: None,
689 rendering_intent: 0,
690 transfer: Default::default(),
691 halftone: Default::default(),
692 bg_ucr: Default::default(),
693 alpha,
694 blend_mode: 0,
695 alpha_is_shape: false,
696 },
697 });
698 }
699 }
700 }
701 b"PolyLine" | b"Polygon" => {
702 if let Some(verts) = dict.get_array(b"Vertices") {
703 let coords: Vec<f64> = verts.iter().filter_map(|o| o.as_f64()).collect();
704 if coords.len() >= 4 {
705 let mut segs = vec![PathSegment::MoveTo(coords[0], coords[1])];
706 for pair in coords[2..].chunks_exact(2) {
707 segs.push(PathSegment::LineTo(pair[0], pair[1]));
708 }
709 if subtype == b"Polygon" {
710 segs.push(PathSegment::ClosePath);
711 }
712 let path = PsPath { segments: segs };
713 self.display_list.push(DisplayElement::Stroke {
714 path,
715 params: StrokeParams {
716 color: color.clone(),
717 line_width: border_width,
718 line_cap: LineCap::Butt,
719 line_join: LineJoin::Miter,
720 miter_limit: 10.0,
721 dash_pattern: dash.clone(),
722 ctm,
723 stroke_adjust: false,
724 is_text_glyph: false,
725 overprint: false,
726 overprint_mode: 0,
727 opm_paired: false,
728 painted_channels: 0,
729 is_device_cmyk: false,
730 spot_color: None,
731 icc_color: None,
732 rendering_intent: 0,
733 transfer: Default::default(),
734 halftone: Default::default(),
735 bg_ucr: Default::default(),
736 alpha,
737 blend_mode: 0,
738 alpha_is_shape: false,
739 },
740 });
741 }
742 }
743 }
744 b"Ink" => {
745 if let Some(ink_list) = dict.get_array(b"InkList") {
746 for stroke_obj in ink_list {
747 let stroke_arr = match stroke_obj {
748 crate::objects::PdfObj::Array(a) => a,
749 _ => continue,
750 };
751 let coords: Vec<f64> =
752 stroke_arr.iter().filter_map(|o| o.as_f64()).collect();
753 if coords.len() >= 4 {
754 let mut segs = vec![PathSegment::MoveTo(coords[0], coords[1])];
755 for pair in coords[2..].chunks_exact(2) {
756 segs.push(PathSegment::LineTo(pair[0], pair[1]));
757 }
758 let path = PsPath { segments: segs };
759 self.display_list.push(DisplayElement::Stroke {
760 path,
761 params: StrokeParams {
762 color: color.clone(),
763 line_width: border_width,
764 line_cap: LineCap::Round,
765 line_join: LineJoin::Round,
766 miter_limit: 10.0,
767 dash_pattern: DashPattern::default(),
768 ctm,
769 stroke_adjust: false,
770 is_text_glyph: false,
771 overprint: false,
772 overprint_mode: 0,
773 opm_paired: false,
774 painted_channels: 0,
775 is_device_cmyk: false,
776 spot_color: None,
777 icc_color: None,
778 rendering_intent: 0,
779 transfer: Default::default(),
780 halftone: Default::default(),
781 bg_ucr: Default::default(),
782 alpha,
783 blend_mode: 0,
784 alpha_is_shape: false,
785 },
786 });
787 }
788 }
789 }
790 }
791 b"Highlight" | b"StrikeOut" | b"Underline" | b"Squiggly" => {
792 if let Some(qp) = dict.get_array(b"QuadPoints") {
793 let pts: Vec<f64> = qp.iter().filter_map(|o| o.as_f64()).collect();
794 for quad in pts.chunks_exact(8) {
797 let (x1, y1) = (quad[0], quad[1]); let (x2, y2) = (quad[2], quad[3]); let (x3, y3) = (quad[4], quad[5]); let (x4, y4) = (quad[6], quad[7]); if subtype == b"Highlight" {
803 let path = PsPath {
805 segments: vec![
806 PathSegment::MoveTo(x1, y1),
807 PathSegment::LineTo(x2, y2),
808 PathSegment::LineTo(x4, y4),
809 PathSegment::LineTo(x3, y3),
810 PathSegment::ClosePath,
811 ],
812 };
813 self.display_list.push(DisplayElement::Fill {
814 path,
815 params: FillParams {
816 color: color.clone(),
817 fill_rule: FillRule::NonZeroWinding,
818 ctm,
819 is_text_glyph: false,
820 overprint: false,
821 overprint_mode: 0,
822 opm_paired: false,
823 painted_channels: 0,
824 is_device_cmyk: false,
825 spot_color: None,
826 icc_color: None,
827 rendering_intent: 0,
828 transfer: Default::default(),
829 halftone: Default::default(),
830 bg_ucr: Default::default(),
831 alpha,
832 blend_mode: 3, alpha_is_shape: false,
834 },
835 });
836 } else {
837 let (lx1, ly1, lx2, ly2) = if subtype == b"StrikeOut" {
839 (
841 (x1 + x3) / 2.0,
842 (y1 + y3) / 2.0,
843 (x2 + x4) / 2.0,
844 (y2 + y4) / 2.0,
845 )
846 } else {
847 (x3, y3, x4, y4)
849 };
850 let path = PsPath {
851 segments: vec![
852 PathSegment::MoveTo(lx1, ly1),
853 PathSegment::LineTo(lx2, ly2),
854 ],
855 };
856 self.display_list.push(DisplayElement::Stroke {
857 path,
858 params: StrokeParams {
859 color: color.clone(),
860 line_width: border_width,
861 line_cap: LineCap::Butt,
862 line_join: LineJoin::Miter,
863 miter_limit: 10.0,
864 dash_pattern: DashPattern::default(),
865 ctm,
866 stroke_adjust: false,
867 is_text_glyph: false,
868 overprint: false,
869 overprint_mode: 0,
870 opm_paired: false,
871 painted_channels: 0,
872 is_device_cmyk: false,
873 spot_color: None,
874 icc_color: None,
875 rendering_intent: 0,
876 transfer: Default::default(),
877 halftone: Default::default(),
878 bg_ucr: Default::default(),
879 alpha,
880 blend_mode: 0,
881 alpha_is_shape: false,
882 },
883 });
884 }
885 }
886 }
887 }
888 b"Square" => {
889 let has_ic = dict.get_array(b"IC").is_some();
891 if border_width < 0.001 && !has_ic {
892 return Ok(());
893 }
894 let path = PsPath {
895 segments: vec![
896 PathSegment::MoveTo(rect[0], rect[1]),
897 PathSegment::LineTo(rect[2], rect[1]),
898 PathSegment::LineTo(rect[2], rect[3]),
899 PathSegment::LineTo(rect[0], rect[3]),
900 PathSegment::ClosePath,
901 ],
902 };
903 if let Some(ic) = dict.get_array(b"IC") {
905 let vals: Vec<f64> = ic.iter().filter_map(|o| o.as_f64()).collect();
906 let ic_color = match vals.len() {
907 1 => DeviceColor::from_gray(vals[0]),
908 3 => DeviceColor::from_rgb(vals[0], vals[1], vals[2]),
909 4 => DeviceColor::from_cmyk(vals[0], vals[1], vals[2], vals[3]),
910 _ => DeviceColor::from_gray(1.0),
911 };
912 self.display_list.push(DisplayElement::Fill {
913 path: path.clone(),
914 params: FillParams {
915 color: ic_color,
916 fill_rule: FillRule::NonZeroWinding,
917 ctm,
918 is_text_glyph: false,
919 overprint: false,
920 overprint_mode: 0,
921 opm_paired: false,
922 painted_channels: 0,
923 is_device_cmyk: false,
924 spot_color: None,
925 icc_color: None,
926 rendering_intent: 0,
927 transfer: Default::default(),
928 halftone: Default::default(),
929 bg_ucr: Default::default(),
930 alpha,
931 blend_mode: 0,
932 alpha_is_shape: false,
933 },
934 });
935 }
936 if border_width < 0.001 {
937 return Ok(());
938 }
939 self.display_list.push(DisplayElement::Stroke {
940 path,
941 params: StrokeParams {
942 color,
943 line_width: border_width,
944 line_cap: LineCap::Butt,
945 line_join: LineJoin::Miter,
946 miter_limit: 10.0,
947 dash_pattern: dash,
948 ctm,
949 stroke_adjust: false,
950 is_text_glyph: false,
951 overprint: false,
952 overprint_mode: 0,
953 opm_paired: false,
954 painted_channels: 0,
955 is_device_cmyk: false,
956 spot_color: None,
957 icc_color: None,
958 rendering_intent: 0,
959 transfer: Default::default(),
960 halftone: Default::default(),
961 bg_ucr: Default::default(),
962 alpha,
963 blend_mode: 0,
964 alpha_is_shape: false,
965 },
966 });
967 }
968 b"Circle" => {
969 let has_ic = dict.get_array(b"IC").is_some();
970 if border_width < 0.001 && !has_ic {
971 return Ok(());
972 }
973 let cx = (rect[0] + rect[2]) / 2.0;
975 let cy = (rect[1] + rect[3]) / 2.0;
976 let rx = (rect[2] - rect[0]) / 2.0;
977 let ry = (rect[3] - rect[1]) / 2.0;
978 let k = 0.5522847498; let path = PsPath {
980 segments: vec![
981 PathSegment::MoveTo(cx + rx, cy),
982 PathSegment::CurveTo {
983 x1: cx + rx,
984 y1: cy + ry * k,
985 x2: cx + rx * k,
986 y2: cy + ry,
987 x3: cx,
988 y3: cy + ry,
989 },
990 PathSegment::CurveTo {
991 x1: cx - rx * k,
992 y1: cy + ry,
993 x2: cx - rx,
994 y2: cy + ry * k,
995 x3: cx - rx,
996 y3: cy,
997 },
998 PathSegment::CurveTo {
999 x1: cx - rx,
1000 y1: cy - ry * k,
1001 x2: cx - rx * k,
1002 y2: cy - ry,
1003 x3: cx,
1004 y3: cy - ry,
1005 },
1006 PathSegment::CurveTo {
1007 x1: cx + rx * k,
1008 y1: cy - ry,
1009 x2: cx + rx,
1010 y2: cy - ry * k,
1011 x3: cx + rx,
1012 y3: cy,
1013 },
1014 PathSegment::ClosePath,
1015 ],
1016 };
1017 if let Some(ic) = dict.get_array(b"IC") {
1018 let vals: Vec<f64> = ic.iter().filter_map(|o| o.as_f64()).collect();
1019 let ic_color = match vals.len() {
1020 1 => DeviceColor::from_gray(vals[0]),
1021 3 => DeviceColor::from_rgb(vals[0], vals[1], vals[2]),
1022 4 => DeviceColor::from_cmyk(vals[0], vals[1], vals[2], vals[3]),
1023 _ => DeviceColor::from_gray(1.0),
1024 };
1025 self.display_list.push(DisplayElement::Fill {
1026 path: path.clone(),
1027 params: FillParams {
1028 color: ic_color,
1029 fill_rule: FillRule::NonZeroWinding,
1030 ctm,
1031 is_text_glyph: false,
1032 overprint: false,
1033 overprint_mode: 0,
1034 opm_paired: false,
1035 painted_channels: 0,
1036 is_device_cmyk: false,
1037 spot_color: None,
1038 icc_color: None,
1039 rendering_intent: 0,
1040 transfer: Default::default(),
1041 halftone: Default::default(),
1042 bg_ucr: Default::default(),
1043 alpha,
1044 blend_mode: 0,
1045 alpha_is_shape: false,
1046 },
1047 });
1048 }
1049 if border_width < 0.001 {
1050 return Ok(());
1051 }
1052 self.display_list.push(DisplayElement::Stroke {
1053 path,
1054 params: StrokeParams {
1055 color,
1056 line_width: border_width,
1057 line_cap: LineCap::Butt,
1058 line_join: LineJoin::Miter,
1059 miter_limit: 10.0,
1060 dash_pattern: dash,
1061 ctm,
1062 stroke_adjust: false,
1063 is_text_glyph: false,
1064 overprint: false,
1065 overprint_mode: 0,
1066 opm_paired: false,
1067 painted_channels: 0,
1068 is_device_cmyk: false,
1069 spot_color: None,
1070 icc_color: None,
1071 rendering_intent: 0,
1072 transfer: Default::default(),
1073 halftone: Default::default(),
1074 bg_ucr: Default::default(),
1075 alpha,
1076 blend_mode: 0,
1077 alpha_is_shape: false,
1078 },
1079 });
1080 }
1081 _ => {
1082 }
1084 }
1085
1086 Ok(())
1087 }
1088
1089 fn interpret_stream(&mut self, data: &[u8]) -> Result<(), PdfError> {
1091 let saved_operand_stack = std::mem::take(&mut self.operand_stack);
1100 let result = self.interpret_stream_inner(data);
1101 self.operand_stack = saved_operand_stack;
1102 result
1103 }
1104
1105 fn interpret_stream_inner(&mut self, data: &[u8]) -> Result<(), PdfError> {
1106 let mut lexer = Lexer::new(data);
1107 let mut prev_token_was_glued_number = false;
1113 loop {
1114 let pos_before = lexer.pos();
1119 let glued_to_prev_number = prev_token_was_glued_number
1120 && pos_before < data.len()
1121 && !is_whitespace_byte(data[pos_before]);
1122 let tok = match lexer.next_token() {
1123 Ok(t) => t,
1124 Err(_) => {
1125 prev_token_was_glued_number = false;
1126 continue;
1127 }
1128 };
1129 prev_token_was_glued_number = false;
1132 match tok {
1133 Token::Eof => break,
1134 Token::Int(n) => {
1135 self.operand_stack.push(Operand::Int(n));
1136 let p = lexer.pos();
1137 prev_token_was_glued_number = p < data.len() && !is_whitespace_byte(data[p]);
1138 }
1139 Token::Real(f) => {
1140 self.operand_stack.push(Operand::Real(f));
1141 let p = lexer.pos();
1142 prev_token_was_glued_number = p < data.len() && !is_whitespace_byte(data[p]);
1143 }
1144 Token::Name(n) => self.operand_stack.push(Operand::Name(n)),
1145 Token::LitString(s) | Token::HexString(s) => {
1146 self.operand_stack.push(Operand::Str(s));
1147 }
1148 Token::Bool(b) => self.operand_stack.push(Operand::Bool(b)),
1149 Token::ArrayBegin => {
1150 let arr = Self::parse_inline_array(&mut lexer)?;
1151 self.operand_stack.push(Operand::Array(arr));
1152 }
1153 Token::DictBegin => {
1154 let dict = crate::lexer::parse_dict_body(&mut lexer)?;
1155 self.operand_stack.push(Operand::Dict(dict));
1156 }
1157 Token::Keyword(kw) => {
1158 let op = if matches!(kw.as_slice(), b"f" | b"B" | b"b" | b"W" | b"T") {
1162 let p = lexer.pos();
1163 if p < data.len() && data[p] == b'*' {
1164 lexer.set_pos(p + 1);
1165 let mut combined = kw;
1166 combined.push(b'*');
1167 combined
1168 } else {
1169 kw
1170 }
1171 } else if kw == b"d" {
1172 let p = lexer.pos();
1173 if p < data.len() && (data[p] == b'0' || data[p] == b'1') {
1174 lexer.set_pos(p + 1);
1175 let mut combined = kw;
1176 combined.push(data[p]);
1177 combined
1178 } else {
1179 kw
1180 }
1181 } else {
1182 kw
1183 };
1184
1185 if op == b"BI" {
1186 self.handle_inline_image(&mut lexer)?;
1187 } else if let Err(_e) = self.dispatch_operator(&op, glued_to_prev_number) {
1188 }
1189 self.operand_stack.clear();
1190 }
1191 Token::DictEnd | Token::ArrayEnd => {
1192 }
1194 }
1195 }
1196 Ok(())
1197 }
1198
1199 fn parse_inline_array(lexer: &mut Lexer) -> Result<Vec<PdfObj>, PdfError> {
1201 let mut elems = Vec::new();
1202 loop {
1203 let tok = lexer.next_token()?;
1204 match tok {
1205 Token::ArrayEnd | Token::Eof => break,
1206 Token::Int(n) => elems.push(PdfObj::Int(n)),
1207 Token::Real(f) => elems.push(PdfObj::Real(f)),
1208 Token::Name(n) => elems.push(PdfObj::Name(n)),
1209 Token::LitString(s) | Token::HexString(s) => elems.push(PdfObj::Str(s)),
1210 Token::Bool(b) => elems.push(PdfObj::Bool(b)),
1211 Token::ArrayBegin => {
1212 let sub = Self::parse_inline_array(lexer)?;
1213 elems.push(PdfObj::Array(sub));
1214 }
1215 Token::DictBegin => {
1216 let d = crate::lexer::parse_dict_body(lexer).unwrap_or_default();
1217 elems.push(PdfObj::Dict(d));
1218 }
1219 Token::Keyword(ref kw) if kw == b"null" => {
1220 elems.push(PdfObj::Null);
1221 }
1222 _ => {}
1223 }
1224 }
1225 Ok(elems)
1226 }
1227
1228 fn dispatch_operator(&mut self, op: &[u8], glued_to_prev_number: bool) -> Result<(), PdfError> {
1238 let expected_args: i32 = match op {
1244 b"m" | b"l" => 2,
1245 b"v" | b"y" | b"re" => 4,
1246 b"c" => 6,
1247 b"h" | b"S" | b"s" | b"f" | b"F" | b"f*" | b"B" | b"B*" | b"b" | b"b*" | b"n" => 0,
1248 _ => -1, };
1250 if expected_args >= 0
1251 && self.operand_stack.len() > expected_args as usize
1252 && !glued_to_prev_number
1253 {
1254 return Ok(());
1255 }
1256
1257 if self.bt_culled {
1259 if op == b"ET" {
1260 self.bt_culled = false;
1261 self.in_text = false;
1262 }
1263 self.operand_stack.clear();
1264 return Ok(());
1265 }
1266
1267 match op {
1268 b"q" => self.op_q(),
1270 b"Q" => self.op_big_q(),
1271 b"cm" => self.op_cm(),
1272 b"w" => self.op_w(),
1273 b"J" => self.op_big_j(),
1274 b"j" => self.op_j(),
1275 b"M" => self.op_big_m(),
1276 b"d" => self.op_d(),
1277 b"ri" => self.op_ri(),
1278 b"i" => self.op_i(),
1279 b"gs" => self.op_gs(),
1280
1281 b"m" => self.op_m(),
1283 b"l" => self.op_l(),
1284 b"c" => self.op_c(),
1285 b"v" => self.op_v(),
1286 b"y" => self.op_y(),
1287 b"h" => self.op_h(),
1288 b"re" => self.op_re(),
1289
1290 b"S" => self.op_big_s(),
1292 b"s" => self.op_small_s(),
1293 b"f" | b"F" => self.op_f(),
1294 b"f*" => self.op_f_star(),
1295 b"B" => self.op_big_b(),
1296 b"B*" => self.op_big_b_star(),
1297 b"b" => self.op_small_b(),
1298 b"b*" => self.op_small_b_star(),
1299 b"n" => self.op_n(),
1300
1301 b"W" => self.op_big_w(),
1303 b"W*" => self.op_big_w_star(),
1304
1305 b"G" if !self.d1_color_suppressed => self.op_big_g(),
1307 b"g" if !self.d1_color_suppressed => self.op_small_g(),
1308 b"RG" if !self.d1_color_suppressed => self.op_big_rg(),
1309 b"rg" if !self.d1_color_suppressed => self.op_small_rg(),
1310 b"K" if !self.d1_color_suppressed => self.op_big_k(),
1311 b"k" if !self.d1_color_suppressed => self.op_small_k(),
1312 b"G" | b"g" | b"RG" | b"rg" | b"K" | b"k" => Ok(()),
1313
1314 b"CS" if !self.d1_color_suppressed => self.op_big_cs(),
1316 b"cs" if !self.d1_color_suppressed => self.op_small_cs(),
1317 b"SC" | b"SCN" if !self.d1_color_suppressed => self.op_sc_stroke(),
1318 b"sc" | b"scn" if !self.d1_color_suppressed => self.op_sc_fill(),
1319 b"CS" | b"cs" | b"SC" | b"SCN" | b"sc" | b"scn" => Ok(()),
1320
1321 b"BT" => {
1323 self.in_text = true;
1324 self.gstate.text_matrix = Matrix::identity();
1325 self.gstate.text_line_matrix = Matrix::identity();
1326 Ok(())
1327 }
1328 b"ET" => {
1329 self.in_text = false;
1330 if let Some(clip_path) = self.text_clip_path.take()
1332 && !clip_path.is_empty()
1333 {
1334 self.display_list.push(DisplayElement::Clip {
1335 path: clip_path.clone(),
1336 params: ClipParams {
1337 fill_rule: FillRule::NonZeroWinding,
1338 ctm: Matrix::identity(),
1339 stroke_params: None,
1340 },
1341 });
1342 self.gstate
1344 .clip_stack
1345 .push((clip_path.clone(), FillRule::NonZeroWinding));
1346 self.gstate.clip_path = Some(clip_path);
1347 self.gstate.clip_path_version += 1;
1348 }
1349 Ok(())
1350 }
1351 b"Tf" => self.op_tf(),
1352 b"Tc" => {
1353 self.gstate.char_spacing = self.pop_number()?;
1354 Ok(())
1355 }
1356 b"Tw" => {
1357 self.gstate.word_spacing = self.pop_number()?;
1358 Ok(())
1359 }
1360 b"TL" => {
1361 self.gstate.text_leading = self.pop_number()?;
1362 Ok(())
1363 }
1364 b"Tr" => {
1365 self.gstate.text_rendering_mode = self.pop_number()? as i32;
1366 Ok(())
1367 }
1368 b"Ts" => {
1369 self.gstate.text_rise = self.pop_number()?;
1370 Ok(())
1371 }
1372 b"Tz" => {
1373 self.gstate.horizontal_scaling = self.pop_number()? / 100.0;
1374 Ok(())
1375 }
1376 b"Td" => self.op_td(),
1377 b"TD" => self.op_big_td(),
1378 b"Tm" => self.op_tm(),
1379 b"T*" => self.op_t_star(),
1380 b"Tj" => self.op_tj(),
1381 b"TJ" => self.op_big_tj(),
1382 b"'" => self.op_quote(),
1383 b"\"" => self.op_dblquote(),
1384
1385 b"Do" => self.op_do(),
1387
1388 b"sh" => self.op_sh(),
1390
1391 b"BMC" => {
1395 self.operand_stack.pop();
1396 self.mc_stack.push(MarkedContentFrame::Other);
1397 Ok(())
1398 }
1399 b"MP" => {
1400 self.operand_stack.pop();
1401 Ok(())
1402 }
1403 b"DP" => {
1404 self.operand_stack.pop();
1405 self.operand_stack.pop();
1406 Ok(())
1407 }
1408 b"BDC" => self.op_bdc(),
1409 b"EMC" => {
1410 if let Some(MarkedContentFrame::Ocg {
1411 parent_list,
1412 visibility,
1413 }) = self.mc_stack.pop()
1414 {
1415 let ocg_list = std::mem::replace(&mut self.display_list, parent_list);
1416 self.display_list.push(DisplayElement::OcgGroup {
1417 elements: ocg_list,
1418 visibility,
1419 });
1420 }
1421 Ok(())
1422 }
1423
1424 b"d0" => Ok(()),
1426 b"d1" => {
1427 self.d1_color_suppressed = true;
1438 self.gstate.stroke_color = self.gstate.fill_color.clone();
1439 self.gstate.stroke_color_space = self.gstate.fill_color_space.clone();
1440 self.gstate.stroke_pattern = None;
1441 self.gstate.stroke_shading_pattern = None;
1442 self.gstate.stroke_painted_channels = self.gstate.fill_painted_channels;
1443 self.gstate.stroke_is_device_cmyk = self.gstate.fill_is_device_cmyk;
1444 self.gstate.stroke_is_none = self.gstate.fill_is_none;
1445 Ok(())
1446 }
1447
1448 b"BX" | b"EX" => Ok(()),
1450
1451 _ => {
1452 Ok(())
1454 }
1455 }
1456 }
1457
1458 fn pop_number(&self) -> Result<f64, PdfError> {
1462 self.operand_stack
1463 .last()
1464 .and_then(|o| o.as_f64())
1465 .ok_or(PdfError::Other("expected number on operand stack".into()))
1466 }
1467
1468 fn get_numbers(&self, n: usize) -> Result<Vec<f64>, PdfError> {
1470 let len = self.operand_stack.len();
1471 if len < n {
1472 return Err(PdfError::Other(format!("need {n} operands, have {len}")));
1473 }
1474 let mut nums = Vec::with_capacity(n);
1475 for i in (len - n)..len {
1476 nums.push(
1477 self.operand_stack[i]
1478 .as_f64()
1479 .ok_or(PdfError::Other("expected number".into()))?,
1480 );
1481 }
1482 Ok(nums)
1483 }
1484
1485 fn transform(&self, x: f64, y: f64) -> (f64, f64) {
1487 self.gstate.ctm.transform_point(x, y)
1488 }
1489
1490 fn take_path(&mut self) -> PsPath {
1492 let path = std::mem::take(&mut self.current_path);
1493 self.current_point = None;
1494 self.subpath_start = None;
1495 path
1496 }
1497
1498 fn apply_pending_clip(&mut self) {
1500 if let Some((path, fill_rule)) = self.gstate.pending_clip.take() {
1501 let has_drawing_segments = path.segments.iter().any(|s| {
1506 matches!(
1507 s,
1508 PathSegment::LineTo(..) | PathSegment::CurveTo { .. } | PathSegment::ClosePath
1509 )
1510 });
1511 let has_moveto = path
1514 .segments
1515 .iter()
1516 .any(|s| matches!(s, PathSegment::MoveTo(..)));
1517 if !has_drawing_segments && has_moveto {
1518 let mut empty = PsPath::new();
1520 empty.segments.push(PathSegment::MoveTo(0.0, 0.0));
1521 empty.segments.push(PathSegment::LineTo(0.0, 0.0));
1522 empty.segments.push(PathSegment::ClosePath);
1523 self.display_list.push(DisplayElement::Clip {
1524 path: empty.clone(),
1525 params: ClipParams {
1526 fill_rule,
1527 ctm: Matrix::identity(),
1528 stroke_params: None,
1529 },
1530 });
1531 self.gstate.clip_stack.push((empty.clone(), fill_rule));
1532 self.gstate.clip_path = Some(empty);
1533 self.gstate.clip_path_version += 1;
1534 return;
1535 }
1536 if !has_drawing_segments {
1537 return;
1539 }
1540 let clip_path = path;
1541 self.display_list.push(DisplayElement::Clip {
1542 path: clip_path.clone(),
1543 params: ClipParams {
1544 fill_rule,
1545 ctm: Matrix::identity(),
1546 stroke_params: None,
1547 },
1548 });
1549 self.gstate.clip_stack.push((clip_path.clone(), fill_rule));
1551 self.gstate.clip_path = Some(clip_path);
1552 self.gstate.clip_path_version += 1;
1553 }
1554 }
1555
1556 fn op_q(&mut self) -> Result<(), PdfError> {
1559 self.gstate_stack.push(self.gstate.clone());
1560 Ok(())
1561 }
1562
1563 fn op_big_q(&mut self) -> Result<(), PdfError> {
1564 if let Some(saved) = self.gstate_stack.pop() {
1565 if self.soft_mask_scope.is_some() && self.gstate.smask_gen != saved.smask_gen {
1571 self.flush_soft_mask();
1572 self.nested_mask_flush_count += 1;
1573 }
1574
1575 let old_clip_version = self.gstate.clip_path_version;
1576 let old_font_name = std::mem::take(&mut self.gstate.text_font_name);
1577 self.gstate = saved;
1578 if self.gstate.clip_path_version != old_clip_version {
1581 self.restore_clip_from_stack();
1582 }
1583 if self.gstate.text_font_name != old_font_name && !self.gstate.text_font_name.is_empty()
1585 {
1586 let name = self.gstate.text_font_name.clone();
1587 self.resolve_current_font(&name);
1588 }
1589 }
1590 Ok(())
1591 }
1592
1593 fn restore_clip_from_stack(&mut self) {
1595 self.display_list.push(DisplayElement::InitClip);
1596 for (clip, fill_rule) in &self.gstate.clip_stack {
1597 self.display_list.push(DisplayElement::Clip {
1598 path: clip.clone(),
1599 params: ClipParams {
1600 fill_rule: *fill_rule,
1601 ctm: Matrix::identity(),
1602 stroke_params: None,
1603 },
1604 });
1605 }
1606 }
1607
1608 fn op_cm(&mut self) -> Result<(), PdfError> {
1609 let n = self.get_numbers(6)?;
1610 let m = Matrix::new(n[0], n[1], n[2], n[3], n[4], n[5]);
1611 self.gstate.ctm = self.gstate.ctm.concat(&m);
1613 Ok(())
1614 }
1615
1616 fn op_w(&mut self) -> Result<(), PdfError> {
1617 self.gstate.line_width = self.pop_number()?;
1618 Ok(())
1619 }
1620
1621 fn op_big_j(&mut self) -> Result<(), PdfError> {
1622 let cap = self.pop_number()? as i32;
1623 if let Some(lc) = LineCap::from_i32(cap) {
1624 self.gstate.line_cap = lc;
1625 }
1626 Ok(())
1627 }
1628
1629 fn op_j(&mut self) -> Result<(), PdfError> {
1630 let join = self.pop_number()? as i32;
1631 if let Some(lj) = LineJoin::from_i32(join) {
1632 self.gstate.line_join = lj;
1633 }
1634 Ok(())
1635 }
1636
1637 fn op_big_m(&mut self) -> Result<(), PdfError> {
1638 self.gstate.miter_limit = self.pop_number()?;
1639 Ok(())
1640 }
1641
1642 fn op_d(&mut self) -> Result<(), PdfError> {
1643 let len = self.operand_stack.len();
1645 if len < 2 {
1646 return Ok(());
1647 }
1648 let offset = self.operand_stack[len - 1].as_f64().unwrap_or(0.0);
1649 let array = match &self.operand_stack[len - 2] {
1650 Operand::Array(arr) => arr.iter().filter_map(|o| o.as_f64()).collect::<Vec<_>>(),
1651 _ => Vec::new(),
1652 };
1653 self.gstate.dash_pattern = DashPattern { array, offset };
1654 Ok(())
1655 }
1656
1657 fn op_ri(&mut self) -> Result<(), PdfError> {
1658 let Some(top) = self.operand_stack.pop() else {
1661 return Ok(());
1662 };
1663 let Some(name) = top.as_name() else {
1664 return Ok(());
1665 };
1666 self.gstate.rendering_intent = match name {
1667 b"Perceptual" => 0,
1668 b"RelativeColorimetric" => 1,
1669 b"Saturation" => 2,
1670 b"AbsoluteColorimetric" => 3,
1671 _ => 0,
1672 };
1673 Ok(())
1674 }
1675
1676 fn op_i(&mut self) -> Result<(), PdfError> {
1677 self.gstate.flatness = self.pop_number()?;
1678 Ok(())
1679 }
1680
1681 fn op_gs(&mut self) -> Result<(), PdfError> {
1682 let name = self
1683 .operand_stack
1684 .last()
1685 .and_then(|o| o.as_name())
1686 .ok_or(PdfError::Other("gs: expected name".into()))?
1687 .to_vec();
1688 self.apply_ext_gstate(&name)
1689 }
1690
1691 fn op_m(&mut self) -> Result<(), PdfError> {
1694 let n = self.get_numbers(2)?;
1695 let (dx, dy) = self.transform(n[0], n[1]);
1696 self.current_path.segments.push(PathSegment::MoveTo(dx, dy));
1697 self.current_point = Some((dx, dy));
1698 self.subpath_start = Some((dx, dy));
1699 Ok(())
1700 }
1701
1702 fn op_l(&mut self) -> Result<(), PdfError> {
1703 let n = self.get_numbers(2)?;
1704 let (dx, dy) = self.transform(n[0], n[1]);
1705 self.current_path.segments.push(PathSegment::LineTo(dx, dy));
1706 self.current_point = Some((dx, dy));
1707 Ok(())
1708 }
1709
1710 fn op_c(&mut self) -> Result<(), PdfError> {
1711 let n = self.get_numbers(6)?;
1712 let (x1, y1) = self.transform(n[0], n[1]);
1713 let (x2, y2) = self.transform(n[2], n[3]);
1714 let (x3, y3) = self.transform(n[4], n[5]);
1715 self.current_path.segments.push(PathSegment::CurveTo {
1716 x1,
1717 y1,
1718 x2,
1719 y2,
1720 x3,
1721 y3,
1722 });
1723 self.current_point = Some((x3, y3));
1724 Ok(())
1725 }
1726
1727 fn op_v(&mut self) -> Result<(), PdfError> {
1728 let n = self.get_numbers(4)?;
1729 let (x1, y1) = self.current_point.unwrap_or((0.0, 0.0));
1730 let (x2, y2) = self.transform(n[0], n[1]);
1731 let (x3, y3) = self.transform(n[2], n[3]);
1732 self.current_path.segments.push(PathSegment::CurveTo {
1733 x1,
1734 y1,
1735 x2,
1736 y2,
1737 x3,
1738 y3,
1739 });
1740 self.current_point = Some((x3, y3));
1741 Ok(())
1742 }
1743
1744 fn op_y(&mut self) -> Result<(), PdfError> {
1745 let n = self.get_numbers(4)?;
1746 let (x1, y1) = self.transform(n[0], n[1]);
1747 let (x3, y3) = self.transform(n[2], n[3]);
1748 self.current_path.segments.push(PathSegment::CurveTo {
1749 x1,
1750 y1,
1751 x2: x3,
1752 y2: y3,
1753 x3,
1754 y3,
1755 });
1756 self.current_point = Some((x3, y3));
1757 Ok(())
1758 }
1759
1760 fn op_h(&mut self) -> Result<(), PdfError> {
1761 self.current_path.segments.push(PathSegment::ClosePath);
1762 if let Some(start) = self.subpath_start {
1763 self.current_point = Some(start);
1764 }
1765 Ok(())
1766 }
1767
1768 fn op_re(&mut self) -> Result<(), PdfError> {
1769 let n = self.get_numbers(4)?;
1770 let (x, y, w, h) = (n[0], n[1], n[2], n[3]);
1771 let p0 = self.transform(x, y);
1773 let p1 = self.transform(x + w, y);
1774 let p2 = self.transform(x + w, y + h);
1775 let p3 = self.transform(x, y + h);
1776 self.current_path
1777 .segments
1778 .push(PathSegment::MoveTo(p0.0, p0.1));
1779 self.current_path
1780 .segments
1781 .push(PathSegment::LineTo(p1.0, p1.1));
1782 self.current_path
1783 .segments
1784 .push(PathSegment::LineTo(p2.0, p2.1));
1785 self.current_path
1786 .segments
1787 .push(PathSegment::LineTo(p3.0, p3.1));
1788 self.current_path.segments.push(PathSegment::ClosePath);
1789 self.current_point = Some(p0);
1790 self.subpath_start = Some(p0);
1791 Ok(())
1792 }
1793
1794 fn op_big_s(&mut self) -> Result<(), PdfError> {
1797 let path = self.take_path();
1799 if !path.is_empty() {
1800 self.emit_stroke(path);
1801 }
1802 self.apply_pending_clip();
1803 Ok(())
1804 }
1805
1806 fn op_small_s(&mut self) -> Result<(), PdfError> {
1807 self.op_h()?;
1809 self.op_big_s()
1810 }
1811
1812 fn op_f(&mut self) -> Result<(), PdfError> {
1813 let path = self.take_path();
1815 if !path.is_empty() {
1816 self.emit_fill(path, FillRule::NonZeroWinding);
1817 }
1818 self.apply_pending_clip();
1819 Ok(())
1820 }
1821
1822 fn op_f_star(&mut self) -> Result<(), PdfError> {
1823 let path = self.take_path();
1825 if !path.is_empty() {
1826 self.emit_fill(path, FillRule::EvenOdd);
1827 }
1828 self.apply_pending_clip();
1829 Ok(())
1830 }
1831
1832 fn op_big_b(&mut self) -> Result<(), PdfError> {
1833 let path = self.take_path();
1835 if !path.is_empty() {
1836 self.emit_fill_stroke(path, FillRule::NonZeroWinding);
1837 }
1838 self.apply_pending_clip();
1839 Ok(())
1840 }
1841
1842 fn op_big_b_star(&mut self) -> Result<(), PdfError> {
1843 let path = self.take_path();
1845 if !path.is_empty() {
1846 self.emit_fill_stroke(path, FillRule::EvenOdd);
1847 }
1848 self.apply_pending_clip();
1849 Ok(())
1850 }
1851
1852 fn op_small_b(&mut self) -> Result<(), PdfError> {
1853 self.op_h()?;
1855 self.op_big_b()
1856 }
1857
1858 fn op_small_b_star(&mut self) -> Result<(), PdfError> {
1859 self.op_h()?;
1861 self.op_big_b_star()
1862 }
1863
1864 fn emit_fill(&mut self, path: PsPath, fill_rule: FillRule) {
1866 if let Some(shading_box) = self.gstate.fill_shading_pattern.clone() {
1867 let bbox = path_device_bbox(&path);
1871 let mut group_dl = DisplayList::new();
1872 group_dl.push(DisplayElement::Clip {
1873 path,
1874 params: ClipParams {
1875 fill_rule,
1876 ctm: Matrix::identity(),
1877 stroke_params: None,
1878 },
1879 });
1880 for elem in shading_box.0.elements() {
1881 group_dl.push(elem.clone());
1882 }
1883 self.display_list.push(DisplayElement::Group {
1884 elements: group_dl,
1885 params: GroupParams {
1886 bbox,
1887 isolated: true,
1888 knockout: false,
1889 blend_mode: self.gstate.blend_mode,
1890 alpha: self.gstate.fill_alpha,
1891 color_space: stet_graphics::display_list::GroupColorSpace::Inherited,
1892 },
1893 });
1894 } else if let Some(pattern) = self.gstate.fill_pattern.clone() {
1895 self.display_list.push(DisplayElement::PatternFill {
1896 params: PatternFillParams {
1897 path,
1898 fill_rule,
1899 tile: pattern.tile,
1900 pattern_matrix: pattern.pattern_matrix,
1901 bbox: pattern.bbox,
1902 xstep: pattern.x_step,
1903 ystep: pattern.y_step,
1904 paint_type: pattern.paint_type,
1905 underlying_color: if pattern.paint_type == 2 {
1906 Some(self.gstate.fill_color.clone())
1907 } else {
1908 None
1909 },
1910 pattern_id: pattern.pattern_id,
1911 device_space_tile: false,
1912 flip_tile_y: false,
1913 stroke_params: None,
1914 overprint_mode: if self.gstate.overprint {
1915 self.gstate.overprint_mode
1916 } else {
1917 0
1918 },
1919 },
1920 });
1921 } else {
1922 self.display_list.push(DisplayElement::Fill {
1923 path,
1924 params: self.gstate.fill_params(fill_rule),
1925 });
1926 }
1927 }
1928
1929 fn emit_stroke(&mut self, path: PsPath) {
1937 let ctm = self.gstate.ctm;
1938 let user_path = if let Some(inv) = ctm.invert() {
1940 path.transform(&inv)
1941 } else {
1942 path.clone()
1943 };
1944
1945 if let Some(pattern) = self.gstate.stroke_pattern.clone() {
1948 let mut sp = self.gstate.stroke_params_with_ctm();
1949 sp.ctm = ctm;
1950 self.display_list.push(DisplayElement::PatternFill {
1951 params: PatternFillParams {
1952 path: user_path,
1953 fill_rule: FillRule::NonZeroWinding,
1954 tile: pattern.tile,
1955 pattern_matrix: pattern.pattern_matrix,
1956 bbox: pattern.bbox,
1957 xstep: pattern.x_step,
1958 ystep: pattern.y_step,
1959 paint_type: pattern.paint_type,
1960 underlying_color: if pattern.paint_type == 2 {
1961 Some(self.gstate.stroke_color.clone())
1962 } else {
1963 None
1964 },
1965 pattern_id: pattern.pattern_id,
1966 device_space_tile: false,
1967 flip_tile_y: false,
1968 stroke_params: Some(sp),
1969 overprint_mode: if self.gstate.overprint {
1970 self.gstate.overprint_mode
1971 } else {
1972 0
1973 },
1974 },
1975 });
1976 return;
1977 }
1978
1979 if let Some(shading_box) = self.gstate.stroke_shading_pattern.clone() {
1981 let mut sp = self.gstate.stroke_params_with_ctm();
1982 sp.ctm = ctm;
1983 let mut bbox = path_device_bbox(&path);
1986 let scale = self.gstate.ctm_scale_factor();
1987 let half_w = self.gstate.line_width * scale * 0.5;
1988 bbox[0] -= half_w;
1989 bbox[1] -= half_w;
1990 bbox[2] += half_w;
1991 bbox[3] += half_w;
1992 let mut group_dl = DisplayList::new();
1993 group_dl.push(DisplayElement::Clip {
1994 path: user_path,
1995 params: ClipParams {
1996 fill_rule: FillRule::NonZeroWinding,
1997 ctm: Matrix::identity(),
1998 stroke_params: Some(sp),
1999 },
2000 });
2001 for elem in shading_box.0.elements() {
2002 group_dl.push(elem.clone());
2003 }
2004 self.display_list.push(DisplayElement::Group {
2005 elements: group_dl,
2006 params: GroupParams {
2007 bbox,
2008 isolated: true,
2009 knockout: false,
2010 blend_mode: self.gstate.blend_mode,
2011 alpha: self.gstate.stroke_alpha,
2012 color_space: stet_graphics::display_list::GroupColorSpace::Inherited,
2013 },
2014 });
2015 return;
2016 }
2017
2018 let mut params = self.gstate.stroke_params_with_ctm();
2019 params.ctm = ctm;
2020 self.display_list.push(DisplayElement::Stroke {
2021 path: user_path,
2022 params,
2023 });
2024 }
2025
2026 fn emit_fill_stroke(&mut self, path: PsPath, fill_rule: FillRule) {
2034 let is_simple_fill =
2035 self.gstate.fill_shading_pattern.is_none() && self.gstate.fill_pattern.is_none();
2036 let is_simple_stroke =
2037 self.gstate.stroke_shading_pattern.is_none() && self.gstate.stroke_pattern.is_none();
2038
2039 let strict_opm1 = self.gstate.overprint_mode == 1 && self.gstate.opm_paired;
2052 let is_white_fill = self.gstate.fill_is_device_cmyk
2053 && self
2054 .gstate
2055 .fill_color
2056 .native_cmyk
2057 .map(|(c, m, y, k)| c == 0.0 && m == 0.0 && y == 0.0 && k == 0.0)
2058 .unwrap_or(false);
2059 let is_white_stroke = self.gstate.stroke_is_device_cmyk
2060 && self
2061 .gstate
2062 .stroke_color
2063 .native_cmyk
2064 .map(|(c, m, y, k)| c == 0.0 && m == 0.0 && y == 0.0 && k == 0.0)
2065 .unwrap_or(false);
2066 let has_any_overprint = self.gstate.overprint || self.gstate.overprint_stroke;
2067 let both_device_cmyk = self.gstate.fill_is_device_cmyk && self.gstate.stroke_is_device_cmyk;
2073 let fill_overprint_safe =
2074 !self.gstate.overprint || (is_white_fill && both_device_cmyk && !strict_opm1);
2075 let stroke_overprint_safe =
2076 !self.gstate.overprint_stroke || (is_white_stroke && both_device_cmyk && !strict_opm1);
2077 let mixed_space_with_overprint = has_any_overprint && !both_device_cmyk;
2082
2083 if is_simple_fill
2084 && is_simple_stroke
2085 && self.gstate.blend_mode == 0
2086 && fill_overprint_safe
2087 && stroke_overprint_safe
2088 && !mixed_space_with_overprint
2089 {
2090 let ctm = self.gstate.ctm;
2091
2092 let mut bbox = path_device_bbox(&path);
2093 let scale = self.gstate.ctm_scale_factor();
2094 let half_w = self.gstate.line_width * scale * 0.5;
2095 bbox[0] -= half_w;
2096 bbox[1] -= half_w;
2097 bbox[2] += half_w;
2098 bbox[3] += half_w;
2099
2100 let fill_elem = DisplayElement::Fill {
2101 path: path.clone(),
2102 params: self.gstate.fill_params(fill_rule),
2103 };
2104
2105 let user_path = if let Some(inv) = ctm.invert() {
2106 path.transform(&inv)
2107 } else {
2108 path.clone()
2109 };
2110 let mut stroke_params = self.gstate.stroke_params_with_ctm();
2111 stroke_params.ctm = ctm;
2112 let stroke_elem = DisplayElement::Stroke {
2113 path: user_path,
2114 params: stroke_params,
2115 };
2116
2117 let mut group_dl = DisplayList::new();
2118 group_dl.push(fill_elem);
2119 group_dl.push(stroke_elem);
2120
2121 self.display_list.push(DisplayElement::Group {
2122 elements: group_dl,
2123 params: stet_graphics::display_list::GroupParams {
2124 bbox,
2125 isolated: true,
2126 knockout: false,
2127 blend_mode: 0,
2128 alpha: 1.0,
2129 color_space: stet_graphics::display_list::GroupColorSpace::Inherited,
2130 },
2131 });
2132 } else {
2133 self.emit_fill(path.clone(), fill_rule);
2134 self.emit_stroke(path);
2135 }
2136 }
2137
2138 fn op_n(&mut self) -> Result<(), PdfError> {
2139 let _path = self.take_path();
2141 self.apply_pending_clip();
2142 Ok(())
2143 }
2144
2145 fn op_big_w(&mut self) -> Result<(), PdfError> {
2148 self.gstate.pending_clip = Some((self.current_path.clone(), FillRule::NonZeroWinding));
2150 Ok(())
2151 }
2152
2153 fn op_big_w_star(&mut self) -> Result<(), PdfError> {
2154 self.gstate.pending_clip = Some((self.current_path.clone(), FillRule::EvenOdd));
2156 Ok(())
2157 }
2158
2159 fn op_big_g(&mut self) -> Result<(), PdfError> {
2162 let g = self.pop_number()?;
2164 let (color, painted, is_cmyk) = self.gray_paint_for_gstate(g);
2165 self.gstate.stroke_color = color;
2166 self.gstate.stroke_color_space = ColorSpaceRef::DeviceGray;
2167 self.gstate.stroke_painted_channels = painted;
2168 self.gstate.stroke_is_device_cmyk = is_cmyk;
2169 self.gstate.stroke_is_none = false;
2170 self.gstate.stroke_spot_color = None;
2171 self.gstate.stroke_icc_color = None;
2172 self.gstate.stroke_pattern = None;
2173 self.gstate.stroke_shading_pattern = None;
2174 Ok(())
2175 }
2176
2177 fn op_small_g(&mut self) -> Result<(), PdfError> {
2178 let g = self.pop_number()?;
2180 let (color, painted, is_cmyk) = self.gray_paint_for_gstate(g);
2181 self.gstate.fill_color = color;
2182 self.gstate.fill_color_space = ColorSpaceRef::DeviceGray;
2183 self.gstate.fill_painted_channels = painted;
2184 self.gstate.fill_is_device_cmyk = is_cmyk;
2185 self.gstate.fill_is_none = false;
2186 self.gstate.fill_spot_color = None;
2187 self.gstate.fill_icc_color = None;
2188 self.gstate.fill_pattern = None;
2189 self.gstate.fill_shading_pattern = None;
2190 Ok(())
2191 }
2192
2193 fn gray_paint_for_gstate(&mut self, g: f64) -> (DeviceColor, u8, bool) {
2202 if self.pdfx_cmyk_intent && !self.in_smask_form {
2203 let k = (1.0 - g).clamp(0.0, 1.0);
2204 let color = DeviceColor::from_cmyk_icc(0.0, 0.0, 0.0, k, &mut self.icc_cache);
2205 (color, stet_graphics::device::CMYK_K, true)
2206 } else {
2207 (DeviceColor::from_gray(g), 0, false)
2208 }
2209 }
2210
2211 fn op_big_rg(&mut self) -> Result<(), PdfError> {
2212 let n = self.get_numbers(3)?;
2214 let (r, g, b) = self.cmyk_group_rgb(n[0], n[1], n[2]);
2215 self.gstate.stroke_color = DeviceColor::from_rgb(r, g, b);
2216 self.gstate.stroke_color_space = ColorSpaceRef::DeviceRGB;
2217 self.gstate.stroke_painted_channels = 0;
2218 self.gstate.stroke_is_device_cmyk = false;
2219 self.gstate.stroke_is_none = false;
2220 self.gstate.stroke_spot_color = None;
2221 self.gstate.stroke_icc_color = None;
2222 self.gstate.stroke_pattern = None;
2223 self.gstate.stroke_shading_pattern = None;
2224 Ok(())
2225 }
2226
2227 fn op_small_rg(&mut self) -> Result<(), PdfError> {
2228 let n = self.get_numbers(3)?;
2230 let (r, g, b) = self.cmyk_group_rgb(n[0], n[1], n[2]);
2231 self.gstate.fill_color = DeviceColor::from_rgb(r, g, b);
2232 self.gstate.fill_color_space = ColorSpaceRef::DeviceRGB;
2233 self.gstate.fill_painted_channels = 0;
2234 self.gstate.fill_is_device_cmyk = false;
2235 self.gstate.fill_is_none = false;
2236 self.gstate.fill_spot_color = None;
2237 self.gstate.fill_icc_color = None;
2238 self.gstate.fill_pattern = None;
2239 self.gstate.fill_shading_pattern = None;
2240 Ok(())
2241 }
2242
2243 fn cmyk_group_rgb(&mut self, r: f64, g: f64, b: f64) -> (f64, f64, f64) {
2246 if self.page_group_is_cmyk {
2247 if let Some(result) = self.icc_cache.round_trip_rgb_via_cmyk(r, g, b) {
2248 return result;
2249 }
2250 }
2251 (r, g, b)
2252 }
2253
2254 fn cmyk_group_promote_image(
2266 &self,
2267 cs: ImageColorSpace,
2268 data: Vec<u8>,
2269 width: u32,
2270 height: u32,
2271 ) -> (ImageColorSpace, Vec<u8>) {
2272 if !self.pdfx_cmyk_intent || self.in_smask_form {
2273 return (cs, data);
2274 }
2275 match cs {
2276 ImageColorSpace::DeviceGray => {
2277 let npx = (width as usize) * (height as usize);
2278 let take = npx.min(data.len());
2279 let mut new_data = vec![0u8; npx * 4];
2280 for i in 0..take {
2281 new_data[i * 4 + 3] = 255 - data[i];
2282 }
2283 (ImageColorSpace::DeviceCMYK, new_data)
2284 }
2285 ImageColorSpace::Separation {
2286 name,
2287 alt_space,
2288 tint_table,
2289 } => {
2290 if matches!(alt_space.as_ref(), ImageColorSpace::DeviceGray)
2291 && tint_table.num_outputs == 1
2292 {
2293 let samples = tint_table.samples_per_dim as usize;
2294 let mut new_data = Vec::with_capacity(samples * 4);
2295 for i in 0..samples {
2296 let g = tint_table.data[i] as f64;
2297 let k = (1.0 - g).clamp(0.0, 1.0) as f32;
2298 new_data.push(0.0);
2299 new_data.push(0.0);
2300 new_data.push(0.0);
2301 new_data.push(k);
2302 }
2303 let promoted_table = TintLookupTable {
2304 num_inputs: 1,
2305 num_outputs: 4,
2306 samples_per_dim: tint_table.samples_per_dim,
2307 data: new_data,
2308 };
2309 (
2310 ImageColorSpace::Separation {
2311 name,
2312 alt_space: Box::new(ImageColorSpace::DeviceCMYK),
2313 tint_table: Arc::new(promoted_table),
2314 },
2315 data,
2316 )
2317 } else {
2318 (
2319 ImageColorSpace::Separation {
2320 name,
2321 alt_space,
2322 tint_table,
2323 },
2324 data,
2325 )
2326 }
2327 }
2328 ImageColorSpace::DeviceN {
2329 names,
2330 alt_space,
2331 tint_table,
2332 } => {
2333 if matches!(alt_space.as_ref(), ImageColorSpace::DeviceGray)
2334 && tint_table.num_outputs == 1
2335 {
2336 let total = tint_table.data.len();
2337 let mut new_data = Vec::with_capacity(total * 4);
2338 for &g in &tint_table.data {
2339 let k = (1.0 - g as f64).clamp(0.0, 1.0) as f32;
2340 new_data.push(0.0);
2341 new_data.push(0.0);
2342 new_data.push(0.0);
2343 new_data.push(k);
2344 }
2345 let promoted_table = TintLookupTable {
2346 num_inputs: tint_table.num_inputs,
2347 num_outputs: 4,
2348 samples_per_dim: tint_table.samples_per_dim,
2349 data: new_data,
2350 };
2351 (
2352 ImageColorSpace::DeviceN {
2353 names,
2354 alt_space: Box::new(ImageColorSpace::DeviceCMYK),
2355 tint_table: Arc::new(promoted_table),
2356 },
2357 data,
2358 )
2359 } else {
2360 (
2361 ImageColorSpace::DeviceN {
2362 names,
2363 alt_space,
2364 tint_table,
2365 },
2366 data,
2367 )
2368 }
2369 }
2370 other => (other, data),
2371 }
2372 }
2373
2374 fn cmyk_group_promote_color(&mut self, color: &mut DeviceColor) {
2380 if !self.pdfx_cmyk_intent || self.in_smask_form {
2381 return;
2382 }
2383 let Some((c, m, y, k)) = color.native_cmyk else {
2384 return;
2385 };
2386 if !(c == 0.0 && m == 0.0 && y == 0.0) {
2391 return;
2392 }
2393 if k == 0.0 {
2401 return;
2402 }
2403 if (color.r - color.g).abs() > f64::EPSILON || (color.r - color.b).abs() > f64::EPSILON {
2404 return;
2405 }
2406 if let Some((r, g, b)) = self.icc_cache.convert_cmyk(0.0, 0.0, 0.0, k) {
2407 color.r = r;
2408 color.g = g;
2409 color.b = b;
2410 }
2411 }
2412
2413 fn op_big_k(&mut self) -> Result<(), PdfError> {
2414 let n = self.get_numbers(4)?;
2416 self.gstate.stroke_color =
2417 DeviceColor::from_cmyk_icc(n[0], n[1], n[2], n[3], &mut self.icc_cache);
2418 self.gstate.stroke_color_space = ColorSpaceRef::DeviceCMYK;
2419 self.gstate.stroke_painted_channels = stet_graphics::device::CMYK_ALL;
2420 self.gstate.stroke_is_device_cmyk = true;
2421 self.gstate.stroke_is_none = false;
2422 self.gstate.stroke_spot_color = None;
2423 self.gstate.stroke_icc_color = None;
2424 self.gstate.stroke_pattern = None;
2425 self.gstate.stroke_shading_pattern = None;
2426 Ok(())
2427 }
2428
2429 fn op_small_k(&mut self) -> Result<(), PdfError> {
2430 let n = self.get_numbers(4)?;
2432 self.gstate.fill_color =
2433 DeviceColor::from_cmyk_icc(n[0], n[1], n[2], n[3], &mut self.icc_cache);
2434 self.gstate.fill_color_space = ColorSpaceRef::DeviceCMYK;
2435 self.gstate.fill_painted_channels = stet_graphics::device::CMYK_ALL;
2436 self.gstate.fill_is_device_cmyk = true;
2437 self.gstate.fill_is_none = false;
2438 self.gstate.fill_spot_color = None;
2439 self.gstate.fill_icc_color = None;
2440 self.gstate.fill_pattern = None;
2441 self.gstate.fill_shading_pattern = None;
2442 Ok(())
2443 }
2444
2445 fn op_big_cs(&mut self) -> Result<(), PdfError> {
2448 let name = self
2450 .operand_stack
2451 .last()
2452 .and_then(|o| o.as_name())
2453 .ok_or(PdfError::Other("CS: expected name".into()))?
2454 .to_vec();
2455 self.gstate.stroke_color_space = name_to_cs_ref(&name);
2456 Ok(())
2457 }
2458
2459 fn op_small_cs(&mut self) -> Result<(), PdfError> {
2460 let name = self
2462 .operand_stack
2463 .last()
2464 .and_then(|o| o.as_name())
2465 .ok_or(PdfError::Other("cs: expected name".into()))?
2466 .to_vec();
2467 self.gstate.fill_color_space = name_to_cs_ref(&name);
2468 Ok(())
2469 }
2470
2471 fn resolve_cs_cached(
2475 &mut self,
2476 cs_ref: &ColorSpaceRef,
2477 ) -> Result<ResolvedColorSpace, PdfError> {
2478 if let ColorSpaceRef::Named(name) = cs_ref {
2479 match name.as_slice() {
2481 b"DeviceGray" | b"G" => return Ok(ResolvedColorSpace::DeviceGray),
2482 b"DeviceRGB" | b"RGB" => return Ok(ResolvedColorSpace::DeviceRGB),
2483 b"DeviceCMYK" | b"CMYK" => return Ok(ResolvedColorSpace::DeviceCMYK),
2484 b"Pattern" => return Ok(ResolvedColorSpace::Pattern),
2485 _ => {}
2486 }
2487 if self.cs_index.is_none() {
2489 let mut index = std::collections::HashMap::new();
2490 if let Some(cs_dict) = self.resolve_resource_subdict(b"ColorSpace") {
2491 for (k, v) in cs_dict.entries() {
2492 index.insert(k.clone(), v.clone());
2493 }
2494 }
2495 self.cs_index = Some(index);
2496 }
2497 if let Some(cs_obj) = self.cs_index.as_ref().unwrap().get(name.as_slice()) {
2498 let cs_obj = cs_obj.clone();
2499 resolve_color_space_obj(&cs_obj, self.resolver)
2500 } else {
2501 resolve_color_space(
2505 &ColorSpaceRef::Named(name.to_vec()),
2506 &self.resources,
2507 self.resolver,
2508 )
2509 }
2510 } else {
2511 resolve_color_space(cs_ref, &self.resources, self.resolver)
2512 }
2513 }
2514
2515 fn op_sc_stroke(&mut self) -> Result<(), PdfError> {
2516 if matches!(self.operand_stack.last(), Some(Operand::Name(_))) {
2521 return self.handle_pattern_stroke();
2522 }
2523 let cs = self.resolve_cs_cached(&self.gstate.stroke_color_space.clone())?;
2524 if matches!(cs, ResolvedColorSpace::Pattern) {
2525 return self.handle_pattern_stroke();
2526 }
2527 let n = cs.num_components();
2528 if n == 0 {
2529 return Ok(());
2530 }
2531 let nums = self.get_numbers(n)?;
2532 self.gstate.stroke_painted_channels = painted_channels_for_cs(&cs);
2533 self.gstate.stroke_is_none = cs.is_none_colorant();
2534 self.gstate.stroke_is_device_cmyk = matches!(
2535 cs,
2536 ResolvedColorSpace::DeviceCMYK | ResolvedColorSpace::ICCBased { n: 4, .. }
2537 );
2538 let intent = self.gstate.rendering_intent;
2539 let mut color = color_space::components_to_device_color_icc_with_intent(
2540 &cs,
2541 &nums,
2542 Some(&mut self.icc_cache),
2543 intent,
2544 );
2545 self.cmyk_group_promote_color(&mut color);
2546 self.gstate.stroke_color = color;
2547 self.gstate.stroke_spot_color =
2548 color_space::build_spot_color(&cs, &nums, &mut self.spot_tint_table_cache);
2549 self.gstate.stroke_icc_color = color_space::build_icc_color(&cs, &nums);
2550 self.gstate.stroke_pattern = None;
2551 self.gstate.stroke_shading_pattern = None;
2552 Ok(())
2553 }
2554
2555 fn op_sc_fill(&mut self) -> Result<(), PdfError> {
2556 if matches!(self.operand_stack.last(), Some(Operand::Name(_))) {
2561 return self.handle_pattern_fill();
2562 }
2563 let cs = self.resolve_cs_cached(&self.gstate.fill_color_space.clone())?;
2564 if matches!(cs, ResolvedColorSpace::Pattern) {
2565 return self.handle_pattern_fill();
2566 }
2567 let n = cs.num_components();
2568 if n == 0 {
2569 return Ok(());
2570 }
2571 let nums = self.get_numbers(n)?;
2572 self.gstate.fill_painted_channels = painted_channels_for_cs(&cs);
2573 self.gstate.fill_is_none = cs.is_none_colorant();
2574 self.gstate.fill_is_device_cmyk = matches!(
2575 cs,
2576 ResolvedColorSpace::DeviceCMYK | ResolvedColorSpace::ICCBased { n: 4, .. }
2577 );
2578 let intent = self.gstate.rendering_intent;
2579 let mut color = color_space::components_to_device_color_icc_with_intent(
2580 &cs,
2581 &nums,
2582 Some(&mut self.icc_cache),
2583 intent,
2584 );
2585 self.cmyk_group_promote_color(&mut color);
2586 self.gstate.fill_color = color;
2587 self.gstate.fill_spot_color =
2588 color_space::build_spot_color(&cs, &nums, &mut self.spot_tint_table_cache);
2589 self.gstate.fill_icc_color = color_space::build_icc_color(&cs, &nums);
2590 self.gstate.fill_pattern = None;
2591 self.gstate.fill_shading_pattern = None;
2592 Ok(())
2593 }
2594
2595 fn op_tf(&mut self) -> Result<(), PdfError> {
2598 let len = self.operand_stack.len();
2600 if len < 2 {
2601 return Ok(());
2602 }
2603 self.gstate.font_size = self.operand_stack[len - 1].as_f64().unwrap_or(12.0);
2604 if let Some(name) = self.operand_stack[len - 2].as_name() {
2605 let name = name.to_vec();
2606 self.gstate.text_font_name = name.clone();
2607 self.resolve_current_font(&name);
2608 }
2609 Ok(())
2610 }
2611
2612 fn resolve_current_font(&mut self, name: &[u8]) {
2614 if let Some(cached) = self.font_cache.get(name) {
2617 let font_ref = self
2621 .resolve_resource_subdict(b"Font")
2622 .and_then(|fd| fd.get(name).cloned());
2623 if let Some(PdfObj::Ref(obj_num, _)) = &font_ref {
2624 let obj_key = obj_num.to_le_bytes().to_vec();
2625 if let Some(obj_cached) = self.font_cache.get(&obj_key) {
2626 self.current_font = Some(Arc::clone(obj_cached));
2629 return;
2630 }
2631 } else {
2635 self.current_font = Some(Arc::clone(cached));
2637 return;
2638 }
2639 }
2640
2641 let font_ref = self
2643 .resolve_resource_subdict(b"Font")
2644 .and_then(|fd| fd.get(name).cloned());
2645 let font_ref = match font_ref {
2646 Some(r) => r,
2647 None => {
2648 if let Some(fallback) = font::fallback_font(self.font_provider.as_ref()) {
2650 let arc = Arc::new(fallback);
2651 self.font_cache.insert(name.to_vec(), Arc::clone(&arc));
2652 self.current_font = Some(arc);
2653 } else {
2654 self.current_font = None;
2655 }
2656 return;
2657 }
2658 };
2659
2660 if let PdfObj::Ref(obj_num, _) = &font_ref {
2662 let obj_key = obj_num.to_le_bytes().to_vec();
2663 if let Some(cached) = self.font_cache.get(&obj_key) {
2664 let arc = Arc::clone(cached);
2665 self.font_cache.insert(name.to_vec(), Arc::clone(&arc));
2666 self.current_font = Some(arc);
2667 return;
2668 }
2669 }
2670
2671 match font::resolve_font(self.resolver, &font_ref, self.font_provider.as_ref()) {
2672 Ok(font) => {
2673 let arc = Arc::new(font);
2674 if let PdfObj::Ref(obj_num, _) = &font_ref {
2676 self.font_cache
2677 .insert(obj_num.to_le_bytes().to_vec(), Arc::clone(&arc));
2678 }
2679 self.font_cache.insert(name.to_vec(), Arc::clone(&arc));
2680 self.current_font = Some(arc);
2681 }
2682 Err(e) => {
2683 use std::sync::Mutex;
2685 static WARNED: Mutex<Vec<String>> = Mutex::new(Vec::new());
2686 let msg = format!("font /{}: {}", String::from_utf8_lossy(name), e);
2687 if let Ok(mut set) = WARNED.lock()
2688 && !set.contains(&msg)
2689 {
2690 eprintln!("warning: {msg}");
2691 set.push(msg);
2692 }
2693 if let Some(fallback) = font::fallback_font(self.font_provider.as_ref()) {
2695 let arc = Arc::new(fallback);
2696 self.font_cache.insert(name.to_vec(), Arc::clone(&arc));
2697 self.current_font = Some(arc);
2698 } else {
2699 self.current_font = None;
2700 }
2701 }
2702 }
2703 }
2704
2705 fn check_text_cull(&mut self) {
2708 if let Some((y_lo, y_hi)) = self.form_cull_y {
2709 let text_y = self.gstate.text_matrix.ty;
2711 if text_y < y_lo || text_y > y_hi {
2712 self.bt_culled = true;
2713 }
2714 }
2715 }
2716
2717 fn op_td(&mut self) -> Result<(), PdfError> {
2718 let n = self.get_numbers(2)?;
2719 let m = Matrix::translate(n[0], n[1]);
2720 self.gstate.text_line_matrix = self.gstate.text_line_matrix.concat(&m);
2721 self.gstate.text_matrix = self.gstate.text_line_matrix;
2722 self.check_text_cull();
2723 Ok(())
2724 }
2725
2726 fn op_big_td(&mut self) -> Result<(), PdfError> {
2727 let n = self.get_numbers(2)?;
2728 self.gstate.text_leading = -n[1];
2729 let m = Matrix::translate(n[0], n[1]);
2730 self.gstate.text_line_matrix = self.gstate.text_line_matrix.concat(&m);
2731 self.gstate.text_matrix = self.gstate.text_line_matrix;
2732 self.check_text_cull();
2733 Ok(())
2734 }
2735
2736 fn op_tm(&mut self) -> Result<(), PdfError> {
2737 let n = self.get_numbers(6)?;
2738 let m = Matrix::new(n[0], n[1], n[2], n[3], n[4], n[5]);
2739 self.gstate.text_matrix = m;
2740 self.gstate.text_line_matrix = m;
2741 self.check_text_cull();
2742 Ok(())
2743 }
2744
2745 fn op_t_star(&mut self) -> Result<(), PdfError> {
2746 let leading = self.gstate.text_leading;
2747 let m = Matrix::translate(0.0, -leading);
2748 self.gstate.text_line_matrix = self.gstate.text_line_matrix.concat(&m);
2749 self.gstate.text_matrix = self.gstate.text_line_matrix;
2750 Ok(())
2751 }
2752
2753 fn op_tj(&mut self) -> Result<(), PdfError> {
2756 let text = match self.operand_stack.last() {
2757 Some(Operand::Str(s)) => s.clone(),
2758 _ => return Ok(()),
2759 };
2760 self.show_text(&text);
2761 Ok(())
2762 }
2763
2764 fn op_big_tj(&mut self) -> Result<(), PdfError> {
2765 let arr = match self.operand_stack.last() {
2766 Some(Operand::Array(a)) => a.clone(),
2767 _ => return Ok(()),
2768 };
2769 let vertical = self.current_font.as_ref().is_some_and(|f| f.wmode() == 1);
2770 for elem in &arr {
2771 match elem {
2772 PdfObj::Str(s) => self.show_text(s),
2773 PdfObj::Int(n) => {
2774 let shift = -*n as f64 / 1000.0 * self.gstate.font_size;
2775 let m = if vertical {
2776 Matrix::translate(0.0, shift)
2777 } else {
2778 Matrix::translate(shift * self.gstate.horizontal_scaling, 0.0)
2779 };
2780 self.gstate.text_matrix = self.gstate.text_matrix.concat(&m);
2781 }
2782 PdfObj::Real(f) => {
2783 let shift = -f / 1000.0 * self.gstate.font_size;
2784 let m = if vertical {
2785 Matrix::translate(0.0, shift)
2786 } else {
2787 Matrix::translate(shift * self.gstate.horizontal_scaling, 0.0)
2788 };
2789 self.gstate.text_matrix = self.gstate.text_matrix.concat(&m);
2790 }
2791 _ => {}
2792 }
2793 }
2794 Ok(())
2795 }
2796
2797 fn op_quote(&mut self) -> Result<(), PdfError> {
2798 self.op_t_star()?;
2800 self.op_tj()
2801 }
2802
2803 fn op_dblquote(&mut self) -> Result<(), PdfError> {
2804 let len = self.operand_stack.len();
2806 if len < 3 {
2807 return Ok(());
2808 }
2809 self.gstate.word_spacing = self.operand_stack[len - 3].as_f64().unwrap_or(0.0);
2810 self.gstate.char_spacing = self.operand_stack[len - 2].as_f64().unwrap_or(0.0);
2811 self.op_t_star()?;
2813 self.op_tj()
2814 }
2815
2816 fn show_text(&mut self, text: &[u8]) {
2818 let font = match &self.current_font {
2819 Some(f) => Arc::clone(f),
2820 None => return,
2821 };
2822
2823 let font_size = self.gstate.font_size;
2824 let char_spacing = self.gstate.char_spacing;
2825 let word_spacing = self.gstate.word_spacing;
2826 let text_rise = self.gstate.text_rise;
2827 let th = self.gstate.horizontal_scaling;
2828 let font_matrix = font.font_matrix();
2829 let render_mode = self.gstate.text_rendering_mode;
2830
2831 if font.is_composite() {
2832 let mut i = 0;
2835 while i < text.len() {
2836 let code_width = font.code_width(text[i]);
2837 if code_width == 1 {
2838 let raw_code = text[i] as u32;
2842 let extra = if raw_code == 0x20 { word_spacing } else { 0.0 };
2843 i += 1;
2844 let cid = font.resolve_code_to_cid(raw_code) as u16;
2845 self.render_cid_glyph(
2846 &font,
2847 cid,
2848 font_size,
2849 char_spacing,
2850 th,
2851 text_rise,
2852 &font_matrix,
2853 render_mode,
2854 extra,
2855 );
2856 } else if i + 1 >= text.len() {
2857 let byte = text[i];
2859 i += 1;
2860 self.render_unicode_glyph(
2861 byte,
2862 font_size,
2863 char_spacing,
2864 th,
2865 text_rise,
2866 &font_matrix,
2867 render_mode,
2868 );
2869 } else {
2870 let width = code_width.min(text.len() - i);
2872 let mut raw_code = 0u32;
2873 for b in &text[i..i + width] {
2874 raw_code = (raw_code << 8) | (*b as u32);
2875 }
2876 let cid = font.resolve_code_to_cid(raw_code) as u16;
2877 let (cid, consumed) = if cid == 0 || (cid == raw_code as u16 && width > 2) {
2882 let byte_cid = font.resolve_code_to_cid(text[i] as u32) as u16;
2883 if byte_cid != 0 && byte_cid != text[i] as u16 {
2884 (byte_cid, 1)
2885 } else {
2886 (cid, width)
2887 }
2888 } else {
2889 (cid, width)
2890 };
2891 let extra = if consumed == 1 && text[i] == 0x20 {
2893 word_spacing
2894 } else {
2895 0.0
2896 };
2897 i += consumed;
2898 if font.has_cid_glyph(cid) {
2899 self.render_cid_glyph(
2901 &font,
2902 cid,
2903 font_size,
2904 char_spacing,
2905 th,
2906 text_rise,
2907 &font_matrix,
2908 render_mode,
2909 extra,
2910 );
2911 } else {
2912 let lo_cid = (raw_code & 0xFF) as u16;
2916 if lo_cid > 0 && font.has_cid_glyph(lo_cid) {
2917 self.render_cid_glyph(
2918 &font,
2919 lo_cid,
2920 font_size,
2921 char_spacing,
2922 th,
2923 text_rise,
2924 &font_matrix,
2925 render_mode,
2926 extra,
2927 );
2928 } else if raw_code <= 0xFF {
2929 self.render_unicode_glyph(
2933 text[i - 2],
2934 font_size,
2935 char_spacing,
2936 th,
2937 text_rise,
2938 &font_matrix,
2939 render_mode,
2940 );
2941 self.render_unicode_glyph(
2942 text[i - 1],
2943 font_size,
2944 char_spacing,
2945 th,
2946 text_rise,
2947 &font_matrix,
2948 render_mode,
2949 );
2950 } else {
2951 self.render_cid_glyph_unicode_fallback(
2954 &font,
2955 cid,
2956 raw_code,
2957 font_size,
2958 char_spacing,
2959 th,
2960 text_rise,
2961 &font_matrix,
2962 render_mode,
2963 extra,
2964 );
2965 }
2966 }
2967 }
2968 }
2969 } else if font.is_type3() {
2970 let fm = font.font_matrix();
2973 let visible = (render_mode & 3) != 3; for &byte in text {
2975 if visible {
2976 self.show_type3_glyph(&font, byte);
2977 }
2978
2979 let w0_glyph = font.glyph_width(byte);
2980 let w0 = w0_glyph * fm.a;
2981 let mut tx = w0 * font_size + char_spacing;
2982 if byte == b' ' {
2983 tx += word_spacing;
2984 }
2985 tx *= th;
2986 let advance = Matrix::translate(tx, 0.0);
2987 self.gstate.text_matrix = self.gstate.text_matrix.concat(&advance);
2988 }
2989 } else {
2990 for &byte in text {
2992 if let Some(glyph_path) = font.glyph_path(byte) {
2993 let text_state_matrix =
2994 Matrix::new(font_size * th, 0.0, 0.0, font_size, 0.0, text_rise);
2995 let trm = self
2996 .gstate
2997 .ctm
2998 .concat(&self.gstate.text_matrix)
2999 .concat(&text_state_matrix)
3000 .concat(&font_matrix);
3001
3002 let device_path = glyph_path.transform(&trm);
3003 if !device_path.is_empty() {
3004 self.emit_text_glyph(device_path, render_mode);
3005 }
3006 }
3007
3008 let w0 = font.glyph_width(byte);
3009 let mut tx = w0 * font_size + char_spacing;
3010 if byte == b' ' {
3011 tx += word_spacing;
3012 }
3013 tx *= th;
3014 let advance = Matrix::translate(tx, 0.0);
3015 self.gstate.text_matrix = self.gstate.text_matrix.concat(&advance);
3016 }
3017 }
3018 }
3019
3020 fn render_cid_glyph(
3022 &mut self,
3023 font: &PdfFont,
3024 cid: u16,
3025 font_size: f64,
3026 char_spacing: f64,
3027 th: f64,
3028 text_rise: f64,
3029 font_matrix: &Matrix,
3030 render_mode: i32,
3031 extra_advance: f64,
3032 ) {
3033 let vertical = font.wmode() == 1;
3034 if let Some(glyph_path) = font.glyph_path_cid(cid) {
3035 let text_state_matrix = if vertical {
3036 let [_w1, v_x, v_y] = font.vertical_metrics_cid(cid);
3039 Matrix::new(
3040 font_size,
3041 0.0,
3042 0.0,
3043 font_size,
3044 -v_x / 1000.0 * font_size,
3045 -v_y / 1000.0 * font_size,
3046 )
3047 } else {
3048 Matrix::new(font_size * th, 0.0, 0.0, font_size, 0.0, text_rise)
3049 };
3050 let trm = self
3051 .gstate
3052 .ctm
3053 .concat(&self.gstate.text_matrix)
3054 .concat(&text_state_matrix)
3055 .concat(font_matrix);
3056 let device_path = glyph_path.transform(&trm);
3057 if !device_path.is_empty() {
3058 self.emit_text_glyph(device_path, render_mode);
3059 }
3060 }
3061 if vertical {
3062 let [w1, _vx, _vy] = font.vertical_metrics_cid(cid);
3063 let ty = w1 / 1000.0 * font_size + char_spacing + extra_advance;
3064 let advance = Matrix::translate(0.0, ty);
3065 self.gstate.text_matrix = self.gstate.text_matrix.concat(&advance);
3066 } else {
3067 let w0 = font.glyph_width_cid(cid);
3068 let tx = (w0 * font_size + char_spacing + extra_advance) * th;
3069 let advance = Matrix::translate(tx, 0.0);
3070 self.gstate.text_matrix = self.gstate.text_matrix.concat(&advance);
3071 }
3072 }
3073
3074 fn render_cid_glyph_unicode_fallback(
3078 &mut self,
3079 font: &PdfFont,
3080 cid: u16,
3081 unicode: u32,
3082 font_size: f64,
3083 char_spacing: f64,
3084 th: f64,
3085 text_rise: f64,
3086 font_matrix: &Matrix,
3087 render_mode: i32,
3088 extra_advance: f64,
3089 ) {
3090 let vertical = font.wmode() == 1;
3091 if let Some(glyph_path) = font.glyph_path_unicode(unicode as u16) {
3093 let text_state_matrix = if vertical {
3094 let [_w1, v_x, v_y] = font.vertical_metrics_cid(cid);
3095 Matrix::new(
3096 font_size,
3097 0.0,
3098 0.0,
3099 font_size,
3100 -v_x / 1000.0 * font_size,
3101 -v_y / 1000.0 * font_size,
3102 )
3103 } else {
3104 Matrix::new(font_size * th, 0.0, 0.0, font_size, 0.0, text_rise)
3105 };
3106 let trm = self
3107 .gstate
3108 .ctm
3109 .concat(&self.gstate.text_matrix)
3110 .concat(&text_state_matrix)
3111 .concat(font_matrix);
3112 let device_path = glyph_path.transform(&trm);
3113 if !device_path.is_empty() {
3114 self.emit_text_glyph(device_path, render_mode);
3115 }
3116 }
3117 if vertical {
3118 let [w1, _vx, _vy] = font.vertical_metrics_cid(cid);
3119 let ty = w1 / 1000.0 * font_size + char_spacing + extra_advance;
3120 let advance = Matrix::translate(0.0, ty);
3121 self.gstate.text_matrix = self.gstate.text_matrix.concat(&advance);
3122 } else {
3123 let w0 = font.glyph_width_cid(cid);
3124 let tx = (w0 * font_size + char_spacing + extra_advance) * th;
3125 let advance = Matrix::translate(tx, 0.0);
3126 self.gstate.text_matrix = self.gstate.text_matrix.concat(&advance);
3127 }
3128 }
3129
3130 fn render_unicode_glyph(
3133 &mut self,
3134 byte: u8,
3135 font_size: f64,
3136 char_spacing: f64,
3137 th: f64,
3138 text_rise: f64,
3139 font_matrix: &Matrix,
3140 render_mode: i32,
3141 ) {
3142 let unicode = font::winansi_byte_to_unicode(byte);
3143 if let Some(glyph_path) = self
3144 .current_font
3145 .as_ref()
3146 .and_then(|f| f.glyph_path_unicode(unicode))
3147 {
3148 let text_state_matrix =
3149 Matrix::new(font_size * th, 0.0, 0.0, font_size, 0.0, text_rise);
3150 let trm = self
3151 .gstate
3152 .ctm
3153 .concat(&self.gstate.text_matrix)
3154 .concat(&text_state_matrix)
3155 .concat(font_matrix);
3156 let device_path = glyph_path.transform(&trm);
3157 if !device_path.is_empty() {
3158 self.emit_text_glyph(device_path, render_mode);
3159 }
3160 }
3161 let w0 = self
3162 .current_font
3163 .as_ref()
3164 .map(|f| f.glyph_width_unicode(unicode))
3165 .unwrap_or(0.0);
3166 let tx = (w0 * font_size + char_spacing) * th;
3167 let advance = Matrix::translate(tx, 0.0);
3168 self.gstate.text_matrix = self.gstate.text_matrix.concat(&advance);
3169 }
3170
3171 fn show_type3_glyph(&mut self, font: &PdfFont, char_code: u8) {
3173 let proc_data = match font.type3_char_proc(char_code) {
3174 Some(data) => data.to_vec(),
3175 None => return,
3176 };
3177 let resources = match font.type3_resources() {
3178 Some(r) => r.clone(),
3179 None => return,
3180 };
3181
3182 let font_size = self.gstate.font_size;
3183 let text_rise = self.gstate.text_rise;
3184 let font_matrix = font.font_matrix();
3185
3186 let th = self.gstate.horizontal_scaling;
3188 let text_state_matrix = Matrix::new(font_size * th, 0.0, 0.0, font_size, 0.0, text_rise);
3189 let trm = self
3190 .gstate
3191 .ctm
3192 .concat(&self.gstate.text_matrix)
3193 .concat(&text_state_matrix)
3194 .concat(&font_matrix);
3195 let stack_depth_before = self.gstate_stack.len();
3198 self.gstate_stack.push(self.gstate.clone());
3199 let mut merged = self.resources.clone();
3204 for (key, value) in resources.entries() {
3205 merged.insert(key.clone(), value.clone());
3206 }
3207 let saved_resources = std::mem::replace(&mut self.resources, merged);
3208 let saved_display_list = std::mem::take(&mut self.display_list);
3209 let saved_path = std::mem::take(&mut self.current_path);
3210 let saved_point = self.current_point.take();
3211 let saved_subpath = self.subpath_start.take();
3212 let saved_font = self.current_font.clone();
3213 let saved_in_text = self.in_text;
3214 let saved_content_stream_ctm = self.content_stream_ctm;
3215 let saved_mc_stack = std::mem::take(&mut self.mc_stack);
3216
3217 self.gstate.ctm = trm;
3218 self.content_stream_ctm = trm;
3221
3222 let saved_d1 = self.d1_color_suppressed;
3223 self.d1_color_suppressed = false;
3224 self.depth += 1;
3225 let _ = self.interpret_stream(&proc_data);
3226 self.depth -= 1;
3227 self.d1_color_suppressed = saved_d1;
3228 let glyph_elements = std::mem::replace(&mut self.display_list, saved_display_list);
3230 self.resources = saved_resources;
3231 self.current_path = saved_path;
3232 self.current_point = saved_point;
3233 self.subpath_start = saved_subpath;
3234 self.current_font = saved_font;
3235 self.in_text = saved_in_text;
3236 self.content_stream_ctm = saved_content_stream_ctm;
3237 self.mc_stack = saved_mc_stack;
3238 self.gstate_stack.truncate(stack_depth_before + 1);
3241 if let Some(saved) = self.gstate_stack.pop() {
3242 self.gstate = saved;
3243 }
3244
3245 for elem in glyph_elements.into_elements() {
3247 self.display_list.push(elem);
3248 }
3249 }
3250
3251 fn emit_text_glyph(&mut self, device_path: PsPath, render_mode: i32) {
3256 let mode = render_mode & 3; let clip = render_mode & 4 != 0; match mode {
3260 0 => {
3261 self.emit_text_fill(device_path.clone());
3263 }
3264 1 => {
3265 self.emit_text_stroke(device_path.clone());
3267 }
3268 2 => {
3269 self.emit_text_fill(device_path.clone());
3271 self.emit_text_stroke(device_path.clone());
3272 }
3273 _ => {} }
3275
3276 if clip {
3278 let tcp = self.text_clip_path.get_or_insert_with(PsPath::new);
3279 tcp.segments.extend_from_slice(&device_path.segments);
3280 }
3281 }
3282
3283 fn emit_text_fill(&mut self, path: PsPath) {
3286 if let Some(shading_box) = self.gstate.fill_shading_pattern.clone() {
3287 let bbox = path_device_bbox(&path);
3290 let mut group_dl = DisplayList::new();
3291 group_dl.push(DisplayElement::Clip {
3292 path,
3293 params: ClipParams {
3294 fill_rule: FillRule::NonZeroWinding,
3295 ctm: Matrix::identity(),
3296 stroke_params: None,
3297 },
3298 });
3299 for elem in shading_box.0.elements() {
3300 group_dl.push(elem.clone());
3301 }
3302 self.display_list.push(DisplayElement::Group {
3303 elements: group_dl,
3304 params: GroupParams {
3305 bbox,
3306 isolated: true,
3307 knockout: false,
3308 blend_mode: self.gstate.blend_mode,
3309 alpha: self.gstate.fill_alpha,
3310 color_space: stet_graphics::display_list::GroupColorSpace::Inherited,
3311 },
3312 });
3313 } else if let Some(pattern) = self.gstate.fill_pattern.clone() {
3314 self.display_list.push(DisplayElement::PatternFill {
3315 params: PatternFillParams {
3316 path,
3317 fill_rule: FillRule::NonZeroWinding,
3318 tile: pattern.tile,
3319 pattern_matrix: pattern.pattern_matrix,
3320 bbox: pattern.bbox,
3321 xstep: pattern.x_step,
3322 ystep: pattern.y_step,
3323 paint_type: pattern.paint_type,
3324 underlying_color: if pattern.paint_type == 2 {
3325 Some(self.gstate.fill_color.clone())
3326 } else {
3327 None
3328 },
3329 pattern_id: pattern.pattern_id,
3330 device_space_tile: false,
3331 flip_tile_y: false,
3332 stroke_params: None,
3333 overprint_mode: if self.gstate.overprint {
3334 self.gstate.overprint_mode
3335 } else {
3336 0
3337 },
3338 },
3339 });
3340 } else {
3341 let mut params = self.gstate.fill_params(FillRule::NonZeroWinding);
3342 params.is_text_glyph = true;
3343 self.display_list
3344 .push(DisplayElement::Fill { path, params });
3345 }
3346 }
3347
3348 fn emit_text_stroke(&mut self, path: PsPath) {
3351 if let Some(shading_box) = self.gstate.stroke_shading_pattern.clone() {
3353 let mut sp = self.gstate.stroke_params();
3354 sp.is_text_glyph = true;
3355 let mut bbox = path_device_bbox(&path);
3357 let half_w = sp.line_width * 0.5;
3358 bbox[0] -= half_w;
3359 bbox[1] -= half_w;
3360 bbox[2] += half_w;
3361 bbox[3] += half_w;
3362 let mut group_dl = DisplayList::new();
3363 group_dl.push(DisplayElement::Clip {
3364 path,
3365 params: ClipParams {
3366 fill_rule: FillRule::NonZeroWinding,
3367 ctm: Matrix::identity(),
3368 stroke_params: Some(sp),
3369 },
3370 });
3371 for elem in shading_box.0.elements() {
3372 group_dl.push(elem.clone());
3373 }
3374 self.display_list.push(DisplayElement::Group {
3375 elements: group_dl,
3376 params: GroupParams {
3377 bbox,
3378 isolated: true,
3379 knockout: false,
3380 blend_mode: self.gstate.blend_mode,
3381 alpha: self.gstate.stroke_alpha,
3382 color_space: stet_graphics::display_list::GroupColorSpace::Inherited,
3383 },
3384 });
3385 return;
3386 }
3387
3388 if let Some(pattern) = self.gstate.stroke_pattern.clone() {
3391 let mut sp = self.gstate.stroke_params();
3392 sp.is_text_glyph = true;
3393 self.display_list.push(DisplayElement::PatternFill {
3394 params: PatternFillParams {
3395 path,
3396 fill_rule: FillRule::NonZeroWinding,
3397 tile: pattern.tile,
3398 pattern_matrix: pattern.pattern_matrix,
3399 bbox: pattern.bbox,
3400 xstep: pattern.x_step,
3401 ystep: pattern.y_step,
3402 paint_type: pattern.paint_type,
3403 underlying_color: if pattern.paint_type == 2 {
3404 Some(self.gstate.stroke_color.clone())
3405 } else {
3406 None
3407 },
3408 pattern_id: pattern.pattern_id,
3409 device_space_tile: false,
3410 flip_tile_y: false,
3411 stroke_params: Some(sp),
3412 overprint_mode: if self.gstate.overprint {
3413 self.gstate.overprint_mode
3414 } else {
3415 0
3416 },
3417 },
3418 });
3419 return;
3420 }
3421
3422 let mut params = self.gstate.stroke_params();
3423 params.is_text_glyph = true;
3424 self.display_list
3425 .push(DisplayElement::Stroke { path, params });
3426 }
3427
3428 fn op_bdc(&mut self) -> Result<(), PdfError> {
3437 let props = self.operand_stack.pop();
3438 let tag = self.operand_stack.pop();
3439
3440 let is_oc = matches!(&tag, Some(Operand::Name(n)) if n == b"OC");
3441 if !is_oc {
3442 self.mc_stack.push(MarkedContentFrame::Other);
3443 return Ok(());
3444 }
3445
3446 let mut pushed = false;
3448 if let Some(Operand::Name(prop_name)) = props
3449 && let Some(props_dict) = self.resolve_resource_subdict(b"Properties")
3450 && let Some(ocg_obj) = props_dict.get(&prop_name)
3451 {
3452 let visibility = self.build_visibility(ocg_obj);
3453 let parent_list = std::mem::replace(&mut self.display_list, DisplayList::new());
3454 self.mc_stack.push(MarkedContentFrame::Ocg {
3455 parent_list,
3456 visibility,
3457 });
3458 pushed = true;
3459 }
3460 if !pushed {
3461 self.mc_stack.push(MarkedContentFrame::Other);
3464 }
3465
3466 Ok(())
3467 }
3468
3469 fn is_ocg_off(&self, ocg_obj: &PdfObj) -> bool {
3473 if let Some((obj_num, _)) = ocg_obj.as_ref() {
3475 if let Ok(resolved) = self.resolver.deref(ocg_obj) {
3477 if let Some(dict) = resolved.as_dict() {
3478 if dict.get_name(b"Type") == Some(b"OCMD") {
3479 return self.is_ocmd_off(dict);
3480 }
3481 }
3482 }
3483 return self.ocg_off.contains(&obj_num);
3485 }
3486 if let Some(dict) = ocg_obj.as_dict() {
3488 if dict.get_name(b"Type") == Some(b"OCMD") {
3489 return self.is_ocmd_off(dict);
3490 }
3491 }
3492 false
3493 }
3494
3495 fn build_visibility(&self, ocg_obj: &PdfObj) -> OcgVisibility {
3507 let resolved = self.resolver.deref(ocg_obj).ok();
3508 let dict = resolved.as_ref().and_then(|o| o.as_dict());
3509
3510 if let Some(dict) = dict
3511 && dict.get_name(b"Type") == Some(b"OCMD")
3512 {
3513 let default_visible = !self.is_ocg_off(ocg_obj);
3518 let throwaway = crate::diagnostics::WarningSink::new();
3524 return crate::layers::ocmd::build_ocmd_visibility(
3525 self.resolver,
3526 dict,
3527 default_visible,
3528 &throwaway,
3529 );
3530 }
3531
3532 if let Some((ocg_id, _)) = ocg_obj.as_ref() {
3533 return OcgVisibility::Single {
3534 ocg_id,
3535 default_visible: !self.ocg_off.contains(&ocg_id),
3536 };
3537 }
3538
3539 OcgVisibility::Single {
3541 ocg_id: 0,
3542 default_visible: !self.is_ocg_off(ocg_obj),
3543 }
3544 }
3545
3546 fn is_ocmd_off(&self, ocmd: &PdfDict) -> bool {
3551 let policy = ocmd.get_name(b"P").unwrap_or(b"AnyOn");
3552
3553 let mut ocg_nums = Vec::new();
3555 if let Some(ocgs_obj) = ocmd.get(b"OCGs") {
3556 match ocgs_obj {
3557 PdfObj::Ref(num, _) => ocg_nums.push(*num),
3558 PdfObj::Array(arr) => {
3559 for item in arr {
3560 if let Some((num, _)) = item.as_ref() {
3561 ocg_nums.push(num);
3562 }
3563 }
3564 }
3565 _ => {}
3566 }
3567 }
3568 if ocg_nums.is_empty() {
3569 return false;
3570 }
3571
3572 let visible = match policy {
3574 b"AllOn" => ocg_nums.iter().all(|n| !self.ocg_off.contains(n)),
3575 b"AnyOff" => ocg_nums.iter().any(|n| self.ocg_off.contains(n)),
3576 b"AllOff" => ocg_nums.iter().all(|n| self.ocg_off.contains(n)),
3577 _ => ocg_nums.iter().any(|n| !self.ocg_off.contains(n)),
3578 };
3579 !visible
3580 }
3581
3582 fn op_do(&mut self) -> Result<(), PdfError> {
3585 let name = self
3586 .operand_stack
3587 .last()
3588 .and_then(|o| o.as_name())
3589 .ok_or(PdfError::Other("Do: expected name".into()))?
3590 .to_vec();
3591
3592 let xobj_dict = self
3594 .resolve_resource_subdict(b"XObject")
3595 .ok_or(PdfError::Other("no XObject resources".into()))?;
3596 let xobj_ref = xobj_dict.get(&name).ok_or_else(|| {
3597 PdfError::Other(format!(
3598 "XObject /{} not found",
3599 String::from_utf8_lossy(&name)
3600 ))
3601 })?;
3602 let xobj_ref_clone = xobj_ref.clone();
3604 let xobj = self.resolver.deref(xobj_ref)?;
3605 let dict = xobj
3606 .as_dict()
3607 .ok_or(PdfError::Other("XObject is not a stream".into()))?;
3608
3609 let xobj_visibility = dict.get(b"OC").map(|oc_obj| self.build_visibility(oc_obj));
3613
3614 let mut wrapped = false;
3615 if let Some(visibility) = xobj_visibility {
3616 let parent_list = std::mem::replace(&mut self.display_list, DisplayList::new());
3617 self.mc_stack.push(MarkedContentFrame::Ocg {
3618 parent_list,
3619 visibility,
3620 });
3621 wrapped = true;
3622 }
3623
3624 let subtype = dict.get_name(b"Subtype").unwrap_or(b"");
3625 match subtype {
3626 b"Image" => self.handle_image_xobject(&xobj_ref_clone, dict)?,
3627 b"Form" => self.handle_form_xobject(&xobj_ref_clone, dict)?,
3628 _ => {}
3629 }
3630
3631 if wrapped {
3633 if let Some(MarkedContentFrame::Ocg {
3634 parent_list,
3635 visibility,
3636 }) = self.mc_stack.pop()
3637 {
3638 let ocg_list = std::mem::replace(&mut self.display_list, parent_list);
3639 self.display_list.push(DisplayElement::OcgGroup {
3640 elements: ocg_list,
3641 visibility,
3642 });
3643 }
3644 }
3645
3646 Ok(())
3647 }
3648
3649 fn handle_image_xobject(&mut self, obj: &PdfObj, dict: &PdfDict) -> Result<(), PdfError> {
3651 if let PdfObj::Ref(obj_num, _) = obj {
3654 if let Some(cached) = self.image_cache.get(obj_num).cloned() {
3655 return self.emit_cached_image(cached);
3656 }
3657 }
3658
3659 let width = self
3661 .resolve_dict_int(dict, b"Width")
3662 .ok_or(PdfError::Other("image missing Width".into()))? as u32;
3663 let height = self
3664 .resolve_dict_int(dict, b"Height")
3665 .ok_or(PdfError::Other("image missing Height".into()))? as u32;
3666
3667 let is_image_mask = dict
3669 .get(b"ImageMask")
3670 .and_then(|o| match o {
3671 PdfObj::Bool(b) => Some(*b),
3672 _ => None,
3673 })
3674 .unwrap_or(false);
3675
3676 let bpc = if is_image_mask {
3677 1
3678 } else {
3679 dict.get_int(b"BitsPerComponent").unwrap_or(8) as u32
3680 };
3681
3682 let gstate_intent = self.gstate.rendering_intent;
3689 let image_intent = match dict
3690 .get(b"Intent")
3691 .and_then(|o| self.resolver.deref(o).ok())
3692 {
3693 Some(PdfObj::Name(n)) => match n.as_slice() {
3694 b"Perceptual" => 0u8,
3695 b"RelativeColorimetric" => 1,
3696 b"Saturation" => 2,
3697 b"AbsoluteColorimetric" => 3,
3698 _ => gstate_intent,
3699 },
3700 _ => gstate_intent,
3701 };
3702
3703 let has_explicit_cs = dict.get(b"ColorSpace").is_some();
3705 let resolved_cs = if is_image_mask {
3706 None
3707 } else if let Some(cs_obj) = dict.get(b"ColorSpace") {
3708 match resolve_color_space_obj(cs_obj, self.resolver) {
3709 Ok(cs) => Some(cs),
3710 Err(_) => {
3711 Some(match bpc {
3715 1 => ResolvedColorSpace::DeviceGray,
3716 _ => ResolvedColorSpace::DeviceRGB,
3717 })
3718 }
3719 }
3720 } else {
3721 Some(ResolvedColorSpace::DeviceRGB)
3723 };
3724
3725 let polarity = if is_image_mask {
3726 if let Some(arr) = dict.get_array(b"Decode") {
3727 let vals: Vec<f64> = arr.iter().filter_map(|o| o.as_f64()).collect();
3728 vals.len() >= 2 && vals[0] > 0.5
3729 } else {
3730 false
3731 }
3732 } else {
3733 false
3734 };
3735
3736 let smask_in_data = dict.get_int(b"SMaskInData").unwrap_or(0);
3739
3740 let filter_name_raw = dict.get_name(b"Filter");
3741 let filter_is_dct = matches!(filter_name_raw, Some(b"DCTDecode" | b"DCT"));
3742 let filter_is_jpx = matches!(filter_name_raw, Some(b"JPXDecode" | b"JPX"))
3744 || dict.get_array(b"Filter").is_some_and(|arr| {
3745 arr.iter()
3746 .any(|f| matches!(f.as_name(), Some(b"JPXDecode" | b"JPX")))
3747 });
3748
3749 let cs_is_indexed = matches!(resolved_cs, Some(ResolvedColorSpace::Indexed { .. }));
3754 let sample_data = if filter_is_dct {
3755 if let Some(raw) = self.resolver.raw_stream_bytes(obj)
3756 && let Some((_jw, jh)) = crate::filters::jpeg_dimensions(raw)
3757 && jh > height * 2
3758 {
3759 let mut patched = raw.to_vec();
3760 crate::filters::patch_jpeg_sof_height(&mut patched, height as u16);
3761 crate::filters::decode_stream(
3762 &patched,
3763 &[crate::filters::Filter::DCTDecode],
3764 &[],
3765 None,
3766 )?
3767 } else {
3768 self.resolver.stream_data_from_obj(obj)?
3769 }
3770 } else if filter_is_jpx && cs_is_indexed {
3771 #[cfg(feature = "jpx")]
3776 {
3777 if let Some(raw) = self.resolver.raw_stream_bytes(obj) {
3778 let jp2_data = crate::filters::decode_pre_jpx(raw, dict);
3779 let (mut data, bpc) = crate::filters::decode_jpx_no_palette(&jp2_data)?;
3780 if bpc < 8 {
3784 let max_val = ((1u32 << bpc) - 1) as f64;
3785 for b in data.iter_mut() {
3786 *b = (*b as f64 / 255.0 * max_val).round() as u8;
3787 }
3788 }
3789 data
3790 } else {
3791 self.resolver.stream_data_from_obj(obj)?
3792 }
3793 }
3794 #[cfg(not(feature = "jpx"))]
3795 {
3796 self.resolver.stream_data_from_obj(obj)?
3797 }
3798 } else {
3799 self.resolver.stream_data_from_obj(obj)?
3800 };
3801
3802 let (width, height) = if filter_is_dct {
3806 if let Some(raw) = self.resolver.raw_stream_bytes(obj)
3807 && let Some((jw, jh)) = crate::filters::jpeg_dimensions(raw)
3808 && (jw != width || jh != height)
3809 && jw <= width * 2
3810 && jh <= height * 2
3811 {
3812 (jw, jh)
3813 } else {
3814 (width, height)
3815 }
3816 } else if filter_is_jpx {
3817 #[cfg(feature = "jpx")]
3818 {
3819 if let Some(raw) = self.resolver.raw_stream_bytes(obj) {
3823 let jp2_data = crate::filters::decode_pre_jpx(raw, dict);
3825 if let Some((jw, jh)) = crate::filters::jpx_dimensions(&jp2_data)
3826 && (jw != width || jh != height)
3827 {
3828 (jw, jh)
3829 } else {
3830 (width, height)
3831 }
3832 } else {
3833 (width, height)
3834 }
3835 }
3836 #[cfg(not(feature = "jpx"))]
3837 {
3838 (width, height)
3839 }
3840 } else {
3841 (width, height)
3842 };
3843
3844 let (resolved_cs, sample_data, smask_in_data_alpha) =
3854 if !is_image_mask && filter_is_jpx && has_explicit_cs {
3855 let n_cs = resolved_cs
3856 .as_ref()
3857 .map_or(3, |cs| cs.num_components() as usize);
3858 let pixels = width as usize * height as usize;
3859 let decoded_comps = if pixels > 0 {
3860 sample_data.len() / pixels
3861 } else {
3862 n_cs
3863 };
3864 if smask_in_data >= 1 && decoded_comps == n_cs + 1 {
3865 let mut color_data = Vec::with_capacity(pixels * n_cs);
3867 let mut alpha_data = Vec::with_capacity(pixels);
3868 for chunk in sample_data.chunks_exact(decoded_comps) {
3869 color_data.extend_from_slice(&chunk[..n_cs]);
3870 alpha_data.push(chunk[n_cs]);
3871 }
3872 (resolved_cs, color_data, Some(alpha_data))
3873 } else if decoded_comps > n_cs {
3874 let mut color_data = Vec::with_capacity(pixels * n_cs);
3877 for chunk in sample_data.chunks_exact(decoded_comps) {
3878 color_data.extend_from_slice(&chunk[..n_cs]);
3879 }
3880 (resolved_cs, color_data, None)
3881 } else {
3882 (resolved_cs, sample_data, None)
3884 }
3885 } else if !is_image_mask && !has_explicit_cs {
3886 let pixels = width as usize * height as usize;
3887 if pixels > 0 {
3888 let n_comps = sample_data.len() / pixels;
3889 if n_comps == 4 && self.is_jpx_rgba(obj) {
3893 if smask_in_data >= 1 {
3894 let mut rgba = sample_data;
3897 for chunk in rgba.chunks_exact_mut(4) {
3898 let a = chunk[3] as u16;
3899 if a == 0 {
3900 chunk[0] = 0;
3901 chunk[1] = 0;
3902 chunk[2] = 0;
3903 } else if a < 255 {
3904 chunk[0] = ((chunk[0] as u16 * a + 127) / 255) as u8;
3905 chunk[1] = ((chunk[1] as u16 * a + 127) / 255) as u8;
3906 chunk[2] = ((chunk[2] as u16 * a + 127) / 255) as u8;
3907 }
3908 }
3909 (None, rgba, None)
3910 } else {
3911 let mut rgb = Vec::with_capacity(pixels * 3);
3913 for chunk in sample_data.chunks_exact(4) {
3914 rgb.push(chunk[0]);
3915 rgb.push(chunk[1]);
3916 rgb.push(chunk[2]);
3917 }
3918 (Some(ResolvedColorSpace::DeviceRGB), rgb, None)
3919 }
3920 } else {
3921 let cs = match n_comps {
3922 1 => ResolvedColorSpace::DeviceGray,
3923 4 => ResolvedColorSpace::DeviceCMYK,
3924 _ => ResolvedColorSpace::DeviceRGB,
3925 };
3926 (Some(cs), sample_data, None)
3927 }
3928 } else {
3929 (resolved_cs, sample_data, None)
3930 }
3931 } else {
3932 (resolved_cs, sample_data, None)
3933 };
3934
3935 let image_matrix =
3937 Matrix::new(width as f64, 0.0, 0.0, -(height as f64), 0.0, height as f64);
3938
3939 if is_image_mask && self.gstate.fill_shading_pattern.is_some() {
3941 let shading_box = self.gstate.fill_shading_pattern.clone().unwrap();
3942 let row_bytes = width.div_ceil(8);
3943 let mut gray = vec![0u8; (width * height) as usize];
3944 for y in 0..height {
3945 for x in 0..width {
3946 let byte_idx = (y * row_bytes + x / 8) as usize;
3947 let bit_idx = 7 - (x % 8);
3948 let bit = if byte_idx < sample_data.len() {
3949 (sample_data[byte_idx] >> bit_idx) & 1
3950 } else {
3951 0
3952 };
3953 let painted = if polarity { bit == 1 } else { bit == 0 };
3954 gray[(y * width + x) as usize] = if painted { 255 } else { 0 };
3955 }
3956 }
3957
3958 let mut mask_dl = DisplayList::new();
3959 mask_dl.push(DisplayElement::Image {
3960 sample_data: Arc::new(gray),
3961 params: ImageParams {
3962 width,
3963 height,
3964 color_space: ImageColorSpace::DeviceGray,
3965 bits_per_component: 8,
3966 ctm: self.gstate.ctm,
3967 image_matrix,
3968 interpolate: false,
3969 mask_color: None,
3970 alpha: 1.0,
3971 blend_mode: 0,
3972 overprint: false,
3973 overprint_mode: 0,
3974 opm_paired: false,
3975 painted_channels: 0,
3976 alpha_is_shape: false,
3977 rendering_intent: 0,
3978 },
3979 });
3980
3981 let mut content_dl = DisplayList::new();
3982 for elem in shading_box.0.elements() {
3983 content_dl.push(elem.clone());
3984 }
3985
3986 let corners = [
3987 self.gstate.ctm.transform_point(0.0, 0.0),
3988 self.gstate.ctm.transform_point(width as f64, 0.0),
3989 self.gstate.ctm.transform_point(0.0, height as f64),
3990 self.gstate.ctm.transform_point(width as f64, height as f64),
3991 ];
3992 let x_min = corners.iter().map(|c| c.0).fold(f64::INFINITY, f64::min);
3993 let y_min = corners.iter().map(|c| c.1).fold(f64::INFINITY, f64::min);
3994 let x_max = corners
3995 .iter()
3996 .map(|c| c.0)
3997 .fold(f64::NEG_INFINITY, f64::max);
3998 let y_max = corners
3999 .iter()
4000 .map(|c| c.1)
4001 .fold(f64::NEG_INFINITY, f64::max);
4002
4003 let parent_clip_bbox = self.current_clip_bbox();
4004 self.display_list.push(DisplayElement::SoftMasked {
4005 mask: mask_dl,
4006 content: content_dl,
4007 params: SoftMaskParams {
4008 subtype: SoftMaskSubtype::Luminosity,
4009 bbox: [x_min, y_min, x_max, y_max],
4010 backdrop_color: None,
4011 transfer_invert: false,
4012 has_nested_mask_scope: false,
4013 parent_clip_bbox,
4014 },
4015 mask_cache: Arc::new(Mutex::new(None)),
4016 });
4017 return Ok(());
4018 }
4019
4020 if is_image_mask
4026 && self.gstate.overprint
4027 && self.gstate.overprint_mode == 1
4028 && self.gstate.fill_pattern.is_none()
4029 && self.gstate.fill_shading_pattern.is_none()
4030 && self.gstate.fill_color.native_cmyk == Some((0.0, 0.0, 0.0, 0.0))
4031 {
4032 return Ok(());
4033 }
4034
4035 if is_image_mask && self.gstate.fill_pattern.is_some() {
4042 let pattern = self.gstate.fill_pattern.clone().unwrap();
4043 let row_bytes = width.div_ceil(8);
4044 let mut gray = vec![0u8; (width * height) as usize];
4045 for y in 0..height {
4046 for x in 0..width {
4047 let byte_idx = (y * row_bytes + x / 8) as usize;
4048 let bit_idx = 7 - (x % 8);
4049 let bit = if byte_idx < sample_data.len() {
4050 (sample_data[byte_idx] >> bit_idx) & 1
4051 } else {
4052 0
4053 };
4054 let painted = if polarity { bit == 1 } else { bit == 0 };
4055 gray[(y * width + x) as usize] = if painted { 255 } else { 0 };
4056 }
4057 }
4058
4059 let mut mask_dl = DisplayList::new();
4060 mask_dl.push(DisplayElement::Image {
4061 sample_data: Arc::new(gray),
4062 params: ImageParams {
4063 width,
4064 height,
4065 color_space: ImageColorSpace::DeviceGray,
4066 bits_per_component: 8,
4067 ctm: self.gstate.ctm,
4068 image_matrix,
4069 interpolate: false,
4070 mask_color: None,
4071 alpha: 1.0,
4072 blend_mode: 0,
4073 overprint: false,
4074 overprint_mode: 0,
4075 opm_paired: false,
4076 painted_channels: 0,
4077 alpha_is_shape: false,
4078 rendering_intent: 0,
4079 },
4080 });
4081
4082 let corners = [
4083 self.gstate.ctm.transform_point(0.0, 0.0),
4084 self.gstate.ctm.transform_point(width as f64, 0.0),
4085 self.gstate.ctm.transform_point(0.0, height as f64),
4086 self.gstate.ctm.transform_point(width as f64, height as f64),
4087 ];
4088 let x_min = corners.iter().map(|c| c.0).fold(f64::INFINITY, f64::min);
4089 let y_min = corners.iter().map(|c| c.1).fold(f64::INFINITY, f64::min);
4090 let x_max = corners
4091 .iter()
4092 .map(|c| c.0)
4093 .fold(f64::NEG_INFINITY, f64::max);
4094 let y_max = corners
4095 .iter()
4096 .map(|c| c.1)
4097 .fold(f64::NEG_INFINITY, f64::max);
4098
4099 let pm = &pattern.pattern_matrix;
4102 let mut content_dl = DisplayList::new();
4103 for elem in pattern.tile.elements() {
4104 if let DisplayElement::Image {
4105 sample_data: sd,
4106 params: ip,
4107 } = elem
4108 {
4109 let dev_ctm = pm.multiply(&ip.ctm);
4110 content_dl.push(DisplayElement::Image {
4111 sample_data: sd.clone(),
4112 params: ImageParams {
4113 ctm: dev_ctm,
4114 ..ip.clone()
4115 },
4116 });
4117 }
4118 }
4119
4120 let parent_clip_bbox = self.current_clip_bbox();
4121 self.display_list.push(DisplayElement::SoftMasked {
4122 mask: mask_dl,
4123 content: content_dl,
4124 params: SoftMaskParams {
4125 subtype: SoftMaskSubtype::Luminosity,
4126 bbox: [x_min, y_min, x_max, y_max],
4127 backdrop_color: None,
4128 transfer_invert: false,
4129 has_nested_mask_scope: false,
4130 parent_clip_bbox,
4131 },
4132 mask_cache: Arc::new(Mutex::new(None)),
4133 });
4134 return Ok(());
4135 }
4136
4137 let (color_space, sample_data) = if !is_image_mask
4143 && let Some(ResolvedColorSpace::DeviceN {
4144 names,
4145 alt,
4146 tint_fn: Some(func),
4147 }) = resolved_cs.as_ref()
4148 && names.len() >= 2
4149 && matches!(
4150 alt.as_ref(),
4151 ResolvedColorSpace::DeviceGray | ResolvedColorSpace::DeviceRGB
4152 ) {
4153 let ni = names.len();
4154 let npixels = width as usize * height as usize;
4155 let mut rgba = vec![255u8; npixels * 4];
4156 let mut inputs = vec![0.0f64; ni];
4157 for i in 0..npixels {
4158 let si = i * ni;
4159 for (c, inp) in inputs.iter_mut().enumerate() {
4160 *inp = sample_data.get(si + c).copied().unwrap_or(0) as f64 / 255.0;
4161 }
4162 let out = func.evaluate(&inputs);
4163 let (r, g, b) = color_space::alt_comps_to_rgb_f64(&out, alt);
4164 let pi = i * 4;
4165 rgba[pi] = r;
4166 rgba[pi + 1] = g;
4167 rgba[pi + 2] = b;
4168 }
4169 (ImageColorSpace::PreconvertedRGBA, rgba)
4170 } else if is_image_mask {
4171 (
4172 ImageColorSpace::Mask {
4173 color: self.gstate.fill_color.clone(),
4174 polarity,
4175 spot_color: self.gstate.fill_spot_color.clone(),
4176 },
4177 sample_data,
4178 )
4179 } else if let Some(ref rcs) = resolved_cs {
4180 (to_image_color_space(rcs), sample_data)
4181 } else {
4182 (ImageColorSpace::PreconvertedRGBA, sample_data)
4184 };
4185
4186 let color_space = if !is_image_mask {
4192 if let ImageColorSpace::Indexed { base, .. } = &color_space {
4193 let expected_1comp = (width * height) as usize;
4194 let base_n = base.num_components() as usize;
4195 if sample_data.len() == expected_1comp * base_n && base_n > 1 {
4196 *base.clone()
4197 } else {
4198 color_space
4199 }
4200 } else {
4201 color_space
4202 }
4203 } else {
4204 color_space
4205 };
4206
4207 let interpolate = dict
4208 .get(b"Interpolate")
4209 .and_then(|o| match o {
4210 PdfObj::Bool(b) => Some(*b),
4211 _ => None,
4212 })
4213 .unwrap_or(false);
4214
4215 let (mask_color, explicit_mask_data) = match dict.get(b"Mask") {
4217 Some(PdfObj::Array(arr)) => {
4218 let mc: Vec<u8> = arr
4220 .iter()
4221 .filter_map(|o| o.as_int().map(|n| n as u8))
4222 .collect();
4223 (Some(mc), None)
4224 }
4225 Some(_mask_obj) => {
4226 let mask_alpha = self
4228 .resolve_explicit_mask(dict, width, height)
4229 .unwrap_or(None);
4230 (None, mask_alpha)
4231 }
4232 None => (None, None),
4233 };
4234
4235 let is_jpx = filter_is_jpx;
4239 let is_dct = filter_is_dct;
4240 let is_indexed = matches!(&color_space, ImageColorSpace::Indexed { .. });
4241 let (sample_data, display_bpc) =
4245 if is_image_mask || bpc == 8 || bpc == 0 || is_jpx || is_dct {
4246 (sample_data, if is_dct || is_jpx { 8 } else { bpc })
4247 } else if bpc == 16 {
4248 (sample_data.chunks(2).map(|c| c[0]).collect(), 8)
4250 } else if bpc > 8 {
4251 (sample_data, bpc)
4252 } else {
4253 (
4254 expand_bits_to_bytes(
4255 &sample_data,
4256 bpc,
4257 width,
4258 height,
4259 color_space.num_components(),
4260 is_indexed,
4261 ),
4262 8,
4263 )
4264 };
4265
4266 let sample_data = if !is_image_mask {
4271 if let Some(decode) = dict.get_array(b"Decode") {
4272 let n_comps = color_space.num_components() as usize;
4273 let decode_vals: Vec<f64> = decode.iter().filter_map(|o| o.as_f64()).collect();
4274 if decode_vals.len() >= n_comps * 2 {
4275 let effective_bpc = if is_jpx || is_dct { 8 } else { bpc };
4276 let max_sample = ((1u32 << effective_bpc) - 1) as f64;
4277 let is_default = if is_indexed {
4280 decode_vals.len() == 2
4281 && (decode_vals[0]).abs() < 1e-6
4282 && (decode_vals[1] - max_sample).abs() < 1e-6
4283 } else {
4284 decode_vals.chunks(2).all(|pair| {
4285 pair.len() == 2
4286 && (pair[0] - 0.0).abs() < 1e-6
4287 && (pair[1] - 1.0).abs() < 1e-6
4288 })
4289 };
4290 if !is_default {
4291 let max_val = if is_indexed {
4294 ((1u32 << effective_bpc) - 1) as f64
4295 } else {
4296 255.0f64
4297 };
4298 let mut result = Vec::with_capacity(sample_data.len());
4299 if is_indexed {
4300 let d_min = decode_vals[0];
4302 let d_max = decode_vals[1];
4303 for &sample in sample_data.iter() {
4304 let val = d_min + (sample as f64 / max_val) * (d_max - d_min);
4305 result.push(val.round().clamp(0.0, 255.0) as u8);
4306 }
4307 } else {
4308 for (i, &sample) in sample_data.iter().enumerate() {
4310 let comp = i % n_comps;
4311 let d_min = decode_vals[comp * 2];
4312 let d_max = decode_vals[comp * 2 + 1];
4313 let val = d_min + (sample as f64 / max_val) * (d_max - d_min);
4314 result.push((val.clamp(0.0, 1.0) * 255.0 + 0.5) as u8);
4315 }
4316 }
4317 result
4318 } else {
4319 sample_data
4320 }
4321 } else {
4322 sample_data
4323 }
4324 } else {
4325 sample_data
4326 }
4327 } else {
4328 sample_data
4329 };
4330
4331 let (sample_data, color_space, resolved_cs) = if !is_image_mask {
4337 let was_device_gray = matches!(color_space, ImageColorSpace::DeviceGray);
4338 let (new_cs, new_data) =
4339 self.cmyk_group_promote_image(color_space, sample_data, width, height);
4340 let new_resolved = if was_device_gray && matches!(new_cs, ImageColorSpace::DeviceCMYK) {
4341 Some(ResolvedColorSpace::DeviceCMYK)
4342 } else {
4343 resolved_cs
4344 };
4345 (new_data, new_cs, new_resolved)
4346 } else {
4347 (sample_data, color_space, resolved_cs)
4348 };
4349
4350 if !is_image_mask {
4354 if let Some(ref rcs) = resolved_cs {
4355 register_icc_profile(rcs, &mut self.icc_cache);
4356 }
4357 }
4358
4359 let smask_result = if !is_image_mask {
4366 let dict_smask = self.resolve_smask(dict, width, height)?;
4367 if dict_smask.is_none() {
4369 if let Some(alpha) = smask_in_data_alpha {
4370 Some((alpha, width, height, None))
4371 } else {
4372 None
4373 }
4374 } else {
4375 dict_smask
4376 }
4377 } else {
4378 None
4379 };
4380
4381 let (sample_data, color_space, width, height) =
4385 if let Some((mask_alpha, mw, mh)) = explicit_mask_data {
4386 let (up_data, up_cs) = if let ImageColorSpace::Indexed {
4389 base,
4390 hival,
4391 lookup,
4392 } = &color_space
4393 {
4394 let n_base = base.num_components() as usize;
4395 let n_pixels = (width * height) as usize;
4396 let mut expanded = vec![0u8; n_pixels * n_base];
4397 for i in 0..n_pixels {
4398 let idx = sample_data.get(i).copied().unwrap_or(0) as usize;
4399 let idx = idx.min(*hival as usize);
4400 let offset = idx * n_base;
4401 for c in 0..n_base {
4402 expanded[i * n_base + c] = lookup.get(offset + c).copied().unwrap_or(0);
4403 }
4404 }
4405 (expanded, *base.clone())
4406 } else {
4407 (sample_data, color_space)
4408 };
4409 let (img_data, img_w, img_h) = if mw > width || mh > height {
4410 let upscaled = bilinear_upsample_image(&up_data, width, height, mw, mh, &up_cs);
4412 (upscaled, mw, mh)
4413 } else {
4414 (up_data, width, height)
4415 };
4416 let rgba = merge_rgb_with_smask(
4417 &img_data,
4418 &mask_alpha,
4419 &up_cs,
4420 img_w,
4421 img_h,
4422 Some(&self.icc_cache),
4423 );
4424 (rgba, ImageColorSpace::PreconvertedRGBA, img_w, img_h)
4425 } else {
4426 (sample_data, color_space, width, height)
4427 };
4428
4429 let sample_data = if !is_image_mask && self.gstate.transfer.has_functions() {
4431 let n_comps = color_space.num_components() as usize;
4432 if n_comps >= 3 {
4433 let mut data = sample_data;
4434 apply_transfer_to_image(&mut data, &self.gstate.transfer, n_comps);
4435 data
4436 } else {
4437 sample_data
4438 }
4439 } else {
4440 sample_data
4441 };
4442
4443 let image_matrix =
4445 Matrix::new(width as f64, 0.0, 0.0, -(height as f64), 0.0, height as f64);
4446
4447 let painted_channels_override = if let ImageColorSpace::Indexed {
4455 base,
4456 hival,
4457 lookup,
4458 } = &color_space
4459 {
4460 if matches!(
4461 base.as_ref(),
4462 ImageColorSpace::DeviceCMYK | ImageColorSpace::ICCBased { n: 4, .. }
4463 ) {
4464 let n_entries = (*hival as usize + 1).min(lookup.len() / 4);
4465 let is_k_only = n_entries > 0
4466 && (0..n_entries).all(|i| {
4467 let off = i * 4;
4468 lookup.get(off).copied().unwrap_or(0) == 0
4469 && lookup.get(off + 1).copied().unwrap_or(0) == 0
4470 && lookup.get(off + 2).copied().unwrap_or(0) == 0
4471 });
4472 if is_k_only {
4473 stet_graphics::device::CMYK_K
4474 } else {
4475 stet_graphics::device::CMYK_ALL
4476 }
4477 } else {
4478 resolved_cs
4479 .as_ref()
4480 .map(painted_channels_for_cs)
4481 .unwrap_or(self.gstate.fill_painted_channels)
4482 }
4483 } else {
4484 resolved_cs
4485 .as_ref()
4486 .map(painted_channels_for_cs)
4487 .unwrap_or(self.gstate.fill_painted_channels)
4488 };
4489
4490 let image_params = ImageParams {
4491 width,
4492 height,
4493 color_space,
4494 bits_per_component: display_bpc as u8,
4495 ctm: self.gstate.ctm,
4496 image_matrix,
4497 interpolate,
4498 mask_color,
4499 alpha: self.gstate.fill_alpha,
4500 blend_mode: self.gstate.blend_mode,
4501 overprint: self.gstate.overprint,
4502 overprint_mode: self.gstate.overprint_mode,
4503 opm_paired: self.gstate.opm_paired,
4504 painted_channels: painted_channels_override,
4505 alpha_is_shape: self.gstate.alpha_is_shape,
4506 rendering_intent: image_intent,
4507 };
4508
4509 if let Some((smask_data, mw, mh, matte)) = smask_result {
4513 const MAX_PIXELS: u64 = 16_000_000; let mut target_w = mw.max(width);
4522 let mut target_h = mh.max(height);
4523 if (target_w as u64) * (target_h as u64) > MAX_PIXELS {
4524 let scale = (MAX_PIXELS as f64 / (target_w as f64 * target_h as f64)).sqrt();
4525 target_w = (target_w as f64 * scale).ceil() as u32;
4526 target_h = (target_h as f64 * scale).ceil() as u32;
4527 }
4528 let (sample_data, width, height) = if target_w > width || target_h > height {
4529 let upscaled = bilinear_upsample_image(
4530 &sample_data,
4531 width,
4532 height,
4533 target_w,
4534 target_h,
4535 &image_params.color_space,
4536 );
4537 (upscaled, target_w, target_h)
4538 } else {
4539 (sample_data, width, height)
4540 };
4541
4542 let smask_data = if mw != width || mh != height {
4544 let mut resampled = vec![0u8; (width * height) as usize];
4545 for y in 0..height {
4546 let sy = (y as u64 * mh as u64 / height as u64) as u32;
4547 for x in 0..width {
4548 let sx = (x as u64 * mw as u64 / width as u64) as u32;
4549 resampled[(y * width + x) as usize] = smask_data
4550 .get((sy * mw + sx) as usize)
4551 .copied()
4552 .unwrap_or(0);
4553 }
4554 }
4555 resampled
4556 } else {
4557 smask_data
4558 };
4559
4560 let sample_data = if let Some(ref mc) = matte {
4564 let n_comps = image_params.color_space.num_components() as usize;
4565 if mc.len() >= n_comps && n_comps >= 3 {
4566 let mut out = sample_data;
4567 let pixels = (width * height) as usize;
4568 for i in 0..pixels {
4569 let a = smask_data[i] as f64 / 255.0;
4570 if a > 0.0 && a < 1.0 {
4571 for c in 0..n_comps.min(3) {
4572 let m = (mc[c] * 255.0).clamp(0.0, 255.0);
4573 let premul = out[i * n_comps + c] as f64;
4574 let orig = m + (premul - m) / a;
4575 out[i * n_comps + c] = orig.round().clamp(0.0, 255.0) as u8;
4576 }
4577 }
4578 }
4579 out
4580 } else {
4581 sample_data
4582 }
4583 } else {
4584 sample_data
4585 };
4586
4587 let sample_arc = Arc::new(sample_data);
4589 let smask_arc = Arc::new(smask_data);
4590 if let PdfObj::Ref(obj_num, _) = obj {
4591 self.image_cache.insert(
4592 *obj_num,
4593 CachedImage {
4594 sample_data: Arc::clone(&sample_arc),
4595 width,
4596 height,
4597 color_space: image_params.color_space.clone(),
4598 bits_per_component: image_params.bits_per_component,
4599 interpolate,
4600 mask_color: image_params.mask_color.clone(),
4601 painted_channels: image_params.painted_channels,
4602 smask: Some((Arc::clone(&smask_arc), width, height, matte.clone())),
4603 rendering_intent: image_params.rendering_intent,
4604 },
4605 );
4606 }
4607
4608 let image_matrix =
4609 Matrix::new(width as f64, 0.0, 0.0, -(height as f64), 0.0, height as f64);
4610
4611 let mut mask_dl = DisplayList::new();
4612 mask_dl.push(DisplayElement::Image {
4613 sample_data: smask_arc,
4614 params: ImageParams {
4615 width,
4616 height,
4617 color_space: ImageColorSpace::DeviceGray,
4618 bits_per_component: 8,
4619 ctm: self.gstate.ctm,
4620 image_matrix,
4621 interpolate,
4622 mask_color: None,
4623 alpha: 1.0,
4624 blend_mode: 0,
4625 overprint: false,
4626 overprint_mode: 0,
4627 opm_paired: false,
4628 painted_channels: 0,
4629 alpha_is_shape: false,
4630 rendering_intent: 0,
4631 },
4632 });
4633
4634 let mut content_dl = DisplayList::new();
4635 content_dl.push(DisplayElement::Image {
4636 sample_data: sample_arc,
4637 params: ImageParams {
4638 width,
4639 height,
4640 image_matrix,
4641 ..image_params
4642 },
4643 });
4644
4645 let corners = [
4646 self.gstate.ctm.transform_point(0.0, 0.0),
4647 self.gstate.ctm.transform_point(1.0, 0.0),
4648 self.gstate.ctm.transform_point(0.0, 1.0),
4649 self.gstate.ctm.transform_point(1.0, 1.0),
4650 ];
4651 let x_min = corners.iter().map(|c| c.0).fold(f64::INFINITY, f64::min);
4652 let y_min = corners.iter().map(|c| c.1).fold(f64::INFINITY, f64::min);
4653 let x_max = corners
4654 .iter()
4655 .map(|c| c.0)
4656 .fold(f64::NEG_INFINITY, f64::max);
4657 let y_max = corners
4658 .iter()
4659 .map(|c| c.1)
4660 .fold(f64::NEG_INFINITY, f64::max);
4661
4662 let parent_clip_bbox = self.current_clip_bbox();
4663 self.display_list.push(DisplayElement::SoftMasked {
4664 mask: mask_dl,
4665 content: content_dl,
4666 params: SoftMaskParams {
4667 subtype: SoftMaskSubtype::Luminosity,
4668 bbox: [x_min, y_min, x_max, y_max],
4669 backdrop_color: None,
4670 transfer_invert: false,
4671 has_nested_mask_scope: false,
4672 parent_clip_bbox,
4673 },
4674 mask_cache: Arc::new(Mutex::new(None)),
4675 });
4676 } else {
4677 let sample_arc = Arc::new(sample_data);
4679 if let PdfObj::Ref(obj_num, _) = obj {
4680 self.image_cache.insert(
4681 *obj_num,
4682 CachedImage {
4683 sample_data: Arc::clone(&sample_arc),
4684 width,
4685 height,
4686 color_space: image_params.color_space.clone(),
4687 bits_per_component: image_params.bits_per_component,
4688 interpolate,
4689 mask_color: image_params.mask_color.clone(),
4690 painted_channels: image_params.painted_channels,
4691 smask: None,
4692 rendering_intent: image_params.rendering_intent,
4693 },
4694 );
4695 }
4696
4697 self.display_list.push(DisplayElement::Image {
4698 sample_data: sample_arc,
4699 params: image_params,
4700 });
4701 }
4702 Ok(())
4703 }
4704
4705 #[allow(clippy::too_many_arguments)]
4708 fn emit_cached_image(&mut self, cached: CachedImage) -> Result<(), PdfError> {
4711 let (sample_data, smask, width, height) = (
4712 cached.sample_data,
4713 cached.smask,
4714 cached.width,
4715 cached.height,
4716 );
4717
4718 let image_matrix =
4719 Matrix::new(width as f64, 0.0, 0.0, -(height as f64), 0.0, height as f64);
4720 let image_params = ImageParams {
4721 width,
4722 height,
4723 color_space: cached.color_space,
4724 bits_per_component: cached.bits_per_component,
4725 ctm: self.gstate.ctm,
4726 image_matrix,
4727 interpolate: cached.interpolate,
4728 mask_color: cached.mask_color,
4729 alpha: self.gstate.fill_alpha,
4730 blend_mode: self.gstate.blend_mode,
4731 overprint: self.gstate.overprint,
4732 overprint_mode: self.gstate.overprint_mode,
4733 opm_paired: self.gstate.opm_paired,
4734 painted_channels: cached.painted_channels,
4735 alpha_is_shape: self.gstate.alpha_is_shape,
4736 rendering_intent: cached.rendering_intent,
4737 };
4738
4739 if let Some((smask_data, sw, sh, _matte)) = smask {
4740 let mut mask_dl = DisplayList::new();
4741 mask_dl.push(DisplayElement::Image {
4742 sample_data: smask_data,
4743 params: ImageParams {
4744 width: sw,
4745 height: sh,
4746 color_space: ImageColorSpace::DeviceGray,
4747 bits_per_component: 8,
4748 ctm: self.gstate.ctm,
4749 image_matrix,
4750 interpolate: cached.interpolate,
4751 mask_color: None,
4752 alpha: 1.0,
4753 blend_mode: 0,
4754 overprint: false,
4755 overprint_mode: 0,
4756 opm_paired: false,
4757 painted_channels: 0,
4758 alpha_is_shape: false,
4759 rendering_intent: 0,
4760 },
4761 });
4762
4763 let mut content_dl = DisplayList::new();
4764 content_dl.push(DisplayElement::Image {
4765 sample_data,
4766 params: ImageParams {
4767 width,
4768 height,
4769 image_matrix,
4770 ..image_params
4771 },
4772 });
4773
4774 let corners = [
4775 self.gstate.ctm.transform_point(0.0, 0.0),
4776 self.gstate.ctm.transform_point(1.0, 0.0),
4777 self.gstate.ctm.transform_point(0.0, 1.0),
4778 self.gstate.ctm.transform_point(1.0, 1.0),
4779 ];
4780 let x_min = corners.iter().map(|c| c.0).fold(f64::INFINITY, f64::min);
4781 let y_min = corners.iter().map(|c| c.1).fold(f64::INFINITY, f64::min);
4782 let x_max = corners
4783 .iter()
4784 .map(|c| c.0)
4785 .fold(f64::NEG_INFINITY, f64::max);
4786 let y_max = corners
4787 .iter()
4788 .map(|c| c.1)
4789 .fold(f64::NEG_INFINITY, f64::max);
4790
4791 let parent_clip_bbox = self.current_clip_bbox();
4792 self.display_list.push(DisplayElement::SoftMasked {
4793 mask: mask_dl,
4794 content: content_dl,
4795 params: SoftMaskParams {
4796 subtype: SoftMaskSubtype::Luminosity,
4797 bbox: [x_min, y_min, x_max, y_max],
4798 backdrop_color: None,
4799 transfer_invert: false,
4800 has_nested_mask_scope: false,
4801 parent_clip_bbox,
4802 },
4803 mask_cache: Arc::new(Mutex::new(None)),
4804 });
4805 } else {
4806 self.display_list.push(DisplayElement::Image {
4807 sample_data,
4808 params: image_params,
4809 });
4810 }
4811 Ok(())
4812 }
4813
4814 fn resolve_smask(
4819 &self,
4820 dict: &PdfDict,
4821 image_w: u32,
4822 image_h: u32,
4823 ) -> Result<Option<(Vec<u8>, u32, u32, Option<Vec<f64>>)>, PdfError> {
4824 let smask_ref = match dict.get(b"SMask") {
4825 Some(obj) => obj.clone(),
4826 None => return Ok(None),
4827 };
4828 let smask_obj = self.resolver.deref(&smask_ref)?;
4829 let smask_dict = match smask_obj.as_dict() {
4830 Some(d) => d,
4831 None => return Ok(None),
4832 };
4833 let sw = smask_dict.get_int(b"Width").unwrap_or(image_w as i64) as u32;
4836 let sh = smask_dict.get_int(b"Height").unwrap_or(image_h as i64) as u32;
4837 if sw == 0 || sh == 0 {
4838 return Ok(None);
4839 }
4840 let bpc = smask_dict.get_int(b"BitsPerComponent").unwrap_or(8) as u32;
4841 let data = self.resolver.stream_data_from_obj(&smask_ref)?;
4842
4843 let mut data = if bpc == 8 {
4846 data
4847 } else if bpc == 16 {
4848 data.chunks(2).map(|c| c[0]).collect()
4850 } else if bpc < 8 {
4851 expand_bits_to_bytes(&data, bpc, sw, sh, 1, false)
4852 } else {
4853 data
4854 };
4855
4856 if let Some(decode) = smask_dict.get_array(b"Decode")
4858 && decode.len() >= 2
4859 {
4860 let d0 = decode[0].as_f64().unwrap_or(0.0);
4861 let d1 = decode[1].as_f64().unwrap_or(1.0);
4862 if (d0 - 1.0).abs() < 1e-6 && d1.abs() < 1e-6 {
4863 for b in data.iter_mut() {
4865 *b = 255 - *b;
4866 }
4867 } else if (d0).abs() > 1e-6 || (d1 - 1.0).abs() > 1e-6 {
4868 for b in data.iter_mut() {
4870 let v = d0 + (d1 - d0) * (*b as f64 / 255.0);
4871 *b = (v * 255.0).round().clamp(0.0, 255.0) as u8;
4872 }
4873 }
4874 }
4875
4876 let matte = smask_dict
4878 .get_array(b"Matte")
4879 .map(|arr| arr.iter().filter_map(|o| o.as_f64()).collect::<Vec<_>>());
4880
4881 Ok(Some((data, sw, sh, matte)))
4882 }
4883
4884 fn resolve_explicit_mask(
4890 &self,
4891 dict: &PdfDict,
4892 image_w: u32,
4893 image_h: u32,
4894 ) -> Result<Option<(Vec<u8>, u32, u32)>, PdfError> {
4895 let mask_ref = match dict.get(b"Mask") {
4896 Some(obj) => obj.clone(),
4897 None => return Ok(None),
4898 };
4899 let mask_obj = self.resolver.deref(&mask_ref)?;
4900 let mask_dict = match mask_obj.as_dict() {
4901 Some(d) => d,
4902 None => return Ok(None),
4903 };
4904 let mw = mask_dict.get_int(b"Width").unwrap_or(0) as u32;
4905 let mh = mask_dict.get_int(b"Height").unwrap_or(0) as u32;
4906 if mw == 0 || mh == 0 {
4907 return Ok(None);
4908 }
4909 let mask_data = self.resolver.stream_data_from_obj(&mask_ref)?;
4910
4911 let invert = if let Some(decode) = mask_dict.get_array(b"Decode") {
4913 if decode.len() >= 2 {
4914 let d0 = decode[0].as_f64().unwrap_or(0.0);
4915 d0 > 0.5
4917 } else {
4918 false
4919 }
4920 } else {
4921 false
4922 };
4923
4924 let row_bytes = mw.div_ceil(8);
4926 let mut alpha = vec![0u8; (mw * mh) as usize];
4927 for y in 0..mh {
4928 for x in 0..mw {
4929 let byte_idx = (y * row_bytes + x / 8) as usize;
4930 let bit_idx = 7 - (x % 8);
4931 let bit = if byte_idx < mask_data.len() {
4932 (mask_data[byte_idx] >> bit_idx) & 1
4933 } else {
4934 0
4935 };
4936 let opaque = if invert { bit == 1 } else { bit == 0 };
4939 alpha[(y * mw + x) as usize] = if opaque { 255 } else { 0 };
4940 }
4941 }
4942
4943 if mw == image_w && mh == image_h {
4948 Ok(Some((alpha, mw, mh)))
4949 } else if mw >= image_w && mh >= image_h {
4950 Ok(Some((alpha, mw, mh)))
4952 } else {
4953 let mut resampled = vec![0u8; (image_w * image_h) as usize];
4955 let ratio_x = mw as f32 / image_w as f32;
4956 let ratio_y = mh as f32 / image_h as f32;
4957 for y in 0..image_h {
4958 let top_f = y as f32 * ratio_y;
4959 let bottom_f = (y + 1) as f32 * ratio_y;
4960 let top = (top_f as u32).min(mh - 1);
4961 let bottom = (bottom_f.ceil() as u32).min(mh);
4962 for x in 0..image_w {
4963 let left_f = x as f32 * ratio_x;
4964 let right_f = (x + 1) as f32 * ratio_x;
4965 let left = (left_f as u32).min(mw - 1);
4966 let right = (right_f.ceil() as u32).min(mw);
4967 let mut sum = 0.0f32;
4968 let mut weight = 0.0f32;
4969 for sy in top..bottom {
4970 let py_top = sy as f32;
4971 let py_bot = (sy + 1) as f32;
4972 let wy = py_bot.min(bottom_f) - py_top.max(top_f);
4973 for sx in left..right {
4974 let px_left = sx as f32;
4975 let px_right = (sx + 1) as f32;
4976 let wx = px_right.min(right_f) - px_left.max(left_f);
4977 let w = wx * wy;
4978 sum += alpha[(sy * mw + sx) as usize] as f32 * w;
4979 weight += w;
4980 }
4981 }
4982 resampled[(y * image_w + x) as usize] = if weight > 0.0 {
4983 (sum / weight + 0.5).min(255.0) as u8
4984 } else {
4985 0
4986 };
4987 }
4988 }
4989 Ok(Some((resampled, image_w, image_h)))
4990 }
4991 }
4992
4993 fn is_jpx_rgba(&self, obj: &PdfObj) -> bool {
4996 #[cfg(feature = "jpx")]
4997 {
4998 if let Ok((raw, filters)) = self.resolver.raw_stream_and_filters(obj) {
4999 if filters
5000 .iter()
5001 .any(|f| matches!(f, crate::filters::Filter::JPXDecode))
5002 {
5003 if let Some((color_channels, has_alpha)) = crate::filters::jpx_color_info(&raw)
5004 {
5005 return color_channels == 3 && has_alpha;
5006 }
5007 }
5008 }
5009 }
5010 false
5011 }
5012
5013 fn handle_form_xobject(&mut self, obj: &PdfObj, dict: &PdfDict) -> Result<(), PdfError> {
5015 if self.depth >= 20 {
5016 return Err(PdfError::Other("Form XObject nesting too deep".into()));
5017 }
5018
5019 let form_resources = if let Some(res_obj) = dict.get(b"Resources") {
5021 match self.resolver.deref(res_obj)? {
5022 PdfObj::Dict(d) => d,
5023 _ => self.resources.clone(),
5024 }
5025 } else {
5026 self.resources.clone()
5027 };
5028
5029 let form_matrix = if let Some(vals) = deref_num_array(self.resolver, dict, b"Matrix") {
5031 if vals.len() == 6 {
5032 Matrix::new(vals[0], vals[1], vals[2], vals[3], vals[4], vals[5])
5033 } else {
5034 Matrix::identity()
5035 }
5036 } else {
5037 Matrix::identity()
5038 };
5039
5040 let bbox = if let Some(vals) = deref_num_array(self.resolver, dict, b"BBox") {
5042 if vals.len() == 4 {
5043 Some((vals[0], vals[1], vals[2], vals[3]))
5044 } else {
5045 None
5046 }
5047 } else {
5048 None
5049 };
5050
5051 let is_transparency_group = self.is_transparency_group(dict);
5053
5054 let form_data = self.resolver.stream_data_from_obj(obj)?;
5056
5057 self.gstate_stack.push(self.gstate.clone());
5060 let saved_stack_depth = self.gstate_stack.len();
5061 let saved_resources = std::mem::replace(&mut self.resources, form_resources);
5062 let saved_font_cache = std::mem::take(&mut self.font_cache);
5063 let saved_current_font = self.current_font.take();
5064 let saved_cs_index = self.cs_index.take(); let saved_content_stream_ctm = self.content_stream_ctm;
5066 let saved_mc_stack = std::mem::take(&mut self.mc_stack);
5067 let saved_path = std::mem::take(&mut self.current_path);
5071 let saved_point = self.current_point.take();
5072 let saved_subpath = self.subpath_start.take();
5073
5074 self.gstate.ctm = self.gstate.ctm.concat(&form_matrix);
5076
5077 self.content_stream_ctm = self.gstate.ctm;
5080
5081 if is_transparency_group {
5082 let group_blend_mode = self.gstate.blend_mode;
5085 let group_alpha = self.gstate.fill_alpha;
5086
5087 self.gstate.fill_alpha = 1.0;
5095 self.gstate.stroke_alpha = 1.0;
5096 self.gstate.soft_mask = None;
5097
5098 let mut group_list = DisplayList::new();
5100 std::mem::swap(&mut self.display_list, &mut group_list);
5101
5102 let saved_scope = self.soft_mask_scope.take();
5104
5105 let device_bbox = self.compute_device_bbox(bbox);
5108
5109 if let Some((x0, y0, x1, y1)) = bbox {
5111 self.push_bbox_clip(x0, y0, x1, y1);
5112 }
5113
5114 self.depth += 1;
5116 self.interpret_stream(&form_data)?;
5117 self.depth -= 1;
5118
5119 self.flush_soft_mask();
5121
5122 std::mem::swap(&mut self.display_list, &mut group_list);
5124
5125 self.soft_mask_scope = saved_scope;
5127
5128 let isolated = self.get_group_isolated(dict);
5130 let knockout = self.get_group_knockout(dict);
5131 let color_space = self.get_group_color_space(dict);
5132
5133 self.display_list.push(DisplayElement::Group {
5135 elements: group_list,
5136 params: GroupParams {
5137 bbox: device_bbox,
5138 isolated,
5139 knockout,
5140 blend_mode: group_blend_mode,
5141 alpha: group_alpha,
5142 color_space,
5143 },
5144 });
5145 } else {
5146 if let Some((x0, y0, x1, y1)) = bbox {
5148 self.push_bbox_clip(x0, y0, x1, y1);
5149 }
5150
5151 let saved_cull = self.form_cull_y.take();
5156 if let Some((_x0, y0, _x1, y1)) = bbox {
5157 let form_height = (y1 - y0).abs();
5158 if form_height > 5000.0 {
5160 let ctm = &self.gstate.ctm;
5163 if ctm.b.abs() < 1e-6 && ctm.c.abs() < 1e-6 && ctm.d.abs() > 1e-6 {
5166 let page_h = self.initial_ctm.ty.abs();
5168 let fy0 = (0.0 - ctm.ty) / ctm.d;
5169 let fy1 = (page_h - ctm.ty) / ctm.d;
5170 let (lo, hi) = if fy0 < fy1 { (fy0, fy1) } else { (fy1, fy0) };
5171 self.form_cull_y = Some((lo - 100.0, hi + 100.0));
5173 }
5174 }
5175 }
5176
5177 self.depth += 1;
5178 self.interpret_stream(&form_data)?;
5179 self.depth -= 1;
5180
5181 self.form_cull_y = saved_cull;
5182 }
5183
5184 while self.gstate_stack.len() > saved_stack_depth {
5190 self.gstate_stack.pop();
5191 }
5192
5193 self.resources = saved_resources;
5195 self.font_cache = saved_font_cache;
5196 self.current_font = saved_current_font;
5197 self.cs_index = saved_cs_index;
5198 self.content_stream_ctm = saved_content_stream_ctm;
5199 self.current_path = saved_path;
5200 self.current_point = saved_point;
5201 self.subpath_start = saved_subpath;
5202 self.mc_stack = saved_mc_stack;
5203 if let Some(saved) = self.gstate_stack.pop() {
5204 let old_clip_version = self.gstate.clip_path_version;
5205 self.gstate = saved;
5206 if !is_transparency_group && self.gstate.clip_path_version != old_clip_version {
5208 self.restore_clip_from_stack();
5209 }
5210 }
5211
5212 Ok(())
5213 }
5214
5215 fn is_transparency_group(&self, dict: &PdfDict) -> bool {
5217 let Some(group_obj) = dict.get(b"Group") else {
5218 return false;
5219 };
5220 let group_dict = match self.resolver.deref(group_obj) {
5221 Ok(PdfObj::Dict(d)) => d,
5222 _ => return false,
5223 };
5224 group_dict.get_name(b"S") == Some(b"Transparency")
5225 }
5226
5227 fn get_group_isolated(&self, dict: &PdfDict) -> bool {
5229 let Some(group_obj) = dict.get(b"Group") else {
5230 return false;
5231 };
5232 let group_dict = match self.resolver.deref(group_obj) {
5233 Ok(PdfObj::Dict(d)) => d,
5234 _ => return false,
5235 };
5236 match group_dict.get(b"I") {
5237 Some(PdfObj::Bool(b)) => *b,
5238 _ => false,
5239 }
5240 }
5241
5242 fn get_group_knockout(&self, dict: &PdfDict) -> bool {
5244 let Some(group_obj) = dict.get(b"Group") else {
5245 return false;
5246 };
5247 let group_dict = match self.resolver.deref(group_obj) {
5248 Ok(PdfObj::Dict(d)) => d,
5249 _ => return false,
5250 };
5251 match group_dict.get(b"K") {
5252 Some(PdfObj::Bool(b)) => *b,
5253 _ => false,
5254 }
5255 }
5256
5257 fn get_group_color_space(
5261 &self,
5262 dict: &PdfDict,
5263 ) -> stet_graphics::display_list::GroupColorSpace {
5264 use stet_graphics::display_list::GroupColorSpace;
5265 let Some(group_obj) = dict.get(b"Group") else {
5266 return GroupColorSpace::Inherited;
5267 };
5268 let group_dict = match self.resolver.deref(group_obj) {
5269 Ok(PdfObj::Dict(d)) => d,
5270 _ => return GroupColorSpace::Inherited,
5271 };
5272 let Some(cs_obj) = group_dict.get(b"CS") else {
5273 return GroupColorSpace::Inherited;
5274 };
5275 let cs_obj = match self.resolver.deref(cs_obj) {
5276 Ok(o) => o,
5277 Err(_) => return GroupColorSpace::Inherited,
5278 };
5279 match cs_obj {
5280 PdfObj::Name(n) => match n.as_slice() {
5281 b"DeviceGray" | b"CalGray" | b"G" => GroupColorSpace::DeviceGray,
5282 b"DeviceRGB" | b"CalRGB" | b"RGB" => GroupColorSpace::DeviceRGB,
5283 b"DeviceCMYK" | b"CMYK" => GroupColorSpace::DeviceCMYK,
5284 _ => GroupColorSpace::Inherited,
5285 },
5286 PdfObj::Array(arr) => {
5287 if let Some(PdfObj::Name(name)) = arr.first()
5289 && name.as_slice() == b"ICCBased"
5290 && let Some(stream_obj) = arr.get(1)
5291 {
5292 let stream_obj = match self.resolver.deref(stream_obj) {
5293 Ok(o) => o,
5294 Err(_) => return GroupColorSpace::Inherited,
5295 };
5296 if let PdfObj::Stream {
5297 dict: stream_dict, ..
5298 } = stream_obj
5299 && let Some(n_obj) = stream_dict.get(b"N")
5300 && let Some(n_val) = n_obj.as_int()
5301 {
5302 return match n_val {
5303 1 => GroupColorSpace::DeviceGray,
5304 3 => GroupColorSpace::DeviceRGB,
5305 4 => GroupColorSpace::DeviceCMYK,
5306 _ => GroupColorSpace::Inherited,
5307 };
5308 }
5309 }
5310 GroupColorSpace::Inherited
5311 }
5312 _ => GroupColorSpace::Inherited,
5313 }
5314 }
5315
5316 fn push_bbox_clip(&mut self, x0: f64, y0: f64, x1: f64, y1: f64) {
5318 let p0 = self.gstate.ctm.transform_point(x0, y0);
5319 let p1 = self.gstate.ctm.transform_point(x1, y0);
5320 let p2 = self.gstate.ctm.transform_point(x1, y1);
5321 let p3 = self.gstate.ctm.transform_point(x0, y1);
5322 let mut clip_path = PsPath::new();
5323 clip_path.segments.push(PathSegment::MoveTo(p0.0, p0.1));
5324 clip_path.segments.push(PathSegment::LineTo(p1.0, p1.1));
5325 clip_path.segments.push(PathSegment::LineTo(p2.0, p2.1));
5326 clip_path.segments.push(PathSegment::LineTo(p3.0, p3.1));
5327 clip_path.segments.push(PathSegment::ClosePath);
5328 self.display_list.push(DisplayElement::Clip {
5329 path: clip_path.clone(),
5330 params: ClipParams {
5331 fill_rule: FillRule::NonZeroWinding,
5332 ctm: Matrix::identity(),
5333 stroke_params: None,
5334 },
5335 });
5336 self.gstate
5337 .clip_stack
5338 .push((clip_path.clone(), FillRule::NonZeroWinding));
5339 self.gstate.clip_path = Some(clip_path);
5340 self.gstate.clip_path_version += 1;
5341 }
5342
5343 fn current_clip_bbox(&self) -> Option<[f64; 4]> {
5349 let path = self.gstate.clip_path.as_ref()?;
5350 let mut x_min = f64::INFINITY;
5351 let mut y_min = f64::INFINITY;
5352 let mut x_max = f64::NEG_INFINITY;
5353 let mut y_max = f64::NEG_INFINITY;
5354 for seg in &path.segments {
5355 let pts: &[(f64, f64)] = match seg {
5356 PathSegment::MoveTo(x, y) | PathSegment::LineTo(x, y) => &[(*x, *y)],
5357 PathSegment::CurveTo {
5358 x1,
5359 y1,
5360 x2,
5361 y2,
5362 x3,
5363 y3,
5364 } => &[(*x1, *y1), (*x2, *y2), (*x3, *y3)][..],
5365 PathSegment::ClosePath => &[],
5366 };
5367 for (x, y) in pts {
5368 x_min = x_min.min(*x);
5369 y_min = y_min.min(*y);
5370 x_max = x_max.max(*x);
5371 y_max = y_max.max(*y);
5372 }
5373 }
5374 if x_min.is_finite() && x_min < x_max && y_min < y_max {
5375 Some([x_min, y_min, x_max, y_max])
5376 } else {
5377 None
5378 }
5379 }
5380
5381 fn compute_device_bbox(&self, bbox: Option<(f64, f64, f64, f64)>) -> [f64; 4] {
5382 let Some((x0, y0, x1, y1)) = bbox else {
5383 return [0.0, 0.0, 1e9, 1e9];
5385 };
5386 let corners = [
5387 self.gstate.ctm.transform_point(x0, y0),
5388 self.gstate.ctm.transform_point(x1, y0),
5389 self.gstate.ctm.transform_point(x0, y1),
5390 self.gstate.ctm.transform_point(x1, y1),
5391 ];
5392 let mut min_x = f64::INFINITY;
5393 let mut min_y = f64::INFINITY;
5394 let mut max_x = f64::NEG_INFINITY;
5395 let mut max_y = f64::NEG_INFINITY;
5396 for (cx, cy) in &corners {
5397 min_x = min_x.min(*cx);
5398 min_y = min_y.min(*cy);
5399 max_x = max_x.max(*cx);
5400 max_y = max_y.max(*cy);
5401 }
5402 [min_x, min_y, max_x, max_y]
5403 }
5404
5405 fn handle_inline_image(&mut self, lexer: &mut Lexer) -> Result<(), PdfError> {
5407 let mut dict = PdfDict::new();
5409 loop {
5410 let tok = lexer.next_token()?;
5411 match tok {
5412 Token::Keyword(ref kw) if kw == b"ID" => break,
5413 Token::Eof => return Ok(()),
5414 Token::Name(key) => {
5415 let expanded_key = expand_inline_key(&key);
5416 let val_tok = lexer.next_token()?;
5417 let val = match val_tok {
5418 Token::Int(n) => PdfObj::Int(n),
5419 Token::Real(f) => PdfObj::Real(f),
5420 Token::Name(n) => PdfObj::Name(expand_inline_value(&n)),
5421 Token::Bool(b) => PdfObj::Bool(b),
5422 Token::LitString(s) | Token::HexString(s) => PdfObj::Str(s),
5423 Token::ArrayBegin => {
5424 let arr = Self::parse_inline_array(lexer)?;
5425 PdfObj::Array(arr)
5426 }
5427 Token::DictBegin => crate::lexer::parse_dict_body(lexer)
5428 .map(PdfObj::Dict)
5429 .unwrap_or(PdfObj::Null),
5430 _ => PdfObj::Null,
5431 };
5432 if dict.get(&expanded_key).is_none() {
5435 dict.insert(expanded_key, val);
5436 }
5437 }
5438 _ => {}
5439 }
5440 }
5441
5442 let data = lexer.data();
5446 let mut pos = lexer.pos();
5447 if pos < data.len() {
5448 if data[pos] == b'\r' {
5449 pos += 1;
5450 if pos < data.len() && data[pos] == b'\n' {
5451 pos += 1;
5452 }
5453 } else if data[pos] == b' ' || data[pos] == b'\n' {
5454 pos += 1;
5455 }
5456 }
5457
5458 let width = dict.get_int(b"Width").unwrap_or(0) as u32;
5460 let height = dict.get_int(b"Height").unwrap_or(0) as u32;
5461 let is_image_mask = matches!(dict.get(b"ImageMask"), Some(PdfObj::Bool(true)));
5462 let bpc = if is_image_mask {
5463 1
5464 } else {
5465 dict.get_int(b"BitsPerComponent").unwrap_or(8) as u32
5466 };
5467
5468 let has_filter = dict.get(b"Filter").is_some() || dict.get(b"F").is_some();
5469
5470 let outermost_is_ascii85 = dict
5474 .get(b"Filter")
5475 .or_else(|| dict.get(b"F"))
5476 .map(|f| match f {
5477 PdfObj::Name(n) => n == b"ASCII85Decode" || n == b"A85",
5478 PdfObj::Array(arr) => arr
5479 .first()
5480 .and_then(|o| o.as_name())
5481 .map(|n| n == b"ASCII85Decode" || n == b"A85")
5482 .unwrap_or(false),
5483 _ => false,
5484 })
5485 .unwrap_or(false);
5486
5487 let resolved_cs = if is_image_mask {
5488 None
5489 } else if let Some(cs_obj) = dict.get(b"ColorSpace") {
5490 let cs_resolved = if let PdfObj::Name(name) = cs_obj {
5493 let from_cache = self
5496 .cs_index
5497 .as_ref()
5498 .and_then(|idx| idx.get(name.as_slice()).cloned());
5499 let res_obj = from_cache.or_else(|| {
5500 self.resolve_resource_subdict(b"ColorSpace")
5501 .and_then(|d| d.get(name).cloned())
5502 });
5503 if let Some(ref obj) = res_obj {
5504 resolve_color_space_obj(obj, self.resolver)
5505 } else {
5506 resolve_color_space_obj(cs_obj, self.resolver)
5507 }
5508 } else {
5509 resolve_color_space_obj(cs_obj, self.resolver)
5510 };
5511 match cs_resolved {
5512 Ok(resolved) => Some(resolved),
5513 Err(_) => Some(ResolvedColorSpace::DeviceGray),
5514 }
5515 } else {
5516 Some(ResolvedColorSpace::DeviceGray)
5517 };
5518 let n_components = resolved_cs
5519 .as_ref()
5520 .map(|cs| cs.num_components() as u32)
5521 .unwrap_or(1);
5522
5523 let row_bits = width * n_components.max(1) * bpc;
5525 let row_bytes = row_bits.div_ceil(8);
5526 let expected_len = (row_bytes * height) as usize;
5527
5528 let start = pos;
5532 let search_from = if has_filter {
5533 start
5534 } else {
5535 start + expected_len
5536 };
5537 let mut end = search_from;
5540 let mut found_no_ws = false;
5541 if !has_filter {
5542 for offset in [
5544 expected_len.saturating_sub(2),
5545 expected_len.saturating_sub(1),
5546 expected_len,
5547 ] {
5548 let p = start + offset;
5549 if p + 1 < data.len()
5550 && data[p] == b'E'
5551 && data[p + 1] == b'I'
5552 && (p + 2 >= data.len() || is_delimiter_or_ws(data[p + 2]))
5553 {
5554 end = p;
5555 found_no_ws = true;
5556 break;
5557 }
5558 }
5559 }
5560 if !found_no_ws {
5561 if outermost_is_ascii85 {
5562 let mut found_a85_end = false;
5565 let mut scan = search_from;
5566 while scan + 1 < data.len() {
5567 if data[scan] == b'~' {
5568 if data[scan + 1] == b'>' {
5569 end = scan + 2;
5571 } else if is_whitespace_byte(data[scan + 1]) {
5572 let mut probe = scan + 1;
5575 while probe < data.len() && is_whitespace_byte(data[probe]) {
5576 probe += 1;
5577 }
5578 if probe + 1 < data.len()
5579 && data[probe] == b'E'
5580 && data[probe + 1] == b'I'
5581 {
5582 end = scan + 1;
5583 } else {
5584 scan += 1;
5585 continue;
5586 }
5587 } else {
5588 scan += 1;
5589 continue;
5590 }
5591 while end < data.len() && is_whitespace_byte(data[end]) {
5592 end += 1;
5593 }
5594 found_a85_end = true;
5596 found_no_ws = true;
5597 break;
5598 }
5599 scan += 1;
5600 }
5601 if !found_a85_end {
5602 while end + 2 < data.len() {
5604 if is_whitespace_byte(data[end])
5605 && data[end + 1] == b'E'
5606 && data[end + 2] == b'I'
5607 && (end + 3 >= data.len() || is_delimiter_or_ws(data[end + 3]))
5608 {
5609 break;
5610 }
5611 end += 1;
5612 }
5613 }
5614 } else {
5615 while end + 2 < data.len() {
5616 if is_whitespace_byte(data[end])
5617 && data[end + 1] == b'E'
5618 && data[end + 2] == b'I'
5619 && (end + 3 >= data.len() || is_delimiter_or_ws(data[end + 3]))
5620 {
5621 break;
5622 }
5623 end += 1;
5624 }
5625 }
5626 }
5627
5628 let sample_data = data[start..end.min(data.len())].to_vec();
5629 let skip_past = if found_no_ws {
5631 (end + 3).min(data.len())
5633 } else {
5634 (end + 4).min(data.len())
5636 };
5637 lexer.set_pos(skip_past);
5638
5639 let sample_data = if has_filter {
5641 match crate::filters::parse_filters(&dict, Some(self.resolver)) {
5642 Ok((filters, parms)) if !filters.is_empty() => {
5643 crate::filters::decode_stream(&sample_data, &filters, &parms, None)
5644 .unwrap_or(sample_data)
5645 }
5646 _ => sample_data,
5647 }
5648 } else {
5649 sample_data
5650 };
5651
5652 let polarity = if is_image_mask {
5654 if let Some(arr) = dict.get_array(b"Decode") {
5655 let vals: Vec<f64> = arr.iter().filter_map(|o| o.as_f64()).collect();
5656 vals.len() >= 2 && vals[0] > 0.5
5657 } else {
5658 false
5659 }
5660 } else {
5661 false
5662 };
5663
5664 let image_matrix =
5665 Matrix::new(width as f64, 0.0, 0.0, -(height as f64), 0.0, height as f64);
5666
5667 if is_image_mask && self.gstate.fill_shading_pattern.is_some() {
5669 let shading_box = self.gstate.fill_shading_pattern.clone().unwrap();
5670
5671 let row_bytes = width.div_ceil(8);
5674 let mut gray = vec![0u8; (width * height) as usize];
5675 for y in 0..height {
5676 for x in 0..width {
5677 let byte_idx = (y * row_bytes + x / 8) as usize;
5678 let bit_idx = 7 - (x % 8);
5679 let bit = if byte_idx < sample_data.len() {
5680 (sample_data[byte_idx] >> bit_idx) & 1
5681 } else {
5682 0
5683 };
5684 let painted = if polarity { bit == 1 } else { bit == 0 };
5687 gray[(y * width + x) as usize] = if painted { 255 } else { 0 };
5688 }
5689 }
5690
5691 let mut mask_dl = DisplayList::new();
5693 mask_dl.push(DisplayElement::Image {
5694 sample_data: Arc::new(gray),
5695 params: ImageParams {
5696 width,
5697 height,
5698 color_space: ImageColorSpace::DeviceGray,
5699 bits_per_component: 8,
5700 ctm: self.gstate.ctm,
5701 image_matrix,
5702 interpolate: false,
5703 mask_color: None,
5704 alpha: 1.0,
5705 blend_mode: 0,
5706 overprint: false,
5707 overprint_mode: 0,
5708 opm_paired: false,
5709 painted_channels: 0,
5710 alpha_is_shape: false,
5711 rendering_intent: 0,
5712 },
5713 });
5714
5715 let mut content_dl = DisplayList::new();
5717 for elem in shading_box.0.elements() {
5718 content_dl.push(elem.clone());
5719 }
5720
5721 let corners = [
5723 self.gstate.ctm.transform_point(0.0, 0.0),
5724 self.gstate.ctm.transform_point(width as f64, 0.0),
5725 self.gstate.ctm.transform_point(0.0, height as f64),
5726 self.gstate.ctm.transform_point(width as f64, height as f64),
5727 ];
5728 let x_min = corners.iter().map(|c| c.0).fold(f64::INFINITY, f64::min);
5729 let y_min = corners.iter().map(|c| c.1).fold(f64::INFINITY, f64::min);
5730 let x_max = corners
5731 .iter()
5732 .map(|c| c.0)
5733 .fold(f64::NEG_INFINITY, f64::max);
5734 let y_max = corners
5735 .iter()
5736 .map(|c| c.1)
5737 .fold(f64::NEG_INFINITY, f64::max);
5738
5739 let parent_clip_bbox = self.current_clip_bbox();
5740 self.display_list.push(DisplayElement::SoftMasked {
5741 mask: mask_dl,
5742 content: content_dl,
5743 params: SoftMaskParams {
5744 subtype: SoftMaskSubtype::Luminosity,
5745 bbox: [x_min, y_min, x_max, y_max],
5746 backdrop_color: None,
5747 transfer_invert: false,
5748 has_nested_mask_scope: false,
5749 parent_clip_bbox,
5750 },
5751 mask_cache: Arc::new(Mutex::new(None)),
5752 });
5753 return Ok(());
5754 }
5755
5756 let color_space = if is_image_mask {
5757 ImageColorSpace::Mask {
5758 color: self.gstate.fill_color.clone(),
5759 polarity,
5760 spot_color: self.gstate.fill_spot_color.clone(),
5761 }
5762 } else {
5763 to_image_color_space(resolved_cs.as_ref().unwrap())
5764 };
5765
5766 let is_indexed = matches!(&color_space, ImageColorSpace::Indexed { .. });
5768 let sample_data = if !is_image_mask && bpc != 8 && bpc != 0 {
5769 expand_bits_to_bytes(&sample_data, bpc, width, height, n_components, is_indexed)
5770 } else {
5771 sample_data
5772 };
5773
5774 let (color_space, sample_data) = if !is_image_mask {
5778 self.cmyk_group_promote_image(color_space, sample_data, width, height)
5779 } else {
5780 (color_space, sample_data)
5781 };
5782
5783 if !is_image_mask {
5786 if let Some(ref rcs) = resolved_cs {
5787 register_icc_profile(rcs, &mut self.icc_cache);
5788 }
5789 }
5790
5791 self.display_list.push(DisplayElement::Image {
5792 sample_data: Arc::new(sample_data),
5793 params: ImageParams {
5794 width,
5795 height,
5796 color_space,
5797 bits_per_component: 8,
5798 ctm: self.gstate.ctm,
5799 image_matrix,
5800 interpolate: false,
5801 mask_color: None,
5802 alpha: self.gstate.fill_alpha,
5803 blend_mode: self.gstate.blend_mode,
5804 overprint: self.gstate.overprint,
5805 overprint_mode: self.gstate.overprint_mode,
5806 opm_paired: self.gstate.opm_paired,
5807 painted_channels: resolved_cs
5808 .as_ref()
5809 .map(painted_channels_for_cs)
5810 .unwrap_or(self.gstate.fill_painted_channels),
5811 alpha_is_shape: self.gstate.alpha_is_shape,
5812 rendering_intent: 0,
5813 },
5814 });
5815
5816 Ok(())
5817 }
5818
5819 fn apply_ext_gstate(&mut self, name: &[u8]) -> Result<(), PdfError> {
5821 let ext_dict = self
5822 .resolve_resource_subdict(b"ExtGState")
5823 .ok_or(PdfError::Other("no ExtGState resources".into()))?;
5824 let gs_ref = ext_dict.get(name).ok_or_else(|| {
5825 PdfError::Other(format!(
5826 "ExtGState /{} not found",
5827 String::from_utf8_lossy(name)
5828 ))
5829 })?;
5830 let gs_obj = self.resolver.deref(gs_ref)?;
5831 let gs_dict = gs_obj
5832 .as_dict()
5833 .ok_or(PdfError::Other("ExtGState is not a dict".into()))?;
5834
5835 if let Some(lw) = gs_dict.get_f64(b"LW") {
5837 self.gstate.line_width = lw;
5838 }
5839 if let Some(lc) = gs_dict.get_int(b"LC")
5840 && let Some(cap) = LineCap::from_i32(lc as i32)
5841 {
5842 self.gstate.line_cap = cap;
5843 }
5844 if let Some(lj) = gs_dict.get_int(b"LJ")
5845 && let Some(join) = LineJoin::from_i32(lj as i32)
5846 {
5847 self.gstate.line_join = join;
5848 }
5849 if let Some(ml) = gs_dict.get_f64(b"ML") {
5850 self.gstate.miter_limit = ml;
5851 }
5852 if let Some(fl) = gs_dict.get_f64(b"FL") {
5853 self.gstate.flatness = fl;
5854 }
5855 if let Some(PdfObj::Bool(sa)) = gs_dict.get(b"SA") {
5856 self.gstate.stroke_adjust = *sa;
5857 }
5858 let has_opm = gs_dict.get(b"OPM").is_some();
5861 if let Some(opm) = gs_dict.get_int(b"OPM") {
5862 self.gstate.overprint_mode = opm as i32;
5863 }
5864 let has_op_flag = gs_dict.get(b"OP").is_some() || gs_dict.get(b"op").is_some();
5865 if self.overprint_enabled {
5866 if let Some(PdfObj::Bool(op)) = gs_dict.get(b"OP") {
5867 self.gstate.overprint = *op;
5868 self.gstate.overprint_stroke = *op;
5870 }
5871 if let Some(PdfObj::Bool(op)) = gs_dict.get(b"op") {
5872 self.gstate.overprint = *op;
5873 }
5874 }
5875 let has_op_upper = gs_dict.get(b"OP").is_some();
5888 let has_op_lower = gs_dict.get(b"op").is_some();
5889 let strict_signal = (has_opm && has_op_flag) || (has_op_upper && has_op_lower);
5890 if strict_signal {
5891 self.gstate.opm_paired = true;
5892 } else if has_opm || has_op_flag {
5893 self.gstate.opm_paired = false;
5894 }
5895 if let Some(ca) = gs_dict.get_f64(b"CA") {
5896 self.gstate.stroke_alpha = ca;
5897 }
5898 if let Some(ca) = gs_dict.get_f64(b"ca") {
5899 self.gstate.fill_alpha = ca;
5900 }
5901 if let Some(b) = gs_dict.get_bool(b"AIS") {
5902 self.gstate.alpha_is_shape = b;
5903 }
5904 if let Some(b) = gs_dict.get_bool(b"TK") {
5905 self.gstate.text_knockout = b;
5906 }
5907 if let Some(PdfObj::Name(ri)) = gs_dict.get(b"RI") {
5909 self.gstate.rendering_intent = match ri.as_slice() {
5910 b"Perceptual" => 0,
5911 b"RelativeColorimetric" => 1,
5912 b"Saturation" => 2,
5913 b"AbsoluteColorimetric" => 3,
5914 _ => 0,
5915 };
5916 }
5917
5918 if let Some(bm) = gs_dict.get(b"BM") {
5920 let bm = self.resolver.deref(bm).unwrap_or_else(|_| bm.clone());
5921 match &bm {
5922 PdfObj::Name(name) => {
5923 self.gstate.blend_mode = blend_mode_from_name(name);
5924 }
5925 PdfObj::Array(arr) => {
5926 for obj in arr {
5927 if let PdfObj::Name(name) = obj {
5928 let mode = blend_mode_from_name(name);
5929 if mode != 0 || name.as_slice() == b"Normal" {
5930 self.gstate.blend_mode = mode;
5931 break;
5932 }
5933 }
5934 }
5935 }
5936 _ => {}
5937 }
5938 }
5939
5940 if let Some(d_arr) = gs_dict.get_array(b"D")
5942 && d_arr.len() == 2
5943 && let (Some(arr), Some(offset)) = (d_arr[0].as_array(), d_arr[1].as_f64())
5944 {
5945 let array: Vec<f64> = arr.iter().filter_map(|o| o.as_f64()).collect();
5946 self.gstate.dash_pattern = DashPattern { array, offset };
5947 }
5948
5949 if let Some(font_arr) = gs_dict.get_array(b"Font")
5951 && font_arr.len() == 2
5952 && let Some(size) = font_arr[1].as_f64()
5953 {
5954 self.gstate.font_size = size;
5955 let font_ref = &font_arr[0];
5957 let cache_key = if let PdfObj::Ref(obj_num, _) = font_ref {
5959 format!("__gs_font_{obj_num}").into_bytes()
5960 } else {
5961 b"__gs_font_inline".to_vec()
5962 };
5963 if let Some(cached) = self.font_cache.get(&cache_key) {
5964 self.current_font = Some(Arc::clone(cached));
5965 } else {
5966 match font::resolve_font(self.resolver, font_ref, self.font_provider.as_ref()) {
5967 Ok(font) => {
5968 let arc = Arc::new(font);
5969 self.font_cache.insert(cache_key, Arc::clone(&arc));
5970 self.current_font = Some(arc);
5971 }
5972 Err(e) => {
5973 eprintln!("warning: ExtGState Font: {e}");
5974 }
5975 }
5976 }
5977 }
5978
5979 if let Some(tr_obj) = gs_dict.get(b"TR2").or_else(|| gs_dict.get(b"TR")) {
5981 self.gstate.transfer = self.parse_transfer_function(tr_obj)?;
5982 }
5983
5984 if let Some(smask_obj) = gs_dict.get(b"SMask") {
5986 let smask_obj = self.resolver.deref(smask_obj)?;
5987 match &smask_obj {
5988 PdfObj::Name(n) if n.as_slice() == b"None" => {
5989 self.flush_soft_mask();
5990 self.gstate.soft_mask = None;
5991 }
5992 PdfObj::Dict(d) => {
5993 self.flush_soft_mask();
5994 match self.resolve_soft_mask(d) {
5995 Ok(sm) => {
5996 let start_index = self.display_list.len();
5997 self.gstate.soft_mask = Some(sm.clone());
5998 self.gstate.smask_gen += 1;
5999 self.soft_mask_scope = Some(SoftMaskScope {
6000 start_index,
6001 mask: sm,
6002 });
6003 }
6004 Err(e) => {
6005 eprintln!("warning: SMask resolve error: {}", e);
6006 }
6007 }
6008 }
6009 _ => {}
6010 }
6011 }
6012
6013 Ok(())
6014 }
6015
6016 fn flush_soft_mask(&mut self) {
6018 if let Some(scope) = self.soft_mask_scope.take()
6019 && self.display_list.len() > scope.start_index
6020 {
6021 let content = self.display_list.split_off(scope.start_index);
6022
6023 let content_bbox = self.content_paint_bbox(&content);
6061 let drop_shadow_skip = scope.mask.backdrop_color == Some([0.0, 0.0, 0.0])
6062 && content_bbox
6063 .map(|c| !bboxes_overlap_substantially(&c, &scope.mask.bbox, 2.0))
6064 .unwrap_or(false);
6065 let skip = scope.mask.mask_list.is_empty() || drop_shadow_skip;
6066 if skip {
6067 for elem in content.into_elements() {
6068 self.display_list.push(elem);
6069 }
6070 } else {
6071 let clip_replay: Vec<DisplayElement> = content
6076 .elements()
6077 .iter()
6078 .filter(|e| matches!(e, DisplayElement::Clip { .. } | DisplayElement::InitClip))
6079 .cloned()
6080 .collect();
6081 let parent_clip_bbox = self.current_clip_bbox();
6082 self.display_list.push(DisplayElement::SoftMasked {
6083 mask: scope.mask.mask_list,
6084 content,
6085 params: SoftMaskParams {
6086 subtype: scope.mask.subtype,
6087 bbox: scope.mask.bbox,
6088 backdrop_color: scope.mask.backdrop_color,
6089 transfer_invert: scope.mask.transfer_invert,
6090 has_nested_mask_scope: scope.mask.has_nested_mask_scope,
6091 parent_clip_bbox,
6092 },
6093 mask_cache: Arc::new(Mutex::new(None)),
6094 });
6095 for elem in clip_replay {
6096 self.display_list.push(elem);
6097 }
6098 }
6099 }
6100 }
6101
6102 fn resolve_group_cs_comps(&self, form_dict: &PdfDict) -> usize {
6105 let cs_name_to_comps = |cs: &[u8]| -> usize {
6106 match cs {
6107 b"DeviceGray" => 1,
6108 b"DeviceRGB" => 3,
6109 b"DeviceCMYK" => 4,
6110 _ => 0,
6111 }
6112 };
6113
6114 let grp_obj = match form_dict.get(b"Group") {
6115 Some(obj) => obj,
6116 None => return 0,
6117 };
6118
6119 let resolved_grp;
6121 let grp = if let Some(d) = grp_obj.as_dict() {
6122 d
6123 } else if let Ok(r) = self.resolver.deref(grp_obj) {
6124 resolved_grp = r;
6125 match resolved_grp.as_dict() {
6126 Some(d) => d,
6127 None => return 0,
6128 }
6129 } else {
6130 return 0;
6131 };
6132
6133 if let Some(cs) = grp.get_name(b"CS") {
6135 return cs_name_to_comps(cs);
6136 }
6137 if let Some(cs_obj) = grp.get(b"CS") {
6138 if let Ok(cs_resolved) = self.resolver.deref(cs_obj) {
6139 if let Some(cs) = cs_resolved.as_name() {
6140 return cs_name_to_comps(cs);
6141 }
6142 }
6143 }
6144 0
6145 }
6146
6147 fn content_paint_bbox(&self, content: &DisplayList) -> Option<[f64; 4]> {
6158 let mut x_min = f64::INFINITY;
6159 let mut y_min = f64::INFINITY;
6160 let mut x_max = f64::NEG_INFINITY;
6161 let mut y_max = f64::NEG_INFINITY;
6162 let mut grow = |bx: [f64; 4]| {
6163 x_min = x_min.min(bx[0].min(bx[2]));
6164 y_min = y_min.min(bx[1].min(bx[3]));
6165 x_max = x_max.max(bx[0].max(bx[2]));
6166 y_max = y_max.max(bx[1].max(bx[3]));
6167 };
6168 for elem in content.elements() {
6169 match elem {
6170 DisplayElement::Fill { path, .. }
6171 | DisplayElement::Stroke { path, .. }
6172 | DisplayElement::Clip { path, .. } => {
6173 let mut px_min = f64::INFINITY;
6174 let mut py_min = f64::INFINITY;
6175 let mut px_max = f64::NEG_INFINITY;
6176 let mut py_max = f64::NEG_INFINITY;
6177 for seg in &path.segments {
6178 let pts: &[(f64, f64)] = match seg {
6179 PathSegment::MoveTo(x, y) | PathSegment::LineTo(x, y) => &[(*x, *y)],
6180 PathSegment::CurveTo {
6181 x1,
6182 y1,
6183 x2,
6184 y2,
6185 x3,
6186 y3,
6187 } => &[(*x1, *y1), (*x2, *y2), (*x3, *y3)][..],
6188 PathSegment::ClosePath => &[],
6189 };
6190 for (x, y) in pts {
6191 px_min = px_min.min(*x);
6192 py_min = py_min.min(*y);
6193 px_max = px_max.max(*x);
6194 py_max = py_max.max(*y);
6195 }
6196 }
6197 if px_min.is_finite() && px_min < px_max && py_min < py_max {
6198 grow([px_min, py_min, px_max, py_max]);
6199 }
6200 }
6201 DisplayElement::Image { params, .. } => {
6202 let ctm = ¶ms.ctm;
6205 let corners = [
6206 ctm.transform_point(0.0, 0.0),
6207 ctm.transform_point(1.0, 0.0),
6208 ctm.transform_point(0.0, 1.0),
6209 ctm.transform_point(1.0, 1.0),
6210 ];
6211 let mut ix_min = f64::INFINITY;
6212 let mut iy_min = f64::INFINITY;
6213 let mut ix_max = f64::NEG_INFINITY;
6214 let mut iy_max = f64::NEG_INFINITY;
6215 for (cx, cy) in &corners {
6216 ix_min = ix_min.min(*cx);
6217 iy_min = iy_min.min(*cy);
6218 ix_max = ix_max.max(*cx);
6219 iy_max = iy_max.max(*cy);
6220 }
6221 grow([ix_min, iy_min, ix_max, iy_max]);
6222 }
6223 DisplayElement::Group { params, .. } => {
6224 grow(params.bbox);
6225 }
6226 DisplayElement::SoftMasked { params, .. } => {
6227 grow(params.bbox);
6228 }
6229 _ => {} }
6231 }
6232 if x_min.is_finite() && x_min < x_max && y_min < y_max {
6233 Some([x_min, y_min, x_max, y_max])
6234 } else {
6235 None
6236 }
6237 }
6238
6239 fn resolve_soft_mask(&mut self, dict: &PdfDict) -> Result<graphics_state::SoftMask, PdfError> {
6241 let subtype = match dict.get_name(b"S") {
6243 Some(b"Alpha") => SoftMaskSubtype::Alpha,
6244 _ => SoftMaskSubtype::Luminosity,
6245 };
6246
6247 let g_ref = dict
6249 .get(b"G")
6250 .ok_or_else(|| PdfError::Other("SMask missing /G".into()))?;
6251 let g_obj = self.resolver.deref(g_ref)?;
6252 let g_dict = g_obj
6253 .as_dict()
6254 .ok_or_else(|| PdfError::Other("SMask /G is not a dict".into()))?;
6255
6256 let bbox_tuple = if let Some(vals) = deref_num_array(self.resolver, g_dict, b"BBox") {
6258 if vals.len() == 4 {
6259 Some((vals[0], vals[1], vals[2], vals[3]))
6260 } else {
6261 None
6262 }
6263 } else {
6264 None
6265 };
6266
6267 let form_matrix = if let Some(vals) = deref_num_array(self.resolver, g_dict, b"Matrix") {
6269 if vals.len() == 6 {
6270 Matrix::new(vals[0], vals[1], vals[2], vals[3], vals[4], vals[5])
6271 } else {
6272 Matrix::identity()
6273 }
6274 } else {
6275 Matrix::identity()
6276 };
6277
6278 let form_resources = if let Some(res_obj) = g_dict.get(b"Resources") {
6280 match self.resolver.deref(res_obj)? {
6281 PdfObj::Dict(d) => d,
6282 _ => self.resources.clone(),
6283 }
6284 } else {
6285 self.resources.clone()
6286 };
6287
6288 let form_data = self.resolver.stream_data_from_obj(g_ref)?;
6290
6291 self.gstate_stack.push(self.gstate.clone());
6294 let saved_resources = std::mem::replace(&mut self.resources, form_resources);
6295 let saved_font_cache = std::mem::take(&mut self.font_cache);
6296 let saved_current_font2 = self.current_font.take();
6297 let saved_cs_index2 = self.cs_index.take();
6298 let saved_display_list = std::mem::replace(&mut self.display_list, DisplayList::new());
6299 let saved_scope = self.soft_mask_scope.take();
6300 let saved_content_stream_ctm = self.content_stream_ctm;
6301 let saved_mc_stack = std::mem::take(&mut self.mc_stack);
6302
6303 self.gstate.ctm = self.gstate.ctm.concat(&form_matrix);
6305 self.content_stream_ctm = self.gstate.ctm;
6308
6309 self.gstate.fill_alpha = 1.0;
6313 self.gstate.stroke_alpha = 1.0;
6314 self.gstate.soft_mask = None;
6315
6316 let device_bbox = self.compute_device_bbox(bbox_tuple);
6320
6321 if let Some((x0, y0, x1, y1)) = bbox_tuple {
6323 self.push_bbox_clip(x0, y0, x1, y1);
6324 }
6325
6326 let saved_cmyk_hash = self.icc_cache.suspend_default_cmyk();
6332 let saved_in_smask_form = self.in_smask_form;
6341 self.in_smask_form = true;
6342
6343 let saved_nested_mask_flush_count = self.nested_mask_flush_count;
6344 self.depth += 1;
6345 let _ = self.interpret_stream(&form_data);
6346 self.depth -= 1;
6347
6348 self.in_smask_form = saved_in_smask_form;
6349 self.icc_cache.restore_default_cmyk(saved_cmyk_hash);
6350
6351 let has_nested_mask_scope = self.nested_mask_flush_count > saved_nested_mask_flush_count;
6356
6357 self.flush_soft_mask();
6359
6360 let mask_list = std::mem::replace(&mut self.display_list, saved_display_list);
6361 self.soft_mask_scope = saved_scope;
6362 self.content_stream_ctm = saved_content_stream_ctm;
6363 self.resources = saved_resources;
6364 self.font_cache = saved_font_cache;
6365 self.current_font = saved_current_font2;
6366 self.cs_index = saved_cs_index2;
6367 self.mc_stack = saved_mc_stack;
6368 if let Some(saved) = self.gstate_stack.pop() {
6369 self.gstate = saved;
6370 }
6371
6372 let group_n_comps = self.resolve_group_cs_comps(g_dict);
6376
6377 let backdrop_color = if let Some(bc_obj) = dict.get(b"BC") {
6378 let bc_resolved = self.resolver.deref(bc_obj).ok();
6379 let bc_arr = bc_resolved
6380 .as_ref()
6381 .and_then(|o| o.as_array())
6382 .or_else(|| bc_obj.as_array());
6383 if let Some(arr) = bc_arr {
6384 let vals: Vec<f64> = arr.iter().filter_map(|o| o.as_f64()).collect();
6385 if group_n_comps == 1 && !vals.is_empty() {
6386 Some([vals[0], vals[0], vals[0]])
6388 } else if group_n_comps == 4 && vals.len() >= 4 {
6389 let c = vals[0];
6391 let m = vals[1];
6392 let y = vals[2];
6393 let k = vals[3];
6394 Some([
6395 (1.0 - c) * (1.0 - k),
6396 (1.0 - m) * (1.0 - k),
6397 (1.0 - y) * (1.0 - k),
6398 ])
6399 } else if vals.len() >= 3 {
6400 Some([vals[0], vals[1], vals[2]])
6401 } else if vals.len() == 1 {
6402 Some([vals[0], vals[0], vals[0]])
6403 } else {
6404 None
6405 }
6406 } else {
6407 None
6408 }
6409 } else {
6410 if group_n_comps == 4 {
6414 Some([1.0, 1.0, 1.0])
6415 } else {
6416 None
6417 }
6418 };
6419
6420 let transfer_invert = if let Some(tr_obj) = dict.get(b"TR") {
6425 if let Ok(tr_data) = self.resolver.stream_data_from_obj(tr_obj) {
6426 let trimmed: Vec<u8> = tr_data
6427 .iter()
6428 .copied()
6429 .filter(|b| !b.is_ascii_whitespace())
6430 .collect();
6431 let s = String::from_utf8_lossy(&trimmed);
6432 s.contains("exchsub")
6433 } else {
6434 false
6435 }
6436 } else {
6437 false
6438 };
6439
6440 let effective_bbox = if let Some(bc) = &backdrop_color {
6445 let bc_lum = 0.2126 * bc[0] + 0.7152 * bc[1] + 0.0722 * bc[2];
6446 let bc_byte = (bc_lum * 255.0 + 0.5) as u8;
6447 let effective = if transfer_invert {
6451 255 - bc_byte
6452 } else {
6453 bc_byte
6454 };
6455 if effective > 0 {
6456 [0.0, 0.0, 1e9, 1e9]
6457 } else {
6458 device_bbox
6459 }
6460 } else {
6461 device_bbox
6462 };
6463
6464 Ok(graphics_state::SoftMask {
6465 mask_list,
6466 subtype,
6467 bbox: effective_bbox,
6468 backdrop_color,
6469 transfer_invert,
6470 has_nested_mask_scope,
6471 })
6472 }
6473
6474 fn parse_transfer_function(
6479 &self,
6480 obj: &PdfObj,
6481 ) -> Result<stet_graphics::device::TransferState, PdfError> {
6482 use crate::resources::function::PdfFunction;
6483 use stet_graphics::device::TransferState;
6484
6485 let obj = self.resolver.deref(obj)?;
6486
6487 if let Some(name) = obj.as_name()
6489 && (name == b"Identity" || name == b"Default")
6490 {
6491 return Ok(TransferState::default());
6492 }
6493
6494 if let PdfObj::Array(arr) = &obj
6496 && arr.len() == 4
6497 {
6498 let mut tables: [Option<Arc<Vec<f64>>>; 4] = Default::default();
6499 for (i, fn_obj) in arr.iter().enumerate() {
6500 let fn_obj = self.resolver.deref(fn_obj)?;
6501 if let Some(name) = fn_obj.as_name()
6502 && (name == b"Identity" || name == b"Default")
6503 {
6504 continue; }
6506 if let Ok(func) = PdfFunction::parse(&fn_obj, self.resolver) {
6507 tables[i] = Some(Arc::new(sample_transfer_function(&func)));
6508 }
6509 }
6510 return Ok(TransferState {
6511 gray: None,
6512 color: Some(tables),
6513 });
6514 }
6515
6516 if let Ok(func) = PdfFunction::parse(&obj, self.resolver) {
6518 let table = Arc::new(sample_transfer_function(&func));
6519 return Ok(TransferState {
6520 gray: Some(table),
6521 color: None,
6522 });
6523 }
6524
6525 Ok(TransferState::default())
6526 }
6527
6528 fn op_sh(&mut self) -> Result<(), PdfError> {
6531 let name = self
6532 .operand_stack
6533 .last()
6534 .and_then(|o| o.as_name())
6535 .ok_or(PdfError::Other("sh: expected name".into()))?
6536 .to_vec();
6537
6538 let shading_dict = self
6539 .resolve_resource_subdict(b"Shading")
6540 .ok_or(PdfError::Other("no Shading resources".into()))?;
6541 let sh_ref = shading_dict.get(&name).ok_or_else(|| {
6542 PdfError::Other(format!(
6543 "Shading /{} not found",
6544 String::from_utf8_lossy(&name)
6545 ))
6546 })?;
6547 let sh_ref_clone = sh_ref.clone();
6548 let sh_obj = self.resolver.deref(sh_ref)?;
6549 let sh_dict = sh_obj
6550 .as_dict()
6551 .ok_or(PdfError::Other("Shading is not a dict".into()))?;
6552
6553 crate::resources::shading::handle_shading(
6554 &sh_ref_clone,
6555 sh_dict,
6556 &self.gstate,
6557 self.resolver,
6558 &mut self.display_list,
6559 &mut self.icc_cache,
6560 )
6561 }
6562
6563 fn handle_pattern_fill(&mut self) -> Result<(), PdfError> {
6566 let name = self
6567 .operand_stack
6568 .last()
6569 .and_then(|o| o.as_name())
6570 .ok_or(PdfError::Other("pattern: expected name".into()))?
6571 .to_vec();
6572
6573 self.extract_pattern_underlying_color(false)?;
6577
6578 let pattern_dict = self
6580 .resolve_resource_subdict(b"Pattern")
6581 .ok_or(PdfError::Other("no Pattern resources".into()))?;
6582 let pat_ref = pattern_dict.get(&name).ok_or_else(|| {
6583 PdfError::Other(format!(
6584 "Pattern /{} not found",
6585 String::from_utf8_lossy(&name)
6586 ))
6587 })?;
6588 let pat_obj = self.resolver.deref(pat_ref)?;
6589 let pat_dict = pat_obj
6590 .as_dict()
6591 .ok_or(PdfError::Other("Pattern is not a dict".into()))?;
6592 let pattern_type = pat_dict.get_int(b"PatternType").unwrap_or(1) as i32;
6593
6594 if pattern_type == 2 {
6595 let shading_dl = self.resolve_shading_pattern(pat_dict)?;
6596 self.gstate.fill_pattern = None;
6597 self.gstate.fill_shading_pattern = Some(Box::new(ShadingPatternDL(shading_dl)));
6598 } else {
6599 let pattern = self.resolve_pattern(&name)?;
6600 self.gstate.fill_shading_pattern = None;
6601 self.gstate.fill_pattern = Some(pattern);
6602 }
6603 Ok(())
6604 }
6605
6606 fn handle_pattern_stroke(&mut self) -> Result<(), PdfError> {
6607 let name = self
6608 .operand_stack
6609 .last()
6610 .and_then(|o| o.as_name())
6611 .ok_or(PdfError::Other("pattern: expected name".into()))?
6612 .to_vec();
6613
6614 self.extract_pattern_underlying_color(true)?;
6616
6617 let pattern_dict = self
6619 .resolve_resource_subdict(b"Pattern")
6620 .ok_or(PdfError::Other("no Pattern resources".into()))?;
6621 let pat_ref = pattern_dict.get(&name).ok_or_else(|| {
6622 PdfError::Other(format!(
6623 "Pattern /{} not found",
6624 String::from_utf8_lossy(&name)
6625 ))
6626 })?;
6627 let pat_obj = self.resolver.deref(pat_ref)?;
6628 let pat_dict = pat_obj
6629 .as_dict()
6630 .ok_or(PdfError::Other("Pattern is not a dict".into()))?;
6631 let pattern_type = pat_dict.get_int(b"PatternType").unwrap_or(1) as i32;
6632
6633 if pattern_type == 2 {
6634 let shading_dl = self.resolve_shading_pattern(pat_dict)?;
6635 self.gstate.stroke_pattern = None;
6636 self.gstate.stroke_shading_pattern = Some(Box::new(ShadingPatternDL(shading_dl)));
6637 } else {
6638 let pattern = self.resolve_pattern(&name)?;
6639 self.gstate.stroke_shading_pattern = None;
6640 self.gstate.stroke_pattern = Some(pattern);
6641 }
6642 Ok(())
6643 }
6644
6645 fn extract_pattern_underlying_color(&mut self, is_stroke: bool) -> Result<(), PdfError> {
6650 let cs_ref = if is_stroke {
6652 &self.gstate.stroke_color_space
6653 } else {
6654 &self.gstate.fill_color_space
6655 };
6656 let cs_name = match cs_ref {
6657 ColorSpaceRef::Named(n) => n.clone(),
6658 _ => return Ok(()),
6659 };
6660
6661 let cs_obj_opt: Option<crate::objects::PdfObj> = self
6665 .cs_index
6666 .as_ref()
6667 .and_then(|idx| idx.get(cs_name.as_slice()).cloned())
6668 .or_else(|| {
6669 let cs_dict = self
6670 .resources
6671 .get(b"ColorSpace")
6672 .and_then(|obj| match obj {
6673 PdfObj::Dict(_) => Some(obj.as_dict().unwrap().clone()),
6674 PdfObj::Ref(n, g) => self.resolver.resolve(*n, *g).ok()?.as_dict().cloned(),
6675 _ => None,
6676 })?;
6677 cs_dict.get(&cs_name).cloned()
6678 });
6679 let cs_obj = match cs_obj_opt {
6680 Some(obj) => obj.clone(),
6681 None => return Ok(()),
6682 };
6683 let cs_resolved = self.resolver.deref(&cs_obj)?;
6684 let arr = match &cs_resolved {
6685 PdfObj::Array(a) if a.len() >= 2 => a,
6686 _ => return Ok(()),
6687 };
6688 if arr[0].as_name() != Some(b"Pattern") {
6690 return Ok(());
6691 }
6692 let underlying_cs = color_space::resolve_color_space_obj(&arr[1], self.resolver)?;
6694 let n = underlying_cs.num_components();
6695 if n == 0 {
6696 return Ok(());
6697 }
6698
6699 let stack_len = self.operand_stack.len();
6702 if stack_len < n + 1 {
6703 return Ok(()); }
6705 let mut nums = Vec::with_capacity(n);
6707 let base = stack_len - 1 - n;
6708 for i in 0..n {
6709 nums.push(self.operand_stack[base + i].as_f64().unwrap_or(0.0));
6710 }
6711 let intent = self.gstate.rendering_intent;
6712 let color = color_space::components_to_device_color_icc_with_intent(
6713 &underlying_cs,
6714 &nums,
6715 Some(&mut self.icc_cache),
6716 intent,
6717 );
6718 if is_stroke {
6719 self.gstate.stroke_color = color;
6720 } else {
6721 self.gstate.fill_color = color;
6722 }
6723 Ok(())
6724 }
6725
6726 fn resolve_pattern(&mut self, name: &[u8]) -> Result<TilingPattern, PdfError> {
6727 let pattern_dict = self
6728 .resolve_resource_subdict(b"Pattern")
6729 .ok_or(PdfError::Other("no Pattern resources".into()))?;
6730 let pat_ref = pattern_dict.get(name).ok_or_else(|| {
6731 PdfError::Other(format!(
6732 "Pattern /{} not found",
6733 String::from_utf8_lossy(name)
6734 ))
6735 })?;
6736
6737 if let PdfObj::Ref(obj_num, gen_num) = pat_ref {
6741 if let Some(cached) = self.pattern_cache.get(&(*obj_num, *gen_num)) {
6742 return Ok(cached.clone());
6743 }
6744 }
6745
6746 let pat_ref_clone = pat_ref.clone();
6747 let pat_obj = self.resolver.deref(pat_ref)?;
6748 let pat_dict = pat_obj
6749 .as_dict()
6750 .ok_or(PdfError::Other("Pattern is not a dict".into()))?;
6751
6752 let pattern_type = pat_dict.get_int(b"PatternType").unwrap_or(1) as i32;
6753
6754 let result = match pattern_type {
6755 1 => self.resolve_tiling_pattern(&pat_ref_clone, pat_dict),
6756 _ => Err(PdfError::Other(format!(
6757 "Unsupported PatternType {pattern_type}"
6758 ))),
6759 }?;
6760
6761 if let PdfObj::Ref(obj_num, gen_num) = pat_ref {
6762 self.pattern_cache
6763 .insert((*obj_num, *gen_num), result.clone());
6764 }
6765
6766 Ok(result)
6767 }
6768
6769 fn resolve_tiling_pattern(
6770 &mut self,
6771 pat_obj: &PdfObj,
6772 pat_dict: &PdfDict,
6773 ) -> Result<TilingPattern, PdfError> {
6774 if self.depth >= 20 {
6775 return Err(PdfError::Other("pattern recursion limit".into()));
6776 }
6777 let paint_type = pat_dict.get_int(b"PaintType").unwrap_or(1) as i32;
6778
6779 let bbox = deref_num_array(self.resolver, pat_dict, b"BBox")
6780 .map(|v| {
6781 if v.len() >= 4 {
6782 [v[0], v[1], v[2], v[3]]
6783 } else {
6784 [0.0, 0.0, 1.0, 1.0]
6785 }
6786 })
6787 .unwrap_or([0.0, 0.0, 1.0, 1.0]);
6788
6789 let x_step = pat_dict.get_f64(b"XStep").unwrap_or(bbox[2] - bbox[0]);
6790 let y_step = pat_dict.get_f64(b"YStep").unwrap_or(bbox[3] - bbox[1]);
6791
6792 let pattern_matrix = deref_num_array(self.resolver, pat_dict, b"Matrix")
6793 .map(|v| {
6794 if v.len() >= 6 {
6795 Matrix::new(v[0], v[1], v[2], v[3], v[4], v[5])
6796 } else {
6797 Matrix::identity()
6798 }
6799 })
6800 .unwrap_or_else(Matrix::identity);
6801
6802 let pattern_resources = if let Some(res_ref) = pat_dict.get(b"Resources") {
6803 match self.resolver.deref(res_ref)? {
6804 PdfObj::Dict(d) => d,
6805 _ => self.resources.clone(),
6806 }
6807 } else {
6808 self.resources.clone()
6809 };
6810
6811 let pattern_data = self.resolver.stream_data_from_obj(pat_obj)?;
6812
6813 let combined_matrix = self.content_stream_ctm.concat(&pattern_matrix);
6818
6819 self.gstate_stack.push(self.gstate.clone());
6824 let saved_resources = std::mem::replace(&mut self.resources, pattern_resources);
6825 let saved_display_list = std::mem::take(&mut self.display_list);
6826 let saved_content_stream_ctm = self.content_stream_ctm;
6827 let saved_path = std::mem::take(&mut self.current_path);
6828 let saved_point = self.current_point.take();
6829 let saved_subpath = self.subpath_start.take();
6830 let saved_mc_stack = std::mem::take(&mut self.mc_stack);
6831
6832 self.gstate.ctm = Matrix::identity();
6833 self.content_stream_ctm = Matrix::identity();
6834 self.gstate.clip_path = None;
6835 self.gstate.clip_path_version = 0;
6836 self.gstate.clip_stack.clear();
6837 self.gstate.fill_pattern = None;
6840 self.gstate.stroke_pattern = None;
6841 self.gstate.fill_shading_pattern = None;
6842 self.gstate.stroke_shading_pattern = None;
6843 self.gstate.text_rendering_mode = 0;
6846
6847 self.depth += 1;
6848 let _ = self.interpret_stream(&pattern_data);
6849 self.depth -= 1;
6850
6851 self.flush_soft_mask();
6853
6854 let tile_display_list = std::mem::replace(&mut self.display_list, saved_display_list);
6855 self.content_stream_ctm = saved_content_stream_ctm;
6856 self.resources = saved_resources;
6857 self.current_path = saved_path;
6858 self.current_point = saved_point;
6859 self.subpath_start = saved_subpath;
6860 self.mc_stack = saved_mc_stack;
6861 if let Some(saved) = self.gstate_stack.pop() {
6862 self.gstate = saved;
6863 }
6864
6865 Ok(TilingPattern {
6866 tile: tile_display_list,
6867 bbox,
6868 x_step,
6869 y_step,
6870 pattern_matrix: combined_matrix,
6871 paint_type,
6872 pattern_id: 0,
6873 flip_tile_y: false,
6874 })
6875 }
6876
6877 fn resolve_shading_pattern(&mut self, pat_dict: &PdfDict) -> Result<DisplayList, PdfError> {
6881 let sh_ref = pat_dict
6882 .get(b"Shading")
6883 .ok_or(PdfError::Other("shading pattern missing /Shading".into()))?;
6884 let sh_ref_clone = sh_ref.clone();
6885 let sh_obj = self.resolver.deref(sh_ref)?;
6886 let sh_dict = sh_obj
6887 .as_dict()
6888 .ok_or(PdfError::Other("Shading is not a dict".into()))?;
6889
6890 let pattern_matrix = deref_num_array(self.resolver, pat_dict, b"Matrix")
6891 .map(|v| {
6892 if v.len() >= 6 {
6893 Matrix::new(v[0], v[1], v[2], v[3], v[4], v[5])
6894 } else {
6895 Matrix::identity()
6896 }
6897 })
6898 .unwrap_or_else(Matrix::identity);
6899
6900 let combined_matrix = self.content_stream_ctm.concat(&pattern_matrix);
6915 let saved_ctm = self.gstate.ctm;
6916 let saved_overprint = self.gstate.overprint;
6917 let saved_overprint_stroke = self.gstate.overprint_stroke;
6918 self.gstate.ctm = combined_matrix;
6919 self.gstate.overprint = false;
6920 self.gstate.overprint_stroke = false;
6921
6922 let mut shading_dl = DisplayList::new();
6923 let result = crate::resources::shading::handle_shading(
6924 &sh_ref_clone,
6925 sh_dict,
6926 &self.gstate,
6927 self.resolver,
6928 &mut shading_dl,
6929 &mut self.icc_cache,
6930 );
6931 self.gstate.ctm = saved_ctm;
6932 self.gstate.overprint = saved_overprint;
6933 self.gstate.overprint_stroke = saved_overprint_stroke;
6934 result?;
6935 Ok(shading_dl)
6936 }
6937}
6938
6939fn bboxes_overlap_substantially(a: &[f64; 4], b: &[f64; 4], min_extent: f64) -> bool {
6951 let (ax0, ay0, ax1, ay1) = (
6952 a[0].min(a[2]),
6953 a[1].min(a[3]),
6954 a[0].max(a[2]),
6955 a[1].max(a[3]),
6956 );
6957 let (bx0, by0, bx1, by1) = (
6958 b[0].min(b[2]),
6959 b[1].min(b[3]),
6960 b[0].max(b[2]),
6961 b[1].max(b[3]),
6962 );
6963 let overlap_w = (ax1.min(bx1) - ax0.max(bx0)).max(0.0);
6964 let overlap_h = (ay1.min(by1) - ay0.max(by0)).max(0.0);
6965 overlap_w >= min_extent && overlap_h >= min_extent
6966}
6967
6968fn path_device_bbox(path: &PsPath) -> [f64; 4] {
6969 let mut x_min = f64::INFINITY;
6970 let mut y_min = f64::INFINITY;
6971 let mut x_max = f64::NEG_INFINITY;
6972 let mut y_max = f64::NEG_INFINITY;
6973 let mut update = |x: f64, y: f64| {
6974 x_min = x_min.min(x);
6975 y_min = y_min.min(y);
6976 x_max = x_max.max(x);
6977 y_max = y_max.max(y);
6978 };
6979 for seg in &path.segments {
6980 match seg {
6981 PathSegment::MoveTo(x, y) | PathSegment::LineTo(x, y) => update(*x, *y),
6982 PathSegment::CurveTo {
6983 x1,
6984 y1,
6985 x2,
6986 y2,
6987 x3,
6988 y3,
6989 } => {
6990 update(*x1, *y1);
6991 update(*x2, *y2);
6992 update(*x3, *y3);
6993 }
6994 PathSegment::ClosePath => {}
6995 }
6996 }
6997 [x_min, y_min, x_max, y_max]
6998}
6999
7000fn name_to_cs_ref(name: &[u8]) -> ColorSpaceRef {
7002 match name {
7003 b"DeviceGray" | b"G" => ColorSpaceRef::DeviceGray,
7004 b"DeviceRGB" | b"RGB" => ColorSpaceRef::DeviceRGB,
7005 b"DeviceCMYK" | b"CMYK" => ColorSpaceRef::DeviceCMYK,
7006 _ => ColorSpaceRef::Named(name.to_vec()),
7007 }
7008}
7009
7010fn expand_inline_key(key: &[u8]) -> Vec<u8> {
7012 match key {
7013 b"BPC" => b"BitsPerComponent".to_vec(),
7014 b"CS" => b"ColorSpace".to_vec(),
7015 b"D" => b"Decode".to_vec(),
7016 b"DP" => b"DecodeParms".to_vec(),
7017 b"F" => b"Filter".to_vec(),
7018 b"H" => b"Height".to_vec(),
7019 b"IM" => b"ImageMask".to_vec(),
7020 b"I" => b"Interpolate".to_vec(),
7021 b"W" => b"Width".to_vec(),
7022 _ => key.to_vec(),
7023 }
7024}
7025
7026fn expand_inline_value(name: &[u8]) -> Vec<u8> {
7028 match name {
7029 b"G" => b"DeviceGray".to_vec(),
7030 b"RGB" => b"DeviceRGB".to_vec(),
7031 b"CMYK" => b"DeviceCMYK".to_vec(),
7032 b"I" => b"Indexed".to_vec(),
7033 b"AHx" => b"ASCIIHexDecode".to_vec(),
7034 b"A85" => b"ASCII85Decode".to_vec(),
7035 b"LZW" => b"LZWDecode".to_vec(),
7036 b"Fl" => b"FlateDecode".to_vec(),
7037 b"RL" => b"RunLengthDecode".to_vec(),
7038 b"CCF" => b"CCITTFaxDecode".to_vec(),
7039 b"DCT" => b"DCTDecode".to_vec(),
7040 _ => name.to_vec(),
7041 }
7042}
7043
7044fn bilinear_upsample_image(
7047 data: &[u8],
7048 sw: u32,
7049 sh: u32,
7050 dw: u32,
7051 dh: u32,
7052 cs: &ImageColorSpace,
7053) -> Vec<u8> {
7054 let n = cs.num_components() as usize;
7055 if n == 0 || sw == 0 || sh == 0 || dw == 0 || dh == 0 {
7056 return data.to_vec();
7057 }
7058 let src_stride = sw as usize * n;
7059 let dst_stride = dw as usize * n;
7060 let mut out = vec![0u8; dst_stride * dh as usize];
7061
7062 for dy in 0..dh as usize {
7063 let sy = (dy as f32 + 0.5) * sh as f32 / dh as f32 - 0.5;
7064 let sy0 = (sy.floor() as i32).clamp(0, sh as i32 - 1) as usize;
7065 let sy1 = (sy0 + 1).min(sh as usize - 1);
7066 let fy = sy - sy0 as f32;
7067
7068 for dx in 0..dw as usize {
7069 let sx = (dx as f32 + 0.5) * sw as f32 / dw as f32 - 0.5;
7070 let sx0 = (sx.floor() as i32).clamp(0, sw as i32 - 1) as usize;
7071 let sx1 = (sx0 + 1).min(sw as usize - 1);
7072 let fx = sx - sx0 as f32;
7073
7074 let w00 = (1.0 - fx) * (1.0 - fy);
7075 let w10 = fx * (1.0 - fy);
7076 let w01 = (1.0 - fx) * fy;
7077 let w11 = fx * fy;
7078
7079 let i00 = sy0 * src_stride + sx0 * n;
7080 let i10 = sy0 * src_stride + sx1 * n;
7081 let i01 = sy1 * src_stride + sx0 * n;
7082 let i11 = sy1 * src_stride + sx1 * n;
7083
7084 let di = dy * dst_stride + dx * n;
7085 for c in 0..n {
7086 let v = data[i00 + c] as f32 * w00
7087 + data[i10 + c] as f32 * w10
7088 + data[i01 + c] as f32 * w01
7089 + data[i11 + c] as f32 * w11;
7090 out[di + c] = (v + 0.5).clamp(0.0, 255.0) as u8;
7091 }
7092 }
7093 }
7094 out
7095}
7096
7097fn merge_rgb_with_smask(
7100 image_data: &[u8],
7101 smask_data: &[u8],
7102 color_space: &ImageColorSpace,
7103 width: u32,
7104 height: u32,
7105 icc: Option<&stet_graphics::icc::IccCache>,
7106) -> Vec<u8> {
7107 if let ImageColorSpace::Indexed {
7109 base,
7110 hival,
7111 lookup,
7112 } = color_space
7113 {
7114 let n_base = base.num_components() as usize;
7115 let n_pixels = (width * height) as usize;
7116 let mut expanded = vec![0u8; n_pixels * n_base];
7117 for i in 0..n_pixels {
7118 let idx = image_data.get(i).copied().unwrap_or(0) as usize;
7119 let idx = idx.min(*hival as usize);
7120 let offset = idx * n_base;
7121 for c in 0..n_base {
7122 expanded[i * n_base + c] = lookup.get(offset + c).copied().unwrap_or(0);
7123 }
7124 }
7125 return merge_rgb_with_smask(&expanded, smask_data, base, width, height, icc);
7126 }
7127
7128 if let ImageColorSpace::Separation {
7130 alt_space,
7131 tint_table,
7132 ..
7133 } = color_space
7134 {
7135 let n_pixels = (width * height) as usize;
7136 let no = tint_table.num_outputs as usize;
7137 let mut expanded = vec![0u8; n_pixels * no];
7138 let mut alt_comps = vec![0.0f32; no];
7139 for i in 0..n_pixels {
7140 let tint = image_data.get(i).copied().unwrap_or(0) as f32 / 255.0;
7141 tint_table.lookup_1d(tint, &mut alt_comps);
7142 for c in 0..no {
7143 expanded[i * no + c] = (alt_comps[c].clamp(0.0, 1.0) * 255.0 + 0.5) as u8;
7144 }
7145 }
7146 return merge_rgb_with_smask(&expanded, smask_data, alt_space, width, height, icc);
7147 }
7148 if let ImageColorSpace::DeviceN {
7149 alt_space,
7150 tint_table,
7151 ..
7152 } = color_space
7153 {
7154 let ni = tint_table.num_inputs as usize;
7155 let no = tint_table.num_outputs as usize;
7156 let n_pixels = (width * height) as usize;
7157 let mut expanded = vec![0u8; n_pixels * no];
7158 let mut inputs = vec![0.0f32; ni];
7159 let mut alt_comps = vec![0.0f32; no];
7160 for i in 0..n_pixels {
7161 let si = i * ni;
7162 for (c, inp) in inputs.iter_mut().enumerate() {
7163 *inp = image_data.get(si + c).copied().unwrap_or(0) as f32 / 255.0;
7164 }
7165 tint_table.lookup_nd(&inputs, &mut alt_comps);
7166 for c in 0..no {
7167 expanded[i * no + c] = (alt_comps[c].clamp(0.0, 1.0) * 255.0 + 0.5) as u8;
7168 }
7169 }
7170 return merge_rgb_with_smask(&expanded, smask_data, alt_space, width, height, icc);
7171 }
7172
7173 let n_pixels = (width * height) as usize;
7174 let mut rgba = vec![255u8; n_pixels * 4];
7175 let n_comps = color_space.num_components();
7176
7177 if n_comps == 4 {
7179 if let Some(cache) = icc {
7180 if let Some(cmyk_hash) = cache.default_cmyk_hash() {
7181 let cmyk_data = if image_data.len() >= n_pixels * 4 {
7182 &image_data[..n_pixels * 4]
7183 } else {
7184 image_data
7185 };
7186 if let Some(rgb) = cache.convert_image_8bit(cmyk_hash, cmyk_data, n_pixels) {
7187 for i in 0..n_pixels {
7188 let alpha = smask_data.get(i).copied().unwrap_or(255);
7189 let dst = i * 4;
7190 let (r, g, b) = (rgb[i * 3], rgb[i * 3 + 1], rgb[i * 3 + 2]);
7191 if alpha == 255 {
7192 rgba[dst] = r;
7193 rgba[dst + 1] = g;
7194 rgba[dst + 2] = b;
7195 rgba[dst + 3] = 255;
7196 } else if alpha == 0 {
7197 rgba[dst] = 0;
7199 rgba[dst + 1] = 0;
7200 rgba[dst + 2] = 0;
7201 rgba[dst + 3] = 0;
7202 } else {
7203 let a = alpha as u16;
7204 rgba[dst] = ((r as u16 * a + 127) / 255) as u8;
7205 rgba[dst + 1] = ((g as u16 * a + 127) / 255) as u8;
7206 rgba[dst + 2] = ((b as u16 * a + 127) / 255) as u8;
7207 rgba[dst + 3] = alpha;
7208 }
7209 }
7210 return rgba;
7211 }
7212 }
7213 }
7214 }
7215
7216 for i in 0..n_pixels {
7217 let alpha = smask_data.get(i).copied().unwrap_or(255);
7218 let dst = i * 4;
7219 match n_comps {
7220 3 => {
7221 let src = i * 3;
7223 rgba[dst] = image_data.get(src).copied().unwrap_or(0);
7224 rgba[dst + 1] = image_data.get(src + 1).copied().unwrap_or(0);
7225 rgba[dst + 2] = image_data.get(src + 2).copied().unwrap_or(0);
7226 }
7227 1 => {
7228 let g = image_data.get(i).copied().unwrap_or(0);
7230 rgba[dst] = g;
7231 rgba[dst + 1] = g;
7232 rgba[dst + 2] = g;
7233 }
7234 4 => {
7235 let src = i * 4;
7237 let c = image_data.get(src).copied().unwrap_or(0) as f64 / 255.0;
7238 let m = image_data.get(src + 1).copied().unwrap_or(0) as f64 / 255.0;
7239 let y = image_data.get(src + 2).copied().unwrap_or(0) as f64 / 255.0;
7240 let k = image_data.get(src + 3).copied().unwrap_or(0) as f64 / 255.0;
7241 rgba[dst] = ((1.0 - c) * (1.0 - k) * 255.0 + 0.5) as u8;
7242 rgba[dst + 1] = ((1.0 - m) * (1.0 - k) * 255.0 + 0.5) as u8;
7243 rgba[dst + 2] = ((1.0 - y) * (1.0 - k) * 255.0 + 0.5) as u8;
7244 }
7245 _ => {
7246 }
7248 }
7249 if alpha == 255 {
7251 rgba[dst + 3] = 255;
7252 } else if alpha == 0 {
7253 rgba[dst] = 0;
7254 rgba[dst + 1] = 0;
7255 rgba[dst + 2] = 0;
7256 rgba[dst + 3] = 0;
7257 } else {
7258 let a = alpha as u16;
7259 rgba[dst] = ((rgba[dst] as u16 * a + 127) / 255) as u8;
7260 rgba[dst + 1] = ((rgba[dst + 1] as u16 * a + 127) / 255) as u8;
7261 rgba[dst + 2] = ((rgba[dst + 2] as u16 * a + 127) / 255) as u8;
7262 rgba[dst + 3] = alpha;
7263 }
7264 }
7265 rgba
7266}
7267
7268fn expand_bits_to_bytes(
7269 data: &[u8],
7270 bpc: u32,
7271 width: u32,
7272 height: u32,
7273 components: u32,
7274 is_indexed: bool,
7275) -> Vec<u8> {
7276 if bpc == 0 || bpc == 8 {
7277 return data.to_vec();
7278 }
7279
7280 let max_val = ((1u32 << bpc) - 1) as f64;
7281 let samples_per_row = width * components.max(1);
7282 let mut result = Vec::with_capacity((width * height * components.max(1)) as usize);
7283
7284 for row in 0..height {
7285 let row_bit_offset = row as usize * ((samples_per_row * bpc).div_ceil(8) * 8) as usize;
7286 for col in 0..samples_per_row {
7287 let bit_offset = row_bit_offset + (col * bpc) as usize;
7288 let byte_offset = bit_offset / 8;
7289 let bit_shift = bit_offset % 8;
7290
7291 if byte_offset >= data.len() {
7292 result.push(0);
7293 continue;
7294 }
7295
7296 let mut val = 0u32;
7298 let mut bits_remaining = bpc;
7299 let mut cur_byte = byte_offset;
7300 let mut cur_bit = bit_shift;
7301
7302 while bits_remaining > 0 && cur_byte < data.len() {
7303 let available = 8 - cur_bit as u32;
7304 let take = bits_remaining.min(available);
7305 let shift = available - take;
7306 let mask = ((1u32 << take) - 1) << shift;
7307 val = (val << take) | ((data[cur_byte] as u32 & mask) >> shift);
7308 bits_remaining -= take;
7309 cur_bit = 0;
7310 cur_byte += 1;
7311 }
7312
7313 if is_indexed {
7316 result.push(val as u8);
7317 } else {
7318 result.push((val as f64 / max_val * 255.0 + 0.5) as u8);
7319 }
7320 }
7321 }
7322
7323 result
7324}
7325
7326fn blend_mode_from_name(name: &[u8]) -> u8 {
7328 match name {
7329 b"Normal" | b"Compatible" => 0,
7330 b"Multiply" => 1,
7331 b"Screen" => 2,
7332 b"Overlay" => 3,
7333 b"Darken" => 4,
7334 b"Lighten" => 5,
7335 b"ColorDodge" => 6,
7336 b"ColorBurn" => 7,
7337 b"HardLight" => 8,
7338 b"SoftLight" => 9,
7339 b"Difference" => 10,
7340 b"Exclusion" => 11,
7341 b"Hue" => 12,
7342 b"Saturation" => 13,
7343 b"Color" => 14,
7344 b"Luminosity" => 15,
7345 _ => 0,
7346 }
7347}
7348
7349fn is_whitespace_byte(b: u8) -> bool {
7350 matches!(b, b' ' | b'\t' | b'\r' | b'\n' | 0x0C | 0x00)
7351}
7352
7353fn is_delimiter_or_ws(b: u8) -> bool {
7354 is_whitespace_byte(b)
7355 || matches!(
7356 b,
7357 b'(' | b')' | b'<' | b'>' | b'[' | b']' | b'{' | b'}' | b'/' | b'%'
7358 )
7359}
7360
7361fn sample_transfer_function(func: &crate::resources::function::PdfFunction) -> Vec<f64> {
7363 (0..256)
7364 .map(|i| {
7365 let t = i as f64 / 255.0;
7366 let result = func.evaluate(&[t]);
7367 result.first().copied().unwrap_or(t).clamp(0.0, 1.0)
7368 })
7369 .collect()
7370}
7371
7372fn apply_transfer_to_image(
7377 data: &mut [u8],
7378 transfer: &stet_graphics::device::TransferState,
7379 components: usize,
7380) {
7381 let (r_table, g_table, b_table) = if let Some(ref color) = transfer.color {
7383 let r = build_u8_lut(color[0].as_ref().map(|v| &v[..]));
7385 let g = build_u8_lut(color[1].as_ref().map(|v| &v[..]));
7386 let b = build_u8_lut(color[2].as_ref().map(|v| &v[..]));
7387 (r, g, b)
7388 } else if let Some(ref gray) = transfer.gray {
7389 let lut = build_u8_lut(Some(&gray[..]));
7391 (lut, lut, lut)
7392 } else {
7393 return; };
7395
7396 let stride = components;
7398 for pixel in data.chunks_exact_mut(stride) {
7399 if pixel.len() >= 3 {
7400 pixel[0] = r_table[pixel[0] as usize];
7401 pixel[1] = g_table[pixel[1] as usize];
7402 pixel[2] = b_table[pixel[2] as usize];
7403 }
7404 }
7405}
7406
7407fn apply_transfer_to_color(
7409 color: &DeviceColor,
7410 transfer: &stet_graphics::device::TransferState,
7411) -> DeviceColor {
7412 if let Some(ref color_tables) = transfer.color {
7413 let r = apply_transfer_component(color.r, color_tables[0].as_ref().map(|v| &v[..]));
7415 let g = apply_transfer_component(color.g, color_tables[1].as_ref().map(|v| &v[..]));
7416 let b = apply_transfer_component(color.b, color_tables[2].as_ref().map(|v| &v[..]));
7417 DeviceColor::from_rgb(r, g, b)
7418 } else if let Some(ref gray) = transfer.gray {
7419 let r = apply_transfer_component(color.r, Some(&gray[..]));
7420 let g = apply_transfer_component(color.g, Some(&gray[..]));
7421 let b = apply_transfer_component(color.b, Some(&gray[..]));
7422 DeviceColor::from_rgb(r, g, b)
7423 } else {
7424 color.clone()
7425 }
7426}
7427
7428fn apply_transfer_component(value: f64, table: Option<&[f64]>) -> f64 {
7430 match table {
7431 None => value,
7432 Some(t) if t.len() != 256 => value,
7433 Some(t) => {
7434 let idx = (value * 255.0).clamp(0.0, 255.0);
7435 let lo = idx.floor() as usize;
7436 let hi = (lo + 1).min(255);
7437 let frac = idx - lo as f64;
7438 let v0 = t[lo];
7439 let v1 = t[hi];
7440 (v0 + frac * (v1 - v0)).clamp(0.0, 1.0)
7441 }
7442 }
7443}
7444
7445fn build_u8_lut(table: Option<&[f64]>) -> [u8; 256] {
7447 let mut lut = [0u8; 256];
7448 match table {
7449 Some(t) if t.len() == 256 => {
7450 for (i, v) in lut.iter_mut().enumerate() {
7451 *v = (t[i].clamp(0.0, 1.0) * 255.0 + 0.5) as u8;
7452 }
7453 }
7454 _ => {
7455 for (i, v) in lut.iter_mut().enumerate() {
7456 *v = i as u8;
7457 }
7458 }
7459 }
7460 lut
7461}