Clj-pdf for Dummies
clj-pdf
is a library for easily generating pdfs from Clojure.
While the library elements used to build the pdf are pretty simplistic, you can still create some advanced things with a little bit of extra work.
In this tutorial, we’ll create a simple cover for a (non-existent) book”clj-pdf For Dummies.” Aren’t those series great?! Here’s the result pdf we’re going to create:
https://github.com/smogg/clj-pdf-for-dummies/blob/master/output/clj-pdf-for-dummies.pdf
And here’s the complete source-code: https://github.com/smogg/clj-pdf-for-dummies
Everything in the PDF was built with code except for the dummy guy, which I recreated by hand in graphics software.
Resources & setup
We are going to start with the default lein template.
Run lein new clj-pdf-for-dummies
from the command line and copy over resources/fonts
and resources/img
folders into your newly created project. Alternatively, you can clone the start
branch of Github repo: https://github.com/smogg/clj-pdf-for-dummies/tree/start
Running the project
We want the end user of our app to be able to run the pdf generation from the command line using lein run
. They will also be able to specify a destination/filename for the pdf in command line args, like this:
lein run --dest "your-dir/pdf-name.pdf"
Start by adding tools-cli
to your project.clj:
:...
Next, let’s write the -main
function:
src/clj-pdf-for-dummies/core.clj
:
We use tools-cli
to parse the arguments passed by the user when they run the project from the command line. We also set a default destination and use io/make-parents
function, to create the directory structure to put our pdf in (if it doesn’t already exist).
You can test that everything is working by running:
lein run --dest "test/dir/test.pdf"
You should see the test/dir
directory structure created.
Next, let’s introduce clj-pdf
and generate some pdfs.
clj-pdf
Include [clj-pdf "2.2.30"]
in your project dependencies, require it inside core.clj
and create a few project variables:
project.clj
:
src/clj-pdf-for-dummies/core.clj
Next, after (io/make-parents dest) line, we are gonna call the
pdffunction from
clj-pdf.core` namespace:
Above will generate a pdf with a string “Hello, this is my first pdf!” on the first (and only) pdf page. Inside the vector passed to pdf/pdf
function you’ll see a map of settings for your document. I hope they are pretty much self-explanatory, but refer to clj-pdf docs for reference.
The shapes
So far our pdf is not exciting so let’s finally get to the nitty gritty and create some design.
In clj-pdf
you specify a cover by setting a :letterhead
key on clj-pdf
settings map:
Notice we’ve also added a [:pagebreak]
before the first pdf page. Otherwise, the text would appear on top of our cover.
Now, let’s display some things on the cover.
We’ll start by setting the background to a yellow color. clj-pdf
offers a [:graphics]
element which takes a function with a single argument. That argument is a pdfgraphics2d
object from an underlying Java iText library. Here’s what you’d do to display a red circle using it:
First we called .setColor
on the pdfgraphics2d
object. All of the following methods will use that color to draw whatever shapes you want. Hence when we use the drawCircle
method, we’ll see a red circle.
You’ll notice that we’ve used a built-in color java.awt.color/RED
, we’ll introduce custom colors in a bit.
Let’s draw a square covering all of the cover using the yellow
color we’ve defined earlier in this tutorial. Our yellow
is in a hexadecimal RGB color format, so first, we have to convert it to something we can use with java.awt.Color
. For that purpose we create a little helper function:
Try generating your pdf now. The first page should be all yellow.
The fonts
Using fonts with [:graphics]
element is a little trickier than with the rest of the elements in clj-pdf
(unless you’re familiar with Java of course).
First off (similar to what we did with colors) you have to create a new java.awt.Font
instances loading the font .ttf
files from resources:
(def special-elite-font(java.awt.Font/createFontjava.awt.Font/TRUETYPE_FONT(-> "fonts/SpecialElite-Regular.ttf" io/resource io/input-stream)))
Sidenote: Unfortunately, this won’t work when running your project from a compiled .jar
. If you need to generate pdfs with custom fonts this way you have two options - use system fonts or create temp directories, outside the jar and load the font from there.
By default, the font we just defined would be tiny. You can resize a font using .deriveFont
method. Let’s create a little helper for later use:
Knowing all that, let’s create another [:graphics]
element for our cover:
In above example, we’re using system fonts to write a few sentences on the cover. Like before, we set the color, then we set the font and last but not least, we draw the string using drawString
method. You should get the idea by now.
To position the [:graphics ...
element we’ve also set :translate
to [0 100]
([x y]
position from top-left corner) as well a tilted it a bit by setting the :rotate
key to -0.05
(degrees).
Two things are missing - the cover title and the dummy guy. Let’s deal with the former.
Call the cover-text
function inside cover
and pass the g2d
object as well as page-width (you’ll see why we need it in a second).
Call lein run
, and you should see something like this:
Next, we have to center the text. You could set the x
position for the string manually, but let’s be serious…
We’ll use .getFontMetrics
method of g2d
object to get the text width and then do some simple math to calculate desired x
position of the text. Here’s how that little helper function could look like:
Let’s use it in the cover-text
function:
Generate the pdf and make sure everything is working as expected. We are getting pretty close!
The SVG
So far we’ve been only using the [:graphics]
element, but there’s another way you can programmatically build graphical elements in your pdf. The [:svg]
element lets you either load an svg file or render an SVG string. Since this tutorial is already getting pretty long, we’re going to do the former, but you could, for example, use a library like Hiccup to create pretty complicated things with it.
For our cover, we are gonna load, position and scale the previously created SVG file, here’s how your final cover
definition should look like:
The end
And that’s pretty much it! While clj-pdf
certainly has some limitations, you can build a lot of great things with it with a little bit of creativity.
Hope you’ve learned something today - let me know if you have any comments and/or questions.