diff --git a/include/oox/export/drawingml.hxx b/include/oox/export/drawingml.hxx index 356f4ef5cc22..d68e0e804fbe 100644 --- a/include/oox/export/drawingml.hxx +++ b/include/oox/export/drawingml.hxx @@ -458,6 +458,11 @@ public: sal_Int32 nXmlNamespace, bool bIsFontworkShape, sal_Int32 nTop, sal_Int32 nBottom, sal_Int32 nLeft, sal_Int32 nRight); + /// Writes one list level inside the list styles container. + void WriteLstStyle(const css::uno::Reference& rParagraph, + bool& rbOverridingCharHeight, sal_Int32& rnCharHeight, + const css::uno::Reference& rXShapePropSet, + sal_Int32 nElement); /** Populates the lstStyle with the shape's text run and paragraph properties */ void WriteLstStyles(const css::uno::Reference& rParagraph, bool& rbOverridingCharHeight, sal_Int32& rnCharHeight, diff --git a/oox/qa/unit/data/outliner-list-style.odp b/oox/qa/unit/data/outliner-list-style.odp new file mode 100644 index 000000000000..dc86caf053bf Binary files /dev/null and b/oox/qa/unit/data/outliner-list-style.odp differ diff --git a/oox/qa/unit/export.cxx b/oox/qa/unit/export.cxx index 63351ae79e4b..30273cd2acf2 100644 --- a/oox/qa/unit/export.cxx +++ b/oox/qa/unit/export.cxx @@ -1432,6 +1432,31 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf163803_ImageFill) assertXPath(pXmlDoc, "//p:pic/p:spPr/a:solidFill"); assertXPath(pXmlDoc, "//p:pic/p:spPr/a:solidFill/a:srgbClr", "val", u"000000"); } + +CPPUNIT_TEST_FIXTURE(Test, testPPTXExportOutlinerListStyle) +{ + // Given a slide with an outliner shape and a matching master slide with its own outliner shape: + loadFromFile(u"outliner-list-style.odp"); + + // When saving to PPTX: + save(u"Impress Office Open XML"_ustr); + + // Then make sure that the list style of the outliner shape on the master page is written: + xmlDocUniquePtr pXmlDoc = parseExport(u"ppt/slideMasters/slideMaster1.xml"_ustr); + assertXPath(pXmlDoc, "//p:sp[2]/p:txBody/a:lstStyle/a:lvl1pPr", 1); + // Without the accompanying fix in place, this test would have failed with: + // - Expected: 1 + // - Actual : 0 + // - XPath '//p:sp[2]/p:txBody/a:lstStyle/a:lvl2pPr' number of nodes is incorrect + // i.e. only the first list level was written, the UI couldn't format a new 2nd level paragraph + // correctly. + assertXPath(pXmlDoc, "//p:sp[2]/p:txBody/a:lstStyle/a:lvl2pPr", 1); + assertXPath(pXmlDoc, "//p:sp[2]/p:txBody/a:lstStyle/a:lvl3pPr", 1); + assertXPath(pXmlDoc, "//p:sp[2]/p:txBody/a:lstStyle/a:lvl4pPr", 1); + assertXPath(pXmlDoc, "//p:sp[2]/p:txBody/a:lstStyle/a:lvl5pPr", 1); + assertXPath(pXmlDoc, "//p:sp[2]/p:txBody/a:lstStyle/a:lvl6pPr", 1); + assertXPath(pXmlDoc, "//p:sp[2]/p:txBody/a:lstStyle/a:lvl7pPr", 1); +} } CPPUNIT_PLUGIN_IMPLEMENT(); diff --git a/oox/source/export/drawingml.cxx b/oox/source/export/drawingml.cxx index 4bb458bd0111..0eef9173abbb 100644 --- a/oox/source/export/drawingml.cxx +++ b/oox/source/export/drawingml.cxx @@ -138,6 +138,7 @@ #include #include #include +#include using namespace ::css; using namespace ::css::beans; @@ -3649,15 +3650,31 @@ bool DrawingML::WriteParagraphProperties(const Reference& rParagra WriteParagraphTabStops( rXPropSet ); // do not end element for lstStyles since, defRPr should be stacked inside it - if( nElement != XML_lvl1pPr ) + bool bLstStyle = false; + switch (nElement) + { + case XML_lvl1pPr: + case XML_lvl2pPr: + case XML_lvl3pPr: + case XML_lvl4pPr: + case XML_lvl5pPr: + case XML_lvl6pPr: + case XML_lvl7pPr: + case XML_lvl8pPr: + case XML_lvl9pPr: + bLstStyle = true; + break; + } + if( !bLstStyle ) mpFS->endElementNS( XML_a, nElement ); return true; } -void DrawingML::WriteLstStyles(const css::uno::Reference& rParagraph, +void DrawingML::WriteLstStyle(const css::uno::Reference& rParagraph, bool& rbOverridingCharHeight, sal_Int32& rnCharHeight, - const css::uno::Reference& rXShapePropSet) + const css::uno::Reference& rXShapePropSet, + sal_Int32 nElement) { Reference xAccess(rParagraph, UNO_QUERY); if (!xAccess.is()) @@ -3684,16 +3701,112 @@ void DrawingML::WriteLstStyles(const css::uno::ReferencehasPropertyByName(u"CharHeight"_ustr)) fFirstCharHeight = xFirstRunPropSet->getPropertyValue(u"CharHeight"_ustr).get(); - mpFS->startElementNS(XML_a, XML_lstStyle); - if( !WriteParagraphProperties(rParagraph, fFirstCharHeight, XML_lvl1pPr) ) - mpFS->startElementNS(XML_a, XML_lvl1pPr); + if( !WriteParagraphProperties(rParagraph, fFirstCharHeight, nElement) ) + mpFS->startElementNS(XML_a, nElement); WriteRunProperties(xFirstRunPropSet, false, XML_defRPr, true, rbOverridingCharHeight, rnCharHeight, GetScriptType(rRun->getString()), rXShapePropSet); - mpFS->endElementNS(XML_a, XML_lvl1pPr); - mpFS->endElementNS(XML_a, XML_lstStyle); + mpFS->endElementNS(XML_a, nElement); } } +namespace +{ +/// In case xShapeProps is an outliner shape, return a paragraph enumeration describing the outline +/// text format. +uno::Reference GetOutlinerTextFormatParaEnum(const uno::Reference& xShapeProps) +{ + SdrObject* pShape = SdrObject::getSdrObjectFromXShape(xShapeProps); + if (pShape->GetObjIdentifier() != SdrObjKind::OutlineText) + { + // Not an outliner shape. + return {}; + } + + SdrPage* pPage = pShape->getSdrPageFromSdrObject(); + if (pPage->TRG_HasMasterPage()) + { + // Not a master page, the matching master page contains the shape that provides the outline + // text format. + SdrPage& rMasterPage = pPage->TRG_GetMasterPage(); + for (const rtl::Reference& pObject : rMasterPage) + { + if (pObject->GetObjIdentifier() == SdrObjKind::OutlineText) + { + pShape = pObject.get(); + break; + } + } + } + + uno::Reference xOutliner(pShape->getUnoShape(), uno::UNO_QUERY); + uno::Reference xText(xOutliner->getText(), uno::UNO_QUERY); + return xText->createEnumeration(); +} +} + +void DrawingML::WriteLstStyles(const css::uno::Reference& rParagraph, + bool& rbOverridingCharHeight, sal_Int32& rnCharHeight, + const css::uno::Reference& rXShapePropSet) +{ + mpFS->startElementNS(XML_a, XML_lstStyle); + + uno::Reference xMasterParagraphEnum = GetOutlinerTextFormatParaEnum(rXShapePropSet); + if (xMasterParagraphEnum.is()) + { + // Outliner shape, write the outline text format as multiple list levels, each with its + // paragraph properties. + sal_Int32 nLevel = 0; + while (xMasterParagraphEnum->hasMoreElements()) + { + uno::Reference xParagraph(xMasterParagraphEnum->nextElement(), uno::UNO_QUERY); + sal_Int32 nElement = 0; + switch (nLevel) + { + case 0: + nElement = XML_lvl1pPr; + break; + case 1: + nElement = XML_lvl2pPr; + break; + case 2: + nElement = XML_lvl3pPr; + break; + case 3: + nElement = XML_lvl4pPr; + break; + case 4: + nElement = XML_lvl5pPr; + break; + case 5: + nElement = XML_lvl6pPr; + break; + case 6: + nElement = XML_lvl7pPr; + break; + case 7: + nElement = XML_lvl8pPr; + break; + case 8: + nElement = XML_lvl9pPr; + break; + } + if (!nElement) + { + break; + } + + WriteLstStyle(xParagraph, rbOverridingCharHeight, rnCharHeight, rXShapePropSet, nElement); + ++nLevel; + } + } + else + { + WriteLstStyle(rParagraph, rbOverridingCharHeight, rnCharHeight, rXShapePropSet, XML_lvl1pPr); + } + + mpFS->endElementNS(XML_a, XML_lstStyle); +} + void DrawingML::WriteParagraph( const Reference< XTextContent >& rParagraph, bool& rbOverridingCharHeight, sal_Int32& rnCharHeight, const css::uno::Reference< css::beans::XPropertySet >& rXShapePropSet)