pptx/unicode-bmp-round-trip

A single slide carries one shape whose text frame holds a characteristic Unicode sample. The DrawingML <a:t> must contain the text verbatim across every script range.

Cases (15)

Feature IDAxis bindingspython-pptxpptxjs
pptx/unicode-bmp-round-trip--arabicscript=arabic
pptx/unicode-bmp-round-trip--asciiscript=ascii
pptx/unicode-bmp-round-trip--chinesescript=chinese
pptx/unicode-bmp-round-trip--combiningscript=combining
pptx/unicode-bmp-round-trip--cyrillicscript=cyrillic
pptx/unicode-bmp-round-trip--devanagariscript=devanagari
pptx/unicode-bmp-round-trip--emoji-bmpscript=emoji-bmp
pptx/unicode-bmp-round-trip--emoji-smpscript=emoji-smp
pptx/unicode-bmp-round-trip--greekscript=greek
pptx/unicode-bmp-round-trip--hebrewscript=hebrew
pptx/unicode-bmp-round-trip--japanesescript=japanese
pptx/unicode-bmp-round-trip--koreanscript=korean
pptx/unicode-bmp-round-trip--latin1script=latin1
pptx/unicode-bmp-round-trip--thaiscript=thai
pptx/unicode-bmp-round-trip--zwj-familyscript=zwj-family

Aggregate

LibraryPassFailPending
python-pptx0015
pptxjs0015

Parameter axes

Spec notes

PresentationML text bodies use DrawingML runs (a:r / a:t) rather than WordprocessingML's w:t. The encoding question is identical: both literal UTF-8 and numeric-character-reference forms are spec-legal, and the XML infoset layer (what XPath sees) collapses them. python-pptx emits literal UTF-8 byte sequences. Covers the same 15-case Cartesian dimension as the docx and xlsx families for cross-format parity; a regression that only affects PowerPoint (e.g. a ZWJ emoji losing bytes during serialisation) will show up here but not in the docx family.

Generator source

scripts/gen_unicode_bmp_round_trip_pptx.py — runs with --arg_template --script {script.id} --out fixtures/pptx/unicode-bmp-round-trip--{script.id}.pptx

#!/usr/bin/env python3
"""Generate ``fixtures/pptx/unicode-bmp-round-trip--<script>.pptx``.

Parameterised generator for the ``pptx/unicode-bmp-round-trip`` family.
Writes a minimal single-slide deck whose title placeholder carries the
payload for one Unicode range. The manifest assertion checks that the
DrawingML ``<a:t>`` element contains the sample substring for that
range via XPath ``contains()``.

The text table is held inline (mirroring the sibling docx/xlsx
generators) so shell quoting of RTL, combining-mark, and emoji-ZWJ
sequences is avoided.

Usage::

    python scripts/gen_unicode_bmp_round_trip_pptx.py \\
        --script chinese \\
        --out fixtures/pptx/unicode-bmp-round-trip--chinese.pptx
"""

from __future__ import annotations

import argparse
from pathlib import Path

from pptx import Presentation

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

_PAYLOADS: dict[str, str] = {
    "ascii":      "Hello world",
    "latin1":     "café naïve résumé",
    "cyrillic":   "Привет мир",
    "greek":      "Γειά σου κόσμε",
    "hebrew":     "שלום עולם",
    "arabic":     "مرحبا بالعالم",
    "chinese":    "你好世界",
    "japanese":   "こんにちは世界",
    "korean":     "안녕하세요 세계",
    "devanagari": "नमस्ते दुनिया",
    "thai":       "สวัสดีชาวโลก",
    "emoji-bmp":  "Star ✨ ok",
    "emoji-smp":  "Grin \U0001F600 ok",
    "combining":  "combined à decomposed à",
    "zwj-family": "family \U0001F468‍\U0001F469‍\U0001F466",
}


def _write(script: str, out: Path) -> None:
    if script not in _PAYLOADS:
        raise SystemExit(
            f"Unknown script id {script!r}; expected one of {sorted(_PAYLOADS)}"
        )
    prs = Presentation()
    blank_layout = prs.slide_layouts[5]  # title only
    slide = prs.slides.add_slide(blank_layout)
    if slide.shapes.title is not None:
        slide.shapes.title.text = _PAYLOADS[script]
    out.parent.mkdir(parents=True, exist_ok=True)
    prs.save(out)


def main() -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument("--script", 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.script, out_path)
    print(out_path)
    return 0


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

Manifest (parent, unexpanded)

{
  "$schema": "../manifest.schema.json",
  "id": "pptx/unicode-bmp-round-trip",
  "kind": "parameterised",
  "title": "Unicode round-trip (pptx)",
  "format": "pptx",
  "category": "text-content",
  "summary": "A single slide carries one shape whose text frame holds a characteristic Unicode sample. The DrawingML <a:t> must contain the text verbatim across every script range.",
  "spec": {
    "source": "ecma-376-5-part-1",
    "clause": "21.1.2.3.4",
    "element": "a:t",
    "rnc_reference": "spec/ecma-376-5/part-1/rnc/DrawingML-Main.rnc",
    "xsd_reference": "spec/ecma-376-5/part-1/xsd/dml-main.xsd",
    "notes": "PresentationML text bodies use DrawingML runs (a:r / a:t) rather than WordprocessingML's w:t. The encoding question is identical: both literal UTF-8 and numeric-character-reference forms are spec-legal, and the XML infoset layer (what XPath sees) collapses them. python-pptx emits literal UTF-8 byte sequences. Covers the same 15-case Cartesian dimension as the docx and xlsx families for cross-format parity; a regression that only affects PowerPoint (e.g. a ZWJ emoji losing bytes during serialisation) will show up here but not in the docx family."
  },
  "fixtures": {
    "machine": "pptx/unicode-bmp-round-trip"
  },
  "generator": {
    "python": "scripts/gen_unicode_bmp_round_trip_pptx.py",
    "arg_template": "--script {script.id} --out fixtures/pptx/unicode-bmp-round-trip--{script.id}.pptx"
  },
  "parameters": {
    "script": [
      {
        "id": "ascii",
        "sample": "Hello"
      },
      {
        "id": "latin1",
        "sample": "café"
      },
      {
        "id": "cyrillic",
        "sample": "Привет"
      },
      {
        "id": "greek",
        "sample": "Γειά"
      },
      {
        "id": "hebrew",
        "sample": "שלום"
      },
      {
        "id": "arabic",
        "sample": "مرحبا"
      },
      {
        "id": "chinese",
        "sample": "你好"
      },
      {
        "id": "japanese",
        "sample": "こんにちは"
      },
      {
        "id": "korean",
        "sample": "안녕하세요"
      },
      {
        "id": "devanagari",
        "sample": "नमस्ते"
      },
      {
        "id": "thai",
        "sample": "สวัสดี"
      },
      {
        "id": "emoji-bmp",
        "sample": "✨"
      },
      {
        "id": "emoji-smp",
        "sample": "😀"
      },
      {
        "id": "combining",
        "sample": "decomposed"
      },
      {
        "id": "zwj-family",
        "sample": "family"
      }
    ]
  },
  "assertions_template": [
    {
      "id": "text-carries-sample-{script.id}",
      "part": "ppt/slides/slide1.xml",
      "namespaces": {
        "a": "http://schemas.openxmlformats.org/drawingml/2006/main"
      },
      "xpath": "//a:t[contains(., '{script.sample}')]",
      "must": "exist",
      "description": "The slide's DrawingML <a:t> element must contain the characteristic sample substring for this script family."
    }
  ]
}