Gradients in a line and in a circle

Gradients in a line and in a circle painted into boxes the page cuts out: a two-colour blend across and another on the diagonal, a circular one whose outer colour is carried out to the corners, and one passing through three colours.

Python write_gradient.py 168 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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
"""Paints axial and radial gradients, each into a clipped box.

The Python twin of the `write_gradient` example in Rust. Four panels: a
horizontal two-colour gradient, a diagonal one, a radial one whose outer colour
is extended to fill the corners, and a three-stop gradient built from a stitched
function. Each is added to the document once, then painted into a rectangle the
page clips to.

The title and the four labels are held in `Words`, once per language, and `HQF_PDF_LANG`
picks which set is drawn.

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

from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path

import _language
import _licence
import _out

import hqf_pdf

# The width and height of every panel, in points.
BOX = (210.0, 170.0)


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

    # The title across the top of the page.
    title: str
    # The label under each of the four panels.
    horizontal: str
    diagonal: str
    radial: str
    three_stops: str


# The page in English.
ENGLISH = Words(
    title="Axial and radial gradients",
    horizontal="Axial, two colours",
    diagonal="Axial, on the diagonal",
    radial="Radial, outer extended",
    three_stops="Axial, three stops",
)

# The page in French.
FRENCH = Words(
    title="Dégradés en ligne droite et en cercle",
    horizontal="En ligne droite, deux couleurs",
    diagonal="En ligne droite, en diagonale",
    radial="En cercle, couleur extérieure prolongée",
    three_stops="En ligne droite, trois couleurs",
)


# 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 panel(content, font, shading, x, y, label):
    """Paint a gradient into the box at (x, y), sized BOX, and label it."""
    content.save_state()
    content.rect(x, y, BOX[0], BOX[1])
    content.clip()
    content.end_path()
    content.draw_shading(shading)
    content.restore_state()

    content.draw_text(font, 10.0, x, y - 15.0, label)


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.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 column x, right column x, top row foot, bottom row foot.
    left, right, top, bottom = 70.0, 315.0, 560.0, 320.0

    # A horizontal two-colour gradient across the top-left box.
    horizontal = document.add_shading(
        hqf_pdf.Axial.rgb(
            (left, top),
            (left + BOX[0], top),
            hqf_pdf.Rgb(0.85, 0.12, 0.12),
            hqf_pdf.Rgb(0.12, 0.2, 0.8),
        )
    )

    # A diagonal two-colour gradient across the top-right box.
    diagonal = document.add_shading(
        hqf_pdf.Axial.rgb(
            (right, bottom + BOX[1]),
            (right + BOX[0], top + BOX[1]),
            hqf_pdf.Rgb(0.95, 0.8, 0.1),
            hqf_pdf.Rgb(0.1, 0.55, 0.2),
        )
    )

    # A radial gradient in the bottom-left box, its outer colour extended so it reaches
    # the corners.
    radial = document.add_shading(
        hqf_pdf.Radial.rgb(
            (left + BOX[0] / 2.0, bottom + BOX[1] / 2.0, 0.0),
            (left + BOX[0] / 2.0, bottom + BOX[1] / 2.0, BOX[0] / 2.0),
            hqf_pdf.Rgb(1.0, 1.0, 1.0),
            hqf_pdf.Rgb(0.1, 0.15, 0.45),
        ).extend(False, True)
    )

    # A three-stop gradient in the bottom-right box, from a stitched function: red to
    # green over the first half, green to blue over the second.
    rainbow_function = 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.6, 0.2])
            ),
            hqf_pdf.Function.exponential(
                (0.0, 1.0), 1.0, ([0.12, 0.6, 0.2], [0.12, 0.2, 0.8])
            ),
        ],
        [0.5],
        [(0.0, 1.0), (0.0, 1.0)],
    )
    rainbow = document.add_shading(
        hqf_pdf.Axial(
            (right, bottom),
            (right + BOX[0], bottom),
            hqf_pdf.DeviceSpace.Rgb,
            rainbow_function,
        )
    )

    content = hqf_pdf.Content()

    content.draw_text(handle, 18.0, left, 770.0, words.title)

    panel(content, handle, horizontal, left, top, words.horizontal)
    panel(content, handle, diagonal, right, top, words.diagonal)
    panel(content, handle, radial, left, bottom, words.radial)
    panel(content, handle, rainbow, right, bottom, words.three_stops)

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