oox/source/ppt/slidepersist.cxx                      |   12 +-
 sd/qa/unit/data/odp/tdf169825_layout_type.odp        |binary
 sd/qa/unit/data/pptx/tdf169825_vertical_layouts.pptx |binary
 sd/qa/unit/export-tests-ooxml4.cxx                   |   89 +++++++++++++++++++
 sd/source/filter/eppt/epptbase.hxx                   |    1 
 sd/source/filter/eppt/epptooxml.hxx                  |    3 
 sd/source/filter/eppt/pptx-epptooxml.cxx             |   43 +++++----
 7 files changed, 123 insertions(+), 25 deletions(-)

New commits:
commit d7b86379a4c225898ba3663ebcbc240629e3edcf
Author:     Aron Budea <[email protected]>
AuthorDate: Fri Dec 5 15:28:56 2025 +1030
Commit:     Balazs Varga <[email protected]>
CommitDate: Fri Dec 12 12:29:27 2025 +0100

    tdf#169825 AutoLayout can be incorrectly exported to PPTX
    
    ...as vertTitleAndTxOverChart, if ODP layout was
    AUTOLAYOUT_NOTES, causing corruption.
    
    PPTX export code directly matched LO's AutoLayout IDs to
    PPTX layout types (OOXML spec: 19.7.15 ST_SlideLayoutType
    (Slide Layout Type)) without conversion, as some don't have
    direct match, and need to be matched to blank layouts. This
    could result in PPTXes that can't be opened in PowerPoint.
    
    It could also result in PPTXes with wrong layout types when
    using LO's own vertical layouts. The files could still be
    opened in PP in this case.
    
    In addition, import code imported vertical PPTX layouts
    with wrong IDs, matching them to notes and handout autolayouts.
    
    Part of the mentioned behavior happened since
    bff76421e234df7246a7f49c71a11432f86e09d1.
    
    Change-Id: Iba7a50795979c22bc566aa2cd6ea4d82f7f9477e
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/195049
    Tested-by: Jenkins
    Reviewed-by: Mike Kaganski <[email protected]>
    Reviewed-by: Aron Budea <[email protected]>
    (cherry picked from commit eb338daa90afd569e1b86c058bb88a89fab6a557)
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/195280
    Reviewed-by: Balazs Varga <[email protected]>
    Tested-by: Jenkins CollaboraOffice <[email protected]>

diff --git a/oox/source/ppt/slidepersist.cxx b/oox/source/ppt/slidepersist.cxx
index 446b7d5623a2..9829586a550c 100644
--- a/oox/source/ppt/slidepersist.cxx
+++ b/oox/source/ppt/slidepersist.cxx
@@ -88,6 +88,8 @@ SlidePersist::~SlidePersist()
 {
 }
 
+// the list 'const PPTXLayoutInfo aLayoutInfo[...]' used in PowerPointExport
+// likely needs to be updated after any changes here
 sal_Int16 SlidePersist::getLayoutFromValueToken() const
 {
     sal_Int16 nLayout = 20;     // 20 == blank (so many magic numbers :-( the 
description at com.sun.star.presentation.DrawPage.Layout does not help)
@@ -97,7 +99,7 @@ sal_Int16 SlidePersist::getLayoutFromValueToken() const
         case XML_chart:             nLayout =  2; break;
         case XML_chartAndTx:        nLayout =  7; break;
         case XML_clipArtAndTx:      nLayout =  9; break;
-        case XML_clipArtAndVertTx:  nLayout = 24; break;
+        case XML_clipArtAndVertTx:  nLayout = 30; break;
         case XML_fourObj:           nLayout = 18; break;
         case XML_obj:               nLayout = 11; break;
         case XML_objAndTx:          nLayout = 13; break;
@@ -112,15 +114,15 @@ sal_Int16 SlidePersist::getLayoutFromValueToken() const
         case XML_twoObjOverTx:      nLayout = 16; break;
         case XML_tx:                nLayout =  1; break;
         case XML_txAndChart:        nLayout =  4; break;
-        case XML_txAndClipArt:      nLayout =  6; break;
+        case XML_txAndClipArt:
         case XML_txAndMedia:        nLayout =  6; break;
         case XML_txAndObj:          nLayout = 10; break;
         case XML_objAndTwoObj:
         case XML_txAndTwoObj:       nLayout = 12; break;
         case XML_txOverObj:         nLayout = 17; break;
-        case XML_vertTitleAndTx:    nLayout = 22; break;
-        case XML_vertTitleAndTxOverChart: nLayout = 21; break;
-        case XML_vertTx:            nLayout = 23; break;
+        case XML_vertTitleAndTx:    nLayout = 28; break;
+        case XML_vertTitleAndTxOverChart: nLayout = 27; break;
+        case XML_vertTx:            nLayout = 29; break;
         case XML_objOnly:           nLayout = 32; break;
 
         case XML_twoTxTwoObj:
diff --git a/sd/qa/unit/data/odp/tdf169825_layout_type.odp 
b/sd/qa/unit/data/odp/tdf169825_layout_type.odp
new file mode 100644
index 000000000000..aa9dda374262
Binary files /dev/null and b/sd/qa/unit/data/odp/tdf169825_layout_type.odp 
differ
diff --git a/sd/qa/unit/data/pptx/tdf169825_vertical_layouts.pptx 
b/sd/qa/unit/data/pptx/tdf169825_vertical_layouts.pptx
new file mode 100644
index 000000000000..5401a39c9826
Binary files /dev/null and 
b/sd/qa/unit/data/pptx/tdf169825_vertical_layouts.pptx differ
diff --git a/sd/qa/unit/export-tests-ooxml4.cxx 
b/sd/qa/unit/export-tests-ooxml4.cxx
index fd9b7fd3cfca..fd0eef3fac23 100644
--- a/sd/qa/unit/export-tests-ooxml4.cxx
+++ b/sd/qa/unit/export-tests-ooxml4.cxx
@@ -20,6 +20,7 @@
 #include <svx/xlndsit.hxx>
 #include <svx/svdoole2.hxx>
 #include <svx/svdotable.hxx>
+#include <xmloff/autolayout.hxx>
 
 #include <com/sun/star/awt/FontUnderline.hpp>
 #include <com/sun/star/drawing/EnhancedCustomShapeParameterPair.hpp>
@@ -1647,6 +1648,94 @@ CPPUNIT_TEST_FIXTURE(SdOOXMLExportTest4, 
testtdf169496_hidden_graphic)
         CPPUNIT_FAIL("Names of graphics is incorrect");
 }
 
+CPPUNIT_TEST_FIXTURE(SdOOXMLExportTest4, testtdf169825_vertical_layouts)
+{
+    createSdImpressDoc("pptx/tdf169825_vertical_layouts.pptx");
+    save(u"Impress Office Open XML"_ustr);
+
+    xmlDocUniquePtr pXmlDocRels = 
parseExport(u"ppt/slides/_rels/slide1.xml.rels"_ustr);
+    CPPUNIT_ASSERT(pXmlDocRels);
+    // find layout XML for the slide from the relationship file
+    OUString sLayoutRelRelative
+        = getXPath(pXmlDocRels, "/rels:Relationships/rels:Relationship", 
"Target");
+    OUString sLayoutRelAbs = sLayoutRelRelative.replaceFirst("..", "ppt");
+    xmlDocUniquePtr pXmlDocLayout = parseExport(sLayoutRelAbs);
+    CPPUNIT_ASSERT(pXmlDocLayout);
+
+    // without the SlidePersist::getLayoutFromValueToken() part of the patch,
+    // this and the next layout types would both be exported as blank
+    assertXPath(pXmlDocLayout, "/p:sldLayout", "type", u"vertTitleAndTx");
+
+    pXmlDocRels = parseExport(u"ppt/slides/_rels/slide2.xml.rels"_ustr);
+    CPPUNIT_ASSERT(pXmlDocRels);
+    sLayoutRelRelative = getXPath(pXmlDocRels, 
"/rels:Relationships/rels:Relationship", "Target");
+    sLayoutRelAbs = sLayoutRelRelative.replaceFirst("..", "ppt");
+    pXmlDocLayout = parseExport(sLayoutRelAbs);
+    CPPUNIT_ASSERT(pXmlDocLayout);
+
+    assertXPath(pXmlDocLayout, "/p:sldLayout", "type", u"vertTx");
+}
+
+CPPUNIT_TEST_FIXTURE(SdOOXMLExportTest4, 
testtdf169825_vertical_layouts_from_scratch)
+{
+    createSdImpressDoc();
+
+    uno::Reference<drawing::XDrawPagesSupplier> xDoc(mxComponent, 
uno::UNO_QUERY_THROW);
+    uno::Reference<drawing::XDrawPages> xPages = xDoc->getDrawPages();
+    uno::Reference<drawing::XDrawPage> xPage(xPages->getByIndex(0), 
uno::UNO_QUERY);
+    uno::Reference<beans::XPropertySet> xPageSet(xPage, uno::UNO_QUERY_THROW);
+    xPageSet->setPropertyValue(
+        "Layout",
+        
uno::Any(static_cast<sal_Int32>(AutoLayout::AUTOLAYOUT_VTITLE_VCONTENT_OVER_VCONTENT)));
+
+    uno::Reference<drawing::XDrawPage> xPage2(xPages->insertNewByIndex(1), 
uno::UNO_SET_THROW);
+    uno::Reference<beans::XPropertySet> xPageSet2(xPage2, 
uno::UNO_QUERY_THROW);
+    xPageSet2->setPropertyValue(
+        "Layout", 
uno::Any(static_cast<sal_Int32>(AutoLayout::AUTOLAYOUT_VTITLE_VCONTENT)));
+    save(u"Impress Office Open XML"_ustr);
+
+    xmlDocUniquePtr pXmlDocRels = 
parseExport(u"ppt/slides/_rels/slide1.xml.rels"_ustr);
+    CPPUNIT_ASSERT(pXmlDocRels);
+    // find layout XML for the slide from the relationship file
+    OUString sLayoutRelRelative
+        = getXPath(pXmlDocRels, "/rels:Relationships/rels:Relationship", 
"Target");
+    OUString sLayoutRelAbs = sLayoutRelRelative.replaceFirst("..", "ppt");
+    xmlDocUniquePtr pXmlDocLayout = parseExport(sLayoutRelAbs);
+    CPPUNIT_ASSERT(pXmlDocLayout);
+
+    // without the fix in place this would be exported as "objTx"
+    assertXPath(pXmlDocLayout, "/p:sldLayout", "type", 
u"vertTitleAndTxOverChart");
+
+    pXmlDocRels = parseExport(u"ppt/slides/_rels/slide2.xml.rels"_ustr);
+    CPPUNIT_ASSERT(pXmlDocRels);
+    sLayoutRelRelative = getXPath(pXmlDocRels, 
"/rels:Relationships/rels:Relationship", "Target");
+    sLayoutRelAbs = sLayoutRelRelative.replaceFirst("..", "ppt");
+    pXmlDocLayout = parseExport(sLayoutRelAbs);
+    CPPUNIT_ASSERT(pXmlDocLayout);
+
+    // without the fix in place this would be exported as "picTx"
+    assertXPath(pXmlDocLayout, "/p:sldLayout", "type", u"vertTitleAndTx");
+}
+
+CPPUNIT_TEST_FIXTURE(SdOOXMLExportTest4, testtdf169825_layout_type)
+{
+    createSdImpressDoc("odp/tdf169825_layout_type.odp");
+    save(u"Impress Office Open XML"_ustr);
+
+    xmlDocUniquePtr pXmlDocRels = 
parseExport(u"ppt/slides/_rels/slide1.xml.rels"_ustr);
+    CPPUNIT_ASSERT(pXmlDocRels);
+    // find layout XML for the slide from the relationship file
+    OUString sLayoutRelRelative
+        = getXPath(pXmlDocRels, "/rels:Relationships/rels:Relationship", 
"Target");
+    OUString sLayoutRelAbs = sLayoutRelRelative.replaceFirst("..", "ppt");
+    xmlDocUniquePtr pXmlDocLayout = parseExport(sLayoutRelAbs);
+    CPPUNIT_ASSERT(pXmlDocLayout);
+
+    // without the fix in place this would be exported as 
"vertTitleAndTxOverChart"
+    // while in the original ODP this was - still possibly bogus - notes layout
+    assertXPath(pXmlDocLayout, "/p:sldLayout", "type", u"blank");
+}
+
 CPPUNIT_TEST_FIXTURE(SdOOXMLExportTest4, testFooterIdxConsistency)
 {
     createSdImpressDoc("pptx/multiplelayoutfooter.pptx");
diff --git a/sd/source/filter/eppt/epptbase.hxx 
b/sd/source/filter/eppt/epptbase.hxx
index 8d587742f40c..a5c960e96c45 100644
--- a/sd/source/filter/eppt/epptbase.hxx
+++ b/sd/source/filter/eppt/epptbase.hxx
@@ -74,7 +74,6 @@ enum class EppLayout
 };
 
 #define EPP_LAYOUT_SIZE 25
-#define OOXML_LAYOUT_SIZE 36
 
 struct PHLayout
 {
diff --git a/sd/source/filter/eppt/epptooxml.hxx 
b/sd/source/filter/eppt/epptooxml.hxx
index 6d157b7ce68e..2a8404089939 100644
--- a/sd/source/filter/eppt/epptooxml.hxx
+++ b/sd/source/filter/eppt/epptooxml.hxx
@@ -23,6 +23,7 @@
 #include <oox/vml/vmldrawing.hxx>
 #include <oox/export/shapes.hxx>
 #include <unotools/securityoptions.hxx>
+#include <xmloff/autolayout.hxx>
 #include "epptbase.hxx"
 
 using ::sax_fastparser::FSHelperPtr;
@@ -147,7 +148,7 @@ private:
 
     ::sax_fastparser::FSHelperPtr mPresentationFS;
 
-    LayoutInfo mLayoutInfo[OOXML_LAYOUT_SIZE];
+    LayoutInfo mLayoutInfo[AUTOLAYOUT_END];
     // 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 (SAL_MAX_UINT32 if not in an equivalency group)
diff --git a/sd/source/filter/eppt/pptx-epptooxml.cxx 
b/sd/source/filter/eppt/pptx-epptooxml.cxx
index 7982f0af77c8..0881ef496c77 100644
--- a/sd/source/filter/eppt/pptx-epptooxml.cxx
+++ b/sd/source/filter/eppt/pptx-epptooxml.cxx
@@ -26,6 +26,7 @@
 #include "epptooxml.hxx"
 #include <oox/export/shapes.hxx>
 #include <svx/svdlayer.hxx>
+#include <xmloff/autolayout.hxx>
 #include <unokywds.hxx>
 #include <osl/file.hxx>
 
@@ -234,20 +235,25 @@ struct PPTXLayoutInfo
 
 }
 
-const PPTXLayoutInfo aLayoutInfo[OOXML_LAYOUT_SIZE] =
+// the function 'SlidePersist::getLayoutFromValueToken()'
+// likely needs to be updated after any changes here
+// unsupported PPTX layouts: txAndMedia, twoColTx, twoTxTwoObj,
+//  twoObjAndObj, objTx, picTx, secHead, objOnly, objAndTwoObj,
+//  mediaAndTx, dgm, cust
+const PPTXLayoutInfo aLayoutInfo[] =
 {
     { 0, "Title Slide", "title" },
     { 1, "Title and text", "tx" },
     { 2, "Title and chart", "chart" },
     { 3, "Title, text on left, text on right", "twoObj" },
     { 4, "Title, text on left and chart on right", "txAndChart" },
+    { 20, "Blank Slide", "blank" },
     { 6, "Title, text on left, clip art on right", "txAndClipArt" },
-    { 6, "Title, text on left, media on right", "txAndMedia" },
     { 7, "Title, chart on left and text on right", "chartAndTx" },
     { 8, "Title and table", "tbl" },
     { 9, "Title, clipart on left, text on right", "clipArtAndTx" },
     { 10, "Title, text on left, object on right", "txAndObj" },
-    { 1, "Title and object", "obj" },
+    { 11, "Title and object", "obj" },
     { 12, "Title, text on left, two objects on right", "txAndTwoObj" },
     { 13, "Title, object on left, text on right", "objAndTx" },
     { 14, "Title, object on top, text on bottom", "objOverTx" },
@@ -257,22 +263,23 @@ const PPTXLayoutInfo aLayoutInfo[OOXML_LAYOUT_SIZE] =
     { 18, "Title and four objects", "fourObj" },
     { 19, "Title Only", "titleOnly" },
     { 20, "Blank Slide", "blank" },
-    { 21, "Vertical title on right, vertical text on top, chart on bottom", 
"vertTitleAndTxOverChart" },
-    { 22, "Vertical title on right, vertical text on left", "vertTitleAndTx" },
-    { 23, "Title and vertical text body", "vertTx" },
-    { 24, "Title, clip art on left, vertical text on right", 
"clipArtAndVertTx" },
-    { 20, "Title, two objects each with text", "twoTxTwoObj" },
-    { 15, "Title, two objects on left, one object on right", "twoObjAndObj" },
-    { 20, "Title, object and caption text", "objTx" },
-    { 20, "Title, picture, and caption text", "picTx" },
-    { 20, "Section header title and subtitle text", "secHead" },
+    { 20, "Blank Slide", "blank" },
+    { 20, "Blank Slide", "blank" },
+    { 20, "Blank Slide", "blank" },
+    { 20, "Blank Slide", "blank" },
+    { 20, "Blank Slide", "blank" },
+    { 20, "Blank Slide", "blank" },
+    { 27, "Vertical title on right, vertical text on top, chart on bottom", 
"vertTitleAndTxOverChart" },
+    { 28, "Vertical title on right, vertical text on left", "vertTitleAndTx" },
+    { 29, "Title and vertical text body", "vertTx" },
+    { 30, "Title, clip art on left, vertical text on right", 
"clipArtAndVertTx" },
+    { 20, "Blank Slide", "blank" },
     { 32, "Object only", "objOnly" },
-    { 12, "Title, one object on left, two objects on right", "objAndTwoObj" },
-    { 20, "Title, media on left, text on right", "mediaAndTx" },
-    { 34, "Title, 6 Content", "blank" }, // not defined in OOXML => blank
-    { 2, "Title and diagram", "dgm" },
-    { 0, "Custom layout defined by user", "cust" },
+    { 20, "Blank Slide", "blank" },
+    { 20, "Blank Slide", "blank" }
 };
+static_assert(std::size(aLayoutInfo) == AUTOLAYOUT_END,
+              "aLayoutInfo must have a corresponding item for each 
AUTOLAYOUT");
 
 PowerPointShapeExport::PowerPointShapeExport(FSHelperPtr pFS, ShapeHashMap* 
pShapeMap,
         PowerPointExport* pFB)
@@ -1991,7 +1998,7 @@ void PowerPointExport::ImplWriteSlideMaster(sal_uInt32 
nPageNum, Reference< XPro
         Reference<XNamed> xNamed(mXDrawPage, UNO_QUERY);
         if (xNamed.is())
             aSlideName = xNamed->getName();
-        for (int i = 0; i < OOXML_LAYOUT_SIZE; ++i)
+        for (int i = 0; i < AUTOLAYOUT_END; ++i)
         {
             if (mLayoutInfo[i].mnFileIdArray.size() > nPageNum
                 && mLayoutInfo[i].mnFileIdArray[nPageNum] > 0)

Reply via email to