pdfmake

pdfmake помогает собирать счета, отчёты, акты, каталоги, сертификаты и другие PDF из данных JavaScript: содержимое описывается объектом документа, а движок сам переносит строки и страницы, раскладывает таблицы и колонки, встраивает шрифты и изображения, добавляет колонтитулы, оглавление, QR-коды, метаданные и защиту паролем. Готовый файл можно открыть в окне предпросмотра, отправить на печать, сохранить через кнопку сайта, получить как Blob или Buffer и передать дальше в приложение.

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

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

Скачать pdfmake

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

Как устроен рабочий процесс в pdfmake

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

Надёжный шаблон удобно строить в три слоя. Слой данных нормализует числа, даты, пустые значения и подписи. Слой представления превращает данные в узлы pdfmake: строки заказа становятся массивом body, реквизиты — колонками, итоги — отдельной таблицей. Слой вывода выбирает, что делать с результатом: открыть, сохранить, отправить в ответ сервера, прикрепить к письму или положить в объектное хранилище. Разделение не обязательно для простого примера, но оно быстро окупается, когда документ содержит условные секции и десятки полей.

Playground показывает важную особенность интерфейса: редактор работает с JavaScript, а не с HTML-страницей. В примерах объект присваивается переменной dd; после изменения кода результат пересобирается и появляется в правой панели. Верхние вкладки открывают заготовки для текста, стилей, колонок, таблиц, списков, отступов и изображений. Это не набор готовых коммерческих бланков, а проверяемые образцы синтаксиса, которые можно адаптировать под собственную схему данных.

Редактор pdfmake и предпросмотр документа с повторно используемыми стилями

Минимальное определение документа

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

function makeReceipt(data) {
  return {
    content: [
      { text: 'Квитанция', style: 'title' },
      { text: `Номер: ${data.number}` },
      { text: `Сумма: ${data.total}` }
    ],
    styles: {
      title: { fontSize: 18, bold: true, marginBottom: 12 }
    },
    defaultStyle: { font: 'Roboto', fontSize: 10 }
  };
}

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

Текст, фрагменты и перенос строк

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

{
  text: [
    'Статус: ',
    { text: 'оплачен', bold: true, color: '#176b36' },
    ' · сумма ',
    { text: '18 450 ₽', fontSize: 12 }
  ],
  marginBottom: 6
}

К базовым параметрам относятся fontSize, bold, italics, color, background, alignment, lineHeight, characterSpacing, decoration и прозрачность. Выравнивание может быть по левому или правому краю, по центру либо по ширине. Подчёркивание и зачёркивание задаются как оформление текста, а не как отдельно нарисованные линии, поэтому следуют за переносом строки. Для верхних и нижних индексов применяются соответствующие свойства текста; их стоит проверять на выбранном шрифте, поскольку метрики гарнитур различаются.

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

Результат pdfmake с заголовком и разными стилями внутри текста

Стили и правила приоритета

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

const styles = {
  title: { fontSize: 20, bold: true, color: '#16324f' },
  muted: { color: '#65717d', fontSize: 8 },
  money: { alignment: 'right', noWrap: true },
  warning: { color: '#9b2c2c', bold: true }
};

const node = {
  text: '12 900 ₽',
  style: ['money', 'warning'],
  marginTop: 2
};

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

Стиль не является CSS-классом и не понимает веб-селекторы. Нельзя написать правило для всех вторых ячеек или автоматически применить оформление по имени HTML-тега. Условная стилизация выполняется при построении узлов: код определяет, какой стиль присвоить строке, ячейке или фрагменту. Это делает результат предсказуемым, но требует явно описать правила представления.

Колонки для реквизитов и сложных шапок

Узел columns делит доступную ширину между дочерними блоками. Для ширины используются число, auto, звёздочка и процент. Число фиксирует размер в пунктах; auto ориентируется на содержимое; звёздочка получает остаток; несколько звёздочных колонок делят его между собой. Свойство columnGap создаёт промежуток между колонками. Внутрь разрешено помещать текст, списки, таблицы, изображения и стеки.

{
  columns: [
    { width: '*', stack: [
      { text: 'Поставщик', style: 'label' },
      { text: supplier.name, bold: true },
      supplier.address
    ]},
    { width: 160, stack: [
      { text: 'Счёт', style: 'label' },
      { text: invoice.number, alignment: 'right' },
      { text: invoice.date, alignment: 'right' }
    ]}
  ],
  columnGap: 24
}

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

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

Playground pdfmake с примерами двух, трёх и смешанных по ширине колонок

Таблицы: структура, ширины и повтор заголовка

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

{
  table: {
    headerRows: 1,
    widths: ['*', 54, 76, 82],
    body: [
      ['Позиция', 'Кол-во', 'Цена', 'Сумма'],
      ...items.map(item => [
        { text: item.name },
        { text: String(item.qty), alignment: 'right' },
        { text: item.price, alignment: 'right' },
        { text: item.total, alignment: 'right', bold: true }
      ])
    ]
  },
  layout: 'lightHorizontalLines'
}

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

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

Редактор pdfmake с базовой таблицей, вложенными элементами и вариантами ширины

Объединение ячеек и вертикальное выравнивание

colSpan объединяет ячейку с соседними справа, rowSpan — с ячейками ниже. После главной ячейки в матрице оставляют заполнители, иначе индексы следующих значений сместятся. Это частая причина ломающейся таблицы. Вертикальное выравнивание принимает значения сверху, по центру и снизу; оно заметно, когда рядом находятся многострочное описание и короткий код.

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

Границы, фон и собственный layout

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

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

Разрыв большой таблицы

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

Списки и многоуровневые инструкции

Маркированный список задаётся ключом ul, нумерованный — ol. Элементы могут быть строками, текстовыми узлами и вложенными списками. Поддерживаются начальное значение, обратный порядок, индивидуальный счётчик элемента, тип маркера, цвет маркера и собственный разделитель номера. Благодаря этому один механизм подходит для обычных тезисов, пунктов регламента, буквенной нумерации и вложенных подпунктов.

{
  ol: [
    'Проверить реквизиты',
    { text: 'Согласовать сумму', bold: true },
    {
      ul: [
        'сверить налог',
        'сверить валюту',
        'проверить округление'
      ],
      markerColor: '#46637f'
    }
  ],
  type: 'upper-roman',
  separator: ['(', ')']
}

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

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

Изображения: ресурсы, масштаб и повторное использование

Растровый узел использует ключ image. Поддерживаются JPEG и PNG. В клиентском сценарии изображение часто передают как data URI либо помещают в виртуальную файловую систему. На сервере допустим путь к файлу. Словарь images связывает короткое имя с данными, путём или сетевым ресурсом; это особенно полезно, когда логотип повторяется в шапке, теле и приложении.

Без размеров применяется исходный размер. Только width или только height сохраняет пропорции. Одновременные числовые ширина и высота растягивают изображение, что обычно нежелательно для логотипов и фотографий. fit вписывает картинку в прямоугольник без обрезки, а cover заполняет прямоугольник с обрезкой и позволяет выбрать выравнивание видимой области. Для каталожных карточек cover даёт одинаковые рамки, а для схем и сканов безопаснее fit.

images: {
  logo: logoData,
  productPhoto: photoData
},
content: [
  { image: 'logo', width: 92, marginBottom: 14 },
  { image: 'productPhoto', fit: [220, 150], alignment: 'center' }
]

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

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

SVG, векторный canvas и QR-коды

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

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

{
  canvas: [
    { type: 'rect', x: 0, y: 0, w: 220, h: 44,
      r: 5, color: '#eef4f8' },
    { type: 'line', x1: 16, y1: 34, x2: 204, y2: 34,
      lineWidth: 1, lineColor: '#8aa4b8' }
  ],
  marginBottom: 8
}

QR-код создаётся прямо из строки. Настраиваются размер, цвета, версия, уровень коррекции ошибок, режим кодирования и маска. Для билета или товарной этикетки достаточно qr и fit. Чем больше данных помещено в код, тем мельче модули; маленький QR с длинной строкой плохо сканируется после печати. Контрольный тест делают на минимальном физическом размере, на обычном принтере и несколькими камерами. Светлый передний цвет, низкий контраст и декоративный фон ухудшают распознавание.

Шрифты и кириллица

Качество документа зависит от шрифтовых файлов, а не от гарнитуры, установленной у получателя. Для семейства описывают начертания normal, bold, italics и bolditalics. Даже если используется один файл, все нужные варианты лучше объявить явно. Иначе запрос жирного или курсивного текста может закончиться ошибкой о неизвестном начертании либо визуальной подменой.

В браузерной сборке шрифты и другие ресурсы можно упаковать в VFS. Генератор VFS создаёт JavaScript-файл со встроенными бинарными данными; его подключают отдельно и регистрируют семейства до создания PDF. Файл следует хранить в собственном каталоге проекта, а не редактировать внутри node_modules, потому что установка зависимостей заменит изменения. Большая гарнитура с несколькими начертаниями заметно увеличивает объём загружаемого кода, поэтому включают только нужные файлы.

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

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

Ошибка вида Font … in style … is not defined означает несоответствие имени семейства или отсутствующее начертание. Пустые квадраты вместо букв указывают, что файл не содержит символ. Когда обычный текст виден, а жирный исчезает, проверяют именно файл bold. После замены шрифта полезно очистить кеш сборщика и убедиться, что VFS действительно пересоздан, а не остался старым артефактом.

Простой PDF, созданный pdfmake с подключённым шрифтом и выделением текста

Размер страницы, ориентация и поля

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

const doc = {
  pageSize: 'A4',
  pageOrientation: 'portrait',
  pageMargins: [42, 72, 42, 58],
  content: buildContent(data)
};

Ориентацию разрешено менять внутри документа вместе с разрывом страницы. Это удобно, когда основной отчёт вертикальный, а широкая ведомость должна быть горизонтальной. Узел, начинающий новый лист, получает pageOrientation и pageBreak: "before"; возврат к вертикальному листу выполняется так же. Не стоит пытаться разместить широкую таблицу в узкой области уменьшением шрифта до нечитаемого размера.

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

Локальный margin применяется к узлам. Четыре числа означают левый, верхний, правый и нижний отступ; два — горизонтальный и вертикальный; одно — одинаковый со всех сторон. Есть и отдельные свойства сторон. Отступ не заменяет высоту строки и не должен использоваться для имитации пустых абзацев: системные интервалы лучше хранить в стилях заголовков и секций.

Разрывы страниц и защита от висячих заголовков

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

pageBreakBefore(currentNode, nodeContainer) {
  const isHeading = currentNode.headlineLevel === 2;
  const nothingAfter =
    nodeContainer.getFollowingNodesOnPage().length === 0;
  return isHeading && nothingAfter;
}

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

Лишняя пустая страница часто появляется из-за pageBreak: "after" у последнего элемента, слишком высокого неразрывного блока или сочетания полей с элементом размером почти во всю страницу. Диагностика начинается с временного удаления принудительных разрывов и запретов разбиения. Затем возвращают правила по одному, используя данные, которые воспроизводят проблему.

Колонтитулы, фон и водяной знак

Шапка и подвал могут быть статическим узлом или функцией. Функция получает номер текущей страницы, общее количество страниц и размеры листа, а возвращает любой допустимый элемент: текст, колонки, таблицу или canvas. Это позволяет на нечётных и чётных страницах менять выравнивание, выводить номер 3 из 12, добавлять название раздела и рисовать линию по ширине листа.

footer(currentPage, pageCount) {
  return {
    columns: [
      { text: 'Конфиденциально', color: '#6d7680' },
      { text: `${currentPage} / ${pageCount}`, alignment: 'right' }
    ],
    margin: [42, 12, 42, 0],
    fontSize: 8
  };
}

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

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

Оглавление, внутренние ссылки и закладки

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

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

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

Метаданные, язык, защита и PDF/A

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

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

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

Режимы PDF/A предназначены для долговременного хранения и требуют подходящей версии PDF, набора параметров, корректных метаданных и встраивания ресурсов. Флаг в определении документа — только часть процесса соответствия. Организация, которой нужен формальное требование долговременного хранения, должна проверять результат валидатором, следить за цветовыми профилями, шрифтами и правилами конкретного подуровня. Аналогично заявленный Tagged PDF или PDF/UA требует осмысленной структуры, а не только переключателя.

Вложения внутри PDF

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

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

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

Вывод в браузере: загрузка, окно, печать и Blob

После createPdf доступны методы загрузки, открытия и печати. Имя файла передают в download. open создаёт окно с PDF, print готовит печать. Современный браузер может блокировать окно, если оно открывается после асинхронного запроса. Решение — создать пустое окно непосредственно в обработчике клика, сохранить ссылку и передать её методу после получения данных.

getBlob подходит для загрузки через собственный интерфейс, предпросмотра, отправки FormData и сохранения через API файловой системы. getBuffer, getBase64, getDataUrl и потоковые методы нужны для интеграции с другими библиотеками. Data URL удобен для небольшого iframe, но увеличивает строковое представление; для крупного документа Blob экономичнее. Созданный объектный URL следует освобождать, когда предпросмотр больше не нужен.

async function showPdf(docDefinition, frame) {
  const blob = await pdfMake.createPdf(docDefinition).getBlob();
  const objectUrl = URL.createObjectURL(blob);
  frame.src = objectUrl;
  return () => URL.revokeObjectURL(objectUrl);
}

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

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

Формирование на сервере Node.js

На сервере тот же объект документа передают фабрике, но ресурсы читаются из файловой системы или контролируемых сетевых ресурсов. Результат можно записать в файл, получить как Buffer или поток. Поток удобен для HTTP-ответа и больших документов, однако заголовки ответа нужно отправить до данных: тип application/pdf, безопасное имя файла и подходящий режим отображения.

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

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

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

Политики доступа к URL и локальным файлам

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

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

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

Производительность на больших документах

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

Изображения уменьшают до фактического разрешения печати. Base64 хранит бинарные данные как более длинную строку и создаёт дополнительные копии в памяти. На сервере путь или Buffer может быть эффективнее, в браузере — Blob, ArrayBuffer и подготовленный VFS в зависимости от сборки. Повторяющиеся ресурсы регистрируют один раз в словаре. Неизменяемый логотип можно кешировать между операциями, не читая файл заново.

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

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

Типовые документы и организация шаблонов

Счёт или акт

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

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

Управленческий отчёт

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

В отчёте с изменяющимся числом разделов массив content собирают через функции buildSummary, buildSection и buildAppendix. Каждая функция возвращает узел или массив узлов и не изменяет общие данные. Такой шаблон проще тестировать: пустой раздел, длинный заголовок и отрицательные значения проверяются отдельно. Перед добавлением результата массивы разворачивают осознанно, чтобы случайно не создать лишний уровень вложенности.

Сертификат и этикетка

Сертификат часто требует фонового бланка, точного центра, нескольких размеров шрифта и QR-кода проверки. Фон помещают в background, данные — в поток с предсказуемыми полями. Имя получателя может быть очень длинным, поэтому задают максимум строк и уменьшают размер по правилу, а не вручную для каждого случая. QR содержит короткий идентификатор или подписанный компактный токен; длинная JSON-структура делает код плотным.

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

Работа с HTML и данными из редактора

pdfmake не принимает произвольную веб-страницу как готовый макет. Его модель — объект документа. Если исходный контент хранится в HTML, нужен отдельный преобразователь, который сопоставляет поддерживаемые теги и стили узлам pdfmake. Такое преобразование не равно полноценному движку CSS: сетки, позиционирование, псевдоэлементы, сложные селекторы и поведение браузера могут отсутствовать.

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

Там, где требуется пиксельное повторение существующей HTML/CSS-страницы, логичнее рассмотреть печать через Chromium. pdfmake выигрывает, когда документ строится из данных, нужны контролируемые страницы, повторяемые заголовки таблиц и единый шаблон для клиента и сервера. Преобразователь HTML полезен как мост для ограниченного богатого текста, но не должен скрывать различие моделей.

Ошибки и способы устранения

Документ не создаётся из-за таблицы

Сообщение о некорректной таблице обычно означает пустой body, отсутствующую строку, неправильное число ячеек или ошибку вокруг colSpan/rowSpan. Сначала выводят подготовленную матрицу и проверяют, что каждая строка — массив. При объединении добавляют заполнители. Если данных нет, вместо пустой таблицы возвращают текст Нет записей или шапку с одной информационной строкой.

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

Проверяют формат JPEG/PNG, непустые данные и корректный префикс data URI. Путь к файлу, работающий на сервере, не станет автоматически доступным браузерной сборке. Сетевой адрес может блокироваться CORS, политикой доступа или авторизацией. Большое изображение сначала заменяют маленьким известным PNG: если оно работает, проблема в исходном ресурсе, а не в расположении узла.

Текст выводится квадратами или падает жирное начертание

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

Открытие и печать не срабатывают

Браузер блокирует окно, созданное после асинхронной операции, либо расширение перехватывает PDF. Пустое окно создают прямо по клику и передают в open или print. Дополнительно предлагают download. При тестировании отключают расширения, проверяют консоль и сравнивают поведение в другом браузере. Метод нельзя вызывать до завершения подготовки изображений и данных.

Контент перекрывает подвал

Нижнее поле меньше высоты подвала. Увеличивают pageMargins, уменьшают содержимое footer или делают его однострочным. Динамическая функция не резервирует место автоматически. Проблему проверяют на странице с самым длинным подвалом и максимальным числом страниц, поскольку строка 100 из 120 шире, чем 1 из 3.

Появилась пустая последняя страница

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

Долгая генерация и зависший интерфейс

Считают число строк, размер исходных изображений и объём VFS. Уменьшают изображения, сокращают вложенность, фиксируют ширины таблицы и исключают повторные base64-данные. Кнопку защищают от повторных запусков. Затем измеряют на реальном наборе. Если оптимизация шаблона не помогает, генерацию переносят в Worker или серверную очередь, а пользователю показывают состояние подготовки.

Тестирование шаблона перед выпуском

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

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

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

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

Подготовка данных до построения документа

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

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

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

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

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

Условные блоки и переменный состав страниц

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

function optionalSection(title, rows) {
  if (!rows || rows.length === 0) return [];
  return [
    { text: title, style: 'sectionTitle' },
    buildRowsTable(rows)
  ];
}

const content = [
  buildHeader(data),
  ...optionalSection('Замечания', data.notes),
  buildTotals(data)
];

Нельзя оставлять в массиве произвольные значения из выражений вида condition && node, если дальнейший код не фильтрует результат. Ложное значение, пустой объект и объект с пустым text ведут себя не одинаково. Единая функция compactContent может удалять только null, undefined и false, не затрагивая число ноль и пустую строку там, где они имеют смысл.

Разрыв страницы связывают с секцией, которая действительно присутствует. Если pageBreak: "before" находится в отдельном пустом заголовке, после скрытия содержимого останется пустой лист. Проще назначить разрыв первому видимому узлу секции. Для приложений условие проверяют до нумерации и создания оглавления, иначе содержание может ссылаться на отсутствующий раздел.

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

Компоненты шаблона и единая система оформления

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

Названия стилей должны описывать роль, а не случайный внешний вид. documentTitle, sectionTitle, tableHeader, money и note понятнее, чем blue14 или smallGray. При смене фирменного цвета или кегля роль остаётся прежней. Для часто используемых размеров, отступов и цветов создают объект токенов, из которого строятся словарь стилей и функции layout.

const tokens = {
  space: { xs: 4, sm: 8, md: 16, lg: 24 },
  type: { body: 9, title: 18, caption: 7 },
  line: { thin: 0.5, strong: 1.2 }
};

const styles = {
  documentTitle: {
    fontSize: tokens.type.title,
    bold: true,
    marginBottom: tokens.space.md
  },
  tableHeader: {
    bold: true,
    fontSize: tokens.type.body
  }
};

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

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

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

Интеграция с интерфейсом приложения

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

В React, Vue, Angular и других UI-средах определение документа не следует хранить как большое реактивное состояние. Его строят по команде из обычных данных и чистых функций. Это уменьшает лишние перерасчёты и не заставляет фреймворк отслеживать тысячи ячеек. Состояние интерфейса хранит только фазу операции, сообщение об ошибке и при необходимости ссылку на Blob для предпросмотра.

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

Web Worker полезен, когда построение большого определения и генерация заметно блокируют интерфейс. В Worker передают сериализуемые данные; функции, DOM-элементы и открытые файловые дескрипторы напрямую не переносятся. Шрифты и изображения должны быть доступны в контексте Worker. Перед такой интеграцией измеряют реальную задержку: для короткого счёта усложнение обмена сообщениями может быть неоправданным.

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

Доступность и проверка читаемости

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

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

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

Альтернативное описание изображения нельзя заменить подписью внутри графики, если смысл важен. pdfmake позволяет управлять семантическими свойствами и режимами структурированного PDF, однако формальное включение опции не гарантирует соответствие требованиям доступности. Результат проверяют специализированным валидатором и реальным экранным диктором. Особое внимание уделяют порядку чтения, языку, заголовкам, ссылкам и декоративным объектам.

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

Воспроизводимость и контроль изменений

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

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

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

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

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

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

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

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

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

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

Встроенного визуального конструктора шаблонов нет. Playground ускоряет подбор параметров, но пользователь по-прежнему редактирует JavaScript. Для отдела, где бланки должен менять дизайнер без участия разработчика, потребуется собственный слой шаблонов, сторонний конструктор или другой продукт. Попытка сделать универсальный визуальный редактор поверх всех узлов pdfmake — отдельный программный проект.

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

Поддержка HTML и CSS не является основной моделью. Сторонний конвертер может перевести ограниченный богатый текст, но сложная веб-вёрстка не переносится автоматически. Изображения ограничены JPEG и PNG на уровне растрового узла; другие форматы заранее преобразуют. Для штрихкодов, сложных графиков, цифровой подписи и специализированных PDF-функций нередко нужны дополнительные библиотеки или отдельный этап обработки.

Автоматическая раскладка избавляет от ручных координат, но не отменяет проектирование печатного макета. Длинное слово, огромная картинка, неразрывная строка таблицы или высокий footer способны нарушить композицию. Шаблон должен задавать ограничения для пользовательских данных, а тесты — включать крайние случаи. Работает на одном счёте не означает, что он выдержит тысячу позиций и адрес в пять строк.

Итоговая схема внедрения

  1. Определить типы документов, форматы страниц, обязательные поля и максимальные объёмы данных.
  2. Выбрать шрифт с нужными символами, подготовить начертания и способ их подключения.
  3. Создать чистые функции для текста, таблиц, реквизитов, итогов, колонтитулов и приложений.
  4. Настроить политики сетевого и локального доступа до добавления пользовательских ресурсов.
  5. Собрать набор граничных примеров и проверять их в Playground и в реальном приложении.
  6. Выбрать способ вывода: download, Blob, Buffer, поток или запись файла, с обработкой ошибок.
  7. Добавить автоматические и визуальные тесты, контроль памяти и проверку готового PDF.

Пользовательский многостраничный отчёт в Playground pdfmake

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

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