1#![cfg(feature = "write")]
2use lopdf::{dictionary, Object, ObjectId, Stream};
14use std::collections::HashSet;
15
16fn unique_xobject_name(xobjects: &lopdf::Dictionary, next: &mut usize) -> String {
22 loop {
23 let candidate = format!("FlatAnnot{}", *next);
24 *next += 1;
25 if !xobjects.has(candidate.as_bytes()) {
26 return candidate;
27 }
28 }
29}
30
31fn as_number(obj: &Object) -> Option<f64> {
33 match obj {
34 Object::Integer(i) => Some(*i as f64),
35 Object::Real(f) => Some(*f as f64),
36 _ => None,
37 }
38}
39
40fn resolve_inherited_resources_clone(
42 doc: &lopdf::Document,
43 page_id: ObjectId,
44) -> Option<lopdf::Dictionary> {
45 let mut current = page_id;
46 let mut visited = HashSet::new();
47 loop {
48 if !visited.insert(current) {
49 return None;
50 }
51 let dict = doc.get_dictionary(current).ok()?;
52 if let Ok(res_obj) = dict.get(b"Resources") {
53 return match res_obj {
54 Object::Dictionary(d) => Some(d.clone()),
55 Object::Reference(id) => doc.get_dictionary(*id).ok().cloned(),
56 _ => None,
57 };
58 }
59 match dict.get(b"Parent") {
60 Ok(Object::Reference(id)) => current = *id,
61 _ => return None,
62 }
63 }
64}
65
66fn resolve_content_streams(doc: &lopdf::Document, page_id: ObjectId) -> Vec<ObjectId> {
68 let page_obj = match doc.get_object(page_id) {
69 Ok(obj) => obj,
70 Err(_) => return Vec::new(),
71 };
72 let page_dict = match page_obj {
73 Object::Dictionary(ref d) => d,
74 _ => return Vec::new(),
75 };
76 match page_dict.get(b"Contents").ok() {
77 Some(c) => flatten_content_refs(doc, c),
78 None => Vec::new(),
79 }
80}
81
82fn flatten_content_refs(doc: &lopdf::Document, obj: &Object) -> Vec<ObjectId> {
83 match obj {
84 Object::Reference(id) => {
85 if let Ok(Object::Array(arr)) = doc.get_object(*id) {
86 return arr
87 .iter()
88 .flat_map(|o| flatten_content_refs(doc, o))
89 .collect();
90 }
91 vec![*id]
92 }
93 Object::Array(arr) => arr
94 .iter()
95 .flat_map(|o| flatten_content_refs(doc, o))
96 .collect(),
97 _ => Vec::new(),
98 }
99}
100
101fn wrap_existing_content_in_save_restore(doc: &mut lopdf::Document, page_id: ObjectId) {
103 let existing = resolve_content_streams(doc, page_id);
104 if existing.is_empty() {
105 return;
106 }
107
108 let q_stream = Stream::new(dictionary! {}, b"q\n".to_vec());
109 let q_id = doc.add_object(Object::Stream(q_stream));
110
111 let big_q_stream = Stream::new(dictionary! {}, b"\nQ\n".to_vec());
112 let big_q_id = doc.add_object(Object::Stream(big_q_stream));
113
114 let mut new_arr = Vec::with_capacity(existing.len() + 2);
116 new_arr.push(Object::Reference(q_id));
117 for id in existing {
118 new_arr.push(Object::Reference(id));
119 }
120 new_arr.push(Object::Reference(big_q_id));
121 let wrapped = Object::Array(new_arr);
122
123 if let Ok(Object::Dictionary(ref mut d)) = doc.get_object_mut(page_id) {
124 d.set("Contents", wrapped);
125 }
126}
127
128fn append_content_to_page(doc: &mut lopdf::Document, page_id: ObjectId, content_id: ObjectId) {
130 let existing = resolve_content_streams(doc, page_id);
131 let new_contents = if existing.is_empty() {
132 Object::Reference(content_id)
133 } else {
134 let mut arr: Vec<Object> = existing.into_iter().map(Object::Reference).collect();
135 arr.push(Object::Reference(content_id));
136 Object::Array(arr)
137 };
138
139 if let Ok(Object::Dictionary(ref mut d)) = doc.get_object_mut(page_id) {
140 d.set("Contents", new_contents);
141 }
142}
143
144fn get_matrix_and_bbox(n_stream_dict: &lopdf::Dictionary, rect: &[f64; 4]) -> Option<[f64; 6]> {
146 let bbox = n_stream_dict.get(b"BBox").ok()?;
147 let bbox_arr = match bbox {
148 Object::Array(ref arr) if arr.len() >= 4 => arr,
149 _ => return None,
150 };
151 let bx0 = as_number(&bbox_arr[0])?;
152 let by0 = as_number(&bbox_arr[1])?;
153 let bx1 = as_number(&bbox_arr[2])?;
154 let by1 = as_number(&bbox_arr[3])?;
155
156 let bw = bx1 - bx0;
157 let bh = by1 - by0;
158 if bw == 0.0 || bh == 0.0 {
159 return None;
160 }
161
162 let rx0 = rect[0];
163 let ry0 = rect[1];
164 let rx1 = rect[2];
165 let ry1 = rect[3];
166 let rw = rx1 - rx0;
167 let rh = ry1 - ry0;
168
169 let matrix_arr = n_stream_dict.get(b"Matrix").ok().and_then(|m| match m {
171 Object::Array(ref arr) if arr.len() >= 6 => Some(arr),
172 _ => None,
173 });
174
175 if let Some(m) = matrix_arr {
176 let ma = as_number(&m[0]).unwrap_or(1.0);
177 let mb = as_number(&m[1]).unwrap_or(0.0);
178 let mc = as_number(&m[2]).unwrap_or(0.0);
179 let md = as_number(&m[3]).unwrap_or(1.0);
180 let me = as_number(&m[4]).unwrap_or(0.0);
181 let mf = as_number(&m[5]).unwrap_or(0.0);
182
183 let corners = [(bx0, by0), (bx1, by0), (bx0, by1), (bx1, by1)];
184 let mut t_corners = [(0.0, 0.0); 4];
185 for (i, &(x, y)) in corners.iter().enumerate() {
186 t_corners[i] = (ma * x + mc * y + me, mb * x + md * y + mf);
187 }
188 let t_bx0 = t_corners.iter().map(|p| p.0).fold(f64::INFINITY, f64::min);
189 let t_by0 = t_corners.iter().map(|p| p.1).fold(f64::INFINITY, f64::min);
190 let t_bx1 = t_corners
191 .iter()
192 .map(|p| p.0)
193 .fold(f64::NEG_INFINITY, f64::max);
194 let t_by1 = t_corners
195 .iter()
196 .map(|p| p.1)
197 .fold(f64::NEG_INFINITY, f64::max);
198
199 let t_bw = t_bx1 - t_bx0;
200 let t_bh = t_by1 - t_by0;
201 if t_bw == 0.0 || t_bh == 0.0 {
202 return None;
203 }
204
205 let sx = rw / t_bw;
206 let sy = rh / t_bh;
207 let tx = rx0 - t_bx0 * sx;
208 let ty = ry0 - t_by0 * sy;
209
210 Some([
211 ma * sx,
212 mb * sy,
213 mc * sx,
214 md * sy,
215 me * sx + tx,
216 mf * sy + ty,
217 ])
218 } else {
219 let sx = rw / bw;
220 let sy = rh / bh;
221 let tx = rx0 - bx0 * sx;
222 let ty = ry0 - by0 * sy;
223 Some([sx, 0.0, 0.0, sy, tx, ty])
224 }
225}
226
227pub fn flatten_annotations(doc: &mut lopdf::Document) -> Result<(), crate::error::AnnotBuildError> {
237 let page_ids: Vec<ObjectId> = doc.get_pages().values().copied().collect();
238 let mut deleted_objects = HashSet::new();
239 let mut popups_to_remove: HashSet<ObjectId> = HashSet::new();
242
243 for page_id in page_ids {
244 let page_dict = match doc.get_object(page_id) {
246 Ok(Object::Dictionary(ref d)) => d.clone(),
247 _ => continue,
248 };
249
250 let annots_arr = match page_dict.get(b"Annots") {
251 Ok(Object::Array(ref arr)) => arr.clone(),
252 Ok(Object::Reference(id)) => match doc.get_object(*id) {
253 Ok(Object::Array(ref arr)) => arr.clone(),
254 _ => continue,
255 },
256 _ => continue,
257 };
258
259 if annots_arr.is_empty() {
260 continue;
261 }
262
263 let mut remaining_annots = Vec::new();
264 let mut draw_ops = Vec::new();
265
266 let mut resources = if let Ok(res_obj) = page_dict.get(b"Resources") {
267 match res_obj {
268 Object::Dictionary(ref d) => d.clone(),
269 Object::Reference(id) => match doc.get_object(*id) {
270 Ok(Object::Dictionary(ref d)) => d.clone(),
271 _ => lopdf::Dictionary::new(),
272 },
273 _ => lopdf::Dictionary::new(),
274 }
275 } else {
276 resolve_inherited_resources_clone(doc, page_id).unwrap_or_default()
277 };
278
279 let mut xobjects = match resources.get(b"XObject") {
280 Ok(Object::Dictionary(ref d)) => d.clone(),
281 Ok(Object::Reference(id)) => match doc.get_object(*id) {
282 Ok(Object::Dictionary(ref d)) => d.clone(),
283 _ => lopdf::Dictionary::new(),
284 },
285 _ => lopdf::Dictionary::new(),
286 };
287
288 let mut flat_annot_count = 0;
289
290 for annot_obj in &annots_arr {
291 let annot_id = match annot_obj {
292 Object::Reference(id) => *id,
293 _ => {
294 remaining_annots.push(annot_obj.clone());
295 continue;
296 }
297 };
298
299 let annot_dict = match doc.get_object(annot_id) {
300 Ok(Object::Dictionary(ref d)) => d.clone(),
301 _ => {
302 remaining_annots.push(annot_obj.clone());
303 continue;
304 }
305 };
306
307 let subtype = match annot_dict.get(b"Subtype") {
308 Ok(Object::Name(ref name)) => name.as_slice(),
309 _ => {
310 remaining_annots.push(annot_obj.clone());
311 continue;
312 }
313 };
314
315 if subtype == b"Widget" || subtype == b"Link" {
317 remaining_annots.push(annot_obj.clone());
318 continue;
319 }
320
321 let ap = match annot_dict.get(b"AP") {
323 Ok(Object::Dictionary(ref d)) => d.clone(),
324 Ok(Object::Reference(id)) => match doc.get_object(*id) {
325 Ok(Object::Dictionary(ref d)) => d.clone(),
326 _ => {
327 remaining_annots.push(annot_obj.clone());
328 continue;
329 }
330 },
331 _ => {
332 remaining_annots.push(annot_obj.clone());
333 continue;
334 }
335 };
336
337 let n_obj = match ap.get(b"N") {
338 Ok(obj) => obj.clone(),
339 _ => {
340 remaining_annots.push(annot_obj.clone());
341 continue;
342 }
343 };
344
345 let (n_stream_id, n_stream) = match n_obj {
346 Object::Reference(id) => match doc.get_object(id) {
347 Ok(Object::Stream(ref s)) => (Some(id), s.clone()),
348 _ => {
349 remaining_annots.push(annot_obj.clone());
350 continue;
351 }
352 },
353 Object::Stream(ref s) => (None, s.clone()),
354 _ => {
355 remaining_annots.push(annot_obj.clone());
356 continue;
357 }
358 };
359
360 let rect_obj = match annot_dict.get(b"Rect") {
362 Ok(Object::Array(ref arr)) if arr.len() >= 4 => arr,
363 _ => {
364 remaining_annots.push(annot_obj.clone());
365 continue;
366 }
367 };
368 let rx0 = match as_number(&rect_obj[0]) {
369 Some(v) => v,
370 None => {
371 remaining_annots.push(annot_obj.clone());
372 continue;
373 }
374 };
375 let ry0 = match as_number(&rect_obj[1]) {
376 Some(v) => v,
377 None => {
378 remaining_annots.push(annot_obj.clone());
379 continue;
380 }
381 };
382 let rx1 = match as_number(&rect_obj[2]) {
383 Some(v) => v,
384 None => {
385 remaining_annots.push(annot_obj.clone());
386 continue;
387 }
388 };
389 let ry1 = match as_number(&rect_obj[3]) {
390 Some(v) => v,
391 None => {
392 remaining_annots.push(annot_obj.clone());
393 continue;
394 }
395 };
396 let rect = [rx0, ry0, rx1, ry1];
397
398 let matrix = match get_matrix_and_bbox(&n_stream.dict, &rect) {
400 Some(m) => m,
401 None => {
402 remaining_annots.push(annot_obj.clone());
403 continue;
404 }
405 };
406
407 let stream_id = match n_stream_id {
409 Some(id) => {
410 if let Ok(Object::Stream(ref mut s)) = doc.get_object_mut(id) {
411 s.dict.set("Type", Object::Name(b"XObject".to_vec()));
412 s.dict.set("Subtype", Object::Name(b"Form".to_vec()));
413 }
414 id
415 }
416 None => {
417 let mut s = n_stream.clone();
418 s.dict.set("Type", Object::Name(b"XObject".to_vec()));
419 s.dict.set("Subtype", Object::Name(b"Form".to_vec()));
420 doc.add_object(Object::Stream(s))
421 }
422 };
423
424 let ap_name = unique_xobject_name(&xobjects, &mut flat_annot_count);
428 xobjects.set(ap_name.as_bytes().to_vec(), Object::Reference(stream_id));
429
430 let draw_op = format!(
432 "q\n{} {} {} {} {} {} cm\n/{} Do\nQ\n",
433 matrix[0], matrix[1], matrix[2], matrix[3], matrix[4], matrix[5], ap_name
434 );
435 draw_ops.extend_from_slice(draw_op.as_bytes());
436
437 deleted_objects.insert(annot_id);
438 if let Ok(Object::Reference(popup_id)) = annot_dict.get(b"Popup") {
441 popups_to_remove.insert(*popup_id);
442 }
443 }
444
445 if !draw_ops.is_empty() {
446 resources.set("XObject", Object::Dictionary(xobjects));
448
449 if let Ok(Object::Dictionary(ref mut pd)) = doc.get_object_mut(page_id) {
450 pd.set("Resources", Object::Dictionary(resources));
451 }
452
453 wrap_existing_content_in_save_restore(doc, page_id);
455
456 let new_content_stream = Stream::new(dictionary! {}, draw_ops);
458 let new_content_id = doc.add_object(Object::Stream(new_content_stream));
459 append_content_to_page(doc, page_id, new_content_id);
460
461 remaining_annots.retain(|o| match o {
464 Object::Reference(id) => !popups_to_remove.contains(id),
465 _ => true,
466 });
467
468 if let Ok(Object::Dictionary(ref mut pd)) = doc.get_object_mut(page_id) {
470 if remaining_annots.is_empty() {
471 pd.remove(b"Annots");
472 } else {
473 pd.set("Annots", Object::Array(remaining_annots));
474 }
475 }
476 }
477 }
478
479 for id in deleted_objects.into_iter().chain(popups_to_remove) {
486 doc.delete_object(id);
487 }
488
489 Ok(())
490}
491
492#[cfg(test)]
493mod tests {
494 use super::unique_xobject_name;
495 use lopdf::{Dictionary, Object};
496
497 #[test]
498 fn unique_xobject_name_skips_existing_entries() {
499 let mut xobjects = Dictionary::new();
500 xobjects.set("FlatAnnot0", Object::Null);
501 xobjects.set("FlatAnnot1", Object::Null);
502
503 let mut next = 0usize;
505 assert_eq!(unique_xobject_name(&xobjects, &mut next), "FlatAnnot2");
506
507 xobjects.set("FlatAnnot2", Object::Null);
509 assert_eq!(unique_xobject_name(&xobjects, &mut next), "FlatAnnot3");
510 }
511
512 #[test]
513 fn unique_xobject_name_empty_dict_starts_at_zero() {
514 let xobjects = Dictionary::new();
515 let mut next = 0usize;
516 assert_eq!(unique_xobject_name(&xobjects, &mut next), "FlatAnnot0");
517 assert_eq!(unique_xobject_name(&xobjects, &mut next), "FlatAnnot1");
518 }
519}