lopdf 0.40.0

A Rust library for PDF document manipulation.
Documentation
use lopdf::{dictionary, Document, Object, ObjectStream};

#[test]
fn test_empty_trailer() {
    let doc = Document::with_version("1.5");
    let obj_id = (1, 0);
    let obj = Object::Dictionary(dictionary! { "Test" => "Value" });
    
    // With empty trailer, all objects should be compressible
    assert!(ObjectStream::can_be_compressed(obj_id, &obj, &doc),
            "Object should be compressible with empty trailer");
}

#[test]
fn test_malformed_encrypt_reference() {
    let mut doc = Document::with_version("1.5");
    
    let obj_id = doc.add_object(dictionary! {
        "Type" => "Catalog"
    });
    
    // Set Encrypt to a malformed reference (wrong generation)
    doc.trailer.set("Encrypt", Object::Reference((999, 99)));
    
    // Object should still be compressible (not the encryption dict)
    assert!(ObjectStream::can_be_compressed(obj_id, doc.objects.get(&obj_id).unwrap(), &doc),
            "Object should be compressible even with malformed Encrypt reference");
}

#[test]
fn test_self_referencing_object() {
    let mut doc = Document::with_version("1.5");
    
    let obj_id = (5, 0);
    doc.objects.insert(obj_id, Object::Dictionary(dictionary! {
        "Type" => "Test",
        "Self" => Object::Reference(obj_id)  // Self reference
    }));
    
    doc.trailer.set("Test", obj_id);
    
    // Should be compressible (not encryption dict)
    assert!(ObjectStream::can_be_compressed(obj_id, doc.objects.get(&obj_id).unwrap(), &doc),
            "Self-referencing object should be compressible");
}

#[test]
fn test_circular_references() {
    let mut doc = Document::with_version("1.5");
    
    let obj1_id = (1, 0);
    let obj2_id = (2, 0);
    
    doc.objects.insert(obj1_id, Object::Dictionary(dictionary! {
        "Next" => Object::Reference(obj2_id)
    }));
    
    doc.objects.insert(obj2_id, Object::Dictionary(dictionary! {
        "Next" => Object::Reference(obj1_id)  // Circular reference
    }));
    
    doc.trailer.set("Start", obj1_id);
    
    // Both should be compressible
    assert!(ObjectStream::can_be_compressed(obj1_id, doc.objects.get(&obj1_id).unwrap(), &doc),
            "First object in circular reference should be compressible");
    assert!(ObjectStream::can_be_compressed(obj2_id, doc.objects.get(&obj2_id).unwrap(), &doc),
            "Second object in circular reference should be compressible");
}

#[test]
fn test_trailer_with_all_pdf_types() {
    let mut doc = Document::with_version("1.5");
    
    let obj_id = doc.add_object(dictionary! { "Type" => "Test" });
    
    // Add all possible PDF types to trailer
    doc.trailer.set("Root", obj_id);
    doc.trailer.set("Null", Object::Null);
    doc.trailer.set("Bool", Object::Boolean(true));
    doc.trailer.set("Int", Object::Integer(42));
    doc.trailer.set("Real", Object::Real(3.14159));
    doc.trailer.set("String", Object::String(b"test".to_vec(), lopdf::StringFormat::Literal));
    doc.trailer.set("Name", Object::Name(b"Test".to_vec()));
    doc.trailer.set("Array", Object::Array(vec![Object::Integer(1), Object::Integer(2)]));
    doc.trailer.set("Dict", Object::Dictionary(dictionary! { "Key" => "Value" }));
    
    // Object should still be compressible
    assert!(ObjectStream::can_be_compressed(obj_id, doc.objects.get(&obj_id).unwrap(), &doc),
            "Object should be compressible with diverse trailer entries");
}

#[test]
fn test_unicode_trailer_keys() {
    let mut doc = Document::with_version("1.5");
    
    let obj_id = doc.add_object(dictionary! { "Test" => "Value" });
    
    // Use Unicode/non-ASCII keys in trailer
    doc.trailer.set("Root", obj_id);
    doc.trailer.set("Ünïcödé", Object::String(b"test".to_vec(), lopdf::StringFormat::Literal));
    doc.trailer.set("日本語", Object::Integer(42));
    doc.trailer.set("🎯", Object::Boolean(true));
    
    // Should still work correctly
    assert!(ObjectStream::can_be_compressed(obj_id, doc.objects.get(&obj_id).unwrap(), &doc),
            "Object should be compressible with Unicode trailer keys");
}

#[test]
fn test_very_large_trailer() {
    let mut doc = Document::with_version("1.5");
    
    let obj_id = doc.add_object(dictionary! { "Type" => "Catalog" });
    
    // Add many entries to trailer
    doc.trailer.set("Root", obj_id);
    for i in 0..1000 {
        doc.trailer.set(format!("Custom{:04}", i).as_bytes(), Object::Integer(i));
    }
    
    // Should still be efficient
    let start = std::time::Instant::now();
    let compressible = ObjectStream::can_be_compressed(obj_id, doc.objects.get(&obj_id).unwrap(), &doc);
    let duration = start.elapsed();
    
    assert!(compressible, "Object should be compressible even with large trailer");
    assert!(duration.as_micros() < 100, "Check should be fast even with large trailer");
}

#[test]
fn test_concurrent_modification_safety() {
    // This test verifies the function doesn't panic with concurrent-like access patterns
    let mut doc = Document::with_version("1.5");
    
    let obj1_id = doc.add_object(dictionary! { "Type" => "Test1" });
    let obj2_id = doc.add_object(dictionary! { "Type" => "Test2" });
    
    doc.trailer.set("Ref1", obj1_id);
    
    // Check first object
    let result1 = ObjectStream::can_be_compressed(obj1_id, doc.objects.get(&obj1_id).unwrap(), &doc);
    
    // Modify trailer between checks
    doc.trailer.set("Ref2", obj2_id);
    doc.trailer.set("Encrypt", obj1_id);  // Now obj1 is encryption dict
    
    // Check again - result should change
    let result2 = ObjectStream::can_be_compressed(obj1_id, doc.objects.get(&obj1_id).unwrap(), &doc);
    
    assert!(result1, "Initially should be compressible");
    assert!(!result2, "Should not be compressible after becoming encryption dict");
}