DOCX Generator
Overview
Produce real Word documents programmatically with exec + Python python-docx, which Navin ships. Do not run import checks or install anything up front: write the script, and only if it actually fails on a missing dependency, report it and ask before touching the environment.
The document must stay editable: headings, paragraphs, tables and styles, never a page pasted as an image.
Language and locale gate (mandatory)
- Before creating or exporting a DOCX, require the user to explicitly select the document language. Do not infer it from the prompt, UI language, template, names, or location. If it is absent, ask and stop generation.
- Carry that language through every heading, label, caption, table, header/footer, filename metadata, date, number and currency. Do not leave source-template text untranslated.
- Use locale-appropriate date, decimal, grouping and currency conventions consistently. Record the selected language/locale in the rerunnable generator data.
- For Arabic and other RTL languages, set paragraph bidi/RTL and right alignment, mirror layout where appropriate, use logical reading order, and choose fonts with the required glyph coverage (for example Noto Naskh Arabic, Noto Sans Arabic or Amiri). Embed or package fonts when the export route permits it; never silently substitute a font lacking glyphs.
Typography & icon rules (mandatory)
- Never use em or en dashes (U+2014 / U+2013) in document text. Use commas, colons, periods or parentheses instead.
- Never use emoji or generic AI-style icons (📊 ✅ 💡 …) in documents. Use numbers, plain monochrome glyphs (✓ ✗) or clean layout elements instead.
HTML document template library
Navin materializes the selected editable Word-style template inside the active
workspace (document.html with A4 .page sections + metadata.json + image.png
preview). When the runtime context contains a "Document Template Attachment",
the user picked one in the WebUI - it is mandatory. Fill that theme: replace
every sample string, keep the CSS, add images when you have them. Do not invent
another look. Three.js does not belong in a Word page:
For a report, letter, proposal, minutes or brief: write semantic JSON
and let the engine render the HTML. Do not copy the lookbook
document.html. That is how titles double, sample KPIs survive, and
image.png sticks on the page.
{
"kind": "report",
"theme": "executive",
"brand": "Navin",
"kicker": "Executive report",
"title": "Revue strategique T3 2026",
"subtitle": "Traction, risques, decisions demandees.",
"meta": {
"prepared_by": "Direction",
"recipients": "Comite de direction",
"date": "15 aout 2026"
},
"sections": [
{
"heading": "1. Synthese",
"body": "Le trimestre confirme la trajectoire.",
"kpis": [
{ "label": "ARR", "value": "4,2 M€", "meta": "+18% YoY" }
]
}
]
}
<navin-python> -m navin.documents.word_design render --document doc.json -o document.html
Section keys: body (string or list of paragraphs), items (bullets;
list: steps / check), kpis, table (headers, rows, optional
total row), callout (string or {title, text, variant}), quote +
author, insight, image + caption, subsections. Common
variants (paragraphs, text, bullets, points, steps, a nested
cover object, meta as a list of lines) are understood too, but write
the canonical keys.
A missing image is omitted. Never leave image.png. Never invent a hex.
A report or proposal with two or more headed sections gets a Sommaire
page after the cover: a visible list plus data-doc-toc="2-3" so Word
can refresh it. Letters and briefs skip it. A report without that
field fails quality. Set language on the JSON.
For a specialized contract template the user attached: read
metadata.json, then replace every visible string. Keep the CSS.
If any lookbook sentence remains, the document is rejected.
Convert the filled file with the bundled converter, whose command is given
in the runtime context:
<navin-python> .navin/resources/tools/html2docx.py document.html -o report.docx
Chromium measures the laid-out page, then headings become Word headings,
lists become real lists, tables become real tables, and typography, colors,
shading and borders follow. Banners repeated on every .page land in the
running header, a "Page 3 / 8" footer becomes PAGE and NUMPAGES fields.
Headings stay with the following paragraph. Table rows do not split across
pages. Around a second per document. Useful flags: --keep-fonts to keep
the CSS font names instead of their Office equivalents, --chromium PATH
when no browser is found, --keep-workdir DIR to inspect intermediate files.
Word behaviour that CSS cannot express is requested with data-doc-* on
the HTML, never with pixel coordinates:
data-doc-toc="1-3" on a nav or heading: a real TOC field the reader
can refresh in Word.
data-doc-section="landscape" on a .page: a new Word section in
landscape, for wide tables and diagrams. Portrait is the default.
data-doc-columns="2" on a page or section: newspaper columns.
data-doc-break="page" on a heading: page break before.
data-doc-keep="next" or "together": keep with next / keep lines.
data-doc-caption="Figure" plus data-doc-bookmark="fig:name": a
numbered caption Word can renumber. Point at it with
data-doc-ref="fig:name".
data-doc-numbering="1-4" on a page: Heading 1..4 numbered 1 / 1.1 / ...
data-doc-cover="true" on the first page: a different first-page header.
data-doc-row="stack" on a side-by-side row: read it top to bottom
instead of as a table. Use it on a header band that holds the page
title, because a heading buried in a table cell gets no outline level
and never reaches the table of contents. A heading that is the row's own
child is already handled; this is for one nested deeper.
Do not invent { "x": 12, "y": 40 } layouts. Describe the document in
HTML semantics and let html2docx decide the Word constructs.
Titles must be real h1-h6 tags. A div styled large and bold looks
right in the preview and arrives in Word as plain text: no navigation
pane, no TOC line, no outline. Keep the class, change the tag.
Reopen the result and fix what needs it with python-docx. Never rebuild the
whole design by hand, and never paste a rendered page as an image.
If the user asked for a PDF instead, render the same filled HTML (headless
Chromium --print-to-pdf or ReportLab) rather than converting the DOCX.
Theme and design tokens
Every Word template carries its palette and its fonts in one place, a
<style id="navin-theme"> block of --nv-* custom properties, and the rest of
its CSS only reads them through var(). That is what makes a finished document
re-themable without touching a word of its content.
<navin-python> .navin/resources/tools/word_design.py themes
<navin-python> .navin/resources/tools/word_design.py apply --theme luxury document.html
<navin-python> .navin/resources/tools/word_design.py audit document.html
The 15 themes: executive, consulting, corporate, modern, minimal,
technology, ai, financial, annual-report, startup, proposal,
research, technical, government, luxury.
When you write or extend CSS in a document, use the tokens instead of colours:
--nv-surface the paper, --nv-bg the canvas around it
--nv-text running text, --nv-heading titles, --nv-muted secondary text
--nv-accent the fill behind table headers and bands, --nv-on-accent the
text that sits on it
--nv-accent-2 the secondary accent for rules and markers, and
--nv-accent-2-ink when that accent has to be read as small text
--nv-border hairlines, --nv-subtle zebra rows, --nv-soft callout tints
--nv-status-high|mid|low|good (+ -soft) for risk, priority and status,
which keep their meaning across themes
--nv-font-heading, --nv-font-body, --nv-font-mono, --nv-radius
On a dark panel, dim with rgba(255,255,255,.85) rather than a named grey, so
the panel still works when the theme changes its accent.
audit fails when a literal colour is left outside the theme block, which is
exactly the thing that survives a theme change and ruins it. Run it before
delivering. _engine/navin-word.css holds ready-made token-driven components:
callouts (info, important, warning, recommendation, key finding), insight
blocks, KPI rows, big numbers, figures with captions, checklists, numbered
steps, pros/cons, signature blocks.
Core recipes
from docx import Document
from docx.shared import Pt, Cm, RGBColor
from docx.enum.text import WD_ALIGN_PARAGRAPH
doc = Document() # or Document("template.docx") to inherit styles
doc.add_heading("Titre", level=1)
p = doc.add_paragraph("Texte avec ")
p.add_run("gras").bold = True
# Table with header
table = doc.add_table(rows=1, cols=3)
table.style = "Light Grid Accent 1"
hdr = table.rows[0].cells
hdr[0].text, hdr[1].text, hdr[2].text = "Col A", "Col B", "Col C"
row = table.add_row().cells
doc.add_picture("logo.png", width=Cm(4))
doc.add_page_break()
# Header/footer
section = doc.sections[0]
section.header.paragraphs[0].text = "Navinspire - Confidentiel"
doc.save("out.docx")
Reading/editing: iterate doc.paragraphs and doc.tables; modify run.text to preserve formatting. For find-replace across runs, join runs carefully (formatting lives at run level).
No empty blocks (mandatory)
Every block of a template gets real content, or it is removed and the layout
rebalanced. No sample text, no placeholder figures, no empty table row, no
orphan image frame. Before delivering, reopen the file and read it through:
leftover sample copy, untranslated labels and empty cells are failures.
Visual QA (before converting)
The filled HTML is laid out in Chromium and read the way a reader would: what
spills past the margin, what is unreadable, what page is half empty, what
heading Word will never see. Every page comes back scored out of 100 with the
thing to change.
<navin-python> .navin/resources/tools/word_qa.py document.html
<navin-python> .navin/resources/tools/word_qa.py document.html --json
Run it on the filled HTML before converting, because that is the stage where a
finding is still fixable by editing the source. It exits non-zero when the
weakest page falls under 85: the weakest page is what the reader notices, so an
average is not enough. Fix what it names and run it again - do not deliver a
document that is still asking for rework.
It also writes qa.json beside the document, and html2docx refuses to
convert while that report says fail. Fix the pages and re-run word_qa.py
until it passes; --force exists only for a user who explicitly accepts the
document as is.
It covers layout (overflow, orphan headings, near-empty pages), readability
(contrast under 4.5:1, unreadably small text), typography, hierarchy (missing,
skipped, or Word-invisible headings), spacing, tables, images and theme
consistency.
Workflow
- Content first: draft the document in Markdown, validate with the user.
- Choose base: the attached HTML template (converter route above), the brand
template from
template-manager (Document("template.docx") keeps styles,
headers, logos), or blank.
- Generate: fill the HTML and convert, or write the python-docx script when
there is no template.
- QA the filled HTML with
word_qa.py, fix the findings, run it again.
- Verify the file itself:
<navin-python> .navin/resources/tools/doc_check.py report.docx
blocks on leftover sample copy, unresolved {{placeholders}}, em dashes,
emoji, empty sections, missing headings and a missing title. Then look at
it: <navin-python> .navin/resources/tools/preview_document.py report.docx previews/
renders the pages (LibreOffice) into PNGs and a contact sheet.
- PDF version needed? Print the HTML source:
<navin-python> .navin/resources/tools/html2pdf.py document.html -o report.pdf
(same pages, real text layer, QA gate). Converting the DOCX is the fallback
when only the DOCX exists: html2pdf.py report.docx -o report.pdf uses
LibreOffice.
- The user's own
.docx (letterhead, contract template): keep it. Run
office_template.py inspect letter.docx to read its styles, sections,
header and footer and {{placeholders}}, then
office_template.py fill letter.docx --data values.json -o letter_out.docx
({{key}}, lists, {{image:logo|4cm}}, {{rows.field}} repeated table
rows). Formatting of the run that carried the placeholder is kept.
Rules
- Use named styles from the template rather than manual formatting everywhere - consistency and editability.
- FR typography in FR documents (see
proofreader).
- Complex layout (multi-column brochures) → consider
pdf-generator instead.
- For a legal contract, make governing law and dispute forum explicit configurable fields. Verify defined terms, party names, dates, obligations, clause cross-references, annexes and signature blocks as one coherent agreement.
- Contract templates are HTML sources: preserve the fully translated and completed HTML beside the exported DOCX (and any PDF), rather than treating HTML as disposable.
- Every generated contract must prominently state that it is a template and must be reviewed by qualified local counsel before signature.
1---2name: docx-generator3description: Create, read, and edit Word (.docx) files - styles, tables, images, headers, and templates - using python-docx. Use whenever a deliverable must be a Word document.4---56# DOCX Generator78## Overview910Produce real Word documents programmatically with `exec` + Python `python-docx`, which Navin ships. Do not run import checks or install anything up front: write the script, and only if it actually fails on a missing dependency, report it and ask before touching the environment.1112The document must stay editable: headings, paragraphs, tables and styles, never a page pasted as an image.1314## Language and locale gate (mandatory)1516- Before creating or exporting a DOCX, require the user to explicitly select the document language. Do not infer it from the prompt, UI language, template, names, or location. If it is absent, ask and stop generation.17- Carry that language through every heading, label, caption, table, header/footer, filename metadata, date, number and currency. Do not leave source-template text untranslated.18- Use locale-appropriate date, decimal, grouping and currency conventions consistently. Record the selected language/locale in the rerunnable generator data.19- For Arabic and other RTL languages, set paragraph bidi/RTL and right alignment, mirror layout where appropriate, use logical reading order, and choose fonts with the required glyph coverage (for example Noto Naskh Arabic, Noto Sans Arabic or Amiri). Embed or package fonts when the export route permits it; never silently substitute a font lacking glyphs.2021## Typography & icon rules (mandatory)2223- Never use em or en dashes (U+2014 / U+2013) in document text. Use commas, colons, periods or parentheses instead.24- Never use emoji or generic AI-style icons (📊 ✅ 💡 …) in documents. Use numbers, plain monochrome glyphs (✓ ✗) or clean layout elements instead.2526## HTML document template library2728Navin materializes the selected editable Word-style template inside the active29workspace (`document.html` with A4 `.page` sections + `metadata.json` + `image.png`30preview). When the runtime context contains a "Document Template Attachment",31the user picked one in the WebUI - it is mandatory. Fill that theme: replace32every sample string, keep the CSS, add images when you have them. Do not invent33another look. Three.js does not belong in a Word page:34351. For a report, letter, proposal, minutes or brief: write semantic JSON36 and let the engine render the HTML. Do not copy the lookbook37 `document.html`. That is how titles double, sample KPIs survive, and38 `image.png` sticks on the page.3940 ```json41 {42 "kind": "report",43 "theme": "executive",44 "brand": "Navin",45 "kicker": "Executive report",46 "title": "Revue strategique T3 2026",47 "subtitle": "Traction, risques, decisions demandees.",48 "meta": {49 "prepared_by": "Direction",50 "recipients": "Comite de direction",51 "date": "15 aout 2026"52 },53 "sections": [54 {55 "heading": "1. Synthese",56 "body": "Le trimestre confirme la trajectoire.",57 "kpis": [58 { "label": "ARR", "value": "4,2 M€", "meta": "+18% YoY" }59 ]60 }61 ]62 }63 ```6465 ```bash66 <navin-python> -m navin.documents.word_design render --document doc.json -o document.html67 ```6869 Section keys: `body` (string or list of paragraphs), `items` (bullets;70 `list`: `steps` / `check`), `kpis`, `table` (`headers`, `rows`, optional71 `total` row), `callout` (string or `{title, text, variant}`), `quote` +72 `author`, `insight`, `image` + `caption`, `subsections`. Common73 variants (`paragraphs`, `text`, `bullets`, `points`, `steps`, a nested74 `cover` object, `meta` as a list of lines) are understood too, but write75 the canonical keys.76 A missing image is omitted. Never leave `image.png`. Never invent a hex.77 A report or proposal with two or more headed sections gets a Sommaire78 page after the cover: a visible list plus `data-doc-toc="2-3"` so Word79 can refresh it. Letters and briefs skip it. A report without that80 field fails quality. Set `language` on the JSON.812. For a specialized contract template the user attached: read82 `metadata.json`, then replace **every** visible string. Keep the CSS.83 If any lookbook sentence remains, the document is rejected.843. Convert the filled file with the bundled converter, whose command is given85 in the runtime context:8687 ```bash88 <navin-python> .navin/resources/tools/html2docx.py document.html -o report.docx89 ```9091 Chromium measures the laid-out page, then headings become Word headings,92 lists become real lists, tables become real tables, and typography, colors,93 shading and borders follow. Banners repeated on every `.page` land in the94 running header, a "Page 3 / 8" footer becomes PAGE and NUMPAGES fields.95 Headings stay with the following paragraph. Table rows do not split across96 pages. Around a second per document. Useful flags: `--keep-fonts` to keep97 the CSS font names instead of their Office equivalents, `--chromium PATH`98 when no browser is found, `--keep-workdir DIR` to inspect intermediate files.99100 Word behaviour that CSS cannot express is requested with `data-doc-*` on101 the HTML, never with pixel coordinates:102103 - `data-doc-toc="1-3"` on a nav or heading: a real TOC field the reader104 can refresh in Word.105 - `data-doc-section="landscape"` on a `.page`: a new Word section in106 landscape, for wide tables and diagrams. Portrait is the default.107 - `data-doc-columns="2"` on a page or section: newspaper columns.108 - `data-doc-break="page"` on a heading: page break before.109 - `data-doc-keep="next"` or `"together"`: keep with next / keep lines.110 - `data-doc-caption="Figure"` plus `data-doc-bookmark="fig:name"`: a111 numbered caption Word can renumber. Point at it with112 `data-doc-ref="fig:name"`.113 - `data-doc-numbering="1-4"` on a page: Heading 1..4 numbered 1 / 1.1 / ...114 - `data-doc-cover="true"` on the first page: a different first-page header.115 - `data-doc-row="stack"` on a side-by-side row: read it top to bottom116 instead of as a table. Use it on a header band that holds the page117 title, because a heading buried in a table cell gets no outline level118 and never reaches the table of contents. A heading that is the row's own119 child is already handled; this is for one nested deeper.120121 Do not invent `{ "x": 12, "y": 40 }` layouts. Describe the document in122 HTML semantics and let html2docx decide the Word constructs.123124 Titles must be real `h1`-`h6` tags. A `div` styled large and bold looks125 right in the preview and arrives in Word as plain text: no navigation126 pane, no TOC line, no outline. Keep the class, change the tag.1274. Reopen the result and fix what needs it with python-docx. Never rebuild the128 whole design by hand, and never paste a rendered page as an image.1295. If the user asked for a PDF instead, render the same filled HTML (headless130 Chromium `--print-to-pdf` or ReportLab) rather than converting the DOCX.131132## Theme and design tokens133134Every Word template carries its palette and its fonts in one place, a135`<style id="navin-theme">` block of `--nv-*` custom properties, and the rest of136its CSS only reads them through `var()`. That is what makes a finished document137re-themable without touching a word of its content.138139```bash140<navin-python> .navin/resources/tools/word_design.py themes141<navin-python> .navin/resources/tools/word_design.py apply --theme luxury document.html142<navin-python> .navin/resources/tools/word_design.py audit document.html143```144145The 15 themes: `executive`, `consulting`, `corporate`, `modern`, `minimal`,146`technology`, `ai`, `financial`, `annual-report`, `startup`, `proposal`,147`research`, `technical`, `government`, `luxury`.148149When you write or extend CSS in a document, use the tokens instead of colours:150151- `--nv-surface` the paper, `--nv-bg` the canvas around it152- `--nv-text` running text, `--nv-heading` titles, `--nv-muted` secondary text153- `--nv-accent` the fill behind table headers and bands, `--nv-on-accent` the154 text that sits on it155- `--nv-accent-2` the secondary accent for rules and markers, and156 `--nv-accent-2-ink` when that accent has to be read as small text157- `--nv-border` hairlines, `--nv-subtle` zebra rows, `--nv-soft` callout tints158- `--nv-status-high|mid|low|good` (+ `-soft`) for risk, priority and status,159 which keep their meaning across themes160- `--nv-font-heading`, `--nv-font-body`, `--nv-font-mono`, `--nv-radius`161162On a dark panel, dim with `rgba(255,255,255,.85)` rather than a named grey, so163the panel still works when the theme changes its accent.164165`audit` fails when a literal colour is left outside the theme block, which is166exactly the thing that survives a theme change and ruins it. Run it before167delivering. `_engine/navin-word.css` holds ready-made token-driven components:168callouts (info, important, warning, recommendation, key finding), insight169blocks, KPI rows, big numbers, figures with captions, checklists, numbered170steps, pros/cons, signature blocks.171172## Core recipes173174```python175from docx import Document176from docx.shared import Pt, Cm, RGBColor177from docx.enum.text import WD_ALIGN_PARAGRAPH178179doc = Document() # or Document("template.docx") to inherit styles180doc.add_heading("Titre", level=1)181p = doc.add_paragraph("Texte avec ")182p.add_run("gras").bold = True183184# Table with header185table = doc.add_table(rows=1, cols=3)186table.style = "Light Grid Accent 1"187hdr = table.rows[0].cells188hdr[0].text, hdr[1].text, hdr[2].text = "Col A", "Col B", "Col C"189row = table.add_row().cells190191doc.add_picture("logo.png", width=Cm(4))192doc.add_page_break()193194# Header/footer195section = doc.sections[0]196section.header.paragraphs[0].text = "Navinspire - Confidentiel"197198doc.save("out.docx")199```200201Reading/editing: iterate `doc.paragraphs` and `doc.tables`; modify `run.text` to preserve formatting. For find-replace across runs, join runs carefully (formatting lives at run level).202203## No empty blocks (mandatory)204205Every block of a template gets real content, or it is removed and the layout206rebalanced. No sample text, no placeholder figures, no empty table row, no207orphan image frame. Before delivering, reopen the file and read it through:208leftover sample copy, untranslated labels and empty cells are failures.209210## Visual QA (before converting)211212The filled HTML is laid out in Chromium and read the way a reader would: what213spills past the margin, what is unreadable, what page is half empty, what214heading Word will never see. Every page comes back scored out of 100 with the215thing to change.216217```bash218<navin-python> .navin/resources/tools/word_qa.py document.html219<navin-python> .navin/resources/tools/word_qa.py document.html --json220```221222Run it on the filled HTML before converting, because that is the stage where a223finding is still fixable by editing the source. It exits non-zero when the224weakest page falls under 85: the weakest page is what the reader notices, so an225average is not enough. Fix what it names and run it again - do not deliver a226document that is still asking for rework.227228It also writes `qa.json` beside the document, and `html2docx` refuses to229convert while that report says fail. Fix the pages and re-run `word_qa.py`230until it passes; `--force` exists only for a user who explicitly accepts the231document as is.232233It covers layout (overflow, orphan headings, near-empty pages), readability234(contrast under 4.5:1, unreadably small text), typography, hierarchy (missing,235skipped, or Word-invisible headings), spacing, tables, images and theme236consistency.237238## Workflow2392401. Content first: draft the document in Markdown, validate with the user.2412. Choose base: the attached HTML template (converter route above), the brand242 template from `template-manager` (`Document("template.docx")` keeps styles,243 headers, logos), or blank.2443. Generate: fill the HTML and convert, or write the python-docx script when245 there is no template.2464. QA the filled HTML with `word_qa.py`, fix the findings, run it again.2475. Verify the file itself: `<navin-python> .navin/resources/tools/doc_check.py report.docx`248 blocks on leftover sample copy, unresolved `{{placeholders}}`, em dashes,249 emoji, empty sections, missing headings and a missing title. Then look at250 it: `<navin-python> .navin/resources/tools/preview_document.py report.docx previews/`251 renders the pages (LibreOffice) into PNGs and a contact sheet.2526. PDF version needed? Print the HTML source:253 `<navin-python> .navin/resources/tools/html2pdf.py document.html -o report.pdf`254 (same pages, real text layer, QA gate). Converting the DOCX is the fallback255 when only the DOCX exists: `html2pdf.py report.docx -o report.pdf` uses256 LibreOffice.2577. The user's own `.docx` (letterhead, contract template): keep it. Run258 `office_template.py inspect letter.docx` to read its styles, sections,259 header and footer and `{{placeholders}}`, then260 `office_template.py fill letter.docx --data values.json -o letter_out.docx`261 (`{{key}}`, lists, `{{image:logo|4cm}}`, `{{rows.field}}` repeated table262 rows). Formatting of the run that carried the placeholder is kept.263264## Rules265266- Use named styles from the template rather than manual formatting everywhere - consistency and editability.267- FR typography in FR documents (see `proofreader`).268- Complex layout (multi-column brochures) → consider `pdf-generator` instead.269- For a legal contract, make governing law and dispute forum explicit configurable fields. Verify defined terms, party names, dates, obligations, clause cross-references, annexes and signature blocks as one coherent agreement.270- Contract templates are HTML sources: preserve the fully translated and completed HTML beside the exported DOCX (and any PDF), rather than treating HTML as disposable.271- Every generated contract must prominently state that it is a template and must be reviewed by qualified local counsel before signature.