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/+/192270
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
Tested-by: Jenkins
This commit is contained in:
Miklos Vajna
2025-10-13 14:24:42 +02:00
parent b0fb88e456
commit 5b8c4db773
4 changed files with 82 additions and 5 deletions
+1 -1
View File
@@ -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;
+23 -1
View File
@@ -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();
+48
View File
@@ -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
+10 -3
View File
@@ -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;
}