mirror of
https://github.com/storytold/craft-libs.git
synced 2026-11-03 01:00:48 +00:00
364 lines
19 KiB
Rust
364 lines
19 KiB
Rust
#![cfg(feature = "raster")]
|
|
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
|
use craft_pdf::{
|
|
interpret::InterpreterSettings,
|
|
raster::{self, Error, Options, RenderCache},
|
|
};
|
|
use std::sync::{
|
|
Arc,
|
|
atomic::{AtomicBool, AtomicUsize, Ordering},
|
|
};
|
|
|
|
fn pdf(content: &str, extra: &str) -> Arc<Vec<u8>> {
|
|
let objects = [
|
|
"<< /Type /Catalog /Pages 2 0 R >>".to_string(),
|
|
"<< /Type /Pages /Kids [3 0 R] /Count 1 >>".to_string(),
|
|
format!("<< /Type /Page /Parent 2 0 R /MediaBox [0 0 40 20] /Contents 4 0 R {extra} >>"),
|
|
format!("<< /Length {} >>\nstream\n{content}\nendstream", content.len()),
|
|
];
|
|
assemble(&objects)
|
|
}
|
|
|
|
fn assemble(objects: &[String]) -> Arc<Vec<u8>> {
|
|
let mut bytes = b"%PDF-1.7\n".to_vec();
|
|
let mut offsets = vec![0];
|
|
for (i, body) in objects.iter().enumerate() {
|
|
offsets.push(bytes.len());
|
|
bytes.extend_from_slice(format!("{} 0 obj\n{body}\nendobj\n", i + 1).as_bytes());
|
|
}
|
|
let start = bytes.len();
|
|
bytes.extend_from_slice(format!("xref\n0 {}\n0000000000 65535 f \n", offsets.len()).as_bytes());
|
|
for offset in offsets.iter().skip(1) {
|
|
bytes.extend_from_slice(format!("{offset:010} 00000 n \n").as_bytes());
|
|
}
|
|
bytes.extend_from_slice(format!("trailer\n<< /Size {} /Root 1 0 R >>\nstartxref\n{start}\n%%EOF\n", offsets.len()).as_bytes());
|
|
Arc::new(bytes)
|
|
}
|
|
|
|
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
|
|
#[cfg_attr(not(target_arch = "wasm32"), test)]
|
|
fn transparent_and_white_backgrounds_are_explicit() {
|
|
let doc = craft_pdf::open(pdf("0 0 1 rg 0 0 20 20 re f", ""), None).unwrap();
|
|
let page = &doc.pages()[0];
|
|
let settings = InterpreterSettings::default();
|
|
let a = raster::render(page, &RenderCache::new(), &settings, &Options::default()).unwrap();
|
|
assert_eq!((a.width, a.height), (40, 20));
|
|
assert_eq!(&a.bytes()[0..4], &[0, 0, 255, 255]);
|
|
assert_eq!(&a.bytes()[120..124], &[0, 0, 0, 0]);
|
|
let b = raster::render(page, &RenderCache::new(), &settings, &Options { background: [255; 4], ..Default::default() }).unwrap();
|
|
assert_eq!(&b.bytes()[120..124], &[255; 4]);
|
|
}
|
|
|
|
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
|
|
#[cfg_attr(not(target_arch = "wasm32"), test)]
|
|
fn cropped_rotated_geometry_and_tile_match_full_raster() {
|
|
let doc = craft_pdf::open(pdf("1 0 0 rg 0 0 40 20 re f", "/CropBox [10 0 40 20] /Rotate 90"), None).unwrap();
|
|
let page = &doc.pages()[0];
|
|
assert_eq!(raster::dimensions(page, &Options::default()).unwrap(), [20, 30]);
|
|
let full = raster::render(page, &RenderCache::new(), &InterpreterSettings::default(), &Options::default()).unwrap();
|
|
let tile = raster::render(
|
|
page,
|
|
&RenderCache::new(),
|
|
&InterpreterSettings::default(),
|
|
&Options { size: Some([5, 5]), offset: [2.0, 3.0], ..Default::default() },
|
|
)
|
|
.unwrap();
|
|
for row in 0..5 {
|
|
assert_eq!(&tile.bytes()[row * 20..(row + 1) * 20], &full.bytes()[((row + 3) * 20 + 2) * 4..((row + 3) * 20 + 7) * 4]);
|
|
}
|
|
}
|
|
|
|
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
|
|
#[cfg_attr(not(target_arch = "wasm32"), test)]
|
|
fn user_unit_scales_rotated_crop_geometry_and_paint_together() {
|
|
let doc = craft_pdf::open(pdf("1 0 0 rg 0 0 40 20 re f", "/CropBox [10 0 40 20] /Rotate 90 /UserUnit 2"), None).unwrap();
|
|
let page = &doc.pages()[0];
|
|
assert_eq!(raster::dimensions(page, &Options::default()).unwrap(), [40, 60]);
|
|
let full = raster::render(page, &RenderCache::new(), &InterpreterSettings::default(), &Options::default()).unwrap();
|
|
assert_eq!((full.width, full.height), (40, 60));
|
|
assert!(full.bytes().as_chunks::<4>().0.iter().all(|pixel| *pixel == [255, 0, 0, 255]));
|
|
}
|
|
|
|
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
|
|
#[cfg_attr(not(target_arch = "wasm32"), test)]
|
|
fn subpixel_page_extent_still_produces_visible_pixels() {
|
|
let doc = craft_pdf::open(pdf("1 0 0 rg 0 0 40 20 re f", "/CropBox [0 0 40 0.2]"), None).unwrap();
|
|
let raster =
|
|
raster::render(&doc.pages()[0], &RenderCache::new(), &InterpreterSettings::default(), &Options { scale: [0.5; 2], ..Default::default() })
|
|
.unwrap();
|
|
assert_eq!((raster.width, raster.height), (20, 1));
|
|
assert!(raster.bytes().as_chunks::<4>().0.iter().any(|pixel| pixel[3] > 0));
|
|
}
|
|
|
|
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
|
|
#[cfg_attr(not(target_arch = "wasm32"), test)]
|
|
fn rounded_up_viewport_draws_the_partial_edge_pixel() {
|
|
let doc = craft_pdf::open(pdf("0 0 1 rg 0 0 40 20 re f", ""), None).unwrap();
|
|
let raster = raster::render(
|
|
&doc.pages()[0],
|
|
&RenderCache::new(),
|
|
&InterpreterSettings::default(),
|
|
&Options { scale: [0.51; 2], size: Some([21, 11]), ..Default::default() },
|
|
)
|
|
.unwrap();
|
|
assert_eq!((raster.width, raster.height), (21, 11));
|
|
let corner = raster.bytes().as_chunks::<4>().0.last().unwrap();
|
|
assert!(corner[3] > 0 && corner[3] < 255, "partial edge pixel: {corner:?}");
|
|
assert_eq!(corner[2], corner[3]);
|
|
}
|
|
|
|
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
|
|
#[cfg_attr(not(target_arch = "wasm32"), test)]
|
|
fn hostile_options_are_rejected_before_buffer_changes() {
|
|
let doc = craft_pdf::open(pdf("", ""), None).unwrap();
|
|
let page = &doc.pages()[0];
|
|
let cache = RenderCache::new();
|
|
for scale in [f32::NAN, f32::INFINITY, -1.0, 0.0] {
|
|
let mut buffer = [123; 4];
|
|
assert_eq!(
|
|
raster::render_into(page, &cache, &InterpreterSettings::default(), &Options { scale: [scale, 1.0], ..Default::default() }, &mut buffer),
|
|
Err(Error::InvalidOptions)
|
|
);
|
|
assert_eq!(buffer, [123; 4]);
|
|
}
|
|
for size in [[0, 1], [8193, 1], [8192, 8192], [u32::MAX, u32::MAX]] {
|
|
assert_eq!(raster::dimensions(page, &Options { size: Some(size), ..Default::default() }), Err(Error::OutputLimit));
|
|
}
|
|
assert_eq!(raster::render_into(page, &cache, &InterpreterSettings::default(), &Options::default(), &mut [0; 4]), Err(Error::BufferLength));
|
|
}
|
|
|
|
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
|
|
#[cfg_attr(not(target_arch = "wasm32"), test)]
|
|
fn pre_cancelled_requests_never_draw() {
|
|
let doc = craft_pdf::open(pdf("0 0 1 rg 0 0 40 20 re f", ""), None).unwrap();
|
|
let settings = InterpreterSettings { cancelled: Some(Arc::new(AtomicBool::new(true))), ..Default::default() };
|
|
let mut bytes = vec![7; 40 * 20 * 4];
|
|
assert_eq!(raster::render_into(&doc.pages()[0], &RenderCache::new(), &settings, &Options::default(), &mut bytes), Err(Error::Cancelled));
|
|
assert!(bytes.iter().all(|b| *b == 7));
|
|
}
|
|
|
|
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
|
|
#[cfg_attr(not(target_arch = "wasm32"), test)]
|
|
fn injected_font_resolver_is_used() {
|
|
let doc = craft_pdf::open(
|
|
pdf("BT /F1 12 Tf 2 5 Td (ABC) Tj ET", "/Resources << /Font << /F1 << /Type /Font /Subtype /Type1 /BaseFont /Helvetica >> >> >>"),
|
|
None,
|
|
)
|
|
.unwrap();
|
|
let calls = Arc::new(AtomicUsize::new(0));
|
|
let count = calls.clone();
|
|
let original = InterpreterSettings::default().font_resolver;
|
|
let settings = InterpreterSettings {
|
|
font_resolver: Arc::new(move |query| {
|
|
count.fetch_add(1, Ordering::Relaxed);
|
|
original(query)
|
|
}),
|
|
..Default::default()
|
|
};
|
|
raster::render(&doc.pages()[0], &RenderCache::new(), &settings, &Options::default()).unwrap();
|
|
assert!(calls.load(Ordering::Relaxed) > 0);
|
|
}
|
|
|
|
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
|
|
#[cfg_attr(not(target_arch = "wasm32"), test)]
|
|
fn decode_warnings_reach_caller_and_result() {
|
|
let doc = craft_pdf::open(pdf("q 10 0 0 10 0 0 cm BI /W 32769 /H 8192 /BPC 8 /CS /RGB ID x EI Q", ""), None).unwrap();
|
|
let calls = Arc::new(AtomicUsize::new(0));
|
|
let count = calls.clone();
|
|
let settings = InterpreterSettings {
|
|
warning_sink: Arc::new(move |_| {
|
|
count.fetch_add(1, Ordering::Relaxed);
|
|
}),
|
|
..Default::default()
|
|
};
|
|
let result = raster::render(&doc.pages()[0], &RenderCache::new(), &settings, &Options::default()).unwrap();
|
|
assert!(result.diagnostics.image_decode_failed);
|
|
assert!(calls.load(Ordering::Relaxed) > 0);
|
|
}
|
|
|
|
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
|
|
#[cfg_attr(not(target_arch = "wasm32"), test)]
|
|
#[cfg(not(target_arch = "wasm32"))]
|
|
fn backend_callback_panic_is_an_error() {
|
|
let doc = craft_pdf::open(
|
|
pdf("BT /F1 12 Tf 2 5 Td (ABC) Tj ET", "/Resources << /Font << /F1 << /Type /Font /Subtype /Type1 /BaseFont /Helvetica >> >> >>"),
|
|
None,
|
|
)
|
|
.unwrap();
|
|
let settings = InterpreterSettings { font_resolver: Arc::new(|_| panic!("synthetic backend failure")), ..Default::default() };
|
|
let error = raster::render(&doc.pages()[0], &RenderCache::new(), &settings, &Options::default()).unwrap_err();
|
|
assert_eq!(error, Error::BackendPanic);
|
|
assert_eq!(error.to_string(), "PDF renderer failed internally");
|
|
}
|
|
|
|
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
|
|
#[cfg_attr(not(target_arch = "wasm32"), test)]
|
|
fn alpha_is_premultiplied_exactly_once() {
|
|
let doc = craft_pdf::open(pdf("/G gs 0 0 1 rg 0 0 40 20 re f", "/Resources << /ExtGState << /G << /ca 0.5 >> >> >>"), None).unwrap();
|
|
let r = raster::render(&doc.pages()[0], &RenderCache::new(), &InterpreterSettings::default(), &Options::default()).unwrap();
|
|
assert_eq!(&r.bytes()[0..4], &[0, 0, 128, 128]);
|
|
}
|
|
|
|
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
|
|
#[cfg_attr(not(target_arch = "wasm32"), test)]
|
|
fn annotation_policy_and_duplicate_ocg_names_remain_independent() {
|
|
let content = "/OC /Left BDC 1 0 0 rg 0 0 20 20 re f EMC /OC /Right BDC 0 0 1 rg 20 0 20 20 re f EMC";
|
|
let appearance = "0 1 0 rg 0 0 40 20 re f";
|
|
let doc=craft_pdf::open(assemble(&[
|
|
"<< /Type /Catalog /Pages 2 0 R /OCProperties << /OCGs [7 0 R 8 0 R] /D << /BaseState /ON >> >> >>".into(),
|
|
"<< /Type /Pages /Count 1 /Kids [3 0 R] >>".into(),
|
|
"<< /Type /Page /Parent 2 0 R /MediaBox [0 0 40 20] /Contents 4 0 R /Annots [5 0 R] /Resources << /Properties << /Left 7 0 R /Right 8 0 R >> >> >>".into(),
|
|
format!("<< /Length {} >>\nstream\n{content}\nendstream",content.len()),
|
|
"<< /Type /Annot /Subtype /Square /Rect [0 0 40 20] /F 4 /AP << /N 6 0 R >> >>".into(),
|
|
format!("<< /Type /XObject /Subtype /Form /BBox [0 0 40 20] /Length {} >>\nstream\n{appearance}\nendstream",appearance.len()),
|
|
"<< /Type /OCG /Name (Duplicate) >>".into(),
|
|
"<< /Type /OCG /Name (Duplicate) >>".into(),
|
|
]),None).unwrap();
|
|
let page = &doc.pages()[0];
|
|
let settings = InterpreterSettings { render_annotations: false, ocg_overrides: Arc::new(vec![(7, 0, false)]), ..Default::default() };
|
|
let hidden = raster::render(page, &RenderCache::new(), &settings, &Options::default()).unwrap();
|
|
assert_eq!(&hidden.bytes()[0..4], &[0; 4]);
|
|
assert_eq!(&hidden.bytes()[120..124], &[0, 0, 255, 255]);
|
|
let visible = raster::render(page, &RenderCache::new(), &InterpreterSettings::default(), &Options::default()).unwrap();
|
|
assert_eq!(&visible.bytes()[0..4], &[0, 255, 0, 255]);
|
|
let comments_hidden =
|
|
raster::render(page, &RenderCache::new(), &InterpreterSettings { hide_comments: true, ..Default::default() }, &Options::default()).unwrap();
|
|
assert_eq!(&comments_hidden.bytes()[0..4], &[255, 0, 0, 255]);
|
|
}
|
|
|
|
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
|
|
#[cfg_attr(not(target_arch = "wasm32"), test)]
|
|
fn colour_replacement_is_explicit_and_preserves_alpha() {
|
|
let doc = craft_pdf::open(pdf("/G gs 0 0 0 rg 0 0 40 20 re f", "/Resources << /ExtGState << /G << /ca 0.5 >> >> >>"), None).unwrap();
|
|
let page = &doc.pages()[0];
|
|
let cache = RenderCache::new();
|
|
let original = raster::render(page, &cache, &InterpreterSettings::default(), &Options::default()).unwrap();
|
|
assert_eq!(&original.bytes()[0..4], &[0, 0, 0, 128]);
|
|
let replacement = raster::ColorReplacement { text: [255, 0, 0], background: [255; 3], only_black: true, line_art: true };
|
|
let changed =
|
|
raster::render(page, &cache, &InterpreterSettings::default(), &Options { color_replacement: Some(replacement), ..Default::default() })
|
|
.unwrap();
|
|
assert_eq!(&changed.bytes()[0..4], &[128, 0, 0, 128]);
|
|
let restored = raster::render(page, &cache, &InterpreterSettings::default(), &Options::default()).unwrap();
|
|
assert_eq!(original.bytes(), restored.bytes());
|
|
}
|
|
|
|
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
|
|
#[cfg_attr(not(target_arch = "wasm32"), test)]
|
|
fn malformed_stream_completion_warning_reaches_raster_and_caller() {
|
|
let doc = craft_pdf::open(
|
|
assemble(&[
|
|
"<< /Type /Catalog /Pages 2 0 R >>".into(),
|
|
"<< /Type /Pages /Kids [3 0 R] /Count 1 >>".into(),
|
|
"<< /Type /Page /Parent 2 0 R /MediaBox [0 0 40 20] /Contents 4 0 R >>".into(),
|
|
"<< /Length 1 /Filter /FlateDecode >>\nstream\nx\nendstream".into(),
|
|
]),
|
|
None,
|
|
)
|
|
.unwrap();
|
|
let calls = Arc::new(AtomicUsize::new(0));
|
|
let count = calls.clone();
|
|
let settings = InterpreterSettings {
|
|
warning_sink: Arc::new(move |w| {
|
|
if matches!(w, craft_pdf::interpret::InterpreterWarning::ContentTruncated) {
|
|
count.fetch_add(1, Ordering::Relaxed);
|
|
}
|
|
}),
|
|
..Default::default()
|
|
};
|
|
let result = raster::render(&doc.pages()[0], &RenderCache::new(), &settings, &Options::default()).unwrap();
|
|
assert!(result.diagnostics.content_truncated);
|
|
assert!(calls.load(Ordering::Relaxed) > 0);
|
|
}
|
|
|
|
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
|
|
#[cfg_attr(not(target_arch = "wasm32"), test)]
|
|
fn preview_settings_and_cache_preserve_final_image_quality() {
|
|
let content = "q 10 0 0 10 20 5 cm /Im0 Do Q q 0.5 0 0 0.5 2 2 cm /Im0 Do Q";
|
|
let pixels = "A".repeat(4 * 4 * 3);
|
|
let doc = craft_pdf::open(assemble(&[
|
|
"<< /Type /Catalog /Pages 2 0 R >>".into(),
|
|
"<< /Type /Pages /Kids [3 0 R] /Count 1 >>".into(),
|
|
"<< /Type /Page /Parent 2 0 R /MediaBox [0 0 40 20] /Contents 4 0 R /Resources << /XObject << /Im0 5 0 R >> >> >>".into(),
|
|
format!("<< /Length {} >>\nstream\n{content}\nendstream", content.len()),
|
|
format!("<< /Type /XObject /Subtype /Image /Width 4 /Height 4 /ColorSpace /DeviceRGB /BitsPerComponent 8 /Length {} >>\nstream\n{pixels}\nendstream", pixels.len()),
|
|
]), None).unwrap();
|
|
let page = &doc.pages()[0];
|
|
let cache = RenderCache::new();
|
|
let preview = raster::render(page, &cache, &InterpreterSettings { preview: true, ..Default::default() }, &Options::default()).unwrap();
|
|
let final_after_preview = raster::render(page, &cache, &InterpreterSettings::default(), &Options::default()).unwrap();
|
|
let fresh_final = raster::render(page, &RenderCache::new(), &InterpreterSettings::default(), &Options::default()).unwrap();
|
|
assert_ne!(preview.bytes(), fresh_final.bytes(), "the tiny image is skipped only for a preview");
|
|
assert_eq!(final_after_preview.bytes(), fresh_final.bytes(), "preview cache must not degrade final output");
|
|
assert!(fresh_final.bytes().as_chunks::<4>().0.iter().filter(|p| **p == [0x41, 0x41, 0x41, 255]).count() > 50);
|
|
}
|
|
|
|
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
|
|
#[cfg_attr(not(target_arch = "wasm32"), test)]
|
|
fn document_decode_limit_warns_for_pages_and_nested_painting() {
|
|
// ASCIIHex wraps a RunLength packet of 128 spaces. The valid stream is cut to
|
|
// 32 bytes only by the lowered document policy, with no malformed operator.
|
|
let filter = "/Filter [/ASCIIHexDecode /RunLengthDecode]";
|
|
let encoded = "812080";
|
|
for kind in ["page", "form", "pattern", "type3"] {
|
|
let (content, resources, extras) = match kind {
|
|
"page" => (encoded.to_owned(), String::new(), Vec::new()),
|
|
"form" => ("/Fm Do".into(), "/Resources << /XObject << /Fm 5 0 R >> >>".into(), vec![
|
|
format!("<< /Type /XObject /Subtype /Form /BBox [0 0 40 20] /Length {} {filter} >>\nstream\n{encoded}\nendstream", encoded.len())
|
|
]),
|
|
"pattern" => ("/Pattern cs /P scn 0 0 40 20 re f".into(), "/Resources << /Pattern << /P 5 0 R >> >>".into(), vec![
|
|
format!("<< /Type /Pattern /PatternType 1 /PaintType 1 /TilingType 1 /BBox [0 0 40 20] /XStep 40 /YStep 20 /Resources << >> /Length {} {filter} >>\nstream\n{encoded}\nendstream", encoded.len())
|
|
]),
|
|
_ => ("BT /F 10 Tf 1 0 0 1 10 10 Tm (A) Tj ET".into(), "/Resources << /Font << /F 5 0 R >> >>".into(), vec![
|
|
"<< /Type /Font /Subtype /Type3 /FontBBox [0 0 500 500] /FontMatrix [0.001 0 0 0.001 0 0] /CharProcs << /A 6 0 R >> /Encoding << /Type /Encoding /Differences [65 /A] >> /FirstChar 65 /LastChar 65 /Widths [500] /Resources << >> >>".into(),
|
|
format!("<< /Length {} {filter} >>\nstream\n{encoded}\nendstream", encoded.len()),
|
|
]),
|
|
};
|
|
let content_filter = if kind == "page" { filter } else { "" };
|
|
let mut objects = vec![
|
|
"<< /Type /Catalog /Pages 2 0 R >>".into(),
|
|
"<< /Type /Pages /Kids [3 0 R] /Count 1 >>".into(),
|
|
format!("<< /Type /Page /Parent 2 0 R /MediaBox [0 0 40 20] /Contents 4 0 R {resources} >>"),
|
|
format!("<< /Length {} {content_filter} >>\nstream\n{content}\nendstream", content.len()),
|
|
];
|
|
objects.extend(extras);
|
|
let data = assemble(&objects);
|
|
for low in [false, true] {
|
|
let decode_limits = if low { craft_pdf::syntax::DecodeLimits::default().with_max_decoded_stream(32) } else { Default::default() };
|
|
let options = craft_pdf::OpenOptions { decode_limits, ..Default::default() };
|
|
let doc = craft_pdf::open_with_options(data.clone(), None, &options).unwrap();
|
|
let calls = Arc::new(AtomicUsize::new(0));
|
|
let count = calls.clone();
|
|
let settings = InterpreterSettings {
|
|
warning_sink: Arc::new(move |w| {
|
|
if matches!(w, craft_pdf::interpret::InterpreterWarning::ContentTruncated) {
|
|
count.fetch_add(1, Ordering::Relaxed);
|
|
}
|
|
}),
|
|
..Default::default()
|
|
};
|
|
let result = raster::render(&doc.pages()[0], &RenderCache::new(), &settings, &Options::default()).unwrap();
|
|
assert_eq!(result.diagnostics.content_truncated, low, "{kind}: low={low}");
|
|
assert_eq!(calls.load(Ordering::Relaxed) > 0, low, "{kind}: caller warning");
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
|
|
#[cfg_attr(not(target_arch = "wasm32"), test)]
|
|
fn failed_form_decode_reports_incomplete_content() {
|
|
let doc = craft_pdf::open(
|
|
assemble(&[
|
|
"<< /Type /Catalog /Pages 2 0 R >>".into(),
|
|
"<< /Type /Pages /Kids [3 0 R] /Count 1 >>".into(),
|
|
"<< /Type /Page /Parent 2 0 R /MediaBox [0 0 40 20] /Contents 4 0 R /Resources << /XObject << /Fm 5 0 R >> >> >>".into(),
|
|
"<< /Length 6 >>\nstream\n/Fm Do\nendstream".into(),
|
|
"<< /Type /XObject /Subtype /Form /BBox [0 0 40 20] /Length 1 /Filter /DCTDecode >>\nstream\nx\nendstream".into(),
|
|
]),
|
|
None,
|
|
)
|
|
.unwrap();
|
|
let result = raster::render(&doc.pages()[0], &RenderCache::new(), &InterpreterSettings::default(), &Options::default()).unwrap();
|
|
assert!(result.diagnostics.content_truncated);
|
|
}
|