Skip to content

feat(office): add bounded DOCX page layout contract #140

Description

@seonghobae

Buyer-visible document-fidelity gap

Inkspan Office currently renders DOCX headings, paragraphs, rich runs, lists, tables, informative PNG figures, hyperlinks, and page breaks, but the request contract deliberately exposes no page size, orientation, or margins. Business reports, proposals, handoffs, and print-ready documents therefore inherit the library template's implicit page setup rather than carrying an explicit deterministic layout contract.

This is a bounded next-release slice. The protected 0.6.0 source candidate must remain unchanged until its release/registry acceptance is complete; implementation may proceed on a Draft branch but must not merge into protected main before the 0.6.0 release boundary is resolved.

Primary technical basis

  • python-docx 1.2.0 official section documentation: https://python-docx.readthedocs.io/en/latest/user/sections.html — a Word section owns page dimensions, orientation, and margins; changing to landscape requires the corresponding page dimensions.
  • python-docx 1.2.0 section API: https://python-docx.readthedocs.io/en/stable/api/section.htmlorientation, page_width, page_height, and edge-margin properties are read/write section properties.
  • ECMA-376 / Office Open XML WordprocessingML section properties (w:sectPr, page size and page margins) remain the normative OOXML representation; repository doctoring must cite the current official ECMA publication in APA 7 style.

Required contract

  1. Add optional top-level DOCX page_layout; XLSX/PPTX contracts remain unchanged.
  2. When present, require one exact complete object rather than merging ambiguous partial defaults:
    • paper_size: a4 or letter;
    • orientation: portrait or landscape;
    • margins_mm: object containing integer top, right, bottom, and left.
  3. Bound every margin to 0–100 mm. Booleans, floats, strings, nulls, aliases, case variants, missing edges, and unknown fields fail closed.
  4. Map A4 to exact 210 × 297 mm and US Letter to exact 8.5 × 11 in dimensions. Landscape swaps physical width/height and sets the Word orientation property; do not guess from width alone.
  5. Apply layout only to the document's existing single default section. Do not add multi-section authoring, headers/footers, gutter, columns, binding, mirrored margins, page numbers, or arbitrary page dimensions in this slice.
  6. Omitted page_layout preserves the current template/default behavior exactly.
  7. Preserve deterministic bytes, network-free rendering, atomic publication, strict JSON resource limits, and existing Office validation/error-redaction boundaries.
  8. Add schema tests, runtime round-trip tests through python-docx, fail-closed invalid-input tests, deterministic-output and no-partial-publication tests.
  9. Keep production statement/branch coverage and public docstring coverage at 100% on Python 3.11–3.14.
  10. Add Office buyer documentation, an ADR, standards/APA-7 doctoring, traceability, and CHANGELOG only when the branch can do so without racing the active canonical documentation writer. If documentation paths are still leased elsewhere, runtime/test work must remain path-disjoint and documentation reconciliation must wait rather than creating competing authority.

Non-goals

  • No arbitrary OOXML or raw section XML.
  • No printer selection or PDF service.
  • No CSS-to-Word page-layout translation.
  • No multiple sections or mixed portrait/landscape pages.
  • No network, filesystem input, credentials, model calls, persistence, tenant, or host authorization authority.

Test-first acceptance

  • RED from the exact protected source generation: a permanent schema/runtime test proves page_layout is currently rejected as an unknown DOCX field.
  • GREEN only after the narrow public schema and renderer apply exact bounded section settings and the focused/full Office suites pass.
  • The branch stays Draft and unmerged while 0.6.0 release issue release: reconcile Office and npm versions before next stable registry publication #118 remains unresolved, so next-release development does not silently alter the current stable candidate.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions