Last active
February 9, 2026 17:20
-
-
Save owulveryck/747bd356b9f809d4bac95005e3ad7c10 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 | |
| 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