1use rpdfium_core::PdfSource;
13use rpdfium_parser::{Object, ObjectStore};
14
15use crate::annotation::{Annotation, parse_single_annotation};
16use crate::error::{DocError, DocResult};
17
18pub fn parse_annotations<S: PdfSource>(
23 annots_obj: &Object,
24 store: &ObjectStore<S>,
25) -> DocResult<Vec<Annotation>> {
26 let resolved = store
27 .deep_resolve(annots_obj)
28 .map_err(|e| DocError::Parser(e.to_string()))?;
29
30 let arr = resolved.as_array().ok_or(DocError::UnexpectedType)?;
31
32 let mut annotations = Vec::with_capacity(arr.len());
33 for item in arr {
34 let obj_id = item.as_reference();
36
37 let annot_obj = store
38 .deep_resolve(item)
39 .map_err(|e| DocError::Parser(e.to_string()))?;
40
41 match parse_single_annotation(annot_obj, store, obj_id) {
42 Ok(annot) => annotations.push(annot),
43 Err(_) => {
44 continue;
46 }
47 }
48 }
49
50 Ok(annotations)
51}
52
53pub fn find_parent_annotation(annotations: &[Annotation], popup: &Annotation) -> Option<usize> {
58 let parent_id = popup.parent_ref?;
59 annotations
60 .iter()
61 .position(|a| a.object_id == Some(parent_id))
62}
63
64pub fn annotation_at_point(annotations: &[Annotation], x: f32, y: f32) -> Option<usize> {
72 annotations.iter().enumerate().rev().find_map(|(i, ann)| {
73 let r = ann.rect;
74 let x_min = r[0].min(r[2]);
77 let x_max = r[0].max(r[2]);
78 let y_min = r[1].min(r[3]);
79 let y_max = r[1].max(r[3]);
80 if x >= x_min && x <= x_max && y >= y_min && y <= y_max {
81 Some(i)
82 } else {
83 None
84 }
85 })
86}
87
88#[inline]
92pub fn annot_get_form_field_at_point(annotations: &[Annotation], x: f32, y: f32) -> Option<usize> {
93 annotation_at_point(annotations, x, y)
94}
95
96#[deprecated(
98 note = "use `annot_get_form_field_at_point()` — matches upstream `FPDFAnnot_GetFormFieldAtPoint`"
99)]
100#[inline]
101pub fn get_annotation_at_point(annotations: &[Annotation], x: f32, y: f32) -> Option<usize> {
102 annotation_at_point(annotations, x, y)
103}
104
105#[deprecated(
107 note = "use `annot_get_form_field_at_point()` — matches upstream `FPDFAnnot_GetFormFieldAtPoint`"
108)]
109#[inline]
110pub fn get_form_field_at_point(annotations: &[Annotation], x: f32, y: f32) -> Option<usize> {
111 annotation_at_point(annotations, x, y)
112}
113
114#[cfg(test)]
115mod tests {
116 use super::*;
117 use crate::action::Action;
118 use crate::annotation::{Annotation, AnnotationFlags, AnnotationSubtypeData, AnnotationType};
119 use crate::destination::Destination;
120 use rpdfium_core::{Name, PdfString};
121 use rpdfium_parser::ObjectId;
122 use std::collections::HashMap;
123
124 fn build_store() -> ObjectStore<Vec<u8>> {
125 let pdf = build_minimal_pdf();
126 ObjectStore::open(pdf, rpdfium_core::ParsingMode::Lenient).unwrap()
127 }
128
129 fn build_minimal_pdf() -> Vec<u8> {
130 let mut pdf = Vec::new();
131 pdf.extend_from_slice(b"%PDF-1.4\n");
132 let obj1_offset = pdf.len();
133 pdf.extend_from_slice(b"1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n");
134 let obj2_offset = pdf.len();
135 pdf.extend_from_slice(b"2 0 obj\n<< /Type /Pages /Kids [] /Count 0 >>\nendobj\n");
136 let xref_offset = pdf.len();
137 pdf.extend_from_slice(b"xref\n0 3\n");
138 pdf.extend_from_slice(b"0000000000 65535 f \r\n");
139 pdf.extend_from_slice(format!("{:010} 00000 n \r\n", obj1_offset).as_bytes());
140 pdf.extend_from_slice(format!("{:010} 00000 n \r\n", obj2_offset).as_bytes());
141 pdf.extend_from_slice(b"trailer\n<< /Size 3 /Root 1 0 R >>\n");
142 pdf.extend_from_slice(format!("startxref\n{}\n%%EOF", xref_offset).as_bytes());
143 pdf
144 }
145
146 fn make_rect_array(x1: f64, y1: f64, x2: f64, y2: f64) -> Object {
147 Object::Array(vec![
148 Object::Real(x1),
149 Object::Real(y1),
150 Object::Real(x2),
151 Object::Real(y2),
152 ])
153 }
154
155 #[test]
156 fn test_parse_empty_annotations_array() {
157 let store = build_store();
158 let arr = Object::Array(vec![]);
159 let result = parse_annotations(&arr, &store).unwrap();
160 assert!(result.is_empty());
161 }
162
163 #[test]
164 fn test_parse_text_annotation() {
165 let store = build_store();
166 let mut dict = HashMap::new();
167 dict.insert(Name::subtype(), Object::Name(Name::from("Text")));
168 dict.insert(Name::rect(), make_rect_array(10.0, 20.0, 100.0, 50.0));
169 dict.insert(
170 Name::contents(),
171 Object::String(PdfString::from_bytes(b"A note".to_vec())),
172 );
173
174 let arr = Object::Array(vec![Object::Dictionary(dict)]);
175 let result = parse_annotations(&arr, &store).unwrap();
176 assert_eq!(result.len(), 1);
177 assert_eq!(result[0].subtype, AnnotationType::Text);
178 assert_eq!(result[0].rect, [10.0, 20.0, 100.0, 50.0]);
179 assert_eq!(result[0].contents.as_deref(), Some("A note"));
180 }
181
182 #[test]
183 fn test_parse_link_annotation_with_uri_action() {
184 let store = build_store();
185 let mut action_dict = HashMap::new();
186 action_dict.insert(Name::s(), Object::Name(Name::from("URI")));
187 action_dict.insert(
188 Name::uri(),
189 Object::String(PdfString::from_bytes(b"https://example.com".to_vec())),
190 );
191
192 let mut dict = HashMap::new();
193 dict.insert(Name::subtype(), Object::Name(Name::from("Link")));
194 dict.insert(Name::rect(), make_rect_array(0.0, 0.0, 200.0, 20.0));
195 dict.insert(Name::a(), Object::Dictionary(action_dict));
196
197 let arr = Object::Array(vec![Object::Dictionary(dict)]);
198 let result = parse_annotations(&arr, &store).unwrap();
199 assert_eq!(result.len(), 1);
200 assert_eq!(result[0].subtype, AnnotationType::Link);
201 match &result[0].action {
202 Some(Action::Uri(uri)) => assert_eq!(uri, "https://example.com"),
203 _ => panic!("expected URI action"),
204 }
205 }
206
207 #[test]
208 fn test_parse_highlight_annotation() {
209 let store = build_store();
210 let mut dict = HashMap::new();
211 dict.insert(Name::subtype(), Object::Name(Name::from("Highlight")));
212 dict.insert(Name::rect(), make_rect_array(50.0, 700.0, 200.0, 720.0));
213 dict.insert(
214 Name::c(),
215 Object::Array(vec![
216 Object::Real(1.0),
217 Object::Real(1.0),
218 Object::Real(0.0),
219 ]),
220 );
221
222 let arr = Object::Array(vec![Object::Dictionary(dict)]);
223 let result = parse_annotations(&arr, &store).unwrap();
224 assert_eq!(result.len(), 1);
225 assert_eq!(result[0].subtype, AnnotationType::Highlight);
226 assert_eq!(result[0].color.as_deref(), Some(&[1.0, 1.0, 0.0][..]));
227 }
228
229 #[test]
230 fn test_border_from_bs_dict() {
231 use crate::annotation::BorderStyle;
232 let store = build_store();
233 let mut bs_dict = HashMap::new();
234 bs_dict.insert(Name::w(), Object::Real(2.5));
235 bs_dict.insert(Name::s(), Object::Name(Name::from("D")));
236
237 let mut dict = HashMap::new();
238 dict.insert(Name::subtype(), Object::Name(Name::from("Text")));
239 dict.insert(Name::rect(), make_rect_array(0.0, 0.0, 100.0, 100.0));
240 dict.insert(Name::bs(), Object::Dictionary(bs_dict));
241
242 let arr = Object::Array(vec![Object::Dictionary(dict)]);
243 let result = parse_annotations(&arr, &store).unwrap();
244 let border = result[0].border.as_ref().unwrap();
245 assert_eq!(border.width, 2.5);
246 assert_eq!(border.style, BorderStyle::Dashed);
247 }
248
249 #[test]
250 fn test_border_from_border_array() {
251 use crate::annotation::BorderStyle;
252 let store = build_store();
253 let mut dict = HashMap::new();
254 dict.insert(Name::subtype(), Object::Name(Name::from("Link")));
255 dict.insert(Name::rect(), make_rect_array(0.0, 0.0, 100.0, 100.0));
256 dict.insert(
257 Name::border(),
258 Object::Array(vec![
259 Object::Integer(0),
260 Object::Integer(0),
261 Object::Real(3.0),
262 ]),
263 );
264
265 let arr = Object::Array(vec![Object::Dictionary(dict)]);
266 let result = parse_annotations(&arr, &store).unwrap();
267 let border = result[0].border.as_ref().unwrap();
268 assert_eq!(border.width, 3.0);
269 assert_eq!(border.style, BorderStyle::Solid);
270 }
271
272 #[test]
273 fn test_annotation_with_no_appearance() {
274 let store = build_store();
275 let mut dict = HashMap::new();
276 dict.insert(Name::subtype(), Object::Name(Name::from("Text")));
277 dict.insert(Name::rect(), make_rect_array(0.0, 0.0, 50.0, 50.0));
278
279 let arr = Object::Array(vec![Object::Dictionary(dict)]);
280 let result = parse_annotations(&arr, &store).unwrap();
281 assert!(result[0].appearance.is_none());
282 }
283
284 #[test]
285 fn test_rect_extraction() {
286 let store = build_store();
287 let mut dict = HashMap::new();
288 dict.insert(Name::subtype(), Object::Name(Name::from("Square")));
289 dict.insert(Name::rect(), make_rect_array(72.0, 144.0, 288.0, 432.0));
290
291 let arr = Object::Array(vec![Object::Dictionary(dict)]);
292 let result = parse_annotations(&arr, &store).unwrap();
293 assert_eq!(result[0].rect, [72.0, 144.0, 288.0, 432.0]);
294 }
295
296 #[test]
297 fn test_multiple_annotations() {
298 let store = build_store();
299
300 let mut dict1 = HashMap::new();
301 dict1.insert(Name::subtype(), Object::Name(Name::from("Text")));
302 dict1.insert(Name::rect(), make_rect_array(0.0, 0.0, 50.0, 50.0));
303
304 let mut dict2 = HashMap::new();
305 dict2.insert(Name::subtype(), Object::Name(Name::from("Link")));
306 dict2.insert(Name::rect(), make_rect_array(100.0, 100.0, 200.0, 200.0));
307
308 let mut dict3 = HashMap::new();
309 dict3.insert(Name::subtype(), Object::Name(Name::from("Highlight")));
310 dict3.insert(Name::rect(), make_rect_array(50.0, 50.0, 150.0, 60.0));
311
312 let arr = Object::Array(vec![
313 Object::Dictionary(dict1),
314 Object::Dictionary(dict2),
315 Object::Dictionary(dict3),
316 ]);
317 let result = parse_annotations(&arr, &store).unwrap();
318 assert_eq!(result.len(), 3);
319 assert_eq!(result[0].subtype, AnnotationType::Text);
320 assert_eq!(result[1].subtype, AnnotationType::Link);
321 assert_eq!(result[2].subtype, AnnotationType::Highlight);
322 }
323
324 #[test]
325 fn test_annotation_with_destination() {
326 let store = build_store();
327 let mut dict = HashMap::new();
328 dict.insert(Name::subtype(), Object::Name(Name::from("Link")));
329 dict.insert(Name::rect(), make_rect_array(0.0, 0.0, 100.0, 20.0));
330 dict.insert(
331 Name::dest(),
332 Object::String(PdfString::from_bytes(b"chapter1".to_vec())),
333 );
334
335 let arr = Object::Array(vec![Object::Dictionary(dict)]);
336 let result = parse_annotations(&arr, &store).unwrap();
337 match &result[0].destination {
338 Some(Destination::Named(name)) => assert_eq!(name, "chapter1"),
339 _ => panic!("expected named destination"),
340 }
341 }
342
343 #[test]
344 fn test_annotation_with_action() {
345 let store = build_store();
346 let mut action_dict = HashMap::new();
347 action_dict.insert(Name::s(), Object::Name(Name::from("Named")));
348 action_dict.insert(Name::n(), Object::Name(Name::from("NextPage")));
349
350 let mut dict = HashMap::new();
351 dict.insert(Name::subtype(), Object::Name(Name::from("Widget")));
352 dict.insert(Name::rect(), make_rect_array(0.0, 0.0, 50.0, 20.0));
353 dict.insert(Name::a(), Object::Dictionary(action_dict));
354
355 let arr = Object::Array(vec![Object::Dictionary(dict)]);
356 let result = parse_annotations(&arr, &store).unwrap();
357 match &result[0].action {
358 Some(Action::Named(name)) => assert_eq!(name, "NextPage"),
359 _ => panic!("expected Named action"),
360 }
361 }
362
363 #[test]
364 fn test_annotation_with_nm_name() {
365 let store = build_store();
366 let mut dict = HashMap::new();
367 dict.insert(Name::subtype(), Object::Name(Name::from("Text")));
368 dict.insert(Name::rect(), make_rect_array(0.0, 0.0, 50.0, 50.0));
369 dict.insert(
370 Name::nm(),
371 Object::String(PdfString::from_bytes(b"annot-001".to_vec())),
372 );
373
374 let arr = Object::Array(vec![Object::Dictionary(dict)]);
375 let result = parse_annotations(&arr, &store).unwrap();
376 assert_eq!(result[0].name.as_deref(), Some("annot-001"));
377 }
378
379 #[test]
380 fn test_annotation_with_flags() {
381 let store = build_store();
382 let mut dict = HashMap::new();
383 dict.insert(Name::subtype(), Object::Name(Name::from("Text")));
384 dict.insert(Name::rect(), make_rect_array(0.0, 0.0, 50.0, 50.0));
385 dict.insert(Name::f(), Object::Integer(6)); let arr = Object::Array(vec![Object::Dictionary(dict)]);
388 let result = parse_annotations(&arr, &store).unwrap();
389 assert!(result[0].flags.hidden());
390 assert!(result[0].flags.print());
391 }
392
393 #[test]
394 fn test_highlight_with_quad_points() {
395 let store = build_store();
396 let mut dict = HashMap::new();
397 dict.insert(Name::subtype(), Object::Name(Name::from("Highlight")));
398 dict.insert(Name::rect(), make_rect_array(0.0, 0.0, 100.0, 20.0));
399 dict.insert(
400 Name::quad_points(),
401 Object::Array(vec![
402 Object::Real(0.0),
403 Object::Real(0.0),
404 Object::Real(100.0),
405 Object::Real(0.0),
406 Object::Real(100.0),
407 Object::Real(20.0),
408 Object::Real(0.0),
409 Object::Real(20.0),
410 ]),
411 );
412
413 let arr = Object::Array(vec![Object::Dictionary(dict)]);
414 let result = parse_annotations(&arr, &store).unwrap();
415 assert_eq!(
416 result[0].subtype_data.quad_points,
417 Some(vec![0.0, 0.0, 100.0, 0.0, 100.0, 20.0, 0.0, 20.0])
418 );
419 }
420
421 #[test]
422 fn test_highlight_with_multiple_quads() {
423 let store = build_store();
424 let mut dict = HashMap::new();
425 dict.insert(Name::subtype(), Object::Name(Name::from("Highlight")));
426 dict.insert(Name::rect(), make_rect_array(0.0, 0.0, 200.0, 40.0));
427 dict.insert(
428 Name::quad_points(),
429 Object::Array(vec![
430 Object::Real(0.0),
432 Object::Real(0.0),
433 Object::Real(100.0),
434 Object::Real(0.0),
435 Object::Real(100.0),
436 Object::Real(20.0),
437 Object::Real(0.0),
438 Object::Real(20.0),
439 Object::Real(0.0),
441 Object::Real(20.0),
442 Object::Real(100.0),
443 Object::Real(20.0),
444 Object::Real(100.0),
445 Object::Real(40.0),
446 Object::Real(0.0),
447 Object::Real(40.0),
448 ]),
449 );
450
451 let arr = Object::Array(vec![Object::Dictionary(dict)]);
452 let result = parse_annotations(&arr, &store).unwrap();
453 let qp = result[0].subtype_data.quad_points.as_ref().unwrap();
454 assert_eq!(qp.len(), 16);
455 }
456
457 #[test]
458 fn test_line_annotation_endpoints() {
459 let store = build_store();
460 let mut dict = HashMap::new();
461 dict.insert(Name::subtype(), Object::Name(Name::from("Line")));
462 dict.insert(Name::rect(), make_rect_array(10.0, 20.0, 100.0, 200.0));
463 dict.insert(
464 Name::l(),
465 Object::Array(vec![
466 Object::Real(10.0),
467 Object::Real(20.0),
468 Object::Real(100.0),
469 Object::Real(200.0),
470 ]),
471 );
472
473 let arr = Object::Array(vec![Object::Dictionary(dict)]);
474 let result = parse_annotations(&arr, &store).unwrap();
475 assert_eq!(
476 result[0].subtype_data.line_points,
477 Some([10.0, 20.0, 100.0, 200.0])
478 );
479 }
480
481 #[test]
482 fn test_line_annotation_leader_line() {
483 let store = build_store();
484 let mut dict = HashMap::new();
485 dict.insert(Name::subtype(), Object::Name(Name::from("Line")));
486 dict.insert(Name::rect(), make_rect_array(0.0, 0.0, 100.0, 100.0));
487 dict.insert(
488 Name::l(),
489 Object::Array(vec![
490 Object::Real(0.0),
491 Object::Real(0.0),
492 Object::Real(100.0),
493 Object::Real(100.0),
494 ]),
495 );
496 dict.insert(Name::ll(), Object::Real(15.0));
497
498 let arr = Object::Array(vec![Object::Dictionary(dict)]);
499 let result = parse_annotations(&arr, &store).unwrap();
500 assert_eq!(
501 result[0].subtype_data.line_points,
502 Some([0.0, 0.0, 100.0, 100.0])
503 );
504 assert_eq!(result[0].subtype_data.leader_line_length, Some(15.0));
505 }
506
507 #[test]
508 fn test_polygon_vertices() {
509 let store = build_store();
510 let mut dict = HashMap::new();
511 dict.insert(Name::subtype(), Object::Name(Name::from("Polygon")));
512 dict.insert(Name::rect(), make_rect_array(0.0, 0.0, 100.0, 100.0));
513 dict.insert(
514 Name::vertices(),
515 Object::Array(vec![
516 Object::Real(0.0),
517 Object::Real(0.0),
518 Object::Real(50.0),
519 Object::Real(100.0),
520 Object::Real(100.0),
521 Object::Real(0.0),
522 ]),
523 );
524
525 let arr = Object::Array(vec![Object::Dictionary(dict)]);
526 let result = parse_annotations(&arr, &store).unwrap();
527 assert_eq!(
528 result[0].subtype_data.vertices,
529 Some(vec![0.0, 0.0, 50.0, 100.0, 100.0, 0.0])
530 );
531 }
532
533 #[test]
534 fn test_polyline_vertices() {
535 let store = build_store();
536 let mut dict = HashMap::new();
537 dict.insert(Name::subtype(), Object::Name(Name::from("PolyLine")));
538 dict.insert(Name::rect(), make_rect_array(10.0, 10.0, 30.0, 20.0));
539 dict.insert(
540 Name::vertices(),
541 Object::Array(vec![
542 Object::Real(10.0),
543 Object::Real(10.0),
544 Object::Real(20.0),
545 Object::Real(20.0),
546 Object::Real(30.0),
547 Object::Real(10.0),
548 ]),
549 );
550
551 let arr = Object::Array(vec![Object::Dictionary(dict)]);
552 let result = parse_annotations(&arr, &store).unwrap();
553 assert_eq!(
554 result[0].subtype_data.vertices,
555 Some(vec![10.0, 10.0, 20.0, 20.0, 30.0, 10.0])
556 );
557 }
558
559 #[test]
560 fn test_ink_annotation_strokes() {
561 let store = build_store();
562 let mut dict = HashMap::new();
563 dict.insert(Name::subtype(), Object::Name(Name::from("Ink")));
564 dict.insert(Name::rect(), make_rect_array(0.0, 0.0, 50.0, 50.0));
565 dict.insert(
566 Name::ink_list(),
567 Object::Array(vec![
568 Object::Array(vec![
569 Object::Real(10.0),
570 Object::Real(10.0),
571 Object::Real(20.0),
572 Object::Real(20.0),
573 ]),
574 Object::Array(vec![
575 Object::Real(30.0),
576 Object::Real(30.0),
577 Object::Real(40.0),
578 Object::Real(40.0),
579 ]),
580 ]),
581 );
582
583 let arr = Object::Array(vec![Object::Dictionary(dict)]);
584 let result = parse_annotations(&arr, &store).unwrap();
585 assert_eq!(
586 result[0].subtype_data.ink_list,
587 Some(vec![
588 vec![10.0, 10.0, 20.0, 20.0],
589 vec![30.0, 30.0, 40.0, 40.0],
590 ])
591 );
592 }
593
594 #[test]
595 fn test_freetext_default_appearance() {
596 let store = build_store();
597 let mut dict = HashMap::new();
598 dict.insert(Name::subtype(), Object::Name(Name::from("FreeText")));
599 dict.insert(Name::rect(), make_rect_array(0.0, 0.0, 200.0, 50.0));
600 dict.insert(
601 Name::da(),
602 Object::String(PdfString::from_bytes(b"0 g /Helv 12 Tf".to_vec())),
603 );
604
605 let arr = Object::Array(vec![Object::Dictionary(dict)]);
606 let result = parse_annotations(&arr, &store).unwrap();
607 assert_eq!(
608 result[0].subtype_data.default_appearance,
609 Some("0 g /Helv 12 Tf".to_string())
610 );
611 }
612
613 #[test]
614 fn test_stamp_name_field() {
615 let store = build_store();
616 let mut dict = HashMap::new();
617 dict.insert(Name::subtype(), Object::Name(Name::from("Stamp")));
618 dict.insert(Name::rect(), make_rect_array(0.0, 0.0, 150.0, 80.0));
619 dict.insert(Name::name_key(), Object::Name(Name::from("Approved")));
620
621 let arr = Object::Array(vec![Object::Dictionary(dict)]);
622 let result = parse_annotations(&arr, &store).unwrap();
623 assert_eq!(
624 result[0].subtype_data.stamp_name,
625 Some("Approved".to_string())
626 );
627 }
628
629 #[test]
630 fn test_text_annotation_no_subtype_data() {
631 let store = build_store();
632 let mut dict = HashMap::new();
633 dict.insert(Name::subtype(), Object::Name(Name::from("Text")));
634 dict.insert(Name::rect(), make_rect_array(0.0, 0.0, 50.0, 50.0));
635
636 let arr = Object::Array(vec![Object::Dictionary(dict)]);
637 let result = parse_annotations(&arr, &store).unwrap();
638 let data = &result[0].subtype_data;
639 assert!(data.quad_points.is_none());
640 assert!(data.line_points.is_none());
641 assert!(data.leader_line_length.is_none());
642 assert!(data.vertices.is_none());
643 assert!(data.ink_list.is_none());
644 assert!(data.default_appearance.is_none());
645 assert!(data.stamp_name.is_none());
646 }
647
648 #[test]
649 fn test_popup_annotation_parent_ref() {
650 let store = build_store();
651 let mut popup_dict = HashMap::new();
652 popup_dict.insert(Name::subtype(), Object::Name(Name::from("Popup")));
653 popup_dict.insert(Name::rect(), make_rect_array(200.0, 700.0, 400.0, 800.0));
654 popup_dict.insert(Name::parent(), Object::Reference(ObjectId::new(5, 0)));
655
656 let arr = Object::Array(vec![Object::Dictionary(popup_dict)]);
657 let result = parse_annotations(&arr, &store).unwrap();
658 assert_eq!(result[0].subtype, AnnotationType::Popup);
659 assert_eq!(result[0].parent_ref, Some(ObjectId::new(5, 0)));
660 }
661
662 #[test]
663 fn test_find_parent_annotation_by_object_id() {
664 let text_annot = Annotation {
665 subtype: AnnotationType::Text,
666 rect: [0.0, 0.0, 50.0, 50.0],
667 contents: Some("Note".into()),
668 flags: AnnotationFlags::from_bits(0),
669 name: None,
670 appearance: None,
671 color: None,
672 border: None,
673 action: None,
674 destination: None,
675 subtype_data: AnnotationSubtypeData::default(),
676 mk: None,
677 file_spec: None,
678 parent_ref: None,
679 object_id: Some(ObjectId::new(5, 0)),
680 open: None,
681 ap_n_bytes: None,
682 ap_r_bytes: None,
683 ap_d_bytes: None,
684 irt_ref: None,
685 field_name: None,
686 alternate_name: None,
687 field_value: None,
688 form_field_flags: None,
689 additional_actions: None,
690 form_field_type: None,
691 options: None,
692 };
693
694 let popup_annot = Annotation {
695 subtype: AnnotationType::Popup,
696 rect: [200.0, 700.0, 400.0, 800.0],
697 contents: None,
698 flags: AnnotationFlags::from_bits(0),
699 name: None,
700 appearance: None,
701 color: None,
702 border: None,
703 action: None,
704 destination: None,
705 subtype_data: AnnotationSubtypeData::default(),
706 mk: None,
707 file_spec: None,
708 parent_ref: Some(ObjectId::new(5, 0)),
709 object_id: Some(ObjectId::new(6, 0)),
710 open: None,
711 ap_n_bytes: None,
712 ap_r_bytes: None,
713 ap_d_bytes: None,
714 irt_ref: None,
715 field_name: None,
716 alternate_name: None,
717 field_value: None,
718 form_field_flags: None,
719 additional_actions: None,
720 form_field_type: None,
721 options: None,
722 };
723
724 let annotations = vec![text_annot, popup_annot.clone()];
725 let parent_idx = find_parent_annotation(&annotations, &popup_annot);
726 assert_eq!(parent_idx, Some(0));
727 }
728
729 #[test]
730 fn test_find_parent_annotation_no_match() {
731 let popup_annot = Annotation {
732 subtype: AnnotationType::Popup,
733 rect: [0.0, 0.0, 100.0, 100.0],
734 contents: None,
735 flags: AnnotationFlags::from_bits(0),
736 name: None,
737 appearance: None,
738 color: None,
739 border: None,
740 action: None,
741 destination: None,
742 subtype_data: AnnotationSubtypeData::default(),
743 mk: None,
744 file_spec: None,
745 parent_ref: Some(ObjectId::new(99, 0)),
746 object_id: None,
747 open: None,
748 ap_n_bytes: None,
749 ap_r_bytes: None,
750 ap_d_bytes: None,
751 irt_ref: None,
752 field_name: None,
753 alternate_name: None,
754 field_value: None,
755 form_field_flags: None,
756 additional_actions: None,
757 form_field_type: None,
758 options: None,
759 };
760
761 let annotations: Vec<Annotation> = Vec::new();
762 assert!(find_parent_annotation(&annotations, &popup_annot).is_none());
763 }
764
765 fn make_annotation(rect: [f32; 4]) -> Annotation {
770 Annotation {
771 subtype: AnnotationType::Widget,
772 rect,
773 contents: None,
774 flags: AnnotationFlags::from_bits(0),
775 name: None,
776 appearance: None,
777 color: None,
778 border: None,
779 action: None,
780 destination: None,
781 subtype_data: AnnotationSubtypeData::default(),
782 mk: None,
783 file_spec: None,
784 parent_ref: None,
785 object_id: None,
786 open: None,
787 ap_n_bytes: None,
788 ap_r_bytes: None,
789 ap_d_bytes: None,
790 irt_ref: None,
791 field_name: None,
792 alternate_name: None,
793 field_value: None,
794 form_field_flags: None,
795 additional_actions: None,
796 form_field_type: None,
797 options: None,
798 }
799 }
800
801 #[test]
802 fn test_annotation_at_point_empty() {
803 let annotations: Vec<Annotation> = Vec::new();
804 assert!(annotation_at_point(&annotations, 50.0, 50.0).is_none());
805 }
806
807 #[test]
808 fn test_annotation_at_point_hit() {
809 let annotations = vec![make_annotation([10.0, 20.0, 100.0, 80.0])];
810 let idx = annotation_at_point(&annotations, 50.0, 50.0);
812 assert_eq!(idx, Some(0));
813 }
814
815 #[test]
816 fn test_annotation_at_point_miss() {
817 let annotations = vec![make_annotation([10.0, 20.0, 100.0, 80.0])];
818 assert!(annotation_at_point(&annotations, 200.0, 200.0).is_none());
820 }
821
822 #[test]
823 fn test_annotation_at_point_returns_topmost() {
824 let annotations = vec![
826 make_annotation([0.0, 0.0, 100.0, 100.0]),
827 make_annotation([0.0, 0.0, 100.0, 100.0]),
828 ];
829 let idx = annotation_at_point(&annotations, 50.0, 50.0);
831 assert_eq!(idx, Some(1));
832 }
833
834 #[test]
844 #[ignore = "popup auto-creation from /Contents not yet implemented"]
845 fn test_cpdf_annot_list_create_popup_annot_from_pdf_encoded() {
846 todo!()
852 }
853
854 #[test]
856 #[ignore = "popup auto-creation from /Contents not yet implemented"]
857 fn test_cpdf_annot_list_create_popup_annot_from_unicode() {
858 todo!()
862 }
863
864 #[test]
866 #[ignore = "popup auto-creation from /Contents not yet implemented"]
867 fn test_cpdf_annot_list_create_popup_annot_from_empty_pdf_encoded() {
868 todo!()
871 }
872
873 #[test]
875 #[ignore = "popup auto-creation from /Contents not yet implemented"]
876 fn test_cpdf_annot_list_create_popup_annot_from_empty_unicode() {
877 todo!()
880 }
881
882 #[test]
884 #[ignore = "popup auto-creation from /Contents not yet implemented"]
885 fn test_cpdf_annot_list_create_popup_annot_from_empty_unicoded_with_escape() {
886 todo!()
890 }
891}