Convertisseur local de notes Obsidian (.md) en PDF sobres et professionnels,
à la qualité d'un document LaTeX compilé — sans API externe.
- Rendu via Chromium (Playwright) + KaTeX pour les maths
- Coloration syntaxique via Pygments
- Syntaxe Obsidian supportée : callouts, wikilinks, embeds
![[image.png]], tags - 100 % local une fois installé
Prérequis : Python 3.10+.
pip install -r requirements.txt
python -m playwright install chromiumAu premier lancement, l'outil télécharge automatiquement les assets KaTeX
(~1 Mo) dans md2pdf/vendor/katex/. Les exécutions suivantes sont 100 % offline.
Lancez l'app sans argument :
python -m md2pdfUne fenêtre s'ouvre avec :
- une zone de glisser-déposer (acceptant fichiers
.mdet dossiers) - un bouton Ajouter… pour parcourir
- un choix de sortie : "me demander pour chaque fichier" (dialogue Enregistrer sous…) ou dossier fixe
- option de racine du vault Obsidian (pour résoudre les
![[image]]) - une option en-tête de page
- une barre de progression pendant la conversion
python -m md2pdf chemin/vers/note.mdProduit ./output/note.pdf à côté du fichier source.
⚠️ Chemins avec espaces : toujours entourer de guillemets.python -m md2pdf "C:\Users\mikal\Dropbox\Obsidian mémoire\Mémoire\07 IA\ONNX.md"
python -m md2pdf mon_dossier/ -rConvertit récursivement tous les .md. La structure de dossiers est
préservée dans output/.
python -m md2pdf note.md --in-placepython -m md2pdf mes_notes/ -r -o ~/Documents/pdfspython -m md2pdf "C:\Users\mikal\Dropbox\Obsidian mémoire\Mémoire\07 IA\ONNX.md" \
--vault-root "C:\Users\mikal\Dropbox\Obsidian mémoire"--vault-root aide à résoudre les ![[image.png]] qui ne sont pas dans le
même dossier que la note (attachments centralisés).
| Option | Effet |
|---|---|
-o, --output DIR |
Dossier de sortie (défaut : ./output/ à côté de l'input) |
-r, --recursive |
Parcourt récursivement les sous-dossiers |
--in-place |
PDF généré à côté du .md source, même nom |
--vault-root DIR |
Racine du vault Obsidian (résolution des images ![[…]]) |
--header |
Affiche le titre du document en en-tête de page (discret) |
--no-footer |
Retire la pagination en pied de page |
--margin VAL |
Marges CSS, ex. 20mm ou "25mm 20mm" (défaut 22mm) |
--format FORMAT |
Format papier : A4, Letter, … (défaut A4) |
--lang CODE |
Langue du document pour la césure (défaut fr) |
| Syntaxe | Rendu PDF |
|---|---|
# ## ### |
Titres hiérarchisés (serif, pas de titre orphelin) |
**gras**, _italique_, ~~~~ |
Gras, italique, barré |
`code inline` |
Code inline avec fond discret |
```lang ... ``` |
Bloc de code avec coloration syntaxique |
$...$ / $$...$$ |
Math inline / block via KaTeX |
| ` | col1 |
> texte |
Blockquote en italique |
> [!note] Titre |
Callout stylisé (note, tip, warning, danger, …) |
[[note]] / [[note|alias]] |
Texte simple (pas de lien cassé dans le PDF) |
![[image.png]] |
Image embarquée dans le PDF |
 |
Image embarquée (chemins relatifs résolus) |
#tag / #physique/ondes |
Petit badge discret |
- [x] / - [ ] |
Cases à cocher |
[^1] + [^1]: … |
Notes de bas de page |
note, info, tip/hint, important, success/check/done,
question/help/faq, warning/caution/attention,
danger/failure/fail/error/bug, example, quote/cite,
abstract/summary/tldr, todo.
- Format A4, marges 22 mm (configurables)
- Police corps : Inter / Source Sans Pro / IBM Plex Sans (fallback système)
- Police titres : Source Serif Pro / Charter (fallback système)
- Police code : JetBrains Mono / Fira Code (fallback système)
- Pagination en bas de page (
1 / N), discrète - Pas de titre orphelin :
break-after: avoidsur tous lesh1–h6 - Contenu protégé : tables, blocs de code, callouts, équations ne se coupent pas à cheval sur deux pages
export_md_pdf/
├── requirements.txt
├── md2pdf/
│ ├── cli.py # Point d'entrée CLI
│ ├── converter.py # Pipeline MD → HTML → PDF
│ ├── renderer.py # markdown-it + plugins + Pygments
│ ├── obsidian.py # Préprocesseur (callouts, wikilinks, embeds)
│ ├── assets.py # Téléchargement KaTeX
│ ├── templates/
│ │ ├── document.html # Gabarit HTML
│ │ └── style.css # Feuille de style typographique
│ └── vendor/katex/ # Rempli au premier lancement
└── samples/
└── demo.md # Fichier de démonstration
Un seul script produit un build autonome pour la plateforme sur laquelle il tourne :
pip install -r requirements.txt
pip install pyinstaller
python -m playwright install chromium
python build.pyRésultat :
| OS | Artifact | Note |
|---|---|---|
| Windows | dist/md2pdf/md2pdf.exe |
Dossier complet à zipper et envoyer |
| macOS | dist/md2pdf.app |
.app standard, glissable dans Apps |
| Linux | dist/md2pdf/md2pdf |
Exécutable + libs dans le dossier |
Le build embarque Chromium (≈ 180 Mo) et les assets KaTeX : l'utilisateur final n'a rien à installer, il double-clique et l'app démarre.
PyInstaller ne fait pas de cross-compilation : il faut lancer python build.py
sur chaque système pour obtenir le binaire correspondant.
- Pour la version Windows : lancer le build depuis Windows (
.exe) - Pour la version macOS : lancer le build depuis un Mac (
.app)
| Option | Effet |
|---|---|
--onefile |
Produit un seul fichier (démarrage plus lent, plus simple à envoyer) |
--clean |
Supprime dist/ et build/ avant de relancer |
- Windows : zipper
dist/md2pdf/→ envoyer le.zip. L'ami dézippe et lancemd2pdf.exe. - macOS : zipper
dist/md2pdf.app→ envoyer. Au 1er lancement : clic-droit → Ouvrir (signature Apple non gérée).
Path does not exist sur un chemin avec espaces
→ Entourer le chemin de guillemets : python -m md2pdf "C:\mon dossier\note.md".
Les équations ne se rendent pas
→ Vérifier l'accès réseau au premier lancement (téléchargement de KaTeX).
Ensuite, les assets sont cachés localement dans md2pdf/vendor/katex/.
Une image ![[img.png]] n'apparaît pas
→ Passer --vault-root vers la racine du vault Obsidian. L'outil cherche
dans le dossier de la note, puis dans attachments/, assets/, images/,
puis récursivement dans le vault.
playwright._impl._api_types.Error: Executable doesn't exist
→ Relancer python -m playwright install chromium.
Usage personnel.