1use std::collections::HashMap;
2
3use justpdf_core::color::{Color as PdfColor, ColorSpace};
4use justpdf_core::content::{ContentOp, Operand, parse_content_stream};
5use justpdf_core::font::{FontInfo, ToUnicodeCMap, parse_font_info};
6use justpdf_core::image;
7use justpdf_core::object::{PdfDict, PdfObject};
8use justpdf_core::ocg::{self, OCConfig};
9use justpdf_core::page::PageInfo;
10use justpdf_core::PdfDocument;
11use tiny_skia::{FillRule, Mask, PathBuilder, Pixmap, Transform};
12
13use crate::device::PixmapDevice;
14use crate::error::{RenderError, Result};
15use crate::glyph_cache::GlyphCache;
16use crate::graphics_state::{
17 GraphicsState, LineCap, LineJoin, Matrix, PdfBlendMode, SoftMask, SoftMaskSubtype,
18};
19
20struct ResolvedFont {
22 info: FontInfo,
23 #[allow(dead_code)]
24 cmap: Option<ToUnicodeCMap>,
25 font_data: Option<Vec<u8>>,
27 cid_to_gid_map: Option<Vec<u16>>,
30}
31
32pub struct RenderInterpreter<'a> {
34 doc: &'a PdfDocument,
35 device: &'a mut PixmapDevice,
36 state: GraphicsState,
37 state_stack: Vec<GraphicsState>,
38 fonts: HashMap<Vec<u8>, ResolvedFont>,
39 page_transform: Matrix,
41 path_builder: Option<PathBuilder>,
43 xobject_depth: u32,
45 glyph_cache: GlyphCache,
47 oc_config: Option<OCConfig>,
49 oc_skip_depth: u32,
52}
53
54impl<'a> RenderInterpreter<'a> {
55 pub fn new(
56 doc: &'a PdfDocument,
57 device: &'a mut PixmapDevice,
58 page_transform: Matrix,
59 ) -> Self {
60 let oc_config = ocg::read_oc_properties(doc)
62 .ok()
63 .flatten()
64 .and_then(|props| props.default_config);
65
66 Self {
67 doc,
68 device,
69 state: GraphicsState::default(),
70 state_stack: Vec::new(),
71 fonts: HashMap::new(),
72 page_transform,
73 path_builder: None,
74 xobject_depth: 0,
75 glyph_cache: GlyphCache::with_default_capacity(),
76 oc_config,
77 oc_skip_depth: 0,
78 }
79 }
80
81 pub fn render_page(&mut self, page: &PageInfo) -> Result<()> {
83 let _ = self.resolve_page_fonts(page);
85
86 let content_data = self.get_page_content(page)?;
88 if !content_data.is_empty() {
89 let ops = parse_content_stream(&content_data).map_err(|e| RenderError::Core(e))?;
90 self.execute_ops(&ops, page)?;
91 }
92
93 let _ = self.render_annotations(page);
95
96 Ok(())
97 }
98
99 fn render_annotations(&mut self, page: &PageInfo) -> Result<()> {
101 let page_obj = self.doc.resolve(&page.page_ref)?;
103 let page_dict = match page_obj.as_dict() {
104 Some(d) => d.clone(),
105 None => return Ok(()),
106 };
107
108 let annots_arr = match page_dict.get(b"Annots") {
109 Some(PdfObject::Array(arr)) => arr.clone(),
110 Some(PdfObject::Reference(r)) => {
111 let r = r.clone();
112 match self.doc.resolve(&r)? {
113 PdfObject::Array(arr) => arr,
114 _ => return Ok(()),
115 }
116 }
117 _ => return Ok(()),
118 };
119
120 for item in &annots_arr {
121 let annot_dict = match item {
122 PdfObject::Reference(r) => {
123 let r = r.clone();
124 match self.doc.resolve(&r)? {
125 PdfObject::Dict(d) => d,
126 _ => continue,
127 }
128 }
129 PdfObject::Dict(d) => d.clone(),
130 _ => continue,
131 };
132
133 let flags = annot_dict
135 .get_i64(b"F")
136 .unwrap_or(0) as u32;
137 if flags & 0x02 != 0 || flags & 0x20 != 0 {
138 continue;
140 }
141
142 let ap_stream = match annot_dict.get(b"AP") {
144 Some(PdfObject::Dict(ap)) => {
145 let n_obj = match ap.get(b"N") {
146 Some(PdfObject::Reference(r)) => {
147 let r = r.clone();
148 self.doc.resolve(&r)?
149 }
150 Some(other) => other.clone(),
151 None => continue,
152 };
153 match n_obj {
154 PdfObject::Stream { dict, data } => (dict, data),
155 _ => continue,
156 }
157 }
158 _ => continue,
159 };
160
161 if annot_dict.get_array(b"Rect").is_none() {
163 continue;
164 }
165
166 self.state_stack.push(self.state.clone());
168
169 let (ap_dict, ap_data) = ap_stream;
170 let _ = self.render_form_xobject(&ap_dict, &ap_data, page);
171
172 if let Some(saved) = self.state_stack.pop() {
174 self.state = saved;
175 }
176 }
177
178 Ok(())
179 }
180
181 fn check_oc_visibility(&self, operands: &[Operand], config: &OCConfig) -> bool {
189 let props = match operands.get(1) {
191 Some(o) => o,
192 None => return true,
193 };
194
195 match props {
196 Operand::Dict(dict) => {
197 let pdf_dict = self.operand_dict_to_pdf_dict(dict);
198 self.check_oc_dict_visibility(&pdf_dict, config)
199 }
200 Operand::Name(_name) => {
201 true
204 }
205 _ => true,
206 }
207 }
208
209 fn check_oc_dict_visibility(&self, dict: &PdfDict, config: &OCConfig) -> bool {
211 let type_name = dict.get_name(b"Type").unwrap_or(b"");
212
213 if type_name == b"OCG" {
214 true
219 } else if type_name == b"OCMD" {
220 if let Some(ocmd) = ocg::parse_ocmd(dict) {
222 ocg::is_ocmd_visible(&ocmd, config)
223 } else {
224 true
225 }
226 } else {
227 if dict.get(b"OCGs").is_some() {
229 if let Some(ocmd) = ocg::parse_ocmd(dict) {
230 return ocg::is_ocmd_visible(&ocmd, config);
231 }
232 }
233 true
234 }
235 }
236
237 fn operand_dict_to_pdf_dict(&self, entries: &[(Vec<u8>, Operand)]) -> PdfDict {
239 let mut dict = PdfDict::new();
240 for (key, value) in entries {
241 dict.insert(key.clone(), Self::operand_to_pdf_object(value));
242 }
243 dict
244 }
245
246 fn operand_to_pdf_object(op: &Operand) -> PdfObject {
248 match op {
249 Operand::Integer(v) => PdfObject::Integer(*v),
250 Operand::Real(v) => PdfObject::Real(*v),
251 Operand::Bool(v) => PdfObject::Bool(*v),
252 Operand::Null => PdfObject::Null,
253 Operand::Name(n) => PdfObject::Name(n.clone()),
254 Operand::String(s) => PdfObject::String(s.clone()),
255 Operand::Array(arr) => {
256 PdfObject::Array(arr.iter().map(Self::operand_to_pdf_object).collect())
257 }
258 Operand::Dict(entries) => {
259 let mut dict = PdfDict::new();
260 for (k, v) in entries {
261 dict.insert(k.clone(), Self::operand_to_pdf_object(v));
262 }
263 PdfObject::Dict(dict)
264 }
265 Operand::InlineImage { .. } => PdfObject::Null,
266 }
267 }
268
269 fn resolve_page_fonts(&mut self, page: &PageInfo) -> Result<()> {
270 let resources_obj = match &page.resources_ref {
271 Some(obj) => self.resolve_object(obj)?,
272 None => return Ok(()),
273 };
274
275 let resources_dict = match &resources_obj {
276 PdfObject::Dict(d) => d.clone(),
277 _ => return Ok(()),
278 };
279
280 let font_dict_obj = match resources_dict.get(b"Font") {
281 Some(PdfObject::Dict(d)) => PdfObject::Dict(d.clone()),
282 Some(PdfObject::Reference(r)) => {
283 let r = r.clone();
284 self.doc.resolve(&r)?
285 }
286 _ => return Ok(()),
287 };
288
289 if let PdfObject::Dict(font_dict) = &font_dict_obj {
290 for (name, val) in font_dict.iter() {
291 let font_obj = match val {
292 PdfObject::Reference(r) => {
293 let r = r.clone();
294 self.doc.resolve(&r)?
295 }
296 other => other.clone(),
297 };
298
299 if let PdfObject::Dict(fd) = &font_obj {
300 let mut info = parse_font_info(fd);
301
302 let cmap = if let Some(PdfObject::Reference(tu_ref)) = fd.get(b"ToUnicode") {
304 let tu_ref = tu_ref.clone();
305 if let Ok(tu_obj) = self.doc.resolve(&tu_ref) {
306 if let PdfObject::Stream { dict, data } = tu_obj {
307 let decoded = self.doc.decode_stream(&dict, &data).ok();
308 decoded.map(|d| ToUnicodeCMap::parse(&d))
309 } else {
310 None
311 }
312 } else {
313 None
314 }
315 } else {
316 None
317 };
318
319 let mut cid_font_descriptor: Option<PdfDict> = None;
321 let mut cid_to_gid_map: Option<Vec<u16>> = None;
322 if info.subtype == b"Type0" {
323 if let Some(PdfObject::Array(descendants)) =
324 fd.get(b"DescendantFonts")
325 {
326 if let Some(desc_ref) = descendants.first() {
327 let desc_obj = match desc_ref {
328 PdfObject::Reference(r) => {
329 let r = r.clone();
330 self.doc.resolve(&r)?
331 }
332 other => other.clone(),
333 };
334 if let PdfObject::Dict(cid_dict) = &desc_obj {
335 let cid_info = parse_font_info(cid_dict);
336 info.widths = cid_info.widths;
337 if let Some(fd_obj) = cid_dict.get(b"FontDescriptor") {
339 let fd_resolved = match fd_obj {
340 PdfObject::Reference(r) => {
341 let r = r.clone();
342 self.doc.resolve(&r).ok()
343 }
344 other => Some(other.clone()),
345 };
346 if let Some(PdfObject::Dict(d)) = fd_resolved {
347 cid_font_descriptor = Some(d);
348 }
349 }
350
351 cid_to_gid_map =
353 self.parse_cid_to_gid_map(cid_dict);
354 }
355 }
356 }
357 }
358
359 let font_data = self.extract_font_data(
361 fd,
362 cid_font_descriptor.as_ref(),
363 );
364
365 self.fonts.insert(
366 name.clone(),
367 ResolvedFont {
368 info,
369 cmap,
370 font_data,
371 cid_to_gid_map,
372 },
373 );
374 }
375 }
376 }
377
378 Ok(())
379 }
380
381 fn extract_font_data(
384 &mut self,
385 font_dict: &PdfDict,
386 cid_descriptor: Option<&PdfDict>,
387 ) -> Option<Vec<u8>> {
388 let descriptor = self
390 .get_font_descriptor(font_dict)
391 .or_else(|| cid_descriptor.cloned());
392
393 let descriptor = descriptor?;
394
395 for key in &[b"FontFile2".as_slice(), b"FontFile3", b"FontFile"] {
397 if let Some(obj) = descriptor.get(*key) {
398 let stream_obj = match obj {
399 PdfObject::Reference(r) => {
400 let r = r.clone();
401 self.doc.resolve(&r).ok()
402 }
403 other => Some(other.clone()),
404 };
405 if let Some(PdfObject::Stream { dict, data }) = stream_obj {
406 if let Ok(decoded) = self.doc.decode_stream(&dict, &data) {
407 return Some(decoded);
408 }
409 }
410 }
411 }
412
413 None
414 }
415
416 fn get_font_descriptor(&mut self, font_dict: &PdfDict) -> Option<PdfDict> {
417 let fd_obj = font_dict.get(b"FontDescriptor")?;
418 let resolved = match fd_obj {
419 PdfObject::Reference(r) => {
420 let r = r.clone();
421 self.doc.resolve(&r).ok()
422 }
423 other => Some(other.clone()),
424 };
425 match resolved {
426 Some(PdfObject::Dict(d)) => Some(d),
427 _ => None,
428 }
429 }
430
431 fn parse_cid_to_gid_map(&mut self, cid_dict: &PdfDict) -> Option<Vec<u16>> {
435 let map_obj = cid_dict.get(b"CIDToGIDMap")?;
436
437 match map_obj {
438 PdfObject::Name(n) if n == b"Identity" => {
439 None
441 }
442 PdfObject::Reference(r) => {
443 let r = r.clone();
444 let resolved = self.doc.resolve(&r).ok()?;
445 if let PdfObject::Stream { dict, data } = resolved {
446 let decoded = self.doc.decode_stream(&dict, &data).ok()?;
447 Some(parse_cid_gid_stream(&decoded))
448 } else {
449 None
450 }
451 }
452 PdfObject::Stream { dict, data } => {
453 let decoded = self.doc.decode_stream(dict, data).ok()?;
454 Some(parse_cid_gid_stream(&decoded))
455 }
456 _ => None,
457 }
458 }
459
460 fn resolve_object(&mut self, obj: &PdfObject) -> Result<PdfObject> {
461 match obj {
462 PdfObject::Reference(r) => {
463 let r = r.clone();
464 Ok(self.doc.resolve(&r)?)
465 }
466 other => Ok(other.clone()),
467 }
468 }
469
470 fn get_page_content(&mut self, page: &PageInfo) -> Result<Vec<u8>> {
471 let contents = match &page.contents_ref {
472 Some(c) => c.clone(),
473 None => return Ok(Vec::new()),
474 };
475
476 match &contents {
477 PdfObject::Reference(r) => {
478 let r = r.clone();
479 let obj = self.doc.resolve(&r)?;
480 match obj {
481 PdfObject::Stream { dict, data } => {
482 Ok(self.doc.decode_stream(&dict, &data).unwrap_or_default())
483 }
484 PdfObject::Array(arr) => self.concat_content_streams(&arr),
485 _ => Ok(Vec::new()),
486 }
487 }
488 PdfObject::Array(arr) => {
489 let arr = arr.clone();
490 self.concat_content_streams(&arr)
491 }
492 PdfObject::Stream { dict, data } => {
493 Ok(self.doc.decode_stream(dict, data).unwrap_or_default())
494 }
495 _ => Ok(Vec::new()),
496 }
497 }
498
499 fn concat_content_streams(&mut self, arr: &[PdfObject]) -> Result<Vec<u8>> {
500 let mut combined = Vec::new();
501 for item in arr {
502 let obj = match item {
503 PdfObject::Reference(r) => {
504 let r = r.clone();
505 self.doc.resolve(&r)?
506 }
507 other => other.clone(),
508 };
509 if let PdfObject::Stream { dict, data } = obj {
510 if let Ok(decoded) = self.doc.decode_stream(&dict, &data) {
512 combined.extend_from_slice(&decoded);
513 combined.push(b' ');
514 }
515 }
516 }
517 Ok(combined)
518 }
519
520 fn execute_ops(&mut self, ops: &[ContentOp], page: &PageInfo) -> Result<()> {
521 for op in ops {
522 let operator = op.operator_str();
523
524 if self.oc_skip_depth > 0 {
527 match operator {
528 "BMC" | "BDC" => self.oc_skip_depth += 1,
529 "EMC" => self.oc_skip_depth -= 1,
530 _ => {}
531 }
532 continue;
533 }
534
535 self.execute_op(op, page)?;
536 }
537 Ok(())
538 }
539
540 fn execute_op(&mut self, op: &ContentOp, page: &PageInfo) -> Result<()> {
541 let operator = op.operator_str();
542 let operands = &op.operands;
543
544 match operator {
545 "q" => {
547 self.state_stack.push(self.state.clone());
548 }
549 "Q" => {
550 let had_clip = self.state.has_clip;
551 if let Some(s) = self.state_stack.pop() {
552 self.state = s;
553 }
554 if had_clip && !self.state.has_clip {
557 self.device.clear_clip();
558 }
561 }
562 "cm" => {
563 if operands.len() >= 6 {
564 let m = Matrix {
565 a: f(operands, 0),
566 b: f(operands, 1),
567 c: f(operands, 2),
568 d: f(operands, 3),
569 e: f(operands, 4),
570 f: f(operands, 5),
571 };
572 self.state.ctm = m.concat(&self.state.ctm);
573 }
574 }
575
576 "w" => {
578 if let Some(v) = operands.first().and_then(|o| o.as_f64()) {
579 self.state.line_width = v;
580 }
581 }
582 "J" => {
583 if let Some(v) = operands.first().and_then(|o| o.as_i64()) {
584 self.state.line_cap = match v {
585 1 => LineCap::Round,
586 2 => LineCap::Square,
587 _ => LineCap::Butt,
588 };
589 }
590 }
591 "j" => {
592 if let Some(v) = operands.first().and_then(|o| o.as_i64()) {
593 self.state.line_join = match v {
594 1 => LineJoin::Round,
595 2 => LineJoin::Bevel,
596 _ => LineJoin::Miter,
597 };
598 }
599 }
600 "M" => {
601 if let Some(v) = operands.first().and_then(|o| o.as_f64()) {
602 self.state.miter_limit = v;
603 }
604 }
605 "d" => {
606 if operands.len() >= 2 {
608 if let Some(arr) = operands[0].as_array() {
609 self.state.dash_pattern =
610 arr.iter().filter_map(|o| o.as_f64()).collect();
611 }
612 self.state.dash_phase = f(operands, 1);
613 }
614 }
615
616 "gs" => {
618 if let Some(name) = operands.first().and_then(|o| o.as_name()) {
619 self.apply_extgstate(name, page)?;
620 }
621 }
622
623 "m" => {
625 let pb = self.path_builder.get_or_insert_with(PathBuilder::new);
626 pb.move_to(f(operands, 0) as f32, f(operands, 1) as f32);
627 }
628 "l" => {
629 if let Some(pb) = &mut self.path_builder {
630 pb.line_to(f(operands, 0) as f32, f(operands, 1) as f32);
631 }
632 }
633 "c" => {
634 if let Some(pb) = &mut self.path_builder {
635 pb.cubic_to(
636 f(operands, 0) as f32,
637 f(operands, 1) as f32,
638 f(operands, 2) as f32,
639 f(operands, 3) as f32,
640 f(operands, 4) as f32,
641 f(operands, 5) as f32,
642 );
643 }
644 }
645 "v" => {
646 if let Some(pb) = &mut self.path_builder {
648 pb.cubic_to(
654 f(operands, 0) as f32, f(operands, 1) as f32,
656 f(operands, 0) as f32,
657 f(operands, 1) as f32,
658 f(operands, 2) as f32,
659 f(operands, 3) as f32,
660 );
661 }
662 }
663 "y" => {
664 if let Some(pb) = &mut self.path_builder {
665 pb.cubic_to(
667 f(operands, 0) as f32,
668 f(operands, 1) as f32,
669 f(operands, 2) as f32,
670 f(operands, 3) as f32,
671 f(operands, 2) as f32,
672 f(operands, 3) as f32,
673 );
674 }
675 }
676 "h" => {
677 if let Some(pb) = &mut self.path_builder {
678 pb.close();
679 }
680 }
681 "re" => {
682 if operands.len() >= 4 {
684 let x = f(operands, 0) as f32;
685 let y = f(operands, 1) as f32;
686 let w = f(operands, 2) as f32;
687 let h = f(operands, 3) as f32;
688 let pb = self.path_builder.get_or_insert_with(PathBuilder::new);
689 pb.move_to(x, y);
690 pb.line_to(x + w, y);
691 pb.line_to(x + w, y + h);
692 pb.line_to(x, y + h);
693 pb.close();
694 }
695 }
696
697 "S" => {
699 self.stroke_current_path(page);
701 }
702 "s" => {
703 if let Some(pb) = &mut self.path_builder {
705 pb.close();
706 }
707 self.stroke_current_path(page);
708 }
709 "f" | "F" => {
710 self.fill_current_path(FillRule::Winding, page);
712 }
713 "f*" => {
714 self.fill_current_path(FillRule::EvenOdd, page);
716 }
717 "B" => {
718 self.fill_current_path_keep(FillRule::Winding, page);
720 self.stroke_current_path(page);
721 }
722 "B*" => {
723 self.fill_current_path_keep(FillRule::EvenOdd, page);
724 self.stroke_current_path(page);
725 }
726 "b" => {
727 if let Some(pb) = &mut self.path_builder {
728 pb.close();
729 }
730 self.fill_current_path_keep(FillRule::Winding, page);
731 self.stroke_current_path(page);
732 }
733 "b*" => {
734 if let Some(pb) = &mut self.path_builder {
735 pb.close();
736 }
737 self.fill_current_path_keep(FillRule::EvenOdd, page);
738 self.stroke_current_path(page);
739 }
740 "n" => {
741 self.path_builder = None;
743 }
744
745 "W" => {
747 self.apply_clip(FillRule::Winding);
748 }
749 "W*" => {
750 self.apply_clip(FillRule::EvenOdd);
751 }
752
753 "CS" => {
755 if let Some(name) = operands.first().and_then(|o| o.as_name()) {
756 self.state.stroke_cs = cs_from_name(name);
757 if name != b"Pattern" {
758 self.state.stroke_pattern_name = None;
759 }
760 }
761 }
762 "cs" => {
763 if let Some(name) = operands.first().and_then(|o| o.as_name()) {
764 self.state.fill_cs = cs_from_name(name);
765 if name != b"Pattern" {
766 self.state.fill_pattern_name = None;
767 }
768 }
769 }
770 "SC" | "SCN" => {
771 let last_is_name = operands.last().and_then(|o| o.as_name());
773 if last_is_name.is_some() {
774 self.state.stroke_pattern_name =
775 last_is_name.map(|n| n.to_vec());
776 let comps: Vec<f64> = operands.iter().filter_map(|o| o.as_f64()).collect();
778 if !comps.is_empty() {
779 self.state.stroke_color = PdfColor { components: comps };
780 }
781 } else {
782 let comps: Vec<f64> = operands.iter().filter_map(|o| o.as_f64()).collect();
783 if !comps.is_empty() {
784 self.state.stroke_color = PdfColor { components: comps };
785 }
786 }
787 }
788 "sc" | "scn" => {
789 let last_is_name = operands.last().and_then(|o| o.as_name());
791 if last_is_name.is_some() {
792 self.state.fill_pattern_name =
793 last_is_name.map(|n| n.to_vec());
794 let comps: Vec<f64> = operands.iter().filter_map(|o| o.as_f64()).collect();
796 if !comps.is_empty() {
797 self.state.fill_color = PdfColor { components: comps };
798 }
799 } else {
800 let comps: Vec<f64> = operands.iter().filter_map(|o| o.as_f64()).collect();
801 if !comps.is_empty() {
802 self.state.fill_color = PdfColor { components: comps };
803 }
804 }
805 }
806 "G" => {
807 self.state.stroke_cs = ColorSpace::DeviceGray;
808 self.state.stroke_color = PdfColor::gray(f(operands, 0));
809 }
810 "g" => {
811 self.state.fill_cs = ColorSpace::DeviceGray;
812 self.state.fill_color = PdfColor::gray(f(operands, 0));
813 }
814 "RG" => {
815 self.state.stroke_cs = ColorSpace::DeviceRGB;
816 self.state.stroke_color =
817 PdfColor::rgb(f(operands, 0), f(operands, 1), f(operands, 2));
818 }
819 "rg" => {
820 self.state.fill_cs = ColorSpace::DeviceRGB;
821 self.state.fill_color =
822 PdfColor::rgb(f(operands, 0), f(operands, 1), f(operands, 2));
823 }
824 "K" => {
825 self.state.stroke_cs = ColorSpace::DeviceCMYK;
826 self.state.stroke_color = PdfColor::cmyk(
827 f(operands, 0),
828 f(operands, 1),
829 f(operands, 2),
830 f(operands, 3),
831 );
832 }
833 "k" => {
834 self.state.fill_cs = ColorSpace::DeviceCMYK;
835 self.state.fill_color = PdfColor::cmyk(
836 f(operands, 0),
837 f(operands, 1),
838 f(operands, 2),
839 f(operands, 3),
840 );
841 }
842
843 "BT" => {
845 self.state.text_matrix = Matrix::identity();
846 self.state.text_line_matrix = Matrix::identity();
847 }
848 "ET" => {}
849 "Tc" => {
850 self.state.text.char_spacing = f(operands, 0);
851 }
852 "Tw" => {
853 self.state.text.word_spacing = f(operands, 0);
854 }
855 "Tz" => {
856 self.state.text.horiz_scaling = f(operands, 0) / 100.0;
857 }
858 "TL" => {
859 self.state.text.leading = f(operands, 0);
860 }
861 "Tf" => {
862 if let Some(name) = operands.first().and_then(|o| o.as_name()) {
863 self.state.text.font_name = name.to_vec();
864 }
865 if operands.len() > 1 {
866 self.state.text.font_size = f(operands, 1);
867 }
868 }
869 "Tr" => {
870 self.state.text.render_mode = operands.first().and_then(|o| o.as_i64()).unwrap_or(0);
871 }
872 "Ts" => {
873 self.state.text.text_rise = f(operands, 0);
874 }
875 "Td" => {
876 let tx = f(operands, 0);
877 let ty = f(operands, 1);
878 let t = Matrix::translate(tx, ty);
879 self.state.text_line_matrix = t.concat(&self.state.text_line_matrix);
880 self.state.text_matrix = self.state.text_line_matrix;
881 }
882 "TD" => {
883 let tx = f(operands, 0);
884 let ty = f(operands, 1);
885 self.state.text.leading = -ty;
886 let t = Matrix::translate(tx, ty);
887 self.state.text_line_matrix = t.concat(&self.state.text_line_matrix);
888 self.state.text_matrix = self.state.text_line_matrix;
889 }
890 "Tm" => {
891 if operands.len() >= 6 {
892 let m = Matrix {
893 a: f(operands, 0),
894 b: f(operands, 1),
895 c: f(operands, 2),
896 d: f(operands, 3),
897 e: f(operands, 4),
898 f: f(operands, 5),
899 };
900 self.state.text_matrix = m;
901 self.state.text_line_matrix = m;
902 }
903 }
904 "T*" => {
905 let leading = self.state.text.leading;
906 let t = Matrix::translate(0.0, -leading);
907 self.state.text_line_matrix = t.concat(&self.state.text_line_matrix);
908 self.state.text_matrix = self.state.text_line_matrix;
909 }
910 "Tj" => {
911 if let Some(s) = operands.first().and_then(|o| o.as_str()) {
912 self.render_text_string(s)?;
913 }
914 }
915 "TJ" => {
916 if let Some(arr) = operands.first().and_then(|o| o.as_array()) {
917 for item in arr {
918 match item {
919 Operand::String(s) => {
920 self.render_text_string(s)?;
921 }
922 Operand::Integer(n) => {
923 self.adjust_text_position(*n as f64);
924 }
925 Operand::Real(n) => {
926 self.adjust_text_position(*n);
927 }
928 _ => {}
929 }
930 }
931 }
932 }
933 "'" => {
934 let leading = self.state.text.leading;
936 let t = Matrix::translate(0.0, -leading);
937 self.state.text_line_matrix = t.concat(&self.state.text_line_matrix);
938 self.state.text_matrix = self.state.text_line_matrix;
939 if let Some(s) = operands.first().and_then(|o| o.as_str()) {
940 self.render_text_string(s)?;
941 }
942 }
943 "\"" => {
944 if operands.len() >= 3 {
946 self.state.text.word_spacing = f(operands, 0);
947 self.state.text.char_spacing = f(operands, 1);
948 let leading = self.state.text.leading;
949 let t = Matrix::translate(0.0, -leading);
950 self.state.text_line_matrix = t.concat(&self.state.text_line_matrix);
951 self.state.text_matrix = self.state.text_line_matrix;
952 if let Some(s) = operands.get(2).and_then(|o| o.as_str()) {
953 self.render_text_string(s)?;
954 }
955 }
956 }
957
958 "Do" => {
960 if let Some(name) = operands.first().and_then(|o| o.as_name()) {
961 self.do_xobject(name, page)?;
962 }
963 }
964
965 "BI" => {
967 if let Some(Operand::InlineImage { dict, data }) = operands.first() {
968 self.render_inline_image(dict, data)?;
969 }
970 }
971
972 "BMC" => {}
974 "BDC" => {
975 if let Some(config) = &self.oc_config {
980 let tag = operands.first().and_then(|o| o.as_name());
981 if tag == Some(b"OC") {
982 let visible = self.check_oc_visibility(operands, config);
983 if !visible {
984 self.oc_skip_depth = 1;
985 }
986 }
987 }
988 }
989 "EMC" => {}
990 "MP" | "DP" => {}
991
992 "sh" => {
994 if let Some(name) = operands.first().and_then(|o| o.as_name()) {
995 self.render_shading(name, page)?;
996 }
997 }
998
999 "d0" | "d1" => {}
1001
1002 "BX" | "EX" => {}
1004
1005 _ => {}
1007 }
1008
1009 Ok(())
1010 }
1011
1012 fn effective_transform(&self) -> Transform {
1015 self.state.ctm.concat(&self.page_transform).to_skia()
1016 }
1017
1018 fn blend_mode(&self) -> tiny_skia::BlendMode {
1019 self.state.blend_mode.to_skia()
1020 }
1021
1022 fn fill_current_path(&mut self, rule: FillRule, page: &PageInfo) {
1023 if let Some(pb) = self.path_builder.take() {
1024 if let Some(path) = pb.finish() {
1025 if self.state.fill_pattern_name.is_some() {
1027 if self.try_fill_with_pattern(&path, rule, page) {
1028 return;
1029 }
1030 }
1031 let transform = self.effective_transform();
1032 let color = self.state.fill_color_rgba();
1033 let bm = self.blend_mode();
1034 self.apply_soft_mask_to_device();
1035 self.device.fill_path(&path, rule, transform, color, bm);
1036 self.restore_clip_after_soft_mask();
1037 }
1038 }
1039 }
1040
1041 fn fill_current_path_keep(&mut self, rule: FillRule, page: &PageInfo) {
1042 if let Some(pb) = &self.path_builder {
1043 let pb_clone = pb.clone();
1044 if let Some(path) = pb_clone.finish() {
1045 if self.state.fill_pattern_name.is_some() {
1047 if self.try_fill_with_pattern(&path, rule, page) {
1048 return;
1049 }
1050 }
1051 let transform = self.effective_transform();
1052 let color = self.state.fill_color_rgba();
1053 let bm = self.blend_mode();
1054 self.apply_soft_mask_to_device();
1055 self.device.fill_path(&path, rule, transform, color, bm);
1056 self.restore_clip_after_soft_mask();
1057 }
1058 }
1059 }
1060
1061 fn stroke_current_path(&mut self, page: &PageInfo) {
1062 if let Some(pb) = self.path_builder.take() {
1063 if let Some(path) = pb.finish() {
1064 if self.state.stroke_pattern_name.is_some() {
1066 if self.try_stroke_with_pattern(&path, page) {
1067 return;
1068 }
1069 }
1070 let transform = self.effective_transform();
1071 let color = self.state.stroke_color_rgba();
1072 let bm = self.blend_mode();
1073 self.apply_soft_mask_to_device();
1074 self.device.stroke_path(&path, transform, color, &self.state, bm);
1075 self.restore_clip_after_soft_mask();
1076 }
1077 }
1078 }
1079
1080 fn apply_soft_mask_to_device(&mut self) {
1083 if let Some(ref soft_mask) = self.state.soft_mask {
1084 if let Some(ref existing_clip) = self.device.clip_mask {
1086 let mut combined = existing_clip.clone();
1088 let combined_data = combined.data_mut();
1089 let mask_data = soft_mask.mask.data();
1090 let len = combined_data.len().min(mask_data.len());
1091 for i in 0..len {
1092 combined_data[i] =
1093 ((combined_data[i] as u16 * mask_data[i] as u16) / 255) as u8;
1094 }
1095 self.device.clip_mask = Some(combined);
1096 } else {
1097 self.device.clip_mask = Some(soft_mask.mask.clone());
1098 }
1099 }
1100 }
1101
1102 fn restore_clip_after_soft_mask(&mut self) {
1104 if self.state.soft_mask.is_some() {
1105 }
1113 }
1114
1115 fn apply_clip(&mut self, rule: FillRule) {
1116 if let Some(pb) = &self.path_builder {
1117 let pb_clone = pb.clone();
1118 if let Some(path) = pb_clone.finish() {
1119 let transform = self.effective_transform();
1120 if self.state.has_clip {
1121 self.device.intersect_clip_path(&path, rule, transform);
1122 } else {
1123 self.device.set_clip_path(&path, rule, transform);
1124 }
1125 self.state.has_clip = true;
1126 }
1127 }
1128 }
1129
1130 fn render_text_string(&mut self, string_bytes: &[u8]) -> Result<()> {
1133 let font_name = self.state.text.font_name.clone();
1134 let font = match self.fonts.get(&font_name) {
1135 Some(f) => f,
1136 None => return Ok(()), };
1138
1139 let font_size = self.state.text.font_size;
1140 let horiz_scaling = self.state.text.horiz_scaling;
1141 let char_spacing = self.state.text.char_spacing;
1142 let word_spacing = self.state.text.word_spacing;
1143 let text_rise = self.state.text.text_rise;
1144 let render_mode = self.state.text.render_mode;
1145
1146 let is_cid = font.info.subtype == b"Type0";
1148
1149 let char_codes: Vec<u32> = if is_cid {
1151 string_bytes
1152 .chunks(2)
1153 .map(|c| {
1154 if c.len() == 2 {
1155 ((c[0] as u32) << 8) | (c[1] as u32)
1156 } else {
1157 c[0] as u32
1158 }
1159 })
1160 .collect()
1161 } else {
1162 string_bytes.iter().map(|b| *b as u32).collect()
1163 };
1164
1165 let widths: Vec<f64> = char_codes
1166 .iter()
1167 .map(|code| font.info.widths.get_width(*code))
1168 .collect();
1169
1170 let font_data = font.font_data.clone();
1172 let cid_to_gid_map = font.cid_to_gid_map.clone();
1173
1174 for (i, code) in char_codes.iter().enumerate() {
1176 let width = widths[i];
1177 let w0 = width / 1000.0;
1178
1179 if render_mode != 3 {
1180 self.render_glyph(
1181 *code,
1182 w0 * font_size,
1183 font_size,
1184 text_rise,
1185 is_cid,
1186 &font_data,
1187 cid_to_gid_map.as_deref(),
1188 )?;
1189 }
1190
1191 let tx = (w0 * font_size + char_spacing) * horiz_scaling;
1193 let tx = if *code == 32 {
1194 tx + word_spacing * horiz_scaling
1195 } else {
1196 tx
1197 };
1198
1199 let advance = Matrix::translate(tx, 0.0);
1200 self.state.text_matrix = advance.concat(&self.state.text_matrix);
1201 }
1202
1203 Ok(())
1204 }
1205
1206 fn render_glyph(
1207 &mut self,
1208 code: u32,
1209 glyph_width: f64,
1210 font_size: f64,
1211 text_rise: f64,
1212 is_cid: bool,
1213 font_data: &Option<Vec<u8>>,
1214 cid_to_gid_map: Option<&[u16]>,
1215 ) -> Result<()> {
1216 if glyph_width.abs() < 0.001 {
1217 return Ok(());
1218 }
1219
1220 if let Some(data) = font_data {
1222 if let Ok(face) = ttf_parser::Face::parse(data, 0) {
1223 let glyph_id = if is_cid {
1224 if let Some(map) = cid_to_gid_map {
1226 let gid = map
1227 .get(code as usize)
1228 .copied()
1229 .unwrap_or(code as u16);
1230 ttf_parser::GlyphId(gid)
1231 } else {
1232 ttf_parser::GlyphId(code as u16)
1234 }
1235 } else {
1236 crate::glyph::char_code_to_glyph_id(&face, code)
1237 };
1238
1239 let gid_raw = glyph_id.0;
1240 let cached_path = self.glyph_cache.get_or_insert(data, gid_raw, || {
1241 crate::glyph::glyph_outline(&face, glyph_id)
1242 }).cloned();
1243
1244 if let Some(path) = cached_path {
1245 let upem = crate::glyph::units_per_em(&face);
1246 if upem > 0.0 {
1247 let scale = font_size / upem;
1251 let glyph_matrix = Matrix {
1252 a: scale,
1253 b: 0.0,
1254 c: 0.0,
1255 d: scale, e: 0.0,
1257 f: text_rise,
1258 };
1259
1260 let text_rendering_matrix = glyph_matrix
1261 .concat(&self.state.text_matrix)
1262 .concat(&self.state.ctm)
1263 .concat(&self.page_transform);
1264
1265 let transform = text_rendering_matrix.to_skia();
1266 let color = self.state.fill_color_rgba();
1267 let bm = self.blend_mode();
1268 self.device
1269 .fill_path(&path, FillRule::Winding, transform, color, bm);
1270 return Ok(());
1271 }
1272 }
1273 }
1274 }
1275
1276 let text_rendering_matrix = self
1278 .state
1279 .text_matrix
1280 .concat(&self.state.ctm)
1281 .concat(&self.page_transform);
1282
1283 let mut pb = PathBuilder::new();
1284 let x = 0.0_f32;
1285 let y = (text_rise - font_size * 0.2) as f32;
1286 let w = glyph_width as f32;
1287 let h = font_size as f32 * 0.8;
1288 pb.move_to(x, y);
1289 pb.line_to(x + w, y);
1290 pb.line_to(x + w, y + h);
1291 pb.line_to(x, y + h);
1292 pb.close();
1293
1294 if let Some(path) = pb.finish() {
1295 let transform = text_rendering_matrix.to_skia();
1296 let color = self.state.fill_color_rgba();
1297 let bm = self.blend_mode();
1298 self.device
1299 .fill_path(&path, FillRule::Winding, transform, color, bm);
1300 }
1301
1302 Ok(())
1303 }
1304
1305 fn adjust_text_position(&mut self, amount: f64) {
1306 let font_size = self.state.text.font_size;
1308 let horiz_scaling = self.state.text.horiz_scaling;
1309 let tx = -amount / 1000.0 * font_size * horiz_scaling;
1310 let advance = Matrix::translate(tx, 0.0);
1311 self.state.text_matrix = advance.concat(&self.state.text_matrix);
1312 }
1313
1314 fn do_xobject(&mut self, name: &[u8], page: &PageInfo) -> Result<()> {
1317 let xobj = self.resolve_xobject(name, page)?;
1318 let xobj = match xobj {
1319 Some(x) => x,
1320 None => return Ok(()),
1321 };
1322
1323 match xobj {
1324 XObjectData::Image { dict, data } => {
1325 let _ = self.render_image(&dict, &data); }
1327 XObjectData::Form { dict, data } => {
1328 if self.xobject_depth > 10 {
1329 return Ok(()); }
1331 self.xobject_depth += 1;
1332 let _ = self.render_form_xobject(&dict, &data, page);
1333 self.xobject_depth -= 1;
1334 }
1335 }
1336
1337 Ok(())
1338 }
1339
1340 fn resolve_xobject(&mut self, name: &[u8], page: &PageInfo) -> Result<Option<XObjectData>> {
1341 let resources_obj = match &page.resources_ref {
1342 Some(obj) => self.resolve_object(obj)?,
1343 None => return Ok(None),
1344 };
1345
1346 let resources_dict = match &resources_obj {
1347 PdfObject::Dict(d) => d.clone(),
1348 _ => return Ok(None),
1349 };
1350
1351 let xobject_dict_obj = match resources_dict.get(b"XObject") {
1352 Some(PdfObject::Dict(d)) => PdfObject::Dict(d.clone()),
1353 Some(PdfObject::Reference(r)) => {
1354 let r = r.clone();
1355 self.doc.resolve(&r)?
1356 }
1357 _ => return Ok(None),
1358 };
1359
1360 let xobject_dict = match &xobject_dict_obj {
1361 PdfObject::Dict(d) => d,
1362 _ => return Ok(None),
1363 };
1364
1365 let xobj_ref = match xobject_dict.get(name) {
1366 Some(PdfObject::Reference(r)) => r.clone(),
1367 _ => return Ok(None),
1368 };
1369
1370 let xobj = self.doc.resolve(&xobj_ref)?;
1371
1372 match xobj {
1373 PdfObject::Stream { dict, data } => {
1374 let subtype = dict.get_name(b"Subtype").unwrap_or(b"");
1375 match subtype {
1376 b"Image" => {
1377 let filter = dict.get(b"Filter").and_then(|o| o.as_name());
1379 let image_data = if filter == Some(b"DCTDecode") {
1380 data.clone()
1381 } else {
1382 match self.doc.decode_stream(&dict, &data) {
1383 Ok(d) => d,
1384 Err(_) => return Ok(None),
1385 }
1386 };
1387 Ok(Some(XObjectData::Image {
1388 dict,
1389 data: image_data,
1390 }))
1391 }
1392 b"Form" => {
1393 match self.doc.decode_stream(&dict, &data) {
1394 Ok(decoded) => Ok(Some(XObjectData::Form {
1395 dict,
1396 data: decoded,
1397 })),
1398 Err(_) => Ok(None),
1399 }
1400 }
1401 _ => Ok(None),
1402 }
1403 }
1404 _ => Ok(None),
1405 }
1406 }
1407
1408 fn render_image(&mut self, dict: &PdfDict, data: &[u8]) -> Result<()> {
1409 let info = image::image_info(dict);
1410
1411 if let Some(ref img_info) = info {
1413 if img_info.is_mask {
1414 return self.render_image_mask(img_info, data, dict);
1415 }
1416 }
1417
1418 let decoded = image::decode_image(data, dict).map_err(|e| RenderError::Core(e))?;
1419
1420 let mut rgba_data = image_to_rgba(&decoded);
1422 let w = decoded.width;
1423 let h = decoded.height;
1424
1425 if let Some(PdfObject::Reference(smask_ref)) = dict.get(b"SMask") {
1427 let smask_ref = smask_ref.clone();
1428 if let Ok(smask_obj) = self.doc.resolve(&smask_ref) {
1429 if let PdfObject::Stream {
1430 dict: smask_dict,
1431 data: smask_data,
1432 } = smask_obj
1433 {
1434 self.apply_image_smask(
1435 &mut rgba_data,
1436 w,
1437 h,
1438 &smask_dict,
1439 &smask_data,
1440 );
1441 }
1442 }
1443 }
1444
1445 if let Some(PdfObject::Reference(mask_ref)) = dict.get(b"Mask") {
1447 let mask_ref = mask_ref.clone();
1448 if let Ok(mask_obj) = self.doc.resolve(&mask_ref) {
1449 if let PdfObject::Stream {
1450 dict: mask_dict,
1451 data: mask_data,
1452 } = mask_obj
1453 {
1454 self.apply_image_explicit_mask(
1455 &mut rgba_data,
1456 w,
1457 h,
1458 &mask_dict,
1459 &mask_data,
1460 );
1461 }
1462 }
1463 }
1464
1465 let img_pixmap =
1466 match tiny_skia::Pixmap::from_vec(rgba_data, tiny_skia::IntSize::from_wh(w, h).unwrap())
1467 {
1468 Some(p) => p,
1469 None => return Ok(()),
1470 };
1471
1472 let image_transform = Matrix {
1474 a: 1.0 / w as f64,
1475 b: 0.0,
1476 c: 0.0,
1477 d: -1.0 / h as f64, e: 0.0,
1479 f: 1.0,
1480 };
1481
1482 let full_transform = image_transform
1483 .concat(&self.state.ctm)
1484 .concat(&self.page_transform);
1485
1486 let bm = self.blend_mode();
1487 self.apply_soft_mask_to_device();
1488 self.device.draw_image(
1489 &img_pixmap.as_ref(),
1490 full_transform.to_skia(),
1491 self.state.fill_alpha as f32,
1492 bm,
1493 );
1494 self.restore_clip_after_soft_mask();
1495
1496 Ok(())
1497 }
1498
1499 fn render_image_mask(
1502 &mut self,
1503 info: &image::ImageInfo,
1504 data: &[u8],
1505 dict: &PdfDict,
1506 ) -> Result<()> {
1507 let w = info.width;
1508 let h = info.height;
1509 let pixel_count = (w * h) as usize;
1510
1511 let decoded_data = match dict.get(b"Filter") {
1513 Some(_) => {
1514 match image::decode_image(data, dict) {
1515 Ok(img) => img.data,
1516 Err(_) => data.to_vec(),
1517 }
1518 }
1519 None => data.to_vec(),
1520 };
1521
1522 let invert = dict
1525 .get_array(b"Decode")
1526 .map(|arr| {
1527 let d0 = arr.first().and_then(|o| o.as_f64()).unwrap_or(0.0);
1528 d0 != 0.0 })
1530 .unwrap_or(false);
1531
1532 let fill_color = self.state.fill_color_rgba();
1533 let mut rgba = vec![0u8; pixel_count * 4];
1534
1535 for i in 0..pixel_count {
1537 let byte_idx = i / 8;
1538 let bit_idx = 7 - (i % 8);
1539 let bit = if byte_idx < decoded_data.len() {
1540 (decoded_data[byte_idx] >> bit_idx) & 1
1541 } else {
1542 0
1543 };
1544
1545 let paint = if invert { bit == 1 } else { bit == 0 };
1547
1548 if paint {
1549 rgba[i * 4] = fill_color[0];
1550 rgba[i * 4 + 1] = fill_color[1];
1551 rgba[i * 4 + 2] = fill_color[2];
1552 rgba[i * 4 + 3] = fill_color[3];
1553 }
1554 }
1556
1557 let img_pixmap =
1558 match tiny_skia::Pixmap::from_vec(rgba, tiny_skia::IntSize::from_wh(w, h).unwrap()) {
1559 Some(p) => p,
1560 None => return Ok(()),
1561 };
1562
1563 let image_transform = Matrix {
1564 a: 1.0 / w as f64,
1565 b: 0.0,
1566 c: 0.0,
1567 d: -1.0 / h as f64,
1568 e: 0.0,
1569 f: 1.0,
1570 };
1571
1572 let full_transform = image_transform
1573 .concat(&self.state.ctm)
1574 .concat(&self.page_transform);
1575
1576 let bm = self.blend_mode();
1577 self.device.draw_image(
1578 &img_pixmap.as_ref(),
1579 full_transform.to_skia(),
1580 self.state.fill_alpha as f32,
1581 bm,
1582 );
1583
1584 Ok(())
1585 }
1586
1587 fn apply_image_smask(
1589 &mut self,
1590 rgba: &mut [u8],
1591 w: u32,
1592 h: u32,
1593 smask_dict: &PdfDict,
1594 smask_data: &[u8],
1595 ) {
1596 let decoded = match self.doc.decode_stream(smask_dict, smask_data) {
1598 Ok(d) => d,
1599 Err(_) => return,
1600 };
1601
1602 let smask_decoded = match image::decode_image(&decoded, smask_dict) {
1603 Ok(img) => img,
1604 Err(_) => return,
1605 };
1606
1607 let pixel_count = (w * h) as usize;
1608 let mask_pixels = smask_decoded.data;
1609
1610 for i in 0..pixel_count {
1612 let mask_val = if smask_decoded.components == 1 {
1613 if smask_decoded.bpc == 8 {
1615 mask_pixels.get(i).copied().unwrap_or(255)
1616 } else if smask_decoded.bpc == 1 {
1617 if mask_pixels.get(i).copied().unwrap_or(255) != 0 {
1618 255
1619 } else {
1620 0
1621 }
1622 } else {
1623 mask_pixels.get(i).copied().unwrap_or(255)
1624 }
1625 } else {
1626 let idx = i * smask_decoded.components as usize;
1628 let r = mask_pixels.get(idx).copied().unwrap_or(255) as f32;
1629 let g = mask_pixels.get(idx + 1).copied().unwrap_or(255) as f32;
1630 let b = mask_pixels.get(idx + 2).copied().unwrap_or(255) as f32;
1631 (0.2126 * r + 0.7152 * g + 0.0722 * b).clamp(0.0, 255.0) as u8
1632 };
1633
1634 let existing_alpha = rgba[i * 4 + 3] as u16;
1636 rgba[i * 4 + 3] = ((existing_alpha * mask_val as u16) / 255) as u8;
1637 }
1638 }
1639
1640 fn apply_image_explicit_mask(
1642 &mut self,
1643 rgba: &mut [u8],
1644 w: u32,
1645 h: u32,
1646 mask_dict: &PdfDict,
1647 mask_data: &[u8],
1648 ) {
1649 let decoded = match self.doc.decode_stream(mask_dict, mask_data) {
1650 Ok(d) => d,
1651 Err(_) => return,
1652 };
1653
1654 let mask_decoded = match image::decode_image(&decoded, mask_dict) {
1655 Ok(img) => img,
1656 Err(_) => return,
1657 };
1658
1659 let pixel_count = (w * h) as usize;
1660
1661 let mask_w = mask_decoded.width as usize;
1663 let mask_h = mask_decoded.height as usize;
1664 let img_w = w as usize;
1665 let img_h = h as usize;
1666
1667 for y in 0..img_h {
1668 for x in 0..img_w {
1669 let i = y * img_w + x;
1670 if i >= pixel_count {
1671 break;
1672 }
1673
1674 let mx = if mask_w > 0 { x * mask_w / img_w } else { 0 };
1676 let my = if mask_h > 0 { y * mask_h / img_h } else { 0 };
1677 let mi = my * mask_w + mx;
1678
1679 let mask_bit = if mask_decoded.bpc == 1 {
1680 let byte_idx = mi / 8;
1682 let bit_idx = 7 - (mi % 8);
1683 if byte_idx < mask_decoded.data.len() {
1684 (mask_decoded.data[byte_idx] >> bit_idx) & 1
1685 } else {
1686 1
1687 }
1688 } else {
1689 if mi < mask_decoded.data.len() {
1691 if mask_decoded.data[mi] > 127 { 1 } else { 0 }
1692 } else {
1693 1
1694 }
1695 };
1696
1697 if mask_bit == 1 {
1699 rgba[i * 4 + 3] = 0; }
1701 }
1702 }
1703 }
1704
1705 fn render_inline_image(
1706 &mut self,
1707 _dict: &[(Vec<u8>, Operand)],
1708 _data: &[u8],
1709 ) -> Result<()> {
1710 Ok(())
1712 }
1713
1714 fn render_shading(&mut self, name: &[u8], page: &PageInfo) -> Result<()> {
1715 let resources_obj = match &page.resources_ref {
1716 Some(obj) => self.resolve_object(obj)?,
1717 None => return Ok(()),
1718 };
1719
1720 let resources_dict = match &resources_obj {
1721 PdfObject::Dict(d) => d.clone(),
1722 _ => return Ok(()),
1723 };
1724
1725 let shading_dict_obj = match resources_dict.get(b"Shading") {
1726 Some(PdfObject::Dict(d)) => PdfObject::Dict(d.clone()),
1727 Some(PdfObject::Reference(r)) => {
1728 let r = r.clone();
1729 self.doc.resolve(&r)?
1730 }
1731 _ => return Ok(()),
1732 };
1733
1734 let shading_container = match &shading_dict_obj {
1735 PdfObject::Dict(d) => d,
1736 _ => return Ok(()),
1737 };
1738
1739 let sh_obj = match shading_container.get(name) {
1740 Some(PdfObject::Reference(r)) => {
1741 let r = r.clone();
1742 self.doc.resolve(&r)?
1743 }
1744 Some(other) => other.clone(),
1745 None => return Ok(()),
1746 };
1747
1748 let (sh_dict, stream_data) = match &sh_obj {
1750 PdfObject::Dict(d) => (d.clone(), None),
1751 PdfObject::Stream { dict, data } => {
1752 let decoded = self.doc.decode_stream(dict, data).ok();
1753 (dict.clone(), decoded)
1754 }
1755 _ => return Ok(()),
1756 };
1757
1758 let mut resolved_dict = sh_dict;
1760 if let Some(PdfObject::Reference(func_ref)) = resolved_dict.get(b"Function").cloned() {
1761 if let Ok(func_obj) = self.doc.resolve(&func_ref) {
1762 resolved_dict.insert(b"Function".to_vec(), func_obj);
1763 }
1764 }
1765
1766 let clip = self.device.clip_mask.as_ref();
1767 crate::shading::render_shading(
1768 &mut self.device.pixmap,
1769 &resolved_dict,
1770 &self.state.ctm,
1771 &self.page_transform,
1772 clip,
1773 stream_data.as_deref(),
1774 );
1775
1776 Ok(())
1777 }
1778
1779 fn render_form_xobject(
1780 &mut self,
1781 dict: &PdfDict,
1782 data: &[u8],
1783 page: &PageInfo,
1784 ) -> Result<()> {
1785 let has_transparency_group = dict
1787 .get(b"Group")
1788 .and_then(|o| match o {
1789 PdfObject::Dict(d) => Some(d),
1790 _ => None,
1791 })
1792 .map(|group| group.get_name(b"S") == Some(b"Transparency"))
1793 .unwrap_or(false);
1794
1795 if has_transparency_group {
1796 return self.render_transparency_group(dict, data, page);
1797 }
1798
1799 self.state_stack.push(self.state.clone());
1801
1802 if let Some(matrix_arr) = dict.get_array(b"Matrix") {
1804 if matrix_arr.len() >= 6 {
1805 let m = Matrix {
1806 a: matrix_arr[0].as_f64().unwrap_or(1.0),
1807 b: matrix_arr[1].as_f64().unwrap_or(0.0),
1808 c: matrix_arr[2].as_f64().unwrap_or(0.0),
1809 d: matrix_arr[3].as_f64().unwrap_or(1.0),
1810 e: matrix_arr[4].as_f64().unwrap_or(0.0),
1811 f: matrix_arr[5].as_f64().unwrap_or(0.0),
1812 };
1813 self.state.ctm = m.concat(&self.state.ctm);
1814 }
1815 }
1816
1817 let ops = parse_content_stream(data).map_err(|e| RenderError::Core(e))?;
1819 self.execute_ops(&ops, page)?;
1820
1821 if let Some(s) = self.state_stack.pop() {
1823 self.state = s;
1824 }
1825
1826 Ok(())
1827 }
1828
1829 fn render_transparency_group(
1832 &mut self,
1833 dict: &PdfDict,
1834 data: &[u8],
1835 page: &PageInfo,
1836 ) -> Result<()> {
1837 let w = self.device.pixmap.width();
1838 let h = self.device.pixmap.height();
1839
1840 let mut temp_pixmap = match Pixmap::new(w, h) {
1842 Some(p) => p,
1843 None => {
1844 return self.render_form_xobject_direct(dict, data, page);
1846 }
1847 };
1848
1849 let is_isolated = dict
1851 .get(b"Group")
1852 .and_then(|o| match o {
1853 PdfObject::Dict(d) => Some(d),
1854 _ => None,
1855 })
1856 .and_then(|group| group.get(b"I"))
1857 .and_then(|o| o.as_bool())
1858 .unwrap_or(false);
1859
1860 if !is_isolated {
1862 temp_pixmap
1863 .data_mut()
1864 .copy_from_slice(self.device.pixmap.data());
1865 }
1866
1867 let saved_clip = self.device.clip_mask.take();
1869 std::mem::swap(&mut self.device.pixmap, &mut temp_pixmap);
1870
1871 self.state_stack.push(self.state.clone());
1873
1874 if let Some(matrix_arr) = dict.get_array(b"Matrix") {
1876 if matrix_arr.len() >= 6 {
1877 let m = Matrix {
1878 a: matrix_arr[0].as_f64().unwrap_or(1.0),
1879 b: matrix_arr[1].as_f64().unwrap_or(0.0),
1880 c: matrix_arr[2].as_f64().unwrap_or(0.0),
1881 d: matrix_arr[3].as_f64().unwrap_or(1.0),
1882 e: matrix_arr[4].as_f64().unwrap_or(0.0),
1883 f: matrix_arr[5].as_f64().unwrap_or(0.0),
1884 };
1885 self.state.ctm = m.concat(&self.state.ctm);
1886 }
1887 }
1888
1889 let ops = parse_content_stream(data).map_err(|e| RenderError::Core(e))?;
1891 let _ = self.execute_ops(&ops, page);
1892
1893 if let Some(s) = self.state_stack.pop() {
1895 self.state = s;
1896 }
1897
1898 std::mem::swap(&mut self.device.pixmap, &mut temp_pixmap);
1900 self.device.clip_mask = saved_clip;
1901
1902 let alpha = self.state.fill_alpha as f32;
1904 let bm = self.blend_mode();
1905 self.device.draw_pixmap(
1906 &temp_pixmap.as_ref(),
1907 Transform::identity(),
1908 alpha,
1909 bm,
1910 );
1911
1912 Ok(())
1913 }
1914
1915 fn render_form_xobject_direct(
1918 &mut self,
1919 dict: &PdfDict,
1920 data: &[u8],
1921 page: &PageInfo,
1922 ) -> Result<()> {
1923 self.state_stack.push(self.state.clone());
1924
1925 if let Some(matrix_arr) = dict.get_array(b"Matrix") {
1926 if matrix_arr.len() >= 6 {
1927 let m = Matrix {
1928 a: matrix_arr[0].as_f64().unwrap_or(1.0),
1929 b: matrix_arr[1].as_f64().unwrap_or(0.0),
1930 c: matrix_arr[2].as_f64().unwrap_or(0.0),
1931 d: matrix_arr[3].as_f64().unwrap_or(1.0),
1932 e: matrix_arr[4].as_f64().unwrap_or(0.0),
1933 f: matrix_arr[5].as_f64().unwrap_or(0.0),
1934 };
1935 self.state.ctm = m.concat(&self.state.ctm);
1936 }
1937 }
1938
1939 let ops = parse_content_stream(data).map_err(|e| RenderError::Core(e))?;
1940 self.execute_ops(&ops, page)?;
1941
1942 if let Some(s) = self.state_stack.pop() {
1943 self.state = s;
1944 }
1945
1946 Ok(())
1947 }
1948
1949 fn apply_extgstate(&mut self, name: &[u8], page: &PageInfo) -> Result<()> {
1950 let resources_obj = match &page.resources_ref {
1951 Some(obj) => self.resolve_object(obj)?,
1952 None => return Ok(()),
1953 };
1954
1955 let resources_dict = match &resources_obj {
1956 PdfObject::Dict(d) => d.clone(),
1957 _ => return Ok(()),
1958 };
1959
1960 let extgstate_dict_obj = match resources_dict.get(b"ExtGState") {
1961 Some(PdfObject::Dict(d)) => PdfObject::Dict(d.clone()),
1962 Some(PdfObject::Reference(r)) => {
1963 let r = r.clone();
1964 self.doc.resolve(&r)?
1965 }
1966 _ => return Ok(()),
1967 };
1968
1969 let extgstate_dict = match &extgstate_dict_obj {
1970 PdfObject::Dict(d) => d,
1971 _ => return Ok(()),
1972 };
1973
1974 let gs_obj = match extgstate_dict.get(name) {
1975 Some(PdfObject::Reference(r)) => {
1976 let r = r.clone();
1977 self.doc.resolve(&r)?
1978 }
1979 Some(other) => other.clone(),
1980 None => return Ok(()),
1981 };
1982
1983 if let PdfObject::Dict(gs_dict) = &gs_obj {
1984 if let Some(lw) = gs_dict.get(b"LW").and_then(|o| o.as_f64()) {
1985 self.state.line_width = lw;
1986 }
1987 if let Some(lc) = gs_dict.get(b"LC").and_then(|o| o.as_i64()) {
1988 self.state.line_cap = match lc {
1989 1 => LineCap::Round,
1990 2 => LineCap::Square,
1991 _ => LineCap::Butt,
1992 };
1993 }
1994 if let Some(lj) = gs_dict.get(b"LJ").and_then(|o| o.as_i64()) {
1995 self.state.line_join = match lj {
1996 1 => LineJoin::Round,
1997 2 => LineJoin::Bevel,
1998 _ => LineJoin::Miter,
1999 };
2000 }
2001 if let Some(ml) = gs_dict.get(b"ML").and_then(|o| o.as_f64()) {
2002 self.state.miter_limit = ml;
2003 }
2004 if let Some(a) = gs_dict.get(b"ca").and_then(|o| o.as_f64()) {
2006 self.state.fill_alpha = a;
2007 }
2008 if let Some(a) = gs_dict.get(b"CA").and_then(|o| o.as_f64()) {
2010 self.state.stroke_alpha = a;
2011 }
2012 if let Some(bm_name) = gs_dict.get(b"BM").and_then(|o| o.as_name()) {
2014 self.state.blend_mode = PdfBlendMode::from_name(bm_name);
2015 }
2016
2017 match gs_dict.get(b"SMask") {
2019 Some(PdfObject::Name(n)) if n == b"None" => {
2020 self.state.soft_mask = None;
2021 }
2022 Some(PdfObject::Dict(smask_dict)) => {
2023 let _ = self.apply_soft_mask(smask_dict.clone(), page);
2024 }
2025 _ => {}
2026 }
2027 }
2028
2029 Ok(())
2030 }
2031
2032 fn apply_soft_mask(&mut self, smask_dict: PdfDict, page: &PageInfo) -> Result<()> {
2034 let subtype = match smask_dict.get_name(b"S") {
2036 Some(b"Luminosity") => SoftMaskSubtype::Luminosity,
2037 Some(b"Alpha") => SoftMaskSubtype::Alpha,
2038 _ => return Ok(()), };
2040
2041 let form_obj = match smask_dict.get(b"G") {
2043 Some(PdfObject::Reference(r)) => {
2044 let r = r.clone();
2045 match self.doc.resolve(&r) {
2046 Ok(obj) => obj,
2047 Err(_) => return Ok(()),
2048 }
2049 }
2050 Some(other) => other.clone(),
2051 None => return Ok(()),
2052 };
2053
2054 let (form_dict, form_data) = match form_obj {
2055 PdfObject::Stream { dict, data } => {
2056 match self.doc.decode_stream(&dict, &data) {
2057 Ok(decoded) => (dict, decoded),
2058 Err(_) => return Ok(()),
2059 }
2060 }
2061 _ => return Ok(()),
2062 };
2063
2064 let w = self.device.pixmap.width();
2065 let h = self.device.pixmap.height();
2066
2067 let mut mask_pixmap = match Pixmap::new(w, h) {
2069 Some(p) => p,
2070 None => return Ok(()),
2071 };
2072
2073 if subtype == SoftMaskSubtype::Luminosity {
2075 mask_pixmap.fill(tiny_skia::Color::BLACK);
2078 }
2079
2080 let saved_clip = self.device.clip_mask.take();
2082 std::mem::swap(&mut self.device.pixmap, &mut mask_pixmap);
2083
2084 self.state_stack.push(self.state.clone());
2086
2087 if let Some(matrix_arr) = form_dict.get_array(b"Matrix") {
2089 if matrix_arr.len() >= 6 {
2090 let m = Matrix {
2091 a: matrix_arr[0].as_f64().unwrap_or(1.0),
2092 b: matrix_arr[1].as_f64().unwrap_or(0.0),
2093 c: matrix_arr[2].as_f64().unwrap_or(0.0),
2094 d: matrix_arr[3].as_f64().unwrap_or(1.0),
2095 e: matrix_arr[4].as_f64().unwrap_or(0.0),
2096 f: matrix_arr[5].as_f64().unwrap_or(0.0),
2097 };
2098 self.state.ctm = m.concat(&self.state.ctm);
2099 }
2100 }
2101
2102 if let Ok(ops) = parse_content_stream(&form_data) {
2104 let _ = self.execute_ops(&ops, page);
2105 }
2106
2107 if let Some(s) = self.state_stack.pop() {
2109 self.state = s;
2110 }
2111
2112 std::mem::swap(&mut self.device.pixmap, &mut mask_pixmap);
2114 self.device.clip_mask = saved_clip;
2115
2116 if let Some(mask) = self.pixmap_to_mask(&mask_pixmap, subtype) {
2118 self.state.soft_mask = Some(SoftMask { mask, subtype });
2119 }
2120
2121 Ok(())
2122 }
2123
2124 fn pixmap_to_mask(&self, pixmap: &Pixmap, subtype: SoftMaskSubtype) -> Option<Mask> {
2126 let w = pixmap.width();
2127 let h = pixmap.height();
2128 let mut mask = Mask::new(w, h)?;
2129 let mask_data = mask.data_mut();
2130 let src_data = pixmap.data();
2131
2132 for i in 0..(w * h) as usize {
2133 let idx = i * 4;
2134 if idx + 3 >= src_data.len() {
2135 break;
2136 }
2137 let value = match subtype {
2138 SoftMaskSubtype::Luminosity => {
2139 let r = src_data[idx] as f32 / 255.0;
2141 let g = src_data[idx + 1] as f32 / 255.0;
2142 let b = src_data[idx + 2] as f32 / 255.0;
2143 (0.2126 * r + 0.7152 * g + 0.0722 * b).clamp(0.0, 1.0) * 255.0
2144 }
2145 SoftMaskSubtype::Alpha => {
2146 src_data[idx + 3] as f32
2147 }
2148 };
2149 mask_data[i] = value as u8;
2150 }
2151
2152 Some(mask)
2153 }
2154
2155 fn resolve_pattern(&mut self, name: &[u8], page: &PageInfo) -> Result<Option<PdfObject>> {
2159 let resources_obj = match &page.resources_ref {
2160 Some(obj) => self.resolve_object(obj)?,
2161 None => return Ok(None),
2162 };
2163
2164 let resources_dict = match &resources_obj {
2165 PdfObject::Dict(d) => d.clone(),
2166 _ => return Ok(None),
2167 };
2168
2169 let pattern_dict_obj = match resources_dict.get(b"Pattern") {
2170 Some(PdfObject::Dict(d)) => PdfObject::Dict(d.clone()),
2171 Some(PdfObject::Reference(r)) => {
2172 let r = r.clone();
2173 self.doc.resolve(&r)?
2174 }
2175 _ => return Ok(None),
2176 };
2177
2178 let pattern_dict = match &pattern_dict_obj {
2179 PdfObject::Dict(d) => d,
2180 _ => return Ok(None),
2181 };
2182
2183 match pattern_dict.get(name) {
2184 Some(PdfObject::Reference(r)) => {
2185 let r = r.clone();
2186 Ok(Some(self.doc.resolve(&r)?))
2187 }
2188 Some(other) => Ok(Some(other.clone())),
2189 None => Ok(None),
2190 }
2191 }
2192
2193 fn render_tiling_pattern(
2195 &mut self,
2196 pattern_dict: &PdfDict,
2197 pattern_data: &[u8],
2198 page: &PageInfo,
2199 ) -> Result<Option<Pixmap>> {
2200 let xstep = pattern_dict
2201 .get(b"XStep")
2202 .and_then(|o| o.as_f64())
2203 .unwrap_or(1.0)
2204 .abs();
2205 let ystep = pattern_dict
2206 .get(b"YStep")
2207 .and_then(|o| o.as_f64())
2208 .unwrap_or(1.0)
2209 .abs();
2210
2211 if xstep < 1.0 || ystep < 1.0 {
2212 return Ok(None);
2213 }
2214
2215 let effective = self.state.ctm.concat(&self.page_transform);
2217 let sx = (effective.a * effective.a + effective.b * effective.b)
2218 .sqrt()
2219 .abs();
2220 let sy = (effective.c * effective.c + effective.d * effective.d)
2221 .sqrt()
2222 .abs();
2223
2224 let cell_w = (xstep * sx).ceil().max(1.0).min(2048.0) as u32;
2226 let cell_h = (ystep * sy).ceil().max(1.0).min(2048.0) as u32;
2227
2228 let mut cell_pixmap = match Pixmap::new(cell_w, cell_h) {
2229 Some(p) => p,
2230 None => return Ok(None),
2231 };
2232
2233 let pattern_matrix = if let Some(matrix_arr) = pattern_dict.get_array(b"Matrix") {
2235 if matrix_arr.len() >= 6 {
2236 Matrix {
2237 a: matrix_arr[0].as_f64().unwrap_or(1.0),
2238 b: matrix_arr[1].as_f64().unwrap_or(0.0),
2239 c: matrix_arr[2].as_f64().unwrap_or(0.0),
2240 d: matrix_arr[3].as_f64().unwrap_or(1.0),
2241 e: matrix_arr[4].as_f64().unwrap_or(0.0),
2242 f: matrix_arr[5].as_f64().unwrap_or(0.0),
2243 }
2244 } else {
2245 Matrix::identity()
2246 }
2247 } else {
2248 Matrix::identity()
2249 };
2250
2251 let scale_to_device = Matrix::scale(
2254 cell_w as f64 / xstep,
2255 cell_h as f64 / ystep,
2256 );
2257 let cell_transform = pattern_matrix.concat(&scale_to_device);
2258
2259 let saved_clip = self.device.clip_mask.take();
2261 std::mem::swap(&mut self.device.pixmap, &mut cell_pixmap);
2262
2263 self.state_stack.push(self.state.clone());
2265 let saved_page_transform = self.page_transform;
2266
2267 self.state.ctm = Matrix::identity();
2269 self.page_transform = cell_transform;
2270
2271 if let Ok(ops) = parse_content_stream(pattern_data) {
2273 let _ = self.execute_ops(&ops, page);
2274 }
2275
2276 self.page_transform = saved_page_transform;
2278 if let Some(s) = self.state_stack.pop() {
2279 self.state = s;
2280 }
2281
2282 std::mem::swap(&mut self.device.pixmap, &mut cell_pixmap);
2284 self.device.clip_mask = saved_clip;
2285
2286 Ok(Some(cell_pixmap))
2287 }
2288
2289 fn try_fill_with_pattern(&mut self, path: &tiny_skia::Path, rule: FillRule, page: &PageInfo) -> bool {
2292 let pattern_name = match &self.state.fill_pattern_name {
2293 Some(name) => name.clone(),
2294 None => return false,
2295 };
2296
2297 if let Ok(Some(pattern_pixmap)) = self.resolve_and_render_pattern(&pattern_name, page) {
2298 let transform = self.effective_transform();
2299 let bm = self.blend_mode();
2300 self.device.fill_path_with_pattern(
2301 path,
2302 rule,
2303 transform,
2304 &pattern_pixmap.as_ref(),
2305 Transform::identity(),
2306 bm,
2307 );
2308 true
2309 } else {
2310 false
2311 }
2312 }
2313
2314 fn try_stroke_with_pattern(&mut self, path: &tiny_skia::Path, page: &PageInfo) -> bool {
2317 let pattern_name = match &self.state.stroke_pattern_name {
2318 Some(name) => name.clone(),
2319 None => return false,
2320 };
2321
2322 if let Ok(Some(pattern_pixmap)) = self.resolve_and_render_pattern(&pattern_name, page) {
2323 let transform = self.effective_transform();
2324 let bm = self.blend_mode();
2325 self.device.stroke_path_with_pattern(
2326 path,
2327 transform,
2328 &self.state,
2329 &pattern_pixmap.as_ref(),
2330 Transform::identity(),
2331 bm,
2332 );
2333 true
2334 } else {
2335 false
2336 }
2337 }
2338
2339 fn resolve_and_render_pattern(
2341 &mut self,
2342 name: &[u8],
2343 page: &PageInfo,
2344 ) -> Result<Option<Pixmap>> {
2345 let pattern_obj = match self.resolve_pattern(name, page)? {
2346 Some(obj) => obj,
2347 None => return Ok(None),
2348 };
2349
2350 match &pattern_obj {
2351 PdfObject::Stream { dict, data } => {
2352 let pattern_type = dict.get_i64(b"PatternType").unwrap_or(0);
2353 match pattern_type {
2354 1 => {
2355 let decoded = match self.doc.decode_stream(dict, data) {
2357 Ok(d) => d,
2358 Err(_) => return Ok(None),
2359 };
2360 let dict = dict.clone();
2361 self.render_tiling_pattern(&dict, &decoded, page)
2362 }
2363 2 => {
2364 self.render_shading_pattern(dict, page)
2366 }
2367 _ => Ok(None),
2368 }
2369 }
2370 PdfObject::Dict(dict) => {
2371 let pattern_type = dict.get_i64(b"PatternType").unwrap_or(0);
2372 if pattern_type == 2 {
2373 self.render_shading_pattern(dict, page)
2374 } else {
2375 Ok(None)
2376 }
2377 }
2378 _ => Ok(None),
2379 }
2380 }
2381
2382 fn render_shading_pattern(
2384 &mut self,
2385 pattern_dict: &PdfDict,
2386 _page: &PageInfo,
2387 ) -> Result<Option<Pixmap>> {
2388 let shading_obj = match pattern_dict.get(b"Shading") {
2390 Some(PdfObject::Reference(r)) => {
2391 let r = r.clone();
2392 match self.doc.resolve(&r) {
2393 Ok(obj) => obj,
2394 Err(_) => return Ok(None),
2395 }
2396 }
2397 Some(other) => other.clone(),
2398 None => return Ok(None),
2399 };
2400
2401 let (shading_dict, stream_data) = match &shading_obj {
2403 PdfObject::Dict(d) => (d.clone(), None),
2404 PdfObject::Stream { dict, data } => {
2405 let decoded = self.doc.decode_stream(dict, data).ok();
2406 (dict.clone(), decoded)
2407 }
2408 _ => return Ok(None),
2409 };
2410
2411 let mut resolved_shading = shading_dict;
2413 if let Some(PdfObject::Reference(func_ref)) = resolved_shading.get(b"Function").cloned() {
2414 if let Ok(func_obj) = self.doc.resolve(&func_ref) {
2415 resolved_shading.insert(b"Function".to_vec(), func_obj);
2416 }
2417 }
2418
2419 let w = self.device.pixmap.width();
2420 let h = self.device.pixmap.height();
2421
2422 let mut shading_pixmap = match Pixmap::new(w, h) {
2423 Some(p) => p,
2424 None => return Ok(None),
2425 };
2426
2427 let pattern_matrix = if let Some(matrix_arr) = pattern_dict.get_array(b"Matrix") {
2429 if matrix_arr.len() >= 6 {
2430 Matrix {
2431 a: matrix_arr[0].as_f64().unwrap_or(1.0),
2432 b: matrix_arr[1].as_f64().unwrap_or(0.0),
2433 c: matrix_arr[2].as_f64().unwrap_or(0.0),
2434 d: matrix_arr[3].as_f64().unwrap_or(1.0),
2435 e: matrix_arr[4].as_f64().unwrap_or(0.0),
2436 f: matrix_arr[5].as_f64().unwrap_or(0.0),
2437 }
2438 } else {
2439 Matrix::identity()
2440 }
2441 } else {
2442 Matrix::identity()
2443 };
2444
2445 let effective_ctm = pattern_matrix.concat(&self.state.ctm);
2447
2448 let clip = self.device.clip_mask.as_ref();
2449 crate::shading::render_shading(
2450 &mut shading_pixmap,
2451 &resolved_shading,
2452 &effective_ctm,
2453 &self.page_transform,
2454 clip,
2455 stream_data.as_deref(),
2456 );
2457
2458 Ok(Some(shading_pixmap))
2459 }
2460}
2461
2462enum XObjectData {
2463 Image { dict: PdfDict, data: Vec<u8> },
2464 Form { dict: PdfDict, data: Vec<u8> },
2465}
2466
2467fn image_to_rgba(img: &image::DecodedImage) -> Vec<u8> {
2469 let pixel_count = (img.width * img.height) as usize;
2470 let mut rgba = vec![255u8; pixel_count * 4];
2471
2472 match img.components {
2473 1 => {
2474 for i in 0..pixel_count.min(img.data.len()) {
2476 let g = img.data[i];
2477 rgba[i * 4] = g;
2478 rgba[i * 4 + 1] = g;
2479 rgba[i * 4 + 2] = g;
2480 }
2481 }
2482 3 => {
2483 for i in 0..pixel_count.min(img.data.len() / 3) {
2485 rgba[i * 4] = img.data[i * 3];
2486 rgba[i * 4 + 1] = img.data[i * 3 + 1];
2487 rgba[i * 4 + 2] = img.data[i * 3 + 2];
2488 }
2489 }
2490 4 => {
2491 for i in 0..pixel_count.min(img.data.len() / 4) {
2493 let c = img.data[i * 4] as f64 / 255.0;
2494 let m = img.data[i * 4 + 1] as f64 / 255.0;
2495 let y = img.data[i * 4 + 2] as f64 / 255.0;
2496 let k = img.data[i * 4 + 3] as f64 / 255.0;
2497 rgba[i * 4] = ((1.0 - c) * (1.0 - k) * 255.0) as u8;
2498 rgba[i * 4 + 1] = ((1.0 - m) * (1.0 - k) * 255.0) as u8;
2499 rgba[i * 4 + 2] = ((1.0 - y) * (1.0 - k) * 255.0) as u8;
2500 }
2501 }
2502 _ => {
2503 for i in 0..pixel_count {
2505 rgba[i * 4] = 0;
2506 rgba[i * 4 + 1] = 0;
2507 rgba[i * 4 + 2] = 0;
2508 }
2509 }
2510 }
2511
2512 rgba
2513}
2514
2515fn cs_from_name(name: &[u8]) -> ColorSpace {
2516 match name {
2517 b"DeviceGray" | b"G" => ColorSpace::DeviceGray,
2518 b"DeviceRGB" | b"RGB" => ColorSpace::DeviceRGB,
2519 b"DeviceCMYK" | b"CMYK" => ColorSpace::DeviceCMYK,
2520 _ => ColorSpace::DeviceRGB, }
2522}
2523
2524fn parse_cid_gid_stream(data: &[u8]) -> Vec<u16> {
2526 data.chunks(2)
2527 .map(|c| {
2528 if c.len() == 2 {
2529 ((c[0] as u16) << 8) | (c[1] as u16)
2530 } else {
2531 c[0] as u16
2532 }
2533 })
2534 .collect()
2535}
2536
2537fn f(operands: &[Operand], idx: usize) -> f64 {
2539 operands.get(idx).and_then(|o| o.as_f64()).unwrap_or(0.0)
2540}