use pdfxml::{load_annotations_from_pdf, Annotation, PdfAnnotationExporter, XfdfDocument};
use std::collections::HashMap;
use std::fs;
use tempfile::NamedTempFile;
#[test]
fn test_full_workflow_minimal_xfdf() {
let xfdf_content = r##"<?xml version="1.0" encoding="UTF-8" ?>
<xfdf xmlns="http://ns.adobe.com/xfdf/" xml:space="preserve">
<annots>
<text subject="Test" page="0" rect="100,700,250,730" color="#FFFF00">
Hello World
</text>
</annots>
</xfdf>"##;
let doc = XfdfDocument::parse(xfdf_content).expect("Failed to parse XFDF");
assert_eq!(doc.annotations.len(), 1);
assert_eq!(doc.total_pages(), 1);
}
#[test]
fn test_export_to_pdf() {
let xfdf_content = r##"<?xml version="1.0" encoding="UTF-8" ?>
<xfdf xmlns="http://ns.adobe.com/xfdf/" xml:space="preserve">
<annots>
<text subject="Comment 1" page="0" rect="100,700,250,730" color="#FF0000">
First comment
</text>
<highlight page="0" rect="50,600,200,620" color="#FFFF00"/>
<text subject="Comment on page 2" page="1" rect="100,500,300,550" color="#00FF00">
Second page comment
</text>
</annots>
</xfdf>"##;
let doc = XfdfDocument::parse(xfdf_content).expect("Failed to parse XFDF");
assert_eq!(doc.annotations.len(), 3);
let output_file = NamedTempFile::new().expect("Failed to create temp file");
let output_path = output_file.path().to_path_buf();
let mut exporter = PdfAnnotationExporter::new();
let result = exporter.export_to_new_pdf(&doc, &output_path);
assert!(result.is_ok());
assert!(output_path.exists());
let metadata = fs::metadata(&output_path).unwrap();
assert!(metadata.len() > 0);
}
#[test]
fn test_round_trip_pdf_to_xfdf() {
let xfdf_content = r##"<?xml version="1.0" encoding="UTF-8" ?>
<xfdf xmlns="http://ns.adobe.com/xfdf/" xml:space="preserve">
<annots>
<text name="note-1" subject="Comment 1" page="0" rect="100,700,250,730" title="Alice" color="#FF0000">
First comment
</text>
<square page="0" rect="50,600,200,620" color="#FFFF00" width="2"/>
<circle page="1" rect="100,500,300,550" color="#00FF00" interiorcolor="#FFCCCC" width="3"/>
</annots>
</xfdf>"##;
let doc = XfdfDocument::parse(xfdf_content).expect("Failed to parse XFDF");
let output_file = NamedTempFile::new().expect("Failed to create temp pdf");
let output_path = output_file.path().to_path_buf();
let mut exporter = PdfAnnotationExporter::new();
exporter.export_to_new_pdf(&doc, &output_path).expect("Failed to export pdf");
let loaded = load_annotations_from_pdf(&output_path).expect("Failed to load annotations from pdf");
assert_eq!(loaded.annotations.len(), 3);
assert_eq!(loaded.total_pages(), 2);
let xml = loaded.to_xfdf_string().expect("Failed to serialize xfdf");
assert!(xml.contains("<xfdf xmlns=\"http://ns.adobe.com/xfdf/\" xml:space=\"preserve\">"));
assert!(xml.contains("<text name=\"note-1\" page=\"0\" rect=\"100,700,250,730\" title=\"Alice\" subject=\"Comment 1\" color=\"#FF0000\">First comment</text>"));
assert!(xml.contains("<square page=\"0\" rect=\"50,600,200,620\" color=\"#FFFF00\" width=\"2\"/>"));
assert!(xml.contains("<circle page=\"1\" rect=\"100,500,300,550\" color=\"#00FF00\" width=\"3\" interiorcolor=\"#FFCCCC\"/>"));
}
#[test]
fn test_round_trip_freetext_textcolor_pdf_to_xfdf() {
let xfdf_content = r##"<?xml version="1.0" encoding="UTF-8" ?>
<xfdf xmlns="http://ns.adobe.com/xfdf/" xml:space="preserve">
<annots>
<freetext page="0" rect="100,700,250,760" color="#0000FF" TextColor="#FF0000" subject="FreeText">Hello FreeText</freetext>
</annots>
</xfdf>"##;
let doc = XfdfDocument::parse(xfdf_content).expect("Failed to parse freetext XFDF");
let output_file = NamedTempFile::new().expect("Failed to create temp pdf");
let output_path = output_file.path().to_path_buf();
let mut exporter = PdfAnnotationExporter::new();
exporter.export_to_new_pdf(&doc, &output_path).expect("Failed to export freetext pdf");
let loaded = load_annotations_from_pdf(&output_path).expect("Failed to load freetext annotations from pdf");
assert_eq!(loaded.annotations.len(), 1);
let xml = loaded.to_xfdf_string().expect("Failed to serialize freetext xfdf");
assert!(xml.contains("<freetext"));
assert!(xml.contains("TextColor=\"#FF0000\""));
assert!(xml.contains("color=\"#0000FF\""));
}
#[test]
fn test_round_trip_freetext_textcolor_precedes_color() {
let xfdf_content = r##"<?xml version="1.0" encoding="UTF-8" ?>
<xfdf xmlns="http://ns.adobe.com/xfdf/" xml:space="preserve">
<annots>
<freetext page="0" rect="120,620,280,690" color="#00FF00" TextColor="#112233">Priority check</freetext>
</annots>
</xfdf>"##;
let doc = XfdfDocument::parse(xfdf_content).expect("Failed to parse freetext priority XFDF");
let output_file = NamedTempFile::new().expect("Failed to create temp pdf");
let output_path = output_file.path().to_path_buf();
let mut exporter = PdfAnnotationExporter::new();
exporter.export_to_new_pdf(&doc, &output_path).expect("Failed to export freetext priority pdf");
let loaded = load_annotations_from_pdf(&output_path).expect("Failed to load freetext priority annotations from pdf");
let xml = loaded.to_xfdf_string().expect("Failed to serialize freetext priority xfdf");
assert!(xml.contains("TextColor=\"#112233\""));
assert!(xml.contains("color=\"#00FF00\""));
}
#[test]
fn test_round_trip_popup_name_and_opacity() {
let xfdf_content = r##"<?xml version="1.0" encoding="UTF-8" ?>
<xfdf xmlns="http://ns.adobe.com/xfdf/" xml:space="preserve">
<annots>
<text name="note-1" page="0" rect="100,700,250,730" color="#FF0000" opacity="0.4">
Parent note
</text>
<popup name="popup-1" page="0" rect="160,640,260,700" parent="note-1" open="yes"/>
</annots>
</xfdf>"##;
let doc = XfdfDocument::parse(xfdf_content).expect("Failed to parse popup XFDF");
let output_file = NamedTempFile::new().expect("Failed to create temp pdf");
let output_path = output_file.path().to_path_buf();
let mut exporter = PdfAnnotationExporter::new();
exporter.export_to_new_pdf(&doc, &output_path).expect("Failed to export popup pdf");
let loaded = load_annotations_from_pdf(&output_path).expect("Failed to load popup annotations from pdf");
assert_eq!(loaded.annotations.len(), 2);
let text = loaded
.annotations
.iter()
.find_map(|annotation| match annotation {
Annotation::Text(text) => Some(text),
_ => None,
})
.expect("Missing text annotation");
assert_eq!(text.base.name.as_deref(), Some("note-1"));
assert!((text.base.opacity - 0.4).abs() < 0.0001);
let popup = loaded
.annotations
.iter()
.find_map(|annotation| match annotation {
Annotation::Popup(popup) => Some(popup),
_ => None,
})
.expect("Missing popup annotation");
assert_eq!(popup.base.name.as_deref(), Some("popup-1"));
assert_eq!(popup.parent_name.as_deref(), Some("note-1"));
assert!(popup.open);
}
#[test]
fn test_export_to_existing_pdf_appends_missing_pages() {
let base_pdf_doc = XfdfDocument {
xmlns: Some("http://ns.adobe.com/xfdf/".to_string()),
fields: Vec::new(),
annotations: Vec::new(),
metadata: HashMap::new(),
};
let merge_doc = XfdfDocument::parse(
r##"<?xml version="1.0" encoding="UTF-8" ?>
<xfdf xmlns="http://ns.adobe.com/xfdf/" xml:space="preserve">
<annots>
<text page="1" rect="100,680,250,720" color="#3366FF">Second page note</text>
</annots>
</xfdf>"##,
)
.expect("Failed to parse merge XFDF");
let input_file = NamedTempFile::new().expect("Failed to create source pdf");
let output_file = NamedTempFile::new().expect("Failed to create merged pdf");
let mut exporter = PdfAnnotationExporter::new();
exporter
.export_to_new_pdf(&base_pdf_doc, input_file.path())
.expect("Failed to create base pdf");
exporter
.export_to_existing_pdf(&merge_doc, input_file.path(), output_file.path())
.expect("Failed to merge annotations into existing pdf");
let loaded = load_annotations_from_pdf(output_file.path()).expect("Failed to load merged annotations");
assert_eq!(loaded.annotations.len(), 1);
assert_eq!(loaded.total_pages(), 2);
assert_eq!(loaded.annotations[0].page(), 1);
}