An inline image (1x1 PNG) anchored inside a paragraph via <w:drawing><wp:inline>.
Library verdicts: python-docx: — docxjs: pass
| Feature id | docx/image-inline |
|---|---|
| Format | docx |
| Category | block-content |
| Spec | ecma-376-5-part-1 § 17.3.3.9 w:drawing |
| ID / part | Predicate | XPath | python-docx | docxjs |
|---|---|---|---|---|
drawing-element-presentword/document.xml | existAt least one <w:drawing> element must be present in the body. | //w:drawing | — | — |
wp-inline-presentword/document.xml | existThe drawing must use the inline layout variant (wp:inline), not wp:anchor. | //w:drawing//wp:inline | — | — |
| ID | Predicate | Selector | python-docx | docxjs |
|---|---|---|---|---|
img-element-present | css_selector/ existAn <img> element must appear in the rendered DOM. | .docx-wrapper img | — | pass |
img-src-is-embedded | css_selector/ existThe rendered <img> must carry an embedded source — either a data:image/ URL (base64-inlined) or a blob: URL (Object URL). Both are valid DOM representations of an image extracted from the docx package; the choice is a renderer-level memory/runtime tradeoff. | .docx-wrapper img[src^='data:image/'], .docx-wrapper img[src^='blob:'] | — | pass |
img-has-a11y-hook | css_selector/ existAccessibility hook: the rendered image must carry some screen-reader-visible signal — an alt attribute (even alt=''), aria-hidden='true' to explicitly mark decorative, aria-label/aria-labelledby to supply a name, a <figure> wrapper, or an explicit role='img'. Accepts any of these conventions; the assertion fails only when NONE are present (the typical renderer-with-zero-a11y case). | .docx-wrapper img[alt], .docx-wrapper img[aria-hidden='true'], .docx-wrapper img[aria-label], .docx-wrapper img[aria-labelledby], .docx-wrapper figure img, .docx-wrapper [role='img'] | — | — |
scripts/gen_image_inline.py
#!/usr/bin/env python3
"""Generate ``fixtures/docx/image-inline.docx``.
A minimal document exercising the ``w:drawing`` inline-picture element.
A one-run paragraph embeds a 1x1 white PNG via ``run.add_picture``,
which yields a <w:drawing><wp:inline>... tree. Satisfies the
assertions in ``features/docx/image-inline.json``.
The PNG is written to a tempfile so python-docx's image header parser
(which reads from disk) accepts it. The hex bytes encode a valid
69-byte 1x1 RGB PNG with a deflate-compressed white pixel IDAT chunk.
"""
from __future__ import annotations
import tempfile
from pathlib import Path
from docx import Document
from docx.shared import Inches
_REPO_ROOT = Path(__file__).resolve().parent.parent
_OUT = _REPO_ROOT / "fixtures" / "docx" / "image-inline.docx"
# Valid 1x1 RGB PNG, 69 bytes. Hand-built with zlib-compressed IDAT.
# Byte layout: PNG signature | IHDR (1x1, 8-bit RGB) | IDAT (deflate of
# filter-byte + white RGB triplet) | IEND.
_TINY_PNG_HEX = (
"89504e470d0a1a0a"
"0000000d49484452000000010000000108020000009077"
"53de"
"0000000c49444154789c63f8ffff3f0005fe02fe0def46b8"
"0000000049454e44ae426082"
)
def main() -> int:
png_bytes = bytes.fromhex(_TINY_PNG_HEX)
# Fixed temp filename (not NamedTemporaryFile) so python-docx's
# per-image Picture-n pic:cNvPr/@name is deterministic across runs —
# NamedTemporaryFile produces a random basename which ends up
# embedded in the docx's XML and defeats reproducible-save.
tmp_path = Path(tempfile.gettempdir()) / "corpus-image-inline.png"
tmp_path.write_bytes(png_bytes)
doc = Document()
paragraph = doc.add_paragraph()
paragraph.add_run().add_picture(str(tmp_path), width=Inches(1.0))
_OUT.parent.mkdir(parents=True, exist_ok=True)
doc.save(_OUT, reproducible=True)
tmp_path.unlink(missing_ok=True)
print(_OUT)
return 0
if __name__ == "__main__":
raise SystemExit(main())
{
"$schema": "../manifest.schema.json",
"id": "docx/image-inline",
"title": "Inline image",
"format": "docx",
"category": "block-content",
"summary": "An inline image (1x1 PNG) anchored inside a paragraph via <w:drawing><wp:inline>.",
"spec": {
"source": "ecma-376-5-part-1",
"clause": "17.3.3.9",
"element": "w:drawing",
"rnc_reference": "spec/ecma-376-5/part-1/rnc/WordprocessingML.rnc",
"xsd_reference": "spec/ecma-376-5/part-1/xsd/wml.xsd",
"notes": "An inline picture is a <w:drawing> whose child is <wp:inline> (namespace http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing). The inline layout variant flows with text; the alternative is <wp:anchor> for floating positioning.\n\nRenderer note: when projecting an embedded image into the DOM, renderers may set the <img> src to either a data: URL (base64-inlined, self-contained, ~33% size overhead) or a blob: URL (Object URL referencing an in-memory Blob, zero overhead but requires a JS runtime and explicit revokeObjectURL lifecycle). Both are equally valid DOM strategies; choosing one over the other is a legitimate implementation tradeoff (memory footprint vs. runtime coupling). The render assertion accepts either."
},
"fixtures": {
"machine": "docx/image-inline",
"office": "docx/image-inline"
},
"generator": {
"python": "scripts/gen_image_inline.py"
},
"assertions": [
{
"id": "drawing-element-present",
"part": "word/document.xml",
"namespaces": {
"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
},
"xpath": "//w:drawing",
"must": "exist",
"description": "At least one <w:drawing> element must be present in the body."
},
{
"id": "wp-inline-present",
"part": "word/document.xml",
"namespaces": {
"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main",
"wp": "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing"
},
"xpath": "//w:drawing//wp:inline",
"must": "exist",
"description": "The drawing must use the inline layout variant (wp:inline), not wp:anchor."
}
],
"render_assertions": [
{
"id": "img-element-present",
"kind": "css_selector",
"selector": ".docx-wrapper img",
"must": "exist",
"description": "An <img> element must appear in the rendered DOM."
},
{
"id": "img-src-is-embedded",
"kind": "css_selector",
"selector": ".docx-wrapper img[src^='data:image/'], .docx-wrapper img[src^='blob:']",
"must": "exist",
"description": "The rendered <img> must carry an embedded source — either a data:image/ URL (base64-inlined) or a blob: URL (Object URL). Both are valid DOM representations of an image extracted from the docx package; the choice is a renderer-level memory/runtime tradeoff."
},
{
"id": "img-has-a11y-hook",
"kind": "css_selector",
"selector": ".docx-wrapper img[alt], .docx-wrapper img[aria-hidden='true'], .docx-wrapper img[aria-label], .docx-wrapper img[aria-labelledby], .docx-wrapper figure img, .docx-wrapper [role='img']",
"must": "exist",
"description": "Accessibility hook: the rendered image must carry some screen-reader-visible signal — an alt attribute (even alt=''), aria-hidden='true' to explicitly mark decorative, aria-label/aria-labelledby to supply a name, a <figure> wrapper, or an explicit role='img'. Accepts any of these conventions; the assertion fails only when NONE are present (the typical renderer-with-zero-a11y case)."
}
]
}
