Implement DOCX export pipeline (Tier 3 fidelity)#12
Merged
Conversation
…argin cursor, End key, split heading level, and add tests - Bug 1: Cmd+key (macOS) now triggers shortcuts instead of inserting the character. Detects Modifiers::SUPER (winit's mapping for the macOS Command key) in addition to CTRL/META. Implements Cmd+A select-all. - Bug 2+3: Arrow-up/down now crosses paragraph boundaries when the target y-coordinate falls outside the current page's content area. Adds find_prev_para_data / find_next_para_data helpers and guards hit_test_page calls with a target_y >= margins.top check to avoid matching Fragment B's negative origin. - Bug 4: End key now uses line_end_offset (new ParagraphLayout method) which returns text_range().end trimmed of trailing '\n', rather than hit_test_point(100_000.0) which could land at the visual start of the next line. - Bug 5: split_block now copies KEY_HEADING_LEVEL from the source block to the new block, preserving heading level (and para/char props) on Enter in headings. - Tests: line_end_offset unit tests in loki-layout; cross-paragraph nav tests in loki-text; split_block style-preservation integration tests (heading level, para props, char props, independence) in loki-doc-model. https://claude.ai/code/session_01WFkcgZx556Fn94tbqnM3nh
build_chain_layouts produced a zero-height stub (parley_layout=None) for any non-StyledPara block (Heading, Para, Plain) that appeared in a keep-with-next chain. That stub was pushed into editing_data.paragraphs, so resolve_cursor_paint found the block but cursor_rect always returned None — causing the repeated WARN log and an invisible cursor. Fix: convert Heading/Para/Plain blocks to StyledParagraph via the same synthesize_heading_para/synthesize_plain_para helpers used in flow_block, so they receive a full layout_paragraph call and a valid parley_layout. Non-text blocks (HR, table, etc.) still use the zero-height stub since the cursor cannot land in them. https://claude.ai/code/session_01WFkcgZx556Fn94tbqnM3nh
layout_paragraph returned early with parley_layout=None whenever text_content was empty. Every empty paragraph in a document (used as blank-line spacers, section dividers, etc.) therefore had no Parley layout, causing resolve_cursor_paint to warn on every render frame while the cursor was in one. Fix: when preserve_for_editing=true and text is empty, build a phantom Parley layout from a single space character. The space forces Parley to produce one line with the paragraph's resolved font metrics, so cursor_rect(0) can return a properly-sized, visible caret. height and line_boundaries remain zero so the empty paragraph still takes no vertical space in the flow (behaviour unchanged from read-only mode). The phantom layout is only built in editing mode; read-only rendering keeps the existing fast path. https://claude.ai/code/session_01WFkcgZx556Fn94tbqnM3nh
ODF fixes: - ODF-1: Map fo:border/fo:padding shorthand → ParaProps.border_*/padding_* - ODF-2: Map OdfTabStop list → ParaProps.tab_stops (TabLeader::None always) - ODF-3: Map fo:background-color → ParaProps.background_color - ODF-4: Map text:text-shadow → CharProps.shadow (non-"none" = true) - ODF-5: Parse style:table-column-properties/style:column-width; pre-build col_style_widths map in OdfMappingContext; resolve per-column widths in map_table with proportional(1.0) fallback - ODF-6: Filter is_covered=true cells in map_table with TODO comment for row_span propagation OOXML fixes: - OOXML-1: Add TableWidth enum (Fixed/Percent/Auto) to doc-model; add Table.width field; map w:tblW in table mapper; parse w:tblPr/w:tblW in document reader (was silently skipped) - OOXML-2: Add DocumentSettings struct with default_tab_stop_pt; add Document.settings field; forward w:defaultTabStop from DocxSettings; add TODO in layout para.rs to use it once threaded through - OOXML-3: Add ParagraphStyle.next_style_id; forward DocxStyle.next in styles mapper Round-trip tests: - loki-ooxml: table width maps to TableWidth::Fixed(250pt); settings maps default_tab_stop_pt ≈ 36pt; fixture extended with w:tblW and settings.xml - loki-odf: border_top, tab_stops (≥2), background_color all set after import; fixture extended with BorderPara/TabPara/BgPara styles https://claude.ai/code/session_01WFkcgZx556Fn94tbqnM3nh
Documents all five architecture decisions for implementing OOXML and ODF export: crate structure (Option A, add write modules to existing crates), intermediate model (Option B, direct serialization from loki-doc-model), fidelity tier (Tier 3, content-only for v0.1), export source (Option A, Document snapshot), and OPC/ZIP infrastructure (loki-opc for OOXML, zip crate directly for ODF, consistent with ADR-0001). Includes: stub state audit, LoroDoc bridge completeness table, crate search results for docx/odf write libraries, minimum part lists for .docx and .odt, 8-step implementation order, v0.1 scope table, and 5 open questions. https://claude.ai/code/session_01WFkcgZx556Fn94tbqnM3nh
Parse all six header/footer variants (default, first-page, even-page) from `style:master-page` in styles.xml and map them into the `PageLayout` header/footer fields via the full inline paragraph parser. Deleted the simplified `collect_para_text` helper in favour of the existing `read_paragraph` function so field codes, spans, and hyperlinks in headers are correctly preserved. Adds five round-trip tests and a layout integration test confirming `page.header_items` / `footer_items` are populated by `loki-layout`. https://claude.ai/code/session_01WFkcgZx556Fn94tbqnM3nh
Add `compute_v_merge_spans` to the OOXML table mapper. Pass 1 expands each row into a flat grid accounting for `w:gridSpan`, recording vMerge state per grid column. Pass 2 scans each column for `Restart` cells and counts consecutive `Continue` cells below, setting `row_span` on the restart cell and removing continuation cells from the output row. The reader already parsed `DocxVMerge::Restart` / `Continue` correctly, so no reader changes were required (OOXML §17.4.84). Also adds a TODO comment in `loki-layout` noting that `row_span` is not yet applied in the visual layout engine. Covered by 5 unit tests in the mapper and 1 round-trip integration test that asserts row_span=2 and continuation-cell removal on a 2×3 fixture. https://claude.ai/code/session_01WFkcgZx556Fn94tbqnM3nh
Tracks style:master-page-name transitions during ODF body mapping and emits a new document section whenever the active master page changes. Each section carries the correct PageLayout (page dimensions, margins, and headers/footers) from its master page. Changes: - Add `master_page_name: Option<String>` to `OdfStyle` (model + all construction sites), parsed from `style:master-page-name` in the styles reader for both styles.xml and content.xml auto-styles. - Add `resolve_master_page_name` helper that walks the `style:parent-style-name` inheritance chain to find a master page transition, with depth-limited cycle protection. - Restructure `map_document` to iterate body children inline, detecting transitions on paragraphs/headings and flushing accumulated blocks into a completed `Section` when the master page changes. - Rename `resolve_page_layout` to `resolve_page_layout_by_name` so it accepts an explicit master page name rather than always selecting the first/Standard master page. - Cover with 5 unit tests for `resolve_master_page_name` and 2 round-trip tests (portrait→landscape section split; no spurious breaks for single-master documents). https://claude.ai/code/session_01WFkcgZx556Fn94tbqnM3nh
…rection) Add parsing and mapping of w:tcMar (cell margins), w:vAlign (vertical alignment), and w:textDirection to the doc-model CellProps. Cell padding is applied in the layout engine to shrink the content area accordingly. https://claude.ai/code/session_01WFkcgZx556Fn94tbqnM3nh
… borders) Parse style:table-cell-properties from ODF table-cell styles and map all properties (padding, vertical-align, writing-mode, background-color, borders) to the format-neutral CellProps. ODF documents now produce the same padded, aligned, coloured cell layout as OOXML. https://claude.ai/code/session_01WFkcgZx556Fn94tbqnM3nh
Implements `DocxExport::export` by assembling a spec-conformant `.docx` ZIP via `loki-opc`. All Tier-3 content is serialized: paragraphs, headings (H1–H6), bullet and ordered lists, tables (with cell padding/valign), horizontal rules, code blocks, line blocks, and div/blockquote containers. Inline formatting covers bold, italic, underline, strikethrough, super/subscript, small caps, code, quoted text, span, styled runs, and bookmarks. Section properties (page size, margins, orientation) are emitted as `w:sectPr` for every section. New modules: - `write/xml.rs` – low-level XML writer helpers and unit converters - `write/document.rs` – `word/document.xml` serializer - `write/styles.rs` – `word/styles.xml` serializer (docDefaults + H1–H6) - `write/numbering.rs` – numbering state + `word/numbering.xml` serializer - `write/assembly.rs` – OPC package assembly and ZIP output Tier-4 content (images, footnotes, hyperlinks, fields) is silently omitted per ADR-0007 Decision 3. All 134 loki-ooxml tests pass. https://claude.ai/code/session_01WFkcgZx556Fn94tbqnM3nh
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR implements the DOCX export functionality for
loki-ooxml, converting aloki-doc-model::Documentinto a conformant.docxfile. The implementation targets Tier 3 fidelity (basic block types, primary character formatting, document structure) as specified in ADR-0007.Key Changes
Core Export Infrastructure
loki-ooxml/src/docx/write/— New module implementing the complete DOCX serialization pipeline:assembly.rs: Top-level OPC package assembly, wires up relationships and content typesdocument.rs: Serializes sections and blocks toword/document.xml; handles headers/footersstyles.rs: Generatesword/styles.xmlwith built-in headings and catalog stylesnumbering.rs: Tracks and serializes list definitions toword/numbering.xmlfootnotes.rs: Serializes footnotes/endnotes to their respective partscollector.rs: Accumulates export state (media, hyperlinks, notes) during serializationxml.rs: Low-level XML writing helpers and namespace constantsmedia.rs: Image data extraction from data URIsmod.rs: Module organizationloki-ooxml/src/docx/export.rs— Replaced stub with functional implementation callingwrite::assemblyDocument Model Enhancements
loki-doc-model/src/settings.rs(new) —DocumentSettingsfor global document-wide configurationloki-doc-model/src/content/table/col.rs— AddedTableWidthenum for table-level width specificationsloki-doc-model/src/content/table/row.rs— AddedCellTextDirectionenum for cell text directionloki-doc-model/src/style/para_style.rs— Addednext_style_idfield for style chainingloki-doc-model/src/document.rs— IntegratedDocumentSettingsintoDocumentImport Improvements
loki-ooxml/src/docx/mapper/table.rs— Enhanced table mapping:w:vMerge) spansrow_spancomputation and continuation cell removalloki-ooxml/src/docx/reader/document.rs— Extended table parsing to capture:w:tblW)w:tcMar)loki-odf/src/odt/mapper/document.rs&props.rs— ODF import enhancements:Testing & Documentation
loki-ooxml/tests/export_p0.rs(new) — Integration tests for P0 features (hyperlinks, images, footnotes)loki-ooxml/tests/export_p1.rs(new) — Integration tests for P1 features (paragraph formatting, lists, tables)docs/adr/0007-export-pipeline.md(new) — Architecture Decision Record documenting design choicesdocs/export-gap-audit-2026-05-10.md(new) — Comprehensive gap audit of export fidelity vs. importMinor Improvements
loki-text/src/editing/navigation.rs— Addedfind_prev_para_datahelper for backward paragraph navigationloki-layout/src/para.rs— Addedline_end_offsetmethod for linehttps://claude.ai/code/session_01WFkcgZx556Fn94tbqnM3nh