tdf#152048: Fix measuring text width with font fallback

This is a regression from:

commit 43a5400063b17ed4ba4cbb38bdf5da4a991f60e2
Author: Khaled Hosny <khaled@libreoffice.org>
Date:   Thu May 25 10:59:18 2023 +0300

    tdf#152048: Fix underline width for Kashida-justified text

It fixed measuring width when there is Kashida justification, but broke
it for font fallback, because of the way MultiSalLayout measures the
text width takes the maximum of the text widths for all its layouts, but
layouts with missing glyphs are now giving wrong text width as they are
measuring the width of .notdef (GID 0) glyph.

This change makes MultiSalLayout measure the text width by iterating
over all its valid glyphs.

Change-Id: I8b19e0d44326c6f0afe6e504ab6d90c6fb3575f9
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/152933
Tested-by: Jenkins
Reviewed-by: خالد حسني <khaled@libreoffice.org>
(cherry picked from commit 38b5e9bc7d45f52f11bad125a584ad2261383de6)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/152920
Reviewed-by: Caolán McNamara <caolan.mcnamara@collabora.com>
diff --git a/vcl/inc/sallayout.hxx b/vcl/inc/sallayout.hxx
index 3549675..d48edaa 100644
--- a/vcl/inc/sallayout.hxx
+++ b/vcl/inc/sallayout.hxx
@@ -62,6 +62,7 @@ class MultiSalLayout final : public SalLayout
public:
    void            DrawText(SalGraphics&) const override;
    sal_Int32       GetTextBreak(DeviceCoordinate nMaxWidth, DeviceCoordinate nCharExtra, int nFactor) const override;
    DeviceCoordinate GetTextWidth() const final override;
    DeviceCoordinate FillDXArray(std::vector<DeviceCoordinate>* pDXArray, const OUString& rStr) const override;
    void            GetCaretPositions(int nArraySize, sal_Int32* pCaretXArray) const override;
    bool            GetNextGlyph(const GlyphItem** pGlyph, DevicePoint& rPos, int& nStart,
diff --git a/vcl/source/gdi/sallayout.cxx b/vcl/source/gdi/sallayout.cxx
index 1ed2a37a..3cdd716 100644
--- a/vcl/source/gdi/sallayout.cxx
+++ b/vcl/source/gdi/sallayout.cxx
@@ -1009,45 +1009,53 @@ sal_Int32 MultiSalLayout::GetTextBreak( DeviceCoordinate nMaxWidth, DeviceCoordi
    return -1;
}

DeviceCoordinate MultiSalLayout::GetTextWidth() const
{
    // Measure text width. There might be holes in each SalLayout due to
    // missing chars, so we use GetNextGlyph() to get the glyphs across all
    // layouts.
    int nStart = 0;
    DevicePoint aPos;
    const GlyphItem* pGlyphItem;

    DeviceCoordinate nWidth = 0;
    while (GetNextGlyph(&pGlyphItem, aPos, nStart))
        nWidth += pGlyphItem->newWidth();

    return nWidth;
}

DeviceCoordinate MultiSalLayout::FillDXArray( std::vector<DeviceCoordinate>* pCharWidths, const OUString& rStr ) const
{
    DeviceCoordinate nMaxWidth = 0;

    // prepare merging of fallback levels
    std::vector<DeviceCoordinate> aTempWidths;
    const int nCharCount = mnEndCharPos - mnMinCharPos;
    if( pCharWidths )
    if (pCharWidths)
    {
        // prepare merging of fallback levels
        std::vector<DeviceCoordinate> aTempWidths;
        const int nCharCount = mnEndCharPos - mnMinCharPos;
        pCharWidths->clear();
        pCharWidths->resize(nCharCount, 0);
    }

    for( int n = mnLevel; --n >= 0; )
    {
        // query every fallback level
        DeviceCoordinate nTextWidth = mpLayouts[n]->FillDXArray( &aTempWidths, rStr );
        if( !nTextWidth )
            continue;
        // merge results from current level
        if( nMaxWidth < nTextWidth )
            nMaxWidth = nTextWidth;
        if( !pCharWidths )
            continue;
        // calculate virtual char widths using most probable fallback layout
        for( int i = 0; i < nCharCount; ++i )
        for (int n = mnLevel; --n >= 0;)
        {
            // #i17359# restriction:
            // one char cannot be resolved from different fallbacks
            if( (*pCharWidths)[i] != 0 )
                continue;
            DeviceCoordinate nCharWidth = aTempWidths[i];
            if( !nCharWidth )
                continue;
            (*pCharWidths)[i] = nCharWidth;
            // query every fallback level
            mpLayouts[n]->FillDXArray(&aTempWidths, rStr);

            // calculate virtual char widths using most probable fallback layout
            for (int i = 0; i < nCharCount; ++i)
            {
                // #i17359# restriction:
                // one char cannot be resolved from different fallbacks
                if ((*pCharWidths)[i] != 0)
                    continue;
                DeviceCoordinate nCharWidth = aTempWidths[i];
                if (!nCharWidth)
                    continue;
                (*pCharWidths)[i] = nCharWidth;
            }
        }
    }

    return nMaxWidth;
    return GetTextWidth();
}

void MultiSalLayout::GetCaretPositions( int nMaxIndex, sal_Int32* pCaretXArray ) const