Назад к блогу
08.04.2026 14:44:20

Как генерировать PDF в Битрикс через wkhtmltopdf: стили, картинки и шапка на каждой странице

В проектах на 1С-Битрикс часто требуется формировать PDF-файлы прямо из админки или пользовательского интерфейса: коммерческие предложения, счета, прайсы, каталоги, спецификации, подборки товаров или внутренние документы для менеджеров.

Один из самых практичных способов решить такую задачу — использовать wkhtmltopdf. Он позволяет собирать PDF из обычного HTML и CSS, то есть верстать документ почти так же, как обычную страницу сайта. Но есть важные нюансы: стили могут не подключаться, картинки могут пропадать после редактирования, а шапка не будет повторяться на каждой странице, если сделать её неправильно.

Ниже разберем рабочую схему генерации PDF в Битрикс: как подключить wkhtmltopdf, как передавать HTML, как правильно вставлять стили и изображения, как сделать повторяющуюся шапку и какие ошибки чаще всего возникают в реальных проектах.

Когда такая задача возникает в Битрикс

Генерация PDF обычно нужна не сама по себе, а как часть бизнес-процесса на сайте. Например, менеджер выбирает товары, меняет количество, добавляет логотип или реквизиты, нажимает кнопку — и система формирует готовое коммерческое предложение в PDF.

Такие задачи относятся не к простой настройке CMS, а к полноценной PHP-доработке проекта. Нужно учитывать структуру инфоблоков, права пользователей, загрузку файлов, редактор контента, временные директории, серверное окружение и сам процесс генерации документа.

Похожие задачи мы решаем в рамках услуги доработки сайтов на 1С-Битрикс и PHP: подключение нового функционала, исправление ошибок после обновлений, доработка компонентов, интеграции и автоматизация внутренних процессов.

Что требуется на сервере

Перед тем как писать код, нужно убедиться, что сервер готов к генерации PDF. Если окружение настроено неправильно, проблема может быть не в Битрикс и не в PHP-коде, а в правах, путях, шрифтах или невозможности запустить бинарник.

  • wkhtmltopdf должен быть установлен на сервере и доступен по понятному пути, например /usr/bin/wkhtmltopdf.
  • У PHP-пользователя должны быть права на запуск бинарного файла.
  • Нужен каталог для временных HTML и PDF-файлов, куда сайт может записывать данные.
  • Желательно заранее проверить системные шрифты, особенно если PDF должен выглядеть как фирменный документ.
  • Для локальных картинок и CSS может понадобиться параметр --enable-local-file-access.

На практике генерация PDF часто вскрывает проблемы серверной части: старые библиотеки, ограничения хостинга, запрет на запуск внешних процессов, ошибки прав доступа или отсутствие нужных шрифтов.

Если на сайте уже есть проблемы с окружением, долгой генерацией страниц, ошибками PHP или нестабильной работой после обновлений, перед внедрением PDF-генерации полезно провести технический аудит сайта и серверной части.

Как обычно устроена генерация PDF в Битрикс

В типовом сценарии PDF-документ собирается не вручную, а из данных сайта. Например, менеджер в интерфейсе выбирает товары, количество, цвет, логотип, реквизиты или отдельные текстовые блоки.

Дальше система может работать по такой схеме:

  • данные пользователя сохраняются в JSON или в отдельную таблицу;
  • шаблон собирает HTML-блоки документа;
  • отдельно формируется шапка, основная часть и подвал;
  • wkhtmltopdf получает итоговый HTML;
  • PDF сохраняется во временную папку, файловую систему или инфоблок;
  • готовый файл отдается пользователю для скачивания.

Важно понимать: wkhtmltopdf — это не редактор PDF. Он работает как браузер для печати. Поэтому всё, что должно попасть в PDF, нужно заранее корректно подготовить в HTML и CSS.

Базовая генерация PDF в компоненте Битрикс

Минимальная рабочая схема — собрать HTML-документ и передать его в wkhtmltopdf. Для стабильной работы удобно использовать proc_open и передавать HTML через stdin.

$htmlBody = (string)($json['header'] . $json['body'] . $json['footer']);

$html = '<!doctype html>
<html lang="ru">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <style>
        html, body {
            font-family: "Times New Roman", Times, serif;
            font-size: 12px;
        }
    </style>
</head>
<body>' . $htmlBody . '</body>
</html>';

$wkhtml = '/path/to/wkhtmltopdf';

$tmpDir = $_SERVER['DOCUMENT_ROOT'] . '/upload/tmp/kp';

if (!is_dir($tmpDir)) {
    @mkdir($tmpDir, 0775, true);
}

$pdfPath = $tmpDir . '/kp_' . $USER->GetID() . '_' . date('Ymd_His') . '.pdf';

$cmd = escapeshellcmd($wkhtml)
    . ' --encoding utf-8 --page-size A4'
    . ' --margin-top 10mm --margin-right 10mm --margin-bottom 10mm --margin-left 10mm'
    . ' --enable-local-file-access --print-media-type'
    . ' - ' . escapeshellarg($pdfPath) . ' 2>&1';

$descriptors = [
    0 => ['pipe', 'r'],
    1 => ['pipe', 'w'],
    2 => ['pipe', 'w'],
];

$process = proc_open($cmd, $descriptors, $pipes);

if (!is_resource($process)) {
    throw new \RuntimeException('wkhtmltopdf не запустился');
}

fwrite($pipes[0], $html);
fclose($pipes[0]);

$stdout = stream_get_contents($pipes[1]);
fclose($pipes[1]);

$stderr = stream_get_contents($pipes[2]);
fclose($pipes[2]);

$code = proc_close($process);

if ($code !== 0 || !is_file($pdfPath) || filesize($pdfPath) < 1000) {
    throw new \RuntimeException("Ошибка генерации PDF. Code: " . $code . "\n" . $stdout . "\n" . $stderr);
}

Такая схема удобна тем, что HTML не нужно сохранять в отдельный файл перед генерацией PDF. Но у этого подхода есть важный нюанс: относительные пути к стилям и картинкам могут не работать.

Почему стили в PDF могут не подключаться

Частая ошибка — передавать HTML через stdin и ожидать, что wkhtmltopdf сам поймет относительные пути:

<link rel="stylesheet" href="/local/templates/site/css/kp.css">

<img src="/upload/logo.png" alt="">

<div style="background-image:url('/upload/bg.png')"></div>

Для обычной страницы браузер понимает, что /upload/logo.png находится на текущем домене. Но при генерации через stdin у документа нет нормального базового URL. Поэтому wkhtmltopdf может просто не понять, откуда брать CSS, фоновые изображения и картинки.

Надежнее всего использовать один из двух вариантов:

  • вставлять CSS прямо внутрь документа через <style>;
  • использовать абсолютные URL или тег <base href="https://domain/">.

Если PDF должен стабильно формироваться на разных окружениях, лучше не рассчитывать на относительные пути. Это особенно важно для проектов, где менеджеры редактируют шаблоны документов через визуальный редактор.

Как правильно подключать CSS для PDF

Самый предсказуемый вариант — читать CSS-файл через PHP и вставлять его прямо в HTML. Тогда wkhtmltopdf не нужно отдельно загружать файл стилей по URL.

$cssPath = $_SERVER['DOCUMENT_ROOT'] . '/local/templates/site/css/pdf.css';
$css = is_file($cssPath) ? file_get_contents($cssPath) : '';

$html = '<!doctype html>
<html lang="ru">
<head>
    <meta charset="utf-8">
    <style>' . $css . '</style>
</head>
<body>' . $htmlBody . '</body>
</html>';

Такой подход особенно полезен, если нужно исправить верстку PDF-документа, привести коммерческое предложение к фирменному стилю, обновить дизайн шапки или подвала документа, настроить таблицы товаров и сделать PDF читаемым на нескольких страницах.

По сути это такая же задача, как исправить верстку сайта или доработать шаблон Битрикс, только результатом становится не страница в браузере, а PDF-файл.

Картинки в PDF: почему они пропадают

Еще одна частая проблема — изображения отображаются в админке или в превью, но не попадают в PDF. Причин может быть несколько:

  • путь к картинке относительный;
  • wkhtmltopdf не имеет доступа к локальному файлу;
  • сервер не может обратиться к домену по HTTPS;
  • визуальный редактор изменил путь при сохранении;
  • картинка закрыта авторизацией или недоступна по прямой ссылке.

Для стабильной генерации PDF лучше использовать абсолютные URL:

<img src="https://www.site.ru/upload/logo.png" alt="Логотип">

Либо добавлять базовый путь в HTML-документ:

<base href="https://www.site.ru/">

Тогда даже изображения вида /upload/logo.png будут корректно разрешаться относительно нужного домена.

Почему картинки пропадают после TinyMCE

В реальных проектах часто бывает так: менеджер открыл шаблон документа, поправил текст в редакторе, сохранил — и после этого картинки перестали попадать в PDF.

Обычно причина в настройках TinyMCE. Редактор может преобразовывать абсолютные ссылки в относительные, убирать часть атрибутов или менять URL при сохранении HTML.

Для PDF-шаблонов лучше явно настроить работу с URL:

tinymce.init({
    selector: '.editable, .editable-header',
    convert_urls: false,
    relative_urls: false,
    remove_script_host: false,
    document_base_url: 'https://www.site.ru/',
    extended_valid_elements: 'img[src|alt|title|width|height|style|class]'
});

Эти настройки помогают сохранить корректные пути к изображениям и не ломать HTML после редактирования. Особенно это важно, если в PDF используются логотипы, подписи, печати, изображения товаров или фирменные графические элементы.

Шапка на каждой странице PDF

Если просто вставить шапку в начало HTML-документа, она появится только на первой странице. Для коммерческого предложения или прайса этого часто недостаточно: заказчик хочет видеть логотип, контакты или реквизиты на каждой странице PDF.

Правильный способ для wkhtmltopdf — использовать параметр --header-html. В этом случае шапка передается отдельным HTML-файлом и повторяется на каждой странице документа.

Общая схема такая:

  • собрать HTML шапки;
  • сохранить его во временный файл;
  • передать путь к файлу через --header-html;
  • увеличить верхний отступ через --margin-top;
  • при необходимости настроить расстояние через --header-spacing.
$headerHtml = '<!doctype html>
<html lang="ru">
<head>
    <meta charset="utf-8">
    <style>
        body {
            margin: 0;
            font-family: "Times New Roman", Times, serif;
            font-size: 12px;
        }
    </style>
</head>
<body>' . $adminHeader . '</body>
</html>';

$headerPath = $tmpDir . '/kp_header_' . $USER->GetID() . '_' . date('Ymd_His') . '.html';

file_put_contents($headerPath, $headerHtml);

$cmd = escapeshellcmd($wkhtml)
    . ' --encoding utf-8 --page-size A4'
    . ' --margin-top 35mm --margin-right 10mm --margin-bottom 15mm --margin-left 10mm'
    . ' --header-html ' . escapeshellarg($headerPath)
    . ' --header-spacing 5'
    . ' --enable-local-file-access --print-media-type'
    . ' - ' . escapeshellarg($pdfPath) . ' 2>&1';

Если шапка наезжает на контент, нужно увеличить --margin-top. Если между шапкой и текстом слишком большой отступ — уменьшить --margin-top или --header-spacing.

Общая шапка для всех менеджеров

В некоторых проектах менеджерам разрешают редактировать тело коммерческого предложения, но не дают менять фирменную шапку. Это правильный подход, если нужно сохранить единый стиль документов: логотип, контакты, реквизиты, адрес, телефоны и юридическую информацию.

Для этого можно хранить эталонную шапку у администратора, а при генерации PDF подставлять её всем пользователям. Пользователь редактирует только основную часть документа, а шапка всегда берется из одного проверенного шаблона.

define('KP_HEADER_USER_ID', 587);

$jsonUser = $obj->findByUserGetJSON($USER->GetID());
$jsonAdmin = $obj->findByUserGetJSON(KP_HEADER_USER_ID);

$adminHeader = (string)($jsonAdmin['header'] ?? '');
$htmlBody = $adminHeader . (string)($jsonUser['body'] ?? '') . (string)($jsonUser['footer'] ?? '');

Такой подход помогает не ломать общий дизайн документов и при этом дает менеджерам гибкость в работе с содержанием коммерческого предложения.

Типовые проблемы при генерации PDF

PDF обрезается по ширине

Если PDF обрезается, часто причина в фиксированной ширине макета. Например, если задать телу документа ширину в пикселях, она может не совпасть с реальной областью печати A4.

  • Не задавайте жесткую ширину вроде body { width: 930px; } без необходимости.
  • Для A4 удобнее использовать миллиметры, например width: 190mm.
  • Учитывайте поля страницы: A4 имеет ширину 210 мм, но часть пространства занимают отступы.
  • Если документ всё равно не помещается, можно уменьшить масштаб через --zoom 0.9.

Не загружаются изображения

Если картинки не попадают в PDF, в первую очередь нужно проверить не HTML, а доступность самих файлов.

  • Используйте абсолютные URL для img src.
  • Проверьте, открывается ли картинка по прямой ссылке без авторизации.
  • Проверьте доступ сервера к домену с изображениями.
  • Для локальных файлов используйте --enable-local-file-access.
  • При необходимости добавьте --allow для нужной директории.

После редактирования в визуальном редакторе ломается HTML

Если после TinyMCE пропадают картинки, меняются стили или исчезают нужные атрибуты, нужно отдельно настроить редактор под PDF-шаблоны.

  • Отключить автоматическое преобразование URL.
  • Запретить превращение абсолютных ссылок в относительные.
  • Разрешить нужные атрибуты для изображений.
  • Добавить базовый URL для документа.

wkhtmltopdf не запускается

Если PDF вообще не создается, нужно проверить путь к бинарнику, права на запуск, права на временную папку и вывод ошибок из stderr.

  • Проверьте путь к wkhtmltopdf через консоль.
  • Убедитесь, что файл можно выполнить.
  • Проверьте, может ли PHP писать во временную директорию.
  • Сохраняйте текст ошибки в лог, а не скрывайте его.

Подобные ситуации часто встречаются при исправлении ошибок в Битрикс после доработок, обновлений или переноса сайта. Сам код может быть правильным, но серверное окружение мешает задаче работать стабильно.

Что важно учитывать при внедрении PDF-генерации

Генерация PDF — это не только кнопка “Скачать”. В реальном проекте нужно продумать весь процесс:

  • где хранятся шаблоны документов;
  • кто может редактировать шапку, тело и подвал;
  • как сохраняются изображения;
  • какие стили используются для печати;
  • куда складываются временные файлы;
  • как очищаются старые PDF;
  • как логируются ошибки генерации;
  • как документ выглядит на нескольких страницах;
  • как защитить файлы от случайного доступа извне.

Если PDF используется для коммерческих предложений, счетов или прайсов, важно также проверить внешний вид: таблицы, отступы, фирменные цвета, логотип, повторяющуюся шапку, переносы строк и читаемость на печати.

Когда лучше делать отдельную доработку, а не временный костыль

Иногда PDF сначала делают “быстро”: собрать HTML, отправить в wkhtmltopdf и скачать файл. Но со временем появляются новые требования: разные шаблоны, разные менеджеры, редактируемые блоки, логотипы, товары, автоматическая подстановка реквизитов, хранение истории документов.

В таких случаях лучше сразу проектировать нормальную доработку:

  • отдельный компонент для генерации PDF;
  • понятную структуру хранения шаблонов;
  • отдельные права для менеджеров и администратора;
  • логирование ошибок;
  • очистку временных файлов;
  • единые стили для всех документов;
  • безопасную работу с изображениями и файлами.

Такой подход особенно важен для интернет-магазинов, B2B-каталогов, корпоративных сайтов и проектов, где менеджеры регулярно формируют документы для клиентов.

Посмотреть другие похожие задачи по разработке, интеграциям и техническим доработкам можно в разделе примеры выполненных работ.

Итог

wkhtmltopdf хорошо подходит для генерации PDF в Битрикс, если правильно подготовить HTML, CSS и серверное окружение. Главное — не воспринимать его как магический конвертер, который сам исправит пути, стили и изображения.

Чтобы PDF формировался стабильно, нужно использовать абсолютные пути или <base href>, вставлять важные стили прямо в документ, отдельно настраивать TinyMCE для сохранения картинок и использовать --header-html, если шапка должна повторяться на каждой странице.

Если на сайте нужно внедрить генерацию коммерческих предложений, прайсов, счетов или каталогов в PDF, можно обратиться через страницу контактов. Подробнее о подходе к разработке и сопровождению проектов можно прочитать на странице о студии.