tdf#116931 Fix scale when calc cells move indirectly

For the anchor mode 'To Cell (resize with cell)' there
were errors e.g. when the cell the object is in does
not scale but move. If that move went further than the
object is, wrong offsets emerged.
Also added full linear per-cell scale instead of only
scale (no translation through scale). Overhauled to
use liner transfomations, too.

Change-Id: Icff0fad168476c68ea10fd5830bf53bd7294b89f
Reviewed-on: https://gerrit.libreoffice.org/63803
Tested-by: Jenkins
Reviewed-by: Armin Le Grand <Armin.Le.Grand@cib.de>
diff --git a/sc/source/core/data/drwlayer.cxx b/sc/source/core/data/drwlayer.cxx
index dc24b9e..540e320 100644
--- a/sc/source/core/data/drwlayer.cxx
+++ b/sc/source/core/data/drwlayer.cxx
@@ -642,11 +642,15 @@ void ScDrawLayer::ResizeLastRectFromAnchor(const SdrObject* pObj, ScDrawObjData&
    aPos.setX(TwipsToHmm(aPos.X()));
    aPos.setY(TwipsToHmm(aPos.Y()));
    aPos += lcl_calcAvailableDiff(*pDoc, nCol1, nRow1, nTab1, rData.maStartOffset);

    // this sets the needed changed position (translation)
    aRect.SetPos(aPos);

    if (bCanResize)
    {
        tools::Rectangle aLastCellRect = rData.getLastCellRect();
        // all this stuff is additional stuff to evtl. not only translate the
        // range (Rectangle), but also check for and evtl. do corrections for it's size
        const tools::Rectangle aLastCellRect(rData.getLastCellRect());

        // If the row was hidden before, or we don't have a valid cell rect, calculate the
        // new rect based on the end point.
@@ -666,61 +670,105 @@ void ScDrawLayer::ResizeLastRectFromAnchor(const SdrObject* pObj, ScDrawObjData&
            // We calculate based on the last cell rect to be able to scale the image
            // as much as the cell was scaled.
            // Still, we keep the image in its current cell (to keep start anchor == end anchor)
            tools::Rectangle aCurrentCellRect = GetCellRect(*GetDocument(), rData.maStart, true);
            double fWidthFactor = static_cast<double>(aCurrentCellRect.GetWidth())
                                  / static_cast<double>(aLastCellRect.GetWidth());
            double fHeightFactor = static_cast<double>(aCurrentCellRect.GetHeight())
                                   / static_cast<double>(aLastCellRect.GetHeight());
            const tools::Rectangle aCurrentCellRect(GetCellRect(*GetDocument(), rData.maStart, true));
            long nCurrentWidth(aCurrentCellRect.GetWidth());
            long nCurrentHeight(aCurrentCellRect.GetHeight());
            const long nLastWidth(aLastCellRect.GetWidth());
            const long nLastHeight(aLastCellRect.GetHeight());

            bool bIsGrowingLarger = aLastCellRect.GetWidth() * aLastCellRect.GetHeight()
                                    < aCurrentCellRect.GetWidth() * aCurrentCellRect.GetHeight();
            // tdf#116931 Avoid and correct nifty numerical problems with the integer
            // based and converted values (GetCellRect uses multiplies with HMM_PER_TWIPS)
            if(nCurrentWidth + 1 == nLastWidth || nCurrentWidth == nLastWidth + 1)
            {
                nCurrentWidth = nLastWidth;
            }

            if (pObj->shouldKeepAspectRatio())
            if(nCurrentHeight + 1 == nLastHeight || nCurrentHeight == nLastHeight + 1)
            {
                nCurrentHeight = nLastHeight;
            }

            // get intial ScalingFactors
            double fWidthFactor(nCurrentWidth == nLastWidth || 0 == nLastWidth
                ? 1.0
                : static_cast<double>(nCurrentWidth) / static_cast<double>(nLastWidth));
            double fHeightFactor(nCurrentHeight == nLastHeight || 0 == nLastHeight
                ? 1.0
                : static_cast<double>(nCurrentHeight) / static_cast<double>(nLastHeight));

            // check if we grow or shrink - and at all
            const bool bIsGrowing(nCurrentWidth > nLastWidth || nCurrentHeight > nLastHeight);
            const bool bIsShrinking(nCurrentWidth < nLastWidth || nCurrentHeight < nLastHeight);
            const bool bIsSizeChanged(bIsGrowing || bIsShrinking);

            // handle AspectRatio, only needed if size does change
            if(bIsSizeChanged && pObj->shouldKeepAspectRatio())
            {
                tools::Rectangle aRectIncludingOffset = aRect;
                aRectIncludingOffset.setWidth(aRect.GetWidth() + rData.maStartOffset.X());
                aRectIncludingOffset.setHeight(aRect.GetHeight() + rData.maStartOffset.Y());
                long nWidth = aRectIncludingOffset.GetWidth();
                assert(nWidth && "div-by-zero");
                double fMaxWidthFactor = static_cast<double>(aCurrentCellRect.GetWidth())
                double fMaxWidthFactor = static_cast<double>(nCurrentWidth)
                                         / static_cast<double>(nWidth);
                long nHeight = aRectIncludingOffset.GetHeight();
                assert(nHeight && "div-by-zero");
                double fMaxHeightFactor = static_cast<double>(aCurrentCellRect.GetHeight())
                double fMaxHeightFactor = static_cast<double>(nCurrentHeight)
                                          / static_cast<double>(nHeight);
                double fMaxFactor = std::min(fMaxHeightFactor, fMaxWidthFactor);

                if (bIsGrowingLarger) // cell is growing larger
                if(bIsGrowing) // cell is growing larger
                {
                    // To actually grow the image, we need to take the max
                    fWidthFactor = fHeightFactor = std::max(fWidthFactor, fHeightFactor);
                }
                else // cell is growing smaller, take the min
                else if(bIsShrinking) // cell is growing smaller, take the min
                {
                    fWidthFactor = fHeightFactor = std::min(fWidthFactor, fHeightFactor);
                }

                // We don't want the image to become larger than the current cell
                fWidthFactor = fHeightFactor = std::min(fWidthFactor, fMaxFactor);
            }

            // When shrinking the cell, and the image still fits in the smaller cell, don't resize it at all
            if (bIsGrowingLarger
                || rData.getShapeRect().GetUnion(aCurrentCellRect) != aCurrentCellRect)
            if(bIsSizeChanged)
            {
                aRect.setWidth(
                    rtl::math::round(static_cast<double>(aRect.GetWidth()) * fWidthFactor));
                aRect.setHeight(
                    rtl::math::round(static_cast<double>(aRect.GetHeight()) * fHeightFactor));
                // tdf#116931 re-organized scaling (if needed)
                // Check if we need to scale at all. Always scale on growing.
                bool bNeedToScale(bIsGrowing);

                // Reduce offset also when shrinking
                if (!bIsGrowingLarger)
                if(!bNeedToScale && bIsShrinking)
                {
                    Point aAvailableSpaceInCell = Point(aRect.getX() - aLastCellRect.TopLeft().X(),
                                                        aRect.getY() - aLastCellRect.TopLeft().Y());
                    aRect.setX(rtl::math::round(static_cast<double>(aRect.getX())
                                                + aAvailableSpaceInCell.X() * fWidthFactor));
                    aRect.setY(rtl::math::round(static_cast<double>(aRect.getY())
                                                + aAvailableSpaceInCell.Y() * fHeightFactor));
                    // Check if original still fits into space. Do *not* forget to
                    // compare with evtl. numerically corrected aCurrentCellRect
                    const bool bFitsInX(aRect.Right() <= aCurrentCellRect.Left() + nCurrentWidth);
                    const bool bFitsInY(aRect.Bottom() <= aCurrentCellRect.Top() + nCurrentHeight);

                    // If the image still fits in the smaller cell, don't resize it at all
                    bNeedToScale = (!bFitsInX || !bFitsInY);
                }

                if(bNeedToScale)
                {
                    // tdf#116931 use transformations now. Translation is already applied
                    // (see aRect.SetPos above), so only scale needs to be applied - relative
                    // to *new* CellRect (which is aCurrentCellRect).
                    // Prepare scale relative to top-left of aCurrentCellRect
                    basegfx::B2DHomMatrix aChange;

                    aChange.translate(-aCurrentCellRect.getX(), -aCurrentCellRect.getY());
                    aChange.scale(fWidthFactor, fHeightFactor);
                    aChange.translate(aCurrentCellRect.getX(), aCurrentCellRect.getY());

                    // create B2DRange and transform by prepared scale
                    basegfx::B2DRange aNewRange(aRect.Left(), aRect.Top(), aRect.Right(), aRect.Bottom());

                    aNewRange.transform(aChange);

                    // apply to aRect
                    aRect = tools::Rectangle(
                        basegfx::fround(aNewRange.getMinX()), basegfx::fround(aNewRange.getMinY()),
                        basegfx::fround(aNewRange.getMaxX()), basegfx::fround(aNewRange.getMaxY()));
                }
            }
        }