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 d590f094cc "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 bff76421e2 "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 <jenkinscollaboraoffice@gmail.com>
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
(cherry picked from commit 2d9c808d1952c18daa007b41e70fc7e63f6c5712)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/180539
Tested-by: Jenkins
Reviewed-by: Jaume Pujantell <jaume.pujantell@collabora.com>
This commit is contained in:
Jaume Pujantell
2025-01-21 13:04:38 +01:00
parent 60b64338c2
commit 9205b4eb09
4 changed files with 164 additions and 17 deletions
+2 -2
View File
@@ -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");
+3 -6
View File
@@ -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)
+8
View File
@@ -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;
+151 -9
View File
@@ -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 << "\n--------------");
// 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;