Создание и изменение PDF файлов в Python

| Python

Создание и изменение PDF файлов в Python

На практике полезно знать и уметь, как создавать и изменять PDF файлы в Python. PDF (Portable Document Format) является одним из наиболее распространенных форматов для обмена документами через Интернет. В одном PDF файле может содержаться текст, изображения, таблицы, формы и мультимедийные материалы, такие как видео и анимация.

Такое обилие типов контента может затруднить работу с PDF-файлами. При открытии PDF файла необходимо декодировать множество различных типов данных! К счастью, в экосистеме Python есть несколько отличных пакетов для чтения, обработки и создания PDF файлов. В этом руководстве вы узнаете, как:

  • Читать текст из PDF
  • Разделить PDF на несколько файлов
  • Соединять и объединять PDF файлы
  • Поворачивать и обрезать страницы в PDF файле
  • Шифровать и расшифровывать PDF-файлы с паролями
  • Создать PDF-файл с нуля

Извлечение текста из PDF

Установка пакета PyPDF2

В этом разделе вы узнаете, как читать PDF-файл и извлекать текст с помощью пакета PyPDF2. Однако, прежде чем начать, нужно установить его с помощью pip:

$pip install PyPDF2

Проверьте установку, выполнив следующую команду в своём терминале:

$ pip show PyPDF2
Name: PyPDF2
Version: 1.26.0
Summary: PDF toolkit
Home-page: http://mstamy2.github.com/PyPDF2
Author: Mathieu Fenniak
Author-email: [email protected]
License: UNKNOWN
Location: d:\work\mytest\lib\site-packages
Requires:
Required-by:

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

Открытие PDF файла

Давайте начнём с открытия PDF-файла и ознакомления с информацией о нём. Воспользуемся тестовым файлом Pride-and-prejudice.pdf, который можно легко найти в гугле по поисковой фразе "jane austen pride and prejudice pdf".

Откройте интерактивное окно CPython и импортируйте класс PdfFileReader из пакета PyPDF2: from PyPDF2 import PdfFileReader Чтобы создать новый экземпляр класса PdfFileReader, вам понадобится путь к PDF файлу, который нужно открыть. Давайте теперь получим его с помощью модуля pathlib:

pdf_path = (
    Path().absolute()
    / 'pride-and-prejudice.pdf'
)

Переменная pdf_path теперь содержит путь к PDF-версии книги Джейн Остин «Гордость и предубеждение». В данном случае программа файл будет доступен по адресу C:\Users\User\pride-and-prejudice.pdf.

Примечание: вам может потребоваться изменить pdf_path, чтобы он соответствовал расположению папки с файлом на вашем компьютере.

Теперь создадим экземпляр PdfFileReader:

pdf = PdfFileReader(str(pdf_path))

Здесь выполняется преобразование pdf_path в строку, потому что PdfFileReader не умеет читать из объекта pathlib.Path.

Также отметим, что все открытые файлы должны быть закрыты до завершения работы программы. Всё это делает за вас объект PdfFileReader, поэтому вам не нужно беспокоиться об открытии или закрытии PDF файла!

Теперь, когда вы создали экземпляр PdfFileReader, вы можете использовать его для сбора информации о PDF. Например, .getNumPages() возвращает количество страниц, содержащихся в файле PDF:

pdf.getNumPages()
479

Обратите внимание, что .getNumPages() написан в mixedCase, а не в lower_case_with_underscores, как рекомендовано в PEP 8. Помните, что PEP 8 – это набор рекомендаций, а не правил. Что касается Python, то вариант mixedCase вполне приемлем.

Примечание. PyPDF2 был адаптирован из пакета pyPdf. pyPdf был написан в 2005 году, всего через четыре года после публикации PEP 8.

В то время многие программисты Python переходили с языков, в которых был более распространен смешанный регистр.

Вы также можете получить доступ к некоторой информации о документе, используя атрибут .documentInfo:

pdf.documentInfo
{'/CreationDate': "D:20080206215355+11'00'", '/Subject': 'Download classic literature as completely free eBooks from Planet eBook. ', '/Author': 'Jane Austen', '/Creator': 'Adobe InDesign CS2 (4.0)', '/Producer': 'Adobe PDF Library 7.0', '/ModDate': "D:20080706185934+10'00'", '/Title': 'Pride and Prejudice', '/Trapped': '/False'}

Объект, возвращаемый .documentInfo, выглядит как словарь, но на самом деле это не то же самое. Вы можете получить доступ к каждому элементу в .documentInfo как к атрибуту.

Например, чтобы получить заголовок, используйте атрибут .title:

pdf.documentInfo.title
'Pride and Prejudice'

Объект .documentInfo содержит метаданные PDF, которые задаются при создании PDF-файла.

Класс PdfFileReader предоставляет все необходимые методы и атрибуты, необходимые для доступа к данным в файле PDF. Давайте посмотрим, что ещё можно сделать с PDF файлом и как это сделать!

Извлечение текста из страницы

Страницы PDF представлены в PyPDF2 классом PageObject. Вы используете экземпляры PageObject для взаимодействия со страницами в PDF файле. Вам не нужно напрямую создавать собственные экземпляры PageObject. Вместо этого вы можете получить к ним доступ с помощью метода .getPage() объекта PdfFileReader.

Есть два шага для извлечения текста с одной страницы PDF:

  • Получить PageObject с помощью PdfFileReader.getPage().
  • Извлечь текст в виде строки с помощью метода .extractText() экземпляра PageObject.

pride-and-prejudice.pdf состоит из 479 страниц. Каждая страница имеет индекс от 0 до 478. Вы можете получить PageObject, представляющий конкретную страницу, передав индекс страницы в PdfFileReader.getPage():

first_page = pdf.getPage(0)

.getPage() возвращает PageObject:

type(first_page)
<class 'PyPDF2.pdf.PageObject'>

Вы можете извлечь текст страницы с помощью PageObject.extractText():

first_page.extractText()
'\\n \\nThe Project Gutenberg EBook of Pride and Prejudice, by Jane
Austen\\n \\n\\nThis eBook is for the use of anyone anywhere at no cost
and with\\n \\nalmost no restrictions whatsoever.  You may copy it,
give it away or\\n \\nre\\n-\\nuse it under the terms of the Project
Gutenberg License included\\n \\nwith this eBook or online at
www.gutenberg.org\\n \\n \\n \\nTitle: Pride and Prejudice\\n \\n
\\nAuthor: Jane Austen\\n \\n \\nRelease Date: August 26, 2008
[EBook #1342]\\n\\n[Last updated: August 11, 2011]\\n \\n \\nLanguage:
Eng\\nlish\\n \\n \\nCharacter set encoding: ASCII\\n \\n \\n***
START OF THIS PROJECT GUTENBERG EBOOK PRIDE AND PREJUDICE ***\\n \\n
\\n \\n \\n \\nProduced by Anonymous Volunteers, and David Widger\\n
\\n \\n \\n \\n \\n \\n \\nPRIDE AND PREJUDICE \\n \\n \\nBy Jane
Austen \\n \\n\\n \\n \\nContents\\n \\n'

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

У каждого объекта PdfFileReader есть атрибут .pages, который можно использовать для итерации по всем страницам PDF-файла по порядку.

Например, следующий цикл for печатает текст с каждой страницы PDF-файла «pride-and-prejudice.pdf»:

for page in pdf.pages:
    print(page.extractText())

Давайте объединим все, что вы узнали, и напишем программу, которая извлекает весь текст из файла pride-and-prejudice.pdf и сохраняет его в .txt файл.

Собираем всё вместе

Откройте новое интерпретатора Python и введите следующий код:

from pathlib import Path
from PyPDF2 import PdfFileReader


# Измените путь ниже на правильный путь для вашего компьютера.
pdf_path = (
    Path().absolute()
    / 'pride-and-prejudice.pdf'
)

# 1
pdf_reader = PdfFileReader(str(pdf_path))
output_file_path = Path.home() / "Pride_and_Prejudice.txt"

# 2
with output_file_path.open(mode="w") as output_file:
    # 3
    title = pdf_reader.documentInfo.title
    num_pages = pdf_reader.getNumPages()
    output_file.write(f"{title}\\nNumber of pages: {num_pages}\\n\\n")
    # 4
    for page in pdf_reader.pages:
        text = page.extractText()
     output_file.write(text)

Давайте разберемся с этим:

  1. Сначала создаётся новый экземпляр PdfFileReader переменной pdf_reader. Вы также создаёте новый объект Path, который указывает на файл Pride_and_Prejudice.txt в вашем домашнем каталоге, и назначаете его переменной output_file_path.

  2. Затем вы открываете output_file_path в режиме записи и назначаете объект файла, возвращаемый .open(), переменной output_file.

  3. Затем внутри блока with вы записываете заголовок PDF и количество страниц в текстовый файл с помощью output_file.write().

  4. Наконец, используется цикл for для перебора всех страниц в PDF файле. На каждом шаге цикла следующий PageObject присваивается переменной страницы. Текст с каждой страницы извлекается с помощью page.extractText() и записывается в output_file.

Когда вы сохраните и запустите программу, она создаст в вашем домашнем каталоге новый файл с именем Pride_and_Prejudice.txt, содержащий полный текст документа Pride-and-prejudice.pdf. Откройте и проверьте!

Извлечение страниц из PDF

В предыдущем разделе вы узнали, как извлечь весь текст из файла PDF и сохранить его в txt файл. Теперь вы узнаете, как извлечь страницу или диапазон страниц из существующего PDF-файла и сохранить их в новом PDF-файле.

Вы можете использовать PdfFileWriter для создания нового PDF файла. Давайте изучим этот класс и узнаем, как создать PDF-файл с помощью PyPDF2.

Использование класса PdfFileWriter

Класс PdfFileWriter создает новые PDF файлы. В интерактивном окне Python импортируйте класс PdfFileWriter и создайте новый экземпляр с именем pdf_writer:

from PyPDF2 import PdfFileWriter
pdf_writer = PdfFileWriter()

Объекты PdfFileWriter похожи на пустые PDF файлы. Вам нужно добавить к ним несколько страниц, прежде чем вы сможете сохранить их в файл.

Идем дальше и добавляем пустую страницу в pdf_writer:

page = pdf_writer.addBlankPage(width=72, height=72)

Параметры ширины и высоты являются обязательными и определяют размеры страницы в единицах, называемых точками. Одна точка равна 1/72 дюйма, поэтому приведенный выше код добавляет пустую страницу размером в один дюйм в pdf_writer.

.addBlankPage() возвращает новый экземпляр PageObject, представляющий страницу, которую вы добавили:

type(page)
<class 'PyPDF2.pdf.PageObject'>

В этом примере вы назначили экземпляр PageObject, возвращаемый .addBlankPage(), переменной страницы, но на практике обычно этого делать не нужно. То есть вы обычно вызываете .addBlankPage(), не присваивая возвращаемое значение чему-либо:

pdf_writer.addBlankPage(width=72, height=72)

Чтобы записать содержимое pdf_writer в PDF файл, передайте объект файла в режиме двоичной записи в pdf_writer.write():

from pathlib import Path
with Path("blank.pdf").open(mode="wb") as output_file:
    pdf_writer.write(output_file)

Этот код создаёт новый файл в текущем рабочем каталоге с именем blank.pdf. Если вы откроете файл с помощью программы для чтения PDF, например Adobe Acrobat, вы увидите документ с одной пустой страницей размером в один дюйм. Технические детали: обратите внимание, что вы сохраняете PDF файл, передавая объект файла в метод .write() объекта PdfFileWriter, а не в метод .write() объекта файла.

В частности, не будет работать следующий код:

with Path("blank.pdf").open(mode="wb") as output_file:
    output_file.write(pdf_writer)

Многим начинающим программистам этот подход кажется обратным, поэтому постарайтесь избежать этой ошибки!

Объекты PdfFileWriter могут записывать в новые PDF файлы, но они не могут создавать новое содержимое с нуля, кроме пустых страниц.

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

Примечание. Вы узнаете, как создавать PDF файлы с нуля далее, в разделе «Создание файла PDF с нуля».

В примере, который вы видели выше, было три шага для создания нового PDF-файла с использованием PyPDF2:

  1. Создание экземпляра PdfFileWriter.
  2. Добавили одну или несколько страниц в экземпляр PdfFileWriter.
  3. Записали в файл с помощью PdfFileWriter.write().

Вы будете видеть этот шаблон снова и снова, изучая различные способы добавления страниц в экземпляр PdfFileWriter.

Извлечение одной страницы из PDF

Давайте ещё раз вернёмся к PDF-файлу «pride-and-prejudice.pdf», с которым вы работали в предыдущем разделе. Откроем PDF-файл, извлечём первую страницу и создадим новый PDF-файл, содержащий только одну извлечённую страницу.

Откройте интерактивное окно Python и импортируйте PdfFileReader и PdfFileWriter из PyPDF2, а также класс Path из модуля pathlib:

from pathlib import Path
from PyPDF2 import PdfFileReader, PdfFileWriter
Теперь откройте файл Pride-and-prejudice.pdf с экземпляром PdfFileReader:
# Измените путь ниже на правильный путь для вашего компьютера.
pdf_path = (
    Path().absolute()
    / 'pride-and-prejudice.pdf'
) 
input_pdf = PdfFileReader(str(pdf_path))

Передайте индекс 0 в .getPage(), чтобы получить PageObject, представляющий первую PDF страницу: first_page = input_pdf.getPage(0) Теперь создайте новый экземпляр PdfFileWriter и добавьте к нему first_page с помощью .addPage():

pdf_writer = PdfFileWriter()
pdf_writer.addPage(first_page)

Метод .addPage() добавляет страницу к набору страниц в объекте pdf_writer, как и .addBlankPage(). Разница в том, что для этого требуется существующий PageObject.

Теперь запишем содержимое pdf_writer в новый файл:

with Path("first_page.pdf").open(mode="wb") as output_file:
    pdf_writer.write(output_file)

Теперь у вас есть новый PDF-файл, сохраненный в вашем текущем рабочем каталоге, с именем first_page.pdf, который содержит титульную страницу файла Pride-and-prejudice.pdf. Довольно аккуратно!

Извлечение нескольких страниц из PDF

Давайте извлечём первую главу из Pride-and-prejudice.pdf и сохраним её в новом PDF-файле.

Если вы откроете Pride-and-prejudice.pdf с помощью средства просмотра PDF, вы увидите, что первая глава находится на второй, третьей и четвертой страницах PDF. Поскольку страницы индексируются, начиная с 0, вам нужно будет извлечь страницы с индексами 1, 2 и 3.

Вы можете настроить все, импортировав нужные вам классы и открыв PDF файл:

from PyPDF2 import PdfFileReader, PdfFileWriter
from pathlib import Path
pdf_path = (
    Path().absolute()
    / 'pride-and-prejudice.pdf'
) 
input_pdf = PdfFileReader(str(pdf_path))

Ваша цель - извлечь страницы с индексами 1, 2 и 3, добавить их в новый экземпляр PdfFileWriter, а затем записать их в новый PDF файл.

Один из способов сделать это - перебрать диапазон чисел, начиная с 1 и заканчивая 3, извлекая страницу на каждом шаге цикла и добавляя ее в экземпляр PdfFileWriter:

   pdf_writer = PdfFileWriter()
   for n in range(1, 4):
       page = input_pdf.getPage(n)
       pdf_writer.addPage(page)

Цикл перебирает числа 1, 2 и 3, поскольку диапазон (1, 4) не включает правую конечную точку. На каждом шаге цикла страница с текущим индексом извлекается с помощью .getPage() и добавляется в pdf_writer с помощью .addPage().

Теперь у pdf_writer есть три страницы, которые вы можете проверить с помощью .getNumPages():

pdf_writer.getNumPages()
3

Наконец, вы можете записать извлеченные страницы в новый PDF файл:

with Path("chapter1.pdf").open(mode="wb") as output_file:
    pdf_writer.write(output_file)

Теперь вы можете открыть файл chapter1.pdf в вашем текущем рабочем каталоге, чтобы прочитать только первую главу «Гордости и предубеждения».

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

Начните с инициализации нового объекта PdfFileWriter:

pdf_writer = PdfFileWriter()

#Теперь переберём фрагмент .pages из индексов, начиная с 1 и заканчивая 4:
for page in input_pdf.pages[1:4]:
    pdf_writer.addPage(page)

Помните, что значения в срезе варьируются от элемента с первым индексом в срезе до, но не включая, элемента со вторым индексом в срезе. Итак .pages [1: 4] возвращает итерацию, содержащую страницы с индексами 1, 2 и 3.

Наконец, запишем содержимое pdf_writer в выходной файл:

with Path("chapter1_slice.pdf").open(mode="wb") as output_file:
    pdf_writer.write(output_file)

Теперь откройте файл chapter1_slice.pdf в вашем текущем рабочем каталоге и сравните его с файлом chapter1.pdf, созданным путем обхода объекта диапазона. Они содержат одинаковые страницы!

Иногда вам нужно извлечь каждую страницу из PDF. Для этого вы можете использовать методы, показанные выше, но PyPDF2 предоставляет шорткат (ярлык). Экземпляры PdfFileWriter содержат метод .appendPagesFromReader(), который можно использовать для добавления страниц из экземпляра PdfFileReader.

Чтобы использовать .appendPagesFromReader(), передайте экземпляр PdfFileReader параметру reader метода. Например, следующий код копирует каждую страницу из PDF-файла «Pride and Prejudice» в экземпляр PdfFileWriter:

pdf_writer = PdfFileWriter()
pdf_writer.appendPagesFromReader(pdf_reader)

pdf_writer теперь содержит каждую страницу в pdf_reader!

Слияние и объединение PDF-файлов

Две распространенные задачи при работе с файлами PDF - это объединение нескольких PDF-файлов в один файл.

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

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

В этом разделе вы узнаете, как делать слияние и объединение PDF-файлов с помощью PdfFileMerger пакета PyPDF2.

Использование класса PdfFileMerger

Класс PdfFileMerger очень похож на класс PdfFileWriter, о котором вы узнали в предыдущем разделе. Вы можете использовать оба класса для записи PDF файлов. В обоих случаях вы добавляете страницы к экземплярам класса, а затем записываете их в файл.

Основное различие между ними заключается в том, что PdfFileWriter может только добавлять или объединять страницы в конец списка страниц, уже содержащихся в модуле записи, тогда как PdfFileMerger может вставлять или объединять страницы в любом месте.

В интерактивном окне Python введите следующий код, чтобы импортировать класс PdfFileMerger и создать новый экземпляр:

from PyPDF2 import PdfFileMerger
pdf_merger = PdfFileMerger()

Объекты PdfFileMerger пусты при первом создании экземпляра. Вам нужно будет добавить несколько страниц к вашему объекту, прежде чем вы сможете что-либо с ним делать.

Есть несколько способов добавить страницы к объекту pdf_merger, и какой из них вы используете, зависит от того, что вам нужно сделать:

  • .append() объединяет каждую страницу в существующем документе PDF с концом страниц, находящихся в настоящее время в PdfFileMerger.
  • .merge() вставляет все страницы в существующий PDF документ после определенной страницы в PdfFileMerger.

В этом разделе вы увидите оба метода, начиная с .append().

Объединение PDF-файлов с помощью .append()

Для дальнейшей работы необходимо подготовить 3 каких-либо PDF файла.

Вы можете начать с использования модуля pathlib, чтобы получить список объектов Path для каждого из трёх файлов в каталоге pdfs домашней папки:

from pathlib import Path
reports_dir = (
    Path.home()
    / "pdfs"
    )

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

Если у вас есть путь к каталогу cost_reports/, назначенному переменной reports_dir, вы можете использовать .glob() для получения итеративного пути к файлам PDF в каталоге.

Посмотрите, что находится в каталоге:

for path in reports_dir.glob("*.pdf"):
    print(path.name)

Expense report 1.pdf
Expense report 3.pdf
Expense report 2.pdf

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

Как правило, порядок путей, возвращаемых функцией .glob(), не гарантируется, поэтому вам придется упорядочивать их самостоятельно. Вы можете сделать это, создав список, содержащий три пути к файлам, а затем вызвав .sort() в этом списке:

expense_reports = list(reports_dir.glob("*.pdf"))
expense_reports.sort()

Помните, что .sort() сортирует список на месте, поэтому вам не нужно присваивать возвращаемое значение переменной. Список файлов будет отсортирован в алфавитном порядке по имени файла после вызова .list().

Чтобы убедиться, что сортировка сработала, еще раз переберите файлы и распечатайте имена файлов:

for path in expense_reports:
     print(path.name)

Expense report 1.pdf
Expense report 2.pdf
Expense report 3.pdf
Выглядит хорошо!

Теперь вы можете объединить три PDF-файла. Для этого воспользуемся функцией PdfFileMerger.append(), которая требует единственный строковый аргумент, представляющий путь к PDF файлу. Когда вы вызываете .append(), все страницы в файле PDF добавляются к набору страниц в объекте PdfFileMerger.

Давайте посмотрим на это в действии. Сначала импортируем класс PdfFileMerger и создадим новый экземпляр:

from PyPDF2 import PdfFileMerger
pdf_merger = PdfFileMerger()

Теперь переберём пути в отсортированном списке файлов и добавим их в pdf_merger:

for path in expense_reports:
    pdf_merger.append(str(path))

Обратите внимание, что каждый объект Path в каталоге pdfs/ преобразуется в строку с помощью str() перед передачей в pdf_merger.append().

Когда все PDF файлы в каталоге pdfs/ объединены в объект pdf_merger, последнее, что вам нужно сделать, это записать всё в выходной PDF файл. У экземпляров PdfFileMerger есть метод .write(), который работает так же, как PdfFileWriter.write().

Откройте новый файл в режиме двоичной записи, затем передайте объект файла методу pdf_merge.write():

with Path("expense_reports.pdf").open(mode="wb") as output_file:
    pdf_merger.write(output_file)

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

Объединение PDF-файлов с помощью .merge()

Чтобы объединить два или более PDF-файлов, используйте PdfFileMerger.merge(). Этот метод аналогичен .append(), за исключением того, что вы должны указать, где в выходном PDF-файле вставить все содержимое из PDF-файла, который вы объединяете. Для следующего кода понадобится 2а PDF файла, для примера взяты report.pdf и toc.pdf. В интерактивном окне Python импортируйте класс PdfFileMerger и создайте объекты Path для файлов report.pdf и toc.pdf:

from pathlib import Path
from PyPDF2 import PdfFileMerger
report_dir = (
    Path.home()
    / " pdfs"
    )
report_path = report_dir / "report.pdf"
toc_path = report_dir / "toc.pdf"

Первое, что вам нужно сделать, это добавить PDF-файл отчета к новому экземпляру PdfFileMerger с помощью .append():

pdf_merger = PdfFileMerger()
pdf_merger.append(str(report_path))

Теперь, когда в pdf_merger есть несколько страниц, вы можете объединить PDF-файл с оглавлением в правильном месте. Если вы откроете файл report.pdf с помощью программы для чтения PDF-файлов, то увидите, что первая страница отчета является титульной. Второй - введение, а остальные страницы содержат разные разделы отчёта.

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

Для этого вызовите pdf_merger.merge() с двумя аргументами:

  1. Целое число 1, указывающее индекс страницы, на которую должно быть вставлено оглавление.
  2. Строка, содержащая путь к PDF файлу для оглавления.

Вот как это выглядит:

pdf_merger.merge(1, str(toc_path))

Каждая страница в оглавлении PDF вставляется перед страницей с индексом 1. Поскольку оглавление PDF - это только одна страница, она вставляется в индекс 1. Страница, текущая в индексе 1, затем перемещается в индекс 2. Страница в настоящее время индекс 2 смещается в индекс 3 и так далее.

Теперь напишите объединенный PDF-файл в выходной файл:

with Path("full_report.pdf").open(mode="wb") as output_file:
    pdf_merger.write(output_file)

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

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

Поворот и обрезка PDF-страниц

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

В этом разделе вы узнаете, как поворачивать и обрезать страницы в файле PDF.

Поворот страниц

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

В новом интерактивном окне Python начнём с импорта классов PdfFileReader и PdfFileWriter из PyPDF2, а также класса Path из модуля pathlib:

from pathlib import Path
from PyPDF2 import PdfFileReader, PdfFileWriter

Теперь создадим объект Path для файла ugly.pdf:

pdf_path = (
    Path.home()
    / "ugly.pdf"
)

Наконец, создадим новые экземпляры PdfFileReader и PdfFileWriter:

pdf_reader = PdfFileReader(str(pdf_path))
pdf_writer = PdfFileWriter()

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

Чтобы решить эту задачу, воспользуемся PageObject.rotateClockwise(). Этот метод принимает целочисленный аргумент в градусах и поворачивает страницу по часовой стрелке на это количество градусов. Например, .rotateClockwise(90) поворачивает страницу PDF по часовой стрелке на девяносто градусов.

Примечание. В дополнение к .rotateClockwise() класс PageObject также содержит .rotateCounterClockwise() для поворота страниц против часовой стрелки.

Есть несколько способов поворота страниц в PDF. Мы обсудим два разных способа сделать это. Оба они основываются на .rotateClockwise(), но используют разные подходы для определения того, какие страницы нужно повернуть.

Первый метод - перебрать индексы страниц в PDF и проверить, соответствует ли каждый индекс странице, которую нужно повернуть. Если это так, то вызывается .rotateClockwise(), чтобы повернуть страницу, а затем добавление её в pdf_writer.

Вот как это выглядит:

for n in range(pdf_reader.getNumPages()):
    page = pdf_reader.getPage(n)
    if n % 2 == 0:
        page.rotateClockwise(90)
    pdf_writer.addPage(page)

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

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

Примечание. При выполнении цикла for, описанного выше, вы увидите множество результатов в интерактивном окне Python. Это потому, что .rotateClockwise() возвращает экземпляр PageObject.

Вы можете пока игнорировать этот вывод. Когда вы запускаете программы из окна редактора Python, этот вывод не будет виден.

Теперь, когда вы перевернули все страницы в PDF, вы можете записать содержимое pdf_writer в новый файл и проверить, всё ли работает:

with Path("ugly_rotated.pdf").open(mode="wb") as output_file:
    pdf_writer.write(output_file)

Теперь в вашем текущем рабочем каталоге должен быть файл с именем ugly_rotated.pdf, в котором некоторые страницы из файла ugly.pdf повернуты.

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

Фактически, вы можете определить, какие страницы нужно повернуть, не зная заранее. Что ж, иногда можно.

Давайте посмотрим, как это сделать, начиная с нового экземпляра PdfFileReader:

pdf_reader = PdfFileReader(str(pdf_path))

Вам нужно сделать это, потому что вы изменили страницы в старом экземпляре PdfFileReader, повернув их. Итак, создавая новый экземпляр, вы начинаете все сначала.

Экземпляры PageObject поддерживают словарь значений, содержащий информацию о странице:

pdf_reader.getPage(0)
{'/Contents': [IndirectObject(11, 0), IndirectObject(12, 0),
IndirectObject(13, 0), IndirectObject(14, 0), IndirectObject(15, 0),
IndirectObject(16, 0), IndirectObject(17, 0), IndirectObject(18, 0)],
'/Rotate': -90, '/Resources': {'/ColorSpace': {'/CS1':
IndirectObject(19, 0), '/CS0': IndirectObject(19, 0)}, '/XObject':
{'/Im0': IndirectObject(21, 0)}, '/Font': {'/TT1':
IndirectObject(23, 0), '/TT0': IndirectObject(25, 0)}, '/ExtGState':
{'/GS0': IndirectObject(27, 0)}}, '/CropBox': [0, 0, 612, 792],
'/Parent': IndirectObject(1, 0), '/MediaBox': [0, 0, 612, 792],
'/Type': '/Page', '/StructParents': 0}

Ой! К этому бессмысленному внешнему виду примешивается ключ /Rotate, котором вы можете увидеть в четвертой строке вывода выше. Значение этого ключа -90.

Вы можете получить доступ к ключу /Rotate в PageObject, используя нотацию нижнего индекса, так же, как вы можете для объекта dict Python:

page = pdf_reader.getPage(0)
page["/Rotate"]
-90

Если вы посмотрите на ключ /Rotate для второй страницы в pdf_reader, вы увидите, что её значение 0:

page = pdf_reader.getPage(1)
page["/Rotate"]
0

Все это означает, что страница с индексом 0 имеет значение поворота -90 градусов. Другими словами, она повернута против часовой стрелки на девяносто градусов. Страница с индексом 1 имеет значение поворота 0, поэтому она вообще не была повернута.

Если вы поверните первую страницу с помощью .rotateClockwise(), значение /Rotate изменится с -90 на 0:

page = pdf_reader.getPage(0)
page["/Rotate"]
-90
page.rotateClockwise(90)
page["/Rotate"]
0

Теперь, когда вы знаете, как проверять ключ /Rotate, вы можете использовать его для поворота страниц в файле ugly.pdf.

Первое, что вам нужно сделать, это повторно инициализировать ваши объекты pdf_reader и pdf_writer, чтобы вы могли начать все сначала:

pdf_reader = PdfFileReader(str(pdf_path))
pdf_writer = PdfFileWriter()

Теперь напишем цикл, который перебирает страницы в итерации pdf_reader.pages, проверяет значение /Rotate и поворачивает страницу, если это значение равно -90:

for page in pdf_reader.pages:
    if page["/Rotate"] == -90:
        page.rotateClockwise(90)
    pdf_writer.addPage(page)

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

Чтобы завершить решение, запишите содержимое pdf_writer в новый файл:

with Path("ugly_rotated2.pdf").open(mode="wb") as output_file:
    pdf_writer.write(output_file)

Теперь вы можете открыть файл ugly_rotated2.pdf в вашем текущем рабочем каталоге и сравнить его с файлом ugly_rotated.pdf, который вы создали ранее. Они должны выглядеть одинаково.

Примечание. Одно предупреждение о ключе /Rotate – его наличие на странице не гарантируется.

Если ключ /Rotate не существует, это обычно означает, что страница не была повернута. Однако это не всегда надежное предположение.

Если у PageObject нет ключа /Rotate, то при попытке доступа к нему будет вызвана ошибка KeyError. Вы можете поймать это исключение с помощью блока try ... except.

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

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

Обрезка страниц

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

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

Для начала импортируем классы PdfFileReader и PdfFileWriter из PyPDF2 и класс Path из модуля pathlib:

from pathlib import Path
from PyPDF2 import PdfFileReader, PdfFileWriter

Теперь создайте объект Path для файла half_and_half.pdf:

pdf_path = (
    Path.home()
    / "half_and_half.pdf"
)

Затем создадим новый объект PdfFileReader и получим первую страницу PDF:

pdf_reader = PdfFileReader(str(pdf_path))
first_page = pdf_reader.getPage(0)

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

Вы можете использовать интерактивное окно Python, чтобы изучить .mediaBox, прежде чем использовать его для обрезки страницы:

first_page.mediaBox
RectangleObject([0, 0, 792, 612])

Атрибут .mediaBox возвращает объект RectangleObject. Этот объект определен в пакете PyPDF2 и представляет собой прямоугольную область на странице.

Список [0, 0, 792, 612] в выходных данных определяет прямоугольную область. Первые два числа - это координаты x и y левого нижнего угла прямоугольника. Третье и четвертое числа представляют ширину и высоту прямоугольника соответственно. Единицами измерения всех значений являются точки, которые равны 1/72 дюйма.

RectangleObject ([0, 0, 792, 612]) представляет прямоугольную область с нижним левым углом в начале координат, шириной 792 точки или 11 дюймов и высотой 612 точек или 8,5 дюймов. Это размеры стандартной страницы формата Letter в альбомной ориентации, которая используется для примера PDF-файла. Страница PDF размером с букву в книжной ориентации вернет выходной объект RectangleObject ([0, 0, 612, 792]).

У RectangleObject есть четыре атрибута, которые возвращают координаты углов прямоугольника: .lowerLeft, .lowerRight, .upperLeft и .upperRight. Как и значения ширины и высоты, эти координаты даются в точках.

Вы можете использовать эти четыре свойства, чтобы получить координаты каждого угла RectangleObject:

first_page.mediaBox.lowerLeft
(0, 0)
first_page.mediaBox.lowerRight
(792, 0)
first_page.mediaBox.upperLeft
(0, 612)
first_page.mediaBox.upperRight
(792, 612)

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

first_page.mediaBox.upperRight[0]
792
first_page.mediaBox.upperRight[1]
612

Вы можете изменить координаты mediaBox, назначив новый кортеж одному из его свойств:

first_page.mediaBox.upperLeft = (0, 480)
first_page.mediaBox.upperLeft
(0, 480)

Когда вы изменяете координаты .upper Left, атрибут .upper Right автоматически настраивается, чтобы сохранить прямоугольную форму:

first_page.mediaBox.upperRight
(792, 480)

Когда вы изменяете координаты RectangleObject, возвращаемого .mediaBox, вы фактически обрезаете страницу. Теперь объект first_page содержит только ту информацию, которая присутствует в границах нового RectangleObject.

Далее, запишем обрезанную страницу в новый PDF файл:

pdf_writer = PdfFileWriter()
pdf_writer.addPage(first_page)
with Path("cropped_page.pdf").open(mode="wb") as output_file:
    pdf_writer.write(output_file)

Если вы откроете файл cropped_page.pdf в текущем рабочем каталоге, вы увидите, что верхняя часть страницы была удалена.

Как бы вы обрезали страницу так, чтобы был виден только текст в левой части страницы? Вам нужно будет сократить горизонтальные размеры страницы вдвое. Вы можете добиться этого, изменив координаты .upperRight объекта .mediaBox. Посмотрим, как это работает.

Во-первых, вам нужно получить новые объекты PdfFileReader и PdfFileWriter, поскольку вы только что изменили первую страницу в pdf_reader и добавили её в pdf_writer:

pdf_reader = PdfFileReader(str(pdf_path))
pdf_writer = PdfFileWriter()

Теперь получим первую страницу PDF:

first_page = pdf_reader.getPage(0)

На этот раз поработаем с копией первой страницы, чтобы только что извлеченная страница осталась нетронутой. Вы можете сделать это, импортировав модуль copy из стандартной библиотеки Python и используя deepcopy() для создания копии страницы:

import copy
left_side = copy.deepcopy(first_page)

Теперь вы можете изменить left_side без изменения свойств first_page. Таким образом, вы можете позже использовать first_page для извлечения текста с правой стороны страницы.

Теперь вам нужно заняться математикой. Уже выяснили, что вам нужно переместить верхний правый угол .mediaBox в верхний центр страницы. Для этого создадим новый кортеж с первым компонентом, равным половине исходного значения, и назначите его свойству .upperRight.

Сначала получим текущие координаты правого верхнего угла .mediaBox.

current_coords = left_side.mediaBox.upperRight

Затем создадим новый кортеж, первая координата которого равна половине значения текущей координаты, а вторая координата такая же, как у оригинала:

new_coords = (current_coords[0] / 2, current_coords[1])

Наконец, назначим новые координаты свойству .upperRight:

left_side.mediaBox.upperRight = new_coords

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

Сначала получим новую копию first_page:

right_side = copy.deepcopy(first_page)

Переместим левый верхний угол вместо правого верхнего угла:

right_side.mediaBox.upperLeft = new_coords

Это устанавливает для верхнего левого угла те же координаты, в которые вы переместили правый верхний угол при извлечении левой части страницы. Итак, right_side.mediaBox теперь представляет собой прямоугольник, левый верхний угол которого находится в центре верхней части страницы, а правый верхний угол - в правом верхнем углу страницы.

Наконец, добавим страницы left_side и right_side в pdf_writer и запишем их в новый файл PDF:

pdf_writer.addPage(left_side)
pdf_writer.addPage(right_side)
with Path("cropped_pages.pdf").open(mode="wb") as output_file:
    pdf_writer.write(output_file)

Теперь откроем файл cropped_pages.pdf в программе для чтения PDF-файлов. Вы должны увидеть файл с двумя страницами, первая из которых содержит текст с левой стороны исходной первой страницы, а вторая – с текстом с исходной правой стороны.

Шифрование и дешифрование PDF-файлов

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

Шифрование PDF-файлов

Вы можете добавить защиту паролем к PDF-файлу, используя метод .encrypt() экземпляра PdfFileWriter(). У него два основных параметра:

  • user_pwd устанавливает пароль пользователя. Это позволяет открывать и читать PDF-файл.
  • owner_pwd устанавливает пароль владельца. Это позволяет открывать PDF без каких-либо ограничений, включая редактирование.

Давайте воспользуемся .encrypt(), чтобы добавить пароль к PDF файлу. Сначала откроем тестовый файл newsletter.pdf в домашнем каталоге:

from pathlib import Path
from PyPDF2 import PdfFileReader, PdfFileWriter
pdf_path = (
    Path.home()
    / "newsletter.pdf"
)
pdf_reader = PdfFileReader(str(pdf_path))

Теперь создадим новый экземпляр PdfFileWriter и добавим к нему страницы из pdf_reader:

pdf_writer = PdfFileWriter()
pdf_writer.appendPagesFromReader(pdf_reader)

Затем добавим пароль «SuperSecret» с помощью pdf_writer.encrypt():

pdf_writer.encrypt(user_pwd="SuperSecret")

Когда вы устанавливаете только user_pwd, аргумент owner_pwd по умолчанию равен той же строке. Итак, в приведенной выше строке кода устанавливаются пароли пользователя и владельца.

Наконец, запишите зашифрованный PDF-файл в выходной файл в вашем домашнем каталоге с именем newsletter_protected.pdf:

output_path = Path.home() / "newsletter_protected.pdf"
with output_path.open(mode="wb") as output_file:
    pdf_writer.write(output_file)

Когда вы откроете PDF-файл с помощью PDF-ридера, вам будет предложено ввести пароль. Введите «SuperSecret», чтобы открыть PDF.

Если вам нужно установить отдельный пароль владельца для PDF, то передайте вторую строку в параметр owner_pwd:

user_pwd = "SuperSecret"
owner_pwd = "ReallySuperSecret"
pdf_writer.encrypt(user_pwd=user_pwd, owner_pwd=owner_pwd)

В этом примере пароль пользователя - «SuperSecret», а пароль владельца - «ReallySuperSecret».

Когда вы шифруете PDF-файл паролем и пытаетесь его открыть, вы должны указать пароль, прежде чем сможете просматривать его содержимое. Эта защита распространяется на чтение из PDF в программе Python. Затем давайте посмотрим, как расшифровать PDF файлы с помощью PyPDF2.

Расшифровка PDF-файлов

Чтобы расшифровать зашифрованный PDF файл, используйте метод .decrypt() экземпляра PdfFileReader.

У .decrypt() есть единственный параметр, называемый паролем, который вы можете использовать для предоставления пароля для расшифровки. Права, которые у вас есть при открытии PDF-файла, зависят от аргумента, который вы передали параметру пароля.

Давайте откроем зашифрованный файл newsletter_protected.pdf, который вы создали в предыдущем разделе, и воспользуемся PyPDF2 для его расшифровки.

Сначала создадим новый экземпляр PdfFileReader с путем к защищенному PDF:

from pathlib import Path
from PyPDF2 import PdfFileReader, PdfFileWriter
pdf_path = Path.home() / "newsletter_protected.pdf"
pdf_reader = PdfFileReader(str(pdf_path))

Прежде чем расшифровать PDF-файл, проверьте, что произойдет, если вы попытаетесь получить первую страницу:

pdf_reader.getPage(0)
Traceback (most recent call last):

Возникает исключение PdfReadError, информирующее вас о том, что PDF файл не был расшифрован.

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

Теперь расшифруем файл:

pdf_reader.decrypt(password="SuperSecret")
1

.decrypt() возвращает целое число, представляющее успех расшифровки:

  • 0 означает, что пароль неверный.
  • 1 означает, что пароль пользователя был найден.
  • 2 означает, что пароль владельца был найден.

После расшифровки файла вы можете получить доступ к его содержимому:

pdf_reader.getPage(0)
{'/Contents': IndirectObject(7, 0), '/CropBox': [0, 0, 612, 792],
'/MediaBox': [0, 0, 612, 792], '/Parent': IndirectObject(1, 0),
'/Resources': IndirectObject(8, 0), '/Rotate': 0, '/Type': '/Page'}

Теперь вы можете извлекать текст и обрезать или поворачивать страницы по своему усмотрению!

Создание файла PDF с нуля

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

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

Примечание. Этот раздел не является исчерпывающим введением в ReportLab, а скорее является примером того, что может эта библиотека.

Дополнительные примеры см. на странице фрагментов кода ReportLab.

Установка reportlab

Для начала вам нужно установить reportlab с помощью pip:

pip install reportlab

Вы можете проверить установку с помощью pip show:

pip show reportlab
Name: reportlab
Version: 3.5.34
Summary: The Reportlab Toolkit
Home-page: http://www.reportlab.com/
Author: Andy Robinson, Robin Becker, the ReportLab team
        and the community
Author-email: [email protected]
License: BSD license (see license.txt for details),
         Copyright (c) 2000-2018, ReportLab Inc.
Location: c:\users\davea\venv\lib\site-packages
Requires: pillow
Required-by:

На момент написания последней версии reportlab была 3.5.34. Если у вас открыт Python, вам необходимо перезапустить его, прежде чем вы сможете использовать пакет reportlab.

Использование класса Canvas

Основным интерфейсом для создания PDF-файлов с помощью reportlab является класс Canvas, расположенный в модуле reportlab.pdfgen.canvas.

Откройте новое интерактивное окно Python и введите следующее, чтобы импортировать класс Canvas:

from reportlab.pdfgen.canvas import Canvas

Когда вы создаете новый экземпляр Canvas, вам необходимо предоставить строку с именем файла PDF, который вы создаете. Идем дальше и создаем новый экземпляр Canvas для файла hello.pdf:

canvas = Canvas("hello.pdf")

Теперь у вас есть экземпляр Canvas, который вы назначили переменной с именем Canvas и который связан с файлом hello.pdf в вашем текущем рабочем каталоге. Однако файла hello.pdf ещё не существует.

Давайте добавим текст в PDF. Для этого используем .drawString():

canvas.drawString(72, 72, "Hello, World")

Первые два аргумента, передаваемые в .drawString(), определяют место на холсте, где написан текст. Первый указывает расстояние от левого края холста, а второй указывает расстояние от нижнего края.

Значения, передаваемые в .drawString(), измеряются в пунктах. Поскольку точка равна 1/72 дюйма, .drawString (72, 72, «Hello, World») рисует строку «Hello, World» на один дюйм слева и на один дюйм снизу страницы.

Чтобы сохранить PDF-файл в файл, используйте .save():

canvas.save()

Теперь у вас есть PDF-файл в вашем текущем рабочем каталоге под названием hello.pdf. Вы можете открыть его с помощью программы для чтения PDF-файлов и увидеть текст Hello, World внизу страницы.

В отношении только что созданного PDF-файла следует обратить внимание на несколько моментов:

  • Размер страницы по умолчанию - A4, что не совпадает со стандартным размером страницы Letter в США.
  • По умолчанию используется шрифт Helvetica с размером шрифта 12 пунктов.

Не зацикливаетесь на этих настройках.

Установка размера страницы

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

Например, чтобы установить размер страницы 8,5 дюймов в ширину и 11 дюймов в высоту, вы должны создать следующий холст:

canvas = Canvas("hello.pdf", pagesize=(612.0, 792.0))

(612, 792) представляет бумагу размера Letter, потому что 8,5 умноженное на 72 равно 612, а 11 умноженное на 72 - 792.

Если вычисления для преобразования точек в дюймы или сантиметры - не ваше дело, вы можете использовать модуль reportlab.lib.units, который поможет вам с преобразованиями. Модуль .units содержит несколько вспомогательных объектов, таких как дюйм и см, которые упрощают преобразование.

Идем дальше и импортируем объекты в дюймах и сантиметрах из модуля reportlab.lib.units:

from reportlab.lib.units import inch, cm

Теперь вы можете осмотреть каждый объект, чтобы увидеть, что они собой представляют:

cm
28.346456692913385
inch
72.0

И см, и дюйм являются значениями с плавающей запятой. Они представляют количество точек, содержащихся в каждом блоке. дюйм равен 72,0 балла, а см - 28,346456692913385 точек.

Чтобы использовать единицы измерения, умножьте название единицы на количество единиц, которые вы хотите преобразовать в точки. Например, вот как с помощью дюйма установить размер страницы 8,5 дюймов в ширину и 11 дюймов в высоту:

canvas = Canvas("hello.pdf", pagesize=(8.5 * inch, 11 * inch))

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

Размеры страниц находятся в модуле reportlab.lib.pagesizes. Например, чтобы установить размер страницы как letter, вы можете импортировать объект LETTER из модуля pagesizes и передать его параметру pagesize при создании экземпляра Canvas:

from reportlab.lib.pagesizes import LETTER
canvas = Canvas("hello.pdf", pagesize=LETTER)

Если вы изучите объект LETTER, то увидите, что это кортеж с плавающей запятой:

LETTER
(612.0, 792.0)

Модуль reportlab.lib.pagesize содержит множество стандартных размеров страниц. Вот несколько с указанием размеров:

Размер страницы Разрешение
A4 210 mm x 297 mm
LETTER 8.5 in x 11 in
LEGAL 8.5 in x 14 in
TABLOID 11 in x 17 in

В дополнение к этому, модуль содержит определения для всех стандартных форматов бумаги ISO 216.

Настройка свойств шрифта

Вы также можете изменить шрифт, размер и цвет шрифта при написании текста на холсте.

Чтобы изменить шрифт и размер шрифта, вы можете использовать .setFont(). Сначала создайте новый экземпляр Canvas с именем файла font-example.pdf и размером страницы Letter:

canvas = Canvas("font-example.pdf", pagesize=LETTER)

Затем установите шрифт Times New Roman размером 18 пунктов:

canvas.setFont("Times-Roman", 18)

Наконец, напишите на холсте строку «Times New Roman (18 pt)» и сохраните ее:

canvas.drawString(1 * inch, 10 * inch, "Times New Roman (18 pt)")
canvas.save()

С этими настройками текст будет написан на расстоянии одного дюйма от левой стороны страницы и десяти дюймов снизу. Откройте файл font-example.pdf в вашем текущем рабочем каталоге и проверьте его!

По умолчанию доступны три шрифта:

  1. "Courier"
  2. "Helvetica"
  3. "Times-Roman"

У каждого шрифта есть варианты, выделенные жирным шрифтом и курсивом. Вот список всех вариантов шрифтов, доступных в reportlab:

  • "Courier"
  • "Courier-Bold
  • "Courier-BoldOblique"
  • "Courier-Oblique"
  • "Helvetica"
  • "Helvetica-Bold"
  • "Helvetica-BoldOblique"
  • "Helvetica-Oblique"
  • "Times-Bold"
  • "Times-BoldItalic
  • "Times-Italic"
  • "Times-Roman"

Вы также можете установить цвет шрифта с помощью .setFillColor(). В следующем примере создается PDF файл с синим текстом с именем font-colors.pdf:

from reportlab.lib.colors import blue
from reportlab.lib.pagesizes import LETTER
from reportlab.lib.units import inch
from reportlab.pdfgen.canvas import Canvas

canvas = Canvas("font-colors.pdf", pagesize=LETTER)


# Установить шрифт Times New Roman с размером 12 пунктов
canvas.setFont("Times-Roman", 12)

# Нарисовать синий текст в одном дюйме слева и в десяти
# дюймы от низа
canvas.setFillColor(blue)
canvas.drawString(1 * inch, 10 * inch, "Blue text")

# Сохранить PDF файл
canvas.save()

blue – это объект, импортированный из модуля reportlab.lib.colors. Этот модуль содержит несколько общих цветов. Полный список цветов можно найти в исходном коде reportlab.

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

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

Заключение: создание и изменение файлов PDF в Python

В этом руководстве вы узнали, как создавать и изменять PDF файлы с помощью пакетов PyPDF2 и reportlab.

С PyPDF2 вы узнали, как:

  • Чтение PDF файлов и извлечение текста с помощью класса PdfFileReader
  • Запись новых PDF файлов с помощью класса PdfFileWriter
  • Слияние и объединение PDF файлов с помощью класса PdfFileMerger
  • Поворот и обрезка PDF страниц
  • Шифрация и расшифровка PDF-файлов с паролями

Вы также познакомились с созданием файлов PDF с нуля с помощью пакета reportlab. Вы научились:

  • Использовать класс Canvas
  • Написали текст на холст с помощью .drawString()
  • Установили шрифт и размер шрифта с помощью .setFont()
  • Изменили цвет шрифта с помощью .setFillColor()