pdf_lib_rs/core/writers/
pdf_writer.rs1use crate::core::context::PdfContext;
2use crate::core::document::{PdfCrossRefSection, PdfTrailer, PdfTrailerDict};
3use crate::core::objects::*;
4use crate::core::objects::pdf_object::PdfObjectTrait;
5use crate::core::syntax::CharCodes;
6use crate::utils::copy_string_into_buffer;
7
8pub struct PdfWriter;
10
11impl PdfWriter {
12 pub fn serialize_to_buffer(context: &PdfContext) -> Vec<u8> {
14 let objects = context.enumerate_indirect_objects();
15
16 let header_size = context.header.size_in_bytes();
18 let mut total_size = header_size + 2; let mut object_sizes = Vec::new();
22 for (pdf_ref, object) in &objects {
23 let obj_num_str = pdf_ref.object_number.to_string();
24 let gen_num_str = pdf_ref.generation_number.to_string();
25 let obj_header_size = obj_num_str.len() + 1 + gen_num_str.len() + 5; let obj_footer_size = 9; let obj_content_size = object.size_in_bytes();
28 let obj_total = obj_header_size + obj_content_size + obj_footer_size;
29 object_sizes.push(obj_total);
30 total_size += obj_total;
31 }
32
33 let mut xref = PdfCrossRefSection::create();
35 let mut offset = header_size + 2;
36 for (i, (pdf_ref, _)) in objects.iter().enumerate() {
37 xref.add_entry(
38 PdfRef::of(pdf_ref.object_number, pdf_ref.generation_number),
39 offset as u64,
40 );
41 offset += object_sizes[i];
42 }
43 let xref_offset = offset;
44
45 let mut trailer_dict = PdfDict::new();
47 trailer_dict.set(
48 PdfName::of("Size"),
49 PdfObject::Number(PdfNumber::of((context.largest_object_number + 1) as f64)),
50 );
51 if let Some(root) = &context.trailer_info.root {
52 trailer_dict.set(PdfName::of("Root"), root.clone());
53 }
54 if let Some(info) = &context.trailer_info.info {
55 trailer_dict.set(PdfName::of("Info"), info.clone());
56 }
57
58 let trailer_dict_obj = PdfTrailerDict::of(trailer_dict);
59 let trailer = PdfTrailer::for_last_cross_ref_section_offset(xref_offset as u64);
60
61 total_size += xref.size_in_bytes();
62 total_size += trailer_dict_obj.size_in_bytes();
63 total_size += 1; total_size += trailer.size_in_bytes();
65
66 let mut buffer = vec![0u8; total_size];
68 let mut off = 0;
69
70 off += context.header.copy_bytes_into(&mut buffer, off);
72 buffer[off] = CharCodes::Newline;
73 off += 1;
74 buffer[off] = CharCodes::Newline;
75 off += 1;
76
77 for (pdf_ref, object) in &objects {
79 let obj_num_str = pdf_ref.object_number.to_string();
80 off += copy_string_into_buffer(&obj_num_str, &mut buffer, off);
81 buffer[off] = CharCodes::Space;
82 off += 1;
83
84 let gen_num_str = pdf_ref.generation_number.to_string();
85 off += copy_string_into_buffer(&gen_num_str, &mut buffer, off);
86 buffer[off] = CharCodes::Space;
87 off += 1;
88
89 buffer[off] = b'o';
90 off += 1;
91 buffer[off] = b'b';
92 off += 1;
93 buffer[off] = b'j';
94 off += 1;
95 buffer[off] = CharCodes::Newline;
96 off += 1;
97
98 off += object.copy_bytes_into(&mut buffer, off);
99
100 buffer[off] = CharCodes::Newline;
101 off += 1;
102 for &b in b"endobj" {
103 buffer[off] = b;
104 off += 1;
105 }
106 buffer[off] = CharCodes::Newline;
107 off += 1;
108 buffer[off] = CharCodes::Newline;
109 off += 1;
110 }
111
112 off += xref.copy_bytes_into(&mut buffer, off);
114
115 off += trailer_dict_obj.copy_bytes_into(&mut buffer, off);
117 buffer[off] = CharCodes::Newline;
118 off += 1;
119
120 off += trailer.copy_bytes_into(&mut buffer, off);
122
123 buffer.truncate(off);
124 buffer
125 }
126}
127
128#[cfg(test)]
129mod tests {
130 use super::*;
131 use crate::core::parser::PdfParser;
132
133 #[test]
134 fn can_serialize_empty_context() {
135 let context = PdfContext::create();
136 let bytes = PdfWriter::serialize_to_buffer(&context);
137 assert!(bytes.starts_with(b"%PDF-1.7"));
138 assert!(bytes.windows(5).any(|w| w == b"%%EOF"));
139 }
140
141 #[test]
142 fn can_serialize_context_with_objects() {
143 let mut context = PdfContext::create();
144 context.register(PdfObject::Number(PdfNumber::of(42.0)));
145 context.register(PdfObject::Name(PdfName::of("Foo")));
146 let bytes = PdfWriter::serialize_to_buffer(&context);
147
148 let output = String::from_utf8_lossy(&bytes);
149 assert!(output.contains("1 0 obj"));
150 assert!(output.contains("42"));
151 assert!(output.contains("2 0 obj"));
152 assert!(output.contains("/Foo"));
153 assert!(output.contains("endobj"));
154 assert!(output.contains("xref"));
155 assert!(output.contains("%%EOF"));
156 }
157
158 #[test]
159 fn roundtrip_parse_serialize_parse() {
160 let mut context = PdfContext::create();
161
162 let mut page_dict = PdfDict::new();
163 page_dict.set(PdfName::of("Type"), PdfObject::Name(PdfName::of("Page")));
164 let page_ref = context.register(PdfObject::Dict(page_dict));
165
166 let mut catalog = PdfDict::new();
167 catalog.set(PdfName::of("Type"), PdfObject::Name(PdfName::of("Catalog")));
168 catalog.set(PdfName::of("Pages"), PdfObject::Ref(page_ref));
169 let catalog_ref = context.register(PdfObject::Dict(catalog));
170
171 context.trailer_info.root = Some(PdfObject::Ref(catalog_ref));
172
173 let bytes = PdfWriter::serialize_to_buffer(&context);
174 let parser = PdfParser::for_bytes(&bytes);
175 let context2 = parser.parse_document().unwrap();
176
177 assert_eq!(context2.object_count(), context.object_count());
178 }
179}