Looking for a Prawn-Like PDF Generation Library in Elixir

I’m in search of an Elixir library that offers PDF generation capabilities similar to Ruby’s Prawn. While there have been discussions about this topic in the past, I’m interested in any recent developments or current libraries that the community recommends. Key features I’m looking for include support for complex layouts, text styling, and custom tables. Any insights or suggestions would be greatly appreciated!

3 Likes

There (sadly) is no real equivalence to Prawn for Elixir.

There are 2 libraries, pdf and mudbrick. The first is more mature, uses GenServers to store the state of the pdf being build, where the second one is very new and still lacking a lot of features, but it uses a struct to store the state.
Mudbrick is easy to extend with your own features, where pdf is practically impossible to extend.
Mudbrick supports OpenType (otf) fonts, pdf, adobe type mananger (atm).

If you do not want to shell out to something like htmltopdf, these are currently the most feasible options.

The font-handling in pdf was recently rewritten, making it compile several times faster.

I have been using pdf for several years, tried mudbrick, I like it, but is not yet there.

10 Likes

If You really miss prawn, You can run it in Elixir with erlport…

3 Likes

We have had great success with https://wkhtmltopdf.org/

Within our phoenix application, a simple GenServer calls out to the wkhtmltopdf binary, passing along generated html and returning the resulting pdf file.

The html itself is generated from a bunch of $template.html.heex files and for each new PDF document type we simply add a new *.html.heex file.

We produce a lot of different types of pdf reports, and regularly add new ones so this approach (converting html → pdf) makes it easy to assign that task to anyone who can write html, and allows us to produce some very rich, and complex page / document designs.

7 Likes

Nothing can compare to the production experience of GitHub - bitcrowd/chromic_pdf: Convenient HTML to PDF/A rendering library for Elixir based on Chrome & Ghostscript imo

8 Likes

I wanted to bring it up but assumed they were not okay with the Chromium dependence.

1 Like

Here we are using GitHub - gutschilla/elixir-pdf-generator: Create PDFs with wkhtmltopdf or puppeteer/chromium from Elixir. for simple PDF and it has been working fine for the last years

2 Likes

+1 for wkhtmltopdf

1 Like

Nice. Didn’t know about mudbrick. A modern native Elixir PDF writer would be so cool for the ecosystem.

1 Like

It depends what you’re looking for, but we use typst (https://typst.app/). We use it to generate high quality output for work instructions (images, pixel perfect layout, table of contents etc etc). Having used it for that, I’d also use it for things like database reports, invoice generation etc. It’s also deterministic - it doesn’t rely on browser engines etc to interpret CSS, so once you have placed elements on a page, they stay put.

There’s a command-line version that compiles to a single binary (it’s written in Rust, so easy enough to add to your docker builder image for most platforms [I think that’s the lingo - I’m no docker whiz]. Build time is a couple of minutes, but the guys at Alembic designed the docker build to minimise the likelihood that it needs to be recompiled for each deploy). We’re currently just calling it with System.cmd. The main downer is you have to assemble all the inputs (e.g. images, JSON data extracts) into the directory where the main .typ file is, so no referencing images on the web. There are Elixir bindings, but for our use-case isn’t wasn’t worth the extra dependency.

The output quality is awesome (it is designed as a Latex successor). Coding up the layout is very similar to composing views with Phoenix function components. Some of the syntax takes a bit of getting used to, but it’s still the least fighting I’ve been through to get nice quality output from a database to a PDF or printer, by a long way, in >30 years of doing this kind of stuff.

Edit: PS - tables allow fine control on layout, styling, row/col-spans etc.

13 Likes

Took a look at what I used last time around, and it was https://weasyprint.org, probably due to the nice templates out of the box… you render the html, path to the css file, and call it using Rambo (you might need {:rambo, github: "/myobie/rambo", branch: "aarch64-apple"},)…

defmodule RamboPdf do
  def create_pdf(item) do
    css = Path.join([PDFfile.path(), "templates/invoice", "invoice.css"])

    # grab data, and EEx.eval_file with the template file
    html =
      PDF.gen_data(item)
      |> PDF.populate_html("templates/invoice", "invoice.html")

    output_file = Path.join([PDFfile.path(), "output", "#{item.room}.pdf"])
    # https://doc.courtbouillon.org/weasyprint/stable/api_reference.html#command-line-api

    task =
      Task.async(fn ->
        Rambo.run("weasyprint", ["--encoding", "utf8", "-q", "-s", css, "-", "-"],
          in: html,
          log: false,
          timeout: 20_000
        )

        # System.cmd("weasyprint", ["--encoding", "utf8", "-q", "-s", css, "-", "-", html])
      end)

    task_output = Task.await(task, :infinity)

    case task_output do
      {:ok, %Rambo{err: _err, out: output, status: 0}} ->
        File.write(output_file, output, [:binary])
        output

      error ->
        IO.inspect("error")
        IO.inspect(error)
    end
  end
end
3 Likes

+1 to what @mindok said - I’ve also had great success shelling out to Typst.

I tried my best to look around for options before making the decision, and Typst fit the sweetspot for me since it’s lightweight, easy to use, very quick, and capable of complicated layouts. Using a headless browser is always an option, but it added more complexity and grew my docker images more than I liked.

You can have a look at Search — Typst: Universe for lots of example Typst templates, so you can get a feeling for what you can do with it.

6 Likes

These guys wrote a blog post about mass generating contract notes (about 1.5M per trading day) for a financial institution using typst: 1.5+ million PDFs in 25 minutes - Zerodha Tech Blog. A different tech stack, but similar concept.

6 Likes

Very interesting discussion. My two cents:

  1. PDF generation is a very important feature for any enterprise application still. However, most developers would want to generate the pdf out of html, css whenever possible.
  2. That said, prawn is a great ruby library. Using it via erlport may be a nice solution. However, if we are going via erlport sort of a solution, weaslyprint is a much better option I think. Mainly because, it can take html, css mix and generate pdf output.
  3. wkhtmlpdf has been a great solutoin - but - age is catching up with it. Very slow - not respecting the modern HTML - lack of full control over the output - are my main cribs about it.
  4. Typst is a great alternative. I have gone through that article on how Zerodha used it. But, I am afraid for a regular application - how can typst bring value? When you have to generate pdfs at such volumes - format same - data different - yes typst might be useful. But, if you have some 40 models - and - each of their list view in print needs slightly different variations - building a typst format for each of them - and maintaining the code might be difficult. Developer would prefer a library that takes the same html that he generated for the list view to be taken - and if he gets a reasonable pdf - that might help him.
  5. Headless browser sort of solutions are quite irritating. Atleast my experience has been that. Lack of any control over what gets into pdf - specially - if we are rendering tables that run into multiple pages - it is absolutely nightmare.
    Definitely a modern solution for the pdf generation problem is due. Let us hope this discussion stimulates something.
3 Likes

I think the assumption that developers want to use html to generate PDFs is not true for a lot of cases. The main argument is as a lot of people point above, there are lots of use-cases where you care about your pages layout, controlling that in html is not possible.

Compared to other languages, you have a powerful templating engine at your disposal: EEx. You can use it to define highly configurable templates, so I’m not sure there is anything different from how you would use phoenix.

I’ve used latex before to compile some huge report PDFs using EEx templates, it worked absolutely great. If I remember correctly I was using iona, but I forked and added some additional features it was missing.

The problem was that latex compiler is just horrible, both performance-wise and how it operates, so having better alternatives these days is absolutely great.

6 Likes

I agree. It was an overly generic statement to say that developers want pdf from html and css. All I wanted to say is configurability and ease of generation - both are quite important in the solution. It did not come out correctly.
I agree - building configurable templates does provide a scalable and modern solution - that does not rely on the headless browser - can be a viable solution.

3 Likes

Just wanted to share above that shows typst usage with elixir / liveview.

Only think missing will be to use Eex to precompile templates but maybe the additional complexity not necessary for smaller projects.

3 Likes

This sounds like an interesting project that I might pick up this winter, as I will need an alternative latex tool for some of my personal projects, where generation from html doesn’t cut it.

3 Likes

FWIW the way I went about creating configurable templates in Typst was to use the json function.

I simply have Elixir write a JSON file with the context data, and the .typst file then makes use of it like this:

context.json

{"x": 42}

document.typst

#let ctx = json("context.json")

This is a Typst document, and x is #ctx.x

Like @D4no0 mentioned we also got access to great EEx templates. I’m not sure why I didn’t think of doing it that way. I sounds like a pretty clever solution.

5 Likes

Throwing my two cents for Typst as well. I’ve been using it with all stacks. But with EEx the experience is much smoother. I have been using it exactly like @ErikNaslund mentioned.
I have to give a shout out to ex_typst | Hex as well, if you want to programmatically do things with Typst this is great.

1 Like