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:
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.
lein new clj-pdf-for-dummies from the command line and copy over
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
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 "2.2.30"] in your project dependencies, require it inside
core.clj and create a few project variables:
(io/make-parents dest) line, we are gonna call thepdf
function fromclj-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.
So far our pdf is not exciting so let’s finally get to the nitty gritty and create some design.
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.
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
[0 100] (
[x y] position from top-left corner) as well a tilted it a bit by setting the
:rotate key to
Two things are missing - the cover title and the dummy guy. Let’s deal with the former.
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).
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…
.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
Generate the pdf and make sure everything is working as expected. We are getting pretty close!
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:
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.