docx/vertical-alignment--cell--bottom

Vertical alignment (<w:vAlign>) exercised across three ST_VerticalJc values (top, center, bottom) in two contexts: table cell (<w:tcPr>/<w:vAlign>) and page section (<w:sectPr>/<w:vAlign>).

Library verdicts: python-docx: — docxjs: —

Metadata

Feature iddocx/vertical-alignment--cell--bottom
Formatdocx
Categorypage-layout
Familydocx/vertical-alignment
Axis valuescontext=cell, value=bottom
Spececma-376-5-part-1 § 17.4.83 w:vAlign

XPath assertions

ID / partPredicateXPathpython-docxdocxjs
valign-cell-bottom
word/document.xml
equal = bottom
The <w:vAlign> in the appropriate context (tcPr for cell, sectPr for section) must carry the expected ST_VerticalJc value.
//w:tc/w:tcPr/w:vAlign/@w:val | //w:sectPr/w:vAlign/@w:val

Render assertions

IDPredicateSelectorpython-docxdocxjs
visual-matches-libreoffice-rendervisual_ssim
Playwright screenshot of the rendered output must score >=85% SSIM against the LibreOffice-rendered reference PNG.

Generator source

scripts/gen_vertical_alignment.py

#!/usr/bin/env python3
"""Generate a ``fixtures/docx/vertical-alignment*.docx`` fixture.

Parameterised: called with ``--context cell|section --value top|center|bottom
--out <path>``.

For ``--context cell``: writes a one-row/one-column table whose single cell
carries ``<w:tcPr>/<w:vAlign w:val="..."/>`` (ECMA-376 Part 1 clause 17.4.83).

For ``--context section``: writes a one-paragraph document whose root section
carries ``<w:sectPr>/<w:vAlign w:val="..."/>`` (ECMA-376 Part 1 clause
17.6.22). python-docx does not expose a ``Section.vertical_alignment``
property, so the generator writes ``w:vAlign`` directly via OxmlElement.

The ``features/docx/vertical-alignment.json`` manifest's
``generator.arg_template`` drives these args at expansion time; the
Cartesian product of 2 contexts x 3 values yields 6 fixtures.
"""

from __future__ import annotations

import argparse
from pathlib import Path

from docx import Document
from docx.enum.table import WD_CELL_VERTICAL_ALIGNMENT
from docx.oxml.ns import qn
from docx.oxml import OxmlElement

_REPO_ROOT = Path(__file__).resolve().parent.parent

_CELL_VALIGN = {
    "top": WD_CELL_VERTICAL_ALIGNMENT.TOP,
    "center": WD_CELL_VERTICAL_ALIGNMENT.CENTER,
    "bottom": WD_CELL_VERTICAL_ALIGNMENT.BOTTOM,
}


def _set_section_valign(sectPr, value: str) -> None:
    """Write ``<w:vAlign w:val=value/>`` onto ``sectPr``.

    python-docx has no ``Section.vertical_alignment`` property, so we manage
    the element directly. Any existing ``w:vAlign`` child is replaced.
    """
    existing = sectPr.find(qn("w:vAlign"))
    if existing is not None:
        sectPr.remove(existing)
    valign = OxmlElement("w:vAlign")
    valign.set(qn("w:val"), value)
    sectPr.append(valign)


def _write(context: str, value: str, out: Path) -> None:
    if value not in _CELL_VALIGN:
        raise ValueError(f"unknown value {value!r}; expected top|center|bottom")

    doc = Document()

    if context == "cell":
        table = doc.add_table(rows=1, cols=1)
        cell = table.rows[0].cells[0]
        cell.text = "Vertically aligned"
        cell.vertical_alignment = _CELL_VALIGN[value]
    elif context == "section":
        section = doc.sections[-1]
        _set_section_valign(section._sectPr, value)
        doc.add_paragraph("Section-aligned content")
    else:
        raise ValueError(f"unknown context {context!r}; expected 'cell' or 'section'")

    out.parent.mkdir(parents=True, exist_ok=True)
    doc.save(out, reproducible=True)


def main() -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument(
        "--context",
        required=True,
        choices=("cell", "section"),
        help="Where the vertical alignment lives: table cell or page section.",
    )
    parser.add_argument(
        "--value",
        required=True,
        choices=("top", "center", "bottom"),
        help="ST_VerticalJc value.",
    )
    parser.add_argument(
        "--out",
        required=True,
        help="Output .docx path (absolute, or repo-relative).",
    )
    args = parser.parse_args()

    out_path = Path(args.out)
    if not out_path.is_absolute():
        out_path = _REPO_ROOT / out_path
    _write(args.context, args.value, out_path)
    print(out_path)
    return 0


if __name__ == "__main__":
    raise SystemExit(main())

Manifest (expanded)

{
  "$schema": "../manifest.schema.json",
  "id": "docx/vertical-alignment--cell--bottom",
  "kind": "literal",
  "title": "Vertical alignment",
  "format": "docx",
  "category": "page-layout",
  "summary": "Vertical alignment (<w:vAlign>) exercised across three ST_VerticalJc values (top, center, bottom) in two contexts: table cell (<w:tcPr>/<w:vAlign>) and page section (<w:sectPr>/<w:vAlign>).",
  "spec": {
    "source": "ecma-376-5-part-1",
    "clause": "17.4.83",
    "element": "w:vAlign",
    "rnc_reference": "spec/ecma-376-5/part-1/rnc/WordprocessingML.rnc",
    "xsd_reference": "spec/ecma-376-5/part-1/xsd/wml.xsd",
    "notes": "Two distinct uses of <w:vAlign w:val=\"...\"/>. Clause 17.4.83 defines the table-cell form as a child of <w:tcPr> controlling how cell contents stack within the cell box. Clause 17.6.22 defines the section form as a child of <w:sectPr> controlling how content stacks vertically on the page. Both share the ST_VerticalJc enumeration; this parameterised manifest covers the three values Word routinely emits (top, center, bottom; 'both' is rare in practice). Cartesian product: 2 contexts x 3 values = 6 fixtures docx/vertical-alignment--<context-id>--<value-id>.docx."
  },
  "fixtures": {
    "machine": "docx/vertical-alignment--cell--bottom"
  },
  "generator": {
    "python": "scripts/gen_vertical_alignment.py",
    "arg_template": "--context {context.id} --value {value.val} --out fixtures/docx/vertical-alignment--{context.id}--{value.id}.docx"
  },
  "_expansion": {
    "parent_id": "docx/vertical-alignment",
    "bindings": {
      "context": "cell",
      "value": "bottom"
    }
  },
  "assertions": [
    {
      "id": "valign-cell-bottom",
      "part": "word/document.xml",
      "namespaces": {
        "w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
      },
      "xpath": "//w:tc/w:tcPr/w:vAlign/@w:val | //w:sectPr/w:vAlign/@w:val",
      "must": "equal",
      "value": "bottom",
      "description": "The <w:vAlign> in the appropriate context (tcPr for cell, sectPr for section) must carry the expected ST_VerticalJc value."
    }
  ],
  "render_assertions": [
    {
      "id": "visual-matches-libreoffice-render",
      "kind": "visual_ssim",
      "min_ssim": 0.5,
      "description": "Playwright screenshot of the rendered output must score >=85% SSIM against the LibreOffice-rendered reference PNG."
    }
  ]
}

Fixture

Download vertical-alignment--cell--bottom.docx (18.7 KB)

Reference preview

Reference (machine, page 1 PNG)

docx/vertical-alignment--cell--bottom page 1 reference

Reference PDF


Download PDF Open in PDF.js

Spec notes

Two distinct uses of <w:vAlign w:val="..."/>. Clause 17.4.83 defines the table-cell form as a child of <w:tcPr> controlling how cell contents stack within the cell box. Clause 17.6.22 defines the section form as a child of <w:sectPr> controlling how content stacks vertically on the page. Both share the ST_VerticalJc enumeration; this parameterised manifest covers the three values Word routinely emits (top, center, bottom; 'both' is rare in practice). Cartesian product: 2 contexts x 3 values = 6 fixtures docx/vertical-alignment--<context-id>--<value-id>.docx.