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::{Angle, 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, Clone, Copy, PartialEq, Eq)]
26pub enum Origin {
27 BottomLeft,
29 TopLeft,
31}
32
33#[derive(Debug, Clone)]
35pub struct DocumentOptions {
36 pub origin: Origin,
39}
40
41impl Default for DocumentOptions {
42 fn default() -> Self {
43 DocumentOptions {
44 origin: Origin::BottomLeft,
45 }
46 }
47}
48
49#[derive(Debug)]
55pub enum FormFieldError {
56 NoActivePage,
58 DuplicateName(String),
60}
61
62impl fmt::Display for FormFieldError {
63 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64 match self {
65 FormFieldError::NoActivePage => write!(f, "add_text_field called with no active page"),
66 FormFieldError::DuplicateName(name) => {
67 write!(f, "duplicate field name: '{}'", name)
68 }
69 }
70 }
71}
72
73impl std::error::Error for FormFieldError {}
74
75struct FormFieldDef {
77 name: String,
78 rect: Rect,
79}
80
81struct FormFieldRecord {
83 name: String,
84 rect: Rect,
85 obj_id: ObjId,
86}
87
88const CATALOG_OBJ: ObjId = ObjId(1, 0);
89const PAGES_OBJ: ObjId = ObjId(2, 0);
90const FIRST_PAGE_OBJ_NUM: u32 = 3;
91
92struct ImageObjIds {
94 xobject: ObjId,
95 smask: Option<ObjId>,
96 pdf_name: String,
97}
98
99struct TrueTypeFontObjIds {
101 type0: ObjId,
102 cid_font: ObjId,
103 descriptor: ObjId,
104 font_file: ObjId,
105 tounicode: ObjId,
106}
107
108struct PageRecord {
113 obj_id: ObjId,
114 content_ids: Vec<ObjId>,
116 width: f64,
117 height: f64,
118 used_fonts: BTreeSet<BuiltinFont>,
119 used_truetype_fonts: BTreeSet<usize>,
120 used_images: BTreeSet<usize>,
121 fields: Vec<FormFieldRecord>,
123}
124
125pub struct PdfDocument<W: Write> {
134 writer: PdfWriter<W>,
135 info: Vec<(String, String)>,
136 page_records: Vec<PageRecord>,
137 current_page: Option<PageBuilder>,
138 next_obj_num: u32,
139 font_obj_ids: BTreeMap<BuiltinFont, ObjId>,
141 truetype_fonts: Vec<TrueTypeFont>,
143 truetype_font_obj_ids: BTreeMap<usize, TrueTypeFontObjIds>,
145 next_font_num: u32,
147 compress: bool,
149 images: Vec<ImageData>,
151 image_obj_ids: BTreeMap<usize, ImageObjIds>,
153 written_images: BTreeSet<usize>,
155 next_image_num: u32,
157 form_field_names: BTreeSet<String>,
159 origin: Origin,
161}
162
163struct PageBuilder {
164 width: f64,
165 height: f64,
166 content_ops: Vec<u8>,
167 used_fonts: BTreeSet<BuiltinFont>,
168 used_truetype_fonts: BTreeSet<usize>,
169 used_images: BTreeSet<usize>,
170 overlay_for: Option<usize>,
173 fields: Vec<FormFieldDef>,
175}
176
177impl PdfDocument<BufWriter<File>> {
178 pub fn create<P: AsRef<Path>>(path: P, options: DocumentOptions) -> io::Result<Self> {
180 let file = File::create(path)?;
181 Self::new(BufWriter::new(file), options)
182 }
183}
184
185impl<W: Write> PdfDocument<W> {
186 pub fn new(writer: W, options: DocumentOptions) -> io::Result<Self> {
189 let mut pdf_writer = PdfWriter::new(writer);
190 pdf_writer.write_header()?;
191
192 Ok(PdfDocument {
193 writer: pdf_writer,
194 info: Vec::new(),
195 page_records: Vec::new(),
196 current_page: None,
197 next_obj_num: FIRST_PAGE_OBJ_NUM,
198 font_obj_ids: BTreeMap::new(),
199 truetype_fonts: Vec::new(),
200 truetype_font_obj_ids: BTreeMap::new(),
201 next_font_num: 15,
202 compress: false,
203 images: Vec::new(),
204 image_obj_ids: BTreeMap::new(),
205 written_images: BTreeSet::new(),
206 next_image_num: 1,
207 form_field_names: BTreeSet::new(),
208 origin: options.origin,
209 })
210 }
211
212 pub fn set_info(&mut self, key: &str, value: &str) -> &mut Self {
214 self.info.push((key.to_string(), value.to_string()));
215 self
216 }
217
218 pub fn set_compression(&mut self, enabled: bool) -> &mut Self {
223 self.compress = enabled;
224 self
225 }
226
227 pub fn load_font_file<P: AsRef<Path>>(&mut self, path: P) -> Result<FontRef, String> {
230 let data =
231 std::fs::read(path.as_ref()).map_err(|e| format!("Failed to read font file: {}", e))?;
232 self.load_font_bytes(data)
233 }
234
235 pub fn load_font_bytes(&mut self, data: Vec<u8>) -> Result<FontRef, String> {
238 let font_num = self.next_font_num;
239 self.next_font_num += 1;
240 let font = TrueTypeFont::from_bytes(data, font_num)?;
241 let idx = self.truetype_fonts.len();
242 self.truetype_fonts.push(font);
243 Ok(FontRef::TrueType(TrueTypeFontId(idx)))
244 }
245
246 pub fn page_count(&self) -> usize {
248 self.page_records.len()
249 }
250
251 pub fn begin_page(&mut self, width: f64, height: f64) -> &mut Self {
254 if self.current_page.is_some() {
255 let _ = self.end_page();
256 }
257 self.current_page = Some(PageBuilder {
258 width,
259 height,
260 content_ops: Vec::new(),
261 used_fonts: BTreeSet::new(),
262 used_truetype_fonts: BTreeSet::new(),
263 used_images: BTreeSet::new(),
264 overlay_for: None,
265 fields: Vec::new(),
266 });
267 self
268 }
269
270 pub fn open_page(&mut self, page_num: usize) -> io::Result<()> {
280 if page_num == 0 || page_num > self.page_records.len() {
281 return Err(io::Error::new(
282 io::ErrorKind::InvalidInput,
283 format!(
284 "open_page: page_num {} out of range (1..={})",
285 page_num,
286 self.page_records.len()
287 ),
288 ));
289 }
290
291 if self.current_page.is_some() {
292 self.end_page()?;
293 }
294
295 let idx = page_num - 1;
296 let width = self.page_records[idx].width;
297 let height = self.page_records[idx].height;
298
299 self.current_page = Some(PageBuilder {
300 width,
301 height,
302 content_ops: Vec::new(),
303 used_fonts: BTreeSet::new(),
304 used_truetype_fonts: BTreeSet::new(),
305 used_images: BTreeSet::new(),
306 overlay_for: Some(idx),
307 fields: Vec::new(),
308 });
309
310 Ok(())
311 }
312
313 pub fn place_text(&mut self, text: &str, x: f64, y: f64) -> &mut Self {
319 let y_pdf = self.transform_y(y);
320 let page = self
321 .current_page
322 .as_mut()
323 .expect("place_text called with no open page");
324 page.used_fonts.insert(BuiltinFont::Helvetica);
325 let escaped = crate::writer::escape_pdf_string(text);
326 let ops = format!(
327 "BT\n/F1 12 Tf\n{} {} Td\n({}) Tj\nET\n",
328 format_coord(x),
329 format_coord(y_pdf),
330 escaped,
331 );
332 page.content_ops.extend_from_slice(ops.as_bytes());
333 self
334 }
335
336 pub fn place_text_styled(
342 &mut self,
343 text: &str,
344 x: f64,
345 y: f64,
346 style: &TextStyle,
347 ) -> &mut Self {
348 let (font_name, text_op) = match style.font {
350 FontRef::Builtin(b) => {
351 let escaped = crate::writer::escape_pdf_string(text);
352 (b.pdf_name().to_string(), format!("({}) Tj", escaped))
353 }
354 FontRef::TrueType(id) => {
355 let font = &mut self.truetype_fonts[id.0];
356 let hex = font.encode_text_hex(text);
357 (font.pdf_name.clone(), format!("{} Tj", hex))
358 }
359 };
360
361 let y_pdf = self.transform_y(y);
362 let page = self
363 .current_page
364 .as_mut()
365 .expect("place_text_styled called with no open page");
366
367 match style.font {
368 FontRef::Builtin(b) => {
369 page.used_fonts.insert(b);
370 }
371 FontRef::TrueType(id) => {
372 page.used_truetype_fonts.insert(id.0);
373 }
374 }
375
376 let ops = format!(
377 "BT\n/{} {} Tf\n{} {} Td\n{}\nET\n",
378 font_name,
379 format_coord(style.font_size),
380 format_coord(x),
381 format_coord(y_pdf),
382 text_op,
383 );
384 page.content_ops.extend_from_slice(ops.as_bytes());
385 self
386 }
387
388 pub fn fit_textflow(&mut self, flow: &mut TextFlow, rect: &Rect) -> io::Result<FitResult> {
394 let pdf_rect = self.transform_rect_top_edge(rect);
395 let (ops, result, used_fonts) =
396 flow.generate_content_ops(&pdf_rect, &mut self.truetype_fonts);
397
398 let page = self
399 .current_page
400 .as_mut()
401 .expect("fit_textflow called with no open page");
402 page.content_ops.extend_from_slice(&ops);
403 page.used_fonts.extend(used_fonts.builtin);
404 page.used_truetype_fonts.extend(used_fonts.truetype);
405 Ok(result)
406 }
407
408 pub fn fit_row(
418 &mut self,
419 table: &Table,
420 row: &Row,
421 cursor: &mut TableCursor,
422 ) -> io::Result<FitResult> {
423 let total_span: usize = row.cells.iter().map(|c| c.col_span.max(1)).sum();
424 if total_span != table.columns.len() {
425 return Err(io::Error::new(
426 io::ErrorKind::InvalidInput,
427 format!(
428 "row col_span sum ({}) must equal table column count ({})",
429 total_span,
430 table.columns.len()
431 ),
432 ));
433 }
434
435 let (ops, result, used_fonts) = match self.origin {
436 Origin::BottomLeft => table.generate_row_ops(row, cursor, &mut self.truetype_fonts),
437 Origin::TopLeft => {
438 let page_h = self.current_page_height();
439 let pdf_top = page_h - cursor.rect.y;
441 let pdf_rect = Rect {
442 x: cursor.rect.x,
443 y: pdf_top,
444 width: cursor.rect.width,
445 height: cursor.rect.height,
446 };
447 let pdf_current_y = page_h - cursor.current_y;
448 let mut pdf_cursor = crate::tables::TableCursor {
449 rect: pdf_rect,
450 current_y: pdf_current_y,
451 first_row: cursor.first_row,
452 };
453 let result = table.generate_row_ops(row, &mut pdf_cursor, &mut self.truetype_fonts);
454 cursor.current_y = page_h - pdf_cursor.current_y;
456 cursor.first_row = pdf_cursor.first_row;
457 result
458 }
459 };
460
461 let page = self
462 .current_page
463 .as_mut()
464 .expect("fit_row called with no open page");
465 page.content_ops.extend_from_slice(&ops);
466 page.used_fonts.extend(used_fonts.builtin);
467 page.used_truetype_fonts.extend(used_fonts.truetype);
468 Ok(result)
469 }
470
471 pub fn load_image_file<P: AsRef<Path>>(&mut self, path: P) -> Result<ImageId, String> {
478 let data = std::fs::read(path.as_ref())
479 .map_err(|e| format!("Failed to read image file: {}", e))?;
480 self.load_image_bytes(data)
481 }
482
483 pub fn load_image_bytes(&mut self, data: Vec<u8>) -> Result<ImageId, String> {
486 let image_data = images::load_image(data)?;
487 let idx = self.images.len();
488 self.images.push(image_data);
489 Ok(ImageId(idx))
490 }
491
492 pub fn place_image(&mut self, image: &ImageId, rect: &Rect, fit: ImageFit) -> &mut Self {
498 let idx = image.0;
499 let img = &self.images[idx];
500
501 let pdf_rect = self.transform_rect(rect);
503 let placement = images::calculate_placement(img.width, img.height, &pdf_rect, fit);
504
505 self.ensure_image_obj_ids(idx);
506 let pdf_name = self.image_obj_ids[&idx].pdf_name.clone();
507
508 let page = self
509 .current_page
510 .as_mut()
511 .expect("place_image called with no open page");
512 page.used_images.insert(idx);
513
514 let mut ops = String::new();
516 ops.push_str("q\n");
517
518 if let Some(clip) = &placement.clip {
520 ops.push_str(&format!(
521 "{} {} {} {} re W n\n",
522 format_coord(clip.x),
523 format_coord(clip.y),
524 format_coord(clip.width),
525 format_coord(clip.height),
526 ));
527 }
528
529 ops.push_str(&format!(
532 "{} 0 0 {} {} {} cm\n",
533 format_coord(placement.width),
534 format_coord(placement.height),
535 format_coord(placement.x),
536 format_coord(placement.y),
537 ));
538
539 ops.push_str(&format!("/{} Do\n", pdf_name));
541 ops.push_str("Q\n");
542
543 page.content_ops.extend_from_slice(ops.as_bytes());
544 self
545 }
546
547 pub fn add_text_field(&mut self, name: &str, rect: Rect) -> Result<(), FormFieldError> {
554 if self.current_page.is_none() {
555 return Err(FormFieldError::NoActivePage);
556 }
557 if self.form_field_names.contains(name) {
558 return Err(FormFieldError::DuplicateName(name.to_string()));
559 }
560 let pdf_rect = self.transform_rect(&rect);
561 self.form_field_names.insert(name.to_string());
562 let page = self.current_page.as_mut().unwrap();
563 page.fields.push(FormFieldDef {
564 name: name.to_string(),
565 rect: pdf_rect,
566 });
567 Ok(())
568 }
569
570 fn ensure_image_obj_ids(&mut self, idx: usize) {
572 if self.image_obj_ids.contains_key(&idx) {
573 return;
574 }
575 let xobject = ObjId(self.next_obj_num, 0);
576 self.next_obj_num += 1;
577
578 let smask = if self.images[idx].smask_data.is_some() {
579 let id = ObjId(self.next_obj_num, 0);
580 self.next_obj_num += 1;
581 Some(id)
582 } else {
583 None
584 };
585
586 let pdf_name = format!("Im{}", self.next_image_num);
587 self.next_image_num += 1;
588
589 self.image_obj_ids.insert(
590 idx,
591 ImageObjIds {
592 xobject,
593 smask,
594 pdf_name,
595 },
596 );
597 }
598
599 fn write_image_xobject(&mut self, idx: usize) -> io::Result<()> {
601 if self.written_images.contains(&idx) {
602 return Ok(());
603 }
604
605 let img = &self.images[idx];
606 let obj_ids = &self.image_obj_ids[&idx];
607 let xobject_id = obj_ids.xobject;
608 let smask_id = obj_ids.smask;
609
610 if let (Some(smask_obj_id), Some(smask_data)) = (smask_id, img.smask_data.as_ref()) {
612 let smask_stream = self.make_stream(
613 vec![
614 ("Type", PdfObject::name("XObject")),
615 ("Subtype", PdfObject::name("Image")),
616 ("Width", PdfObject::Integer(img.width as i64)),
617 ("Height", PdfObject::Integer(img.height as i64)),
618 ("ColorSpace", PdfObject::name("DeviceGray")),
619 ("BitsPerComponent", PdfObject::Integer(8)),
620 ],
621 smask_data.clone(),
622 );
623 self.writer.write_object(smask_obj_id, &smask_stream)?;
624 }
625
626 let mut entries: Vec<(&str, PdfObject)> = vec![
628 ("Type", PdfObject::name("XObject")),
629 ("Subtype", PdfObject::name("Image")),
630 ("Width", PdfObject::Integer(img.width as i64)),
631 ("Height", PdfObject::Integer(img.height as i64)),
632 ("ColorSpace", PdfObject::name(img.color_space.pdf_name())),
633 (
634 "BitsPerComponent",
635 PdfObject::Integer(img.bits_per_component as i64),
636 ),
637 ];
638
639 if let Some(smask_obj_id) = smask_id {
640 entries.push(("SMask", PdfObject::Reference(smask_obj_id)));
641 }
642
643 let image_obj = match img.format {
646 ImageFormat::Jpeg => {
647 entries.push(("Filter", PdfObject::name("DCTDecode")));
648 PdfObject::stream(entries, img.data.clone())
649 }
650 ImageFormat::Png => self.make_stream(entries, img.data.clone()),
651 };
652
653 self.writer.write_object(xobject_id, &image_obj)?;
654 self.written_images.insert(idx);
655 Ok(())
656 }
657
658 pub fn set_stroke_color(&mut self, color: Color) -> &mut Self {
664 let page = self
665 .current_page
666 .as_mut()
667 .expect("set_stroke_color called with no open page");
668 let ops = format!(
669 "{} {} {} RG\n",
670 format_coord(color.r),
671 format_coord(color.g),
672 format_coord(color.b),
673 );
674 page.content_ops.extend_from_slice(ops.as_bytes());
675 self
676 }
677
678 pub fn set_fill_color(&mut self, color: Color) -> &mut Self {
680 let page = self
681 .current_page
682 .as_mut()
683 .expect("set_fill_color called with no open page");
684 let ops = format!(
685 "{} {} {} rg\n",
686 format_coord(color.r),
687 format_coord(color.g),
688 format_coord(color.b),
689 );
690 page.content_ops.extend_from_slice(ops.as_bytes());
691 self
692 }
693
694 pub fn set_line_width(&mut self, width: f64) -> &mut Self {
696 let page = self
697 .current_page
698 .as_mut()
699 .expect("set_line_width called with no open page");
700 let ops = format!("{} w\n", format_coord(width));
701 page.content_ops.extend_from_slice(ops.as_bytes());
702 self
703 }
704
705 pub fn move_to(&mut self, x: f64, y: f64) -> &mut Self {
710 let y_pdf = self.transform_y(y);
711 let page = self
712 .current_page
713 .as_mut()
714 .expect("move_to called with no open page");
715 let ops = format!("{} {} m\n", format_coord(x), format_coord(y_pdf));
716 page.content_ops.extend_from_slice(ops.as_bytes());
717 self
718 }
719
720 pub fn line_to(&mut self, x: f64, y: f64) -> &mut Self {
725 let y_pdf = self.transform_y(y);
726 let page = self
727 .current_page
728 .as_mut()
729 .expect("line_to called with no open page");
730 let ops = format!("{} {} l\n", format_coord(x), format_coord(y_pdf));
731 page.content_ops.extend_from_slice(ops.as_bytes());
732 self
733 }
734
735 pub fn rect(&mut self, x: f64, y: f64, width: f64, height: f64) -> &mut Self {
740 let r = self.transform_rect(&Rect {
741 x,
742 y,
743 width,
744 height,
745 });
746 let page = self
747 .current_page
748 .as_mut()
749 .expect("rect called with no open page");
750 let ops = format!(
751 "{} {} {} {} re\n",
752 format_coord(r.x),
753 format_coord(r.y),
754 format_coord(r.width),
755 format_coord(r.height),
756 );
757 page.content_ops.extend_from_slice(ops.as_bytes());
758 self
759 }
760
761 pub fn curve_to(&mut self, x1: f64, y1: f64, x2: f64, y2: f64, x3: f64, y3: f64) -> &mut Self {
767 let y1_pdf = self.transform_y(y1);
768 let y2_pdf = self.transform_y(y2);
769 let y3_pdf = self.transform_y(y3);
770 let page = self
771 .current_page
772 .as_mut()
773 .expect("curve_to called with no open page");
774 let ops = format!(
775 "{} {} {} {} {} {} c\n",
776 format_coord(x1),
777 format_coord(y1_pdf),
778 format_coord(x2),
779 format_coord(y2_pdf),
780 format_coord(x3),
781 format_coord(y3_pdf),
782 );
783 page.content_ops.extend_from_slice(ops.as_bytes());
784 self
785 }
786
787 pub fn arc(&mut self, cx: f64, cy: f64, radius: f64, start: Angle, end: Angle) -> &mut Self {
799 let start_rad = start.to_radians();
800 let end_rad = end.to_radians();
801
802 let sx = cx + radius * start_rad.cos();
804 let sy = cy + radius * start_rad.sin();
805 self.move_to(sx, sy);
806
807 for (x1, y1, x2, y2, x3, y3) in arc_bezier_segments(cx, cy, radius, start_rad, end_rad) {
808 self.curve_to(x1, y1, x2, y2, x3, y3);
809 }
810 self
811 }
812
813 pub fn circle(&mut self, cx: f64, cy: f64, radius: f64) -> &mut Self {
821 self.arc(
822 cx,
823 cy,
824 radius,
825 Angle::radians(0.0),
826 Angle::radians(std::f64::consts::TAU),
827 )
828 .close_path()
829 }
830
831 pub fn close_path(&mut self) -> &mut Self {
833 let page = self
834 .current_page
835 .as_mut()
836 .expect("close_path called with no open page");
837 page.content_ops.extend_from_slice(b"h\n");
838 self
839 }
840
841 pub fn stroke(&mut self) -> &mut Self {
843 let page = self
844 .current_page
845 .as_mut()
846 .expect("stroke called with no open page");
847 page.content_ops.extend_from_slice(b"S\n");
848 self
849 }
850
851 pub fn fill(&mut self) -> &mut Self {
853 let page = self
854 .current_page
855 .as_mut()
856 .expect("fill called with no open page");
857 page.content_ops.extend_from_slice(b"f\n");
858 self
859 }
860
861 pub fn fill_stroke(&mut self) -> &mut Self {
863 let page = self
864 .current_page
865 .as_mut()
866 .expect("fill_stroke called with no open page");
867 page.content_ops.extend_from_slice(b"B\n");
868 self
869 }
870
871 pub fn save_state(&mut self) -> &mut Self {
873 let page = self
874 .current_page
875 .as_mut()
876 .expect("save_state called with no open page");
877 page.content_ops.extend_from_slice(b"q\n");
878 self
879 }
880
881 pub fn restore_state(&mut self) -> &mut Self {
883 let page = self
884 .current_page
885 .as_mut()
886 .expect("restore_state called with no open page");
887 page.content_ops.extend_from_slice(b"Q\n");
888 self
889 }
890
891 fn transform_y(&self, y: f64) -> f64 {
899 match self.origin {
900 Origin::BottomLeft => y,
901 Origin::TopLeft => {
902 let page_height = self
903 .current_page
904 .as_ref()
905 .expect("transform_y called with no open page")
906 .height;
907 page_height - y
908 }
909 }
910 }
911
912 fn transform_rect(&self, rect: &Rect) -> Rect {
920 match self.origin {
921 Origin::BottomLeft => *rect,
922 Origin::TopLeft => {
923 let page_height = self
924 .current_page
925 .as_ref()
926 .expect("transform_rect called with no open page")
927 .height;
928 Rect {
929 x: rect.x,
930 y: page_height - rect.y - rect.height,
931 width: rect.width,
932 height: rect.height,
933 }
934 }
935 }
936 }
937
938 fn transform_rect_top_edge(&self, rect: &Rect) -> Rect {
946 match self.origin {
947 Origin::BottomLeft => *rect,
948 Origin::TopLeft => {
949 let page_height = self
950 .current_page
951 .as_ref()
952 .expect("transform_rect_top_edge called with no open page")
953 .height;
954 Rect {
955 x: rect.x,
956 y: page_height - rect.y,
957 width: rect.width,
958 height: rect.height,
959 }
960 }
961 }
962 }
963
964 fn current_page_height(&self) -> f64 {
966 self.current_page.as_ref().expect("no open page").height
967 }
968
969 fn make_stream(&self, mut dict_entries: Vec<(&str, PdfObject)>, data: Vec<u8>) -> PdfObject {
971 if self.compress {
972 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
973 encoder.write_all(&data).expect("flate2 in-memory write");
974 let compressed = encoder.finish().expect("flate2 finish");
975 dict_entries.push(("Filter", PdfObject::name("FlateDecode")));
976 PdfObject::stream(dict_entries, compressed)
977 } else {
978 PdfObject::stream(dict_entries, data)
979 }
980 }
981
982 fn ensure_font_written(&mut self, font: BuiltinFont) -> io::Result<ObjId> {
984 if let Some(&id) = self.font_obj_ids.get(&font) {
985 return Ok(id);
986 }
987 let id = ObjId(self.next_obj_num, 0);
988 self.next_obj_num += 1;
989 let obj = PdfObject::dict(vec![
990 ("Type", PdfObject::name("Font")),
991 ("Subtype", PdfObject::name("Type1")),
992 ("BaseFont", PdfObject::name(font.pdf_base_name())),
993 ]);
994 self.writer.write_object(id, &obj)?;
995 self.font_obj_ids.insert(font, id);
996 Ok(id)
997 }
998
999 fn ensure_tt_font_obj_ids(&mut self, idx: usize) -> &TrueTypeFontObjIds {
1001 if !self.truetype_font_obj_ids.contains_key(&idx) {
1002 let type0 = ObjId(self.next_obj_num, 0);
1003 self.next_obj_num += 1;
1004 let cid_font = ObjId(self.next_obj_num, 0);
1005 self.next_obj_num += 1;
1006 let descriptor = ObjId(self.next_obj_num, 0);
1007 self.next_obj_num += 1;
1008 let font_file = ObjId(self.next_obj_num, 0);
1009 self.next_obj_num += 1;
1010 let tounicode = ObjId(self.next_obj_num, 0);
1011 self.next_obj_num += 1;
1012 self.truetype_font_obj_ids.insert(
1013 idx,
1014 TrueTypeFontObjIds {
1015 type0,
1016 cid_font,
1017 descriptor,
1018 font_file,
1019 tounicode,
1020 },
1021 );
1022 }
1023 &self.truetype_font_obj_ids[&idx]
1024 }
1025
1026 pub fn end_page(&mut self) -> io::Result<()> {
1030 let page = self
1031 .current_page
1032 .take()
1033 .expect("end_page called with no open page");
1034
1035 for &font in &page.used_fonts {
1037 self.ensure_font_written(font)?;
1038 }
1039
1040 for &idx in &page.used_truetype_fonts {
1042 self.ensure_tt_font_obj_ids(idx);
1043 }
1044
1045 let used_images: Vec<usize> = page.used_images.iter().copied().collect();
1047 for idx in &used_images {
1048 self.write_image_xobject(*idx)?;
1049 }
1050
1051 let content_id = ObjId(self.next_obj_num, 0);
1052 self.next_obj_num += 1;
1053
1054 let content_stream = self.make_stream(vec![], page.content_ops);
1056 self.writer.write_object(content_id, &content_stream)?;
1057
1058 let field_records: Vec<FormFieldRecord> = page
1060 .fields
1061 .into_iter()
1062 .map(|def| {
1063 let obj_id = ObjId(self.next_obj_num, 0);
1064 self.next_obj_num += 1;
1065 FormFieldRecord {
1066 name: def.name,
1067 rect: def.rect,
1068 obj_id,
1069 }
1070 })
1071 .collect();
1072
1073 match page.overlay_for {
1074 None => {
1075 let page_id = ObjId(self.next_obj_num, 0);
1078 self.next_obj_num += 1;
1079
1080 self.page_records.push(PageRecord {
1081 obj_id: page_id,
1082 content_ids: vec![content_id],
1083 width: page.width,
1084 height: page.height,
1085 used_fonts: page.used_fonts,
1086 used_truetype_fonts: page.used_truetype_fonts,
1087 used_images: page.used_images,
1088 fields: field_records,
1089 });
1090 }
1091 Some(idx) => {
1092 let record = &mut self.page_records[idx];
1094 record.content_ids.push(content_id);
1095 record.used_fonts.extend(page.used_fonts);
1096 record.used_truetype_fonts.extend(page.used_truetype_fonts);
1097 record.used_images.extend(page.used_images);
1098 }
1100 }
1101
1102 Ok(())
1103 }
1104
1105 fn build_font_dict(&self, used_fonts: &[BuiltinFont], used_truetype: &[usize]) -> PdfObject {
1107 let mut entries: Vec<(String, PdfObject)> = used_fonts
1108 .iter()
1109 .map(|f| {
1110 (
1111 f.pdf_name().to_string(),
1112 PdfObject::Reference(self.font_obj_ids[f]),
1113 )
1114 })
1115 .collect();
1116
1117 for &idx in used_truetype {
1118 let name = self.truetype_fonts[idx].pdf_name.clone();
1119 let type0_id = self.truetype_font_obj_ids[&idx].type0;
1120 entries.push((name, PdfObject::Reference(type0_id)));
1121 }
1122
1123 PdfObject::Dictionary(entries)
1124 }
1125
1126 fn build_resource_dict(
1128 &self,
1129 used_fonts: &[BuiltinFont],
1130 used_truetype: &[usize],
1131 used_images: &[usize],
1132 ) -> PdfObject {
1133 let font_dict = self.build_font_dict(used_fonts, used_truetype);
1134
1135 let xobject_entries: Vec<(String, PdfObject)> = used_images
1136 .iter()
1137 .filter_map(|idx| {
1138 self.image_obj_ids
1139 .get(idx)
1140 .map(|ids| (ids.pdf_name.clone(), PdfObject::Reference(ids.xobject)))
1141 })
1142 .collect();
1143
1144 let mut resource_entries: Vec<(String, PdfObject)> = vec![("Font".to_string(), font_dict)];
1145 if !xobject_entries.is_empty() {
1146 resource_entries.push((
1147 "XObject".to_string(),
1148 PdfObject::Dictionary(xobject_entries),
1149 ));
1150 }
1151
1152 PdfObject::Dictionary(resource_entries)
1153 }
1154
1155 fn build_contents(content_ids: &[ObjId]) -> PdfObject {
1157 if content_ids.len() == 1 {
1158 PdfObject::Reference(content_ids[0])
1159 } else {
1160 PdfObject::array(
1161 content_ids
1162 .iter()
1163 .map(|id| PdfObject::Reference(*id))
1164 .collect(),
1165 )
1166 }
1167 }
1168
1169 fn write_widget_annotations(&mut self, page_idx: usize) -> io::Result<()> {
1171 let page_obj_id = self.page_records[page_idx].obj_id;
1172 let field_ids: Vec<(String, Rect, ObjId)> = self.page_records[page_idx]
1173 .fields
1174 .iter()
1175 .map(|f| (f.name.clone(), f.rect, f.obj_id))
1176 .collect();
1177
1178 for (name, rect, obj_id) in field_ids {
1179 let rect_array = PdfObject::array(vec![
1181 PdfObject::Real(rect.x),
1182 PdfObject::Real(rect.y),
1183 PdfObject::Real(rect.x + rect.width),
1184 PdfObject::Real(rect.y + rect.height),
1185 ]);
1186 let widget = PdfObject::dict(vec![
1187 ("Type", PdfObject::name("Annot")),
1188 ("Subtype", PdfObject::name("Widget")),
1189 ("FT", PdfObject::name("Tx")),
1190 ("T", PdfObject::literal_string(&name)),
1191 ("Rect", rect_array),
1192 ("P", PdfObject::Reference(page_obj_id)),
1193 ("F", PdfObject::Integer(4)), ]);
1195 self.writer.write_object(obj_id, &widget)?;
1196 }
1197 Ok(())
1198 }
1199
1200 fn write_page_dicts(&mut self) -> io::Result<()> {
1203 for i in 0..self.page_records.len() {
1204 self.write_widget_annotations(i)?;
1205
1206 let obj_id = self.page_records[i].obj_id;
1208 let content_ids: Vec<ObjId> =
1209 self.page_records[i].content_ids.iter().copied().collect();
1210 let width = self.page_records[i].width;
1211 let height = self.page_records[i].height;
1212 let used_fonts: Vec<BuiltinFont> =
1213 self.page_records[i].used_fonts.iter().copied().collect();
1214 let used_truetype: Vec<usize> = self.page_records[i]
1215 .used_truetype_fonts
1216 .iter()
1217 .copied()
1218 .collect();
1219 let used_images: Vec<usize> =
1220 self.page_records[i].used_images.iter().copied().collect();
1221 let annot_ids: Vec<ObjId> = self.page_records[i]
1222 .fields
1223 .iter()
1224 .map(|f| f.obj_id)
1225 .collect();
1226
1227 let resources = self.build_resource_dict(&used_fonts, &used_truetype, &used_images);
1228 let contents = Self::build_contents(&content_ids);
1229
1230 let mut page_entries = vec![
1231 ("Type", PdfObject::name("Page")),
1232 ("Parent", PdfObject::Reference(PAGES_OBJ)),
1233 (
1234 "MediaBox",
1235 PdfObject::array(vec![
1236 PdfObject::Integer(0),
1237 PdfObject::Integer(0),
1238 PdfObject::Real(width),
1239 PdfObject::Real(height),
1240 ]),
1241 ),
1242 ("Contents", contents),
1243 ("Resources", resources),
1244 ];
1245
1246 if !annot_ids.is_empty() {
1247 let annots = PdfObject::array(
1248 annot_ids
1249 .iter()
1250 .map(|id| PdfObject::Reference(*id))
1251 .collect(),
1252 );
1253 page_entries.push(("Annots", annots));
1254 }
1255
1256 let page_dict = PdfObject::dict(page_entries);
1257 self.writer.write_object(obj_id, &page_dict)?;
1258 }
1259 Ok(())
1260 }
1261
1262 fn collect_all_field_ids(&self) -> Vec<ObjId> {
1264 self.page_records
1265 .iter()
1266 .flat_map(|r| r.fields.iter().map(|f| f.obj_id))
1267 .collect()
1268 }
1269
1270 fn write_acroform(&mut self) -> io::Result<Option<ObjId>> {
1273 let all_field_ids = self.collect_all_field_ids();
1274 if all_field_ids.is_empty() {
1275 return Ok(None);
1276 }
1277
1278 let fields_array = PdfObject::array(
1279 all_field_ids
1280 .iter()
1281 .map(|id| PdfObject::Reference(*id))
1282 .collect(),
1283 );
1284
1285 let acroform_id = ObjId(self.next_obj_num, 0);
1286 self.next_obj_num += 1;
1287
1288 let acroform = PdfObject::dict(vec![
1289 ("Fields", fields_array),
1290 ("NeedAppearances", PdfObject::Boolean(true)),
1291 ("DA", PdfObject::literal_string("/Helv 12 Tf 0 g")),
1293 ]);
1294 self.writer.write_object(acroform_id, &acroform)?;
1295 Ok(Some(acroform_id))
1296 }
1297
1298 fn write_truetype_fonts(&mut self) -> io::Result<()> {
1301 let indices: Vec<usize> = self.truetype_font_obj_ids.keys().copied().collect();
1302
1303 for idx in indices {
1304 let obj_ids_type0 = self.truetype_font_obj_ids[&idx].type0;
1305 let obj_ids_cid = self.truetype_font_obj_ids[&idx].cid_font;
1306 let obj_ids_desc = self.truetype_font_obj_ids[&idx].descriptor;
1307 let obj_ids_file = self.truetype_font_obj_ids[&idx].font_file;
1308 let obj_ids_tounicode = self.truetype_font_obj_ids[&idx].tounicode;
1309
1310 let font = &self.truetype_fonts[idx];
1311
1312 let original_len = font.font_data.len() as i64;
1314 let font_file_stream = self.make_stream(
1315 vec![("Length1", PdfObject::Integer(original_len))],
1316 font.font_data.clone(),
1317 );
1318 self.writer.write_object(obj_ids_file, &font_file_stream)?;
1319
1320 let descriptor = PdfObject::dict(vec![
1322 ("Type", PdfObject::name("FontDescriptor")),
1323 ("FontName", PdfObject::name(&font.postscript_name)),
1324 ("Flags", PdfObject::Integer(font.flags as i64)),
1325 (
1326 "FontBBox",
1327 PdfObject::array(vec![
1328 PdfObject::Integer(font.scale_to_pdf(font.bbox[0])),
1329 PdfObject::Integer(font.scale_to_pdf(font.bbox[1])),
1330 PdfObject::Integer(font.scale_to_pdf(font.bbox[2])),
1331 PdfObject::Integer(font.scale_to_pdf(font.bbox[3])),
1332 ]),
1333 ),
1334 ("ItalicAngle", PdfObject::Real(font.italic_angle)),
1335 ("Ascent", PdfObject::Integer(font.scale_to_pdf(font.ascent))),
1336 (
1337 "Descent",
1338 PdfObject::Integer(font.scale_to_pdf(font.descent)),
1339 ),
1340 (
1341 "CapHeight",
1342 PdfObject::Integer(font.scale_to_pdf(font.cap_height)),
1343 ),
1344 ("StemV", PdfObject::Integer(font.scale_to_pdf(font.stem_v))),
1345 ("FontFile2", PdfObject::Reference(obj_ids_file)),
1346 ]);
1347 self.writer.write_object(obj_ids_desc, &descriptor)?;
1348
1349 let w_array = font.build_w_array();
1351 let cid_font = PdfObject::dict(vec![
1352 ("Type", PdfObject::name("Font")),
1353 ("Subtype", PdfObject::name("CIDFontType2")),
1354 ("BaseFont", PdfObject::name(&font.postscript_name)),
1355 (
1356 "CIDSystemInfo",
1357 PdfObject::dict(vec![
1358 ("Registry", PdfObject::literal_string("Adobe")),
1359 ("Ordering", PdfObject::literal_string("Identity")),
1360 ("Supplement", PdfObject::Integer(0)),
1361 ]),
1362 ),
1363 ("FontDescriptor", PdfObject::Reference(obj_ids_desc)),
1364 ("DW", PdfObject::Integer(font.default_width_pdf())),
1365 ("W", PdfObject::Array(w_array)),
1366 ]);
1367 self.writer.write_object(obj_ids_cid, &cid_font)?;
1368
1369 let tounicode_data = font.build_tounicode_cmap();
1371 let tounicode = self.make_stream(vec![], tounicode_data);
1372 self.writer.write_object(obj_ids_tounicode, &tounicode)?;
1373
1374 let type0 = PdfObject::dict(vec![
1376 ("Type", PdfObject::name("Font")),
1377 ("Subtype", PdfObject::name("Type0")),
1378 ("BaseFont", PdfObject::name(&font.postscript_name)),
1379 ("Encoding", PdfObject::name("Identity-H")),
1380 (
1381 "DescendantFonts",
1382 PdfObject::array(vec![PdfObject::Reference(obj_ids_cid)]),
1383 ),
1384 ("ToUnicode", PdfObject::Reference(obj_ids_tounicode)),
1385 ]);
1386 self.writer.write_object(obj_ids_type0, &type0)?;
1387 }
1388
1389 Ok(())
1390 }
1391
1392 pub fn end_document(mut self) -> io::Result<W> {
1396 if self.current_page.is_some() {
1398 self.end_page()?;
1399 }
1400
1401 self.write_page_dicts()?;
1403
1404 self.write_truetype_fonts()?;
1406
1407 let acroform_id = self.write_acroform()?;
1409
1410 let info_id = if !self.info.is_empty() {
1412 let id = ObjId(self.next_obj_num, 0);
1413 self.next_obj_num += 1;
1414 let entries: Vec<(&str, PdfObject)> = self
1415 .info
1416 .iter()
1417 .map(|(k, v)| (k.as_str(), PdfObject::literal_string(v)))
1418 .collect();
1419 let info_obj = PdfObject::dict(entries);
1420 self.writer.write_object(id, &info_obj)?;
1421 Some(id)
1422 } else {
1423 None
1424 };
1425
1426 let kids: Vec<PdfObject> = self
1428 .page_records
1429 .iter()
1430 .map(|r| PdfObject::Reference(r.obj_id))
1431 .collect();
1432 let page_count = self.page_records.len() as i64;
1433 let pages = PdfObject::dict(vec![
1434 ("Type", PdfObject::name("Pages")),
1435 ("Kids", PdfObject::Array(kids)),
1436 ("Count", PdfObject::Integer(page_count)),
1437 ]);
1438 self.writer.write_object(PAGES_OBJ, &pages)?;
1439
1440 let mut catalog_entries = vec![
1442 ("Type", PdfObject::name("Catalog")),
1443 ("Pages", PdfObject::Reference(PAGES_OBJ)),
1444 ];
1445 if let Some(acroform) = acroform_id {
1446 catalog_entries.push(("AcroForm", PdfObject::Reference(acroform)));
1447 }
1448 let catalog = PdfObject::dict(catalog_entries);
1449 self.writer.write_object(CATALOG_OBJ, &catalog)?;
1450
1451 self.writer.write_xref_and_trailer(CATALOG_OBJ, info_id)?;
1453
1454 Ok(self.writer.into_inner())
1455 }
1456}
1457
1458fn arc_bezier_segments(
1467 cx: f64,
1468 cy: f64,
1469 radius: f64,
1470 start_rad: f64,
1471 end_rad: f64,
1472) -> Vec<(f64, f64, f64, f64, f64, f64)> {
1473 const MAX_SEGMENT: f64 = std::f64::consts::FRAC_PI_2; let mut segments = Vec::new();
1476 let total = end_rad - start_rad;
1477 let n = (total.abs() / MAX_SEGMENT).ceil().max(1.0) as u32;
1478 let step = total / n as f64;
1479
1480 for i in 0..n {
1481 let a = start_rad + i as f64 * step;
1482 let b = a + step;
1483 let k = 4.0 / 3.0 * ((b - a) / 4.0).tan();
1484
1485 let (cos_a, sin_a) = (a.cos(), a.sin());
1486 let (cos_b, sin_b) = (b.cos(), b.sin());
1487
1488 let x1 = cx + radius * (cos_a - k * sin_a);
1489 let y1 = cy + radius * (sin_a + k * cos_a);
1490 let x2 = cx + radius * (cos_b + k * sin_b);
1491 let y2 = cy + radius * (sin_b - k * cos_b);
1492 let x3 = cx + radius * cos_b;
1493 let y3 = cy + radius * sin_b;
1494
1495 segments.push((x1, y1, x2, y2, x3, y3));
1496 }
1497 segments
1498}
1499
1500pub(crate) fn format_coord(v: f64) -> String {
1502 if v == v.floor() && v.abs() < 1e15 {
1503 format!("{}", v as i64)
1504 } else {
1505 let s = format!("{:.4}", v);
1506 let s = s.trim_end_matches('0');
1507 let s = s.trim_end_matches('.');
1508 s.to_string()
1509 }
1510}