# #74 — Support complet des documents PDF > **Statut :** ✅ Terminé (2026-09-07 — C3 + Range 206 + config G3 + indexation incrémentale, commit 7042307. Optionnels D2/E4/H2/I2 non retenus) > **Effort :** 4-5 jours | **Impact :** 🟡 > **Références :** [Roadmap](../ROADMAP.md) · [Changelog — 2.1.0](../../CHANGELOG.md#210--2026-09-08) - **Description :** Prise en charge native des fichiers PDF dans ObsiGate avec parité fonctionnelle complète avec les documents Markdown : apparition dans l'arborescence, indexation full-text, visualisation inline dans le navigateur, recherche TF-IDF, et téléchargement. - **Implémentation réelle (vérifiée 2026-09-07) :** - **Bugs corrigés (2026-09) :** `api_pdf_stream` crashait en 500 (`NameError: current_user` jamais injecté) ; l'indexation incrémentale du watcher faisait `read_text()` sur les PDFs (garbage) ; Range/206 et `pdf/info` absents malgré le texte ci-dessous. - `GET /api/file/{vault}/pdf/info` — métadonnées seules sans transférer le document (C3) - Stream avec `Accept-Ranges` + 206 Partial Content (single range, suffix-range, 416) (C2) - `OBSIGATE_PDF_MAX_SIZE_MB` (50) + `OBSIGATE_PDF_EXTRACT_TIMEOUT` (30s via thread-pool) (B4/G3) - Backend `backend/pdf_reader.py` (existant) — extraction pypdf + pymupdf (fallback), métadonnées, TOC - `backend/indexer.py` — `.pdf` dans SUPPORTED_EXTENSIONS, extraction dans `index_document()` - `backend/main.py` — flag `is_pdf: True` retourné par `api_file_view`, endpoint `GET /api/file/{vault}/pdf/stream` avec support Range/206 - `backend/search.py` — filtre `ext:pdf` (déjà implémenté avant cette PR) - `frontend/js/viewer.js:451-480` — branche `if (data.is_pdf)` + iframe + toolbar + TOC + bouton download - **Tests :** `tests/test_pdf.py` (26 tests verts) — text/metadata/TOC + indexation scan/incrémentale + filtre ext + stream 200/206/416 + /pdf/info + limite de taille - **Bug fixé dans cette PR :** `PdfReader` NameError dans `pdf_reader.py` quand pymupdf est installé (la variable `PdfReader` n'était déclarée que dans la branche `except ImportError`) - `backend/requirements-test.txt` (nouveau) — `reportlab` pour générer des PDFs de test - **Sous-tâches :** ## A. Backend — Extraction de texte PDF (1-1.5 jour) - [x] **A1. Dépendance** : `pypdf>=4.0` retenu dans requirements (pure Python, simplicité Docker) ; PyMuPDF (`fitz`) utilisé automatiquement en priorité s'il est importable — l'inverse du plan initial, fonctionnellement équivalent. - [x] **A2. Module `backend/pdf_reader.py`** : Créer un module dédié avec les fonctions : - `extract_pdf_text(file_path: Path) -> str` : extrait tout le texte du PDF, page par page, avec séparateur `\f` entre pages. Gère les PDF encodés, protégés par mot de passe (retourne erreur explicite), et corrompus. - `extract_pdf_metadata(file_path: Path) -> dict` : extrait titre, auteur, sujet, nombre de pages, taille. - `extract_pdf_preview(file_path: Path, max_chars: int = 100000) -> str` : extrait les N premiers caractères pour l'indexation (limité par `SEARCH_CONTENT_LIMIT`). - [x] **A3. Fallback pypdf** : Si pymupdf non disponible (exception d'import), fallback automatique sur `pypdf` avec un log warning. Code structuré avec une interface abstraite (`PdfReader` protocol) pour swap transparent. ## B. Backend — Indexation des PDF (1 jour) - [x] **B1. Ajout à `SUPPORTED_EXTENSIONS`** : Ajouter `.pdf` au set dans `backend/indexer.py:56`. Déclencher un rebuild complet de l'index (incrémental via le file watcher pour les nouveaux PDFs). - [x] **B2. Lecture PDF dans les DEUX chemins d'indexation** (`_scan_vault` + `_index_single_file_sync`, utilisé par le watcher) : détection `.pdf` → `extract_pdf_text()`. Fix 2026-09 : seul le scan complet gérait les PDFs, l'incrémental indexait du garbage. - [x] **B3. Métadonnées PDF (adapté)** : titre PDF prioritaire sur le nom de fichier dans l'index ; `pages`/`author` exposés via `api_file_view` + `/pdf/info` (non stockés dans l'entrée d'index). - [x] **B4. Gestion d'erreur robuste** : PDF corrompu → log warning + skip (ne pas bloquer l'indexation). PDF volumineux (>50 Mo) → log info + extraction tronquée à `SEARCH_CONTENT_LIMIT`. Timeout d'extraction configurable (30s par défaut). ## C. Backend — API endpoints PDF (0.5 jour) - [x] **C1. Modification de `api_file_view()`** (`backend/main.py:2270`) : Avant la tentative de `read_text()`, détecter `.pdf` par extension. Pour les PDF : - Extraire le texte avec `extract_pdf_text()` - Extraire les métadonnées (pages, auteur) - Retourner une réponse structurée : `is_pdf: true`, `page_count`, `pdf_metadata`, `html` (aperçu texte formaté), `raw_length` - Le champ `html` contient un rendu texte simple (pas de markdown) : texte paginé ou première page formatée - [x] **C2. Nouvel endpoint `GET /api/file/{vault}/pdf/stream`** : Sert le fichier PDF brut avec `Content-Type: application/pdf` et `Content-Disposition: inline` pour visualisation dans le navigateur. Supporte le `Range` header (HTTP 206 Partial Content) pour le streaming progressif des gros PDFs — essentiel pour la performance sur des documents volumineux. - [x] **C3. Nouvel endpoint `GET /api/file/{vault}/pdf/info`** : Retourne les métadonnées seules (pages, titre, auteur) sans le contenu — permet à l'UI d'afficher les infos avant de charger le PDF lourd. - [x] **C4. Endpoint download** : Déjà fonctionnel (`/api/file/{vault}/download`) — aucun changement nécessaire. ## D. Frontend — Arborescence de fichiers (0.5 jour) - [x] **D1. Icône et filtre** : L'icône PDF (`file-text` de Lucide) est déjà mappée dans `EXT_ICONS` (`frontend/js/utils.js:129`). Une fois `.pdf` dans `SUPPORTED_EXTENSIONS`, les PDFs apparaissent automatiquement dans l'arborescence via l'API `list_directory`. Aucun changement UI nécessaire. - [ ] **D2. Distinction visuelle** — ⚪ NON RETENU : Sous-titre léger sous le nom du fichier dans l'arborescence indiquant le nombre de pages (ex: « 12 pages ») pour différencier rapidement les PDF des MD. Donnée disponible via l'API `pdf/info`. - [x] **D3. Drag & drop et upload** : Le mécanisme d'upload existant (`POST /api/file/{vault}/upload`) fonctionne déjà pour tout type de fichier. Vérifier que le MIME type `application/pdf` est correctement détecté et que le watcher réindexe automatiquement. ## E. Frontend — Viewer PDF (1 jour) - [x] **E1. Rendu inline natif** : Utiliser le visualiseur PDF intégré du navigateur via `