1use rpdfium_core::Matrix;
7use rpdfium_doc::Annotation;
8use rpdfium_graphics::Bitmap;
9use rpdfium_page::display::{DisplayTree, walk};
10
11use crate::cfx_defaultrenderdevice::TinySkiaBackend;
12use crate::error::RenderError;
13use crate::image::ImageDecoder;
14use crate::page_transform::compute_page_transform;
15use crate::render_defines::RenderConfig;
16use crate::renderdevicedriver_iface::RenderBackend;
17use crate::renderer::DisplayRenderer;
18
19#[derive(Debug, Clone)]
25pub struct AnnotationOverlay {
26 pub annotation: Annotation,
28 pub display_tree: DisplayTree,
30}
31
32pub fn render_with_annotations(
38 tree: &DisplayTree,
39 annotations: &[AnnotationOverlay],
40 config: &RenderConfig,
41 decoder: Option<&dyn ImageDecoder>,
42) -> Result<Bitmap, RenderError> {
43 let mut backend = TinySkiaBackend::new();
44
45 let effective_bg = config
47 .forced_color_scheme
48 .as_ref()
49 .map_or(&config.background, |s| &s.background_color);
50
51 let mut surface = backend.create_surface(config.width, config.height, effective_bg);
52
53 let page_transform = match config.media_box {
54 Some(ref mb) => compute_page_transform(mb, config.width, config.height, config.rotation),
55 None => Matrix::identity(),
56 };
57
58 {
60 let mut renderer =
61 DisplayRenderer::new(&mut backend, &mut surface, page_transform, decoder)
62 .with_per_feature_aa(
63 config.text_antialiasing,
64 config.path_antialiasing,
65 config.image_antialiasing,
66 );
67 if let Some(ref scheme) = config.forced_color_scheme {
68 renderer = renderer.with_forced_color_scheme(scheme.clone());
69 }
70 walk(tree, &mut renderer);
71 }
72
73 for overlay in annotations {
75 if !should_render_annotation(&overlay.annotation) {
76 continue;
77 }
78
79 let rect = &overlay.annotation.rect;
81 let annot_transform = Matrix::from_translation(rect[0] as f64, rect[1] as f64);
82 let combined = page_transform.pre_concat(&annot_transform);
83
84 let mut renderer = DisplayRenderer::new(&mut backend, &mut surface, combined, decoder)
87 .with_per_feature_aa(
88 config.text_antialiasing,
89 config.path_antialiasing,
90 config.image_antialiasing,
91 );
92 if let Some(ref scheme) = config.forced_color_scheme {
93 renderer = renderer.with_forced_color_scheme(scheme.clone());
94 }
95 walk(&overlay.display_tree, &mut renderer);
96 }
97
98 Ok(backend.finish(surface))
99}
100
101fn should_render_annotation(annotation: &Annotation) -> bool {
108 annotation.flags.is_visible()
109}
110
111#[cfg(test)]
112mod tests {
113 use super::*;
114 use rpdfium_doc::{AnnotationFlags, AnnotationSubtypeData, AnnotationType};
115 use rpdfium_graphics::BlendMode;
116 use rpdfium_page::display::DisplayNode;
117
118 use crate::color_convert::RgbaColor;
119
120 fn make_empty_tree() -> DisplayTree {
121 DisplayTree {
122 root: DisplayNode::Group {
123 blend_mode: BlendMode::Normal,
124 clip: None,
125 opacity: 1.0,
126 isolated: false,
127 knockout: false,
128 soft_mask: None,
129 children: Vec::new(),
130 },
131 }
132 }
133
134 fn make_test_annotation(flags: u32) -> Annotation {
135 Annotation {
136 subtype: AnnotationType::Text,
137 rect: [10.0, 10.0, 50.0, 50.0],
138 contents: None,
139 flags: AnnotationFlags::from_bits(flags),
140 name: None,
141 appearance: None,
142 color: None,
143 border: None,
144 action: None,
145 destination: None,
146 subtype_data: AnnotationSubtypeData::default(),
147 mk: None,
148 file_spec: None,
149 parent_ref: None,
150 object_id: None,
151 open: None,
152 ap_n_bytes: None,
153 ap_r_bytes: None,
154 ap_d_bytes: None,
155 irt_ref: None,
156 field_name: None,
157 alternate_name: None,
158 field_value: None,
159 form_field_flags: None,
160 additional_actions: None,
161 form_field_type: None,
162 options: None,
163 }
164 }
165
166 #[test]
167 fn test_render_annotations_empty_list() {
168 let tree = make_empty_tree();
169 let config = RenderConfig {
170 width: 100,
171 height: 100,
172 background: RgbaColor::WHITE,
173 ..RenderConfig::default()
174 };
175 let bitmap = render_with_annotations(&tree, &[], &config, None).unwrap();
176 assert_eq!(bitmap.width, 100);
177 assert_eq!(bitmap.height, 100);
178 }
179
180 #[test]
181 fn test_annotation_visibility_filtering() {
182 assert!(should_render_annotation(&make_test_annotation(0)));
184
185 assert!(!should_render_annotation(&make_test_annotation(2)));
187
188 assert!(!should_render_annotation(&make_test_annotation(32)));
190
191 assert!(should_render_annotation(&make_test_annotation(4)));
193
194 assert!(!should_render_annotation(&make_test_annotation(6)));
196 }
197
198 #[test]
199 fn test_render_with_visible_annotation_overlay() {
200 let tree = make_empty_tree();
201 let overlay = AnnotationOverlay {
202 annotation: make_test_annotation(0), display_tree: make_empty_tree(),
204 };
205 let config = RenderConfig {
206 width: 50,
207 height: 50,
208 background: RgbaColor::WHITE,
209 ..RenderConfig::default()
210 };
211 let bitmap = render_with_annotations(&tree, &[overlay], &config, None).unwrap();
212 assert_eq!(bitmap.width, 50);
213 assert_eq!(bitmap.height, 50);
214 }
215}