rust_pdf_box/cos/
writer.rs1use crate::cos::document::COSDocument;
2use crate::cos::object::{
3 COSArray, COSDictionary, COSName, COSObject, COSStream, COSString, ObjectId, StringFormat,
4};
5use crate::error::PdfResult;
6use std::collections::BTreeMap;
7use std::io::Write;
8
9pub struct Writer<W: Write> {
10 writer: W,
11 written_bytes: usize,
12}
13
14impl<W: Write> Writer<W> {
15 pub fn new(writer: W) -> Self {
16 Self {
17 writer,
18 written_bytes: 0,
19 }
20 }
21
22 pub fn with_offset(writer: W, offset: usize) -> Self {
23 Self {
24 writer,
25 written_bytes: offset,
26 }
27 }
28
29 pub fn written_bytes(&self) -> usize {
30 self.written_bytes
31 }
32
33 pub fn into_inner(self) -> W {
34 self.writer
35 }
36
37 pub fn write_bytes(&mut self, bytes: &[u8]) -> PdfResult<()> {
38 self.writer.write_all(bytes)?;
39 self.written_bytes += bytes.len();
40 Ok(())
41 }
42
43 pub fn write_str(&mut self, s: &str) -> PdfResult<()> {
44 self.write_bytes(s.as_bytes())
45 }
46
47 pub fn write_line(&mut self, s: &str) -> PdfResult<()> {
48 self.write_str(s)?;
49 self.write_bytes(b"\n")
50 }
51
52 pub fn write_document(&mut self, doc: &COSDocument) -> PdfResult<()> {
54 self.write_line(&format!("%PDF-{}", doc.version))?;
56 self.write_bytes(b"%\xE2\xE3\xCF\xD3\n")?;
58
59 let mut offsets: BTreeMap<ObjectId, usize> = BTreeMap::new();
60
61 for (&id, obj) in &doc.objects {
63 offsets.insert(id, self.written_bytes);
64 self.write_indirect_object(id, obj)?;
65 }
66
67 let xref_offset = self.written_bytes;
69 let size = (doc.max_object_number + 1) as usize;
70
71 self.write_line("xref")?;
72 self.write_line(&format!("0 {}", size))?;
73
74 self.write_str("0000000000 65535 f \n")?;
76
77 for num in 1..=doc.max_object_number {
78 let id = ObjectId::new(num, 0);
79 if let Some(&off) = offsets.get(&id) {
80 self.write_str(&format!("{:010} {:05} n \n", off, id.generation))?;
81 } else {
82 self.write_str("0000000000 00000 f \n")?;
83 }
84 }
85
86 let mut trailer = doc.trailer.clone();
88 trailer.set("Size", size as i64);
89
90 self.write_line("trailer")?;
91 self.write_object(&COSObject::Dictionary(trailer))?;
92 self.write_line("")?;
93
94 self.write_line("startxref")?;
96 self.write_line(&format!("{}", xref_offset))?;
97 self.write_line("%%EOF")?;
98
99 Ok(())
100 }
101
102 pub fn write_incremental_update(
104 &mut self,
105 doc: &COSDocument,
106 prev_startxref: usize,
107 ) -> PdfResult<()> {
108 let mut updated_offsets: BTreeMap<ObjectId, usize> = BTreeMap::new();
109
110 for &id in &doc.dirty_objects {
112 if let Some(obj) = doc.get_object(id) {
113 updated_offsets.insert(id, self.written_bytes);
114 self.write_indirect_object(id, obj)?;
115 }
116 }
117
118 let xref_offset = self.written_bytes;
119 self.write_line("xref")?;
120
121 for (&id, &off) in &updated_offsets {
123 self.write_line(&format!("{} 1", id.num))?;
124 self.write_str(&format!("{:010} {:05} n \n", off, id.generation))?;
125 }
126
127 let mut trailer = doc.trailer.clone();
129 let size = (doc.max_object_number + 1) as i64;
130 trailer.set("Size", size);
131 trailer.set("Prev", prev_startxref as i64);
132
133 self.write_line("trailer")?;
134 self.write_object(&COSObject::Dictionary(trailer))?;
135 self.write_line("")?;
136
137 self.write_line("startxref")?;
139 self.write_line(&format!("{}", xref_offset))?;
140 self.write_line("%%EOF")?;
141
142 Ok(())
143 }
144
145 pub fn write_indirect_object(&mut self, id: ObjectId, obj: &COSObject) -> PdfResult<()> {
146 self.write_line(&format!("{} {} obj", id.num, id.generation))?;
147 self.write_object(obj)?;
148 self.write_line("\nendobj")?;
149 Ok(())
150 }
151
152 pub fn write_object(&mut self, obj: &COSObject) -> PdfResult<()> {
153 match obj {
154 COSObject::Null => self.write_str("null"),
155 COSObject::Boolean(b) => self.write_str(if *b { "true" } else { "false" }),
156 COSObject::Integer(i) => self.write_str(&format!("{}", i)),
157 COSObject::Real(f) => {
158 if f.fract() == 0.0 {
159 self.write_str(&format!("{:.1}", f))
160 } else {
161 let mut s = format!("{:.5}", f);
162 while s.ends_with('0') && s.contains('.') {
163 s.pop();
164 }
165 if s.ends_with('.') {
166 s.pop();
167 }
168 self.write_str(&s)
169 }
170 }
171 COSObject::String(s) => self.write_string(s),
172 COSObject::Name(n) => self.write_name(n),
173 COSObject::Array(a) => self.write_array(a),
174 COSObject::Dictionary(d) => self.write_dictionary(d),
175 COSObject::Stream(s) => self.write_stream(s),
176 COSObject::Reference(id) => self.write_str(&format!("{} {} R", id.num, id.generation)),
177 }
178 }
179
180 pub fn write_name(&mut self, name: &COSName) -> PdfResult<()> {
181 self.write_str("/")?;
182 for &b in name.as_str().as_bytes() {
183 if matches!(b, 33..=126)
184 && !matches!(
185 b,
186 b'(' | b')' | b'<' | b'>' | b'[' | b']' | b'{' | b'}' | b'/' | b'%' | b'#'
187 )
188 {
189 self.write_bytes(&[b])?;
190 } else {
191 self.write_str(&format!("#{:02X}", b))?;
192 }
193 }
194 Ok(())
195 }
196
197 pub fn write_string(&mut self, s: &COSString) -> PdfResult<()> {
198 match s.format {
199 StringFormat::Literal => {
200 self.write_str("(")?;
201 for &b in &s.data {
202 match b {
203 b'(' => self.write_str(r"\(")?,
204 b')' => self.write_str(r"\)")?,
205 b'\\' => self.write_str(r"\\")?,
206 b'\r' => self.write_str(r"\r")?,
207 b'\n' => self.write_str(r"\n")?,
208 _ => self.write_bytes(&[b])?,
209 }
210 }
211 self.write_str(")")
212 }
213 StringFormat::Hex => {
214 self.write_str("<")?;
215 for &b in &s.data {
216 self.write_str(&format!("{:02X}", b))?;
217 }
218 self.write_str(">")
219 }
220 }
221 }
222
223 pub fn write_array(&mut self, arr: &COSArray) -> PdfResult<()> {
224 self.write_str("[")?;
225 for (i, item) in arr.iter().enumerate() {
226 if i > 0 {
227 self.write_str(" ")?;
228 }
229 self.write_object(item)?;
230 }
231 self.write_str("]")
232 }
233
234 pub fn write_dictionary(&mut self, dict: &COSDictionary) -> PdfResult<()> {
235 self.write_line("<<")?;
236 for (key, val) in &dict.entries {
237 self.write_str(" ")?;
238 self.write_name(key)?;
239 self.write_str(" ")?;
240 self.write_object(val)?;
241 self.write_line("")?;
242 }
243 self.write_str(">>")
244 }
245
246 pub fn write_stream(&mut self, stream: &COSStream) -> PdfResult<()> {
247 let mut dict = stream.dict.clone();
248 dict.set("Length", stream.data.len() as i64);
249
250 self.write_dictionary(&dict)?;
251 self.write_line("")?;
252 self.write_line("stream")?;
253 self.write_bytes(&stream.data)?;
254 self.write_line("")?;
255 self.write_str("endstream")
256 }
257}