diff --git a/docs/specs/2026-07-27-md2doc-design.md b/docs/specs/2026-07-27-md2doc-design.md new file mode 100644 index 0000000..361ec50 --- /dev/null +++ b/docs/specs/2026-07-27-md2doc-design.md @@ -0,0 +1,212 @@ +# md2doc — design spec + +**Data:** 2026-07-27 +**Autor:** Sebastian Petrescu +**Stare:** draft (pending empirical review) + +--- + +## 1. Obiectiv + +Construieste `md2doc.py` — un utilitar single-file Python care converteste +Markdown la `.docx` (Microsoft Word OOXML), simetric cu `md2pdf.py` existent. +In acelasi plan: bug-fixes pentru `md2pdf.py` identificate in timpul +brainstorming-ului. + +## 2. Context + +`md2pdf.py` exista, functioneaza (verified: rularea pe `pluxee-todo.md` +produce PDF byte-identic cu cel committed), dar are 2 bug-uri: + +- **B1.** `convert_md_to_pdf()` accepta parametru `footer`, dar argparse nu + expune flag-ul `--footer`. Functionalitate moarta in CLI. +- **B2.** `--forms` ruleaza fara eroare, dar nu produce campuri AcroForm. + Verificare: PDF-ul rezultat nu contine `/AcroForm` si nici anotari + `/Widget`. `weasyprint` nu genereaza campuri de formular din `` + HTML in mod automat. + +Pentru md2doc: niciun work anterior in AW pe conversie md→docx. Research +extern (web-search-prime) arata 3 abordari posibile: Pandoc (CLI/subprocess), +pypandoc (wrapper), sau python-docx cu parsare manuala. Am ales a treia +pentru a pastra controlul fin asupra stilurilor si consistenta cu md2pdf.py. + +## 3. Arhitectura + +### 3.1 Layout repository + +``` +tools/ +├── md2pdf.py (existing — bug fixes only, scope-limited) +├── md2doc.py (new — sibling, mirrors md2pdf structure) +└── docs/specs/2026-07-27-md2doc-design.md +``` + +md2doc.py este single-file, simetric cu md2pdf.py. Nu se face refactor spre +package comun — YAGNI pentru 2 fisiere. + +### 3.2 Structura md2doc.py + +Pattern duplicat din md2pdf.py: + +```python +#!/usr/bin/env python3 +import argparse, sys +from pathlib import Path +import markdown +from bs4 import BeautifulSoup +from docx import Document +from docx.shared import Pt, RGBColor, Cm + +STYLES = { + "elegant": {...}, # default + "report": {...}, + "default": {...}, + "dark": {...}, + "mono": {...}, +} + +COMMON = {...} # reguli comune, ca si COMMON_CSS in md2pdf + +def convert_md_to_doc(md_file, output_file, style="elegant", footer=""): + ... + +def main(): + parser = argparse.ArgumentParser(...) + ... +``` + +### 3.3 Pipeline conversie + +``` +md text + ↓ _bulletize() (reutilizat din md2pdf — scos ca util comun) + ↓ markdown.markdown() (extensii: tables, fenced_code, toc, attr_list, nl2br) + ↓ BeautifulSoup HTML + ↓ walk DOM, emit docx elements +.docx file +``` + +`_bulletize()` e duplicated logic between md2pdf and md2doc. Pentru scope-ul +curent (2 fisiere), se copiaza. Daca pe viitor se adauga un al 3-lea converter, +se refactor la un `md_utils.py` comun. + +## 4. Mapare stiluri (md → docx) + +Fiecare stil din `STYLES` defineste echivalentele Word ale CSS-ului md2pdf. +Mapping-ul e manual, element cu element. + +| Element MD | md2pdf (CSS) | md2doc (Word) | +|---|---|---| +| h1 | font-size + border-bottom | Heading 1 style + paragraph bottom border | +| h2/h3/h4 | font-size + color | Heading 2/3/4 style | +| p | line-height + color | paragraph format | +| strong | font-weight 700 | run.bold = True | +| em | italic | run.italic = True | +| code (inline) | monospace + bg | run.font.name = mono + run highlight | +| pre | block + border-left | paragraph + indent + shading | +| blockquote | border-left + bg | paragraph left indent + italic + shading | +| table | borders + header bg | docx Table + cell shading + borders | +| ul/ol | list markers | List Bullet / List Number paragraph styles | +| a | color + underline | run.color + run.underline | +| hr | border-top | paragraph bottom border | + +### 4.1 Ipoteze de verificat (HARD CLAIMS — astea trebuie validate empiric inainte de implementare) + +Urmatoarele afirmatii despre python-docx sunt **ipoteze**, nu fapte, pana +cand reviewer-ul empiric le rupe sau le confirma cu comenzi reale: + +- **H1.** python-docx poate adauga bottom border pe un paragraph Heading 1 + (necesita manipulare XML — Issue python-docx #105 sugereaza ca nu exista + API direct). +- **H2.** python-docx poate aplica shading pe un paragraph (pentru code/blockquote) + — documentatia oficiala nu listeaza API;StackTrace-ul sugereaza XML + manipulation cu `OxmlElement('w:shd')`. +- **H3.** python-docx poate crea liste nested (Level 2, Level 3) care se + randeaza corect in Word SI Google Docs. Issue #122 si thread-urile + Latenate/Reddit sugereaza probleme cunoscute aici. +- **H4.** Cell shading (`w:shd` pe `w:tcPr`) functioneaza pentru background + de header row intr-un tabel. +- **H5.** `style = document.styles['List Bullet']` exista intr-un Document + nou creat de python-docx (sau trebuie adaugat manual). + +Pana nu se valideaza H1-H5 cu comenzi reale (run python-docx, deschide output +in Word/Pages, verifica vizual), mapping-ul din tabel este plan, nu fapt. + +## 5. CLI interface + +Identica cu md2pdf.py: + +``` +md2doc.py input.md # → input.docx +md2doc.py input.md -o output.docx +md2doc.py docs/ # converteste toate .md din docs/ +md2doc.py input.md --style mono +md2doc.py input.md -q # quiet +``` + +## 6. Bug-fixes md2pdf.py (scope-limited) + +### 6.1 B1: expunere `--footer` + +Adauga argument argparse: + +```python +parser.add_argument('--footer', help='Custom footer text (right-aligned)') +``` + +Si paseaza-l in apelul `convert_md_to_pdf(md_file, pdf_file, args.style, +forms=args.forms, footer=args.footer)`. + +### 6.2 B2: `--forms` nu produce AcroForm + +Fapt stabilit (verified): weasyprint 68.1 nu genereaza campuri de formular +din `` sau `