ワンクリックで
md2pdf-typora
Convert Markdown to PDF using Typora's Whitey theme via pandoc + Chrome headless, replicating Typora's PDF export appearance
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Convert Markdown to PDF using Typora's Whitey theme via pandoc + Chrome headless, replicating Typora's PDF export appearance
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
Generate Python matplotlib plotting scripts in the user's scienceplots lab style: with plt.style.context(["science", "nature"]), pparam = dict(...), raw-string LaTeX labels, and fig.savefig(..., dpi=300, bbox_inches='tight'). Support parquet, CSV, NPY/NPZ data and single-line, multi-line, scatter/errorbar, or subplot variants. Generate both the reproducible plotting script and the requested PNG/PDF/SVG figure by default. Use for publication-style plot scripts, science/nature figures, plot-from-data scaffolds, and Korean or English requests for matplotlib graph scripts.
Create rigorous teaching explanations as a single markdown document with equations, assumptions, derivations, plots, optional schematic prompts, and PDF export. Use when the user asks to explain a physics, math, ML, statistics, or CS concept; write lecture, tutorial, or seminar notes; derive results step by step; build a kind but rigorous walkthrough; or turn a paper section into classroom-ready material. Finished explanations are archived to ~/Dropbox/ConceptExplainer/<Topic>/<concept-slug>/ automatically, regardless of language.
Create or revise a markdown research or experiment report that makes the work UNDERSTANDABLE (why it matters, what the core idea is, what it achieved), backed by traceable evidence, integrated plots, optional literature support, plot manifest tracking, and version history, inside a single Harness without external Codex or Gemini calls. Use when you need to generate `report.md`, explain a result or method so a colleague can understand it, inventory or validate plots, create `plots/plot_manifest.json`, manage `report_versions.json`, connect report sections to supporting references, or turn an experiment or output directory into a reusable report workflow.
Produce a journal-club-style paper presentation (9 sections: TL;DR, Problem, Key Idea, How It Works, Key Results, Why It Matters, Strengths/Limitations/Open Questions, Discussion Questions, Takeaways) from an arXiv ID/URL, a PDF, raw text/markdown, or a local LaTeX source (.tex / project dir). Helps a reading group UNDERSTAND and DISCUSS the paper — not score or accept/reject it. Grounds every claim in the source, renders math as LaTeX, auto-matches the source language (Korean source -> Korean review). When LaTeX source is available (arXiv e-print or local .tex), embeds the paper's real figures with captions; optionally generates two friendly-whiteboard infographic figures via the bundled codex image_generation tool. Use when the user wants a journal-club review, paper walkthrough/presentation, or paper explainer, to "review this PDF/paper like a journal club", or 논문 저널클럽 리뷰/발표자료/논문 설명. For an OpenReview referee report use workshop-paper-review; for an adversarial pre-submission audit use adversarial-review.
Search the live web via z.ai web_search_prime (GLM Coding Plan MCP server). Use when the user wants current information, recent trends, news, blog posts, docs, or any web resource that academic-database search (reference-search) does not cover. Triggers on web search, 웹 검색, search the web, 최근 동향, 최신 트렌드, latest trends, find online, 인터넷에서 찾아, what's new, current state of X, search z.ai, GLM web search.
Generate a hand-drawn, whiteboard-sketch schematic that explains a concept, pipeline, architecture, algorithm, or mechanism at a glance, on a pure white background. Composes a friendly hand-lettered infographic prompt (wavy marker strokes, numbered panels flowing left to right, chunky chalk arrows, hand-written notes beside shapes) and by default renders it to a PNG via the bundled codex image_generation tool (ChatGPT OAuth); falls back to emitting the copy-paste prompt when codex is unavailable. Use when the user wants a hand-drawn / hand-written / whiteboard-style schematic, a sketch diagram that shows a schema, a friendly explainer figure, a doodle-style method overview, or 손그림/화이트보드 스타일 도식/개념도. For matplotlib data plots use scienceplot-py or xkcd-py; for a multi-style (editorial, blueprint, neon, poster) prompt-only composer use wide-slide-illustrator; for the paper-review method+results pair use journal-club-review.
| name | md2pdf-typora |
| description | Convert Markdown to PDF using Typora's Whitey theme via pandoc + Chrome headless, replicating Typora's PDF export appearance |
Convert a Markdown file to PDF that closely matches Typora's PDF export with the Whitey theme. Uses pandoc for MD→HTML conversion and Chrome headless for HTML→PDF rendering.
/md2pdf-typora <input.md> [options]
<input.md> — Path to the input Markdown file (required)--output <path> — Output PDF path (default: same directory as input, .pdf extension)--dropbox [subfolder] — Copy PDF to ~/Dropbox/Magi/[subfolder]/--send-telegram — Send compiled PDF via Telegram after compilation--toc — Include table of contentsBefore pandoc conversion, handle Typora-specific syntax and pandoc parser quirks that the input may not anticipate:
[TOC] handling: Typora uses [TOC] as an inline TOC marker, but pandoc ignores it and leaves it as literal text. Always strip [TOC] from the input and use pandoc's --toc flag instead.--- is immediately followed by a ## heading with no blank line between them, pandoc's reader can interpret the pair as a setext-style table fragment, swallowing the heading and several paragraphs into a single <table><td> cell. Symptoms: missing TOC entries for that section, AND the section's body content (especially pipe tables) renders as a single inline run-on paragraph in the PDF. This pattern is common in chunked-translation workflows where chunk N ends with --- and chunk N+1 starts with ## , then cat-merging skips the blank line. The pre-processor inserts a blank line between any --- line and an immediately-following ## heading.TMP_MD="${INPUT_DIR}/_typora_tmp.md"
# (a) strip Typora [TOC] markers
# (b) ensure a blank line between '---' HR and an immediately-following '## ' heading
python3 - "$INPUT" "$TMP_MD" << 'PYEOF'
import sys
src, dst = sys.argv[1], sys.argv[2]
out = []
with open(src) as f:
lines = f.readlines()
for i, line in enumerate(lines):
if line.rstrip("\n").strip() == "[TOC]":
continue # strip Typora TOC marker
out.append(line)
if line.rstrip() == "---" and i + 1 < len(lines) and lines[i+1].lstrip().startswith("## "):
out.append("\n") # insert blank line so pandoc doesn't fuse '---'+heading into a table
with open(dst, "w") as f:
f.writelines(out)
PYEOF
Use pandoc to convert the pre-processed Markdown to standalone HTML with:
--toc --toc-depth=2 for table of contents (always enabled since [TOC] was stripped)CSS_PATH="$HOME/.claude/skills/md2pdf-typora/typora-whitey.css"
cd "$INPUT_DIR"
pandoc "$(basename "$TMP_MD")" \
-f markdown-yaml_metadata_block+tex_math_dollars \
-o "$TMP_HTML" \
--standalone \
--mathjax \
--css="$CSS_PATH" \
--metadata title="$TITLE" \
--highlight-style=pygments \
--toc --toc-depth=2
# Force MathJax SVG output (replaces pandoc's default CHTML mode).
# CHTML depends on STIX-Web webfonts loaded from CDN; under Chrome headless,
# missing-glyph fallbacks render Greek letters (\phi, \theta etc.) as empty
# boxes and inline sub/superscripts wrap onto separate lines. SVG renders
# every glyph as a path with no font dependency, eliminating both failures.
sed -i 's|/tex-chtml-full.js|/tex-svg-full.js|g' "$TMP_HTML"
Why -f markdown-yaml_metadata_block+tex_math_dollars: pandoc's default markdown reader treats any colon (:) on an early line as a potential YAML key. Korean / non-English documents that open with a metadata blockquote like > **도메인**: 물리학 raise spurious YAML parse exception at line N, column M errors and abort. Disabling the yaml_metadata_block extension bypasses this false positive without losing any other parsing capability — true YAML frontmatter (delimited by leading and trailing ---) is rare in PDF inputs, and metadata is supplied via --metadata title=... instead. The companion +tex_math_dollars keeps $...$/$$...$$ math active.
Title extraction: Use the first # heading in the file, or the filename if none exists.
Image handling: Pandoc resolves relative image paths from the input file's directory. Always run pandoc from the input file's directory:
cd "$(dirname "$INPUT")" && pandoc "$(basename "$TMP_MD")" ...
Why SVG over CHTML: CHTML (pandoc's default) requires Chrome headless to download and apply MathJax web fonts before printing. In practice this fails silently — Greek letters become □ boxes, and inline math like $\theta_{\mathrm{UV}}$ wraps with θ on one line and _UV on the next. SVG mode renders every symbol as inline <svg> path data, so the PDF is glyph-correct regardless of font cache state. SVG output is also slightly larger (~10-30%) but immune to network/cache flakiness.
This is the critical post-processing step. Pandoc's output has several issues that must be fixed:
--css adds a <link> tag that Chrome headless can't resolve. Replace it with an inline <style> block.<h1 class="title"> from --metadata title AND keeps the body <h1> from the markdown # heading. Remove the metadata title h1 to avoid duplication.<nav id="TOC"> before all body content (including the <h1>). Extract the TOC nav block and re-insert it after the first body </h1> so it appears below the title, matching Typora's [TOC] behavior.max-width: 960px and base font-size: 19px were tuned for on-screen reading and overflow both A4 (794px) and Letter (816px) page widths. Without overrides, content silently extends past the printable area, wide tables collapse with overflowing cells, equations push past the right margin, and code blocks scroll off the page. The patch CSS pins @page { size: A4 }, sets body { max-width: none }, switches tables to table-layout: fixed with explicit column widths and word-break so multi-line cells wrap correctly, prevents page breaks inside equations / blockquotes / table rows, and keeps headings attached to their following content (page-break-after: avoid).<mjx-container> for each formula. Without white-space: nowrap, inline math like $\theta_{\mathrm{UV}}$ can wrap mid-expression, splitting θ from its subscript across lines. The patch CSS pins each container to one line.python3 << 'PYEOF'
import re
css_path = "$CSS_PATH"
html_path = "$TMP_HTML"
with open(css_path) as f:
css = f.read()
with open(html_path) as f:
html = f.read()
# Print-layout overrides (kept inside the same <style> block as Whitey CSS so
# they cascade after the theme rules and win without `!important` battles).
print_css = """
@page { size: A4; margin: 18mm 14mm 20mm 14mm; }
html { font-size: 14px !important; }
body { max-width: none !important; margin: 0 !important; padding: 0 !important;
line-height: 1.45 !important; text-align: left !important; }
h1 { font-size: 1.9em !important; margin-top: 0.8em !important; }
h2 { font-size: 1.5em !important; margin-top: 1.2em !important;
page-break-after: avoid; }
h3 { font-size: 1.2em !important; page-break-after: avoid; }
h4 { font-size: 1.05em !important; page-break-after: avoid; }
p, li { orphans: 2; widows: 2; }
/* Tables: fixed layout + column widths + cell wrap so wide content
(especially CJK paragraphs) does not overflow the page width. */
table { table-layout: fixed !important; width: 100% !important;
font-size: 0.88em !important; word-break: keep-all;
overflow-wrap: anywhere; page-break-inside: auto; }
table th, table td { padding: 5px 7px !important; line-height: 1.35 !important;
vertical-align: top !important;
word-break: keep-all; overflow-wrap: anywhere; }
table thead { display: table-header-group; }
table tr { page-break-inside: avoid; }
/* 3-column tables (e.g. 'this concept | is not | distinguishing feature') */
table th:first-child, table td:first-child { width: 22%; }
table th:nth-child(2), table td:nth-child(2) { width: 22%; }
table th:nth-child(3), table td:nth-child(3) { width: 56%; }
/* 2-column tables (e.g. key/value glossaries): override the 3-col widths */
table:not(:has(thead th:nth-child(3))) th:first-child,
table:not(:has(thead th:nth-child(3))) td:first-child { width: 28%; }
table:not(:has(thead th:nth-child(3))) th:nth-child(2),
table:not(:has(thead th:nth-child(3))) td:nth-child(2) { width: 72%; }
pre { white-space: pre-wrap !important; word-wrap: break-word !important;
font-size: 0.85em !important; page-break-inside: avoid; }
code { word-break: break-all; overflow-wrap: anywhere; }
blockquote { page-break-inside: avoid; margin: 0.8em 0; padding: 0.4em 0.9em;
border-left: 3px solid #bbb; }
ul, ol { margin: 0.4em 0 0.4em 1.2em; padding-left: 0.4em; }
li { margin-bottom: 0.15em; }
hr { page-break-after: always; visibility: hidden;
height: 0; margin: 0; border: 0; }
nav#TOC { font-size: 0.85em; line-height: 1.35; page-break-after: always; }
nav#TOC ul { list-style: none; padding-left: 1em; margin: 0.2em 0; }
"""
# 1) Inline CSS — Whitey theme followed by print overrides
html = re.sub(
r'<link rel="stylesheet" href="[^"]*typora-whitey\.css"[^>]*/>',
f'<style>\n{css}\n{print_css}\n</style>', html
)
# 2) Add image scaling + MathJax SVG inline-math no-wrap CSS
html = html.replace('</style>', """
img {
max-width: 100% !important;
height: auto !important;
display: block;
margin: 0.8em auto;
page-break-inside: avoid;
}
mjx-container {
white-space: nowrap;
}
mjx-container[display="true"] {
margin: 0.6em 0 !important;
page-break-inside: avoid !important;
}
mjx-container[display="true"] > svg,
mjx-container[display="true"] > mjx-math {
max-width: 100%;
}
</style>""", 1)
# 3) Remove pandoc's duplicate title h1 (has class="title")
html = re.sub(r'<h1 class="title">[^<]*</h1>\s*', '', html)
# 4) Move TOC nav block to after the first body </h1>
toc_match = re.search(r'(<nav\s+id="TOC"[^>]*>.*?</nav>)', html, re.DOTALL)
if toc_match:
toc_block = toc_match.group(1)
html = html.replace(toc_block, '', 1)
html = html.replace('</h1>', '</h1>\n' + toc_block, 1)
with open(html_path, 'w') as f:
f.write(html)
PYEOF
google-chrome-stable \
--headless=new \
--disable-gpu \
--no-pdf-header-footer \
--virtual-time-budget=30000 \
--print-to-pdf="$OUTPUT_PDF" \
"file://$TMP_HTML" 2>/dev/null
Chrome PDF options:
--no-pdf-header-footer — removes default header/footer with URL and date--virtual-time-budget=30000 — gives 30 seconds for Google Fonts CDN + MathJax SVG bundle (~1 MB) to fully load and typeset every formula before printing. Documents with hundreds of math expressions are still well within budget; documents with very few math expressions never wait the full 30s in practice.@page { size: A4 } rule (Step 3). Chrome's command-line default is Letter; the CSS rule overrides it. To switch to Letter, change size: A4 to size: Letter in the print CSS — do not pass --print-to-pdf-paper-size, which is brittle across Chrome versions.IMPORTANT: HTML must be in the same directory as the input markdown so that relative image paths (e.g., plots/foo.png) resolve correctly. Do NOT write HTML to /tmp/.
--dropbox was specified:
DROPBOX_BASE="$HOME/Dropbox/Magi"
mkdir -p "$DROPBOX_BASE/$SUBFOLDER"
cp "$OUTPUT_PDF" "$DROPBOX_BASE/$SUBFOLDER/"
--send-telegram was specified, send via Telegram reply tool#!/usr/bin/env bash
set -euo pipefail
INPUT="$1"
BASENAME="$(basename "${INPUT}" .md)"
INPUT_DIR="$(cd "$(dirname "$INPUT")" && pwd)"
CSS_PATH="$HOME/.claude/skills/md2pdf-typora/typora-whitey.css"
TMP_MD="${INPUT_DIR}/_typora_tmp.md"
TMP_HTML="${INPUT_DIR}/_typora_tmp.html"
OUTPUT_PDF="${OUTPUT:-${INPUT_DIR}/${BASENAME}.pdf}"
# Extract title from first H1
TITLE=$(grep -m1 '^# ' "$INPUT" | sed 's/^# //' || echo "$BASENAME")
# Step 1: Pre-process — strip Typora's [TOC] AND insert blank line between
# any '---' HR and an immediately-following '## ' heading (otherwise pandoc
# fuses them into a setext-style table that swallows the heading + body).
python3 - "$INPUT" "$TMP_MD" << 'PYEOF'
import sys
src, dst = sys.argv[1], sys.argv[2]
out = []
with open(src) as f:
lines = f.readlines()
for i, line in enumerate(lines):
if line.rstrip("\n").strip() == "[TOC]":
continue
out.append(line)
if line.rstrip() == "---" and i + 1 < len(lines) and lines[i+1].lstrip().startswith("## "):
out.append("\n")
with open(dst, "w") as f:
f.writelines(out)
PYEOF
# Step 2: MD → HTML (run from input dir for relative image paths).
# `-f markdown-yaml_metadata_block` disables YAML metadata block detection so
# that Korean / non-English documents whose body lines contain ':' do not
# raise spurious YAML parse errors. `+tex_math_dollars` keeps `$...$` math.
cd "$INPUT_DIR"
pandoc "$(basename "$TMP_MD")" \
-f markdown-yaml_metadata_block+tex_math_dollars \
-o "$TMP_HTML" \
--standalone \
--mathjax \
--css="$CSS_PATH" \
--metadata title="$TITLE" \
--highlight-style=pygments \
--toc --toc-depth=2
# Force MathJax SVG output (eliminates CHTML font-fallback bugs).
sed -i 's|/tex-chtml-full.js|/tex-svg-full.js|g' "$TMP_HTML"
# Step 3: Patch HTML — inline CSS + print-layout overrides, fix TOC position,
# scale images, no-wrap inline math.
python3 << 'PYEOF'
import re
css_path = "$CSS_PATH"
html_path = "$TMP_HTML"
with open(css_path) as f: css = f.read()
with open(html_path) as f: html = f.read()
print_css = """
@page { size: A4; margin: 18mm 14mm 20mm 14mm; }
html { font-size: 14px !important; }
body { max-width: none !important; margin: 0 !important; padding: 0 !important;
line-height: 1.45 !important; text-align: left !important; }
h1 { font-size: 1.9em !important; margin-top: 0.8em !important; }
h2 { font-size: 1.5em !important; margin-top: 1.2em !important; page-break-after: avoid; }
h3 { font-size: 1.2em !important; page-break-after: avoid; }
h4 { font-size: 1.05em !important; page-break-after: avoid; }
p, li { orphans: 2; widows: 2; }
table { table-layout: fixed !important; width: 100% !important;
font-size: 0.88em !important; word-break: keep-all; overflow-wrap: anywhere;
page-break-inside: auto; }
table th, table td { padding: 5px 7px !important; line-height: 1.35 !important;
vertical-align: top !important; word-break: keep-all;
overflow-wrap: anywhere; }
table thead { display: table-header-group; }
table tr { page-break-inside: avoid; }
table th:first-child, table td:first-child { width: 22%; }
table th:nth-child(2), table td:nth-child(2) { width: 22%; }
table th:nth-child(3), table td:nth-child(3) { width: 56%; }
table:not(:has(thead th:nth-child(3))) th:first-child,
table:not(:has(thead th:nth-child(3))) td:first-child { width: 28%; }
table:not(:has(thead th:nth-child(3))) th:nth-child(2),
table:not(:has(thead th:nth-child(3))) td:nth-child(2) { width: 72%; }
pre { white-space: pre-wrap !important; word-wrap: break-word !important;
font-size: 0.85em !important; page-break-inside: avoid; }
code { word-break: break-all; overflow-wrap: anywhere; }
blockquote { page-break-inside: avoid; margin: 0.8em 0; padding: 0.4em 0.9em;
border-left: 3px solid #bbb; }
ul, ol { margin: 0.4em 0 0.4em 1.2em; padding-left: 0.4em; }
li { margin-bottom: 0.15em; }
hr { page-break-after: always; visibility: hidden; height: 0; margin: 0; border: 0; }
nav#TOC { font-size: 0.85em; line-height: 1.35; page-break-after: always; }
nav#TOC ul { list-style: none; padding-left: 1em; margin: 0.2em 0; }
"""
# Inline CSS (Whitey + print overrides)
html = re.sub(r'<link rel="stylesheet" href="[^"]*typora-whitey\.css"[^>]*/>',
f'<style>\n{css}\n{print_css}\n</style>', html)
# Image scaling + MathJax SVG inline-math no-wrap, attached after the main <style>
html = html.replace('</style>', """
img { max-width: 100% !important; height: auto !important; display: block;
margin: 0.8em auto; page-break-inside: avoid; }
mjx-container { white-space: nowrap; }
mjx-container[display="true"] { margin: 0.6em 0 !important;
page-break-inside: avoid !important; }
mjx-container[display="true"] > svg,
mjx-container[display="true"] > mjx-math { max-width: 100%; }
</style>""", 1)
# Remove pandoc's duplicate title h1
html = re.sub(r'<h1 class="title">[^<]*</h1>\s*', '', html)
# Move TOC after body h1
toc_match = re.search(r'(<nav\s+id="TOC"[^>]*>.*?</nav>)', html, re.DOTALL)
if toc_match:
toc_block = toc_match.group(1)
html = html.replace(toc_block, '', 1)
html = html.replace('</h1>', '</h1>\n' + toc_block, 1)
with open(html_path, 'w') as f: f.write(html)
PYEOF
# Step 4: HTML → PDF
google-chrome-stable \
--headless=new \
--disable-gpu \
--no-pdf-header-footer \
--virtual-time-budget=30000 \
--print-to-pdf="$OUTPUT_PDF" \
"file://$TMP_HTML" 2>/dev/null
# Cleanup
rm -f "$TMP_HTML" "$TMP_MD"
echo "PDF created: $OUTPUT_PDF ($(du -h "$OUTPUT_PDF" | cut -f1))"
tex-svg-full.js, loaded from CDN). The pipeline rewrites pandoc's default CHTML reference to SVG because CHTML silently fails when STIX-Web webfonts are unavailable to Chrome headless (Greek letters become □ boxes; inline sub/superscripts wrap onto separate lines). SVG renders every glyph as inline path data and is robust against font-cache state.mathjax@3/es5/tex-svg-full.js plus dependencies) and adjust the sed patch to point at the local URL.~/.claude/skills/md2pdf-typora/typora-whitey.cssThese were caught in production and are now handled by the pre-processor / pandoc flags / print CSS by default:
--- followed by ## heading on the next line, no blank line between them. Symptom: missing TOC entries for the affected sections AND the section's body content (especially the first table after the heading) renders as a single inline run-on paragraph, sometimes spanning multiple paragraphs welded into one cell. Root cause: pandoc parses ---\n## ... as a setext-style table fragment. Fix: Step 1 pre-processor inserts a blank line.YAML parse exception at line N, column M from pandoc on Korean / non-English documents. Symptom: pandoc aborts with an opaque error before any HTML is produced. Root cause: pandoc's default markdown reader treats early-line : (very common in Korean blockquotes like > **도메인**: 물리학) as a YAML key. Fix: Step 2 invokes pandoc with -f markdown-yaml_metadata_block+tex_math_dollars to disable the YAML metadata block extension while keeping $...$ math.max-width: 960px is wider than A4 (794px), and tables default to table-layout: auto which sizes columns from content. Fix: Step 3 print CSS sets body { max-width: none }, table { table-layout: fixed }, explicit column-width allocations, and word-break: keep-all; overflow-wrap: anywhere so Korean text wraps cleanly inside cells.page-break-inside: avoid on each.page-break-after: avoid on h2/h3/h4.