docx/font-color-theme

Set a run's foreground color to a theme colour slot, optionally shifted by a themeTint or themeShade.

Cases (84)

Feature IDAxis bindingspython-docxdocxjs
docx/font-color-theme--base--accent1shift=base, theme=accent1pass
docx/font-color-theme--base--accent2shift=base, theme=accent2pass
docx/font-color-theme--base--accent3shift=base, theme=accent3pass
docx/font-color-theme--base--accent4shift=base, theme=accent4pass
docx/font-color-theme--base--accent5shift=base, theme=accent5pass
docx/font-color-theme--base--accent6shift=base, theme=accent6pass
docx/font-color-theme--base--background1shift=base, theme=background1pass
docx/font-color-theme--base--background2shift=base, theme=background2pass
docx/font-color-theme--base--followed-hyperlinkshift=base, theme=followed-hyperlinkpass
docx/font-color-theme--base--hyperlinkshift=base, theme=hyperlinkpass
docx/font-color-theme--base--text1shift=base, theme=text1pass
docx/font-color-theme--base--text2shift=base, theme=text2pass
docx/font-color-theme--shade-40--accent1shift=shade-40, theme=accent1pass
docx/font-color-theme--shade-40--accent2shift=shade-40, theme=accent2pass
docx/font-color-theme--shade-40--accent3shift=shade-40, theme=accent3pass
docx/font-color-theme--shade-40--accent4shift=shade-40, theme=accent4pass
docx/font-color-theme--shade-40--accent5shift=shade-40, theme=accent5pass
docx/font-color-theme--shade-40--accent6shift=shade-40, theme=accent6pass
docx/font-color-theme--shade-40--background1shift=shade-40, theme=background1pass
docx/font-color-theme--shade-40--background2shift=shade-40, theme=background2pass
docx/font-color-theme--shade-40--followed-hyperlinkshift=shade-40, theme=followed-hyperlinkpass
docx/font-color-theme--shade-40--hyperlinkshift=shade-40, theme=hyperlinkpass
docx/font-color-theme--shade-40--text1shift=shade-40, theme=text1pass
docx/font-color-theme--shade-40--text2shift=shade-40, theme=text2pass
docx/font-color-theme--shade-60--accent1shift=shade-60, theme=accent1pass
docx/font-color-theme--shade-60--accent2shift=shade-60, theme=accent2pass
docx/font-color-theme--shade-60--accent3shift=shade-60, theme=accent3pass
docx/font-color-theme--shade-60--accent4shift=shade-60, theme=accent4pass
docx/font-color-theme--shade-60--accent5shift=shade-60, theme=accent5pass
docx/font-color-theme--shade-60--accent6shift=shade-60, theme=accent6pass
docx/font-color-theme--shade-60--background1shift=shade-60, theme=background1pass
docx/font-color-theme--shade-60--background2shift=shade-60, theme=background2pass
docx/font-color-theme--shade-60--followed-hyperlinkshift=shade-60, theme=followed-hyperlinkpass
docx/font-color-theme--shade-60--hyperlinkshift=shade-60, theme=hyperlinkpass
docx/font-color-theme--shade-60--text1shift=shade-60, theme=text1pass
docx/font-color-theme--shade-60--text2shift=shade-60, theme=text2pass
docx/font-color-theme--shade-80--accent1shift=shade-80, theme=accent1pass
docx/font-color-theme--shade-80--accent2shift=shade-80, theme=accent2pass
docx/font-color-theme--shade-80--accent3shift=shade-80, theme=accent3pass
docx/font-color-theme--shade-80--accent4shift=shade-80, theme=accent4pass
docx/font-color-theme--shade-80--accent5shift=shade-80, theme=accent5pass
docx/font-color-theme--shade-80--accent6shift=shade-80, theme=accent6pass
docx/font-color-theme--shade-80--background1shift=shade-80, theme=background1pass
docx/font-color-theme--shade-80--background2shift=shade-80, theme=background2pass
docx/font-color-theme--shade-80--followed-hyperlinkshift=shade-80, theme=followed-hyperlinkpass
docx/font-color-theme--shade-80--hyperlinkshift=shade-80, theme=hyperlinkpass
docx/font-color-theme--shade-80--text1shift=shade-80, theme=text1pass
docx/font-color-theme--shade-80--text2shift=shade-80, theme=text2pass
docx/font-color-theme--tint-40--accent1shift=tint-40, theme=accent1pass
docx/font-color-theme--tint-40--accent2shift=tint-40, theme=accent2pass
docx/font-color-theme--tint-40--accent3shift=tint-40, theme=accent3pass
docx/font-color-theme--tint-40--accent4shift=tint-40, theme=accent4pass
docx/font-color-theme--tint-40--accent5shift=tint-40, theme=accent5pass
docx/font-color-theme--tint-40--accent6shift=tint-40, theme=accent6pass
docx/font-color-theme--tint-40--background1shift=tint-40, theme=background1pass
docx/font-color-theme--tint-40--background2shift=tint-40, theme=background2pass
docx/font-color-theme--tint-40--followed-hyperlinkshift=tint-40, theme=followed-hyperlinkpass
docx/font-color-theme--tint-40--hyperlinkshift=tint-40, theme=hyperlinkpass
docx/font-color-theme--tint-40--text1shift=tint-40, theme=text1pass
docx/font-color-theme--tint-40--text2shift=tint-40, theme=text2pass
docx/font-color-theme--tint-60--accent1shift=tint-60, theme=accent1pass
docx/font-color-theme--tint-60--accent2shift=tint-60, theme=accent2pass
docx/font-color-theme--tint-60--accent3shift=tint-60, theme=accent3pass
docx/font-color-theme--tint-60--accent4shift=tint-60, theme=accent4pass
docx/font-color-theme--tint-60--accent5shift=tint-60, theme=accent5pass
docx/font-color-theme--tint-60--accent6shift=tint-60, theme=accent6pass
docx/font-color-theme--tint-60--background1shift=tint-60, theme=background1pass
docx/font-color-theme--tint-60--background2shift=tint-60, theme=background2pass
docx/font-color-theme--tint-60--followed-hyperlinkshift=tint-60, theme=followed-hyperlinkpass
docx/font-color-theme--tint-60--hyperlinkshift=tint-60, theme=hyperlinkpass
docx/font-color-theme--tint-60--text1shift=tint-60, theme=text1pass
docx/font-color-theme--tint-60--text2shift=tint-60, theme=text2pass
docx/font-color-theme--tint-80--accent1shift=tint-80, theme=accent1pass
docx/font-color-theme--tint-80--accent2shift=tint-80, theme=accent2pass
docx/font-color-theme--tint-80--accent3shift=tint-80, theme=accent3pass
docx/font-color-theme--tint-80--accent4shift=tint-80, theme=accent4pass
docx/font-color-theme--tint-80--accent5shift=tint-80, theme=accent5pass
docx/font-color-theme--tint-80--accent6shift=tint-80, theme=accent6pass
docx/font-color-theme--tint-80--background1shift=tint-80, theme=background1pass
docx/font-color-theme--tint-80--background2shift=tint-80, theme=background2pass
docx/font-color-theme--tint-80--followed-hyperlinkshift=tint-80, theme=followed-hyperlinkpass
docx/font-color-theme--tint-80--hyperlinkshift=tint-80, theme=hyperlinkpass
docx/font-color-theme--tint-80--text1shift=tint-80, theme=text1pass
docx/font-color-theme--tint-80--text2shift=tint-80, theme=text2pass

Aggregate

LibraryPassFailPending
python-docx0084
docxjs8400

Parameter axes

Spec notes

<w:color w:val="auto" w:themeColor="<slot>" [w:themeTint="<hex>" | w:themeShade="<hex>"]/> binds the run's foreground color to one of the 12 theme-color slots (ST_ThemeColor) from the document's theme1.xml. The optional w:themeTint / w:themeShade attributes shift the resolved colour lighter (tint) or darker (shade) by a 1-byte hex factor (Word's scale: 66=40%, 99=60%, CC=80%). Themes and tint/shade resolution are a rendering concern — authoring assertions only verify the XML attributes round-trip; rendering assertions only verify the run text is visible. This manifest is a parameterised family: 12 theme slots x 7 tint/shade shifts = 84 fixtures, each a distinct .docx named docx/font-color-theme--<theme>--<shift>.docx.

Generator source

scripts/gen_font_color_theme.py — runs with --arg_template --theme {theme.val} --tint "{shift.themeTint}" --shade "{shift.themeShade}" --out fixtures/docx/font-color-theme--{shift.id}--{theme.id}.docx

#!/usr/bin/env python3
"""Generate a ``fixtures/docx/font-color-theme--*.docx`` fixture.

Parameterised generator for the ``docx/font-color-theme`` feature
family (12 theme slots x 7 tint/shade shifts = 84 cases).

Each invocation writes one fixture. The ``features/docx/font-color-theme.json``
manifest's ``generator.arg_template`` drives the arguments at expansion time::

    --theme <slot> --tint "<hex|empty>" --shade "<hex|empty>" --out <path>

``<slot>`` is an ``ST_ThemeColor`` enumeration value (e.g. ``accent1``,
``background1``, ``followedHyperlink``). ``--tint`` and ``--shade`` each
take a 2-digit uppercase hex byte (e.g. ``66``, ``99``, ``CC``) OR an
empty string to indicate "no shift". A given case sets at most one of
tint/shade — setting neither produces the base theme colour.

python-docx's ``font.color.rgb`` descriptor only writes ``w:val``, so
this generator reaches down to the oxml layer to add the
``w:themeColor`` / ``w:themeTint`` / ``w:themeShade`` attributes
directly, matching how Word emits the element (clause 17.3.2.6).
"""

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(theme: str, tint: str, shade: str, out: Path) -> None:
    doc = Document()
    paragraph = doc.add_paragraph()
    run = paragraph.add_run("Theme colored text")
    rPr = run._r.get_or_add_rPr()
    color = OxmlElement("w:color")
    # ``w:val`` is required by the schema even when the effective colour
    # is resolved from the theme — Word writes ``auto`` as a placeholder.
    color.set(qn("w:val"), "auto")
    color.set(qn("w:themeColor"), theme)
    if tint:
        color.set(qn("w:themeTint"), tint)
    if shade:
        color.set(qn("w:themeShade"), shade)
    rPr.append(color)
    out.parent.mkdir(parents=True, exist_ok=True)
    doc.save(out, reproducible=True)


def main() -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument(
        "--theme",
        default="accent1",
        help="ST_ThemeColor enum value (e.g. accent1, background1, followedHyperlink).",
    )
    parser.add_argument(
        "--tint",
        default="",
        help='2-digit uppercase hex byte (e.g. "66") or "" to skip.',
    )
    parser.add_argument(
        "--shade",
        default="",
        help='2-digit uppercase hex byte (e.g. "CC") or "" to skip.',
    )
    parser.add_argument(
        "--out",
        default=str(
            _REPO_ROOT / "fixtures" / "docx" / "font-color-theme.docx"
        ),
    )
    args = parser.parse_args()

    if args.tint and args.shade:
        raise SystemExit(
            "font-color-theme: --tint and --shade are mutually exclusive "
            "(Word picks one or neither per w:color element)."
        )

    out_path = Path(args.out)
    if not out_path.is_absolute():
        out_path = _REPO_ROOT / out_path
    _write(args.theme, args.tint, args.shade, out_path)
    print(out_path)
    return 0


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

Manifest (parent, unexpanded)

{
  "$schema": "../manifest.schema.json",
  "id": "docx/font-color-theme",
  "kind": "parameterised",
  "title": "Font color (theme)",
  "format": "docx",
  "category": "text-formatting",
  "summary": "Set a run's foreground color to a theme colour slot, optionally shifted by a themeTint or themeShade.",
  "spec": {
    "source": "ecma-376-5-part-1",
    "clause": "17.3.2.6",
    "element": "w:color",
    "rnc_reference": "spec/ecma-376-5/part-1/rnc/WordprocessingML.rnc",
    "xsd_reference": "spec/ecma-376-5/part-1/xsd/wml.xsd",
    "notes": "<w:color w:val=\"auto\" w:themeColor=\"<slot>\" [w:themeTint=\"<hex>\" | w:themeShade=\"<hex>\"]/> binds the run's foreground color to one of the 12 theme-color slots (ST_ThemeColor) from the document's theme1.xml. The optional w:themeTint / w:themeShade attributes shift the resolved colour lighter (tint) or darker (shade) by a 1-byte hex factor (Word's scale: 66=40%, 99=60%, CC=80%). Themes and tint/shade resolution are a rendering concern — authoring assertions only verify the XML attributes round-trip; rendering assertions only verify the run text is visible. This manifest is a parameterised family: 12 theme slots x 7 tint/shade shifts = 84 fixtures, each a distinct .docx named docx/font-color-theme--<theme>--<shift>.docx."
  },
  "fixtures": {
    "machine": "docx/font-color-theme"
  },
  "generator": {
    "python": "scripts/gen_font_color_theme.py",
    "arg_template": "--theme {theme.val} --tint \"{shift.themeTint}\" --shade \"{shift.themeShade}\" --out fixtures/docx/font-color-theme--{shift.id}--{theme.id}.docx"
  },
  "parameters": {
    "theme": [
      {
        "id": "background1",
        "val": "background1"
      },
      {
        "id": "text1",
        "val": "text1"
      },
      {
        "id": "background2",
        "val": "background2"
      },
      {
        "id": "text2",
        "val": "text2"
      },
      {
        "id": "accent1",
        "val": "accent1"
      },
      {
        "id": "accent2",
        "val": "accent2"
      },
      {
        "id": "accent3",
        "val": "accent3"
      },
      {
        "id": "accent4",
        "val": "accent4"
      },
      {
        "id": "accent5",
        "val": "accent5"
      },
      {
        "id": "accent6",
        "val": "accent6"
      },
      {
        "id": "hyperlink",
        "val": "hyperlink"
      },
      {
        "id": "followed-hyperlink",
        "val": "followedHyperlink"
      }
    ],
    "shift": [
      {
        "id": "base",
        "themeShade": "",
        "themeTint": ""
      },
      {
        "id": "tint-40",
        "themeShade": "",
        "themeTint": "66"
      },
      {
        "id": "tint-60",
        "themeShade": "",
        "themeTint": "99"
      },
      {
        "id": "tint-80",
        "themeShade": "",
        "themeTint": "CC"
      },
      {
        "id": "shade-40",
        "themeShade": "66",
        "themeTint": ""
      },
      {
        "id": "shade-60",
        "themeShade": "99",
        "themeTint": ""
      },
      {
        "id": "shade-80",
        "themeShade": "CC",
        "themeTint": ""
      }
    ]
  },
  "assertions_template": [
    {
      "id": "theme-color-is-{theme.id}",
      "part": "word/document.xml",
      "namespaces": {
        "w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
      },
      "xpath": "//w:r/w:rPr/w:color/@w:themeColor",
      "must": "equal",
      "value": "{theme.val}",
      "description": "The run's w:color element must carry w:themeColor bound to the expected theme slot."
    }
  ],
  "render_assertions_template": [
    {
      "id": "font-color-theme-{theme.id}-{shift.id}-text-present",
      "kind": "css_selector",
      "selector": ".docx-wrapper span, .docx-wrapper p",
      "must": "exist",
      "description": "The rendered DOM must contain the fixture's run (theme-colour resolution itself is out of scope — renderers vary widely)."
    }
  ]
}