1use std::collections::BTreeMap;
2use std::collections::BTreeSet;
3use std::fmt;
4use std::fs::File;
5use std::io::{self, BufWriter, Write};
6use std::path::Path;
7
8use flate2::write::ZlibEncoder;
9use flate2::Compression;
10
11use crate::fonts::{BuiltinFont, FontRef, TrueTypeFontId};
12use crate::graphics::Color;
13use crate::images::{self, ImageData, ImageFit, ImageFormat, ImageId};
14use crate::objects::{ObjId, PdfObject};
15use crate::tables::{Row, Table, TableCursor};
16use crate::textflow::{FitResult, Rect, TextFlow, TextStyle};
17use crate::truetype::TrueTypeFont;
18use crate::writer::PdfWriter;
19
20#[derive(Debug)]
26pub enum FormFieldError {
27 NoActivePage,
29 DuplicateName(String),
31}
32
33impl fmt::Display for FormFieldError {
34 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35 match self {
36 FormFieldError::NoActivePage => write!(f, "add_text_field called with no active page"),
37 FormFieldError::DuplicateName(name) => {
38 write!(f, "duplicate field name: '{}'", name)
39 }
40 }
41 }
42}
43
44impl std::error::Error for FormFieldError {}
45
46struct FormFieldDef {
48 name: String,
49 rect: Rect,
50}
51
52struct FormFieldRecord {
54 name: String,
55 rect: Rect,
56 obj_id: ObjId,
57}
58
59const CATALOG_OBJ: ObjId = ObjId(1, 0);
60const PAGES_OBJ: ObjId = ObjId(2, 0);
61const FIRST_PAGE_OBJ_NUM: u32 = 3;
62
63struct ImageObjIds {
65 xobject: ObjId,
66 smask: Option<ObjId>,
67 pdf_name: String,
68}
69
70struct TrueTypeFontObjIds {
72 type0: ObjId,
73 cid_font: ObjId,
74 descriptor: ObjId,
75 font_file: ObjId,
76 tounicode: ObjId,
77}
78
79struct PageRecord {
84 obj_id: ObjId,
85 content_ids: Vec<ObjId>,
87 width: f64,
88 height: f64,
89 used_fonts: BTreeSet<BuiltinFont>,
90 used_truetype_fonts: BTreeSet<usize>,
91 used_images: BTreeSet<usize>,
92 fields: Vec<FormFieldRecord>,
94}
95
96pub struct PdfDocument<W: Write> {
105 writer: PdfWriter<W>,
106 info: Vec<(String, String)>,
107 page_records: Vec<PageRecord>,
108 current_page: Option<PageBuilder>,
109 next_obj_num: u32,
110 font_obj_ids: BTreeMap<BuiltinFont, ObjId>,
112 truetype_fonts: Vec<TrueTypeFont>,
114 truetype_font_obj_ids: BTreeMap<usize, TrueTypeFontObjIds>,
116 next_font_num: u32,
118 compress: bool,
120 images: Vec<ImageData>,
122 image_obj_ids: BTreeMap<usize, ImageObjIds>,
124 written_images: BTreeSet<usize>,
126 next_image_num: u32,
128 form_field_names: BTreeSet<String>,
130}
131
132struct PageBuilder {
133 width: f64,
134 height: f64,
135 content_ops: Vec<u8>,
136 used_fonts: BTreeSet<BuiltinFont>,
137 used_truetype_fonts: BTreeSet<usize>,
138 used_images: BTreeSet<usize>,
139 overlay_for: Option<usize>,
142 fields: Vec<FormFieldDef>,
144}
145
146impl PdfDocument<BufWriter<File>> {
147 pub fn create<P: AsRef<Path>>(path: P) -> io::Result<Self> {
149 let file = File::create(path)?;
150 Self::new(BufWriter::new(file))
151 }
152}
153
154impl<W: Write> PdfDocument<W> {
155 pub fn new(writer: W) -> io::Result<Self> {
158 let mut pdf_writer = PdfWriter::new(writer);
159 pdf_writer.write_header()?;
160
161 Ok(PdfDocument {
162 writer: pdf_writer,
163 info: Vec::new(),
164 page_records: Vec::new(),
165 current_page: None,
166 next_obj_num: FIRST_PAGE_OBJ_NUM,
167 font_obj_ids: BTreeMap::new(),
168 truetype_fonts: Vec::new(),
169 truetype_font_obj_ids: BTreeMap::new(),
170 next_font_num: 15,
171 compress: false,
172 images: Vec::new(),
173 image_obj_ids: BTreeMap::new(),
174 written_images: BTreeSet::new(),
175 next_image_num: 1,
176 form_field_names: BTreeSet::new(),
177 })
178 }
179
180 pub fn set_info(&mut self, key: &str, value: &str) -> &mut Self {
182 self.info.push((key.to_string(), value.to_string()));
183 self
184 }
185
186 pub fn set_compression(&mut self, enabled: bool) -> &mut Self {
191 self.compress = enabled;
192 self
193 }
194
195 pub fn load_font_file<P: AsRef<Path>>(&mut self, path: P) -> Result<FontRef, String> {
198 let data =
199 std::fs::read(path.as_ref()).map_err(|e| format!("Failed to read font file: {}", e))?;
200 self.load_font_bytes(data)
201 }
202
203 pub fn load_font_bytes(&mut self, data: Vec<u8>) -> Result<FontRef, String> {
206 let font_num = self.next_font_num;
207 self.next_font_num += 1;
208 let font = TrueTypeFont::from_bytes(data, font_num)?;
209 let idx = self.truetype_fonts.len();
210 self.truetype_fonts.push(font);
211 Ok(FontRef::TrueType(TrueTypeFontId(idx)))
212 }
213
214 pub fn page_count(&self) -> usize {
216 self.page_records.len()
217 }
218
219 pub fn begin_page(&mut self, width: f64, height: f64) -> &mut Self {
222 if self.current_page.is_some() {
223 let _ = self.end_page();
224 }
225 self.current_page = Some(PageBuilder {
226 width,
227 height,
228 content_ops: Vec::new(),
229 used_fonts: BTreeSet::new(),
230 used_truetype_fonts: BTreeSet::new(),
231 used_images: BTreeSet::new(),
232 overlay_for: None,
233 fields: Vec::new(),
234 });
235 self
236 }
237
238 pub fn open_page(&mut self, page_num: usize) -> io::Result<()> {
248 if page_num == 0 || page_num > self.page_records.len() {
249 return Err(io::Error::new(
250 io::ErrorKind::InvalidInput,
251 format!(
252 "open_page: page_num {} out of range (1..={})",
253 page_num,
254 self.page_records.len()
255 ),
256 ));
257 }
258
259 if self.current_page.is_some() {
260 self.end_page()?;
261 }
262
263 let idx = page_num - 1;
264 let width = self.page_records[idx].width;
265 let height = self.page_records[idx].height;
266
267 self.current_page = Some(PageBuilder {
268 width,
269 height,
270 content_ops: Vec::new(),
271 used_fonts: BTreeSet::new(),
272 used_truetype_fonts: BTreeSet::new(),
273 used_images: BTreeSet::new(),
274 overlay_for: Some(idx),
275 fields: Vec::new(),
276 });
277
278 Ok(())
279 }
280
281 pub fn place_text(&mut self, text: &str, x: f64, y: f64) -> &mut Self {
284 let page = self
285 .current_page
286 .as_mut()
287 .expect("place_text called with no open page");
288 page.used_fonts.insert(BuiltinFont::Helvetica);
289 let escaped = crate::writer::escape_pdf_string(text);
290 let ops = format!(
291 "BT\n/F1 12 Tf\n{} {} Td\n({}) Tj\nET\n",
292 format_coord(x),
293 format_coord(y),
294 escaped,
295 );
296 page.content_ops.extend_from_slice(ops.as_bytes());
297 self
298 }
299
300 pub fn place_text_styled(
303 &mut self,
304 text: &str,
305 x: f64,
306 y: f64,
307 style: &TextStyle,
308 ) -> &mut Self {
309 let (font_name, text_op) = match style.font {
311 FontRef::Builtin(b) => {
312 let escaped = crate::writer::escape_pdf_string(text);
313 (b.pdf_name().to_string(), format!("({}) Tj", escaped))
314 }
315 FontRef::TrueType(id) => {
316 let font = &mut self.truetype_fonts[id.0];
317 let hex = font.encode_text_hex(text);
318 (font.pdf_name.clone(), format!("{} Tj", hex))
319 }
320 };
321
322 let page = self
323 .current_page
324 .as_mut()
325 .expect("place_text_styled called with no open page");
326
327 match style.font {
328 FontRef::Builtin(b) => {
329 page.used_fonts.insert(b);
330 }
331 FontRef::TrueType(id) => {
332 page.used_truetype_fonts.insert(id.0);
333 }
334 }
335
336 let ops = format!(
337 "BT\n/{} {} Tf\n{} {} Td\n{}\nET\n",
338 font_name,
339 format_coord(style.font_size),
340 format_coord(x),
341 format_coord(y),
342 text_op,
343 );
344 page.content_ops.extend_from_slice(ops.as_bytes());
345 self
346 }
347
348 pub fn fit_textflow(&mut self, flow: &mut TextFlow, rect: &Rect) -> io::Result<FitResult> {
352 let (ops, result, used_fonts) = flow.generate_content_ops(rect, &mut self.truetype_fonts);
353
354 let page = self
355 .current_page
356 .as_mut()
357 .expect("fit_textflow called with no open page");
358 page.content_ops.extend_from_slice(&ops);
359 page.used_fonts.extend(used_fonts.builtin);
360 page.used_truetype_fonts.extend(used_fonts.truetype);
361 Ok(result)
362 }
363
364 pub fn fit_row(
374 &mut self,
375 table: &Table,
376 row: &Row,
377 cursor: &mut TableCursor,
378 ) -> io::Result<FitResult> {
379 let total_span: usize = row.cells.iter().map(|c| c.col_span.max(1)).sum();
380 if total_span != table.columns.len() {
381 return Err(io::Error::new(
382 io::ErrorKind::InvalidInput,
383 format!(
384 "row col_span sum ({}) must equal table column count ({})",
385 total_span,
386 table.columns.len()
387 ),
388 ));
389 }
390
391 let (ops, result, used_fonts) =
392 table.generate_row_ops(row, cursor, &mut self.truetype_fonts);
393
394 let page = self
395 .current_page
396 .as_mut()
397 .expect("fit_row called with no open page");
398 page.content_ops.extend_from_slice(&ops);
399 page.used_fonts.extend(used_fonts.builtin);
400 page.used_truetype_fonts.extend(used_fonts.truetype);
401 Ok(result)
402 }
403
404 pub fn load_image_file<P: AsRef<Path>>(&mut self, path: P) -> Result<ImageId, String> {
411 let data = std::fs::read(path.as_ref())
412 .map_err(|e| format!("Failed to read image file: {}", e))?;
413 self.load_image_bytes(data)
414 }
415
416 pub fn load_image_bytes(&mut self, data: Vec<u8>) -> Result<ImageId, String> {
419 let image_data = images::load_image(data)?;
420 let idx = self.images.len();
421 self.images.push(image_data);
422 Ok(ImageId(idx))
423 }
424
425 pub fn place_image(&mut self, image: &ImageId, rect: &Rect, fit: ImageFit) -> &mut Self {
427 let idx = image.0;
428 let img = &self.images[idx];
429 let page_height = self
430 .current_page
431 .as_ref()
432 .expect("place_image called with no open page")
433 .height;
434
435 let placement = images::calculate_placement(img.width, img.height, rect, fit, page_height);
436
437 self.ensure_image_obj_ids(idx);
438 let pdf_name = self.image_obj_ids[&idx].pdf_name.clone();
439
440 let page = self
441 .current_page
442 .as_mut()
443 .expect("place_image called with no open page");
444 page.used_images.insert(idx);
445
446 let mut ops = String::new();
448 ops.push_str("q\n");
449
450 if let Some(clip) = &placement.clip {
452 ops.push_str(&format!(
453 "{} {} {} {} re W n\n",
454 format_coord(clip.x),
455 format_coord(clip.y),
456 format_coord(clip.width),
457 format_coord(clip.height),
458 ));
459 }
460
461 ops.push_str(&format!(
464 "{} 0 0 {} {} {} cm\n",
465 format_coord(placement.width),
466 format_coord(placement.height),
467 format_coord(placement.x),
468 format_coord(placement.y),
469 ));
470
471 ops.push_str(&format!("/{} Do\n", pdf_name));
473 ops.push_str("Q\n");
474
475 page.content_ops.extend_from_slice(ops.as_bytes());
476 self
477 }
478
479 pub fn add_text_field(&mut self, name: &str, rect: Rect) -> Result<(), FormFieldError> {
484 if self.current_page.is_none() {
485 return Err(FormFieldError::NoActivePage);
486 }
487 if self.form_field_names.contains(name) {
488 return Err(FormFieldError::DuplicateName(name.to_string()));
489 }
490 self.form_field_names.insert(name.to_string());
491 let page = self.current_page.as_mut().unwrap();
492 page.fields.push(FormFieldDef {
493 name: name.to_string(),
494 rect,
495 });
496 Ok(())
497 }
498
499 fn ensure_image_obj_ids(&mut self, idx: usize) {
501 if self.image_obj_ids.contains_key(&idx) {
502 return;
503 }
504 let xobject = ObjId(self.next_obj_num, 0);
505 self.next_obj_num += 1;
506
507 let smask = if self.images[idx].smask_data.is_some() {
508 let id = ObjId(self.next_obj_num, 0);
509 self.next_obj_num += 1;
510 Some(id)
511 } else {
512 None
513 };
514
515 let pdf_name = format!("Im{}", self.next_image_num);
516 self.next_image_num += 1;
517
518 self.image_obj_ids.insert(
519 idx,
520 ImageObjIds {
521 xobject,
522 smask,
523 pdf_name,
524 },
525 );
526 }
527
528 fn write_image_xobject(&mut self, idx: usize) -> io::Result<()> {
530 if self.written_images.contains(&idx) {
531 return Ok(());
532 }
533
534 let img = &self.images[idx];
535 let obj_ids = &self.image_obj_ids[&idx];
536 let xobject_id = obj_ids.xobject;
537 let smask_id = obj_ids.smask;
538
539 if let (Some(smask_obj_id), Some(smask_data)) = (smask_id, img.smask_data.as_ref()) {
541 let smask_stream = self.make_stream(
542 vec![
543 ("Type", PdfObject::name("XObject")),
544 ("Subtype", PdfObject::name("Image")),
545 ("Width", PdfObject::Integer(img.width as i64)),
546 ("Height", PdfObject::Integer(img.height as i64)),
547 ("ColorSpace", PdfObject::name("DeviceGray")),
548 ("BitsPerComponent", PdfObject::Integer(8)),
549 ],
550 smask_data.clone(),
551 );
552 self.writer.write_object(smask_obj_id, &smask_stream)?;
553 }
554
555 let mut entries: Vec<(&str, PdfObject)> = vec![
557 ("Type", PdfObject::name("XObject")),
558 ("Subtype", PdfObject::name("Image")),
559 ("Width", PdfObject::Integer(img.width as i64)),
560 ("Height", PdfObject::Integer(img.height as i64)),
561 ("ColorSpace", PdfObject::name(img.color_space.pdf_name())),
562 (
563 "BitsPerComponent",
564 PdfObject::Integer(img.bits_per_component as i64),
565 ),
566 ];
567
568 if let Some(smask_obj_id) = smask_id {
569 entries.push(("SMask", PdfObject::Reference(smask_obj_id)));
570 }
571
572 let image_obj = match img.format {
575 ImageFormat::Jpeg => {
576 entries.push(("Filter", PdfObject::name("DCTDecode")));
577 PdfObject::stream(entries, img.data.clone())
578 }
579 ImageFormat::Png => self.make_stream(entries, img.data.clone()),
580 };
581
582 self.writer.write_object(xobject_id, &image_obj)?;
583 self.written_images.insert(idx);
584 Ok(())
585 }
586
587 pub fn set_stroke_color(&mut self, color: Color) -> &mut Self {
593 let page = self
594 .current_page
595 .as_mut()
596 .expect("set_stroke_color called with no open page");
597 let ops = format!(
598 "{} {} {} RG\n",
599 format_coord(color.r),
600 format_coord(color.g),
601 format_coord(color.b),
602 );
603 page.content_ops.extend_from_slice(ops.as_bytes());
604 self
605 }
606
607 pub fn set_fill_color(&mut self, color: Color) -> &mut Self {
609 let page = self
610 .current_page
611 .as_mut()
612 .expect("set_fill_color called with no open page");
613 let ops = format!(
614 "{} {} {} rg\n",
615 format_coord(color.r),
616 format_coord(color.g),
617 format_coord(color.b),
618 );
619 page.content_ops.extend_from_slice(ops.as_bytes());
620 self
621 }
622
623 pub fn set_line_width(&mut self, width: f64) -> &mut Self {
625 let page = self
626 .current_page
627 .as_mut()
628 .expect("set_line_width called with no open page");
629 let ops = format!("{} w\n", format_coord(width));
630 page.content_ops.extend_from_slice(ops.as_bytes());
631 self
632 }
633
634 pub fn move_to(&mut self, x: f64, y: f64) -> &mut Self {
636 let page = self
637 .current_page
638 .as_mut()
639 .expect("move_to called with no open page");
640 let ops = format!("{} {} m\n", format_coord(x), format_coord(y));
641 page.content_ops.extend_from_slice(ops.as_bytes());
642 self
643 }
644
645 pub fn line_to(&mut self, x: f64, y: f64) -> &mut Self {
647 let page = self
648 .current_page
649 .as_mut()
650 .expect("line_to called with no open page");
651 let ops = format!("{} {} l\n", format_coord(x), format_coord(y));
652 page.content_ops.extend_from_slice(ops.as_bytes());
653 self
654 }
655
656 pub fn rect(&mut self, x: f64, y: f64, width: f64, height: f64) -> &mut Self {
658 let page = self
659 .current_page
660 .as_mut()
661 .expect("rect called with no open page");
662 let ops = format!(
663 "{} {} {} {} re\n",
664 format_coord(x),
665 format_coord(y),
666 format_coord(width),
667 format_coord(height),
668 );
669 page.content_ops.extend_from_slice(ops.as_bytes());
670 self
671 }
672
673 pub fn close_path(&mut self) -> &mut Self {
675 let page = self
676 .current_page
677 .as_mut()
678 .expect("close_path called with no open page");
679 page.content_ops.extend_from_slice(b"h\n");
680 self
681 }
682
683 pub fn stroke(&mut self) -> &mut Self {
685 let page = self
686 .current_page
687 .as_mut()
688 .expect("stroke called with no open page");
689 page.content_ops.extend_from_slice(b"S\n");
690 self
691 }
692
693 pub fn fill(&mut self) -> &mut Self {
695 let page = self
696 .current_page
697 .as_mut()
698 .expect("fill called with no open page");
699 page.content_ops.extend_from_slice(b"f\n");
700 self
701 }
702
703 pub fn fill_stroke(&mut self) -> &mut Self {
705 let page = self
706 .current_page
707 .as_mut()
708 .expect("fill_stroke called with no open page");
709 page.content_ops.extend_from_slice(b"B\n");
710 self
711 }
712
713 pub fn save_state(&mut self) -> &mut Self {
715 let page = self
716 .current_page
717 .as_mut()
718 .expect("save_state called with no open page");
719 page.content_ops.extend_from_slice(b"q\n");
720 self
721 }
722
723 pub fn restore_state(&mut self) -> &mut Self {
725 let page = self
726 .current_page
727 .as_mut()
728 .expect("restore_state called with no open page");
729 page.content_ops.extend_from_slice(b"Q\n");
730 self
731 }
732
733 fn make_stream(&self, mut dict_entries: Vec<(&str, PdfObject)>, data: Vec<u8>) -> PdfObject {
735 if self.compress {
736 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
737 encoder.write_all(&data).expect("flate2 in-memory write");
738 let compressed = encoder.finish().expect("flate2 finish");
739 dict_entries.push(("Filter", PdfObject::name("FlateDecode")));
740 PdfObject::stream(dict_entries, compressed)
741 } else {
742 PdfObject::stream(dict_entries, data)
743 }
744 }
745
746 fn ensure_font_written(&mut self, font: BuiltinFont) -> io::Result<ObjId> {
748 if let Some(&id) = self.font_obj_ids.get(&font) {
749 return Ok(id);
750 }
751 let id = ObjId(self.next_obj_num, 0);
752 self.next_obj_num += 1;
753 let obj = PdfObject::dict(vec![
754 ("Type", PdfObject::name("Font")),
755 ("Subtype", PdfObject::name("Type1")),
756 ("BaseFont", PdfObject::name(font.pdf_base_name())),
757 ]);
758 self.writer.write_object(id, &obj)?;
759 self.font_obj_ids.insert(font, id);
760 Ok(id)
761 }
762
763 fn ensure_tt_font_obj_ids(&mut self, idx: usize) -> &TrueTypeFontObjIds {
765 if !self.truetype_font_obj_ids.contains_key(&idx) {
766 let type0 = ObjId(self.next_obj_num, 0);
767 self.next_obj_num += 1;
768 let cid_font = ObjId(self.next_obj_num, 0);
769 self.next_obj_num += 1;
770 let descriptor = ObjId(self.next_obj_num, 0);
771 self.next_obj_num += 1;
772 let font_file = ObjId(self.next_obj_num, 0);
773 self.next_obj_num += 1;
774 let tounicode = ObjId(self.next_obj_num, 0);
775 self.next_obj_num += 1;
776 self.truetype_font_obj_ids.insert(
777 idx,
778 TrueTypeFontObjIds {
779 type0,
780 cid_font,
781 descriptor,
782 font_file,
783 tounicode,
784 },
785 );
786 }
787 &self.truetype_font_obj_ids[&idx]
788 }
789
790 pub fn end_page(&mut self) -> io::Result<()> {
794 let page = self
795 .current_page
796 .take()
797 .expect("end_page called with no open page");
798
799 for &font in &page.used_fonts {
801 self.ensure_font_written(font)?;
802 }
803
804 for &idx in &page.used_truetype_fonts {
806 self.ensure_tt_font_obj_ids(idx);
807 }
808
809 let used_images: Vec<usize> = page.used_images.iter().copied().collect();
811 for idx in &used_images {
812 self.write_image_xobject(*idx)?;
813 }
814
815 let content_id = ObjId(self.next_obj_num, 0);
816 self.next_obj_num += 1;
817
818 let content_stream = self.make_stream(vec![], page.content_ops);
820 self.writer.write_object(content_id, &content_stream)?;
821
822 let field_records: Vec<FormFieldRecord> = page
824 .fields
825 .into_iter()
826 .map(|def| {
827 let obj_id = ObjId(self.next_obj_num, 0);
828 self.next_obj_num += 1;
829 FormFieldRecord {
830 name: def.name,
831 rect: def.rect,
832 obj_id,
833 }
834 })
835 .collect();
836
837 match page.overlay_for {
838 None => {
839 let page_id = ObjId(self.next_obj_num, 0);
842 self.next_obj_num += 1;
843
844 self.page_records.push(PageRecord {
845 obj_id: page_id,
846 content_ids: vec![content_id],
847 width: page.width,
848 height: page.height,
849 used_fonts: page.used_fonts,
850 used_truetype_fonts: page.used_truetype_fonts,
851 used_images: page.used_images,
852 fields: field_records,
853 });
854 }
855 Some(idx) => {
856 let record = &mut self.page_records[idx];
858 record.content_ids.push(content_id);
859 record.used_fonts.extend(page.used_fonts);
860 record.used_truetype_fonts.extend(page.used_truetype_fonts);
861 record.used_images.extend(page.used_images);
862 }
864 }
865
866 Ok(())
867 }
868
869 fn build_font_dict(&self, used_fonts: &[BuiltinFont], used_truetype: &[usize]) -> PdfObject {
871 let mut entries: Vec<(String, PdfObject)> = used_fonts
872 .iter()
873 .map(|f| {
874 (
875 f.pdf_name().to_string(),
876 PdfObject::Reference(self.font_obj_ids[f]),
877 )
878 })
879 .collect();
880
881 for &idx in used_truetype {
882 let name = self.truetype_fonts[idx].pdf_name.clone();
883 let type0_id = self.truetype_font_obj_ids[&idx].type0;
884 entries.push((name, PdfObject::Reference(type0_id)));
885 }
886
887 PdfObject::Dictionary(entries)
888 }
889
890 fn build_resource_dict(
892 &self,
893 used_fonts: &[BuiltinFont],
894 used_truetype: &[usize],
895 used_images: &[usize],
896 ) -> PdfObject {
897 let font_dict = self.build_font_dict(used_fonts, used_truetype);
898
899 let xobject_entries: Vec<(String, PdfObject)> = used_images
900 .iter()
901 .filter_map(|idx| {
902 self.image_obj_ids
903 .get(idx)
904 .map(|ids| (ids.pdf_name.clone(), PdfObject::Reference(ids.xobject)))
905 })
906 .collect();
907
908 let mut resource_entries: Vec<(String, PdfObject)> = vec![("Font".to_string(), font_dict)];
909 if !xobject_entries.is_empty() {
910 resource_entries.push((
911 "XObject".to_string(),
912 PdfObject::Dictionary(xobject_entries),
913 ));
914 }
915
916 PdfObject::Dictionary(resource_entries)
917 }
918
919 fn build_contents(content_ids: &[ObjId]) -> PdfObject {
921 if content_ids.len() == 1 {
922 PdfObject::Reference(content_ids[0])
923 } else {
924 PdfObject::array(
925 content_ids
926 .iter()
927 .map(|id| PdfObject::Reference(*id))
928 .collect(),
929 )
930 }
931 }
932
933 fn write_widget_annotations(&mut self, page_idx: usize) -> io::Result<()> {
935 let page_obj_id = self.page_records[page_idx].obj_id;
936 let field_ids: Vec<(String, Rect, ObjId)> = self.page_records[page_idx]
937 .fields
938 .iter()
939 .map(|f| (f.name.clone(), f.rect, f.obj_id))
940 .collect();
941
942 for (name, rect, obj_id) in field_ids {
943 let rect_array = PdfObject::array(vec![
945 PdfObject::Real(rect.x),
946 PdfObject::Real(rect.y),
947 PdfObject::Real(rect.x + rect.width),
948 PdfObject::Real(rect.y + rect.height),
949 ]);
950 let widget = PdfObject::dict(vec![
951 ("Type", PdfObject::name("Annot")),
952 ("Subtype", PdfObject::name("Widget")),
953 ("FT", PdfObject::name("Tx")),
954 ("T", PdfObject::literal_string(&name)),
955 ("Rect", rect_array),
956 ("P", PdfObject::Reference(page_obj_id)),
957 ("F", PdfObject::Integer(4)), ]);
959 self.writer.write_object(obj_id, &widget)?;
960 }
961 Ok(())
962 }
963
964 fn write_page_dicts(&mut self) -> io::Result<()> {
967 for i in 0..self.page_records.len() {
968 self.write_widget_annotations(i)?;
969
970 let obj_id = self.page_records[i].obj_id;
972 let content_ids: Vec<ObjId> =
973 self.page_records[i].content_ids.iter().copied().collect();
974 let width = self.page_records[i].width;
975 let height = self.page_records[i].height;
976 let used_fonts: Vec<BuiltinFont> =
977 self.page_records[i].used_fonts.iter().copied().collect();
978 let used_truetype: Vec<usize> = self.page_records[i]
979 .used_truetype_fonts
980 .iter()
981 .copied()
982 .collect();
983 let used_images: Vec<usize> =
984 self.page_records[i].used_images.iter().copied().collect();
985 let annot_ids: Vec<ObjId> = self.page_records[i]
986 .fields
987 .iter()
988 .map(|f| f.obj_id)
989 .collect();
990
991 let resources = self.build_resource_dict(&used_fonts, &used_truetype, &used_images);
992 let contents = Self::build_contents(&content_ids);
993
994 let mut page_entries = vec![
995 ("Type", PdfObject::name("Page")),
996 ("Parent", PdfObject::Reference(PAGES_OBJ)),
997 (
998 "MediaBox",
999 PdfObject::array(vec![
1000 PdfObject::Integer(0),
1001 PdfObject::Integer(0),
1002 PdfObject::Real(width),
1003 PdfObject::Real(height),
1004 ]),
1005 ),
1006 ("Contents", contents),
1007 ("Resources", resources),
1008 ];
1009
1010 if !annot_ids.is_empty() {
1011 let annots = PdfObject::array(
1012 annot_ids
1013 .iter()
1014 .map(|id| PdfObject::Reference(*id))
1015 .collect(),
1016 );
1017 page_entries.push(("Annots", annots));
1018 }
1019
1020 let page_dict = PdfObject::dict(page_entries);
1021 self.writer.write_object(obj_id, &page_dict)?;
1022 }
1023 Ok(())
1024 }
1025
1026 fn collect_all_field_ids(&self) -> Vec<ObjId> {
1028 self.page_records
1029 .iter()
1030 .flat_map(|r| r.fields.iter().map(|f| f.obj_id))
1031 .collect()
1032 }
1033
1034 fn write_acroform(&mut self) -> io::Result<Option<ObjId>> {
1037 let all_field_ids = self.collect_all_field_ids();
1038 if all_field_ids.is_empty() {
1039 return Ok(None);
1040 }
1041
1042 let fields_array = PdfObject::array(
1043 all_field_ids
1044 .iter()
1045 .map(|id| PdfObject::Reference(*id))
1046 .collect(),
1047 );
1048
1049 let acroform_id = ObjId(self.next_obj_num, 0);
1050 self.next_obj_num += 1;
1051
1052 let acroform = PdfObject::dict(vec![
1053 ("Fields", fields_array),
1054 ("NeedAppearances", PdfObject::Boolean(true)),
1055 ("DA", PdfObject::literal_string("/Helv 12 Tf 0 g")),
1057 ]);
1058 self.writer.write_object(acroform_id, &acroform)?;
1059 Ok(Some(acroform_id))
1060 }
1061
1062 fn write_truetype_fonts(&mut self) -> io::Result<()> {
1065 let indices: Vec<usize> = self.truetype_font_obj_ids.keys().copied().collect();
1066
1067 for idx in indices {
1068 let obj_ids_type0 = self.truetype_font_obj_ids[&idx].type0;
1069 let obj_ids_cid = self.truetype_font_obj_ids[&idx].cid_font;
1070 let obj_ids_desc = self.truetype_font_obj_ids[&idx].descriptor;
1071 let obj_ids_file = self.truetype_font_obj_ids[&idx].font_file;
1072 let obj_ids_tounicode = self.truetype_font_obj_ids[&idx].tounicode;
1073
1074 let font = &self.truetype_fonts[idx];
1075
1076 let original_len = font.font_data.len() as i64;
1078 let font_file_stream = self.make_stream(
1079 vec![("Length1", PdfObject::Integer(original_len))],
1080 font.font_data.clone(),
1081 );
1082 self.writer.write_object(obj_ids_file, &font_file_stream)?;
1083
1084 let descriptor = PdfObject::dict(vec![
1086 ("Type", PdfObject::name("FontDescriptor")),
1087 ("FontName", PdfObject::name(&font.postscript_name)),
1088 ("Flags", PdfObject::Integer(font.flags as i64)),
1089 (
1090 "FontBBox",
1091 PdfObject::array(vec![
1092 PdfObject::Integer(font.scale_to_pdf(font.bbox[0])),
1093 PdfObject::Integer(font.scale_to_pdf(font.bbox[1])),
1094 PdfObject::Integer(font.scale_to_pdf(font.bbox[2])),
1095 PdfObject::Integer(font.scale_to_pdf(font.bbox[3])),
1096 ]),
1097 ),
1098 ("ItalicAngle", PdfObject::Real(font.italic_angle)),
1099 ("Ascent", PdfObject::Integer(font.scale_to_pdf(font.ascent))),
1100 (
1101 "Descent",
1102 PdfObject::Integer(font.scale_to_pdf(font.descent)),
1103 ),
1104 (
1105 "CapHeight",
1106 PdfObject::Integer(font.scale_to_pdf(font.cap_height)),
1107 ),
1108 ("StemV", PdfObject::Integer(font.scale_to_pdf(font.stem_v))),
1109 ("FontFile2", PdfObject::Reference(obj_ids_file)),
1110 ]);
1111 self.writer.write_object(obj_ids_desc, &descriptor)?;
1112
1113 let w_array = font.build_w_array();
1115 let cid_font = PdfObject::dict(vec![
1116 ("Type", PdfObject::name("Font")),
1117 ("Subtype", PdfObject::name("CIDFontType2")),
1118 ("BaseFont", PdfObject::name(&font.postscript_name)),
1119 (
1120 "CIDSystemInfo",
1121 PdfObject::dict(vec![
1122 ("Registry", PdfObject::literal_string("Adobe")),
1123 ("Ordering", PdfObject::literal_string("Identity")),
1124 ("Supplement", PdfObject::Integer(0)),
1125 ]),
1126 ),
1127 ("FontDescriptor", PdfObject::Reference(obj_ids_desc)),
1128 ("DW", PdfObject::Integer(font.default_width_pdf())),
1129 ("W", PdfObject::Array(w_array)),
1130 ]);
1131 self.writer.write_object(obj_ids_cid, &cid_font)?;
1132
1133 let tounicode_data = font.build_tounicode_cmap();
1135 let tounicode = self.make_stream(vec![], tounicode_data);
1136 self.writer.write_object(obj_ids_tounicode, &tounicode)?;
1137
1138 let type0 = PdfObject::dict(vec![
1140 ("Type", PdfObject::name("Font")),
1141 ("Subtype", PdfObject::name("Type0")),
1142 ("BaseFont", PdfObject::name(&font.postscript_name)),
1143 ("Encoding", PdfObject::name("Identity-H")),
1144 (
1145 "DescendantFonts",
1146 PdfObject::array(vec![PdfObject::Reference(obj_ids_cid)]),
1147 ),
1148 ("ToUnicode", PdfObject::Reference(obj_ids_tounicode)),
1149 ]);
1150 self.writer.write_object(obj_ids_type0, &type0)?;
1151 }
1152
1153 Ok(())
1154 }
1155
1156 pub fn end_document(mut self) -> io::Result<W> {
1160 if self.current_page.is_some() {
1162 self.end_page()?;
1163 }
1164
1165 self.write_page_dicts()?;
1167
1168 self.write_truetype_fonts()?;
1170
1171 let acroform_id = self.write_acroform()?;
1173
1174 let info_id = if !self.info.is_empty() {
1176 let id = ObjId(self.next_obj_num, 0);
1177 self.next_obj_num += 1;
1178 let entries: Vec<(&str, PdfObject)> = self
1179 .info
1180 .iter()
1181 .map(|(k, v)| (k.as_str(), PdfObject::literal_string(v)))
1182 .collect();
1183 let info_obj = PdfObject::dict(entries);
1184 self.writer.write_object(id, &info_obj)?;
1185 Some(id)
1186 } else {
1187 None
1188 };
1189
1190 let kids: Vec<PdfObject> = self
1192 .page_records
1193 .iter()
1194 .map(|r| PdfObject::Reference(r.obj_id))
1195 .collect();
1196 let page_count = self.page_records.len() as i64;
1197 let pages = PdfObject::dict(vec![
1198 ("Type", PdfObject::name("Pages")),
1199 ("Kids", PdfObject::Array(kids)),
1200 ("Count", PdfObject::Integer(page_count)),
1201 ]);
1202 self.writer.write_object(PAGES_OBJ, &pages)?;
1203
1204 let mut catalog_entries = vec![
1206 ("Type", PdfObject::name("Catalog")),
1207 ("Pages", PdfObject::Reference(PAGES_OBJ)),
1208 ];
1209 if let Some(acroform) = acroform_id {
1210 catalog_entries.push(("AcroForm", PdfObject::Reference(acroform)));
1211 }
1212 let catalog = PdfObject::dict(catalog_entries);
1213 self.writer.write_object(CATALOG_OBJ, &catalog)?;
1214
1215 self.writer.write_xref_and_trailer(CATALOG_OBJ, info_id)?;
1217
1218 Ok(self.writer.into_inner())
1219 }
1220}
1221
1222pub(crate) fn format_coord(v: f64) -> String {
1224 if v == v.floor() && v.abs() < 1e15 {
1225 format!("{}", v as i64)
1226 } else {
1227 let s = format!("{:.4}", v);
1228 let s = s.trim_end_matches('0');
1229 let s = s.trim_end_matches('.');
1230 s.to_string()
1231 }
1232}