/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include <sal/types.h>
#include <scitems.hxx>
#include <editeng/justifyitem.hxx>
#include <o3tl/safeint.hxx>
#include <o3tl/unit_conversion.hxx>
#include <unotools/textsearch.hxx>
#include <unotools/charclass.hxx>
#include <osl/diagnose.h>

#include <patattr.hxx>
#include <table.hxx>
#include <document.hxx>
#include <docsh.hxx>
#include <drwlayer.hxx>
#include <olinetab.hxx>
#include <global.hxx>
#include <globstr.hrc>
#include <scresid.hxx>
#include <refupdat.hxx>
#include <markdata.hxx>
#include <progress.hxx>
#include <prnsave.hxx>
#include <printopt.hxx>
#include <scmod.hxx>
#include <tabprotection.hxx>
#include <sheetevents.hxx>
#include <segmenttree.hxx>
#include <dbdata.hxx>
#include <conditio.hxx>
#include <globalnames.hxx>
#include <cellvalue.hxx>
#include <scmatrix.hxx>
#include <refupdatecontext.hxx>
#include <rowheightcontext.hxx>
#include <compressedarray.hxx>
#include <tabvwsh.hxx>
#include <SheetViewManager.hxx>
#include <vcl/svapp.hxx>

#include <formula/vectortoken.hxx>
#include <token.hxx>

#include <vector>
#include <memory>

namespace {

ScProgress* GetProgressBar(
    SCSIZE nCount, SCSIZE nTotalCount, ScProgress* pOuterProgress, const ScDocument* pDoc)
{
    if (nTotalCount < 1000)
    {
        // if the total number of rows is less than 1000, don't even bother
        // with the progress bar because drawing progress bar can be very
        // expensive especially in GTK.
        return nullptr;
    }

    if (pOuterProgress)
        return pOuterProgress;

    if (nCount > 1)
        return new ScProgress(
            pDoc->GetDocumentShell(), ScResId(STR_PROGRESS_HEIGHTING), nTotalCount, true);

    return nullptr;
}

void GetOptimalHeightsInColumn(
    sc::RowHeightContext& rCxt, ScColContainer& rCol, SCROW nStartRow, SCROW nEndRow,
    ScProgress* pProgress, sal_uLong nProgressStart )
{
    assert(nStartRow <= nEndRow);

    //  first, one time over the whole range
    //  (with the last column in the hope that they most likely still are
    //  on standard format)


    rCol.back().GetOptimalHeight(rCxt, nStartRow, nEndRow, 0, 0);

    //  from there search for the standard height that is in use in the lower part

    RowHeightsArray& rHeights = rCxt.getHeightArray();
    sal_uInt16 nMinHeight = rHeights.GetValue(nEndRow);
    SCSIZE nPos = nEndRow - 1;
    while ( nPos )
    {
        auto aRangeData = rHeights.GetRangeData(nPos-1);
        if (aRangeData.maValue < nMinHeight)
            break;
        nPos = std::max<SCSIZE>(0, aRangeData.mnRow1);
    }

    const SCROW nMinStart = nPos;

    sal_uInt64 nWeightedCount = nProgressStart + rCol.back().GetWeightedCount(nStartRow, nEndRow);
    const SCCOL maxCol = rCol.size() - 1; // last col done already above
    for (SCCOL nCol=0; nCol<maxCol; nCol++)
    {
        rCol[nCol].GetOptimalHeight(rCxt, nStartRow, nEndRow, nMinHeight, nMinStart);

        if (pProgress)
        {
            nWeightedCount += rCol[nCol].GetWeightedCount(nStartRow, nEndRow);
            pProgress->SetState( nWeightedCount );
        }
    }
}

struct OptimalHeightsFuncObjBase
{
    virtual ~OptimalHeightsFuncObjBase() {}
    virtual bool operator() (SCROW nStartRow, SCROW nEndRow, sal_uInt16 nHeight, bool bApi) = 0;
};

struct SetRowHeightOnlyFunc : public OptimalHeightsFuncObjBase
{
    ScTable* mpTab;
    explicit SetRowHeightOnlyFunc(ScTable* pTab) :
        mpTab(pTab)
    {}

    virtual bool operator() (SCROW nStartRow, SCROW nEndRow, sal_uInt16 nHeight, bool /* bApi */) override
    {
        mpTab->SetRowHeightOnly(nStartRow, nEndRow, nHeight);
        return false;
    }
};

struct SetRowHeightRangeFunc : public OptimalHeightsFuncObjBase
{
    ScTable* mpTab;
    double mnPPTY;

    SetRowHeightRangeFunc(ScTable* pTab, double nPPTY) :
        mpTab(pTab),
        mnPPTY(nPPTY)
    {}

    virtual bool operator() (SCROW nStartRow, SCROW nEndRow, sal_uInt16 nHeight, bool bApi) override
    {
        return mpTab->SetRowHeightRange(nStartRow, nEndRow, nHeight, mnPPTY, bApi);
    }
};

bool SetOptimalHeightsToRows(
    sc::RowHeightContext& rCxt,
    OptimalHeightsFuncObjBase& rFuncObj,
    ScBitMaskCompressedArray<SCROW, CRFlags>* pRowFlags, SCROW nStartRow, SCROW nEndRow,
    bool bApi )
{
    bool bChanged = false;
    SCROW nRngStart = 0;
    SCROW nRngEnd = 0;
    sal_uInt16 nLast = 0;
    sal_uInt16 nExtraHeight = rCxt.getExtraHeight();
    for (SCSIZE i = nStartRow; i <= o3tl::make_unsigned(nEndRow); i++)
    {
        size_t nIndex;
        SCROW nRegionEndRow;
        CRFlags nRowFlag = pRowFlags->GetValue( i, nIndex, nRegionEndRow );
        if ( nRegionEndRow > nEndRow )
            nRegionEndRow = nEndRow;
        SCSIZE nMoreRows = nRegionEndRow - i;     // additional equal rows after first

        bool bAutoSize = !(nRowFlag & CRFlags::ManualSize);
        if (bAutoSize || rCxt.isForceAutoSize())
        {
            if (nExtraHeight)
            {
                if (bAutoSize)
                    pRowFlags->SetValue( i, nRegionEndRow, nRowFlag | CRFlags::ManualSize);
            }
            else if (!bAutoSize)
                pRowFlags->SetValue( i, nRegionEndRow, nRowFlag & ~CRFlags::ManualSize);

            for (SCSIZE nInner = i; nInner <= i + nMoreRows; ++nInner)
            {
                if (nLast)
                {
                    SCROW nRangeRowEnd;
                    size_t nTmp;
                    sal_uInt16 nRangeValue = rCxt.getHeightArray().GetValue(nInner, nTmp, nRangeRowEnd);
                    if (nRangeValue + nExtraHeight == nLast)
                    {
                        nRngEnd = std::min<SCSIZE>(i + nMoreRows, nRangeRowEnd);
                        nInner = nRangeRowEnd;
                    }
                    else
                    {
                        bChanged |= rFuncObj(nRngStart, nRngEnd, nLast, bApi);
                        nLast = 0;
                    }
                }
                if (!nLast)
                {
                    nLast = rCxt.getHeightArray().GetValue(nInner) + rCxt.getExtraHeight();
                    nRngStart = nInner;
                    nRngEnd = nInner;
                }
            }
        }
        else
        {
            if (nLast)
                bChanged |= rFuncObj(nRngStart, nRngEnd, nLast, bApi);
            nLast = 0;
        }
        i += nMoreRows;     // already handled - skip
    }
    if (nLast)
        bChanged |= rFuncObj(nRngStart, nRngEnd, nLast, bApi);

    return bChanged;
}

}

ScTable::ScTable( ScDocument& rDoc, SCTAB nNewTab, const OUString& rNewName,
                    bool bColInfo, bool bRowInfo ) :
    nTab(nNewTab),
    rDocument(rDoc),
    aCol( rDoc.GetSheetLimits(), INITIALCOLCOUNT ),
    aName( rNewName ),
    aCodeName( rNewName ),
    nLinkRefreshDelay( 0 ),
    nLinkMode( ScLinkMode::NONE ),
    aPageStyle( ScResId(STR_STYLENAME_STANDARD) ),
    nRepeatStartX( SCCOL_REPEAT_NONE ),
    nRepeatEndX( SCCOL_REPEAT_NONE ),
    nRepeatStartY( SCROW_REPEAT_NONE ),
    nRepeatEndY( SCROW_REPEAT_NONE ),
    mnOptimalMinRowHeight(0),
    maFilterData(*this),
    nTableAreaX( 0 ),
    nTableAreaY( 0 ),
    nTableAreaVisibleX( 0 ),
    nTableAreaVisibleY( 0 ),
    pSortCollator( nullptr ),
    nLockCount( 0 ),
    aScenarioColor( COL_LIGHTGRAY ),
    aTabBgColor( COL_AUTO ),
    nScenarioFlags(ScScenarioFlags::NONE),
    mpCondFormatList( new ScConditionalFormatList() ),
    maLOKFreezeCell(-1, -1, nNewTab),
    bScenario(false),
    bLayoutRTL(false),
    bLoadingRTL(false),
    bPageSizeValid(false),
    bTableAreaValid(false),
    bTableAreaVisibleValid(false),
    bVisible(true),
    bPendingRowHeights(false),
    bCalcNotification(false),
    bGlobalKeepQuery(false),
    bPrintEntireSheet(true),
    bActiveScenario(false),
    mbPageBreaksValid(false),
    mbForceBreaks(false),
    mbTotalsRowBelow(true),
    bStreamValid(false),
    mpSheetViewManager(new sc::SheetViewManager())
{
    aDefaultColData.InitAttrArray(new ScAttrArray(static_cast<SCCOL>(-1), nNewTab, rDoc, nullptr));
    if (bColInfo)
    {
        mpColWidth.reset( new ScCompressedArray<SCCOL, sal_uInt16>( rDocument.MaxCol()+1, STD_COL_WIDTH ) );
        mpColFlags.reset( new ScBitMaskCompressedArray<SCCOL, CRFlags>( rDocument.MaxCol()+1, CRFlags::NONE ) );
    }

    if (bRowInfo)
    {
        mpRowHeights.reset(new ScFlatUInt16RowSegments(rDocument.MaxRow(), GetOptimalMinRowHeight()));
        pRowFlags.reset(new ScBitMaskCompressedArray<SCROW, CRFlags>( rDocument.MaxRow(), CRFlags::NONE));
    }

    if ( rDocument.IsDocVisible() )
    {
        //  when a sheet is added to a visible document,
        //  initialize its RTL flag from the system locale
        bLayoutRTL = ScGlobal::IsSystemRTL();
    }

    ScDrawLayer* pDrawLayer = rDocument.GetDrawLayer();
    if (pDrawLayer)
    {
        if ( pDrawLayer->ScAddPage( nTab ) )    // sal_False (not inserted) during Undo
        {
            pDrawLayer->ScRenamePage( nTab, aName );
            sal_uLong const nx = o3tl::convert((rDocument.MaxCol()+1) * STD_COL_WIDTH, o3tl::Length::twip, o3tl::Length::mm100);
            sal_uLong ny = o3tl::convert((rDocument.MaxRow() + 1) * GetOptimalMinRowHeight(),
                                         o3tl::Length::twip, o3tl::Length::mm10);
            pDrawLayer->SetPageSize( static_cast<sal_uInt16>(nTab), Size( nx, ny ), false );
        }
    }

    for (SCCOL k=0; k < aCol.size(); k++)
        aCol[k].Init( k, nTab, rDocument, true );
}

void ScTable::ImplDestroy()
{
    if (!rDocument.IsInDtorClear())
    {
        for (SCCOL nCol = 0; nCol < aCol.size(); ++nCol)
        {
            aCol[nCol].FreeNotes();
        }
        //  In the dtor, don't delete the pages in the wrong order.
        //  (or else nTab does not reflect the page number!)
        //  In ScDocument::Clear is afterwards used from Clear at the Draw Layer to delete everything.

        ScDrawLayer* pDrawLayer = rDocument.GetDrawLayer();
        if (pDrawLayer)
            pDrawLayer->ScRemovePage( nTab );
    }

    pRowFlags.reset();
    pSheetEvents.reset();
    pOutlineTable.reset();
    pSearchText.reset();
    moRepeatColRange.reset();
    moRepeatRowRange.reset();
    pScenarioRanges.reset();
    mpRangeName.reset();
    pDBDataNoName.reset();
    DestroySortCollator();
}

ScTable::~ScTable()
{
    suppress_fun_call_w_exception(ImplDestroy());
}

sal_Int64 ScTable::GetHashCode() const
{
    return sal::static_int_cast<sal_Int64>(reinterpret_cast<sal_IntPtr>(this));
}

void ScTable::SetName( const OUString& rNewName )
{
    aName = rNewName;
    aUpperName.clear(); // invalidated if the name is changed

    // SetStreamValid is handled in ScDocument::RenameTab
}

const OUString& ScTable::GetUpperName() const
{
    if (aUpperName.isEmpty() && !aName.isEmpty())
        aUpperName = ScGlobal::getCharClass().uppercase(aName);
    return aUpperName;
}

void ScTable::SetVisible( bool bVis )
{
    if (bVisible != bVis)
        SetStreamValid(false);

    bVisible = bVis;
}

void ScTable::SetStreamValid( bool bSet, bool bIgnoreLock )
{
    if (!bStreamValid && !bSet)
        return; // shortcut
    if ( bIgnoreLock || !rDocument.IsStreamValidLocked() )
        bStreamValid = bSet;
}

void ScTable::SetPendingRowHeights( bool bSet )
{
    bPendingRowHeights = bSet;
}

void ScTable::SetLayoutRTL( bool bSet )
{
    bLayoutRTL = bSet;
}

void ScTable::SetLoadingRTL( bool bSet )
{
    bLoadingRTL = bSet;
}

void ScTable::SetTabBgColor(const Color& rColor)
{
    if (aTabBgColor != rColor)
    {
        // The tab color has changed.  Set this table 'modified'.
        aTabBgColor = rColor;
        SetStreamValid(false);
    }
}

void ScTable::SetScenario( bool bFlag )
{
    bScenario = bFlag;
}

void ScTable::SetLink( ScLinkMode nMode,
                        const OUString& rDoc, const OUString& rFlt, const OUString& rOpt,
                        const OUString& rTab, sal_Int32 nRefreshDelay )
{
    nLinkMode = nMode;
    aLinkDoc = rDoc;        // File
    aLinkFlt = rFlt;        // Filter
    aLinkOpt = rOpt;        // Filter options
    aLinkTab = rTab;        // Sheet name in source file
    nLinkRefreshDelay = nRefreshDelay;  // refresh delay in seconds, 0==off

    SetStreamValid(false);
}

sal_uInt16 ScTable::GetOptimalColWidth( SCCOL nCol, OutputDevice* pDev,
                                    double nPPTX, double nPPTY,
                                    const Fraction& rZoomX, const Fraction& rZoomY,
                                    bool bFormula, const ScMarkData* pMarkData,
                                    const ScColWidthParam* pParam )
{
    if ( nCol >= aCol.size() )
        return ( STD_COL_WIDTH - STD_EXTRA_WIDTH );

    return aCol[nCol].GetOptimalColWidth( pDev, nPPTX, nPPTY, rZoomX, rZoomY,
        bFormula, STD_COL_WIDTH - STD_EXTRA_WIDTH, pMarkData, pParam );
}

tools::Long ScTable::GetNeededSize( SCCOL nCol, SCROW nRow,
                                OutputDevice* pDev,
                                double nPPTX, double nPPTY,
                                const Fraction& rZoomX, const Fraction& rZoomY,
                                bool bWidth, bool bTotalSize, bool bInPrintTwips )
{
    if ( nCol >= aCol.size() )
        return 0;

    ScNeededSizeOptions aOptions;
    aOptions.bSkipMerged = false;       // count merged cells
    aOptions.bTotalSize  = bTotalSize;

    return aCol[nCol].GetNeededSize
        ( nRow, pDev, nPPTX, nPPTY, rZoomX, rZoomY, bWidth, aOptions, nullptr, bInPrintTwips );
}

bool ScTable::SetOptimalHeight(
    sc::RowHeightContext& rCxt, SCROW nStartRow, SCROW nEndRow, bool bApi,
    ScProgress* pOuterProgress, sal_uInt64 nProgressStart )
{
    assert(nStartRow <= nEndRow);

    OSL_ENSURE( rCxt.getExtraHeight() == 0 || rCxt.isForceAutoSize(),
        "automatic OptimalHeight with Extra" );

    if ( rDocument.IsAdjustHeightLocked() )
    {
        return false;
    }

    if (!rCxt.isForceAutoSize())
    {
        // Optimize - exit early if all rows have defined height: super expensive GetOptimalHeight
        size_t nIndex;
        SCROW nRow;
        CRFlags nRowFlags = pRowFlags->GetValue(nStartRow, nIndex, nRow); // changes nIndex, nRow
        if (nRowFlags & CRFlags::ManualSize) // first block of rows is manual - are all the rest?
        {
            bool bAllRowsAreManualHeight = true;
            while (nRow < nEndRow)
            {
                nRowFlags = pRowFlags->GetNextValue(nIndex, nRow);
                if (!(nRowFlags & CRFlags::ManualSize))
                {
                    bAllRowsAreManualHeight = false;
                    break;
                }
            }
            if (bAllRowsAreManualHeight)
                return false;
        }
    }

    SCSIZE  nCount = static_cast<SCSIZE>(nEndRow-nStartRow+1);

    ScProgress* pProgress = GetProgressBar(nCount, GetWeightedCount(), pOuterProgress, &rDocument);

    mpRowHeights->enableTreeSearch(false);

    GetOptimalHeightsInColumn(rCxt, aCol, nStartRow, nEndRow, pProgress, nProgressStart);

    SetRowHeightRangeFunc aFunc(this, rCxt.getPPTY());
    bool bChanged = SetOptimalHeightsToRows(rCxt, aFunc, pRowFlags.get(), nStartRow, nEndRow, bApi);

    if ( pProgress != pOuterProgress )
        delete pProgress;

    mpRowHeights->enableTreeSearch(true);

    if (bChanged)
    {
        // reposition (cell) anchored items with setDrawPageSize ScObjectHandling::RecalcPosMode
        SetDrawPageSize(/*ResetStreamValid=*/true, /*UpdateNoteCaptionPos=*/false);

        if (ScViewData* pViewData = ScDocShell::GetViewData())
        {
            ScTabViewShell::notifyAllViewsSheetGeomInvalidation(
                pViewData->GetViewShell(),
                false /* bColsAffected */, true /* bRowsAffected */,
                true /* bSizes*/, false /* bHidden */, false /* bFiltered */,
                false /* bGroups */, nTab);
        }
    }

    return bChanged;
}

void ScTable::SetOptimalHeightOnly(
    sc::RowHeightContext& rCxt, SCROW nStartRow, SCROW nEndRow,
    ScProgress* pOuterProgress, sal_uInt64 nProgressStart )
{
    OSL_ENSURE( rCxt.getExtraHeight() == 0 || rCxt.isForceAutoSize(),
        "automatic OptimalHeight with Extra" );

    if ( rDocument.IsAdjustHeightLocked() )
        return;

    SCSIZE  nCount = static_cast<SCSIZE>(nEndRow-nStartRow+1);

    ScProgress* pProgress = GetProgressBar(nCount, GetWeightedCount(), pOuterProgress, &rDocument);

    GetOptimalHeightsInColumn(rCxt, aCol, nStartRow, nEndRow, pProgress, nProgressStart);

    SetRowHeightOnlyFunc aFunc(this);

    bool bChanged = SetOptimalHeightsToRows(rCxt, aFunc, pRowFlags.get(), nStartRow, nEndRow, true);

    if ( pProgress != pOuterProgress )
        delete pProgress;

    if (bChanged)
    {
        if (ScViewData* pViewData = ScDocShell::GetViewData())
        {
            ScTabViewShell::notifyAllViewsSheetGeomInvalidation(
                pViewData->GetViewShell(),
                false /* bColsAffected */, true /* bRowsAffected */,
                true /* bSizes*/, false /* bHidden */, false /* bFiltered */,
                false /* bGroups */, nTab);
        }
    }
}

bool ScTable::GetCellArea( SCCOL& rEndCol, SCROW& rEndRow ) const
{
    bool bFound = false;
    SCCOL nMaxX = 0;
    SCROW nMaxY = 0;
    for (SCCOL i=0; i<aCol.size(); i++)
    {
            if (!aCol[i].IsEmptyData())
            {
                bFound = true;
                nMaxX = i;
                SCROW nRow = aCol[i].GetLastDataPos();
                if (nRow > nMaxY)
                    nMaxY = nRow;
            }
            if ( aCol[i].HasCellNotes() )
            {
                SCROW maxNoteRow = aCol[i].GetCellNotesMaxRow();
                if (maxNoteRow >= nMaxY)
                {
                    bFound = true;
                    nMaxY = maxNoteRow;
                }
                if (i>nMaxX)
                {
                    bFound = true;
                    nMaxX = i;
                }
            }
            if (aCol[i].HasSparklines())
            {
                SCROW maxSparklineRow = aCol[i].GetSparklinesMaxRow();
                if (maxSparklineRow >= nMaxY)
                {
                    bFound = true;
                    nMaxY = maxSparklineRow;
                }
                if (i > nMaxX)
                {
                    bFound = true;
                    nMaxX = i;
                }
            }
    }

    rEndCol = nMaxX;
    rEndRow = nMaxY;
    return bFound;
}

bool ScTable::GetTableArea( SCCOL& rEndCol, SCROW& rEndRow, bool bCalcHiddens) const
{
    bool bRet = true;               //TODO: remember?
    if (bCalcHiddens)
    {
        if (!bTableAreaValid)
        {
            bRet = GetPrintArea(nTableAreaX, nTableAreaY, true, bCalcHiddens);
            bTableAreaValid = true;
        }
        rEndCol = nTableAreaX;
        rEndRow = nTableAreaY;
    }
    else
    {
        if (!bTableAreaVisibleValid)
        {
            bRet = GetPrintArea(nTableAreaVisibleX, nTableAreaVisibleY, true, bCalcHiddens);
            bTableAreaVisibleValid = true;
        }
        rEndCol = nTableAreaVisibleX;
        rEndRow = nTableAreaVisibleY;
    }
    return bRet;
}

const SCCOL SC_COLUMNS_STOP = 30;

bool ScTable::GetPrintArea( SCCOL& rEndCol, SCROW& rEndRow, bool bNotes, bool bCalcHiddens ) const
{
    bool bFound = false;
    SCCOL nMaxX = 0;
    SCROW nMaxY = 0;
    SCCOL i;

    bool bSkipEmpty = ScModule::get()->GetPrintOptions().GetSkipEmpty();

    for (i=0; i<aCol.size(); i++)               // Test data
    {
        if (bCalcHiddens || !rDocument.ColHidden(i, nTab))
        {
            if (!aCol[i].IsEmptyData())
            {
                bFound = true;
                if (i>nMaxX)
                    nMaxX = i;
                SCROW nColY = aCol[i].GetLastDataPos();
                if (nColY > nMaxY)
                    nMaxY = nColY;
            }
            if (bNotes && aCol[i].HasCellNotes() )
            {
                SCROW maxNoteRow = aCol[i].GetCellNotesMaxRow();
                if (maxNoteRow >= nMaxY)
                {
                    bFound = true;
                    nMaxY = maxNoteRow;
                }
                if (i>nMaxX)
                {
                    bFound = true;
                    nMaxX = i;
                }
            }
            if (aCol[i].HasSparklines())
            {
                SCROW maxSparklineRow = aCol[i].GetSparklinesMaxRow();
                if (maxSparklineRow >= nMaxY)
                {
                    bFound = true;
                    nMaxY = maxSparklineRow;
                }
                if (i > nMaxX)
                {
                    bFound = true;
                    nMaxX = i;
                }
            }
        }
    }

    SCCOL nMaxDataX = nMaxX;

    for (i=0; i<aCol.size(); i++)               // Test attribute
    {
        if (bCalcHiddens || !rDocument.ColHidden(i, nTab))
        {
            SCROW nLastRow;
            if (aCol[i].GetLastVisibleAttr( nLastRow, bSkipEmpty ))
            {
                bFound = true;
                nMaxX = i;
                if (nLastRow > nMaxY)
                    nMaxY = nLastRow;
            }
        }
    }

    if (nMaxX == rDocument.MaxCol())                    // omit attribute at the right
    {
        --nMaxX;
        while ( nMaxX>0 && aCol[nMaxX].IsVisibleAttrEqual(aCol[nMaxX+1], 0, rDocument.MaxRow()) )
            --nMaxX;
    }

    if ( nMaxX < nMaxDataX )
    {
        nMaxX = nMaxDataX;
    }
    else if ( nMaxX > nMaxDataX )
    {
        SCCOL nAttrStartX = nMaxDataX + 1;
        while ( nAttrStartX < (aCol.size()-1) )
        {
            SCCOL nAttrEndX = nAttrStartX;
            while ( nAttrEndX < (aCol.size()-1) && aCol[nAttrStartX].IsVisibleAttrEqual(aCol[nAttrEndX+1], 0, rDocument.MaxRow()) )
                ++nAttrEndX;
            if ( nAttrEndX + 1 - nAttrStartX >= SC_COLUMNS_STOP )
            {
                // found equally-formatted columns behind data -> stop before these columns
                nMaxX = nAttrStartX - 1;

                // also don't include default-formatted columns before that
                SCROW nDummyRow;
                while ( nMaxX > nMaxDataX && !aCol[nMaxX].GetLastVisibleAttr( nDummyRow, bSkipEmpty ) )
                    --nMaxX;
                break;
            }
            nAttrStartX = nAttrEndX + 1;
        }
    }

    rEndCol = nMaxX;
    rEndRow = nMaxY;
    return bFound;
}

bool ScTable::GetPrintAreaHor( SCROW nStartRow, SCROW nEndRow,
                                SCCOL& rEndCol ) const
{
    bool bFound = false;
    SCCOL nMaxX = 0;
    SCCOL i;

    for (i=0; i<aCol.size(); i++)               // Test attribute
    {
        if (aCol[i].HasVisibleAttrIn( nStartRow, nEndRow ))
        {
            bFound = true;
            nMaxX = i;
        }
    }

    if (nMaxX == rDocument.MaxCol())                    // omit attribute at the right
    {
        --nMaxX;
        while ( nMaxX>0 && aCol[nMaxX].IsVisibleAttrEqual(aCol[nMaxX+1], nStartRow, nEndRow) )
            --nMaxX;
    }

    for (i=0; i<aCol.size(); i++)               // test the data
    {
        if (!aCol[i].IsEmptyData( nStartRow, nEndRow ))        //TODO: bNotes ??????
        {
            bFound = true;
            if (i > nMaxX)
                nMaxX = i;
        }
        else if (aCol[i].HasSparklines())
        {
            if (i > nMaxX)
            {
                bFound = true;
                nMaxX = i;
            }
        }
    }

    rEndCol = nMaxX;
    return bFound;
}

bool ScTable::GetPrintAreaVer( SCCOL nStartCol, SCCOL nEndCol,
                                SCROW& rEndRow, bool bNotes ) const
{
    bool bFound = false;
    SCROW nMaxY = 0;
    SCCOL i;

    bool bSkipEmpty = ScModule::get()->GetPrintOptions().GetSkipEmpty();

    for (i=nStartCol; i<=nEndCol && i < aCol.size(); i++)              // Test attribute
    {
        SCROW nLastRow;
        if (aCol[i].GetLastVisibleAttr( nLastRow, bSkipEmpty ))
        {
            bFound = true;
            if (nLastRow > nMaxY)
                nMaxY = nLastRow;
        }
    }

    for (i=nStartCol; i<=nEndCol && i < aCol.size(); i++)              // Test data
    {
        if (!aCol[i].IsEmptyData())
        {
            bFound = true;
            SCROW nColY = aCol[i].GetLastDataPos();
            if (nColY > nMaxY)
                nMaxY = nColY;
        }
        if (bNotes && aCol[i].HasCellNotes() )
        {
            SCROW maxNoteRow =aCol[i].GetCellNotesMaxRow();
            if (maxNoteRow > nMaxY)
            {
                bFound = true;
                nMaxY = maxNoteRow;
            }
        }
        if (aCol[i].HasSparklines())
        {
            SCROW maxNoteRow = aCol[i].GetSparklinesMaxRow();
            if (maxNoteRow > nMaxY)
            {
                bFound = true;
                nMaxY = maxNoteRow;
            }
        }
    }

    rEndRow = nMaxY;
    return bFound;
}

bool ScTable::GetDataStart( SCCOL& rStartCol, SCROW& rStartRow ) const
{
    bool bFound = false;
    SCCOL nMinX = aCol.size()-1;
    SCROW nMinY = rDocument.MaxRow();
    SCCOL i;

    for (i=0; i<aCol.size(); i++)                   // Test attribute
    {
        SCROW nFirstRow;
        if (aCol[i].GetFirstVisibleAttr( nFirstRow ))
        {
            if (!bFound)
                nMinX = i;
            bFound = true;
            if (nFirstRow < nMinY)
                nMinY = nFirstRow;
        }
    }

    if (nMinX == 0)                                     // omit attribute at the right
    {
        if ( aCol.size() > 1 && aCol[0].IsVisibleAttrEqual(aCol[1], 0, rDocument.MaxRow())) // no single ones
        {
            ++nMinX;
            while ( nMinX<(aCol.size()-1) && aCol[nMinX].IsVisibleAttrEqual(aCol[nMinX-1], 0, rDocument.MaxRow()))
                ++nMinX;
        }
    }

    bool bDatFound = false;
    for (i=0; i<aCol.size(); i++)                   // Test data
    {
        if (!aCol[i].IsEmptyData())
        {
            if (!bDatFound && i<nMinX)
                nMinX = i;
            bFound = bDatFound = true;
            SCROW nRow = aCol[i].GetFirstDataPos();
            if (nRow < nMinY)
                nMinY = nRow;
        }
        if ( aCol[i].HasCellNotes() )
        {
            SCROW minNoteRow = aCol[i].GetCellNotesMinRow();
            if (minNoteRow <= nMinY)
            {
                bFound = true;
                nMinY = minNoteRow;
            }
            if (i<nMinX)
            {
                bFound = true;
                nMinX = i;
            }
        }
        if (aCol[i].HasSparklines())
        {
            SCROW minSparkline = aCol[i].GetSparklinesMinRow();
            if (minSparkline <= nMinY)
            {
                bFound = true;
                nMinY = minSparkline;
            }
            if (i < nMinX)
            {
                bFound = true;
                nMinX = i;
            }
        }
    }
    rStartCol = nMinX;
    rStartRow = nMinY;
    return bFound;
}

void ScTable::GetDataArea( SCCOL& rStartCol, SCROW& rStartRow, SCCOL& rEndCol, SCROW& rEndRow,
                           bool bIncludeOld, bool bOnlyDown ) const
{
    // return the smallest area containing at least all contiguous cells having data. This area
    // is a square containing also empty cells. It may shrink or extend the area given as input
    // Flags as modifiers:
    //
    //     bIncludeOld = true ensure that the returned area contains at least the initial area,
    //                   independently of the emptiness of rows / columns (i.e. does not allow shrinking)
    //     bOnlyDown = true means extend / shrink the inputted area only down, i.e modify only rEndRow

    rStartCol = std::min<SCCOL>( rStartCol, aCol.size()-1 );
    rEndCol   = std::min<SCCOL>( rEndCol,   aCol.size()-1 );

    bool bLeft = false;
    bool bRight  = false;
    bool bTop = false;
    bool bBottom = false;
    bool bChanged = false;

    // We need to cache sc::ColumnBlockConstPosition per each column.
    std::vector< sc::ColumnBlockConstPosition > blockPos( rEndCol + 1 );
    for( SCCOL i = 0; i <= rEndCol; ++i )
        aCol[ i ].InitBlockPosition( blockPos[ i ] );

    do
    {
        bChanged = false;

        if (!bOnlyDown)
        {
            SCROW nStart = rStartRow;
            SCROW nEnd = rEndRow;
            if (nStart>0) --nStart;
            if (nEnd<rDocument.MaxRow()) ++nEnd;

            if (rEndCol < (aCol.size()-1))
                if (!aCol[rEndCol+1].IsEmptyData(nStart,nEnd))
                {
                    assert( int( blockPos.size()) == rEndCol + 1 );
                    ++rEndCol;
                    blockPos.resize( blockPos.size() + 1 );
                    aCol[ rEndCol ].InitBlockPosition( blockPos[ rEndCol ] );
                    bChanged = true;
                    bRight = true;
                }

            if (rStartCol > 0)
                if (!aCol[rStartCol-1].IsEmptyData(nStart,nEnd))
                {
                    --rStartCol;
                    bChanged = true;
                    bLeft = true;
                }

            if (rStartRow > 0)
            {
                SCROW nTest = rStartRow-1;
                bool needExtend = false;
                for ( SCCOL i = rStartCol; i<=rEndCol && !needExtend; i++)
                    if (aCol[i].HasDataAt(blockPos[i], nTest))
                        needExtend = true;
                if (needExtend)
                {
                    --rStartRow;
                    bChanged = true;
                    bTop = true;
                }
            }
        }

        if (rEndRow < rDocument.MaxRow())
        {
            SCROW nTest = rEndRow+1;
            bool needExtend = false;
            for ( SCCOL i = rStartCol; i<=rEndCol && !needExtend; i++)
                if (aCol[i].HasDataAt(blockPos[ i ], nTest))
                    needExtend = true;
            if (needExtend)
            {
                ++rEndRow;
                bChanged = true;
                bBottom = true;
            }
        }
    }
    while( bChanged );

    if ( !bIncludeOld && !bOnlyDown )
    {
        if ( !bLeft )
            while ( rStartCol < rEndCol && rStartCol < (aCol.size()-1) && aCol[rStartCol].IsEmptyData(rStartRow,rEndRow) )
                ++rStartCol;

        if ( !bRight )
            while ( rEndCol > 0 && rStartCol < rEndCol && aCol[rEndCol].IsEmptyData(rStartRow,rEndRow) )
                --rEndCol;

        if ( !bTop && rStartRow < rDocument.MaxRow() && rStartRow < rEndRow )
        {
            bool bShrink = true;
            do
            {
                for ( SCCOL i = rStartCol; i<=rEndCol && bShrink; i++)
                    if (aCol[i].HasDataAt(rStartRow))
                        bShrink = false;
                if (bShrink)
                    ++rStartRow;
            } while (bShrink && rStartRow < rDocument.MaxRow() && rStartRow < rEndRow);
        }
    }

    if ( !bIncludeOld )
    {
        if ( !bBottom && rEndRow > 0 && rStartRow < rEndRow )
        {
            SCROW nLastDataRow = GetLastDataRow( rStartCol, rEndCol, rEndRow);
            if (nLastDataRow < rEndRow)
                rEndRow = std::max( rStartRow, nLastDataRow);
        }
    }
}

bool ScTable::GetDataAreaSubrange( ScRange& rRange ) const
{
    SCCOL nCol1 = rRange.aStart.Col(), nCol2 = rRange.aEnd.Col();

    if ( nCol1 >= aCol.size() )
        return false;

    nCol2 = std::min<SCCOL>( nCol2, aCol.size()-1 );

    SCROW nRow1 = rRange.aStart.Row(), nRow2 = rRange.aEnd.Row();

    SCCOL nFirstNonEmptyCol = -1, nLastNonEmptyCol = -1;
    SCROW nRowStart = nRow2, nRowEnd = nRow1;

    for ( SCCOL nCol = nCol1; nCol <= nCol2; ++nCol )
    {
        SCROW nRowStartThis = nRow1, nRowEndThis = nRow2;
        bool bTrimmed = aCol[nCol].TrimEmptyBlocks(nRowStartThis, nRowEndThis);
        if ( bTrimmed )
        {
            if ( nFirstNonEmptyCol == -1 )
                nFirstNonEmptyCol = nCol;
            nLastNonEmptyCol = nCol;

            nRowStart = std::min<SCROW>(nRowStart, nRowStartThis);
            nRowEnd = std::max<SCROW>(nRowEnd, nRowEndThis);
        }
    }

    if ( nFirstNonEmptyCol == -1 )
        return false;

    assert(nFirstNonEmptyCol <= nLastNonEmptyCol);
    assert(nRowStart <= nRowEnd);

    rRange.aStart.Set(nFirstNonEmptyCol, nRowStart, rRange.aStart.Tab());
    rRange.aEnd.Set(nLastNonEmptyCol, nRowEnd, rRange.aEnd.Tab());

    return true;
}

bool ScTable::ShrinkToUsedDataArea( bool& o_bShrunk, SCCOL& rStartCol, SCROW& rStartRow,
        SCCOL& rEndCol, SCROW& rEndRow, bool bColumnsOnly, bool bStickyTopRow, bool bStickyLeftCol,
        ScDataAreaExtras* pDataAreaExtras ) const
{
    rStartCol = std::min<SCCOL>( rStartCol, aCol.size()-1 );
    // check for rEndCol is done below.

    o_bShrunk = false;

    PutInOrder( rStartCol, rEndCol);
    PutInOrder( rStartRow, rEndRow);
    if (rStartCol < 0)
    {
        rStartCol = 0;
        o_bShrunk = true;
    }
    if (rStartRow < 0)
    {
        rStartRow = 0;
        o_bShrunk = true;
    }
    if (rEndCol >= aCol.size())
    {
        rEndCol = aCol.size()-1;
        o_bShrunk = true;
    }
    if (rEndRow > rDocument.MaxRow())
    {
        rEndRow = rDocument.MaxRow();
        o_bShrunk = true;
    }

    while (rStartCol < rEndCol)
    {
        if (aCol[rEndCol].IsEmptyData( rStartRow, rEndRow))
        {
            if (pDataAreaExtras && pDataAreaExtras->mnEndCol < rEndCol)
            {
                // Check in order of likeliness.
                if (    (pDataAreaExtras->mbCellFormats
                            && aCol[rEndCol].GetPatternCount( rStartRow, rEndRow) > 1
                            && aCol[rEndCol].HasVisibleAttrIn( rStartRow, rEndRow)) ||
                        (pDataAreaExtras->mbCellNotes
                         && !aCol[rEndCol].IsNotesEmptyBlock( rStartRow, rEndRow)) ||
                        (pDataAreaExtras->mbCellDrawObjects
                         && !aCol[rEndCol].IsDrawObjectsEmptyBlock( rStartRow, rEndRow)))
                    pDataAreaExtras->mnEndCol = rEndCol;
            }

            --rEndCol;
            o_bShrunk = true;
        }
        else
            break;  // while
    }

    if (!bStickyLeftCol)
    {
        while (rStartCol < rEndCol)
        {
            if (aCol[rStartCol].IsEmptyData( rStartRow, rEndRow))
            {
                if (pDataAreaExtras && pDataAreaExtras->mnStartCol > rStartCol)
                {
                    // Check in order of likeliness.
                    if (    (pDataAreaExtras->mbCellFormats
                                && aCol[rStartCol].GetPatternCount( rStartRow, rEndRow) > 1
                                && aCol[rStartCol].HasVisibleAttrIn( rStartRow, rEndRow)) ||
                            (pDataAreaExtras->mbCellNotes
                             && !aCol[rStartCol].IsNotesEmptyBlock( rStartRow, rEndRow)) ||
                            (pDataAreaExtras->mbCellDrawObjects
                             && !aCol[rStartCol].IsDrawObjectsEmptyBlock( rStartRow, rEndRow)))
                        pDataAreaExtras->mnStartCol = rStartCol;
                }

                ++rStartCol;
                o_bShrunk = true;
            }
            else
                break;  // while
        }
    }

    if (!bColumnsOnly)
    {
        while (rStartRow < rEndRow)
        {
            SCROW nLastDataRow = GetLastDataRow(rStartCol, rEndCol, rEndRow, pDataAreaExtras);
            if (0 <= nLastDataRow && nLastDataRow < rEndRow)
            {
                rEndRow = std::max( rStartRow, nLastDataRow);
                o_bShrunk = true;
            }
            else
                break;  // while
        }

        if (!bStickyTopRow)
        {
            while (rStartRow < rEndRow)
            {
                bool bFound = false;
                for (SCCOL i=rStartCol; i<=rEndCol && !bFound; i++)
                {
                    if (aCol[i].HasDataAt(rStartRow, pDataAreaExtras))
                        bFound = true;
                }
                if (!bFound)
                {
                    ++rStartRow;
                    o_bShrunk = true;
                }
                else
                    break;  // while
            }
        }
    }

    return rStartCol != rEndCol || (bColumnsOnly ?
            !aCol[rStartCol].IsEmptyData( rStartRow, rEndRow) :
            (rStartRow != rEndRow ||
                aCol[rStartCol].HasDataAt( rStartRow, pDataAreaExtras)));
}

SCROW ScTable::GetLastDataRow( SCCOL nCol1, SCCOL nCol2, SCROW nLastRow, ScDataAreaExtras* pDataAreaExtras ) const
{
    if ( !IsColValid( nCol1 ) || !ValidCol( nCol2 ) )
        return -1;

    nCol2 = std::min<SCCOL>( nCol2, aCol.size() - 1 );

    SCROW nNewLastRow = 0;
    for (SCCOL i = nCol1; i <= nCol2; ++i)
    {
        SCROW nThis = aCol[i].GetLastDataPos(nLastRow, pDataAreaExtras);
        if (nNewLastRow < nThis)
            nNewLastRow = nThis;
    }

    return nNewLastRow;
}

bool ScTable::IsEmptyData( SCCOL nStartCol, SCROW nStartRow,
                            SCCOL nEndCol, SCROW nEndRow ) const
{
    for( SCCOL col : GetAllocatedColumnsRange( nStartCol, nEndCol ))
        if( !aCol[col].IsEmptyData( nStartRow, nEndRow ))
            return false;
    return true;
}

SCSIZE ScTable::GetEmptyLinesInBlock( SCCOL nStartCol, SCROW nStartRow,
                                        SCCOL nEndCol, SCROW nEndRow, ScDirection eDir ) const
{
    SCCOL nStartColOrig = nStartCol;
    SCCOL nEndColOrig   = nEndCol;
    nStartCol = std::min<SCCOL>( nStartCol, aCol.size()-1 );
    nEndCol   = std::min<SCCOL>( nEndCol,   aCol.size()-1 );

    // The region is not allocated and does not contain any data.
    if ( nStartColOrig != nStartCol )
        return ( ((eDir == DIR_BOTTOM) || (eDir == DIR_TOP)) ?
                 static_cast<SCSIZE>(nEndRow - nStartRow + 1) :
                 static_cast<SCSIZE>(nEndColOrig - nStartColOrig + 1) );

    SCSIZE nGapRight = static_cast<SCSIZE>(nEndColOrig - nEndCol);
    SCSIZE nCount = 0;
    SCCOL nCol;
    if ((eDir == DIR_BOTTOM) || (eDir == DIR_TOP))
    {
        nCount = static_cast<SCSIZE>(nEndRow - nStartRow + 1);
        for (nCol = nStartCol; nCol <= nEndCol; nCol++)
            nCount = std::min(nCount, aCol[nCol].GetEmptyLinesInBlock(nStartRow, nEndRow, eDir));
    }
    else if (eDir == DIR_RIGHT)
    {
        nCol = nEndCol;
        while ((nCol >= nStartCol) &&
                 aCol[nCol].IsEmptyData(nStartRow, nEndRow))
        {
            nCount++;
            nCol--;
        }
        nCount += nGapRight;
    }
    else
    {
        nCol = nStartCol;
        while ((nCol <= nEndCol) && aCol[nCol].IsEmptyData(nStartRow, nEndRow))
        {
            nCount++;
            nCol++;
        }

        // If the area between nStartCol and nEndCol are empty,
        // add the count of unallocated columns on the right.
        if ( nCol > nEndCol )
            nCount += nGapRight;
    }
    return nCount;
}

bool ScTable::IsEmptyLine( SCROW nRow, SCCOL nStartCol, SCCOL nEndCol ) const
{
    // The range of columns are unallocated hence empty.
    if ( nStartCol >= aCol.size() )
        return true;

    nEndCol   = std::min<SCCOL>( nEndCol,   aCol.size()-1 );

    for (SCCOL i=nStartCol; i<=nEndCol; i++)
        if (aCol[i].HasDataAt(nRow))
            return false;
    return true;
}

void ScTable::LimitChartArea( SCCOL& rStartCol, SCROW& rStartRow, SCCOL& rEndCol, SCROW& rEndRow ) const
{
    rStartCol = std::min<SCCOL>( rStartCol, aCol.size()-1 );
    rEndCol   = std::min<SCCOL>( rEndCol,   aCol.size()-1 );

    while ( rStartCol<rEndCol && aCol[rStartCol].IsEmptyData(rStartRow,rEndRow) )
        ++rStartCol;

    while ( rStartCol<rEndCol && aCol[rEndCol].IsEmptyData(rStartRow,rEndRow) )
        --rEndCol;

    while ( rStartRow<rEndRow && IsEmptyLine(rStartRow, rStartCol, rEndCol) )
        ++rStartRow;

    // Optimised loop for finding the bottom of the area, can be costly in large
    // spreadsheets.
    SCROW lastDataPos = 0;
    for (SCCOL i=rStartCol; i<=rEndCol; i++)
        lastDataPos = std::max(lastDataPos, aCol[i].GetLastDataPos());
    // reduce EndRow to the last row with data
    rEndRow = std::min(rEndRow, lastDataPos);
    // but make sure EndRow is >= StartRow
    rEndRow = std::max(rStartRow, rEndRow);
}

SCCOL ScTable::FindNextVisibleCol( SCCOL nCol, bool bRight ) const
{
    if(bRight)
    {
        nCol++;
        SCCOL nEnd = 0;
        bool bHidden = rDocument.ColHidden(nCol, nTab, nullptr, &nEnd);
        if(bHidden)
            nCol = nEnd +1;

        return std::min<SCCOL>(rDocument.MaxCol(), nCol);
    }
    else
    {
        nCol--;
        SCCOL nStart = rDocument.MaxCol();
        bool bHidden = rDocument.ColHidden(nCol, nTab, &nStart);
        if(bHidden)
            nCol = nStart - 1;

        return std::max<SCCOL>(0, nCol);
    }
}

SCCOL ScTable::FindNextVisibleColWithContent( SCCOL nCol, bool bRight, SCROW nRow ) const
{
    const SCCOL nLastCol = aCol.size() - 1;
    if(bRight)
    {
        // If nCol is the last allocated column index, there won't be any content to its right.
        // To maintain the original return behaviour, return rDocument.MaxCol().
        if(nCol >= nLastCol)
            return rDocument.MaxCol();

        do
        {
            nCol++;
            SCCOL nEndCol = 0;
            bool bHidden = rDocument.ColHidden( nCol, nTab, nullptr, &nEndCol );
            if(bHidden)
            {
                nCol = nEndCol +1;
                // Can end search early as there is no data after nLastCol.
                // For nCol == nLastCol, it may still have data so don't want to return rDocument.MaxCol().
                if(nCol > nLastCol)
                    return rDocument.MaxCol();
            }

            if(aCol[nCol].HasVisibleDataAt(nRow))
                return nCol;
        }
        while(nCol < nLastCol); // Stop search as soon as the last allocated column is searched.

        return rDocument.MaxCol();
    }
    else
    {
        if(nCol == 0)
            return 0;

        // If nCol is in the unallocated range [nLastCol+1, rDocument.MaxCol()], then move it directly after nLastCol
        // as there is no data in the unallocated range. This also makes the search faster and avoids
        // the need for more range checks in the loop below.
        if ( nCol > nLastCol )
            nCol = nLastCol + 1;

        do
        {
            nCol--;
            SCCOL nStartCol = rDocument.MaxCol();
            bool bHidden = rDocument.ColHidden( nCol, nTab, &nStartCol );
            if(bHidden)
            {
                nCol = nStartCol -1;
                if(nCol <= 0)
                    return 0;
            }

            if(aCol[nCol].HasVisibleDataAt(nRow))
                return nCol;
        }
        while(nCol > 0);

        return 0;
    }
}

void ScTable::FindAreaPos( SCCOL& rCol, SCROW& rRow, ScMoveDirection eDirection ) const
{
    const SCCOL nLastCol = aCol.size() - 1;

    if (eDirection == SC_MOVE_LEFT || eDirection == SC_MOVE_RIGHT)
    {
        SCCOL nNewCol = rCol;
        bool bThere = ( nNewCol <= nLastCol ) && aCol[nNewCol].HasVisibleDataAt(rRow);
        bool bRight = (eDirection == SC_MOVE_RIGHT);
        if (bThere)
        {
            if(nNewCol >= rDocument.MaxCol() && eDirection == SC_MOVE_RIGHT)
                return;
            else if(nNewCol == 0 && eDirection == SC_MOVE_LEFT)
                return;

            SCCOL nNextCol = FindNextVisibleCol( nNewCol, bRight );

            if( nNextCol <= nLastCol && aCol[nNextCol].HasVisibleDataAt(rRow) )
            {
                bool bFound = false;
                nNewCol = nNextCol;
                do
                {
                    nNextCol = FindNextVisibleCol( nNewCol, bRight );
                    if( nNextCol <= nLastCol && aCol[nNextCol].HasVisibleDataAt(rRow) )
                        nNewCol = nNextCol;
                    else
                        bFound = true;
                }
                while(!bFound && nNextCol > 0 && nNextCol < rDocument.MaxCol());
            }
            else
            {
                nNewCol = FindNextVisibleColWithContent(nNewCol, bRight, rRow);
            }
        }
        else
        {
            nNewCol = FindNextVisibleColWithContent(nNewCol, bRight, rRow);
        }

        if (nNewCol<0)
            nNewCol=0;
        if (nNewCol>rDocument.MaxCol())
            nNewCol=rDocument.MaxCol();
        rCol = nNewCol;
    }
    else
    {
        if ( rCol <= nLastCol )
            aCol[rCol].FindDataAreaPos(rRow,eDirection == SC_MOVE_DOWN);
        else
        {
            // The cell (rCol, rRow) is equivalent to an empty cell (although not allocated).
            // Set rRow to 0 or rDocument.MaxRow() depending on eDirection to maintain the behaviour of
            // ScColumn::FindDataAreaPos() when the given column is empty.
            rRow = ( eDirection == SC_MOVE_DOWN ) ? rDocument.MaxRow() : 0;
        }
    }
}

bool ScTable::ValidNextPos( SCCOL nCol, SCROW nRow, const ScMarkData& rMark,
                                bool bMarked, bool bUnprotected ) const
{
    if (!ValidCol(nCol) || !ValidRow(nRow))
        return false;

    if (rDocument.HasAttrib(nCol, nRow, nTab, nCol, nRow, nTab, HasAttrFlags::Overlapped))
        // Skip an overlapped cell.
        return false;

    if (bMarked && !rMark.IsCellMarked(nCol,nRow))
        return false;

    /* TODO: for cursor movement *only* this should even take the protection
     * options (select locked, select unlocked) into account, see
     * ScTabView::SkipCursorHorizontal() and ScTabView::SkipCursorVertical(). */
    if (bUnprotected && rDocument.HasAttrib(nCol, nRow, nTab, nCol, nRow, nTab, HasAttrFlags::Protected))
        return false;

    if (bMarked || bUnprotected)        //TODO: also in other case ???
    {
        // Hidden cells must be skipped, as the cursor would end up on the next cell
        // even if it is protected or not marked.
        //TODO: control per Extra-Parameter, only for Cursor movement ???

        if (RowHidden(nRow))
            return false;

        if (ColHidden(nCol))
            return false;
    }

    return true;
}

// Skips the current cell if it is Hidden, Overlapped or Protected and Sheet is Protected
bool ScTable::SkipRow( const SCCOL nCol, SCROW& rRow, const SCROW nMovY,
        const ScMarkData& rMark, const bool bUp, const SCROW nUsedY,
        const bool bMarked, const bool bSheetProtected ) const
{
    if ( !ValidRow( rRow ))
        return false;

    if (bSheetProtected && rDocument.HasAttrib( nCol, rRow, nTab, nCol, rRow, nTab, HasAttrFlags::Protected))
    {
        if ( rRow > nUsedY )
            rRow = (bUp ? nUsedY : rDocument.MaxRow() + nMovY);
        else
            rRow += nMovY;

        if (bMarked)
            rRow  = rMark.GetNextMarked( nCol, rRow, bUp );

        return true;
    }
    else
    {
        bool bRowHidden  = RowHidden( rRow );
        bool bOverlapped = rDocument.HasAttrib( nCol, rRow, nTab, nCol, rRow, nTab, HasAttrFlags::Overlapped );

        if ( bRowHidden || bOverlapped )
        {
            rRow += nMovY;
            if (bMarked)
                rRow = rMark.GetNextMarked( nCol, rRow, bUp );

            return true;
        }
    }

    return false;
}

void ScTable::GetNextPos( SCCOL& rCol, SCROW& rRow, SCCOL nMovX, SCROW nMovY,
        bool bMarked, bool bUnprotected, const ScMarkData& rMark, SCCOL nTabStartCol ) const
{
    // Ensure bMarked is set only if there is a mark.
    assert( !bMarked || rMark.IsMarked() || rMark.IsMultiMarked());

    const bool bSheetProtected = IsProtected();

    if ( bUnprotected && !bSheetProtected )     // Is sheet really protected?
        bUnprotected = false;

    SCCOL nCol = rCol + nMovX;
    SCROW nRow = rRow + nMovY;

    SCCOL nStartCol, nEndCol;
    SCROW nStartRow, nEndRow;
    if (bMarked)
    {
        ScRange aRange( ScAddress::UNINITIALIZED);
        if (rMark.IsMarked())
            aRange = rMark.GetMarkArea();
        else if (rMark.IsMultiMarked())
            aRange = rMark.GetMultiMarkArea();
        else
        {
            // Covered by assert() above, but for NDEBUG build.
            if (ValidColRow(nCol,nRow))
            {
                rCol = nCol;
                rRow = nRow;
            }
            return;
        }
        nStartCol = aRange.aStart.Col();
        nStartRow = aRange.aStart.Row();
        nEndCol = aRange.aEnd.Col();
        nEndRow = aRange.aEnd.Row();
    }
    else if (bUnprotected)
    {
        nStartCol = 0;
        nStartRow = 0;
        nEndCol = rCol;
        nEndRow = rRow;
        rDocument.GetPrintArea( nTab, nEndCol, nEndRow, true );
        // Add some cols/rows to the print area (which is "content or
        // visually different from empty") to enable travelling through
        // protected forms with empty cells and no visual indicator.
        // 42 might be good enough and not too much...
        nEndCol = std::min<SCCOL>( nEndCol+42, rDocument.MaxCol());
        nEndRow = std::min<SCROW>( nEndRow+42, rDocument.MaxRow());
    }
    else
    {
        // Invalid values show up for instance for Tab, when nothing is
        // selected and not protected (left / right edge), then leave values
        // unchanged.
        if (ValidColRow(nCol,nRow))
        {
            rCol = nCol;
            rRow = nRow;
        }

        // Caller ensures actually moving nMovY to jump to prev/next row's
        // start col.
        if (nTabStartCol != SC_TABSTART_NONE)
            rCol = nTabStartCol;

        return;
    }

    if ( nMovY && (bMarked || bUnprotected))
    {
        do
        {
            const bool bUp = (nMovY < 0);
            const SCCOL nColAdd = (bUp ? -1 : 1);

            if (bMarked)
                nRow = rMark.GetNextMarked( nCol, nRow, bUp );

            if (nTabStartCol != SC_TABSTART_NONE)
            {
                /* NOTE: If current rCol < nTabStartCol when going down, there
                 * is no way to detect if the previous Tab wrapped around to
                 * the next row or if it was a Shift+Tab going backwards. The
                 * result after a wrap is an odd jump to the next row's
                 * nTabStartCol, which is logical though and always has been
                 * the case. Similar for rCol > nTabStartCol when going up.
                 * Related, it would be nice to limit advancing the position
                 * within bounds even if another wrap would occur, but again we
                 * can't tell if previously Tab or Shift+Tab was used, so we
                 * don't know if it would be nTabStartCol to nEndCol (for Tab)
                 * or nStartCol to nTabStartCol (for Shift+Tab). */

                // Continue moving horizontally.
                nMovX = nColAdd;
                nCol = nTabStartCol;
                break;  // do
            }

            while ( SkipRow( nCol, nRow, nMovY, rMark, bUp, nEndRow, bMarked, bSheetProtected ))
                ;

            sal_uInt16 nWrap = 0;
            while ( nRow < nStartRow || nRow > nEndRow )
            {
                nCol += nColAdd;

                while (nStartCol <= nCol && nCol <= nEndCol && ValidCol(nCol) && ColHidden(nCol))
                    nCol += nColAdd;    //  skip hidden cols

                if (nCol < nStartCol)
                {
                    nCol = nEndCol;

                    if (++nWrap >= 2)
                        return;
                }
                else if (nCol > nEndCol)
                {
                    nCol = nStartCol;

                    if (++nWrap >= 2)
                        return;
                }
                if (nRow < nStartRow)
                    nRow = nEndRow;
                else if (nRow > nEndRow)
                    nRow = nStartRow;

                if (bMarked)
                    nRow = rMark.GetNextMarked( nCol, nRow, bUp );

                while ( SkipRow( nCol, nRow, nMovY, rMark, bUp, nEndRow, bMarked, bSheetProtected ))
                    ;
            }
        } while (false);
    }

    if ( nMovX && ( bMarked || bUnprotected ) )
    {
        // wrap initial skip counting:
        if (nCol < nStartCol)
        {
            nCol = nEndCol;
            --nRow;
            if (nRow < nStartRow)
                nRow = nEndRow;
        }
        if (nCol > nEndCol)
        {
            nCol = nStartCol;
            ++nRow;
            if (nRow > nEndRow)
                nRow = nStartRow;
        }

        if ( !ValidNextPos(nCol, nRow, rMark, bMarked, bUnprotected) )
        {
            const SCCOL nColCount = nEndCol - nStartCol + 1;
            std::unique_ptr<SCROW[]> pNextRows( new SCROW[nColCount]);
            const SCCOL nLastCol = aCol.size() - 1;
            const bool bUp = (nMovX < 0);   // Moving left also means moving up in rows.
            const SCROW nRowAdd = (bUp ? -1 : 1);
            sal_uInt16 nWrap = 0;

            if (bUp)
            {
                for (SCCOL i = 0; i < nColCount; ++i)
                    pNextRows[i] = (i + nStartCol > nCol) ? (nRow + nRowAdd) : nRow;
            }
            else
            {
                for (SCCOL i = 0; i < nColCount; ++i)
                    pNextRows[i] = (i + nStartCol < nCol) ? (nRow + nRowAdd) : nRow;
            }
            do
            {
                SCROW nNextRow = pNextRows[nCol - nStartCol] + nRowAdd;
                if ( bMarked )
                    nNextRow = rMark.GetNextMarked( nCol, nNextRow, bUp );
                if ( bUnprotected )
                    nNextRow = ( nCol <= nLastCol ) ? aCol[nCol].GetNextUnprotected( nNextRow, bUp ) :
                        aDefaultColData.GetNextUnprotected( nNextRow, bUp );
                pNextRows[nCol - nStartCol] = nNextRow;

                if (bUp)
                {
                    SCROW nMaxRow = nStartRow - 1;
                    for (SCCOL i = 0; i < nColCount; ++i)
                    {
                        if (pNextRows[i] >= nMaxRow)    // when two equal the right one
                        {
                            nMaxRow = pNextRows[i];
                            nCol = i + nStartCol;
                        }
                    }
                    nRow = nMaxRow;

                    if ( nRow < nStartRow )
                    {
                        if (++nWrap >= 2)
                            return;
                        nCol = nEndCol;
                        nRow = nEndRow;
                        for (SCCOL i = 0; i < nColCount; ++i)
                            pNextRows[i] = nEndRow;     // do it all over again
                    }
                }
                else
                {
                    SCROW nMinRow = nEndRow + 1;
                    for (SCCOL i = 0; i < nColCount; ++i)
                    {
                        if (pNextRows[i] < nMinRow)     // when two equal the left one
                        {
                            nMinRow = pNextRows[i];
                            nCol = i + nStartCol;
                        }
                    }
                    nRow = nMinRow;

                    if ( nRow > nEndRow )
                    {
                        if (++nWrap >= 2)
                            return;
                        nCol = nStartCol;
                        nRow = nStartRow;
                        for (SCCOL i = 0; i < nColCount; ++i)
                            pNextRows[i] = nStartRow;   // do it all over again
                    }
                }
            }
            while ( !ValidNextPos(nCol, nRow, rMark, bMarked, bUnprotected) );
        }
    }

    if (ValidColRow(nCol,nRow))
    {
        rCol = nCol;
        rRow = nRow;
    }
}

bool ScTable::GetNextMarkedCell( SCCOL& rCol, SCROW& rRow, const ScMarkData& rMark ) const
{
    ++rRow;                 // next row

    while ( rCol < aCol.size() )
    {
        ScMarkArray aArray( rMark.GetMarkArray( rCol ) );
        while ( rRow <= rDocument.MaxRow() )
        {
            SCROW nStart = aArray.GetNextMarked( rRow, false );
            if ( nStart <= rDocument.MaxRow() )
            {
                SCROW nEnd = aArray.GetMarkEnd( nStart, false );

                const sc::CellStoreType& rCells = aCol[rCol].maCells;
                std::pair<sc::CellStoreType::const_iterator,size_t> aPos = rCells.position(nStart);
                sc::CellStoreType::const_iterator it = aPos.first;
                SCROW nTestRow = nStart;
                if (it->type == sc::element_type_empty)
                {
                    // Skip the empty block.
                    nTestRow += it->size - aPos.second;
                    ++it;
                    if (it == rCells.end())
                    {
                        // No more block.  Move on to the next column.
                        rRow = rDocument.MaxRow() + 1;
                        continue;
                    }
                }

                if (nTestRow <= nEnd)
                {
                    // Cell found.
                    rRow = nTestRow;
                    return true;
                }

                rRow = nEnd + 1;                // Search for next selected range
            }
            else
                rRow = rDocument.MaxRow() + 1;              // End of column
        }
        rRow = 0;
        ++rCol;                                 // test next column
    }

    // Though searched only the allocated columns, it is equivalent to a search till rDocument.MaxCol().
    rCol = rDocument.MaxCol() + 1;
    return false;                               // Through all columns
}

void ScTable::UpdateDrawRef( UpdateRefMode eUpdateRefMode, SCCOL nCol1, SCROW nRow1, SCTAB nTab1,
                                    SCCOL nCol2, SCROW nRow2, SCTAB nTab2,
                                    SCCOL nDx, SCROW nDy, SCTAB nDz, bool bUpdateNoteCaptionPos )
{
    if ( !(nTab >= nTab1 && nTab <= nTab2 && nDz == 0) )       // only within the table
        return;

    ScDrawLayer* pDrawLayer = rDocument.GetDrawLayer();
    if ( eUpdateRefMode != URM_COPY && pDrawLayer )
    {
        if ( eUpdateRefMode == URM_MOVE )
        {                                               // source range
            nCol1 = sal::static_int_cast<SCCOL>( nCol1 - nDx );
            nRow1 = sal::static_int_cast<SCROW>( nRow1 - nDy );
            nCol2 = sal::static_int_cast<SCCOL>( nCol2 - nDx );
            nRow2 = sal::static_int_cast<SCROW>( nRow2 - nDy );
        }
        pDrawLayer->MoveArea( nTab, nCol1,nRow1, nCol2,nRow2, nDx,nDy,
                                (eUpdateRefMode == URM_INSDEL), bUpdateNoteCaptionPos );
    }
}

void ScTable::UpdateReference(
    sc::RefUpdateContext& rCxt, ScDocument* pUndoDoc, bool bIncludeDraw, bool bUpdateNoteCaptionPos )
{
    bool bUpdated = false;
    UpdateRefMode eUpdateRefMode = rCxt.meMode;
    SCCOL nDx = rCxt.mnColDelta;
    SCROW nDy = rCxt.mnRowDelta;
    SCTAB nDz = rCxt.mnTabDelta;
    SCCOL nCol1 = rCxt.maRange.aStart.Col(), nCol2 = rCxt.maRange.aEnd.Col();
    SCROW nRow1 = rCxt.maRange.aStart.Row(), nRow2 = rCxt.maRange.aEnd.Row();
    SCTAB nTab1 = rCxt.maRange.aStart.Tab(), nTab2 = rCxt.maRange.aEnd.Tab();

    // Named expressions need to be updated before formulas accessing them.
    if (mpRangeName)
        mpRangeName->UpdateReference(rCxt, nTab);

    if (rCxt.meMode == URM_COPY )
    {
        for( SCCOL col : GetAllocatedColumnsRange( rCxt.maRange.aStart.Col(), rCxt.maRange.aEnd.Col()))
            bUpdated |= aCol[col].UpdateReference(rCxt, pUndoDoc);
    }
    else
    {
        for (SCCOL col : GetAllocatedColumnsRange(0, rDocument.MaxCol()))
            bUpdated |= aCol[col].UpdateReference(rCxt, pUndoDoc);
        // When deleting row(s), delete same row from the default attribute
        if (nDy < 0)
            aDefaultColData.DeleteRow(nRow1+nDy, -nDy);
    }

    if ( bIncludeDraw )
        UpdateDrawRef( eUpdateRefMode, nCol1, nRow1, nTab1, nCol2, nRow2, nTab2, nDx, nDy, nDz, bUpdateNoteCaptionPos );

    if ( nTab >= nTab1 && nTab <= nTab2 && nDz == 0 )       // print ranges: only within the table
    {
        SCTAB nSTab = nTab;
        SCTAB nETab = nTab;
        SCCOL nSCol = 0;
        SCROW nSRow = 0;
        SCCOL nECol = 0;
        SCROW nERow = 0;
        bool bRecalcPages = false;

        for ( auto& rPrintRange : aPrintRanges )
        {
            nSCol = rPrintRange.aStart.Col();
            nSRow = rPrintRange.aStart.Row();
            nECol = rPrintRange.aEnd.Col();
            nERow = rPrintRange.aEnd.Row();

            // do not try to modify sheet index of print range
            if ( ScRefUpdate::Update( rDocument, eUpdateRefMode,
                                      nCol1,nRow1,nTab, nCol2,nRow2,nTab,
                                      nDx,nDy,0,
                                      nSCol,nSRow,nSTab, nECol,nERow,nETab ) )
            {
                rPrintRange = ScRange( nSCol, nSRow, 0, nECol, nERow, 0 );
                bRecalcPages = true;
            }
        }

        if ( moRepeatColRange )
        {
            nSCol = moRepeatColRange->aStart.Col();
            nSRow = moRepeatColRange->aStart.Row();
            nECol = moRepeatColRange->aEnd.Col();
            nERow = moRepeatColRange->aEnd.Row();

            // do not try to modify sheet index of repeat range
            if ( ScRefUpdate::Update( rDocument, eUpdateRefMode,
                                      nCol1,nRow1,nTab, nCol2,nRow2,nTab,
                                      nDx,nDy,0,
                                      nSCol,nSRow,nSTab, nECol,nERow,nETab ) )
            {
                *moRepeatColRange = ScRange( nSCol, nSRow, 0, nECol, nERow, 0 );
                bRecalcPages = true;
                nRepeatStartX = nSCol;  // for UpdatePageBreaks
                nRepeatEndX = nECol;
            }
        }

        if ( moRepeatRowRange )
        {
            nSCol = moRepeatRowRange->aStart.Col();
            nSRow = moRepeatRowRange->aStart.Row();
            nECol = moRepeatRowRange->aEnd.Col();
            nERow = moRepeatRowRange->aEnd.Row();

            // do not try to modify sheet index of repeat range
            if ( ScRefUpdate::Update( rDocument, eUpdateRefMode,
                                      nCol1,nRow1,nTab, nCol2,nRow2,nTab,
                                      nDx,nDy,0,
                                      nSCol,nSRow,nSTab, nECol,nERow,nETab ) )
            {
                *moRepeatRowRange = ScRange( nSCol, nSRow, 0, nECol, nERow, 0 );
                bRecalcPages = true;
                nRepeatStartY = nSRow;  // for UpdatePageBreaks
                nRepeatEndY = nERow;
            }
        }

        //  updating print ranges is not necessary with multiple print ranges
        if ( bRecalcPages && GetPrintRangeCount() <= 1 )
        {
            UpdatePageBreaks(nullptr);

            rDocument.RepaintRange( ScRange(0,0,nTab,rDocument.MaxCol(),rDocument.MaxRow(),nTab) );
        }
    }

    if (bUpdated)
        SetStreamValid(false);

    if(mpCondFormatList)
        mpCondFormatList->UpdateReference(rCxt);

    if (pTabProtection)
        pTabProtection->updateReference( eUpdateRefMode, rDocument, rCxt.maRange, nDx, nDy, nDz);
}

void ScTable::UpdateTranspose( const ScRange& rSource, const ScAddress& rDest,
                                    ScDocument* pUndoDoc )
{
    for (auto const & rpCol : aCol)
        rpCol->UpdateTranspose( rSource, rDest, pUndoDoc );
}

void ScTable::UpdateGrow( const ScRange& rArea, SCCOL nGrowX, SCROW nGrowY )
{
    for (auto const & rpCol : aCol)
        rpCol->UpdateGrow( rArea, nGrowX, nGrowY );
}

void ScTable::UpdateInsertTab( sc::RefUpdateInsertTabContext& rCxt )
{
    // Store the old tab number in sc::UpdatedRangeNames for
    // ScTokenArray::AdjustReferenceOnInsertedTab() to check with
    // isNameModified()
    if (mpRangeName)
        mpRangeName->UpdateInsertTab(rCxt, nTab);

    if (nTab >= rCxt.mnInsertPos)
    {
        nTab += rCxt.mnSheets;
        if (pDBDataNoName)
            pDBDataNoName->UpdateMoveTab(nTab - 1 ,nTab);
    }

    if (mpCondFormatList)
        mpCondFormatList->UpdateInsertTab(rCxt);

    if (pTabProtection)
        pTabProtection->updateReference( URM_INSDEL, rDocument,
                ScRange( 0, 0, rCxt.mnInsertPos, rDocument.MaxCol(), rDocument.MaxRow(), MAXTAB),
                0, 0, rCxt.mnSheets);

    for (SCCOL i=0; i < aCol.size(); i++)
        aCol[i].UpdateInsertTab(rCxt);

    SetStreamValid(false);
}

void ScTable::UpdateDeleteTab( sc::RefUpdateDeleteTabContext& rCxt )
{
    // Store the old tab number in sc::UpdatedRangeNames for
    // ScTokenArray::AdjustReferenceOnDeletedTab() to check with
    // isNameModified()
    if (mpRangeName)
        mpRangeName->UpdateDeleteTab(rCxt, nTab);

    if (nTab > rCxt.mnDeletePos)
    {
        nTab -= rCxt.mnSheets;
        if (pDBDataNoName)
            pDBDataNoName->UpdateMoveTab(nTab + 1,nTab);
    }

    if (mpCondFormatList)
        mpCondFormatList->UpdateDeleteTab(rCxt);

    if (pTabProtection)
        pTabProtection->updateReference( URM_INSDEL, rDocument,
                ScRange( 0, 0, rCxt.mnDeletePos, rDocument.MaxCol(), rDocument.MaxRow(), MAXTAB),
                0, 0, -rCxt.mnSheets);

    for (SCCOL i = 0; i < aCol.size(); ++i)
        aCol[i].UpdateDeleteTab(rCxt);

    SetStreamValid(false);
}

void ScTable::UpdateMoveTab(
    sc::RefUpdateMoveTabContext& rCxt, SCTAB nTabNo, ScProgress* pProgress )
{
    nTab = nTabNo;
    if (mpRangeName)
        mpRangeName->UpdateMoveTab(rCxt, nTab);

    if (pDBDataNoName)
        pDBDataNoName->UpdateMoveTab(rCxt.mnOldPos, rCxt.mnNewPos);

    if(mpCondFormatList)
        mpCondFormatList->UpdateMoveTab(rCxt);

    if (pTabProtection)
        pTabProtection->updateReference( URM_REORDER, rDocument,
                ScRange( 0, 0, rCxt.mnOldPos, rDocument.MaxCol(), rDocument.MaxRow(), MAXTAB),
                0, 0, rCxt.mnNewPos - rCxt.mnOldPos);

    for ( SCCOL i=0; i < aCol.size(); i++ )
    {
        aCol[i].UpdateMoveTab(rCxt, nTabNo);
        if (pProgress)
            pProgress->SetState(pProgress->GetState() + aCol[i].GetCodeCount());
    }

    SetStreamValid(false);
}

void ScTable::UpdateCompile( bool bForceIfNameInUse )
{
    for (SCCOL i=0; i < aCol.size(); i++)
    {
        aCol[i].UpdateCompile( bForceIfNameInUse );
    }
}

void ScTable::SetTabNo(SCTAB nNewTab)
{
    nTab = nNewTab;
    for (SCCOL i=0; i < aCol.size(); i++)
        aCol[i].SetTabNo(nNewTab);
}

void ScTable::FindRangeNamesInUse(SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
                               sc::UpdatedRangeNames& rIndexes) const
{
    for (SCCOL i = nCol1; i <= nCol2 && IsColValid( i ); i++)
        aCol[i].FindRangeNamesInUse(nRow1, nRow2, rIndexes);
}

void ScTable::ExtendPrintArea( OutputDevice* pDev,
                    SCCOL /* nStartCol */, SCROW nStartRow, SCCOL& rEndCol, SCROW nEndRow )
{
    if ( !mpColFlags || !pRowFlags )
    {
        OSL_FAIL("ExtendPrintArea: No ColInfo or RowInfo");
        return;
    }

    Point aPix1000 = pDev->LogicToPixel(Point(1000,1000), MapMode(MapUnit::MapTwip));
    double nPPTX = aPix1000.X() / 1000.0;
    double nPPTY = aPix1000.Y() / 1000.0;

    // First, mark those columns that we need to skip i.e. hidden and empty columns.

    ScFlatBoolColSegments aSkipCols(rDocument.MaxCol());
    aSkipCols.setFalse(0, rDocument.MaxCol());
    for (SCCOL i = 0; i <= rDocument.MaxCol(); ++i)
    {
        SCCOL nLastCol = i;
        if (ColHidden(i, nullptr, &nLastCol))
        {
            // Columns are hidden in this range.
            aSkipCols.setTrue(i, nLastCol);
        }
        else
        {
            // These columns are visible.  Check for empty columns.
            SCCOL nEmptyCount = 0;
            SCCOL j = i;
            for (; j <= nLastCol; ++j)
            {
                if ( j >= aCol.size() )
                    break;
                if (aCol[j].IsCellCountZero()) // empty
                    nEmptyCount++;
            }
            if (nEmptyCount)
                aSkipCols.setTrue(i,i+nEmptyCount);
            if ( j >= aCol.size() )
                aSkipCols.setTrue( j, rDocument.MaxCol() );
        }
        i = nLastCol;
    }

    ScFlatBoolColSegments::RangeData aColData;
    for (SCCOL nCol = rEndCol; nCol >= 0; --nCol)
    {
        if (!aSkipCols.getRangeData(nCol, aColData))
            // Failed to get the data.  This should never happen!
            return;

        if (aColData.mbValue)
        {
            // Skip these columns.
            nCol = aColData.mnCol1; // move toward 0.
            continue;
        }

        // These are visible and non-empty columns.
        for (SCCOL nDataCol = nCol; 0 <= nDataCol && nDataCol >= aColData.mnCol1; --nDataCol)
        {
            SCCOL nPrintCol = nDataCol;
            VisibleDataCellIterator aIter(rDocument, *maFilterData.mpHiddenRows, aCol[nDataCol]);
            ScRefCellValue aCell = aIter.reset(nStartRow);
            if (aCell.isEmpty())
                // No visible cells found in this column.  Skip it.
                continue;

            while (!aCell.isEmpty())
            {
                SCCOL nNewCol = nDataCol;
                SCROW nRow = aIter.getRow();
                if (nRow > nEndRow)
                    // Went past the last row position.  Bail out.
                    break;

                MaybeAddExtraColumn(nNewCol, nRow, pDev, nPPTX, nPPTY);
                if (nNewCol > nPrintCol)
                    nPrintCol = nNewCol;
                aCell = aIter.next();
            }

            if (nPrintCol > rEndCol)
                // Make sure we don't shrink the print area.
                rEndCol = nPrintCol;
        }
        nCol = aColData.mnCol1; // move toward 0.
    }
}

void ScTable::MaybeAddExtraColumn(SCCOL& rCol, SCROW nRow, OutputDevice* pDev, double nPPTX, double nPPTY)
{
    // tdf#128873 we do not need to calculate text width (heavy operation)
    // when we for sure know that an additional column will not be added
    if (GetAllocatedColumnsCount() > rCol + 1)
    {
        ScRefCellValue aNextCell = aCol[rCol + 1].GetCellValue(nRow);
        if (!aNextCell.isEmpty())
        {
            // return rCol as is
            return;
        }
    }

    ScColumn& rColumn = aCol[rCol];
    ScRefCellValue aCell = rColumn.GetCellValue(nRow);
    if (!aCell.hasString())
        return;

    tools::Long nPixel = rColumn.GetTextWidth(nRow);

    // Width already calculated in Idle-Handler ?
    if ( TEXTWIDTH_DIRTY == nPixel )
    {
        ScNeededSizeOptions aOptions;
        aOptions.bTotalSize  = true;
        aOptions.bFormula    = false; //TODO: pass as parameter
        aOptions.bSkipMerged = false;

        Fraction aZoom(1,1);
        nPixel = rColumn.GetNeededSize(
            nRow, pDev, nPPTX, nPPTY, aZoom, aZoom, true, aOptions, nullptr );

        rColumn.SetTextWidth(nRow, static_cast<sal_uInt16>(nPixel));
    }

    tools::Long nTwips = static_cast<tools::Long>(nPixel / nPPTX);
    tools::Long nDocW = GetColWidth( rCol );

    tools::Long nMissing = nTwips - nDocW;
    if ( nMissing > 0 )
    {
        //  look at alignment

        const ScPatternAttr* pPattern = GetPattern( rCol, nRow );
        const SfxItemSet* pCondSet = rDocument.GetCondResult( rCol, nRow, nTab );

        SvxCellHorJustify eHorJust =
                        pPattern->GetItem( ATTR_HOR_JUSTIFY, pCondSet ).GetValue();
        if ( eHorJust == SvxCellHorJustify::Center )
            nMissing /= 2;                          // distributed into both directions
        else
        {
            // STANDARD is LEFT (only text is handled here)
            bool bRight = ( eHorJust == SvxCellHorJustify::Right );
            if ( IsLayoutRTL() )
                bRight = !bRight;
            if ( bRight )
                nMissing = 0;       // extended only to the left (logical)
        }
    }

    SCCOL nNewCol = rCol;
    while (nMissing > 0 && nNewCol < rDocument.MaxCol())
    {
        auto nNextCol = nNewCol + 1;
        bool bNextEmpty = true;
        if (GetAllocatedColumnsCount() > nNextCol)
        {
            ScRefCellValue aNextCell = aCol[nNextCol].GetCellValue(nRow);
            bNextEmpty = aNextCell.isEmpty();
        }
        if (!bNextEmpty)
        {
            // Cell content in a next column ends display of this string.
            nMissing = 0;
        }
        else
            nMissing -= GetColWidth(++nNewCol);
    }
    rCol = nNewCol;
}

namespace {

class SetTableIndex
{
    SCTAB mnTab;
public:
    explicit SetTableIndex(SCTAB nTab) : mnTab(nTab) {}

    void operator() (ScRange& rRange) const
    {
        rRange.aStart.SetTab(mnTab);
        rRange.aEnd.SetTab(mnTab);
    }
};

}

void ScTable::CopyPrintRange(const ScTable& rTable)
{
    // The table index shouldn't be used when the print range is used, but
    // just in case set the correct table index.

    aPrintRanges = rTable.aPrintRanges;
    ::std::for_each(aPrintRanges.begin(), aPrintRanges.end(), SetTableIndex(nTab));

    bPrintEntireSheet = rTable.bPrintEntireSheet;

    moRepeatColRange.reset();
    if (rTable.moRepeatColRange)
    {
        moRepeatColRange.emplace(*rTable.moRepeatColRange);
        moRepeatColRange->aStart.SetTab(nTab);
        moRepeatColRange->aEnd.SetTab(nTab);
    }

    moRepeatRowRange.reset();
    if (rTable.moRepeatRowRange)
    {
        moRepeatRowRange.emplace(*rTable.moRepeatRowRange);
        moRepeatRowRange->aStart.SetTab(nTab);
        moRepeatRowRange->aEnd.SetTab(nTab);
    }
}

void ScTable::SetRepeatColRange( std::optional<ScRange> oNew )
{
    moRepeatColRange = std::move(oNew);

    SetStreamValid(false);

    InvalidatePageBreaks();
}

void ScTable::SetRepeatRowRange( std::optional<ScRange> oNew )
{
    moRepeatRowRange = std::move(oNew);

    SetStreamValid(false);

    InvalidatePageBreaks();
}

void ScTable::ClearPrintRanges()
{
    aPrintRanges.clear();
    bPrintEntireSheet = false;

    SetStreamValid(false);

    InvalidatePageBreaks();     // #i117952# forget page breaks for an old print range
}

void ScTable::ClearPrintNamedRanges()
{
    // tdf#100034 Clearing print ranges also requires to remove all print named ranges
    // Iterate over all named ranges to determine which are print areas to be removed
    if (mpRangeName)
    {
        std::vector<ScRangeData*> aRangesToRemove;
        for (auto it = mpRangeName->begin(); it != mpRangeName->end(); it++)
        {
            ScRangeData* pData = it->second.get();
            if (pData->HasType(ScRangeData::Type::PrintArea))
                aRangesToRemove.push_back(pData);
        }

        // Effectively remove all named ranges that refer to print ranges
        for (auto pItem : aRangesToRemove)
            mpRangeName->erase(*pItem);
    }
}

void ScTable::AddPrintRange( const ScRange& rNew )
{
    bPrintEntireSheet = false;
    if( aPrintRanges.size() < 0xFFFF )
        aPrintRanges.push_back( rNew );

    SetStreamValid(false);

    InvalidatePageBreaks();
}

void ScTable::SetPrintEntireSheet()
{
    if( !IsPrintEntireSheet() )
    {
        ClearPrintRanges();
        bPrintEntireSheet = true;
    }
}

const ScRange* ScTable::GetPrintRange(sal_uInt16 nPos) const
{
    return (nPos < GetPrintRangeCount()) ? &aPrintRanges[ nPos ] : nullptr;
}

void ScTable::FillPrintSaver( ScPrintSaverTab& rSaveTab ) const
{
    rSaveTab.SetAreas( std::vector(aPrintRanges), bPrintEntireSheet );
    rSaveTab.SetRepeat( moRepeatColRange, moRepeatRowRange );
}

void ScTable::RestorePrintRanges( const ScPrintSaverTab& rSaveTab )
{
    aPrintRanges = rSaveTab.GetPrintRanges();
    bPrintEntireSheet = rSaveTab.IsEntireSheet();
    SetRepeatColRange( rSaveTab.GetRepeatCol() );
    SetRepeatRowRange( rSaveTab.GetRepeatRow() );

    InvalidatePageBreaks();     // #i117952# forget page breaks for an old print range
    UpdatePageBreaks(nullptr);
}

ScTable::VisibleDataCellIterator::VisibleDataCellIterator(const ScDocument& rDoc, ScFlatBoolRowSegments& rRowSegs, ScColumn& rColumn) :
    mrDocument(rDoc),
    mrRowSegs(rRowSegs),
    mrColumn(rColumn),
    mnCurRow(ROW_NOT_FOUND),
    mnUBound(ROW_NOT_FOUND)
{
}

ScRefCellValue ScTable::VisibleDataCellIterator::reset(SCROW nRow)
{
    if (nRow > mrDocument.MaxRow())
    {
        mnCurRow = ROW_NOT_FOUND;
        return ScRefCellValue();
    }

    ScFlatBoolRowSegments::RangeData aData;
    if (!mrRowSegs.getRangeData(nRow, aData))
    {
        mnCurRow = ROW_NOT_FOUND;
        return ScRefCellValue();
    }

    if (!aData.mbValue)
    {
        // specified row is visible.  Take it.
        mnCurRow = nRow;
        mnUBound = aData.mnRow2;
    }
    else
    {
        // specified row is not-visible.  The first visible row is the start of
        // the next segment.
        mnCurRow = aData.mnRow2 + 1;
        mnUBound = mnCurRow; // get range data on the next iteration.
        if (mnCurRow > mrDocument.MaxRow())
        {
            // Make sure the row doesn't exceed our current limit.
            mnCurRow = ROW_NOT_FOUND;
            return ScRefCellValue();
        }
    }

    maCell = mrColumn.GetCellValue(mnCurRow);
    if (!maCell.isEmpty())
        // First visible cell found.
        return maCell;

    // Find a first visible cell below this row (if any).
    return next();
}

ScRefCellValue ScTable::VisibleDataCellIterator::next()
{
    if (mnCurRow == ROW_NOT_FOUND)
        return ScRefCellValue();

    while (mrColumn.GetNextDataPos(mnCurRow))
    {
        if (mnCurRow > mnUBound)
        {
            // We don't know the visibility of this row range.  Query it.
            ScFlatBoolRowSegments::RangeData aData;
            if (!mrRowSegs.getRangeData(mnCurRow, aData))
            {
                mnCurRow = ROW_NOT_FOUND;
                return ScRefCellValue();
            }

            if (aData.mbValue)
            {
                // This row is invisible.  Skip to the last invisible row and
                // try again.
                mnCurRow = mnUBound = aData.mnRow2;
                continue;
            }

            // This row is visible.
            mnUBound = aData.mnRow2;
        }

        maCell = mrColumn.GetCellValue(mnCurRow);
        if (!maCell.isEmpty())
            return maCell;
    }

    mnCurRow = ROW_NOT_FOUND;
    return ScRefCellValue();
}

void ScTable::SetAnonymousDBData(std::unique_ptr<ScDBData> pDBData)
{
    pDBDataNoName = std::move(pDBData);
}

sal_uInt32 ScTable::AddCondFormat( std::unique_ptr<ScConditionalFormat> pNew )
{
    if(!mpCondFormatList)
        mpCondFormatList.reset(new ScConditionalFormatList());

    sal_uInt32 nMax = mpCondFormatList->getMaxKey();

    pNew->SetKey(nMax+1);
    mpCondFormatList->InsertNew(std::move(pNew));

    return nMax + 1;
}

SvtScriptType ScTable::GetScriptType( SCCOL nCol, SCROW nRow ) const
{
    if ( !IsColValid( nCol ) )
        return SvtScriptType::NONE;

    return aCol[nCol].GetScriptType(nRow);
}

void ScTable::SetScriptType( SCCOL nCol, SCROW nRow, SvtScriptType nType )
{
    if (!ValidCol(nCol))
        return;

    aCol[nCol].SetScriptType(nRow, nType);
}

SvtScriptType ScTable::GetRangeScriptType(
    sc::ColumnBlockPosition& rBlockPos, SCCOL nCol, SCROW nRow1, SCROW nRow2 )
{
    if ( !IsColValid( nCol ) )
        return SvtScriptType::NONE;

    sc::CellStoreType::iterator itr = aCol[nCol].maCells.begin();
    return aCol[nCol].GetRangeScriptType(rBlockPos.miCellTextAttrPos, nRow1, nRow2, itr);
}

formula::FormulaTokenRef ScTable::ResolveStaticReference( SCCOL nCol, SCROW nRow )
{
    if ( !ValidCol( nCol ) || !ValidRow( nRow ) )
        return formula::FormulaTokenRef();
    if ( nCol >= aCol.size() )
        // Return a value of 0.0 if column not exists
        return formula::FormulaTokenRef(new formula::FormulaDoubleToken(0.0));
    return aCol[nCol].ResolveStaticReference(nRow);
}

formula::FormulaTokenRef ScTable::ResolveStaticReference( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 )
{
    if (nCol2 < nCol1 || nRow2 < nRow1)
        return formula::FormulaTokenRef();

    if ( !ValidCol( nCol1 ) || !ValidCol( nCol2 ) || !ValidRow( nRow1 ) || !ValidRow( nRow2 ) )
        return formula::FormulaTokenRef();

    SCCOL nMaxCol;
    if ( nCol2 >= aCol.size() )
        nMaxCol = aCol.size() - 1;
    else
        nMaxCol = nCol2;

    ScMatrixRef pMat(new ScMatrix(nCol2-nCol1+1, nRow2-nRow1+1, 0.0));
    for (SCCOL nCol = nCol1; nCol <= nMaxCol; ++nCol)
    {
        if (!aCol[nCol].ResolveStaticReference(*pMat, nCol2-nCol1, nRow1, nRow2))
            // Column contains non-static cell. Failed.
            return formula::FormulaTokenRef();
    }

    return formula::FormulaTokenRef(new ScMatrixToken(std::move(pMat)));
}

formula::VectorRefArray ScTable::FetchVectorRefArray( SCCOL nCol, SCROW nRow1, SCROW nRow2 )
{
    if (nRow2 < nRow1)
        return formula::VectorRefArray();

    if ( !IsColValid( nCol ) || !ValidRow( nRow1 ) || !ValidRow( nRow2 ) )
        return formula::VectorRefArray();

    return aCol[nCol].FetchVectorRefArray(nRow1, nRow2);
}

#ifdef DBG_UTIL
void ScTable::AssertNoInterpretNeeded( SCCOL nCol, SCROW nRow1, SCROW nRow2 )
{
    assert( nRow2 >= nRow1 );
    assert( IsColValid( nCol ) && ValidRow( nRow1 ) && ValidRow( nRow2 ) );
    return aCol[nCol].AssertNoInterpretNeeded(nRow1, nRow2);
}
#endif

bool ScTable::HandleRefArrayForParallelism( SCCOL nCol, SCROW nRow1, SCROW nRow2, const ScFormulaCellGroupRef& mxGroup, ScAddress* pDirtiedAddress )
{
    if (nRow2 < nRow1)
        return false;

    if ( !IsColValid( nCol ) || !ValidRow( nRow1 ) || !ValidRow( nRow2 ) )
        return false;

    maFilterData.makeReady();

    return aCol[nCol].HandleRefArrayForParallelism(nRow1, nRow2, mxGroup, pDirtiedAddress);
}

ScRefCellValue ScTable::GetRefCellValue( SCCOL nCol, SCROW nRow )
{
    if ( !IsColRowValid( nCol, nRow ) )
        return ScRefCellValue();

    return aCol[nCol].GetCellValue(nRow);
}

ScRefCellValue ScTable::GetRefCellValue( SCCOL nCol, SCROW nRow, sc::ColumnBlockPosition& rBlockPos )
{
    if ( !IsColRowValid( nCol, nRow ) )
        return ScRefCellValue();

    return aCol[nCol].GetCellValue(rBlockPos, nRow);
}

SvtBroadcaster* ScTable::GetBroadcaster( SCCOL nCol, SCROW nRow )
{
    if ( !IsColRowValid( nCol, nRow ) )
        return nullptr;

    return aCol[nCol].GetBroadcaster(nRow);
}

void ScTable::DeleteBroadcasters(
    sc::ColumnBlockPosition& rBlockPos, SCCOL nCol, SCROW nRow1, SCROW nRow2 )
{
    if ( !IsColValid( nCol ) )
        return;

    aCol[nCol].DeleteBroadcasters(rBlockPos, nRow1, nRow2);
}

void ScTable::DeleteEmptyBroadcasters()
{
    for( auto& col : aCol )
        col->DeleteEmptyBroadcasters();
}

void ScTable::FillMatrix( ScMatrix& rMat, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, svl::SharedStringPool* pPool ) const
{
    size_t nMatCol = 0;
    nCol2 = ClampToAllocatedColumns(nCol2);
    for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol, ++nMatCol)
        aCol[nCol].FillMatrix(rMat, nMatCol, nRow1, nRow2, pPool);
}

void ScTable::InterpretDirtyCells( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 )
{
    nCol2 = ClampToAllocatedColumns(nCol2);
    for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol)
        aCol[nCol].InterpretDirtyCells(nRow1, nRow2);
}

bool ScTable::InterpretCellsIfNeeded( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 )
{
    nCol2 = ClampToAllocatedColumns(nCol2);
    bool allInterpreted = true;
    for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol)
        if(!aCol[nCol].InterpretCellsIfNeeded(nRow1, nRow2))
            allInterpreted = false;
    return allInterpreted;
}

void ScTable::SetFormulaResults( SCCOL nCol, SCROW nRow, const double* pResults, size_t nLen )
{
    if (!ValidCol(nCol))
        return;

    aCol[nCol].SetFormulaResults(nRow, pResults, nLen);
}

void ScTable::CalculateInColumnInThread( ScInterpreterContext& rContext,
                                         SCCOL nColStart, SCCOL nColEnd,
                                         SCROW nRowStart, SCROW nRowEnd,
                                         unsigned nThisThread, unsigned nThreadsTotal)
{
    if (!ValidCol(nColStart) || !ValidCol(nColEnd))
        return;

    size_t nLen = nRowEnd - nRowStart + 1;
    size_t nOffset = 0;
    for (SCCOL nCurrCol = nColStart; nCurrCol <= nColEnd; ++nCurrCol)
    {
        aCol[nCurrCol].CalculateInThread( rContext, nRowStart, nLen, nOffset, nThisThread, nThreadsTotal );
        nOffset += nLen;
    }
}

void ScTable::HandleStuffAfterParallelCalculation( SCCOL nColStart, SCCOL nColEnd, SCROW nRow, size_t nLen,
                                                   ScInterpreter* pInterpreter)
{
    assert(ValidCol(nColStart) && ValidCol(nColEnd));

    for (SCCOL nCurrCol = nColStart; nCurrCol <= nColEnd; ++nCurrCol)
        aCol[nCurrCol].HandleStuffAfterParallelCalculation( nRow, nLen, pInterpreter );
}

#if DUMP_COLUMN_STORAGE
void ScTable::DumpColumnStorage( SCCOL nCol ) const
{
    if ( !IsColValid( nCol ) )
        return;

    aCol[nCol].DumpColumnStorage();
}
#endif

const SvtBroadcaster* ScTable::GetBroadcaster( SCCOL nCol, SCROW nRow ) const
{
    if ( !IsColRowValid( nCol, nRow ) )
        return nullptr;

    return aCol[nCol].GetBroadcaster(nRow);
}

void ScTable::DeleteConditionalFormat( sal_uLong nIndex )
{
    mpCondFormatList->erase(nIndex);
}

void ScTable::SetCondFormList( ScConditionalFormatList* pNew )
{
    mpCondFormatList.reset( pNew );
}

ScConditionalFormatList* ScTable::GetCondFormList()
{
    if(!mpCondFormatList)
        mpCondFormatList.reset( new ScConditionalFormatList() );

    return mpCondFormatList.get();
}

const ScConditionalFormatList* ScTable::GetCondFormList() const
{
    return mpCondFormatList.get();
}

ScColumnsRange ScTable::GetWritableColumnsRange(SCCOL nColBegin, SCCOL nColEnd)
{
    // because the range is inclusive, some code will pass nColEnd<nColBegin to indicate an empty range
    if (nColEnd < nColBegin)
        return ScColumnsRange(-1, -1);
    assert( nColEnd >= 0 && nColEnd <= GetDoc().MaxCol());
    CreateColumnIfNotExists(nColEnd);
    return GetColumnsRange(nColBegin, nColEnd);
}

ScColumnsRange ScTable::GetAllocatedColumnsRange(SCCOL nColBegin, SCCOL nColEnd) const
{
    if (nColBegin >= aCol.size())
        return ScColumnsRange(-1, -1);
    // clamp end of range to available columns
    if (nColEnd >= aCol.size())
        nColEnd = aCol.size() - 1;
    return GetColumnsRange(nColBegin, nColEnd);
}

ScColumnsRange ScTable::GetColumnsRange(SCCOL nColBegin, SCCOL nColEnd) const
{
    // because the range is inclusive, some code will pass nColEnd<nColBegin to indicate an empty range
    if (nColEnd < nColBegin)
        return ScColumnsRange(-1, -1);
    assert( nColBegin >= 0 && nColBegin <= GetDoc().MaxCol());
    assert( nColEnd >= 0 && nColEnd <= GetDoc().MaxCol());
    return ScColumnsRange(nColBegin, nColEnd + 1); // change inclusive end to past-end
}

// out-of-line the cold part of the CreateColumnIfNotExists function
void ScTable::CreateColumnIfNotExistsImpl( const SCCOL nScCol )
{
    // When doing multi-threaded load of, e.g. XLS files, we can hit this, which calls
    // into SfxItemPool::Put, in parallel with other code that calls into SfxItemPool::Put,
    // which is bad since that code is not thread-safe.
    SolarMutexGuard aGuard;
    const SCCOL aOldColSize = aCol.size();
    aCol.resize( rDocument.GetSheetLimits(), static_cast< size_t >( nScCol + 1 ) );
    for (SCCOL i = aOldColSize; i <= nScCol; i++)
        aCol[i].Init( i, nTab, rDocument, false );
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
