ReportLab

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

Основной рабочий выбор сводится к двум уровням API. Canvas рисует страницу командами по координатам и подходит для бланков, билетов, этикеток и нестандартной векторной графики. Platypus принимает последовательность абзацев, таблиц, изображений и других Flowable-объектов, измеряет их, переносит между фреймами и страницами и применяет единые стили.

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

Скачать ReportLab

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

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

Работа начинается не с открытия пустой страницы, а с описания результата в Python-коде. Скрипт получает данные, выбирает формат страницы, создаёт объект документа, добавляет элементы и завершает запись файла. Для фиксированного бланка обычно удобен canvas: каждый вызов рисует строку, линию, прямоугольник или изображение в заданной точке. Для отчёта, где объём текста и число строк заранее неизвестны, удобнее Platypus: содержимое помещается в список Flowable-объектов, а движок сам решает, где закончить страницу и продолжить следующий блок.

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

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

Структура DocTemplate, PageTemplate, Frame и Flowable в ReportLab

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

Установка и минимальная проверка

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

python -m venv .venv
.venv\Scripts\python -m pip install reportlab
.venv\Scripts\python -c "import reportlab; print("ReportLab imported")"

На Linux и macOS путь к интерпретатору в виртуальном окружении отличается, но смысл тот же: установка должна выполняться тем же Python, который запускает приложение. Ошибка ModuleNotFoundError обычно означает, что pip и python относятся к разным окружениям. Надёжная форма команды — python -m pip, потому что она привязывает установку к выбранному интерпретатору. В IDE дополнительно проверяют активный interpreter проекта, а в контейнере — слой, где выполнялась установка.

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

from reportlab.pdfgen import canvas

c = canvas.Canvas("hello.pdf")
c.drawString(72, 770, "Hello, ReportLab")
c.showPage()
c.save()

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

Canvas: точные координаты и рисование страницы

Canvas — основной инструмент для бланков, билетов, этикеток, сертификатов и других документов, где положение каждого элемента известно заранее. Координаты измеряются в пунктах: 72 пункта равны одному дюйму. Начало координат по умолчанию находится в левом нижнем углу, ось X направлена вправо, а ось Y — вверх. Для перехода к привычной метрической системе используются константы mm, cm и inch. Выражение 20*mm читается лучше, чем вычисленное вручную число пунктов, и уменьшает риск смешения единиц.

Размер страницы задаётся при создании canvas. Готовые константы A4, A5, letter и legal покрывают типовые случаи, а произвольный формат передаётся кортежем ширины и высоты. Альбомную ориентацию получают функцией landscape, которая меняет местами стороны. Размер следует выбирать до вычисления сетки и полей. Если один и тот же шаблон должен работать на A4 и Letter, координаты лучше вычислять от page_width и page_height, а не хранить отдельные магические числа.

from reportlab.lib.pagesizes import A4, landscape
from reportlab.lib.units import mm
from reportlab.pdfgen.canvas import Canvas

w, h = landscape(A4)
c = Canvas("ticket.pdf", pagesize=(w, h))
c.rect(15*mm, 15*mm, w-30*mm, h-30*mm)
c.drawString(20*mm, h-25*mm, "Номер заказа")
c.save()

Команды drawString, drawRightString и drawCentredString отличаются способом привязки текста. Для цены, номера или даты удобно правое выравнивание, потому что конец строки остаётся на одной вертикали. Для заголовка — центрирование относительно известной оси. Длина строки может вычисляться stringWidth; это полезно для линии подчёркивания, ручной колонки или проверки, помещается ли подпись. Однако stringWidth не выполняет перенос и не учитывает сложную верстку абзаца — для этого нужен TextObject или Paragraph.

Пример трансформации и зеркального отображения на canvas

Трансформации translate, rotate и scale изменяют систему координат всех последующих команд. Перед локальным поворотом или масштабированием состояние сохраняют saveState, а затем восстанавливают restoreState. Иначе поворот логотипа повлияет на таблицу и колонтитул ниже. Пары сохранения и восстановления должны быть сбалансированы, особенно в функциях, которые могут завершиться исключением. Практичный шаблон — оборачивать локальное рисование в try/finally и вызывать restoreState в секции finally.

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

Линии, контуры, заливки и цветовые пространства

Графические примитивы canvas включают линии, прямоугольники, скруглённые прямоугольники, окружности, эллипсы, дуги и клинья. У каждого объекта отдельно настраиваются обводка и заливка. Цвет линии задаётся setStrokeColor, цвет внутренней области — setFillColor. Толщина управляется setLineWidth, окончания — setLineCap, соединения сегментов — setLineJoin. Для пунктирной линии используется setDash; после неё важно вернуть сплошной стиль, если следующие элементы не должны наследовать пунктир.

Параметры толщины, окончания и стиля линии в ReportLab

RGB подходит для документов, ориентированных на экран и офисную печать. Значения можно задавать объектами colors.red, HexColor или числовыми каналами от 0 до 1. Для полиграфической подготовки применяется CMYK, включая специальные классы с параметрами плотности и overprint. Простого переключения на CMYK недостаточно: растровые изображения могут оставаться в RGB, а итоговое цветоделение нужно проверять средствами допечатной подготовки. ReportLab не заменяет цветопробу и не гарантирует, что произвольный JPEG соответствует выбранному профилю.

Модели RGB и образцы цветов, созданные ReportLab

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

Path объединяет последовательность moveTo, lineTo, curveTo и close в единый контур. Он нужен для логотипов, пиктограмм, нестандартных рамок, карт и декоративных элементов. Один и тот же путь можно обвести, залить или выполнить обе операции. Кривые Безье задаются двумя управляющими точками и концом сегмента; при сложной форме удобно сначала строить контур в отдельной функции и тестировать его на координатной сетке. Неправильное направление подпутей может повлиять на правило заливки и вид отверстий.

Составной векторный контур карандаша

Контур руки из кривых Безье

Залитая форма руки после замыкания контура

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

Текстовые объекты, перенос строк и типографика

drawString рисует одну строку и не двигает курсор автоматически. Для многострочного блока используется TextObject, который создаётся beginText. Ему задают начальную точку, шрифт, размер, межстрочный интервал, межсимвольный и межсловный пробел. textLine выводит строку и перемещает курсор на следующую позицию, textOut продолжает вывод в текущей строке, moveCursor добавляет смещение. После настройки объект передаётся drawText. Такой подход быстрее и чище, чем десятки независимых вызовов drawString.

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

Стандартные PDF-шрифты и их начертания

В PDF присутствует набор стандартных шрифтов, но он рассчитан в основном на латиницу и символы встроенных кодировок. Для кириллицы, греческого, расширенной латиницы и большинства азиатских письменностей регистрируют внешний шрифт. TrueType-регистрация выполняется через pdfmetrics.registerFont и TTFont. Имя, под которым шрифт зарегистрирован, затем используется в canvas, ParagraphStyle и таблицах. Нельзя путать внутреннее имя с именем файла: ошибка KeyError или сообщение о неизвестном шрифте часто означает несовпадение этих строк.

from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont

pdfmetrics.registerFont(TTFont("AppSans", "fonts/AppSans-Regular.ttf"))
c.setFont("AppSans", 11)
c.drawString(72, 760, "Отчёт сформирован автоматически")

Пример текста с внедрённым TrueType-шрифтом

Для семейства с жирным и курсивным начертанием регистрируют каждый файл, затем связывают семейство через registerFontFamily. После этого теги b и i в Paragraph могут выбирать соответствующие начертания. Если семейство не связано, библиотека иногда пытается синтезировать замену или оставляет основной шрифт, из-за чего выделение пропадает. В тестовом PDF полезно вывести четыре строки: обычную, жирную, курсивную и жирную курсивную, а затем проверить свойства встроенных шрифтов в просмотрщике.

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

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

Paragraph и встроенная разметка

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

Текст из внешних данных нельзя вставлять без экранирования. Символы амперсанда и угловых скобок интерпретируются как разметка и могут вызвать ошибку парсера. Для пользовательского текста применяется xml.sax.saxutils.escape или собственная функция, которая сначала экранирует данные, а затем добавляет только контролируемые теги. Особенно важно разделять содержимое и оформительскую разметку в счетах, комментариях и выгрузках из CRM.

from xml.sax.saxutils import escape
from reportlab.platypus import Paragraph

name = escape(customer_name)
story.append(Paragraph(f"Клиент: <b>{name}</b>", styles["BodyText"]))

ParagraphStyle содержит fontName, fontSize, leading, textColor, alignment, leftIndent, rightIndent, firstLineIndent, spaceBefore, spaceAfter и множество дополнительных параметров. Стиль можно наследовать от базового через parent, что позволяет определить общую типографику и менять только отличия заголовка, примечания или подписи. Изменение объекта стиля после создания Paragraph может повлиять на последующие элементы; для независимых вариантов создают новый стиль, а не временно меняют общий.

Параметры отступов, рамки и фона ParagraphStyle

Параметры allowWidows и allowOrphans управляют одиночными строками в начале и конце страницы, однако реальный результат зависит от доступной высоты фрейма. keepWithNext старается удержать заголовок со следующим блоком. Если группа слишком велика для страницы, чрезмерное количество keepWithNext может привести к LayoutError. Для составного блока с изображением, заголовком и таблицей применяют KeepTogether, но только когда весь блок гарантированно помещается на пустой странице. Большой KeepTogether не умеет волшебным образом сжать содержимое.

Встроенные изображения и выравнивание внутри абзаца

Маркированные пункты можно строить Paragraph с bulletText или классами ListFlowable и ListItem. Второй вариант лучше для многоуровневых списков, управления отступами, нумерацией и разрывами. Для длинного пункта следует проверять, что маркер не пересекается с первой строкой и что leftIndent больше bulletIndent. В определениях и компактных справочниках применяют висячий отступ: маркер или термин находится левее, а все строки описания начинаются на одной вертикали.

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

Platypus: Story, Flowable и сборка документа

В Platypus документ собирается из Story — последовательности Flowable-объектов. К базовым элементам относятся Paragraph, Spacer, Image, Table, PageBreak, NextPageTemplate, KeepTogether и рисунки ReportLab Graphics. SimpleDocTemplate подходит для одной схемы страницы с одинаковыми полями. BaseDocTemplate нужен, когда есть титульная страница, несколько колонок, разные колонтитулы, страницы приложений или переключение шаблонов по ходу документа.

Метод build проходит по Story, измеряет элементы и помещает их в доступные Frame. Flowable сначала получает wrap(availWidth, availHeight), возвращает требуемый размер, затем вызывается draw. Если элемент выше доступного пространства, движок пытается split. Paragraph и Table умеют разбиваться, Image по умолчанию — нет. Собственный Flowable должен корректно реализовывать wrap и draw, а при необходимости split. Ошибка в возвращаемой высоте приводит к наложению или бесконечным попыткам размещения.

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

Spacer удобен для небольших фиксированных интервалов, но не должен компенсировать ошибки стилей. Вертикальную ритмику задают spaceBefore и spaceAfter, чтобы расстояние сохранялось при переносе. Большой Spacer может оказаться первым элементом новой страницы и оставить пустую область; conditionalSpaceBefore у некоторых элементов помогает избежать такого эффекта. Для принудительного начала раздела применяют PageBreak, а CondPageBreak создаёт разрыв только при недостатке места.

PageTemplate содержит список Frame и функцию onPage. Frame определяет прямоугольную область с внутренними отступами, куда течёт Story. На странице можно разместить две колонки, боковую панель и основную область, но переход между фреймами происходит в порядке их объявления. Если содержимое должно сначала заполнить левую колонку, затем правую, фреймы располагают именно так. Для перехода к другой схеме добавляют NextPageTemplate перед PageBreak.

Пользовательский Flowable полезен для шкалы, подписи с линией, компактного индикатора статуса или блока, которого нет среди стандартных элементов. В draw используется self.canv, а координаты начинаются в левом нижнем углу области элемента. Хороший Flowable не меняет внешнее состояние canvas: он вызывает saveState, рисует, затем restoreState. Размеры и данные передаются через конструктор; глобальные переменные усложняют повторное использование и параллельную генерацию.

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

Таблицы: размеры, стили и разбиение

Table принимает двумерную последовательность, где каждая ячейка может содержать строку, число, Paragraph, Image, Drawing или список Flowable. Простая строка не переносится так гибко, как Paragraph, поэтому длинные описания и заголовки столбцов лучше сразу оборачивать в Paragraph. Ширины можно передать явно, оставить None для автоматического расчёта или использовать комбинацию фиксированных и вычисляемых значений. Автоматический режим удобен для небольших таблиц, но на сотнях строк и сложных ячейках увеличивает время измерения.

TableStyle применяет команды к прямоугольным диапазонам ячеек. GRID и BOX рисуют сетку и внешнюю рамку, BACKGROUND задаёт фон, TEXTCOLOR — цвет текста, FONTNAME и FONTSIZE — шрифт, ALIGN и VALIGN — выравнивание, LEFTPADDING и другие отступы — внутреннее пространство. Диапазон задаётся координатами от левого верхнего угла; отрицательные индексы отсчитываются от конца. Команда для (-1,-1) затрагивает последнюю ячейку, а диапазон (0,0),(-1,0) — всю первую строку.

data = [["Товар", "Кол-во", "Сумма"], *rows]
table = Table(data, colWidths=[90*mm, 25*mm, 35*mm], repeatRows=1)
table.setStyle(TableStyle([
    ("BACKGROUND", (0,0), (-1,0), colors.HexColor("#E8EEF7")),
    ("GRID", (0,0), (-1,-1), 0.4, colors.grey),
    ("ALIGN", (1,1), (-1,-1), "RIGHT"),
    ("VALIGN", (0,0), (-1,-1), "TOP"),
]))

repeatRows повторяет одну или несколько строк заголовка после разрыва страницы. Это важнее декоративной сетки: читатель должен понимать столбцы на каждой странице. splitByRow разрешает разрывать таблицу между строками; splitInRow позволяет делить слишком высокую строку, если содержимое ячеек поддерживает разбиение. Без splitInRow одна ячейка с длинным Paragraph может не помещаться даже на пустую страницу и вызвать LayoutError.

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

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

Чередование фона строк можно задать командами ROWBACKGROUNDS или сформировать набор BACKGROUND по индексам. Условное форматирование лучше вычислять в Python: например, отрицательные значения получают другой TextColor, просроченные строки — фон, итоговая строка — верхнюю линию. Это делает правило прозрачным и не требует изменять TableStyle после сборки. Для доступности и копирования чисел не следует превращать таблицу в растровое изображение.

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

Шаблоны страниц, колонтитулы и нумерация

Колонтитулы рисуются через onFirstPage и onLaterPages у SimpleDocTemplate или через onPage у PageTemplate. Функция получает canvas и doc, поэтому знает размер страницы, текущий номер и границы доступной области. Внутри неё сохраняют состояние, выбирают шрифт, рисуют линию, текст или логотип и восстанавливают состояние. Колонтитул не является частью Story и не отнимает место автоматически; поля документа должны заранее оставлять достаточную высоту.

Номер страницы доступен как canvas.getPageNumber. Формат страница X из Y требует знать общее число страниц, которого нет в первом проходе. Решение — специальный Canvas, сохраняющий состояние каждой страницы и после завершения повторно отрисовывающий колонтитулы с итоговым количеством, либо multiBuild с уведомлениями. Самый простой вариант X не требует второго прохода и надёжнее для потоковой выдачи.

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

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

Разные ориентации внутри одного PDF реализуются разными PageTemplate с соответствующим pagesize и обработчиком. Перед широкой таблицей добавляют переключение на альбомный шаблон и разрыв, после таблицы — возврат к книжному. Элемент, который уже начал размещаться, не может изменить размер текущей страницы задним числом, поэтому переключение должно предшествовать PageBreak. Обработчики обязаны использовать размеры текущего шаблона, а не глобальную константу A4.

Закладки и оглавление требуют стабильных имён целей. В обработчике afterFlowable можно распознавать Paragraph определённого стиля, создавать bookmarkPage и добавлять запись outline. TableOfContents получает уведомления о заголовках во время сборки, поэтому обычно применяется multiBuild. Текст заголовка очищают от разметки, а ключ формируют уникальным; два одинаковых заголовка не должны перезаписывать одну закладку.

Пустая последняя страница чаще всего появляется из-за PageBreak в самом конце Story или из-за обработчика, который вызывает showPage вручную. В Platypus нельзя самостоятельно завершать страницу в onPage: сборщик делает это сам. Перед добавлением финального PageBreak проверяют, будет ли после него содержимое.

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

Canvas.drawImage размещает растровое изображение по координатам и может сохранять пропорции через preserveAspectRatio. drawInlineImage встраивает данные иначе и обычно хуже подходит для повторяющихся изображений, потому что не использует повторное обращение к одному ресурсу так эффективно. Для логотипа, встречающегося на каждой странице, drawImage предпочтительнее. В Platypus класс Image измеряется как Flowable и участвует в переносе.

Размер в PDF задаётся в пунктах и не равен пиксельному размеру файла. Фотография 4000×3000 пикселей, показанная в блоке 100×75 пунктов, остаётся тяжёлым ресурсом, если её предварительно не уменьшить. Для экранного отчёта обычно достаточно подготовить изображение под реальный размер с разумным запасом. Для печати выбирают разрешение с учётом физического размера и требований типографии. Повторное сжатие JPEG может ухудшить текст и тонкие линии, поэтому диаграммы лучше оставлять векторными.

Параметр mask="auto" использует альфа-канал PNG и позволяет отображать прозрачный фон. У старых GIF или изображений с палитрой результат нужно проверять, потому что маска может трактоваться иначе. Если белый фон логотипа должен быть прозрачным, лучше подготовить корректный PNG, чем перечислять диапазоны цвета в маске: диапазон может случайно удалить светлые детали внутри рисунка.

ImageReader принимает путь, поток или объект изображения и кэширует данные для повторного использования. При генерации в памяти BytesIO нужно вернуть позицию потока в начало перед чтением. Ошибка cannot identify image file часто возникает, когда поток пуст, позиция находится в конце или сервер вернул HTML вместо изображения. До передачи в ReportLab полезно проверить Content-Type, размер и декодирование библиотекой изображений.

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

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

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

ReportLab Graphics: векторные рисунки и рендереры

Пакет reportlab.graphics описывает рисунок независимо от конечного формата. Drawing содержит примитивы Rect, Circle, Ellipse, Line, PolyLine, Polygon, String, Path и Group. Один Drawing можно поместить в PDF как Flowable, отрисовать на canvas или вывести через доступный рендерер в SVG и растровый формат. Это удобно для диаграммы, которая должна одинаково использоваться в отчёте, веб-интерфейсе и электронной почте.

Базовые векторные фигуры ReportLab Graphics

Каждая фигура имеет геометрические свойства и свойства оформления. Например, Rect хранит x, y, width, height, fillColor, strokeColor и strokeWidth. Объект можно именовать при добавлении в Drawing, чтобы позже изменить конкретный элемент без поиска по списку contents. Это полезно для шаблона индикатора: один раз создаётся рисунок, затем обновляются значение, подпись и цвет состояния.

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

Группы и преобразования элементов ReportLab Graphics

renderPDF сохраняет векторную природу фигур в PDF. Растровый renderPM зависит от дополнительного backend; в современных установках используется связка с Cairo. Если импорт renderPM проходит, а drawToFile завершается ошибкой, нужно проверить установку графического backend, библиотек шрифтов и доступность нужного формата. Для серверного контейнера это отдельная системная зависимость, которую следует включить в образ и проверить в CI.

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

Drawing может участвовать в Platypus и разбираться как обычный Flowable, если его размер не превышает фрейм. При адаптивном шаблоне рисунок масштабируют до доступной ширины, сохраняя отношение сторон. Масштабирование только свойства width без изменения transform не всегда меняет содержимое так, как ожидается; безопаснее использовать scale или функцию, создающую Drawing нужного размера.

Диаграммы, оси, легенды и подписи данных

Chart-модули строятся поверх Drawing. Доступны столбчатые, линейные, круговые и другие бизнес-диаграммы. Типичный процесс состоит из создания Drawing, добавления объекта диаграммы, передачи данных и настройки осей, подписей, легенды и цветов. Координаты x и y диаграммы относятся к Drawing, а width и height задают внутреннюю область построения. Нужно оставлять место для подписей категорий, делений и легенды, иначе они выйдут за границы рисунка.

Ось и столбчатая диаграмма с двумя рядами данных

VerticalBarChart принимает список рядов. Если нужен один ряд, данные всё равно передаются как последовательность внутри последовательности. categoryAxis.categoryNames должны соответствовать количеству категорий. valueAxis.valueMin, valueMax и valueStep задают шкалу явно; автоматический диапазон удобен, но может менять визуальное сравнение между отчётами. Для серии ежемесячных отчётов фиксированная шкала часто честнее, если значения находятся в известном диапазоне.

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

LineChart и LinePlot отличаются моделью данных. Категориальная линейная диаграмма использует последовательные позиции и подписи категорий, а LinePlot работает с парами X,Y и подходит для неравномерной шкалы. Нельзя передавать временные интервалы как просто подписи, если расстояние между датами имеет смысл: в этом случае нужна числовая или временная ось. Линии настраиваются по толщине, цвету и символам точек; слишком много рядов требует легенды и различимых маркеров, а не только оттенков.

Линейная диаграмма ReportLab с двумя рядами

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

Круговая диаграмма с подписями секторов

Варианты подписей круговой диаграммы

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

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

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

Штрихкоды, QR-коды и машинно читаемые метки

Модули barcode создают Code 128, Code 39, EAN, UPC, QR и другие метки в зависимости от выбранного класса. Штрихкод можно отрисовать как Drawing или поместить на canvas. Перед созданием нужно проверить допустимый алфавит и длину. EAN и UPC рассчитаны на строго определённое количество цифр и контрольную сумму, тогда как Code 128 допускает более широкий набор символов. Передача неподходящей строки должна выявляться до сборки документа.

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

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

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

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

Интерактивные формы, ссылки, закладки и аннотации

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

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

Ссылки внутри документа строятся из двух частей: bookmarkPage создаёт цель, linkRect или linkAbsolute — кликабельную область. Относительная ссылка учитывает текущую систему координат, абсолютная — координаты страницы. Для оглавления удобнее создавать ссылки в обработчике Paragraph или через встроенную разметку. Кликабельный прямоугольник должен покрывать текст, но не соседнюю строку; при ручном расчёте учитывают высоту шрифта и ведущий интервал.

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

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

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

Метаданные, защита и параметры PDF

Методы setTitle, setAuthor, setSubject и setKeywords записывают свойства документа. Они не рисуют текст на странице, но отображаются в свойствах файла и помогают системам хранения. Значения формируются из проверенных строк без служебных секретов. Имя пользователя, внутренний путь сервера или SQL-запрос не должны попадать в метаданные случайно.

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

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

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

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

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

Интеграция с веб-приложением, API и очередью задач

PDF можно записывать не только в файл, но и в BytesIO. Это удобно для HTTP-ответа, вложения электронной почты или сохранения в объектное хранилище. После build или save поток переводят в начало методом seek(0), затем читают bytes. Для больших документов хранение всего результата в памяти может быть дорого, поэтому применяют временный файл или задачу фоновой очереди.

from io import BytesIO
from reportlab.platypus import SimpleDocTemplate

buffer = BytesIO()
doc = SimpleDocTemplate(buffer)
doc.build(story)
buffer.seek(0)
pdf_bytes = buffer.getvalue()

HTTP-ответ должен содержать корректный тип application/pdf и безопасное имя в Content-Disposition. Имя формируют отдельно от пути файловой системы, удаляют управляющие символы и учитывают кодировку. Нельзя передавать пользовательскую строку прямо в заголовок. Для просмотра в браузере выбирают inline, для явного сохранения — attachment; это решение веб-слоя, а не библиотеки генерации.

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

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

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

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

Производительность и работа с большими отчётами

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

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

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

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

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

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

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

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

LayoutError сообщает, что Flowable не помещается в доступную область и не может быть разбит. В сообщении обычно указаны тип элемента, его размеры и Frame. Первым делом проверяют, помещается ли элемент на пустой странице. Если нет, уменьшают изображение, разрешают splitInRow для таблицы, разбивают KeepTogether или исправляют собственный Flowable. Увеличение страницы без понимания причины лишь скрывает проблему.

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

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

Шрифт отображается квадратами, когда в файле нет нужных глифов или используется не тот зарегистрированный шрифт. Проверяют конкретный TTF/OTF, семейство, путь и наличие символов. Если текст смешивает несколько письменностей, может понадобиться fallback по фрагментам, потому что один корпоративный шрифт не покрывает всё. Стандартный Helvetica не является универсальным Unicode-шрифтом.

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

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

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

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

Проверка качества созданного PDF

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

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

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

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

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

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

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

ПрограммаЛучше подходит дляГлавное ограничение
ReportLabТочной программной верстки, таблиц, диаграмм и массовой генерации PDF из PythonМакет описывается кодом, а не визуально
fpdf2Быстрой генерации документов на Python с простым API, Unicode, таблицами и базовой разметкойСложные многофреймовые макеты требуют больше ручной логики
borbСоздания и программной обработки PDF единым чистым Python APIЭкосистема и число готовых примеров меньше, чем у старых решений
WeasyPrintПреобразования HTML и CSS в печатные отчёты, счета и публикацииМакет зависит от поддерживаемого подмножества HTML и CSS
wkhtmltopdfКомандного преобразования готовых веб-страниц в PDF через движок WebKitСтарый движок ограничивает современные веб-возможности

ReportLab выбирают, когда документ строится из структурированных данных и требуется точный контроль над координатами, переносом, таблицами, закладками и векторными диаграммами. fpdf2 удобнее для небольшого проекта, где важен компактный и понятный API без сложной системы PageTemplate. borb подходит, если в одном Python-инструменте нужны и создание, и операции с существующими PDF, но конкретный сценарий следует сверять с документацией и лицензией используемых модулей.

WeasyPrint практичнее, когда дизайн уже существует как HTML/CSS и команда хорошо владеет веб-версткой. Он снимает необходимость вручную описывать каждый Paragraph и Table, но требует учитывать печатные CSS-правила и поддержку браузерных возможностей. wkhtmltopdf полезен для наследуемых систем и прямого преобразования страниц, однако старый WebKit делает его менее предсказуемым для современного CSS. Для ручного редактирования существующего файла эти инструменты не заменяют PDF-редактор: программные генераторы решают задачу выпуска документов из данных, а не интерактивной правки существующей страницы.

Практические сценарии применения

Счёт или накладная обычно строится сочетанием canvas и Platypus. Canvas рисует постоянную сетку, реквизиты и номера страниц, Story содержит адреса, позиции, итоги и условия оплаты. Денежные значения форматируются до передачи в Paragraph, десятичные разряды выравниваются вправо, заголовок таблицы повторяется. Если строк слишком много, приложение решает, где размещать промежуточные итоги и подпись, а не надеется, что большой KeepTogether удержит всё на одной странице.

Сертификат или билет удобнее рисовать canvas: фон, рамка, QR-код, имя и номер имеют фиксированные координаты. Длинное имя измеряется stringWidth, при необходимости уменьшается шрифт до допустимого минимума или переносится по заранее определённому правилу. Автоматическое бесконечное уменьшение недопустимо, иначе редкий длинный текст станет нечитаемым.

Аналитический отчёт строится через BaseDocTemplate: титульная страница, оглавление, главы, таблицы и диаграммы. Заголовки отправляют уведомления TableOfContents и создают закладки. Широкие таблицы переключают страницу в альбомную ориентацию, а обычный текст возвращают в книжную. Диаграммы сопровождаются таблицами данных и короткими выводами.

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

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

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

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

Когда ReportLab подходит, а когда нужен другой инструмент

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

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

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

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

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