sd/qa/unit/data/pptx/onemaster-twolayouts.pptx |binary sd/qa/unit/export-tests-ooxml2.cxx | 12 - sd/qa/unit/export-tests-ooxml4.cxx | 46 +++- sd/source/filter/eppt/epptooxml.hxx | 11 + sd/source/filter/eppt/pptx-epptooxml.cxx | 253 +++++++++++++++++++++++-- 5 files changed, 295 insertions(+), 27 deletions(-)
New commits: commit 8bf01e14f35941265ec83ba995e2fccdce2c02ab Author: Jaume Pujantell <jaume.pujant...@collabora.com> AuthorDate: Tue Jan 7 09:22:46 2025 +0100 Commit: Xisco Fauli <xiscofa...@libreoffice.org> CommitDate: Tue Feb 4 15:46:50 2025 +0100 sd: de-duplicate masters on save to pptx follow-up After commit 2d9c808d1952c18daa007b41e70fc7e63f6c5712 "sd: de-duplicate slide masters on save to pptx", there was some loss of data on save since when saving only the layouts for de-duplicated masters, not enough of the shape tree was being saved. Ideally we may want a clever algorithm that best separates the shape tree between master and layout, but just saving all the shape tree on the layout files when de-duplicating masters also works. Change-Id: I8b932708ef9212a4d5034713066299065083da8d Reviewed-on: https://gerrit.libreoffice.org/c/core/+/179861 Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoff...@gmail.com> Reviewed-by: Miklos Vajna <vmik...@collabora.com> (cherry picked from commit 3c8c537b3cfc4cf812e8728f6e6e46c0596ba7f6) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/180546 Tested-by: Jenkins Reviewed-by: Jaume Pujantell <jaume.pujant...@collabora.com> Signed-off-by: Xisco Fauli <xiscofa...@libreoffice.org> Reviewed-on: https://gerrit.libreoffice.org/c/core/+/181108 diff --git a/sd/qa/unit/data/pptx/onemaster-twolayouts.pptx b/sd/qa/unit/data/pptx/onemaster-twolayouts.pptx new file mode 100644 index 000000000000..f27260f2e583 Binary files /dev/null and b/sd/qa/unit/data/pptx/onemaster-twolayouts.pptx differ diff --git a/sd/qa/unit/export-tests-ooxml2.cxx b/sd/qa/unit/export-tests-ooxml2.cxx index 96c94aec32c1..d84abbd28e79 100644 --- a/sd/qa/unit/export-tests-ooxml2.cxx +++ b/sd/qa/unit/export-tests-ooxml2.cxx @@ -1316,7 +1316,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"14"); + "spid", u"59"); } CPPUNIT_TEST_FIXTURE(SdOOXMLExportTest2, testTdf112280) @@ -1889,10 +1889,10 @@ CPPUNIT_TEST_FIXTURE(SdOOXMLExportTest2, testTdf59323_slideFooters) } // Test placeholder indexes - xmlDocUniquePtr pXmlDocMaster = parseExport(u"ppt/slideMasters/slideMaster1.xml"_ustr); - assertXPath(pXmlDocMaster, "//p:ph [@type='dt']", "idx", u"1"); - assertXPath(pXmlDocMaster, "//p:ph [@type='ftr']", "idx", u"2"); - assertXPath(pXmlDocMaster, "//p:ph [@type='sldNum']", "idx", u"3"); + xmlDocUniquePtr pXmlDocLayout = parseExport(u"ppt/slideLayouts/slideLayout1.xml"_ustr); + assertXPath(pXmlDocLayout, "//p:ph [@type='dt']", "idx", u"1"); + assertXPath(pXmlDocLayout, "//p:ph [@type='ftr']", "idx", u"2"); + assertXPath(pXmlDocLayout, "//p:ph [@type='sldNum']", "idx", u"3"); xmlDocUniquePtr pXmlDocSlide1 = parseExport(u"ppt/slides/slide1.xml"_ustr); assertXPath(pXmlDocSlide1, "//p:ph [@type='dt']", "idx", u"1"); diff --git a/sd/qa/unit/export-tests-ooxml4.cxx b/sd/qa/unit/export-tests-ooxml4.cxx index 29d45da07bba..74862adca919 100644 --- a/sd/qa/unit/export-tests-ooxml4.cxx +++ b/sd/qa/unit/export-tests-ooxml4.cxx @@ -1274,6 +1274,43 @@ CPPUNIT_TEST_FIXTURE(SdOOXMLExportTest4, testPlaceHolderFitHeightToText) CPPUNIT_ASSERT_MESSAGE("PlaceHolder Fit height to text should be true.", bTextAutoGrowHeight); } +CPPUNIT_TEST_FIXTURE(SdOOXMLExportTest4, testDeduplicateMasters) +{ + createSdImpressDoc("pptx/onemaster-twolayouts.pptx"); + saveAndReload("Impress Office Open XML"); + + // Check that the document still has one master and two layouts + xmlDocUniquePtr pXmlDocContent = parseExport("ppt/presentation.xml"); + assertXPath(pXmlDocContent, "/p:presentation/p:sldMasterIdLst/p:sldMasterId"_ostr, 1); + pXmlDocContent = parseExport("ppt/slideMasters/slideMaster1.xml"); + assertXPath(pXmlDocContent, "/p:sldMaster/p:sldLayoutIdLst/p:sldLayoutId"_ostr, 2); + + // Check that both background colors have been preserved + uno::Reference<drawing::XMasterPagesSupplier> xDoc(mxComponent, uno::UNO_QUERY); + CPPUNIT_ASSERT(xDoc.is()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(2), xDoc->getMasterPages()->getCount()); + + uno::Reference<drawing::XDrawPage> xPage(xDoc->getMasterPages()->getByIndex(0), + uno::UNO_QUERY_THROW); + uno::Reference<beans::XPropertySet> xPropSet(xPage, uno::UNO_QUERY); + uno::Any aAny = xPropSet->getPropertyValue("Background"); + CPPUNIT_ASSERT(aAny.hasValue()); + uno::Reference<beans::XPropertySet> aXBackgroundPropSet; + aAny >>= aXBackgroundPropSet; + Color nColor; + CPPUNIT_ASSERT(aXBackgroundPropSet->getPropertyValue("FillColor") >>= nColor); + CPPUNIT_ASSERT_EQUAL(Color(0x0E2841), nColor); + + uno::Reference<drawing::XDrawPage> xPage1(xDoc->getMasterPages()->getByIndex(1), + uno::UNO_QUERY_THROW); + uno::Reference<beans::XPropertySet> xPropSet1(xPage1, uno::UNO_QUERY); + aAny = xPropSet1->getPropertyValue("Background"); + CPPUNIT_ASSERT(aAny.hasValue()); + aAny >>= aXBackgroundPropSet; + CPPUNIT_ASSERT(aXBackgroundPropSet->getPropertyValue("FillColor") >>= nColor); + CPPUNIT_ASSERT_EQUAL(Color(0x000000), nColor); +} + CPPUNIT_PLUGIN_IMPLEMENT(); /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sd/source/filter/eppt/epptooxml.hxx b/sd/source/filter/eppt/epptooxml.hxx index 8fcb4c0a9fbe..c44dc8fcfe1e 100644 --- a/sd/source/filter/eppt/epptooxml.hxx +++ b/sd/source/filter/eppt/epptooxml.hxx @@ -90,6 +90,9 @@ private: virtual void ImplWriteNotes( sal_uInt32 nPageNum ) override; virtual void ImplWriteSlideMaster( sal_uInt32 nPageNum, css::uno::Reference< css::beans::XPropertySet > const & aXBackgroundPropSet ) override; void ImplWritePPTXLayout( sal_Int32 nOffset, sal_uInt32 nMasterNum, const OUString& aSlideName ); + void ImplWritePPTXLayoutWithContent( + sal_Int32 nOffset, sal_uInt32 nMasterNum, const OUString& aSlideName, + css::uno::Reference<css::beans::XPropertySet> const& aXBackgroundPropSet); static void WriteDefaultColorSchemes(const FSHelperPtr& pFS); void WriteTheme( sal_Int32 nThemeNum, model::Theme* pTheme ); @@ -145,7 +148,7 @@ private: LayoutInfo mLayoutInfo[OOXML_LAYOUT_SIZE]; // Pairs of masters and layouts as used by Impress std::vector<std::pair<SdrPage*, sal_Int32>> maMastersLayouts; - // For each Impress master, which master will represent it on the exported file (themselves by default) + // For each Impress master, which master will represent it on the exported file (SAL_MAX_UINT32 if not in an equivalency group) std::vector<sal_uInt32> maEquivalentMasters; std::unique_ptr<SvtSecurityMapPersonalInfo> mpAuthorIDs; // map authors to remove personal info std::vector< ::sax_fastparser::FSHelperPtr > mpSlidesFSArray; diff --git a/sd/source/filter/eppt/pptx-epptooxml.cxx b/sd/source/filter/eppt/pptx-epptooxml.cxx index 0e7b0453144b..dbc12fac58e7 100644 --- a/sd/source/filter/eppt/pptx-epptooxml.cxx +++ b/sd/source/filter/eppt/pptx-epptooxml.cxx @@ -1513,10 +1513,9 @@ void PowerPointExport::FindEquivalentMasterPages() css::uno::Reference<css::drawing::XDrawPages> xDrawPages( mXMasterPagesSupplier->getMasterPages()); maMastersLayouts.resize(mnMasterPages); - maEquivalentMasters.resize(mnMasterPages); + maEquivalentMasters.resize(mnMasterPages, SAL_MAX_UINT32); for (sal_uInt32 i = 0; i < mnMasterPages; i++) { - maEquivalentMasters[i] = i; css::uno::Reference<css::drawing::XDrawPage> xDrawPage; uno::Any aAny(xDrawPages->getByIndex(i)); aAny >>= xDrawPage; @@ -1537,11 +1536,11 @@ void PowerPointExport::FindEquivalentMasterPages() for (sal_uInt32 i = 0; i < mnMasterPages; i++) { - if (!maMastersLayouts[i].first || maEquivalentMasters[i] != i) + if (!maMastersLayouts[i].first || maEquivalentMasters[i] != SAL_MAX_UINT32) continue; for (sal_uInt32 j = i + 1; j < mnMasterPages; j++) { - if (!maMastersLayouts[j].first || maEquivalentMasters[j] != j) + if (!maMastersLayouts[j].first || maEquivalentMasters[j] != SAL_MAX_UINT32) continue; if (lcl_ComparePageProperties(maMastersLayouts[i].first, maMastersLayouts[j].first) @@ -1550,6 +1549,7 @@ void PowerPointExport::FindEquivalentMasterPages() // If both masters have the same properties and objects, // we assume they are the same and only export the first one maEquivalentMasters[j] = i; + maEquivalentMasters[i] = i; } } } @@ -1564,9 +1564,22 @@ sal_uInt32 PowerPointExport::GetEquivalentMasterPage(sal_uInt32 nMasterPage) void PowerPointExport::ImplWriteSlideMaster(sal_uInt32 nPageNum, Reference< XPropertySet > const& aXBackgroundPropSet) { - if (nPageNum != GetEquivalentMasterPage(nPageNum)) + SAL_INFO("sd.eppt", "write master slide: " << nPageNum << " --------------"); + + if (nPageNum != GetEquivalentMasterPage(nPageNum) + && GetEquivalentMasterPage(nPageNum) != SAL_MAX_UINT32) { - // We already exported it's layout on an equivalent master so do nothing + // It's equivalent to an already written master, write only the layout file + if (maMastersLayouts[nPageNum].second != -1) + { + OUString aSlideName; + Reference<XNamed> xNamed(mXDrawPage, UNO_QUERY); + if (xNamed.is()) + aSlideName = xNamed->getName(); + ImplWritePPTXLayoutWithContent(maMastersLayouts[nPageNum].second, nPageNum, aSlideName, + aXBackgroundPropSet); + } + // Close the list tag if it was the last one if (nPageNum == mnMasterPages - 1) mPresentationFS->endElementNS(XML_p, XML_sldMasterIdLst); @@ -1574,8 +1587,6 @@ void PowerPointExport::ImplWriteSlideMaster(sal_uInt32 nPageNum, Reference< XPro return; } - SAL_INFO("sd.eppt", "write master slide: " << nPageNum << " --------------"); - // slides list if (nPageNum == 0) mPresentationFS->startElementNS(XML_p, XML_sldMasterIdLst); @@ -1614,9 +1625,19 @@ void PowerPointExport::ImplWriteSlideMaster(sal_uInt32 nPageNum, Reference< XPro pFS->startElementNS(XML_p, XML_cSld); - if (aXBackgroundPropSet) - ImplWriteBackground(pFS, aXBackgroundPropSet); - WriteShapeTree(pFS, MASTER, true); + if (GetEquivalentMasterPage(nPageNum) != nPageNum) + { + if (aXBackgroundPropSet) + ImplWriteBackground(pFS, aXBackgroundPropSet); + WriteShapeTree(pFS, MASTER, true); + } + else + { + // Minimal shape tree, the actual one will be written in the layout file. + pFS->startElementNS(XML_p, XML_spTree); + pFS->write(MAIN_GROUP); + pFS->endElementNS(XML_p, XML_spTree); + } pFS->endElementNS(XML_p, XML_cSld); @@ -1733,16 +1754,24 @@ void PowerPointExport::ImplWriteSlideMaster(sal_uInt32 nPageNum, Reference< XPro for (auto nLayout : aLayouts) { - ImplWritePPTXLayout(nLayout, nPageNum, aSlideName); + if (GetEquivalentMasterPage(nPageNum) == nPageNum) + ImplWritePPTXLayoutWithContent(nLayout, nPageNum, aSlideName, aXBackgroundPropSet); + else + ImplWritePPTXLayout(nLayout, nPageNum, aSlideName); AddLayoutIdAndRelation(pFS, GetLayoutFileId(nLayout, nPageNum)); } - // Export layouts of other Impress masters that came from a sinlge pptx master with multiple layouts + // Add layouts of other Impress masters that came from a single pptx master with multiple layouts for (sal_uInt32 i = 0; i < mnMasterPages; i++) { if (i != nPageNum && maEquivalentMasters[i] == nPageNum && maMastersLayouts[i].second != -1) { - ImplWritePPTXLayout(maMastersLayouts[i].second, i, aSlideName); + // Reserve layout file Id to be writen later + if (mLayoutInfo[maMastersLayouts[i].second].mnFileIdArray.size() < mnMasterPages) + mLayoutInfo[maMastersLayouts[i].second].mnFileIdArray.resize(mnMasterPages); + mLayoutInfo[maMastersLayouts[i].second].mnFileIdArray[i] = mnLayoutFileIdMax; + mnLayoutFileIdMax++; + AddLayoutIdAndRelation(pFS, GetLayoutFileId(maMastersLayouts[i].second, i)); } } @@ -1800,9 +1829,9 @@ void PowerPointExport::ImplWritePPTXLayout(sal_Int32 nOffset, sal_uInt32 nMaster u"application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml"_ustr); // add implicit relation of slide layout to slide master - addRelation(pFS->getOutputStream(), oox::getRelationship(Relationship::SLIDEMASTER), - Concat2View("../slideMasters/slideMaster" - + OUString::number(GetEquivalentMasterPage(nMasterNum) + 1) + ".xml")); + addRelation( + pFS->getOutputStream(), oox::getRelationship(Relationship::SLIDEMASTER), + Concat2View("../slideMasters/slideMaster" + OUString::number(nMasterNum + 1) + ".xml")); pFS->startElementNS(XML_p, XML_sldLayout, PNMSS, @@ -1835,6 +1864,57 @@ void PowerPointExport::ImplWritePPTXLayout(sal_Int32 nOffset, sal_uInt32 nMaster pFS->endDocument(); } +void PowerPointExport::ImplWritePPTXLayoutWithContent( + sal_Int32 nOffset, sal_uInt32 nMasterNum, const OUString& aSlideName, + Reference<XPropertySet> const& aXBackgroundPropSet) +{ + SAL_INFO("sd.eppt", "write layout: " << nOffset); + + if (mLayoutInfo[nOffset].mnFileIdArray.size() < mnMasterPages) + { + mLayoutInfo[nOffset].mnFileIdArray.resize(mnMasterPages); + } + + if (mLayoutInfo[nOffset].mnFileIdArray[nMasterNum] == 0) + { + mLayoutInfo[nOffset].mnFileIdArray[nMasterNum] = mnLayoutFileIdMax; + mnLayoutFileIdMax++; + } + sal_Int32 nLayoutFileId = mLayoutInfo[nOffset].mnFileIdArray[nMasterNum]; + + FSHelperPtr pFS = openFragmentStreamWithSerializer( + "ppt/slideLayouts/slideLayout" + OUString::number(nLayoutFileId) + ".xml", + "application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml"); + + // add implicit relation of slide layout to slide master + addRelation(pFS->getOutputStream(), oox::getRelationship(Relationship::SLIDEMASTER), + Concat2View("../slideMasters/slideMaster" + + OUString::number(GetEquivalentMasterPage(nMasterNum) + 1) + ".xml")); + + pFS->startElementNS(XML_p, XML_sldLayout, PNMSS, XML_type, aLayoutInfo[nOffset].sType, + XML_preserve, "1"); + + if (!aSlideName.isEmpty()) + { + pFS->startElementNS(XML_p, XML_cSld, XML_name, aSlideName); + } + else + { + pFS->startElementNS(XML_p, XML_cSld, XML_name, aLayoutInfo[nOffset].sName); + } + + if (aXBackgroundPropSet) + ImplWriteBackground(pFS, aXBackgroundPropSet); + + WriteShapeTree(pFS, MASTER, true); + + pFS->endElementNS(XML_p, XML_cSld); + + pFS->endElementNS(XML_p, XML_sldLayout); + + pFS->endDocument(); +} + void PowerPointExport::WriteShapeTree(const FSHelperPtr& pFS, PageType ePageType, bool bMaster) { PowerPointShapeExport aDML(pFS, &maShapeMap, this); @@ -2457,25 +2537,26 @@ Reference<XShape> PowerPointExport::GetReferencedPlaceholderXShape(const Placeho } if (ePresObjKind != PresObjKind::NONE) { - SdPage* pMasterPage; + SdrPage* pPage; if (ePageType == LAYOUT) { // since Layout pages do not have drawpages themselves - mXDrawPage is still the master they reference to.. - pMasterPage = SdPage::getImplementation(mXDrawPage); + pPage = SdPage::getImplementation(mXDrawPage); } else { - SdrPage* pPage = &SdPage::getImplementation(mXDrawPage)->TRG_GetMasterPage(); - for (sal_uInt32 i = 0; i < mnMasterPages; i++) + pPage = &SdPage::getImplementation(mXDrawPage)->TRG_GetMasterPage(); + } + for (sal_uInt32 i = 0; i < mnMasterPages; i++) + { + if (maMastersLayouts[i].first == pPage) { - if (maMastersLayouts[i].first == pPage) - { + if (maEquivalentMasters[i] < mnMasterPages) pPage = maMastersLayouts[maEquivalentMasters[i]].first; - break; - } + break; } - pMasterPage = dynamic_cast<SdPage*>(pPage); } + SdPage* pMasterPage = dynamic_cast<SdPage*>(pPage); if (SdrObject* pMasterFooter = (pMasterPage ? pMasterPage->GetPresObj(ePresObjKind) : nullptr)) return GetXShapeForSdrObject(pMasterFooter); commit 6d67e8d786343c17bcbe451b04965f10a94944e2 Author: Jaume Pujantell <jaume.pujant...@collabora.com> AuthorDate: Tue Dec 17 15:10:03 2024 +0100 Commit: Xisco Fauli <xiscofa...@libreoffice.org> CommitDate: Tue Feb 4 15:46:39 2025 +0100 sd: de-duplicate slide masters on save to pptx pptx files have layouts separated from masters, so multiple layouts can reference the same master. On import that master is duplicated for each layout. So we want that if it is saved without modification, the created file contains the same number of masters and layouts as the original pptx file. The commit d590f094ccd28ca449eff91692c2178058d5c621 "tdf#155512: sd: filter: eppt: add "SlideLayout" property to Slide Master" tried to do this but saved too many layouts, which appeared as extra masters on reload. The commit bff76421e234df7246a7f49c71a11432f86e09d1 "tdf#157740 FILESAVE PPTX: fix explosion of the number of master slides" fixed the issue of extra layouts but removed the de-duplication of masters. This commit avoids saving duplicated masters while keeping the correct saving of layouts. testTdf157740_slideMasters now checks for the correct number of masters and layouts. Change-Id: I199c55abe59d39c64e1e3e1fbd620e88f4f7290d Reviewed-on: https://gerrit.libreoffice.org/c/core/+/178669 Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoff...@gmail.com> Reviewed-by: Miklos Vajna <vmik...@collabora.com> (cherry picked from commit 2d9c808d1952c18daa007b41e70fc7e63f6c5712) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/180539 Tested-by: Jenkins Reviewed-by: Jaume Pujantell <jaume.pujant...@collabora.com> Signed-off-by: Xisco Fauli <xiscofa...@libreoffice.org> Reviewed-on: https://gerrit.libreoffice.org/c/core/+/181107 diff --git a/sd/qa/unit/export-tests-ooxml2.cxx b/sd/qa/unit/export-tests-ooxml2.cxx index 892ce86f231a..96c94aec32c1 100644 --- a/sd/qa/unit/export-tests-ooxml2.cxx +++ b/sd/qa/unit/export-tests-ooxml2.cxx @@ -1316,7 +1316,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"67"); + "spid", u"14"); } CPPUNIT_TEST_FIXTURE(SdOOXMLExportTest2, testTdf112280) @@ -1695,7 +1695,7 @@ CPPUNIT_TEST_FIXTURE(SdOOXMLExportTest2, testAccentColor) xmlDocUniquePtr pXmlDocTheme1 = parseExport(u"ppt/theme/theme1.xml"_ustr); assertXPath(pXmlDocTheme1, "/a:theme/a:themeElements/a:clrScheme/a:accent6/a:srgbClr", "val", u"70ad47"); - xmlDocUniquePtr pXmlDocTheme2 = parseExport(u"ppt/theme/theme12.xml"_ustr); + xmlDocUniquePtr pXmlDocTheme2 = parseExport(u"ppt/theme/theme2.xml"_ustr); assertXPath(pXmlDocTheme2, "/a:theme/a:themeElements/a:clrScheme/a:accent6/a:srgbClr", "val", u"deb340"); diff --git a/sd/qa/unit/export-tests-ooxml4.cxx b/sd/qa/unit/export-tests-ooxml4.cxx index 5c8085911303..29d45da07bba 100644 --- a/sd/qa/unit/export-tests-ooxml4.cxx +++ b/sd/qa/unit/export-tests-ooxml4.cxx @@ -1153,15 +1153,12 @@ CPPUNIT_TEST_FIXTURE(SdOOXMLExportTest4, testTdf157740_slideMasters) createSdImpressDoc("pptx/tdf157740.pptx"); saveAndReload(u"Impress Office Open XML"_ustr); - // Test how many slidemaster we have + // The original file has 1 slide master and 7 slide layouts in that master xmlDocUniquePtr pXmlDocContent = parseExport(u"ppt/presentation.xml"_ustr); - assertXPath(pXmlDocContent, "/p:presentation/p:sldMasterIdLst/p:sldMasterId", 7); + assertXPath(pXmlDocContent, "/p:presentation/p:sldMasterIdLst/p:sldMasterId", 1); pXmlDocContent = parseExport(u"ppt/slideMasters/slideMaster1.xml"_ustr); - assertXPath(pXmlDocContent, "/p:sldMaster/p:sldLayoutIdLst/p:sldLayoutId", 1); - - pXmlDocContent = parseExport(u"ppt/slideMasters/slideMaster7.xml"_ustr); - assertXPath(pXmlDocContent, "/p:sldMaster/p:sldLayoutIdLst/p:sldLayoutId", 1); + assertXPath(pXmlDocContent, "/p:sldMaster/p:sldLayoutIdLst/p:sldLayoutId", 7); } CPPUNIT_TEST_FIXTURE(SdOOXMLExportTest4, testTdf159931_slideLayouts) diff --git a/sd/source/filter/eppt/epptooxml.hxx b/sd/source/filter/eppt/epptooxml.hxx index f8e4eea812cf..8fcb4c0a9fbe 100644 --- a/sd/source/filter/eppt/epptooxml.hxx +++ b/sd/source/filter/eppt/epptooxml.hxx @@ -131,6 +131,9 @@ private: css::uno::Reference<css::drawing::XShape> GetReferencedPlaceholderXShape(const PlaceholderType eType, PageType ePageType) const; void WritePlaceholderReferenceShapes(PowerPointShapeExport& rDML, PageType ePageType); + void FindEquivalentMasterPages(); + sal_uInt32 GetEquivalentMasterPage(sal_uInt32 nMasterPage); + /// Should we export as .pptm, ie. do we contain macros? bool mbPptm; @@ -140,6 +143,10 @@ private: ::sax_fastparser::FSHelperPtr mPresentationFS; LayoutInfo mLayoutInfo[OOXML_LAYOUT_SIZE]; + // Pairs of masters and layouts as used by Impress + std::vector<std::pair<SdrPage*, sal_Int32>> maMastersLayouts; + // For each Impress master, which master will represent it on the exported file (themselves by default) + std::vector<sal_uInt32> maEquivalentMasters; std::unique_ptr<SvtSecurityMapPersonalInfo> mpAuthorIDs; // map authors to remove personal info std::vector< ::sax_fastparser::FSHelperPtr > mpSlidesFSArray; sal_Int32 mnLayoutFileIdMax; @@ -147,6 +154,7 @@ private: sal_uInt32 mnSlideIdMax; sal_uInt32 mnSlideMasterIdMax; sal_uInt32 mnAnimationNodeIdMax; + sal_uInt32 mnThemeIdMax; sal_uInt32 mnDiagramId; diff --git a/sd/source/filter/eppt/pptx-epptooxml.cxx b/sd/source/filter/eppt/pptx-epptooxml.cxx index 01174b023902..0e7b0453144b 100644 --- a/sd/source/filter/eppt/pptx-epptooxml.cxx +++ b/sd/source/filter/eppt/pptx-epptooxml.cxx @@ -25,6 +25,8 @@ #include <oox/ole/vbaproject.hxx> #include "epptooxml.hxx" #include <oox/export/shapes.hxx> +#include <svx/svdlayer.hxx> +#include <unokywds.hxx> #include <comphelper/sequenceashashmap.hxx> #include <comphelper/storagehelper.hxx> @@ -41,6 +43,8 @@ #include <com/sun/star/drawing/FillStyle.hpp> #include <com/sun/star/drawing/XDrawPages.hpp> #include <com/sun/star/drawing/XDrawPagesSupplier.hpp> +#include <com/sun/star/drawing/XMasterPageTarget.hpp> +#include <com/sun/star/drawing/XMasterPagesSupplier.hpp> #include <com/sun/star/embed/ElementModes.hpp> #include <com/sun/star/geometry/RealPoint2D.hpp> #include <com/sun/star/office/XAnnotationEnumeration.hpp> @@ -348,6 +352,7 @@ PowerPointExport::PowerPointExport(const Reference< XComponentContext >& rContex , mnSlideIdMax(1 << 8) , mnSlideMasterIdMax(1U << 31) , mnAnimationNodeIdMax(1) + , mnThemeIdMax(0) , mnDiagramId(1) , mbCreateNotes(false) , mnPlaceholderIndexMax(1) @@ -1449,8 +1454,126 @@ void PowerPointExport::AddLayoutIdAndRelation(const FSHelperPtr& pFS, sal_Int32 FSNS(XML_r, XML_id), sRelId); } +static bool lcl_ContainsEquivalentObject(SdrPage* pPage, SdrObject* pObj) +{ + bool bFound = false; + + if (!pPage || !pObj) + return bFound; + + for (size_t nObj = 0; nObj < pPage->GetObjCount(); ++nObj) + { + SdrObject* pObjNext = pPage->GetObj(nObj); + if (pObjNext && pObjNext->GetMergedItemSet().Equals(pObj->GetMergedItemSet(), false)) + { + bFound = true; + break; + } + } + + return bFound; +} + +static bool lcl_ComparePageObjects(SdrPage* pMasterPage, SdrPage* pMasterNext) +{ + if (!pMasterPage || !pMasterNext) + return false; + + SdrObject* pObjNext; + SdrLayerID aLayer = pMasterNext->GetLayerAdmin().GetLayerID(sUNO_LayerName_background_objects); + + for (size_t nObj = 0; nObj < pMasterPage->GetObjCount(); ++nObj) + { + pObjNext = pMasterPage->GetObj(nObj); + if (!pObjNext || pObjNext->GetLayer() == aLayer) + continue; + + if (!lcl_ContainsEquivalentObject(pMasterNext, pObjNext)) + return false; + } + + // No differences found + return true; +} + +static bool lcl_ComparePageProperties(SdrPage* pMasterPage, SdrPage* pMasterNext) +{ + if (!pMasterPage || !pMasterNext) + return false; + + SdrPageProperties& rProperties = pMasterPage->getSdrPageProperties(); + SdrPageProperties& rNextProperties = pMasterNext->getSdrPageProperties(); + + return rProperties.GetItemSet().Equals(rNextProperties.GetItemSet(), false) + && rProperties.getTheme().get() == rNextProperties.getTheme().get(); +} + +void PowerPointExport::FindEquivalentMasterPages() +{ + css::uno::Reference<css::drawing::XDrawPages> xDrawPages( + mXMasterPagesSupplier->getMasterPages()); + maMastersLayouts.resize(mnMasterPages); + maEquivalentMasters.resize(mnMasterPages); + for (sal_uInt32 i = 0; i < mnMasterPages; i++) + { + maEquivalentMasters[i] = i; + css::uno::Reference<css::drawing::XDrawPage> xDrawPage; + uno::Any aAny(xDrawPages->getByIndex(i)); + aAny >>= xDrawPage; + if (!xDrawPage.is()) + continue; + + maMastersLayouts[i] = std::make_pair(SdPage::getImplementation(xDrawPage), -1); + uno::Reference<beans::XPropertySet> xPagePropSet(xDrawPage, uno::UNO_QUERY_THROW); + if (xPagePropSet.is()) + { + uno::Any aLayout = xPagePropSet->getPropertyValue("SlideLayout"); + if (aLayout.hasValue()) + { + aLayout >>= maMastersLayouts[i].second; + } + } + } + + for (sal_uInt32 i = 0; i < mnMasterPages; i++) + { + if (!maMastersLayouts[i].first || maEquivalentMasters[i] != i) + continue; + for (sal_uInt32 j = i + 1; j < mnMasterPages; j++) + { + if (!maMastersLayouts[j].first || maEquivalentMasters[j] != j) + continue; + + if (lcl_ComparePageProperties(maMastersLayouts[i].first, maMastersLayouts[j].first) + && lcl_ComparePageObjects(maMastersLayouts[i].first, maMastersLayouts[j].first)) + { + // If both masters have the same properties and objects, + // we assume they are the same and only export the first one + maEquivalentMasters[j] = i; + } + } + } +} + +sal_uInt32 PowerPointExport::GetEquivalentMasterPage(sal_uInt32 nMasterPage) +{ + if (maEquivalentMasters.size() == 0) + FindEquivalentMasterPages(); + return maEquivalentMasters[nMasterPage]; +} + void PowerPointExport::ImplWriteSlideMaster(sal_uInt32 nPageNum, Reference< XPropertySet > const& aXBackgroundPropSet) { + if (nPageNum != GetEquivalentMasterPage(nPageNum)) + { + // We already exported it's layout on an equivalent master so do nothing + // Close the list tag if it was the last one + if (nPageNum == mnMasterPages - 1) + mPresentationFS->endElementNS(XML_p, XML_sldMasterIdLst); + + return; + } + SAL_INFO("sd.eppt", "write master slide: " << nPageNum << " --------------"); // slides list @@ -1481,12 +1604,11 @@ void PowerPointExport::ImplWriteSlideMaster(sal_uInt32 nPageNum, Reference< XPro } // write theme per master - WriteTheme(nPageNum, pTheme); + WriteTheme(mnThemeIdMax, pTheme); // add implicit relation to the presentation theme - addRelation(pFS->getOutputStream(), - oox::getRelationship(Relationship::THEME), - Concat2View("../theme/theme" + OUString::number(nPageNum + 1) + ".xml")); + addRelation(pFS->getOutputStream(), oox::getRelationship(Relationship::THEME), + Concat2View("../theme/theme" + OUString::number(++mnThemeIdMax) + ".xml")); pFS->startElementNS(XML_p, XML_sldMaster, PNMSS); @@ -1615,6 +1737,16 @@ void PowerPointExport::ImplWriteSlideMaster(sal_uInt32 nPageNum, Reference< XPro AddLayoutIdAndRelation(pFS, GetLayoutFileId(nLayout, nPageNum)); } + // Export layouts of other Impress masters that came from a sinlge pptx master with multiple layouts + for (sal_uInt32 i = 0; i < mnMasterPages; i++) + { + if (i != nPageNum && maEquivalentMasters[i] == nPageNum && maMastersLayouts[i].second != -1) + { + ImplWritePPTXLayout(maMastersLayouts[i].second, i, aSlideName); + AddLayoutIdAndRelation(pFS, GetLayoutFileId(maMastersLayouts[i].second, i)); + } + } + pFS->endElementNS(XML_p, XML_sldLayoutIdLst); pFS->endElementNS(XML_p, XML_sldMaster); @@ -1668,9 +1800,9 @@ void PowerPointExport::ImplWritePPTXLayout(sal_Int32 nOffset, sal_uInt32 nMaster u"application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml"_ustr); // add implicit relation of slide layout to slide master - addRelation(pFS->getOutputStream(), - oox::getRelationship(Relationship::SLIDEMASTER), - Concat2View("../slideMasters/slideMaster" + OUString::number(nMasterNum + 1) + ".xml")); + addRelation(pFS->getOutputStream(), oox::getRelationship(Relationship::SLIDEMASTER), + Concat2View("../slideMasters/slideMaster" + + OUString::number(GetEquivalentMasterPage(nMasterNum) + 1) + ".xml")); pFS->startElementNS(XML_p, XML_sldLayout, PNMSS, @@ -2333,9 +2465,19 @@ Reference<XShape> PowerPointExport::GetReferencedPlaceholderXShape(const Placeho } else { - pMasterPage = &static_cast<SdPage&>(SdPage::getImplementation(mXDrawPage)->TRG_GetMasterPage()); + SdrPage* pPage = &SdPage::getImplementation(mXDrawPage)->TRG_GetMasterPage(); + for (sal_uInt32 i = 0; i < mnMasterPages; i++) + { + if (maMastersLayouts[i].first == pPage) + { + pPage = maMastersLayouts[maEquivalentMasters[i]].first; + break; + } + } + pMasterPage = dynamic_cast<SdPage*>(pPage); } - if (SdrObject* pMasterFooter = pMasterPage->GetPresObj(ePresObjKind)) + if (SdrObject* pMasterFooter + = (pMasterPage ? pMasterPage->GetPresObj(ePresObjKind) : nullptr)) return GetXShapeForSdrObject(pMasterFooter); } return nullptr;