include/oox/ppt/presentationfragmenthandler.hxx  |    2 
 include/oox/ppt/slidepersist.hxx                 |    1 
 oox/source/ppt/presentationfragmenthandler.cxx   |   65 ++++++++++++++++++
 sd/qa/unit/data/pptx/layout-clrmap-override.pptx |binary
 sd/qa/unit/export-tests-ooxml4.cxx               |   22 ++++++
 sd/source/filter/eppt/epptooxml.hxx              |    1 
 sd/source/filter/eppt/pptx-epptooxml.cxx         |   82 +++++++++++++++++++++++
 7 files changed, 173 insertions(+)

New commits:
commit 18dc10bd26720140564db2f328bdd495872ffb21
Author:     Szymon Kłos <[email protected]>
AuthorDate: Tue Feb 3 14:57:00 2026 +0000
Commit:     Tomaž Vajngerl <[email protected]>
CommitDate: Wed Feb 11 13:56:28 2026 +0100

    pptx: export overrideClrMapping in slide layouts
    
    - it's feature allowing to change mapping of colors for slide layouts
    - we can use theme colors like accent1 etc.
    - we were correctly importing them but on export colors were different
      due to missing mapping information
    
    Change-Id: Id73e87d5f912e851e6a4a32d516ec3548a984cef
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/198768
    Tested-by: Jenkins CollaboraOffice <[email protected]>
    Reviewed-by: Tomaž Vajngerl <[email protected]>

diff --git a/include/oox/ppt/presentationfragmenthandler.hxx 
b/include/oox/ppt/presentationfragmenthandler.hxx
index b0e46e16bc4b..845ca2e5e6e8 100644
--- a/include/oox/ppt/presentationfragmenthandler.hxx
+++ b/include/oox/ppt/presentationfragmenthandler.hxx
@@ -58,6 +58,8 @@ private:
                            const OUString& rMasterFragmentPath);
     void saveThemeToGrabBag(const oox::drawingml::ThemePtr& pThemePtr, 
sal_Int32 nThemeIdx);
     void saveColorMapToGrabBag(const oox::drawingml::ClrMapPtr& pClrMapPtr);
+    void saveLayoutColorMapToGrabBag(const oox::drawingml::ClrMapPtr& 
pClrMapPtr,
+                                     sal_Int32 nMasterIndex);
     void importCustomSlideShow(std::vector<CustomShow>& rCustomShowList);
     static void importSlideNames(::oox::core::XmlFilterBase& rFilter, const 
std::vector<SlidePersistPtr>& rSlidePersist);
 
diff --git a/include/oox/ppt/slidepersist.hxx b/include/oox/ppt/slidepersist.hxx
index c8cd4cc7647f..a4fe042b5496 100644
--- a/include/oox/ppt/slidepersist.hxx
+++ b/include/oox/ppt/slidepersist.hxx
@@ -97,6 +97,7 @@ public:
     bool isNotesPage() const { return mbNotes; }
 
     void setLayoutValueToken( sal_Int32 nLayoutValueToken ) { 
mnLayoutValueToken = nLayoutValueToken; }
+    sal_Int32 getLayoutValueToken() const { return mnLayoutValueToken; }
     sal_Int16 getLayoutFromValueToken() const;
 
 
diff --git a/oox/source/ppt/presentationfragmenthandler.cxx 
b/oox/source/ppt/presentationfragmenthandler.cxx
index 83c308902499..c4c682f755ae 100644
--- a/oox/source/ppt/presentationfragmenthandler.cxx
+++ b/oox/source/ppt/presentationfragmenthandler.cxx
@@ -286,7 +286,19 @@ void PresentationFragmentHandler::importMasterSlide(const 
Reference<frame::XMode
             }
         }
         importSlide( xMasterFragmentHandler, pMasterPersistPtr );
+
+        // Save master's color map before importing layout (layout may 
override it)
+        oox::drawingml::ClrMapPtr pMasterClrMap = 
pMasterPersistPtr->getClrMap();
+        saveColorMapToGrabBag(pMasterClrMap);
+
         rFilter.importFragment( new LayoutFragmentHandler( rFilter, 
aLayoutFragmentPath, pMasterPersistPtr ) );
+
+        // Check if layout had a color map override (clrMap changed during 
layout import)
+        if (pMasterPersistPtr->getClrMap() != pMasterClrMap && 
pMasterPersistPtr->getClrMap())
+        {
+            saveLayoutColorMapToGrabBag(pMasterPersistPtr->getClrMap(), 
nIndex);
+        }
+
         pMasterPersistPtr->createBackground( rFilter );
         pMasterPersistPtr->createXShapes( rFilter );
 
@@ -422,6 +434,59 @@ void 
PresentationFragmentHandler::saveColorMapToGrabBag(const oox::drawingml::Cl
     }
 }
 
+void PresentationFragmentHandler::saveLayoutColorMapToGrabBag(
+    const oox::drawingml::ClrMapPtr& pClrMapPtr,
+    sal_Int32 nMasterIndex)
+{
+    if (!pClrMapPtr)
+        return;
+
+    try
+    {
+        uno::Reference<beans::XPropertySet> xDocProps(getFilter().getModel(), 
uno::UNO_QUERY);
+        if (xDocProps.is())
+        {
+            uno::Reference<beans::XPropertySetInfo> xPropsInfo = 
xDocProps->getPropertySetInfo();
+
+            static constexpr OUString aGrabBagPropName = 
u"InteropGrabBag"_ustr;
+            if (xPropsInfo.is() && 
xPropsInfo->hasPropertyByName(aGrabBagPropName))
+            {
+                static constexpr auto constTokenArray = 
std::to_array<sal_Int32>({
+                        XML_bg1,     XML_tx1,     XML_bg2,     XML_tx2,
+                        XML_accent1, XML_accent2, XML_accent3, XML_accent4,
+                        XML_accent5, XML_accent6, XML_hlink,   XML_folHlink
+                });
+
+                comphelper::SequenceAsHashMap aGrabBag(
+                    xDocProps->getPropertyValue(aGrabBagPropName));
+
+                std::vector<beans::PropertyValue> aClrMapList;
+                size_t nColorMapSize = constTokenArray.size();
+                aClrMapList.reserve(nColorMapSize);
+                for (size_t i = 0; i < nColorMapSize; ++i)
+                {
+                    sal_Int32 nToken = constTokenArray[i];
+                    pClrMapPtr->getColorMap(nToken);
+                    aClrMapList.push_back(
+                        comphelper::makePropertyValue(OUString::number(i), 
nToken));
+                }
+
+                // Build key: "OOXLayoutClrMapOvr_<masterIndex>"
+                OUString sKey = "OOXLayoutClrMapOvr_" + 
OUString::number(nMasterIndex);
+
+                aGrabBag[sKey] <<= 
comphelper::containerToSequence(aClrMapList);
+
+                xDocProps->setPropertyValue(aGrabBagPropName,
+                                            
uno::Any(aGrabBag.getAsConstPropertyValueList()));
+            }
+        }
+    }
+    catch (const uno::Exception&)
+    {
+        SAL_WARN("oox", 
"oox::ppt::PresentationFragmentHandler::saveLayoutColorMapToGrabBag, Failed to 
save grab bag");
+    }
+}
+
 void PresentationFragmentHandler::importMasterSlides()
 {
     OUString aMasterFragmentPath;
diff --git a/sd/qa/unit/data/pptx/layout-clrmap-override.pptx 
b/sd/qa/unit/data/pptx/layout-clrmap-override.pptx
new file mode 100644
index 000000000000..aa87549f9e9c
Binary files /dev/null and b/sd/qa/unit/data/pptx/layout-clrmap-override.pptx 
differ
diff --git a/sd/qa/unit/export-tests-ooxml4.cxx 
b/sd/qa/unit/export-tests-ooxml4.cxx
index 5b23922b8149..764f70bdab68 100644
--- a/sd/qa/unit/export-tests-ooxml4.cxx
+++ b/sd/qa/unit/export-tests-ooxml4.cxx
@@ -65,6 +65,28 @@ CPPUNIT_TEST_FIXTURE(SdOOXMLExportTest4, testTdf160591)
     assertXPath(pXmlDoc2, 
"/p:sldMaster/p:cSld/p:bg/p:bgPr/a:solidFill/a:schemeClr", "val", u"dk1");
 }
 
+CPPUNIT_TEST_FIXTURE(SdOOXMLExportTest4, testLayoutClrMapOvr)
+{
+    // Test that slide layout color map override (clrMapOvr) is preserved on 
round-trip
+    createSdImpressDoc("pptx/layout-clrmap-override.pptx");
+    save(TestFilter::PPTX);
+
+    // Verify the slide layout has clrMapOvr with overrideClrMapping
+    xmlDocUniquePtr pXmlDoc = 
parseExport(u"ppt/slideLayouts/slideLayout1.xml"_ustr);
+    assertXPath(pXmlDoc, "/p:sldLayout/p:clrMapOvr/a:overrideClrMapping", 
"bg1", u"dk1");
+    assertXPath(pXmlDoc, "/p:sldLayout/p:clrMapOvr/a:overrideClrMapping", 
"tx1", u"lt1");
+    assertXPath(pXmlDoc, "/p:sldLayout/p:clrMapOvr/a:overrideClrMapping", 
"bg2", u"dk2");
+    assertXPath(pXmlDoc, "/p:sldLayout/p:clrMapOvr/a:overrideClrMapping", 
"tx2", u"lt2");
+    assertXPath(pXmlDoc, "/p:sldLayout/p:clrMapOvr/a:overrideClrMapping", 
"accent1", u"accent1");
+    assertXPath(pXmlDoc, "/p:sldLayout/p:clrMapOvr/a:overrideClrMapping", 
"accent2", u"accent2");
+    assertXPath(pXmlDoc, "/p:sldLayout/p:clrMapOvr/a:overrideClrMapping", 
"accent3", u"accent3");
+    assertXPath(pXmlDoc, "/p:sldLayout/p:clrMapOvr/a:overrideClrMapping", 
"accent4", u"accent4");
+    assertXPath(pXmlDoc, "/p:sldLayout/p:clrMapOvr/a:overrideClrMapping", 
"accent5", u"accent5");
+    assertXPath(pXmlDoc, "/p:sldLayout/p:clrMapOvr/a:overrideClrMapping", 
"accent6", u"accent6");
+    assertXPath(pXmlDoc, "/p:sldLayout/p:clrMapOvr/a:overrideClrMapping", 
"hlink", u"hlink");
+    assertXPath(pXmlDoc, "/p:sldLayout/p:clrMapOvr/a:overrideClrMapping", 
"folHlink", u"folHlink");
+}
+
 CPPUNIT_TEST_FIXTURE(SdOOXMLExportTest4, testSmartArtPreserve)
 {
     createSdImpressDoc("pptx/smartart-preserve.pptx");
diff --git a/sd/source/filter/eppt/epptooxml.hxx 
b/sd/source/filter/eppt/epptooxml.hxx
index 2a8404089939..c8359ecd0316 100644
--- a/sd/source/filter/eppt/epptooxml.hxx
+++ b/sd/source/filter/eppt/epptooxml.hxx
@@ -94,6 +94,7 @@ private:
     void ImplWritePPTXLayoutWithContent(
         sal_Int32 nOffset, sal_uInt32 nMasterNum, const OUString& aSlideName,
         css::uno::Reference<css::beans::XPropertySet> const& 
aXBackgroundPropSet);
+    void WriteLayoutClrMapOvr(const ::sax_fastparser::FSHelperPtr& pFS, 
sal_uInt32 nMasterNum);
     static void WriteDefaultColorSchemes(const FSHelperPtr& pFS);
     void WriteTheme( sal_Int32 nThemeNum, model::Theme* pTheme );
 
diff --git a/sd/source/filter/eppt/pptx-epptooxml.cxx 
b/sd/source/filter/eppt/pptx-epptooxml.cxx
index b8733f1954bf..bd61ad14bfc9 100644
--- a/sd/source/filter/eppt/pptx-epptooxml.cxx
+++ b/sd/source/filter/eppt/pptx-epptooxml.cxx
@@ -2226,6 +2226,84 @@ void PowerPointExport::ImplWriteSlideMaster(sal_uInt32 
nPageNum, Reference< XPro
     pFS->endDocument();
 }
 
+void PowerPointExport::WriteLayoutClrMapOvr(const FSHelperPtr& pFS, sal_uInt32 
nMasterNum)
+{
+    // Build key: "OOXLayoutClrMapOvr_<masterIndex>"
+    OUString sKey = "OOXLayoutClrMapOvr_" + OUString::number(nMasterNum);
+
+    // Get grab bag
+    uno::Sequence<beans::PropertyValue> aGrabBag;
+    Reference<XPropertySet> xModel(mXModel, UNO_QUERY);
+    if 
(xModel->getPropertySetInfo()->hasPropertyByName(u"InteropGrabBag"_ustr))
+        xModel->getPropertyValue(u"InteropGrabBag"_ustr) >>= aGrabBag;
+
+    // Look for layout color map override
+    uno::Sequence<beans::PropertyValue> aClrMapOvr;
+    for (const auto& rProp : aGrabBag)
+    {
+        if (rProp.Name == sKey)
+        {
+            rProp.Value >>= aClrMapOvr;
+            break;
+        }
+    }
+
+    pFS->startElementNS(XML_p, XML_clrMapOvr);
+
+    if (aClrMapOvr.hasElements())
+    {
+        // Convert token values to string names
+        std::vector<OUString> aClrMap(12);
+        for (const auto& item : aClrMapOvr)
+        {
+            sal_Int32 nToken = XML_TOKEN_INVALID;
+            item.Value >>= nToken;
+            OUString sName;
+            switch (nToken)
+            {
+                case XML_dk1:      sName = u"dk1"_ustr;      break;
+                case XML_lt1:      sName = u"lt1"_ustr;      break;
+                case XML_dk2:      sName = u"dk2"_ustr;      break;
+                case XML_lt2:      sName = u"lt2"_ustr;      break;
+                case XML_accent1:  sName = u"accent1"_ustr;  break;
+                case XML_accent2:  sName = u"accent2"_ustr;  break;
+                case XML_accent3:  sName = u"accent3"_ustr;  break;
+                case XML_accent4:  sName = u"accent4"_ustr;  break;
+                case XML_accent5:  sName = u"accent5"_ustr;  break;
+                case XML_accent6:  sName = u"accent6"_ustr;  break;
+                case XML_hlink:    sName = u"hlink"_ustr;    break;
+                case XML_folHlink: sName = u"folHlink"_ustr; break;
+                default:           sName = u"lt1"_ustr;      break;
+            }
+            sal_Int32 nIndex = item.Name.toInt32();
+            if (nIndex >= 0 && nIndex < 12)
+                aClrMap[nIndex] = sName;
+        }
+
+        // Write override color mapping
+        pFS->singleElementNS(XML_a, XML_overrideClrMapping,
+                             XML_bg1, aClrMap[0],
+                             XML_tx1, aClrMap[1],
+                             XML_bg2, aClrMap[2],
+                             XML_tx2, aClrMap[3],
+                             XML_accent1, aClrMap[4],
+                             XML_accent2, aClrMap[5],
+                             XML_accent3, aClrMap[6],
+                             XML_accent4, aClrMap[7],
+                             XML_accent5, aClrMap[8],
+                             XML_accent6, aClrMap[9],
+                             XML_hlink, aClrMap[10],
+                             XML_folHlink, aClrMap[11]);
+    }
+    else
+    {
+        // No override - inherit from master
+        pFS->singleElementNS(XML_a, XML_masterClrMapping);
+    }
+
+    pFS->endElementNS(XML_p, XML_clrMapOvr);
+}
+
 sal_Int32 PowerPointExport::GetLayoutFileId(sal_Int32 nOffset, sal_uInt32 
nMasterNum)
 {
     SAL_INFO("sd.eppt", "GetLayoutFileId offset: " << nOffset << " master: " 
<< nMasterNum);
@@ -2295,6 +2373,8 @@ void PowerPointExport::ImplWritePPTXLayout(sal_Int32 
nOffset, sal_uInt32 nMaster
 
     pFS->endElementNS(XML_p, XML_cSld);
 
+    WriteLayoutClrMapOvr(pFS, nMasterNum);
+
     pFS->endElementNS(XML_p, XML_sldLayout);
 
     mLayoutInfo[ nOffset ].mnFileIdArray[ nMasterNum ] = mnLayoutFileIdMax;
@@ -2355,6 +2435,8 @@ void PowerPointExport::ImplWritePPTXLayoutWithContent(
 
     pFS->endElementNS(XML_p, XML_cSld);
 
+    WriteLayoutClrMapOvr(pFS, nMasterNum);
+
     pFS->endElementNS(XML_p, XML_sldLayout);
 
     pFS->endDocument();

Reply via email to