md2pdf

package module
v1.9.2 Latest Latest
Warning

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

Go to latest
Published: Mar 5, 2026 License: BSD-3-Clause Imports: 19 Imported by: 0

README

go-md2pdf

Go Reference Go Report Card Build Status Coverage License

Go library and CLI for Markdown to PDF conversion using headless Chrome. Auto-downloads Chromium on first run. Features cover pages, automatic table of contents, footers with page numbers, signatures, watermarks, and 8 built-in CSS themes with custom template support. Supports parallel batch processing.

See example outputs

Example PDF outputs

Table of Contents

Installation

go install github.com/alnah/go-md2pdf/cmd/md2pdf@latest
Other installation methods
Docker
docker pull ghcr.io/alnah/go-md2pdf:latest
Binary Download

Download pre-built binaries from GitHub Releases.

Requirements

  • Go 1.25+
  • Chrome/Chromium (downloaded automatically on first run)

Docker/CI users: See Troubleshooting for setup instructions.

Quick Start

CLI
md2pdf convert document.md                # Single file
md2pdf convert ./docs/ -o ./output/       # Batch convert
md2pdf convert -c work document.md        # With config
md2pdf config init                        # Create config with wizard
Library
package main

import (
    "context"
    "log"
    "os"

    "github.com/alnah/go-md2pdf"
)

func main() {
    conv, err := md2pdf.NewConverter()
    if err != nil {
        log.Fatal(err)
    }
    defer conv.Close()

    result, err := conv.Convert(context.Background(), md2pdf.Input{
        Markdown: "# Hello World\n\nGenerated with go-md2pdf.",
    })
    if err != nil {
        log.Fatal(err)
    }

    os.WriteFile("output.pdf", result.PDF, 0644)
}

The Convert() method returns a ConvertResult containing:

  • result.PDF - the generated PDF bytes
  • result.HTML - the intermediate HTML (useful for debugging)

Use Input.HTMLOnly: true to skip PDF generation and only produce HTML.

Features

  • CLI + Library - Use as md2pdf command or import in Go, with shell completion
  • Batch conversion - Process directories with parallel workers
  • Cover pages - Title, subtitle, logo, author, organization, date, version
  • Table of contents - Auto-generated from headings with configurable depth
  • Frontmatter stripping - YAML frontmatter (--- blocks) stripped before conversion
  • Custom styling - Embedded themes or your own CSS (some limitations)
  • Page settings - Size (letter, A4, legal), orientation, margins
  • Signatures - Name, title, email, photo, links
  • Footers - Page numbers, dates, status text
  • Watermarks - Diagonal background text (BRAND, etc.)

CLI Reference

md2pdf convert document.md                # Single file
md2pdf convert ./docs/ -o ./output/       # Batch convert
md2pdf convert -c work document.md        # With config
md2pdf convert --style technical doc.md   # With style
md2pdf config init                        # Interactive config wizard
All flags
md2pdf <command> [flags] [args]

Commands:
  convert      Convert markdown files to PDF
  config       Manage configuration files
  doctor       Check system configuration
  completion   Generate shell completion script
  version      Show version information
  help         Show help for a command

md2pdf convert <input> [flags]

Input/Output:
  -o, --output <path>       Output file or directory
  -c, --config <name>       Config file name or path
  -w, --workers <n>         Parallel workers (0 = auto)
  -t, --timeout <duration>  PDF generation timeout (default: 30s)
                            Examples: 30s, 2m, 1m30s

Author:
      --author-name <s>     Author name
      --author-title <s>    Author professional title
      --author-email <s>    Author email
      --author-org <s>      Organization name
      --author-phone <s>    Author phone number
      --author-address <s>  Author postal address
      --author-dept <s>     Author department

Document:
      --doc-title <s>       Document title ("" = auto from H1)
      --doc-subtitle <s>    Document subtitle
      --doc-version <s>     Version string
      --doc-date <s>        Date (see Date Formats section)
      --doc-client <s>      Client name
      --doc-project <s>     Project name
      --doc-type <s>        Document type
      --doc-id <s>          Document ID/reference
      --doc-desc <s>        Document description

Page:
  -p, --page-size <s>       letter, a4, legal (default: letter)
      --orientation <s>     portrait, landscape (default: portrait)
      --margin <f>          Margin in inches (default: 0.5)

Footer:
      --footer-position <s> left, center, right (default: right)
      --footer-text <s>     Custom footer text
      --footer-page-number  Show page numbers
      --footer-doc-id       Show document ID in footer
      --no-footer           Disable footer

Cover:
      --cover-logo <path>   Logo path or URL
      --cover-dept          Show author department on cover
      --no-cover            Disable cover page

Signature:
      --sig-image <path>    Signature image path
      --no-signature        Disable signature block

Table of Contents:
      --toc-title <s>       TOC heading text
      --toc-min-depth <n>   Min heading depth (1-6, default: 2)
                            1=H1, 2=H2, etc. Use 2 to skip title
      --toc-max-depth <n>   Max heading depth (1-6, default: 3)
      --no-toc              Disable table of contents

Watermark:
      --wm-text <s>         Watermark text
      --wm-color <s>        Color hex (default: #888888)
      --wm-opacity <f>      Opacity 0.0-1.0 (default: 0.1)
      --wm-angle <f>        Angle in degrees (default: -45)
      --no-watermark        Disable watermark

Page Breaks:
      --break-before <s>    Break before headings: h1,h2,h3
      --orphans <n>         Min lines at page bottom (default: 2)
      --widows <n>          Min lines at page top (default: 2)
      --no-page-breaks      Disable page break features

Assets & Styling:
      --style <name|path>   CSS style name or file path (default: default)
                            Name: uses embedded or custom asset (e.g., "technical")
                            Path: reads file directly (contains / or \)
      --template <name|path> Template set name or directory path
      --asset-path <dir>    Custom asset directory (overrides config)
      --no-style            Disable CSS styling

Debug Output:
      --html                Output HTML alongside PDF
      --html-only           Output HTML only, skip PDF generation

Output Control:
  -q, --quiet               Only show errors
  -v, --verbose             Show detailed timing

md2pdf config init [flags]

Config Init:
      --output <path>       Output path for generated config (default: ./md2pdf.yaml)
      --force               Overwrite destination if it exists
      --no-input            Use defaults without interactive prompts
Examples
# Single file with custom output
md2pdf convert -o report.pdf input.md

# Batch with config
md2pdf convert -c work ./docs/ -o ./pdfs/

# Custom CSS, no footer
md2pdf convert --style ./custom.css --no-footer document.md

# A4 landscape with 1-inch margins
md2pdf convert -p a4 --orientation landscape --margin 1.0 document.md

# With watermark
md2pdf convert --wm-text "DRAFT" --wm-opacity 0.15 document.md

# Override document title
md2pdf convert --doc-title "Final Report" document.md

# Page breaks before H1 and H2 headings
md2pdf convert --break-before h1,h2 document.md

# Use embedded style by name
md2pdf convert --style technical document.md

# Debug: output HTML alongside PDF
md2pdf convert --html document.md

# Debug: output HTML only (no PDF)
md2pdf convert --html-only document.md

# Use custom assets directory
md2pdf convert --asset-path ./my-assets document.md

# Interactive config wizard
md2pdf config init

# Non-interactive config generation (CI/scripts)
md2pdf config init --no-input --output ./configs/work.yaml --force
Shell Completion

Generate shell completion scripts for tab-completion of commands, flags, and file arguments:

# Bash - add to ~/.bashrc
eval "$(md2pdf completion bash)"

# Zsh - add to ~/.zshrc
eval "$(md2pdf completion zsh)"

# Fish - save to completions directory
md2pdf completion fish > ~/.config/fish/completions/md2pdf.fish

# PowerShell - add to $PROFILE
md2pdf completion powershell | Out-String | Invoke-Expression
Exit Codes
Code Name Description
0 Success Conversion completed successfully
1 General Unexpected or unclassified error
2 Usage Invalid flags, configuration, or validation failure
3 I/O File not found, permission denied, write failure
4 Browser Chrome not found, connection failed, timeout

Example usage in scripts:

md2pdf convert document.md
case $? in
    0) echo "Success" ;;
    2) echo "Check your flags or config" ;;
    3) echo "Check file permissions" ;;
    4) echo "Check Chrome installation" ;;
    *) echo "Unknown error" ;;
esac
Doctor Command

Diagnose system configuration before running conversions:

md2pdf doctor           # Human-readable output
md2pdf doctor --json    # JSON output for CI/scripts

Checks performed:

  • Chrome/Chromium: binary exists, version, sandbox status
  • Environment: container detection (Docker, Podman, Kubernetes)
  • System: temp directory writability

Exit codes:

  • 0 - All checks passed (including warnings)
  • 1 - Errors found (conversion will likely fail)

Example CI usage:

# Fail pipeline early if setup is broken
md2pdf doctor --json | jq -e '.status != "errors"' || exit 1
Docker
# Convert a single file
docker run --rm -v $(pwd):/data ghcr.io/alnah/go-md2pdf convert document.md

# Convert with output path
docker run --rm -v $(pwd):/data ghcr.io/alnah/go-md2pdf convert -o output.pdf input.md

# Batch convert directory
docker run --rm -v $(pwd):/data ghcr.io/alnah/go-md2pdf convert ./docs/ -o ./pdfs/

Note: The official Docker image has all dependencies pre-installed. For custom images, see Troubleshooting.

Environment Variables

Environment variables provide CI/CD-friendly configuration without requiring YAML files.

Priority: CLI flags > config file > environment variables > defaults

MD2PDF Variables
Variable Description
MD2PDF_CONFIG Config file path (e.g., /app/config.yaml)
MD2PDF_INPUT_DIR Default input directory
MD2PDF_OUTPUT_DIR Default output directory
MD2PDF_TIMEOUT PDF generation timeout (e.g., 2m, 90s)
MD2PDF_STYLE CSS style name or path (e.g., technical)
MD2PDF_WORKERS Parallel workers (e.g., 4)
MD2PDF_AUTHOR_NAME Author name for cover/signature
MD2PDF_AUTHOR_ORG Organization name
MD2PDF_AUTHOR_EMAIL Author email
MD2PDF_DOC_VERSION Document version
MD2PDF_DOC_DATE Document date (supports auto)
MD2PDF_DOC_ID Document ID
MD2PDF_PAGE_SIZE Page size: letter, a4, legal
MD2PDF_COVER_LOGO Cover logo path/URL (auto-enables cover)
MD2PDF_WATERMARK_TEXT Watermark text (auto-enables watermark)
MD2PDF_CONTAINER Set to 1 to force container detection (for md2pdf doctor)

Unknown MD2PDF_* variables trigger a warning to catch typos.

CI/CD Examples

GitHub Actions:

- name: Generate PDFs
  env:
    MD2PDF_STYLE: technical
    MD2PDF_AUTHOR_ORG: ${{ github.repository_owner }}
    MD2PDF_DOC_VERSION: ${{ github.ref_name }}
    MD2PDF_WATERMARK_TEXT: ${{ github.ref_name == 'main' && '' || 'DRAFT' }}
  run: md2pdf convert ./docs/ -o ./output/

GitLab CI:

pdf:
  variables:
    MD2PDF_STYLE: corporate
    MD2PDF_OUTPUT_DIR: ./artifacts/pdf
    MD2PDF_DOC_DATE: auto
  script:
    - md2pdf convert ./docs/

Docker:

docker run --rm \
  -e MD2PDF_STYLE=technical \
  -e MD2PDF_AUTHOR_ORG="Acme Corp" \
  -e ROD_NO_SANDBOX=1 \
  -v $(pwd):/data \
  ghcr.io/alnah/go-md2pdf convert ./docs/
Browser Variables (go-rod)
Variable Default Description
ROD_NO_SANDBOX - Set to 1 to disable Chrome sandbox (required for Docker/CI)
ROD_BROWSER_BIN - Path to custom Chrome/Chromium binary

These are used by the underlying go-rod browser automation library. Error messages will suggest these variables when browser issues are detected in CI/Docker environments.

Configuration

Config files are searched in the current directory first, then in the user config directory:

OS User Config Directory
Linux ~/.config/go-md2pdf/
macOS ~/Library/Application Support/go-md2pdf/
Windows %APPDATA%\go-md2pdf\

Supported formats: .yaml, .yml

Config Init Wizard

Use the wizard to generate a valid config file without writing YAML manually:

# Interactive wizard (TTY required)
md2pdf config init

# Custom destination
md2pdf config init --output ./configs/work.yaml

# Non-interactive defaults (CI/scripts)
md2pdf config init --no-input --output ./configs/work.yaml --force

Wizard behavior:

  • Prompts are in English and include available options plus an example value.
  • Type ? at a prompt to display inline help and a YAML snippet.
  • Interactive mode collects style, author fields, page size, and optional signature/watermark/cover settings.
  • Interactive mode shows a summary and YAML preview before write confirmation.
  • Without --force, existing files are preserved; with --force, overwrite is explicit and safe.
Option Type Default Description
input.defaultDir string - Default input directory
output.defaultDir string - Default output directory
timeout string "30s" PDF generation timeout (e.g., "30s", "2m")
style string "default" CSS style name or path
assets.basePath string - Custom assets directory (styles, templates)
author.name string - Author name (used by cover, signature)
author.title string - Author professional title
author.email string - Author email
author.organization string - Organization name
author.phone string - Contact phone number
author.address string - Postal address (multiline via YAML |)
author.department string - Department name
document.title string - Document title ("" = auto from H1)
document.subtitle string - Document subtitle
document.version string - Version string (used in cover, footer)
document.date string - Date (see Date Formats)
document.clientName string - Client/customer name
document.projectName string - Project name
document.documentType string - Document type (e.g., "Specification")
document.documentID string - Document ID (e.g., "DOC-2025-001")
document.description string - Brief document summary
page.size string "letter" letter, a4, legal
page.orientation string "portrait" portrait, landscape
page.margin float 0.5 Margin in inches (0.25-3.0)
cover.enabled bool false Show cover page
cover.logo string - Logo path or URL
cover.showDepartment bool false Show author.department on cover
toc.enabled bool false Show table of contents
toc.title string - TOC title (empty = no title)
toc.minDepth int 2 Min heading depth (1-6, skips H1)
toc.maxDepth int 3 Max heading depth (1-6)
footer.enabled bool false Show footer
footer.showPageNumber bool false Show page numbers
footer.position string "right" left, center, right
footer.text string - Custom footer text
footer.showDocumentID bool false Show document.documentID in footer
signature.enabled bool false Show signature block
signature.imagePath string - Photo path or URL
signature.links array - Links (label, url)
watermark.enabled bool false Show watermark
watermark.text string - Watermark text (required if enabled)
watermark.color string "#888888" Watermark color (hex)
watermark.opacity float 0.1 Watermark opacity (0.0-1.0)
watermark.angle float -45 Watermark rotation (degrees)
pageBreaks.enabled bool false Enable page break features
pageBreaks.beforeH1 bool false Page break before H1 headings
pageBreaks.beforeH2 bool false Page break before H2 headings
pageBreaks.beforeH3 bool false Page break before H3 headings
pageBreaks.orphans int 2 Min lines at page bottom (1-5)
pageBreaks.widows int 2 Min lines at page top (1-5)
Example config file
# ~/.config/go-md2pdf/work.yaml

# Input/Output directories
input:
  defaultDir: './docs/markdown' # Default input when no arg provided

output:
  defaultDir: './docs/pdf' # Default output when no -o flag

# PDF generation timeout (default: 30s)
# Use Go duration format: 30s, 2m, 1m30s
timeout: '1m'

# Shared author info (used by cover and signature)
author:
  name: 'John Doe'
  title: 'Senior Developer'
  email: 'john@example.com'
  organization: 'Acme Corp'
  phone: '+1 555-0123'
  address: |
    123 Main Street
    San Francisco, CA 94102
  department: 'Engineering'

# Shared document metadata (used by cover and footer)
document:
  title: '' # "" = auto from H1 or filename
  subtitle: 'Internal Document'
  version: 'v1.0'
  # Date formats:
  #   - Literal: '2025-01-11'
  #   - Auto (ISO): 'auto' -> 2025-01-11
  #   - Auto with format: 'auto:DD/MM/YYYY' -> 11/01/2025
  #   - Auto with preset: 'auto:long' -> January 11, 2025
  # Presets: iso, european, us, long
  # Tokens: YYYY, YY, MMMM, MMM, MM, M, DD, D
  # Escaping: [text] -> literal text
  date: 'auto'
  clientName: 'Client Corp'
  projectName: 'Project Alpha'
  documentType: 'Technical Specification'
  documentID: 'DOC-2025-001'
  description: 'Technical documentation for Project Alpha'

# Page layout
page:
  size: 'a4'           # letter (default), a4, legal
  orientation: 'portrait' # portrait (default), landscape
  margin: 0.75         # inches, 0.25-3.0 (default: 0.5)

# Styling
# Available styles:
#   - default: minimal, neutral styling (applied when no style specified)
#   - technical: system-ui, clean borders, GitHub syntax highlighting
#   - creative: colorful headings, badges, bullet points
#   - academic: Georgia/Times serif, 1.8 line height, academic tables
#   - corporate: Arial/Helvetica, blue accents, business style
#   - legal: Times New Roman, double line height, wide margins
#   - invoice: Arial, optimized tables, minimal cover
#   - manuscript: Courier New mono, scene breaks, simplified cover
# Accepts name (e.g., "technical") or path (e.g., "./custom.css")
style: 'technical'

assets:
  basePath: '' # "" = use embedded assets

# Cover page
cover:
  enabled: true
  logo: '/path/to/logo.png' # path or URL
  showDepartment: true      # show author.department on cover

# Table of contents
toc:
  enabled: true
  title: 'Table of Contents'
  minDepth: 2 # 1-6 (default: 2, skips H1)
  maxDepth: 3 # 1-6 (default: 3)

# Footer
footer:
  enabled: true
  position: 'center'     # left, center, right (default: right)
  showPageNumber: true
  showDocumentID: true   # show document.documentID in footer
  text: ''               # optional custom text

# Signature block
signature:
  enabled: true
  imagePath: '/path/to/signature.png'
  links:
    - label: 'GitHub'
      url: 'https://github.com/johndoe'
    - label: 'LinkedIn'
      url: 'https://linkedin.com/in/johndoe'

# Watermark
watermark:
  enabled: false
  text: 'DRAFT'      # DRAFT, CONFIDENTIAL, SAMPLE, PREVIEW, etc.
  color: '#888888'   # hex color (default: #888888)
  opacity: 0.1       # 0.0-1.0 (default: 0.1, recommended: 0.05-0.15)
  angle: -45         # -90 to 90 (default: -45 = diagonal)

# Page breaks
pageBreaks:
  enabled: true
  beforeH1: true
  beforeH2: false
  beforeH3: false
  orphans: 2 # min lines at page bottom, 1-5 (default: 2)
  widows: 2  # min lines at page top, 1-5 (default: 2)
Date Formats

The document.date field supports auto-generation with customizable formats:

Syntax Example Output
auto auto 2026-01-09
auto:FORMAT auto:DD/MM/YYYY 09/01/2026
auto:preset auto:long January 9, 2026

Presets: iso (YYYY-MM-DD), european (DD/MM/YYYY), us (MM/DD/YYYY), long (MMMM D, YYYY)

Tokens: YYYY, YY, MMMM (January), MMM (Jan), MM, M, DD, D

Escaping: Use brackets for literal text: auto:[Date:] YYYY-MM-DD → "Date: 2026-01-09"

Library Usage

With Relative Images

When your markdown contains relative image paths like ![logo](./images/logo.png), specify the source directory so they resolve correctly:

content, _ := os.ReadFile("docs/report.md")

result, err := conv.Convert(ctx, md2pdf.Input{
    Markdown:  string(content),
    SourceDir: "docs/", // Images resolve relative to this directory
})

The CLI automatically sets SourceDir to the input file's directory, so relative images work out of the box.

With Cover Page
result, err := conv.Convert(ctx, md2pdf.Input{
    Markdown: content,
    Cover: &md2pdf.Cover{
        Title:        "Project Report",
        Subtitle:     "Q4 2025 Analysis",
        Author:       "John Doe",
        AuthorTitle:  "Senior Analyst",
        Organization: "Acme Corp",
        Date:         "2025-12-15",
        Version:      "v1.0",
        Logo:         "/path/to/logo.png", // or URL
        ClientName:   "Client Corp",       // extended metadata
        ProjectName:  "Project Alpha",
        DocumentType: "Technical Report",
        DocumentID:   "DOC-2025-001",
    },
})
With Table of Contents
result, err := conv.Convert(ctx, md2pdf.Input{
    Markdown: content,
    TOC: &md2pdf.TOC{
        Title:    "Contents",
        MinDepth: 2, // Start at h2 (skip document title)
        MaxDepth: 3, // Include up to h3
    },
})
With Footer
result, err := conv.Convert(ctx, md2pdf.Input{
    Markdown: content,
    Footer: &md2pdf.Footer{
        ShowPageNumber: true,
        Position:       "center",
        Date:           "2025-12-15",
        Status:         "DRAFT",
    },
})
With Signature
result, err := conv.Convert(ctx, md2pdf.Input{
    Markdown: content,
    Signature: &md2pdf.Signature{
        Name:         "John Doe",
        Title:        "Senior Developer",
        Email:        "john@example.com",
        Organization: "Acme Corp",
        Phone:        "+1 555-0123",  // extended metadata
        Department:   "Engineering",
    },
})
With Watermark
result, err := conv.Convert(ctx, md2pdf.Input{
    Markdown: content,
    Watermark: &md2pdf.Watermark{
        Text:    "CONFIDENTIAL",
        Color:   "#888888",
        Opacity: 0.1,
        Angle:   -45,
    },
})
With Page Settings
result, err := conv.Convert(ctx, md2pdf.Input{
    Markdown: content,
    Page: &md2pdf.PageSettings{
        Size:        md2pdf.PageSizeA4,
        Orientation: md2pdf.OrientationLandscape,
        Margin:      1.0, // inches
    },
})
With Page Breaks
result, err := conv.Convert(ctx, md2pdf.Input{
    Markdown: content,
    PageBreaks: &md2pdf.PageBreaks{
        BeforeH1: true, // Page break before H1 headings
        BeforeH2: true, // Page break before H2 headings
        Orphans:  3,    // Min 3 lines at page bottom
        Widows:   3,    // Min 3 lines at page top
    },
})
With Custom CSS

The CSS field in Input accepts a CSS string that is injected into the HTML for this specific conversion:

// CSS string injected into this document only
result, err := conv.Convert(ctx, md2pdf.Input{
    Markdown: content,
    CSS: `
        body { font-family: Georgia, serif; }
        h1 { color: #2c3e50; }
        code { background: #f8f9fa; }
    `,
})

This is useful for:

  • Document-specific styling that differs from the base theme
  • Dynamically generated CSS (e.g., user-selected colors)
  • Quick overrides without changing service configuration

For reusable styles across all conversions, see With Custom Assets.

With Custom Assets

Override embedded CSS styles and HTML templates:

// Option 1: Use embedded style by name
conv, err := md2pdf.NewConverter(md2pdf.WithStyle("technical"))

// Option 2: Load CSS from file path
conv, err := md2pdf.NewConverter(md2pdf.WithStyle("./custom.css"))

// Option 3: Provide CSS content directly
conv, err := md2pdf.NewConverter(md2pdf.WithStyle("body { font-family: Georgia; }"))

// Option 4: Load from custom directory (with fallback to embedded)
conv, err := md2pdf.NewConverter(md2pdf.WithAssetPath("/path/to/assets"))

// Option 5: Provide template set directly
ts := md2pdf.NewTemplateSet("custom", coverHTML, signatureHTML)
conv, err := md2pdf.NewConverter(md2pdf.WithTemplateSet(ts))

// Option 6: Full control with custom loader
loader, err := md2pdf.NewAssetLoader("/path/to/assets")
if err != nil {
    log.Fatal(err)
}
conv, err := md2pdf.NewConverter(md2pdf.WithAssetLoader(loader))

WithStyle accepts a style name, file path, or CSS content:

  • Name: "technical" loads the embedded style
  • Path: "./custom.css" reads from file (detected by / or \)
  • CSS: "body { ... }" uses content directly (detected by {)

Expected directory structure for WithAssetPath:

/path/to/assets/
├── styles/
│   ├── default.css      # Override default style
│   └── technical.css    # Add custom style
└── templates/
    └── default/         # Template set directory
        ├── cover.html       # Cover page template
        └── signature.html   # Signature block template

Available embedded styles: default, technical, creative, academic, corporate, legal, invoice, manuscript

Missing files fall back to embedded defaults silently.

Note: Converter-level options (WithAssetPath, WithStyle, WithAssetLoader) configure the base theme for all conversions. To add document-specific CSS on top of the base theme, use Input.CSS in the Convert() call.

With Converter Pool (Parallel Processing)

For batch conversion, use ConverterPool to process multiple files in parallel:

package main

import (
    "context"
    "log"
    "os"
    "sync"

    "github.com/alnah/go-md2pdf"
)

func main() {
    // Create pool with 4 workers (each has its own browser instance)
    pool := md2pdf.NewConverterPool(4)
    defer pool.Close()

    files := []string{"doc1.md", "doc2.md", "doc3.md", "doc4.md"}
    var wg sync.WaitGroup

    for _, file := range files {
        wg.Add(1)
        go func(f string) {
            defer wg.Done()

            conv := pool.Acquire()
            if conv == nil {
                log.Printf("failed to acquire converter: %v", pool.InitError())
                return
            }
            defer pool.Release(conv)

            content, _ := os.ReadFile(f)
            result, err := conv.Convert(context.Background(), md2pdf.Input{
                Markdown: string(content),
            })
            if err != nil {
                log.Printf("convert %s: %v", f, err)
                return
            }
            os.WriteFile(f+".pdf", result.PDF, 0644)
        }(file)
    }
    wg.Wait()
}

Use md2pdf.ResolvePoolSize(0) to auto-calculate optimal pool size based on CPU cores.

Documentation

Full API documentation with runnable examples: pkg.go.dev/github.com/alnah/go-md2pdf

Troubleshooting

Run md2pdf doctor to diagnose system configuration issues:

md2pdf doctor           # Human-readable diagnostics
md2pdf doctor --json    # JSON output for CI/scripts
Docker and CI/CD
"Failed to connect to browser" or blank PDF

Chrome requires disabling its sandbox in containerized environments:

export ROD_NO_SANDBOX=1
md2pdf convert document.md

Or in Docker:

docker run -e ROD_NO_SANDBOX=1 -v $(pwd):/data ghcr.io/alnah/go-md2pdf convert doc.md
Missing dependencies on Linux

If Chrome fails to start, install required libraries:

# Debian/Ubuntu
sudo apt-get update
sudo apt-get install -y \
    libnss3 \
    libatk-bridge2.0-0 \
    libcups2 \
    libdrm2 \
    libxkbcommon0 \
    libxcomposite1 \
    libxdamage1 \
    libxrandr2 \
    libgbm1 \
    libasound2

# Alpine
apk add --no-cache \
    chromium \
    nss \
    freetype \
    harfbuzz \
    ca-certificates \
    ttf-freefont

Note: Dependency lists may change with Chrome versions. See chromedp dependencies for the latest requirements.

Using custom Chrome/Chromium

Point to a specific browser binary:

export ROD_BROWSER_BIN=/usr/bin/chromium-browser
md2pdf convert document.md
Common Errors
Error Cause Solution
"failed to connect to browser" Chrome not installed or sandbox issue Install Chrome or set ROD_NO_SANDBOX=1
"page load failed" Timeout on large document Use --timeout 2m or longer
Blank PDF Missing system libraries Install Chrome dependencies (see above)
"style not found" Invalid style name Use: default, technical, creative, academic, corporate, legal, invoice, manuscript
Fonts look different System fonts vary Use Docker image for consistent fonts
Platform Notes
  • macOS/Windows: Chrome is downloaded automatically. No special setup needed.
  • Linux: May require installing Chrome dependencies (see above).
  • Docker/CI: Always set ROD_NO_SANDBOX=1 and install dependencies, or use the official Docker image.

Known Limitations

Design philosophy: Professional PDF generation from Markdown. No LaTeX. No complexity.

By Design

These are intentional to keep the tool simple:

Not Supported Why Alternative
Raw HTML tags Security (prevents code execution during conversion) Cover config for logos, native markdown ![]() for images, custom CSS for styling
LaTeX/MathJax Adds complexity, requires external tools Pre-render as PNG/SVG
Wikilinks [[...]] Not relevant for PDF output Use [text](url)
Admonitions ::: Not implemented Use blockquotes
Chrome PDF Engine

Inherited from the browser's print-to-PDF:

  • No PDF/A archival format
  • No multi-column layouts
  • No per-page headers/footers
  • No mixed orientation in one document
  • System fonts only (not embedded)
Platform Notes
Issue Solution
Long code lines overflow Keep lines under ~80 chars
Fonts differ across systems Use Docker for consistency
Docker/CI fails Set ROD_NO_SANDBOX=1 (see Troubleshooting)

Contributing

See: CONTRIBUTING.md.

License

See: BSD-3-Clause.

Documentation

Overview

Package md2pdf converts Markdown documents to PDF using headless Chrome.

Quick Start

Create a converter, convert markdown, and close when done:

conv, err := md2pdf.NewConverter()
if err != nil {
    log.Fatal(err)
}
defer conv.Close()

result, err := conv.Convert(ctx, md2pdf.Input{
    Markdown: "# Hello\n\nWorld",
})
if err != nil {
    log.Fatal(err)
}
os.WriteFile("output.pdf", result.PDF, 0644)

The result contains both the PDF bytes (result.PDF) and the intermediate HTML (result.HTML) for debugging. Use Input.HTMLOnly to skip PDF generation.

Conversion Pipeline

The conversion process follows these stages:

  1. Markdown preprocessing (line normalization, ==highlight== syntax)
  2. Markdown to HTML conversion via Goldmark (GFM, syntax highlighting)
  3. HTML injection (CSS, cover page, TOC, signature block)
  4. PDF rendering via headless Chrome (go-rod)

Configuration

Use functional options to customize the converter:

conv, err := md2pdf.NewConverter(
    md2pdf.WithTimeout(2 * time.Minute),
    md2pdf.WithStyle("technical"),
    md2pdf.WithAssetPath("/path/to/custom/assets"),
)

Per-conversion options are passed via Input:

result, err := conv.Convert(ctx, md2pdf.Input{
    Markdown:  content,
    SourceDir: "/path/to/markdown",  // for relative image paths
    CSS:       "body { font-size: 14px; }",
    Page:      &md2pdf.PageSettings{Size: "a4"},
    Footer:    &md2pdf.Footer{ShowPageNumber: true},
    Cover:     &md2pdf.Cover{Title: "Report"},
    TOC:       &md2pdf.TOC{Title: "Contents"},
    Watermark: &md2pdf.Watermark{Text: "DRAFT"},
    Signature: &md2pdf.Signature{Name: "John Doe"},
})

Parallel Processing

For batch conversion, use ConverterPool to manage multiple browser instances:

pool := md2pdf.NewConverterPool(4)
defer pool.Close()

conv := pool.Acquire()
defer pool.Release(conv)
result, err := conv.Convert(ctx, input)

Custom Assets

Override built-in styles and templates using AssetLoader:

loader, err := md2pdf.NewAssetLoader("/path/to/assets")
conv, err := md2pdf.NewConverter(md2pdf.WithAssetLoader(loader))

Asset directory structure:

assets/
├── styles/
│   └── custom.css
└── templates/
    └── custom/
        ├── cover.html
        └── signature.html

Browser Requirements

PDF generation requires Chrome/Chromium. The go-rod library automatically downloads a managed Chromium instance on first run (~/.cache/rod/browser/).

For containers and CI environments, set ROD_NO_SANDBOX=1 to disable the Chrome sandbox. Use ROD_BROWSER_BIN to specify a custom Chrome binary.

Example

Example demonstrates basic markdown to HTML conversion. For PDF output, set HTMLOnly to false (requires Chrome).

conv, err := md2pdf.NewConverter()
if err != nil {
	fmt.Println("error:", err)
	return
}
defer conv.Close()

result, err := conv.Convert(context.Background(), md2pdf.Input{
	Markdown: "# Hello World\n\nThis is a test.",
	HTMLOnly: true, // Skip PDF generation for this example
})
if err != nil {
	fmt.Println("error:", err)
	return
}

// Check that HTML was generated
if strings.Contains(string(result.HTML), "<h1") {
	fmt.Println("HTML generated successfully")
}
Output:
HTML generated successfully
Example (WithCover)

Example_withCover demonstrates adding a cover page.

conv, err := md2pdf.NewConverter()
if err != nil {
	fmt.Println("error:", err)
	return
}
defer conv.Close()

result, err := conv.Convert(context.Background(), md2pdf.Input{
	Markdown: "# Introduction\n\nDocument content here.",
	Cover: &md2pdf.Cover{
		Title:        "Project Report",
		Subtitle:     "Q4 2025 Analysis",
		Author:       "John Doe",
		Organization: "Acme Corp",
		Date:         "2025-12-15",
		Version:      "v1.0",
	},
	HTMLOnly: true,
})
if err != nil {
	fmt.Println("error:", err)
	return
}

if strings.Contains(string(result.HTML), "Project Report") {
	fmt.Println("Cover page generated")
}
Output:
Cover page generated
Example (WithCustomCSS)

Example_withCustomCSS demonstrates injecting custom CSS.

conv, err := md2pdf.NewConverter()
if err != nil {
	fmt.Println("error:", err)
	return
}
defer conv.Close()

result, err := conv.Convert(context.Background(), md2pdf.Input{
	Markdown: "# Styled Document\n\nCustom styling applied.",
	CSS: `
			body { font-family: Georgia, serif; }
			h1 { color: #2c3e50; border-bottom: 2px solid #3498db; }
		`,
	HTMLOnly: true,
})
if err != nil {
	fmt.Println("error:", err)
	return
}

if strings.Contains(string(result.HTML), "Georgia") {
	fmt.Println("Custom CSS injected")
}
Output:
Custom CSS injected
Example (WithFooter)

Example_withFooter demonstrates adding a page footer.

conv, err := md2pdf.NewConverter()
if err != nil {
	fmt.Println("error:", err)
	return
}
defer conv.Close()

result, err := conv.Convert(context.Background(), md2pdf.Input{
	Markdown: "# Document with Footer\n\nContent here.",
	Footer: &md2pdf.Footer{
		Position:       "center",
		ShowPageNumber: true,
		Date:           "2025-01-15",
		Text:           "Confidential",
	},
	HTMLOnly: true,
})
if err != nil {
	fmt.Println("error:", err)
	return
}

if len(result.HTML) > 0 {
	fmt.Println("Footer configured")
}
Output:
Footer configured
Example (WithPageSettings)

Example_withPageSettings demonstrates configuring page settings.

conv, err := md2pdf.NewConverter()
if err != nil {
	fmt.Println("error:", err)
	return
}
defer conv.Close()

result, err := conv.Convert(context.Background(), md2pdf.Input{
	Markdown: "# A4 Document\n\nConfigured for A4 paper.",
	Page: &md2pdf.PageSettings{
		Size:        md2pdf.PageSizeA4,
		Orientation: md2pdf.OrientationPortrait,
		Margin:      1.0, // inches
	},
	HTMLOnly: true,
})
if err != nil {
	fmt.Println("error:", err)
	return
}

if len(result.HTML) > 0 {
	fmt.Println("Page settings configured")
}
Output:
Page settings configured
Example (WithSignature)

Example_withSignature demonstrates adding a signature block.

conv, err := md2pdf.NewConverter()
if err != nil {
	fmt.Println("error:", err)
	return
}
defer conv.Close()

result, err := conv.Convert(context.Background(), md2pdf.Input{
	Markdown: "# Report\n\nDocument content.",
	Signature: &md2pdf.Signature{
		Name:         "Jane Smith",
		Title:        "Senior Engineer",
		Email:        "jane@example.com",
		Organization: "Tech Corp",
	},
	HTMLOnly: true,
})
if err != nil {
	fmt.Println("error:", err)
	return
}

if strings.Contains(string(result.HTML), "Jane Smith") {
	fmt.Println("Signature block added")
}
Output:
Signature block added
Example (WithTOC)

Example_withTOC demonstrates adding a table of contents.

conv, err := md2pdf.NewConverter()
if err != nil {
	fmt.Println("error:", err)
	return
}
defer conv.Close()

markdown := `# Document Title

## Chapter 1

Content for chapter 1.

## Chapter 2

Content for chapter 2.

### Section 2.1

Subsection content.
`

result, err := conv.Convert(context.Background(), md2pdf.Input{
	Markdown: markdown,
	TOC: &md2pdf.TOC{
		Title:    "Contents",
		MinDepth: 2, // Start at h2 (skip document title)
		MaxDepth: 3, // Include up to h3
	},
	HTMLOnly: true,
})
if err != nil {
	fmt.Println("error:", err)
	return
}

if strings.Contains(string(result.HTML), "toc") {
	fmt.Println("TOC generated")
}
Output:
TOC generated
Example (WithWatermark)

Example_withWatermark demonstrates adding a watermark.

conv, err := md2pdf.NewConverter()
if err != nil {
	fmt.Println("error:", err)
	return
}
defer conv.Close()

result, err := conv.Convert(context.Background(), md2pdf.Input{
	Markdown: "# Draft Document\n\nThis is a draft.",
	Watermark: &md2pdf.Watermark{
		Text:    "DRAFT",
		Color:   "#888888",
		Opacity: 0.1,
		Angle:   -45,
	},
	HTMLOnly: true,
})
if err != nil {
	fmt.Println("error:", err)
	return
}

if strings.Contains(string(result.HTML), "DRAFT") {
	fmt.Println("Watermark CSS generated")
}
Output:
Watermark CSS generated

Index

Examples

Constants

View Source
const (
	// DefaultStyle is the name of the built-in CSS style.
	DefaultStyle = "default"

	// DefaultTemplateSet is the name of the built-in template set.
	DefaultTemplateSet = "default"
)

Asset name constants for built-in styles and templates.

View Source
const (
	// MinPoolSize ensures at least one worker is available.
	MinPoolSize = 1

	// MaxPoolSize caps browser instances to limit memory (~200MB each).
	MaxPoolSize = 8
)

Pool sizing constants.

View Source
const (
	PageSizeLetter = "letter"
	PageSizeA4     = "a4"
	PageSizeLegal  = "legal"
)

Page size constants.

View Source
const (
	OrientationPortrait  = "portrait"
	OrientationLandscape = "landscape"
)

Orientation constants.

View Source
const (
	MinMargin     = 0.25
	MaxMargin     = 3.0
	DefaultMargin = 0.5
)

Margin bounds in inches.

View Source
const (
	MinOrphans     = 1
	MaxOrphans     = 5
	DefaultOrphans = 2
	MinWidows      = 1
	MaxWidows      = 5
	DefaultWidows  = 2
)

Orphan/widow bounds for page break control.

View Source
const (
	MinWatermarkOpacity     = 0.0
	MaxWatermarkOpacity     = 1.0
	DefaultWatermarkOpacity = 0.1
	MinWatermarkAngle       = -90.0
	MaxWatermarkAngle       = 90.0
	DefaultWatermarkAngle   = -45.0
	DefaultWatermarkColor   = "#888888"
)

Watermark bounds.

View Source
const (
	DefaultTOCMinDepth = 2 // Skip H1 by default (document title)
	DefaultTOCMaxDepth = 3
)

TOC depth bounds.

Variables

View Source
var (
	ErrEmptyMarkdown   = errors.New("markdown content cannot be empty")
	ErrHTMLConversion  = errors.New("HTML conversion failed")
	ErrPDFGeneration   = errors.New("PDF generation failed")
	ErrBrowserConnect  = errors.New("failed to connect to browser")
	ErrPageCreate      = errors.New("failed to create browser page")
	ErrPageLoad        = errors.New("failed to load page")
	ErrSignatureRender = errors.New("signature template rendering failed")

	// Page settings validation errors.
	ErrInvalidPageSize    = errors.New("invalid page size")
	ErrInvalidOrientation = errors.New("invalid orientation")
	ErrInvalidMargin      = errors.New("invalid margin")

	// Footer validation errors.
	ErrInvalidFooterPosition = errors.New("invalid footer position")

	// Watermark validation errors.
	ErrInvalidWatermarkColor = errors.New("invalid watermark color")

	// Cover validation errors.
	ErrCoverLogoNotFound = errors.New("cover logo file not found")
	ErrCoverRender       = errors.New("cover template rendering failed")

	// Signature validation errors.
	ErrSignatureImageNotFound = errors.New("signature image file not found")

	// TOC validation errors.
	ErrInvalidTOCDepth = errors.New("invalid TOC depth")

	// Page breaks validation errors.
	ErrInvalidOrphans = errors.New("invalid orphans value")
	ErrInvalidWidows  = errors.New("invalid widows value")

	// Asset loading errors.
	ErrStyleNotFound         = errors.New("style not found")
	ErrTemplateSetNotFound   = errors.New("template set not found")
	ErrIncompleteTemplateSet = errors.New("template set missing required template")
	ErrInvalidAssetPath      = errors.New("invalid asset path")
)

Sentinel errors for library operations.

Functions

func ResolvePoolSize added in v0.7.0

func ResolvePoolSize(workers int) int

ResolvePoolSize determines the optimal pool size. Priority: explicit workers > GOMAXPROCS-based calculation. Exported for use by servers and CLIs.

Types

type AssetLoader added in v1.2.2

type AssetLoader interface {
	// LoadStyle loads a CSS style by name (without .css extension).
	// Returns ErrStyleNotFound if the style doesn't exist.
	LoadStyle(name string) (string, error)

	// LoadTemplateSet loads cover and signature templates by name.
	// Returns ErrTemplateSetNotFound if the template set doesn't exist.
	// Returns ErrIncompleteTemplateSet if required templates are missing.
	LoadTemplateSet(name string) (*TemplateSet, error)
}

AssetLoader defines the contract for loading CSS styles and HTML templates. Implementations may load from filesystem, embedded assets, S3, database, etc.

The library provides NewAssetLoader() for filesystem-based loading with fallback to embedded defaults. Implement this interface for custom backends.

func NewAssetLoader added in v1.2.2

func NewAssetLoader(basePath string) (AssetLoader, error)

NewAssetLoader creates an AssetLoader for the given base path. If basePath is empty, returns a loader using only embedded assets. If basePath is set, custom assets take precedence with fallback to embedded.

The basePath directory should contain:

  • styles/{name}.css for CSS styles
  • templates/{name}/cover.html and signature.html for template sets

Returns ErrInvalidAssetPath if basePath is set but not a valid, readable directory.

Example

ExampleNewAssetLoader demonstrates loading custom assets.

// NewAssetLoader with empty path uses embedded assets only
loader, err := md2pdf.NewAssetLoader("")
if err != nil {
	fmt.Println("error:", err)
	return
}

conv, err := md2pdf.NewConverter(md2pdf.WithAssetLoader(loader))
if err != nil {
	fmt.Println("error:", err)
	return
}
defer conv.Close()

result, err := conv.Convert(context.Background(), md2pdf.Input{
	Markdown: "# Custom Assets\n\nUsing asset loader.",
	HTMLOnly: true,
})
if err != nil {
	fmt.Println("error:", err)
	return
}

if len(result.HTML) > 0 {
	fmt.Println("Asset loader configured")
}
Output:
Asset loader configured

type ConvertResult added in v1.2.2

type ConvertResult struct {
	HTML []byte // Final HTML after all injections
	PDF  []byte // Generated PDF (empty if HTMLOnly)
}

ConvertResult holds both HTML and PDF output from conversion. HTML is always populated; PDF is empty when Input.HTMLOnly is true.

type Converter added in v1.5.0

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

Converter orchestrates the markdown-to-PDF conversion pipeline. Create with New() or NewConverter(), use Convert() for conversion, and Close() when done.

func New deprecated

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

New creates a Converter with default configuration. Use options to customize behavior (e.g., WithTimeout, WithAssetLoader, WithTemplateSet). Returns error if asset loading or template parsing fails.

Deprecated: Use NewConverter instead. New will be removed in v2.

func NewConverter added in v1.5.0

func NewConverter(opts ...Option) (*Converter, error)

NewConverter creates a Converter with default configuration. Use options to customize behavior (e.g., WithTimeout, WithAssetLoader, WithTemplateSet). Returns error if asset loading or template parsing fails.

Example (WithStyle)

ExampleNewConverter_withStyle demonstrates using a built-in style.

conv, err := md2pdf.NewConverter(md2pdf.WithStyle("technical"))
if err != nil {
	fmt.Println("error:", err)
	return
}
defer conv.Close()

result, err := conv.Convert(context.Background(), md2pdf.Input{
	Markdown: "# Technical Document\n\nUsing the technical style.",
	HTMLOnly: true,
})
if err != nil {
	fmt.Println("error:", err)
	return
}

// Technical style uses system-ui font
if strings.Contains(string(result.HTML), "system-ui") {
	fmt.Println("Technical style applied")
}
Output:
Technical style applied

func (*Converter) Close added in v1.5.0

func (c *Converter) Close() error

Close releases resources (headless Chrome browser).

func (*Converter) Convert added in v1.5.0

func (c *Converter) Convert(ctx context.Context, input Input) (result *ConvertResult, err error)

Convert runs the full pipeline and returns the result containing HTML and PDF. The context is used for cancellation and timeout. If input.HTMLOnly is true, PDF generation is skipped (for debugging). Recovers from internal panics to prevent crashes from propagating to callers.

type ConverterPool added in v1.5.0

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

ConverterPool manages a pool of Converter instances for parallel processing. Each converter has its own browser instance, enabling true parallelism. Converters are created lazily on first acquire to avoid startup delay.

Example

ExampleConverterPool demonstrates parallel batch processing.

pool := md2pdf.NewConverterPool(2)

// Process two documents in parallel
docs := []string{
	"# Document 1\n\nFirst document.",
	"# Document 2\n\nSecond document.",
}

// Channel to collect results, WaitGroup to synchronize goroutines
results := make(chan bool, len(docs))
var wg sync.WaitGroup

for _, doc := range docs {
	wg.Add(1)
	go func(markdown string) {
		defer wg.Done()

		conv := pool.Acquire()
		if conv == nil {
			results <- false
			return
		}
		defer pool.Release(conv)

		result, err := conv.Convert(context.Background(), md2pdf.Input{
			Markdown: markdown,
			HTMLOnly: true,
		})
		results <- err == nil && strings.Contains(string(result.HTML), "Document")
	}(doc)
}

// Wait for all goroutines to finish before closing pool
wg.Wait()
pool.Close()

// Collect results
success := 0
for range docs {
	if <-results {
		success++
	}
}
fmt.Printf("Processed %d documents\n", success)
Output:
Processed 2 documents

func NewConverterPool added in v1.5.0

func NewConverterPool(n int, opts ...Option) *ConverterPool

NewConverterPool creates a pool with capacity for n Converter instances. Converters are created lazily when acquired, not at pool creation. Options are applied to each converter when created.

func NewServicePool deprecated added in v0.7.0

func NewServicePool(n int, opts ...Option) *ConverterPool

NewServicePool creates a pool with capacity for n Converter instances.

Deprecated: Use NewConverterPool instead. NewServicePool will be removed in v2.

func (*ConverterPool) Acquire added in v1.5.0

func (p *ConverterPool) Acquire() *Converter

Acquire gets a converter from the pool, creating one if needed. Blocks if all converters are in use. Returns nil and sets internal error if converter creation fails. Use InitError() to check for initialization failures.

func (*ConverterPool) Close added in v1.5.0

func (p *ConverterPool) Close() error

Close releases all browser resources. Returns an aggregated error if multiple converters fail to close.

func (*ConverterPool) InitError added in v1.5.0

func (p *ConverterPool) InitError() error

InitError returns the first error encountered during converter creation. Returns nil if all converters were created successfully.

func (*ConverterPool) Release added in v1.5.0

func (p *ConverterPool) Release(conv *Converter)

Release returns a converter to the pool. The lock is released before sending to avoid deadlock when channel is full.

func (*ConverterPool) Size added in v1.5.0

func (p *ConverterPool) Size() int

Size returns the pool capacity.

type Cover added in v0.16.0

type Cover struct {
	Title        string // Document title (required)
	Subtitle     string // Optional subtitle
	Author       string // Author name (optional)
	AuthorTitle  string // Author's professional title (optional)
	Organization string // Organization name (optional)
	Date         string // Date string (optional)
	Version      string // Version string (optional)
	// Extended metadata fields
	ClientName   string // Client/customer name (optional)
	ProjectName  string // Project name (optional)
	DocumentType string // Document type, e.g., "Technical Specification" (optional)
	DocumentID   string // Document reference, e.g., "DOC-2024-001" (optional)
	Description  string // Brief document summary (optional)
	Department   string // Author's department (optional, shared with Signature via config)
}

Cover configures the cover page.

func (*Cover) Validate added in v0.16.0

func (c *Cover) Validate() error

Validate checks that cover settings are valid. Returns nil if c is nil (nil means no cover).

type Footer struct {
	Position       string // "left", "center", "right" (default: "right")
	ShowPageNumber bool
	Date           string
	Status         string
	Text           string
	DocumentID     string // Document reference number (optional)
}

Footer configures the PDF footer.

func (*Footer) Validate added in v0.13.0

func (f *Footer) Validate() error

Validate checks that footer settings are valid. Returns nil if f is nil (nil means no footer).

type Input

type Input struct {
	Markdown   string        // Markdown content (required)
	SourceDir  string        // Base directory for resolving relative paths (optional)
	CSS        string        // Custom CSS (optional)
	Footer     *Footer       // Footer config (optional)
	Signature  *Signature    // Signature config (optional)
	Page       *PageSettings // Page settings (optional, nil = defaults)
	Watermark  *Watermark    // Watermark config (optional)
	Cover      *Cover        // Cover page config (optional)
	TOC        *TOC          // Table of contents config (optional)
	PageBreaks *PageBreaks   // Page break config (optional)
	HTMLOnly   bool          // If true, skip PDF generation (for debugging)
}

Input contains conversion parameters.

type Link struct {
	Label string
	URL   string
}

Link represents a clickable link.

type Option

type Option func(*Converter)

Option configures a Converter.

func WithAssetLoader added in v1.2.2

func WithAssetLoader(loader AssetLoader) Option

WithAssetLoader sets a custom asset loader for CSS styles and HTML templates. Use NewAssetLoader(basePath) to load from a custom directory with fallback to embedded assets, or implement AssetLoader for custom backends.

Example:

loader, err := md2pdf.NewAssetLoader("/path/to/assets")
if err != nil {
    log.Fatal(err)
}
conv, err := md2pdf.NewConverter(md2pdf.WithAssetLoader(loader))

func WithAssetPath added in v1.2.2

func WithAssetPath(path string) Option

WithAssetPath configures asset loading from a filesystem directory. Custom assets take precedence; missing assets fall back to embedded defaults.

The directory should contain:

  • styles/{name}.css for CSS styles
  • templates/{name}/cover.html and signature.html for template sets

This is equivalent to calling NewAssetLoader(path) and WithAssetLoader(). Returns error from NewConverter() if the path is invalid.

func WithStyle added in v1.2.2

func WithStyle(style string) Option

WithStyle sets the CSS style for all conversions. Accepts:

  • Style name: "technical", "default", "corporate"
  • File path: "./custom.css", "/path/to/style.css"
  • CSS content: "body { font-size: 14px; }"

Detection: paths contain / or \, CSS content contains {, otherwise treated as a style name.

func WithTemplateSet added in v1.2.2

func WithTemplateSet(ts *TemplateSet) Option

WithTemplateSet sets a custom template set for cover and signature. Use this to override the default templates loaded from embedded assets.

Example:

ts := md2pdf.NewTemplateSet("custom", coverHTML, signatureHTML)
conv, err := md2pdf.NewConverter(md2pdf.WithTemplateSet(ts))

func WithTimeout

func WithTimeout(d time.Duration) Option

WithTimeout sets the conversion timeout. Panics if d <= 0 (programmer error, similar to time.NewTicker).

type PageBreaks added in v1.0.0

type PageBreaks struct {
	BeforeH1 bool // Page break before H1 headings (default: false)
	BeforeH2 bool // Page break before H2 headings (default: false)
	BeforeH3 bool // Page break before H3 headings (default: false)
	Orphans  int  // Min lines at page bottom (default: 2, range: 1-5)
	Widows   int  // Min lines at page top (default: 2, range: 1-5)
}

PageBreaks configures page break behavior for PDF output.

func (*PageBreaks) Validate added in v1.0.0

func (pb *PageBreaks) Validate() error

Validate checks that page break settings are valid. Returns nil if pb is nil (nil means use defaults).

type PageSettings added in v0.7.0

type PageSettings struct {
	Size        string  // "letter", "a4", "legal"
	Orientation string  // "portrait", "landscape"
	Margin      float64 // inches, applied to all sides
}

PageSettings configures PDF page dimensions.

func DefaultPageSettings added in v0.7.0

func DefaultPageSettings() *PageSettings

DefaultPageSettings returns page settings with default values.

func (*PageSettings) Validate added in v0.7.0

func (p *PageSettings) Validate() error

Validate checks that page settings are valid. Returns nil if p is nil (nil means use defaults). Empty values are allowed and will use defaults at runtime. Does not mutate - uses case-insensitive comparison.

type Service deprecated

type Service = Converter

Service is an alias for Converter for backward compatibility.

Deprecated: Use Converter instead. This alias will be removed in v2.

type ServicePool deprecated added in v0.7.0

type ServicePool = ConverterPool

ServicePool is an alias for ConverterPool for backward compatibility.

Deprecated: Use ConverterPool instead. This alias will be removed in v2.

type Signature

type Signature struct {
	Name         string
	Title        string
	Email        string
	Organization string
	ImagePath    string
	Links        []Link
	// Extended metadata fields
	Phone      string // Contact phone number (optional)
	Address    string // Postal address (optional, use YAML literal block for multiline)
	Department string // Department name (optional)
}

Signature configures the signature block.

func (*Signature) Validate added in v1.7.5

func (s *Signature) Validate() error

Validate checks that signature settings are valid. Returns nil if s is nil (nil means no signature).

Note: Only ImagePath is validated (file existence). Other fields like Email and Links are pure content that renders as-is - this is a PDF rendering tool, not a data validation tool. Users control their content.

type TOC added in v1.0.0

type TOC struct {
	Title    string // Title above TOC (empty = no title)
	MinDepth int    // 1-6, minimum heading level to include (default: 2, skips H1)
	MaxDepth int    // 1-6, maximum heading level to include (default: 3)
}

TOC configures the table of contents.

func (*TOC) Validate added in v1.0.0

func (t *TOC) Validate() error

Validate checks that TOC settings are valid. Returns nil if t is nil (nil means no TOC).

type TemplateSet added in v1.2.2

type TemplateSet struct {
	Name      string // Identifier (name or path)
	Cover     string // Cover page template HTML
	Signature string // Signature block template HTML
}

TemplateSet holds HTML templates for document generation. A template set contains cover and signature templates that work together.

func NewTemplateSet added in v1.2.2

func NewTemplateSet(name, cover, signature string) *TemplateSet

NewTemplateSet creates a TemplateSet from cover and signature HTML content. This is a convenience constructor for users providing templates directly.

type Watermark added in v0.15.1

type Watermark struct {
	Text    string  // Text to display (e.g., "DRAFT", "CONFIDENTIAL")
	Color   string  // Hex color (default: "#888888")
	Opacity float64 // 0.0 to 1.0 (default: 0.1)
	Angle   float64 // Rotation in degrees (default: -45)
}

Watermark configures a background text watermark.

func (*Watermark) Validate added in v0.15.1

func (w *Watermark) Validate() error

Validate checks that watermark settings are valid. Returns nil if w is nil (nil means no watermark).

Directories

Path Synopsis
cmd
md2pdf command
Package main provides the md2pdf CLI.
Package main provides the md2pdf CLI.
internal
assets
Package assets provides CSS styles and HTML templates for PDF generation.
Package assets provides CSS styles and HTML templates for PDF generation.
config
Package config centralizes parsing and validation so every entry point enforces the same safety and defaulting rules.
Package config centralizes parsing and validation so every entry point enforces the same safety and defaulting rules.
dateutil
Package dateutil provides date format parsing utilities.
Package dateutil provides date format parsing utilities.
fileutil
Package fileutil provides file and path utility functions.
Package fileutil provides file and path utility functions.
hints
Package hints provides actionable error hints for common failure scenarios.
Package hints provides actionable error hints for common failure scenarios.
pipeline
Package pipeline implements the Markdown-to-HTML conversion pipeline.
Package pipeline implements the Markdown-to-HTML conversion pipeline.
process
Package process contains process-control helpers used for robust shutdown.
Package process contains process-control helpers used for robust shutdown.
styleinput
Package styleinput classifies user-provided style values so callers can resolve style names, files, and inline CSS through one decision path.
Package styleinput classifies user-provided style values so callers can resolve style names, files, and inline CSS through one decision path.
yamlutil
Package yamlutil wraps YAML parsing to isolate the external dependency.
Package yamlutil wraps YAML parsing to isolate the external dependency.

Jump to

Keyboard shortcuts

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