reportlab-layout 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- reportlab_layout-1.0.0/.gitignore +18 -0
- reportlab_layout-1.0.0/CHANGELOG.md +72 -0
- reportlab_layout-1.0.0/LICENSE +21 -0
- reportlab_layout-1.0.0/PKG-INFO +242 -0
- reportlab_layout-1.0.0/README.md +209 -0
- reportlab_layout-1.0.0/docs/DOC.md +518 -0
- reportlab_layout-1.0.0/docs/img/ancrage-cap.png +0 -0
- reportlab_layout-1.0.0/docs/img/centrage.png +0 -0
- reportlab_layout-1.0.0/examples/attestation.py +85 -0
- reportlab_layout-1.0.0/pyproject.toml +93 -0
- reportlab_layout-1.0.0/src/reportlab_layout/__init__.py +69 -0
- reportlab_layout-1.0.0/src/reportlab_layout/boxes.py +30 -0
- reportlab_layout-1.0.0/src/reportlab_layout/colors.py +31 -0
- reportlab_layout-1.0.0/src/reportlab_layout/cursor.py +58 -0
- reportlab_layout-1.0.0/src/reportlab_layout/document.py +525 -0
- reportlab_layout-1.0.0/src/reportlab_layout/frames.py +36 -0
- reportlab_layout-1.0.0/src/reportlab_layout/geometry.py +138 -0
- reportlab_layout-1.0.0/src/reportlab_layout/images.py +79 -0
- reportlab_layout-1.0.0/src/reportlab_layout/metrics.py +204 -0
- reportlab_layout-1.0.0/src/reportlab_layout/numbering.py +69 -0
- reportlab_layout-1.0.0/src/reportlab_layout/py.typed +0 -0
- reportlab_layout-1.0.0/src/reportlab_layout/shapes.py +103 -0
- reportlab_layout-1.0.0/src/reportlab_layout/styles.py +84 -0
- reportlab_layout-1.0.0/src/reportlab_layout/text.py +80 -0
- reportlab_layout-1.0.0/tests/conftest.py +33 -0
- reportlab_layout-1.0.0/tests/test_cursor.py +36 -0
- reportlab_layout-1.0.0/tests/test_document.py +232 -0
- reportlab_layout-1.0.0/tests/test_geometry.py +58 -0
- reportlab_layout-1.0.0/tests/test_images.py +52 -0
- reportlab_layout-1.0.0/tests/test_metrics.py +129 -0
- reportlab_layout-1.0.0/tests/test_numbering.py +57 -0
- reportlab_layout-1.0.0/tests/test_shapes.py +56 -0
- reportlab_layout-1.0.0/tests/test_styles.py +49 -0
- reportlab_layout-1.0.0/tests/test_text.py +55 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
__pycache__/
|
|
2
|
+
*.py[cod]
|
|
3
|
+
.venv/
|
|
4
|
+
dist/
|
|
5
|
+
build/
|
|
6
|
+
*.egg-info/
|
|
7
|
+
.pytest_cache/
|
|
8
|
+
.ruff_cache/
|
|
9
|
+
.mypy_cache/
|
|
10
|
+
.coverage
|
|
11
|
+
htmlcov/
|
|
12
|
+
.DS_Store
|
|
13
|
+
*.pdf
|
|
14
|
+
!docs/**/*.pdf
|
|
15
|
+
logs/
|
|
16
|
+
|
|
17
|
+
# Ancien pdf_maker, conservé en local le temps de la relecture.
|
|
18
|
+
legacy/
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Journal des modifications
|
|
2
|
+
|
|
3
|
+
Le format suit [Keep a Changelog](https://keepachangelog.com/fr/1.1.0/) et le
|
|
4
|
+
versionnage [SemVer](https://semver.org/lang/fr/).
|
|
5
|
+
|
|
6
|
+
## [1.0.0] — 2026-08-25
|
|
7
|
+
|
|
8
|
+
Première version publiable. Le paquet `pdf_maker`, jusqu'ici module local, est
|
|
9
|
+
découpé, corrigé et renommé `reportlab_layout`. L'API passe en `snake_case` :
|
|
10
|
+
c'est une rupture assumée, la table de correspondance complète est dans
|
|
11
|
+
[`docs/DOC.md`](docs/DOC.md#migration-depuis-pdf_maker).
|
|
12
|
+
|
|
13
|
+
### Corrigé
|
|
14
|
+
|
|
15
|
+
- **Centrage vertical du texte.** L'ancrage se calculait par
|
|
16
|
+
`y - hauteur/2` avec `hauteur = ascendante - descendante`. La descendante
|
|
17
|
+
étant négative, le texte descendait de `|descendante|` de trop, soit environ
|
|
18
|
+
20 % du corps. Le décalage correct est `(ascendante + descendante)/2`.
|
|
19
|
+
- **Métriques ignorant l'échelle de tracé.** `drawStringCenterHV` et
|
|
20
|
+
`drawStringLeftCenterV` lisaient la hauteur au corps nominal du style, tandis
|
|
21
|
+
que le texte était dessiné à `fontSize × size` ; `drawStringCenterH` mesurait
|
|
22
|
+
de même la largeur au corps nominal. À `size=0.5`, le centrage était faux d'un
|
|
23
|
+
facteur deux, horizontalement comme verticalement. `TextMetrics` porte
|
|
24
|
+
désormais l'échelle et toutes les métriques en tiennent compte.
|
|
25
|
+
- **`before` interprété dans deux unités.** Il était converti en `unit` pour
|
|
26
|
+
positionner l'élément, mais ajouté en points au curseur, décalant tout ce qui
|
|
27
|
+
suivait.
|
|
28
|
+
- **`valign="top"` de `drawParagraph`.** La hauteur, en points, était ajoutée à
|
|
29
|
+
une ordonnée exprimée en `unit`. Remplacé par `valign` sur `draw()`, appliqué
|
|
30
|
+
en mode absolu.
|
|
31
|
+
- **`addstyle` de `drawTable`.** `TableStyle.add(liste)` empilait la liste comme
|
|
32
|
+
une commande unique, ce qui faisait échouer le rendu du tableau
|
|
33
|
+
(`ValueError: not enough values to unpack`). Les commandes supplémentaires
|
|
34
|
+
passent maintenant par `style=`.
|
|
35
|
+
- **`frameParagraph` levait `TypeError`.** Il transmettait `fontsize=` à
|
|
36
|
+
`getParagraph`, qui n'acceptait pas ce paramètre.
|
|
37
|
+
- **`get_image` sans largeur levait `TypeError`.** Les paramètres `hauteur` et
|
|
38
|
+
`scale` étaient déclarés mais ignorés. `ImageSpec.scaled` les gère et refuse
|
|
39
|
+
explicitement une demande sans contrainte.
|
|
40
|
+
- **`drawParagraph` rendait `(paragraphe, boîte)`**, incohérent avec les autres
|
|
41
|
+
méthodes de tracé, ce qui cassait les appels déballant quatre valeurs. Toutes
|
|
42
|
+
les méthodes rendent désormais une `Box`.
|
|
43
|
+
- **`NumberedCanvas` perdait la dernière page** quand il servait de canvas
|
|
44
|
+
autonome, sans `DocTemplate`.
|
|
45
|
+
- **États de canvas qui fuyaient.** Couleurs, épaisseur de trait et rotation
|
|
46
|
+
n'étaient pas restaurées après un tracé et contaminaient les suivants.
|
|
47
|
+
- **Collision de styles.** La feuille `styles` était un objet de module partagé :
|
|
48
|
+
deux modules définissant le même nom de style levaient `KeyError` à l'import.
|
|
49
|
+
- **Impressions sur la sortie standard.** `newFrame` et `drawFrame` écrivaient
|
|
50
|
+
systématiquement sur `stdout` ; le paquet passe par `logging`.
|
|
51
|
+
|
|
52
|
+
### Ajouté
|
|
53
|
+
|
|
54
|
+
- `Box`, quadruplet nommé rendu par tous les tracés.
|
|
55
|
+
- `TextMetrics`, métriques d'un style à une échelle donnée, avec les ancrages.
|
|
56
|
+
- `PageGeometry` et `Cursor`, seuls dépositaires des conversions de repères.
|
|
57
|
+
- `draw_string`, méthode unique remplaçant les quatre variantes de tracé de
|
|
58
|
+
chaîne, avec `halign`, `valign`, `angle`, `dx`, `dy`.
|
|
59
|
+
- Ancrage `valign="cap"`, centrage sur la boîte de capitale : sur une étiquette
|
|
60
|
+
courte, l'encre tombe au milieu de sa case à moins de 0,02 pt, et une rangée
|
|
61
|
+
d'étiquettes partage la même ligne de base quels que soient leurs jambages.
|
|
62
|
+
`TextMetrics.cap_height` s'appuie sur `STANDARD_CAP_HEIGHTS`, table reprise
|
|
63
|
+
des fichiers AFM d'Adobe, que reportlab n'expose pas ;
|
|
64
|
+
`scripts/cap_height_probe.py` la revérifie par rastérisation.
|
|
65
|
+
- `ImageSpec`, remplaçant le dictionnaire à clés françaises.
|
|
66
|
+
- `make_stylesheet()` et `add_style()`, pour des feuilles de styles isolées.
|
|
67
|
+
- Gestionnaire de contexte : la sortie n'est écrite que si le bloc réussit.
|
|
68
|
+
- `cursor_y`, `cursor_point`, `remaining_height`, `Cursor.fits`.
|
|
69
|
+
- Prise en charge des noms de format (`"A4"`, `"letter"`) et des couleurs en
|
|
70
|
+
hexadécimal ou en nom CSS.
|
|
71
|
+
- Annotations de types sur toute l'API publique, avec `py.typed`.
|
|
72
|
+
- 124 tests, exécutés sur Python 3.11 à 3.14 et reportlab 4.x et 5.x.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 antnardo
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: reportlab_layout
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Une couche de mise en page à curseur au-dessus de reportlab : le flux d'un document, le canvas toujours accessible.
|
|
5
|
+
Project-URL: Homepage, https://github.com/antnardo/reportlab_layout
|
|
6
|
+
Project-URL: Documentation, https://github.com/antnardo/reportlab_layout/blob/main/docs/DOC.md
|
|
7
|
+
Project-URL: Repository, https://github.com/antnardo/reportlab_layout
|
|
8
|
+
Project-URL: Issues, https://github.com/antnardo/reportlab_layout/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/antnardo/reportlab_layout/blob/main/CHANGELOG.md
|
|
10
|
+
Author-email: antnardo <antnardo@users.noreply.github.com>
|
|
11
|
+
License-Expression: MIT
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: canvas,layout,pdf,report,reportlab,typesetting
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Environment :: Console
|
|
16
|
+
Classifier: Intended Audience :: Developers
|
|
17
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
18
|
+
Classifier: Natural Language :: French
|
|
19
|
+
Classifier: Operating System :: OS Independent
|
|
20
|
+
Classifier: Programming Language :: Python :: 3
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
25
|
+
Classifier: Topic :: Printing
|
|
26
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
27
|
+
Classifier: Topic :: Text Processing :: Markup
|
|
28
|
+
Classifier: Typing :: Typed
|
|
29
|
+
Requires-Python: >=3.11
|
|
30
|
+
Requires-Dist: pillow>=10.0
|
|
31
|
+
Requires-Dist: reportlab>=4.0
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
|
|
34
|
+
# reportlab_layout
|
|
35
|
+
|
|
36
|
+
[](https://github.com/antnardo/reportlab_layout/actions/workflows/ci.yml)
|
|
37
|
+
[](https://pypi.org/project/reportlab_layout/)
|
|
38
|
+
[](https://pypi.org/project/reportlab_layout/)
|
|
39
|
+
[](https://github.com/antnardo/reportlab_layout/blob/main/LICENSE)
|
|
40
|
+
|
|
41
|
+
Un curseur qui descend dans la page, et le canvas reportlab resté sous la main.
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install reportlab_layout
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
from reportlab_layout import PDFMaker
|
|
49
|
+
|
|
50
|
+
with PDFMaker("bulletin.pdf", top=25, auto_page_break=True) as doc:
|
|
51
|
+
doc.set_footer(doc.make_paragraph("Établissement X", "Right"))
|
|
52
|
+
doc.draw_paragraph("Bulletin du 3e trimestre", "Heading1 Centered")
|
|
53
|
+
doc.draw_centered_line(wscale=0.4)
|
|
54
|
+
doc.add_space()
|
|
55
|
+
doc.draw_table([["Matière", "Note"], ["Mathématiques", "17"]])
|
|
56
|
+
|
|
57
|
+
# Et au même endroit, sans changer d'outil ni de fichier :
|
|
58
|
+
doc.draw_round_rect(doc.x_left, doc.cursor_y - 40, doc.content_width, 40, radius=6, fill="#eef4fb")
|
|
59
|
+
doc.draw_string("Mention", *doc.cursor_point, halign="left", valign="middle")
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Le problème que ça règle
|
|
63
|
+
|
|
64
|
+
reportlab offre deux façons de faire, et elles ne se mélangent pas bien.
|
|
65
|
+
|
|
66
|
+
Le **canvas** dessine où vous lui dites, en points, depuis le coin bas-gauche.
|
|
67
|
+
Précis, mais vous tenez vous-même la position courante, vous recalculez les
|
|
68
|
+
hauteurs à la main, et une ligne insérée au milieu décale tout le reste.
|
|
69
|
+
|
|
70
|
+
**Platypus** enchaîne des *flowables* dans des frames et gère le flux, la
|
|
71
|
+
pagination, la coupure des tableaux. Mais il prend la main sur la page : pour
|
|
72
|
+
tracer un cadre au point près derrière un paragraphe, il faut passer par les
|
|
73
|
+
rappels `onPage` d'un `BaseDocTemplate`, c'est-à-dire écrire la mise en page à
|
|
74
|
+
deux endroits différents et dans le désordre.
|
|
75
|
+
|
|
76
|
+
Ce paquet garde les flowables de platypus, mais les pose lui-même à la position
|
|
77
|
+
d'un curseur que vous pouvez lire, déplacer et interroger à tout moment. Le
|
|
78
|
+
canvas reste accessible par `doc.canvas`. Les deux modes se mélangent ligne à
|
|
79
|
+
ligne.
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
doc.draw_paragraph("Ce paragraphe descend le curseur.") # flux
|
|
83
|
+
y = doc.cursor_y # position courante
|
|
84
|
+
doc.draw_rect(doc.x_left, y - 30, doc.content_width, 30) # absolu
|
|
85
|
+
doc.advance(30) # le curseur suit
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Ce qu'il apporte
|
|
89
|
+
|
|
90
|
+
- **Un curseur explicite.** `doc.cursor.depth`, `doc.cursor_y`,
|
|
91
|
+
`doc.remaining_height`, `doc.advance(h)`. Aucune magie : vous savez toujours
|
|
92
|
+
où vous en êtes dans la page.
|
|
93
|
+
- **Une seule méthode pour le texte simple.** `draw_string(texte, x, y,
|
|
94
|
+
halign=…, valign=…, scale=…, angle=…)` remplace la demi-douzaine de variantes
|
|
95
|
+
qu'on finit toujours par écrire, et **le centrage vertical est juste** — voir
|
|
96
|
+
plus bas.
|
|
97
|
+
- **Un retour uniforme.** Chaque tracé rend une `Box(x, y, width, height)`, qui
|
|
98
|
+
se déballe comme un quadruplet. On sait ce qu'on vient de poser et où.
|
|
99
|
+
- **Les deux repères tenus séparément.** L'ordonnée canvas monte, la profondeur
|
|
100
|
+
du curseur descend ; `PageGeometry` est le seul endroit qui convertit.
|
|
101
|
+
- **Rien qui fuit.** Chaque tracé est encadré par `saveState`/`restoreState` :
|
|
102
|
+
une couleur ou une épaisseur de trait ne contamine pas le tracé suivant.
|
|
103
|
+
- **Deux dépendances**, reportlab et Pillow. Ni navigateur sans tête, ni LaTeX,
|
|
104
|
+
ni binaire système.
|
|
105
|
+
|
|
106
|
+
## Le centrage vertical, puisque c'est le point de départ
|
|
107
|
+
|
|
108
|
+
`canvas.drawString(x, y, texte)` place la **ligne de base** en `y`. Pour centrer
|
|
109
|
+
une chaîne dans une case, la formule qu'on écrit spontanément est
|
|
110
|
+
`y_centre - hauteur/2` avec `hauteur = ascendante - descendante`. Elle est
|
|
111
|
+
fausse : la descendante étant négative, elle descend le texte de `|descendante|`
|
|
112
|
+
de trop, soit environ 20 % du corps. Le bon décalage est
|
|
113
|
+
`y_centre - (ascendante + descendante)/2`.
|
|
114
|
+
|
|
115
|
+

|
|
116
|
+
|
|
117
|
+
Le filet rouge marque le milieu exact de la case. À gauche l'ancienne formule, à
|
|
118
|
+
droite `valign="middle"`. En bas la même chose au corps réduit de moitié : la
|
|
119
|
+
version fautive dérive aussi horizontalement, parce que la largeur était mesurée
|
|
120
|
+
au corps nominal alors que le texte était tracé plus petit.
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
doc.draw_string("Titre", x, y, halign="center", valign="middle", scale=0.5)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
`TextMetrics` porte la correction : toutes ses métriques tiennent compte de
|
|
127
|
+
l'échelle, et `baseline(y, valign)` rend directement la ligne de base voulue.
|
|
128
|
+
|
|
129
|
+
Reste une question que la formule ne tranche pas : centrer **quoi** ?
|
|
130
|
+
`valign="middle"` centre la boîte em, qui réserve la place des jambages même
|
|
131
|
+
quand la chaîne n'en a pas — un libellé comme `DS 3` s'en trouve haut d'environ
|
|
132
|
+
10 % du corps. `valign="cap"` centre la boîte de capitale : sur une étiquette
|
|
133
|
+
courte, l'encre tombe au milieu de sa case à moins d'un vingtième de point, et
|
|
134
|
+
une rangée d'étiquettes partage la même ligne de base qu'elles aient ou non des
|
|
135
|
+
jambages. C'est l'ancrage des cellules, bandeaux et badges. Le détail chiffré
|
|
136
|
+
est dans [`docs/DOC.md`](https://github.com/antnardo/reportlab_layout/blob/main/docs/DOC.md#middle-ou-cap-).
|
|
137
|
+
|
|
138
|
+
## Étude comparative
|
|
139
|
+
|
|
140
|
+
### Le paysage
|
|
141
|
+
|
|
142
|
+
| Outil | Modèle | Dép. système | Licence | Statut (août 2026) |
|
|
143
|
+
| --- | --- | --- | --- | --- |
|
|
144
|
+
| [reportlab](https://pypi.org/project/reportlab/) canvas | absolu, points | non | BSD | 5.0.1, très actif |
|
|
145
|
+
| reportlab platypus | flux, flowables | non | BSD | idem |
|
|
146
|
+
| **`reportlab_layout`** | **curseur + canvas** | **non** | **MIT** | **ce paquet** |
|
|
147
|
+
| [fpdf2](https://pypi.org/project/fpdf2/) | curseur natif | non | LGPL-3.0 | 2.8.8, très actif |
|
|
148
|
+
| [pdfino](https://pypi.org/project/pdfino/) | surcouche platypus | non | MIT | 0.1.0, 2023 |
|
|
149
|
+
| [pdfdocument](https://pypi.org/project/pdfdocument/) | surcouche platypus | non | BSD | 4.0.0, 2020 |
|
|
150
|
+
| [borb](https://pypi.org/project/borb/) | modèle objet | non | AGPL-3.0 | 3.0.9, actif |
|
|
151
|
+
| [WeasyPrint](https://pypi.org/project/weasyprint/) | HTML + CSS | non | BSD | 69.0, très actif |
|
|
152
|
+
| [pdfme](https://pypi.org/project/pdfme/) | document décrit en dict | non | MIT | 0.5.0 |
|
|
153
|
+
| [rst2pdf](https://pypi.org/project/rst2pdf/) | reStructuredText | non | MIT | 0.105, actif |
|
|
154
|
+
| pypdf / pikepdf | manipulation, pas génération | non | BSD / MPL | actifs |
|
|
155
|
+
| pylatex, Typst, wkhtmltopdf | balisage + moteur externe | **oui** | diverses | variés |
|
|
156
|
+
|
|
157
|
+
### Ce que fait vraiment la concurrence
|
|
158
|
+
|
|
159
|
+
**reportlab platypus** est le concurrent sérieux, et il fait beaucoup plus que
|
|
160
|
+
ce paquet : coupure de tableaux sur plusieurs pages, `KeepTogether`, table des
|
|
161
|
+
matières, signets, gabarits multi-frames. Si votre document est un rapport qui
|
|
162
|
+
coule tout seul du début à la fin, **utilisez platypus** : ce paquet n'a rien à
|
|
163
|
+
lui apporter. La différence tient à un point : dès qu'il faut alterner tracé
|
|
164
|
+
libre et flux dans le même geste, platypus vous impose de séparer le contenu
|
|
165
|
+
(la *story*) de la décoration (les rappels `onPage`). Ici, tout s'écrit dans
|
|
166
|
+
l'ordre où ça se dessine.
|
|
167
|
+
|
|
168
|
+
**fpdf2** est la comparaison la plus honnête, parce que c'est déjà un modèle à
|
|
169
|
+
curseur — `set_xy`, `cell`, `multi_cell`, `ln()` — et qu'il est excellent :
|
|
170
|
+
sous-ensemble HTML, tableaux, signature numérique, communauté vivante. Trois
|
|
171
|
+
différences concrètes. Sa licence est la LGPL-3.0, ce qui suffit à l'écarter
|
|
172
|
+
dans certains contextes ; reportlab est BSD et ce paquet MIT. Son moteur de
|
|
173
|
+
texte riche est un sous-ensemble HTML là où les `Paragraph` de reportlab
|
|
174
|
+
acceptent un balisage propre (`<super>`, `<font>`, indices, puces) et savent se
|
|
175
|
+
replier dans une largeur donnée. Et son curseur *est* l'API : on ne peut pas
|
|
176
|
+
poser un flowable reportlab au milieu. Si vous partez de zéro et que la LGPL ne
|
|
177
|
+
vous gêne pas, fpdf2 est un très bon choix.
|
|
178
|
+
|
|
179
|
+
**pdfino** et **pdfdocument** occupent exactement la même case : une surcouche
|
|
180
|
+
séquentielle au-dessus de platypus. Elles sont plus anciennes et plus simples —
|
|
181
|
+
`h1()`, `p()`, `table()` — et pdfino permet même d'insérer un flowable brut.
|
|
182
|
+
Ce qu'aucune des deux n'offre : la lecture et le déplacement explicites du
|
|
183
|
+
curseur, le placement absolu en points dans la même API, les métriques de
|
|
184
|
+
police corrigées, et une `Box` en retour de chaque tracé. Elles publient un
|
|
185
|
+
document ; celui-ci laisse construire une page. À noter aussi : pdfdocument n'a
|
|
186
|
+
pas publié depuis 2020 et pdfino en est à une 0.1.0 de 2023.
|
|
187
|
+
|
|
188
|
+
**borb** propose un modèle objet complet et lit aussi les PDF existants. C'est
|
|
189
|
+
plus ambitieux ; c'est aussi de l'AGPL-3.0, une licence qui contamine tout
|
|
190
|
+
service qui l'expose sur le réseau. À écarter d'emblée en contexte propriétaire.
|
|
191
|
+
|
|
192
|
+
**WeasyPrint** produit la plus belle typographie de la liste et gère le CSS
|
|
193
|
+
paginé (`@page`, en-têtes courants, coupures). Le prix à payer : il faut
|
|
194
|
+
exprimer la mise en page en HTML et CSS, et le rendu passe par un moteur de
|
|
195
|
+
rendu complet. Pour un document dont la géométrie se calcule — un planning
|
|
196
|
+
annuel, un tableau de créneaux, une grille de photos — décrire des coordonnées
|
|
197
|
+
en CSS est un détour. Pour une facture ou un rapport dont vous avez déjà le
|
|
198
|
+
gabarit web, WeasyPrint gagne largement.
|
|
199
|
+
|
|
200
|
+
**pdfme**, **rst2pdf**, **pylatex** partagent le même parti : décrire le
|
|
201
|
+
document dans un format (dict, reStructuredText, LaTeX) et laisser un moteur le
|
|
202
|
+
composer. Excellent quand le contenu est le sujet ; inadapté quand ce sont les
|
|
203
|
+
coordonnées qui le sont. pylatex et Typst ajoutent en plus une dépendance
|
|
204
|
+
système lourde.
|
|
205
|
+
|
|
206
|
+
**pypdf** et **pikepdf** ne génèrent pas de document : ils fusionnent, découpent
|
|
207
|
+
et signent. Complémentaires, pas concurrents.
|
|
208
|
+
|
|
209
|
+
### En résumé
|
|
210
|
+
|
|
211
|
+
| Votre besoin | Préférez |
|
|
212
|
+
| --- | --- |
|
|
213
|
+
| Un rapport qui coule tout seul, tableaux coupés sur plusieurs pages, table des matières | reportlab platypus |
|
|
214
|
+
| Le gabarit existe déjà en HTML/CSS | WeasyPrint |
|
|
215
|
+
| Un curseur simple, sans reportlab, et la LGPL ne pose pas problème | fpdf2 |
|
|
216
|
+
| Publier un document simple en quelques appels | pdfino |
|
|
217
|
+
| Découper, fusionner ou signer des PDF existants | pypdf, pikepdf |
|
|
218
|
+
| Le contenu est un texte balisé | rst2pdf, pylatex |
|
|
219
|
+
| **Une page dont la géométrie se calcule, en alternant flux et tracé au point près** | **`reportlab_layout`** |
|
|
220
|
+
|
|
221
|
+
Ce paquet occupe une case étroite : les documents où l'on connaît la formule qui
|
|
222
|
+
donne la position de chaque élément — plannings, grilles, calendriers, tableaux
|
|
223
|
+
de créneaux, planches — et où l'on veut quand même écrire du texte au fil de
|
|
224
|
+
l'eau sans compter les points à la main.
|
|
225
|
+
|
|
226
|
+
## Aller plus loin
|
|
227
|
+
|
|
228
|
+
- [`docs/DOC.md`](https://github.com/antnardo/reportlab_layout/blob/main/docs/DOC.md) — référence complète de l'API, repères,
|
|
229
|
+
positionnement, styles, métriques, frames, pagination.
|
|
230
|
+
- [`examples/attestation.py`](https://github.com/antnardo/reportlab_layout/blob/main/examples/attestation.py) — un document d'une page,
|
|
231
|
+
flux et tracé absolu mêlés.
|
|
232
|
+
- [`scripts/centering_proof.py`](https://github.com/antnardo/reportlab_layout/blob/main/scripts/centering_proof.py) — la preuve
|
|
233
|
+
chiffrée et visuelle du centrage.
|
|
234
|
+
|
|
235
|
+
## Compatibilité
|
|
236
|
+
|
|
237
|
+
Python 3.11 à 3.14, reportlab 4.x et 5.x. La matrice d'intégration continue
|
|
238
|
+
couvre chacune de ces huit combinaisons.
|
|
239
|
+
|
|
240
|
+
## Licence
|
|
241
|
+
|
|
242
|
+
MIT. reportlab est sous licence BSD, Pillow sous licence MIT-CMU.
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# reportlab_layout
|
|
2
|
+
|
|
3
|
+
[](https://github.com/antnardo/reportlab_layout/actions/workflows/ci.yml)
|
|
4
|
+
[](https://pypi.org/project/reportlab_layout/)
|
|
5
|
+
[](https://pypi.org/project/reportlab_layout/)
|
|
6
|
+
[](https://github.com/antnardo/reportlab_layout/blob/main/LICENSE)
|
|
7
|
+
|
|
8
|
+
Un curseur qui descend dans la page, et le canvas reportlab resté sous la main.
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
pip install reportlab_layout
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
```python
|
|
15
|
+
from reportlab_layout import PDFMaker
|
|
16
|
+
|
|
17
|
+
with PDFMaker("bulletin.pdf", top=25, auto_page_break=True) as doc:
|
|
18
|
+
doc.set_footer(doc.make_paragraph("Établissement X", "Right"))
|
|
19
|
+
doc.draw_paragraph("Bulletin du 3e trimestre", "Heading1 Centered")
|
|
20
|
+
doc.draw_centered_line(wscale=0.4)
|
|
21
|
+
doc.add_space()
|
|
22
|
+
doc.draw_table([["Matière", "Note"], ["Mathématiques", "17"]])
|
|
23
|
+
|
|
24
|
+
# Et au même endroit, sans changer d'outil ni de fichier :
|
|
25
|
+
doc.draw_round_rect(doc.x_left, doc.cursor_y - 40, doc.content_width, 40, radius=6, fill="#eef4fb")
|
|
26
|
+
doc.draw_string("Mention", *doc.cursor_point, halign="left", valign="middle")
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Le problème que ça règle
|
|
30
|
+
|
|
31
|
+
reportlab offre deux façons de faire, et elles ne se mélangent pas bien.
|
|
32
|
+
|
|
33
|
+
Le **canvas** dessine où vous lui dites, en points, depuis le coin bas-gauche.
|
|
34
|
+
Précis, mais vous tenez vous-même la position courante, vous recalculez les
|
|
35
|
+
hauteurs à la main, et une ligne insérée au milieu décale tout le reste.
|
|
36
|
+
|
|
37
|
+
**Platypus** enchaîne des *flowables* dans des frames et gère le flux, la
|
|
38
|
+
pagination, la coupure des tableaux. Mais il prend la main sur la page : pour
|
|
39
|
+
tracer un cadre au point près derrière un paragraphe, il faut passer par les
|
|
40
|
+
rappels `onPage` d'un `BaseDocTemplate`, c'est-à-dire écrire la mise en page à
|
|
41
|
+
deux endroits différents et dans le désordre.
|
|
42
|
+
|
|
43
|
+
Ce paquet garde les flowables de platypus, mais les pose lui-même à la position
|
|
44
|
+
d'un curseur que vous pouvez lire, déplacer et interroger à tout moment. Le
|
|
45
|
+
canvas reste accessible par `doc.canvas`. Les deux modes se mélangent ligne à
|
|
46
|
+
ligne.
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
doc.draw_paragraph("Ce paragraphe descend le curseur.") # flux
|
|
50
|
+
y = doc.cursor_y # position courante
|
|
51
|
+
doc.draw_rect(doc.x_left, y - 30, doc.content_width, 30) # absolu
|
|
52
|
+
doc.advance(30) # le curseur suit
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Ce qu'il apporte
|
|
56
|
+
|
|
57
|
+
- **Un curseur explicite.** `doc.cursor.depth`, `doc.cursor_y`,
|
|
58
|
+
`doc.remaining_height`, `doc.advance(h)`. Aucune magie : vous savez toujours
|
|
59
|
+
où vous en êtes dans la page.
|
|
60
|
+
- **Une seule méthode pour le texte simple.** `draw_string(texte, x, y,
|
|
61
|
+
halign=…, valign=…, scale=…, angle=…)` remplace la demi-douzaine de variantes
|
|
62
|
+
qu'on finit toujours par écrire, et **le centrage vertical est juste** — voir
|
|
63
|
+
plus bas.
|
|
64
|
+
- **Un retour uniforme.** Chaque tracé rend une `Box(x, y, width, height)`, qui
|
|
65
|
+
se déballe comme un quadruplet. On sait ce qu'on vient de poser et où.
|
|
66
|
+
- **Les deux repères tenus séparément.** L'ordonnée canvas monte, la profondeur
|
|
67
|
+
du curseur descend ; `PageGeometry` est le seul endroit qui convertit.
|
|
68
|
+
- **Rien qui fuit.** Chaque tracé est encadré par `saveState`/`restoreState` :
|
|
69
|
+
une couleur ou une épaisseur de trait ne contamine pas le tracé suivant.
|
|
70
|
+
- **Deux dépendances**, reportlab et Pillow. Ni navigateur sans tête, ni LaTeX,
|
|
71
|
+
ni binaire système.
|
|
72
|
+
|
|
73
|
+
## Le centrage vertical, puisque c'est le point de départ
|
|
74
|
+
|
|
75
|
+
`canvas.drawString(x, y, texte)` place la **ligne de base** en `y`. Pour centrer
|
|
76
|
+
une chaîne dans une case, la formule qu'on écrit spontanément est
|
|
77
|
+
`y_centre - hauteur/2` avec `hauteur = ascendante - descendante`. Elle est
|
|
78
|
+
fausse : la descendante étant négative, elle descend le texte de `|descendante|`
|
|
79
|
+
de trop, soit environ 20 % du corps. Le bon décalage est
|
|
80
|
+
`y_centre - (ascendante + descendante)/2`.
|
|
81
|
+
|
|
82
|
+

|
|
83
|
+
|
|
84
|
+
Le filet rouge marque le milieu exact de la case. À gauche l'ancienne formule, à
|
|
85
|
+
droite `valign="middle"`. En bas la même chose au corps réduit de moitié : la
|
|
86
|
+
version fautive dérive aussi horizontalement, parce que la largeur était mesurée
|
|
87
|
+
au corps nominal alors que le texte était tracé plus petit.
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
doc.draw_string("Titre", x, y, halign="center", valign="middle", scale=0.5)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
`TextMetrics` porte la correction : toutes ses métriques tiennent compte de
|
|
94
|
+
l'échelle, et `baseline(y, valign)` rend directement la ligne de base voulue.
|
|
95
|
+
|
|
96
|
+
Reste une question que la formule ne tranche pas : centrer **quoi** ?
|
|
97
|
+
`valign="middle"` centre la boîte em, qui réserve la place des jambages même
|
|
98
|
+
quand la chaîne n'en a pas — un libellé comme `DS 3` s'en trouve haut d'environ
|
|
99
|
+
10 % du corps. `valign="cap"` centre la boîte de capitale : sur une étiquette
|
|
100
|
+
courte, l'encre tombe au milieu de sa case à moins d'un vingtième de point, et
|
|
101
|
+
une rangée d'étiquettes partage la même ligne de base qu'elles aient ou non des
|
|
102
|
+
jambages. C'est l'ancrage des cellules, bandeaux et badges. Le détail chiffré
|
|
103
|
+
est dans [`docs/DOC.md`](https://github.com/antnardo/reportlab_layout/blob/main/docs/DOC.md#middle-ou-cap-).
|
|
104
|
+
|
|
105
|
+
## Étude comparative
|
|
106
|
+
|
|
107
|
+
### Le paysage
|
|
108
|
+
|
|
109
|
+
| Outil | Modèle | Dép. système | Licence | Statut (août 2026) |
|
|
110
|
+
| --- | --- | --- | --- | --- |
|
|
111
|
+
| [reportlab](https://pypi.org/project/reportlab/) canvas | absolu, points | non | BSD | 5.0.1, très actif |
|
|
112
|
+
| reportlab platypus | flux, flowables | non | BSD | idem |
|
|
113
|
+
| **`reportlab_layout`** | **curseur + canvas** | **non** | **MIT** | **ce paquet** |
|
|
114
|
+
| [fpdf2](https://pypi.org/project/fpdf2/) | curseur natif | non | LGPL-3.0 | 2.8.8, très actif |
|
|
115
|
+
| [pdfino](https://pypi.org/project/pdfino/) | surcouche platypus | non | MIT | 0.1.0, 2023 |
|
|
116
|
+
| [pdfdocument](https://pypi.org/project/pdfdocument/) | surcouche platypus | non | BSD | 4.0.0, 2020 |
|
|
117
|
+
| [borb](https://pypi.org/project/borb/) | modèle objet | non | AGPL-3.0 | 3.0.9, actif |
|
|
118
|
+
| [WeasyPrint](https://pypi.org/project/weasyprint/) | HTML + CSS | non | BSD | 69.0, très actif |
|
|
119
|
+
| [pdfme](https://pypi.org/project/pdfme/) | document décrit en dict | non | MIT | 0.5.0 |
|
|
120
|
+
| [rst2pdf](https://pypi.org/project/rst2pdf/) | reStructuredText | non | MIT | 0.105, actif |
|
|
121
|
+
| pypdf / pikepdf | manipulation, pas génération | non | BSD / MPL | actifs |
|
|
122
|
+
| pylatex, Typst, wkhtmltopdf | balisage + moteur externe | **oui** | diverses | variés |
|
|
123
|
+
|
|
124
|
+
### Ce que fait vraiment la concurrence
|
|
125
|
+
|
|
126
|
+
**reportlab platypus** est le concurrent sérieux, et il fait beaucoup plus que
|
|
127
|
+
ce paquet : coupure de tableaux sur plusieurs pages, `KeepTogether`, table des
|
|
128
|
+
matières, signets, gabarits multi-frames. Si votre document est un rapport qui
|
|
129
|
+
coule tout seul du début à la fin, **utilisez platypus** : ce paquet n'a rien à
|
|
130
|
+
lui apporter. La différence tient à un point : dès qu'il faut alterner tracé
|
|
131
|
+
libre et flux dans le même geste, platypus vous impose de séparer le contenu
|
|
132
|
+
(la *story*) de la décoration (les rappels `onPage`). Ici, tout s'écrit dans
|
|
133
|
+
l'ordre où ça se dessine.
|
|
134
|
+
|
|
135
|
+
**fpdf2** est la comparaison la plus honnête, parce que c'est déjà un modèle à
|
|
136
|
+
curseur — `set_xy`, `cell`, `multi_cell`, `ln()` — et qu'il est excellent :
|
|
137
|
+
sous-ensemble HTML, tableaux, signature numérique, communauté vivante. Trois
|
|
138
|
+
différences concrètes. Sa licence est la LGPL-3.0, ce qui suffit à l'écarter
|
|
139
|
+
dans certains contextes ; reportlab est BSD et ce paquet MIT. Son moteur de
|
|
140
|
+
texte riche est un sous-ensemble HTML là où les `Paragraph` de reportlab
|
|
141
|
+
acceptent un balisage propre (`<super>`, `<font>`, indices, puces) et savent se
|
|
142
|
+
replier dans une largeur donnée. Et son curseur *est* l'API : on ne peut pas
|
|
143
|
+
poser un flowable reportlab au milieu. Si vous partez de zéro et que la LGPL ne
|
|
144
|
+
vous gêne pas, fpdf2 est un très bon choix.
|
|
145
|
+
|
|
146
|
+
**pdfino** et **pdfdocument** occupent exactement la même case : une surcouche
|
|
147
|
+
séquentielle au-dessus de platypus. Elles sont plus anciennes et plus simples —
|
|
148
|
+
`h1()`, `p()`, `table()` — et pdfino permet même d'insérer un flowable brut.
|
|
149
|
+
Ce qu'aucune des deux n'offre : la lecture et le déplacement explicites du
|
|
150
|
+
curseur, le placement absolu en points dans la même API, les métriques de
|
|
151
|
+
police corrigées, et une `Box` en retour de chaque tracé. Elles publient un
|
|
152
|
+
document ; celui-ci laisse construire une page. À noter aussi : pdfdocument n'a
|
|
153
|
+
pas publié depuis 2020 et pdfino en est à une 0.1.0 de 2023.
|
|
154
|
+
|
|
155
|
+
**borb** propose un modèle objet complet et lit aussi les PDF existants. C'est
|
|
156
|
+
plus ambitieux ; c'est aussi de l'AGPL-3.0, une licence qui contamine tout
|
|
157
|
+
service qui l'expose sur le réseau. À écarter d'emblée en contexte propriétaire.
|
|
158
|
+
|
|
159
|
+
**WeasyPrint** produit la plus belle typographie de la liste et gère le CSS
|
|
160
|
+
paginé (`@page`, en-têtes courants, coupures). Le prix à payer : il faut
|
|
161
|
+
exprimer la mise en page en HTML et CSS, et le rendu passe par un moteur de
|
|
162
|
+
rendu complet. Pour un document dont la géométrie se calcule — un planning
|
|
163
|
+
annuel, un tableau de créneaux, une grille de photos — décrire des coordonnées
|
|
164
|
+
en CSS est un détour. Pour une facture ou un rapport dont vous avez déjà le
|
|
165
|
+
gabarit web, WeasyPrint gagne largement.
|
|
166
|
+
|
|
167
|
+
**pdfme**, **rst2pdf**, **pylatex** partagent le même parti : décrire le
|
|
168
|
+
document dans un format (dict, reStructuredText, LaTeX) et laisser un moteur le
|
|
169
|
+
composer. Excellent quand le contenu est le sujet ; inadapté quand ce sont les
|
|
170
|
+
coordonnées qui le sont. pylatex et Typst ajoutent en plus une dépendance
|
|
171
|
+
système lourde.
|
|
172
|
+
|
|
173
|
+
**pypdf** et **pikepdf** ne génèrent pas de document : ils fusionnent, découpent
|
|
174
|
+
et signent. Complémentaires, pas concurrents.
|
|
175
|
+
|
|
176
|
+
### En résumé
|
|
177
|
+
|
|
178
|
+
| Votre besoin | Préférez |
|
|
179
|
+
| --- | --- |
|
|
180
|
+
| Un rapport qui coule tout seul, tableaux coupés sur plusieurs pages, table des matières | reportlab platypus |
|
|
181
|
+
| Le gabarit existe déjà en HTML/CSS | WeasyPrint |
|
|
182
|
+
| Un curseur simple, sans reportlab, et la LGPL ne pose pas problème | fpdf2 |
|
|
183
|
+
| Publier un document simple en quelques appels | pdfino |
|
|
184
|
+
| Découper, fusionner ou signer des PDF existants | pypdf, pikepdf |
|
|
185
|
+
| Le contenu est un texte balisé | rst2pdf, pylatex |
|
|
186
|
+
| **Une page dont la géométrie se calcule, en alternant flux et tracé au point près** | **`reportlab_layout`** |
|
|
187
|
+
|
|
188
|
+
Ce paquet occupe une case étroite : les documents où l'on connaît la formule qui
|
|
189
|
+
donne la position de chaque élément — plannings, grilles, calendriers, tableaux
|
|
190
|
+
de créneaux, planches — et où l'on veut quand même écrire du texte au fil de
|
|
191
|
+
l'eau sans compter les points à la main.
|
|
192
|
+
|
|
193
|
+
## Aller plus loin
|
|
194
|
+
|
|
195
|
+
- [`docs/DOC.md`](https://github.com/antnardo/reportlab_layout/blob/main/docs/DOC.md) — référence complète de l'API, repères,
|
|
196
|
+
positionnement, styles, métriques, frames, pagination.
|
|
197
|
+
- [`examples/attestation.py`](https://github.com/antnardo/reportlab_layout/blob/main/examples/attestation.py) — un document d'une page,
|
|
198
|
+
flux et tracé absolu mêlés.
|
|
199
|
+
- [`scripts/centering_proof.py`](https://github.com/antnardo/reportlab_layout/blob/main/scripts/centering_proof.py) — la preuve
|
|
200
|
+
chiffrée et visuelle du centrage.
|
|
201
|
+
|
|
202
|
+
## Compatibilité
|
|
203
|
+
|
|
204
|
+
Python 3.11 à 3.14, reportlab 4.x et 5.x. La matrice d'intégration continue
|
|
205
|
+
couvre chacune de ces huit combinaisons.
|
|
206
|
+
|
|
207
|
+
## Licence
|
|
208
|
+
|
|
209
|
+
MIT. reportlab est sous licence BSD, Pillow sous licence MIT-CMU.
|