sd/qa/unit/data/pptx/onemaster-twolayouts.pptx |binary sd/qa/unit/export-tests-ooxml2.cxx | 10 - sd/qa/unit/export-tests-ooxml4.cxx | 37 ++++++ sd/source/filter/eppt/epptooxml.hxx | 5 sd/source/filter/eppt/pptx-epptooxml.cxx | 133 ++++++++++++++++++++----- 5 files changed, 153 insertions(+), 32 deletions(-)
New commits: commit 3c8c537b3cfc4cf812e8728f6e6e46c0596ba7f6 Author: Jaume Pujantell <jaume.pujant...@collabora.com> AuthorDate: Tue Jan 7 09:22:46 2025 +0100 Commit: Miklos Vajna <vmik...@collabora.com> CommitDate: Thu Jan 9 12:17:13 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> 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 a17b7b29b249..7a1df1662769 100644 --- a/sd/qa/unit/export-tests-ooxml2.cxx +++ b/sd/qa/unit/export-tests-ooxml2.cxx @@ -1343,7 +1343,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"_ostr, - "spid"_ostr, "14"); + "spid"_ostr, "59"); } CPPUNIT_TEST_FIXTURE(SdOOXMLExportTest2, testTdf112280) @@ -1936,10 +1936,10 @@ CPPUNIT_TEST_FIXTURE(SdOOXMLExportTest2, testTdf59323_slideFooters) } // Test placeholder indexes - xmlDocUniquePtr pXmlDocMaster = parseExport("ppt/slideMasters/slideMaster1.xml"); - assertXPath(pXmlDocMaster, "//p:ph [@type='dt']"_ostr, "idx"_ostr, "1"); - assertXPath(pXmlDocMaster, "//p:ph [@type='ftr']"_ostr, "idx"_ostr, "2"); - assertXPath(pXmlDocMaster, "//p:ph [@type='sldNum']"_ostr, "idx"_ostr, "3"); + xmlDocUniquePtr pXmlDocLayout = parseExport("ppt/slideLayouts/slideLayout1.xml"); + assertXPath(pXmlDocLayout, "//p:ph [@type='dt']"_ostr, "idx"_ostr, "1"); + assertXPath(pXmlDocLayout, "//p:ph [@type='ftr']"_ostr, "idx"_ostr, "2"); + assertXPath(pXmlDocLayout, "//p:ph [@type='sldNum']"_ostr, "idx"_ostr, "3"); xmlDocUniquePtr pXmlDocSlide1 = parseExport("ppt/slides/slide1.xml"); assertXPath(pXmlDocSlide1, "//p:ph [@type='dt']"_ostr, "idx"_ostr, "1"); diff --git a/sd/qa/unit/export-tests-ooxml4.cxx b/sd/qa/unit/export-tests-ooxml4.cxx index dee24a51b509..f53f959388b9 100644 --- a/sd/qa/unit/export-tests-ooxml4.cxx +++ b/sd/qa/unit/export-tests-ooxml4.cxx @@ -1160,6 +1160,43 @@ CPPUNIT_TEST_FIXTURE(SdOOXMLExportTest4, testTdf159931_slideLayouts) bool(xNameAccess->hasByName("ppt/slideLayouts/" + sSlideLayoutName2))); } +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 e36fd3eac739..92ab55e1debd 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::vector< ::sax_fastparser::FSHelperPtr > mpSlidesFSArray; sal_Int32 mnLayoutFileIdMax; diff --git a/sd/source/filter/eppt/pptx-epptooxml.cxx b/sd/source/filter/eppt/pptx-epptooxml.cxx index b6a025e8a78a..a9f2fa4603ab 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 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); + // 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 "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")); + 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);