jsPDF

jsPDF создаёт PDF-документы из JavaScript-кода: выводит текст и векторную графику, добавляет изображения, страницы, ссылки и поля форм, настраивает формат листа, шрифты, метаданные и сохраняет результат в файл, Blob, ArrayBuffer или строку данных.

Рабочий процесс строится вокруг объекта документа. Сначала задаются единицы измерения, ориентация и формат страницы, затем команды размещают содержимое по координатам, после чего результат открывается в окне предпросмотра или передаётся пользователю. Такой подход удобен для счетов, билетов, сертификатов, отчётов, этикеток и других документов, которые собираются из данных приложения.

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

Скачать jsPDF

Оценка 9.7 Рекомендуем
  • Редактирование PDF
  • Русский интерфейс
  • Просто новичкам
Скачать бесплатно на Windows
Лучшая альтернатива
jsPDF
Оценка 8.5
  • Нет визуального редактора
  • Шрифты подключаются вручную
  • HTML зависит от html2canvas
Скачать jsPDF
Загрузка начнётся после нажатия

Как устроена работа с jsPDF

Основной объект создаётся конструктором new jsPDF(). Он хранит список страниц, ресурсы шрифтов и изображений, графическое состояние, внутренние объекты PDF и параметры сериализации. После создания команды выполняются последовательно: если сначала установить шрифт и цвет, а затем вызвать text(), надпись получит эти свойства; если добавить страницу, дальнейшие команды попадут уже на неё. Поэтому шаблон документа удобно рассматривать как сценарий рисования сверху вниз, а не как редактируемую разметку с автоматическим потоком.

Минимальная последовательность состоит из импорта jsPDF, создания объекта, вызова text() и сохранения через save(). В приложении к ней обычно добавляют проверку данных, функцию для общей шапки, цикл по строкам, условие перехода на новую страницу и завершающий блок с реквизитами. Чем раньше координаты, размеры шрифтов и интервалы вынесены в именованные константы, тем легче менять оформление без поиска чисел по всему файлу.

Координатная система начинается в левом верхнем углу страницы: горизонтальная координата растёт вправо, вертикальная — вниз. Для текста переданная точка относится к базовой линии, а для прямоугольника — к его верхнему левому углу. Из-за этого одинаковая координата по вертикали не означает визуальное совпадение верхних краёв текста и графической фигуры. При выравнивании заголовка внутри цветной плашки учитывают высоту строки и метрики шрифта, а не просто ставят одинаковое значение y.

Форма React и сформированный jsPDF-документ в соседнем окне

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

Выбор формата страницы и единиц измерения

Конструктор принимает объект параметров. Свойство orientation задаёт книжную или альбомную ориентацию, unit определяет базовую единицу, а format выбирает именованный лист либо массив с шириной и высотой. Для европейских документов обычно удобно использовать миллиметры, для веб-макетов — пиксели или пункты, для полиграфических расчётов — пункты. Важно не смешивать величины из CSS и величины PDF без явного коэффициента: 100 пикселей изображения и 100 миллиметров на странице дают совершенно разный размер.

Размер рабочей области читается через doc.internal.pageSize.getWidth() и getHeight(). Это надёжнее жёстко заданных чисел, особенно когда один шаблон должен работать и в книжной, и в альбомной ориентации. Например, правую границу таблицы вычисляют как ширину страницы минус правое поле, а центр заголовка — как половину ширины. После добавления страницы другого формата размеры следует получить заново, потому что активный лист мог измениться.

Параметр precision влияет на число знаков в координатах и некоторых числовых операндах. Слишком грубое округление заметно на мелкой сетке, тонких линиях и повторяющихся элементах, а чрезмерная точность увеличивает текстовую часть PDF. userUnit применяется для нестандартного масштаба, но не заменяет аккуратного выбора основной единицы. Для большинства деловых шаблонов достаточно единой системы в миллиметрах и округления координат на уровне функций макета.

Практическая схема полей

Полезно объявить поля один раз: например, 15 мм слева и справа, 18 мм сверху и 15 мм снизу. Доступная ширина будет равна ширине листа за вычетом обоих горизонтальных полей. Доступная высота аналогично учитывает верх и низ. Все блоки получают не абсолютную ширину страницы, а эту внутреннюю область. Такой расчёт предотвращает случайный выход текста к краю и упрощает смену формата: достаточно пересчитать контейнер, а не переписывать каждую координату.

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

Текст: шрифт, размер, цвет и выравнивание

Метод text() принимает строку либо массив строк, координаты и объект параметров. Перед выводом выбираются гарнитура через setFont(), кегль через setFontSize() и цвет через setTextColor(). Встроенные PDF-шрифты удобны для латинских прототипов, но их набор символов ограничен. Для русскоязычного документа почти всегда требуется шрифт TrueType с кириллицей, иначе буквы заменяются пустыми знаками, искажёнными символами или вовсе не появляются.

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

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

Измерение строки до вывода

Методы измерения помогают принять решение до рисования. getTextWidth() возвращает ширину строки с учётом выбранного шрифта и размера, а getTextDimensions() даёт более развёрнутые размеры. Проверка нужна для номера заказа, длинного имени организации, артикула и любого поля с непредсказуемой длиной. Если строка не помещается, можно уменьшить кегль в допустимых пределах, перенести текст, сократить только отображаемую подпись или изменить ширину соседней колонки.

Не стоит автоматически уменьшать шрифт до нечитаемого размера. Лучше задать нижний предел и после его достижения переходить к переносу. Для кодов и идентификаторов, которые нельзя разрывать, полезно вывести значение отдельной строкой или расширить ячейку. Для обычного текста предпочтительнее splitTextToSize() либо параметр maxWidth, но высоту полученного массива строк всё равно нужно включить в расчёт следующего блока.

Перенос длинного текста и межстрочные интервалы

splitTextToSize() делит строку на фрагменты, которые помещаются в указанную ширину с учётом метрик активного шрифта. Полученный массив передают в text() или обрабатывают построчно. Это основной инструмент для адресов, комментариев, описаний товаров и примечаний. Он не знает о границах страницы, поэтому после разделения требуется вычислить высоту блока и решить, помещается ли весь абзац либо сколько строк можно вывести до перехода.

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

Демонстрация jsPDF с наложением длинного текста до корректного расчёта строк

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

Демонстрация jsPDF после корректного разбиения и расчёта высоты текста

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

Подключение кириллицы и собственных TTF-шрифтов

Для кириллицы выбирают TTF-файл, содержащий нужные глифы, и регистрируют его во внутренней виртуальной файловой системе. Последовательность состоит из addFileToVFS(), addFont() и setFont(). В браузере двоичное содержимое получают через fetch, преобразуют в подходящую строку или используют заранее созданный модуль. В сборке для сервера путь к файлу и разрешение на чтение должны быть настроены явно.

Имя виртуального файла, семейство и начертание должны совпадать между регистрацией и выбором. Распространённая ошибка — добавить файл под одним именем, зарегистрировать семейство с другим регистром, а затем вызвать setFont() третьим вариантом. В результате jsPDF возвращается к встроенной гарнитуре, и кириллица пропадает. Диагностика начинается с getFontList(): в списке должно появиться зарегистрированное семейство и требуемые начертания.

Жирный и курсивный текст не создаются искусственным наклоном или утолщением, если для них нужны настоящие начертания. Каждый TTF-файл регистрируется отдельно как normal, bold, italic или bolditalic. При отсутствии соответствующего файла запрос может дать неожиданный результат. Для брендов и документов с точной типографикой лучше подключить полный набор, а не рассчитывать на синтезирование.

Размер файла и набор символов

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

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

Направление письма и арабский текст

Модуль арабского текста содержит processArabic(), который преобразует символы в формы, подходящие для отображения, а setR2L() меняет направление вывода. Эти функции полезны для коротких надписей и полей, но не превращают jsPDF в полноценный движок сложной письменности. Смешанные фрагменты справа налево и слева направо, цифры, скобки и знаки пунктуации требуют тестирования на реальных данных.

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

Управление страницами

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

setPage() переключает активный лист по номеру, getNumberOfPages() возвращает их количество. Это позволяет сначала сформировать весь документ, а затем пройти по страницам и добавить Страница X из Y. Такой двухпроходный подход удобнее, чем пытаться узнать итоговое число во время первой отрисовки. Перед каждой записью номера нужно выбрать страницу и вычислить позицию относительно её собственных размеров.

Методы вставки, удаления и перемещения страниц применяют, когда логика документа зависит от условий. Например, титульный лист добавляют после основной генерации, пустую страницу убирают, а приложение переставляют перед подписями. После таких операций номера страниц меняются, поэтому финальные колонтитулы разумно рисовать последними. Ссылки и закладки также следует проверить после перестановки, поскольку их цели зависят от структуры документа.

Демонстрация jsPDF с двумя портретными страницами в предпросмотре

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

Демонстрация jsPDF с альбомной ориентацией страниц

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

Линии, прямоугольники и векторная графика

Базовые фигуры создаются методами line(), rect(), roundedRect(), circle(), ellipse(), triangle() и средствами построения путей. Для каждой фигуры выбирается режим: только обводка, только заливка либо оба действия. Цвет линии задаётся через setDrawColor(), цвет заливки через setFillColor(), толщина через setLineWidth(). Эти команды подходят для таблиц, разделителей, плашек, чекбоксов и простых диаграмм.

Цвет можно передавать в сером, RGB или CMYK-представлении в зависимости от числа параметров. Для дробных значений удобно применять строки, чтобы избежать заметного накопления ошибок двоичной арифметики JavaScript. В прикладном коде палитру лучше хранить в одном объекте и передавать через небольшие функции. Тогда изменение фирменного оттенка не требует правки десятков вызовов, а контраст текста на цветной заливке проверяется централизованно.

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

Пути и отсечение

Сложный контур строится последовательностью сегментов и затем обводится, заполняется или используется как область отсечения. Отсечение подходит для фотографии в круге, цветного фона внутри произвольной формы и миниатюры, которая не должна выходить за рамку. После применения клипа важно восстановить графическое состояние, иначе все последующие элементы останутся обрезанными. Для этого пару saveGraphicsState() и restoreGraphicsState() ставят вокруг локального эффекта.

Векторные элементы сохраняют чёткость при увеличении и обычно занимают меньше места, чем растровая картинка с теми же линиями. Поэтому рамки, логотип из простых форм, QR-окружение и диаграммы выгоднее рисовать командами. Растр оставляют для фотографий и сложных иллюстраций. Смешанный подход даёт более компактный документ и лучшее качество печати.

Прозрачность и графические состояния

Объект GState позволяет задавать прозрачность заливки и обводки. Его применяют для полупрозрачных подложек, водяных обозначений и наложения диаграмм. Состояние добавляют в документ, активируют перед рисованием и затем возвращают прежнее. Если забыть восстановление, прозрачность распространится на текст и другие блоки, поэтому локальные эффекты всегда оборачивают сохранением и восстановлением графического состояния.

Прозрачность может отображаться немного по-разному в просмотрщиках, особенно поверх сложных изображений. Для критичных печатных форм лучше проверить файл в браузере, Adobe Acrobat Reader и системном просмотрщике. Если ожидается печать на офисном принтере, тестируют также бумажный экземпляр: тонкая светлая подложка на экране иногда исчезает, а тёмный полупрозрачный водяной знак мешает чтению текста.

Режим advanced API, матрицы и повторяемые объекты

По умолчанию используется совместимый режим API, рассчитанный на привычные методы и плагины. Расширенный режим включается через advancedAPI() на время переданной функции. В нём доступны матрицы преобразования, шаблоны и Form Objects. После завершения callback jsPDF возвращается к прежнему режиму, поэтому расширенные операции удобно изолировать в отдельной функции, не меняя остальной шаблон.

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

Form Object хранит набор команд как повторно используемый объект PDF. Это подходит для логотипа, фоновой сетки, пиктограммы или карточки, которая встречается много раз. Вместо дублирования всех операторов на каждой странице документ ссылается на один объект с нужной матрицей. Выигрыш зависит от сложности элемента, но для сотен повторов может быть заметным. Текст, который должен индексироваться и копироваться отдельно, без необходимости не превращают в сложную графическую группу.

Добавление изображений

addImage() принимает Data URL, элемент изображения, canvas, типизированный массив и другие поддерживаемые представления. Указываются формат, координаты, ширина и высота, а при необходимости псевдоним, сжатие и поворот. jsPDF работает с распространёнными растровыми форматами через соответствующие модули, но качество итогового файла определяется исходными пикселями и выбранным физическим размером на странице.

Правильный масштаб рассчитывают по пропорциям. Если изображение шириной 1600 и высотой 900 пикселей должно занять 160 мм, высота равна 90 мм. Одновременное указание произвольных ширины и высоты растягивает картинку. Для фотографии ограничивают оба измерения: сначала вычисляют коэффициенты по ширине и высоте доступного прямоугольника, затем берут меньший и центрируют изображение. Такой режим вписать сохраняет весь кадр; режим заполнить требует предварительного кадрирования.

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

CORS и загрузка в браузере

Картинка с другого домена может не попасть в canvas из-за политики CORS. Симптом — ошибка безопасности, пустая область либо невозможность получить Data URL. Надёжные варианты: хранить ресурс вместе с приложением, настроить разрешающие заголовки на сервере, получить файл через собственный backend или загрузить его как Blob с разрешённого адреса. Установка одного лишь атрибута crossOrigin не помогает, если сервер не отправляет нужный заголовок.

Асинхронную загрузку всех изображений завершают до вызова сохранения. Если создать Image, назначить src и сразу выполнить addImage(), декодирование может ещё идти. Для каждого ресурса используют Promise, await image.decode() либо чтение Blob и только после успешного завершения строят PDF. Ошибки загрузки обрабатывают отдельно: подставляют нейтральную заглушку, исключают необязательную фотографию или прекращают создание с понятным сообщением.

SVG и масштабируемые иллюстрации

Для SVG применяется addSvgAsImage(), а преобразование опирается на дополнительную библиотеку canvg. Векторный исходник удобно использовать для диаграмм и логотипов, но конкретный путь может быть преобразован в растровое изображение в зависимости от метода. Перед использованием проверяют градиенты, маски, внешние стили, фильтры и шрифты: сложные возможности SVG поддерживаются не одинаково.

SVG, полученный из пользовательского ввода, следует очищать. В нём могут присутствовать внешние ссылки, стили и элементы, не нужные для печати. Для стабильного результата лучше формировать ограниченный SVG самостоятельно, задавать явные размеры и встраивать используемые стили. Если требуется сохранить каждую кривую векторной, иногда эффективнее преобразовать данные диаграммы непосредственно в команды линий и путей jsPDF, а не передавать готовый DOM-элемент.

Преобразование HTML через метод html

Метод html() получает DOM-элемент или строку разметки и выполняется асинхронно. В его настройках задаются координаты, ширина окна рендеринга, масштаб, поля, автоматическое разбиение и callback, в котором документ уже можно сохранить. Для отрисовки используется html2canvas, а строковая разметка дополнительно требует DOMPurify. Поэтому результат ближе к снимку браузерного представления, чем к полноценной печатной верстке с поддержкой всех правил CSS.

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

Ширина исходного элемента, параметр windowWidth и ширина в PDF должны быть согласованы. Если DOM рассчитан на широкий экран, а в PDF отведена узкая колонка, текст и картинки масштабируются или обрезаются. Для печати создают отдельный контейнер с фиксированной шириной, отключают анимации, дожидаются загрузки шрифтов и изображений, затем передают его в html(). Скрытие контейнера через display:none может лишить его измеряемых размеров; лучше разместить его вне видимой области.

Многостраничный HTML-вывод jsPDF с проблемой полей и разрыва изображения

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

Что проверить при пустом результате

  • Дождаться выполнения callback или Promise метода html().
  • Убедиться, что контейнер имеет ненулевые ширину и высоту.
  • Дождаться загрузки веб-шрифтов через document.fonts.ready.
  • Проверить доступность изображений и правила CORS.
  • Установить html2canvas, а для строковой разметки также DOMPurify.
  • Убрать временно сложные фильтры, псевдоэлементы и внешние стили.

Если приложение использует сборщик, дополнительные зависимости могут попадать в отдельные chunks. Ошибка загрузки такого файла проявляется только при вызове html() или SVG-функции, хотя базовый текстовый PDF работает. Проверяют сетевые запросы, базовый путь публикации и настройки externals. Исключать зависимость из сборки можно лишь тогда, когда соответствующая функция не используется либо библиотека предоставляется другим способом.

Canvas и интерфейс context2d

Модуль canvas предоставляет интерфейс, похожий на HTML Canvas, а context2d реализует команды путей, текста, трансформаций и состояния. Это облегчает перенос кода простых диаграмм и рисунков. Однако полное совпадение с браузерным canvas не гарантируется: свойства композиции, фильтры, пиксельные операции и особенности шрифтов нужно проверять. Чем сложнее графика, тем полезнее минимальный тест каждого используемого метода.

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

Таблицы и плагин AutoTable

В ядре jsPDF есть все примитивы для таблицы: текст, линии, прямоугольники, измерение ширины и управление страницами. Небольшую таблицу можно построить самостоятельно, но разработчику придётся рассчитывать высоту каждой строки, переносы, ширины колонок, повтор заголовка и разрывы страниц. Для десятков строк эта логика становится значительной частью проекта и требует тестов на крайних данных.

jsPDF-AutoTable — отдельный плагин, который добавляет autoTable(). Он принимает столбцы и строки, строит сетку, переносит текст и продолжает таблицу на новых страницах. Плагин не является встроенным визуальным редактором: стили, обработчики ячеек и формат данных по-прежнему задаются кодом. При обновлении зависимостей важно проверять совместимость импорта и типизации, потому что объект jsPDF и функция плагина должны быть подключены к одному экземпляру.

Перед таблицей полезно нормализовать данные: преобразовать даты и суммы в строки, заменить отсутствующие значения, ограничить непредсказуемые комментарии и определить выравнивание по типу столбца. Денежные значения выравниваются вправо, короткие коды — по центру, описания — влево. Итоговую сумму лучше рисовать отдельным блоком после таблицы, используя координату её завершения, а не пытаться вставить итог как обычную строку с теми же правилами.

Ручной расчёт ширины колонок

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

Ссылки, аннотации и переходы внутри документа

link() создаёт активную область по координатам, а textWithLink() связывает текст с адресом или внутренним переходом. Внешнюю ссылку лучше выделить визуально и сделать активную область достаточно большой для нажатия. Внутренние переходы применяют в оглавлении: каждая строка ведёт на страницу и позицию раздела. После изменения структуры нужно пересчитать цели, поэтому оглавление формируют после определения страниц либо сохраняют карту разделов во время генерации.

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

Закладки и структура разделов

Модуль outline создаёт дерево закладок, видимое в боковой панели многих PDF-просмотрщиков. Закладки особенно полезны в длинном отчёте: пользователь переходит к разделу без прокрутки десятков страниц. Для каждого пункта указывают название и цель. Иерархию строят одновременно с логической структурой документа, но добавление выполняют после того, как известны номера страниц.

Закладки не заменяют видимое оглавление. При печати боковая панель недоступна, поэтому важный документ получает и страницу содержания, и навигационное дерево. Названия должны быть короткими и отличимыми друг от друга. Если несколько разделов начинаются одинаково, в панели они выглядят как дубликаты. Для автоматических отчётов полезно включать период или идентификатор только там, где он помогает различать разделы.

Интерактивные поля AcroForm

Модуль AcroForm позволяет добавлять текстовые поля, флажки, переключатели, списки и кнопки. У каждого поля задаются имя, прямоугольник, значение и свойства отображения. Поля подходят для форм, которые получатель должен заполнить после создания PDF. Имя должно быть уникальным: повтор одинакового имени может связать значения нескольких видимых элементов, что иногда полезно для синхронных полей, но чаще приводит к неожиданному поведению.

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

Размер шрифта, цвет, рамка и фоновая заливка полей должны сочетаться с печатным макетом. Пустое поле оставляют достаточно высоким для ввода, но не перекрывают соседний текст. Если значение заранее известно, часто надёжнее вывести обычный текст, а интерактивность оставить только там, где пользователь действительно должен что-то изменить.

Метаданные, язык и идентификаторы

setProperties() задаёт заголовок, тему, автора, ключевые слова и создателя документа. Эти свойства видны в информации о файле и помогают поиску, но не отображаются на странице. Значения следует формировать из доверенных данных и ограничивать разумной длиной. Служебные идентификаторы, которые не должны быть видны получателю, лучше не переносить в общедоступные поля метаданных.

setLanguage() указывает язык содержимого. Это полезно для вспомогательных технологий и корректной обработки текста, хотя одной языковой метки недостаточно для полноценной доступности. setCreationDate() задаёт дату создания, а setFileId() — идентификатор файла. Для воспроизводимых тестов эти значения можно фиксировать, иначе два PDF с одинаковым видимым содержимым будут отличаться на уровне байтов.

Модуль XMP позволяет добавить расширенные метаданные. Использовать его стоит только при ясной схеме и требованиях системы документооборота. Некорректный XML или несогласованные значения усложняют обработку. Для обычного счёта достаточно базовых свойств, языка и корректного имени файла. Если принимающая система проверяет PDF/A или специальный профиль, одной записи XMP недостаточно: требуется отдельная проверка соответствия всему стандарту.

Параметры просмотра, автопечать и JavaScript внутри PDF

viewerPreferences() задаёт предпочтения просмотра: например, отображение панелей, режим страницы и некоторые параметры печати. Это рекомендации для программы просмотра, а не обязательные команды. Конкретный просмотрщик может их игнорировать. Поэтому документ должен оставаться понятным и при стандартных настройках, без зависимости от автоматически открытой панели или масштаба.

autoPrint() добавляет действие печати при открытии. Современные программы часто блокируют автоматические действия или требуют подтверждение пользователя. Такой режим уместен в контролируемом киоске, но раздражает при обычной пересылке и может восприниматься как подозрительное поведение. Для большинства сценариев достаточно понятной кнопки Печать в приложении, которая сначала создаёт файл или Blob, а затем открывает системный диалог.

addJS() помещает JavaScript в PDF. Поддержка крайне неоднородна, а политики безопасности могут отключать выполнение полностью. Нельзя строить обязательный рабочий процесс на скрипте внутри документа. Кроме того, любой динамический код повышает требования к проверке данных и доверию получателя. Если интерактивность можно реализовать в веб-приложении до формирования PDF, такой путь обычно контролируемее.

Способы получения результата

save() инициирует загрузку в браузере или записывает файл при использовании серверной сборки. Метод удобен для кнопки Сформировать PDF, но не всегда подходит, если документ нужно сначала отправить на сервер, показать в диалоге или вложить в сообщение. Для этих задач применяется output() с нужным типом результата.

Режим arraybuffer возвращает двоичный буфер, который можно передать через API или сохранить средствами окружения. blob удобен для загрузки через fetch, создания объекта File и интеграции с браузерными API. bloburl создаёт временный адрес для iframe или новой вкладки, а datauristring возвращает строку Data URI. Последняя заметно раздувает данные и хуже подходит для крупных документов.

Временный Blob URL необходимо освобождать через URL.revokeObjectURL(), когда окно предпросмотра закрыто или заменено новым документом. Иначе длительная сессия с многократным формированием отчётов удерживает память. При отправке на сервер Blob передают как тело запроса или часть FormData, задавая имя и MIME-тип. Сервер должен повторно проверять права пользователя и не доверять метаданным, переданным клиентом.

Предпросмотр без лишней вкладки

Для встроенного предпросмотра создают Blob URL и присваивают его src элементу iframe или object. Перед заменой запоминают прежний адрес и освобождают его после загрузки нового. Следует предусмотреть состояние ожидания: построение большого отчёта может занять заметное время, а пустая область выглядит как ошибка. Если встроенный просмотр PDF запрещён политикой браузера, показывают кнопку открытия в новой вкладке и отдельную кнопку сохранения.

Работа в Node.js

В Node.js импорт выбирает сборку, использующую файловые операции вместо браузерных API. save() записывает документ в файловую систему, но доступ к локальным ресурсам ограничивается. Для шрифтов и изображений предпочтительно использовать разрешения самого Node через флаги permission model, перечисляя только необходимые пути. Это лучше, чем открывать чтение всего диска для генератора отчётов.

Серверный сценарий обычно создаёт документ в памяти и отправляет буфер в HTTP-ответ. В этом случае имя файла задаётся заголовком ответа, а MIME-тип — application/pdf. Если отчёт большой, нужно учитывать пиковую память: jsPDF собирает структуру документа перед выдачей, поэтому одновременное формирование множества тяжёлых файлов может перегрузить процесс. Очередь задач, ограничение параллелизма и предварительное уменьшение изображений помогают стабилизировать нагрузку.

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

Подключение через ES-модули, UMD и сборщики

В проекте с ES-модулями используется именованный импорт import { jsPDF } from "jspdf". В CommonJS применяется require() с извлечением свойства jsPDF. При подключении UMD-файла через тег script конструктор находится в window.jspdf. Ошибка jsPDF is not defined часто возникает, когда пример для глобального подключения копируют в модульный проект или наоборот.

Пакет содержит сборки для ES-модулей, Node и UMD. Обычно точный файл выбирать не нужно: сборщик использует поля пакета. Ручное указание файла оправдано при подключении без сборщика или при особых требованиях окружения. Одновременная загрузка UMD через script и импорт из npm способна создать два независимых экземпляра. Плагин, подключённый к одному, не появится на объектах другого, поэтому способ подключения должен быть единым.

Дополнительные библиотеки для HTML и SVG загружаются динамически. Webpack и похожие инструменты создают отдельные части сборки. Если функция не используется, зависимость можно объявить внешней, чтобы не выпускать лишний chunk; если используется, исключение приведёт к ошибке во время работы. Конфигурацию проверяют не только в режиме разработки, но и после публикации, где базовый путь и имена файлов отличаются.

TypeScript

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

Безопасная обработка данных

Текст, адреса, имена файлов, аннотации и HTML могут приходить от пользователя. Их нужно проверять до передачи библиотеке. Для обычного text() контролируют тип, длину и допустимые управляющие символы. Для HTML-строки требуется очистка DOMPurify, но и после неё лучше разрешать ограниченный набор элементов и стилей. Для ссылок разрешают только ожидаемые схемы и не превращают произвольное значение в активный переход.

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

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

Производительность и размер PDF

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

Параметр compress включает сжатие потоков PDF. Он уменьшает текстовые и векторные данные, но не исправляет неудачно подготовленные JPEG и не делает автоматически маленьким встроенный TTF. Сначала оптимизируют ресурсы, затем сравнивают размер со сжатием. Для диагностики полезно создать три файла: только текст, текст со шрифтом и полный документ. Разница сразу показывает, какой ресурс даёт основной рост.

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

Память браузера

Data URL хранит двоичные данные в текстовом виде и создаёт дополнительные копии строки. Для крупных ресурсов предпочтительнее ArrayBuffer, Uint8Array и Blob. После использования удаляют ссылки на большие canvas, освобождают Blob URL и не сохраняют каждый промежуточный PDF в состоянии интерфейса. На мобильных устройствах документ с несколькими полноразмерными фотографиями может закрыть вкладку без явной ошибки, поэтому ограничение разрешения должно применяться до добавления изображений.

Практический шаблон счёта

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

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

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

Билеты, сертификаты и этикетки

Для билета важны точные размеры, крупный код, дата, место и визуальный контроль. Формат страницы можно задать массивом в миллиметрах, соответствующим реальной заготовке. QR-код или штрихкод создаётся внешним генератором, затем добавляется как изображение или canvas. Под кодом печатают текстовое значение, чтобы билет можно было обработать при повреждении изображения. Контраст и свободная зона вокруг кода проверяются сканером, а не только глазами.

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

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

Отчёты с диаграммами и повторяющимися блоками

Отчёт удобно собирать из модели данных, не связанной с DOM. Заголовок раздела, набор показателей, диаграмма и комментарий образуют один блок. Функция сначала оценивает высоту, затем решает, начинать ли новый лист. Простую диаграмму рисуют линиями и прямоугольниками, сложную получают из SVG или canvas. Подписи осей и легенду лучше выводить текстом jsPDF, чтобы они оставались чёткими.

Для повторяющейся карточки показателя задают ширину, высоту, внутренние отступы и правила переноса. Карточки размещаются по сетке; когда следующая не помещается по горизонтали, начинается новая строка, а при нехватке высоты — новая страница. Такой алгоритм устойчив к разному числу показателей. Цвет не должен быть единственным носителем смысла: рядом с индикатором добавляют число, знак или подпись.

Графики, полученные как canvas, желательно создавать в увеличенном разрешении относительно CSS-размера. При вставке указывают физический размер в PDF, а лишние пиксели обеспечивают резкость. Однако увеличение в четыре раза по обеим осям даёт в шестнадцать раз больше пикселей, поэтому масштаб выбирают разумно. Для векторного SVG проверяют, что шрифты и стили встроены, иначе на другом устройстве внешний вид может измениться.

Нумерация страниц и колонтитулы

Колонтитул рисуют функцией, принимающей номер страницы, общее число и размеры листа. После формирования основного содержимого цикл проходит от первой страницы до последней, выбирает каждую через setPage() и выводит номер. Такая последовательность поддерживает вставку и удаление страниц. Если титульный лист не должен нумероваться, отображаемый номер вычисляют отдельно, не меняя реальный индекс.

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

Проверка качества сформированного документа

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

Визуальные регрессионные тесты преобразуют страницы в изображения и сравнивают с эталоном с допустимым порогом. Они полезны после изменения шрифтов, сборщика и расчёта таблиц. Динамические даты и идентификаторы в тестовом шаблоне фиксируют, иначе каждый запуск отличается. Небольшие различия сглаживания между движками могут давать шум, поэтому сравнение проводят в одном контролируемом окружении.

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

Частые ошибки и способы исправления

Конструктор не найден

Сообщение о неопределённом jsPDF означает несовпадение способа подключения и кода. При ES-импорте используется именованный экспорт, при UMD — свойство глобального объекта. Проверяют порядок script-тегов, отсутствие ошибки загрузки и то, что код выполняется после подключения. В проекте со сборщиком не следует одновременно обращаться к window.jspdf, если библиотека импортирована в модуль.

Кириллица отображается квадратиками

Причина — встроенный шрифт без нужных глифов или неуспешная регистрация TTF. Проверяют содержимое файла, последовательность VFS, addFont(), имя семейства и setFont(). Тест начинают с одной короткой строки и вызова getFontList(). Если отдельные символы по-прежнему отсутствуют, выбирают гарнитуру с более полным набором и статическим TTF-файлом.

Документ сохраняется до завершения HTML

html() работает асинхронно, поэтому save() должен находиться в callback или выполняться после await. Сохранение сразу после вызова создаёт пустой либо частично заполненный документ. Аналогичное правило действует для загрузки изображений и шрифтов. Все Promise собирают через Promise.all(), затем начинают построение или завершают его после готовности ресурсов.

Изображение обрезано или растянуто

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

Файл слишком большой

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

Плагин не добавил метод

Чаще всего плагин импортирован не тем способом либо подключён к другому экземпляру jsPDF. Проверяют документацию плагина, порядок импорта и отсутствие второй копии пакета в дереве зависимостей. В монорепозитории полезно изучить дедупликацию. Если TypeScript не видит метод, но выполнение работает, проверяют типовые декларации; если метода нет и во время выполнения, проблема именно в подключении.

PDF открывается с предупреждением

Предупреждение может появиться из-за неправильного двоичного преобразования, обрезанного ответа сервера или повреждённого содержимого. Буфер нельзя превращать в UTF-8-строку обычными средствами. В HTTP-ответе задают правильный MIME-тип и передают точное число байтов. Прокси и middleware не должны пытаться модифицировать уже сформированный файл как текст. Для диагностики сохраняют исходный ArrayBuffer и сравнивают хеш до и после передачи.

Ограничения, которые важно учитывать

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

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

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

Сравнение jsPDF с аналогами

Прямые аналоги различаются не только синтаксисом, но и моделью построения. Одни рассчитаны на рисование командами в браузере, другие удобнее для потоковой генерации на сервере, третьи описывают документ декларативным объектом или печатают HTML через движок браузера. Выбор следует делать по исходным данным, требованиям к CSS, месту выполнения и необходимости работать с уже существующими PDF.

ПрограммаЛучше подходит дляГлавное ограничение
jsPDFИнтерактивная генерация PDF из JavaScript, точное рисование текста, фигур и изображенийНет визуального редактора и автоматической печатной верстки
pdf-libСоздание и изменение существующих PDF в браузере или Node.jsСложные макеты и таблицы нужно строить самостоятельно
PDFKitПотоковая генерация документов на сервере Node.jsБраузерное применение требует дополнительной организации сборки и вывода
pdfmakeДекларативные отчёты, таблицы и стили из объектного описанияТочный произвольный макет ограничен моделью документа
PuppeteerПечать веб-страниц с современным HTML и CSS через ChromiumНужен управляемый браузер и больше ресурсов на сервере
PDF CommanderРучное редактирование, сборка и оформление готовых PDF пользователемНе предназначен для генерации документов из JavaScript-кода

jsPDF выбирают, когда документ должен формироваться по событию в JavaScript-приложении, а разработчику нужен прямой контроль координат и ресурсов. pdf-lib лучше подходит, если требуется открыть существующий PDF, заполнить или изменить его. PDFKit удобен для серверного потока, pdfmake — для отчётов с декларативной структурой, Puppeteer — когда решающей является поддержка HTML и CSS. PDF Commander уместен для ручной работы с готовыми файлами, а не как программный генератор.

Как организовать код шаблона

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

Полезные помощники: ensureSpace(height) для проверки страницы, drawParagraph() для переноса текста, drawImageFit() для сохранения пропорций, drawHeader() и drawFooter(), а также formatMoney(). Они должны получать документ и явный контекст, а не читать случайные глобальные переменные. Тогда один и тот же блок можно использовать в браузере и Node.js, подменив только загрузку ресурсов и способ выдачи результата.

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

Двухпроходная компоновка

Когда документ содержит оглавление, ссылки и X из Y, удобно сначала выполнить измерительный проход. Он рассчитывает страницы и координаты разделов без окончательной записи или с временным документом. Второй проход рисует известную структуру. Для простого отчёта достаточно одного прохода и финального добавления номеров, но сложные перекрёстные ссылки становятся надёжнее при явном плане страниц.

Контроль данных перед генерацией

До создания документа проверяют обязательные поля, допустимую длину и типы. Пустое значение не должно превращаться в строку undefined или null. Для числа заранее выбирают формат округления и локаль, для даты — часовой пояс и формат. Текст нормализуют: заменяют нежелательные управляющие символы, сохраняют значимые переводы строк и ограничивают объём комментария, если макет не рассчитан на десятки страниц.

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

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

Печать и совместимость просмотрщиков

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

Для печати важны физические размеры и поля устройства. Диалог печати может включать Подогнать, из-за чего этикетка или бланк уменьшается. В инструкции пользователю стоит указать масштаб 100 %, если размер критичен. Цветной макет проверяют в оттенках серого: светлые линии и различия только по цвету могут исчезнуть. Тонкие линии менее 0,2–0,3 мм некоторые принтеры передают нестабильно.

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

Работа без визуального редактора

Отсутствие панели перетаскивания компенсируется быстрым циклом изменить код — обновить предпросмотр. В демонстрации редактор и PDF находятся рядом, поэтому координаты, цвета и размеры можно проверять немедленно. В проекте аналогичный режим создают отдельной страницей разработчика: слева тестовые данные и параметры, справа iframe с Blob URL. Такая лаборатория ускоряет настройку шаблона и не смешивается с пользовательским экраном.

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

Вторая форма React и отдельный PDF, созданный jsPDF по введённым данным

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

Когда HTML лучше заменить ручной компоновкой

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

Решение принимают по тестовому документу с худшими данными. Если метод html() корректно переносит нужные элементы, шрифты и изображения, нет смысла переписывать всё вручную. Если приходится добавлять десятки CSS-исключений, разрезать DOM и исправлять каждую страницу после снимка, ручной шаблон будет проще сопровождать. В любом случае асинхронные ресурсы и границы страницы остаются явной частью процесса.

Когда jsPDF подходит лучше всего

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

Для свободной верстки уровня издательской системы, сложного редактирования существующих файлов или строгой доступности потребуется дополнительный инструмент. Это не недостаток конкретного метода, а граница модели рисования. Успешный проект заранее выбирает, какие задачи решаются jsPDF, какие — плагином, а какие — серверной печатью HTML или редактором PDF. Тогда библиотека остаётся компактным и понятным компонентом, а не пытается заменить все виды работы с документами.

Итоговый рабочий порядок

  1. Определить физический формат, поля, ориентацию и единицы измерения.
  2. Подготовить модель данных и заранее форматировать даты, суммы и подписи.
  3. Подключить TTF с нужными символами и проверить кириллицу коротким тестом.
  4. Разделить шаблон на функции блоков, возвращающие фактическую высоту.
  5. Добавить централизованную проверку свободного места и создание новой страницы.
  6. Оптимизировать изображения до разумного разрешения и дождаться их загрузки.
  7. Сформировать основное содержимое, затем добавить номера страниц, закладки и ссылки.
  8. Получить Blob или ArrayBuffer для предпросмотра, отправки либо сохранения.
  9. Проверить крайние данные, несколько просмотрщиков и реальную печать.
  10. Зафиксировать эталонный документ для автоматических регрессионных тестов.

При таком порядке jsPDF остаётся управляемым даже в длинном документе. Координаты не разбросаны по обработчикам, шрифты и изображения загружаются до начала, а переход страницы выполняется одной функцией. Пользователь получает предсказуемый файл, разработчик — воспроизводимый шаблон, который можно менять без случайного наложения текста и потери ресурсов.