cache SalLayoutGlyphs in ScOutputData::LayoutStrings()

OutputDevice::GetTextWidth() requires the layout, and DrawText()
requires it as well. Moreover GetTextWidth() may be called multiple
times (e.g. if the whole string doesn't fit the cell).
Cache up to 1000 layouts, they get cleaned up after LayoutStrings(),
and with larger cells there won't be that many of them, and with
smaller cells they should generally contain short strings.

Change-Id: I42defd275467078b2c02c7700ca9bbc2c6e65e15
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/109708
Tested-by: Jenkins
Reviewed-by: Luboš Luňák <l.lunak@collabora.com>
diff --git a/sc/source/ui/view/output2.cxx b/sc/source/ui/view/output2.cxx
index 764919b..e7083ed 100644
--- a/sc/source/ui/view/output2.cxx
+++ b/sc/source/ui/view/output2.cxx
@@ -51,6 +51,8 @@
#include <vcl/outdev.hxx>
#include <vcl/pdfextoutdevdata.hxx>
#include <vcl/settings.hxx>
#include <vcl/glyphitem.hxx>
#include <vcl/vcllayout.hxx>
#include <sal/log.hxx>
#include <unotools/charclass.hxx>
#include <osl/diagnose.h>
@@ -74,6 +76,7 @@

#include <memory>
#include <vector>
#include <o3tl/lru_map.hxx>

#include <math.h>

@@ -113,6 +116,7 @@ class ScDrawStringsVars
    tools::Long                nExpWidth;

    ScRefCellValue      maLastCell;
    mutable o3tl::lru_map<OUString, SalLayoutGlyphs> mCachedGlyphs;
    sal_uLong           nValueFormat;
    bool                bLineBreak;
    bool                bRepeat;
@@ -155,6 +159,7 @@ public:
    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.
@@ -175,6 +180,10 @@ public:

    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;

private:
    tools::Long        GetMaxDigitWidth();     // in logic units
    tools::Long        GetSignWidth();
@@ -200,6 +209,7 @@ ScDrawStringsVars::ScDrawStringsVars(ScOutputData* pData, bool bPTL) :
    nSignWidth( 0 ),
    nDotWidth( 0 ),
    nExpWidth( 0 ),
    mCachedGlyphs( 1000 ),
    nValueFormat( 0 ),
    bLineBreak  ( false ),
    bRepeat     ( false ),
@@ -291,6 +301,7 @@ void ScDrawStringsVars::SetPattern(
    nSignWidth     = 0;
    nDotWidth      = 0;
    nExpWidth      = 0;
    mCachedGlyphs.clear();

    pPattern = pNew;
    pCondSet = pSet;
@@ -445,6 +456,7 @@ void ScDrawStringsVars::SetPatternSimple( const ScPatternAttr* pNew, const SfxIt
    nSignWidth     = 0;
    nDotWidth      = 0;
    nExpWidth      = 0;
    mCachedGlyphs.clear();

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

@@ -558,7 +570,7 @@ void ScDrawStringsVars::RepeatToFill( tools::Long nColWidth )
    if ( nRepeatPos == -1 || nRepeatPos > aString.getLength() )
        return;

    tools::Long nCharWidth = pOutput->pFmtDevice->GetTextWidth(OUString(nRepeatChar));
    tools::Long nCharWidth = GetFmtTextWidth(OUString(nRepeatChar));

    if ( nCharWidth < 1 || (bPixelToLogic && nCharWidth < pOutput->mpRefDevice->PixelToLogic(Size(1,0)).Width()) )
        return;
@@ -675,7 +687,7 @@ void ScDrawStringsVars::SetTextToWidthOrHash( ScRefCellValue& rCell, tools::Long
        aString = sTempOut;
    }

    tools::Long nActualTextWidth = pOutput->pFmtDevice->GetTextWidth(aString);
    tools::Long nActualTextWidth = GetFmtTextWidth(aString);
    if (nActualTextWidth > nWidth)
    {
        // Even after the decimal adjustment the text doesn't fit.  Give up.
@@ -693,7 +705,7 @@ void ScDrawStringsVars::SetAutoText( const OUString& rAutoText )

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

    if ( !pRefDevice->GetConnectMetaFile() || pRefDevice->GetOutDevType() == OUTDEV_PRINTER )
@@ -725,6 +737,7 @@ tools::Long ScDrawStringsVars::GetMaxDigitWidth()
    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);
    }
@@ -759,11 +772,32 @@ tools::Long ScDrawStringsVars::GetExpWidth()
    return nExpWidth;
}

const SalLayoutGlyphs* ScDrawStringsVars::GetLayoutGlyphs(const OUString& rString) const
{
    auto it = mCachedGlyphs.find( rString );
    if( it != mCachedGlyphs.end() && it->second.IsValid())
        return &it->second;
    std::unique_ptr<SalLayout> layout = pOutput->pFmtDevice->ImplLayout( rString, 0, rString.getLength(),
        Point( 0, 0 ), 0, nullptr, SalLayoutFlags::GlyphItemsOnly );
    if( layout && layout->GetGlyphs())
    {
        mCachedGlyphs.insert( std::make_pair( rString, *layout->GetGlyphs()));
        assert(mCachedGlyphs.find( rString ) == mCachedGlyphs.begin()); // newly inserted item is first
        return &mCachedGlyphs.begin()->second;
    }
    return nullptr;
}

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( pFmtDevice->GetTextWidth( aString ) );
    aTextSize.setWidth( GetFmtTextWidth( aString ) );
    aTextSize.setHeight( pFmtDevice->GetTextHeight() );

    if ( !pRefDevice->GetConnectMetaFile() || pRefDevice->GetOutDevType() == OUTDEV_PRINTER )
@@ -2043,7 +2077,7 @@ tools::Rectangle ScOutputData::LayoutStrings(bool bPixelToLogic, bool bPaint, co
                                        aShort = aShort.copy(nTextLen-nShortLen);

                                        // Adjust the text position after shortening of the string.
                                        double fShortWidth = pFmtDevice->GetTextWidth(aShort);
                                        double fShortWidth = aVars.GetFmtTextWidth(aShort);
                                        double fOffset = fTextWidth - fShortWidth;
                                        aDrawTextPos.Move(fOffset, 0);
                                    }
@@ -2082,7 +2116,8 @@ tools::Rectangle ScOutputData::LayoutStrings(bool bPixelToLogic, bool bPaint, co
                            else
                            {
                                if (bPaint)
                                    mpDev->DrawText(aDrawTextPos, aShort);
                                    mpDev->DrawText(aDrawTextPos, aShort, 0, -1, nullptr, nullptr,
                                        aVars.GetLayoutGlyphs(aShort));
                            }
                        }