# md2doc — design spec **Data:** 2026-07-27 **Autor:** Sebastian Petrescu **Stare:** draft (pending empirical review, post Stage 1 review fixes) --- ## 1. Obiectiv Construiește `md2doc.py` — un utilitar single-file Python care convertește Markdown la `.docx` (Microsoft Word OOXML), simetric cu `md2pdf.py` existent. În același plan: bug-fixes pentru `md2pdf.py` identificate în timpul brainstorming-ului. ## 2. Context `md2pdf.py` există, funcționează (verified: rularea pe `pluxee-todo.md` produce PDF byte-identic cu cel committed), dar are 2 bug-uri: - **B1.** `convert_md_to_pdf()` acceptă parametru `footer`, dar argparse nu expune flag-ul `--footer`. Funcționalitate moartă în CLI. - **B2.** `--forms` rulează fără eroare, dar nu produce câmpuri AcroForm. Verificare: PDF-ul rezultat nu conține `/AcroForm` și nici adnotări `/Widget`. `weasyprint` nu generează câmpuri de formular din `` HTML în mod automat. Pentru md2doc: niciun work anterior în AW pe conversie md→docx. Research extern (web-search-prime) arată 3 abordări posibile: Pandoc (CLI/subprocess), pypandoc (wrapper), sau python-docx cu parsare manuală. Am ales a treia pentru a păstra controlul fin asupra stilurilor și consistența cu md2pdf.py. ## 3. Arhitectură ### 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 fișiere. ### 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": {...}, } # Reguli comune — echivalentul COMMON_CSS din md2pdf, tradus în Word. # Conține proprietăți care nu țin de un stil anume. Override-uibile per stil: # la inițializare, STYLES[style] face merge peste COMMON (deep merge per key), # deci un stil poate suprascrie orice valoare de aici. COMMON = { "page_break_before_h1": False, # H1 pe pagină nouă (definabil per stil) "keep_with_next": True, # headings nu se despart de următorul paragraf "table_cell_valign": "top", # vertical-align pe celule "orphans": 3, # minimum linii orphan "widows": 3, # minimum linii widow } def convert_md_to_doc(md_file, output_file, style="elegant"): ... def main(): parser = argparse.ArgumentParser(...) ... ``` ### 3.3 Pipeline conversie ``` md text ↓ _bulletize() (copiat din md2pdf.py — YAGNI refactor comun) ↓ BLANK_MARKER injection (copiată din md2pdf — preservă multi-blank-lines) ↓ markdown.markdown() (extensii: tables, fenced_code, toc, attr_list, ↓ md_in_html, nl2br) ↓ replace BLANK_MARKER cu