1use super::*;
8
9impl PdfDocument {
10 fn parse_font_size_from_da(da: &str) -> f32 {
15 let tokens: Vec<&str> = da.split_whitespace().collect();
16 for i in 0..tokens.len() {
17 if tokens[i] == "Tf"
18 && i > 0
19 && let Ok(size) = tokens[i - 1].parse::<f32>()
20 && size > 0.0
21 {
22 return size;
23 }
24 }
25 10.0
26 }
27
28 pub(super) fn extract_widget_spans(&self, page_index: usize) -> Vec<TextSpan> {
34 use crate::extractors::forms::field_flags;
35 use crate::geometry::Rect;
36
37 let page_obj = match self.get_page(page_index) {
38 Ok(o) => o,
39 Err(_) => return Vec::new(),
40 };
41 let page_dict = match page_obj.as_dict() {
42 Some(d) => d,
43 None => return Vec::new(),
44 };
45
46 let annots_arr = match page_dict.get("Annots") {
47 Some(Object::Array(arr)) => arr.clone(),
48 Some(Object::Reference(r)) => match self.load_object(*r) {
49 Ok(Object::Array(arr)) => arr,
50 _ => return Vec::new(),
51 },
52 _ => return Vec::new(),
53 };
54
55 let mut spans = Vec::new();
56 let base_sequence = 1_000_000;
57 for (idx, annot_obj) in annots_arr.iter().enumerate() {
60 let annot_ref = match annot_obj {
61 Object::Reference(r) => *r,
62 _ => continue,
63 };
64 let dict = match self.load_object(annot_ref) {
65 Ok(obj) => match obj.as_dict() {
66 Some(d) => d.clone(),
67 None => continue,
68 },
69 Err(_) => continue,
70 };
71
72 let subtype = match dict.get("Subtype").and_then(|s| s.as_name()) {
73 Some(s) => s.to_string(),
74 None => continue,
75 };
76 if !subtype.eq_ignore_ascii_case("widget") {
77 continue;
78 }
79
80 if let Some(Object::Integer(f)) = dict.get("F")
83 && *f & (0x1 | 0x2 | 0x20) != 0
84 {
85 continue;
86 }
87
88 let rect = match dict.get("Rect") {
89 Some(Object::Array(arr)) if arr.len() == 4 => {
90 let mut coords = [0.0f32; 4];
91 let mut ok = true;
92 for (i, item) in arr.iter().enumerate() {
93 match item {
94 Object::Integer(n) => coords[i] = *n as f32,
95 Object::Real(f) => coords[i] = *f as f32,
96 _ => {
97 ok = false;
98 break;
99 }
100 }
101 }
102 if !ok {
103 continue;
104 }
105 let x = coords[0].min(coords[2]);
106 let y = coords[1].min(coords[3]);
107 let w = (coords[2] - coords[0]).abs();
108 let h = (coords[3] - coords[1]).abs();
109 if w < 0.1 || h < 0.1 {
110 continue;
111 }
112 Rect::new(x, y, w, h)
113 }
114 Some(Object::Reference(r)) => match self.load_object(*r) {
115 Ok(Object::Array(arr)) if arr.len() == 4 => {
116 let mut coords = [0.0f32; 4];
117 let mut ok = true;
118 for (i, item) in arr.iter().enumerate() {
119 match item {
120 Object::Integer(n) => coords[i] = *n as f32,
121 Object::Real(f) => coords[i] = *f as f32,
122 _ => {
123 ok = false;
124 break;
125 }
126 }
127 }
128 if !ok {
129 continue;
130 }
131 let x = coords[0].min(coords[2]);
132 let y = coords[1].min(coords[3]);
133 let w = (coords[2] - coords[0]).abs();
134 let h = (coords[3] - coords[1]).abs();
135 if w < 0.1 || h < 0.1 {
136 continue;
137 }
138 Rect::new(x, y, w, h)
139 }
140 _ => continue,
141 },
142 _ => continue,
143 };
144
145 let ft = dict
146 .get("FT")
147 .and_then(|o| o.as_name())
148 .map(|s| s.to_string())
149 .or_else(|| self.resolve_inherited_ft(&dict));
150
151 let ff = dict
152 .get("Ff")
153 .and_then(|o| match o {
154 Object::Integer(i) => Some(*i as u32),
155 _ => None,
156 })
157 .or_else(|| self.resolve_inherited_ff(&dict));
158 let ff = ff.unwrap_or(0);
159
160 let display_text = match ft.as_deref() {
161 Some("Tx") => {
162 if ff & field_flags::PASSWORD != 0 {
163 Some("********".to_string())
164 } else {
165 let value = Self::parse_string_value_static(dict.get("V"))
166 .or_else(|| self.resolve_inherited_field_value(&dict));
167 match value {
168 Some(v) if !v.trim().is_empty() => {
169 Some(Self::truncate_to_widget_capacity(v.trim().to_string(), &rect))
179 }
180 _ => {
181 self.extract_text_from_ap_stream(&dict).and_then(|t| {
189 let t = t.trim().to_string();
190 if t.is_empty() {
191 return None;
192 }
193 Some(Self::truncate_to_widget_capacity(t, &rect))
194 })
195 }
196 }
197 }
198 }
199 Some("Btn") => {
200 if ff & field_flags::PUSH_BUTTON != 0 {
201 dict.get("MK")
206 .and_then(|mk| mk.as_dict())
207 .and_then(|mk| Self::parse_string_value_static(mk.get("CA")))
208 .and_then(|s| {
209 let t = s.trim().to_string();
210 if t.is_empty() { None } else { Some(t) }
211 })
212 } else {
213 let value = Self::parse_string_value_static(dict.get("V"))
214 .or_else(|| self.resolve_inherited_field_value(&dict));
215 let is_checked = match &value {
216 Some(v) => {
217 let v_lower = v.to_ascii_lowercase();
218 v_lower != "off" && !v_lower.is_empty()
219 }
220 None => false,
221 };
222 if is_checked {
223 Some("[x]".to_string())
224 } else {
225 None
231 }
232 }
233 }
234 Some("Ch") => {
235 let value = dict.get("V");
236 match value {
237 Some(Object::Array(arr)) => {
238 let items: Vec<String> = arr
239 .iter()
240 .filter_map(|item| Self::parse_string_value_static(Some(item)))
241 .collect();
242 if items.is_empty() { None } else { Some(items.join(", ")) }
243 }
244 other => Self::parse_string_value_static(other)
245 .or_else(|| self.resolve_inherited_field_value(&dict))
246 .and_then(|v| {
247 let t = v.trim().to_string();
248 if t.is_empty() { None } else { Some(t) }
249 }),
250 }
251 }
252 Some("Sig") => None,
253 _ => Self::parse_string_value_static(dict.get("V"))
254 .or_else(|| self.resolve_inherited_field_value(&dict))
255 .and_then(|v| {
256 let t = v.trim().to_string();
257 if t.is_empty() { None } else { Some(t) }
258 }),
259 };
260
261 let text = match display_text {
262 Some(t) if !t.is_empty() => t,
263 _ => {
264 match self.extract_text_from_ap_stream(&dict) {
272 Some(ap) if !ap.trim().is_empty() => ap.trim().to_string(),
273 _ => continue,
274 }
275 }
276 };
277
278 let font_size = {
279 let da = dict
280 .get("DA")
281 .and_then(|o| match o {
282 Object::String(s) => Some(Self::decode_pdf_text_string(s)),
283 _ => None,
284 })
285 .or_else(|| self.resolve_inherited_da(&dict));
286
287 match da {
288 Some(da_str) => {
289 let size = Self::parse_font_size_from_da(&da_str);
290 if size <= 0.0 {
291 (rect.height * 0.7).clamp(6.0, 24.0)
292 } else {
293 size
294 }
295 }
296 None => (rect.height * 0.7).clamp(6.0, 24.0),
297 }
298 };
299
300 spans.push(TextSpan {
301 provenance: None,
302 artifact_type: None,
303 text,
304 bbox: rect,
305 font_name: String::new(),
306 font_size,
307 font_weight: crate::layout::text_block::FontWeight::Normal,
308 is_italic: false,
309 is_monospace: false,
310 color: crate::layout::text_block::Color { r: 0.0, g: 0.0, b: 0.0 },
311 mcid: None,
312 mcid_scope: None,
313 sequence: base_sequence + idx,
314 split_boundary_before: false,
315 offset_semantic: false,
316 char_spacing: 0.0,
317 word_spacing: 0.0,
318 horizontal_scaling: 100.0,
319 primary_detected: false,
320 char_widths: vec![],
321 char_x_offsets: Vec::new(),
322 heading_level: None,
323 rotation_degrees: 0.0,
324 wmode: 0,
325 text_rise: 0.0,
326 rtl_draw_logical: false,
327 mirrored: false,
328 page_rotation_applied: 0,
329 });
330 }
331
332 spans
333 }
334
335 pub(super) fn annotation_content_spans(&self, page_index: usize) -> Vec<TextSpan> {
342 use crate::geometry::Rect;
343
344 let page_obj = match self.get_page(page_index) {
345 Ok(o) => o,
346 Err(_) => return Vec::new(),
347 };
348 let page_dict = match page_obj.as_dict() {
349 Some(d) => d,
350 None => return Vec::new(),
351 };
352
353 let annots_arr = match page_dict.get("Annots") {
354 Some(Object::Array(arr)) => arr.clone(),
355 Some(Object::Reference(r)) => match self.load_object(*r) {
356 Ok(Object::Array(arr)) => arr,
357 _ => return Vec::new(),
358 },
359 _ => return Vec::new(),
360 };
361
362 let mut spans: Vec<TextSpan> = Vec::new();
363 let base_sequence = 2_000_000usize; for (idx, annot_obj) in annots_arr.iter().enumerate() {
366 let annot_ref = match annot_obj {
367 Object::Reference(r) => *r,
368 _ => continue,
369 };
370 let dict = match self.load_object(annot_ref) {
371 Ok(obj) => match obj.as_dict() {
372 Some(d) => d.clone(),
373 None => continue,
374 },
375 Err(_) => continue,
376 };
377
378 let subtype = match dict.get("Subtype").and_then(|s| s.as_name()) {
379 Some(s) => s.to_string(),
380 None => continue,
381 };
382 let subtype_lc = subtype.to_ascii_lowercase();
383
384 if subtype_lc == "widget" || subtype_lc == "popup" {
387 continue;
388 }
389
390 if let Some(Object::Integer(f)) = dict.get("F")
391 && *f & (0x1 | 0x2 | 0x20) != 0
392 {
393 continue;
394 }
395
396 let is_visible = matches!(subtype_lc.as_str(), "freetext" | "stamp");
403 if !is_visible {
404 continue;
405 }
406
407 let text = {
408 let from_contents = if let Some(Object::String(s)) = dict.get("Contents") {
409 let decoded = Self::decode_pdf_text_string(s).trim().to_string();
410 if decoded.is_empty() { None } else { Some(decoded) }
411 } else {
412 None
413 };
414 if let Some(t) = from_contents {
415 t
416 } else {
417 match self.extract_text_from_ap_stream(&dict) {
418 Some(ap_text) if !ap_text.trim().is_empty() => ap_text.trim().to_string(),
419 _ => continue,
420 }
421 }
422 };
423
424 let rect_obj = match dict.get("Rect") {
425 Some(Object::Reference(r)) => match self.load_object(*r) {
426 Ok(o) => o,
427 Err(_) => continue,
428 },
429 Some(o) => o.clone(),
430 None => continue,
431 };
432 let rect = match rect_obj.as_array() {
433 Some(arr) if arr.len() == 4 => {
434 let mut coords = [0.0f32; 4];
435 let mut ok = true;
436 for (i, item) in arr.iter().enumerate() {
437 match item {
438 Object::Integer(n) => coords[i] = *n as f32,
439 Object::Real(f) => coords[i] = *f as f32,
440 _ => {
441 ok = false;
442 break;
443 }
444 }
445 }
446 if !ok {
447 continue;
448 }
449 let x = coords[0].min(coords[2]);
450 let y = coords[1].min(coords[3]);
451 let w = (coords[2] - coords[0]).abs();
452 let h = (coords[3] - coords[1]).abs();
453 Rect {
454 x,
455 y,
456 width: w.max(1.0),
457 height: h.max(1.0),
458 }
459 }
460 _ => continue,
461 };
462
463 spans.push(TextSpan {
464 provenance: None,
465 artifact_type: None,
466 text,
467 bbox: rect,
468 font_name: String::new(),
469 font_size: 12.0,
470 font_weight: crate::layout::text_block::FontWeight::Normal,
471 is_italic: false,
472 is_monospace: false,
473 color: crate::layout::text_block::Color { r: 0.0, g: 0.0, b: 0.0 },
474 mcid: None,
475 mcid_scope: None,
476 sequence: base_sequence + idx,
477 split_boundary_before: false,
478 offset_semantic: false,
479 char_spacing: 0.0,
480 word_spacing: 0.0,
481 horizontal_scaling: 100.0,
482 primary_detected: false,
483 char_widths: vec![],
484 char_x_offsets: Vec::new(),
485 heading_level: None,
486 rotation_degrees: 0.0,
487 wmode: 0,
488 text_rise: 0.0,
489 rtl_draw_logical: false,
490 mirrored: false,
491 page_rotation_applied: 0,
492 });
493 }
494
495 spans
496 }
497
498 fn resolve_inherited_ff(&self, dict: &std::collections::HashMap<String, Object>) -> Option<u32> {
500 let mut parent_ref = match dict.get("Parent") {
501 Some(Object::Reference(r)) => Some(*r),
502 _ => return None,
503 };
504 let mut depth = 0;
505 while let Some(pref) = parent_ref {
506 if depth >= 10 {
507 break;
508 }
509 depth += 1;
510 match self.load_object(pref) {
511 Ok(parent_obj) => {
512 if let Some(parent_dict) = parent_obj.as_dict() {
513 if let Some(Object::Integer(ff)) = parent_dict.get("Ff") {
514 return Some(*ff as u32);
515 }
516 parent_ref = match parent_dict.get("Parent") {
517 Some(Object::Reference(r)) => Some(*r),
518 _ => None,
519 };
520 } else {
521 break;
522 }
523 }
524 _ => {
525 break;
526 }
527 }
528 }
529 None
530 }
531
532 fn resolve_inherited_da(&self, dict: &std::collections::HashMap<String, Object>) -> Option<String> {
534 let mut parent_ref = match dict.get("Parent") {
535 Some(Object::Reference(r)) => Some(*r),
536 _ => None,
537 };
538 let mut depth = 0;
539 while let Some(pref) = parent_ref {
540 if depth >= 10 {
541 break;
542 }
543 depth += 1;
544 match self.load_object(pref) {
545 Ok(parent_obj) => {
546 if let Some(parent_dict) = parent_obj.as_dict() {
547 if let Some(Object::String(da)) = parent_dict.get("DA") {
548 return Some(Self::decode_pdf_text_string(da));
549 }
550 parent_ref = match parent_dict.get("Parent") {
551 Some(Object::Reference(r)) => Some(*r),
552 _ => None,
553 };
554 } else {
555 break;
556 }
557 }
558 _ => {
559 break;
560 }
561 }
562 }
563
564 if let Some(trailer_dict) = self.trailer.as_dict()
565 && let Some(root_ref) = trailer_dict.get("Root").and_then(|o| o.as_reference())
566 && let Ok(root_obj) = self.load_object(root_ref)
567 && let Some(root_dict) = root_obj.as_dict()
568 {
569 let acroform = match root_dict.get("AcroForm") {
570 Some(Object::Reference(r)) => self.load_object(*r).ok(),
571 Some(obj) => Some(obj.clone()),
572 None => None,
573 };
574 if let Some(acroform_obj) = acroform
575 && let Some(af_dict) = acroform_obj.as_dict()
576 && let Some(Object::String(da)) = af_dict.get("DA")
577 {
578 return Some(Self::decode_pdf_text_string(da));
579 }
580 }
581
582 None
583 }
584
585 pub(super) fn append_non_widget_annotation_text(&self, page_index: usize, text: &mut String) {
592 let page_obj = match self.get_page(page_index) {
596 Ok(o) => o,
597 Err(_) => return,
598 };
599 let page_dict = match page_obj.as_dict() {
600 Some(d) => d,
601 None => return,
602 };
603
604 let annots_arr = match page_dict.get("Annots") {
605 Some(Object::Array(arr)) => arr.clone(),
606 Some(Object::Reference(r)) => match self.load_object(*r) {
607 Ok(Object::Array(arr)) => arr,
608 _ => return,
609 },
610 _ => return,
611 };
612
613 let mut annot_texts: Vec<String> = Vec::new();
614
615 for annot_obj in &annots_arr {
616 let len_before_annot = annot_texts.len();
617 let annot_ref = match annot_obj {
618 Object::Reference(r) => *r,
619 _ => continue,
620 };
621 let dict = match self.load_object(annot_ref) {
622 Ok(obj) => match obj.as_dict() {
623 Some(d) => d.clone(),
624 None => continue,
625 },
626 Err(_) => continue,
627 };
628
629 if let Some(Object::Integer(f)) = dict.get("F")
632 && *f & (0x1 | 0x2 | 0x20) != 0
633 {
634 continue;
635 }
636
637 let subtype = match dict.get("Subtype").and_then(|s| s.as_name()) {
638 Some(s) => s.to_string(),
639 None => continue,
640 };
641 let subtype_lower = subtype.to_ascii_lowercase();
642
643 match subtype_lower.as_str() {
644 "widget" => {
645 continue;
648 }
649 "freetext" | "stamp" => {
650 if let Some(Object::String(s)) = dict.get("Contents") {
651 let decoded = Self::decode_pdf_text_string(s);
652 let trimmed = decoded.trim().to_string();
653 if !trimmed.is_empty() {
654 annot_texts.push(trimmed);
655 }
656 }
657 }
658 "text" => {}
661 "line" | "circle" | "square" | "polygon" | "polyline" => {}
664 "highlight" | "underline" | "strikeout" | "squiggly" | "caret" | "fileattachment" | "redact"
674 | "ink" => {}
675 "link" => {
678 if let Some(Object::String(s)) = dict.get("Contents") {
679 let decoded = Self::decode_pdf_text_string(s);
680 let trimmed = decoded.trim().to_string();
681 if !trimmed.is_empty() {
682 annot_texts.push(trimmed);
683 }
684 }
685 }
686 "popup" => {
689 let mut got_text = false;
690 if let Some(parent_ref) = dict.get("Parent").and_then(|o| o.as_reference())
691 && let Ok(parent_obj) = self.load_object(parent_ref)
692 && let Some(parent_dict) = parent_obj.as_dict()
693 && let Some(Object::String(s)) = parent_dict.get("Contents")
694 {
695 let decoded = Self::decode_pdf_text_string(s);
696 let trimmed = decoded.trim().to_string();
697 if !trimmed.is_empty() {
698 annot_texts.push(trimmed);
699 got_text = true;
700 }
701 }
702 if !got_text && let Some(Object::String(s)) = dict.get("Contents") {
703 let decoded = Self::decode_pdf_text_string(s);
704 let trimmed = decoded.trim().to_string();
705 if !trimmed.is_empty() {
706 annot_texts.push(trimmed);
707 }
708 }
709 }
710 _ => {
711 if let Some(Object::String(s)) = dict.get("Contents") {
712 let decoded = Self::decode_pdf_text_string(s);
713 let trimmed = decoded.trim().to_string();
714 if !trimmed.is_empty() {
715 annot_texts.push(trimmed);
716 }
717 }
718 }
719 }
720
721 let text_before = annot_texts.len();
722 if text_before == len_before_annot
723 && let Some(ap_text) = self.extract_text_from_ap_stream(&dict)
724 {
725 let trimmed = ap_text.trim().to_string();
726 if !trimmed.is_empty() {
727 annot_texts.push(trimmed);
728 }
729 }
730 }
731
732 if !annot_texts.is_empty() {
733 if !text.is_empty() && !text.ends_with('\n') {
734 text.push('\n');
735 }
736 text.push_str(&annot_texts.join("\n"));
737 }
738 }
739
740 pub fn extract_annotation_appearance_text(
746 &self,
747 annotation: &crate::annotations::Annotation,
748 excluded_layers: &std::collections::HashSet<String>,
749 ) -> Option<String> {
750 self.extract_text_from_ap_stream_filtered(annotation.raw_dict.as_ref()?, excluded_layers)
751 }
752
753 fn extract_text_from_ap_stream(&self, annot_dict: &std::collections::HashMap<String, Object>) -> Option<String> {
754 self.extract_text_from_ap_stream_filtered(annot_dict, &std::collections::HashSet::new())
755 }
756
757 fn extract_text_from_ap_stream_filtered(
758 &self,
759 annot_dict: &std::collections::HashMap<String, Object>,
760 excluded_layers: &std::collections::HashSet<String>,
761 ) -> Option<String> {
762 use crate::extractors::TextExtractor;
763
764 let ap_obj = annot_dict.get("AP")?;
765 let ap = if let Some(r) = ap_obj.as_reference() {
766 self.load_object(r).ok()?
767 } else {
768 ap_obj.clone()
769 };
770 let ap_dict = ap.as_dict()?;
771
772 let n_obj = ap_dict.get("N")?;
773 let (n_stream, n_ref) = match n_obj {
774 Object::Reference(r) => (self.load_object(*r).ok()?, *r),
775 _ => return None,
776 };
777
778 let n_dict = n_stream.as_dict()?;
779
780 let stream_data = match self.decode_stream_with_encryption(&n_stream, n_ref) {
781 Ok(data) => data,
782 Err(_) => return None,
783 };
784
785 if !Self::may_contain_text(&stream_data) {
786 return None;
787 }
788
789 let mut extractor = TextExtractor::new();
790 if !excluded_layers.is_empty() {
791 extractor.set_excluded_layers(excluded_layers.clone());
792 }
793
794 {
798 let resources = n_dict.get("Resources")?;
799 let res_obj = if let Some(r) = resources.as_reference() {
800 self.load_object(r).ok().unwrap_or_else(|| resources.clone())
801 } else {
802 resources.clone()
803 };
804 extractor.set_resources(res_obj.clone());
805 extractor.set_document(self);
806 let _ = self.load_fonts(&res_obj, &mut extractor);
807 }
808
809 let spans = extractor.extract_text_spans(&stream_data).ok()?;
810 if spans.is_empty() {
811 return None;
812 }
813
814 let text: String = spans.iter().map(|s| s.text.as_str()).collect::<Vec<_>>().join(" ");
815 if text.trim().is_empty() {
816 return None;
817 }
818 Some(text)
819 }
820
821 fn widget_text_capacity(bbox: &crate::geometry::Rect) -> usize {
837 let area = bbox.width.max(0.0) * bbox.height.max(0.0);
838 (0.0175 * area) as usize + 64
839 }
840
841 fn truncate_to_widget_capacity(text: String, bbox: &crate::geometry::Rect) -> String {
847 let cap = Self::widget_text_capacity(bbox);
848 let n = text.chars().count();
849 if n <= cap {
850 return text;
851 }
852 text.chars().take(cap).collect()
853 }
854
855 fn resolve_inherited_ft(&self, dict: &std::collections::HashMap<String, Object>) -> Option<String> {
857 let mut parent_ref = match dict.get("Parent") {
858 Some(Object::Reference(r)) => Some(*r),
859 _ => return None,
860 };
861 let mut depth = 0;
862 while let Some(pref) = parent_ref {
863 if depth >= 10 {
864 break;
865 }
866 depth += 1;
867 match self.load_object(pref) {
868 Ok(parent_obj) => {
869 if let Some(parent_dict) = parent_obj.as_dict() {
870 if let Some(ft) = parent_dict.get("FT").and_then(|o| o.as_name()) {
871 return Some(ft.to_string());
872 }
873 parent_ref = match parent_dict.get("Parent") {
874 Some(Object::Reference(r)) => Some(*r),
875 _ => None,
876 };
877 } else {
878 break;
879 }
880 }
881 _ => {
882 break;
883 }
884 }
885 }
886 None
887 }
888
889 fn resolve_inherited_field_value(&self, dict: &std::collections::HashMap<String, Object>) -> Option<String> {
891 let mut parent_ref = match dict.get("Parent") {
892 Some(Object::Reference(r)) => Some(*r),
893 _ => return None,
894 };
895 let mut depth = 0;
896 while let Some(pref) = parent_ref {
897 if depth >= 10 {
898 break;
899 }
900 depth += 1;
901 match self.load_object(pref) {
902 Ok(parent_obj) => {
903 if let Some(parent_dict) = parent_obj.as_dict() {
904 if let Some(v) = Self::parse_string_value_static(parent_dict.get("V")) {
905 return Some(v);
906 }
907 parent_ref = match parent_dict.get("Parent") {
908 Some(Object::Reference(r)) => Some(*r),
909 _ => None,
910 };
911 } else {
912 break;
913 }
914 }
915 _ => {
916 break;
917 }
918 }
919 }
920 None
921 }
922
923 pub(super) fn parse_string_value_static(obj: Option<&Object>) -> Option<String> {
926 match obj {
927 Some(Object::String(s)) => Some(Self::decode_pdf_text_string(s)),
928 Some(Object::Name(n)) => Some(n.clone()),
929 Some(Object::Integer(i)) => Some(i.to_string()),
930 Some(Object::Real(f)) => Some(f.to_string()),
931 _ => None,
932 }
933 }
934
935 pub(super) fn decode_pdf_text_string(bytes: &[u8]) -> String {
937 if bytes.len() >= 2 && bytes[0] == 0xFE && bytes[1] == 0xFF {
938 let utf16_bytes = &bytes[2..];
939 let utf16_pairs: Vec<u16> = utf16_bytes
940 .as_chunks::<2>()
941 .0
942 .iter()
943 .map(|chunk| u16::from_be_bytes([chunk[0], chunk[1]]))
944 .collect();
945 String::from_utf16(&utf16_pairs).unwrap_or_else(|_| String::from_utf8_lossy(bytes).to_string())
946 } else if bytes.len() >= 2 && bytes[0] == 0xFF && bytes[1] == 0xFE {
947 let utf16_bytes = &bytes[2..];
948 let utf16_pairs: Vec<u16> = utf16_bytes
949 .as_chunks::<2>()
950 .0
951 .iter()
952 .map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]]))
953 .collect();
954 String::from_utf16(&utf16_pairs).unwrap_or_else(|_| String::from_utf8_lossy(bytes).to_string())
955 } else {
956 bytes
957 .iter()
958 .filter_map(|&b| crate::fonts::font_dict::pdfdoc_encoding_lookup(b))
959 .collect()
960 }
961 }
962
963 #[cfg(test)]
968 pub(super) fn strip_xhtml_tags(xhtml: &str) -> String {
969 let mut result = String::with_capacity(xhtml.len());
970 let mut inside_tag = false;
971 for ch in xhtml.chars() {
972 match ch {
973 '<' => inside_tag = true,
974 '>' => inside_tag = false,
975 _ if !inside_tag => result.push(ch),
976 _ => {}
977 }
978 }
979 result
980 }
981}