zenpdf 0.2.0

PDF page renderer via hayro with page selection and render bounds
Documentation
use zenpdf::{
    PageSelection, PdfConfig, PdfError, RenderBounds, page_count, page_dimensions, render_page,
    render_pages,
};

/// Test PDF generated by fpdf2: single A4 page (595.28 x 841.89 pt) with "Hello" text.
fn test_pdf() -> Vec<u8> {
    std::fs::read(concat!(
        env!("CARGO_MANIFEST_DIR"),
        "/tests/fixtures/test.pdf"
    ))
    .expect("test PDF not found at tests/fixtures/test.pdf")
}

// A4 dimensions in points (fpdf2 default).
const A4_W: f32 = 595.28;
const A4_H: f32 = 841.89;

#[test]
fn count() {
    assert_eq!(page_count(&test_pdf()).unwrap(), 1);
}

#[test]
fn dimensions() {
    let (w, h) = page_dimensions(&test_pdf(), 0).unwrap();
    assert!((w - A4_W).abs() < 1.0, "width {w} should be ~{A4_W}");
    assert!((h - A4_H).abs() < 1.0, "height {h} should be ~{A4_H}");
}

#[test]
fn out_of_range() {
    let err = page_dimensions(&test_pdf(), 5).unwrap_err();
    assert!(matches!(
        err,
        PdfError::PageOutOfRange { index: 5, count: 1 }
    ));
}

#[test]
fn render_scale_1x() {
    let page = render_page(&test_pdf(), 0, &RenderBounds::Scale(1.0)).unwrap();
    assert_eq!(page.index, 0);
    // floor(595.28) = 595, floor(841.89) = 841
    assert_eq!(page.buffer.width(), 595);
    assert_eq!(page.buffer.height(), 841);
    assert!((page.source_width_pt - A4_W).abs() < 1.0);
    assert!((page.source_height_pt - A4_H).abs() < 1.0);
}

#[test]
fn render_dpi_144() {
    let page = render_page(&test_pdf(), 0, &RenderBounds::Dpi(144.0)).unwrap();
    // 2x scale: floor(595.28*2)=1190, floor(841.89*2)=1683
    assert_eq!(page.buffer.width(), 1190);
    assert_eq!(page.buffer.height(), 1683);
}

#[test]
fn render_fit_width() {
    let page = render_page(&test_pdf(), 0, &RenderBounds::FitWidth(595)).unwrap();
    assert_eq!(page.buffer.width(), 595);
    // scale = 595 / 595.28 ≈ 0.99953 → height ≈ floor(841.89 * 0.99953) = 841
    assert_eq!(page.buffer.height(), 841);
}

#[test]
fn render_fit_height() {
    let page = render_page(&test_pdf(), 0, &RenderBounds::FitHeight(841)).unwrap();
    // scale = 841 / 841.89 ≈ 0.99894 → width ≈ floor(595.28 * 0.99894) = 594
    assert_eq!(page.buffer.height(), 841);
    assert!(page.buffer.width() >= 594 && page.buffer.width() <= 595);
}

#[test]
fn render_fit_box() {
    let page = render_page(
        &test_pdf(),
        0,
        &RenderBounds::FitBox {
            width: 300,
            height: 10000,
        },
    )
    .unwrap();
    // Width is the constraint: scale = 300/595.28 ≈ 0.5040
    assert_eq!(page.buffer.width(), 300);
    let expected_h = (A4_H * (300.0 / A4_W)).floor() as u32;
    assert_eq!(page.buffer.height(), expected_h);
}

#[test]
fn render_exact() {
    let page = render_page(
        &test_pdf(),
        0,
        &RenderBounds::Exact {
            width: 800,
            height: 600,
        },
    )
    .unwrap();
    assert_eq!(page.buffer.width(), 800);
    assert_eq!(page.buffer.height(), 600);
}

#[test]
fn render_all_pages() {
    let pages = render_pages(&test_pdf(), &PdfConfig::default()).unwrap();
    assert_eq!(pages.len(), 1);
    assert_eq!(pages[0].index, 0);
}

#[test]
fn render_page_twice() {
    let config = PdfConfig {
        pages: PageSelection::Pages(vec![0, 0]),
        bounds: RenderBounds::Scale(0.25),
        ..PdfConfig::default()
    };
    let pages = render_pages(&test_pdf(), &config).unwrap();
    assert_eq!(pages.len(), 2);
    assert_eq!(pages[0].index, 0);
    assert_eq!(pages[1].index, 0);
}

#[test]
fn invalid_pdf() {
    let err = page_count(b"not a pdf").unwrap_err();
    assert!(matches!(err, PdfError::InvalidPdf(_)));
}

#[test]
fn white_background() {
    // Render at tiny scale for speed; mostly-empty page should be white.
    let page = render_page(&test_pdf(), 0, &RenderBounds::Scale(0.1)).unwrap();
    let bytes = page.buffer.as_contiguous_bytes().expect("contiguous");
    // Spot-check: last row should be all-white (no content at bottom of page).
    let bpp = 4;
    let row_bytes = page.buffer.width() as usize * bpp;
    let last_row = &bytes[bytes.len() - row_bytes..];
    for &b in last_row {
        assert_eq!(b, 255, "bottom of empty page should be white");
    }
}