mirror of
https://github.com/storytold/designcraft.git
synced 2026-10-25 10:46:50 -06:00
Merge main8dff7ecdb0into the original reader head4b7e4aaab6, preserving both histories and their attribution. Keep main's placed-page crop/cache APIs, crop-aware raster sizing, linked-file reads and unrelated dependencies while retaining the shared reader's checked layered-export errors. Route main's added PDF bounds code and placed-PDF tests through craft-pdf. Correct the fetched-source notice to craft-libs 89dcbb3 and the transferred source snapshot to PdfCraft 7437bde, with asset provenance recorded separately. Add a regression for hidden-layer export with a placed TrimBox. This records unchanged treebf630f5252, validated locally on 2026-10-20: 13 PDF backend tests, 94 renderer tests, 16 WASM package checks and PDF test compilation to WASM passed. Seven PDF exports / ten pages match exact main in native PNGs, independent Poppler pixels and extracted text. Formatting, dependency, clippy, asset and layer checks passed; the lockfile did not drift. Full local CI is not green: native validation recorded 2,927 passes, two sandbox-denied listener binds and one unavailable GPU-adapter case. Five other listener-dependent tests were not attempted; five existing ignores remain. Runtime WASM was not executed without an available runner. These limits are preserved, and no builds or tests were rerun for this commit.
62 lines
3.4 KiB
Rust
62 lines
3.4 KiB
Rust
fn page(width: f32, height: f32) -> Vec<u8> {
|
|
let content = format!("1 0 0 rg 0 0 {width} {height} re f");
|
|
format!(
|
|
"%PDF-1.7\n1 0 obj << /Type /Catalog /Pages 2 0 R >> endobj\n2 0 obj << /Type /Pages /Kids [3 0 R] /Count 1 >> endobj\n3 0 obj << /Type /Page /Parent 2 0 R /MediaBox [0 0 {width} {height}] /Contents 4 0 R >> endobj\n4 0 obj << /Length {} >> stream\n{content}\nendstream endobj\ntrailer << /Root 1 0 R >>\n%%EOF",
|
|
content.len()
|
|
)
|
|
.into_bytes()
|
|
}
|
|
|
|
#[test]
|
|
fn thin_pdf_page_has_visible_preview_and_export_pixels() {
|
|
let bytes = page(1000.0, 0.2);
|
|
let preview = designcraft_render::render_pdf_page(&bytes, 0, 700).unwrap();
|
|
assert_eq!((preview.width(), preview.height()), (700, 1));
|
|
assert!(preview.data().iter().any(|pixel| pixel.a > 0));
|
|
let png = designcraft_render::pdf_page_png_checked(&bytes, 0, 700, &[], None).unwrap();
|
|
let image = image::load_from_memory(&png).unwrap().to_rgba8();
|
|
assert_eq!(image.dimensions(), (700, 1));
|
|
assert!(image.pixels().any(|pixel| pixel[3] > 0));
|
|
}
|
|
|
|
#[test]
|
|
fn empty_media_box_preserves_the_parsers_default_page_repair() {
|
|
let bytes = page(0.0, 0.0);
|
|
let missing = String::from_utf8(bytes.clone()).unwrap().replace("/MediaBox [0 0 0 0]", "");
|
|
let size = designcraft_render::pdf_page_size(&bytes, 0).unwrap();
|
|
assert!(size.0 > 0.0 && size.1 > 0.0);
|
|
assert_eq!(Some(size), designcraft_render::pdf_page_size(missing.as_bytes(), 0));
|
|
let preview = designcraft_render::render_pdf_page(&bytes, 0, 700).unwrap();
|
|
let png = designcraft_render::pdf_page_png_checked(&bytes, 0, 700, &[], None).unwrap();
|
|
let image = image::load_from_memory(&png).unwrap();
|
|
assert_eq!((image.width(), image.height()), (u32::from(preview.width()), u32::from(preview.height())));
|
|
}
|
|
|
|
#[test]
|
|
fn pdf_input_limit_is_actionable_before_placement() {
|
|
let error = designcraft_render::check_pdf_input_size(craft_pdf::MAX_INPUT_BYTES + 1).unwrap_err();
|
|
assert!(matches!(error, designcraft_render::PdfRasterError::Input(_)));
|
|
assert!(error.to_string().contains("input limit"));
|
|
}
|
|
|
|
#[test]
|
|
fn pdf_boxes_match_scaled_and_rotated_page_coordinates() {
|
|
for (rotation, size, trim, media) in [
|
|
(0, (200.0, 120.0), (20.0, 40.0, 140.0, 60.0), (-20.0, -40.0, 400.0, 200.0)),
|
|
(90, (120.0, 200.0), (20.0, 20.0, 60.0, 140.0), (-40.0, -20.0, 200.0, 400.0)),
|
|
(180, (200.0, 120.0), (40.0, 20.0, 140.0, 60.0), (-180.0, -40.0, 400.0, 200.0)),
|
|
(270, (120.0, 200.0), (40.0, 40.0, 60.0, 140.0), (-40.0, -180.0, 200.0, 400.0)),
|
|
] {
|
|
let bytes = String::from_utf8(page(200.0, 100.0)).unwrap().replace(
|
|
"/MediaBox [0 0 200 100]",
|
|
&format!("/MediaBox [0 0 200 100] /CropBox [10 20 110 80] /TrimBox [20 30 90 60] /UserUnit 2 /Rotate {rotation}"),
|
|
);
|
|
let bytes = bytes.as_bytes();
|
|
assert_eq!(designcraft_render::pdf_page_size(bytes, 0), Some(size), "rotation {rotation}");
|
|
assert_eq!(designcraft_render::pdf_page_box(bytes, 0, "crop"), Some((0.0, 0.0, size.0, size.1)), "rotation {rotation}");
|
|
assert_eq!(designcraft_render::pdf_page_box(bytes, 0, "trim"), Some(trim), "rotation {rotation}");
|
|
assert_eq!(designcraft_render::pdf_page_box(bytes, 0, "media"), Some(media), "rotation {rotation}");
|
|
assert_eq!(designcraft_render::pdf_page_box(bytes, 0, "art"), Some((0.0, 0.0, size.0, size.1)), "missing box, rotation {rotation}");
|
|
}
|
|
}
|