markdown-note-render

Convert Markdown documents to PDF or static HTML
git clone https://git.0xfab.ch/markdown-note-render.git
Log | Files | Refs

commit 90175299d4181db91d5fdd56f5873cc0cd3fca63
Author: Fabian Wermelinger <info@0xfab.ch>
Date:   Sat,  1 Feb 2025 17:04:39 +0100

Add markdown note rendering script

Diffstat:
A.gitignore | 6++++++
Arendernote | 217+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Astatic/css/.gitignore | 1+
Astatic/css/style.css | 113+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 337 insertions(+), 0 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1,6 @@ +/node_modules +package.json +package-lock.json +*.md +*.pdf +*.html diff --git a/rendernote b/rendernote @@ -0,0 +1,217 @@ +#!/usr/bin/env bash + +main() { + if [ $# -eq 0 ]; then + cat <<EOF +USAGE $0 [-pdf|-html] <markdown note> [<markdown note> ...] +EOF + exit 1 + fi + local convert=render_html + case "$1" in + -pdf) shift; convert=render_pdf;; + -html) shift; convert=render_html;; + esac + for note; do + ${convert} "${note}" + done +} + +render_pdf() { + local input="$(mktemp)" + if [ -z "$(awk '/^---$/,/^---$/' "${1}")" ]; then + cat <<'EOF' >"${input}" +--- +fontsize: 12pt +papersize: a4 +linkcolor: blue +header-includes: + - \usepackage[top=60pt,bottom=60pt,left=80pt,right=80pt]{geometry} + - \usepackage{bm} + - \usepackage{sectsty} +include-before: + - \allsectionsfont{\sffamily} +--- +EOF + fi + cat ${1} >>"${input}" + pandoc \ + --from markdown --to pdf \ + --highlight-style pygments \ + --output "${1%.*}.pdf" "${input}" + rm -f "${input}" +} + +render_html() { + local lua_filter="$(mktemp)" + local html_template="$(mktemp)" + local style_sheets="$(mktemp)" + local raw_html="$(mktemp)" + + cat <<'EOF' >"${lua_filter}" +function Math(elem) + assert(FORMAT:match('html')) + local wrap + if elem.mathtype == 'InlineMath' then + wrap = '<latexinline>' .. elem.text .. '</latexinline>' + else + wrap = '<latexdisplay>' .. elem.text .. '</latexdisplay>' + end + return pandoc.RawInline('html', wrap) +end +EOF + cat <<'EOF' >"${html_template}" +<!DOCTYPE html> +<html xmlns="http://www.w3.org/1999/xhtml" lang="$lang$" xml:lang="$lang$"$if(dir)$ dir="$dir$"$endif$> +<head> + <meta charset="utf-8" /> + <meta name="generator" content="pandoc" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" /> +$for(author-meta)$ + <meta name="author" content="$author-meta$" /> +$endfor$ +$if(date-meta)$ + <meta name="dcterms.date" content="$date-meta$" /> +$endif$ +$if(keywords)$ + <meta name="keywords" content="$for(keywords)$$keywords$$sep$, $endfor$" /> +$endif$ +$if(description-meta)$ + <meta name="description" content="$description-meta$" /> +$endif$ + <title>$if(title-prefix)$$title-prefix$ – $endif$$pagetitle$</title> + <style> + $styles.html()$ + </style> +$for(header-includes)$ + $header-includes$ +$endfor$ +</head> +<body> +$for(include-before)$ +$include-before$ +$endfor$ +$if(toc)$ +<nav id="$idprefix$TOC" role="doc-toc"> +$if(toc-title)$ +<h2 id="$idprefix$toc-title">$toc-title$</h2> +$endif$ +$table-of-contents$ +</nav> +$endif$ +$body$ +$for(include-after)$ +$include-after$ +$endfor$ +</body> +</html> +EOF + + pandoc \ + --from markdown --to html \ + --metadata title="$(basename --suffix=.md -- "${1}")" \ + --highlight-style pygments \ + --lua-filter "${lua_filter}" \ + --template "${html_template}" \ + "${1}" >"${raw_html}" + + local dst="$(cd "$(dirname -- "${1}")" >/dev/null; pwd -P)/$(basename -- "${1}")" + local script="$(readlink -f -- "${BASH_SOURCE[0]}")" + script_dir="$(dirname -- "${script}")" >/dev/null + if [ ! -z "$(grep '<latex[a-z]*>' "${raw_html}")" ]; then + render_latex "${raw_html}" "${style_sheets}" + fi + echo "<link rel='stylesheet' href='${script_dir}/static/css/style.css'>" >>"${style_sheets}" + pandoc \ + --from html --to html \ + --standalone \ + --embed-resources \ + --template "${html_template}" \ + --include-in-header "${style_sheets}" \ + --output "${dst%.*}.html" "${raw_html}" + + rm -f "${raw_html}" "${style_sheets}" "${html_template}" "${lua_filter}" +} + +render_latex() { + pushd "${script_dir}" >/dev/null + if [ -z "$(npm ls --parseable katex)" ]; then + npm install katex linkedom + fi + + cat <<EOF | node - +const katex = require('katex'); +const {parseHTML} = require('linkedom'); +const fs = require('node:fs'); +fs.readFile('${1}', 'utf8', (err, content) => { + const {document} = parseHTML(content.toString()); + const inline_items = document.querySelectorAll('latexinline'); + const display_items = document.querySelectorAll('latexdisplay'); + inline_items.forEach((item) => { + const katex_code = katex.renderToString(item.innerHTML, { + output: 'html', + displayMode: false, + }); + item.outerHTML = katex_code; + }); + display_items.forEach((item) => { + const katex_code = katex.renderToString(item.innerHTML, { + output: 'html', + displayMode: true, + }); + item.outerHTML = katex_code; + }); + fs.writeFile('${1}', document.toString(), err => {}); +}); +EOF + + local katex_version="$(npm ls --parseable --long katex)" + katex_version="${katex_version##*@}" + local katex_css="${script_dir}/static/css/katex/${katex_version}" + static_katex "${katex_version}" "${katex_css}" + echo "<link rel='stylesheet' href='${katex_css}/math_fonts.css'>" >>"${2}" + echo "<link rel='stylesheet' href='${katex_css}/katex.min.css'>" >>"${2}" + popd >/dev/null +} + +static_katex() { + local version="${1}" + local dst="${2}" + if [ -d "${dst}" ]; then + return + fi + local src="https://cdn.jsdelivr.net/npm/katex@${version}/dist" + mkdir -p "${dst}" + curl -s "${src}/katex.min.css" | sed 's/@font-face{[^}]*}//g' >"${dst}/katex.min.css" + + font_type='woff' + fonts=( + "@font-face{font-family:KaTeX_AMS;font-style:normal;font-weight:400;src:url(fonts/KaTeX_AMS-Regular.${font_type}) format('${font_type}')}" + "@font-face{font-family:KaTeX_Caligraphic;font-style:normal;font-weight:700;src:url(fonts/KaTeX_Caligraphic-Bold.${font_type}) format('${font_type}')}" + "@font-face{font-family:KaTeX_Caligraphic;font-style:normal;font-weight:400;src:url(fonts/KaTeX_Caligraphic-Regular.${font_type}) format('${font_type}')}" + "@font-face{font-family:KaTeX_Fraktur;font-style:normal;font-weight:700;src:url(fonts/KaTeX_Fraktur-Bold.${font_type}) format('${font_type}')}" + "@font-face{font-family:KaTeX_Fraktur;font-style:normal;font-weight:400;src:url(fonts/KaTeX_Fraktur-Regular.${font_type}) format('${font_type}')}" + "@font-face{font-family:KaTeX_Main;font-style:normal;font-weight:700;src:url(fonts/KaTeX_Main-Bold.${font_type}) format('${font_type}')}" + "@font-face{font-family:KaTeX_Main;font-style:italic;font-weight:700;src:url(fonts/KaTeX_Main-BoldItalic.${font_type}) format('${font_type}')}" + "@font-face{font-family:KaTeX_Main;font-style:italic;font-weight:400;src:url(fonts/KaTeX_Main-Italic.${font_type}) format('${font_type}')}" + "@font-face{font-family:KaTeX_Main;font-style:normal;font-weight:400;src:url(fonts/KaTeX_Main-Regular.${font_type}) format('${font_type}')}" + "@font-face{font-family:KaTeX_Math;font-style:italic;font-weight:700;src:url(fonts/KaTeX_Math-BoldItalic.${font_type}) format('${font_type}')}" + "@font-face{font-family:KaTeX_Math;font-style:italic;font-weight:400;src:url(fonts/KaTeX_Math-Italic.${font_type}) format('${font_type}')}" + "@font-face{font-family:KaTeX_SansSerif;font-style:normal;font-weight:700;src:url(fonts/KaTeX_SansSerif-Bold.${font_type}) format('${font_type}')}" + "@font-face{font-family:KaTeX_SansSerif;font-style:italic;font-weight:400;src:url(fonts/KaTeX_SansSerif-Italic.${font_type}) format('${font_type}')}" + "@font-face{font-family:KaTeX_SansSerif;font-style:normal;font-weight:400;src:url(fonts/KaTeX_SansSerif-Regular.${font_type}) format('${font_type}')}" + "@font-face{font-family:KaTeX_Script;font-style:normal;font-weight:400;src:url(fonts/KaTeX_Script-Regular.${font_type}) format('${font_type}')}" + "@font-face{font-family:KaTeX_Size1;font-style:normal;font-weight:400;src:url(fonts/KaTeX_Size1-Regular.${font_type}) format('${font_type}')}" + "@font-face{font-family:KaTeX_Size2;font-style:normal;font-weight:400;src:url(fonts/KaTeX_Size2-Regular.${font_type}) format('${font_type}')}" + "@font-face{font-family:KaTeX_Size3;font-style:normal;font-weight:400;src:url(fonts/KaTeX_Size3-Regular.${font_type}) format('${font_type}')}" + "@font-face{font-family:KaTeX_Size4;font-style:normal;font-weight:400;src:url(fonts/KaTeX_Size4-Regular.${font_type}) format('${font_type}')}" + "@font-face{font-family:KaTeX_Typewriter;font-style:normal;font-weight:400;src:url(fonts/KaTeX_Typewriter-Regular.${font_type}) format('${font_type}')}" + ) + rm -f "${dst}/math_fonts.css" + for font in "${fonts[@]}"; do + file=$(echo "${font}" | grep -o "fonts/.*\.${font_type}") + echo ${font/${file}/"\"data:font/${font_type};charset=utf-8;base64,$(curl -s "${src}/${file}" | base64 -w 0 -)\""} >>"${dst}/math_fonts.css" + done +} + +main "$@" diff --git a/static/css/.gitignore b/static/css/.gitignore @@ -0,0 +1 @@ +/katex diff --git a/static/css/style.css b/static/css/style.css @@ -0,0 +1,113 @@ +body { + font-family: 'TeXGyrePagella', serif; + font-size: 116%; + text-align: justify; + max-width: 700px; +} + +h1 { + font-family: 'Lato', sans-serif; + font-size: 220%; + margin-top: 32px; + margin-bottom: 24px; + text-align: center; +} + +h2, h3 { + font-family: 'Lato', sans-serif; + font-weight: bold; + font-style: italic; + font-size: 130%; + margin-top: 24px; + margin-bottom: 8px; +} + +h3 { + font-style: normal; + font-size: 115%; + margin-top: 8px; + margin-bottom: 4px; +} + +h4, h5, h6 { + font-family: 'Lato', sans-serif; + font-weight: normal; + font-style: normal; + font-size: 115%; + margin-bottom: 4px; +} + +h5 { + font-style: italic; + font-size: 110%; +} + +h6 { + font-style: italic; + font-size: 105%; +} + +blockquote { + background-color: rgba(27,31,35,.1) !important; + padding: 8px 16px; + border-radius: 4px; +} + +blockquote p { + display: inline; +} + +code { + font-family: 'Inconsolata', monospace; + color: inherit; + background-color: rgba(27,31,35,.1); + border-radius: 4px; + padding: 2px 4px; +} + +pre { + background-color: rgba(27,31,35,.1) !important; + padding: 8px; + border-radius: 4px; +} + +pre code { + padding: 0; +} + +table { + width: 100%; + margin-top: 1rem; + margin-bottom: 1rem; + margin-left: auto; + margin-right: auto; + display: table; +} + +table tr th:empty { + display: none; +} + +img, svg { + max-width: 100%; + display: block; + margin-left: auto; + margin-right: auto; + width: 100%; + height: auto; + margin-top: 1rem; + margin-bottom: 1rem; + border-radius: 4px; +} + +.katex { + font-size: 1.1em; +} + +div.sourceCode { + margin: 0; +} + +.sourceCode { + overflow: auto; +}