use rpdfium_core::PdfSource;
use rpdfium_parser::{Object, ObjectStore};
use crate::annotation::{Annotation, parse_single_annotation};
use crate::error::{DocError, DocResult};
pub fn parse_annotations<S: PdfSource>(
annots_obj: &Object,
store: &ObjectStore<S>,
) -> DocResult<Vec<Annotation>> {
let resolved = store
.deep_resolve(annots_obj)
.map_err(|e| DocError::Parser(e.to_string()))?;
let arr = resolved.as_array().ok_or(DocError::UnexpectedType)?;
let mut annotations = Vec::with_capacity(arr.len());
for item in arr {
let obj_id = item.as_reference();
let annot_obj = store
.deep_resolve(item)
.map_err(|e| DocError::Parser(e.to_string()))?;
match parse_single_annotation(annot_obj, store, obj_id) {
Ok(annot) => annotations.push(annot),
Err(_) => {
continue;
}
}
}
Ok(annotations)
}
pub fn find_parent_annotation(annotations: &[Annotation], popup: &Annotation) -> Option<usize> {
let parent_id = popup.parent_ref?;
annotations
.iter()
.position(|a| a.object_id == Some(parent_id))
}
pub fn annotation_at_point(annotations: &[Annotation], x: f32, y: f32) -> Option<usize> {
annotations.iter().enumerate().rev().find_map(|(i, ann)| {
let r = ann.rect;
let x_min = r[0].min(r[2]);
let x_max = r[0].max(r[2]);
let y_min = r[1].min(r[3]);
let y_max = r[1].max(r[3]);
if x >= x_min && x <= x_max && y >= y_min && y <= y_max {
Some(i)
} else {
None
}
})
}
#[inline]
pub fn annot_get_form_field_at_point(annotations: &[Annotation], x: f32, y: f32) -> Option<usize> {
annotation_at_point(annotations, x, y)
}
#[deprecated(
note = "use `annot_get_form_field_at_point()` — matches upstream `FPDFAnnot_GetFormFieldAtPoint`"
)]
#[inline]
pub fn get_annotation_at_point(annotations: &[Annotation], x: f32, y: f32) -> Option<usize> {
annotation_at_point(annotations, x, y)
}
#[deprecated(
note = "use `annot_get_form_field_at_point()` — matches upstream `FPDFAnnot_GetFormFieldAtPoint`"
)]
#[inline]
pub fn get_form_field_at_point(annotations: &[Annotation], x: f32, y: f32) -> Option<usize> {
annotation_at_point(annotations, x, y)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::action::Action;
use crate::annotation::{Annotation, AnnotationFlags, AnnotationSubtypeData, AnnotationType};
use crate::destination::Destination;
use rpdfium_core::{Name, PdfString};
use rpdfium_parser::ObjectId;
use std::collections::HashMap;
fn build_store() -> ObjectStore<Vec<u8>> {
let pdf = build_minimal_pdf();
ObjectStore::open(pdf, rpdfium_core::ParsingMode::Lenient).unwrap()
}
fn build_minimal_pdf() -> Vec<u8> {
let mut pdf = Vec::new();
pdf.extend_from_slice(b"%PDF-1.4\n");
let obj1_offset = pdf.len();
pdf.extend_from_slice(b"1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n");
let obj2_offset = pdf.len();
pdf.extend_from_slice(b"2 0 obj\n<< /Type /Pages /Kids [] /Count 0 >>\nendobj\n");
let xref_offset = pdf.len();
pdf.extend_from_slice(b"xref\n0 3\n");
pdf.extend_from_slice(b"0000000000 65535 f \r\n");
pdf.extend_from_slice(format!("{:010} 00000 n \r\n", obj1_offset).as_bytes());
pdf.extend_from_slice(format!("{:010} 00000 n \r\n", obj2_offset).as_bytes());
pdf.extend_from_slice(b"trailer\n<< /Size 3 /Root 1 0 R >>\n");
pdf.extend_from_slice(format!("startxref\n{}\n%%EOF", xref_offset).as_bytes());
pdf
}
fn make_rect_array(x1: f64, y1: f64, x2: f64, y2: f64) -> Object {
Object::Array(vec![
Object::Real(x1),
Object::Real(y1),
Object::Real(x2),
Object::Real(y2),
])
}
#[test]
fn test_parse_empty_annotations_array() {
let store = build_store();
let arr = Object::Array(vec![]);
let result = parse_annotations(&arr, &store).unwrap();
assert!(result.is_empty());
}
#[test]
fn test_parse_text_annotation() {
let store = build_store();
let mut dict = HashMap::new();
dict.insert(Name::subtype(), Object::Name(Name::from("Text")));
dict.insert(Name::rect(), make_rect_array(10.0, 20.0, 100.0, 50.0));
dict.insert(
Name::contents(),
Object::String(PdfString::from_bytes(b"A note".to_vec())),
);
let arr = Object::Array(vec![Object::Dictionary(dict)]);
let result = parse_annotations(&arr, &store).unwrap();
assert_eq!(result.len(), 1);
assert_eq!(result[0].subtype, AnnotationType::Text);
assert_eq!(result[0].rect, [10.0, 20.0, 100.0, 50.0]);
assert_eq!(result[0].contents.as_deref(), Some("A note"));
}
#[test]
fn test_parse_link_annotation_with_uri_action() {
let store = build_store();
let mut action_dict = HashMap::new();
action_dict.insert(Name::s(), Object::Name(Name::from("URI")));
action_dict.insert(
Name::uri(),
Object::String(PdfString::from_bytes(b"https://example.com".to_vec())),
);
let mut dict = HashMap::new();
dict.insert(Name::subtype(), Object::Name(Name::from("Link")));
dict.insert(Name::rect(), make_rect_array(0.0, 0.0, 200.0, 20.0));
dict.insert(Name::a(), Object::Dictionary(action_dict));
let arr = Object::Array(vec![Object::Dictionary(dict)]);
let result = parse_annotations(&arr, &store).unwrap();
assert_eq!(result.len(), 1);
assert_eq!(result[0].subtype, AnnotationType::Link);
match &result[0].action {
Some(Action::Uri(uri)) => assert_eq!(uri, "https://example.com"),
_ => panic!("expected URI action"),
}
}
#[test]
fn test_parse_highlight_annotation() {
let store = build_store();
let mut dict = HashMap::new();
dict.insert(Name::subtype(), Object::Name(Name::from("Highlight")));
dict.insert(Name::rect(), make_rect_array(50.0, 700.0, 200.0, 720.0));
dict.insert(
Name::c(),
Object::Array(vec![
Object::Real(1.0),
Object::Real(1.0),
Object::Real(0.0),
]),
);
let arr = Object::Array(vec![Object::Dictionary(dict)]);
let result = parse_annotations(&arr, &store).unwrap();
assert_eq!(result.len(), 1);
assert_eq!(result[0].subtype, AnnotationType::Highlight);
assert_eq!(result[0].color.as_deref(), Some(&[1.0, 1.0, 0.0][..]));
}
#[test]
fn test_border_from_bs_dict() {
use crate::annotation::BorderStyle;
let store = build_store();
let mut bs_dict = HashMap::new();
bs_dict.insert(Name::w(), Object::Real(2.5));
bs_dict.insert(Name::s(), Object::Name(Name::from("D")));
let mut dict = HashMap::new();
dict.insert(Name::subtype(), Object::Name(Name::from("Text")));
dict.insert(Name::rect(), make_rect_array(0.0, 0.0, 100.0, 100.0));
dict.insert(Name::bs(), Object::Dictionary(bs_dict));
let arr = Object::Array(vec![Object::Dictionary(dict)]);
let result = parse_annotations(&arr, &store).unwrap();
let border = result[0].border.as_ref().unwrap();
assert_eq!(border.width, 2.5);
assert_eq!(border.style, BorderStyle::Dashed);
}
#[test]
fn test_border_from_border_array() {
use crate::annotation::BorderStyle;
let store = build_store();
let mut dict = HashMap::new();
dict.insert(Name::subtype(), Object::Name(Name::from("Link")));
dict.insert(Name::rect(), make_rect_array(0.0, 0.0, 100.0, 100.0));
dict.insert(
Name::border(),
Object::Array(vec![
Object::Integer(0),
Object::Integer(0),
Object::Real(3.0),
]),
);
let arr = Object::Array(vec![Object::Dictionary(dict)]);
let result = parse_annotations(&arr, &store).unwrap();
let border = result[0].border.as_ref().unwrap();
assert_eq!(border.width, 3.0);
assert_eq!(border.style, BorderStyle::Solid);
}
#[test]
fn test_annotation_with_no_appearance() {
let store = build_store();
let mut dict = HashMap::new();
dict.insert(Name::subtype(), Object::Name(Name::from("Text")));
dict.insert(Name::rect(), make_rect_array(0.0, 0.0, 50.0, 50.0));
let arr = Object::Array(vec![Object::Dictionary(dict)]);
let result = parse_annotations(&arr, &store).unwrap();
assert!(result[0].appearance.is_none());
}
#[test]
fn test_rect_extraction() {
let store = build_store();
let mut dict = HashMap::new();
dict.insert(Name::subtype(), Object::Name(Name::from("Square")));
dict.insert(Name::rect(), make_rect_array(72.0, 144.0, 288.0, 432.0));
let arr = Object::Array(vec![Object::Dictionary(dict)]);
let result = parse_annotations(&arr, &store).unwrap();
assert_eq!(result[0].rect, [72.0, 144.0, 288.0, 432.0]);
}
#[test]
fn test_multiple_annotations() {
let store = build_store();
let mut dict1 = HashMap::new();
dict1.insert(Name::subtype(), Object::Name(Name::from("Text")));
dict1.insert(Name::rect(), make_rect_array(0.0, 0.0, 50.0, 50.0));
let mut dict2 = HashMap::new();
dict2.insert(Name::subtype(), Object::Name(Name::from("Link")));
dict2.insert(Name::rect(), make_rect_array(100.0, 100.0, 200.0, 200.0));
let mut dict3 = HashMap::new();
dict3.insert(Name::subtype(), Object::Name(Name::from("Highlight")));
dict3.insert(Name::rect(), make_rect_array(50.0, 50.0, 150.0, 60.0));
let arr = Object::Array(vec![
Object::Dictionary(dict1),
Object::Dictionary(dict2),
Object::Dictionary(dict3),
]);
let result = parse_annotations(&arr, &store).unwrap();
assert_eq!(result.len(), 3);
assert_eq!(result[0].subtype, AnnotationType::Text);
assert_eq!(result[1].subtype, AnnotationType::Link);
assert_eq!(result[2].subtype, AnnotationType::Highlight);
}
#[test]
fn test_annotation_with_destination() {
let store = build_store();
let mut dict = HashMap::new();
dict.insert(Name::subtype(), Object::Name(Name::from("Link")));
dict.insert(Name::rect(), make_rect_array(0.0, 0.0, 100.0, 20.0));
dict.insert(
Name::dest(),
Object::String(PdfString::from_bytes(b"chapter1".to_vec())),
);
let arr = Object::Array(vec![Object::Dictionary(dict)]);
let result = parse_annotations(&arr, &store).unwrap();
match &result[0].destination {
Some(Destination::Named(name)) => assert_eq!(name, "chapter1"),
_ => panic!("expected named destination"),
}
}
#[test]
fn test_annotation_with_action() {
let store = build_store();
let mut action_dict = HashMap::new();
action_dict.insert(Name::s(), Object::Name(Name::from("Named")));
action_dict.insert(Name::n(), Object::Name(Name::from("NextPage")));
let mut dict = HashMap::new();
dict.insert(Name::subtype(), Object::Name(Name::from("Widget")));
dict.insert(Name::rect(), make_rect_array(0.0, 0.0, 50.0, 20.0));
dict.insert(Name::a(), Object::Dictionary(action_dict));
let arr = Object::Array(vec![Object::Dictionary(dict)]);
let result = parse_annotations(&arr, &store).unwrap();
match &result[0].action {
Some(Action::Named(name)) => assert_eq!(name, "NextPage"),
_ => panic!("expected Named action"),
}
}
#[test]
fn test_annotation_with_nm_name() {
let store = build_store();
let mut dict = HashMap::new();
dict.insert(Name::subtype(), Object::Name(Name::from("Text")));
dict.insert(Name::rect(), make_rect_array(0.0, 0.0, 50.0, 50.0));
dict.insert(
Name::nm(),
Object::String(PdfString::from_bytes(b"annot-001".to_vec())),
);
let arr = Object::Array(vec![Object::Dictionary(dict)]);
let result = parse_annotations(&arr, &store).unwrap();
assert_eq!(result[0].name.as_deref(), Some("annot-001"));
}
#[test]
fn test_annotation_with_flags() {
let store = build_store();
let mut dict = HashMap::new();
dict.insert(Name::subtype(), Object::Name(Name::from("Text")));
dict.insert(Name::rect(), make_rect_array(0.0, 0.0, 50.0, 50.0));
dict.insert(Name::f(), Object::Integer(6));
let arr = Object::Array(vec![Object::Dictionary(dict)]);
let result = parse_annotations(&arr, &store).unwrap();
assert!(result[0].flags.hidden());
assert!(result[0].flags.print());
}
#[test]
fn test_highlight_with_quad_points() {
let store = build_store();
let mut dict = HashMap::new();
dict.insert(Name::subtype(), Object::Name(Name::from("Highlight")));
dict.insert(Name::rect(), make_rect_array(0.0, 0.0, 100.0, 20.0));
dict.insert(
Name::quad_points(),
Object::Array(vec![
Object::Real(0.0),
Object::Real(0.0),
Object::Real(100.0),
Object::Real(0.0),
Object::Real(100.0),
Object::Real(20.0),
Object::Real(0.0),
Object::Real(20.0),
]),
);
let arr = Object::Array(vec![Object::Dictionary(dict)]);
let result = parse_annotations(&arr, &store).unwrap();
assert_eq!(
result[0].subtype_data.quad_points,
Some(vec![0.0, 0.0, 100.0, 0.0, 100.0, 20.0, 0.0, 20.0])
);
}
#[test]
fn test_highlight_with_multiple_quads() {
let store = build_store();
let mut dict = HashMap::new();
dict.insert(Name::subtype(), Object::Name(Name::from("Highlight")));
dict.insert(Name::rect(), make_rect_array(0.0, 0.0, 200.0, 40.0));
dict.insert(
Name::quad_points(),
Object::Array(vec![
Object::Real(0.0),
Object::Real(0.0),
Object::Real(100.0),
Object::Real(0.0),
Object::Real(100.0),
Object::Real(20.0),
Object::Real(0.0),
Object::Real(20.0),
Object::Real(0.0),
Object::Real(20.0),
Object::Real(100.0),
Object::Real(20.0),
Object::Real(100.0),
Object::Real(40.0),
Object::Real(0.0),
Object::Real(40.0),
]),
);
let arr = Object::Array(vec![Object::Dictionary(dict)]);
let result = parse_annotations(&arr, &store).unwrap();
let qp = result[0].subtype_data.quad_points.as_ref().unwrap();
assert_eq!(qp.len(), 16);
}
#[test]
fn test_line_annotation_endpoints() {
let store = build_store();
let mut dict = HashMap::new();
dict.insert(Name::subtype(), Object::Name(Name::from("Line")));
dict.insert(Name::rect(), make_rect_array(10.0, 20.0, 100.0, 200.0));
dict.insert(
Name::l(),
Object::Array(vec![
Object::Real(10.0),
Object::Real(20.0),
Object::Real(100.0),
Object::Real(200.0),
]),
);
let arr = Object::Array(vec![Object::Dictionary(dict)]);
let result = parse_annotations(&arr, &store).unwrap();
assert_eq!(
result[0].subtype_data.line_points,
Some([10.0, 20.0, 100.0, 200.0])
);
}
#[test]
fn test_line_annotation_leader_line() {
let store = build_store();
let mut dict = HashMap::new();
dict.insert(Name::subtype(), Object::Name(Name::from("Line")));
dict.insert(Name::rect(), make_rect_array(0.0, 0.0, 100.0, 100.0));
dict.insert(
Name::l(),
Object::Array(vec![
Object::Real(0.0),
Object::Real(0.0),
Object::Real(100.0),
Object::Real(100.0),
]),
);
dict.insert(Name::ll(), Object::Real(15.0));
let arr = Object::Array(vec![Object::Dictionary(dict)]);
let result = parse_annotations(&arr, &store).unwrap();
assert_eq!(
result[0].subtype_data.line_points,
Some([0.0, 0.0, 100.0, 100.0])
);
assert_eq!(result[0].subtype_data.leader_line_length, Some(15.0));
}
#[test]
fn test_polygon_vertices() {
let store = build_store();
let mut dict = HashMap::new();
dict.insert(Name::subtype(), Object::Name(Name::from("Polygon")));
dict.insert(Name::rect(), make_rect_array(0.0, 0.0, 100.0, 100.0));
dict.insert(
Name::vertices(),
Object::Array(vec![
Object::Real(0.0),
Object::Real(0.0),
Object::Real(50.0),
Object::Real(100.0),
Object::Real(100.0),
Object::Real(0.0),
]),
);
let arr = Object::Array(vec![Object::Dictionary(dict)]);
let result = parse_annotations(&arr, &store).unwrap();
assert_eq!(
result[0].subtype_data.vertices,
Some(vec![0.0, 0.0, 50.0, 100.0, 100.0, 0.0])
);
}
#[test]
fn test_polyline_vertices() {
let store = build_store();
let mut dict = HashMap::new();
dict.insert(Name::subtype(), Object::Name(Name::from("PolyLine")));
dict.insert(Name::rect(), make_rect_array(10.0, 10.0, 30.0, 20.0));
dict.insert(
Name::vertices(),
Object::Array(vec![
Object::Real(10.0),
Object::Real(10.0),
Object::Real(20.0),
Object::Real(20.0),
Object::Real(30.0),
Object::Real(10.0),
]),
);
let arr = Object::Array(vec![Object::Dictionary(dict)]);
let result = parse_annotations(&arr, &store).unwrap();
assert_eq!(
result[0].subtype_data.vertices,
Some(vec![10.0, 10.0, 20.0, 20.0, 30.0, 10.0])
);
}
#[test]
fn test_ink_annotation_strokes() {
let store = build_store();
let mut dict = HashMap::new();
dict.insert(Name::subtype(), Object::Name(Name::from("Ink")));
dict.insert(Name::rect(), make_rect_array(0.0, 0.0, 50.0, 50.0));
dict.insert(
Name::ink_list(),
Object::Array(vec![
Object::Array(vec![
Object::Real(10.0),
Object::Real(10.0),
Object::Real(20.0),
Object::Real(20.0),
]),
Object::Array(vec![
Object::Real(30.0),
Object::Real(30.0),
Object::Real(40.0),
Object::Real(40.0),
]),
]),
);
let arr = Object::Array(vec![Object::Dictionary(dict)]);
let result = parse_annotations(&arr, &store).unwrap();
assert_eq!(
result[0].subtype_data.ink_list,
Some(vec![
vec![10.0, 10.0, 20.0, 20.0],
vec![30.0, 30.0, 40.0, 40.0],
])
);
}
#[test]
fn test_freetext_default_appearance() {
let store = build_store();
let mut dict = HashMap::new();
dict.insert(Name::subtype(), Object::Name(Name::from("FreeText")));
dict.insert(Name::rect(), make_rect_array(0.0, 0.0, 200.0, 50.0));
dict.insert(
Name::da(),
Object::String(PdfString::from_bytes(b"0 g /Helv 12 Tf".to_vec())),
);
let arr = Object::Array(vec![Object::Dictionary(dict)]);
let result = parse_annotations(&arr, &store).unwrap();
assert_eq!(
result[0].subtype_data.default_appearance,
Some("0 g /Helv 12 Tf".to_string())
);
}
#[test]
fn test_stamp_name_field() {
let store = build_store();
let mut dict = HashMap::new();
dict.insert(Name::subtype(), Object::Name(Name::from("Stamp")));
dict.insert(Name::rect(), make_rect_array(0.0, 0.0, 150.0, 80.0));
dict.insert(Name::name_key(), Object::Name(Name::from("Approved")));
let arr = Object::Array(vec![Object::Dictionary(dict)]);
let result = parse_annotations(&arr, &store).unwrap();
assert_eq!(
result[0].subtype_data.stamp_name,
Some("Approved".to_string())
);
}
#[test]
fn test_text_annotation_no_subtype_data() {
let store = build_store();
let mut dict = HashMap::new();
dict.insert(Name::subtype(), Object::Name(Name::from("Text")));
dict.insert(Name::rect(), make_rect_array(0.0, 0.0, 50.0, 50.0));
let arr = Object::Array(vec![Object::Dictionary(dict)]);
let result = parse_annotations(&arr, &store).unwrap();
let data = &result[0].subtype_data;
assert!(data.quad_points.is_none());
assert!(data.line_points.is_none());
assert!(data.leader_line_length.is_none());
assert!(data.vertices.is_none());
assert!(data.ink_list.is_none());
assert!(data.default_appearance.is_none());
assert!(data.stamp_name.is_none());
}
#[test]
fn test_popup_annotation_parent_ref() {
let store = build_store();
let mut popup_dict = HashMap::new();
popup_dict.insert(Name::subtype(), Object::Name(Name::from("Popup")));
popup_dict.insert(Name::rect(), make_rect_array(200.0, 700.0, 400.0, 800.0));
popup_dict.insert(Name::parent(), Object::Reference(ObjectId::new(5, 0)));
let arr = Object::Array(vec![Object::Dictionary(popup_dict)]);
let result = parse_annotations(&arr, &store).unwrap();
assert_eq!(result[0].subtype, AnnotationType::Popup);
assert_eq!(result[0].parent_ref, Some(ObjectId::new(5, 0)));
}
#[test]
fn test_find_parent_annotation_by_object_id() {
let text_annot = Annotation {
subtype: AnnotationType::Text,
rect: [0.0, 0.0, 50.0, 50.0],
contents: Some("Note".into()),
flags: AnnotationFlags::from_bits(0),
name: None,
appearance: None,
color: None,
border: None,
action: None,
destination: None,
subtype_data: AnnotationSubtypeData::default(),
mk: None,
file_spec: None,
parent_ref: None,
object_id: Some(ObjectId::new(5, 0)),
open: None,
ap_n_bytes: None,
ap_r_bytes: None,
ap_d_bytes: None,
irt_ref: None,
field_name: None,
alternate_name: None,
field_value: None,
form_field_flags: None,
additional_actions: None,
form_field_type: None,
options: None,
};
let popup_annot = Annotation {
subtype: AnnotationType::Popup,
rect: [200.0, 700.0, 400.0, 800.0],
contents: None,
flags: AnnotationFlags::from_bits(0),
name: None,
appearance: None,
color: None,
border: None,
action: None,
destination: None,
subtype_data: AnnotationSubtypeData::default(),
mk: None,
file_spec: None,
parent_ref: Some(ObjectId::new(5, 0)),
object_id: Some(ObjectId::new(6, 0)),
open: None,
ap_n_bytes: None,
ap_r_bytes: None,
ap_d_bytes: None,
irt_ref: None,
field_name: None,
alternate_name: None,
field_value: None,
form_field_flags: None,
additional_actions: None,
form_field_type: None,
options: None,
};
let annotations = vec![text_annot, popup_annot.clone()];
let parent_idx = find_parent_annotation(&annotations, &popup_annot);
assert_eq!(parent_idx, Some(0));
}
#[test]
fn test_find_parent_annotation_no_match() {
let popup_annot = Annotation {
subtype: AnnotationType::Popup,
rect: [0.0, 0.0, 100.0, 100.0],
contents: None,
flags: AnnotationFlags::from_bits(0),
name: None,
appearance: None,
color: None,
border: None,
action: None,
destination: None,
subtype_data: AnnotationSubtypeData::default(),
mk: None,
file_spec: None,
parent_ref: Some(ObjectId::new(99, 0)),
object_id: None,
open: None,
ap_n_bytes: None,
ap_r_bytes: None,
ap_d_bytes: None,
irt_ref: None,
field_name: None,
alternate_name: None,
field_value: None,
form_field_flags: None,
additional_actions: None,
form_field_type: None,
options: None,
};
let annotations: Vec<Annotation> = Vec::new();
assert!(find_parent_annotation(&annotations, &popup_annot).is_none());
}
fn make_annotation(rect: [f32; 4]) -> Annotation {
Annotation {
subtype: AnnotationType::Widget,
rect,
contents: None,
flags: AnnotationFlags::from_bits(0),
name: None,
appearance: None,
color: None,
border: None,
action: None,
destination: None,
subtype_data: AnnotationSubtypeData::default(),
mk: None,
file_spec: None,
parent_ref: None,
object_id: None,
open: None,
ap_n_bytes: None,
ap_r_bytes: None,
ap_d_bytes: None,
irt_ref: None,
field_name: None,
alternate_name: None,
field_value: None,
form_field_flags: None,
additional_actions: None,
form_field_type: None,
options: None,
}
}
#[test]
fn test_annotation_at_point_empty() {
let annotations: Vec<Annotation> = Vec::new();
assert!(annotation_at_point(&annotations, 50.0, 50.0).is_none());
}
#[test]
fn test_annotation_at_point_hit() {
let annotations = vec![make_annotation([10.0, 20.0, 100.0, 80.0])];
let idx = annotation_at_point(&annotations, 50.0, 50.0);
assert_eq!(idx, Some(0));
}
#[test]
fn test_annotation_at_point_miss() {
let annotations = vec![make_annotation([10.0, 20.0, 100.0, 80.0])];
assert!(annotation_at_point(&annotations, 200.0, 200.0).is_none());
}
#[test]
fn test_annotation_at_point_returns_topmost() {
let annotations = vec![
make_annotation([0.0, 0.0, 100.0, 100.0]),
make_annotation([0.0, 0.0, 100.0, 100.0]),
];
let idx = annotation_at_point(&annotations, 50.0, 50.0);
assert_eq!(idx, Some(1));
}
#[test]
#[ignore = "popup auto-creation from /Contents not yet implemented"]
fn test_cpdf_annot_list_create_popup_annot_from_pdf_encoded() {
todo!()
}
#[test]
#[ignore = "popup auto-creation from /Contents not yet implemented"]
fn test_cpdf_annot_list_create_popup_annot_from_unicode() {
todo!()
}
#[test]
#[ignore = "popup auto-creation from /Contents not yet implemented"]
fn test_cpdf_annot_list_create_popup_annot_from_empty_pdf_encoded() {
todo!()
}
#[test]
#[ignore = "popup auto-creation from /Contents not yet implemented"]
fn test_cpdf_annot_list_create_popup_annot_from_empty_unicode() {
todo!()
}
#[test]
#[ignore = "popup auto-creation from /Contents not yet implemented"]
fn test_cpdf_annot_list_create_popup_annot_from_empty_unicoded_with_escape() {
todo!()
}
}