Формат переносимого документа (PDF) не является форматом WYSIWYG («Что видишь, то и получаешь»). Он был разработан как платформенно-независимый, независимый от базовой операционной системы и механизмов рендеринга.
Чтобы достичь этого, PDF был создан для взаимодействия с помощью чего-то, больше похожего на язык программирования, и полагается на серию инструкций и операций для достижения результата. Фактически, PDF основан на языке сценариев - PostScript , который был первым независимым от устройства языком описания страниц .
В нем есть операторы, которые изменяют графические состояния , которые на высоком уровне выглядят примерно так:
- Установите шрифт Helvetica.
- Установите черный цвет обводки
- Перейти к (60,700)
- Нарисуйте глиф "H".
Это объясняет несколько вещей:
- Почему так сложно однозначно извлечь текст из PDF
- Почему сложно редактировать PDF-документ
- Почему большинство библиотек PDF применяют очень низкоуровневый подход к созданию контента (вы, программист, должны указать координаты, в которых будет отображаться текст, поля и т. Д.)
В этом руководстве мы будем использоватьpText
- библиотеку Python, предназначенную для чтения, обработки и создания PDF-документов, чтобы создать PDF-документ. Он предлагает как низкоуровневую модель (позволяющую получить доступ к точным координатам и макету, если вы решите их использовать), так и высокоуровневую модель (где вы можете делегировать точные расчеты полей, позиций и т. Д. Менеджеру по макету) .
Мы рассмотрим, как создавать и проверять PDF-документ в Python ,
используя pText, а также как использовать некоторые LayoutElements
для
добавления штрих-кодов и таблиц.
Установка pText
pText можно загрузить из источника
наGitHub или
установить через pip
:
$ pip install ptext-joris-schellekens
Примечание. На момент написания в версии 1.8.6 по умолчанию не
устанавливаются внешние требования, такие как библиотеки
python-barcode
и qrcode
Если появится сообщение об ошибке,
установите их вручную:
$ pip install qrcode python-barcode requests
Создание PDF-документа на Python с помощью pText
pText имеет два интуитивно понятных ключевых класса - Document
и
Page
, которые представляют документ и страницы в нем. Это основная
структура для создания PDF-документов.
Кроме того, PDF
представляет собой API для загрузки и сохранения
Document
нами документов.
Имея это в виду, давайте создадим пустой файл PDF:
from ptext.pdf.document import Document
from ptext.pdf.page.page import Page
from ptext.pdf.pdf import PDF
# Create an empty Document
document = Document()
# Create an empty page
page = Page()
# Add the Page to the Document
document.append_page(page)
# Write the Document to a file
with open("output.pdf", "wb") as pdf_file_handle:
PDF.dumps(pdf_file_handle, document)
Большая часть кода здесь говорит сама за себя. Мы начинаем с создания
пустого Document
, затем добавляем пустую Page
в Document
с
помощью функции append()
и, наконец, сохраняем файл с помощью
PDF.dumps()
.
Стоит отметить, что мы использовали "wb"
для записи в двоичном
режиме , поскольку мы не хотим, чтобы Python кодировал этот текст.
Это приводит к пустому файлу PDF с именем output.pdf
в вашей локальной
файловой системе:
{.ezlazyload}
Создание документа «Hello World» с помощью pText
Конечно, пустые PDF-документы не содержат много информации. Давайте
добавим контент на Page
, прежде чем добавлять его в экземпляр
Document
Подобно двум интегральным классам, описанным ранее, чтобы добавить
контент на Page
, мы добавим PageLayout
который указывает тип
макета, который мы хотели бы видеть, и добавим один или несколько
Paragraph
в этот макет.
С этой целью Document
является экземпляром самого низкого уровня в
иерархии объектов, в то время как Paragraph
является экземпляром
самого высокого уровня, размещенным поверх PageLayout
и,
следовательно, Page
.
Давайте добавим Paragraph
на нашу Page
:
from ptext.pdf.document import Document
from ptext.pdf.page.page import Page
from ptext.pdf.pdf import PDF
from ptext.pdf.canvas.layout.paragraph import Paragraph
from ptext.pdf.canvas.layout.page_layout import SingleColumnLayout
from ptext.io.read.types import Decimal
document = Document()
page = Page()
# Setting a layout manager on the Page
layout = SingleColumnLayout(page)
# Adding a Paragraph to the Page
layout.add(Paragraph("Hello World", font_size=Decimal(20), font="Helvetica"))
document.append_page(page)
with open("output.pdf", "wb") as pdf_file_handle:
PDF.dumps(pdf_file_handle, document)
Вы заметите, что мы добавили 2 дополнительных объекта:
- Экземпляр
PageLayout
, более конкретный через его подклассSingleColumnLayout
: этот класс отслеживает, где контент добавляется наPage
, какие области доступны для будущего контента, каковыPage
и какие ведущие ( пространство междуParagraph
) должно быть.
Поскольку здесь мы работаем только с одним столбцом, мы используем
SingleColumnLayout
. В качестве альтернативы мы можем использовать
MultiColumnLayout
.
Paragraph
: этот класс представляет блок текста. Вы можете установить такие свойства, как шрифт, font_size, font_color и многие другие. Дополнительные примеры вы можете найти в документации.
Это генерирует output.pdf
, содержащий наш Paragraph
:
{.ezlazyload}
Проверка созданного PDF с помощью pText
Примечание. Этот раздел является необязательным, если вас не интересует внутренняя работа PDF-документа.
Но может быть очень полезно немного узнать о формате (например, при отладке классической проблемы «почему мой контент теперь отображается на этой странице»).
Обычно программа для чтения PDF читает документ, начиная с последних байтов:
xref
0 11
0000000000 00000 f
0000000015 00000 n
0000002169 00000 n
0000000048 00000 n
0000000105 00000 n
0000000258 00000 n
0000000413 00000 n
0000000445 00000 n
0000000475 00000 n
0000000653 00000 n
0000001938 00000 n
trailer
<</Root 1 0 R /Info 2 0 R /Size 11 /ID [<61e6d144af4b84e0e0aa52deab87cfe9><61e6d144af4b84e0e0aa52deab87cfe9>]>>
startxref
2274
%%EOF
Здесь мы видим маркер конца файла ( %%EOF
) и таблицу перекрестных
ссылок (обычно сокращенно xref
).
xref
ограничена лексем"startxref"
и"xref"
.
xref
(в документе может быть несколько) действует как справочная
таблица для программы чтения PDF-файлов.
Он содержит байтовое смещение (начиная с верхней части файла) каждого
объекта в PDF. Первая строка xref
( 0 11
) говорит, что в этой
xref
11 объектов , и что первый объект начинается с номера 0 .
Каждая последующая строка состоит из байтового смещения, за которым
следует так называемый номер поколения и буква f
или n
:
- Объекты, отмеченные буквой
f
являются свободными, их рендеринг не ожидается. - Объекты, отмеченные буквой
n
, «используются».
Внизу xref
мы находим словарь трейлеров . Словари в синтаксисе PDF
разделяются знаками <<
и >>
.
В этом словаре есть следующие пары:
/Root 1 0 R
/Info 2 0 R
/Size 11
/ID [<61e6d144af4b84e0e0aa52deab87cfe9> <61e6d144af4b84e0e0aa52deab87cfe9>]
Словарь трейлеров является отправной точкой для программы чтения PDF-файлов и содержит ссылки на все другие данные.
В таком случае:
/Root
: это еще один словарь, который ссылается на фактическое содержание документа./Info
: это словарь, содержащий метаинформацию документа (автор, название и т. Д.).
Строки типа 1 0 R
в синтаксисе PDF называются «ссылками». И здесь нам
пригодится таблица xref
Чтобы найти объект, связанный с 1 0 R
мы смотрим на объект 1 (номер
поколения 0 ).
Таблица xref
сообщает нам, что мы можем ожидать найти этот объект в
байте 15 документа.
Если мы проверим это, мы найдем:
1 0 obj
<</Pages 3 0 R>>
endobj
Обратите внимание, как этот объект начинается с 1 0 obj
и
заканчивается endobj
. Это еще одно подтверждение того, что мы на
самом деле имеем дело с объектом 1 .
Этот словарь говорит нам, что мы можем найти страницы документа в объекте 3 :
3 0 obj
<</Count 1 /Kids [4 0 R]
/Type /Pages>>
endobj
Это /Pages
, и он сообщает нам, что в этом документе 1 страница (
/Count
). Запись для /Kids
обычно представляет собой массив с одной
ссылкой на объект на страницу.
Мы можем ожидать найти первую страницу в объекте 4 :
4 0 obj
<</Type /Page /MediaBox [0 0 595 842]
/Contents 5 0 R /Resources 6 0 R /Parent 3 0 R>>
endobj
Этот словарь содержит несколько интересных статей:
/MediaBox
: физические размеры страницы (в данном случае страница размером A4)./Contents
: ссылка на (обычно сжатый) поток операторов содержимого PDF./Resources
: ссылка на словарь, содержащий все ресурсы (шрифты, изображения и т. Д.), Используемые для рендеринга этой страницы.
Давайте проверим объект 5, чтобы узнать, что на самом деле отображается на этой странице:
5 0 obj
<</Filter /FlateDecode /Length 85>>
stream
xÚã [email protected]
\È<§ ® ž `a ¥£šÔw3T0„É
€!K¡š3B˜ „ ž œenl 7' §9 ©99ù
åùE9)š
!Y('®!8õ ÂyšT*î
endstream
endobj
Как упоминалось ранее, этот (контентный) поток сжимается. Вы можете
определить, какой метод сжатия использовался, с помощью записи /Filter
. Если мы применим распаковку ( unzip
) к объекту 5, мы должны
получить фактические операторы содержимого:
5 0 obj
<</Filter /FlateDecode /Length 85>>
stream
q
BT
0.000000 0.000000 0.000000 rg
/F1 1.000000 Tf
20.000000 0 0 20.000000 60.000000 738.000000 Tm
(Hello world) Tj
ET
Q
endstream
endobj
Наконец, мы находимся на уровне, на котором мы можем декодировать контент. Каждая строка состоит из аргументов, за которыми следует их оператор. Быстро пройдемся по операторам:
q
: сохраняет текущее графическое состояние (помещая его в стек).BT
: начать текст.0 0 0 rg
: установить текущий цвет обводки на (0,0,0
) rgb. Это черный./F1 1 Tf
: установить текущий шрифт на/F1
(это запись в словаре ресурсов, упомянутом ранее) и размер шрифта на1
.20.000000 0 0 20.000000 60.000000 738.000000 Tm
: установить текстовую матрицу. Текстовые матрицы требуют отдельного руководства. Достаточно сказать, что эта матрица регулирует размер шрифта и положение текста. Здесь мы масштабируем шрифт доfont-size 20
и устанавливаем курсор для рисования текста на60,738
. Система координат PDF начинается в нижнем левом углу страницы. Итак,60,738
находится где-то рядом с левым верхом страницы (учитывая, что842
единицы).(Hello world) Tj
: строки в синтаксисе PDF разделяются(
и)
. Эта команда указывает программе чтения PDF-файлов отобразить строку «Hello world» в позиции, которую мы указали ранее с помощью текстовой матрицы, с шрифтом, размером и цветом, которые мы указали в командах до этого.ET
: конец текста.Q
: извлечь состояние графики из стека (таким образом восстанавливая состояние графики).
Добавление других элементов макета pText на страницы
pText
поставляется с широким спектром объектов LayoutElement
В
предыдущем примере мы кратко исследовали Paragraph
. Но есть и другие
элементы , такие как UnorderedList
, OrderedList
, Image
, Shape
, Barcode
и Table
.
Давайте создадим немного более сложный пример с Table
и Barcode
.
Tables
состоят из TableCell
, которые мы добавляем в экземпляр
Table
Barcode
может быть одним из многих BarcodeType
- мы будем
использовать QR
код:
from ptext.pdf.document import Document
from ptext.pdf.page.page import Page
from ptext.pdf.pdf import PDF
from ptext.pdf.canvas.layout.paragraph import Paragraph
from ptext.pdf.canvas.layout.page_layout import SingleColumnLayout
from ptext.io.read.types import Decimal
from ptext.pdf.canvas.layout.table import Table, TableCell
from ptext.pdf.canvas.layout.barcode import Barcode, BarcodeType
from ptext.pdf.canvas.color.color import X11Color
document = Document()
page = Page()
# Layout
layout = SingleColumnLayout(page)
# Create and add heading
layout.add(Paragraph("DefaultCorp Invoice", font="Helvetica", font_size=Decimal(20)))
# Create and add barcode
layout.add(Barcode(data="0123456789", type=BarcodeType.QR, width=Decimal(64), height=Decimal(64)))
# Create and add table
table = Table(number_of_rows=5, number_of_columns=4)
# Header row
table.add(TableCell(Paragraph("Item", font_color=X11Color("White")), background_color=X11Color("SlateGray")))
table.add(TableCell(Paragraph("Unit Price", font_color=X11Color("White")), background_color=X11Color("SlateGray")))
table.add(TableCell(Paragraph("Amount", font_color=X11Color("White")), background_color=X11Color("SlateGray")))
table.add(TableCell(Paragraph("Price", font_color=X11Color("White")), background_color=X11Color("SlateGray")))
# Data rows
for n in [("Lorem", 4.99, 1), ("Ipsum", 9.99, 2), ("Dolor", 1.99, 3), ("Sit", 1.99, 1)]:
table.add(Paragraph(n[0]))
table.add(Paragraph(str(n[1])))
table.add(Paragraph(str(n[2])))
table.add(Paragraph(str(n[1] * n[2])))
# Set padding
table.set_padding_on_all_cells(Decimal(5), Decimal(5), Decimal(5), Decimal(5))
layout.add(table)
# Append page
document.append_page(page)
# Persist PDF to file
with open("output4.pdf", "wb") as pdf_file_handle:
PDF.dumps(pdf_file_handle, document)
Некоторые детали реализации:
pText
поддерживает различные цветовые модели, в том числе:RGBColor
,HexColor
,X11Color
иHSVColor
.- Вы можете добавлять
LayoutElement
непосредственно вTable
, но вы также можете обернуть ихTableCell
, это дает вам некоторые дополнительные параметры, такие как установкаcol_span
иrow_span
или, в данном случае,background_color
. - Если
font
,font_size
илиfont_color
не указаны,Paragraph
примет значение по умолчаниюHelvetica
,size 12
,black
.
Это приводит к:
{.ezlazyload}
Заключение
В этом руководстве мы рассмотрели pText , библиотеку для чтения, записи и управления файлами PDF.
Мы рассмотрели ключевые классы, такие как Document
и Page
, а также
некоторые элементы, такие как Paragraph
, Barcode
и PageLayout
.
Наконец, мы создали несколько PDF-файлов с различным содержимым, а также
проверили, как в PDF-файлах хранятся данные.