Examples

The code for the examples can be found in the examples/ directory.

📚 A book

This example shows:

  • Creating single pages and autogenerated ones

  • Page numbering

  • Simple graphics

  • Custom font

Source code (click to expand)
from __future__ import annotations

import re
from pathlib import Path
from typing import Iterable, Sequence

from pdfje import XY, AutoPage, Document, Page
from pdfje.draw import Ellipse, Rect, Text
from pdfje.fonts import TrueType
from pdfje.layout import Paragraph, Rule
from pdfje.style import Style
from pdfje.units import inch, mm


def main() -> None:
    "Generate a PDF with the content of The Great Gatsby"
    Document(
        [TITLE_PAGE]
        + [AutoPage(blocks, template=create_page) for blocks in chapters()],
        style=CRIMSON,
    ).write("book.pdf")


def create_page(num: int) -> Page:
    # Add a page number at the bottom of the base page
    return BASEPAGE.add(
        Text(
            (PAGESIZE.x / 2, mm(20)), str(num), Style(size=10), align="center"
        )
    )


PAGESIZE = XY(inch(5), inch(8))
BASEPAGE = Page(
    [
        # The title in small text at the top of the page
        Text(
            (PAGESIZE.x / 2, PAGESIZE.y - mm(10)),
            "The Great Gatsby",
            Style(size=10, italic=True),
            align="center",
        ),
    ],
    size=PAGESIZE,
    margin=(mm(20), mm(20), mm(25)),
)

HEADING = Style(size=20, bold=True, line_spacing=3.5)

TITLE_PAGE = Page(
    [
        # Some nice shapes
        Rect(
            (PAGESIZE.x / 2 - 200, 275),  # use page dimensions to center it
            width=400,
            height=150,
            fill="#99aaff",
            stroke=None,
        ),
        Ellipse((PAGESIZE.x / 2, 350), 300, 100, fill="#22d388"),
        # The title and author on top of the shapes
        Text(
            (PAGESIZE.x / 2, 380),
            "The Great Gatsby",
            Style(size=30, bold=True),
            align="center",
        ),
        Text(
            (PAGESIZE.x / 2, 335),
            "F. Scott Fitzgerald",
            Style(size=14, italic=True),
            align="center",
        ),
    ],
    size=PAGESIZE,
)
CRIMSON = TrueType(
    Path(__file__).parent / "../resources/fonts/CrimsonText-Regular.ttf",
    Path(__file__).parent / "../resources/fonts/CrimsonText-Bold.ttf",
    Path(__file__).parent / "../resources/fonts/CrimsonText-Italic.ttf",
    Path(__file__).parent / "../resources/fonts/CrimsonText-BoldItalic.ttf",
)


_CHAPTER_NUMERALS = set("I II III IV V VI VII VIII IX X".split())


def chapters() -> Iterable[Sequence[Paragraph | Rule]]:
    "Book content grouped by chapters"
    buffer: list[Paragraph | Rule] = [Paragraph("Chapter I\n", HEADING)]
    indent = 0
    for p in PARAGRAPHS:
        if p.strip() in _CHAPTER_NUMERALS:
            yield buffer
            buffer = [Paragraph(f"Chapter {p.strip()}\n", HEADING)]
            indent = 0
        elif p.startswith("------"):
            buffer.append(Rule("#aaaaaa", (20, 10, 10)))
        else:
            buffer.append(
                Paragraph(
                    p, Style(line_spacing=1.2), align="justify", indent=indent
                )
            )
            indent = 15
    yield buffer


PARAGRAPHS = [
    m.replace("\n", " ")
    for m in re.split(
        r"\n\n",
        (
            Path(__file__).parent / "../resources/books/the-great-gatsby.txt"
        ).read_text()[1374:-18415],
    )
]

if __name__ == "__main__":
    main()

📰 Multiple columns

This example shows the flexibility of the layout engine.

Source code (click to expand)
from __future__ import annotations

from pdfje import AutoPage, Column, Document, Page
from pdfje.fonts import times_roman
from pdfje.layout import Paragraph
from pdfje.style import Style, italic
from pdfje.units import A3, A4, A6, inch, mm


def main() -> None:
    "Generate a PDF with differently styled text layed out in various columns"
    Document(
        [
            AutoPage(
                # Repeat the same text in different styles
                [Paragraph(LOREM_IPSUM, s) for s in STYLES * 3],
                # Cycle through the three page templates
                template=lambda i: TEMPLATES[i % 3],
            )
        ]
    ).write("multicolumn.pdf")


STYLES = [Style(size=10), "#225588" | italic, Style(size=15, font=times_roman)]
TEMPLATES = [
    # A one-column page
    Page(size=A6, margin=mm(15)),
    # A two-column page
    Page(
        columns=[
            Column(
                (inch(1), inch(1)),
                width=(A4.x / 2) - inch(1.25),
                height=A4.y - inch(2),
            ),
            Column(
                (A4.x / 2 + inch(0.25), inch(1)),
                width=(A4.x / 2) - inch(1.25),
                height=A4.y - inch(2),
            ),
        ]
    ),
    # A page with three arbitrary columns
    Page(
        size=A3.flip(),
        columns=[
            Column(
                (inch(1), inch(1)),
                width=(A3.y / 4),
                height=A3.x - inch(2),
            ),
            Column(
                (A3.y / 4 + inch(1.5), inch(5)),
                width=(A3.y / 2) - inch(1.25),
                height=A3.x - inch(8),
            ),
            Column(
                ((A3.y * 0.8) + inch(0.25), inch(4)),
                width=(A3.y / 10),
                height=inch(5),
            ),
        ],
    ),
]


LOREM_IPSUM = """\
Lorem ipsum dolor sit amet, consectetur adipiscing elit. \
Integer sed aliquet justo. Donec eu ultricies velit, porta pharetra massa. \
Ut non augue a urna iaculis vulputate ut sit amet sem. \
Nullam lectus felis, rhoncus sed convallis a, egestas semper risus. \
Fusce gravida metus non vulputate vestibulum. \
Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere \
cubilia curae; Donec placerat suscipit velit. \
Mauris tincidunt lorem a eros eleifend tincidunt. \
Maecenas faucibus imperdiet massa quis pretium. Integer in lobortis nisi. \
Mauris at odio nec sem volutpat aliquam. Aliquam erat volutpat. \

Fusce at vehicula justo. Vestibulum eget viverra velit. \
Vivamus et nisi pulvinar, elementum lorem nec, volutpat leo. \
Aliquam erat volutpat. Sed tristique quis arcu vitae vehicula. \
Morbi egestas vel diam eget dapibus. Donec sit amet lorem turpis. \
Maecenas ultrices nunc vitae enim scelerisque tempus. \
Maecenas aliquet dui non hendrerit viverra. \
Aliquam fringilla, est sit amet gravida convallis, elit ipsum efficitur orci, \
eget convallis neque nunc nec lorem. Nam nisl sem, \
tristique a ultrices sed, finibus id enim.

Etiam vel dolor ultricies, gravida felis in, vestibulum magna. \
In diam ex, elementum ut massa a, facilisis sollicitudin lacus. \
Integer lacus ante, ullamcorper ac mauris eget, rutrum facilisis velit. \
Mauris eu enim efficitur, malesuada ipsum nec, sodales enim. \
Nam ac tortor velit. Suspendisse ut leo a felis aliquam dapibus ut a justo. \
Vestibulum sed commodo tortor. Sed vitae enim ipsum. \
Duis pellentesque dui et ipsum suscipit, in semper odio dictum. \

Sed in fermentum leo. Donec maximus suscipit metus. \
Nulla convallis tortor mollis urna maximus mattis. \
Sed aliquet leo ac sem aliquam, et ultricies mauris maximus. \
Cras orci ex, fermentum nec purus non, molestie venenatis odio. \
Etiam vitae sollicitudin nisl. Sed a ullamcorper velit. \

Aliquam congue aliquet eros scelerisque hendrerit. Vestibulum quis ante ex. \
Fusce venenatis mauris dolor, nec mattis libero pharetra feugiat. \
Pellentesque habitant morbi tristique senectus et netus et malesuada \
fames ac turpis egestas. Cras vitae nisl molestie augue finibus lobortis. \
In hac habitasse platea dictumst. Maecenas rutrum interdum urna, \
ut finibus tortor facilisis ac. Donec in fringilla mi. \
Sed molestie accumsan nisi at mattis. \
Integer eget orci nec urna finibus porta. \
Sed eu dui vel lacus pulvinar blandit sed a urna. \
Quisque lacus arcu, mattis vel rhoncus hendrerit, dapibus sed massa. \
Vivamus sed massa est. In hac habitasse platea dictumst. \
Nullam volutpat sapien quis tincidunt sagittis. \
"""

if __name__ == "__main__":
    main()