Skip to content

Instantly share code, notes, and snippets.

@owulveryck
Last active February 9, 2026 17:20
Show Gist options
  • Select an option

  • Save owulveryck/747bd356b9f809d4bac95005e3ad7c10 to your computer and use it in GitHub Desktop.

Select an option

Save owulveryck/747bd356b9f809d4bac95005e3ad7c10 to your computer and use it in GitHub Desktop.
#!/usr/bin/env bash
set -euo pipefail
# Constants matching reMarkable 2 specifications from renderer.go
readonly RM2_WIDTH="157.7mm"
readonly RM2_HEIGHT="210.3mm"
readonly MARGIN_LEFT="4.5mm"
readonly MARGIN_RIGHT="18mm"
readonly MARGIN_TOP="4.5mm"
readonly MARGIN_BOTTOM="4.5mm"
readonly DEFAULT_FONT_SIZE="10"
readonly LINE_STRETCH="1.3"
# Variables (matching main.go flags)
recursive=false
output_dir=""
verbose=false
font_size="$DEFAULT_FONT_SIZE"
# Font variables (can be modified for fallbacks)
body_font="Merriweather"
code_font="Roboto Mono"
# Counters
successful=0
failed=0
declare -a failed_files=()
# Colors for output
readonly RED='\033[0;31m'
readonly GREEN='\033[0;32m'
readonly YELLOW='\033[1;33m'
readonly NC='\033[0m' # No Color
usage() {
cat <<EOF
Usage: $(basename "$0") [OPTIONS]
Pandoc-based Markdown to PDF converter for reMarkable 2
Options:
-r Recursively process subdirectories
-o DIR Output directory for PDF files (default: same as source)
-v Verbose output
-f SIZE Base font size in points (default: 12)
-h Show this help message
Examples:
$(basename "$0") # Convert all .md files in current directory
$(basename "$0") -r # Convert all .md files recursively
$(basename "$0") -o pdfs -v # Convert to pdfs/ directory with verbose output
$(basename "$0") -f 14 # Convert with 14pt base font size
EOF
}
log_error() {
echo -e "${RED}Error: $*${NC}" >&2
}
log_warn() {
echo -e "${YELLOW}Warning: $*${NC}" >&2
}
log_info() {
if $verbose; then
echo -e "${GREEN}$*${NC}"
fi
}
check_dependencies() {
local missing=()
if ! command -v pandoc >/dev/null 2>&1; then
missing+=("pandoc")
fi
if ! command -v xelatex >/dev/null 2>&1; then
missing+=("xelatex (from TeXLive or MikTeX)")
fi
if [[ ${#missing[@]} -gt 0 ]]; then
log_error "Missing required dependencies: ${missing[*]}"
echo ""
echo "Installation instructions:"
echo " macOS: brew install pandoc basictex"
echo " Ubuntu: apt-get install pandoc texlive-xetex texlive-fonts-recommended"
echo " Fedora: dnf install pandoc texlive-xetex texlive-collection-fontsrecommended"
return 1
fi
# Check for fonts (non-fatal, will use fallbacks)
if command -v fc-list >/dev/null 2>&1; then
# Disable pipefail temporarily to avoid SIGPIPE errors from grep -q
set +o pipefail
if ! fc-list 2>/dev/null | grep -qi "Merriweather"; then
log_warn "Merriweather font not found, using fallback"
body_font="Georgia"
fi
if ! fc-list 2>/dev/null | grep -qi "Roboto Mono"; then
log_warn "Roboto Mono font not found, using fallback"
code_font="Courier New"
fi
set -o pipefail
fi
return 0
}
create_latex_header() {
local temp_file
temp_file=$(mktemp)
# Calculate heading sizes matching renderer.go:56-67
local h1_size=$((font_size + 8))
local h2_size=$((font_size + 6))
local h3_size=$((font_size + 4))
local h4_size=$((font_size + 2))
local h5_size=$((font_size + 1))
local h6_size=$font_size
# Calculate line heights (size * 1.4)
local h1_line=$(awk "BEGIN {printf \"%.1f\", $h1_size * 1.4}")
local h2_line=$(awk "BEGIN {printf \"%.1f\", $h2_size * 1.4}")
local h3_line=$(awk "BEGIN {printf \"%.1f\", $h3_size * 1.4}")
local h4_line=$(awk "BEGIN {printf \"%.1f\", $h4_size * 1.4}")
local h5_line=$(awk "BEGIN {printf \"%.1f\", $h5_size * 1.4}")
local h6_line=$(awk "BEGIN {printf \"%.1f\", $h6_size * 1.4}")
# First part: fancyhdr setup (no variable substitution needed)
cat > "$temp_file" <<'EOF'
% Page numbering in footer
\usepackage{fancyhdr}
\pagestyle{fancy}
\fancyhf{}
\fancyfoot[C]{\small\itshape Page \thepage}
\renewcommand{\headrulewidth}{0pt}
\renewcommand{\footrulewidth}{0pt}
\fancypagestyle{plain}{%
\fancyhf{}%
\fancyfoot[C]{\small\itshape Page \thepage}%
\renewcommand{\headrulewidth}{0pt}%
\renewcommand{\footrulewidth}{0pt}%
}
% Better table handling with wrapping and page breaks
\usepackage{longtable}
\usepackage{booktabs}
\usepackage{array}
% Allow tables to break across pages and wrap text in cells
\setlength{\LTpre}{0pt}
\setlength{\LTpost}{0pt}
% Heading formatting with page breaks
\usepackage{titlesec}
EOF
# Second part: heading formats with variable substitution
cat >> "$temp_file" <<EOF
% H1 with page break before
\titleformat{\section}
{\newpage\fontsize{$h1_size}{$h1_line}\bfseries\selectfont}
{}
{0em}
{}
% H2 with page break before
\titleformat{\subsection}
{\newpage\fontsize{$h2_size}{$h2_line}\bfseries\selectfont}
{}
{0em}
{}
% H3-H6 without page breaks
\titleformat*{\subsubsection}{\fontsize{$h3_size}{$h3_line}\bfseries\selectfont}
\titleformat*{\paragraph}{\fontsize{$h4_size}{$h4_line}\bfseries\selectfont}
\titleformat*{\subparagraph}{\fontsize{$h5_size}{$h5_line}\bfseries\selectfont}
EOF
echo "$temp_file"
}
find_markdown_files() {
local dir="$1"
local -a files=()
if $recursive; then
# Recursive mode: find all .md files, skip hidden files and directories
while IFS= read -r -d '' file; do
files+=("$file")
done < <(find "$dir" -type f -name "*.md" -not -path "*/.*" -print0)
else
# Non-recursive: scan only current directory, skip hidden files
while IFS= read -r -d '' file; do
local basename
basename=$(basename "$file")
if [[ ! "$basename" =~ ^\. ]]; then
files+=("$file")
fi
done < <(find "$dir" -maxdepth 1 -type f -name "*.md" -print0)
fi
printf '%s\n' "${files[@]}"
}
get_output_path() {
local input_file="$1"
local base_name
local pdf_name
base_name=$(basename "$input_file" .md)
pdf_name="${base_name}.pdf"
if [[ -n "$output_dir" ]]; then
echo "${output_dir}/${pdf_name}"
else
echo "$(dirname "$input_file")/${pdf_name}"
fi
}
cleanup_latex_temp_files() {
local pdf_path="$1"
local base_path="${pdf_path%.pdf}"
# Remove LaTeX temporary files
rm -f "${base_path}.aux" "${base_path}.log" "${base_path}.out" "${base_path}.tex"
}
convert_file() {
local input_file="$1"
local output_path="$2"
local latex_header="$3"
# Create output directory if needed
local output_parent
output_parent=$(dirname "$output_path")
if [[ ! -d "$output_parent" ]]; then
mkdir -p "$output_parent" || return 1
fi
# Run pandoc conversion
local error_output
error_output=$(mktemp)
if pandoc "$input_file" \
--pdf-engine=xelatex \
--from=markdown+footnotes+gfm_auto_identifiers+pipe_tables+strikeout+task_lists+smart \
--to=pdf \
--output="$output_path" \
--variable geometry:"paperwidth=${RM2_WIDTH},paperheight=${RM2_HEIGHT},left=${MARGIN_LEFT},right=${MARGIN_RIGHT},top=${MARGIN_TOP},bottom=${MARGIN_BOTTOM}" \
--variable mainfont="${body_font}" \
--variable monofont="${code_font}" \
--variable fontsize="${font_size}pt" \
--variable linestretch="${LINE_STRETCH}" \
--include-in-header="$latex_header" \
--highlight-style=tango \
--standalone \
2>"$error_output"; then
rm -f "$error_output"
cleanup_latex_temp_files "$output_path"
return 0
else
if $verbose && [[ -s "$error_output" ]]; then
cat "$error_output" >&2
fi
rm -f "$error_output"
return 1
fi
}
main() {
# Parse command line arguments
while getopts "ro:vf:h" opt; do
case $opt in
r) recursive=true ;;
o) output_dir="$OPTARG" ;;
v) verbose=true ;;
f) font_size="$OPTARG" ;;
h) usage; exit 0 ;;
*) usage; exit 1 ;;
esac
done
shift $((OPTIND - 1))
# Validate font size
if ! [[ "$font_size" =~ ^[0-9]+$ ]] || [[ $font_size -lt 8 ]] || [[ $font_size -gt 24 ]]; then
log_error "Font size must be between 8 and 24"
exit 1
fi
# Check dependencies
if ! check_dependencies; then
exit 1
fi
# Create output directory if specified
if [[ -n "$output_dir" ]]; then
if ! mkdir -p "$output_dir" 2>/dev/null; then
log_error "Cannot create output directory: $output_dir"
exit 1
fi
fi
# Find markdown files
local -a markdown_files=()
while IFS= read -r file; do
[[ -n "$file" ]] && markdown_files+=("$file")
done < <(find_markdown_files ".")
if [[ ${#markdown_files[@]} -eq 0 ]]; then
echo "No markdown files found."
exit 0
fi
if $verbose; then
echo "Found ${#markdown_files[@]} markdown file(s)"
fi
# Create LaTeX header with custom heading sizes
local latex_header
latex_header=$(create_latex_header)
# Process each file
for input_file in "${markdown_files[@]}"; do
local output_path
output_path=$(get_output_path "$input_file")
if $verbose; then
echo "Converting: $input_file -> $(basename "$output_path")"
fi
if convert_file "$input_file" "$output_path" "$latex_header"; then
successful=$((successful + 1))
else
log_error "Failed to convert $input_file"
failed=$((failed + 1))
failed_files+=("$input_file")
fi
done
# Cleanup LaTeX header
rm -f "$latex_header"
# Print summary (matching main.go:73 format)
echo ""
echo "Conversion complete: $successful successful, $failed failed"
# List failed files if any
if [[ $failed -gt 0 ]]; then
echo ""
echo "Failed files:"
for file in "${failed_files[@]}"; do
echo " - $file"
done
exit 1
fi
}
main "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment