docling-project / docling-project/docling

PPTX chart image rendering deletes charts nested in a group shape

Open
#4,231 0 comments 0 reactions 1 assignee Claimed by @DanielNg0729 View on GitHub
bug pptx
Dominant language
Python
Stars
66.4k
Forks
4.8k
Avg merge
2d 21h
Merged PRs (30d)
84

Description

### Bug

`MsPowerpointDocumentBackend._isolate_chart_presentation` deletes the chart it is
supposed to isolate when that chart sits inside a group shape. With
`render_chart_images=True`, such a chart gets a blank white image attached instead of
a rendering.

The shape walk in `_walk_linear` recurses into groups, so `_handle_chart` is reached
with the `shape_id` of a *nested* shape:

```python
def handle_groups(shape, parent_slide, slide_ind, doc, slide_size):
if _safe_shape_type(shape) == MSO_SHAPE_TYPE.GROUP:
for groupedshape in self._iter_shapes_by_position(shape.shapes):
handle_shapes(groupedshape, parent_slide, slide_ind, doc, slide_size)
```

`_isolate_chart_presentation` then prunes only the slide's **top-level** shapes:

```python
for shp in list(target_slide.shapes):
if shp.shape_id != chart_shape_id:
shp._element.getparent().remove(shp._element)
```

`target_slide.shapes` never yields the nested chart, so the enclosing `` —
whose own id differs from the chart's — is removed along with everything it contains.
The isolated presentation is an empty slide, LibreOffice renders a blank page, and
`crop_whitespace` returns that page unchanged because `diff.getbbox()` is `None` on a
uniform image. A slide-sized white PNG is attached as the chart picture, with nothing
logged.

The docstring documents a fallback that the code does not implement:

> When the chart is not a top-level shape (e.g. nested in a group) its `shape_id` is
> not found among the slide's shapes, so the slide is left intact and the whole slide
> is rendered instead — a best-effort fallback.

Nothing checks whether the id was found; every non-matching shape is removed
regardless, so an unmatched id empties the slide rather than leaving it intact.

### Steps to reproduce

```python
from pathlib import Path

from pptx import Presentation
from pptx.chart.data import CategoryChartData
from pptx.enum.chart import XL_CHART_TYPE
from pptx.util import Inches

from docling.backend.mspowerpoint_backend import MsPowerpointDocumentBackend

prs = Presentation()
slide = prs.slides.add_slide(prs.slide_layouts[6])
group = slide.shapes.add_group_shape()

chart_data = CategoryChartData()
chart_data.categories = ["a", "b", "c"]
chart_data.add_series("s1", (1.0, 2.0, 3.0))
chart = group.shapes.add_chart(
XL_CHART_TYPE.COLUMN_CLUSTERED,
Inches(1), Inches(1), Inches(4), Inches(3),
chart_data,
)
prs.save("grouped_chart.pptx")

backend = object.__new__(MsPowerpointDocumentBackend)
backend.pptx_obj = Presentation("grouped_chart.pptx")
backend._isolate_chart_presentation(0, chart.shape_id, Path("isolated.pptx"))

isolated = Presentation("isolated.pptx")
print("shapes left on the slide:", list(isolated.slides[0].shapes))
```

### Docling version

```
docling 2.126.0

```

### Actual behaviour

```
shapes left on the slide: []
```

The slide is empty; the chart and its group are gone. End to end
(`MsPowerpointBackendOptions(render_chart_images=True)` with LibreOffice installed),
the resulting `PictureItem` carries a blank white image the size of the slide.

### Expected behaviour

```
shapes left on the slide: []
```

The chart survives isolation and is rendered, exactly as a top-level chart is.

### Notes

The enclosing groups have to be kept rather than the chart hoisted to the slide: a
group's `chOff`/`chExt` define the child coordinate space that the nested
`graphicFrame`'s own `xfrm` is expressed in, so re-parenting it to `spTree` would
move or scale the chart. Pruning the siblings at every level of the ancestor chain
keeps the geometry untouched.

Introduced in #3794 / #3809. There is no test covering a grouped chart.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.