1use std::collections::{HashMap, HashSet};
13use std::sync::Arc;
14
15use rpdfium_core::Name;
16use rpdfium_core::error::PdfError;
17use rpdfium_parser::object::{Object, ObjectId, StreamData};
18use rpdfium_parser::store::ObjectStore;
19use rpdfium_parser::trailer::TrailerInfo;
20
21use crate::cpdf_flateencoder::create_compressed_stream;
22use crate::error::EditError;
23use crate::font_reg::{FontRegistration, FontType};
24use crate::page_object::PageObject;
25
26#[derive(Debug, Clone)]
28pub struct FormFieldSpec {
29 pub field_type: Name,
31 pub name: String,
33 pub rect: [f32; 4],
35 pub page_index: usize,
37 pub default_value: Option<String>,
39 pub flags: u32,
41}
42
43pub struct EditDocument {
48 store: Option<ObjectStore<Arc<[u8]>>>,
50 modified: HashMap<ObjectId, Object>,
52 new_objects: HashMap<ObjectId, Object>,
54 deleted: HashSet<ObjectId>,
56 next_object_number: u32,
58 page_ids: Vec<ObjectId>,
60 catalog_id: ObjectId,
62 dirty: bool,
64 pub(crate) ap_objects: HashMap<ObjectId, Vec<PageObject>>,
68}
69
70impl EditDocument {
71 pub fn from_store(
73 store: ObjectStore<Arc<[u8]>>,
74 page_ids: Vec<ObjectId>,
75 catalog_id: ObjectId,
76 ) -> Self {
77 let next = store.last_obj_num() + 1;
78 Self {
79 store: Some(store),
80 modified: HashMap::new(),
81 new_objects: HashMap::new(),
82 deleted: HashSet::new(),
83 next_object_number: next,
84 page_ids,
85 catalog_id,
86 dirty: false,
87 ap_objects: HashMap::new(),
88 }
89 }
90
91 pub fn new_blank() -> Self {
93 let catalog_id = ObjectId::new(1, 0);
94 let pages_id = ObjectId::new(2, 0);
95
96 let mut pages_dict = HashMap::new();
97 pages_dict.insert(
98 Name::r#type(),
99 Object::Name(Name::from_bytes(b"Pages".to_vec())),
100 );
101 pages_dict.insert(Name::kids(), Object::Array(Vec::new()));
102 pages_dict.insert(Name::count(), Object::Integer(0));
103
104 let mut catalog_dict = HashMap::new();
105 catalog_dict.insert(
106 Name::r#type(),
107 Object::Name(Name::from_bytes(b"Catalog".to_vec())),
108 );
109 catalog_dict.insert(Name::pages(), Object::Reference(pages_id));
110
111 let mut new_objects = HashMap::new();
112 new_objects.insert(catalog_id, Object::Dictionary(catalog_dict));
113 new_objects.insert(pages_id, Object::Dictionary(pages_dict));
114
115 Self {
116 store: None,
117 modified: HashMap::new(),
118 new_objects,
119 deleted: HashSet::new(),
120 next_object_number: 3,
121 page_ids: Vec::new(),
122 catalog_id,
123 dirty: true,
124 ap_objects: HashMap::new(),
125 }
126 }
127
128 pub fn resolve(&self, id: ObjectId) -> Result<&Object, EditError> {
130 if self.deleted.contains(&id) {
131 return Err(EditError::Parse(PdfError::UnknownObject(id)));
132 }
133 if let Some(obj) = self.modified.get(&id) {
134 return Ok(obj);
135 }
136 if let Some(obj) = self.new_objects.get(&id) {
137 return Ok(obj);
138 }
139 if let Some(store) = &self.store {
140 return Ok(store.resolve(id)?);
141 }
142 Err(EditError::Parse(PdfError::UnknownObject(id)))
143 }
144
145 pub fn get_mut(&mut self, id: ObjectId) -> Result<&mut Object, EditError> {
147 if self.deleted.contains(&id) {
148 return Err(EditError::Parse(PdfError::UnknownObject(id)));
149 }
150 self.dirty = true;
151
152 if self.modified.contains_key(&id) {
154 return Ok(self.modified.get_mut(&id).unwrap());
155 }
156
157 if self.new_objects.contains_key(&id) {
159 return Ok(self.new_objects.get_mut(&id).unwrap());
160 }
161
162 if let Some(store) = &self.store {
164 let obj = store.resolve(id)?.clone();
165 self.modified.insert(id, obj);
166 return Ok(self.modified.get_mut(&id).unwrap());
167 }
168
169 Err(EditError::Parse(PdfError::UnknownObject(id)))
170 }
171
172 pub fn add_object(&mut self, obj: Object) -> ObjectId {
174 let id = self.allocate_id();
175 self.new_objects.insert(id, obj);
176 self.dirty = true;
177 id
178 }
179
180 pub fn delete_object(&mut self, id: ObjectId) {
182 self.modified.remove(&id);
183 self.new_objects.remove(&id);
184 self.deleted.insert(id);
185 self.dirty = true;
186 }
187
188 pub fn set_object(&mut self, id: ObjectId, obj: Object) {
190 if self.store.as_ref().is_some_and(|s| s.contains(id)) {
191 self.modified.insert(id, obj);
192 } else {
193 self.new_objects.insert(id, obj);
194 }
195 self.deleted.remove(&id);
196 self.dirty = true;
197 }
198
199 pub fn allocate_id(&mut self) -> ObjectId {
201 let id = ObjectId::new(self.next_object_number, 0);
202 self.next_object_number += 1;
203 id
204 }
205
206 pub fn changed_ids(&self) -> impl Iterator<Item = ObjectId> + '_ {
208 self.modified.keys().chain(self.new_objects.keys()).copied()
209 }
210
211 pub fn all_ids(&self) -> Vec<ObjectId> {
213 let mut ids: HashSet<ObjectId> = HashSet::new();
214
215 if let Some(store) = &self.store {
217 for id in store.object_ids() {
218 ids.insert(*id);
219 }
220 }
221
222 for id in self.modified.keys() {
224 ids.insert(*id);
225 }
226 for id in self.new_objects.keys() {
227 ids.insert(*id);
228 }
229
230 for id in &self.deleted {
232 ids.remove(id);
233 }
234
235 let mut result: Vec<ObjectId> = ids.into_iter().collect();
236 result.sort_by_key(|id| (id.number, id.generation));
237 result
238 }
239
240 #[inline]
242 pub fn is_dirty(&self) -> bool {
243 self.dirty
244 }
245
246 pub fn security_handler(&self) -> Option<&rpdfium_parser::security::SecurityHandler> {
248 self.store.as_ref().and_then(|s| s.security_handler())
249 }
250
251 pub fn base_store(&self) -> Option<&ObjectStore<Arc<[u8]>>> {
253 self.store.as_ref()
254 }
255
256 pub fn trailer(&self) -> Option<&TrailerInfo> {
258 self.store.as_ref().map(|s| s.trailer())
259 }
260
261 pub fn page_count(&self) -> usize {
263 self.page_ids.len()
264 }
265
266 pub fn page_id(&self, index: usize) -> Option<ObjectId> {
268 self.page_ids.get(index).copied()
269 }
270
271 #[inline]
273 pub fn catalog_id(&self) -> ObjectId {
274 self.catalog_id
275 }
276
277 pub(crate) fn page_ids_mut(&mut self) -> &mut Vec<ObjectId> {
279 &mut self.page_ids
280 }
281
282 #[inline]
284 pub fn page_ids(&self) -> &[ObjectId] {
285 &self.page_ids
286 }
287
288 #[inline]
290 pub fn deleted_ids(&self) -> &HashSet<ObjectId> {
291 &self.deleted
292 }
293
294 #[inline]
296 pub fn next_object_number(&self) -> u32 {
297 self.next_object_number
298 }
299
300 pub fn decode_stream(&self, stream: &Object) -> Result<Vec<u8>, EditError> {
302 if let Object::Stream {
304 data: StreamData::Decoded { data },
305 ..
306 } = stream
307 {
308 return Ok(data.clone());
309 }
310 if let Some(store) = &self.store {
312 Ok(store.decode_stream(stream)?)
313 } else {
314 Err(EditError::Other(
315 "no base store for raw stream decoding".into(),
316 ))
317 }
318 }
319
320 pub fn pages_id(&self) -> Result<ObjectId, EditError> {
322 let catalog = self.resolve(self.catalog_id)?;
323 let dict = catalog
324 .as_dict()
325 .ok_or(EditError::Other("catalog is not a dictionary".into()))?;
326 match dict.get(&Name::pages()) {
327 Some(Object::Reference(id)) => Ok(*id),
328 _ => Err(EditError::Other("catalog missing /Pages reference".into())),
329 }
330 }
331
332 pub fn load_standard_font(&mut self, name: &str) -> Result<FontRegistration, EditError> {
341 let mut dict = HashMap::new();
342 dict.insert(
343 Name::r#type(),
344 Object::Name(Name::from_bytes(b"Font".to_vec())),
345 );
346 dict.insert(
347 Name::from_bytes(b"Subtype".to_vec()),
348 Object::Name(Name::from_bytes(b"Type1".to_vec())),
349 );
350 dict.insert(
351 Name::from_bytes(b"BaseFont".to_vec()),
352 Object::Name(Name::from_bytes(name.as_bytes().to_vec())),
353 );
354
355 let id = self.add_object(Object::Dictionary(dict));
356 Ok(FontRegistration::new_standard(id, name))
357 }
358
359 pub fn load_font_from_data(
370 &mut self,
371 data: &[u8],
372 font_type: FontType,
373 is_cid: bool,
374 ) -> Result<FontRegistration, EditError> {
375 if is_cid {
376 return Err(EditError::NotSupported(
377 "CID font embedding (is_cid=true) is not yet supported".into(),
378 ));
379 }
380
381 let (subtype_bytes, font_file_key): (&[u8], &[u8]) = match font_type {
383 FontType::TrueType => (b"TrueType", b"FontFile2"),
384 FontType::Type1 => (b"Type1", b"FontFile"),
385 };
386
387 let mut stream_dict = HashMap::new();
389 stream_dict.insert(
390 Name::from_bytes(b"Subtype".to_vec()),
391 Object::Name(Name::from_bytes(font_file_key.to_vec())),
392 );
393 let font_stream = create_compressed_stream(data, stream_dict);
394 let stream_id = self.add_object(font_stream);
395
396 let mut desc_dict = HashMap::new();
398 desc_dict.insert(
399 Name::r#type(),
400 Object::Name(Name::from_bytes(b"FontDescriptor".to_vec())),
401 );
402 desc_dict.insert(
403 Name::from_bytes(b"FontName".to_vec()),
404 Object::Name(Name::from_bytes(b"EmbeddedFont".to_vec())),
405 );
406 desc_dict.insert(
407 Name::from_bytes(b"Flags".to_vec()),
408 Object::Integer(4), );
410 desc_dict.insert(
411 Name::from_bytes(font_file_key.to_vec()),
412 Object::Reference(stream_id),
413 );
414 let desc_id = self.add_object(Object::Dictionary(desc_dict));
415
416 let mut font_dict = HashMap::new();
418 font_dict.insert(
419 Name::r#type(),
420 Object::Name(Name::from_bytes(b"Font".to_vec())),
421 );
422 font_dict.insert(
423 Name::from_bytes(b"Subtype".to_vec()),
424 Object::Name(Name::from_bytes(subtype_bytes.to_vec())),
425 );
426 font_dict.insert(
427 Name::from_bytes(b"BaseFont".to_vec()),
428 Object::Name(Name::from_bytes(b"EmbeddedFont".to_vec())),
429 );
430 font_dict.insert(
431 Name::from_bytes(b"FontDescriptor".to_vec()),
432 Object::Reference(desc_id),
433 );
434 let font_id = self.add_object(Object::Dictionary(font_dict));
435
436 let mut reg =
437 FontRegistration::new_embedded(font_id, "EmbeddedFont", font_type, data.to_vec());
438 reg.set_flags(4); reg.set_weight(400); reg.set_italic_angle(0.0); Ok(reg)
443 }
444
445 #[inline]
449 pub fn text_load_font(
450 &mut self,
451 data: &[u8],
452 font_type: FontType,
453 is_cid: bool,
454 ) -> Result<FontRegistration, EditError> {
455 self.load_font_from_data(data, font_type, is_cid)
456 }
457
458 #[deprecated(note = "use `text_load_font()` — matches upstream `FPDFText_LoadFont`")]
460 #[inline]
461 pub fn load_font(
462 &mut self,
463 data: &[u8],
464 font_type: FontType,
465 is_cid: bool,
466 ) -> Result<FontRegistration, EditError> {
467 self.load_font_from_data(data, font_type, is_cid)
468 }
469
470 pub fn load_cid_type2_font(
479 &mut self,
480 _font_data: &[u8],
481 _to_unicode_cmap: Option<&[u8]>,
482 _cid_to_gid_map: Option<&[u8]>,
483 _is_cjk: bool,
484 ) -> Result<FontRegistration, EditError> {
485 Err(EditError::NotSupported(
486 "load_cid_type2_font: CID Type2 font embedding not yet implemented".into(),
487 ))
488 }
489
490 pub fn page_rotation(&self, page_index: usize) -> Result<u32, EditError> {
497 let page_id = self
498 .page_ids
499 .get(page_index)
500 .copied()
501 .ok_or(EditError::PageOutOfRange {
502 index: page_index,
503 count: self.page_ids.len(),
504 })?;
505 let page_obj = self.resolve(page_id)?;
506 let dict = page_obj
507 .as_dict()
508 .ok_or(EditError::Other("page not a dict".into()))?;
509 let rotate = dict
510 .get(&Name::rotate())
511 .and_then(|o| o.as_i64())
512 .unwrap_or(0);
513 Ok(rotate.rem_euclid(360) as u32)
514 }
515
516 #[inline]
520 pub fn page_get_rotation(&self, page_index: usize) -> Result<u32, EditError> {
521 self.page_rotation(page_index)
522 }
523
524 #[deprecated(note = "use `page_get_rotation()` — matches upstream `FPDFPage_GetRotation`")]
529 #[inline]
530 pub fn get_page_rotation(&self, page_index: usize) -> Result<u32, EditError> {
531 self.page_rotation(page_index)
532 }
533
534 #[deprecated(note = "use `page_get_rotation()` — matches upstream `FPDFPage_GetRotation`")]
536 #[inline]
537 pub fn get_rotation(&self, page_index: usize) -> Result<u32, EditError> {
538 self.page_rotation(page_index)
539 }
540
541 pub(crate) fn sync_pages_tree(&mut self) -> Result<(), EditError> {
543 let pages_id = self.pages_id()?;
544 let kids: Vec<Object> = self
546 .page_ids
547 .iter()
548 .map(|id| Object::Reference(*id))
549 .collect();
550 let count = self.page_ids.len() as i64;
551
552 let pages = self.get_mut(pages_id)?;
553 let dict = pages
554 .as_dict_mut()
555 .ok_or(EditError::Other("pages node is not a dictionary".into()))?;
556 dict.insert(Name::kids(), Object::Array(kids));
557 dict.insert(Name::count(), Object::Integer(count));
558 Ok(())
559 }
560
561 pub fn add_form_field(&mut self, spec: FormFieldSpec) -> Result<ObjectId, EditError> {
569 let count = self.page_count();
570 if spec.page_index >= count {
571 return Err(EditError::PageOutOfRange {
572 index: spec.page_index,
573 count,
574 });
575 }
576
577 let acroform_id = self.ensure_acroform()?;
579
580 let mut widget_dict = HashMap::new();
582 widget_dict.insert(
583 Name::r#type(),
584 Object::Name(Name::from_bytes(b"Annot".to_vec())),
585 );
586 widget_dict.insert(
587 Name::subtype(),
588 Object::Name(Name::from_bytes(b"Widget".to_vec())),
589 );
590 widget_dict.insert(
591 Name::rect(),
592 Object::Array(vec![
593 Object::Real(spec.rect[0] as f64),
594 Object::Real(spec.rect[1] as f64),
595 Object::Real(spec.rect[2] as f64),
596 Object::Real(spec.rect[3] as f64),
597 ]),
598 );
599 let page_id = self.page_ids()[spec.page_index];
600 widget_dict.insert(Name::p(), Object::Reference(page_id));
601 let widget_id = self.add_object(Object::Dictionary(widget_dict));
602
603 let mut field_dict = HashMap::new();
605 field_dict.insert(Name::ft(), Object::Name(spec.field_type.clone()));
606 field_dict.insert(
607 Name::t(),
608 Object::String(rpdfium_core::PdfString::from_bytes(
609 spec.name.as_bytes().to_vec(),
610 )),
611 );
612 field_dict.insert(
613 Name::rect(),
614 Object::Array(vec![
615 Object::Real(spec.rect[0] as f64),
616 Object::Real(spec.rect[1] as f64),
617 Object::Real(spec.rect[2] as f64),
618 Object::Real(spec.rect[3] as f64),
619 ]),
620 );
621 if spec.flags != 0 {
622 field_dict.insert(Name::ff(), Object::Integer(spec.flags as i64));
623 }
624 if let Some(ref dv) = spec.default_value {
625 field_dict.insert(
626 Name::dv(),
627 Object::String(rpdfium_core::PdfString::from_bytes(dv.as_bytes().to_vec())),
628 );
629 }
630 field_dict.insert(
632 Name::from_bytes(b"Kids".to_vec()),
633 Object::Array(vec![Object::Reference(widget_id)]),
634 );
635 let field_id = self.add_object(Object::Dictionary(field_dict));
636
637 {
639 let acroform = self.get_mut(acroform_id)?;
640 if let Object::Dictionary(dict) = acroform {
641 let fields_key = Name::from_bytes(b"Fields".to_vec());
642 let fields = dict
643 .entry(fields_key)
644 .or_insert_with(|| Object::Array(Vec::new()));
645 if let Object::Array(arr) = fields {
646 arr.push(Object::Reference(field_id));
647 }
648 }
649 }
650
651 {
653 let page = self.get_mut(page_id)?;
654 if let Object::Dictionary(dict) = page {
655 let annots = dict
656 .entry(Name::annots())
657 .or_insert_with(|| Object::Array(Vec::new()));
658 if let Object::Array(arr) = annots {
659 arr.push(Object::Reference(widget_id));
660 }
661 }
662 }
663
664 Ok(field_id)
665 }
666
667 pub fn delete_form_field(&mut self, field_name: &str) -> Result<(), EditError> {
673 let catalog_id = self.catalog_id();
675 let acroform_id = {
676 let catalog = self.resolve(catalog_id)?;
677 let dict = catalog
678 .as_dict()
679 .ok_or(EditError::Other("catalog is not a dict".into()))?;
680 match dict.get(&Name::from_bytes(b"AcroForm".to_vec())) {
681 Some(Object::Reference(id)) => *id,
682 _ => {
683 return Err(EditError::FieldNotFound(field_name.to_string()));
684 }
685 }
686 };
687
688 let field_id = {
689 let acroform = self.resolve(acroform_id)?;
690 let adict = acroform
691 .as_dict()
692 .ok_or(EditError::Other("AcroForm is not a dict".into()))?;
693 let fields_arr = adict
694 .get(&Name::from_bytes(b"Fields".to_vec()))
695 .and_then(|o| o.as_array())
696 .ok_or(EditError::FieldNotFound(field_name.to_string()))?;
697
698 let mut found_id: Option<ObjectId> = None;
699 for item in fields_arr {
700 if let Object::Reference(fid) = item {
701 if let Ok(fobj) = self.resolve(*fid) {
702 if let Some(fdict) = fobj.as_dict() {
703 let t_name = fdict
704 .get(&Name::t())
705 .and_then(|o| o.as_string())
706 .map(|s| String::from_utf8_lossy(s.as_bytes()).into_owned())
707 .unwrap_or_default();
708 if t_name == field_name {
709 found_id = Some(*fid);
710 break;
711 }
712 }
713 }
714 }
715 }
716 found_id.ok_or(EditError::FieldNotFound(field_name.to_string()))?
717 };
718
719 let widget_ids: Vec<ObjectId> = {
721 let field_obj = self.resolve(field_id)?;
722 if let Some(fdict) = field_obj.as_dict() {
723 fdict
724 .get(&Name::from_bytes(b"Kids".to_vec()))
725 .and_then(|o| o.as_array())
726 .map(|arr| arr.iter().filter_map(|o| o.as_reference()).collect())
727 .unwrap_or_default()
728 } else {
729 Vec::new()
730 }
731 };
732
733 for widget_id in &widget_ids {
735 let page_ids_clone: Vec<ObjectId> = self.page_ids.clone();
737 for pid in &page_ids_clone {
738 let page = self.get_mut(*pid)?;
739 if let Object::Dictionary(dict) = page {
740 if let Some(Object::Array(annots)) = dict.get_mut(&Name::annots()) {
741 let wid = *widget_id;
742 annots.retain(|obj| {
743 if let Object::Reference(id) = obj {
744 *id != wid
745 } else {
746 true
747 }
748 });
749 }
750 }
751 }
752 }
753
754 for widget_id in widget_ids {
756 self.delete_object(widget_id);
757 }
758
759 {
761 let acroform = self.get_mut(acroform_id)?;
762 if let Object::Dictionary(adict) = acroform {
763 if let Some(Object::Array(fields)) =
764 adict.get_mut(&Name::from_bytes(b"Fields".to_vec()))
765 {
766 fields.retain(|obj| {
767 if let Object::Reference(id) = obj {
768 *id != field_id
769 } else {
770 true
771 }
772 });
773 }
774 }
775 }
776
777 self.delete_object(field_id);
779 Ok(())
780 }
781
782 pub fn copy_viewer_preferences_from(&mut self, src: &EditDocument) -> Result<(), EditError> {
790 let vp_key = Name::from_bytes(b"ViewerPreferences".to_vec());
791
792 let src_catalog_id = src.catalog_id();
794 let src_catalog = src.resolve(src_catalog_id)?;
795 let src_dict = src_catalog
796 .as_dict()
797 .ok_or(EditError::Other("src catalog not a dict".into()))?;
798
799 let vp_obj = match src_dict.get(&vp_key) {
800 None => return Ok(()), Some(Object::Reference(id)) => src.resolve(*id)?.clone(),
802 Some(obj) => obj.clone(),
803 };
804
805 let vp_id = self.add_object(vp_obj);
807
808 let dest_catalog_id = self.catalog_id();
810 let dest_catalog = self.get_mut(dest_catalog_id)?;
811 if let Object::Dictionary(dict) = dest_catalog {
812 dict.insert(vp_key, Object::Reference(vp_id));
813 }
814
815 Ok(())
816 }
817
818 pub fn new_xobject_from_page(
830 &mut self,
831 src: &EditDocument,
832 src_page_index: usize,
833 ) -> Result<(rpdfium_parser::object::ObjectId, (f64, f64)), EditError> {
834 crate::cpdf_npagetooneexporter::make_xobject_from_page(src, src_page_index, self)
835 }
836
837 pub fn set_media_box(
845 &mut self,
846 page_index: usize,
847 rect: rpdfium_core::Rect,
848 ) -> Result<(), EditError> {
849 self.set_page_box(page_index, Name::media_box(), rect)
850 }
851
852 #[inline]
856 pub fn page_set_media_box(
857 &mut self,
858 page_index: usize,
859 rect: rpdfium_core::Rect,
860 ) -> Result<(), EditError> {
861 self.set_media_box(page_index, rect)
862 }
863
864 pub fn set_crop_box(
868 &mut self,
869 page_index: usize,
870 rect: rpdfium_core::Rect,
871 ) -> Result<(), EditError> {
872 self.set_page_box(page_index, Name::crop_box(), rect)
873 }
874
875 #[inline]
879 pub fn page_set_crop_box(
880 &mut self,
881 page_index: usize,
882 rect: rpdfium_core::Rect,
883 ) -> Result<(), EditError> {
884 self.set_crop_box(page_index, rect)
885 }
886
887 pub fn set_bleed_box(
891 &mut self,
892 page_index: usize,
893 rect: rpdfium_core::Rect,
894 ) -> Result<(), EditError> {
895 self.set_page_box(page_index, Name::bleed_box(), rect)
896 }
897
898 #[inline]
902 pub fn page_set_bleed_box(
903 &mut self,
904 page_index: usize,
905 rect: rpdfium_core::Rect,
906 ) -> Result<(), EditError> {
907 self.set_bleed_box(page_index, rect)
908 }
909
910 pub fn set_trim_box(
914 &mut self,
915 page_index: usize,
916 rect: rpdfium_core::Rect,
917 ) -> Result<(), EditError> {
918 self.set_page_box(page_index, Name::trim_box(), rect)
919 }
920
921 #[inline]
925 pub fn page_set_trim_box(
926 &mut self,
927 page_index: usize,
928 rect: rpdfium_core::Rect,
929 ) -> Result<(), EditError> {
930 self.set_trim_box(page_index, rect)
931 }
932
933 pub fn set_art_box(
937 &mut self,
938 page_index: usize,
939 rect: rpdfium_core::Rect,
940 ) -> Result<(), EditError> {
941 self.set_page_box(page_index, Name::art_box(), rect)
942 }
943
944 #[inline]
948 pub fn page_set_art_box(
949 &mut self,
950 page_index: usize,
951 rect: rpdfium_core::Rect,
952 ) -> Result<(), EditError> {
953 self.set_art_box(page_index, rect)
954 }
955
956 fn set_page_box(
958 &mut self,
959 page_index: usize,
960 key: Name,
961 rect: rpdfium_core::Rect,
962 ) -> Result<(), EditError> {
963 let page_id = self
964 .page_ids
965 .get(page_index)
966 .copied()
967 .ok_or(EditError::PageOutOfRange {
968 index: page_index,
969 count: self.page_ids.len(),
970 })?;
971 let page = self.get_mut(page_id)?;
972 let dict = page
973 .as_dict_mut()
974 .ok_or(EditError::Other("page is not a dict".into()))?;
975 dict.insert(
976 key,
977 Object::Array(vec![
978 Object::Real(rect.left),
979 Object::Real(rect.bottom),
980 Object::Real(rect.right),
981 Object::Real(rect.top),
982 ]),
983 );
984 Ok(())
985 }
986
987 pub fn add_attachment(&mut self, name: &str) -> Result<usize, EditError> {
999 let mut stream_dict = HashMap::new();
1001 stream_dict.insert(
1002 Name::r#type(),
1003 Object::Name(Name::from_bytes(b"EmbeddedFile".to_vec())),
1004 );
1005 let stream = Object::Stream {
1006 dict: stream_dict,
1007 data: rpdfium_parser::object::StreamData::Decoded { data: Vec::new() },
1008 };
1009 let stream_id = self.add_object(stream);
1010
1011 let mut ef_dict = HashMap::new();
1013 ef_dict.insert(Name::f(), Object::Reference(stream_id));
1014
1015 let mut fs_dict = HashMap::new();
1017 fs_dict.insert(Name::r#type(), Object::Name(Name::file_spec_type()));
1018 fs_dict.insert(
1019 Name::uf(),
1020 Object::String(rpdfium_core::PdfString::from_bytes(
1021 name.as_bytes().to_vec(),
1022 )),
1023 );
1024 fs_dict.insert(
1025 Name::f(),
1026 Object::String(rpdfium_core::PdfString::from_bytes(
1027 name.as_bytes().to_vec(),
1028 )),
1029 );
1030 fs_dict.insert(Name::ef(), Object::Dictionary(ef_dict));
1031 let fs_id = self.add_object(Object::Dictionary(fs_dict));
1032
1033 let ef_names_id = self.ensure_embedded_files()?;
1035 let ef_names = self.get_mut(ef_names_id)?;
1036 let dict = ef_names
1037 .as_dict_mut()
1038 .ok_or(EditError::Other("EmbeddedFiles is not a dict".into()))?;
1039 let names_key = Name::names();
1040 let names_arr = dict
1041 .entry(names_key)
1042 .or_insert_with(|| Object::Array(Vec::new()));
1043 if let Object::Array(arr) = names_arr {
1044 let index = arr.len() / 2;
1045 arr.push(Object::String(rpdfium_core::PdfString::from_bytes(
1046 name.as_bytes().to_vec(),
1047 )));
1048 arr.push(Object::Reference(fs_id));
1049 Ok(index)
1050 } else {
1051 Err(EditError::Other(
1052 "EmbeddedFiles/Names is not an array".into(),
1053 ))
1054 }
1055 }
1056
1057 #[deprecated(note = "use `add_attachment()` — matches upstream FPDFDoc_AddAttachment")]
1062 #[inline]
1063 pub fn add_doc_attachment(&mut self, name: &str) -> Result<usize, EditError> {
1064 self.add_attachment(name)
1065 }
1066
1067 pub fn delete_attachment(&mut self, index: usize) -> Result<(), EditError> {
1071 let ef_names_id = self.find_embedded_files_id()?;
1072 let ef_names = self.get_mut(ef_names_id)?;
1073 let dict = ef_names
1074 .as_dict_mut()
1075 .ok_or(EditError::Other("EmbeddedFiles is not a dict".into()))?;
1076 let names_arr = dict
1077 .get_mut(&Name::names())
1078 .and_then(|o| {
1079 if let Object::Array(arr) = o {
1080 Some(arr)
1081 } else {
1082 None
1083 }
1084 })
1085 .ok_or(EditError::Other(
1086 "EmbeddedFiles/Names is not an array".into(),
1087 ))?;
1088
1089 let pair_start = index * 2;
1091 if pair_start + 1 >= names_arr.len() {
1092 return Err(EditError::Other(format!(
1093 "attachment index {index} out of range (have {} attachments)",
1094 names_arr.len() / 2
1095 )));
1096 }
1097
1098 if let Object::Reference(fs_id) = names_arr[pair_start + 1] {
1100 self.delete_object(fs_id);
1101 }
1102
1103 let ef_names = self.get_mut(ef_names_id)?;
1105 let dict = ef_names.as_dict_mut().unwrap();
1106 if let Some(Object::Array(arr)) = dict.get_mut(&Name::names()) {
1107 let pair_start = index * 2;
1108 if pair_start + 1 < arr.len() {
1109 arr.remove(pair_start + 1);
1110 arr.remove(pair_start);
1111 }
1112 }
1113 Ok(())
1114 }
1115
1116 #[deprecated(note = "use `delete_attachment()` — matches upstream FPDFDoc_DeleteAttachment")]
1121 #[inline]
1122 pub fn delete_doc_attachment(&mut self, index: usize) -> Result<(), EditError> {
1123 self.delete_attachment(index)
1124 }
1125
1126 pub fn attachment_has_key(&self, index: usize, key: &str) -> Result<bool, EditError> {
1131 let fs_dict = self.resolve_attachment_dict(index)?;
1132 Ok(fs_dict.contains_key(&Name::from_bytes(key.as_bytes().to_vec())))
1133 }
1134
1135 #[deprecated(note = "use `attachment_has_key()` — matches upstream `FPDFAttachment_HasKey`")]
1137 #[inline]
1138 pub fn has_key(&self, index: usize, key: &str) -> Result<bool, EditError> {
1139 self.attachment_has_key(index, key)
1140 }
1141
1142 pub fn attachment_value_type(
1150 &self,
1151 index: usize,
1152 key: &str,
1153 ) -> Result<Option<&'static str>, EditError> {
1154 let fs_dict = self.resolve_attachment_dict(index)?;
1155 let name_key = Name::from_bytes(key.as_bytes().to_vec());
1156 match fs_dict.get(&name_key) {
1157 None => Ok(None),
1158 Some(obj) => Ok(Some(object_type_name(obj))),
1159 }
1160 }
1161
1162 #[inline]
1166 pub fn attachment_get_value_type(
1167 &self,
1168 index: usize,
1169 key: &str,
1170 ) -> Result<Option<&'static str>, EditError> {
1171 self.attachment_value_type(index, key)
1172 }
1173
1174 #[deprecated(
1176 note = "use `attachment_get_value_type()` — matches upstream `FPDFAttachment_GetValueType`"
1177 )]
1178 #[inline]
1179 pub fn get_value_type(
1180 &self,
1181 index: usize,
1182 key: &str,
1183 ) -> Result<Option<&'static str>, EditError> {
1184 self.attachment_value_type(index, key)
1185 }
1186
1187 #[deprecated(
1192 note = "use `attachment_get_value_type()` — matches upstream `FPDFAttachment_GetValueType`"
1193 )]
1194 #[inline]
1195 pub fn get_attachment_value_type(
1196 &self,
1197 index: usize,
1198 key: &str,
1199 ) -> Result<Option<&'static str>, EditError> {
1200 self.attachment_value_type(index, key)
1201 }
1202
1203 pub fn set_attachment_string(
1207 &mut self,
1208 index: usize,
1209 key: &str,
1210 value: &str,
1211 ) -> Result<(), EditError> {
1212 let fs_id = self.resolve_attachment_object_id(index)?;
1213 let fs_obj = self.get_mut(fs_id)?;
1214 let dict = fs_obj
1215 .as_dict_mut()
1216 .ok_or(EditError::Other("filespec is not a dict".into()))?;
1217 dict.insert(
1218 Name::from_bytes(key.as_bytes().to_vec()),
1219 Object::String(rpdfium_core::PdfString::from_bytes(
1220 value.as_bytes().to_vec(),
1221 )),
1222 );
1223 Ok(())
1224 }
1225
1226 #[inline]
1230 pub fn attachment_set_string_value(
1231 &mut self,
1232 index: usize,
1233 key: &str,
1234 value: &str,
1235 ) -> Result<(), EditError> {
1236 self.set_attachment_string(index, key, value)
1237 }
1238
1239 #[deprecated(
1241 note = "use `attachment_set_string_value()` — matches upstream `FPDFAttachment_SetStringValue`"
1242 )]
1243 #[inline]
1244 pub fn set_string_value(
1245 &mut self,
1246 index: usize,
1247 key: &str,
1248 value: &str,
1249 ) -> Result<(), EditError> {
1250 self.set_attachment_string(index, key, value)
1251 }
1252
1253 pub fn attachment_string(&self, index: usize, key: &str) -> Result<Option<String>, EditError> {
1257 let fs_dict = self.resolve_attachment_dict(index)?;
1258 let name_key = Name::from_bytes(key.as_bytes().to_vec());
1259 Ok(fs_dict
1260 .get(&name_key)
1261 .and_then(|o| o.as_string())
1262 .map(|s| s.to_string_lossy()))
1263 }
1264
1265 #[inline]
1269 pub fn attachment_get_string_value(
1270 &self,
1271 index: usize,
1272 key: &str,
1273 ) -> Result<Option<String>, EditError> {
1274 self.attachment_string(index, key)
1275 }
1276
1277 #[deprecated(
1279 note = "use `attachment_get_string_value()` — matches upstream `FPDFAttachment_GetStringValue`"
1280 )]
1281 #[inline]
1282 pub fn get_string_value(&self, index: usize, key: &str) -> Result<Option<String>, EditError> {
1283 self.attachment_string(index, key)
1284 }
1285
1286 #[deprecated(
1291 note = "use `attachment_get_string_value()` — matches upstream `FPDFAttachment_GetStringValue`"
1292 )]
1293 #[inline]
1294 pub fn get_attachment_string(
1295 &self,
1296 index: usize,
1297 key: &str,
1298 ) -> Result<Option<String>, EditError> {
1299 self.attachment_string(index, key)
1300 }
1301
1302 pub fn set_attachment_file(&mut self, index: usize, data: &[u8]) -> Result<(), EditError> {
1308 let stream_id = {
1310 let fs_dict = self.resolve_attachment_dict(index)?;
1311 let ef_obj = fs_dict
1312 .get(&Name::ef())
1313 .ok_or(EditError::Other("filespec missing /EF".into()))?;
1314 let ef_dict = ef_obj
1315 .as_dict()
1316 .ok_or(EditError::Other("/EF is not a dict".into()))?;
1317 ef_dict
1318 .get(&Name::f())
1319 .and_then(|o| o.as_reference())
1320 .ok_or(EditError::Other("/EF/F is not a reference".into()))?
1321 };
1322
1323 let mut stream_dict = HashMap::new();
1325 stream_dict.insert(
1326 Name::r#type(),
1327 Object::Name(Name::from_bytes(b"EmbeddedFile".to_vec())),
1328 );
1329 let stream = Object::Stream {
1330 dict: stream_dict,
1331 data: rpdfium_parser::object::StreamData::Decoded {
1332 data: data.to_vec(),
1333 },
1334 };
1335 self.set_object(stream_id, stream);
1336 Ok(())
1337 }
1338
1339 #[inline]
1343 pub fn attachment_set_file(&mut self, index: usize, data: &[u8]) -> Result<(), EditError> {
1344 self.set_attachment_file(index, data)
1345 }
1346
1347 #[deprecated(note = "use `attachment_set_file()` — matches upstream `FPDFAttachment_SetFile`")]
1349 #[inline]
1350 pub fn set_file(&mut self, index: usize, data: &[u8]) -> Result<(), EditError> {
1351 self.set_attachment_file(index, data)
1352 }
1353
1354 pub fn attachment_file(&self, index: usize) -> Result<Option<Vec<u8>>, EditError> {
1362 let fs_dict = self.resolve_attachment_dict(index)?;
1363 let ef_obj = match fs_dict.get(&Name::ef()) {
1364 Some(o) => o.clone(),
1365 None => return Ok(None),
1366 };
1367 let ef_dict = match ef_obj.as_dict() {
1368 Some(d) => d.clone(),
1369 None => return Ok(None),
1370 };
1371 let stream_id = match ef_dict.get(&Name::f()).and_then(|o| o.as_reference()) {
1372 Some(id) => id,
1373 None => return Ok(None),
1374 };
1375 let stream_obj = self.resolve(stream_id)?.clone();
1378 match &stream_obj {
1379 Object::Stream { data, .. } => match data {
1380 StreamData::Decoded { data } => Ok(Some(data.clone())),
1381 StreamData::Raw { .. } => {
1382 self.decode_stream(&stream_obj).map(Some)
1384 }
1385 },
1386 _ => Ok(None),
1387 }
1388 }
1389
1390 #[inline]
1394 pub fn attachment_get_file(&self, index: usize) -> Result<Option<Vec<u8>>, EditError> {
1395 self.attachment_file(index)
1396 }
1397
1398 #[deprecated(note = "use `attachment_get_file()` — matches upstream `FPDFAttachment_GetFile`")]
1402 #[inline]
1403 pub fn get_file(&self, index: usize) -> Result<Option<Vec<u8>>, EditError> {
1404 self.attachment_file(index)
1405 }
1406
1407 fn find_embedded_files_id(&self) -> Result<ObjectId, EditError> {
1413 let catalog_id = self.catalog_id();
1414 let catalog = self.resolve(catalog_id)?;
1415 let catalog_dict = catalog
1416 .as_dict()
1417 .ok_or(EditError::Other("catalog is not a dict".into()))?;
1418
1419 let names_ref = catalog_dict
1420 .get(&Name::names())
1421 .and_then(|o| o.as_reference())
1422 .ok_or(EditError::Other("catalog missing /Names reference".into()))?;
1423
1424 let names_obj = self.resolve(names_ref)?;
1425 let names_dict = names_obj
1426 .as_dict()
1427 .ok_or(EditError::Other("/Names is not a dict".into()))?;
1428
1429 names_dict
1430 .get(&Name::embedded_files())
1431 .and_then(|o| o.as_reference())
1432 .ok_or(EditError::Other("/Names missing /EmbeddedFiles".into()))
1433 }
1434
1435 fn ensure_embedded_files(&mut self) -> Result<ObjectId, EditError> {
1437 let catalog_id = self.catalog_id();
1438 let catalog = self.resolve(catalog_id)?;
1439 let catalog_dict = catalog
1440 .as_dict()
1441 .ok_or(EditError::Other("catalog is not a dict".into()))?;
1442
1443 let names_id = if let Some(Object::Reference(id)) = catalog_dict.get(&Name::names()) {
1445 *id
1446 } else {
1447 let names_dict = HashMap::new();
1449 let id = self.add_object(Object::Dictionary(names_dict));
1450 let catalog_mut = self.get_mut(catalog_id)?;
1451 if let Object::Dictionary(d) = catalog_mut {
1452 d.insert(Name::names(), Object::Reference(id));
1453 }
1454 id
1455 };
1456
1457 let names_obj = self.resolve(names_id)?;
1459 let names_dict = names_obj
1460 .as_dict()
1461 .ok_or(EditError::Other("/Names is not a dict".into()))?;
1462
1463 if let Some(Object::Reference(ef_id)) = names_dict.get(&Name::embedded_files()) {
1464 Ok(*ef_id)
1465 } else {
1466 let mut ef_dict = HashMap::new();
1468 ef_dict.insert(Name::names(), Object::Array(Vec::new()));
1469 let ef_id = self.add_object(Object::Dictionary(ef_dict));
1470
1471 let names_mut = self.get_mut(names_id)?;
1472 if let Object::Dictionary(d) = names_mut {
1473 d.insert(Name::embedded_files(), Object::Reference(ef_id));
1474 }
1475 Ok(ef_id)
1476 }
1477 }
1478
1479 fn resolve_attachment_dict(&self, index: usize) -> Result<HashMap<Name, Object>, EditError> {
1481 let fs_id = self.resolve_attachment_object_id(index)?;
1482 let fs_obj = self.resolve(fs_id)?;
1483 fs_obj
1484 .as_dict()
1485 .cloned()
1486 .ok_or(EditError::Other("filespec is not a dict".into()))
1487 }
1488
1489 fn resolve_attachment_object_id(&self, index: usize) -> Result<ObjectId, EditError> {
1491 let ef_names_id = self.find_embedded_files_id()?;
1492 let ef_names = self.resolve(ef_names_id)?;
1493 let dict = ef_names
1494 .as_dict()
1495 .ok_or(EditError::Other("EmbeddedFiles is not a dict".into()))?;
1496 let names_arr =
1497 dict.get(&Name::names())
1498 .and_then(|o| o.as_array())
1499 .ok_or(EditError::Other(
1500 "EmbeddedFiles/Names is not an array".into(),
1501 ))?;
1502
1503 let pair_start = index * 2;
1504 if pair_start + 1 >= names_arr.len() {
1505 return Err(EditError::Other(format!(
1506 "attachment index {index} out of range (have {} attachments)",
1507 names_arr.len() / 2
1508 )));
1509 }
1510
1511 names_arr[pair_start + 1]
1512 .as_reference()
1513 .ok_or(EditError::Other(
1514 "attachment value is not a reference".into(),
1515 ))
1516 }
1517
1518 fn ensure_acroform(&mut self) -> Result<ObjectId, EditError> {
1521 let catalog_id = self.catalog_id();
1522 let catalog = self.resolve(catalog_id)?;
1523 let acroform_key = Name::from_bytes(b"AcroForm".to_vec());
1524
1525 if let Some(Object::Reference(id)) = catalog.as_dict().and_then(|d| d.get(&acroform_key)) {
1527 return Ok(*id);
1528 }
1529
1530 let mut acroform_dict = HashMap::new();
1532 acroform_dict.insert(
1533 Name::from_bytes(b"Fields".to_vec()),
1534 Object::Array(Vec::new()),
1535 );
1536 let acroform_id = self.add_object(Object::Dictionary(acroform_dict));
1537
1538 let catalog_mut = self.get_mut(catalog_id)?;
1540 if let Object::Dictionary(dict) = catalog_mut {
1541 dict.insert(acroform_key, Object::Reference(acroform_id));
1542 }
1543 Ok(acroform_id)
1544 }
1545}
1546
1547fn object_type_name(obj: &Object) -> &'static str {
1549 match obj {
1550 Object::Boolean(_) => "boolean",
1551 Object::Integer(_) | Object::Real(_) => "number",
1552 Object::String(_) => "string",
1553 Object::Name(_) => "name",
1554 Object::Array(_) => "array",
1555 Object::Dictionary(_) => "dictionary",
1556 Object::Stream { .. } => "stream",
1557 Object::Reference(_) => "reference",
1558 Object::Null => "null",
1559 }
1560}
1561
1562#[cfg(test)]
1563mod tests {
1564 use super::*;
1565
1566 #[test]
1567 fn test_new_blank_has_catalog_and_pages() {
1568 let doc = EditDocument::new_blank();
1569 assert_eq!(doc.catalog_id(), ObjectId::new(1, 0));
1570 assert_eq!(doc.page_count(), 0);
1571 assert!(doc.is_dirty());
1572
1573 let catalog = doc.resolve(doc.catalog_id()).unwrap();
1575 let dict = catalog.as_dict().unwrap();
1576 assert!(dict.contains_key(&Name::r#type()));
1577
1578 let pages_ref = dict.get(&Name::pages()).unwrap();
1580 if let Object::Reference(pages_id) = pages_ref {
1581 let pages = doc.resolve(*pages_id).unwrap();
1582 let pdict = pages.as_dict().unwrap();
1583 assert_eq!(pdict.get(&Name::count()).and_then(|o| o.as_i64()), Some(0));
1584 } else {
1585 panic!("expected reference to Pages");
1586 }
1587 }
1588
1589 #[test]
1590 fn test_add_object_returns_unique_ids() {
1591 let mut doc = EditDocument::new_blank();
1592 let id1 = doc.add_object(Object::Integer(1));
1593 let id2 = doc.add_object(Object::Integer(2));
1594 assert_ne!(id1, id2);
1595
1596 let obj1 = doc.resolve(id1).unwrap();
1597 assert_eq!(obj1.as_i64(), Some(1));
1598 let obj2 = doc.resolve(id2).unwrap();
1599 assert_eq!(obj2.as_i64(), Some(2));
1600 }
1601
1602 #[test]
1603 fn test_delete_object_makes_unresolvable() {
1604 let mut doc = EditDocument::new_blank();
1605 let id = doc.add_object(Object::Integer(42));
1606 assert!(doc.resolve(id).is_ok());
1607
1608 doc.delete_object(id);
1609 assert!(doc.resolve(id).is_err());
1610 }
1611
1612 #[test]
1613 fn test_set_object_replaces_value() {
1614 let mut doc = EditDocument::new_blank();
1615 let id = doc.add_object(Object::Integer(1));
1616 doc.set_object(id, Object::Integer(2));
1617
1618 let obj = doc.resolve(id).unwrap();
1619 assert_eq!(obj.as_i64(), Some(2));
1620 }
1621
1622 #[test]
1623 fn test_get_mut_modifies_in_place() {
1624 let mut doc = EditDocument::new_blank();
1625 let id = doc.add_object(Object::Integer(1));
1626 let obj = doc.get_mut(id).unwrap();
1627 *obj = Object::Integer(99);
1628
1629 let resolved = doc.resolve(id).unwrap();
1630 assert_eq!(resolved.as_i64(), Some(99));
1631 }
1632
1633 #[test]
1634 fn test_all_ids_includes_all_non_deleted() {
1635 let mut doc = EditDocument::new_blank();
1636 let id1 = doc.add_object(Object::Integer(1));
1637 let id2 = doc.add_object(Object::Integer(2));
1638 doc.delete_object(id2);
1639
1640 let all = doc.all_ids();
1641 assert!(all.contains(&id1));
1642 assert!(!all.contains(&id2));
1643 assert!(all.contains(&ObjectId::new(1, 0)));
1645 assert!(all.contains(&ObjectId::new(2, 0)));
1646 }
1647
1648 #[test]
1649 fn test_changed_ids_lists_modifications() {
1650 let mut doc = EditDocument::new_blank();
1651 let id = doc.add_object(Object::Integer(42));
1652
1653 let changed: Vec<ObjectId> = doc.changed_ids().collect();
1654 assert!(changed.contains(&id));
1656 }
1657
1658 #[test]
1659 fn test_pages_id_resolves_from_catalog() {
1660 let doc = EditDocument::new_blank();
1661 let pages_id = doc.pages_id().unwrap();
1662 assert_eq!(pages_id, ObjectId::new(2, 0));
1663 }
1664
1665 #[test]
1666 fn test_sync_pages_tree_updates_kids() {
1667 let mut doc = EditDocument::new_blank();
1668 let page_id = ObjectId::new(10, 0);
1669 doc.page_ids_mut().push(page_id);
1670 doc.new_objects.insert(
1671 page_id,
1672 Object::Dictionary({
1673 let mut d = HashMap::new();
1674 d.insert(
1675 Name::r#type(),
1676 Object::Name(Name::from_bytes(b"Page".to_vec())),
1677 );
1678 d
1679 }),
1680 );
1681 doc.sync_pages_tree().unwrap();
1682
1683 let pages_id = doc.pages_id().unwrap();
1684 let pages = doc.resolve(pages_id).unwrap();
1685 let dict = pages.as_dict().unwrap();
1686 assert_eq!(dict.get(&Name::count()).and_then(|o| o.as_i64()), Some(1));
1687 let kids = dict.get(&Name::kids()).and_then(|o| o.as_array()).unwrap();
1688 assert_eq!(kids.len(), 1);
1689 }
1690
1691 #[test]
1692 fn test_allocate_id_increments() {
1693 let mut doc = EditDocument::new_blank();
1694 let first = doc.next_object_number();
1695 let id1 = doc.allocate_id();
1696 assert_eq!(id1.number, first);
1697 let id2 = doc.allocate_id();
1698 assert_eq!(id2.number, first + 1);
1699 }
1700
1701 #[test]
1702 fn test_deleted_ids_tracked() {
1703 let mut doc = EditDocument::new_blank();
1704 let id = doc.add_object(Object::Null);
1705 assert!(doc.deleted_ids().is_empty());
1706 doc.delete_object(id);
1707 assert!(doc.deleted_ids().contains(&id));
1708 }
1709
1710 #[test]
1715 fn test_load_standard_font_creates_font_object() {
1716 let mut doc = EditDocument::new_blank();
1717 let reg = doc.load_standard_font("Helvetica").unwrap();
1718
1719 assert!(reg.object_id.number > 0);
1721
1722 let obj = doc.resolve(reg.object_id).unwrap();
1724 let dict = obj.as_dict().unwrap();
1725
1726 let subtype = dict
1727 .get(&Name::from_bytes(b"Subtype".to_vec()))
1728 .and_then(|o| o.as_name())
1729 .map(|n| n.as_bytes().to_vec());
1730 assert_eq!(subtype, Some(b"Type1".to_vec()));
1731
1732 let base = dict
1733 .get(&Name::from_bytes(b"BaseFont".to_vec()))
1734 .and_then(|o| o.as_name())
1735 .map(|n| n.as_bytes().to_vec());
1736 assert_eq!(base, Some(b"Helvetica".to_vec()));
1737 }
1738
1739 #[test]
1740 fn test_load_font_from_data_creates_embedded_objects() {
1741 let mut doc = EditDocument::new_blank();
1742 let initial_count = doc.all_ids().len();
1743
1744 let fake_data = b"FAKE_TRUETYPE_DATA_BYTES";
1746 let reg = doc
1747 .load_font_from_data(fake_data, crate::font_reg::FontType::TrueType, false)
1748 .unwrap();
1749
1750 let new_count = doc.all_ids().len();
1752 assert_eq!(new_count, initial_count + 3);
1753
1754 let font_obj = doc.resolve(reg.object_id).unwrap();
1756 let font_dict = font_obj.as_dict().unwrap();
1757 let subtype = font_dict
1758 .get(&Name::from_bytes(b"Subtype".to_vec()))
1759 .and_then(|o| o.as_name())
1760 .map(|n| n.as_bytes().to_vec());
1761 assert_eq!(subtype, Some(b"TrueType".to_vec()));
1762 }
1763
1764 #[test]
1769 fn test_copy_viewer_preferences_from_noop_when_src_has_none() {
1770 let src = EditDocument::new_blank();
1771 let mut dest = EditDocument::new_blank();
1772 dest.copy_viewer_preferences_from(&src).unwrap();
1774 let catalog = dest.resolve(dest.catalog_id()).unwrap();
1776 let dict = catalog.as_dict().unwrap();
1777 assert!(!dict.contains_key(&Name::from_bytes(b"ViewerPreferences".to_vec())));
1778 }
1779
1780 #[test]
1781 fn test_copy_viewer_preferences_from_copies_dict() {
1782 let mut src = EditDocument::new_blank();
1783
1784 let vp_key = Name::from_bytes(b"ViewerPreferences".to_vec());
1786 let fit_window_key = Name::from_bytes(b"FitWindow".to_vec());
1787 let mut vp_dict = std::collections::HashMap::new();
1788 vp_dict.insert(fit_window_key.clone(), Object::Boolean(true));
1789 let vp_id = src.add_object(Object::Dictionary(vp_dict));
1790 let catalog_id = src.catalog_id();
1791 let catalog = src.get_mut(catalog_id).unwrap();
1792 if let Object::Dictionary(dict) = catalog {
1793 dict.insert(vp_key.clone(), Object::Reference(vp_id));
1794 }
1795
1796 let mut dest = EditDocument::new_blank();
1797 dest.copy_viewer_preferences_from(&src).unwrap();
1798
1799 let dest_catalog = dest.resolve(dest.catalog_id()).unwrap();
1801 let dest_dict = dest_catalog.as_dict().unwrap();
1802 let vp_ref = dest_dict
1803 .get(&vp_key)
1804 .expect("/ViewerPreferences not found");
1805
1806 let new_vp_id = match vp_ref {
1808 Object::Reference(id) => *id,
1809 _ => panic!("expected reference"),
1810 };
1811 let new_vp = dest.resolve(new_vp_id).unwrap();
1812 let new_vp_dict = new_vp.as_dict().unwrap();
1813 assert_eq!(
1814 new_vp_dict.get(&fit_window_key).and_then(|o| o.as_bool()),
1815 Some(true)
1816 );
1817 }
1818
1819 #[test]
1820 fn test_load_font_from_data_cid_true_returns_error() {
1821 let mut doc = EditDocument::new_blank();
1822 let result = doc.load_font_from_data(b"data", crate::font_reg::FontType::TrueType, true);
1823 assert!(result.is_err());
1824 assert!(matches!(result, Err(EditError::NotSupported(_))));
1825 }
1826
1827 #[test]
1832 fn test_set_media_box_updates_page_dict() {
1833 use rpdfium_core::Rect;
1834 let mut doc = EditDocument::new_blank();
1835 doc.add_page(Rect::new(0.0, 0.0, 612.0, 792.0));
1836 doc.set_media_box(0, Rect::new(10.0, 20.0, 600.0, 780.0))
1837 .unwrap();
1838 let page_id = doc.page_id(0).unwrap();
1839 let page = doc.resolve(page_id).unwrap();
1840 let dict = page.as_dict().unwrap();
1841 let arr = dict.get(&Name::media_box()).unwrap().as_array().unwrap();
1842 assert_eq!(arr[0].as_f64(), Some(10.0));
1843 assert_eq!(arr[1].as_f64(), Some(20.0));
1844 assert_eq!(arr[2].as_f64(), Some(600.0));
1845 assert_eq!(arr[3].as_f64(), Some(780.0));
1846 }
1847
1848 #[test]
1849 fn test_set_crop_box_updates_page_dict() {
1850 use rpdfium_core::Rect;
1851 let mut doc = EditDocument::new_blank();
1852 doc.add_page(Rect::new(0.0, 0.0, 612.0, 792.0));
1853 doc.set_crop_box(0, Rect::new(5.0, 5.0, 607.0, 787.0))
1854 .unwrap();
1855 let page_id = doc.page_id(0).unwrap();
1856 let page = doc.resolve(page_id).unwrap();
1857 let dict = page.as_dict().unwrap();
1858 assert!(dict.contains_key(&Name::crop_box()));
1859 }
1860
1861 #[test]
1862 fn test_set_bleed_box_updates_page_dict() {
1863 use rpdfium_core::Rect;
1864 let mut doc = EditDocument::new_blank();
1865 doc.add_page(Rect::new(0.0, 0.0, 612.0, 792.0));
1866 doc.set_bleed_box(0, Rect::new(-3.0, -3.0, 615.0, 795.0))
1867 .unwrap();
1868 let page_id = doc.page_id(0).unwrap();
1869 let page = doc.resolve(page_id).unwrap();
1870 let dict = page.as_dict().unwrap();
1871 assert!(dict.contains_key(&Name::bleed_box()));
1872 }
1873
1874 #[test]
1875 fn test_set_trim_box_updates_page_dict() {
1876 use rpdfium_core::Rect;
1877 let mut doc = EditDocument::new_blank();
1878 doc.add_page(Rect::new(0.0, 0.0, 595.0, 842.0));
1879 doc.set_trim_box(0, Rect::new(8.0, 8.0, 587.0, 834.0))
1880 .unwrap();
1881 let page_id = doc.page_id(0).unwrap();
1882 let page = doc.resolve(page_id).unwrap();
1883 let dict = page.as_dict().unwrap();
1884 assert!(dict.contains_key(&Name::trim_box()));
1885 }
1886
1887 #[test]
1888 fn test_set_art_box_updates_page_dict() {
1889 use rpdfium_core::Rect;
1890 let mut doc = EditDocument::new_blank();
1891 doc.add_page(Rect::new(0.0, 0.0, 595.0, 842.0));
1892 doc.set_art_box(0, Rect::new(12.0, 12.0, 583.0, 830.0))
1893 .unwrap();
1894 let page_id = doc.page_id(0).unwrap();
1895 let page = doc.resolve(page_id).unwrap();
1896 let dict = page.as_dict().unwrap();
1897 assert!(dict.contains_key(&Name::art_box()));
1898 }
1899
1900 #[test]
1901 fn test_set_page_box_out_of_range_returns_error() {
1902 use rpdfium_core::Rect;
1903 let mut doc = EditDocument::new_blank();
1904 assert!(
1905 doc.set_media_box(0, Rect::new(0.0, 0.0, 612.0, 792.0))
1906 .is_err()
1907 );
1908 }
1909
1910 #[test]
1911 fn test_get_page_rotation_default_zero() {
1912 let mut doc = EditDocument::new_blank();
1913 doc.add_page(rpdfium_core::Rect::new(0.0, 0.0, 612.0, 792.0));
1914 assert_eq!(doc.page_rotation(0).unwrap(), 0);
1915 }
1916
1917 #[test]
1918 fn test_get_page_rotation_after_set() {
1919 let mut doc = EditDocument::new_blank();
1920 doc.add_page(rpdfium_core::Rect::new(0.0, 0.0, 612.0, 792.0));
1921 doc.page_set_rotation(0, 90).unwrap();
1922 assert_eq!(doc.page_rotation(0).unwrap(), 90);
1923 }
1924
1925 #[test]
1926 fn test_get_page_rotation_out_of_range() {
1927 let doc = EditDocument::new_blank();
1928 assert!(doc.page_rotation(0).is_err());
1929 }
1930
1931 #[test]
1936 fn test_load_standard_font_returns_flags_and_weight() {
1937 let mut doc = EditDocument::new_blank();
1938 let reg = doc.load_standard_font("Helvetica").unwrap();
1939 assert_eq!(reg.flags(), Some(32));
1940 assert_eq!(reg.weight(), Some(400));
1941 }
1942
1943 #[test]
1944 fn test_load_font_from_data_returns_flags_and_weight() {
1945 let mut doc = EditDocument::new_blank();
1946 let fake_data = b"FAKE_TRUETYPE_DATA";
1947 let reg = doc
1948 .load_font_from_data(fake_data, crate::font_reg::FontType::TrueType, false)
1949 .unwrap();
1950 assert_eq!(reg.flags(), Some(4));
1952 assert_eq!(reg.weight(), Some(400));
1954 }
1955
1956 #[test]
1961 fn test_add_attachment_creates_embedded_file() {
1962 let mut doc = EditDocument::new_blank();
1963 let idx = doc.add_attachment("test.pdf").unwrap();
1964 assert_eq!(idx, 0);
1965
1966 let name = doc.attachment_string(0, "UF").unwrap();
1968 assert_eq!(name.as_deref(), Some("test.pdf"));
1969 let name_f = doc.attachment_string(0, "F").unwrap();
1970 assert_eq!(name_f.as_deref(), Some("test.pdf"));
1971 }
1972
1973 #[test]
1974 fn test_add_multiple_attachments() {
1975 let mut doc = EditDocument::new_blank();
1976 let idx0 = doc.add_attachment("a.pdf").unwrap();
1977 let idx1 = doc.add_attachment("b.pdf").unwrap();
1978 assert_eq!(idx0, 0);
1979 assert_eq!(idx1, 1);
1980
1981 assert_eq!(
1982 doc.attachment_string(0, "UF").unwrap().as_deref(),
1983 Some("a.pdf")
1984 );
1985 assert_eq!(
1986 doc.attachment_string(1, "UF").unwrap().as_deref(),
1987 Some("b.pdf")
1988 );
1989 }
1990
1991 #[test]
1992 fn test_delete_attachment_removes_entry() {
1993 let mut doc = EditDocument::new_blank();
1994 doc.add_attachment("a.pdf").unwrap();
1995 doc.add_attachment("b.pdf").unwrap();
1996
1997 doc.delete_attachment(0).unwrap();
1998
1999 assert_eq!(
2001 doc.attachment_string(0, "UF").unwrap().as_deref(),
2002 Some("b.pdf")
2003 );
2004 assert!(doc.attachment_string(1, "UF").is_err());
2006 }
2007
2008 #[test]
2009 fn test_delete_attachment_out_of_range_returns_error() {
2010 let mut doc = EditDocument::new_blank();
2011 doc.add_attachment("a.pdf").unwrap();
2012 assert!(doc.delete_attachment(5).is_err());
2013 }
2014
2015 #[test]
2016 fn test_attachment_has_key_returns_correct_result() {
2017 let mut doc = EditDocument::new_blank();
2018 doc.add_attachment("test.pdf").unwrap();
2019
2020 assert!(doc.attachment_has_key(0, "UF").unwrap());
2021 assert!(doc.attachment_has_key(0, "F").unwrap());
2022 assert!(doc.attachment_has_key(0, "EF").unwrap());
2023 assert!(!doc.attachment_has_key(0, "NonExistent").unwrap());
2024 }
2025
2026 #[test]
2027 fn test_attachment_value_type_returns_correct_type() {
2028 let mut doc = EditDocument::new_blank();
2029 doc.add_attachment("test.pdf").unwrap();
2030
2031 assert_eq!(doc.attachment_value_type(0, "UF").unwrap(), Some("string"));
2032 assert_eq!(doc.attachment_value_type(0, "Type").unwrap(), Some("name"));
2033 assert_eq!(
2034 doc.attachment_value_type(0, "EF").unwrap(),
2035 Some("dictionary")
2036 );
2037 assert_eq!(doc.attachment_value_type(0, "NoSuchKey").unwrap(), None);
2038 }
2039
2040 #[test]
2041 fn test_set_attachment_string_writes_value() {
2042 let mut doc = EditDocument::new_blank();
2043 doc.add_attachment("test.pdf").unwrap();
2044
2045 doc.set_attachment_string(0, "Desc", "A test file").unwrap();
2046 assert_eq!(
2047 doc.attachment_string(0, "Desc").unwrap().as_deref(),
2048 Some("A test file")
2049 );
2050 }
2051
2052 #[test]
2053 fn test_set_attachment_file_writes_data() {
2054 let mut doc = EditDocument::new_blank();
2055 doc.add_attachment("test.txt").unwrap();
2056
2057 doc.set_attachment_file(0, b"Hello, World!").unwrap();
2058
2059 let fs_dict = doc.resolve_attachment_dict(0).unwrap();
2062 let ef_dict = fs_dict.get(&Name::ef()).unwrap().as_dict().unwrap();
2063 let stream_id = ef_dict.get(&Name::f()).unwrap().as_reference().unwrap();
2064 let stream_obj = doc.resolve(stream_id).unwrap();
2065 if let Object::Stream {
2066 data: rpdfium_parser::object::StreamData::Decoded { data },
2067 ..
2068 } = stream_obj
2069 {
2070 assert_eq!(data, b"Hello, World!");
2071 } else {
2072 panic!("expected decoded stream");
2073 }
2074 }
2075
2076 #[test]
2077 fn test_attachment_index_out_of_range_returns_error() {
2078 let doc = EditDocument::new_blank();
2079 assert!(doc.attachment_string(0, "UF").is_err());
2081 assert!(doc.attachment_has_key(0, "UF").is_err());
2082 }
2083
2084 #[test]
2092 fn test_set_string_value() {
2093 use rpdfium_core::PdfString;
2094 let mut doc = EditDocument::new_blank();
2095 let id = doc.add_object(Object::String(PdfString::from_bytes(b"hello".to_vec())));
2096 doc.set_object(id, Object::String(PdfString::from_bytes(b"world".to_vec())));
2097 let obj = doc.resolve(id).unwrap();
2098 assert_eq!(
2099 obj.as_string().map(|s| s.as_bytes()),
2100 Some(b"world".as_slice())
2101 );
2102 }
2103
2104 #[test]
2108 fn test_object_clone() {
2109 use rpdfium_core::PdfString;
2110 let original = Object::Dictionary({
2111 let mut d = HashMap::new();
2112 d.insert(
2113 Name::from_bytes(b"Key".to_vec()),
2114 Object::String(PdfString::from_bytes(b"Value".to_vec())),
2115 );
2116 d.insert(
2117 Name::from_bytes(b"Array".to_vec()),
2118 Object::Array(vec![Object::Integer(1), Object::Integer(2)]),
2119 );
2120 d
2121 });
2122 let cloned = original.clone();
2123 let orig_dict = original.as_dict().unwrap();
2125 let clone_dict = cloned.as_dict().unwrap();
2126 assert_eq!(orig_dict.len(), clone_dict.len());
2127 assert!(clone_dict.contains_key(&Name::from_bytes(b"Key".to_vec())));
2128 assert!(clone_dict.contains_key(&Name::from_bytes(b"Array".to_vec())));
2129 }
2130
2131 #[test]
2135 fn test_array_building() {
2136 use rpdfium_core::PdfString;
2137 let mut doc = EditDocument::new_blank();
2138
2139 let arr = Object::Array(vec![
2141 Object::Integer(1),
2142 Object::Integer(2),
2143 Object::Real(3.14),
2144 ]);
2145 let id = doc.add_object(arr);
2146 let obj = doc.resolve(id).unwrap();
2147 let a = obj.as_array().unwrap();
2148 assert_eq!(a.len(), 3);
2149 assert_eq!(a[0].as_i64(), Some(1));
2150 assert_eq!(a[1].as_i64(), Some(2));
2151 assert!((a[2].as_f64().unwrap() - 3.14).abs() < 0.01);
2152
2153 let arr2 = Object::Array(vec![
2155 Object::String(PdfString::from_bytes(b"test".to_vec())),
2156 Object::Name(Name::from_bytes(b"Name1".to_vec())),
2157 ]);
2158 let id2 = doc.add_object(arr2);
2159 let obj2 = doc.resolve(id2).unwrap();
2160 let a2 = obj2.as_array().unwrap();
2161 assert_eq!(
2162 a2[0].as_string().map(|s| s.as_bytes()),
2163 Some(b"test".as_slice())
2164 );
2165 assert_eq!(
2166 a2[1].as_name().map(|n| n.as_bytes()),
2167 Some(b"Name1".as_slice())
2168 );
2169 }
2170
2171 #[test]
2175 fn test_array_add_reference_and_get() {
2176 let mut doc = EditDocument::new_blank();
2177 let target_id = doc.add_object(Object::Integer(42));
2178 let arr = Object::Array(vec![Object::Reference(target_id)]);
2179 let arr_id = doc.add_object(arr);
2180
2181 let obj = doc.resolve(arr_id).unwrap();
2182 let a = obj.as_array().unwrap();
2183 assert_eq!(a.len(), 1);
2184 match &a[0] {
2185 Object::Reference(ref_id) => {
2186 let resolved = doc.resolve(*ref_id).unwrap();
2187 assert_eq!(resolved.as_i64(), Some(42));
2188 }
2189 _ => panic!("expected reference"),
2190 }
2191 }
2192
2193 #[test]
2197 fn test_stream_set_data() {
2198 let mut doc = EditDocument::new_blank();
2199 let id = doc.add_object(Object::Stream {
2200 dict: {
2201 let mut d = HashMap::new();
2202 d.insert(Name::from_bytes(b"Length".to_vec()), Object::Integer(5));
2203 d
2204 },
2205 data: StreamData::Decoded {
2206 data: b"hello".to_vec(),
2207 },
2208 });
2209
2210 doc.set_object(
2212 id,
2213 Object::Stream {
2214 dict: {
2215 let mut d = HashMap::new();
2216 d.insert(Name::from_bytes(b"Length".to_vec()), Object::Integer(5));
2217 d
2218 },
2219 data: StreamData::Decoded {
2220 data: b"world".to_vec(),
2221 },
2222 },
2223 );
2224
2225 let obj = doc.resolve(id).unwrap();
2226 if let Object::Stream {
2227 data: StreamData::Decoded { data },
2228 ..
2229 } = obj
2230 {
2231 assert_eq!(data, b"world");
2232 } else {
2233 panic!("expected decoded stream");
2234 }
2235 }
2236
2237 #[test]
2241 fn test_dictionary_clone_direct() {
2242 use rpdfium_core::PdfString;
2243 let mut doc = EditDocument::new_blank();
2244 let mut d = HashMap::new();
2245 d.insert(Name::from_bytes(b"Key1".to_vec()), Object::Integer(1));
2246 d.insert(
2247 Name::from_bytes(b"Key2".to_vec()),
2248 Object::String(PdfString::from_bytes(b"val".to_vec())),
2249 );
2250 let id = doc.add_object(Object::Dictionary(d));
2251
2252 let obj = doc.resolve(id).unwrap().clone();
2253 let dict = obj.as_dict().unwrap();
2254 assert_eq!(
2255 dict.get(&Name::from_bytes(b"Key1".to_vec()))
2256 .and_then(|o| o.as_i64()),
2257 Some(1)
2258 );
2259
2260 doc.set_object(id, Object::Integer(99));
2262 let original = doc.resolve(id).unwrap();
2263 assert_eq!(original.as_i64(), Some(99));
2264 assert!(dict.contains_key(&Name::from_bytes(b"Key1".to_vec())));
2266 }
2267
2268 #[test]
2272 fn test_array_mutations() {
2273 let mut doc = EditDocument::new_blank();
2274 let id = doc.add_object(Object::Array(vec![
2275 Object::Integer(10),
2276 Object::Integer(20),
2277 Object::Integer(30),
2278 ]));
2279
2280 let obj = doc.get_mut(id).unwrap();
2282 if let Object::Array(arr) = obj {
2283 arr.remove(1);
2284 }
2285 let obj = doc.resolve(id).unwrap();
2286 let a = obj.as_array().unwrap();
2287 assert_eq!(a.len(), 2);
2288 assert_eq!(a[0].as_i64(), Some(10));
2289 assert_eq!(a[1].as_i64(), Some(30));
2290
2291 let obj = doc.get_mut(id).unwrap();
2293 if let Object::Array(arr) = obj {
2294 arr.insert(1, Object::Integer(25));
2295 }
2296 let obj = doc.resolve(id).unwrap();
2297 let a = obj.as_array().unwrap();
2298 assert_eq!(a.len(), 3);
2299 assert_eq!(a[1].as_i64(), Some(25));
2300
2301 let obj = doc.get_mut(id).unwrap();
2303 if let Object::Array(arr) = obj {
2304 arr.clear();
2305 }
2306 let obj = doc.resolve(id).unwrap();
2307 let a = obj.as_array().unwrap();
2308 assert!(a.is_empty());
2309
2310 let arr_obj = Object::Array(vec![Object::Integer(1), Object::Integer(2)]);
2312 let cloned = arr_obj.clone();
2313 let ca = cloned.as_array().unwrap();
2314 assert_eq!(ca.len(), 2);
2315 assert_eq!(ca[0].as_i64(), Some(1));
2316 assert_eq!(ca[1].as_i64(), Some(2));
2317 }
2318
2319 #[test]
2323 fn test_clone_does_not_stack_overflow() {
2324 let mut doc = EditDocument::new_blank();
2325 let id = doc.allocate_id();
2327 let obj = Object::Array(vec![Object::Reference(id)]);
2328 doc.new_objects.insert(id, obj);
2329
2330 let resolved = doc.resolve(id).unwrap();
2333 let cloned = resolved.clone();
2334 if let Object::Array(arr) = &cloned {
2335 assert_eq!(arr.len(), 1);
2336 assert!(matches!(&arr[0], Object::Reference(r) if *r == id));
2337 } else {
2338 panic!("expected array");
2339 }
2340 }
2341
2342 #[test]
2346 fn test_reference_to_reference() {
2347 let mut doc = EditDocument::new_blank();
2348 let inner_id = doc.add_object(Object::Integer(42));
2349 let outer_id = doc.add_object(Object::Reference(inner_id));
2350
2351 let outer = doc.resolve(outer_id).unwrap();
2353 match outer {
2354 Object::Reference(ref_id) => {
2355 let inner = doc.resolve(*ref_id).unwrap();
2356 assert_eq!(inner.as_i64(), Some(42));
2357 }
2358 _ => panic!("expected reference object"),
2359 }
2360 }
2361
2362 #[test]
2366 fn test_stream_set_data_and_remove_filter() {
2367 let mut doc = EditDocument::new_blank();
2368 let id = doc.add_object(Object::Stream {
2369 dict: {
2370 let mut d = HashMap::new();
2371 d.insert(Name::from_bytes(b"Length".to_vec()), Object::Integer(5));
2372 d.insert(
2373 Name::from_bytes(b"Filter".to_vec()),
2374 Object::Name(Name::from_bytes(b"FlateDecode".to_vec())),
2375 );
2376 d.insert(
2377 Name::from_bytes(b"DecodeParms".to_vec()),
2378 Object::Dictionary(HashMap::new()),
2379 );
2380 d
2381 },
2382 data: StreamData::Decoded {
2383 data: b"hello".to_vec(),
2384 },
2385 });
2386
2387 let obj = doc.get_mut(id).unwrap();
2389 if let Object::Stream { dict, data } = obj {
2390 *data = StreamData::Decoded {
2391 data: b"new data without filter".to_vec(),
2392 };
2393 dict.remove(&Name::from_bytes(b"Filter".to_vec()));
2394 dict.remove(&Name::from_bytes(b"DecodeParms".to_vec()));
2395 dict.insert(Name::from_bytes(b"Length".to_vec()), Object::Integer(23));
2396 }
2397
2398 let obj = doc.resolve(id).unwrap();
2399 if let Object::Stream { dict, data } = obj {
2400 assert!(
2402 !dict.contains_key(&Name::from_bytes(b"Filter".to_vec())),
2403 "Filter should be removed"
2404 );
2405 assert!(
2406 !dict.contains_key(&Name::from_bytes(b"DecodeParms".to_vec())),
2407 "DecodeParms should be removed"
2408 );
2409 assert_eq!(
2411 dict.get(&Name::from_bytes(b"Length".to_vec()))
2412 .and_then(|o| o.as_i64()),
2413 Some(23)
2414 );
2415 if let StreamData::Decoded { data: d } = data {
2416 assert_eq!(d, b"new data without filter");
2417 } else {
2418 panic!("expected decoded stream");
2419 }
2420 } else {
2421 panic!("expected stream");
2422 }
2423 }
2424
2425 #[test]
2431 fn test_stream_length_in_dictionary() {
2432 let mut doc = EditDocument::new_blank();
2433 let id = doc.add_object(Object::Stream {
2435 dict: {
2436 let mut d = HashMap::new();
2437 d.insert(Name::from_bytes(b"Length".to_vec()), Object::Integer(100));
2438 d
2439 },
2440 data: StreamData::Decoded {
2441 data: vec![0u8; 100],
2442 },
2443 });
2444 let obj = doc.resolve(id).unwrap();
2445 if let Object::Stream { dict, .. } = obj {
2446 assert_eq!(
2447 dict.get(&Name::from_bytes(b"Length".to_vec()))
2448 .and_then(|o| o.as_i64()),
2449 Some(100)
2450 );
2451 } else {
2452 panic!("expected stream");
2453 }
2454 }
2455
2456 #[test]
2460 fn test_make_reference() {
2461 let mut doc = EditDocument::new_blank();
2462 let target_id = doc.add_object(Object::Integer(42));
2463 let ref_obj = Object::Reference(target_id);
2465 let ref_id = doc.add_object(ref_obj);
2466 let resolved = doc.resolve(ref_id).unwrap();
2467 match resolved {
2468 Object::Reference(id) => {
2469 assert_eq!(*id, target_id);
2470 let inner = doc.resolve(*id).unwrap();
2471 assert_eq!(inner.as_i64(), Some(42));
2472 }
2473 _ => panic!("expected reference"),
2474 }
2475 }
2476
2477 #[test]
2481 fn test_extract_object_on_remove() {
2482 let mut doc = EditDocument::new_blank();
2483 let id = doc.add_object(Object::Dictionary({
2484 let mut d = HashMap::new();
2485 d.insert(Name::from_bytes(b"child".to_vec()), Object::Integer(42));
2486 d
2487 }));
2488 let obj = doc.get_mut(id).unwrap();
2489 if let Object::Dictionary(dict) = obj {
2490 let removed = dict.remove(&Name::from_bytes(b"child".to_vec()));
2491 assert!(removed.is_some());
2492 assert_eq!(removed.unwrap().as_i64(), Some(42));
2493 let missing = dict.remove(&Name::from_bytes(b"non_exists".to_vec()));
2495 assert!(missing.is_none());
2496 }
2497 }
2498
2499 #[test]
2505 fn test_convert_to_indirect() {
2506 let mut doc = EditDocument::new_blank();
2507 let dict_id = doc.add_object(Object::Dictionary({
2508 let mut d = HashMap::new();
2509 d.insert(Name::from_bytes(b"clams".to_vec()), Object::Integer(42));
2510 d
2511 }));
2512 let value_id = doc.add_object(Object::Integer(42));
2515 let obj = doc.get_mut(dict_id).unwrap();
2516 if let Object::Dictionary(dict) = obj {
2517 dict.insert(
2518 Name::from_bytes(b"clams".to_vec()),
2519 Object::Reference(value_id),
2520 );
2521 }
2522 let obj = doc.resolve(dict_id).unwrap();
2524 let dict = obj.as_dict().unwrap();
2525 let entry = dict.get(&Name::from_bytes(b"clams".to_vec())).unwrap();
2526 assert!(entry.is_reference());
2527 if let Object::Reference(ref_id) = entry {
2529 let resolved = doc.resolve(*ref_id).unwrap();
2530 assert_eq!(resolved.as_i64(), Some(42));
2531 }
2532 }
2533}