auto-print
Turns markdown manuscripts into books: an epub, a docx, a pdf and a Kindle kpf for each one.
It is made for writers who keep their drafts in markdown, next to their notes. A manuscript file can hold a spelling list, character notes and cut scenes. auto-print publishes only the story, plus the extra pages you name, such as a dedication or a character guide.
- The source files are never changed.
- One config file lists the books. One command builds all of them.
- Under the hood, pandoc does the conversion. A Chromium-based browser prints the pdf, and Amazon's Kindle Previewer makes the kpf.
Why it exists
auto-print was developed to create books out of Pavel Safronov's upcoming writing.
- Upcoming writing: blog.fuzzyworld.net/upcoming
- All writing: blog.fuzzyworld.net
Requirements
- Node 22.18 or newer.
- pandoc 3. It converts the markdown to epub, docx and html. Tested with pandoc 3.6.1. macOS:
brew install pandoc. - Google Chrome, or Chromium, Edge or Brave, for the pdf and for generated covers.
Set the
CHROME_PATHenvironment variable to use a browser in a non-standard location. - Kindle Previewer 4, only for the kpf. macOS:
brew install --cask kindle-previewer. Set theKINDLE_PREVIEWER_PATHenvironment variable to use a copy in a non-standard location.
If a tool is missing, only the formats that need it fail. The other files are still written.
Install
Install the auto-print command from GitLab:
npm install --global git+https://gitlab.com/PavelSafronov/auto-print.git
Or run it from a clone, with nothing installed. Node runs the TypeScript source directly:
git clone https://gitlab.com/PavelSafronov/auto-print.git
node auto-print/src/cli.ts --config path/to/books.json
Usage
auto-print # build every book in ./books.json
auto-print my-novel # build only the named book(s)
auto-print --config books/books.json
auto-print --list # list the books, build nothing
The epub, docx and pdf take a few seconds in total. The kpf takes about 20 seconds per book. Each run overwrites the previous output files.
The config file
{
"outDir": "out",
"defaults": {
"author": "Your Name",
"formats": ["epub", "docx", "pdf", "kpf"]
},
"books": [
{
"slug": "my-novel",
"title": "My Novel",
"source": "drafts/my-novel.md",
"cover": "covers/my-novel.jpg"
}
]
}
Every path in the config file is relative to the folder of the config file.
The files of each book are written to <outDir>/<slug>/, for example out/my-novel/my-novel.epub.
| Top-level field | Required | Meaning |
|---|---|---|
books |
yes | The list of books. |
defaults |
no | Settings for every book. A book's own setting wins over the default. |
outDir |
no | Output folder. Default "auto-print-out". |
| Book field | Required | Meaning |
|---|---|---|
slug |
yes | Name of the output folder and files. Use lowercase words joined by dashes. |
title |
yes | Book title, used on the cover, the title page and in the file metadata. |
source |
yes | The markdown file. Its story section can link to other files, see "Collections of stories". |
author |
yes | In the book or in defaults. |
cover |
no | Cover image for the epub and kpf. Without it, a text cover is generated: title, "by", author. |
section |
no | Heading that holds the story. Default "Writing". Use null to take the whole file. |
language |
no | Default "en-US". |
formats |
no | Default ["epub", "docx", "pdf", "kpf"]. List fewer to skip some. |
chapterBreaks |
no | epub and kpf only. Default false. See "Chapter breaks in the epub". |
front |
no | Headings whose sections become pages before the story. See "Extra pages". |
back |
no | Headings whose sections become pages after the story. See "Extra pages". |
What happens to a manuscript
- auto-print finds the heading named
Writing(or thesectionyou set). Any heading level, any letter case. If the name is used at several levels, the shallowest heading wins. - It takes everything under that heading, up to the next heading of the same or a higher level. Everything else in the file, such as notes or removed scenes, is left out.
- It removes HTML comments, such as a blog's
<!--more-->marker. - It promotes the headings, so the story's top headings become chapters. If the story's chapters are
###headings, they become level 1. - Text before the first chapter heading, such as a content warning, is kept as an opening section.
- Images are found relative to the file they are written in, so
works.
A manuscript might look like this:
# My Novel
## Notes
Things only the author should see.
## Writing
[Content warning: peril.]
### Chapter One
#### The morning
The hero woke up.
## Removed writing
A scene that was cut.
Collections of stories
A book can be a collection of stories that each live in their own file.
The book's source is then an index file, and its Writing section holds only links to the story files, one per line:
## Writing
[The first story](./story-1.md)
[The second story](./story-2.md)
<!-- [The third story](./story-3.md) -->
- auto-print reads the
Writingsection of each linked file, in the order of the links. - A link inside an HTML comment (
<!-- -->) is left out. Use this for a story that is not ready. - A
Writingsection that mixes links with other text stops the build with an error.
Each story file is handled with the rules in "What happens to a manuscript", with one difference: the text before a story's first heading, such as its content warning, is moved to just below that heading. Without this move, a story's content warning would appear at the end of the previous story.
Extra pages
Pages such as a dedication, a content warnings page, a character guide or a map are sections in the book's source file.
The front and back lists name the headings of those sections, in the order they appear in the book.
{
"slug": "my-collection",
"title": "My Collection",
"source": "my-collection.md",
"front": ["Dedication", "Content Warnings"],
"back": ["Characters", "Map"]
}
## Dedication
To my city.
## Characters
### The hero
The one the stories are about.
## Map

Only the listed sections are published. Other sections, such as notes, stay private.
- Headings match ignoring letter case. If a name is used at several heading levels, the shallowest heading wins.
So a
## Characterspage is published, and### Charactersnotes inside a## Notessection are not. - Two headings with the same name at the same level stop the build with an error. So does a listed name that is not in the file.
- The section heading becomes the page title. Headings inside the section become sub-headings of the page.
- Every extra page starts on a new page, in every format.
- A section named
Dedicationhas the typical look of a dedication: centered, in italics, about a third of the way down the page. Its heading is not shown, and it is left out of the table of contents. - A section named
Copyrightis left out of the table of contents. - In the epub and the kpf, front pages are marked as front matter, and back pages as back matter (appendices). E-readers use these marks to know where the story starts.
Chapter breaks in the epub
The chapterBreaks setting controls how the chapters appear in the epub and the kpf.
false(the default): the story flows continuously. A chapter is only a heading in the text.true: every chapter starts on a new page.
In both cases the chapters are listed in the table of contents. The docx and the pdf always flow continuously.
Sentences on separate lines
A manuscript can put each sentence on its own line, or write each paragraph as one long line. Both styles produce the same book.
- A single line break joins the two lines with a space, inside the same paragraph.
- A new paragraph starts only at an empty line. A line that holds only spaces also counts as empty.
This works because auto-print reads the files as GitHub-flavored markdown (gfm), where a single line break is not a real line break.
A line can be read as markdown formatting by mistake. Avoid these at the start or end of a line:
| In the manuscript | What happens |
|---|---|
A line that starts with -, *, + or 1. |
It starts a list and ends the paragraph. Other numbers, such as 2., are safe. |
A line of only --- or ===, directly after a text line |
The line above it becomes a heading. |
A line that starts with # and a space |
It becomes a heading. |
A line that starts with > |
It becomes a block quote. |
A line that starts with ::: |
It starts or ends a styled block. |
Two spaces, or a \, at the end of a line |
A real line break is kept inside the paragraph. |
{.word}, {#word} or {key=value} at the end of a heading, or right after an image or link |
It is read as a setting and not shown. Other braces, like {quietly}, are shown as written. |
A dash such as — at the start of a line is safe.
How each format is made
- epub: pandoc, with a table of contents built from the headings. The stylesheet is pandoc's default one, plus assets/epub.css.
- docx: pandoc, with assets/reference.docx as the Word template.
It is pandoc's default template plus a
Dedicationparagraph style. Chapters use theHeading 1style. - pdf: pandoc makes a standalone html page with assets/print.css, then a headless browser prints it. The browser does not always exit by itself after printing, so auto-print waits for the pdf and then stops the browser.
- kpf: Kindle Previewer's command line tool converts an epub into a kpf.
auto-print builds a fresh epub for this, so the kpf works even if
epubis not informats.
Development
npm install # TypeScript and Node's type definitions, for the checks below
npm test # unit tests, plus pandoc tests that are skipped when pandoc is missing
npm run typecheck
npm run build # compiles src/ to dist/, which the installed command runs
The code has no runtime dependencies. decisions.md records why it works the way it does.
assets/reference.docx is made by scripts/make-reference-docx.sh.
Run it again to change the Word styles.