A two-page services contract

A services agreement over two pages, ending in signature, name and date fields a reader fills in.

Python write_contract.py 671 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
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
"""Draws a services agreement two parties sign in a reader: a letterhead, the
parties, numbered clauses set as flowing paragraphs, and a signature block with a
signature field, a printed name and a date for each side.

The Python twin of the `write_contract` example in Rust: the same document,
through the binding rather than through the library directly.

The clauses are prose, so the page is laid out by measuring: each clause is
broken to the column, and a clause that would not fit under what is already on
the page starts the next one instead, heading and body together. The number of
pages is whatever that comes to, and the footer of every page says which of them
it is.

The fields at the foot are what makes the page an agreement rather than a picture
of one: a reader signs the signature field, types the name and the date into the
two boxes under it, and every field carries a border so the boxes to click are
plain to see.

Every word the page draws is held in `Words`, once per language, and `HQF_PDF_LANG`
picks which one is drawn. The parties' names and addresses, their registration numbers,
the agreement's number, the day rate and the names the form fields answer to are the
same in both, and the French runs to its own number of pages.

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

from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path

import _language
import _licence
import _out

import hqf_pdf

# A4, in points, and the page's left edge with its mirrored right.
PAGE_WIDTH = 595.276
PAGE_HEIGHT = 841.89
LEFT = 56.0
RIGHT = PAGE_WIDTH - LEFT
# The width every paragraph is broken to.
BODY_WIDTH = PAGE_WIDTH - 2 * LEFT

# The ink the headings and rules are drawn in, and the grey of the small print.
ACCENT = hqf_pdf.Rgb(0.11, 0.33, 0.55)
MUTED = hqf_pdf.Rgb(0.42, 0.42, 0.45)
INK = hqf_pdf.Rgb(0.1, 0.1, 0.12)

# The logo the letterhead carries: the committed fixture, so the example runs on any
# machine. It stands in for the provider's mark.
LOGO = Path(__file__).resolve().parents[2] / "hqf-pdf" / "tests" / "images" / "hqf_development.png"

# The size the clause bodies are set at, and the distance from one of their baselines to
# the next.
BODY_SIZE = 9.5
LEADING = 13.5
# The drop from a clause heading's baseline to the top of its body, and the gap left
# under a clause before the next one starts.
HEADING_DROP = 5.0
CLAUSE_GAP = 19.0

# Where the clauses start on the pages after the first. On the first they start under
# the preamble, wherever the preamble ends.
NEXT_TOP = 782.0
# The lowest a clause or a block may reach before it moves to the next page.
BOTTOM = 100.0
# The room the signature block needs under the last clause.
SIGNATURE_HEIGHT = 250.0

# The colour and width every field's border is stroked in.
BORDER = hqf_pdf.Rgb.gray(0.55)
BORDER_WIDTH = 0.75

# The width of a signature column, and the left edge of the second one.
COLUMN_WIDTH = 224.0
COLUMN_TWO = 315.276

# What the agreement is called.
NUMBER = "HQF-2026-0087"

# The party that supplies the services and the party that buys them: the name, the two
# lines under it, and the tax number the third line carries.
PROVIDER_NAME = "HQF Development"
PROVIDER_LINES = (
    "42 lot les Genêts, 13480 Calas, France",
    "SIRET 752 492 777 00012 · APE 6202A",
)
PROVIDER_VAT = "FR59 752 492 777"
CLIENT_NAME = "Northwind Trading SARL"
CLIENT_LINES = (
    "18 quai du Port, 13002 Marseille, France",
    "RCS Marseille 812 665 431",
)
CLIENT_VAT = "FR41 812 665 431"

# Who signs, in the order their columns are drawn, and the name the column's fields
# answer to. A field name is what a form-filling program reads, not something the page
# draws, so it is the same in every language.
SIGNATORIES = [
    ("Olivier Pons", "Provider", LEFT),
    ("Claire Lemoine", "Client", COLUMN_TWO),
]


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

    What is not language stays out of it: the parties' names and addresses, their
    registration numbers, the agreement's number, the day rate and the names the form
    fields answer to are drawn from constants of their own and read the same in every
    language.
    """

    # The words the document's title is built from, and the word set large at the head
    # of the first page.
    title: str
    heading: str
    # What stands before the agreement's number.
    number_label: str
    # What stands before the day it was signed, and that day.
    signed_label: str
    agreement_date: str
    # What stands before the day it runs from, and that day.
    effective_label: str
    effective_date: str
    # The heading over each party.
    provider_heading: str
    client_heading: str
    # The word before a tax number, wherever one is set.
    vat: str
    # The sentence that binds the parties, cut where the two dates go into it.
    preamble_first: str
    preamble_second: str
    preamble_after: str
    # The clauses the agreement is made of: a heading, and the paragraph under it.
    clauses: tuple[tuple[str, str], ...]
    # The heading over the signature block, and the sentence under it, cut where the
    # three things a reader fills in are named.
    signatures: str
    note_before: str
    note_marked: str
    note_after: str
    # The heading over each signature column, and what each signatory does.
    signatory_headings: tuple[str, str]
    signatory_roles: tuple[str, str]
    # What stands under each of a column's three boxes.
    signature_label: str
    printed_name_label: str
    date_label: str
    # The words the page number is built from, at the foot of every page.
    page_before: str
    page_word: str
    page_of: str


# The agreement in English.
ENGLISH = Words(
    title="Services agreement",
    heading="AGREEMENT",
    number_label="Agreement No.",
    signed_label="Signed",
    agreement_date="20 July 2026",
    effective_label="Effective",
    effective_date="1 September 2026",
    provider_heading="SERVICE PROVIDER",
    client_heading="CLIENT",
    vat="VAT",
    preamble_first="This agreement is made on",
    preamble_second="between the parties named above, and takes effect on",
    preamble_after=(
        ". It sets out the terms on which the Provider supplies software "
        "development and support services to the Client. Each clause below binds "
        "both parties from the date the last of them signs."
    ),
    clauses=(
        (
            "Scope of services",
            "The Provider designs, builds and maintains the software described in the "
            "statement of work annexed to this agreement, together with any further work "
            "the parties agree to in writing. A change of scope is agreed in writing "
            "before it is carried out, and the Provider may decline a change that cannot "
            "be delivered within the agreed term.",
        ),
        (
            "Term and commencement",
            "This agreement begins on the effective date shown above and runs for twelve "
            "months. It renews for successive periods of twelve months unless either "
            "party gives written notice of at least sixty days before the end of the "
            "period then running.",
        ),
        (
            "Fees and payment",
            "The Client pays the Provider 620.00 EUR for each day of work, before value "
            "added tax. The Provider invoices monthly in arrears and each invoice falls "
            "due thirty days after its issue date. A sum still unpaid on the due date "
            "carries interest at the statutory rate, and no reminder has to be sent for "
            "that interest to run.",
        ),
        (
            "Intellectual property",
            "Once an invoice has been paid in full, the Client owns the source code and "
            "the documents written for it under this agreement. The Provider keeps the "
            "tools, libraries and generic components it brought to the work or wrote for "
            "general use, and grants the Client a perpetual non-exclusive licence to use "
            "them as part of the delivered software.",
        ),
        (
            "Confidentiality",
            "Each party keeps in confidence what it learns of the other's business, "
            "customers and technology, and uses it only to perform this agreement. That "
            "duty outlives the agreement by five years. It does not cover what is already "
            "public through no fault of the party that received it, nor what a court or a "
            "regulator requires be disclosed.",
        ),
        (
            "Warranties and liability",
            "The Provider warrants that the work is performed with the skill and care of "
            "a competent professional, and corrects at its own cost any defect reported "
            "within ninety days of delivery. Neither party is liable for loss of profit, "
            "loss of data or any indirect loss, and the liability of each party under "
            "this agreement is capped at the fees paid over the twelve months before the "
            "claim arose.",
        ),
        (
            "Termination",
            "Either party may end this agreement on sixty days' written notice, and "
            "either may end it at once if the other commits a material breach that is not "
            "put right within thirty days of being asked in writing to put it right. On "
            "termination the Client pays for the work done up to that date and the "
            "Provider hands over the deliverables in the state they are then in.",
        ),
        (
            "Notices",
            "Notice under this agreement is given in writing to the address shown for the "
            "party at the head of this document, and takes effect on the day it is "
            "delivered. A party that moves tells the other within fifteen days.",
        ),
        (
            "Entire agreement and governing law",
            "This document, with the statement of work annexed to it, is the whole of "
            "what the parties have agreed on this subject and replaces every earlier "
            "understanding of it. The agreement is governed by French law, and any "
            "dispute the parties cannot settle between them goes to the courts of "
            "Aix-en-Provence, France.",
        ),
    ),
    signatures="SIGNATURES",
    note_before="Each party signs in the boxes below: a ",
    note_marked="signature, a printed name and a date",
    note_after=(
        ", filled in from a reader. The agreement takes effect on the later of "
        "the two dates written here, and a copy of the signed document goes to "
        "each party."
    ),
    signatory_headings=("FOR THE PROVIDER", "FOR THE CLIENT"),
    signatory_roles=("Director", "Managing Director"),
    signature_label="Signature",
    printed_name_label="Printed name",
    date_label="Date (DD/MM/YYYY)",
    page_before="Agreement",
    page_word="page",
    page_of="of",
)

# The agreement in French.
FRENCH = Words(
    title="Contrat de prestation de services",
    heading="CONTRAT",
    number_label="Contrat n°",
    signed_label="Signé le",
    agreement_date="20 juillet 2026",
    effective_label="Prise d'effet",
    effective_date="1er septembre 2026",
    provider_heading="PRESTATAIRE",
    client_heading="CLIENT",
    vat="TVA",
    preamble_first="Le présent contrat est conclu le",
    preamble_second="entre les parties désignées ci-dessus et prend effet le",
    preamble_after=(
        ". Il fixe les conditions dans lesquelles le Prestataire fournit au Client "
        "des prestations de développement et de maintenance logicielle. Chaque "
        "article ci-dessous engage les deux parties à compter de la date de la "
        "dernière signature."
    ),
    clauses=(
        (
            "Objet du contrat",
            "Le Prestataire conçoit, développe et maintient le logiciel décrit dans le "
            "cahier des charges annexé au présent contrat, ainsi que toute prestation "
            "complémentaire convenue par écrit entre les parties. Toute modification de "
            "l'objet est convenue par écrit avant son exécution, et le Prestataire peut "
            "refuser une modification qui ne peut être livrée dans le délai convenu.",
        ),
        (
            "Durée et prise d'effet",
            "Le présent contrat prend effet à la date indiquée ci-dessus et court pour "
            "douze mois. Il se renouvelle par périodes successives de douze mois, sauf "
            "dénonciation écrite de l'une des parties adressée au moins soixante jours "
            "avant le terme de la période en cours.",
        ),
        (
            "Rémunération",
            "Le Client verse au Prestataire 620.00 EUR par journée de travail, hors "
            "taxes. Le Prestataire facture chaque mois à terme échu et chaque facture "
            "est payable trente jours après sa date d'émission. Toute somme impayée à "
            "l'échéance porte intérêt au taux légal, sans qu'une mise en demeure soit "
            "nécessaire.",
        ),
        (
            "Propriété intellectuelle",
            "Dès le paiement intégral de la facture correspondante, le Client devient "
            "propriétaire du code source et des documents rédigés pour lui au titre du "
            "présent contrat. Le Prestataire conserve les outils, bibliothèques et "
            "composants génériques qu'il a apportés ou écrits pour un usage général, et "
            "concède au Client une licence perpétuelle et non exclusive de les utiliser "
            "au sein du logiciel livré.",
        ),
        (
            "Confidentialité",
            "Chaque partie tient confidentiel ce qu'elle apprend de l'activité, de la "
            "clientèle et de la technologie de l'autre, et ne l'utilise que pour "
            "l'exécution du présent contrat. Cette obligation survit cinq ans au "
            "contrat. Elle ne couvre ni ce qui est déjà public sans faute de la partie "
            "qui l'a reçu, ni ce dont un tribunal ou une autorité exige la "
            "communication.",
        ),
        (
            "Garanties et responsabilité",
            "Le Prestataire garantit que les travaux sont exécutés avec le soin et la "
            "compétence d'un professionnel avisé, et corrige à ses frais tout défaut "
            "signalé dans les quatre-vingt-dix jours suivant la livraison. Aucune des "
            "parties ne répond de la perte d'exploitation, de la perte de données ni "
            "d'aucun dommage indirect, et la responsabilité de chaque partie au titre "
            "du présent contrat est plafonnée aux sommes versées au cours des douze "
            "mois précédant le fait générateur.",
        ),
        (
            "Résiliation",
            "Chaque partie peut résilier le présent contrat moyennant un préavis écrit "
            "de soixante jours, et chacune peut y mettre fin immédiatement si l'autre "
            "commet un manquement grave auquel elle ne remédie pas dans les trente "
            "jours d'une mise en demeure écrite. À la résiliation, le Client règle les "
            "travaux exécutés jusqu'à cette date et le Prestataire remet les livrables "
            "en l'état où ils se trouvent.",
        ),
        (
            "Notifications",
            "Toute notification au titre du présent contrat est faite par écrit à "
            "l'adresse indiquée pour la partie en tête du présent document, et prend "
            "effet le jour de sa remise. La partie qui change d'adresse en informe "
            "l'autre dans les quinze jours.",
        ),
        (
            "Intégralité du contrat et loi applicable",
            "Le présent document, avec le cahier des charges qui lui est annexé, "
            "constitue l'intégralité de l'accord des parties sur son objet et remplace "
            "tout accord antérieur portant sur celui-ci. Le contrat est régi par la loi "
            "française, et tout litige que les parties ne peuvent régler entre elles "
            "relève des tribunaux d'Aix-en-Provence, France.",
        ),
    ),
    signatures="SIGNATURES",
    note_before="Chaque partie signe dans les cases ci-dessous : une ",
    note_marked="signature, un nom en clair et une date",
    note_after=(
        ", saisis depuis un lecteur. Le contrat prend effet à la plus tardive des "
        "deux dates portées ici, et un exemplaire signé est remis à chaque partie."
    ),
    signatory_headings=("POUR LE PRESTATAIRE", "POUR LE CLIENT"),
    signatory_roles=("Gérant", "Directrice générale"),
    signature_label="Signature",
    printed_name_label="Nom en clair",
    date_label="Date (JJ/MM/AAAA)",
    page_before="Contrat",
    page_word="page",
    page_of="sur",
)


# 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 text(
    content: hqf_pdf.Content,
    font: hqf_pdf.FontHandle,
    size: float,
    x: float,
    y: float,
    color: hqf_pdf.Rgb,
    s: str,
) -> None:
    """Draws a line of text, left-aligned, in a colour of its own."""
    content.set_fill(color)
    content.draw_text(font, size, x, y, s)


def text_right(
    content: hqf_pdf.Content,
    font: hqf_pdf.FontHandle,
    size: float,
    right: float,
    y: float,
    color: hqf_pdf.Rgb,
    s: str,
) -> None:
    """Draws a line of text whose right edge sits at ``right``."""
    text(content, font, size, right - font.measure(s, size), y, color, s)


def rule(content: hqf_pdf.Content, y: float, width: float, color: hqf_pdf.Rgb) -> None:
    """A horizontal rule from ``LEFT`` to ``RIGHT`` at height ``y``."""
    content.set_stroke(color)
    content.set_line_width(width)
    content.move_to(LEFT, y)
    content.line_to(RIGHT, y)
    content.stroke()


def letterhead(
    content: hqf_pdf.Content,
    font: hqf_pdf.FontHandle,
    logo: hqf_pdf.ImageHandle,
    words: Words,
) -> None:
    """Draws the letterhead: the mark, the provider's name, what the page is, and the
    agreement's number and dates, closed off by a rule.
    """
    logo_w, logo_h = logo.fit_within(84.0, 84.0)
    content.draw_image(logo, LEFT, 826.5 - logo_h, logo_w, logo_h)

    text(content, font, 8.5, LEFT, 768.0, MUTED, "42 lot les Genêts, 13480 Calas, France")
    text(content, font, 8.5, LEFT, 756.0, MUTED, "www.hqf.fr")

    text_right(content, font, 26.0, RIGHT, 796.0, ACCENT, words.heading)
    text_right(content, font, 9.5, RIGHT, 770.0, INK, f"{words.number_label}  {NUMBER}")
    text_right(
        content,
        font,
        9.5,
        RIGHT,
        757.0,
        MUTED,
        f"{words.signed_label}  {words.agreement_date}",
    )
    text_right(
        content,
        font,
        9.5,
        RIGHT,
        744.0,
        MUTED,
        f"{words.effective_label}  {words.effective_date}",
    )

    rule(content, 730.0, 1.2, ACCENT)


def parties(content: hqf_pdf.Content, font: hqf_pdf.FontHandle, words: Words) -> float:
    """Draws the two parties, side by side, and the sentence that binds them. Answers
    with the height the first clause may start at, which is under however many lines
    the sentence came to.
    """
    columns = (
        (words.provider_heading, PROVIDER_NAME, PROVIDER_LINES, PROVIDER_VAT, LEFT),
        (words.client_heading, CLIENT_NAME, CLIENT_LINES, CLIENT_VAT, COLUMN_TWO),
    )

    for heading, name, lines, vat, x in columns:
        text(content, font, 8.0, x, 712.0, MUTED, heading)
        text(content, font, 11.5, x, 696.0, INK, name)
        y = 681.0
        for line in lines:
            text(content, font, 8.5, x, y, MUTED, line)
            y -= 11.5
        text(content, font, 8.5, x, y, MUTED, f"{words.vat} {vat}")

    # The date the agreement runs from is underlined mid-sentence, which is what rich
    # text is for.
    body = hqf_pdf.Style(font, BODY_SIZE)
    marked = hqf_pdf.Style(font, BODY_SIZE, underline=True)
    preamble = (
        hqf_pdf.RichText(align=hqf_pdf.Align.Justify)
        .push(
            f"{words.preamble_first} {words.agreement_date} {words.preamble_second} ",
            body,
        )
        .push(words.effective_date, marked)
        .push(words.preamble_after, body)
    )

    preamble_top = 632.0
    lines = preamble.break_lines(BODY_WIDTH)
    content.set_fill(INK)
    preamble.draw(content, lines, LEFT, preamble_top, BODY_WIDTH)

    return preamble_top - hqf_pdf.RichText.height(lines) - 24.0


def signatures(
    content: hqf_pdf.Content, font: hqf_pdf.FontHandle, words: Words, top: float
) -> list[object]:
    """Draws the signature block from ``top`` down: a heading, a sentence saying what a
    reader does with it, and a column for each party carrying a signature field, a box
    for the printed name and a box for the date. Answers with the fields, for the page
    they land on.
    """
    text(content, font, 10.5, LEFT, top, ACCENT, words.signatures)

    body = hqf_pdf.Style(font, 9.0)
    marked = hqf_pdf.Style(font, 9.0, underline=True)
    note = (
        hqf_pdf.RichText(align=hqf_pdf.Align.Justify)
        .push(words.note_before, body)
        .push(words.note_marked, marked)
        .push(words.note_after, body)
    )
    note_top = top - HEADING_DROP
    lines = note.break_lines(BODY_WIDTH)
    content.set_fill(INK)
    note.draw(content, lines, LEFT, note_top, BODY_WIDTH)

    fields: list[object] = []
    block_top = note_top - hqf_pdf.RichText.height(lines) - 28.0
    columns = zip(SIGNATORIES, words.signatory_headings, words.signatory_roles)
    for (name, field_name, x), heading, role in columns:
        text(content, font, 8.0, x, block_top, MUTED, heading)
        text(content, font, 9.5, x, block_top - 14.0, INK, f"{name}{role}")

        signature_bottom = block_top - 84.0
        fields.append(
            hqf_pdf.SignatureField(
                f"{field_name} signature",
                x,
                signature_bottom,
                COLUMN_WIDTH,
                48.0,
                border_color=BORDER,
                border_width=BORDER_WIDTH,
            )
        )
        text(content, font, 7.5, x, signature_bottom - 10.0, MUTED, words.signature_label)

        # The printed name and the date are text fields, so what a reader types is read
        # back as text and not as a picture of a hand.
        for label, field_label, bottom in (
            (words.printed_name_label, "printed name", signature_bottom - 42.0),
            (words.date_label, "date", signature_bottom - 84.0),
        ):
            fields.append(
                hqf_pdf.FormField(
                    f"{field_name} {field_label}",
                    x,
                    bottom,
                    COLUMN_WIDTH,
                    18.0,
                    font,
                    10.0,
                    border_color=BORDER,
                    border_width=BORDER_WIDTH,
                )
            )
            text(content, font, 7.5, x, bottom - 10.0, MUTED, label)

    return fields


def footer(
    content: hqf_pdf.Content,
    font: hqf_pdf.FontHandle,
    words: Words,
    number: int,
    total: int,
) -> None:
    """Draws the foot of one page: a rule, what the law asks the provider to carry, and
    which page of how many this is.
    """
    rule(content, 78.0, 0.5, hqf_pdf.Rgb.gray(0.8))

    mentions = (
        "HQF Development · 42 lot les Genêts, 13480 Calas, France · "
        f"SIRET 752 492 777 00012 · APE 6202A · {words.vat} {PROVIDER_VAT}"
    )
    mentions_x = LEFT + (BODY_WIDTH - font.measure(mentions, 7.0)) / 2.0
    text(content, font, 7.0, mentions_x, 66.0, MUTED, mentions)

    label = (
        f"{words.page_before} {NUMBER}{words.page_word} {number} "
        f"{words.page_of} {total}"
    )
    label_x = LEFT + (BODY_WIDTH - font.measure(label, 7.0)) / 2.0
    text(content, font, 7.0, label_x, 55.0, MUTED, 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("contract.pdf", language)).stem)

    document = hqf_pdf.Document()
    document.set_license(_licence.licensed())
    document.set_info("Title", f"{words.title} {NUMBER}")

    font = document.add_font(hqf_pdf.Font.from_path(_out.font_path()))
    logo = document.add_image(hqf_pdf.Image.from_path(LOGO))

    content = hqf_pdf.Content()
    letterhead(content, font, logo, words)
    top = parties(content, font, words)

    # Every clause is measured before it is drawn, and one that would run past the foot
    # of the page starts the next one whole: a heading is never left behind on a page
    # away from the paragraph it introduces.
    finished: list[hqf_pdf.Content] = []
    flow = hqf_pdf.TextFlow(
        font, BODY_SIZE, align=hqf_pdf.Align.Justify, leading=LEADING
    )

    for index, (heading, clause) in enumerate(words.clauses):
        lines = flow.break_lines(clause, BODY_WIDTH)
        height = flow.height(lines)
        if top - (HEADING_DROP + height + CLAUSE_GAP) < BOTTOM:
            finished.append(content)
            content = hqf_pdf.Content()
            top = NEXT_TOP

        text(content, font, 10.5, LEFT, top, ACCENT, f"{index + 1}. {heading}")

        body_top = top - HEADING_DROP
        content.set_fill(INK)
        flow.draw(content, lines, LEFT, body_top, BODY_WIDTH)

        top = body_top - height - CLAUSE_GAP

    if top - SIGNATURE_HEIGHT < BOTTOM:
        finished.append(content)
        content = hqf_pdf.Content()
        top = NEXT_TOP
    fields = signatures(content, font, words, top - 10.0)
    finished.append(content)

    # The footer says which page of how many, so it is drawn once the count is known,
    # and the fields go on the page the signature block landed on.
    total = len(finished)
    for index, page_content in enumerate(finished):
        footer(page_content, font, words, index + 1, total)

        page = hqf_pdf.Page(PAGE_WIDTH, PAGE_HEIGHT)
        page.set_content(page_content)
        if index + 1 == total:
            for field in fields:
                if isinstance(field, hqf_pdf.SignatureField):
                    page.add_signature(field)
                else:
                    page.add_field(field)
        document.add_page(page)

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


if __name__ == "__main__":
    main()