Layout¶
layout/ ships five documented python-docx gaps. None of them
duplicate functionality python-docx already exposes (orientation,
margins, page size, per-section header / footer, add_section).
For the calls, see the layout guide.
set_columns(section, num, *, space, separator, widths) in
layout/columns.py emits <w:cols w:num=... w:space=... w:sep=...>
into the section's sectPr. Idempotent — replaces any existing
<w:cols>. With widths supplied, it emits per-column <w:col>
children with w:equalWidth="0" so Word reads widths from the children
rather than the parent w:space.
insert_section_break(paragraph, *, start_type) in
layout/breaks.py handles the case Document.add_section does not —
inserting a break mid-document. The algorithm clones the trailing
body-level <w:sectPr> (the document's "sentinel"), sets <w:type>
on the clone to the requested start kind, and calls python-docx's
CT_P.set_sectPr(clone) to embed it in the chosen paragraph's pPr.
The new section inherits all properties (page size, margins, header /
footer references) from the sentinel; both sections render with the
same headers and footers unless the caller mutates the returned
Section proxy.
enable_distinct_even_odd_headers(doc) in layout/settings.py
writes <w:evenAndOddHeaders/> into settings.xml via the
schema-strict insertion pattern. This flag is
constantly confused with two other things: the per-section <w:titlePg>
(controls whether first page has a distinct header/footer, exposed by
python-docx as Section.different_first_page_header_footer), and the
per-section header/footer reference types (w:headerReference w:type="even",
which Word reads because the doc-level flag is set). All three are
required for a real even-page-distinct workflow. disable_… removes
the doc-level element; both functions are idempotent.
set_line_numbering(section, *, count_by, restart, start, distance)
in layout/line_numbering.py emits <w:lnNumType> into the section's
sectPr — Word's mechanism for the marginal line numbers that legal
and contract documents require. Schema-strict via
core.insert_before_first_anchor; the element lands in its
ECMA-376 17.6.17 slot regardless of which other sectPr children
exist. restart is the only argument that validates eagerly (one of
"newPage" / "newSection" / "continuous"); count_by and start
must be ≥ 1. Idempotent.
set_page_borders(section, *, top, bottom, left, right) in
layout/borders.py emits <w:pgBorders> from a Border dataclass
per side (style, size in eighths of a point, color, space in
twips). Sides set to None are omitted from the emitted XML; passing
all four as None removes the element rather than emitting an empty
container. Schema-strict, idempotent.
The same Border shape drives table and cell borders, with
one caveat about space documented there.