editeng/source/editeng/EditLine.cxx                  |    8 +-
 oox/source/drawingml/textbodyproperties.cxx          |   20 +++++
 sd/qa/unit/data/pptx/tdf165732.pptx                  |binary
 sd/qa/unit/import-tests.cxx                          |   29 ++++++++
 svx/source/sdr/primitive2d/sdrdecompositiontools.cxx |   64 ++++++++++---------
 5 files changed, 90 insertions(+), 31 deletions(-)

New commits:
commit f54fc10ea8fb4c89412239be79bf8f4b40b525f4
Author:     Samuel Mehrbrodt <[email protected]>
AuthorDate: Thu Feb 19 14:28:28 2026 +0100
Commit:     Adolfo Jayme Barrientos <[email protected]>
CommitDate: Wed Feb 25 15:13:36 2026 +0100

    tdf#165732 PPTX: Fix misaligned text in small text boxes
    
    Make sure to clamp the inset (left+right) in 
TextBodyProperties::pushTextDistances()
    as it was already done for top+bottom.
    This fixes most alignment issues from the bug documents.
    
    Additionally, allow negative values in EditLine::SetStartPosX since the 
offset
    might be negative to have centered text.
    This fixes the alignment of number "1" in the fourcenter-left-right.pptx 
bug document
    which was still misaligned after the above mentioned fix.
    
    Also improve the fix made in commit c0693ef135f4654d69451fc9ca5a735931aebde2
    to clip the inset on the drawinglayer level on all four sides.
    This also helps to correctly display the example files when they are saved 
to odp format
    (in which case the bug would reappear).
    In contrast to pptx, odp does not specify what should happen in this case,
    so clipping on import does not seem appropriate here.
    
    Change-Id: I33e5be1123becef17f84427649d1f2cdbfe2d7e7
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/199709
    Reviewed-by: Samuel Mehrbrodt <[email protected]>
    Tested-by: Jenkins
    (cherry picked from commit 9822795072ca855dcebc518dcb211d2389ba49f9)
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/199785
    Reviewed-by: Adolfo Jayme Barrientos <[email protected]>

diff --git a/editeng/source/editeng/EditLine.cxx 
b/editeng/source/editeng/EditLine.cxx
index 7dbd121e26e4..0b8de5b5b17a 100644
--- a/editeng/source/editeng/EditLine.cxx
+++ b/editeng/source/editeng/EditLine.cxx
@@ -88,10 +88,10 @@ void EditLine::SetHeight(sal_uInt16 nHeight, sal_uInt16 
nTextHeight)
 
 void EditLine::SetStartPosX(sal_Int32 nStart)
 {
-    if (nStart > 0)
-        mnStartPosX = nStart;
-    else
-        mnStartPosX = 0;
+    // tdf#165732 When centered text overflows its container (text width > 
paper width),
+    // the centering offset (paperWidth - textWidth) / 2 is negative.
+    // So we must not clamp the start pos here.
+    mnStartPosX = nStart;
 }
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/oox/source/drawingml/textbodyproperties.cxx 
b/oox/source/drawingml/textbodyproperties.cxx
index 25a1f0ced330..2d88147052f3 100644
--- a/oox/source/drawingml/textbodyproperties.cxx
+++ b/oox/source/drawingml/textbodyproperties.cxx
@@ -128,6 +128,26 @@ void TextBodyProperties::pushTextDistances(Size const& 
rTextAreaSize)
         nOff = (nOff + 1) % aProps.size();
     }
 
+    // Check if left and right are set
+    if (maTextDistanceValues[0] && maTextDistanceValues[2])
+    {
+        double nWidth = rTextAreaSize.getWidth();
+
+        double nLeft = *maTextDistanceValues[0];
+        double nRight = *maTextDistanceValues[2];
+
+        // Check if left + right is more than text area width.
+        // If yes, we need to adjust the values as defined in OOXML.
+        // (Overload zero width to mean don't adjust)
+        if (nWidth > 0 && nLeft + nRight >= nWidth)
+        {
+            double diffFactor = (nLeft + nRight - nWidth) / 2.0;
+
+            maTextDistanceValues[0] = nLeft - diffFactor;
+            maTextDistanceValues[2] = nRight - diffFactor;
+        }
+    }
+
     // Check if bottom and top are set
     if (maTextDistanceValues[1] && maTextDistanceValues[3])
     {
diff --git a/sd/qa/unit/data/pptx/tdf165732.pptx 
b/sd/qa/unit/data/pptx/tdf165732.pptx
new file mode 100644
index 000000000000..05598e30bfb1
Binary files /dev/null and b/sd/qa/unit/data/pptx/tdf165732.pptx differ
diff --git a/sd/qa/unit/import-tests.cxx b/sd/qa/unit/import-tests.cxx
index f47bdc1b730c..bafca667088e 100644
--- a/sd/qa/unit/import-tests.cxx
+++ b/sd/qa/unit/import-tests.cxx
@@ -1969,6 +1969,35 @@ CPPUNIT_TEST_FIXTURE(SdImportTest, testTdf93830)
     CPPUNIT_ASSERT_EQUAL(sal_Int32(4024), nTextLeftDistance);
 }
 
+CPPUNIT_TEST_FIXTURE(SdImportTest, testTdf165732)
+{
+    // Text insets larger than shape dimension should be clamped symmetrically.
+    createSdImpressDoc("pptx/tdf165732.pptx");
+    uno::Reference<drawing::XDrawPage> xPage(getPage(0));
+
+    // Shape "2": each side reduced by 1: 200 -> 199.
+    uno::Reference<beans::XPropertySet> xShape0(xPage->getByIndex(0), 
uno::UNO_QUERY);
+    sal_Int32 nLeft = 0, nRight = 0;
+    xShape0->getPropertyValue(u"TextLeftDistance"_ustr) >>= nLeft;
+    xShape0->getPropertyValue(u"TextRightDistance"_ustr) >>= nRight;
+    CPPUNIT_ASSERT_EQUAL(sal_Int32(199), nLeft);
+    CPPUNIT_ASSERT_EQUAL(sal_Int32(199), nRight);
+
+    // Shape "1": no clamping.
+    uno::Reference<beans::XPropertySet> xShape1(xPage->getByIndex(1), 
uno::UNO_QUERY);
+    sal_Int32 nLeft1 = 0;
+    xShape1->getPropertyValue(u"TextLeftDistance"_ustr) >>= nLeft1;
+    CPPUNIT_ASSERT_EQUAL(sal_Int32(100), nLeft1);
+
+    // Shape "5": top and bottom reduced by 17.5: 200 -> 183 (rounded).
+    uno::Reference<beans::XPropertySet> xShape7(xPage->getByIndex(7), 
uno::UNO_QUERY);
+    sal_Int32 nUpper = 0, nLower = 0;
+    xShape7->getPropertyValue(u"TextUpperDistance"_ustr) >>= nUpper;
+    xShape7->getPropertyValue(u"TextLowerDistance"_ustr) >>= nLower;
+    CPPUNIT_ASSERT_EQUAL(sal_Int32(183), nUpper);
+    CPPUNIT_ASSERT_EQUAL(sal_Int32(183), nLower);
+}
+
 CPPUNIT_TEST_FIXTURE(SdImportTest, testTdf127129)
 {
     createSdImpressDoc("pptx/tdf127129.pptx");
diff --git a/svx/source/sdr/primitive2d/sdrdecompositiontools.cxx 
b/svx/source/sdr/primitive2d/sdrdecompositiontools.cxx
index b0237633303f..1317a8f138c9 100644
--- a/svx/source/sdr/primitive2d/sdrdecompositiontools.cxx
+++ b/svx/source/sdr/primitive2d/sdrdecompositiontools.cxx
@@ -108,34 +108,49 @@ basegfx::B2DRange getTextAnchorRange(const 
attribute::SdrTextAttribute& rText,
     const OutlinerParaObject& rOutlinerParaObj = rText.getOutlinerParaObject();
     const bool bVerticalWriting(rOutlinerParaObj.IsEffectivelyVertical());
     const double fWidthForText = bVerticalWriting ? rSnapRange.getHeight() : 
rSnapRange.getWidth();
+    const double fHeightForText = bVerticalWriting ? rSnapRange.getWidth() : 
rSnapRange.getHeight();
     // create a range describing the wanted text position and size 
(aTextAnchorRange). This
     // means to use the text distance values here
-    // If the margin is larger than the entire width of the text area, then 
limit the
-    // margin.
-    const double fTextLeftDistance
-        = std::min(static_cast<double>(rText.getTextLeftDistance()), 
fWidthForText);
-    const double nTextRightDistance
-        = std::min(static_cast<double>(rText.getTextRightDistance()), 
fWidthForText);
+    // If left + right margins exceed the available width, reduce each by half 
the excess.
+    double fTextLeftDistance = 
static_cast<double>(rText.getTextLeftDistance());
+    double fTextRightDistance = 
static_cast<double>(rText.getTextRightDistance());
+    if (fWidthForText > 0 && fTextLeftDistance + fTextRightDistance >= 
fWidthForText)
+    {
+        const double diffFactor = (fTextLeftDistance + fTextRightDistance - 
fWidthForText) / 2.0;
+        fTextLeftDistance -= diffFactor;
+        fTextRightDistance -= diffFactor;
+    }
+    // If top + bottom margins exceed the available height, reduce each by 
half the excess.
+    // Guard on fWidthForText > 0 for consistency with the horizontal check: 
skip for
+    // degenerate zero-width shapes where the zero-width expansion handles 
layout instead.
+    double fTextUpperDistance = 
static_cast<double>(rText.getTextUpperDistance());
+    double fTextLowerDistance = 
static_cast<double>(rText.getTextLowerDistance());
+    if (fWidthForText > 0 && fHeightForText > 0 && fTextUpperDistance + 
fTextLowerDistance >= fHeightForText)
+    {
+        const double diffFactor = (fTextUpperDistance + fTextLowerDistance - 
fHeightForText) / 2.0;
+        fTextUpperDistance -= diffFactor;
+        fTextLowerDistance -= diffFactor;
+    }
     double fDistanceForTextL, fDistanceForTextT, fDistanceForTextR, 
fDistanceForTextB;
     if (!bVerticalWriting)
     {
         fDistanceForTextL = fTextLeftDistance;
-        fDistanceForTextT = rText.getTextUpperDistance();
-        fDistanceForTextR = nTextRightDistance;
-        fDistanceForTextB = rText.getTextLowerDistance();
+        fDistanceForTextT = fTextUpperDistance;
+        fDistanceForTextR = fTextRightDistance;
+        fDistanceForTextB = fTextLowerDistance;
     }
     else if (rOutlinerParaObj.IsTopToBottom())
     {
-        fDistanceForTextL = rText.getTextLowerDistance();
+        fDistanceForTextL = fTextLowerDistance;
         fDistanceForTextT = fTextLeftDistance;
-        fDistanceForTextR = rText.getTextUpperDistance();
-        fDistanceForTextB = nTextRightDistance;
+        fDistanceForTextR = fTextUpperDistance;
+        fDistanceForTextB = fTextRightDistance;
     }
     else
     {
-        fDistanceForTextL = rText.getTextUpperDistance();
-        fDistanceForTextT = nTextRightDistance;
-        fDistanceForTextR = rText.getTextLowerDistance();
+        fDistanceForTextL = fTextUpperDistance;
+        fDistanceForTextT = fTextRightDistance;
+        fDistanceForTextR = fTextLowerDistance;
         fDistanceForTextB = fTextLeftDistance;
     }
     const basegfx::B2DPoint aTopLeft(rSnapRange.getMinX() + fDistanceForTextL,
@@ -143,27 +158,22 @@ basegfx::B2DRange getTextAnchorRange(const 
attribute::SdrTextAttribute& rText,
     const basegfx::B2DPoint aBottomRight(rSnapRange.getMaxX() - 
fDistanceForTextR,
                                          rSnapRange.getMaxY() - 
fDistanceForTextB);
     basegfx::B2DRange aAnchorRange;
-
-    // tdf#165732 Margins are too large in some cases (left > right or top > 
bottom)
-    // - in this case do not expand the range
-    bool bInvalidMargins
-        = (aTopLeft.getX() >= aBottomRight.getX() || aTopLeft.getY() >= 
aBottomRight.getY());
-    if (!bInvalidMargins)
-    {
-        aAnchorRange.expand(aTopLeft);
-        aAnchorRange.expand(aBottomRight);
-    }
+    aAnchorRange.expand(aTopLeft);
+    aAnchorRange.expand(aBottomRight);
 
     // If the shape has no width, then don't attempt to break the text into 
multiple
     // lines, not a single character would satisfy a zero width requirement.
     // SdrTextObj::impDecomposeBlockTextPrimitive() uses the same constant to
     // effectively set no limits.
-    if (!bVerticalWriting && aAnchorRange.getWidth() == 0)
+    // Also check fWidthForText == 0: a zero-width shape whose margins invert 
the
+    // anchor range gets a non-zero anchor width after B2DRange normalization, 
so
+    // the getWidth() == 0 check would not trigger without this second 
condition.
+    if (!bVerticalWriting && (fWidthForText == 0 || aAnchorRange.getWidth() == 
0))
     {
         aAnchorRange.expand(basegfx::B2DPoint(aTopLeft.getX() - 1000000, 
aTopLeft.getY()));
         aAnchorRange.expand(basegfx::B2DPoint(aBottomRight.getX() + 1000000, 
aBottomRight.getY()));
     }
-    else if (bVerticalWriting && aAnchorRange.getHeight() == 0)
+    else if (bVerticalWriting && (fWidthForText == 0 || 
aAnchorRange.getHeight() == 0))
     {
         aAnchorRange.expand(basegfx::B2DPoint(aTopLeft.getX(), aTopLeft.getY() 
- 1000000));
         aAnchorRange.expand(basegfx::B2DPoint(aBottomRight.getX(), 
aBottomRight.getY() + 1000000));

Reply via email to