Конвертация HTML/XML в PDF без потери стилей и символов
Чтобы качественно конвертировать HTML или XML в PDF с сохранением сложной верстки и корректным отображением кириллицы, необходимо использовать движки рендеринга на базе Chromium (например, Puppeteer) для HTML или процессоры XSL-FO (например, Apache FOP) для XML. Ключевые условия успеха: принудительное использование кодировки UTF-8, встраивание (embedding) шрифтов в итоговый файл и адаптация CSS под печатные носители через медиа-запрос @media print.
Ниже рассмотрены проверенные методы, инструменты и настройки, которые гарантируют предсказуемый результат.
Краткий ответ: Для HTML лучше всего подходит headless-браузер (Chrome/Puppeteer), так как он поддерживает современные стандарты CSS. Для строго структурированного XML используйте связку XSLT + XSL-FO + Apache FOP. Всегда встраивайте шрифты в PDF.
Основные проблемы при конвертации
Прежде чем выбирать инструмент, важно понять, где чаще всего ломается процесс:
- Кодировка символов. Если исходный файл не в UTF-8 или шрифт не поддерживает нужные глифы (например, кириллицу), в PDF появятся «кракозябры» или пустые квадраты.
- Потеря стилей. Многие легкие конвертеры игнорируют Flexbox, Grid или сложные селекторы CSS3.
- Разрывы страниц. Текст может обрезаться посередине строки или таблицы, если не заданы правила
break-inside. - Отсутствующие ресурсы. Изображения и шрифты, подключенные относительными путями, могут не загрузиться при серверной генерации.
Метод 1: Конвертация HTML в PDF (Headless Chrome / Puppeteer)
Это самый популярный способ для веб-разработчиков. Он использует тот же движок, что и браузер пользователя, поэтому визуальная точность максимальна.
Почему это работает
Headless Chrome полностью рендерит страницу, выполняя JavaScript и применяя CSS, а затем «печатает» её в PDF.
Настройка окружения
Убедитесь, что ваш HTML-документ соответствует стандартам:
- Кодировка указана явно:
<meta charset="UTF-8">. - Шрифты подключены корректно (лучше использовать локальные файлы или надежные CDN).
Пример конфигурации (Node.js + Puppeteer)
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
// Переходим на страницу или загружаем HTML из строки
await page.goto('https://example.com/invoice.html', { waitUntil: 'networkidle0' });
await page.pdf({
path: 'document.pdf',
format: 'A4',
printBackground: true, // Важно: печатать фоновые цвета и изображения
margin: {
top: '20mm',
right: '20mm',
bottom: '20mm',
left: '20mm'
},
preferCSSPageSize: true // Использовать размеры из CSS @page
});
await browser.close();
})();
Совет по CSS: Используйте медиа-запрос @media print для скрытия ненужных элементов (меню, футеров сайта) и настройки разрывов страниц.
@media print {
.no-print { display: none; }
tr { break-inside: avoid; } /* Запрет разрыва строк таблицы */
}
Метод 2: Конвертация XML в PDF (XSL-FO + Apache FOP)
Если у вас есть чистые данные в XML (например, выгрузки из 1С, банковские отчеты), прямая конвертация в HTML может быть избыточной. Стандарт индустрии здесь — XSL-FO (Formatting Objects).
Алгоритм действий
- XML: Исходные данные.
- XSLT: Трансформация XML в промежуточный формат XSL-FO (описание разметки страницы, колонок, шрифтов).
- FOP (Apache FOP): Процессор, который превращает FO-файл в PDF.
Преимущества
- Полный контроль над позиционированием элементов (абсолютная точность).
- Идеально для документов со строгой структурой (счета, акты, накладные).
- Независимость от браузерных движков.
Настройка шрифтов в FOP
Чтобы кириллица отображалась корректно, необходимо зарегистрировать шрифты в конфигурационном файле fop.xconf:
<fonts>
<font embed-url="fonts/arial.ttf">
<font-triplet name="Arial" style="normal" weight="normal"/>
</font>
<font embed-url="fonts/arialbd.ttf">
<font-triplet name="Arial" style="normal" weight="bold"/>
</font>
</fonts>
Сравнение инструментов
| Инструмент | Тип источника | Качество CSS | Сложность настройки | Лицензия |
|---|---|---|---|---|
| Puppeteer / Playwright | HTML | Отличное (CSS3) | Низкая | Open Source |
| wkhtmltopdf | HTML | Среднее (устарел) | Низкая | Open Source |
| PrinceXML | HTML/XML | Идеальное (Paged Media) | Средняя | Коммерческая |
| Apache FOP | XML (FO) | Специфичное (FO) | Высокая | Open Source |
| WeasyPrint | HTML | Хорошее (CSS Paged) | Средняя | Open Source |
Внимание: wkhtmltopdf больше не поддерживается активно и использует старый движок WebKit. Для современных проектов рекомендуется использовать Puppeteer или WeasyPrint.
Работа с кодировками и шрифтами
Самая частая ошибка — отсутствие символов в итоговом файле. Вот чеклист для избежания проблем:
- Всегда используйте UTF-8. Это должно быть указано и в мета-теге HTML, и в заголовках HTTP, и в настройках процессора XML.
- Встраивание шрифтов (Embedding). Не надейтесь на то, что на компьютере пользователя или сервера установлен нужный шрифт.
- В CSS:
@font-faceс указанием пути к файлу.ttfили.woff2. - В настройках конвертера: включите флаг
embedFonts(или аналогичный).
- В CSS:
- Проверка поддержки глифов. Убедитесь, что выбранный шрифт содержит кириллические символы. Стандартные шрифты вроде Times New Roman или Arial обычно подходят, но кастомные веб-шрифты нужно проверять.
Частые ошибки и их решения
1. Обрезанные таблицы
Проблема: Строка таблицы разрывается между страницами, часть текста исчезает. Решение: Добавьте в CSS:
table, tr, td, th {
break-inside: avoid;
}
2. Пустые квадраты вместо текста
Проблема: Шрифт не встроился или не поддерживает язык. Решение: Проверьте логи конвертера на ошибки загрузки шрифтов. Убедитесь, что путь к шрифту абсолютный или корректно указан относительно рабочей директории.
3. Неверные отступы и поля
Проблема: Принтер добавляет свои поля поверх ваших.
Решение: В настройках PDF явно задавайте margin: 0 в CSS для body и управляйте полями только через параметры инструмента (например, margin в Puppeteer) или через @page { margin: 2cm; }.
FAQ
Вопрос: Можно ли конвертировать сложный HTML с JavaScript-графиками?
Да, если использовать Headless Chrome (Puppeteer/Playwright). Перед генерацией PDF нужно дождаться полной отрисовки графиков, используя waitUntil: 'networkidle0' или ожидание конкретного селектора.
Вопрос: Какой инструмент выбрать для массовой генерации счетов? Если счета формируются из XML-данных — Apache FOP или коммерческий PrinceXML (он умеет принимать и HTML, и XML). Если верстка делается на HTML/CSS — Puppeteer. PrinceXML часто предпочтительнее для типографского качества, но он платный.
Вопрос: Как уменьшить размер итогового PDF? Отключите встраивание неиспользуемых символов шрифта (subsetting). Большинство современных инструментов делают это по умолчанию, но стоит проверить настройки. Также оптимизируйте изображения перед вставкой в HTML/XML.
Вопрос: Поддерживает ли конвертация гиперссылки?
Да, стандартные теги <a href="..."> сохраняются как активные ссылки в PDF при использовании любых современных конвертеров.