report-to-pdf — Rapport Markdown en PDF élégant
Générateur déterministe et scripté de PDF à partir de Markdown. Il analyse un sous-ensemble restreint de Markdown « rapport » avec un petit analyseur de blocs artisanal (pas de bibliothèque Markdown externe) et le met en page via fpdf2 avec les mêmes conventions visuelles que le générateur build-user-guide.py du module Magento de ColeteOnline — page de titre, en-tête/pied de page, table des matières, barres d'accent pour les titres de section, blocs de code ombrés, tableaux avec bordures et rayures. La logique de mise en page ne change jamais entre les exécutions sur la même entrée : même rapport en entrée, même PDF en sortie, à chaque fois — pas de LLM dans la boucle de rendu.
Quand l'utiliser
- « Transforme cette revue Markdown en PDF pour le mentor. »
- « Exporte mon rapport en PDF soigné. »
- « Je veux que cela ait le même aspect à chaque fois, pas reformaté par un LLM à chaque exécution. »
- Tout fichier Markdown
.mdrapport/synthèse contenant des titres, listes, blocs de code ou tableaux et nécessitant une version PDF de qualité client/mentor/marque.
Comment exécuter
python3 <skill-dir>/scripts/build_report_pdf.py <input.md> [options]
(<skill-dir> est le répertoire de base de ce skill, le dossier contenant ce SKILL.md.)
Options :
-o / --output <chemin>— chemin du PDF de sortie (par défaut :<input>.pdfà côté du fichier source).--logo <chemin>— image de logo optionnelle (PNG/WebP/JPEG, tout ce que Pillow peut ouvrir) sur la page de titre. Répétez le flag pour plusieurs logos, dessinés côte à côte sur une seule ligne (également définissable via frontmatterlogo: a.png, b.png, chemins relatifs au fichier.md).--title,--subtitle,--version,--vendor,--author— remplace les valeurs correspondantes du frontmatter (voir ci-dessous) ; les flags CLI l'emportent.--cover center|left— mise en page de la couverture. Tout le bloc de titre (logo, titre, sous-titre, auteur, version) est centré verticalement dans les deux modes ;center(par défaut) le centre aussi horizontalement,leftl'aligne à gauche. Également définissable via frontmattercover:.--no-header— supprime l'en-tête courant et l'espace qu'il réserve, de sorte que le contenu de chaque page commence à la même marge supérieure (également définissable via frontmatterheader: false).--accent "R,V,B"— remplace la couleur d'accent utilisée pour la barre supérieure de la page de titre et les barres d'accent des titres de section (par défaut220,50,50, rouge ColeteOnline).--dark— rend avec le thème de couleur sombre au lieu du thème clair par défaut (également définissable via frontmattertheme: dark;--darkl'emporte si les deux sont présents). Voir « Thème sombre » ci-dessous.
Le script installe automatiquement fpdf2, Pillow et Pygments via pip install --user si une importation échoue, en miroir de l'auto-installation Pillow du script de référence. Les trois ont été vérifiés importables et le script a été exécuté de bout en bout pendant le développement (voir Tests ci-dessous).
Conventions Markdown attendues
Bloc de frontmatter optionnel tout en haut du fichier :
---
title: Titre de mon rapport
subtitle: Un sous-titre d'une ligne
version: 1.0
vendor: ColeteOnline SRL
accent: 220,50,50
---
Toutes les clés sont optionnelles et se rabattent de manière raisonnable :
title— se rabat sur la première# H1trouvée dans le corps, puis sur le nom du fichier d'entrée.subtitle— se rabat sur une chaîne vide (omis de la page de titre).version— se rabat sur la date du jour (YYYY-MM-DD) ; définissezversion: nonepour masquer la ligne de version.vendor— se rabat sur une chaîne vide (la ligne de copyright est omise du pied de page de la page de titre si non définie).author— nom(s) de l'auteur, affiché(s) sur la couverture sous le sous-titre (par ex.author: Jane Doe, John Smith). Omis si non défini.cover—center(par défaut) ouleft; voir--coverci-dessus.header—falsesupprime l'en-tête courant ; voir--no-header.justify—truejustifie le corps du texte, les titres et les cellules de tableau ; la valeur par défaut est alignée à gauche (le texte justifié étire les espaces entre les mots sur les lignes courtes, par exemple un titre sur deux lignes).accent— se rabat sur le rouge de marque ColeteOnline220,50,50.
Corps Markdown, et comment chaque élément est rendu :
| Markdown | Rendu |
|---|---|
# Titre | Le tout premier # dans le fichier est le titre de la page de titre uniquement (consommé, non répété dans le corps). Tout # ultérieur est un titre de section principal (voir ci-dessous). |
# Section principale (après le premier #) | Titre principal. Commence toujours sa propre page (sauf si le curseur est déjà en haut d'une page vierge), grande barre colorée à gauche, gras 16pt, texte noir — et obtient une entrée dans la table des matières. Utilisez-le uniquement pour les divisions de niveau supérieur ; chaque titre coûte une page. |
## Sous-section | Sous-titre. Gras 13pt, texte de couleur d'accent avec un court souligné de couleur d'accent (visuellement distinct des deux niveaux au-dessus et en dessous) — ne force pas de saut de page, continue simplement le flux. Obtient toujours une entrée dans la TdM. Dispose de sa propre vérification « ne pas laisser un titre en bas d'une page ». |
### Sous-sous-section (et ####+) | Titre tertiaire. Gras 12pt, gris neutre, fine règle pleine largeur, pas de saut de page, pas d'entrée dans la TdM (garde la TdM lisible). |
| Texte de paragraphe | Paragraphe de corps justifié à gauche, 10pt. |
- item / * item | Liste à puces (•). |
1. item | Liste numérotée. |
```lang ... ``` | Bloc de code monospace ombré avec bordure ; l'étiquette de langue (si présente) est imprimée comme une petite étiquette en italique au-dessus du bloc, et pilote la coloration syntaxique réelle Pygments (voir ci-dessous). |
| ` | a |
--- / *** / ___ sur sa propre ligne | Espacement vertical (pas un saut de page). |
<br> sur sa propre ligne | Une ligne de corps vide explicite (5,5 mm). Empilez-en plusieurs pour plus d'espace. Le moteur de rendu n'ajoute pas d'espace en haut d'une page, utilisez-le donc lorsque vous souhaitez un espace supplémentaire. |
<!-- pagebreak --> sur sa propre ligne | Commence une nouvelle page (sans effet si déjà en haut d'une page). |
 sur sa propre ligne | Image centrée avec bordure, redimensionnée pour s'adapter ; les chemins relatifs sont résolus par rapport au dossier du fichier .md. PNG/JPEG/WebP et SVG (voir « Figures, boîtes et mathématiques »). Une image raster nue de moins de 900 px de large est limitée à ~130x105 mm ; une figure (image suivie d'une légende), tout SVG et toute image de 900 px ou plus peut utiliser la pleine largeur du texte (hauteur max 120 mm). Une figure qui ne tient pas dans l'espace restant sur la page est réduite pour y tenir (jusqu'à 70 % de sa hauteur) au lieu de sauter à la page suivante et de laisser un espace vide. |
*Figure: ...* (une ligne entière en italique juste après une image) | La légende de l'image : petite, centrée, en italique, conservée sur la même page que l'image. Le texte alternatif n'est jamais imprimé. |
> text lignes | Boîte de définition : une boîte avec bordure et légèrement ombrée par blockquote (le terme en gras et les mathématiques en ligne fonctionnent à l'intérieur ; une ligne > sans rien après commence un nouveau paragraphe dans la même boîte). |
$...$ | Mathématiques en ligne, converties en texte Unicode (lettres grecques, ≤ ≥ × ·, exposants/indices, \frac{a}{b} -> (a)/b, \sqrt, \text{}, accents). Un prix comme $5 n'est pas mathématique. |
$$...$$ (une ligne, ou sur ses propres lignes) | Mathématiques en affichage, une image centrée issue du premier backend mathématique fonctionnel (voir « Figures, boîtes et mathématiques »). |
| Bloc indenté de 4 espaces après une ligne vide | Bloc de code sans étiquette de langue (les exports de notebook impriment ainsi la sortie des cellules). Non appliqué juste après une liste. |
**gras** | Texte en gras réel, via le moteur de rendu markdown=True intégré de fpdf2. |
[texte](url) | Une véritable annotation de lien PDF cliquable — seul le texte affiché est montré, pas l'URL brute — stylisé dans une couleur de lien distincte. |
*italique* / _italique_ | Rendu en italique (converti en la forme native __italic__ de fpdf2 en interne). |
`code` | Les backticks de code en ligne sont supprimés — pas de support monospace en ligne, rendu comme du texte brut. |
Les titres, paragraphes, puces, éléments numérotés et cellules de tableau sont tous rendus via le chemin markdown=True de fpdf2, donc le texte en gras et les [liens](url) fonctionnent partout où du texte apparaît, y compris dans les titres et les cellules de tableau. Les blocs de code délimités sont la seule exception — leur contenu est rendu de manière complètement littérale (aucune analyse Markdown), donc un exemple de code contenant des **, __ ou []() littéraux ne sera pas mal interprété.
Figures, boîtes et mathématiques
Écrit de sorte qu'un compagnon Markdown de notebook (sortie nbconvert) rende directement : report-to-pdf notebook.md remplace une étape PDF séparée basée sur Chrome.
Images SVG.  fonctionne. Le dessin SVG propre de fpdf2 ignore les pointes de flèche <marker>, les filtres et les masques, dessine le texte dans une police de base (pas de grec) et colorie mal le texte sans fill, donc chaque SVG est pixellisé à 2,5x et intégré en PNG, en utilisant, dans l'ordre : un Chrome/Edge sans tête (trouvé automatiquement ; définissez REPORT_PDF_BROWSER avec un chemin de navigateur pour remplacer), puis cairosvg si importable. Sans aucun des deux, le SVG est dessiné nativement par fpdf2 et un avertissement indique qu'il peut être incomplet. La pixellisation est mise en cache par exécution.
Backends mathématiques en affichage (--math auto|latex|mathtext|unicode, ou frontmatter math: ; par défaut auto). Le script imprime quel backend a été utilisé.
latex: vrai LaTeX quandpdflatex,xelatexoutectonicest dans le PATH (pluspdftocairo, PyMuPDF oudvisvgmpour créer l'image), oulatex+dvipng. Chaque formule est compilée avecstandalone,amsmath,amssymbetxcolorà 12 pt, puis recadrée en PNG transparent à 300 dpi dans la couleur du texte. Installation : MiKTeX (Windows), TeX Live (apt install texlive-latex-base texlive-latex-extra poppler-utils, macOSbrew install --cask mactex-no-gui), outectonic(binaire unique, télécharge les paquets à la demande ;conda install -c conda-forge tectonicouscoop install tectonic; testé de bout en bout sur Windows avec PyMuPDF comme convertisseur d'image).mathtext: sous-ensemble LaTeX de matplotlib (fractions, sommes, grec,\text{}est mappé sur\mathrm). Pas besoin de TeX, seulement matplotlib. Des environnements commealignedne sont pas supportés et échouent.unicode: la même conversion de texte que les mathématiques en ligne, centrée.
Un backend qui est manquant ou qui échoue (par exemple une formule rejetée par TeX) rétrograde silencieusement au suivant ; --math latex dégrade de la même manière.
Test : python tests/run_checks.py (conversion mathématique, analyseur, routage SVG, repli du backend avec PATH simulé, construction de bout en bout de tests/fixture.md). Rendu d'un compagnon : python scripts/build_report_pdf.py notebook.md -o notebook.pdf.
Coloration syntaxique
Les blocs de code délimités sont tokenisés avec de véritables lexers Pygments (pas un surligneur d'expressions régulières artisanal — le code est exactement le genre de chose qu'un petit analyseur ad hoc peut mal interpréter subtilement : échappements de chaînes, commentaires imbriqués, littéraux de gabarits). L'étiquette de langue sur la clôture (```typescript, ```sql, ```pythnetwork, ...) sélectionne le lexer ; une poignée d'alias communs sont normalisés (ts/ tsx → typescript, js/jsx → javascript, sh/shell/zsh → bash, yml → yaml, prisma → graphql — le lexer intégré le plus proche, et une bonne correspondance en pratique car la syntaxe @attribute(...) de Prisma et la syntaxe @directive(...) de GraphQL se tokenisent presque à l'identique).
La coloration est un bonus, jamais une exigence stricte pour rendre un bloc de code — une étiquette de langue manquante, une langue que Pygments ne reconnaît pas (même après la table d'alias), ou Pygments lui-même qui n'est pas importable, tout cela se rabat sur le comportement exact d'avant la coloration : le bloc entier dans une seule couleur plate, pas de plantage, pas de contenu manquant.
Palette de couleurs (choisie pour la lisibilité sur le fond gris clair du bloc de code, non liée à un thème Pygments particulier) :
| Catégorie de token | Couleur |
|---|---|
Mots-clés (interface, async, const, NOT NULL, mots-clés de type comme string/number) | Magenta |
| Chaînes | Vert |
| Nombres | Marron/orange |
| Commentaires | Gris |
Noms de fonctions/classes, décorateurs/attributs (@map, @relation) | Bleu / marron |
Prédéfinis (Promise, etc.) | Cyan |
| Tout le reste (identifiants, ponctuation, opérateurs) | La couleur de base du code |
Un type de token sans entrée exacte dans la palette se résout en remontant la hiérarchie des types de tokens de Pygments (par exemple Keyword.Declaration n'a pas sa propre entrée, mais son parent Keyword en a une) jusqu'à ce qu'une correspondance soit trouvée — la racine nue Token correspond toujours, donc cela n'échoue jamais à se résoudre en une couleur.
La logique de réduction pour le monospace (une ligne de code trop large pour la page réduit uniformément la police de tout le bloc plutôt que de déborder) n'est pas affectée par la coloration — elle mesure les largeurs de caractères brutes, qui ne changent pas en fonction de la couleur dans laquelle un token est finalement dessiné.
Thème sombre
Passez --dark, ou définissez theme: dark dans le frontmatter, pour rendre tout le document dans un thème sombre au lieu du thème clair par défaut. Chaque couleur du document bascule vers un équivalent adapté au sombre — pas seulement les blocs de code :
- L'arrière-plan de la page lui-même est rempli en sombre (fpdf2 n'a pas de paramètre natif de « couleur d'arrière-plan de page », donc cela est fait en remplissant la page complète dans le crochet
header(), avant que quoi que ce soit d'autre ne soit dessiné par-dessus — y compris sur la page de titre). - Le texte du corps, les titres (les trois niveaux), le texte atténué/pied de page, les lignes de règle, les rayures de tableau et l'en-tête/pied de page courant utilisent tous un ensemble de couleurs séparé pour le mode sombre, pas simplement une version atténuée du mode clair.
- La coloration syntaxique utilise sa propre palette de mode sombre (roses/verts/bleus plus vifs choisis pour le contraste sur l'arrière-plan de code sombre), pas les couleurs du mode clair atténuées.
- La couleur des liens (à la fois les liens Markdown
[texte](url)et les liens de navigation de la TdM) passe à un bleu plus clair qui reste lisible sur un fond sombre.
La couleur --accent (la barre supérieure, la barre gauche du titre principal, et le texte/souligné du sous-titre) est utilisée telle quelle dans les deux thèmes — choisissez-en une qui fonctionne à la fois sur un fond blanc et un fond quasi noir si vous prévoyez de générer le même rapport dans les deux thèmes, ou remplacez --accent par thème si ce n'est pas le cas.
Table des matières
La TdM en arrière-plan (voir « Comment cela rend » ci-dessus)
…
[suite tronquée pour la traduction]