Markdown Print Styles and PDF Formatting: Complete Guide for Professional Document Output and Print-Ready Publications

Master print styles and PDF formatting for Markdown documents. Learn CSS print media queries, page layout control, typography optimization, and automated PDF generation for professional document output.

Print styles and PDF formatting for Markdown documents enable professional document output that maintains design consistency, typography excellence, and layout precision across digital screens and physical publications. While Markdown provides semantic structure ideal for web display, specialized CSS print media queries and PDF generation techniques ensure optimal presentation when documents transition from screen to paper or professional PDF distribution.

Why Master Print Styles for Markdown?

Professional print formatting provides essential benefits for document publication:

  • Professional Publications: Create print-ready documents that maintain brand standards and typography excellence
  • Consistent Output: Ensure reliable formatting across different PDF generators and printing systems
  • Cost-Effective Publishing: Optimize layouts for efficient paper usage and reduced printing costs
  • Accessibility Compliance: Implement print styles that support high contrast and readable typography
  • Cross-Platform Reliability: Generate consistent results across browsers and PDF conversion tools

Understanding Print CSS Fundamentals

CSS print media queries provide comprehensive control over document appearance in print format:

/* comprehensive-print-styles.css - Professional print formatting */

/* Print-specific CSS variables */
@media print {
  :root {
    --print-font-family: Georgia, 'Times New Roman', serif;
    --print-font-size: 12pt;
    --print-line-height: 1.4;
    --print-margin: 1in;
    --print-header-height: 0.5in;
    --print-footer-height: 0.5in;
    
    --print-primary-color: #000000;
    --print-secondary-color: #333333;
    --print-accent-color: #666666;
    --print-border-color: #000000;
    
    --print-heading-size-h1: 18pt;
    --print-heading-size-h2: 16pt;
    --print-heading-size-h3: 14pt;
    --print-heading-size-h4: 12pt;
  }
  
  /* Page setup and margins */
  @page {
    size: 8.5in 11in; /* US Letter size */
    margin: var(--print-margin);
    
    /* Headers and footers */
    @top-left {
      content: "Document Title";
      font: 10pt var(--print-font-family);
      color: var(--print-secondary-color);
    }
    
    @top-right {
      content: "Page " counter(page);
      font: 10pt var(--print-font-family);
      color: var(--print-secondary-color);
    }
    
    @bottom-center {
      content: "© 2025 Company Name - Confidential";
      font: 8pt var(--print-font-family);
      color: var(--print-accent-color);
    }
  }
  
  /* A4 page size alternative */
  @page :first {
    size: A4;
    margin-top: 2in; /* Extra space for letterhead */
  }
  
  /* Chapter pages */
  @page chapter {
    @bottom-center {
      content: none;
    }
    
    @top-left {
      content: attr(data-chapter-title);
    }
  }
  
  /* Base document styles */
  * {
    -webkit-print-color-adjust: exact !important;
    color-adjust: exact !important;
    print-color-adjust: exact !important;
  }
  
  body {
    font-family: var(--print-font-family);
    font-size: var(--print-font-size);
    line-height: var(--print-line-height);
    color: var(--print-primary-color);
    background: white;
    margin: 0;
    padding: 0;
  }
  
  /* Typography optimization for print */
  h1, h2, h3, h4, h5, h6 {
    font-family: var(--print-font-family);
    font-weight: bold;
    color: var(--print-primary-color);
    page-break-after: avoid;
    page-break-inside: avoid;
    margin-top: 24pt;
    margin-bottom: 12pt;
  }
  
  h1 {
    font-size: var(--print-heading-size-h1);
    page-break-before: always;
    counter-reset: section;
  }
  
  h1:first-child {
    page-break-before: avoid;
    margin-top: 0;
  }
  
  h2 {
    font-size: var(--print-heading-size-h2);
    page-break-before: avoid;
    counter-increment: section;
    counter-reset: subsection;
  }
  
  h2::before {
    content: counter(section) ". ";
  }
  
  h3 {
    font-size: var(--print-heading-size-h3);
    counter-increment: subsection;
  }
  
  h3::before {
    content: counter(section) "." counter(subsection) " ";
  }
  
  h4 {
    font-size: var(--print-heading-size-h4);
  }
  
  /* Paragraph and text formatting */
  p {
    margin: 0 0 12pt 0;
    text-align: justify;
    text-justify: inter-word;
    hyphens: auto;
    -webkit-hyphens: auto;
    -ms-hyphens: auto;
    orphans: 3;
    widows: 3;
  }
  
  p:last-child {
    margin-bottom: 0;
  }
  
  /* List formatting */
  ul, ol {
    margin: 12pt 0;
    padding-left: 24pt;
    page-break-inside: avoid;
  }
  
  li {
    margin-bottom: 6pt;
    page-break-inside: avoid;
    orphans: 2;
    widows: 2;
  }
  
  /* Table formatting */
  table {
    width: 100%;
    border-collapse: collapse;
    margin: 12pt 0;
    page-break-inside: avoid;
    font-size: 10pt;
  }
  
  table, th, td {
    border: 1pt solid var(--print-border-color);
  }
  
  th {
    background: #f0f0f0 !important;
    font-weight: bold;
    padding: 6pt;
    text-align: left;
  }
  
  td {
    padding: 4pt 6pt;
    vertical-align: top;
  }
  
  /* Table headers on new pages */
  thead {
    display: table-header-group;
  }
  
  tfoot {
    display: table-footer-group;
  }
  
  /* Code formatting */
  code {
    font-family: 'Courier New', Courier, monospace;
    font-size: 10pt;
    background: #f5f5f5;
    padding: 1pt 2pt;
    border: 0.5pt solid #cccccc;
  }
  
  pre {
    background: #f8f8f8;
    border: 1pt solid #cccccc;
    padding: 8pt;
    margin: 12pt 0;
    white-space: pre-wrap;
    word-wrap: break-word;
    page-break-inside: avoid;
    font-family: 'Courier New', Courier, monospace;
    font-size: 9pt;
    line-height: 1.2;
  }
  
  pre code {
    background: none;
    border: none;
    padding: 0;
  }
  
  /* Blockquote formatting */
  blockquote {
    border-left: 3pt solid var(--print-border-color);
    margin: 12pt 0 12pt 12pt;
    padding-left: 12pt;
    font-style: italic;
    page-break-inside: avoid;
  }
  
  /* Image handling */
  img {
    max-width: 100%;
    height: auto;
    page-break-inside: avoid;
    page-break-after: avoid;
  }
  
  figure {
    margin: 12pt 0;
    page-break-inside: avoid;
    text-align: center;
  }
  
  figcaption {
    font-size: 10pt;
    font-style: italic;
    margin-top: 6pt;
    text-align: center;
  }
  
  /* Hide web-only elements */
  .no-print,
  .screen-only,
  nav,
  .navigation,
  .sidebar,
  .advertisement,
  .social-share,
  button:not(.print-button),
  input[type="search"],
  .search-form {
    display: none !important;
  }
  
  /* Show print-only elements */
  .print-only {
    display: block !important;
  }
  
  /* URL display for links */
  a:after {
    content: " (" attr(href) ")";
    font-size: 9pt;
    color: var(--print-secondary-color);
    text-decoration: none;
  }
  
  a[href^="#"]:after,
  a[href^="javascript:"]:after,
  a[href=""]:after {
    content: "";
  }
  
  /* Page break controls */
  .page-break-before {
    page-break-before: always;
  }
  
  .page-break-after {
    page-break-after: always;
  }
  
  .page-break-inside-avoid {
    page-break-inside: avoid;
  }
  
  .keep-together {
    page-break-inside: avoid;
    display: block;
  }
  
  /* Chapter and section breaks */
  .chapter {
    page: chapter;
    page-break-before: always;
  }
  
  .section {
    page-break-before: avoid;
  }
  
  /* Table of contents styling */
  .toc {
    page-break-after: always;
  }
  
  .toc-entry {
    display: flex;
    justify-content: space-between;
    margin-bottom: 6pt;
    border-bottom: 1pt dotted var(--print-accent-color);
    padding-bottom: 2pt;
  }
  
  .toc-title {
    flex-grow: 1;
    margin-right: 12pt;
  }
  
  .toc-page {
    flex-shrink: 0;
    font-weight: bold;
  }
  
  /* Print optimization for specific content types */
  .callout,
  .alert,
  .note {
    border: 2pt solid var(--print-border-color);
    padding: 8pt;
    margin: 12pt 0;
    background: #f9f9f9 !important;
    page-break-inside: avoid;
  }
  
  .callout-title {
    font-weight: bold;
    margin-bottom: 6pt;
  }
  
  /* Footnote styling */
  .footnote {
    font-size: 9pt;
    margin-top: 24pt;
    border-top: 1pt solid var(--print-border-color);
    padding-top: 6pt;
  }
  
  .footnote-ref {
    vertical-align: super;
    font-size: 8pt;
    line-height: 0;
  }
  
  /* Index styling */
  .index {
    column-count: 2;
    column-gap: 24pt;
    column-rule: 1pt solid var(--print-accent-color);
  }
  
  .index-entry {
    margin-bottom: 3pt;
    text-indent: -12pt;
    margin-left: 12pt;
  }
  
  .index-subentry {
    margin-left: 12pt;
    font-size: 10pt;
  }
}

/* High-resolution print styles */
@media print and (min-resolution: 300dpi) {
  body {
    font-size: 11pt;
  }
  
  h1 { font-size: 17pt; }
  h2 { font-size: 15pt; }
  h3 { font-size: 13pt; }
  
  code, pre {
    font-size: 8pt;
  }
}

/* Print styles for different paper sizes */
@media print and (width: 210mm) { /* A4 width */
  :root {
    --print-margin: 25mm;
  }
  
  @page {
    size: A4;
    margin: var(--print-margin);
  }
}

@media print and (width: 8.5in) { /* US Letter width */
  :root {
    --print-margin: 1in;
  }
  
  @page {
    size: letter;
    margin: var(--print-margin);
  }
}

Advanced Page Layout and Typography

Professional document formatting with sophisticated layout control:

/* advanced-print-layout.css - Enterprise document formatting */

@media print {
  /* Document structure variables */
  :root {
    --document-title: "Technical Documentation";
    --document-version: "v2.1";
    --document-date: "September 2025";
    --company-name: "Professional Publishing";
    
    --page-number-format: decimal;
    --chapter-number-format: upper-roman;
    --section-number-format: decimal;
  }
  
  /* Multi-page document structure */
  @page cover {
    margin: 0;
    
    @top-left { content: none; }
    @top-right { content: none; }
    @bottom-center { content: none; }
  }
  
  @page toc {
    @top-left {
      content: "Table of Contents";
    }
    
    @bottom-center {
      content: "Page " counter(page, lower-roman);
    }
  }
  
  @page chapter:first {
    margin-top: 2in;
    
    @top-left { content: none; }
  }
  
  @page chapter:left {
    margin-left: 1.5in;
    margin-right: 1in;
    
    @top-left {
      content: var(--document-title);
    }
    
    @top-right { content: none; }
  }
  
  @page chapter:right {
    margin-left: 1in;
    margin-right: 1.5in;
    
    @top-left { content: none; }
    
    @top-right {
      content: "Chapter " counter(chapter, var(--chapter-number-format));
    }
  }
  
  /* Advanced counters */
  body {
    counter-reset: chapter 0 page 1;
  }
  
  .cover-page {
    page: cover;
    page-break-after: always;
  }
  
  .table-of-contents {
    page: toc;
    counter-reset: page 1;
    page-break-after: always;
  }
  
  .chapter {
    page: chapter;
    page-break-before: always;
    counter-increment: chapter;
    counter-reset: section 0 subsection 0;
  }
  
  /* Chapter title pages */
  .chapter-title {
    font-size: 24pt;
    font-weight: bold;
    text-align: center;
    margin-top: 2in;
    margin-bottom: 1in;
    page-break-after: avoid;
  }
  
  .chapter-title::before {
    content: "Chapter " counter(chapter, var(--chapter-number-format));
    display: block;
    font-size: 18pt;
    font-weight: normal;
    margin-bottom: 0.5in;
    color: var(--print-secondary-color);
  }
  
  .chapter-summary {
    font-style: italic;
    text-align: center;
    margin: 1in 2in;
    page-break-after: always;
  }
  
  /* Advanced section numbering */
  .section-header {
    counter-increment: section;
    counter-reset: subsection 0;
  }
  
  .section-header::before {
    content: counter(chapter) "." counter(section) " ";
    font-weight: bold;
  }
  
  .subsection-header {
    counter-increment: subsection;
  }
  
  .subsection-header::before {
    content: counter(chapter) "." counter(section) "." counter(subsection) " ";
  }
  
  /* Professional table formatting */
  .data-table {
    font-size: 9pt;
    margin: 18pt 0;
    width: 100%;
  }
  
  .data-table th {
    background: #e0e0e0 !important;
    font-weight: bold;
    text-align: center;
    padding: 8pt 6pt;
    border: 1pt solid var(--print-border-color);
  }
  
  .data-table td {
    padding: 4pt 6pt;
    border: 0.5pt solid var(--print-accent-color);
    text-align: left;
  }
  
  .data-table .numeric {
    text-align: right;
    font-family: 'Courier New', monospace;
  }
  
  .data-table .centered {
    text-align: center;
  }
  
  /* Table captions and numbering */
  .table-caption {
    font-weight: bold;
    margin-bottom: 6pt;
    text-align: center;
    counter-increment: table;
  }
  
  .table-caption::before {
    content: "Table " counter(chapter) "-" counter(table) ": ";
  }
  
  /* Figure numbering and captions */
  .figure {
    counter-increment: figure;
    text-align: center;
    page-break-inside: avoid;
    margin: 18pt 0;
  }
  
  .figure-caption {
    font-style: italic;
    font-size: 10pt;
    margin-top: 6pt;
    counter-increment: figure;
  }
  
  .figure-caption::before {
    content: "Figure " counter(chapter) "-" counter(figure) ": ";
    font-weight: bold;
  }
  
  /* Code listing formatting */
  .code-listing {
    page-break-inside: avoid;
    counter-increment: listing;
  }
  
  .code-listing-title {
    font-weight: bold;
    margin-bottom: 6pt;
    border-bottom: 1pt solid var(--print-accent-color);
    padding-bottom: 3pt;
  }
  
  .code-listing-title::before {
    content: "Listing " counter(chapter) "-" counter(listing) ": ";
  }
  
  .code-listing pre {
    margin-top: 0;
    border: 1pt solid var(--print-border-color);
    background: #f8f8f8 !important;
  }
  
  /* Cross-references */
  .ref-chapter::before {
    content: "Chapter " target-counter(attr(href), chapter);
  }
  
  .ref-section::before {
    content: "Section " target-counter(attr(href), chapter) "." target-counter(attr(href), section);
  }
  
  .ref-page::after {
    content: " (page " target-counter(attr(href), page) ")";
    font-style: italic;
    color: var(--print-secondary-color);
  }
  
  /* Appendix formatting */
  .appendix {
    page-break-before: always;
    counter-reset: section 0;
    counter-increment: appendix;
  }
  
  .appendix-title::before {
    content: "Appendix " counter(appendix, upper-alpha) ": ";
  }
  
  /* Glossary and index */
  .glossary-term {
    font-weight: bold;
    text-indent: -18pt;
    margin-left: 18pt;
    margin-bottom: 6pt;
  }
  
  .glossary-definition {
    margin-bottom: 12pt;
    margin-left: 18pt;
  }
  
  /* Bibliography and references */
  .bibliography {
    page-break-before: always;
  }
  
  .reference {
    text-indent: -24pt;
    margin-left: 24pt;
    margin-bottom: 12pt;
    counter-increment: reference;
  }
  
  .reference::before {
    content: "[" counter(reference) "] ";
    font-weight: bold;
  }
  
  /* Mathematical equations */
  .equation {
    text-align: center;
    margin: 18pt 0;
    counter-increment: equation;
  }
  
  .equation::after {
    content: " (" counter(chapter) "." counter(equation) ")";
    float: right;
    margin-right: 12pt;
  }
  
  /* Warning and note boxes */
  .warning-box {
    border: 2pt solid #cc0000 !important;
    background: #ffe6e6 !important;
    padding: 12pt;
    margin: 18pt 0;
    page-break-inside: avoid;
  }
  
  .warning-box::before {
    content: "⚠ WARNING: ";
    font-weight: bold;
    color: #cc0000;
  }
  
  .note-box {
    border: 1pt solid var(--print-accent-color);
    background: #f0f0f0 !important;
    padding: 8pt;
    margin: 12pt 0;
    font-size: 10pt;
  }
  
  .note-box::before {
    content: "Note: ";
    font-weight: bold;
  }
  
  /* Signature lines */
  .signature-line {
    border-bottom: 1pt solid var(--print-border-color);
    width: 3in;
    height: 24pt;
    margin: 24pt 0 6pt 0;
    display: inline-block;
  }
  
  .signature-label {
    font-size: 10pt;
    color: var(--print-secondary-color);
  }
  
  /* Legal document formatting */
  .legal-section {
    margin-left: 0.5in;
    text-indent: -0.5in;
  }
  
  .legal-subsection {
    margin-left: 1in;
    text-indent: -0.5in;
  }
  
  .legal-paragraph {
    margin-left: 1.5in;
    text-indent: -0.5in;
  }
}

PDF Generation and Automation

Command-Line PDF Generation

Automated PDF generation from Markdown using various tools:

#!/bin/bash
# markdown-to-pdf-generator.sh - Professional PDF generation script

# Configuration variables
INPUT_DIR="./markdown-docs"
OUTPUT_DIR="./pdf-output"
STYLES_DIR="./print-styles"
TEMPLATE_DIR="./pdf-templates"

# Pandoc-based PDF generation
generate_pdf_pandoc() {
    local input_file="$1"
    local output_file="$2"
    local template="${3:-default}"
    
    echo "Generating PDF with Pandoc: $input_file -> $output_file"
    
    pandoc "$input_file" \
        --pdf-engine=xelatex \
        --template="$TEMPLATE_DIR/${template}.latex" \
        --css="$STYLES_DIR/print-styles.css" \
        --metadata-file="$TEMPLATE_DIR/metadata.yaml" \
        --table-of-contents \
        --toc-depth=3 \
        --number-sections \
        --highlight-style=tango \
        --variable geometry:margin=1in \
        --variable fontsize:12pt \
        --variable documentclass:article \
        --variable papersize:letter \
        --filter pandoc-crossref \
        --filter pandoc-citeproc \
        --output "$output_file"
}

# Puppeteer-based PDF generation
generate_pdf_puppeteer() {
    local input_file="$1"
    local output_file="$2"
    
    echo "Generating PDF with Puppeteer: $input_file -> $output_file"
    
    node -e "
const puppeteer = require('puppeteer');
const markdown = require('markdown-it')();
const fs = require('fs');

(async () => {
  const browser = await puppeteer.launch({
    headless: true,
    args: ['--no-sandbox', '--disable-setuid-sandbox']
  });
  
  const page = await browser.newPage();
  
  // Read and convert Markdown
  const markdownContent = fs.readFileSync('$input_file', 'utf8');
  const htmlContent = markdown.render(markdownContent);
  
  // Read CSS styles
  const printStyles = fs.readFileSync('$STYLES_DIR/comprehensive-print-styles.css', 'utf8');
  const layoutStyles = fs.readFileSync('$STYLES_DIR/advanced-print-layout.css', 'utf8');
  
  // Create complete HTML document
  const fullHtml = \`
<!DOCTYPE html>
<html>
<head>
    <meta charset='utf-8'>
    <title>Document</title>
    <style>
        \${printStyles}
        \${layoutStyles}
        
        @media screen {
            body { 
                max-width: 8.5in; 
                margin: 0 auto; 
                padding: 1in;
                background: white;
                box-shadow: 0 0 10px rgba(0,0,0,0.1);
            }
        }
    </style>
</head>
<body class='typography-enhanced'>
    \${htmlContent}
</body>
</html>\`;
  
  await page.setContent(fullHtml, { waitUntil: 'networkidle2' });
  
  await page.pdf({
    path: '$output_file',
    format: 'Letter',
    margin: {
      top: '1in',
      right: '1in',
      bottom: '1in',
      left: '1in'
    },
    printBackground: true,
    preferCSSPageSize: true,
    displayHeaderFooter: true,
    headerTemplate: \`
      <div style='font-size: 10px; width: 100%; text-align: center; color: #666;'>
        <span class='title'></span>
      </div>\`,
    footerTemplate: \`
      <div style='font-size: 10px; width: 100%; text-align: center; color: #666;'>
        Page <span class='pageNumber'></span> of <span class='totalPages'></span>
      </div>\`
  });
  
  await browser.close();
  console.log('PDF generated successfully');
})();
" 2>/dev/null
}

# WeasyPrint-based PDF generation
generate_pdf_weasyprint() {
    local input_file="$1"
    local output_file="$2"
    
    echo "Generating PDF with WeasyPrint: $input_file -> $output_file"
    
    # Convert Markdown to HTML first
    local html_file="${input_file%.*}.html"
    
    python3 -c "
import markdown
from weasyprint import HTML, CSS
from weasyprint.text.fonts import FontConfiguration

# Read Markdown file
with open('$input_file', 'r', encoding='utf-8') as f:
    md_content = f.read()

# Convert to HTML
md = markdown.Markdown(extensions=[
    'toc',
    'tables', 
    'fenced_code',
    'codehilite',
    'attr_list'
])
html_content = md.convert(md_content)

# Create complete HTML document
full_html = f'''
<!DOCTYPE html>
<html>
<head>
    <meta charset='utf-8'>
    <title>Document</title>
    <link rel='stylesheet' href='$STYLES_DIR/comprehensive-print-styles.css'>
    <link rel='stylesheet' href='$STYLES_DIR/advanced-print-layout.css'>
</head>
<body>
    {html_content}
</body>
</html>
'''

# Generate PDF
font_config = FontConfiguration()
html_doc = HTML(string=full_html, base_url='$PWD/')
css_files = [
    CSS('$STYLES_DIR/comprehensive-print-styles.css'),
    CSS('$STYLES_DIR/advanced-print-layout.css')
]

html_doc.write_pdf(
    '$output_file',
    stylesheets=css_files,
    font_config=font_config
)

print('PDF generated successfully with WeasyPrint')
"
}

# Prince XML-based PDF generation (commercial)
generate_pdf_prince() {
    local input_file="$1"
    local output_file="$2"
    
    echo "Generating PDF with Prince XML: $input_file -> $output_file"
    
    # Convert Markdown to HTML
    local html_file="${input_file%.*}.html"
    markdown "$input_file" > "$html_file"
    
    # Generate PDF with Prince
    prince \
        --css="$STYLES_DIR/comprehensive-print-styles.css" \
        --css="$STYLES_DIR/advanced-print-layout.css" \
        --media=print \
        --pdf-profile="PDF/A-1a" \
        --pdf-title="Professional Document" \
        --pdf-author="Document Generator" \
        --pdf-keywords="Markdown, PDF, Professional" \
        --pdf-creator="Prince XML" \
        "$html_file" \
        --output="$output_file"
    
    # Clean up temporary HTML
    rm "$html_file"
}

# Batch processing function
process_markdown_files() {
    local method="${1:-pandoc}"
    local template="${2:-default}"
    
    # Create output directory
    mkdir -p "$OUTPUT_DIR"
    
    # Process all Markdown files
    find "$INPUT_DIR" -name "*.md" -type f | while read -r input_file; do
        local filename=$(basename "$input_file" .md)
        local output_file="$OUTPUT_DIR/${filename}.pdf"
        
        case "$method" in
            "pandoc")
                generate_pdf_pandoc "$input_file" "$output_file" "$template"
                ;;
            "puppeteer")
                generate_pdf_puppeteer "$input_file" "$output_file"
                ;;
            "weasyprint")
                generate_pdf_weasyprint "$input_file" "$output_file"
                ;;
            "prince")
                generate_pdf_prince "$input_file" "$output_file"
                ;;
            *)
                echo "Unknown method: $method"
                echo "Available methods: pandoc, puppeteer, weasyprint, prince"
                return 1
                ;;
        esac
        
        # Validate PDF generation
        if [ -f "$output_file" ]; then
            local file_size=$(stat -f%z "$output_file" 2>/dev/null || stat -c%s "$output_file" 2>/dev/null)
            if [ "$file_size" -gt 1024 ]; then
                echo "✓ Successfully generated: $output_file ($file_size bytes)"
            else
                echo "✗ Failed to generate valid PDF: $output_file"
            fi
        else
            echo "✗ PDF generation failed: $output_file"
        fi
    done
}

# Quality assurance checks
validate_pdf_output() {
    local pdf_file="$1"
    
    echo "Validating PDF: $pdf_file"
    
    # Check file exists and has content
    if [ ! -f "$pdf_file" ]; then
        echo "✗ PDF file does not exist"
        return 1
    fi
    
    local file_size=$(stat -f%z "$pdf_file" 2>/dev/null || stat -c%s "$pdf_file" 2>/dev/null)
    if [ "$file_size" -lt 1024 ]; then
        echo "✗ PDF file too small ($file_size bytes)"
        return 1
    fi
    
    # Validate PDF structure
    if command -v pdfinfo >/dev/null 2>&1; then
        local page_count=$(pdfinfo "$pdf_file" 2>/dev/null | grep "Pages:" | awk '{print $2}')
        if [ "$page_count" -gt 0 ]; then
            echo "✓ PDF contains $page_count pages"
        else
            echo "✗ PDF appears to be corrupted"
            return 1
        fi
    fi
    
    # Check for text content
    if command -v pdftotext >/dev/null 2>&1; then
        local text_length=$(pdftotext "$pdf_file" - 2>/dev/null | wc -c)
        if [ "$text_length" -gt 100 ]; then
            echo "✓ PDF contains text content ($text_length characters)"
        else
            echo "⚠ PDF may not contain extractable text"
        fi
    fi
    
    echo "✓ PDF validation completed"
}

# Main execution
main() {
    local method="${1:-pandoc}"
    local template="${2:-default}"
    local validate="${3:-false}"
    
    echo "=== Markdown to PDF Generator ==="
    echo "Method: $method"
    echo "Template: $template"
    echo "Input Directory: $INPUT_DIR"
    echo "Output Directory: $OUTPUT_DIR"
    echo "=================================="
    
    # Check dependencies
    case "$method" in
        "pandoc")
            if ! command -v pandoc >/dev/null 2>&1; then
                echo "Error: Pandoc is not installed"
                exit 1
            fi
            ;;
        "puppeteer")
            if ! command -v node >/dev/null 2>&1; then
                echo "Error: Node.js is not installed"
                exit 1
            fi
            ;;
        "weasyprint")
            if ! command -v python3 >/dev/null 2>&1; then
                echo "Error: Python 3 is not installed"
                exit 1
            fi
            ;;
        "prince")
            if ! command -v prince >/dev/null 2>&1; then
                echo "Error: Prince XML is not installed"
                exit 1
            fi
            ;;
    esac
    
    # Process files
    process_markdown_files "$method" "$template"
    
    # Validate output if requested
    if [ "$validate" = "true" ]; then
        echo "=== Validating Generated PDFs ==="
        find "$OUTPUT_DIR" -name "*.pdf" -type f | while read -r pdf_file; do
            validate_pdf_output "$pdf_file"
        done
    fi
    
    echo "=== Generation Complete ==="
}

# Execute if script is run directly
if [ "${BASH_SOURCE[0]}" = "${0}" ]; then
    main "$@"
fi

GitHub Actions Workflow for PDF Generation

Automated PDF generation in CI/CD pipelines:


# .github/workflows/generate-pdfs.yml
name: Generate PDF Documentation

on:
  push:
    branches: [main]
    paths: ['docs/**/*.md']
  pull_request:
    branches: [main]
    paths: ['docs/**/*.md']
  workflow_dispatch:
    inputs:
      pdf_method:
        description: 'PDF generation method'
        required: true
        default: 'puppeteer'
        type: choice
        options:
          - 'puppeteer'
          - 'pandoc'
          - 'weasyprint'

jobs:
  generate-pdfs:
    runs-on: ubuntu-latest
    
    steps:
    - name: Checkout repository
      uses: actions/checkout@v4
      
    - name: Setup Node.js
      uses: actions/setup-node@v4
      with:
        node-version: '18'
        cache: 'npm'
        
    - name: Install Node.js dependencies
      run: |
        npm install -g puppeteer markdown-it
        npm install puppeteer markdown-it
        
    - name: Setup Python
      uses: actions/setup-python@v4
      with:
        python-version: '3.11'
        cache: 'pip'
        
    - name: Install Python dependencies
      run: |
        pip install weasyprint markdown beautifulsoup4
        
    - name: Install Pandoc
      uses: r-lib/actions/setup-pandoc@v2
      with:
        pandoc-version: 'latest'
        
    - name: Install LaTeX for Pandoc
      run: |
        sudo apt-get update
        sudo apt-get install -y texlive-latex-recommended \
                               texlive-latex-extra \
                               texlive-fonts-recommended \
                               texlive-xetex
    
    - name: Create output directories
      run: |
        mkdir -p pdf-output
        mkdir -p pdf-reports
        
    - name: Generate PDFs with Puppeteer
      if: github.event.inputs.pdf_method == 'puppeteer' || github.event.inputs.pdf_method == ''
      run: |
        node generate-pdfs.js
        
    - name: Generate PDFs with Pandoc
      if: github.event.inputs.pdf_method == 'pandoc'
      run: |
        for file in docs/**/*.md; do
          filename=$(basename "$file" .md)
          pandoc "$file" \
            --pdf-engine=xelatex \
            --css=styles/print-styles.css \
            --table-of-contents \
            --number-sections \
            --variable geometry:margin=1in \
            --output "pdf-output/${filename}.pdf"
        done
        
    - name: Generate PDFs with WeasyPrint
      if: github.event.inputs.pdf_method == 'weasyprint'
      run: |
        python generate-pdfs-weasyprint.py
        
    - name: Validate PDF files
      run: |
        echo "=== PDF Generation Report ===" > pdf-reports/generation-report.txt
        echo "Generated at: $(date)" >> pdf-reports/generation-report.txt
        echo "Method: ${{ github.event.inputs.pdf_method || 'puppeteer' }}" >> pdf-reports/generation-report.txt
        echo "" >> pdf-reports/generation-report.txt
        
        total_files=0
        successful_files=0
        
        for pdf in pdf-output/*.pdf; do
          if [ -f "$pdf" ]; then
            total_files=$((total_files + 1))
            file_size=$(stat -c%s "$pdf")
            
            if [ "$file_size" -gt 1024 ]; then
              successful_files=$((successful_files + 1))
              echo "✓ $(basename "$pdf"): $file_size bytes" >> pdf-reports/generation-report.txt
            else
              echo "✗ $(basename "$pdf"): Failed ($file_size bytes)" >> pdf-reports/generation-report.txt
            fi
          fi
        done
        
        echo "" >> pdf-reports/generation-report.txt
        echo "Summary: $successful_files/$total_files files generated successfully" >> pdf-reports/generation-report.txt
        
        # Fail if no PDFs were generated successfully
        if [ "$successful_files" -eq 0 ]; then
          echo "Error: No PDFs were generated successfully"
          exit 1
        fi
        
    - name: Upload PDF artifacts
      uses: actions/upload-artifact@v3
      with:
        name: generated-pdfs-${{ github.event.inputs.pdf_method || 'puppeteer' }}
        path: pdf-output/
        retention-days: 30
        
    - name: Upload generation report
      uses: actions/upload-artifact@v3
      with:
        name: pdf-generation-report
        path: pdf-reports/
        retention-days: 7
        
    - name: Comment on PR with results
      if: github.event_name == 'pull_request'
      uses: actions/github-script@v6
      with:
        script: |
          const fs = require('fs');
          const report = fs.readFileSync('pdf-reports/generation-report.txt', 'utf8');
          
          github.rest.issues.createComment({
            issue_number: context.issue.number,
            owner: context.repo.owner,
            repo: context.repo.repo,
            body: `## PDF Generation Report
            
\`\`\`
${report}
\`\`\`

Download the generated PDFs from the workflow artifacts.`
          });

Platform-Specific Implementation

Jekyll PDF Generation Plugin

Jekyll plugin for automated PDF generation:

# _plugins/pdf_generator.rb
require 'puppeteer-ruby'
require 'kramdown'
require 'liquid'

module Jekyll
  class PDFGenerator < Generator
    safe true
    priority :low

    def generate(site)
      @site = site
      @config = site.config['pdf_generator'] || {}
      
      # Skip if PDF generation is disabled
      return unless @config['enabled']
      
      # Create PDF output directory
      pdf_dir = File.join(site.dest, 'pdfs')
      FileUtils.mkdir_p(pdf_dir)
      
      # Generate PDFs for specified collections
      collections_to_process = @config['collections'] || ['posts']
      
      collections_to_process.each do |collection_name|
        collection = site.collections[collection_name]
        next unless collection
        
        collection.docs.each do |doc|
          generate_pdf_for_document(doc, pdf_dir)
        end
      end
      
      # Generate combined PDFs if configured
      generate_combined_pdfs(pdf_dir) if @config['combined_pdfs']
    end
    
    private
    
    def generate_pdf_for_document(doc, output_dir)
      return unless should_generate_pdf?(doc)
      
      begin
        Jekyll.logger.info "PDF Generator:", "Processing #{doc.relative_path}"
        
        # Prepare HTML content
        html_content = prepare_html_content(doc)
        
        # Generate PDF filename
        pdf_filename = "#{doc.basename_without_ext}.pdf"
        pdf_path = File.join(output_dir, pdf_filename)
        
        # Generate PDF using Puppeteer
        generate_pdf_with_puppeteer(html_content, pdf_path, doc)
        
        Jekyll.logger.info "PDF Generator:", "Generated #{pdf_filename}"
        
      rescue StandardError => e
        Jekyll.logger.error "PDF Generator:", "Failed to generate PDF for #{doc.relative_path}: #{e.message}"
      end
    end
    
    def should_generate_pdf?(doc)
      # Check if document should be converted to PDF
      return false if doc.data['pdf'] == false
      return true if doc.data['pdf'] == true
      
      # Default behavior based on configuration
      @config['auto_generate'] != false
    end
    
    def prepare_html_content(doc)
      # Get the rendered content
      content = doc.content
      
      # Apply PDF-specific transformations
      content = transform_content_for_pdf(content, doc)
      
      # Create complete HTML document
      template = get_pdf_template
      
      # Prepare template variables
      template_vars = {
        'title' => doc.data['title'] || doc.basename_without_ext,
        'author' => doc.data['author'] || @site.config['author'],
        'date' => doc.data['date']&.strftime('%B %d, %Y') || '',
        'description' => doc.data['description'] || '',
        'content' => content,
        'print_styles' => get_print_styles,
        'site' => @site.config
      }
      
      # Render template
      liquid_template = Liquid::Template.parse(template)
      liquid_template.render(template_vars)
    end
    
    def transform_content_for_pdf(content, doc)
      # Convert relative URLs to absolute
      base_url = @site.config['url'] || ''
      content = content.gsub(/href="\//, "href=\"#{base_url}/")
      content = content.gsub(/src="\//, "src=\"#{base_url}/")
      
      # Add print-specific classes
      content = content.gsub(/<h([1-6])/, '<h\1 class="pdf-heading"')
      content = content.gsub(/<table/, '<table class="pdf-table"')
      content = content.gsub(/<pre/, '<pre class="pdf-code"')
      
      # Handle page breaks
      content = content.gsub(/<!-- page-break -->/, '<div class="page-break-before"></div>')
      
      content
    end
    
    def get_pdf_template
      template_path = @config['template'] || File.join(@site.source, '_layouts', 'pdf.html')
      
      if File.exist?(template_path)
        File.read(template_path)
      else
        default_pdf_template
      end
    end
    
    def default_pdf_template
      <<~HTML
        <!DOCTYPE html>
        <html>
        <head>
            <meta charset="utf-8">
            <title>{{ title }}</title>
            <meta name="author" content="{{ author }}">
            <meta name="description" content="{{ description }}">
            <style>
                {{ print_styles }}
            </style>
        </head>
        <body>
            <header class="pdf-header">
                <h1>{{ title }}</h1>
                {% if author %}
                <p class="author">By {{ author }}</p>
                {% endif %}
                {% if date %}
                <p class="date">{{ date }}</p>
                {% endif %}
            </header>
            
            <main class="pdf-content">
                {{ content }}
            </main>
            
            <footer class="pdf-footer">
                <p>Generated from {{ site.title | default: "Jekyll Site" }}</p>
            </footer>
        </body>
        </html>
      HTML
    end
    
    def get_print_styles
      styles_path = File.join(@site.source, 'assets', 'css', 'print.css')
      
      if File.exist?(styles_path)
        File.read(styles_path)
      else
        # Return basic print styles
        basic_print_styles
      end
    end
    
    def basic_print_styles
      <<~CSS
        @media print {
          body {
            font-family: Georgia, serif;
            font-size: 12pt;
            line-height: 1.4;
            color: #000;
            background: white;
          }
          
          .pdf-header {
            text-align: center;
            margin-bottom: 2em;
            page-break-after: avoid;
          }
          
          .pdf-header h1 {
            font-size: 18pt;
            margin-bottom: 0.5em;
          }
          
          .author, .date {
            font-size: 10pt;
            color: #666;
          }
          
          .pdf-heading {
            page-break-after: avoid;
            margin-top: 1.5em;
          }
          
          .pdf-table {
            border-collapse: collapse;
            width: 100%;
            margin: 1em 0;
          }
          
          .pdf-table th, .pdf-table td {
            border: 1px solid #000;
            padding: 6pt;
          }
          
          .pdf-code {
            font-family: Courier, monospace;
            font-size: 10pt;
            background: #f5f5f5;
            border: 1px solid #ccc;
            padding: 8pt;
            page-break-inside: avoid;
          }
          
          .page-break-before {
            page-break-before: always;
          }
          
          .pdf-footer {
            margin-top: 2em;
            padding-top: 1em;
            border-top: 1px solid #ccc;
            font-size: 10pt;
            text-align: center;
            color: #666;
          }
        }
      CSS
    end
    
    def generate_pdf_with_puppeteer(html_content, pdf_path, doc)
      # PDF generation options from configuration
      pdf_options = @config['pdf_options'] || {}
      
      default_options = {
        format: 'Letter',
        margin: {
          top: '1in',
          right: '1in',
          bottom: '1in',
          left: '1in'
        },
        print_background: true,
        prefer_css_page_size: true
      }
      
      options = default_options.merge(pdf_options)
      
      # Use Puppeteer to generate PDF
      Puppeteer.launch(headless: true) do |browser|
        page = browser.new_page
        page.set_content(html_content, wait_until: 'networkidle2')
        page.pdf(path: pdf_path, **options)
      end
    end
    
    def generate_combined_pdfs(output_dir)
      combined_configs = @config['combined_pdfs']
      
      combined_configs.each do |config|
        next unless config['name'] && config['collections']
        
        Jekyll.logger.info "PDF Generator:", "Generating combined PDF: #{config['name']}"
        
        # Collect documents
        documents = []
        config['collections'].each do |collection_name|
          collection = @site.collections[collection_name]
          next unless collection
          
          collection_docs = collection.docs.select { |doc| should_generate_pdf?(doc) }
          collection_docs = collection_docs.sort_by { |doc| doc.data['date'] || Time.now } if config['sort_by_date']
          documents.concat(collection_docs)
        end
        
        # Generate combined HTML
        combined_content = generate_combined_content(documents, config)
        
        # Generate combined PDF
        pdf_filename = "#{config['name']}.pdf"
        pdf_path = File.join(output_dir, pdf_filename)
        
        generate_pdf_with_puppeteer(combined_content, pdf_path, nil)
        
        Jekyll.logger.info "PDF Generator:", "Generated combined PDF: #{pdf_filename}"
      end
    end
    
    def generate_combined_content(documents, config)
      content_parts = []
      
      # Add title page if configured
      if config['title_page']
        content_parts << generate_title_page(config['title_page'])
      end
      
      # Add table of contents if configured
      if config['table_of_contents']
        content_parts << generate_table_of_contents(documents)
      end
      
      # Add documents
      documents.each_with_index do |doc, index|
        # Add page break between documents
        content_parts << '<div class="page-break-before"></div>' if index > 0
        
        # Add document content
        doc_content = prepare_html_content(doc)
        content_parts << extract_body_content(doc_content)
      end
      
      # Wrap in complete HTML document
      template_vars = {
        'title' => config['title'] || config['name'],
        'author' => config['author'] || @site.config['author'],
        'date' => Time.now.strftime('%B %d, %Y'),
        'content' => content_parts.join("\n"),
        'print_styles' => get_print_styles,
        'site' => @site.config
      }
      
      liquid_template = Liquid::Template.parse(get_pdf_template)
      liquid_template.render(template_vars)
    end
    
    def generate_title_page(title_config)
      <<~HTML
        <div class="title-page">
          <h1>#{title_config['title']}</h1>
          #{title_config['subtitle'] ? "<h2>#{title_config['subtitle']}</h2>" : ''}
          #{title_config['author'] ? "<p class='author'>#{title_config['author']}</p>" : ''}
          #{title_config['date'] ? "<p class='date'>#{title_config['date']}</p>" : ''}
        </div>
        <div class="page-break-after"></div>
      HTML
    end
    
    def generate_table_of_contents(documents)
      toc_entries = documents.map.with_index do |doc, index|
        title = doc.data['title'] || doc.basename_without_ext
        "<div class='toc-entry'><span class='toc-title'>#{title}</span><span class='toc-page'>#{index + 1}</span></div>"
      end.join("\n")
      
      <<~HTML
        <div class="table-of-contents">
          <h2>Table of Contents</h2>
          #{toc_entries}
        </div>
        <div class="page-break-after"></div>
      HTML
    end
    
    def extract_body_content(html)
      # Extract content from <main> or <body> tags
      match = html.match(/<main[^>]*>(.*?)<\/main>/m) || html.match(/<body[^>]*>(.*?)<\/body>/m)
      match ? match[1] : html
    end
  end
end

Hugo PDF Shortcodes

Hugo shortcodes for PDF-optimized content:

<!-- layouts/shortcodes/pdf-chapter.html -->
{{ $title := .Get "title" | default .Page.Title }}
{{ $number := .Get "number" }}

<div class="chapter" data-chapter-title="{{ $title }}">
  {{ if $number }}
    <h1 class="chapter-title" data-chapter-number="{{ $number }}">{{ $title }}</h1>
  {{ else }}
    <h1 class="chapter-title">{{ $title }}</h1>
  {{ end }}
  
  {{ if .Get "summary" }}
    <div class="chapter-summary">{{ .Get "summary" | markdownify }}</div>
  {{ end }}
  
  <div class="chapter-content">
    {{ .Inner | markdownify }}
  </div>
</div>
<!-- layouts/shortcodes/pdf-table.html -->
{{ $caption := .Get "caption" }}
{{ $class := .Get "class" | default "data-table" }}

<div class="table-container">
  {{ if $caption }}
    <div class="table-caption">{{ $caption }}</div>
  {{ end }}
  
  <table class="{{ $class }}">
    {{ .Inner }}
  </table>
</div>
<!-- layouts/shortcodes/pdf-figure.html -->
{{ $src := .Get "src" }}
{{ $caption := .Get "caption" }}
{{ $alt := .Get "alt" | default $caption }}

<figure class="figure">
  <img src="{{ $src | relURL }}" alt="{{ $alt }}" class="figure-image">
  {{ if $caption }}
    <figcaption class="figure-caption">{{ $caption | markdownify }}</figcaption>
  {{ end }}
</figure>
<!-- layouts/shortcodes/pdf-code.html -->
{{ $language := .Get "lang" | default "text" }}
{{ $title := .Get "title" }}
{{ $file := .Get "file" }}

<div class="code-listing">
  {{ if $title }}
    <div class="code-listing-title">{{ $title }}</div>
  {{ end }}
  
  <pre class="code-listing-content" data-language="{{ $language }}"><code class="language-{{ $language }}">{{- if $file -}}{{ readFile $file }}{{- else -}}{{ .Inner }}{{- end -}}</code></pre>
</div>
<!-- layouts/shortcodes/pdf-pagebreak.html -->
<div class="page-break-before"></div>
<!-- layouts/shortcodes/pdf-note.html -->
{{ $type := .Get "type" | default "note" }}
{{ $title := .Get "title" }}

<div class="{{ $type }}-box">
  {{ if $title }}
    <div class="note-title">{{ $title }}</div>
  {{ end }}
  {{ .Inner | markdownify }}
</div>

Hugo Content Usage:


{{< pdf-chapter title="Introduction" number="1" summary="Overview of the system architecture and design principles." >}}

This chapter provides a comprehensive introduction to the system design.

## System Overview

The architecture follows a microservices pattern with the following components:

{{< pdf-table caption="System Components Overview" >}}
| Component | Purpose | Technology |
|-----------|---------|------------|
| API Gateway | Request routing | Node.js |
| User Service | Authentication | Python |
| Data Service | Storage | PostgreSQL |
{{< /pdf-table >}}

{{< pdf-figure src="/images/architecture-diagram.png" caption="System Architecture Diagram showing the relationship between components" >}}

{{< pdf-code lang="javascript" title="API Gateway Configuration" >}}
const express = require('express');
const app = express();

app.use('/api/users', userRoutes);
app.use('/api/data', dataRoutes);

app.listen(3000, () => {
  console.log('API Gateway running on port 3000');
});
{{< /pdf-code >}}

{{< pdf-note type="warning" title="Security Consideration" >}}
Always validate and sanitize input data before processing to prevent security vulnerabilities.
{{< /pdf-note >}}

{{< /pdf-chapter >}}

{{< pdf-pagebreak >}}

{{< pdf-chapter title="Implementation Details" number="2" >}}

This chapter covers the detailed implementation of each component.

{{< /pdf-chapter >}}

Integration with Modern Workflows

Print styles and PDF formatting integrate seamlessly with comprehensive Markdown workflows. When combined with document versioning and change tracking, professional PDF output ensures version-controlled document distribution that maintains formatting consistency across revisions while enabling collaborative development of complex publications.

For comprehensive documentation systems requiring both digital and print delivery, PDF formatting works effectively with custom CSS classes and styling to create design systems that translate seamlessly from web presentation to professional print output while maintaining brand identity and visual hierarchy.

When managing enterprise documentation workflows, print styles complement blockquote advanced styling to create professional publications that effectively communicate complex information through both textual content and sophisticated visual presentation that works across digital screens and physical print media.

Troubleshooting Common PDF Issues

Font and Typography Problems

Problem: Fonts not rendering correctly in PDF output or missing special characters

Solutions:

/* Font fallback and embedding strategies */
@media print {
  /* Specify complete font stacks */
  body {
    font-family: Georgia, 'Times New Roman', Times, serif;
  }
  
  code, pre {
    font-family: 'Courier New', Courier, 'Lucida Console', monospace;
  }
  
  /* Ensure Unicode support */
  * {
    font-variant-numeric: normal;
    font-feature-settings: normal;
  }
  
  /* Fix for missing characters */
  .unicode-fix {
    font-family: 'Arial Unicode MS', Arial, sans-serif;
  }
  
  /* Web font fallbacks */
  @font-face {
    font-family: 'CustomFont';
    src: local('Georgia'), local('Times New Roman');
    font-display: block;
  }
}

PDF Generation Configuration:

// Puppeteer font handling
const pdfOptions = {
  format: 'Letter',
  margin: { top: '1in', right: '1in', bottom: '1in', left: '1in' },
  printBackground: true,
  preferCSSPageSize: true,
  
  // Font loading settings
  waitUntil: ['networkidle2', 'load'],
  timeout: 60000,
  
  // Ensure fonts are loaded
  args: [
    '--no-sandbox',
    '--disable-setuid-sandbox',
    '--disable-font-subpixel-positioning',
    '--enable-font-antialiasing'
  ]
};

Page Break and Layout Issues

Problem: Content breaking awkwardly across pages or overlapping page margins

Solutions:

@media print {
  /* Better page break control */
  .keep-together {
    page-break-inside: avoid;
    break-inside: avoid;
  }
  
  .force-page-break {
    page-break-before: always;
    break-before: page;
  }
  
  .avoid-page-break-after {
    page-break-after: avoid;
    break-after: avoid;
  }
  
  /* Fix for widows and orphans */
  p, li, dd {
    orphans: 3;
    widows: 3;
  }
  
  /* Heading page break control */
  h1, h2, h3, h4 {
    page-break-after: avoid;
    break-after: avoid;
    page-break-inside: avoid;
    break-inside: avoid;
  }
  
  /* List handling */
  ul, ol {
    page-break-inside: avoid;
    break-inside: avoid;
  }
  
  /* Table handling */
  table {
    page-break-inside: avoid;
    break-inside: avoid;
  }
  
  thead {
    display: table-header-group;
  }
  
  tr {
    page-break-inside: avoid;
    break-inside: avoid;
  }
  
  /* Image handling */
  img {
    page-break-inside: avoid;
    break-inside: avoid;
    page-break-after: avoid;
    break-after: avoid;
    max-width: 100%;
    height: auto;
  }
  
  /* Code block handling */
  pre {
    page-break-inside: avoid;
    break-inside: avoid;
    white-space: pre-wrap;
    word-wrap: break-word;
    overflow-wrap: break-word;
  }
}

Color and Background Issues

Problem: Colors or backgrounds not appearing in PDF output

Solutions:

@media print {
  /* Force color and background printing */
  * {
    -webkit-print-color-adjust: exact !important;
    color-adjust: exact !important;
    print-color-adjust: exact !important;
  }
  
  /* Ensure backgrounds are printed */
  .highlight-box {
    background: #f0f8ff !important;
    -webkit-print-color-adjust: exact;
  }
  
  /* Convert colors to print-friendly versions */
  .screen-blue {
    color: #003366 !important; /* Darker for print */
    background: #e6f3ff !important; /* Lighter for print */
  }
  
  /* High contrast alternatives */
  @media print and (prefers-contrast: high) {
    .color-element {
      background: white !important;
      color: black !important;
      border: 2px solid black !important;
    }
  }
}

Puppeteer Configuration:

const page = await browser.newPage();

// Enable background graphics
await page.emulateMediaType('print');
await page.evaluateOnNewDocument(() => {
  document.documentElement.style.setProperty('-webkit-print-color-adjust', 'exact');
});

await page.pdf({
  path: outputPath,
  format: 'Letter',
  printBackground: true, // Critical for backgrounds
  preferCSSPageSize: true,
  margin: { top: '1in', right: '1in', bottom: '1in', left: '1in' }
});

Conclusion

Print styles and PDF formatting for Markdown documents transform digital content into professional publications that maintain design excellence, typography precision, and layout consistency across different output formats and distribution channels. By mastering CSS print media queries, automated PDF generation techniques, and platform-specific implementations, content creators can produce high-quality documents that serve both digital and physical publication requirements.

The key to successful print formatting lies in understanding the fundamental differences between screen and print media, implementing robust CSS that handles page breaks gracefully, and choosing appropriate PDF generation tools that meet specific project requirements. Whether you're creating technical documentation, academic papers, or business reports, the techniques covered in this guide provide the foundation for professional document output that maintains quality across all distribution formats.

Remember to test your print styles across different PDF generators and browsers, validate output quality with various paper sizes and printer settings, and implement automated workflows that ensure consistent results across large document sets. With proper print style implementation, your Markdown content becomes a powerful platform for professional publication that bridges the gap between digital creation and physical distribution while maintaining the simplicity and portability that makes Markdown ideal for modern content workflows.