Text and shapes filled with a gradient

A gradient used as the colour something is filled with: a headline whose letters are windows onto a blend of three colours, and a panel filled with a circular one.

Python write_gradient_fill.py 141 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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
"""Fills text and a shape with a gradient, through a shading pattern.

The Python twin of the `write_gradient_fill` example in Rust. Where
`write_gradient` paints a gradient into a clipped box, this makes a gradient the
colour something is filled with: a headline whose letters are windows onto a
three-stop blend, and a panel filled with a radial one. The gradient lives in
the page's own space, so the letters show the part of it behind them.

The two captions are held in `Words`, once per language, and `HQF_PDF_LANG` picks which
set is drawn. The headline is the library's own name and reads the same in every
language.

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

from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path

import _language
import _licence
import _out

import hqf_pdf

# The headline the gradient fills, which is the library's own name.
HEADLINE = "hqf-pdf"


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

    # The caption under the headline.
    letters: str
    # The caption under the panel.
    shape: str


# The page in English.
ENGLISH = Words(
    letters="Text filled with a gradient, through a shading pattern.",
    shape="A shape filled with a radial gradient.",
)

# The page in French.
FRENCH = Words(
    letters="Du texte rempli par un dégradé, au moyen d'un motif.",
    shape="Une forme remplie par un dégradé en cercle.",
)


# 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("gradient_fill.pdf", language)).stem)

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

    left = 70.0

    # A three-stop gradient spanning the width of the headline, set as a fill pattern so
    # the letters show it through their shapes.
    headline_size = 84.0
    headline_baseline = 660.0
    headline_width = handle.measure(HEADLINE, headline_size)
    ramp = hqf_pdf.Function.stitching(
        (0.0, 1.0),
        [
            hqf_pdf.Function.exponential(
                (0.0, 1.0), 1.0, ([0.85, 0.12, 0.12], [0.12, 0.55, 0.2])
            ),
            hqf_pdf.Function.exponential(
                (0.0, 1.0), 1.0, ([0.12, 0.55, 0.2], [0.12, 0.2, 0.8])
            ),
        ],
        [0.5],
        [(0.0, 1.0), (0.0, 1.0)],
    )
    letters = document.add_shading_pattern(
        hqf_pdf.Axial(
            (left, headline_baseline),
            (left + headline_width, headline_baseline),
            hqf_pdf.DeviceSpace.Rgb,
            ramp,
        )
    )

    # A radial gradient filling a panel below the headline.
    px, py, pw, ph = left, 350.0, 455.0, 190.0
    panel = document.add_shading_pattern(
        hqf_pdf.Radial.rgb(
            (px + pw / 2.0, py + ph / 2.0, 0.0),
            (px + pw / 2.0, py + ph / 2.0, pw / 2.0),
            hqf_pdf.Rgb(1.0, 1.0, 1.0),
            hqf_pdf.Rgb(0.1, 0.15, 0.45),
        ).extend(False, True)
    )

    content = hqf_pdf.Content()

    # The headline, its letters filled with the gradient.
    content.save_state()
    content.set_fill_pattern(letters)
    content.draw_text(handle, headline_size, left, headline_baseline, HEADLINE)
    content.restore_state()

    # A caption, in plain black.
    content.draw_text(handle, 13.0, left, headline_baseline - 26.0, words.letters)

    # The panel, filled with the radial gradient.
    content.save_state()
    content.set_fill_pattern(panel)
    content.rect(px, py, pw, ph)
    content.fill()
    content.restore_state()

    content.draw_text(handle, 12.0, px, py - 16.0, words.shape)

    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()