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, MAX_OBJECT_DEPTH, 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;
44use stet_graphics::image_limits::{
45 validate_bits_per_component, validate_image_dimension, validate_image_size,
46};
47
48const MAX_CONTENT_NESTING: u32 = 20;
58
59enum MarkedContentFrame {
65 Ocg {
68 parent_list: DisplayList,
69 visibility: OcgVisibility,
70 },
71 Other,
73}
74
75fn deref_num_array(resolver: &Resolver, dict: &PdfDict, key: &[u8]) -> Option<Vec<f64>> {
83 let obj = dict.get(key)?;
84 if let Some(arr) = obj.as_array() {
85 return Some(arr.iter().filter_map(|o| o.as_f64()).collect());
86 }
87 let resolved = resolver.deref(obj).ok()?;
88 resolved
89 .as_array()
90 .map(|a| a.iter().filter_map(|o| o.as_f64()).collect())
91}
92
93#[derive(Clone, Debug)]
95pub enum Operand {
96 Int(i64),
97 Real(f64),
98 Name(Vec<u8>),
99 Str(Vec<u8>),
100 Array(Vec<PdfObj>),
101 Dict(PdfDict),
102 Bool(bool),
103}
104
105impl Operand {
106 fn as_f64(&self) -> Option<f64> {
108 match self {
109 Operand::Int(n) => Some(*n as f64),
110 Operand::Real(f) => Some(*f),
111 _ => None,
112 }
113 }
114
115 fn as_name(&self) -> Option<&[u8]> {
117 match self {
118 Operand::Name(n) => Some(n),
119 _ => None,
120 }
121 }
122
123 #[allow(dead_code)]
125 fn as_str(&self) -> Option<&[u8]> {
126 match self {
127 Operand::Str(s) => Some(s),
128 _ => None,
129 }
130 }
131}
132
133#[derive(Clone)]
136struct CachedImage {
137 sample_data: Arc<Vec<u8>>,
138 width: u32,
139 height: u32,
140 color_space: ImageColorSpace,
141 bits_per_component: u8,
142 interpolate: bool,
143 mask_color: Option<Vec<u8>>,
144 painted_channels: u8,
146 smask: Option<(Arc<Vec<u8>>, u32, u32, Option<Vec<f64>>)>,
148 rendering_intent: u8,
152}
153
154struct SoftMaskScope {
156 start_index: usize,
158 mask: graphics_state::SoftMask,
160}
161
162pub struct ContentInterpreter<'a> {
164 resolver: &'a Resolver<'a>,
165 resources: PdfDict,
166 gstate_stack: Vec<PdfGraphicsState>,
167 gstate: PdfGraphicsState,
168 current_path: PsPath,
169 current_point: Option<(f64, f64)>,
170 subpath_start: Option<(f64, f64)>,
171 operand_stack: Vec<Operand>,
172 display_list: DisplayList,
173 in_text: bool,
174 depth: u32,
181 d1_color_suppressed: bool,
184 font_cache: FontCache,
185 current_font: Option<Arc<PdfFont>>,
186 content_stream_ctm: Matrix,
191 initial_ctm: Matrix,
196 icc_cache: IccCache,
198 soft_mask_scope: Option<SoftMaskScope>,
200 nested_mask_flush_count: u32,
204 font_provider: Option<FontProvider>,
206 text_clip_path: Option<PsPath>,
209 page_group_is_cmyk: bool,
212 pdfx_cmyk_intent: bool,
228 in_smask_form: bool,
241 overprint_enabled: bool,
244 pattern_cache: std::collections::HashMap<(u32, u16), TilingPattern>,
248 ocg_off: std::collections::HashSet<u32>,
250 mc_stack: Vec<MarkedContentFrame>,
255 cs_index: Option<std::collections::HashMap<Vec<u8>, PdfObj>>,
258 form_cull_y: Option<(f64, f64)>,
262 bt_culled: bool,
264 image_cache: std::collections::HashMap<u32, CachedImage>,
268 spot_tint_table_cache:
275 std::collections::HashMap<Vec<u8>, Arc<stet_graphics::device::TintLookupTable>>,
276}
277
278impl<'a> ContentInterpreter<'a> {
279 pub fn new(
281 resolver: &'a Resolver<'a>,
282 resources: PdfDict,
283 initial_ctm: Matrix,
284 icc_cache: &IccCache,
285 font_provider: Option<FontProvider>,
286 overprint_enabled: bool,
287 ocg_off: &std::collections::HashSet<u32>,
288 ) -> Self {
289 Self {
290 resolver,
291 resources,
292 gstate_stack: Vec::new(),
293 gstate: PdfGraphicsState::new(initial_ctm),
294 current_path: PsPath::new(),
295 current_point: None,
296 subpath_start: None,
297 operand_stack: Vec::new(),
298 display_list: DisplayList::new(),
299 content_stream_ctm: initial_ctm,
300 initial_ctm,
301 in_text: false,
302 depth: 0,
303 d1_color_suppressed: false,
304 nested_mask_flush_count: 0,
305 font_cache: FontCache::new(),
306 current_font: None,
307 icc_cache: icc_cache.clone(),
308 soft_mask_scope: None,
309 font_provider,
310 text_clip_path: None,
311 page_group_is_cmyk: false,
312 pdfx_cmyk_intent: false,
313 in_smask_form: false,
314 overprint_enabled,
315 pattern_cache: std::collections::HashMap::new(),
316 ocg_off: ocg_off.clone(),
317 mc_stack: Vec::new(),
318 cs_index: None,
319 form_cull_y: None,
320 bt_culled: false,
321 image_cache: std::collections::HashMap::new(),
322 spot_tint_table_cache: std::collections::HashMap::new(),
323 }
324 }
325
326 pub fn set_page_group_cmyk(&mut self) {
330 self.page_group_is_cmyk = true;
331 }
332
333 pub fn set_pdfx_cmyk_intent(&mut self) {
337 self.pdfx_cmyk_intent = true;
338 }
339
340 fn resolve_dict_int(&self, dict: &PdfDict, key: &[u8]) -> Option<i64> {
344 let obj = dict.get(key)?;
345 if let Some(n) = obj.as_int() {
346 return Some(n);
347 }
348 let resolved = self.resolver.deref(obj).ok()?;
350 resolved.as_int()
351 }
352
353 fn resolve_resource_subdict(&self, key: &[u8]) -> Option<PdfDict> {
354 let obj = self.resources.get(key)?;
355 if let Some(d) = obj.as_dict() {
357 return Some(d.clone());
358 }
359 let resolved = self.resolver.deref(obj).ok()?;
361 resolved.as_dict().cloned()
362 }
363
364 pub fn interpret(mut self, data: &[u8]) -> Result<DisplayList, PdfError> {
366 if let Err(e) = self.interpret_stream(data) {
367 eprintln!("warning: content stream error: {}", e);
368 }
369 self.flush_soft_mask();
371 Ok(self.display_list)
374 }
375
376 pub fn interpret_stream_public(&mut self, data: &[u8]) -> Result<(), PdfError> {
378 self.interpret_stream(data)
379 }
380
381 pub fn into_display_list(mut self) -> DisplayList {
383 self.flush_soft_mask();
384 while let Some(frame) = self.mc_stack.pop() {
386 if let MarkedContentFrame::Ocg {
387 parent_list,
388 visibility,
389 } = frame
390 {
391 let ocg_list = std::mem::replace(&mut self.display_list, parent_list);
392 self.display_list.push(DisplayElement::OcgGroup {
393 elements: ocg_list,
394 visibility,
395 });
396 }
397 }
398 self.display_list
399 }
400
401 pub fn unwind_gstate_stack(&mut self) {
405 while let Some(saved) = self.gstate_stack.pop() {
406 let old_clip_version = self.gstate.clip_path_version;
407 self.gstate = saved;
408 if self.gstate.clip_path_version != old_clip_version {
409 self.restore_clip_from_stack();
410 }
411 }
412 }
413
414 pub fn reset_clip_for_annotations(&mut self) {
417 self.display_list.push(DisplayElement::InitClip);
418 self.gstate.clip_path = None;
419 self.gstate.clip_stack.clear();
420 self.gstate.clip_path_version += 1;
421 }
422
423 pub fn render_annotation(&mut self, obj_num: u32, gen_num: u16) -> Result<(), PdfError> {
425 let annot_obj = self.resolver.resolve(obj_num, gen_num)?;
426 let annot_dict = annot_obj
427 .as_dict()
428 .ok_or(PdfError::Other("annotation not a dict".into()))?;
429
430 let subtype = annot_dict.get_name(b"Subtype").unwrap_or(b"");
431
432 let flags = annot_dict.get_int(b"F").unwrap_or(0);
436 if flags & 0x02 != 0 {
437 return Ok(()); }
439
440 let rect = annot_dict
443 .get(b"Rect")
444 .and_then(|obj| {
445 let resolved = self.resolver.deref(obj).ok().unwrap_or(obj.clone());
446 let a = resolved
447 .as_array()
448 .or_else(|| annot_dict.get_array(b"Rect"))?;
449 if a.len() >= 4 {
450 let r0 = a[0].as_f64()?;
451 let r1 = a[1].as_f64()?;
452 let r2 = a[2].as_f64()?;
453 let r3 = a[3].as_f64()?;
454 Some([r0.min(r2), r1.min(r3), r0.max(r2), r1.max(r3)])
455 } else {
456 None
457 }
458 })
459 .ok_or(PdfError::Other("annotation missing Rect".into()))?;
460
461 let ap_obj = match annot_dict.get(b"AP") {
464 Some(ap) => ap,
465 None => {
466 return self.synthesize_annotation(annot_dict, &rect);
467 }
468 };
469 let ap_dict = match self.resolver.deref(ap_obj)? {
470 PdfObj::Dict(d) => d,
471 _ => return Err(PdfError::Other("AP not a dict".into())),
472 };
473
474 let n_ref = ap_dict.get(b"N").ok_or(PdfError::Other("no AP/N".into()))?;
475
476 let n_obj = self.resolver.deref(n_ref)?;
480 let (n_ref, form_dict) = if let Some(d) = n_obj.as_dict() {
481 if d.get(b"BBox").is_some() {
482 (n_ref.clone(), d.clone())
484 } else {
485 let as_name = annot_dict.get_name(b"AS").unwrap_or(b"Off");
491 let state_ref = match d.get(as_name) {
492 Some(r) => r,
493 None if subtype == b"Widget" => {
494 return Ok(());
496 }
497 None => {
498 match d.entries().first().map(|(_, v)| v) {
500 Some(r) => r,
501 None => return Ok(()),
502 }
503 }
504 };
505 let state_obj = self.resolver.deref(state_ref)?;
506 let state_dict = state_obj
507 .as_dict()
508 .ok_or(PdfError::Other("AP/N state not a stream".into()))?;
509 (state_ref.clone(), state_dict.clone())
510 }
511 } else {
512 return Err(PdfError::Other("AP/N not a dict or stream".into()));
513 };
514
515 let bbox = form_dict
519 .get(b"BBox")
520 .and_then(|obj| {
521 let resolved = self.resolver.deref(obj).ok().unwrap_or(obj.clone());
522 let a = resolved.as_array()?;
523 if a.len() >= 4 {
524 Some([
525 a[0].as_f64()?,
526 a[1].as_f64()?,
527 a[2].as_f64()?,
528 a[3].as_f64()?,
529 ])
530 } else {
531 None
532 }
533 })
534 .unwrap_or([rect[0], rect[1], rect[2], rect[3]]);
535
536 let form_matrix = deref_num_array(self.resolver, &form_dict, b"Matrix")
538 .and_then(|v| {
539 if v.len() == 6 {
540 Some(Matrix::new(v[0], v[1], v[2], v[3], v[4], v[5]))
541 } else {
542 None
543 }
544 })
545 .unwrap_or_else(Matrix::identity);
546
547 let (tb0x, tb0y) = form_matrix.transform_point(bbox[0], bbox[1]);
552 let (tb1x, tb1y) = form_matrix.transform_point(bbox[2], bbox[3]);
553 let tbbox_w = (tb1x - tb0x).abs().max(0.001);
554 let tbbox_h = (tb1y - tb0y).abs().max(0.001);
555 let rect_w = (rect[2] - rect[0]).abs();
556 let rect_h = (rect[3] - rect[1]).abs();
557 let sx = rect_w / tbbox_w;
558 let sy = rect_h / tbbox_h;
559 let tx = rect[0] - tb0x.min(tb1x) * sx;
560 let ty = rect[1] - tb0y.min(tb1y) * sy;
561 let bbox_to_rect = Matrix::new(sx, 0.0, 0.0, sy, tx, ty);
562
563 let saved_gstate = self.gstate.clone();
565 let saved_stack_depth = self.gstate_stack.len();
566 let saved_resources = self.resources.clone();
567 let saved_mc_stack = std::mem::take(&mut self.mc_stack);
568 if let Some(res_obj) = form_dict.get(b"Resources")
570 && let Ok(PdfObj::Dict(d)) = self.resolver.deref(res_obj)
571 {
572 self.resources = d;
573 }
574
575 self.gstate.ctm = self.initial_ctm.concat(&bbox_to_rect).concat(&form_matrix);
580
581 let saved_content_stream_ctm = self.content_stream_ctm;
584 self.content_stream_ctm = self.gstate.ctm;
585
586 let form_data = self.resolver.stream_data_from_obj(&n_ref)?;
590 self.depth += 1;
591 let _ = self.interpret_stream(&form_data);
592 self.depth -= 1;
593
594 self.gstate_stack.truncate(saved_stack_depth);
596 self.content_stream_ctm = saved_content_stream_ctm;
597 self.resources = saved_resources;
598 self.mc_stack = saved_mc_stack;
599 self.gstate = saved_gstate;
600
601 self.display_list.push(DisplayElement::InitClip);
603 if let Some(ref clip) = self.gstate.clip_path {
604 self.display_list.push(DisplayElement::Clip {
605 path: clip.clone(),
606 params: ClipParams {
607 fill_rule: FillRule::NonZeroWinding,
608 ctm: Matrix::identity(),
609 stroke_params: None,
610 },
611 });
612 }
613
614 Ok(())
615 }
616
617 fn synthesize_annotation(
620 &mut self,
621 dict: &crate::objects::PdfDict,
622 rect: &[f64; 4],
623 ) -> Result<(), PdfError> {
624 let subtype = dict.get_name(b"Subtype").unwrap_or(b"");
625
626 let color = if let Some(c) = dict.get_array(b"C") {
628 let vals: Vec<f64> = c.iter().filter_map(|o| o.as_f64()).collect();
629 match vals.len() {
630 1 => DeviceColor::from_gray(vals[0]),
631 3 => DeviceColor::from_rgb(vals[0], vals[1], vals[2]),
632 4 => DeviceColor::from_cmyk(vals[0], vals[1], vals[2], vals[3]),
633 _ => DeviceColor::from_gray(0.0),
634 }
635 } else {
636 DeviceColor::from_gray(0.0)
637 };
638
639 let alpha = dict.get(b"CA").and_then(|o| o.as_f64()).unwrap_or(1.0);
641
642 let border_width = dict
644 .get(b"BS")
645 .and_then(|bs| self.resolver.deref(bs).ok())
646 .and_then(|bs| bs.as_dict().and_then(|d| d.get_f64(b"W")))
647 .or_else(|| {
648 dict.get_array(b"Border")
649 .and_then(|arr| arr.get(2).and_then(|o| o.as_f64()))
650 })
651 .unwrap_or(1.0);
652
653 let dash = dict
655 .get(b"BS")
656 .and_then(|bs| self.resolver.deref(bs).ok())
657 .and_then(|bs| {
658 let d = bs.as_dict()?;
659 let style = d.get_name(b"S")?;
660 if style == b"D" {
661 let arr = d
662 .get_array(b"D")
663 .map(|a| a.iter().filter_map(|o| o.as_f64()).collect::<Vec<_>>())
664 .unwrap_or_else(|| vec![3.0]);
665 Some(DashPattern {
666 array: arr,
667 offset: 0.0,
668 })
669 } else {
670 None
671 }
672 })
673 .unwrap_or_default();
674
675 let ctm = self.initial_ctm;
676
677 match subtype {
678 b"Line" => {
679 if let Some(l) = dict.get_array(b"L") {
681 let coords: Vec<f64> = l.iter().filter_map(|o| o.as_f64()).collect();
682 if coords.len() >= 4 {
683 let (x1, y1, x2, y2) = (coords[0], coords[1], coords[2], coords[3]);
684 let path = PsPath {
685 segments: vec![
686 PathSegment::MoveTo(x1, y1),
687 PathSegment::LineTo(x2, y2),
688 ],
689 };
690 self.display_list.push(DisplayElement::Stroke {
691 path,
692 params: StrokeParams {
693 color: color.clone(),
694 line_width: border_width,
695 line_cap: LineCap::Butt,
696 line_join: LineJoin::Miter,
697 miter_limit: 10.0,
698 dash_pattern: dash.clone(),
699 ctm,
700 stroke_adjust: false,
701 is_text_glyph: false,
702 overprint: false,
703 overprint_mode: 0,
704 opm_paired: false,
705 painted_channels: 0,
706 is_device_cmyk: false,
707 spot_color: None,
708 icc_color: None,
709 rendering_intent: 0,
710 transfer: Default::default(),
711 halftone: Default::default(),
712 bg_ucr: Default::default(),
713 alpha,
714 blend_mode: 0,
715 alpha_is_shape: false,
716 },
717 });
718 }
719 }
720 }
721 b"PolyLine" | b"Polygon" => {
722 if let Some(verts) = dict.get_array(b"Vertices") {
723 let coords: Vec<f64> = verts.iter().filter_map(|o| o.as_f64()).collect();
724 if coords.len() >= 4 {
725 let mut segs = vec![PathSegment::MoveTo(coords[0], coords[1])];
726 for pair in coords[2..].chunks_exact(2) {
727 segs.push(PathSegment::LineTo(pair[0], pair[1]));
728 }
729 if subtype == b"Polygon" {
730 segs.push(PathSegment::ClosePath);
731 }
732 let path = PsPath { segments: segs };
733 self.display_list.push(DisplayElement::Stroke {
734 path,
735 params: StrokeParams {
736 color: color.clone(),
737 line_width: border_width,
738 line_cap: LineCap::Butt,
739 line_join: LineJoin::Miter,
740 miter_limit: 10.0,
741 dash_pattern: dash.clone(),
742 ctm,
743 stroke_adjust: false,
744 is_text_glyph: false,
745 overprint: false,
746 overprint_mode: 0,
747 opm_paired: false,
748 painted_channels: 0,
749 is_device_cmyk: false,
750 spot_color: None,
751 icc_color: None,
752 rendering_intent: 0,
753 transfer: Default::default(),
754 halftone: Default::default(),
755 bg_ucr: Default::default(),
756 alpha,
757 blend_mode: 0,
758 alpha_is_shape: false,
759 },
760 });
761 }
762 }
763 }
764 b"Ink" => {
765 if let Some(ink_list) = dict.get_array(b"InkList") {
766 for stroke_obj in ink_list {
767 let stroke_arr = match stroke_obj {
768 crate::objects::PdfObj::Array(a) => a,
769 _ => continue,
770 };
771 let coords: Vec<f64> =
772 stroke_arr.iter().filter_map(|o| o.as_f64()).collect();
773 if coords.len() >= 4 {
774 let mut segs = vec![PathSegment::MoveTo(coords[0], coords[1])];
775 for pair in coords[2..].chunks_exact(2) {
776 segs.push(PathSegment::LineTo(pair[0], pair[1]));
777 }
778 let path = PsPath { segments: segs };
779 self.display_list.push(DisplayElement::Stroke {
780 path,
781 params: StrokeParams {
782 color: color.clone(),
783 line_width: border_width,
784 line_cap: LineCap::Round,
785 line_join: LineJoin::Round,
786 miter_limit: 10.0,
787 dash_pattern: DashPattern::default(),
788 ctm,
789 stroke_adjust: false,
790 is_text_glyph: false,
791 overprint: false,
792 overprint_mode: 0,
793 opm_paired: false,
794 painted_channels: 0,
795 is_device_cmyk: false,
796 spot_color: None,
797 icc_color: None,
798 rendering_intent: 0,
799 transfer: Default::default(),
800 halftone: Default::default(),
801 bg_ucr: Default::default(),
802 alpha,
803 blend_mode: 0,
804 alpha_is_shape: false,
805 },
806 });
807 }
808 }
809 }
810 }
811 b"Highlight" | b"StrikeOut" | b"Underline" | b"Squiggly" => {
812 if let Some(qp) = dict.get_array(b"QuadPoints") {
813 let pts: Vec<f64> = qp.iter().filter_map(|o| o.as_f64()).collect();
814 for quad in pts.chunks_exact(8) {
817 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" {
823 let path = PsPath {
825 segments: vec![
826 PathSegment::MoveTo(x1, y1),
827 PathSegment::LineTo(x2, y2),
828 PathSegment::LineTo(x4, y4),
829 PathSegment::LineTo(x3, y3),
830 PathSegment::ClosePath,
831 ],
832 };
833 self.display_list.push(DisplayElement::Fill {
834 path,
835 params: FillParams {
836 color: color.clone(),
837 fill_rule: FillRule::NonZeroWinding,
838 ctm,
839 is_text_glyph: false,
840 overprint: false,
841 overprint_mode: 0,
842 opm_paired: false,
843 painted_channels: 0,
844 is_device_cmyk: false,
845 spot_color: None,
846 icc_color: None,
847 rendering_intent: 0,
848 transfer: Default::default(),
849 halftone: Default::default(),
850 bg_ucr: Default::default(),
851 alpha,
852 blend_mode: 3, alpha_is_shape: false,
854 },
855 });
856 } else {
857 let (lx1, ly1, lx2, ly2) = if subtype == b"StrikeOut" {
859 (
861 (x1 + x3) / 2.0,
862 (y1 + y3) / 2.0,
863 (x2 + x4) / 2.0,
864 (y2 + y4) / 2.0,
865 )
866 } else {
867 (x3, y3, x4, y4)
869 };
870 let path = PsPath {
871 segments: vec![
872 PathSegment::MoveTo(lx1, ly1),
873 PathSegment::LineTo(lx2, ly2),
874 ],
875 };
876 self.display_list.push(DisplayElement::Stroke {
877 path,
878 params: StrokeParams {
879 color: color.clone(),
880 line_width: border_width,
881 line_cap: LineCap::Butt,
882 line_join: LineJoin::Miter,
883 miter_limit: 10.0,
884 dash_pattern: DashPattern::default(),
885 ctm,
886 stroke_adjust: false,
887 is_text_glyph: false,
888 overprint: false,
889 overprint_mode: 0,
890 opm_paired: false,
891 painted_channels: 0,
892 is_device_cmyk: false,
893 spot_color: None,
894 icc_color: None,
895 rendering_intent: 0,
896 transfer: Default::default(),
897 halftone: Default::default(),
898 bg_ucr: Default::default(),
899 alpha,
900 blend_mode: 0,
901 alpha_is_shape: false,
902 },
903 });
904 }
905 }
906 }
907 }
908 b"Square" => {
909 let has_ic = dict.get_array(b"IC").is_some();
911 if border_width < 0.001 && !has_ic {
912 return Ok(());
913 }
914 let path = PsPath {
915 segments: vec![
916 PathSegment::MoveTo(rect[0], rect[1]),
917 PathSegment::LineTo(rect[2], rect[1]),
918 PathSegment::LineTo(rect[2], rect[3]),
919 PathSegment::LineTo(rect[0], rect[3]),
920 PathSegment::ClosePath,
921 ],
922 };
923 if let Some(ic) = dict.get_array(b"IC") {
925 let vals: Vec<f64> = ic.iter().filter_map(|o| o.as_f64()).collect();
926 let ic_color = match vals.len() {
927 1 => DeviceColor::from_gray(vals[0]),
928 3 => DeviceColor::from_rgb(vals[0], vals[1], vals[2]),
929 4 => DeviceColor::from_cmyk(vals[0], vals[1], vals[2], vals[3]),
930 _ => DeviceColor::from_gray(1.0),
931 };
932 self.display_list.push(DisplayElement::Fill {
933 path: path.clone(),
934 params: FillParams {
935 color: ic_color,
936 fill_rule: FillRule::NonZeroWinding,
937 ctm,
938 is_text_glyph: false,
939 overprint: false,
940 overprint_mode: 0,
941 opm_paired: false,
942 painted_channels: 0,
943 is_device_cmyk: false,
944 spot_color: None,
945 icc_color: None,
946 rendering_intent: 0,
947 transfer: Default::default(),
948 halftone: Default::default(),
949 bg_ucr: Default::default(),
950 alpha,
951 blend_mode: 0,
952 alpha_is_shape: false,
953 },
954 });
955 }
956 if border_width < 0.001 {
957 return Ok(());
958 }
959 self.display_list.push(DisplayElement::Stroke {
960 path,
961 params: StrokeParams {
962 color,
963 line_width: border_width,
964 line_cap: LineCap::Butt,
965 line_join: LineJoin::Miter,
966 miter_limit: 10.0,
967 dash_pattern: dash,
968 ctm,
969 stroke_adjust: false,
970 is_text_glyph: false,
971 overprint: false,
972 overprint_mode: 0,
973 opm_paired: false,
974 painted_channels: 0,
975 is_device_cmyk: false,
976 spot_color: None,
977 icc_color: None,
978 rendering_intent: 0,
979 transfer: Default::default(),
980 halftone: Default::default(),
981 bg_ucr: Default::default(),
982 alpha,
983 blend_mode: 0,
984 alpha_is_shape: false,
985 },
986 });
987 }
988 b"Circle" => {
989 let has_ic = dict.get_array(b"IC").is_some();
990 if border_width < 0.001 && !has_ic {
991 return Ok(());
992 }
993 let cx = (rect[0] + rect[2]) / 2.0;
995 let cy = (rect[1] + rect[3]) / 2.0;
996 let rx = (rect[2] - rect[0]) / 2.0;
997 let ry = (rect[3] - rect[1]) / 2.0;
998 let k = 0.5522847498; let path = PsPath {
1000 segments: vec![
1001 PathSegment::MoveTo(cx + rx, cy),
1002 PathSegment::CurveTo {
1003 x1: cx + rx,
1004 y1: cy + ry * k,
1005 x2: cx + rx * k,
1006 y2: cy + ry,
1007 x3: cx,
1008 y3: cy + ry,
1009 },
1010 PathSegment::CurveTo {
1011 x1: cx - rx * k,
1012 y1: cy + ry,
1013 x2: cx - rx,
1014 y2: cy + ry * k,
1015 x3: cx - rx,
1016 y3: cy,
1017 },
1018 PathSegment::CurveTo {
1019 x1: cx - rx,
1020 y1: cy - ry * k,
1021 x2: cx - rx * k,
1022 y2: cy - ry,
1023 x3: cx,
1024 y3: cy - ry,
1025 },
1026 PathSegment::CurveTo {
1027 x1: cx + rx * k,
1028 y1: cy - ry,
1029 x2: cx + rx,
1030 y2: cy - ry * k,
1031 x3: cx + rx,
1032 y3: cy,
1033 },
1034 PathSegment::ClosePath,
1035 ],
1036 };
1037 if let Some(ic) = dict.get_array(b"IC") {
1038 let vals: Vec<f64> = ic.iter().filter_map(|o| o.as_f64()).collect();
1039 let ic_color = match vals.len() {
1040 1 => DeviceColor::from_gray(vals[0]),
1041 3 => DeviceColor::from_rgb(vals[0], vals[1], vals[2]),
1042 4 => DeviceColor::from_cmyk(vals[0], vals[1], vals[2], vals[3]),
1043 _ => DeviceColor::from_gray(1.0),
1044 };
1045 self.display_list.push(DisplayElement::Fill {
1046 path: path.clone(),
1047 params: FillParams {
1048 color: ic_color,
1049 fill_rule: FillRule::NonZeroWinding,
1050 ctm,
1051 is_text_glyph: false,
1052 overprint: false,
1053 overprint_mode: 0,
1054 opm_paired: false,
1055 painted_channels: 0,
1056 is_device_cmyk: false,
1057 spot_color: None,
1058 icc_color: None,
1059 rendering_intent: 0,
1060 transfer: Default::default(),
1061 halftone: Default::default(),
1062 bg_ucr: Default::default(),
1063 alpha,
1064 blend_mode: 0,
1065 alpha_is_shape: false,
1066 },
1067 });
1068 }
1069 if border_width < 0.001 {
1070 return Ok(());
1071 }
1072 self.display_list.push(DisplayElement::Stroke {
1073 path,
1074 params: StrokeParams {
1075 color,
1076 line_width: border_width,
1077 line_cap: LineCap::Butt,
1078 line_join: LineJoin::Miter,
1079 miter_limit: 10.0,
1080 dash_pattern: dash,
1081 ctm,
1082 stroke_adjust: false,
1083 is_text_glyph: false,
1084 overprint: false,
1085 overprint_mode: 0,
1086 opm_paired: false,
1087 painted_channels: 0,
1088 is_device_cmyk: false,
1089 spot_color: None,
1090 icc_color: None,
1091 rendering_intent: 0,
1092 transfer: Default::default(),
1093 halftone: Default::default(),
1094 bg_ucr: Default::default(),
1095 alpha,
1096 blend_mode: 0,
1097 alpha_is_shape: false,
1098 },
1099 });
1100 }
1101 _ => {
1102 }
1104 }
1105
1106 Ok(())
1107 }
1108
1109 fn interpret_stream(&mut self, data: &[u8]) -> Result<(), PdfError> {
1111 let saved_operand_stack = std::mem::take(&mut self.operand_stack);
1120 let result = self.interpret_stream_inner(data);
1121 self.operand_stack = saved_operand_stack;
1122 result
1123 }
1124
1125 fn interpret_stream_inner(&mut self, data: &[u8]) -> Result<(), PdfError> {
1126 let mut lexer = Lexer::new(data);
1127 let mut prev_token_was_glued_number = false;
1133 loop {
1134 let pos_before = lexer.pos();
1139 let glued_to_prev_number = prev_token_was_glued_number
1140 && pos_before < data.len()
1141 && !is_whitespace_byte(data[pos_before]);
1142 let tok = match lexer.next_token() {
1143 Ok(t) => t,
1144 Err(_) => {
1145 prev_token_was_glued_number = false;
1146 continue;
1147 }
1148 };
1149 prev_token_was_glued_number = false;
1152 match tok {
1153 Token::Eof => break,
1154 Token::Int(n) => {
1155 self.operand_stack.push(Operand::Int(n));
1156 let p = lexer.pos();
1157 prev_token_was_glued_number = p < data.len() && !is_whitespace_byte(data[p]);
1158 }
1159 Token::Real(f) => {
1160 self.operand_stack.push(Operand::Real(f));
1161 let p = lexer.pos();
1162 prev_token_was_glued_number = p < data.len() && !is_whitespace_byte(data[p]);
1163 }
1164 Token::Name(n) => self.operand_stack.push(Operand::Name(n)),
1165 Token::LitString(s) | Token::HexString(s) => {
1166 self.operand_stack.push(Operand::Str(s));
1167 }
1168 Token::Bool(b) => self.operand_stack.push(Operand::Bool(b)),
1169 Token::ArrayBegin => {
1170 let arr = Self::parse_inline_array(&mut lexer)?;
1171 self.operand_stack.push(Operand::Array(arr));
1172 }
1173 Token::DictBegin => {
1174 let dict = crate::lexer::parse_dict_body(&mut lexer)?;
1175 self.operand_stack.push(Operand::Dict(dict));
1176 }
1177 Token::Keyword(kw) => {
1178 let op = if matches!(kw.as_slice(), b"f" | b"B" | b"b" | b"W" | b"T") {
1182 let p = lexer.pos();
1183 if p < data.len() && data[p] == b'*' {
1184 lexer.set_pos(p + 1);
1185 let mut combined = kw;
1186 combined.push(b'*');
1187 combined
1188 } else {
1189 kw
1190 }
1191 } else if kw == b"d" {
1192 let p = lexer.pos();
1193 if p < data.len() && (data[p] == b'0' || data[p] == b'1') {
1194 lexer.set_pos(p + 1);
1195 let mut combined = kw;
1196 combined.push(data[p]);
1197 combined
1198 } else {
1199 kw
1200 }
1201 } else {
1202 kw
1203 };
1204
1205 if op == b"BI" {
1206 self.handle_inline_image(&mut lexer)?;
1207 } else if let Err(_e) = self.dispatch_operator(&op, glued_to_prev_number) {
1208 }
1209 self.operand_stack.clear();
1210 }
1211 Token::DictEnd | Token::ArrayEnd => {
1212 }
1214 }
1215 }
1216 Ok(())
1217 }
1218
1219 fn parse_inline_array(lexer: &mut Lexer) -> Result<Vec<PdfObj>, PdfError> {
1225 Self::parse_inline_array_at_depth(lexer, 1)
1226 }
1227
1228 fn parse_inline_array_at_depth(lexer: &mut Lexer, depth: u32) -> Result<Vec<PdfObj>, PdfError> {
1233 if depth > MAX_OBJECT_DEPTH {
1234 return Err(PdfError::NestingTooDeep {
1235 context: "content-stream array",
1236 limit: MAX_OBJECT_DEPTH,
1237 });
1238 }
1239 let mut elems = Vec::new();
1240 loop {
1241 let tok = lexer.next_token()?;
1242 match tok {
1243 Token::ArrayEnd | Token::Eof => break,
1244 Token::Int(n) => elems.push(PdfObj::Int(n)),
1245 Token::Real(f) => elems.push(PdfObj::Real(f)),
1246 Token::Name(n) => elems.push(PdfObj::Name(n)),
1247 Token::LitString(s) | Token::HexString(s) => elems.push(PdfObj::Str(s)),
1248 Token::Bool(b) => elems.push(PdfObj::Bool(b)),
1249 Token::ArrayBegin => {
1250 match Self::parse_inline_array_at_depth(lexer, depth + 1) {
1254 Ok(sub) => elems.push(PdfObj::Array(sub)),
1255 Err(_) => continue,
1256 }
1257 }
1258 Token::DictBegin => {
1259 let d = crate::lexer::parse_dict_body_at_depth(lexer, depth + 1)
1260 .unwrap_or_default();
1261 elems.push(PdfObj::Dict(d));
1262 }
1263 Token::Keyword(ref kw) if kw == b"null" => {
1264 elems.push(PdfObj::Null);
1265 }
1266 _ => {}
1267 }
1268 }
1269 Ok(elems)
1270 }
1271
1272 fn dispatch_operator(&mut self, op: &[u8], glued_to_prev_number: bool) -> Result<(), PdfError> {
1282 let expected_args: i32 = match op {
1288 b"m" | b"l" => 2,
1289 b"v" | b"y" | b"re" => 4,
1290 b"c" => 6,
1291 b"h" | b"S" | b"s" | b"f" | b"F" | b"f*" | b"B" | b"B*" | b"b" | b"b*" | b"n" => 0,
1292 _ => -1, };
1294 if expected_args >= 0
1295 && self.operand_stack.len() > expected_args as usize
1296 && !glued_to_prev_number
1297 {
1298 return Ok(());
1299 }
1300
1301 if self.bt_culled {
1303 if op == b"ET" {
1304 self.bt_culled = false;
1305 self.in_text = false;
1306 }
1307 self.operand_stack.clear();
1308 return Ok(());
1309 }
1310
1311 match op {
1312 b"q" => self.op_q(),
1314 b"Q" => self.op_big_q(),
1315 b"cm" => self.op_cm(),
1316 b"w" => self.op_w(),
1317 b"J" => self.op_big_j(),
1318 b"j" => self.op_j(),
1319 b"M" => self.op_big_m(),
1320 b"d" => self.op_d(),
1321 b"ri" => self.op_ri(),
1322 b"i" => self.op_i(),
1323 b"gs" => self.op_gs(),
1324
1325 b"m" => self.op_m(),
1327 b"l" => self.op_l(),
1328 b"c" => self.op_c(),
1329 b"v" => self.op_v(),
1330 b"y" => self.op_y(),
1331 b"h" => self.op_h(),
1332 b"re" => self.op_re(),
1333
1334 b"S" => self.op_big_s(),
1336 b"s" => self.op_small_s(),
1337 b"f" | b"F" => self.op_f(),
1338 b"f*" => self.op_f_star(),
1339 b"B" => self.op_big_b(),
1340 b"B*" => self.op_big_b_star(),
1341 b"b" => self.op_small_b(),
1342 b"b*" => self.op_small_b_star(),
1343 b"n" => self.op_n(),
1344
1345 b"W" => self.op_big_w(),
1347 b"W*" => self.op_big_w_star(),
1348
1349 b"G" if !self.d1_color_suppressed => self.op_big_g(),
1351 b"g" if !self.d1_color_suppressed => self.op_small_g(),
1352 b"RG" if !self.d1_color_suppressed => self.op_big_rg(),
1353 b"rg" if !self.d1_color_suppressed => self.op_small_rg(),
1354 b"K" if !self.d1_color_suppressed => self.op_big_k(),
1355 b"k" if !self.d1_color_suppressed => self.op_small_k(),
1356 b"G" | b"g" | b"RG" | b"rg" | b"K" | b"k" => Ok(()),
1357
1358 b"CS" if !self.d1_color_suppressed => self.op_big_cs(),
1360 b"cs" if !self.d1_color_suppressed => self.op_small_cs(),
1361 b"SC" | b"SCN" if !self.d1_color_suppressed => self.op_sc_stroke(),
1362 b"sc" | b"scn" if !self.d1_color_suppressed => self.op_sc_fill(),
1363 b"CS" | b"cs" | b"SC" | b"SCN" | b"sc" | b"scn" => Ok(()),
1364
1365 b"BT" => {
1367 self.in_text = true;
1368 self.gstate.text_matrix = Matrix::identity();
1369 self.gstate.text_line_matrix = Matrix::identity();
1370 Ok(())
1371 }
1372 b"ET" => {
1373 self.in_text = false;
1374 if let Some(clip_path) = self.text_clip_path.take()
1376 && !clip_path.is_empty()
1377 {
1378 self.display_list.push(DisplayElement::Clip {
1379 path: clip_path.clone(),
1380 params: ClipParams {
1381 fill_rule: FillRule::NonZeroWinding,
1382 ctm: Matrix::identity(),
1383 stroke_params: None,
1384 },
1385 });
1386 self.gstate
1388 .clip_stack
1389 .push((clip_path.clone(), FillRule::NonZeroWinding));
1390 self.gstate.clip_path = Some(clip_path);
1391 self.gstate.clip_path_version += 1;
1392 }
1393 Ok(())
1394 }
1395 b"Tf" => self.op_tf(),
1396 b"Tc" => {
1397 self.gstate.char_spacing = self.pop_number()?;
1398 Ok(())
1399 }
1400 b"Tw" => {
1401 self.gstate.word_spacing = self.pop_number()?;
1402 Ok(())
1403 }
1404 b"TL" => {
1405 self.gstate.text_leading = self.pop_number()?;
1406 Ok(())
1407 }
1408 b"Tr" => {
1409 self.gstate.text_rendering_mode = self.pop_number()? as i32;
1410 Ok(())
1411 }
1412 b"Ts" => {
1413 self.gstate.text_rise = self.pop_number()?;
1414 Ok(())
1415 }
1416 b"Tz" => {
1417 self.gstate.horizontal_scaling = self.pop_number()? / 100.0;
1418 Ok(())
1419 }
1420 b"Td" => self.op_td(),
1421 b"TD" => self.op_big_td(),
1422 b"Tm" => self.op_tm(),
1423 b"T*" => self.op_t_star(),
1424 b"Tj" => self.op_tj(),
1425 b"TJ" => self.op_big_tj(),
1426 b"'" => self.op_quote(),
1427 b"\"" => self.op_dblquote(),
1428
1429 b"Do" => self.op_do(),
1431
1432 b"sh" => self.op_sh(),
1434
1435 b"BMC" => {
1439 self.operand_stack.pop();
1440 self.mc_stack.push(MarkedContentFrame::Other);
1441 Ok(())
1442 }
1443 b"MP" => {
1444 self.operand_stack.pop();
1445 Ok(())
1446 }
1447 b"DP" => {
1448 self.operand_stack.pop();
1449 self.operand_stack.pop();
1450 Ok(())
1451 }
1452 b"BDC" => self.op_bdc(),
1453 b"EMC" => {
1454 if let Some(MarkedContentFrame::Ocg {
1455 parent_list,
1456 visibility,
1457 }) = self.mc_stack.pop()
1458 {
1459 let ocg_list = std::mem::replace(&mut self.display_list, parent_list);
1460 self.display_list.push(DisplayElement::OcgGroup {
1461 elements: ocg_list,
1462 visibility,
1463 });
1464 }
1465 Ok(())
1466 }
1467
1468 b"d0" => Ok(()),
1470 b"d1" => {
1471 self.d1_color_suppressed = true;
1482 self.gstate.stroke_color = self.gstate.fill_color.clone();
1483 self.gstate.stroke_color_space = self.gstate.fill_color_space.clone();
1484 self.gstate.stroke_pattern = None;
1485 self.gstate.stroke_shading_pattern = None;
1486 self.gstate.stroke_painted_channels = self.gstate.fill_painted_channels;
1487 self.gstate.stroke_is_device_cmyk = self.gstate.fill_is_device_cmyk;
1488 self.gstate.stroke_is_none = self.gstate.fill_is_none;
1489 Ok(())
1490 }
1491
1492 b"BX" | b"EX" => Ok(()),
1494
1495 _ => {
1496 Ok(())
1498 }
1499 }
1500 }
1501
1502 fn pop_number(&self) -> Result<f64, PdfError> {
1506 self.operand_stack
1507 .last()
1508 .and_then(|o| o.as_f64())
1509 .ok_or(PdfError::Other("expected number on operand stack".into()))
1510 }
1511
1512 fn get_numbers(&self, n: usize) -> Result<Vec<f64>, PdfError> {
1514 let len = self.operand_stack.len();
1515 if len < n {
1516 return Err(PdfError::Other(format!("need {n} operands, have {len}")));
1517 }
1518 let mut nums = Vec::with_capacity(n);
1519 for i in (len - n)..len {
1520 nums.push(
1521 self.operand_stack[i]
1522 .as_f64()
1523 .ok_or(PdfError::Other("expected number".into()))?,
1524 );
1525 }
1526 Ok(nums)
1527 }
1528
1529 fn transform(&self, x: f64, y: f64) -> (f64, f64) {
1531 self.gstate.ctm.transform_point(x, y)
1532 }
1533
1534 fn take_path(&mut self) -> PsPath {
1536 let path = std::mem::take(&mut self.current_path);
1537 self.current_point = None;
1538 self.subpath_start = None;
1539 path
1540 }
1541
1542 fn apply_pending_clip(&mut self) {
1544 if let Some((path, fill_rule)) = self.gstate.pending_clip.take() {
1545 let has_drawing_segments = path.segments.iter().any(|s| {
1550 matches!(
1551 s,
1552 PathSegment::LineTo(..) | PathSegment::CurveTo { .. } | PathSegment::ClosePath
1553 )
1554 });
1555 let has_moveto = path
1558 .segments
1559 .iter()
1560 .any(|s| matches!(s, PathSegment::MoveTo(..)));
1561 if !has_drawing_segments && has_moveto {
1562 let mut empty = PsPath::new();
1564 empty.segments.push(PathSegment::MoveTo(0.0, 0.0));
1565 empty.segments.push(PathSegment::LineTo(0.0, 0.0));
1566 empty.segments.push(PathSegment::ClosePath);
1567 self.display_list.push(DisplayElement::Clip {
1568 path: empty.clone(),
1569 params: ClipParams {
1570 fill_rule,
1571 ctm: Matrix::identity(),
1572 stroke_params: None,
1573 },
1574 });
1575 self.gstate.clip_stack.push((empty.clone(), fill_rule));
1576 self.gstate.clip_path = Some(empty);
1577 self.gstate.clip_path_version += 1;
1578 return;
1579 }
1580 if !has_drawing_segments {
1581 return;
1583 }
1584 let clip_path = path;
1585 self.display_list.push(DisplayElement::Clip {
1586 path: clip_path.clone(),
1587 params: ClipParams {
1588 fill_rule,
1589 ctm: Matrix::identity(),
1590 stroke_params: None,
1591 },
1592 });
1593 self.gstate.clip_stack.push((clip_path.clone(), fill_rule));
1595 self.gstate.clip_path = Some(clip_path);
1596 self.gstate.clip_path_version += 1;
1597 }
1598 }
1599
1600 fn op_q(&mut self) -> Result<(), PdfError> {
1603 self.gstate_stack.push(self.gstate.clone());
1604 Ok(())
1605 }
1606
1607 fn op_big_q(&mut self) -> Result<(), PdfError> {
1608 if let Some(saved) = self.gstate_stack.pop() {
1609 if self.soft_mask_scope.is_some() && self.gstate.smask_gen != saved.smask_gen {
1615 self.flush_soft_mask();
1616 self.nested_mask_flush_count += 1;
1617 }
1618
1619 let old_clip_version = self.gstate.clip_path_version;
1620 let old_font_name = std::mem::take(&mut self.gstate.text_font_name);
1621 self.gstate = saved;
1622 if self.gstate.clip_path_version != old_clip_version {
1625 self.restore_clip_from_stack();
1626 }
1627 if self.gstate.text_font_name != old_font_name && !self.gstate.text_font_name.is_empty()
1629 {
1630 let name = self.gstate.text_font_name.clone();
1631 self.resolve_current_font(&name);
1632 }
1633 }
1634 Ok(())
1635 }
1636
1637 fn restore_clip_from_stack(&mut self) {
1639 self.display_list.push(DisplayElement::InitClip);
1640 for (clip, fill_rule) in &self.gstate.clip_stack {
1641 self.display_list.push(DisplayElement::Clip {
1642 path: clip.clone(),
1643 params: ClipParams {
1644 fill_rule: *fill_rule,
1645 ctm: Matrix::identity(),
1646 stroke_params: None,
1647 },
1648 });
1649 }
1650 }
1651
1652 fn op_cm(&mut self) -> Result<(), PdfError> {
1653 let n = self.get_numbers(6)?;
1654 let m = Matrix::new(n[0], n[1], n[2], n[3], n[4], n[5]);
1655 self.gstate.ctm = self.gstate.ctm.concat(&m);
1657 Ok(())
1658 }
1659
1660 fn op_w(&mut self) -> Result<(), PdfError> {
1661 self.gstate.line_width = self.pop_number()?;
1662 Ok(())
1663 }
1664
1665 fn op_big_j(&mut self) -> Result<(), PdfError> {
1666 let cap = self.pop_number()? as i32;
1667 if let Some(lc) = LineCap::from_i32(cap) {
1668 self.gstate.line_cap = lc;
1669 }
1670 Ok(())
1671 }
1672
1673 fn op_j(&mut self) -> Result<(), PdfError> {
1674 let join = self.pop_number()? as i32;
1675 if let Some(lj) = LineJoin::from_i32(join) {
1676 self.gstate.line_join = lj;
1677 }
1678 Ok(())
1679 }
1680
1681 fn op_big_m(&mut self) -> Result<(), PdfError> {
1682 self.gstate.miter_limit = self.pop_number()?;
1683 Ok(())
1684 }
1685
1686 fn op_d(&mut self) -> Result<(), PdfError> {
1687 let len = self.operand_stack.len();
1689 if len < 2 {
1690 return Ok(());
1691 }
1692 let offset = self.operand_stack[len - 1].as_f64().unwrap_or(0.0);
1693 let array = match &self.operand_stack[len - 2] {
1694 Operand::Array(arr) => arr.iter().filter_map(|o| o.as_f64()).collect::<Vec<_>>(),
1695 _ => Vec::new(),
1696 };
1697 self.gstate.dash_pattern = DashPattern { array, offset };
1698 Ok(())
1699 }
1700
1701 fn op_ri(&mut self) -> Result<(), PdfError> {
1702 let Some(top) = self.operand_stack.pop() else {
1705 return Ok(());
1706 };
1707 let Some(name) = top.as_name() else {
1708 return Ok(());
1709 };
1710 self.gstate.rendering_intent = match name {
1711 b"Perceptual" => 0,
1712 b"RelativeColorimetric" => 1,
1713 b"Saturation" => 2,
1714 b"AbsoluteColorimetric" => 3,
1715 _ => 0,
1716 };
1717 Ok(())
1718 }
1719
1720 fn op_i(&mut self) -> Result<(), PdfError> {
1721 self.gstate.flatness = self.pop_number()?;
1722 Ok(())
1723 }
1724
1725 fn op_gs(&mut self) -> Result<(), PdfError> {
1726 let name = self
1727 .operand_stack
1728 .last()
1729 .and_then(|o| o.as_name())
1730 .ok_or(PdfError::Other("gs: expected name".into()))?
1731 .to_vec();
1732 self.apply_ext_gstate(&name)
1733 }
1734
1735 fn op_m(&mut self) -> Result<(), PdfError> {
1738 let n = self.get_numbers(2)?;
1739 let (dx, dy) = self.transform(n[0], n[1]);
1740 self.current_path.segments.push(PathSegment::MoveTo(dx, dy));
1741 self.current_point = Some((dx, dy));
1742 self.subpath_start = Some((dx, dy));
1743 Ok(())
1744 }
1745
1746 fn op_l(&mut self) -> Result<(), PdfError> {
1747 let n = self.get_numbers(2)?;
1748 let (dx, dy) = self.transform(n[0], n[1]);
1749 self.current_path.segments.push(PathSegment::LineTo(dx, dy));
1750 self.current_point = Some((dx, dy));
1751 Ok(())
1752 }
1753
1754 fn op_c(&mut self) -> Result<(), PdfError> {
1755 let n = self.get_numbers(6)?;
1756 let (x1, y1) = self.transform(n[0], n[1]);
1757 let (x2, y2) = self.transform(n[2], n[3]);
1758 let (x3, y3) = self.transform(n[4], n[5]);
1759 self.current_path.segments.push(PathSegment::CurveTo {
1760 x1,
1761 y1,
1762 x2,
1763 y2,
1764 x3,
1765 y3,
1766 });
1767 self.current_point = Some((x3, y3));
1768 Ok(())
1769 }
1770
1771 fn op_v(&mut self) -> Result<(), PdfError> {
1772 let n = self.get_numbers(4)?;
1773 let (x1, y1) = self.current_point.unwrap_or((0.0, 0.0));
1774 let (x2, y2) = self.transform(n[0], n[1]);
1775 let (x3, y3) = self.transform(n[2], n[3]);
1776 self.current_path.segments.push(PathSegment::CurveTo {
1777 x1,
1778 y1,
1779 x2,
1780 y2,
1781 x3,
1782 y3,
1783 });
1784 self.current_point = Some((x3, y3));
1785 Ok(())
1786 }
1787
1788 fn op_y(&mut self) -> Result<(), PdfError> {
1789 let n = self.get_numbers(4)?;
1790 let (x1, y1) = self.transform(n[0], n[1]);
1791 let (x3, y3) = self.transform(n[2], n[3]);
1792 self.current_path.segments.push(PathSegment::CurveTo {
1793 x1,
1794 y1,
1795 x2: x3,
1796 y2: y3,
1797 x3,
1798 y3,
1799 });
1800 self.current_point = Some((x3, y3));
1801 Ok(())
1802 }
1803
1804 fn op_h(&mut self) -> Result<(), PdfError> {
1805 self.current_path.segments.push(PathSegment::ClosePath);
1806 if let Some(start) = self.subpath_start {
1807 self.current_point = Some(start);
1808 }
1809 Ok(())
1810 }
1811
1812 fn op_re(&mut self) -> Result<(), PdfError> {
1813 let n = self.get_numbers(4)?;
1814 let (x, y, w, h) = (n[0], n[1], n[2], n[3]);
1815 let p0 = self.transform(x, y);
1817 let p1 = self.transform(x + w, y);
1818 let p2 = self.transform(x + w, y + h);
1819 let p3 = self.transform(x, y + h);
1820 self.current_path
1821 .segments
1822 .push(PathSegment::MoveTo(p0.0, p0.1));
1823 self.current_path
1824 .segments
1825 .push(PathSegment::LineTo(p1.0, p1.1));
1826 self.current_path
1827 .segments
1828 .push(PathSegment::LineTo(p2.0, p2.1));
1829 self.current_path
1830 .segments
1831 .push(PathSegment::LineTo(p3.0, p3.1));
1832 self.current_path.segments.push(PathSegment::ClosePath);
1833 self.current_point = Some(p0);
1834 self.subpath_start = Some(p0);
1835 Ok(())
1836 }
1837
1838 fn op_big_s(&mut self) -> Result<(), PdfError> {
1841 let path = self.take_path();
1843 if !path.is_empty() {
1844 self.emit_stroke(path);
1845 }
1846 self.apply_pending_clip();
1847 Ok(())
1848 }
1849
1850 fn op_small_s(&mut self) -> Result<(), PdfError> {
1851 self.op_h()?;
1853 self.op_big_s()
1854 }
1855
1856 fn op_f(&mut self) -> Result<(), PdfError> {
1857 let path = self.take_path();
1859 if !path.is_empty() {
1860 self.emit_fill(path, FillRule::NonZeroWinding);
1861 }
1862 self.apply_pending_clip();
1863 Ok(())
1864 }
1865
1866 fn op_f_star(&mut self) -> Result<(), PdfError> {
1867 let path = self.take_path();
1869 if !path.is_empty() {
1870 self.emit_fill(path, FillRule::EvenOdd);
1871 }
1872 self.apply_pending_clip();
1873 Ok(())
1874 }
1875
1876 fn op_big_b(&mut self) -> Result<(), PdfError> {
1877 let path = self.take_path();
1879 if !path.is_empty() {
1880 self.emit_fill_stroke(path, FillRule::NonZeroWinding);
1881 }
1882 self.apply_pending_clip();
1883 Ok(())
1884 }
1885
1886 fn op_big_b_star(&mut self) -> Result<(), PdfError> {
1887 let path = self.take_path();
1889 if !path.is_empty() {
1890 self.emit_fill_stroke(path, FillRule::EvenOdd);
1891 }
1892 self.apply_pending_clip();
1893 Ok(())
1894 }
1895
1896 fn op_small_b(&mut self) -> Result<(), PdfError> {
1897 self.op_h()?;
1899 self.op_big_b()
1900 }
1901
1902 fn op_small_b_star(&mut self) -> Result<(), PdfError> {
1903 self.op_h()?;
1905 self.op_big_b_star()
1906 }
1907
1908 fn emit_fill(&mut self, path: PsPath, fill_rule: FillRule) {
1910 if let Some(shading_box) = self.gstate.fill_shading_pattern.clone() {
1911 let bbox = path_device_bbox(&path);
1915 let mut group_dl = DisplayList::new();
1916 group_dl.push(DisplayElement::Clip {
1917 path,
1918 params: ClipParams {
1919 fill_rule,
1920 ctm: Matrix::identity(),
1921 stroke_params: None,
1922 },
1923 });
1924 for elem in shading_box.0.elements() {
1925 group_dl.push(elem.clone());
1926 }
1927 self.display_list.push(DisplayElement::Group {
1928 elements: group_dl,
1929 params: GroupParams {
1930 bbox,
1931 isolated: true,
1932 knockout: false,
1933 blend_mode: self.gstate.blend_mode,
1934 alpha: self.gstate.fill_alpha,
1935 color_space: stet_graphics::display_list::GroupColorSpace::Inherited,
1936 },
1937 });
1938 } else if let Some(pattern) = self.gstate.fill_pattern.clone() {
1939 self.display_list.push(DisplayElement::PatternFill {
1940 params: PatternFillParams {
1941 path,
1942 fill_rule,
1943 tile: pattern.tile,
1944 pattern_matrix: pattern.pattern_matrix,
1945 bbox: pattern.bbox,
1946 xstep: pattern.x_step,
1947 ystep: pattern.y_step,
1948 paint_type: pattern.paint_type,
1949 underlying_color: if pattern.paint_type == 2 {
1950 Some(self.gstate.fill_color.clone())
1951 } else {
1952 None
1953 },
1954 pattern_id: pattern.pattern_id,
1955 device_space_tile: false,
1956 flip_tile_y: false,
1957 stroke_params: None,
1958 overprint_mode: if self.gstate.overprint {
1959 self.gstate.overprint_mode
1960 } else {
1961 0
1962 },
1963 },
1964 });
1965 } else {
1966 self.display_list.push(DisplayElement::Fill {
1967 path,
1968 params: self.gstate.fill_params(fill_rule),
1969 });
1970 }
1971 }
1972
1973 fn emit_stroke(&mut self, path: PsPath) {
1981 let ctm = self.gstate.ctm;
1982 let user_path = if let Some(inv) = ctm.invert() {
1984 path.transform(&inv)
1985 } else {
1986 path.clone()
1987 };
1988
1989 if let Some(pattern) = self.gstate.stroke_pattern.clone() {
1992 let mut sp = self.gstate.stroke_params_with_ctm();
1993 sp.ctm = ctm;
1994 self.display_list.push(DisplayElement::PatternFill {
1995 params: PatternFillParams {
1996 path: user_path,
1997 fill_rule: FillRule::NonZeroWinding,
1998 tile: pattern.tile,
1999 pattern_matrix: pattern.pattern_matrix,
2000 bbox: pattern.bbox,
2001 xstep: pattern.x_step,
2002 ystep: pattern.y_step,
2003 paint_type: pattern.paint_type,
2004 underlying_color: if pattern.paint_type == 2 {
2005 Some(self.gstate.stroke_color.clone())
2006 } else {
2007 None
2008 },
2009 pattern_id: pattern.pattern_id,
2010 device_space_tile: false,
2011 flip_tile_y: false,
2012 stroke_params: Some(sp),
2013 overprint_mode: if self.gstate.overprint {
2014 self.gstate.overprint_mode
2015 } else {
2016 0
2017 },
2018 },
2019 });
2020 return;
2021 }
2022
2023 if let Some(shading_box) = self.gstate.stroke_shading_pattern.clone() {
2025 let mut sp = self.gstate.stroke_params_with_ctm();
2026 sp.ctm = ctm;
2027 let mut bbox = path_device_bbox(&path);
2030 let scale = self.gstate.ctm_scale_factor();
2031 let half_w = self.gstate.line_width * scale * 0.5;
2032 bbox[0] -= half_w;
2033 bbox[1] -= half_w;
2034 bbox[2] += half_w;
2035 bbox[3] += half_w;
2036 let mut group_dl = DisplayList::new();
2037 group_dl.push(DisplayElement::Clip {
2038 path: user_path,
2039 params: ClipParams {
2040 fill_rule: FillRule::NonZeroWinding,
2041 ctm: Matrix::identity(),
2042 stroke_params: Some(sp),
2043 },
2044 });
2045 for elem in shading_box.0.elements() {
2046 group_dl.push(elem.clone());
2047 }
2048 self.display_list.push(DisplayElement::Group {
2049 elements: group_dl,
2050 params: GroupParams {
2051 bbox,
2052 isolated: true,
2053 knockout: false,
2054 blend_mode: self.gstate.blend_mode,
2055 alpha: self.gstate.stroke_alpha,
2056 color_space: stet_graphics::display_list::GroupColorSpace::Inherited,
2057 },
2058 });
2059 return;
2060 }
2061
2062 let mut params = self.gstate.stroke_params_with_ctm();
2063 params.ctm = ctm;
2064 self.display_list.push(DisplayElement::Stroke {
2065 path: user_path,
2066 params,
2067 });
2068 }
2069
2070 fn emit_fill_stroke(&mut self, path: PsPath, fill_rule: FillRule) {
2078 let is_simple_fill =
2079 self.gstate.fill_shading_pattern.is_none() && self.gstate.fill_pattern.is_none();
2080 let is_simple_stroke =
2081 self.gstate.stroke_shading_pattern.is_none() && self.gstate.stroke_pattern.is_none();
2082
2083 let strict_opm1 = self.gstate.overprint_mode == 1 && self.gstate.opm_paired;
2096 let is_white_fill = self.gstate.fill_is_device_cmyk
2097 && self
2098 .gstate
2099 .fill_color
2100 .native_cmyk
2101 .map(|(c, m, y, k)| c == 0.0 && m == 0.0 && y == 0.0 && k == 0.0)
2102 .unwrap_or(false);
2103 let is_white_stroke = self.gstate.stroke_is_device_cmyk
2104 && self
2105 .gstate
2106 .stroke_color
2107 .native_cmyk
2108 .map(|(c, m, y, k)| c == 0.0 && m == 0.0 && y == 0.0 && k == 0.0)
2109 .unwrap_or(false);
2110 let has_any_overprint = self.gstate.overprint || self.gstate.overprint_stroke;
2111 let both_device_cmyk = self.gstate.fill_is_device_cmyk && self.gstate.stroke_is_device_cmyk;
2117 let fill_overprint_safe =
2118 !self.gstate.overprint || (is_white_fill && both_device_cmyk && !strict_opm1);
2119 let stroke_overprint_safe =
2120 !self.gstate.overprint_stroke || (is_white_stroke && both_device_cmyk && !strict_opm1);
2121 let mixed_space_with_overprint = has_any_overprint && !both_device_cmyk;
2126
2127 if is_simple_fill
2128 && is_simple_stroke
2129 && self.gstate.blend_mode == 0
2130 && fill_overprint_safe
2131 && stroke_overprint_safe
2132 && !mixed_space_with_overprint
2133 {
2134 let ctm = self.gstate.ctm;
2135
2136 let mut bbox = path_device_bbox(&path);
2137 let scale = self.gstate.ctm_scale_factor();
2138 let half_w = self.gstate.line_width * scale * 0.5;
2139 bbox[0] -= half_w;
2140 bbox[1] -= half_w;
2141 bbox[2] += half_w;
2142 bbox[3] += half_w;
2143
2144 let fill_elem = DisplayElement::Fill {
2145 path: path.clone(),
2146 params: self.gstate.fill_params(fill_rule),
2147 };
2148
2149 let user_path = if let Some(inv) = ctm.invert() {
2150 path.transform(&inv)
2151 } else {
2152 path.clone()
2153 };
2154 let mut stroke_params = self.gstate.stroke_params_with_ctm();
2155 stroke_params.ctm = ctm;
2156 let stroke_elem = DisplayElement::Stroke {
2157 path: user_path,
2158 params: stroke_params,
2159 };
2160
2161 let mut group_dl = DisplayList::new();
2162 group_dl.push(fill_elem);
2163 group_dl.push(stroke_elem);
2164
2165 self.display_list.push(DisplayElement::Group {
2166 elements: group_dl,
2167 params: stet_graphics::display_list::GroupParams {
2168 bbox,
2169 isolated: true,
2170 knockout: false,
2171 blend_mode: 0,
2172 alpha: 1.0,
2173 color_space: stet_graphics::display_list::GroupColorSpace::Inherited,
2174 },
2175 });
2176 } else {
2177 self.emit_fill(path.clone(), fill_rule);
2178 self.emit_stroke(path);
2179 }
2180 }
2181
2182 fn op_n(&mut self) -> Result<(), PdfError> {
2183 let _path = self.take_path();
2185 self.apply_pending_clip();
2186 Ok(())
2187 }
2188
2189 fn op_big_w(&mut self) -> Result<(), PdfError> {
2192 self.gstate.pending_clip = Some((self.current_path.clone(), FillRule::NonZeroWinding));
2194 Ok(())
2195 }
2196
2197 fn op_big_w_star(&mut self) -> Result<(), PdfError> {
2198 self.gstate.pending_clip = Some((self.current_path.clone(), FillRule::EvenOdd));
2200 Ok(())
2201 }
2202
2203 fn op_big_g(&mut self) -> Result<(), PdfError> {
2206 let g = self.pop_number()?;
2208 let (color, painted, is_cmyk) = self.gray_paint_for_gstate(g);
2209 self.gstate.stroke_color = color;
2210 self.gstate.stroke_color_space = ColorSpaceRef::DeviceGray;
2211 self.gstate.stroke_painted_channels = painted;
2212 self.gstate.stroke_is_device_cmyk = is_cmyk;
2213 self.gstate.stroke_is_none = false;
2214 self.gstate.stroke_spot_color = None;
2215 self.gstate.stroke_icc_color = None;
2216 self.gstate.stroke_pattern = None;
2217 self.gstate.stroke_shading_pattern = None;
2218 Ok(())
2219 }
2220
2221 fn op_small_g(&mut self) -> Result<(), PdfError> {
2222 let g = self.pop_number()?;
2224 let (color, painted, is_cmyk) = self.gray_paint_for_gstate(g);
2225 self.gstate.fill_color = color;
2226 self.gstate.fill_color_space = ColorSpaceRef::DeviceGray;
2227 self.gstate.fill_painted_channels = painted;
2228 self.gstate.fill_is_device_cmyk = is_cmyk;
2229 self.gstate.fill_is_none = false;
2230 self.gstate.fill_spot_color = None;
2231 self.gstate.fill_icc_color = None;
2232 self.gstate.fill_pattern = None;
2233 self.gstate.fill_shading_pattern = None;
2234 Ok(())
2235 }
2236
2237 fn gray_paint_for_gstate(&mut self, g: f64) -> (DeviceColor, u8, bool) {
2246 if self.pdfx_cmyk_intent && !self.in_smask_form {
2247 let k = (1.0 - g).clamp(0.0, 1.0);
2248 let color = DeviceColor::from_cmyk_icc(0.0, 0.0, 0.0, k, &mut self.icc_cache);
2249 (color, stet_graphics::device::CMYK_K, true)
2250 } else {
2251 (DeviceColor::from_gray(g), 0, false)
2252 }
2253 }
2254
2255 fn op_big_rg(&mut self) -> Result<(), PdfError> {
2256 let n = self.get_numbers(3)?;
2258 let (r, g, b) = self.cmyk_group_rgb(n[0], n[1], n[2]);
2259 self.gstate.stroke_color = DeviceColor::from_rgb(r, g, b);
2260 self.gstate.stroke_color_space = ColorSpaceRef::DeviceRGB;
2261 self.gstate.stroke_painted_channels = 0;
2262 self.gstate.stroke_is_device_cmyk = false;
2263 self.gstate.stroke_is_none = false;
2264 self.gstate.stroke_spot_color = None;
2265 self.gstate.stroke_icc_color = None;
2266 self.gstate.stroke_pattern = None;
2267 self.gstate.stroke_shading_pattern = None;
2268 Ok(())
2269 }
2270
2271 fn op_small_rg(&mut self) -> Result<(), PdfError> {
2272 let n = self.get_numbers(3)?;
2274 let (r, g, b) = self.cmyk_group_rgb(n[0], n[1], n[2]);
2275 self.gstate.fill_color = DeviceColor::from_rgb(r, g, b);
2276 self.gstate.fill_color_space = ColorSpaceRef::DeviceRGB;
2277 self.gstate.fill_painted_channels = 0;
2278 self.gstate.fill_is_device_cmyk = false;
2279 self.gstate.fill_is_none = false;
2280 self.gstate.fill_spot_color = None;
2281 self.gstate.fill_icc_color = None;
2282 self.gstate.fill_pattern = None;
2283 self.gstate.fill_shading_pattern = None;
2284 Ok(())
2285 }
2286
2287 fn cmyk_group_rgb(&mut self, r: f64, g: f64, b: f64) -> (f64, f64, f64) {
2290 if self.page_group_is_cmyk {
2291 if let Some(result) = self.icc_cache.round_trip_rgb_via_cmyk(r, g, b) {
2292 return result;
2293 }
2294 }
2295 (r, g, b)
2296 }
2297
2298 fn cmyk_group_promote_image(
2310 &self,
2311 cs: ImageColorSpace,
2312 data: Vec<u8>,
2313 width: u32,
2314 height: u32,
2315 ) -> (ImageColorSpace, Vec<u8>) {
2316 if !self.pdfx_cmyk_intent || self.in_smask_form {
2317 return (cs, data);
2318 }
2319 match cs {
2320 ImageColorSpace::DeviceGray => {
2321 let npx = (width as usize) * (height as usize);
2322 let take = npx.min(data.len());
2323 let mut new_data = vec![0u8; npx * 4];
2324 for i in 0..take {
2325 new_data[i * 4 + 3] = 255 - data[i];
2326 }
2327 (ImageColorSpace::DeviceCMYK, new_data)
2328 }
2329 ImageColorSpace::Separation {
2330 name,
2331 alt_space,
2332 tint_table,
2333 } => {
2334 if matches!(alt_space.as_ref(), ImageColorSpace::DeviceGray)
2335 && tint_table.num_outputs == 1
2336 {
2337 let samples = tint_table.samples_per_dim as usize;
2338 let mut new_data = Vec::with_capacity(samples * 4);
2339 for i in 0..samples {
2340 let g = tint_table.data[i] as f64;
2341 let k = (1.0 - g).clamp(0.0, 1.0) as f32;
2342 new_data.push(0.0);
2343 new_data.push(0.0);
2344 new_data.push(0.0);
2345 new_data.push(k);
2346 }
2347 let promoted_table = TintLookupTable {
2348 num_inputs: 1,
2349 num_outputs: 4,
2350 samples_per_dim: tint_table.samples_per_dim,
2351 data: new_data,
2352 };
2353 (
2354 ImageColorSpace::Separation {
2355 name,
2356 alt_space: Box::new(ImageColorSpace::DeviceCMYK),
2357 tint_table: Arc::new(promoted_table),
2358 },
2359 data,
2360 )
2361 } else {
2362 (
2363 ImageColorSpace::Separation {
2364 name,
2365 alt_space,
2366 tint_table,
2367 },
2368 data,
2369 )
2370 }
2371 }
2372 ImageColorSpace::DeviceN {
2373 names,
2374 alt_space,
2375 tint_table,
2376 } => {
2377 if matches!(alt_space.as_ref(), ImageColorSpace::DeviceGray)
2378 && tint_table.num_outputs == 1
2379 {
2380 let total = tint_table.data.len();
2381 let mut new_data = Vec::with_capacity(total * 4);
2382 for &g in &tint_table.data {
2383 let k = (1.0 - g as f64).clamp(0.0, 1.0) as f32;
2384 new_data.push(0.0);
2385 new_data.push(0.0);
2386 new_data.push(0.0);
2387 new_data.push(k);
2388 }
2389 let promoted_table = TintLookupTable {
2390 num_inputs: tint_table.num_inputs,
2391 num_outputs: 4,
2392 samples_per_dim: tint_table.samples_per_dim,
2393 data: new_data,
2394 };
2395 (
2396 ImageColorSpace::DeviceN {
2397 names,
2398 alt_space: Box::new(ImageColorSpace::DeviceCMYK),
2399 tint_table: Arc::new(promoted_table),
2400 },
2401 data,
2402 )
2403 } else {
2404 (
2405 ImageColorSpace::DeviceN {
2406 names,
2407 alt_space,
2408 tint_table,
2409 },
2410 data,
2411 )
2412 }
2413 }
2414 other => (other, data),
2415 }
2416 }
2417
2418 fn cmyk_group_promote_color(&mut self, color: &mut DeviceColor) {
2424 if !self.pdfx_cmyk_intent || self.in_smask_form {
2425 return;
2426 }
2427 let Some((c, m, y, k)) = color.native_cmyk else {
2428 return;
2429 };
2430 if !(c == 0.0 && m == 0.0 && y == 0.0) {
2435 return;
2436 }
2437 if k == 0.0 {
2445 return;
2446 }
2447 if (color.r - color.g).abs() > f64::EPSILON || (color.r - color.b).abs() > f64::EPSILON {
2448 return;
2449 }
2450 if let Some((r, g, b)) = self.icc_cache.convert_cmyk(0.0, 0.0, 0.0, k) {
2451 color.r = r;
2452 color.g = g;
2453 color.b = b;
2454 }
2455 }
2456
2457 fn op_big_k(&mut self) -> Result<(), PdfError> {
2458 let n = self.get_numbers(4)?;
2460 self.gstate.stroke_color =
2461 DeviceColor::from_cmyk_icc(n[0], n[1], n[2], n[3], &mut self.icc_cache);
2462 self.gstate.stroke_color_space = ColorSpaceRef::DeviceCMYK;
2463 self.gstate.stroke_painted_channels = stet_graphics::device::CMYK_ALL;
2464 self.gstate.stroke_is_device_cmyk = true;
2465 self.gstate.stroke_is_none = false;
2466 self.gstate.stroke_spot_color = None;
2467 self.gstate.stroke_icc_color = None;
2468 self.gstate.stroke_pattern = None;
2469 self.gstate.stroke_shading_pattern = None;
2470 Ok(())
2471 }
2472
2473 fn op_small_k(&mut self) -> Result<(), PdfError> {
2474 let n = self.get_numbers(4)?;
2476 self.gstate.fill_color =
2477 DeviceColor::from_cmyk_icc(n[0], n[1], n[2], n[3], &mut self.icc_cache);
2478 self.gstate.fill_color_space = ColorSpaceRef::DeviceCMYK;
2479 self.gstate.fill_painted_channels = stet_graphics::device::CMYK_ALL;
2480 self.gstate.fill_is_device_cmyk = true;
2481 self.gstate.fill_is_none = false;
2482 self.gstate.fill_spot_color = None;
2483 self.gstate.fill_icc_color = None;
2484 self.gstate.fill_pattern = None;
2485 self.gstate.fill_shading_pattern = None;
2486 Ok(())
2487 }
2488
2489 fn op_big_cs(&mut self) -> Result<(), PdfError> {
2492 let name = self
2494 .operand_stack
2495 .last()
2496 .and_then(|o| o.as_name())
2497 .ok_or(PdfError::Other("CS: expected name".into()))?
2498 .to_vec();
2499 self.gstate.stroke_color_space = name_to_cs_ref(&name);
2500 Ok(())
2501 }
2502
2503 fn op_small_cs(&mut self) -> Result<(), PdfError> {
2504 let name = self
2506 .operand_stack
2507 .last()
2508 .and_then(|o| o.as_name())
2509 .ok_or(PdfError::Other("cs: expected name".into()))?
2510 .to_vec();
2511 self.gstate.fill_color_space = name_to_cs_ref(&name);
2512 Ok(())
2513 }
2514
2515 fn resolve_cs_cached(
2519 &mut self,
2520 cs_ref: &ColorSpaceRef,
2521 ) -> Result<ResolvedColorSpace, PdfError> {
2522 if let ColorSpaceRef::Named(name) = cs_ref {
2523 match name.as_slice() {
2525 b"DeviceGray" | b"G" => return Ok(ResolvedColorSpace::DeviceGray),
2526 b"DeviceRGB" | b"RGB" => return Ok(ResolvedColorSpace::DeviceRGB),
2527 b"DeviceCMYK" | b"CMYK" => return Ok(ResolvedColorSpace::DeviceCMYK),
2528 b"Pattern" => return Ok(ResolvedColorSpace::Pattern),
2529 _ => {}
2530 }
2531 if self.cs_index.is_none() {
2533 let mut index = std::collections::HashMap::new();
2534 if let Some(cs_dict) = self.resolve_resource_subdict(b"ColorSpace") {
2535 for (k, v) in cs_dict.entries() {
2536 index.insert(k.clone(), v.clone());
2537 }
2538 }
2539 self.cs_index = Some(index);
2540 }
2541 if let Some(cs_obj) = self.cs_index.as_ref().unwrap().get(name.as_slice()) {
2542 let cs_obj = cs_obj.clone();
2543 resolve_color_space_obj(&cs_obj, self.resolver)
2544 } else {
2545 resolve_color_space(
2549 &ColorSpaceRef::Named(name.to_vec()),
2550 &self.resources,
2551 self.resolver,
2552 )
2553 }
2554 } else {
2555 resolve_color_space(cs_ref, &self.resources, self.resolver)
2556 }
2557 }
2558
2559 fn op_sc_stroke(&mut self) -> Result<(), PdfError> {
2560 if matches!(self.operand_stack.last(), Some(Operand::Name(_))) {
2565 return self.handle_pattern_stroke();
2566 }
2567 let cs = self.resolve_cs_cached(&self.gstate.stroke_color_space.clone())?;
2568 if matches!(cs, ResolvedColorSpace::Pattern) {
2569 return self.handle_pattern_stroke();
2570 }
2571 let n = cs.num_components();
2572 if n == 0 {
2573 return Ok(());
2574 }
2575 let nums = self.get_numbers(n)?;
2576 self.gstate.stroke_painted_channels = painted_channels_for_cs(&cs);
2577 self.gstate.stroke_is_none = cs.is_none_colorant();
2578 self.gstate.stroke_is_device_cmyk = matches!(
2579 cs,
2580 ResolvedColorSpace::DeviceCMYK | ResolvedColorSpace::ICCBased { n: 4, .. }
2581 );
2582 let intent = self.gstate.rendering_intent;
2583 let mut color = color_space::components_to_device_color_icc_with_intent(
2584 &cs,
2585 &nums,
2586 Some(&mut self.icc_cache),
2587 intent,
2588 );
2589 self.cmyk_group_promote_color(&mut color);
2590 self.gstate.stroke_color = color;
2591 self.gstate.stroke_spot_color =
2592 color_space::build_spot_color(&cs, &nums, &mut self.spot_tint_table_cache);
2593 self.gstate.stroke_icc_color = color_space::build_icc_color(&cs, &nums);
2594 self.gstate.stroke_pattern = None;
2595 self.gstate.stroke_shading_pattern = None;
2596 Ok(())
2597 }
2598
2599 fn op_sc_fill(&mut self) -> Result<(), PdfError> {
2600 if matches!(self.operand_stack.last(), Some(Operand::Name(_))) {
2605 return self.handle_pattern_fill();
2606 }
2607 let cs = self.resolve_cs_cached(&self.gstate.fill_color_space.clone())?;
2608 if matches!(cs, ResolvedColorSpace::Pattern) {
2609 return self.handle_pattern_fill();
2610 }
2611 let n = cs.num_components();
2612 if n == 0 {
2613 return Ok(());
2614 }
2615 let nums = self.get_numbers(n)?;
2616 self.gstate.fill_painted_channels = painted_channels_for_cs(&cs);
2617 self.gstate.fill_is_none = cs.is_none_colorant();
2618 self.gstate.fill_is_device_cmyk = matches!(
2619 cs,
2620 ResolvedColorSpace::DeviceCMYK | ResolvedColorSpace::ICCBased { n: 4, .. }
2621 );
2622 let intent = self.gstate.rendering_intent;
2623 let mut color = color_space::components_to_device_color_icc_with_intent(
2624 &cs,
2625 &nums,
2626 Some(&mut self.icc_cache),
2627 intent,
2628 );
2629 self.cmyk_group_promote_color(&mut color);
2630 self.gstate.fill_color = color;
2631 self.gstate.fill_spot_color =
2632 color_space::build_spot_color(&cs, &nums, &mut self.spot_tint_table_cache);
2633 self.gstate.fill_icc_color = color_space::build_icc_color(&cs, &nums);
2634 self.gstate.fill_pattern = None;
2635 self.gstate.fill_shading_pattern = None;
2636 Ok(())
2637 }
2638
2639 fn op_tf(&mut self) -> Result<(), PdfError> {
2642 let len = self.operand_stack.len();
2644 if len < 2 {
2645 return Ok(());
2646 }
2647 self.gstate.font_size = self.operand_stack[len - 1].as_f64().unwrap_or(12.0);
2648 if let Some(name) = self.operand_stack[len - 2].as_name() {
2649 let name = name.to_vec();
2650 self.gstate.text_font_name = name.clone();
2651 self.resolve_current_font(&name);
2652 }
2653 Ok(())
2654 }
2655
2656 fn resolve_current_font(&mut self, name: &[u8]) {
2658 if let Some(cached) = self.font_cache.get(name) {
2661 let font_ref = self
2665 .resolve_resource_subdict(b"Font")
2666 .and_then(|fd| fd.get(name).cloned());
2667 if let Some(PdfObj::Ref(obj_num, _)) = &font_ref {
2668 let obj_key = obj_num.to_le_bytes().to_vec();
2669 if let Some(obj_cached) = self.font_cache.get(&obj_key) {
2670 self.current_font = Some(Arc::clone(obj_cached));
2673 return;
2674 }
2675 } else {
2679 self.current_font = Some(Arc::clone(cached));
2681 return;
2682 }
2683 }
2684
2685 let font_ref = self
2687 .resolve_resource_subdict(b"Font")
2688 .and_then(|fd| fd.get(name).cloned());
2689 let font_ref = match font_ref {
2690 Some(r) => r,
2691 None => {
2692 if let Some(fallback) = font::fallback_font(self.font_provider.as_ref()) {
2694 let arc = Arc::new(fallback);
2695 self.font_cache.insert(name.to_vec(), Arc::clone(&arc));
2696 self.current_font = Some(arc);
2697 } else {
2698 self.current_font = None;
2699 }
2700 return;
2701 }
2702 };
2703
2704 if let PdfObj::Ref(obj_num, _) = &font_ref {
2706 let obj_key = obj_num.to_le_bytes().to_vec();
2707 if let Some(cached) = self.font_cache.get(&obj_key) {
2708 let arc = Arc::clone(cached);
2709 self.font_cache.insert(name.to_vec(), Arc::clone(&arc));
2710 self.current_font = Some(arc);
2711 return;
2712 }
2713 }
2714
2715 match font::resolve_font(self.resolver, &font_ref, self.font_provider.as_ref()) {
2716 Ok(font) => {
2717 let arc = Arc::new(font);
2718 if let PdfObj::Ref(obj_num, _) = &font_ref {
2720 self.font_cache
2721 .insert(obj_num.to_le_bytes().to_vec(), Arc::clone(&arc));
2722 }
2723 self.font_cache.insert(name.to_vec(), Arc::clone(&arc));
2724 self.current_font = Some(arc);
2725 }
2726 Err(e) => {
2727 use std::sync::Mutex;
2729 static WARNED: Mutex<Vec<String>> = Mutex::new(Vec::new());
2730 let msg = format!("font /{}: {}", String::from_utf8_lossy(name), e);
2731 if let Ok(mut set) = WARNED.lock()
2732 && !set.contains(&msg)
2733 {
2734 eprintln!("warning: {msg}");
2735 set.push(msg);
2736 }
2737 if let Some(fallback) = font::fallback_font(self.font_provider.as_ref()) {
2739 let arc = Arc::new(fallback);
2740 self.font_cache.insert(name.to_vec(), Arc::clone(&arc));
2741 self.current_font = Some(arc);
2742 } else {
2743 self.current_font = None;
2744 }
2745 }
2746 }
2747 }
2748
2749 fn check_text_cull(&mut self) {
2752 if let Some((y_lo, y_hi)) = self.form_cull_y {
2753 let text_y = self.gstate.text_matrix.ty;
2755 if text_y < y_lo || text_y > y_hi {
2756 self.bt_culled = true;
2757 }
2758 }
2759 }
2760
2761 fn op_td(&mut self) -> Result<(), PdfError> {
2762 let n = self.get_numbers(2)?;
2763 let m = Matrix::translate(n[0], n[1]);
2764 self.gstate.text_line_matrix = self.gstate.text_line_matrix.concat(&m);
2765 self.gstate.text_matrix = self.gstate.text_line_matrix;
2766 self.check_text_cull();
2767 Ok(())
2768 }
2769
2770 fn op_big_td(&mut self) -> Result<(), PdfError> {
2771 let n = self.get_numbers(2)?;
2772 self.gstate.text_leading = -n[1];
2773 let m = Matrix::translate(n[0], n[1]);
2774 self.gstate.text_line_matrix = self.gstate.text_line_matrix.concat(&m);
2775 self.gstate.text_matrix = self.gstate.text_line_matrix;
2776 self.check_text_cull();
2777 Ok(())
2778 }
2779
2780 fn op_tm(&mut self) -> Result<(), PdfError> {
2781 let n = self.get_numbers(6)?;
2782 let m = Matrix::new(n[0], n[1], n[2], n[3], n[4], n[5]);
2783 self.gstate.text_matrix = m;
2784 self.gstate.text_line_matrix = m;
2785 self.check_text_cull();
2786 Ok(())
2787 }
2788
2789 fn op_t_star(&mut self) -> Result<(), PdfError> {
2790 let leading = self.gstate.text_leading;
2791 let m = Matrix::translate(0.0, -leading);
2792 self.gstate.text_line_matrix = self.gstate.text_line_matrix.concat(&m);
2793 self.gstate.text_matrix = self.gstate.text_line_matrix;
2794 Ok(())
2795 }
2796
2797 fn op_tj(&mut self) -> Result<(), PdfError> {
2800 let text = match self.operand_stack.last() {
2801 Some(Operand::Str(s)) => s.clone(),
2802 _ => return Ok(()),
2803 };
2804 self.show_text(&text);
2805 Ok(())
2806 }
2807
2808 fn op_big_tj(&mut self) -> Result<(), PdfError> {
2809 let arr = match self.operand_stack.last() {
2810 Some(Operand::Array(a)) => a.clone(),
2811 _ => return Ok(()),
2812 };
2813 let vertical = self.current_font.as_ref().is_some_and(|f| f.wmode() == 1);
2814 for elem in &arr {
2815 match elem {
2816 PdfObj::Str(s) => self.show_text(s),
2817 PdfObj::Int(n) => {
2818 let shift = -*n as f64 / 1000.0 * self.gstate.font_size;
2819 let m = if vertical {
2820 Matrix::translate(0.0, shift)
2821 } else {
2822 Matrix::translate(shift * self.gstate.horizontal_scaling, 0.0)
2823 };
2824 self.gstate.text_matrix = self.gstate.text_matrix.concat(&m);
2825 }
2826 PdfObj::Real(f) => {
2827 let shift = -f / 1000.0 * self.gstate.font_size;
2828 let m = if vertical {
2829 Matrix::translate(0.0, shift)
2830 } else {
2831 Matrix::translate(shift * self.gstate.horizontal_scaling, 0.0)
2832 };
2833 self.gstate.text_matrix = self.gstate.text_matrix.concat(&m);
2834 }
2835 _ => {}
2836 }
2837 }
2838 Ok(())
2839 }
2840
2841 fn op_quote(&mut self) -> Result<(), PdfError> {
2842 self.op_t_star()?;
2844 self.op_tj()
2845 }
2846
2847 fn op_dblquote(&mut self) -> Result<(), PdfError> {
2848 let len = self.operand_stack.len();
2850 if len < 3 {
2851 return Ok(());
2852 }
2853 self.gstate.word_spacing = self.operand_stack[len - 3].as_f64().unwrap_or(0.0);
2854 self.gstate.char_spacing = self.operand_stack[len - 2].as_f64().unwrap_or(0.0);
2855 self.op_t_star()?;
2857 self.op_tj()
2858 }
2859
2860 fn show_text(&mut self, text: &[u8]) {
2862 let font = match &self.current_font {
2863 Some(f) => Arc::clone(f),
2864 None => return,
2865 };
2866
2867 let font_size = self.gstate.font_size;
2868 let char_spacing = self.gstate.char_spacing;
2869 let word_spacing = self.gstate.word_spacing;
2870 let text_rise = self.gstate.text_rise;
2871 let th = self.gstate.horizontal_scaling;
2872 let font_matrix = font.font_matrix();
2873 let render_mode = self.gstate.text_rendering_mode;
2874
2875 if font.is_composite() {
2876 let mut i = 0;
2879 while i < text.len() {
2880 let code_width = font.code_width(text[i]);
2881 if code_width == 1 {
2882 let raw_code = text[i] as u32;
2886 let extra = if raw_code == 0x20 { word_spacing } else { 0.0 };
2887 i += 1;
2888 let cid = font.resolve_code_to_cid(raw_code) as u16;
2889 self.render_cid_glyph(
2890 &font,
2891 cid,
2892 font_size,
2893 char_spacing,
2894 th,
2895 text_rise,
2896 &font_matrix,
2897 render_mode,
2898 extra,
2899 );
2900 } else if i + 1 >= text.len() {
2901 let byte = text[i];
2903 i += 1;
2904 self.render_unicode_glyph(
2905 byte,
2906 font_size,
2907 char_spacing,
2908 th,
2909 text_rise,
2910 &font_matrix,
2911 render_mode,
2912 );
2913 } else {
2914 let width = code_width.min(text.len() - i);
2916 let mut raw_code = 0u32;
2917 for b in &text[i..i + width] {
2918 raw_code = (raw_code << 8) | (*b as u32);
2919 }
2920 let cid = font.resolve_code_to_cid(raw_code) as u16;
2921 let (cid, consumed) = if cid == 0 || (cid == raw_code as u16 && width > 2) {
2926 let byte_cid = font.resolve_code_to_cid(text[i] as u32) as u16;
2927 if byte_cid != 0 && byte_cid != text[i] as u16 {
2928 (byte_cid, 1)
2929 } else {
2930 (cid, width)
2931 }
2932 } else {
2933 (cid, width)
2934 };
2935 let extra = if consumed == 1 && text[i] == 0x20 {
2937 word_spacing
2938 } else {
2939 0.0
2940 };
2941 i += consumed;
2942 if font.has_cid_glyph(cid) {
2943 self.render_cid_glyph(
2945 &font,
2946 cid,
2947 font_size,
2948 char_spacing,
2949 th,
2950 text_rise,
2951 &font_matrix,
2952 render_mode,
2953 extra,
2954 );
2955 } else {
2956 let lo_cid = (raw_code & 0xFF) as u16;
2960 if lo_cid > 0 && font.has_cid_glyph(lo_cid) {
2961 self.render_cid_glyph(
2962 &font,
2963 lo_cid,
2964 font_size,
2965 char_spacing,
2966 th,
2967 text_rise,
2968 &font_matrix,
2969 render_mode,
2970 extra,
2971 );
2972 } else if raw_code <= 0xFF {
2973 self.render_unicode_glyph(
2977 text[i - 2],
2978 font_size,
2979 char_spacing,
2980 th,
2981 text_rise,
2982 &font_matrix,
2983 render_mode,
2984 );
2985 self.render_unicode_glyph(
2986 text[i - 1],
2987 font_size,
2988 char_spacing,
2989 th,
2990 text_rise,
2991 &font_matrix,
2992 render_mode,
2993 );
2994 } else {
2995 self.render_cid_glyph_unicode_fallback(
2998 &font,
2999 cid,
3000 raw_code,
3001 font_size,
3002 char_spacing,
3003 th,
3004 text_rise,
3005 &font_matrix,
3006 render_mode,
3007 extra,
3008 );
3009 }
3010 }
3011 }
3012 }
3013 } else if font.is_type3() {
3014 let fm = font.font_matrix();
3017 let visible = (render_mode & 3) != 3; for &byte in text {
3019 if visible {
3020 self.show_type3_glyph(&font, byte);
3021 }
3022
3023 let w0_glyph = font.glyph_width(byte);
3024 let w0 = w0_glyph * fm.a;
3025 let mut tx = w0 * font_size + char_spacing;
3026 if byte == b' ' {
3027 tx += word_spacing;
3028 }
3029 tx *= th;
3030 let advance = Matrix::translate(tx, 0.0);
3031 self.gstate.text_matrix = self.gstate.text_matrix.concat(&advance);
3032 }
3033 } else {
3034 for &byte in text {
3036 if let Some(glyph_path) = font.glyph_path(byte) {
3037 let text_state_matrix =
3038 Matrix::new(font_size * th, 0.0, 0.0, font_size, 0.0, text_rise);
3039 let trm = self
3040 .gstate
3041 .ctm
3042 .concat(&self.gstate.text_matrix)
3043 .concat(&text_state_matrix)
3044 .concat(&font_matrix);
3045
3046 let device_path = glyph_path.transform(&trm);
3047 if !device_path.is_empty() {
3048 self.emit_text_glyph(device_path, render_mode);
3049 }
3050 }
3051
3052 let w0 = font.glyph_width(byte);
3053 let mut tx = w0 * font_size + char_spacing;
3054 if byte == b' ' {
3055 tx += word_spacing;
3056 }
3057 tx *= th;
3058 let advance = Matrix::translate(tx, 0.0);
3059 self.gstate.text_matrix = self.gstate.text_matrix.concat(&advance);
3060 }
3061 }
3062 }
3063
3064 fn render_cid_glyph(
3066 &mut self,
3067 font: &PdfFont,
3068 cid: u16,
3069 font_size: f64,
3070 char_spacing: f64,
3071 th: f64,
3072 text_rise: f64,
3073 font_matrix: &Matrix,
3074 render_mode: i32,
3075 extra_advance: f64,
3076 ) {
3077 let vertical = font.wmode() == 1;
3078 if let Some(glyph_path) = font.glyph_path_cid(cid) {
3079 let text_state_matrix = if vertical {
3080 let [_w1, v_x, v_y] = font.vertical_metrics_cid(cid);
3083 Matrix::new(
3084 font_size,
3085 0.0,
3086 0.0,
3087 font_size,
3088 -v_x / 1000.0 * font_size,
3089 -v_y / 1000.0 * font_size,
3090 )
3091 } else {
3092 Matrix::new(font_size * th, 0.0, 0.0, font_size, 0.0, text_rise)
3093 };
3094 let trm = self
3095 .gstate
3096 .ctm
3097 .concat(&self.gstate.text_matrix)
3098 .concat(&text_state_matrix)
3099 .concat(font_matrix);
3100 let device_path = glyph_path.transform(&trm);
3101 if !device_path.is_empty() {
3102 self.emit_text_glyph(device_path, render_mode);
3103 }
3104 }
3105 if vertical {
3106 let [w1, _vx, _vy] = font.vertical_metrics_cid(cid);
3107 let ty = w1 / 1000.0 * font_size + char_spacing + extra_advance;
3108 let advance = Matrix::translate(0.0, ty);
3109 self.gstate.text_matrix = self.gstate.text_matrix.concat(&advance);
3110 } else {
3111 let w0 = font.glyph_width_cid(cid);
3112 let tx = (w0 * font_size + char_spacing + extra_advance) * th;
3113 let advance = Matrix::translate(tx, 0.0);
3114 self.gstate.text_matrix = self.gstate.text_matrix.concat(&advance);
3115 }
3116 }
3117
3118 fn render_cid_glyph_unicode_fallback(
3122 &mut self,
3123 font: &PdfFont,
3124 cid: u16,
3125 unicode: u32,
3126 font_size: f64,
3127 char_spacing: f64,
3128 th: f64,
3129 text_rise: f64,
3130 font_matrix: &Matrix,
3131 render_mode: i32,
3132 extra_advance: f64,
3133 ) {
3134 let vertical = font.wmode() == 1;
3135 if let Some(glyph_path) = font.glyph_path_unicode(unicode as u16) {
3137 let text_state_matrix = if vertical {
3138 let [_w1, v_x, v_y] = font.vertical_metrics_cid(cid);
3139 Matrix::new(
3140 font_size,
3141 0.0,
3142 0.0,
3143 font_size,
3144 -v_x / 1000.0 * font_size,
3145 -v_y / 1000.0 * font_size,
3146 )
3147 } else {
3148 Matrix::new(font_size * th, 0.0, 0.0, font_size, 0.0, text_rise)
3149 };
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 if vertical {
3162 let [w1, _vx, _vy] = font.vertical_metrics_cid(cid);
3163 let ty = w1 / 1000.0 * font_size + char_spacing + extra_advance;
3164 let advance = Matrix::translate(0.0, ty);
3165 self.gstate.text_matrix = self.gstate.text_matrix.concat(&advance);
3166 } else {
3167 let w0 = font.glyph_width_cid(cid);
3168 let tx = (w0 * font_size + char_spacing + extra_advance) * th;
3169 let advance = Matrix::translate(tx, 0.0);
3170 self.gstate.text_matrix = self.gstate.text_matrix.concat(&advance);
3171 }
3172 }
3173
3174 fn render_unicode_glyph(
3177 &mut self,
3178 byte: u8,
3179 font_size: f64,
3180 char_spacing: f64,
3181 th: f64,
3182 text_rise: f64,
3183 font_matrix: &Matrix,
3184 render_mode: i32,
3185 ) {
3186 let unicode = font::winansi_byte_to_unicode(byte);
3187 if let Some(glyph_path) = self
3188 .current_font
3189 .as_ref()
3190 .and_then(|f| f.glyph_path_unicode(unicode))
3191 {
3192 let text_state_matrix =
3193 Matrix::new(font_size * th, 0.0, 0.0, font_size, 0.0, text_rise);
3194 let trm = self
3195 .gstate
3196 .ctm
3197 .concat(&self.gstate.text_matrix)
3198 .concat(&text_state_matrix)
3199 .concat(font_matrix);
3200 let device_path = glyph_path.transform(&trm);
3201 if !device_path.is_empty() {
3202 self.emit_text_glyph(device_path, render_mode);
3203 }
3204 }
3205 let w0 = self
3206 .current_font
3207 .as_ref()
3208 .map(|f| f.glyph_width_unicode(unicode))
3209 .unwrap_or(0.0);
3210 let tx = (w0 * font_size + char_spacing) * th;
3211 let advance = Matrix::translate(tx, 0.0);
3212 self.gstate.text_matrix = self.gstate.text_matrix.concat(&advance);
3213 }
3214
3215 fn show_type3_glyph(&mut self, font: &PdfFont, char_code: u8) {
3223 if self.depth >= MAX_CONTENT_NESTING {
3224 return;
3225 }
3226 let proc_data = match font.type3_char_proc(char_code) {
3227 Some(data) => data.to_vec(),
3228 None => return,
3229 };
3230 let resources = match font.type3_resources() {
3231 Some(r) => r.clone(),
3232 None => return,
3233 };
3234
3235 let font_size = self.gstate.font_size;
3236 let text_rise = self.gstate.text_rise;
3237 let font_matrix = font.font_matrix();
3238
3239 let th = self.gstate.horizontal_scaling;
3241 let text_state_matrix = Matrix::new(font_size * th, 0.0, 0.0, font_size, 0.0, text_rise);
3242 let trm = self
3243 .gstate
3244 .ctm
3245 .concat(&self.gstate.text_matrix)
3246 .concat(&text_state_matrix)
3247 .concat(&font_matrix);
3248 let stack_depth_before = self.gstate_stack.len();
3251 self.gstate_stack.push(self.gstate.clone());
3252 let mut merged = self.resources.clone();
3257 for (key, value) in resources.entries() {
3258 merged.insert(key.clone(), value.clone());
3259 }
3260 let saved_resources = std::mem::replace(&mut self.resources, merged);
3261 let saved_display_list = std::mem::take(&mut self.display_list);
3262 let saved_path = std::mem::take(&mut self.current_path);
3263 let saved_point = self.current_point.take();
3264 let saved_subpath = self.subpath_start.take();
3265 let saved_font = self.current_font.clone();
3266 let saved_in_text = self.in_text;
3267 let saved_content_stream_ctm = self.content_stream_ctm;
3268 let saved_mc_stack = std::mem::take(&mut self.mc_stack);
3269
3270 self.gstate.ctm = trm;
3271 self.content_stream_ctm = trm;
3274
3275 let saved_d1 = self.d1_color_suppressed;
3276 self.d1_color_suppressed = false;
3277 self.depth += 1;
3278 let _ = self.interpret_stream(&proc_data);
3279 self.depth -= 1;
3280 self.d1_color_suppressed = saved_d1;
3281 let glyph_elements = std::mem::replace(&mut self.display_list, saved_display_list);
3283 self.resources = saved_resources;
3284 self.current_path = saved_path;
3285 self.current_point = saved_point;
3286 self.subpath_start = saved_subpath;
3287 self.current_font = saved_font;
3288 self.in_text = saved_in_text;
3289 self.content_stream_ctm = saved_content_stream_ctm;
3290 self.mc_stack = saved_mc_stack;
3291 self.gstate_stack.truncate(stack_depth_before + 1);
3294 if let Some(saved) = self.gstate_stack.pop() {
3295 self.gstate = saved;
3296 }
3297
3298 for elem in glyph_elements.into_elements() {
3300 self.display_list.push(elem);
3301 }
3302 }
3303
3304 fn emit_text_glyph(&mut self, device_path: PsPath, render_mode: i32) {
3309 let mode = render_mode & 3; let clip = render_mode & 4 != 0; match mode {
3313 0 => {
3314 self.emit_text_fill(device_path.clone());
3316 }
3317 1 => {
3318 self.emit_text_stroke(device_path.clone());
3320 }
3321 2 => {
3322 self.emit_text_fill(device_path.clone());
3324 self.emit_text_stroke(device_path.clone());
3325 }
3326 _ => {} }
3328
3329 if clip {
3331 let tcp = self.text_clip_path.get_or_insert_with(PsPath::new);
3332 tcp.segments.extend_from_slice(&device_path.segments);
3333 }
3334 }
3335
3336 fn emit_text_fill(&mut self, path: PsPath) {
3339 if let Some(shading_box) = self.gstate.fill_shading_pattern.clone() {
3340 let bbox = path_device_bbox(&path);
3343 let mut group_dl = DisplayList::new();
3344 group_dl.push(DisplayElement::Clip {
3345 path,
3346 params: ClipParams {
3347 fill_rule: FillRule::NonZeroWinding,
3348 ctm: Matrix::identity(),
3349 stroke_params: None,
3350 },
3351 });
3352 for elem in shading_box.0.elements() {
3353 group_dl.push(elem.clone());
3354 }
3355 self.display_list.push(DisplayElement::Group {
3356 elements: group_dl,
3357 params: GroupParams {
3358 bbox,
3359 isolated: true,
3360 knockout: false,
3361 blend_mode: self.gstate.blend_mode,
3362 alpha: self.gstate.fill_alpha,
3363 color_space: stet_graphics::display_list::GroupColorSpace::Inherited,
3364 },
3365 });
3366 } else if let Some(pattern) = self.gstate.fill_pattern.clone() {
3367 self.display_list.push(DisplayElement::PatternFill {
3368 params: PatternFillParams {
3369 path,
3370 fill_rule: FillRule::NonZeroWinding,
3371 tile: pattern.tile,
3372 pattern_matrix: pattern.pattern_matrix,
3373 bbox: pattern.bbox,
3374 xstep: pattern.x_step,
3375 ystep: pattern.y_step,
3376 paint_type: pattern.paint_type,
3377 underlying_color: if pattern.paint_type == 2 {
3378 Some(self.gstate.fill_color.clone())
3379 } else {
3380 None
3381 },
3382 pattern_id: pattern.pattern_id,
3383 device_space_tile: false,
3384 flip_tile_y: false,
3385 stroke_params: None,
3386 overprint_mode: if self.gstate.overprint {
3387 self.gstate.overprint_mode
3388 } else {
3389 0
3390 },
3391 },
3392 });
3393 } else {
3394 let mut params = self.gstate.fill_params(FillRule::NonZeroWinding);
3395 params.is_text_glyph = true;
3396 self.display_list
3397 .push(DisplayElement::Fill { path, params });
3398 }
3399 }
3400
3401 fn emit_text_stroke(&mut self, path: PsPath) {
3404 if let Some(shading_box) = self.gstate.stroke_shading_pattern.clone() {
3406 let mut sp = self.gstate.stroke_params();
3407 sp.is_text_glyph = true;
3408 let mut bbox = path_device_bbox(&path);
3410 let half_w = sp.line_width * 0.5;
3411 bbox[0] -= half_w;
3412 bbox[1] -= half_w;
3413 bbox[2] += half_w;
3414 bbox[3] += half_w;
3415 let mut group_dl = DisplayList::new();
3416 group_dl.push(DisplayElement::Clip {
3417 path,
3418 params: ClipParams {
3419 fill_rule: FillRule::NonZeroWinding,
3420 ctm: Matrix::identity(),
3421 stroke_params: Some(sp),
3422 },
3423 });
3424 for elem in shading_box.0.elements() {
3425 group_dl.push(elem.clone());
3426 }
3427 self.display_list.push(DisplayElement::Group {
3428 elements: group_dl,
3429 params: GroupParams {
3430 bbox,
3431 isolated: true,
3432 knockout: false,
3433 blend_mode: self.gstate.blend_mode,
3434 alpha: self.gstate.stroke_alpha,
3435 color_space: stet_graphics::display_list::GroupColorSpace::Inherited,
3436 },
3437 });
3438 return;
3439 }
3440
3441 if let Some(pattern) = self.gstate.stroke_pattern.clone() {
3444 let mut sp = self.gstate.stroke_params();
3445 sp.is_text_glyph = true;
3446 self.display_list.push(DisplayElement::PatternFill {
3447 params: PatternFillParams {
3448 path,
3449 fill_rule: FillRule::NonZeroWinding,
3450 tile: pattern.tile,
3451 pattern_matrix: pattern.pattern_matrix,
3452 bbox: pattern.bbox,
3453 xstep: pattern.x_step,
3454 ystep: pattern.y_step,
3455 paint_type: pattern.paint_type,
3456 underlying_color: if pattern.paint_type == 2 {
3457 Some(self.gstate.stroke_color.clone())
3458 } else {
3459 None
3460 },
3461 pattern_id: pattern.pattern_id,
3462 device_space_tile: false,
3463 flip_tile_y: false,
3464 stroke_params: Some(sp),
3465 overprint_mode: if self.gstate.overprint {
3466 self.gstate.overprint_mode
3467 } else {
3468 0
3469 },
3470 },
3471 });
3472 return;
3473 }
3474
3475 let mut params = self.gstate.stroke_params();
3476 params.is_text_glyph = true;
3477 self.display_list
3478 .push(DisplayElement::Stroke { path, params });
3479 }
3480
3481 fn op_bdc(&mut self) -> Result<(), PdfError> {
3490 let props = self.operand_stack.pop();
3491 let tag = self.operand_stack.pop();
3492
3493 let is_oc = matches!(&tag, Some(Operand::Name(n)) if n == b"OC");
3494 if !is_oc {
3495 self.mc_stack.push(MarkedContentFrame::Other);
3496 return Ok(());
3497 }
3498
3499 let mut pushed = false;
3501 if let Some(Operand::Name(prop_name)) = props
3502 && let Some(props_dict) = self.resolve_resource_subdict(b"Properties")
3503 && let Some(ocg_obj) = props_dict.get(&prop_name)
3504 {
3505 let visibility = self.build_visibility(ocg_obj);
3506 let parent_list = std::mem::replace(&mut self.display_list, DisplayList::new());
3507 self.mc_stack.push(MarkedContentFrame::Ocg {
3508 parent_list,
3509 visibility,
3510 });
3511 pushed = true;
3512 }
3513 if !pushed {
3514 self.mc_stack.push(MarkedContentFrame::Other);
3517 }
3518
3519 Ok(())
3520 }
3521
3522 fn is_ocg_off(&self, ocg_obj: &PdfObj) -> bool {
3526 if let Some((obj_num, _)) = ocg_obj.as_ref() {
3528 if let Ok(resolved) = self.resolver.deref(ocg_obj) {
3530 if let Some(dict) = resolved.as_dict() {
3531 if dict.get_name(b"Type") == Some(b"OCMD") {
3532 return self.is_ocmd_off(dict);
3533 }
3534 }
3535 }
3536 return self.ocg_off.contains(&obj_num);
3538 }
3539 if let Some(dict) = ocg_obj.as_dict() {
3541 if dict.get_name(b"Type") == Some(b"OCMD") {
3542 return self.is_ocmd_off(dict);
3543 }
3544 }
3545 false
3546 }
3547
3548 fn build_visibility(&self, ocg_obj: &PdfObj) -> OcgVisibility {
3560 let resolved = self.resolver.deref(ocg_obj).ok();
3561 let dict = resolved.as_ref().and_then(|o| o.as_dict());
3562
3563 if let Some(dict) = dict
3564 && dict.get_name(b"Type") == Some(b"OCMD")
3565 {
3566 let default_visible = !self.is_ocg_off(ocg_obj);
3571 let throwaway = crate::diagnostics::WarningSink::new();
3577 return crate::layers::ocmd::build_ocmd_visibility(
3578 self.resolver,
3579 dict,
3580 default_visible,
3581 &throwaway,
3582 );
3583 }
3584
3585 if let Some((ocg_id, _)) = ocg_obj.as_ref() {
3586 return OcgVisibility::Single {
3587 ocg_id,
3588 default_visible: !self.ocg_off.contains(&ocg_id),
3589 };
3590 }
3591
3592 OcgVisibility::Single {
3594 ocg_id: 0,
3595 default_visible: !self.is_ocg_off(ocg_obj),
3596 }
3597 }
3598
3599 fn is_ocmd_off(&self, ocmd: &PdfDict) -> bool {
3604 let policy = ocmd.get_name(b"P").unwrap_or(b"AnyOn");
3605
3606 let mut ocg_nums = Vec::new();
3608 if let Some(ocgs_obj) = ocmd.get(b"OCGs") {
3609 match ocgs_obj {
3610 PdfObj::Ref(num, _) => ocg_nums.push(*num),
3611 PdfObj::Array(arr) => {
3612 for item in arr {
3613 if let Some((num, _)) = item.as_ref() {
3614 ocg_nums.push(num);
3615 }
3616 }
3617 }
3618 _ => {}
3619 }
3620 }
3621 if ocg_nums.is_empty() {
3622 return false;
3623 }
3624
3625 let visible = match policy {
3627 b"AllOn" => ocg_nums.iter().all(|n| !self.ocg_off.contains(n)),
3628 b"AnyOff" => ocg_nums.iter().any(|n| self.ocg_off.contains(n)),
3629 b"AllOff" => ocg_nums.iter().all(|n| self.ocg_off.contains(n)),
3630 _ => ocg_nums.iter().any(|n| !self.ocg_off.contains(n)),
3631 };
3632 !visible
3633 }
3634
3635 fn op_do(&mut self) -> Result<(), PdfError> {
3638 let name = self
3639 .operand_stack
3640 .last()
3641 .and_then(|o| o.as_name())
3642 .ok_or(PdfError::Other("Do: expected name".into()))?
3643 .to_vec();
3644
3645 let xobj_dict = self
3647 .resolve_resource_subdict(b"XObject")
3648 .ok_or(PdfError::Other("no XObject resources".into()))?;
3649 let xobj_ref = xobj_dict.get(&name).ok_or_else(|| {
3650 PdfError::Other(format!(
3651 "XObject /{} not found",
3652 String::from_utf8_lossy(&name)
3653 ))
3654 })?;
3655 let xobj_ref_clone = xobj_ref.clone();
3657 let xobj = self.resolver.deref(xobj_ref)?;
3658 let dict = xobj
3659 .as_dict()
3660 .ok_or(PdfError::Other("XObject is not a stream".into()))?;
3661
3662 let xobj_visibility = dict.get(b"OC").map(|oc_obj| self.build_visibility(oc_obj));
3666
3667 let mut wrapped = false;
3668 if let Some(visibility) = xobj_visibility {
3669 let parent_list = std::mem::replace(&mut self.display_list, DisplayList::new());
3670 self.mc_stack.push(MarkedContentFrame::Ocg {
3671 parent_list,
3672 visibility,
3673 });
3674 wrapped = true;
3675 }
3676
3677 let subtype = dict.get_name(b"Subtype").unwrap_or(b"");
3678 match subtype {
3679 b"Image" => self.handle_image_xobject(&xobj_ref_clone, dict)?,
3680 b"Form" => self.handle_form_xobject(&xobj_ref_clone, dict)?,
3681 _ => {}
3682 }
3683
3684 if wrapped {
3686 if let Some(MarkedContentFrame::Ocg {
3687 parent_list,
3688 visibility,
3689 }) = self.mc_stack.pop()
3690 {
3691 let ocg_list = std::mem::replace(&mut self.display_list, parent_list);
3692 self.display_list.push(DisplayElement::OcgGroup {
3693 elements: ocg_list,
3694 visibility,
3695 });
3696 }
3697 }
3698
3699 Ok(())
3700 }
3701
3702 fn handle_image_xobject(&mut self, obj: &PdfObj, dict: &PdfDict) -> Result<(), PdfError> {
3704 if let PdfObj::Ref(obj_num, _) = obj {
3707 if let Some(cached) = self.image_cache.get(obj_num).cloned() {
3708 return self.emit_cached_image(cached);
3709 }
3710 }
3711
3712 let width = validate_image_dimension(self.resolve_dict_int(dict, b"Width"))
3719 .ok_or(PdfError::Other("image has missing or invalid Width".into()))?;
3720 let height = validate_image_dimension(self.resolve_dict_int(dict, b"Height")).ok_or(
3721 PdfError::Other("image has missing or invalid Height".into()),
3722 )?;
3723 validate_image_size(width, height)
3724 .ok_or(PdfError::Other("image dimensions too large".into()))?;
3725
3726 let is_image_mask = dict
3728 .get(b"ImageMask")
3729 .and_then(|o| match o {
3730 PdfObj::Bool(b) => Some(*b),
3731 _ => None,
3732 })
3733 .unwrap_or(false);
3734
3735 let bpc = if is_image_mask {
3736 1
3737 } else {
3738 validate_bits_per_component(dict.get_int(b"BitsPerComponent"))
3739 .ok_or(PdfError::Other("image has invalid BitsPerComponent".into()))?
3740 };
3741
3742 let gstate_intent = self.gstate.rendering_intent;
3749 let image_intent = match dict
3750 .get(b"Intent")
3751 .and_then(|o| self.resolver.deref(o).ok())
3752 {
3753 Some(PdfObj::Name(n)) => match n.as_slice() {
3754 b"Perceptual" => 0u8,
3755 b"RelativeColorimetric" => 1,
3756 b"Saturation" => 2,
3757 b"AbsoluteColorimetric" => 3,
3758 _ => gstate_intent,
3759 },
3760 _ => gstate_intent,
3761 };
3762
3763 let has_explicit_cs = dict.get(b"ColorSpace").is_some();
3765 let resolved_cs = if is_image_mask {
3766 None
3767 } else if let Some(cs_obj) = dict.get(b"ColorSpace") {
3768 match resolve_color_space_obj(cs_obj, self.resolver) {
3769 Ok(cs) => Some(cs),
3770 Err(_) => {
3771 Some(match bpc {
3775 1 => ResolvedColorSpace::DeviceGray,
3776 _ => ResolvedColorSpace::DeviceRGB,
3777 })
3778 }
3779 }
3780 } else {
3781 Some(ResolvedColorSpace::DeviceRGB)
3783 };
3784
3785 let polarity = if is_image_mask {
3786 if let Some(arr) = dict.get_array(b"Decode") {
3787 let vals: Vec<f64> = arr.iter().filter_map(|o| o.as_f64()).collect();
3788 vals.len() >= 2 && vals[0] > 0.5
3789 } else {
3790 false
3791 }
3792 } else {
3793 false
3794 };
3795
3796 let smask_in_data = dict.get_int(b"SMaskInData").unwrap_or(0);
3799
3800 let filter_name_raw = dict.get_name(b"Filter");
3801 let filter_is_dct = matches!(filter_name_raw, Some(b"DCTDecode" | b"DCT"));
3802 let filter_is_jpx = matches!(filter_name_raw, Some(b"JPXDecode" | b"JPX"))
3804 || dict.get_array(b"Filter").is_some_and(|arr| {
3805 arr.iter()
3806 .any(|f| matches!(f.as_name(), Some(b"JPXDecode" | b"JPX")))
3807 });
3808
3809 let cs_is_indexed = matches!(resolved_cs, Some(ResolvedColorSpace::Indexed { .. }));
3814 let sample_data = if filter_is_dct {
3815 if let Some(raw) = self.resolver.raw_stream_bytes(obj)
3816 && let Some((_jw, jh)) = crate::filters::jpeg_dimensions(raw)
3817 && jh > height * 2
3818 {
3819 let mut patched = raw.to_vec();
3820 crate::filters::patch_jpeg_sof_height(&mut patched, height as u16);
3821 crate::filters::decode_stream(
3822 &patched,
3823 &[crate::filters::Filter::DCTDecode],
3824 &[],
3825 None,
3826 )?
3827 } else {
3828 self.resolver.stream_data_from_obj(obj)?
3829 }
3830 } else if filter_is_jpx && cs_is_indexed {
3831 #[cfg(feature = "jpx")]
3836 {
3837 if let Some(raw) = self.resolver.raw_stream_bytes(obj) {
3838 let jp2_data = crate::filters::decode_pre_jpx(raw, dict);
3839 let (mut data, bpc) = crate::filters::decode_jpx_no_palette(&jp2_data)?;
3840 if bpc < 8 {
3844 let max_val = ((1u32 << bpc) - 1) as f64;
3845 for b in data.iter_mut() {
3846 *b = (*b as f64 / 255.0 * max_val).round() as u8;
3847 }
3848 }
3849 data
3850 } else {
3851 self.resolver.stream_data_from_obj(obj)?
3852 }
3853 }
3854 #[cfg(not(feature = "jpx"))]
3855 {
3856 self.resolver.stream_data_from_obj(obj)?
3857 }
3858 } else {
3859 self.resolver.stream_data_from_obj(obj)?
3860 };
3861
3862 let (width, height) = if filter_is_dct {
3866 if let Some(raw) = self.resolver.raw_stream_bytes(obj)
3867 && let Some((jw, jh)) = crate::filters::jpeg_dimensions(raw)
3868 && (jw != width || jh != height)
3869 && jw <= width * 2
3870 && jh <= height * 2
3871 {
3872 (jw, jh)
3873 } else {
3874 (width, height)
3875 }
3876 } else if filter_is_jpx {
3877 #[cfg(feature = "jpx")]
3878 {
3879 if let Some(raw) = self.resolver.raw_stream_bytes(obj) {
3883 let jp2_data = crate::filters::decode_pre_jpx(raw, dict);
3885 if let Some((jw, jh)) = crate::filters::jpx_dimensions(&jp2_data)
3886 && (jw != width || jh != height)
3887 {
3888 (jw, jh)
3889 } else {
3890 (width, height)
3891 }
3892 } else {
3893 (width, height)
3894 }
3895 }
3896 #[cfg(not(feature = "jpx"))]
3897 {
3898 (width, height)
3899 }
3900 } else {
3901 (width, height)
3902 };
3903
3904 let (resolved_cs, sample_data, smask_in_data_alpha) =
3914 if !is_image_mask && filter_is_jpx && has_explicit_cs {
3915 let n_cs = resolved_cs
3916 .as_ref()
3917 .map_or(3, |cs| cs.num_components() as usize);
3918 let pixels = width as usize * height as usize;
3919 let decoded_comps = if pixels > 0 {
3920 sample_data.len() / pixels
3921 } else {
3922 n_cs
3923 };
3924 if smask_in_data >= 1 && decoded_comps == n_cs + 1 {
3925 let mut color_data = Vec::with_capacity(pixels.saturating_mul(n_cs));
3927 let mut alpha_data = Vec::with_capacity(pixels);
3928 for chunk in sample_data.chunks_exact(decoded_comps) {
3929 color_data.extend_from_slice(&chunk[..n_cs]);
3930 alpha_data.push(chunk[n_cs]);
3931 }
3932 (resolved_cs, color_data, Some(alpha_data))
3933 } else if decoded_comps > n_cs {
3934 let mut color_data = Vec::with_capacity(pixels.saturating_mul(n_cs));
3937 for chunk in sample_data.chunks_exact(decoded_comps) {
3938 color_data.extend_from_slice(&chunk[..n_cs]);
3939 }
3940 (resolved_cs, color_data, None)
3941 } else {
3942 (resolved_cs, sample_data, None)
3944 }
3945 } else if !is_image_mask && !has_explicit_cs {
3946 let pixels = width as usize * height as usize;
3947 if pixels > 0 {
3948 let n_comps = sample_data.len() / pixels;
3949 if n_comps == 4 && self.is_jpx_rgba(obj) {
3953 if smask_in_data >= 1 {
3954 let mut rgba = sample_data;
3957 for chunk in rgba.chunks_exact_mut(4) {
3958 let a = chunk[3] as u16;
3959 if a == 0 {
3960 chunk[0] = 0;
3961 chunk[1] = 0;
3962 chunk[2] = 0;
3963 } else if a < 255 {
3964 chunk[0] = ((chunk[0] as u16 * a + 127) / 255) as u8;
3965 chunk[1] = ((chunk[1] as u16 * a + 127) / 255) as u8;
3966 chunk[2] = ((chunk[2] as u16 * a + 127) / 255) as u8;
3967 }
3968 }
3969 (None, rgba, None)
3970 } else {
3971 let mut rgb = Vec::with_capacity(pixels * 3);
3973 for chunk in sample_data.chunks_exact(4) {
3974 rgb.push(chunk[0]);
3975 rgb.push(chunk[1]);
3976 rgb.push(chunk[2]);
3977 }
3978 (Some(ResolvedColorSpace::DeviceRGB), rgb, None)
3979 }
3980 } else {
3981 let cs = match n_comps {
3982 1 => ResolvedColorSpace::DeviceGray,
3983 4 => ResolvedColorSpace::DeviceCMYK,
3984 _ => ResolvedColorSpace::DeviceRGB,
3985 };
3986 (Some(cs), sample_data, None)
3987 }
3988 } else {
3989 (resolved_cs, sample_data, None)
3990 }
3991 } else {
3992 (resolved_cs, sample_data, None)
3993 };
3994
3995 let image_matrix =
3997 Matrix::new(width as f64, 0.0, 0.0, -(height as f64), 0.0, height as f64);
3998
3999 if is_image_mask && self.gstate.fill_shading_pattern.is_some() {
4001 let shading_box = self.gstate.fill_shading_pattern.clone().unwrap();
4002 let row_bytes = width.div_ceil(8);
4003 let mut gray = vec![0u8; (width * height) as usize];
4004 for y in 0..height {
4005 for x in 0..width {
4006 let byte_idx = (y * row_bytes + x / 8) as usize;
4007 let bit_idx = 7 - (x % 8);
4008 let bit = if byte_idx < sample_data.len() {
4009 (sample_data[byte_idx] >> bit_idx) & 1
4010 } else {
4011 0
4012 };
4013 let painted = if polarity { bit == 1 } else { bit == 0 };
4014 gray[(y * width + x) as usize] = if painted { 255 } else { 0 };
4015 }
4016 }
4017
4018 let mut mask_dl = DisplayList::new();
4019 mask_dl.push(DisplayElement::Image {
4020 sample_data: Arc::new(gray),
4021 params: ImageParams {
4022 width,
4023 height,
4024 color_space: ImageColorSpace::DeviceGray,
4025 bits_per_component: 8,
4026 ctm: self.gstate.ctm,
4027 image_matrix,
4028 interpolate: false,
4029 mask_color: None,
4030 alpha: 1.0,
4031 blend_mode: 0,
4032 overprint: false,
4033 overprint_mode: 0,
4034 opm_paired: false,
4035 painted_channels: 0,
4036 alpha_is_shape: false,
4037 rendering_intent: 0,
4038 },
4039 });
4040
4041 let mut content_dl = DisplayList::new();
4042 for elem in shading_box.0.elements() {
4043 content_dl.push(elem.clone());
4044 }
4045
4046 let corners = [
4047 self.gstate.ctm.transform_point(0.0, 0.0),
4048 self.gstate.ctm.transform_point(width as f64, 0.0),
4049 self.gstate.ctm.transform_point(0.0, height as f64),
4050 self.gstate.ctm.transform_point(width as f64, height as f64),
4051 ];
4052 let x_min = corners.iter().map(|c| c.0).fold(f64::INFINITY, f64::min);
4053 let y_min = corners.iter().map(|c| c.1).fold(f64::INFINITY, f64::min);
4054 let x_max = corners
4055 .iter()
4056 .map(|c| c.0)
4057 .fold(f64::NEG_INFINITY, f64::max);
4058 let y_max = corners
4059 .iter()
4060 .map(|c| c.1)
4061 .fold(f64::NEG_INFINITY, f64::max);
4062
4063 let parent_clip_bbox = self.current_clip_bbox();
4064 self.display_list.push(DisplayElement::SoftMasked {
4065 mask: mask_dl,
4066 content: content_dl,
4067 params: SoftMaskParams {
4068 subtype: SoftMaskSubtype::Luminosity,
4069 bbox: [x_min, y_min, x_max, y_max],
4070 backdrop_color: None,
4071 transfer_invert: false,
4072 has_nested_mask_scope: false,
4073 parent_clip_bbox,
4074 },
4075 mask_cache: Arc::new(Mutex::new(None)),
4076 });
4077 return Ok(());
4078 }
4079
4080 if is_image_mask
4086 && self.gstate.overprint
4087 && self.gstate.overprint_mode == 1
4088 && self.gstate.fill_pattern.is_none()
4089 && self.gstate.fill_shading_pattern.is_none()
4090 && self.gstate.fill_color.native_cmyk == Some((0.0, 0.0, 0.0, 0.0))
4091 {
4092 return Ok(());
4093 }
4094
4095 if is_image_mask && self.gstate.fill_pattern.is_some() {
4102 let pattern = self.gstate.fill_pattern.clone().unwrap();
4103 let row_bytes = width.div_ceil(8);
4104 let mut gray = vec![0u8; (width * height) as usize];
4105 for y in 0..height {
4106 for x in 0..width {
4107 let byte_idx = (y * row_bytes + x / 8) as usize;
4108 let bit_idx = 7 - (x % 8);
4109 let bit = if byte_idx < sample_data.len() {
4110 (sample_data[byte_idx] >> bit_idx) & 1
4111 } else {
4112 0
4113 };
4114 let painted = if polarity { bit == 1 } else { bit == 0 };
4115 gray[(y * width + x) as usize] = if painted { 255 } else { 0 };
4116 }
4117 }
4118
4119 let mut mask_dl = DisplayList::new();
4120 mask_dl.push(DisplayElement::Image {
4121 sample_data: Arc::new(gray),
4122 params: ImageParams {
4123 width,
4124 height,
4125 color_space: ImageColorSpace::DeviceGray,
4126 bits_per_component: 8,
4127 ctm: self.gstate.ctm,
4128 image_matrix,
4129 interpolate: false,
4130 mask_color: None,
4131 alpha: 1.0,
4132 blend_mode: 0,
4133 overprint: false,
4134 overprint_mode: 0,
4135 opm_paired: false,
4136 painted_channels: 0,
4137 alpha_is_shape: false,
4138 rendering_intent: 0,
4139 },
4140 });
4141
4142 let corners = [
4143 self.gstate.ctm.transform_point(0.0, 0.0),
4144 self.gstate.ctm.transform_point(width as f64, 0.0),
4145 self.gstate.ctm.transform_point(0.0, height as f64),
4146 self.gstate.ctm.transform_point(width as f64, height as f64),
4147 ];
4148 let x_min = corners.iter().map(|c| c.0).fold(f64::INFINITY, f64::min);
4149 let y_min = corners.iter().map(|c| c.1).fold(f64::INFINITY, f64::min);
4150 let x_max = corners
4151 .iter()
4152 .map(|c| c.0)
4153 .fold(f64::NEG_INFINITY, f64::max);
4154 let y_max = corners
4155 .iter()
4156 .map(|c| c.1)
4157 .fold(f64::NEG_INFINITY, f64::max);
4158
4159 let pm = &pattern.pattern_matrix;
4162 let mut content_dl = DisplayList::new();
4163 for elem in pattern.tile.elements() {
4164 if let DisplayElement::Image {
4165 sample_data: sd,
4166 params: ip,
4167 } = elem
4168 {
4169 let dev_ctm = pm.multiply(&ip.ctm);
4170 content_dl.push(DisplayElement::Image {
4171 sample_data: sd.clone(),
4172 params: ImageParams {
4173 ctm: dev_ctm,
4174 ..ip.clone()
4175 },
4176 });
4177 }
4178 }
4179
4180 let parent_clip_bbox = self.current_clip_bbox();
4181 self.display_list.push(DisplayElement::SoftMasked {
4182 mask: mask_dl,
4183 content: content_dl,
4184 params: SoftMaskParams {
4185 subtype: SoftMaskSubtype::Luminosity,
4186 bbox: [x_min, y_min, x_max, y_max],
4187 backdrop_color: None,
4188 transfer_invert: false,
4189 has_nested_mask_scope: false,
4190 parent_clip_bbox,
4191 },
4192 mask_cache: Arc::new(Mutex::new(None)),
4193 });
4194 return Ok(());
4195 }
4196
4197 let (color_space, sample_data) = if !is_image_mask
4203 && let Some(ResolvedColorSpace::DeviceN {
4204 names,
4205 alt,
4206 tint_fn: Some(func),
4207 }) = resolved_cs.as_ref()
4208 && names.len() >= 2
4209 && matches!(
4210 alt.as_ref(),
4211 ResolvedColorSpace::DeviceGray | ResolvedColorSpace::DeviceRGB
4212 ) {
4213 let ni = names.len();
4214 let npixels = width as usize * height as usize;
4215 let mut rgba = vec![255u8; npixels * 4];
4216 let mut inputs = vec![0.0f64; ni];
4217 for i in 0..npixels {
4218 let si = i * ni;
4219 for (c, inp) in inputs.iter_mut().enumerate() {
4220 *inp = sample_data.get(si + c).copied().unwrap_or(0) as f64 / 255.0;
4221 }
4222 let out = func.evaluate(&inputs);
4223 let (r, g, b) = color_space::alt_comps_to_rgb_f64(&out, alt);
4224 let pi = i * 4;
4225 rgba[pi] = r;
4226 rgba[pi + 1] = g;
4227 rgba[pi + 2] = b;
4228 }
4229 (ImageColorSpace::PreconvertedRGBA, rgba)
4230 } else if is_image_mask {
4231 (
4232 ImageColorSpace::Mask {
4233 color: self.gstate.fill_color.clone(),
4234 polarity,
4235 spot_color: self.gstate.fill_spot_color.clone(),
4236 },
4237 sample_data,
4238 )
4239 } else if let Some(ref rcs) = resolved_cs {
4240 (to_image_color_space(rcs), sample_data)
4241 } else {
4242 (ImageColorSpace::PreconvertedRGBA, sample_data)
4244 };
4245
4246 let color_space = if !is_image_mask {
4252 if let ImageColorSpace::Indexed { base, .. } = &color_space {
4253 let expected_1comp = (width * height) as usize;
4254 let base_n = base.num_components() as usize;
4255 if sample_data.len() == expected_1comp * base_n && base_n > 1 {
4256 *base.clone()
4257 } else {
4258 color_space
4259 }
4260 } else {
4261 color_space
4262 }
4263 } else {
4264 color_space
4265 };
4266
4267 let interpolate = dict
4268 .get(b"Interpolate")
4269 .and_then(|o| match o {
4270 PdfObj::Bool(b) => Some(*b),
4271 _ => None,
4272 })
4273 .unwrap_or(false);
4274
4275 let (mask_color, explicit_mask_data) = match dict.get(b"Mask") {
4277 Some(PdfObj::Array(arr)) => {
4278 let mc: Vec<u8> = arr
4280 .iter()
4281 .filter_map(|o| o.as_int().map(|n| n as u8))
4282 .collect();
4283 (Some(mc), None)
4284 }
4285 Some(_mask_obj) => {
4286 let mask_alpha = self
4288 .resolve_explicit_mask(dict, width, height)
4289 .unwrap_or(None);
4290 (None, mask_alpha)
4291 }
4292 None => (None, None),
4293 };
4294
4295 let is_jpx = filter_is_jpx;
4299 let is_dct = filter_is_dct;
4300 let is_indexed = matches!(&color_space, ImageColorSpace::Indexed { .. });
4301 let (sample_data, display_bpc) =
4305 if is_image_mask || bpc == 8 || bpc == 0 || is_jpx || is_dct {
4306 (sample_data, if is_dct || is_jpx { 8 } else { bpc })
4307 } else if bpc == 16 {
4308 (sample_data.chunks(2).map(|c| c[0]).collect(), 8)
4310 } else if bpc > 8 {
4311 (sample_data, bpc)
4312 } else {
4313 (
4314 expand_bits_to_bytes(
4315 &sample_data,
4316 bpc,
4317 width,
4318 height,
4319 color_space.num_components(),
4320 is_indexed,
4321 ),
4322 8,
4323 )
4324 };
4325
4326 let sample_data = if !is_image_mask {
4331 if let Some(decode) = dict.get_array(b"Decode") {
4332 let n_comps = color_space.num_components() as usize;
4333 let decode_vals: Vec<f64> = decode.iter().filter_map(|o| o.as_f64()).collect();
4334 if decode_vals.len() >= n_comps * 2 {
4335 let effective_bpc = if is_jpx || is_dct { 8 } else { bpc };
4336 let max_sample = ((1u32 << effective_bpc) - 1) as f64;
4337 let is_default = if is_indexed {
4340 decode_vals.len() == 2
4341 && (decode_vals[0]).abs() < 1e-6
4342 && (decode_vals[1] - max_sample).abs() < 1e-6
4343 } else {
4344 decode_vals.chunks(2).all(|pair| {
4345 pair.len() == 2
4346 && (pair[0] - 0.0).abs() < 1e-6
4347 && (pair[1] - 1.0).abs() < 1e-6
4348 })
4349 };
4350 if !is_default {
4351 let max_val = if is_indexed {
4354 ((1u32 << effective_bpc) - 1) as f64
4355 } else {
4356 255.0f64
4357 };
4358 let mut result = Vec::with_capacity(sample_data.len());
4359 if is_indexed {
4360 let d_min = decode_vals[0];
4362 let d_max = decode_vals[1];
4363 for &sample in sample_data.iter() {
4364 let val = d_min + (sample as f64 / max_val) * (d_max - d_min);
4365 result.push(val.round().clamp(0.0, 255.0) as u8);
4366 }
4367 } else {
4368 for (i, &sample) in sample_data.iter().enumerate() {
4370 let comp = i % n_comps;
4371 let d_min = decode_vals[comp * 2];
4372 let d_max = decode_vals[comp * 2 + 1];
4373 let val = d_min + (sample as f64 / max_val) * (d_max - d_min);
4374 result.push((val.clamp(0.0, 1.0) * 255.0 + 0.5) as u8);
4375 }
4376 }
4377 result
4378 } else {
4379 sample_data
4380 }
4381 } else {
4382 sample_data
4383 }
4384 } else {
4385 sample_data
4386 }
4387 } else {
4388 sample_data
4389 };
4390
4391 let (sample_data, color_space, resolved_cs) = if !is_image_mask {
4397 let was_device_gray = matches!(color_space, ImageColorSpace::DeviceGray);
4398 let (new_cs, new_data) =
4399 self.cmyk_group_promote_image(color_space, sample_data, width, height);
4400 let new_resolved = if was_device_gray && matches!(new_cs, ImageColorSpace::DeviceCMYK) {
4401 Some(ResolvedColorSpace::DeviceCMYK)
4402 } else {
4403 resolved_cs
4404 };
4405 (new_data, new_cs, new_resolved)
4406 } else {
4407 (sample_data, color_space, resolved_cs)
4408 };
4409
4410 if !is_image_mask {
4414 if let Some(ref rcs) = resolved_cs {
4415 register_icc_profile(rcs, &mut self.icc_cache);
4416 }
4417 }
4418
4419 let smask_result = if !is_image_mask {
4426 let dict_smask = self.resolve_smask(dict, width, height)?;
4427 if dict_smask.is_none() {
4429 if let Some(alpha) = smask_in_data_alpha {
4430 Some((alpha, width, height, None))
4431 } else {
4432 None
4433 }
4434 } else {
4435 dict_smask
4436 }
4437 } else {
4438 None
4439 };
4440
4441 let (sample_data, color_space, width, height) =
4445 if let Some((mask_alpha, mw, mh)) = explicit_mask_data {
4446 let (up_data, up_cs) = if let ImageColorSpace::Indexed {
4449 base,
4450 hival,
4451 lookup,
4452 } = &color_space
4453 {
4454 let n_base = base.num_components() as usize;
4455 let n_pixels = (width * height) as usize;
4456 let mut expanded = vec![0u8; n_pixels.saturating_mul(n_base)];
4460 for i in 0..n_pixels {
4461 let idx = sample_data.get(i).copied().unwrap_or(0) as usize;
4462 let idx = idx.min(*hival as usize);
4463 let offset = idx * n_base;
4464 for c in 0..n_base {
4465 expanded[i * n_base + c] = lookup.get(offset + c).copied().unwrap_or(0);
4466 }
4467 }
4468 (expanded, *base.clone())
4469 } else {
4470 (sample_data, color_space)
4471 };
4472 let (img_data, img_w, img_h) = if mw > width || mh > height {
4473 let upscaled = bilinear_upsample_image(&up_data, width, height, mw, mh, &up_cs);
4475 (upscaled, mw, mh)
4476 } else {
4477 (up_data, width, height)
4478 };
4479 let rgba = merge_rgb_with_smask(
4480 &img_data,
4481 &mask_alpha,
4482 &up_cs,
4483 img_w,
4484 img_h,
4485 Some(&self.icc_cache),
4486 );
4487 (rgba, ImageColorSpace::PreconvertedRGBA, img_w, img_h)
4488 } else {
4489 (sample_data, color_space, width, height)
4490 };
4491
4492 let sample_data = if !is_image_mask && self.gstate.transfer.has_functions() {
4494 let n_comps = color_space.num_components() as usize;
4495 if n_comps >= 3 {
4496 let mut data = sample_data;
4497 apply_transfer_to_image(&mut data, &self.gstate.transfer, n_comps);
4498 data
4499 } else {
4500 sample_data
4501 }
4502 } else {
4503 sample_data
4504 };
4505
4506 let image_matrix =
4508 Matrix::new(width as f64, 0.0, 0.0, -(height as f64), 0.0, height as f64);
4509
4510 let painted_channels_override = if let ImageColorSpace::Indexed {
4518 base,
4519 hival,
4520 lookup,
4521 } = &color_space
4522 {
4523 if matches!(
4524 base.as_ref(),
4525 ImageColorSpace::DeviceCMYK | ImageColorSpace::ICCBased { n: 4, .. }
4526 ) {
4527 let n_entries = (*hival as usize + 1).min(lookup.len() / 4);
4528 let is_k_only = n_entries > 0
4529 && (0..n_entries).all(|i| {
4530 let off = i * 4;
4531 lookup.get(off).copied().unwrap_or(0) == 0
4532 && lookup.get(off + 1).copied().unwrap_or(0) == 0
4533 && lookup.get(off + 2).copied().unwrap_or(0) == 0
4534 });
4535 if is_k_only {
4536 stet_graphics::device::CMYK_K
4537 } else {
4538 stet_graphics::device::CMYK_ALL
4539 }
4540 } else {
4541 resolved_cs
4542 .as_ref()
4543 .map(painted_channels_for_cs)
4544 .unwrap_or(self.gstate.fill_painted_channels)
4545 }
4546 } else {
4547 resolved_cs
4548 .as_ref()
4549 .map(painted_channels_for_cs)
4550 .unwrap_or(self.gstate.fill_painted_channels)
4551 };
4552
4553 let image_params = ImageParams {
4554 width,
4555 height,
4556 color_space,
4557 bits_per_component: display_bpc as u8,
4558 ctm: self.gstate.ctm,
4559 image_matrix,
4560 interpolate,
4561 mask_color,
4562 alpha: self.gstate.fill_alpha,
4563 blend_mode: self.gstate.blend_mode,
4564 overprint: self.gstate.overprint,
4565 overprint_mode: self.gstate.overprint_mode,
4566 opm_paired: self.gstate.opm_paired,
4567 painted_channels: painted_channels_override,
4568 alpha_is_shape: self.gstate.alpha_is_shape,
4569 rendering_intent: image_intent,
4570 };
4571
4572 if let Some((smask_data, mw, mh, matte)) = smask_result {
4576 const MAX_PIXELS: u64 = 16_000_000; let mut target_w = mw.max(width);
4585 let mut target_h = mh.max(height);
4586 if (target_w as u64) * (target_h as u64) > MAX_PIXELS {
4587 let scale = (MAX_PIXELS as f64 / (target_w as f64 * target_h as f64)).sqrt();
4588 target_w = (target_w as f64 * scale).ceil() as u32;
4589 target_h = (target_h as f64 * scale).ceil() as u32;
4590 }
4591 let (sample_data, width, height) = if target_w > width || target_h > height {
4592 let upscaled = bilinear_upsample_image(
4593 &sample_data,
4594 width,
4595 height,
4596 target_w,
4597 target_h,
4598 &image_params.color_space,
4599 );
4600 (upscaled, target_w, target_h)
4601 } else {
4602 (sample_data, width, height)
4603 };
4604
4605 let smask_data = if mw != width || mh != height {
4607 let mut resampled = vec![0u8; (width * height) as usize];
4608 for y in 0..height {
4609 let sy = (y as u64 * mh as u64 / height as u64) as u32;
4610 for x in 0..width {
4611 let sx = (x as u64 * mw as u64 / width as u64) as u32;
4612 resampled[(y * width + x) as usize] = smask_data
4613 .get((sy * mw + sx) as usize)
4614 .copied()
4615 .unwrap_or(0);
4616 }
4617 }
4618 resampled
4619 } else {
4620 smask_data
4621 };
4622
4623 let sample_data = if let Some(ref mc) = matte {
4627 let n_comps = image_params.color_space.num_components() as usize;
4628 if mc.len() >= n_comps && n_comps >= 3 {
4629 let mut out = sample_data;
4630 let pixels = (width * height) as usize;
4631 for i in 0..pixels {
4632 let a = smask_data[i] as f64 / 255.0;
4633 if a > 0.0 && a < 1.0 {
4634 for c in 0..n_comps.min(3) {
4635 let m = (mc[c] * 255.0).clamp(0.0, 255.0);
4636 let premul = out[i * n_comps + c] as f64;
4637 let orig = m + (premul - m) / a;
4638 out[i * n_comps + c] = orig.round().clamp(0.0, 255.0) as u8;
4639 }
4640 }
4641 }
4642 out
4643 } else {
4644 sample_data
4645 }
4646 } else {
4647 sample_data
4648 };
4649
4650 let sample_arc = Arc::new(sample_data);
4652 let smask_arc = Arc::new(smask_data);
4653 if let PdfObj::Ref(obj_num, _) = obj {
4654 self.image_cache.insert(
4655 *obj_num,
4656 CachedImage {
4657 sample_data: Arc::clone(&sample_arc),
4658 width,
4659 height,
4660 color_space: image_params.color_space.clone(),
4661 bits_per_component: image_params.bits_per_component,
4662 interpolate,
4663 mask_color: image_params.mask_color.clone(),
4664 painted_channels: image_params.painted_channels,
4665 smask: Some((Arc::clone(&smask_arc), width, height, matte.clone())),
4666 rendering_intent: image_params.rendering_intent,
4667 },
4668 );
4669 }
4670
4671 let image_matrix =
4672 Matrix::new(width as f64, 0.0, 0.0, -(height as f64), 0.0, height as f64);
4673
4674 let mut mask_dl = DisplayList::new();
4675 mask_dl.push(DisplayElement::Image {
4676 sample_data: smask_arc,
4677 params: ImageParams {
4678 width,
4679 height,
4680 color_space: ImageColorSpace::DeviceGray,
4681 bits_per_component: 8,
4682 ctm: self.gstate.ctm,
4683 image_matrix,
4684 interpolate,
4685 mask_color: None,
4686 alpha: 1.0,
4687 blend_mode: 0,
4688 overprint: false,
4689 overprint_mode: 0,
4690 opm_paired: false,
4691 painted_channels: 0,
4692 alpha_is_shape: false,
4693 rendering_intent: 0,
4694 },
4695 });
4696
4697 let mut content_dl = DisplayList::new();
4698 content_dl.push(DisplayElement::Image {
4699 sample_data: sample_arc,
4700 params: ImageParams {
4701 width,
4702 height,
4703 image_matrix,
4704 ..image_params
4705 },
4706 });
4707
4708 let corners = [
4709 self.gstate.ctm.transform_point(0.0, 0.0),
4710 self.gstate.ctm.transform_point(1.0, 0.0),
4711 self.gstate.ctm.transform_point(0.0, 1.0),
4712 self.gstate.ctm.transform_point(1.0, 1.0),
4713 ];
4714 let x_min = corners.iter().map(|c| c.0).fold(f64::INFINITY, f64::min);
4715 let y_min = corners.iter().map(|c| c.1).fold(f64::INFINITY, f64::min);
4716 let x_max = corners
4717 .iter()
4718 .map(|c| c.0)
4719 .fold(f64::NEG_INFINITY, f64::max);
4720 let y_max = corners
4721 .iter()
4722 .map(|c| c.1)
4723 .fold(f64::NEG_INFINITY, f64::max);
4724
4725 let parent_clip_bbox = self.current_clip_bbox();
4726 self.display_list.push(DisplayElement::SoftMasked {
4727 mask: mask_dl,
4728 content: content_dl,
4729 params: SoftMaskParams {
4730 subtype: SoftMaskSubtype::Luminosity,
4731 bbox: [x_min, y_min, x_max, y_max],
4732 backdrop_color: None,
4733 transfer_invert: false,
4734 has_nested_mask_scope: false,
4735 parent_clip_bbox,
4736 },
4737 mask_cache: Arc::new(Mutex::new(None)),
4738 });
4739 } else {
4740 let sample_arc = Arc::new(sample_data);
4742 if let PdfObj::Ref(obj_num, _) = obj {
4743 self.image_cache.insert(
4744 *obj_num,
4745 CachedImage {
4746 sample_data: Arc::clone(&sample_arc),
4747 width,
4748 height,
4749 color_space: image_params.color_space.clone(),
4750 bits_per_component: image_params.bits_per_component,
4751 interpolate,
4752 mask_color: image_params.mask_color.clone(),
4753 painted_channels: image_params.painted_channels,
4754 smask: None,
4755 rendering_intent: image_params.rendering_intent,
4756 },
4757 );
4758 }
4759
4760 self.display_list.push(DisplayElement::Image {
4761 sample_data: sample_arc,
4762 params: image_params,
4763 });
4764 }
4765 Ok(())
4766 }
4767
4768 #[allow(clippy::too_many_arguments)]
4771 fn emit_cached_image(&mut self, cached: CachedImage) -> Result<(), PdfError> {
4774 let (sample_data, smask, width, height) = (
4775 cached.sample_data,
4776 cached.smask,
4777 cached.width,
4778 cached.height,
4779 );
4780
4781 let image_matrix =
4782 Matrix::new(width as f64, 0.0, 0.0, -(height as f64), 0.0, height as f64);
4783 let image_params = ImageParams {
4784 width,
4785 height,
4786 color_space: cached.color_space,
4787 bits_per_component: cached.bits_per_component,
4788 ctm: self.gstate.ctm,
4789 image_matrix,
4790 interpolate: cached.interpolate,
4791 mask_color: cached.mask_color,
4792 alpha: self.gstate.fill_alpha,
4793 blend_mode: self.gstate.blend_mode,
4794 overprint: self.gstate.overprint,
4795 overprint_mode: self.gstate.overprint_mode,
4796 opm_paired: self.gstate.opm_paired,
4797 painted_channels: cached.painted_channels,
4798 alpha_is_shape: self.gstate.alpha_is_shape,
4799 rendering_intent: cached.rendering_intent,
4800 };
4801
4802 if let Some((smask_data, sw, sh, _matte)) = smask {
4803 let mut mask_dl = DisplayList::new();
4804 mask_dl.push(DisplayElement::Image {
4805 sample_data: smask_data,
4806 params: ImageParams {
4807 width: sw,
4808 height: sh,
4809 color_space: ImageColorSpace::DeviceGray,
4810 bits_per_component: 8,
4811 ctm: self.gstate.ctm,
4812 image_matrix,
4813 interpolate: cached.interpolate,
4814 mask_color: None,
4815 alpha: 1.0,
4816 blend_mode: 0,
4817 overprint: false,
4818 overprint_mode: 0,
4819 opm_paired: false,
4820 painted_channels: 0,
4821 alpha_is_shape: false,
4822 rendering_intent: 0,
4823 },
4824 });
4825
4826 let mut content_dl = DisplayList::new();
4827 content_dl.push(DisplayElement::Image {
4828 sample_data,
4829 params: ImageParams {
4830 width,
4831 height,
4832 image_matrix,
4833 ..image_params
4834 },
4835 });
4836
4837 let corners = [
4838 self.gstate.ctm.transform_point(0.0, 0.0),
4839 self.gstate.ctm.transform_point(1.0, 0.0),
4840 self.gstate.ctm.transform_point(0.0, 1.0),
4841 self.gstate.ctm.transform_point(1.0, 1.0),
4842 ];
4843 let x_min = corners.iter().map(|c| c.0).fold(f64::INFINITY, f64::min);
4844 let y_min = corners.iter().map(|c| c.1).fold(f64::INFINITY, f64::min);
4845 let x_max = corners
4846 .iter()
4847 .map(|c| c.0)
4848 .fold(f64::NEG_INFINITY, f64::max);
4849 let y_max = corners
4850 .iter()
4851 .map(|c| c.1)
4852 .fold(f64::NEG_INFINITY, f64::max);
4853
4854 let parent_clip_bbox = self.current_clip_bbox();
4855 self.display_list.push(DisplayElement::SoftMasked {
4856 mask: mask_dl,
4857 content: content_dl,
4858 params: SoftMaskParams {
4859 subtype: SoftMaskSubtype::Luminosity,
4860 bbox: [x_min, y_min, x_max, y_max],
4861 backdrop_color: None,
4862 transfer_invert: false,
4863 has_nested_mask_scope: false,
4864 parent_clip_bbox,
4865 },
4866 mask_cache: Arc::new(Mutex::new(None)),
4867 });
4868 } else {
4869 self.display_list.push(DisplayElement::Image {
4870 sample_data,
4871 params: image_params,
4872 });
4873 }
4874 Ok(())
4875 }
4876
4877 fn resolve_smask(
4882 &self,
4883 dict: &PdfDict,
4884 image_w: u32,
4885 image_h: u32,
4886 ) -> Result<Option<(Vec<u8>, u32, u32, Option<Vec<f64>>)>, PdfError> {
4887 let smask_ref = match dict.get(b"SMask") {
4888 Some(obj) => obj.clone(),
4889 None => return Ok(None),
4890 };
4891 let smask_obj = self.resolver.deref(&smask_ref)?;
4892 let smask_dict = match smask_obj.as_dict() {
4893 Some(d) => d,
4894 None => return Ok(None),
4895 };
4896 let sw =
4899 validate_image_dimension(smask_dict.get_int(b"Width").or(Some(i64::from(image_w))));
4900 let sh =
4901 validate_image_dimension(smask_dict.get_int(b"Height").or(Some(i64::from(image_h))));
4902 let (Some(sw), Some(sh)) = (sw, sh) else {
4903 return Ok(None);
4904 };
4905 if validate_image_size(sw, sh).is_none() {
4906 return Ok(None);
4907 }
4908 let Some(bpc) = validate_bits_per_component(smask_dict.get_int(b"BitsPerComponent")) else {
4909 return Ok(None);
4910 };
4911 let data = self.resolver.stream_data_from_obj(&smask_ref)?;
4912
4913 let mut data = if bpc == 8 {
4916 data
4917 } else if bpc == 16 {
4918 data.chunks(2).map(|c| c[0]).collect()
4920 } else if bpc < 8 {
4921 expand_bits_to_bytes(&data, bpc, sw, sh, 1, false)
4922 } else {
4923 data
4924 };
4925
4926 if let Some(decode) = smask_dict.get_array(b"Decode")
4928 && decode.len() >= 2
4929 {
4930 let d0 = decode[0].as_f64().unwrap_or(0.0);
4931 let d1 = decode[1].as_f64().unwrap_or(1.0);
4932 if (d0 - 1.0).abs() < 1e-6 && d1.abs() < 1e-6 {
4933 for b in data.iter_mut() {
4935 *b = 255 - *b;
4936 }
4937 } else if (d0).abs() > 1e-6 || (d1 - 1.0).abs() > 1e-6 {
4938 for b in data.iter_mut() {
4940 let v = d0 + (d1 - d0) * (*b as f64 / 255.0);
4941 *b = (v * 255.0).round().clamp(0.0, 255.0) as u8;
4942 }
4943 }
4944 }
4945
4946 let matte = smask_dict
4948 .get_array(b"Matte")
4949 .map(|arr| arr.iter().filter_map(|o| o.as_f64()).collect::<Vec<_>>());
4950
4951 Ok(Some((data, sw, sh, matte)))
4952 }
4953
4954 fn resolve_explicit_mask(
4960 &self,
4961 dict: &PdfDict,
4962 image_w: u32,
4963 image_h: u32,
4964 ) -> Result<Option<(Vec<u8>, u32, u32)>, PdfError> {
4965 let mask_ref = match dict.get(b"Mask") {
4966 Some(obj) => obj.clone(),
4967 None => return Ok(None),
4968 };
4969 let mask_obj = self.resolver.deref(&mask_ref)?;
4970 let mask_dict = match mask_obj.as_dict() {
4971 Some(d) => d,
4972 None => return Ok(None),
4973 };
4974 let (Some(mw), Some(mh)) = (
4975 validate_image_dimension(mask_dict.get_int(b"Width")),
4976 validate_image_dimension(mask_dict.get_int(b"Height")),
4977 ) else {
4978 return Ok(None);
4979 };
4980 if validate_image_size(mw, mh).is_none() {
4981 return Ok(None);
4982 }
4983 let mask_data = self.resolver.stream_data_from_obj(&mask_ref)?;
4984
4985 let invert = if let Some(decode) = mask_dict.get_array(b"Decode") {
4987 if decode.len() >= 2 {
4988 let d0 = decode[0].as_f64().unwrap_or(0.0);
4989 d0 > 0.5
4991 } else {
4992 false
4993 }
4994 } else {
4995 false
4996 };
4997
4998 let row_bytes = mw.div_ceil(8);
5000 let mut alpha = vec![0u8; (mw * mh) as usize];
5001 for y in 0..mh {
5002 for x in 0..mw {
5003 let byte_idx = (y * row_bytes + x / 8) as usize;
5004 let bit_idx = 7 - (x % 8);
5005 let bit = if byte_idx < mask_data.len() {
5006 (mask_data[byte_idx] >> bit_idx) & 1
5007 } else {
5008 0
5009 };
5010 let opaque = if invert { bit == 1 } else { bit == 0 };
5013 alpha[(y * mw + x) as usize] = if opaque { 255 } else { 0 };
5014 }
5015 }
5016
5017 if mw == image_w && mh == image_h {
5022 Ok(Some((alpha, mw, mh)))
5023 } else if mw >= image_w && mh >= image_h {
5024 Ok(Some((alpha, mw, mh)))
5026 } else {
5027 let mut resampled = vec![0u8; (image_w * image_h) as usize];
5029 let ratio_x = mw as f32 / image_w as f32;
5030 let ratio_y = mh as f32 / image_h as f32;
5031 for y in 0..image_h {
5032 let top_f = y as f32 * ratio_y;
5033 let bottom_f = (y + 1) as f32 * ratio_y;
5034 let top = (top_f as u32).min(mh - 1);
5035 let bottom = (bottom_f.ceil() as u32).min(mh);
5036 for x in 0..image_w {
5037 let left_f = x as f32 * ratio_x;
5038 let right_f = (x + 1) as f32 * ratio_x;
5039 let left = (left_f as u32).min(mw - 1);
5040 let right = (right_f.ceil() as u32).min(mw);
5041 let mut sum = 0.0f32;
5042 let mut weight = 0.0f32;
5043 for sy in top..bottom {
5044 let py_top = sy as f32;
5045 let py_bot = (sy + 1) as f32;
5046 let wy = py_bot.min(bottom_f) - py_top.max(top_f);
5047 for sx in left..right {
5048 let px_left = sx as f32;
5049 let px_right = (sx + 1) as f32;
5050 let wx = px_right.min(right_f) - px_left.max(left_f);
5051 let w = wx * wy;
5052 sum += alpha[(sy * mw + sx) as usize] as f32 * w;
5053 weight += w;
5054 }
5055 }
5056 resampled[(y * image_w + x) as usize] = if weight > 0.0 {
5057 (sum / weight + 0.5).min(255.0) as u8
5058 } else {
5059 0
5060 };
5061 }
5062 }
5063 Ok(Some((resampled, image_w, image_h)))
5064 }
5065 }
5066
5067 fn is_jpx_rgba(&self, obj: &PdfObj) -> bool {
5070 #[cfg(feature = "jpx")]
5071 {
5072 if let Ok((raw, filters)) = self.resolver.raw_stream_and_filters(obj) {
5073 if filters
5074 .iter()
5075 .any(|f| matches!(f, crate::filters::Filter::JPXDecode))
5076 {
5077 if let Some((color_channels, has_alpha)) = crate::filters::jpx_color_info(&raw)
5078 {
5079 return color_channels == 3 && has_alpha;
5080 }
5081 }
5082 }
5083 }
5084 false
5085 }
5086
5087 fn handle_form_xobject(&mut self, obj: &PdfObj, dict: &PdfDict) -> Result<(), PdfError> {
5089 if self.depth >= MAX_CONTENT_NESTING {
5090 return Err(PdfError::Other("Form XObject nesting too deep".into()));
5091 }
5092
5093 let form_resources = if let Some(res_obj) = dict.get(b"Resources") {
5095 match self.resolver.deref(res_obj)? {
5096 PdfObj::Dict(d) => d,
5097 _ => self.resources.clone(),
5098 }
5099 } else {
5100 self.resources.clone()
5101 };
5102
5103 let form_matrix = if let Some(vals) = deref_num_array(self.resolver, dict, b"Matrix") {
5105 if vals.len() == 6 {
5106 Matrix::new(vals[0], vals[1], vals[2], vals[3], vals[4], vals[5])
5107 } else {
5108 Matrix::identity()
5109 }
5110 } else {
5111 Matrix::identity()
5112 };
5113
5114 let bbox = if let Some(vals) = deref_num_array(self.resolver, dict, b"BBox") {
5116 if vals.len() == 4 {
5117 Some((vals[0], vals[1], vals[2], vals[3]))
5118 } else {
5119 None
5120 }
5121 } else {
5122 None
5123 };
5124
5125 let is_transparency_group = self.is_transparency_group(dict);
5127
5128 let form_data = self.resolver.stream_data_from_obj(obj)?;
5130
5131 self.gstate_stack.push(self.gstate.clone());
5134 let saved_stack_depth = self.gstate_stack.len();
5135 let saved_resources = std::mem::replace(&mut self.resources, form_resources);
5136 let saved_font_cache = std::mem::take(&mut self.font_cache);
5137 let saved_current_font = self.current_font.take();
5138 let saved_cs_index = self.cs_index.take(); let saved_content_stream_ctm = self.content_stream_ctm;
5140 let saved_mc_stack = std::mem::take(&mut self.mc_stack);
5141 let saved_path = std::mem::take(&mut self.current_path);
5145 let saved_point = self.current_point.take();
5146 let saved_subpath = self.subpath_start.take();
5147
5148 self.gstate.ctm = self.gstate.ctm.concat(&form_matrix);
5150
5151 self.content_stream_ctm = self.gstate.ctm;
5154
5155 if is_transparency_group {
5156 let group_blend_mode = self.gstate.blend_mode;
5159 let group_alpha = self.gstate.fill_alpha;
5160
5161 self.gstate.fill_alpha = 1.0;
5169 self.gstate.stroke_alpha = 1.0;
5170 self.gstate.soft_mask = None;
5171
5172 let mut group_list = DisplayList::new();
5174 std::mem::swap(&mut self.display_list, &mut group_list);
5175
5176 let saved_scope = self.soft_mask_scope.take();
5178
5179 let device_bbox = self.compute_device_bbox(bbox);
5182
5183 if let Some((x0, y0, x1, y1)) = bbox {
5185 self.push_bbox_clip(x0, y0, x1, y1);
5186 }
5187
5188 self.depth += 1;
5190 self.interpret_stream(&form_data)?;
5191 self.depth -= 1;
5192
5193 self.flush_soft_mask();
5195
5196 std::mem::swap(&mut self.display_list, &mut group_list);
5198
5199 self.soft_mask_scope = saved_scope;
5201
5202 let isolated = self.get_group_isolated(dict);
5204 let knockout = self.get_group_knockout(dict);
5205 let color_space = self.get_group_color_space(dict);
5206
5207 self.display_list.push(DisplayElement::Group {
5209 elements: group_list,
5210 params: GroupParams {
5211 bbox: device_bbox,
5212 isolated,
5213 knockout,
5214 blend_mode: group_blend_mode,
5215 alpha: group_alpha,
5216 color_space,
5217 },
5218 });
5219 } else {
5220 if let Some((x0, y0, x1, y1)) = bbox {
5222 self.push_bbox_clip(x0, y0, x1, y1);
5223 }
5224
5225 let saved_cull = self.form_cull_y.take();
5230 if let Some((_x0, y0, _x1, y1)) = bbox {
5231 let form_height = (y1 - y0).abs();
5232 if form_height > 5000.0 {
5234 let ctm = &self.gstate.ctm;
5237 if ctm.b.abs() < 1e-6 && ctm.c.abs() < 1e-6 && ctm.d.abs() > 1e-6 {
5240 let page_h = self.initial_ctm.ty.abs();
5242 let fy0 = (0.0 - ctm.ty) / ctm.d;
5243 let fy1 = (page_h - ctm.ty) / ctm.d;
5244 let (lo, hi) = if fy0 < fy1 { (fy0, fy1) } else { (fy1, fy0) };
5245 self.form_cull_y = Some((lo - 100.0, hi + 100.0));
5247 }
5248 }
5249 }
5250
5251 self.depth += 1;
5252 self.interpret_stream(&form_data)?;
5253 self.depth -= 1;
5254
5255 self.form_cull_y = saved_cull;
5256 }
5257
5258 while self.gstate_stack.len() > saved_stack_depth {
5264 self.gstate_stack.pop();
5265 }
5266
5267 self.resources = saved_resources;
5269 self.font_cache = saved_font_cache;
5270 self.current_font = saved_current_font;
5271 self.cs_index = saved_cs_index;
5272 self.content_stream_ctm = saved_content_stream_ctm;
5273 self.current_path = saved_path;
5274 self.current_point = saved_point;
5275 self.subpath_start = saved_subpath;
5276 self.mc_stack = saved_mc_stack;
5277 if let Some(saved) = self.gstate_stack.pop() {
5278 let old_clip_version = self.gstate.clip_path_version;
5279 self.gstate = saved;
5280 if !is_transparency_group && self.gstate.clip_path_version != old_clip_version {
5282 self.restore_clip_from_stack();
5283 }
5284 }
5285
5286 Ok(())
5287 }
5288
5289 fn is_transparency_group(&self, dict: &PdfDict) -> bool {
5291 let Some(group_obj) = dict.get(b"Group") else {
5292 return false;
5293 };
5294 let group_dict = match self.resolver.deref(group_obj) {
5295 Ok(PdfObj::Dict(d)) => d,
5296 _ => return false,
5297 };
5298 group_dict.get_name(b"S") == Some(b"Transparency")
5299 }
5300
5301 fn get_group_isolated(&self, dict: &PdfDict) -> bool {
5303 let Some(group_obj) = dict.get(b"Group") else {
5304 return false;
5305 };
5306 let group_dict = match self.resolver.deref(group_obj) {
5307 Ok(PdfObj::Dict(d)) => d,
5308 _ => return false,
5309 };
5310 match group_dict.get(b"I") {
5311 Some(PdfObj::Bool(b)) => *b,
5312 _ => false,
5313 }
5314 }
5315
5316 fn get_group_knockout(&self, dict: &PdfDict) -> bool {
5318 let Some(group_obj) = dict.get(b"Group") else {
5319 return false;
5320 };
5321 let group_dict = match self.resolver.deref(group_obj) {
5322 Ok(PdfObj::Dict(d)) => d,
5323 _ => return false,
5324 };
5325 match group_dict.get(b"K") {
5326 Some(PdfObj::Bool(b)) => *b,
5327 _ => false,
5328 }
5329 }
5330
5331 fn get_group_color_space(
5335 &self,
5336 dict: &PdfDict,
5337 ) -> stet_graphics::display_list::GroupColorSpace {
5338 use stet_graphics::display_list::GroupColorSpace;
5339 let Some(group_obj) = dict.get(b"Group") else {
5340 return GroupColorSpace::Inherited;
5341 };
5342 let group_dict = match self.resolver.deref(group_obj) {
5343 Ok(PdfObj::Dict(d)) => d,
5344 _ => return GroupColorSpace::Inherited,
5345 };
5346 let Some(cs_obj) = group_dict.get(b"CS") else {
5347 return GroupColorSpace::Inherited;
5348 };
5349 let cs_obj = match self.resolver.deref(cs_obj) {
5350 Ok(o) => o,
5351 Err(_) => return GroupColorSpace::Inherited,
5352 };
5353 match cs_obj {
5354 PdfObj::Name(n) => match n.as_slice() {
5355 b"DeviceGray" | b"CalGray" | b"G" => GroupColorSpace::DeviceGray,
5356 b"DeviceRGB" | b"CalRGB" | b"RGB" => GroupColorSpace::DeviceRGB,
5357 b"DeviceCMYK" | b"CMYK" => GroupColorSpace::DeviceCMYK,
5358 _ => GroupColorSpace::Inherited,
5359 },
5360 PdfObj::Array(arr) => {
5361 if let Some(PdfObj::Name(name)) = arr.first()
5363 && name.as_slice() == b"ICCBased"
5364 && let Some(stream_obj) = arr.get(1)
5365 {
5366 let stream_obj = match self.resolver.deref(stream_obj) {
5367 Ok(o) => o,
5368 Err(_) => return GroupColorSpace::Inherited,
5369 };
5370 if let PdfObj::Stream {
5371 dict: stream_dict, ..
5372 } = stream_obj
5373 && let Some(n_obj) = stream_dict.get(b"N")
5374 && let Some(n_val) = n_obj.as_int()
5375 {
5376 return match n_val {
5377 1 => GroupColorSpace::DeviceGray,
5378 3 => GroupColorSpace::DeviceRGB,
5379 4 => GroupColorSpace::DeviceCMYK,
5380 _ => GroupColorSpace::Inherited,
5381 };
5382 }
5383 }
5384 GroupColorSpace::Inherited
5385 }
5386 _ => GroupColorSpace::Inherited,
5387 }
5388 }
5389
5390 fn push_bbox_clip(&mut self, x0: f64, y0: f64, x1: f64, y1: f64) {
5392 let p0 = self.gstate.ctm.transform_point(x0, y0);
5393 let p1 = self.gstate.ctm.transform_point(x1, y0);
5394 let p2 = self.gstate.ctm.transform_point(x1, y1);
5395 let p3 = self.gstate.ctm.transform_point(x0, y1);
5396 let mut clip_path = PsPath::new();
5397 clip_path.segments.push(PathSegment::MoveTo(p0.0, p0.1));
5398 clip_path.segments.push(PathSegment::LineTo(p1.0, p1.1));
5399 clip_path.segments.push(PathSegment::LineTo(p2.0, p2.1));
5400 clip_path.segments.push(PathSegment::LineTo(p3.0, p3.1));
5401 clip_path.segments.push(PathSegment::ClosePath);
5402 self.display_list.push(DisplayElement::Clip {
5403 path: clip_path.clone(),
5404 params: ClipParams {
5405 fill_rule: FillRule::NonZeroWinding,
5406 ctm: Matrix::identity(),
5407 stroke_params: None,
5408 },
5409 });
5410 self.gstate
5411 .clip_stack
5412 .push((clip_path.clone(), FillRule::NonZeroWinding));
5413 self.gstate.clip_path = Some(clip_path);
5414 self.gstate.clip_path_version += 1;
5415 }
5416
5417 fn current_clip_bbox(&self) -> Option<[f64; 4]> {
5423 let path = self.gstate.clip_path.as_ref()?;
5424 let mut x_min = f64::INFINITY;
5425 let mut y_min = f64::INFINITY;
5426 let mut x_max = f64::NEG_INFINITY;
5427 let mut y_max = f64::NEG_INFINITY;
5428 for seg in &path.segments {
5429 let pts: &[(f64, f64)] = match seg {
5430 PathSegment::MoveTo(x, y) | PathSegment::LineTo(x, y) => &[(*x, *y)],
5431 PathSegment::CurveTo {
5432 x1,
5433 y1,
5434 x2,
5435 y2,
5436 x3,
5437 y3,
5438 } => &[(*x1, *y1), (*x2, *y2), (*x3, *y3)][..],
5439 PathSegment::ClosePath => &[],
5440 };
5441 for (x, y) in pts {
5442 x_min = x_min.min(*x);
5443 y_min = y_min.min(*y);
5444 x_max = x_max.max(*x);
5445 y_max = y_max.max(*y);
5446 }
5447 }
5448 if x_min.is_finite() && x_min < x_max && y_min < y_max {
5449 Some([x_min, y_min, x_max, y_max])
5450 } else {
5451 None
5452 }
5453 }
5454
5455 fn compute_device_bbox(&self, bbox: Option<(f64, f64, f64, f64)>) -> [f64; 4] {
5456 let Some((x0, y0, x1, y1)) = bbox else {
5457 return [0.0, 0.0, 1e9, 1e9];
5459 };
5460 let corners = [
5461 self.gstate.ctm.transform_point(x0, y0),
5462 self.gstate.ctm.transform_point(x1, y0),
5463 self.gstate.ctm.transform_point(x0, y1),
5464 self.gstate.ctm.transform_point(x1, y1),
5465 ];
5466 let mut min_x = f64::INFINITY;
5467 let mut min_y = f64::INFINITY;
5468 let mut max_x = f64::NEG_INFINITY;
5469 let mut max_y = f64::NEG_INFINITY;
5470 for (cx, cy) in &corners {
5471 min_x = min_x.min(*cx);
5472 min_y = min_y.min(*cy);
5473 max_x = max_x.max(*cx);
5474 max_y = max_y.max(*cy);
5475 }
5476 [min_x, min_y, max_x, max_y]
5477 }
5478
5479 fn handle_inline_image(&mut self, lexer: &mut Lexer) -> Result<(), PdfError> {
5481 let mut dict = PdfDict::new();
5483 loop {
5484 let tok = lexer.next_token()?;
5485 match tok {
5486 Token::Keyword(ref kw) if kw == b"ID" => break,
5487 Token::Eof => return Ok(()),
5488 Token::Name(key) => {
5489 let expanded_key = expand_inline_key(&key);
5490 let val_tok = lexer.next_token()?;
5491 let val = match val_tok {
5492 Token::Int(n) => PdfObj::Int(n),
5493 Token::Real(f) => PdfObj::Real(f),
5494 Token::Name(n) => PdfObj::Name(expand_inline_value(&n)),
5495 Token::Bool(b) => PdfObj::Bool(b),
5496 Token::LitString(s) | Token::HexString(s) => PdfObj::Str(s),
5497 Token::ArrayBegin => {
5498 let arr = Self::parse_inline_array(lexer)?;
5499 PdfObj::Array(arr)
5500 }
5501 Token::DictBegin => crate::lexer::parse_dict_body(lexer)
5502 .map(PdfObj::Dict)
5503 .unwrap_or(PdfObj::Null),
5504 _ => PdfObj::Null,
5505 };
5506 if dict.get(&expanded_key).is_none() {
5509 dict.insert(expanded_key, val);
5510 }
5511 }
5512 _ => {}
5513 }
5514 }
5515
5516 let data = lexer.data();
5520 let mut pos = lexer.pos();
5521 if pos < data.len() {
5522 if data[pos] == b'\r' {
5523 pos += 1;
5524 if pos < data.len() && data[pos] == b'\n' {
5525 pos += 1;
5526 }
5527 } else if data[pos] == b' ' || data[pos] == b'\n' {
5528 pos += 1;
5529 }
5530 }
5531
5532 let width = validate_image_dimension(dict.get_int(b"Width")).unwrap_or(0);
5534 let height = validate_image_dimension(dict.get_int(b"Height")).unwrap_or(0);
5535 let (width, height) = match validate_image_size(width, height) {
5538 Some(_) => (width, height),
5539 None => (0, 0),
5540 };
5541 let is_image_mask = matches!(dict.get(b"ImageMask"), Some(PdfObj::Bool(true)));
5542 let bpc = if is_image_mask {
5543 1
5544 } else {
5545 validate_bits_per_component(dict.get_int(b"BitsPerComponent")).unwrap_or(8)
5546 };
5547
5548 let has_filter = dict.get(b"Filter").is_some() || dict.get(b"F").is_some();
5549
5550 let outermost_is_ascii85 = dict
5554 .get(b"Filter")
5555 .or_else(|| dict.get(b"F"))
5556 .map(|f| match f {
5557 PdfObj::Name(n) => n == b"ASCII85Decode" || n == b"A85",
5558 PdfObj::Array(arr) => arr
5559 .first()
5560 .and_then(|o| o.as_name())
5561 .map(|n| n == b"ASCII85Decode" || n == b"A85")
5562 .unwrap_or(false),
5563 _ => false,
5564 })
5565 .unwrap_or(false);
5566
5567 let resolved_cs = if is_image_mask {
5568 None
5569 } else if let Some(cs_obj) = dict.get(b"ColorSpace") {
5570 let cs_resolved = if let PdfObj::Name(name) = cs_obj {
5573 let from_cache = self
5576 .cs_index
5577 .as_ref()
5578 .and_then(|idx| idx.get(name.as_slice()).cloned());
5579 let res_obj = from_cache.or_else(|| {
5580 self.resolve_resource_subdict(b"ColorSpace")
5581 .and_then(|d| d.get(name).cloned())
5582 });
5583 if let Some(ref obj) = res_obj {
5584 resolve_color_space_obj(obj, self.resolver)
5585 } else {
5586 resolve_color_space_obj(cs_obj, self.resolver)
5587 }
5588 } else {
5589 resolve_color_space_obj(cs_obj, self.resolver)
5590 };
5591 match cs_resolved {
5592 Ok(resolved) => Some(resolved),
5593 Err(_) => Some(ResolvedColorSpace::DeviceGray),
5594 }
5595 } else {
5596 Some(ResolvedColorSpace::DeviceGray)
5597 };
5598 let n_components = resolved_cs
5599 .as_ref()
5600 .map(|cs| cs.num_components() as u32)
5601 .unwrap_or(1);
5602
5603 let row_bits = width * n_components.max(1) * bpc;
5605 let row_bytes = row_bits.div_ceil(8);
5606 let expected_len = (row_bytes * height) as usize;
5607
5608 let start = pos;
5612 let search_from = if has_filter {
5613 start
5614 } else {
5615 start + expected_len
5616 };
5617 let mut end = search_from;
5620 let mut found_no_ws = false;
5621 if !has_filter {
5622 for offset in [
5624 expected_len.saturating_sub(2),
5625 expected_len.saturating_sub(1),
5626 expected_len,
5627 ] {
5628 let p = start + offset;
5629 if p + 1 < data.len()
5630 && data[p] == b'E'
5631 && data[p + 1] == b'I'
5632 && (p + 2 >= data.len() || is_delimiter_or_ws(data[p + 2]))
5633 {
5634 end = p;
5635 found_no_ws = true;
5636 break;
5637 }
5638 }
5639 }
5640 if !found_no_ws {
5641 if outermost_is_ascii85 {
5642 let mut found_a85_end = false;
5645 let mut scan = search_from;
5646 while scan + 1 < data.len() {
5647 if data[scan] == b'~' {
5648 if data[scan + 1] == b'>' {
5649 end = scan + 2;
5651 } else if is_whitespace_byte(data[scan + 1]) {
5652 let mut probe = scan + 1;
5655 while probe < data.len() && is_whitespace_byte(data[probe]) {
5656 probe += 1;
5657 }
5658 if probe + 1 < data.len()
5659 && data[probe] == b'E'
5660 && data[probe + 1] == b'I'
5661 {
5662 end = scan + 1;
5663 } else {
5664 scan += 1;
5665 continue;
5666 }
5667 } else {
5668 scan += 1;
5669 continue;
5670 }
5671 while end < data.len() && is_whitespace_byte(data[end]) {
5672 end += 1;
5673 }
5674 found_a85_end = true;
5676 found_no_ws = true;
5677 break;
5678 }
5679 scan += 1;
5680 }
5681 if !found_a85_end {
5682 while end + 2 < data.len() {
5684 if is_whitespace_byte(data[end])
5685 && data[end + 1] == b'E'
5686 && data[end + 2] == b'I'
5687 && (end + 3 >= data.len() || is_delimiter_or_ws(data[end + 3]))
5688 {
5689 break;
5690 }
5691 end += 1;
5692 }
5693 }
5694 } else {
5695 while end + 2 < data.len() {
5696 if is_whitespace_byte(data[end])
5697 && data[end + 1] == b'E'
5698 && data[end + 2] == b'I'
5699 && (end + 3 >= data.len() || is_delimiter_or_ws(data[end + 3]))
5700 {
5701 break;
5702 }
5703 end += 1;
5704 }
5705 }
5706 }
5707
5708 let sample_data = data[start..end.min(data.len())].to_vec();
5709 let skip_past = if found_no_ws {
5711 (end + 3).min(data.len())
5713 } else {
5714 (end + 4).min(data.len())
5716 };
5717 lexer.set_pos(skip_past);
5718
5719 let sample_data = if has_filter {
5721 match crate::filters::parse_filters(&dict, Some(self.resolver)) {
5722 Ok((filters, parms)) if !filters.is_empty() => {
5723 crate::filters::decode_stream(&sample_data, &filters, &parms, None)
5724 .unwrap_or(sample_data)
5725 }
5726 _ => sample_data,
5727 }
5728 } else {
5729 sample_data
5730 };
5731
5732 let polarity = if is_image_mask {
5734 if let Some(arr) = dict.get_array(b"Decode") {
5735 let vals: Vec<f64> = arr.iter().filter_map(|o| o.as_f64()).collect();
5736 vals.len() >= 2 && vals[0] > 0.5
5737 } else {
5738 false
5739 }
5740 } else {
5741 false
5742 };
5743
5744 let image_matrix =
5745 Matrix::new(width as f64, 0.0, 0.0, -(height as f64), 0.0, height as f64);
5746
5747 if is_image_mask && self.gstate.fill_shading_pattern.is_some() {
5749 let shading_box = self.gstate.fill_shading_pattern.clone().unwrap();
5750
5751 let row_bytes = width.div_ceil(8);
5754 let mut gray = vec![0u8; (width * height) as usize];
5755 for y in 0..height {
5756 for x in 0..width {
5757 let byte_idx = (y * row_bytes + x / 8) as usize;
5758 let bit_idx = 7 - (x % 8);
5759 let bit = if byte_idx < sample_data.len() {
5760 (sample_data[byte_idx] >> bit_idx) & 1
5761 } else {
5762 0
5763 };
5764 let painted = if polarity { bit == 1 } else { bit == 0 };
5767 gray[(y * width + x) as usize] = if painted { 255 } else { 0 };
5768 }
5769 }
5770
5771 let mut mask_dl = DisplayList::new();
5773 mask_dl.push(DisplayElement::Image {
5774 sample_data: Arc::new(gray),
5775 params: ImageParams {
5776 width,
5777 height,
5778 color_space: ImageColorSpace::DeviceGray,
5779 bits_per_component: 8,
5780 ctm: self.gstate.ctm,
5781 image_matrix,
5782 interpolate: false,
5783 mask_color: None,
5784 alpha: 1.0,
5785 blend_mode: 0,
5786 overprint: false,
5787 overprint_mode: 0,
5788 opm_paired: false,
5789 painted_channels: 0,
5790 alpha_is_shape: false,
5791 rendering_intent: 0,
5792 },
5793 });
5794
5795 let mut content_dl = DisplayList::new();
5797 for elem in shading_box.0.elements() {
5798 content_dl.push(elem.clone());
5799 }
5800
5801 let corners = [
5803 self.gstate.ctm.transform_point(0.0, 0.0),
5804 self.gstate.ctm.transform_point(width as f64, 0.0),
5805 self.gstate.ctm.transform_point(0.0, height as f64),
5806 self.gstate.ctm.transform_point(width as f64, height as f64),
5807 ];
5808 let x_min = corners.iter().map(|c| c.0).fold(f64::INFINITY, f64::min);
5809 let y_min = corners.iter().map(|c| c.1).fold(f64::INFINITY, f64::min);
5810 let x_max = corners
5811 .iter()
5812 .map(|c| c.0)
5813 .fold(f64::NEG_INFINITY, f64::max);
5814 let y_max = corners
5815 .iter()
5816 .map(|c| c.1)
5817 .fold(f64::NEG_INFINITY, f64::max);
5818
5819 let parent_clip_bbox = self.current_clip_bbox();
5820 self.display_list.push(DisplayElement::SoftMasked {
5821 mask: mask_dl,
5822 content: content_dl,
5823 params: SoftMaskParams {
5824 subtype: SoftMaskSubtype::Luminosity,
5825 bbox: [x_min, y_min, x_max, y_max],
5826 backdrop_color: None,
5827 transfer_invert: false,
5828 has_nested_mask_scope: false,
5829 parent_clip_bbox,
5830 },
5831 mask_cache: Arc::new(Mutex::new(None)),
5832 });
5833 return Ok(());
5834 }
5835
5836 let color_space = if is_image_mask {
5837 ImageColorSpace::Mask {
5838 color: self.gstate.fill_color.clone(),
5839 polarity,
5840 spot_color: self.gstate.fill_spot_color.clone(),
5841 }
5842 } else {
5843 to_image_color_space(resolved_cs.as_ref().unwrap())
5844 };
5845
5846 let is_indexed = matches!(&color_space, ImageColorSpace::Indexed { .. });
5848 let sample_data = if !is_image_mask && bpc != 8 && bpc != 0 {
5849 expand_bits_to_bytes(&sample_data, bpc, width, height, n_components, is_indexed)
5850 } else {
5851 sample_data
5852 };
5853
5854 let (color_space, sample_data) = if !is_image_mask {
5858 self.cmyk_group_promote_image(color_space, sample_data, width, height)
5859 } else {
5860 (color_space, sample_data)
5861 };
5862
5863 if !is_image_mask {
5866 if let Some(ref rcs) = resolved_cs {
5867 register_icc_profile(rcs, &mut self.icc_cache);
5868 }
5869 }
5870
5871 self.display_list.push(DisplayElement::Image {
5872 sample_data: Arc::new(sample_data),
5873 params: ImageParams {
5874 width,
5875 height,
5876 color_space,
5877 bits_per_component: 8,
5878 ctm: self.gstate.ctm,
5879 image_matrix,
5880 interpolate: false,
5881 mask_color: None,
5882 alpha: self.gstate.fill_alpha,
5883 blend_mode: self.gstate.blend_mode,
5884 overprint: self.gstate.overprint,
5885 overprint_mode: self.gstate.overprint_mode,
5886 opm_paired: self.gstate.opm_paired,
5887 painted_channels: resolved_cs
5888 .as_ref()
5889 .map(painted_channels_for_cs)
5890 .unwrap_or(self.gstate.fill_painted_channels),
5891 alpha_is_shape: self.gstate.alpha_is_shape,
5892 rendering_intent: 0,
5893 },
5894 });
5895
5896 Ok(())
5897 }
5898
5899 fn apply_ext_gstate(&mut self, name: &[u8]) -> Result<(), PdfError> {
5901 let ext_dict = self
5902 .resolve_resource_subdict(b"ExtGState")
5903 .ok_or(PdfError::Other("no ExtGState resources".into()))?;
5904 let gs_ref = ext_dict.get(name).ok_or_else(|| {
5905 PdfError::Other(format!(
5906 "ExtGState /{} not found",
5907 String::from_utf8_lossy(name)
5908 ))
5909 })?;
5910 let gs_obj = self.resolver.deref(gs_ref)?;
5911 let gs_dict = gs_obj
5912 .as_dict()
5913 .ok_or(PdfError::Other("ExtGState is not a dict".into()))?;
5914
5915 if let Some(lw) = gs_dict.get_f64(b"LW") {
5917 self.gstate.line_width = lw;
5918 }
5919 if let Some(lc) = gs_dict.get_int(b"LC")
5920 && let Some(cap) = LineCap::from_i32(lc as i32)
5921 {
5922 self.gstate.line_cap = cap;
5923 }
5924 if let Some(lj) = gs_dict.get_int(b"LJ")
5925 && let Some(join) = LineJoin::from_i32(lj as i32)
5926 {
5927 self.gstate.line_join = join;
5928 }
5929 if let Some(ml) = gs_dict.get_f64(b"ML") {
5930 self.gstate.miter_limit = ml;
5931 }
5932 if let Some(fl) = gs_dict.get_f64(b"FL") {
5933 self.gstate.flatness = fl;
5934 }
5935 if let Some(PdfObj::Bool(sa)) = gs_dict.get(b"SA") {
5936 self.gstate.stroke_adjust = *sa;
5937 }
5938 let has_opm = gs_dict.get(b"OPM").is_some();
5941 if let Some(opm) = gs_dict.get_int(b"OPM") {
5942 self.gstate.overprint_mode = opm as i32;
5943 }
5944 let has_op_flag = gs_dict.get(b"OP").is_some() || gs_dict.get(b"op").is_some();
5945 if self.overprint_enabled {
5946 if let Some(PdfObj::Bool(op)) = gs_dict.get(b"OP") {
5947 self.gstate.overprint = *op;
5948 self.gstate.overprint_stroke = *op;
5950 }
5951 if let Some(PdfObj::Bool(op)) = gs_dict.get(b"op") {
5952 self.gstate.overprint = *op;
5953 }
5954 }
5955 let has_op_upper = gs_dict.get(b"OP").is_some();
5968 let has_op_lower = gs_dict.get(b"op").is_some();
5969 let strict_signal = (has_opm && has_op_flag) || (has_op_upper && has_op_lower);
5970 if strict_signal {
5971 self.gstate.opm_paired = true;
5972 } else if has_opm || has_op_flag {
5973 self.gstate.opm_paired = false;
5974 }
5975 if let Some(ca) = gs_dict.get_f64(b"CA") {
5976 self.gstate.stroke_alpha = ca;
5977 }
5978 if let Some(ca) = gs_dict.get_f64(b"ca") {
5979 self.gstate.fill_alpha = ca;
5980 }
5981 if let Some(b) = gs_dict.get_bool(b"AIS") {
5982 self.gstate.alpha_is_shape = b;
5983 }
5984 if let Some(b) = gs_dict.get_bool(b"TK") {
5985 self.gstate.text_knockout = b;
5986 }
5987 if let Some(PdfObj::Name(ri)) = gs_dict.get(b"RI") {
5989 self.gstate.rendering_intent = match ri.as_slice() {
5990 b"Perceptual" => 0,
5991 b"RelativeColorimetric" => 1,
5992 b"Saturation" => 2,
5993 b"AbsoluteColorimetric" => 3,
5994 _ => 0,
5995 };
5996 }
5997
5998 if let Some(bm) = gs_dict.get(b"BM") {
6000 let bm = self.resolver.deref(bm).unwrap_or_else(|_| bm.clone());
6001 match &bm {
6002 PdfObj::Name(name) => {
6003 self.gstate.blend_mode = blend_mode_from_name(name);
6004 }
6005 PdfObj::Array(arr) => {
6006 for obj in arr {
6007 if let PdfObj::Name(name) = obj {
6008 let mode = blend_mode_from_name(name);
6009 if mode != 0 || name.as_slice() == b"Normal" {
6010 self.gstate.blend_mode = mode;
6011 break;
6012 }
6013 }
6014 }
6015 }
6016 _ => {}
6017 }
6018 }
6019
6020 if let Some(d_arr) = gs_dict.get_array(b"D")
6022 && d_arr.len() == 2
6023 && let (Some(arr), Some(offset)) = (d_arr[0].as_array(), d_arr[1].as_f64())
6024 {
6025 let array: Vec<f64> = arr.iter().filter_map(|o| o.as_f64()).collect();
6026 self.gstate.dash_pattern = DashPattern { array, offset };
6027 }
6028
6029 if let Some(font_arr) = gs_dict.get_array(b"Font")
6031 && font_arr.len() == 2
6032 && let Some(size) = font_arr[1].as_f64()
6033 {
6034 self.gstate.font_size = size;
6035 let font_ref = &font_arr[0];
6037 let cache_key = if let PdfObj::Ref(obj_num, _) = font_ref {
6039 format!("__gs_font_{obj_num}").into_bytes()
6040 } else {
6041 b"__gs_font_inline".to_vec()
6042 };
6043 if let Some(cached) = self.font_cache.get(&cache_key) {
6044 self.current_font = Some(Arc::clone(cached));
6045 } else {
6046 match font::resolve_font(self.resolver, font_ref, self.font_provider.as_ref()) {
6047 Ok(font) => {
6048 let arc = Arc::new(font);
6049 self.font_cache.insert(cache_key, Arc::clone(&arc));
6050 self.current_font = Some(arc);
6051 }
6052 Err(e) => {
6053 eprintln!("warning: ExtGState Font: {e}");
6054 }
6055 }
6056 }
6057 }
6058
6059 if let Some(tr_obj) = gs_dict.get(b"TR2").or_else(|| gs_dict.get(b"TR")) {
6061 self.gstate.transfer = self.parse_transfer_function(tr_obj)?;
6062 }
6063
6064 if let Some(smask_obj) = gs_dict.get(b"SMask") {
6066 let smask_obj = self.resolver.deref(smask_obj)?;
6067 match &smask_obj {
6068 PdfObj::Name(n) if n.as_slice() == b"None" => {
6069 self.flush_soft_mask();
6070 self.gstate.soft_mask = None;
6071 }
6072 PdfObj::Dict(d) => {
6073 self.flush_soft_mask();
6074 match self.resolve_soft_mask(d) {
6075 Ok(sm) => {
6076 let start_index = self.display_list.len();
6077 self.gstate.soft_mask = Some(sm.clone());
6078 self.gstate.smask_gen += 1;
6079 self.soft_mask_scope = Some(SoftMaskScope {
6080 start_index,
6081 mask: sm,
6082 });
6083 }
6084 Err(e) => {
6085 eprintln!("warning: SMask resolve error: {}", e);
6086 }
6087 }
6088 }
6089 _ => {}
6090 }
6091 }
6092
6093 Ok(())
6094 }
6095
6096 fn flush_soft_mask(&mut self) {
6098 if let Some(scope) = self.soft_mask_scope.take()
6099 && self.display_list.len() > scope.start_index
6100 {
6101 let content = self.display_list.split_off(scope.start_index);
6102
6103 let content_bbox = self.content_paint_bbox(&content);
6141 let drop_shadow_skip = scope.mask.backdrop_color == Some([0.0, 0.0, 0.0])
6142 && content_bbox
6143 .map(|c| !bboxes_overlap_substantially(&c, &scope.mask.bbox, 2.0))
6144 .unwrap_or(false);
6145 let skip = scope.mask.mask_list.is_empty() || drop_shadow_skip;
6146 if skip {
6147 for elem in content.into_elements() {
6148 self.display_list.push(elem);
6149 }
6150 } else {
6151 let clip_replay: Vec<DisplayElement> = content
6156 .elements()
6157 .iter()
6158 .filter(|e| matches!(e, DisplayElement::Clip { .. } | DisplayElement::InitClip))
6159 .cloned()
6160 .collect();
6161 let parent_clip_bbox = self.current_clip_bbox();
6162 self.display_list.push(DisplayElement::SoftMasked {
6163 mask: scope.mask.mask_list,
6164 content,
6165 params: SoftMaskParams {
6166 subtype: scope.mask.subtype,
6167 bbox: scope.mask.bbox,
6168 backdrop_color: scope.mask.backdrop_color,
6169 transfer_invert: scope.mask.transfer_invert,
6170 has_nested_mask_scope: scope.mask.has_nested_mask_scope,
6171 parent_clip_bbox,
6172 },
6173 mask_cache: Arc::new(Mutex::new(None)),
6174 });
6175 for elem in clip_replay {
6176 self.display_list.push(elem);
6177 }
6178 }
6179 }
6180 }
6181
6182 fn resolve_group_cs_comps(&self, form_dict: &PdfDict) -> usize {
6185 let cs_name_to_comps = |cs: &[u8]| -> usize {
6186 match cs {
6187 b"DeviceGray" => 1,
6188 b"DeviceRGB" => 3,
6189 b"DeviceCMYK" => 4,
6190 _ => 0,
6191 }
6192 };
6193
6194 let grp_obj = match form_dict.get(b"Group") {
6195 Some(obj) => obj,
6196 None => return 0,
6197 };
6198
6199 let resolved_grp;
6201 let grp = if let Some(d) = grp_obj.as_dict() {
6202 d
6203 } else if let Ok(r) = self.resolver.deref(grp_obj) {
6204 resolved_grp = r;
6205 match resolved_grp.as_dict() {
6206 Some(d) => d,
6207 None => return 0,
6208 }
6209 } else {
6210 return 0;
6211 };
6212
6213 if let Some(cs) = grp.get_name(b"CS") {
6215 return cs_name_to_comps(cs);
6216 }
6217 if let Some(cs_obj) = grp.get(b"CS") {
6218 if let Ok(cs_resolved) = self.resolver.deref(cs_obj) {
6219 if let Some(cs) = cs_resolved.as_name() {
6220 return cs_name_to_comps(cs);
6221 }
6222 }
6223 }
6224 0
6225 }
6226
6227 fn content_paint_bbox(&self, content: &DisplayList) -> Option<[f64; 4]> {
6238 let mut x_min = f64::INFINITY;
6239 let mut y_min = f64::INFINITY;
6240 let mut x_max = f64::NEG_INFINITY;
6241 let mut y_max = f64::NEG_INFINITY;
6242 let mut grow = |bx: [f64; 4]| {
6243 x_min = x_min.min(bx[0].min(bx[2]));
6244 y_min = y_min.min(bx[1].min(bx[3]));
6245 x_max = x_max.max(bx[0].max(bx[2]));
6246 y_max = y_max.max(bx[1].max(bx[3]));
6247 };
6248 for elem in content.elements() {
6249 match elem {
6250 DisplayElement::Fill { path, .. }
6251 | DisplayElement::Stroke { path, .. }
6252 | DisplayElement::Clip { path, .. } => {
6253 let mut px_min = f64::INFINITY;
6254 let mut py_min = f64::INFINITY;
6255 let mut px_max = f64::NEG_INFINITY;
6256 let mut py_max = f64::NEG_INFINITY;
6257 for seg in &path.segments {
6258 let pts: &[(f64, f64)] = match seg {
6259 PathSegment::MoveTo(x, y) | PathSegment::LineTo(x, y) => &[(*x, *y)],
6260 PathSegment::CurveTo {
6261 x1,
6262 y1,
6263 x2,
6264 y2,
6265 x3,
6266 y3,
6267 } => &[(*x1, *y1), (*x2, *y2), (*x3, *y3)][..],
6268 PathSegment::ClosePath => &[],
6269 };
6270 for (x, y) in pts {
6271 px_min = px_min.min(*x);
6272 py_min = py_min.min(*y);
6273 px_max = px_max.max(*x);
6274 py_max = py_max.max(*y);
6275 }
6276 }
6277 if px_min.is_finite() && px_min < px_max && py_min < py_max {
6278 grow([px_min, py_min, px_max, py_max]);
6279 }
6280 }
6281 DisplayElement::Image { params, .. } => {
6282 let ctm = ¶ms.ctm;
6285 let corners = [
6286 ctm.transform_point(0.0, 0.0),
6287 ctm.transform_point(1.0, 0.0),
6288 ctm.transform_point(0.0, 1.0),
6289 ctm.transform_point(1.0, 1.0),
6290 ];
6291 let mut ix_min = f64::INFINITY;
6292 let mut iy_min = f64::INFINITY;
6293 let mut ix_max = f64::NEG_INFINITY;
6294 let mut iy_max = f64::NEG_INFINITY;
6295 for (cx, cy) in &corners {
6296 ix_min = ix_min.min(*cx);
6297 iy_min = iy_min.min(*cy);
6298 ix_max = ix_max.max(*cx);
6299 iy_max = iy_max.max(*cy);
6300 }
6301 grow([ix_min, iy_min, ix_max, iy_max]);
6302 }
6303 DisplayElement::Group { params, .. } => {
6304 grow(params.bbox);
6305 }
6306 DisplayElement::SoftMasked { params, .. } => {
6307 grow(params.bbox);
6308 }
6309 _ => {} }
6311 }
6312 if x_min.is_finite() && x_min < x_max && y_min < y_max {
6313 Some([x_min, y_min, x_max, y_max])
6314 } else {
6315 None
6316 }
6317 }
6318
6319 fn resolve_soft_mask(&mut self, dict: &PdfDict) -> Result<graphics_state::SoftMask, PdfError> {
6321 if self.depth >= MAX_CONTENT_NESTING {
6326 return Err(PdfError::Other("soft mask nesting too deep".into()));
6327 }
6328
6329 let subtype = match dict.get_name(b"S") {
6331 Some(b"Alpha") => SoftMaskSubtype::Alpha,
6332 _ => SoftMaskSubtype::Luminosity,
6333 };
6334
6335 let g_ref = dict
6337 .get(b"G")
6338 .ok_or_else(|| PdfError::Other("SMask missing /G".into()))?;
6339 let g_obj = self.resolver.deref(g_ref)?;
6340 let g_dict = g_obj
6341 .as_dict()
6342 .ok_or_else(|| PdfError::Other("SMask /G is not a dict".into()))?;
6343
6344 let bbox_tuple = if let Some(vals) = deref_num_array(self.resolver, g_dict, b"BBox") {
6346 if vals.len() == 4 {
6347 Some((vals[0], vals[1], vals[2], vals[3]))
6348 } else {
6349 None
6350 }
6351 } else {
6352 None
6353 };
6354
6355 let form_matrix = if let Some(vals) = deref_num_array(self.resolver, g_dict, b"Matrix") {
6357 if vals.len() == 6 {
6358 Matrix::new(vals[0], vals[1], vals[2], vals[3], vals[4], vals[5])
6359 } else {
6360 Matrix::identity()
6361 }
6362 } else {
6363 Matrix::identity()
6364 };
6365
6366 let form_resources = if let Some(res_obj) = g_dict.get(b"Resources") {
6368 match self.resolver.deref(res_obj)? {
6369 PdfObj::Dict(d) => d,
6370 _ => self.resources.clone(),
6371 }
6372 } else {
6373 self.resources.clone()
6374 };
6375
6376 let form_data = self.resolver.stream_data_from_obj(g_ref)?;
6378
6379 self.gstate_stack.push(self.gstate.clone());
6382 let saved_resources = std::mem::replace(&mut self.resources, form_resources);
6383 let saved_font_cache = std::mem::take(&mut self.font_cache);
6384 let saved_current_font2 = self.current_font.take();
6385 let saved_cs_index2 = self.cs_index.take();
6386 let saved_display_list = std::mem::replace(&mut self.display_list, DisplayList::new());
6387 let saved_scope = self.soft_mask_scope.take();
6388 let saved_content_stream_ctm = self.content_stream_ctm;
6389 let saved_mc_stack = std::mem::take(&mut self.mc_stack);
6390
6391 self.gstate.ctm = self.gstate.ctm.concat(&form_matrix);
6393 self.content_stream_ctm = self.gstate.ctm;
6396
6397 self.gstate.fill_alpha = 1.0;
6401 self.gstate.stroke_alpha = 1.0;
6402 self.gstate.soft_mask = None;
6403
6404 let device_bbox = self.compute_device_bbox(bbox_tuple);
6408
6409 if let Some((x0, y0, x1, y1)) = bbox_tuple {
6411 self.push_bbox_clip(x0, y0, x1, y1);
6412 }
6413
6414 let saved_cmyk_hash = self.icc_cache.suspend_default_cmyk();
6420 let saved_in_smask_form = self.in_smask_form;
6429 self.in_smask_form = true;
6430
6431 let saved_nested_mask_flush_count = self.nested_mask_flush_count;
6432 self.depth += 1;
6433 let _ = self.interpret_stream(&form_data);
6434 self.depth -= 1;
6435
6436 self.in_smask_form = saved_in_smask_form;
6437 self.icc_cache.restore_default_cmyk(saved_cmyk_hash);
6438
6439 let has_nested_mask_scope = self.nested_mask_flush_count > saved_nested_mask_flush_count;
6444
6445 self.flush_soft_mask();
6447
6448 let mask_list = std::mem::replace(&mut self.display_list, saved_display_list);
6449 self.soft_mask_scope = saved_scope;
6450 self.content_stream_ctm = saved_content_stream_ctm;
6451 self.resources = saved_resources;
6452 self.font_cache = saved_font_cache;
6453 self.current_font = saved_current_font2;
6454 self.cs_index = saved_cs_index2;
6455 self.mc_stack = saved_mc_stack;
6456 if let Some(saved) = self.gstate_stack.pop() {
6457 self.gstate = saved;
6458 }
6459
6460 let group_n_comps = self.resolve_group_cs_comps(g_dict);
6464
6465 let backdrop_color = if let Some(bc_obj) = dict.get(b"BC") {
6466 let bc_resolved = self.resolver.deref(bc_obj).ok();
6467 let bc_arr = bc_resolved
6468 .as_ref()
6469 .and_then(|o| o.as_array())
6470 .or_else(|| bc_obj.as_array());
6471 if let Some(arr) = bc_arr {
6472 let vals: Vec<f64> = arr.iter().filter_map(|o| o.as_f64()).collect();
6473 if group_n_comps == 1 && !vals.is_empty() {
6474 Some([vals[0], vals[0], vals[0]])
6476 } else if group_n_comps == 4 && vals.len() >= 4 {
6477 let c = vals[0];
6479 let m = vals[1];
6480 let y = vals[2];
6481 let k = vals[3];
6482 Some([
6483 (1.0 - c) * (1.0 - k),
6484 (1.0 - m) * (1.0 - k),
6485 (1.0 - y) * (1.0 - k),
6486 ])
6487 } else if vals.len() >= 3 {
6488 Some([vals[0], vals[1], vals[2]])
6489 } else if vals.len() == 1 {
6490 Some([vals[0], vals[0], vals[0]])
6491 } else {
6492 None
6493 }
6494 } else {
6495 None
6496 }
6497 } else {
6498 if group_n_comps == 4 {
6502 Some([1.0, 1.0, 1.0])
6503 } else {
6504 None
6505 }
6506 };
6507
6508 let transfer_invert = if let Some(tr_obj) = dict.get(b"TR") {
6513 if let Ok(tr_data) = self.resolver.stream_data_from_obj(tr_obj) {
6514 let trimmed: Vec<u8> = tr_data
6515 .iter()
6516 .copied()
6517 .filter(|b| !b.is_ascii_whitespace())
6518 .collect();
6519 let s = String::from_utf8_lossy(&trimmed);
6520 s.contains("exchsub")
6521 } else {
6522 false
6523 }
6524 } else {
6525 false
6526 };
6527
6528 let effective_bbox = if let Some(bc) = &backdrop_color {
6533 let bc_lum = 0.2126 * bc[0] + 0.7152 * bc[1] + 0.0722 * bc[2];
6534 let bc_byte = (bc_lum * 255.0 + 0.5) as u8;
6535 let effective = if transfer_invert {
6539 255 - bc_byte
6540 } else {
6541 bc_byte
6542 };
6543 if effective > 0 {
6544 [0.0, 0.0, 1e9, 1e9]
6545 } else {
6546 device_bbox
6547 }
6548 } else {
6549 device_bbox
6550 };
6551
6552 Ok(graphics_state::SoftMask {
6553 mask_list,
6554 subtype,
6555 bbox: effective_bbox,
6556 backdrop_color,
6557 transfer_invert,
6558 has_nested_mask_scope,
6559 })
6560 }
6561
6562 fn parse_transfer_function(
6567 &self,
6568 obj: &PdfObj,
6569 ) -> Result<stet_graphics::device::TransferState, PdfError> {
6570 use crate::resources::function::PdfFunction;
6571 use stet_graphics::device::TransferState;
6572
6573 let obj = self.resolver.deref(obj)?;
6574
6575 if let Some(name) = obj.as_name()
6577 && (name == b"Identity" || name == b"Default")
6578 {
6579 return Ok(TransferState::default());
6580 }
6581
6582 if let PdfObj::Array(arr) = &obj
6584 && arr.len() == 4
6585 {
6586 let mut tables: [Option<Arc<Vec<f64>>>; 4] = Default::default();
6587 for (i, fn_obj) in arr.iter().enumerate() {
6588 let fn_obj = self.resolver.deref(fn_obj)?;
6589 if let Some(name) = fn_obj.as_name()
6590 && (name == b"Identity" || name == b"Default")
6591 {
6592 continue; }
6594 if let Ok(func) = PdfFunction::parse(&fn_obj, self.resolver) {
6595 tables[i] = Some(Arc::new(sample_transfer_function(&func)));
6596 }
6597 }
6598 return Ok(TransferState {
6599 gray: None,
6600 color: Some(tables),
6601 });
6602 }
6603
6604 if let Ok(func) = PdfFunction::parse(&obj, self.resolver) {
6606 let table = Arc::new(sample_transfer_function(&func));
6607 return Ok(TransferState {
6608 gray: Some(table),
6609 color: None,
6610 });
6611 }
6612
6613 Ok(TransferState::default())
6614 }
6615
6616 fn op_sh(&mut self) -> Result<(), PdfError> {
6619 let name = self
6620 .operand_stack
6621 .last()
6622 .and_then(|o| o.as_name())
6623 .ok_or(PdfError::Other("sh: expected name".into()))?
6624 .to_vec();
6625
6626 let shading_dict = self
6627 .resolve_resource_subdict(b"Shading")
6628 .ok_or(PdfError::Other("no Shading resources".into()))?;
6629 let sh_ref = shading_dict.get(&name).ok_or_else(|| {
6630 PdfError::Other(format!(
6631 "Shading /{} not found",
6632 String::from_utf8_lossy(&name)
6633 ))
6634 })?;
6635 let sh_ref_clone = sh_ref.clone();
6636 let sh_obj = self.resolver.deref(sh_ref)?;
6637 let sh_dict = sh_obj
6638 .as_dict()
6639 .ok_or(PdfError::Other("Shading is not a dict".into()))?;
6640
6641 crate::resources::shading::handle_shading(
6642 &sh_ref_clone,
6643 sh_dict,
6644 &self.gstate,
6645 self.resolver,
6646 &mut self.display_list,
6647 &mut self.icc_cache,
6648 )
6649 }
6650
6651 fn handle_pattern_fill(&mut self) -> Result<(), PdfError> {
6654 let name = self
6655 .operand_stack
6656 .last()
6657 .and_then(|o| o.as_name())
6658 .ok_or(PdfError::Other("pattern: expected name".into()))?
6659 .to_vec();
6660
6661 self.extract_pattern_underlying_color(false)?;
6665
6666 let pattern_dict = self
6668 .resolve_resource_subdict(b"Pattern")
6669 .ok_or(PdfError::Other("no Pattern resources".into()))?;
6670 let pat_ref = pattern_dict.get(&name).ok_or_else(|| {
6671 PdfError::Other(format!(
6672 "Pattern /{} not found",
6673 String::from_utf8_lossy(&name)
6674 ))
6675 })?;
6676 let pat_obj = self.resolver.deref(pat_ref)?;
6677 let pat_dict = pat_obj
6678 .as_dict()
6679 .ok_or(PdfError::Other("Pattern is not a dict".into()))?;
6680 let pattern_type = pat_dict.get_int(b"PatternType").unwrap_or(1) as i32;
6681
6682 if pattern_type == 2 {
6683 let shading_dl = self.resolve_shading_pattern(pat_dict)?;
6684 self.gstate.fill_pattern = None;
6685 self.gstate.fill_shading_pattern = Some(Box::new(ShadingPatternDL(shading_dl)));
6686 } else {
6687 let pattern = self.resolve_pattern(&name)?;
6688 self.gstate.fill_shading_pattern = None;
6689 self.gstate.fill_pattern = Some(pattern);
6690 }
6691 Ok(())
6692 }
6693
6694 fn handle_pattern_stroke(&mut self) -> Result<(), PdfError> {
6695 let name = self
6696 .operand_stack
6697 .last()
6698 .and_then(|o| o.as_name())
6699 .ok_or(PdfError::Other("pattern: expected name".into()))?
6700 .to_vec();
6701
6702 self.extract_pattern_underlying_color(true)?;
6704
6705 let pattern_dict = self
6707 .resolve_resource_subdict(b"Pattern")
6708 .ok_or(PdfError::Other("no Pattern resources".into()))?;
6709 let pat_ref = pattern_dict.get(&name).ok_or_else(|| {
6710 PdfError::Other(format!(
6711 "Pattern /{} not found",
6712 String::from_utf8_lossy(&name)
6713 ))
6714 })?;
6715 let pat_obj = self.resolver.deref(pat_ref)?;
6716 let pat_dict = pat_obj
6717 .as_dict()
6718 .ok_or(PdfError::Other("Pattern is not a dict".into()))?;
6719 let pattern_type = pat_dict.get_int(b"PatternType").unwrap_or(1) as i32;
6720
6721 if pattern_type == 2 {
6722 let shading_dl = self.resolve_shading_pattern(pat_dict)?;
6723 self.gstate.stroke_pattern = None;
6724 self.gstate.stroke_shading_pattern = Some(Box::new(ShadingPatternDL(shading_dl)));
6725 } else {
6726 let pattern = self.resolve_pattern(&name)?;
6727 self.gstate.stroke_shading_pattern = None;
6728 self.gstate.stroke_pattern = Some(pattern);
6729 }
6730 Ok(())
6731 }
6732
6733 fn extract_pattern_underlying_color(&mut self, is_stroke: bool) -> Result<(), PdfError> {
6738 let cs_ref = if is_stroke {
6740 &self.gstate.stroke_color_space
6741 } else {
6742 &self.gstate.fill_color_space
6743 };
6744 let cs_name = match cs_ref {
6745 ColorSpaceRef::Named(n) => n.clone(),
6746 _ => return Ok(()),
6747 };
6748
6749 let cs_obj_opt: Option<crate::objects::PdfObj> = self
6753 .cs_index
6754 .as_ref()
6755 .and_then(|idx| idx.get(cs_name.as_slice()).cloned())
6756 .or_else(|| {
6757 let cs_dict = self
6758 .resources
6759 .get(b"ColorSpace")
6760 .and_then(|obj| match obj {
6761 PdfObj::Dict(_) => Some(obj.as_dict().unwrap().clone()),
6762 PdfObj::Ref(n, g) => self.resolver.resolve(*n, *g).ok()?.as_dict().cloned(),
6763 _ => None,
6764 })?;
6765 cs_dict.get(&cs_name).cloned()
6766 });
6767 let cs_obj = match cs_obj_opt {
6768 Some(obj) => obj.clone(),
6769 None => return Ok(()),
6770 };
6771 let cs_resolved = self.resolver.deref(&cs_obj)?;
6772 let arr = match &cs_resolved {
6773 PdfObj::Array(a) if a.len() >= 2 => a,
6774 _ => return Ok(()),
6775 };
6776 if arr[0].as_name() != Some(b"Pattern") {
6778 return Ok(());
6779 }
6780 let underlying_cs = color_space::resolve_color_space_obj(&arr[1], self.resolver)?;
6782 let n = underlying_cs.num_components();
6783 if n == 0 {
6784 return Ok(());
6785 }
6786
6787 let stack_len = self.operand_stack.len();
6790 if stack_len < n + 1 {
6791 return Ok(()); }
6793 let mut nums = Vec::with_capacity(n);
6795 let base = stack_len - 1 - n;
6796 for i in 0..n {
6797 nums.push(self.operand_stack[base + i].as_f64().unwrap_or(0.0));
6798 }
6799 let intent = self.gstate.rendering_intent;
6800 let color = color_space::components_to_device_color_icc_with_intent(
6801 &underlying_cs,
6802 &nums,
6803 Some(&mut self.icc_cache),
6804 intent,
6805 );
6806 if is_stroke {
6807 self.gstate.stroke_color = color;
6808 } else {
6809 self.gstate.fill_color = color;
6810 }
6811 Ok(())
6812 }
6813
6814 fn resolve_pattern(&mut self, name: &[u8]) -> Result<TilingPattern, PdfError> {
6815 let pattern_dict = self
6816 .resolve_resource_subdict(b"Pattern")
6817 .ok_or(PdfError::Other("no Pattern resources".into()))?;
6818 let pat_ref = pattern_dict.get(name).ok_or_else(|| {
6819 PdfError::Other(format!(
6820 "Pattern /{} not found",
6821 String::from_utf8_lossy(name)
6822 ))
6823 })?;
6824
6825 if let PdfObj::Ref(obj_num, gen_num) = pat_ref {
6829 if let Some(cached) = self.pattern_cache.get(&(*obj_num, *gen_num)) {
6830 return Ok(cached.clone());
6831 }
6832 }
6833
6834 let pat_ref_clone = pat_ref.clone();
6835 let pat_obj = self.resolver.deref(pat_ref)?;
6836 let pat_dict = pat_obj
6837 .as_dict()
6838 .ok_or(PdfError::Other("Pattern is not a dict".into()))?;
6839
6840 let pattern_type = pat_dict.get_int(b"PatternType").unwrap_or(1) as i32;
6841
6842 let result = match pattern_type {
6843 1 => self.resolve_tiling_pattern(&pat_ref_clone, pat_dict),
6844 _ => Err(PdfError::Other(format!(
6845 "Unsupported PatternType {pattern_type}"
6846 ))),
6847 }?;
6848
6849 if let PdfObj::Ref(obj_num, gen_num) = pat_ref {
6850 self.pattern_cache
6851 .insert((*obj_num, *gen_num), result.clone());
6852 }
6853
6854 Ok(result)
6855 }
6856
6857 fn resolve_tiling_pattern(
6858 &mut self,
6859 pat_obj: &PdfObj,
6860 pat_dict: &PdfDict,
6861 ) -> Result<TilingPattern, PdfError> {
6862 if self.depth >= MAX_CONTENT_NESTING {
6863 return Err(PdfError::Other("pattern recursion limit".into()));
6864 }
6865 let paint_type = pat_dict.get_int(b"PaintType").unwrap_or(1) as i32;
6866
6867 let bbox = deref_num_array(self.resolver, pat_dict, b"BBox")
6868 .map(|v| {
6869 if v.len() >= 4 {
6870 [v[0], v[1], v[2], v[3]]
6871 } else {
6872 [0.0, 0.0, 1.0, 1.0]
6873 }
6874 })
6875 .unwrap_or([0.0, 0.0, 1.0, 1.0]);
6876
6877 let x_step = pat_dict.get_f64(b"XStep").unwrap_or(bbox[2] - bbox[0]);
6878 let y_step = pat_dict.get_f64(b"YStep").unwrap_or(bbox[3] - bbox[1]);
6879
6880 let pattern_matrix = deref_num_array(self.resolver, pat_dict, b"Matrix")
6881 .map(|v| {
6882 if v.len() >= 6 {
6883 Matrix::new(v[0], v[1], v[2], v[3], v[4], v[5])
6884 } else {
6885 Matrix::identity()
6886 }
6887 })
6888 .unwrap_or_else(Matrix::identity);
6889
6890 let pattern_resources = if let Some(res_ref) = pat_dict.get(b"Resources") {
6891 match self.resolver.deref(res_ref)? {
6892 PdfObj::Dict(d) => d,
6893 _ => self.resources.clone(),
6894 }
6895 } else {
6896 self.resources.clone()
6897 };
6898
6899 let pattern_data = self.resolver.stream_data_from_obj(pat_obj)?;
6900
6901 let combined_matrix = self.content_stream_ctm.concat(&pattern_matrix);
6906
6907 self.gstate_stack.push(self.gstate.clone());
6912 let saved_resources = std::mem::replace(&mut self.resources, pattern_resources);
6913 let saved_display_list = std::mem::take(&mut self.display_list);
6914 let saved_content_stream_ctm = self.content_stream_ctm;
6915 let saved_path = std::mem::take(&mut self.current_path);
6916 let saved_point = self.current_point.take();
6917 let saved_subpath = self.subpath_start.take();
6918 let saved_mc_stack = std::mem::take(&mut self.mc_stack);
6919
6920 self.gstate.ctm = Matrix::identity();
6921 self.content_stream_ctm = Matrix::identity();
6922 self.gstate.clip_path = None;
6923 self.gstate.clip_path_version = 0;
6924 self.gstate.clip_stack.clear();
6925 self.gstate.fill_pattern = None;
6928 self.gstate.stroke_pattern = None;
6929 self.gstate.fill_shading_pattern = None;
6930 self.gstate.stroke_shading_pattern = None;
6931 self.gstate.text_rendering_mode = 0;
6934
6935 self.depth += 1;
6936 let _ = self.interpret_stream(&pattern_data);
6937 self.depth -= 1;
6938
6939 self.flush_soft_mask();
6941
6942 let tile_display_list = std::mem::replace(&mut self.display_list, saved_display_list);
6943 self.content_stream_ctm = saved_content_stream_ctm;
6944 self.resources = saved_resources;
6945 self.current_path = saved_path;
6946 self.current_point = saved_point;
6947 self.subpath_start = saved_subpath;
6948 self.mc_stack = saved_mc_stack;
6949 if let Some(saved) = self.gstate_stack.pop() {
6950 self.gstate = saved;
6951 }
6952
6953 Ok(TilingPattern {
6954 tile: tile_display_list,
6955 bbox,
6956 x_step,
6957 y_step,
6958 pattern_matrix: combined_matrix,
6959 paint_type,
6960 pattern_id: 0,
6961 flip_tile_y: false,
6962 })
6963 }
6964
6965 fn resolve_shading_pattern(&mut self, pat_dict: &PdfDict) -> Result<DisplayList, PdfError> {
6969 let sh_ref = pat_dict
6970 .get(b"Shading")
6971 .ok_or(PdfError::Other("shading pattern missing /Shading".into()))?;
6972 let sh_ref_clone = sh_ref.clone();
6973 let sh_obj = self.resolver.deref(sh_ref)?;
6974 let sh_dict = sh_obj
6975 .as_dict()
6976 .ok_or(PdfError::Other("Shading is not a dict".into()))?;
6977
6978 let pattern_matrix = deref_num_array(self.resolver, pat_dict, b"Matrix")
6979 .map(|v| {
6980 if v.len() >= 6 {
6981 Matrix::new(v[0], v[1], v[2], v[3], v[4], v[5])
6982 } else {
6983 Matrix::identity()
6984 }
6985 })
6986 .unwrap_or_else(Matrix::identity);
6987
6988 let combined_matrix = self.content_stream_ctm.concat(&pattern_matrix);
7003 let saved_ctm = self.gstate.ctm;
7004 let saved_overprint = self.gstate.overprint;
7005 let saved_overprint_stroke = self.gstate.overprint_stroke;
7006 self.gstate.ctm = combined_matrix;
7007 self.gstate.overprint = false;
7008 self.gstate.overprint_stroke = false;
7009
7010 let mut shading_dl = DisplayList::new();
7011 let result = crate::resources::shading::handle_shading(
7012 &sh_ref_clone,
7013 sh_dict,
7014 &self.gstate,
7015 self.resolver,
7016 &mut shading_dl,
7017 &mut self.icc_cache,
7018 );
7019 self.gstate.ctm = saved_ctm;
7020 self.gstate.overprint = saved_overprint;
7021 self.gstate.overprint_stroke = saved_overprint_stroke;
7022 result?;
7023 Ok(shading_dl)
7024 }
7025}
7026
7027fn bboxes_overlap_substantially(a: &[f64; 4], b: &[f64; 4], min_extent: f64) -> bool {
7039 let (ax0, ay0, ax1, ay1) = (
7040 a[0].min(a[2]),
7041 a[1].min(a[3]),
7042 a[0].max(a[2]),
7043 a[1].max(a[3]),
7044 );
7045 let (bx0, by0, bx1, by1) = (
7046 b[0].min(b[2]),
7047 b[1].min(b[3]),
7048 b[0].max(b[2]),
7049 b[1].max(b[3]),
7050 );
7051 let overlap_w = (ax1.min(bx1) - ax0.max(bx0)).max(0.0);
7052 let overlap_h = (ay1.min(by1) - ay0.max(by0)).max(0.0);
7053 overlap_w >= min_extent && overlap_h >= min_extent
7054}
7055
7056fn path_device_bbox(path: &PsPath) -> [f64; 4] {
7057 let mut x_min = f64::INFINITY;
7058 let mut y_min = f64::INFINITY;
7059 let mut x_max = f64::NEG_INFINITY;
7060 let mut y_max = f64::NEG_INFINITY;
7061 let mut update = |x: f64, y: f64| {
7062 x_min = x_min.min(x);
7063 y_min = y_min.min(y);
7064 x_max = x_max.max(x);
7065 y_max = y_max.max(y);
7066 };
7067 for seg in &path.segments {
7068 match seg {
7069 PathSegment::MoveTo(x, y) | PathSegment::LineTo(x, y) => update(*x, *y),
7070 PathSegment::CurveTo {
7071 x1,
7072 y1,
7073 x2,
7074 y2,
7075 x3,
7076 y3,
7077 } => {
7078 update(*x1, *y1);
7079 update(*x2, *y2);
7080 update(*x3, *y3);
7081 }
7082 PathSegment::ClosePath => {}
7083 }
7084 }
7085 [x_min, y_min, x_max, y_max]
7086}
7087
7088fn name_to_cs_ref(name: &[u8]) -> ColorSpaceRef {
7090 match name {
7091 b"DeviceGray" | b"G" => ColorSpaceRef::DeviceGray,
7092 b"DeviceRGB" | b"RGB" => ColorSpaceRef::DeviceRGB,
7093 b"DeviceCMYK" | b"CMYK" => ColorSpaceRef::DeviceCMYK,
7094 _ => ColorSpaceRef::Named(name.to_vec()),
7095 }
7096}
7097
7098fn expand_inline_key(key: &[u8]) -> Vec<u8> {
7100 match key {
7101 b"BPC" => b"BitsPerComponent".to_vec(),
7102 b"CS" => b"ColorSpace".to_vec(),
7103 b"D" => b"Decode".to_vec(),
7104 b"DP" => b"DecodeParms".to_vec(),
7105 b"F" => b"Filter".to_vec(),
7106 b"H" => b"Height".to_vec(),
7107 b"IM" => b"ImageMask".to_vec(),
7108 b"I" => b"Interpolate".to_vec(),
7109 b"W" => b"Width".to_vec(),
7110 _ => key.to_vec(),
7111 }
7112}
7113
7114fn expand_inline_value(name: &[u8]) -> Vec<u8> {
7116 match name {
7117 b"G" => b"DeviceGray".to_vec(),
7118 b"RGB" => b"DeviceRGB".to_vec(),
7119 b"CMYK" => b"DeviceCMYK".to_vec(),
7120 b"I" => b"Indexed".to_vec(),
7121 b"AHx" => b"ASCIIHexDecode".to_vec(),
7122 b"A85" => b"ASCII85Decode".to_vec(),
7123 b"LZW" => b"LZWDecode".to_vec(),
7124 b"Fl" => b"FlateDecode".to_vec(),
7125 b"RL" => b"RunLengthDecode".to_vec(),
7126 b"CCF" => b"CCITTFaxDecode".to_vec(),
7127 b"DCT" => b"DCTDecode".to_vec(),
7128 _ => name.to_vec(),
7129 }
7130}
7131
7132fn bilinear_upsample_image(
7135 data: &[u8],
7136 sw: u32,
7137 sh: u32,
7138 dw: u32,
7139 dh: u32,
7140 cs: &ImageColorSpace,
7141) -> Vec<u8> {
7142 let n = cs.num_components() as usize;
7143 if n == 0 || sw == 0 || sh == 0 || dw == 0 || dh == 0 {
7144 return data.to_vec();
7145 }
7146 let src_stride = sw as usize * n;
7147 let dst_stride = dw as usize * n;
7148 let mut out = vec![0u8; dst_stride * dh as usize];
7149
7150 for dy in 0..dh as usize {
7151 let sy = (dy as f32 + 0.5) * sh as f32 / dh as f32 - 0.5;
7152 let sy0 = (sy.floor() as i32).clamp(0, sh as i32 - 1) as usize;
7153 let sy1 = (sy0 + 1).min(sh as usize - 1);
7154 let fy = sy - sy0 as f32;
7155
7156 for dx in 0..dw as usize {
7157 let sx = (dx as f32 + 0.5) * sw as f32 / dw as f32 - 0.5;
7158 let sx0 = (sx.floor() as i32).clamp(0, sw as i32 - 1) as usize;
7159 let sx1 = (sx0 + 1).min(sw as usize - 1);
7160 let fx = sx - sx0 as f32;
7161
7162 let w00 = (1.0 - fx) * (1.0 - fy);
7163 let w10 = fx * (1.0 - fy);
7164 let w01 = (1.0 - fx) * fy;
7165 let w11 = fx * fy;
7166
7167 let i00 = sy0 * src_stride + sx0 * n;
7168 let i10 = sy0 * src_stride + sx1 * n;
7169 let i01 = sy1 * src_stride + sx0 * n;
7170 let i11 = sy1 * src_stride + sx1 * n;
7171
7172 let di = dy * dst_stride + dx * n;
7173 for c in 0..n {
7174 let v = data[i00 + c] as f32 * w00
7175 + data[i10 + c] as f32 * w10
7176 + data[i01 + c] as f32 * w01
7177 + data[i11 + c] as f32 * w11;
7178 out[di + c] = (v + 0.5).clamp(0.0, 255.0) as u8;
7179 }
7180 }
7181 }
7182 out
7183}
7184
7185fn merge_rgb_with_smask(
7188 image_data: &[u8],
7189 smask_data: &[u8],
7190 color_space: &ImageColorSpace,
7191 width: u32,
7192 height: u32,
7193 icc: Option<&stet_graphics::icc::IccCache>,
7194) -> Vec<u8> {
7195 if let ImageColorSpace::Indexed {
7197 base,
7198 hival,
7199 lookup,
7200 } = color_space
7201 {
7202 let n_base = base.num_components() as usize;
7203 let n_pixels = (width * height) as usize;
7204 let mut expanded = vec![0u8; n_pixels.saturating_mul(n_base)];
7205 for i in 0..n_pixels {
7206 let idx = image_data.get(i).copied().unwrap_or(0) as usize;
7207 let idx = idx.min(*hival as usize);
7208 let offset = idx * n_base;
7209 for c in 0..n_base {
7210 expanded[i * n_base + c] = lookup.get(offset + c).copied().unwrap_or(0);
7211 }
7212 }
7213 return merge_rgb_with_smask(&expanded, smask_data, base, width, height, icc);
7214 }
7215
7216 if let ImageColorSpace::Separation {
7218 alt_space,
7219 tint_table,
7220 ..
7221 } = color_space
7222 {
7223 let n_pixels = (width * height) as usize;
7224 let no = tint_table.num_outputs as usize;
7225 let mut expanded = vec![0u8; n_pixels.saturating_mul(no)];
7226 let mut alt_comps = vec![0.0f32; no];
7227 for i in 0..n_pixels {
7228 let tint = image_data.get(i).copied().unwrap_or(0) as f32 / 255.0;
7229 tint_table.lookup_1d(tint, &mut alt_comps);
7230 for c in 0..no {
7231 expanded[i * no + c] = (alt_comps[c].clamp(0.0, 1.0) * 255.0 + 0.5) as u8;
7232 }
7233 }
7234 return merge_rgb_with_smask(&expanded, smask_data, alt_space, width, height, icc);
7235 }
7236 if let ImageColorSpace::DeviceN {
7237 alt_space,
7238 tint_table,
7239 ..
7240 } = color_space
7241 {
7242 let ni = tint_table.num_inputs as usize;
7243 let no = tint_table.num_outputs as usize;
7244 let n_pixels = (width * height) as usize;
7245 let mut expanded = vec![0u8; n_pixels.saturating_mul(no)];
7246 let mut inputs = vec![0.0f32; ni];
7247 let mut alt_comps = vec![0.0f32; no];
7248 for i in 0..n_pixels {
7249 let si = i * ni;
7250 for (c, inp) in inputs.iter_mut().enumerate() {
7251 *inp = image_data.get(si + c).copied().unwrap_or(0) as f32 / 255.0;
7252 }
7253 tint_table.lookup_nd(&inputs, &mut alt_comps);
7254 for c in 0..no {
7255 expanded[i * no + c] = (alt_comps[c].clamp(0.0, 1.0) * 255.0 + 0.5) as u8;
7256 }
7257 }
7258 return merge_rgb_with_smask(&expanded, smask_data, alt_space, width, height, icc);
7259 }
7260
7261 let n_pixels = (width * height) as usize;
7262 let mut rgba = vec![255u8; n_pixels * 4];
7263 let n_comps = color_space.num_components();
7264
7265 if n_comps == 4 {
7267 if let Some(cache) = icc {
7268 if let Some(cmyk_hash) = cache.default_cmyk_hash() {
7269 let cmyk_data = if image_data.len() >= n_pixels * 4 {
7270 &image_data[..n_pixels * 4]
7271 } else {
7272 image_data
7273 };
7274 if let Some(rgb) = cache.convert_image_8bit(cmyk_hash, cmyk_data, n_pixels) {
7275 for i in 0..n_pixels {
7276 let alpha = smask_data.get(i).copied().unwrap_or(255);
7277 let dst = i * 4;
7278 let (r, g, b) = (rgb[i * 3], rgb[i * 3 + 1], rgb[i * 3 + 2]);
7279 if alpha == 255 {
7280 rgba[dst] = r;
7281 rgba[dst + 1] = g;
7282 rgba[dst + 2] = b;
7283 rgba[dst + 3] = 255;
7284 } else if alpha == 0 {
7285 rgba[dst] = 0;
7287 rgba[dst + 1] = 0;
7288 rgba[dst + 2] = 0;
7289 rgba[dst + 3] = 0;
7290 } else {
7291 let a = alpha as u16;
7292 rgba[dst] = ((r as u16 * a + 127) / 255) as u8;
7293 rgba[dst + 1] = ((g as u16 * a + 127) / 255) as u8;
7294 rgba[dst + 2] = ((b as u16 * a + 127) / 255) as u8;
7295 rgba[dst + 3] = alpha;
7296 }
7297 }
7298 return rgba;
7299 }
7300 }
7301 }
7302 }
7303
7304 for i in 0..n_pixels {
7305 let alpha = smask_data.get(i).copied().unwrap_or(255);
7306 let dst = i * 4;
7307 match n_comps {
7308 3 => {
7309 let src = i * 3;
7311 rgba[dst] = image_data.get(src).copied().unwrap_or(0);
7312 rgba[dst + 1] = image_data.get(src + 1).copied().unwrap_or(0);
7313 rgba[dst + 2] = image_data.get(src + 2).copied().unwrap_or(0);
7314 }
7315 1 => {
7316 let g = image_data.get(i).copied().unwrap_or(0);
7318 rgba[dst] = g;
7319 rgba[dst + 1] = g;
7320 rgba[dst + 2] = g;
7321 }
7322 4 => {
7323 let src = i * 4;
7325 let c = image_data.get(src).copied().unwrap_or(0) as f64 / 255.0;
7326 let m = image_data.get(src + 1).copied().unwrap_or(0) as f64 / 255.0;
7327 let y = image_data.get(src + 2).copied().unwrap_or(0) as f64 / 255.0;
7328 let k = image_data.get(src + 3).copied().unwrap_or(0) as f64 / 255.0;
7329 rgba[dst] = ((1.0 - c) * (1.0 - k) * 255.0 + 0.5) as u8;
7330 rgba[dst + 1] = ((1.0 - m) * (1.0 - k) * 255.0 + 0.5) as u8;
7331 rgba[dst + 2] = ((1.0 - y) * (1.0 - k) * 255.0 + 0.5) as u8;
7332 }
7333 _ => {
7334 }
7336 }
7337 if alpha == 255 {
7339 rgba[dst + 3] = 255;
7340 } else if alpha == 0 {
7341 rgba[dst] = 0;
7342 rgba[dst + 1] = 0;
7343 rgba[dst + 2] = 0;
7344 rgba[dst + 3] = 0;
7345 } else {
7346 let a = alpha as u16;
7347 rgba[dst] = ((rgba[dst] as u16 * a + 127) / 255) as u8;
7348 rgba[dst + 1] = ((rgba[dst + 1] as u16 * a + 127) / 255) as u8;
7349 rgba[dst + 2] = ((rgba[dst + 2] as u16 * a + 127) / 255) as u8;
7350 rgba[dst + 3] = alpha;
7351 }
7352 }
7353 rgba
7354}
7355
7356fn expand_bits_to_bytes(
7357 data: &[u8],
7358 bpc: u32,
7359 width: u32,
7360 height: u32,
7361 components: u32,
7362 is_indexed: bool,
7363) -> Vec<u8> {
7364 if bpc == 0 || bpc == 8 {
7365 return data.to_vec();
7366 }
7367
7368 if bpc >= 32 {
7372 return Vec::new();
7373 }
7374 let max_val = ((1u32 << bpc) - 1) as f64;
7375 let samples_per_row = width * components.max(1);
7376 let capacity = (width as usize)
7380 .saturating_mul(height as usize)
7381 .saturating_mul(components.max(1) as usize);
7382 let mut result = Vec::with_capacity(capacity);
7383
7384 for row in 0..height {
7385 let row_bit_offset = row as usize * ((samples_per_row * bpc).div_ceil(8) * 8) as usize;
7386 for col in 0..samples_per_row {
7387 let bit_offset = row_bit_offset + (col * bpc) as usize;
7388 let byte_offset = bit_offset / 8;
7389 let bit_shift = bit_offset % 8;
7390
7391 if byte_offset >= data.len() {
7392 result.push(0);
7393 continue;
7394 }
7395
7396 let mut val = 0u32;
7398 let mut bits_remaining = bpc;
7399 let mut cur_byte = byte_offset;
7400 let mut cur_bit = bit_shift;
7401
7402 while bits_remaining > 0 && cur_byte < data.len() {
7403 let available = 8 - cur_bit as u32;
7404 let take = bits_remaining.min(available);
7405 let shift = available - take;
7406 let mask = ((1u32 << take) - 1) << shift;
7407 val = (val << take) | ((data[cur_byte] as u32 & mask) >> shift);
7408 bits_remaining -= take;
7409 cur_bit = 0;
7410 cur_byte += 1;
7411 }
7412
7413 if is_indexed {
7416 result.push(val as u8);
7417 } else {
7418 result.push((val as f64 / max_val * 255.0 + 0.5) as u8);
7419 }
7420 }
7421 }
7422
7423 result
7424}
7425
7426fn blend_mode_from_name(name: &[u8]) -> u8 {
7428 match name {
7429 b"Normal" | b"Compatible" => 0,
7430 b"Multiply" => 1,
7431 b"Screen" => 2,
7432 b"Overlay" => 3,
7433 b"Darken" => 4,
7434 b"Lighten" => 5,
7435 b"ColorDodge" => 6,
7436 b"ColorBurn" => 7,
7437 b"HardLight" => 8,
7438 b"SoftLight" => 9,
7439 b"Difference" => 10,
7440 b"Exclusion" => 11,
7441 b"Hue" => 12,
7442 b"Saturation" => 13,
7443 b"Color" => 14,
7444 b"Luminosity" => 15,
7445 _ => 0,
7446 }
7447}
7448
7449fn is_whitespace_byte(b: u8) -> bool {
7450 matches!(b, b' ' | b'\t' | b'\r' | b'\n' | 0x0C | 0x00)
7451}
7452
7453fn is_delimiter_or_ws(b: u8) -> bool {
7454 is_whitespace_byte(b)
7455 || matches!(
7456 b,
7457 b'(' | b')' | b'<' | b'>' | b'[' | b']' | b'{' | b'}' | b'/' | b'%'
7458 )
7459}
7460
7461fn sample_transfer_function(func: &crate::resources::function::PdfFunction) -> Vec<f64> {
7463 (0..256)
7464 .map(|i| {
7465 let t = i as f64 / 255.0;
7466 let result = func.evaluate(&[t]);
7467 result.first().copied().unwrap_or(t).clamp(0.0, 1.0)
7468 })
7469 .collect()
7470}
7471
7472fn apply_transfer_to_image(
7477 data: &mut [u8],
7478 transfer: &stet_graphics::device::TransferState,
7479 components: usize,
7480) {
7481 let (r_table, g_table, b_table) = if let Some(ref color) = transfer.color {
7483 let r = build_u8_lut(color[0].as_ref().map(|v| &v[..]));
7485 let g = build_u8_lut(color[1].as_ref().map(|v| &v[..]));
7486 let b = build_u8_lut(color[2].as_ref().map(|v| &v[..]));
7487 (r, g, b)
7488 } else if let Some(ref gray) = transfer.gray {
7489 let lut = build_u8_lut(Some(&gray[..]));
7491 (lut, lut, lut)
7492 } else {
7493 return; };
7495
7496 let stride = components;
7498 for pixel in data.chunks_exact_mut(stride) {
7499 if pixel.len() >= 3 {
7500 pixel[0] = r_table[pixel[0] as usize];
7501 pixel[1] = g_table[pixel[1] as usize];
7502 pixel[2] = b_table[pixel[2] as usize];
7503 }
7504 }
7505}
7506
7507fn apply_transfer_to_color(
7509 color: &DeviceColor,
7510 transfer: &stet_graphics::device::TransferState,
7511) -> DeviceColor {
7512 if let Some(ref color_tables) = transfer.color {
7513 let r = apply_transfer_component(color.r, color_tables[0].as_ref().map(|v| &v[..]));
7515 let g = apply_transfer_component(color.g, color_tables[1].as_ref().map(|v| &v[..]));
7516 let b = apply_transfer_component(color.b, color_tables[2].as_ref().map(|v| &v[..]));
7517 DeviceColor::from_rgb(r, g, b)
7518 } else if let Some(ref gray) = transfer.gray {
7519 let r = apply_transfer_component(color.r, Some(&gray[..]));
7520 let g = apply_transfer_component(color.g, Some(&gray[..]));
7521 let b = apply_transfer_component(color.b, Some(&gray[..]));
7522 DeviceColor::from_rgb(r, g, b)
7523 } else {
7524 color.clone()
7525 }
7526}
7527
7528fn apply_transfer_component(value: f64, table: Option<&[f64]>) -> f64 {
7530 match table {
7531 None => value,
7532 Some(t) if t.len() != 256 => value,
7533 Some(t) => {
7534 let idx = (value * 255.0).clamp(0.0, 255.0);
7535 let lo = idx.floor() as usize;
7536 let hi = (lo + 1).min(255);
7537 let frac = idx - lo as f64;
7538 let v0 = t[lo];
7539 let v1 = t[hi];
7540 (v0 + frac * (v1 - v0)).clamp(0.0, 1.0)
7541 }
7542 }
7543}
7544
7545fn build_u8_lut(table: Option<&[f64]>) -> [u8; 256] {
7547 let mut lut = [0u8; 256];
7548 match table {
7549 Some(t) if t.len() == 256 => {
7550 for (i, v) in lut.iter_mut().enumerate() {
7551 *v = (t[i].clamp(0.0, 1.0) * 255.0 + 0.5) as u8;
7552 }
7553 }
7554 _ => {
7555 for (i, v) in lut.iter_mut().enumerate() {
7556 *v = i as u8;
7557 }
7558 }
7559 }
7560 lut
7561}