wkhtmltopdf with Patched Qt: The Complete Developer's Guide
PHPLaravelPDF

wkhtmltopdf with Patched Qt: The Complete Developer's Guide

Ihor (Harry) ChyshkalaIhor (Harry) Chyshkala

Introduction

PDF generation from HTML remains one of the core challenges in web development. In the PHP world, wkhtmltopdf stands as one of the most popular tools for this task — a command-line utility that uses the WebKit engine to render HTML into PDF. However, not all wkhtmltopdf versions are created equal. In this comprehensive article, we'll explore the critical differences between regular and "patched" versions, why the latter is essential for production applications, and how to properly install and configure it.

What is wkhtmltopdf?

wkhtmltopdf is an open-source command-line utility developed for converting HTML and CSS to PDF and various image formats. Created by Jakob Truelsen in 2008, it has since become the de-facto standard for server-side PDF generation in web applications.

Key advantages of wkhtmltopdf:

  • Accurate rendering: Uses the WebKit engine, the same as in browsers
  • CSS support: Full CSS support, including media queries
  • JavaScript: Ability to execute JavaScript before PDF generation
  • Headers and footers: Built-in header/footer support
  • Stability: Time-tested solution
  • Integration: Easy integration with PHP through libraries like SnappyPDF

The Problem: Regular vs Patched Qt

Regular (Unpatched) Version

When you install wkhtmltopdf through standard package managers (apt, yum, etc.), you get a version compiled against the system Qt library. This version has significant limitations:

$ sudo apt-get install wkhtmltopdf
$ wkhtmltopdf --version
wkhtmltopdf 0.12.6

$ wkhtmltopdf --print-media-type test.html test.pdf
The switch --print-media-type, is not support using unpatched qt, and will be ignored.

Limitations of the unpatched version:

  1. No print media query support — CSS @media print rules are ignored
  2. Landscape orientation issues — incorrect handling of landscape orientation
  3. Unstable header/footer — positioning and styling problems
  4. Limited CSS3 support — some modern CSS properties don't work
  5. Font issues — incorrect rendering of custom fonts

Patched Qt Version

The "patched" version of wkhtmltopdf comes with a specially modified Qt WebKit that includes numerous print-specific fixes:

$ wkhtmltopdf --version
wkhtmltopdf 0.12.6 (with patched qt)

$ wkhtmltopdf --print-media-type test.html test.pdf
Loading page (1/2)
Printing pages (2/2)
Done

Advantages of the patched version:

  1. Full print media query support — correct handling of CSS for print
  2. Stable landscape orientation — flawless landscape orientation handling
  3. Reliable header/footer — proper positioning and repetition on each page
  4. Modern CSS support — support for CSS3, flexbox, grid
  5. Quality typography — correct font and kerning handling
  6. Precise positioning — pixel-perfect element positioning

Technical Differences

Anatomy of the Patches

The patches in Qt WebKit for wkhtmltopdf include hundreds of changes aimed at improving print functionality:

// Example patch for print media query support
// Original Qt WebKit
bool WebPage::shouldApplyPrintMediaType() const
{
    return false; // Always returned false
}

// Patched version
bool WebPage::shouldApplyPrintMediaType() const
{
    return m_printMediaTypeEnabled; // Considers settings
}

Key improvement areas:

  1. Media Query Engine: Redesigned media query processing mechanism
  2. Page Layout: Improved page layout algorithm
  3. Font Rendering: Optimized font rendering for print
  4. CSS Parser: Extended CSS property support for print
  5. Memory Management: Optimized memory management for large documents

Installing the Patched Version

Ubuntu/Debian

# Remove old version
sudo apt-get remove wkhtmltopdf

# Download patched version
wget https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6.1-3/wkhtmltox_0.12.6.1-3.jammy_amd64.deb

# Install dependencies
sudo apt-get update
sudo apt-get install -y libfontconfig1 libfreetype6 libx11-6 libxext6 libxrender1

# Install package
sudo dpkg -i wkhtmltox_0.12.6.1-3.jammy_amd64.deb
sudo apt-get install -f

CentOS/RHEL

# Remove old version
sudo yum remove wkhtmltopdf

# Download and install
wget https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6.1-3/wkhtmltox-0.12.6.1-3.centos8.x86_64.rpm
sudo yum localinstall wkhtmltox-0.12.6.1-3.centos8.x86_64.rpm

macOS

# With Homebrew
brew install --cask wkhtmltopdf

# Or download from official site
wget https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6.1-3/wkhtmltox-0.12.6.1-3.macos-cocoa.pkg

Docker (Recommended for production)

FROM ubuntu:20.04

RUN apt-get update && apt-get install -y \
    wget fontconfig libfontconfig1 libfreetype6 \
    libx11-6 libxext6 libxrender1 xvfb

RUN wget -O wkhtmltox.deb \
    https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6.1-3/wkhtmltox_0.12.6.1-3.focal_amd64.deb && \
    dpkg -i wkhtmltox.deb && \
    rm wkhtmltox.deb

# Headless wrapper
RUN echo '#!/bin/bash\nxvfb-run -a wkhtmltopdf "$@"' > /usr/local/bin/wkhtmltopdf-headless && \
    chmod +x /usr/local/bin/wkhtmltopdf-headless

Practical Examples

CSS for print with patched version

/* Basic print styles */
@page {
    size: A4 portrait;
    margin: 2cm 1.5cm;
}

@page:first {
    margin-top: 5cm; /* More space for header */
}

@page:left {
    margin-left: 3cm;
    margin-right: 1cm;
}

@page:right {
    margin-left: 1cm;
    margin-right: 3cm;
}

/* Media queries only work with patched version */
@media print {
    /* Hide navigation elements */
    .navigation, .sidebar {
        display: none !important;
    }
    
    /* Optimize typography */
    body {
        font-size: 10pt;
        line-height: 1.4;
        color: #000;
    }
    
    /* Handle page breaks */
    .page-break {
        page-break-before: always;
    }
    
    .no-break {
        page-break-inside: avoid;
    }
    
    /* Special table styles */
    table {
        border-collapse: collapse;
        page-break-inside: auto;
    }
    
    tr {
        page-break-inside: avoid;
        page-break-after: auto;
    }
    
    thead {
        display: table-header-group;
    }
    
    tfoot {
        display: table-footer-group;
    }
}

/* Screen styles (not applied in PDF) */
@media screen {
    .print-only {
        display: none;
    }
}

Advanced capabilities

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Advanced PDF Report</title>
    <style>
        @page {
            size: A4 landscape;
            margin: 15mm;
            
            @top-left {
                content: "Company Report";
                font-size: 8pt;
                color: #666;
            }
            
            @top-right {
                content: "Page " counter(page) " of " counter(pages);
                font-size: 8pt;
                color: #666;
            }
            
            @bottom-center {
                content: "Confidential";
                font-size: 8pt;
                color: #999;
            }
        }
        
        /* CSS Grid supported in patched version */
        .dashboard {
            display: grid;
            grid-template-columns: repeat(3, 1fr);
            gap: 20px;
            margin: 20px 0;
        }
        
        .metric-card {
            border: 1px solid #ddd;
            padding: 15px;
            border-radius: 8px;
            text-align: center;
        }
        
        /* Flexbox for alignment */
        .flex-container {
            display: flex;
            justify-content: space-between;
            align-items: center;
        }
        
        /* CSS3 gradients work */
        .chart-header {
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            padding: 10px;
            border-radius: 5px 5px 0 0;
        }
    </style>
</head>
<body>
    <h1>Financial Dashboard</h1>
    
    <div class="dashboard">
        <div class="metric-card">
            <h3>Revenue</h3>
            <div class="metric-value">$125,000</div>
        </div>
        <div class="metric-card">
            <h3>Expenses</h3>
            <div class="metric-value">$89,500</div>
        </div>
        <div class="metric-card">
            <h3>Profit</h3>
            <div class="metric-value">$35,500</div>
        </div>
    </div>
    
    <div class="chart-section">
        <div class="chart-header">
            <div class="flex-container">
                <h2>Monthly Trends</h2>
                <span>July 2025</span>
            </div>
        </div>
        <!-- Chart content -->
    </div>
</body>
</html>

PHP Integration

SnappyPDF

// composer require knplabs/knp-snappy

use Knp\Snappy\Pdf;

$snappy = new Pdf('/usr/local/bin/wkhtmltopdf');

// Settings for patched version
$snappy->setOptions([
    'page-size' => 'A4',
    'orientation' => 'landscape',
    'print-media-type' => true,        // Only works with patched!
    'enable-javascript' => true,
    'javascript-delay' => 1000,
    'enable-smart-shrinking' => false,
    'margin-top' => '15mm',
    'margin-bottom' => '15mm',
    'margin-left' => '15mm',
    'margin-right' => '15mm',
    'header-html' => $headerHtml,
    'footer-html' => $footerHtml,
    'header-spacing' => 5,
    'footer-spacing' => 5,
]);

$html = view('reports.invoice', compact('data'))->render();
$pdf = $snappy->getOutputFromHtml($html);

return response($pdf, 200, [
    'Content-Type' => 'application/pdf',
    'Content-Disposition' => 'attachment; filename="invoice.pdf"'
]);

Laravel DomPDF vs SnappyPDF

// Performance and quality comparison

// DomPDF - pure PHP, limited CSS
$pdf = PDF::loadView('report', $data);
// ❌ No CSS Grid/Flexbox support
// ❌ Limited CSS3 support
// ❌ Complex layout issues

// SnappyPDF with patched wkhtmltopdf
$pdf = SnappyPdf::loadHTML($html)
    ->setOption('print-media-type', true)
    ->setOrientation('landscape');
// ✅ Full modern CSS support
// ✅ Accurate browser-like rendering
// ✅ Excellent performance

Installation Automation

Ansible Playbook

---
- name: Install wkhtmltopdf with patched Qt
  hosts: webservers
  become: yes
  
  vars:
    wkhtmltopdf_version: "0.12.6.1-3"
    wkhtmltopdf_package: "wkhtmltox_{{ wkhtmltopdf_version }}.focal_amd64.deb"
    wkhtmltopdf_url: "https://github.com/wkhtmltopdf/packaging/releases/download/{{ wkhtmltopdf_version }}/{{ wkhtmltopdf_package }}"
  
  tasks:
    - name: Remove existing wkhtmltopdf
      apt:
        name: wkhtmltopdf
        state: absent
    
    - name: Install dependencies
      apt:
        name:
          - libfontconfig1
          - libfreetype6
          - libx11-6
          - libxext6
          - libxrender1
          - fontconfig
          - xvfb
        state: present
        update_cache: yes
    
    - name: Download wkhtmltopdf with patched Qt
      get_url:
        url: "{{ wkhtmltopdf_url }}"
        dest: "/tmp/{{ wkhtmltopdf_package }}"
    
    - name: Install wkhtmltopdf
      apt:
        deb: "/tmp/{{ wkhtmltopdf_package }}"
        state: present
    
    - name: Create symlink
      file:
        src: /usr/local/bin/wkhtmltopdf
        dest: /usr/bin/wkhtmltopdf
        state: link
        force: yes
    
    - name: Verify installation
      command: wkhtmltopdf --version
      register: version_output
      
    - name: Check for patched Qt
      fail:
        msg: "wkhtmltopdf is not using patched Qt"
      when: "'with patched qt' not in version_output.stdout"
    
    - name: Test PDF generation
      shell: |
        echo '<h1>Test</h1>' | wkhtmltopdf --print-media-type - /tmp/test.pdf
        test -f /tmp/test.pdf
      register: test_result
    
    - name: Cleanup test file
      file:
        path: /tmp/test.pdf
        state: absent

Monitoring and Diagnostics

Runtime version checking

class WkhtmltopdfChecker
{
    public static function checkVersion()
    {
        $version = shell_exec('wkhtmltopdf --version 2>&1');
        
        return [
            'version' => trim($version),
            'is_patched' => strpos($version, 'with patched qt') !== false,
            'binary_path' => shell_exec('which wkhtmltopdf'),
            'is_executable' => is_executable(trim(shell_exec('which wkhtmltopdf')))
        ];
    }
    
    public static function validateConfiguration()
    {
        $info = self::checkVersion();
        
        if (!$info['is_patched']) {
            throw new Exception('wkhtmltopdf is not using patched Qt. Print media queries will not work.');
        }
        
        if (!$info['is_executable']) {
            throw new Exception('wkhtmltopdf binary is not executable.');
        }
        
        return $info;
    }
}

// In Laravel service provider
public function boot()
{
    if (app()->environment('production')) {
        try {
            WkhtmltopdfChecker::validateConfiguration();
        } catch (Exception $e) {
            Log::critical('wkhtmltopdf configuration error', [
                'error' => $e->getMessage()
            ]);
        }
    }
}

Logging and debugging

class PdfGenerator
{
    private function generatePdf($html, $options = [])
    {
        $start = microtime(true);
        
        Log::info('PDF generation started', [
            'options' => $options,
            'html_size' => strlen($html)
        ]);
        
        try {
            $pdf = $this->snappy->getOutputFromHtml($html);
            
            $duration = microtime(true) - $start;
            
            Log::info('PDF generation completed', [
                'duration' => $duration,
                'pdf_size' => strlen($pdf)
            ]);
            
            return $pdf;
            
        } catch (Exception $e) {
            Log::error('PDF generation failed', [
                'error' => $e->getMessage(),
                'options' => $options,
                'duration' => microtime(true) - $start
            ]);
            
            throw $e;
        }
    }
}

Performance Optimization

Caching

class CachedPdfGenerator
{
    private $cache;
    private $snappy;
    
    public function __construct(Cache $cache, Pdf $snappy)
    {
        $this->cache = $cache;
        $this->snappy = $snappy;
    }
    
    public function generateFromTemplate($template, $data, $options = [])
    {
        // Create cache key based on template, data and options
        $cacheKey = 'pdf_' . md5($template . serialize($data) . serialize($options));
        
        return $this->cache->remember($cacheKey, 3600, function () use ($template, $data, $options) {
            $html = view($template, $data)->render();
            return $this->snappy->getOutputFromHtml($html, $options);
        });
    }
}

Queue jobs

// Job for asynchronous PDF generation
class GeneratePdfJob implements ShouldQueue
{
    private $reportId;
    private $template;
    private $data;
    
    public function handle(PdfGenerator $generator)
    {
        $pdf = $generator->generateFromTemplate($this->template, $this->data);
        
        // Save PDF to storage
        Storage::put("reports/{$this->reportId}.pdf", $pdf);
        
        // Notify user
        $this->notifyUser();
    }
}

// Usage
GeneratePdfJob::dispatch($reportId, 'reports.invoice', $data);

Alternatives and Comparison

Modern alternatives

Puppeteer/Chrome Headless

const puppeteer = require('puppeteer');

const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.setContent(html);
const pdf = await page.pdf({
    format: 'A4',
    landscape: true,
    printBackground: true
});

Playwright

use Nesk\Puphpeteer\Puppeteer;

$puppeteer = new Puppeteer;
$browser = $puppeteer->launch();
$page = $browser->newPage();
$page->setContent($html);
$pdf = $page->pdf(['format' => 'A4']);

WeasyPrint (Python-based, probably micro service or docker)

import weasyprint

html = weasyprint.HTML(string=html_string)
pdf = html.write_pdf()

Comparison table

SolutionCSS SupportPerformanceMemory UsageMaintenance
wkhtmltopdf (patched)★★★★☆★★★★★★★★★☆★★★☆☆
Puppeteer★★★★★★★★☆☆★★☆☆☆★★★★☆
DomPDF★★☆☆☆★★★★★★★★★★★★★★★
WeasyPrint★★★★☆★★★☆☆★★★☆☆★★★☆☆

Conclusion

wkhtmltopdf with patched Qt remains one of the best solutions for server-side PDF generation, especially in PHP applications. Despite the emergence of modern alternatives like Puppeteer, the patched version of wkhtmltopdf provides:

  • Stability: Years-tested solution
  • Performance: Optimized resource consumption
  • Compatibility: Excellent integration with existing PHP applications
  • Functionality: Full support for modern web standards

Key takeaways:

  1. Always use the patched version for production applications
  2. Plan installation in advance — the patched version requires manual installation
  3. Test print media queries — this is the main indicator of correct functionality
  4. Monitor version at runtime — add checks to your CI/CD pipeline
  5. Consider Docker for consistency across environments

A properly configured wkhtmltopdf with patched Qt will provide your application with high-quality PDF document generation with minimal overhead and maximum stability.

🎁 Bonus Script: One-Command wkhtmltopdf Installation

Made it this far? Here's your reward - a universal installer that handles everything automatically:

#!/bin/bash

# =============================================================================
# wkhtmltopdf Patched Qt Universal Installer
# =============================================================================
# 
# Universal script for installing wkhtmltopdf with patched Qt
# Supports: Ubuntu 18.04+, Debian 9+, CentOS 7+, Amazon Linux 2
# 
# Author: DevOps Team
# Version: 2.1.0
# Date: 2024
# 
# Usage:
#   chmod +x install_wkhtmltopdf.sh
#   sudo ./install_wkhtmltopdf.sh
# 
# Options:
#   --version VER    Specify version to install (default: 0.12.6.1-3)
#   --force          Force reinstallation without confirmation
#   --test           Run tests only without installation
#   --docker         Prepare for Docker environment
#   --headless       Install with headless mode support
#   --help           Show help
# 
# =============================================================================

set -euo pipefail

# Colors for output
readonly RED='\033[0;31m'
readonly GREEN='\033[0;32m'
readonly YELLOW='\033[1;33m'
readonly BLUE='\033[0;34m'
readonly NC='\033[0m' # No Color

# Configuration
readonly SCRIPT_VERSION="2.1.0"
readonly DEFAULT_WKHTMLTOPDF_VERSION="0.12.6.1-3"
readonly GITHUB_RELEASES_URL="https://github.com/wkhtmltopdf/packaging/releases/download"
readonly TEMP_DIR="/tmp/wkhtmltopdf-installer-$$"
readonly LOG_FILE="/var/log/wkhtmltopdf-install.log"

# Global variables
WKHTMLTOPDF_VERSION="$DEFAULT_WKHTMLTOPDF_VERSION"
FORCE_INSTALL=false
TEST_ONLY=false
DOCKER_MODE=false
HEADLESS_MODE=false
DETECTED_OS=""
DETECTED_VERSION=""
DETECTED_ARCH=""
PACKAGE_URL=""
PACKAGE_NAME=""

# =============================================================================
# Utilities
# =============================================================================

log() {
    local level="$1"
    shift
    local message="$*"
    local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    echo -e "${timestamp} [${level}] ${message}" | tee -a "$LOG_FILE" >&2
}

info() {
    echo -e "${BLUE}[INFO]${NC} $*"
    log "INFO" "$*"
}

warn() {
    echo -e "${YELLOW}[WARN]${NC} $*"
    log "WARN" "$*"
}

error() {
    echo -e "${RED}[ERROR]${NC} $*"
    log "ERROR" "$*"
}

success() {
    echo -e "${GREEN}[SUCCESS]${NC} $*"
    log "SUCCESS" "$*"
}

die() {
    error "$*"
    exit 1
}

# Check root privileges
check_root() {
    if [[ $EUID -ne 0 ]]; then
        die "This script must be run as root (use sudo)"
    fi
}

# Setup temporary directory
setup_temp_dir() {
    mkdir -p "$TEMP_DIR"
    trap cleanup EXIT
}

cleanup() {
    if [[ -d "$TEMP_DIR" ]]; then
        rm -rf "$TEMP_DIR"
    fi
}

# =============================================================================
# System Detection
# =============================================================================

detect_os() {
    info "Detecting operating system..."
    
    if [[ -f /etc/os-release ]]; then
        source /etc/os-release
        DETECTED_OS="$ID"
        DETECTED_VERSION="$VERSION_ID"
    elif [[ -f /etc/redhat-release ]]; then
        if grep -q "CentOS" /etc/redhat-release; then
            DETECTED_OS="centos"
            DETECTED_VERSION=$(grep -oE '[0-9]+\.[0-9]+' /etc/redhat-release | head -1)
        elif grep -q "Red Hat" /etc/redhat-release; then
            DETECTED_OS="rhel"
            DETECTED_VERSION=$(grep -oE '[0-9]+\.[0-9]+' /etc/redhat-release | head -1)
        fi
    else
        die "Unable to detect operating system"
    fi
    
    # Detect architecture
    DETECTED_ARCH=$(uname -m)
    if [[ "$DETECTED_ARCH" == "x86_64" ]]; then
        DETECTED_ARCH="amd64"
    elif [[ "$DETECTED_ARCH" == "aarch64" ]]; then
        DETECTED_ARCH="arm64"
    else
        die "Unsupported architecture: $DETECTED_ARCH"
    fi
    
    info "Detected system: $DETECTED_OS $DETECTED_VERSION ($DETECTED_ARCH)"
}

# Determine the correct package
determine_package() {
    info "Determining appropriate package..."
    
    case "$DETECTED_OS" in
        "ubuntu")
            case "$DETECTED_VERSION" in
                "24.04")
                    PACKAGE_NAME="wkhtmltox_${WKHTMLTOPDF_VERSION}.jammy_${DETECTED_ARCH}.deb"
                    warn "Ubuntu 24.04 - using package for 22.04 (jammy)"
                    ;;
                "22.04")
                    PACKAGE_NAME="wkhtmltox_${WKHTMLTOPDF_VERSION}.jammy_${DETECTED_ARCH}.deb"
                    ;;
                "20.04")
                    PACKAGE_NAME="wkhtmltox_${WKHTMLTOPDF_VERSION}.focal_${DETECTED_ARCH}.deb"
                    ;;
                "18.04")
                    PACKAGE_NAME="wkhtmltox_${WKHTMLTOPDF_VERSION}.bionic_${DETECTED_ARCH}.deb"
                    ;;
                *)
                    warn "Unknown Ubuntu version $DETECTED_VERSION, trying jammy"
                    PACKAGE_NAME="wkhtmltox_${WKHTMLTOPDF_VERSION}.jammy_${DETECTED_ARCH}.deb"
                    ;;
            esac
            ;;
        "debian")
            if [[ "${DETECTED_VERSION%%.*}" -ge 11 ]]; then
                PACKAGE_NAME="wkhtmltox_${WKHTMLTOPDF_VERSION}.bullseye_${DETECTED_ARCH}.deb"
            elif [[ "${DETECTED_VERSION%%.*}" -eq 10 ]]; then
                PACKAGE_NAME="wkhtmltox_${WKHTMLTOPDF_VERSION}.buster_${DETECTED_ARCH}.deb"
            else
                die "Unsupported Debian version: $DETECTED_VERSION"
            fi
            ;;
        "centos"|"rhel")
            if [[ "${DETECTED_VERSION%%.*}" -eq 8 ]]; then
                PACKAGE_NAME="wkhtmltox-${WKHTMLTOPDF_VERSION}.centos8.x86_64.rpm"
            elif [[ "${DETECTED_VERSION%%.*}" -eq 7 ]]; then
                PACKAGE_NAME="wkhtmltox-${WKHTMLTOPDF_VERSION}.centos7.x86_64.rpm"
            else
                die "Unsupported CentOS/RHEL version: $DETECTED_VERSION"
            fi
            ;;
        "amzn")
            PACKAGE_NAME="wkhtmltox-${WKHTMLTOPDF_VERSION}.centos7.x86_64.rpm"
            ;;
        *)
            die "Unsupported operating system: $DETECTED_OS"
            ;;
    esac
    
    PACKAGE_URL="${GITHUB_RELEASES_URL}/${WKHTMLTOPDF_VERSION}/${PACKAGE_NAME}"
    info "Selected package: $PACKAGE_NAME"
    info "Download URL: $PACKAGE_URL"
}

# =============================================================================
# Dependencies Installation
# =============================================================================

install_dependencies() {
    info "Installing dependencies..."
    
    case "$DETECTED_OS" in
        "ubuntu"|"debian")
            export DEBIAN_FRONTEND=noninteractive
            
            apt-get update -qq
            
            local deps=(
                "wget" "ca-certificates" "fontconfig" "libfontconfig1"
                "libfreetype6" "libx11-6" "libxext6" "libxrender1"
                "libstdc++6" "libc6" "zlib1g"
            )
            
            # Additional dependencies for different versions
            if [[ "$DETECTED_OS" == "ubuntu" ]] && [[ "${DETECTED_VERSION}" > "20" ]]; then
                deps+=("libgcc-s1")
            else
                deps+=("libgcc1")
            fi
            
            if [[ "$HEADLESS_MODE" == true ]]; then
                deps+=("xvfb" "xfonts-base" "xfonts-75dpi")
            fi
            
            if [[ "$DOCKER_MODE" == true ]]; then
                deps+=("fonts-liberation" "fonts-dejavu-core")
            fi
            
            apt-get install -y "${deps[@]}"
            ;;
            
        "centos"|"rhel"|"amzn")
            local package_manager="yum"
            if command -v dnf >/dev/null 2>&1; then
                package_manager="dnf"
            fi
            
            $package_manager update -y -q
            
            local deps=(
                "wget" "ca-certificates" "fontconfig" "freetype"
                "libX11" "libXext" "libXrender" "libstdc++" "glibc" "zlib"
            )
            
            if [[ "$HEADLESS_MODE" == true ]]; then
                deps+=("xorg-x11-server-Xvfb" "xorg-x11-fonts-base" "xorg-x11-fonts-75dpi")
            fi
            
            $package_manager install -y "${deps[@]}"
            ;;
    esac
}

# =============================================================================
# Remove Existing Installation
# =============================================================================

remove_existing() {
    info "Checking for existing installations..."
    
    case "$DETECTED_OS" in
        "ubuntu"|"debian")
            if dpkg -l | grep -q wkhtmltopdf; then
                if [[ "$FORCE_INSTALL" == true ]]; then
                    info "Removing existing wkhtmltopdf..."
                    apt-get remove -y wkhtmltopdf wkhtmltox
                else
                    warn "Existing wkhtmltopdf installation detected"
                    read -p "Remove existing version? (y/N): " -n 1 -r
                    echo
                    if [[ $REPLY =~ ^[Yy]$ ]]; then
                        apt-get remove -y wkhtmltopdf wkhtmltox
                    else
                        die "Installation cancelled by user"
                    fi
                fi
            fi
            ;;
            
        "centos"|"rhel"|"amzn")
            if rpm -qa | grep -q wkhtmltopdf; then
                if [[ "$FORCE_INSTALL" == true ]]; then
                    info "Removing existing wkhtmltopdf..."
                    rpm -e wkhtmltopdf || true
                    rpm -e wkhtmltox || true
                else
                    warn "Existing wkhtmltopdf installation detected"
                    read -p "Remove existing version? (y/N): " -n 1 -r
                    echo
                    if [[ $REPLY =~ ^[Yy]$ ]]; then
                        rpm -e wkhtmltopdf || true
                        rpm -e wkhtmltox || true
                    else
                        die "Installation cancelled by user"
                    fi
                fi
            fi
            ;;
    esac
}

# =============================================================================
# Download and Installation
# =============================================================================

download_package() {
    info "Downloading wkhtmltopdf package..."
    
    cd "$TEMP_DIR"
    
    # Try downloading from main URL
    if ! wget -q --timeout=30 --tries=3 "$PACKAGE_URL"; then
        warn "Main URL unavailable, trying alternative sources..."
        
        # Alternative URLs
        local alt_urls=(
            "https://downloads.wkhtmltopdf.org/${WKHTMLTOPDF_VERSION%%-*}/${WKHTMLTOPDF_VERSION}/${PACKAGE_NAME}"
            "https://github.com/wkhtmltopdf/wkhtmltopdf/releases/download/${WKHTMLTOPDF_VERSION}/${PACKAGE_NAME}"
        )
        
        local downloaded=false
        for url in "${alt_urls[@]}"; do
            info "Trying alternative URL: $url"
            if wget -q --timeout=30 --tries=3 "$url"; then
                downloaded=true
                break
            fi
        done
        
        if [[ "$downloaded" == false ]]; then
            die "Failed to download wkhtmltopdf package"
        fi
    fi
    
    # Check file integrity
    if [[ ! -f "$PACKAGE_NAME" ]] || [[ ! -s "$PACKAGE_NAME" ]]; then
        die "Downloaded file is corrupted or empty"
    fi
    
    info "Package downloaded successfully: $(ls -lh "$PACKAGE_NAME" | awk '{print $5}')"
}

install_package() {
    info "Installing wkhtmltopdf package..."
    
    cd "$TEMP_DIR"
    
    case "$DETECTED_OS" in
        "ubuntu"|"debian")
            # Try installing with dpkg
            if dpkg -i "$PACKAGE_NAME" 2>/dev/null; then
                success "Package installed successfully"
            else
                info "Fixing dependencies..."
                apt-get install -f -y
                
                # Retry
                if dpkg -i "$PACKAGE_NAME"; then
                    success "Package installed after fixing dependencies"
                else
                    die "Failed to install package"
                fi
            fi
            ;;
            
        "centos"|"rhel"|"amzn")
            local package_manager="yum"
            if command -v dnf >/dev/null 2>&1; then
                package_manager="dnf"
            fi
            
            if $package_manager localinstall -y "$PACKAGE_NAME"; then
                success "Package installed successfully"
            else
                die "Failed to install package"
            fi
            ;;
    esac
}

# =============================================================================
# Post-installation Setup
# =============================================================================

setup_symlinks() {
    info "Setting up symbolic links..."
    
    # Standard installation paths
    local possible_paths=(
        "/usr/local/bin/wkhtmltopdf"
        "/opt/wkhtmltox/bin/wkhtmltopdf"
        "/usr/bin/wkhtmltopdf"
    )
    
    local installed_path=""
    for path in "${possible_paths[@]}"; do
        if [[ -f "$path" ]]; then
            installed_path="$path"
            break
        fi
    done
    
    if [[ -z "$installed_path" ]]; then
        die "Unable to find installed wkhtmltopdf"
    fi
    
    info "wkhtmltopdf found at: $installed_path"
    
    # Create standard link if needed
    if [[ "$installed_path" != "/usr/bin/wkhtmltopdf" ]]; then
        if [[ -f "/usr/bin/wkhtmltopdf" ]]; then
            rm -f "/usr/bin/wkhtmltopdf"
        fi
        ln -sf "$installed_path" "/usr/bin/wkhtmltopdf"
        info "Created symbolic link: /usr/bin/wkhtmltopdf -> $installed_path"
    fi
    
    # Set proper permissions
    chmod 755 "$installed_path"
    
    # Create headless wrapper if needed
    if [[ "$HEADLESS_MODE" == true ]]; then
        create_headless_wrapper "$installed_path"
    fi
}

create_headless_wrapper() {
    local wkhtmltopdf_path="$1"
    local wrapper_path="/usr/local/bin/wkhtmltopdf-headless"
    
    info "Creating headless wrapper..."
    
    cat > "$wrapper_path" << EOF
#!/bin/bash
# wkhtmltopdf headless wrapper
# Auto-generated by installer

set -euo pipefail

# Check for DISPLAY
if [[ -z "\${DISPLAY:-}" ]]; then
    # Run with xvfb-run
    exec xvfb-run -a --server-args="-screen 0 1024x768x24" "$wkhtmltopdf_path" "\$@"
else
    # DISPLAY already set, run directly
    exec "$wkhtmltopdf_path" "\$@"
fi
EOF
    
    chmod +x "$wrapper_path"
    info "Headless wrapper created: $wrapper_path"
}

# =============================================================================
# Testing
# =============================================================================

run_tests() {
    info "Running tests..."
    
    local test_dir="$TEMP_DIR/tests"
    mkdir -p "$test_dir"
    cd "$test_dir"
    
    # Test 1: Version check
    info "Test 1: Version check"
    local version_output
    if version_output=$(wkhtmltopdf --version 2>&1); then
        info "Version: $version_output"
        
        if echo "$version_output" | grep -q "with patched qt"; then
            success "✅ Patched Qt detected"
        else
            error "❌ Patched Qt NOT detected"
            return 1
        fi
    else
        error "❌ Failed to get wkhtmltopdf version"
        return 1
    fi
    
    # Test 2: Basic PDF generation
    info "Test 2: Basic PDF generation"
    cat > test_basic.html << 'EOF'
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Basic Test</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 20px; }
        .header { background: #f0f0f0; padding: 10px; border-radius: 5px; }
    </style>
</head>
<body>
    <div class="header">
        <h1>wkhtmltopdf Test Document</h1>
        <p>Timestamp: $(date)</p>
    </div>
    <p>This is a basic test of wkhtmltopdf functionality.</p>
</body>
</html>
EOF
    
    if wkhtmltopdf test_basic.html test_basic.pdf >/dev/null 2>&1; then
        if [[ -f test_basic.pdf ]] && [[ -s test_basic.pdf ]]; then
            success "✅ Basic PDF generation works (size: $(ls -lh test_basic.pdf | awk '{print $5}'))"
        else
            error "❌ PDF file is empty or not created"
            return 1
        fi
    else
        error "❌ Error generating basic PDF"
        return 1
    fi
    
    # Test 3: Print media queries (critical for patched version)
    info "Test 3: Print media queries"
    cat > test_media.html << 'EOF'
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Media Query Test</title>
    <style>
        @media screen {
            .screen-only { display: block; color: blue; }
            .print-only { display: none; }
        }
        @media print {
            .screen-only { display: none; }
            .print-only { display: block; color: red; background: yellow; padding: 10px; }
        }
        body { font-family: Arial, sans-serif; margin: 20px; }
    </style>
</head>
<body>
    <h1>Media Query Test</h1>
    <div class="screen-only">This should NOT appear in PDF</div>
    <div class="print-only">This SHOULD appear in PDF with red text and yellow background</div>
    <p>Regular content that should always appear.</p>
</body>
</html>
EOF
    
    if wkhtmltopdf --print-media-type test_media.html test_media.pdf 2>&1 | grep -q "not support using unpatched qt"; then
        error "❌ Print media queries not supported (unpatched Qt)"
        return 1
    elif wkhtmltopdf --print-media-type test_media.html test_media.pdf >/dev/null 2>&1; then
        if [[ -f test_media.pdf ]] && [[ -s test_media.pdf ]]; then
            success "✅ Print media queries work"
        else
            error "❌ Error generating PDF with media queries"
            return 1
        fi
    else
        error "❌ Error testing print media queries"
        return 1
    fi
    
    # Test 4: Landscape orientation
    info "Test 4: Landscape orientation"
    cat > test_landscape.html << 'EOF'
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Landscape Test</title>
    <style>
        @page { size: A4 landscape; margin: 20mm; }
        body { font-family: Arial, sans-serif; }
        .wide-table { width: 100%; border-collapse: collapse; }
        .wide-table th, .wide-table td { border: 1px solid #333; padding: 8px; text-align: left; }
        .wide-table th { background-color: #f2f2f2; }
    </style>
</head>
<body>
    <h1>Landscape Orientation Test</h1>
    <table class="wide-table">
        <tr>
            <th>Column 1</th><th>Column 2</th><th>Column 3</th><th>Column 4</th><th>Column 5</th><th>Column 6</th>
        </tr>
        <tr>
            <td>Data 1</td><td>Data 2</td><td>Data 3</td><td>Data 4</td><td>Data 5</td><td>Data 6</td>
        </tr>
    </table>
</body>
</html>
EOF
    
    if wkhtmltopdf --orientation Landscape --print-media-type test_landscape.html test_landscape.pdf >/dev/null 2>&1; then
        if [[ -f test_landscape.pdf ]] && [[ -s test_landscape.pdf ]]; then
            success "✅ Landscape orientation works"
        else
            error "❌ Error generating landscape PDF"
            return 1
        fi
    else
        error "❌ Error testing landscape orientation"
        return 1
    fi
    
    # Test 5: Headless mode (if enabled)
    if [[ "$HEADLESS_MODE" == true ]]; then
        info "Test 5: Headless mode"
        if command -v wkhtmltopdf-headless >/dev/null 2>&1; then
            if DISPLAY="" wkhtmltopdf-headless test_basic.html test_headless.pdf >/dev/null 2>&1; then
                if [[ -f test_headless.pdf ]] && [[ -s test_headless.pdf ]]; then
                    success "✅ Headless mode works"
                else
                    error "❌ Error in headless mode"
                    return 1
                fi
            else
                error "❌ Error testing headless mode"
                return 1
            fi
        else
            warn "⚠️ Headless wrapper not found"
        fi
    fi
    
    success "All tests passed successfully!"
    
    # Show summary
    info "Test summary:"
    echo "  📁 Test files created in: $test_dir"
    echo "  📄 PDF file sizes:"
    ls -lh *.pdf 2>/dev/null | awk '{print "    " $9 ": " $5}' || true
    
    return 0
}

# =============================================================================
# Information Functions
# =============================================================================

show_help() {
    cat << 'EOF'
wkhtmltopdf Patched Qt Universal Installer

USAGE:
    sudo ./install_wkhtmltopdf.sh [OPTIONS]

OPTIONS:
    --version VER    Specify version to install (default: 0.12.6.1-3)
    --force          Force reinstallation without confirmation
    --test           Run tests only without installation
    --docker         Prepare for Docker environment (additional fonts)
    --headless       Install with headless mode support (xvfb)
    --help           Show this help

EXAMPLES:
    # Basic installation
    sudo ./install_wkhtmltopdf.sh
    
    # Install specific version
    sudo ./install_wkhtmltopdf.sh --version 0.12.6.1-2
    
    # Force reinstallation
    sudo ./install_wkhtmltopdf.sh --force
    
    # Install for Docker with headless support
    sudo ./install_wkhtmltopdf.sh --docker --headless
    
    # Test existing installation only
    sudo ./install_wkhtmltopdf.sh --test

SUPPORTED SYSTEMS:
    - Ubuntu 18.04, 20.04, 22.04, 24.04
    - Debian 9, 10, 11, 12
    - CentOS 7, 8
    - RHEL 7, 8
    - Amazon Linux 2

IMPORTANT:
    - Script requires root privileges (use sudo)
    - Patched version provides print media query support
    - Regular version from repositories does NOT support many features

EOF
}

show_system_info() {
    info "System information:"
    echo "  🖥️  OS: $DETECTED_OS $DETECTED_VERSION"
    echo "  🏗️  Architecture: $DETECTED_ARCH"
    echo "  📦 Target package: $PACKAGE_NAME"
    echo "  🔗 Download URL: $PACKAGE_URL"
    echo "  📝 Log file: $LOG_FILE"
    echo "  🔧 Modes:"
    echo "     - Force install: $FORCE_INSTALL"
    echo "     - Docker mode: $DOCKER_MODE"
    echo "     - Headless mode: $HEADLESS_MODE"
    echo "     - Test only: $TEST_ONLY"
}

show_final_info() {
    success "wkhtmltopdf installation completed!"
    echo
    info "Installation information:"
    
    local wkhtmltopdf_path=$(which wkhtmltopdf)
    echo "  📍 Path: $wkhtmltopdf_path"
    
    local version_info=$(wkhtmltopdf --version 2>&1 | head -1)
    echo "  📋 Version: $version_info"
    
    if echo "$version_info" | grep -q "with patched qt"; then
        echo "  ✅ Patched Qt: Yes"
    else
        echo "  ❌ Patched Qt: No"
    fi
    
    if [[ "$HEADLESS_MODE" == true ]] && command -v wkhtmltopdf-headless >/dev/null 2>&1; then
        echo "  🖥️  Headless wrapper: /usr/local/bin/wkhtmltopdf-headless"
    fi
    
    echo
    info "Usage examples:"
    echo "  # Basic PDF generation"
    echo "  wkhtmltopdf index.html document.pdf"
    echo
    echo "  # With print media query support"
    echo "  wkhtmltopdf --print-media-type index.html document.pdf"
    echo
    echo "  # Landscape orientation"
    echo "  wkhtmltopdf --orientation Landscape index.html document.pdf"
    
    if [[ "$HEADLESS_MODE" == true ]]; then
        echo
        echo "  # Headless mode (without X11)"
        echo "  wkhtmltopdf-headless index.html document.pdf"
    fi
    
    echo
    info "Documentation: https://wkhtmltopdf.org/usage/wkhtmltopdf.txt"
    info "Installation log saved to: $LOG_FILE"
}

# =============================================================================
# Main Logic
# =============================================================================

parse_arguments() {
    while [[ $# -gt 0 ]]; do
        case $1 in
            --version)
                WKHTMLTOPDF_VERSION="$2"
                shift 2
                ;;
            --force)
                FORCE_INSTALL=true
                shift
                ;;
            --test)
                TEST_ONLY=true
                shift
                ;;
            --docker)
                DOCKER_MODE=true
                shift
                ;;
            --headless)
                HEADLESS_MODE=true
                shift
                ;;
            --help)
                show_help
                exit 0
                ;;
            *)
                error "Unknown option: $1"
                echo "Use --help for help"
                exit 1
                ;;
        esac
    done
}

main() {
    echo "wkhtmltopdf Patched Qt Universal Installer v$SCRIPT_VERSION"
    echo "============================================================"
    echo
    
    # Create log file
    mkdir -p "$(dirname "$LOG_FILE")"
    touch "$LOG_FILE"
    
    # Parse arguments
    parse_arguments "$@"
    
    # Check root privileges (except for test mode)
    if [[ "$TEST_ONLY" == false ]]; then
        check_root
    fi
    
    # Setup temporary directory
    setup_temp_dir
    
    # Detect system
    detect_os
    determine_package
    show_system_info
    echo
    
    # If test only
    if [[ "$TEST_ONLY" == true ]]; then
        info "Test mode - skipping installation"
        if command -v wkhtmltopdf >/dev/null 2>&1; then
            run_tests
        else
            die "wkhtmltopdf not installed. Install it first without --test option"
        fi
        exit 0
    fi
    
    # Main installation procedure
    info "Starting wkhtmltopdf installation with patched Qt..."
    
    install_dependencies
    remove_existing
    download_package
    install_package
    setup_symlinks
    
    echo
    info "Running tests to verify installation..."
    if run_tests; then
        echo
        show_final_info
    else
        die "Tests failed. Please check installation."
    fi
}

# Run main function with all arguments
main "$@"

About the Author

Ihor (Harry) Chyshkala

Ihor (Harry) Chyshkala

Code Alchemist: Transmuting Ideas into Reality with JS & PHP. DevOps Wizard: Transforming Infrastructure into Cloud Gold | Orchestrating CI/CD Magic | Crafting Automation Elixirs