build
Parse MDX files, validate them, compose HTML, and render to PDF, EPUB, and/or Kindle (MOBI/AZW3).
pretty-converter build [flags]
| Flag |
Default |
Description |
--format |
"pdf" |
Output formats, comma-separated: pdf, epub, kindle (e.g. pdf,epub,kindle) |
--out |
"out.pdf" |
Output path (extension determines single format; base name for multi-format) |
--title |
"" |
Book title |
--subtitle |
"" |
Book subtitle |
--author |
"" |
Book author |
--theme |
"default" |
Theme name (builtin, custom, or a .theme.yml/.css path) — see Themes |
--css |
"" |
Custom CSS file path (overrides the theme entirely) |
--template |
"" |
Custom HTML template file path (overrides the theme's HTML) |
--cover-image |
"" |
Custom cover image (.png/.jpg/.jpeg/.svg/.webp); the cover page is sized to the image's own dimensions, replacing the text cover |
--timeout |
"" |
Render timeout (e.g. 30s, 1m) |
--language |
"en" |
EPUB/Kindle language (BCP-47 tag, e.g. en, es) |
--json |
false |
Output as JSON |
--no-cover |
false |
Omit the cover page |
--no-toc |
false |
Omit the table of contents |
--no-page-numbers |
false |
Omit page numbers |
--no-header |
false |
Omit the running page header |
--no-outline |
false |
Skip PDF bookmarks/outline (measured near-zero cost on its own — see BENCHMARKS.md) |
--no-tagged-pdf |
false |
Skip PDF accessibility tagging (PDF/UA) — faster on very large documents |
--fast |
false |
Shorthand for --no-header --no-page-numbers --no-outline --no-tagged-pdf — 40-44% faster on large books; an explicit flag among these still wins |
--color-primary |
"" |
Theme override: primary color (e.g. #1a56db) |
--color-accent |
"" |
Theme override: accent color |
--color-text |
"" |
Theme override: body text color |
--color-muted |
"" |
Theme override: muted/caption text color |
--color-bg |
"" |
Theme override: page background color |
--font-heading |
"" |
Theme override: heading font family |
--font-body |
"" |
Theme override: body font family |
--font-code |
"" |
Theme override: code font family |
--density |
"" |
Spacing density: compact, normal, or relaxed |
--allow-network-fonts |
false |
Allow fetching Google Fonts declared by the theme (enables network access) |
Build Pipeline
The build command runs through these stages per format:
- Parse — Read and parse all MDX files in the source directory
- Validate — Check frontmatter, duplicate IDs, heading depth, content warnings
- PDF compose — Assemble HTML with TOC, cover page, and embedded CSS/template (PDF only)
- PDF render — Generate PDF via headless Chrome, then run an automatic quality audit (PDF only)
- EPUB write — Package chapters directly into EPUB 3, no Chrome needed (EPUB only)
- Kindle convert — Build the same EPUB internally, then convert it to MOBI/AZW3 via Calibre's
ebook-convert (Kindle only)
Chrome is only required when pdf is in the format list; Calibre is only required when kindle is. An epub/kindle-only build skips Chrome detection entirely, and a pdf/epub-only build skips Calibre detection entirely.
PDF Quality Audit
Right after rendering, build runs a best-effort audit of the composed document and reports anything worth a second look — it's advisory for DOM/layout findings and only reports at error severity for output that's actually corrupt. The final summary's Warnings count reflects this (and --json's warnings array lists them in full). Checks:
| Check |
Flags |
overflow-x |
Content wider than its box (long code lines, wide tables/images) that print will clip instead of wrap |
overflow-y |
Content taller than a fixed-height box, so it clips when printed |
broken-image |
An <img> that never resolved to real pixels |
image-low-res |
An image displayed at more than ~2x its intrinsic size, so it will look pixelated on paper |
empty-content |
The document has almost no visible text — usually a sign composition silently produced nothing |
low-contrast |
Text whose contrast ratio fails WCAG 2.2 (4.5:1 normal, 3:1 large text) against its effective background |
heading-clip-risk |
A heading that forces a page break without enough top margin to clear the print engine's header/margin strip, so its top would render clipped |
broken-anchor |
An <a href="#fragment"> with no matching element — dead in-document links break TOC and PDF bookmarks |
duplicate-id |
The same id attribute used twice, which breaks anchors, the TOC, and PDF bookmarks |
toc-mismatch |
The TOC links to an id that doesn't exist, or a body section heading has no TOC entry |
font-load-fail |
A font family the page requests could not be loaded (missing local font, or a Google Font blocked by the default network lockdown) and will silently fall back |
page-break-inside-risk |
A table/code block without page-break-inside: avoid, so print can slice it mid-row |
line-break-risk |
A block with orphans/widows below 2, so a single line can be stranded at the top/bottom of a page |
page-count |
The generated PDF has no detectable pages — the output file may be empty or corrupt |
pdf-empty |
The generated PDF is zero bytes — the output file is empty |
pdf-eof-missing |
The generated PDF is missing its %%EOF marker — the output may be truncated or corrupt |
unused-component |
A component registered via WithComponent() was never used in any document — check the tag spelling |
The audit reads the composed HTML before it's handed to the print engine, so it can't see two things that live purely inside Chrome's own print pipeline: the fixed ~0.2in header/footer inset and the actual page-break slicing (both covered by base.css's own layout rules instead — see the CHANGELOG for the bugs those rules exist to prevent).
Pre-flight Checks
Before the pipeline starts, build verifies (per selected formats):
- Chrome/Chromium is available (only when
pdf is in the format list)
- Calibre's
ebook-convert is available (only when kindle is in the format list)
- Source directory exists
- At least one
.md/.mdx/.txt file is present
- Each output path's directory is writable
- Custom CSS file exists (if specified)
- Custom template file exists (if specified)
- Custom cover image exists and is a supported format (
.png/.jpg/.jpeg/.svg/.webp, if specified)
check
Parse and validate all MDX files without building a PDF. Previously named validate.
pretty-converter check [flags]
| Flag |
Default |
Description |
--strict |
false |
Treat content warnings as errors |
Turn raw, unstructured .txt (a single file or a directory) into clean
.mdx files — proper [X.Y.Z] frontmatter, Markdown headings, lists, and
fenced code blocks — plus a scaffolded go-pretty-converter.yml, ready to
check/build immediately.
Detection is heuristic and deterministic: no AI/LLM, no network access, and
deliberately conservative — every rule is tuned to avoid misreading
ordinary prose as structure, rather than to maximize how much structure it
finds. format never touches Chrome and never produces a PDF itself; the
generated output is meant to be reviewed, then run through check/
analyze/build like any other source.
pretty-converter format <input> [flags]
| Flag |
Default |
Description |
--out |
formatted |
Output directory for the generated .mdx files + go-pretty-converter.yml |
--force |
false |
Overwrite an existing, non-empty --out directory |
--title |
"" |
Book title for the scaffolded go-pretty-converter.yml |
--author |
"" |
Book author for the scaffolded go-pretty-converter.yml |
--no-verify |
false |
Skip re-parsing/analyzing the generated output |
--json |
false |
Output as JSON |
| Rule |
Signal |
What it produces |
| Chapter boundary |
A blank-line-isolated short line (or two lines, the second a === underline) that reads as a title, not a sentence |
A new .mdx file, [N.0.0] |
| Fenced code |
A block already wrapped in ``` fences |
Passed through byte-for-byte |
| Indented code |
Every non-blank line in a block indented ≥4 spaces or a tab |
Dedented and wrapped in a fenced block; consecutive indented blocks merge into one |
| List |
≥2 lines starting with -/*/• or N./N) |
A normalized -/N. Markdown list |
| Subheading |
A line immediately followed by a --- underline |
## Title within the current chapter |
| Paragraph |
Everything else |
Markdown-escaped prose (so raw text can't be misread as syntax once re-parsed) |
A lone numbered line like 1. Introduction is a chapter title, not a
list — the ≥2-line threshold above is what disambiguates it from 1. Step one / 2. Step two. An isolated short line without a --- underline
stays a paragraph, not a subheading — that signal alone is indistinguishable
from an ordinarily soft-wrapped first line.
format also prints an advisory theme suggestion (never auto-applied,
just written into the scaffolded config's theme: key) based on lightweight
content signals — resume-style section headers, citation markers, code
density, and the like.
By default, the generated output is re-parsed and run through the same
content analysis analyze uses, purely as an informational summary.
Exit status: 0 unless an I/O error occurs (a bad input path, or an
existing --out directory without --force) — verify findings never fail
the run; enforcement happens later, at check/analyze/build time.
analyze
Statically analyze parsed MDX content for patterns that render poorly — or
break outright — across PDF, EPUB, and Kindle output. No Chrome or Calibre
required: analyze works directly on parsed content, before any build.
This is a different layer from check: check validates frontmatter and
structure (required fields, id format, duplicate ids, heading depth);
analyze looks at content itself — heading hierarchy, tables, images,
code blocks, lists, and chapter length — with an eye specifically on how
each renders across formats.
pretty-converter analyze [flags]
| Flag |
Default |
Description |
--strict |
false |
Treat warnings as blocking, in addition to errors |
--json |
false |
Output as JSON |
--max-table-columns |
6 |
Flag tables wider than this many columns |
--max-code-line-length |
100 |
Flag code lines longer than this many characters |
--max-list-depth |
3 |
Flag lists nested deeper than this |
--long-chapter-words |
3000 |
Flag chapters at/above this word count with no subheadings |
Findings are grouped into three severities:
| Severity |
Meaning |
| Errors |
Content that will actually break in some format (a dead #anchor link, a missing local image file) |
| Warnings |
Content that will render, but poorly, in some format (a table too wide for Kindle, a skipped heading level, deep list nesting, an overlong code line) |
| Improvements (info) |
Suggestions worth a look, not necessarily wrong (no headings, no tags, an oversized image, a remote image URL that PDF's network lockdown renders blank) |
| Check |
Severity |
What it flags |
broken-internal-anchor |
error |
A link to #id where no element in the document has that id |
duplicate-element-id |
error |
The same id used on more than one element — breaks anchors and TOC/bookmark navigation |
image-file-not-found |
error |
A local image reference that doesn't resolve to a file on disk |
heading-level-skip |
warning |
A heading jumps more than one level deeper than the previous one (e.g. h1 → h3), breaking the outline PDF bookmarks/EPUB nav/Kindle TOC build from headings |
multiple-h1 |
warning |
More than one top-level heading in a document — each MDX file becomes one chapter, so this makes for an ambiguous chapter title |
wide-table |
warning |
A table with more columns than --max-table-columns |
image-missing-alt |
warning |
An <img> with no (or empty) alt text |
long-code-line |
warning |
A code block with a line longer than --max-code-line-length |
deep-list-nesting |
warning |
A list nested deeper than --max-list-depth |
long-chapter-no-subheadings |
warning |
A chapter at/above --long-chapter-words with no h2+ subheadings, so no in-chapter navigation |
no-headings |
info |
A non-empty document with zero headings |
image-external-url |
info |
An image referenced by a remote URL — blank in PDF (network access is blocked by default) and often blank offline on e-readers too |
image-oversized |
info |
A local raster image wider than useful for print/e-ink, bloating output size |
no-tags |
info |
A document with no frontmatter tags |
Exit status: 0 unless errors are found; --strict also fails on warnings.
epub
Parse MDX files, validate them, and write a single EPUB 3 file — no
Chrome/Chromium involved, unlike build. Each MDX document becomes its own
chapter, in the same order as the PDF's table of contents.
pretty-converter epub [flags]
| Flag |
Default |
Description |
--out |
"out.epub" |
Output EPUB path |
--title |
"" |
Book title |
--subtitle |
"" |
Book subtitle (used as the EPUB's dc:description) |
--author |
"" |
Book author |
--theme |
"default" |
Theme name (theme CSS is converted to reflowable EPUB form) |
--css |
"" |
Custom CSS file path (overrides theme entirely) |
--cover-image |
"" |
Custom cover image (.png/.jpg/.jpeg/.svg/.webp), full-bleed as the first page |
--language |
"en" |
Book language (BCP-47 tag, e.g. en, es) |
--color-primary |
"" |
Theme override: primary color |
--color-accent |
"" |
Theme override: accent color |
--color-text |
"" |
Theme override: body text color |
--color-muted |
"" |
Theme override: muted/caption color |
--color-bg |
"" |
Theme override: page background color |
--font-heading |
"" |
Theme override: heading font family |
--font-body |
"" |
Theme override: body font family |
--font-code |
"" |
Theme override: code font family |
--density |
"" |
Spacing density: compact, normal, or relaxed |
--allow-network-fonts |
false |
Allow fetching Google Fonts declared by the theme |
Reuses --source/--config like every other command, and render.cover_image
from go-pretty-converter.yml if --cover-image isn't passed — the same cover
image works for both build and epub. Unlike PDF output — which uses
@page rules and print-oriented layout — EPUB uses the same theme system
through ResolveForEPUB, which produces a reflowable stylesheet (relative
units, no print-only rules, no cover/TOC/page-number sections) that works
across e-reader devices.
kindle
Parse MDX files, validate them, and convert them into a Kindle-ready ebook
file — no Chrome/Chromium involved, unlike build. Internally this builds
the same EPUB the epub command would produce, then converts it with
Calibre's ebook-convert (Amazon retired its own KindleGen tool in 2022;
Calibre is the standard replacement), so Calibre must be installed with
ebook-convert reachable on PATH — or pointed to explicitly with
--calibre-path / PRETTY_CONVERTER_CALIBRE_PATH. See Requirements.
pretty-converter kindle [flags]
| Flag |
Default |
Description |
--out |
"out.mobi" |
Output Kindle ebook path (.mobi or .azw3) |
--title |
"" |
Book title |
--subtitle |
"" |
Book subtitle |
--author |
"" |
Book author |
--theme |
"default" |
Theme name (theme CSS is converted to reflowable EPUB form, then to Kindle form) |
--css |
"" |
Custom CSS file path (overrides theme entirely) |
--cover-image |
"" |
Custom cover image (.png/.jpg/.jpeg/.svg/.webp), full-bleed as the first page |
--language |
"en" |
Book language (BCP-47 tag, e.g. en, es) |
--color-primary |
"" |
Theme override: primary color |
--color-accent |
"" |
Theme override: accent color |
--color-text |
"" |
Theme override: body text color |
--color-muted |
"" |
Theme override: muted/caption color |
--color-bg |
"" |
Theme override: page background color |
--font-heading |
"" |
Theme override: heading font family |
--font-body |
"" |
Theme override: body font family |
--font-code |
"" |
Theme override: code font family |
--density |
"" |
Spacing density: compact, normal, or relaxed |
--allow-network-fonts |
false |
Allow fetching Google Fonts declared by the theme |
--no-verify |
false |
Skip the post-conversion integrity check (see below) |
The target format (MOBI, AZW3, ...) is inferred from --out's extension —
.mobi (the default) is the most broadly compatible legacy Kindle format;
.azw3 targets the newer KF8 format with fuller CSS support. ebook-convert
output on failure or timeout (5 minute default) is surfaced verbatim in the
error message.
Every successful conversion runs a post-conversion integrity check by
default: Calibre re-extracts the file's text layer, and the result is scanned
for (a) leaked markup — raw HTML/KF8 fragments (aid=, class="chroma-,
highlight token classes) that broken renderers spill into visible text — and
(b) missing content — every source chapter's title must appear in the
extracted text. A file that fails the check is reported with the damaged
chapters named; --no-verify skips the check for builds where the extra
Calibre pass (seconds to a couple of minutes on large books) is not wanted.
Two Kindle-safety guarantees are baked into the conversion itself, no flags
needed: the EPUB stylesheet ships with CSS custom properties (var(--pdf-*))
resolved to concrete values and display: flex/grid removed (raw var() and
flex are unsupported by Kindle's KF8 renderer, and silently dropping those
rules is what makes adjacent text runs collide and splice on device), and
code blocks are converted to plain <pre><code> text — Chroma's
per-token <span> soup becomes hundreds of KF8 anchor fragments per block,
which old Kindle renderers mangle into raw markup. PDF and EPUB output keep
syntax highlighting and the themed CSS.
theme
List, inspect, and manage themes.
pretty-converter theme list
pretty-converter theme show <name>
pretty-converter theme new <name> [flags]
pretty-converter theme add <path> [flags]
theme list
Prints every builtin theme (name + description) followed by any custom
themes discovered in ./themes/ (project) and the global themes directory
(~/.config/pretty-converter/themes on Linux, via os.UserConfigDir()).
theme show <name>
Resolves a theme (builtin, custom, or a .theme.yml/.css path) with no
customization and prints its final, fully-assembled CSS to stdout — useful
to inspect a theme or pipe it somewhere (pretty-converter theme show dark > dark.css).
theme new <name>
Scaffolds a starter <name>.theme.yml you can hand-edit.
| Flag |
Default |
Description |
--from |
"default" |
Builtin theme to base the scaffold on |
--global |
false |
Write to the global themes directory instead of ./themes |
Refuses to overwrite an existing file.
theme add <path>
Imports an existing .theme.yml or raw .css file as a managed custom
theme (a loose .css file is wrapped into a minimal .theme.yml with
extends: default and the file's content as its css: block).
| Flag |
Default |
Description |
--as |
"" |
Name to register the imported theme under (default: derived from the file name) |
--global |
false |
Copy to the global themes directory instead of ./themes |
init
Scaffold a new book project with sample MDX files and configuration.
pretty-converter init [directory] [flags]
Interactive mode (default): runs a terminal form asking for title, author, theme, source directory.
| Flag |
Default |
Description |
--bare |
false |
Non-interactive init with flags |
--title |
"My Book" |
Book title (for --bare) |
--author |
"go-pretty-converter" |
Book author (for --bare) |
--theme |
"default" |
Book theme (for --bare) |
--json |
false |
Output as JSON |
serve
Parse MDX files, compose HTML, and serve with live reload on file changes. No Chrome required.
pretty-converter serve [flags]
| Flag |
Default |
Description |
--port |
8080 |
HTTP server port |
Uses Server-Sent Events for live reload. Watches .md, .mdx, .txt, .yaml, and .yml files for changes.
watch
Watch the source directory for changes and rebuild the PDF on every file change.
pretty-converter watch [flags]
Debounces changes by 300ms. Watches .md, .mdx, .txt, .yaml, and .yml files. Prints a build/error summary on Ctrl+C.
version
Print the version number.
pretty-converter version
completion
Generate shell completion scripts.
pretty-converter completion [bash|zsh|fish|powershell]
| Shell |
Install command |
| bash |
pretty-converter completion bash > /etc/bash_completion.d/pretty-converter |
| zsh |
pretty-converter completion zsh > "${fpath[1]}/_pretty-converter" |
| fish |
pretty-converter completion fish > ~/.config/fish/completions/pretty-converter.fish |
| powershell |
pretty-converter completion powershell > _pretty-converter.ps1 then . .\_pretty-converter.ps1 |