Создаём красивый PDF-файл с помощью библиотеки borb

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

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

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

В этом руководстве мы рассмотрим, как создать листовку, содержащую пользовательскую графику (представленную операторами PDF).

Устанавливаем библиотеку borb

borb можно загрузить из источника на GitHub или установить через оператора pip:

$ pip install borb

Что у нас получится

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

Мы сделаем вот такой флаер, чтобы продвигать какой-то продукт, принадлежащий какой-то компании:

Создаём PDF-файл с помощью библиотеки borb

Создание PDF-документа в borb обычно выполняется в несколько шагов:

  • Создаём новый Document
  • Создаём новый Page и присоединяем его к Document
  • Устанавливаем PageLayout на Page
  • Сохраняем Document

Вот так будет выглядеть наш код:

from borb.pdf.document import Document
from borb.pdf.page.page import Page
from borb.pdf.canvas.layout.page_layout.multi_column_layout import SingleColumnLayout
from borb.pdf.canvas.layout.page_layout.page_layout import PageLayout
from borb.pdf.pdf import PDF

def main():
  # Создаём новый Document
  pdf = Document()

  # Создаём новый Page
  page = Page()

  # Присоединяем Page к Document
  pdf.append_page(page)

  # Создаём PageLayout
  layout: PageLayout = SingleColumnLayout(page)

  # Будущий код с содержимым будет здесь
  
  # Сохраняем PDF
  with open("output.pdf", "wb") as pdf_file_handle:
      PDF.dumps(pdf_file_handle, pdf)
  
if __name__ == '__main__':
  main()

Создаём PDF-флаер с помощью borb

Теперь, когда у нас есть пустой холст для работы, давайте добавим содержимое. Мы начнем с названия, например “Your Company”:

# Новые import
from borb.pdf.canvas.layout.text.paragraph import Paragraph  
from borb.pdf.canvas.color.color import HexColor  
from decimal import Decimal  
  
# Контактная информация
layout.add(
  Paragraph("Your Company", 
            font_color=HexColor("#6d64e8"), 
            font_size=Decimal(20)
    )
)

Теперь добавим QR-код и контактную информацию. Для этого мы будем использовать Table.

Также нам нужно расположение QR-кода (позже мы добавим к нему кое-что особенное). Итак, давайте начнем с того, что сначала объявим об этом:

# Новые import
from borb.pdf.canvas.layout.image.barcode import Barcode, BarcodeType  
from borb.pdf.canvas.layout.layout_element import LayoutElement
  
# Код для создания QR-кода LayoutElement
qr_code: LayoutElement = Barcode(
    data="https://www.borbpdf.com",
    width=Decimal(64),
    height=Decimal(64),
    type=BarcodeType.QR,
)

Теперь можно создать и добавить Table:

# Новые import
from borb.pdf.canvas.layout.table.flexible_column_width_table import FlexibleColumnWidthTable
  
layout.add(
    FlexibleColumnWidthTable(number_of_columns=2, number_of_rows=1)
    .add(qr_code)
    .add(
        Paragraph(
            """
            500 South Buena Vista Street
            Burbank CA
            91521-0991 USA
            """,
            padding_top=Decimal(12),
            respect_newlines_in_text=True,
            font_color=HexColor("#666666"),
            font_size=Decimal(10),
        )
    )
    .no_borders()
)

Давайте запустим программу и посмотрим, как выглядит сгенерированный PDF-файл.

Неплохо! QR-код расположен прямо под названием компании, содержит правильную контактную информацию и кодирует предоставленные нами контактные данные.

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

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

Это простая фишка, но для читателя она очень полезна:

page.append_remote_go_to_annotation(
  qr_code.get_bounding_box(), uri="https://www.borbpdf.com"
)

Добавляем информацию о нашем продукте

Теперь можно добавить заголовок и подзаголовок продукта:

# Заголовок
layout.add(
    Paragraph(
        "Productbrochure", font_color=HexColor("#283592"), font_size=Decimal(34)
    )
)

# Подзаголовок
layout.add(
    Paragraph(
        "September 4th, 2021",
        font_color=HexColor("#e01b84"),
        font_size=Decimal(11),
    )
)

И точно так же мы добавим заголовок описания продукта и какой-нибудь странный текст:

# описание продукта
layout.add(
    Paragraph(
        "Product Overview", font_color=HexColor("000000"), font_size=Decimal(21)
    )
)

layout.add(
   Paragraph(
        """
        Far far away, behind the word mountains, far from the countries Vokalia and Consonantia, there live the blind texts. 
        Separated they live in Bookmarksgrove right at the coast of the Semantics, a large language ocean. 
        A small river named Duden flows by their place and supplies it with the necessary regelialia.
        """
    )
)

layout.add(
    Paragraph(
        """
        It is a paradisematic country, in which roasted parts of sentences fly into your mouth. 
        Even the all-powerful Pointing has no control about the blind texts it is an almost unorthographic life. 
        One day however a small line of blind text by the name of Lorem Ipsum decided to leave for the far World of Grammar.
        """,
        margin_bottom=Decimal(12)
    )
)

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

Когда мы запустим программу, наш результат будет таким:

Теперь мы можем добавить информацию о продукте. Добавим изображение (Image) вместе со списком некоторых функций продукта. Опять-таки, мы можем использовать Table, чтобы изображение и функции были параллельны.

Обычно над списком функций есть заголовок, поэтому мы добавим Table с 2 столбцами (изображение и функции) и 2 строками (одна для заголовка и одна для функций).

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

# Новые import
from borb.pdf.canvas.layout.image.image import Image
from borb.pdf.canvas.layout.table.table import TableCell  
from borb.pdf.canvas.layout.table.fixed_column_width_table import FixedColumnWidthTable
from borb.pdf.canvas.layout.list.unordered_list import UnorderedList
  
# Таблица с картинкой и основными функциями
layout.add(
    FixedColumnWidthTable(
        number_of_rows=2,
        number_of_columns=2,
        column_widths=[Decimal(0.3), Decimal(0.7)],
    )
    .add(
        TableCell(
            Image(
                  "https://www.att.com/catalog/en/skus/images/apple-iphone%2012-purple-450x350.png",
                width=Decimal(128),
                height=Decimal(128),
            ),
            row_span=2,
        )
    )
    .add(
        Paragraph(
            "Key Features",
            font_color=HexColor("e01b84"),
            font="Helvetica-Bold",
            padding_bottom=Decimal(10),
        )
    )
    .add(
        UnorderedList()
        .add(Paragraph("Nam aliquet ex eget felis lobortis aliquet sit amet ut risus."))
        .add(Paragraph("Maecenas sit amet odio ut erat tincidunt consectetur accumsan ut nunc."))
        .add(Paragraph("Phasellus eget magna et justo malesuada fringilla."))
        .add(Paragraph("Maecenas vitae dui ac nisi aliquam malesuada in consequat sapien."))
    )
    .no_borders()
)

Опять-таки, мы добавили padding_bottom в некоторые ячейки Table, чтобы добавить пространство. Полученный PDF-файл:

Последнее, что мы сделаем – это добавим рисунок в правом верхнем углу и в нижнем колонтитуле.

Используем объект Shape из borb

borb может отображать на странице (Page) любую форму (Shape). Форма (Shape) представляет собой произвольную последовательность точек (в коде это typing.Tuple[Decimal, Decimal]), которые образуют непрерывную линию. Можете сами придумать, какие формы хотите сделать.

Мы начнем с определения метода, который отображает треугольники и квадраты в правом верхнем углу страницы (Page):

# Новые import
from borb.pdf.canvas.geometry.rectangle import Rectangle
from borb.pdf.canvas.layout.image.shape import Shape
from borb.pdf.page.page_size import PageSize
import typing
import random
  
  
def add_gray_artwork_upper_right_corner(page: Page) -> None:
  """
  This method will add a gray artwork of squares and triangles in the upper right corner
  of the given Page
  """
    grays: typing.List[HexColor] = [
        HexColor("A9A9A9"),
        HexColor("D3D3D3"),
        HexColor("DCDCDC"),
        HexColor("E0E0E0"),
        HexColor("E8E8E8"),
        HexColor("F0F0F0"),
    ]
    ps: typing.Tuple[Decimal, Decimal] = PageSize.A4_PORTRAIT.value
    N: int = 4
    M: Decimal = Decimal(32)
    
    # Рисуем треугольники
    for i in range(0, N):
        x: Decimal = ps[0] - N * M + i * M
        y: Decimal = ps[1] - (i + 1) * M
        rg: HexColor = random.choice(grays)
        Shape(
            points=[(x + M, y), (x + M, y + M), (x, y + M)],
            stroke_color=rg,
            fill_color=rg,
        ).layout(page, Rectangle(x, y, M, M))
        
    # Рисуем квадрат
    for i in range(0, N - 1):
        for j in range(0, N - 1):
            if j > i:
                continue
            x: Decimal = ps[0] - (N - 1) * M + i * M
            y: Decimal = ps[1] - (j + 1) * M
            rg: HexColor = random.choice(grays)
            Shape(
                points=[(x, y), (x + M, y), (x + M, y + M), (x, y + M)],
                stroke_color=rg,
                fill_color=rg,
            ).layout(page, Rectangle(x, y, M, M))

Добавим в основной код и получим следующее:

Аналогично, мы могли бы добавить некоторые графические изображения в нижней части страницы:

  • Строка, отделяющая нижний колонтитул от основного содержимого страницы
  • Небольшой геометрический элемент, уравновешивающий графику в верхней части страницы

Так бы это выглядело:

from borb.pdf.canvas.line_art.line_art_factory import LineArtFactory

def add_colored_artwork_bottom_right_corner(page: Page) -> None:
  """
  This method will add a blue/purple artwork of lines 
  and squares to the bottom right corner
  of the given Page
  """
    ps: typing.Tuple[Decimal, Decimal] = PageSize.A4_PORTRAIT.value
    
    # Квадрат
    Shape(
      points=[
          (ps[0] - 32, 40),
          (ps[0], 40),
          (ps[0], 40 + 32),
          (ps[0] - 32, 40 + 32),
      ],
      stroke_color=HexColor("d53067"),
      fill_color=HexColor("d53067"),
    ).layout(page, Rectangle(ps[0] - 32, 40, 32, 32))
    
    # Квадрат
    Shape(
      points=[
          (ps[0] - 64, 40),
          (ps[0] - 32, 40),
          (ps[0] - 32, 40 + 32),
          (ps[0] - 64, 40 + 32),
      ],
      stroke_color=HexColor("eb3f79"),
      fill_color=HexColor("eb3f79"),
    ).layout(page, Rectangle(ps[0] - 64, 40, 32, 32))
    
    # Треугольник
    Shape(
      points=[
          (ps[0] - 96, 40),
          (ps[0] - 64, 40),
          (ps[0] - 64, 40 + 32),
      ],
      stroke_color=HexColor("e01b84"),
      fill_color=HexColor("e01b84"),
    ).layout(page, Rectangle(ps[0] - 96, 40, 32, 32))
        
    # Линия
    r: Rectangle = Rectangle(Decimal(0), Decimal(32), ps[0], Decimal(8))
    Shape(
      points=LineArtFactory.rectangle(r),
      stroke_color=HexColor("283592"),
      fill_color=HexColor("283592"),
    ).layout(page, r)

Итоговый файл должен выглядеть так:

Заключение

В этом руководстве мы рассмотрели некоторые основные строительные блоки PDF-документов с использованием borb. Мы настроили отступы и поля, а также размер и цвет шрифта. Мы также создали графику с использованием объекта Shape и рабочего интерактивного QR-кода.

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

Ответить