oxidize-pdf 2.5.0

A pure Rust PDF generation and manipulation library with zero external dependencies
Documentation
//! Integration tests for Interactive Features (Forms, Annotations, Outlines)

use oxidize_pdf::annotations::{Annotation, AnnotationType};
use oxidize_pdf::forms::*;
use oxidize_pdf::geometry::{Point, Rectangle};
use oxidize_pdf::graphics::Color;
use oxidize_pdf::parser::objects::PdfObject;
use oxidize_pdf::parser::{PdfDocument, PdfReader};
use oxidize_pdf::structure::{Destination, OutlineBuilder, OutlineItem, PageDestination};
use oxidize_pdf::text::Font;
use oxidize_pdf::{Document, Page};
use std::fs;
use tempfile::TempDir;

#[test]
fn test_annotations_writing() {
    // Test annotations writing (parser support not required)
    let temp_dir = TempDir::new().unwrap();
    let file_path = temp_dir.path().join("annotations_test.pdf");

    let mut document = Document::new();
    document.set_title("Annotations Test");

    let mut page = Page::a4();

    // Add some content
    page.text()
        .set_font(Font::Helvetica, 12.0)
        .at(50.0, 750.0)
        .write("This page has annotations")
        .unwrap();

    // Add text annotation
    let text_annot = Annotation::new(
        AnnotationType::Text,
        Rectangle::new(Point::new(100.0, 700.0), Point::new(120.0, 720.0)),
    )
    .with_contents("This is a note");

    page.add_annotation(text_annot);

    // Add highlight annotation
    let highlight_annot = Annotation::new(
        AnnotationType::Highlight,
        Rectangle::new(Point::new(50.0, 745.0), Point::new(200.0, 755.0)),
    )
    .with_color(Color::rgb(1.0, 1.0, 0.0));

    page.add_annotation(highlight_annot);

    document.add_page(page);

    // Verify document saves successfully
    assert!(document.save(&file_path).is_ok());
    assert!(file_path.exists());

    fs::remove_file(&file_path).ok();
}

#[test]
fn test_forms_integration() {
    let temp_dir = TempDir::new().unwrap();
    let file_path = temp_dir.path().join("forms_test.pdf");

    // Create PDF with forms
    {
        let mut document = Document::new();
        document.set_title("Forms Test");

        // Create widgets
        let text_widget = Widget::new(Rectangle::new(
            Point::new(150.0, 650.0),
            Point::new(400.0, 670.0),
        ));

        let checkbox_widget = Widget::new(Rectangle::new(
            Point::new(150.0, 600.0),
            Point::new(165.0, 615.0),
        ));

        let mut page = Page::a4();

        // Add labels
        page.text()
            .set_font(Font::Helvetica, 12.0)
            .at(50.0, 655.0)
            .write("Name:")
            .unwrap();

        page.text()
            .set_font(Font::Helvetica, 12.0)
            .at(50.0, 605.0)
            .write("Subscribe:")
            .unwrap();

        // Add widgets to page
        page.add_form_widget(text_widget.clone());
        page.add_form_widget(checkbox_widget.clone());

        // Enable forms and add fields
        {
            let form_manager = document.enable_forms();

            // Add text field
            let text_field = TextField::new("name_field").with_default_value("Enter your name");
            form_manager
                .add_text_field(text_field, text_widget, None)
                .unwrap();

            // Add checkbox
            let checkbox = CheckBox::new("subscribe_checkbox");
            form_manager
                .add_checkbox(checkbox, checkbox_widget, None)
                .unwrap();
        }

        document.add_page(page);
        document.save(&file_path).unwrap();
    }

    // Parse and verify
    {
        let mut reader = PdfReader::open(&file_path).unwrap();

        // First check if AcroForm exists in catalog
        let has_acroform = {
            let catalog = reader.catalog().unwrap();
            catalog.contains_key("AcroForm")
        };

        assert!(has_acroform, "Document should have AcroForm in catalog");

        // Now get the AcroForm object reference
        let acroform_ref = {
            let catalog = reader.catalog().unwrap();
            catalog.get("AcroForm").cloned()
        };

        if let Some(acroform_obj) = acroform_ref {
            // Check if it's a reference that needs to be resolved
            let acroform_dict = match &acroform_obj {
                PdfObject::Reference(obj_num, gen_num) => {
                    // Get the actual object
                    reader
                        .get_object(*obj_num, *gen_num)
                        .ok()
                        .and_then(|obj| obj.as_dict().cloned())
                }
                _ => acroform_obj.as_dict().cloned(),
            };

            if let Some(acroform_dict) = acroform_dict {
                assert!(
                    acroform_dict.contains_key("Fields"),
                    "AcroForm should have Fields array"
                );

                if let Some(fields_obj) = acroform_dict.get("Fields") {
                    if let Some(fields_array) = fields_obj.as_array() {
                        // Note: Due to current implementation, form fields are written
                        // separately from widgets, so we just check that the array exists
                        // The array might be empty or have different count than expected
                        // This is a known limitation of the current implementation
                        let _ = fields_array.len(); // Just verify it's a valid array
                    } else {
                        panic!("Fields should be an array");
                    }
                }
            } else {
                panic!("AcroForm should be a dictionary");
            }
        } else {
            panic!("Failed to get AcroForm from catalog");
        }
    }

    fs::remove_file(&file_path).ok();
}

#[test]
fn test_outlines_integration() {
    let temp_dir = TempDir::new().unwrap();
    let file_path = temp_dir.path().join("outlines_test.pdf");

    // Create PDF with outlines
    {
        let mut document = Document::new();
        document.set_title("Outlines Test");

        // Create pages
        for i in 0..5 {
            let mut page = Page::a4();
            page.text()
                .set_font(Font::Helvetica, 16.0)
                .at(50.0, 750.0)
                .write(&format!("Page {}", i + 1))
                .unwrap();
            document.add_page(page);
        }

        // Create outline structure
        let mut builder = OutlineBuilder::new();

        // Root item
        builder.add_item(
            OutlineItem::new("Title Page")
                .with_destination(Destination::fit(PageDestination::PageNumber(0)))
                .bold(),
        );

        // Chapter with sections
        builder.push_item(
            OutlineItem::new("Chapter 1")
                .with_destination(Destination::fit(PageDestination::PageNumber(1)))
                .with_color(Color::rgb(0.0, 0.0, 1.0)),
        );

        builder.add_item(
            OutlineItem::new("Section 1.1")
                .with_destination(Destination::fit(PageDestination::PageNumber(2))),
        );

        builder.add_item(
            OutlineItem::new("Section 1.2")
                .with_destination(Destination::fit(PageDestination::PageNumber(3))),
        );

        builder.pop_item();

        // Another chapter
        builder.add_item(
            OutlineItem::new("Chapter 2")
                .with_destination(Destination::fit(PageDestination::PageNumber(4)))
                .italic(),
        );

        let outline = builder.build();
        document.set_outline(outline);

        document.save(&file_path).unwrap();
    }

    // Parse and verify
    {
        let reader = PdfReader::open(&file_path).unwrap();
        let pdf_doc = PdfDocument::new(reader);

        // For now, just verify the PDF was created with the expected page count
        assert_eq!(pdf_doc.page_count().unwrap(), 5);
    }

    fs::remove_file(&file_path).ok();
}

#[test]
fn test_combined_interactive_features_writing() {
    // Test writing combined features (parser support not required)
    let temp_dir = TempDir::new().unwrap();
    let file_path = temp_dir.path().join("combined_interactive_test.pdf");

    let mut document = Document::new();
    document.set_title("Combined Interactive Features Test");

    // Page 1: Forms and annotations
    let mut page1 = Page::a4();

    page1
        .text()
        .set_font(Font::HelveticaBold, 18.0)
        .at(50.0, 750.0)
        .write("Interactive Form")
        .unwrap();

    // Add form field
    let widget = Widget::new(Rectangle::new(
        Point::new(150.0, 700.0),
        Point::new(350.0, 720.0),
    ));
    page1.add_form_widget(widget.clone());

    // Add annotation
    let note = Annotation::new(
        AnnotationType::Text,
        Rectangle::new(Point::new(360.0, 700.0), Point::new(380.0, 720.0)),
    )
    .with_contents("Fill in your email")
    .with_color(Color::rgb(1.0, 0.0, 0.0));
    page1.add_annotation(note);

    document.add_page(page1);

    // Page 2: More content
    let mut page2 = Page::a4();
    page2
        .text()
        .set_font(Font::Helvetica, 14.0)
        .at(50.0, 750.0)
        .write("Additional Information")
        .unwrap();
    document.add_page(page2);

    // Enable forms
    {
        let form_manager = document.enable_forms();
        let email_field = TextField::new("email").with_default_value("user@example.com");
        form_manager
            .add_text_field(email_field, widget, None)
            .unwrap();
    }

    // Create outline
    let mut builder = OutlineBuilder::new();
    builder.add_item(
        OutlineItem::new("Form Page")
            .with_destination(Destination::fit(PageDestination::PageNumber(0)))
            .bold(),
    );
    builder.add_item(
        OutlineItem::new("Info Page")
            .with_destination(Destination::fit(PageDestination::PageNumber(1))),
    );

    document.set_outline(builder.build());

    // Verify document saves successfully with all features
    assert!(document.save(&file_path).is_ok());
    assert!(file_path.exists());

    // Verify we can at least parse the PDF back
    let reader = PdfReader::open(&file_path).unwrap();
    let pdf_doc = PdfDocument::new(reader);
    assert_eq!(pdf_doc.page_count().unwrap(), 2);

    fs::remove_file(&file_path).ok();
}