maltoe

maltoe

ChromicPDF - PDF generator

Hello!

Came here to announce ChromicPDF, a pet project PDF generator I’ve been working on for the past few months. Why another PDF generator, you may be asking? Because it was fun to implement a client for (small parts of) the Chrome DevTools Protocol in Elixir.

That’s basically Chromic’s main feature, it can talk to Chrome without the need for puppeteer / a NodeJS at runtime. It launches Chrome, spawns a number of targets that it keeps around, and to print a PDF instructs them to navigate to a URL. In sum, this makes for relatively performant PDF generation flow.

For good measure, I threw in a PDF/A converter using Ghostscript, capable of creating veraPDF-test-passing PDF/A-3b files - obviously inspired by a project where we needed to generate invoice PDFs for long-term storage.

Would be super interested in any opinion you might have! It’s not (yet) on hex.pm, will wait a bit to see if people are interested. Documentation can be found in the README and, oddly, in the Supervisor module.

cheers,
Malte

:tada:

Also kudos to my employer for letting us work on pet projects :+1:

First 10 of 63 Posts Switch mode

dino

dino

Hi maltoe,

This looks like an interesting project, kudos on building it! :+1:

I’ve got some feedback after reading the project’s README.
When calling the ChromicPDF.print_to_pdfa function you’re providing a map in the print_to_pdf argument, however the specific map keys are in camel case while in Elixir I think it’s more standard/common to use snake case, so for example, you’d use:

print_to_pdf: %{
    # Margins are in given inches
    margin_top: 0.393701,
    margin_left: 0.787402,
    margin_right: 0.787402,
    margin_bottom: 1.1811,
   ...
}

instead of the more javascript-like one in the example:

print_to_pdf: %{
    # Margins are in given inches
    marginTop: 0.393701,
    marginLeft: 0.787402,
    marginRight: 0.787402,
    marginBottom: 1.1811,
    ...
}

This might just be nitpicking but I thought I should let you know! Congrats once again in building this :slight_smile:

Phillipp

Phillipp

Have not read the code nor do I know about the Chrome api, but maybe those options will be passed directly to the API and he just wanted to avoid manually converting from snake case to camel case.
If that is the case, I personally would leave it as it is as it follows the API and people who are familiar with the API would have an easier time working with it. However, if the goal is to completely abstract that, then having them in snake case would be better.

It depends on the viewpoint.

egze

egze

Great work. The fewer dependencies - the better.

Release it to hex!

I wish there was also a maintained library that writes PDFs without any dependencies, even Chrome. Straight to binary.

NobbZ

NobbZ

This is quite a tough one… You need something to either render the fonts into a path that you can embed into the PDF, or into something that you can embed as a font into the PDF.

egze

egze

There is for example PrawnPDF in Ruby GitHub - prawnpdf/prawn: Fast, Nimble PDF Writer for Ruby · GitHub and they do it without a browser or other libs. I always wondered why there is nothing like this in Elixir.

maltoe

maltoe

@dino @Phillipp is correct, these are passed directly to Chrome and I didn’t want to convert them, or have to maintain a list of them in the library, etc. Perhaps I should convert them to string keys in the README to make this more clear? What do you think?

@egze There is Gutenex which doesn’t have any dependencies and writes PDF. However, PDF is a pretty complex format and writing PDF directly also means you have to come up with a proper text processing API (i.e. something like LaTeX), and in the end teach your users/designers to use it.

IMO this is a call you have to make again with every project that has PDF rendering requirements. Rendering quality is likely to be better with a text processor (not saying Chrome doesn’t render “good enough” PDFs, just that with LaTeX you can do even better); but rendering HTML to PDF gives you the benefit that your designers can easily write templates in their usual environment and new developers can easily make changes without having to learn/adapt to a text processing language. Plus, browser support for print stylesheets and printing webpages in general has become much better in the last years; for example, Chrome automatically renders table headers again on new pages (if your table has proper <thead> tags).

egze

egze

@maltoe I was wondering, how does it work with Chrome with many parallel calls to create a PDF? Is the same Chrome instance reused, or is one started every time? How many parallel calls can it handle?

dino

dino

I don’t know if converting the keys to strings would make this more clear, so I’d leave it as is :slight_smile:

maltoe

maltoe

@egze The same Chrome instance is reused. It has multiple tabs open that are part of a session pool (using poolboy for now), so it can handle multiple calls in parallel. However, there is still only a single connection between Elixir and the Chrome instance, so in theory that could become a bottleneck, although there isn’t much communication happening and I assume most time is spent inside the tab processes when navigating the page / generating the PDF. It’s essentially puppeteer in Elixir :-). Of course, very limited in terms of the functionality it supports from the Chrome API, but conceptionally it works the same.

How many parallel calls: I don’t know, I’ve set the number of workers in the session pool to 5, for no particular reason. Generally speaking, I haven’t done much benchmarking yet, only compared (eye-balled) it against printing a PDF from the command line with Chrome (i.e. spawning an instance every time) and ChromicPDF is definitely magnitudes faster.

maltoe

maltoe

@egze If you would want to maximize “PDF throughput”, you could use the {:html, <html blob>} way of passing input, which in turn calls the Page.setDocumentContent function to replace a tab’s contents.

I’ve also been thinking about connecting it to a LiveView to have it only mutate the DOM in places where it needs to (say you’re generating invoice PDFs and you only need to replace the addressee and order items) → This would probably result in the lowest latency between call and PDF printed, though it’s a bit difficult to synchronize the LiveView-based page mutation and the Page.printToPDF call. Also, this is probably just me looking for intesting problems to solve, not a realistic use-case :slight_smile:

Where Next?

Trending in Announcing Top

bluzky
You may know https://ui.shadcn.com/, a UI component library for React. I really love it’s design style and components. I’ve built some co...
387 14960 120
New
JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
shahryarjb
The Chelekom project is a library of Phoenix and LiveView components generated via Mix tasks to fit developer needs seamlessly. One of i...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge &amp; Solve. They are GUI (Emerge) and State management (S...
New
ausimian
Emily is an Elixir library that runs Nx computations on Apple’s MLX. Install it as the default Nx backend and Nx, defn, Axon, Nx.Serving,...
New
quatermain
Hello, I’m sharing my plugin here in forum after some time so it has time to mature and proof yourself. I use Claude Code daily on a pr...
New

Other Trending Topics Top

type1fool
I just stumbled on a newly redesigned elixir-lang.org. :tada: It looks like @Software_Mansion did the work, and I think it is generally a...
New
akoutmos
@hugobarauna and I (Alex Koutmos) have been hard at work on writing a book on Nerves that takes you from simply blinking LEDs to building...
New
juhalehtonen
There has been a thread to discuss the Stack Overflow Developer Survey on this forum every year since 2018, so here’s yet another one for...
New
bjorng
We want to introduce a new native datatype to Erlang: native records. Although replacing all tuple records with native records is not our...
New
spammy
I’m looking to build a personal workflow to quickly deploy web applications written in elixir/phoenix, for local consumption (ie not on t...
New
yureehuh
Introduction Founded in 2017 by landscape ecologist and fire mitigation expert Harry Statter, Frontline developed the first fully integra...
New

We're in Beta

About us Mission Statement