Генерация PDF в проектах на Python: от простого к сложному
Основные подходы к созданию PDF на Python
Какое решение позволяет быстро и гибко создавать PDF с текстом, изображениями и таблицами без внешних зависимостей?
Наиболее эффективным решением для создания PDF с нуля является библиотека FPDF2 (форк оригинального FPDF). Она не требует внешних программ, поддерживает Unicode через TrueType шрифты и предоставляет простой API для добавления текста, изображений, таблиц и линий.
Установка
pip install fpdf2Python создание pdf (создание pdf на python)
Пример создания простого PDF
from fpdf import FPDF
pdf = FPDF()
pdf.add_page()
pdf.set_font('Arial', 'B', 16)
pdf.cell(40, 10, 'Привет, мир!')
pdf.output('hello.pdf')
Пояснение шагов
- Создаётся экземпляр класса FPDF.
- Метод add_page() добавляет новую страницу.
- set_font() задаёт шрифт (имя, стиль, размер). Для кириллицы используется шрифт Arial (его необходимо добавить отдельно, но встроенный Helvetica не поддерживает русские буквы).
- cell() выводит текст в ячейке. Параметры: ширина, высота, текст.
- output() сохраняет документ.
Работа с кириллицей
Для корректного отображения русского текста нужно добавить TrueType шрифт с поддержкой кириллицы, например DejaVuSans:
pdf.add_font('DejaVu', '', 'DejaVuSans.ttf', uni=True)
pdf.set_font('DejaVu', '', 14)
Добавление изображения и таблицы
pdf.image('logo.png', x=10, y=8, w=30)
pdf.set_font('DejaVu', '', 10)
for row in [['Имя', 'Возраст'], ['Анна', '25'], ['Иван', '30']]:
for col in row:
pdf.cell(40, 10, col, border=1)
pdf.ln()
Типичные проблемы и ошибки:
- Ошибка:
FPDF error: Unsupported font type: /Type1при использовании встроенного шрифта. Решение: добавить TTF шрифт с помощьюadd_font(). - Проблема: текст не помещается в ячейку. Решение: использовать
multi_cell()для автоматического переноса. - Ошибка:
UnicodeEncodeErrorпри записи. Решение: убедиться, что установлен шрифт с Unicode и параметрuni=True.
Вариант 1: ReportLab - сложная графика и точное позиционирование
Как создать PDF с точным позиционированием элементов, векторной графикой и диаграммами?
Библиотека ReportLab предоставляет низкоуровневый API для рисования линий, прямоугольников, текста и изображений с абсолютными координатами. Она идеальна для профессиональных отчётов, накладных и графиков.
from reportlab.pdfgen import canvas
c = canvas.Canvas('report.pdf')
c.drawString(100, 750, 'Заголовок отчёта')
c.line(50, 740, 550, 740)
c.drawImage('logo.png', 50, 700, width=100, height=50)
c.save()
Проблемы:
- Необходимость ручного расчёта координат Y (от нижнего края).
- Ошибка
AttributeError: module 'reportlab' has no attribute 'graphics'- нужно импортироватьfrom reportlab.graphics.shapes import Drawing.
Вариант 2: pdfkit - конвертация HTML в PDF
Как превратить веб-страницу или HTML-шаблон в PDF с сохранением стилей?
pdfkit является обёрткой для утилиты wkhtmltopdf. Он принимает HTML строку или URL и возвращает PDF. Удобен для генерации документов на основе шаблонов.
import pdfkit
pdfkit.from_string('<h1>Привет</h1><p>Текст</p>', 'output.pdf')
pdfkit.from_url('http://example.com', 'page.pdf')
Типичные ошибки:
- Не установлен wkhtmltopdf - скачать с официального сайта и добавить в PATH.
- Русские символы отображаются кракозябрами - указать опцию
--encoding UTF-8.
Вариант 3: WeasyPrint - современный HTML+CSS в PDF
Как конвертировать HTML с CSS3 (flexbox, grid) в PDF без внешних зависимостей?
WeasyPrint использует собственный движок рендеринга, поддерживает продвинутые CSS-свойства и не требует wkhtmltopdf. Подходит для сложных дизайнов.
from weasyprint import HTML
HTML(string='<html><body><h1>Документ</h1></body></html>').write_pdf('output.pdf')
HTML(filename='template.html').write_pdf('result.pdf')
Проблемы:
- Медленная работа при большом количестве страниц.
- Некоторые CSS-свойства (например, @page с полями) могут игнорироваться.
Расширенные примеры создания PDF
Ниже приведены практические примеры, демонстрирующие возможности различных библиотек.
Пример 1: Многостраничный документ с колонтитулами (FPDF2)
Создание документа с нумерацией страниц и повторяющимся верхним и нижним колонтитулом.
from fpdf import FPDF
class CustomPDF(FPDF):
def header(self):
self.set_font('DejaVu', 'B', 12)
self.cell(0, 10, 'Отчёт компании', 0, 1, 'C')
def footer(self):
self.set_y(-15)
self.set_font('DejaVu', 'I', 8)
self.cell(0, 10, f'Страница {self.page_no()}/{{nb}}', 0, 0, 'C')
pdf = CustomPDF()
pdf.alias_nb_pages()
pdf.add_font('DejaVu', '', 'DejaVuSans.ttf', uni=True)
pdf.add_font('DejaVu', 'B', 'DejaVuSans-Bold.ttf', uni=True)
pdf.add_font('DejaVu', 'I', 'DejaVuSans-Oblique.ttf', uni=True)
for i in range(1, 4):
pdf.add_page()
pdf.set_font('DejaVu', '', 12)
pdf.cell(0, 10, f'Содержимое страницы {i}', 0, 1)
pdf.output('document_with_header_footer.pdf')
Результат: PDF-файл из 3 страниц, на каждой странице вверху выводится текст "Отчёт компании", внизу - номер страницы и общее количество.
Пример 2: Создание PDF с таблицей, автоматическим переносом текста (FPDF2)
from fpdf import FPDF
pdf = FPDF()
pdf.add_page()
pdf.add_font('DejaVu', '', 'DejaVuSans.ttf', uni=True)
pdf.set_font('DejaVu', '', 10)
# Заголовки
headers = ['Имя', 'Описание', 'Цена']
col_widths = [40, 100, 30]
for i, header in enumerate(headers):
pdf.cell(col_widths[i], 10, header, border=1, align='C')
pdf.ln()
# Данные
data = [
['Товар A', 'Длинное описание, которое не помещается в одну строку', '100'],
['Товар B', 'Краткое описание', '250'],
]
for row in data:
for i, cell_text in enumerate(row):
pdf.cell(col_widths[i], 10, cell_text, border=1)
pdf.ln()
pdf.output('table.pdf')
Результат: Таблица с тремя колонками, текст в ячейках обрезается по ширине - для переноса нужна multi_cell.
Для переноса текста внутри ячейки используется multi_cell() с последующим выравниванием высоты строк.
Пример 3: График на PDF с помощью ReportLab
from reportlab.pdfgen import canvas
from reportlab.graphics.shapes import Drawing, Line, Circle, String
from reportlab.graphics import renderPDF
d = Drawing(400, 200)
d.add(Line(50, 50, 350, 150)) # линия тренда
d.add(Circle(100, 70, 5, fillColor='red')) # точка данных
d.add(String(150, 100, 'Продажи', fontName='Helvetica', fontSize=12))
c = canvas.Canvas('chart.pdf')
renderPDF.draw(d, c, 50, 400)
c.save()
Результат: PDF-страница с простым графиком: линия, точка и текст. Можно добавлять сложные диаграммы через reportlab.graphics.charts.
Пример 4: Объединение нескольких PDF в один (PyPDF2)
from PyPDF2 import PdfMerger
merger = PdfMerger()
merger.append('file1.pdf')
merger.append('file2.pdf')
merger.write('merged.pdf')
merger.close()
Результат: Файл merged.pdf содержит страницы из file1.pdf и file2.pdf в порядке добавления.
Пример 5: Добавление водяного знака (ReportLab + PyPDF2)
from reportlab.pdfgen import canvas
from PyPDF2 import PdfReader, PdfWriter
# Создание водяного знака
c = canvas.Canvas('watermark.pdf')
c.setFont('Helvetica', 60)
c.setFillColorRGB(0.5, 0.5, 0.5, 0.3)
c.rotate(45)
c.drawString(100, 100, 'Черновик')
c.save()
# Наложение на существующий PDF
reader = PdfReader('original.pdf')
watermark = PdfReader('watermark.pdf').pages[0]
writer = PdfWriter()
for page in reader.pages:
page.merge_page(watermark)
writer.add_page(page)
with open('watermarked.pdf', 'wb') as f:
writer.write(f)
Результат: На каждой странице исходного документа появляется полупрозрачная надпись "Черновик" под углом 45 градусов.
Пример 6: Защита PDF паролем (pypdf)
from pypdf import PdfReader, PdfWriter
reader = PdfReader('document.pdf')
writer = PdfWriter()
for page in reader.pages:
writer.add_page(page)
writer.encrypt('user_password', 'owner_password')
with open('protected.pdf', 'wb') as f:
writer.write(f)
Результат: PDF-файл, открытие которого требует пароль пользователя, а владелец может изменять права.