docx/complex-script-bold

When a run is marked bold or italic, Word mirrors the toggle to the complex-script (CS) track so Arabic / Hebrew / Farsi characters in that run also render bold/italic. python-docx 2026.05.1 fixed this: setting font.bold=True now also emits <w:bCs/>; setting font.italic=True also emits <w:iCs/>. This family regresses that fix.

Cases (2)

Feature IDAxis bindingspython-docxdocxjs
docx/complex-script-bold--bold-ontoggle=bold-on
docx/complex-script-bold--italic-ontoggle=italic-on

Aggregate

LibraryPassFailPending
python-docx002
docxjs002

Parameter axes

Spec notes

WordprocessingML encodes bold/italic as a twin pair of toggle properties per run: <w:b/>/<w:i/> apply to Latin + high-ANSI + East-Asian glyphs; <w:bCs/>/<w:iCs/> apply to the complex-script (bidi) track — Arabic, Hebrew, Farsi, etc. The spec defines them independently (17.3.2.1 for w:b, 17.3.2.2 for w:bCs; 17.3.2.15/16 for w:i / w:iCs). Microsoft Word always emits both in lockstep: setting bold in the UI writes both <w:b/> and <w:bCs/>, and omitting <w:bCs/> silently drops bold on any Arabic character in the run. Prior to python-docx 2026.05.1 the library only wrote <w:b/>, so bold arabic rounds-tripped as plain arabic. The fix in font.py:71-76 mirrors the toggle. This family verifies both halves of the mirror for bold and italic (4 cases: bold+bCs, bold-off+bCs-off, italic+iCs, italic-off+iCs-off).

Generator source

scripts/gen_complex_script_bold.py — runs with --arg_template --toggle {toggle.id} --out fixtures/docx/complex-script-bold--{toggle.id}.docx

#!/usr/bin/env python3
"""Generate ``fixtures/docx/complex-script-bold--<toggle>.docx``.

Parameterised generator for the ``docx/complex-script-bold`` regression
family. Each case sets one run-level toggle via the python-docx public
API (``font.bold`` or ``font.italic``) and asserts the library emits
both the primary element (``<w:b/>`` / ``<w:i/>``) *and* the complex-
script mirror (``<w:bCs/>`` / ``<w:iCs/>``).

Regression target: python-docx 2026.05.1 fixed the omitted CS mirror.
Prior releases silently dropped bold / italic on Arabic / Hebrew /
Farsi characters because the CS toggle wasn't written.

Usage::

    python scripts/gen_complex_script_bold.py --toggle bold-on \\
        --out fixtures/docx/complex-script-bold--bold-on.docx
"""

from __future__ import annotations

import argparse
from pathlib import Path
from typing import Callable

from docx import Document

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


def _case_bold(doc):
    p = doc.add_paragraph()
    r = p.add_run("complex script test")
    r.font.bold = True


def _case_italic(doc):
    p = doc.add_paragraph()
    r = p.add_run("complex script test")
    r.font.italic = True


_CASES: dict[str, Callable] = {
    "bold-on":   _case_bold,
    "italic-on": _case_italic,
}


def _write(toggle: str, out: Path) -> None:
    if toggle not in _CASES:
        raise SystemExit(
            f"Unknown toggle id {toggle!r}; expected one of {sorted(_CASES)}"
        )
    doc = Document()
    _CASES[toggle](doc)
    out.parent.mkdir(parents=True, exist_ok=True)
    doc.save(out, reproducible=True)


def main() -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument("--toggle", required=True)
    parser.add_argument("--out", required=True)
    args = parser.parse_args()

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


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

Manifest (parent, unexpanded)

{
  "$schema": "../manifest.schema.json",
  "id": "docx/complex-script-bold",
  "kind": "parameterised",
  "title": "Complex-script bold/italic mirror (bCs, iCs)",
  "format": "docx",
  "category": "text-formatting",
  "summary": "When a run is marked bold or italic, Word mirrors the toggle to the complex-script (CS) track so Arabic / Hebrew / Farsi characters in that run also render bold/italic. python-docx 2026.05.1 fixed this: setting font.bold=True now also emits <w:bCs/>; setting font.italic=True also emits <w:iCs/>. This family regresses that fix.",
  "spec": {
    "source": "ecma-376-5-part-1",
    "clause": "17.3.2.2",
    "element": "w:bCs",
    "rnc_reference": "spec/ecma-376-5/part-1/rnc/WordprocessingML.rnc",
    "xsd_reference": "spec/ecma-376-5/part-1/xsd/wml.xsd",
    "notes": "WordprocessingML encodes bold/italic as a twin pair of toggle properties per run: <w:b/>/<w:i/> apply to Latin + high-ANSI + East-Asian glyphs; <w:bCs/>/<w:iCs/> apply to the complex-script (bidi) track — Arabic, Hebrew, Farsi, etc. The spec defines them independently (17.3.2.1 for w:b, 17.3.2.2 for w:bCs; 17.3.2.15/16 for w:i / w:iCs). Microsoft Word always emits both in lockstep: setting bold in the UI writes both <w:b/> and <w:bCs/>, and omitting <w:bCs/> silently drops bold on any Arabic character in the run. Prior to python-docx 2026.05.1 the library only wrote <w:b/>, so bold arabic rounds-tripped as plain arabic. The fix in font.py:71-76 mirrors the toggle. This family verifies both halves of the mirror for bold and italic (4 cases: bold+bCs, bold-off+bCs-off, italic+iCs, italic-off+iCs-off)."
  },
  "fixtures": {
    "machine": "docx/complex-script-bold"
  },
  "generator": {
    "python": "scripts/gen_complex_script_bold.py",
    "arg_template": "--toggle {toggle.id} --out fixtures/docx/complex-script-bold--{toggle.id}.docx"
  },
  "parameters": {
    "toggle": [
      {
        "id": "bold-on",
        "primary_tag": "w:b",
        "cs_tag": "w:bCs"
      },
      {
        "id": "italic-on",
        "primary_tag": "w:i",
        "cs_tag": "w:iCs"
      }
    ]
  },
  "assertions_template": [
    {
      "id": "primary-present-{toggle.id}",
      "part": "word/document.xml",
      "namespaces": {
        "w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
      },
      "xpath": "//w:r[w:t[normalize-space()='complex script test']]/w:rPr/{toggle.primary_tag}",
      "must": "exist",
      "description": "The primary (non-CS) toggle element must be present on the run."
    },
    {
      "id": "cs-mirror-present-{toggle.id}",
      "part": "word/document.xml",
      "namespaces": {
        "w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
      },
      "xpath": "//w:r[w:t[normalize-space()='complex script test']]/w:rPr/{toggle.cs_tag}",
      "must": "exist",
      "description": "The complex-script mirror toggle element must also be present. This is the 2026.05.1 regression: without it, bold/italic silently drops on Arabic / Hebrew / Farsi characters in the run."
    },
    {
      "id": "primary-val-not-false-{toggle.id}",
      "part": "word/document.xml",
      "namespaces": {
        "w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
      },
      "xpath": "//w:r[w:t[normalize-space()='complex script test']]/w:rPr/{toggle.primary_tag}/@w:val",
      "must": "not-match",
      "value": "^(0|false|off)$",
      "description": "If w:val is present on the primary toggle it must not explicitly disable it."
    },
    {
      "id": "cs-val-not-false-{toggle.id}",
      "part": "word/document.xml",
      "namespaces": {
        "w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
      },
      "xpath": "//w:r[w:t[normalize-space()='complex script test']]/w:rPr/{toggle.cs_tag}/@w:val",
      "must": "not-match",
      "value": "^(0|false|off)$",
      "description": "If w:val is present on the CS toggle it must not explicitly disable it."
    }
  ]
}