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.

Change-Id: Icdb3f08404ca4d212d25a1967bfdc0bfc7186007
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/105427
Tested-by: Jenkins
Reviewed-by: Regina Henschel <rb.henschel@t-online.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 0000000..bbc1af8
--- /dev/null
+++ b/sc/qa/unit/data/ods/tdf117948_CollapseBeforeShape.ods
Binary files differ
diff --git a/sc/qa/unit/scshapetest.cxx b/sc/qa/unit/scshapetest.cxx
index 49bad11..d2c8cc9 100644
--- a/sc/qa/unit/scshapetest.cxx
+++ b/sc/qa/unit/scshapetest.cxx
@@ -33,13 +33,14 @@ public:
    ScShapeTest();
    void saveAndReload(css::uno::Reference<css::lang::XComponent>& xComponent,
                       const OUString& rFilter);

    void testTdf117948_CollapseBeforeShape();
    void testTdf137355_UndoHideRows();
    void testTdf115655_HideDetail();
    void testFitToCellSize();
    void testCustomShapeCellAnchoredRotatedShape();

    CPPUNIT_TEST_SUITE(ScShapeTest);
    CPPUNIT_TEST(testTdf117948_CollapseBeforeShape);
    CPPUNIT_TEST(testTdf137355_UndoHideRows);
    CPPUNIT_TEST(testTdf115655_HideDetail);
    CPPUNIT_TEST(testFitToCellSize);
@@ -96,6 +97,83 @@ static void lcl_AssertRectEqualWithTolerance(const OString& sInfo,
                           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::testTdf137355_UndoHideRows()
{
    // The document contains a shape anchored "To Cell" with start in cell C3 and end in cell D6.
diff --git a/sc/source/filter/xml/xmlexprt.cxx b/sc/source/filter/xml/xmlexprt.cxx
index 9699361..a70ca93 100644
--- a/sc/source/filter/xml/xmlexprt.cxx
+++ b/sc/source/filter/xml/xmlexprt.cxx
@@ -117,6 +117,9 @@
#include <svx/svdobj.hxx>
#include <svx/svdocapt.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>
@@ -3472,23 +3475,45 @@ void ScXMLExport::WriteShapes(const ScMyCell& rMyCell)
    if( !(rMyCell.bHasShape && !rMyCell.aShapeList.empty() && pDoc) )
        return;

    awt::Point aPoint;
    // Hiding row or col does not change the shape geometry.
    tools::Rectangle aRect = pDoc->GetMMRect(rMyCell.maCellAddress.Col(), rMyCell.maCellAddress.Row(),
    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;
    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 &&
@@ -3505,7 +3530,15 @@ 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);
        }
    }
}