fpdf2

fpdf2 помогает собирать PDF из Python-кода: размещать текст по координатам или в потоковой вёрстке, строить таблицы, вставлять растровые и SVG-изображения, рисовать фигуры, добавлять ссылки, оглавление, шифрование и цифровую подпись. Основные инструменты — объект FPDF, методы управления страницей и курсором, шрифты TrueType, контекстные менеджеры таблиц и графики, а результат можно сохранить в файл либо сразу получить как байтовый буфер.

Работа начинается с создания объекта FPDF, добавления страницы и выбора шрифта. После этого сценарий последовательно вызывает методы вывода: cell() для строки в прямоугольной области, multi_cell() для абзацев с переносами, write() для непрерывного текста, image() для иллюстраций и table() для табличных данных. Координаты, поля, цвета и размеры задаются явно, поэтому один и тот же шаблон воспроизводимо формирует документы из базы данных, CSV, веб-формы или расчётного модуля.

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

Скачать fpdf2

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

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

Документ формируется как последовательность команд. Объект FPDF хранит параметры страницы, текущую позицию курсора, активный шрифт, цвета, толщину линий и набор уже добавленных ресурсов. Вызов add_page() создаёт страницу и переводит курсор к левому верхнему полю; затем методы вывода записывают PDF-операторы в буфер. output() завершает структуру файла, собирает таблицу объектов и возвращает байты либо записывает их по указанному пути.

Минимальный сценарий обязан выбрать шрифт до первого вывода текста. Встроенные Helvetica, Times и Courier подходят для латиницы и ограниченного набора символов, но русский текст следует печатать через добавленный TrueType-шрифт. Ошибка выбора шрифта проявляется не при создании объекта, а в момент вывода строки: библиотека проверяет, может ли активный шрифт закодировать каждый символ.

from fpdf import FPDF

pdf = FPDF()
pdf.add_page()
pdf.add_font('DejaVu', fname='DejaVuSans.ttf')
pdf.set_font('DejaVu', size=12)
pdf.multi_cell(0, 7, 'Отчёт сформирован автоматически')
pdf.output('report.pdf')

Значение ширины 0 у multi_cell() означает использование доступного пространства до правого поля. Высота 7 задаётся в единицах документа; при стандартной единице миллиметр это расстояние между базовыми линиями соседних строк. Для предсказуемого результата лучше явно выбирать формат страницы, поля и размер шрифта, а не полагаться на значения, накопленные предыдущими функциями шаблона.

Установка и изоляция зависимостей

Пакет устанавливается через менеджер Python-пакетов командой pip install fpdf2. Импорт при этом выполняется из пространства имён fpdf: from fpdf import FPDF. Именно несовпадение имени дистрибутива и имени модуля часто вызывает путаницу. В окружении не следует одновременно держать старый пакет с названием fpdf, потому что оба проекта предоставляют одно и то же пространство имён и файлы одного пакета могут перекрыть файлы другого.

Надёжная последовательность для нового проекта — создать виртуальное окружение, обновить pip, установить fpdf2 и зафиксировать версии зависимостей. В рабочем репозитории полезно хранить requirements.txt или lock-файл, а шрифты и изображения включать в собственный каталог ресурсов. Тогда сборка не зависит от того, какие гарнитуры установлены в операционной системе пользователя или контейнера.

python -m venv .venv
# Windows: .venv\Scripts\activate
# Linux и macOS: source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install fpdf2

Для базовых функций используются Pillow, defusedxml и fontTools. Pillow обрабатывает растровые изображения, fontTools читает и подготавливает шрифты, а defusedxml снижает риск опасной обработки XML при работе с SVG. Дополнительные возможности требуют отдельных библиотек: текстовое формообразование опирается на uharfbuzz, а цифровая подпись — на pyHanko. Их имеет смысл добавлять только в те окружения, где соответствующая функция действительно используется.

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

Страницы, формат бумаги и система координат

Конструктор FPDF принимает ориентацию, единицу измерения и формат. По умолчанию создаётся страница A4 в портретной ориентации и миллиметрах. Для альбомного отчёта можно передать orientation="L", для макета в пунктах — unit="pt", а для нестандартной этикетки — кортеж ширины и высоты. Поддерживаются именованные форматы A3, A4, A5, Letter и Legal, а пользовательский размер позволяет печатать билеты, ценники и карточки без последующего масштабирования.

pdf = FPDF(orientation='L', unit='mm', format='A4')
pdf.set_margins(left=12, top=15, right=12)
pdf.set_auto_page_break(auto=True, margin=14)
pdf.add_page()

Начало координат находится в левом верхнем углу, ось X направлена вправо, ось Y — вниз. Это отличается от математической системы, где Y растёт вверх, поэтому координаты элементов лучше вычислять через свойства страницы: w, h, l_margin, r_margin, t_margin и b_margin. Полезные свойства epw и eph возвращают эффективную ширину и высоту внутри полей и уменьшают число ручных вычитаний.

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

Автоматический разрыв страницы срабатывает, когда следующий элемент пересечёт нижнюю границу, определяемую margin в set_auto_page_break(). Он удобен для обычного текста, но сложный блок лучше предварительно измерить или заключить в dry_run/unbreakable-контекст. Иначе заголовок раздела может остаться внизу, а связанная с ним таблица начнётся на следующем листе.

Курсор, ячейки и управление переходами

Текущая позиция хранится в свойствах x и y. set_x(), set_y() и set_xy() перемещают курсор, get_x() и get_y() помогают вычислять положение следующего элемента. Отрицательное значение set_y() отсчитывается от нижнего края страницы, поэтому его удобно применять в footer(). Метод ln() переводит курсор вниз на заданную высоту и возвращает X к левому полю.

cell() выводит одну строку в прямоугольной области. Параметры w и h задают размеры, border — рамку, align — выравнивание, fill — заливку. Новые параметры new_x и new_y явно определяют, где окажется курсор после ячейки; это безопаснее старых комбинаций ln, потому что код сразу показывает намерение: продолжить справа, перейти к левому полю или спуститься на следующую строку.

pdf.cell(45, 8, 'Номер заказа', border=1)
pdf.cell(0, 8, order_id, border=1, new_x='LMARGIN', new_y='NEXT')

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

write() подходит для текста, который должен течь как обычный абзац и продолжаться с текущей позиции, включая смену стилей и ссылки. text() помещает строку по точным координатам базовой линии и не влияет на курсор; этот метод полезен для подписей на бланке, но неудобен для длинного текста, потому что не выполняет переносов и не контролирует выход за границы.

Переносы строк и расчёт высоты текста

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

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

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

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

Шрифты TrueType, Unicode и кириллица

Русский текст следует выводить шрифтом TrueType или OpenType, добавленным через add_font(). Достаточно передать семейство и путь к файлу; библиотека анализирует таблицы шрифта и встраивает в PDF подмножество реально использованных глифов. Подмножество уменьшает размер документа по сравнению с полным внедрением гарнитуры, особенно если отчёт содержит только кириллицу, цифры и ограниченный набор знаков.

Начертания регистрируются отдельно. Если шаблон использует обычный, полужирный и курсивный текст, необходимо вызвать add_font() для каждого файла с соответствующим style. Простая установка style="B" не создаёт настоящую жирность, если файл для этого начертания не зарегистрирован. Подмена программным утолщением может выглядеть неодинаково в просмотрщиках и ухудшать соответствие макету.

pdf.add_font('NotoSans', fname='fonts/NotoSans-Regular.ttf')
pdf.add_font('NotoSans', style='B', fname='fonts/NotoSans-Bold.ttf')
pdf.set_font('NotoSans', size=10)

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

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

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

Формообразование текста, лигатуры и направления письма

Для арабского, иврита, индийских письменностей и качественной типографики латиницы требуется формообразование текста. Оно включается set_text_shaping(True) и использует HarfBuzz через пакет uharfbuzz. Движок выбирает контекстные формы глифов, применяет лигатуры и кернинг, а также выполняет двунаправленный алгоритм для строк, где смешиваются фрагменты слева направо и справа налево.

Сравнение текста без формообразования и с включённым формообразованием в fpdf2

Направление можно определить автоматически или задать параметрами direction, script и language. Явные значения полезны для коротких фрагментов, где первый сильный символ не отражает направление всего абзаца, например когда строка начинается с номера заказа. Для арабского абзаца задают direction="rtl", script="arab" и подходящий языковой тег.

Функции OpenType разрешается включать и отключать через словарь features. Например, отключение liga и kern помогает диагностировать отличие метрик или проверить, какой именно механизм меняет длину строки. Типографические настройки следует закреплять в одном месте шаблона, иначе одинаковые фразы могут занимать разную ширину на разных страницах.

Формообразование не применяется к базовым Type 1-шрифтам и требует внешней зависимости. Кроме того, автоматическая подстановка общего числа страниц через специальный маркер может конфликтовать с этим режимом. Для документов с RTL-текстом и итоговым счётчиком страниц лучше отдельно протестировать колонтитул либо вычислять количество страниц другим способом после формирования структуры.

Колонтитулы, нумерация и повторяемые элементы

Чтобы повторять шапку и подвал, создают подкласс FPDF и переопределяют header() и footer(). Эти методы вызываются при добавлении каждой страницы. В header() обычно размещают логотип, название документа и тонкую линию; в footer() устанавливают Y относительно нижнего края и выводят номер страницы. Основной текст должен начинаться ниже высоты шапки, поэтому верхнее поле или явный перевод курсора согласуют с её фактическим размером.

class ReportPDF(FPDF):
    def header(self):
        self.set_font('NotoSans', style='B', size=11)
        self.cell(0, 7, 'Ежемесячный отчёт', new_x='LMARGIN', new_y='NEXT')
        self.line(self.l_margin, self.y, self.w - self.r_margin, self.y)
        self.ln(4)

    def footer(self):
        self.set_y(-12)
        self.set_font('NotoSans', size=8)
        self.cell(0, 6, f'Страница {self.page_no()}', align='C')

Если первая страница должна иметь другой заголовок, условие проверяет page_no(). Для титульного листа можно временно отключить header() флагом экземпляра или добавить отдельную страницу до включения режима. Не следует вручную вызывать header() из основного кода: add_page() уже делает это и двойной вызов создаст наложение.

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

Таблицы без ручного рисования сетки

Контекстный менеджер table() строит таблицу из строк и ячеек, рассчитывает высоту, переносит текст и повторяет заголовочные строки на новых страницах. Внутри блока создаётся row(), затем для каждой колонки вызывается cell(). Для простого набора данных можно передать последовательность строк, а для сложной ячейки — текст, изображение, ссылку, стиль или объединение нескольких столбцов.

Таблица fpdf2 с текстом и небольшими изображениями в ячейках

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

Таблица fpdf2 с изображениями, растянутыми по ширине ячеек

borders_layout управляет схемой границ: полная сетка, горизонтальные линии, минимальное оформление и другие варианты. cell_fill_color вместе с cell_fill_mode позволяет чередовать фон строк или выделить заголовок. Цвета лучше задавать единообразно через константы шаблона, чтобы отчёты разных модулей не расходились по оттенкам.

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

Данные из pandas превращаются в список заголовков и строк, после чего передаются в table(). Значения следует заранее форматировать: даты — в нужный вид, Decimal — с фиксированным числом знаков, None — в пустую строку. Не полагайтесь на стандартное str() для финансов, потому что оно не добавляет разряды и может вывести научную нотацию.

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

Растровые изображения и управление качеством

image() принимает путь, URL, объект BytesIO или изображение Pillow. Для автономной и воспроизводимой генерации безопаснее работать с локальными файлами или уже загруженными байтами, а не обращаться к сети во время построения документа. Поддерживаются распространённые растровые форматы, включая PNG с альфа-каналом и JPEG. При повторной вставке одного и того же ресурса библиотека обычно хранит его в PDF один раз.

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

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

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

SVG, векторные пути и ограничения импорта

SVG передаётся в image() так же, как растровая картинка, но преобразуется в PDF-пути. Это позволяет встраивать диаграммы, пиктограммы и логотипы без пикселизации. SVG должен содержать понятные размеры либо при вызове нужно указать w и h. Перед массовой обработкой неизвестных файлов установите ограничения сложности, чтобы глубоко вложенные use-элементы и повторения не потребляли чрезмерно много памяти и времени.

Поддержка SVG ориентирована на пути и базовые графические конструкции, а не на полный браузерный движок. Скрипты, сложные CSS-правила, фильтры, анимация и некоторые текстовые элементы могут быть проигнорированы или отображены иначе. Надёжный рабочий процесс — открыть конкретный SVG в тестовом PDF, сравнить результат с эталоном и при несовместимости заранее преобразовать его в более простой SVG или PNG.

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

Фигуры, линии, кривые и заливки

Для простых схем доступны line(), rect(), circle(), ellipse(), polygon(), arc(), solid_arc(), bezier(), regular_polygon() и star(). Перед рисованием устанавливают толщину линии, цвет контура и цвет заливки. Параметр style определяет, нужен ли только контур, только заливка или оба варианта. Координаты используют ту же систему страницы, поэтому фигуры легко совмещать с текстом и таблицами.

Круг с заливкой и контуром, созданный средствами fpdf2

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

Вложенные прямоугольники с последовательными оттенками серого в fpdf2

Кривые Безье используются для плавных контуров и пользовательских символов. Цепочки кривых требуют согласованных контрольных точек, иначе в месте соединения появляется излом. Для сложной иллюстрации удобнее модуль drawing, который строит путь из последовательности линий и кривых и применяет стиль ко всему объекту.

Залитая кривая Безье, созданная API fpdf2

Цепочки квадратичных и кубических кривых Безье в fpdf2

Пунктир задаётся set_dash_pattern(), а стиль концов и соединений — параметрами графического состояния. Используйте local_context(), когда временно меняете цвет, прозрачность или толщину: после выхода из блока предыдущие параметры восстановятся и случайная настройка не повлияет на остальные страницы.

Преобразования и прозрачность

Контекстные менеджеры rotation(), skew() и mirror() применяют преобразование к объектам внутри блока. Точка преобразования задаётся координатами; если её выбрать неправильно, элемент повернётся вокруг неожиданного центра и уйдёт за страницу. Перед трансформацией удобно нарисовать тестовую рамку или вычислить геометрический центр элемента.

Прозрачность регулируется fill_opacity и stroke_opacity внутри local_context(). Она применяется к заливкам, линиям, изображениям и тексту. Эффект подходит для водяных знаков, выделения областей и наложения диаграмм, но в деловых документах контраст нужно проверять на печати: полупрозрачный серый текст может стать нечитаемым на офисном принтере.

Наложение фигур, текста и логотипа с разной прозрачностью в fpdf2

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

Штрихкоды, QR-коды и графики

Встроенный метод interleaved2of5() создаёт линейный код Interleaved 2 of 5, а дополнительные форматы обычно генерируются специализированной библиотекой и вставляются как Pillow-изображение или SVG. Такой подход применяется к QR, DataMatrix, PDF417, Aztec и Code 128. fpdf2 отвечает за размещение и размер в PDF, а корректность кодирования — за внешний генератор.

Линейный штрихкод Code 128, помещённый в PDF через fpdf2

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

Графики Matplotlib, pandas, Plotly и других библиотек вставляются через буфер изображения. Matplotlib удобно сохранять в BytesIO как PNG или SVG, после чего передавать буфер в image(). Для Plotly статический экспорт обычно требует Kaleido. Если SVG содержит неподдерживаемые текстовые конструкции, используйте PNG, иначе подписи осей могут исчезнуть.

Графики Matplotlib, подготовленные для вставки в PDF через fpdf2

Диаграмма pandas в документе fpdf2

Пузырьковая диаграмма Plotly, вставленная в PDF fpdf2

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

HTML-фрагменты и упрощённая разметка

write_html() преобразует ограниченный набор HTML в текст, списки, ссылки, таблицы и изображения. Это удобно, когда содержимое уже хранится как безопасная разметка из редактора или шаблона. Функция не является браузером: сложная CSS-вёрстка, произвольные скрипты, современные сетки и точное соответствие веб-странице не поддерживаются.

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

Стили заголовков и кода можно задавать через параметры обработчика HTML. Если нужен точный корпоративный макет, лучше использовать прямые методы FPDF, потому что они дают полный контроль над координатами и переносами. write_html() полезен для описаний товаров, комментариев и небольших форматированных блоков, но не заменяет систему шаблонов для сложной формы.

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

Шаблоны и массовое формирование документов

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

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

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

Имена выходных файлов нужно очищать от недопустимых символов и коллизий. Надёжнее использовать внутренний идентификатор плюс короткое понятное имя, а не только ФИО клиента. После output() проверяйте, что буфер не пуст, и сохраняйте журнал с идентификатором записи, количеством страниц и контрольной суммой файла.

Ссылки, оглавление и навигация внутри PDF

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

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

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

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

Метаданные, доступность и альтернативные описания

set_title(), set_author(), set_subject(), set_keywords() и set_creator() заполняют метаданные документа. Эти поля используются поиском, архивами и системами документооборота, поэтому их лучше получать из структурированных данных, а не копировать из видимого заголовка вслепую. Не помещайте в метаданные конфиденциальные внутренние идентификаторы, если файл будет передан наружу.

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

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

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

Аннотации и вложенные файлы

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

В PDF можно вложить дополнительный файл. Это удобно для исходного CSV, XML, изображения или машиночитаемого счёта, но увеличивает размер и меняет требования безопасности. Перед вложением проверьте расширение, MIME-тип и лимит размера, не принимайте произвольный путь из пользовательского ввода и не прикладывайте временные файлы, которые содержат больше данных, чем видимый документ.

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

Шифрование и ограничения доступа

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

Шифрование защищает файл при хранении и передаче, но после открытия пользователь может сфотографировать или перепечатать содержимое. Поэтому не следует воспринимать флаг запрет копирования как систему управления правами. Для чувствительных отчётов применяйте контроль доступа к хранилищу, сроки действия ссылок и журналирование загрузок.

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

Цифровая подпись

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

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

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

PDF/A и архивные требования

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

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

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

Получение байтов и работа в веб-приложениях

output() без пути возвращает байтовый буфер, поэтому документ не обязательно сохранять на диск. Во Flask, Django или FastAPI эти байты передают в HTTP-ответ с типом application/pdf и корректным Content-Disposition. Имя файла кодируют безопасно, а длину ответа при необходимости указывает фреймворк. Временный файл нужен только для внешней программы, которая принимает путь, или для очень большого документа, который нельзя держать в памяти.

pdf_bytes = bytes(pdf.output())
# Далее pdf_bytes передаётся в ответ веб-фреймворка
# с Content-Type: application/pdf

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

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

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

Совместная работа с pypdf и готовыми документами

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

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

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

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

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

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

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

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

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

Тестирование макетов и проверка результата

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

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

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

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

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

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

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

Удалённые URL в изображениях удобны для прототипа, но в производстве создают риск SSRF и нестабильность. Загружайте ресурс отдельным сетевым клиентом с белым списком доменов, тайм-аутом, лимитом размера и запретом внутренних адресов, затем передавайте в fpdf2 локальные байты. Так сетевые правила отделены от логики верстки.

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

Типовые ошибки и способы их устранения

ModuleNotFoundError или импорт не той библиотеки

Проверьте, что pip и python относятся к одному окружению: используйте python -m pip show fpdf2 и затем выведите fpdf.__file__. Если одновременно установлен пакет fpdf, удалите конфликтующие пакеты, очистите окружение и установите только fpdf2. Переустановка поверх смешанных файлов не всегда исправляет пространство имён, поэтому новое виртуальное окружение надёжнее.

Шрифт не задан

Сообщение о том, что шрифт не установлен, означает, что set_font() не был вызван после создания страницы или выбранное семейство не зарегистрировано. Убедитесь, что add_font() выполнен до set_font(), путь существует, а style совпадает с зарегистрированным начертанием. После add_page() активный шрифт обычно сохраняется, но явная установка в самостоятельной функции делает код понятнее.

Символ нельзя закодировать

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

Текст выходит за границы

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

Изображение не найдено или не читается

Преобразуйте относительный путь в абсолютный от каталога проекта и проверьте существование файла до image(). Для BytesIO верните позицию в начало методом seek(0) после записи. Если Pillow не распознаёт формат, сохраните ресурс как PNG или JPEG и исключите повреждённые метаданные. Ошибка одного изображения не должна оставлять частично сформированный документ в каталоге публикации.

Таблица разрывается в неудобном месте

Уменьшите высоту строк, настройте повтор заголовка и заранее проверьте свободное место. Итоги и подписи выводите отдельным блоком, который можно перенести целиком. Если одна строка выше страницы из-за огромного текста или картинки, автоматический разрыв не поможет: ограничьте содержимое или разделите его на несколько строк.

PDF открывается, но валидатор его отклоняет

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

Практический сценарий: счёт или акт

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

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

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

Практический сценарий: аналитический отчёт

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

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

Большие таблицы разбивают по разделам и повторяют шапку. Если данные поступают из pandas, форматирование выполняют до передачи в fpdf2. Даты и проценты приводят к строкам, пропуски заменяют понятным обозначением, а порядок колонок фиксируют явно. Это предотвращает изменение макета после обновления источника данных.

Практический сценарий: сертификаты и персональные карточки

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

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

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

Практический сценарий: каталог с изображениями

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

Карточки удобно рассчитывать сеткой: число колонок, ширина, промежуток и высота. Перед добавлением новой карточки проверяют, помещается ли она по Y; если нет, добавляют страницу и сбрасывают координаты. Для описания используют multi_cell() с максимальным числом строк, а полный текст переносят в отдельный раздел или сокращают по правилам бизнеса.

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

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

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

ПрограммаЛучше подходит дляГлавное ограничение
fpdf2Счетов, отчётов и шаблонных PDF из PythonМакет задаётся кодом, готовые PDF не редактируются
ReportLabСложной программной вёрстки и промышленной отчётностиБолее объёмный API и выше порог входа
borbСоздания и анализа PDF в одном Python-инструментеИная модель API и меньше совместимых примеров
WeasyPrintПреобразования HTML и CSS в печатные документыТребует HTML-макета и системных компонентов рендеринга
PyMuPDFЧтения, рендеринга и изменения существующих PDFДля потоковой генерации отчётов API менее декларативен
PDF CommanderРучного редактирования, объединения и оформления PDFНе предназначен для массовой генерации из Python

fpdf2 разумно выбирать, когда данные уже находятся в Python и документ строится из повторяемых блоков с точными координатами. ReportLab подходит командам, которым нужны развитые средства компоновки и большой накопленный стек. WeasyPrint удобнее, если макет естественно описывается HTML и CSS. PyMuPDF предпочтителен для чтения, визуализации и изменения существующих страниц, а PDF Commander — когда оператору нужен визуальный интерфейс для единичных файлов без программирования.

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

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

Она также полезна для документов, где требуется смешать точное позиционирование с потоковым текстом. Координаты задают шапку, подписи и декоративные элементы, а multi_cell(), table() и текстовые области автоматически обрабатывают переменный объём. Такой гибрид проще поддерживать, чем полностью ручной расчёт каждой строки.

fpdf2 не является лучшим выбором для визуального редактирования готового договора, распознавания скана, сложного HTML/CSS или полноценной допечатной подготовки. В этих задачах рациональнее использовать специализированный редактор, OCR-систему, браузерный движок печати или издательский пакет, а fpdf2 оставить для автоматизированных частей процесса.

Контрольный список перед внедрением

  1. Создайте отдельное окружение и убедитесь, что импортируется именно пакет fpdf2 без конфликта пространства имён.
  2. Закрепите версию Python, зависимости, шрифты и изображения в репозитории или сборочном образе.
  3. Определите формат страниц, поля, единицы и правила автоматического разрыва до написания отдельных блоков.
  4. Подключите TrueType-шрифты для кириллицы и зарегистрируйте все используемые начертания.
  5. Сформируйте функции для заголовков, таблиц, изображений и итогов, которые возвращают фактическую нижнюю координату.
  6. Проверьте длинные строки, пустые данные, большие изображения и максимальное число страниц.
  7. Добавьте автоматическую проверку структуры PDF, ключевого текста, размера файла и времени генерации.
  8. Проведите визуальное сравнение и реальную печать, если документ предназначен для бумаги.
  9. Включите лимиты на пользовательские ресурсы и исключите прямой доступ к произвольным путям и URL.
  10. Для подписи, шифрования или PDF/A проверьте требования принимающей системы независимым валидатором.

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

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

Разделяйте построение документа на небольшие функции: render_header(), render_customer_block(), render_items_table(), render_totals() и render_signatures(). Каждая функция принимает объект FPDF и подготовленные данные, не обращается напрямую к базе и возвращает координату либо сведения о добавленных страницах. Такой контракт упрощает тестирование и не даёт скрытым изменениям курсора распространяться на весь файл.

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

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

Наблюдаемость в производственной системе

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

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

Печать и цвет

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

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

Обновление зависимости без поломки макета

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

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

Работа с локалями

Не полагайтесь на системную локаль при форматировании дат и чисел. Явно задавайте формат даты, десятичный разделитель, разряды и знак валюты. Значение, подготовленное для PDF, должно быть строкой с окончательным представлением; тогда сервер в другой стране не изменит вид суммы или порядок дня и месяца.

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

Обработка пустых и отсутствующих значений

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

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

Выбор между координатами и потоком

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

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

Документ как часть транзакции

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

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

Резервные ресурсы и отказоустойчивость

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

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

Хранение и выдача результата

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

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

Поддержка нескольких шаблонов

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

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

Очереди и фоновые задачи

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

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