diff --git a/Cargo.lock b/Cargo.lock index ca64fd79..d43c0e0b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3745,6 +3745,7 @@ dependencies = [ name = "pdfcraft-render" version = "0.4.0" dependencies = [ + "bytemuck", "getrandom 0.4.3", "hayro", "hayro-syntax", diff --git a/crates/render/Cargo.toml b/crates/render/Cargo.toml index fa68a064..0a42520e 100644 --- a/crates/render/Cargo.toml +++ b/crates/render/Cargo.toml @@ -16,6 +16,8 @@ lopdf = { workspace = true } kurbo = { workspace = true } thiserror = { workspace = true } log = { workspace = true } +# Moves rendered pixels out of the renderer's pixmap without copying; already in the tree through hayro. +bytemuck = { version = "1", features = ["extern_crate_alloc"] } # lopdf (bootstrap) draws random bytes for encryption; on the web they come from the browser. [target.'cfg(target_arch = "wasm32")'.dependencies] diff --git a/crates/render/src/raster.rs b/crates/render/src/raster.rs index 716d1ddd..f9512df4 100644 --- a/crates/render/src/raster.rs +++ b/crates/render/src/raster.rs @@ -17,6 +17,7 @@ use hayro::hayro_interpret::InterpreterSettings; use hayro::hayro_interpret::font::{FontData, FontQuery}; use hayro::hayro_interpret::hayro_cmap::CidFamily; use hayro::hayro_syntax::Pdf; +use hayro::vello_cpu::Pixmap; use hayro::vello_cpu::color::palette::css::WHITE; use hayro::{RenderCache, RenderSettings, render}; @@ -222,7 +223,7 @@ fn render_page<'a>(pdf: &'a Pdf, cache: &RenderCache<'a>, settings: &Interpreter } }; let pixmap = render(page, cache, settings, &rs); - Ok((pixmap.width() as u32, pixmap.height() as u32, pixmap.data_as_u8_slice().to_vec(), None)) + Ok((pixmap.width() as u32, pixmap.height() as u32, into_bytes(pixmap), None)) })); match result { Ok(Ok(v)) => Ok(v), @@ -231,6 +232,13 @@ fn render_page<'a>(pdf: &'a Pdf, cache: &RenderCache<'a>, settings: &Interpreter } } +/// The pixmap's premultiplied RGBA bytes, moved out rather than copied (a page raster can be +/// hundreds of megabytes). Four one-byte channels always recast to bytes in place; the copy is +/// only there because the cast's signature allows failure. +fn into_bytes(pixmap: Pixmap) -> Vec { + bytemuck::allocation::try_cast_vec(pixmap.take()).unwrap_or_else(|(_, pixels)| pixels.iter().flat_map(|p| [p.r, p.g, p.b, p.a]).collect()) +} + fn finish(req: RenderRequest, start: Stopwatch, r: Result) -> RenderedPage { let millis = start.millis(); match r { @@ -732,6 +740,18 @@ trailer << /Root 1 0 R >> assert_eq!(px(80, 5), &[255, 255, 255, 255]); } + #[test] + fn rendered_pixels_are_moved_out_of_the_pixmap() { + let mut pixmap = Pixmap::new(3, 2); + for (i, b) in pixmap.data_as_u8_slice_mut().iter_mut().enumerate() { + *b = i as u8; + } + let (at, expected) = (pixmap.data_as_u8_slice().as_ptr(), pixmap.data_as_u8_slice().to_vec()); + let bytes = into_bytes(pixmap); + assert_eq!(bytes, expected); + assert_eq!(bytes.as_ptr(), at, "the pixmap's own buffer, not a copy"); + } + #[test] fn missing_page_and_garbage_input_fail_gracefully() { let mut r = PageRenderer::new(Arc::new(ONE_PAGE.to_vec()), RenderConfig::default());