Set a run's typeface on a specific w:rFonts script slot (ascii or hAnsi) across ten common font families.
| Library | Pass | Fail | Pending |
|---|---|---|---|
python-docx | 0 | 0 | 20 |
docxjs | 0 | 0 | 20 |
font (10 values): arial, times, calibri, cambria, georgia, verdana, tahoma, helvetica, courier, consolasslot (2 values): ascii, hansiscripts/gen_font_family_param.py — runs with --arg_template --font "{font.name}" --slot {slot.attr} --out fixtures/docx/font-family-param--{font.id}--{slot.id}.docx
#!/usr/bin/env python3
"""Generate a ``fixtures/docx/font-family-param--<font>--<slot>.docx`` fixture.
Parameterised generator for the ``docx/font-family-param`` feature family.
One invocation writes a single fixture with the given font name applied to
a single ``w:rFonts`` slot attribute (``w:ascii`` or ``w:hAnsi``).
Usage::
python scripts/gen_font_family_param.py \
--font "Arial" \
--slot ascii \
--out fixtures/docx/font-family-param--arial--ascii.docx
``--font`` is the typeface name written into the script-slot attribute.
``--slot`` is the local name of the ``w:rFonts`` attribute to set; the two
slots exercised by this family are:
- ``ascii`` -> ``w:ascii`` (Latin script)
- ``hAnsi`` -> ``w:hAnsi`` (high-ANSI aka Western European extended)
python-docx's ``run.font.name`` sets the ascii + hAnsi + cs + eastAsian
slots together, so this generator bypasses that helper and manipulates
``w:rFonts`` directly via :class:`OxmlElement` in order to exercise a
single slot per fixture.
"""
from __future__ import annotations
import argparse
from pathlib import Path
from docx import Document
from docx.oxml import OxmlElement
from docx.oxml.ns import qn
_REPO_ROOT = Path(__file__).resolve().parent.parent
def _write(font: str, slot: str, out: Path) -> None:
doc = Document()
paragraph = doc.add_paragraph()
run = paragraph.add_run("Font test text")
# python-docx's run.font.name sets ascii+hAnsi+cs+eastAsian together — use
# OxmlElement directly so we can exercise a single slot.
rPr = run._r.get_or_add_rPr()
existing = rPr.find(qn("w:rFonts"))
if existing is not None:
rPr.remove(existing)
rFonts = OxmlElement("w:rFonts")
rFonts.set(qn(f"w:{slot}"), font)
rPr.append(rFonts)
out.parent.mkdir(parents=True, exist_ok=True)
doc.save(out, reproducible=True)
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument(
"--font",
required=True,
help="Font family name to write into the slot attribute, e.g. 'Arial'.",
)
parser.add_argument(
"--slot",
required=True,
help="w:rFonts slot local-name to populate, e.g. 'ascii' or 'hAnsi'.",
)
parser.add_argument(
"--out",
required=True,
help="Repo-relative or absolute output path for the .docx.",
)
args = parser.parse_args()
out_path = Path(args.out)
if not out_path.is_absolute():
out_path = _REPO_ROOT / out_path
_write(args.font, args.slot, out_path)
print(out_path)
return 0
if __name__ == "__main__":
raise SystemExit(main())
{
"$schema": "../manifest.schema.json",
"id": "docx/font-family-param",
"kind": "parameterised",
"title": "Font family (parameterised)",
"format": "docx",
"category": "text-formatting",
"summary": "Set a run's typeface on a specific w:rFonts script slot (ascii or hAnsi) across ten common font families.",
"spec": {
"source": "ecma-376-5-part-1",
"clause": "17.3.2.25",
"element": "w:rFonts",
"rnc_reference": "spec/ecma-376-5/part-1/rnc/WordprocessingML.rnc",
"xsd_reference": "spec/ecma-376-5/part-1/xsd/wml.xsd",
"notes": "<w:rFonts/> carries per-script font slot attributes: w:ascii, w:hAnsi, w:cs, w:eastAsia (plus theme-named w:asciiTheme, etc.). This parameterised manifest expands into 10 fonts x 2 slots = 20 fixtures, each populating a single slot via OxmlElement so the ascii and hAnsi slots can be asserted independently (python-docx's run.font.name normally sets all four at once). The literal companion manifest docx/font-family remains unchanged for the pre-parameterised shape."
},
"fixtures": {
"machine": "docx/font-family-param"
},
"generator": {
"python": "scripts/gen_font_family_param.py",
"arg_template": "--font \"{font.name}\" --slot {slot.attr} --out fixtures/docx/font-family-param--{font.id}--{slot.id}.docx"
},
"parameters": {
"font": [
{
"id": "arial",
"name": "Arial"
},
{
"id": "times",
"name": "Times New Roman"
},
{
"id": "calibri",
"name": "Calibri"
},
{
"id": "cambria",
"name": "Cambria"
},
{
"id": "georgia",
"name": "Georgia"
},
{
"id": "verdana",
"name": "Verdana"
},
{
"id": "tahoma",
"name": "Tahoma"
},
{
"id": "helvetica",
"name": "Helvetica"
},
{
"id": "courier",
"name": "Courier New"
},
{
"id": "consolas",
"name": "Consolas"
}
],
"slot": [
{
"id": "ascii",
"attr": "ascii"
},
{
"id": "hansi",
"attr": "hAnsi"
}
]
},
"assertions_template": [
{
"id": "rfonts-{slot.id}-is-{font.id}",
"part": "word/document.xml",
"namespaces": {
"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
},
"xpath": "//w:r[w:t[normalize-space()='Font test text']]/w:rPr/w:rFonts/@w:{slot.attr}",
"must": "equal",
"value": "{font.name}",
"description": "The selected w:rFonts slot must carry the expected typeface name."
}
]
}