Breaking a paragraph up, and aligning it

A paragraph broken into lines, set four ways: left, right, centred, justified.

Python write_flow.py 110 lines
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
"""Lays a paragraph out four ways, to see the alignment.

The Python twin of the `write_flow` example in Rust. The paragraph is held in `Words`,
once per language, and `HQF_PDF_LANG` picks which one is laid out.

Usage: python examples/write_flow.py [out.pdf] [font.ttf]
       HQF_PDF_LANG=fr python examples/write_flow.py
"""

from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path

import _language
import _licence
import _out

import hqf_pdf


@dataclass(frozen=True)
class Words:
    """Every word the page lays out, in one language."""

    # The paragraph the four alignments are shown on.
    paragraph: str


# The page in English.
ENGLISH = Words(
    paragraph=(
        "This library lays text out by breaking a paragraph into lines that fit a "
        "box, then placing each line according to an alignment. The same words are "
        "shown below flush left, flush right, centred, and justified, so the "
        "difference between them is plain to see at a glance."
    )
)

# The page in French.
FRENCH = Words(
    paragraph=(
        "Cette bibliothèque met le texte en page en découpant un paragraphe en "
        "lignes qui tiennent dans une boîte, puis en plaçant chaque ligne selon un "
        "alignement. Les mêmes mots sont repris ci-dessous alignés à gauche, alignés "
        "à droite, centrés, puis justifiés, pour que la différence entre eux se voie "
        "d'un seul coup d'œil."
    )
)


# Every language the example is written in. A language is added by writing its own set
# of words and naming it here.
WORDS = {_language.ENGLISH: ENGLISH, _language.FRENCH: FRENCH}


def main() -> None:
    language = _language.from_environment()
    words = _language.words_of(WORDS, language)

    # A named file is written as named; the default one carries the language, so the two
    # languages do not overwrite each other in `tmp/`.
    out = _out.output_path(Path(_language.file_name("flow.pdf", language)).stem)

    document = hqf_pdf.Document()
    document.set_license(_licence.licensed())
    handle = document.add_font(hqf_pdf.Font.from_path(_out.font_path()))

    content = hqf_pdf.Content()
    box_width = 400.0
    x = 80.0
    top = 780.0

    for align in (
        hqf_pdf.Align.Left,
        hqf_pdf.Align.Right,
        hqf_pdf.Align.Center,
        hqf_pdf.Align.Justify,
    ):
        flow = hqf_pdf.TextFlow(handle, 11.0, align=align, leading=15.0)
        lines = flow.break_lines(words.paragraph, box_width)
        flow.draw(content, lines, x, top, box_width)

        # A frame around the text column, inset from the glyphs on every side.
        height = flow.height(lines)
        pad = 8.0
        content.save_state()
        content.set_stroke(hqf_pdf.Rgb.gray(0.8))
        content.set_line_width(0.5)
        content.rect(
            x - pad,
            top - height - pad,
            box_width + pad * 2.0,
            height + pad * 2.0,
        )
        content.stroke()
        content.restore_state()

        top -= height + 30.0

    page = hqf_pdf.Page.a4()
    page.set_content(content)
    document.add_page(page)

    written = document.write(out)
    print(f"wrote {out}: {written} bytes")


if __name__ == "__main__":
    main()