online.git: engine/include engine/oox engine/sd

0 views
Skip to first unread message

"Mike Kaganski (via cogerrit)"

unread,
4:24 AM (13 hours ago) 4:24 AM
to collaboraon...@googlegroups.com
engine/include/oox/export/shapes.hxx | 2
engine/oox/source/ppt/pptshape.cxx | 8 +-
engine/sd/qa/unit/export-tests-ooxml4.cxx | 55 +++++++++++---
engine/sd/source/filter/eppt/epptooxml.hxx | 3
engine/sd/source/filter/eppt/pptx-epptooxml.cxx | 94 +++++++++++++++++-------
5 files changed, 122 insertions(+), 40 deletions(-)

New commits:
commit 73f1e76b1760d4e936aa22627623c922dcda964b
Author: Mike Kaganski <mike.k...@collabora.com>
AuthorDate: Thu May 21 17:24:15 2026 +0500
Commit: Caolán McNamara <caolan....@collabora.com>
CommitDate: Mon May 25 08:23:21 2026 +0000

cool#15754: Fix PPTX round-trip of empty picture placeholders

An empty picture placeholder on a slide used to export as <p:pic>
with the internal "Insert Image" icon embedded as
<a:blipFill><a:stretch/>, which PowerPoint stretched across the
whole slide. Emit it as <p:sp> with <p:ph type="pic"/> and an empty
<p:spPr/> so geometry, fill and any custGeom from the layout get
inherited.

The layout-side <p:ph type="pic"/> used to be mapped to a
presentation.OutlinerShape and re-exported as <p:ph type="body"/>,
breaking slide-side inheritance on round-trip (the slide's
<p:ph type="pic"/> couldn't find a matching pic placeholder on the
layout and ended up 0x0 and invisible). Map XML_pic to
presentation.GraphicObjectShape on layouts too, matching the slide.

Signed-off-by: Mike Kaganski <mike.k...@collabora.com>
Change-Id: Ie21f7dfe6822b8ddf00a443c523db89d926dcadc
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/3068
Tested-by: Caolán McNamara <caolan....@collabora.com>
Reviewed-by: Caolán McNamara <caolan....@collabora.com>
Tested-by: Jenkins CPCI <rel...@collaboraoffice.com>

diff --git a/engine/include/oox/export/shapes.hxx b/engine/include/oox/export/shapes.hxx
index eb544bcfa35c..acca817d095e 100644
--- a/engine/include/oox/export/shapes.hxx
+++ b/engine/include/oox/export/shapes.hxx
@@ -124,7 +124,7 @@ public:
WriteCustomShape( const css::uno::Reference< css::drawing::XShape >& xShape );
ShapeExport&
WriteEllipseShape( const css::uno::Reference< css::drawing::XShape >& xShape );
- ShapeExport&
+ virtual ShapeExport&
WriteGraphicObjectShape( const css::uno::Reference< css::drawing::XShape >& xShape );
ShapeExport&
WriteGroupShape( const css::uno::Reference< css::drawing::XShape >& xShape );
diff --git a/engine/oox/source/ppt/pptshape.cxx b/engine/oox/source/ppt/pptshape.cxx
index ac2aa5c93c0d..1b2a015df9a5 100644
--- a/engine/oox/source/ppt/pptshape.cxx
+++ b/engine/oox/source/ppt/pptshape.cxx
@@ -379,10 +379,10 @@ void PPTShape::addShape(
sServiceName = u"com.sun.star.presentation.TableShape"_ustr;
break;
case XML_pic :
- if (meShapeLocation == Layout)
- sServiceName = sOutlinerShapeService;
- else
- sServiceName = u"com.sun.star.presentation.GraphicObjectShape"_ustr;
+ // <p:ph type="pic"/> is a real picture placeholder on both
+ // slides and layouts, so the layout's type survives PPTX
+ // round-trip and slide-side inheritance can resolve it.
+ sServiceName = u"com.sun.star.presentation.GraphicObjectShape"_ustr;
break;
case XML_media :
if (meShapeLocation == Layout)
diff --git a/engine/sd/qa/unit/export-tests-ooxml4.cxx b/engine/sd/qa/unit/export-tests-ooxml4.cxx
index e0defb13e43b..ba7b671c6b65 100644
--- a/engine/sd/qa/unit/export-tests-ooxml4.cxx
+++ b/engine/sd/qa/unit/export-tests-ooxml4.cxx
@@ -863,15 +863,52 @@ CPPUNIT_TEST_FIXTURE(SdOOXMLExportTest4, testTdf140912_PicturePlaceholder)
// Given a graphic placeholder with a custom prompt:
createSdImpressDoc("pptx/tdfpictureplaceholder.pptx");

- uno::Reference<beans::XPropertySet> xShapeProps(getShapeFromPage(0, 0));
- bool isEmptyPresentationObject = false;
- // Without the fix, it would not be imported as empty presentation object;
- // the text would be treated as its content.
- xShapeProps->getPropertyValue(u"IsEmptyPresentationObject"_ustr) >>= isEmptyPresentationObject;
- CPPUNIT_ASSERT(isEmptyPresentationObject);
-
- // If we supported custom prompt text, here we would also test "String" property,
- // which would be equal to "Insert Image". See first tests: testCustomPromptTexts
+ awt::Size aSizeBefore;
+ awt::Point aPosBefore;
+ {
+ uno::Reference<beans::XPropertySet> xShapeProps(getShapeFromPage(0, 0));
+ bool isEmptyPresentationObject = false;
+ // Without the fix, it would not be imported as empty presentation object;
+ // the text would be treated as its content.
+ xShapeProps->getPropertyValue(u"IsEmptyPresentationObject"_ustr)
+ >>= isEmptyPresentationObject;
+ CPPUNIT_ASSERT(isEmptyPresentationObject);
+
+ // If we supported custom prompt text, here we would also test "String" property,
+ // which would be equal to "Insert Image". See first tests: testCustomPromptTexts
+
+ auto xShape = xShapeProps.queryThrow<drawing::XShape>();
+ aSizeBefore = xShape->getSize();
+ aPosBefore = xShape->getPosition();
+ }
+
+ // The picture placeholder must round-trip with its size and position (inherited from the layout
+ // via an empty <p:spPr/>), and must remain visible.
+ saveAndReload(TestFilter::PPTX);
+
+ {
+ // After reload, the saved markup should be <p:sp> with <p:ph type="pic"/>
+ // and an empty <p:spPr/>.
+ xmlDocUniquePtr pXmlDoc = parseExport(u"ppt/slides/slide1.xml"_ustr);
+ assertXPath(pXmlDoc, "/p:sld/p:cSld/p:spTree/p:sp/p:nvSpPr/p:nvPr/p:ph", "type", u"pic");
+ assertXPathChildren(pXmlDoc, "/p:sld/p:cSld/p:spTree/p:sp/p:spPr", 0);
+
+ uno::Reference<beans::XPropertySet> xShapeProps(getShapeFromPage(0, 0));
+ bool bEmpty = false;
+ xShapeProps->getPropertyValue(u"IsEmptyPresentationObject"_ustr) >>= bEmpty;
+ CPPUNIT_ASSERT(bEmpty);
+
+ auto xShape = xShapeProps.queryThrow<drawing::XShape>();
+ const awt::Size aSizeAfter(xShape->getSize());
+ const awt::Point aPosAfter(xShape->getPosition());
+
+ // Size and position must survive the round-trip. Without proper layout inheritance in the
+ // reimport path the shape would be 0x0 at (0, 0) and visually disappear.
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(aSizeBefore.Width, aSizeAfter.Width, 1);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(aSizeBefore.Height, aSizeAfter.Height, 1);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(aPosBefore.X, aPosAfter.X, 1);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(aPosBefore.Y, aPosAfter.Y, 1);
+ }
}

CPPUNIT_TEST_FIXTURE(SdOOXMLExportTest4, testEnhancedPathViewBox)
diff --git a/engine/sd/source/filter/eppt/epptooxml.hxx b/engine/sd/source/filter/eppt/epptooxml.hxx
index bddf992de635..085a01f78f3a 100644
--- a/engine/sd/source/filter/eppt/epptooxml.hxx
+++ b/engine/sd/source/filter/eppt/epptooxml.hxx
@@ -51,7 +51,8 @@ enum PlaceholderType
DateAndTime,
Outliner,
Title,
- Subtitle
+ Subtitle,
+ Picture
};

class PowerPointShapeExport;
diff --git a/engine/sd/source/filter/eppt/pptx-epptooxml.cxx b/engine/sd/source/filter/eppt/pptx-epptooxml.cxx
index 3bd05a5c3464..ecb13403191b 100644
--- a/engine/sd/source/filter/eppt/pptx-epptooxml.cxx
+++ b/engine/sd/source/filter/eppt/pptx-epptooxml.cxx
@@ -172,6 +172,7 @@ public:
ShapeExport& WriteNonVisualProperties(const Reference< XShape >& xShape) override;
ShapeExport& WriteTextShape(const Reference< XShape >& xShape) override;
ShapeExport& WriteUnknownShape(const Reference< XShape >& xShape) override;
+ ShapeExport& WriteGraphicObjectShape(const Reference< XShape >& xShape) override;
ShapeExport& WritePlaceholderShape(const Reference< XShape >& xShape, PlaceholderType ePlaceholder);
/** Writes a placeholder shape that references the placeholder on the master slide */
ShapeExport& WritePlaceholderReferenceShape(PlaceholderType ePlaceholder, sal_Int32 nReferencedPlaceholderIdx, PageType ePageType, const Reference<XPropertySet>& rXPagePropSet);
@@ -219,6 +220,8 @@ const char* getPlaceholderTypeName(PlaceholderType ePlaceholder)
return "title";
case Subtitle:
return "subTitle";
+ case Picture:
+ return "pic";
default:
SAL_INFO("sd.eppt", "warning: unhandled placeholder type: " << ePlaceholder);
return "";
@@ -389,6 +392,32 @@ ShapeExport& PowerPointShapeExport::WriteUnknownShape(const Reference< XShape >&
return *this;
}

+ShapeExport& PowerPointShapeExport::WriteGraphicObjectShape(const Reference<XShape>& xShape)
+{
+ // Picture placeholders serialise as <p:sp> with <p:ph type="pic"/>: on
+ // a layout/master always, on a slide only while still empty (a
+ // user-inserted picture remains a <p:pic>).
+ if (xShape && xShape->getShapeType() == "com.sun.star.presentation.GraphicObjectShape")
+ {
+ if (auto xProps = xShape.query<XPropertySet>())
+ {
+ try
+ {
+ bool bIsEmpty = xProps->getPropertyValue(u"IsEmptyPresentationObject"_ustr) == true;
+ if ((mbMaster || bIsEmpty) && WritePlaceholder(xShape, Picture, mbMaster))
+ return *this;
+ }
+ catch (const UnknownPropertyException&)
+ {
+ // Plain drawing.GraphicObjectShape has no
+ // IsEmptyPresentationObject; fall through.
+ }
+ }
+ }
+
+ return ShapeExport::WriteGraphicObjectShape(xShape);
+}
+
PowerPointExport::PowerPointExport(const Reference< XComponentContext >& rContext, const uno::Sequence<uno::Any>& rArguments)
: XmlFilterBase(rContext)
, mpAuthorIDs( new SvtSecurityMapPersonalInfo )
@@ -2550,38 +2579,51 @@ ShapeExport& PowerPointShapeExport::WritePlaceholderShape(const Reference< XShap
mpFS->endElementNS(XML_p, XML_nvPr);
mpFS->endElementNS(XML_p, XML_nvSpPr);

- // visual shape properties
- mpFS->startElementNS(XML_p, XML_spPr);
- WriteShapeTransformation(xShape, XML_a);
- WritePresetShape("rect"_ostr);
- if (xProps.is())
+ if (ePlaceholder == Picture && !mbMaster)
{
- WriteBlipFill(xProps, u"Graphic"_ustr);
- // Do not forget to export the visible properties.
- WriteFill( xProps, xShape->getSize());
- WriteOutline( xProps );
- WriteShapeEffects( xProps );
-
- bool bHas3DEffectinShape = false;
- uno::Sequence<beans::PropertyValue> grabBag;
- if (xProps->getPropertySetInfo()->hasPropertyByName(u"InteropGrabBag"_ustr))
- xProps->getPropertyValue(u"InteropGrabBag"_ustr) >>= grabBag;
-
- for (auto const& it : grabBag)
- if (it.Name == "3DEffectProperties")
- bHas3DEffectinShape = true;
-
- if( bHas3DEffectinShape)
- Write3DEffects( xProps, /*bIsText=*/false );
+ // An empty <p:spPr/> on the slide lets PowerPoint inherit geometry,
+ // fill and the custGeom polygon from the layout.
+ mpFS->singleElementNS(XML_p, XML_spPr);
+ }
+ else
+ {
+ // visual shape properties
+ mpFS->startElementNS(XML_p, XML_spPr);
+ WriteShapeTransformation(xShape, XML_a);
+ WritePresetShape("rect"_ostr);
+ if (xProps.is())
+ {
+ // A picture placeholder's "Graphic" property is the internal
+ // placeholder icon; don't serialise it as a blipFill.
+ if (ePlaceholder != Picture)
+ WriteBlipFill(xProps, u"Graphic"_ustr);
+ // Do not forget to export the visible properties.
+ WriteFill( xProps, xShape->getSize());
+ WriteOutline( xProps );
+ WriteShapeEffects( xProps );
+
+ bool bHas3DEffectinShape = false;
+ uno::Sequence<beans::PropertyValue> grabBag;
+ if (xProps->getPropertySetInfo()->hasPropertyByName(u"InteropGrabBag"_ustr))
+ xProps->getPropertyValue(u"InteropGrabBag"_ustr) >>= grabBag;
+
+ for (auto const& it : grabBag)
+ if (it.Name == "3DEffectProperties")
+ bHas3DEffectinShape = true;
+
+ if( bHas3DEffectinShape)
+ Write3DEffects( xProps, /*bIsText=*/false );
+ }
+ mpFS->endElementNS(XML_p, XML_spPr);
}
- mpFS->endElementNS(XML_p, XML_spPr);

bool bWritePropertiesAsLstStyles
= (mePageType == PageType::MASTER)
&& (ePlaceholder == Title || ePlaceholder == Subtitle || ePlaceholder == Outliner);

- WriteTextBox(xShape, XML_p,
- bUsePlaceholderIndex || bWritePropertiesAsLstStyles);
+ // A slide-side empty picture placeholder inherits the prompt from the layout.
+ if (ePlaceholder != Picture || mbMaster)
+ WriteTextBox(xShape, XML_p, bUsePlaceholderIndex || bWritePropertiesAsLstStyles);

mpFS->endElementNS(XML_p, XML_sp);

@@ -3076,6 +3118,8 @@ Reference<XShape> PowerPointExport::GetReferencedPlaceholderXShape(const Placeho
break;
case oox::core::Subtitle:
break;
+ case oox::core::Picture:
+ break;
}
if (ePresObjKind != PresObjKind::NONE)
{

Reply all
Reply to author
Forward
0 new messages