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

Can we load and render SVGs? #61

Closed iocoker closed 1 year ago

iocoker commented 1 year ago

Hi there, I just discovered pdfium-render a couple of days ago via the new ChatGPT.

Is it possible to load and render SVG strings with this library bindings? We basically generate PDFs for printing industry and are currently on PDFBox, just looking for alternatives that can handle SVGs natively with color space mappings, overprint etc. Thanks!

ajrcarey commented 1 year ago

Hi @iocoker,

pdfium-render aims to support all the functionality provided by the upstream Pdfium library. To my knowledge, Pdfium does not support creating PDF page objects directly from SVGs.

However, paths in the PDF file format and SVGs are very closely related (they are both descended from Postscript). It should be relatively straight-forward to import them into a PDF page path object. SVG supports more functionality than PDF can accommodate (e.g. animations), so you will not be able to import everything.

You will need to use a separate crate to iterate over each segment in each path in the source SVG; the svg crate (https://crates.io/crates/svg) looks promising. As you iterate over each segment, you can add a corresponding segment to a path object using pdfium-render. It would look something like this (pseudocode):

let pdfium = Pdfium::new(
    Pdfium::bind_to_library(Pdfium::pdfium_platform_library_name_at_path("./"))
        .or_else(|_| Pdfium::bind_to_system_library())?,
    );

let document = pdfium.create_new_pdf()?;

let mut page = document.pages().create_page_at_end(PdfPagePaperSize::a4())?;

let mut path = PdfPagePathObject::new(
    &document,
    PdfPoints::ZERO, // far left ...
    PdfPoints::ZERO, // ... and bottom of page
    Some(PdfColor::SOLID_RED),
    Some(PdfPoints::new(1.0)),
    Some(PdfColor::SOLD_MAGENTA)
);

for each segment in your svg {
    match segment.type() {
        SVG_SEGMENT_TYPE_MOVE(x, y) => path.move_to(PdfPoints::new(x), PdfPoints::new(y)),
        SVG_SEGMENT_TYPE_LINE(x, y) => path.line_to(PdfPoints::new(x), PdfPoints::new(y)),
        SVG_SEGMENT_TYPE_BEZIER(cx1, cy1, cx2, cy2, x, y) => path.bezier_to(
            PdfPoints::new(x),
            PdfPoints::new(y),
            PdfPoints::new(cx1),
            PdfPoints::new(cy1),
            PdfPoints::new(cx2),
            PdfPoints::new(cy2)
        ),
        ...
    }
}

page.objects_mut().add_path_object(path)?;

document.save_to_file("/path/to/output.pdf");

The PDF format uses a coordinate system where (0,0) is the extreme bottom-left of the page. This is vertically flipped from the usual screen space coordinate system where (0,0) is the top-left of the screen. I am not sure what coordinate system SVG uses, but you may need to flip your vertical coordinates.

Each path object on a PDF page has a single stroke and fill. SVG is probably a bit more sophisticated than this. If you need to change stroke and/or fill colors, you will need to close off the current path object and start a new one.

If you just need to convert a static SVG to a PDF file, there is already a crate that does this: https://crates.io/crates/svg2pdf. You may also wish to consider non-Rust command line tools, such as Batik: https://xmlgraphics.apache.org/batik/

Tell me more about your color requirements. Google took over the Pdfium project from Foxit so they could release an open source renderer that they could use in Chromium and Android. Their focus is almost entirely on screen rendering; I would be hesistant to make any promises about Pdfium's usefulness when working with non-RGB colorspaces. I'm not even sure Pdfium supports specifying a color that isn't in the RGB colorspace when creating new objects; I'd need to check. (It does support reading colors in other colorspaces when parsing existing objects in existing PDF files.)

iocoker commented 1 year ago

Thanks so much for the detailed clarification @ajrcarey.

Yes, I'm aware of the PDF coordinate systems, we'll possibly use matrix transforms for all that. The other frameworks don't support SVGs natively either, so for PDFBox, we use Batik via a bridge: https://github.com/rototor/pdfbox-graphics2d, just wanted better performance which Rust can offer.

We usually render objects in different color spaces based on the customer's requirements, RGB or CMYK for process printing and in other instances, specify spot color names for single ink. We also require color mapping for specific target color spaces.

So for instance, a design intended for print on a news paper material and a coated paper will require the colors to be adjusted differently for both medium as a newspaper material will soak more ink and is not pure white. That is usually done using icc color mapping.

ajrcarey commented 1 year ago

I really wonder if Pdfium has the color support you need. All color specification for page objects, including paths, is limited to RGBA. Pdfium can read colorspace information for existing images only (not paths), but does not let you specify colorspace information when creating a new object. Is that enough for what you want to do?

ajrcarey commented 1 year ago

Actually https://docs.rs/usvg/latest/usvg/index.html looks like an even better match because it simplifies all paths down to the basic MoveTo, LineTo, CurveTo, Close commands that PDF supports. This reduces the complexity of the conversion task substantially.

Dealing with text, specifically fonts, could be an interesting challenge.

I still believe color handling is your biggest problem. Pdfium may not offer the features you require.

If you have a sample SVG file you are able to share, that would be good.

iocoker commented 1 year ago

Yes you're right, handling color spaces will be the limiting factor.

For SVGs, we like to cover most features in SVG 1.1 with the exception of filters and embedded images. Any SVG with such, is flattened into a raster with high density and the customer is notified of such.

Thanks so much, we'll give it a try and keep this updated as we go along