Phaengris.Art hand-drawn arts, city photos, linux / dev stories

Embed Custom Fonts Into PDF Documents With Ruby and Puppeteer

When you need to build a PDF document from HTML, Puppeteer is the tool to use.

But things are not so obvious when it comes to custom fonts.

First, make sure that CustomFont is installed in your system. Not in your app, but in the system - that’s the trick.

I tried to use @font-face { src: url('file:///path/to/CustomFont.ttf') } in the HTML, but it didn’t work. While it works in browser when the document is rendered as HTML, it seems not to be taken into account by Puppeteer when it renders the PDF.

In Fedora you can copy it into ~/.local/share/fonts, then run fc-cache -f -v to update the font cache.

I didn’t use a remote url because of one of the requirements was the font to be stored locally, without dependencies on the internet.

html = <<~HTML
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title></title>
  <style>
    /* Remember that your HTML will be "printed" so print settings are in charge */
    @media print {
      body {
        font-family: "CustomFont", sans-serif;
      }
    }
  </style>
</head>
<body>
  This text will be printed with the custom font.
</body>
</html>
HTML

# A4 is not a mandatory format of course, I use it just as an example

pdf = Puppeteer.launch(
  headless: true,
  default_viewport: Puppeteer::Viewport.new(width: A4_WIDTH_PX, height: A4_HEIGHT_PX),
  args: %W[--window-size=#{A4_WIDTH_PX},#{A4_HEIGHT_PX} --start-maximized]
) do |browser|
  page = browser.new_page
  page.content = html
  page.pdf(
    # you may calculate is as DPI * A4_WIDTH_INCHES, thus providing various file sizes depends on the quality you need
    width: A4_WIDTH_PX,
    height: A4_HEIGHT_PX,
    # by default it doesn't print background
    # you can try to print any page from your browser and you'll see same behavior by default
    # so we have to mention it explicitly that we want the background
    # otherwise your background colors / images will be missing
    print_background: true,
    omit_background: false,
    # just to be sure the document size is what we expect it to be
    # still you probably want to implement some margins in your HTML, but that will be under your control
    margin_top: 0,
    margin_bottom: 0,
    margin_left: 0,
    margin_right: 0,
    paper_width: A4_WIDTH_INCHES,
    paper_height: A4_HEIGHT_INCHES
  )
end
File.write('output.pdf', pdf)

Your HTML will be printed as a PDF document with the custom font, which will be embedded into the document, so you can share it with anyone who doesn’t have the font installed.

A hint: to split your HTML document into PDF pages use page-break-before: always or page-break-after: always in your CSS, like that

.page {
  width: var(--a4-width);
  height: var(--a4-height);
  page-break-after: always;
  margin: 0;
  padding: var(--margin-external);
}