oox/inc/drawingml/textliststyle.hxx    |    2 -
 oox/qa/unit/drawingml.cxx              |   24 +++++++++++++++-
 oox/source/drawingml/shape.cxx         |   48 +++++++++++++++++++++++++++++++++
 oox/source/drawingml/textliststyle.cxx |   13 ++++++--
 4 files changed, 82 insertions(+), 5 deletions(-)

New commits:
commit b2c5d52f733cca2656daa0a2cfdd85a1108635f4
Author:     Miklos Vajna <[email protected]>
AuthorDate: Mon Oct 13 09:02:10 2025 +0200
Commit:     Caolán McNamara <[email protected]>
CommitDate: Mon Oct 13 09:54:56 2025 +0200

    tdf#168559 PPTX imp: fix missing list style for outline shapes on master 
pages
    
    Load oox/qa/unit/data/outliner-list-style.odp into Impress, save as
    PPTX, load it the PPTX, save the PPTX again (source is now PPTX), open
    it again, go to the end of the first bullet, enter, tab, indent grows to
    a large value.
    
    What happens is that the PPTX export could write the correct list style
    for the shape (based on the master page) when the source was ODP, but it
    can't do the same when the source was PPTX, so after all this a PPTX
    import problem.
    
    Fix this by improving the formatting of the outliner shape on master
    pages: if the outliner shape was empty, then we don't insert formatted
    text body, so instead try to format the existing content of the outliner
    shape, given that such shapes have pre-existing content on the master
    page.
    
    Notes:
    - Do this only for outliner shapes, CppunitTest_sd_import_tests2's
      testTdf103792 is a sample that shows doing this for all placeholders
      on the master page is not correct.
    - The first line offset is typically negative, but do guard for positive
      values, otherwise the exported ODP would be invalid, see
      CppunitTest_sd_export_tests's testTdf128550.
    - It's possible to have outliner shapes with custom prompt text, which
      is typically not bulleted, so leave those alone, otherwise we would
      lose that prompt text, as visible in
      CppunitTest_sd_export_tests-ooxml4's testCustomPromptTexts.
    
    Change-Id: I5b1768a4a4b9cb8494785a38e040dacdf4ab5289
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/192264
    Tested-by: Jenkins CollaboraOffice <[email protected]>
    Reviewed-by: Caolán McNamara <[email protected]>

diff --git a/oox/inc/drawingml/textliststyle.hxx 
b/oox/inc/drawingml/textliststyle.hxx
index 88de4621de13..282391452456 100644
--- a/oox/inc/drawingml/textliststyle.hxx
+++ b/oox/inc/drawingml/textliststyle.hxx
@@ -61,7 +61,7 @@ public:
 
     /// Set properties on xNumRules based on maListStyle, for all levels 
except nIgnoreLevel.
     void pushToNumberingRules(const 
css::uno::Reference<css::container::XIndexReplace>& xNumRules,
-                              size_t nIgnoreLevel);
+                              std::optional<size_t> nIgnoreLevel);
 
 #ifdef DBG_UTIL
     void dump() const;
diff --git a/oox/qa/unit/drawingml.cxx b/oox/qa/unit/drawingml.cxx
index a442ef1f28ef..e5fc762aa12d 100644
--- a/oox/qa/unit/drawingml.cxx
+++ b/oox/qa/unit/drawingml.cxx
@@ -31,6 +31,7 @@
 #include <com/sun/star/util/XTheme.hpp>
 #include <com/sun/star/drawing/HomogenMatrix3.hpp>
 #include <com/sun/star/drawing/PointSequenceSequence.hpp>
+#include <com/sun/star/drawing/XMasterPagesSupplier.hpp>
 
 #include <docmodel/uno/UnoGradientTools.hxx>
 #include <docmodel/uno/UnoComplexColor.hxx>
@@ -786,7 +787,7 @@ CPPUNIT_TEST_FIXTURE(OoxDrawingmlTest, 
testDOCXVerticalLineRotation)
     CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(0), nRotateAngle);
 }
 
-CPPUNIT_TEST_FIXTURE(OoxDrawingmlTest, 
testPPTXImportOutlinerListStyleDirectFormat)
+CPPUNIT_TEST_FIXTURE(OoxDrawingmlTest, testPPTXImportOutlinerListStyle)
 {
     // Given a PPTX file with a slide with an outline shape:
     // When loading that document:
@@ -818,6 +819,27 @@ CPPUNIT_TEST_FIXTURE(OoxDrawingmlTest, 
testPPTXImportOutlinerListStyleDirectForm
     // - Actual  : -800
     CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(-889), nFirstLineOffset);
     // i.e. the sum of these two were 2800, not 1499, the indent was larger 
than expected.
+
+    // Also check that the master slide's outliner has the correct numbering 
rules:
+    uno::Reference<drawing::XMasterPagesSupplier> 
xMasterPagesSupplier(mxComponent, uno::UNO_QUERY);
+    uno::Reference<drawing::XDrawPage> xMasterPage(
+        xMasterPagesSupplier->getMasterPages()->getByIndex(0), uno::UNO_QUERY);
+    // First would be the title shape.
+    uno::Reference<text::XTextRange> xMasterShape(xMasterPage->getByIndex(1), 
uno::UNO_QUERY);
+    xShapeText.set(xMasterShape->getText(), uno::UNO_QUERY);
+    xParagraphs = xShapeText->createEnumeration();
+    // As a sample, check the 2nd paragraph's level 3 left margin.
+    xParagraphs->nextElement();
+    xParagraph.set(xParagraphs->nextElement(), uno::UNO_QUERY);
+    xParagraph->getPropertyValue(u"NumberingRules"_ustr) >>= xNumberingRules;
+    aMap = comphelper::SequenceAsHashMap(xNumberingRules->getByIndex(2));
+    nLeftMargin = 0;
+    aMap["LeftMargin"] >>= nLeftMargin;
+    // Without the accompanying fix in place, this test would have failed with:
+    // - Expected: 2388
+    // - Actual  : 3600
+    // i.e. the master was wrong, re-export to PPTX could not write the 
correct numbering rules.
+    CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(2388), nLeftMargin);
 }
 
 CPPUNIT_PLUGIN_IMPLEMENT();
diff --git a/oox/source/drawingml/shape.cxx b/oox/source/drawingml/shape.cxx
index 897a7ac9fc51..8967f616c958 100644
--- a/oox/source/drawingml/shape.cxx
+++ b/oox/source/drawingml/shape.cxx
@@ -71,6 +71,7 @@
 #include <com/sun/star/awt/FontWeight.hpp>
 #include <com/sun/star/graphic/XGraphic.hpp>
 #include <com/sun/star/container/XNamed.hpp>
+#include <com/sun/star/container/XEnumerationAccess.hpp>
 #include <com/sun/star/lang/XMultiServiceFactory.hpp>
 #include <com/sun/star/xml/dom/XDocument.hpp>
 #include <com/sun/star/xml/sax/XFastSAXSerializable.hpp>
@@ -914,6 +915,45 @@ Degree100 lcl_MSORotateAngleToAPIAngle(const sal_Int32 
nMSORotationAngle)
     // API RotateAngle has opposite direction than nMSORotationAngle, thus 
'minus'.
     return NormAngle36000(-nAngle);
 }
+
+void PushMasterTextListStyleToMasterShapeParagraphs(TextListStyle& 
rMasterTextListStyle, const uno::Reference<drawing::XShape>& xShape)
+{
+    uno::Reference<text::XTextRange> xShapeText(xShape, UNO_QUERY);
+    if (!xShapeText.is())
+    {
+        return;
+    }
+
+    uno::Reference<container::XEnumerationAccess> xText(xShapeText->getText(), 
uno::UNO_QUERY);
+    if (!xText.is())
+    {
+        return;
+    }
+
+    uno::Reference<container::XEnumeration> xParagraphs = 
xText->createEnumeration();
+    if (!xParagraphs.is())
+    {
+        return;
+    }
+
+    while (xParagraphs->hasMoreElements())
+    {
+        uno::Reference<beans::XPropertySet> 
xParagraph(xParagraphs->nextElement(), uno::UNO_QUERY);
+        if (!xParagraph.is())
+        {
+            continue;
+        }
+
+        uno::Reference<container::XIndexReplace> xNumberingRules;
+        if (!(xParagraph->getPropertyValue("NumberingRules") >>= 
xNumberingRules))
+        {
+            continue;
+        }
+
+        rMasterTextListStyle.pushToNumberingRules(xNumberingRules, 
std::nullopt);
+        xParagraph->setPropertyValue("NumberingRules", 
uno::Any(xNumberingRules));
+    }
+}
 }
 
 Reference< XShape > const & Shape::createAndInsert(
@@ -1286,6 +1326,7 @@ Reference< XShape > const & Shape::createAndInsert(
     }
 
     Reference< XPropertySet > xSet( mxShape, UNO_QUERY );
+    bool bPlaceholderWithCustomPrompt = pPlaceholder && 
pPlaceholder->getCustomPrompt();
     if (xSet.is())
     {
         if (bTopWriterLine && !maSize.Width)
@@ -2209,6 +2250,13 @@ Reference< XShape > const & Shape::createAndInsert(
                 }
             }
         }
+        else if (mpTextBody && aServiceName == 
"com.sun.star.presentation.OutlinerShape"
+                 && mpMasterTextListStyle && !bPlaceholderWithCustomPrompt)
+        {
+            // If mpTextBody is not empty, then the insertAt() above inserts 
formatted text. If it's
+            // empty, then we format the existing text of the outliner shape 
here.
+            
PushMasterTextListStyleToMasterShapeParagraphs(*mpMasterTextListStyle, mxShape);
+        }
         else if (mbTextBox)
         {
             // No drawingML text, but WPS text is expected: save the theme
diff --git a/oox/source/drawingml/textliststyle.cxx 
b/oox/source/drawingml/textliststyle.cxx
index ee695908d48a..0ad0b5108b27 100644
--- a/oox/source/drawingml/textliststyle.cxx
+++ b/oox/source/drawingml/textliststyle.cxx
@@ -70,7 +70,7 @@ void TextListStyle::apply( const TextListStyle& 
rTextListStyle )
 }
 
 void TextListStyle::pushToNumberingRules(const 
uno::Reference<container::XIndexReplace>& xNumRules,
-                                         size_t nIgnoreLevel)
+                                         std::optional<size_t> oIgnoreLevel)
 {
     TextParagraphPropertiesArray& rListStyle = getListStyle();
     size_t nLevels = xNumRules->getCount();
@@ -81,7 +81,7 @@ void TextListStyle::pushToNumberingRules(const 
uno::Reference<container::XIndexR
 
     for (size_t nLevel = 0; nLevel < nLevels; ++nLevel)
     {
-        if (nLevel == nIgnoreLevel)
+        if (oIgnoreLevel && nLevel == *oIgnoreLevel)
         {
             continue;
         }
@@ -96,7 +96,14 @@ void TextListStyle::pushToNumberingRules(const 
uno::Reference<container::XIndexR
         }
         if (rLevel.getFirstLineIndentation())
         {
-            aMap["FirstLineOffset"] <<= *rLevel.getFirstLineIndentation();
+            sal_Int32 nFirstLineIndentation = 
*rLevel.getFirstLineIndentation();
+            if (nFirstLineIndentation > 0)
+            {
+                // The opposite of this would be exported as 
<style:list-level-properties
+                // text:min-label-width="..."> to ODF, where negative values 
are not allowed.
+                nFirstLineIndentation = 0;
+            }
+            aMap["FirstLineOffset"] <<= nFirstLineIndentation;
             bChanged = true;
         }
 

Reply via email to