sc/qa/unit/data/ods/tdf117948_CollapseBeforeShape.ods |binary
 sc/qa/unit/scshapetest.cxx                            |  112 ++++++++++++++++++
 sc/source/filter/xml/xmlexprt.cxx                     |   47 ++++++-
 3 files changed, 150 insertions(+), 9 deletions(-)

New commits:
commit 393d061a0e34917b1029f399b86058bd94a16d51
Author:     Regina Henschel <rb.hensc...@t-online.de>
AuthorDate: Sat Nov 7 01:08:53 2020 +0100
Commit:     Thorsten Behrens <thorsten.behr...@allotropia.de>
CommitDate: Tue Mar 21 09:25:26 2023 +0000

    tdf#117948 Do not treat hidden rows as zero in ODF export
    
    The object geometry in ODF file format has values so as if no hidden
    columns or rows exists. But for rendering the object geometry has to
    treat hidden rows and columns as zero. This patch changes the object
    geometry temporarily to the 'no hidden columns and rows' mode for
    export and restores the original geometry afterwards.
    The patch considers hidden columns left from the shape and hidden rows
    above the shape. So the object is shifted. Considering hidden columns
    or rows in the area, which is covered by the shape, is still missing.
    That would possibly require scaling.
    
    cherry-pick from commit: 1cb6bb9576871ff5d56c9810ff1791abc3dd64fd
    (tdf#117948 Do not treat hidden rows as zero in ODF export)
    
    Change-Id: Icdb3f08404ca4d212d25a1967bfdc0bfc7186007
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/105427
    Tested-by: Jenkins
    Reviewed-by: Regina Henschel <rb.hensc...@t-online.de>
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/149163
    Tested-by: Gabor Kelemen <kelem...@ubuntu.com>
    Reviewed-by: Thorsten Behrens <thorsten.behr...@allotropia.de>

diff --git a/sc/qa/unit/data/ods/tdf117948_CollapseBeforeShape.ods 
b/sc/qa/unit/data/ods/tdf117948_CollapseBeforeShape.ods
new file mode 100644
index 000000000000..bbc1af87cf07
Binary files /dev/null and 
b/sc/qa/unit/data/ods/tdf117948_CollapseBeforeShape.ods differ
diff --git a/sc/qa/unit/scshapetest.cxx b/sc/qa/unit/scshapetest.cxx
index 55a2b0a8537c..f348854bb9e3 100644
--- a/sc/qa/unit/scshapetest.cxx
+++ b/sc/qa/unit/scshapetest.cxx
@@ -13,6 +13,7 @@
 #include <svx/svdoashp.hxx>
 #include <svx/svdpage.hxx>
 #include <svx/svdouno.hxx>
+#include <unotools/tempfile.hxx>
 
 #include <docsh.hxx>
 #include <drwlayer.hxx>
@@ -29,14 +30,18 @@ class ScShapeTest : public CalcUnoApiTest
 {
 public:
     ScShapeTest();
+    void saveAndReload(css::uno::Reference<css::lang::XComponent>& xComponent,
+                       const OUString& rFilter);
 
     virtual void tearDown() override;
 
+    void testTdf117948_CollapseBeforeShape();
     void testFitToCellSize();
     void testCustomShapeCellAnchoredRotatedShape();
     void testFormSizeWithHiddenCol();
 
     CPPUNIT_TEST_SUITE(ScShapeTest);
+    CPPUNIT_TEST(testTdf117948_CollapseBeforeShape);
     CPPUNIT_TEST(testFitToCellSize);
     CPPUNIT_TEST(testCustomShapeCellAnchoredRotatedShape);
     CPPUNIT_TEST(testFormSizeWithHiddenCol);
@@ -51,6 +56,21 @@ ScShapeTest::ScShapeTest()
 {
 }
 
+void ScShapeTest::saveAndReload(css::uno::Reference<css::lang::XComponent>& 
xComponent,
+                                const OUString& rFilter)
+{
+    utl::TempFile aTempFile;
+    aTempFile.EnableKillingFile();
+    css::uno::Sequence<css::beans::PropertyValue> aArgs(1);
+    aArgs[0].Name = "FilterName";
+    aArgs[0].Value <<= rFilter; // e.g. "calc8"
+    css::uno::Reference<css::frame::XStorable> xStorable(xComponent, 
css::uno::UNO_QUERY_THROW);
+    xStorable->storeAsURL(aTempFile.GetURL(), aArgs);
+    css::uno::Reference<css::util::XCloseable> xCloseable(xComponent, 
css::uno::UNO_QUERY_THROW);
+    xCloseable->close(true);
+    xComponent = loadFromDesktop(aTempFile.GetURL(), 
"com.sun.star.sheet.SpreadsheetDocument");
+}
+
 static OUString lcl_compareRectWithTolerance(const tools::Rectangle& rExpected,
                                              const tools::Rectangle& rActual,
                                              const sal_Int32 nTolerance)
@@ -74,6 +94,98 @@ static OUString lcl_compareRectWithTolerance(const 
tools::Rectangle& rExpected,
     return sErrors;
 }
 
+static void lcl_AssertRectEqualWithTolerance(const OString& sInfo,
+                                             const tools::Rectangle& rExpected,
+                                             const tools::Rectangle& rActual,
+                                             const sal_Int32 nTolerance)
+{
+    // Left
+    OString sMsg = sInfo + " Left expected " + 
OString::number(rExpected.Left()) + " actual "
+                   + OString::number(rActual.Left()) + " Tolerance " + 
OString::number(nTolerance);
+    CPPUNIT_ASSERT_MESSAGE(sMsg.getStr(), labs(rExpected.Left() - 
rActual.Left()) <= nTolerance);
+    // Top
+    sMsg = sInfo + " Top expected " + OString::number(rExpected.Top()) + " 
actual "
+           + OString::number(rActual.Top()) + " Tolerance " + 
OString::number(nTolerance);
+    CPPUNIT_ASSERT_MESSAGE(sMsg.getStr(), labs(rExpected.Top() - 
rActual.Top()) <= nTolerance);
+    // Width
+    sMsg = sInfo + " Width expected " + OString::number(rExpected.GetWidth()) 
+ " actual "
+           + OString::number(rActual.GetWidth()) + " Tolerance " + 
OString::number(nTolerance);
+    CPPUNIT_ASSERT_MESSAGE(sMsg.getStr(),
+                           labs(rExpected.GetWidth() - rActual.GetWidth()) <= 
nTolerance);
+    // Height
+    sMsg = sInfo + " Height expected " + 
OString::number(rExpected.GetHeight()) + " actual "
+           + OString::number(rActual.GetHeight()) + " Tolerance " + 
OString::number(nTolerance);
+    CPPUNIT_ASSERT_MESSAGE(sMsg.getStr(),
+                           labs(rExpected.GetHeight() - rActual.GetHeight()) 
<= nTolerance);
+}
+
+void ScShapeTest::testTdf117948_CollapseBeforeShape()
+{
+    // The document contains a column group left from the image. The group is 
exanded. Collapse the
+    // group, save and reload. The original error was, that the line was on 
wrong position after reload.
+    // After the fix for 'resive with cell', the custom shape had wrong 
position and size too.
+    OUString aFileURL;
+    createFileURL("tdf117948_CollapseBeforeShape.ods", aFileURL);
+    uno::Reference<css::lang::XComponent> xComponent = 
loadFromDesktop(aFileURL);
+    CPPUNIT_ASSERT(xComponent.is());
+    // Get ScDocShell
+    SfxObjectShell* pFoundShell = 
SfxObjectShell::GetShellFromComponent(xComponent);
+    CPPUNIT_ASSERT_MESSAGE("Failed to access document shell", pFoundShell);
+    ScDocShell* pDocSh = dynamic_cast<ScDocShell*>(pFoundShell);
+    CPPUNIT_ASSERT(pDocSh);
+    // Get document and objects
+    ScDocument& rDoc = pDocSh->GetDocument();
+    ScDrawLayer* pDrawLayer = rDoc.GetDrawLayer();
+    CPPUNIT_ASSERT_MESSAGE("Load: No ScDrawLayer", pDrawLayer);
+    const SdrPage* pPage = pDrawLayer->GetPage(0);
+    CPPUNIT_ASSERT_MESSAGE("Load: No draw page", pPage);
+    SdrObject* pObj0 = pPage->GetObj(0);
+    CPPUNIT_ASSERT_MESSAGE("Load: custom shape not found", pObj0);
+    SdrObject* pObj1 = pPage->GetObj(1);
+    CPPUNIT_ASSERT_MESSAGE("Load: Vertical line not found", pObj1);
+    // Collapse the group
+    ScTabViewShell* pViewShell = pDocSh->GetBestViewShell(false);
+    CPPUNIT_ASSERT_MESSAGE("Load: No ScTabViewShell", pViewShell);
+    pViewShell->GetViewData().SetCurX(1);
+    pViewShell->GetViewData().SetCurY(0);
+    pViewShell->GetViewData().GetDispatcher().Execute(SID_OUTLINE_HIDE);
+    // Check anchor and position of shape. The expected values are taken from 
UI before saving.
+    tools::Rectangle aSnapRect0Collapse = pObj0->GetSnapRect();
+    tools::Rectangle aExpectedRect0(Point(4672, 1334), Size(1787, 1723));
+    lcl_AssertRectEqualWithTolerance("Collapse: Custom shape", aExpectedRect0, 
aSnapRect0Collapse,
+                                     1);
+    tools::Rectangle aSnapRect1Collapse = pObj1->GetSnapRect();
+    tools::Rectangle aExpectedRect1(Point(5647, 4172), Size(21, 3441));
+    lcl_AssertRectEqualWithTolerance("Collape: Line", aExpectedRect1, 
aSnapRect1Collapse, 1);
+    // Save and reload
+    saveAndReload(xComponent, "calc8");
+    CPPUNIT_ASSERT(xComponent);
+    // Get ScDocShell
+    pFoundShell = SfxObjectShell::GetShellFromComponent(xComponent);
+    CPPUNIT_ASSERT_MESSAGE("Reload: Failed to access document shell", 
pFoundShell);
+    pDocSh = dynamic_cast<ScDocShell*>(pFoundShell);
+    CPPUNIT_ASSERT(pDocSh);
+    // Get document and objects
+    ScDocument& rDoc2 = pDocSh->GetDocument();
+    pDrawLayer = rDoc2.GetDrawLayer();
+    CPPUNIT_ASSERT_MESSAGE("Reload: No ScDrawLayer", pDrawLayer);
+    pPage = pDrawLayer->GetPage(0);
+    CPPUNIT_ASSERT_MESSAGE("Reload: No draw page", pPage);
+    pObj0 = pPage->GetObj(0);
+    CPPUNIT_ASSERT_MESSAGE("Reload: custom shape no longer exists", pObj0);
+    pObj1 = pPage->GetObj(1);
+    CPPUNIT_ASSERT_MESSAGE("Reload: custom shape no longer exists", pObj1);
+    // Assert objects size and position are not changed. Actual values differ 
a little bit
+    // because of cumulated Twips-Hmm conversion errors.
+    tools::Rectangle aSnapRect0Reload = pObj0->GetSnapRect();
+    lcl_AssertRectEqualWithTolerance("Reload: Custom shape geometry has 
changed.", aExpectedRect0,
+                                     aSnapRect0Reload, 2);
+    tools::Rectangle aSnapRect1Reload = pObj1->GetSnapRect();
+    lcl_AssertRectEqualWithTolerance("Reload: Line geometry has changed.", 
aExpectedRect1,
+                                     aSnapRect1Reload, 2);
+    pDocSh->DoClose();
+}
+
 void ScShapeTest::testFitToCellSize()
 {
     // The document has a cell anchored custom shape. Applying
diff --git a/sc/source/filter/xml/xmlexprt.cxx 
b/sc/source/filter/xml/xmlexprt.cxx
index 0c8206f9f39e..325be2471eb3 100644
--- a/sc/source/filter/xml/xmlexprt.cxx
+++ b/sc/source/filter/xml/xmlexprt.cxx
@@ -119,6 +119,9 @@
 #include <svx/svdocapt.hxx>
 #include <svtools/miscopt.hxx>
 #include <vcl/svapp.hxx>
+#include <svx/unoapi.hxx>
+#include <basegfx/polygon/b2dpolypolygon.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
 
 #include <comphelper/processfactory.hxx>
 #include <com/sun/star/beans/XPropertySet.hpp>
@@ -3473,23 +3476,42 @@ void ScXMLExport::WriteShapes(const ScMyCell& rMyCell)
 {
     if( rMyCell.bHasShape && !rMyCell.aShapeList.empty() && pDoc )
     {
+        tools::Rectangle aRectFull = pDoc->GetMMRect(
+            rMyCell.maCellAddress.Col(), rMyCell.maCellAddress.Row(), 
rMyCell.maCellAddress.Col(),
+            rMyCell.maCellAddress.Row(), rMyCell.maCellAddress.Tab(), false 
/*bHiddenAsZero*/);
+        tools::Rectangle aRectReduced = pDoc->GetMMRect(
+            rMyCell.maCellAddress.Col(), rMyCell.maCellAddress.Row(), 
rMyCell.maCellAddress.Col(),
+            rMyCell.maCellAddress.Row(), rMyCell.maCellAddress.Tab(), true 
/*bHiddenAsZero*/);
+        // Reference point
         awt::Point aPoint;
-        // Hiding row or col does not change the shape geometry.
-        tools::Rectangle aRect = pDoc->GetMMRect(rMyCell.maCellAddress.Col(), 
rMyCell.maCellAddress.Row(),
-            rMyCell.maCellAddress.Col(), rMyCell.maCellAddress.Row(), 
rMyCell.maCellAddress.Tab(),
-            false /*bHiddenAsZero*/);
         bool bNegativePage = pDoc->IsNegativePage(rMyCell.maCellAddress.Tab());
         if (bNegativePage)
-            aPoint.X = aRect.Right();
+            aPoint.X = aRectFull.Right();
         else
-            aPoint.X = aRect.Left();
-        aPoint.Y = aRect.Top();
+            aPoint.X = aRectFull.Left();
+        aPoint.Y = aRectFull.Top();
         for (const auto& rShape : rMyCell.aShapeList)
         {
             if (rShape.xShape.is())
             {
-                if (bNegativePage)
-                    aPoint.X = 2 * rShape.xShape->getPosition().X + 
rShape.xShape->getSize().Width - aPoint.X;
+                basegfx::B2DPolyPolygon aPolyPolygonOrig;
+                basegfx::B2DHomMatrix aMatrixOrig;
+                bool bNeedsRestore = false;
+                SdrObject* pObj = GetSdrObjectFromXShape(rShape.xShape);
+                if (pObj && aRectFull != aRectReduced)
+                {
+                    // There are hidden rows or columns above or before the 
start cell.
+                    // The current object geometry is based on 
bHiddenAsZero=true, but ODF file format
+                    // needs it as if there were no hidden rows or columns.
+                    // We shift the object and restore it later.
+                    bNeedsRestore = true;
+                    pObj->TRGetBaseGeometry(aMatrixOrig, aPolyPolygonOrig);
+                    basegfx::B2DHomMatrix aMatrixFull(aMatrixOrig);
+                    aMatrixFull.translate(aRectFull.Left() - 
aRectReduced.Left(),
+                                          aRectFull.Top() - 
aRectReduced.Top());
+                    pObj->TRSetBaseGeometry(aMatrixFull, aPolyPolygonOrig);
+                }
+                // ToDo: Adapt object shew and rotation to 
bHiddenAsZero=false, tdf#137033
 
                 // We only write the end address if we want the shape to 
resize with the cell
                 if ( rShape.bResizeWithCell &&
@@ -3506,7 +3528,14 @@ void ScXMLExport::WriteShapes(const ScMyCell& rMyCell)
                             sBuffer, rShape.nEndY);
                     AddAttribute(XML_NAMESPACE_TABLE, XML_END_Y, 
sBuffer.makeStringAndClear());
                 }
+                if (bNegativePage)
+                    aPoint.X = 2 * rShape.xShape->getPosition().X + 
rShape.xShape->getSize().Width
+                               - aPoint.X;
                 ExportShape(rShape.xShape, &aPoint);
+
+                // Restore object geometry
+                if (bNeedsRestore)
+                    pObj->TRSetBaseGeometry(aMatrixOrig, aPolyPolygonOrig);
             }
         }
     }

Reply via email to