Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  column2.cxx   Sprache: C

 
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
/*
 * 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 <column.hxx>
#include <docsh.hxx>
#include <scitems.hxx>
#include <formulacell.hxx>
#include <document.hxx>
#include <drwlayer.hxx>
#include <attarray.hxx>
#include <patattr.hxx>
#include <cellform.hxx>
#include <editutil.hxx>
#include <subtotal.hxx>
#include <markdata.hxx>
#include <fillinfo.hxx>
#include <segmenttree.hxx>
#include <docparam.hxx>
#include <cellvalue.hxx>
#include <tokenarray.hxx>
#include <formulagroup.hxx>
#include <listenercontext.hxx>
#include <mtvcellfunc.hxx>
#include <progress.hxx>
#include <scmatrix.hxx>
#include <rowheightcontext.hxx>
#include <tokenstringcontext.hxx>
#include <sortparam.hxx>
#include <SparklineGroup.hxx>
#include <SparklineList.hxx>

#include <editeng/eeitem.hxx>
#include <o3tl/safeint.hxx>
#include <o3tl/unit_conversion.hxx>
#include <svx/algitem.hxx>
#include <editeng/editobj.hxx>
#include <editeng/editstat.hxx>
#include <editeng/emphasismarkitem.hxx>
#include <editeng/fhgtitem.hxx>
#include <svx/rotmodit.hxx>
#include <editeng/unolingu.hxx>
#include <editeng/justifyitem.hxx>
#include <svl/numformat.hxx>
#include <svl/zforlist.hxx>
#include <svl/broadcast.hxx>
#include <utility>
#include <vcl/outdev.hxx>
#include <formula/errorcodes.hxx>
#include <formula/vectortoken.hxx>

#include <algorithm>
#include <limits>
#include <memory>
#include <numeric>

// factor from font size to optimal cell height (text width)
#define SC_ROT_BREAK_FACTOR     6

static bool IsAmbiguousScript( SvtScriptType nScript )
{
    //TODO: move to a header file
    return ( nScript != SvtScriptType::LATIN &&
             nScript != SvtScriptType::ASIAN &&
             nScript != SvtScriptType::COMPLEX );
}

//  Data operations

tools::Long ScColumn::GetNeededSize(
    SCROW nRow, OutputDevice* pDev, double nPPTX, double nPPTY,
    const Fraction& rZoomX, const Fraction& rZoomY,
    bool bWidth, const ScNeededSizeOptions& rOptions,
    const ScPatternAttr** ppPatternChange, bool bInPrintTwips ) const
{
    // If bInPrintTwips is set, the size calculated should be in print twips,
    // else it should be in pixels.

    // Switch unit to MapTwip instead ? (temporarily and then revert before exit).
    if (bInPrintTwips)
        assert(pDev->GetMapMode().GetMapUnit() == MapUnit::MapTwip);

    std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(nRow);
    sc::CellStoreType::const_iterator it = aPos.first;
    if (it == maCells.end() || it->type == sc::element_type_empty)
        // Empty cell, or invalid row.
        return 0;

    tools::Long nValue = 0;
    ScRefCellValue aCell = GetCellValue(it, aPos.second);
    double nPPT = bWidth ? nPPTX : nPPTY;

    auto conditionalScaleFunc = [bInPrintTwips](tools::Long nMeasure, double fScale) {
        return bInPrintTwips ? nMeasure : static_cast<tools::Long>(nMeasure * fScale);
    };

    const ScPatternAttr* pPattern = rOptions.aPattern.getScPatternAttr();
    if (!pPattern)
        pPattern = pAttrArray->GetPattern( nRow );

    //      merged?
    //      Do not merge in conditional formatting

    const ScMergeAttr*      pMerge = &pPattern->GetItem(ATTR_MERGE);
    const ScMergeFlagAttr*  pFlag = &pPattern->GetItem(ATTR_MERGE_FLAG);

    if ( bWidth )
    {
        if ( pFlag->IsHorOverlapped() )
            return 0;
        if ( rOptions.bSkipMerged && pMerge->GetColMerge() > 1 )
            return 0;
    }
    else
    {
        if ( pFlag->IsVerOverlapped() )
            return 0;
        if ( rOptions.bSkipMerged && pMerge->GetRowMerge() > 1 )
            return 0;
    }

    //      conditional formatting
    ScDocument& rDocument = GetDoc();
    const SfxItemSet* pCondSet = rDocument.GetCondResult( nCol, nRow, nTab );

    //The pPattern may change in GetCondResult
    if (aCell.getType() == CELLTYPE_FORMULA)
    {
        pPattern = pAttrArray->GetPattern( nRow );
        if (ppPatternChange)
            *ppPatternChange = pPattern;
    }
    //  line break?

    const SvxHorJustifyItem* pCondItem;
    SvxCellHorJustify eHorJust;
    if (pCondSet && (pCondItem = pCondSet->GetItemIfSet(ATTR_HOR_JUSTIFY)) )
        eHorJust = pCondItem->GetValue();
    else
        eHorJust = pPattern->GetItem( ATTR_HOR_JUSTIFY ).GetValue();
    bool bBreak;
    const ScLineBreakCell* pLineBreakCell;
    if ( eHorJust == SvxCellHorJustify::Block )
        bBreak = true;
    else if ( pCondSet && (pLineBreakCell = pCondSet->GetItemIfSet(ATTR_LINEBREAK)) )
        bBreak = pLineBreakCell->GetValue();
    else
        bBreak = pPattern->GetItem(ATTR_LINEBREAK).GetValue();

    ScInterpreterContext& rContext = rDocument.GetNonThreadedContext();
    sal_uInt32 nFormat = pPattern->GetNumberFormat( rContext, pCondSet );

    // get "cell is value" flag
    // Must be synchronized with ScOutputData::LayoutStrings()
    bool bCellIsValue = (aCell.getType() == CELLTYPE_VALUE);
    if (aCell.getType() == CELLTYPE_FORMULA)
    {
        ScFormulaCell* pFCell = aCell.getFormula();
        bCellIsValue = pFCell->IsRunning();
        if (!bCellIsValue)
        {
            bCellIsValue = pFCell->IsValue();
            if (bCellIsValue)
            {
                // the pattern may change in IsValue()
                pPattern = pAttrArray->GetPattern( nRow );
                if (ppPatternChange)
                    *ppPatternChange = pPattern;
            }
        }
    }

    // #i111387#, tdf#121040: disable automatic line breaks for all number formats
    if (bBreak && bCellIsValue && (rContext.NFGetType(nFormat) == SvNumFormatType::NUMBER))
    {
        // If a formula cell needs to be interpreted during aCell.hasNumeric()
        // to determine the type, the pattern may get invalidated because the
        // result may set a number format. In which case there's also the
        // General format not set anymore...
        bool bMayInvalidatePattern = (aCell.getType() == CELLTYPE_FORMULA);
        const CellAttributeHolder aOldPattern(pPattern);
        bool bNumeric = aCell.hasNumeric();
        if (bMayInvalidatePattern)
        {
            pPattern = pAttrArray->GetPattern( nRow );
            if (ppPatternChange)
                *ppPatternChange = pPattern;    // XXX caller may have to check for change!
        }
        if (bNumeric)
        {
            if (!bMayInvalidatePattern || ScPatternAttr::areSame(pPattern, aOldPattern.getScPatternAttr()))
                bBreak = false;
            else
            {
                nFormat = pPattern->GetNumberFormat( rContext, pCondSet );
                if (rContext.NFGetType(nFormat) == SvNumFormatType::NUMBER)
                    bBreak = false;
            }
        }
    }

    //  get other attributes from pattern and conditional formatting

    SvxCellOrientation eOrient = pPattern->GetCellOrientation( pCondSet );
    bool bAsianVertical = ( eOrient == SvxCellOrientation::Stacked &&
            pPattern->GetItem( ATTR_VERTICAL_ASIAN, pCondSet ).GetValue() );
    if ( bAsianVertical )
        bBreak = false;

    if ( bWidth && bBreak )     // after determining bAsianVertical (bBreak may be reset)
        return 0;

    Degree100 nRotate(0);
    SvxRotateMode eRotMode = SVX_ROTATE_MODE_STANDARD;
    if ( eOrient == SvxCellOrientation::Standard )
    {
        const ScRotateValueItem* pRotateValueItem;
        if (pCondSet &&
                (pRotateValueItem = pCondSet->GetItemIfSet(ATTR_ROTATE_VALUE)) )
            nRotate = pRotateValueItem->GetValue();
        else
            nRotate = pPattern->GetItem(ATTR_ROTATE_VALUE).GetValue();
        if ( nRotate )
        {
            const SvxRotateModeItem* pRotateModeItem;
            if (pCondSet &&
                    (pRotateModeItem = pCondSet->GetItemIfSet(ATTR_ROTATE_MODE)) )
                eRotMode = pRotateModeItem->GetValue();
            else
                eRotMode = pPattern->GetItem(ATTR_ROTATE_MODE).GetValue();

            if ( nRotate == 18000_deg100 )
                eRotMode = SVX_ROTATE_MODE_STANDARD;    // no overflow
        }
    }

    if ( eHorJust == SvxCellHorJustify::Repeat )
    {
        // ignore orientation/rotation if "repeat" is active
        eOrient = SvxCellOrientation::Standard;
        nRotate = 0_deg100;
        bAsianVertical = false;
    }

    const SvxMarginItem* pMargin;
    if (pCondSet &&
            (pMargin = pCondSet->GetItemIfSet(ATTR_MARGIN)) )
        ;
    else
        pMargin = &pPattern->GetItem(ATTR_MARGIN);
    sal_uInt16 nIndent = 0;
    if ( eHorJust == SvxCellHorJustify::Left )
    {
        const ScIndentItem* pIndentItem;
        if (pCondSet &&
                (pIndentItem = pCondSet->GetItemIfSet(ATTR_INDENT)) )
            nIndent = pIndentItem->GetValue();
        else
            nIndent = pPattern->GetItem(ATTR_INDENT).GetValue();
    }

    SvtScriptType nScript = rDocument.GetScriptType(nCol, nRow, nTab);
    if (nScript == SvtScriptType::NONE) nScript = ScGlobal::GetDefaultScriptType();

    //  also call SetFont for edit cells, because bGetFont may be set only once
    //  bGetFont is set also if script type changes
    if (rOptions.bGetFont)
    {
        Fraction aFontZoom = ( eOrient == SvxCellOrientation::Standard ) ? rZoomX : rZoomY;
        vcl::Font aFont;
        aFont.SetKerning(FontKerning::NONE); // like ScDrawStringsVars::SetPattern
        // font color doesn't matter here
        pPattern->fillFontOnly(aFont, pDev, &aFontZoom, pCondSet, nScript);
        pDev->SetFont(aFont);
    }

    bool bAddMargin = true;
    CellType eCellType = aCell.getType();

    bool bEditEngine = (eCellType == CELLTYPE_EDIT ||
                        eOrient == SvxCellOrientation::Stacked ||
                        IsAmbiguousScript(nScript) ||
                        ((eCellType == CELLTYPE_FORMULA) && aCell.getFormula()->IsMultilineResult()));

    if (!bEditEngine)                                   // direct output
    {
        const Color* pColor;
        OUString aValStr = ScCellFormat::GetString(
            aCell, nFormat, &pColor, &rContext, rDocument, true, rOptions.bFormula);

        if (!aValStr.isEmpty())
        {
            //  SetFont is moved up

            tools::Long nWidth = 0;
            if ( eOrient != SvxCellOrientation::Standard )
            {
                tools::Long nHeight = pDev->GetTextHeight();
                // swap width and height
                nValue = bWidth ? nHeight : pDev->GetTextWidth( aValStr );
                nWidth = nHeight;
            }
            else if ( nRotate )
            {
                //TODO: take different X/Y scaling into consideration

                // avoid calling the expensive GetTextWidth when not needed
                auto TextWidth = [&, w = std::optional<tools::Long>()]() mutable
                {
                    if (!w)
                        w = pDev->GetTextWidth(aValStr);
                    return *w;
                };
                auto TextHeight = [&, h = std::optional<tools::Long>()]() mutable
                {
                    if (!h)
                        h = pDev->GetTextHeight();
                    return *h;
                };
                double nRealOrient = toRadians(nRotate);
                double nCosAbs = fabs( cos( nRealOrient ) );
                double nSinAbs = fabs( sin( nRealOrient ) );
                if ( eRotMode == SVX_ROTATE_MODE_STANDARD )
                    nWidth  = static_cast<tools::Long>( TextWidth() * nCosAbs + TextHeight() * nSinAbs );
                else if ( rOptions.bTotalSize )
                {
                    nWidth = conditionalScaleFunc(rDocument.GetColWidth( nCol,nTab ), nPPT);
                    bAddMargin = false;
                    //  only to the right:
                    //TODO: differ on direction up/down (only Text/whole height)
                    if ( pPattern->GetRotateDir( pCondSet ) == ScRotateDir::Right )
                        nWidth += static_cast<tools::Long>( rDocument.GetRowHeight( nRow,nTab ) *
                                            (bInPrintTwips ? 1.0 : nPPT) * nCosAbs / nSinAbs );
                }
                else
                    nWidth  = static_cast<tools::Long>( TextHeight() / nSinAbs );   //TODO: limit?

                if (bWidth)
                    nValue = nWidth;
                else
                {
                    tools::Long nHeight = static_cast<tools::Long>( TextHeight() * nCosAbs + TextWidth() * nSinAbs );
                    if ( bBreak && !rOptions.bTotalSize )
                    {
                        //  limit size for line break
                        tools::Long nCmp = pDev->GetFont().GetFontSize().Height() * SC_ROT_BREAK_FACTOR;
                        if ( nHeight > nCmp )
                            nHeight = nCmp;
                    }
                    nValue = nHeight;
                }
            }
            else if (bBreak && !bWidth)
            {
                nWidth = pDev->GetTextWidth(aValStr);
                nValue = pDev->GetTextHeight();
            }
            else
                // in the common case (height), avoid calling the expensive GetTextWidth
                nValue = bWidth ? pDev->GetTextWidth( aValStr ) : pDev->GetTextHeight();

            if ( bAddMargin )
            {
                if (bWidth)
                {
                    nValue += conditionalScaleFunc(pMargin->GetLeftMargin(), nPPT) +
                              conditionalScaleFunc(pMargin->GetRightMargin(), nPPT);
                    if ( nIndent )
                        nValue += conditionalScaleFunc(nIndent, nPPT);
                }
                else
                    nValue += conditionalScaleFunc(pMargin->GetTopMargin(), nPPT) +
                              conditionalScaleFunc(pMargin->GetBottomMargin(), nPPT);
            }

            //  linebreak done ?

            if ( bBreak && !bWidth )
            {
                //  test with EditEngine the safety at 90%
                //  (due to rounding errors and because EditEngine formats partially differently)

                tools::Long nDocSize = conditionalScaleFunc((rDocument.GetColWidth( nCol,nTab ) -
                                    pMargin->GetLeftMargin() - pMargin->GetRightMargin() -
                                    nIndent), nPPTX);
                nDocSize = (nDocSize * 9) / 10;           // for safety
                if (nWidth > nDocSize)
                    bEditEngine = true;
            }
        }
    }

    if (bEditEngine)
    {
        //  the font is not reset each time with !bEditEngine
        vcl::Font aOldFont = pDev->GetFont();

        MapMode aHMMMode( MapUnit::Map100thMM, Point(), rZoomX, rZoomY );

        // save in document ?
        std::unique_ptr<ScFieldEditEngine> pEngine = rDocument.CreateFieldEditEngine();

        const bool bPrevUpdateLayout = pEngine->SetUpdateLayout( false );
        bool bTextWysiwyg = ( pDev->GetOutDevType() == OUTDEV_PRINTER );
        MapMode aOld = pDev->GetMapMode();
        pDev->SetMapMode( aHMMMode );
        pEngine->SetRefDevice( pDev );
        rDocument.ApplyAsianEditSettings( *pEngine );
        SfxItemSet aSet(pEngine->GetEmptyItemSet());
        if ( ScStyleSheet* pPreviewStyle = rDocument.GetPreviewCellStyle( nCol, nRow, nTab ) )
        {
            ScPatternAttr aPreviewPattern( *pPattern );
            aPreviewPattern.SetStyleSheet(pPreviewStyle);
            aPreviewPattern.FillEditItemSet(&aSet, pCondSet);
        }
        else
        {
            SfxItemSet* pFontSet = rDocument.GetPreviewFont( nCol, nRow, nTab );
            pPattern->FillEditItemSet(&aSet, pFontSet ? pFontSet : pCondSet);
        }
//          no longer needed, are set with the text (is faster)
//          pEngine->SetDefaults( pSet );

        if ( aSet.Get(EE_PARA_HYPHENATE).GetValue() ) {

            css::uno::Reference<css::linguistic2::XHyphenator> xXHyphenator( LinguMgr::GetHyphenator() );
            pEngine->SetHyphenator( xXHyphenator );
        }

        Size aPaper( 1000000, 1000000 );
        if ( eOrient==SvxCellOrientation::Stacked && !bAsianVertical )
            aPaper.setWidth( 1 );
        else if (bBreak)
        {
            double fWidthFactor = bInPrintTwips ? 1.0 : nPPTX;
            if ( bTextWysiwyg )
            {
                //  if text is formatted for printer, don't use PixelToLogic,
                //  to ensure the exact same paper width (and same line breaks) as in
                //  ScEditUtil::GetEditArea, used for output.

                fWidthFactor = o3tl::convert(1.0, o3tl::Length::twip, o3tl::Length::mm100);
            }

            // use original width for hidden columns:
            tools::Long nDocWidth = static_cast<tools::Long>( rDocument.GetOriginalWidth(nCol,nTab) * fWidthFactor );
            SCCOL nColMerge = pMerge->GetColMerge();
            if (nColMerge > 1)
                for (SCCOL nColAdd=1; nColAdd<nColMerge; nColAdd++)
                    nDocWidth += static_cast<tools::Long>( rDocument.GetColWidth(nCol+nColAdd,nTab) * fWidthFactor );
            nDocWidth -= static_cast<tools::Long>( pMargin->GetLeftMargin() * fWidthFactor )
                       + static_cast<tools::Long>( pMargin->GetRightMargin() * fWidthFactor )
                       + 1;     // output size is width-1 pixel (due to gridline)
            if ( nIndent )
                nDocWidth -= static_cast<tools::Long>( nIndent * fWidthFactor );

            // space for AutoFilter button:  20 * nZoom/100
            constexpr tools::Long nFilterButtonWidthPix = 20; // Autofilter pixel width at 100% zoom.
            if ( pFlag->HasAutoFilter() && !bTextWysiwyg )
                nDocWidth -= bInPrintTwips ? o3tl::convert(nFilterButtonWidthPix, o3tl::Length::px,
                                                           o3tl::Length::twip)
                                           : tools::Long(rZoomX * nFilterButtonWidthPix);

            aPaper.setWidth( nDocWidth );

            if ( !bTextWysiwyg )
            {
                aPaper = bInPrintTwips ?
                        o3tl::convert(aPaper, o3tl::Length::twip, o3tl::Length::mm100) :
                        pDev->PixelToLogic(aPaper, aHMMMode);
            }
        }
        pEngine->SetPaperSize(aPaper);

        if (aCell.getType() == CELLTYPE_EDIT)
        {
            pEngine->SetTextNewDefaults(*aCell.getEditText(), std::move(aSet));
        }
        else
        {
            const Color* pColor;
            OUString aString = ScCellFormat::GetString(
                aCell, nFormat, &pColor, &rContext, rDocument, true,
                rOptions.bFormula);

            if (!aString.isEmpty())
                pEngine->SetTextNewDefaults(aString, std::move(aSet));
            else
                pEngine->SetDefaults(std::move(aSet));
        }

        bool bEngineVertical = pEngine->IsEffectivelyVertical();
        pEngine->SetVertical( bAsianVertical );
        pEngine->SetUpdateLayout( bPrevUpdateLayout );

        bool bEdWidth = bWidth;
        if ( eOrient != SvxCellOrientation::Standard && eOrient != SvxCellOrientation::Stacked )
            bEdWidth = !bEdWidth;
        if ( nRotate )
        {
            //TODO: take different X/Y scaling into consideration

            Size aSize( pEngine->CalcTextWidth(), pEngine->GetTextHeight() );
            double nRealOrient = toRadians(nRotate);
            double nCosAbs = fabs( cos( nRealOrient ) );
            double nSinAbs = fabs( sin( nRealOrient ) );
            tools::Long nHeight = static_cast<tools::Long>( aSize.Height() * nCosAbs + aSize.Width() * nSinAbs );
            tools::Long nWidth;
            if ( eRotMode == SVX_ROTATE_MODE_STANDARD )
                nWidth  = static_cast<tools::Long>( aSize.Width() * nCosAbs + aSize.Height() * nSinAbs );
            else if ( rOptions.bTotalSize )
            {
                nWidth = conditionalScaleFunc(rDocument.GetColWidth( nCol,nTab ), nPPT);
                bAddMargin = false;
                if ( pPattern->GetRotateDir( pCondSet ) == ScRotateDir::Right )
                    nWidth += static_cast<tools::Long>( rDocument.GetRowHeight( nRow,nTab ) *
                                        (bInPrintTwips ? 1.0 : nPPT) * nCosAbs / nSinAbs );
            }
            else
                nWidth  = static_cast<tools::Long>( aSize.Height() / nSinAbs );   //TODO: limit?
            aSize = Size( nWidth, nHeight );

            Size aTextSize = bInPrintTwips ?
                    o3tl::toTwips(aSize, o3tl::Length::mm100) :
                    pDev->LogicToPixel(aSize, aHMMMode);

            if ( bEdWidth )
                nValue = aTextSize.Width();
            else
            {
                nValue = aTextSize.Height();

                if ( bBreak && !rOptions.bTotalSize )
                {
                    //  limit size for line break
                    tools::Long nCmp = aOldFont.GetFontSize().Height() * SC_ROT_BREAK_FACTOR;
                    if ( nValue > nCmp )
                        nValue = nCmp;
                }
            }
        }
        else if ( bEdWidth )
        {
            if (bBreak)
                nValue = 0;
            else
            {
                sal_uInt32 aTextSize(pEngine->CalcTextWidth());
                nValue = bInPrintTwips ?
                        o3tl::toTwips(aTextSize, o3tl::Length::mm100) :
                        pDev->LogicToPixel(Size(aTextSize, 0), aHMMMode).Width();
            }
        }
        else            // height
        {
            sal_uInt32 aTextSize(pEngine->GetTextHeight());
            nValue = bInPrintTwips ?
                    o3tl::toTwips(aTextSize, o3tl::Length::mm100) :
                    pDev->LogicToPixel(Size(0, aTextSize), aHMMMode).Height();
        }

        if ( nValue && bAddMargin )
        {
            if (bWidth)
            {
                nValue += conditionalScaleFunc(pMargin->GetLeftMargin(), nPPT) +
                          conditionalScaleFunc(pMargin->GetRightMargin(), nPPT);
                if (nIndent)
                    nValue += conditionalScaleFunc(nIndent, nPPT);
            }
            else
            {
                nValue += conditionalScaleFunc(pMargin->GetTopMargin(), nPPT) +
                          conditionalScaleFunc(pMargin->GetBottomMargin(), nPPT);

                if ( bAsianVertical && pDev->GetOutDevType() != OUTDEV_PRINTER )
                {
                    //  add 1pt extra (default margin value) for line breaks with SetVertical
                    constexpr tools::Long nDefaultMarginInPoints = 1;
                    nValue += conditionalScaleFunc(
                        o3tl::convert(nDefaultMarginInPoints, o3tl::Length::pt, o3tl::Length::twip),
                        nPPT);
                }
            }
        }

        //  EditEngine is cached and re-used, so the old vertical flag must be restored
        pEngine->SetVertical( bEngineVertical );

        rDocument.DisposeFieldEditEngine(pEngine);

        pDev->SetMapMode( aOld );
        pDev->SetFont( aOldFont );
    }

    if (bWidth)
    {
        //      place for Autofilter Button
        //      20 * nZoom/100
        //      Conditional formatting is not interesting here
        constexpr tools::Long nFilterButtonWidthPix = 20; // Autofilter pixel width at 100% zoom.
        ScMF nFlags = pPattern->GetItem(ATTR_MERGE_FLAG).GetValue();
        if (nFlags & ScMF::Auto)
            nValue += bInPrintTwips ? o3tl::convert(nFilterButtonWidthPix, o3tl::Length::px,
                                                    o3tl::Length::twip)
                                    : tools::Long(rZoomX * nFilterButtonWidthPix);
    }

    return nValue;
}

namespace {

class MaxStrLenFinder
{
    ScDocument& mrDoc;
    sal_uInt32 mnFormat;
    OUString maMaxLenStr;
    sal_Int32 mnMaxLen;

    // tdf#59820 - search for the longest substring in a multiline string
    void checkLineBreak(const OUString& aStrVal)
    {
        sal_Int32 nFromIndex = 0;
        sal_Int32 nToIndex = aStrVal.indexOf('\n', nFromIndex);
        // if there is no line break, just take the length of the entire string
        if (nToIndex == -1)
        {
            mnMaxLen = aStrVal.getLength();
            maMaxLenStr = aStrVal;
        }
        else
        {
            sal_Int32 nMaxLen = 0;
            // search for the longest substring in the multiline string
            while (nToIndex != -1)
            {
                if (nMaxLen < nToIndex - nFromIndex)
                {
                    nMaxLen = nToIndex - nFromIndex;
                }
                nFromIndex = nToIndex + 1;
                nToIndex = aStrVal.indexOf('\n', nFromIndex);
            }
            // take into consideration the last part of multiline string
            nToIndex = aStrVal.getLength() - nFromIndex;
            if (nMaxLen < nToIndex)
            {
                nMaxLen = nToIndex;
            }
            // assign new maximum including its substring
            if (mnMaxLen < nMaxLen)
            {
                mnMaxLen = nMaxLen;
                maMaxLenStr = aStrVal.subView(nFromIndex);
            }
        }
    }

    void checkLength(const ScRefCellValue& rCell)
    {
        const Color* pColor;
        OUString aValStr = ScCellFormat::GetString(
            rCell, mnFormat, &pColor, nullptr, mrDoc);

        if (aValStr.getLength() <= mnMaxLen)
            return;

        switch (rCell.getType())
        {
            case CELLTYPE_NONE:
            case CELLTYPE_VALUE:
                mnMaxLen = aValStr.getLength();
                maMaxLenStr = aValStr;
                break;
            case CELLTYPE_EDIT:
            case CELLTYPE_STRING:
            case CELLTYPE_FORMULA:
            default:
                checkLineBreak(aValStr);
        }
    }

public:
    MaxStrLenFinder(ScDocument& rDoc, sal_uInt32 nFormat) :
        mrDoc(rDoc), mnFormat(nFormat), mnMaxLen(0) {}

    void operator() (size_t /*nRow*/, double f)
    {
        ScRefCellValue aCell(f);
        checkLength(aCell);
    }

    void operator() (size_t /*nRow*/, const svl::SharedString& rSS)
    {
        if (rSS.getLength() > mnMaxLen)
        {
            checkLineBreak(rSS.getString());
        }
    }

    void operator() (size_t /*nRow*/, const EditTextObject* p)
    {
        ScRefCellValue aCell(p);
        checkLength(aCell);
    }

    void operator() (size_t /*nRow*/, const ScFormulaCell* p)
    {
        ScRefCellValue aCell(const_cast<ScFormulaCell*>(p));
        checkLength(aCell);
    }

    const OUString& getMaxLenStr() const { return maMaxLenStr; }
};

}

sal_uInt16 ScColumn::GetOptimalColWidth(
    OutputDevice* pDev, double nPPTX, double nPPTY, const Fraction& rZoomX, const Fraction&&nbsp;rZoomY,
    bool bFormula, sal_uInt16 nOldWidth, const ScMarkData* pMarkData, const ScColWidthParam* pParam) const
{
    if (maCells.block_size() == 1 && maCells.begin()->type == sc::element_type_empty)
        // All cells are empty.
        return nOldWidth;

    sc::SingleColumnSpanSet::SpansType aMarkedSpans;
    if (pMarkData && (pMarkData->IsMarked() || pMarkData->IsMultiMarked()))
    {
        sc::SingleColumnSpanSet aSpanSet(GetDoc().GetSheetLimits());
        aSpanSet.scan(*pMarkData, nTab, nCol);
        aSpanSet.getSpans(aMarkedSpans);
    }
    else
        // "Select" the entire column if no selection exists.
        aMarkedSpans.emplace_back(0, GetDoc().MaxRow());

    sal_uInt16 nWidth = static_cast<sal_uInt16>(nOldWidth*nPPTX);
    bool bFound = false;
    ScDocument& rDocument = GetDoc();

    if ( pParam && pParam->mbSimpleText )
    {   // all the same except for number format
        SCROW nRow = 0;
        const ScPatternAttr* pPattern = GetPattern( nRow );
        vcl::Font aFont;
        aFont.SetKerning(FontKerning::NONE); // like ScDrawStringsVars::SetPattern
        // font color doesn't matter here
        pPattern->fillFontOnly(aFont, pDev, &rZoomX);
        pDev->SetFont(aFont);
        const SvxMarginItem* pMargin = &pPattern->GetItem(ATTR_MARGIN);
        tools::Long nMargin = static_cast<tools::Long>( pMargin->GetLeftMargin() * nPPTX ) +
                        static_cast<tools::Long>( pMargin->GetRightMargin() * nPPTX );

        // Try to find the row that has the longest string, and measure the width of that string.
        ScInterpreterContext& rContext = rDocument.GetNonThreadedContext();
        sal_uInt32 nFormat = pPattern->GetNumberFormat(rContext);
        while ((nFormat % SV_COUNTRY_LANGUAGE_OFFSET) == 0 && nRow <= 2)
        {
            // This is often used with CSV import or other data having a header
            // row; if there is no specific format set try next row for actual
            // data format.
            // Or again in case there was a leading sep=";" row or two header
            // rows..
            const ScPatternAttr* pNextPattern = GetPattern( ++nRow );
            if (!ScPatternAttr::areSame(pNextPattern, pPattern))
                nFormat = pNextPattern->GetNumberFormat(rContext);
        }
        OUString aLongStr;
        const Color* pColor;
        if (pParam->mnMaxTextRow >= 0)
        {
            ScRefCellValue aCell = GetCellValue(pParam->mnMaxTextRow);
            aLongStr = ScCellFormat::GetString(
                aCell, nFormat, &pColor, &rContext, rDocument);
        }
        else
        {
            // Go though all non-empty cells within selection.
            MaxStrLenFinder aFunc(rDocument, nFormat);
            sc::CellStoreType::const_iterator itPos = maCells.begin();
            for (const auto& rMarkedSpan : aMarkedSpans)
                itPos = sc::ParseAllNonEmpty(itPos, maCells, rMarkedSpan.mnRow1, rMarkedSpan.mnRow2, aFunc);

            aLongStr = aFunc.getMaxLenStr();
        }

        if (!aLongStr.isEmpty())
        {
            nWidth = pDev->GetTextWidth(aLongStr) + static_cast<sal_uInt16>(nMargin);
            bFound = true;
        }
    }
    else
    {
        ScNeededSizeOptions aOptions;
        aOptions.bFormula = bFormula;
        const ScPatternAttr* pOldPattern = nullptr;

        // Go though all non-empty cells within selection.
        sc::CellStoreType::const_iterator itPos = maCells.begin();
        // coverity[auto_causes_copy] This trivial copy is intentional
        for (auto [ nRow, nRow2 ] : aMarkedSpans)
        {
            while (nRow <= nRow2)
            {
                size_t nOffset;
                std::tie(itPos, nOffset) = maCells.position(itPos, nRow);
                if (itPos->type == sc::element_type_empty)
                {
                    // Skip empty cells.
                    nRow += itPos->size - nOffset;
                    continue;
                }

                for (; nOffset < itPos->size && nRow <= nRow2; ++nOffset, ++nRow)
                {
                    SvtScriptType nScript = rDocument.GetScriptType(nCol, nRow, nTab);
                    if (nScript == SvtScriptType::NONE)
                        nScript = ScGlobal::GetDefaultScriptType();

                    const ScPatternAttr* pPattern = GetPattern(nRow);
                    aOptions.bGetFont = (!ScPatternAttr::areSame(pPattern, pOldPattern) || nScript != SvtScriptType::NONE);
                    aOptions.aPattern.setScPatternAttr(pPattern);
                    pOldPattern = aOptions.aPattern.getScPatternAttr();
                    sal_uInt16 nThis = static_cast<sal_uInt16>(GetNeededSize(
                        nRow, pDev, nPPTX, nPPTY, rZoomX, rZoomY, true, aOptions, &pOldPattern));
                    if (nThis && (nThis > nWidth || !bFound))
                    {
                        nWidth = nThis;
                        bFound = true;
                    }
                }
            }
        }
    }

    if (bFound)
    {
        nWidth += 2;
        sal_uInt16 nTwips = static_cast<sal_uInt16>(
            std::min(nWidth / nPPTX, std::numeric_limits<sal_uInt16>::max() / 2.0));
        return nTwips;
    }
    else
        return nOldWidth;
}

static sal_uInt16 lcl_GetAttribHeight(const ScPatternAttr& rPattern, sal_uInt16 nFontHeightId,
                                      sal_uInt16 nMinHeight)
{
    const SvxFontHeightItem& rFontHeight =
        static_cast<const SvxFontHeightItem&>(rPattern.GetItem(nFontHeightId));

    sal_uInt16 nHeight = rFontHeight.GetHeight();
    nHeight *= 1.18;

    if ( rPattern.GetItem(ATTR_FONT_EMPHASISMARK).GetEmphasisMark() != FontEmphasisMark::NONE )
    {
        //  add height for emphasis marks
        //TODO: font metrics should be used instead
        nHeight += nHeight / 4;
    }

    const SvxMarginItem& rMargin = rPattern.GetItem(ATTR_MARGIN);

    nHeight += rMargin.GetTopMargin() + rMargin.GetBottomMargin();

    if (nHeight > STD_ROWHEIGHT_DIFF)
        nHeight -= STD_ROWHEIGHT_DIFF;

    if (nHeight < nMinHeight)
        nHeight = nMinHeight;

    return nHeight;
}

//  pHeight in Twips
//  optimize nMinHeight, nMinStart : with nRow >= nMinStart is at least nMinHeight
//  (is only evaluated with bStdAllowed)

void ScColumn::GetOptimalHeight(
    sc::RowHeightContext& rCxt, SCROW nStartRow, SCROW nEndRow, sal_uInt16 nMinHeight, SCROW nMinStart )
{
    ScDocument& rDocument = GetDoc();
    RowHeightsArray& rHeights = rCxt.getHeightArray();
    ScAttrIterator aIter( pAttrArray.get(), nStartRow, nEndRow, &rDocument.getCellAttributeHelper().getDefaultCellAttribute() );

    SCROW nStart = -1;
    SCROW nEnd = -1;
    SCROW nEditPos = 0;
    SCROW nNextEnd = 0;

    //  with conditional formatting, always consider the individual cells

    const ScPatternAttr* pPattern = aIter.Next(nStart,nEnd);
    const sal_uInt16 nOptimalMinRowHeight = GetDoc().GetSheetOptimalMinRowHeight(nTab);
    while ( pPattern )
    {
        const ScMergeAttr*      pMerge = &pPattern->GetItem(ATTR_MERGE);
        const ScMergeFlagAttr*  pFlag = &pPattern->GetItem(ATTR_MERGE_FLAG);
        if ( pMerge->GetRowMerge() > 1 || pFlag->IsOverlapped() )
        {
            //  do nothing - vertically with merged and overlapping,
            //        horizontally only with overlapped (invisible) -
            //        only one horizontal merged is always considered
        }
        else
        {
            bool bStdAllowed = (pPattern->GetCellOrientation() == SvxCellOrientation::Standard);
            bool bStdOnly = false;
            if (bStdAllowed)
            {
                bool bBreak = pPattern->GetItem(ATTR_LINEBREAK).GetValue() ||
                              (pPattern->GetItem( ATTR_HOR_JUSTIFY ).GetValue() ==
                                    SvxCellHorJustify::Block);
                bStdOnly = !bBreak;

                // conditional formatting: loop all cells
                if (bStdOnly &&
                    !pPattern->GetItem(ATTR_CONDITIONAL).GetCondFormatData().empty())
                {
                    bStdOnly = false;
                }

                // rotated text: loop all cells
                if ( bStdOnly && pPattern->GetItem(ATTR_ROTATE_VALUE).GetValue() )
                    bStdOnly = false;
            }

            if (bStdOnly)
            {
                bool bHasEditCells = HasEditCells(nStart,nEnd,nEditPos);
                // Call to HasEditCells() may change pattern due to
                // calculation, => sync always.
                // We don't know which row changed first, but as pPattern
                // covered nStart to nEnd we can pick nStart. Worst case we
                // have to repeat that for every row in range if every row
                // changed.
                pPattern = aIter.Resync( nStart, nStart, nEnd);
                if (bHasEditCells && nEnd < nEditPos)
                    bHasEditCells = false;              // run into that again
                if (bHasEditCells)                      // includes mixed script types
                {
                    if (nEditPos == nStart)
                    {
                        bStdOnly = false;
                        if (nEnd > nEditPos)
                            nNextEnd = nEnd;
                        nEnd = nEditPos;                // calculate single
                        bStdAllowed = false;            // will be computed in any case per cell
                    }
                    else
                    {
                        nNextEnd = nEnd;
                        nEnd = nEditPos - 1;            // standard - part
                    }
                }
            }

            sc::SingleColumnSpanSet aSpanSet(GetDoc().GetSheetLimits());
            aSpanSet.scan(*this, nStart, nEnd);
            sc::SingleColumnSpanSet::SpansType aSpans;
            aSpanSet.getSpans(aSpans);

            if (bStdAllowed)
            {
                sal_uInt16 nLatHeight = 0;
                sal_uInt16 nCjkHeight = 0;
                sal_uInt16 nCtlHeight = 0;
                sal_uInt16 nDefHeight;
                SvtScriptType nDefScript = ScGlobal::GetDefaultScriptType();
                if ( nDefScript == SvtScriptType::ASIAN )
                    nDefHeight = nCjkHeight = lcl_GetAttribHeight(*pPattern, ATTR_CJK_FONT_HEIGHT,
                                                                  nOptimalMinRowHeight);
                else if ( nDefScript == SvtScriptType::COMPLEX )
                    nDefHeight = nCtlHeight = lcl_GetAttribHeight(*pPattern, ATTR_CTL_FONT_HEIGHT,
                                                                  nOptimalMinRowHeight);
                else
                    nDefHeight = nLatHeight = lcl_GetAttribHeight(*pPattern, ATTR_FONT_HEIGHT,
                                                                  nOptimalMinRowHeight);

                //  if everything below is already larger, the loop doesn't have to
                //  be run again
                SCROW nStdEnd = nEnd;
                if ( nDefHeight <= nMinHeight && nStdEnd >= nMinStart )
                    nStdEnd = (nMinStart>0) ? nMinStart-1 : 0;

                if (nStart <= nStdEnd)
                {
                    SCROW nRow = nStart;
                    for (;;)
                    {
                        size_t nIndex;
                        SCROW nRangeEnd;
                        sal_uInt16 nRangeHeight = rHeights.GetValue(nRow, nIndex, nRangeEnd);
                        if (nRangeHeight < nDefHeight)
                            rHeights.SetValue(nRow, std::min(nRangeEnd, nStdEnd), nDefHeight);
                        nRow = nRangeEnd + 1;
                        if (nRow > nStdEnd)
                            break;
                    }
                }

                if ( bStdOnly )
                {
                    //  if cells are not handled individually below,
                    //  check for cells with different script type
                    sc::CellTextAttrStoreType::iterator itAttr = maCellTextAttrs.begin();
                    sc::CellStoreType::iterator itCells = maCells.begin();
                    for (const auto& rSpan : aSpans)
                    {
                        for (SCROW nRow = rSpan.mnRow1; nRow <= rSpan.mnRow2; ++nRow)
                        {
                            SvtScriptType nScript = GetRangeScriptType(itAttr, nRow, nRow, itCells);
                            if (nScript == nDefScript)
                                continue;

                            if ( nScript == SvtScriptType::ASIAN )
                            {
                                if ( nCjkHeight == 0 )
                                    nCjkHeight = lcl_GetAttribHeight(*pPattern,
                                                                     ATTR_CJK_FONT_HEIGHT,
                                                                     nOptimalMinRowHeight);
                                if (nCjkHeight > rHeights.GetValue(nRow))
                                    rHeights.SetValue(nRow, nRow, nCjkHeight);
                            }
                            else if ( nScript == SvtScriptType::COMPLEX )
                            {
                                if ( nCtlHeight == 0 )
                                    nCtlHeight = lcl_GetAttribHeight(*pPattern,
                                                                     ATTR_CTL_FONT_HEIGHT,
                                                                     nOptimalMinRowHeight);
                                if (nCtlHeight > rHeights.GetValue(nRow))
                                    rHeights.SetValue(nRow, nRow, nCtlHeight);
                            }
                            else
                            {
                                if ( nLatHeight == 0 )
                                    nLatHeight = lcl_GetAttribHeight(*pPattern, ATTR_FONT_HEIGHT,
                                                                     nOptimalMinRowHeight);
                                if (nLatHeight > rHeights.GetValue(nRow))
                                    rHeights.SetValue(nRow, nRow, nLatHeight);
                            }
                        }
                    }
                }
            }

            if (!bStdOnly)                      // search covered cells
            {
                ScNeededSizeOptions aOptions;
                CellAttributeHolder aOldPattern;

                for (const auto& rSpan : aSpans)
                {
                    for (SCROW nRow = rSpan.mnRow1; nRow <= rSpan.mnRow2; ++nRow)
                    {
                        //  only calculate the cell height when it's used later (#37928#)

                        if (rCxt.isForceAutoSize() || !(rDocument.GetRowFlags(nRow, nTab) & CRFlags::ManualSize) )
                        {
                            aOptions.aPattern.setScPatternAttr(pPattern);
                            aOldPattern.setScPatternAttr(aOptions.aPattern.getScPatternAttr());
                            sal_uInt16 nHeight = static_cast<sal_uInt16>(
                                std::min(
                                    GetNeededSize( nRow, rCxt.getOutputDevice(), rCxt.getPPTX(), rCxt.getPPTY(),
                                                   rCxt.getZoomX(), rCxt.getZoomY(), false, aOptions,
                                                   &pPattern) / rCxt.getPPTY(),
                                    double(std::numeric_limits<sal_uInt16>::max())));
                            if (nHeight > rHeights.GetValue(nRow))
                                rHeights.SetValue(nRow, nRow, nHeight);

                            // Pattern changed due to calculation? => sync.
                            if (!ScPatternAttr::areSame(pPattern, aOldPattern.getScPatternAttr()))
                            {
                                pPattern = aIter.Resync( nRow, nStart, nEnd);
                                nNextEnd = 0;
                            }
                        }
                    }
                }
            }
        }

        if (nNextEnd > 0)
        {
            nStart = nEnd + 1;
            nEnd = nNextEnd;
            nNextEnd = 0;
        }
        else
            pPattern = aIter.Next(nStart,nEnd);
    }
}

bool ScColumn::GetNextSpellingCell(SCROW& nRow, bool bInSel, const ScMarkData& rData) const
{
    ScDocument& rDocument = GetDoc();
    sc::CellStoreType::const_iterator it = maCells.position(nRow).first;
    mdds::mtv::element_t eType = it->type;
    if (!bInSel && it != maCells.end() && eType != sc::element_type_empty)
    {
        if ( (eType == sc::element_type_string || eType == sc::element_type_edittext) &&
             !(HasAttrib( nRow, nRow, HasAttrFlags::Protected) &&
               rDocument.IsTabProtected(nTab)) )
            return true;
    }
    if (bInSel)
    {
        SCROW lastDataPos = GetLastDataPos();
        for (;;)
        {
            nRow = rData.GetNextMarked(nCol, nRow, false);
            if (!rDocument.ValidRow(nRow) || nRow > lastDataPos )
            {
                nRow = GetDoc().MaxRow()+1;
                return false;
            }
            else
            {
                it = maCells.position(it, nRow).first;
                eType = it->type;
                if ( (eType == sc::element_type_string || eType == sc::element_type_edittext) &&
                     !(HasAttrib( nRow, nRow, HasAttrFlags::Protected) &&
                       rDocument.IsTabProtected(nTab)) )
                    return true;
                else
                    nRow++;
            }
        }
    }
    else
    {
        while (GetNextDataPos(nRow))
        {
            it = maCells.position(it, nRow).first;
            eType = it->type;
            if ( (eType == sc::element_type_string || eType == sc::element_type_edittext) &&
                 !(HasAttrib( nRow, nRow, HasAttrFlags::Protected) &&
                   rDocument.IsTabProtected(nTab)) )
                return true;
            else
                nRow++;
        }
        nRow = GetDoc().MaxRow()+1;
        return false;
    }
}

namespace {

class StrEntries
{
    sc::CellStoreType& mrCells;

protected:
    struct StrEntry
    {
        SCROW mnRow;
        OUString maStr;

        StrEntry(SCROW nRow, OUString aStr) : mnRow(nRow), maStr(std::move(aStr)) {}
    };

    std::vector<StrEntry> maStrEntries;
    ScDocument& mrDoc;

    StrEntries(sc::CellStoreType& rCells, ScDocument& rDoc) : mrCells(rCells), mrDoc(rDoc) {}

public:
    void commitStrings()
    {
        svl::SharedStringPool& rPool = mrDoc.GetSharedStringPool();
        sc::CellStoreType::iterator it = mrCells.begin();
        for (const auto& rStrEntry : maStrEntries)
            it = mrCells.set(it, rStrEntry.mnRow, rPool.intern(rStrEntry.maStr));
    }
};

class RemoveEditAttribsHandler : public StrEntries
{
    std::unique_ptr<ScFieldEditEngine> mpEngine;

public:
    RemoveEditAttribsHandler(sc::CellStoreType& rCells, ScDocument& rDoc) : StrEntries(rCells, rDoc) {}

    void operator() (size_t nRow, EditTextObject*& pObj)
    {
        //  For the test on hard formatting (ScEditAttrTester), are the defaults in the
        //  EditEngine of no importance. When the tester would later recognise the same
        //  attributes in default and hard formatting and has to remove them, the correct
        //  defaults must be set in the EditEngine for each cell.

        //  test for attributes
        if (!mpEngine)
        {
            mpEngine.reset(new ScFieldEditEngine(&mrDoc, mrDoc.GetEditPool()));
            //  EEControlBits::ONLINESPELLING if there are errors already
            mpEngine->SetControlWord(mpEngine->GetControlWord() | EEControlBits::ONLINESPELLING);
            mrDoc.ApplyAsianEditSettings(*mpEngine);
        }
        mpEngine->SetTextCurrentDefaults(*pObj);
        sal_Int32 nParCount = mpEngine->GetParagraphCount();
        for (sal_Int32 nPar=0; nPar<nParCount; nPar++)
        {
            mpEngine->RemoveCharAttribs(nPar);
            const SfxItemSet& rOld = mpEngine->GetParaAttribs(nPar);
            if ( rOld.Count() )
            {
                SfxItemSet aNew( *rOld.GetPool(), rOld.GetRanges() );   // empty
                mpEngine->SetParaAttribs( nPar, aNew );
            }
        }
        //  change URL field to text (not possible otherwise, thus pType=0)
        mpEngine->RemoveFields();

        bool bSpellErrors = mpEngine->HasOnlineSpellErrors();
        bool bNeedObject = bSpellErrors || nParCount>1;         // keep errors/paragraphs
        //  ScEditAttrTester is not needed anymore, arrays are gone

        if (bNeedObject)                                      // remains edit cell
        {
            EEControlBits nCtrl = mpEngine->GetControlWord();
            EEControlBits nWantBig = bSpellErrors ? EEControlBits::ALLOWBIGOBJS : EEControlBits::NONE;
            if ( ( nCtrl & EEControlBits::ALLOWBIGOBJS ) != nWantBig )
                mpEngine->SetControlWord( (nCtrl & ~EEControlBits::ALLOWBIGOBJS) | nWantBig );

            // Overwrite the existing object.
            delete pObj;
            pObj = mpEngine->CreateTextObject().release();
        }
        else                                            // create String
        {
            // Store the string replacement for later commits.
            OUString aText = ScEditUtil::GetSpaceDelimitedString(*mpEngine);
            maStrEntries.emplace_back(nRow, aText);
        }
    }
};

class TestTabRefAbsHandler
{
    SCTAB mnTab;
    bool mbTestResult;
public:
    explicit TestTabRefAbsHandler(SCTAB nTab) : mnTab(nTab), mbTestResult(false) {}

    void operator() (size_t /*nRow*/, const ScFormulaCell* pCell)
    {
        if (const_cast<ScFormulaCell*>(pCell)->TestTabRefAbs(mnTab))
            mbTestResult = true;
    }

    bool getTestResult() const { return mbTestResult; }
};

}

void ScColumn::RemoveEditAttribs( sc::ColumnBlockPosition& rBlockPos, SCROW nStartRow, SCROW nEndRow )
{
    RemoveEditAttribsHandler aFunc(maCells, GetDoc());

    rBlockPos.miCellPos = sc::ProcessEditText(
        rBlockPos.miCellPos, maCells, nStartRow, nEndRow, aFunc);

    aFunc.commitStrings();
}

bool ScColumn::TestTabRefAbs(SCTAB nTable) const
{
    TestTabRefAbsHandler aFunc(nTable);
    sc::ParseFormula(maCells, aFunc);
    return aFunc.getTestResult();
}

bool ScColumn::IsEmptyData() const
{
    return maCells.block_size() == 1 && maCells.begin()->type == sc::element_type_empty;
}

namespace {

class CellCounter
{
    size_t mnCount;
public:
    CellCounter() : mnCount(0) {}

    void operator() (
        const sc::CellStoreType::value_type& node, size_t /*nOffset*/, size_t nDataSize)
    {
        if (node.type == sc::element_type_empty)
            return;

        mnCount += nDataSize;
    }

    size_t getCount() const { return mnCount; }
};

}

SCSIZE ScColumn::VisibleCount( SCROW nStartRow, SCROW nEndRow ) const
{
    CellCounter aFunc;
    sc::ParseBlock(maCells.begin(), maCells, aFunc, nStartRow, nEndRow);
    return aFunc.getCount();
}

bool ScColumn::HasVisibleDataAt(SCROW nRow) const
{
    std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(nRow);
    sc::CellStoreType::const_iterator it = aPos.first;
    if (it == maCells.end())
        // Likely invalid row number.
        return false;

    return it->type != sc::element_type_empty;
}

bool ScColumn::IsEmptyData(SCROW nStartRow, SCROW nEndRow) const
{
    // simple case
    if (maCells.block_size() == 1 && maCells.begin()->type == sc::element_type_empty)
        return true;

    std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(nStartRow);
    sc::CellStoreType::const_iterator it = aPos.first;
    if (it == maCells.end())
        // Invalid row number.
        return false;

    if (it->type != sc::element_type_empty)
        // Non-empty cell at the start position.
        return false;

    // start position of next block which is not empty.
    SCROW nNextRow = nStartRow + it->size - aPos.second;
    return nEndRow < nNextRow;
}

bool ScColumn::IsNotesEmptyBlock(SCROW nStartRow, SCROW nEndRow) const
{
    std::pair<sc::CellNoteStoreType::const_iterator,size_t> aPos = maCellNotes.position(nStartRow);
    sc::CellNoteStoreType::const_iterator it = aPos.first;
    if (it == maCellNotes.end())
        // Invalid row number.
        return false;

    if (it->type != sc::element_type_empty)
        // Non-empty cell at the start position.
        return false;

    // start position of next block which is not empty.
    SCROW nNextRow = nStartRow + it->size - aPos.second;
    return nEndRow < nNextRow;
}

SCSIZE ScColumn::GetEmptyLinesInBlock( SCROW nStartRow, SCROW nEndRow, ScDirection eDir const
{
    // Given a range of rows, find a top or bottom empty segment.
    switch (eDir)
    {
        case DIR_TOP:
        {
            // Determine the length of empty head segment.
            size_t nLength = nEndRow - nStartRow + 1;
            std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(nStartRow);
            sc::CellStoreType::const_iterator it = aPos.first;
            if (it->type != sc::element_type_empty)
                // First row is already not empty.
                return 0;

            // length of this empty block minus the offset.
            size_t nThisLen = it->size - aPos.second;
            return std::min(nThisLen, nLength);
        }
        break;
        case DIR_BOTTOM:
        {
            // Determine the length of empty tail segment.
            size_t nLength = nEndRow - nStartRow + 1;
            std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(nEndRow);
            sc::CellStoreType::const_iterator it = aPos.first;
            if (it->type != sc::element_type_empty)
                // end row is already not empty.
                return 0;

            // length of this empty block from the tip to the end row position.
            size_t nThisLen = aPos.second + 1;
            return std::min(nThisLen, nLength);
        }
        break;
        default:
            ;
    }

    return 0;
}

SCROW ScColumn::GetFirstDataPos() const
{
    if (IsEmptyData())
        return 0;

    sc::CellStoreType::const_iterator it = maCells.begin();
    if (it->type != sc::element_type_empty)
        return 0;

    return it->size;
}

SCROW ScColumn::GetLastDataPos() const
{
    if (IsEmptyData())
        return 0;

    sc::CellStoreType::const_reverse_iterator it = maCells.rbegin();
    if (it->type != sc::element_type_empty)
        return GetDoc().MaxRow();

    return GetDoc().MaxRow() - static_cast<SCROW>(it->size);
}

SCROW ScColumn::GetLastDataPos( SCROW nLastRow, ScDataAreaExtras* pDataAreaExtras ) const
{
    nLastRow = std::min( nLastRow, GetDoc().MaxRow());

    if (pDataAreaExtras && pDataAreaExtras->mnEndRow < nLastRow)
    {
        // Check in order of likeliness.
        if (    (pDataAreaExtras->mbCellFormats && HasVisibleAttrIn(nLastRow, nLastRow)) ||
                (pDataAreaExtras->mbCellNotes && !IsNotesEmptyBlock(nLastRow, nLastRow)) ||
                (pDataAreaExtras->mbCellDrawObjects && !IsDrawObjectsEmptyBlock(nLastRow, nLastRow)))
            pDataAreaExtras->mnEndRow = nLastRow;
    }

    sc::CellStoreType::const_position_type aPos = maCells.position(nLastRow);

    if (aPos.first->type != sc::element_type_empty)
        return nLastRow;

    if (aPos.first == maCells.begin())
        // This is the first block, and is empty.
        return 0;

    return static_cast<SCROW>(aPos.first->position - 1);
}

bool ScColumn::GetPrevDataPos(SCROW& rRow) const
{
    std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(rRow);
    sc::CellStoreType::const_iterator it = aPos.first;
    if (it == maCells.end())
        return false;

    if (it->type == sc::element_type_empty)
    {
        if (it == maCells.begin())
            // No more previous non-empty cell.
            return false;

        rRow -= aPos.second + 1; // Last row position of the previous block.
        return true;
    }

    // This block is not empty.
    if (aPos.second)
    {
        // There are preceding cells in this block. Simply move back one cell.
        --rRow;
        return true;
    }

    // This is the first cell in a non-empty block. Move back to the previous block.
    if (it == maCells.begin())
        // No more preceding block.
        return false;

    --rRow; // Move to the last cell of the previous block.
    --it;
    if (it->type == sc::element_type_empty)
    {
        // This block is empty.
        if (it == maCells.begin())
            // No more preceding blocks.
            return false;

        // Skip the whole empty block segment.
        rRow -= it->size;
    }

    return true;
}

bool ScColumn::GetNextDataPos(SCROW& rRow) const        // greater than rRow
{
    std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(rRow);
    sc::CellStoreType::const_iterator it = aPos.first;
    if (it == maCells.end())
        return false;

    if (it->type == sc::element_type_empty)
    {
        // This block is empty. Skip ahead to the next block (if exists).
        rRow += it->size - aPos.second;
        ++it;
        if (it == maCells.end())
            // No more next block.
            return false;

        // Next block exists, and is non-empty.
        return true;
    }

    if (aPos.second < it->size - 1)
    {
        // There are still cells following the current position.
        ++rRow;
        return true;
    }

    // This is the last cell in the block. Move ahead to the next block.
    rRow += it->size - aPos.second; // First cell in the next block.
    ++it;
    if (it == maCells.end())
        // No more next block.
        return false;

    if (it->type == sc::element_type_empty)
    {
        // Next block is empty. Move to the next block.
        rRow += it->size;
        ++it;
        if (it == maCells.end())
            return false;
    }

    return true;
}

bool ScColumn::TrimEmptyBlocks(SCROW& rRowStart, SCROW& rRowEnd) const
{
    assert(rRowStart <= rRowEnd);
    SCROW nRowStartNew = rRowStart, nRowEndNew = rRowEnd;

    // Trim down rRowStart first
    std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(rRowStart);
    sc::CellStoreType::const_iterator it = aPos.first;
    if (it == maCells.end())
        return false;

    if (it->type == sc::element_type_empty)
    {
        // This block is empty. Skip ahead to the next block (if exists).
        nRowStartNew += it->size - aPos.second;
        if (nRowStartNew > rRowEnd)
            return false;
        ++it;
        if (it == maCells.end())
            // No more next block.
            return false;
    }

    // Trim up rRowEnd next
    aPos = maCells.position(rRowEnd);
    it = aPos.first;
    if (it == maCells.end())
    {
        rRowStart = nRowStartNew;
        return true// Because trimming of rRowStart is ok
    }

    if (it->type == sc::element_type_empty)
    {
        // rRowEnd cannot be in the first block which is empty !
        assert(it != maCells.begin());
        // This block is empty. Skip to the previous block (it exists).
        nRowEndNew -= aPos.second + 1; // Last row position of the previous block.
        assert(nRowStartNew <= nRowEndNew);
    }

    rRowStart = nRowStartNew;
    rRowEnd = nRowEndNew;
    return true;
}

SCROW ScColumn::FindNextVisibleRow(SCROW nRow, bool bForward) const
{
    if(bForward)
    {
        nRow++;
        SCROW nEndRow = 0;
        bool bHidden = GetDoc().RowHidden(nRow, nTab, nullptr, &nEndRow);
        if(bHidden)
            return std::min<SCROW>(GetDoc().MaxRow(), nEndRow + 1);
        else
            return nRow;
    }
    else
    {
        nRow--;
        SCROW nStartRow = GetDoc().MaxRow();
        bool bHidden = GetDoc().RowHidden(nRow, nTab, &nStartRow);
        if(bHidden)
            return std::max<SCROW>(0, nStartRow - 1);
        else
            return nRow;
    }
}

SCROW ScColumn::FindNextVisibleRowWithContent(
    sc::CellStoreType::const_iterator& itPos, SCROW nRow, bool bForward) const
{
    ScDocument& rDocument = GetDoc();
    if (bForward)
    {
        do
        {
            nRow++;
            SCROW nEndRow = 0;
            bool bHidden = rDocument.RowHidden(nRow, nTab, nullptr, &nEndRow);
            if (bHidden)
            {
                nRow = nEndRow + 1;
                if(nRow >= GetDoc().MaxRow())
                    return GetDoc().MaxRow();
            }

            std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(itPos, nRow);
            itPos = aPos.first;
            if (itPos == maCells.end())
                // Invalid row.
                return GetDoc().MaxRow();

            if (itPos->type != sc::element_type_empty)
                return nRow;

            // Move to the last cell of the current empty block.
            nRow += itPos->size - aPos.second - 1;
        }
        while (nRow < GetDoc().MaxRow());

        return GetDoc().MaxRow();
    }

    do
    {
        nRow--;
        SCROW nStartRow = GetDoc().MaxRow();
        bool bHidden = rDocument.RowHidden(nRow, nTab, &nStartRow);
        if (bHidden)
        {
            nRow = nStartRow - 1;
            if(nRow <= 0)
                return 0;
        }

        std::pair<sc::CellStoreType::const_iterator,size_t> aPos = maCells.position(itPos, nRow);
        itPos = aPos.first;
        if (itPos == maCells.end())
            // Invalid row.
            return 0;

        if (itPos->type != sc::element_type_empty)
            return nRow;

        // Move to the first cell of the current empty block.
        nRow -= aPos.second;
    }
    while (nRow > 0);

    return 0;
}

void ScColumn::CellStorageModified()
{
    // Remove cached values. Given how often this function is called and how (not that) often
    // the cached values are used, it should be more efficient to just discard everything
    // instead of trying to figure out each time exactly what to discard.
    GetDoc().DiscardFormulaGroupContext();

    // TODO: Update column's "last updated" timestamp here.

    assert(sal::static_int_cast<SCROW>(maCells.size()) == GetDoc().GetMaxRowCount()
        && "Size of the cell array is incorrect." );

    assert(sal::static_int_cast<SCROW>(maCellTextAttrs.size()) == GetDoc().GetMaxRowCount()
        && "Size of the cell text attribute array is incorrect.");

    assert(sal::static_int_cast<SCROW>(maBroadcasters.size()) == GetDoc().GetMaxRowCount()
        && "Size of the broadcaster array is incorrect.");

#if DEBUG_COLUMN_STORAGE
    // Make sure that these two containers are synchronized wrt empty segments.
    auto lIsEmptyType = [](const auto& rElement) { return rElement.type == sc::element_type_empty; };
    // Move to the first empty blocks.
    auto itCell = std::find_if(maCells.begin(), maCells.end(), lIsEmptyType);
    auto itAttr = std::find_if(maCellTextAttrs.begin(), maCellTextAttrs.end(), lIsEmptyType);

    while (itCell != maCells.end())
    {
        if (itCell->position != itAttr->position || itCell->size != itAttr->size)
        {
            cout << "ScColumn::CellStorageModified: Cell array and cell text attribute array are out of sync." << endl;
            cout << "-- cell array" << endl;
            maCells.dump_blocks(cout);
            cout << "-- attribute array" << endl;
            maCellTextAttrs.dump_blocks(cout);
            cout.flush();
            abort();
        }

        // Move to the next empty blocks.
        ++itCell;
        itCell = std::find_if(itCell, maCells.end(), lIsEmptyType);

        ++itAttr;
        itAttr = std::find_if(itAttr, maCellTextAttrs.end(), lIsEmptyType);
    }
#endif
}

#if DUMP_COLUMN_STORAGE

namespace {

#define DUMP_FORMULA_RESULTS 0

struct ColumnStorageDumper
{
    const ScDocument& mrDoc;

    ColumnStorageDumper( const ScDocument& rDoc ) : mrDoc(rDoc) {}

    void operator() (const sc::CellStoreType::value_type& rNode) const
    {
        switch (rNode.type)
        {
            case sc::element_type_numeric:
                cout << " * numeric block (pos=" << rNode.position << ", length=" << rNode.size << ")" << endl;
                break;
            case sc::element_type_string:
                cout << " * string block (pos=" << rNode.position << ", length=" << rNode.size << ")" << endl;
                break;
            case sc::element_type_edittext:
                cout << " * edit-text block (pos=" << rNode.position << ", length=" << rNode.size << ")" << endl;
                break;
            case sc::element_type_formula:
                dumpFormulaBlock(rNode);
                break;
            case sc::element_type_empty:
                cout << " * empty block (pos=" << rNode.position << ", length=" << rNode.size << ")" << endl;
                break;
            default:
                cout << " * unknown block" << endl;
        }
    }

    void dumpFormulaBlock(const sc::CellStoreType::value_type& rNode) const
    {
        cout << " * formula block (pos=" << rNode.position << ", length=" << rNode.size << ")" << endl;
        sc::formula_block::const_iterator it = sc::formula_block::begin(*rNode.data);
        sc::formula_block::const_iterator itEnd = sc::formula_block::end(*rNode.data);

        for (; it != itEnd; ++it)
        {
            const ScFormulaCell* pCell = *it;
            if (!pCell->IsShared())
            {
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=87 H=97 G=91

¤ Dauer der Verarbeitung: 0.30 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge