Creating PDFs
While Nanoc is typically used to generate websites, it can also be a useful tool when generating other types of content. This guide will give an example of how you could use Nanoc to generate PDFs via LaTeX.
Initial setup
This guide assumes a new site, populated with a handful of Markdown documents in the content/ directory, along with the default stylesheet generated by the create-site command:
% tree content
content
├── stylesheet.css
├── index.md
└── essays
├── the-nature-of-infinity.md
└── self-consistency-in-spacetime.md
The Rules file for such a site will contain some code to handle Markdown files and pass them through kramdown:
compile '/**/*.md' do
filter :kramdown
layout '/default.*'
end
route '/**/*.md' do
if item.identifier =~ '/index.*'
'/index.html'
else
item.identifier.without_ext + '/index.html'
end
end
compile '/**/*' do
write item.identifier.to_s
end
layout '/**/*', :erb
Adding PDF support
Generating a PDF from a Markdown document will happen in two steps: first convert Markdown to LaTeX, and then convert LaTeX to PDF. There will be two Nanoc filters that implement these conversions, as well as a compilation rule setup that generates a PDF in addition to the HTML files that are already being generated.
The first step will be a Markdown-to-LaTeX filter. Create lib/filters/kramdown2latex.rb with the following content:
require 'kramdown'
Nanoc::Filter.define(:kramdown2latex) do |content, params|
::Kramdown::Document.new(content, params).to_latex
end
To generate a proper LaTeX file, however, the output from kramdown needs to be wrapped to make it a proper document. A Nanoc layout fits this use case perfectly. Create layouts/article.tex.erb with the following content:
\documentclass{article}
\usepackage{inputenc}
\usepackage{fontenc}
\usepackage{listings}
\usepackage{hyperref}
\begin{document}
<%= yield %>
\end{document}
The extra packages are needed by kramdown; it generates LaTeX content that makes use of these packages.
Next up is a filter that converts LaTeX to PDF. Since LaTeX is text and PDF is not, the filter will need to convert from :text
to :binary
. The filter itself will invoke an external executable that generates the PDF (in this case, pdflatex). The filter is somewhat complex, partially due to the complexity of invoking the executable:
Class.new(Nanoc::Filter) do
identifier :latex2pdf
type :text => :binary
TMP_BASENAME = 'nanoc-latex'
TMP_EXTENSION = '.tex'
def run(content, params = {})
Tempfile.open([TMP_BASENAME, TMP_EXTENSION]) do |f|
f.write(content)
f.flush
system(
'pdflatex',
'-halt-on-error',
'-output-directory',
File.dirname(f.path),
f.path)
system('mv', f.path.sub('.tex', '.pdf'), output_filename)
end
end
end
With both the Markdown-to-LaTeX and the LaTeX-to-PDF pieces in place, this site is nearly ready to create PDFs. Add the following piece of code to the top of the Rules file:
compile '/**/*.md', rep: :pdf do
filter :kramdown2latex
layout '/article.tex.erb'
filter :latex2pdf
write item.identifier.without_ext + '.pdf'
end
Note the rep: :pdf
on the first line. This means that compilation rule generates a new item representation (named pdf
) in addition to the default item representation that already exists (and generates HTML).
Now run nanoc, and you’ll see PDF files being generated into the output/ directory.