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)
