1use super::*;
8
9impl PdfDocument {
10 pub fn extract_paths(&self, page_index: usize) -> Result<Vec<crate::elements::PathContent>> {
47 use crate::content::{Operator, parse_content_stream_paths_only};
48 use crate::elements::{LineCap, LineJoin};
49 use crate::extractors::paths::{FillRule, PathExtractor, PathGraphicsStateStack};
50 use crate::layout::Color;
51
52 let page = self.get_page(page_index)?;
53 let page_dict = page.as_dict().ok_or_else(|| Error::ParseError {
54 offset: 0,
55 reason: "Page is not a dictionary".to_string(),
56 })?;
57
58 let content_data = match self.get_page_content_data(page_index) {
60 Ok(data) => data,
61 Err(error) => {
62 tracing::warn!(
63 target: crate::LOG_TARGET_ROOT,
64 operation = "decode_page_path_content",
65 page_index,
66 error_code = error.telemetry_code(),
67 error_offset = ?error.telemetry_offset(),
68 "returning empty paths after decode failure"
69 );
70 return Ok(Vec::new());
71 }
72 };
73
74 let operators = match parse_content_stream_paths_only(&content_data) {
75 Ok(ops) => ops,
76 Err(error) => {
77 tracing::warn!(
78 target: crate::LOG_TARGET_ROOT,
79 operation = "parse_page_paths",
80 page_index,
81 error_code = error.telemetry_code(),
82 error_offset = ?error.telemetry_offset(),
83 "returning empty paths after parse failure"
84 );
85 return Ok(Vec::new());
86 }
87 };
88
89 let mut extractor = PathExtractor::new();
90 let mut state_stack = PathGraphicsStateStack::new();
91
92 if let Some(resources) = page_dict.get("Resources") {
93 let resolved_resources = if let Some(ref_obj) = resources.as_reference() {
94 self.load_object(ref_obj)?
95 } else {
96 resources.clone()
97 };
98 extractor.set_resources(resolved_resources);
99 }
100
101 for op in operators {
102 match op {
103 Operator::SaveState => {
104 state_stack.save();
105 }
106 Operator::RestoreState => {
107 state_stack.restore();
108 extractor.update_from_path_state(state_stack.current());
109 }
110 Operator::Cm { a, b, c, d, e, f } => {
111 let state = state_stack.current_mut();
112 let new_matrix = crate::content::Matrix { a, b, c, d, e, f };
113 state.ctm = new_matrix.multiply(&state.ctm);
115 extractor.set_ctm(state.ctm);
116 }
117
118 Operator::SetStrokeRgb { r, g, b } => {
119 state_stack.current_mut().stroke_color_rgb = (r, g, b);
120 extractor.set_stroke_color(Color::new(r, g, b));
121 }
122 Operator::SetStrokeGray { gray } => {
123 state_stack.current_mut().stroke_color_rgb = (gray, gray, gray);
124 extractor.set_stroke_color(Color::new(gray, gray, gray));
125 }
126 Operator::SetStrokeCmyk { c, m, y, k } => {
127 let (r, g, b) = crate::color::cmyk_to_rgb(c, m, y, k);
128 state_stack.current_mut().stroke_color_rgb = (r, g, b);
129 extractor.set_stroke_color(Color::new(r, g, b));
130 }
131
132 Operator::SetFillRgb { r, g, b } => {
133 state_stack.current_mut().fill_color_rgb = (r, g, b);
134 extractor.set_fill_color(Color::new(r, g, b));
135 }
136 Operator::SetFillGray { gray } => {
137 state_stack.current_mut().fill_color_rgb = (gray, gray, gray);
138 extractor.set_fill_color(Color::new(gray, gray, gray));
139 }
140 Operator::SetFillCmyk { c, m, y, k } => {
141 let (r, g, b) = crate::color::cmyk_to_rgb(c, m, y, k);
142 state_stack.current_mut().fill_color_rgb = (r, g, b);
143 extractor.set_fill_color(Color::new(r, g, b));
144 }
145
146 Operator::SetLineWidth { width } => {
147 state_stack.current_mut().line_width = width;
148 extractor.set_line_width(width);
149 }
150 Operator::SetLineCap { cap_style } => {
151 state_stack.current_mut().line_cap = cap_style;
152 let cap = match cap_style {
153 1 => LineCap::Round,
154 2 => LineCap::Square,
155 _ => LineCap::Butt,
156 };
157 extractor.set_line_cap(cap);
158 }
159 Operator::SetLineJoin { join_style } => {
160 state_stack.current_mut().line_join = join_style;
161 let join = match join_style {
162 1 => LineJoin::Round,
163 2 => LineJoin::Bevel,
164 _ => LineJoin::Miter,
165 };
166 extractor.set_line_join(join);
167 }
168
169 Operator::MoveTo { x, y } => {
170 extractor.move_to(x, y);
171 }
172 Operator::LineTo { x, y } => {
173 extractor.line_to(x, y);
174 }
175 Operator::CurveTo { x1, y1, x2, y2, x3, y3 } => {
176 extractor.curve_to(x1, y1, x2, y2, x3, y3);
177 }
178 Operator::CurveToV { x2, y2, x3, y3 } => {
179 extractor.curve_to_v(x2, y2, x3, y3);
180 }
181 Operator::CurveToY { x1, y1, x3, y3 } => {
182 extractor.curve_to_y(x1, y1, x3, y3);
183 }
184 Operator::Rectangle { x, y, width, height } => {
185 extractor.rectangle(x, y, width, height);
186 }
187 Operator::ClosePath => {
188 extractor.close_path();
189 }
190
191 Operator::Stroke => {
192 extractor.stroke();
193 }
194 Operator::Fill => {
195 extractor.fill(FillRule::NonZero);
196 }
197 Operator::FillEvenOdd => {
198 extractor.fill(FillRule::EvenOdd);
199 }
200 Operator::CloseStroke => {
201 extractor.close_and_stroke();
202 }
203 Operator::CloseFillStroke => {
204 extractor.close_fill_and_stroke(FillRule::NonZero);
205 }
206 Operator::FillStroke => {
207 extractor.fill_and_stroke(FillRule::NonZero);
208 }
209 Operator::FillStrokeEvenOdd => {
210 extractor.fill_and_stroke(FillRule::EvenOdd);
211 }
212 Operator::CloseFillStrokeEvenOdd => {
213 extractor.close_fill_and_stroke(FillRule::EvenOdd);
214 }
215 Operator::EndPath => {
216 extractor.end_path();
217 }
218
219 Operator::ClipNonZero => {
220 extractor.clip_non_zero();
221 }
222 Operator::ClipEvenOdd => {
223 extractor.clip_even_odd();
224 }
225
226 Operator::Do { name } => {
227 if let Err(error) = self.process_form_xobject_paths(&name, &mut extractor, &mut state_stack) {
228 tracing::warn!(target: LOG_TARGET,
229 error_code = error.telemetry_code(),
230 error_offset = ?error.telemetry_offset(),
231 "failed to process XObject in path extraction"
232 );
233 }
234 }
235
236 Operator::BeginMarkedContent { .. } => {
244 extractor.push_oc_layer(None);
245 }
246 Operator::BeginMarkedContentDict { tag, properties } => {
247 let layer = if tag == "OC" {
248 self.resolve_oc_layer_name(extractor.current_resources(), &properties)
249 } else {
250 None
251 };
252 extractor.push_oc_layer(layer);
253 }
254 Operator::EndMarkedContent => {
255 extractor.pop_oc_layer();
256 }
257
258 _ => {}
259 }
260 }
261
262 Ok(extractor.finish())
263 }
264
265 pub(super) fn resolve_oc_layer_name(
285 &self,
286 resources: Option<&crate::object::Object>,
287 properties: &crate::object::Object,
288 ) -> Option<String> {
289 const OC_NAME_MAX_DEPTH: u8 = 8;
290
291 if let Some(dict) = properties.as_dict() {
292 return self.read_oc_name(dict, OC_NAME_MAX_DEPTH);
293 }
294
295 let prop_name = properties.as_name()?;
296 let resources_obj = self.deref_object(resources?)?;
297 let properties_dict = resources_obj.as_dict()?.get("Properties")?;
298 let properties_obj = self.deref_object(properties_dict)?;
299 let target = properties_obj.as_dict()?.get(prop_name)?;
300 let target_obj = self.deref_object(target)?;
301 self.read_oc_name(target_obj.as_dict()?, OC_NAME_MAX_DEPTH)
302 }
303
304 pub(super) fn read_oc_name(
320 &self,
321 dict: &std::collections::HashMap<String, crate::object::Object>,
322 depth: u8,
323 ) -> Option<String> {
324 use crate::object::Object;
325
326 if depth == 0 {
327 return None;
328 }
329
330 if matches!(dict.get("Type").and_then(|t| t.as_name()), Some("OCMD")) {
331 let ocgs = self.deref_object(dict.get("OCGs")?)?;
332 let first_ocg = match ocgs.as_array() {
333 Some(entries) => entries
334 .iter()
335 .find_map(|e| self.deref_object(e).filter(|o| o.as_dict().is_some())),
336 None => Some(ocgs.clone()),
337 };
338 return self.read_oc_name(first_ocg?.as_dict()?, depth - 1);
339 }
340
341 match dict.get("Name")? {
342 Object::String(bytes) => Some(Self::decode_pdf_text_string(bytes)),
343 Object::Name(s) => Some(s.clone()),
346 _ => None,
347 }
348 }
349
350 fn deref_object(&self, obj: &crate::object::Object) -> Option<crate::object::Object> {
354 match obj.as_reference() {
355 Some(r) => self.load_object(r).ok(),
356 None => Some(obj.clone()),
357 }
358 }
359
360 pub fn extract_rects(&self, page_index: usize) -> Result<Vec<crate::elements::PathContent>> {
364 let paths = self.extract_paths(page_index)?;
365 Ok(paths.into_iter().filter(|p| p.is_rectangle()).collect())
366 }
367
368 pub fn extract_lines(&self, page_index: usize) -> Result<Vec<crate::elements::PathContent>> {
372 let paths = self.extract_paths(page_index)?;
373 Ok(paths.into_iter().filter(|p| p.is_straight_line()).collect())
374 }
375
376 fn process_form_xobject_paths(
392 &self,
393 name: &str,
394 extractor: &mut crate::extractors::paths::PathExtractor,
395 state_stack: &mut crate::extractors::paths::PathGraphicsStateStack,
396 ) -> Result<()> {
397 use crate::content::{Matrix, Operator, parse_content_stream_paths_only};
398 use crate::elements::{LineCap, LineJoin};
399 use crate::extractors::paths::FillRule;
400 use crate::layout::Color;
401
402 let xobject_ref = match extractor.resolve_xobject_ref(name, |ref_obj| self.load_object(ref_obj)) {
403 Some(r) => r,
404 None => return Ok(()),
405 };
406
407 if !extractor.can_process_xobject(xobject_ref) {
408 return Ok(());
409 }
410 extractor.push_xobject(xobject_ref);
411
412 let xobject = match self.load_object(xobject_ref) {
413 Ok(obj) => obj,
414 Err(e) => {
415 extractor.pop_xobject_failed();
416 return Err(e);
417 }
418 };
419 let xobject_dict = match xobject.as_dict() {
420 Some(dict) => dict,
421 None => {
422 extractor.pop_xobject_failed();
423 return Err(Error::ParseError {
424 offset: 0,
425 reason: "XObject is not a dictionary".to_string(),
426 });
427 }
428 };
429
430 match xobject_dict.get("Subtype") {
431 Some(subtype_obj) => {
432 if let Some(subtype_name) = subtype_obj.as_name() {
433 if subtype_name != "Form" {
434 extractor.pop_xobject();
435 return Ok(());
436 }
437 } else {
438 extractor.pop_xobject();
439 return Ok(());
440 }
441 }
442 None => {
443 extractor.pop_xobject();
444 return Ok(());
445 }
446 }
447
448 let cached_stream = { self.xobject_stream_cache.lock_or_recover().get(&xobject_ref).cloned() };
450 let stream_data = if let Some(cached) = cached_stream {
451 cached.as_ref().clone()
452 } else {
453 match self.decode_stream_with_encryption(&xobject, xobject_ref) {
454 Ok(data) => {
455 const MAX_STREAM_CACHE_BYTES: usize = 50 * 1024 * 1024;
456 let current = self.xobject_stream_cache_bytes.load(Ordering::Relaxed);
457 if current + data.len() <= MAX_STREAM_CACHE_BYTES {
458 self.xobject_stream_cache_bytes
459 .store(current + data.len(), Ordering::Relaxed);
460 self.xobject_stream_cache
461 .lock_or_recover()
462 .insert(xobject_ref, std::sync::Arc::new(data.clone()));
463 }
464 data
465 }
466 Err(e) => {
467 extractor.pop_xobject_failed();
468 return Err(e);
469 }
470 }
471 };
472
473 let operators = match parse_content_stream_paths_only(&stream_data) {
474 Ok(ops) => ops,
475 Err(e) => {
476 extractor.pop_xobject_failed();
477 return Err(e);
478 }
479 };
480
481 let matrix = if let Some(matrix_obj) = xobject_dict.get("Matrix") {
482 if let Some(array) = matrix_obj.as_array() {
483 if array.len() >= 6 {
484 let mut matrix = Matrix::identity();
485 let mut values = [0.0f32; 6];
486 let mut valid = true;
487
488 for (i, val) in array.iter().take(6).enumerate() {
489 let num = if let Some(f) = val.as_real() {
490 f as f32
491 } else if let Some(i_val) = val.as_integer() {
492 i_val as f32
493 } else {
494 valid = false;
495 break;
496 };
497 values[i] = num;
498 }
499
500 if valid {
501 matrix.a = values[0];
502 matrix.b = values[1];
503 matrix.c = values[2];
504 matrix.d = values[3];
505 matrix.e = values[4];
506 matrix.f = values[5];
507 matrix
508 } else {
509 Matrix::identity()
510 }
511 } else {
512 Matrix::identity()
513 }
514 } else {
515 Matrix::identity()
516 }
517 } else {
518 Matrix::identity()
519 };
520
521 state_stack.save();
522
523 if extractor.has_current_path() {
524 extractor.end_path();
525 }
526
527 let state = state_stack.current_mut();
530 state.ctm = matrix.multiply(&state.ctm);
531 extractor.set_ctm(state.ctm);
532
533 let saved_scope = if let Some(xobj_resources) = xobject_dict.get("Resources") {
540 let resolved = if let Some(res_ref) = xobj_resources.as_reference() {
541 self.load_object(res_ref).unwrap_or_else(|_| xobj_resources.clone())
542 } else {
543 xobj_resources.clone()
544 };
545 Some(extractor.swap_resources(Some(resolved)))
546 } else {
547 None
548 };
549
550 let oc_base_depth = extractor.oc_layer_depth();
553
554 for op in operators {
555 match op {
556 Operator::SaveState => {
557 state_stack.save();
558 }
559 Operator::RestoreState => {
560 state_stack.restore();
561 extractor.update_from_path_state(state_stack.current());
562 }
563 Operator::Cm { a, b, c, d, e, f } => {
564 let state = state_stack.current_mut();
565 let new_matrix = Matrix { a, b, c, d, e, f };
566 state.ctm = new_matrix.multiply(&state.ctm);
568 extractor.set_ctm(state.ctm);
569 }
570
571 Operator::SetStrokeRgb { r, g, b } => {
574 state_stack.current_mut().stroke_color_rgb = (r, g, b);
575 extractor.set_stroke_color(Color::new(r, g, b));
576 }
577 Operator::SetStrokeGray { gray } => {
578 state_stack.current_mut().stroke_color_rgb = (gray, gray, gray);
579 extractor.set_stroke_color(Color::new(gray, gray, gray));
580 }
581 Operator::SetStrokeCmyk { c, m, y, k } => {
582 let (r, g, b) = crate::color::cmyk_to_rgb(c, m, y, k);
583 state_stack.current_mut().stroke_color_rgb = (r, g, b);
584 extractor.set_stroke_color(Color::new(r, g, b));
585 }
586 Operator::SetFillRgb { r, g, b } => {
587 state_stack.current_mut().fill_color_rgb = (r, g, b);
588 extractor.set_fill_color(Color::new(r, g, b));
589 }
590 Operator::SetFillGray { gray } => {
591 state_stack.current_mut().fill_color_rgb = (gray, gray, gray);
592 extractor.set_fill_color(Color::new(gray, gray, gray));
593 }
594 Operator::SetFillCmyk { c, m, y, k } => {
595 let (r, g, b) = crate::color::cmyk_to_rgb(c, m, y, k);
596 state_stack.current_mut().fill_color_rgb = (r, g, b);
597 extractor.set_fill_color(Color::new(r, g, b));
598 }
599 Operator::SetLineWidth { width } => {
600 state_stack.current_mut().line_width = width;
601 extractor.set_line_width(width);
602 }
603 Operator::SetLineCap { cap_style } => {
604 state_stack.current_mut().line_cap = cap_style;
605 let cap = match cap_style {
606 1 => LineCap::Round,
607 2 => LineCap::Square,
608 _ => LineCap::Butt,
609 };
610 extractor.set_line_cap(cap);
611 }
612 Operator::SetLineJoin { join_style } => {
613 state_stack.current_mut().line_join = join_style;
614 let join = match join_style {
615 1 => LineJoin::Round,
616 2 => LineJoin::Bevel,
617 _ => LineJoin::Miter,
618 };
619 extractor.set_line_join(join);
620 }
621
622 Operator::MoveTo { x, y } => extractor.move_to(x, y),
623 Operator::LineTo { x, y } => extractor.line_to(x, y),
624 Operator::CurveTo { x1, y1, x2, y2, x3, y3 } => {
625 extractor.curve_to(x1, y1, x2, y2, x3, y3);
626 }
627 Operator::CurveToV { x2, y2, x3, y3 } => {
628 extractor.curve_to_v(x2, y2, x3, y3);
629 }
630 Operator::CurveToY { x1, y1, x3, y3 } => {
631 extractor.curve_to_y(x1, y1, x3, y3);
632 }
633 Operator::Rectangle { x, y, width, height } => {
634 extractor.rectangle(x, y, width, height);
635 }
636 Operator::ClosePath => extractor.close_path(),
637
638 Operator::Stroke => extractor.stroke(),
639 Operator::CloseStroke => extractor.close_and_stroke(),
640 Operator::Fill => extractor.fill(FillRule::NonZero),
641 Operator::FillEvenOdd => extractor.fill(FillRule::EvenOdd),
642 Operator::CloseFillStroke => extractor.close_fill_and_stroke(FillRule::NonZero),
643 Operator::FillStroke => extractor.fill_and_stroke(FillRule::NonZero),
644 Operator::FillStrokeEvenOdd => extractor.fill_and_stroke(FillRule::EvenOdd),
645 Operator::CloseFillStrokeEvenOdd => {
646 extractor.close_fill_and_stroke(FillRule::EvenOdd);
647 }
648 Operator::EndPath => extractor.end_path(),
649
650 Operator::ClipNonZero => extractor.clip_non_zero(),
651 Operator::ClipEvenOdd => extractor.clip_even_odd(),
652
653 Operator::Do { name: nested_name } => {
654 if let Err(error) = self.process_form_xobject_paths(&nested_name, extractor, state_stack) {
655 tracing::warn!(target: LOG_TARGET,
656 error_code = error.telemetry_code(),
657 error_offset = ?error.telemetry_offset(),
658 "failed to process nested XObject"
659 );
660 }
661 }
662
663 Operator::BeginMarkedContent { .. } => {
671 extractor.push_oc_layer(None);
672 }
673 Operator::BeginMarkedContentDict { tag, properties } => {
674 let layer = if tag == "OC" {
675 self.resolve_oc_layer_name(extractor.current_resources(), &properties)
676 } else {
677 None
678 };
679 extractor.push_oc_layer(layer);
680 }
681 Operator::EndMarkedContent => {
682 extractor.pop_oc_layer();
683 }
684
685 _ => {}
686 }
687 }
688
689 if extractor.has_current_path() {
690 extractor.end_path();
691 }
692
693 extractor.truncate_oc_layers(oc_base_depth);
694
695 if let Some(saved) = saved_scope {
696 extractor.restore_resources(saved);
697 }
698
699 state_stack.restore();
700 extractor.update_from_path_state(state_stack.current());
701
702 extractor.pop_xobject();
703
704 Ok(())
705 }
706
707 pub fn extract_paths_in_rect(
737 &self,
738 page_index: usize,
739 region: crate::geometry::Rect,
740 ) -> Result<Vec<crate::elements::PathContent>> {
741 let paths = self.extract_paths(page_index)?;
742
743 Ok(paths
749 .into_iter()
750 .filter(|path| path.rendered_bbox().intersects(®ion))
751 .collect())
752 }
753}