/* -*- 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/frmdiritem.hxx>
#include <editeng/justifyitem.hxx>
#include <svx/rotmodit.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 <formula/errorcodes.hxx>
#include <svl/numformat.hxx>
#include <svl/zforlist.hxx>
#include <svl/zformat.hxx>
#include <vcl/kernarray.hxx>
#include <vcl/svapp.hxx>
#include <vcl/metric.hxx>
#include <vcl/outdev.hxx>
#include <vcl/pdfextoutdevdata.hxx>
#include <vcl/settings.hxx>
#include <vcl/glyphitem.hxx>
#include <vcl/glyphitemcache.hxx>
#include <sal/log.hxx>
#include <unotools/charclass.hxx>
#include <osl/diagnose.h>

#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 <stlsheet.hxx>
#include <spellcheckcontext.hxx>
#include <scopetools.hxx>
#include <tabvwsh.hxx>

#include <com/sun/star/i18n/DirectionProperty.hpp>
#include <comphelper/scopeguard.hxx>
#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;
constexpr auto HMM_PER_TWIPS = o3tl::convert(1.0, o3tl::Length::twip, o3tl::Length::mm100);

class ScDrawStringsVars
{
    ScOutputData*       pOutput;                // connection

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

    vcl::Font           aFont;                  // created from attributes
    FontMetric          aMetric;
    tools::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;
    tools::Long                nOriginalWidth;
    tools::Long                nMaxDigitWidth;
    tools::Long                nSignWidth;
    tools::Long                nDotWidth;
    tools::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( const ScRefCellValue& rCell );   // TRUE -> drop pOldPattern
    void        SetHashText();
    bool SetTextToWidthOrHash( ScRefCellValue& rCell, tools::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; }
    tools::Long                    GetOriginalWidth() const { return nOriginalWidth; }
    tools::Long             GetFmtTextWidth(const OUString& rString);

    // 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( tools::Long nColWidth );

    tools::Long    GetAscent() const   { return nAscentPixel; }
    bool    IsRotated() const   { return bRotated; }

    void    SetShrinkScale( tools::Long nScale, SvtScriptType nScript );

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

    bool    HasEditCharacters() const;

    // ScOutputData::LayoutStrings() usually triggers a number of calls that require
    // to lay out the text, which is relatively slow, so cache that operation.
    const SalLayoutGlyphs*  GetLayoutGlyphs(const OUString& rString) const
    {
        return SalLayoutGlyphsCache::self()->GetLayoutGlyphs(pOutput->pFmtDevice, rString);
    }

private:
    tools::Long        GetMaxDigitWidth();     // in logic units
    tools::Long        GetSignWidth();
    tools::Long        GetDotWidth();
    tools::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 )
{
    bCellContrast = pOutput->mbUseStyleColor &&
            Application::GetSettings().GetStyleSettings().GetHighContrastMode();

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

void ScDrawStringsVars::SetShrinkScale( tools::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->maZoomY;
    vcl::Font aTmpFont;
    pPattern->fillFontOnly(aTmpFont, pFmtDevice, &aFraction, pCondSet, nScript);
    // only need font height
    tools::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);
}

}

static bool lcl_isNumberFormatText(const ScDocument* pDoc, SCCOL nCellX, SCROW nCellY, SCTAB mnTab )
{
    sal_uInt32 nCurrentNumberFormat = pDoc->GetNumberFormat( nCellX, nCellY, mnTab );
    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 ? ScAutoFontColorMode::IgnoreAll : ScAutoFontColorMode::IgnoreFont;
        else
            eColorMode = bCellContrast ? ScAutoFontColorMode::IgnoreBack : ScAutoFontColorMode::Display;
    }
    else
        eColorMode = ScAutoFontColorMode::Print;

    if (bPixelToLogic)
        pPattern->fillFont(aFont, eColorMode, pFmtDevice, nullptr, pCondSet, nScript, &aBackConfigColor, &aTextConfigColor);
    else
        pPattern->fillFont(aFont, eColorMode, pFmtDevice, &pOutput->maZoomY, 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;
    }

    sal_Int16 nRot;
    switch (eAttrOrient)
    {
        case SvxCellOrientation::Standard:
            nRot = 0;
            bRotated = pPattern->GetItem( ATTR_ROTATE_VALUE, pCondSet ).GetValue() != 0_deg100 &&
                       !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( Degree10(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.
    // See also matching kerning cases in ScColumn::GetNeededSize and
    // ScColumn::GetOptimalColWidth.
    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;
    nValueFormat = pPattern->GetNumberFormat( pOutput->mpDoc->GetFormatTable(), pCondSet );

    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();
}

static bool SameValue( const ScRefCellValue& rCell, const ScRefCellValue& rOldCell )
{
    return rOldCell.getType() == CELLTYPE_VALUE && rCell.getType() == CELLTYPE_VALUE &&
        rCell.getDouble() == rOldCell.getDouble();
}

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

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

            const Color* pColor;
            sal_uLong nFormat = nValueFormat;
            aString = ScCellFormat::GetString( rCell,
                                     nFormat, &pColor,
                                     nullptr,
                                     *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, u"" );
                        // 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(u"###"_ustr);
}

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

    // Measuring a string containing a single copy of the repeat char is inaccurate.
    // To increase accuracy, start with a representative sample of a padding sequence.
    constexpr sal_Int32 nSampleSize = 20;
    OUStringBuffer aFill(nSampleSize);
    comphelper::string::padToLength(aFill, nSampleSize, nRepeatChar);

    tools::Long nSampleWidth = GetFmtTextWidth(aFill.makeStringAndClear());
    double nAvgCharWidth = static_cast<double>(nSampleWidth) / static_cast<double>(nSampleSize);

    // Intentionally truncate to round toward zero
    auto nCharWidth = static_cast<tools::Long>(nAvgCharWidth);
    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 ?
    tools::Long nTextWidth = aTextSize.Width();
    if ( bPixelToLogic )
    {
        nColWidth = pOutput->mpRefDevice->PixelToLogic(Size(nColWidth,0)).Width();
        nTextWidth = pOutput->mpRefDevice->PixelToLogic(Size(nTextWidth,0)).Width();
    }

    tools::Long nSpaceToFill = nColWidth - nTextWidth;
    if ( nSpaceToFill <= nCharWidth )
        return;

    // Intentionally truncate to round toward zero
    auto nCharsToInsert = static_cast<sal_Int32>(static_cast<double>(nSpaceToFill) / nAvgCharWidth);
    aFill.ensureCapacity(nCharsToInsert);
    comphelper::string::padToLength(aFill, nCharsToInsert, nRepeatChar);
    aString = aString.replaceAt( nRepeatPos, 0, aFill );
    TextChanged();
}

bool ScDrawStringsVars::SetTextToWidthOrHash( ScRefCellValue& rCell, tools::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.getType();
    if (eType != CELLTYPE_VALUE && eType != CELLTYPE_FORMULA)
        // must be a value or formula cell.
        return false;

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

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

    double fVal = rCell.getValue();

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

    tools::Long nMaxDigit = GetMaxDigitWidth();
    if (!nMaxDigit)
        return false;

    sal_uInt16 nNumDigits = static_cast<sal_uInt16>(nWidth / nMaxDigit);
    {
        OUString sTempOut(aString);
        if (!pNumFormat->GetOutputString(fVal, nNumDigits, sTempOut, pOutput->mpDoc->GetFormatTable()->GetNatNum()))
        {
            aString = sTempOut;
            // Failed to get output string.  Bail out.
            return false;
        }
        aString = sTempOut;
    }
    sal_uInt8 nSignCount = 0, nDecimalCount = 0, nExpCount = 0;
    sal_Int32 nLen = aString.getLength();
    sal_Unicode cDecSep = ScGlobal::getLocaleData().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, pOutput->mpDoc->GetFormatTable()->GetNatNum()))
        {
            aString = sTempOut;
            // Failed to get output string.  Bail out.
            return false;
        }
        aString = sTempOut;
    }

    tools::Long nActualTextWidth = GetFmtTextWidth(aString);
    if (nActualTextWidth > nWidth)
    {
        // Even after the decimal adjustment the text doesn't fit.  Give up.
        SetHashText();
        return true;
    }

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

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

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

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

    aTextSize.setHeight( aMetric.GetAscent() + aMetric.GetDescent() );
    if ( GetOrient() != SvxCellOrientation::Standard )
    {
        tools::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
}

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

    for (char i = 0; i < 10; ++i)
    {
        char cDigit = '0' + i;
        // Do not cache this with GetFmtTextWidth(), nMaxDigitWidth is already cached.
        tools::Long n = pOutput->pFmtDevice->GetTextWidth(OUString(cDigit));
        nMaxDigitWidth = ::std::max(nMaxDigitWidth, n);
    }
    return nMaxDigitWidth;
}

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

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

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

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

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

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

tools::Long ScDrawStringsVars::GetFmtTextWidth( const OUString& rString )
{
    return pOutput->pFmtDevice->GetTextWidth( rString, 0, -1, nullptr, GetLayoutGlyphs( rString ));
}

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

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

    aTextSize.setHeight( aMetric.GetAscent() + aMetric.GetDescent() );
    if ( GetOrient() != SvxCellOrientation::Standard )
    {
        tools::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:
                // tdf#122676: Ignore CHAR_NBSP (this is thousand separator in any number)
                // if repeat character is set
                if (nRepeatPos < 0)
                    return true;
                break;
            case CHAR_SHY:
            case CHAR_ZWSP:
            case CHAR_LRM:
            case CHAR_RLM:
            case CHAR_NBHY:
            case CHAR_WJ:
                return true;
            default:
                break;
        }
    }

    return false;
}

double ScOutputData::GetStretch() const
{
    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>(maZoomY) / static_cast<double>(maZoomX);
    }
    else
    {
        // when formatting for printer, device map mode has already been taken care of
        return static_cast<double>(maZoomY) / static_cast<double>(maZoomX);
    }
}

//  output strings

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

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

    if ( !aURL.isEmpty() && pPDFData )
    {
        vcl::PDFExtOutDevBookmarkEntry aBookmark;
        aBookmark.nLinkId = pPDFData->CreateLink(rRect, u""_ustr);
        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.getType())
    {
        case CELLTYPE_VALUE:
            pFont->SetColor(*mxValueColor);
        break;
        case CELLTYPE_STRING:
            pFont->SetColor(*mxTextColor);
        break;
        case CELLTYPE_FORMULA:
            pFont->SetColor(*mxFormulaColor);
        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.getType())
    {
        case CELLTYPE_VALUE:
            aColor = *mxValueColor;
            break;
        case CELLTYPE_STRING:
        case CELLTYPE_EDIT:
            aColor = *mxTextColor;
            break;
        case CELLTYPE_FORMULA:
            aColor = *mxFormulaColor;
            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 == mnVisX1 );
    bool bIsTop  = ( nY == mnVisY1 ) || bVisRowChanged;

    bool bHOver;
    bool bVOver;
    bool bHidden;

    if (!mpDoc->ColHidden(nX, mnTab) && nX >= mnX1 && nX <= mnX2
            && !mpDoc->RowHidden(nY, mnTab) && nY >= mnY1 && nY <= mnY2)
    {
        ScCellInfo* pInfo = &mpRowInfo[nArrY].cellInfo(nX);
        bHOver = pInfo->bHOverlapped;
        bVOver = pInfo->bVOverlapped;
    }
    else
    {
        ScMF nOverlap2 = mpDoc->GetAttr(nX, nY, mnTab, ATTR_MERGE_FLAG).GetValue();
        bHOver = bool(nOverlap2 & ScMF::Hor);
        bVOver = bool(nOverlap2 & ScMF::Ver);
    }

    if ( bHOver && bVOver )
        bDoMerge = bIsLeft && bIsTop;
    else if ( bHOver )
        bDoMerge = bIsLeft;
    else if ( bVOver )
        bDoMerge = bIsTop;

    rOverX = nX;
    rOverY = nY;

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

        if (rOverX >= mnX1 && !bHidden)
        {
            bHOver = mpRowInfo[nArrY].cellInfo(rOverX).bHOverlapped;
            bVOver = mpRowInfo[nArrY].cellInfo(rOverX).bVOverlapped;
        }
        else
        {
            ScMF nOverlap = mpDoc->GetAttr(rOverX, rOverY, mnTab, ATTR_MERGE_FLAG).GetValue();
            bHOver = bool(nOverlap & ScMF::Hor);
            bVOver = bool(nOverlap & ScMF::Ver);
        }
    }

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

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

        if (rOverX >= mnX1 && rOverY >= mnY1 &&
            !mpDoc->ColHidden(rOverX, mnTab) &&
            !mpDoc->RowHidden(rOverY, mnTab) &&
            mpRowInfo[nArrY].nRowNo == rOverY)
        {
            bVOver = mpRowInfo[nArrY].cellInfo(rOverX).bVOverlapped;
        }
        else
        {
            ScMF nOverlap = mpDoc->GetAttr( rOverX, rOverY, mnTab, ATTR_MERGE_FLAG ).GetValue();
            bVOver = bool(nOverlap & ScMF::Ver);
        }
    }

    return true;
}

static bool StringDiffer( const ScPatternAttr*& rpOldPattern, const ScPatternAttr* pNewPattern )
{
    assert(pNewPattern && "pNewPattern");

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

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

static 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 <= mnX2 )
        bEmpty = pThisRowInfo->basicCellInfo(nX).bEmptyCellText;
    else
    {
        ScRefCellValue aCell(*mpDoc, ScAddress(nX, nY, mnTab));
        bEmpty = aCell.isEmpty();
    }

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

        bool bIsPrint = ( meType == OUTTYPE_PRINTER );

        if ( bIsPrint || mbTabProtected )
        {
            const ScProtectionAttr* pAttr =
                    mpDoc->GetEffItem( nX, nY, mnTab, ATTR_PROTECTION );
            if ( bIsPrint && pAttr->GetHidePrint() )
                bEmpty = true;
            else if ( mbTabProtected )
            {
                if ( pAttr->GetHideCell() )
                    bEmpty = true;
                else if ( mbShowFormulas && pAttr->GetHideFormula() )
                {
                    if (mpDoc->GetCellType(ScAddress(nX, nY, mnTab)) == 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, mnTab));
    if (!aCell.isEmpty() && !IsEmptyCellText(nullptr, nX, nY))
        return false;

    const ScPatternAttr* pPattern = mpDoc->GetPattern( nX, nY, mnTab );
    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, tools::Long nPosX, tools::Long nPosY,
                                  SCCOL nCellX, SCROW nCellY, tools::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 = mpRowInfo[nArrY];

    tools::Long nLayoutSign = mbLayoutRTL ? -1 : 1;

    tools::Long nCellPosX = nPosX;         // find nCellX position, starting at nX/nPosX
    SCCOL nCompCol = nX;
    while ( nCellX > nCompCol )
    {
        //! extra member function for width?
        tools::Long nColWidth = ( nCompCol <= mnX2 ) ?
                mpRowInfo[0].basicCellInfo(nCompCol).nWidth :
                static_cast<tools::Long>( mpDoc->GetColWidth( nCompCol, mnTab ) * mnPPTX );
        nCellPosX += nColWidth * nLayoutSign;
        ++nCompCol;
    }
    while ( nCellX < nCompCol )
    {
        --nCompCol;
        tools::Long nColWidth = ( nCompCol <= mnX2 ) ?
                mpRowInfo[0].basicCellInfo(nCompCol).nWidth :
                static_cast<tools::Long>( mpDoc->GetColWidth( nCompCol, mnTab ) * mnPPTX );
        nCellPosX -= nColWidth * nLayoutSign;
    }

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

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

    tools::Long nMergeSizeX = 0;
    for ( tools::Long i=0; i<nMergeCols; i++ )
    {
        tools::Long nColWidth = ( nCellX+i <= mnX2 ) ?
                mpRowInfo[0].basicCellInfo(nCellX+i).nWidth :
                static_cast<tools::Long>( mpDoc->GetColWidth( sal::static_int_cast<SCCOL>(nCellX+i), mnTab ) * mnPPTX );
        nMergeSizeX += nColWidth;
    }
    tools::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 += mpDoc->GetScaledRowHeight( nCellY+nDirect, nCellY+nMergeRows-1, mnTab, 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);

        tools::Long nMissing = nNeeded - nMergeSizeX;
        tools::Long nLeftMissing = 0;
        tools::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 ( mbLayoutRTL )
            ::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 < mpDoc->MaxCol() && ( bOverwrite || IsAvailable( nRightX+1, nCellY ) ) )
            {
                ++nRightX;
                tools::Long nAdd = static_cast<tools::Long>( mpDoc->GetColWidth( nRightX, mnTab ) * mnPPTX );
                nRightMissing -= nAdd;
                rParam.maClipRect.AdjustRight(nAdd * nLayoutSign );

                if ( rThisRowInfo.nRowNo == nCellY && nRightX >= mnX1 && nRightX <= mnX2 )
                    rThisRowInfo.cellInfo(nRightX-1).bHideGrid = true;
            }

            while ( nLeftMissing > 0 && nLeftX > 0 && ( bOverwrite || IsAvailable( nLeftX-1, nCellY ) ) )
            {
                if ( rThisRowInfo.nRowNo == nCellY && nLeftX >= mnX1 && nLeftX <= mnX2 )
                    rThisRowInfo.cellInfo(nLeftX-1).bHideGrid = true;

                --nLeftX;
                tools::Long nAdd = static_cast<tools::Long>( mpDoc->GetColWidth( nLeftX, mnTab ) * 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 && mbMarkClipped && nRightX >= mnX1 && nRightX <= mnX2 && !bBreak && !bCellIsValue )
        {
            rThisRowInfo.cellInfo(nRightX).nClipMark |= ScClipMark::Right;
            mbAnyClipped = true;
            tools::Long nMarkPixel = static_cast<tools::Long>( SC_CLIPMARK_SIZE * mnPPTX );
            rParam.maClipRect.AdjustRight( -(nMarkPixel * nLayoutSign) );
        }
        if ( nLeftMissing > 0 && mbMarkClipped && nLeftX >= mnX1 && nLeftX <= mnX2 && !bBreak && !bCellIsValue )
        {
            rThisRowInfo.cellInfo(nLeftX).nClipMark |= ScClipMark::Left;
            mbAnyClipped = true;
            tools::Long nMarkPixel = static_cast<tools::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 ( meType==OUTTYPE_WINDOW &&
             ( rPattern.GetItem(ATTR_MERGE_FLAG).GetValue() & (ScMF::Auto|ScMF::Button|ScMF::ButtonPopup) ) &&
             ( !bBreak || mpRefDevice == pFmtDevice ) )
        {
            // filter drop-down width depends on row height
            double fZoom = mpRefDevice ? static_cast<double>(mpRefDevice->GetMapMode().GetScaleY()) : 1.0;
            fZoom = fZoom > 1.0 ? fZoom : 1.0;
            const tools::Long nFilter = fZoom * DROPDOWN_BITMAP_SIZE;
            bool bFit = ( nNeeded + nFilter <= nMergeSizeX );
            if ( bFit )
            {
                // 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) );
            }
        }
    }

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

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

namespace {

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

    switch (ScGlobal::getCharClass().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 mnTab, 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(mnTab) && (beginsWithRTLCharacter( rText))) ? SvxCellHorJustify::Right : SvxCellHorJustify::Left;
        }
        else
            eHorJustContext = SvxCellHorJustify::Right;
    }
    return eHorJustContext;
}

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

void ScOutputData::LayoutStrings(bool bPixelToLogic)
{
    vcl::PDFExtOutDevData* pPDF = dynamic_cast<vcl::PDFExtOutDevData*>(mpDev->GetExtOutDevData());
    bool bTaggedPDF = pPDF && pPDF->GetIsExportTaggedPDF();
    if (bTaggedPDF)
    {
        pPDF->WrapBeginStructureElement(vcl::pdf::StructElement::Table, u"Table"_ustr);
        pPDF->GetScPDFState()->m_TableRowMap.clear();
    }

    bool bOrigIsInLayoutStrings = mpDoc->IsInLayoutStrings();
    mpDoc->SetLayoutStrings(true);
    OSL_ENSURE( mpDev == mpRefDevice ||
                mpDev->GetMapMode().GetMapUnit() == mpRefDevice->GetMapMode().GetMapUnit(),
                "LayoutStrings: different MapUnits ?!?!" );
    vcl::text::ComplexTextLayoutFlags eTextLayout = mpDev->GetLayoutMode();
    mpDev->SetLayoutMode(vcl::text::ComplexTextLayoutFlags::Default);

    comphelper::ScopeGuard g([this, bOrigIsInLayoutStrings, eTextLayout] {
        mpDoc->SetLayoutStrings(bOrigIsInLayoutStrings);
        mpDev->SetLayoutMode(eTextLayout);
    });

    sc::IdleSwitch aIdleSwitch(*mpDoc, false);

    // Try to limit interpreting to only visible cells. Calling e.g. IsValue()
    // on a formula cell that needs interpreting would call Interpret()
    // for the entire formula group, which could be large.
    mpDoc->InterpretCellsIfNeeded( ScRange( mnX1, mnY1, mnTab, mnX2, mnY2, mnTab ));

    ScDrawStringsVars aVars( this, bPixelToLogic );

    bool bProgress = false;

    tools::Long nInitPosX = mnScrX;
    if ( mbLayoutRTL )
        nInitPosX += mnMirrorW - 1;              // pixels
    tools::Long nLayoutSign = mbLayoutRTL ? -1 : 1;

    SCCOL nLastContentCol = mpDoc->MaxCol();
    if ( mnX2 < mpDoc->MaxCol() )
    {
        SCROW nEndRow;
        mpDoc->GetCellArea(mnTab, nLastContentCol, nEndRow);
    }

    SCCOL nLoopStartX = mnX1;
    if ( mnX1 > 0  && !bTaggedPDF )
        --nLoopStartX;          // start before mnX1 for rest of long text to the left

    // variables for GetOutputArea
    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;

    KernArray aDX;
    tools::Long nPosY = mnScrY;
    for (SCSIZE nArrY=1; nArrY+1<mnArrCount; nArrY++)
    {
        RowInfo* pThisRowInfo = &mpRowInfo[nArrY];
        SCROW nY = pThisRowInfo->nRowNo;
        if (pThisRowInfo->bChanged)
        {
            bool bReopenRowTag = false;
            tools::Long nPosX = nInitPosX;
            if ( nLoopStartX < mnX1 )
                nPosX -= mpRowInfo[0].basicCellInfo(nLoopStartX).nWidth * nLayoutSign;
            std::optional<SCCOL> oFirstNonEmptyCellX;
            std::optional<SCCOL> oLastEmptyCellX;
            for (SCCOL nX=nLoopStartX; nX<=mnX2; nX++)
            {
                LayoutStringsImpl(bPixelToLogic, pThisRowInfo, nX, nY, nArrY, oFirstNonEmptyCellX, oLastEmptyCellX, nLastContentCol,
                                  aAltPatterns, pOldPattern, pOldCondSet, nOldScript, aVars,
                                  bProgress, nPosX, nPosY, bTaggedPDF, bReopenRowTag, pPDF, nLayoutSign, aDX);
                nPosX += mpRowInfo[0].basicCellInfo(nX).nWidth * nLayoutSign;
            }
        }
        nPosY += mpRowInfo[nArrY].nHeight;
    }
    if (bTaggedPDF)
        pPDF->EndStructureElement(); // Table

    if ( bProgress )
        ScProgress::DeleteInterpretProgress();
}

/// inner loop of LayoutStrings
void ScOutputData::LayoutStringsImpl(bool const bPixelToLogic, RowInfo* const pThisRowInfo,
            SCCOL const nX, SCROW const nY, SCSIZE const nArrY,
            std::optional<SCCOL>& oFirstNonEmptyCellX,
            std::optional<SCCOL>& oLastEmptyCellX,
            SCCOL const nLastContentCol,
            std::vector<std::unique_ptr<ScPatternAttr> >& aAltPatterns,
            const ScPatternAttr*& pOldPattern,
            const SfxItemSet*& pOldCondSet,
            SvtScriptType& nOldScript,
            ScDrawStringsVars& aVars,
            bool& bProgress, tools::Long const nPosX, tools::Long const nPosY,
            bool const bTaggedPDF, bool& bReopenRowTag, vcl::PDFExtOutDevData* const pPDF,
            tools::Long const nLayoutSign, KernArray& aDX)
{
    const ScPatternAttr* pPattern = nullptr;
    const SfxItemSet* pCondSet = nullptr;
    bool bCellIsValue = false;
    tools::Long nNeededWidth = 0;
    OutputAreaParam aAreaParam;
    bool bMergeEmpty = false;
    const ScCellInfo* pInfo = &pThisRowInfo->cellInfo(nX);
    bool bEmpty = nX < mnX1 || pThisRowInfo->basicCellInfo(nX).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 = !mpRowInfo[nArrY-1].bChanged;
        if (GetMergeOrigin( nX,nY, nArrY, nOverX,nOverY, bVisChanged ))
        {
            nCellX = nOverX;
            nCellY = nOverY;
            bDoCell = true;
        }
        else
            bMergeEmpty = true;
    }

    //  Is there a long text further to the left, that overlaps this region?

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

        if ( *oFirstNonEmptyCellX < mnX1 &&
             !IsEmptyCellText( pThisRowInfo, *oFirstNonEmptyCellX, nY ) &&
             !mpDoc->HasAttrib( *oFirstNonEmptyCellX,nY,mnTab, mnX1,nY,mnTab, HasAttrFlags::Merged | HasAttrFlags::Overlapped ) )
        {
            nCellX = *oFirstNonEmptyCellX;
            bDoCell = true;
        }
    }

    //  Is there a long text further to the right, that overlaps this region?

    if ( bEmpty && !bMergeEmpty && nX == mnX2 && !bOverlapped )
    {
        if (!oLastEmptyCellX)
        {
            //  don't have to look further than nLastContentCol
            SCCOL nTempX=nX;
            while (nTempX < nLastContentCol && IsEmptyCellText( pThisRowInfo, nTempX, nY ))
                ++nTempX;
            oLastEmptyCellX = nTempX;
        }

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

    //  normal visible cell

    if (!bEmpty)
        bDoCell = true;

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

    if ( bDoCell && mbEditMode && nCellX == mnEditCol && nCellY == mnEditRow )
        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 >= mnX1 && nCellX <= mnX2 )
            aCell = pThisRowInfo->cellInfo(nCellX).maCell;
        else
            GetVisibleCell( nCellX, nCellY, mnTab, aCell );      // get from document
        if (aCell.isEmpty())
            bDoCell = false;
        else if (aCell.getType() == CELLTYPE_EDIT)
            bUseEditEngine = true;
    }

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

    if (bDoCell && !bUseEditEngine)
    {
        if ( nCellY == nY && nCellX >= mnX1 && nCellX <= mnX2 )
        {
            ScCellInfo& rCellInfo = pThisRowInfo->cellInfo(nCellX);
            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, mnTab );
                pCondSet = mpDoc->GetCondResult( nCellX, nCellY, mnTab );
            }
        }
        else        // get from document
        {
            pPattern = mpDoc->GetPattern( nCellX, nCellY, mnTab );
            pCondSet = mpDoc->GetCondResult( nCellX, nCellY, mnTab );
        }
        if ( mpDoc->GetPreviewFont() || mpDoc->GetPreviewCellStyle() )
        {
            aAltPatterns.push_back(std::make_unique<ScPatternAttr>(*pPattern));
            ScPatternAttr* pAltPattern = aAltPatterns.back().get();
            if (  ScStyleSheet* pPreviewStyle = mpDoc->GetPreviewCellStyle( nCellX, nCellY, mnTab ) )
            {
                pAltPattern->SetStyleSheet(pPreviewStyle);
            }
            else if ( SfxItemSet* pFontSet = mpDoc->GetPreviewFont( nCellX, nCellY, mnTab ) )
            {
                if ( const SvxFontItem* pItem = pFontSet->GetItemIfSet( ATTR_FONT ) )
                    pAltPattern->ItemSetPut( *pItem );
                if ( const SvxFontItem* pItem = pFontSet->GetItemIfSet( ATTR_CJK_FONT ) )
                    pAltPattern->ItemSetPut( *pItem );
                if ( const SvxFontItem* pItem = pFontSet->GetItemIfSet( ATTR_CTL_FONT ) )
                    pAltPattern->ItemSetPut( *pItem );
            }
            pPattern = pAltPattern;
        }

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

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

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

        if ( !ScPatternAttr::areSame(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.getType() == CELLTYPE_FORMULA);
        if ( bFormulaCell )
            lcl_CreateInterpretProgress(bProgress, mpDoc, aCell.getFormula());
        if ( aVars.SetText(aCell) )
            pOldPattern = nullptr;
        bUseEditEngine = aVars.HasEditCharacters() || (bFormulaCell && aCell.getFormula()->IsMultilineResult());
    }
    tools::Long nTotalMargin = 0;
    SvxCellHorJustify eOutHorJust = SvxCellHorJustify::Standard;
    if (bDoCell && !bUseEditEngine)
    {
        CellType eCellType = aCell.getType();
        bCellIsValue = ( eCellType == CELLTYPE_VALUE );
        if ( eCellType == CELLTYPE_FORMULA )
        {
            ScFormulaCell* pFCell = aCell.getFormula();
            bCellIsValue = pFCell->IsRunning() || pFCell->IsValue();
        }

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

        bool bBreak = ( aVars.GetLineBreak() || aVars.GetHorJust() == SvxCellHorJustify::Block );
        // #i111387# #o11817313# tdf#121040 disable automatic line breaks for all number formats
        // Must be synchronized with ScColumn::GetNeededSize()
        SvNumberFormatter* pFormatter = mpDoc->GetFormatTable();
        if (bBreak && bCellIsValue && (pFormatter->GetType(aVars.GetResultValueFormat()) == SvNumFormatType::NUMBER))
            bBreak = false;

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

        nTotalMargin =
            static_cast<tools::Long>(aVars.GetLeftTotal() * mnPPTX) +
            static_cast<tools::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
            {
                tools::Long nAvailable = aAreaParam.maAlignRect.GetWidth() - nTotalMargin;
                tools::Long nScaleSize = aVars.GetTextSize().Width();         // without margin

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

                    aVars.SetShrinkScale( nScale, nOldScript );
                    tools::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 )
        {
            tools::Long nAvailable = aAreaParam.maAlignRect.GetWidth() - nTotalMargin;
            tools::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 )
            {
                tools::Long nRepeatCount = nAvailable / nRepeatSize;
                if ( nRepeatCount > 1 )
                {
                    OUString aCellStr = aVars.GetString();
                    OUStringBuffer aRepeated(aCellStr);
                    for ( tools::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
            {
                tools::Long nHeight = aVars.GetTextSize().Height() +
                                static_cast<tools::Long>(aVars.GetMargin()->GetTopMargin()*mnPPTY) +
                                static_cast<tools::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 ScCellInfo to be drawn in DrawEdit:
        //  Cells to the left are marked directly, cells to the
        //  right are handled by the flag for mnX2
        SCCOL nMarkX = ( nCellX <= mnX2 ) ? nCellX : mnX2;
        pThisRowInfo->basicCellInfo(nMarkX).bEditEngine = true;
        bDoCell = false;    // don't draw here

        // Mark the tagged "TD" structure element to be drawn in DrawEdit
        if (bTaggedPDF)
        {
            if (bReopenRowTag)
                ReopenPDFStructureElement(vcl::pdf::StructElement::TableRow, nY);
            else
            {
                sal_Int32 nId = pPDF->EnsureStructureElement(nullptr);
                pPDF->InitStructureElement(nId, vcl::pdf::StructElement::TableRow,
                                           u"TR"_ustr);
                pPDF->BeginStructureElement(nId);
                pPDF->GetScPDFState()->m_TableRowMap.emplace(nY, nId);
                bReopenRowTag = true;
            }

            pPDF->WrapBeginStructureElement(vcl::pdf::StructElement::TableData,
                                            u"TD"_ustr);

            sal_Int32 nId = pPDF->GetCurrentStructureElement();
            pPDF->GetScPDFState()->m_TableDataMap[{ nY, nX }] = nId;

            pPDF->EndStructureElement(); // TableData
            pPDF->EndStructureElement(); // TableRow
        }
    }
    if ( bDoCell )
    {
        if ( bCellIsValue && ( aAreaParam.mbLeftClip || aAreaParam.mbRightClip ) )
        {
            bool bHasHashText = false;
            if (mbShowFormulas)
            {
                aVars.SetHashText();
                bHasHashText = true;
            }
            else
                // Adjust the decimals to fit the available column width.
                bHasHashText = aVars.SetTextToWidthOrHash( aCell, aAreaParam.mnColWidth - nTotalMargin );

            if ( bHasHashText )
            {
                tools::Long nMarkPixel = SC_CLIPMARK_SIZE * mnPPTX;

                if ( eOutHorJust == SvxCellHorJustify::Left )
                {
                    if ( nCellY == nY && nCellX >= mnX1 && nCellX <= mnX2 )
                        mpRowInfo[nArrY].cellInfo(nCellX).nClipMark |= ScClipMark::Right;
                    mbAnyClipped = true;
                    aAreaParam.maClipRect.AdjustRight( -(nMarkPixel * nLayoutSign) );
                }
                else if ( eOutHorJust == SvxCellHorJustify::Right )
                {
                    if ( nCellY == nY && nCellX >= mnX1 && nCellX <= mnX2 )
                        mpRowInfo[nArrY].cellInfo(nCellX).nClipMark |= ScClipMark::Left;
                    mbAnyClipped = true;
                    aAreaParam.maClipRect.AdjustLeft(nMarkPixel * nLayoutSign);
                }
                else
                {
                    if ( nCellY == nY && nCellX >= mnX1 && nCellX <= mnX2 )
                    {
                        mpRowInfo[nArrY].cellInfo(nCellX).nClipMark |= ScClipMark::Right;
                        mpRowInfo[nArrY].cellInfo(nCellX).nClipMark |= ScClipMark::Left;
                    }
                    mbAnyClipped = true;
                    aAreaParam.maClipRect.AdjustRight( -(nMarkPixel * nLayoutSign) );
                    aAreaParam.maClipRect.AdjustLeft(nMarkPixel * nLayoutSign);
                }
            }

            nNeededWidth = aVars.GetTextSize().Width() +
                        static_cast<tools::Long>( aVars.GetLeftTotal() * mnPPTX ) +
                        static_cast<tools::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;
            }
        }

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

        bool bOutside = ( aAreaParam.maClipRect.Right() < mnScrX || aAreaParam.maClipRect.Left() >= mnScrX + mnScrW );
        // 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<tools::Long>( aVars.GetLeftTotal() * mnPPTX );
                    break;
                case SvxCellHorJustify::Right:
                    nJustPosX += nAvailWidth - aVars.GetTextSize().Width() -
                                static_cast<tools::Long>( aVars.GetRightTotal() * mnPPTX );
                    bRightAdjusted = true;
                    break;
                case SvxCellHorJustify::Center:
                    nJustPosX += ( nAvailWidth - aVars.GetTextSize().Width() +
                                static_cast<tools::Long>( aVars.GetLeftTotal() * mnPPTX ) -
                                static_cast<tools::Long>( aVars.GetMargin()->GetRightMargin() * mnPPTX ) ) / 2;
                    break;
                default:
                {
                    // added to avoid warnings
                }
            }

            tools::Long nTestClipHeight = aVars.GetTextSize().Height();
            switch (aVars.GetVerJust())
            {
                case SvxCellVerJustify::Top:
                case SvxCellVerJustify::Block:
                    {
                        tools::Long nTop = static_cast<tools::Long>( aVars.GetMargin()->GetTopMargin() * mnPPTY );
                        nJustPosY += nTop;
                        nTestClipHeight += nTop;
                    }
                    break;
                case SvxCellVerJustify::Bottom:
                    {
                        tools::Long nBot = static_cast<tools::Long>( aVars.GetMargin()->GetBottomMargin() * mnPPTY );
                        nJustPosY += nOutHeight - aVars.GetTextSize().Height() - nBot;
                        nTestClipHeight += nBot;
                    }
                    break;
                case SvxCellVerJustify::Center:
                    {
                        tools::Long nTop = static_cast<tools::Long>( aVars.GetMargin()->GetTopMargin() * mnPPTY );
                        tools::Long nBot = static_cast<tools::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 ( meType != OUTTYPE_PRINTER ||
                        ( mpDoc->GetRowFlags( nCellY, mnTab ) & 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( mnScrX );
                    aAreaParam.maClipRect.SetRight( mnScrX+mnScrW );
                }
                if (!bVClip)
                {
                    aAreaParam.maClipRect.SetTop( mnScrY );
                    aAreaParam.maClipRect.SetBottom( mnScrY+mnScrH );
                }

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

                if (mbMetaFile)
                {
                    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):

            const OUString& aString = aVars.GetString();
            if (!aString.isEmpty())
            {
                if (bTaggedPDF)
                {
                    if (bReopenRowTag)
                        ReopenPDFStructureElement(vcl::pdf::StructElement::TableRow,
                                                  nY);
                    else
                    {
                        sal_Int32 nId = pPDF->EnsureStructureElement(nullptr);
                        pPDF->InitStructureElement(
                            nId, vcl::pdf::StructElement::TableRow, u"TR"_ustr);
                        pPDF->BeginStructureElement(nId);
                        pPDF->GetScPDFState()->m_TableRowMap.emplace(nY, nId);
                        bReopenRowTag = true;
                    }

                    pPDF->WrapBeginStructureElement(vcl::pdf::StructElement::TableData,
                                                    u"TD"_ustr);
                    pPDF->WrapBeginStructureElement(vcl::pdf::StructElement::Paragraph,
                                                    u"P"_ustr);
                }

                // 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 = aVars.GetFmtTextWidth(aShort);
                            double fOffset = fTextWidth - fShortWidth;
                            aDrawTextPos.Move(fOffset, 0);
                        }
                    }
                }

                if (mbMetaFile || pFmtDevice != mpDev || maZoomX != maZoomY)
                {
                    size_t nLen = aShort.getLength();
                    if (aDX.size() < nLen)
                        aDX.resize(nLen, 0);

                    pFmtDevice->GetTextArray(aShort, &aDX);

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

                    mpDev->DrawTextArray(aDrawTextPos, aShort, aDX, {}, 0, nLen);
                }
                else
                {
                    mpDev->DrawText(aDrawTextPos, aShort, 0, -1, nullptr, nullptr,
                        aVars.GetLayoutGlyphs(aShort));
                }
                if (bTaggedPDF)
                {
                    pPDF->EndStructureElement(); // Paragraph
                    pPDF->EndStructureElement(); // TableData
                    pPDF->EndStructureElement(); // TableRow
                }
            }

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

            // PDF: whole-cell hyperlink from formula?
            bool bHasURL = pPDF && aCell.getType() == CELLTYPE_FORMULA && aCell.getFormula()->IsHyperLinkCell();
            if (bHasURL)
            {
                tools::Rectangle aURLRect( aURLStart, aVars.GetTextSize() );
                lcl_DoHyperlinkResult(mpDev, aURLRect, aCell);
            }
        }
    }
}

void ScOutputData::SetRefDevice( OutputDevice* pRDev )
{
    mpRefDevice = pFmtDevice = pRDev;
    // reset EditEngine because it depends on pFmtDevice and mpRefDevice
    mxOutputEditEngine.reset();
}

void ScOutputData::SetFmtDevice( OutputDevice* pRDev )
{
    pFmtDevice = pRDev;
    // reset EditEngine because it depends on pFmtDevice
    mxOutputEditEngine.reset();
}

void ScOutputData::SetUseStyleColor( bool bSet )
{
    mbUseStyleColor = bSet;
    // reset EditEngine because it depends on mbUseStyleColor
    mxOutputEditEngine.reset();
}

void ScOutputData::InitOutputEditEngine()
{
    if (!mxOutputEditEngine)
    {
        mxOutputEditEngine = std::make_unique<ScFieldEditEngine>(mpDoc, mpDoc->GetEditEnginePool());
        mxOutputEditEngine->SetUpdateLayout( false );
        mxOutputEditEngine->EnableUndo( false ); // don't need undo for painting purposes
        // a RefDevice always has to be set, otherwise EditEngine would create a VirtualDevice
        mxOutputEditEngine->SetRefDevice( pFmtDevice );
        EEControlBits nCtrl = mxOutputEditEngine->GetControlWord();
        if ( mbShowSpellErrors )
            nCtrl |= EEControlBits::ONLINESPELLING;
        if ( meType == OUTTYPE_PRINTER )
            nCtrl &= ~EEControlBits::MARKFIELDS;
        else
            nCtrl &= ~EEControlBits::MARKURLFIELDS;   // URLs not shaded for output
        mxOutputEditEngine->SetControlWord( nCtrl );
        mxOutputEditEngine->EnableAutoColor( mbUseStyleColor );
    }
    else
    {
        // just in case someone turned it on during the last paint cycle
        mxOutputEditEngine->SetUpdateLayout( false );
    }
    // we don't track changes to these settings, so we have to apply them every time
    mpDoc->ApplyAsianEditSettings( *mxOutputEditEngine );
    mxOutputEditEngine->SetDefaultHorizontalTextDirection( mpDoc->GetEditTextDirection( mnTab ) );
}

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

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

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

static void lcl_ScaleFonts( EditEngine& rEngine, tools::Long nPercent )
{
    bool bUpdateMode = rEngine.SetUpdateLayout( 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 ( const sal_Int32 nEnd : aPortions )
        {
            ESelection aSel( nPar, nStart, nPar, nEnd );
            SfxItemSet aAttribs = rEngine.GetAttribs( aSel );

            tools::Long nWestern = aAttribs.Get(EE_CHAR_FONTHEIGHT).GetHeight();
            tools::Long nCJK = aAttribs.Get(EE_CHAR_FONTHEIGHT_CJK).GetHeight();
            tools::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.SetUpdateLayout( true );
}

static tools::Long lcl_GetEditSize( EditEngine& rEngine, bool bWidth, bool bSwap, Degree100 nAttrRotate )
{
    if ( bSwap )
        bWidth = !bWidth;

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

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

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

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

        tools::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 );
        tools::Long nAvailable = rAlignRect.GetHeight() - nTopM - nBottomM;
        tools::Long nScale = ( nAvailable * 100 ) / nScaleSize;

        lcl_ScaleFonts( rEngine, nScale );
        rEngineHeight = lcl_GetEditSize( rEngine, false, bSwap, nAttrRotate );
        tools::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 );
        tools::Long nPixelWidth = bPixelToLogic ?
            mpRefDevice->LogicToPixel(Size(rEngineWidth,0)).Width() : rEngineWidth;
        rNeededPixel = nPixelWidth + nLeftM + nRightM;
    }
    else if ( rLeftClip || rRightClip )
    {
        // horizontal

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

        if ( nScaleSize <= nAvailable )
            return;

        tools::Long nScale = ( nAvailable * 100 ) / nScaleSize;

        lcl_ScaleFonts( rEngine, nScale );
        rEngineWidth = lcl_GetEditSize( rEngine, true, false, nAttrRotate );
        tools::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)
{}

bool ScOutputData::DrawEditParam::readCellContent(
    const ScDocument* pDoc, bool bShowNullValues, bool bShowFormulas, bool bSyntaxMode, bool bUseStyleColor, bool bForceAutoColor, bool& rWrapFields)
{
    if (maCell.getType() == CELLTYPE_EDIT)
    {
        const EditTextObject* pData = maCell.getEditText();
        if (pData)
        {
            mpEngine->SetTextCurrentDefaults(*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 );
        const Color* pColor;
        OUString aString = ScCellFormat::GetString( maCell,
                                 nFormat, &pColor,
                                 nullptr,
                                 *pDoc,
                                 bShowNullValues,
                                 bShowFormulas);

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

    if (maMisspellRanges.mpRanges)
        mpEngine->SetAllMisspellRanges(*maMisspellRanges.mpRanges);

    return true;
}

static Color GetConfBackgroundColor()
{
    if (const ScTabViewShell* pTabViewShellBg = ScTabViewShell::GetActiveViewShell())
        return pTabViewShellBg->GetViewRenderingData().GetDocColor();
    return ScModule::get()->GetColorConfig().GetColorValue(svtools::DOCCOLOR).nColor;
}

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

    if (ScPatternAttr::areSame(mpPattern, mpOldPattern) && mpCondSet == mpOldCondSet && mpPreviewFontSet == mpOldPreviewFontSet )
        return;

    Color nConfBackColor = GetConfBackgroundColor();
    bool bCellContrast = bUseStyleColor &&
            Application::GetSettings().GetStyleSettings().GetHighContrastMode();

    SfxItemSet aSet( mpEngine->GetEmptyItemSet() );
    mpPattern->FillEditItemSet( &aSet, mpCondSet );
    if ( mpPreviewFontSet )
    {
        if ( const SvxFontItem* pItem = mpPreviewFontSet->GetItemIfSet( ATTR_FONT ) )
        {
            // tdf#125054 adapt WhichID
            aSet.PutAsTargetWhich(*pItem, EE_CHAR_FONTINFO);
        }
        if ( const SvxFontItem* pItem = mpPreviewFontSet->GetItemIfSet( ATTR_CJK_FONT ) )
        {
            // tdf#125054 adapt WhichID
            aSet.PutAsTargetWhich(*pItem, EE_CHAR_FONTINFO_CJK);
        }
        if ( const SvxFontItem* pItem = mpPreviewFontSet->GetItemIfSet( ATTR_CTL_FONT ) )
        {
            // tdf#125054 adapt WhichID
            aSet.PutAsTargetWhich(*pItem, EE_CHAR_FONTINFO_CTL);
        }
    }
    bool bParaHyphenate = aSet.Get(EE_PARA_HYPHENATE).GetValue();
    mpEngine->SetDefaults( std::move(aSet) );
    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 && bParaHyphenate )
    {
        //  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.IsTransparent() || bCellContrast ) )
        aBackCol = nConfBackColor;
    mpEngine->SetBackgroundColor( aBackCol );
}

void ScOutputData::DrawEditParam::calcMargins(tools::Long& rTopM, tools::Long& rLeftM, tools::Long& rBottomM, tools::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<ScIndentItem, sal_uInt16>(*mpPattern, ATTR_INDENT, mpCondSet);

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

void ScOutputData::DrawEditParam::calcPaperSize(
    Size& rPaperSize, const tools::Rectangle& rAlignRect, double nPPTX, double nPPTY) const
{
    tools::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, tools::Long& rWidth, tools::Long& rHeight) const
{
    tools::Long nEngineWidth = 0;
    if (!mbBreak || meOrient == SvxCellOrientation::Stacked || mbAsianVertical)
        nEngineWidth = static_cast<tools::Long>(pEngine->CalcTextWidth());

    tools::Long nEngineHeight = pEngine->GetTextHeight();

    if (isVerticallyOriented())
        std::swap( nEngineWidth, nEngineHeight );

    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.getType() != CELLTYPE_FORMULA)
        return false;

    return maCell.getFormula()->IsHyperLinkCell();
}

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

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

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

    if (!mbBreak)
        return;

    // 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()");
                        [[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.getType() == 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.getEditText();
        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;

        const bool bPrevUpdateLayout = pEngine->SetUpdateLayout(false);
        pEngine->SetDefaultItem( SvxAdjustItem(eEditAdjust, EE_PARA_JUST) );
        pEngine->SetUpdateLayout(bPrevUpdateLayout);
        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;

    tools::Long nURLWidth = static_cast<tools::Long>(mpEngine->CalcTextWidth());
    tools::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() < mnScrX )
    {
        rAreaParam.maClipRect.SetLeft( mnScrX );
        rAreaParam.mbLeftClip = true;
    }
    if( rAreaParam.maClipRect.Right() > mnScrX + mnScrW )
    {
        rAreaParam.maClipRect.SetRight( mnScrX + mnScrW );          //! minus one?
        rAreaParam.mbRightClip = true;
    }

    bool bVClip = false;

    if( rAreaParam.maClipRect.Top() < mnScrY )
    {
        rAreaParam.maClipRect.SetTop( mnScrY );
        bVClip = true;
    }
    if( rAreaParam.maClipRect.Bottom() > mnScrY + mnScrH )
    {
        rAreaParam.maClipRect.SetBottom( mnScrY + mnScrH );     //! 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))
            return;

        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
tools::Long ScOutputData::SetEngineTextAndGetWidth( DrawEditParam& rParam, const OUString& rSetString,
                                             tools::Long& rNeededPixel, tools::Long nAddWidthPixels )
{
    rParam.mpEngine->SetTextCurrentDefaults( rSetString );
    tools::Long nEngineWidth = static_cast<tools::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);
    Degree100 nAttrRotate = lcl_GetValue<ScRotateValueItem, Degree100>(*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_deg100;

        // #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
    tools::Long nTopM, nLeftM, nBottomM, nRightM;
    rParam.calcMargins(nTopM, nLeftM, nBottomM, nRightM, mnPPTX, mnPPTY);

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

    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, mnTab, 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();
    // Don't format unnecessary parts if the text will be drawn from top (Standard will
    // act that way if text doesn't fit, see below).
    rParam.mpEngine->EnableSkipOutsideFormat(rParam.meVerJust==SvxCellVerJustify::Top
        || rParam.meVerJust==SvxCellVerJustify::Standard);

    //  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->SetUpdateLayout( true );     // after SetText, before CalcTextWidth/GetTextHeight

    //  Get final output area using the calculated width

    tools::Long nEngineWidth, nEngineHeight;
    rParam.getEngineSize(rParam.mpEngine, nEngineWidth, nEngineHeight);

    tools::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_deg100, 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).

            tools::Long nFormatted = nNeededPixel - nLeftM - nRightM;      // without margin
            tools::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();

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

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

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

        if (rParam.mnX >= mnX1 && rParam.mbCellIsValue
            && (aAreaParam.mbLeftClip || aAreaParam.mbRightClip))
        {
            SetEngineTextAndGetWidth( rParam, u"###"_ustr, nNeededPixel, ( nLeftM + nRightM ) );
            tools::Long nLayoutSign = mbLayoutRTL ? -1 : 1;
            ScCellInfo* pClipMarkCell = &rParam.mpThisRowInfo->cellInfo(rParam.mnX);
            SetClipMarks( aAreaParam, pClipMarkCell, eOutHorJust, nLayoutSign );
        }

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

    tools::Long nStartX = aAreaParam.maAlignRect.Left();
    tools::Long nStartY = aAreaParam.maAlignRect.Top();
    tools::Long nCellWidth = aAreaParam.maAlignRect.GetWidth();
    tools::Long nOutWidth = nCellWidth - 1 - nLeftM - nRightM;
    tools::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() < mnScrX || aAreaParam.maClipRect.Left() >= mnScrX + mnScrW);
    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 ( meType != OUTTYPE_PRINTER ||
            ( mpDoc->GetRowFlags( rParam.mnCellY, mnTab ) & 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 && mbMarkClipped &&
             ( rParam.mpEngine->GetParagraphCount() > 1 || rParam.mpEngine->GetLineCount(0) > 1 ) )
        {
            ScCellInfo* pClipMarkCell = nullptr;
            if ( bMerged )
            {
                //  anywhere in the merged area...
                SCCOL nClipX = ( rParam.mnX < mnX1 ) ? mnX1 : rParam.mnX;
                pClipMarkCell = &mpRowInfo[(rParam.mnArrY != 0) ? rParam.mnArrY : 1].cellInfo(nClipX);
            }
            else
                pClipMarkCell = &rParam.mpThisRowInfo->cellInfo(rParam.mnX);

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

            tools::Long nMarkPixel = static_cast<tools::Long>( SC_CLIPMARK_SIZE * mnPPTX );
            if ( aAreaParam.maClipRect.Right() - nMarkPixel > aAreaParam.maClipRect.Left() )
                aAreaParam.maClipRect.AdjustRight( -nMarkPixel );

            // Standard is normally treated as Bottom, but if text height is clipped, then
            // Top looks better and also allows using EditEngine::EnableSkipOutsideFormat().
            if (rParam.meVerJust==SvxCellVerJustify::Standard)
                rParam.meVerJust=SvxCellVerJustify::Top;
        }
    }

    Point aURLStart;

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

        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->DrawText_ToRectangle(*mpDev, aClip.getRect(), aDocStart, false);
        }
        else
        {
            rParam.mpEngine->DrawText_ToPosition(*mpDev, aLogicStart);
        }
    }

    rParam.adjustForHyperlinkInPDF(aURLStart, mpDev);
}

void ScOutputData::SetClipMarks( OutputAreaParam &aAreaParam, ScCellInfo* pClipMarkCell,
                                 SvxCellHorJustify eOutHorJust,
                                 tools::Long nLayoutSign )
{
    tools::Long nMarkPixel = SC_CLIPMARK_SIZE * mnPPTX;

    if ( eOutHorJust == SvxCellHorJustify::Left )
    {
        pClipMarkCell->nClipMark |= ScClipMark::Right;
        mbAnyClipped = true;
        aAreaParam.maClipRect.AdjustRight( -( nMarkPixel * nLayoutSign ) );
    }
    else if ( eOutHorJust == SvxCellHorJustify::Right )
    {
        pClipMarkCell->nClipMark |= ScClipMark::Left;
        mbAnyClipped = true;
        aAreaParam.maClipRect.AdjustLeft( nMarkPixel * nLayoutSign );
    }
    else
    {
        pClipMarkCell->nClipMark |= ScClipMark::Right;
        pClipMarkCell->nClipMark |= ScClipMark::Left;
        mbAnyClipped = true;
        aAreaParam.maClipRect.AdjustRight( -( nMarkPixel * nLayoutSign ) );
        aAreaParam.maClipRect.AdjustLeft( nMarkPixel * nLayoutSign );
    }

}

void ScOutputData::ShowClipMarks( DrawEditParam& rParam, tools::Long nEngineWidth, const Size& aCellSize,
                                  bool bMerged, OutputAreaParam& aAreaParam, bool bTop)
{
    //  Show clip marks if width 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 (nEngineWidth - aCellSize.Width() <= 100 || !rParam.mbBreak || !mbMarkClipped
        || (rParam.mpEngine->GetParagraphCount() <= 1 && rParam.mpEngine->GetLineCount(0) <= 1))
        return;

    ScCellInfo* pClipMarkCell = nullptr;
    if (bMerged)
    {
        //  anywhere in the merged area...
        SCCOL nClipX = (rParam.mnX < mnX1) ? mnX1 : rParam.mnX;
        pClipMarkCell = &mpRowInfo[(rParam.mnArrY != 0) ? rParam.mnArrY : 1].cellInfo(nClipX);
    }
    else
        pClipMarkCell = &rParam.mpThisRowInfo->cellInfo(rParam.mnX);

    mbAnyClipped = true;
    mbVertical = true;
    const tools::Long nMarkPixel = static_cast<tools::Long>(SC_CLIPMARK_SIZE * mnPPTX);
    if (bTop)
    {
        pClipMarkCell->nClipMark |= ScClipMark::Top;
        if (aAreaParam.maClipRect.Top() - nMarkPixel < aAreaParam.maClipRect.Bottom())
            aAreaParam.maClipRect.AdjustTop(+nMarkPixel);
    }
    else
    {
        pClipMarkCell->nClipMark |= ScClipMark::Bottom;
        if (aAreaParam.maClipRect.Top() - nMarkPixel < aAreaParam.maClipRect.Bottom())
            aAreaParam.maClipRect.AdjustBottom(-nMarkPixel);
    }
}

ClearableClipRegionPtr ScOutputData::Clip( DrawEditParam& rParam, const Size& aCellSize,
                                                        OutputAreaParam& aAreaParam, tools::Long nEngineWidth,
                                                        bool bWrapFields, bool bTop)
{
    // 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 ( nEngineWidth >= aCellSize.Width() + aRefOne.Width() )
    {
        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 ( meType != OUTTYPE_PRINTER ||
            ( mpDoc->GetRowFlags( rParam.mnCellY, mnTab ) & CRFlags::ManualSize ) ||
            ( rParam.mpCondSet && SfxItemState::SET ==
                rParam.mpCondSet->GetItemState(ATTR_FONT_HEIGHT) ) )
            bClip = true;
        else
            bSimClip = true;

        ShowClipMarks( rParam, nEngineWidth, aCellSize, bMerged, aAreaParam, bTop);
    }

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

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
    tools::Long nTopM, nLeftM, nBottomM, nRightM;
    rParam.calcMargins(nTopM, nLeftM, nBottomM, nRightM, mnPPTX, mnPPTY);

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

    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->SetUpdateLayout( true );     // after SetText, before CalcTextWidth/GetTextHeight

    //  Get final output area using the calculated width

    tools::Long nEngineWidth, nEngineHeight;
    rParam.getEngineSize(rParam.mpEngine, nEngineWidth, nEngineHeight);

    tools::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_deg100, 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 tools::Long nFormatted = nNeededPixel - nLeftM - nRightM;      // without margin
            const tools::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();

                tools::Long nRepeatSize = 0;
                SetEngineTextAndGetWidth( rParam, aCellStr, nRepeatSize, 0 );
                if ( pFmtDevice != mpRefDevice )
                    ++nRepeatSize;
                if ( nRepeatSize > 0 )
                {
                    const tools::Long nRepeatCount = nAvailable / nRepeatSize;
                    if ( nRepeatCount > 1 )
                    {
                        OUStringBuffer aRepeated(aCellStr);
                        for ( tools::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, u"###"_ustr, nNeededPixel, ( nLeftM + nRightM ) );

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

    tools::Long nStartX = aAreaParam.maAlignRect.Left();
    const tools::Long nStartY = aAreaParam.maAlignRect.Top();
    const tools::Long nCellWidth = aAreaParam.maAlignRect.GetWidth();
    const tools::Long nOutWidth = nCellWidth - 1 - nLeftM - nRightM;
    const tools::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() < mnScrX || aAreaParam.maClipRect.Left() >= mnScrX + mnScrW);
    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, nEngineWidth, bWrapFields, true );

        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);

            tools::Long nGap = 0;
            tools::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->DrawText_ToPosition(*mpDev, aLogicStart, 900_deg10);
    }

    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
    tools::Long nTopM, nLeftM, nBottomM, nRightM;
    rParam.calcMargins(nTopM, nLeftM, nBottomM, nRightM, mnPPTX, mnPPTY);

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

    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->SetUpdateLayout( true );     // after SetText, before CalcTextWidth/GetTextHeight

    //  Get final output area using the calculated width

    tools::Long nEngineWidth, nEngineHeight;
    rParam.getEngineSize(rParam.mpEngine, nEngineWidth, nEngineHeight);

    tools::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_deg100, 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 tools::Long nFormatted = nNeededPixel - nLeftM - nRightM;      // without margin
            const tools::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();

                tools::Long nRepeatSize = 0;
                SetEngineTextAndGetWidth( rParam, aCellStr, nRepeatSize, 0 );

                if ( pFmtDevice != mpRefDevice )
                    ++nRepeatSize;
                if ( nRepeatSize > 0 )
                {
                    const tools::Long nRepeatCount = nAvailable / nRepeatSize;
                    if ( nRepeatCount > 1 )
                    {
                        OUStringBuffer aRepeated(aCellStr);
                        for ( tools::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, u"###"_ustr, nNeededPixel, ( nLeftM + nRightM ) );

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

    tools::Long nStartX = aAreaParam.maAlignRect.Left();
    const tools::Long nStartY = aAreaParam.maAlignRect.Top();
    const tools::Long nCellWidth = aAreaParam.maAlignRect.GetWidth();
    const tools::Long nOutWidth = nCellWidth - 1 - nLeftM - nRightM;
    const tools::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() < mnScrX || aAreaParam.maClipRect.Left() >= mnScrX + mnScrW);
    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, nEngineWidth, bWrapFields, false );

        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);

                tools::Long nGap = 0;
                tools::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->DrawText_ToPosition(*mpDev, aLogicStart, 2700_deg10);
    }

    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
    tools::Long nTopM, nLeftM, nBottomM, nRightM;
    rParam.calcMargins(nTopM, nLeftM, nBottomM, nRightM, mnPPTX, mnPPTY);

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

    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, mnTab, 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->SetUpdateLayout( true );     // after SetText, before CalcTextWidth/GetTextHeight

    //  Get final output area using the calculated width

    tools::Long nEngineWidth, nEngineHeight;
    rParam.getEngineSize(rParam.mpEngine, nEngineWidth, nEngineHeight);

    tools::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_deg100, rParam.mbPixelToLogic,
            nEngineWidth, nEngineHeight, nNeededPixel,
            aAreaParam.mbLeftClip, aAreaParam.mbRightClip );

        if ( rParam.mbCellIsValue && ( aAreaParam.mbLeftClip || aAreaParam.mbRightClip ) )
        {
            nEngineWidth = SetEngineTextAndGetWidth( rParam, u"###"_ustr, nNeededPixel, ( nLeftM + nRightM ) );
            tools::Long nLayoutSign = mbLayoutRTL ? -1 : 1;
            ScCellInfo* pClipMarkCell = &rParam.mpThisRowInfo->cellInfo(rParam.mnX);
            SetClipMarks( aAreaParam, pClipMarkCell, eOutHorJust, nLayoutSign );
        }

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

    tools::Long nStartX = aAreaParam.maAlignRect.Left();
    tools::Long nStartY = aAreaParam.maAlignRect.Top();
    tools::Long nCellWidth = aAreaParam.maAlignRect.GetWidth();
    tools::Long nOutWidth = nCellWidth - 1 - nLeftM - nRightM;
    tools::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() < mnScrX || aAreaParam.maClipRect.Left() >= mnScrX + mnScrW);
    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 ( meType != OUTTYPE_PRINTER ||
            ( mpDoc->GetRowFlags( rParam.mnCellY, mnTab ) & 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 && mbMarkClipped &&
             ( rParam.mpEngine->GetParagraphCount() > 1 || rParam.mpEngine->GetLineCount(0) > 1 ) )
        {
            ScCellInfo* pClipMarkCell = nullptr;
            if ( bMerged )
            {
                //  anywhere in the merged area...
                SCCOL nClipX = ( rParam.mnX < mnX1 ) ? mnX1 : rParam.mnX;
                pClipMarkCell = &mpRowInfo[(rParam.mnArrY != 0) ? rParam.mnArrY : 1].cellInfo(nClipX);
            }
            else
                pClipMarkCell = &rParam.mpThisRowInfo->cellInfo(rParam.mnX);

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

            tools::Long nMarkPixel = static_cast<tools::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, mbMetaFile);

        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->DrawText_ToRectangle(*mpDev, aClip.getRect(), aDocStart, false);
        }
        else
        {
            rParam.mpEngine->DrawText_ToPosition(*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);
    Degree100 nAttrRotate = lcl_GetValue<ScRotateValueItem, Degree100>(*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
    tools::Long nTopM, nLeftM, nBottomM, nRightM;
    rParam.calcMargins(nTopM, nLeftM, nBottomM, nRightM, mnPPTX, mnPPTY);

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

    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, mnTab, 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->SetUpdateLayout( true );     // after SetText, before CalcTextWidth/GetTextHeight

    //  Get final output area using the calculated width

    tools::Long nEngineWidth, nEngineHeight;
    rParam.getEngineSize(rParam.mpEngine, nEngineWidth, nEngineHeight);

    tools::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_deg100, rParam.mbPixelToLogic,
            nEngineWidth, nEngineHeight, nNeededPixel,
            aAreaParam.mbLeftClip, aAreaParam.mbRightClip );
    }
    if ( rParam.mbCellIsValue && ( aAreaParam.mbLeftClip || aAreaParam.mbRightClip ) )
    {
        nEngineWidth = SetEngineTextAndGetWidth( rParam, u"###"_ustr, nNeededPixel, ( nLeftM + nRightM ) );
        tools::Long nLayoutSign = mbLayoutRTL ? -1 : 1;
        ScCellInfo* pClipMarkCell = &rParam.mpThisRowInfo->cellInfo(rParam.mnX);
        SetClipMarks( aAreaParam, pClipMarkCell, eOutHorJust, nLayoutSign );
    }

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

    tools::Long nStartX = aAreaParam.maAlignRect.Left();
    tools::Long nStartY = aAreaParam.maAlignRect.Top();
    tools::Long nCellWidth = aAreaParam.maAlignRect.GetWidth();
    tools::Long nOutWidth = nCellWidth - 1 - nLeftM - nRightM;
    tools::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() < mnScrX || aAreaParam.maClipRect.Left() >= mnScrX + mnScrW);
    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 ( meType != OUTTYPE_PRINTER ||
            ( mpDoc->GetRowFlags( rParam.mnCellY, mnTab ) & 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 && mbMarkClipped &&
             ( rParam.mpEngine->GetParagraphCount() > 1 || rParam.mpEngine->GetLineCount(0) > 1 ) )
        {
            ScCellInfo* pClipMarkCell = nullptr;
            if ( bMerged )
            {
                //  anywhere in the merged area...
                SCCOL nClipX = ( rParam.mnX < mnX1 ) ? mnX1 : rParam.mnX;
                pClipMarkCell = &mpRowInfo[(rParam.mnArrY != 0) ? rParam.mnArrY : 1].cellInfo(nClipX);
            }
            else
                pClipMarkCell = &rParam.mpThisRowInfo->cellInfo(rParam.mnX);

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

            tools::Long nMarkPixel = static_cast<tools::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, mbMetaFile);

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

        tools::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->DrawText_ToPosition(*mpDev, aLogicStart);
    }

    rParam.adjustForHyperlinkInPDF(aURLStart, mpDev);
}

void ScOutputData::DrawEdit(bool bPixelToLogic)
{
    vcl::PDFExtOutDevData* pPDF = dynamic_cast<vcl::PDFExtOutDevData*>(mpDev->GetExtOutDevData());
    bool bTaggedPDF = pPDF && pPDF->GetIsExportTaggedPDF();

    InitOutputEditEngine();

    bool bHyphenatorSet = false;
    const ScPatternAttr* pOldPattern = nullptr;
    const SfxItemSet*    pOldCondSet = nullptr;
    const SfxItemSet*    pOldPreviewFontSet = nullptr;
    ScRefCellValue aCell;

    tools::Long nInitPosX = mnScrX;
    if ( mbLayoutRTL )
    {
        nInitPosX += mnMirrorW - 1;
    }
    tools::Long nLayoutSign = mbLayoutRTL ? -1 : 1;

    SCCOL nLastContentCol = mpDoc->MaxCol();
    if ( mnX2 < mpDoc->MaxCol() )
    {
        SCROW nEndRow;
        mpDoc->GetCellArea(mnTab, nLastContentCol, nEndRow);
    }

    tools::Long nRowPosY = mnScrY;
    for (SCSIZE nArrY=0; nArrY+1<mnArrCount; nArrY++)            // 0 of the rest of the merged
    {
        RowInfo* pThisRowInfo = &mpRowInfo[nArrY];

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

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

                if (pThisRowInfo->basicCellInfo(nX).bEditEngine)
                {
                    SCROW nY = pThisRowInfo->nRowNo;

                    bool bReopenTag = false;
                    if (bTaggedPDF)
                        bReopenTag = ReopenPDFStructureElement(vcl::pdf::StructElement::TableData, nY, nX);

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

                    // if merged cell contains hidden row or column or both
                    const ScMergeFlagAttr& rMergeFlag = mpDoc->GetAttr(nX, nY, mnTab, ATTR_MERGE_FLAG);
                    bool bOverlapped = (rMergeFlag.IsHorOverlapped() || rMergeFlag.IsVerOverlapped());

                    tools::Long nPosY = nRowPosY;
                    if (bOverlapped)
                    {
                        nY = mpRowInfo[nArrY].nRowNo;
                        SCCOL nOverX;                   // start of the merged cells
                        SCROW nOverY;
                        if (GetMergeOrigin( nX,nY, nArrY, nOverX,nOverY, true ))
                        {
                            nCellX = nOverX;
                            nCellY = nOverY;
                            bDoCell = true;
                        }
                    }
                    else if ( nX == mnX2 && pThisRowInfo->cellInfo(nX).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,mnTab, nX,nY,mnTab, HasAttrFlags::Merged | HasAttrFlags::Overlapped ) )
                        {
                            nCellX = nTempX;
                            bDoCell = true;
                        }
                    }
                    else
                    {
                        bDoCell = true;
                    }

                    if ( bDoCell && mbEditMode && nCellX == mnEditCol && nCellY == mnEditRow )
                        bDoCell = false;

                    const ScPatternAttr* pPattern = nullptr;
                    const SfxItemSet* pCondSet = nullptr;
                    if (bDoCell)
                    {
                        if ( nCellY == nY && nCellX >= mnX1 && nCellX <= mnX2 &&
                             !mpDoc->ColHidden(nCellX, mnTab) )
                        {
                            ScCellInfo& rCellInfo = pThisRowInfo->cellInfo(nCellX);
                            pPattern = rCellInfo.pPatternAttr;
                            pCondSet = rCellInfo.pConditionSet;
                            aCell = rCellInfo.maCell;
                        }
                        else        // get from document
                        {
                            pPattern = mpDoc->GetPattern( nCellX, nCellY, mnTab );
                            pCondSet = mpDoc->GetCondResult( nCellX, nCellY, mnTab );
                            GetVisibleCell( nCellX, nCellY, mnTab, aCell );
                        }
                        if (aCell.isEmpty())
                            bDoCell = false;
                    }
                    if (bDoCell)
                    {
                        if ( mpDoc->GetPreviewCellStyle() )
                        {
                            if ( ScStyleSheet* pPreviewStyle = mpDoc->GetPreviewCellStyle( nCellX, nCellY, mnTab ) )
                            {
                                pPreviewPattr.reset( new ScPatternAttr(*pPattern) );
                                pPreviewPattr->SetStyleSheet(pPreviewStyle);
                                pPattern = pPreviewPattr.get();
                            }
                        }
                        SfxItemSet* pPreviewFontSet = mpDoc->GetPreviewFont( nCellX, nCellY, mnTab );
                        lcl_ClearEdit( *mxOutputEditEngine );      // also calls SetUpdateMode(sal_False)

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

                        DrawEditParam aParam(pPattern, pCondSet, lcl_SafeIsValue(aCell));
                        const bool bNumberFormatIsText = lcl_isNumberFormatText( mpDoc, nCellX, nCellY, mnTab );
                        aParam.meHorJustContext = getAlignmentFromContext( aParam.meHorJustAttr,
                                aParam.mbCellIsValue, aStr, *pPattern, pCondSet, mpDoc, mnTab, bNumberFormatIsText);
                        aParam.meHorJustResult = (aParam.meHorJustAttr == SvxCellHorJustify::Block) ?
                                SvxCellHorJustify::Block : aParam.meHorJustContext;
                        aParam.mbPixelToLogic = bPixelToLogic;
                        aParam.mbHyphenatorSet = bHyphenatorSet;
                        aParam.mpEngine = mxOutputEditEngine.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.maMisspellRanges = 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;
                    }
                    if (bReopenTag)
                        pPDF->EndStructureElement();
                }
                nPosX += mpRowInfo[0].basicCellInfo(nX).nWidth * nLayoutSign;
            }
        }
        nRowPosY += mpRowInfo[nArrY].nHeight;
    }

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

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

    Color nConfBackColor = GetConfBackgroundColor();
    bool bCellContrast = mbUseStyleColor &&
            Application::GetSettings().GetStyleSettings().GetHighContrastMode();

    bool bHyphenatorSet = false;
    const ScPatternAttr* pPattern;
    const SfxItemSet*    pCondSet;
    const ScPatternAttr* pOldPattern = nullptr;
    const SfxItemSet*    pOldCondSet = nullptr;
    ScRefCellValue aCell;

    tools::Long nInitPosX = mnScrX;
    if ( mbLayoutRTL )
    {
        nInitPosX += mnMirrorW - 1;
    }
    tools::Long nLayoutSign = mbLayoutRTL ? -1 : 1;

    tools::Long nRowPosY = mnScrY;
    for (SCSIZE nArrY=0; nArrY+1<mnArrCount; nArrY++)            // 0 for the rest of the merged
    {
        RowInfo* pThisRowInfo = &mpRowInfo[nArrY];
        tools::Long nCellHeight = static_cast<tools::Long>(pThisRowInfo->nHeight);
        if (nArrY==1) nRowPosY = mnScrY;                         // positions before are calculated individually

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

                const ScCellInfo* pInfo = &pThisRowInfo->cellInfo(nX);
                if ( pInfo->nRotateDir != ScRotateDir::NONE )
                {
                    SCROW nY = pThisRowInfo->nRowNo;

                    bool bHidden = false;
                    if (mbEditMode)
                        if ( nX == mnEditCol && nY == mnEditRow )
                            bHidden = true;

                    if (!bHidden)
                    {
                        lcl_ClearEdit( *mxOutputEditEngine );      // also calls SetUpdateMode(sal_False)

                        tools::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, mnTab );
                            bFromDoc = true;
                        }
                        aCell = pInfo->maCell;
                        if (bFromDoc)
                            pCondSet = mpDoc->GetCondResult( nX, nY, mnTab );

                        if (aCell.isEmpty() && nX>mnX2)
                            GetVisibleCell( nX, nY, mnTab, aCell );

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

                        tools::Long nCellWidth = static_cast<tools::Long>(mpRowInfo[0].basicCellInfo(nX).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;

                        tools::Long nStartX = nPosX;
                        tools::Long nStartY = nPosY;
                        if (nX<mnX1)
                        {
                            if ((bBreak || eOrient!=SvxCellOrientation::Standard) && !bMerged)
                                bHidden = true;
                            else
                            {
                                nStartX = nInitPosX;
                                SCCOL nCol = mnX1;
                                while (nCol > nX)
                                {
                                    --nCol;
                                    nStartX -= nLayoutSign * static_cast<tools::Long>(mpRowInfo[0].basicCellInfo(nCol).nWidth);
                                }
                            }
                        }
                        tools::Long nCellStartX = nStartX;

                        // omit substitute representation of small text

                        if (!bHidden)
                        {
                            tools::Long nOutWidth = nCellWidth - 1;
                            tools::Long nOutHeight = nCellHeight;

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

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

                            // syntax mode is ignored here...

                            // StringDiffer doesn't look at hyphenate, language items
                            if ( !ScPatternAttr::areSame(pPattern, pOldPattern) || pCondSet != pOldCondSet )
                            {
                                SfxItemSet aSet( mxOutputEditEngine->GetEmptyItemSet() );
                                pPattern->FillEditItemSet( &aSet, pCondSet );

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

                                bool bParaHyphenate = aSet.Get(EE_PARA_HYPHENATE).GetValue();
                                mxOutputEditEngine->SetDefaults( std::move(aSet) );
                                pOldPattern = pPattern;
                                pOldCondSet = pCondSet;

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

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

                                Color aBackCol =
                                    pPattern->GetItem( ATTR_BACKGROUND, pCondSet ).GetColor();
                                if ( mbUseStyleColor && ( aBackCol.IsTransparent() || bCellContrast ) )
                                    aBackCol = nConfBackColor;
                                mxOutputEditEngine->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();

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

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

                            // rotate here already, to adjust paper size for page breaks
                            Degree100 nAttrRotate;
                            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();

                                    // tdf#143377 To use the same limits to avoid too big Skew
                                    // with TextOrientation in Calc, use 1/2 degree here, too.
                                    // This equals '50' in the notation here (100th degree)
                                    static const sal_Int32 nMinRad(50);

                                    // bring nAttrRotate to the range [0..36000[
                                    nAttrRotate = Degree100(((nAttrRotate.get() % 36000) + 36000) % 36000);

                                    // check for to be avoided extreme values and correct
                                    if (nAttrRotate < Degree100(nMinRad))
                                    {
                                        // range [0..50]
                                        nAttrRotate = Degree100(nMinRad);
                                        eRotMode = SVX_ROTATE_MODE_STANDARD;    // no overflow
                                    }
                                    else if (nAttrRotate > Degree100(36000 - nMinRad))
                                    {
                                        // range [35950..36000[
                                        nAttrRotate = Degree100(36000 - nMinRad);
                                        eRotMode = SVX_ROTATE_MODE_STANDARD;    // no overflow
                                    }
                                    else if (nAttrRotate > Degree100(18000 - nMinRad) && (nAttrRotate < Degree100(18000 + nMinRad)))
                                    {
                                        // range 50 around 18000, [17950..18050]
                                        nAttrRotate = (nAttrRotate > Degree100(18000))
                                            ? Degree100(18000 + nMinRad)
                                            : Degree100(18000 - nMinRad);
                                        eRotMode = SVX_ROTATE_MODE_STANDARD;    // no overflow
                                    }

                                    if ( mbLayoutRTL )
                                    {
                                        // keep in range [0..36000[
                                        nAttrRotate = Degree100(36000 - nAttrRotate.get());
                                    }

                                    double nRealOrient = toRadians(nAttrRotate);   // 1/100 degree
                                    nCos = cos( nRealOrient );

                                    // tdf#143377 new strategy: instead of using zero for nSin, which
                                    // would be the *correct* value, continue with the corrected maximum
                                    // allowed value which is then *not* zero. This is similar to
                                    // the behaviour before where (just due to numerical unprecisions)
                                    // nSin was also not zero (pure coincidence), but very close to it.
                                    // I checked and tried to make safe all places below that use
                                    // nSin and divide by it, but there is too much going on and that
                                    // would not be safe, so rely on the same values as before, but
                                    // now numerically limited to not get the Skew go havoc
                                    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<tools::Long>(nOutHeight / fabs(nSin)) );
                                }
                                else if (eOrient == SvxCellOrientation::Standard)
                                    aPaperSize.setWidth( nOutWidth );
                                else
                                    aPaperSize.setWidth( nOutHeight - 1 );
                            }
                            if (bPixelToLogic)
                                mxOutputEditEngine->SetPaperSize(mpRefDevice->PixelToLogic(aPaperSize));
                            else
                                mxOutputEditEngine->SetPaperSize(aPaperSize);  // scale is always 1

                            // read data from cell

                            if (aCell.getType() == CELLTYPE_EDIT)
                            {
                                if (aCell.getEditText())
                                    mxOutputEditEngine->SetTextCurrentDefaults(*aCell.getEditText());
                                else
                                {
                                    OSL_FAIL("pData == 0");
                                }
                            }
                            else
                            {
                                sal_uInt32 nFormat = pPattern->GetNumberFormat(
                                                            mpDoc->GetFormatTable(), pCondSet );
                                const Color* pColor;
                                OUString aString = ScCellFormat::GetString( aCell,
                                                         nFormat, &pColor,
                                                         nullptr,
                                                         *mpDoc,
                                                         mbShowNullValues,
                                                         mbShowFormulas);

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

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

                            mxOutputEditEngine->SetUpdateLayout( true );     // after SetText, before CalcTextWidth/GetTextHeight

                            tools::Long nEngineWidth  = static_cast<tools::Long>(mxOutputEditEngine->CalcTextWidth());
                            tools::Long nEngineHeight = mxOutputEditEngine->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
                                    tools::Long nEnginePixel = mpRefDevice->LogicToPixel(
                                                            Size(0,nEngineHeight)).Height();
                                    tools::Long nEffHeight = nOutHeight - static_cast<tools::Long>(nEnginePixel * nAbsCos) + 2;
                                    tools::Long nNewWidth = static_cast<tools::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<tools::Long>(nEffHeight / nAbsSin) + 2;
                                            nSteps = 0;
                                        }
                                        else
                                            --nSteps;

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

                                        nEngineWidth  = static_cast<tools::Long>(mxOutputEditEngine->CalcTextWidth());
                                        nEngineHeight = mxOutputEditEngine->GetTextHeight();
                                    }
                                }
                            }

                            tools::Long nRealWidth  = nEngineWidth;
                            tools::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<tools::Long>( nRealWidth * nAbsCos +
                                                            nRealHeight * nAbsSin );
                                else
                                    nEngineWidth = static_cast<tools::Long>( nRealHeight / nAbsSin );
                                //! limit !!!

                                nEngineHeight = static_cast<tools::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( mnScrX+mnScrW-nStartX, mnScrY+mnScrH-nStartY );

                                // go on writing

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

                                tools::Long nGridWidth = nEngineWidth;
                                bool bNegative = false;
                                if ( eRotMode != SVX_ROTATE_MODE_STANDARD )
                                {
                                    nGridWidth = aCellSize.Width() +
                                            std::abs(static_cast<tools::Long>( aCellSize.Height() * nCos / nSin ));
                                    bNegative = ( pInfo->nRotateDir == ScRotateDir::Left );
                                    if ( mbLayoutRTL )
                                        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;
                                tools::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 )
                                {
                                    tools::Long nPixelWidth = bPixelToLogic ?
                                        mpRefDevice->LogicToPixel(Size(nEngineWidth,0)).Width() : nEngineWidth;
                                    tools::Long nNeededPixel = nPixelWidth + nLeftM + nRightM;

                                    aAreaParam.mbLeftClip = aAreaParam.mbRightClip = true;

                                    // always do height
                                    ShrinkEditEngine( *mxOutputEditEngine, 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( *mxOutputEditEngine, 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<tools::Long>(mxOutputEditEngine->CalcTextWidth());
                                    nRealHeight = mxOutputEditEngine->GetTextHeight();

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

                                tools::Long nClipStartX = nStartX;
                                if (nX<mnX1)
                                {
                                    //! clipping is not needed when on the left side of the window

                                    if (nStartX<mnScrX)
                                    {
                                        tools::Long nDif = mnScrX - nStartX;
                                        nClipStartX = mnScrX;
                                        aClipSize.AdjustWidth( -nDif );
                                    }
                                }

                                tools::Long nClipStartY = nStartY;
                                if (nArrY==0 && nClipStartY < nRowPosY )
                                {
                                    tools::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 = mnScrX;
                                    aClipSize.setWidth( mnScrW );
                                }

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

                                if (mbMetaFile)
                                {
                                    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 )
                                {
                                    tools::Long nAvailWidth = aCellSize.Width();
                                    if (meType==OUTTYPE_WINDOW &&
                                            eOrient!=SvxCellOrientation::Stacked &&
                                            pInfo->bAutoFilter)
                                    {
                                        // filter drop-down width depends on row height
                                        double fZoom = mpRefDevice ? static_cast<double>(mpRefDevice->GetMapMode().GetScaleY()) : 1.0;
                                        fZoom = fZoom > 1.0 ? fZoom : 1.0;
                                        if (bPixelToLogic)
                                            nAvailWidth -= mpRefDevice->PixelToLogic(Size(0,fZoom * DROPDOWN_BITMAP_SIZE)).Height();
                                        else
                                            nAvailWidth -= fZoom * DROPDOWN_BITMAP_SIZE;
                                        tools::Long nComp = nEngineWidth;
                                        if (nAvailWidth<nComp) nAvailWidth=nComp;
                                    }

                                    // horizontal orientation

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

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

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

                                            mxOutputEditEngine->SetUpdateLayout( 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 ( mbLayoutRTL )
                                {
                                    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" );

                                Degree10 nOriVal = 0_deg10;
                                if ( nAttrRotate )
                                {
                                    // attribute is 1/100, Font 1/10 degrees
                                    nOriVal = to<Degree10>(nAttrRotate);

                                    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;

                                        tools::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<tools::Long>(nAddX) );
                                    aLogicStart.AdjustY(static_cast<tools::Long>(nAddY) );
                                }

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

                                mxOutputEditEngine->DrawText_ToPosition(*mpDev, aLogicStart, nOriVal);

                                if (mbMetaFile)
                                    mpDev->Pop();
                                else
                                    mpDev->SetClipRegion();
                            }
                        }
                    }
                }
                nPosX += mpRowInfo[0].basicCellInfo(nX).nWidth * nLayoutSign;
            }
        }
        nRowPosY += mpRowInfo[nArrY].nHeight;
    }
}

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