PhantomJS is simultaneously a wonderful and terrible piece of software.

I opted to use phantomjs over many other alternatives to create PDFs from HTML generated by a Rails 5.1 web-app.

Locally, everything worked after getting through some PhantomJS quirks. However, once I deployed to the stage environment (an AWS EC2 t2.small instance running an Amazon Linux AMI) for testing, things got a little weird.

I had been importing the pdf-specific fonts in a print.scss stylesheet.

// print.scss

@import url('https://fonts.googleapis.com/css?family=Roboto:300,300i,400,400i,500,500i,700,700i');

Finally, when the PDF service ran the pdf generation script, bin/phantomjs rasterize.js, this happened.

Expected Output:

Actual Output:

You can notice inconsistent kerning and font weights in this simple example, but it looks much worse on a paragraph of text and large headers.

After some digging, there are a number of annoying and related WebFont / True Type rendering bugs in 2.X versions of PhantomJS:

None of the "work-arounds" in these issues actually worked for me. So, after many hours of playing wack-a-mole, I eventually solved the rendering problem
by converting the TrueType Webfonts imported through Google Fonts into a Type1 font installed physically on the server.

Now, we'll walk through that process so that others won't have to go down the rabbit-hole I did.

Step 1: Convert your .ttf font files to a .pfb format.

Grab your True Type Font files. For this example, I went to Google Fonts and downloaded all of the Roboto .ttf files.

Then, head over to the Online Font Converter or any font-converter of your choice. Select .pfb and upload your .ttf files.

Download the generated .zip folder and extract all of the contents.

Finally, move all of the generated .pfb files into a new Roboto directory to keep the folder structure simple.

Step 2: Get your converted font on to the server.

scp -r ~/Downloads/Roboto username@example.com:~/

Step 3: Install the the font.

Now we'll move the fonts into the proper directory. You'll need sudo permissions to move anything into /usr/share/fonts/*:

sudo mv ~/Roboto /usr/share/fonts/default/Type1

There are alternative locations you could place the font, like ~/.fonts. However, the major benefit of putting it in usr/share/fonts is that it make Roboto available to all of this server's users, instead of this particular user.

Also, if you take a gander in the Type1 sub-directory, you'll notice a number of different filetypes, including afm and pfb. I found through testing that I only needed pfb files. However, you might want to try adding afm if you have trouble getting the font to render properly.

Step 4: Update the font cache.

$ fc-cache -fv
/usr/share/fonts: caching, new cache contents: 0 fonts, 3 dirs
/usr/share/fonts/default: caching, new cache contents: 0 fonts, 2 dirs
/usr/share/fonts/default/Type1: caching, new cache contents: 35 fonts, 1 dirs
/usr/share/fonts/default/Type1/Roboto: caching, new cache contents: 12 fonts, 0 dirs
/usr/share/fonts/default/ghostscript: caching, new cache contents: 13 fonts, 0 dirs
fc-cache: succeeded

You should see fc-cache: succeeded at the end of this output. Now double-check that Roboto is available for use with the fc-list method:

$ fc-list | grep Roboto
Roboto:style=Italic
Roboto Black:style=Italic
Roboto:style=Bold
Roboto Black:style=Regular
Roboto Medium:style=Regular
Roboto:style=Regular
Roboto Light:style=Italic
Roboto Thin:style=Italic
Roboto:style=Bold Italic
Roboto Medium:style=Italic
Roboto Thin:style=Regular
Roboto Light:style=Regular

Step 5: Use it.

Now you can setup your font stack for phantomjs to use:

// print.scss

$font-family-sans-serif: Roboto, "Helvetica Neue", Arial, sans-serif !default;
$ phantomjs rasterize.js my-test-file.pdf

Sweet. Problem solved.