Создание PDF в Node.js с помощью PDFKit

Введение
PDFKit — это библиотека для Node.js, предназначенная для создания и изменения PDF‑документов программно. Она предоставляет понятный API для добавления текста, изображений, векторной графики и управления страницами. Подходит для отчётов, счётов, чеков, билетов и любых документов, которые нужно сгенерировать на сервере.
Определение: PDF (Portable Document Format) — формат для представления документов, сохраняющий оформление и внешний вид на любых устройствах.
Быстрая проверка окружения
Перед началом убедитесь, что на машине установлены Node.js и npm. В терминале выполните:
node -v
npm -vЕсли команды возвращают версии — можно продолжать.
Установка PDFKit
В каталоге проекта запустите:
npm install pdfkitЭто добавит pdfkit в зависимости проекта.
Важная заметка: если ваш проект требует встроенных шрифтов или поддержки шрифтовых файлов TrueType/OpenType, убедитесь, что эти файлы доступны в репозитории или в окружении выполнения.
Создание простого PDF — минимальный пример
Основные шаги:
- Подключить модуль pdfkit и fs.
- Создать экземпляр PDFDocument.
- Пропайпить поток в файл или ответ HTTP.
- Заполнить содержимым.
- Вызвать doc.end() для завершения потока.
Пример сохранения в файл:
const PDFDocument = require('pdfkit');
const fs = require('fs');
const doc = new PDFDocument();
doc.pipe(fs.createWriteStream('MyPDFDoc.pdf'));
doc.text('Coding is Easy!');
doc.end();Запустите: node <имя_скрипта>. После выполнения в рабочем каталоге появится MyPDFDoc.pdf.
Потоковая отправка PDF в HTTP‑ответ
Часто PDF генерируют на сервере и сразу отправляют в браузер без сохранения на диск.
Пример Express.js:
const express = require('express');
const PDFDocument = require('pdfkit');
const app = express();
app.get('/invoice', (req, res) => {
const doc = new PDFDocument();
res.setHeader('Content-Type', 'application/pdf');
res.setHeader('Content-Disposition', 'attachment; filename=invoice.pdf');
doc.pipe(res);
doc.text('Invoice #12345');
doc.end();
});
app.listen(3000);Важно: заголовки нужно установить до начала передачи данных.
Добавление текста и форматирование
Основное: метод text() добавляет текст и перемещает текущую позицию курсора на новую строку. По умолчанию PDFKit использует стандартные 14 шрифтов PDF, но можно подключать внешние.
Примеры: позиционирование и перемещение:
doc.text('Coding is Easy!');
// Позиционирование по координатам (x, y)
doc.text('Coding is Easy!', 100, 100);
// Переместиться вниз на 3 строки
doc.moveDown(3);
// Переместиться вверх на 1 строку
doc.moveUp();Шрифты и размеры:
// Использование стандартного шрифта
doc.font('Times-Roman').text('Coding is Easy!');
// Цвет и размер
doc.fillColor('red').fontSize(8).text('Coding is Easy!');Если нужно подключить свой файл шрифта:
// Регистрация и использование локального TTF/OTF
// (если шрифт лежит в ./fonts/Custom.ttf)
doc.registerFont('Custom', 'fonts/Custom.ttf');
doc.font('Custom').text('Текст с пользовательским шрифтом');Добавление изображений
PDFKit поддерживает PNG и JPEG. Чтобы вставить изображение, передайте путь к файлу или Buffer.
// Вставить изображение в исходном размере
doc.image('path/to/image.jpeg');
// Указать ширину и/или высоту
doc.image('path/to/image.jpg', { width: 300 });
doc.image('path/to/image.jpg', { width: 300, height: 200 });Также можно задавать выравнивание и положение (x, y). При работе с большими изображениями учитывайте потребление памяти.
Управление страницами и размерами
Добавление новой страницы:
doc.addPage();Можно подписаться на событие добавления страницы:
doc.on('pageAdded', () => doc.text('Coding is Easy!'));Размеры страниц и ориентация задаются при создании документа или при добавлении страницы. Примеры стандартных размеров:
// Установить размер по умолчанию при создании документа
const docA5 = new PDFDocument({ size: 'A5' });
// Поменять размер при добавлении страницы
doc.addPage({ size: 'A7' });Установка полей (отступов) при добавлении страницы:
// Задаём разные отступы
doc.addPage({
margins: {
top: 72,
bottom: 72,
left: 50,
right: 50
}
});
// Один общий отступ для всех сторон
doc.addPage({ margin: 60 });Примечание по локализации единиц: в PDFKit размеры задаются в пунктах (points). 72 пункта = 1 дюйм.
Векторная графика: линии, прямоугольники, кривые
PDFKit позволяет рисовать простые фигуры:
// Рисуем линию
doc.moveTo(100, 150).lineTo(100, 250).stroke();
// Прямоугольник
doc.rect(100, 300, 200, 100).fill('#FF0000');Используйте save() и restore() для локального изменения состояния (цвет, ширина линии, трансформации).
Практические рекомендации и эвристики
- Минимизируйте встраивание больших растровых изображений — используйте оптимизированные форматы и размеры.
- Для таблиц используйте сторонние утилиты или прорисовывайте линии и текст вручную; PDFKit не содержит готовой реализации таблиц, но есть вспомогательные пакеты сообщества.
- Тестируйте генерацию на объёмах, похожих на продакшен, чтобы выявить утечки памяти.
- Всегда закрывайте поток вызовом doc.end(), иначе файл/ответ останутся неполными.
- Для многостраничных шаблонов выносите повторяющиеся элементы (шапка/подвал) в функцию, вызываемую при событии pageAdded.
Когда PDFKit может не подойти (ограничения)
- Если нужен точный рендер HTML/CSS в PDF (сложная верстка), лучше использовать движки, рендерящие HTML (Puppeteer, wkhtmltopdf).
- При большом количестве таблиц со сложным переносом текста возможно удобнее использовать специализированные конструкторы PDF или готовые шаблоны.
- Для массовой генерации миллионов документов обратите внимание на ресурсоёмкость и горизонтальное масштабирование сервиса генерации.
Альтернативы и сравнение (обзор)
- Puppeteer — рендерит HTML/CSS в PDF (хорошо для сложной верстки, но тяжелее по ресурсам).
- wkhtmltopdf — инструмент командной строки: преобразует HTML в PDF через WebKit.
- PDFMake — удобен для декларативного описания контента в формате JSON; проще для таблиц и структуры, но другой подход к API.
Выбор зависит от того, хотите ли вы генерировать PDF из HTML (Puppeteer/wkhtmltopdf) или формировать документ программно (PDFKit/PDFMake).
Контроль качества и тесты
Критерии приёмки:
- PDF откывается в основных ридерах (Adobe Reader, браузеры).
- Шрифты отображаются корректно; нет замены шрифта на системный.
- Все страницы содержат ожидаемый контент (шапка, тело, подвал).
- Сгенерированные файлы соответствуют ожидаемым размерам и числу страниц.
Тестовые сценарии:
- Генерация документа с разными шрифтами и языками (кириллица, латиница).
- Вставка большого изображения и проверка, что файл не превышает допустимого размера.
- Стресс‑тест: одновременные запросы генерации PDF и контроль потребления памяти.
Role‑based checklist (проверка ролям)
Разработчик:
- Установил pdfkit и зависимости.
- Написал модуль генерации с учётом ошибок и логированием.
DevOps:
- Настроил лимиты памяти/пул воркеров для сервиса генерации.
- Обеспечил хранение шрифтов в доступном и защищённом месте.
QA:
- Проверил отображение на целевых ридерах.
- Запустил регрессионные тесты формата и содержимого.
Мини‑методология: как организовать генерацию PDF в проекте
- Выделите отдельный модуль/сервис для генерации PDF.
- Создайте шаблоны и функции для повторяющихся блоков (шапка, подвал).
- Поддерживайте централизованное хранилище шрифтов и ресурсов (изображения).
- Логируйте ошибки и время генерации; добавьте метрики (latency, success/failure).
- Тестируйте с эталонными примерами и добавляйте их в CI.
Пример расширенного шаблона счёта (invoice)
Короткий пример структуры исходного кода для счета:
function renderHeader(doc, invoice) {
doc.fontSize(20).text('Company Name', 50, 50);
doc.fontSize(10).text(`Invoice #${invoice.number}`, 50, 80);
}
function renderFooter(doc) {
doc.fontSize(10).text('Thank you for your business.', 50, doc.page.height - 50);
}
function renderTable(doc, items) {
// простая прорисовка строк таблицы через text() и линии
}
function createInvoice(invoice, path) {
const PDFDocument = require('pdfkit');
const fs = require('fs');
const doc = new PDFDocument({ margin: 50 });
doc.pipe(fs.createWriteStream(path));
renderHeader(doc, invoice);
renderTable(doc, invoice.items);
renderFooter(doc);
doc.end();
}Отладка и распространённые ошибки
- «PDF не открывается» — скорее всего, забыли doc.end() или в поток уже записаны данные до установки заголовков.
- «Шрифт не отображается» — путь к файлу шрифта неверен или шрифт не поддерживает нужные глифы.
- «Память растёт» — проверьте, не храните в памяти большие картинки как строки; используйте потоки и оптимизацию изображений.
Важно: логируйте стек ошибок и сохраняйте образцы неудачных входных данных для воспроизведения.
Безопасность и приватность
- Если вы генерируете PDF с персональными данными, храните и передавайте их в зашифрованном виде и соблюдайте правила GDPR/локального законодательства.
- Минимизируйте права файлов и доступ к шрифтам/ресурсам.
Ментальные модели при выборе инструмента
- Если нужно «программно» рисовать документ — PDFKit.
- Если нужно «верстать» через HTML/CSS — Puppeteer или wkhtmltopdf.
- Если нужен декларативный JSON‑подход — PDFMake.
Короткий глоссарий
- doc.pipe — метод для передачи данных из PDFKit в поток (файл/HTTP‑ответ).
- doc.end — завершает генерацию документа и закрывает поток.
- points/пункты — единица измерения в PDF (72 пункта = 1 дюйм).
Итог и рекомендации
PDFKit — стабильный выбор, если вы хотите гибко управлять содержимым PDF программно. Для сложной HTML‑верстки рассмотрите рендеринг через браузерный движок. Всегда тестируйте генерацию на реальных данных и предусмотрите обработку ошибок и ограничение ресурсов.
Important: всегда вызываетe doc.end(), тестируйте вывод в целевых PDF‑ридерах и следите за использованием памяти в нагрузочных сценариях.
Краткое резюме:
- PDFKit удобен для программной генерации PDF из Node.js.
- Поддерживает текст, изображения, векторную графику и управление страницами.
- При необходимости комбинируйте с другими инструментами (Puppeteer, PDFMake) в зависимости от задач.