Markdown to Slides and PDF — A zsh Pipeline
Three zsh functions that take a single markdown file and emit it as a slide PDF, a body PDF, or a document PDF. marp.zsh wraps a pandoc/xelatex pipeline to produce LaTeX Beamer presentations, md_pdf.zsh drives the same engine with a two-mode interface (slide or document), and screenshot.zsh manages macOS screenshot output settings via defaults write.
What this is
Three tools, three responsibilities:
marp.zsh— slide PDF via pandoc + LaTeX Beamer, with theme and author metadata options. Registered as themd2pdfalias.md_pdf.zsh— the lower-level pipeline function (_md_pdf_impl) with explicit--docand--themeflags, covering both landscape slides and portrait A4 documents.screenshot.zsh— macOSdefaults writewrapper that controls where screenshots land, what format they use, and whether the floating thumbnail appears. Registered as thescreenshotalias.
All three are sourced from ~/.oh-my-zsh/custom/ and load at shell start.
The slide functions share the same engine: pandoc rendering to the beamer target, compiled with xelatex, using AppleGothic for CJK and Latin text and Menlo for monospace. The aspect ratio is fixed at 16:10 (aspectratio=1610) to match MacBook screens.
marp.zsh — markdown to slide PDF
marp.zsh defines md_to_pdf and aliases it as md2pdf. It delegates to a Python main.py inside a dedicated virtualenv at $MD_LATEX_PATH (/Users/jaesolshin/Documents/GitHub/md_latex).
Input and output paths
The function takes a single .md file as its first argument. Output defaults to the same basename with a .pdf extension in the working directory. A custom output path is supported via --output.
md2pdf presentation.md
md2pdf presentation.md --output final.pdfIf no file is provided, or if the file has a non-.md extension, the function prints an error and returns 1. The virtualenv is activated automatically; if .venv/bin/activate is not found at $MD_LATEX_PATH, the call aborts.
Options
| Flag | Default | Effect |
|---|---|---|
--template |
base |
Template selection: base or metropolis |
--title |
(from frontmatter) | Overrides the presentation title |
--author |
(from frontmatter) | Overrides the author field |
--output |
<input>.pdf |
Output file path |
--tex-only |
false | Emit .tex only; skip PDF compilation |
--verbose |
false | Pass --verbose to main.py |
The --template metropolis option maps to a Beamer Metropolis theme. --tex-only is useful for inspecting the LaTeX before compiling, or for submitting source to a publisher.
Sample calls
# Basic slide PDF from presentation.md
md2pdf presentation.md
# Metropolis theme with explicit title
md2pdf presentation.md --template metropolis --title 'Q2 Review'
# LaTeX source only, no PDF compilation
md2pdf presentation.md --tex-only
# Named output
md2pdf presentation.md --author 'Jaesol Shin' --output q2-review.pdfThe function prints 변환 시작: <file> at start, 변환 완료! on success, and reports the output file size. On failure it prints 변환 실패 and returns the exit code from main.py.
md_pdf.zsh — markdown to slide or document PDF
md_pdf.zsh defines _md_pdf_impl, which handles the same two output types directly via pandoc rather than delegating to a Python wrapper. It is designed for converting one or many .md files in a single call.
Two modes
Beamer (default): Landscape 16:10 slide PDF. Uses pandoc -t beamer with --pdf-engine=xelatex and injects a temp header for layout and font settings.
Document (--doc): Portrait A4 PDF. Uses pandoc without the beamer target, with geometry:margin=2cm, and falls back to a second pandoc invocation without CJKmainfont if the first fails — useful on machines where the xeCJK package has issues.
# Slide mode (default)
_md_pdf_impl talk.md
# Document mode
_md_pdf_impl --doc notes.md
# All .md files in current directory
_md_pdf_implWhen called with no arguments, the function globs *.md and converts every file it finds.
Beamer theme variants
The --theme flag selects a Beamer color and layout preset. Three named variants have hand-written LaTeX headers baked in:
warm — beige background (RGB 250,245,230), Boadilla theme, seahorse colortheme. Warm and low-contrast, suited for reading-heavy slides.
red-gray / red_gray — crimson (RGB 139,0,0) frame titles and structure, light gray (RGB 230,230,230) header/footer bars, white canvas, Madrid layout. The title page reads an event: field from the YAML frontmatter and renders it above the title block if present.
Named Beamer themes — any string not matching warm or red-gray is passed directly to pandoc as -V theme=<name>, so Boadilla, Berlin, Copenhagen, Warsaw, Singapore, and others all work.
_md_pdf_impl --theme red-gray keynote.md
_md_pdf_impl --theme warm workshop.md
_md_pdf_impl --theme Berlin tech-talk.mdWhat the injected header does
All beamer variants inject a temp header file with these shared settings:
\setbeamersize{text margin left=1.5cm, text margin right=1.5cm}
\setbeamerfont{frametitle}{series=\bfseries,size=\Large}
\setbeamertemplate{frametitle}[default][left,leftskip=0.5cm]
\addtobeamertemplate{frametitle}{\vspace{0.3cm}}{}
\setbeamerfont{normal text}{size=\large}
\renewcommand{\baselinestretch}{2}Line spacing is set to 2 (\baselinestretch), which reads cleanly at distance on a projected slide. The frametitle is bold and left-aligned with 0.5cm inset. The temp file is written to mktemp and removed after each conversion.
Document mode header
Document mode injects a minimal header:
\usepackage{xeCJK}
\setlength{\parindent}{0pt}
\setlength{\parskip}{0.5em}No indent, 0.5em paragraph spacing, and explicit CJK support via xeCJK. The --wrap=preserve pandoc flag passes line breaks through unchanged.
screenshot.zsh — macOS screenshot settings
screenshot.zsh defines _screenshot and aliases it as screenshot. It is a defaults write com.apple.screencapture wrapper with three writable settings and a read option.
This is not a headless browser capture tool. It controls where macOS's own screenshot mechanism (Cmd+Shift+3/4/5) saves files, what format it uses, and whether the floating thumbnail is shown after capture.
Options
screenshot -location <path> Set save directory (created if absent)
screenshot -type <format> Set file format
screenshot -thumb <true|false> Show or hide floating thumbnail
screenshot -get Print current settings
screenshot -h UsageSupported formats: png, jpg, jpeg, pdf, tiff, gif, heic. The string jpeg is normalized to jpg before writing. After any setting change, killall SystemUIServer restarts the menu bar daemon so the new settings take effect immediately without a logout.
# Move screenshots to a dedicated folder
screenshot -location ~/Pictures/Screenshots
# Switch to HEIC for smaller files
screenshot -type heic
# Disable the floating preview thumbnail
screenshot -thumb false
# Check current state
screenshot -get
# location : /Users/jaesolshin/Pictures/Screenshots
# type : heic
# thumbnail: falseThe -location flag expands ~ via eval before calling mkdir -p, so paths with tilde work as expected.
Using them together
A draft lives in a single .md file with a YAML frontmatter block. The event: field, if present, is picked up by the red-gray theme and placed above the title on the cover slide. Running _md_pdf_impl --theme red-gray draft.md produces the slide PDF. Running _md_pdf_impl --doc draft.md produces a companion A4 handout from the same source.
For iteration, the theme switch is one flag. Moving from warm to red-gray to a named Beamer theme requires no changes to the markdown file — the YAML frontmatter carries the metadata, and the function handles the rest.
# Slide version for presentation
_md_pdf_impl --theme red-gray talk.md
# Document version for distribution
_md_pdf_impl --doc talk.md
# Move all screenshots taken during review to the project folder
screenshot -location ~/Projects/talk/screenshotsFor a longer writing workflow where multiple .md files need conversion, calling _md_pdf_impl from the project directory with no arguments converts everything in one pass.
Operational notes
Dependencies
pandoc— the core conversion enginexelatex— PDF compilation (part of a TeX Live or MacTeX installation)AppleGothic— Korean font, pre-installed on macOSMenlo— monospace font, pre-installed on macOS- Python virtualenv at
$MD_LATEX_PATHwithmain.py— required only formd2pdf/marp.zsh
None of the three functions require Node.js, npm, or a browser process.
Korean font handling
Both pipelines set CJKmainfont=AppleGothic and mainfont=AppleGothic. On macOS this resolves reliably because AppleGothic ships with the OS. On Linux or CI environments, AppleGothic is absent and xelatex will fail at font loading. Substituting a system font (NanumGothic or Noto Sans CJK KR) in the pandoc -V flags and reinstalling the TeX run works as a drop-in replacement.
The --doc fallback in md_pdf.zsh runs pandoc a second time without CJKmainfont if the first invocation fails. This handles machines where xeCJK is installed but the specific CJK font is not — Latin text will render correctly even if Hangul falls back to a box.
Temp files
Both marp.zsh and md_pdf.zsh write Beamer header customizations to temp files via mktemp. Each temp file is removed with rm -f after the pandoc call completes. If pandoc is killed mid-run (Ctrl+C), the temp file may be left behind in /tmp but will be cleaned by the OS on the next restart.
SystemUIServer restart
screenshot.zsh calls killall SystemUIServer after every settings change. This briefly flashes the menu bar on macOS Ventura and later — a side effect of restarting the menu bar process. The function suppresses the output with >/dev/null 2>&1 || true, so no error is printed if the process is not running.