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();
