Write books as plain Markdown in git; build them into print-ready PDF, web HTML, EPUB, and DOCX with a TypeScript pipeline around Pandoc.
The complete workflow (scaffolding, conventions, gotchas, verification) ships as a skill for Claude Code / opencode:
git clone https://github.com/mahabubone/bookforge
mkdir -p ~/.claude/skills # Claude Code
cp -r bookforge/skill/bookforge ~/.claude/skills/
# or for opencode: .opencode/skills/bookforge/ inside any projectThen use the bookforge skill when writing books, scaffolding new books, or troubleshooting builds. The repo itself doubles as the reference template.
# first time
npm install # TS tooling (yaml, tsx)
# pandoc >= 3.10 required (binary in ~/.local/bin works)
npm run build # all books, all formats
npm run novel # one book
npm run bangla-novel # Bangla folklore novel
npm run git-bangla # Git for beginners (Bangla)
npx tsx src/build.ts --book novel --format html --format epub
npm run clean # rm -rf distOutputs land in dist/<book>/: <id>.html, <id>.pdf, <id>.epub, <id>.docx, cover.png, plus fonts/ (self-contained preview) and dist/tmp/<book>/manuscript.md (the composed debug artifact).
books/
<book-id>/
book.yaml # metadata + build options
frontmatter/ # dedication, copyright, preface (## headings)
parts/ # novel only: part openers (# headings)
chapters/ # one file per chapter, numbered (# or ##)
backmatter/ # acknowledgments, appendix, glossary
assets/
css/base.css # shared typography + components (inlined into HTML)
css/print.css # CSS Paged Media: @page, running heads, page numbers
css/screen.css # browser-only layout
css/epub.css # reader-safe, self-contained
css/books/<id>.css # optional per-book overrides
covers/<id>.html # cover template -> PDF -> PNG (pdftoppm)
docx/reference.docx # DOCX style sheet (regenerate: pandoc --print-default-data-file)
fonts/ # OFL fonts: Source Serif 4, Source Sans 3, JetBrains Mono, Noto Serif/Sans Bengali
src/
build.ts # CLI: --book, --format
config.ts # book.yaml schema + discovery
compose.ts # manuscript composition + title page
run.ts # pandoc/weasyprint invocations per format
id: novel
title: The Cartographer's Shadow
author: A. N. Author
division: part # part -> # = parts, ## = chapters | chapter -> # = chapters
numberSections: false # technical books: true
tocDepth: 0 # 0 = no TOC; 2 = TOC with subheadings
highlightStyle: tango # pandoc syntax palette (technical)
epubChapterLevel: 1 # EPUB split depth
formats: [html, pdf, epub, docx]- Fenced divs for components — always use attribute syntax (safe across pandoc versions, including 3.10):
::: {.callout .note}/{.callout .warning}/{.callout .tip},{.epigraph},{.dedication},{.copyright}. - Scene breaks (novel):
***on its own line — renders as a centered ✦ ornament. - Footnotes:
[^1]/[^1]: ...; renumbered automatically. - Cross-references:
[text](#heading-id); set explicit ids with{#id}on headings. - Callouts:
::: {.callout .note}+**Title.** text+:::. - Code: fenced blocks with language tags get syntax highlighting via
--syntax-highlighting. - Glossary: pandoc definition lists (
term/: definition).
HTML and PDF share 90% of their CSS: base.css + per-book override are inlined into the HTML <head>, and the PDF is the same HTML rendered by WeasyPrint (--pdf-engine=weasyprint), which supports CSS Paged Media — @page size/margins, margin-box page numbers, running headers via string-set/string(). Fonts are embedded variable TTFs; WeasyPrint resolves them via --base-url, browsers via relative fonts/ URLs.
EPUB gets a conservative self-contained stylesheet (readers ignore most CSS); DOCX gets named styles from the committed reference.docx.
Covers are HTML templates rendered by WeasyPrint to PDF, then converted to cover.png (1563×2344 px) with pdftoppm. WeasyPrint 69's CLI is PDF-only (PNG output was removed), so the two-step route is required. The PNG feeds the EPUB as --epub-cover-image.
Bangla books work end to end: books/bangla-novel (folk-tale novel) and books/git-bangla (Git for beginners) are full examples.
- Fonts:
Noto Serif Bengali(body) +Noto Sans Bengali(headings) ship inassets/fonts/and are installed to~/.fontsfor WeasyPrint. CSS stacks fall back through them; EPUBs embed them via--epub-embed-font(declared inbook.yamlasfonts:), with@font-facerules already inepub.cssusing pandoc's documented../fonts/paths. lang: bnflows intobook.yaml, HTMLlangattribute, and EPUB metadata.- Complex-script rules:
letter-spacingandtext-transformbreak Bengali conjunct shaping — the per-book CSS (assets/css/books/bangla-novel.css,git-bangla.css) neutralizes them (headings, title page, callouts, tables, running headers). genreLabeloverrides the English "A Novel" / "Technical Handbook" title-page kicker for non-English books.- PDF text extraction with
pdftotextscatters Bengali glyphs — that's an extraction artifact, not a rendering problem; WeasyPrint shapes via Pango/HarfBuzz.
- pandoc >= 3.10 renames
--highlight-styleto--syntax-highlighting; config keyhighlightStylemaps to it. - pandoc 3.10.1 has a regression: short-form multi-class fenced divs (
::: callout note) parse as plain text. The{.callout .note}attribute form is unaffected — use it. - WeasyPrint does not support
overflow-xor@mediaqueries; keep those inscreen.css(browser-only). - PDF engine alternatives:
--pdf-engine=xelatexneeds a TeX distribution and ignores your CSS;typstis a growing third option.
- New book: copy
books/novel/orbooks/technical/, editbook.yaml, write chapters; the pipeline discovers any directory withbook.yaml. - New format: add a
buildXinsrc/run.ts+ npm script or CLI--formatentry. - Math, citations (Zotero/BibTeX), and custom pandoc Lua filters slot in via
inputArgsinsrc/run.ts.