"""Extraction de texte et de mots depuis les PDFs via pdfplumber. pdfplumber est une bibliothèque Python pure : aucune dépendance système (contrairement à l'ancien `pdftotext`/poppler), ce qui permet d'empaqueter l'application en exécutable autonome (Windows notamment). """ from dataclasses import dataclass, field import pdfplumber @dataclass class PdfContent: """Contenu extrait d'un PDF. Attributes: text: Texte de toutes les pages avec mise en page préservée (équivalent de `pdftotext -layout`). Consommé par les parseurs opérations et locataires. words: Mots de la première page avec leurs coordonnées (``text``, ``x0``, ``x1``, ``top``, ``bottom``). Utilisé par le parseur de métadonnées pour les champs d'en-tête multi-colonnes où l'alignement par espaces n'est pas fiable. """ text: str words: list[dict] = field(default_factory=list) def read_pdf(pdf_path: str) -> PdfContent: """Lit un PDF et retourne son texte (layout) et les mots de l'en-tête. Args: pdf_path: Chemin vers le fichier PDF Returns: Un objet :class:`PdfContent`. """ text_parts: list[str] = [] header_words: list[dict] = [] with pdfplumber.open(pdf_path) as pdf: for page_index, page in enumerate(pdf.pages): text_parts.append(page.extract_text(layout=True) or "") # Les métadonnées se trouvent uniquement sur la première page. if page_index == 0: header_words = [ { "text": w["text"], "x0": w["x0"], "x1": w["x1"], "top": w["top"], "bottom": w["bottom"], } for w in page.extract_words() ] return PdfContent(text="\n".join(text_parts), words=header_words) def extract_text_from_pdf(pdf_path: str) -> str: """Extrait le texte du PDF avec mise en page préservée. Conserve la signature historique (retourne une chaîne) pour la CLI et les parseurs qui ne consomment que le texte. Args: pdf_path: Chemin vers le fichier PDF Returns: Texte extrait du PDF avec mise en page préservée """ return read_pdf(pdf_path).text