---
ai_assisted: true
title: "Markdown to Slides and PDF — A zsh Pipeline"
description: "Two zsh functions turn markdown into a Beamer slide PDF or an A4 document PDF via pandoc and xelatex; a third sets macOS screenshot location and format."
pubDate: 2026-05-20T20:39:11+09:00
category: "Development"
tags: ["zsh", "Markdown", "Pandoc", "PDF", "Slides", "LaTeX", "Content Workflow", "Dotfiles"]
heroImage: ""
serial: 16
draft: false
published: true
---

Three zsh functions, two of which take a single markdown file and emit it as a slide 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 the `md2pdf` alias.
- `md_pdf.zsh` — the lower-level pipeline function (`_md_pdf_impl`) with explicit `--doc` and `--theme` flags, covering both landscape slides and portrait A4 documents.
- `screenshot.zsh` — macOS `defaults write` wrapper that controls where screenshots land, what format they use, and whether the floating thumbnail appears. Registered as the `screenshot` alias.

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`.

```sh
md2pdf presentation.md
md2pdf presentation.md --output final.pdf
```

If 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

```sh
# 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.pdf
```

The 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.

```sh
# Slide mode (default)
_md_pdf_impl talk.md

# Document mode
_md_pdf_impl --doc notes.md

# All .md files in current directory
_md_pdf_impl
```

When 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.

```sh
_md_pdf_impl --theme red-gray keynote.md
_md_pdf_impl --theme warm workshop.md
_md_pdf_impl --theme Berlin tech-talk.md
```

### What the injected header does

All beamer variants inject a temp header file with these shared settings:

```latex
\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:

```latex
\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                   Usage
```

Supported 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.

```sh
# 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: false
```

The `-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.

```sh
# 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/screenshots
```

For 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 engine
- `xelatex` — PDF compilation (part of a TeX Live or MacTeX installation)
- `AppleGothic` — Korean font, pre-installed on macOS
- `Menlo` — monospace font, pre-installed on macOS
- Python virtualenv at `$MD_LATEX_PATH` with `main.py` — required only for `md2pdf` / `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.
