2026-05-20 21:39:12 +08:00

785 lines
30 KiB
Bash
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
# make.sh — minimax-pdf unified CLI
# Usage: bash make.sh <command> [options]
#
# Commands:
# check Verify all dependencies
# fix Auto-install missing dependencies
# run --title T --type TYPE Full pipeline → output.pdf
# --out FILE Output path (default: output.pdf)
# --author A --date D
# --subtitle S
# --abstract A Optional abstract text for cover
# --cover-image URL Optional cover image URL/path
# --content FILE Path to content.json (optional)
# demo Build a full-featured demo to demo.pdf
#
# Document types:
# report proposal resume portfolio academic general
# minimal stripe diagonal frame editorial
# magazine darkroom terminal poster
#
# Content block types:
# h1 h2 h3 body bullet numbered callout table
# image figure code math chart flowchart bibliography
# divider caption pagebreak spacer
#
# Exit codes: 0 success, 1 usage error, 2 dep missing, 3 runtime error
set -euo pipefail
SCRIPTS="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
NODE="node"
# ── Auto-detect Python (cross-platform) ────────────────────────────────────────
detect_python() {
local candidate
# 1. PATH 中的 python3 / python排除 Windows Store 空壳)
for candidate in python3 python; do
if command -v "$candidate" &>/dev/null \
&& "$candidate" -c "import sys; assert sys.version_info >= (3,9)" 2>/dev/null; then
echo "$candidate"; return
fi
done
# 2. Windows 常见安装路径(官方安装器 / Chocolatey / Scoop / Anaconda / Miniconda
local win_candidates=()
# 官方安装器 — 当前用户
for d in "/c/Users/$USER/AppData/Local/Programs/Python"/Python3*/; do
[[ -x "${d}python.exe" ]] && win_candidates+=("${d}python.exe")
done
# 官方安装器 — 全局
for d in "/c/Python3"*/ "/c/Program Files/Python3"*/; do
[[ -x "${d}python.exe" ]] && win_candidates+=("${d}python.exe")
done
# Chocolatey
[[ -x "/c/ProgramData/chocolatey/bin/python.exe" ]] && \
win_candidates+=("/c/ProgramData/chocolatey/bin/python.exe")
# Scoop
[[ -x "/c/Users/$USER/scoop/apps/python/current/python.exe" ]] && \
win_candidates+=("/c/Users/$USER/scoop/apps/python/current/python.exe")
# Anaconda / Miniconda
for d in "/c/Users/$USER/anaconda3" "/c/Users/$USER/miniconda3" \
"/c/ProgramData/anaconda3" "/c/ProgramData/miniconda3"; do
[[ -x "$d/python.exe" ]] && win_candidates+=("$d/python.exe")
done
for candidate in "${win_candidates[@]}"; do
if "$candidate" -c "import sys; assert sys.version_info >= (3,9)" 2>/dev/null; then
echo "$candidate"; return
fi
done
# 3. macOS Homebrew
for candidate in /opt/homebrew/bin/python3 /usr/local/bin/python3; do
if [[ -x "$candidate" ]] \
&& "$candidate" -c "import sys; assert sys.version_info >= (3,9)" 2>/dev/null; then
echo "$candidate"; return
fi
done
# 未找到 — 返回空字符串,由调用方处理
echo ""
}
PY="$(detect_python)"
# ── Interactive prompt helper ──────────────────────────────────────────────────
# 询问用户选择:自动安装 or 手动安装
# Usage: ask_install "Python" → sets REPLY to "auto" or "manual" or "skip"
ask_install() {
local dep_name="$1"
echo ""
bold " $dep_name is not installed. What would you like to do?"
echo " [1] Auto-install (let this script install it for you)"
echo " [2] Manual install (show me the instructions, I'll do it myself)"
echo " [3] Skip (continue without it)"
echo ""
read -rp " Choose [1/2/3]: " choice
case "$choice" in
1) REPLY="auto" ;;
2) REPLY="manual" ;;
*) REPLY="skip" ;;
esac
}
# ── Auto-install helpers ───────────────────────────────────────────────────────
install_python_auto() {
bold " Attempting to install Python..."
if command -v winget &>/dev/null; then
echo " Using: winget install Python.Python.3.12"
winget install Python.Python.3.12 --accept-package-agreements --accept-source-agreements 2>&1 | tail -5
elif command -v choco &>/dev/null; then
echo " Using: choco install python3"
choco install python3 -y 2>&1 | tail -5
elif command -v scoop &>/dev/null; then
echo " Using: scoop install python"
scoop install python 2>&1 | tail -5
elif command -v brew &>/dev/null; then
echo " Using: brew install python3"
brew install python3 2>&1 | tail -5
elif command -v apt-get &>/dev/null; then
echo " Using: sudo apt-get install python3 python3-pip"
sudo apt-get install -y python3 python3-pip 2>&1 | tail -5
elif command -v dnf &>/dev/null; then
echo " Using: sudo dnf install python3 python3-pip"
sudo dnf install -y python3 python3-pip 2>&1 | tail -5
else
red " No supported package manager found. Please install manually."
show_python_manual_instructions
return 1
fi
echo ""
green " Python installed. Please restart your terminal and re-run this script."
echo " (New PATH entries require a terminal restart to take effect)"
return 0
}
show_python_manual_instructions() {
echo ""
bold " Install Python 3.9+ manually:"
echo " Windows: winget install Python.Python.3.12"
echo " or https://www.python.org/downloads/"
echo " (IMPORTANT: check 'Add Python to PATH' during install)"
echo " macOS: brew install python3"
echo " Linux: sudo apt install python3 python3-pip (Debian/Ubuntu)"
echo " sudo dnf install python3 python3-pip (Fedora)"
echo ""
}
install_node_auto() {
bold " Attempting to install Node.js..."
if command -v winget &>/dev/null; then
echo " Using: winget install OpenJS.NodeJS.LTS"
winget install OpenJS.NodeJS.LTS --accept-package-agreements --accept-source-agreements 2>&1 | tail -5
elif command -v choco &>/dev/null; then
echo " Using: choco install nodejs-lts"
choco install nodejs-lts -y 2>&1 | tail -5
elif command -v scoop &>/dev/null; then
echo " Using: scoop install nodejs-lts"
scoop install nodejs-lts 2>&1 | tail -5
elif command -v brew &>/dev/null; then
echo " Using: brew install node"
brew install node 2>&1 | tail -5
elif command -v apt-get &>/dev/null; then
echo " Using: sudo apt-get install nodejs npm"
sudo apt-get install -y nodejs npm 2>&1 | tail -5
else
red " No supported package manager found. Please install manually."
show_node_manual_instructions
return 1
fi
echo ""
green " Node.js installed. Please restart your terminal and re-run this script."
return 0
}
show_node_manual_instructions() {
echo ""
bold " Install Node.js 18+ manually:"
echo " Windows: winget install OpenJS.NodeJS.LTS"
echo " or https://nodejs.org/en/download/"
echo " macOS: brew install node"
echo " Linux: sudo apt install nodejs npm (Debian/Ubuntu)"
echo " or https://nodejs.org/en/download/"
echo ""
}
# ── Colour helpers ─────────────────────────────────────────────────────────────
red() { printf '\033[0;31m%s\033[0m\n' "$*"; }
green() { printf '\033[0;32m%s\033[0m\n' "$*"; }
yellow() { printf '\033[0;33m%s\033[0m\n' "$*"; }
bold() { printf '\033[1m%s\033[0m\n' "$*"; }
# ── check ──────────────────────────────────────────────────────────────────────
cmd_check() {
local ok=true
bold "Checking dependencies..."
# Python
if [[ -n "$PY" ]] && $PY -c "import sys" 2>/dev/null; then
green " ✓ python ($PY) $($PY --version 2>&1 | awk '{print $2}')"
else
red " ✗ Python 3.9+ not found"
ask_install "Python"
case "$REPLY" in
auto) install_python_auto; exit $? ;;
manual) show_python_manual_instructions; exit 2 ;;
*) yellow " Skipped Python — PDF generation will not work without it." ;;
esac
ok=false
fi
# reportlab
if [[ -n "$PY" ]] && $PY -c "import reportlab" 2>/dev/null; then
green " ✓ reportlab"
else
yellow " ⚠ reportlab not installed (run: make.sh fix)"
ok=false
fi
# pypdf
if [[ -n "$PY" ]] && $PY -c "import pypdf" 2>/dev/null; then
green " ✓ pypdf"
else
yellow " ⚠ pypdf not installed (run: make.sh fix)"
ok=false
fi
# Node.js
if command -v node &>/dev/null; then
green " ✓ node $(node --version)"
else
red " ✗ Node.js not found"
ask_install "Node.js"
case "$REPLY" in
auto) install_node_auto; exit $? ;;
manual) show_node_manual_instructions; exit 2 ;;
*) yellow " Skipped Node.js — cover rendering will not work without it." ;;
esac
ok=false
fi
# Playwright + Browser (prefer local Chrome/Edge)
local browser_found=false
if node -e "
const pw = (() => { try { return require('playwright'); } catch(_) {
const {execSync} = require('child_process');
try { return require(execSync('npm root -g').toString().trim()+'/playwright'); } catch(_) { return null; }
}})();
if (!pw) process.exit(1);
(async () => {
for (const ch of ['chrome','msedge','chromium']) {
try { const b = await pw.chromium.launch({channel:ch}); await b.close(); console.log(ch); process.exit(0); } catch(_) {}
}
try { const b = await pw.chromium.launch(); await b.close(); console.log('bundled-chromium'); process.exit(0); } catch(_) {}
process.exit(1);
})();
" 2>/dev/null; then
green " ✓ playwright + browser detected"
browser_found=true
fi
if ! $browser_found; then
yellow " ⚠ playwright or browser not found (run: make.sh fix)"
ok=false
fi
# matplotlib (optional)
if [[ -n "$PY" ]] && $PY -c "import matplotlib" 2>/dev/null; then
green " ✓ matplotlib (math, chart, flowchart blocks enabled)"
else
yellow " ⚠ matplotlib not installed — math/chart/flowchart blocks degrade to text (run: make.sh fix)"
fi
if $ok; then
green "\nAll dependencies satisfied."
exit 0
else
yellow "\nSome dependencies missing. Run: bash make.sh fix"
exit 2
fi
}
# ── fix ────────────────────────────────────────────────────────────────────────
cmd_fix() {
bold "Installing missing dependencies..."
local rc=0
# Python
if [[ -n "$PY" ]] && $PY -c "import sys" 2>/dev/null; then
green " ✓ Python found ($PY)"
else
red " ✗ Python 3.9+ not found"
ask_install "Python"
case "$REPLY" in
auto)
install_python_auto || rc=2
if [[ $rc -ne 0 ]]; then
yellow " Auto-install may have failed. Please restart terminal and re-run."
exit $rc
fi
# Re-detect after install
PY="$(detect_python)"
if [[ -z "$PY" ]]; then
yellow " Python still not detected. Please restart terminal and re-run: bash make.sh fix"
exit 2
fi
;;
manual)
show_python_manual_instructions
echo " After installing Python, re-run: bash make.sh fix"
exit 2
;;
*)
yellow " Skipped Python — cannot install Python packages."
rc=2
;;
esac
fi
# Python packages
if [[ -n "$PY" ]] && $PY -c "import sys" 2>/dev/null; then
$PY -m pip install --break-system-packages -q reportlab pypdf matplotlib 2>/dev/null \
|| $PY -m pip install -q reportlab pypdf matplotlib 2>/dev/null \
|| { yellow " pip install failed — try: $PY -m pip install reportlab pypdf matplotlib"; rc=3; }
green " ✓ Python packages installed (reportlab, pypdf, matplotlib)"
fi
# Node.js
if ! command -v node &>/dev/null; then
red " ✗ Node.js not found"
ask_install "Node.js"
case "$REPLY" in
auto)
install_node_auto || rc=2
if [[ $rc -ne 0 ]]; then
yellow " Auto-install may have failed. Please restart terminal and re-run."
exit $rc
fi
;;
manual)
show_node_manual_instructions
echo " After installing Node.js, re-run: bash make.sh fix"
exit 2
;;
*)
yellow " Skipped Node.js — cover rendering will not work."
rc=2
;;
esac
fi
# Playwright + Browser: prefer local Chrome/Edge, only download Chromium as last resort
if command -v node &>/dev/null && command -v npm &>/dev/null; then
# Ensure playwright is installed
if ! node -e "require('playwright')" 2>/dev/null && \
! node -e "require(require('child_process').execSync('npm root -g').toString().trim()+'/playwright')" 2>/dev/null; then
npm install -g playwright --silent 2>/dev/null || { yellow " playwright npm install failed"; rc=3; }
fi
# Check if local Chrome or Edge is available — skip Chromium download if so
local has_local_browser=false
for ch in chrome msedge; do
if node -e "
const pw = (() => { try { return require('playwright'); } catch(_) {
try { return require(require('child_process').execSync('npm root -g').toString().trim()+'/playwright'); } catch(_) { return null; }
}})();
if (!pw) process.exit(1);
pw.chromium.launch({channel:'$ch'}).then(b => b.close().then(() => process.exit(0))).catch(() => process.exit(1));
" 2>/dev/null; then
green " ✓ Local browser detected ($ch) — skipping Chromium download"
has_local_browser=true
break
fi
done
if ! $has_local_browser; then
yellow " No local Chrome/Edge found — downloading Chromium..."
npx playwright install chromium --silent 2>/dev/null && \
green " ✓ Playwright Chromium installed" || \
{ yellow " Chromium install failed — install Chrome or Edge manually"; rc=3; }
fi
else
yellow " npm not found — cannot install Playwright automatically"
rc=2
fi
if [[ $rc -eq 0 ]]; then
green "\nAll dependencies installed. Run: bash make.sh check"
fi
exit $rc
}
# ── run ────────────────────────────────────────────────────────────────────────
cmd_run() {
# Pre-flight: ensure critical deps exist before doing any work
if [[ -z "$PY" ]] || ! $PY -c "import sys" 2>/dev/null; then
red "Error: Python 3.9+ is required but not found."
echo "Run: bash make.sh check (to diagnose and install)"
exit 2
fi
if ! command -v node &>/dev/null; then
red "Error: Node.js is required but not found."
echo "Run: bash make.sh check (to diagnose and install)"
exit 2
fi
local title="Untitled Document"
local type="general"
local author=""
local date=""
local subtitle=""
local abstract=""
local cover_image=""
local accent=""
local cover_bg=""
local content_file=""
local out="output.pdf"
local workdir
workdir="$(mktemp -d)"
# Parse options
while [[ $# -gt 0 ]]; do
case "$1" in
--title) title="$2"; shift 2 ;;
--type) type="$2"; shift 2 ;;
--author) author="$2"; shift 2 ;;
--date) date="$2"; shift 2 ;;
--subtitle) subtitle="$2"; shift 2 ;;
--abstract) abstract="$2"; shift 2 ;;
--cover-image) cover_image="$2"; shift 2 ;;
--accent) accent="$2"; shift 2 ;;
--cover-bg) cover_bg="$2"; shift 2 ;;
--content) content_file="$2"; shift 2 ;;
--out) out="$2"; shift 2 ;;
*) echo "Unknown option: $1"; exit 1 ;;
esac
done
bold "Building: $title"
echo " Type : $type"
echo " Output : $out"
# Step 1: tokens
echo ""
bold "Step 1/4 Generating design tokens..."
local accent_args=()
[[ -n "$accent" ]] && accent_args+=(--accent "$accent")
[[ -n "$cover_bg" ]] && accent_args+=(--cover-bg "$cover_bg")
$PY "$SCRIPTS/palette.py" \
--title "$title" --type "$type" \
--author "$author" --date "$date" \
--out "$workdir/tokens.json" \
"${accent_args[@]+"${accent_args[@]}"}"
# Inject optional cover fields into tokens.json
if [[ -n "$abstract" || -n "$cover_image" ]]; then
PDF_ABSTRACT="$abstract" PDF_COVER_IMAGE="$cover_image" PDF_TOKENS="$workdir/tokens.json" \
$PY - <<'PYEOF'
import json, os
with open(os.environ["PDF_TOKENS"]) as f:
t = json.load(f)
abstract = os.environ.get("PDF_ABSTRACT", "")
cover_image = os.environ.get("PDF_COVER_IMAGE", "")
if abstract:
t["abstract"] = abstract
if cover_image:
t["cover_image"] = cover_image
with open(os.environ["PDF_TOKENS"], "w") as f:
json.dump(t, f, indent=2)
PYEOF
fi
cat "$workdir/tokens.json" | $PY -c "
import json,sys
t=json.load(sys.stdin)
print(f' Mood : {t[\"mood\"]}')
print(f' Pattern : {t[\"cover_pattern\"]}')
print(f' Fonts : {t[\"font_display\"]} / {t[\"font_body\"]}')"
# Step 2: cover HTML + render
echo ""
bold "Step 2/4 Rendering cover..."
local subtitle_args=()
[[ -n "$subtitle" ]] && subtitle_args=(--subtitle "$subtitle")
$PY "$SCRIPTS/cover.py" \
--tokens "$workdir/tokens.json" \
--out "$workdir/cover.html" \
"${subtitle_args[@]+"${subtitle_args[@]}"}"
$NODE "$SCRIPTS/render_cover.js" \
--input "$workdir/cover.html" \
--out "$workdir/cover.pdf"
green " ✓ Cover rendered"
# Step 3: body
echo ""
bold "Step 3/4 Rendering body pages..."
if [[ -z "$content_file" ]]; then
# Generate a minimal placeholder body
cat > "$workdir/content.json" <<'JSON'
[
{"type":"h1", "text":"Document Body"},
{"type":"body", "text":"Replace this with your content.json file using --content path/to/content.json"},
{"type":"body", "text":"See the content.json schema in the skill README for the full list of supported block types: h1, h2, h3, body, bullet, callout, table, pagebreak, spacer."}
]
JSON
content_file="$workdir/content.json"
yellow " No content file provided — using placeholder body."
fi
# Validate content.json before rendering
if ! $PY -c "
import json, sys
try:
with open(sys.argv[1], encoding='utf-8') as f:
data = json.load(f)
blocks = data if isinstance(data, list) else data.get('blocks', data.get('content', []))
if not isinstance(blocks, list) or len(blocks) == 0:
print('Error: content.json must contain a non-empty array of blocks', file=sys.stderr)
sys.exit(1)
print(f' content.json validated: {len(blocks)} blocks')
except json.JSONDecodeError as e:
print(f'Error: content.json is not valid JSON — {e}', file=sys.stderr)
print(f'Hint: For long documents, write Markdown and use: make.sh reformat --input doc.md ...', file=sys.stderr)
sys.exit(1)
" "$content_file"; then
red " ✗ content.json validation failed"
exit 3
fi
$PY "$SCRIPTS/render_body.py" \
--tokens "$workdir/tokens.json" \
--content "$content_file" \
--out "$workdir/body.pdf"
green " ✓ Body rendered"
# Step 4: merge
echo ""
bold "Step 4/4 Merging and QA..."
$PY "$SCRIPTS/merge.py" \
--cover "$workdir/cover.pdf" \
--body "$workdir/body.pdf" \
--out "$out" \
--title "$title"
# Cleanup
rm -rf "$workdir"
}
# ── fill ──────────────────────────────────────────────────────────────────────
cmd_fill() {
local input="" out="" values="" data_file="" inspect_only=false
while [[ $# -gt 0 ]]; do
case "$1" in
--input) input="$2"; shift 2 ;;
--out) out="$2"; shift 2 ;;
--values) values="$2"; shift 2 ;;
--data) data_file="$2"; shift 2 ;;
--inspect) inspect_only=true; shift ;;
*) echo "Unknown option: $1"; exit 1 ;;
esac
done
if [[ -z "$input" ]]; then
echo "Usage: make.sh fill --input form.pdf [--out filled.pdf] [--values '{...}'] [--data values.json] [--inspect]"
exit 1
fi
if $inspect_only || [[ -z "$out" && -z "$values" && -z "$data_file" ]]; then
bold "Inspecting form fields in: $input"
$PY "$SCRIPTS/fill_inspect.py" --input "$input"
return
fi
bold "Filling form: $input$out"
local val_args=""
if [[ -n "$values" ]]; then val_args="--values $values"; fi
if [[ -n "$data_file" ]]; then val_args="--data $data_file"; fi
$PY "$SCRIPTS/fill_write.py" --input "$input" --out "$out" $val_args
}
# ── reformat ───────────────────────────────────────────────────────────────────
cmd_reformat() {
local input="" title="Reformatted Document" type="general"
local author="" date="" out="output.pdf" subtitle=""
local tmpdir
tmpdir="$(mktemp -d)"
while [[ $# -gt 0 ]]; do
case "$1" in
--input) input="$2"; shift 2 ;;
--title) title="$2"; shift 2 ;;
--type) type="$2"; shift 2 ;;
--author) author="$2"; shift 2 ;;
--date) date="$2"; shift 2 ;;
--subtitle) subtitle="$2"; shift 2 ;;
--out) out="$2"; shift 2 ;;
*) echo "Unknown option: $1"; exit 1 ;;
esac
done
if [[ -z "$input" ]]; then
echo "Usage: make.sh reformat --input source.md --title T --type TYPE --out output.pdf"
exit 1
fi
bold "Parsing: $input"
$PY "$SCRIPTS/reformat_parse.py" --input "$input" --out "$tmpdir/content.json"
green " ✓ Parsed to content.json"
bold "Applying design and building PDF..."
local sub_args=()
[[ -n "$subtitle" ]] && sub_args=(--subtitle "$subtitle")
cmd_run \
--title "$title" --type "$type" \
--author "$author" --date "$date" \
--content "$tmpdir/content.json" \
--out "$out" \
"${sub_args[@]+"${sub_args[@]}"}"
rm -rf "$tmpdir"
}
# ── demo ──────────────────────────────────────────────────────────────────────
cmd_demo() {
local tmpdir
tmpdir="$(mktemp -d)"
cat > "$tmpdir/content.json" <<'JSON'
[
{"type":"h1", "text":"Executive Summary"},
{"type":"body", "text":"This document was generated by minimax-pdf — a skill for creating visually polished PDFs. Every design decision is rooted in the document type and content, not a generic template."},
{"type":"callout", "text":"Key insight: design tokens flow from palette.py through every renderer, keeping cover and body visually consistent."},
{"type":"h1", "text":"How It Works"},
{"type":"h2", "text":"The Token Pipeline"},
{"type":"body", "text":"The palette.py script infers a color palette and typography pair from the document type. These tokens are written to tokens.json and consumed by every downstream script."},
{"type":"numbered","text":"palette.py generates color tokens, font selection, and the cover pattern"},
{"type":"numbered","text":"cover.py renders the cover HTML using the selected pattern"},
{"type":"numbered","text":"render_cover.js uses Playwright to convert the HTML cover to PDF"},
{"type":"numbered","text":"render_body.py builds inner pages from content.json using ReportLab"},
{"type":"numbered","text":"merge.py combines cover + body and runs final QA checks"},
{"type":"h2", "text":"Cover Patterns"},
{"type":"table",
"headers": ["Pattern", "Document type", "Visual character"],
"rows": [
["fullbleed", "report, general", "Deep background · dot-grid texture"],
["split", "proposal", "Left dark panel · right dot-grid"],
["typographic", "resume, academic", "Oversized display type · first-word accent"],
["atmospheric", "portfolio", "Dark bg · radial glow · dot-grid"],
["magazine", "magazine", "Cream bg · centered · hero image"],
["darkroom", "darkroom", "Navy bg · centered · grayscale image"],
["terminal", "terminal", "Near-black · grid lines · monospace"],
["poster", "poster", "White · thick sidebar · oversized title"]
]
},
{"type":"h1", "text":"Data Visualisation"},
{"type":"h2", "text":"Performance Metrics (Chart)"},
{"type":"body", "text":"Charts are rendered natively using matplotlib with a color palette derived from the document accent. No external chart services or image files required."},
{"type":"chart",
"chart_type": "bar",
"title": "Quarterly Performance",
"labels": ["Q1", "Q2", "Q3", "Q4"],
"datasets": [
{"label": "Revenue", "values": [120, 145, 132, 178]},
{"label": "Expenses", "values": [95, 108, 99, 122]}
],
"y_label": "USD (thousands)",
"caption": "Quarterly revenue vs. expenses"
},
{"type":"h2", "text":"Market Share (Pie Chart)"},
{"type":"chart",
"chart_type": "pie",
"labels": ["Product A", "Product B", "Product C", "Other"],
"datasets": [{"values": [42, 28, 18, 12]}],
"caption": "Annual market share by product line"
},
{"type":"pagebreak"},
{"type":"h1", "text":"Mathematics"},
{"type":"body", "text":"Display math is rendered via matplotlib mathtext — no LaTeX binary installation required. Inline references use standard [N] notation in body text."},
{"type":"math", "text":"E = mc^2", "label":"(1)"},
{"type":"math", "text":"\\int_0^\\infty e^{-x^2}\\,dx = \\frac{\\sqrt{\\pi}}{2}", "label":"(2)"},
{"type":"math", "text":"\\sum_{n=1}^{\\infty} \\frac{1}{n^2} = \\frac{\\pi^2}{6}", "caption":"Basel problem (Euler, 1734)"},
{"type":"h1", "text":"Process Flow"},
{"type":"body", "text":"Flowcharts are drawn directly using matplotlib patches — no Graphviz or external tools needed. Supported node shapes: rect, diamond, oval, parallelogram."},
{"type":"flowchart",
"nodes": [
{"id":"start", "label":"Start", "shape":"oval"},
{"id":"input", "label":"Receive Input", "shape":"parallelogram"},
{"id":"valid", "label":"Valid?", "shape":"diamond"},
{"id":"proc", "label":"Process Data", "shape":"rect"},
{"id":"err", "label":"Return Error", "shape":"rect"},
{"id":"out", "label":"Return Result", "shape":"parallelogram"},
{"id":"end", "label":"End", "shape":"oval"}
],
"edges": [
{"from":"start", "to":"input"},
{"from":"input", "to":"valid"},
{"from":"valid", "to":"proc", "label":"Yes"},
{"from":"valid", "to":"err", "label":"No"},
{"from":"proc", "to":"out"},
{"from":"err", "to":"end"},
{"from":"out", "to":"end"}
],
"caption": "Data validation and processing flow"
},
{"type":"h1", "text":"Code Example"},
{"type":"code", "language":"python",
"text":"# Design token pipeline\ntokens = palette.build_tokens(\n title=\"Annual Report\",\n doc_type=\"report\",\n author=\"J. Smith\",\n date=\"March 2026\",\n)\nhtml = cover.render(tokens)\npdf = render_cover(html)"},
{"type":"h1", "text":"Design Principles"},
{"type":"body", "text":"The aesthetic system is documented in design/design.md. The core rule: every design decision must be rooted in the document content and purpose. A color chosen because it fits the content will always outperform a color chosen because it seems safe."},
{"type":"h2", "text":"Restraint over decoration"},
{"type":"body", "text":"The page is done when there is nothing left to remove. Accent color appears on section rules only — not on headings, not on bullets. No card components, no drop shadows."},
{"type":"callout", "text":"A PDF passes the quality bar when a designer would not be embarrassed to hand it to a client."},
{"type":"pagebreak"},
{"type":"bibliography",
"title": "References",
"items": [
{"id":"1","text":"Bringhurst, R. (2004). The Elements of Typographic Style (3rd ed.). Hartley & Marks."},
{"id":"2","text":"Cairo, A. (2016). The Truthful Art: Data, Charts, and Maps for Communication. New Riders."},
{"id":"3","text":"Hochuli, J. & Kinross, R. (1996). Designing Books: Practice and Theory. Hyphen Press."}
]
}
]
JSON
cmd_run \
--title "minimax-pdf demo" \
--type "report" \
--author "minimax-pdf skill" \
--date "$(date '+%B %Y')" \
--subtitle "A demonstration of the token-based design pipeline" \
--content "$tmpdir/content.json" \
--out "demo.pdf"
rm -rf "$tmpdir"
}
# ── dispatch ───────────────────────────────────────────────────────────────────
main() {
if [[ $# -lt 1 ]]; then
bold "minimax-pdf — make.sh"
echo ""
echo "Usage: bash make.sh <command> [options]"
echo ""
echo "Commands:"
echo " check Verify all dependencies"
echo " fix Auto-install missing deps"
echo " run --title T --type TYPE CREATE: full pipeline → PDF"
echo " [--author A] [--date D] [--subtitle S]"
echo " [--abstract A] [--cover-image URL]"
echo " [--accent #HEX] [--cover-bg #HEX]"
echo " [--content content.json] [--out output.pdf]"
echo " fill --input f.pdf FILL: inspect or fill form fields"
echo " reformat --input doc.md REFORMAT: parse doc → apply design → PDF"
echo " demo Build a full-featured demo PDF"
exit 0
fi
case "$1" in
check) cmd_check ;;
fix) cmd_fix ;;
run) shift; cmd_run "$@" ;;
fill) shift; cmd_fill "$@" ;;
reformat) shift; cmd_reformat "$@" ;;
demo) cmd_demo ;;
*) echo "Unknown command: $1"; exit 1 ;;
esac
}
main "$@"