    /* -*- 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 <scitems.hxx>
    #include <editeng/eeitem.hxx>

    #include <editeng/adjustitem.hxx>
    #include <svx/algitem.hxx>
    #include <editeng/brushitem.hxx>
    #include <svtools/colorcfg.hxx>
    #include <editeng/colritem.hxx>
    #include <editeng/charreliefitem.hxx>
    #include <editeng/crossedoutitem.hxx>
    #include <editeng/contouritem.hxx>
    #include <editeng/editobj.hxx>
    #include <editeng/editstat.hxx>
    #include <editeng/emphasismarkitem.hxx>
    #include <editeng/fhgtitem.hxx>
    #include <editeng/forbiddenruleitem.hxx>
    #include <editeng/forbiddencharacterstable.hxx>
    #include <editeng/frmdiritem.hxx>
    #include <editeng/langitem.hxx>
    #include <editeng/justifyitem.hxx>
    #include <svx/rotmodit.hxx>
    #include <editeng/scripttypeitem.hxx>
    #include <editeng/udlnitem.hxx>
    #include <editeng/unolingu.hxx>
    #include <editeng/fontitem.hxx>
    #include <editeng/postitem.hxx>
    #include <editeng/shdditem.hxx>
    #include <editeng/wghtitem.hxx>
    #include <editeng/wrlmitem.hxx>
    #include <svl/zforlist.hxx>
    #include <svl/zformat.hxx>
    #include <vcl/svapp.hxx>
    #include <vcl/metric.hxx>
    #include <vcl/outdev.hxx>
    #include <vcl/pdfextoutdevdata.hxx>
    #include <vcl/settings.hxx>
    #include <o3tl/make_unique.hxx>
    #include <sal/log.hxx>

    #include <output.hxx>
    #include <document.hxx>
    #include <formulacell.hxx>
    #include <attrib.hxx>
    #include <patattr.hxx>
    #include <cellform.hxx>
    #include <editutil.hxx>
    #include <progress.hxx>
    #include <scmod.hxx>
    #include <fillinfo.hxx>
    #include <viewdata.hxx>
    #include <tabvwsh.hxx>
    #include <docsh.hxx>
    #include <markdata.hxx>
    #include <stlsheet.hxx>
    #include <spellcheckcontext.hxx>
    #include <scopetools.hxx>

    #include <com/sun/star/i18n/DirectionProperty.hpp>
    #include <comphelper/string.hxx>

    #include <memory>
    #include <vector>

    #include <math.h>

    using namespace com::sun::star;

    //! Merge Autofilter width with column.cxx
    #define DROPDOWN_BITMAP_SIZE        18

    #define DRAWTEXT_MAX    32767

    const sal_uInt16 SC_SHRINKAGAIN_MAX = 7;

    class ScDrawStringsVars
    {
        ScOutputData*       pOutput;                // connection

        const ScPatternAttr* pPattern;              // attribute
        const SfxItemSet*   pCondSet;               // from conditional formatting

        vcl::Font           aFont;                  // created from attributes
        FontMetric          aMetric;
        long                nAscentPixel;           // always pixels
        SvxCellOrientation  eAttrOrient;
        SvxCellHorJustify   eAttrHorJust;
        SvxCellVerJustify   eAttrVerJust;
        SvxCellJustifyMethod eAttrHorJustMethod;
        const SvxMarginItem* pMargin;
        sal_uInt16          nIndent;
        bool                bRotated;

        OUString            aString;                // contents
        Size                aTextSize;
        long                nOriginalWidth;
        long                nMaxDigitWidth;
        long                nSignWidth;
        long                nDotWidth;
        long                nExpWidth;

        ScRefCellValue      maLastCell;
        sal_uLong           nValueFormat;
        bool                bLineBreak;
        bool                bRepeat;
        bool                bShrink;

        bool                bPixelToLogic;
        bool                bCellContrast;

        Color               aBackConfigColor;       // used for ScPatternAttr::GetFont calls
        Color               aTextConfigColor;
        sal_Int32           nRepeatPos;
        sal_Unicode         nRepeatChar;

    public:
                    ScDrawStringsVars(ScOutputData* pData, bool bPTL);

                    //  SetPattern = ex-SetVars
                    //  SetPatternSimple: without Font

        void SetPattern(
            const ScPatternAttr* pNew, const SfxItemSet* pSet, const ScRefCellValue& rCell,
            SvtScriptType nScript );

        void        SetPatternSimple( const ScPatternAttr* pNew, const SfxItemSet* pSet );

        bool SetText( ScRefCellValue& rCell );   // TRUE -> drop pOldPattern
        void        SetHashText();
        void SetTextToWidthOrHash( ScRefCellValue& rCell, long nWidth );
        void        SetAutoText( const OUString& rAutoText );

        SvxCellOrientation      GetOrient() const        { return eAttrOrient; }
        SvxCellHorJustify       GetHorJust() const       { return eAttrHorJust; }
        SvxCellVerJustify       GetVerJust() const       { return eAttrVerJust; }
        SvxCellJustifyMethod    GetHorJustMethod() const { return eAttrHorJustMethod; }
        const SvxMarginItem*    GetMargin() const        { return pMargin; }

        sal_uInt16              GetLeftTotal() const     { return pMargin->GetLeftMargin() + nIndent; }
        sal_uInt16              GetRightTotal() const    { return pMargin->GetRightMargin() + nIndent; }

        const OUString&         GetString() const        { return aString; }
        const Size&             GetTextSize() const      { return aTextSize; }
        long                    GetOriginalWidth() const { return nOriginalWidth; }

        // Get the effective number format, including formula result types.
        // This assumes that a formula cell has already been calculated.
        sal_uLong GetResultValueFormat() const { return nValueFormat;}

        bool    GetLineBreak() const                    { return bLineBreak; }
        bool    IsRepeat() const                        { return bRepeat; }
        bool    IsShrink() const                        { return bShrink; }
        void        RepeatToFill( long nColWidth );

        long    GetAscent() const   { return nAscentPixel; }
        bool    IsRotated() const   { return bRotated; }

        void    SetShrinkScale( long nScale, SvtScriptType nScript );

        bool    HasCondHeight() const   { return pCondSet && SfxItemState::SET ==
                                            pCondSet->GetItemState( ATTR_FONT_HEIGHT ); }

        bool    HasEditCharacters() const;

    private:
        long        GetMaxDigitWidth();     // in logic units
        long        GetSignWidth();
        long        GetDotWidth();
        long        GetExpWidth();
        void        TextChanged();
    };

    ScDrawStringsVars::ScDrawStringsVars(ScOutputData* pData, bool bPTL) :
        pOutput     ( pData ),
        pPattern    ( nullptr ),
        pCondSet    ( nullptr ),
        nAscentPixel(0),
        eAttrOrient ( SvxCellOrientation::Standard ),
        eAttrHorJust( SvxCellHorJustify::Standard ),
        eAttrVerJust( SvxCellVerJustify::Bottom ),
        eAttrHorJustMethod( SvxCellJustifyMethod::Auto ),
        pMargin     ( nullptr ),
        nIndent     ( 0 ),
        bRotated    ( false ),
        nOriginalWidth( 0 ),
        nMaxDigitWidth( 0 ),
        nSignWidth( 0 ),
        nDotWidth( 0 ),
        nExpWidth( 0 ),
        nValueFormat( 0 ),
        bLineBreak  ( false ),
        bRepeat     ( false ),
        bShrink     ( false ),
        bPixelToLogic( bPTL ),
        nRepeatPos( -1 ),
        nRepeatChar( 0x0 )
    {
        ScModule* pScMod = SC_MOD();
        bCellContrast = pOutput->mbUseStyleColor &&
                Application::GetSettings().GetStyleSettings().GetHighContrastMode();

        const svtools::ColorConfig& rColorConfig = pScMod->GetColorConfig();
        aBackConfigColor = rColorConfig.GetColorValue(svtools::DOCCOLOR).nColor;
        aTextConfigColor = rColorConfig.GetColorValue(svtools::FONTCOLOR).nColor;
    }

    void ScDrawStringsVars::SetShrinkScale( long nScale, SvtScriptType nScript )
    {
        // text remains valid, size is updated

        OutputDevice* pDev = pOutput->mpDev;
        OutputDevice* pRefDevice = pOutput->mpRefDevice;
        OutputDevice* pFmtDevice = pOutput->pFmtDevice;

        // call GetFont with a modified fraction, use only the height

        Fraction aFraction( nScale, 100 );
        if ( !bPixelToLogic )
            aFraction *= pOutput->aZoomY;
        vcl::Font aTmpFont;
        pPattern->GetFont( aTmpFont, SC_AUTOCOL_RAW, pFmtDevice, &aFraction, pCondSet, nScript );
        long nNewHeight = aTmpFont.GetFontHeight();
        if ( nNewHeight > 0 )
            aFont.SetFontHeight( nNewHeight );

        // set font and dependent variables as in SetPattern

        pDev->SetFont( aFont );
        if ( pFmtDevice != pDev )
            pFmtDevice->SetFont( aFont );

        aMetric = pFmtDevice->GetFontMetric();
        if ( pFmtDevice->GetOutDevType() == OUTDEV_PRINTER && aMetric.GetInternalLeading() == 0 )
        {
            OutputDevice* pDefaultDev = Application::GetDefaultDevice();
            MapMode aOld = pDefaultDev->GetMapMode();
            pDefaultDev->SetMapMode( pFmtDevice->GetMapMode() );
            aMetric = pDefaultDev->GetFontMetric( aFont );
            pDefaultDev->SetMapMode( aOld );
        }

        nAscentPixel = aMetric.GetAscent();
        if ( bPixelToLogic )
            nAscentPixel = pRefDevice->LogicToPixel( Size( 0, nAscentPixel ) ).Height();

        SetAutoText( aString );     // same text again, to get text size
    }

    namespace {

    template<typename ItemType, typename EnumType>
    EnumType lcl_GetValue(const ScPatternAttr& rPattern, sal_uInt16 nWhich, const SfxItemSet* pCondSet)
    {
        const ItemType& rItem = static_cast<const ItemType&>(rPattern.GetItem(nWhich, pCondSet));
        return static_cast<EnumType>(rItem.GetValue());
    }

    bool lcl_GetBoolValue(const ScPatternAttr& rPattern, sal_uInt16 nWhich, const SfxItemSet* pCondSet)
    {
        return lcl_GetValue<SfxBoolItem, bool>(rPattern, nWhich, pCondSet);
    }

    }

    bool lcl_isNumberFormatText(const ScDocument* pDoc, SCCOL nCellX, SCROW nCellY, SCTAB nTab )
    {
        sal_uInt32 nCurrentNumberFormat;
        pDoc->GetNumberFormat( nCellX, nCellY, nTab, nCurrentNumberFormat);
        SvNumberFormatter* pNumberFormatter = pDoc->GetFormatTable();
        return pNumberFormatter->GetType( nCurrentNumberFormat ) == SvNumFormatType::TEXT;
    }

    void ScDrawStringsVars::SetPattern(
        const ScPatternAttr* pNew, const SfxItemSet* pSet, const ScRefCellValue& rCell,
        SvtScriptType nScript )
    {
        nMaxDigitWidth = 0;
        nSignWidth     = 0;
        nDotWidth      = 0;
        nExpWidth      = 0;

        pPattern = pNew;
        pCondSet = pSet;

        // evaluate pPattern

        OutputDevice* pDev = pOutput->mpDev;
        OutputDevice* pRefDevice = pOutput->mpRefDevice;
        OutputDevice* pFmtDevice = pOutput->pFmtDevice;

        // font

        ScAutoFontColorMode eColorMode;
        if ( pOutput->mbUseStyleColor )
        {
            if ( pOutput->mbForceAutoColor )
                eColorMode = bCellContrast ? SC_AUTOCOL_IGNOREALL : SC_AUTOCOL_IGNOREFONT;
            else
                eColorMode = bCellContrast ? SC_AUTOCOL_IGNOREBACK : SC_AUTOCOL_DISPLAY;
        }
        else
            eColorMode = SC_AUTOCOL_PRINT;

        if ( bPixelToLogic )
            pPattern->GetFont( aFont, eColorMode, pFmtDevice, nullptr, pCondSet, nScript,
                                &aBackConfigColor, &aTextConfigColor );
        else
            pPattern->GetFont( aFont, eColorMode, pFmtDevice, &pOutput->aZoomY, pCondSet, nScript,
                                &aBackConfigColor, &aTextConfigColor );
        aFont.SetAlignment(ALIGN_BASELINE);

        // orientation

        eAttrOrient = pPattern->GetCellOrientation( pCondSet );

        //  alignment

        eAttrHorJust = pPattern->GetItem( ATTR_HOR_JUSTIFY, pCondSet ).GetValue();

        eAttrVerJust = pPattern->GetItem( ATTR_VER_JUSTIFY, pCondSet ).GetValue();
        if ( eAttrVerJust == SvxCellVerJustify::Standard )
            eAttrVerJust = SvxCellVerJustify::Bottom;

        // justification method

        eAttrHorJustMethod = lcl_GetValue<SvxJustifyMethodItem, SvxCellJustifyMethod>(*pPattern, ATTR_HOR_JUSTIFY_METHOD, pCondSet);

        //  line break

        bLineBreak = pPattern->GetItem( ATTR_LINEBREAK, pCondSet ).GetValue();

        //  handle "repeat" alignment

        bRepeat = ( eAttrHorJust == SvxCellHorJustify::Repeat );
        if ( bRepeat )
        {
            // "repeat" disables rotation (before constructing the font)
            eAttrOrient = SvxCellOrientation::Standard;

            // #i31843# "repeat" with "line breaks" is treated as default alignment (but rotation is still disabled)
            if ( bLineBreak )
                eAttrHorJust = SvxCellHorJustify::Standard;
        }

        short nRot;
        switch (eAttrOrient)
        {
            case SvxCellOrientation::Standard:
                nRot = 0;
                bRotated = pPattern->GetItem( ATTR_ROTATE_VALUE, pCondSet ).GetValue() != 0 &&
                           !bRepeat;
                break;
            case SvxCellOrientation::Stacked:
                nRot = 0;
                bRotated = false;
                break;
            case SvxCellOrientation::TopBottom:
                nRot = 2700;
                bRotated = false;
                break;
            case SvxCellOrientation::BottomUp:
                nRot = 900;
                bRotated = false;
                break;
            default:
                OSL_FAIL("Invalid SvxCellOrientation value");
                nRot = 0;
                bRotated = false;
                break;
        }
        aFont.SetOrientation( nRot );

        // syntax mode

        if (pOutput->mbSyntaxMode)
            pOutput->SetSyntaxColor(&aFont, rCell);

        // There is no cell attribute for kerning, default is kerning OFF, all
        // kerning is stored at an EditText object that is drawn using EditEngine.
        aFont.SetKerning( FontKerning::NONE);

        pDev->SetFont( aFont );
        if ( pFmtDevice != pDev )
            pFmtDevice->SetFont( aFont );

        aMetric = pFmtDevice->GetFontMetric();

        // if there is the leading 0 on a printer device, we have problems
        // -> take metric from the screen (as for EditEngine!)
        if ( pFmtDevice->GetOutDevType() == OUTDEV_PRINTER && aMetric.GetInternalLeading() == 0 )
        {
            OutputDevice* pDefaultDev = Application::GetDefaultDevice();
            MapMode aOld = pDefaultDev->GetMapMode();
            pDefaultDev->SetMapMode( pFmtDevice->GetMapMode() );
            aMetric = pDefaultDev->GetFontMetric( aFont );
            pDefaultDev->SetMapMode( aOld );
        }

        nAscentPixel = aMetric.GetAscent();
        if ( bPixelToLogic )
            nAscentPixel = pRefDevice->LogicToPixel( Size( 0, nAscentPixel ) ).Height();

        Color aULineColor( pPattern->GetItem( ATTR_FONT_UNDERLINE, pCondSet ).GetColor() );
        pDev->SetTextLineColor( aULineColor );

        Color aOLineColor( pPattern->GetItem( ATTR_FONT_OVERLINE, pCondSet ).GetColor() );
        pDev->SetOverlineColor( aOLineColor );

        // number format

        nValueFormat = pPattern->GetNumberFormat( pOutput->mpDoc->GetFormatTable(), pCondSet );

        // margins
        pMargin = &pPattern->GetItem( ATTR_MARGIN, pCondSet );
        if ( eAttrHorJust == SvxCellHorJustify::Left || eAttrHorJust == SvxCellHorJustify::Right )
            nIndent = pPattern->GetItem( ATTR_INDENT, pCondSet ).GetValue();
        else
            nIndent = 0;

        // "Shrink to fit"

        bShrink = pPattern->GetItem( ATTR_SHRINKTOFIT, pCondSet ).GetValue();

        // at least the text size needs to be retrieved again
        //! differentiate and do not get the text again from the number format?
        maLastCell.clear();
    }

    void ScDrawStringsVars::SetPatternSimple( const ScPatternAttr* pNew, const SfxItemSet* pSet )
    {
        nMaxDigitWidth = 0;
        nSignWidth     = 0;
        nDotWidth      = 0;
        nExpWidth      = 0;

        // Is called, when the font variables do not change (!StringDiffer)

        pPattern = pNew;
        pCondSet = pSet;        //! is this needed ???

        // number format

        sal_uLong nOld = nValueFormat;
        const SfxPoolItem* pFormItem;
        if ( !pCondSet || pCondSet->GetItemState(ATTR_VALUE_FORMAT,true,&pFormItem) != SfxItemState::SET )
            pFormItem = &pPattern->GetItem(ATTR_VALUE_FORMAT);
        const SfxPoolItem* pLangItem;
        if ( !pCondSet || pCondSet->GetItemState(ATTR_LANGUAGE_FORMAT,true,&pLangItem) != SfxItemState::SET )
            pLangItem = &pPattern->GetItem(ATTR_LANGUAGE_FORMAT);
        nValueFormat = pOutput->mpDoc->GetFormatTable()->GetFormatForLanguageIfBuiltIn(
                        static_cast<const SfxUInt32Item*>(pFormItem)->GetValue(),
                        static_cast<const SvxLanguageItem*>(pLangItem)->GetLanguage() );

        if (nValueFormat != nOld)
            maLastCell.clear();           // always reformat

        // margins

        pMargin = &pPattern->GetItem( ATTR_MARGIN, pCondSet );

        if ( eAttrHorJust == SvxCellHorJustify::Left )
            nIndent = pPattern->GetItem( ATTR_INDENT, pCondSet ).GetValue();
        else
            nIndent = 0;

        // "Shrink to fit"

        bShrink = pPattern->GetItem( ATTR_SHRINKTOFIT, pCondSet ).GetValue();
    }

    inline bool SameValue( const ScRefCellValue& rCell, const ScRefCellValue& rOldCell )
    {
        return rOldCell.meType == CELLTYPE_VALUE && rCell.meType == CELLTYPE_VALUE &&
            rCell.mfValue == rOldCell.mfValue;
    }

    bool ScDrawStringsVars::SetText( ScRefCellValue& rCell )
    {
        bool bChanged = false;

        if (!rCell.isEmpty())
        {
            if (!SameValue(rCell, maLastCell))
            {
                maLastCell = rCell;          // store cell

                Color* pColor;
                sal_uLong nFormat = nValueFormat;
                ScCellFormat::GetString( rCell,
                                         nFormat, aString, &pColor,
                                         *pOutput->mpDoc->GetFormatTable(),
                                         pOutput->mpDoc,
                                         pOutput->mbShowNullValues,
                                         pOutput->mbShowFormulas,
                                         true );
                if ( nFormat )
                {
                    nRepeatPos = aString.indexOf( 0x1B );
                    if ( nRepeatPos != -1 )
                    {
                        if (nRepeatPos + 1 == aString.getLength())
                            nRepeatPos = -1;
                        else
                        {
                            nRepeatChar = aString[ nRepeatPos + 1 ];
                            // delete placeholder and char to repeat
                            aString = aString.replaceAt( nRepeatPos, 2, "" );
                            // Do not cache/reuse a repeat-filled string, column
                            // widths or fonts or sizes may differ.
                            maLastCell.clear();
                        }
                    }
                }
                else
                {
                    nRepeatPos = -1;
                    nRepeatChar = 0x0;
                }
                if (aString.getLength() > DRAWTEXT_MAX)
                    aString = aString.copy(0, DRAWTEXT_MAX);

                if ( pColor && !pOutput->mbSyntaxMode && !( pOutput->mbUseStyleColor && pOutput->mbForceAutoColor ) )
                {
                    OutputDevice* pDev = pOutput->mpDev;
                    aFont.SetColor(*pColor);
                    pDev->SetFont( aFont );   // only for output
                    bChanged = true;
                    maLastCell.clear();       // next time return here again
                }

                TextChanged();
            }
            // otherwise keep string/size
        }
        else
        {
            aString.clear();
            maLastCell.clear();
            aTextSize = Size(0,0);
            nOriginalWidth = 0;
        }

        return bChanged;
    }

    void ScDrawStringsVars::SetHashText()
    {
        SetAutoText("###");
    }

    void ScDrawStringsVars::RepeatToFill( long nColWidth )
    {
        if ( nRepeatPos == -1 || nRepeatPos > aString.getLength() )
            return;

        long nCharWidth = pOutput->pFmtDevice->GetTextWidth(OUString(nRepeatChar));

        if ( nCharWidth < 1 || (bPixelToLogic && nCharWidth < pOutput->mpRefDevice->PixelToLogic(Size(1,0)).Width()) )
            return;

        // Are there restrictions on the cell type we should filter out here ?
        long nTextWidth = aTextSize.Width();
        if ( bPixelToLogic )
        {
            nColWidth = pOutput->mpRefDevice->PixelToLogic(Size(nColWidth,0)).Width();
            nTextWidth = pOutput->mpRefDevice->PixelToLogic(Size(nTextWidth,0)).Width();
        }

        long nSpaceToFill = ( nColWidth - nTextWidth );
        if ( nSpaceToFill <= nCharWidth )
            return;

        long nCharsToInsert = nSpaceToFill / nCharWidth;
        OUStringBuffer aFill;
        comphelper::string::padToLength(aFill, nCharsToInsert, nRepeatChar);
        aString = aString.replaceAt( nRepeatPos, 0, aFill.makeStringAndClear() );
        TextChanged();
    }

    void ScDrawStringsVars::SetTextToWidthOrHash( ScRefCellValue& rCell, long nWidth )
    {
        // #i113045# do the single-character width calculations in logic units
        if (bPixelToLogic)
            nWidth = pOutput->mpRefDevice->PixelToLogic(Size(nWidth,0)).Width();

        CellType eType = rCell.meType;
        if (eType != CELLTYPE_VALUE && eType != CELLTYPE_FORMULA)
            // must be a value or formula cell.
            return;

        if (eType == CELLTYPE_FORMULA)
        {
            ScFormulaCell* pFCell = rCell.mpFormula;
            if (pFCell->GetErrCode() != FormulaError::NONE || pOutput->mbShowFormulas)
            {
                SetHashText();      // If the error string doesn't fit, always use "###". Also for "display formulas" (#i116691#)
                return;
            }
            // If it's formula, the result must be a value.
            if (!pFCell->IsValue())
                return;
        }

        sal_uLong nFormat = GetResultValueFormat();
        if ((nFormat % SV_COUNTRY_LANGUAGE_OFFSET) != 0)
        {
            // Not 'General' number format.  Set hash text and bail out.
            SetHashText();
            return;
        }

        double fVal = rCell.getValue();

        const SvNumberformat* pNumFormat = pOutput->mpDoc->GetFormatTable()->GetEntry(nFormat);
        if (!pNumFormat)
            return;

        long nMaxDigit = GetMaxDigitWidth();
        if (!nMaxDigit)
            return;

        sal_uInt16 nNumDigits = static_cast<sal_uInt16>(nWidth / nMaxDigit);
        {
            OUString sTempOut(aString);
            if (!pNumFormat->GetOutputString(fVal, nNumDigits, sTempOut))
            {
                aString = sTempOut;
                // Failed to get output string.  Bail out.
                return;
            }
            aString = sTempOut;
        }
        sal_uInt8 nSignCount = 0, nDecimalCount = 0, nExpCount = 0;
        sal_Int32 nLen = aString.getLength();
        sal_Unicode cDecSep = ScGlobal::GetpLocaleData()->getLocaleItem().decimalSeparator[0];
        for( sal_Int32 i = 0; i < nLen; ++i )
        {
            sal_Unicode c = aString[i];
            if (c == '-')
                ++nSignCount;
            else if (c == cDecSep)
                ++nDecimalCount;
            else if (c == 'E')
                ++nExpCount;
        }

        // #i112250# A small value might be formatted as "0" when only counting the digits,
        // but fit into the column when considering the smaller width of the decimal separator.
        if (aString == "0" && fVal != 0.0)
            nDecimalCount = 1;

        if (nDecimalCount)
            nWidth += (nMaxDigit - GetDotWidth()) * nDecimalCount;
        if (nSignCount)
            nWidth += (nMaxDigit - GetSignWidth()) * nSignCount;
        if (nExpCount)
            nWidth += (nMaxDigit - GetExpWidth()) * nExpCount;

        if (nDecimalCount || nSignCount || nExpCount)
        {
            // Re-calculate.
            nNumDigits = static_cast<sal_uInt16>(nWidth / nMaxDigit);
            OUString sTempOut(aString);
            if (!pNumFormat->GetOutputString(fVal, nNumDigits, sTempOut))
            {
                aString = sTempOut;
                // Failed to get output string.  Bail out.
                return;
            }
            aString = sTempOut;
        }

        long nActualTextWidth = pOutput->pFmtDevice->GetTextWidth(aString);
        if (nActualTextWidth > nWidth)
        {
            // Even after the decimal adjustment the text doesn't fit.  Give up.
            SetHashText();
            return;
        }

        TextChanged();
        maLastCell.clear();   // #i113022# equal cell and format in another column may give different string
    }

    void ScDrawStringsVars::SetAutoText( const OUString& rAutoText )
    {
        aString = rAutoText;

        OutputDevice* pRefDevice = pOutput->mpRefDevice;
        OutputDevice* pFmtDevice = pOutput->pFmtDevice;
        aTextSize.setWidth( pFmtDevice->GetTextWidth( aString ) );
        aTextSize.setHeight( pFmtDevice->GetTextHeight() );

        if ( !pRefDevice->GetConnectMetaFile() || pRefDevice->GetOutDevType() == OUTDEV_PRINTER )
        {
            double fMul = pOutput->GetStretch();
            aTextSize.setWidth( static_cast<long>(aTextSize.Width() / fMul + 0.5) );
        }

        aTextSize.setHeight( aMetric.GetAscent() + aMetric.GetDescent() );
        if ( GetOrient() != SvxCellOrientation::Standard )
        {
            long nTemp = aTextSize.Height();
            aTextSize.setHeight( aTextSize.Width() );
            aTextSize.setWidth( nTemp );
        }

        nOriginalWidth = aTextSize.Width();
        if ( bPixelToLogic )
            aTextSize = pRefDevice->LogicToPixel( aTextSize );

        maLastCell.clear();       // the same text may fit in the next cell
    }

    long ScDrawStringsVars::GetMaxDigitWidth()
    {
        if (nMaxDigitWidth > 0)
            return nMaxDigitWidth;

        for (sal_Char i = 0; i < 10; ++i)
        {
            sal_Char cDigit = '0' + i;
            long n = pOutput->pFmtDevice->GetTextWidth(OUString(cDigit));
            nMaxDigitWidth = ::std::max(nMaxDigitWidth, n);
        }
        return nMaxDigitWidth;
    }

    long ScDrawStringsVars::GetSignWidth()
    {
        if (nSignWidth > 0)
            return nSignWidth;

        nSignWidth = pOutput->pFmtDevice->GetTextWidth(OUString('-'));
        return nSignWidth;
    }

    long ScDrawStringsVars::GetDotWidth()
    {
        if (nDotWidth > 0)
            return nDotWidth;

        const OUString& sep = ScGlobal::GetpLocaleData()->getLocaleItem().decimalSeparator;
        nDotWidth = pOutput->pFmtDevice->GetTextWidth(sep);
        return nDotWidth;
    }

    long ScDrawStringsVars::GetExpWidth()
    {
        if (nExpWidth > 0)
            return nExpWidth;

        nExpWidth = pOutput->pFmtDevice->GetTextWidth(OUString('E'));
        return nExpWidth;
    }

    void ScDrawStringsVars::TextChanged()
    {
        OutputDevice* pRefDevice = pOutput->mpRefDevice;
        OutputDevice* pFmtDevice = pOutput->pFmtDevice;
        aTextSize.setWidth( pFmtDevice->GetTextWidth( aString ) );
        aTextSize.setHeight( pFmtDevice->GetTextHeight() );

        if ( !pRefDevice->GetConnectMetaFile() || pRefDevice->GetOutDevType() == OUTDEV_PRINTER )
        {
            double fMul = pOutput->GetStretch();
            aTextSize.setWidth( static_cast<long>(aTextSize.Width() / fMul + 0.5) );
        }

        aTextSize.setHeight( aMetric.GetAscent() + aMetric.GetDescent() );
        if ( GetOrient() != SvxCellOrientation::Standard )
        {
            long nTemp = aTextSize.Height();
            aTextSize.setHeight( aTextSize.Width() );
            aTextSize.setWidth( nTemp );
        }

        nOriginalWidth = aTextSize.Width();
        if ( bPixelToLogic )
            aTextSize = pRefDevice->LogicToPixel( aTextSize );
    }

    bool ScDrawStringsVars::HasEditCharacters() const
    {
        for (sal_Int32 nIdx = 0; nIdx < aString.getLength(); ++nIdx)
        {
            switch(aString[nIdx])
            {
                case CHAR_NBSP:
                case CHAR_SHY:
                case CHAR_ZWSP:
                case CHAR_LRM:
                case CHAR_RLM:
                case CHAR_NBHY:
                case CHAR_ZWNBSP:
                    return true;
                default:
                    break;
            }
        }

        return false;
    }

    double ScOutputData::GetStretch()
    {
        if ( mpRefDevice->IsMapModeEnabled() )
        {
            //  If a non-trivial MapMode is set, its scale is now already
            //  taken into account in the OutputDevice's font handling
            //  (OutputDevice::ImplNewFont, see #95414#).
            //  The old handling below is only needed for pixel output.
            return 1.0;
        }

        // calculation in double is faster than Fraction multiplication
        // and doesn't overflow

        if ( mpRefDevice == pFmtDevice )
        {
            MapMode aOld = mpRefDevice->GetMapMode();
            return static_cast<double>(aOld.GetScaleY()) / static_cast<double>(aOld.GetScaleX()) * static_cast<double>(aZoomY) / static_cast<double>(aZoomX);
        }
        else
        {
            // when formatting for printer, device map mode has already been taken care of
            return static_cast<double>(aZoomY) / static_cast<double>(aZoomX);
        }
    }

    //  output strings

    static void lcl_DoHyperlinkResult( const OutputDevice* pDev, const tools::Rectangle& rRect, ScRefCellValue& rCell )
    {
        vcl::PDFExtOutDevData* pPDFData = dynamic_cast< vcl::PDFExtOutDevData* >( pDev->GetExtOutDevData() );

        OUString aCellText;
        OUString aURL;
        if (rCell.meType == CELLTYPE_FORMULA)
        {
            ScFormulaCell* pFCell = rCell.mpFormula;
            if ( pFCell->IsHyperLinkCell() )
                pFCell->GetURLResult( aURL, aCellText );
        }

        if ( !aURL.isEmpty() && pPDFData )
        {
            vcl::PDFExtOutDevBookmarkEntry aBookmark;
            aBookmark.nLinkId = pPDFData->CreateLink( rRect );
            aBookmark.aBookmark = aURL;
            std::vector< vcl::PDFExtOutDevBookmarkEntry >& rBookmarks = pPDFData->GetBookmarks();
            rBookmarks.push_back( aBookmark );
        }
    }

    void ScOutputData::SetSyntaxColor( vcl::Font* pFont, const ScRefCellValue& rCell )
    {
        switch (rCell.meType)
        {
            case CELLTYPE_VALUE:
                pFont->SetColor(*pValueColor);
            break;
            case CELLTYPE_STRING:
                pFont->SetColor(*pTextColor);
            break;
            case CELLTYPE_FORMULA:
                pFont->SetColor(*pFormulaColor);
            break;
            default:
            {
                // added to avoid warnings
            }
        }
    }

    static void lcl_SetEditColor( EditEngine& rEngine, const Color& rColor )
    {
        ESelection aSel( 0, 0, rEngine.GetParagraphCount(), 0 );
        SfxItemSet aSet( rEngine.GetEmptyItemSet() );
        aSet.Put( SvxColorItem( rColor, EE_CHAR_COLOR ) );
        rEngine.QuickSetAttribs( aSet, aSel );
        // function is called with update mode set to FALSE
    }

    void ScOutputData::SetEditSyntaxColor( EditEngine& rEngine, const ScRefCellValue& rCell )
    {
        Color aColor;
        switch (rCell.meType)
        {
            case CELLTYPE_VALUE:
                aColor = *pValueColor;
                break;
            case CELLTYPE_STRING:
                aColor = *pTextColor;
                break;
            case CELLTYPE_FORMULA:
                aColor = *pFormulaColor;
                break;
            default:
            {
                // added to avoid warnings
            }
        }
        lcl_SetEditColor( rEngine, aColor );
    }

    bool ScOutputData::GetMergeOrigin( SCCOL nX, SCROW nY, SCSIZE nArrY,
                                        SCCOL& rOverX, SCROW& rOverY,
                                        bool bVisRowChanged )
    {
        bool bDoMerge = false;
        bool bIsLeft = ( nX == nVisX1 );
        bool bIsTop  = ( nY == nVisY1 ) || bVisRowChanged;

        CellInfo* pInfo = &pRowInfo[nArrY].pCellInfo[nX+1];
        if ( pInfo->bHOverlapped && pInfo->bVOverlapped )
            bDoMerge = bIsLeft && bIsTop;
        else if ( pInfo->bHOverlapped )
            bDoMerge = bIsLeft;
        else if ( pInfo->bVOverlapped )
            bDoMerge = bIsTop;

        rOverX = nX;
        rOverY = nY;
        bool bHOver = pInfo->bHOverlapped;
        bool bVOver = pInfo->bVOverlapped;
        bool bHidden;

        while (bHOver)              // nY constant
        {
            --rOverX;
            bHidden = mpDoc->ColHidden(rOverX, nTab);
            if ( !bDoMerge && !bHidden )
                return false;

            if (rOverX >= nX1 && !bHidden)
            {
                bHOver = pRowInfo[nArrY].pCellInfo[rOverX+1].bHOverlapped;
                bVOver = pRowInfo[nArrY].pCellInfo[rOverX+1].bVOverlapped;
            }
            else
            {
                ScMF nOverlap = mpDoc->GetAttr(rOverX, rOverY, nTab, ATTR_MERGE_FLAG)->GetValue();
                bHOver = bool(nOverlap & ScMF::Hor);
                bVOver = bool(nOverlap & ScMF::Ver);
            }
        }

        while (bVOver)
        {
            --rOverY;
            bHidden = mpDoc->RowHidden(rOverY, nTab);
            if ( !bDoMerge && !bHidden )
                return false;

            if (nArrY>0)
                --nArrY;                        // local copy !

            if (rOverX >= nX1 && rOverY >= nY1 &&
                !mpDoc->ColHidden(rOverX, nTab) &&
                !mpDoc->RowHidden(rOverY, nTab) &&
                pRowInfo[nArrY].nRowNo == rOverY)
            {
                bVOver = pRowInfo[nArrY].pCellInfo[rOverX+1].bVOverlapped;
            }
            else
            {
                ScMF nOverlap = mpDoc->GetAttr( rOverX, rOverY, nTab, ATTR_MERGE_FLAG )->GetValue();
                bVOver = bool(nOverlap & ScMF::Ver);
            }
        }

        return true;
    }

    inline bool StringDiffer( const ScPatternAttr*& rpOldPattern, const ScPatternAttr* pNewPattern )
    {
        OSL_ENSURE( pNewPattern, "pNewPattern" );

        if ( pNewPattern == rpOldPattern )
            return false;
        else if ( !rpOldPattern )
            return true;
        else if ( &pNewPattern->GetItem( ATTR_FONT ) != &rpOldPattern->GetItem( ATTR_FONT ) )
            return true;
        else if ( &pNewPattern->GetItem( ATTR_CJK_FONT ) != &rpOldPattern->GetItem( ATTR_CJK_FONT ) )
            return true;
        else if ( &pNewPattern->GetItem( ATTR_CTL_FONT ) != &rpOldPattern->GetItem( ATTR_CTL_FONT ) )
            return true;
        else if ( &pNewPattern->GetItem( ATTR_FONT_HEIGHT ) != &rpOldPattern->GetItem( ATTR_FONT_HEIGHT ) )
            return true;
        else if ( &pNewPattern->GetItem( ATTR_CJK_FONT_HEIGHT ) != &rpOldPattern->GetItem( ATTR_CJK_FONT_HEIGHT ) )
            return true;
        else if ( &pNewPattern->GetItem( ATTR_CTL_FONT_HEIGHT ) != &rpOldPattern->GetItem( ATTR_CTL_FONT_HEIGHT ) )
            return true;
        else if ( &pNewPattern->GetItem( ATTR_FONT_WEIGHT ) != &rpOldPattern->GetItem( ATTR_FONT_WEIGHT ) )
            return true;
        else if ( &pNewPattern->GetItem( ATTR_CJK_FONT_WEIGHT ) != &rpOldPattern->GetItem( ATTR_CJK_FONT_WEIGHT ) )
            return true;
        else if ( &pNewPattern->GetItem( ATTR_CTL_FONT_WEIGHT ) != &rpOldPattern->GetItem( ATTR_CTL_FONT_WEIGHT ) )
            return true;
        else if ( &pNewPattern->GetItem( ATTR_FONT_POSTURE ) != &rpOldPattern->GetItem( ATTR_FONT_POSTURE ) )
            return true;
        else if ( &pNewPattern->GetItem( ATTR_CJK_FONT_POSTURE ) != &rpOldPattern->GetItem( ATTR_CJK_FONT_POSTURE ) )
            return true;
        else if ( &pNewPattern->GetItem( ATTR_CTL_FONT_POSTURE ) != &rpOldPattern->GetItem( ATTR_CTL_FONT_POSTURE ) )
            return true;
        else if ( &pNewPattern->GetItem( ATTR_FONT_UNDERLINE ) != &rpOldPattern->GetItem( ATTR_FONT_UNDERLINE ) )
            return true;
        else if ( &pNewPattern->GetItem( ATTR_FONT_OVERLINE ) != &rpOldPattern->GetItem( ATTR_FONT_OVERLINE ) )
            return true;
        else if ( &pNewPattern->GetItem( ATTR_FONT_WORDLINE ) != &rpOldPattern->GetItem( ATTR_FONT_WORDLINE ) )
            return true;
        else if ( &pNewPattern->GetItem( ATTR_FONT_CROSSEDOUT ) != &rpOldPattern->GetItem( ATTR_FONT_CROSSEDOUT ) )
            return true;
        else if ( &pNewPattern->GetItem( ATTR_FONT_CONTOUR ) != &rpOldPattern->GetItem( ATTR_FONT_CONTOUR ) )
            return true;
        else if ( &pNewPattern->GetItem( ATTR_FONT_SHADOWED ) != &rpOldPattern->GetItem( ATTR_FONT_SHADOWED ) )
            return true;
        else if ( &pNewPattern->GetItem( ATTR_FONT_COLOR ) != &rpOldPattern->GetItem( ATTR_FONT_COLOR ) )
            return true;
        else if ( &pNewPattern->GetItem( ATTR_HOR_JUSTIFY ) != &rpOldPattern->GetItem( ATTR_HOR_JUSTIFY ) )
            return true;
        else if ( &pNewPattern->GetItem( ATTR_HOR_JUSTIFY_METHOD ) != &rpOldPattern->GetItem( ATTR_HOR_JUSTIFY_METHOD ) )
            return true;
        else if ( &pNewPattern->GetItem( ATTR_VER_JUSTIFY ) != &rpOldPattern->GetItem( ATTR_VER_JUSTIFY ) )
            return true;
        else if ( &pNewPattern->GetItem( ATTR_VER_JUSTIFY_METHOD ) != &rpOldPattern->GetItem( ATTR_VER_JUSTIFY_METHOD ) )
            return true;
        else if ( &pNewPattern->GetItem( ATTR_STACKED ) != &rpOldPattern->GetItem( ATTR_STACKED ) )
            return true;
        else if ( &pNewPattern->GetItem( ATTR_LINEBREAK ) != &rpOldPattern->GetItem( ATTR_LINEBREAK ) )
            return true;
        else if ( &pNewPattern->GetItem( ATTR_MARGIN ) != &rpOldPattern->GetItem( ATTR_MARGIN ) )
            return true;
        else if ( &pNewPattern->GetItem( ATTR_ROTATE_VALUE ) != &rpOldPattern->GetItem( ATTR_ROTATE_VALUE ) )
            return true;
        else if ( &pNewPattern->GetItem( ATTR_FORBIDDEN_RULES ) != &rpOldPattern->GetItem( ATTR_FORBIDDEN_RULES ) )
            return true;
        else if ( &pNewPattern->GetItem( ATTR_FONT_EMPHASISMARK ) != &rpOldPattern->GetItem( ATTR_FONT_EMPHASISMARK ) )
            return true;
        else if ( &pNewPattern->GetItem( ATTR_FONT_RELIEF ) != &rpOldPattern->GetItem( ATTR_FONT_RELIEF ) )
            return true;
        else if ( &pNewPattern->GetItem( ATTR_BACKGROUND ) != &rpOldPattern->GetItem( ATTR_BACKGROUND ) )
            return true;    // needed with automatic text color
        else
        {
            rpOldPattern = pNewPattern;
            return false;
        }
    }

    static inline void lcl_CreateInterpretProgress( bool& bProgress, ScDocument* pDoc,
            const ScFormulaCell* pFCell )
    {
        if ( !bProgress && pFCell->GetDirty() )
        {
            ScProgress::CreateInterpretProgress( pDoc );
            bProgress = true;
        }
    }

    inline bool IsAmbiguousScript( SvtScriptType nScript )
    {
        return ( nScript != SvtScriptType::LATIN &&
                 nScript != SvtScriptType::ASIAN &&
                 nScript != SvtScriptType::COMPLEX );
    }

    bool ScOutputData::IsEmptyCellText( const RowInfo* pThisRowInfo, SCCOL nX, SCROW nY )
    {
        // pThisRowInfo may be NULL

        bool bEmpty;
        if ( pThisRowInfo && nX <= nX2 )
            bEmpty = pThisRowInfo->pCellInfo[nX+1].bEmptyCellText;
        else
        {
            ScRefCellValue aCell(*mpDoc, ScAddress(nX, nY, nTab));
            bEmpty = aCell.isEmpty();
        }

        if ( !bEmpty && ( nX < nX1 || nX > nX2 || !pThisRowInfo ) )
        {
            //  for the range nX1..nX2 in RowInfo, cell protection attribute is already evaluated
            //  into bEmptyCellText in ScDocument::FillInfo / lcl_HidePrint (printfun)

            bool bIsPrint = ( eType == OUTTYPE_PRINTER );

            if ( bIsPrint || bTabProtected )
            {
                const ScProtectionAttr* pAttr =
                        mpDoc->GetEffItem( nX, nY, nTab, ATTR_PROTECTION );
                if ( bIsPrint && pAttr->GetHidePrint() )
                    bEmpty = true;
                else if ( bTabProtected )
                {
                    if ( pAttr->GetHideCell() )
                        bEmpty = true;
                    else if ( mbShowFormulas && pAttr->GetHideFormula() )
                    {
                        if (mpDoc->GetCellType(ScAddress(nX, nY, nTab)) == CELLTYPE_FORMULA)
                            bEmpty = true;
                    }
                }
            }
        }
        return bEmpty;
    }

    void ScOutputData::GetVisibleCell( SCCOL nCol, SCROW nRow, SCTAB nTabP, ScRefCellValue& rCell )
    {
        rCell.assign(*mpDoc, ScAddress(nCol, nRow, nTabP));
        if (!rCell.isEmpty() && IsEmptyCellText(nullptr, nCol, nRow))
            rCell.clear();
    }

    bool ScOutputData::IsAvailable( SCCOL nX, SCROW nY )
    {
        //  apply the same logic here as in DrawStrings/DrawEdit:
        //  Stop at non-empty or merged or overlapped cell,
        //  where a note is empty as well as a cell that's hidden by protection settings

        ScRefCellValue aCell(*mpDoc, ScAddress(nX, nY, nTab));
        if (!aCell.isEmpty() && !IsEmptyCellText(nullptr, nX, nY))
            return false;

        const ScPatternAttr* pPattern = mpDoc->GetPattern( nX, nY, nTab );
        return !(pPattern->GetItem(ATTR_MERGE).IsMerged() ||
             pPattern->GetItem(ATTR_MERGE_FLAG).IsOverlapped());
    }

    // nX, nArrY:       loop variables from DrawStrings / DrawEdit
    // nPosX, nPosY:    corresponding positions for nX, nArrY
    // nCellX, nCellY:  position of the cell that contains the text
    // nNeeded:         Text width, including margin
    // rPattern:        cell format at nCellX, nCellY
    // nHorJustify:     horizontal alignment (visual) to determine which cells to use for long strings
    // bCellIsValue:    if set, don't extend into empty cells
    // bBreak:          if set, don't extend, and don't set clip marks (but rLeftClip/rRightClip is set)
    // bOverwrite:      if set, also extend into non-empty cells (for rotated text)
    // rParam           output: various area parameters.

    void ScOutputData::GetOutputArea( SCCOL nX, SCSIZE nArrY, long nPosX, long nPosY,
                                      SCCOL nCellX, SCROW nCellY, long nNeeded,
                                      const ScPatternAttr& rPattern,
                                      sal_uInt16 nHorJustify, bool bCellIsValue,
                                      bool bBreak, bool bOverwrite,
                                      OutputAreaParam& rParam )
    {
        //  rThisRowInfo may be for a different row than nCellY, is still used for clip marks
        RowInfo& rThisRowInfo = pRowInfo[nArrY];

        long nLayoutSign = bLayoutRTL ? -1 : 1;

        long nCellPosX = nPosX;         // find nCellX position, starting at nX/nPosX
        SCCOL nCompCol = nX;
        while ( nCellX > nCompCol )
        {
            //! extra member function for width?
            long nColWidth = ( nCompCol <= nX2 ) ?
                    pRowInfo[0].pCellInfo[nCompCol+1].nWidth :
                    static_cast<long>( mpDoc->GetColWidth( nCompCol, nTab ) * mnPPTX );
            nCellPosX += nColWidth * nLayoutSign;
            ++nCompCol;
        }
        while ( nCellX < nCompCol )
        {
            --nCompCol;
            long nColWidth = ( nCompCol <= nX2 ) ?
                    pRowInfo[0].pCellInfo[nCompCol+1].nWidth :
                    static_cast<long>( mpDoc->GetColWidth( nCompCol, nTab ) * mnPPTX );
            nCellPosX -= nColWidth * nLayoutSign;
        }

        long nCellPosY = nPosY;         // find nCellY position, starting at nArrY/nPosY
        SCSIZE nCompArr = nArrY;
        SCROW nCompRow = pRowInfo[nCompArr].nRowNo;
        while ( nCellY > nCompRow )
        {
            if ( nCompArr + 1 < nArrCount )
            {
                nCellPosY += pRowInfo[nCompArr].nHeight;
                ++nCompArr;
                nCompRow = pRowInfo[nCompArr].nRowNo;
            }
            else
            {
                sal_uInt16 nDocHeight = mpDoc->GetRowHeight( nCompRow, nTab );
                if ( nDocHeight )
                    nCellPosY += static_cast<long>( nDocHeight * mnPPTY );
                ++nCompRow;
            }
        }
        nCellPosY -= static_cast<long>(mpDoc->GetScaledRowHeight( nCellY, nCompRow-1, nTab, mnPPTY ));

        const ScMergeAttr* pMerge = &rPattern.GetItem( ATTR_MERGE );
        bool bMerged = pMerge->IsMerged();
        long nMergeCols = pMerge->GetColMerge();
        if ( nMergeCols == 0 )
            nMergeCols = 1;
        long nMergeRows = pMerge->GetRowMerge();
        if ( nMergeRows == 0 )
            nMergeRows = 1;

        long nMergeSizeX = 0;
        for ( long i=0; i<nMergeCols; i++ )
        {
            long nColWidth = ( nCellX+i <= nX2 ) ?
                    pRowInfo[0].pCellInfo[nCellX+i+1].nWidth :
                    static_cast<long>( mpDoc->GetColWidth( sal::static_int_cast<SCCOL>(nCellX+i), nTab ) * mnPPTX );
            nMergeSizeX += nColWidth;
        }
        long nMergeSizeY = 0;
        short nDirect = 0;
        if ( rThisRowInfo.nRowNo == nCellY )
        {
            // take first row's height from row info
            nMergeSizeY += rThisRowInfo.nHeight;
            nDirect = 1;        // skip in loop
        }
        // following rows always from document
        nMergeSizeY += static_cast<long>(mpDoc->GetScaledRowHeight( nCellY+nDirect, nCellY+nMergeRows-1, nTab, mnPPTY));

        --nMergeSizeX;      // leave out the grid horizontally, also for alignment (align between grid lines)

        rParam.mnColWidth = nMergeSizeX; // store the actual column width.
        rParam.mnLeftClipLength = rParam.mnRightClipLength = 0;

        // construct the rectangles using logical left/right values (justify is called at the end)

        // rAlignRect is the single cell or merged area, used for alignment.

        rParam.maAlignRect.SetLeft( nCellPosX );
        rParam.maAlignRect.SetRight( nCellPosX + ( nMergeSizeX - 1 ) * nLayoutSign );
        rParam.maAlignRect.SetTop( nCellPosY );
        rParam.maAlignRect.SetBottom( nCellPosY + nMergeSizeY - 1 );

        //  rClipRect is all cells that are used for output.
        //  For merged cells this is the same as rAlignRect, otherwise neighboring cells can also be used.

        rParam.maClipRect = rParam.maAlignRect;
        if ( nNeeded > nMergeSizeX )
        {
            SvxCellHorJustify eHorJust = static_cast<SvxCellHorJustify>(nHorJustify);

            long nMissing = nNeeded - nMergeSizeX;
            long nLeftMissing = 0;
            long nRightMissing = 0;
            switch ( eHorJust )
            {
                case SvxCellHorJustify::Left:
                    nRightMissing = nMissing;
                    break;
                case SvxCellHorJustify::Right:
                    nLeftMissing = nMissing;
                    break;
                case SvxCellHorJustify::Center:
                    nLeftMissing = nMissing / 2;
                    nRightMissing = nMissing - nLeftMissing;
                    break;
                default:
                {
                    // added to avoid warnings
                }
            }

            // nLeftMissing, nRightMissing are logical, eHorJust values are visual
            if ( bLayoutRTL )
                ::std::swap( nLeftMissing, nRightMissing );

            SCCOL nRightX = nCellX;
            SCCOL nLeftX = nCellX;
            if ( !bMerged && !bCellIsValue && !bBreak )
            {
                //  look for empty cells into which the text can be extended

                while ( nRightMissing > 0 && nRightX < MAXCOL && ( bOverwrite || IsAvailable( nRightX+1, nCellY ) ) )
                {
                    ++nRightX;
                    long nAdd = static_cast<long>( mpDoc->GetColWidth( nRightX, nTab ) * mnPPTX );
                    nRightMissing -= nAdd;
                    rParam.maClipRect.AdjustRight(nAdd * nLayoutSign );

                    if ( rThisRowInfo.nRowNo == nCellY && nRightX >= nX1 && nRightX <= nX2 )
                        rThisRowInfo.pCellInfo[nRightX].bHideGrid = true;
                }

                while ( nLeftMissing > 0 && nLeftX > 0 && ( bOverwrite || IsAvailable( nLeftX-1, nCellY ) ) )
                {
                    if ( rThisRowInfo.nRowNo == nCellY && nLeftX >= nX1 && nLeftX <= nX2 )
                        rThisRowInfo.pCellInfo[nLeftX].bHideGrid = true;

                    --nLeftX;
                    long nAdd = static_cast<long>( mpDoc->GetColWidth( nLeftX, nTab ) * mnPPTX );
                    nLeftMissing -= nAdd;
                    rParam.maClipRect.AdjustLeft( -(nAdd * nLayoutSign) );
                }
            }

            //  Set flag and reserve space for clipping mark triangle,
            //  even if rThisRowInfo isn't for nCellY (merged cells).
            if ( nRightMissing > 0 && bMarkClipped && nRightX >= nX1 && nRightX <= nX2 && !bBreak && !bCellIsValue )
            {
                rThisRowInfo.pCellInfo[nRightX+1].nClipMark |= ScClipMark::Right;
                bAnyClipped = true;
                long nMarkPixel = static_cast<long>( SC_CLIPMARK_SIZE * mnPPTX );
                rParam.maClipRect.AdjustRight( -(nMarkPixel * nLayoutSign) );
            }
            if ( nLeftMissing > 0 && bMarkClipped && nLeftX >= nX1 && nLeftX <= nX2 && !bBreak && !bCellIsValue )
            {
                rThisRowInfo.pCellInfo[nLeftX+1].nClipMark |= ScClipMark::Left;
                bAnyClipped = true;
                long nMarkPixel = static_cast<long>( SC_CLIPMARK_SIZE * mnPPTX );
                rParam.maClipRect.AdjustLeft(nMarkPixel * nLayoutSign );
            }

            rParam.mbLeftClip = ( nLeftMissing > 0 );
            rParam.mbRightClip = ( nRightMissing > 0 );
            rParam.mnLeftClipLength = nLeftMissing;
            rParam.mnRightClipLength = nRightMissing;
        }
        else
        {
            rParam.mbLeftClip = rParam.mbRightClip = false;

            // leave space for AutoFilter on screen
            // (for automatic line break: only if not formatting for printer, as in ScColumn::GetNeededSize)

            if ( eType==OUTTYPE_WINDOW &&
                 ( rPattern.GetItem(ATTR_MERGE_FLAG).GetValue() & (ScMF::Auto|ScMF::Button|ScMF::ButtonPopup) ) &&
                 ( !bBreak || mpRefDevice == pFmtDevice ) )
            {
                // filter drop-down width is now independent from row height
                const long nFilter = DROPDOWN_BITMAP_SIZE;
                bool bFit = ( nNeeded + nFilter <= nMergeSizeX );
                if ( bFit || bCellIsValue )
                {
                    // content fits even in the remaining area without the filter button
                    // -> align within that remaining area

                    rParam.maAlignRect.AdjustRight( -(nFilter * nLayoutSign) );
                    rParam.maClipRect.AdjustRight( -(nFilter * nLayoutSign) );

                    // if a number doesn't fit, don't hide part of the number behind the button
                    // -> set clip flags, so "###" replacement is used (but also within the smaller area)

                    if ( !bFit )
                        rParam.mbLeftClip = rParam.mbRightClip = true;
                }
            }
        }

        //  justify both rectangles for alignment calculation, use with DrawText etc.

        rParam.maAlignRect.Justify();
        rParam.maClipRect.Justify();
    }

    namespace {

    bool beginsWithRTLCharacter(const OUString& rStr)
    {
        if (rStr.isEmpty())
            return false;

        switch (ScGlobal::pCharClass->getCharacterDirection(rStr, 0))
        {
            case i18n::DirectionProperty_RIGHT_TO_LEFT:
            case i18n::DirectionProperty_RIGHT_TO_LEFT_ARABIC:
            case i18n::DirectionProperty_RIGHT_TO_LEFT_EMBEDDING:
            case i18n::DirectionProperty_RIGHT_TO_LEFT_OVERRIDE:
                return true;
            default:
                ;
        }

        return false;
    }

    }

    /** Get left, right or centered alignment from RTL context.

        Does not return standard, block or repeat, for these the contextual left or
        right alignment is returned.
     */
    static SvxCellHorJustify getAlignmentFromContext( SvxCellHorJustify eInHorJust,
            bool bCellIsValue, const OUString& rText,
            const ScPatternAttr& rPattern, const SfxItemSet* pCondSet,
            const ScDocument* pDoc, SCTAB nTab, const bool  bNumberFormatIsText )
    {
        SvxCellHorJustify eHorJustContext = eInHorJust;
        bool bUseWritingDirection = false;
        if (eInHorJust == SvxCellHorJustify::Standard)
        {
            // fdo#32530: Default alignment depends on value vs
            // string, and the direction of the 1st letter.
            if (beginsWithRTLCharacter( rText)) //If language is RTL
            {
                if (bCellIsValue)
                   eHorJustContext = bNumberFormatIsText ? SvxCellHorJustify::Right : SvxCellHorJustify::Left;
                else
                    eHorJustContext = SvxCellHorJustify::Right;
            }
            else if (bCellIsValue) //If language is not RTL
                eHorJustContext = bNumberFormatIsText ? SvxCellHorJustify::Left : SvxCellHorJustify::Right;
            else
                bUseWritingDirection = true;
        }

        if (bUseWritingDirection ||
                eInHorJust == SvxCellHorJustify::Block || eInHorJust == SvxCellHorJustify::Repeat)
        {
            SvxFrameDirection nDirection = lcl_GetValue<SvxFrameDirectionItem, SvxFrameDirection>(rPattern, ATTR_WRITINGDIR, pCondSet);
            if (nDirection == SvxFrameDirection::Horizontal_LR_TB || nDirection == SvxFrameDirection::Vertical_LR_TB)
                eHorJustContext = SvxCellHorJustify::Left;
            else if (nDirection == SvxFrameDirection::Environment)
            {
                SAL_WARN_IF( !pDoc, "sc.ui", "getAlignmentFromContext - pDoc==NULL");
                // fdo#73588: The content of the cell must also
                // begin with a RTL character to be right
                // aligned; otherwise, it should be left aligned.
                eHorJustContext = (pDoc && pDoc->IsLayoutRTL(nTab) && (beginsWithRTLCharacter( rText))) ? SvxCellHorJustify::Right : SvxCellHorJustify::Left;
            }
            else
                eHorJustContext = SvxCellHorJustify::Right;
        }
        return eHorJustContext;
    }

    void ScOutputData::DrawStrings( bool bPixelToLogic )
    {
        LayoutStrings(bPixelToLogic);
    }

    tools::Rectangle ScOutputData::LayoutStrings(bool bPixelToLogic, bool bPaint, const ScAddress &rAddress)
    {
        OSL_ENSURE( mpDev == mpRefDevice ||
                    mpDev->GetMapMode().GetMapUnit() == mpRefDevice->GetMapMode().GetMapUnit(),
                    "LayoutStrings: different MapUnits ?!?!" );

        vcl::PDFExtOutDevData* pPDFData = dynamic_cast< vcl::PDFExtOutDevData* >(mpDev->GetExtOutDevData() );

        sc::IdleSwitch aIdleSwitch(*mpDoc, false);
        ScDrawStringsVars aVars( this, bPixelToLogic );

        bool bProgress = false;

        long nInitPosX = nScrX;
        if ( bLayoutRTL )
            nInitPosX += nMirrorW - 1;              // pixels
        long nLayoutSign = bLayoutRTL ? -1 : 1;

        SCCOL nLastContentCol = MAXCOL;
        if ( nX2 < MAXCOL )
            nLastContentCol = sal::static_int_cast<SCCOL>(
                nLastContentCol - mpDoc->GetEmptyLinesInBlock( nX2+1, nY1, nTab, MAXCOL, nY2, nTab, DIR_RIGHT ) );
        SCCOL nLoopStartX = nX1;
        if ( nX1 > 0 )
            --nLoopStartX;          // start before nX1 for rest of long text to the left

        // variables for GetOutputArea
        OutputAreaParam aAreaParam;
        bool bCellIsValue = false;
        long nNeededWidth = 0;
        const ScPatternAttr* pPattern = nullptr;
        const SfxItemSet* pCondSet = nullptr;
        const ScPatternAttr* pOldPattern = nullptr;
        const SfxItemSet* pOldCondSet = nullptr;
        SvtScriptType nOldScript = SvtScriptType::NONE;

        // alternative pattern instances in case we need to modify the pattern
        // before processing the cell value.
        std::vector<std::unique_ptr<ScPatternAttr> > aAltPatterns;

        std::vector<long> aDX;
        long nPosY = nScrY;
        for (SCSIZE nArrY=1; nArrY+1<nArrCount; nArrY++)
        {
            RowInfo* pThisRowInfo = &pRowInfo[nArrY];
            SCROW nY = pThisRowInfo->nRowNo;
            if ((bPaint && pThisRowInfo->bChanged) || (!bPaint && rAddress.Row() == nY))
            {
                long nPosX = nInitPosX;
                if ( nLoopStartX < nX1 )
                    nPosX -= pRowInfo[0].pCellInfo[nLoopStartX+1].nWidth * nLayoutSign;
                for (SCCOL nX=nLoopStartX; nX<=nX2; nX++)
                {
                    bool bMergeEmpty = false;
                    CellInfo* pInfo = &pThisRowInfo->pCellInfo[nX+1];
                    bool bEmpty = nX < nX1 || pInfo->bEmptyCellText;

                    SCCOL nCellX = nX;                  // position where the cell really starts
                    SCROW nCellY = nY;
                    bool bDoCell = false;
                    bool bUseEditEngine = false;

                    //  Part of a merged cell?

                    bool bOverlapped = (pInfo->bHOverlapped || pInfo->bVOverlapped);
                    if ( bOverlapped )
                    {
                        bEmpty = true;

                        SCCOL nOverX;                   // start of the merged cells
                        SCROW nOverY;
                        bool bVisChanged = !pRowInfo[nArrY-1].bChanged;
                        if (GetMergeOrigin( nX,nY, nArrY, nOverX,nOverY, bVisChanged ))
                        {
                            nCellX = nOverX;
                            nCellY = nOverY;
                            bDoCell = true;
                        }
                        else
                            bMergeEmpty = true;
                    }

                    //  Rest of a long text further to the left?

                    if ( bEmpty && !bMergeEmpty && nX < nX1 && !bOverlapped )
                    {
                        SCCOL nTempX=nX1;
                        while (nTempX > 0 && IsEmptyCellText( pThisRowInfo, nTempX, nY ))
                            --nTempX;

                        if ( nTempX < nX1 &&
                             !IsEmptyCellText( pThisRowInfo, nTempX, nY ) &&
                             !mpDoc->HasAttrib( nTempX,nY,nTab, nX1,nY,nTab, HasAttrFlags::Merged | HasAttrFlags::Overlapped ) )
                        {
                            nCellX = nTempX;
                            bDoCell = true;
                        }
                    }

                    //  Rest of a long text further to the right?

                    if ( bEmpty && !bMergeEmpty && nX == nX2 && !bOverlapped )
                    {
                        //  don't have to look further than nLastContentCol

                        SCCOL nTempX=nX;
                        while (nTempX < nLastContentCol && IsEmptyCellText( pThisRowInfo, nTempX, nY ))
                            ++nTempX;

                        if ( nTempX > nX &&
                             !IsEmptyCellText( pThisRowInfo, nTempX, nY ) &&
                             !mpDoc->HasAttrib( nTempX,nY,nTab, nX,nY,nTab, HasAttrFlags::Merged | HasAttrFlags::Overlapped ) )
                        {
                            nCellX = nTempX;
                            bDoCell = true;
                        }
                    }

                    //  normal visible cell

                    if (!bEmpty)
                        bDoCell = true;

                    //  don't output the cell that's being edited

                    if ( bDoCell && bEditMode && nCellX == nEditCol && nCellY == nEditRow )
                        bDoCell = false;

                    // skip text in cell if data bar/icon set is set and only value selected
                    if ( bDoCell )
                    {
                        if(pInfo->pDataBar && !pInfo->pDataBar->mbShowValue)
                            bDoCell = false;
                        if(pInfo->pIconSet && !pInfo->pIconSet->mbShowValue)
                            bDoCell = false;
                    }

                    //  output the cell text

                    ScRefCellValue aCell;
                    if (bDoCell)
                    {
                        if ( nCellY == nY && nCellX == nX && nCellX >= nX1 && nCellX <= nX2 )
                            aCell = pThisRowInfo->pCellInfo[nCellX+1].maCell;
                        else
                            GetVisibleCell( nCellX, nCellY, nTab, aCell );      // get from document
                        if (aCell.isEmpty())
                            bDoCell = false;
                        else if (aCell.meType == CELLTYPE_EDIT)
                            bUseEditEngine = true;
                    }

                    // Check if this cell is mis-spelled.
                    if (bDoCell && !bUseEditEngine && aCell.meType == CELLTYPE_STRING)
                    {
                        if (mpSpellCheckCxt && mpSpellCheckCxt->isMisspelled(nCellX, nCellY))
                            bUseEditEngine = true;
                    }

                    if (bDoCell && !bUseEditEngine)
                    {
                        if ( nCellY == nY && nCellX >= nX1 && nCellX <= nX2 )
                        {
                            CellInfo& rCellInfo = pThisRowInfo->pCellInfo[nCellX+1];
                            pPattern = rCellInfo.pPatternAttr;
                            pCondSet = rCellInfo.pConditionSet;

                            if ( !pPattern )
                            {
                                // #i68085# pattern from cell info for hidden columns is null,
                                // test for null is quicker than using column flags
                                pPattern = mpDoc->GetPattern( nCellX, nCellY, nTab );
                                pCondSet = mpDoc->GetCondResult( nCellX, nCellY, nTab );
                            }
                        }
                        else        // get from document
                        {
                            pPattern = mpDoc->GetPattern( nCellX, nCellY, nTab );
                            pCondSet = mpDoc->GetCondResult( nCellX, nCellY, nTab );
                        }
                        if ( mpDoc->GetPreviewFont() || mpDoc->GetPreviewCellStyle() )
                        {
                            aAltPatterns.push_back(o3tl::make_unique<ScPatternAttr>(*pPattern));
                            ScPatternAttr* pAltPattern = aAltPatterns.back().get();
                            if (  ScStyleSheet* pPreviewStyle = mpDoc->GetPreviewCellStyle( nCellX, nCellY, nTab ) )
                            {
                                pAltPattern->SetStyleSheet(pPreviewStyle);
                            }
                            else if ( SfxItemSet* pFontSet = mpDoc->GetPreviewFont( nCellX, nCellY, nTab ) )
                            {
                                const SfxPoolItem* pItem;
                                if ( pFontSet->GetItemState( ATTR_FONT, true, &pItem ) == SfxItemState::SET )
                                    pAltPattern->GetItemSet().Put( static_cast<const SvxFontItem&>(*pItem) );
                                if ( pFontSet->GetItemState( ATTR_CJK_FONT, true, &pItem ) == SfxItemState::SET )
                                    pAltPattern->GetItemSet().Put( static_cast<const SvxFontItem&>(*pItem) );
                                if ( pFontSet->GetItemState( ATTR_CTL_FONT, true, &pItem ) == SfxItemState::SET )
                                    pAltPattern->GetItemSet().Put( static_cast<const SvxFontItem&>(*pItem) );
                            }
                            pPattern = pAltPattern;
                        }

                        if (aCell.hasNumeric() &&
                                pPattern->GetItem(ATTR_LINEBREAK, pCondSet).GetValue())
                        {
                            // Disable line break when the cell content is numeric.
                            aAltPatterns.push_back(o3tl::make_unique<ScPatternAttr>(*pPattern));
                            ScPatternAttr* pAltPattern = aAltPatterns.back().get();
                            SfxBoolItem aLineBreak(ATTR_LINEBREAK, false);
                            pAltPattern->GetItemSet().Put(aLineBreak);
                            pPattern = pAltPattern;
                        }

                        SvtScriptType nScript = mpDoc->GetCellScriptType(
                            ScAddress(nCellX, nCellY, nTab),
                            pPattern->GetNumberFormat(mpDoc->GetFormatTable(), pCondSet));

                        if (nScript == SvtScriptType::NONE)
                            nScript = ScGlobal::GetDefaultScriptType();

                        if ( pPattern != pOldPattern || pCondSet != pOldCondSet ||
                             nScript != nOldScript || mbSyntaxMode )
                        {
                            if ( StringDiffer(pOldPattern,pPattern) ||
                                 pCondSet != pOldCondSet || nScript != nOldScript || mbSyntaxMode )
                            {
                                aVars.SetPattern(pPattern, pCondSet, aCell, nScript);
                            }
                            else
                                aVars.SetPatternSimple( pPattern, pCondSet );
                            pOldPattern = pPattern;
                            pOldCondSet = pCondSet;
                            nOldScript = nScript;
                        }

                        //  use edit engine for rotated, stacked or mixed-script text
                        if ( aVars.GetOrient() == SvxCellOrientation::Stacked ||
                             aVars.IsRotated() || IsAmbiguousScript(nScript) )
                            bUseEditEngine = true;
                    }
                    if (bDoCell && !bUseEditEngine)
                    {
                        bool bFormulaCell = (aCell.meType == CELLTYPE_FORMULA);
                        if ( bFormulaCell )
                            lcl_CreateInterpretProgress(bProgress, mpDoc, aCell.mpFormula);
                        if ( aVars.SetText(aCell) )
                            pOldPattern = nullptr;
                        bUseEditEngine = aVars.HasEditCharacters() || (bFormulaCell && aCell.mpFormula->IsMultilineResult());
                    }
                    long nTotalMargin = 0;
                    SvxCellHorJustify eOutHorJust = SvxCellHorJustify::Standard;
                    if (bDoCell && !bUseEditEngine)
                    {
                        CellType eCellType = aCell.meType;
                        bCellIsValue = ( eCellType == CELLTYPE_VALUE );
                        if ( eCellType == CELLTYPE_FORMULA )
                        {
                            ScFormulaCell* pFCell = aCell.mpFormula;
                            bCellIsValue = pFCell->IsRunning() || pFCell->IsValue();
                        }

                        const bool bNumberFormatIsText = lcl_isNumberFormatText( mpDoc, nCellX, nCellY, nTab );
                        eOutHorJust = getAlignmentFromContext( aVars.GetHorJust(), bCellIsValue, aVars.GetString(),
                                *pPattern, pCondSet, mpDoc, nTab, bNumberFormatIsText );

                        bool bBreak = ( aVars.GetLineBreak() || aVars.GetHorJust() == SvxCellHorJustify::Block );
                        // #i111387# #o11817313# disable automatic line breaks only for "General" number format
                        if (bBreak && bCellIsValue && (aVars.GetResultValueFormat() % SV_COUNTRY_LANGUAGE_OFFSET) == 0)
                            bBreak = false;

                        bool bRepeat = aVars.IsRepeat() && !bBreak;
                        bool bShrink = aVars.IsShrink() && !bBreak && !bRepeat;

                        nTotalMargin =
                            static_cast<long>(aVars.GetLeftTotal() * mnPPTX) +
                            static_cast<long>(aVars.GetMargin()->GetRightMargin() * mnPPTX);

                        nNeededWidth = aVars.GetTextSize().Width() + nTotalMargin;

                        // GetOutputArea gives justified rectangles
                        GetOutputArea( nX, nArrY, nPosX, nPosY, nCellX, nCellY, nNeededWidth,
                                       *pPattern, sal::static_int_cast<sal_uInt16>(eOutHorJust),
                                       bCellIsValue || bRepeat || bShrink, bBreak, false,
                                       aAreaParam );

                        aVars.RepeatToFill( aAreaParam.mnColWidth - nTotalMargin );
                        if ( bShrink )
                        {
                            if ( aVars.GetOrient() != SvxCellOrientation::Standard )
                            {
                                // Only horizontal scaling is handled here.
                                // DrawEdit is used to vertically scale 90 deg rotated text.
                                bUseEditEngine = true;
                            }
                            else if ( aAreaParam.mbLeftClip || aAreaParam.mbRightClip )     // horizontal
                            {
                                long nAvailable = aAreaParam.maAlignRect.GetWidth() - nTotalMargin;
                                long nScaleSize = aVars.GetTextSize().Width();         // without margin

                                if ( nAvailable > 0 && nScaleSize > 0 )       // 0 if the text is empty (formulas, number formats)
                                {
                                    long nScale = ( nAvailable * 100 ) / nScaleSize;

                                    aVars.SetShrinkScale( nScale, nOldScript );
                                    long nNewSize = aVars.GetTextSize().Width();

                                    sal_uInt16 nShrinkAgain = 0;
                                    while ( nNewSize > nAvailable && nShrinkAgain < SC_SHRINKAGAIN_MAX )
                                    {
                                        // If the text is still too large, reduce the scale again by 10%, until it fits,
                                        // at most 7 times (it's less than 50% of the calculated scale then).

                                        nScale = ( nScale * 9 ) / 10;
                                        aVars.SetShrinkScale( nScale, nOldScript );
                                        nNewSize = aVars.GetTextSize().Width();
                                        ++nShrinkAgain;
                                    }
                                    // If even at half the size the font still isn't rendered smaller,
                                    // fall back to normal clipping (showing ### for numbers).
                                    if ( nNewSize <= nAvailable )
                                    {
                                        // Reset relevant parameters.
                                        aAreaParam.mbLeftClip = aAreaParam.mbRightClip = false;
                                        aAreaParam.mnLeftClipLength = aAreaParam.mnRightClipLength = 0;
                                    }

                                    pOldPattern = nullptr;
                                }
                            }
                        }

                        if ( bRepeat && !aAreaParam.mbLeftClip && !aAreaParam.mbRightClip )
                        {
                            long nAvailable = aAreaParam.maAlignRect.GetWidth() - nTotalMargin;
                            long nRepeatSize = aVars.GetTextSize().Width();         // without margin
                            // When formatting for the printer, the text sizes don't always add up.
                            // Round down (too few repetitions) rather than exceeding the cell size then:
                            if ( pFmtDevice != mpRefDevice )
                                ++nRepeatSize;
                            if ( nRepeatSize > 0 )
                            {
                                long nRepeatCount = nAvailable / nRepeatSize;
                                if ( nRepeatCount > 1 )
                                {
                                    OUString aCellStr = aVars.GetString();
                                    OUStringBuffer aRepeated = aCellStr;
                                    for ( long nRepeat = 1; nRepeat < nRepeatCount; nRepeat++ )
                                        aRepeated.append(aCellStr);
                                    aVars.SetAutoText( aRepeated.makeStringAndClear() );
                                }
                            }
                        }

                        //  use edit engine if automatic line breaks are needed
                        if ( bBreak )
                        {
                            if ( aVars.GetOrient() == SvxCellOrientation::Standard )
                                bUseEditEngine = ( aAreaParam.mbLeftClip || aAreaParam.mbRightClip );
                            else
                            {
                                long nHeight = aVars.GetTextSize().Height() +
                                                static_cast<long>(aVars.GetMargin()->GetTopMargin()*mnPPTY) +
                                                static_cast<long>(aVars.GetMargin()->GetBottomMargin()*mnPPTY);
                                bUseEditEngine = ( nHeight > aAreaParam.maClipRect.GetHeight() );
                            }
                        }
                        if (!bUseEditEngine)
                        {
                            bUseEditEngine =
                                aVars.GetHorJust() == SvxCellHorJustify::Block &&
                                aVars.GetHorJustMethod() == SvxCellJustifyMethod::Distribute;
                        }
                    }
                    if (bUseEditEngine)
                    {
                        //  mark the cell in CellInfo to be drawn in DrawEdit:
                        //  Cells to the left are marked directly, cells to the
                        //  right are handled by the flag for nX2
                        SCCOL nMarkX = ( nCellX <= nX2 ) ? nCellX : nX2;
                        RowInfo* pMarkRowInfo = ( nCellY == nY ) ? pThisRowInfo : &pRowInfo[0];
                        pMarkRowInfo->pCellInfo[nMarkX+1].bEditEngine = true;
                        bDoCell = false;    // don't draw here
                    }
                    if ( bDoCell )
                    {
                        if ( bCellIsValue && ( aAreaParam.mbLeftClip || aAreaParam.mbRightClip ) )
                        {
                            if (mbShowFormulas)
                                aVars.SetHashText();
                            else
                                // Adjust the decimals to fit the available column width.
                                aVars.SetTextToWidthOrHash(aCell, aAreaParam.mnColWidth - nTotalMargin);

                            nNeededWidth = aVars.GetTextSize().Width() +
                                        static_cast<long>( aVars.GetLeftTotal() * mnPPTX ) +
                                        static_cast<long>( aVars.GetMargin()->GetRightMargin() * mnPPTX );
                            if ( nNeededWidth <= aAreaParam.maClipRect.GetWidth() )
                            {
                                // Cell value is no longer clipped.  Reset relevant parameters.
                                aAreaParam.mbLeftClip = aAreaParam.mbRightClip = false;
                                aAreaParam.mnLeftClipLength = aAreaParam.mnRightClipLength = 0;
                            }

                            //  If the "###" replacement doesn't fit into the cells, no clip marks
                            //  are shown, as the "###" already denotes too little space.
                            //  The rectangles from the first GetOutputArea call remain valid.
                        }

                        long nJustPosX = aAreaParam.maAlignRect.Left();     // "justified" - effect of alignment will be added
                        long nJustPosY = aAreaParam.maAlignRect.Top();
                        long nAvailWidth = aAreaParam.maAlignRect.GetWidth();
                        long nOutHeight = aAreaParam.maAlignRect.GetHeight();

                        bool bOutside = ( aAreaParam.maClipRect.Right() < nScrX || aAreaParam.maClipRect.Left() >= nScrX + nScrW );
                        // Take adjusted values of aAreaParam.mbLeftClip and aAreaParam.mbRightClip
                        bool bVClip = AdjustAreaParamClipRect(aAreaParam);
                        bool bHClip = aAreaParam.mbLeftClip || aAreaParam.mbRightClip;

                        // check horizontal space

                        if ( !bOutside )
                        {
                            bool bRightAdjusted = false;        // to correct text width calculation later
                            switch (eOutHorJust)
                            {
                                case SvxCellHorJustify::Left:
                                    nJustPosX += static_cast<long>( aVars.GetLeftTotal() * mnPPTX );
                                    break;
                                case SvxCellHorJustify::Right:
                                    nJustPosX += nAvailWidth - aVars.GetTextSize().Width() -
                                                static_cast<long>( aVars.GetRightTotal() * mnPPTX );
                                    bRightAdjusted = true;
                                    break;
                                case SvxCellHorJustify::Center:
                                    nJustPosX += ( nAvailWidth - aVars.GetTextSize().Width() +
                                                static_cast<long>( aVars.GetLeftTotal() * mnPPTX ) -
                                                static_cast<long>( aVars.GetMargin()->GetRightMargin() * mnPPTX ) ) / 2;
                                    break;
                                default:
                                {
                                    // added to avoid warnings
                                }
                            }

                            long nTestClipHeight = aVars.GetTextSize().Height();
                            switch (aVars.GetVerJust())
                            {
                                case SvxCellVerJustify::Top:
                                case SvxCellVerJustify::Block:
                                    {
                                        long nTop = static_cast<long>( aVars.GetMargin()->GetTopMargin() * mnPPTY );
                                        nJustPosY += nTop;
                                        nTestClipHeight += nTop;
                                    }
                                    break;
                                case SvxCellVerJustify::Bottom:
                                    {
                                        long nBot = static_cast<long>( aVars.GetMargin()->GetBottomMargin() * mnPPTY );
                                        nJustPosY += nOutHeight - aVars.GetTextSize().Height() - nBot;
                                        nTestClipHeight += nBot;
                                    }
                                    break;
                                case SvxCellVerJustify::Center:
                                    {
                                        long nTop = static_cast<long>( aVars.GetMargin()->GetTopMargin() * mnPPTY );
                                        long nBot = static_cast<long>( aVars.GetMargin()->GetBottomMargin() * mnPPTY );
                                        nJustPosY += ( nOutHeight + nTop -
                                                        aVars.GetTextSize().Height() - nBot ) / 2;
                                        nTestClipHeight += std::abs( nTop - nBot );
                                    }
                                    break;
                                default:
                                {
                                    // added to avoid warnings
                                }
                            }

                            if ( nTestClipHeight > nOutHeight )
                            {
                                // no vertical clipping when printing cells with optimal height,
                                // except when font size is from conditional formatting.
                                if ( eType != OUTTYPE_PRINTER ||
                                        ( mpDoc->GetRowFlags( nCellY, nTab ) & CRFlags::ManualSize ) ||
                                        ( aVars.HasCondHeight() ) )
                                    bVClip = true;
                            }

                            if ( bHClip || bVClip )
                            {
                                // only clip the affected dimension so that not all right-aligned
                                // columns are cut off when performing a non-proportional resize
                                if (!bHClip)
                                {
                                    aAreaParam.maClipRect.SetLeft( nScrX );
                                    aAreaParam.maClipRect.SetRight( nScrX+nScrW );
                                }
                                if (!bVClip)
                                {
                                    aAreaParam.maClipRect.SetTop( nScrY );
                                    aAreaParam.maClipRect.SetBottom( nScrY+nScrH );
                                }

                                //  aClipRect is not used after SetClipRegion/IntersectClipRegion,
                                //  so it can be modified here
                                if (bPixelToLogic)
                                    aAreaParam.maClipRect = mpRefDevice->PixelToLogic( aAreaParam.maClipRect );

                                if (bMetaFile)
                                {
                                    mpDev->Push();
                                    mpDev->IntersectClipRegion( aAreaParam.maClipRect );
                                }
                                else
                                    mpDev->SetClipRegion( vcl::Region( aAreaParam.maClipRect ) );
                            }

                            Point aURLStart( nJustPosX, nJustPosY );    // copy before modifying for orientation

                            switch (aVars.GetOrient())
                            {
                                case SvxCellOrientation::Standard:
                                    nJustPosY += aVars.GetAscent();
                                    break;
                                case SvxCellOrientation::TopBottom:
                                    nJustPosX += aVars.GetTextSize().Width() - aVars.GetAscent();
                                    break;
                                case SvxCellOrientation::BottomUp:
                                    nJustPosY += aVars.GetTextSize().Height();
                                    nJustPosX += aVars.GetAscent();
                                    break;
                                default:
                                {
                                    // added to avoid warnings
                                }
                            }

                            // When clipping, the visible part is now completely defined by the alignment,
                            // there's no more special handling to show the right part of RTL text.

                            Point aDrawTextPos( nJustPosX, nJustPosY );
                            if ( bPixelToLogic )
                            {
                                //  undo text width adjustment in pixels
                                if (bRightAdjusted)
                                    aDrawTextPos.AdjustX(aVars.GetTextSize().Width() );

                                aDrawTextPos = mpRefDevice->PixelToLogic( aDrawTextPos );

                                //  redo text width adjustment in logic units
                                if (bRightAdjusted)
                                    aDrawTextPos.AdjustX( -(aVars.GetOriginalWidth()) );
                            }

                            // in Metafiles always use DrawTextArray to ensure that positions are
                            // recorded (for non-proportional resize):

                            OUString aString = aVars.GetString();
                            if (!aString.isEmpty())
                            {
                                // If the string is clipped, make it shorter for
                                // better performance since drawing by HarfBuzz is
                                // quite expensive especially for long string.

                                OUString aShort = aString;

                                // But never fiddle with numeric values.
                                // (Which was the cause of tdf#86024).
                                // The General automatic format output takes
                                // care of this, or fixed width numbers either fit
                                // or display as ###.
                                if (!bCellIsValue)
                                {
                                    double fVisibleRatio = 1.0;
                                    double fTextWidth = aVars.GetTextSize().Width();
                                    sal_Int32 nTextLen = aString.getLength();
                                    if (eOutHorJust == SvxCellHorJustify::Left && aAreaParam.mnRightClipLength > 0)
                                    {
                                        fVisibleRatio = (fTextWidth - aAreaParam.mnRightClipLength) / fTextWidth;
                                        if (0.0 < fVisibleRatio && fVisibleRatio < 1.0)
                                        {
                                            // Only show the left-end segment.
                                            sal_Int32 nShortLen = fVisibleRatio*nTextLen + 1;
                                            aShort = aShort.copy(0, nShortLen);
                                        }
                                    }
                                    else if (eOutHorJust == SvxCellHorJustify::Right && aAreaParam.mnLeftClipLength > 0)
                                    {
                                        fVisibleRatio = (fTextWidth - aAreaParam.mnLeftClipLength) / fTextWidth;
                                        if (0.0 < fVisibleRatio && fVisibleRatio < 1.0)
                                        {
                                            // Only show the right-end segment.
                                            sal_Int32 nShortLen = fVisibleRatio*nTextLen + 1;
                                            aShort = aShort.copy(nTextLen-nShortLen);

                                            // Adjust the text position after shortening of the string.
                                            double fShortWidth = pFmtDevice->GetTextWidth(aShort);
                                            double fOffset = fTextWidth - fShortWidth;
                                            aDrawTextPos.Move(fOffset, 0);
                                        }
                                    }
                                }

                                // if we are not painting, it means we are interested in
                                // the area of the text that covers the specified cell
                                if (!bPaint && rAddress.Col() == nX)
                                {
                                    tools::Rectangle aRect;
                                    mpDev->GetTextBoundRect(aRect, aShort);
                                    aRect += aDrawTextPos;
                                    return aRect;
                                }

                                if (bMetaFile || pFmtDevice != mpDev || aZoomX != aZoomY)
                                {
                                    size_t nLen = aShort.getLength();
                                    if (aDX.size() < nLen)
                                        aDX.resize(nLen, 0);

                                    pFmtDevice->GetTextArray(aShort, &aDX[0]);

                                    if ( !mpRefDevice->GetConnectMetaFile() ||
                                            mpRefDevice->GetOutDevType() == OUTDEV_PRINTER )
                                    {
                                        double fMul = GetStretch();
                                        for (size_t i = 0; i < nLen; ++i)
                                            aDX[i] = static_cast<sal_Int32>(aDX[i] / fMul + 0.5);
                                    }

                                    if (bPaint)
                                        mpDev->DrawTextArray(aDrawTextPos, aShort, &aDX[0]);
                                }
                                else
                                {
                                    if (bPaint)
                                        mpDev->DrawText(aDrawTextPos, aShort);
                                }
                            }

                            if ( bHClip || bVClip )
                            {
                                if (bMetaFile)
                                    mpDev->Pop();
                                else
                                    mpDev->SetClipRegion();
                            }

                            // PDF: whole-cell hyperlink from formula?
                            bool bHasURL = pPDFData && aCell.meType == CELLTYPE_FORMULA && aCell.mpFormula->IsHyperLinkCell();
                            if (bPaint && bHasURL)
                            {
                                tools::Rectangle aURLRect( aURLStart, aVars.GetTextSize() );
                                lcl_DoHyperlinkResult(mpDev, aURLRect, aCell);
                            }
                        }
                    }
                    nPosX += pRowInfo[0].pCellInfo[nX+1].nWidth * nLayoutSign;
                }
            }
            nPosY += pRowInfo[nArrY].nHeight;
        }
        if ( bProgress )
            ScProgress::DeleteInterpretProgress();

        return tools::Rectangle();
    }

    ScFieldEditEngine* ScOutputData::CreateOutputEditEngine()
    {
        ScFieldEditEngine* pEngine = new ScFieldEditEngine(mpDoc, mpDoc->GetEnginePool());
        pEngine->SetUpdateMode( false );
        // a RefDevice always has to be set, otherwise EditEngine would create a VirtualDevice
        pEngine->SetRefDevice( pFmtDevice );
        EEControlBits nCtrl = pEngine->GetControlWord();
        if ( bShowSpellErrors )
            nCtrl |= EEControlBits::ONLINESPELLING;
        if ( eType == OUTTYPE_PRINTER )
            nCtrl &= ~EEControlBits::MARKFIELDS;
        if ( eType == OUTTYPE_WINDOW && mpRefDevice == pFmtDevice )
            nCtrl &= ~EEControlBits::FORMAT100;       // use the actual MapMode
        pEngine->SetControlWord( nCtrl );
        mpDoc->ApplyAsianEditSettings( *pEngine );
        pEngine->EnableAutoColor( mbUseStyleColor );
        pEngine->SetDefaultHorizontalTextDirection( mpDoc->GetEditTextDirection( nTab ) );
        return pEngine;
    }

    static void lcl_ClearEdit( EditEngine& rEngine )       // text and attributes
    {
        rEngine.SetUpdateMode( false );

        rEngine.SetText(EMPTY_OUSTRING);
        // do not keep any para-attributes
        const SfxItemSet& rPara = rEngine.GetParaAttribs(0);
        if (rPara.Count())
            rEngine.SetParaAttribs( 0,
                        SfxItemSet( *rPara.GetPool(), rPara.GetRanges() ) );
    }

    static bool lcl_SafeIsValue( ScRefCellValue& rCell )
    {
        switch (rCell.meType)
        {
            case CELLTYPE_VALUE:
                return true;
            case CELLTYPE_FORMULA:
            {
                ScFormulaCell* pFCell = rCell.mpFormula;
                if (pFCell->IsRunning() || pFCell->IsValue())
                    return true;
            }
            break;
            default:
            {
                // added to avoid warnings
            }
        }
        return false;
    }

    static void lcl_ScaleFonts( EditEngine& rEngine, long nPercent )
    {
        bool bUpdateMode = rEngine.GetUpdateMode();
        if ( bUpdateMode )
            rEngine.SetUpdateMode( false );

        sal_Int32 nParCount = rEngine.GetParagraphCount();
        for (sal_Int32 nPar=0; nPar<nParCount; nPar++)
        {
            std::vector<sal_Int32> aPortions;
            rEngine.GetPortions( nPar, aPortions );

            sal_Int32 nStart = 0;
            for ( std::vector<sal_Int32>::const_iterator it(aPortions.begin()); it != aPortions.end(); ++it )
            {
                sal_Int32 nEnd = *it;
                ESelection aSel( nPar, nStart, nPar, nEnd );
                SfxItemSet aAttribs = rEngine.GetAttribs( aSel );

                long nWestern = aAttribs.Get(EE_CHAR_FONTHEIGHT).GetHeight();
                long nCJK = aAttribs.Get(EE_CHAR_FONTHEIGHT_CJK).GetHeight();
                long nCTL = aAttribs.Get(EE_CHAR_FONTHEIGHT_CTL).GetHeight();

                nWestern = ( nWestern * nPercent ) / 100;
                nCJK     = ( nCJK     * nPercent ) / 100;
                nCTL     = ( nCTL     * nPercent ) / 100;

                aAttribs.Put( SvxFontHeightItem( nWestern, 100, EE_CHAR_FONTHEIGHT ) );
                aAttribs.Put( SvxFontHeightItem( nCJK, 100, EE_CHAR_FONTHEIGHT_CJK ) );
                aAttribs.Put( SvxFontHeightItem( nCTL, 100, EE_CHAR_FONTHEIGHT_CTL ) );

                rEngine.QuickSetAttribs( aAttribs, aSel );      //! remove paragraph attributes from aAttribs?

                nStart = nEnd;
            }
        }

        if ( bUpdateMode )
            rEngine.SetUpdateMode( true );
    }

    static long lcl_GetEditSize( EditEngine& rEngine, bool bWidth, bool bSwap, long nAttrRotate )
    {
        if ( bSwap )
            bWidth = !bWidth;

        if ( nAttrRotate )
        {
            long nRealWidth  = static_cast<long>(rEngine.CalcTextWidth());
            long nRealHeight = rEngine.GetTextHeight();

            // assuming standard mode, otherwise width isn't used

            double nRealOrient = nAttrRotate * F_PI18000;   // 1/100th degrees
            double nAbsCos = fabs( cos( nRealOrient ) );
            double nAbsSin = fabs( sin( nRealOrient ) );
            if ( bWidth )
                return static_cast<long>( nRealWidth * nAbsCos + nRealHeight * nAbsSin );
            else
                return static_cast<long>( nRealHeight * nAbsCos + nRealWidth * nAbsSin );
        }
        else if ( bWidth )
            return static_cast<long>(rEngine.CalcTextWidth());
        else
            return rEngine.GetTextHeight();
    }

    void ScOutputData::ShrinkEditEngine( EditEngine& rEngine, const tools::Rectangle& rAlignRect,
                long nLeftM, long nTopM, long nRightM, long nBottomM,
                bool bWidth, SvxCellOrientation nOrient, long nAttrRotate, bool bPixelToLogic,
                long& rEngineWidth, long& rEngineHeight, long& rNeededPixel, bool& rLeftClip, bool& rRightClip )
    {
        if ( !bWidth )
        {
            // vertical

            long nScaleSize = bPixelToLogic ?
                mpRefDevice->LogicToPixel(Size(0,rEngineHeight)).Height() : rEngineHeight;

            // Don't scale if it fits already.
            // Allowing to extend into the margin, to avoid scaling at optimal height.
            if ( nScaleSize <= rAlignRect.GetHeight() )
                return;

            bool bSwap = ( nOrient == SvxCellOrientation::TopBottom || nOrient == SvxCellOrientation::BottomUp );
            long nAvailable = rAlignRect.GetHeight() - nTopM - nBottomM;
            long nScale = ( nAvailable * 100 ) / nScaleSize;

            lcl_ScaleFonts( rEngine, nScale );
            rEngineHeight = lcl_GetEditSize( rEngine, false, bSwap, nAttrRotate );
            long nNewSize = bPixelToLogic ?
                mpRefDevice->LogicToPixel(Size(0,rEngineHeight)).Height() : rEngineHeight;

            sal_uInt16 nShrinkAgain = 0;
            while ( nNewSize > nAvailable && nShrinkAgain < SC_SHRINKAGAIN_MAX )
            {
                // further reduce, like in DrawStrings
                lcl_ScaleFonts( rEngine, 90 );     // reduce by 10%
                rEngineHeight = lcl_GetEditSize( rEngine, false, bSwap, nAttrRotate );
                nNewSize = bPixelToLogic ?
                    mpRefDevice->LogicToPixel(Size(0,rEngineHeight)).Height() : rEngineHeight;
                ++nShrinkAgain;
            }

            // sizes for further processing (alignment etc):
            rEngineWidth = lcl_GetEditSize( rEngine, true, bSwap, nAttrRotate );
            long nPixelWidth = bPixelToLogic ?
                mpRefDevice->LogicToPixel(Size(rEngineWidth,0)).Width() : rEngineWidth;
            rNeededPixel = nPixelWidth + nLeftM + nRightM;
        }
        else if ( rLeftClip || rRightClip )
        {
            // horizontal

            long nAvailable = rAlignRect.GetWidth() - nLeftM - nRightM;
            long nScaleSize = rNeededPixel - nLeftM - nRightM;      // without margin

            if ( nScaleSize <= nAvailable )
                return;

            long nScale = ( nAvailable * 100 ) / nScaleSize;

            lcl_ScaleFonts( rEngine, nScale );
            rEngineWidth = lcl_GetEditSize( rEngine, true, false, nAttrRotate );
            long nNewSize = bPixelToLogic ?
                mpRefDevice->LogicToPixel(Size(rEngineWidth,0)).Width() : rEngineWidth;

            sal_uInt16 nShrinkAgain = 0;
            while ( nNewSize > nAvailable && nShrinkAgain < SC_SHRINKAGAIN_MAX )
            {
                // further reduce, like in DrawStrings
                lcl_ScaleFonts( rEngine, 90 );     // reduce by 10%
                rEngineWidth = lcl_GetEditSize( rEngine, true, false, nAttrRotate );
                nNewSize = bPixelToLogic ?
                    mpRefDevice->LogicToPixel(Size(rEngineWidth,0)).Width() : rEngineWidth;
                ++nShrinkAgain;
            }
            if ( nNewSize <= nAvailable )
                rLeftClip = rRightClip = false;

            // sizes for further processing (alignment etc):
            rNeededPixel = nNewSize + nLeftM + nRightM;
            rEngineHeight = lcl_GetEditSize( rEngine, false, false, nAttrRotate );
        }
    }

    ScOutputData::DrawEditParam::DrawEditParam(const ScPatternAttr* pPattern, const SfxItemSet* pCondSet, bool bCellIsValue) :
        meHorJustAttr( lcl_GetValue<SvxHorJustifyItem, SvxCellHorJustify>(*pPattern, ATTR_HOR_JUSTIFY, pCondSet) ),
        meHorJustContext( meHorJustAttr ),
        meHorJustResult( meHorJustAttr ),
        meVerJust( lcl_GetValue<SvxVerJustifyItem, SvxCellVerJustify>(*pPattern, ATTR_VER_JUSTIFY, pCondSet) ),
        meHorJustMethod( lcl_GetValue<SvxJustifyMethodItem, SvxCellJustifyMethod>(*pPattern, ATTR_HOR_JUSTIFY_METHOD, pCondSet) ),
        meVerJustMethod( lcl_GetValue<SvxJustifyMethodItem, SvxCellJustifyMethod>(*pPattern, ATTR_VER_JUSTIFY_METHOD, pCondSet) ),
        meOrient( pPattern->GetCellOrientation(pCondSet) ),
        mnArrY(0),
        mnX(0), mnCellX(0), mnCellY(0),
        mnPosX(0), mnPosY(0), mnInitPosX(0),
        mbBreak( (meHorJustAttr == SvxCellHorJustify::Block) || lcl_GetBoolValue(*pPattern, ATTR_LINEBREAK, pCondSet) ),
        mbCellIsValue(bCellIsValue),
        mbAsianVertical(false),
        mbPixelToLogic(false),
        mbHyphenatorSet(false),
        mpEngine(nullptr),
        mpPattern(pPattern),
        mpCondSet(pCondSet),
        mpPreviewFontSet(nullptr),
        mpOldPattern(nullptr),
        mpOldCondSet(nullptr),
        mpOldPreviewFontSet(nullptr),
        mpThisRowInfo(nullptr),
        mpMisspellRanges(nullptr)
    {}

    bool ScOutputData::DrawEditParam::readCellContent(
        const ScDocument* pDoc, bool bShowNullValues, bool bShowFormulas, bool bSyntaxMode, bool bUseStyleColor, bool bForceAutoColor, bool& rWrapFields)
    {
        if (maCell.meType == CELLTYPE_EDIT)
        {
            const EditTextObject* pData = maCell.mpEditText;
            if (pData)
            {
                mpEngine->SetText(*pData);

                if ( mbBreak && !mbAsianVertical && pData->HasField() )
                {
                    //  Fields aren't wrapped, so clipping is enabled to prevent
                    //  a field from being drawn beyond the cell size

                    rWrapFields = true;
                }
            }
            else
            {
                OSL_FAIL("pData == 0");
                return false;
            }
        }
        else
        {
            sal_uInt32 nFormat = mpPattern->GetNumberFormat(
                                        pDoc->GetFormatTable(), mpCondSet );
            OUString aString;
            Color* pColor;
            ScCellFormat::GetString( maCell,
                                     nFormat,aString, &pColor,
                                     *pDoc->GetFormatTable(),
                                     pDoc,
                                     bShowNullValues,
                                     bShowFormulas);

            mpEngine->SetText(aString);
            if ( pColor && !bSyntaxMode && !( bUseStyleColor && bForceAutoColor ) )
                lcl_SetEditColor( *mpEngine, *pColor );
        }

        if (mpMisspellRanges)
            mpEngine->SetAllMisspellRanges(*mpMisspellRanges);

        return true;
    }

    void ScOutputData::DrawEditParam::setPatternToEngine(bool bUseStyleColor)
    {
        // syntax highlighting mode is ignored here
        // StringDiffer doesn't look at hyphenate, language items

        if (mpPattern == mpOldPattern && mpCondSet == mpOldCondSet && mpPreviewFontSet == mpOldPreviewFontSet )
            return;

        Color nConfBackColor = SC_MOD()->GetColorConfig().GetColorValue(svtools::DOCCOLOR).nColor;
        bool bCellContrast = bUseStyleColor &&
                Application::GetSettings().GetStyleSettings().GetHighContrastMode();

        SfxItemSet* pSet = new SfxItemSet( mpEngine->GetEmptyItemSet() );
        mpPattern->FillEditItemSet( pSet, mpCondSet );
        if ( mpPreviewFontSet )
        {
            const SfxPoolItem* pItem;
            if ( mpPreviewFontSet->GetItemState( ATTR_FONT, true, &pItem ) == SfxItemState::SET )
            {
                SvxFontItem aFontItem(EE_CHAR_FONTINFO);
                aFontItem = static_cast<const SvxFontItem&>(*pItem);
                pSet->Put( aFontItem );
            }
            if ( mpPreviewFontSet->GetItemState( ATTR_CJK_FONT, true, &pItem ) == SfxItemState::SET )
            {
                SvxFontItem aCjkFontItem(EE_CHAR_FONTINFO_CJK);
                aCjkFontItem = static_cast<const SvxFontItem&>(*pItem);
                pSet->Put( aCjkFontItem );
            }
            if ( mpPreviewFontSet->GetItemState( ATTR_CTL_FONT, true, &pItem ) == SfxItemState::SET )
            {
                SvxFontItem aCtlFontItem(EE_CHAR_FONTINFO_CTL);
                aCtlFontItem = static_cast<const SvxFontItem&>(*pItem);
                pSet->Put( aCtlFontItem );
            }
        }
        mpEngine->SetDefaults( pSet );
        mpOldPattern = mpPattern;
        mpOldCondSet = mpCondSet;
        mpOldPreviewFontSet = mpPreviewFontSet;

        EEControlBits nControl = mpEngine->GetControlWord();
        if (meOrient == SvxCellOrientation::Stacked)
            nControl |= EEControlBits::ONECHARPERLINE;
        else
            nControl &= ~EEControlBits::ONECHARPERLINE;
        mpEngine->SetControlWord( nControl );

        if ( !mbHyphenatorSet && pSet->Get(EE_PARA_HYPHENATE).GetValue() )
        {
            //  set hyphenator the first time it is needed
            css::uno::Reference<css::linguistic2::XHyphenator> xXHyphenator( LinguMgr::GetHyphenator() );
            mpEngine->SetHyphenator( xXHyphenator );
            mbHyphenatorSet = true;
        }

        Color aBackCol = mpPattern->GetItem( ATTR_BACKGROUND, mpCondSet ).GetColor();
        if ( bUseStyleColor && ( aBackCol.GetTransparency() > 0 || bCellContrast ) )
            aBackCol = nConfBackColor;
        mpEngine->SetBackgroundColor( aBackCol );
    }

    void ScOutputData::DrawEditParam::calcMargins(long& rTopM, long& rLeftM, long& rBottomM, long& rRightM, double nPPTX, double nPPTY) const
    {
        const SvxMarginItem& rMargin = mpPattern->GetItem(ATTR_MARGIN, mpCondSet);

        sal_uInt16 nIndent = 0;
        if (meHorJustAttr == SvxCellHorJustify::Left || meHorJustAttr == SvxCellHorJustify::Right)
            nIndent = lcl_GetValue<SfxUInt16Item, sal_uInt16>(*mpPattern, ATTR_INDENT, mpCondSet);

        rLeftM   = static_cast<long>(((rMargin.GetLeftMargin() + nIndent) * nPPTX));
        rTopM    = static_cast<long>((rMargin.GetTopMargin() * nPPTY));
        rRightM  = static_cast<long>((rMargin.GetRightMargin() * nPPTX));
        rBottomM = static_cast<long>((rMargin.GetBottomMargin() * nPPTY));
        if(meHorJustAttr == SvxCellHorJustify::Right)
        {
            rLeftM   = static_cast<long>((rMargin.GetLeftMargin()  * nPPTX));
            rRightM  = static_cast<long>(((rMargin.GetRightMargin() + nIndent) * nPPTX));
        }
    }

    void ScOutputData::DrawEditParam::calcPaperSize(
        Size& rPaperSize, const tools::Rectangle& rAlignRect, double nPPTX, double nPPTY) const
    {
        long nTopM, nLeftM, nBottomM, nRightM;
        calcMargins(nTopM, nLeftM, nBottomM, nRightM, nPPTX, nPPTY);

        if (isVerticallyOriented())
        {
            rPaperSize.setWidth( rAlignRect.GetHeight() - nTopM - nBottomM );
            rPaperSize.setHeight( rAlignRect.GetWidth() - nLeftM - nRightM );
        }
        else
        {
            rPaperSize.setWidth( rAlignRect.GetWidth() - nLeftM - nRightM );
            rPaperSize.setHeight( rAlignRect.GetHeight() - nTopM - nBottomM );
        }

        if (mbAsianVertical)
        {
            rPaperSize.setHeight( rAlignRect.GetHeight() - nTopM - nBottomM );
            // Subtract some extra value from the height or else the text would go
            // outside the cell area.  The value of 5 is arbitrary, and is based
            // entirely on heuristics.
            rPaperSize.AdjustHeight( -5 );
        }
    }

    void ScOutputData::DrawEditParam::getEngineSize(ScFieldEditEngine* pEngine, long& rWidth, long& rHeight) const
    {
        long nEngineWidth = 0;
        if (!mbBreak || meOrient == SvxCellOrientation::Stacked || mbAsianVertical)
            nEngineWidth = static_cast<long>(pEngine->CalcTextWidth());

        long nEngineHeight = pEngine->GetTextHeight();

        if (isVerticallyOriented())
        {
            long nTemp = nEngineWidth;
            nEngineWidth = nEngineHeight;
            nEngineHeight = nTemp;
        }

        if (meOrient == SvxCellOrientation::Stacked)
            nEngineWidth = nEngineWidth * 11 / 10;

        rWidth = nEngineWidth;
        rHeight = nEngineHeight;
    }

    bool ScOutputData::DrawEditParam::hasLineBreak() const
    {
        return (mbBreak || (meOrient == SvxCellOrientation::Stacked) || mbAsianVertical);
    }

    bool ScOutputData::DrawEditParam::isHyperlinkCell() const
    {
        if (maCell.meType != CELLTYPE_FORMULA)
            return false;

        return maCell.mpFormula->IsHyperLinkCell();
    }

    bool ScOutputData::DrawEditParam::isVerticallyOriented() const
    {
        return (meOrient == SvxCellOrientation::TopBottom || meOrient == SvxCellOrientation::BottomUp);
    }

    void ScOutputData::DrawEditParam::calcStartPosForVertical(
        Point& rLogicStart, long nCellWidth, long nEngineWidth, long nTopM, const OutputDevice* pRefDevice)
    {
        OSL_ENSURE(isVerticallyOriented(), "Use this only for vertically oriented cell!");

        if (mbPixelToLogic)
            rLogicStart = pRefDevice->PixelToLogic(rLogicStart);

        if (mbBreak)
        {
            // vertical adjustment is within the EditEngine
            if (mbPixelToLogic)
                rLogicStart.AdjustY(pRefDevice->PixelToLogic(Size(0,nTopM)).Height() );
            else
                rLogicStart.AdjustY(nTopM );

            switch (meHorJustResult)
            {
                case SvxCellHorJustify::Center:
                    rLogicStart.AdjustX((nCellWidth - nEngineWidth) / 2 );
                break;
                case SvxCellHorJustify::Right:
                    rLogicStart.AdjustX(nCellWidth - nEngineWidth );
                break;
                default:
                    ; // do nothing
            }
        }
    }

    void ScOutputData::DrawEditParam::setAlignmentToEngine()
    {
        if (isVerticallyOriented() || mbAsianVertical)
        {
            SvxAdjust eSvxAdjust = SvxAdjust::Left;
            switch (meVerJust)
            {
                case SvxCellVerJustify::Top:
                    eSvxAdjust = (meOrient == SvxCellOrientation::TopBottom || mbAsianVertical) ?
                                SvxAdjust::Left : SvxAdjust::Right;
                    break;
                case SvxCellVerJustify::Center:
                    eSvxAdjust = SvxAdjust::Center;
                    break;
                case SvxCellVerJustify::Bottom:
                case SvxCellVerJustify::Standard:
                    eSvxAdjust = (meOrient == SvxCellOrientation::TopBottom || mbAsianVertical) ?
                                SvxAdjust::Right : SvxAdjust::Left;
                    break;
                case SvxCellVerJustify::Block:
                    eSvxAdjust = SvxAdjust::Block;
                    break;
            }

            mpEngine->SetDefaultItem( SvxAdjustItem(eSvxAdjust, EE_PARA_JUST) );
            mpEngine->SetDefaultItem( SvxJustifyMethodItem(meVerJustMethod, EE_PARA_JUST_METHOD) );

            if (meHorJustResult == SvxCellHorJustify::Block)
                mpEngine->SetDefaultItem( SvxVerJustifyItem(SvxCellVerJustify::Block, EE_PARA_VER_JUST) );
        }
        else
        {
            //  horizontal alignment now may depend on cell content
            //  (for values with number formats with mixed script types)
            //  -> always set adjustment

            SvxAdjust eSvxAdjust = SvxAdjust::Left;
            if (meOrient == SvxCellOrientation::Stacked)
                eSvxAdjust = SvxAdjust::Center;
            else if (mbBreak)
            {
                if (meOrient == SvxCellOrientation::Standard)
                    switch (meHorJustResult)
                    {
                        case SvxCellHorJustify::Repeat:            // repeat is not yet implemented
                        case SvxCellHorJustify::Standard:
                            SAL_WARN("sc.ui","meHorJustResult does not match getAlignmentFromContext()");
                            SAL_FALLTHROUGH;
                        case SvxCellHorJustify::Left:
                            eSvxAdjust = SvxAdjust::Left;
                            break;
                        case SvxCellHorJustify::Center:
                            eSvxAdjust = SvxAdjust::Center;
                            break;
                        case SvxCellHorJustify::Right:
                            eSvxAdjust = SvxAdjust::Right;
                            break;
                        case SvxCellHorJustify::Block:
                            eSvxAdjust = SvxAdjust::Block;
                            break;
                    }
                else
                    switch (meVerJust)
                    {
                        case SvxCellVerJustify::Top:
                            eSvxAdjust = SvxAdjust::Right;
                            break;
                        case SvxCellVerJustify::Center:
                            eSvxAdjust = SvxAdjust::Center;
                            break;
                        case SvxCellVerJustify::Bottom:
                        case SvxCellVerJustify::Standard:
                            eSvxAdjust = SvxAdjust::Left;
                            break;
                        case SvxCellVerJustify::Block:
                            eSvxAdjust = SvxAdjust::Block;
                            break;
                    }
            }

            mpEngine->SetDefaultItem( SvxAdjustItem(eSvxAdjust, EE_PARA_JUST) );

            if (mbAsianVertical)
            {
                mpEngine->SetDefaultItem( SvxJustifyMethodItem(meVerJustMethod, EE_PARA_JUST_METHOD) );
                if (meHorJustResult == SvxCellHorJustify::Block)
                    mpEngine->SetDefaultItem( SvxVerJustifyItem(SvxCellVerJustify::Block, EE_PARA_VER_JUST) );
            }
            else
            {
                mpEngine->SetDefaultItem( SvxJustifyMethodItem(meHorJustMethod, EE_PARA_JUST_METHOD) );
                if (meVerJust == SvxCellVerJustify::Block)
                    mpEngine->SetDefaultItem( SvxVerJustifyItem(SvxCellVerJustify::Block, EE_PARA_VER_JUST) );
            }
        }

        mpEngine->SetVertical(mbAsianVertical);
        if (maCell.meType == CELLTYPE_EDIT)
        {
            // We need to synchronize the vertical mode in the EditTextObject
            // instance too.  No idea why we keep this state in two separate
            // instances.
            const EditTextObject* pData = maCell.mpEditText;
            if (pData)
                const_cast<EditTextObject*>(pData)->SetVertical(mbAsianVertical);
        }
    }

    bool ScOutputData::DrawEditParam::adjustHorAlignment(ScFieldEditEngine* pEngine)
    {
        if (meHorJustResult == SvxCellHorJustify::Right || meHorJustResult == SvxCellHorJustify::Center)
        {
            SvxAdjust eEditAdjust = (meHorJustResult == SvxCellHorJustify::Center) ?
                SvxAdjust::Center : SvxAdjust::Right;

            pEngine->SetUpdateMode(false);
            pEngine->SetDefaultItem( SvxAdjustItem(eEditAdjust, EE_PARA_JUST) );
            pEngine->SetUpdateMode(true);
            return true;
        }
        return false;
    }

    void ScOutputData::DrawEditParam::adjustForHyperlinkInPDF(Point aURLStart, const OutputDevice* pDev)
    {
        // PDF: whole-cell hyperlink from formula?
        vcl::PDFExtOutDevData* pPDFData = dynamic_cast<vcl::PDFExtOutDevData* >( pDev->GetExtOutDevData() );
        bool bHasURL = pPDFData && isHyperlinkCell();
        if (!bHasURL)
            return;

        long nURLWidth = static_cast<long>(mpEngine->CalcTextWidth());
        long nURLHeight = mpEngine->GetTextHeight();
        if (mbBreak)
        {
            Size aPaper = mpEngine->GetPaperSize();
            if ( mbAsianVertical )
                nURLHeight = aPaper.Height();
            else
                nURLWidth = aPaper.Width();
        }
        if (isVerticallyOriented())
            std::swap( nURLWidth, nURLHeight );
        else if (mbAsianVertical)
            aURLStart.AdjustX( -nURLWidth );

        tools::Rectangle aURLRect( aURLStart, Size( nURLWidth, nURLHeight ) );
        lcl_DoHyperlinkResult(pDev, aURLRect, maCell);
    }

    // Returns true if the rect is clipped vertically
    bool ScOutputData::AdjustAreaParamClipRect(OutputAreaParam& rAreaParam)
    {
        if( rAreaParam.maClipRect.Left() < nScrX )
        {
            rAreaParam.maClipRect.SetLeft( nScrX );
            rAreaParam.mbLeftClip = true;
        }
        if( rAreaParam.maClipRect.Right() > nScrX + nScrW )
        {
            rAreaParam.maClipRect.SetRight( nScrX + nScrW );          //! minus one?
            rAreaParam.mbRightClip = true;
        }

        bool bVClip = false;

        if( rAreaParam.maClipRect.Top() < nScrY )
        {
            rAreaParam.maClipRect.SetTop( nScrY );
            bVClip = true;
        }
        if( rAreaParam.maClipRect.Bottom() > nScrY + nScrH )
        {
            rAreaParam.maClipRect.SetBottom( nScrY + nScrH );     //! minus one?
            bVClip = true;
        }
        return bVClip;
    }

    // Doesn't handle clip marks - should be handled in advance using GetOutputArea
    class ClearableClipRegion
    {
    public:
        ClearableClipRegion( const tools::Rectangle& rRect, bool bClip, bool bSimClip,
                            const VclPtr<OutputDevice>& pDev, bool bMetaFile )
            :mbMetaFile(bMetaFile)
        {
            if (bClip || bSimClip)
            {
                maRect = rRect;
                if (bClip)  // for bSimClip only initialize aClipRect
                {
                    mpDev.reset(pDev);
                    if (mbMetaFile)
                    {
                        mpDev->Push();
                        mpDev->IntersectClipRegion(maRect);
                    }
                    else
                        mpDev->SetClipRegion(vcl::Region(maRect));
                }
            }
        }

        ~ClearableClipRegion()
        {
            // Pop() or SetClipRegion() must only be called in case bClip was true
            // in the ctor, and only then mpDev is set.
            if (mpDev)
            {
                if (mbMetaFile)
                    mpDev->Pop();
                else
                    mpDev->SetClipRegion();
            }
        }

        const tools::Rectangle& getRect() const { return maRect; }

    private:
        tools::Rectangle        maRect;
        VclPtr<OutputDevice>    mpDev;
        bool                    mbMetaFile;
    };

    // Returns needed width in current units; sets rNeededPixel to needed width in pixels
    long ScOutputData::SetEngineTextAndGetWidth( DrawEditParam& rParam, const OUString& rSetString,
                                                 long& rNeededPixel, long nAddWidthPixels )
    {
        rParam.mpEngine->SetText( rSetString );
        long nEngineWidth = static_cast<long>( rParam.mpEngine->CalcTextWidth() );
        if ( rParam.mbPixelToLogic )
            rNeededPixel = mpRefDevice->LogicToPixel( Size( nEngineWidth, 0 ) ).Width();
        else
            rNeededPixel = nEngineWidth;

        rNeededPixel += nAddWidthPixels;

        return nEngineWidth;
    }

    void ScOutputData::DrawEditStandard(DrawEditParam& rParam)
    {
        OSL_ASSERT(rParam.meOrient == SvxCellOrientation::Standard);
        OSL_ASSERT(!rParam.mbAsianVertical);

        Size aRefOne = mpRefDevice->PixelToLogic(Size(1,1));

        bool bRepeat = (rParam.meHorJustAttr == SvxCellHorJustify::Repeat && !rParam.mbBreak);
        bool bShrink = !rParam.mbBreak && !bRepeat && lcl_GetBoolValue(*rParam.mpPattern, ATTR_SHRINKTOFIT, rParam.mpCondSet);
        long nAttrRotate = lcl_GetValue<SfxInt32Item, long>(*rParam.mpPattern, ATTR_ROTATE_VALUE, rParam.mpCondSet);

        if ( rParam.meHorJustAttr == SvxCellHorJustify::Repeat )
        {
            // ignore orientation/rotation if "repeat" is active
            rParam.meOrient = SvxCellOrientation::Standard;
            nAttrRotate = 0;

            // #i31843# "repeat" with "line breaks" is treated as default alignment
            // (but rotation is still disabled).
            // Default again leads to context dependent alignment instead of
            // SvxCellHorJustify::Standard.
            if ( rParam.mbBreak )
                rParam.meHorJustResult = rParam.meHorJustContext;
        }

        if (nAttrRotate)
        {
            //! set flag to find the cell in DrawRotated again ?
            //! (or flag already set during DrawBackground, then no query here)
            return;     // rotated is outputted separately
        }

        SvxCellHorJustify eOutHorJust = rParam.meHorJustContext;

        //! mirror margin values for RTL?
        //! move margin down to after final GetOutputArea call
        long nTopM, nLeftM, nBottomM, nRightM;
        rParam.calcMargins(nTopM, nLeftM, nBottomM, nRightM, mnPPTX, mnPPTY);

        SCCOL nXForPos = rParam.mnX;
        if ( nXForPos < nX1 )
        {
            nXForPos = nX1;
            rParam.mnPosX = rParam.mnInitPosX;
        }
        SCSIZE nArrYForPos = rParam.mnArrY;
        if ( nArrYForPos < 1 )
        {
            nArrYForPos = 1;
            rParam.mnPosY = nScrY;
        }

        OutputAreaParam aAreaParam;

        //  Initial page size - large for normal text, cell size for automatic line breaks

        Size aPaperSize( 1000000, 1000000 );
        if (rParam.mbBreak)
        {
            //  call GetOutputArea with nNeeded=0, to get only the cell width

            //! handle nArrY == 0
            GetOutputArea( nXForPos, nArrYForPos, rParam.mnPosX, rParam.mnPosY, rParam.mnCellX, rParam.mnCellY, 0,
                           *rParam.mpPattern, sal::static_int_cast<sal_uInt16>(eOutHorJust),
                           rParam.mbCellIsValue, true, false, aAreaParam );

            //! special ScEditUtil handling if formatting for printer
            rParam.calcPaperSize(aPaperSize, aAreaParam.maAlignRect, mnPPTX, mnPPTY);
        }
        if (rParam.mbPixelToLogic)
        {
            Size aLogicSize = mpRefDevice->PixelToLogic(aPaperSize);
            if ( rParam.mbBreak && !rParam.mbAsianVertical && mpRefDevice != pFmtDevice )
            {
                // #i85342# screen display and formatting for printer,
                // use same GetEditArea call as in ScViewData::SetEditEngine

                Fraction aFract(1,1);
                tools::Rectangle aUtilRect = ScEditUtil( mpDoc, rParam.mnCellX, rParam.mnCellY, nTab, Point(0,0), pFmtDevice,
                    HMM_PER_TWIPS, HMM_PER_TWIPS, aFract, aFract ).GetEditArea( rParam.mpPattern, false );
                aLogicSize.setWidth( aUtilRect.GetWidth() );
            }
            rParam.mpEngine->SetPaperSize(aLogicSize);
        }
        else
            rParam.mpEngine->SetPaperSize(aPaperSize);

        //  Fill the EditEngine (cell attributes and text)

        // default alignment for asian vertical mode is top-right
        if ( rParam.mbAsianVertical && rParam.meVerJust == SvxCellVerJustify::Standard )
            rParam.meVerJust = SvxCellVerJustify::Top;

        rParam.setPatternToEngine(mbUseStyleColor);
        rParam.setAlignmentToEngine();

        //  Read content from cell

        bool bWrapFields = false;
        if (!rParam.readCellContent(mpDoc, mbShowNullValues, mbShowFormulas, mbSyntaxMode, mbUseStyleColor, mbForceAutoColor, bWrapFields))
            // Failed to read cell content.  Bail out.
            return;

        if ( mbSyntaxMode )
            SetEditSyntaxColor(*rParam.mpEngine, rParam.maCell);
        else if ( mbUseStyleColor && mbForceAutoColor )
            lcl_SetEditColor( *rParam.mpEngine, COL_AUTO );     //! or have a flag at EditEngine

        rParam.mpEngine->SetUpdateMode( true );     // after SetText, before CalcTextWidth/GetTextHeight

        //  Get final output area using the calculated width

        long nEngineWidth, nEngineHeight;
        rParam.getEngineSize(rParam.mpEngine, nEngineWidth, nEngineHeight);

        long nNeededPixel = nEngineWidth;
        if (rParam.mbPixelToLogic)
            nNeededPixel = mpRefDevice->LogicToPixel(Size(nNeededPixel,0)).Width();
        nNeededPixel += nLeftM + nRightM;

        if (!rParam.mbBreak || bShrink)
        {
            // for break, the first GetOutputArea call is sufficient
            GetOutputArea( nXForPos, nArrYForPos, rParam.mnPosX, rParam.mnPosY, rParam.mnCellX, rParam.mnCellY, nNeededPixel,
                           *rParam.mpPattern, sal::static_int_cast<sal_uInt16>(eOutHorJust),
                           rParam.mbCellIsValue || bRepeat || bShrink, false, false, aAreaParam );

            if ( bShrink )
            {
                ShrinkEditEngine( *rParam.mpEngine, aAreaParam.maAlignRect,
                    nLeftM, nTopM, nRightM, nBottomM, true,
                    rParam.meOrient, 0, rParam.mbPixelToLogic,
                    nEngineWidth, nEngineHeight, nNeededPixel,
                    aAreaParam.mbLeftClip, aAreaParam.mbRightClip );
            }
            if ( bRepeat && !aAreaParam.mbLeftClip && !aAreaParam.mbRightClip && rParam.mpEngine->GetParagraphCount() == 1 )
            {
                // First check if twice the space for the formatted text is available
                // (otherwise just keep it unchanged).

                long nFormatted = nNeededPixel - nLeftM - nRightM;      // without margin
                long nAvailable = aAreaParam.maAlignRect.GetWidth() - nLeftM - nRightM;
                if ( nAvailable >= 2 * nFormatted )
                {
                    // "repeat" is handled with unformatted text (for performance reasons)
                    OUString aCellStr = rParam.mpEngine->GetText();

                    long nRepeatSize = 0;
                    SetEngineTextAndGetWidth( rParam, aCellStr, nRepeatSize, 0 );
                    if ( pFmtDevice != mpRefDevice )
                        ++nRepeatSize;
                    if ( nRepeatSize > 0 )
                    {
                        long nRepeatCount = nAvailable / nRepeatSize;
                        if ( nRepeatCount > 1 )
                        {
                            OUStringBuffer aRepeated = aCellStr;
                            for ( long nRepeat = 1; nRepeat < nRepeatCount; nRepeat++ )
                                aRepeated.append(aCellStr);

                            nEngineWidth = SetEngineTextAndGetWidth( rParam, aRepeated.makeStringAndClear(),
                                                            nNeededPixel, (nLeftM + nRightM ) );

                            nEngineHeight = rParam.mpEngine->GetTextHeight();
                        }
                    }
                }
            }

            if ( rParam.mbCellIsValue && ( aAreaParam.mbLeftClip || aAreaParam.mbRightClip ) )
            {
                nEngineWidth = SetEngineTextAndGetWidth( rParam, "###", nNeededPixel, ( nLeftM + nRightM ) );

                //  No clip marks if "###" doesn't fit (same as in DrawStrings)
            }

            if (eOutHorJust != SvxCellHorJustify::Left)
            {
                aPaperSize.setWidth( nNeededPixel + 1 );
                if (rParam.mbPixelToLogic)
                    rParam.mpEngine->SetPaperSize(mpRefDevice->PixelToLogic(aPaperSize));
                else
                    rParam.mpEngine->SetPaperSize(aPaperSize);
            }
        }

        long nStartX = aAreaParam.maAlignRect.Left();
        long nStartY = aAreaParam.maAlignRect.Top();
        long nCellWidth = aAreaParam.maAlignRect.GetWidth();
        long nOutWidth = nCellWidth - 1 - nLeftM - nRightM;
        long nOutHeight = aAreaParam.maAlignRect.GetHeight() - nTopM - nBottomM;

        if (rParam.mbBreak)
        {
            //  text with automatic breaks is aligned only within the
            //  edit engine's paper size, the output of the whole area
            //  is always left-aligned

            nStartX += nLeftM;
        }
        else
        {
            if ( eOutHorJust == SvxCellHorJustify::Right )
                nStartX -= nNeededPixel - nCellWidth + nRightM + 1;
            else if ( eOutHorJust == SvxCellHorJustify::Center )
                nStartX -= ( nNeededPixel - nCellWidth + nRightM + 1 - nLeftM ) / 2;
            else
                nStartX += nLeftM;
        }

        bool bOutside = (aAreaParam.maClipRect.Right() < nScrX || aAreaParam.maClipRect.Left() >= nScrX + nScrW);
        if (bOutside)
            return;

        // Also take fields in a cell with automatic breaks into account: clip to cell width
        bool bClip = AdjustAreaParamClipRect(aAreaParam) || aAreaParam.mbLeftClip || aAreaParam.mbRightClip || bWrapFields;
        bool bSimClip = false;

        Size aCellSize;         // output area, excluding margins, in logical units
        if (rParam.mbPixelToLogic)
            aCellSize = mpRefDevice->PixelToLogic( Size( nOutWidth, nOutHeight ) );
        else
            aCellSize = Size( nOutWidth, nOutHeight );

        if ( nEngineHeight >= aCellSize.Height() + aRefOne.Height() )
        {
            const ScMergeAttr* pMerge = &rParam.mpPattern->GetItem(ATTR_MERGE);
            bool bMerged = pMerge->GetColMerge() > 1 || pMerge->GetRowMerge() > 1;

            //  Don't clip for text height when printing rows with optimal height,
            //  except when font size is from conditional formatting.
            //! Allow clipping when vertically merged?
            if ( eType != OUTTYPE_PRINTER ||
                ( mpDoc->GetRowFlags( rParam.mnCellY, nTab ) & CRFlags::ManualSize ) ||
                ( rParam.mpCondSet && SfxItemState::SET ==
                    rParam.mpCondSet->GetItemState(ATTR_FONT_HEIGHT) ) )
                bClip = true;
            else
                bSimClip = true;

            //  Show clip marks if height is at least 5pt too small and
            //  there are several lines of text.
            //  Not for asian vertical text, because that would interfere
            //  with the default right position of the text.
            //  Only with automatic line breaks, to avoid having to find
            //  the cells with the horizontal end of the text again.
            if ( nEngineHeight - aCellSize.Height() > 100 &&
                 rParam.mbBreak && bMarkClipped &&
                 ( rParam.mpEngine->GetParagraphCount() > 1 || rParam.mpEngine->GetLineCount(0) > 1 ) )
            {
                CellInfo* pClipMarkCell = nullptr;
                if ( bMerged )
                {
                    //  anywhere in the merged area...
                    SCCOL nClipX = ( rParam.mnX < nX1 ) ? nX1 : rParam.mnX;
                    pClipMarkCell = &pRowInfo[(rParam.mnArrY != 0) ? rParam.mnArrY : 1].pCellInfo[nClipX+1];
                }
                else
                    pClipMarkCell = &rParam.mpThisRowInfo->pCellInfo[rParam.mnX+1];

                pClipMarkCell->nClipMark |= ScClipMark::Right;      //! also allow left?
                bAnyClipped = true;

                long nMarkPixel = static_cast<long>( SC_CLIPMARK_SIZE * mnPPTX );
                if ( aAreaParam.maClipRect.Right() - nMarkPixel > aAreaParam.maClipRect.Left() )
                    aAreaParam.maClipRect.AdjustRight( -nMarkPixel );
            }
        }

        Point aURLStart;

        {   // Clip marks are already handled in GetOutputArea
            ClearableClipRegion aClip(rParam.mbPixelToLogic ? mpRefDevice->PixelToLogic(aAreaParam.maClipRect)
                                    : aAreaParam.maClipRect, bClip, bSimClip, mpDev, bMetaFile);

            Point aLogicStart;
            if (rParam.mbPixelToLogic)
                aLogicStart = mpRefDevice->PixelToLogic( Point(nStartX,nStartY) );
            else
                aLogicStart = Point(nStartX, nStartY);

            if (!rParam.mbBreak)
            {
                //  horizontal alignment
                if (rParam.adjustHorAlignment(rParam.mpEngine))
                    // reset adjustment for the next cell
                    rParam.mpOldPattern = nullptr;
            }

            if (rParam.meVerJust==SvxCellVerJustify::Bottom ||
                rParam.meVerJust==SvxCellVerJustify::Standard)
            {
                //! if pRefDevice != pFmtDevice, keep heights in logic units,
                //! only converting margin?

                if (rParam.mbPixelToLogic)
                    aLogicStart.AdjustY(mpRefDevice->PixelToLogic( Size(0, nTopM +
                                    mpRefDevice->LogicToPixel(aCellSize).Height() -
                                    mpRefDevice->LogicToPixel(Size(0,nEngineHeight)).Height()
                                    )).Height() );
                else
                    aLogicStart.AdjustY(nTopM + aCellSize.Height() - nEngineHeight );
            }
            else if (rParam.meVerJust==SvxCellVerJustify::Center)
            {
                if (rParam.mbPixelToLogic)
                    aLogicStart.AdjustY(mpRefDevice->PixelToLogic( Size(0, nTopM + (
                                    mpRefDevice->LogicToPixel(aCellSize).Height() -
                                    mpRefDevice->LogicToPixel(Size(0,nEngineHeight)).Height() )
                                    / 2)).Height() );
                else
                    aLogicStart.AdjustY(nTopM + (aCellSize.Height() - nEngineHeight) / 2 );
            }
            else        // top
            {
                if (rParam.mbPixelToLogic)
                    aLogicStart.AdjustY(mpRefDevice->PixelToLogic(Size(0,nTopM)).Height() );
                else
                    aLogicStart.AdjustY(nTopM );
            }

            aURLStart = aLogicStart;      // copy before modifying for orientation

            // bMoveClipped handling has been replaced by complete alignment
            // handling (also extending to the left).

            if (bSimClip)
            {
                // no hard clip, only draw the affected rows
                Point aDocStart = aClip.getRect().TopLeft();
                aDocStart -= aLogicStart;
                rParam.mpEngine->Draw( mpDev, aClip.getRect(), aDocStart, false );
            }
            else
            {
                rParam.mpEngine->Draw(mpDev, aLogicStart);
            }
        }

        rParam.adjustForHyperlinkInPDF(aURLStart, mpDev);
    }

    void ScOutputData::ShowClipMarks( DrawEditParam& rParam, long nEngineHeight, const Size& aCellSize,
                                      bool bMerged, OutputAreaParam& aAreaParam)
    {
        //  Show clip marks if height is at least 5pt too small and
        //  there are several lines of text.
        //  Not for asian vertical text, because that would interfere
        //  with the default right position of the text.
        //  Only with automatic line breaks, to avoid having to find
        //  the cells with the horizontal end of the text again.
        if ( nEngineHeight - aCellSize.Height() > 100 &&
                rParam.mbBreak && bMarkClipped &&
                ( rParam.mpEngine->GetParagraphCount() > 1 || rParam.mpEngine->GetLineCount(0) > 1 ) )
        {
            CellInfo* pClipMarkCell = nullptr;
            if ( bMerged )
            {
                //  anywhere in the merged area...
                SCCOL nClipX = ( rParam.mnX < nX1 ) ? nX1 : rParam.mnX;
                pClipMarkCell = &pRowInfo[(rParam.mnArrY != 0) ? rParam.mnArrY : 1].pCellInfo[nClipX+1];
            }
            else
                pClipMarkCell = &rParam.mpThisRowInfo->pCellInfo[rParam.mnX+1];

            pClipMarkCell->nClipMark |= ScClipMark::Right;      //! also allow left?
            bAnyClipped = true;

            const long nMarkPixel = static_cast<long>( SC_CLIPMARK_SIZE * mnPPTX );
            if ( aAreaParam.maClipRect.Right() - nMarkPixel > aAreaParam.maClipRect.Left() )
                aAreaParam.maClipRect.AdjustRight( -nMarkPixel );
        }
    }

    std::unique_ptr<ClearableClipRegion> ScOutputData::Clip( DrawEditParam& rParam, const Size& aCellSize,
                                                            OutputAreaParam& aAreaParam, long nEngineHeight,
                                                            bool bWrapFields)
    {
        // Also take fields in a cell with automatic breaks into account: clip to cell width
        bool bClip = AdjustAreaParamClipRect(aAreaParam) || aAreaParam.mbLeftClip || aAreaParam.mbRightClip || bWrapFields;
        bool bSimClip = false;

        const Size& aRefOne = mpRefDevice->PixelToLogic(Size(1,1));
        if ( nEngineHeight >= aCellSize.Height() + aRefOne.Height() )
        {
            const ScMergeAttr* pMerge = &rParam.mpPattern->GetItem(ATTR_MERGE);
            const bool bMerged = pMerge->GetColMerge() > 1 || pMerge->GetRowMerge() > 1;

            //  Don't clip for text height when printing rows with optimal height,
            //  except when font size is from conditional formatting.
            //! Allow clipping when vertically merged?
            if ( eType != OUTTYPE_PRINTER ||
                ( mpDoc->GetRowFlags( rParam.mnCellY, nTab ) & CRFlags::ManualSize ) ||
                ( rParam.mpCondSet && SfxItemState::SET ==
                    rParam.mpCondSet->GetItemState(ATTR_FONT_HEIGHT) ) )
                bClip = true;
            else
                bSimClip = true;

            ShowClipMarks( rParam, nEngineHeight, aCellSize, bMerged, aAreaParam);
        }

            // Clip marks are already handled in GetOutputArea
        return  std::unique_ptr<ClearableClipRegion>( new ClearableClipRegion( rParam.mbPixelToLogic ?
                                                    mpRefDevice->PixelToLogic( aAreaParam.maClipRect )
                                                  : aAreaParam.maClipRect, bClip, bSimClip, mpDev, bMetaFile ));
    }

    void ScOutputData::DrawEditBottomTop(DrawEditParam& rParam)
    {
        OSL_ASSERT(rParam.meHorJustAttr != SvxCellHorJustify::Repeat);

        const bool bRepeat = (rParam.meHorJustAttr == SvxCellHorJustify::Repeat && !rParam.mbBreak);
        const bool bShrink = !rParam.mbBreak && !bRepeat && lcl_GetBoolValue(*rParam.mpPattern, ATTR_SHRINKTOFIT, rParam.mpCondSet);

        SvxCellHorJustify eOutHorJust = rParam.meHorJustContext;

        //! mirror margin values for RTL?
        //! move margin down to after final GetOutputArea call
        long nTopM, nLeftM, nBottomM, nRightM;
        rParam.calcMargins(nTopM, nLeftM, nBottomM, nRightM, mnPPTX, mnPPTY);

        SCCOL nXForPos = rParam.mnX;
        if ( nXForPos < nX1 )
        {
            nXForPos = nX1;
            rParam.mnPosX = rParam.mnInitPosX;
        }
        SCSIZE nArrYForPos = rParam.mnArrY;
        if ( nArrYForPos < 1 )
        {
            nArrYForPos = 1;
            rParam.mnPosY = nScrY;
        }

        OutputAreaParam aAreaParam;

        //  Initial page size - large for normal text, cell size for automatic line breaks

        Size aPaperSize( 1000000, 1000000 );
        if (rParam.mbBreak)
        {
            //  call GetOutputArea with nNeeded=0, to get only the cell width

            //! handle nArrY == 0
            GetOutputArea( nXForPos, nArrYForPos, rParam.mnPosX, rParam.mnPosY, rParam.mnCellX, rParam.mnCellY, 0,
                           *rParam.mpPattern, sal::static_int_cast<sal_uInt16>(eOutHorJust),
                           rParam.mbCellIsValue, true, false, aAreaParam );

            //! special ScEditUtil handling if formatting for printer
            rParam.calcPaperSize(aPaperSize, aAreaParam.maAlignRect, mnPPTX, mnPPTY);
        }
        if (rParam.mbPixelToLogic)
        {
            Size aLogicSize = mpRefDevice->PixelToLogic(aPaperSize);
            rParam.mpEngine->SetPaperSize(aLogicSize);
        }
        else
            rParam.mpEngine->SetPaperSize(aPaperSize);

        //  Fill the EditEngine (cell attributes and text)

        rParam.setPatternToEngine(mbUseStyleColor);
        rParam.setAlignmentToEngine();

        //  Read content from cell

        bool bWrapFields = false;
        if (!rParam.readCellContent(mpDoc, mbShowNullValues, mbShowFormulas, mbSyntaxMode, mbUseStyleColor, mbForceAutoColor, bWrapFields))
            // Failed to read cell content.  Bail out.
            return;

        if ( mbSyntaxMode )
            SetEditSyntaxColor( *rParam.mpEngine, rParam.maCell );
        else if ( mbUseStyleColor && mbForceAutoColor )
            lcl_SetEditColor( *rParam.mpEngine, COL_AUTO );     //! or have a flag at EditEngine

        rParam.mpEngine->SetUpdateMode( true );     // after SetText, before CalcTextWidth/GetTextHeight

        //  Get final output area using the calculated width

        long nEngineWidth, nEngineHeight;
        rParam.getEngineSize(rParam.mpEngine, nEngineWidth, nEngineHeight);

        long nNeededPixel = nEngineWidth;
        if (rParam.mbPixelToLogic)
            nNeededPixel = mpRefDevice->LogicToPixel(Size(nNeededPixel,0)).Width();
        nNeededPixel += nLeftM + nRightM;

        if (!rParam.mbBreak || bShrink)
        {
            // for break, the first GetOutputArea call is sufficient
            GetOutputArea( nXForPos, nArrYForPos, rParam.mnPosX, rParam.mnPosY, rParam.mnCellX, rParam.mnCellY, nNeededPixel,
                           *rParam.mpPattern, sal::static_int_cast<sal_uInt16>(eOutHorJust),
                           rParam.mbCellIsValue || bRepeat || bShrink, false, false, aAreaParam );

            if ( bShrink )
            {
                ShrinkEditEngine( *rParam.mpEngine, aAreaParam.maAlignRect,
                    nLeftM, nTopM, nRightM, nBottomM, false,
                    (rParam.meOrient), 0, rParam.mbPixelToLogic,
                    nEngineWidth, nEngineHeight, nNeededPixel,
                    aAreaParam.mbLeftClip, aAreaParam.mbRightClip );
            }
            if ( bRepeat && !aAreaParam.mbLeftClip && !aAreaParam.mbRightClip && rParam.mpEngine->GetParagraphCount() == 1 )
            {
                // First check if twice the space for the formatted text is available
                // (otherwise just keep it unchanged).

                const long nFormatted = nNeededPixel - nLeftM - nRightM;      // without margin
                const long nAvailable = aAreaParam.maAlignRect.GetWidth() - nLeftM - nRightM;
                if ( nAvailable >= 2 * nFormatted )
                {
                    // "repeat" is handled with unformatted text (for performance reasons)
                    OUString aCellStr = rParam.mpEngine->GetText();

                    long nRepeatSize = 0;
                    SetEngineTextAndGetWidth( rParam, aCellStr, nRepeatSize, 0 );
                    if ( pFmtDevice != mpRefDevice )
                        ++nRepeatSize;
                    if ( nRepeatSize > 0 )
                    {
                        const long nRepeatCount = nAvailable / nRepeatSize;
                        if ( nRepeatCount > 1 )
                        {
                            OUStringBuffer aRepeated = aCellStr;
                            for ( long nRepeat = 1; nRepeat < nRepeatCount; nRepeat++ )
                                aRepeated.append(aCellStr);

                            nEngineWidth = SetEngineTextAndGetWidth( rParam, aRepeated.makeStringAndClear(),
                                                                nNeededPixel, (nLeftM + nRightM ) );

                            nEngineHeight = rParam.mpEngine->GetTextHeight();
                        }
                    }
                }
            }
            if ( rParam.mbCellIsValue && ( aAreaParam.mbLeftClip || aAreaParam.mbRightClip ) )
            {
                nEngineWidth = SetEngineTextAndGetWidth( rParam, "###", nNeededPixel, ( nLeftM + nRightM ) );

                //  No clip marks if "###" doesn't fit (same as in DrawStrings)
            }
        }

        long nStartX = aAreaParam.maAlignRect.Left();
        const long nStartY = aAreaParam.maAlignRect.Top();
        const long nCellWidth = aAreaParam.maAlignRect.GetWidth();
        const long nOutWidth = nCellWidth - 1 - nLeftM - nRightM;
        const long nOutHeight = aAreaParam.maAlignRect.GetHeight() - nTopM - nBottomM;

        if (rParam.mbBreak)
        {
            //  text with automatic breaks is aligned only within the
            //  edit engine's paper size, the output of the whole area
            //  is always left-aligned

            nStartX += nLeftM;
        }
        else
        {
            if ( eOutHorJust == SvxCellHorJustify::Right )
                nStartX -= nNeededPixel - nCellWidth + nRightM + 1;
            else if ( eOutHorJust == SvxCellHorJustify::Center )
                nStartX -= ( nNeededPixel - nCellWidth + nRightM + 1 - nLeftM ) / 2;
            else
                nStartX += nLeftM;
        }

        const bool bOutside = (aAreaParam.maClipRect.Right() < nScrX || aAreaParam.maClipRect.Left() >= nScrX + nScrW);
        if (bOutside)
            return;

        // output area, excluding margins, in logical units
        const Size& aCellSize = rParam.mbPixelToLogic
            ? mpRefDevice->PixelToLogic( Size( nOutWidth, nOutHeight ) )
            : Size( nOutWidth, nOutHeight );

        Point aURLStart;

        {
            const auto pClipRegion = Clip( rParam, aCellSize, aAreaParam, nEngineHeight, bWrapFields );

            Point aLogicStart(nStartX, nStartY);
            rParam.calcStartPosForVertical(aLogicStart, aCellSize.Width(), nEngineWidth, nTopM, mpRefDevice);

            aURLStart = aLogicStart;      // copy before modifying for orientation

            if (rParam.meHorJustResult == SvxCellHorJustify::Block || rParam.mbBreak)
            {
                Size aPSize = rParam.mpEngine->GetPaperSize();
                aPSize.setWidth( aCellSize.Height() );
                rParam.mpEngine->SetPaperSize(aPSize);
                aLogicStart.AdjustY(
                    rParam.mbBreak ? aPSize.Width() : nEngineHeight );
            }
            else
            {
                // Note that the "paper" is rotated 90 degrees to the left, so
                // paper's width is in vertical direction.  Also, the whole text
                // is on a single line, as text wrap is not in effect.

                // Set the paper width to be the width of the text.
                Size aPSize = rParam.mpEngine->GetPaperSize();
                aPSize.setWidth( rParam.mpEngine->CalcTextWidth() );
                rParam.mpEngine->SetPaperSize(aPSize);

                long nGap = 0;
                long nTopOffset = 0;
                if (rParam.mbPixelToLogic)
                {
                    nGap = mpRefDevice->LogicToPixel(aCellSize).Height() - mpRefDevice->LogicToPixel(aPSize).Width();
                    nGap = mpRefDevice->PixelToLogic(Size(0, nGap)).Height();
                    nTopOffset = mpRefDevice->PixelToLogic(Size(0,nTopM)).Height();
                }
                else
                {
                    nGap = aCellSize.Height() - aPSize.Width();
                    nTopOffset = nTopM;
                }

                // First, align text to bottom.
                aLogicStart.AdjustY(aCellSize.Height() );
                aLogicStart.AdjustY(nTopOffset );

                switch (rParam.meVerJust)
                {
                    case SvxCellVerJustify::Standard:
                    case SvxCellVerJustify::Bottom:
                        // align to bottom (do nothing).
                    break;
                    case SvxCellVerJustify::Center:
                        // center it.
                        aLogicStart.AdjustY( -(nGap / 2) );
                    break;
                    case SvxCellVerJustify::Block:
                    case SvxCellVerJustify::Top:
                        // align to top
                        aLogicStart.AdjustY( -nGap );
                    break;
                    default:
                        ;
                }
            }

            rParam.mpEngine->Draw(mpDev, aLogicStart, 900);
        }

        rParam.adjustForHyperlinkInPDF(aURLStart, mpDev);
    }

    void ScOutputData::DrawEditTopBottom(DrawEditParam& rParam)
    {
        OSL_ASSERT(rParam.meHorJustAttr != SvxCellHorJustify::Repeat);

        const bool bRepeat = (rParam.meHorJustAttr == SvxCellHorJustify::Repeat && !rParam.mbBreak);
        const bool bShrink = !rParam.mbBreak && !bRepeat && lcl_GetBoolValue(*rParam.mpPattern, ATTR_SHRINKTOFIT, rParam.mpCondSet);

        SvxCellHorJustify eOutHorJust = rParam.meHorJustContext;

        //! mirror margin values for RTL?
        //! move margin down to after final GetOutputArea call
        long nTopM, nLeftM, nBottomM, nRightM;
        rParam.calcMargins(nTopM, nLeftM, nBottomM, nRightM, mnPPTX, mnPPTY);

        SCCOL nXForPos = rParam.mnX;
        if ( nXForPos < nX1 )
        {
            nXForPos = nX1;
            rParam.mnPosX = rParam.mnInitPosX;
        }
        SCSIZE nArrYForPos = rParam.mnArrY;
        if ( nArrYForPos < 1 )
        {
            nArrYForPos = 1;
            rParam.mnPosY = nScrY;
        }

        OutputAreaParam aAreaParam;

        //  Initial page size - large for normal text, cell size for automatic line breaks

        Size aPaperSize( 1000000, 1000000 );
        if (rParam.hasLineBreak())
        {
            //  call GetOutputArea with nNeeded=0, to get only the cell width

            //! handle nArrY == 0
            GetOutputArea( nXForPos, nArrYForPos, rParam.mnPosX, rParam.mnPosY, rParam.mnCellX, rParam.mnCellY, 0,
                           *rParam.mpPattern, sal::static_int_cast<sal_uInt16>(eOutHorJust),
                           rParam.mbCellIsValue, true, false, aAreaParam );

            //! special ScEditUtil handling if formatting for printer
            rParam.calcPaperSize(aPaperSize, aAreaParam.maAlignRect, mnPPTX, mnPPTY);
        }
        if (rParam.mbPixelToLogic)
        {
            Size aLogicSize = mpRefDevice->PixelToLogic(aPaperSize);
            rParam.mpEngine->SetPaperSize(aLogicSize);
        }
        else
            rParam.mpEngine->SetPaperSize(aPaperSize);

        //  Fill the EditEngine (cell attributes and text)

        rParam.setPatternToEngine(mbUseStyleColor);
        rParam.setAlignmentToEngine();

        //  Read content from cell

        bool bWrapFields = false;
        if (!rParam.readCellContent(mpDoc, mbShowNullValues, mbShowFormulas, mbSyntaxMode, mbUseStyleColor, mbForceAutoColor, bWrapFields))
            // Failed to read cell content.  Bail out.
            return;

        if ( mbSyntaxMode )
            SetEditSyntaxColor( *rParam.mpEngine, rParam.maCell );
        else if ( mbUseStyleColor && mbForceAutoColor )
            lcl_SetEditColor( *rParam.mpEngine, COL_AUTO );     //! or have a flag at EditEngine

        rParam.mpEngine->SetUpdateMode( true );     // after SetText, before CalcTextWidth/GetTextHeight

        //  Get final output area using the calculated width

        long nEngineWidth, nEngineHeight;
        rParam.getEngineSize(rParam.mpEngine, nEngineWidth, nEngineHeight);

        long nNeededPixel = nEngineWidth;
        if (rParam.mbPixelToLogic)
            nNeededPixel = mpRefDevice->LogicToPixel(Size(nNeededPixel,0)).Width();
        nNeededPixel += nLeftM + nRightM;

        if (!rParam.mbBreak || bShrink)
        {
            // for break, the first GetOutputArea call is sufficient
            GetOutputArea( nXForPos, nArrYForPos, rParam.mnPosX, rParam.mnPosY, rParam.mnCellX, rParam.mnCellY, nNeededPixel,
                           *rParam.mpPattern, sal::static_int_cast<sal_uInt16>(eOutHorJust),
                           rParam.mbCellIsValue || bRepeat || bShrink, false, false, aAreaParam );

            if ( bShrink )
            {
                ShrinkEditEngine( *rParam.mpEngine, aAreaParam.maAlignRect,
                    nLeftM, nTopM, nRightM, nBottomM, false,
                    rParam.meOrient, 0, rParam.mbPixelToLogic,
                    nEngineWidth, nEngineHeight, nNeededPixel,
                    aAreaParam.mbLeftClip, aAreaParam.mbRightClip );
            }
            if ( bRepeat && !aAreaParam.mbLeftClip && !aAreaParam.mbRightClip && rParam.mpEngine->GetParagraphCount() == 1 )
            {
                // First check if twice the space for the formatted text is available
                // (otherwise just keep it unchanged).

                const long nFormatted = nNeededPixel - nLeftM - nRightM;      // without margin
                const long nAvailable = aAreaParam.maAlignRect.GetWidth() - nLeftM - nRightM;
                if ( nAvailable >= 2 * nFormatted )
                {
                    // "repeat" is handled with unformatted text (for performance reasons)
                    OUString aCellStr = rParam.mpEngine->GetText();

                   long nRepeatSize = 0;
                   SetEngineTextAndGetWidth( rParam, aCellStr, nRepeatSize, 0 );

                    if ( pFmtDevice != mpRefDevice )
                        ++nRepeatSize;
                    if ( nRepeatSize > 0 )
                    {
                        const long nRepeatCount = nAvailable / nRepeatSize;
                        if ( nRepeatCount > 1 )
                        {
                            OUStringBuffer aRepeated = aCellStr;
                            for ( long nRepeat = 1; nRepeat < nRepeatCount; nRepeat++ )
                                aRepeated.append(aCellStr);

                            nEngineWidth = SetEngineTextAndGetWidth( rParam, aRepeated.makeStringAndClear(),
                                                                nNeededPixel, (nLeftM + nRightM ) );

                            nEngineHeight = rParam.mpEngine->GetTextHeight();
                        }
                    }
                }
            }
            if ( rParam.mbCellIsValue && ( aAreaParam.mbLeftClip || aAreaParam.mbRightClip ) )
            {
                nEngineWidth = SetEngineTextAndGetWidth( rParam, "###", nNeededPixel, ( nLeftM + nRightM ) );

                //  No clip marks if "###" doesn't fit (same as in DrawStrings)
            }
        }

        long nStartX = aAreaParam.maAlignRect.Left();
        const long nStartY = aAreaParam.maAlignRect.Top();
        const long nCellWidth = aAreaParam.maAlignRect.GetWidth();
        const long nOutWidth = nCellWidth - 1 - nLeftM - nRightM;
        const long nOutHeight = aAreaParam.maAlignRect.GetHeight() - nTopM - nBottomM;

        if (rParam.mbBreak)
        {
            //  text with automatic breaks is aligned only within the
            //  edit engine's paper size, the output of the whole area
            //  is always left-aligned

            nStartX += nLeftM;
            if (rParam.meHorJustResult == SvxCellHorJustify::Block)
                nStartX += aPaperSize.Height();
        }
        else
        {
            if ( eOutHorJust == SvxCellHorJustify::Right )
                nStartX -= nNeededPixel - nCellWidth + nRightM + 1;
            else if ( eOutHorJust == SvxCellHorJustify::Center )
                nStartX -= ( nNeededPixel - nCellWidth + nRightM + 1 - nLeftM ) / 2;
            else
                nStartX += nLeftM;
        }

        const bool bOutside = (aAreaParam.maClipRect.Right() < nScrX || aAreaParam.maClipRect.Left() >= nScrX + nScrW);
        if (bOutside)
            return;

        // output area, excluding margins, in logical units
        const Size& aCellSize = rParam.mbPixelToLogic
            ? mpRefDevice->PixelToLogic( Size( nOutWidth, nOutHeight ) )
            : Size( nOutWidth, nOutHeight );

        Point aURLStart;

        {
            const auto pClipRegion = Clip( rParam, aCellSize, aAreaParam, nEngineHeight, bWrapFields );

            Point aLogicStart(nStartX, nStartY);
            rParam.calcStartPosForVertical(aLogicStart, aCellSize.Width(), nEngineWidth, nTopM, mpRefDevice);

            aURLStart = aLogicStart;      // copy before modifying for orientation

            if (rParam.meHorJustResult != SvxCellHorJustify::Block)
            {
                aLogicStart.AdjustX(nEngineWidth );
                if (!rParam.mbBreak)
                {
                    // Set the paper width to text size.
                    Size aPSize = rParam.mpEngine->GetPaperSize();
                    aPSize.setWidth( rParam.mpEngine->CalcTextWidth() );
                    rParam.mpEngine->SetPaperSize(aPSize);

                    long nGap = 0;
                    long nTopOffset = 0; // offset by top margin
                    if (rParam.mbPixelToLogic)
                    {
                        nGap = mpRefDevice->LogicToPixel(aPSize).Width() - mpRefDevice->LogicToPixel(aCellSize).Height();
                        nGap = mpRefDevice->PixelToLogic(Size(0, nGap)).Height();
                        nTopOffset = mpRefDevice->PixelToLogic(Size(0,nTopM)).Height();
                    }
                    else
                    {
                        nGap = aPSize.Width() - aCellSize.Height();
                        nTopOffset = nTopM;
                    }
                    aLogicStart.AdjustY(nTopOffset );

                    switch (rParam.meVerJust)
                    {
                        case SvxCellVerJustify::Standard:
                        case SvxCellVerJustify::Bottom:
                            // align to bottom
                            aLogicStart.AdjustY( -nGap );
                        break;
                        case SvxCellVerJustify::Center:
                            // center it.
                            aLogicStart.AdjustY( -(nGap / 2) );
                        break;
                        case SvxCellVerJustify::Block:
                        case SvxCellVerJustify::Top:
                            // align to top (do nothing)
                        default:
                            ;
                    }
                }
            }

            // bMoveClipped handling has been replaced by complete alignment
            // handling (also extending to the left).

            rParam.mpEngine->Draw(mpDev, aLogicStart, 2700);
        }

        rParam.adjustForHyperlinkInPDF(aURLStart, mpDev);
    }

    void ScOutputData::DrawEditStacked(DrawEditParam& rParam)
    {
        OSL_ASSERT(rParam.meHorJustAttr != SvxCellHorJustify::Repeat);
        Size aRefOne = mpRefDevice->PixelToLogic(Size(1,1));

        bool bRepeat = (rParam.meHorJustAttr == SvxCellHorJustify::Repeat && !rParam.mbBreak);
        bool bShrink = !rParam.mbBreak && !bRepeat && lcl_GetBoolValue(*rParam.mpPattern, ATTR_SHRINKTOFIT, rParam.mpCondSet);

        rParam.mbAsianVertical =
            lcl_GetBoolValue(*rParam.mpPattern, ATTR_VERTICAL_ASIAN, rParam.mpCondSet);

        if ( rParam.mbAsianVertical )
        {
            // in asian mode, use EditEngine::SetVertical instead of EEControlBits::ONECHARPERLINE
            rParam.meOrient = SvxCellOrientation::Standard;
            DrawEditAsianVertical(rParam);
            return;
        }

        SvxCellHorJustify eOutHorJust = rParam.meHorJustContext;

        //! mirror margin values for RTL?
        //! move margin down to after final GetOutputArea call
        long nTopM, nLeftM, nBottomM, nRightM;
        rParam.calcMargins(nTopM, nLeftM, nBottomM, nRightM, mnPPTX, mnPPTY);

        SCCOL nXForPos = rParam.mnX;
        if ( nXForPos < nX1 )
        {
            nXForPos = nX1;
            rParam.mnPosX = rParam.mnInitPosX;
        }
        SCSIZE nArrYForPos = rParam.mnArrY;
        if ( nArrYForPos < 1 )
        {
            nArrYForPos = 1;
            rParam.mnPosY = nScrY;
        }

        OutputAreaParam aAreaParam;

        //  Initial page size - large for normal text, cell size for automatic line breaks

        Size aPaperSize( 1000000, 1000000 );
        //  call GetOutputArea with nNeeded=0, to get only the cell width

        //! handle nArrY == 0
        GetOutputArea( nXForPos, nArrYForPos, rParam.mnPosX, rParam.mnPosY, rParam.mnCellX, rParam.mnCellY, 0,
                       *rParam.mpPattern, sal::static_int_cast<sal_uInt16>(eOutHorJust),
                       rParam.mbCellIsValue, true, false, aAreaParam );

        //! special ScEditUtil handling if formatting for printer
        rParam.calcPaperSize(aPaperSize, aAreaParam.maAlignRect, mnPPTX, mnPPTY);

        if (rParam.mbPixelToLogic)
        {
            Size aLogicSize = mpRefDevice->PixelToLogic(aPaperSize);
            if ( rParam.mbBreak && mpRefDevice != pFmtDevice )
            {
                // #i85342# screen display and formatting for printer,
                // use same GetEditArea call as in ScViewData::SetEditEngine

                Fraction aFract(1,1);
                tools::Rectangle aUtilRect = ScEditUtil( mpDoc, rParam.mnCellX, rParam.mnCellY, nTab, Point(0,0), pFmtDevice,
                    HMM_PER_TWIPS, HMM_PER_TWIPS, aFract, aFract ).GetEditArea( rParam.mpPattern, false );
                aLogicSize.setWidth( aUtilRect.GetWidth() );
            }
            rParam.mpEngine->SetPaperSize(aLogicSize);
        }
        else
            rParam.mpEngine->SetPaperSize(aPaperSize);

        //  Fill the EditEngine (cell attributes and text)

        rParam.setPatternToEngine(mbUseStyleColor);
        rParam.setAlignmentToEngine();

        //  Read content from cell

        bool bWrapFields = false;
        if (!rParam.readCellContent(mpDoc, mbShowNullValues, mbShowFormulas, mbSyntaxMode, mbUseStyleColor, mbForceAutoColor, bWrapFields))
            // Failed to read cell content.  Bail out.
            return;

        if ( mbSyntaxMode )
            SetEditSyntaxColor( *rParam.mpEngine, rParam.maCell );
        else if ( mbUseStyleColor && mbForceAutoColor )
            lcl_SetEditColor( *rParam.mpEngine, COL_AUTO );     //! or have a flag at EditEngine

        rParam.mpEngine->SetUpdateMode( true );     // after SetText, before CalcTextWidth/GetTextHeight

        //  Get final output area using the calculated width

        long nEngineWidth, nEngineHeight;
        rParam.getEngineSize(rParam.mpEngine, nEngineWidth, nEngineHeight);

        long nNeededPixel = nEngineWidth;
        if (rParam.mbPixelToLogic)
            nNeededPixel = mpRefDevice->LogicToPixel(Size(nNeededPixel,0)).Width();
        nNeededPixel += nLeftM + nRightM;

        if (bShrink)
        {
            // for break, the first GetOutputArea call is sufficient
            GetOutputArea( nXForPos, nArrYForPos, rParam.mnPosX, rParam.mnPosY, rParam.mnCellX, rParam.mnCellY, nNeededPixel,
                           *rParam.mpPattern, sal::static_int_cast<sal_uInt16>(eOutHorJust),
                           true, false, false, aAreaParam );

            ShrinkEditEngine( *rParam.mpEngine, aAreaParam.maAlignRect,
                nLeftM, nTopM, nRightM, nBottomM, true,
                rParam.meOrient, 0, rParam.mbPixelToLogic,
                nEngineWidth, nEngineHeight, nNeededPixel,
                aAreaParam.mbLeftClip, aAreaParam.mbRightClip );

            if ( rParam.mbCellIsValue && ( aAreaParam.mbLeftClip || aAreaParam.mbRightClip ) )
            {
                nEngineWidth = SetEngineTextAndGetWidth( rParam, "###", nNeededPixel, ( nLeftM + nRightM ) );

                //  No clip marks if "###" doesn't fit (same as in DrawStrings)
            }

            if ( eOutHorJust != SvxCellHorJustify::Left )
            {
                aPaperSize.setWidth( nNeededPixel + 1 );
                if (rParam.mbPixelToLogic)
                    rParam.mpEngine->SetPaperSize(mpRefDevice->PixelToLogic(aPaperSize));
                else
                    rParam.mpEngine->SetPaperSize(aPaperSize);
            }
        }

        long nStartX = aAreaParam.maAlignRect.Left();
        long nStartY = aAreaParam.maAlignRect.Top();
        long nCellWidth = aAreaParam.maAlignRect.GetWidth();
        long nOutWidth = nCellWidth - 1 - nLeftM - nRightM;
        long nOutHeight = aAreaParam.maAlignRect.GetHeight() - nTopM - nBottomM;

        if (rParam.mbBreak)
        {
            //  text with automatic breaks is aligned only within the
            //  edit engine's paper size, the output of the whole area
            //  is always left-aligned

            nStartX += nLeftM;
        }
        else
        {
            if ( eOutHorJust == SvxCellHorJustify::Right )
                nStartX -= nNeededPixel - nCellWidth + nRightM + 1;
            else if ( eOutHorJust == SvxCellHorJustify::Center )
                nStartX -= ( nNeededPixel - nCellWidth + nRightM + 1 - nLeftM ) / 2;
            else
                nStartX += nLeftM;
        }

        bool bOutside = (aAreaParam.maClipRect.Right() < nScrX || aAreaParam.maClipRect.Left() >= nScrX + nScrW);
        if (bOutside)
            return;

        // Also take fields in a cell with automatic breaks into account: clip to cell width
        bool bClip = AdjustAreaParamClipRect(aAreaParam) || aAreaParam.mbLeftClip || aAreaParam.mbRightClip || bWrapFields;
        bool bSimClip = false;

        Size aCellSize;         // output area, excluding margins, in logical units
        if (rParam.mbPixelToLogic)
            aCellSize = mpRefDevice->PixelToLogic( Size( nOutWidth, nOutHeight ) );
        else
            aCellSize = Size( nOutWidth, nOutHeight );

        if ( nEngineHeight >= aCellSize.Height() + aRefOne.Height() )
        {
            const ScMergeAttr* pMerge = &rParam.mpPattern->GetItem(ATTR_MERGE);
            bool bMerged = pMerge->GetColMerge() > 1 || pMerge->GetRowMerge() > 1;

            //  Don't clip for text height when printing rows with optimal height,
            //  except when font size is from conditional formatting.
            //! Allow clipping when vertically merged?
            if ( eType != OUTTYPE_PRINTER ||
                ( mpDoc->GetRowFlags( rParam.mnCellY, nTab ) & CRFlags::ManualSize ) ||
                ( rParam.mpCondSet && SfxItemState::SET ==
                    rParam.mpCondSet->GetItemState(ATTR_FONT_HEIGHT) ) )
                bClip = true;
            else
                bSimClip = true;

            //  Show clip marks if height is at least 5pt too small and
            //  there are several lines of text.
            //  Not for asian vertical text, because that would interfere
            //  with the default right position of the text.
            //  Only with automatic line breaks, to avoid having to find
            //  the cells with the horizontal end of the text again.
            if ( nEngineHeight - aCellSize.Height() > 100 &&
                 rParam.mbBreak && bMarkClipped &&
                 ( rParam.mpEngine->GetParagraphCount() > 1 || rParam.mpEngine->GetLineCount(0) > 1 ) )
            {
                CellInfo* pClipMarkCell = nullptr;
                if ( bMerged )
                {
                    //  anywhere in the merged area...
                    SCCOL nClipX = ( rParam.mnX < nX1 ) ? nX1 : rParam.mnX;
                    pClipMarkCell = &pRowInfo[(rParam.mnArrY != 0) ? rParam.mnArrY : 1].pCellInfo[nClipX+1];
                }
                else
                    pClipMarkCell = &rParam.mpThisRowInfo->pCellInfo[rParam.mnX+1];

                pClipMarkCell->nClipMark |= ScClipMark::Right;      //! also allow left?
                bAnyClipped = true;

                long nMarkPixel = static_cast<long>( SC_CLIPMARK_SIZE * mnPPTX );
                if ( aAreaParam.maClipRect.Right() - nMarkPixel > aAreaParam.maClipRect.Left() )
                    aAreaParam.maClipRect.AdjustRight( -nMarkPixel );
            }
        }

        Point aURLStart;

        {   // Clip marks are already handled in GetOutputArea
            ClearableClipRegion aClip(rParam.mbPixelToLogic ? mpRefDevice->PixelToLogic(aAreaParam.maClipRect)
                                    : aAreaParam.maClipRect, bClip, bSimClip, mpDev, bMetaFile);

            Point aLogicStart;
            if (rParam.mbPixelToLogic)
                aLogicStart = mpRefDevice->PixelToLogic( Point(nStartX,nStartY) );
            else
                aLogicStart = Point(nStartX, nStartY);

            if (rParam.meVerJust==SvxCellVerJustify::Bottom ||
                rParam.meVerJust==SvxCellVerJustify::Standard)
            {
                //! if pRefDevice != pFmtDevice, keep heights in logic units,
                //! only converting margin?

                if (rParam.mbPixelToLogic)
                    aLogicStart.AdjustY(mpRefDevice->PixelToLogic( Size(0, nTopM +
                                    mpRefDevice->LogicToPixel(aCellSize).Height() -
                                    mpRefDevice->LogicToPixel(Size(0,nEngineHeight)).Height()
                                    )).Height() );
                else
                    aLogicStart.AdjustY(nTopM + aCellSize.Height() - nEngineHeight );
            }
            else if (rParam.meVerJust==SvxCellVerJustify::Center)
            {
                if (rParam.mbPixelToLogic)
                    aLogicStart.AdjustY(mpRefDevice->PixelToLogic( Size(0, nTopM + (
                                    mpRefDevice->LogicToPixel(aCellSize).Height() -
                                    mpRefDevice->LogicToPixel(Size(0,nEngineHeight)).Height() )
                                    / 2)).Height() );
                else
                    aLogicStart.AdjustY(nTopM + (aCellSize.Height() - nEngineHeight) / 2 );
            }
            else        // top
            {
                if (rParam.mbPixelToLogic)
                    aLogicStart.AdjustY(mpRefDevice->PixelToLogic(Size(0,nTopM)).Height() );
                else
                    aLogicStart.AdjustY(nTopM );
            }

            aURLStart = aLogicStart;      // copy before modifying for orientation

            Size aPaperLogic = rParam.mpEngine->GetPaperSize();
            aPaperLogic.setWidth( nEngineWidth );
            rParam.mpEngine->SetPaperSize(aPaperLogic);

            // bMoveClipped handling has been replaced by complete alignment
            // handling (also extending to the left).

            if (bSimClip)
            {
                // no hard clip, only draw the affected rows
                Point aDocStart = aClip.getRect().TopLeft();
                aDocStart -= aLogicStart;
                rParam.mpEngine->Draw( mpDev, aClip.getRect(), aDocStart, false );
            }
            else
            {
                rParam.mpEngine->Draw( mpDev, aLogicStart );
            }
        }

        rParam.adjustForHyperlinkInPDF(aURLStart, mpDev);
    }

    void ScOutputData::DrawEditAsianVertical(DrawEditParam& rParam)
    {
        // When in asian vertical orientation, the orientation value is STANDARD,
        // and the asian vertical boolean is true.
        OSL_ASSERT(rParam.meOrient == SvxCellOrientation::Standard);
        OSL_ASSERT(rParam.mbAsianVertical);
        OSL_ASSERT(rParam.meHorJustAttr != SvxCellHorJustify::Repeat);

        Size aRefOne = mpRefDevice->PixelToLogic(Size(1,1));

        bool bHidden = false;
        bool bShrink = !rParam.mbBreak && lcl_GetBoolValue(*rParam.mpPattern, ATTR_SHRINKTOFIT, rParam.mpCondSet);
        long nAttrRotate = lcl_GetValue<SfxInt32Item, long>(*rParam.mpPattern, ATTR_ROTATE_VALUE, rParam.mpCondSet);

        if (nAttrRotate)
        {
            //! set flag to find the cell in DrawRotated again ?
            //! (or flag already set during DrawBackground, then no query here)
            bHidden = true;     // rotated is outputted separately
        }

        // default alignment for asian vertical mode is top-right
        /* TODO: is setting meHorJustContext and meHorJustResult unconditionally to
         * SvxCellHorJustify::Right really wanted? Seems this was done all the time,
         * also before context was introduced and everything was attr only. */
        if ( rParam.meHorJustAttr == SvxCellHorJustify::Standard )
            rParam.meHorJustResult = rParam.meHorJustContext = SvxCellHorJustify::Right;

        if (bHidden)
            return;

        SvxCellHorJustify eOutHorJust = rParam.meHorJustContext;

        //! mirror margin values for RTL?
        //! move margin down to after final GetOutputArea call
        long nTopM, nLeftM, nBottomM, nRightM;
        rParam.calcMargins(nTopM, nLeftM, nBottomM, nRightM, mnPPTX, mnPPTY);

        SCCOL nXForPos = rParam.mnX;
        if ( nXForPos < nX1 )
        {
            nXForPos = nX1;
            rParam.mnPosX = rParam.mnInitPosX;
        }
        SCSIZE nArrYForPos = rParam.mnArrY;
        if ( nArrYForPos < 1 )
        {
            nArrYForPos = 1;
            rParam.mnPosY = nScrY;
        }

        OutputAreaParam aAreaParam;

        //  Initial page size - large for normal text, cell size for automatic line breaks

        Size aPaperSize( 1000000, 1000000 );
        //  call GetOutputArea with nNeeded=0, to get only the cell width

        //! handle nArrY == 0
        GetOutputArea( nXForPos, nArrYForPos, rParam.mnPosX, rParam.mnPosY, rParam.mnCellX, rParam.mnCellY, 0,
                       *rParam.mpPattern, sal::static_int_cast<sal_uInt16>(eOutHorJust),
                       rParam.mbCellIsValue, true, false, aAreaParam );

        //! special ScEditUtil handling if formatting for printer
        rParam.calcPaperSize(aPaperSize, aAreaParam.maAlignRect, mnPPTX, mnPPTY);

        if (rParam.mbPixelToLogic)
        {
            Size aLogicSize = mpRefDevice->PixelToLogic(aPaperSize);
            if ( rParam.mbBreak && !rParam.mbAsianVertical && mpRefDevice != pFmtDevice )
            {
                // #i85342# screen display and formatting for printer,
                // use same GetEditArea call as in ScViewData::SetEditEngine

                Fraction aFract(1,1);
                tools::Rectangle aUtilRect = ScEditUtil( mpDoc, rParam.mnCellX, rParam.mnCellY, nTab, Point(0,0), pFmtDevice,
                    HMM_PER_TWIPS, HMM_PER_TWIPS, aFract, aFract ).GetEditArea( rParam.mpPattern, false );
                aLogicSize.setWidth( aUtilRect.GetWidth() );
            }
            rParam.mpEngine->SetPaperSize(aLogicSize);
        }
        else
            rParam.mpEngine->SetPaperSize(aPaperSize);

        //  Fill the EditEngine (cell attributes and text)

        // default alignment for asian vertical mode is top-right
        if ( rParam.meVerJust == SvxCellVerJustify::Standard )
            rParam.meVerJust = SvxCellVerJustify::Top;

        rParam.setPatternToEngine(mbUseStyleColor);
        rParam.setAlignmentToEngine();

        //  Read content from cell

        bool bWrapFields = false;
        if (!rParam.readCellContent(mpDoc, mbShowNullValues, mbShowFormulas, mbSyntaxMode, mbUseStyleColor, mbForceAutoColor, bWrapFields))
            // Failed to read cell content.  Bail out.
            return;

        if ( mbSyntaxMode )
            SetEditSyntaxColor( *rParam.mpEngine, rParam.maCell );
        else if ( mbUseStyleColor && mbForceAutoColor )
            lcl_SetEditColor( *rParam.mpEngine, COL_AUTO );     //! or have a flag at EditEngine

        rParam.mpEngine->SetUpdateMode( true );     // after SetText, before CalcTextWidth/GetTextHeight

        //  Get final output area using the calculated width

        long nEngineWidth, nEngineHeight;
        rParam.getEngineSize(rParam.mpEngine, nEngineWidth, nEngineHeight);

        long nNeededPixel = nEngineWidth;
        if (rParam.mbPixelToLogic)
            nNeededPixel = mpRefDevice->LogicToPixel(Size(nNeededPixel,0)).Width();
        nNeededPixel += nLeftM + nRightM;

        // for break, the first GetOutputArea call is sufficient
        GetOutputArea( nXForPos, nArrYForPos, rParam.mnPosX, rParam.mnPosY, rParam.mnCellX, rParam.mnCellY, nNeededPixel,
                       *rParam.mpPattern, sal::static_int_cast<sal_uInt16>(eOutHorJust),
                       rParam.mbCellIsValue || bShrink, false, false, aAreaParam );

        if ( bShrink )
        {
            ShrinkEditEngine( *rParam.mpEngine, aAreaParam.maAlignRect,
                nLeftM, nTopM, nRightM, nBottomM, false,
                rParam.meOrient, 0, rParam.mbPixelToLogic,
                nEngineWidth, nEngineHeight, nNeededPixel,
                aAreaParam.mbLeftClip, aAreaParam.mbRightClip );
        }
        if ( rParam.mbCellIsValue && ( aAreaParam.mbLeftClip || aAreaParam.mbRightClip ) )
        {
            nEngineWidth = SetEngineTextAndGetWidth( rParam, "###", nNeededPixel, ( nLeftM + nRightM ) );

            //  No clip marks if "###" doesn't fit (same as in DrawStrings)
        }

        if (eOutHorJust != SvxCellHorJustify::Left)
        {
            aPaperSize.setWidth( nNeededPixel + 1 );
            if (rParam.mbPixelToLogic)
                rParam.mpEngine->SetPaperSize(mpRefDevice->PixelToLogic(aPaperSize));
            else
                rParam.mpEngine->SetPaperSize(aPaperSize);
        }

        long nStartX = aAreaParam.maAlignRect.Left();
        long nStartY = aAreaParam.maAlignRect.Top();
        long nCellWidth = aAreaParam.maAlignRect.GetWidth();
        long nOutWidth = nCellWidth - 1 - nLeftM - nRightM;
        long nOutHeight = aAreaParam.maAlignRect.GetHeight() - nTopM - nBottomM;

        //  text with automatic breaks is aligned only within the
        //  edit engine's paper size, the output of the whole area
        //  is always left-aligned

        nStartX += nLeftM;

        bool bOutside = (aAreaParam.maClipRect.Right() < nScrX || aAreaParam.maClipRect.Left() >= nScrX + nScrW);
        if (bOutside)
            return;

        // Also take fields in a cell with automatic breaks into account: clip to cell width
        bool bClip = AdjustAreaParamClipRect(aAreaParam) || aAreaParam.mbLeftClip || aAreaParam.mbRightClip || bWrapFields;
        bool bSimClip = false;

        Size aCellSize;         // output area, excluding margins, in logical units
        if (rParam.mbPixelToLogic)
            aCellSize = mpRefDevice->PixelToLogic( Size( nOutWidth, nOutHeight ) );
        else
            aCellSize = Size( nOutWidth, nOutHeight );

        if ( nEngineHeight >= aCellSize.Height() + aRefOne.Height() )
        {
            const ScMergeAttr* pMerge = &rParam.mpPattern->GetItem(ATTR_MERGE);
            bool bMerged = pMerge->GetColMerge() > 1 || pMerge->GetRowMerge() > 1;

            //  Don't clip for text height when printing rows with optimal height,
            //  except when font size is from conditional formatting.
            //! Allow clipping when vertically merged?
            if ( eType != OUTTYPE_PRINTER ||
                ( mpDoc->GetRowFlags( rParam.mnCellY, nTab ) & CRFlags::ManualSize ) ||
                ( rParam.mpCondSet && SfxItemState::SET ==
                    rParam.mpCondSet->GetItemState(ATTR_FONT_HEIGHT) ) )
                bClip = true;
            else
                bSimClip = true;

            //  Show clip marks if height is at least 5pt too small and
            //  there are several lines of text.
            //  Not for asian vertical text, because that would interfere
            //  with the default right position of the text.
            //  Only with automatic line breaks, to avoid having to find
            //  the cells with the horizontal end of the text again.
            if ( nEngineHeight - aCellSize.Height() > 100 &&
                 ( rParam.mbBreak || rParam.meOrient == SvxCellOrientation::Stacked ) &&
                 !rParam.mbAsianVertical && bMarkClipped &&
                 ( rParam.mpEngine->GetParagraphCount() > 1 || rParam.mpEngine->GetLineCount(0) > 1 ) )
            {
                CellInfo* pClipMarkCell = nullptr;
                if ( bMerged )
                {
                    //  anywhere in the merged area...
                    SCCOL nClipX = ( rParam.mnX < nX1 ) ? nX1 : rParam.mnX;
                    pClipMarkCell = &pRowInfo[(rParam.mnArrY != 0) ? rParam.mnArrY : 1].pCellInfo[nClipX+1];
                }
                else
                    pClipMarkCell = &rParam.mpThisRowInfo->pCellInfo[rParam.mnX+1];

                pClipMarkCell->nClipMark |= ScClipMark::Right;      //! also allow left?
                bAnyClipped = true;

                long nMarkPixel = static_cast<long>( SC_CLIPMARK_SIZE * mnPPTX );
                if ( aAreaParam.maClipRect.Right() - nMarkPixel > aAreaParam.maClipRect.Left() )
                    aAreaParam.maClipRect.AdjustRight( -nMarkPixel );
            }
        }

        Point aURLStart;

        {   // Clip marks are already handled in GetOutputArea
            ClearableClipRegion aClip(rParam.mbPixelToLogic ? mpRefDevice->PixelToLogic(aAreaParam.maClipRect)
                                    : aAreaParam.maClipRect, bClip, bSimClip, mpDev, bMetaFile);

            Point aLogicStart;
            if (rParam.mbPixelToLogic)
                aLogicStart = mpRefDevice->PixelToLogic( Point(nStartX,nStartY) );
            else
                aLogicStart = Point(nStartX, nStartY);

            long nAvailWidth = aCellSize.Width();
            // space for AutoFilter is already handled in GetOutputArea

            //  horizontal alignment

            if (rParam.meHorJustResult==SvxCellHorJustify::Right)
                aLogicStart.AdjustX(nAvailWidth - nEngineWidth );
            else if (rParam.meHorJustResult==SvxCellHorJustify::Center)
                aLogicStart.AdjustX((nAvailWidth - nEngineWidth) / 2 );

            // paper size is subtracted below
            aLogicStart.AdjustX(nEngineWidth );

            // vertical adjustment is within the EditEngine
            if (rParam.mbPixelToLogic)
                aLogicStart.AdjustY(mpRefDevice->PixelToLogic(Size(0,nTopM)).Height() );
            else
                aLogicStart.AdjustY(nTopM );

            aURLStart = aLogicStart;      // copy before modifying for orientation

            // bMoveClipped handling has been replaced by complete alignment
            // handling (also extending to the left).

            // with SetVertical, the start position is top left of
            // the whole output area, not the text itself
            aLogicStart.AdjustX( -(rParam.mpEngine->GetPaperSize().Width()) );

            rParam.mpEngine->Draw(mpDev, aLogicStart);
        }

        rParam.adjustForHyperlinkInPDF(aURLStart, mpDev);
    }

    void ScOutputData::DrawEdit(bool bPixelToLogic)
    {
        std::unique_ptr<ScFieldEditEngine> pEngine;
        bool bHyphenatorSet = false;
        const ScPatternAttr* pOldPattern = nullptr;
        const SfxItemSet*    pOldCondSet = nullptr;
        const SfxItemSet*    pOldPreviewFontSet = nullptr;
        ScRefCellValue aCell;

        long nInitPosX = nScrX;
        if ( bLayoutRTL )
        {
            nInitPosX += nMirrorW - 1;
        }
        long nLayoutSign = bLayoutRTL ? -1 : 1;

        //! store nLastContentCol as member!
        SCCOL nLastContentCol = MAXCOL;
        if ( nX2 < MAXCOL )
            nLastContentCol = sal::static_int_cast<SCCOL>(
                nLastContentCol - mpDoc->GetEmptyLinesInBlock( nX2+1, nY1, nTab, MAXCOL, nY2, nTab, DIR_RIGHT ) );

        long nRowPosY = nScrY;
        for (SCSIZE nArrY=0; nArrY+1<nArrCount; nArrY++)            // 0 fo the rest of the merged
        {
            RowInfo* pThisRowInfo = &pRowInfo[nArrY];

            if (nArrY==1) nRowPosY = nScrY;                         // positions before are calculated individually

            if ( pThisRowInfo->bChanged || nArrY==0 )
            {
                long nPosX = 0;
                for (SCCOL nX=0; nX<=nX2; nX++)                     // due to overflow
                {
                    std::unique_ptr< ScPatternAttr > pPreviewPattr;
                    if (nX==nX1) nPosX = nInitPosX;                 // positions before nX1 are calculated individually

                    CellInfo*   pInfo = &pThisRowInfo->pCellInfo[nX+1];
                    if (pInfo->bEditEngine)
                    {
                        SCROW nY = pThisRowInfo->nRowNo;

                        SCCOL nCellX = nX;                  // position where the cell really starts
                        SCROW nCellY = nY;
                        bool bDoCell = false;

                        long nPosY = nRowPosY;
                        if ( nArrY == 0 )
                        {
                            nPosY = nScrY;
                            nY = pRowInfo[1].nRowNo;
                            SCCOL nOverX;                   // start of the merged cells
                            SCROW nOverY;
                            if (GetMergeOrigin( nX,nY, 1, nOverX,nOverY, true ))
                            {
                                nCellX = nOverX;
                                nCellY = nOverY;
                                bDoCell = true;
                            }
                        }
                        else if ( nX == nX2 && pThisRowInfo->pCellInfo[nX+1].maCell.isEmpty() )
                        {
                            //  Rest of a long text further to the right?

                            SCCOL nTempX=nX;
                            while (nTempX < nLastContentCol && IsEmptyCellText( pThisRowInfo, nTempX, nY ))
                                ++nTempX;

                            if ( nTempX > nX &&
                                 !IsEmptyCellText( pThisRowInfo, nTempX, nY ) &&
                                 !mpDoc->HasAttrib( nTempX,nY,nTab, nX,nY,nTab, HasAttrFlags::Merged | HasAttrFlags::Overlapped ) )
                            {
                                nCellX = nTempX;
                                bDoCell = true;
                            }
                        }
                        else
                        {
                            bDoCell = true;
                        }

                        if ( bDoCell && bEditMode && nCellX == nEditCol && nCellY == nEditRow )
                            bDoCell = false;

                        const ScPatternAttr* pPattern = nullptr;
                        const SfxItemSet* pCondSet = nullptr;
                        if (bDoCell)
                        {
                            if ( nCellY == nY && nCellX >= nX1 && nCellX <= nX2 &&
                                 !mpDoc->ColHidden(nCellX, nTab) )
                            {
                                CellInfo& rCellInfo = pThisRowInfo->pCellInfo[nCellX+1];
                                pPattern = rCellInfo.pPatternAttr;
                                pCondSet = rCellInfo.pConditionSet;
                                aCell = rCellInfo.maCell;
                            }
                            else        // get from document
                            {
                                pPattern = mpDoc->GetPattern( nCellX, nCellY, nTab );
                                pCondSet = mpDoc->GetCondResult( nCellX, nCellY, nTab );
                                GetVisibleCell( nCellX, nCellY, nTab, aCell );
                            }
                            if (aCell.isEmpty())
                                bDoCell = false;
                        }
                        if (bDoCell)
                        {
                            if ( mpDoc->GetPreviewCellStyle() )
                            {
                                if ( ScStyleSheet* pPreviewStyle = mpDoc->GetPreviewCellStyle( nCellX, nCellY, nTab ) )
                                {
                                    pPreviewPattr.reset( new ScPatternAttr(*pPattern) );
                                    pPreviewPattr->SetStyleSheet(pPreviewStyle);
                                    pPattern = pPreviewPattr.get();
                                }
                            }
                            SfxItemSet* pPreviewFontSet = mpDoc->GetPreviewFont( nCellX, nCellY, nTab );
                            if (!pEngine)
                                pEngine.reset(CreateOutputEditEngine());
                            else
                                lcl_ClearEdit( *pEngine );      // also calls SetUpdateMode(sal_False)

                            // fdo#32530: Check if the first character is RTL.
                            OUString aStr = mpDoc->GetString(nCellX, nCellY, nTab);

                            DrawEditParam aParam(pPattern, pCondSet, lcl_SafeIsValue(aCell));
                            const bool bNumberFormatIsText = lcl_isNumberFormatText( mpDoc, nCellX, nCellY, nTab );
                            aParam.meHorJustContext = getAlignmentFromContext( aParam.meHorJustAttr,
                                    aParam.mbCellIsValue, aStr, *pPattern, pCondSet, mpDoc, nTab, bNumberFormatIsText);
                            aParam.meHorJustResult = (aParam.meHorJustAttr == SvxCellHorJustify::Block) ?
                                    SvxCellHorJustify::Block : aParam.meHorJustContext;
                            aParam.mbPixelToLogic = bPixelToLogic;
                            aParam.mbHyphenatorSet = bHyphenatorSet;
                            aParam.mpEngine = pEngine.get();
                            aParam.maCell = aCell;
                            aParam.mnArrY = nArrY;
                            aParam.mnX = nX;
                            aParam.mnCellX = nCellX;
                            aParam.mnCellY = nCellY;
                            aParam.mnPosX = nPosX;
                            aParam.mnPosY = nPosY;
                            aParam.mnInitPosX = nInitPosX;
                            aParam.mpPreviewFontSet = pPreviewFontSet;
                            aParam.mpOldPattern = pOldPattern;
                            aParam.mpOldCondSet = pOldCondSet;
                            aParam.mpOldPreviewFontSet = pOldPreviewFontSet;
                            aParam.mpThisRowInfo = pThisRowInfo;
                            if (mpSpellCheckCxt)
                                aParam.mpMisspellRanges = mpSpellCheckCxt->getMisspellRanges(nCellX, nCellY);

                            if (aParam.meHorJustAttr == SvxCellHorJustify::Repeat)
                            {
                                // ignore orientation/rotation if "repeat" is active
                                aParam.meOrient = SvxCellOrientation::Standard;
                            }
                            switch (aParam.meOrient)
                            {
                                case SvxCellOrientation::BottomUp:
                                    DrawEditBottomTop(aParam);
                                break;
                                case SvxCellOrientation::TopBottom:
                                    DrawEditTopBottom(aParam);
                                break;
                                case SvxCellOrientation::Stacked:
                                    // this can be vertically stacked or asian vertical.
                                    DrawEditStacked(aParam);
                                break;
                                default:
                                    DrawEditStandard(aParam);
                            }

                            // Retrieve parameters for next iteration.
                            pOldPattern = aParam.mpOldPattern;
                            pOldCondSet = aParam.mpOldCondSet;
                            pOldPreviewFontSet = aParam.mpOldPreviewFontSet;
                            bHyphenatorSet = aParam.mbHyphenatorSet;
                        }
                    }
                    nPosX += pRowInfo[0].pCellInfo[nX+1].nWidth * nLayoutSign;
                }
            }
            nRowPosY += pRowInfo[nArrY].nHeight;
        }

        pEngine.reset();

        if (mrTabInfo.maArray.HasCellRotation())
        {
            DrawRotated(bPixelToLogic);     //! call from outside ?
        }
    }

    void ScOutputData::DrawRotated(bool bPixelToLogic)
    {
        //! store nRotMax
        SCCOL nRotMax = nX2;
        for (SCSIZE nRotY=0; nRotY<nArrCount; nRotY++)
            if (pRowInfo[nRotY].nRotMaxCol != SC_ROTMAX_NONE && pRowInfo[nRotY].nRotMaxCol > nRotMax)
                nRotMax = pRowInfo[nRotY].nRotMaxCol;

        ScModule* pScMod = SC_MOD();
        Color nConfBackColor = pScMod->GetColorConfig().GetColorValue(svtools::DOCCOLOR).nColor;
        bool bCellContrast = mbUseStyleColor &&
                Application::GetSettings().GetStyleSettings().GetHighContrastMode();

        std::unique_ptr<ScFieldEditEngine> pEngine;
        bool bHyphenatorSet = false;
        const ScPatternAttr* pPattern;
        const SfxItemSet*    pCondSet;
        const ScPatternAttr* pOldPattern = nullptr;
        const SfxItemSet*    pOldCondSet = nullptr;
        ScRefCellValue aCell;

        long nInitPosX = nScrX;
        if ( bLayoutRTL )
        {
            nInitPosX += nMirrorW - 1;
        }
        long nLayoutSign = bLayoutRTL ? -1 : 1;

        long nRowPosY = nScrY;
        for (SCSIZE nArrY=0; nArrY+1<nArrCount; nArrY++)            // 0 for the rest of the merged
        {
            RowInfo* pThisRowInfo = &pRowInfo[nArrY];
            long nCellHeight = static_cast<long>(pThisRowInfo->nHeight);
            if (nArrY==1) nRowPosY = nScrY;                         // positions before are calculated individually

            if ( ( pThisRowInfo->bChanged || nArrY==0 ) && pThisRowInfo->nRotMaxCol != SC_ROTMAX_NONE )
            {
                long nPosX = 0;
                for (SCCOL nX=0; nX<=nRotMax; nX++)
                {
                    if (nX==nX1) nPosX = nInitPosX;                 // positions before nX1 are calculated individually

                    CellInfo* pInfo = &pThisRowInfo->pCellInfo[nX+1];
                    if ( pInfo->nRotateDir != ScRotateDir::NONE )
                    {
                        SCROW nY = pThisRowInfo->nRowNo;

                        bool bHidden = false;
                        if (bEditMode)
                            if ( nX == nEditCol && nY == nEditRow )
                                bHidden = true;

                        if (!bHidden)
                        {
                            if (!pEngine)
                                pEngine.reset(CreateOutputEditEngine());
                            else
                                lcl_ClearEdit( *pEngine );      // also calls SetUpdateMode(sal_False)

                            long nPosY = nRowPosY;

                            //! rest from merged cells further up do not work!

                            bool bFromDoc = false;
                            pPattern = pInfo->pPatternAttr;
                            pCondSet = pInfo->pConditionSet;
                            if (!pPattern)
                            {
                                pPattern = mpDoc->GetPattern( nX, nY, nTab );
                                bFromDoc = true;
                            }
                            aCell = pInfo->maCell;
                            if (bFromDoc)
                                pCondSet = mpDoc->GetCondResult( nX, nY, nTab );

                            if (aCell.isEmpty() && nX>nX2)
                                GetVisibleCell( nX, nY, nTab, aCell );

                            if (aCell.isEmpty() || IsEmptyCellText(pThisRowInfo, nX, nY))
                                bHidden = true;     // nRotateDir is also set without a cell

                            long nCellWidth = static_cast<long>(pRowInfo[0].pCellInfo[nX+1].nWidth);

                            SvxCellHorJustify eHorJust =
                                                pPattern->GetItem(ATTR_HOR_JUSTIFY, pCondSet).GetValue();
                            bool bBreak = ( eHorJust == SvxCellHorJustify::Block ) ||
                                        pPattern->GetItem(ATTR_LINEBREAK, pCondSet).GetValue();
                            bool bRepeat = ( eHorJust == SvxCellHorJustify::Repeat && !bBreak );
                            bool bShrink = !bBreak && !bRepeat &&
                                            pPattern->GetItem( ATTR_SHRINKTOFIT, pCondSet ).GetValue();
                            SvxCellOrientation eOrient = pPattern->GetCellOrientation( pCondSet );

                            const ScMergeAttr* pMerge = &pPattern->GetItem(ATTR_MERGE);
                            bool bMerged = pMerge->GetColMerge() > 1 || pMerge->GetRowMerge() > 1;

                            long nStartX = nPosX;
                            long nStartY = nPosY;
                            if (nX<nX1)
                            {
                                if ((bBreak || eOrient!=SvxCellOrientation::Standard) && !bMerged)
                                    bHidden = true;
                                else
                                {
                                    nStartX = nInitPosX;
                                    SCCOL nCol = nX1;
                                    while (nCol > nX)
                                    {
                                        --nCol;
                                        nStartX -= nLayoutSign * static_cast<long>(pRowInfo[0].pCellInfo[nCol+1].nWidth);
                                    }
                                }
                            }
                            long nCellStartX = nStartX;

                            // omit substitute representation of small text

                            if (!bHidden)
                            {
                                long nOutWidth = nCellWidth - 1;
                                long nOutHeight = nCellHeight;

                                if ( bMerged )
                                {
                                    SCCOL nCountX = pMerge->GetColMerge();
                                    for (SCCOL i=1; i<nCountX; i++)
                                        nOutWidth += static_cast<long>( mpDoc->GetColWidth(nX+i,nTab) * mnPPTX );
                                    SCROW nCountY = pMerge->GetRowMerge();
                                    nOutHeight += static_cast<long>(mpDoc->GetScaledRowHeight( nY+1, nY+nCountY-1, nTab, mnPPTY));
                                }

                                SvxCellVerJustify eVerJust =
                                                    pPattern->GetItem(ATTR_VER_JUSTIFY, pCondSet).GetValue();

                                // syntax mode is ignored here...

                                // StringDiffer doesn't look at hyphenate, language items
                                if ( pPattern != pOldPattern || pCondSet != pOldCondSet )
                                {
                                    SfxItemSet* pSet = new SfxItemSet( pEngine->GetEmptyItemSet() );
                                    pPattern->FillEditItemSet( pSet, pCondSet );

                                                                        // adjustment for EditEngine
                                    SvxAdjust eSvxAdjust = SvxAdjust::Left;
                                    if (eOrient==SvxCellOrientation::Stacked)
                                        eSvxAdjust = SvxAdjust::Center;
                                    // adjustment for bBreak is omitted here
                                    pSet->Put( SvxAdjustItem( eSvxAdjust, EE_PARA_JUST ) );

                                    pEngine->SetDefaults( pSet );
                                    pOldPattern = pPattern;
                                    pOldCondSet = pCondSet;

                                    EEControlBits nControl = pEngine->GetControlWord();
                                    if (eOrient==SvxCellOrientation::Stacked)
                                        nControl |= EEControlBits::ONECHARPERLINE;
                                    else
                                        nControl &= ~EEControlBits::ONECHARPERLINE;
                                    pEngine->SetControlWord( nControl );

                                    if ( !bHyphenatorSet && pSet->Get(EE_PARA_HYPHENATE).GetValue() )
                                    {
                                        //  set hyphenator the first time it is needed
                                        css::uno::Reference<css::linguistic2::XHyphenator> xXHyphenator( LinguMgr::GetHyphenator() );
                                        pEngine->SetHyphenator( xXHyphenator );
                                        bHyphenatorSet = true;
                                    }

                                    Color aBackCol =
                                        pPattern->GetItem( ATTR_BACKGROUND, pCondSet ).GetColor();
                                    if ( mbUseStyleColor && ( aBackCol.GetTransparency() > 0 || bCellContrast ) )
                                        aBackCol = nConfBackColor;
                                    pEngine->SetBackgroundColor( aBackCol );
                                }

                                // margins

                                //! change position and paper size to EditUtil !!!

                                const SvxMarginItem* pMargin =
                                                        &pPattern->GetItem(ATTR_MARGIN, pCondSet);
                                sal_uInt16 nIndent = 0;
                                if ( eHorJust == SvxCellHorJustify::Left )
                                    nIndent = pPattern->GetItem(ATTR_INDENT, pCondSet).GetValue();

                                long nTotalHeight = nOutHeight; // without subtracting the margin
                                if ( bPixelToLogic )
                                    nTotalHeight = mpRefDevice->PixelToLogic(Size(0,nTotalHeight)).Height();

                                long nLeftM = static_cast<long>( (pMargin->GetLeftMargin() + nIndent) * mnPPTX );
                                long nTopM  = static_cast<long>( pMargin->GetTopMargin() * mnPPTY );
                                long nRightM  = static_cast<long>( pMargin->GetRightMargin() * mnPPTX );
                                long nBottomM = static_cast<long>( pMargin->GetBottomMargin() * mnPPTY );
                                nStartX += nLeftM;
                                nStartY += nTopM;
                                nOutWidth -= nLeftM + nRightM;
                                nOutHeight -= nTopM + nBottomM;

                                // rotate here already, to adjust paper size for page breaks
                                long nAttrRotate = 0;
                                double nSin = 0.0;
                                double nCos = 1.0;
                                SvxRotateMode eRotMode = SVX_ROTATE_MODE_STANDARD;
                                if ( eOrient == SvxCellOrientation::Standard )
                                {
                                    nAttrRotate = pPattern->
                                                        GetItem(ATTR_ROTATE_VALUE, pCondSet).GetValue();
                                    if ( nAttrRotate )
                                    {
                                        eRotMode = pPattern->GetItem(ATTR_ROTATE_MODE, pCondSet).GetValue();

                                        if ( nAttrRotate == 18000 )
                                            eRotMode = SVX_ROTATE_MODE_STANDARD;    // no overflow

                                        if ( bLayoutRTL )
                                            nAttrRotate = -nAttrRotate;

                                        double nRealOrient = nAttrRotate * F_PI18000;   // 1/100 degree
                                        nCos = cos( nRealOrient );
                                        nSin = sin( nRealOrient );
                                    }
                                }

                                Size aPaperSize( 1000000, 1000000 );
                                if (eOrient==SvxCellOrientation::Stacked)
                                    aPaperSize.setWidth( nOutWidth );             // to center
                                else if (bBreak)
                                {
                                    if (nAttrRotate)
                                    {
                                        //! the correct paper size for break depends on the number
                                        //! of rows, as long as the rows can not be outputted individually
                                        //! offsetted -> therefore unlimited, so no wrapping.
                                        //! With offset rows the following would be correct:
                                        aPaperSize.setWidth( static_cast<long>(nOutHeight / fabs(nSin)) );
                                    }
                                    else if (eOrient == SvxCellOrientation::Standard)
                                        aPaperSize.setWidth( nOutWidth );
                                    else
                                        aPaperSize.setWidth( nOutHeight - 1 );
                                }
                                if (bPixelToLogic)
                                    pEngine->SetPaperSize(mpRefDevice->PixelToLogic(aPaperSize));
                                else
                                    pEngine->SetPaperSize(aPaperSize);  // scale is always 1

                                // read data from cell

                                if (aCell.meType == CELLTYPE_EDIT)
                                {
                                    if (aCell.mpEditText)
                                        pEngine->SetText(*aCell.mpEditText);
                                    else
                                    {
                                        OSL_FAIL("pData == 0");
                                    }
                                }
                                else
                                {
                                    sal_uInt32 nFormat = pPattern->GetNumberFormat(
                                                                mpDoc->GetFormatTable(), pCondSet );
                                    OUString aString;
                                    Color* pColor;
                                    ScCellFormat::GetString( aCell,
                                                             nFormat,aString, &pColor,
                                                             *mpDoc->GetFormatTable(),
                                                             mpDoc,
                                                             mbShowNullValues,
                                                             mbShowFormulas);

                                    pEngine->SetText(aString);
                                    if ( pColor && !mbSyntaxMode && !( mbUseStyleColor && mbForceAutoColor ) )
                                        lcl_SetEditColor( *pEngine, *pColor );
                                }

                                if ( mbSyntaxMode )
                                {
                                    SetEditSyntaxColor(*pEngine, aCell);
                                }
                                else if ( mbUseStyleColor && mbForceAutoColor )
                                    lcl_SetEditColor( *pEngine, COL_AUTO );     //! or have a flag at EditEngine

                                pEngine->SetUpdateMode( true );     // after SetText, before CalcTextWidth/GetTextHeight

                                long nEngineWidth  = static_cast<long>(pEngine->CalcTextWidth());
                                long nEngineHeight = pEngine->GetTextHeight();

                                if (nAttrRotate && bBreak)
                                {
                                    double nAbsCos = fabs( nCos );
                                    double nAbsSin = fabs( nSin );

                                    // adjust width of papersize for height of text
                                    int nSteps = 5;
                                    while (nSteps > 0)
                                    {
                                        // everything is in pixels
                                        long nEnginePixel = mpRefDevice->LogicToPixel(
                                                                Size(0,nEngineHeight)).Height();
                                        long nEffHeight = nOutHeight - static_cast<long>(nEnginePixel * nAbsCos) + 2;
                                        long nNewWidth = static_cast<long>(nEffHeight / nAbsSin) + 2;
                                        bool bFits = ( nNewWidth >= aPaperSize.Width() );
                                        if ( bFits )
                                            nSteps = 0;
                                        else
                                        {
                                            if ( nNewWidth < 4 )
                                            {
                                                // can't fit -> fall back to using half height
                                                nEffHeight = nOutHeight / 2;
                                                nNewWidth = static_cast<long>(nEffHeight / nAbsSin) + 2;
                                                nSteps = 0;
                                            }
                                            else
                                                --nSteps;

                                            // set paper width and get new text height
                                            aPaperSize.setWidth( nNewWidth );
                                            if (bPixelToLogic)
                                                pEngine->SetPaperSize(mpRefDevice->PixelToLogic(aPaperSize));
                                            else
                                                pEngine->SetPaperSize(aPaperSize);  // Scale is always 1
                                            //pEngine->QuickFormatDoc( sal_True );

                                            nEngineWidth  = static_cast<long>(pEngine->CalcTextWidth());
                                            nEngineHeight = pEngine->GetTextHeight();
                                        }
                                    }
                                }

                                long nRealWidth  = nEngineWidth;
                                long nRealHeight = nEngineHeight;

                                // when rotated, adjust size
                                if (nAttrRotate)
                                {
                                    double nAbsCos = fabs( nCos );
                                    double nAbsSin = fabs( nSin );

                                    if ( eRotMode == SVX_ROTATE_MODE_STANDARD )
                                        nEngineWidth = static_cast<long>( nRealWidth * nAbsCos +
                                                                nRealHeight * nAbsSin );
                                    else
                                        nEngineWidth = static_cast<long>( nRealHeight / nAbsSin );
                                    //! limit !!!

                                    nEngineHeight = static_cast<long>( nRealHeight * nAbsCos +
                                                             nRealWidth * nAbsSin );
                                }

                                if (!nAttrRotate)           //  only rotated text here
                                    bHidden = true;         //! check first !!!

                                //! omit which doesn't stick out

                                if (!bHidden)
                                {
                                    Size aClipSize( nScrX+nScrW-nStartX, nScrY+nScrH-nStartY );

                                    // go on writing

                                    Size aCellSize;
                                    if (bPixelToLogic)
                                        aCellSize = mpRefDevice->PixelToLogic( Size( nOutWidth, nOutHeight ) );
                                    else
                                        aCellSize = Size( nOutWidth, nOutHeight );  // scale is one

                                    long nGridWidth = nEngineWidth;
                                    bool bNegative = false;
                                    if ( eRotMode != SVX_ROTATE_MODE_STANDARD )
                                    {
                                        nGridWidth = aCellSize.Width() +
                                                std::abs(static_cast<long>( aCellSize.Height() * nCos / nSin ));
                                        bNegative = ( pInfo->nRotateDir == ScRotateDir::Left );
                                        if ( bLayoutRTL )
                                            bNegative = !bNegative;
                                    }

                                    // use GetOutputArea to hide the grid
                                    // (clip region is done manually below)
                                    OutputAreaParam aAreaParam;

                                    SCCOL nCellX = nX;
                                    SCROW nCellY = nY;
                                    SvxCellHorJustify eOutHorJust = eHorJust;
                                    if ( eRotMode != SVX_ROTATE_MODE_STANDARD )
                                        eOutHorJust = bNegative ? SvxCellHorJustify::Right : SvxCellHorJustify::Left;
                                    long nNeededWidth = nGridWidth;     // in pixel for GetOutputArea
                                    if ( bPixelToLogic )
                                        nNeededWidth =  mpRefDevice->LogicToPixel(Size(nNeededWidth,0)).Width();

                                    GetOutputArea( nX, nArrY, nCellStartX, nPosY, nCellX, nCellY, nNeededWidth,
                                                    *pPattern, sal::static_int_cast<sal_uInt16>(eOutHorJust),
                                                    false, false, true, aAreaParam );

                                    if ( bShrink )
                                    {
                                        long nPixelWidth = bPixelToLogic ?
                                            mpRefDevice->LogicToPixel(Size(nEngineWidth,0)).Width() : nEngineWidth;
                                        long nNeededPixel = nPixelWidth + nLeftM + nRightM;

                                        aAreaParam.mbLeftClip = aAreaParam.mbRightClip = true;

                                        // always do height
                                        ShrinkEditEngine( *pEngine, aAreaParam.maAlignRect, nLeftM, nTopM, nRightM, nBottomM,
                                            false, eOrient, nAttrRotate, bPixelToLogic,
                                            nEngineWidth, nEngineHeight, nNeededPixel, aAreaParam.mbLeftClip, aAreaParam.mbRightClip );

                                        if ( eRotMode == SVX_ROTATE_MODE_STANDARD )
                                        {
                                            // do width only if rotating within the cell (standard mode)
                                            ShrinkEditEngine( *pEngine, aAreaParam.maAlignRect, nLeftM, nTopM, nRightM, nBottomM,
                                                true, eOrient, nAttrRotate, bPixelToLogic,
                                                nEngineWidth, nEngineHeight, nNeededPixel, aAreaParam.mbLeftClip, aAreaParam.mbRightClip );
                                        }

                                        // nEngineWidth/nEngineHeight is updated in ShrinkEditEngine
                                        // (but width is only valid for standard mode)
                                        nRealWidth  = static_cast<long>(pEngine->CalcTextWidth());
                                        nRealHeight = pEngine->GetTextHeight();

                                        if ( eRotMode != SVX_ROTATE_MODE_STANDARD )
                                            nEngineWidth = static_cast<long>( nRealHeight / fabs( nSin ) );
                                    }

                                    long nClipStartX = nStartX;
                                    if (nX<nX1)
                                    {
                                        //! clipping is not needed when on the left side of the window

                                        if (nStartX<nScrX)
                                        {
                                            long nDif = nScrX - nStartX;
                                            nClipStartX = nScrX;
                                            aClipSize.AdjustWidth( -nDif );
                                        }
                                    }

                                    long nClipStartY = nStartY;
                                    if (nArrY==0)
                                    {
                                        if ( nClipStartY < nRowPosY )
                                        {
                                            long nDif = nRowPosY - nClipStartY;
                                            nClipStartY = nRowPosY;
                                            aClipSize.AdjustHeight( -nDif );
                                        }
                                    }

                                    if ( nAttrRotate /* && eRotMode != SVX_ROTATE_MODE_STANDARD */ )
                                    {
                                        // only clip rotated output text at the page border
                                        nClipStartX = nScrX;
                                        aClipSize.setWidth( nScrW );
                                    }

                                    if (bPixelToLogic)
                                        aAreaParam.maClipRect = mpRefDevice->PixelToLogic( tools::Rectangle(
                                                        Point(nClipStartX,nClipStartY), aClipSize ) );
                                    else
                                        aAreaParam.maClipRect = tools::Rectangle(Point(nClipStartX, nClipStartY),
                                                                aClipSize );    // Scale = 1

                                    if (bMetaFile)
                                    {
                                        mpDev->Push();
                                        mpDev->IntersectClipRegion( aAreaParam.maClipRect );
                                    }
                                    else
                                        mpDev->SetClipRegion( vcl::Region( aAreaParam.maClipRect ) );

                                    Point aLogicStart;
                                    if (bPixelToLogic)
                                        aLogicStart = mpRefDevice->PixelToLogic( Point(nStartX,nStartY) );
                                    else
                                        aLogicStart = Point(nStartX, nStartY);
                                    if ( eOrient!=SvxCellOrientation::Standard || !bBreak )
                                    {
                                        long nAvailWidth = aCellSize.Width();
                                        if (eType==OUTTYPE_WINDOW &&
                                                eOrient!=SvxCellOrientation::Stacked &&
                                                pInfo->bAutoFilter)
                                        {
                                            // filter drop-down width is now independent from row height
                                            if (bPixelToLogic)
                                                nAvailWidth -= mpRefDevice->PixelToLogic(Size(0,DROPDOWN_BITMAP_SIZE)).Height();
                                            else
                                                nAvailWidth -= DROPDOWN_BITMAP_SIZE;
                                            long nComp = nEngineWidth;
                                            if (nAvailWidth<nComp) nAvailWidth=nComp;
                                        }

                                        // horizontal orientation

                                        if (eOrient==SvxCellOrientation::Standard && !nAttrRotate)
                                        {
                                            if (eHorJust==SvxCellHorJustify::Right ||
                                                eHorJust==SvxCellHorJustify::Center)
                                            {
                                                pEngine->SetUpdateMode( false );

                                                SvxAdjust eSvxAdjust =
                                                    (eHorJust==SvxCellHorJustify::Right) ?
                                                        SvxAdjust::Right : SvxAdjust::Center;
                                                pEngine->SetDefaultItem(
                                                    SvxAdjustItem( eSvxAdjust, EE_PARA_JUST ) );

                                                aPaperSize.setWidth( nOutWidth );
                                                if (bPixelToLogic)
                                                    pEngine->SetPaperSize(mpRefDevice->PixelToLogic(aPaperSize));
                                                else
                                                    pEngine->SetPaperSize(aPaperSize);

                                                pEngine->SetUpdateMode( true );
                                            }
                                        }
                                        else
                                        {
                                            // rotated text is centered by default
                                            if (eHorJust==SvxCellHorJustify::Right)
                                                aLogicStart.AdjustX(nAvailWidth - nEngineWidth );
                                            else if (eHorJust==SvxCellHorJustify::Center ||
                                                     eHorJust==SvxCellHorJustify::Standard)
                                                aLogicStart.AdjustX((nAvailWidth - nEngineWidth) / 2 );
                                        }
                                    }

                                    if ( bLayoutRTL )
                                    {
                                        if (bPixelToLogic)
                                            aLogicStart.AdjustX( -(mpRefDevice->PixelToLogic(
                                                            Size( nCellWidth, 0 ) ).Width()) );
                                        else
                                            aLogicStart.AdjustX( -nCellWidth );
                                    }

                                    if ( eOrient==SvxCellOrientation::Standard ||
                                         eOrient==SvxCellOrientation::Stacked || !bBreak )
                                    {
                                        if (eVerJust==SvxCellVerJustify::Bottom ||
                                            eVerJust==SvxCellVerJustify::Standard)
                                        {
                                            if (bPixelToLogic)
                                                aLogicStart.AdjustY(mpRefDevice->PixelToLogic( Size(0,
                                                                mpRefDevice->LogicToPixel(aCellSize).Height() -
                                                                mpRefDevice->LogicToPixel(Size(0,nEngineHeight)).Height()
                                                                )).Height() );
                                            else
                                                aLogicStart.AdjustY(aCellSize.Height() - nEngineHeight );
                                        }

                                        else if (eVerJust==SvxCellVerJustify::Center)
                                        {
                                            if (bPixelToLogic)
                                                aLogicStart.AdjustY(mpRefDevice->PixelToLogic( Size(0,(
                                                                mpRefDevice->LogicToPixel(aCellSize).Height() -
                                                                mpRefDevice->LogicToPixel(Size(0,nEngineHeight)).Height())
                                                                / 2)).Height() );
                                            else
                                                aLogicStart.AdjustY((aCellSize.Height() - nEngineHeight) / 2 );
                                        }
                                    }

                                    // TOPBOTTOM and BOTTOMTOP are handled in DrawStrings/DrawEdit
                                    OSL_ENSURE( eOrient == SvxCellOrientation::Standard && nAttrRotate,
                                                "DrawRotated: no rotation" );

                                    long nOriVal = 0;
                                    if ( nAttrRotate )
                                    {
                                        // attribute is 1/100, Font 1/10 Grad
                                        nOriVal = nAttrRotate / 10;

                                        double nAddX = 0.0;
                                        double nAddY = 0.0;
                                        if ( nCos > 0.0 && eRotMode != SVX_ROTATE_MODE_STANDARD )
                                        {
                                            //! limit !!!
                                            double nH = nRealHeight * nCos;
                                            nAddX += nH * ( nCos / fabs(nSin) );
                                        }
                                        if ( nCos < 0.0 && eRotMode == SVX_ROTATE_MODE_STANDARD )
                                            nAddX -= nRealWidth * nCos;
                                        if ( nSin < 0.0 )
                                            nAddX -= nRealHeight * nSin;
                                        if ( nSin > 0.0 )
                                            nAddY += nRealWidth * nSin;
                                        if ( nCos < 0.0 )
                                            nAddY -= nRealHeight * nCos;

                                        if ( eRotMode != SVX_ROTATE_MODE_STANDARD )
                                        {
                                            //! limit !!!
                                            double nSkew = nTotalHeight * nCos / fabs(nSin);
                                            if ( eRotMode == SVX_ROTATE_MODE_CENTER )
                                                nAddX -= nSkew * 0.5;
                                            if ( ( eRotMode == SVX_ROTATE_MODE_TOP && nSin > 0.0 ) ||
                                                 ( eRotMode == SVX_ROTATE_MODE_BOTTOM && nSin < 0.0 ) )
                                                nAddX -= nSkew;

                                            long nUp = 0;
                                            if ( eVerJust == SvxCellVerJustify::Center )
                                                nUp = ( aCellSize.Height() - nEngineHeight ) / 2;
                                            else if ( eVerJust == SvxCellVerJustify::Top )
                                            {
                                                if ( nSin > 0.0 )
                                                    nUp = aCellSize.Height() - nEngineHeight;
                                            }
                                            else    // BOTTOM / STANDARD
                                            {
                                                if ( nSin < 0.0 )
                                                    nUp = aCellSize.Height() - nEngineHeight;
                                            }
                                            if ( nUp )
                                                nAddX += ( nUp * nCos / fabs(nSin) );
                                        }

                                        aLogicStart.AdjustX(static_cast<long>(nAddX) );
                                        aLogicStart.AdjustY(static_cast<long>(nAddY) );
                                    }

                                    //  bSimClip is not used here (because nOriVal is set)

                                    pEngine->Draw( mpDev, aLogicStart, static_cast<short>(nOriVal) );

                                    if (bMetaFile)
                                        mpDev->Pop();
                                    else
                                        mpDev->SetClipRegion();
                                }
                            }
                        }
                    }
                    nPosX += pRowInfo[0].pCellInfo[nX+1].nWidth * nLayoutSign;
                }
            }
            nRowPosY += pRowInfo[nArrY].nHeight;
        }
    }

    /* vim:set shiftwidth=4 softtabstop=4 expandtab: */

↑ V1004 The 'pPattern' pointer was used unsafely after it was verified against nullptr. Check lines: 4577, 4620.