1use crate::error::Result;
2use crate::objects::{Object, ObjectId};
3use crate::page::Page;
4use crate::writer::PdfWriter;
5use std::collections::HashMap;
6
7pub struct Document {
24 pub(crate) pages: Vec<Page>,
25 #[allow(dead_code)]
26 pub(crate) objects: HashMap<ObjectId, Object>,
27 #[allow(dead_code)]
28 pub(crate) next_object_id: u32,
29 pub(crate) metadata: DocumentMetadata,
30}
31
32#[derive(Debug, Clone)]
34pub struct DocumentMetadata {
35 pub title: Option<String>,
37 pub author: Option<String>,
39 pub subject: Option<String>,
41 pub keywords: Option<String>,
43 pub creator: Option<String>,
45 pub producer: Option<String>,
47}
48
49impl Default for DocumentMetadata {
50 fn default() -> Self {
51 Self {
52 title: None,
53 author: None,
54 subject: None,
55 keywords: None,
56 creator: Some("oxidize_pdf".to_string()),
57 producer: Some("oxidize_pdf".to_string()),
58 }
59 }
60}
61
62impl Document {
63 pub fn new() -> Self {
65 Self {
66 pages: Vec::new(),
67 objects: HashMap::new(),
68 next_object_id: 1,
69 metadata: DocumentMetadata::default(),
70 }
71 }
72
73 pub fn add_page(&mut self, page: Page) {
75 self.pages.push(page);
76 }
77
78 pub fn set_title(&mut self, title: impl Into<String>) {
80 self.metadata.title = Some(title.into());
81 }
82
83 pub fn set_author(&mut self, author: impl Into<String>) {
85 self.metadata.author = Some(author.into());
86 }
87
88 pub fn set_subject(&mut self, subject: impl Into<String>) {
90 self.metadata.subject = Some(subject.into());
91 }
92
93 pub fn set_keywords(&mut self, keywords: impl Into<String>) {
95 self.metadata.keywords = Some(keywords.into());
96 }
97
98 pub fn save(&mut self, path: impl AsRef<std::path::Path>) -> Result<()> {
104 let mut writer = PdfWriter::new(path)?;
105 writer.write_document(self)?;
106 Ok(())
107 }
108
109 pub fn write(&mut self, buffer: &mut Vec<u8>) -> Result<()> {
115 let mut writer = PdfWriter::new_with_writer(buffer);
116 writer.write_document(self)?;
117 Ok(())
118 }
119
120 #[allow(dead_code)]
121 pub(crate) fn allocate_object_id(&mut self) -> ObjectId {
122 let id = ObjectId::new(self.next_object_id, 0);
123 self.next_object_id += 1;
124 id
125 }
126
127 #[allow(dead_code)]
128 pub(crate) fn add_object(&mut self, obj: Object) -> ObjectId {
129 let id = self.allocate_object_id();
130 self.objects.insert(id, obj);
131 id
132 }
133}
134
135impl Default for Document {
136 fn default() -> Self {
137 Self::new()
138 }
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144
145 #[test]
146 fn test_document_new() {
147 let doc = Document::new();
148 assert!(doc.pages.is_empty());
149 assert!(doc.objects.is_empty());
150 assert_eq!(doc.next_object_id, 1);
151 assert!(doc.metadata.title.is_none());
152 assert!(doc.metadata.author.is_none());
153 assert!(doc.metadata.subject.is_none());
154 assert!(doc.metadata.keywords.is_none());
155 assert_eq!(doc.metadata.creator, Some("oxidize_pdf".to_string()));
156 assert_eq!(doc.metadata.producer, Some("oxidize_pdf".to_string()));
157 }
158
159 #[test]
160 fn test_document_default() {
161 let doc = Document::default();
162 assert!(doc.pages.is_empty());
163 assert_eq!(doc.next_object_id, 1);
164 }
165
166 #[test]
167 fn test_add_page() {
168 let mut doc = Document::new();
169 let page1 = Page::a4();
170 let page2 = Page::letter();
171
172 doc.add_page(page1);
173 assert_eq!(doc.pages.len(), 1);
174
175 doc.add_page(page2);
176 assert_eq!(doc.pages.len(), 2);
177 }
178
179 #[test]
180 fn test_set_title() {
181 let mut doc = Document::new();
182 assert!(doc.metadata.title.is_none());
183
184 doc.set_title("Test Document");
185 assert_eq!(doc.metadata.title, Some("Test Document".to_string()));
186
187 doc.set_title(String::from("Another Title"));
188 assert_eq!(doc.metadata.title, Some("Another Title".to_string()));
189 }
190
191 #[test]
192 fn test_set_author() {
193 let mut doc = Document::new();
194 assert!(doc.metadata.author.is_none());
195
196 doc.set_author("John Doe");
197 assert_eq!(doc.metadata.author, Some("John Doe".to_string()));
198 }
199
200 #[test]
201 fn test_set_subject() {
202 let mut doc = Document::new();
203 assert!(doc.metadata.subject.is_none());
204
205 doc.set_subject("Test Subject");
206 assert_eq!(doc.metadata.subject, Some("Test Subject".to_string()));
207 }
208
209 #[test]
210 fn test_set_keywords() {
211 let mut doc = Document::new();
212 assert!(doc.metadata.keywords.is_none());
213
214 doc.set_keywords("test, pdf, rust");
215 assert_eq!(doc.metadata.keywords, Some("test, pdf, rust".to_string()));
216 }
217
218 #[test]
219 fn test_metadata_default() {
220 let metadata = DocumentMetadata::default();
221 assert!(metadata.title.is_none());
222 assert!(metadata.author.is_none());
223 assert!(metadata.subject.is_none());
224 assert!(metadata.keywords.is_none());
225 assert_eq!(metadata.creator, Some("oxidize_pdf".to_string()));
226 assert_eq!(metadata.producer, Some("oxidize_pdf".to_string()));
227 }
228
229 #[test]
230 fn test_allocate_object_id() {
231 let mut doc = Document::new();
232
233 let id1 = doc.allocate_object_id();
234 assert_eq!(id1.number(), 1);
235 assert_eq!(id1.generation(), 0);
236 assert_eq!(doc.next_object_id, 2);
237
238 let id2 = doc.allocate_object_id();
239 assert_eq!(id2.number(), 2);
240 assert_eq!(id2.generation(), 0);
241 assert_eq!(doc.next_object_id, 3);
242 }
243
244 #[test]
245 fn test_add_object() {
246 let mut doc = Document::new();
247 assert!(doc.objects.is_empty());
248
249 let obj = Object::Boolean(true);
250 let id = doc.add_object(obj.clone());
251
252 assert_eq!(id.number(), 1);
253 assert_eq!(doc.objects.len(), 1);
254 assert!(doc.objects.contains_key(&id));
255 }
256
257 #[test]
258 fn test_write_to_buffer() {
259 let mut doc = Document::new();
260 doc.set_title("Buffer Test");
261 doc.add_page(Page::a4());
262
263 let mut buffer = Vec::new();
264 let result = doc.write(&mut buffer);
265
266 assert!(result.is_ok());
267 assert!(!buffer.is_empty());
268 assert!(buffer.starts_with(b"%PDF-1.7"));
269 }
270
271 #[test]
272 fn test_document_with_multiple_pages() {
273 let mut doc = Document::new();
274 doc.set_title("Multi-page Document");
275 doc.set_author("Test Author");
276 doc.set_subject("Testing multiple pages");
277 doc.set_keywords("test, multiple, pages");
278
279 for _ in 0..5 {
280 doc.add_page(Page::a4());
281 }
282
283 assert_eq!(doc.pages.len(), 5);
284 assert_eq!(doc.metadata.title, Some("Multi-page Document".to_string()));
285 assert_eq!(doc.metadata.author, Some("Test Author".to_string()));
286 }
287
288 #[test]
289 fn test_empty_document_write() {
290 let mut doc = Document::new();
291 let mut buffer = Vec::new();
292
293 let result = doc.write(&mut buffer);
295 assert!(result.is_ok());
296 assert!(!buffer.is_empty());
297 assert!(buffer.starts_with(b"%PDF-1.7"));
298 }
299
300 mod integration_tests {
302 use super::*;
303 use crate::text::Font;
304 use crate::graphics::Color;
305 use tempfile::TempDir;
306 use std::fs;
307
308 #[test]
309 fn test_document_writer_roundtrip() {
310 let temp_dir = TempDir::new().unwrap();
311 let file_path = temp_dir.path().join("test.pdf");
312
313 let mut doc = Document::new();
315 doc.set_title("Integration Test");
316 doc.set_author("Test Author");
317 doc.set_subject("Writer Integration");
318 doc.set_keywords("test, writer, integration");
319
320 let mut page = Page::a4();
321 page.text()
322 .set_font(Font::Helvetica, 12.0)
323 .at(100.0, 700.0)
324 .write("Integration Test Content")
325 .unwrap();
326
327 doc.add_page(page);
328
329 let result = doc.save(&file_path);
331 assert!(result.is_ok());
332
333 assert!(file_path.exists());
335 let metadata = fs::metadata(&file_path).unwrap();
336 assert!(metadata.len() > 0);
337
338 let content = fs::read(&file_path).unwrap();
340 assert!(content.starts_with(b"%PDF-1.7"));
341 assert!(content.ends_with(b"%%EOF\n") || content.ends_with(b"%%EOF"));
343 }
344
345 #[test]
346 fn test_document_with_complex_content() {
347 let temp_dir = TempDir::new().unwrap();
348 let file_path = temp_dir.path().join("complex.pdf");
349
350 let mut doc = Document::new();
351 doc.set_title("Complex Content Test");
352
353 let mut page = Page::a4();
355
356 page.text()
358 .set_font(Font::Helvetica, 14.0)
359 .at(50.0, 750.0)
360 .write("Complex Content Test")
361 .unwrap();
362
363 page.graphics()
365 .set_fill_color(Color::rgb(0.8, 0.2, 0.2))
366 .rectangle(50.0, 500.0, 200.0, 100.0)
367 .fill();
368
369 page.graphics()
370 .set_stroke_color(Color::rgb(0.2, 0.2, 0.8))
371 .set_line_width(2.0)
372 .move_to(50.0, 400.0)
373 .line_to(250.0, 400.0)
374 .stroke();
375
376 doc.add_page(page);
377
378 let result = doc.save(&file_path);
380 assert!(result.is_ok());
381 assert!(file_path.exists());
382 }
383
384 #[test]
385 fn test_document_multiple_pages_integration() {
386 let temp_dir = TempDir::new().unwrap();
387 let file_path = temp_dir.path().join("multipage.pdf");
388
389 let mut doc = Document::new();
390 doc.set_title("Multi-page Integration Test");
391
392 for i in 1..=5 {
394 let mut page = Page::a4();
395
396 page.text()
397 .set_font(Font::Helvetica, 16.0)
398 .at(50.0, 750.0)
399 .write(&format!("Page {}", i))
400 .unwrap();
401
402 page.text()
403 .set_font(Font::Helvetica, 12.0)
404 .at(50.0, 700.0)
405 .write(&format!("This is the content for page {}", i))
406 .unwrap();
407
408 let color = match i % 3 {
410 0 => Color::rgb(1.0, 0.0, 0.0),
411 1 => Color::rgb(0.0, 1.0, 0.0),
412 _ => Color::rgb(0.0, 0.0, 1.0),
413 };
414
415 page.graphics()
416 .set_fill_color(color)
417 .rectangle(50.0, 600.0, 100.0, 50.0)
418 .fill();
419
420 doc.add_page(page);
421 }
422
423 let result = doc.save(&file_path);
425 assert!(result.is_ok());
426 assert!(file_path.exists());
427
428 let metadata = fs::metadata(&file_path).unwrap();
430 assert!(metadata.len() > 1000); }
432
433 #[test]
434 fn test_document_metadata_persistence() {
435 let temp_dir = TempDir::new().unwrap();
436 let file_path = temp_dir.path().join("metadata.pdf");
437
438 let mut doc = Document::new();
439 doc.set_title("Metadata Persistence Test");
440 doc.set_author("Test Author");
441 doc.set_subject("Testing metadata preservation");
442 doc.set_keywords("metadata, persistence, test");
443
444 doc.add_page(Page::a4());
445
446 let result = doc.save(&file_path);
448 assert!(result.is_ok());
449
450 let content = fs::read(&file_path).unwrap();
452 let content_str = String::from_utf8_lossy(&content);
453
454 assert!(content_str.contains("Metadata Persistence Test"));
456 assert!(content_str.contains("Test Author"));
457 }
458
459 #[test]
460 fn test_document_writer_error_handling() {
461 let mut doc = Document::new();
462 doc.add_page(Page::a4());
463
464 let result = doc.save("/invalid/path/test.pdf");
466 assert!(result.is_err());
467 }
468
469 #[test]
470 fn test_document_object_management() {
471 let mut doc = Document::new();
472
473 let obj1 = Object::Boolean(true);
475 let obj2 = Object::Integer(42);
476 let obj3 = Object::Real(3.14);
477
478 let id1 = doc.add_object(obj1.clone());
479 let id2 = doc.add_object(obj2.clone());
480 let id3 = doc.add_object(obj3.clone());
481
482 assert_eq!(id1.number(), 1);
483 assert_eq!(id2.number(), 2);
484 assert_eq!(id3.number(), 3);
485
486 assert_eq!(doc.objects.len(), 3);
487 assert!(doc.objects.contains_key(&id1));
488 assert!(doc.objects.contains_key(&id2));
489 assert!(doc.objects.contains_key(&id3));
490
491 assert_eq!(doc.objects.get(&id1), Some(&obj1));
493 assert_eq!(doc.objects.get(&id2), Some(&obj2));
494 assert_eq!(doc.objects.get(&id3), Some(&obj3));
495 }
496
497 #[test]
498 fn test_document_page_integration() {
499 let mut doc = Document::new();
500
501 let page1 = Page::a4();
503 let page2 = Page::letter();
504 let mut page3 = Page::new(500.0, 400.0);
505
506 page3.text()
508 .set_font(Font::Helvetica, 10.0)
509 .at(25.0, 350.0)
510 .write("Custom size page")
511 .unwrap();
512
513 doc.add_page(page1);
514 doc.add_page(page2);
515 doc.add_page(page3);
516
517 assert_eq!(doc.pages.len(), 3);
518
519 assert!(doc.pages[0].width() > 500.0); assert!(doc.pages[0].height() > 700.0); assert!(doc.pages[1].width() > 500.0); assert!(doc.pages[1].height() > 700.0); assert_eq!(doc.pages[2].width(), 500.0); assert_eq!(doc.pages[2].height(), 400.0); }
527
528 #[test]
529 fn test_document_content_generation() {
530 let temp_dir = TempDir::new().unwrap();
531 let file_path = temp_dir.path().join("content.pdf");
532
533 let mut doc = Document::new();
534 doc.set_title("Content Generation Test");
535
536 let mut page = Page::a4();
537
538 for i in 0..10 {
540 let y_pos = 700.0 - (i as f64 * 30.0);
541 page.text()
542 .set_font(Font::Helvetica, 12.0)
543 .at(50.0, y_pos)
544 .write(&format!("Generated line {}", i + 1))
545 .unwrap();
546 }
547
548 doc.add_page(page);
549
550 let result = doc.save(&file_path);
552 assert!(result.is_ok());
553 assert!(file_path.exists());
554
555 let metadata = fs::metadata(&file_path).unwrap();
557 assert!(metadata.len() > 500); }
559
560 #[test]
561 fn test_document_buffer_vs_file_write() {
562 let temp_dir = TempDir::new().unwrap();
563 let file_path = temp_dir.path().join("buffer_vs_file.pdf");
564
565 let mut doc = Document::new();
566 doc.set_title("Buffer vs File Test");
567 doc.add_page(Page::a4());
568
569 let mut buffer = Vec::new();
571 let buffer_result = doc.write(&mut buffer);
572 assert!(buffer_result.is_ok());
573
574 let file_result = doc.save(&file_path);
576 assert!(file_result.is_ok());
577
578 let file_content = fs::read(&file_path).unwrap();
580
581 assert!(buffer.starts_with(b"%PDF-1.7"));
583 assert!(file_content.starts_with(b"%PDF-1.7"));
584 assert!(buffer.ends_with(b"%%EOF\n"));
585 assert!(file_content.ends_with(b"%%EOF\n"));
586
587 let buffer_str = String::from_utf8_lossy(&buffer);
589 let file_str = String::from_utf8_lossy(&file_content);
590 assert!(buffer_str.contains("Buffer vs File Test"));
591 assert!(file_str.contains("Buffer vs File Test"));
592 }
593
594 #[test]
595 fn test_document_large_content_handling() {
596 let temp_dir = TempDir::new().unwrap();
597 let file_path = temp_dir.path().join("large_content.pdf");
598
599 let mut doc = Document::new();
600 doc.set_title("Large Content Test");
601
602 let mut page = Page::a4();
603
604 let large_text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. ".repeat(200);
606 page.text()
607 .set_font(Font::Helvetica, 10.0)
608 .at(50.0, 750.0)
609 .write(&large_text)
610 .unwrap();
611
612 doc.add_page(page);
613
614 let result = doc.save(&file_path);
616 assert!(result.is_ok());
617 assert!(file_path.exists());
618
619 let metadata = fs::metadata(&file_path).unwrap();
621 assert!(metadata.len() > 2000); }
623
624 #[test]
625 fn test_document_incremental_building() {
626 let temp_dir = TempDir::new().unwrap();
627 let file_path = temp_dir.path().join("incremental.pdf");
628
629 let mut doc = Document::new();
630
631 doc.set_title("Incremental Building Test");
633
634 let mut page1 = Page::a4();
636 page1.text()
637 .set_font(Font::Helvetica, 12.0)
638 .at(50.0, 750.0)
639 .write("First page content")
640 .unwrap();
641 doc.add_page(page1);
642
643 doc.set_author("Incremental Author");
645 doc.set_subject("Incremental Subject");
646
647 let mut page2 = Page::a4();
649 page2.text()
650 .set_font(Font::Helvetica, 12.0)
651 .at(50.0, 750.0)
652 .write("Second page content")
653 .unwrap();
654 doc.add_page(page2);
655
656 doc.set_keywords("incremental, building, test");
658
659 let result = doc.save(&file_path);
661 assert!(result.is_ok());
662 assert!(file_path.exists());
663
664 assert_eq!(doc.pages.len(), 2);
666 assert_eq!(doc.metadata.title, Some("Incremental Building Test".to_string()));
667 assert_eq!(doc.metadata.author, Some("Incremental Author".to_string()));
668 assert_eq!(doc.metadata.subject, Some("Incremental Subject".to_string()));
669 assert_eq!(doc.metadata.keywords, Some("incremental, building, test".to_string()));
670 }
671
672 #[test]
673 fn test_document_concurrent_page_operations() {
674 let mut doc = Document::new();
675 doc.set_title("Concurrent Operations Test");
676
677 let mut pages = Vec::new();
679
680 for i in 0..5 {
682 let mut page = Page::a4();
683 page.text()
684 .set_font(Font::Helvetica, 12.0)
685 .at(50.0, 750.0)
686 .write(&format!("Concurrent page {}", i))
687 .unwrap();
688 pages.push(page);
689 }
690
691 for page in pages {
693 doc.add_page(page);
694 }
695
696 assert_eq!(doc.pages.len(), 5);
697
698 let temp_dir = TempDir::new().unwrap();
700 let file_path = temp_dir.path().join("concurrent.pdf");
701 let result = doc.save(&file_path);
702 assert!(result.is_ok());
703 }
704
705 #[test]
706 fn test_document_memory_efficiency() {
707 let mut doc = Document::new();
708 doc.set_title("Memory Efficiency Test");
709
710 for i in 0..10 {
712 let mut page = Page::a4();
713 page.text()
714 .set_font(Font::Helvetica, 12.0)
715 .at(50.0, 700.0)
716 .write(&format!("Memory test page {}", i))
717 .unwrap();
718 doc.add_page(page);
719 }
720
721 let mut buffer = Vec::new();
723 let result = doc.write(&mut buffer);
724 assert!(result.is_ok());
725 assert!(!buffer.is_empty());
726
727 assert!(buffer.len() < 1_000_000); }
730 }
731}