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


Quelle  output2.cxx   Sprache: C

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


#include <scitems.hxx>
#include <editeng/eeitem.hxx>

#include <editeng/adjustitem.hxx>
#include <svx/algitem.hxx>
#include <editeng/brushitem.hxx>
#include <svtools/colorcfg.hxx>
#include <editeng/colritem.hxx>
#include <editeng/charreliefitem.hxx>
#include <editeng/crossedoutitem.hxx>
#include <editeng/contouritem.hxx>
#include <editeng/editobj.hxx>
#include <editeng/editstat.hxx>
#include <editeng/emphasismarkitem.hxx>
#include <editeng/fhgtitem.hxx>
#include <editeng/forbiddenruleitem.hxx>
#include <editeng/frmdiritem.hxx>
#include <editeng/justifyitem.hxx>
#include <svx/rotmodit.hxx>
#include <editeng/udlnitem.hxx>
#include <editeng/unolingu.hxx>
#include <editeng/fontitem.hxx>
#include <editeng/postitem.hxx>
#include <editeng/shdditem.hxx>
#include <editeng/wghtitem.hxx>
#include <editeng/wrlmitem.hxx>
#include <formula/errorcodes.hxx>
#include <svl/numformat.hxx>
#include <svl/zforlist.hxx>
#include <svl/zformat.hxx>
#include <vcl/kernarray.hxx>
#include <vcl/svapp.hxx>
#include <vcl/metric.hxx>
#include <vcl/outdev.hxx>
#include <vcl/pdfextoutdevdata.hxx>
#include <vcl/settings.hxx>
#include <vcl/glyphitem.hxx>
#include <vcl/glyphitemcache.hxx>
#include <sal/log.hxx>
#include <unotools/charclass.hxx>
#include <osl/diagnose.h>

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

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

#include <memory>
#include <vector>

#include <math.h>

using namespace com::sun::star;

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

#define DRAWTEXT_MAX    32767

const sal_uInt16 SC_SHRINKAGAIN_MAX = 7;
constexpr auto HMM_PER_TWIPS = o3tl::convert(1.0, o3tl::Length::twip, o3tl::Length::mm100);

class ScDrawStringsVars
{
    ScOutputData*       pOutput;                // connection

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

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

    OUString            aString;                // contents
    Size                aTextSize;
    tools::Long                nOriginalWidth;
    tools::Long                nMaxDigitWidth;
    tools::Long                nSignWidth;
    tools::Long                nDotWidth;
    tools::Long                nExpWidth;

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

    bool                bPixelToLogic;
    bool                bCellContrast;

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

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

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

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

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

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

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

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

    const OUString&         GetString() const        { return aString; }
    const Size&             GetTextSize() const      { return aTextSize; }
    tools::Long                    GetOriginalWidth() const { return nOriginalWidth; }
    tools::Long             GetFmtTextWidth(const OUString& rString);

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

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

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

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

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

    bool    HasEditCharacters() const;

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

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

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

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

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

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

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

    Fraction aFraction( nScale, 100 );
    if ( !bPixelToLogic )
        aFraction *= pOutput->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&nbsp;);

    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, const ScRefCellValue& rCell )
{
    vcl::PDFExtOutDevData* pPDFData = dynamic_cast< vcl::PDFExtOutDevData* >( pDev->GetExtOutDevData() );

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

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

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

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

void ScOutputData::SetEditSyntaxColor( EditEngine& rEngine, const ScRefCellValue&&nbsp;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&&nbsp;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)
    {
        pPDF->WrapBeginStructureElement(vcl::pdf::StructElement::Table, u"Table"_ustr);
        pPDF->GetScPDFState()->m_TableRowMap.clear();
    }

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

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

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

    // Try to limit interpreting to only visible cells. Calling e.g. IsValue()
    // on a formula cell that needs interpreting would call Interpret()
    // for the entire formula group, which could be large.
    mpDoc->InterpretCellsIfNeeded( ScRange( 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)
        {
            bool bReopenRowTag = false;
            tools::Long nPosX = nInitPosX;
            if ( nLoopStartX < nX1 )
                nPosX -= pRowInfo[0].basicCellInfo(nLoopStartX).nWidth * nLayoutSign;
            for (SCCOL nX=nLoopStartX; nX<=nX2; nX++)
            {
                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
--> --------------------

--> maximum size reached

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

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

¤ Dauer der Verarbeitung: 0.23 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

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

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge