prettypdf

package module
v0.10.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 16, 2026 License: MIT Imports: 12 Imported by: 0

README

go-pretty-pdf

Go Reference CI Go Report Card

Transform a directory of MDX files into a beautiful, print-ready PDF via headless Chrome.

Library + CLI. Use it as a composable Go library or as a standalone command-line tool.

Install

CLI (binary)
go install github.com/sazardev/go-pretty-pdf/cmd/pretty-pdf@latest
Library
go get github.com/sazardev/go-pretty-pdf
Requirements
  • Go 1.26+
  • Chrome or Chromium — optional. If none is found on your system, pretty-pdf automatically downloads and caches a small headless-only Chrome build the first time you run it (like Playwright/Puppeteer do). Already have Chrome installed? It's used as-is, nothing is downloaded. Prefer to control this yourself? Pass --chrome-path /path/to/chrome or set PRETTY_PDF_CHROME_PATH. Auto-download currently covers linux/amd64, darwin/amd64, darwin/arm64, and windows/amd64 — on linux/arm64 (no official build exists yet) install Chromium via your package manager and point --chrome-path at it.

Quick start

CLI
# Scaffold a new book project (interactive wizard)
pretty-pdf init my-book

# Build a PDF
pretty-pdf build --source my-book --out my-book.pdf

# Watch for changes and rebuild
pretty-pdf watch --source my-book --out my-book.pdf

# Validate MDX files
pretty-pdf check --source my-book
Library
package main

import (
	"context"
	"log"

	prettypdf "github.com/sazardev/go-pretty-pdf"
)

func main() {
	pdf, err := prettypdf.New(
		prettypdf.WithSourceDir("./docs"),
		prettypdf.WithOutputFile("output.pdf"),
		prettypdf.WithTitle("My Documentation"),
		prettypdf.WithAuthor("Jane Doe"),
	)
	if err != nil {
		log.Fatal(err)
	}

	if err := pdf.Build(context.Background()); err != nil {
		log.Fatal(err)
	}
}

How it works

MDX files → Parse frontmatter & markdown → Transpile components → Compose HTML → Render PDF
  1. Parse — goldmark parses MDX files with YAML frontmatter
  2. Transpile — custom components (<DeepDive>, <Warning>, <Axiom>) become styled HTML
  3. Compose — HTML assembled with embedded template + CSS + auto-generated Table of Contents
  4. Render — headless Chrome prints to PDF with headers, footers, and PDF bookmarks, then an automatic quality audit checks the result for overflowing content, broken images, low-contrast text, near-empty output, and headings at risk of being clipped by the print engine (see pretty-pdf build's Warnings output, or render.RenderToPDFWithAudit in the library API)

Documents are sorted by their [X.Y.Z] frontmatter ID, not filename.

Trust model

MDX is parsed with raw HTML passthrough enabled, and custom components don't escape their inner content — this lets authors embed arbitrary HTML/CSS for rich documents, but it also means a .mdx file can contain a <script> tag that will execute during rendering. By default, headless Chrome's network access is blocked while rendering (see WithNetworkAccess), so scripts can't exfiltrate data or fetch remote content — but they still run. Only build PDFs from MDX you trust. See SECURITY.md for details.

MDX format

---
id: "[1.0.0]"
title: "Getting Started"
subtitle: "A simple introduction"
tags: [example, intro]
difficulty: "beginner"
status: complete
completeness: 100
depends_on: []
---

# Welcome to Your Book

This is the first chapter.

## Variables

You can use {{key}} syntax for variable substitution: running {{product}} v{{version}}.

Required frontmatter fields: id (format [X.Y.Z]), title.

Built-in components

Component Usage Appearance
<DeepDive> <DeepDive title="Details">...</DeepDive> Blue info panel
<Warning> <Warning title="Note">...</Warning> Orange warning panel
<Axiom> <Axiom>...</Axiom> Green italic quote

Register custom components via WithComponent():

prettypdf.WithComponent("Callout", func(attrs map[string]string, inner string) string {
	level := attrs["level"]
	return fmt.Sprintf(`<div class="callout callout-%s">%s</div>`, level, inner)
})

Configuration

Create a go-pretty-pdf.yml in your project:

title: "My Book"
subtitle: "A Complete Guide"
author: "Jane Doe"
source: book
output: out.pdf
theme: default

css: custom.css
template: custom-template.html

vars:
  product: "go-pretty-pdf"
  version: "1.0"

lint:
  require_frontmatter: [id, title]
  require_id_format: "[X.Y.Z]"
  no_duplicate_ids: true
  max_heading_depth: 3

render:
  timeout: 60s
  paper: a4
  margin_top: 20mm
  margin_bottom: 20mm
  margin_left: 15mm
  margin_right: 15mm
  header_title: "{{title}}"

Library API

// Constructor with functional options
pdf, err := prettypdf.New(opts...)

// All-in-one build pipeline
pdf.Build(ctx)

// Step-by-step pipeline
docs, _ := pdf.ParseDir()
errs := pdf.ValidateDoc(doc)
html, _ := pdf.ComposeHTML(docs)
pdf.Render(html)

// Quality audit from the most recent Build/Render call (nil if neither ran yet)
audit := pdf.LastAudit()

// Validation-only
errs, _ := pdf.Validate(ctx)

// Lower-level: render straight to PDF and get the audit report back
report, err := render.RenderToPDFWithAudit(html, "out.pdf", render.DefaultOptions())
Available options
Option Description
WithSourceDir(dir) MDX source directory (default: book)
WithOutputFile(path) Output PDF path (default: out.pdf)
WithTitle(title) Document title
WithSubtitle(sub) Document subtitle
WithAuthor(author) Document author
WithCSS(css) Custom CSS content string
WithTemplate(html) Custom HTML template string
WithTheme(t) Apply a raw theme.Theme (no customization/section toggles)
WithThemeName(name, opts) Resolve a theme by name (builtin, custom, or file path) with color/font/section customization
WithComponent(name, handler) Register custom MDX component
WithValidator(v) Custom validation logic
WithTimeout(d) Chrome render timeout (default: 60s)
WithHeaderTitle(t) PDF header title
WithVerbose(bool) Enable verbose logging
WithVars(map) Variable substitution map
WithRenderMargins(t,b,l,r) PDF margins in inches
WithPaperSize(w,h) Paper size in inches
WithConfig(cfg) Apply source/output/title/subtitle/author from config
WithConfigCSSAndTemplate(cfg) Load CSS/template from config file paths
WithFullConfig(cfg) Apply the entire config struct (source, CSS/template, theme, vars, render settings) in one call
WithNetworkAccess(bool) Allow headless Chrome to make network requests while rendering (default: false, blocked)

Themes

Seventeen built-in themes, each a palette/typography layer over one shared structural stylesheet — clean and professional by default, easy to customize without writing CSS, and extendable with your own custom themes:

default · minimal · modern · classic · corporate · dark · academic · editorial · sepia · terminal · blueprint · ivy · government · resume · legal · latex · gruvbox

# Pick a theme, tweak colors/fonts/density, drop sections you don't want
pretty-pdf build --theme corporate \
  --color-primary "#0ea5e9" --font-heading "Georgia, serif" \
  --no-cover --no-page-numbers --density compact

# Scaffold your own reusable theme
pretty-pdf theme new my-report --from corporate
pretty-pdf theme list
prettypdf.WithThemeName("corporate", theme.Options{
	Colors:   theme.Colors{Primary: "#0ea5e9"},
	Sections: theme.Sections{Cover: theme.BoolPtr(false)},
})

Custom themes live in <name>.theme.yml files (project-local ./themes/ or a global themes directory) and extends a builtin theme. Full reference, all customization fields, and the pretty-pdf theme command family: see docs/cli.md#themes.

CLI reference

pretty-pdf build     Build a PDF from MDX source files
pretty-pdf epub      Build an EPUB from MDX source files (no Chrome required)
pretty-pdf check     Validate MDX files without building
pretty-pdf theme     List, inspect, and manage themes
pretty-pdf init      Scaffold a new book project (interactive wizard)
pretty-pdf watch     Watch for changes and rebuild automatically
pretty-pdf serve     Preview MDX as HTML with live reload (no Chrome required)
pretty-pdf version   Print the version number

Run pretty-pdf <command> --help for the full flag list of any command.

Global flags: --config, --source, --verbose, --no-color, --quiet

License

MIT — see LICENSE.

Documentation

Overview

Package prettypdf transforms MDX source files into print-ready PDFs via headless Chrome.

It is both a composable Go library and a CLI tool. The library exposes a pipeline with step-by-step methods (ParseDir, ValidateDoc, ComposeHTML, Render) and 18 functional options for full customization.

Quick start (library)

pdf, err := prettypdf.New(
	prettypdf.WithSourceDir("./docs"),
	prettypdf.WithOutputFile("output.pdf"),
	prettypdf.WithTitle("My Book"),
)
if err != nil {
	log.Fatal(err)
}
if err := pdf.Build(context.Background()); err != nil {
	log.Fatal(err)
}

Quick start (CLI)

go install github.com/sazardev/go-pretty-pdf/cmd/pretty-pdf@latest
pretty-pdf build --source ./docs --out out.pdf

MDX files require frontmatter with an `id` field in [X.Y.Z] format (e.g. `[1.0.0]`). Documents are sorted by ID, not filename. Variables in {{key}} syntax are substituted before parsing.

Built-in custom components: <DeepDive>, <Warning>, <Axiom>. Additional components can be registered via WithComponent().

Trust model

MDX is parsed with raw HTML passthrough enabled, and component transpilation does not escape inner content. Only build PDFs from MDX you trust — see SECURITY.md for details. Network access during rendering is blocked by default (see WithNetworkAccess).

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ComposeHTML

func ComposeHTML(docs []*mdx.Document, opts ComposeOptions) (string, error)

Types

type ComposeOptions

type ComposeOptions = compose.Options

func DefaultComposeOptions

func DefaultComposeOptions() ComposeOptions

type Option

type Option func(*PDF)

func WithAuthor

func WithAuthor(author string) Option

func WithCSS

func WithCSS(css string) Option

func WithChromeExecPath added in v0.5.0

func WithChromeExecPath(path string) Option

WithChromeExecPath pins rendering to a specific Chrome/Chromium binary instead of chromedp's default system discovery. Leave unset (or pass "") to keep the default behavior. See the chromemgr package for resolving this automatically, including downloading a browser when none is installed.

func WithComponent

func WithComponent(name string, handler mdx.ComponentHandler) Option

func WithConfig

func WithConfig(cfg *config.Config) Option

func WithConfigCSSAndTemplate

func WithConfigCSSAndTemplate(cfg *config.Config) Option

WithConfigCSSAndTemplate resolves cfg.Theme (with cfg.ThemeOptions customization) and then loads CSS/template content from cfg.CSS/ cfg.Template, which — being explicit file overrides — take priority over the theme and replace its CSS/template outright. Read/resolve failures are recorded as warnings and flushed to stderr by New() once all options have been applied, so ordering relative to WithVerbose does not matter.

func WithCoverImage added in v0.8.0

func WithCoverImage(imagePath string) Option

WithCoverImage replaces the theme's text cover with a full-bleed page built from imagePath (.png/.jpg/.jpeg). That cover page is sized to the image's own pixel dimensions exactly — a square image gets a square cover page — while every other page keeps its configured paper size. The theme's own text cover (title/subtitle/metadata) is suppressed regardless of theme/section settings and regardless of the order options are applied in — see New(), which enforces this once after every Option has run.

func WithEpubLanguage added in v0.10.0

func WithEpubLanguage(lang string) Option

func WithFormats added in v0.10.0

func WithFormats(formats ...OutputFormat) Option

func WithFullConfig added in v0.3.0

func WithFullConfig(cfg *config.Config) Option

WithFullConfig applies every field of cfg: source/output/title/subtitle /author (via WithConfig), CSS/template/theme (via WithConfigCSSAndTemplate), variable substitution (cfg.Vars), and render settings (cfg.Render: timeout, paper size, margins, header title). Unlike WithConfig and WithConfigCSSAndTemplate, which only cover a subset of Config, this is the single option needed to fully apply a loaded go-pretty-pdf.yml.

func WithHeaderTitle

func WithHeaderTitle(title string) Option

WithHeaderTitle sets the PDF page header text. If never called, New() defaults it to the document title (WithTitle/composeOpts.Title).

func WithNetworkAccess added in v0.3.0

func WithNetworkAccess(enabled bool) Option

WithNetworkAccess controls whether headless Chrome may make outbound network requests while rendering. It defaults to false: the composed HTML is a self-contained data URI, so network access is blocked to prevent SSRF/exfiltration from untrusted MDX content (e.g. a malicious <img> or <script> tag). Enable it only if your documents intentionally reference remote images, fonts, or other resources by URL.

func WithOutputFile

func WithOutputFile(path string) Option

func WithPaperSize

func WithPaperSize(width, height float64) Option

func WithRenderMargins

func WithRenderMargins(top, bottom, left, right float64) Option

func WithSourceDir

func WithSourceDir(dir string) Option

func WithSubtitle

func WithSubtitle(subtitle string) Option

func WithTemplate

func WithTemplate(html string) Option

func WithTheme

func WithTheme(t theme.Theme) Option

WithTheme applies a raw builtin/synthetic Theme's CSS as-is, with no customization (colors/fonts/sections/density) and no section toggles applied. It shares composeOpts.CSS with WithCSS and WithThemeName — whichever of these options is applied last wins, since New() applies options in the order they're passed. Most callers should prefer WithThemeName, which resolves section toggles (cover/TOC/page numbers/header) into composeOpts/renderOpts too.

func WithThemeName added in v0.4.0

func WithThemeName(name string, opts theme.Options) Option

WithThemeName resolves a theme by name — a builtin ("default", "corporate", ...), a custom theme discovered in ./themes/ or the global themes directory, or a direct path to a .theme.yml/.css file — applies opts customization (colors, fonts, density, network fonts), and wires the resulting section toggles (cover, TOC, page numbers, header) into composeOpts/renderOpts.

func WithTimeout

func WithTimeout(d time.Duration) Option

func WithTitle

func WithTitle(title string) Option

func WithValidator

func WithValidator(v mdx.Validator) Option

func WithVars

func WithVars(vars map[string]string) Option

func WithVerbose

func WithVerbose(v bool) Option

type OutputFormat added in v0.10.0

type OutputFormat string
const (
	FormatPDF  OutputFormat = "pdf"
	FormatEPUB OutputFormat = "epub"
)

func ParseFormats added in v0.10.0

func ParseFormats(s string) ([]OutputFormat, error)

type PDF

type PDF struct {
	// contains filtered or unexported fields
}

func New

func New(opts ...Option) (*PDF, error)

func (*PDF) Build

func (p *PDF) Build(ctx context.Context) error

func (*PDF) ComposeHTML

func (p *PDF) ComposeHTML(docs []*mdx.Document) (string, error)

func (*PDF) Formats added in v0.10.0

func (p *PDF) Formats() []OutputFormat

func (*PDF) LastAudit added in v0.8.0

func (p *PDF) LastAudit() *render.AuditReport

LastAudit returns the visual/structural audit report from the most recent Build or Render call, or nil if neither has run yet. See render.AuditReport and render/audit.go for what it checks.

func (*PDF) NeedsChrome added in v0.10.0

func (p *PDF) NeedsChrome() bool

func (*PDF) ParseDir

func (p *PDF) ParseDir() ([]*mdx.Document, error)

func (*PDF) Render

func (p *PDF) Render(html string) error

func (*PDF) RenderEpub added in v0.10.0

func (p *PDF) RenderEpub(docs []*mdx.Document, outputPath string) error

func (*PDF) Validate

func (p *PDF) Validate(ctx context.Context) ([]mdx.ValidationError, error)

func (*PDF) ValidateAll added in v0.2.0

func (p *PDF) ValidateAll(docs []*mdx.Document) []mdx.ValidationError

func (*PDF) ValidateDoc

func (p *PDF) ValidateDoc(doc *mdx.Document) []mdx.ValidationError

func (*PDF) Warnings added in v0.9.0

func (p *PDF) Warnings() []string

Warnings returns the non-fatal configuration warnings recorded by New — e.g. an unresolvable theme name or an unreadable --css/--template file — each of which caused that option to fall back rather than apply. They are also printed to stderr by New itself; this accessor exists for callers that want to detect the condition programmatically instead.

Directories

Path Synopsis
Package chromemgr resolves a working Chrome/Chromium executable for headless rendering without requiring the user to install one by hand.
Package chromemgr resolves a working Chrome/Chromium executable for headless rendering without requiring the user to install one by hand.
cmd
pretty-pdf command
Package epub converts parsed MDX documents into a single EPUB 3 file — no headless Chrome involved, unlike the PDF pipeline in render.
Package epub converts parsed MDX documents into a single EPUB 3 file — no headless Chrome involved, unlike the PDF pipeline in render.
scripts
bump command
docsgen command
Package theme implements go-pretty-pdf's theme engine: a set of professional built-in themes plus a customization layer (colors, fonts, section toggles, density) that composes on top of them via CSS custom properties, and a YAML format for user-defined custom themes.
Package theme implements go-pretty-pdf's theme engine: a set of professional built-in themes plus a customization layer (colors, fonts, section toggles, density) that composes on top of them via CSS custom properties, and a YAML format for user-defined custom themes.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL