/* -*- 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 )
{
    ScModule* pScMod = SC_MOD();
    bCellContrast = pOutput->mbUseStyleColor &&
            Application::GetSettings().GetStyleSettings().GetHighContrastMode();
 
    const svtools::ColorConfig& rColorConfig = pScMod->GetColorConfig();
    aBackConfigColor = rColorConfig.GetColorValue(svtools::DOCCOLOR).nColor;
    aTextConfigColor = rColorConfig.GetColorValue(svtools::FONTCOLOR).nColor;
}
 
void ScDrawStringsVars::SetShrinkScale( 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->aZoomY;
    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 nTab )
{
    sal_uInt32 nCurrentNumberFormat = pDoc->GetNumberFormat( nCellX, nCellY, nTab );
    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->aZoomY, pCondSet, nScript, &aBackConfigColor, &aTextConfigColor );
 
    aFont.SetAlignment(ALIGN_BASELINE);
 
    // orientation
 
    eAttrOrient = pPattern->GetCellOrientation( pCondSet );
 
    //  alignment
 
    eAttrHorJust = pPattern->GetItem( ATTR_HOR_JUSTIFY, pCondSet ).GetValue();
 
    eAttrVerJust = pPattern->GetItem( ATTR_VER_JUSTIFY, pCondSet ).GetValue();
    if ( eAttrVerJust == SvxCellVerJustify::Standard )
        eAttrVerJust = SvxCellVerJustify::Bottom;
 
    // justification method
 
    eAttrHorJustMethod = lcl_GetValue<SvxJustifyMethodItem, SvxCellJustifyMethod>(*pPattern, ATTR_HOR_JUSTIFY_METHOD, pCondSet);
 
    //  line break
 
    bLineBreak = pPattern->GetItem( ATTR_LINEBREAK, pCondSet ).GetValue();
 
    //  handle "repeat" alignment
 
    bRepeat = ( eAttrHorJust == SvxCellHorJustify::Repeat );
    if ( bRepeat )
    {
        // "repeat" disables rotation (before constructing the font)
        eAttrOrient = SvxCellOrientation::Standard;
 
        // #i31843# "repeat" with "line breaks" is treated as default alignment (but rotation is still disabled)
        if ( bLineBreak )
            eAttrHorJust = SvxCellHorJustify::Standard;
    }
 
    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>(aZoomY) / static_cast<double>(aZoomX);
    }
    else
    {
        // when formatting for printer, device map mode has already been taken care of
        return static_cast<double>(aZoomY) / static_cast<double>(aZoomX);
    }
}
 
//  output strings
 
static void lcl_DoHyperlinkResult( const OutputDevice* pDev, const tools::Rectangle& rRect, ScRefCellValue& rCell )
{
    vcl::PDFExtOutDevData* pPDFData = dynamic_cast< vcl::PDFExtOutDevData* >( pDev->GetExtOutDevData() );
 
    OUString 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, aCellText);
        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 == nVisX1 );
    bool bIsTop  = ( nY == nVisY1 ) || bVisRowChanged;
 
    bool bHOver;
    bool bVOver;
    bool bHidden;
 
    if (!mpDoc->ColHidden(nX, nTab) && nX >= nX1 && nX <= nX2
            && !mpDoc->RowHidden(nY, nTab) && nY >= nY1 && nY <= nY2)
    {
        ScCellInfo* pInfo = &pRowInfo[nArrY].cellInfo(nX);
        bHOver = pInfo->bHOverlapped;
        bVOver = pInfo->bVOverlapped;
    }
    else
    {
        ScMF nOverlap2 = mpDoc->GetAttr(nX, nY, nTab, 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, nTab);
        if ( !bDoMerge && !bHidden )
            return false;
 
        if (rOverX >= nX1 && !bHidden)
        {
            bHOver = pRowInfo[nArrY].cellInfo(rOverX).bHOverlapped;
            bVOver = pRowInfo[nArrY].cellInfo(rOverX).bVOverlapped;
        }
        else
        {
            ScMF nOverlap = mpDoc->GetAttr(rOverX, rOverY, nTab, ATTR_MERGE_FLAG)->GetValue();
            bHOver = bool(nOverlap & ScMF::Hor);
            bVOver = bool(nOverlap & ScMF::Ver);
        }
    }
 
    while (bVOver)
    {
        --rOverY;
        bHidden = mpDoc->RowHidden(rOverY, nTab);
        if ( !bDoMerge && !bHidden )
            return false;
 
        if (nArrY>0)
            --nArrY;                        // local copy !
 
        if (rOverX >= nX1 && rOverY >= nY1 &&
            !mpDoc->ColHidden(rOverX, nTab) &&
            !mpDoc->RowHidden(rOverY, nTab) &&
            pRowInfo[nArrY].nRowNo == rOverY)
        {
            bVOver = pRowInfo[nArrY].cellInfo(rOverX).bVOverlapped;
        }
        else
        {
            ScMF nOverlap = mpDoc->GetAttr( rOverX, rOverY, nTab, 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 <= nX2 )
        bEmpty = pThisRowInfo->basicCellInfo(nX).bEmptyCellText;
    else
    {
        ScRefCellValue aCell(*mpDoc, ScAddress(nX, nY, nTab));
        bEmpty = aCell.isEmpty();
    }
 
    if ( !bEmpty && ( nX < nX1 || nX > nX2 || !pThisRowInfo ) )
    {
        //  for the range nX1..nX2 in RowInfo, cell protection attribute is already evaluated
        //  into bEmptyCellText in ScDocument::FillInfo / lcl_HidePrint (printfun)
 
        bool bIsPrint = ( eType == OUTTYPE_PRINTER );
 
        if ( bIsPrint || bTabProtected )
        {
            const ScProtectionAttr* pAttr =
                    mpDoc->GetEffItem( nX, nY, nTab, ATTR_PROTECTION );
            if ( bIsPrint && pAttr->GetHidePrint() )
                bEmpty = true;
            else if ( bTabProtected )
            {
                if ( pAttr->GetHideCell() )
                    bEmpty = true;
                else if ( mbShowFormulas && pAttr->GetHideFormula() )
                {
                    if (mpDoc->GetCellType(ScAddress(nX, nY, nTab)) == CELLTYPE_FORMULA)
                        bEmpty = true;
                }
            }
        }
    }
    return bEmpty;
}
 
void ScOutputData::GetVisibleCell( SCCOL nCol, SCROW nRow, SCTAB nTabP, ScRefCellValue& rCell )
{
    rCell.assign(*mpDoc, ScAddress(nCol, nRow, nTabP));
    if (!rCell.isEmpty() && IsEmptyCellText(nullptr, nCol, nRow))
        rCell.clear();
}
 
bool ScOutputData::IsAvailable( SCCOL nX, SCROW nY )
{
    //  apply the same logic here as in DrawStrings/DrawEdit:
    //  Stop at non-empty or merged or overlapped cell,
    //  where a note is empty as well as a cell that's hidden by protection settings
 
    ScRefCellValue aCell(*mpDoc, ScAddress(nX, nY, nTab));
    if (!aCell.isEmpty() && !IsEmptyCellText(nullptr, nX, nY))
        return false;
 
    const ScPatternAttr* pPattern = mpDoc->GetPattern( nX, nY, nTab );
    return !(pPattern->GetItem(ATTR_MERGE).IsMerged() ||
         pPattern->GetItem(ATTR_MERGE_FLAG).IsOverlapped());
}
 
// nX, nArrY:       loop variables from DrawStrings / DrawEdit
// nPosX, nPosY:    corresponding positions for nX, nArrY
// nCellX, nCellY:  position of the cell that contains the text
// nNeeded:         Text width, including margin
// rPattern:        cell format at nCellX, nCellY
// nHorJustify:     horizontal alignment (visual) to determine which cells to use for long strings
// bCellIsValue:    if set, don't extend into empty cells
// bBreak:          if set, don't extend, and don't set clip marks (but rLeftClip/rRightClip is set)
// bOverwrite:      if set, also extend into non-empty cells (for rotated text)
// rParam           output: various area parameters.
 
void ScOutputData::GetOutputArea( SCCOL nX, SCSIZE nArrY, 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 = pRowInfo[nArrY];
 
    tools::Long nLayoutSign = bLayoutRTL ? -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 <= nX2 ) ?
                pRowInfo[0].basicCellInfo(nCompCol).nWidth :
                static_cast<tools::Long>( mpDoc->GetColWidth( nCompCol, nTab ) * mnPPTX );
        nCellPosX += nColWidth * nLayoutSign;
        ++nCompCol;
    }
    while ( nCellX < nCompCol )
    {
        --nCompCol;
        tools::Long nColWidth = ( nCompCol <= nX2 ) ?
                pRowInfo[0].basicCellInfo(nCompCol).nWidth :
                static_cast<tools::Long>( mpDoc->GetColWidth( nCompCol, nTab ) * mnPPTX );
        nCellPosX -= nColWidth * nLayoutSign;
    }
 
    tools::Long nCellPosY = nPosY;         // find nCellY position, starting at nArrY/nPosY
    SCSIZE nCompArr = nArrY;
    SCROW nCompRow = pRowInfo[nCompArr].nRowNo;
    while ( nCellY > nCompRow )
    {
        if ( nCompArr + 1 < nArrCount )
        {
            nCellPosY += pRowInfo[nCompArr].nHeight;
            ++nCompArr;
            nCompRow = pRowInfo[nCompArr].nRowNo;
        }
        else
        {
            sal_uInt16 nDocHeight = mpDoc->GetRowHeight( nCompRow, nTab );
            if ( nDocHeight )
                nCellPosY += static_cast<tools::Long>( nDocHeight * mnPPTY );
            ++nCompRow;
        }
    }
    nCellPosY -= mpDoc->GetScaledRowHeight( nCellY, nCompRow-1, nTab, 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 <= nX2 ) ?
                pRowInfo[0].basicCellInfo(nCellX+i).nWidth :
                static_cast<tools::Long>( mpDoc->GetColWidth( sal::static_int_cast<SCCOL>(nCellX+i), nTab ) * 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, nTab, mnPPTY);
 
    --nMergeSizeX;      // leave out the grid horizontally, also for alignment (align between grid lines)
 
    rParam.mnColWidth = nMergeSizeX; // store the actual column width.
    rParam.mnLeftClipLength = rParam.mnRightClipLength = 0;
 
    // construct the rectangles using logical left/right values (justify is called at the end)
 
    // rAlignRect is the single cell or merged area, used for alignment.
 
    rParam.maAlignRect.SetLeft( nCellPosX );
    rParam.maAlignRect.SetRight( nCellPosX + ( nMergeSizeX - 1 ) * nLayoutSign );
    rParam.maAlignRect.SetTop( nCellPosY );
    rParam.maAlignRect.SetBottom( nCellPosY + nMergeSizeY - 1 );
 
    //  rClipRect is all cells that are used for output.
    //  For merged cells this is the same as rAlignRect, otherwise neighboring cells can also be used.
 
    rParam.maClipRect = rParam.maAlignRect;
    if ( nNeeded > nMergeSizeX )
    {
        SvxCellHorJustify eHorJust = static_cast<SvxCellHorJustify>(nHorJustify);
 
        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 ( bLayoutRTL )
            ::std::swap( nLeftMissing, nRightMissing );
 
        SCCOL nRightX = nCellX;
        SCCOL nLeftX = nCellX;
        if ( !bMerged && !bCellIsValue && !bBreak )
        {
            //  look for empty cells into which the text can be extended
 
            while ( nRightMissing > 0 && nRightX < mpDoc->MaxCol() && ( bOverwrite || IsAvailable( nRightX+1, nCellY ) ) )
            {
                ++nRightX;
                tools::Long nAdd = static_cast<tools::Long>( mpDoc->GetColWidth( nRightX, nTab ) * mnPPTX );
                nRightMissing -= nAdd;
                rParam.maClipRect.AdjustRight(nAdd * nLayoutSign );
 
                if ( rThisRowInfo.nRowNo == nCellY && nRightX >= nX1 && nRightX <= nX2 )
                    rThisRowInfo.cellInfo(nRightX-1).bHideGrid = true;
            }
 
            while ( nLeftMissing > 0 && nLeftX > 0 && ( bOverwrite || IsAvailable( nLeftX-1, nCellY ) ) )
            {
                if ( rThisRowInfo.nRowNo == nCellY && nLeftX >= nX1 && nLeftX <= nX2 )
                    rThisRowInfo.cellInfo(nLeftX-1).bHideGrid = true;
 
                --nLeftX;
                tools::Long nAdd = static_cast<tools::Long>( mpDoc->GetColWidth( nLeftX, nTab ) * mnPPTX );
                nLeftMissing -= nAdd;
                rParam.maClipRect.AdjustLeft( -(nAdd * nLayoutSign) );
            }
        }
 
        //  Set flag and reserve space for clipping mark triangle,
        //  even if rThisRowInfo isn't for nCellY (merged cells).
        if ( nRightMissing > 0 && bMarkClipped && nRightX >= nX1 && nRightX <= nX2 && !bBreak && !bCellIsValue )
        {
            rThisRowInfo.cellInfo(nRightX).nClipMark |= ScClipMark::Right;
            bAnyClipped = true;
            tools::Long nMarkPixel = static_cast<tools::Long>( SC_CLIPMARK_SIZE * mnPPTX );
            rParam.maClipRect.AdjustRight( -(nMarkPixel * nLayoutSign) );
        }
        if ( nLeftMissing > 0 && bMarkClipped && nLeftX >= nX1 && nLeftX <= nX2 && !bBreak && !bCellIsValue )
        {
            rThisRowInfo.cellInfo(nLeftX).nClipMark |= ScClipMark::Left;
            bAnyClipped = 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 ( eType==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 nTab, const bool  bNumberFormatIsText )
{
    SvxCellHorJustify eHorJustContext = eInHorJust;
    bool bUseWritingDirection = false;
    if (eInHorJust == SvxCellHorJustify::Standard)
    {
        // fdo#32530: Default alignment depends on value vs
        // string, and the direction of the 1st letter.
        if (beginsWithRTLCharacter( rText)) //If language is RTL
        {
            if (bCellIsValue)
               eHorJustContext = bNumberFormatIsText ? SvxCellHorJustify::Right : SvxCellHorJustify::Left;
            else
                eHorJustContext = SvxCellHorJustify::Right;
        }
        else if (bCellIsValue) //If language is not RTL
            eHorJustContext = bNumberFormatIsText ? SvxCellHorJustify::Left : SvxCellHorJustify::Right;
        else
            bUseWritingDirection = true;
    }
 
    if (bUseWritingDirection ||
            eInHorJust == SvxCellHorJustify::Block || eInHorJust == SvxCellHorJustify::Repeat)
    {
        SvxFrameDirection nDirection = lcl_GetValue<SvxFrameDirectionItem, SvxFrameDirection>(rPattern, ATTR_WRITINGDIR, pCondSet);
        if (nDirection == SvxFrameDirection::Horizontal_LR_TB || nDirection == SvxFrameDirection::Vertical_LR_TB)
            eHorJustContext = SvxCellHorJustify::Left;
        else if (nDirection == SvxFrameDirection::Environment)
        {
            SAL_WARN_IF( !pDoc, "sc.ui", "getAlignmentFromContext - pDoc==NULL");
            // fdo#73588: The content of the cell must also
            // begin with a RTL character to be right
            // aligned; otherwise, it should be left aligned.
            eHorJustContext = (pDoc && pDoc->IsLayoutRTL(nTab) && (beginsWithRTLCharacter( rText))) ? SvxCellHorJustify::Right : SvxCellHorJustify::Left;
        }
        else
            eHorJustContext = SvxCellHorJustify::Right;
    }
    return eHorJustContext;
}
 
void ScOutputData::DrawStrings( bool bPixelToLogic )
{
    LayoutStrings(bPixelToLogic);
}
 
void ScOutputData::LayoutStrings(bool bPixelToLogic)
{
    vcl::PDFExtOutDevData* pPDF = dynamic_cast<vcl::PDFExtOutDevData*>(mpDev->GetExtOutDevData());
    bool bTaggedPDF = pPDF && pPDF->GetIsExportTaggedPDF();
    if (bTaggedPDF)
    {
        bool bReopenTag = ReopenPDFStructureElement(vcl::PDFWriter::Table);
        if (!bReopenTag)
        {
            sal_Int32 nId = pPDF->EnsureStructureElement(nullptr);
            pPDF->InitStructureElement(nId, vcl::PDFWriter::Table, u"Table"_ustr);
            pPDF->BeginStructureElement(nId);
            pPDF->GetScPDFState()->m_TableId = nId;
        }
    }
 
    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( nX1, nY1, nTab, nX2, nY2, nTab ));
 
    ScDrawStringsVars aVars( this, bPixelToLogic );
 
    bool bProgress = false;
 
    tools::Long nInitPosX = nScrX;
    if ( bLayoutRTL )
        nInitPosX += nMirrorW - 1;              // pixels
    tools::Long nLayoutSign = bLayoutRTL ? -1 : 1;
 
    SCCOL nLastContentCol = mpDoc->MaxCol();
    if ( nX2 < mpDoc->MaxCol() )
    {
        SCROW nEndRow;
        mpDoc->GetCellArea(nTab, nLastContentCol, nEndRow);
    }
 
    SCCOL nLoopStartX = nX1;
    if ( nX1 > 0  && !bTaggedPDF )
        --nLoopStartX;          // start before nX1 for rest of long text to the left
 
    // variables for GetOutputArea
    OutputAreaParam aAreaParam;
    bool bCellIsValue = false;
    tools::Long nNeededWidth = 0;
    const ScPatternAttr* pPattern = nullptr;
    const SfxItemSet* pCondSet = nullptr;
    const ScPatternAttr* pOldPattern = nullptr;
    const SfxItemSet* pOldCondSet = nullptr;
    SvtScriptType nOldScript = SvtScriptType::NONE;
 
    // alternative pattern instances in case we need to modify the pattern
    // before processing the cell value.
    std::vector<std::unique_ptr<ScPatternAttr> > aAltPatterns;
 
    KernArray aDX;
    tools::Long nPosY = nScrY;
    for (SCSIZE nArrY=1; nArrY+1<nArrCount; nArrY++)
    {
        RowInfo* pThisRowInfo = &pRowInfo[nArrY];
        SCROW nY = pThisRowInfo->nRowNo;
        if (pThisRowInfo->bChanged)
        {
            if (bTaggedPDF)
            {
                bool bReopenTag = false;
                if (nLoopStartX != 0)
                {
                    bReopenTag
                        = ReopenPDFStructureElement(vcl::PDFWriter::TableRow, nY);
                }
                if (!bReopenTag)
                {
                    sal_Int32 nId = pPDF->EnsureStructureElement(nullptr);
                    pPDF->InitStructureElement(nId, vcl::PDFWriter::TableRow, u"TR"_ustr);
                    pPDF->BeginStructureElement(nId);
                    pPDF->GetScPDFState()->m_TableRowMap.emplace(nY, nId);
                }
            }
 
            tools::Long nPosX = nInitPosX;
            if ( nLoopStartX < nX1 )
                nPosX -= pRowInfo[0].basicCellInfo(nLoopStartX).nWidth * nLayoutSign;
            for (SCCOL nX=nLoopStartX; nX<=nX2; nX++)
            {
                if (bTaggedPDF)
                    pPDF->WrapBeginStructureElement(vcl::PDFWriter::TableData, u"TD"_ustr);
 
                bool bMergeEmpty = false;
                const ScCellInfo* pInfo = &pThisRowInfo->cellInfo(nX);
                bool bEmpty = nX < nX1 || 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 = !pRowInfo[nArrY-1].bChanged;
                    if (GetMergeOrigin( nX,nY, nArrY, nOverX,nOverY, bVisChanged ))
                    {
                        nCellX = nOverX;
                        nCellY = nOverY;
                        bDoCell = true;
                    }
                    else
                        bMergeEmpty = true;
                }
 
                //  Rest of a long text further to the left?
 
                if ( bEmpty && !bMergeEmpty && nX < nX1 && !bOverlapped )
                {
                    SCCOL nTempX=nX1;
                    while (nTempX > 0 && IsEmptyCellText( pThisRowInfo, nTempX, nY ))
                        --nTempX;
 
                    if ( nTempX < nX1 &&
                         !IsEmptyCellText( pThisRowInfo, nTempX, nY ) &&
                         !mpDoc->HasAttrib( nTempX,nY,nTab, nX1,nY,nTab, HasAttrFlags::Merged | HasAttrFlags::Overlapped ) )
                    {
                        nCellX = nTempX;
                        bDoCell = true;
                    }
                }
 
                //  Rest of a long text further to the right?
 
                if ( bEmpty && !bMergeEmpty && nX == nX2 && !bOverlapped )
                {
                    //  don't have to look further than nLastContentCol
 
                    SCCOL nTempX=nX;
                    while (nTempX < nLastContentCol && IsEmptyCellText( pThisRowInfo, nTempX, nY ))
                        ++nTempX;
 
                    if ( nTempX > nX &&
                         !IsEmptyCellText( pThisRowInfo, nTempX, nY ) &&
                         !mpDoc->HasAttrib( nTempX,nY,nTab, nX,nY,nTab, HasAttrFlags::Merged | HasAttrFlags::Overlapped ) )
                    {
                        nCellX = nTempX;
                        bDoCell = true;
                    }
                }
 
                //  normal visible cell
 
                if (!bEmpty)
                    bDoCell = true;
 
                //  don't output the cell that's being edited
 
                if ( bDoCell && bEditMode && nCellX == nEditCol && nCellY == nEditRow )
                    bDoCell = false;
 
                // skip text in cell if data bar/icon set is set and only value selected
                if ( bDoCell )
                {
                    if(pInfo->pDataBar && !pInfo->pDataBar->mbShowValue)
                        bDoCell = false;
                    if(pInfo->pIconSet && !pInfo->pIconSet->mbShowValue)
                        bDoCell = false;
                }
 
                //  output the cell text
 
                ScRefCellValue aCell;
                if (bDoCell)
                {
                    if ( nCellY == nY && nCellX == nX && nCellX >= nX1 && nCellX <= nX2 )
                        aCell = pThisRowInfo->cellInfo(nCellX).maCell;
                    else
                        GetVisibleCell( nCellX, nCellY, nTab, 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 >= nX1 && nCellX <= nX2 )
                    {
                        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, nTab );
                            pCondSet = mpDoc->GetCondResult( nCellX, nCellY, nTab );
                        }
                    }
                    else        // get from document
                    {
                        pPattern = mpDoc->GetPattern( nCellX, nCellY, nTab );
                        pCondSet = mpDoc->GetCondResult( nCellX, nCellY, nTab );
                    }
                    if ( mpDoc->GetPreviewFont() || mpDoc->GetPreviewCellStyle() )
                    {
                        aAltPatterns.push_back(std::make_unique<ScPatternAttr>(*pPattern));
                        ScPatternAttr* pAltPattern = aAltPatterns.back().get();
                        if (  ScStyleSheet* pPreviewStyle = mpDoc->GetPreviewCellStyle( nCellX, nCellY, nTab ) )
                        {
                            pAltPattern->SetStyleSheet(pPreviewStyle);
                        }
                        else if ( SfxItemSet* pFontSet = mpDoc->GetPreviewFont( nCellX, nCellY, nTab ) )
                        {
                            if ( const SvxFontItem* pItem = pFontSet->GetItemIfSet( ATTR_FONT ) )
                                pAltPattern->GetItemSet().Put( *pItem );
                            if ( const SvxFontItem* pItem = pFontSet->GetItemIfSet( ATTR_CJK_FONT ) )
                                pAltPattern->GetItemSet().Put( *pItem );
                            if ( const SvxFontItem* pItem = pFontSet->GetItemIfSet( ATTR_CTL_FONT ) )
                                pAltPattern->GetItemSet().Put( *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->GetItemSet().Put(aLineBreak);
                        pPattern = pAltPattern;
                    }
 
                    SvtScriptType nScript = mpDoc->GetCellScriptType(
                        ScAddress(nCellX, nCellY, nTab),
                        pPattern->GetNumberFormat(mpDoc->GetFormatTable(), pCondSet));
 
                    if (nScript == SvtScriptType::NONE)
                        nScript = ScGlobal::GetDefaultScriptType();
 
                    if ( !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, nTab );
                    eOutHorJust = getAlignmentFromContext( aVars.GetHorJust(), bCellIsValue, aVars.GetString(),
                            *pPattern, pCondSet, mpDoc, nTab, 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 nX2
                    SCCOL nMarkX = ( nCellX <= nX2 ) ? nCellX : nX2;
                    pThisRowInfo->basicCellInfo(nMarkX).bEditEngine = true;
                    bDoCell = false;    // don't draw here
 
                    // Mark the tagged "TD" structure element to be drawn in DrawEdit
                    if (bTaggedPDF)
                    {
                        sal_Int32 nId = pPDF->GetCurrentStructureElement();
                        pPDF->GetScPDFState()->m_TableDataMap[{nY, nX}] = nId;
                    }
                }
                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 >= nX1 && nCellX <= nX2 )
                                    pRowInfo[nArrY].cellInfo(nCellX).nClipMark |= ScClipMark::Right;
                                bAnyClipped = true;
                                aAreaParam.maClipRect.AdjustRight( -(nMarkPixel * nLayoutSign) );
                            }
                            else if ( eOutHorJust == SvxCellHorJustify::Right )
                            {
                                if ( nCellY == nY && nCellX >= nX1 && nCellX <= nX2 )
                                    pRowInfo[nArrY].cellInfo(nCellX).nClipMark |= ScClipMark::Left;
                                bAnyClipped = true;
                                aAreaParam.maClipRect.AdjustLeft(nMarkPixel * nLayoutSign);
                            }
                            else
                            {
                                if ( nCellY == nY && nCellX >= nX1 && nCellX <= nX2 )
                                {
                                    pRowInfo[nArrY].cellInfo(nCellX).nClipMark |= ScClipMark::Right;
                                    pRowInfo[nArrY].cellInfo(nCellX).nClipMark |= ScClipMark::Left;
                                }
                                bAnyClipped = 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() < nScrX || aAreaParam.maClipRect.Left() >= nScrX + nScrW );
                    // Take adjusted values of aAreaParam.mbLeftClip and aAreaParam.mbRightClip
                    bool bVClip = AdjustAreaParamClipRect(aAreaParam);
                    bool bHClip = aAreaParam.mbLeftClip || aAreaParam.mbRightClip;
 
                    // check horizontal space
 
                    if ( !bOutside )
                    {
                        bool bRightAdjusted = false;        // to correct text width calculation later
                        switch (eOutHorJust)
                        {
                            case SvxCellHorJustify::Left:
                                nJustPosX += static_cast<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 ( eType != OUTTYPE_PRINTER ||
                                    ( mpDoc->GetRowFlags( nCellY, nTab ) & CRFlags::ManualSize ) ||
                                    ( aVars.HasCondHeight() ) )
                                bVClip = true;
                        }
 
                        if ( bHClip || bVClip )
                        {
                            // only clip the affected dimension so that not all right-aligned
                            // columns are cut off when performing a non-proportional resize
                            if (!bHClip)
                            {
                                aAreaParam.maClipRect.SetLeft( nScrX );
                                aAreaParam.maClipRect.SetRight( nScrX+nScrW );
                            }
                            if (!bVClip)
                            {
                                aAreaParam.maClipRect.SetTop( nScrY );
                                aAreaParam.maClipRect.SetBottom( nScrY+nScrH );
                            }
 
                            //  aClipRect is not used after SetClipRegion/IntersectClipRegion,
                            //  so it can be modified here
                            if (bPixelToLogic)
                                aAreaParam.maClipRect = mpRefDevice->PixelToLogic( aAreaParam.maClipRect );
 
                            if (bMetaFile)
                            {
                                mpDev->Push();
                                mpDev->IntersectClipRegion( aAreaParam.maClipRect );
                            }
                            else
                                mpDev->SetClipRegion( vcl::Region( aAreaParam.maClipRect ) );
                        }
 
                        Point aURLStart( nJustPosX, nJustPosY );    // copy before modifying for orientation
 
                        switch (aVars.GetOrient())
                        {
                            case SvxCellOrientation::Standard:
                                nJustPosY += aVars.GetAscent();
                                break;
                            case SvxCellOrientation::TopBottom:
                                nJustPosX += aVars.GetTextSize().Width() - aVars.GetAscent();
                                break;
                            case SvxCellOrientation::BottomUp:
                                nJustPosY += aVars.GetTextSize().Height();
                                nJustPosX += aVars.GetAscent();
                                break;
                            default:
                            {
                                // added to avoid warnings
                            }
                        }
 
                        // When clipping, the visible part is now completely defined by the alignment,
                        // there's no more special handling to show the right part of RTL text.
 
                        Point aDrawTextPos( nJustPosX, nJustPosY );
                        if ( bPixelToLogic )
                        {
                            //  undo text width adjustment in pixels
                            if (bRightAdjusted)
                                aDrawTextPos.AdjustX(aVars.GetTextSize().Width() );
 
                            aDrawTextPos = mpRefDevice->PixelToLogic( aDrawTextPos );
 
                            //  redo text width adjustment in logic units
                            if (bRightAdjusted)
                                aDrawTextPos.AdjustX( -(aVars.GetOriginalWidth()) );
                        }
 
                        // in Metafiles always use DrawTextArray to ensure that positions are
                        // recorded (for non-proportional resize):
 
                        const OUString& aString = aVars.GetString();
                        if (!aString.isEmpty())
                        {
                            if (bTaggedPDF)
                                pPDF->WrapBeginStructureElement(vcl::PDFWriter::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 (bMetaFile || pFmtDevice != mpDev || aZoomX != aZoomY)
                            {
                                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();
                        }
 
                        if ( bHClip || bVClip )
                        {
                            if (bMetaFile)
                                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);
                        }
                    }
                }
                nPosX += pRowInfo[0].basicCellInfo(nX).nWidth * nLayoutSign;
                if (bTaggedPDF)
                    pPDF->EndStructureElement();
            }
            if (bTaggedPDF)
                pPDF->EndStructureElement();
        }
        nPosY += pRowInfo[nArrY].nHeight;
    }
    if (bTaggedPDF)
        pPDF->EndStructureElement();
 
    if ( bProgress )
        ScProgress::DeleteInterpretProgress();
}
 
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->GetEnginePool());
        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 ( bShowSpellErrors )
            nCtrl |= EEControlBits::ONLINESPELLING;
        if ( eType == 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( nTab ) );
}
 
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( 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),
    mpMisspellRanges(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 (mpMisspellRanges)
        mpEngine->SetAllMisspellRanges(*mpMisspellRanges);
 
    return true;
}
 
static Color GetConfBackgroundColor()
{
    if (const ScTabViewShell* pTabViewShellBg = ScTabViewShell::GetActiveViewShell())
        return pTabViewShellBg->GetViewRenderingData().GetDocColor();
    return SC_MOD()->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();
 
    auto pSet = std::make_unique<SfxItemSet>( mpEngine->GetEmptyItemSet() );
    mpPattern->FillEditItemSet( pSet.get(), mpCondSet );
    if ( mpPreviewFontSet )
    {
        if ( const SvxFontItem* pItem = mpPreviewFontSet->GetItemIfSet( ATTR_FONT ) )
        {
            // tdf#125054 adapt WhichID
            pSet->PutAsTargetWhich(*pItem, EE_CHAR_FONTINFO);
        }
        if ( const SvxFontItem* pItem = mpPreviewFontSet->GetItemIfSet( ATTR_CJK_FONT ) )
        {
            // tdf#125054 adapt WhichID
            pSet->PutAsTargetWhich(*pItem, EE_CHAR_FONTINFO_CJK);
        }
        if ( const SvxFontItem* pItem = mpPreviewFontSet->GetItemIfSet( ATTR_CTL_FONT ) )
        {
            // tdf#125054 adapt WhichID
            pSet->PutAsTargetWhich(*pItem, EE_CHAR_FONTINFO_CTL);
        }
    }
    bool bParaHyphenate = pSet->Get(EE_PARA_HYPHENATE).GetValue();
    mpEngine->SetDefaults( std::move(pSet) );
    mpOldPattern = mpPattern;
    mpOldCondSet = mpCondSet;
    mpOldPreviewFontSet = mpPreviewFontSet;
 
    EEControlBits nControl = mpEngine->GetControlWord();
    if (meOrient == SvxCellOrientation::Stacked)
        nControl |= EEControlBits::ONECHARPERLINE;
    else
        nControl &= ~EEControlBits::ONECHARPERLINE;
    mpEngine->SetControlWord( nControl );
 
    if ( !mbHyphenatorSet && 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() < nScrX )
    {
        rAreaParam.maClipRect.SetLeft( nScrX );
        rAreaParam.mbLeftClip = true;
    }
    if( rAreaParam.maClipRect.Right() > nScrX + nScrW )
    {
        rAreaParam.maClipRect.SetRight( nScrX + nScrW );          //! minus one?
        rAreaParam.mbRightClip = true;
    }
 
    bool bVClip = false;
 
    if( rAreaParam.maClipRect.Top() < nScrY )
    {
        rAreaParam.maClipRect.SetTop( nScrY );
        bVClip = true;
    }
    if( rAreaParam.maClipRect.Bottom() > nScrY + nScrH )
    {
        rAreaParam.maClipRect.SetBottom( nScrY + nScrH );     //! minus one?
        bVClip = true;
    }
    return bVClip;
}
 
// Doesn't handle clip marks - should be handled in advance using GetOutputArea
class ClearableClipRegion
{
public:
    ClearableClipRegion( const tools::Rectangle& rRect, bool bClip, bool bSimClip,
                        const VclPtr<OutputDevice>& pDev, bool bMetaFile )
        :mbMetaFile(bMetaFile)
    {
        if (!(bClip || bSimClip))
            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() COVERITY_NOEXCEPT_FALSE
    {
        // 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 < nX1 )
    {
        nXForPos = nX1;
        rParam.mnPosX = rParam.mnInitPosX;
    }
    SCSIZE nArrYForPos = rParam.mnArrY;
    if ( nArrYForPos < 1 )
    {
        nArrYForPos = 1;
        rParam.mnPosY = nScrY;
    }
 
    OutputAreaParam aAreaParam;
 
    //  Initial page size - large for normal text, cell size for automatic line breaks
 
    Size aPaperSize( 1000000, 1000000 );
    if (rParam.mbBreak)
    {
        //  call GetOutputArea with nNeeded=0, to get only the cell width
 
        //! handle nArrY == 0
        GetOutputArea( nXForPos, nArrYForPos, rParam.mnPosX, rParam.mnPosY, rParam.mnCellX, rParam.mnCellY, 0,
                       *rParam.mpPattern, sal::static_int_cast<sal_uInt16>(eOutHorJust),
                       rParam.mbCellIsValue, true, false, aAreaParam );
 
        //! special ScEditUtil handling if formatting for printer
        rParam.calcPaperSize(aPaperSize, aAreaParam.maAlignRect, mnPPTX, mnPPTY);
    }
    if (rParam.mbPixelToLogic)
    {
        Size aLogicSize = mpRefDevice->PixelToLogic(aPaperSize);
        if ( rParam.mbBreak && !rParam.mbAsianVertical && mpRefDevice != pFmtDevice )
        {
            // #i85342# screen display and formatting for printer,
            // use same GetEditArea call as in ScViewData::SetEditEngine
 
            Fraction aFract(1,1);
            tools::Rectangle aUtilRect = ScEditUtil( mpDoc, rParam.mnCellX, rParam.mnCellY, nTab, Point(0,0), pFmtDevice,
                HMM_PER_TWIPS, HMM_PER_TWIPS, aFract, aFract ).GetEditArea( rParam.mpPattern, false );
            aLogicSize.setWidth( aUtilRect.GetWidth() );
        }
        rParam.mpEngine->SetPaperSize(aLogicSize);
    }
    else
        rParam.mpEngine->SetPaperSize(aPaperSize);
 
    //  Fill the EditEngine (cell attributes and text)
 
    // default alignment for asian vertical mode is top-right
    if ( rParam.mbAsianVertical && rParam.meVerJust == SvxCellVerJustify::Standard )
        rParam.meVerJust = SvxCellVerJustify::Top;
 
    rParam.setPatternToEngine(mbUseStyleColor);
    rParam.setAlignmentToEngine();
    // 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 >= nX1 && rParam.mbCellIsValue
            && (aAreaParam.mbLeftClip || aAreaParam.mbRightClip))
        {
            SetEngineTextAndGetWidth( rParam, u"###"_ustr, nNeededPixel, ( nLeftM + nRightM ) );
            tools::Long nLayoutSign = bLayoutRTL ? -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() < nScrX || aAreaParam.maClipRect.Left() >= nScrX + nScrW);
    if (bOutside)
        return;
 
    // Also take fields in a cell with automatic breaks into account: clip to cell width
    bool bClip = AdjustAreaParamClipRect(aAreaParam) || aAreaParam.mbLeftClip || aAreaParam.mbRightClip || bWrapFields;
    bool bSimClip = false;
 
    Size aCellSize;         // output area, excluding margins, in logical units
    if (rParam.mbPixelToLogic)
        aCellSize = mpRefDevice->PixelToLogic( Size( nOutWidth, nOutHeight ) );
    else
        aCellSize = Size( nOutWidth, nOutHeight );
 
    if ( nEngineHeight >= aCellSize.Height() + aRefOne.Height() )
    {
        const ScMergeAttr* pMerge = &rParam.mpPattern->GetItem(ATTR_MERGE);
        bool bMerged = pMerge->GetColMerge() > 1 || pMerge->GetRowMerge() > 1;
 
        //  Don't clip for text height when printing rows with optimal height,
        //  except when font size is from conditional formatting.
        //! Allow clipping when vertically merged?
        if ( eType != OUTTYPE_PRINTER ||
            ( mpDoc->GetRowFlags( rParam.mnCellY, nTab ) & CRFlags::ManualSize ) ||
            ( rParam.mpCondSet && SfxItemState::SET ==
                rParam.mpCondSet->GetItemState(ATTR_FONT_HEIGHT) ) )
            bClip = true;
        else
            bSimClip = true;
 
        //  Show clip marks if height is at least 5pt too small and
        //  there are several lines of text.
        //  Not for asian vertical text, because that would interfere
        //  with the default right position of the text.
        //  Only with automatic line breaks, to avoid having to find
        //  the cells with the horizontal end of the text again.
        if ( nEngineHeight - aCellSize.Height() > 100 &&
             rParam.mbBreak && bMarkClipped &&
             ( rParam.mpEngine->GetParagraphCount() > 1 || rParam.mpEngine->GetLineCount(0) > 1 ) )
        {
            ScCellInfo* pClipMarkCell = nullptr;
            if ( bMerged )
            {
                //  anywhere in the merged area...
                SCCOL nClipX = ( rParam.mnX < nX1 ) ? nX1 : rParam.mnX;
                pClipMarkCell = &pRowInfo[(rParam.mnArrY != 0) ? rParam.mnArrY : 1].cellInfo(nClipX);
            }
            else
                pClipMarkCell = &rParam.mpThisRowInfo->cellInfo(rParam.mnX);
 
            pClipMarkCell->nClipMark |= ScClipMark::Right;      //! also allow left?
            bAnyClipped = 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, bMetaFile);
 
        Point aLogicStart;
        if (rParam.mbPixelToLogic)
            aLogicStart = mpRefDevice->PixelToLogic( Point(nStartX,nStartY) );
        else
            aLogicStart = Point(nStartX, nStartY);
 
        if (!rParam.mbBreak)
        {
            //  horizontal alignment
            if (rParam.adjustHorAlignment(rParam.mpEngine))
                // reset adjustment for the next cell
                rParam.mpOldPattern = nullptr;
        }
 
        if (rParam.meVerJust==SvxCellVerJustify::Bottom ||
            rParam.meVerJust==SvxCellVerJustify::Standard)
        {
            //! if pRefDevice != pFmtDevice, keep heights in logic units,
            //! only converting margin?
 
            if (rParam.mbPixelToLogic)
                aLogicStart.AdjustY(mpRefDevice->PixelToLogic( Size(0, nTopM +
                                mpRefDevice->LogicToPixel(aCellSize).Height() -
                                mpRefDevice->LogicToPixel(Size(0,nEngineHeight)).Height()
                                )).Height() );
            else
                aLogicStart.AdjustY(nTopM + aCellSize.Height() - nEngineHeight );
        }
        else if (rParam.meVerJust==SvxCellVerJustify::Center)
        {
            if (rParam.mbPixelToLogic)
                aLogicStart.AdjustY(mpRefDevice->PixelToLogic( Size(0, nTopM + (
                                mpRefDevice->LogicToPixel(aCellSize).Height() -
                                mpRefDevice->LogicToPixel(Size(0,nEngineHeight)).Height() )
                                / 2)).Height() );
            else
                aLogicStart.AdjustY(nTopM + (aCellSize.Height() - nEngineHeight) / 2 );
        }
        else        // top
        {
            if (rParam.mbPixelToLogic)
                aLogicStart.AdjustY(mpRefDevice->PixelToLogic(Size(0,nTopM)).Height() );
            else
                aLogicStart.AdjustY(nTopM );
        }
 
        aURLStart = aLogicStart;      // copy before modifying for orientation
 
        // bMoveClipped handling has been replaced by complete alignment
        // handling (also extending to the left).
 
        if (bSimClip)
        {
            // no hard clip, only draw the affected rows
            Point aDocStart = aClip.getRect().TopLeft();
            aDocStart -= aLogicStart;
            rParam.mpEngine->Draw(*mpDev, aClip.getRect(), aDocStart, false);
        }
        else
        {
            rParam.mpEngine->Draw(*mpDev, aLogicStart);
        }
    }
 
    rParam.adjustForHyperlinkInPDF(aURLStart, mpDev);
}
 
void ScOutputData::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;
        bAnyClipped = true;
        aAreaParam.maClipRect.AdjustRight( -( nMarkPixel * nLayoutSign ) );
    }
    else if ( eOutHorJust == SvxCellHorJustify::Right )
    {
        pClipMarkCell->nClipMark |= ScClipMark::Left;
        bAnyClipped = true;
        aAreaParam.maClipRect.AdjustLeft( nMarkPixel * nLayoutSign );
    }
    else
    {
        pClipMarkCell->nClipMark |= ScClipMark::Right;
        pClipMarkCell->nClipMark |= ScClipMark::Left;
        bAnyClipped = 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 || !bMarkClipped
        || (rParam.mpEngine->GetParagraphCount() <= 1 && rParam.mpEngine->GetLineCount(0) <= 1))
        return;
 
    ScCellInfo* pClipMarkCell = nullptr;
    if (bMerged)
    {
        //  anywhere in the merged area...
        SCCOL nClipX = (rParam.mnX < nX1) ? nX1 : rParam.mnX;
        pClipMarkCell = &pRowInfo[(rParam.mnArrY != 0) ? rParam.mnArrY : 1].cellInfo(nClipX);
    }
    else
        pClipMarkCell = &rParam.mpThisRowInfo->cellInfo(rParam.mnX);
 
    bAnyClipped = true;
    bVertical = 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 ( eType != OUTTYPE_PRINTER ||
            ( mpDoc->GetRowFlags( rParam.mnCellY, nTab ) & CRFlags::ManualSize ) ||
            ( rParam.mpCondSet && SfxItemState::SET ==
                rParam.mpCondSet->GetItemState(ATTR_FONT_HEIGHT) ) )
            bClip = true;
        else
            bSimClip = true;
 
        ShowClipMarks( rParam, 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, bMetaFile));
}
 
void ScOutputData::DrawEditBottomTop(DrawEditParam& rParam)
{
    OSL_ASSERT(rParam.meHorJustAttr != SvxCellHorJustify::Repeat);
 
    const bool bRepeat = (rParam.meHorJustAttr == SvxCellHorJustify::Repeat && !rParam.mbBreak);
    const bool bShrink = !rParam.mbBreak && !bRepeat && lcl_GetBoolValue(*rParam.mpPattern, ATTR_SHRINKTOFIT, rParam.mpCondSet);
 
    SvxCellHorJustify eOutHorJust = rParam.meHorJustContext;
 
    //! mirror margin values for RTL?
    //! move margin down to after final GetOutputArea call
    tools::Long nTopM, nLeftM, nBottomM, nRightM;
    rParam.calcMargins(nTopM, nLeftM, nBottomM, nRightM, mnPPTX, mnPPTY);
 
    SCCOL nXForPos = rParam.mnX;
    if ( nXForPos < nX1 )
    {
        nXForPos = nX1;
        rParam.mnPosX = rParam.mnInitPosX;
    }
    SCSIZE nArrYForPos = rParam.mnArrY;
    if ( nArrYForPos < 1 )
    {
        nArrYForPos = 1;
        rParam.mnPosY = nScrY;
    }
 
    OutputAreaParam aAreaParam;
 
    //  Initial page size - large for normal text, cell size for automatic line breaks
 
    Size aPaperSize( 1000000, 1000000 );
    if (rParam.mbBreak)
    {
        //  call GetOutputArea with nNeeded=0, to get only the cell width
 
        //! handle nArrY == 0
        GetOutputArea( nXForPos, nArrYForPos, rParam.mnPosX, rParam.mnPosY, rParam.mnCellX, rParam.mnCellY, 0,
                       *rParam.mpPattern, sal::static_int_cast<sal_uInt16>(eOutHorJust),
                       rParam.mbCellIsValue, true, false, aAreaParam );
 
        //! special ScEditUtil handling if formatting for printer
        rParam.calcPaperSize(aPaperSize, aAreaParam.maAlignRect, mnPPTX, mnPPTY);
    }
    if (rParam.mbPixelToLogic)
    {
        Size aLogicSize = mpRefDevice->PixelToLogic(aPaperSize);
        rParam.mpEngine->SetPaperSize(aLogicSize);
    }
    else
        rParam.mpEngine->SetPaperSize(aPaperSize);
 
    //  Fill the EditEngine (cell attributes and text)
 
    rParam.setPatternToEngine(mbUseStyleColor);
    rParam.setAlignmentToEngine();
 
    //  Read content from cell
 
    bool bWrapFields = false;
    if (!rParam.readCellContent(mpDoc, mbShowNullValues, mbShowFormulas, mbSyntaxMode, mbUseStyleColor, mbForceAutoColor, bWrapFields))
        // Failed to read cell content.  Bail out.
        return;
 
    if ( mbSyntaxMode )
        SetEditSyntaxColor( *rParam.mpEngine, rParam.maCell );
    else if ( mbUseStyleColor && mbForceAutoColor )
        lcl_SetEditColor( *rParam.mpEngine, COL_AUTO );     //! or have a flag at EditEngine
 
    rParam.mpEngine->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() < nScrX || aAreaParam.maClipRect.Left() >= nScrX + nScrW);
    if (bOutside)
        return;
 
    // output area, excluding margins, in logical units
    const Size aCellSize = rParam.mbPixelToLogic
        ? mpRefDevice->PixelToLogic( Size( nOutWidth, nOutHeight ) )
        : Size( nOutWidth, nOutHeight );
 
    Point aURLStart;
 
    {
        const auto pClipRegion = Clip( rParam, aCellSize, aAreaParam, 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->Draw(*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 < nX1 )
    {
        nXForPos = nX1;
        rParam.mnPosX = rParam.mnInitPosX;
    }
    SCSIZE nArrYForPos = rParam.mnArrY;
    if ( nArrYForPos < 1 )
    {
        nArrYForPos = 1;
        rParam.mnPosY = nScrY;
    }
 
    OutputAreaParam aAreaParam;
 
    //  Initial page size - large for normal text, cell size for automatic line breaks
 
    Size aPaperSize( 1000000, 1000000 );
    if (rParam.hasLineBreak())
    {
        //  call GetOutputArea with nNeeded=0, to get only the cell width
 
        //! handle nArrY == 0
        GetOutputArea( nXForPos, nArrYForPos, rParam.mnPosX, rParam.mnPosY, rParam.mnCellX, rParam.mnCellY, 0,
                       *rParam.mpPattern, sal::static_int_cast<sal_uInt16>(eOutHorJust),
                       rParam.mbCellIsValue, true, false, aAreaParam );
 
        //! special ScEditUtil handling if formatting for printer
        rParam.calcPaperSize(aPaperSize, aAreaParam.maAlignRect, mnPPTX, mnPPTY);
    }
    if (rParam.mbPixelToLogic)
    {
        Size aLogicSize = mpRefDevice->PixelToLogic(aPaperSize);
        rParam.mpEngine->SetPaperSize(aLogicSize);
    }
    else
        rParam.mpEngine->SetPaperSize(aPaperSize);
 
    //  Fill the EditEngine (cell attributes and text)
 
    rParam.setPatternToEngine(mbUseStyleColor);
    rParam.setAlignmentToEngine();
 
    //  Read content from cell
 
    bool bWrapFields = false;
    if (!rParam.readCellContent(mpDoc, mbShowNullValues, mbShowFormulas, mbSyntaxMode, mbUseStyleColor, mbForceAutoColor, bWrapFields))
        // Failed to read cell content.  Bail out.
        return;
 
    if ( mbSyntaxMode )
        SetEditSyntaxColor( *rParam.mpEngine, rParam.maCell );
    else if ( mbUseStyleColor && mbForceAutoColor )
        lcl_SetEditColor( *rParam.mpEngine, COL_AUTO );     //! or have a flag at EditEngine
 
    rParam.mpEngine->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() < nScrX || aAreaParam.maClipRect.Left() >= nScrX + nScrW);
    if (bOutside)
        return;
 
    // output area, excluding margins, in logical units
    const Size aCellSize = rParam.mbPixelToLogic
        ? mpRefDevice->PixelToLogic( Size( nOutWidth, nOutHeight ) )
        : Size( nOutWidth, nOutHeight );
 
    Point aURLStart;
 
    {
        const auto pClipRegion = Clip( rParam, aCellSize, aAreaParam, 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->Draw(*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 < nX1 )
    {
        nXForPos = nX1;
        rParam.mnPosX = rParam.mnInitPosX;
    }
    SCSIZE nArrYForPos = rParam.mnArrY;
    if ( nArrYForPos < 1 )
    {
        nArrYForPos = 1;
        rParam.mnPosY = nScrY;
    }
 
    OutputAreaParam aAreaParam;
 
    //  Initial page size - large for normal text, cell size for automatic line breaks
 
    Size aPaperSize( 1000000, 1000000 );
    //  call GetOutputArea with nNeeded=0, to get only the cell width
 
    //! handle nArrY == 0
    GetOutputArea( nXForPos, nArrYForPos, rParam.mnPosX, rParam.mnPosY, rParam.mnCellX, rParam.mnCellY, 0,
                   *rParam.mpPattern, sal::static_int_cast<sal_uInt16>(eOutHorJust),
                   rParam.mbCellIsValue, true, false, aAreaParam );
 
    //! special ScEditUtil handling if formatting for printer
    rParam.calcPaperSize(aPaperSize, aAreaParam.maAlignRect, mnPPTX, mnPPTY);
 
    if (rParam.mbPixelToLogic)
    {
        Size aLogicSize = mpRefDevice->PixelToLogic(aPaperSize);
        if ( rParam.mbBreak && mpRefDevice != pFmtDevice )
        {
            // #i85342# screen display and formatting for printer,
            // use same GetEditArea call as in ScViewData::SetEditEngine
 
            Fraction aFract(1,1);
            tools::Rectangle aUtilRect = ScEditUtil( mpDoc, rParam.mnCellX, rParam.mnCellY, nTab, Point(0,0), pFmtDevice,
                HMM_PER_TWIPS, HMM_PER_TWIPS, aFract, aFract ).GetEditArea( rParam.mpPattern, false );
            aLogicSize.setWidth( aUtilRect.GetWidth() );
        }
        rParam.mpEngine->SetPaperSize(aLogicSize);
    }
    else
        rParam.mpEngine->SetPaperSize(aPaperSize);
 
    //  Fill the EditEngine (cell attributes and text)
 
    rParam.setPatternToEngine(mbUseStyleColor);
    rParam.setAlignmentToEngine();
 
    //  Read content from cell
 
    bool bWrapFields = false;
    if (!rParam.readCellContent(mpDoc, mbShowNullValues, mbShowFormulas, mbSyntaxMode, mbUseStyleColor, mbForceAutoColor, bWrapFields))
        // Failed to read cell content.  Bail out.
        return;
 
    if ( mbSyntaxMode )
        SetEditSyntaxColor( *rParam.mpEngine, rParam.maCell );
    else if ( mbUseStyleColor && mbForceAutoColor )
        lcl_SetEditColor( *rParam.mpEngine, COL_AUTO );     //! or have a flag at EditEngine
 
    rParam.mpEngine->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 = bLayoutRTL ? -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() < nScrX || aAreaParam.maClipRect.Left() >= nScrX + nScrW);
    if (bOutside)
        return;
 
    // Also take fields in a cell with automatic breaks into account: clip to cell width
    bool bClip = AdjustAreaParamClipRect(aAreaParam) || aAreaParam.mbLeftClip || aAreaParam.mbRightClip || bWrapFields;
    bool bSimClip = false;
 
    Size aCellSize;         // output area, excluding margins, in logical units
    if (rParam.mbPixelToLogic)
        aCellSize = mpRefDevice->PixelToLogic( Size( nOutWidth, nOutHeight ) );
    else
        aCellSize = Size( nOutWidth, nOutHeight );
 
    if ( nEngineHeight >= aCellSize.Height() + aRefOne.Height() )
    {
        const ScMergeAttr* pMerge = &rParam.mpPattern->GetItem(ATTR_MERGE);
        bool bMerged = pMerge->GetColMerge() > 1 || pMerge->GetRowMerge() > 1;
 
        //  Don't clip for text height when printing rows with optimal height,
        //  except when font size is from conditional formatting.
        //! Allow clipping when vertically merged?
        if ( eType != OUTTYPE_PRINTER ||
            ( mpDoc->GetRowFlags( rParam.mnCellY, nTab ) & CRFlags::ManualSize ) ||
            ( rParam.mpCondSet && SfxItemState::SET ==
                rParam.mpCondSet->GetItemState(ATTR_FONT_HEIGHT) ) )
            bClip = true;
        else
            bSimClip = true;
 
        //  Show clip marks if height is at least 5pt too small and
        //  there are several lines of text.
        //  Not for asian vertical text, because that would interfere
        //  with the default right position of the text.
        //  Only with automatic line breaks, to avoid having to find
        //  the cells with the horizontal end of the text again.
        if ( nEngineHeight - aCellSize.Height() > 100 &&
             rParam.mbBreak && bMarkClipped &&
             ( rParam.mpEngine->GetParagraphCount() > 1 || rParam.mpEngine->GetLineCount(0) > 1 ) )
        {
            ScCellInfo* pClipMarkCell = nullptr;
            if ( bMerged )
            {
                //  anywhere in the merged area...
                SCCOL nClipX = ( rParam.mnX < nX1 ) ? nX1 : rParam.mnX;
                pClipMarkCell = &pRowInfo[(rParam.mnArrY != 0) ? rParam.mnArrY : 1].cellInfo(nClipX);
            }
            else
                pClipMarkCell = &rParam.mpThisRowInfo->cellInfo(rParam.mnX);
 
            pClipMarkCell->nClipMark |= ScClipMark::Right;      //! also allow left?
            bAnyClipped = 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, bMetaFile);
 
        Point aLogicStart;
        if (rParam.mbPixelToLogic)
            aLogicStart = mpRefDevice->PixelToLogic( Point(nStartX,nStartY) );
        else
            aLogicStart = Point(nStartX, nStartY);
 
        if (rParam.meVerJust==SvxCellVerJustify::Bottom ||
            rParam.meVerJust==SvxCellVerJustify::Standard)
        {
            //! if pRefDevice != pFmtDevice, keep heights in logic units,
            //! only converting margin?
 
            if (rParam.mbPixelToLogic)
                aLogicStart.AdjustY(mpRefDevice->PixelToLogic( Size(0, nTopM +
                                mpRefDevice->LogicToPixel(aCellSize).Height() -
                                mpRefDevice->LogicToPixel(Size(0,nEngineHeight)).Height()
                                )).Height() );
            else
                aLogicStart.AdjustY(nTopM + aCellSize.Height() - nEngineHeight );
        }
        else if (rParam.meVerJust==SvxCellVerJustify::Center)
        {
            if (rParam.mbPixelToLogic)
                aLogicStart.AdjustY(mpRefDevice->PixelToLogic( Size(0, nTopM + (
                                mpRefDevice->LogicToPixel(aCellSize).Height() -
                                mpRefDevice->LogicToPixel(Size(0,nEngineHeight)).Height() )
                                / 2)).Height() );
            else
                aLogicStart.AdjustY(nTopM + (aCellSize.Height() - nEngineHeight) / 2 );
        }
        else        // top
        {
            if (rParam.mbPixelToLogic)
                aLogicStart.AdjustY(mpRefDevice->PixelToLogic(Size(0,nTopM)).Height() );
            else
                aLogicStart.AdjustY(nTopM );
        }
 
        aURLStart = aLogicStart;      // copy before modifying for orientation
 
        Size aPaperLogic = rParam.mpEngine->GetPaperSize();
        aPaperLogic.setWidth( nEngineWidth );
        rParam.mpEngine->SetPaperSize(aPaperLogic);
 
        // bMoveClipped handling has been replaced by complete alignment
        // handling (also extending to the left).
 
        if (bSimClip)
        {
            // no hard clip, only draw the affected rows
            Point aDocStart = aClip.getRect().TopLeft();
            aDocStart -= aLogicStart;
            rParam.mpEngine->Draw(*mpDev, aClip.getRect(), aDocStart, false);
        }
        else
        {
            rParam.mpEngine->Draw(*mpDev, aLogicStart);
        }
    }
 
    rParam.adjustForHyperlinkInPDF(aURLStart, mpDev);
}
 
void ScOutputData::DrawEditAsianVertical(DrawEditParam& rParam)
{
    // When in asian vertical orientation, the orientation value is STANDARD,
    // and the asian vertical boolean is true.
    OSL_ASSERT(rParam.meOrient == SvxCellOrientation::Standard);
    OSL_ASSERT(rParam.mbAsianVertical);
    OSL_ASSERT(rParam.meHorJustAttr != SvxCellHorJustify::Repeat);
 
    Size aRefOne = mpRefDevice->PixelToLogic(Size(1,1));
 
    bool bHidden = false;
    bool bShrink = !rParam.mbBreak && lcl_GetBoolValue(*rParam.mpPattern, ATTR_SHRINKTOFIT, rParam.mpCondSet);
    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 < nX1 )
    {
        nXForPos = nX1;
        rParam.mnPosX = rParam.mnInitPosX;
    }
    SCSIZE nArrYForPos = rParam.mnArrY;
    if ( nArrYForPos < 1 )
    {
        nArrYForPos = 1;
        rParam.mnPosY = nScrY;
    }
 
    OutputAreaParam aAreaParam;
 
    //  Initial page size - large for normal text, cell size for automatic line breaks
 
    Size aPaperSize( 1000000, 1000000 );
    //  call GetOutputArea with nNeeded=0, to get only the cell width
 
    //! handle nArrY == 0
    GetOutputArea( nXForPos, nArrYForPos, rParam.mnPosX, rParam.mnPosY, rParam.mnCellX, rParam.mnCellY, 0,
                   *rParam.mpPattern, sal::static_int_cast<sal_uInt16>(eOutHorJust),
                   rParam.mbCellIsValue, true, false, aAreaParam );
 
    //! special ScEditUtil handling if formatting for printer
    rParam.calcPaperSize(aPaperSize, aAreaParam.maAlignRect, mnPPTX, mnPPTY);
 
    if (rParam.mbPixelToLogic)
    {
        Size aLogicSize = mpRefDevice->PixelToLogic(aPaperSize);
        if ( rParam.mbBreak && !rParam.mbAsianVertical && mpRefDevice != pFmtDevice )
        {
            // #i85342# screen display and formatting for printer,
            // use same GetEditArea call as in ScViewData::SetEditEngine
 
            Fraction aFract(1,1);
            tools::Rectangle aUtilRect = ScEditUtil( mpDoc, rParam.mnCellX, rParam.mnCellY, nTab, Point(0,0), pFmtDevice,
                HMM_PER_TWIPS, HMM_PER_TWIPS, aFract, aFract ).GetEditArea( rParam.mpPattern, false );
            aLogicSize.setWidth( aUtilRect.GetWidth() );
        }
        rParam.mpEngine->SetPaperSize(aLogicSize);
    }
    else
        rParam.mpEngine->SetPaperSize(aPaperSize);
 
    //  Fill the EditEngine (cell attributes and text)
 
    // default alignment for asian vertical mode is top-right
    if ( rParam.meVerJust == SvxCellVerJustify::Standard )
        rParam.meVerJust = SvxCellVerJustify::Top;
 
    rParam.setPatternToEngine(mbUseStyleColor);
    rParam.setAlignmentToEngine();
 
    //  Read content from cell
 
    bool bWrapFields = false;
    if (!rParam.readCellContent(mpDoc, mbShowNullValues, mbShowFormulas, mbSyntaxMode, mbUseStyleColor, mbForceAutoColor, bWrapFields))
        // Failed to read cell content.  Bail out.
        return;
 
    if ( mbSyntaxMode )
        SetEditSyntaxColor( *rParam.mpEngine, rParam.maCell );
    else if ( mbUseStyleColor && mbForceAutoColor )
        lcl_SetEditColor( *rParam.mpEngine, COL_AUTO );     //! or have a flag at EditEngine
 
    rParam.mpEngine->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 = bLayoutRTL ? -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() < nScrX || aAreaParam.maClipRect.Left() >= nScrX + nScrW);
    if (bOutside)
        return;
 
    // Also take fields in a cell with automatic breaks into account: clip to cell width
    bool bClip = AdjustAreaParamClipRect(aAreaParam) || aAreaParam.mbLeftClip || aAreaParam.mbRightClip || bWrapFields;
    bool bSimClip = false;
 
    Size aCellSize;         // output area, excluding margins, in logical units
    if (rParam.mbPixelToLogic)
        aCellSize = mpRefDevice->PixelToLogic( Size( nOutWidth, nOutHeight ) );
    else
        aCellSize = Size( nOutWidth, nOutHeight );
 
    if ( nEngineHeight >= aCellSize.Height() + aRefOne.Height() )
    {
        const ScMergeAttr* pMerge = &rParam.mpPattern->GetItem(ATTR_MERGE);
        bool bMerged = pMerge->GetColMerge() > 1 || pMerge->GetRowMerge() > 1;
 
        //  Don't clip for text height when printing rows with optimal height,
        //  except when font size is from conditional formatting.
        //! Allow clipping when vertically merged?
        if ( eType != OUTTYPE_PRINTER ||
            ( mpDoc->GetRowFlags( rParam.mnCellY, nTab ) & CRFlags::ManualSize ) ||
            ( rParam.mpCondSet && SfxItemState::SET ==
                rParam.mpCondSet->GetItemState(ATTR_FONT_HEIGHT) ) )
            bClip = true;
        else
            bSimClip = true;
 
        //  Show clip marks if height is at least 5pt too small and
        //  there are several lines of text.
        //  Not for asian vertical text, because that would interfere
        //  with the default right position of the text.
        //  Only with automatic line breaks, to avoid having to find
        //  the cells with the horizontal end of the text again.
        if ( nEngineHeight - aCellSize.Height() > 100 &&
             ( rParam.mbBreak || rParam.meOrient == SvxCellOrientation::Stacked ) &&
             !rParam.mbAsianVertical && bMarkClipped &&
             ( rParam.mpEngine->GetParagraphCount() > 1 || rParam.mpEngine->GetLineCount(0) > 1 ) )
        {
            ScCellInfo* pClipMarkCell = nullptr;
            if ( bMerged )
            {
                //  anywhere in the merged area...
                SCCOL nClipX = ( rParam.mnX < nX1 ) ? nX1 : rParam.mnX;
                pClipMarkCell = &pRowInfo[(rParam.mnArrY != 0) ? rParam.mnArrY : 1].cellInfo(nClipX);
            }
            else
                pClipMarkCell = &rParam.mpThisRowInfo->cellInfo(rParam.mnX);
 
            pClipMarkCell->nClipMark |= ScClipMark::Right;      //! also allow left?
            bAnyClipped = 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, bMetaFile);
 
        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->Draw(*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 = nScrX;
    if ( bLayoutRTL )
    {
        nInitPosX += nMirrorW - 1;
    }
    tools::Long nLayoutSign = bLayoutRTL ? -1 : 1;
 
    SCCOL nLastContentCol = mpDoc->MaxCol();
    if ( nX2 < mpDoc->MaxCol() )
    {
        SCROW nEndRow;
        mpDoc->GetCellArea(nTab, nLastContentCol, nEndRow);
    }
 
    tools::Long nRowPosY = nScrY;
    for (SCSIZE nArrY=0; nArrY+1<nArrCount; nArrY++)            // 0 of the rest of the merged
    {
        RowInfo* pThisRowInfo = &pRowInfo[nArrY];
 
        if (nArrY==1) nRowPosY = nScrY;                         // positions before are calculated individually
 
        if ( pThisRowInfo->bChanged || nArrY==0 )
        {
            tools::Long nPosX = 0;
            for (SCCOL nX=0; nX<=nX2; nX++)                     // due to overflow
            {
                std::unique_ptr< ScPatternAttr > pPreviewPattr;
                if (nX==nX1) nPosX = nInitPosX;                 // positions before nX1 are calculated individually
 
                if (pThisRowInfo->basicCellInfo(nX).bEditEngine)
                {
                    SCROW nY = pThisRowInfo->nRowNo;
 
                    bool bReopenTag = false;
                    if (bTaggedPDF)
                        bReopenTag = ReopenPDFStructureElement(vcl::PDFWriter::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* pMergeFlag = mpDoc->GetAttr(nX, nY, nTab, ATTR_MERGE_FLAG);
                    bool bOverlapped = (pMergeFlag->IsHorOverlapped() || pMergeFlag->IsVerOverlapped());
 
                    tools::Long nPosY = nRowPosY;
                    if (bOverlapped)
                    {
                        nY = pRowInfo[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 == nX2 && 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,nTab, nX,nY,nTab, HasAttrFlags::Merged | HasAttrFlags::Overlapped ) )
                        {
                            nCellX = nTempX;
                            bDoCell = true;
                        }
                    }
                    else
                    {
                        bDoCell = true;
                    }
 
                    if ( bDoCell && bEditMode && nCellX == nEditCol && nCellY == nEditRow )
                        bDoCell = false;
 
                    const ScPatternAttr* pPattern = nullptr;
                    const SfxItemSet* pCondSet = nullptr;
                    if (bDoCell)
                    {
                        if ( nCellY == nY && nCellX >= nX1 && nCellX <= nX2 &&
                             !mpDoc->ColHidden(nCellX, nTab) )
                        {
                            ScCellInfo& rCellInfo = pThisRowInfo->cellInfo(nCellX);
                            pPattern = rCellInfo.pPatternAttr;
                            pCondSet = rCellInfo.pConditionSet;
                            aCell = rCellInfo.maCell;
                        }
                        else        // get from document
                        {
                            pPattern = mpDoc->GetPattern( nCellX, nCellY, nTab );
                            pCondSet = mpDoc->GetCondResult( nCellX, nCellY, nTab );
                            GetVisibleCell( nCellX, nCellY, nTab, aCell );
                        }
                        if (aCell.isEmpty())
                            bDoCell = false;
                    }
                    if (bDoCell)
                    {
                        if ( mpDoc->GetPreviewCellStyle() )
                        {
                            if ( ScStyleSheet* pPreviewStyle = mpDoc->GetPreviewCellStyle( nCellX, nCellY, nTab ) )
                            {
                                pPreviewPattr.reset( new ScPatternAttr(*pPattern) );
                                pPreviewPattr->SetStyleSheet(pPreviewStyle);
                                pPattern = pPreviewPattr.get();
                            }
                        }
                        SfxItemSet* pPreviewFontSet = mpDoc->GetPreviewFont( nCellX, nCellY, nTab );
                        lcl_ClearEdit( *mxOutputEditEngine );      // also calls SetUpdateMode(sal_False)
 
                        // fdo#32530: Check if the first character is RTL.
                        OUString aStr = mpDoc->GetString(nCellX, nCellY, nTab);
 
                        DrawEditParam aParam(pPattern, pCondSet, lcl_SafeIsValue(aCell));
                        const bool bNumberFormatIsText = lcl_isNumberFormatText( mpDoc, nCellX, nCellY, nTab );
                        aParam.meHorJustContext = getAlignmentFromContext( aParam.meHorJustAttr,
                                aParam.mbCellIsValue, aStr, *pPattern, pCondSet, mpDoc, nTab, bNumberFormatIsText);
                        aParam.meHorJustResult = (aParam.meHorJustAttr == SvxCellHorJustify::Block) ?
                                SvxCellHorJustify::Block : aParam.meHorJustContext;
                        aParam.mbPixelToLogic = bPixelToLogic;
                        aParam.mbHyphenatorSet = bHyphenatorSet;
                        aParam.mpEngine = 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.mpMisspellRanges = mpSpellCheckCxt->getMisspellRanges(nCellX, nCellY);
 
                        if (aParam.meHorJustAttr == SvxCellHorJustify::Repeat)
                        {
                            // ignore orientation/rotation if "repeat" is active
                            aParam.meOrient = SvxCellOrientation::Standard;
                        }
                        switch (aParam.meOrient)
                        {
                            case SvxCellOrientation::BottomUp:
                                DrawEditBottomTop(aParam);
                            break;
                            case SvxCellOrientation::TopBottom:
                                DrawEditTopBottom(aParam);
                            break;
                            case SvxCellOrientation::Stacked:
                                // this can be vertically stacked or asian vertical.
                                DrawEditStacked(aParam);
                            break;
                            default:
                                DrawEditStandard(aParam);
                        }
 
                        // Retrieve parameters for next iteration.
                        pOldPattern = aParam.mpOldPattern;
                        pOldCondSet = aParam.mpOldCondSet;
                        pOldPreviewFontSet = aParam.mpOldPreviewFontSet;
                        bHyphenatorSet = aParam.mbHyphenatorSet;
                    }
                    if (bReopenTag)
                        pPDF->EndStructureElement();
                }
                nPosX += pRowInfo[0].basicCellInfo(nX).nWidth * nLayoutSign;
            }
        }
        nRowPosY += pRowInfo[nArrY].nHeight;
    }
 
    if (mrTabInfo.maArray.HasCellRotation())
    {
        DrawRotated(bPixelToLogic);     //! call from outside ?
    }
}
 
void ScOutputData::DrawRotated(bool bPixelToLogic)
{
    InitOutputEditEngine();
    //! store nRotMax
    SCCOL nRotMax = nX2;
    for (SCSIZE nRotY=0; nRotY<nArrCount; nRotY++)
        if (pRowInfo[nRotY].nRotMaxCol != SC_ROTMAX_NONE && pRowInfo[nRotY].nRotMaxCol > nRotMax)
            nRotMax = pRowInfo[nRotY].nRotMaxCol;
 
    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 = nScrX;
    if ( bLayoutRTL )
    {
        nInitPosX += nMirrorW - 1;
    }
    tools::Long nLayoutSign = bLayoutRTL ? -1 : 1;
 
    tools::Long nRowPosY = nScrY;
    for (SCSIZE nArrY=0; nArrY+1<nArrCount; nArrY++)            // 0 for the rest of the merged
    {
        RowInfo* pThisRowInfo = &pRowInfo[nArrY];
        tools::Long nCellHeight = static_cast<tools::Long>(pThisRowInfo->nHeight);
        if (nArrY==1) nRowPosY = nScrY;                         // 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==nX1) nPosX = nInitPosX;                 // positions before nX1 are calculated individually
 
                const ScCellInfo* pInfo = &pThisRowInfo->cellInfo(nX);
                if ( pInfo->nRotateDir != ScRotateDir::NONE )
                {
                    SCROW nY = pThisRowInfo->nRowNo;
 
                    bool bHidden = false;
                    if (bEditMode)
                        if ( nX == nEditCol && nY == nEditRow )
                            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, nTab );
                            bFromDoc = true;
                        }
                        aCell = pInfo->maCell;
                        if (bFromDoc)
                            pCondSet = mpDoc->GetCondResult( nX, nY, nTab );
 
                        if (aCell.isEmpty() && nX>nX2)
                            GetVisibleCell( nX, nY, nTab, aCell );
 
                        if (aCell.isEmpty() || IsEmptyCellText(pThisRowInfo, nX, nY))
                            bHidden = true;     // nRotateDir is also set without a cell
 
                        tools::Long nCellWidth = static_cast<tools::Long>(pRowInfo[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<nX1)
                        {
                            if ((bBreak || eOrient!=SvxCellOrientation::Standard) && !bMerged)
                                bHidden = true;
                            else
                            {
                                nStartX = nInitPosX;
                                SCCOL nCol = nX1;
                                while (nCol > nX)
                                {
                                    --nCol;
                                    nStartX -= nLayoutSign * static_cast<tools::Long>(pRowInfo[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,nTab) * mnPPTX;
                                SCROW nCountY = pMerge->GetRowMerge();
                                nOutHeight += mpDoc->GetScaledRowHeight( nY+1, nY+nCountY-1, nTab, mnPPTY);
                            }
 
                            SvxCellVerJustify eVerJust =
                                                pPattern->GetItem(ATTR_VER_JUSTIFY, pCondSet).GetValue();
 
                            // syntax mode is ignored here...
 
                            // StringDiffer doesn't look at hyphenate, language items
                            if ( !ScPatternAttr::areSame(pPattern, pOldPattern) || pCondSet != pOldCondSet )
                            {
                                auto pSet = std::make_unique<SfxItemSet>( mxOutputEditEngine->GetEmptyItemSet() );
                                pPattern->FillEditItemSet( pSet.get(), pCondSet );
 
                                                                    // adjustment for EditEngine
                                SvxAdjust eSvxAdjust = SvxAdjust::Left;
                                if (eOrient==SvxCellOrientation::Stacked)
                                    eSvxAdjust = SvxAdjust::Center;
                                // adjustment for bBreak is omitted here
                                pSet->Put( SvxAdjustItem( eSvxAdjust, EE_PARA_JUST ) );
 
                                bool bParaHyphenate = pSet->Get(EE_PARA_HYPHENATE).GetValue();
                                mxOutputEditEngine->SetDefaults( std::move(pSet) );
                                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 ( bLayoutRTL )
                                    {
                                        // 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( nScrX+nScrW-nStartX, nScrY+nScrH-nStartY );
 
                                // go on writing
 
                                Size aCellSize;
                                if (bPixelToLogic)
                                    aCellSize = mpRefDevice->PixelToLogic( Size( nOutWidth, nOutHeight ) );
                                else
                                    aCellSize = Size( nOutWidth, nOutHeight );  // scale is one
 
                                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 ( bLayoutRTL )
                                        bNegative = !bNegative;
                                }
 
                                // use GetOutputArea to hide the grid
                                // (clip region is done manually below)
                                OutputAreaParam aAreaParam;
 
                                SCCOL nCellX = nX;
                                SCROW nCellY = nY;
                                SvxCellHorJustify eOutHorJust = eHorJust;
                                if ( eRotMode != SVX_ROTATE_MODE_STANDARD )
                                    eOutHorJust = bNegative ? SvxCellHorJustify::Right : SvxCellHorJustify::Left;
                                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<nX1)
                                {
                                    //! clipping is not needed when on the left side of the window
 
                                    if (nStartX<nScrX)
                                    {
                                        tools::Long nDif = nScrX - nStartX;
                                        nClipStartX = nScrX;
                                        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 = nScrX;
                                    aClipSize.setWidth( nScrW );
                                }
 
                                if (bPixelToLogic)
                                    aAreaParam.maClipRect = mpRefDevice->PixelToLogic( tools::Rectangle(
                                                    Point(nClipStartX,nClipStartY), aClipSize ) );
                                else
                                    aAreaParam.maClipRect = tools::Rectangle(Point(nClipStartX, nClipStartY),
                                                            aClipSize );    // Scale = 1
 
                                if (bMetaFile)
                                {
                                    mpDev->Push();
                                    mpDev->IntersectClipRegion( aAreaParam.maClipRect );
                                }
                                else
                                    mpDev->SetClipRegion( vcl::Region( aAreaParam.maClipRect ) );
 
                                Point aLogicStart;
                                if (bPixelToLogic)
                                    aLogicStart = mpRefDevice->PixelToLogic( Point(nStartX,nStartY) );
                                else
                                    aLogicStart = Point(nStartX, nStartY);
                                if ( eOrient!=SvxCellOrientation::Standard || !bBreak )
                                {
                                    tools::Long nAvailWidth = aCellSize.Width();
                                    if (eType==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 ( bLayoutRTL )
                                {
                                    if (bPixelToLogic)
                                        aLogicStart.AdjustX( -(mpRefDevice->PixelToLogic(
                                                        Size( nCellWidth, 0 ) ).Width()) );
                                    else
                                        aLogicStart.AdjustX( -nCellWidth );
                                }
 
                                if ( eOrient==SvxCellOrientation::Standard ||
                                     eOrient==SvxCellOrientation::Stacked || !bBreak )
                                {
                                    if (eVerJust==SvxCellVerJustify::Bottom ||
                                        eVerJust==SvxCellVerJustify::Standard)
                                    {
                                        if (bPixelToLogic)
                                            aLogicStart.AdjustY(mpRefDevice->PixelToLogic( Size(0,
                                                            mpRefDevice->LogicToPixel(aCellSize).Height() -
                                                            mpRefDevice->LogicToPixel(Size(0,nEngineHeight)).Height()
                                                            )).Height() );
                                        else
                                            aLogicStart.AdjustY(aCellSize.Height() - nEngineHeight );
                                    }
 
                                    else if (eVerJust==SvxCellVerJustify::Center)
                                    {
                                        if (bPixelToLogic)
                                            aLogicStart.AdjustY(mpRefDevice->PixelToLogic( Size(0,(
                                                            mpRefDevice->LogicToPixel(aCellSize).Height() -
                                                            mpRefDevice->LogicToPixel(Size(0,nEngineHeight)).Height())
                                                            / 2)).Height() );
                                        else
                                            aLogicStart.AdjustY((aCellSize.Height() - nEngineHeight) / 2 );
                                    }
                                }
 
                                // TOPBOTTOM and BOTTOMTOP are handled in DrawStrings/DrawEdit
                                OSL_ENSURE( eOrient == SvxCellOrientation::Standard && nAttrRotate,
                                            "DrawRotated: no rotation" );
 
                                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->Draw(*mpDev, aLogicStart, nOriVal);
 
                                if (bMetaFile)
                                    mpDev->Pop();
                                else
                                    mpDev->SetClipRegion();
                            }
                        }
                    }
                }
                nPosX += pRowInfo[0].basicCellInfo(nX).nWidth * nLayoutSign;
            }
        }
        nRowPosY += pRowInfo[nArrY].nHeight;
    }
}
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V530 The return value of function 'padToLength' is required to be utilized.

V530 The return value of function 'padToLength' is required to be utilized.

V522 There might be dereferencing of a potential null pointer 'pPDF'.