1pub mod cid_unicode;
11pub mod cmap;
12pub mod color_space;
13pub mod font;
14mod gid_maps;
15pub mod graphics_state;
16mod standard_fonts;
17
18use crate::error::PdfError;
19use crate::lexer::{Lexer, Token};
20use crate::objects::{PdfDict, PdfObj};
21use crate::resolver::Resolver;
22
23use self::color_space::{
24 ResolvedColorSpace, components_to_device_color_icc, painted_channels_for_cs,
25 register_icc_profile, resolve_color_space, 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};
39use stet_graphics::display_list::{
40 DisplayElement, DisplayList, GroupParams, SoftMaskParams, SoftMaskSubtype,
41};
42use stet_graphics::icc::IccCache;
43
44enum MarkedContentFrame {
50 Ocg {
53 parent_list: DisplayList,
54 ocg_id: u32,
55 default_visible: bool,
56 },
57 Other,
59}
60
61fn deref_num_array(resolver: &Resolver, dict: &PdfDict, key: &[u8]) -> Option<Vec<f64>> {
69 let obj = dict.get(key)?;
70 if let Some(arr) = obj.as_array() {
71 return Some(arr.iter().filter_map(|o| o.as_f64()).collect());
72 }
73 let resolved = resolver.deref(obj).ok()?;
74 resolved
75 .as_array()
76 .map(|a| a.iter().filter_map(|o| o.as_f64()).collect())
77}
78
79#[derive(Clone, Debug)]
81pub enum Operand {
82 Int(i64),
83 Real(f64),
84 Name(Vec<u8>),
85 Str(Vec<u8>),
86 Array(Vec<PdfObj>),
87 Dict(PdfDict),
88 Bool(bool),
89}
90
91impl Operand {
92 fn as_f64(&self) -> Option<f64> {
94 match self {
95 Operand::Int(n) => Some(*n as f64),
96 Operand::Real(f) => Some(*f),
97 _ => None,
98 }
99 }
100
101 fn as_name(&self) -> Option<&[u8]> {
103 match self {
104 Operand::Name(n) => Some(n),
105 _ => None,
106 }
107 }
108
109 #[allow(dead_code)]
111 fn as_str(&self) -> Option<&[u8]> {
112 match self {
113 Operand::Str(s) => Some(s),
114 _ => None,
115 }
116 }
117}
118
119#[derive(Clone)]
122struct CachedImage {
123 sample_data: Arc<Vec<u8>>,
124 width: u32,
125 height: u32,
126 color_space: ImageColorSpace,
127 bits_per_component: u8,
128 interpolate: bool,
129 mask_color: Option<Vec<u8>>,
130 painted_channels: u8,
132 smask: Option<(Arc<Vec<u8>>, u32, u32, Option<Vec<f64>>)>,
134}
135
136struct SoftMaskScope {
138 start_index: usize,
140 mask: graphics_state::SoftMask,
142}
143
144pub struct ContentInterpreter<'a> {
146 resolver: &'a Resolver<'a>,
147 resources: PdfDict,
148 gstate_stack: Vec<PdfGraphicsState>,
149 gstate: PdfGraphicsState,
150 current_path: PsPath,
151 current_point: Option<(f64, f64)>,
152 subpath_start: Option<(f64, f64)>,
153 operand_stack: Vec<Operand>,
154 display_list: DisplayList,
155 in_text: bool,
156 depth: u32,
157 d1_color_suppressed: bool,
160 font_cache: FontCache,
161 current_font: Option<Arc<PdfFont>>,
162 content_stream_ctm: Matrix,
167 initial_ctm: Matrix,
172 icc_cache: IccCache,
174 soft_mask_scope: Option<SoftMaskScope>,
176 nested_mask_flush_count: u32,
180 font_provider: Option<FontProvider>,
182 text_clip_path: Option<PsPath>,
185 page_group_is_cmyk: bool,
188 overprint_enabled: bool,
191 pattern_cache: std::collections::HashMap<(u32, u16), TilingPattern>,
195 ocg_off: std::collections::HashSet<u32>,
197 mc_stack: Vec<MarkedContentFrame>,
202 cs_index: Option<std::collections::HashMap<Vec<u8>, PdfObj>>,
205 form_cull_y: Option<(f64, f64)>,
209 bt_culled: bool,
211 image_cache: std::collections::HashMap<u32, CachedImage>,
215}
216
217impl<'a> ContentInterpreter<'a> {
218 pub fn new(
220 resolver: &'a Resolver<'a>,
221 resources: PdfDict,
222 initial_ctm: Matrix,
223 icc_cache: &IccCache,
224 font_provider: Option<FontProvider>,
225 overprint_enabled: bool,
226 ocg_off: &std::collections::HashSet<u32>,
227 ) -> Self {
228 Self {
229 resolver,
230 resources,
231 gstate_stack: Vec::new(),
232 gstate: PdfGraphicsState::new(initial_ctm),
233 current_path: PsPath::new(),
234 current_point: None,
235 subpath_start: None,
236 operand_stack: Vec::new(),
237 display_list: DisplayList::new(),
238 content_stream_ctm: initial_ctm,
239 initial_ctm,
240 in_text: false,
241 depth: 0,
242 d1_color_suppressed: false,
243 nested_mask_flush_count: 0,
244 font_cache: FontCache::new(),
245 current_font: None,
246 icc_cache: icc_cache.clone(),
247 soft_mask_scope: None,
248 font_provider,
249 text_clip_path: None,
250 page_group_is_cmyk: false,
251 overprint_enabled,
252 pattern_cache: std::collections::HashMap::new(),
253 ocg_off: ocg_off.clone(),
254 mc_stack: Vec::new(),
255 cs_index: None,
256 form_cull_y: None,
257 bt_culled: false,
258 image_cache: std::collections::HashMap::new(),
259 }
260 }
261
262 pub fn set_page_group_cmyk(&mut self) {
266 self.page_group_is_cmyk = true;
267 }
268
269 fn resolve_dict_int(&self, dict: &PdfDict, key: &[u8]) -> Option<i64> {
273 let obj = dict.get(key)?;
274 if let Some(n) = obj.as_int() {
275 return Some(n);
276 }
277 let resolved = self.resolver.deref(obj).ok()?;
279 resolved.as_int()
280 }
281
282 fn resolve_resource_subdict(&self, key: &[u8]) -> Option<PdfDict> {
283 let obj = self.resources.get(key)?;
284 if let Some(d) = obj.as_dict() {
286 return Some(d.clone());
287 }
288 let resolved = self.resolver.deref(obj).ok()?;
290 resolved.as_dict().cloned()
291 }
292
293 pub fn interpret(mut self, data: &[u8]) -> Result<DisplayList, PdfError> {
295 if let Err(e) = self.interpret_stream(data) {
296 eprintln!("warning: content stream error: {}", e);
297 }
298 self.flush_soft_mask();
300 Ok(self.display_list)
303 }
304
305 pub fn interpret_stream_public(&mut self, data: &[u8]) -> Result<(), PdfError> {
307 self.interpret_stream(data)
308 }
309
310 pub fn into_display_list(mut self) -> DisplayList {
312 self.flush_soft_mask();
313 while let Some(frame) = self.mc_stack.pop() {
315 if let MarkedContentFrame::Ocg {
316 parent_list,
317 ocg_id,
318 default_visible,
319 } = frame
320 {
321 let ocg_list = std::mem::replace(&mut self.display_list, parent_list);
322 self.display_list.push(DisplayElement::OcgGroup {
323 elements: ocg_list,
324 ocg_id,
325 default_visible,
326 });
327 }
328 }
329 self.display_list
330 }
331
332 pub fn unwind_gstate_stack(&mut self) {
336 while let Some(saved) = self.gstate_stack.pop() {
337 let old_clip_version = self.gstate.clip_path_version;
338 self.gstate = saved;
339 if self.gstate.clip_path_version != old_clip_version {
340 self.restore_clip_from_stack();
341 }
342 }
343 }
344
345 pub fn reset_clip_for_annotations(&mut self) {
348 self.display_list.push(DisplayElement::InitClip);
349 self.gstate.clip_path = None;
350 self.gstate.clip_stack.clear();
351 self.gstate.clip_path_version += 1;
352 }
353
354 pub fn render_annotation(&mut self, obj_num: u32, gen_num: u16) -> Result<(), PdfError> {
356 let annot_obj = self.resolver.resolve(obj_num, gen_num)?;
357 let annot_dict = annot_obj
358 .as_dict()
359 .ok_or(PdfError::Other("annotation not a dict".into()))?;
360
361 let subtype = annot_dict.get_name(b"Subtype").unwrap_or(b"");
362
363 let flags = annot_dict.get_int(b"F").unwrap_or(0);
367 if flags & 0x02 != 0 {
368 return Ok(()); }
370
371 let rect = annot_dict
374 .get(b"Rect")
375 .and_then(|obj| {
376 let resolved = self.resolver.deref(obj).ok().unwrap_or(obj.clone());
377 let a = resolved
378 .as_array()
379 .or_else(|| annot_dict.get_array(b"Rect"))?;
380 if a.len() >= 4 {
381 let r0 = a[0].as_f64()?;
382 let r1 = a[1].as_f64()?;
383 let r2 = a[2].as_f64()?;
384 let r3 = a[3].as_f64()?;
385 Some([r0.min(r2), r1.min(r3), r0.max(r2), r1.max(r3)])
386 } else {
387 None
388 }
389 })
390 .ok_or(PdfError::Other("annotation missing Rect".into()))?;
391
392 let ap_obj = match annot_dict.get(b"AP") {
395 Some(ap) => ap,
396 None => {
397 return self.synthesize_annotation(annot_dict, &rect);
398 }
399 };
400 let ap_dict = match self.resolver.deref(ap_obj)? {
401 PdfObj::Dict(d) => d,
402 _ => return Err(PdfError::Other("AP not a dict".into())),
403 };
404
405 let n_ref = ap_dict.get(b"N").ok_or(PdfError::Other("no AP/N".into()))?;
406
407 let n_obj = self.resolver.deref(n_ref)?;
411 let (n_ref, form_dict) = if let Some(d) = n_obj.as_dict() {
412 if d.get(b"BBox").is_some() {
413 (n_ref.clone(), d.clone())
415 } else {
416 let as_name = annot_dict.get_name(b"AS").unwrap_or(b"Off");
422 let state_ref = match d.get(as_name) {
423 Some(r) => r,
424 None if subtype == b"Widget" => {
425 return Ok(());
427 }
428 None => {
429 match d.entries().first().map(|(_, v)| v) {
431 Some(r) => r,
432 None => return Ok(()),
433 }
434 }
435 };
436 let state_obj = self.resolver.deref(state_ref)?;
437 let state_dict = state_obj
438 .as_dict()
439 .ok_or(PdfError::Other("AP/N state not a stream".into()))?;
440 (state_ref.clone(), state_dict.clone())
441 }
442 } else {
443 return Err(PdfError::Other("AP/N not a dict or stream".into()));
444 };
445
446 let bbox = form_dict
450 .get(b"BBox")
451 .and_then(|obj| {
452 let resolved = self.resolver.deref(obj).ok().unwrap_or(obj.clone());
453 let a = resolved.as_array()?;
454 if a.len() >= 4 {
455 Some([
456 a[0].as_f64()?,
457 a[1].as_f64()?,
458 a[2].as_f64()?,
459 a[3].as_f64()?,
460 ])
461 } else {
462 None
463 }
464 })
465 .unwrap_or([rect[0], rect[1], rect[2], rect[3]]);
466
467 let form_matrix = deref_num_array(self.resolver, &form_dict, b"Matrix")
469 .and_then(|v| {
470 if v.len() == 6 {
471 Some(Matrix::new(v[0], v[1], v[2], v[3], v[4], v[5]))
472 } else {
473 None
474 }
475 })
476 .unwrap_or_else(Matrix::identity);
477
478 let (tb0x, tb0y) = form_matrix.transform_point(bbox[0], bbox[1]);
483 let (tb1x, tb1y) = form_matrix.transform_point(bbox[2], bbox[3]);
484 let tbbox_w = (tb1x - tb0x).abs().max(0.001);
485 let tbbox_h = (tb1y - tb0y).abs().max(0.001);
486 let rect_w = (rect[2] - rect[0]).abs();
487 let rect_h = (rect[3] - rect[1]).abs();
488 let sx = rect_w / tbbox_w;
489 let sy = rect_h / tbbox_h;
490 let tx = rect[0] - tb0x.min(tb1x) * sx;
491 let ty = rect[1] - tb0y.min(tb1y) * sy;
492 let bbox_to_rect = Matrix::new(sx, 0.0, 0.0, sy, tx, ty);
493
494 let saved_gstate = self.gstate.clone();
496 let saved_stack_depth = self.gstate_stack.len();
497 let saved_resources = self.resources.clone();
498 let saved_mc_stack = std::mem::take(&mut self.mc_stack);
499 if let Some(res_obj) = form_dict.get(b"Resources")
501 && let Ok(PdfObj::Dict(d)) = self.resolver.deref(res_obj)
502 {
503 self.resources = d;
504 }
505
506 self.gstate.ctm = self.initial_ctm.concat(&bbox_to_rect).concat(&form_matrix);
511
512 let saved_content_stream_ctm = self.content_stream_ctm;
515 self.content_stream_ctm = self.gstate.ctm;
516
517 let form_data = self.resolver.stream_data_from_obj(&n_ref)?;
521 self.depth += 1;
522 let _ = self.interpret_stream(&form_data);
523 self.depth -= 1;
524
525 self.gstate_stack.truncate(saved_stack_depth);
527 self.content_stream_ctm = saved_content_stream_ctm;
528 self.resources = saved_resources;
529 self.mc_stack = saved_mc_stack;
530 self.gstate = saved_gstate;
531
532 self.display_list.push(DisplayElement::InitClip);
534 if let Some(ref clip) = self.gstate.clip_path {
535 self.display_list.push(DisplayElement::Clip {
536 path: clip.clone(),
537 params: ClipParams {
538 fill_rule: FillRule::NonZeroWinding,
539 ctm: Matrix::identity(),
540 stroke_params: None,
541 },
542 });
543 }
544
545 Ok(())
546 }
547
548 fn synthesize_annotation(
551 &mut self,
552 dict: &crate::objects::PdfDict,
553 rect: &[f64; 4],
554 ) -> Result<(), PdfError> {
555 let subtype = dict.get_name(b"Subtype").unwrap_or(b"");
556
557 let color = if let Some(c) = dict.get_array(b"C") {
559 let vals: Vec<f64> = c.iter().filter_map(|o| o.as_f64()).collect();
560 match vals.len() {
561 1 => DeviceColor::from_gray(vals[0]),
562 3 => DeviceColor::from_rgb(vals[0], vals[1], vals[2]),
563 4 => DeviceColor::from_cmyk(vals[0], vals[1], vals[2], vals[3]),
564 _ => DeviceColor::from_gray(0.0),
565 }
566 } else {
567 DeviceColor::from_gray(0.0)
568 };
569
570 let alpha = dict.get(b"CA").and_then(|o| o.as_f64()).unwrap_or(1.0);
572
573 let border_width = dict
575 .get(b"BS")
576 .and_then(|bs| self.resolver.deref(bs).ok())
577 .and_then(|bs| bs.as_dict().and_then(|d| d.get_f64(b"W")))
578 .or_else(|| {
579 dict.get_array(b"Border")
580 .and_then(|arr| arr.get(2).and_then(|o| o.as_f64()))
581 })
582 .unwrap_or(1.0);
583
584 let dash = dict
586 .get(b"BS")
587 .and_then(|bs| self.resolver.deref(bs).ok())
588 .and_then(|bs| {
589 let d = bs.as_dict()?;
590 let style = d.get_name(b"S")?;
591 if style == b"D" {
592 let arr = d
593 .get_array(b"D")
594 .map(|a| a.iter().filter_map(|o| o.as_f64()).collect::<Vec<_>>())
595 .unwrap_or_else(|| vec![3.0]);
596 Some(DashPattern {
597 array: arr,
598 offset: 0.0,
599 })
600 } else {
601 None
602 }
603 })
604 .unwrap_or_default();
605
606 let ctm = self.initial_ctm;
607
608 match subtype {
609 b"Line" => {
610 if let Some(l) = dict.get_array(b"L") {
612 let coords: Vec<f64> = l.iter().filter_map(|o| o.as_f64()).collect();
613 if coords.len() >= 4 {
614 let (x1, y1, x2, y2) = (coords[0], coords[1], coords[2], coords[3]);
615 let path = PsPath {
616 segments: vec![
617 PathSegment::MoveTo(x1, y1),
618 PathSegment::LineTo(x2, y2),
619 ],
620 };
621 self.display_list.push(DisplayElement::Stroke {
622 path,
623 params: StrokeParams {
624 color: color.clone(),
625 line_width: border_width,
626 line_cap: LineCap::Butt,
627 line_join: LineJoin::Miter,
628 miter_limit: 10.0,
629 dash_pattern: dash.clone(),
630 ctm,
631 stroke_adjust: false,
632 is_text_glyph: false,
633 overprint: false,
634 overprint_mode: 0,
635 opm_paired: false,
636 painted_channels: 0,
637 is_device_cmyk: false,
638 spot_color: None,
639 rendering_intent: 0,
640 transfer: Default::default(),
641 halftone: Default::default(),
642 bg_ucr: Default::default(),
643 alpha,
644 blend_mode: 0,
645 },
646 });
647 }
648 }
649 }
650 b"PolyLine" | b"Polygon" => {
651 if let Some(verts) = dict.get_array(b"Vertices") {
652 let coords: Vec<f64> = verts.iter().filter_map(|o| o.as_f64()).collect();
653 if coords.len() >= 4 {
654 let mut segs = vec![PathSegment::MoveTo(coords[0], coords[1])];
655 for pair in coords[2..].chunks_exact(2) {
656 segs.push(PathSegment::LineTo(pair[0], pair[1]));
657 }
658 if subtype == b"Polygon" {
659 segs.push(PathSegment::ClosePath);
660 }
661 let path = PsPath { segments: segs };
662 self.display_list.push(DisplayElement::Stroke {
663 path,
664 params: StrokeParams {
665 color: color.clone(),
666 line_width: border_width,
667 line_cap: LineCap::Butt,
668 line_join: LineJoin::Miter,
669 miter_limit: 10.0,
670 dash_pattern: dash.clone(),
671 ctm,
672 stroke_adjust: false,
673 is_text_glyph: false,
674 overprint: false,
675 overprint_mode: 0,
676 opm_paired: false,
677 painted_channels: 0,
678 is_device_cmyk: false,
679 spot_color: None,
680 rendering_intent: 0,
681 transfer: Default::default(),
682 halftone: Default::default(),
683 bg_ucr: Default::default(),
684 alpha,
685 blend_mode: 0,
686 },
687 });
688 }
689 }
690 }
691 b"Ink" => {
692 if let Some(ink_list) = dict.get_array(b"InkList") {
693 for stroke_obj in ink_list {
694 let stroke_arr = match stroke_obj {
695 crate::objects::PdfObj::Array(a) => a,
696 _ => continue,
697 };
698 let coords: Vec<f64> =
699 stroke_arr.iter().filter_map(|o| o.as_f64()).collect();
700 if coords.len() >= 4 {
701 let mut segs = vec![PathSegment::MoveTo(coords[0], coords[1])];
702 for pair in coords[2..].chunks_exact(2) {
703 segs.push(PathSegment::LineTo(pair[0], pair[1]));
704 }
705 let path = PsPath { segments: segs };
706 self.display_list.push(DisplayElement::Stroke {
707 path,
708 params: StrokeParams {
709 color: color.clone(),
710 line_width: border_width,
711 line_cap: LineCap::Round,
712 line_join: LineJoin::Round,
713 miter_limit: 10.0,
714 dash_pattern: DashPattern::default(),
715 ctm,
716 stroke_adjust: false,
717 is_text_glyph: false,
718 overprint: false,
719 overprint_mode: 0,
720 opm_paired: false,
721 painted_channels: 0,
722 is_device_cmyk: false,
723 spot_color: None,
724 rendering_intent: 0,
725 transfer: Default::default(),
726 halftone: Default::default(),
727 bg_ucr: Default::default(),
728 alpha,
729 blend_mode: 0,
730 },
731 });
732 }
733 }
734 }
735 }
736 b"Highlight" | b"StrikeOut" | b"Underline" | b"Squiggly" => {
737 if let Some(qp) = dict.get_array(b"QuadPoints") {
738 let pts: Vec<f64> = qp.iter().filter_map(|o| o.as_f64()).collect();
739 for quad in pts.chunks_exact(8) {
742 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" {
748 let path = PsPath {
750 segments: vec![
751 PathSegment::MoveTo(x1, y1),
752 PathSegment::LineTo(x2, y2),
753 PathSegment::LineTo(x4, y4),
754 PathSegment::LineTo(x3, y3),
755 PathSegment::ClosePath,
756 ],
757 };
758 self.display_list.push(DisplayElement::Fill {
759 path,
760 params: FillParams {
761 color: color.clone(),
762 fill_rule: FillRule::NonZeroWinding,
763 ctm,
764 is_text_glyph: false,
765 overprint: false,
766 overprint_mode: 0,
767 opm_paired: false,
768 painted_channels: 0,
769 is_device_cmyk: false,
770 spot_color: None,
771 rendering_intent: 0,
772 transfer: Default::default(),
773 halftone: Default::default(),
774 bg_ucr: Default::default(),
775 alpha,
776 blend_mode: 3, },
778 });
779 } else {
780 let (lx1, ly1, lx2, ly2) = if subtype == b"StrikeOut" {
782 (
784 (x1 + x3) / 2.0,
785 (y1 + y3) / 2.0,
786 (x2 + x4) / 2.0,
787 (y2 + y4) / 2.0,
788 )
789 } else {
790 (x3, y3, x4, y4)
792 };
793 let path = PsPath {
794 segments: vec![
795 PathSegment::MoveTo(lx1, ly1),
796 PathSegment::LineTo(lx2, ly2),
797 ],
798 };
799 self.display_list.push(DisplayElement::Stroke {
800 path,
801 params: StrokeParams {
802 color: color.clone(),
803 line_width: border_width,
804 line_cap: LineCap::Butt,
805 line_join: LineJoin::Miter,
806 miter_limit: 10.0,
807 dash_pattern: DashPattern::default(),
808 ctm,
809 stroke_adjust: false,
810 is_text_glyph: false,
811 overprint: false,
812 overprint_mode: 0,
813 opm_paired: false,
814 painted_channels: 0,
815 is_device_cmyk: false,
816 spot_color: None,
817 rendering_intent: 0,
818 transfer: Default::default(),
819 halftone: Default::default(),
820 bg_ucr: Default::default(),
821 alpha,
822 blend_mode: 0,
823 },
824 });
825 }
826 }
827 }
828 }
829 b"Square" => {
830 let has_ic = dict.get_array(b"IC").is_some();
832 if border_width < 0.001 && !has_ic {
833 return Ok(());
834 }
835 let path = PsPath {
836 segments: vec![
837 PathSegment::MoveTo(rect[0], rect[1]),
838 PathSegment::LineTo(rect[2], rect[1]),
839 PathSegment::LineTo(rect[2], rect[3]),
840 PathSegment::LineTo(rect[0], rect[3]),
841 PathSegment::ClosePath,
842 ],
843 };
844 if let Some(ic) = dict.get_array(b"IC") {
846 let vals: Vec<f64> = ic.iter().filter_map(|o| o.as_f64()).collect();
847 let ic_color = match vals.len() {
848 1 => DeviceColor::from_gray(vals[0]),
849 3 => DeviceColor::from_rgb(vals[0], vals[1], vals[2]),
850 4 => DeviceColor::from_cmyk(vals[0], vals[1], vals[2], vals[3]),
851 _ => DeviceColor::from_gray(1.0),
852 };
853 self.display_list.push(DisplayElement::Fill {
854 path: path.clone(),
855 params: FillParams {
856 color: ic_color,
857 fill_rule: FillRule::NonZeroWinding,
858 ctm,
859 is_text_glyph: false,
860 overprint: false,
861 overprint_mode: 0,
862 opm_paired: false,
863 painted_channels: 0,
864 is_device_cmyk: false,
865 spot_color: None,
866 rendering_intent: 0,
867 transfer: Default::default(),
868 halftone: Default::default(),
869 bg_ucr: Default::default(),
870 alpha,
871 blend_mode: 0,
872 },
873 });
874 }
875 if border_width < 0.001 {
876 return Ok(());
877 }
878 self.display_list.push(DisplayElement::Stroke {
879 path,
880 params: StrokeParams {
881 color,
882 line_width: border_width,
883 line_cap: LineCap::Butt,
884 line_join: LineJoin::Miter,
885 miter_limit: 10.0,
886 dash_pattern: dash,
887 ctm,
888 stroke_adjust: false,
889 is_text_glyph: false,
890 overprint: false,
891 overprint_mode: 0,
892 opm_paired: false,
893 painted_channels: 0,
894 is_device_cmyk: false,
895 spot_color: None,
896 rendering_intent: 0,
897 transfer: Default::default(),
898 halftone: Default::default(),
899 bg_ucr: Default::default(),
900 alpha,
901 blend_mode: 0,
902 },
903 });
904 }
905 b"Circle" => {
906 let has_ic = dict.get_array(b"IC").is_some();
907 if border_width < 0.001 && !has_ic {
908 return Ok(());
909 }
910 let cx = (rect[0] + rect[2]) / 2.0;
912 let cy = (rect[1] + rect[3]) / 2.0;
913 let rx = (rect[2] - rect[0]) / 2.0;
914 let ry = (rect[3] - rect[1]) / 2.0;
915 let k = 0.5522847498; let path = PsPath {
917 segments: vec![
918 PathSegment::MoveTo(cx + rx, cy),
919 PathSegment::CurveTo {
920 x1: cx + rx,
921 y1: cy + ry * k,
922 x2: cx + rx * k,
923 y2: cy + ry,
924 x3: cx,
925 y3: cy + ry,
926 },
927 PathSegment::CurveTo {
928 x1: cx - rx * k,
929 y1: cy + ry,
930 x2: cx - rx,
931 y2: cy + ry * k,
932 x3: cx - rx,
933 y3: cy,
934 },
935 PathSegment::CurveTo {
936 x1: cx - rx,
937 y1: cy - ry * k,
938 x2: cx - rx * k,
939 y2: cy - ry,
940 x3: cx,
941 y3: cy - ry,
942 },
943 PathSegment::CurveTo {
944 x1: cx + rx * k,
945 y1: cy - ry,
946 x2: cx + rx,
947 y2: cy - ry * k,
948 x3: cx + rx,
949 y3: cy,
950 },
951 PathSegment::ClosePath,
952 ],
953 };
954 if let Some(ic) = dict.get_array(b"IC") {
955 let vals: Vec<f64> = ic.iter().filter_map(|o| o.as_f64()).collect();
956 let ic_color = match vals.len() {
957 1 => DeviceColor::from_gray(vals[0]),
958 3 => DeviceColor::from_rgb(vals[0], vals[1], vals[2]),
959 4 => DeviceColor::from_cmyk(vals[0], vals[1], vals[2], vals[3]),
960 _ => DeviceColor::from_gray(1.0),
961 };
962 self.display_list.push(DisplayElement::Fill {
963 path: path.clone(),
964 params: FillParams {
965 color: ic_color,
966 fill_rule: FillRule::NonZeroWinding,
967 ctm,
968 is_text_glyph: false,
969 overprint: false,
970 overprint_mode: 0,
971 opm_paired: false,
972 painted_channels: 0,
973 is_device_cmyk: false,
974 spot_color: None,
975 rendering_intent: 0,
976 transfer: Default::default(),
977 halftone: Default::default(),
978 bg_ucr: Default::default(),
979 alpha,
980 blend_mode: 0,
981 },
982 });
983 }
984 if border_width < 0.001 {
985 return Ok(());
986 }
987 self.display_list.push(DisplayElement::Stroke {
988 path,
989 params: StrokeParams {
990 color,
991 line_width: border_width,
992 line_cap: LineCap::Butt,
993 line_join: LineJoin::Miter,
994 miter_limit: 10.0,
995 dash_pattern: dash,
996 ctm,
997 stroke_adjust: false,
998 is_text_glyph: false,
999 overprint: false,
1000 overprint_mode: 0,
1001 opm_paired: false,
1002 painted_channels: 0,
1003 is_device_cmyk: false,
1004 spot_color: None,
1005 rendering_intent: 0,
1006 transfer: Default::default(),
1007 halftone: Default::default(),
1008 bg_ucr: Default::default(),
1009 alpha,
1010 blend_mode: 0,
1011 },
1012 });
1013 }
1014 _ => {
1015 }
1017 }
1018
1019 Ok(())
1020 }
1021
1022 fn interpret_stream(&mut self, data: &[u8]) -> Result<(), PdfError> {
1024 let saved_operand_stack = std::mem::take(&mut self.operand_stack);
1033 let result = self.interpret_stream_inner(data);
1034 self.operand_stack = saved_operand_stack;
1035 result
1036 }
1037
1038 fn interpret_stream_inner(&mut self, data: &[u8]) -> Result<(), PdfError> {
1039 let mut lexer = Lexer::new(data);
1040 let mut prev_token_was_glued_number = false;
1046 loop {
1047 let pos_before = lexer.pos();
1052 let glued_to_prev_number = prev_token_was_glued_number
1053 && pos_before < data.len()
1054 && !is_whitespace_byte(data[pos_before]);
1055 let tok = match lexer.next_token() {
1056 Ok(t) => t,
1057 Err(_) => {
1058 prev_token_was_glued_number = false;
1059 continue;
1060 }
1061 };
1062 prev_token_was_glued_number = false;
1065 match tok {
1066 Token::Eof => break,
1067 Token::Int(n) => {
1068 self.operand_stack.push(Operand::Int(n));
1069 let p = lexer.pos();
1070 prev_token_was_glued_number = p < data.len() && !is_whitespace_byte(data[p]);
1071 }
1072 Token::Real(f) => {
1073 self.operand_stack.push(Operand::Real(f));
1074 let p = lexer.pos();
1075 prev_token_was_glued_number = p < data.len() && !is_whitespace_byte(data[p]);
1076 }
1077 Token::Name(n) => self.operand_stack.push(Operand::Name(n)),
1078 Token::LitString(s) | Token::HexString(s) => {
1079 self.operand_stack.push(Operand::Str(s));
1080 }
1081 Token::Bool(b) => self.operand_stack.push(Operand::Bool(b)),
1082 Token::ArrayBegin => {
1083 let arr = Self::parse_inline_array(&mut lexer)?;
1084 self.operand_stack.push(Operand::Array(arr));
1085 }
1086 Token::DictBegin => {
1087 let dict = crate::lexer::parse_dict_body(&mut lexer)?;
1088 self.operand_stack.push(Operand::Dict(dict));
1089 }
1090 Token::Keyword(kw) => {
1091 let op = if matches!(kw.as_slice(), b"f" | b"B" | b"b" | b"W" | b"T") {
1095 let p = lexer.pos();
1096 if p < data.len() && data[p] == b'*' {
1097 lexer.set_pos(p + 1);
1098 let mut combined = kw;
1099 combined.push(b'*');
1100 combined
1101 } else {
1102 kw
1103 }
1104 } else if kw == b"d" {
1105 let p = lexer.pos();
1106 if p < data.len() && (data[p] == b'0' || data[p] == b'1') {
1107 lexer.set_pos(p + 1);
1108 let mut combined = kw;
1109 combined.push(data[p]);
1110 combined
1111 } else {
1112 kw
1113 }
1114 } else {
1115 kw
1116 };
1117
1118 if op == b"BI" {
1119 self.handle_inline_image(&mut lexer)?;
1120 } else if let Err(_e) = self.dispatch_operator(&op, glued_to_prev_number) {
1121 }
1122 self.operand_stack.clear();
1123 }
1124 Token::DictEnd | Token::ArrayEnd => {
1125 }
1127 }
1128 }
1129 Ok(())
1130 }
1131
1132 fn parse_inline_array(lexer: &mut Lexer) -> Result<Vec<PdfObj>, PdfError> {
1134 let mut elems = Vec::new();
1135 loop {
1136 let tok = lexer.next_token()?;
1137 match tok {
1138 Token::ArrayEnd | Token::Eof => break,
1139 Token::Int(n) => elems.push(PdfObj::Int(n)),
1140 Token::Real(f) => elems.push(PdfObj::Real(f)),
1141 Token::Name(n) => elems.push(PdfObj::Name(n)),
1142 Token::LitString(s) | Token::HexString(s) => elems.push(PdfObj::Str(s)),
1143 Token::Bool(b) => elems.push(PdfObj::Bool(b)),
1144 Token::ArrayBegin => {
1145 let sub = Self::parse_inline_array(lexer)?;
1146 elems.push(PdfObj::Array(sub));
1147 }
1148 Token::DictBegin => {
1149 let d = crate::lexer::parse_dict_body(lexer).unwrap_or_default();
1150 elems.push(PdfObj::Dict(d));
1151 }
1152 Token::Keyword(ref kw) if kw == b"null" => {
1153 elems.push(PdfObj::Null);
1154 }
1155 _ => {}
1156 }
1157 }
1158 Ok(elems)
1159 }
1160
1161 fn dispatch_operator(&mut self, op: &[u8], glued_to_prev_number: bool) -> Result<(), PdfError> {
1171 let expected_args: i32 = match op {
1177 b"m" | b"l" => 2,
1178 b"v" | b"y" | b"re" => 4,
1179 b"c" => 6,
1180 b"h" | b"S" | b"s" | b"f" | b"F" | b"f*" | b"B" | b"B*" | b"b" | b"b*" | b"n" => 0,
1181 _ => -1, };
1183 if expected_args >= 0
1184 && self.operand_stack.len() > expected_args as usize
1185 && !glued_to_prev_number
1186 {
1187 return Ok(());
1188 }
1189
1190 if self.bt_culled {
1192 if op == b"ET" {
1193 self.bt_culled = false;
1194 self.in_text = false;
1195 }
1196 self.operand_stack.clear();
1197 return Ok(());
1198 }
1199
1200 match op {
1201 b"q" => self.op_q(),
1203 b"Q" => self.op_big_q(),
1204 b"cm" => self.op_cm(),
1205 b"w" => self.op_w(),
1206 b"J" => self.op_big_j(),
1207 b"j" => self.op_j(),
1208 b"M" => self.op_big_m(),
1209 b"d" => self.op_d(),
1210 b"ri" => self.op_ri(),
1211 b"i" => self.op_i(),
1212 b"gs" => self.op_gs(),
1213
1214 b"m" => self.op_m(),
1216 b"l" => self.op_l(),
1217 b"c" => self.op_c(),
1218 b"v" => self.op_v(),
1219 b"y" => self.op_y(),
1220 b"h" => self.op_h(),
1221 b"re" => self.op_re(),
1222
1223 b"S" => self.op_big_s(),
1225 b"s" => self.op_small_s(),
1226 b"f" | b"F" => self.op_f(),
1227 b"f*" => self.op_f_star(),
1228 b"B" => self.op_big_b(),
1229 b"B*" => self.op_big_b_star(),
1230 b"b" => self.op_small_b(),
1231 b"b*" => self.op_small_b_star(),
1232 b"n" => self.op_n(),
1233
1234 b"W" => self.op_big_w(),
1236 b"W*" => self.op_big_w_star(),
1237
1238 b"G" if !self.d1_color_suppressed => self.op_big_g(),
1240 b"g" if !self.d1_color_suppressed => self.op_small_g(),
1241 b"RG" if !self.d1_color_suppressed => self.op_big_rg(),
1242 b"rg" if !self.d1_color_suppressed => self.op_small_rg(),
1243 b"K" if !self.d1_color_suppressed => self.op_big_k(),
1244 b"k" if !self.d1_color_suppressed => self.op_small_k(),
1245 b"G" | b"g" | b"RG" | b"rg" | b"K" | b"k" => Ok(()),
1246
1247 b"CS" if !self.d1_color_suppressed => self.op_big_cs(),
1249 b"cs" if !self.d1_color_suppressed => self.op_small_cs(),
1250 b"SC" | b"SCN" if !self.d1_color_suppressed => self.op_sc_stroke(),
1251 b"sc" | b"scn" if !self.d1_color_suppressed => self.op_sc_fill(),
1252 b"CS" | b"cs" | b"SC" | b"SCN" | b"sc" | b"scn" => Ok(()),
1253
1254 b"BT" => {
1256 self.in_text = true;
1257 self.gstate.text_matrix = Matrix::identity();
1258 self.gstate.text_line_matrix = Matrix::identity();
1259 Ok(())
1260 }
1261 b"ET" => {
1262 self.in_text = false;
1263 if let Some(clip_path) = self.text_clip_path.take()
1265 && !clip_path.is_empty()
1266 {
1267 self.display_list.push(DisplayElement::Clip {
1268 path: clip_path.clone(),
1269 params: ClipParams {
1270 fill_rule: FillRule::NonZeroWinding,
1271 ctm: Matrix::identity(),
1272 stroke_params: None,
1273 },
1274 });
1275 self.gstate
1277 .clip_stack
1278 .push((clip_path.clone(), FillRule::NonZeroWinding));
1279 self.gstate.clip_path = Some(clip_path);
1280 self.gstate.clip_path_version += 1;
1281 }
1282 Ok(())
1283 }
1284 b"Tf" => self.op_tf(),
1285 b"Tc" => {
1286 self.gstate.char_spacing = self.pop_number()?;
1287 Ok(())
1288 }
1289 b"Tw" => {
1290 self.gstate.word_spacing = self.pop_number()?;
1291 Ok(())
1292 }
1293 b"TL" => {
1294 self.gstate.text_leading = self.pop_number()?;
1295 Ok(())
1296 }
1297 b"Tr" => {
1298 self.gstate.text_rendering_mode = self.pop_number()? as i32;
1299 Ok(())
1300 }
1301 b"Ts" => {
1302 self.gstate.text_rise = self.pop_number()?;
1303 Ok(())
1304 }
1305 b"Tz" => {
1306 self.gstate.horizontal_scaling = self.pop_number()? / 100.0;
1307 Ok(())
1308 }
1309 b"Td" => self.op_td(),
1310 b"TD" => self.op_big_td(),
1311 b"Tm" => self.op_tm(),
1312 b"T*" => self.op_t_star(),
1313 b"Tj" => self.op_tj(),
1314 b"TJ" => self.op_big_tj(),
1315 b"'" => self.op_quote(),
1316 b"\"" => self.op_dblquote(),
1317
1318 b"Do" => self.op_do(),
1320
1321 b"sh" => self.op_sh(),
1323
1324 b"BMC" => {
1328 self.operand_stack.pop();
1329 self.mc_stack.push(MarkedContentFrame::Other);
1330 Ok(())
1331 }
1332 b"MP" => {
1333 self.operand_stack.pop();
1334 Ok(())
1335 }
1336 b"DP" => {
1337 self.operand_stack.pop();
1338 self.operand_stack.pop();
1339 Ok(())
1340 }
1341 b"BDC" => self.op_bdc(),
1342 b"EMC" => {
1343 if let Some(MarkedContentFrame::Ocg {
1344 parent_list,
1345 ocg_id,
1346 default_visible,
1347 }) = self.mc_stack.pop()
1348 {
1349 let ocg_list = std::mem::replace(&mut self.display_list, parent_list);
1350 self.display_list.push(DisplayElement::OcgGroup {
1351 elements: ocg_list,
1352 ocg_id,
1353 default_visible,
1354 });
1355 }
1356 Ok(())
1357 }
1358
1359 b"d0" => Ok(()),
1361 b"d1" => {
1362 self.d1_color_suppressed = true;
1373 self.gstate.stroke_color = self.gstate.fill_color.clone();
1374 self.gstate.stroke_color_space = self.gstate.fill_color_space.clone();
1375 self.gstate.stroke_pattern = None;
1376 self.gstate.stroke_shading_pattern = None;
1377 self.gstate.stroke_painted_channels = self.gstate.fill_painted_channels;
1378 self.gstate.stroke_is_device_cmyk = self.gstate.fill_is_device_cmyk;
1379 self.gstate.stroke_is_none = self.gstate.fill_is_none;
1380 Ok(())
1381 }
1382
1383 b"BX" | b"EX" => Ok(()),
1385
1386 _ => {
1387 Ok(())
1389 }
1390 }
1391 }
1392
1393 fn pop_number(&self) -> Result<f64, PdfError> {
1397 self.operand_stack
1398 .last()
1399 .and_then(|o| o.as_f64())
1400 .ok_or(PdfError::Other("expected number on operand stack".into()))
1401 }
1402
1403 fn get_numbers(&self, n: usize) -> Result<Vec<f64>, PdfError> {
1405 let len = self.operand_stack.len();
1406 if len < n {
1407 return Err(PdfError::Other(format!("need {n} operands, have {len}")));
1408 }
1409 let mut nums = Vec::with_capacity(n);
1410 for i in (len - n)..len {
1411 nums.push(
1412 self.operand_stack[i]
1413 .as_f64()
1414 .ok_or(PdfError::Other("expected number".into()))?,
1415 );
1416 }
1417 Ok(nums)
1418 }
1419
1420 fn transform(&self, x: f64, y: f64) -> (f64, f64) {
1422 self.gstate.ctm.transform_point(x, y)
1423 }
1424
1425 fn take_path(&mut self) -> PsPath {
1427 let path = std::mem::take(&mut self.current_path);
1428 self.current_point = None;
1429 self.subpath_start = None;
1430 path
1431 }
1432
1433 fn apply_pending_clip(&mut self) {
1435 if let Some((path, fill_rule)) = self.gstate.pending_clip.take() {
1436 let has_drawing_segments = path.segments.iter().any(|s| {
1441 matches!(
1442 s,
1443 PathSegment::LineTo(..) | PathSegment::CurveTo { .. } | PathSegment::ClosePath
1444 )
1445 });
1446 let has_moveto = path
1449 .segments
1450 .iter()
1451 .any(|s| matches!(s, PathSegment::MoveTo(..)));
1452 if !has_drawing_segments && has_moveto {
1453 let mut empty = PsPath::new();
1455 empty.segments.push(PathSegment::MoveTo(0.0, 0.0));
1456 empty.segments.push(PathSegment::LineTo(0.0, 0.0));
1457 empty.segments.push(PathSegment::ClosePath);
1458 self.display_list.push(DisplayElement::Clip {
1459 path: empty.clone(),
1460 params: ClipParams {
1461 fill_rule,
1462 ctm: Matrix::identity(),
1463 stroke_params: None,
1464 },
1465 });
1466 self.gstate.clip_stack.push((empty.clone(), fill_rule));
1467 self.gstate.clip_path = Some(empty);
1468 self.gstate.clip_path_version += 1;
1469 return;
1470 }
1471 if !has_drawing_segments {
1472 return;
1474 }
1475 let clip_path = path;
1476 self.display_list.push(DisplayElement::Clip {
1477 path: clip_path.clone(),
1478 params: ClipParams {
1479 fill_rule,
1480 ctm: Matrix::identity(),
1481 stroke_params: None,
1482 },
1483 });
1484 self.gstate.clip_stack.push((clip_path.clone(), fill_rule));
1486 self.gstate.clip_path = Some(clip_path);
1487 self.gstate.clip_path_version += 1;
1488 }
1489 }
1490
1491 fn op_q(&mut self) -> Result<(), PdfError> {
1494 self.gstate_stack.push(self.gstate.clone());
1495 Ok(())
1496 }
1497
1498 fn op_big_q(&mut self) -> Result<(), PdfError> {
1499 if let Some(saved) = self.gstate_stack.pop() {
1500 if self.soft_mask_scope.is_some() && self.gstate.smask_gen != saved.smask_gen {
1506 self.flush_soft_mask();
1507 self.nested_mask_flush_count += 1;
1508 }
1509
1510 let old_clip_version = self.gstate.clip_path_version;
1511 let old_font_name = std::mem::take(&mut self.gstate.text_font_name);
1512 self.gstate = saved;
1513 if self.gstate.clip_path_version != old_clip_version {
1516 self.restore_clip_from_stack();
1517 }
1518 if self.gstate.text_font_name != old_font_name && !self.gstate.text_font_name.is_empty()
1520 {
1521 let name = self.gstate.text_font_name.clone();
1522 self.resolve_current_font(&name);
1523 }
1524 }
1525 Ok(())
1526 }
1527
1528 fn restore_clip_from_stack(&mut self) {
1530 self.display_list.push(DisplayElement::InitClip);
1531 for (clip, fill_rule) in &self.gstate.clip_stack {
1532 self.display_list.push(DisplayElement::Clip {
1533 path: clip.clone(),
1534 params: ClipParams {
1535 fill_rule: *fill_rule,
1536 ctm: Matrix::identity(),
1537 stroke_params: None,
1538 },
1539 });
1540 }
1541 }
1542
1543 fn op_cm(&mut self) -> Result<(), PdfError> {
1544 let n = self.get_numbers(6)?;
1545 let m = Matrix::new(n[0], n[1], n[2], n[3], n[4], n[5]);
1546 self.gstate.ctm = self.gstate.ctm.concat(&m);
1548 Ok(())
1549 }
1550
1551 fn op_w(&mut self) -> Result<(), PdfError> {
1552 self.gstate.line_width = self.pop_number()?;
1553 Ok(())
1554 }
1555
1556 fn op_big_j(&mut self) -> Result<(), PdfError> {
1557 let cap = self.pop_number()? as i32;
1558 if let Some(lc) = LineCap::from_i32(cap) {
1559 self.gstate.line_cap = lc;
1560 }
1561 Ok(())
1562 }
1563
1564 fn op_j(&mut self) -> Result<(), PdfError> {
1565 let join = self.pop_number()? as i32;
1566 if let Some(lj) = LineJoin::from_i32(join) {
1567 self.gstate.line_join = lj;
1568 }
1569 Ok(())
1570 }
1571
1572 fn op_big_m(&mut self) -> Result<(), PdfError> {
1573 self.gstate.miter_limit = self.pop_number()?;
1574 Ok(())
1575 }
1576
1577 fn op_d(&mut self) -> Result<(), PdfError> {
1578 let len = self.operand_stack.len();
1580 if len < 2 {
1581 return Ok(());
1582 }
1583 let offset = self.operand_stack[len - 1].as_f64().unwrap_or(0.0);
1584 let array = match &self.operand_stack[len - 2] {
1585 Operand::Array(arr) => arr.iter().filter_map(|o| o.as_f64()).collect::<Vec<_>>(),
1586 _ => Vec::new(),
1587 };
1588 self.gstate.dash_pattern = DashPattern { array, offset };
1589 Ok(())
1590 }
1591
1592 fn op_ri(&mut self) -> Result<(), PdfError> {
1593 Ok(())
1595 }
1596
1597 fn op_i(&mut self) -> Result<(), PdfError> {
1598 self.gstate.flatness = self.pop_number()?;
1599 Ok(())
1600 }
1601
1602 fn op_gs(&mut self) -> Result<(), PdfError> {
1603 let name = self
1604 .operand_stack
1605 .last()
1606 .and_then(|o| o.as_name())
1607 .ok_or(PdfError::Other("gs: expected name".into()))?
1608 .to_vec();
1609 self.apply_ext_gstate(&name)
1610 }
1611
1612 fn op_m(&mut self) -> Result<(), PdfError> {
1615 let n = self.get_numbers(2)?;
1616 let (dx, dy) = self.transform(n[0], n[1]);
1617 self.current_path.segments.push(PathSegment::MoveTo(dx, dy));
1618 self.current_point = Some((dx, dy));
1619 self.subpath_start = Some((dx, dy));
1620 Ok(())
1621 }
1622
1623 fn op_l(&mut self) -> Result<(), PdfError> {
1624 let n = self.get_numbers(2)?;
1625 let (dx, dy) = self.transform(n[0], n[1]);
1626 self.current_path.segments.push(PathSegment::LineTo(dx, dy));
1627 self.current_point = Some((dx, dy));
1628 Ok(())
1629 }
1630
1631 fn op_c(&mut self) -> Result<(), PdfError> {
1632 let n = self.get_numbers(6)?;
1633 let (x1, y1) = self.transform(n[0], n[1]);
1634 let (x2, y2) = self.transform(n[2], n[3]);
1635 let (x3, y3) = self.transform(n[4], n[5]);
1636 self.current_path.segments.push(PathSegment::CurveTo {
1637 x1,
1638 y1,
1639 x2,
1640 y2,
1641 x3,
1642 y3,
1643 });
1644 self.current_point = Some((x3, y3));
1645 Ok(())
1646 }
1647
1648 fn op_v(&mut self) -> Result<(), PdfError> {
1649 let n = self.get_numbers(4)?;
1650 let (x1, y1) = self.current_point.unwrap_or((0.0, 0.0));
1651 let (x2, y2) = self.transform(n[0], n[1]);
1652 let (x3, y3) = self.transform(n[2], n[3]);
1653 self.current_path.segments.push(PathSegment::CurveTo {
1654 x1,
1655 y1,
1656 x2,
1657 y2,
1658 x3,
1659 y3,
1660 });
1661 self.current_point = Some((x3, y3));
1662 Ok(())
1663 }
1664
1665 fn op_y(&mut self) -> Result<(), PdfError> {
1666 let n = self.get_numbers(4)?;
1667 let (x1, y1) = self.transform(n[0], n[1]);
1668 let (x3, y3) = self.transform(n[2], n[3]);
1669 self.current_path.segments.push(PathSegment::CurveTo {
1670 x1,
1671 y1,
1672 x2: x3,
1673 y2: y3,
1674 x3,
1675 y3,
1676 });
1677 self.current_point = Some((x3, y3));
1678 Ok(())
1679 }
1680
1681 fn op_h(&mut self) -> Result<(), PdfError> {
1682 self.current_path.segments.push(PathSegment::ClosePath);
1683 if let Some(start) = self.subpath_start {
1684 self.current_point = Some(start);
1685 }
1686 Ok(())
1687 }
1688
1689 fn op_re(&mut self) -> Result<(), PdfError> {
1690 let n = self.get_numbers(4)?;
1691 let (x, y, w, h) = (n[0], n[1], n[2], n[3]);
1692 let p0 = self.transform(x, y);
1694 let p1 = self.transform(x + w, y);
1695 let p2 = self.transform(x + w, y + h);
1696 let p3 = self.transform(x, y + h);
1697 self.current_path
1698 .segments
1699 .push(PathSegment::MoveTo(p0.0, p0.1));
1700 self.current_path
1701 .segments
1702 .push(PathSegment::LineTo(p1.0, p1.1));
1703 self.current_path
1704 .segments
1705 .push(PathSegment::LineTo(p2.0, p2.1));
1706 self.current_path
1707 .segments
1708 .push(PathSegment::LineTo(p3.0, p3.1));
1709 self.current_path.segments.push(PathSegment::ClosePath);
1710 self.current_point = Some(p0);
1711 self.subpath_start = Some(p0);
1712 Ok(())
1713 }
1714
1715 fn op_big_s(&mut self) -> Result<(), PdfError> {
1718 let path = self.take_path();
1720 if !path.is_empty() {
1721 self.emit_stroke(path);
1722 }
1723 self.apply_pending_clip();
1724 Ok(())
1725 }
1726
1727 fn op_small_s(&mut self) -> Result<(), PdfError> {
1728 self.op_h()?;
1730 self.op_big_s()
1731 }
1732
1733 fn op_f(&mut self) -> Result<(), PdfError> {
1734 let path = self.take_path();
1736 if !path.is_empty() {
1737 self.emit_fill(path, FillRule::NonZeroWinding);
1738 }
1739 self.apply_pending_clip();
1740 Ok(())
1741 }
1742
1743 fn op_f_star(&mut self) -> Result<(), PdfError> {
1744 let path = self.take_path();
1746 if !path.is_empty() {
1747 self.emit_fill(path, FillRule::EvenOdd);
1748 }
1749 self.apply_pending_clip();
1750 Ok(())
1751 }
1752
1753 fn op_big_b(&mut self) -> Result<(), PdfError> {
1754 let path = self.take_path();
1756 if !path.is_empty() {
1757 self.emit_fill_stroke(path, FillRule::NonZeroWinding);
1758 }
1759 self.apply_pending_clip();
1760 Ok(())
1761 }
1762
1763 fn op_big_b_star(&mut self) -> Result<(), PdfError> {
1764 let path = self.take_path();
1766 if !path.is_empty() {
1767 self.emit_fill_stroke(path, FillRule::EvenOdd);
1768 }
1769 self.apply_pending_clip();
1770 Ok(())
1771 }
1772
1773 fn op_small_b(&mut self) -> Result<(), PdfError> {
1774 self.op_h()?;
1776 self.op_big_b()
1777 }
1778
1779 fn op_small_b_star(&mut self) -> Result<(), PdfError> {
1780 self.op_h()?;
1782 self.op_big_b_star()
1783 }
1784
1785 fn emit_fill(&mut self, path: PsPath, fill_rule: FillRule) {
1787 if let Some(shading_box) = self.gstate.fill_shading_pattern.clone() {
1788 let bbox = path_device_bbox(&path);
1792 let mut group_dl = DisplayList::new();
1793 group_dl.push(DisplayElement::Clip {
1794 path,
1795 params: ClipParams {
1796 fill_rule,
1797 ctm: Matrix::identity(),
1798 stroke_params: None,
1799 },
1800 });
1801 for elem in shading_box.0.elements() {
1802 group_dl.push(elem.clone());
1803 }
1804 self.display_list.push(DisplayElement::Group {
1805 elements: group_dl,
1806 params: GroupParams {
1807 bbox,
1808 isolated: true,
1809 knockout: false,
1810 blend_mode: self.gstate.blend_mode,
1811 alpha: self.gstate.fill_alpha,
1812 color_space: stet_graphics::display_list::GroupColorSpace::Inherited,
1813 },
1814 });
1815 } else if let Some(pattern) = self.gstate.fill_pattern.clone() {
1816 self.display_list.push(DisplayElement::PatternFill {
1817 params: PatternFillParams {
1818 path,
1819 fill_rule,
1820 tile: pattern.tile,
1821 pattern_matrix: pattern.pattern_matrix,
1822 bbox: pattern.bbox,
1823 xstep: pattern.x_step,
1824 ystep: pattern.y_step,
1825 paint_type: pattern.paint_type,
1826 underlying_color: if pattern.paint_type == 2 {
1827 Some(self.gstate.fill_color.clone())
1828 } else {
1829 None
1830 },
1831 pattern_id: pattern.pattern_id,
1832 device_space_tile: false,
1833 flip_tile_y: false,
1834 stroke_params: None,
1835 overprint_mode: if self.gstate.overprint {
1836 self.gstate.overprint_mode
1837 } else {
1838 0
1839 },
1840 },
1841 });
1842 } else {
1843 self.display_list.push(DisplayElement::Fill {
1844 path,
1845 params: self.gstate.fill_params(fill_rule),
1846 });
1847 }
1848 }
1849
1850 fn emit_stroke(&mut self, path: PsPath) {
1858 let ctm = self.gstate.ctm;
1859 let user_path = if let Some(inv) = ctm.invert() {
1861 path.transform(&inv)
1862 } else {
1863 path.clone()
1864 };
1865
1866 if let Some(pattern) = self.gstate.stroke_pattern.clone() {
1869 let mut sp = self.gstate.stroke_params_with_ctm();
1870 sp.ctm = ctm;
1871 self.display_list.push(DisplayElement::PatternFill {
1872 params: PatternFillParams {
1873 path: user_path,
1874 fill_rule: FillRule::NonZeroWinding,
1875 tile: pattern.tile,
1876 pattern_matrix: pattern.pattern_matrix,
1877 bbox: pattern.bbox,
1878 xstep: pattern.x_step,
1879 ystep: pattern.y_step,
1880 paint_type: pattern.paint_type,
1881 underlying_color: if pattern.paint_type == 2 {
1882 Some(self.gstate.stroke_color.clone())
1883 } else {
1884 None
1885 },
1886 pattern_id: pattern.pattern_id,
1887 device_space_tile: false,
1888 flip_tile_y: false,
1889 stroke_params: Some(sp),
1890 overprint_mode: if self.gstate.overprint {
1891 self.gstate.overprint_mode
1892 } else {
1893 0
1894 },
1895 },
1896 });
1897 return;
1898 }
1899
1900 if let Some(shading_box) = self.gstate.stroke_shading_pattern.clone() {
1902 let mut sp = self.gstate.stroke_params_with_ctm();
1903 sp.ctm = ctm;
1904 let mut bbox = path_device_bbox(&path);
1907 let scale = self.gstate.ctm_scale_factor();
1908 let half_w = self.gstate.line_width * scale * 0.5;
1909 bbox[0] -= half_w;
1910 bbox[1] -= half_w;
1911 bbox[2] += half_w;
1912 bbox[3] += half_w;
1913 let mut group_dl = DisplayList::new();
1914 group_dl.push(DisplayElement::Clip {
1915 path: user_path,
1916 params: ClipParams {
1917 fill_rule: FillRule::NonZeroWinding,
1918 ctm: Matrix::identity(),
1919 stroke_params: Some(sp),
1920 },
1921 });
1922 for elem in shading_box.0.elements() {
1923 group_dl.push(elem.clone());
1924 }
1925 self.display_list.push(DisplayElement::Group {
1926 elements: group_dl,
1927 params: GroupParams {
1928 bbox,
1929 isolated: true,
1930 knockout: false,
1931 blend_mode: self.gstate.blend_mode,
1932 alpha: self.gstate.stroke_alpha,
1933 color_space: stet_graphics::display_list::GroupColorSpace::Inherited,
1934 },
1935 });
1936 return;
1937 }
1938
1939 let mut params = self.gstate.stroke_params_with_ctm();
1940 params.ctm = ctm;
1941 self.display_list.push(DisplayElement::Stroke {
1942 path: user_path,
1943 params,
1944 });
1945 }
1946
1947 fn emit_fill_stroke(&mut self, path: PsPath, fill_rule: FillRule) {
1955 let is_simple_fill =
1956 self.gstate.fill_shading_pattern.is_none() && self.gstate.fill_pattern.is_none();
1957 let is_simple_stroke =
1958 self.gstate.stroke_shading_pattern.is_none() && self.gstate.stroke_pattern.is_none();
1959
1960 let strict_opm1 = self.gstate.overprint_mode == 1 && self.gstate.opm_paired;
1973 let is_white_fill = self.gstate.fill_is_device_cmyk
1974 && self
1975 .gstate
1976 .fill_color
1977 .native_cmyk
1978 .map(|(c, m, y, k)| c == 0.0 && m == 0.0 && y == 0.0 && k == 0.0)
1979 .unwrap_or(false);
1980 let is_white_stroke = self.gstate.stroke_is_device_cmyk
1981 && self
1982 .gstate
1983 .stroke_color
1984 .native_cmyk
1985 .map(|(c, m, y, k)| c == 0.0 && m == 0.0 && y == 0.0 && k == 0.0)
1986 .unwrap_or(false);
1987 let has_any_overprint = self.gstate.overprint || self.gstate.overprint_stroke;
1988 let both_device_cmyk = self.gstate.fill_is_device_cmyk && self.gstate.stroke_is_device_cmyk;
1994 let fill_overprint_safe =
1995 !self.gstate.overprint || (is_white_fill && both_device_cmyk && !strict_opm1);
1996 let stroke_overprint_safe =
1997 !self.gstate.overprint_stroke || (is_white_stroke && both_device_cmyk && !strict_opm1);
1998 let mixed_space_with_overprint = has_any_overprint && !both_device_cmyk;
2003
2004 if is_simple_fill
2005 && is_simple_stroke
2006 && self.gstate.blend_mode == 0
2007 && fill_overprint_safe
2008 && stroke_overprint_safe
2009 && !mixed_space_with_overprint
2010 {
2011 let ctm = self.gstate.ctm;
2012
2013 let mut bbox = path_device_bbox(&path);
2014 let scale = self.gstate.ctm_scale_factor();
2015 let half_w = self.gstate.line_width * scale * 0.5;
2016 bbox[0] -= half_w;
2017 bbox[1] -= half_w;
2018 bbox[2] += half_w;
2019 bbox[3] += half_w;
2020
2021 let fill_elem = DisplayElement::Fill {
2022 path: path.clone(),
2023 params: self.gstate.fill_params(fill_rule),
2024 };
2025
2026 let user_path = if let Some(inv) = ctm.invert() {
2027 path.transform(&inv)
2028 } else {
2029 path.clone()
2030 };
2031 let mut stroke_params = self.gstate.stroke_params_with_ctm();
2032 stroke_params.ctm = ctm;
2033 let stroke_elem = DisplayElement::Stroke {
2034 path: user_path,
2035 params: stroke_params,
2036 };
2037
2038 let mut group_dl = DisplayList::new();
2039 group_dl.push(fill_elem);
2040 group_dl.push(stroke_elem);
2041
2042 self.display_list.push(DisplayElement::Group {
2043 elements: group_dl,
2044 params: stet_graphics::display_list::GroupParams {
2045 bbox,
2046 isolated: true,
2047 knockout: false,
2048 blend_mode: 0,
2049 alpha: 1.0,
2050 color_space: stet_graphics::display_list::GroupColorSpace::Inherited,
2051 },
2052 });
2053 } else {
2054 self.emit_fill(path.clone(), fill_rule);
2055 self.emit_stroke(path);
2056 }
2057 }
2058
2059 fn op_n(&mut self) -> Result<(), PdfError> {
2060 let _path = self.take_path();
2062 self.apply_pending_clip();
2063 Ok(())
2064 }
2065
2066 fn op_big_w(&mut self) -> Result<(), PdfError> {
2069 self.gstate.pending_clip = Some((self.current_path.clone(), FillRule::NonZeroWinding));
2071 Ok(())
2072 }
2073
2074 fn op_big_w_star(&mut self) -> Result<(), PdfError> {
2075 self.gstate.pending_clip = Some((self.current_path.clone(), FillRule::EvenOdd));
2077 Ok(())
2078 }
2079
2080 fn op_big_g(&mut self) -> Result<(), PdfError> {
2083 let g = self.pop_number()?;
2085 self.gstate.stroke_color = DeviceColor::from_gray(g);
2086 self.gstate.stroke_color_space = ColorSpaceRef::DeviceGray;
2087 self.gstate.stroke_painted_channels = 0;
2088 self.gstate.stroke_is_device_cmyk = false;
2089 self.gstate.stroke_is_none = false;
2090 self.gstate.stroke_pattern = None;
2091 self.gstate.stroke_shading_pattern = None;
2092 Ok(())
2093 }
2094
2095 fn op_small_g(&mut self) -> Result<(), PdfError> {
2096 let g = self.pop_number()?;
2098 self.gstate.fill_color = DeviceColor::from_gray(g);
2099 self.gstate.fill_color_space = ColorSpaceRef::DeviceGray;
2100 self.gstate.fill_painted_channels = 0;
2101 self.gstate.fill_is_device_cmyk = false;
2102 self.gstate.fill_is_none = false;
2103 self.gstate.fill_pattern = None;
2104 self.gstate.fill_shading_pattern = None;
2105 Ok(())
2106 }
2107
2108 fn op_big_rg(&mut self) -> Result<(), PdfError> {
2109 let n = self.get_numbers(3)?;
2111 let (r, g, b) = self.cmyk_group_rgb(n[0], n[1], n[2]);
2112 self.gstate.stroke_color = DeviceColor::from_rgb(r, g, b);
2113 self.gstate.stroke_color_space = ColorSpaceRef::DeviceRGB;
2114 self.gstate.stroke_painted_channels = 0;
2115 self.gstate.stroke_is_device_cmyk = false;
2116 self.gstate.stroke_is_none = false;
2117 self.gstate.stroke_pattern = None;
2118 self.gstate.stroke_shading_pattern = None;
2119 Ok(())
2120 }
2121
2122 fn op_small_rg(&mut self) -> Result<(), PdfError> {
2123 let n = self.get_numbers(3)?;
2125 let (r, g, b) = self.cmyk_group_rgb(n[0], n[1], n[2]);
2126 self.gstate.fill_color = DeviceColor::from_rgb(r, g, b);
2127 self.gstate.fill_color_space = ColorSpaceRef::DeviceRGB;
2128 self.gstate.fill_painted_channels = 0;
2129 self.gstate.fill_is_device_cmyk = false;
2130 self.gstate.fill_is_none = false;
2131 self.gstate.fill_pattern = None;
2132 self.gstate.fill_shading_pattern = None;
2133 Ok(())
2134 }
2135
2136 fn cmyk_group_rgb(&mut self, r: f64, g: f64, b: f64) -> (f64, f64, f64) {
2139 if self.page_group_is_cmyk {
2140 if let Some(result) = self.icc_cache.round_trip_rgb_via_cmyk(r, g, b) {
2141 return result;
2142 }
2143 }
2144 (r, g, b)
2145 }
2146
2147 fn op_big_k(&mut self) -> Result<(), PdfError> {
2148 let n = self.get_numbers(4)?;
2150 self.gstate.stroke_color =
2151 DeviceColor::from_cmyk_icc(n[0], n[1], n[2], n[3], &mut self.icc_cache);
2152 self.gstate.stroke_color_space = ColorSpaceRef::DeviceCMYK;
2153 self.gstate.stroke_painted_channels = stet_graphics::device::CMYK_ALL;
2154 self.gstate.stroke_is_device_cmyk = true;
2155 self.gstate.stroke_is_none = false;
2156 self.gstate.stroke_pattern = None;
2157 self.gstate.stroke_shading_pattern = None;
2158 Ok(())
2159 }
2160
2161 fn op_small_k(&mut self) -> Result<(), PdfError> {
2162 let n = self.get_numbers(4)?;
2164 self.gstate.fill_color =
2165 DeviceColor::from_cmyk_icc(n[0], n[1], n[2], n[3], &mut self.icc_cache);
2166 self.gstate.fill_color_space = ColorSpaceRef::DeviceCMYK;
2167 self.gstate.fill_painted_channels = stet_graphics::device::CMYK_ALL;
2168 self.gstate.fill_is_device_cmyk = true;
2169 self.gstate.fill_is_none = false;
2170 self.gstate.fill_pattern = None;
2171 self.gstate.fill_shading_pattern = None;
2172 Ok(())
2173 }
2174
2175 fn op_big_cs(&mut self) -> Result<(), PdfError> {
2178 let name = self
2180 .operand_stack
2181 .last()
2182 .and_then(|o| o.as_name())
2183 .ok_or(PdfError::Other("CS: expected name".into()))?
2184 .to_vec();
2185 self.gstate.stroke_color_space = name_to_cs_ref(&name);
2186 Ok(())
2187 }
2188
2189 fn op_small_cs(&mut self) -> Result<(), PdfError> {
2190 let name = self
2192 .operand_stack
2193 .last()
2194 .and_then(|o| o.as_name())
2195 .ok_or(PdfError::Other("cs: expected name".into()))?
2196 .to_vec();
2197 self.gstate.fill_color_space = name_to_cs_ref(&name);
2198 Ok(())
2199 }
2200
2201 fn resolve_cs_cached(
2205 &mut self,
2206 cs_ref: &ColorSpaceRef,
2207 ) -> Result<ResolvedColorSpace, PdfError> {
2208 if let ColorSpaceRef::Named(name) = cs_ref {
2209 match name.as_slice() {
2211 b"DeviceGray" | b"G" => return Ok(ResolvedColorSpace::DeviceGray),
2212 b"DeviceRGB" | b"RGB" => return Ok(ResolvedColorSpace::DeviceRGB),
2213 b"DeviceCMYK" | b"CMYK" => return Ok(ResolvedColorSpace::DeviceCMYK),
2214 b"Pattern" => return Ok(ResolvedColorSpace::Pattern),
2215 _ => {}
2216 }
2217 if self.cs_index.is_none() {
2219 let mut index = std::collections::HashMap::new();
2220 if let Some(cs_dict) = self.resolve_resource_subdict(b"ColorSpace") {
2221 for (k, v) in cs_dict.entries() {
2222 index.insert(k.clone(), v.clone());
2223 }
2224 }
2225 self.cs_index = Some(index);
2226 }
2227 if let Some(cs_obj) = self.cs_index.as_ref().unwrap().get(name.as_slice()) {
2228 let cs_obj = cs_obj.clone();
2229 resolve_color_space_obj(&cs_obj, self.resolver)
2230 } else {
2231 resolve_color_space(
2235 &ColorSpaceRef::Named(name.to_vec()),
2236 &self.resources,
2237 self.resolver,
2238 )
2239 }
2240 } else {
2241 resolve_color_space(cs_ref, &self.resources, self.resolver)
2242 }
2243 }
2244
2245 fn op_sc_stroke(&mut self) -> Result<(), PdfError> {
2246 if matches!(self.operand_stack.last(), Some(Operand::Name(_))) {
2251 return self.handle_pattern_stroke();
2252 }
2253 let cs = self.resolve_cs_cached(&self.gstate.stroke_color_space.clone())?;
2254 if matches!(cs, ResolvedColorSpace::Pattern) {
2255 return self.handle_pattern_stroke();
2256 }
2257 let n = cs.num_components();
2258 if n == 0 {
2259 return Ok(());
2260 }
2261 let nums = self.get_numbers(n)?;
2262 self.gstate.stroke_painted_channels = painted_channels_for_cs(&cs);
2263 self.gstate.stroke_is_none = cs.is_none_colorant();
2264 self.gstate.stroke_is_device_cmyk = matches!(
2265 cs,
2266 ResolvedColorSpace::DeviceCMYK | ResolvedColorSpace::ICCBased { n: 4, .. }
2267 );
2268 self.gstate.stroke_color =
2269 components_to_device_color_icc(&cs, &nums, Some(&mut self.icc_cache));
2270 self.gstate.stroke_pattern = None;
2271 self.gstate.stroke_shading_pattern = None;
2272 Ok(())
2273 }
2274
2275 fn op_sc_fill(&mut self) -> Result<(), PdfError> {
2276 if matches!(self.operand_stack.last(), Some(Operand::Name(_))) {
2281 return self.handle_pattern_fill();
2282 }
2283 let cs = self.resolve_cs_cached(&self.gstate.fill_color_space.clone())?;
2284 if matches!(cs, ResolvedColorSpace::Pattern) {
2285 return self.handle_pattern_fill();
2286 }
2287 let n = cs.num_components();
2288 if n == 0 {
2289 return Ok(());
2290 }
2291 let nums = self.get_numbers(n)?;
2292 self.gstate.fill_painted_channels = painted_channels_for_cs(&cs);
2293 self.gstate.fill_is_none = cs.is_none_colorant();
2294 self.gstate.fill_is_device_cmyk = matches!(
2295 cs,
2296 ResolvedColorSpace::DeviceCMYK | ResolvedColorSpace::ICCBased { n: 4, .. }
2297 );
2298 self.gstate.fill_color =
2299 components_to_device_color_icc(&cs, &nums, Some(&mut self.icc_cache));
2300 self.gstate.fill_pattern = None;
2301 self.gstate.fill_shading_pattern = None;
2302 Ok(())
2303 }
2304
2305 fn op_tf(&mut self) -> Result<(), PdfError> {
2308 let len = self.operand_stack.len();
2310 if len < 2 {
2311 return Ok(());
2312 }
2313 self.gstate.font_size = self.operand_stack[len - 1].as_f64().unwrap_or(12.0);
2314 if let Some(name) = self.operand_stack[len - 2].as_name() {
2315 let name = name.to_vec();
2316 self.gstate.text_font_name = name.clone();
2317 self.resolve_current_font(&name);
2318 }
2319 Ok(())
2320 }
2321
2322 fn resolve_current_font(&mut self, name: &[u8]) {
2324 if let Some(cached) = self.font_cache.get(name) {
2327 let font_ref = self
2331 .resolve_resource_subdict(b"Font")
2332 .and_then(|fd| fd.get(name).cloned());
2333 if let Some(PdfObj::Ref(obj_num, _)) = &font_ref {
2334 let obj_key = obj_num.to_le_bytes().to_vec();
2335 if let Some(obj_cached) = self.font_cache.get(&obj_key) {
2336 self.current_font = Some(Arc::clone(obj_cached));
2339 return;
2340 }
2341 } else {
2345 self.current_font = Some(Arc::clone(cached));
2347 return;
2348 }
2349 }
2350
2351 let font_ref = self
2353 .resolve_resource_subdict(b"Font")
2354 .and_then(|fd| fd.get(name).cloned());
2355 let font_ref = match font_ref {
2356 Some(r) => r,
2357 None => {
2358 if let Some(fallback) = font::fallback_font(self.font_provider.as_ref()) {
2360 let arc = Arc::new(fallback);
2361 self.font_cache.insert(name.to_vec(), Arc::clone(&arc));
2362 self.current_font = Some(arc);
2363 } else {
2364 self.current_font = None;
2365 }
2366 return;
2367 }
2368 };
2369
2370 if let PdfObj::Ref(obj_num, _) = &font_ref {
2372 let obj_key = obj_num.to_le_bytes().to_vec();
2373 if let Some(cached) = self.font_cache.get(&obj_key) {
2374 let arc = Arc::clone(cached);
2375 self.font_cache.insert(name.to_vec(), Arc::clone(&arc));
2376 self.current_font = Some(arc);
2377 return;
2378 }
2379 }
2380
2381 match font::resolve_font(self.resolver, &font_ref, self.font_provider.as_ref()) {
2382 Ok(font) => {
2383 let arc = Arc::new(font);
2384 if let PdfObj::Ref(obj_num, _) = &font_ref {
2386 self.font_cache
2387 .insert(obj_num.to_le_bytes().to_vec(), Arc::clone(&arc));
2388 }
2389 self.font_cache.insert(name.to_vec(), Arc::clone(&arc));
2390 self.current_font = Some(arc);
2391 }
2392 Err(e) => {
2393 use std::sync::Mutex;
2395 static WARNED: Mutex<Vec<String>> = Mutex::new(Vec::new());
2396 let msg = format!("font /{}: {}", String::from_utf8_lossy(name), e);
2397 if let Ok(mut set) = WARNED.lock()
2398 && !set.contains(&msg)
2399 {
2400 eprintln!("warning: {msg}");
2401 set.push(msg);
2402 }
2403 if let Some(fallback) = font::fallback_font(self.font_provider.as_ref()) {
2405 let arc = Arc::new(fallback);
2406 self.font_cache.insert(name.to_vec(), Arc::clone(&arc));
2407 self.current_font = Some(arc);
2408 } else {
2409 self.current_font = None;
2410 }
2411 }
2412 }
2413 }
2414
2415 fn check_text_cull(&mut self) {
2418 if let Some((y_lo, y_hi)) = self.form_cull_y {
2419 let text_y = self.gstate.text_matrix.ty;
2421 if text_y < y_lo || text_y > y_hi {
2422 self.bt_culled = true;
2423 }
2424 }
2425 }
2426
2427 fn op_td(&mut self) -> Result<(), PdfError> {
2428 let n = self.get_numbers(2)?;
2429 let m = Matrix::translate(n[0], n[1]);
2430 self.gstate.text_line_matrix = self.gstate.text_line_matrix.concat(&m);
2431 self.gstate.text_matrix = self.gstate.text_line_matrix;
2432 self.check_text_cull();
2433 Ok(())
2434 }
2435
2436 fn op_big_td(&mut self) -> Result<(), PdfError> {
2437 let n = self.get_numbers(2)?;
2438 self.gstate.text_leading = -n[1];
2439 let m = Matrix::translate(n[0], n[1]);
2440 self.gstate.text_line_matrix = self.gstate.text_line_matrix.concat(&m);
2441 self.gstate.text_matrix = self.gstate.text_line_matrix;
2442 self.check_text_cull();
2443 Ok(())
2444 }
2445
2446 fn op_tm(&mut self) -> Result<(), PdfError> {
2447 let n = self.get_numbers(6)?;
2448 let m = Matrix::new(n[0], n[1], n[2], n[3], n[4], n[5]);
2449 self.gstate.text_matrix = m;
2450 self.gstate.text_line_matrix = m;
2451 self.check_text_cull();
2452 Ok(())
2453 }
2454
2455 fn op_t_star(&mut self) -> Result<(), PdfError> {
2456 let leading = self.gstate.text_leading;
2457 let m = Matrix::translate(0.0, -leading);
2458 self.gstate.text_line_matrix = self.gstate.text_line_matrix.concat(&m);
2459 self.gstate.text_matrix = self.gstate.text_line_matrix;
2460 Ok(())
2461 }
2462
2463 fn op_tj(&mut self) -> Result<(), PdfError> {
2466 let text = match self.operand_stack.last() {
2467 Some(Operand::Str(s)) => s.clone(),
2468 _ => return Ok(()),
2469 };
2470 self.show_text(&text);
2471 Ok(())
2472 }
2473
2474 fn op_big_tj(&mut self) -> Result<(), PdfError> {
2475 let arr = match self.operand_stack.last() {
2476 Some(Operand::Array(a)) => a.clone(),
2477 _ => return Ok(()),
2478 };
2479 let vertical = self.current_font.as_ref().is_some_and(|f| f.wmode() == 1);
2480 for elem in &arr {
2481 match elem {
2482 PdfObj::Str(s) => self.show_text(s),
2483 PdfObj::Int(n) => {
2484 let shift = -*n as f64 / 1000.0 * self.gstate.font_size;
2485 let m = if vertical {
2486 Matrix::translate(0.0, shift)
2487 } else {
2488 Matrix::translate(shift * self.gstate.horizontal_scaling, 0.0)
2489 };
2490 self.gstate.text_matrix = self.gstate.text_matrix.concat(&m);
2491 }
2492 PdfObj::Real(f) => {
2493 let shift = -f / 1000.0 * self.gstate.font_size;
2494 let m = if vertical {
2495 Matrix::translate(0.0, shift)
2496 } else {
2497 Matrix::translate(shift * self.gstate.horizontal_scaling, 0.0)
2498 };
2499 self.gstate.text_matrix = self.gstate.text_matrix.concat(&m);
2500 }
2501 _ => {}
2502 }
2503 }
2504 Ok(())
2505 }
2506
2507 fn op_quote(&mut self) -> Result<(), PdfError> {
2508 self.op_t_star()?;
2510 self.op_tj()
2511 }
2512
2513 fn op_dblquote(&mut self) -> Result<(), PdfError> {
2514 let len = self.operand_stack.len();
2516 if len < 3 {
2517 return Ok(());
2518 }
2519 self.gstate.word_spacing = self.operand_stack[len - 3].as_f64().unwrap_or(0.0);
2520 self.gstate.char_spacing = self.operand_stack[len - 2].as_f64().unwrap_or(0.0);
2521 self.op_t_star()?;
2523 self.op_tj()
2524 }
2525
2526 fn show_text(&mut self, text: &[u8]) {
2528 let font = match &self.current_font {
2529 Some(f) => Arc::clone(f),
2530 None => return,
2531 };
2532
2533 let font_size = self.gstate.font_size;
2534 let char_spacing = self.gstate.char_spacing;
2535 let word_spacing = self.gstate.word_spacing;
2536 let text_rise = self.gstate.text_rise;
2537 let th = self.gstate.horizontal_scaling;
2538 let font_matrix = font.font_matrix();
2539 let render_mode = self.gstate.text_rendering_mode;
2540
2541 if font.is_composite() {
2542 let mut i = 0;
2545 while i < text.len() {
2546 let code_width = font.code_width(text[i]);
2547 if code_width == 1 {
2548 let raw_code = text[i] as u32;
2552 let extra = if raw_code == 0x20 { word_spacing } else { 0.0 };
2553 i += 1;
2554 let cid = font.resolve_code_to_cid(raw_code) as u16;
2555 self.render_cid_glyph(
2556 &font,
2557 cid,
2558 font_size,
2559 char_spacing,
2560 th,
2561 text_rise,
2562 &font_matrix,
2563 render_mode,
2564 extra,
2565 );
2566 } else if i + 1 >= text.len() {
2567 let byte = text[i];
2569 i += 1;
2570 self.render_unicode_glyph(
2571 byte,
2572 font_size,
2573 char_spacing,
2574 th,
2575 text_rise,
2576 &font_matrix,
2577 render_mode,
2578 );
2579 } else {
2580 let width = code_width.min(text.len() - i);
2582 let mut raw_code = 0u32;
2583 for b in &text[i..i + width] {
2584 raw_code = (raw_code << 8) | (*b as u32);
2585 }
2586 let cid = font.resolve_code_to_cid(raw_code) as u16;
2587 let (cid, consumed) = if cid == 0 || (cid == raw_code as u16 && width > 2) {
2592 let byte_cid = font.resolve_code_to_cid(text[i] as u32) as u16;
2593 if byte_cid != 0 && byte_cid != text[i] as u16 {
2594 (byte_cid, 1)
2595 } else {
2596 (cid, width)
2597 }
2598 } else {
2599 (cid, width)
2600 };
2601 let extra = if consumed == 1 && text[i] == 0x20 {
2603 word_spacing
2604 } else {
2605 0.0
2606 };
2607 i += consumed;
2608 if font.has_cid_glyph(cid) {
2609 self.render_cid_glyph(
2611 &font,
2612 cid,
2613 font_size,
2614 char_spacing,
2615 th,
2616 text_rise,
2617 &font_matrix,
2618 render_mode,
2619 extra,
2620 );
2621 } else {
2622 let lo_cid = (raw_code & 0xFF) as u16;
2626 if lo_cid > 0 && font.has_cid_glyph(lo_cid) {
2627 self.render_cid_glyph(
2628 &font,
2629 lo_cid,
2630 font_size,
2631 char_spacing,
2632 th,
2633 text_rise,
2634 &font_matrix,
2635 render_mode,
2636 extra,
2637 );
2638 } else if raw_code <= 0xFF {
2639 self.render_unicode_glyph(
2643 text[i - 2],
2644 font_size,
2645 char_spacing,
2646 th,
2647 text_rise,
2648 &font_matrix,
2649 render_mode,
2650 );
2651 self.render_unicode_glyph(
2652 text[i - 1],
2653 font_size,
2654 char_spacing,
2655 th,
2656 text_rise,
2657 &font_matrix,
2658 render_mode,
2659 );
2660 } else {
2661 self.render_cid_glyph_unicode_fallback(
2664 &font,
2665 cid,
2666 raw_code,
2667 font_size,
2668 char_spacing,
2669 th,
2670 text_rise,
2671 &font_matrix,
2672 render_mode,
2673 extra,
2674 );
2675 }
2676 }
2677 }
2678 }
2679 } else if font.is_type3() {
2680 let fm = font.font_matrix();
2683 let visible = (render_mode & 3) != 3; for &byte in text {
2685 if visible {
2686 self.show_type3_glyph(&font, byte);
2687 }
2688
2689 let w0_glyph = font.glyph_width(byte);
2690 let w0 = w0_glyph * fm.a;
2691 let mut tx = w0 * font_size + char_spacing;
2692 if byte == b' ' {
2693 tx += word_spacing;
2694 }
2695 tx *= th;
2696 let advance = Matrix::translate(tx, 0.0);
2697 self.gstate.text_matrix = self.gstate.text_matrix.concat(&advance);
2698 }
2699 } else {
2700 for &byte in text {
2702 if let Some(glyph_path) = font.glyph_path(byte) {
2703 let text_state_matrix =
2704 Matrix::new(font_size * th, 0.0, 0.0, font_size, 0.0, text_rise);
2705 let trm = self
2706 .gstate
2707 .ctm
2708 .concat(&self.gstate.text_matrix)
2709 .concat(&text_state_matrix)
2710 .concat(&font_matrix);
2711
2712 let device_path = glyph_path.transform(&trm);
2713 if !device_path.is_empty() {
2714 self.emit_text_glyph(device_path, render_mode);
2715 }
2716 }
2717
2718 let w0 = font.glyph_width(byte);
2719 let mut tx = w0 * font_size + char_spacing;
2720 if byte == b' ' {
2721 tx += word_spacing;
2722 }
2723 tx *= th;
2724 let advance = Matrix::translate(tx, 0.0);
2725 self.gstate.text_matrix = self.gstate.text_matrix.concat(&advance);
2726 }
2727 }
2728 }
2729
2730 fn render_cid_glyph(
2732 &mut self,
2733 font: &PdfFont,
2734 cid: u16,
2735 font_size: f64,
2736 char_spacing: f64,
2737 th: f64,
2738 text_rise: f64,
2739 font_matrix: &Matrix,
2740 render_mode: i32,
2741 extra_advance: f64,
2742 ) {
2743 let vertical = font.wmode() == 1;
2744 if let Some(glyph_path) = font.glyph_path_cid(cid) {
2745 let text_state_matrix = if vertical {
2746 let [_w1, v_x, v_y] = font.vertical_metrics_cid(cid);
2749 Matrix::new(
2750 font_size,
2751 0.0,
2752 0.0,
2753 font_size,
2754 -v_x / 1000.0 * font_size,
2755 -v_y / 1000.0 * font_size,
2756 )
2757 } else {
2758 Matrix::new(font_size * th, 0.0, 0.0, font_size, 0.0, text_rise)
2759 };
2760 let trm = self
2761 .gstate
2762 .ctm
2763 .concat(&self.gstate.text_matrix)
2764 .concat(&text_state_matrix)
2765 .concat(font_matrix);
2766 let device_path = glyph_path.transform(&trm);
2767 if !device_path.is_empty() {
2768 self.emit_text_glyph(device_path, render_mode);
2769 }
2770 }
2771 if vertical {
2772 let [w1, _vx, _vy] = font.vertical_metrics_cid(cid);
2773 let ty = w1 / 1000.0 * font_size + char_spacing + extra_advance;
2774 let advance = Matrix::translate(0.0, ty);
2775 self.gstate.text_matrix = self.gstate.text_matrix.concat(&advance);
2776 } else {
2777 let w0 = font.glyph_width_cid(cid);
2778 let tx = (w0 * font_size + char_spacing + extra_advance) * th;
2779 let advance = Matrix::translate(tx, 0.0);
2780 self.gstate.text_matrix = self.gstate.text_matrix.concat(&advance);
2781 }
2782 }
2783
2784 fn render_cid_glyph_unicode_fallback(
2788 &mut self,
2789 font: &PdfFont,
2790 cid: u16,
2791 unicode: u32,
2792 font_size: f64,
2793 char_spacing: f64,
2794 th: f64,
2795 text_rise: f64,
2796 font_matrix: &Matrix,
2797 render_mode: i32,
2798 extra_advance: f64,
2799 ) {
2800 let vertical = font.wmode() == 1;
2801 if let Some(glyph_path) = font.glyph_path_unicode(unicode as u16) {
2803 let text_state_matrix = if vertical {
2804 let [_w1, v_x, v_y] = font.vertical_metrics_cid(cid);
2805 Matrix::new(
2806 font_size,
2807 0.0,
2808 0.0,
2809 font_size,
2810 -v_x / 1000.0 * font_size,
2811 -v_y / 1000.0 * font_size,
2812 )
2813 } else {
2814 Matrix::new(font_size * th, 0.0, 0.0, font_size, 0.0, text_rise)
2815 };
2816 let trm = self
2817 .gstate
2818 .ctm
2819 .concat(&self.gstate.text_matrix)
2820 .concat(&text_state_matrix)
2821 .concat(font_matrix);
2822 let device_path = glyph_path.transform(&trm);
2823 if !device_path.is_empty() {
2824 self.emit_text_glyph(device_path, render_mode);
2825 }
2826 }
2827 if vertical {
2828 let [w1, _vx, _vy] = font.vertical_metrics_cid(cid);
2829 let ty = w1 / 1000.0 * font_size + char_spacing + extra_advance;
2830 let advance = Matrix::translate(0.0, ty);
2831 self.gstate.text_matrix = self.gstate.text_matrix.concat(&advance);
2832 } else {
2833 let w0 = font.glyph_width_cid(cid);
2834 let tx = (w0 * font_size + char_spacing + extra_advance) * th;
2835 let advance = Matrix::translate(tx, 0.0);
2836 self.gstate.text_matrix = self.gstate.text_matrix.concat(&advance);
2837 }
2838 }
2839
2840 fn render_unicode_glyph(
2843 &mut self,
2844 byte: u8,
2845 font_size: f64,
2846 char_spacing: f64,
2847 th: f64,
2848 text_rise: f64,
2849 font_matrix: &Matrix,
2850 render_mode: i32,
2851 ) {
2852 let unicode = font::winansi_byte_to_unicode(byte);
2853 if let Some(glyph_path) = self
2854 .current_font
2855 .as_ref()
2856 .and_then(|f| f.glyph_path_unicode(unicode))
2857 {
2858 let text_state_matrix =
2859 Matrix::new(font_size * th, 0.0, 0.0, font_size, 0.0, text_rise);
2860 let trm = self
2861 .gstate
2862 .ctm
2863 .concat(&self.gstate.text_matrix)
2864 .concat(&text_state_matrix)
2865 .concat(font_matrix);
2866 let device_path = glyph_path.transform(&trm);
2867 if !device_path.is_empty() {
2868 self.emit_text_glyph(device_path, render_mode);
2869 }
2870 }
2871 let w0 = self
2872 .current_font
2873 .as_ref()
2874 .map(|f| f.glyph_width_unicode(unicode))
2875 .unwrap_or(0.0);
2876 let tx = (w0 * font_size + char_spacing) * th;
2877 let advance = Matrix::translate(tx, 0.0);
2878 self.gstate.text_matrix = self.gstate.text_matrix.concat(&advance);
2879 }
2880
2881 fn show_type3_glyph(&mut self, font: &PdfFont, char_code: u8) {
2883 let proc_data = match font.type3_char_proc(char_code) {
2884 Some(data) => data.to_vec(),
2885 None => return,
2886 };
2887 let resources = match font.type3_resources() {
2888 Some(r) => r.clone(),
2889 None => return,
2890 };
2891
2892 let font_size = self.gstate.font_size;
2893 let text_rise = self.gstate.text_rise;
2894 let font_matrix = font.font_matrix();
2895
2896 let th = self.gstate.horizontal_scaling;
2898 let text_state_matrix = Matrix::new(font_size * th, 0.0, 0.0, font_size, 0.0, text_rise);
2899 let trm = self
2900 .gstate
2901 .ctm
2902 .concat(&self.gstate.text_matrix)
2903 .concat(&text_state_matrix)
2904 .concat(&font_matrix);
2905 let stack_depth_before = self.gstate_stack.len();
2908 self.gstate_stack.push(self.gstate.clone());
2909 let mut merged = self.resources.clone();
2914 for (key, value) in resources.entries() {
2915 merged.insert(key.clone(), value.clone());
2916 }
2917 let saved_resources = std::mem::replace(&mut self.resources, merged);
2918 let saved_display_list = std::mem::take(&mut self.display_list);
2919 let saved_path = std::mem::take(&mut self.current_path);
2920 let saved_point = self.current_point.take();
2921 let saved_subpath = self.subpath_start.take();
2922 let saved_font = self.current_font.clone();
2923 let saved_in_text = self.in_text;
2924 let saved_content_stream_ctm = self.content_stream_ctm;
2925 let saved_mc_stack = std::mem::take(&mut self.mc_stack);
2926
2927 self.gstate.ctm = trm;
2928 self.content_stream_ctm = trm;
2931
2932 let saved_d1 = self.d1_color_suppressed;
2933 self.d1_color_suppressed = false;
2934 self.depth += 1;
2935 let _ = self.interpret_stream(&proc_data);
2936 self.depth -= 1;
2937 self.d1_color_suppressed = saved_d1;
2938 let glyph_elements = std::mem::replace(&mut self.display_list, saved_display_list);
2940 self.resources = saved_resources;
2941 self.current_path = saved_path;
2942 self.current_point = saved_point;
2943 self.subpath_start = saved_subpath;
2944 self.current_font = saved_font;
2945 self.in_text = saved_in_text;
2946 self.content_stream_ctm = saved_content_stream_ctm;
2947 self.mc_stack = saved_mc_stack;
2948 self.gstate_stack.truncate(stack_depth_before + 1);
2951 if let Some(saved) = self.gstate_stack.pop() {
2952 self.gstate = saved;
2953 }
2954
2955 for elem in glyph_elements.into_elements() {
2957 self.display_list.push(elem);
2958 }
2959 }
2960
2961 fn emit_text_glyph(&mut self, device_path: PsPath, render_mode: i32) {
2966 let mode = render_mode & 3; let clip = render_mode & 4 != 0; match mode {
2970 0 => {
2971 self.emit_text_fill(device_path.clone());
2973 }
2974 1 => {
2975 self.emit_text_stroke(device_path.clone());
2977 }
2978 2 => {
2979 self.emit_text_fill(device_path.clone());
2981 self.emit_text_stroke(device_path.clone());
2982 }
2983 _ => {} }
2985
2986 if clip {
2988 let tcp = self.text_clip_path.get_or_insert_with(PsPath::new);
2989 tcp.segments.extend_from_slice(&device_path.segments);
2990 }
2991 }
2992
2993 fn emit_text_fill(&mut self, path: PsPath) {
2996 if let Some(shading_box) = self.gstate.fill_shading_pattern.clone() {
2997 let bbox = path_device_bbox(&path);
3000 let mut group_dl = DisplayList::new();
3001 group_dl.push(DisplayElement::Clip {
3002 path,
3003 params: ClipParams {
3004 fill_rule: FillRule::NonZeroWinding,
3005 ctm: Matrix::identity(),
3006 stroke_params: None,
3007 },
3008 });
3009 for elem in shading_box.0.elements() {
3010 group_dl.push(elem.clone());
3011 }
3012 self.display_list.push(DisplayElement::Group {
3013 elements: group_dl,
3014 params: GroupParams {
3015 bbox,
3016 isolated: true,
3017 knockout: false,
3018 blend_mode: self.gstate.blend_mode,
3019 alpha: self.gstate.fill_alpha,
3020 color_space: stet_graphics::display_list::GroupColorSpace::Inherited,
3021 },
3022 });
3023 } else if let Some(pattern) = self.gstate.fill_pattern.clone() {
3024 self.display_list.push(DisplayElement::PatternFill {
3025 params: PatternFillParams {
3026 path,
3027 fill_rule: FillRule::NonZeroWinding,
3028 tile: pattern.tile,
3029 pattern_matrix: pattern.pattern_matrix,
3030 bbox: pattern.bbox,
3031 xstep: pattern.x_step,
3032 ystep: pattern.y_step,
3033 paint_type: pattern.paint_type,
3034 underlying_color: if pattern.paint_type == 2 {
3035 Some(self.gstate.fill_color.clone())
3036 } else {
3037 None
3038 },
3039 pattern_id: pattern.pattern_id,
3040 device_space_tile: false,
3041 flip_tile_y: false,
3042 stroke_params: None,
3043 overprint_mode: if self.gstate.overprint {
3044 self.gstate.overprint_mode
3045 } else {
3046 0
3047 },
3048 },
3049 });
3050 } else {
3051 let mut params = self.gstate.fill_params(FillRule::NonZeroWinding);
3052 params.is_text_glyph = true;
3053 self.display_list
3054 .push(DisplayElement::Fill { path, params });
3055 }
3056 }
3057
3058 fn emit_text_stroke(&mut self, path: PsPath) {
3061 if let Some(shading_box) = self.gstate.stroke_shading_pattern.clone() {
3063 let mut sp = self.gstate.stroke_params();
3064 sp.is_text_glyph = true;
3065 let mut bbox = path_device_bbox(&path);
3067 let half_w = sp.line_width * 0.5;
3068 bbox[0] -= half_w;
3069 bbox[1] -= half_w;
3070 bbox[2] += half_w;
3071 bbox[3] += half_w;
3072 let mut group_dl = DisplayList::new();
3073 group_dl.push(DisplayElement::Clip {
3074 path,
3075 params: ClipParams {
3076 fill_rule: FillRule::NonZeroWinding,
3077 ctm: Matrix::identity(),
3078 stroke_params: Some(sp),
3079 },
3080 });
3081 for elem in shading_box.0.elements() {
3082 group_dl.push(elem.clone());
3083 }
3084 self.display_list.push(DisplayElement::Group {
3085 elements: group_dl,
3086 params: GroupParams {
3087 bbox,
3088 isolated: true,
3089 knockout: false,
3090 blend_mode: self.gstate.blend_mode,
3091 alpha: self.gstate.stroke_alpha,
3092 color_space: stet_graphics::display_list::GroupColorSpace::Inherited,
3093 },
3094 });
3095 return;
3096 }
3097
3098 if let Some(pattern) = self.gstate.stroke_pattern.clone() {
3101 let mut sp = self.gstate.stroke_params();
3102 sp.is_text_glyph = true;
3103 self.display_list.push(DisplayElement::PatternFill {
3104 params: PatternFillParams {
3105 path,
3106 fill_rule: FillRule::NonZeroWinding,
3107 tile: pattern.tile,
3108 pattern_matrix: pattern.pattern_matrix,
3109 bbox: pattern.bbox,
3110 xstep: pattern.x_step,
3111 ystep: pattern.y_step,
3112 paint_type: pattern.paint_type,
3113 underlying_color: if pattern.paint_type == 2 {
3114 Some(self.gstate.stroke_color.clone())
3115 } else {
3116 None
3117 },
3118 pattern_id: pattern.pattern_id,
3119 device_space_tile: false,
3120 flip_tile_y: false,
3121 stroke_params: Some(sp),
3122 overprint_mode: if self.gstate.overprint {
3123 self.gstate.overprint_mode
3124 } else {
3125 0
3126 },
3127 },
3128 });
3129 return;
3130 }
3131
3132 let mut params = self.gstate.stroke_params();
3133 params.is_text_glyph = true;
3134 self.display_list
3135 .push(DisplayElement::Stroke { path, params });
3136 }
3137
3138 fn op_bdc(&mut self) -> Result<(), PdfError> {
3147 let props = self.operand_stack.pop();
3148 let tag = self.operand_stack.pop();
3149
3150 let is_oc = matches!(&tag, Some(Operand::Name(n)) if n == b"OC");
3151 if !is_oc {
3152 self.mc_stack.push(MarkedContentFrame::Other);
3153 return Ok(());
3154 }
3155
3156 let mut pushed = false;
3158 if let Some(Operand::Name(prop_name)) = props {
3159 if let Some(props_dict) = self.resolve_resource_subdict(b"Properties") {
3160 if let Some(ocg_obj) = props_dict.get(&prop_name) {
3161 let ocg_id = self.ocg_obj_num(ocg_obj);
3162 let is_off = self.is_ocg_off(ocg_obj);
3163 let parent_list = std::mem::replace(&mut self.display_list, DisplayList::new());
3164 self.mc_stack.push(MarkedContentFrame::Ocg {
3165 parent_list,
3166 ocg_id,
3167 default_visible: !is_off,
3168 });
3169 pushed = true;
3170 }
3171 }
3172 }
3173 if !pushed {
3174 self.mc_stack.push(MarkedContentFrame::Other);
3177 }
3178
3179 Ok(())
3180 }
3181
3182 fn ocg_obj_num(&self, ocg_obj: &PdfObj) -> u32 {
3185 if let Some((obj_num, _)) = ocg_obj.as_ref() {
3186 obj_num
3187 } else {
3188 0
3189 }
3190 }
3191
3192 fn is_ocg_off(&self, ocg_obj: &PdfObj) -> bool {
3196 if let Some((obj_num, _)) = ocg_obj.as_ref() {
3198 if let Ok(resolved) = self.resolver.deref(ocg_obj) {
3200 if let Some(dict) = resolved.as_dict() {
3201 if dict.get_name(b"Type") == Some(b"OCMD") {
3202 return self.is_ocmd_off(dict);
3203 }
3204 }
3205 }
3206 return self.ocg_off.contains(&obj_num);
3208 }
3209 if let Some(dict) = ocg_obj.as_dict() {
3211 if dict.get_name(b"Type") == Some(b"OCMD") {
3212 return self.is_ocmd_off(dict);
3213 }
3214 }
3215 false
3216 }
3217
3218 fn is_ocmd_off(&self, ocmd: &PdfDict) -> bool {
3223 let policy = ocmd.get_name(b"P").unwrap_or(b"AnyOn");
3224
3225 let mut ocg_nums = Vec::new();
3227 if let Some(ocgs_obj) = ocmd.get(b"OCGs") {
3228 match ocgs_obj {
3229 PdfObj::Ref(num, _) => ocg_nums.push(*num),
3230 PdfObj::Array(arr) => {
3231 for item in arr {
3232 if let Some((num, _)) = item.as_ref() {
3233 ocg_nums.push(num);
3234 }
3235 }
3236 }
3237 _ => {}
3238 }
3239 }
3240 if ocg_nums.is_empty() {
3241 return false;
3242 }
3243
3244 let visible = match policy {
3246 b"AllOn" => ocg_nums.iter().all(|n| !self.ocg_off.contains(n)),
3247 b"AnyOff" => ocg_nums.iter().any(|n| self.ocg_off.contains(n)),
3248 b"AllOff" => ocg_nums.iter().all(|n| self.ocg_off.contains(n)),
3249 _ => ocg_nums.iter().any(|n| !self.ocg_off.contains(n)),
3250 };
3251 !visible
3252 }
3253
3254 fn op_do(&mut self) -> Result<(), PdfError> {
3257 let name = self
3258 .operand_stack
3259 .last()
3260 .and_then(|o| o.as_name())
3261 .ok_or(PdfError::Other("Do: expected name".into()))?
3262 .to_vec();
3263
3264 let xobj_dict = self
3266 .resolve_resource_subdict(b"XObject")
3267 .ok_or(PdfError::Other("no XObject resources".into()))?;
3268 let xobj_ref = xobj_dict.get(&name).ok_or_else(|| {
3269 PdfError::Other(format!(
3270 "XObject /{} not found",
3271 String::from_utf8_lossy(&name)
3272 ))
3273 })?;
3274 let xobj_ref_clone = xobj_ref.clone();
3276 let xobj = self.resolver.deref(xobj_ref)?;
3277 let dict = xobj
3278 .as_dict()
3279 .ok_or(PdfError::Other("XObject is not a stream".into()))?;
3280
3281 let xobj_ocg_info: Option<(u32, bool)> = dict
3285 .get(b"OC")
3286 .map(|oc_obj| (self.ocg_obj_num(oc_obj), !self.is_ocg_off(oc_obj)));
3287
3288 let mut wrapped = false;
3289 if let Some((ocg_id, default_visible)) = xobj_ocg_info {
3290 let parent_list = std::mem::replace(&mut self.display_list, DisplayList::new());
3291 self.mc_stack.push(MarkedContentFrame::Ocg {
3292 parent_list,
3293 ocg_id,
3294 default_visible,
3295 });
3296 wrapped = true;
3297 }
3298
3299 let subtype = dict.get_name(b"Subtype").unwrap_or(b"");
3300 match subtype {
3301 b"Image" => self.handle_image_xobject(&xobj_ref_clone, dict)?,
3302 b"Form" => self.handle_form_xobject(&xobj_ref_clone, dict)?,
3303 _ => {}
3304 }
3305
3306 if wrapped {
3308 if let Some(MarkedContentFrame::Ocg {
3309 parent_list,
3310 ocg_id,
3311 default_visible,
3312 }) = self.mc_stack.pop()
3313 {
3314 let ocg_list = std::mem::replace(&mut self.display_list, parent_list);
3315 self.display_list.push(DisplayElement::OcgGroup {
3316 elements: ocg_list,
3317 ocg_id,
3318 default_visible,
3319 });
3320 }
3321 }
3322
3323 Ok(())
3324 }
3325
3326 fn handle_image_xobject(&mut self, obj: &PdfObj, dict: &PdfDict) -> Result<(), PdfError> {
3328 if let PdfObj::Ref(obj_num, _) = obj {
3331 if let Some(cached) = self.image_cache.get(obj_num).cloned() {
3332 return self.emit_cached_image(cached);
3333 }
3334 }
3335
3336 let width = self
3338 .resolve_dict_int(dict, b"Width")
3339 .ok_or(PdfError::Other("image missing Width".into()))? as u32;
3340 let height = self
3341 .resolve_dict_int(dict, b"Height")
3342 .ok_or(PdfError::Other("image missing Height".into()))? as u32;
3343
3344 let is_image_mask = dict
3346 .get(b"ImageMask")
3347 .and_then(|o| match o {
3348 PdfObj::Bool(b) => Some(*b),
3349 _ => None,
3350 })
3351 .unwrap_or(false);
3352
3353 let bpc = if is_image_mask {
3354 1
3355 } else {
3356 dict.get_int(b"BitsPerComponent").unwrap_or(8) as u32
3357 };
3358
3359 let has_explicit_cs = dict.get(b"ColorSpace").is_some();
3361 let resolved_cs = if is_image_mask {
3362 None
3363 } else if let Some(cs_obj) = dict.get(b"ColorSpace") {
3364 match resolve_color_space_obj(cs_obj, self.resolver) {
3365 Ok(cs) => Some(cs),
3366 Err(_) => {
3367 Some(match bpc {
3371 1 => ResolvedColorSpace::DeviceGray,
3372 _ => ResolvedColorSpace::DeviceRGB,
3373 })
3374 }
3375 }
3376 } else {
3377 Some(ResolvedColorSpace::DeviceRGB)
3379 };
3380
3381 let polarity = if is_image_mask {
3382 if let Some(arr) = dict.get_array(b"Decode") {
3383 let vals: Vec<f64> = arr.iter().filter_map(|o| o.as_f64()).collect();
3384 vals.len() >= 2 && vals[0] > 0.5
3385 } else {
3386 false
3387 }
3388 } else {
3389 false
3390 };
3391
3392 let smask_in_data = dict.get_int(b"SMaskInData").unwrap_or(0);
3395
3396 let filter_name_raw = dict.get_name(b"Filter");
3397 let filter_is_dct = matches!(filter_name_raw, Some(b"DCTDecode" | b"DCT"));
3398 let filter_is_jpx = matches!(filter_name_raw, Some(b"JPXDecode" | b"JPX"))
3400 || dict.get_array(b"Filter").is_some_and(|arr| {
3401 arr.iter()
3402 .any(|f| matches!(f.as_name(), Some(b"JPXDecode" | b"JPX")))
3403 });
3404
3405 let cs_is_indexed = matches!(resolved_cs, Some(ResolvedColorSpace::Indexed { .. }));
3410 let sample_data = if filter_is_dct {
3411 if let Some(raw) = self.resolver.raw_stream_bytes(obj)
3412 && let Some((_jw, jh)) = crate::filters::jpeg_dimensions(raw)
3413 && jh > height * 2
3414 {
3415 let mut patched = raw.to_vec();
3416 crate::filters::patch_jpeg_sof_height(&mut patched, height as u16);
3417 crate::filters::decode_stream(
3418 &patched,
3419 &[crate::filters::Filter::DCTDecode],
3420 &[],
3421 None,
3422 )?
3423 } else {
3424 self.resolver.stream_data_from_obj(obj)?
3425 }
3426 } else if filter_is_jpx && cs_is_indexed {
3427 #[cfg(feature = "jpx")]
3432 {
3433 if let Some(raw) = self.resolver.raw_stream_bytes(obj) {
3434 let jp2_data = crate::filters::decode_pre_jpx(raw, dict);
3435 let (mut data, bpc) = crate::filters::decode_jpx_no_palette(&jp2_data)?;
3436 if bpc < 8 {
3440 let max_val = ((1u32 << bpc) - 1) as f64;
3441 for b in data.iter_mut() {
3442 *b = (*b as f64 / 255.0 * max_val).round() as u8;
3443 }
3444 }
3445 data
3446 } else {
3447 self.resolver.stream_data_from_obj(obj)?
3448 }
3449 }
3450 #[cfg(not(feature = "jpx"))]
3451 {
3452 self.resolver.stream_data_from_obj(obj)?
3453 }
3454 } else {
3455 self.resolver.stream_data_from_obj(obj)?
3456 };
3457
3458 let (width, height) = if filter_is_dct {
3462 if let Some(raw) = self.resolver.raw_stream_bytes(obj)
3463 && let Some((jw, jh)) = crate::filters::jpeg_dimensions(raw)
3464 && (jw != width || jh != height)
3465 && jw <= width * 2
3466 && jh <= height * 2
3467 {
3468 (jw, jh)
3469 } else {
3470 (width, height)
3471 }
3472 } else if filter_is_jpx {
3473 #[cfg(feature = "jpx")]
3474 {
3475 if let Some(raw) = self.resolver.raw_stream_bytes(obj) {
3479 let jp2_data = crate::filters::decode_pre_jpx(raw, dict);
3481 if let Some((jw, jh)) = crate::filters::jpx_dimensions(&jp2_data)
3482 && (jw != width || jh != height)
3483 {
3484 (jw, jh)
3485 } else {
3486 (width, height)
3487 }
3488 } else {
3489 (width, height)
3490 }
3491 }
3492 #[cfg(not(feature = "jpx"))]
3493 {
3494 (width, height)
3495 }
3496 } else {
3497 (width, height)
3498 };
3499
3500 let (resolved_cs, sample_data, smask_in_data_alpha) =
3510 if !is_image_mask && filter_is_jpx && has_explicit_cs {
3511 let n_cs = resolved_cs
3512 .as_ref()
3513 .map_or(3, |cs| cs.num_components() as usize);
3514 let pixels = width as usize * height as usize;
3515 let decoded_comps = if pixels > 0 {
3516 sample_data.len() / pixels
3517 } else {
3518 n_cs
3519 };
3520 if smask_in_data >= 1 && decoded_comps == n_cs + 1 {
3521 let mut color_data = Vec::with_capacity(pixels * n_cs);
3523 let mut alpha_data = Vec::with_capacity(pixels);
3524 for chunk in sample_data.chunks_exact(decoded_comps) {
3525 color_data.extend_from_slice(&chunk[..n_cs]);
3526 alpha_data.push(chunk[n_cs]);
3527 }
3528 (resolved_cs, color_data, Some(alpha_data))
3529 } else if decoded_comps > n_cs {
3530 let mut color_data = Vec::with_capacity(pixels * n_cs);
3533 for chunk in sample_data.chunks_exact(decoded_comps) {
3534 color_data.extend_from_slice(&chunk[..n_cs]);
3535 }
3536 (resolved_cs, color_data, None)
3537 } else {
3538 (resolved_cs, sample_data, None)
3540 }
3541 } else if !is_image_mask && !has_explicit_cs {
3542 let pixels = width as usize * height as usize;
3543 if pixels > 0 {
3544 let n_comps = sample_data.len() / pixels;
3545 if n_comps == 4 && self.is_jpx_rgba(obj) {
3549 if smask_in_data >= 1 {
3550 let mut rgba = sample_data;
3553 for chunk in rgba.chunks_exact_mut(4) {
3554 let a = chunk[3] as u16;
3555 if a == 0 {
3556 chunk[0] = 0;
3557 chunk[1] = 0;
3558 chunk[2] = 0;
3559 } else if a < 255 {
3560 chunk[0] = ((chunk[0] as u16 * a + 127) / 255) as u8;
3561 chunk[1] = ((chunk[1] as u16 * a + 127) / 255) as u8;
3562 chunk[2] = ((chunk[2] as u16 * a + 127) / 255) as u8;
3563 }
3564 }
3565 (None, rgba, None)
3566 } else {
3567 let mut rgb = Vec::with_capacity(pixels * 3);
3569 for chunk in sample_data.chunks_exact(4) {
3570 rgb.push(chunk[0]);
3571 rgb.push(chunk[1]);
3572 rgb.push(chunk[2]);
3573 }
3574 (Some(ResolvedColorSpace::DeviceRGB), rgb, None)
3575 }
3576 } else {
3577 let cs = match n_comps {
3578 1 => ResolvedColorSpace::DeviceGray,
3579 4 => ResolvedColorSpace::DeviceCMYK,
3580 _ => ResolvedColorSpace::DeviceRGB,
3581 };
3582 (Some(cs), sample_data, None)
3583 }
3584 } else {
3585 (resolved_cs, sample_data, None)
3586 }
3587 } else {
3588 (resolved_cs, sample_data, None)
3589 };
3590
3591 let image_matrix =
3593 Matrix::new(width as f64, 0.0, 0.0, -(height as f64), 0.0, height as f64);
3594
3595 if is_image_mask && self.gstate.fill_shading_pattern.is_some() {
3597 let shading_box = self.gstate.fill_shading_pattern.clone().unwrap();
3598 let row_bytes = width.div_ceil(8);
3599 let mut gray = vec![0u8; (width * height) as usize];
3600 for y in 0..height {
3601 for x in 0..width {
3602 let byte_idx = (y * row_bytes + x / 8) as usize;
3603 let bit_idx = 7 - (x % 8);
3604 let bit = if byte_idx < sample_data.len() {
3605 (sample_data[byte_idx] >> bit_idx) & 1
3606 } else {
3607 0
3608 };
3609 let painted = if polarity { bit == 1 } else { bit == 0 };
3610 gray[(y * width + x) as usize] = if painted { 255 } else { 0 };
3611 }
3612 }
3613
3614 let mut mask_dl = DisplayList::new();
3615 mask_dl.push(DisplayElement::Image {
3616 sample_data: Arc::new(gray),
3617 params: ImageParams {
3618 width,
3619 height,
3620 color_space: ImageColorSpace::DeviceGray,
3621 bits_per_component: 8,
3622 ctm: self.gstate.ctm,
3623 image_matrix,
3624 interpolate: false,
3625 mask_color: None,
3626 alpha: 1.0,
3627 blend_mode: 0,
3628 overprint: false,
3629 overprint_mode: 0,
3630 opm_paired: false,
3631 painted_channels: 0,
3632 },
3633 });
3634
3635 let mut content_dl = DisplayList::new();
3636 for elem in shading_box.0.elements() {
3637 content_dl.push(elem.clone());
3638 }
3639
3640 let corners = [
3641 self.gstate.ctm.transform_point(0.0, 0.0),
3642 self.gstate.ctm.transform_point(width as f64, 0.0),
3643 self.gstate.ctm.transform_point(0.0, height as f64),
3644 self.gstate.ctm.transform_point(width as f64, height as f64),
3645 ];
3646 let x_min = corners.iter().map(|c| c.0).fold(f64::INFINITY, f64::min);
3647 let y_min = corners.iter().map(|c| c.1).fold(f64::INFINITY, f64::min);
3648 let x_max = corners
3649 .iter()
3650 .map(|c| c.0)
3651 .fold(f64::NEG_INFINITY, f64::max);
3652 let y_max = corners
3653 .iter()
3654 .map(|c| c.1)
3655 .fold(f64::NEG_INFINITY, f64::max);
3656
3657 let parent_clip_bbox = self.current_clip_bbox();
3658 self.display_list.push(DisplayElement::SoftMasked {
3659 mask: mask_dl,
3660 content: content_dl,
3661 params: SoftMaskParams {
3662 subtype: SoftMaskSubtype::Luminosity,
3663 bbox: [x_min, y_min, x_max, y_max],
3664 backdrop_color: None,
3665 transfer_invert: false,
3666 has_nested_mask_scope: false,
3667 parent_clip_bbox,
3668 },
3669 mask_cache: Arc::new(Mutex::new(None)),
3670 });
3671 return Ok(());
3672 }
3673
3674 if is_image_mask
3680 && self.gstate.overprint
3681 && self.gstate.overprint_mode == 1
3682 && self.gstate.fill_pattern.is_none()
3683 && self.gstate.fill_shading_pattern.is_none()
3684 && self.gstate.fill_color.native_cmyk == Some((0.0, 0.0, 0.0, 0.0))
3685 {
3686 return Ok(());
3687 }
3688
3689 if is_image_mask && self.gstate.fill_pattern.is_some() {
3696 let pattern = self.gstate.fill_pattern.clone().unwrap();
3697 let row_bytes = width.div_ceil(8);
3698 let mut gray = vec![0u8; (width * height) as usize];
3699 for y in 0..height {
3700 for x in 0..width {
3701 let byte_idx = (y * row_bytes + x / 8) as usize;
3702 let bit_idx = 7 - (x % 8);
3703 let bit = if byte_idx < sample_data.len() {
3704 (sample_data[byte_idx] >> bit_idx) & 1
3705 } else {
3706 0
3707 };
3708 let painted = if polarity { bit == 1 } else { bit == 0 };
3709 gray[(y * width + x) as usize] = if painted { 255 } else { 0 };
3710 }
3711 }
3712
3713 let mut mask_dl = DisplayList::new();
3714 mask_dl.push(DisplayElement::Image {
3715 sample_data: Arc::new(gray),
3716 params: ImageParams {
3717 width,
3718 height,
3719 color_space: ImageColorSpace::DeviceGray,
3720 bits_per_component: 8,
3721 ctm: self.gstate.ctm,
3722 image_matrix,
3723 interpolate: false,
3724 mask_color: None,
3725 alpha: 1.0,
3726 blend_mode: 0,
3727 overprint: false,
3728 overprint_mode: 0,
3729 opm_paired: false,
3730 painted_channels: 0,
3731 },
3732 });
3733
3734 let corners = [
3735 self.gstate.ctm.transform_point(0.0, 0.0),
3736 self.gstate.ctm.transform_point(width as f64, 0.0),
3737 self.gstate.ctm.transform_point(0.0, height as f64),
3738 self.gstate.ctm.transform_point(width as f64, height as f64),
3739 ];
3740 let x_min = corners.iter().map(|c| c.0).fold(f64::INFINITY, f64::min);
3741 let y_min = corners.iter().map(|c| c.1).fold(f64::INFINITY, f64::min);
3742 let x_max = corners
3743 .iter()
3744 .map(|c| c.0)
3745 .fold(f64::NEG_INFINITY, f64::max);
3746 let y_max = corners
3747 .iter()
3748 .map(|c| c.1)
3749 .fold(f64::NEG_INFINITY, f64::max);
3750
3751 let pm = &pattern.pattern_matrix;
3754 let mut content_dl = DisplayList::new();
3755 for elem in pattern.tile.elements() {
3756 if let DisplayElement::Image {
3757 sample_data: sd,
3758 params: ip,
3759 } = elem
3760 {
3761 let dev_ctm = pm.multiply(&ip.ctm);
3762 content_dl.push(DisplayElement::Image {
3763 sample_data: sd.clone(),
3764 params: ImageParams {
3765 ctm: dev_ctm,
3766 ..ip.clone()
3767 },
3768 });
3769 }
3770 }
3771
3772 let parent_clip_bbox = self.current_clip_bbox();
3773 self.display_list.push(DisplayElement::SoftMasked {
3774 mask: mask_dl,
3775 content: content_dl,
3776 params: SoftMaskParams {
3777 subtype: SoftMaskSubtype::Luminosity,
3778 bbox: [x_min, y_min, x_max, y_max],
3779 backdrop_color: None,
3780 transfer_invert: false,
3781 has_nested_mask_scope: false,
3782 parent_clip_bbox,
3783 },
3784 mask_cache: Arc::new(Mutex::new(None)),
3785 });
3786 return Ok(());
3787 }
3788
3789 let (color_space, sample_data) = if !is_image_mask
3795 && let Some(ResolvedColorSpace::DeviceN {
3796 names,
3797 alt,
3798 tint_fn: Some(func),
3799 }) = resolved_cs.as_ref()
3800 && names.len() >= 2
3801 && matches!(
3802 alt.as_ref(),
3803 ResolvedColorSpace::DeviceGray | ResolvedColorSpace::DeviceRGB
3804 ) {
3805 let ni = names.len();
3806 let npixels = width as usize * height as usize;
3807 let mut rgba = vec![255u8; npixels * 4];
3808 let mut inputs = vec![0.0f64; ni];
3809 for i in 0..npixels {
3810 let si = i * ni;
3811 for (c, inp) in inputs.iter_mut().enumerate() {
3812 *inp = sample_data.get(si + c).copied().unwrap_or(0) as f64 / 255.0;
3813 }
3814 let out = func.evaluate(&inputs);
3815 let (r, g, b) = color_space::alt_comps_to_rgb_f64(&out, alt);
3816 let pi = i * 4;
3817 rgba[pi] = r;
3818 rgba[pi + 1] = g;
3819 rgba[pi + 2] = b;
3820 }
3821 (ImageColorSpace::PreconvertedRGBA, rgba)
3822 } else if is_image_mask {
3823 (
3824 ImageColorSpace::Mask {
3825 color: self.gstate.fill_color.clone(),
3826 polarity,
3827 },
3828 sample_data,
3829 )
3830 } else if let Some(ref rcs) = resolved_cs {
3831 (to_image_color_space(rcs), sample_data)
3832 } else {
3833 (ImageColorSpace::PreconvertedRGBA, sample_data)
3835 };
3836
3837 let color_space = if !is_image_mask {
3843 if let ImageColorSpace::Indexed { base, .. } = &color_space {
3844 let expected_1comp = (width * height) as usize;
3845 let base_n = base.num_components() as usize;
3846 if sample_data.len() == expected_1comp * base_n && base_n > 1 {
3847 *base.clone()
3848 } else {
3849 color_space
3850 }
3851 } else {
3852 color_space
3853 }
3854 } else {
3855 color_space
3856 };
3857
3858 let interpolate = dict
3859 .get(b"Interpolate")
3860 .and_then(|o| match o {
3861 PdfObj::Bool(b) => Some(*b),
3862 _ => None,
3863 })
3864 .unwrap_or(false);
3865
3866 let (mask_color, explicit_mask_data) = match dict.get(b"Mask") {
3868 Some(PdfObj::Array(arr)) => {
3869 let mc: Vec<u8> = arr
3871 .iter()
3872 .filter_map(|o| o.as_int().map(|n| n as u8))
3873 .collect();
3874 (Some(mc), None)
3875 }
3876 Some(_mask_obj) => {
3877 let mask_alpha = self
3879 .resolve_explicit_mask(dict, width, height)
3880 .unwrap_or(None);
3881 (None, mask_alpha)
3882 }
3883 None => (None, None),
3884 };
3885
3886 let is_jpx = filter_is_jpx;
3890 let is_dct = filter_is_dct;
3891 let is_indexed = matches!(&color_space, ImageColorSpace::Indexed { .. });
3892 let (sample_data, display_bpc) =
3896 if is_image_mask || bpc == 8 || bpc == 0 || is_jpx || is_dct {
3897 (sample_data, if is_dct || is_jpx { 8 } else { bpc })
3898 } else if bpc == 16 {
3899 (sample_data.chunks(2).map(|c| c[0]).collect(), 8)
3901 } else if bpc > 8 {
3902 (sample_data, bpc)
3903 } else {
3904 (
3905 expand_bits_to_bytes(
3906 &sample_data,
3907 bpc,
3908 width,
3909 height,
3910 color_space.num_components(),
3911 is_indexed,
3912 ),
3913 8,
3914 )
3915 };
3916
3917 let sample_data = if !is_image_mask {
3922 if let Some(decode) = dict.get_array(b"Decode") {
3923 let n_comps = color_space.num_components() as usize;
3924 let decode_vals: Vec<f64> = decode.iter().filter_map(|o| o.as_f64()).collect();
3925 if decode_vals.len() >= n_comps * 2 {
3926 let effective_bpc = if is_jpx || is_dct { 8 } else { bpc };
3927 let max_sample = ((1u32 << effective_bpc) - 1) as f64;
3928 let is_default = if is_indexed {
3931 decode_vals.len() == 2
3932 && (decode_vals[0]).abs() < 1e-6
3933 && (decode_vals[1] - max_sample).abs() < 1e-6
3934 } else {
3935 decode_vals.chunks(2).all(|pair| {
3936 pair.len() == 2
3937 && (pair[0] - 0.0).abs() < 1e-6
3938 && (pair[1] - 1.0).abs() < 1e-6
3939 })
3940 };
3941 if !is_default {
3942 let max_val = if is_indexed {
3945 ((1u32 << effective_bpc) - 1) as f64
3946 } else {
3947 255.0f64
3948 };
3949 let mut result = Vec::with_capacity(sample_data.len());
3950 if is_indexed {
3951 let d_min = decode_vals[0];
3953 let d_max = decode_vals[1];
3954 for &sample in sample_data.iter() {
3955 let val = d_min + (sample as f64 / max_val) * (d_max - d_min);
3956 result.push(val.round().clamp(0.0, 255.0) as u8);
3957 }
3958 } else {
3959 for (i, &sample) in sample_data.iter().enumerate() {
3961 let comp = i % n_comps;
3962 let d_min = decode_vals[comp * 2];
3963 let d_max = decode_vals[comp * 2 + 1];
3964 let val = d_min + (sample as f64 / max_val) * (d_max - d_min);
3965 result.push((val.clamp(0.0, 1.0) * 255.0 + 0.5) as u8);
3966 }
3967 }
3968 result
3969 } else {
3970 sample_data
3971 }
3972 } else {
3973 sample_data
3974 }
3975 } else {
3976 sample_data
3977 }
3978 } else {
3979 sample_data
3980 };
3981
3982 let (sample_data, color_space, resolved_cs) = if !is_image_mask
3987 && bpc == 1
3988 && self.page_group_is_cmyk
3989 && matches!(color_space, ImageColorSpace::DeviceGray)
3990 {
3991 let npixels = (width * height) as usize;
3992 let mut cmyk = vec![0u8; npixels * 4];
3993 for i in 0..npixels {
3994 let g = sample_data.get(i).copied().unwrap_or(0);
3995 cmyk[i * 4 + 3] = 255 - g; }
3997 (
3998 cmyk,
3999 ImageColorSpace::DeviceCMYK,
4000 Some(ResolvedColorSpace::DeviceCMYK),
4001 )
4002 } else {
4003 (sample_data, color_space, resolved_cs)
4004 };
4005
4006 if !is_image_mask {
4010 if let Some(ref rcs) = resolved_cs {
4011 register_icc_profile(rcs, &mut self.icc_cache);
4012 }
4013 }
4014
4015 let smask_result = if !is_image_mask {
4022 let dict_smask = self.resolve_smask(dict, width, height)?;
4023 if dict_smask.is_none() {
4025 if let Some(alpha) = smask_in_data_alpha {
4026 Some((alpha, width, height, None))
4027 } else {
4028 None
4029 }
4030 } else {
4031 dict_smask
4032 }
4033 } else {
4034 None
4035 };
4036
4037 let (sample_data, color_space, width, height) =
4041 if let Some((mask_alpha, mw, mh)) = explicit_mask_data {
4042 let (up_data, up_cs) = if let ImageColorSpace::Indexed {
4045 base,
4046 hival,
4047 lookup,
4048 } = &color_space
4049 {
4050 let n_base = base.num_components() as usize;
4051 let n_pixels = (width * height) as usize;
4052 let mut expanded = vec![0u8; n_pixels * n_base];
4053 for i in 0..n_pixels {
4054 let idx = sample_data.get(i).copied().unwrap_or(0) as usize;
4055 let idx = idx.min(*hival as usize);
4056 let offset = idx * n_base;
4057 for c in 0..n_base {
4058 expanded[i * n_base + c] = lookup.get(offset + c).copied().unwrap_or(0);
4059 }
4060 }
4061 (expanded, *base.clone())
4062 } else {
4063 (sample_data, color_space)
4064 };
4065 let (img_data, img_w, img_h) = if mw > width || mh > height {
4066 let upscaled = bilinear_upsample_image(&up_data, width, height, mw, mh, &up_cs);
4068 (upscaled, mw, mh)
4069 } else {
4070 (up_data, width, height)
4071 };
4072 let rgba = merge_rgb_with_smask(
4073 &img_data,
4074 &mask_alpha,
4075 &up_cs,
4076 img_w,
4077 img_h,
4078 Some(&self.icc_cache),
4079 );
4080 (rgba, ImageColorSpace::PreconvertedRGBA, img_w, img_h)
4081 } else {
4082 (sample_data, color_space, width, height)
4083 };
4084
4085 let sample_data = if !is_image_mask && self.gstate.transfer.has_functions() {
4087 let n_comps = color_space.num_components() as usize;
4088 if n_comps >= 3 {
4089 let mut data = sample_data;
4090 apply_transfer_to_image(&mut data, &self.gstate.transfer, n_comps);
4091 data
4092 } else {
4093 sample_data
4094 }
4095 } else {
4096 sample_data
4097 };
4098
4099 let image_matrix =
4101 Matrix::new(width as f64, 0.0, 0.0, -(height as f64), 0.0, height as f64);
4102
4103 let painted_channels_override = if let ImageColorSpace::Indexed {
4111 base,
4112 hival,
4113 lookup,
4114 } = &color_space
4115 {
4116 if matches!(
4117 base.as_ref(),
4118 ImageColorSpace::DeviceCMYK | ImageColorSpace::ICCBased { n: 4, .. }
4119 ) {
4120 let n_entries = (*hival as usize + 1).min(lookup.len() / 4);
4121 let is_k_only = n_entries > 0
4122 && (0..n_entries).all(|i| {
4123 let off = i * 4;
4124 lookup.get(off).copied().unwrap_or(0) == 0
4125 && lookup.get(off + 1).copied().unwrap_or(0) == 0
4126 && lookup.get(off + 2).copied().unwrap_or(0) == 0
4127 });
4128 if is_k_only {
4129 stet_graphics::device::CMYK_K
4130 } else {
4131 stet_graphics::device::CMYK_ALL
4132 }
4133 } else {
4134 resolved_cs
4135 .as_ref()
4136 .map(painted_channels_for_cs)
4137 .unwrap_or(self.gstate.fill_painted_channels)
4138 }
4139 } else {
4140 resolved_cs
4141 .as_ref()
4142 .map(painted_channels_for_cs)
4143 .unwrap_or(self.gstate.fill_painted_channels)
4144 };
4145
4146 let image_params = ImageParams {
4147 width,
4148 height,
4149 color_space,
4150 bits_per_component: display_bpc as u8,
4151 ctm: self.gstate.ctm,
4152 image_matrix,
4153 interpolate,
4154 mask_color,
4155 alpha: self.gstate.fill_alpha,
4156 blend_mode: self.gstate.blend_mode,
4157 overprint: self.gstate.overprint,
4158 overprint_mode: self.gstate.overprint_mode,
4159 opm_paired: self.gstate.opm_paired,
4160 painted_channels: painted_channels_override,
4161 };
4162
4163 if let Some((smask_data, mw, mh, matte)) = smask_result {
4167 const MAX_PIXELS: u64 = 16_000_000; let mut target_w = mw.max(width);
4176 let mut target_h = mh.max(height);
4177 if (target_w as u64) * (target_h as u64) > MAX_PIXELS {
4178 let scale = (MAX_PIXELS as f64 / (target_w as f64 * target_h as f64)).sqrt();
4179 target_w = (target_w as f64 * scale).ceil() as u32;
4180 target_h = (target_h as f64 * scale).ceil() as u32;
4181 }
4182 let (sample_data, width, height) = if target_w > width || target_h > height {
4183 let upscaled = bilinear_upsample_image(
4184 &sample_data,
4185 width,
4186 height,
4187 target_w,
4188 target_h,
4189 &image_params.color_space,
4190 );
4191 (upscaled, target_w, target_h)
4192 } else {
4193 (sample_data, width, height)
4194 };
4195
4196 let smask_data = if mw != width || mh != height {
4198 let mut resampled = vec![0u8; (width * height) as usize];
4199 for y in 0..height {
4200 let sy = (y as u64 * mh as u64 / height as u64) as u32;
4201 for x in 0..width {
4202 let sx = (x as u64 * mw as u64 / width as u64) as u32;
4203 resampled[(y * width + x) as usize] = smask_data
4204 .get((sy * mw + sx) as usize)
4205 .copied()
4206 .unwrap_or(0);
4207 }
4208 }
4209 resampled
4210 } else {
4211 smask_data
4212 };
4213
4214 let sample_data = if let Some(ref mc) = matte {
4218 let n_comps = image_params.color_space.num_components() as usize;
4219 if mc.len() >= n_comps && n_comps >= 3 {
4220 let mut out = sample_data;
4221 let pixels = (width * height) as usize;
4222 for i in 0..pixels {
4223 let a = smask_data[i] as f64 / 255.0;
4224 if a > 0.0 && a < 1.0 {
4225 for c in 0..n_comps.min(3) {
4226 let m = (mc[c] * 255.0).clamp(0.0, 255.0);
4227 let premul = out[i * n_comps + c] as f64;
4228 let orig = m + (premul - m) / a;
4229 out[i * n_comps + c] = orig.round().clamp(0.0, 255.0) as u8;
4230 }
4231 }
4232 }
4233 out
4234 } else {
4235 sample_data
4236 }
4237 } else {
4238 sample_data
4239 };
4240
4241 let sample_arc = Arc::new(sample_data);
4243 let smask_arc = Arc::new(smask_data);
4244 if let PdfObj::Ref(obj_num, _) = obj {
4245 self.image_cache.insert(
4246 *obj_num,
4247 CachedImage {
4248 sample_data: Arc::clone(&sample_arc),
4249 width,
4250 height,
4251 color_space: image_params.color_space.clone(),
4252 bits_per_component: image_params.bits_per_component,
4253 interpolate,
4254 mask_color: image_params.mask_color.clone(),
4255 painted_channels: image_params.painted_channels,
4256 smask: Some((Arc::clone(&smask_arc), width, height, matte.clone())),
4257 },
4258 );
4259 }
4260
4261 let image_matrix =
4262 Matrix::new(width as f64, 0.0, 0.0, -(height as f64), 0.0, height as f64);
4263
4264 let mut mask_dl = DisplayList::new();
4265 mask_dl.push(DisplayElement::Image {
4266 sample_data: smask_arc,
4267 params: ImageParams {
4268 width,
4269 height,
4270 color_space: ImageColorSpace::DeviceGray,
4271 bits_per_component: 8,
4272 ctm: self.gstate.ctm,
4273 image_matrix,
4274 interpolate,
4275 mask_color: None,
4276 alpha: 1.0,
4277 blend_mode: 0,
4278 overprint: false,
4279 overprint_mode: 0,
4280 opm_paired: false,
4281 painted_channels: 0,
4282 },
4283 });
4284
4285 let mut content_dl = DisplayList::new();
4286 content_dl.push(DisplayElement::Image {
4287 sample_data: sample_arc,
4288 params: ImageParams {
4289 width,
4290 height,
4291 image_matrix,
4292 ..image_params
4293 },
4294 });
4295
4296 let corners = [
4297 self.gstate.ctm.transform_point(0.0, 0.0),
4298 self.gstate.ctm.transform_point(1.0, 0.0),
4299 self.gstate.ctm.transform_point(0.0, 1.0),
4300 self.gstate.ctm.transform_point(1.0, 1.0),
4301 ];
4302 let x_min = corners.iter().map(|c| c.0).fold(f64::INFINITY, f64::min);
4303 let y_min = corners.iter().map(|c| c.1).fold(f64::INFINITY, f64::min);
4304 let x_max = corners
4305 .iter()
4306 .map(|c| c.0)
4307 .fold(f64::NEG_INFINITY, f64::max);
4308 let y_max = corners
4309 .iter()
4310 .map(|c| c.1)
4311 .fold(f64::NEG_INFINITY, f64::max);
4312
4313 let parent_clip_bbox = self.current_clip_bbox();
4314 self.display_list.push(DisplayElement::SoftMasked {
4315 mask: mask_dl,
4316 content: content_dl,
4317 params: SoftMaskParams {
4318 subtype: SoftMaskSubtype::Luminosity,
4319 bbox: [x_min, y_min, x_max, y_max],
4320 backdrop_color: None,
4321 transfer_invert: false,
4322 has_nested_mask_scope: false,
4323 parent_clip_bbox,
4324 },
4325 mask_cache: Arc::new(Mutex::new(None)),
4326 });
4327 } else {
4328 let sample_arc = Arc::new(sample_data);
4330 if let PdfObj::Ref(obj_num, _) = obj {
4331 self.image_cache.insert(
4332 *obj_num,
4333 CachedImage {
4334 sample_data: Arc::clone(&sample_arc),
4335 width,
4336 height,
4337 color_space: image_params.color_space.clone(),
4338 bits_per_component: image_params.bits_per_component,
4339 interpolate,
4340 mask_color: image_params.mask_color.clone(),
4341 painted_channels: image_params.painted_channels,
4342 smask: None,
4343 },
4344 );
4345 }
4346
4347 self.display_list.push(DisplayElement::Image {
4348 sample_data: sample_arc,
4349 params: image_params,
4350 });
4351 }
4352 Ok(())
4353 }
4354
4355 #[allow(clippy::too_many_arguments)]
4358 fn emit_cached_image(&mut self, cached: CachedImage) -> Result<(), PdfError> {
4361 let (sample_data, smask, width, height) = (
4362 cached.sample_data,
4363 cached.smask,
4364 cached.width,
4365 cached.height,
4366 );
4367
4368 let image_matrix =
4369 Matrix::new(width as f64, 0.0, 0.0, -(height as f64), 0.0, height as f64);
4370 let image_params = ImageParams {
4371 width,
4372 height,
4373 color_space: cached.color_space,
4374 bits_per_component: cached.bits_per_component,
4375 ctm: self.gstate.ctm,
4376 image_matrix,
4377 interpolate: cached.interpolate,
4378 mask_color: cached.mask_color,
4379 alpha: self.gstate.fill_alpha,
4380 blend_mode: self.gstate.blend_mode,
4381 overprint: self.gstate.overprint,
4382 overprint_mode: self.gstate.overprint_mode,
4383 opm_paired: self.gstate.opm_paired,
4384 painted_channels: cached.painted_channels,
4385 };
4386
4387 if let Some((smask_data, sw, sh, _matte)) = smask {
4388 let mut mask_dl = DisplayList::new();
4389 mask_dl.push(DisplayElement::Image {
4390 sample_data: smask_data,
4391 params: ImageParams {
4392 width: sw,
4393 height: sh,
4394 color_space: ImageColorSpace::DeviceGray,
4395 bits_per_component: 8,
4396 ctm: self.gstate.ctm,
4397 image_matrix,
4398 interpolate: cached.interpolate,
4399 mask_color: None,
4400 alpha: 1.0,
4401 blend_mode: 0,
4402 overprint: false,
4403 overprint_mode: 0,
4404 opm_paired: false,
4405 painted_channels: 0,
4406 },
4407 });
4408
4409 let mut content_dl = DisplayList::new();
4410 content_dl.push(DisplayElement::Image {
4411 sample_data,
4412 params: ImageParams {
4413 width,
4414 height,
4415 image_matrix,
4416 ..image_params
4417 },
4418 });
4419
4420 let corners = [
4421 self.gstate.ctm.transform_point(0.0, 0.0),
4422 self.gstate.ctm.transform_point(1.0, 0.0),
4423 self.gstate.ctm.transform_point(0.0, 1.0),
4424 self.gstate.ctm.transform_point(1.0, 1.0),
4425 ];
4426 let x_min = corners.iter().map(|c| c.0).fold(f64::INFINITY, f64::min);
4427 let y_min = corners.iter().map(|c| c.1).fold(f64::INFINITY, f64::min);
4428 let x_max = corners
4429 .iter()
4430 .map(|c| c.0)
4431 .fold(f64::NEG_INFINITY, f64::max);
4432 let y_max = corners
4433 .iter()
4434 .map(|c| c.1)
4435 .fold(f64::NEG_INFINITY, f64::max);
4436
4437 let parent_clip_bbox = self.current_clip_bbox();
4438 self.display_list.push(DisplayElement::SoftMasked {
4439 mask: mask_dl,
4440 content: content_dl,
4441 params: SoftMaskParams {
4442 subtype: SoftMaskSubtype::Luminosity,
4443 bbox: [x_min, y_min, x_max, y_max],
4444 backdrop_color: None,
4445 transfer_invert: false,
4446 has_nested_mask_scope: false,
4447 parent_clip_bbox,
4448 },
4449 mask_cache: Arc::new(Mutex::new(None)),
4450 });
4451 } else {
4452 self.display_list.push(DisplayElement::Image {
4453 sample_data,
4454 params: image_params,
4455 });
4456 }
4457 Ok(())
4458 }
4459
4460 fn resolve_smask(
4465 &self,
4466 dict: &PdfDict,
4467 image_w: u32,
4468 image_h: u32,
4469 ) -> Result<Option<(Vec<u8>, u32, u32, Option<Vec<f64>>)>, PdfError> {
4470 let smask_ref = match dict.get(b"SMask") {
4471 Some(obj) => obj.clone(),
4472 None => return Ok(None),
4473 };
4474 let smask_obj = self.resolver.deref(&smask_ref)?;
4475 let smask_dict = match smask_obj.as_dict() {
4476 Some(d) => d,
4477 None => return Ok(None),
4478 };
4479 let sw = smask_dict.get_int(b"Width").unwrap_or(image_w as i64) as u32;
4482 let sh = smask_dict.get_int(b"Height").unwrap_or(image_h as i64) as u32;
4483 if sw == 0 || sh == 0 {
4484 return Ok(None);
4485 }
4486 let bpc = smask_dict.get_int(b"BitsPerComponent").unwrap_or(8) as u32;
4487 let data = self.resolver.stream_data_from_obj(&smask_ref)?;
4488
4489 let mut data = if bpc == 8 {
4492 data
4493 } else if bpc == 16 {
4494 data.chunks(2).map(|c| c[0]).collect()
4496 } else if bpc < 8 {
4497 expand_bits_to_bytes(&data, bpc, sw, sh, 1, false)
4498 } else {
4499 data
4500 };
4501
4502 if let Some(decode) = smask_dict.get_array(b"Decode")
4504 && decode.len() >= 2
4505 {
4506 let d0 = decode[0].as_f64().unwrap_or(0.0);
4507 let d1 = decode[1].as_f64().unwrap_or(1.0);
4508 if (d0 - 1.0).abs() < 1e-6 && d1.abs() < 1e-6 {
4509 for b in data.iter_mut() {
4511 *b = 255 - *b;
4512 }
4513 } else if (d0).abs() > 1e-6 || (d1 - 1.0).abs() > 1e-6 {
4514 for b in data.iter_mut() {
4516 let v = d0 + (d1 - d0) * (*b as f64 / 255.0);
4517 *b = (v * 255.0).round().clamp(0.0, 255.0) as u8;
4518 }
4519 }
4520 }
4521
4522 let matte = smask_dict
4524 .get_array(b"Matte")
4525 .map(|arr| arr.iter().filter_map(|o| o.as_f64()).collect::<Vec<_>>());
4526
4527 Ok(Some((data, sw, sh, matte)))
4528 }
4529
4530 fn resolve_explicit_mask(
4536 &self,
4537 dict: &PdfDict,
4538 image_w: u32,
4539 image_h: u32,
4540 ) -> Result<Option<(Vec<u8>, u32, u32)>, PdfError> {
4541 let mask_ref = match dict.get(b"Mask") {
4542 Some(obj) => obj.clone(),
4543 None => return Ok(None),
4544 };
4545 let mask_obj = self.resolver.deref(&mask_ref)?;
4546 let mask_dict = match mask_obj.as_dict() {
4547 Some(d) => d,
4548 None => return Ok(None),
4549 };
4550 let mw = mask_dict.get_int(b"Width").unwrap_or(0) as u32;
4551 let mh = mask_dict.get_int(b"Height").unwrap_or(0) as u32;
4552 if mw == 0 || mh == 0 {
4553 return Ok(None);
4554 }
4555 let mask_data = self.resolver.stream_data_from_obj(&mask_ref)?;
4556
4557 let invert = if let Some(decode) = mask_dict.get_array(b"Decode") {
4559 if decode.len() >= 2 {
4560 let d0 = decode[0].as_f64().unwrap_or(0.0);
4561 d0 > 0.5
4563 } else {
4564 false
4565 }
4566 } else {
4567 false
4568 };
4569
4570 let row_bytes = mw.div_ceil(8);
4572 let mut alpha = vec![0u8; (mw * mh) as usize];
4573 for y in 0..mh {
4574 for x in 0..mw {
4575 let byte_idx = (y * row_bytes + x / 8) as usize;
4576 let bit_idx = 7 - (x % 8);
4577 let bit = if byte_idx < mask_data.len() {
4578 (mask_data[byte_idx] >> bit_idx) & 1
4579 } else {
4580 0
4581 };
4582 let opaque = if invert { bit == 1 } else { bit == 0 };
4585 alpha[(y * mw + x) as usize] = if opaque { 255 } else { 0 };
4586 }
4587 }
4588
4589 if mw == image_w && mh == image_h {
4594 Ok(Some((alpha, mw, mh)))
4595 } else if mw >= image_w && mh >= image_h {
4596 Ok(Some((alpha, mw, mh)))
4598 } else {
4599 let mut resampled = vec![0u8; (image_w * image_h) as usize];
4601 let ratio_x = mw as f32 / image_w as f32;
4602 let ratio_y = mh as f32 / image_h as f32;
4603 for y in 0..image_h {
4604 let top_f = y as f32 * ratio_y;
4605 let bottom_f = (y + 1) as f32 * ratio_y;
4606 let top = (top_f as u32).min(mh - 1);
4607 let bottom = (bottom_f.ceil() as u32).min(mh);
4608 for x in 0..image_w {
4609 let left_f = x as f32 * ratio_x;
4610 let right_f = (x + 1) as f32 * ratio_x;
4611 let left = (left_f as u32).min(mw - 1);
4612 let right = (right_f.ceil() as u32).min(mw);
4613 let mut sum = 0.0f32;
4614 let mut weight = 0.0f32;
4615 for sy in top..bottom {
4616 let py_top = sy as f32;
4617 let py_bot = (sy + 1) as f32;
4618 let wy = py_bot.min(bottom_f) - py_top.max(top_f);
4619 for sx in left..right {
4620 let px_left = sx as f32;
4621 let px_right = (sx + 1) as f32;
4622 let wx = px_right.min(right_f) - px_left.max(left_f);
4623 let w = wx * wy;
4624 sum += alpha[(sy * mw + sx) as usize] as f32 * w;
4625 weight += w;
4626 }
4627 }
4628 resampled[(y * image_w + x) as usize] = if weight > 0.0 {
4629 (sum / weight + 0.5).min(255.0) as u8
4630 } else {
4631 0
4632 };
4633 }
4634 }
4635 Ok(Some((resampled, image_w, image_h)))
4636 }
4637 }
4638
4639 fn is_jpx_rgba(&self, obj: &PdfObj) -> bool {
4642 #[cfg(feature = "jpx")]
4643 {
4644 if let Ok((raw, filters)) = self.resolver.raw_stream_and_filters(obj) {
4645 if filters
4646 .iter()
4647 .any(|f| matches!(f, crate::filters::Filter::JPXDecode))
4648 {
4649 if let Some((color_channels, has_alpha)) = crate::filters::jpx_color_info(&raw)
4650 {
4651 return color_channels == 3 && has_alpha;
4652 }
4653 }
4654 }
4655 }
4656 false
4657 }
4658
4659 fn handle_form_xobject(&mut self, obj: &PdfObj, dict: &PdfDict) -> Result<(), PdfError> {
4661 if self.depth >= 20 {
4662 return Err(PdfError::Other("Form XObject nesting too deep".into()));
4663 }
4664
4665 let form_resources = if let Some(res_obj) = dict.get(b"Resources") {
4667 match self.resolver.deref(res_obj)? {
4668 PdfObj::Dict(d) => d,
4669 _ => self.resources.clone(),
4670 }
4671 } else {
4672 self.resources.clone()
4673 };
4674
4675 let form_matrix = if let Some(vals) = deref_num_array(self.resolver, dict, b"Matrix") {
4677 if vals.len() == 6 {
4678 Matrix::new(vals[0], vals[1], vals[2], vals[3], vals[4], vals[5])
4679 } else {
4680 Matrix::identity()
4681 }
4682 } else {
4683 Matrix::identity()
4684 };
4685
4686 let bbox = if let Some(vals) = deref_num_array(self.resolver, dict, b"BBox") {
4688 if vals.len() == 4 {
4689 Some((vals[0], vals[1], vals[2], vals[3]))
4690 } else {
4691 None
4692 }
4693 } else {
4694 None
4695 };
4696
4697 let is_transparency_group = self.is_transparency_group(dict);
4699
4700 let form_data = self.resolver.stream_data_from_obj(obj)?;
4702
4703 self.gstate_stack.push(self.gstate.clone());
4706 let saved_stack_depth = self.gstate_stack.len();
4707 let saved_resources = std::mem::replace(&mut self.resources, form_resources);
4708 let saved_font_cache = std::mem::take(&mut self.font_cache);
4709 let saved_current_font = self.current_font.take();
4710 let saved_cs_index = self.cs_index.take(); let saved_content_stream_ctm = self.content_stream_ctm;
4712 let saved_mc_stack = std::mem::take(&mut self.mc_stack);
4713 let saved_path = std::mem::take(&mut self.current_path);
4717 let saved_point = self.current_point.take();
4718 let saved_subpath = self.subpath_start.take();
4719
4720 self.gstate.ctm = self.gstate.ctm.concat(&form_matrix);
4722
4723 self.content_stream_ctm = self.gstate.ctm;
4726
4727 if is_transparency_group {
4728 let group_blend_mode = self.gstate.blend_mode;
4731 let group_alpha = self.gstate.fill_alpha;
4732
4733 self.gstate.fill_alpha = 1.0;
4741 self.gstate.stroke_alpha = 1.0;
4742 self.gstate.soft_mask = None;
4743
4744 let mut group_list = DisplayList::new();
4746 std::mem::swap(&mut self.display_list, &mut group_list);
4747
4748 let saved_scope = self.soft_mask_scope.take();
4750
4751 let device_bbox = self.compute_device_bbox(bbox);
4754
4755 if let Some((x0, y0, x1, y1)) = bbox {
4757 self.push_bbox_clip(x0, y0, x1, y1);
4758 }
4759
4760 self.depth += 1;
4762 self.interpret_stream(&form_data)?;
4763 self.depth -= 1;
4764
4765 self.flush_soft_mask();
4767
4768 std::mem::swap(&mut self.display_list, &mut group_list);
4770
4771 self.soft_mask_scope = saved_scope;
4773
4774 let isolated = self.get_group_isolated(dict);
4776 let knockout = self.get_group_knockout(dict);
4777 let color_space = self.get_group_color_space(dict);
4778
4779 self.display_list.push(DisplayElement::Group {
4781 elements: group_list,
4782 params: GroupParams {
4783 bbox: device_bbox,
4784 isolated,
4785 knockout,
4786 blend_mode: group_blend_mode,
4787 alpha: group_alpha,
4788 color_space,
4789 },
4790 });
4791 } else {
4792 if let Some((x0, y0, x1, y1)) = bbox {
4794 self.push_bbox_clip(x0, y0, x1, y1);
4795 }
4796
4797 let saved_cull = self.form_cull_y.take();
4802 if let Some((_x0, y0, _x1, y1)) = bbox {
4803 let form_height = (y1 - y0).abs();
4804 if form_height > 5000.0 {
4806 let ctm = &self.gstate.ctm;
4809 if ctm.b.abs() < 1e-6 && ctm.c.abs() < 1e-6 && ctm.d.abs() > 1e-6 {
4812 let page_h = self.initial_ctm.ty.abs();
4814 let fy0 = (0.0 - ctm.ty) / ctm.d;
4815 let fy1 = (page_h - ctm.ty) / ctm.d;
4816 let (lo, hi) = if fy0 < fy1 { (fy0, fy1) } else { (fy1, fy0) };
4817 self.form_cull_y = Some((lo - 100.0, hi + 100.0));
4819 }
4820 }
4821 }
4822
4823 self.depth += 1;
4824 self.interpret_stream(&form_data)?;
4825 self.depth -= 1;
4826
4827 self.form_cull_y = saved_cull;
4828 }
4829
4830 while self.gstate_stack.len() > saved_stack_depth {
4836 self.gstate_stack.pop();
4837 }
4838
4839 self.resources = saved_resources;
4841 self.font_cache = saved_font_cache;
4842 self.current_font = saved_current_font;
4843 self.cs_index = saved_cs_index;
4844 self.content_stream_ctm = saved_content_stream_ctm;
4845 self.current_path = saved_path;
4846 self.current_point = saved_point;
4847 self.subpath_start = saved_subpath;
4848 self.mc_stack = saved_mc_stack;
4849 if let Some(saved) = self.gstate_stack.pop() {
4850 let old_clip_version = self.gstate.clip_path_version;
4851 self.gstate = saved;
4852 if !is_transparency_group && self.gstate.clip_path_version != old_clip_version {
4854 self.restore_clip_from_stack();
4855 }
4856 }
4857
4858 Ok(())
4859 }
4860
4861 fn is_transparency_group(&self, dict: &PdfDict) -> bool {
4863 let Some(group_obj) = dict.get(b"Group") else {
4864 return false;
4865 };
4866 let group_dict = match self.resolver.deref(group_obj) {
4867 Ok(PdfObj::Dict(d)) => d,
4868 _ => return false,
4869 };
4870 group_dict.get_name(b"S") == Some(b"Transparency")
4871 }
4872
4873 fn get_group_isolated(&self, dict: &PdfDict) -> bool {
4875 let Some(group_obj) = dict.get(b"Group") else {
4876 return false;
4877 };
4878 let group_dict = match self.resolver.deref(group_obj) {
4879 Ok(PdfObj::Dict(d)) => d,
4880 _ => return false,
4881 };
4882 match group_dict.get(b"I") {
4883 Some(PdfObj::Bool(b)) => *b,
4884 _ => false,
4885 }
4886 }
4887
4888 fn get_group_knockout(&self, dict: &PdfDict) -> bool {
4890 let Some(group_obj) = dict.get(b"Group") else {
4891 return false;
4892 };
4893 let group_dict = match self.resolver.deref(group_obj) {
4894 Ok(PdfObj::Dict(d)) => d,
4895 _ => return false,
4896 };
4897 match group_dict.get(b"K") {
4898 Some(PdfObj::Bool(b)) => *b,
4899 _ => false,
4900 }
4901 }
4902
4903 fn get_group_color_space(
4907 &self,
4908 dict: &PdfDict,
4909 ) -> stet_graphics::display_list::GroupColorSpace {
4910 use stet_graphics::display_list::GroupColorSpace;
4911 let Some(group_obj) = dict.get(b"Group") else {
4912 return GroupColorSpace::Inherited;
4913 };
4914 let group_dict = match self.resolver.deref(group_obj) {
4915 Ok(PdfObj::Dict(d)) => d,
4916 _ => return GroupColorSpace::Inherited,
4917 };
4918 let Some(cs_obj) = group_dict.get(b"CS") else {
4919 return GroupColorSpace::Inherited;
4920 };
4921 let cs_obj = match self.resolver.deref(cs_obj) {
4922 Ok(o) => o,
4923 Err(_) => return GroupColorSpace::Inherited,
4924 };
4925 match cs_obj {
4926 PdfObj::Name(n) => match n.as_slice() {
4927 b"DeviceGray" | b"CalGray" | b"G" => GroupColorSpace::DeviceGray,
4928 b"DeviceRGB" | b"CalRGB" | b"RGB" => GroupColorSpace::DeviceRGB,
4929 b"DeviceCMYK" | b"CMYK" => GroupColorSpace::DeviceCMYK,
4930 _ => GroupColorSpace::Inherited,
4931 },
4932 PdfObj::Array(arr) => {
4933 if let Some(PdfObj::Name(name)) = arr.first()
4935 && name.as_slice() == b"ICCBased"
4936 && let Some(stream_obj) = arr.get(1)
4937 {
4938 let stream_obj = match self.resolver.deref(stream_obj) {
4939 Ok(o) => o,
4940 Err(_) => return GroupColorSpace::Inherited,
4941 };
4942 if let PdfObj::Stream {
4943 dict: stream_dict, ..
4944 } = stream_obj
4945 && let Some(n_obj) = stream_dict.get(b"N")
4946 && let Some(n_val) = n_obj.as_int()
4947 {
4948 return match n_val {
4949 1 => GroupColorSpace::DeviceGray,
4950 3 => GroupColorSpace::DeviceRGB,
4951 4 => GroupColorSpace::DeviceCMYK,
4952 _ => GroupColorSpace::Inherited,
4953 };
4954 }
4955 }
4956 GroupColorSpace::Inherited
4957 }
4958 _ => GroupColorSpace::Inherited,
4959 }
4960 }
4961
4962 fn push_bbox_clip(&mut self, x0: f64, y0: f64, x1: f64, y1: f64) {
4964 let p0 = self.gstate.ctm.transform_point(x0, y0);
4965 let p1 = self.gstate.ctm.transform_point(x1, y0);
4966 let p2 = self.gstate.ctm.transform_point(x1, y1);
4967 let p3 = self.gstate.ctm.transform_point(x0, y1);
4968 let mut clip_path = PsPath::new();
4969 clip_path.segments.push(PathSegment::MoveTo(p0.0, p0.1));
4970 clip_path.segments.push(PathSegment::LineTo(p1.0, p1.1));
4971 clip_path.segments.push(PathSegment::LineTo(p2.0, p2.1));
4972 clip_path.segments.push(PathSegment::LineTo(p3.0, p3.1));
4973 clip_path.segments.push(PathSegment::ClosePath);
4974 self.display_list.push(DisplayElement::Clip {
4975 path: clip_path.clone(),
4976 params: ClipParams {
4977 fill_rule: FillRule::NonZeroWinding,
4978 ctm: Matrix::identity(),
4979 stroke_params: None,
4980 },
4981 });
4982 self.gstate
4983 .clip_stack
4984 .push((clip_path.clone(), FillRule::NonZeroWinding));
4985 self.gstate.clip_path = Some(clip_path);
4986 self.gstate.clip_path_version += 1;
4987 }
4988
4989 fn current_clip_bbox(&self) -> Option<[f64; 4]> {
4995 let path = self.gstate.clip_path.as_ref()?;
4996 let mut x_min = f64::INFINITY;
4997 let mut y_min = f64::INFINITY;
4998 let mut x_max = f64::NEG_INFINITY;
4999 let mut y_max = f64::NEG_INFINITY;
5000 for seg in &path.segments {
5001 let pts: &[(f64, f64)] = match seg {
5002 PathSegment::MoveTo(x, y) | PathSegment::LineTo(x, y) => &[(*x, *y)],
5003 PathSegment::CurveTo {
5004 x1,
5005 y1,
5006 x2,
5007 y2,
5008 x3,
5009 y3,
5010 } => &[(*x1, *y1), (*x2, *y2), (*x3, *y3)][..],
5011 PathSegment::ClosePath => &[],
5012 };
5013 for (x, y) in pts {
5014 x_min = x_min.min(*x);
5015 y_min = y_min.min(*y);
5016 x_max = x_max.max(*x);
5017 y_max = y_max.max(*y);
5018 }
5019 }
5020 if x_min.is_finite() && x_min < x_max && y_min < y_max {
5021 Some([x_min, y_min, x_max, y_max])
5022 } else {
5023 None
5024 }
5025 }
5026
5027 fn compute_device_bbox(&self, bbox: Option<(f64, f64, f64, f64)>) -> [f64; 4] {
5028 let Some((x0, y0, x1, y1)) = bbox else {
5029 return [0.0, 0.0, 1e9, 1e9];
5031 };
5032 let corners = [
5033 self.gstate.ctm.transform_point(x0, y0),
5034 self.gstate.ctm.transform_point(x1, y0),
5035 self.gstate.ctm.transform_point(x0, y1),
5036 self.gstate.ctm.transform_point(x1, y1),
5037 ];
5038 let mut min_x = f64::INFINITY;
5039 let mut min_y = f64::INFINITY;
5040 let mut max_x = f64::NEG_INFINITY;
5041 let mut max_y = f64::NEG_INFINITY;
5042 for (cx, cy) in &corners {
5043 min_x = min_x.min(*cx);
5044 min_y = min_y.min(*cy);
5045 max_x = max_x.max(*cx);
5046 max_y = max_y.max(*cy);
5047 }
5048 [min_x, min_y, max_x, max_y]
5049 }
5050
5051 fn handle_inline_image(&mut self, lexer: &mut Lexer) -> Result<(), PdfError> {
5053 let mut dict = PdfDict::new();
5055 loop {
5056 let tok = lexer.next_token()?;
5057 match tok {
5058 Token::Keyword(ref kw) if kw == b"ID" => break,
5059 Token::Eof => return Ok(()),
5060 Token::Name(key) => {
5061 let expanded_key = expand_inline_key(&key);
5062 let val_tok = lexer.next_token()?;
5063 let val = match val_tok {
5064 Token::Int(n) => PdfObj::Int(n),
5065 Token::Real(f) => PdfObj::Real(f),
5066 Token::Name(n) => PdfObj::Name(expand_inline_value(&n)),
5067 Token::Bool(b) => PdfObj::Bool(b),
5068 Token::LitString(s) | Token::HexString(s) => PdfObj::Str(s),
5069 Token::ArrayBegin => {
5070 let arr = Self::parse_inline_array(lexer)?;
5071 PdfObj::Array(arr)
5072 }
5073 Token::DictBegin => crate::lexer::parse_dict_body(lexer)
5074 .map(PdfObj::Dict)
5075 .unwrap_or(PdfObj::Null),
5076 _ => PdfObj::Null,
5077 };
5078 if dict.get(&expanded_key).is_none() {
5081 dict.insert(expanded_key, val);
5082 }
5083 }
5084 _ => {}
5085 }
5086 }
5087
5088 let data = lexer.data();
5092 let mut pos = lexer.pos();
5093 if pos < data.len() {
5094 if data[pos] == b'\r' {
5095 pos += 1;
5096 if pos < data.len() && data[pos] == b'\n' {
5097 pos += 1;
5098 }
5099 } else if data[pos] == b' ' || data[pos] == b'\n' {
5100 pos += 1;
5101 }
5102 }
5103
5104 let width = dict.get_int(b"Width").unwrap_or(0) as u32;
5106 let height = dict.get_int(b"Height").unwrap_or(0) as u32;
5107 let is_image_mask = matches!(dict.get(b"ImageMask"), Some(PdfObj::Bool(true)));
5108 let bpc = if is_image_mask {
5109 1
5110 } else {
5111 dict.get_int(b"BitsPerComponent").unwrap_or(8) as u32
5112 };
5113
5114 let has_filter = dict.get(b"Filter").is_some() || dict.get(b"F").is_some();
5115
5116 let outermost_is_ascii85 = dict
5120 .get(b"Filter")
5121 .or_else(|| dict.get(b"F"))
5122 .map(|f| match f {
5123 PdfObj::Name(n) => n == b"ASCII85Decode" || n == b"A85",
5124 PdfObj::Array(arr) => arr
5125 .first()
5126 .and_then(|o| o.as_name())
5127 .map(|n| n == b"ASCII85Decode" || n == b"A85")
5128 .unwrap_or(false),
5129 _ => false,
5130 })
5131 .unwrap_or(false);
5132
5133 let resolved_cs = if is_image_mask {
5134 None
5135 } else if let Some(cs_obj) = dict.get(b"ColorSpace") {
5136 let cs_resolved = if let PdfObj::Name(name) = cs_obj {
5139 let from_cache = self
5142 .cs_index
5143 .as_ref()
5144 .and_then(|idx| idx.get(name.as_slice()).cloned());
5145 let res_obj = from_cache.or_else(|| {
5146 self.resolve_resource_subdict(b"ColorSpace")
5147 .and_then(|d| d.get(name).cloned())
5148 });
5149 if let Some(ref obj) = res_obj {
5150 resolve_color_space_obj(obj, self.resolver)
5151 } else {
5152 resolve_color_space_obj(cs_obj, self.resolver)
5153 }
5154 } else {
5155 resolve_color_space_obj(cs_obj, self.resolver)
5156 };
5157 match cs_resolved {
5158 Ok(resolved) => Some(resolved),
5159 Err(_) => Some(ResolvedColorSpace::DeviceGray),
5160 }
5161 } else {
5162 Some(ResolvedColorSpace::DeviceGray)
5163 };
5164 let n_components = resolved_cs
5165 .as_ref()
5166 .map(|cs| cs.num_components() as u32)
5167 .unwrap_or(1);
5168
5169 let row_bits = width * n_components.max(1) * bpc;
5171 let row_bytes = row_bits.div_ceil(8);
5172 let expected_len = (row_bytes * height) as usize;
5173
5174 let start = pos;
5178 let search_from = if has_filter {
5179 start
5180 } else {
5181 start + expected_len
5182 };
5183 let mut end = search_from;
5186 let mut found_no_ws = false;
5187 if !has_filter {
5188 for offset in [
5190 expected_len.saturating_sub(2),
5191 expected_len.saturating_sub(1),
5192 expected_len,
5193 ] {
5194 let p = start + offset;
5195 if p + 1 < data.len()
5196 && data[p] == b'E'
5197 && data[p + 1] == b'I'
5198 && (p + 2 >= data.len() || is_delimiter_or_ws(data[p + 2]))
5199 {
5200 end = p;
5201 found_no_ws = true;
5202 break;
5203 }
5204 }
5205 }
5206 if !found_no_ws {
5207 if outermost_is_ascii85 {
5208 let mut found_a85_end = false;
5211 let mut scan = search_from;
5212 while scan + 1 < data.len() {
5213 if data[scan] == b'~' {
5214 if data[scan + 1] == b'>' {
5215 end = scan + 2;
5217 } else if is_whitespace_byte(data[scan + 1]) {
5218 let mut probe = scan + 1;
5221 while probe < data.len() && is_whitespace_byte(data[probe]) {
5222 probe += 1;
5223 }
5224 if probe + 1 < data.len()
5225 && data[probe] == b'E'
5226 && data[probe + 1] == b'I'
5227 {
5228 end = scan + 1;
5229 } else {
5230 scan += 1;
5231 continue;
5232 }
5233 } else {
5234 scan += 1;
5235 continue;
5236 }
5237 while end < data.len() && is_whitespace_byte(data[end]) {
5238 end += 1;
5239 }
5240 found_a85_end = true;
5242 found_no_ws = true;
5243 break;
5244 }
5245 scan += 1;
5246 }
5247 if !found_a85_end {
5248 while end + 2 < data.len() {
5250 if is_whitespace_byte(data[end])
5251 && data[end + 1] == b'E'
5252 && data[end + 2] == b'I'
5253 && (end + 3 >= data.len() || is_delimiter_or_ws(data[end + 3]))
5254 {
5255 break;
5256 }
5257 end += 1;
5258 }
5259 }
5260 } else {
5261 while end + 2 < data.len() {
5262 if is_whitespace_byte(data[end])
5263 && data[end + 1] == b'E'
5264 && data[end + 2] == b'I'
5265 && (end + 3 >= data.len() || is_delimiter_or_ws(data[end + 3]))
5266 {
5267 break;
5268 }
5269 end += 1;
5270 }
5271 }
5272 }
5273
5274 let sample_data = data[start..end.min(data.len())].to_vec();
5275 let skip_past = if found_no_ws {
5277 (end + 3).min(data.len())
5279 } else {
5280 (end + 4).min(data.len())
5282 };
5283 lexer.set_pos(skip_past);
5284
5285 let sample_data = if has_filter {
5287 match crate::filters::parse_filters(&dict, Some(self.resolver)) {
5288 Ok((filters, parms)) if !filters.is_empty() => {
5289 crate::filters::decode_stream(&sample_data, &filters, &parms, None)
5290 .unwrap_or(sample_data)
5291 }
5292 _ => sample_data,
5293 }
5294 } else {
5295 sample_data
5296 };
5297
5298 let polarity = if is_image_mask {
5300 if let Some(arr) = dict.get_array(b"Decode") {
5301 let vals: Vec<f64> = arr.iter().filter_map(|o| o.as_f64()).collect();
5302 vals.len() >= 2 && vals[0] > 0.5
5303 } else {
5304 false
5305 }
5306 } else {
5307 false
5308 };
5309
5310 let image_matrix =
5311 Matrix::new(width as f64, 0.0, 0.0, -(height as f64), 0.0, height as f64);
5312
5313 if is_image_mask && self.gstate.fill_shading_pattern.is_some() {
5315 let shading_box = self.gstate.fill_shading_pattern.clone().unwrap();
5316
5317 let row_bytes = width.div_ceil(8);
5320 let mut gray = vec![0u8; (width * height) as usize];
5321 for y in 0..height {
5322 for x in 0..width {
5323 let byte_idx = (y * row_bytes + x / 8) as usize;
5324 let bit_idx = 7 - (x % 8);
5325 let bit = if byte_idx < sample_data.len() {
5326 (sample_data[byte_idx] >> bit_idx) & 1
5327 } else {
5328 0
5329 };
5330 let painted = if polarity { bit == 1 } else { bit == 0 };
5333 gray[(y * width + x) as usize] = if painted { 255 } else { 0 };
5334 }
5335 }
5336
5337 let mut mask_dl = DisplayList::new();
5339 mask_dl.push(DisplayElement::Image {
5340 sample_data: Arc::new(gray),
5341 params: ImageParams {
5342 width,
5343 height,
5344 color_space: ImageColorSpace::DeviceGray,
5345 bits_per_component: 8,
5346 ctm: self.gstate.ctm,
5347 image_matrix,
5348 interpolate: false,
5349 mask_color: None,
5350 alpha: 1.0,
5351 blend_mode: 0,
5352 overprint: false,
5353 overprint_mode: 0,
5354 opm_paired: false,
5355 painted_channels: 0,
5356 },
5357 });
5358
5359 let mut content_dl = DisplayList::new();
5361 for elem in shading_box.0.elements() {
5362 content_dl.push(elem.clone());
5363 }
5364
5365 let corners = [
5367 self.gstate.ctm.transform_point(0.0, 0.0),
5368 self.gstate.ctm.transform_point(width as f64, 0.0),
5369 self.gstate.ctm.transform_point(0.0, height as f64),
5370 self.gstate.ctm.transform_point(width as f64, height as f64),
5371 ];
5372 let x_min = corners.iter().map(|c| c.0).fold(f64::INFINITY, f64::min);
5373 let y_min = corners.iter().map(|c| c.1).fold(f64::INFINITY, f64::min);
5374 let x_max = corners
5375 .iter()
5376 .map(|c| c.0)
5377 .fold(f64::NEG_INFINITY, f64::max);
5378 let y_max = corners
5379 .iter()
5380 .map(|c| c.1)
5381 .fold(f64::NEG_INFINITY, f64::max);
5382
5383 let parent_clip_bbox = self.current_clip_bbox();
5384 self.display_list.push(DisplayElement::SoftMasked {
5385 mask: mask_dl,
5386 content: content_dl,
5387 params: SoftMaskParams {
5388 subtype: SoftMaskSubtype::Luminosity,
5389 bbox: [x_min, y_min, x_max, y_max],
5390 backdrop_color: None,
5391 transfer_invert: false,
5392 has_nested_mask_scope: false,
5393 parent_clip_bbox,
5394 },
5395 mask_cache: Arc::new(Mutex::new(None)),
5396 });
5397 return Ok(());
5398 }
5399
5400 let color_space = if is_image_mask {
5401 ImageColorSpace::Mask {
5402 color: self.gstate.fill_color.clone(),
5403 polarity,
5404 }
5405 } else {
5406 to_image_color_space(resolved_cs.as_ref().unwrap())
5407 };
5408
5409 let is_indexed = matches!(&color_space, ImageColorSpace::Indexed { .. });
5411 let sample_data = if !is_image_mask && bpc != 8 && bpc != 0 {
5412 expand_bits_to_bytes(&sample_data, bpc, width, height, n_components, is_indexed)
5413 } else {
5414 sample_data
5415 };
5416
5417 if !is_image_mask {
5420 if let Some(ref rcs) = resolved_cs {
5421 register_icc_profile(rcs, &mut self.icc_cache);
5422 }
5423 }
5424
5425 self.display_list.push(DisplayElement::Image {
5426 sample_data: Arc::new(sample_data),
5427 params: ImageParams {
5428 width,
5429 height,
5430 color_space,
5431 bits_per_component: 8,
5432 ctm: self.gstate.ctm,
5433 image_matrix,
5434 interpolate: false,
5435 mask_color: None,
5436 alpha: self.gstate.fill_alpha,
5437 blend_mode: self.gstate.blend_mode,
5438 overprint: self.gstate.overprint,
5439 overprint_mode: self.gstate.overprint_mode,
5440 opm_paired: self.gstate.opm_paired,
5441 painted_channels: resolved_cs
5442 .as_ref()
5443 .map(painted_channels_for_cs)
5444 .unwrap_or(self.gstate.fill_painted_channels),
5445 },
5446 });
5447
5448 Ok(())
5449 }
5450
5451 fn apply_ext_gstate(&mut self, name: &[u8]) -> Result<(), PdfError> {
5453 let ext_dict = self
5454 .resolve_resource_subdict(b"ExtGState")
5455 .ok_or(PdfError::Other("no ExtGState resources".into()))?;
5456 let gs_ref = ext_dict.get(name).ok_or_else(|| {
5457 PdfError::Other(format!(
5458 "ExtGState /{} not found",
5459 String::from_utf8_lossy(name)
5460 ))
5461 })?;
5462 let gs_obj = self.resolver.deref(gs_ref)?;
5463 let gs_dict = gs_obj
5464 .as_dict()
5465 .ok_or(PdfError::Other("ExtGState is not a dict".into()))?;
5466
5467 if let Some(lw) = gs_dict.get_f64(b"LW") {
5469 self.gstate.line_width = lw;
5470 }
5471 if let Some(lc) = gs_dict.get_int(b"LC")
5472 && let Some(cap) = LineCap::from_i32(lc as i32)
5473 {
5474 self.gstate.line_cap = cap;
5475 }
5476 if let Some(lj) = gs_dict.get_int(b"LJ")
5477 && let Some(join) = LineJoin::from_i32(lj as i32)
5478 {
5479 self.gstate.line_join = join;
5480 }
5481 if let Some(ml) = gs_dict.get_f64(b"ML") {
5482 self.gstate.miter_limit = ml;
5483 }
5484 if let Some(fl) = gs_dict.get_f64(b"FL") {
5485 self.gstate.flatness = fl;
5486 }
5487 if let Some(PdfObj::Bool(sa)) = gs_dict.get(b"SA") {
5488 self.gstate.stroke_adjust = *sa;
5489 }
5490 let has_opm = gs_dict.get(b"OPM").is_some();
5493 if let Some(opm) = gs_dict.get_int(b"OPM") {
5494 self.gstate.overprint_mode = opm as i32;
5495 }
5496 let has_op_flag = gs_dict.get(b"OP").is_some() || gs_dict.get(b"op").is_some();
5497 if self.overprint_enabled {
5498 if let Some(PdfObj::Bool(op)) = gs_dict.get(b"OP") {
5499 self.gstate.overprint = *op;
5500 self.gstate.overprint_stroke = *op;
5502 }
5503 if let Some(PdfObj::Bool(op)) = gs_dict.get(b"op") {
5504 self.gstate.overprint = *op;
5505 }
5506 }
5507 if has_opm && has_op_flag {
5516 self.gstate.opm_paired = true;
5517 } else if has_opm || has_op_flag {
5518 self.gstate.opm_paired = false;
5519 }
5520 if let Some(ca) = gs_dict.get_f64(b"CA") {
5521 self.gstate.stroke_alpha = ca;
5522 }
5523 if let Some(ca) = gs_dict.get_f64(b"ca") {
5524 self.gstate.fill_alpha = ca;
5525 }
5526
5527 if let Some(bm) = gs_dict.get(b"BM") {
5529 let bm = self.resolver.deref(bm).unwrap_or_else(|_| bm.clone());
5530 match &bm {
5531 PdfObj::Name(name) => {
5532 self.gstate.blend_mode = blend_mode_from_name(name);
5533 }
5534 PdfObj::Array(arr) => {
5535 for obj in arr {
5536 if let PdfObj::Name(name) = obj {
5537 let mode = blend_mode_from_name(name);
5538 if mode != 0 || name.as_slice() == b"Normal" {
5539 self.gstate.blend_mode = mode;
5540 break;
5541 }
5542 }
5543 }
5544 }
5545 _ => {}
5546 }
5547 }
5548
5549 if let Some(d_arr) = gs_dict.get_array(b"D")
5551 && d_arr.len() == 2
5552 && let (Some(arr), Some(offset)) = (d_arr[0].as_array(), d_arr[1].as_f64())
5553 {
5554 let array: Vec<f64> = arr.iter().filter_map(|o| o.as_f64()).collect();
5555 self.gstate.dash_pattern = DashPattern { array, offset };
5556 }
5557
5558 if let Some(font_arr) = gs_dict.get_array(b"Font")
5560 && font_arr.len() == 2
5561 && let Some(size) = font_arr[1].as_f64()
5562 {
5563 self.gstate.font_size = size;
5564 let font_ref = &font_arr[0];
5566 let cache_key = if let PdfObj::Ref(obj_num, _) = font_ref {
5568 format!("__gs_font_{obj_num}").into_bytes()
5569 } else {
5570 b"__gs_font_inline".to_vec()
5571 };
5572 if let Some(cached) = self.font_cache.get(&cache_key) {
5573 self.current_font = Some(Arc::clone(cached));
5574 } else {
5575 match font::resolve_font(self.resolver, font_ref, self.font_provider.as_ref()) {
5576 Ok(font) => {
5577 let arc = Arc::new(font);
5578 self.font_cache.insert(cache_key, Arc::clone(&arc));
5579 self.current_font = Some(arc);
5580 }
5581 Err(e) => {
5582 eprintln!("warning: ExtGState Font: {e}");
5583 }
5584 }
5585 }
5586 }
5587
5588 if let Some(tr_obj) = gs_dict.get(b"TR2").or_else(|| gs_dict.get(b"TR")) {
5590 self.gstate.transfer = self.parse_transfer_function(tr_obj)?;
5591 }
5592
5593 if let Some(smask_obj) = gs_dict.get(b"SMask") {
5595 let smask_obj = self.resolver.deref(smask_obj)?;
5596 match &smask_obj {
5597 PdfObj::Name(n) if n.as_slice() == b"None" => {
5598 self.flush_soft_mask();
5599 self.gstate.soft_mask = None;
5600 }
5601 PdfObj::Dict(d) => {
5602 self.flush_soft_mask();
5603 match self.resolve_soft_mask(d) {
5604 Ok(sm) => {
5605 let start_index = self.display_list.len();
5606 self.gstate.soft_mask = Some(sm.clone());
5607 self.gstate.smask_gen += 1;
5608 self.soft_mask_scope = Some(SoftMaskScope {
5609 start_index,
5610 mask: sm,
5611 });
5612 }
5613 Err(e) => {
5614 eprintln!("warning: SMask resolve error: {}", e);
5615 }
5616 }
5617 }
5618 _ => {}
5619 }
5620 }
5621
5622 Ok(())
5623 }
5624
5625 fn flush_soft_mask(&mut self) {
5627 if let Some(scope) = self.soft_mask_scope.take()
5628 && self.display_list.len() > scope.start_index
5629 {
5630 let content = self.display_list.split_off(scope.start_index);
5631
5632 let content_bbox = self.content_paint_bbox(&content);
5670 let drop_shadow_skip = scope.mask.backdrop_color == Some([0.0, 0.0, 0.0])
5671 && content_bbox
5672 .map(|c| !bboxes_overlap_substantially(&c, &scope.mask.bbox, 2.0))
5673 .unwrap_or(false);
5674 let skip = scope.mask.mask_list.is_empty() || drop_shadow_skip;
5675 if skip {
5676 for elem in content.into_elements() {
5677 self.display_list.push(elem);
5678 }
5679 } else {
5680 let clip_replay: Vec<DisplayElement> = content
5685 .elements()
5686 .iter()
5687 .filter(|e| matches!(e, DisplayElement::Clip { .. } | DisplayElement::InitClip))
5688 .cloned()
5689 .collect();
5690 let parent_clip_bbox = self.current_clip_bbox();
5691 self.display_list.push(DisplayElement::SoftMasked {
5692 mask: scope.mask.mask_list,
5693 content,
5694 params: SoftMaskParams {
5695 subtype: scope.mask.subtype,
5696 bbox: scope.mask.bbox,
5697 backdrop_color: scope.mask.backdrop_color,
5698 transfer_invert: scope.mask.transfer_invert,
5699 has_nested_mask_scope: scope.mask.has_nested_mask_scope,
5700 parent_clip_bbox,
5701 },
5702 mask_cache: Arc::new(Mutex::new(None)),
5703 });
5704 for elem in clip_replay {
5705 self.display_list.push(elem);
5706 }
5707 }
5708 }
5709 }
5710
5711 fn resolve_group_cs_comps(&self, form_dict: &PdfDict) -> usize {
5714 let cs_name_to_comps = |cs: &[u8]| -> usize {
5715 match cs {
5716 b"DeviceGray" => 1,
5717 b"DeviceRGB" => 3,
5718 b"DeviceCMYK" => 4,
5719 _ => 0,
5720 }
5721 };
5722
5723 let grp_obj = match form_dict.get(b"Group") {
5724 Some(obj) => obj,
5725 None => return 0,
5726 };
5727
5728 let resolved_grp;
5730 let grp = if let Some(d) = grp_obj.as_dict() {
5731 d
5732 } else if let Ok(r) = self.resolver.deref(grp_obj) {
5733 resolved_grp = r;
5734 match resolved_grp.as_dict() {
5735 Some(d) => d,
5736 None => return 0,
5737 }
5738 } else {
5739 return 0;
5740 };
5741
5742 if let Some(cs) = grp.get_name(b"CS") {
5744 return cs_name_to_comps(cs);
5745 }
5746 if let Some(cs_obj) = grp.get(b"CS") {
5747 if let Ok(cs_resolved) = self.resolver.deref(cs_obj) {
5748 if let Some(cs) = cs_resolved.as_name() {
5749 return cs_name_to_comps(cs);
5750 }
5751 }
5752 }
5753 0
5754 }
5755
5756 fn content_paint_bbox(&self, content: &DisplayList) -> Option<[f64; 4]> {
5767 let mut x_min = f64::INFINITY;
5768 let mut y_min = f64::INFINITY;
5769 let mut x_max = f64::NEG_INFINITY;
5770 let mut y_max = f64::NEG_INFINITY;
5771 let mut grow = |bx: [f64; 4]| {
5772 x_min = x_min.min(bx[0].min(bx[2]));
5773 y_min = y_min.min(bx[1].min(bx[3]));
5774 x_max = x_max.max(bx[0].max(bx[2]));
5775 y_max = y_max.max(bx[1].max(bx[3]));
5776 };
5777 for elem in content.elements() {
5778 match elem {
5779 DisplayElement::Fill { path, .. }
5780 | DisplayElement::Stroke { path, .. }
5781 | DisplayElement::Clip { path, .. } => {
5782 let mut px_min = f64::INFINITY;
5783 let mut py_min = f64::INFINITY;
5784 let mut px_max = f64::NEG_INFINITY;
5785 let mut py_max = f64::NEG_INFINITY;
5786 for seg in &path.segments {
5787 let pts: &[(f64, f64)] = match seg {
5788 PathSegment::MoveTo(x, y) | PathSegment::LineTo(x, y) => &[(*x, *y)],
5789 PathSegment::CurveTo {
5790 x1,
5791 y1,
5792 x2,
5793 y2,
5794 x3,
5795 y3,
5796 } => &[(*x1, *y1), (*x2, *y2), (*x3, *y3)][..],
5797 PathSegment::ClosePath => &[],
5798 };
5799 for (x, y) in pts {
5800 px_min = px_min.min(*x);
5801 py_min = py_min.min(*y);
5802 px_max = px_max.max(*x);
5803 py_max = py_max.max(*y);
5804 }
5805 }
5806 if px_min.is_finite() && px_min < px_max && py_min < py_max {
5807 grow([px_min, py_min, px_max, py_max]);
5808 }
5809 }
5810 DisplayElement::Image { params, .. } => {
5811 let ctm = ¶ms.ctm;
5814 let corners = [
5815 ctm.transform_point(0.0, 0.0),
5816 ctm.transform_point(1.0, 0.0),
5817 ctm.transform_point(0.0, 1.0),
5818 ctm.transform_point(1.0, 1.0),
5819 ];
5820 let mut ix_min = f64::INFINITY;
5821 let mut iy_min = f64::INFINITY;
5822 let mut ix_max = f64::NEG_INFINITY;
5823 let mut iy_max = f64::NEG_INFINITY;
5824 for (cx, cy) in &corners {
5825 ix_min = ix_min.min(*cx);
5826 iy_min = iy_min.min(*cy);
5827 ix_max = ix_max.max(*cx);
5828 iy_max = iy_max.max(*cy);
5829 }
5830 grow([ix_min, iy_min, ix_max, iy_max]);
5831 }
5832 DisplayElement::Group { params, .. } => {
5833 grow(params.bbox);
5834 }
5835 DisplayElement::SoftMasked { params, .. } => {
5836 grow(params.bbox);
5837 }
5838 _ => {} }
5840 }
5841 if x_min.is_finite() && x_min < x_max && y_min < y_max {
5842 Some([x_min, y_min, x_max, y_max])
5843 } else {
5844 None
5845 }
5846 }
5847
5848 fn resolve_soft_mask(&mut self, dict: &PdfDict) -> Result<graphics_state::SoftMask, PdfError> {
5850 let subtype = match dict.get_name(b"S") {
5852 Some(b"Alpha") => SoftMaskSubtype::Alpha,
5853 _ => SoftMaskSubtype::Luminosity,
5854 };
5855
5856 let g_ref = dict
5858 .get(b"G")
5859 .ok_or_else(|| PdfError::Other("SMask missing /G".into()))?;
5860 let g_obj = self.resolver.deref(g_ref)?;
5861 let g_dict = g_obj
5862 .as_dict()
5863 .ok_or_else(|| PdfError::Other("SMask /G is not a dict".into()))?;
5864
5865 let bbox_tuple = if let Some(vals) = deref_num_array(self.resolver, g_dict, b"BBox") {
5867 if vals.len() == 4 {
5868 Some((vals[0], vals[1], vals[2], vals[3]))
5869 } else {
5870 None
5871 }
5872 } else {
5873 None
5874 };
5875
5876 let form_matrix = if let Some(vals) = deref_num_array(self.resolver, g_dict, b"Matrix") {
5878 if vals.len() == 6 {
5879 Matrix::new(vals[0], vals[1], vals[2], vals[3], vals[4], vals[5])
5880 } else {
5881 Matrix::identity()
5882 }
5883 } else {
5884 Matrix::identity()
5885 };
5886
5887 let form_resources = if let Some(res_obj) = g_dict.get(b"Resources") {
5889 match self.resolver.deref(res_obj)? {
5890 PdfObj::Dict(d) => d,
5891 _ => self.resources.clone(),
5892 }
5893 } else {
5894 self.resources.clone()
5895 };
5896
5897 let form_data = self.resolver.stream_data_from_obj(g_ref)?;
5899
5900 self.gstate_stack.push(self.gstate.clone());
5903 let saved_resources = std::mem::replace(&mut self.resources, form_resources);
5904 let saved_font_cache = std::mem::take(&mut self.font_cache);
5905 let saved_current_font2 = self.current_font.take();
5906 let saved_cs_index2 = self.cs_index.take();
5907 let saved_display_list = std::mem::replace(&mut self.display_list, DisplayList::new());
5908 let saved_scope = self.soft_mask_scope.take();
5909 let saved_content_stream_ctm = self.content_stream_ctm;
5910 let saved_mc_stack = std::mem::take(&mut self.mc_stack);
5911
5912 self.gstate.ctm = self.gstate.ctm.concat(&form_matrix);
5914 self.content_stream_ctm = self.gstate.ctm;
5917
5918 self.gstate.fill_alpha = 1.0;
5922 self.gstate.stroke_alpha = 1.0;
5923 self.gstate.soft_mask = None;
5924
5925 let device_bbox = self.compute_device_bbox(bbox_tuple);
5929
5930 if let Some((x0, y0, x1, y1)) = bbox_tuple {
5932 self.push_bbox_clip(x0, y0, x1, y1);
5933 }
5934
5935 let saved_cmyk_hash = self.icc_cache.suspend_default_cmyk();
5941
5942 let saved_nested_mask_flush_count = self.nested_mask_flush_count;
5943 self.depth += 1;
5944 let _ = self.interpret_stream(&form_data);
5945 self.depth -= 1;
5946
5947 self.icc_cache.restore_default_cmyk(saved_cmyk_hash);
5948
5949 let has_nested_mask_scope = self.nested_mask_flush_count > saved_nested_mask_flush_count;
5954
5955 self.flush_soft_mask();
5957
5958 let mask_list = std::mem::replace(&mut self.display_list, saved_display_list);
5959 self.soft_mask_scope = saved_scope;
5960 self.content_stream_ctm = saved_content_stream_ctm;
5961 self.resources = saved_resources;
5962 self.font_cache = saved_font_cache;
5963 self.current_font = saved_current_font2;
5964 self.cs_index = saved_cs_index2;
5965 self.mc_stack = saved_mc_stack;
5966 if let Some(saved) = self.gstate_stack.pop() {
5967 self.gstate = saved;
5968 }
5969
5970 let group_n_comps = self.resolve_group_cs_comps(g_dict);
5974
5975 let backdrop_color = if let Some(bc_obj) = dict.get(b"BC") {
5976 let bc_resolved = self.resolver.deref(bc_obj).ok();
5977 let bc_arr = bc_resolved
5978 .as_ref()
5979 .and_then(|o| o.as_array())
5980 .or_else(|| bc_obj.as_array());
5981 if let Some(arr) = bc_arr {
5982 let vals: Vec<f64> = arr.iter().filter_map(|o| o.as_f64()).collect();
5983 if group_n_comps == 1 && !vals.is_empty() {
5984 Some([vals[0], vals[0], vals[0]])
5986 } else if group_n_comps == 4 && vals.len() >= 4 {
5987 let c = vals[0];
5989 let m = vals[1];
5990 let y = vals[2];
5991 let k = vals[3];
5992 Some([
5993 (1.0 - c) * (1.0 - k),
5994 (1.0 - m) * (1.0 - k),
5995 (1.0 - y) * (1.0 - k),
5996 ])
5997 } else if vals.len() >= 3 {
5998 Some([vals[0], vals[1], vals[2]])
5999 } else if vals.len() == 1 {
6000 Some([vals[0], vals[0], vals[0]])
6001 } else {
6002 None
6003 }
6004 } else {
6005 None
6006 }
6007 } else {
6008 if group_n_comps == 4 {
6012 Some([1.0, 1.0, 1.0])
6013 } else {
6014 None
6015 }
6016 };
6017
6018 let transfer_invert = if let Some(tr_obj) = dict.get(b"TR") {
6023 if let Ok(tr_data) = self.resolver.stream_data_from_obj(tr_obj) {
6024 let trimmed: Vec<u8> = tr_data
6025 .iter()
6026 .copied()
6027 .filter(|b| !b.is_ascii_whitespace())
6028 .collect();
6029 let s = String::from_utf8_lossy(&trimmed);
6030 s.contains("exchsub")
6031 } else {
6032 false
6033 }
6034 } else {
6035 false
6036 };
6037
6038 let effective_bbox = if let Some(bc) = &backdrop_color {
6043 let bc_lum = 0.2126 * bc[0] + 0.7152 * bc[1] + 0.0722 * bc[2];
6044 let bc_byte = (bc_lum * 255.0 + 0.5) as u8;
6045 let effective = if transfer_invert {
6049 255 - bc_byte
6050 } else {
6051 bc_byte
6052 };
6053 if effective > 0 {
6054 [0.0, 0.0, 1e9, 1e9]
6055 } else {
6056 device_bbox
6057 }
6058 } else {
6059 device_bbox
6060 };
6061
6062 Ok(graphics_state::SoftMask {
6063 mask_list,
6064 subtype,
6065 bbox: effective_bbox,
6066 backdrop_color,
6067 transfer_invert,
6068 has_nested_mask_scope,
6069 })
6070 }
6071
6072 fn parse_transfer_function(
6077 &self,
6078 obj: &PdfObj,
6079 ) -> Result<stet_graphics::device::TransferState, PdfError> {
6080 use crate::resources::function::PdfFunction;
6081 use stet_graphics::device::TransferState;
6082
6083 let obj = self.resolver.deref(obj)?;
6084
6085 if let Some(name) = obj.as_name()
6087 && (name == b"Identity" || name == b"Default")
6088 {
6089 return Ok(TransferState::default());
6090 }
6091
6092 if let PdfObj::Array(arr) = &obj
6094 && arr.len() == 4
6095 {
6096 let mut tables: [Option<Arc<Vec<f64>>>; 4] = Default::default();
6097 for (i, fn_obj) in arr.iter().enumerate() {
6098 let fn_obj = self.resolver.deref(fn_obj)?;
6099 if let Some(name) = fn_obj.as_name()
6100 && (name == b"Identity" || name == b"Default")
6101 {
6102 continue; }
6104 if let Ok(func) = PdfFunction::parse(&fn_obj, self.resolver) {
6105 tables[i] = Some(Arc::new(sample_transfer_function(&func)));
6106 }
6107 }
6108 return Ok(TransferState {
6109 gray: None,
6110 color: Some(tables),
6111 });
6112 }
6113
6114 if let Ok(func) = PdfFunction::parse(&obj, self.resolver) {
6116 let table = Arc::new(sample_transfer_function(&func));
6117 return Ok(TransferState {
6118 gray: Some(table),
6119 color: None,
6120 });
6121 }
6122
6123 Ok(TransferState::default())
6124 }
6125
6126 fn op_sh(&mut self) -> Result<(), PdfError> {
6129 let name = self
6130 .operand_stack
6131 .last()
6132 .and_then(|o| o.as_name())
6133 .ok_or(PdfError::Other("sh: expected name".into()))?
6134 .to_vec();
6135
6136 let shading_dict = self
6137 .resolve_resource_subdict(b"Shading")
6138 .ok_or(PdfError::Other("no Shading resources".into()))?;
6139 let sh_ref = shading_dict.get(&name).ok_or_else(|| {
6140 PdfError::Other(format!(
6141 "Shading /{} not found",
6142 String::from_utf8_lossy(&name)
6143 ))
6144 })?;
6145 let sh_ref_clone = sh_ref.clone();
6146 let sh_obj = self.resolver.deref(sh_ref)?;
6147 let sh_dict = sh_obj
6148 .as_dict()
6149 .ok_or(PdfError::Other("Shading is not a dict".into()))?;
6150
6151 crate::resources::shading::handle_shading(
6152 &sh_ref_clone,
6153 sh_dict,
6154 &self.gstate,
6155 self.resolver,
6156 &mut self.display_list,
6157 &mut self.icc_cache,
6158 )
6159 }
6160
6161 fn handle_pattern_fill(&mut self) -> Result<(), PdfError> {
6164 let name = self
6165 .operand_stack
6166 .last()
6167 .and_then(|o| o.as_name())
6168 .ok_or(PdfError::Other("pattern: expected name".into()))?
6169 .to_vec();
6170
6171 self.extract_pattern_underlying_color(false)?;
6175
6176 let pattern_dict = self
6178 .resolve_resource_subdict(b"Pattern")
6179 .ok_or(PdfError::Other("no Pattern resources".into()))?;
6180 let pat_ref = pattern_dict.get(&name).ok_or_else(|| {
6181 PdfError::Other(format!(
6182 "Pattern /{} not found",
6183 String::from_utf8_lossy(&name)
6184 ))
6185 })?;
6186 let pat_obj = self.resolver.deref(pat_ref)?;
6187 let pat_dict = pat_obj
6188 .as_dict()
6189 .ok_or(PdfError::Other("Pattern is not a dict".into()))?;
6190 let pattern_type = pat_dict.get_int(b"PatternType").unwrap_or(1) as i32;
6191
6192 if pattern_type == 2 {
6193 let shading_dl = self.resolve_shading_pattern(pat_dict)?;
6194 self.gstate.fill_pattern = None;
6195 self.gstate.fill_shading_pattern = Some(Box::new(ShadingPatternDL(shading_dl)));
6196 } else {
6197 let pattern = self.resolve_pattern(&name)?;
6198 self.gstate.fill_shading_pattern = None;
6199 self.gstate.fill_pattern = Some(pattern);
6200 }
6201 Ok(())
6202 }
6203
6204 fn handle_pattern_stroke(&mut self) -> Result<(), PdfError> {
6205 let name = self
6206 .operand_stack
6207 .last()
6208 .and_then(|o| o.as_name())
6209 .ok_or(PdfError::Other("pattern: expected name".into()))?
6210 .to_vec();
6211
6212 self.extract_pattern_underlying_color(true)?;
6214
6215 let pattern_dict = self
6217 .resolve_resource_subdict(b"Pattern")
6218 .ok_or(PdfError::Other("no Pattern resources".into()))?;
6219 let pat_ref = pattern_dict.get(&name).ok_or_else(|| {
6220 PdfError::Other(format!(
6221 "Pattern /{} not found",
6222 String::from_utf8_lossy(&name)
6223 ))
6224 })?;
6225 let pat_obj = self.resolver.deref(pat_ref)?;
6226 let pat_dict = pat_obj
6227 .as_dict()
6228 .ok_or(PdfError::Other("Pattern is not a dict".into()))?;
6229 let pattern_type = pat_dict.get_int(b"PatternType").unwrap_or(1) as i32;
6230
6231 if pattern_type == 2 {
6232 let shading_dl = self.resolve_shading_pattern(pat_dict)?;
6233 self.gstate.stroke_pattern = None;
6234 self.gstate.stroke_shading_pattern = Some(Box::new(ShadingPatternDL(shading_dl)));
6235 } else {
6236 let pattern = self.resolve_pattern(&name)?;
6237 self.gstate.stroke_shading_pattern = None;
6238 self.gstate.stroke_pattern = Some(pattern);
6239 }
6240 Ok(())
6241 }
6242
6243 fn extract_pattern_underlying_color(&mut self, is_stroke: bool) -> Result<(), PdfError> {
6248 let cs_ref = if is_stroke {
6250 &self.gstate.stroke_color_space
6251 } else {
6252 &self.gstate.fill_color_space
6253 };
6254 let cs_name = match cs_ref {
6255 ColorSpaceRef::Named(n) => n.clone(),
6256 _ => return Ok(()),
6257 };
6258
6259 let cs_obj_opt: Option<crate::objects::PdfObj> = self
6263 .cs_index
6264 .as_ref()
6265 .and_then(|idx| idx.get(cs_name.as_slice()).cloned())
6266 .or_else(|| {
6267 let cs_dict = self
6268 .resources
6269 .get(b"ColorSpace")
6270 .and_then(|obj| match obj {
6271 PdfObj::Dict(_) => Some(obj.as_dict().unwrap().clone()),
6272 PdfObj::Ref(n, g) => self.resolver.resolve(*n, *g).ok()?.as_dict().cloned(),
6273 _ => None,
6274 })?;
6275 cs_dict.get(&cs_name).cloned()
6276 });
6277 let cs_obj = match cs_obj_opt {
6278 Some(obj) => obj.clone(),
6279 None => return Ok(()),
6280 };
6281 let cs_resolved = self.resolver.deref(&cs_obj)?;
6282 let arr = match &cs_resolved {
6283 PdfObj::Array(a) if a.len() >= 2 => a,
6284 _ => return Ok(()),
6285 };
6286 if arr[0].as_name() != Some(b"Pattern") {
6288 return Ok(());
6289 }
6290 let underlying_cs = color_space::resolve_color_space_obj(&arr[1], self.resolver)?;
6292 let n = underlying_cs.num_components();
6293 if n == 0 {
6294 return Ok(());
6295 }
6296
6297 let stack_len = self.operand_stack.len();
6300 if stack_len < n + 1 {
6301 return Ok(()); }
6303 let mut nums = Vec::with_capacity(n);
6305 let base = stack_len - 1 - n;
6306 for i in 0..n {
6307 nums.push(self.operand_stack[base + i].as_f64().unwrap_or(0.0));
6308 }
6309 let color = color_space::components_to_device_color_icc(
6310 &underlying_cs,
6311 &nums,
6312 Some(&mut self.icc_cache),
6313 );
6314 if is_stroke {
6315 self.gstate.stroke_color = color;
6316 } else {
6317 self.gstate.fill_color = color;
6318 }
6319 Ok(())
6320 }
6321
6322 fn resolve_pattern(&mut self, name: &[u8]) -> Result<TilingPattern, PdfError> {
6323 let pattern_dict = self
6324 .resolve_resource_subdict(b"Pattern")
6325 .ok_or(PdfError::Other("no Pattern resources".into()))?;
6326 let pat_ref = pattern_dict.get(name).ok_or_else(|| {
6327 PdfError::Other(format!(
6328 "Pattern /{} not found",
6329 String::from_utf8_lossy(name)
6330 ))
6331 })?;
6332
6333 if let PdfObj::Ref(obj_num, gen_num) = pat_ref {
6337 if let Some(cached) = self.pattern_cache.get(&(*obj_num, *gen_num)) {
6338 return Ok(cached.clone());
6339 }
6340 }
6341
6342 let pat_ref_clone = pat_ref.clone();
6343 let pat_obj = self.resolver.deref(pat_ref)?;
6344 let pat_dict = pat_obj
6345 .as_dict()
6346 .ok_or(PdfError::Other("Pattern is not a dict".into()))?;
6347
6348 let pattern_type = pat_dict.get_int(b"PatternType").unwrap_or(1) as i32;
6349
6350 let result = match pattern_type {
6351 1 => self.resolve_tiling_pattern(&pat_ref_clone, pat_dict),
6352 _ => Err(PdfError::Other(format!(
6353 "Unsupported PatternType {pattern_type}"
6354 ))),
6355 }?;
6356
6357 if let PdfObj::Ref(obj_num, gen_num) = pat_ref {
6358 self.pattern_cache
6359 .insert((*obj_num, *gen_num), result.clone());
6360 }
6361
6362 Ok(result)
6363 }
6364
6365 fn resolve_tiling_pattern(
6366 &mut self,
6367 pat_obj: &PdfObj,
6368 pat_dict: &PdfDict,
6369 ) -> Result<TilingPattern, PdfError> {
6370 if self.depth >= 20 {
6371 return Err(PdfError::Other("pattern recursion limit".into()));
6372 }
6373 let paint_type = pat_dict.get_int(b"PaintType").unwrap_or(1) as i32;
6374
6375 let bbox = deref_num_array(self.resolver, pat_dict, b"BBox")
6376 .map(|v| {
6377 if v.len() >= 4 {
6378 [v[0], v[1], v[2], v[3]]
6379 } else {
6380 [0.0, 0.0, 1.0, 1.0]
6381 }
6382 })
6383 .unwrap_or([0.0, 0.0, 1.0, 1.0]);
6384
6385 let x_step = pat_dict.get_f64(b"XStep").unwrap_or(bbox[2] - bbox[0]);
6386 let y_step = pat_dict.get_f64(b"YStep").unwrap_or(bbox[3] - bbox[1]);
6387
6388 let pattern_matrix = deref_num_array(self.resolver, pat_dict, b"Matrix")
6389 .map(|v| {
6390 if v.len() >= 6 {
6391 Matrix::new(v[0], v[1], v[2], v[3], v[4], v[5])
6392 } else {
6393 Matrix::identity()
6394 }
6395 })
6396 .unwrap_or_else(Matrix::identity);
6397
6398 let pattern_resources = if let Some(res_ref) = pat_dict.get(b"Resources") {
6399 match self.resolver.deref(res_ref)? {
6400 PdfObj::Dict(d) => d,
6401 _ => self.resources.clone(),
6402 }
6403 } else {
6404 self.resources.clone()
6405 };
6406
6407 let pattern_data = self.resolver.stream_data_from_obj(pat_obj)?;
6408
6409 let combined_matrix = self.content_stream_ctm.concat(&pattern_matrix);
6414
6415 self.gstate_stack.push(self.gstate.clone());
6420 let saved_resources = std::mem::replace(&mut self.resources, pattern_resources);
6421 let saved_display_list = std::mem::take(&mut self.display_list);
6422 let saved_content_stream_ctm = self.content_stream_ctm;
6423 let saved_path = std::mem::take(&mut self.current_path);
6424 let saved_point = self.current_point.take();
6425 let saved_subpath = self.subpath_start.take();
6426 let saved_mc_stack = std::mem::take(&mut self.mc_stack);
6427
6428 self.gstate.ctm = Matrix::identity();
6429 self.content_stream_ctm = Matrix::identity();
6430 self.gstate.clip_path = None;
6431 self.gstate.clip_path_version = 0;
6432 self.gstate.clip_stack.clear();
6433 self.gstate.fill_pattern = None;
6436 self.gstate.stroke_pattern = None;
6437 self.gstate.fill_shading_pattern = None;
6438 self.gstate.stroke_shading_pattern = None;
6439 self.gstate.text_rendering_mode = 0;
6442
6443 self.depth += 1;
6444 let _ = self.interpret_stream(&pattern_data);
6445 self.depth -= 1;
6446
6447 self.flush_soft_mask();
6449
6450 let tile_display_list = std::mem::replace(&mut self.display_list, saved_display_list);
6451 self.content_stream_ctm = saved_content_stream_ctm;
6452 self.resources = saved_resources;
6453 self.current_path = saved_path;
6454 self.current_point = saved_point;
6455 self.subpath_start = saved_subpath;
6456 self.mc_stack = saved_mc_stack;
6457 if let Some(saved) = self.gstate_stack.pop() {
6458 self.gstate = saved;
6459 }
6460
6461 Ok(TilingPattern {
6462 tile: tile_display_list,
6463 bbox,
6464 x_step,
6465 y_step,
6466 pattern_matrix: combined_matrix,
6467 paint_type,
6468 pattern_id: 0,
6469 flip_tile_y: false,
6470 })
6471 }
6472
6473 fn resolve_shading_pattern(&mut self, pat_dict: &PdfDict) -> Result<DisplayList, PdfError> {
6477 let sh_ref = pat_dict
6478 .get(b"Shading")
6479 .ok_or(PdfError::Other("shading pattern missing /Shading".into()))?;
6480 let sh_ref_clone = sh_ref.clone();
6481 let sh_obj = self.resolver.deref(sh_ref)?;
6482 let sh_dict = sh_obj
6483 .as_dict()
6484 .ok_or(PdfError::Other("Shading is not a dict".into()))?;
6485
6486 let pattern_matrix = deref_num_array(self.resolver, pat_dict, b"Matrix")
6487 .map(|v| {
6488 if v.len() >= 6 {
6489 Matrix::new(v[0], v[1], v[2], v[3], v[4], v[5])
6490 } else {
6491 Matrix::identity()
6492 }
6493 })
6494 .unwrap_or_else(Matrix::identity);
6495
6496 let combined_matrix = self.content_stream_ctm.concat(&pattern_matrix);
6511 let saved_ctm = self.gstate.ctm;
6512 let saved_overprint = self.gstate.overprint;
6513 let saved_overprint_stroke = self.gstate.overprint_stroke;
6514 self.gstate.ctm = combined_matrix;
6515 self.gstate.overprint = false;
6516 self.gstate.overprint_stroke = false;
6517
6518 let mut shading_dl = DisplayList::new();
6519 let result = crate::resources::shading::handle_shading(
6520 &sh_ref_clone,
6521 sh_dict,
6522 &self.gstate,
6523 self.resolver,
6524 &mut shading_dl,
6525 &mut self.icc_cache,
6526 );
6527 self.gstate.ctm = saved_ctm;
6528 self.gstate.overprint = saved_overprint;
6529 self.gstate.overprint_stroke = saved_overprint_stroke;
6530 result?;
6531 Ok(shading_dl)
6532 }
6533}
6534
6535fn bboxes_overlap_substantially(a: &[f64; 4], b: &[f64; 4], min_extent: f64) -> bool {
6547 let (ax0, ay0, ax1, ay1) = (
6548 a[0].min(a[2]),
6549 a[1].min(a[3]),
6550 a[0].max(a[2]),
6551 a[1].max(a[3]),
6552 );
6553 let (bx0, by0, bx1, by1) = (
6554 b[0].min(b[2]),
6555 b[1].min(b[3]),
6556 b[0].max(b[2]),
6557 b[1].max(b[3]),
6558 );
6559 let overlap_w = (ax1.min(bx1) - ax0.max(bx0)).max(0.0);
6560 let overlap_h = (ay1.min(by1) - ay0.max(by0)).max(0.0);
6561 overlap_w >= min_extent && overlap_h >= min_extent
6562}
6563
6564fn path_device_bbox(path: &PsPath) -> [f64; 4] {
6565 let mut x_min = f64::INFINITY;
6566 let mut y_min = f64::INFINITY;
6567 let mut x_max = f64::NEG_INFINITY;
6568 let mut y_max = f64::NEG_INFINITY;
6569 let mut update = |x: f64, y: f64| {
6570 x_min = x_min.min(x);
6571 y_min = y_min.min(y);
6572 x_max = x_max.max(x);
6573 y_max = y_max.max(y);
6574 };
6575 for seg in &path.segments {
6576 match seg {
6577 PathSegment::MoveTo(x, y) | PathSegment::LineTo(x, y) => update(*x, *y),
6578 PathSegment::CurveTo {
6579 x1,
6580 y1,
6581 x2,
6582 y2,
6583 x3,
6584 y3,
6585 } => {
6586 update(*x1, *y1);
6587 update(*x2, *y2);
6588 update(*x3, *y3);
6589 }
6590 PathSegment::ClosePath => {}
6591 }
6592 }
6593 [x_min, y_min, x_max, y_max]
6594}
6595
6596fn name_to_cs_ref(name: &[u8]) -> ColorSpaceRef {
6598 match name {
6599 b"DeviceGray" | b"G" => ColorSpaceRef::DeviceGray,
6600 b"DeviceRGB" | b"RGB" => ColorSpaceRef::DeviceRGB,
6601 b"DeviceCMYK" | b"CMYK" => ColorSpaceRef::DeviceCMYK,
6602 _ => ColorSpaceRef::Named(name.to_vec()),
6603 }
6604}
6605
6606fn expand_inline_key(key: &[u8]) -> Vec<u8> {
6608 match key {
6609 b"BPC" => b"BitsPerComponent".to_vec(),
6610 b"CS" => b"ColorSpace".to_vec(),
6611 b"D" => b"Decode".to_vec(),
6612 b"DP" => b"DecodeParms".to_vec(),
6613 b"F" => b"Filter".to_vec(),
6614 b"H" => b"Height".to_vec(),
6615 b"IM" => b"ImageMask".to_vec(),
6616 b"I" => b"Interpolate".to_vec(),
6617 b"W" => b"Width".to_vec(),
6618 _ => key.to_vec(),
6619 }
6620}
6621
6622fn expand_inline_value(name: &[u8]) -> Vec<u8> {
6624 match name {
6625 b"G" => b"DeviceGray".to_vec(),
6626 b"RGB" => b"DeviceRGB".to_vec(),
6627 b"CMYK" => b"DeviceCMYK".to_vec(),
6628 b"I" => b"Indexed".to_vec(),
6629 b"AHx" => b"ASCIIHexDecode".to_vec(),
6630 b"A85" => b"ASCII85Decode".to_vec(),
6631 b"LZW" => b"LZWDecode".to_vec(),
6632 b"Fl" => b"FlateDecode".to_vec(),
6633 b"RL" => b"RunLengthDecode".to_vec(),
6634 b"CCF" => b"CCITTFaxDecode".to_vec(),
6635 b"DCT" => b"DCTDecode".to_vec(),
6636 _ => name.to_vec(),
6637 }
6638}
6639
6640fn bilinear_upsample_image(
6643 data: &[u8],
6644 sw: u32,
6645 sh: u32,
6646 dw: u32,
6647 dh: u32,
6648 cs: &ImageColorSpace,
6649) -> Vec<u8> {
6650 let n = cs.num_components() as usize;
6651 if n == 0 || sw == 0 || sh == 0 || dw == 0 || dh == 0 {
6652 return data.to_vec();
6653 }
6654 let src_stride = sw as usize * n;
6655 let dst_stride = dw as usize * n;
6656 let mut out = vec![0u8; dst_stride * dh as usize];
6657
6658 for dy in 0..dh as usize {
6659 let sy = (dy as f32 + 0.5) * sh as f32 / dh as f32 - 0.5;
6660 let sy0 = (sy.floor() as i32).clamp(0, sh as i32 - 1) as usize;
6661 let sy1 = (sy0 + 1).min(sh as usize - 1);
6662 let fy = sy - sy0 as f32;
6663
6664 for dx in 0..dw as usize {
6665 let sx = (dx as f32 + 0.5) * sw as f32 / dw as f32 - 0.5;
6666 let sx0 = (sx.floor() as i32).clamp(0, sw as i32 - 1) as usize;
6667 let sx1 = (sx0 + 1).min(sw as usize - 1);
6668 let fx = sx - sx0 as f32;
6669
6670 let w00 = (1.0 - fx) * (1.0 - fy);
6671 let w10 = fx * (1.0 - fy);
6672 let w01 = (1.0 - fx) * fy;
6673 let w11 = fx * fy;
6674
6675 let i00 = sy0 * src_stride + sx0 * n;
6676 let i10 = sy0 * src_stride + sx1 * n;
6677 let i01 = sy1 * src_stride + sx0 * n;
6678 let i11 = sy1 * src_stride + sx1 * n;
6679
6680 let di = dy * dst_stride + dx * n;
6681 for c in 0..n {
6682 let v = data[i00 + c] as f32 * w00
6683 + data[i10 + c] as f32 * w10
6684 + data[i01 + c] as f32 * w01
6685 + data[i11 + c] as f32 * w11;
6686 out[di + c] = (v + 0.5).clamp(0.0, 255.0) as u8;
6687 }
6688 }
6689 }
6690 out
6691}
6692
6693fn merge_rgb_with_smask(
6696 image_data: &[u8],
6697 smask_data: &[u8],
6698 color_space: &ImageColorSpace,
6699 width: u32,
6700 height: u32,
6701 icc: Option<&stet_graphics::icc::IccCache>,
6702) -> Vec<u8> {
6703 if let ImageColorSpace::Indexed {
6705 base,
6706 hival,
6707 lookup,
6708 } = color_space
6709 {
6710 let n_base = base.num_components() as usize;
6711 let n_pixels = (width * height) as usize;
6712 let mut expanded = vec![0u8; n_pixels * n_base];
6713 for i in 0..n_pixels {
6714 let idx = image_data.get(i).copied().unwrap_or(0) as usize;
6715 let idx = idx.min(*hival as usize);
6716 let offset = idx * n_base;
6717 for c in 0..n_base {
6718 expanded[i * n_base + c] = lookup.get(offset + c).copied().unwrap_or(0);
6719 }
6720 }
6721 return merge_rgb_with_smask(&expanded, smask_data, base, width, height, icc);
6722 }
6723
6724 if let ImageColorSpace::Separation {
6726 alt_space,
6727 tint_table,
6728 ..
6729 } = color_space
6730 {
6731 let n_pixels = (width * height) as usize;
6732 let no = tint_table.num_outputs as usize;
6733 let mut expanded = vec![0u8; n_pixels * no];
6734 let mut alt_comps = vec![0.0f32; no];
6735 for i in 0..n_pixels {
6736 let tint = image_data.get(i).copied().unwrap_or(0) as f32 / 255.0;
6737 tint_table.lookup_1d(tint, &mut alt_comps);
6738 for c in 0..no {
6739 expanded[i * no + c] = (alt_comps[c].clamp(0.0, 1.0) * 255.0 + 0.5) as u8;
6740 }
6741 }
6742 return merge_rgb_with_smask(&expanded, smask_data, alt_space, width, height, icc);
6743 }
6744 if let ImageColorSpace::DeviceN {
6745 alt_space,
6746 tint_table,
6747 ..
6748 } = color_space
6749 {
6750 let ni = tint_table.num_inputs as usize;
6751 let no = tint_table.num_outputs as usize;
6752 let n_pixels = (width * height) as usize;
6753 let mut expanded = vec![0u8; n_pixels * no];
6754 let mut inputs = vec![0.0f32; ni];
6755 let mut alt_comps = vec![0.0f32; no];
6756 for i in 0..n_pixels {
6757 let si = i * ni;
6758 for (c, inp) in inputs.iter_mut().enumerate() {
6759 *inp = image_data.get(si + c).copied().unwrap_or(0) as f32 / 255.0;
6760 }
6761 tint_table.lookup_nd(&inputs, &mut alt_comps);
6762 for c in 0..no {
6763 expanded[i * no + c] = (alt_comps[c].clamp(0.0, 1.0) * 255.0 + 0.5) as u8;
6764 }
6765 }
6766 return merge_rgb_with_smask(&expanded, smask_data, alt_space, width, height, icc);
6767 }
6768
6769 let n_pixels = (width * height) as usize;
6770 let mut rgba = vec![255u8; n_pixels * 4];
6771 let n_comps = color_space.num_components();
6772
6773 if n_comps == 4 {
6775 if let Some(cache) = icc {
6776 if let Some(cmyk_hash) = cache.default_cmyk_hash() {
6777 let cmyk_data = if image_data.len() >= n_pixels * 4 {
6778 &image_data[..n_pixels * 4]
6779 } else {
6780 image_data
6781 };
6782 if let Some(rgb) = cache.convert_image_8bit(cmyk_hash, cmyk_data, n_pixels) {
6783 for i in 0..n_pixels {
6784 let alpha = smask_data.get(i).copied().unwrap_or(255);
6785 let dst = i * 4;
6786 let (r, g, b) = (rgb[i * 3], rgb[i * 3 + 1], rgb[i * 3 + 2]);
6787 if alpha == 255 {
6788 rgba[dst] = r;
6789 rgba[dst + 1] = g;
6790 rgba[dst + 2] = b;
6791 rgba[dst + 3] = 255;
6792 } else if alpha == 0 {
6793 rgba[dst] = 0;
6795 rgba[dst + 1] = 0;
6796 rgba[dst + 2] = 0;
6797 rgba[dst + 3] = 0;
6798 } else {
6799 let a = alpha as u16;
6800 rgba[dst] = ((r as u16 * a + 127) / 255) as u8;
6801 rgba[dst + 1] = ((g as u16 * a + 127) / 255) as u8;
6802 rgba[dst + 2] = ((b as u16 * a + 127) / 255) as u8;
6803 rgba[dst + 3] = alpha;
6804 }
6805 }
6806 return rgba;
6807 }
6808 }
6809 }
6810 }
6811
6812 for i in 0..n_pixels {
6813 let alpha = smask_data.get(i).copied().unwrap_or(255);
6814 let dst = i * 4;
6815 match n_comps {
6816 3 => {
6817 let src = i * 3;
6819 rgba[dst] = image_data.get(src).copied().unwrap_or(0);
6820 rgba[dst + 1] = image_data.get(src + 1).copied().unwrap_or(0);
6821 rgba[dst + 2] = image_data.get(src + 2).copied().unwrap_or(0);
6822 }
6823 1 => {
6824 let g = image_data.get(i).copied().unwrap_or(0);
6826 rgba[dst] = g;
6827 rgba[dst + 1] = g;
6828 rgba[dst + 2] = g;
6829 }
6830 4 => {
6831 let src = i * 4;
6833 let c = image_data.get(src).copied().unwrap_or(0) as f64 / 255.0;
6834 let m = image_data.get(src + 1).copied().unwrap_or(0) as f64 / 255.0;
6835 let y = image_data.get(src + 2).copied().unwrap_or(0) as f64 / 255.0;
6836 let k = image_data.get(src + 3).copied().unwrap_or(0) as f64 / 255.0;
6837 rgba[dst] = ((1.0 - c) * (1.0 - k) * 255.0 + 0.5) as u8;
6838 rgba[dst + 1] = ((1.0 - m) * (1.0 - k) * 255.0 + 0.5) as u8;
6839 rgba[dst + 2] = ((1.0 - y) * (1.0 - k) * 255.0 + 0.5) as u8;
6840 }
6841 _ => {
6842 }
6844 }
6845 if alpha == 255 {
6847 rgba[dst + 3] = 255;
6848 } else if alpha == 0 {
6849 rgba[dst] = 0;
6850 rgba[dst + 1] = 0;
6851 rgba[dst + 2] = 0;
6852 rgba[dst + 3] = 0;
6853 } else {
6854 let a = alpha as u16;
6855 rgba[dst] = ((rgba[dst] as u16 * a + 127) / 255) as u8;
6856 rgba[dst + 1] = ((rgba[dst + 1] as u16 * a + 127) / 255) as u8;
6857 rgba[dst + 2] = ((rgba[dst + 2] as u16 * a + 127) / 255) as u8;
6858 rgba[dst + 3] = alpha;
6859 }
6860 }
6861 rgba
6862}
6863
6864fn expand_bits_to_bytes(
6865 data: &[u8],
6866 bpc: u32,
6867 width: u32,
6868 height: u32,
6869 components: u32,
6870 is_indexed: bool,
6871) -> Vec<u8> {
6872 if bpc == 0 || bpc == 8 {
6873 return data.to_vec();
6874 }
6875
6876 let max_val = ((1u32 << bpc) - 1) as f64;
6877 let samples_per_row = width * components.max(1);
6878 let mut result = Vec::with_capacity((width * height * components.max(1)) as usize);
6879
6880 for row in 0..height {
6881 let row_bit_offset = row as usize * ((samples_per_row * bpc).div_ceil(8) * 8) as usize;
6882 for col in 0..samples_per_row {
6883 let bit_offset = row_bit_offset + (col * bpc) as usize;
6884 let byte_offset = bit_offset / 8;
6885 let bit_shift = bit_offset % 8;
6886
6887 if byte_offset >= data.len() {
6888 result.push(0);
6889 continue;
6890 }
6891
6892 let mut val = 0u32;
6894 let mut bits_remaining = bpc;
6895 let mut cur_byte = byte_offset;
6896 let mut cur_bit = bit_shift;
6897
6898 while bits_remaining > 0 && cur_byte < data.len() {
6899 let available = 8 - cur_bit as u32;
6900 let take = bits_remaining.min(available);
6901 let shift = available - take;
6902 let mask = ((1u32 << take) - 1) << shift;
6903 val = (val << take) | ((data[cur_byte] as u32 & mask) >> shift);
6904 bits_remaining -= take;
6905 cur_bit = 0;
6906 cur_byte += 1;
6907 }
6908
6909 if is_indexed {
6912 result.push(val as u8);
6913 } else {
6914 result.push((val as f64 / max_val * 255.0 + 0.5) as u8);
6915 }
6916 }
6917 }
6918
6919 result
6920}
6921
6922fn blend_mode_from_name(name: &[u8]) -> u8 {
6924 match name {
6925 b"Normal" | b"Compatible" => 0,
6926 b"Multiply" => 1,
6927 b"Screen" => 2,
6928 b"Overlay" => 3,
6929 b"Darken" => 4,
6930 b"Lighten" => 5,
6931 b"ColorDodge" => 6,
6932 b"ColorBurn" => 7,
6933 b"HardLight" => 8,
6934 b"SoftLight" => 9,
6935 b"Difference" => 10,
6936 b"Exclusion" => 11,
6937 b"Hue" => 12,
6938 b"Saturation" => 13,
6939 b"Color" => 14,
6940 b"Luminosity" => 15,
6941 _ => 0,
6942 }
6943}
6944
6945fn is_whitespace_byte(b: u8) -> bool {
6946 matches!(b, b' ' | b'\t' | b'\r' | b'\n' | 0x0C | 0x00)
6947}
6948
6949fn is_delimiter_or_ws(b: u8) -> bool {
6950 is_whitespace_byte(b)
6951 || matches!(
6952 b,
6953 b'(' | b')' | b'<' | b'>' | b'[' | b']' | b'{' | b'}' | b'/' | b'%'
6954 )
6955}
6956
6957fn sample_transfer_function(func: &crate::resources::function::PdfFunction) -> Vec<f64> {
6959 (0..256)
6960 .map(|i| {
6961 let t = i as f64 / 255.0;
6962 let result = func.evaluate(&[t]);
6963 result.first().copied().unwrap_or(t).clamp(0.0, 1.0)
6964 })
6965 .collect()
6966}
6967
6968fn apply_transfer_to_image(
6973 data: &mut [u8],
6974 transfer: &stet_graphics::device::TransferState,
6975 components: usize,
6976) {
6977 let (r_table, g_table, b_table) = if let Some(ref color) = transfer.color {
6979 let r = build_u8_lut(color[0].as_ref().map(|v| &v[..]));
6981 let g = build_u8_lut(color[1].as_ref().map(|v| &v[..]));
6982 let b = build_u8_lut(color[2].as_ref().map(|v| &v[..]));
6983 (r, g, b)
6984 } else if let Some(ref gray) = transfer.gray {
6985 let lut = build_u8_lut(Some(&gray[..]));
6987 (lut, lut, lut)
6988 } else {
6989 return; };
6991
6992 let stride = components;
6994 for pixel in data.chunks_exact_mut(stride) {
6995 if pixel.len() >= 3 {
6996 pixel[0] = r_table[pixel[0] as usize];
6997 pixel[1] = g_table[pixel[1] as usize];
6998 pixel[2] = b_table[pixel[2] as usize];
6999 }
7000 }
7001}
7002
7003fn apply_transfer_to_color(
7005 color: &DeviceColor,
7006 transfer: &stet_graphics::device::TransferState,
7007) -> DeviceColor {
7008 if let Some(ref color_tables) = transfer.color {
7009 let r = apply_transfer_component(color.r, color_tables[0].as_ref().map(|v| &v[..]));
7011 let g = apply_transfer_component(color.g, color_tables[1].as_ref().map(|v| &v[..]));
7012 let b = apply_transfer_component(color.b, color_tables[2].as_ref().map(|v| &v[..]));
7013 DeviceColor::from_rgb(r, g, b)
7014 } else if let Some(ref gray) = transfer.gray {
7015 let r = apply_transfer_component(color.r, Some(&gray[..]));
7016 let g = apply_transfer_component(color.g, Some(&gray[..]));
7017 let b = apply_transfer_component(color.b, Some(&gray[..]));
7018 DeviceColor::from_rgb(r, g, b)
7019 } else {
7020 color.clone()
7021 }
7022}
7023
7024fn apply_transfer_component(value: f64, table: Option<&[f64]>) -> f64 {
7026 match table {
7027 None => value,
7028 Some(t) if t.len() != 256 => value,
7029 Some(t) => {
7030 let idx = (value * 255.0).clamp(0.0, 255.0);
7031 let lo = idx.floor() as usize;
7032 let hi = (lo + 1).min(255);
7033 let frac = idx - lo as f64;
7034 let v0 = t[lo];
7035 let v1 = t[hi];
7036 (v0 + frac * (v1 - v0)).clamp(0.0, 1.0)
7037 }
7038 }
7039}
7040
7041fn build_u8_lut(table: Option<&[f64]>) -> [u8; 256] {
7043 let mut lut = [0u8; 256];
7044 match table {
7045 Some(t) if t.len() == 256 => {
7046 for (i, v) in lut.iter_mut().enumerate() {
7047 *v = (t[i].clamp(0.0, 1.0) * 255.0 + 0.5) as u8;
7048 }
7049 }
7050 _ => {
7051 for (i, v) in lut.iter_mut().enumerate() {
7052 *v = i as u8;
7053 }
7054 }
7055 }
7056 lut
7057}