From 9b3b1373b283f5ef55719bcca0cfc9070e07cd79 Mon Sep 17 00:00:00 2001 From: Mike Kaganski Date: Mon, 17 Aug 2026 16:27:22 +0500 Subject: [PATCH] cool#16080: write the body the slide master holds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The master part was written from the page standing for the group of equivalent masters, so it carried that layout's placeholders and nothing else - for a deck whose first layout is a title slide, no body at all, and PowerPoint offered no Master text styles to edit. Take a type a master may hold that this page has none of from the first page of the group that has it. A layout placeholder was imported with what it inherits from the master, so the first layout with a body carries the master's own geometry, which is where PowerPoint puts the box. A deck whose first body-bearing layout states a geometry of its own hands that over instead. testTdf106867 moved its expected spid from 65 to 66: the master part writes one shape more, and shape ids are document-wide. It still names the p:pic that holds the video. Underneath is a document model difference: PowerPoint has masters that carry layouts, Impress has masters alone, so the import maps each layout to a master of its own and the export reconstructs both levels from them. Signed-off-by: Mike Kaganski Change-Id: I1e6d311e5f20cd3377ce4c0c3f2dfa59ff4dfefb Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/9196 Tested-by: Jenkins CPCI Reviewed-by: Caolán McNamara (cherry picked from commit 73dfd24f01a6fdf5fbd7c0d48f17c027442fb870) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/210234 Tested-by: Jenkins Reviewed-by: Xisco Fauli --- sd/qa/unit/export-tests-ooxml2.cxx | 2 +- sd/qa/unit/export-tests-ooxml4.cxx | 7 ++++ sd/source/filter/eppt/epptooxml.hxx | 4 ++- sd/source/filter/eppt/pptx-epptooxml.cxx | 41 ++++++++++++++++++++++-- 4 files changed, 50 insertions(+), 4 deletions(-) diff --git a/sd/qa/unit/export-tests-ooxml2.cxx b/sd/qa/unit/export-tests-ooxml2.cxx index 7975f3c19037..c07ad3530c69 100644 --- a/sd/qa/unit/export-tests-ooxml2.cxx +++ b/sd/qa/unit/export-tests-ooxml2.cxx @@ -1325,7 +1325,7 @@ CPPUNIT_TEST_FIXTURE(SdOOXMLExportTest2, testTdf106867) "/p:sld/p:timing/p:tnLst/p:par/p:cTn/p:childTnLst/p:seq/p:cTn/p:childTnLst/p:par/" "p:cTn/p:childTnLst/p:par/p:cTn/p:childTnLst/p:par/p:cTn/p:childTnLst/p:cmd/" "p:cBhvr/p:tgtEl/p:spTgt", - "spid", u"65"); + "spid", u"66"); } CPPUNIT_TEST_FIXTURE(SdOOXMLExportTest2, testTdf112280) diff --git a/sd/qa/unit/export-tests-ooxml4.cxx b/sd/qa/unit/export-tests-ooxml4.cxx index 0dedfcb3ad7c..a2497555329e 100644 --- a/sd/qa/unit/export-tests-ooxml4.cxx +++ b/sd/qa/unit/export-tests-ooxml4.cxx @@ -2466,6 +2466,13 @@ CPPUNIT_TEST_FIXTURE(SdOOXMLExportTest4, testCool16080_masterKeepsItsPlaceholder assertXPath(pMaster, aTree + "/p:sp/p:nvSpPr/p:nvPr/p:ph[@type='dt']", 1); assertXPath(pMaster, aTree + "/p:sp/p:nvSpPr/p:nvPr/p:ph[@type='ftr']", 1); assertXPath(pMaster, aTree + "/p:sp/p:nvSpPr/p:nvPr/p:ph[@type='sldNum']", 1); + + // The body comes from the first layout that has one, since the page standing for the group is + // the Title Slide layout and has none. That layout inherits the master's geometry, so the + // placeholder lands where the master put it rather than where a layout would. + assertXPath(pMaster, aTree + "/p:sp/p:nvSpPr/p:nvPr/p:ph[@type='body']", 1); + assertXPath(pMaster, aTree + "/p:sp[p:nvSpPr/p:nvPr/p:ph/@type='body']/p:spPr/a:xfrm/a:off", + "y", u"1825560"); } CPPUNIT_TEST_FIXTURE(SdOOXMLExportTest4, testCool16079_dateTimeField) diff --git a/sd/source/filter/eppt/epptooxml.hxx b/sd/source/filter/eppt/epptooxml.hxx index 8dba253148e3..faeb02ddff32 100644 --- a/sd/source/filter/eppt/epptooxml.hxx +++ b/sd/source/filter/eppt/epptooxml.hxx @@ -113,7 +113,7 @@ private: // shapes void WriteShapeTree( const ::sax_fastparser::FSHelperPtr& pFS, PageType ePageType, bool bMaster, - bool bSlideMasterPart = false ); + bool bSlideMasterPart = false, sal_uInt32 nMasterNum = 0 ); sal_uInt32 GetNewSlideId() { return mnSlideIdMax ++; } sal_uInt32 GetNewSlideMasterId() { return mnSlideMasterIdMax ++; } @@ -139,6 +139,8 @@ private: void WritePlaceholderReferenceShapes(PowerPointShapeExport& rDML, PageType ePageType); /** Writes the master page's content placeholders into the layout they belong on */ void WriteLayoutContentPlaceholders(PowerPointShapeExport& rDML); + /** Writes the placeholders a slide master holds that the page standing for it has none of */ + void WriteMasterOwnPlaceholders(PowerPointShapeExport& rDML, sal_uInt32 nMasterNum); void FindEquivalentMasterPages(); sal_uInt32 GetEquivalentMasterPage(sal_uInt32 nMasterPage); diff --git a/sd/source/filter/eppt/pptx-epptooxml.cxx b/sd/source/filter/eppt/pptx-epptooxml.cxx index 703f24339b0c..77c53d832d4f 100644 --- a/sd/source/filter/eppt/pptx-epptooxml.cxx +++ b/sd/source/filter/eppt/pptx-epptooxml.cxx @@ -2208,7 +2208,7 @@ void PowerPointExport::ImplWriteSlideMaster(sal_uInt32 nPageNum, Reference< XPro // every layout. if (aXBackgroundPropSet) ImplWriteBackground(pFS, aXBackgroundPropSet); - WriteShapeTree(pFS, MASTER, true, /*bSlideMasterPart=*/true); + WriteShapeTree(pFS, MASTER, true, /*bSlideMasterPart=*/true, nPageNum); pFS->endElementNS(XML_p, XML_cSld); @@ -2588,7 +2588,7 @@ void PowerPointExport::ImplWritePPTXLayoutWithContent( } void PowerPointExport::WriteShapeTree(const FSHelperPtr& pFS, PageType ePageType, bool bMaster, - bool bSlideMasterPart) + bool bSlideMasterPart, sal_uInt32 nMasterNum) { PowerPointShapeExport aDML(pFS, &maShapeMap, this); aDML.SetMaster(bMaster); @@ -2650,6 +2650,8 @@ void PowerPointExport::WriteShapeTree(const FSHelperPtr& pFS, PageType ePageType WritePlaceholderReferenceShapes(aDML, ePageType); if ( ePageType == LAYOUT ) WriteLayoutContentPlaceholders(aDML); + if (bSlideMasterPart) + WriteMasterOwnPlaceholders(aDML, nMasterNum); pFS->endElementNS(XML_p, XML_spTree); } @@ -3282,6 +3284,41 @@ void PowerPointExport::WriteLayoutContentPlaceholders(PowerPointShapeExport& rDM } } +void PowerPointExport::WriteMasterOwnPlaceholders(PowerPointShapeExport& rDML, + sal_uInt32 nMasterNum) +{ + SdPage* pPage = SdPage::getImplementation(mXDrawPage); + if (!pPage) + return; + + for (const auto& [ePlaceholder, ePresObjKind] : + { std::pair(Title, PresObjKind::Title), std::pair(Outliner, PresObjKind::Outline) }) + { + assert(isPlaceholderAllowedOnSlideMaster(ePlaceholder)); + if (pPage->GetPresObj(ePresObjKind)) + continue; // the shape tree has written the page's own + + // The pages standing for one slide master are its layouts, and a layout placeholder was + // imported with the geometry and the formatting it inherits from the master, so the first + // of them to carry this type carries the master's prototype of it. A layout that states + // its own geometry gives that instead - the closest thing left to ask. + for (sal_uInt32 i = 0; i < mnMasterPages; ++i) + { + if (i == mnCanvasMasterIndex + || (i != nMasterNum && maEquivalentMasters[i] != nMasterNum)) + continue; + + SdPage* pOther = dynamic_cast(maMastersLayouts[i].first); + SdrObject* pObj = pOther ? pOther->GetPresObj(ePresObjKind) : nullptr; + if (!pObj) + continue; + + rDML.WritePlaceholderShape(GetXShapeForSdrObject(pObj), ePlaceholder); + break; + } + } +} + sal_Int32 PowerPointExport::GetOrCreatePlaceholderIndex(const css::uno::Reference &rXShape) { // One index per shape, however many parts write it: a slide's placeholder pairs with the