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.