A fast, themeable markdown-to-PDF converter built with Go. Convert your markdown files to beautiful PDFs with built-in and custom themes. Perfect for documentation, reports, and technical writing.
- 📄 Markdown to PDF - Fast, reliable conversion via Pandoc
- 🎨 Theme Support - 3 built-in themes (default, dark, academic) + unlimited custom themes
- ⚙️ Theme Management - List, add, and remove themes via CLI
- 📝 Custom Themes - Create themes in
~/.config/veve/themes/with YAML metadata - 🌐 Remote Images - Automatically download and embed remote images from HTTP/HTTPS URLs
- 🔀 Unix Composability - Full stdin/stdout support for piping and scripting
- 🛠️ Configuration - TOML-based config with XDG Base Directory support
- 🚀 Cross-Platform - macOS, Linux, Windows support
- 🔧 Pandoc Flexibility - Configurable PDF engines and Pandoc options
- 📦 Shell Completions - bash, zsh, and fish autocompletion support
- 🌍 Unicode & Emoji Support - Automatic PDF engine selection for unicode content including emoji, CJK characters, math symbols, and diacritics
brew tap madstone-tech/tap
brew install veveShell completions are automatically installed with Homebrew.
# AMD64
curl -sL https://github.com/madstone-tech/veve-cli/releases/latest/download/veve_Linux_x86_64.tar.gz | tar xz
sudo mv veve /usr/local/bin/
# ARM64
curl -sL https://github.com/madstone-tech/veve-cli/releases/latest/download/veve_Linux_arm64.tar.gz | tar xz
sudo mv veve /usr/local/bin/# Debian/Ubuntu (when available)
sudo apt install veve
# Fedora (when available)
sudo dnf install veveDownload the latest .zip file from releases, extract it, and add the folder to your PATH.
# Latest version
go install github.com/madstone-tech/veve-cli/cmd/veve@latest
# Specific version
go install github.com/madstone-tech/veve-cli/cmd/[email protected]# Convert markdown to PDF
veve input.md -o output.pdf
# Use default output name (input.pdf)
veve input.md# List all available themes
veve theme list
# Convert with a specific theme
veve input.md --theme dark -o output.pdf
veve input.md --theme academic -o output.pdf# Automatically download and embed remote images (enabled by default)
veve input.md -o output.pdf
# Explicit enable
veve input.md --enable-remote-images=true -o output.pdf
# Disable remote image downloading
veve input.md --enable-remote-images=false -o output.pdf
# Customize timeout and retries for unreliable networks
veve input.md \
--remote-images-timeout=30 \
--remote-images-max-retries=5 \
-o output.pdf
# Use custom temp directory for downloads
veve input.md \
--remote-images-temp-dir=/mnt/fast-storage \
-o output.pdfveve automatically detects and renders unicode content including emoji, CJK characters, mathematical symbols, and diacritics. The tool selects an appropriate PDF engine based on system availability.
- Emoji: 🎉 📄 ✅ 🚀 ❤️ (standard emoji sets and ZWJ sequences like 👨💻)
- CJK Characters: 世界 日本 中国 (Chinese, Japanese, Korean)
- Mathematical Symbols: ∑ ± ∫ ∈ ∪ ∩ (and others)
- Diacritics: Café naïve Zürich (combining marks and accents)
- Special Characters: © ® ™ € £ ¥ § ¶ † ‡
# Automatic engine selection for unicode content
veve unicode-document.md -o output.pdf
# Content with emoji 🎉, CJK 世界, math ∑, diacritics é automatically rendered# Use specific unicode-capable engine
veve document.md --engine xelatex -o output.pdf
veve document.md --engine weasyprint -o output.pdf
# Available engines: xelatex, lualatex, weasyprint, princeveve requires a unicode-capable PDF engine. Engines are tested in this order:
- xelatex (recommended) - Fast, widely available, excellent unicode support
- lualatex - Similar capabilities to xelatex
- weasyprint - Python-based, good unicode support
- prince - Commercial option with premium support
macOS:
# Install mactex (includes xelatex)
brew install mactexUbuntu/Debian:
# Option 1: xelatex
sudo apt-get update
sudo apt-get install texlive-xetex
# Option 2: weasyprint
sudo apt-get install weasyprintFedora/RHEL:
# Option 1: xelatex
sudo dnf install texlive-xetex
# Option 2: weasyprint
sudo dnf install weasyprintWindows:
- Download MiKTeX and select xelatex during installation
- Or install weasyprint via pip:
pip install weasyprint
Example Markdown:
# My Document
Here's a remote image from a CDN:

And another from an external source:

Local images still work too:
Features:
- 📥 Automatic download and embedding of HTTP/HTTPS image URLs
- ⚡ Concurrent downloads (5 images at a time by default)
- 🔄 Automatic retry with exponential backoff for transient failures
- 💾 Disk space limits (500MB per session, 100MB per image)
- 🧹 Automatic cleanup of temporary files
- ✅ Graceful degradation if some images fail to download
- 📊 Detailed error messages for troubleshooting
# Create custom theme directory
mkdir -p ~/.config/veve/themes
# Create a custom theme file
cat > ~/.config/veve/themes/mygreen.css << 'EOF'
---
name: mygreen
author: Your Name
description: My green theme
version: 1.0.0
---
body {
font-family: Georgia, serif;
color: darkgreen;
}
h1 {
color: forestgreen;
border-bottom: 2px solid darkgreen;
}
EOF
# Use your custom theme
veve input.md --theme mygreen -o output.pdf# List all themes (built-in + custom)
veve theme list
# Install a theme from file
veve theme add mytheme /path/to/mytheme.css
# Install a theme from URL
veve theme add mytheme https://example.com/themes/mytheme.css
# Remove a custom theme
veve theme remove mytheme# Convert all markdown files in current directory
for file in *.md; do
veve "$file" -o "${file%.md}.pdf"
done
# With specific theme
for file in *.md; do
veve "$file" --theme dark -o "${file%.md}.pdf"
done# Convert from stdin to stdout
cat input.md | veve - -o output.pdf
# Pipe to other commands
veve input.md -o - | curl -F "file=@-" https://api.example.com/upload
# Integration with other tools
pandoc-generated-md | veve - -o output.pdfveve uses TOML for configuration. Config files are loaded from:
~/.config/veve/veve.toml(XDG Base Directory)- Environment variables (override config file)
# ~/.config/veve/veve.toml
# Default theme to use if not specified
default_theme = "dark"
# Default PDF engine
pdf_engine = "pdflatex"
# Quiet mode (suppress non-error output)
quiet = false
# Verbose mode (detailed output)
verbose = false# Override configuration via environment
export VEVE_DEFAULT_THEME="dark"
export VEVE_PDF_ENGINE="xelatex"
export VEVE_QUIET="false"
export VEVE_VERBOSE="true"veve [input] [flags]Core Flags:
-o, --output string- Output PDF file path (default: input filename with .pdf extension)-t, --theme string- Theme to use for PDF styling (default: "default")-e, --pdf-engine string- Pandoc PDF engine to use (default: "pdflatex")--quiet- Suppress non-error output--verbose- Enable verbose output-h, --help- Show help message-v, --version- Show version
Remote Images Flags:
-r, --enable-remote-images- Download and embed remote images (default: true)--remote-images-timeout int- Timeout in seconds per image download (default: 10)--remote-images-max-retries int- Maximum retry attempts for failed downloads (default: 3)--remote-images-temp-dir string- Custom temporary directory for downloads (default: system temp)
# List themes
veve theme list
# Add theme from file or URL
veve theme add <name> <path/url>
# Remove theme
veve theme remove <name>
veve theme remove <name> --force # Skip confirmationveve provides shell completion for bash, zsh, and fish shells. Completions include support for:
- Commands:
convert,theme,completion - Flags:
--engine,--theme,--output, etc. - Engine names:
xelatex,lualatex,weasyprint,prince
The easiest way to install completions:
# Auto-detect your shell and install
./scripts/install-completion.sh
# Or specify the shell explicitly
./scripts/install-completion.sh bash
./scripts/install-completion.sh zsh
./scripts/install-completion.sh fishIf you prefer to install manually or the script doesn't work for you:
Bash:
# Generate and save completion
veve completion bash > ~/.bash_completions/veve
# Add to ~/.bashrc or ~/.bash_profile
source ~/.bash_completions/veveZsh:
# Create zsh completion directory
mkdir -p ~/.zsh/completions
# Generate completion
veve completion zsh > ~/.zsh/completions/_veve
# Add to ~/.zshrc
fpath=(~/.zsh/completions $fpath)
autoload -U compinit && compinitFish:
# Create fish completions directory
mkdir -p ~/.config/fish/completions
# Generate completion
veve completion fish > ~/.config/fish/completions/veve.fishAfter installation, reload your shell configuration:
# Bash/Fish
source ~/.bashrc
source ~/.config/fish/config.fish
# Zsh
source ~/.zshrcThen test by typing:
veve conver<TAB> # Should complete to "convert"
veve convert --engine <TAB> # Should show available enginesCreate custom themes with CSS styling. See THEME_DEVELOPMENT.md for detailed guide.
---
name: mytheme
author: Your Name
description: A custom theme
version: 1.0.0
---
/* CSS styling */
body {
font-family: Georgia, serif;
color: #333;
}
h1 {
color: #006699;
border-bottom: 3px solid #006699;
}
code {
background-color: #f5f5f5;
padding: 2px 4px;
border-radius: 3px;
}- Built-in themes: Embedded in binary
- User themes:
~/.config/veve/themes/*.css - Local themes: Any path via
--theme /path/to/theme.css
# Generate PDF from markdown documentation
veve docs/guide.md -o guide.pdf --theme academic
# Batch convert documentation
for doc in docs/*.md; do
veve "$doc" --theme academic -o "${doc%.md}.pdf"
done# Hugo integration in build script
for md in content/**/*.md; do
veve "$md" -o "static/${md%.md}.pdf"
done
# Gatsby integration
npm run build && veve content/blog/*.md -o public/pdfs/# GitHub Actions example
- name: Generate PDFs
run: |
for file in docs/*.md; do
veve "$file" --theme default -o "build/${file%.md}.pdf"
done
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
path: build/*.pdfRemote images are enabled by default. Just use remote image URLs in your markdown:

When you run veve convert document.md:
- Images are automatically downloaded
- Downloaded files are cached in temp directory
- Markdown is rewritten with local paths
- PDF is generated with embedded images
- Temp files are cleaned up
For Fast Networks:
veve document.md # Defaults are optimal for typical networksFor Slow Networks:
veve document.md \
--remote-images-timeout=30 \
--remote-images-max-retries=5For Large Image Batches:
veve document.md \
--remote-images-temp-dir=/mnt/fast-ssd # Use faster storage| Problem | Solution |
|---|---|
| Images not downloading | Feature is enabled by default. Check that URLs are correct |
| Timeout errors | Increase timeout: --remote-images-timeout=30 |
| Rate limit errors (429) | Automatic retries handle this. Check image source |
| 404 errors | Verify image URLs in markdown are correct |
| Disk space exceeded | Reduce document size or split into multiple conversions |
| Cleanup warnings | Use custom temp dir: --remote-images-temp-dir=./temp |
For more details, see specs/002-remote-images/quickstart.md.
[ERROR] pandoc: Pandoc is required but not installed or not in PATH
Solution: Install Pandoc:
# macOS
brew install pandoc
# Linux (Debian/Ubuntu)
sudo apt-get install pandoc
# Linux (Fedora)
sudo dnf install pandoc
# Windows (Chocolatey)
choco install pandoc[ERROR] invalid theme 'mytheme': available themes are: [default dark academic]
Solution:
- Check theme file exists:
ls ~/.config/veve/themes/mytheme.css - Use correct theme name (without .css extension)
- Use full path for local themes:
veve input.md --theme /path/to/mytheme.css
Solution: Ensure your markdown file is UTF-8 encoded:
file -i input.md # Check encoding
iconv -f utf-16 -t utf-8 input.md > input_utf8.md # Convert if neededSolution:
- Use faster PDF engine:
--pdf-engine xelatex(faster than pdflatex) - Check Pandoc performance:
pandoc --version - Simplify CSS in theme (reduce complexity)
- Go 1.25 or later
- Pandoc 2.18 or later
- Git
# Clone repository
git clone https://github.com/madstone-tech/veve-cli.git
cd veve-cli
# Build
go build -o veve ./cmd/veve
# Install to system
sudo mv veve /usr/local/bin/
# Verify
veve --versionThis project uses Task to streamline common development workflows. Install Task if you haven't already:
# macOS
brew install go-task/tap/go-task
# Linux
sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d
# Or via Go
go install github.com/go-task/task/v3/cmd/task@latestThen use Task for common operations:
# Setup development environment
task dev-setup
# Run tests
task test # All tests with race detector
task test-unit # Unit tests only
task test-contract # Contract tests only
task test-coverage # Tests with coverage report
# Code quality
task fmt # Format with gofmt
task lint # Run linter
task precommit # Run all pre-commit checks
# Build and install
task build # Build binary
task install # Build and install to /usr/local/bin
task uninstall # Remove installation
# See all available tasks
task --list-allFor detailed information, see TASKFILE_GUIDE.md.
Traditional Go commands still work:
# Run tests directly
go test ./...
# Run with coverage
go test -cover ./...
# Generate completions
./scripts/generate-completions.sh
# Install completions
./scripts/install-completion.shContributions welcome! Please see CONTRIBUTING.md for guidelines.
# Clone and setup
git clone https://github.com/madstone-tech/veve-cli.git
cd veve-cli
# Install dependencies
go mod download
# Run tests
go test -v ./...
# Run linter
golangci-lint run ./...See docs/RELEASE.md for detailed release instructions.
Quick version:
# Tag a release
git tag -a v0.2.0 -m "Release v0.2.0"
git push origin v0.2.0
# GitHub Actions automatically builds and releases- Remote Images Guide - Automatic image downloading and embedding
- Theme Development Guide - Create custom themes
- Integration Examples - Use veve in your workflow
- Release Guide - Create releases
- Contributing Guide - Development guidelines
Typical conversion times (Pandoc + PDF generation):
| Task | Time |
|---|---|
| Simple document (< 10 pages) | < 2 seconds |
| Complex document (< 50 pages) | 2-5 seconds |
| Large document (> 50 pages) | 5-10 seconds |
Times vary based on Pandoc, PDF engine, and system performance
When remote images are included:
| Scenario | Time |
|---|---|
| Single image (2MB) | ~2 seconds |
| 5 images (10MB total) | ~2-3 seconds |
| 20 images (40MB total) | ~10 seconds |
With 5 concurrent downloads (default). Times depend on network speed and image source responsiveness.
Performance Tips:
- Concurrent downloads (5 workers) provide ~2.5x speedup vs sequential
- Images are cached during conversion (no re-downloads for duplicates)
- Slow images don't block others (concurrent downloads continue)
- Timeouts prevent hanging on unresponsive sources (default 10s, configurable)
- ✅ macOS (10.15+)
- ✅ Linux (Ubuntu 18.04+, Fedora 30+, etc.)
- ✅ Windows (10+)
- ✅ Go 1.20+
- ✅ Go 1.21+
- ✅ Pandoc 2.18+
- ✅ Pandoc 2.19+
- ✅ Pandoc 3.x+
MIT License - see LICENSE for details
- Built with Cobra for CLI
- Configuration with Viper
- Conversion powered by Pandoc
- Inspiration from Marked 2
- Issues: GitHub Issues
- Discussions: GitHub Discussions
- Documentation: See
/docsdirectory
- ✨ New Feature: Automatic remote image downloading and embedding
- Downloads HTTP/HTTPS images during conversion
- Concurrent downloads (5 workers default)
- Retry logic with exponential backoff
- Disk space limits (500MB per session, 100MB per image)
- Detailed error messages and logging
- Graceful degradation on network failures
- 🔄 Improved error handling and reporting
- 📚 Enhanced documentation and examples
- Initial release
- Markdown to PDF conversion
- Theme support (built-in and custom)
- Theme management CLI
- Configuration support
- Web UI for theme preview
- Built-in theme marketplace
- Docker image with Pandoc
- Package managers (apt, rpm, brew, etc.)
- Template variables (author, date, etc.)
- PDF merge capability
- Batch image download progress indicator
- Image caching across multiple conversions
Transform your documentation with veve-cli 📄✨