Skip to content

Instantly share code, notes, and snippets.

@manifestinteractive
Last active June 29, 2026 17:13
Show Gist options
  • Select an option

  • Save manifestinteractive/630a70c3f58a862723d4e7072ba07e26 to your computer and use it in GitHub Desktop.

Select an option

Save manifestinteractive/630a70c3f58a862723d4e7072ba07e26 to your computer and use it in GitHub Desktop.
Quick way to convert Markdown into pretty PDFs using macOS.

Markdown to PDF

This is just a quick way to convert Markdown into pretty PDFs using macOS and Homebrew.

I created this as a more customizable solution to the homebrew md2pdf package, which has a horrible time with things like markdown tables.

Install Dependencies

You only need two packages installed to make this work:

brew install weasyprint
brew install pandoc

This allows you to run a command in terminal like this:

pandoc filename.md -o filename.pdf --from=gfm --standalone --css=path/to/style.css --pdf-engine=weasyprint

Make it Pretty

OK, so now that we can make PDFs easily, we can add some custom styles to make it pretty.

I have made the style.css CSS file you can use as a starter.

You can put it wherever you want, but I would recommend it go here:

$HOME/.config/md2pdf/style.css

Make it Useful

Now, you can certainly use the full bash command example I provided above and call it a day, but why not make this a bit more helpful?

  1. Create a file /usr/local/bin/md2pdf
  2. Copy and Paste the content from md2pdf.sh into this new file
  3. Make that new file executable
sudo chmod +x /usr/local/bin/md2pdf

Now you can do all sorts of fun stuff:

# Convert a single markdown file
md2pdf /path/to/file.md
md2pdf ./relative/file.md

# Convert all markdown files in folder
md2pdf /path/to/folder

# Convert all markdown files in folder and subfolders
md2pdf /path/to/folder --recursive
#!/usr/bin/env bash
# Save as /usr/local/bin/md2pdf
# sudo chmod +x /usr/local/bin/md2pdf
set -euo pipefail
CSS_URL="$HOME/.config/md2pdf/style.css"
PDF_ENGINE="weasyprint"
usage() {
echo "Usage:"
echo " md2pdf /path/to/file.md"
echo " md2pdf ./relative/file.md"
echo " md2pdf /path/to/folder"
echo " md2pdf /path/to/folder --recursive"
}
convert_file() {
local input_file="$1"
local output_file="${input_file%.*}.pdf"
echo "Converting: $input_file"
pandoc "$input_file" \
-o "$output_file" \
--from=gfm \
--standalone \
--css="$CSS_URL" \
--pdf-engine="$PDF_ENGINE"
}
if [[ $# -lt 1 || $# -gt 2 ]]; then
usage
exit 1
fi
TARGET="$1"
RECURSIVE=false
if [[ $# -eq 2 ]]; then
if [[ "$2" == "--recursive" ]]; then
RECURSIVE=true
else
usage
exit 1
fi
fi
if ! command -v pandoc >/dev/null 2>&1; then
echo "Error: pandoc is not installed or not in PATH."
exit 1
fi
if ! command -v "$PDF_ENGINE" >/dev/null 2>&1; then
echo "Error: $PDF_ENGINE is not installed or not in PATH."
exit 1
fi
if [[ -f "$TARGET" ]]; then
if [[ "$TARGET" != *.md ]]; then
echo "Error: file must be a .md file."
exit 1
fi
convert_file "$TARGET"
exit 0
fi
if [[ -d "$TARGET" ]]; then
if [[ "$RECURSIVE" == true ]]; then
find "$TARGET" -type f -name "*.md" -print0 |
while IFS= read -r -d '' file; do
convert_file "$file"
done
else
find "$TARGET" -maxdepth 1 -type f -name "*.md" -print0 |
while IFS= read -r -d '' file; do
convert_file "$file"
done
fi
exit 0
fi
echo "Error: target does not exist: $TARGET"
exit 1
/* Save to: ~/.config/md2pdf/style.css */
@page {
size: Letter;
margin: 0.35in;
}
* {
box-sizing: border-box;
}
body {
font-family: Helvetica, Arial, sans-serif;
font-size: 12px;
line-height: 1.2;
padding: 30px;
background-color: white;
color: #333;
}
body > *:first-child,
blockquote > :first-child {
margin-top: 0 !important;
}
body > *:last-child,
blockquote > :last-child {
margin-bottom: 0 !important;
}
details {
display: block;
}
summary {
display: list-item;
}
details summary {
cursor: pointer;
}
a {
background-color: initial;
color: #0366d6;
text-decoration: none;
}
a:hover {
text-decoration: underline;
outline-width: 0;
}
a:active {
outline-width: 0;
}
a:not([href]) {
color: inherit;
text-decoration: none;
}
strong {
font-weight: 600;
}
hr {
box-sizing: initial;
height: 0.25em;
padding: 0;
margin: 24px 0;
overflow: hidden;
background-color: #e1e4e8;
border: 0;
}
hr::before,
hr::after {
display: table;
content: "";
}
hr::after {
clear: both;
}
img {
max-width: 100%;
border-style: none;
box-sizing: initial;
background-color: #fff;
}
img[align="right"] {
padding-left: 20px;
}
img[align="left"] {
padding-right: 20px;
}
input {
font: inherit;
margin: 0;
overflow: visible;
}
[type="checkbox"] {
box-sizing: border-box;
padding: 0;
}
:checked + .radio-label {
position: relative;
z-index: 1;
border-color: #0366d6;
}
blockquote,
details,
dl,
ol,
p,
pre,
table,
ul {
margin-top: 0;
margin-bottom: 16px;
}
p {
margin-top: 0;
}
blockquote {
padding: 0 1em;
margin: 0 0 16px;
color: #6a737d;
border-left: 0.25em solid #dfe2e5;
}
ol,
ul {
padding-left: 2em;
margin-top: 0;
}
ol ol,
ul ol {
list-style-type: lower-roman;
}
ol ol ol,
ol ul ol,
ul ol ol,
ul ul ol {
list-style-type: lower-alpha;
}
ol ol,
ol ul,
ul ol,
ul ul {
margin-top: 0;
margin-bottom: 0;
}
li > p {
margin-top: 16px;
}
li + li {
margin-top: 0.25em;
}
dl {
padding: 0;
}
dd {
margin-left: 0;
}
dl dt {
padding: 0;
margin-top: 16px;
font-size: 1em;
font-style: italic;
font-weight: 600;
}
dl dd {
padding: 0 16px;
margin-bottom: 16px;
}
h1,
h2,
h3,
h4,
h5,
h6 {
margin-top: 24px;
margin-bottom: 16px;
font-weight: 600;
line-height: 1.25;
}
h1,
h2 {
padding-bottom: 0.3em;
border-bottom: 1px solid #eaecef;
}
h1 {
font-size: 1.6em;
}
h2 {
font-size: 1.4em;
}
h3 {
font-size: 1.2em;
}
h4 {
font-size: 1em;
}
h5 {
font-size: 0.875em;
}
h6 {
font-size: 0.85em;
color: #6a737d;
}
table {
display: block;
width: 100%;
overflow: auto;
border-spacing: 0;
border-collapse: collapse;
}
td,
th {
padding: 6px 13px;
border: 1px solid #dfe2e5;
}
table th {
font-weight: 600;
}
table tr {
background-color: #fff;
border-top: 1px solid #c6cbd1;
}
table tr:nth-child(2n) {
background-color: #f6f8fa;
}
code,
kbd,
pre {
font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, monospace;
}
code,
pre {
font-size: 12px;
}
code {
padding: 0.2em 0.4em;
margin: 0;
font-size: 85%;
background-color: rgba(27, 31, 35, 0.05);
border-radius: 3px;
}
kbd {
display: inline-block;
padding: 3px 5px;
font: 11px SFMono-Regular, Consolas, "Liberation Mono", Menlo, monospace;
line-height: 10px;
color: #444d56;
vertical-align: middle;
background-color: #fafbfc;
border: 1px solid #d1d5da;
border-radius: 3px;
}
pre {
padding: 16px;
overflow: auto;
word-wrap: normal;
font-size: 85%;
line-height: 1.45;
background-color: #f6f8fa;
border-radius: 3px;
}
pre > code,
pre code {
display: inline;
max-width: 100%;
padding: 0;
margin: 0;
overflow: visible;
font-size: 100%;
line-height: inherit;
word-break: normal;
word-wrap: normal;
white-space: pre;
background: transparent;
border: 0;
}
table {
display: block;
width: 100%;
overflow: auto;
border-spacing: 0;
border-collapse: collapse;
font-size: 0.75em;
line-height: 1.1em;
}
table th,
table td {
padding: 6px 13px;
border: 1px solid #dfe2e5;
vertical-align: top;
}
table th {
font-weight: 600;
white-space: nowrap;
background-color: #f6f8fa;
}
table td {
white-space: normal;
}
table tr {
background-color: #fff;
border-top: 1px solid #c6cbd1;
}
table tr:nth-child(even) {
background-color: #f6f8fa;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment