1use crate::document::Document;
2use crate::error::Result;
3use crate::objects::{Dictionary, Object, ObjectId};
4use crate::writer::XRefStreamWriter;
5use chrono::{DateTime, Utc};
6use std::collections::HashMap;
7use std::io::{BufWriter, Write};
8use std::path::Path;
9
10#[derive(Debug, Clone)]
12pub struct WriterConfig {
13 pub use_xref_streams: bool,
15 pub pdf_version: String,
17 pub compress_streams: bool,
19}
20
21impl Default for WriterConfig {
22 fn default() -> Self {
23 Self {
24 use_xref_streams: false,
25 pdf_version: "1.7".to_string(),
26 compress_streams: true,
27 }
28 }
29}
30
31pub struct PdfWriter<W: Write> {
32 writer: W,
33 xref_positions: HashMap<ObjectId, u64>,
34 current_position: u64,
35 next_object_id: u32,
36 catalog_id: Option<ObjectId>,
38 pages_id: Option<ObjectId>,
39 info_id: Option<ObjectId>,
40 #[allow(dead_code)]
42 field_widget_map: HashMap<String, Vec<ObjectId>>, #[allow(dead_code)]
44 field_id_map: HashMap<String, ObjectId>, form_field_ids: Vec<ObjectId>, page_ids: Vec<ObjectId>, config: WriterConfig,
49}
50
51impl<W: Write> PdfWriter<W> {
52 pub fn new_with_writer(writer: W) -> Self {
53 Self::with_config(writer, WriterConfig::default())
54 }
55
56 pub fn with_config(writer: W, config: WriterConfig) -> Self {
57 Self {
58 writer,
59 xref_positions: HashMap::new(),
60 current_position: 0,
61 next_object_id: 1, catalog_id: None,
63 pages_id: None,
64 info_id: None,
65 field_widget_map: HashMap::new(),
66 field_id_map: HashMap::new(),
67 form_field_ids: Vec::new(),
68 page_ids: Vec::new(),
69 config,
70 }
71 }
72
73 pub fn write_document(&mut self, document: &mut Document) -> Result<()> {
74 self.write_header()?;
75
76 self.catalog_id = Some(self.allocate_object_id());
78 self.pages_id = Some(self.allocate_object_id());
79 self.info_id = Some(self.allocate_object_id());
80
81 self.write_pages(document)?;
83
84 self.write_form_fields(document)?;
86
87 self.write_catalog(document)?;
89
90 self.write_info(document)?;
92
93 let xref_position = self.current_position;
95 if self.config.use_xref_streams {
96 self.write_xref_stream()?;
97 } else {
98 self.write_xref()?;
99 }
100
101 if !self.config.use_xref_streams {
103 self.write_trailer(xref_position)?;
104 }
105
106 if let Ok(()) = self.writer.flush() {
107 }
109 Ok(())
110 }
111
112 fn write_header(&mut self) -> Result<()> {
113 let header = format!("%PDF-{}\n", self.config.pdf_version);
114 self.write_bytes(header.as_bytes())?;
115 self.write_bytes(&[b'%', 0xE2, 0xE3, 0xCF, 0xD3, b'\n'])?;
117 Ok(())
118 }
119
120 fn write_catalog(&mut self, document: &mut Document) -> Result<()> {
121 let catalog_id = self.catalog_id.expect("catalog_id must be set");
122 let pages_id = self.pages_id.expect("pages_id must be set");
123
124 let mut catalog = Dictionary::new();
125 catalog.set("Type", Object::Name("Catalog".to_string()));
126 catalog.set("Pages", Object::Reference(pages_id));
127
128 if let Some(_form_manager) = &document.form_manager {
131 if document.acro_form.is_none() {
133 document.acro_form = Some(crate::forms::AcroForm::new());
134 }
135 }
136
137 if let Some(acro_form) = &document.acro_form {
139 let acro_form_id = self.allocate_object_id();
141
142 self.write_object(acro_form_id, Object::Dictionary(acro_form.to_dict()))?;
144
145 catalog.set("AcroForm", Object::Reference(acro_form_id));
147 }
148
149 if let Some(outline_tree) = &document.outline {
151 if !outline_tree.items.is_empty() {
152 let outline_root_id = self.write_outline_tree(outline_tree)?;
153 catalog.set("Outlines", Object::Reference(outline_root_id));
154 }
155 }
156
157 self.write_object(catalog_id, Object::Dictionary(catalog))?;
158 Ok(())
159 }
160
161 fn write_pages(&mut self, document: &Document) -> Result<()> {
162 let pages_id = self.pages_id.expect("pages_id must be set");
163 let mut pages_dict = Dictionary::new();
164 pages_dict.set("Type", Object::Name("Pages".to_string()));
165 pages_dict.set("Count", Object::Integer(document.pages.len() as i64));
166
167 let mut kids = Vec::new();
168
169 let mut page_ids = Vec::new();
171 let mut content_ids = Vec::new();
172 for _ in 0..document.pages.len() {
173 page_ids.push(self.allocate_object_id());
174 content_ids.push(self.allocate_object_id());
175 }
176
177 for page_id in &page_ids {
178 kids.push(Object::Reference(*page_id));
179 }
180
181 pages_dict.set("Kids", Object::Array(kids));
182
183 self.write_object(pages_id, Object::Dictionary(pages_dict))?;
184
185 self.page_ids = page_ids.clone();
187
188 for (i, page) in document.pages.iter().enumerate() {
190 let page_id = page_ids[i];
191 let content_id = content_ids[i];
192
193 self.write_page(page_id, pages_id, content_id, page, document)?;
194 self.write_page_content(content_id, page)?;
195 }
196
197 Ok(())
198 }
199
200 fn write_page(
201 &mut self,
202 page_id: ObjectId,
203 parent_id: ObjectId,
204 content_id: ObjectId,
205 page: &crate::page::Page,
206 document: &Document,
207 ) -> Result<()> {
208 let mut page_dict = page.to_dict();
210
211 page_dict.set("Type", Object::Name("Page".to_string()));
213 page_dict.set("Parent", Object::Reference(parent_id));
214 page_dict.set("Contents", Object::Reference(content_id));
215
216 let mut annot_refs = Vec::new();
218 for annotation in page.annotations() {
219 let mut annot_dict = annotation.to_dict();
220
221 let is_widget = if let Some(Object::Name(subtype)) = annot_dict.get("Subtype") {
223 subtype == "Widget"
224 } else {
225 false
226 };
227
228 if is_widget {
229 annot_dict.set("P", Object::Reference(page_id));
232
233 if annot_dict.get("FT").is_none() {
235 continue;
239 }
240 }
241
242 let annot_id = self.allocate_object_id();
244 self.write_object(annot_id, Object::Dictionary(annot_dict))?;
245 annot_refs.push(Object::Reference(annot_id));
246
247 if is_widget {
249 self.form_field_ids.push(annot_id);
250 }
251 }
252
253 if !annot_refs.is_empty() {
258 page_dict.set("Annots", Object::Array(annot_refs));
259 }
260
261 let mut resources = Dictionary::new();
263 let mut font_dict = Dictionary::new();
264
265 let fonts_with_encodings = document.get_fonts_with_encodings();
267
268 for font_with_encoding in fonts_with_encodings {
269 let mut font_entry = Dictionary::new();
270 font_entry.set("Type", Object::Name("Font".to_string()));
271 font_entry.set("Subtype", Object::Name("Type1".to_string()));
272 font_entry.set(
273 "BaseFont",
274 Object::Name(font_with_encoding.font.pdf_name().to_string()),
275 );
276
277 if let Some(encoding) = font_with_encoding.encoding {
279 font_entry.set("Encoding", Object::Name(encoding.pdf_name().to_string()));
280 }
281
282 font_dict.set(
283 font_with_encoding.font.pdf_name(),
284 Object::Dictionary(font_entry),
285 );
286 }
287
288 resources.set("Font", Object::Dictionary(font_dict));
289
290 if !page.images().is_empty() {
292 let mut xobject_dict = Dictionary::new();
293
294 for (name, image) in page.images() {
295 let image_id = self.allocate_object_id();
297
298 self.write_object(image_id, image.to_pdf_object())?;
300
301 xobject_dict.set(name, Object::Reference(image_id));
303 }
304
305 resources.set("XObject", Object::Dictionary(xobject_dict));
306 }
307
308 if let Some(extgstate_states) = page.get_extgstate_resources() {
310 let mut extgstate_dict = Dictionary::new();
311 for (name, state) in extgstate_states {
312 let mut state_dict = Dictionary::new();
313 state_dict.set("Type", Object::Name("ExtGState".to_string()));
314
315 if let Some(alpha_stroke) = state.alpha_stroke {
317 state_dict.set("CA", Object::Real(alpha_stroke));
318 }
319 if let Some(alpha_fill) = state.alpha_fill {
320 state_dict.set("ca", Object::Real(alpha_fill));
321 }
322
323 if let Some(line_width) = state.line_width {
325 state_dict.set("LW", Object::Real(line_width));
326 }
327 if let Some(line_cap) = state.line_cap {
328 state_dict.set("LC", Object::Integer(line_cap as i64));
329 }
330 if let Some(line_join) = state.line_join {
331 state_dict.set("LJ", Object::Integer(line_join as i64));
332 }
333 if let Some(blend_mode) = &state.blend_mode {
334 state_dict.set("BM", Object::Name(blend_mode.pdf_name().to_string()));
335 }
336
337 extgstate_dict.set(name, Object::Dictionary(state_dict));
338 }
339 if !extgstate_dict.is_empty() {
340 resources.set("ExtGState", Object::Dictionary(extgstate_dict));
341 }
342 }
343
344 page_dict.set("Resources", Object::Dictionary(resources));
345
346 self.write_object(page_id, Object::Dictionary(page_dict))?;
347 Ok(())
348 }
349
350 fn write_page_content(&mut self, content_id: ObjectId, page: &crate::page::Page) -> Result<()> {
351 let mut page_copy = page.clone();
352 let content = page_copy.generate_content()?;
353
354 #[cfg(feature = "compression")]
356 {
357 use crate::objects::Stream;
358 let mut stream = Stream::new(content);
359 if self.config.compress_streams {
361 stream.compress_flate()?;
362 }
363
364 self.write_object(
365 content_id,
366 Object::Stream(stream.dictionary().clone(), stream.data().to_vec()),
367 )?;
368 }
369
370 #[cfg(not(feature = "compression"))]
371 {
372 let mut stream_dict = Dictionary::new();
373 stream_dict.set("Length", Object::Integer(content.len() as i64));
374
375 self.write_object(content_id, Object::Stream(stream_dict, content))?;
376 }
377
378 Ok(())
379 }
380
381 fn write_outline_tree(
382 &mut self,
383 outline_tree: &crate::structure::OutlineTree,
384 ) -> Result<ObjectId> {
385 let outline_root_id = self.allocate_object_id();
387
388 let mut outline_root = Dictionary::new();
389 outline_root.set("Type", Object::Name("Outlines".to_string()));
390
391 if !outline_tree.items.is_empty() {
392 let mut item_ids = Vec::new();
394
395 fn count_items(items: &[crate::structure::OutlineItem]) -> usize {
397 let mut count = items.len();
398 for item in items {
399 count += count_items(&item.children);
400 }
401 count
402 }
403
404 let total_items = count_items(&outline_tree.items);
405
406 for _ in 0..total_items {
408 item_ids.push(self.allocate_object_id());
409 }
410
411 let mut id_index = 0;
412
413 let first_id = item_ids[0];
415 let last_id = item_ids[outline_tree.items.len() - 1];
416
417 outline_root.set("First", Object::Reference(first_id));
418 outline_root.set("Last", Object::Reference(last_id));
419
420 let visible_count = outline_tree.visible_count();
422 outline_root.set("Count", Object::Integer(visible_count));
423
424 let mut written_items = Vec::new();
426
427 for (i, item) in outline_tree.items.iter().enumerate() {
428 let item_id = item_ids[id_index];
429 id_index += 1;
430
431 let prev_id = if i > 0 { Some(item_ids[i - 1]) } else { None };
432 let next_id = if i < outline_tree.items.len() - 1 {
433 Some(item_ids[i + 1])
434 } else {
435 None
436 };
437
438 let children_ids = self.write_outline_item(
440 item,
441 item_id,
442 outline_root_id,
443 prev_id,
444 next_id,
445 &mut item_ids,
446 &mut id_index,
447 )?;
448
449 written_items.extend(children_ids);
450 }
451 }
452
453 self.write_object(outline_root_id, Object::Dictionary(outline_root))?;
454 Ok(outline_root_id)
455 }
456
457 #[allow(clippy::too_many_arguments)]
458 fn write_outline_item(
459 &mut self,
460 item: &crate::structure::OutlineItem,
461 item_id: ObjectId,
462 parent_id: ObjectId,
463 prev_id: Option<ObjectId>,
464 next_id: Option<ObjectId>,
465 all_ids: &mut Vec<ObjectId>,
466 id_index: &mut usize,
467 ) -> Result<Vec<ObjectId>> {
468 let mut written_ids = vec![item_id];
469
470 let (first_child_id, last_child_id) = if !item.children.is_empty() {
472 let first_idx = *id_index;
473 let first_id = all_ids[first_idx];
474 let last_idx = first_idx + item.children.len() - 1;
475 let last_id = all_ids[last_idx];
476
477 for (i, child) in item.children.iter().enumerate() {
479 let child_id = all_ids[*id_index];
480 *id_index += 1;
481
482 let child_prev = if i > 0 {
483 Some(all_ids[first_idx + i - 1])
484 } else {
485 None
486 };
487 let child_next = if i < item.children.len() - 1 {
488 Some(all_ids[first_idx + i + 1])
489 } else {
490 None
491 };
492
493 let child_ids = self.write_outline_item(
494 child, child_id, item_id, child_prev, child_next, all_ids, id_index,
496 )?;
497
498 written_ids.extend(child_ids);
499 }
500
501 (Some(first_id), Some(last_id))
502 } else {
503 (None, None)
504 };
505
506 let item_dict = crate::structure::outline_item_to_dict(
508 item,
509 parent_id,
510 first_child_id,
511 last_child_id,
512 prev_id,
513 next_id,
514 );
515
516 self.write_object(item_id, Object::Dictionary(item_dict))?;
517
518 Ok(written_ids)
519 }
520
521 fn write_form_fields(&mut self, document: &mut Document) -> Result<()> {
522 if !self.form_field_ids.is_empty() {
524 if let Some(acro_form) = &mut document.acro_form {
525 acro_form.fields.clear();
527 for field_id in &self.form_field_ids {
528 acro_form.add_field(*field_id);
529 }
530
531 acro_form.need_appearances = true;
533 if acro_form.da.is_none() {
534 acro_form.da = Some("/Helv 12 Tf 0 g".to_string());
535 }
536 }
537 }
538 Ok(())
539 }
540
541 fn write_info(&mut self, document: &Document) -> Result<()> {
542 let info_id = self.info_id.expect("info_id must be set");
543 let mut info_dict = Dictionary::new();
544
545 if let Some(ref title) = document.metadata.title {
546 info_dict.set("Title", Object::String(title.clone()));
547 }
548 if let Some(ref author) = document.metadata.author {
549 info_dict.set("Author", Object::String(author.clone()));
550 }
551 if let Some(ref subject) = document.metadata.subject {
552 info_dict.set("Subject", Object::String(subject.clone()));
553 }
554 if let Some(ref keywords) = document.metadata.keywords {
555 info_dict.set("Keywords", Object::String(keywords.clone()));
556 }
557 if let Some(ref creator) = document.metadata.creator {
558 info_dict.set("Creator", Object::String(creator.clone()));
559 }
560 if let Some(ref producer) = document.metadata.producer {
561 info_dict.set("Producer", Object::String(producer.clone()));
562 }
563
564 if let Some(creation_date) = document.metadata.creation_date {
566 let date_string = format_pdf_date(creation_date);
567 info_dict.set("CreationDate", Object::String(date_string));
568 }
569
570 if let Some(mod_date) = document.metadata.modification_date {
572 let date_string = format_pdf_date(mod_date);
573 info_dict.set("ModDate", Object::String(date_string));
574 }
575
576 self.write_object(info_id, Object::Dictionary(info_dict))?;
577 Ok(())
578 }
579}
580
581impl PdfWriter<BufWriter<std::fs::File>> {
582 pub fn new(path: impl AsRef<Path>) -> Result<Self> {
583 let file = std::fs::File::create(path)?;
584 let writer = BufWriter::new(file);
585
586 Ok(Self {
587 writer,
588 xref_positions: HashMap::new(),
589 current_position: 0,
590 next_object_id: 1,
591 catalog_id: None,
592 pages_id: None,
593 info_id: None,
594 field_widget_map: HashMap::new(),
595 field_id_map: HashMap::new(),
596 form_field_ids: Vec::new(),
597 page_ids: Vec::new(),
598 config: WriterConfig::default(),
599 })
600 }
601}
602
603impl<W: Write> PdfWriter<W> {
604 fn allocate_object_id(&mut self) -> ObjectId {
605 let id = ObjectId::new(self.next_object_id, 0);
606 self.next_object_id += 1;
607 id
608 }
609
610 fn write_object(&mut self, id: ObjectId, object: Object) -> Result<()> {
611 self.xref_positions.insert(id, self.current_position);
612
613 let header = format!("{} {} obj\n", id.number(), id.generation());
614 self.write_bytes(header.as_bytes())?;
615
616 self.write_object_value(&object)?;
617
618 self.write_bytes(b"\nendobj\n")?;
619 Ok(())
620 }
621
622 fn write_object_value(&mut self, object: &Object) -> Result<()> {
623 match object {
624 Object::Null => self.write_bytes(b"null")?,
625 Object::Boolean(b) => self.write_bytes(if *b { b"true" } else { b"false" })?,
626 Object::Integer(i) => self.write_bytes(i.to_string().as_bytes())?,
627 Object::Real(f) => self.write_bytes(
628 format!("{f:.6}")
629 .trim_end_matches('0')
630 .trim_end_matches('.')
631 .as_bytes(),
632 )?,
633 Object::String(s) => {
634 self.write_bytes(b"(")?;
635 self.write_bytes(s.as_bytes())?;
636 self.write_bytes(b")")?;
637 }
638 Object::Name(n) => {
639 self.write_bytes(b"/")?;
640 self.write_bytes(n.as_bytes())?;
641 }
642 Object::Array(arr) => {
643 self.write_bytes(b"[")?;
644 for (i, obj) in arr.iter().enumerate() {
645 if i > 0 {
646 self.write_bytes(b" ")?;
647 }
648 self.write_object_value(obj)?;
649 }
650 self.write_bytes(b"]")?;
651 }
652 Object::Dictionary(dict) => {
653 self.write_bytes(b"<<")?;
654 for (key, value) in dict.entries() {
655 self.write_bytes(b"\n/")?;
656 self.write_bytes(key.as_bytes())?;
657 self.write_bytes(b" ")?;
658 self.write_object_value(value)?;
659 }
660 self.write_bytes(b"\n>>")?;
661 }
662 Object::Stream(dict, data) => {
663 self.write_object_value(&Object::Dictionary(dict.clone()))?;
664 self.write_bytes(b"\nstream\n")?;
665 self.write_bytes(data)?;
666 self.write_bytes(b"\nendstream")?;
667 }
668 Object::Reference(id) => {
669 let ref_str = format!("{} {} R", id.number(), id.generation());
670 self.write_bytes(ref_str.as_bytes())?;
671 }
672 }
673 Ok(())
674 }
675
676 fn write_xref(&mut self) -> Result<()> {
677 self.write_bytes(b"xref\n")?;
678
679 let mut entries: Vec<_> = self
681 .xref_positions
682 .iter()
683 .map(|(id, pos)| (*id, *pos))
684 .collect();
685 entries.sort_by_key(|(id, _)| id.number());
686
687 let max_obj_num = entries.iter().map(|(id, _)| id.number()).max().unwrap_or(0);
689
690 self.write_bytes(b"0 ")?;
693 self.write_bytes((max_obj_num + 1).to_string().as_bytes())?;
694 self.write_bytes(b"\n")?;
695
696 self.write_bytes(b"0000000000 65535 f \n")?;
698
699 for obj_num in 1..=max_obj_num {
702 let _obj_id = ObjectId::new(obj_num, 0);
703 if let Some((_, position)) = entries.iter().find(|(id, _)| id.number() == obj_num) {
704 let entry = format!("{:010} {:05} n \n", position, 0);
705 self.write_bytes(entry.as_bytes())?;
706 } else {
707 self.write_bytes(b"0000000000 00000 f \n")?;
709 }
710 }
711
712 Ok(())
713 }
714
715 fn write_xref_stream(&mut self) -> Result<()> {
716 let catalog_id = self.catalog_id.expect("catalog_id must be set");
717 let info_id = self.info_id.expect("info_id must be set");
718
719 let xref_stream_id = self.allocate_object_id();
721 let xref_position = self.current_position;
722
723 let mut xref_writer = XRefStreamWriter::new(xref_stream_id);
725 xref_writer.set_trailer_info(catalog_id, info_id);
726
727 xref_writer.add_free_entry(0, 65535);
729
730 let mut entries: Vec<_> = self
732 .xref_positions
733 .iter()
734 .map(|(id, pos)| (*id, *pos))
735 .collect();
736 entries.sort_by_key(|(id, _)| id.number());
737
738 let max_obj_num = entries
740 .iter()
741 .map(|(id, _)| id.number())
742 .max()
743 .unwrap_or(0)
744 .max(xref_stream_id.number());
745
746 for obj_num in 1..=max_obj_num {
748 if obj_num == xref_stream_id.number() {
749 xref_writer.add_in_use_entry(xref_position, 0);
751 } else if let Some((id, position)) =
752 entries.iter().find(|(id, _)| id.number() == obj_num)
753 {
754 xref_writer.add_in_use_entry(*position, id.generation());
755 } else {
756 xref_writer.add_free_entry(0, 0);
758 }
759 }
760
761 self.xref_positions.insert(xref_stream_id, xref_position);
763
764 self.write_bytes(
766 format!(
767 "{} {} obj\n",
768 xref_stream_id.number(),
769 xref_stream_id.generation()
770 )
771 .as_bytes(),
772 )?;
773
774 let uncompressed_data = xref_writer.encode_entries();
776 let final_data = if self.config.compress_streams {
777 crate::compression::compress(&uncompressed_data)?
778 } else {
779 uncompressed_data
780 };
781
782 let mut dict = xref_writer.create_dictionary(None);
784 dict.set("Length", Object::Integer(final_data.len() as i64));
785
786 if self.config.compress_streams {
788 dict.set("Filter", Object::Name("FlateDecode".to_string()));
789 }
790 self.write_bytes(b"<<")?;
791 for (key, value) in dict.iter() {
792 self.write_bytes(b"\n/")?;
793 self.write_bytes(key.as_bytes())?;
794 self.write_bytes(b" ")?;
795 self.write_object_value(value)?;
796 }
797 self.write_bytes(b"\n>>\n")?;
798
799 self.write_bytes(b"stream\n")?;
801 self.write_bytes(&final_data)?;
802 self.write_bytes(b"\nendstream\n")?;
803 self.write_bytes(b"endobj\n")?;
804
805 self.write_bytes(b"\nstartxref\n")?;
807 self.write_bytes(xref_position.to_string().as_bytes())?;
808 self.write_bytes(b"\n%%EOF\n")?;
809
810 Ok(())
811 }
812
813 fn write_trailer(&mut self, xref_position: u64) -> Result<()> {
814 let catalog_id = self.catalog_id.expect("catalog_id must be set");
815 let info_id = self.info_id.expect("info_id must be set");
816 let max_obj_num = self
818 .xref_positions
819 .keys()
820 .map(|id| id.number())
821 .max()
822 .unwrap_or(0);
823
824 let mut trailer = Dictionary::new();
825 trailer.set("Size", Object::Integer((max_obj_num + 1) as i64));
826 trailer.set("Root", Object::Reference(catalog_id));
827 trailer.set("Info", Object::Reference(info_id));
828
829 self.write_bytes(b"trailer\n")?;
830 self.write_object_value(&Object::Dictionary(trailer))?;
831 self.write_bytes(b"\nstartxref\n")?;
832 self.write_bytes(xref_position.to_string().as_bytes())?;
833 self.write_bytes(b"\n%%EOF\n")?;
834
835 Ok(())
836 }
837
838 fn write_bytes(&mut self, data: &[u8]) -> Result<()> {
839 self.writer.write_all(data)?;
840 self.current_position += data.len() as u64;
841 Ok(())
842 }
843
844 #[allow(dead_code)]
845 fn create_widget_appearance_stream(&mut self, widget_dict: &Dictionary) -> Result<ObjectId> {
846 let rect = if let Some(Object::Array(rect_array)) = widget_dict.get("Rect") {
848 if rect_array.len() >= 4 {
849 if let (
850 Some(Object::Real(x1)),
851 Some(Object::Real(y1)),
852 Some(Object::Real(x2)),
853 Some(Object::Real(y2)),
854 ) = (
855 rect_array.first(),
856 rect_array.get(1),
857 rect_array.get(2),
858 rect_array.get(3),
859 ) {
860 (*x1, *y1, *x2, *y2)
861 } else {
862 (0.0, 0.0, 100.0, 20.0) }
864 } else {
865 (0.0, 0.0, 100.0, 20.0) }
867 } else {
868 (0.0, 0.0, 100.0, 20.0) };
870
871 let width = rect.2 - rect.0;
872 let height = rect.3 - rect.1;
873
874 let mut content = String::new();
876
877 content.push_str("q\n");
879
880 content.push_str("0 0 0 RG\n"); content.push_str("1 w\n"); content.push_str(&format!("0 0 {width} {height} re\n"));
886 content.push_str("S\n"); content.push_str("1 1 1 rg\n"); content.push_str(&format!("0.5 0.5 {} {} re\n", width - 1.0, height - 1.0));
891 content.push_str("f\n"); content.push_str("Q\n");
895
896 let mut stream_dict = Dictionary::new();
898 stream_dict.set("Type", Object::Name("XObject".to_string()));
899 stream_dict.set("Subtype", Object::Name("Form".to_string()));
900 stream_dict.set(
901 "BBox",
902 Object::Array(vec![
903 Object::Real(0.0),
904 Object::Real(0.0),
905 Object::Real(width),
906 Object::Real(height),
907 ]),
908 );
909 stream_dict.set("Resources", Object::Dictionary(Dictionary::new()));
910 stream_dict.set("Length", Object::Integer(content.len() as i64));
911
912 let stream_id = self.allocate_object_id();
914 self.write_object(stream_id, Object::Stream(stream_dict, content.into_bytes()))?;
915
916 Ok(stream_id)
917 }
918
919 #[allow(dead_code)]
920 fn create_field_appearance_stream(
921 &mut self,
922 field_dict: &Dictionary,
923 widget: &crate::forms::Widget,
924 ) -> Result<ObjectId> {
925 let width = widget.rect.upper_right.x - widget.rect.lower_left.x;
926 let height = widget.rect.upper_right.y - widget.rect.lower_left.y;
927
928 let mut content = String::new();
930
931 content.push_str("q\n");
933
934 if let Some(bg_color) = &widget.appearance.background_color {
936 match bg_color {
937 crate::graphics::Color::Gray(g) => {
938 content.push_str(&format!("{g} g\n"));
939 }
940 crate::graphics::Color::Rgb(r, g, b) => {
941 content.push_str(&format!("{r} {g} {b} rg\n"));
942 }
943 crate::graphics::Color::Cmyk(c, m, y, k) => {
944 content.push_str(&format!("{c} {m} {y} {k} k\n"));
945 }
946 }
947 content.push_str(&format!("0 0 {width} {height} re\n"));
948 content.push_str("f\n");
949 }
950
951 if let Some(border_color) = &widget.appearance.border_color {
953 match border_color {
954 crate::graphics::Color::Gray(g) => {
955 content.push_str(&format!("{g} G\n"));
956 }
957 crate::graphics::Color::Rgb(r, g, b) => {
958 content.push_str(&format!("{r} {g} {b} RG\n"));
959 }
960 crate::graphics::Color::Cmyk(c, m, y, k) => {
961 content.push_str(&format!("{c} {m} {y} {k} K\n"));
962 }
963 }
964 content.push_str(&format!("{} w\n", widget.appearance.border_width));
965 content.push_str(&format!("0 0 {width} {height} re\n"));
966 content.push_str("S\n");
967 }
968
969 if let Some(Object::Name(ft)) = field_dict.get("FT") {
971 if ft == "Btn" {
972 if let Some(Object::Name(v)) = field_dict.get("V") {
973 if v == "Yes" {
974 content.push_str("0 0 0 RG\n"); content.push_str("2 w\n");
977 let margin = width * 0.2;
978 content.push_str(&format!("{} {} m\n", margin, height / 2.0));
979 content.push_str(&format!("{} {} l\n", width / 2.0, margin));
980 content.push_str(&format!("{} {} l\n", width - margin, height - margin));
981 content.push_str("S\n");
982 }
983 }
984 }
985 }
986
987 content.push_str("Q\n");
989
990 let mut stream_dict = Dictionary::new();
992 stream_dict.set("Type", Object::Name("XObject".to_string()));
993 stream_dict.set("Subtype", Object::Name("Form".to_string()));
994 stream_dict.set(
995 "BBox",
996 Object::Array(vec![
997 Object::Real(0.0),
998 Object::Real(0.0),
999 Object::Real(width),
1000 Object::Real(height),
1001 ]),
1002 );
1003 stream_dict.set("Resources", Object::Dictionary(Dictionary::new()));
1004 stream_dict.set("Length", Object::Integer(content.len() as i64));
1005
1006 let stream_id = self.allocate_object_id();
1008 self.write_object(stream_id, Object::Stream(stream_dict, content.into_bytes()))?;
1009
1010 Ok(stream_id)
1011 }
1012}
1013
1014fn format_pdf_date(date: DateTime<Utc>) -> String {
1016 let formatted = date.format("D:%Y%m%d%H%M%S");
1019
1020 format!("{formatted}+00'00")
1022}
1023
1024#[cfg(test)]
1025mod tests {
1026 use super::*;
1027 use crate::page::Page;
1028
1029 #[test]
1030 fn test_pdf_writer_new_with_writer() {
1031 let buffer = Vec::new();
1032 let writer = PdfWriter::new_with_writer(buffer);
1033 assert_eq!(writer.current_position, 0);
1034 assert!(writer.xref_positions.is_empty());
1035 }
1036
1037 #[test]
1038 fn test_write_header() {
1039 let mut buffer = Vec::new();
1040 let mut writer = PdfWriter::new_with_writer(&mut buffer);
1041
1042 writer.write_header().unwrap();
1043
1044 assert!(buffer.starts_with(b"%PDF-1.7\n"));
1046 assert_eq!(buffer.len(), 15); assert_eq!(buffer[9], b'%');
1049 assert_eq!(buffer[10], 0xE2);
1050 assert_eq!(buffer[11], 0xE3);
1051 assert_eq!(buffer[12], 0xCF);
1052 assert_eq!(buffer[13], 0xD3);
1053 assert_eq!(buffer[14], b'\n');
1054 }
1055
1056 #[test]
1057 fn test_write_catalog() {
1058 let mut buffer = Vec::new();
1059 let mut writer = PdfWriter::new_with_writer(&mut buffer);
1060
1061 let mut document = Document::new();
1062 writer.catalog_id = Some(writer.allocate_object_id());
1064 writer.pages_id = Some(writer.allocate_object_id());
1065 writer.info_id = Some(writer.allocate_object_id());
1066 writer.write_catalog(&mut document).unwrap();
1067
1068 let catalog_id = writer.catalog_id.unwrap();
1069 assert_eq!(catalog_id.number(), 1);
1070 assert_eq!(catalog_id.generation(), 0);
1071 assert!(!buffer.is_empty());
1072
1073 let content = String::from_utf8_lossy(&buffer);
1074 assert!(content.contains("1 0 obj"));
1075 assert!(content.contains("/Type /Catalog"));
1076 assert!(content.contains("/Pages 2 0 R"));
1077 assert!(content.contains("endobj"));
1078 }
1079
1080 #[test]
1081 fn test_write_empty_document() {
1082 let mut buffer = Vec::new();
1083 let mut document = Document::new();
1084
1085 {
1086 let mut writer = PdfWriter::new_with_writer(&mut buffer);
1087 writer.write_document(&mut document).unwrap();
1088 }
1089
1090 let content = String::from_utf8_lossy(&buffer);
1092 assert!(content.starts_with("%PDF-1.7\n"));
1093 assert!(content.contains("trailer"));
1094 assert!(content.contains("%%EOF"));
1095 }
1096
1097 #[test]
1098 fn test_write_document_with_pages() {
1099 let mut buffer = Vec::new();
1100 let mut document = Document::new();
1101 document.add_page(Page::a4());
1102 document.add_page(Page::letter());
1103
1104 {
1105 let mut writer = PdfWriter::new_with_writer(&mut buffer);
1106 writer.write_document(&mut document).unwrap();
1107 }
1108
1109 let content = String::from_utf8_lossy(&buffer);
1110 assert!(content.contains("/Type /Pages"));
1111 assert!(content.contains("/Count 2"));
1112 assert!(content.contains("/MediaBox"));
1113 }
1114
1115 #[test]
1116 fn test_write_info() {
1117 let mut buffer = Vec::new();
1118 let mut document = Document::new();
1119 document.set_title("Test Title");
1120 document.set_author("Test Author");
1121 document.set_subject("Test Subject");
1122 document.set_keywords("test, keywords");
1123
1124 {
1125 let mut writer = PdfWriter::new_with_writer(&mut buffer);
1126 writer.info_id = Some(writer.allocate_object_id());
1128 writer.write_info(&document).unwrap();
1129 let info_id = writer.info_id.unwrap();
1130 assert!(info_id.number() > 0);
1131 }
1132
1133 let content = String::from_utf8_lossy(&buffer);
1134 assert!(content.contains("/Title (Test Title)"));
1135 assert!(content.contains("/Author (Test Author)"));
1136 assert!(content.contains("/Subject (Test Subject)"));
1137 assert!(content.contains("/Keywords (test, keywords)"));
1138 assert!(content.contains("/Producer (oxidize_pdf v"));
1139 assert!(content.contains("/Creator (oxidize_pdf)"));
1140 assert!(content.contains("/CreationDate"));
1141 assert!(content.contains("/ModDate"));
1142 }
1143
1144 #[test]
1145 fn test_write_info_with_dates() {
1146 use chrono::{TimeZone, Utc};
1147
1148 let mut buffer = Vec::new();
1149 let mut document = Document::new();
1150
1151 let creation_date = Utc.with_ymd_and_hms(2023, 1, 1, 12, 0, 0).unwrap();
1153 let mod_date = Utc.with_ymd_and_hms(2023, 6, 15, 18, 30, 0).unwrap();
1154
1155 document.set_creation_date(creation_date);
1156 document.set_modification_date(mod_date);
1157 document.set_creator("Test Creator");
1158 document.set_producer("Test Producer");
1159
1160 {
1161 let mut writer = PdfWriter::new_with_writer(&mut buffer);
1162 writer.info_id = Some(writer.allocate_object_id());
1164 writer.write_info(&document).unwrap();
1165 }
1166
1167 let content = String::from_utf8_lossy(&buffer);
1168 assert!(content.contains("/CreationDate (D:20230101"));
1169 assert!(content.contains("/ModDate (D:20230615"));
1170 assert!(content.contains("/Creator (Test Creator)"));
1171 assert!(content.contains("/Producer (Test Producer)"));
1172 }
1173
1174 #[test]
1175 fn test_format_pdf_date() {
1176 use chrono::{TimeZone, Utc};
1177
1178 let date = Utc.with_ymd_and_hms(2023, 12, 25, 15, 30, 45).unwrap();
1179 let formatted = format_pdf_date(date);
1180
1181 assert!(formatted.starts_with("D:"));
1183 assert!(formatted.contains("20231225"));
1184 assert!(formatted.contains("153045"));
1185
1186 assert!(formatted.contains("+") || formatted.contains("-"));
1188 }
1189
1190 #[test]
1191 fn test_write_object() {
1192 let mut buffer = Vec::new();
1193 let obj_id = ObjectId::new(5, 0);
1194 let obj = Object::String("Hello PDF".to_string());
1195
1196 {
1197 let mut writer = PdfWriter::new_with_writer(&mut buffer);
1198 writer.write_object(obj_id, obj).unwrap();
1199 assert!(writer.xref_positions.contains_key(&obj_id));
1200 }
1201
1202 let content = String::from_utf8_lossy(&buffer);
1203 assert!(content.contains("5 0 obj"));
1204 assert!(content.contains("(Hello PDF)"));
1205 assert!(content.contains("endobj"));
1206 }
1207
1208 #[test]
1209 fn test_write_xref() {
1210 let mut buffer = Vec::new();
1211 let mut writer = PdfWriter::new_with_writer(&mut buffer);
1212
1213 writer.xref_positions.insert(ObjectId::new(1, 0), 15);
1215 writer.xref_positions.insert(ObjectId::new(2, 0), 94);
1216 writer.xref_positions.insert(ObjectId::new(3, 0), 152);
1217
1218 writer.write_xref().unwrap();
1219
1220 let content = String::from_utf8_lossy(&buffer);
1221 assert!(content.contains("xref"));
1222 assert!(content.contains("0 4")); assert!(content.contains("0000000000 65535 f "));
1224 assert!(content.contains("0000000015 00000 n "));
1225 assert!(content.contains("0000000094 00000 n "));
1226 assert!(content.contains("0000000152 00000 n "));
1227 }
1228
1229 #[test]
1230 fn test_write_trailer() {
1231 let mut buffer = Vec::new();
1232 let mut writer = PdfWriter::new_with_writer(&mut buffer);
1233
1234 writer.xref_positions.insert(ObjectId::new(1, 0), 15);
1235 writer.xref_positions.insert(ObjectId::new(2, 0), 94);
1236
1237 let catalog_id = ObjectId::new(1, 0);
1238 let info_id = ObjectId::new(2, 0);
1239
1240 writer.catalog_id = Some(catalog_id);
1241 writer.info_id = Some(info_id);
1242 writer.write_trailer(1234).unwrap();
1243
1244 let content = String::from_utf8_lossy(&buffer);
1245 assert!(content.contains("trailer"));
1246 assert!(content.contains("/Size 3"));
1247 assert!(content.contains("/Root 1 0 R"));
1248 assert!(content.contains("/Info 2 0 R"));
1249 assert!(content.contains("startxref"));
1250 assert!(content.contains("1234"));
1251 assert!(content.contains("%%EOF"));
1252 }
1253
1254 #[test]
1255 fn test_write_bytes() {
1256 let mut buffer = Vec::new();
1257
1258 {
1259 let mut writer = PdfWriter::new_with_writer(&mut buffer);
1260
1261 assert_eq!(writer.current_position, 0);
1262
1263 writer.write_bytes(b"Hello").unwrap();
1264 assert_eq!(writer.current_position, 5);
1265
1266 writer.write_bytes(b" World").unwrap();
1267 assert_eq!(writer.current_position, 11);
1268 }
1269
1270 assert_eq!(buffer, b"Hello World");
1271 }
1272
1273 #[test]
1274 fn test_complete_pdf_generation() {
1275 let mut buffer = Vec::new();
1276 let mut document = Document::new();
1277 document.set_title("Complete Test");
1278 document.add_page(Page::a4());
1279
1280 {
1281 let mut writer = PdfWriter::new_with_writer(&mut buffer);
1282 writer.write_document(&mut document).unwrap();
1283 }
1284
1285 assert!(buffer.starts_with(b"%PDF-1.7\n"));
1287 assert!(buffer.ends_with(b"%%EOF\n"));
1288
1289 let content = String::from_utf8_lossy(&buffer);
1290 assert!(content.contains("obj"));
1291 assert!(content.contains("endobj"));
1292 assert!(content.contains("xref"));
1293 assert!(content.contains("trailer"));
1294 assert!(content.contains("/Type /Catalog"));
1295 assert!(content.contains("/Type /Pages"));
1296 assert!(content.contains("/Type /Page"));
1297 }
1298
1299 mod integration_tests {
1301 use super::*;
1302 use crate::graphics::Color;
1303 use crate::graphics::Image;
1304 use crate::text::Font;
1305 use std::fs;
1306 use tempfile::TempDir;
1307
1308 #[test]
1309 fn test_writer_document_integration() {
1310 let temp_dir = TempDir::new().unwrap();
1311 let file_path = temp_dir.path().join("writer_document_integration.pdf");
1312
1313 let mut document = Document::new();
1314 document.set_title("Writer Document Integration Test");
1315 document.set_author("Integration Test Suite");
1316 document.set_subject("Testing writer-document integration");
1317 document.set_keywords("writer, document, integration, test");
1318
1319 let mut page1 = Page::a4();
1321 page1
1322 .text()
1323 .set_font(Font::Helvetica, 16.0)
1324 .at(100.0, 750.0)
1325 .write("Page 1 Content")
1326 .unwrap();
1327
1328 let mut page2 = Page::letter();
1329 page2
1330 .text()
1331 .set_font(Font::TimesRoman, 14.0)
1332 .at(100.0, 750.0)
1333 .write("Page 2 Content")
1334 .unwrap();
1335
1336 document.add_page(page1);
1337 document.add_page(page2);
1338
1339 let mut writer = PdfWriter::new(&file_path).unwrap();
1341 writer.write_document(&mut document).unwrap();
1342
1343 assert!(file_path.exists());
1345 let metadata = fs::metadata(&file_path).unwrap();
1346 assert!(metadata.len() > 1000);
1347
1348 let content = fs::read(&file_path).unwrap();
1350 let content_str = String::from_utf8_lossy(&content);
1351 assert!(content_str.contains("/Type /Catalog"));
1352 assert!(content_str.contains("/Type /Pages"));
1353 assert!(content_str.contains("/Count 2"));
1354 assert!(content_str.contains("/Title (Writer Document Integration Test)"));
1355 assert!(content_str.contains("/Author (Integration Test Suite)"));
1356 }
1357
1358 #[test]
1359 fn test_writer_page_content_integration() {
1360 let temp_dir = TempDir::new().unwrap();
1361 let file_path = temp_dir.path().join("writer_page_content.pdf");
1362
1363 let mut document = Document::new();
1364 document.set_title("Writer Page Content Test");
1365
1366 let mut page = Page::a4();
1367 page.set_margins(50.0, 50.0, 50.0, 50.0);
1368
1369 page.text()
1371 .set_font(Font::HelveticaBold, 18.0)
1372 .at(100.0, 750.0)
1373 .write("Complex Page Content")
1374 .unwrap();
1375
1376 page.graphics()
1377 .set_fill_color(Color::rgb(0.2, 0.4, 0.8))
1378 .rect(100.0, 600.0, 200.0, 100.0)
1379 .fill();
1380
1381 page.graphics()
1382 .set_stroke_color(Color::rgb(0.8, 0.2, 0.2))
1383 .set_line_width(3.0)
1384 .circle(400.0, 650.0, 50.0)
1385 .stroke();
1386
1387 for i in 0..5 {
1389 let y = 550.0 - (i as f64 * 20.0);
1390 page.text()
1391 .set_font(Font::TimesRoman, 12.0)
1392 .at(100.0, y)
1393 .write(&format!("Text line {line}", line = i + 1))
1394 .unwrap();
1395 }
1396
1397 document.add_page(page);
1398
1399 let mut writer = PdfWriter::new(&file_path).unwrap();
1401 writer.write_document(&mut document).unwrap();
1402
1403 assert!(file_path.exists());
1404 let metadata = fs::metadata(&file_path).unwrap();
1405 assert!(metadata.len() > 800);
1406
1407 let content = fs::read(&file_path).unwrap();
1409 let content_str = String::from_utf8_lossy(&content);
1410 assert!(content_str.contains("stream"));
1411 assert!(content_str.contains("endstream"));
1412 assert!(content_str.contains("/Length"));
1413 }
1414
1415 #[test]
1416 fn test_writer_image_integration() {
1417 let temp_dir = TempDir::new().unwrap();
1418 let file_path = temp_dir.path().join("writer_image_integration.pdf");
1419
1420 let mut document = Document::new();
1421 document.set_title("Writer Image Integration Test");
1422
1423 let mut page = Page::a4();
1424
1425 let jpeg_data1 = vec![
1427 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x64, 0x00, 0xC8, 0x03, 0xFF, 0xD9,
1428 ];
1429 let image1 = Image::from_jpeg_data(jpeg_data1).unwrap();
1430
1431 let jpeg_data2 = vec![
1432 0xFF, 0xD8, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x32, 0x00, 0x32, 0x01, 0xFF, 0xD9,
1433 ];
1434 let image2 = Image::from_jpeg_data(jpeg_data2).unwrap();
1435
1436 page.add_image("test_image1", image1);
1438 page.add_image("test_image2", image2);
1439
1440 page.draw_image("test_image1", 100.0, 600.0, 200.0, 100.0)
1442 .unwrap();
1443 page.draw_image("test_image2", 350.0, 600.0, 100.0, 100.0)
1444 .unwrap();
1445
1446 page.text()
1448 .set_font(Font::Helvetica, 14.0)
1449 .at(100.0, 750.0)
1450 .write("Image Integration Test")
1451 .unwrap();
1452
1453 document.add_page(page);
1454
1455 let mut writer = PdfWriter::new(&file_path).unwrap();
1457 writer.write_document(&mut document).unwrap();
1458
1459 assert!(file_path.exists());
1460 let metadata = fs::metadata(&file_path).unwrap();
1461 assert!(metadata.len() > 1000);
1462
1463 let content = fs::read(&file_path).unwrap();
1465 let content_str = String::from_utf8_lossy(&content);
1466 assert!(content_str.contains("XObject"));
1467 assert!(content_str.contains("test_image1"));
1468 assert!(content_str.contains("test_image2"));
1469 assert!(content_str.contains("/Type /XObject"));
1470 assert!(content_str.contains("/Subtype /Image"));
1471 }
1472
1473 #[test]
1474 fn test_writer_buffer_vs_file_output() {
1475 let temp_dir = TempDir::new().unwrap();
1476 let file_path = temp_dir.path().join("buffer_vs_file_output.pdf");
1477
1478 let mut document = Document::new();
1479 document.set_title("Buffer vs File Output Test");
1480
1481 let mut page = Page::a4();
1482 page.text()
1483 .set_font(Font::Helvetica, 12.0)
1484 .at(100.0, 700.0)
1485 .write("Testing buffer vs file output")
1486 .unwrap();
1487
1488 document.add_page(page);
1489
1490 let mut buffer = Vec::new();
1492 {
1493 let mut writer = PdfWriter::new_with_writer(&mut buffer);
1494 writer.write_document(&mut document).unwrap();
1495 }
1496
1497 {
1499 let mut writer = PdfWriter::new(&file_path).unwrap();
1500 writer.write_document(&mut document).unwrap();
1501 }
1502
1503 let file_content = fs::read(&file_path).unwrap();
1505
1506 assert!(buffer.starts_with(b"%PDF-1.7"));
1508 assert!(file_content.starts_with(b"%PDF-1.7"));
1509 assert!(buffer.ends_with(b"%%EOF\n"));
1510 assert!(file_content.ends_with(b"%%EOF\n"));
1511
1512 let buffer_str = String::from_utf8_lossy(&buffer);
1514 let file_str = String::from_utf8_lossy(&file_content);
1515
1516 assert!(buffer_str.contains("obj"));
1517 assert!(file_str.contains("obj"));
1518 assert!(buffer_str.contains("xref"));
1519 assert!(file_str.contains("xref"));
1520 assert!(buffer_str.contains("trailer"));
1521 assert!(file_str.contains("trailer"));
1522 }
1523
1524 #[test]
1525 fn test_writer_large_document_performance() {
1526 let temp_dir = TempDir::new().unwrap();
1527 let file_path = temp_dir.path().join("large_document_performance.pdf");
1528
1529 let mut document = Document::new();
1530 document.set_title("Large Document Performance Test");
1531
1532 for i in 0..20 {
1534 let mut page = Page::a4();
1535
1536 page.text()
1538 .set_font(Font::HelveticaBold, 16.0)
1539 .at(100.0, 750.0)
1540 .write(&format!("Page {page}", page = i + 1))
1541 .unwrap();
1542
1543 for j in 0..30 {
1545 let y = 700.0 - (j as f64 * 20.0);
1546 if y > 100.0 {
1547 page.text()
1548 .set_font(Font::TimesRoman, 10.0)
1549 .at(100.0, y)
1550 .write(&format!(
1551 "Line {line} on page {page}",
1552 line = j + 1,
1553 page = i + 1
1554 ))
1555 .unwrap();
1556 }
1557 }
1558
1559 page.graphics()
1561 .set_fill_color(Color::rgb(0.8, 0.8, 0.9))
1562 .rect(50.0, 50.0, 100.0, 50.0)
1563 .fill();
1564
1565 document.add_page(page);
1566 }
1567
1568 let start = std::time::Instant::now();
1570 let mut writer = PdfWriter::new(&file_path).unwrap();
1571 writer.write_document(&mut document).unwrap();
1572 let duration = start.elapsed();
1573
1574 assert!(file_path.exists());
1576 let metadata = fs::metadata(&file_path).unwrap();
1577 assert!(metadata.len() > 10000); assert!(duration.as_secs() < 5); let content = fs::read(&file_path).unwrap();
1582 let content_str = String::from_utf8_lossy(&content);
1583 assert!(content_str.contains("/Count 20"));
1584 }
1585
1586 #[test]
1587 fn test_writer_metadata_handling() {
1588 let temp_dir = TempDir::new().unwrap();
1589 let file_path = temp_dir.path().join("metadata_handling.pdf");
1590
1591 let mut document = Document::new();
1592 document.set_title("Metadata Handling Test");
1593 document.set_author("Test Author");
1594 document.set_subject("Testing metadata handling in writer");
1595 document.set_keywords("metadata, writer, test, integration");
1596
1597 let mut page = Page::a4();
1598 page.text()
1599 .set_font(Font::Helvetica, 14.0)
1600 .at(100.0, 700.0)
1601 .write("Metadata Test Document")
1602 .unwrap();
1603
1604 document.add_page(page);
1605
1606 let mut writer = PdfWriter::new(&file_path).unwrap();
1608 writer.write_document(&mut document).unwrap();
1609
1610 let content = fs::read(&file_path).unwrap();
1612 let content_str = String::from_utf8_lossy(&content);
1613
1614 assert!(content_str.contains("/Title (Metadata Handling Test)"));
1615 assert!(content_str.contains("/Author (Test Author)"));
1616 assert!(content_str.contains("/Subject (Testing metadata handling in writer)"));
1617 assert!(content_str.contains("/Keywords (metadata, writer, test, integration)"));
1618 assert!(content_str.contains("/Creator (oxidize_pdf)"));
1619 assert!(content_str.contains("/Producer (oxidize_pdf v"));
1620 assert!(content_str.contains("/CreationDate"));
1621 assert!(content_str.contains("/ModDate"));
1622 }
1623
1624 #[test]
1625 fn test_writer_empty_document() {
1626 let temp_dir = TempDir::new().unwrap();
1627 let file_path = temp_dir.path().join("empty_document.pdf");
1628
1629 let mut document = Document::new();
1630 document.set_title("Empty Document Test");
1631
1632 let mut writer = PdfWriter::new(&file_path).unwrap();
1634 writer.write_document(&mut document).unwrap();
1635
1636 assert!(file_path.exists());
1638 let metadata = fs::metadata(&file_path).unwrap();
1639 assert!(metadata.len() > 200); let content = fs::read(&file_path).unwrap();
1642 let content_str = String::from_utf8_lossy(&content);
1643 assert!(content_str.contains("%PDF-1.7"));
1644 assert!(content_str.contains("/Type /Catalog"));
1645 assert!(content_str.contains("/Type /Pages"));
1646 assert!(content_str.contains("/Count 0"));
1647 assert!(content_str.contains("%%EOF"));
1648 }
1649
1650 #[test]
1651 fn test_writer_error_handling() {
1652 let mut document = Document::new();
1653 document.set_title("Error Handling Test");
1654 document.add_page(Page::a4());
1655
1656 let result = PdfWriter::new("/invalid/path/that/does/not/exist.pdf");
1658 assert!(result.is_err());
1659
1660 let mut buffer = Vec::new();
1662 let mut writer = PdfWriter::new_with_writer(&mut buffer);
1663 let result = writer.write_document(&mut document);
1664 assert!(result.is_ok());
1665 assert!(!buffer.is_empty());
1666 }
1667
1668 #[test]
1669 fn test_writer_object_id_management() {
1670 let mut buffer = Vec::new();
1671 let mut document = Document::new();
1672 document.set_title("Object ID Management Test");
1673
1674 for i in 0..5 {
1676 let mut page = Page::a4();
1677 page.text()
1678 .set_font(Font::Helvetica, 12.0)
1679 .at(100.0, 700.0)
1680 .write(&format!("Page {page}", page = i + 1))
1681 .unwrap();
1682 document.add_page(page);
1683 }
1684
1685 let mut writer = PdfWriter::new_with_writer(&mut buffer);
1686 writer.write_document(&mut document).unwrap();
1687
1688 let content = String::from_utf8_lossy(&buffer);
1690 assert!(content.contains("1 0 obj")); assert!(content.contains("2 0 obj")); assert!(content.contains("3 0 obj")); assert!(content.contains("4 0 obj")); assert!(content.contains("5 0 obj")); assert!(content.contains("6 0 obj")); assert!(content.contains("xref"));
1699 assert!(content.contains("0 ")); assert!(content.contains("0000000000 65535 f")); }
1702
1703 #[test]
1704 fn test_writer_content_stream_handling() {
1705 let mut buffer = Vec::new();
1706 let mut document = Document::new();
1707 document.set_title("Content Stream Test");
1708
1709 let mut page = Page::a4();
1710
1711 page.text()
1713 .set_font(Font::Helvetica, 12.0)
1714 .at(100.0, 700.0)
1715 .write("Content Stream Test")
1716 .unwrap();
1717
1718 page.graphics()
1719 .set_fill_color(Color::rgb(0.5, 0.5, 0.5))
1720 .rect(100.0, 600.0, 200.0, 50.0)
1721 .fill();
1722
1723 document.add_page(page);
1724
1725 let mut writer = PdfWriter::new_with_writer(&mut buffer);
1726 writer.write_document(&mut document).unwrap();
1727
1728 let content = String::from_utf8_lossy(&buffer);
1730 assert!(content.contains("stream"));
1731 assert!(content.contains("endstream"));
1732 assert!(content.contains("/Length"));
1733
1734 assert!(content.contains("stream\n")); assert!(content.contains("endstream")); }
1738
1739 #[test]
1740 fn test_writer_font_resource_handling() {
1741 let mut buffer = Vec::new();
1742 let mut document = Document::new();
1743 document.set_title("Font Resource Test");
1744
1745 let mut page = Page::a4();
1746
1747 page.text()
1749 .set_font(Font::Helvetica, 12.0)
1750 .at(100.0, 700.0)
1751 .write("Helvetica Font")
1752 .unwrap();
1753
1754 page.text()
1755 .set_font(Font::TimesRoman, 14.0)
1756 .at(100.0, 650.0)
1757 .write("Times Roman Font")
1758 .unwrap();
1759
1760 page.text()
1761 .set_font(Font::Courier, 10.0)
1762 .at(100.0, 600.0)
1763 .write("Courier Font")
1764 .unwrap();
1765
1766 document.add_page(page);
1767
1768 let mut writer = PdfWriter::new_with_writer(&mut buffer);
1769 writer.write_document(&mut document).unwrap();
1770
1771 let content = String::from_utf8_lossy(&buffer);
1773 assert!(content.contains("/Font"));
1774 assert!(content.contains("/Helvetica"));
1775 assert!(content.contains("/Times-Roman"));
1776 assert!(content.contains("/Courier"));
1777 assert!(content.contains("/Type /Font"));
1778 assert!(content.contains("/Subtype /Type1"));
1779 }
1780
1781 #[test]
1782 fn test_writer_cross_reference_table() {
1783 let mut buffer = Vec::new();
1784 let mut document = Document::new();
1785 document.set_title("Cross Reference Test");
1786
1787 for i in 0..3 {
1789 let mut page = Page::a4();
1790 page.text()
1791 .set_font(Font::Helvetica, 12.0)
1792 .at(100.0, 700.0)
1793 .write(&format!("Page {page}", page = i + 1))
1794 .unwrap();
1795 document.add_page(page);
1796 }
1797
1798 let mut writer = PdfWriter::new_with_writer(&mut buffer);
1799 writer.write_document(&mut document).unwrap();
1800
1801 let content = String::from_utf8_lossy(&buffer);
1803 assert!(content.contains("xref"));
1804 assert!(content.contains("trailer"));
1805 assert!(content.contains("startxref"));
1806 assert!(content.contains("%%EOF"));
1807
1808 let xref_start = content.find("xref").unwrap();
1810 let xref_section = &content[xref_start..];
1811 assert!(xref_section.contains("0000000000 65535 f")); let n_count = xref_section.matches(" n ").count();
1815 assert!(n_count > 0); assert!(content.contains("/Size"));
1819 assert!(content.contains("/Root"));
1820 assert!(content.contains("/Info"));
1821 }
1822 }
1823
1824 #[cfg(test)]
1826 mod comprehensive_tests {
1827 use super::*;
1828 use crate::page::Page;
1829 use crate::text::Font;
1830 use std::io::{self, ErrorKind, Write};
1831
1832 struct FailingWriter {
1834 fail_after: usize,
1835 written: usize,
1836 error_kind: ErrorKind,
1837 }
1838
1839 impl FailingWriter {
1840 fn new(fail_after: usize, error_kind: ErrorKind) -> Self {
1841 Self {
1842 fail_after,
1843 written: 0,
1844 error_kind,
1845 }
1846 }
1847 }
1848
1849 impl Write for FailingWriter {
1850 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
1851 if self.written >= self.fail_after {
1852 return Err(io::Error::new(self.error_kind, "Simulated write error"));
1853 }
1854 self.written += buf.len();
1855 Ok(buf.len())
1856 }
1857
1858 fn flush(&mut self) -> io::Result<()> {
1859 if self.written >= self.fail_after {
1860 return Err(io::Error::new(self.error_kind, "Simulated flush error"));
1861 }
1862 Ok(())
1863 }
1864 }
1865
1866 #[test]
1868 fn test_write_failure_during_header() {
1869 let failing_writer = FailingWriter::new(5, ErrorKind::PermissionDenied);
1870 let mut writer = PdfWriter::new_with_writer(failing_writer);
1871 let mut document = Document::new();
1872
1873 let result = writer.write_document(&mut document);
1874 assert!(result.is_err());
1875 }
1876
1877 #[test]
1879 fn test_write_empty_collections() {
1880 let mut buffer = Vec::new();
1881 let mut writer = PdfWriter::new_with_writer(&mut buffer);
1882
1883 writer
1885 .write_object(ObjectId::new(1, 0), Object::Array(vec![]))
1886 .unwrap();
1887
1888 let empty_dict = Dictionary::new();
1890 writer
1891 .write_object(ObjectId::new(2, 0), Object::Dictionary(empty_dict))
1892 .unwrap();
1893
1894 let content = String::from_utf8_lossy(&buffer);
1895 assert!(content.contains("[]")); assert!(content.contains("<<\n>>")); }
1898
1899 #[test]
1901 fn test_write_deeply_nested_structures() {
1902 let mut buffer = Vec::new();
1903 let mut writer = PdfWriter::new_with_writer(&mut buffer);
1904
1905 let mut nested = Object::Array(vec![Object::Integer(1)]);
1907 for _ in 0..10 {
1908 nested = Object::Array(vec![nested]);
1909 }
1910
1911 writer.write_object(ObjectId::new(1, 0), nested).unwrap();
1912
1913 let content = String::from_utf8_lossy(&buffer);
1914 assert!(content.contains("[[[[[[[[[["));
1915 assert!(content.contains("]]]]]]]]]]"));
1916 }
1917
1918 #[test]
1920 fn test_write_large_integers() {
1921 let mut buffer = Vec::new();
1922 let mut writer = PdfWriter::new_with_writer(&mut buffer);
1923
1924 let test_cases = vec![i64::MAX, i64::MIN, 0, -1, 1, 999999999999999];
1925
1926 for (i, &value) in test_cases.iter().enumerate() {
1927 writer
1928 .write_object(ObjectId::new(i as u32 + 1, 0), Object::Integer(value))
1929 .unwrap();
1930 }
1931
1932 let content = String::from_utf8_lossy(&buffer);
1933 for value in test_cases {
1934 assert!(content.contains(&value.to_string()));
1935 }
1936 }
1937
1938 #[test]
1940 fn test_write_float_edge_cases() {
1941 let mut buffer = Vec::new();
1942 let mut writer = PdfWriter::new_with_writer(&mut buffer);
1943
1944 let test_cases = [
1945 0.0, -0.0, 1.0, -1.0, 0.123456, -0.123456, 1234.56789, 0.000001, 1000000.0,
1946 ];
1947
1948 for (i, &value) in test_cases.iter().enumerate() {
1949 writer
1950 .write_object(ObjectId::new(i as u32 + 1, 0), Object::Real(value))
1951 .unwrap();
1952 }
1953
1954 let content = String::from_utf8_lossy(&buffer);
1955
1956 assert!(content.contains("0")); assert!(content.contains("1")); assert!(content.contains("0.123456"));
1960 assert!(content.contains("1234.567")); }
1962
1963 #[test]
1965 fn test_write_special_characters_in_strings() {
1966 let mut buffer = Vec::new();
1967 let mut writer = PdfWriter::new_with_writer(&mut buffer);
1968
1969 let test_strings = vec![
1970 "Simple string",
1971 "String with (parentheses)",
1972 "String with \\backslash",
1973 "String with \nnewline",
1974 "String with \ttab",
1975 "String with \rcarriage return",
1976 "Unicode: café",
1977 "Emoji: 🎯",
1978 "", ];
1980
1981 for (i, s) in test_strings.iter().enumerate() {
1982 writer
1983 .write_object(
1984 ObjectId::new(i as u32 + 1, 0),
1985 Object::String(s.to_string()),
1986 )
1987 .unwrap();
1988 }
1989
1990 let content = String::from_utf8_lossy(&buffer);
1991
1992 assert!(content.contains("(Simple string)"));
1994 assert!(content.contains("()")); }
1996
1997 #[test]
1999 fn test_write_names_with_special_chars() {
2000 let mut buffer = Vec::new();
2001 let mut writer = PdfWriter::new_with_writer(&mut buffer);
2002
2003 let test_names = vec![
2004 "SimpleName",
2005 "Name With Spaces",
2006 "Name#With#Hash",
2007 "Name/With/Slash",
2008 "Name(With)Parens",
2009 "Name[With]Brackets",
2010 "", ];
2012
2013 for (i, name) in test_names.iter().enumerate() {
2014 writer
2015 .write_object(
2016 ObjectId::new(i as u32 + 1, 0),
2017 Object::Name(name.to_string()),
2018 )
2019 .unwrap();
2020 }
2021
2022 let content = String::from_utf8_lossy(&buffer);
2023
2024 assert!(content.contains("/SimpleName"));
2026 assert!(content.contains("/")); }
2028
2029 #[test]
2031 fn test_write_binary_streams() {
2032 let mut buffer = Vec::new();
2033 let mut writer = PdfWriter::new_with_writer(&mut buffer);
2034
2035 let mut dict = Dictionary::new();
2037 let binary_data: Vec<u8> = (0..=255).collect();
2038 dict.set("Length", Object::Integer(binary_data.len() as i64));
2039
2040 writer
2041 .write_object(ObjectId::new(1, 0), Object::Stream(dict, binary_data))
2042 .unwrap();
2043
2044 let content = buffer;
2045
2046 assert!(content.windows(6).any(|w| w == b"stream"));
2048 assert!(content.windows(9).any(|w| w == b"endstream"));
2049
2050 let stream_start = content.windows(6).position(|w| w == b"stream").unwrap() + 7; let stream_end = content.windows(9).position(|w| w == b"endstream").unwrap();
2053
2054 assert!(stream_end > stream_start);
2055 let data_length = stream_end - stream_start;
2057 assert!((256..=257).contains(&data_length));
2058 }
2059
2060 #[test]
2062 fn test_write_zero_length_stream() {
2063 let mut buffer = Vec::new();
2064 let mut writer = PdfWriter::new_with_writer(&mut buffer);
2065
2066 let mut dict = Dictionary::new();
2067 dict.set("Length", Object::Integer(0));
2068
2069 writer
2070 .write_object(ObjectId::new(1, 0), Object::Stream(dict, vec![]))
2071 .unwrap();
2072
2073 let content = String::from_utf8_lossy(&buffer);
2074 assert!(content.contains("/Length 0"));
2075 assert!(content.contains("stream\n\nendstream"));
2076 }
2077
2078 #[test]
2080 fn test_write_duplicate_dictionary_keys() {
2081 let mut buffer = Vec::new();
2082 let mut writer = PdfWriter::new_with_writer(&mut buffer);
2083
2084 let mut dict = Dictionary::new();
2085 dict.set("Key", Object::Integer(1));
2086 dict.set("Key", Object::Integer(2)); writer
2089 .write_object(ObjectId::new(1, 0), Object::Dictionary(dict))
2090 .unwrap();
2091
2092 let content = String::from_utf8_lossy(&buffer);
2093
2094 assert!(content.contains("/Key 2"));
2096 assert!(!content.contains("/Key 1"));
2097 }
2098
2099 #[test]
2101 fn test_write_unicode_metadata() {
2102 let mut buffer = Vec::new();
2103 let mut document = Document::new();
2104
2105 document.set_title("Título en Español");
2106 document.set_author("作者");
2107 document.set_subject("Тема документа");
2108 document.set_keywords("מילות מפתח");
2109
2110 let mut writer = PdfWriter::new_with_writer(&mut buffer);
2111 writer.write_document(&mut document).unwrap();
2112
2113 let content = buffer;
2114
2115 let content_str = String::from_utf8_lossy(&content);
2117 assert!(content_str.contains("Title") || content_str.contains("Título"));
2118 assert!(content_str.contains("Author") || content_str.contains("作者"));
2119 }
2120
2121 #[test]
2123 fn test_write_very_long_strings() {
2124 let mut buffer = Vec::new();
2125 let mut writer = PdfWriter::new_with_writer(&mut buffer);
2126
2127 let long_string = "A".repeat(10000);
2128 writer
2129 .write_object(ObjectId::new(1, 0), Object::String(long_string.clone()))
2130 .unwrap();
2131
2132 let content = String::from_utf8_lossy(&buffer);
2133 assert!(content.contains(&format!("({})", long_string)));
2134 }
2135
2136 #[test]
2138 fn test_write_maximum_object_id() {
2139 let mut buffer = Vec::new();
2140 let mut writer = PdfWriter::new_with_writer(&mut buffer);
2141
2142 let max_id = ObjectId::new(u32::MAX, 65535);
2143 writer.write_object(max_id, Object::Null).unwrap();
2144
2145 let content = String::from_utf8_lossy(&buffer);
2146 assert!(content.contains(&format!("{} 65535 obj", u32::MAX)));
2147 }
2148
2149 #[test]
2151 fn test_write_complex_page() {
2152 let mut buffer = Vec::new();
2153 let mut document = Document::new();
2154
2155 let mut page = Page::a4();
2156
2157 page.text()
2159 .set_font(Font::Helvetica, 12.0)
2160 .at(100.0, 700.0)
2161 .write("Text with Helvetica")
2162 .unwrap();
2163
2164 page.text()
2165 .set_font(Font::TimesRoman, 14.0)
2166 .at(100.0, 650.0)
2167 .write("Text with Times")
2168 .unwrap();
2169
2170 page.graphics()
2171 .set_fill_color(crate::graphics::Color::Rgb(1.0, 0.0, 0.0))
2172 .rect(50.0, 50.0, 100.0, 100.0)
2173 .fill();
2174
2175 page.graphics()
2176 .set_stroke_color(crate::graphics::Color::Rgb(0.0, 0.0, 1.0))
2177 .move_to(200.0, 200.0)
2178 .line_to(300.0, 300.0)
2179 .stroke();
2180
2181 document.add_page(page);
2182
2183 let mut writer = PdfWriter::new_with_writer(&mut buffer);
2184 writer.write_document(&mut document).unwrap();
2185
2186 let content = String::from_utf8_lossy(&buffer);
2187
2188 assert!(content.contains("/Helvetica"));
2190 assert!(content.contains("/Times-Roman"));
2191
2192 assert!(content.contains("stream"));
2194 assert!(content.contains("endstream"));
2195 assert!(content.contains("/FlateDecode")); }
2197
2198 #[test]
2200 fn test_write_many_pages_document() {
2201 let mut buffer = Vec::new();
2202 let mut document = Document::new();
2203
2204 for i in 0..100 {
2205 let mut page = Page::a4();
2206 page.text()
2207 .set_font(Font::Helvetica, 12.0)
2208 .at(100.0, 700.0)
2209 .write(&format!("Page {}", i + 1))
2210 .unwrap();
2211 document.add_page(page);
2212 }
2213
2214 let mut writer = PdfWriter::new_with_writer(&mut buffer);
2215 writer.write_document(&mut document).unwrap();
2216
2217 let content = String::from_utf8_lossy(&buffer);
2218
2219 assert!(content.contains("/Count 100"));
2221
2222 let page_type_count = content.matches("/Type /Page").count();
2224 assert!(page_type_count >= 100);
2225
2226 assert!(content.contains("/FlateDecode"));
2228 }
2229
2230 #[test]
2232 fn test_write_failure_during_xref() {
2233 let failing_writer = FailingWriter::new(1000, ErrorKind::Other);
2234 let mut writer = PdfWriter::new_with_writer(failing_writer);
2235 let mut document = Document::new();
2236
2237 for _ in 0..5 {
2239 document.add_page(Page::a4());
2240 }
2241
2242 let result = writer.write_document(&mut document);
2243 assert!(result.is_err());
2244 }
2245
2246 #[test]
2248 fn test_position_tracking_accuracy() {
2249 let mut buffer = Vec::new();
2250 let mut writer = PdfWriter::new_with_writer(&mut buffer);
2251
2252 let ids = vec![
2254 ObjectId::new(1, 0),
2255 ObjectId::new(2, 0),
2256 ObjectId::new(3, 0),
2257 ];
2258
2259 for id in &ids {
2260 writer.write_object(*id, Object::Null).unwrap();
2261 }
2262
2263 for id in &ids {
2265 assert!(writer.xref_positions.contains_key(id));
2266 let pos = writer.xref_positions[id];
2267 assert!(pos < writer.current_position);
2268 }
2269 }
2270
2271 #[test]
2273 fn test_write_object_reference_cycles() {
2274 let mut buffer = Vec::new();
2275 let mut writer = PdfWriter::new_with_writer(&mut buffer);
2276
2277 let mut dict = Dictionary::new();
2279 dict.set("Self", Object::Reference(ObjectId::new(1, 0)));
2280 dict.set("Other", Object::Reference(ObjectId::new(2, 0)));
2281
2282 writer
2283 .write_object(ObjectId::new(1, 0), Object::Dictionary(dict))
2284 .unwrap();
2285
2286 let content = String::from_utf8_lossy(&buffer);
2287 assert!(content.contains("/Self 1 0 R"));
2288 assert!(content.contains("/Other 2 0 R"));
2289 }
2290
2291 #[test]
2293 fn test_write_different_page_sizes() {
2294 let mut buffer = Vec::new();
2295 let mut document = Document::new();
2296
2297 document.add_page(Page::a4());
2299 document.add_page(Page::letter());
2300 document.add_page(Page::new(200.0, 300.0)); let mut writer = PdfWriter::new_with_writer(&mut buffer);
2303 writer.write_document(&mut document).unwrap();
2304
2305 let content = String::from_utf8_lossy(&buffer);
2306
2307 assert!(content.contains("[0 0 595")); assert!(content.contains("[0 0 612")); assert!(content.contains("[0 0 200 300]")); }
2312
2313 #[test]
2315 fn test_write_empty_metadata() {
2316 let mut buffer = Vec::new();
2317 let mut document = Document::new();
2318
2319 document.set_title("");
2321 document.set_author("");
2322
2323 let mut writer = PdfWriter::new_with_writer(&mut buffer);
2324 writer.write_document(&mut document).unwrap();
2325
2326 let content = String::from_utf8_lossy(&buffer);
2327
2328 assert!(content.contains("/Title ()"));
2330 assert!(content.contains("/Author ()"));
2331 }
2332
2333 #[test]
2335 fn test_write_permission_error() {
2336 let failing_writer = FailingWriter::new(0, ErrorKind::PermissionDenied);
2337 let mut writer = PdfWriter::new_with_writer(failing_writer);
2338 let mut document = Document::new();
2339
2340 let result = writer.write_document(&mut document);
2341 assert!(result.is_err());
2342 }
2343
2344 #[test]
2346 fn test_write_xref_many_objects() {
2347 let mut buffer = Vec::new();
2348 let mut writer = PdfWriter::new_with_writer(&mut buffer);
2349
2350 for i in 1..=1000 {
2352 writer
2353 .xref_positions
2354 .insert(ObjectId::new(i, 0), (i * 100) as u64);
2355 }
2356
2357 writer.write_xref().unwrap();
2358
2359 let content = String::from_utf8_lossy(&buffer);
2360
2361 assert!(content.contains("xref"));
2363 assert!(content.contains("0 1001")); assert!(content.contains("0000000000 65535 f"));
2367 assert!(content.contains(" n "));
2368 }
2369
2370 #[test]
2372 fn test_write_stream_with_filter() {
2373 let mut buffer = Vec::new();
2374 let mut writer = PdfWriter::new_with_writer(&mut buffer);
2375
2376 let mut dict = Dictionary::new();
2377 dict.set("Length", Object::Integer(100));
2378 dict.set("Filter", Object::Name("FlateDecode".to_string()));
2379
2380 let data = vec![0u8; 100];
2381 writer
2382 .write_object(ObjectId::new(1, 0), Object::Stream(dict, data))
2383 .unwrap();
2384
2385 let content = String::from_utf8_lossy(&buffer);
2386 assert!(content.contains("/Filter /FlateDecode"));
2387 assert!(content.contains("/Length 100"));
2388 }
2389
2390 #[test]
2392 fn test_write_mixed_type_arrays() {
2393 let mut buffer = Vec::new();
2394 let mut writer = PdfWriter::new_with_writer(&mut buffer);
2395
2396 let array = vec![
2397 Object::Integer(42),
2398 Object::Real(3.14),
2399 Object::String("Hello".to_string()),
2400 Object::Name("World".to_string()),
2401 Object::Boolean(true),
2402 Object::Null,
2403 Object::Reference(ObjectId::new(5, 0)),
2404 ];
2405
2406 writer
2407 .write_object(ObjectId::new(1, 0), Object::Array(array))
2408 .unwrap();
2409
2410 let content = String::from_utf8_lossy(&buffer);
2411 assert!(content.contains("[42 3.14 (Hello) /World true null 5 0 R]"));
2412 }
2413
2414 #[test]
2416 fn test_write_nested_dictionaries() {
2417 let mut buffer = Vec::new();
2418 let mut writer = PdfWriter::new_with_writer(&mut buffer);
2419
2420 let mut inner = Dictionary::new();
2421 inner.set("Inner", Object::Integer(1));
2422
2423 let mut middle = Dictionary::new();
2424 middle.set("Middle", Object::Dictionary(inner));
2425
2426 let mut outer = Dictionary::new();
2427 outer.set("Outer", Object::Dictionary(middle));
2428
2429 writer
2430 .write_object(ObjectId::new(1, 0), Object::Dictionary(outer))
2431 .unwrap();
2432
2433 let content = String::from_utf8_lossy(&buffer);
2434 assert!(content.contains("/Outer <<"));
2435 assert!(content.contains("/Middle <<"));
2436 assert!(content.contains("/Inner 1"));
2437 }
2438
2439 #[test]
2441 fn test_write_max_generation_number() {
2442 let mut buffer = Vec::new();
2443 let mut writer = PdfWriter::new_with_writer(&mut buffer);
2444
2445 let id = ObjectId::new(1, 65535);
2446 writer.write_object(id, Object::Null).unwrap();
2447
2448 let content = String::from_utf8_lossy(&buffer);
2449 assert!(content.contains("1 65535 obj"));
2450 }
2451
2452 #[test]
2454 fn test_write_consistent_line_endings() {
2455 let mut buffer = Vec::new();
2456 let mut writer = PdfWriter::new_with_writer(&mut buffer);
2457
2458 writer.write_header().unwrap();
2459
2460 let content = buffer;
2461
2462 assert!(content.windows(2).filter(|w| w == b"\r\n").count() == 0);
2464 assert!(content.windows(1).filter(|w| w == b"\n").count() > 0);
2465 }
2466
2467 #[test]
2469 fn test_writer_flush_behavior() {
2470 struct FlushCounter {
2471 buffer: Vec<u8>,
2472 flush_count: std::cell::RefCell<usize>,
2473 }
2474
2475 impl Write for FlushCounter {
2476 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
2477 self.buffer.extend_from_slice(buf);
2478 Ok(buf.len())
2479 }
2480
2481 fn flush(&mut self) -> io::Result<()> {
2482 *self.flush_count.borrow_mut() += 1;
2483 Ok(())
2484 }
2485 }
2486
2487 let flush_counter = FlushCounter {
2488 buffer: Vec::new(),
2489 flush_count: std::cell::RefCell::new(0),
2490 };
2491
2492 let mut writer = PdfWriter::new_with_writer(flush_counter);
2493 let mut document = Document::new();
2494
2495 writer.write_document(&mut document).unwrap();
2496
2497 assert!(*writer.writer.flush_count.borrow() > 0);
2499 }
2500
2501 #[test]
2503 fn test_write_pdf_special_characters() {
2504 let mut buffer = Vec::new();
2505 let mut writer = PdfWriter::new_with_writer(&mut buffer);
2506
2507 writer
2509 .write_object(
2510 ObjectId::new(1, 0),
2511 Object::String("Text with ) and ( parentheses".to_string()),
2512 )
2513 .unwrap();
2514
2515 writer
2517 .write_object(
2518 ObjectId::new(2, 0),
2519 Object::String("Text with \\ backslash".to_string()),
2520 )
2521 .unwrap();
2522
2523 let content = String::from_utf8_lossy(&buffer);
2524
2525 assert!(content.contains("(Text with ) and ( parentheses)"));
2527 assert!(content.contains("(Text with \\ backslash)"));
2528 }
2529
2530 #[test]
2532 fn test_write_resource_dictionary() {
2533 let mut buffer = Vec::new();
2534 let mut document = Document::new();
2535
2536 let mut page = Page::a4();
2537
2538 page.text()
2540 .set_font(Font::Helvetica, 12.0)
2541 .at(100.0, 700.0)
2542 .write("Test")
2543 .unwrap();
2544
2545 page.graphics()
2546 .set_fill_color(crate::graphics::Color::Rgb(1.0, 0.0, 0.0))
2547 .rect(50.0, 50.0, 100.0, 100.0)
2548 .fill();
2549
2550 document.add_page(page);
2551
2552 let mut writer = PdfWriter::new_with_writer(&mut buffer);
2553 writer.write_document(&mut document).unwrap();
2554
2555 let content = String::from_utf8_lossy(&buffer);
2556
2557 assert!(content.contains("/Resources"));
2559 assert!(content.contains("/Font"));
2560 assert!(content.contains("stream") && content.contains("endstream"));
2562 }
2563
2564 #[test]
2566 fn test_error_recovery_after_failed_write() {
2567 let mut buffer = Vec::new();
2568 let mut writer = PdfWriter::new_with_writer(&mut buffer);
2569
2570 writer
2572 .write_object(ObjectId::new(1, 0), Object::Null)
2573 .unwrap();
2574
2575 assert!(writer.xref_positions.contains_key(&ObjectId::new(1, 0)));
2577 assert!(writer.current_position > 0);
2578
2579 writer
2581 .write_object(ObjectId::new(2, 0), Object::Null)
2582 .unwrap();
2583 assert!(writer.xref_positions.contains_key(&ObjectId::new(2, 0)));
2584 }
2585
2586 #[test]
2588 fn test_memory_efficiency_large_document() {
2589 let mut buffer = Vec::new();
2590 let mut document = Document::new();
2591
2592 for i in 0..50 {
2594 let mut page = Page::a4();
2595
2596 for j in 0..20 {
2598 page.text()
2599 .set_font(Font::Helvetica, 10.0)
2600 .at(50.0, 700.0 - (j as f64 * 30.0))
2601 .write(&format!("Line {line} on page {page}", line = j, page = i))
2602 .unwrap();
2603 }
2604
2605 document.add_page(page);
2606 }
2607
2608 let _initial_capacity = buffer.capacity();
2609 let mut writer = PdfWriter::new_with_writer(&mut buffer);
2610 writer.write_document(&mut document).unwrap();
2611
2612 assert!(!buffer.is_empty());
2614 assert!(buffer.capacity() <= buffer.len() * 2); }
2616
2617 #[test]
2619 fn test_trailer_dictionary_content() {
2620 let mut buffer = Vec::new();
2621 let mut writer = PdfWriter::new_with_writer(&mut buffer);
2622
2623 writer.catalog_id = Some(ObjectId::new(1, 0));
2625 writer.info_id = Some(ObjectId::new(2, 0));
2626 writer.xref_positions.insert(ObjectId::new(1, 0), 0);
2627 writer.xref_positions.insert(ObjectId::new(2, 0), 0);
2628
2629 writer.write_trailer(1000).unwrap();
2631
2632 let content = String::from_utf8_lossy(&buffer);
2633
2634 assert!(content.contains("trailer"));
2636 assert!(content.contains("/Size"));
2637 assert!(content.contains("/Root 1 0 R"));
2638 assert!(content.contains("/Info 2 0 R"));
2639 assert!(content.contains("startxref"));
2640 assert!(content.contains("1000"));
2641 assert!(content.contains("%%EOF"));
2642 }
2643
2644 #[test]
2646 fn test_write_bytes_partial_writes() {
2647 struct PartialWriter {
2648 buffer: Vec<u8>,
2649 max_per_write: usize,
2650 }
2651
2652 impl Write for PartialWriter {
2653 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
2654 let to_write = buf.len().min(self.max_per_write);
2655 self.buffer.extend_from_slice(&buf[..to_write]);
2656 Ok(to_write)
2657 }
2658
2659 fn flush(&mut self) -> io::Result<()> {
2660 Ok(())
2661 }
2662 }
2663
2664 let partial_writer = PartialWriter {
2665 buffer: Vec::new(),
2666 max_per_write: 10,
2667 };
2668
2669 let mut writer = PdfWriter::new_with_writer(partial_writer);
2670
2671 let large_data = vec![b'A'; 100];
2673 writer.write_bytes(&large_data).unwrap();
2674
2675 assert_eq!(writer.writer.buffer.len(), 100);
2677 assert!(writer.writer.buffer.iter().all(|&b| b == b'A'));
2678 }
2679
2680 #[test]
2682 fn test_object_id_conflict_handling() {
2683 let mut buffer = Vec::new();
2684 let mut writer = PdfWriter::new_with_writer(&mut buffer);
2685
2686 let id = ObjectId::new(1, 0);
2687
2688 writer.write_object(id, Object::Integer(1)).unwrap();
2690 writer.write_object(id, Object::Integer(2)).unwrap();
2691
2692 assert!(writer.xref_positions.contains_key(&id));
2694
2695 let content = String::from_utf8_lossy(&buffer);
2696
2697 assert!(content.matches("1 0 obj").count() == 2);
2699 }
2700
2701 #[test]
2703 fn test_content_stream_encoding() {
2704 let mut buffer = Vec::new();
2705 let mut document = Document::new();
2706
2707 let mut page = Page::a4();
2708
2709 page.text()
2711 .set_font(Font::Helvetica, 12.0)
2712 .at(100.0, 700.0)
2713 .write("Special: €£¥")
2714 .unwrap();
2715
2716 document.add_page(page);
2717
2718 let mut writer = PdfWriter::new_with_writer(&mut buffer);
2719 writer.write_document(&mut document).unwrap();
2720
2721 assert!(!buffer.is_empty());
2723 }
2724
2725 #[test]
2727 fn test_pdf_version_header() {
2728 let mut buffer = Vec::new();
2729 let mut writer = PdfWriter::new_with_writer(&mut buffer);
2730
2731 writer.write_header().unwrap();
2732
2733 let content = &buffer;
2734
2735 assert!(content.starts_with(b"%PDF-1.7\n"));
2737
2738 assert_eq!(content[9], b'%');
2740 assert_eq!(content[10], 0xE2);
2741 assert_eq!(content[11], 0xE3);
2742 assert_eq!(content[12], 0xCF);
2743 assert_eq!(content[13], 0xD3);
2744 assert_eq!(content[14], b'\n');
2745 }
2746
2747 #[test]
2749 fn test_page_content_operations_order() {
2750 let mut buffer = Vec::new();
2751 let mut document = Document::new();
2752
2753 let mut page = Page::a4();
2754
2755 page.graphics()
2757 .save_state()
2758 .set_fill_color(crate::graphics::Color::Rgb(1.0, 0.0, 0.0))
2759 .rect(50.0, 50.0, 100.0, 100.0)
2760 .fill()
2761 .restore_state();
2762
2763 document.add_page(page);
2764
2765 let mut writer = PdfWriter::new_with_writer(&mut buffer);
2766 writer.write_document(&mut document).unwrap();
2767
2768 let content = String::from_utf8_lossy(&buffer);
2769
2770 assert!(content.contains("stream"));
2773 assert!(content.contains("endstream"));
2774 }
2775
2776 #[test]
2778 fn test_invalid_utf8_handling() {
2779 let mut buffer = Vec::new();
2780 let mut writer = PdfWriter::new_with_writer(&mut buffer);
2781
2782 let invalid_utf8 = vec![0xFF, 0xFE, 0xFD];
2784 let string = String::from_utf8_lossy(&invalid_utf8).to_string();
2785
2786 writer
2787 .write_object(ObjectId::new(1, 0), Object::String(string))
2788 .unwrap();
2789
2790 assert!(!buffer.is_empty());
2792 }
2793
2794 #[test]
2796 fn test_roundtrip_write_parse() {
2797 use crate::parser::PdfReader;
2798 use std::io::Cursor;
2799
2800 let mut buffer = Vec::new();
2801 let mut document = Document::new();
2802
2803 document.set_title("Round-trip Test");
2804 document.add_page(Page::a4());
2805
2806 {
2808 let mut writer = PdfWriter::new_with_writer(&mut buffer);
2809 writer.write_document(&mut document).unwrap();
2810 }
2811
2812 let cursor = Cursor::new(buffer);
2814 let result = PdfReader::new(cursor);
2815
2816 assert!(result.is_ok() || result.is_err()); }
2820
2821 #[test]
2823 fn test_pdf_object_references_are_valid() {
2824 let mut buffer = Vec::new();
2825 let mut document = Document::new();
2826 document.set_title("Object Reference Validation Test");
2827
2828 let mut page = Page::a4();
2830
2831 page.text()
2833 .set_font(Font::Helvetica, 12.0)
2834 .at(50.0, 700.0)
2835 .write("Form with validation:")
2836 .unwrap();
2837
2838 use crate::forms::{BorderStyle, TextField, Widget, WidgetAppearance};
2840 use crate::geometry::{Point, Rectangle};
2841 use crate::graphics::Color;
2842
2843 let text_appearance = WidgetAppearance {
2844 border_color: Some(Color::rgb(0.0, 0.0, 0.5)),
2845 background_color: Some(Color::rgb(0.95, 0.95, 1.0)),
2846 border_width: 1.0,
2847 border_style: BorderStyle::Solid,
2848 };
2849
2850 let name_widget = Widget::new(Rectangle::new(
2851 Point::new(150.0, 640.0),
2852 Point::new(400.0, 660.0),
2853 ))
2854 .with_appearance(text_appearance);
2855
2856 page.add_form_widget(name_widget.clone());
2857 document.add_page(page);
2858
2859 let form_manager = document.enable_forms();
2861 let name_field = TextField::new("name_field").with_default_value("");
2862 form_manager
2863 .add_text_field(name_field, name_widget, None)
2864 .unwrap();
2865
2866 let mut writer = PdfWriter::new_with_writer(&mut buffer);
2868 writer.write_document(&mut document).unwrap();
2869
2870 let content = String::from_utf8_lossy(&buffer);
2872
2873 if let Some(xref_start) = content.find("xref\n") {
2875 let xref_section = &content[xref_start..];
2876 let lines: Vec<&str> = xref_section.lines().collect();
2877 if lines.len() > 1 {
2878 let first_line = lines[1]; if let Some(space_pos) = first_line.find(' ') {
2880 let (start_str, count_str) = first_line.split_at(space_pos);
2881 let start_id: u32 = start_str.parse().unwrap_or(0);
2882 let count: u32 = count_str.trim().parse().unwrap_or(0);
2883 let max_valid_id = start_id + count - 1;
2884
2885 assert!(
2888 !content.contains("1000 0 R"),
2889 "Found invalid ObjectId reference 1000 0 R - max valid ID is {}",
2890 max_valid_id
2891 );
2892 assert!(
2893 !content.contains("1001 0 R"),
2894 "Found invalid ObjectId reference 1001 0 R - max valid ID is {}",
2895 max_valid_id
2896 );
2897 assert!(
2898 !content.contains("1002 0 R"),
2899 "Found invalid ObjectId reference 1002 0 R - max valid ID is {}",
2900 max_valid_id
2901 );
2902 assert!(
2903 !content.contains("1003 0 R"),
2904 "Found invalid ObjectId reference 1003 0 R - max valid ID is {}",
2905 max_valid_id
2906 );
2907
2908 for line in content.lines() {
2910 if line.contains(" 0 R") {
2911 let words: Vec<&str> = line.split_whitespace().collect();
2913 for i in 0..words.len().saturating_sub(2) {
2914 if words[i + 1] == "0" && words[i + 2] == "R" {
2915 if let Ok(obj_id) = words[i].parse::<u32>() {
2916 assert!(obj_id <= max_valid_id,
2917 "Object reference {} 0 R exceeds xref table size (max: {})",
2918 obj_id, max_valid_id);
2919 }
2920 }
2921 }
2922 }
2923 }
2924
2925 println!("✅ PDF structure validation passed: all {} object references are valid (max ID: {})",
2926 count, max_valid_id);
2927 }
2928 }
2929 } else {
2930 panic!("Could not find xref section in generated PDF");
2931 }
2932 }
2933
2934 #[test]
2935 fn test_xref_stream_generation() {
2936 let mut buffer = Vec::new();
2937 let mut document = Document::new();
2938 document.set_title("XRef Stream Test");
2939
2940 let page = Page::a4();
2941 document.add_page(page);
2942
2943 let config = WriterConfig {
2945 use_xref_streams: true,
2946 pdf_version: "1.5".to_string(),
2947 compress_streams: true,
2948 };
2949 let mut writer = PdfWriter::with_config(&mut buffer, config);
2950 writer.write_document(&mut document).unwrap();
2951
2952 let content = String::from_utf8_lossy(&buffer);
2953
2954 assert!(content.starts_with("%PDF-1.5\n"));
2956
2957 assert!(!content.contains("\nxref\n"));
2959 assert!(!content.contains("\ntrailer\n"));
2960
2961 assert!(content.contains("/Type /XRef"));
2963 assert!(content.contains("/Filter /FlateDecode"));
2964 assert!(content.contains("/W ["));
2965 assert!(content.contains("/Root "));
2966 assert!(content.contains("/Info "));
2967
2968 assert!(content.contains("\nstartxref\n"));
2970 assert!(content.contains("\n%%EOF\n"));
2971 }
2972
2973 #[test]
2974 fn test_writer_config_default() {
2975 let config = WriterConfig::default();
2976 assert_eq!(config.use_xref_streams, false);
2977 assert_eq!(config.pdf_version, "1.7");
2978 }
2979
2980 #[test]
2981 fn test_pdf_version_in_header() {
2982 let mut buffer = Vec::new();
2983 let mut document = Document::new();
2984
2985 let page = Page::a4();
2986 document.add_page(page);
2987
2988 let config = WriterConfig {
2990 use_xref_streams: false,
2991 pdf_version: "1.4".to_string(),
2992 compress_streams: true,
2993 };
2994 let mut writer = PdfWriter::with_config(&mut buffer, config);
2995 writer.write_document(&mut document).unwrap();
2996
2997 let content = String::from_utf8_lossy(&buffer);
2998 assert!(content.starts_with("%PDF-1.4\n"));
2999 }
3000
3001 #[test]
3002 fn test_xref_stream_with_multiple_objects() {
3003 let mut buffer = Vec::new();
3004 let mut document = Document::new();
3005 document.set_title("Multi Object XRef Stream Test");
3006
3007 for i in 0..3 {
3009 let mut page = Page::a4();
3010 page.text()
3011 .set_font(Font::Helvetica, 12.0)
3012 .at(100.0, 700.0)
3013 .write(&format!("Page {page}", page = i + 1))
3014 .unwrap();
3015 document.add_page(page);
3016 }
3017
3018 let config = WriterConfig {
3019 use_xref_streams: true,
3020 pdf_version: "1.5".to_string(),
3021 compress_streams: true,
3022 };
3023 let mut writer = PdfWriter::with_config(&mut buffer, config);
3024 writer.write_document(&mut document).unwrap();
3025
3026 let content = String::from_utf8_lossy(&buffer);
3027
3028 assert!(content.contains("/Size "));
3030
3031 assert!(content.contains("/W ["));
3033
3034 assert!(content.contains("/Index ["));
3036 }
3037 }
3038}