1use crate::Result;
2use crate::{Dictionary, Document, FontData, Object, ObjectId, Stream};
3
4impl Document {
5 pub fn with_version<S: Into<String>>(version: S) -> Document {
7 let mut document = Self::new();
8 document.version = version.into();
9 document
10 }
11
12 pub fn new_object_id(&mut self) -> ObjectId {
14 self.max_id += 1;
15 (self.max_id, 0)
16 }
17
18 pub fn add_object<T: Into<Object>>(&mut self, object: T) -> ObjectId {
20 self.max_id += 1;
21 let id = (self.max_id, 0);
22 self.objects.insert(id, object.into());
23 id
24 }
25
26 pub fn set_object<T: Into<Object>>(&mut self, id: ObjectId, object: T) {
27 self.objects.insert(id, object.into());
28 }
29
30 pub fn remove_object(&mut self, object_id: &ObjectId) -> Result<()> {
35 self.objects.remove(object_id);
36 Ok(())
37 }
38
39 pub fn remove_annot(&mut self, object_id: &ObjectId) -> Result<()> {
44 for (_, page_id) in self.get_pages() {
45 let page = self.get_object_mut(page_id)?.as_dict_mut()?;
46 let annots = page.get_mut(b"Annots")?.as_array_mut()?;
47
48 annots.retain(|object| {
49 if let Ok(id) = object.as_reference() {
50 return id != *object_id;
51 }
52
53 true
54 });
55 }
56
57 self.remove_object(object_id)?;
58
59 Ok(())
60 }
61
62 pub fn get_or_create_resources(&mut self, page_id: ObjectId) -> Result<&mut Object> {
66 let resources_id = {
67 let page = self.get_object(page_id).and_then(Object::as_dict)?;
68 if page.has(b"Resources") {
69 page.get(b"Resources").and_then(Object::as_reference).ok()
70 } else {
71 None
72 }
73 };
74 if let Some(res_id) = resources_id {
75 return self.get_object_mut(res_id);
76 }
77 let page = self.get_object_mut(page_id).and_then(Object::as_dict_mut)?;
78 if !page.has(b"Resources") {
79 page.set(b"Resources", Dictionary::new());
80 }
81 page.get_mut(b"Resources")
82 }
83
84 pub fn add_xobject<N: Into<Vec<u8>>>(
88 &mut self,
89 page_id: ObjectId,
90 xobject_name: N,
91 xobject_id: ObjectId,
92 ) -> Result<()> {
93 if let Ok(resources) = self
94 .get_or_create_resources(page_id)
95 .and_then(Object::as_dict_mut)
96 {
97 if !resources.has(b"XObject") {
98 resources.set("XObject", Dictionary::new());
99 }
100 let mut xobjects = resources.get_mut(b"XObject")?;
101 if let Object::Reference(xobjects_ref_id) = xobjects {
102 let mut xobjects_id = *xobjects_ref_id;
103 while let Object::Reference(id) = self.get_object(xobjects_id)? {
104 xobjects_id = *id;
105 }
106 xobjects = self.get_object_mut(xobjects_id)?;
107 }
108 let xobjects = Object::as_dict_mut(xobjects)?;
109 xobjects.set(xobject_name, Object::Reference(xobject_id));
110 }
111 Ok(())
112 }
113
114 pub fn add_graphics_state<N: Into<Vec<u8>>>(
118 &mut self,
119 page_id: ObjectId,
120 gs_name: N,
121 gs_id: ObjectId,
122 ) -> Result<()> {
123 if let Ok(resources) = self
124 .get_or_create_resources(page_id)
125 .and_then(Object::as_dict_mut)
126 {
127 if !resources.has(b"ExtGState") {
128 resources.set("ExtGState", Dictionary::new());
129 }
130 let states = resources
131 .get_mut(b"ExtGState")
132 .and_then(Object::as_dict_mut)?;
133 states.set(gs_name, Object::Reference(gs_id));
134 }
135 Ok(())
136 }
137
138 pub fn add_font(&mut self, font_data: FontData) -> Result<ObjectId> {
172 let font_stream = Stream::new(
174 dictionary! {
175 "Length1" => Object::Integer(font_data.bytes().len() as i64),
176 },
177 font_data.bytes(),
178 );
179 let font_file_id = self.add_object(font_stream);
180 let font_name = font_data.font_name.clone();
181
182 let font_descriptor_id = self.add_object(dictionary! {
184 "Type" => "FontDescriptor",
185 "FontName" => Object::Name(font_name.clone().into_bytes()),
186 "Flags" => Object::Integer(font_data.flags),
187 "FontBBox" => Object::Array(vec![
188 Object::Integer(font_data.font_bbox.0),
189 Object::Integer(font_data.font_bbox.1),
190 Object::Integer(font_data.font_bbox.2),
191 Object::Integer(font_data.font_bbox.3),
192 ]),
193 "ItalicAngle" => Object::Integer(font_data.italic_angle),
194 "Ascent" => Object::Integer(font_data.ascent),
195 "Descent" => Object::Integer(font_data.descent),
196 "CapHeight" => Object::Integer(font_data.cap_height),
197 "StemV" => Object::Integer(font_data.stem_v),
198 "FontFile2" => Object::Reference(font_file_id),
199 });
200
201 let font_id = self.add_object(dictionary! {
203 "Type" => "Font",
204 "Subtype" => "TrueType",
205 "BaseFont" => Object::Name(font_name.clone().into_bytes()),
206 "FontDescriptor" => Object::Reference(font_descriptor_id),
207 "Encoding" => Object::Name(font_data.encoding.into_bytes()),
208 });
209
210 Ok(font_id)
211 }
212}
213
214#[cfg(test)]
215pub mod tests {
216 use std::path::PathBuf;
217
218 use crate::content::*;
219 use crate::{Document, FontData, Object, Stream};
220
221 #[cfg(not(feature = "time"))]
222 pub fn get_timestamp() -> Object {
223 Object::string_literal("D:19700101000000Z")
224 }
225
226 #[cfg(feature = "time")]
227 pub fn get_timestamp() -> Object {
228 time::OffsetDateTime::now_utc().into()
229 }
230
231 pub fn create_document() -> Document {
233 create_document_with_texts(&["Hello World!"])
234 }
235
236 pub fn create_document_with_texts(texts_for_pages: &[&str]) -> Document {
237 let mut doc = Document::with_version("1.5");
238 let info_id = doc.add_object(dictionary! {
239 "Title" => Object::string_literal("Create PDF document example"),
240 "Creator" => Object::string_literal("https://crates.io/crates/lopdf"),
241 "CreationDate" => get_timestamp(),
242 });
243 let pages_id = doc.new_object_id();
244 let font_id = doc.add_object(dictionary! {
245 "Type" => "Font",
246 "Subtype" => "Type1",
247 "BaseFont" => "Courier",
248 });
249 let resources_id = doc.add_object(dictionary! {
250 "Font" => dictionary! {
251 "F1" => font_id,
252 },
253 });
254 let contents = texts_for_pages.iter().map(|text| Content {
255 operations: vec![
256 Operation::new("BT", vec![]),
257 Operation::new("Tf", vec!["F1".into(), 48.into()]),
258 Operation::new("Td", vec![100.into(), 600.into()]),
259 Operation::new("Tj", vec![Object::string_literal(*text)]),
260 Operation::new("ET", vec![]),
261 ],
262 });
263
264 let pages = contents.map(|content| {
265 let content_id = doc.add_object(Stream::new(dictionary! {}, content.encode().unwrap()));
266 let page = doc.add_object(dictionary! {
267 "Type" => "Page",
268 "Parent" => pages_id,
269 "Contents" => content_id,
270 });
271 page.into()
272 });
273
274 let pages = dictionary! {
275 "Type" => "Pages",
276 "Kids" => pages.collect::<Vec<Object>>(),
277 "Count" => 1,
278 "Resources" => resources_id,
279 "MediaBox" => vec![0.into(), 0.into(), 595.into(), 842.into()],
280 };
281 doc.objects.insert(pages_id, Object::Dictionary(pages));
282 let catalog_id = doc.add_object(dictionary! {
283 "Type" => "Catalog",
284 "Pages" => pages_id,
285 });
286 doc.trailer.set("Root", catalog_id);
287 doc.trailer.set("Info", info_id);
288 doc.trailer.set(
289 "ID",
290 Object::Array(vec![
291 Object::string_literal(b"ABC"),
292 Object::string_literal(b"DEF"),
293 ]),
294 );
295 doc.compress();
296 doc
297 }
298
299 pub fn save_document(file_path: &PathBuf, doc: &mut Document) {
301 let res = doc.save(file_path);
302
303 assert!(match res {
304 Ok(_file) => true,
305 Err(_e) => false,
306 });
307 }
308
309 #[test]
310 fn save_created_document() {
311 let temp_dir = tempfile::tempdir().unwrap();
313 let file_path = temp_dir.path().join("test_1_create.pdf");
314
315 let mut doc = create_document();
316 save_document(&file_path, &mut doc);
318 assert!(file_path.exists());
319 }
320
321 #[test]
322 #[ignore] fn test_add_font_embeds_font_correctly() {
324 let font_file = std::fs::read("./tests/resources/fonts/Montserrat-Regular.ttf").unwrap();
326
327 let mut font_data = FontData::new(&font_file, "MyFont".to_string());
329 font_data
330 .set_flags(32)
331 .set_font_bbox((0, -200, 1000, 800))
332 .set_italic_angle(0)
333 .set_ascent(750)
334 .set_descent(-250)
335 .set_cap_height(700)
336 .set_stem_v(80)
337 .set_encoding("WinAnsiEncoding".to_string());
338
339 let mut doc = Document::with_version("1.5");
341
342 let page_id = doc.new_object_id();
344 doc.set_object(page_id, dictionary! {});
345
346 let font_id = doc.add_font(font_data.clone()).unwrap();
348
349 let font_obj = doc.get_object(font_id).unwrap();
351 let font_dict = font_obj.as_dict().unwrap();
352
353 assert_eq!(
355 font_dict.get(b"BaseFont").unwrap(),
356 &Object::Name(b"MyFont".to_vec())
357 );
358
359 assert_eq!(
361 font_dict.get(b"Encoding").unwrap(),
362 &Object::Name(b"WinAnsiEncoding".to_vec())
363 );
364
365 let descriptor_ref = font_dict
367 .get(b"FontDescriptor")
368 .unwrap()
369 .as_reference()
370 .unwrap();
371 let descriptor_obj = doc.get_object(descriptor_ref).unwrap().as_dict().unwrap();
372 assert_eq!(
373 descriptor_obj.get(b"FontName").unwrap(),
374 &Object::Name(b"MyFont".to_vec())
375 );
376
377 let font_file_ref = descriptor_obj
379 .get(b"FontFile2")
380 .unwrap()
381 .as_reference()
382 .unwrap();
383 let font_stream = doc.get_object(font_file_ref).unwrap().as_stream().unwrap();
384 assert_eq!(font_stream.content, font_file);
385 }
386}