Last active
August 26, 2026 16:42
-
-
Save schwartz1375/c311c2434d9f7d61ab18cbe57a565f9f to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| # Extracts each ```mermaid fence from a Markdown file and renders it as a | |
| # standalone image file. Companion to md_to_pdf.sh (same toolchain, same | |
| # install steps) for when you want just the figure, not a full-document PDF | |
| # that can split a tall diagram across pages. | |
| # | |
| # Usage: ./extract_mermaid.sh <input.md> [svg|png|pdf|both] [outdir] | |
| # both -> svg AND png (default): svg is the vector render kept beside the | |
| # .md (PDF pipeline, GitHub preview); png (3x) is what a pandoc | |
| # docx build must embed. A pandoc-built .docx with a bare svg and | |
| # no rsvg-convert fallback shows EMPTY FRAMES in Word, so any | |
| # document destined for .docx needs the png twin. | |
| # svg -> vector only; fine for manual Insert > Pictures in Word 365, | |
| # NOT sufficient for pandoc docx builds (see above) | |
| # png -> raster only at 3x scale; for slides or chat | |
| # pdf -> one diagram per file, scaled to fit a single page (--pdfFit) | |
| # | |
| # Output naming: <input-basename>-1.<fmt>, -2, ... (one per mermaid fence, | |
| # in document order). Spaces in the input name become underscores. | |
| # | |
| # v3 changes (August 2026): | |
| # - `both` mode added and made the default. Discovered while building the | |
| # Authorization-Derived Topology docx: pandoc embeds a bare svg with no | |
| # raster fallback when rsvg-convert is absent, and Word renders those as | |
| # blank frames. Rendering svg+png together makes the docx-safe asset | |
| # exist by default instead of being a rescue step. | |
| # | |
| # v2: ${EXTRA[@]+...} guard — macOS ships bash 3.2, where expanding an empty | |
| # array under `set -u` is an "unbound variable" error (fixed upstream in 4.4). | |
| set -euo pipefail | |
| [[ $# -lt 1 ]] && { echo "Usage: $0 <input.md> [svg|png|pdf|both] [outdir]"; exit 1; } | |
| [[ -f "$1" ]] || { echo "Error: $1 not found"; exit 1; } | |
| INPUT="$1" | |
| FMT="${2:-both}" | |
| case "$FMT" in svg|png|pdf) FORMATS="$FMT";; both) FORMATS="svg png";; *) echo "Error: format must be svg, png, pdf, or both"; exit 1;; esac | |
| OUTDIR_RAW="${3:-.}" | |
| mkdir -p "$OUTDIR_RAW" | |
| OUTDIR="$(cd "$OUTDIR_RAW" && pwd)" | |
| CHROME="$(ls -d "$HOME/.cache/puppeteer/chrome-headless-shell"/*/chrome-headless-shell-mac-arm64/chrome-headless-shell 2>/dev/null | sort -V | tail -1)" | |
| command -v mmdc &>/dev/null || { echo "Error: mmdc not found. Run: npm install -g @mermaid-js/mermaid-cli"; exit 1; } | |
| [[ -x "${CHROME:-}" ]] || { echo "Error: chrome-headless-shell not found. Run: npx puppeteer browsers install chrome-headless-shell"; exit 1; } | |
| WORK_DIR="$(mktemp -d)" | |
| trap 'rm -rf "$WORK_DIR"' EXIT | |
| # spaces -> '_' so mmdc's generated filenames stay predictable | |
| BASENAME="$(basename "${INPUT%.md}" | tr ' ' '_')" | |
| PUPPETEER_CFG="$WORK_DIR/puppeteer_config.json" | |
| printf '{"executablePath": "%s", "args": ["--no-sandbox"]}\n' "$CHROME" > "$PUPPETEER_CFG" | |
| shopt -s nullglob | |
| TOTAL=0 | |
| for F in $FORMATS; do | |
| EXTRA=() | |
| [[ "$F" == "png" ]] && EXTRA+=(--scale 3) | |
| [[ "$F" == "pdf" ]] && EXTRA+=(--pdfFit) | |
| echo "-> Rendering Mermaid fences from $(basename "$INPUT") as $F..." | |
| mmdc \ | |
| -i "$INPUT" \ | |
| -o "$WORK_DIR/${BASENAME}.md" \ | |
| -e "$F" \ | |
| -b white \ | |
| -p "$PUPPETEER_CFG" \ | |
| ${EXTRA[@]+"${EXTRA[@]}"} \ | |
| --quiet | |
| FILES=( "$WORK_DIR/${BASENAME}"-*."$F" ) | |
| [[ ${#FILES[@]} -eq 0 ]] && { echo "Error: no \`\`\`mermaid fences found in $INPUT"; exit 1; } | |
| for f in "${FILES[@]}"; do | |
| cp "$f" "$OUTDIR/" | |
| echo "OK Saved: $OUTDIR/$(basename "$f") ($(du -h "$f" | cut -f1))" | |
| done | |
| TOTAL=$((TOTAL + ${#FILES[@]})) | |
| done | |
| echo "Done: $TOTAL file(s) written to $OUTDIR" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment