Конвертация HTML в PDF: от верстки до готового файла
Чтобы сделать PDF из HTML-кода или веб-страницы, необходимо использовать браузерный движок (например, через Puppeteer или Playwright) или специализированные утилиты (wkhtmltopdf), предварительно настроив CSS-стили для печати (@media print). Это гарантирует, что документ будет выглядеть идентично на любом устройстве, сохранит разрывы страниц и корректно отобразит шрифты.
Зачем нужен PDF вместо HTML
HTML отлично подходит для экранного чтения, но плох для фиксации макета. PDF необходим, когда требуется:
- Неизменяемость: гарантия, что клиент увидит документ именно так, как вы задумали (счета, акты, договоры).
- Печать: точное соответствие размера листа (A4, Letter) и полей.
- Архивация: сохранение «слепка» страницы на конкретную дату.
Золотое правило: не создавайте отдельную HTML-версию для печати. Используйте один источник истины и скрывайте лишние элементы через CSS-медиазапрос @media print.
Шаг 1. Подготовка HTML и CSS для печати
Качество итогового PDF на 90% зависит от стилей. Браузеры по умолчанию печатают страницы плохо: обрезают картинки, оставляют колонтитулы браузера и игнорируют фоны.
Базовые настройки @page и @media print
Добавьте эти стили в ваш CSS-файл или тег <style>:
/* Настройки листа */
@page {
size: A4; /* Или Letter, Legal */
margin: 15mm; /* Поля для печати */
}
/* Стили только для печати/PDF */
@media print {
/* Скрываем навигацию, кнопки, футеры сайта */
header, nav, .no-print, button, .ads-banner {
display: none !important;
}
/* Основной контент */
body {
font-size: 12pt; /* Оптимально для чтения с бумаги */
color: #000;
background: #fff;
}
/* Ссылки: показываем URL в скобках */
a[href]:after {
content: " (" attr(href) ")";
font-size: 0.8em;
color: #666;
}
/* Изображения не должны вылезать за границы */
img {
max-width: 100% !important;
page-break-inside: avoid; /* Не разрывать картинку между страницами */
}
/* Таблицы: не разрывать строки */
tr {
page-break-inside: avoid;
}
/* Принудительный разрыв страницы */
.page-break {
page-break-before: always; /* Старый синтаксис для совместимости */
break-before: page; /* Новый стандарт */
}
}
Свойство print-background: true (или аналог в инструменте генерации) должно быть включено явно, иначе цветные плашки и фоны станут белыми.
Шаг 2. Выбор инструмента для генерации
Вариант А: Puppeteer / Playwright (Рекомендуемый)
Идеально для Node.js-проектов. Использует реальный Chromium, поэтому поддерживает современный CSS (Grid, Flexbox) и JavaScript.
Пример на Puppeteer:
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
// 1. Загрузка из URL
// await page.goto('https://example.com/invoice/123', { waitUntil: 'networkidle0' });
// 2. Или загрузка из HTML-строки
const htmlContent = `<html><body><h1>Счет №1</h1>...</body></html>`;
await page.setContent(htmlContent, { waitUntil: 'networkidle0' });
// 3. Генерация PDF
await page.pdf({
path: 'invoice.pdf',
format: 'A4',
printBackground: true, // Важно для цветов
margin: {
top: '10mm',
right: '10mm',
bottom: '10mm',
left: '10mm'
}
});
await browser.close();
})();
Плюсы:
- Полная поддержка современного веба.
- Можно выполнять JS перед печатью (например, подгрузить данные через API).
- Корректная работа со шрифтами.
Вариант Б: wkhtmltopdf
Старый, но надежный инструмент на базе движка WebKit (Qt). Работает как отдельная утилита командной строки.
wkhtmltopdf --page-size A4 --margin-top 10mm input.html output.pdf
Плюсы:
- Не требует запуска тяжелого браузера.
- Низкое потребление памяти при массовой генерации простых документов.
Минусы:
- Плохая поддержка современного CSS (Flexbox/Grid могут работать некорректно).
- Проект находится в режиме поддержки, обновления редки.
Вариант В: Браузерная печать (Ручной режим)
Если нужно сделать PDF разово без кода:
- Откройте страницу в Chrome/Edge.
- Нажмите
Ctrl + P(Windows) илиCmd + P(Mac). - В пункте «Принтер» выберите «Сохранить как PDF».
- В дополнительных настройках включите «Фоновая графика».
Сравнение методов
| Метод | Качество верстки | Сложность внедрения | Производительность | Для чего подходит |
|---|---|---|---|---|
| Puppeteer/Playwright | Отличное (Chromium) | Средняя (Node.js) | Средняя (тяжелый процесс) | Счета, сложные отчеты, SPA |
| wkhtmltopdf | Хорошее (устаревшее) | Низкая (CLI) | Высокая | Простые статьи, старые сервера |
| Библиотеки JS (jsPDF) | Слабое (верстка вручную) | Высокая | Высокая | Простые чеки, клиентская генерация |
| Онлайн-конвертеры | Зависит от сервиса | Нулевая | Н/А | Разовые задачи, конфиденциальные данные не использовать |
Избегайте онлайн-конвертеров для документов с персональными данными (паспорта, счета, договоры). Вы передаете информацию третьим лицам. Используйте локальную генерацию.
Частые ошибки и как их исправить
-
Обрезанный текст или картинки.
- Причина: Элемент не поместился на страницу.
- Решение: Добавьте
page-break-inside: avoid;для блоков, которые нельзя разрывать (карточки товаров, строки таблиц).
-
Отсутствуют фоновые цвета.
- Причина: Браузеры экономят краску.
- Решение: В коде генерации установите флаг
printBackground: true(Puppeteer) или-B(wkhtmltopdf).
-
Шрифты выглядят иначе или исчезли.
- Причина: Шрифт не успел загрузиться или недоступен в системе рендеринга.
- Решение: Используйте веб-шрифты (Google Fonts) с
display: swapили встраивайте шрифты в base64 внутри CSS. В Puppeteer дождитесь события загрузки шрифтов.
-
Лишние пустые страницы в конце.
- Причина: Небольшие отступы или пустые блоки
div. - Решение: Проверьте высоту контента. Иногда помогает уменьшение
marginв@pageили скрытие пустых элементов через JS перед генерацией.
- Причина: Небольшие отступы или пустые блоки
FAQ
Можно ли сделать PDF прямо в браузере клиента (без сервера)?
Да, используя библиотеки вроде html2pdf.js или jspdf. Однако качество верстки будет ниже, чем при серверной генерации через Headless Chrome, так как эти библиотеки часто «рисуют» PDF как изображение или упрощенную векторную графику.
Как добавить нумерацию страниц?
В чистом CSS это сложно. В @page можно использовать счетчики:
@page {
@bottom-right {
content: counter(page) " из " counter(pages);
}
}
Поддержка этого свойства варьируется в разных инструментах. В Puppeteer надежнее добавлять колонтитулы через HTML-шаблонизатор перед генерацией.
Почему таблица разрывается посередине строки?
Убедитесь, что для тега tr или td установлено свойство break-inside: avoid; (или page-break-inside: avoid;).