系列: traffic light
bash
158 行
· 更新于 2026-07-09
convert_to_pdf.sh
traffic_light/convert_to_pdf.sh
#!/bin/bash
# Convert Markdown -> styled HTML -> PDF (via headless Chrome)
# Usage: ./convert_to_pdf.sh input.md [output.pdf]
set -euo pipefail
INPUT="${1:?Usage: $0 input.md [output.pdf]}"
BASENAME="$(basename "${INPUT%.md}")"
OUTPUT="${2:-${BASENAME}.pdf}"
WORKDIR="$(mktemp -d)"
HTML_BODY="$WORKDIR/body.html"
HTML_FULL="$WORKDIR/full.html"
trap 'rm -rf "$WORKDIR"' EXIT
# 1) Markdown -> HTML body (GitHub Flavored Markdown)
md2html --github "$INPUT" -o "$HTML_BODY"
# 2) Wrap with full HTML + CSS optimized for Chinese + code blocks + tables
cat > "$HTML_FULL" <<'EOF'
<!DOCTYPE html>
<html lang="zh-Hant">
<head>
<meta charset="UTF-8">
<title>__TITLE__</title>
<style>
@page {
size: A4;
margin: 18mm 16mm 20mm 16mm;
@bottom-right { content: counter(page) " / " counter(pages); }
}
html { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
body {
font-family: "PingFang TC", "Heiti TC", "Microsoft JhengHei",
"Noto Sans CJK TC", "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 10.5pt;
line-height: 1.55;
color: #1f1f1f;
max-width: 100%;
}
h1, h2, h3, h4, h5, h6 { color: #0a3d62; line-height: 1.25; margin-top: 1.4em; }
h1 { font-size: 22pt; border-bottom: 2px solid #0a3d62; padding-bottom: 6px; }
h2 { font-size: 16pt; border-bottom: 1px solid #d0d7de; padding-bottom: 4px; margin-top: 1.6em; }
h3 { font-size: 13pt; }
h4 { font-size: 11.5pt; }
h1, h2 { page-break-after: avoid; }
h3, h4 { page-break-after: avoid; }
p, ul, ol, table { page-break-inside: avoid; }
a { color: #0969da; text-decoration: none; }
/* Inline code */
code {
font-family: "SF Mono", "JetBrains Mono", "Menlo", "Consolas", monospace;
font-size: 9pt;
background: #f3f4f6;
color: #c7254e;
padding: 1px 5px;
border-radius: 3px;
}
/* Code block */
pre {
font-family: "SF Mono", "JetBrains Mono", "Menlo", "Consolas", monospace;
font-size: 8.5pt;
line-height: 1.4;
background: #f6f8fa;
border: 1px solid #d0d7de;
border-radius: 6px;
padding: 10px 12px;
overflow-x: auto;
white-space: pre;
page-break-inside: avoid;
color: #1f1f1f;
}
pre code {
background: transparent;
color: inherit;
padding: 0;
font-size: inherit;
}
/* Tables */
table {
border-collapse: collapse;
width: 100%;
margin: 10px 0;
font-size: 9.5pt;
}
th, td {
border: 1px solid #d0d7de;
padding: 6px 9px;
vertical-align: top;
text-align: left;
}
th { background: #f1f5f9; color: #0a3d62; font-weight: 600; }
tr:nth-child(even) td { background: #fafbfc; }
/* Block quote */
blockquote {
border-left: 4px solid #0a3d62;
background: #f0f6fb;
margin: 10px 0;
padding: 8px 14px;
color: #1f2937;
}
/* Horizontal rule */
hr { border: none; border-top: 1px solid #d0d7de; margin: 22px 0; }
/* Lists */
ul, ol { padding-left: 22px; }
li { margin: 2px 0; }
/* Cover */
.cover {
text-align: center;
padding-top: 30mm;
page-break-after: always;
}
.cover h1 { border: none; font-size: 26pt; }
.cover p { font-size: 13pt; color: #555; margin-top: 12px; }
</style>
</head>
<body>
__BODY__
</body>
</html>
EOF
# Inject body + title
TITLE="$BASENAME"
python3 - "$HTML_FULL" "$HTML_BODY" "$TITLE" <<'PY'
import sys, pathlib
full_path, body_path, title = sys.argv[1], sys.argv[2], sys.argv[3]
body = pathlib.Path(body_path).read_text(encoding="utf-8")
full = pathlib.Path(full_path).read_text(encoding="utf-8")
full = full.replace("__BODY__", body).replace("__TITLE__", title)
pathlib.Path(full_path).write_text(full, encoding="utf-8")
PY
# 3) HTML -> PDF via headless Chrome
CHROME="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
OUTPUT_ABS="$(cd "$(dirname "$OUTPUT")" && pwd)/$(basename "$OUTPUT")"
"$CHROME" \
--headless=new \
--disable-gpu \
--no-pdf-header-footer \
--print-to-pdf="$OUTPUT_ABS" \
--print-to-pdf-no-header \
--no-sandbox \
--virtual-time-budget=10000 \
"file://$HTML_FULL" 2>/dev/null
echo "PDF generated: $OUTPUT_ABS"
ls -lh "$OUTPUT_ABS"