PDFium

Elixir bindings for the pdfium library.

Installing

Precompiled NIFs are built against OTP 27 and load on OTP 26 and newer, on x86_64 and aarch64 Linux with glibc or musl and on arm64 macOS. Elsewhere the package compiles from source and needs a C++ compiler, make and curl.

Developing

The development environment is a Nix shell carrying Erlang, Elixir, a C++ compiler and gh:

nix develop

CI pushes everything it builds to the gmile Cachix cache, the musl Erlang among it. To download those instead of building them, trust the cache once:

cachix use gmile

mix compile builds the NIF for this machine through the Makefile, which downloads the libpdfium archive for the @libpdfium tag in mix.exs and compiles c_src/pdfium_nif.cpp against it and the Fine headers from hex. This is also what a user of the package gets when no precompiled NIF matches their platform.

Usage

  1. open a PDF file descriptor:

    {:ok, document} = PDFium.load_document("file.pdf")
    # => {:ok, #Reference<0.2181297728.2193227786.166499>}
  2. get the number of pages in the file:

    {:ok, pages} = PDFium.get_page_count(document)
    # => {:ok, 1}
  3. render a page to file:

    {:ok, ref} = PDFium.load_document("/Users/eugene/Downloads/7ade6db09604a8b41104763c6f16a987.pdf")
    {:ok, binary, w, h} = PDFium.get_page_bitmap(ref, 0, 300) # 300 for DPI
    {:ok, image} = Vix.Vips.Image.new_from_binary(binary, w, h, 4, :VIPS_FORMAT_UCHAR)
    {:ok, _image} = Image.write(image, "/tmp/sample.png")

Releasing

Every Monday the Update to latest libpdfium workflow opens a pull request that bumps @libpdfium and @version in mix.exs and the archive hashes in nix/libpdfium.nix. Merging it into main runs the Release workflow, which builds the archives on a Linux and a macOS runner, creates the GitHub release and publishes the package to hex. Run the workflow by hand to release outside the schedule:

gh workflow run update-libpdfium.yaml

For any other tag, run the same app the workflow runs with the tag as its argument:

nix run .#update -- chromium/8138

Every push to main that touches mix.exs is released, so bump @version with it.

Running CI steps locally

The flake builds the NIF for every Linux target from any Linux machine with the cross toolchains of nixpkgs, and the macOS one on a Mac:

nix build .#nif-aarch64-linux-musl

The targets are aarch64-linux-gnu, aarch64-linux-musl, x86_64-linux-gnu, x86_64-linux-musl and aarch64-apple-darwin. nix build alone builds the one for this machine, and nix build .#archives the tarballs this machine can build. Nix caches every derivation by its inputs, so a NIF is rebuilt only when c_src/, mix.exs or the toolchain change, and CI keeps the store between runs with cache-nix-action.

Every workflow step is a flake app, so it runs the same way on a runner and here:

nix run .#test-aarch64-linux-musl
nix run .#release
nix run .#update

test-<target> puts that target's NIF in priv/ and runs mix test on an Erlang built against the same libc, so a musl target runs on a musl BEAM from pkgsMusl. A system only has the test apps for the targets it can run: both of its Linux targets, or the macOS one. CI runs each on a runner of its kind. release creates the GitHub release from a directory of archives and publishes the package to hex. update moves mix.exs and nix/libpdfium.nix to the latest libpdfium, bumps the version, pushes the update-libpdfium branch and opens the pull request.

When a release fails

Releases are immutable, so a tag can never be reused, not even after deleting the release. Whatever failed, the way forward is another pull request bumping @version in mix.exs to the next patch. The same holds for a change to that file that forgot to bump the version: the release run goes red once and the next bump releases normally.

The archives of any run can be fetched for inspection:

gh run download 42028488533 --dir artifacts

Repository settings the workflows rely on

Known issues

License

See [LICENSE].