Updating My Zettlr Markdown Workflow (Again)
Earlier this year I wrote about how I'd moved to using Zettlr for my Markdown writing, and how I'd set up export templates so I could generate PDFs and epubs with a single click. That workflow has been rock solid ever since, which is why I was so annoyed when a Zettlr update recently wiped all my export defaults files.
I keep backups of everything, so restoring the files wasn't a problem (plus blogging about it all here helped me keep a record of what I'd done last time and why, which was handy). What was a problem was that for some reason they simply didn't work anymore. I was getting LaTeX errors about duplicate \documentclass commands, some troubleshooting revealed to be due to using include-in-header to add my LaTeX formatting. At some point after writing my last post this error must have reared its head again, because my most recent backup included the .tex template in tempalte rather than include-in-header.
What I figured out is that when you use include-in-header, Pandoc expects only preamble content - packages, commands, and so on. It then creates its own document structure. But my template was trying to create a complete document, which meant Pandoc was generating a document structure and my template was generating one, hence the duplicate \documentclass error. It seems that adding it to the template line fixed this issue last time, but this time it wasn't quite so simple.
The fix was to convert my header file into a proper Pandoc template. Instead of just including my custom packages and styling, I needed to create a full template with all the Pandoc variable placeholders. This meant adding things like , , and so on, so that the YAML frontmatter in my documents would actually work. This required completely rewriting my LaTeX template file, and took a little longer than I would have liked, but I think it's a much more robust solution now.
The other critical thing I'd been missing was the \tightlist definition. Pandoc uses this command for compact lists in Markdown, and if it's not defined in your template you'll get errors. The fix is simple:
\providecommand{\tightlist}{%
\setlength{\itemsep}{0pt}\setlength{\parskip}{0pt}}
With all that sorted, my PDF exports were working again. But while I was fixing things, I decided to finally tackle two features I've been meaning to add for ages - cover images for epubs, and automatic backmatter.
Adding Cover Images
Getting cover images working in epubs was straightforward once I figured out the right syntax. In your markdown file's YAML frontmatter, you just need to add:
---
title: "Your Book Title"
author: "Your Name"
cover-image: G:\Path\To\Your\cover.jpg
---
Note that you use cover-image, not epub-cover-image, and on Windows you need to use backslashes in the path. This lets each document have its own cover image without having to hardcode anything into the export defaults file.
Adding Backmatter
This one was trickier. I wanted to automatically include an "About the Author" page at the end of every epub export, but I didn't want to have to manually paste it into every document. Pandoc has an include-after-body option, but that's designed for already-processed content (like HTML or LaTeX), not markdown that needs to be converted. If you use it with a markdown file, the markdown syntax doesn't render - you just end up with raw markdown tacked onto the end of your table of contents.
The solution was to create a Pandoc Lua filter. Lua filters let you modify how Pandoc processes documents, and in this case I needed one that would read an external markdown file, parse it, and append it to the end of my document.
Here's the filter I ended up with:
-- This filter runs after metadata is processed
function Pandoc(doc)
-- Check if backmatter file is specified in metadata
if doc.meta['backmatter-file'] then
local backmatter_path = pandoc.utils.stringify(doc.meta['backmatter-file'])
-- Read the backmatter file
local file = io.open(backmatter_path, "r")
if file then
local content = file:read("*all")
file:close()
-- Parse the markdown content into Pandoc AST
local backmatter_doc = pandoc.read(content, "markdown")
-- Add a horizontal rule separator
table.insert(doc.blocks, pandoc.HorizontalRule())
-- Append all backmatter blocks to the end of the document
for , block in ipairs(backmatterdoc.blocks) do
table.insert(doc.blocks, block)
end
else
-- If file couldn't be opened, add a warning
io.stderr:write("Warning: Could not open backmatter file: " .. backmatter_path .. "\n")
end
end
return doc
end
I saved this as include-backmatter.lua in my Templates folder, then added it to my epub export defaults file. Now in my Markdown files I just need to add this to my YAML header:
---
title: "Your Book Title"
author: "Your Name"
cover-image: G:\Path\To\cover.jpg
backmatter-file: G:\My Drive\Zettlr Writing Sync Folder\Templates\about-author.md
---
Because it's Zettlr, I can just set this up as a snippet so that it's easy to add to any file that needs it. The filter reads the about-author.md file, processes all the markdown (links, headers, lists, everything), and appends it to the end of the epub.
The Current Setup
My export defaults files are still fairly simple, and I've attached them to this free post on Patreon. Everything works with a single click again, and now my epubs have cover images and backmatter without me having to think about it. It took a couple of hours to get everything working properly, but it's definitely been worth it. Hopefully this is useful to someone else trying to do similar things with Zettlr and Pandoc. Later today I'll be uploading the first chapter of this years December serial for Patrons, so if you want to see what this all looks like keep an eye out for that.