A PNG picture placed on a slide via shapes.add_picture is emitted as a <p:pic> whose <p:blipFill>/<a:blip> carries a relationship-embedded reference to the image part.
Library verdicts: python-pptx: — pptxjs: pass
| Feature id | pptx/image-on-slide |
|---|---|
| Format | pptx |
| Category | images |
| Spec | ecma-376-5-part-1 § 20.2.2.3 p:pic |
| ID / part | Predicate | XPath | python-pptx | pptxjs |
|---|---|---|---|---|
image-pic-element-presentppt/slides/slide1.xml | existThe slide must contain at least one <p:pic> element — the picture shape emitted by add_picture(). | //p:pic | — | — |
image-blip-has-embed-referenceppt/slides/slide1.xml | match = ^rId\d+$The picture's <a:blip> must carry an r:embed attribute whose value is a relationship id (rIdN). | //p:pic/p:blipFill/a:blip/@r:embed | — | — |
| ID | Predicate | Selector | python-pptx | pptxjs |
|---|---|---|---|---|
image-on-slide-img-rendered | css_selector/ existThe renderer must emit an <img> (or picture-wrapped img) with a resolvable src — either a data URL, a remote URL, a blob URL, a root-relative path, or nested under a picture-shape container. | img[src^='data:image/'], img[src^='http'], img[src^='blob:'], img[src^='/'], [data-kind='pic'] img, .pptx-picture img, picture img | — | pass |
slide-count | css_selector/ equal-countExactly one slide container must be rendered. Catches renderers that emit zero slides (hard failure) or duplicate the slide. | section.pptx-slide, .pptx-slide, section[data-slide], .slide, [data-slide] | — | — |
image-on-slide-has-a11y-hook | css_selector/ existAccessibility: the slide image must carry some screen-reader-visible signal — an alt attribute (even alt=''), aria-hidden='true' to mark decorative, aria-label/aria-labelledby to supply a name, a <figure> wrapper, or an explicit role='img'. Accepts any of these conventions; a renderer that emits a bare <img> with no a11y attributes or container fails this assertion. | img[alt], img[aria-hidden='true'], img[aria-label], img[aria-labelledby], figure img, [role='img'] | — | — |
scripts/gen_image_on_slide.py
#!/usr/bin/env python3
"""Generate ``fixtures/pptx/image-on-slide.pptx``.
A one-slide presentation carrying a tiny 1x1 PNG added via
``shapes.add_picture``. The emitted slide XML carries a <p:pic>
whose <p:blipFill>/<a:blip> references an image part through the
slide's rels. Designed to satisfy the assertions in
``features/pptx/image-on-slide.json``.
The PNG is embedded inline as a hex literal — a pre-decoded 1x1
RGBA image — so the generator has no filesystem dependency beyond
the python-pptx package itself.
"""
from __future__ import annotations
import io
from pathlib import Path
from pptx import Presentation
from pptx.util import Inches
import datetime as _dt
_REPRODUCIBLE_DT = _dt.datetime(2000, 1, 1, 0, 0, 0)
_REPO_ROOT = Path(__file__).resolve().parent.parent
_OUT = _REPO_ROOT / "fixtures" / "pptx" / "image-on-slide.pptx"
# Minimal valid 1x1 RGBA PNG. Decoded from
# magic(8) + IHDR(13 data, 1x1 RGBA) + IDAT(13 zlib-deflated) + IEND(0)
_PNG_BYTES = bytes.fromhex(
"89504E470D0A1A0A"
"0000000D49484452000000010000000108060000001F15C489"
"0000000D49444154789C63000100000500010D0A2DB4"
"0000000049454E44AE426082"
)
def main() -> int:
prs = Presentation()
# slide_layouts[5] is the "title only" layout; the only picture we embed
# is the one added below via shapes.add_picture.
slide = prs.slides.add_slide(prs.slide_layouts[5])
slide.shapes.add_picture(
io.BytesIO(_PNG_BYTES), Inches(1), Inches(1), Inches(2), Inches(2)
)
_OUT.parent.mkdir(parents=True, exist_ok=True)
prs.save(_OUT, zip_date_time=_REPRODUCIBLE_DT)
print(_OUT)
return 0
if __name__ == "__main__":
raise SystemExit(main()){
"$schema": "../manifest.schema.json",
"id": "pptx/image-on-slide",
"title": "Raster image on slide",
"format": "pptx",
"category": "images",
"summary": "A PNG picture placed on a slide via shapes.add_picture is emitted as a <p:pic> whose <p:blipFill>/<a:blip> carries a relationship-embedded reference to the image part.",
"spec": {
"source": "ecma-376-5-part-1",
"clause": "20.2.2.3",
"element": "p:pic",
"rnc_reference": "spec/ecma-376-5/part-1/rnc/PresentationML.rnc",
"xsd_reference": "spec/ecma-376-5/part-1/xsd/pml.xsd",
"notes": "python-pptx emits shapes.add_picture() as a <p:pic> (not a <p:sp>) on the slide's <p:spTree>. The <p:blipFill>/<a:blip> carries r:embed='rIdN' pointing to the image part via the slide's rels. The embedded image part itself lives under ppt/media/ (e.g. ppt/media/image1.png)."
},
"fixtures": {
"machine": "pptx/image-on-slide",
"office": "pptx/image-on-slide"
},
"generator": {
"python": "scripts/gen_image_on_slide.py"
},
"assertions": [
{
"id": "image-pic-element-present",
"part": "ppt/slides/slide1.xml",
"namespaces": {
"p": "http://schemas.openxmlformats.org/presentationml/2006/main",
"a": "http://schemas.openxmlformats.org/drawingml/2006/main",
"r": "http://schemas.openxmlformats.org/officeDocument/2006/relationships"
},
"xpath": "//p:pic",
"must": "exist",
"description": "The slide must contain at least one <p:pic> element — the picture shape emitted by add_picture()."
},
{
"id": "image-blip-has-embed-reference",
"part": "ppt/slides/slide1.xml",
"namespaces": {
"p": "http://schemas.openxmlformats.org/presentationml/2006/main",
"a": "http://schemas.openxmlformats.org/drawingml/2006/main",
"r": "http://schemas.openxmlformats.org/officeDocument/2006/relationships"
},
"xpath": "//p:pic/p:blipFill/a:blip/@r:embed",
"must": "match",
"value": "^rId\\d+$",
"description": "The picture's <a:blip> must carry an r:embed attribute whose value is a relationship id (rIdN)."
}
],
"render_assertions": [
{
"id": "image-on-slide-img-rendered",
"kind": "css_selector",
"selector": "img[src^='data:image/'], img[src^='http'], img[src^='blob:'], img[src^='/'], [data-kind='pic'] img, .pptx-picture img, picture img",
"must": "exist",
"description": "The renderer must emit an <img> (or picture-wrapped img) with a resolvable src — either a data URL, a remote URL, a blob URL, a root-relative path, or nested under a picture-shape container."
},
{
"id": "slide-count",
"kind": "css_selector",
"selector": "section.pptx-slide, .pptx-slide, section[data-slide], .slide, [data-slide]",
"must": "equal-count",
"count": 1,
"description": "Exactly one slide container must be rendered. Catches renderers that emit zero slides (hard failure) or duplicate the slide."
},
{
"id": "image-on-slide-has-a11y-hook",
"kind": "css_selector",
"selector": "img[alt], img[aria-hidden='true'], img[aria-label], img[aria-labelledby], figure img, [role='img']",
"must": "exist",
"description": "Accessibility: the slide image must carry some screen-reader-visible signal — an alt attribute (even alt=''), aria-hidden='true' to mark decorative, aria-label/aria-labelledby to supply a name, a <figure> wrapper, or an explicit role='img'. Accepts any of these conventions; a renderer that emits a bare <img> with no a11y attributes or container fails this assertion."
}
]
}
