rust_pdf_box/sign/
visual.rs1use crate::content::color::Color;
2use crate::content::stream::{AppendMode, PDPageContentStream};
3use crate::cos::object::{COSDictionary, COSName, COSStream, ObjectId};
4use crate::error::PdfResult;
5use crate::font::type1::PDType1Font;
6use crate::image::PDImageXObject;
7use crate::pd::rectangle::PDRectangle;
8
9#[derive(Debug, Clone)]
11pub struct VisualSignature {
12 pub page_number: usize, pub rect: PDRectangle,
14 pub title: String,
15 pub details: String,
16 pub seal_image: Option<PDImageXObject>,
17 pub border_color: Color,
18 pub background_color: Option<Color>,
19 pub text_color: Color,
20}
21
22impl VisualSignature {
23 pub fn at_page(page_number: usize) -> Self {
25 Self {
26 page_number,
27 rect: PDRectangle::new(72.0, 72.0, 272.0, 152.0),
28 title: "Digitally Signed".to_string(),
29 details: "Verified Digital Signature".to_string(),
30 seal_image: None,
31 border_color: Color::rgb(0.0, 0.4, 0.8),
32 background_color: Some(Color::rgb(0.96, 0.98, 1.0)),
33 text_color: Color::rgb(0.1, 0.1, 0.2),
34 }
35 }
36
37 pub fn rect(mut self, llx: f64, lly: f64, urx: f64, ury: f64) -> Self {
38 self.rect = PDRectangle::new(llx, lly, urx, ury);
39 self
40 }
41
42 pub fn title(mut self, title: impl Into<String>) -> Self {
43 self.title = title.into();
44 self
45 }
46
47 pub fn detail(mut self, details: impl Into<String>) -> Self {
48 self.details = details.into();
49 self
50 }
51
52 pub fn seal(mut self, image: PDImageXObject) -> Self {
53 self.seal_image = Some(image);
54 self
55 }
56
57 pub fn border_color(mut self, color: Color) -> Self {
58 self.border_color = color;
59 self
60 }
61
62 pub fn background_color(mut self, color: Color) -> Self {
63 self.background_color = Some(color);
64 self
65 }
66
67 pub fn build_appearance_stream(
69 &self,
70 cos_doc: &mut crate::cos::document::COSDocument,
71 ) -> PdfResult<ObjectId> {
72 let w = self.rect.width();
73 let h = self.rect.height();
74
75 let mut stream_content = PDPageContentStream::new(AppendMode::Overwrite);
76 stream_content.save_graphics_state();
77
78 if let Some(bg) = self.background_color {
80 stream_content.set_fill_color(bg);
81 stream_content.round_rect(1.0, 1.0, w - 2.0, h - 2.0, 4.0);
82 stream_content.fill();
83 }
84
85 stream_content.set_stroke_color(self.border_color);
87 stream_content.set_line_width(1.5);
88 stream_content.round_rect(1.0, 1.0, w - 2.0, h - 2.0, 4.0);
89 stream_content.stroke();
90
91 let text_start_x = if let Some(ref img) = self.seal_image {
93 let img_size = (h - 16.0).min(50.0);
94 let img_name = "ImSig";
95 let mut resources = COSDictionary::new();
96 let mut xobj_dict = COSDictionary::new();
97 xobj_dict.set(COSName::new(img_name), img.image_id);
98 resources.set("XObject", xobj_dict);
99
100 stream_content.draw_image(img_name, 8.0, (h - img_size) / 2.0, img_size, img_size);
101 img_size + 16.0
102 } else {
103 10.0
104 };
105
106 let font_title = PDType1Font::HelveticaBold;
108 let font_body = PDType1Font::Helvetica;
109
110 let font_title_id = cos_doc.add_object(font_title.to_dict());
112 let font_body_id = cos_doc.add_object(font_body.to_dict());
113
114 let mut font_dict = COSDictionary::new();
115 font_dict.set(COSName::new("FTitle"), font_title_id);
116 font_dict.set(COSName::new("FBody"), font_body_id);
117
118 let mut res_dict = COSDictionary::new();
119 res_dict.set("Font", font_dict);
120
121 if let Some(ref img) = self.seal_image {
122 let mut xobj_dict = COSDictionary::new();
123 xobj_dict.set(COSName::new("ImSig"), img.image_id);
124 res_dict.set("XObject", xobj_dict);
125 }
126
127 stream_content.begin_text();
129 stream_content.set_font("FTitle", 11.0);
130 stream_content.set_fill_color(self.text_color);
131 stream_content.show_text_at(&self.title, text_start_x, h - 18.0);
132 stream_content.end_text();
133
134 stream_content.begin_text();
136 stream_content.set_font("FBody", 8.0);
137 stream_content.set_fill_color(self.text_color);
138
139 let lines: Vec<&str> = self.details.lines().collect();
140 stream_content.show_text_multiline(&lines, text_start_x, h - 30.0, 10.0);
141 stream_content.end_text();
142
143 stream_content.restore_graphics_state();
144
145 let mut form_dict = COSDictionary::new();
147 form_dict.set("Type", COSName::new("XObject"));
148 form_dict.set("Subtype", COSName::new("Form"));
149 form_dict.set("BBox", PDRectangle::new(0.0, 0.0, w, h));
150 form_dict.set("Resources", res_dict);
151
152 let raw_bytes = stream_content.finish_to_bytes();
153 let mut cos_stream = COSStream::new(form_dict, raw_bytes);
154 let _ = cos_stream.encode_flate();
155
156 let stream_id = cos_doc.add_object(cos_stream);
157 Ok(stream_id)
158 }
159}