1#![deny(missing_docs)]
48
49#[macro_use]
50extern crate lazy_static;
51
52use chrono::offset::Local;
53use std::collections::{BTreeMap, HashMap};
54use std::fmt;
55use std::fs::File;
56use std::io::{self, Seek, SeekFrom, Write};
57
58mod fontsource;
59pub use crate::fontsource::{BuiltinFont, FontSource};
60
61mod fontref;
62pub use crate::fontref::FontRef;
63
64mod fontmetrics;
65pub use crate::fontmetrics::FontMetrics;
66
67mod encoding;
68pub use crate::encoding::Encoding;
69
70pub mod graphicsstate;
71
72mod outline;
73use crate::outline::OutlineItem;
74
75mod canvas;
76pub use crate::canvas::Canvas;
77
78mod textobject;
79pub use crate::textobject::TextObject;
80
81pub struct Pdf {
89 output: File,
90 object_offsets: Vec<i64>,
91 page_objects_ids: Vec<usize>,
92 all_font_object_ids: HashMap<BuiltinFont, usize>,
93 outline_items: Vec<OutlineItem>,
94 document_info: BTreeMap<String, String>,
95}
96
97const ROOT_OBJECT_ID: usize = 1;
98const PAGES_OBJECT_ID: usize = 2;
99
100impl Pdf {
101 pub fn create(filename: &str) -> io::Result<Pdf> {
103 let file = File::create(filename)?;
104 Pdf::new(file)
105 }
106
107 pub fn new(mut output: File) -> io::Result<Pdf> {
109 output.write_all(b"%PDF-1.7\n%\xB5\xED\xAE\xFB\n")?;
111 Ok(Pdf {
112 output,
113 object_offsets: vec![-1, -1, -1],
116 page_objects_ids: Vec::new(),
117 all_font_object_ids: HashMap::new(),
118 outline_items: Vec::new(),
119 document_info: BTreeMap::new(),
120 })
121 }
122 pub fn set_title(&mut self, title: &str) {
124 self.document_info
125 .insert("Title".to_string(), title.to_string());
126 }
127 pub fn set_author(&mut self, author: &str) {
129 self.document_info
130 .insert("Author".to_string(), author.to_string());
131 }
132 pub fn set_subject(&mut self, subject: &str) {
134 self.document_info
135 .insert("Subject".to_string(), subject.to_string());
136 }
137 pub fn set_keywords(&mut self, keywords: &str) {
139 self.document_info
140 .insert("Keywords".to_string(), keywords.to_string());
141 }
142 pub fn set_creator(&mut self, creator: &str) {
146 self.document_info
147 .insert("Creator".to_string(), creator.to_string());
148 }
149 pub fn set_producer(&mut self, producer: &str) {
152 self.document_info
153 .insert("Producer".to_string(), producer.to_string());
154 }
155
156 fn tell(&mut self) -> io::Result<u64> {
158 self.output.seek(SeekFrom::Current(0))
159 }
160
161 pub fn render_page<F>(
167 &mut self,
168 width: f32,
169 height: f32,
170 render_contents: F,
171 ) -> io::Result<()>
172 where
173 F: FnOnce(&mut Canvas) -> io::Result<()>,
174 {
175 let (contents_object_id, content_length, fonts, outline_items) = self
176 .write_new_object(move |contents_object_id, pdf| {
177 writeln!(
179 pdf.output,
180 "<< /Length {} 0 R >>\n\
181 stream",
182 contents_object_id + 1,
183 )?;
184
185 let start = pdf.tell()?;
186 writeln!(pdf.output, "/DeviceRGB cs /DeviceRGB CS")?;
187 let mut fonts = HashMap::new();
188 let mut outline_items = Vec::new();
189 render_contents(&mut Canvas::new(
190 &mut pdf.output,
191 &mut fonts,
192 &mut outline_items,
193 ))?;
194 let end = pdf.tell()?;
195
196 writeln!(pdf.output, "endstream")?;
197 Ok((contents_object_id, end - start, fonts, outline_items))
198 })?;
199 self.write_new_object(|length_object_id, pdf| {
200 assert!(length_object_id == contents_object_id + 1);
201 writeln!(pdf.output, "{}", content_length)
202 })?;
203
204 let mut font_oids = NamedRefs::new(fonts.len());
205 for (src, r) in fonts {
206 if let Some(&object_id) = self.all_font_object_ids.get(&src) {
207 font_oids.insert(r, object_id);
208 } else {
209 let object_id = src.write_object(self)?;
210 font_oids.insert(r, object_id);
211 self.all_font_object_ids.insert(src, object_id);
212 }
213 }
214 let page_oid = self.write_page_dict(
215 contents_object_id,
216 width,
217 height,
218 font_oids,
219 )?;
220 for mut item in outline_items {
223 item.set_page(page_oid);
224 self.outline_items.push(item);
225 }
226 self.page_objects_ids.push(page_oid);
227 Ok(())
228 }
229
230 fn write_page_dict(
231 &mut self,
232 content_oid: usize,
233 width: f32,
234 height: f32,
235 font_oids: NamedRefs,
236 ) -> io::Result<usize> {
237 self.write_new_object(|page_oid, pdf| {
238 writeln!(
239 pdf.output,
240 "<< /Type /Page\n \
241 /Parent {parent} 0 R\n \
242 /Resources << /Font << {fonts}>> >>\n \
243 /MediaBox [ 0 0 {width} {height} ]\n \
244 /Contents {c_oid} 0 R\n\
245 >>",
246 parent = PAGES_OBJECT_ID,
247 fonts = font_oids,
248 width = width,
249 height = height,
250 c_oid = content_oid,
251 )
252 .map(|_| page_oid)
253 })
254 }
255
256 fn write_new_object<F, T>(&mut self, write_content: F) -> io::Result<T>
257 where
258 F: FnOnce(usize, &mut Pdf) -> io::Result<T>,
259 {
260 let id = self.object_offsets.len();
261 let (result, offset) =
262 self.write_object(id, |pdf| write_content(id, pdf))?;
263 self.object_offsets.push(offset);
264 Ok(result)
265 }
266
267 fn write_object_with_id<F, T>(
268 &mut self,
269 id: usize,
270 write_content: F,
271 ) -> io::Result<T>
272 where
273 F: FnOnce(&mut Pdf) -> io::Result<T>,
274 {
275 assert!(self.object_offsets[id] == -1);
276 let (result, offset) = self.write_object(id, write_content)?;
277 self.object_offsets[id] = offset;
278 Ok(result)
279 }
280
281 fn write_object<F, T>(
282 &mut self,
283 id: usize,
284 write_content: F,
285 ) -> io::Result<(T, i64)>
286 where
287 F: FnOnce(&mut Pdf) -> io::Result<T>,
288 {
289 let offset = self.tell()? as i64;
291 writeln!(self.output, "{} 0 obj", id)?;
292 let result = write_content(self)?;
293 writeln!(self.output, "endobj")?;
294 Ok((result, offset))
295 }
296
297 pub fn finish(mut self) -> io::Result<()> {
301 self.write_object_with_id(PAGES_OBJECT_ID, |pdf| {
302 write!(
303 pdf.output,
304 "<< /Type /Pages\n \
305 /Count {}\n ",
306 pdf.page_objects_ids.len()
307 )?;
308 write!(pdf.output, "/Kids [ ")?;
309 for id in &pdf.page_objects_ids {
310 write!(pdf.output, "{} 0 R ", id)?;
311 }
312 writeln!(pdf.output, "]\n>>")
313 })?;
314 let document_info_id = if !self.document_info.is_empty() {
315 let info = self.document_info.clone();
316 self.write_new_object(|page_object_id, pdf| {
317 write!(pdf.output, "<<")?;
318 for (key, value) in info {
319 writeln!(pdf.output, " /{} ({})", key, value)?;
320 }
321 let now = Local::now().format("%Y%m%d%H%M%S%z").to_string();
322 write!(
323 pdf.output,
324 " /CreationDate (D:{now})\n \
325 /ModDate (D:{now})",
326 now = now,
327 )?;
328 writeln!(pdf.output, ">>")?;
329 Ok(Some(page_object_id))
330 })?
331 } else {
332 None
333 };
334
335 let outlines_id = self.write_outlines()?;
336
337 self.write_object_with_id(ROOT_OBJECT_ID, |pdf| {
338 writeln!(
339 pdf.output,
340 "<< /Type /Catalog\n \
341 /Pages {} 0 R",
342 PAGES_OBJECT_ID,
343 )?;
344 if let Some(outlines_id) = outlines_id {
345 writeln!(pdf.output, "/Outlines {} 0 R", outlines_id)?;
346 }
347 writeln!(pdf.output, ">>")
348 })?;
349 let startxref = self.tell()?;
350 writeln!(
351 self.output,
352 "xref\n\
353 0 {}\n\
354 0000000000 65535 f ",
355 self.object_offsets.len(),
356 )?;
357 for &offset in &self.object_offsets[1..] {
360 assert!(offset >= 0);
361 writeln!(self.output, "{:010} 00000 n ", offset)?;
362 }
363 writeln!(
364 self.output,
365 "trailer\n\
366 << /Size {size}\n \
367 /Root {root} 0 R",
368 size = self.object_offsets.len(),
369 root = ROOT_OBJECT_ID,
370 )?;
371 if let Some(id) = document_info_id {
372 writeln!(self.output, " /Info {} 0 R", id)?;
373 }
374 writeln!(
375 self.output,
376 ">>\n\
377 startxref\n\
378 {}\n\
379 %%EOF",
380 startxref,
381 )
382 }
383
384 fn write_outlines(&mut self) -> io::Result<Option<usize>> {
385 if self.outline_items.is_empty() {
386 return Ok(None);
387 }
388
389 let parent_id = self.object_offsets.len();
390 self.object_offsets.push(-1);
391 let count = self.outline_items.len();
392 let mut first_id = 0;
393 let mut last_id = 0;
394 let items = self.outline_items.clone();
395 for (i, item) in items.iter().enumerate() {
396 let (is_first, is_last) = (i == 0, i == count - 1);
397 let id = self.write_new_object(|object_id, pdf| {
398 item.write_dictionary(
399 &mut pdf.output,
400 parent_id,
401 if is_first { None } else { Some(object_id - 1) },
402 if is_last { None } else { Some(object_id + 1) },
403 )
404 .and(Ok(object_id))
405 })?;
406 if is_first {
407 first_id = id;
408 }
409 if is_last {
410 last_id = id;
411 }
412 }
413 self.write_object_with_id(parent_id, |pdf| {
414 writeln!(
415 pdf.output,
416 "<< /Type /Outlines\n \
417 /First {first} 0 R\n \
418 /Last {last} 0 R\n \
419 /Count {count}\n\
420 >>",
421 last = last_id,
422 first = first_id,
423 count = count,
424 )
425 })?;
426 Ok(Some(parent_id))
427 }
428}
429
430struct NamedRefs {
431 oids: HashMap<FontRef, usize>,
432}
433
434impl NamedRefs {
435 fn new(capacity: usize) -> Self {
436 NamedRefs {
437 oids: HashMap::with_capacity(capacity),
438 }
439 }
440 fn insert(&mut self, name: FontRef, oid: usize) -> Option<usize> {
441 self.oids.insert(name, oid)
442 }
443}
444
445impl fmt::Display for NamedRefs {
446 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
447 for (name, id) in &self.oids {
448 write!(f, "{} {} 0 R ", name, id)?;
449 }
450 Ok(())
451 }
452}