НейроАгент

Лучшее руководство по преобразованию HTML в PDF с PHP на Linux

Полное руководство по преобразованию HTML/CSS в PDF на Linux-серверах с использованием PHP. Сравните решения DOMPDF, Mpdf, Puppeteer и wkhtmltopdf с инструкциями по установке.

Вопрос

Как я могу конвертировать HTML и CSS в PDF на Linux-сервере с помощью PHP? У меня есть HTML-документ, который корректно отображается в браузерах, но его нужно конвертировать в PDF. Я пробовал несколько решений:

  1. DOMPDF: Возникли проблемы с таблицами и изображениями, чрезмерное потребление памяти
  2. HTML2PDF/HTML2PS: Лучшее отображение таблиц, но возникали ошибки с неизвестным node_type()
  3. Htmldoc: Работает с базовым HTML, но имеет слабую поддержку CSS

Мне нужно решение, которое работает на Linux и, в идеале, работает по запросу через PHP на веб-сервере. Какие лучшие подходы или альтернативы существуют для такой конвертации?

НейроАгент

Преобразование HTML и CSS в PDF на Linux-серверах с использованием PHP можно осуществить несколькими надежными подходами. Современные решения, такие как headless-браузеры (Chrome/Chromium), обеспечивают наиболее точное рендеринг, в то время как специализированные библиотеки, такие как Mpdf или Snappy, предоставляют нативные для PHP альтернативы, которые решают ограничения, с которыми вы столкнулись при использовании DOMPDF и других инструментов.

Содержание

Обзор методов преобразования HTML в PDF

Преобразование HTML в PDF на Linux-серверах обычно относится к трем основным категориям:

  1. Нативные для PHP библиотеки, которые напрямую анализируют HTML и генерируют PDF-вывод
  2. Инструменты командной строки, которые преобразуют HTML в PDF через системные вызовы
  3. Решения на основе headless-браузеров, которые рендерят HTML в среде браузера и преобразуют в PDF

Каждый подход имеет свои преимущества и недостатки. Нативные для PHP библиотеки, такие как Mpdf, легковесны и легко интегрируются, но могут испытывать трудности со сложным CSS. Инструменты командной строки, такие как Htmldoc, обеспечивают хорошую базовую поддержку, но с ограниченными возможностями CSS. Headless-браузеры обеспечивают наиболее точное рендеринг, но требуют больше системных ресурсов.

Ключевой момент: Лучший подход зависит от ваших конкретных требований к поддержке CSS, производительности, системным ресурсам и качеству вывода.

Современные решения на основе headless-браузеров

Puppeteer с мостом Node.js

Puppeteer, библиотека для Node.js, обеспечивает превосходное преобразование HTML в PDF через headless Chrome/Chromium. Хотя это не нативное для PHP решение, вы можете создать мост между PHP и Node.js:

php
// Пример моста PHP к Node.js для Puppeteer
function generatePdfWithPuppeteer($html, $outputPath) {
    $tempHtmlFile = tempnam(sys_get_temp_dir(), 'html_');
    file_put_contents($tempHtmlFile, $html);
    
    $command = "node puppeteer-pdf.js " . escapeshellarg($tempHtmlFile) . " " . escapeshellarg($outputPath);
    exec($command, $output, $returnCode);
    
    unlink($tempHtmlFile);
    
    return ($returnCode === 0);
}

Преимущества:

  • Отличная поддержка CSS, включая современные функции
  • Точное рендеринг, соответствующее браузерам
  • Поддержка сложных макетов, таблиц и изображений

Недостатки:

  • Требует Node.js на сервере
  • Более высокое потребление памяти
  • Более сложная настройка

WeasyPrint

WeasyPrint - это библиотека для Python, которая преобразует HTML/CSS в PDF с отличным соответствием стандартам:

php
// PHP-обертка для WeasyPrint
function generatePdfWithWeasyPrint($html, $outputPath) {
    $tempHtmlFile = tempnam(sys_get_temp_dir(), 'html_');
    file_put_contents($tempHtmlFile, $html);
    
    $command = "weasyprint " . escapeshellarg($tempHtmlFile) . " " . escapeshellarg($outputPath);
    exec($command, $output, $returnCode);
    
    unlink($tempHtmlFile);
    
    return ($returnCode === 0);
}

Преимущества:

  • Отличная поддержка CSS3
  • Высокое качество вывода
  • Хорошая производительность

Недостатки:

  • Требует зависимости Python
  • Ограниченная поддержка JavaScript
  • Более крутая кривая обучения

Нативные для PHP библиотеки и альтернативы

Mpdf (Улучшенная альтернатива DOMPDF)

Mpdf часто рекомендуется как преемник DOMPDF с лучшей производительностью и функциональностью:

php
require_once __DIR__ . '/vendor/autoload.php';

$mpdf = new \Mpdf\Mpdf([
    'mode' => 'utf-8',
    'format' => 'A4',
    'margin_left' => 10,
    'margin_right' => 10,
    'margin_top' => 10,
    'margin_bottom' => 10
]);

$mpdf->WriteHTML($htmlContent);
$mpdf->Output('document.pdf', 'D');

Ключевые улучшения по сравнению с DOMPDF:

  • Лучшее рендеринг таблиц
  • Улучшенная поддержка CSS
  • Более низкое потребление памяти
  • Лучшая обработка изображений

Установка:

bash
composer require mpdf/mpdf

TCPDF

TCPDF - еще одна надежная PHP-библиотека с обширными функциями:

php
require_once('tcpdf/tcpdf.php');

$pdf = new TCPDF(PDF_PAGE_ORIENTATION, PDF_UNIT, PDF_PAGE_FORMAT, true, 'UTF-8', false);

$pdf->AddPage();
$pdf->writeHTML($htmlContent, true, false, true, false, '');
$pdf->Output('document.pdf', 'D');

Функции:

  • Поддержка сложных макетов
  • Хорошо подходит для форм и таблиц
  • Обширная документация

Snappy (обертка для wkhtmltopdf)

Snappy предоставляет PHP-обертку для мощного инструмента командной строки wkhtmltopdf:

php
require_once __DIR__ . '/vendor/autoload.php';

use Knp\Snappy\Pdf;

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

$snappy->generateFromHtml(
    '<h1>Счет</h1><p>Уважаемый клиент</p>',
    '/path/to/bill.pdf'
);

Преимущества:

  • Использует реальный движок рендеринга WebKit
  • Отличная поддержка CSS
  • Хорошо обрабатывает сложные макеты

Руководство по установке и настройке

Системные требования

Перед установкой любого решения для преобразования HTML в PDF убедитесь, что ваш Linux-сервер соответствует этим требованиям:

bash
# Базовые системные требования
sudo apt update
sudo apt install -y php php-cli php-gd php-xml php-mbstring
sudo apt install -y libpng-dev libjpeg-dev libfreetype6-dev
sudo apt install -y fontconfig

Установка Mpdf

bash
# Установка Mpdf через Composer
composer require mpdf/mpdf

# Или сначала установите системные зависимости
sudo apt install -y php-pear php-dev libpng-dev libjpeg-dev libfreetype6-dev
sudo pecl install imagick
echo "extension=imagick.so" | sudo tee /etc/php/$(php -r 'echo PHP_MAJOR_VERSION.".".PHP_MINOR_VERSION;')/cli/conf.d/imagick.ini

Установка wkhtmltopdf для Snappy

bash
# Загрузка и установка wkhtmltopdf
sudo apt install -y xvfb
wget https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6-1/wkhtmltox_0.12.6-1.bionic_amd64.deb
sudo dpkg -i wkhtmltox_0.12.6-1.bionic_amd64.deb
sudo apt install -f

# Проверка установки
wkhtmltopdf --version

Настройка headless Chrome

bash
# Установка Chrome/Chromium
sudo apt install -y chromium-browser

# Или установка Google Chrome
wget -q -O - https://dl.google.com/linux/linux_signing_key.pub | sudo apt-key add -
echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" | sudo tee /etc/apt/sources.list.d/google-chrome.list
sudo apt update
sudo apt install -y google-chrome-stable

# Установка Node.js и Puppeteer
curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -
sudo apt install -y nodejs
npm install -g puppeteer

Техники оптимизации производительности

Управление памятью

Для нативных для PHP библиотек, таких как Mpdf:

php
// Настройте лимиты памяти в php.ini
memory_limit = 512M

# В PHP коде
ini_set('memory_limit', '512M');

# Для больших документов используйте потоковую передачу
$mpdf = new \Mpdf\Mpdf([
    'mode' => 'utf-8',
    'format' => 'A4',
    'tempDir' => '/tmp/mpdf',
    'setAutoTopMargin' => 'pad',
    'setAutoBottomMargin' => 'pad'
]);

# При необходимости обрабатывайте HTML по частям
$mpdf->WriteHTML($htmlContent);
$mpdf->Output('document.pdf', 'F');

Кэширование и оптимизация

php
# Реализуйте кэширование для часто генерируемых PDF
function getCachedPdf($key, $html, $ttl = 3600) {
    $cacheFile = "/tmp/pdf_cache/{$key}.pdf";
    
    if (file_exists($cacheFile) && (time() - filemtime($cacheFile)) < $ttl) {
        return $cacheFile;
    }
    
    # Генерация PDF
    generatePdfWithLibrary($html, $cacheFile);
    return $cacheFile;
}

# Оптимизация HTML для генерации PDF
function optimizeHtmlForPdf($html) {
    # Удаление ненужных элементов
    $html = preg_replace('/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/mi', '', $html);
    $html = preg_replace('/<style\b[^<]*(?:(?!<\/style>)<[^<]*)*<\/style>/mi', '', $html);
    
    # Оптимизация изображений
    $html = preg_replace('/<img([^>]+)>/i', '<img$1 loading="eager" />', $html);
    
    return $html;
}

Фоновая обработка

Для ресурсоемких преобразований:

php
# Используйте очереди или фоновую обработку
function generatePdfInBackground($html, $outputPath, $callback = null) {
    $tempHtmlFile = tempnam(sys_get_temp_dir(), 'html_');
    file_put_contents($tempHtmlFile, $html);
    
    $command = "nohup php generate_pdf.php " . escapeshellarg($tempHtmlFile) . " " . escapeshellarg($outputPath) . " > /dev/null 2>&1 &";
    exec($command);
    
    if ($callback) {
        $callback($tempHtmlFile);
    }
}

# Рабочий скрипт (generate_pdf.php)
$htmlFile = $argv[1];
$outputFile = $argv[2];

$html = file_get_contents($htmlFile);
generatePdfWithLibrary($html, $outputFile);
unlink($htmlFile);

Устранение распространенных проблем

Проблемы с памятью в DOMPDF/Mpdf

php
# Решение: увеличьте память и оптимизируйте обработку
$mpdf = new \Mpdf\Mpdf([
    'mode' => 'utf-8',
    'format' => 'A4',
    'tempDir' => '/tmp/mpdf',
    'memoryLimit' => '512M',
    'debug' => true # включите режим отладки
]);

# Для очень больших документов разбейте на страницы
$htmlParts = explode('<page-break>', $htmlContent);
foreach ($htmlParts as $part) {
    $mpdf->AddPage();
    $mpdf->WriteHTML($part);
}

Проблемы совместимости CSS

php
# Решение: используйте CSS-сбросы и стили, специфичные для PDF
$pdfStyles = '
    @page {
        size: A4;
        margin: 1cm;
    }
    
    body {
        font-family: Arial, sans-serif;
        font-size: 12pt;
        line-height: 1.4;
    }
    
    table {
        border-collapse: collapse;
        width: 100%;
    }
    
    table td, table th {
        border: 1px solid #ddd;
        padding: 8px;
        text-align: left;
    }
    
    img {
        max-width: 100%;
        height: auto;
    }
';

# Примените стили, специфичные для PDF
$html = '<style>' . $pdfStyles . '</style>' . $html;

Проблемы загрузки шрифтов

php
# Решение: встраивайте пользовательские шрифты
$mpdf = new \Mpdf\Mpdf([
    'mode' => 'utf-8',
    'format' => 'A4',
    'default_font_size' => 0,
    'default_font' => 'helvetica',
    'fontDir' => [
        __DIR__ . '/fonts/',
        __DIR__ . '/tcpdf/fonts/',
        '/usr/share/fonts/truetype/dejavu/'
    ],
    'fontdata' => [
        'customfont' => [
            'R' => 'Custom-Regular.ttf',
            'B' => 'Custom-Bold.ttf',
            'I' => 'Custom-Italic.ttf',
            'BI' => 'Custom-BoldItalic.ttf'
        ]
    ]
]);

Проблемы рендеринга таблиц

php
# Решение: используйте HTML-структуру, дружественную к таблицам
$html = '
<table>
    <thead>
        <tr>
            <th>Заголовок 1</th>
            <th>Заголовок 2</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>Данные 1</td>
            <td>Данные 2</td>
        </tr>
    </tbody>
</table>
';

# CSS для таблиц
$css = '
table {
    border-collapse: collapse;
    width: 100%;
    page-break-inside: avoid;
}
 
table td, table th {
    border: 1px solid #000;
    padding: 6px;
    text-align: left;
}

thead {
    background-color: #f2f2f2;
}
';

Расширенные функции и настройка

Добавление верхних и нижних колонтитулов

php
# Пример с Mpdf для верхних и нижних колонтитулов
$mpdf = new \Mpdf\Mpdf();

# Настройка верхних и нижних колонтитулов
$mpdf->SetHTMLHeader('
<div style="text-align: right; font-size: 10pt;">
    Страница {PAGENO} из {nbpg}
</div>
');

$mpdf->SetHTMLFooter('
<div style="text-align: center; font-size: 8pt;">
    Сгенерировано ' . date('Y-m-d H:i:s') . '
</div>
');

$mpdf->WriteHTML($html);
$mpdf->Output('document.pdf', 'D');

Водяные знаки и безопасность

php
# Добавление водяного знака в PDF
$mpdf = new \Mpdf\Mpdf();

# Добавление водяного знака
$mpdf->SetWatermarkText('ЧЕРНОВИК');
$mpdf->showWatermarkText = true;

# Настройка безопасности PDF
$mpdf->SetProtection(
    ['copy', 'print'], // Разрешить копирование и печать
    'user_password',   // Пароль пользователя
    'owner_password'   // Пароль владельца
);

$mpdf->WriteHTML($html);
$mpdf->Output('document.pdf', 'D');

Поддержка нескольких языков

php
# Поддержка разных языков и шрифтов
$mpdf = new \Mpdf\Mpdf([
    'mode' => 'utf-8',
    'format' => 'A4',
    'fontDir' => [
        __DIR__ . '/fonts/',
        '/usr/share/fonts/truetype/'
    ],
    'fontdata' => [
        'cjk' => [
            'R' => 'NotoSansCJK-Regular.ttc',
            'useOTL' => 0x100
        ]
    ]
]);

# Настройки, специфичные для языка
$mpdf->autoScriptToLang = true;
$mpdf->autoLangToFont = true;

$mpdf->WriteHTML('<p>Привет мир!</p>');
$mpdf->WriteHTML('<p>Bonjour le monde!</p>');
$mpdf->Output('multilingual.pdf', 'D');

Генерация динамического контента

php
# Генерация PDF с динамическими данными из базы данных
function generateInvoicePdf($invoiceId) {
    # Получение данных счета
    $invoice = getInvoiceData($invoiceId);
    $items = getInvoiceItems($invoiceId);
    
    # Генерация HTML-шаблона
    $html = '
    <h1>Счет #' . $invoice['id'] . '</h1>
    <p>Дата: ' . $invoice['date'] . '</p>
    <table>
        <thead>
            <tr>
                <th>Товар</th>
                <th>Количество</th>
                <th>Цена</th>
                <th>Итого</th>
            </tr>
        </thead>
        <tbody>';
    
    foreach ($items as $item) {
        $html .= '
            <tr>
                <td>' . $item['description'] . '</td>
                <td>' . $item['quantity'] . '</td>
                <td>$' . number_format($item['price'], 2) . '</td>
                <td>$' . number_format($item['quantity'] * $item['price'], 2) . '</td>
            </tr>';
    }
    
    $html .= '
        </tbody>
    </table>
    <p><strong>Итого: $' . number_format($invoice['total'], 2) . '</strong></p>
    ';
    
    # Генерация PDF
    $mpdf = new \Mpdf\Mpdf();
    $mpdf->WriteHTML($html);
    $mpdf->Output('invoice_' . $invoiceId . '.pdf', 'D');
}

Заключение

Преобразование HTML в PDF на Linux-серверах с использованием PHP можно успешно реализовать несколькими подходами в зависимости от ваших конкретных требований:

  1. Для высококачественной поддержки CSS: используйте решения на основе headless-браузеров, такие как Puppeteer или WeasyPrint, которые обеспечивают наиболее точное рендеринг, но требуют дополнительных системных зависимостей.

  2. Для нативных решений PHP: Mpdf предлагает лучший баланс функций и производительности, решая многие ограничения, с которыми вы столкнулись при использовании DOMPDF, особенно для таблиц и использования памяти.

  3. Для сложных макетов: рассмотрите wkhtmltopdf через Snappy, который использует движок рендеринга WebKit для отличной поддержки CSS, оставаясь доступным через PHP.

  4. Для оптимизации производительности: реализуйте кэширование, фоновую обработку и техники управления памятью для эффективной работы с большими документами.

Рекомендуемые следующие шаги: начните с Mpdf из-за его легкой интеграции и хорошей производительности, затем исследуйте решения на основе headless-браузеров, если вам нужна поддержка продвинутого CSS. Всегда тестируйте с вашим конкретным HTML-контентом, чтобы убедиться, что совместимость и производительность соответствуют вашим требованиям.

Источники

  1. Документация Mpdf - Официальная PHP библиотека для генерации PDF
  2. Документация Puppeteer - API headless Chrome для Node.js
  3. wkhtmltopdf - Инструмент командной строки для преобразования HTML в PDF
  4. Документация WeasyPrint - Преобразователь HTML/CSS в PDF
  5. Документация TCPDF - PHP библиотека для PDF
  6. PHP-обертка Snappy - PHP интерфейс для wkhtmltopdf
  7. Лучшие практики производительности PHP - Управление памятью