sd/qa/unit/export-tests-ooxml2.cxx       |    4 
 sd/qa/unit/export-tests-ooxml4.cxx       |    9 -
 sd/source/filter/eppt/epptooxml.hxx      |    8 +
 sd/source/filter/eppt/pptx-epptooxml.cxx |  160 +++++++++++++++++++++++++++++--
 4 files changed, 164 insertions(+), 17 deletions(-)
New commits:
commit 9205b4eb09dcb4c91539e092db521154fc4e9523
Author:     Jaume Pujantell <jaume.pujant...@collabora.com>
AuthorDate: Tue Dec 17 15:10:03 2024 +0100
Commit:     Jaume Pujantell <jaume.pujant...@collabora.com>
CommitDate: Tue Jan 21 13:04:38 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>

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;

Reply via email to