#!/usr/bin/env python3
"""Le CV adalan en page HTML responsive, ou en PDF A4 portrait imprimé depuis cette page.
Module de remplir_cv.py, qui l'appelle quand le fichier de sortie finit par .html ou .pdf :
uv run scripts/remplir_cv.py cv.json --langue fr -o "CV_Camille_D - adalan - septembre 2026.html"
uv run scripts/remplir_cv.py cv.json --langue fr -o "CV_Camille_D - adalan - septembre 2026.pdf"
Le contenu est le même JSON que pour la trame PowerPoint. La page tient en un seul fichier : la feuille
de style assets/web/cv.css, les polices assets/polices/*.woff2, le logo et les courbes de niveau y
sont intégrés, sans appel à un fichier ou à un site extérieur.
Le PDF sort de l'impression de cette page par un navigateur de la famille Chromium : Chrome, Chromium,
Edge, Brave, ou le Chromium de Playwright. La variable d'environnement CHROME peut en donner le chemin.
Aucune dépendance Python en dehors de la bibliothèque standard.
"""
import base64
import html
import os
import re
import shutil
import subprocess
import sys
import tempfile
import time
from pathlib import Path
import remplir_cv as rc
WEB = rc.ASSETS / "web"
POLICES = rc.ASSETS / "polices"
POLICES_WEB = [ # famille, graisse, fichier : les graisses et chasses de la charte, figées
("adalan Sans", 340, "adalan-sans-light.woff2"),
("adalan Sans", 420, "adalan-sans.woff2"),
("adalan Sans", 650, "adalan-sans-bold.woff2"),
("adalan Display", 800, "adalan-display.woff2"),
("adalan Heading", 700, "adalan-heading.woff2"),
("adalan Mono", 400, "adalan-mono.woff2"),
("adalan Mono", 500, "adalan-mono-medium.woff2"),
]
SIGNATURE = "AI Driven Insight"
ICONE_PDF = ('')
# Navigateurs de la famille Chromium, dans l'ordre où on les cherche.
COMMANDES = ["google-chrome", "google-chrome-stable", "chromium", "chromium-browser", "chrome",
"microsoft-edge", "msedge", "brave-browser"]
APPLICATIONS = [
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
"/Applications/Chromium.app/Contents/MacOS/Chromium",
"/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
"/Applications/Brave Browser.app/Contents/MacOS/Brave Browser",
r"C:\Program Files\Google\Chrome\Application\chrome.exe",
r"C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe",
]
PLAYWRIGHT = [ # sous le dossier des navigateurs de Playwright
"chromium-*/chrome-mac*/*.app/Contents/MacOS/*",
"chromium-*/chrome-linux*/chrome",
"chromium-*/chrome-win*/chrome.exe",
"chromium_headless_shell-*/chrome-*/chrome-headless-shell",
"chromium_headless_shell-*/chrome-*/headless_shell",
]
def e(texte):
"""Texte échappé, avec les espaces insécables de la typographie française : avant « : » et dans
les guillemets, fines avant « ; ! ? ». L'anglais n'a pas d'espace à ces endroits : il ne change pas."""
texte = re.sub(r"[ \u00a0\u202f]+([:»])", "\u00a0\\1", texte)
texte = re.sub(r"«[ \u00a0\u202f]+", "«\u00a0", texte)
texte = re.sub(r"[ \u00a0\u202f]+([;!?])", "\u202f\\1", texte)
return html.escape(texte, quote=False)
# ---------------------------------------------------------------------------------------------
# Morceaux de page
def avec_libelle(texte):
"""« Libellé : détail » : le libellé en gras, comme dans la trame."""
libelle, reste = rc.libelle(texte)
return (f"{e(libelle.strip())} " if libelle else "") + e(reste)
def points(liste):
return '
' + "".join(f"
{avec_libelle(x)}
" for x in liste) + "
"
def entete_mission(titre):
"""Le poste, puis les années et le client : le découpage de la trame."""
poste, client, dates = rc.decouper_titre(titre)
morceaux = ([f'{e(dates)}'] if dates else []) + ([e(client)] if client else [])
meta = f'
{" · ".join(morceaux)}
' if morceaux else ""
return f'
{e(poste)}
{meta}'
def svg_en_ligne(chemin, classe, etiquette):
"""Le logo en SVG dans la page : ses classes deviennent des couleurs, son identifiant disparaît."""
svg = re.sub(r"<\?xml[^>]*\?>", "", chemin.read_text(encoding="utf-8"))
couleurs = dict(re.findall(r"\.([\w-]+)\{fill:(#[0-9a-fA-F]{3,6});?\}", svg))
svg = re.sub(r".*?", "", svg, flags=re.S)
svg = re.sub(r'class="([\w-]+)"', lambda m: f'fill="{couleurs.get(m.group(1), "currentColor")}"', svg)
svg = re.sub(r'\sid="[^"]*"', "", svg)
return svg.strip().replace("