1use std::io::{self, Write};
2
3use crate::objects::{ObjId, PdfObject};
4
5pub struct PdfWriter<W: Write> {
8 writer: W,
9 offset: usize,
10 xref_entries: Vec<(u32, usize)>,
11}
12
13impl<W: Write> PdfWriter<W> {
14 pub fn new(writer: W) -> Self {
16 PdfWriter {
17 writer,
18 offset: 0,
19 xref_entries: Vec::new(),
20 }
21 }
22
23 fn write_bytes(&mut self, data: &[u8]) -> io::Result<()> {
25 self.writer.write_all(data)?;
26 self.offset += data.len();
27 Ok(())
28 }
29
30 fn write_str(&mut self, s: &str) -> io::Result<()> {
32 self.write_bytes(s.as_bytes())
33 }
34
35 pub fn write_header(&mut self) -> io::Result<()> {
37 self.write_str("%PDF-1.7\n")?;
38 self.write_bytes(b"%\xe2\xe3\xcf\xd3\n")?;
40 Ok(())
41 }
42
43 pub fn write_object(&mut self, id: ObjId, obj: &PdfObject) -> io::Result<()> {
45 self.xref_entries.push((id.0, self.offset));
46 self.write_str(&format!("{} {} obj\n", id.0, id.1))?;
47 self.write_pdf_object(obj)?;
48 self.write_str("\nendobj\n")?;
49 Ok(())
50 }
51
52 fn write_pdf_object(&mut self, obj: &PdfObject) -> io::Result<()> {
54 match obj {
55 PdfObject::Null => self.write_str("null"),
56 PdfObject::Boolean(b) => {
57 if *b {
58 self.write_str("true")
59 } else {
60 self.write_str("false")
61 }
62 }
63 PdfObject::Integer(n) => self.write_str(&n.to_string()),
64 PdfObject::Real(f) => {
65 let s = format_real(*f);
66 self.write_str(&s)
67 }
68 PdfObject::Name(name) => {
69 self.write_str("/")?;
70 self.write_str(name)
71 }
72 PdfObject::LiteralString(s) => {
73 self.write_str("(")?;
74 self.write_str(&escape_pdf_string(s))?;
75 self.write_str(")")
76 }
77 PdfObject::Array(items) => {
78 self.write_str("[")?;
79 for (i, item) in items.iter().enumerate() {
80 if i > 0 {
81 self.write_str(" ")?;
82 }
83 self.write_pdf_object(item)?;
84 }
85 self.write_str("]")
86 }
87 PdfObject::Dictionary(entries) => {
88 self.write_str("<<")?;
89 for (key, val) in entries {
90 self.write_str(" /")?;
91 self.write_str(key)?;
92 self.write_str(" ")?;
93 self.write_pdf_object(val)?;
94 }
95 self.write_str(" >>")
96 }
97 PdfObject::Stream { dict, data } => {
98 self.write_str("<<")?;
99 for (key, val) in dict {
100 self.write_str(" /")?;
101 self.write_str(key)?;
102 self.write_str(" ")?;
103 self.write_pdf_object(val)?;
104 }
105 self.write_str(" /Length ")?;
106 self.write_str(&data.len().to_string())?;
107 self.write_str(" >>\nstream\n")?;
108 self.write_bytes(data)?;
109 self.write_str("\nendstream")
110 }
111 PdfObject::Reference(id) => self.write_str(&format!("{} {} R", id.0, id.1)),
112 }
113 }
114
115 pub fn current_offset(&self) -> usize {
117 self.offset
118 }
119
120 pub fn write_xref_and_trailer(
122 &mut self,
123 root_id: ObjId,
124 info_id: Option<ObjId>,
125 ) -> io::Result<()> {
126 let xref_offset = self.offset;
127
128 self.xref_entries.sort_by_key(|&(num, _)| num);
130
131 let max_obj = self.xref_entries.last().map(|&(num, _)| num).unwrap_or(0);
132 let size = max_obj + 1;
133
134 self.write_str("xref\n")?;
135 self.write_str(&format!("0 {}\n", size))?;
136
137 self.write_bytes(b"0000000000 65535 f\r\n")?;
139
140 let mut offset_map = std::collections::HashMap::new();
142 for &(num, off) in &self.xref_entries {
143 offset_map.insert(num, off);
144 }
145
146 for obj_num in 1..size {
148 if let Some(&off) = offset_map.get(&obj_num) {
149 let entry = format!("{:010} {:05} n\r\n", off, 0);
150 self.write_bytes(entry.as_bytes())?;
151 } else {
152 self.write_bytes(b"0000000000 00000 f\r\n")?;
154 }
155 }
156
157 self.write_str("trailer\n")?;
159 self.write_str(&format!(
160 "<< /Size {} /Root {} {} R",
161 size, root_id.0, root_id.1,
162 ))?;
163 if let Some(info) = info_id {
164 self.write_str(&format!(" /Info {} {} R", info.0, info.1,))?;
165 }
166 self.write_str(" >>\n")?;
167
168 self.write_str("startxref\n")?;
169 self.write_str(&format!("{}\n", xref_offset))?;
170 self.write_str("%%EOF\n")?;
171
172 Ok(())
173 }
174
175 pub fn into_inner(self) -> W {
177 self.writer
178 }
179}
180
181pub fn escape_pdf_string(s: &str) -> String {
183 let mut result = String::with_capacity(s.len());
184 for c in s.chars() {
185 match c {
186 '\\' => result.push_str("\\\\"),
187 '(' => result.push_str("\\("),
188 ')' => result.push_str("\\)"),
189 _ => result.push(c),
190 }
191 }
192 result
193}
194
195fn format_real(f: f64) -> String {
198 if f == f.floor() && f.abs() < 1e15 {
199 format!("{:.1}", f)
200 } else {
201 let s = format!("{:.6}", f);
202 let s = s.trim_end_matches('0');
203 let s = s.trim_end_matches('.');
204 s.to_string()
205 }
206}