ajrcarey / pdfium-render

A high-level idiomatic Rust wrapper around Pdfium, the C++ PDF library used by the Google Chromium project.
https://crates.io/crates/pdfium-render
Other
364 stars 59 forks source link

Zero copy rendering #83

Closed DorianRudolph closed 1 year ago

DorianRudolph commented 1 year ago

I would like to render a PdfPage into an external buffer via a raw pointer. My use case is to render directly into a skia bitmap. This way I don't have to copy the data from the PdfBitmap to the skia bitmap, saving one copy.

Right now there does not appear to be a way to give pdfium-render a pointer or slice to render into.

ajrcarey commented 1 year ago

Hi @DorianRudolph , yes, Pdfium does support rendering into an external buffer. We would need to add a new constructor to PdfBitmap. Are you wanting to provide a buffer in the form of a &mut [u8] slice?

(This approach is fundamentally incompatible with WASM because Pdfium and pdfium-render inhabit separate memory address spaces when running in the browser.)

ajrcarey commented 1 year ago

Under the assumption that you will be providing a &mut [u8], added a new PdfBitmap::external() function that lets you pass in a buffer. You can test the function by using pdfium-render as a git dependency in your project's Cargo.toml.

DorianRudolph commented 1 year ago

Thanks, this is what I had in mind. I should be able to test it sometime this week.

Eventually I would like to get my project running in the web. Why would this not work in WASM? I guess your concern is that pdfium and pdfium-render are compiled to two different .wasm modules? I'm just learning about WASM, but could something like this work to establish a shared memory between WASM modules?

ajrcarey commented 1 year ago

Because Pdfium and pdfium-render are compiled separately, they inhabit independent WASM modules with independent linear address spaces. In theory it might be possible to combine these; in practice I highly doubt it could ever work safely, because the two modules have two different memory allocators that would have no knowledge of one-another and would therefore be endlessly clobbering one another's memory allocations.

So long as the WASM memory address spaces are separate, it will never be possible to share a buffer between Pdfium and pdfium-render without a copy.

Because Javascript does have access to all memory irrespective of WASM modules, it is possible to work around this to a certain degree. The WASM-specific PdfBitmap::as_array() function lets you pass through the array data of a Pdfium FPDF_BITMAP buffer directly to Javascript without copying it into pdfium-render's address space first.

My suggestion is that you have two code paths: one using PdfBitmap::external() for your non-WASM builds, and one using PdfBitmap::as_array() for your WASM build.

You are welcome to try getting Pdfium and pdfium-render to share a single linear address space, but I would not be able to help you with this.

I will rename the PdfBitmap::external() function in the next commit, and restrict it to non-WASM builds only.

ajrcarey commented 1 year ago

Renamed PdfBitmap::external() as PdfBitmap::from_bytes(). Restricted compilation to non-WASM targets.

ajrcarey commented 1 year ago

Added unit test for PdfBitmap::from_bytes(). As there have been no further comments, and I believe PdfBitmap::from_bytes() provides the functionality you require, I am closing the issue. Feel free to reopen if you feel the issue has not been resolved.

DorianRudolph commented 1 year ago

Yes thank you it works for me.

Btw, this is how I render into a skia image:

let w: f32 = page.width().value;
let h = page.height().value;
let scale = (2_000_000.0 / (w * h)).sqrt();
let width = (w * scale) as i32;
let height = (h * scale) as i32;
let data = unsafe { Data::new_uninitialized((width * height * 4) as usize) };
assert!(data.inner()._base.fRefCnt == 1);

let mut bitmap = unsafe {
  let mut_data = std::slice::from_raw_parts_mut(data.inner().fPtr as _, data.size());
  PdfBitmap::from_bytes(width, height, PdfBitmapFormat::BGRA, mut_data, pdfium.bindings())
    .expect("Could not create bitmap")
};
page
  .render_into_bitmap(&mut bitmap, width, height, None)
  .expect("Could not render page");

let size = ISize::new(width, height);
let info = ImageInfo::new(size, ColorType::RGBA8888, AlphaType::Premul, None);  // need RGB and not BGR, not sure why
let image = Image::from_raster_data(&info, data, (width * 4) as usize).unwrap();