tdf#104921: Cleanup Kashida insertion logic

Communicate Kashida insertion positions in an explicit way.

Rest of LibreOffice communicate adjustments to character widths (e.g.
for justification or spacing) using so-called DX array. DX array is an
array of absolute character positions (e.g. DX[n] is the position after
character n from the start of the lines, and its widths is DX[n] -
DX[n-1]).

This DX array is modified also when Kashidas are inserted after a given
character for Arabic justification, by expanding its width. VCL would
use this to know where to insert the Kashidas and how many ones.

But because DX array is used for both widths adjustments and kashida
insertion, this turns out to be a source of bugs since VCL has tosecond
guess the DX array to find which is pure width adjustment and which also
involves Kashida insertion, and the heuristics it uses are fragile.

This change adds a second array of booleans that records where Kashida
is inserted and communicates it all the way from where Kashida insertion
is decoded in Writer and down to VCL layout.

This change passes the Kashida array only when it seems necessary (e.g.
during drawing but not when measuring text since the DX array is enough
in this case). Hopefully no places where Kashida insertion needs to be
passed down were missed.

A couple of glyph and layout flags that were used for old heuristics and
no longer needed and are removed.

This also fixes:
tdf#87731
tdf#106309
tdf#108604
tdf#112849
tdf#114257
tdf#127176
tdf#145647
tdf#146199

Change-Id: I4ed0850ef2fdc3e9143341afac649e7e7d463c39
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/138068
Tested-by: Jenkins
Reviewed-by: Caolán McNamara <caolanm@redhat.com>
diff --git a/canvas/source/cairo/cairo_textlayout.cxx b/canvas/source/cairo/cairo_textlayout.cxx
index cbbf02c..2b48dd9 100644
--- a/canvas/source/cairo/cairo_textlayout.cxx
+++ b/canvas/source/cairo/cairo_textlayout.cxx
@@ -270,7 +270,7 @@ namespace cairocanvas

        if (maLogicalAdvancements.hasElements())
        {
            rOutDev.DrawTextArray( rOutpos, maText.Text, aOffsets,
            rOutDev.DrawTextArray( rOutpos, maText.Text, aOffsets, {},
                                   ::canvas::tools::numeric_cast<sal_uInt16>(maText.StartPosition),
                                   ::canvas::tools::numeric_cast<sal_uInt16>(maText.Length) );
        }
diff --git a/canvas/source/directx/dx_textlayout_drawhelper.cxx b/canvas/source/directx/dx_textlayout_drawhelper.cxx
index 20ff8bd4..9e83b77 100644
--- a/canvas/source/directx/dx_textlayout_drawhelper.cxx
+++ b/canvas/source/directx/dx_textlayout_drawhelper.cxx
@@ -215,6 +215,7 @@ namespace dxcanvas
                xVirtualDevice->DrawTextArray( aEmptyPoint,
                                              aText,
                                              DXArray,
                                              {},
                                              rText.StartPosition,
                                              rText.Length,
                                              bIsRTL ? SalLayoutFlags::BiDiRtl : SalLayoutFlags::NONE);
diff --git a/canvas/source/vcl/textlayout.cxx b/canvas/source/vcl/textlayout.cxx
index 63a3453..f628d15 100644
--- a/canvas/source/vcl/textlayout.cxx
+++ b/canvas/source/vcl/textlayout.cxx
@@ -344,6 +344,7 @@ namespace vclcanvas
            rOutDev.DrawTextArray( rOutpos,
                                   maText.Text,
                                   aOffsets,
                                   {},
                                   ::canvas::tools::numeric_cast<sal_uInt16>(maText.StartPosition),
                                   ::canvas::tools::numeric_cast<sal_uInt16>(maText.Length) );
        }
diff --git a/drawinglayer/source/primitive2d/textbreakuphelper.cxx b/drawinglayer/source/primitive2d/textbreakuphelper.cxx
index 5ca10ce..8f92d98 100644
--- a/drawinglayer/source/primitive2d/textbreakuphelper.cxx
+++ b/drawinglayer/source/primitive2d/textbreakuphelper.cxx
@@ -58,6 +58,7 @@ namespace drawinglayer::primitive2d
            // prepare values for new portion
            basegfx::B2DHomMatrix aNewTransform;
            std::vector< double > aNewDXArray;
            std::vector< sal_Bool > aNewKashidaArray;
            const bool bNewStartIsNotOldStart(nIndex > mrSource.getTextPosition());

            if(!mbNoDXArray)
@@ -68,6 +69,13 @@ namespace drawinglayer::primitive2d
                    mrSource.getDXArray().begin() + ((nIndex + nLength) - mrSource.getTextPosition()));
            }

            if(!mbNoDXArray && !mrSource.getKashidaArray().empty())
            {
                aNewKashidaArray = std::vector< sal_Bool >(
                    mrSource.getKashidaArray().begin() + (nIndex - mrSource.getTextPosition()),
                    mrSource.getKashidaArray().begin() + ((nIndex + nLength) - mrSource.getTextPosition()));
            }

            if(bNewStartIsNotOldStart)
            {
                // needs to be moved to a new start position
@@ -137,6 +145,7 @@ namespace drawinglayer::primitive2d
                        nIndex,
                        nLength,
                        std::move(aNewDXArray),
                        std::move(aNewKashidaArray),
                        mrSource.getFontAttribute(),
                        mrSource.getLocale(),
                        mrSource.getFontColor(),
@@ -168,6 +177,7 @@ namespace drawinglayer::primitive2d
                        nIndex,
                        nLength,
                        std::move(aNewDXArray),
                        std::move(aNewKashidaArray),
                        mrSource.getFontAttribute(),
                        mrSource.getLocale(),
                        mrSource.getFontColor()));
diff --git a/drawinglayer/source/primitive2d/textdecoratedprimitive2d.cxx b/drawinglayer/source/primitive2d/textdecoratedprimitive2d.cxx
index 0db26fb..b14e699 100644
--- a/drawinglayer/source/primitive2d/textdecoratedprimitive2d.cxx
+++ b/drawinglayer/source/primitive2d/textdecoratedprimitive2d.cxx
@@ -36,6 +36,7 @@ namespace drawinglayer::primitive2d
            sal_Int32 nTextPosition,
            sal_Int32 nTextLength,
            const std::vector< double >& rDXArray,
            const std::vector< sal_Bool >& rKashidaArray,
            const attribute::FontAttribute& rFontAttribute) const
        {
            // create the SimpleTextPrimitive needed in any case
@@ -46,6 +47,7 @@ namespace drawinglayer::primitive2d
                    nTextPosition,
                    nTextLength,
                    std::vector(rDXArray),
                    std::vector(rKashidaArray),
                    rFontAttribute,
                    getLocale(),
                    getFontColor())));
@@ -189,7 +191,7 @@ namespace drawinglayer::primitive2d
                getFontAttribute().getBiDiStrong());

            // handle as one word
            impCreateGeometryContent(aRetval, aDecTrans, getText(), getTextPosition(), getTextLength(), getDXArray(), aNewFontAttribute);
            impCreateGeometryContent(aRetval, aDecTrans, getText(), getTextPosition(), getTextLength(), getDXArray(), getKashidaArray(), aNewFontAttribute);

            // Handle Shadow, Outline and TextRelief
            if(!aRetval.empty())
@@ -294,6 +296,7 @@ namespace drawinglayer::primitive2d
            sal_Int32 nTextPosition,
            sal_Int32 nTextLength,
            std::vector< double >&& rDXArray,
            std::vector< sal_Bool >&& rKashidaArray,
            const attribute::FontAttribute& rFontAttribute,
            const css::lang::Locale& rLocale,
            const basegfx::BColor& rFontColor,
@@ -312,7 +315,7 @@ namespace drawinglayer::primitive2d
            bool bEmphasisMarkBelow,
            TextRelief eTextRelief,
            bool bShadow)
        :   TextSimplePortionPrimitive2D(rNewTransform, rText, nTextPosition, nTextLength, std::move(rDXArray), rFontAttribute, rLocale, rFontColor, false, 0, rFillColor),
        :   TextSimplePortionPrimitive2D(rNewTransform, rText, nTextPosition, nTextLength, std::move(rDXArray), std::move(rKashidaArray), rFontAttribute, rLocale, rFontColor, false, 0, rFillColor),
            maOverlineColor(rOverlineColor),
            maTextlineColor(rTextlineColor),
            meFontOverline(eFontOverline),
diff --git a/drawinglayer/source/primitive2d/textlayoutdevice.cxx b/drawinglayer/source/primitive2d/textlayoutdevice.cxx
index f70f9f6..78e0c23 100644
--- a/drawinglayer/source/primitive2d/textlayoutdevice.cxx
+++ b/drawinglayer/source/primitive2d/textlayoutdevice.cxx
@@ -215,8 +215,8 @@ double TextLayouterDevice::getTextWidth(const OUString& rText, sal_uInt32 nIndex

void TextLayouterDevice::getTextOutlines(basegfx::B2DPolyPolygonVector& rB2DPolyPolyVector,
                                         const OUString& rText, sal_uInt32 nIndex,
                                         sal_uInt32 nLength,
                                         const std::vector<double>& rDXArray) const
                                         sal_uInt32 nLength, const std::vector<double>& rDXArray,
                                         const std::vector<sal_Bool>& rKashidaArray) const
{
    const sal_uInt32 nDXArrayCount(rDXArray.size());
    sal_uInt32 nTextLength(nLength);
@@ -239,7 +239,7 @@ void TextLayouterDevice::getTextOutlines(basegfx::B2DPolyPolygonVector& rB2DPoly
        }

        mrDevice.GetTextOutlines(rB2DPolyPolyVector, rText, nIndex, nIndex, nLength, 0,
                                 aIntegerDXArray);
                                 aIntegerDXArray, rKashidaArray);
    }
    else
    {
diff --git a/drawinglayer/source/primitive2d/textprimitive2d.cxx b/drawinglayer/source/primitive2d/textprimitive2d.cxx
index 6330c89..f60f73b 100644
--- a/drawinglayer/source/primitive2d/textprimitive2d.cxx
+++ b/drawinglayer/source/primitive2d/textprimitive2d.cxx
@@ -132,13 +132,13 @@ void TextSimplePortionPrimitive2D::getTextOutlinesAndTransformation(

        // get the text outlines
        aTextLayouter.getTextOutlines(rTarget, getText(), getTextPosition(), getTextLength(),
                                      aScaledDXArray);
                                      aScaledDXArray, getKashidaArray());
    }
    else
    {
        // get the text outlines
        aTextLayouter.getTextOutlines(rTarget, getText(), getTextPosition(), getTextLength(),
                                      getDXArray());
                                      getDXArray(), getKashidaArray());
    }

    // create primitives for the outlines
@@ -202,14 +202,16 @@ void TextSimplePortionPrimitive2D::create2DDecomposition(

TextSimplePortionPrimitive2D::TextSimplePortionPrimitive2D(
    basegfx::B2DHomMatrix rNewTransform, OUString rText, sal_Int32 nTextPosition,
    sal_Int32 nTextLength, std::vector<double>&& rDXArray, attribute::FontAttribute aFontAttribute,
    css::lang::Locale aLocale, const basegfx::BColor& rFontColor, bool bFilled,
    tools::Long nWidthToFill, const Color& rTextFillColor)
    sal_Int32 nTextLength, std::vector<double>&& rDXArray, std::vector<sal_Bool>&& rKashidaArray,
    attribute::FontAttribute aFontAttribute, css::lang::Locale aLocale,
    const basegfx::BColor& rFontColor, bool bFilled, tools::Long nWidthToFill,
    const Color& rTextFillColor)
    : maTextTransform(std::move(rNewTransform))
    , maText(std::move(rText))
    , mnTextPosition(nTextPosition)
    , mnTextLength(nTextLength)
    , maDXArray(std::move(rDXArray))
    , maKashidaArray(std::move(rKashidaArray))
    , maFontAttribute(std::move(aFontAttribute))
    , maLocale(std::move(aLocale))
    , maFontColor(rFontColor)
@@ -241,6 +243,7 @@ bool TextSimplePortionPrimitive2D::operator==(const BasePrimitive2D& rPrimitive)
                && getTextPosition() == rCompare.getTextPosition()
                && getTextLength() == rCompare.getTextLength()
                && getDXArray() == rCompare.getDXArray()
                && getKashidaArray() == rCompare.getKashidaArray()
                && getFontAttribute() == rCompare.getFontAttribute()
                && LocalesAreEqual(getLocale(), rCompare.getLocale())
                && getFontColor() == rCompare.getFontColor() && mbFilled == rCompare.mbFilled
diff --git a/drawinglayer/source/primitive2d/textstrikeoutprimitive2d.cxx b/drawinglayer/source/primitive2d/textstrikeoutprimitive2d.cxx
index f7aedb3..269be2d 100644
--- a/drawinglayer/source/primitive2d/textstrikeoutprimitive2d.cxx
+++ b/drawinglayer/source/primitive2d/textstrikeoutprimitive2d.cxx
@@ -97,6 +97,7 @@ namespace drawinglayer::primitive2d
                    0,
                    len,
                    std::move(aDXArray),
                    {},
                    getFontAttribute(),
                    getLocale(),
                    getFontColor()));
diff --git a/drawinglayer/source/processor2d/vclprocessor2d.cxx b/drawinglayer/source/processor2d/vclprocessor2d.cxx
index 019feba..8228008 100644
--- a/drawinglayer/source/processor2d/vclprocessor2d.cxx
+++ b/drawinglayer/source/processor2d/vclprocessor2d.cxx
@@ -321,7 +321,8 @@ void VclProcessor2D::RenderTextSimpleOrDecoratedPortionPrimitive2D(

            if (!aTransformedDXArray.empty())
            {
                mpOutputDevice->DrawTextArray(aStartPoint, aText, aTransformedDXArray, nPos, nLen);
                mpOutputDevice->DrawTextArray(aStartPoint, aText, aTransformedDXArray,
                                              rTextCandidate.getKashidaArray(), nPos, nLen);
            }
            else
            {
diff --git a/drawinglayer/source/tools/emfphelperdata.cxx b/drawinglayer/source/tools/emfphelperdata.cxx
index 5422595..e9b8422 100644
--- a/drawinglayer/source/tools/emfphelperdata.cxx
+++ b/drawinglayer/source/tools/emfphelperdata.cxx
@@ -1683,6 +1683,7 @@ namespace emfplushelper
                                            0,             // text always starts at 0
                                            stringLength,
                                            std::move(emptyVector),   // EMF-PLUS has no DX-array
                                            {},
                                            fontAttribute,
                                            locale,
                                            color.getBColor(), // Font Color
@@ -1702,6 +1703,7 @@ namespace emfplushelper
                                            0,             // text always starts at 0
                                            stringLength,
                                            std::move(emptyVector),   // EMF-PLUS has no DX-array
                                            {},
                                            fontAttribute,
                                            locale,
                                            color.getBColor());
@@ -2195,6 +2197,7 @@ namespace emfplushelper
                                                    pos,            // take character at current pos
                                                    aLength,        // use determined length
                                                    std::move(aDXArray),       // generated DXArray
                                                    {},
                                                    fontAttribute,
                                                    Application::GetSettings().GetLanguageTag().getLocale(),
                                                    color.getBColor(),
@@ -2214,6 +2217,7 @@ namespace emfplushelper
                                                    pos,            // take character at current pos
                                                    aLength,        // use determined length
                                                    std::move(aDXArray),       // generated DXArray
                                                    {},
                                                    fontAttribute,
                                                    Application::GetSettings().GetLanguageTag().getLocale(),
                                                    color.getBColor());
diff --git a/drawinglayer/source/tools/wmfemfhelper.cxx b/drawinglayer/source/tools/wmfemfhelper.cxx
index f763cd8..4ec6863 100644
--- a/drawinglayer/source/tools/wmfemfhelper.cxx
+++ b/drawinglayer/source/tools/wmfemfhelper.cxx
@@ -1077,6 +1077,7 @@ namespace wmfemfhelper
        sal_uInt16 nTextStart,
        sal_uInt16 nTextLength,
        std::vector< double >&& rDXArray,
        std::vector< sal_Bool >&& rKashidaArray,
        TargetHolder& rTarget,
        PropertyHolder const & rProperty)
    {
@@ -1161,6 +1162,7 @@ namespace wmfemfhelper
                    nTextStart,
                    nTextLength,
                    std::move(rDXArray),
                    std::move(rKashidaArray),
                    aFontAttribute,
                    aLocale,
                    aFontColor,
@@ -1189,6 +1191,7 @@ namespace wmfemfhelper
                    nTextStart,
                    nTextLength,
                    std::vector(rDXArray),
                    std::vector(rKashidaArray),
                    std::move(aFontAttribute),
                    std::move(aLocale),
                    aFontColor);
@@ -1770,6 +1773,7 @@ namespace wmfemfhelper
                            nTextIndex,
                            nTextLength,
                            std::move(aDXArray),
                            {},
                            rTargetHolders.Current(),
                            rPropertyHolders.Current());
                    }
@@ -1794,6 +1798,7 @@ namespace wmfemfhelper
                        // prepare DXArray (if used)
                        std::vector< double > aDXArray;
                        const std::vector<sal_Int32> & rDXArray = pA->GetDXArray();
                        std::vector< sal_Bool > aKashidaArray = pA->GetKashidaArray();

                        if(!rDXArray.empty())
                        {
@@ -1811,6 +1816,7 @@ namespace wmfemfhelper
                            nTextIndex,
                            nTextLength,
                            std::move(aDXArray),
                            std::move(aKashidaArray),
                            rTargetHolders.Current(),
                            rPropertyHolders.Current());
                    }
@@ -1874,6 +1880,7 @@ namespace wmfemfhelper
                            nTextIndex,
                            nTextLength,
                            std::move(aTextArray),
                            {},
                            rTargetHolders.Current(),
                            rPropertyHolders.Current());
                    }
diff --git a/editeng/inc/editdoc.hxx b/editeng/inc/editdoc.hxx
index 6ce00d0..25a3dca 100644
--- a/editeng/inc/editdoc.hxx
+++ b/editeng/inc/editdoc.hxx
@@ -463,6 +463,7 @@ public:

private:
    CharPosArrayType aPositions;
    std::vector<sal_Bool> aKashidaPositions;
    sal_Int32          nTxtWidth;
    sal_Int32          nStartPosX;
    sal_Int32          nStart;     // could be replaced by nStartPortion
@@ -531,6 +532,9 @@ public:
    CharPosArrayType& GetCharPosArray() { return aPositions;}
    const CharPosArrayType& GetCharPosArray() const { return aPositions;}

    std::vector<sal_Bool>& GetKashidaArray() { return aKashidaPositions; }
    const std::vector<sal_Bool>& GetKashidaArray() const { return aKashidaPositions; }

    EditLine*       Clone() const;

    EditLine&   operator = ( const EditLine& rLine );
diff --git a/editeng/source/editeng/editeng.cxx b/editeng/source/editeng/editeng.cxx
index db4ae5d..88bc04a 100644
--- a/editeng/source/editeng/editeng.cxx
+++ b/editeng/source/editeng/editeng.cxx
@@ -2465,7 +2465,8 @@ css::uno::Reference< css::datatransfer::XTransferable >
// ======================    Virtual Methods    ========================

void EditEngine::DrawingText( const Point&, const OUString&, sal_Int32, sal_Int32,
                              o3tl::span<const sal_Int32>, const SvxFont&, sal_Int32 /*nPara*/, sal_uInt8 /*nRightToLeft*/,
                              o3tl::span<const sal_Int32>, o3tl::span<const sal_Bool>,
                              const SvxFont&, sal_Int32 /*nPara*/, sal_uInt8 /*nRightToLeft*/,
                              const EEngineData::WrongSpellVector*, const SvxFieldData*, bool, bool,
                              const css::lang::Locale*, const Color&, const Color&)

diff --git a/editeng/source/editeng/impedit3.cxx b/editeng/source/editeng/impedit3.cxx
index 143a763..9d71560 100644
--- a/editeng/source/editeng/impedit3.cxx
+++ b/editeng/source/editeng/impedit3.cxx
@@ -2147,6 +2147,10 @@ void ImpEditEngine::ImpAdjustBlocks( ParaPortion* pParaPortion, EditLine* pLine,
        nLastScript = nScript;
    }

    // Save the number of blanks, we will use it below when marking Kashida
    // positions.
    auto nBlankSize = aPositions.size();

    // Kashidas ?
    ImpFindKashidas( pNode, nFirstChar, nLastChar, aPositions );

@@ -2186,6 +2190,19 @@ void ImpEditEngine::ImpAdjustBlocks( ParaPortion* pParaPortion, EditLine* pLine,
    DBG_ASSERT( nSomeExtraSpace < static_cast<tools::Long>(nGaps), "AdjustBlocks: ExtraSpace too large" );
    DBG_ASSERT( nSomeExtraSpace >= 0, "AdjustBlocks: ExtraSpace < 0 " );

    // Mark Kashida positions, so that VCL knows where to insert Kashida and
    // where to only expand the width.
    if (aPositions.size() > nBlankSize)
    {
        pLine->GetKashidaArray().resize(pLine->GetCharPosArray().size(), false);
        for (auto i = nBlankSize; i < aPositions.size(); i++)
        {
            auto nChar = aPositions[i];
            if ( nChar < nLastChar )
                pLine->GetKashidaArray()[nChar-nFirstChar] = 1 /*sal_True*/;
        }
    }

    // Correct the positions in the Array and the portion widths:
    // Last character won't be considered...
    for (auto const& nChar : aPositions)
@@ -2202,7 +2219,6 @@ void ImpEditEngine::ImpAdjustBlocks( ParaPortion* pParaPortion, EditLine* pLine,
                rLastPortion.GetSize().AdjustWidth( 1 );

            // Correct positions in array
            // Even for kashidas just change positions, VCL will then draw the kashida automatically
            sal_Int32 nPortionEnd = nPortionStart + rLastPortion.GetLen();
            for ( sal_Int32 _n = nChar; _n < nPortionEnd; _n++ )
            {
@@ -3293,6 +3309,7 @@ void ImpEditEngine::Paint( OutputDevice& rOutDev, tools::Rectangle aClipRect, Po
                                sal_Int32 nTextStart = 0;
                                sal_Int32 nTextLen = 0;
                                o3tl::span<const sal_Int32> pDXArray;
                                o3tl::span<const sal_Bool> pKashidaArray;
                                std::vector<sal_Int32> aTmpDXArray;

                                if ( rTextPortion.GetKind() == PortionKind::TEXT )
@@ -3303,6 +3320,12 @@ void ImpEditEngine::Paint( OutputDevice& rOutDev, tools::Rectangle aClipRect, Po
                                    pDXArray = o3tl::span(pLine->GetCharPosArray().data() + (nIndex - pLine->GetStart()),
                                                    pLine->GetCharPosArray().size() - (nIndex - pLine->GetStart()));

                                    if (!pLine->GetKashidaArray().empty())
                                    {
                                        pKashidaArray = o3tl::span(pLine->GetKashidaArray().data() + (nIndex - pLine->GetStart()),
                                                    pLine->GetKashidaArray().size() - (nIndex - pLine->GetStart()));
                                    }

                                    // Paint control characters (#i55716#)
                                    /* XXX: Given that there's special handling
                                     * only for some specific characters
@@ -3552,7 +3575,7 @@ void ImpEditEngine::Paint( OutputDevice& rOutDev, tools::Rectangle aClipRect, Po
                                        ImplCalcDigitLang(aTmpFont.GetLanguage()));

                                    // StripPortions() data callback
                                    GetEditEnginePtr()->DrawingText( aOutPos, aText, nTextStart, nTextLen, pDXArray,
                                    GetEditEnginePtr()->DrawingText( aOutPos, aText, nTextStart, nTextLen, pDXArray, pKashidaArray,
                                        aTmpFont, n, rTextPortion.GetRightToLeftLevel(),
                                        !aWrongSpellVector.empty() ? &aWrongSpellVector : nullptr,
                                        pFieldData,
@@ -3656,7 +3679,7 @@ void ImpEditEngine::Paint( OutputDevice& rOutDev, tools::Rectangle aClipRect, Po
                                            --nTextLen;

                                        // output directly
                                        aTmpFont.QuickDrawText( &rOutDev, aRealOutPos, aText, nTextStart, nTextLen, pDXArray );
                                        aTmpFont.QuickDrawText( &rOutDev, aRealOutPos, aText, nTextStart, nTextLen, pDXArray, pKashidaArray );

                                        if ( bDrawFrame )
                                        {
@@ -3792,7 +3815,7 @@ void ImpEditEngine::Paint( OutputDevice& rOutDev, tools::Rectangle aClipRect, Po
                                    const Color aTextLineColor(rOutDev.GetTextLineColor());

                                    GetEditEnginePtr()->DrawingText(
                                        aTmpPos, OUString(), 0, 0, {},
                                        aTmpPos, OUString(), 0, 0, {}, {},
                                        aTmpFont, n, 0,
                                        nullptr,
                                        nullptr,
@@ -3841,7 +3864,7 @@ void ImpEditEngine::Paint( OutputDevice& rOutDev, tools::Rectangle aClipRect, Po
                const Color aTextLineColor(rOutDev.GetTextLineColor());

                GetEditEnginePtr()->DrawingText(
                    aTmpPos, OUString(), 0, 0, {},
                    aTmpPos, OUString(), 0, 0, {}, {},
                    aTmpFont, n, 0,
                    nullptr,
                    nullptr,
diff --git a/editeng/source/items/svxfont.cxx b/editeng/source/items/svxfont.cxx
index ca8c5f3..ac36087 100644
--- a/editeng/source/items/svxfont.cxx
+++ b/editeng/source/items/svxfont.cxx
@@ -545,21 +545,24 @@ Size SvxFont::GetTextSize(const OutputDevice& rOut, const OUString &rTxt,

static void DrawTextArray( OutputDevice* pOut, const Point& rStartPt, const OUString& rStr,
                           o3tl::span<const sal_Int32> pDXAry,
                           o3tl::span<const sal_Bool> pKashidaAry,
                           sal_Int32 nIndex, sal_Int32 nLen )
{
    const SalLayoutGlyphs* layoutGlyphs = SalLayoutGlyphsCache::self()->GetLayoutGlyphs(pOut, rStr, nIndex, nLen);
    pOut->DrawTextArray(rStartPt, rStr, pDXAry, nIndex, nLen, SalLayoutFlags::NONE, layoutGlyphs);
    pOut->DrawTextArray(rStartPt, rStr, pDXAry, pKashidaAry, nIndex, nLen, SalLayoutFlags::NONE, layoutGlyphs);
}

void SvxFont::QuickDrawText( OutputDevice *pOut,
    const Point &rPos, const OUString &rTxt,
    const sal_Int32 nIdx, const sal_Int32 nLen, o3tl::span<const sal_Int32> pDXArray ) const
    const sal_Int32 nIdx, const sal_Int32 nLen,
    o3tl::span<const sal_Int32> pDXArray,
    o3tl::span<const sal_Bool> pKashidaArray) const
{

    // Font has to be selected in OutputDevice...
    if ( !IsCaseMap() && !IsCapital() && !IsKern() && !IsEsc() )
    {
        DrawTextArray( pOut, rPos, rTxt, pDXArray, nIdx, nLen );
        DrawTextArray( pOut, rPos, rTxt, pDXArray, pKashidaArray, nIdx, nLen );
        return;
    }

@@ -596,9 +599,9 @@ void SvxFont::QuickDrawText( OutputDevice *pOut,
        else
        {
            if ( !IsCaseMap() )
                DrawTextArray( pOut, aPos, rTxt, pDXArray, nIdx, nLen );
                DrawTextArray( pOut, aPos, rTxt, pDXArray, pKashidaArray, nIdx, nLen );
            else
                DrawTextArray( pOut, aPos, CalcCaseMap( rTxt ), pDXArray, nIdx, nLen );
                DrawTextArray( pOut, aPos, CalcCaseMap( rTxt ), pDXArray, pKashidaArray, nIdx, nLen );
        }
    }
}
diff --git a/editeng/source/outliner/outleeng.cxx b/editeng/source/outliner/outleeng.cxx
index e4fc414..275636b 100644
--- a/editeng/source/outliner/outleeng.cxx
+++ b/editeng/source/outliner/outleeng.cxx
@@ -141,7 +141,8 @@ OUString OutlinerEditEng::GetUndoComment( sal_uInt16 nUndoId ) const
}

void OutlinerEditEng::DrawingText( const Point& rStartPos, const OUString& rText, sal_Int32 nTextStart, sal_Int32 nTextLen,
                                   o3tl::span<const sal_Int32> pDXArray, const SvxFont& rFont, sal_Int32 nPara, sal_uInt8 nRightToLeft,
                                   o3tl::span<const sal_Int32> pDXArray, o3tl::span<const sal_Bool> pKashidaArray,
                                   const SvxFont& rFont, sal_Int32 nPara, sal_uInt8 nRightToLeft,
                                   const EEngineData::WrongSpellVector* pWrongSpellVector,
                                   const SvxFieldData* pFieldData,
                                   bool bEndOfLine,
@@ -150,7 +151,7 @@ void OutlinerEditEng::DrawingText( const Point& rStartPos, const OUString& rText
                                   const Color& rOverlineColor,
                                   const Color& rTextLineColor)
{
    pOwner->DrawingText(rStartPos,rText,nTextStart,nTextLen,pDXArray,rFont,nPara,nRightToLeft,
    pOwner->DrawingText(rStartPos,rText,nTextStart,nTextLen,pDXArray,pKashidaArray,rFont,nPara,nRightToLeft,
        pWrongSpellVector, pFieldData, bEndOfLine, bEndOfParagraph, false/*bEndOfBullet*/, pLocale, rOverlineColor, rTextLineColor);
}

diff --git a/editeng/source/outliner/outleeng.hxx b/editeng/source/outliner/outleeng.hxx
index 963e745..d19b54e 100644
--- a/editeng/source/outliner/outleeng.hxx
+++ b/editeng/source/outliner/outleeng.hxx
@@ -44,7 +44,8 @@ public:
    virtual void        ParagraphConnected( sal_Int32 nLeftParagraph, sal_Int32 nRightParagraph ) override;

    virtual void DrawingText( const Point& rStartPos, const OUString& rText, sal_Int32 nTextStart,
                              sal_Int32 nTextLen, o3tl::span<const sal_Int32> pDXArray, const SvxFont& rFont,
                              sal_Int32 nTextLen, o3tl::span<const sal_Int32> pDXArray,
                              o3tl::span<const sal_Bool> pKashidaArray, const SvxFont& rFont,
                              sal_Int32 nPara, sal_uInt8 nRightToLeft,
                              const EEngineData::WrongSpellVector* pWrongSpellVector,
                              const SvxFieldData* pFieldData,
diff --git a/editeng/source/outliner/outliner.cxx b/editeng/source/outliner/outliner.cxx
index 4d67810..d7ea276 100644
--- a/editeng/source/outliner/outliner.cxx
+++ b/editeng/source/outliner/outliner.cxx
@@ -974,7 +974,7 @@ void Outliner::PaintBullet(sal_Int32 nPara, const Point& rStartPos, const Point&
                    aTextPos.AdjustY( -(aMetric.GetDescent()) );
                }

                DrawingText(aTextPos, pPara->GetText(), 0, pPara->GetText().getLength(), aBuf,
                DrawingText(aTextPos, pPara->GetText(), 0, pPara->GetText().getLength(), aBuf, {},
                    aSvxFont, nPara, bRightToLeftPara ? 1 : 0, nullptr, nullptr, false, false, true, nullptr, Color(), Color());
            }
            else
@@ -1650,7 +1650,8 @@ void Outliner::StripPortions()
}

void Outliner::DrawingText( const Point& rStartPos, const OUString& rText, sal_Int32 nTextStart,
                            sal_Int32 nTextLen, o3tl::span<const sal_Int32> pDXArray,const SvxFont& rFont,
                            sal_Int32 nTextLen, o3tl::span<const sal_Int32> pDXArray,
                            o3tl::span<const sal_Bool> pKashidaArray, const SvxFont& rFont,
                            sal_Int32 nPara, sal_uInt8 nRightToLeft,
                            const EEngineData::WrongSpellVector* pWrongSpellVector,
                            const SvxFieldData* pFieldData,
@@ -1663,7 +1664,7 @@ void Outliner::DrawingText( const Point& rStartPos, const OUString& rText, sal_I
{
    if(aDrawPortionHdl.IsSet())
    {
        DrawPortionInfo aInfo( rStartPos, rText, nTextStart, nTextLen, rFont, nPara, pDXArray, pWrongSpellVector,
        DrawPortionInfo aInfo( rStartPos, rText, nTextStart, nTextLen, rFont, nPara, pDXArray, pKashidaArray, pWrongSpellVector,
            pFieldData, pLocale, rOverlineColor, rTextLineColor, nRightToLeft, false, 0, bEndOfLine, bEndOfParagraph, bEndOfBullet);

        aDrawPortionHdl.Call( &aInfo );
@@ -1676,7 +1677,7 @@ void Outliner::DrawingTab( const Point& rStartPos, tools::Long nWidth, const OUS
{
    if(aDrawPortionHdl.IsSet())
    {
        DrawPortionInfo aInfo( rStartPos, rChar, 0, rChar.getLength(), rFont, nPara, {}, nullptr,
        DrawPortionInfo aInfo( rStartPos, rChar, 0, rChar.getLength(), rFont, nPara, {}, {}, nullptr,
            nullptr, nullptr, rOverlineColor, rTextLineColor, nRightToLeft, true, nWidth, bEndOfLine, bEndOfParagraph, false);

        aDrawPortionHdl.Call( &aInfo );
diff --git a/emfio/source/reader/mtftools.cxx b/emfio/source/reader/mtftools.cxx
index 67dfdbe..95c5118 100644
--- a/emfio/source/reader/mtftools.cxx
+++ b/emfio/source/reader/mtftools.cxx
@@ -1828,7 +1828,7 @@ namespace emfio
                {
                    Point aCharDisplacement( i ? (*pDXArry)[i-1] : 0, i ? pDYArry[i-1] : 0 );
                    Point().RotateAround(aCharDisplacement, maFont.GetOrientation());
                    mpGDIMetaFile->AddAction( new MetaTextArrayAction( rPosition + aCharDisplacement, OUString( rText[i] ), o3tl::span<const sal_Int32>{}, 0, 1 ) );
                    mpGDIMetaFile->AddAction( new MetaTextArrayAction( rPosition + aCharDisplacement, OUString( rText[i] ), o3tl::span<const sal_Int32>{}, {}, 0, 1 ) );
                }
            }
            else
@@ -1859,7 +1859,7 @@ namespace emfio
                    pVDev->GetTextArray( rText, &aMyDXArray, 0, rText.getLength());
                    pDX = aMyDXArray;
                }
                mpGDIMetaFile->AddAction( new MetaTextArrayAction( rPosition, rText, pDX, 0, rText.getLength() ) );
                mpGDIMetaFile->AddAction( new MetaTextArrayAction( rPosition, rText, pDX, {}, 0, rText.getLength() ) );
            }
        }
        SetGfxMode( nOldGfxMode );
diff --git a/include/drawinglayer/primitive2d/textdecoratedprimitive2d.hxx b/include/drawinglayer/primitive2d/textdecoratedprimitive2d.hxx
index 1a4d821..73663ff 100644
--- a/include/drawinglayer/primitive2d/textdecoratedprimitive2d.hxx
+++ b/include/drawinglayer/primitive2d/textdecoratedprimitive2d.hxx
@@ -63,6 +63,7 @@ namespace drawinglayer::primitive2d
                sal_Int32 nTextPosition,
                sal_Int32 nTextLength,
                const ::std::vector< double >& rDXArray,
                const ::std::vector< sal_Bool >& rKashidaArray,
                const attribute::FontAttribute& rFontAttribute) const;

            /// local decomposition.
@@ -77,6 +78,7 @@ namespace drawinglayer::primitive2d
                sal_Int32 nTextPosition,
                sal_Int32 nTextLength,
                std::vector< double >&& rDXArray,
                std::vector< sal_Bool >&& rKashidaArray,
                const attribute::FontAttribute& rFontAttribute,
                const css::lang::Locale& rLocale,
                const basegfx::BColor& rFontColor,
diff --git a/include/drawinglayer/primitive2d/textlayoutdevice.hxx b/include/drawinglayer/primitive2d/textlayoutdevice.hxx
index 9358776..6348de0 100644
--- a/include/drawinglayer/primitive2d/textlayoutdevice.hxx
+++ b/include/drawinglayer/primitive2d/textlayoutdevice.hxx
@@ -84,7 +84,8 @@ public:
    double getTextWidth(const OUString& rText, sal_uInt32 nIndex, sal_uInt32 nLength) const;

    void getTextOutlines(basegfx::B2DPolyPolygonVector&, const OUString& rText, sal_uInt32 nIndex,
                         sal_uInt32 nLength, const ::std::vector<double>& rDXArray) const;
                         sal_uInt32 nLength, const ::std::vector<double>& rDXArray,
                         const ::std::vector<sal_Bool>& rKashidaArray) const;

    basegfx::B2DRange getTextBoundRect(const OUString& rText, sal_uInt32 nIndex,
                                       sal_uInt32 nLength) const;
diff --git a/include/drawinglayer/primitive2d/textprimitive2d.hxx b/include/drawinglayer/primitive2d/textprimitive2d.hxx
index fd80e53..4182b0c 100644
--- a/include/drawinglayer/primitive2d/textprimitive2d.hxx
+++ b/include/drawinglayer/primitive2d/textprimitive2d.hxx
@@ -72,6 +72,9 @@ namespace drawinglayer::primitive2d
    point in FontCoordinateSystem X-Direction (given by maTextTransform) to the start
    point of the second character

    @param rKashidaArray
    The Kashida insertion positions.

    @param rFontAttribute
    The font definition

@@ -107,6 +110,9 @@ private:
    /// The DX array in logic units
    std::vector<double> maDXArray;

    /// The Kashida array
    std::vector<sal_Bool> maKashidaArray;

    /// The font definition
    attribute::FontAttribute maFontAttribute;

@@ -139,6 +145,7 @@ public:
    TextSimplePortionPrimitive2D(basegfx::B2DHomMatrix aNewTransform, OUString aText,
                                 sal_Int32 nTextPosition, sal_Int32 nTextLength,
                                 std::vector<double>&& rDXArray,
                                 std::vector<sal_Bool>&& rKashidaArray,
                                 attribute::FontAttribute aFontAttribute, css::lang::Locale aLocale,
                                 const basegfx::BColor& rFontColor, bool bFilled = false,
                                 tools::Long nWidthToFill = 0,
@@ -156,6 +163,7 @@ public:
    sal_Int32 getTextPosition() const { return mnTextPosition; }
    sal_Int32 getTextLength() const { return mnTextLength; }
    const ::std::vector<double>& getDXArray() const { return maDXArray; }
    const ::std::vector<sal_Bool>& getKashidaArray() const { return maKashidaArray; }
    const attribute::FontAttribute& getFontAttribute() const { return maFontAttribute; }
    const css::lang::Locale& getLocale() const { return maLocale; }
    const basegfx::BColor& getFontColor() const { return maFontColor; }
diff --git a/include/editeng/editeng.hxx b/include/editeng/editeng.hxx
index a0ce29c..ae09906 100644
--- a/include/editeng/editeng.hxx
+++ b/include/editeng/editeng.hxx
@@ -498,7 +498,9 @@ public:

    virtual void DrawingText( const Point& rStartPos, const OUString& rText,
                              sal_Int32 nTextStart, sal_Int32 nTextLen,
                              o3tl::span<const sal_Int32> pDXArray, const SvxFont& rFont,
                              o3tl::span<const sal_Int32> pDXArray,
                              o3tl::span<const sal_Bool> pKashidaArray,
                              const SvxFont& rFont,
                              sal_Int32 nPara, sal_uInt8 nRightToLeft,
                              const EEngineData::WrongSpellVector* pWrongSpellVector,
                              const SvxFieldData* pFieldData,
diff --git a/include/editeng/outliner.hxx b/include/editeng/outliner.hxx
index 6e19aa1..3326a3d 100644
--- a/include/editeng/outliner.hxx
+++ b/include/editeng/outliner.hxx
@@ -401,6 +401,7 @@ public:
    sal_Int32           mnPara;
    const SvxFont&      mrFont;
    o3tl::span<const sal_Int32> mpDXArray;
    o3tl::span<const sal_Bool> mpKashidaArray;

    const EEngineData::WrongSpellVector*  mpWrongSpellVector;
    const SvxFieldData* mpFieldData;
@@ -427,6 +428,7 @@ public:
        const SvxFont& rFnt,
        sal_Int32 nPar,
        o3tl::span<const sal_Int32> pDXArr,
        o3tl::span<const sal_Bool> pKashidaArr,
        const EEngineData::WrongSpellVector* pWrongSpellVector,
        const SvxFieldData* pFieldData,
        const css::lang::Locale* pLocale,
@@ -445,6 +447,7 @@ public:
        mnPara(nPar),
        mrFont(rFnt),
        mpDXArray(pDXArr),
        mpKashidaArray(pKashidaArr),
        mpWrongSpellVector(pWrongSpellVector),
        mpFieldData(pFieldData),
        mpLocale(pLocale),
@@ -818,7 +821,9 @@ public:

    void DrawingText( const Point& rStartPos, const OUString& rText,
                              sal_Int32 nTextStart, sal_Int32 nTextLen,
                              o3tl::span<const sal_Int32> pDXArray, const SvxFont& rFont,
                              o3tl::span<const sal_Int32> pDXArray,
                              o3tl::span<const sal_Bool> pKashidaArray,
                              const SvxFont& rFont,
                              sal_Int32 nPara, sal_uInt8 nRightToLeft,
                              const EEngineData::WrongSpellVector* pWrongSpellVector,
                              const SvxFieldData* pFieldData,
diff --git a/include/editeng/svxfont.hxx b/include/editeng/svxfont.hxx
index e0f92c5..ac4ec39 100644
--- a/include/editeng/svxfont.hxx
+++ b/include/editeng/svxfont.hxx
@@ -96,7 +96,8 @@ public:

    void QuickDrawText( OutputDevice *pOut, const Point &rPos, const OUString &rTxt,
                        const sal_Int32 nIdx = 0, const sal_Int32 nLen = SAL_MAX_INT32,
                        o3tl::span<const sal_Int32> pDXArray = {} ) const;
                        o3tl::span<const sal_Int32> pDXArray = {},
                        o3tl::span<const sal_Bool> pKashidaArray = {} ) const;

    Size QuickGetTextSize( const OutputDevice *pOut, const OUString &rTxt,
                           const sal_Int32 nIdx, const sal_Int32 nLen,
diff --git a/include/vcl/metaact.hxx b/include/vcl/metaact.hxx
index 37c3db3..11caf48 100644
--- a/include/vcl/metaact.hxx
+++ b/include/vcl/metaact.hxx
@@ -507,6 +507,7 @@ private:
    Point       maStartPt;
    OUString    maStr;
    std::vector<sal_Int32> maDXAry;
    std::vector<sal_Bool> maKashidaAry;
    sal_Int32   mnIndex;
    sal_Int32   mnLen;

@@ -516,10 +517,14 @@ public:
                        MetaTextArrayAction();
                        MetaTextArrayAction( const MetaTextArrayAction& rAction );
    MetaTextArrayAction( const Point& rStartPt, OUString aStr,
                         std::vector<sal_Int32>  rDXAry, sal_Int32 nIndex,
                         std::vector<sal_Int32> rDXAry,
                         std::vector<sal_Bool> pKashidaAry,
                         sal_Int32 nIndex,
                         sal_Int32 nLen );
    MetaTextArrayAction( const Point& rStartPt, OUString aStr,
                         o3tl::span<const sal_Int32> pDXAry, sal_Int32 nIndex,
                         o3tl::span<const sal_Int32> pDXAry,
                         o3tl::span<const sal_Bool> pKashidaAry,
                         sal_Int32 nIndex,
                         sal_Int32 nLen );

    virtual void        Execute( OutputDevice* pOut ) override;
@@ -534,11 +539,13 @@ public:
    sal_Int32       GetIndex() const { return mnIndex; }
    sal_Int32       GetLen() const { return mnLen; }
    const std::vector<sal_Int32> & GetDXArray() const { return maDXAry; }
    const std::vector<sal_Bool> & GetKashidaArray() const { return maKashidaAry; }
    void            SetPoint(const Point& rPt) { maStartPt = rPt; }
    void            SetText(const OUString& rStr) { maStr = rStr; }
    void            SetIndex(sal_Int32 rIndex) { mnIndex = rIndex; }
    void            SetLen(sal_Int32 rLen) { mnLen = rLen; }
    void            SetDXArray(std::vector<sal_Int32> aArray);
    void            SetKashidaArray(std::vector<sal_Bool> aArray);
};

class SAL_DLLPUBLIC_RTTI MetaStretchTextAction final : public MetaAction
diff --git a/include/vcl/outdev.hxx b/include/vcl/outdev.hxx
index 2595415..af55ebd 100644
--- a/include/vcl/outdev.hxx
+++ b/include/vcl/outdev.hxx
@@ -963,6 +963,7 @@ public:
    bool                        GetTextBoundRect( tools::Rectangle& rRect,
                                                  const OUString& rStr, sal_Int32 nBase = 0, sal_Int32 nIndex = 0, sal_Int32 nLen = -1,
                                                  sal_uLong nLayoutWidth = 0, o3tl::span<const sal_Int32> pDXArray = {},
                                                  o3tl::span<const sal_Bool> pKashidaArray = {},
                                                  const SalLayoutGlyphs* pGlyphs = nullptr ) const;

    tools::Rectangle            ImplGetTextBoundRect( const SalLayout& ) const;
@@ -973,12 +974,14 @@ public:
    bool                        GetTextOutlines( PolyPolyVector&,
                                                 const OUString& rStr, sal_Int32 nBase = 0, sal_Int32 nIndex = 0,
                                                 sal_Int32 nLen = -1,
                                                 sal_uLong nLayoutWidth = 0, o3tl::span<const sal_Int32> pDXArray = {} ) const;
                                                 sal_uLong nLayoutWidth = 0, o3tl::span<const sal_Int32> pDXArray = {},
                                                 o3tl::span<const sal_Bool> pKashidaArray = {} ) const;

    bool                        GetTextOutlines( basegfx::B2DPolyPolygonVector &rVector,
                                                 const OUString& rStr, sal_Int32 nBase, sal_Int32 nIndex = 0,
                                                 sal_Int32 nLen = -1,
                                                 sal_uLong nLayoutWidth = 0, o3tl::span<const sal_Int32> pDXArray = {} ) const;
                                                 sal_uLong nLayoutWidth = 0, o3tl::span<const sal_Int32> pDXArray = {},
                                                 o3tl::span<const sal_Bool> pKashidaArray = {} ) const;


    OUString                    GetEllipsisString( const OUString& rStr, tools::Long nMaxWidth,
@@ -1044,6 +1047,7 @@ public:

    void                        DrawTextArray( const Point& rStartPt, const OUString& rStr,
                                               o3tl::span<const sal_Int32> pDXAry,
                                               o3tl::span<const sal_Bool> pKashidaAry={},
                                               sal_Int32 nIndex = 0,
                                               sal_Int32 nLen = -1,
                                               SalLayoutFlags flags = SalLayoutFlags::NONE,
@@ -1236,7 +1240,9 @@ public:
    std::unique_ptr<SalLayout>
                                ImplLayout( const OUString&, sal_Int32 nIndex, sal_Int32 nLen,
                                            const Point& rLogicPos = Point(0,0), tools::Long nLogicWidth=0,
                                            o3tl::span<const sal_Int32> pLogicDXArray={}, SalLayoutFlags flags = SalLayoutFlags::NONE,
                                            o3tl::span<const sal_Int32> pLogicDXArray={},
                                            o3tl::span<const sal_Bool> pKashidaArray={},
                                            SalLayoutFlags flags = SalLayoutFlags::NONE,
                                            vcl::text::TextLayoutCache const* = nullptr,
                                            const SalLayoutGlyphs* pGlyphs = nullptr) const;

diff --git a/include/vcl/pdfwriter.hxx b/include/vcl/pdfwriter.hxx
index 8764f3f..0c2cbb6 100644
--- a/include/vcl/pdfwriter.hxx
+++ b/include/vcl/pdfwriter.hxx
@@ -764,6 +764,7 @@ The following structure describes the permissions used in PDF security
                                      FontLineStyle eOverline );
    void                DrawTextArray( const Point& rStartPt, const OUString& rStr,
                                       o3tl::span<const sal_Int32> pDXAry,
                                       o3tl::span<const sal_Bool> pKashidaAry,
                                       sal_Int32 nIndex,
                                       sal_Int32 nLen );
    void                DrawStretchText( const Point& rStartPt, sal_uLong nWidth,
diff --git a/include/vcl/rendercontext/SalLayoutFlags.hxx b/include/vcl/rendercontext/SalLayoutFlags.hxx
index 68c26ca..e8c5901 100644
--- a/include/vcl/rendercontext/SalLayoutFlags.hxx
+++ b/include/vcl/rendercontext/SalLayoutFlags.hxx
@@ -30,13 +30,12 @@ enum class SalLayoutFlags
    DisableKerning = 0x0010,
    KerningAsian = 0x0020,
    Vertical = 0x0040,
    KashidaJustification = 0x0800,
    ForFallback = 0x2000,
    GlyphItemsOnly = 0x4000,
};
namespace o3tl
{
template <> struct typed_flags<SalLayoutFlags> : is_typed_flags<SalLayoutFlags, 0x6877>
template <> struct typed_flags<SalLayoutFlags> : is_typed_flags<SalLayoutFlags, 0x6077>
{
};
}
diff --git a/sc/source/ui/view/hintwin.cxx b/sc/source/ui/view/hintwin.cxx
index 1dc76d13..d57d8e0 100644
--- a/sc/source/ui/view/hintwin.cxx
+++ b/sc/source/ui/view/hintwin.cxx
@@ -83,7 +83,7 @@ drawinglayer::primitive2d::Primitive2DContainer ScOverlayHint::createOverlaySequ
    rtl::Reference<drawinglayer::primitive2d::TextSimplePortionPrimitive2D> pTitle =
        new drawinglayer::primitive2d::TextSimplePortionPrimitive2D(
                        aTextMatrix, m_aTitle, 0, m_aTitle.getLength(),
                        std::vector<double>(), std::move(aFontAttr), css::lang::Locale(),
                        std::vector<double>(), {}, std::move(aFontAttr), css::lang::Locale(),
                        rColor.getBColor());

    Point aTextStart(nLeft + aHintMargin.Width() + aIndent.Width(),
@@ -123,7 +123,7 @@ drawinglayer::primitive2d::Primitive2DContainer ScOverlayHint::createOverlaySequ
        rtl::Reference<drawinglayer::primitive2d::TextSimplePortionPrimitive2D> pMessage =
                                        new drawinglayer::primitive2d::TextSimplePortionPrimitive2D(
                                                aTextMatrix, aLine, 0, aLine.getLength(),
                                                std::vector<double>(), aFontAttr, css::lang::Locale(),
                                                std::vector<double>(), {}, aFontAttr, css::lang::Locale(),
                                                rColor.getBColor());

        rRange.expand(pMessage->getB2DRange(aDummy));
diff --git a/sd/source/ui/view/sdview.cxx b/sd/source/ui/view/sdview.cxx
index 429a8c4..7099977 100644
--- a/sd/source/ui/view/sdview.cxx
+++ b/sd/source/ui/view/sdview.cxx
@@ -430,6 +430,7 @@ void ViewRedirector::createRedirectedPrimitive2DSequence(
                                0,
                                nTextLength,
                                std::move(aDXArray),
                                {},
                                std::move(aFontAttribute),
                                std::move(aLocale),
                                aRGBColor));
diff --git a/sfx2/source/control/thumbnailviewitem.cxx b/sfx2/source/control/thumbnailviewitem.cxx
index 36a33ed..9b75120 100644
--- a/sfx2/source/control/thumbnailviewitem.cxx
+++ b/sfx2/source/control/thumbnailviewitem.cxx
@@ -269,6 +269,7 @@ void ThumbnailViewItem::addTextPrimitives (const OUString& rText, const Thumbnai
                    new TextSimplePortionPrimitive2D(aTextMatrix,
                                                     aText, nLineStart, nLineLength,
                                                     std::vector<double>(),
                                                     {},
                                                     pAttrs->aFontAttr,
                                                     css::lang::Locale(),
                                                     aTextColor));
diff --git a/svgio/source/svgreader/svgcharacternode.cxx b/svgio/source/svgreader/svgcharacternode.cxx
index 581129c..0a4731b 100644
--- a/svgio/source/svgreader/svgcharacternode.cxx
+++ b/svgio/source/svgreader/svgcharacternode.cxx
@@ -452,6 +452,7 @@ namespace svgio::svgreader
                        nIndex,
                        nLength,
                        std::move(aTextArray),
                        {},
                        aFontAttribute,
                        aLocale,
                        aFill,
@@ -480,6 +481,7 @@ namespace svgio::svgreader
                        nIndex,
                        nLength,
                        std::move(aTextArray),
                        {},
                        aFontAttribute,
                        aLocale,
                        aFill);
diff --git a/svtools/source/control/ruler.cxx b/svtools/source/control/ruler.cxx
index 07e030a..340d29b 100644
--- a/svtools/source/control/ruler.cxx
+++ b/svtools/source/control/ruler.cxx
@@ -79,7 +79,7 @@ SalLayoutGlyphs* lcl_GetRulerTextGlyphs(const vcl::RenderContext& rRenderContext
    // Calculate glyph items.

    std::unique_ptr<SalLayout> pLayout = rRenderContext.ImplLayout(
        rText, 0, rText.getLength(), Point(0, 0), 0, {}, SalLayoutFlags::GlyphItemsOnly);
        rText, 0, rText.getLength(), Point(0, 0), 0, {}, {}, SalLayoutFlags::GlyphItemsOnly);
    if (!pLayout)
        return nullptr;

@@ -339,7 +339,7 @@ void Ruler::ImplVDrawText(vcl::RenderContext& rRenderContext, tools::Long nX, to
    tools::Rectangle aRect;
    SalLayoutGlyphs* pTextLayout
        = lcl_GetRulerTextGlyphs(rRenderContext, rText, maTextGlyphs[rText]);
    rRenderContext.GetTextBoundRect(aRect, rText, 0, 0, -1, 0, {}, pTextLayout);
    rRenderContext.GetTextBoundRect(aRect, rText, 0, 0, -1, 0, {}, {}, pTextLayout);

    tools::Long nShiftX = ( aRect.GetWidth() / 2 ) + aRect.Left();
    tools::Long nShiftY = ( aRect.GetHeight() / 2 ) + aRect.Top();
diff --git a/svx/source/diagram/IDiagramHelper.cxx b/svx/source/diagram/IDiagramHelper.cxx
index 672c748..ad1db23 100644
--- a/svx/source/diagram/IDiagramHelper.cxx
+++ b/svx/source/diagram/IDiagramHelper.cxx
@@ -167,7 +167,8 @@ void OverlayDiagramPrimitive::create2DDecomposition(
            aName,
            0,
            aName.getLength(),
            aDXArray);
            aDXArray,
            {});

        // put into one PolyPolygon (also simplification - overlapping chars
        // may create XOR gaps, so these exist for a reason, but low probability)
diff --git a/svx/source/svdraw/svdotextdecomposition.cxx b/svx/source/svdraw/svdotextdecomposition.cxx
index 666c20d..f5955eb 100644
--- a/svx/source/svdraw/svdotextdecomposition.cxx
+++ b/svx/source/svdraw/svdotextdecomposition.cxx
@@ -234,6 +234,18 @@ namespace
            }
        }

        ::std::vector< sal_Bool > aKashidaArray;

        if(!rInfo.mpKashidaArray.empty() && rInfo.mnTextLen)
        {
            aKashidaArray.reserve(rInfo.mnTextLen);

            for(sal_Int32 a=0; a < rInfo.mnTextLen; a++)
            {
                aKashidaArray.push_back(rInfo.mpKashidaArray[a]);
            }
        }

        // create complex text primitive and append
        const Color aFontColor(rInfo.mrFont.GetColor());
        const basegfx::BColor aBFontColor(aFontColor.getBColor());
@@ -316,6 +328,7 @@ namespace
                rInfo.mnTextStart,
                rInfo.mnTextLen,
                std::vector(aDXArray),
                std::vector(aKashidaArray),
                aFontAttribute,
                rInfo.mpLocale ? *rInfo.mpLocale : css::lang::Locale(),
                aBFontColor,
@@ -344,6 +357,7 @@ namespace
                rInfo.mnTextStart,
                rInfo.mnTextLen,
                std::vector(aDXArray),
                std::vector(aKashidaArray),
                std::move(aFontAttribute),
                rInfo.mpLocale ? *rInfo.mpLocale : css::lang::Locale(),
                aBFontColor,
diff --git a/svx/source/svdraw/svdotextpathdecomposition.cxx b/svx/source/svdraw/svdotextpathdecomposition.cxx
index 99a7169..7537625b 100644
--- a/svx/source/svdraw/svdotextpathdecomposition.cxx
+++ b/svx/source/svdraw/svdotextpathdecomposition.cxx
@@ -58,6 +58,7 @@ namespace
        sal_Int32                                   mnParagraph;
        SvxFont                                     maFont;
        ::std::vector< double >                     maDblDXArray;   // double DXArray, font size independent -> unit coordinate system
        ::std::vector< sal_Bool >                   maKashidaArray;
        lang::Locale                           maLocale;

        bool                                        mbRTL : 1;
@@ -70,6 +71,7 @@ namespace
            mnTextLength(rInfo.mnTextLen),
            mnParagraph(rInfo.mnPara),
            maFont(rInfo.mrFont),
            maKashidaArray(rInfo.mpKashidaArray.begin(), rInfo.mpKashidaArray.end()),
            maLocale(rInfo.mpLocale ? *rInfo.mpLocale : lang::Locale()),
            mbRTL(!rInfo.mrFont.IsVertical() && rInfo.IsRTL())
        {
@@ -107,6 +109,7 @@ namespace
        const SvxFont& getFont() const { return maFont; }
        bool isRTL() const { return mbRTL; }
        const ::std::vector< double >& getDoubleDXArray() const { return maDblDXArray; }
        const ::std::vector< sal_Bool >& getKashidaArray() const { return maKashidaArray; }
        const lang::Locale& getLocale() const { return maLocale; }

        sal_Int32 getPortionIndex(sal_Int32 nIndex, sal_Int32 nLength) const
@@ -497,6 +500,7 @@ namespace
                                        nPortionIndex,
                                        nNextGlyphLen,
                                        std::vector(aNewDXArray),
                                        std::vector(pCandidate->getKashidaArray()),
                                        aCandidateFontAttribute,
                                        pCandidate->getLocale(),
                                        aRGBShadowColor) );
@@ -514,6 +518,7 @@ namespace
                                        nPortionIndex,
                                        nNextGlyphLen,
                                        std::move(aNewDXArray),
                                        std::vector(pCandidate->getKashidaArray()),
                                        aCandidateFontAttribute,
                                        pCandidate->getLocale(),
                                        aRGBColor) );
diff --git a/svx/source/tbxctrls/StylesPreviewWindow.cxx b/svx/source/tbxctrls/StylesPreviewWindow.cxx
index 3ad9fa7..8f5dfea 100644
--- a/svx/source/tbxctrls/StylesPreviewWindow.cxx
+++ b/svx/source/tbxctrls/StylesPreviewWindow.cxx
@@ -358,7 +358,8 @@ void StyleItemController::DrawText(vcl::RenderContext& rRenderContext)
    const SalLayoutGlyphs* layoutGlyphs
        = SalLayoutGlyphsCache::self()->GetLayoutGlyphs(&rRenderContext, m_aStyleName.second);
    tools::Rectangle aTextRect;
    rRenderContext.GetTextBoundRect(aTextRect, m_aStyleName.second, 0, 0, -1, 0, {}, layoutGlyphs);
    rRenderContext.GetTextBoundRect(aTextRect, m_aStyleName.second, 0, 0, -1, 0, {}, {},
                                    layoutGlyphs);

    Point aPos(0, 0);
    aPos.AdjustX(LEFT_MARGIN);
diff --git a/sw/source/core/inc/scriptinfo.hxx b/sw/source/core/inc/scriptinfo.hxx
index cfe9ef3..ee53680 100644
--- a/sw/source/core/inc/scriptinfo.hxx
+++ b/sw/source/core/inc/scriptinfo.hxx
@@ -289,7 +289,7 @@ public:
                The value which has to be added to a kashida opportunity.
    @return The number of kashida opportunities in the given range
*/
    sal_Int32 KashidaJustify( sal_Int32* pKernArray,
    sal_Int32 KashidaJustify( sal_Int32* pKernArray, sal_Bool* pKashidaArray,
          TextFrameIndex nStt, TextFrameIndex nLen, tools::Long nSpaceAdd = 0) const;

/** Clears array of kashidas marked as invalid
diff --git a/sw/source/core/layout/paintfrm.cxx b/sw/source/core/layout/paintfrm.cxx
index 1c9fde9..dd76c86 100644
--- a/sw/source/core/layout/paintfrm.cxx
+++ b/sw/source/core/layout/paintfrm.cxx
@@ -3782,6 +3782,7 @@ void SwColumnFrame::PaintBreak( ) const
                aTextMatrix,
                aBreakText, 0, aBreakText.getLength(),
                std::vector< double >(),
                {},
                std::move(aFontAttr),
                lang::Locale(),
                aLineColor ) );
diff --git a/sw/source/core/text/itradj.cxx b/sw/source/core/text/itradj.cxx
index a952ce7..b248f20 100644
--- a/sw/source/core/text/itradj.cxx
+++ b/sw/source/core/text/itradj.cxx
@@ -122,7 +122,7 @@ static bool lcl_CheckKashidaPositions( SwScriptInfo& rSI, SwTextSizeInfo& rInf, 
    // total number of kashida positions, or the number of kashida positions after some positions
    // have been dropped.
    // Here we want the clean total, which is OK: We have called ClearKashidaInvalid() before.
    rKashidas = rSI.KashidaJustify(nullptr, rItr.GetStart(), rItr.GetLength());
    rKashidas = rSI.KashidaJustify(nullptr, nullptr, rItr.GetStart(), rItr.GetLength());

    if (rKashidas <= 0) // nothing to do
        return true;
@@ -147,7 +147,7 @@ static bool lcl_CheckKashidaPositions( SwScriptInfo& rSI, SwTextSizeInfo& rInf, 

        if (nNext == TextFrameIndex(COMPLETE_STRING) || nNext > nEnd)
            nNext = nEnd;
        sal_Int32 nKashidasInAttr = rSI.KashidaJustify(nullptr, nIdx, nNext - nIdx);
        sal_Int32 nKashidasInAttr = rSI.KashidaJustify(nullptr, nullptr, nIdx, nNext - nIdx);
        if (nKashidasInAttr > 0)
        {
            // Kashida glyph looks suspicious, skip Kashida justification
@@ -212,7 +212,7 @@ static bool lcl_CheckKashidaWidth ( SwScriptInfo& rSI, SwTextSizeInfo& rInf, SwT

            if (nNext == TextFrameIndex(COMPLETE_STRING) || nNext > nEnd)
                nNext = nEnd;
            sal_Int32 nKashidasInAttr = rSI.KashidaJustify(nullptr, nIdx, nNext - nIdx);
            sal_Int32 nKashidasInAttr = rSI.KashidaJustify(nullptr, nullptr, nIdx, nNext - nIdx);

            tools::Long nFontMinKashida = rInf.GetOut()->GetMinKashida();
            if ( nFontMinKashida && nKashidasInAttr > 0 && SwScriptInfo::IsArabicText( rInf.GetText(), nIdx, nNext - nIdx ) )
diff --git a/sw/source/core/text/porlay.cxx b/sw/source/core/text/porlay.cxx
index 30abc01..3db6a07 100644
--- a/sw/source/core/text/porlay.cxx
+++ b/sw/source/core/text/porlay.cxx
@@ -2231,6 +2231,7 @@ tools::Long SwScriptInfo::Compress(sal_Int32* pKernArray, TextFrameIndex nIdx, T
// have been dropped, depending on the state of the m_KashidaInvalid set.

sal_Int32 SwScriptInfo::KashidaJustify( sal_Int32* pKernArray,
                                        sal_Bool* pKashidaArray,
                                        TextFrameIndex const nStt,
                                        TextFrameIndex const nLen,
                                        tools::Long nSpaceAdd ) const
@@ -2286,6 +2287,11 @@ sal_Int32 SwScriptInfo::KashidaJustify( sal_Int32* pKernArray,
        {
            TextFrameIndex nArrayPos = nIdx - nStt;

            // mark Kashida insertion positions, code in VCL will use this
            // array to know where to insert Kashida.
            if (pKashidaArray)
                pKashidaArray[sal_Int32(nArrayPos)] = true;

            // next kashida position
            ++nCntKash;
            while (nCntKash < nCntKashEnd && !IsKashidaValid(nCntKash))
diff --git a/sw/source/core/text/portxt.cxx b/sw/source/core/text/portxt.cxx
index c0f6649..09a2fc2 100644
--- a/sw/source/core/text/portxt.cxx
+++ b/sw/source/core/text/portxt.cxx
@@ -113,7 +113,7 @@ static TextFrameIndex lcl_AddSpace(const SwTextSizeInfo &rInf,
    {
        if ( SwScriptInfo::IsArabicText( *pStr, nPos, nEnd - nPos ) && pSI->CountKashida() )
        {
            const sal_Int32 nKashRes = pSI->KashidaJustify(nullptr, nPos, nEnd - nPos);
            const sal_Int32 nKashRes = pSI->KashidaJustify(nullptr, nullptr, nPos, nEnd - nPos);
            // i60591: need to check result of KashidaJustify
            // determine if kashida justification is applicable
            if (nKashRes != -1)
diff --git a/sw/source/core/txtnode/fntcache.cxx b/sw/source/core/txtnode/fntcache.cxx
index 4058f12..88d9037 100644
--- a/sw/source/core/txtnode/fntcache.cxx
+++ b/sw/source/core/txtnode/fntcache.cxx
@@ -951,7 +951,7 @@ void SwFntObj::DrawText( SwDrawTextInfo &rInf )
                rInf.GetFrame()->SwitchHorizontalToVertical( aTextOriginPos );

            rInf.GetOut().DrawTextArray( aTextOriginPos, rInf.GetText(),
                aKernArray, sal_Int32(rInf.GetIdx()), sal_Int32(rInf.GetLen()));
                aKernArray, {}, sal_Int32(rInf.GetIdx()), sal_Int32(rInf.GetLen()));

            return;
        }
@@ -980,6 +980,7 @@ void SwFntObj::DrawText( SwDrawTextInfo &rInf )
        {
            std::vector<sal_Int32> aKernArray;
            GetTextArray(rInf.GetOut(), rInf, aKernArray);
            std::vector<sal_Bool> aKashidaArray;

            if( bStretch )
            {
@@ -1050,13 +1051,16 @@ void SwFntObj::DrawText( SwDrawTextInfo &rInf )
                {
                    if ( SwScriptInfo::IsArabicText( rInf.GetText(), rInf.GetIdx(), rInf.GetLen() ) )
                    {
                        aKashidaArray.resize(aKernArray.size(), false);
                        if ( pSI && pSI->CountKashida() &&
                            pSI->KashidaJustify( aKernArray.data(), rInf.GetIdx(),
                            pSI->KashidaJustify( aKernArray.data(), aKashidaArray.data(), rInf.GetIdx(),
                                                 rInf.GetLen(), nSpaceAdd ) != -1 )
                        {
                            bSpecialJust = true;
                            nSpaceAdd = 0;
                        }
                        else
                            aKashidaArray.clear();
                    }
                }

@@ -1104,18 +1108,18 @@ void SwFntObj::DrawText( SwDrawTextInfo &rInf )
                        aKernArray[0] = rInf.GetWidth() + nSpaceAdd;

                        rInf.GetOut().DrawTextArray( aTextOriginPos, rInf.GetText(),
                             aKernArray, sal_Int32(rInf.GetIdx()), 1 );
                             aKernArray, aKashidaArray, sal_Int32(rInf.GetIdx()), 1 );
                    }
                    else
                    {
                        aKernArray[ sal_Int32(rInf.GetLen()) - 2 ] += nSpaceAdd;
                        rInf.GetOut().DrawTextArray( aTextOriginPos, rInf.GetText(),
                            aKernArray, sal_Int32(rInf.GetIdx()), sal_Int32(rInf.GetLen()));
                            aKernArray, aKashidaArray, sal_Int32(rInf.GetIdx()), sal_Int32(rInf.GetLen()));
                    }
                }
                else
                    rInf.GetOut().DrawTextArray( aTextOriginPos, rInf.GetText(),
                            aKernArray, sal_Int32(rInf.GetIdx()), sal_Int32(rInf.GetLen()));
                            aKernArray, aKashidaArray, sal_Int32(rInf.GetIdx()), sal_Int32(rInf.GetLen()));
            }
            else
            {
@@ -1209,6 +1213,8 @@ void SwFntObj::DrawText( SwDrawTextInfo &rInf )
            GetTextArray(rInf.GetOut(), rInf, aKernArray);
        }

        std::vector<sal_Bool> aKashidaArray;

        // Modify Printer and ScreenArrays for special justifications

        tools::Long nSpaceAdd = rInf.GetSpace() / SPACING_PRECISION_FACTOR;
@@ -1248,12 +1254,16 @@ void SwFntObj::DrawText( SwDrawTextInfo &rInf )
            {
                if ( SwScriptInfo::IsArabicText( rInf.GetText(), rInf.GetIdx(), rInf.GetLen() ) )
                {
                    aKashidaArray.resize(aKernArray.size(), false);
                    if ( pSI && pSI->CountKashida() &&
                         pSI->KashidaJustify( aKernArray.data(), rInf.GetIdx(),
                         pSI->KashidaJustify( aKernArray.data(), aKashidaArray.data(), rInf.GetIdx(),
                                              rInf.GetLen(), nSpaceAdd ) != -1 )
                        nSpaceAdd = 0;
                    else
                    {
                        aKashidaArray.clear();
                        bNoHalfSpace = true;
                    }
                }
            }

@@ -1339,9 +1349,9 @@ void SwFntObj::DrawText( SwDrawTextInfo &rInf )
                rInf.GetFrame()->SwitchHorizontalToVertical( aTextOriginPos );

            rInf.GetOut().DrawTextArray( aTextOriginPos, rInf.GetText(),
                         aKernArray, sal_Int32(rInf.GetIdx()), 1 );
                         aKernArray, aKashidaArray, sal_Int32(rInf.GetIdx()), 1 );
            if( bBullet )
                rInf.GetOut().DrawTextArray( aTextOriginPos, *pStr, aKernArray,
                rInf.GetOut().DrawTextArray( aTextOriginPos, *pStr, aKernArray, aKashidaArray,
                                             rInf.GetIdx() ? 1 : 0, 1 );
        }
        else
@@ -1464,7 +1474,7 @@ void SwFntObj::DrawText( SwDrawTextInfo &rInf )
                            : sal_Int32(rInf.GetIdx());
                const SalLayoutGlyphs* pGlyphs = SalLayoutGlyphsCache::self()->GetLayoutGlyphs(&rInf.GetOut(),
                     *pStr, nTmpIdx, nLen);
                rInf.GetOut().DrawTextArray( aTextOriginPos, *pStr, aKernArray,
                rInf.GetOut().DrawTextArray( aTextOriginPos, *pStr, aKernArray, aKashidaArray,
                                             nTmpIdx , nLen, SalLayoutFlags::NONE, pGlyphs );
                if (bBullet)
                {
@@ -1503,7 +1513,7 @@ void SwFntObj::DrawText( SwDrawTextInfo &rInf )
                        }
                    }
                    rInf.GetOut().DrawTextArray( aTextOriginPos, aBulletOverlay, aKernArray,
                                                 nTmpIdx , nLen );
                                                 aKashidaArray, nTmpIdx , nLen );
                    pTmpFont->SetColor( aPreviousColor );

                    pTmpFont->SetUnderline(aPreviousUnderline);
@@ -1738,7 +1748,7 @@ TextFrameIndex SwFntObj::GetModelPositionForViewPoint(SwDrawTextInfo &rInf)
            if ( SwScriptInfo::IsArabicText( rInf.GetText(), rInf.GetIdx(), rInf.GetLen() ) )
            {
                if ( pSI && pSI->CountKashida() &&
                    pSI->KashidaJustify( aKernArray.data(), rInf.GetIdx(), rInf.GetLen(),
                    pSI->KashidaJustify( aKernArray.data(), nullptr, rInf.GetIdx(), rInf.GetLen(),
                                         nSpaceAdd ) != -1 )
                    nSpaceAdd = 0;
            }
diff --git a/sw/source/uibase/docvw/HeaderFooterWin.cxx b/sw/source/uibase/docvw/HeaderFooterWin.cxx
index cca26f4..ba75b20 100644
--- a/sw/source/uibase/docvw/HeaderFooterWin.cxx
+++ b/sw/source/uibase/docvw/HeaderFooterWin.cxx
@@ -319,7 +319,7 @@ void SwHeaderFooterWin::PaintButton()
    aSeq.push_back(drawinglayer::primitive2d::Primitive2DReference(
                    new drawinglayer::primitive2d::TextSimplePortionPrimitive2D(
                        aTextMatrix, m_sLabel, 0, m_sLabel.getLength(),
                        std::vector<double>(), std::move(aFontAttr), css::lang::Locale(), aLineColor)));
                        std::vector<double>(), {}, std::move(aFontAttr), css::lang::Locale(), aLineColor)));

    // Create the 'plus' or 'arrow' primitive
    B2DRectangle aSignArea(B2DPoint(aRect.Right() - BUTTON_WIDTH, 0.0),
diff --git a/sw/source/uibase/docvw/UnfloatTableButton.cxx b/sw/source/uibase/docvw/UnfloatTableButton.cxx
index 8232073..49bf9c6 100644
--- a/sw/source/uibase/docvw/UnfloatTableButton.cxx
+++ b/sw/source/uibase/docvw/UnfloatTableButton.cxx
@@ -223,7 +223,7 @@ void UnfloatTableButton::PaintButton()

    aSeq.push_back(drawinglayer::primitive2d::Primitive2DReference(
        new drawinglayer::primitive2d::TextSimplePortionPrimitive2D(
            aTextMatrix, m_sLabel, 0, m_sLabel.getLength(), std::vector<double>(),
            aTextMatrix, m_sLabel, 0, m_sLabel.getLength(), std::vector<double>(), {},
            std::move(aFontAttr), css::lang::Locale(), aLineColor)));

    // Create the processor and process the primitives
diff --git a/vcl/inc/ImplLayoutArgs.hxx b/vcl/inc/ImplLayoutArgs.hxx
index a94557a..105a4e2 100644
--- a/vcl/inc/ImplLayoutArgs.hxx
+++ b/vcl/inc/ImplLayoutArgs.hxx
@@ -37,6 +37,7 @@ public:
    // positioning related inputs
    const DeviceCoordinate* mpDXArray; // in integer pixel units
    const double* mpAltNaturalDXArray; // in floating point pixel units
    const sal_Bool* mpKashidaArray;
    DeviceCoordinate mnLayoutWidth; // in pixel units
    Degree10 mnOrientation; // in 0-3600 system

@@ -50,6 +51,7 @@ public:
    void SetLayoutWidth(DeviceCoordinate nWidth);
    void SetDXArray(const DeviceCoordinate* pDXArray);
    void SetAltNaturalDXArray(const double* pDXArray);
    void SetKashidaArray(const sal_Bool* pKashidaArray);
    void SetOrientation(Degree10 nOrientation);

    void ResetPos();
diff --git a/vcl/inc/impglyphitem.hxx b/vcl/inc/impglyphitem.hxx
index f0e4e70..f43ef0e 100644
--- a/vcl/inc/impglyphitem.hxx
+++ b/vcl/inc/impglyphitem.hxx
@@ -37,7 +37,6 @@ enum class GlyphItemFlags : sal_uInt16
    IS_DIACRITIC = 0x04,
    IS_VERTICAL = 0x08,
    IS_SPACING = 0x10,
    ALLOW_KASHIDA = 0x20,
    IS_DROPPED = 0x40,
    IS_CLUSTER_START = 0x80,
    IS_UNSAFE_TO_BREAK = 0x100, // HB_GLYPH_FLAG_UNSAFE_TO_BREAK from harfbuzz
@@ -45,7 +44,7 @@ enum class GlyphItemFlags : sal_uInt16
};
namespace o3tl
{
template <> struct typed_flags<GlyphItemFlags> : is_typed_flags<GlyphItemFlags, 0x3ff>
template <> struct typed_flags<GlyphItemFlags> : is_typed_flags<GlyphItemFlags, 0x3df>
{
};
};
@@ -82,7 +81,6 @@ public:
    bool IsDiacritic() const { return bool(m_nFlags & GlyphItemFlags::IS_DIACRITIC); }
    bool IsVertical() const { return bool(m_nFlags & GlyphItemFlags::IS_VERTICAL); }
    bool IsSpacing() const { return bool(m_nFlags & GlyphItemFlags::IS_SPACING); }
    bool AllowKashida() const { return bool(m_nFlags & GlyphItemFlags::ALLOW_KASHIDA); }
    bool IsDropped() const { return bool(m_nFlags & GlyphItemFlags::IS_DROPPED); }
    bool IsClusterStart() const { return bool(m_nFlags & GlyphItemFlags::IS_CLUSTER_START); }
    bool IsUnsafeToBreak() const { return bool(m_nFlags & GlyphItemFlags::IS_UNSAFE_TO_BREAK); }
diff --git a/vcl/inc/pdf/pdfwriter_impl.hxx b/vcl/inc/pdf/pdfwriter_impl.hxx
index d718529..4ebddbe 100644
--- a/vcl/inc/pdf/pdfwriter_impl.hxx
+++ b/vcl/inc/pdf/pdfwriter_impl.hxx
@@ -1188,7 +1188,7 @@ public:

    /* actual drawing functions */
    void drawText( const Point& rPos, const OUString& rText, sal_Int32 nIndex, sal_Int32 nLen, bool bTextLines = true );
    void drawTextArray( const Point& rPos, const OUString& rText, o3tl::span<const sal_Int32> pDXArray, sal_Int32 nIndex, sal_Int32 nLen );
    void drawTextArray( const Point& rPos, const OUString& rText, o3tl::span<const sal_Int32> pDXArray, o3tl::span<const sal_Bool> pKashidaArray, sal_Int32 nIndex, sal_Int32 nLen );
    void drawStretchText( const Point& rPos, sal_uLong nWidth, const OUString& rText,
                          sal_Int32 nIndex, sal_Int32 nLen  );
    void drawText( const tools::Rectangle& rRect, const OUString& rOrigStr, DrawTextFlags nStyle );
diff --git a/vcl/inc/sallayout.hxx b/vcl/inc/sallayout.hxx
index 35f27ee..885de24 100644
--- a/vcl/inc/sallayout.hxx
+++ b/vcl/inc/sallayout.hxx
@@ -143,7 +143,7 @@ private:
                    GenericSalLayout& operator=( const GenericSalLayout& ) = delete;

    template<typename DC>
    void            ApplyDXArray(const DC*, SalLayoutFlags nLayoutFlags);
    void            ApplyDXArray(const DC*, const sal_Bool*);
    void            Justify(DeviceCoordinate nNewWidth);
    void            ApplyAsianKerning(const OUString& rStr);

diff --git a/vcl/qa/cppunit/complextext.cxx b/vcl/qa/cppunit/complextext.cxx
index 9e96205..ff85b14 100644
--- a/vcl/qa/cppunit/complextext.cxx
+++ b/vcl/qa/cppunit/complextext.cxx
@@ -49,14 +49,12 @@ public:

    /// Play with font measuring etc.
    void testArabic();
    void testKashida();
    void testTdf95650(); // Windows-only issue
    void testCaching();
    void testCachingSubstring();

    CPPUNIT_TEST_SUITE(VclComplexTextTest);
    CPPUNIT_TEST(testArabic);
    CPPUNIT_TEST(testKashida);
    CPPUNIT_TEST(testTdf95650);
    CPPUNIT_TEST(testCaching);
    CPPUNIT_TEST(testCachingSubstring);
@@ -114,31 +112,6 @@ void VclComplexTextTest::testArabic()
#endif
}

void VclComplexTextTest::testKashida()
{
#if HAVE_MORE_FONTS
    // Cache the glyph list of some Arabic text.
    ScopedVclPtrInstance<VirtualDevice> pOutputDevice;
    auto aText
        = OUString(u"عنصر الفوسفور عنصر فلزي صلب. تتكون الدورة الرابعة من 15 عنصرا.");
    std::unique_ptr<SalLayout> pLayout = pOutputDevice->ImplLayout(
        aText, 0, aText.getLength(), Point(0, 0), 0, {}, SalLayoutFlags::GlyphItemsOnly);
    SalLayoutGlyphs aGlyphs = pLayout->GetGlyphs();
    CPPUNIT_ASSERT(aGlyphs.IsValid());
    CPPUNIT_ASSERT(aGlyphs.Impl(0) != nullptr);

    // Now lay it out using the cached glyph list.
    vcl::text::ImplLayoutArgs aLayoutArgs(aText, 0, aText.getLength(), SalLayoutFlags::NONE,
                               pOutputDevice->GetFont().GetLanguageTag(), nullptr);
    pLayout = pOutputDevice->GetGraphics()->GetTextLayout(0);
    CPPUNIT_ASSERT(pLayout->LayoutText(aLayoutArgs, aGlyphs.Impl(0)));

    // Without the accompanying fix in place, this test would have failed with 'assertion failed'.
    // The kashida justification flag was lost when going via the glyph cache.
    CPPUNIT_ASSERT(aLayoutArgs.mnFlags & SalLayoutFlags::KashidaJustification);
#endif
}

void VclComplexTextTest::testTdf95650()
{
    static constexpr OUStringLiteral aTxt =
@@ -152,7 +125,7 @@ void VclComplexTextTest::testTdf95650()

    OutputDevice *pOutDev = pWin->GetOutDev();
    // Check that the following executes without failing assertion
    pOutDev->ImplLayout(aTxt, 9, 1, Point(), 0, {}, SalLayoutFlags::BiDiRtl);
    pOutDev->ImplLayout(aTxt, 9, 1, Point(), 0, {}, {}, SalLayoutFlags::BiDiRtl);
}

static void checkCompareGlyphs( const SalLayoutGlyphs& aGlyphs1, const SalLayoutGlyphs& aGlyphs2,
@@ -190,11 +163,11 @@ static void testCachedGlyphs( const OUString& aText, const OUString& aFontName )
    SalLayoutGlyphsCache::self()->clear();
    // Get the glyphs for the text.
    std::unique_ptr<SalLayout> pLayout1 = pOutputDevice->ImplLayout(
        aText, 0, aText.getLength(), Point(0, 0), 0, {}, SalLayoutFlags::GlyphItemsOnly);
        aText, 0, aText.getLength(), Point(0, 0), 0, {}, {}, SalLayoutFlags::GlyphItemsOnly);
    SalLayoutGlyphs aGlyphs1 = pLayout1->GetGlyphs();
    // Reuse the cached glyphs to get glyphs again.
    std::unique_ptr<SalLayout> pLayout2 = pOutputDevice->ImplLayout(
        aText, 0, aText.getLength(), Point(0, 0), 0, {}, SalLayoutFlags::GlyphItemsOnly, nullptr, &aGlyphs1);
        aText, 0, aText.getLength(), Point(0, 0), 0, {}, {}, SalLayoutFlags::GlyphItemsOnly, nullptr, &aGlyphs1);
    SalLayoutGlyphs aGlyphs2 = pLayout2->GetGlyphs();
    checkCompareGlyphs(aGlyphs1, aGlyphs2, message + " (reuse)");
    // Get cached glyphs from SalLayoutGlyphsCache.
@@ -228,7 +201,7 @@ static void testCachedGlyphsSubstring( const OUString& aText, const OUString& aF
    SalLayoutGlyphsCache::self()->clear();
    std::shared_ptr<const vcl::text::TextLayoutCache> layoutCache = OutputDevice::CreateTextLayoutCache(aText);
    // Get the glyphs for the entire text once, to ensure the cache can built subsets from it.
    pOutputDevice->ImplLayout( aText, 0, aText.getLength(), Point(0, 0), 0, {}, SalLayoutFlags::GlyphItemsOnly,
    pOutputDevice->ImplLayout( aText, 0, aText.getLength(), Point(0, 0), 0, {}, {}, SalLayoutFlags::GlyphItemsOnly,
        layoutCache.get());
    // Now check for all subsets. Some of them possibly do not make sense in practice, but the code
    // should cope with them.
@@ -237,7 +210,7 @@ static void testCachedGlyphsSubstring( const OUString& aText, const OUString& aF
        {
            std::string message = prefix + " (" + std::to_string(pos) + "/" + std::to_string(len) + ")";
            std::unique_ptr<SalLayout> pLayout1 = pOutputDevice->ImplLayout(
                aText, pos, len, Point(0, 0), 0, {}, SalLayoutFlags::GlyphItemsOnly, layoutCache.get());
                aText, pos, len, Point(0, 0), 0, {}, {}, SalLayoutFlags::GlyphItemsOnly, layoutCache.get());
            SalLayoutGlyphs aGlyphs1 = pLayout1->GetGlyphs();
            const SalLayoutGlyphs* aGlyphs2 = SalLayoutGlyphsCache::self()->GetLayoutGlyphs(
                pOutputDevice, aText, pos, len, 0, layoutCache.get());
diff --git a/vcl/qa/cppunit/svm/svmtest.cxx b/vcl/qa/cppunit/svm/svmtest.cxx
index fcea61a..2f4f17d 100644
--- a/vcl/qa/cppunit/svm/svmtest.cxx
+++ b/vcl/qa/cppunit/svm/svmtest.cxx
@@ -860,7 +860,7 @@ void SvmTest::testTextArray()
    ScopedVclPtrInstance<VirtualDevice> pVirtualDev;
    setupBaseVirtualDevice(*pVirtualDev, aGDIMetaFile);
    sal_Int32 const aDX[] = { 10, 15, 20, 25, 30, 35 };
    pVirtualDev->DrawTextArray(Point(4,6), "123456", aDX, 1, 4);
    pVirtualDev->DrawTextArray(Point(4,6), "123456", aDX, {}, 1, 4);

    checkTextArray(writeAndReadStream(aGDIMetaFile));
    checkTextArray(readFile(u"textarray.svm"));
diff --git a/vcl/qa/cppunit/text.cxx b/vcl/qa/cppunit/text.cxx
index 668e84e..7a73ee3 100644
--- a/vcl/qa/cppunit/text.cxx
+++ b/vcl/qa/cppunit/text.cxx
@@ -702,7 +702,7 @@ void VclTextTest::testImplLayoutArgs_PrepareFallback_precalculatedglyphs()
    const OString sUTF8String(u8"Тхе яуицк\n ыумпед овер");
    const OUString sTestString(OUString::fromUtf8(sUTF8String));
    std::unique_ptr<SalLayout> pLayout
        = pVirDev->ImplLayout(sTestString, 0, sTestString.getLength(), Point(0, 0), 0, {},
        = pVirDev->ImplLayout(sTestString, 0, sTestString.getLength(), Point(0, 0), 0, {}, {},
                              SalLayoutFlags::GlyphItemsOnly);
    SalLayoutGlyphs aGlyphs = pLayout->GetGlyphs();
    SalLayoutGlyphsImpl* pGlyphsImpl = aGlyphs.Impl(1);
diff --git a/vcl/source/control/imp_listbox.cxx b/vcl/source/control/imp_listbox.cxx
index 9117706..fbd655e 100644
--- a/vcl/source/control/imp_listbox.cxx
+++ b/vcl/source/control/imp_listbox.cxx
@@ -590,7 +590,7 @@ SalLayoutGlyphs* ImplEntryType::GetTextGlyphs(const OutputDevice* pOutputDevice)
        return &maStrGlyphs;

    std::unique_ptr<SalLayout> pLayout = pOutputDevice->ImplLayout(
        maStr, 0, maStr.getLength(), Point(0, 0), 0, {}, SalLayoutFlags::GlyphItemsOnly);
        maStr, 0, maStr.getLength(), Point(0, 0), 0, {}, {}, SalLayoutFlags::GlyphItemsOnly);
    if (!pLayout)
        return nullptr;

diff --git a/vcl/source/filter/eps/eps.cxx b/vcl/source/filter/eps/eps.cxx
index bb25bdc..797d6b8 100644
--- a/vcl/source/filter/eps/eps.cxx
+++ b/vcl/source/filter/eps/eps.cxx
@@ -201,7 +201,7 @@ private:

    void                ImplSetClipRegion( vcl::Region const & rRegion );
    void                ImplBmp( Bitmap const *, Bitmap const *, const Point &, double nWidth, double nHeight );
    void                ImplText( const OUString& rUniString, const Point& rPos, o3tl::span<const sal_Int32> pDXArry, sal_Int32 nWidth, VirtualDevice const & rVDev );
    void                ImplText( const OUString& rUniString, const Point& rPos, o3tl::span<const sal_Int32> pDXArry, o3tl::span<const sal_Bool> pKashidaArry, sal_Int32 nWidth, VirtualDevice const & rVDev );
    void                ImplSetAttrForText( const Point & rPoint );
    void                ImplWriteCharacter( char );
    void                ImplWriteString( const OString&, VirtualDevice const & rVDev, o3tl::span<const sal_Int32> pDXArry, bool bStretch );
@@ -748,7 +748,7 @@ void PSWriter::ImplWriteActions( const GDIMetaFile& rMtf, VirtualDevice& rVDev )
                OUString  aUniStr = pA->GetText().copy( pA->GetIndex(), pA->GetLen() );
                Point     aPoint( pA->GetPoint() );

                ImplText( aUniStr, aPoint, {}, 0, rVDev );
                ImplText( aUniStr, aPoint, {}, {}, 0, rVDev );
            }
            break;

@@ -764,7 +764,7 @@ void PSWriter::ImplWriteActions( const GDIMetaFile& rMtf, VirtualDevice& rVDev )
                OUString  aUniStr = pA->GetText().copy( pA->GetIndex(), pA->GetLen() );
                Point     aPoint( pA->GetPoint() );

                ImplText( aUniStr, aPoint, {}, pA->GetWidth(), rVDev );
                ImplText( aUniStr, aPoint, {}, {}, pA->GetWidth(), rVDev );
            }
            break;

@@ -774,7 +774,7 @@ void PSWriter::ImplWriteActions( const GDIMetaFile& rMtf, VirtualDevice& rVDev )
                OUString  aUniStr = pA->GetText().copy( pA->GetIndex(), pA->GetLen() );
                Point     aPoint( pA->GetPoint() );

                ImplText( aUniStr, aPoint, pA->GetDXArray(), 0, rVDev );
                ImplText( aUniStr, aPoint, pA->GetDXArray(), pA->GetKashidaArray(), 0, rVDev );
            }
            break;

@@ -1988,7 +1988,7 @@ void PSWriter::ImplWriteString( const OString& rString, VirtualDevice const & rV
    }
}

void PSWriter::ImplText( const OUString& rUniString, const Point& rPos, o3tl::span<const sal_Int32> pDXArry, sal_Int32 nWidth, VirtualDevice const & rVDev )
void PSWriter::ImplText( const OUString& rUniString, const Point& rPos, o3tl::span<const sal_Int32> pDXArry, o3tl::span<const sal_Bool> pKashidaArry, sal_Int32 nWidth, VirtualDevice const & rVDev )
{
    if ( rUniString.isEmpty() )
        return;
@@ -2015,7 +2015,7 @@ void PSWriter::ImplText( const OUString& rUniString, const Point& rPos, o3tl::sp
        bool bOldLineColor = bLineColor;
        bLineColor = false;
        std::vector<tools::PolyPolygon> aPolyPolyVec;
        if ( pVirDev->GetTextOutlines( aPolyPolyVec, rUniString, 0, 0, -1, nWidth, pDXArry ) )
        if ( pVirDev->GetTextOutlines( aPolyPolyVec, rUniString, 0, 0, -1, nWidth, pDXArry, pKashidaArry ) )
        {
            // always adjust text position to match baseline alignment
            ImplWriteLine( "pum" );
diff --git a/vcl/source/filter/svm/SvmConverter.cxx b/vcl/source/filter/svm/SvmConverter.cxx
index 4127d13..efc036c 100644
--- a/vcl/source/filter/svm/SvmConverter.cxx
+++ b/vcl/source/filter/svm/SvmConverter.cxx
@@ -809,7 +809,7 @@ void SVMConverter::ImplConvertFromSVM1( SvStream& rIStm, GDIMetaFile& rMtf )
                    if ( nUnicodeCommentActionNumber == i )
                        ImplReadUnicodeComment( nUnicodeCommentStreamPos, rIStm, aStr );
                    ClampRange(aStr, nIndex, nLen, &aDXAry);
                    rMtf.AddAction( new MetaTextArrayAction( aPt, aStr, std::move(aDXAry), nIndex, nLen ) );
                    rMtf.AddAction( new MetaTextArrayAction( aPt, aStr, std::move(aDXAry), {}, nIndex, nLen ) );
                }

                if (nActionSize < 24)
diff --git a/vcl/source/gdi/CommonSalLayout.cxx b/vcl/source/gdi/CommonSalLayout.cxx
index 98364d4..da73343 100644
--- a/vcl/source/gdi/CommonSalLayout.cxx
+++ b/vcl/source/gdi/CommonSalLayout.cxx
@@ -197,9 +197,9 @@ void GenericSalLayout::AdjustLayout(vcl::text::ImplLayoutArgs& rArgs)
    SalLayout::AdjustLayout(rArgs);

    if (rArgs.mpAltNaturalDXArray) // Used when "TextRenderModeForResolutionIndependentLayout" is set
        ApplyDXArray(rArgs.mpAltNaturalDXArray, rArgs.mnFlags);
        ApplyDXArray(rArgs.mpAltNaturalDXArray, rArgs.mpKashidaArray);
    else if (rArgs.mpDXArray)   // Normal case
        ApplyDXArray(rArgs.mpDXArray, rArgs.mnFlags);
        ApplyDXArray(rArgs.mpDXArray, rArgs.mpKashidaArray);
    else if (rArgs.mnLayoutWidth)
        Justify(rArgs.mnLayoutWidth);
    // apply asian kerning if the glyphs are not already formatted
@@ -565,14 +565,6 @@ bool GenericSalLayout::LayoutText(vcl::text::ImplLayoutArgs& rArgs, const SalLay
                if (u_isUWhiteSpace(aChar))
                    nGlyphFlags |= GlyphItemFlags::IS_SPACING;

                if (aSubRun.maScript == HB_SCRIPT_ARABIC &&
                    HB_DIRECTION_IS_BACKWARD(aSubRun.maDirection) &&
                    !(nGlyphFlags & GlyphItemFlags::IS_SPACING))
                {
                    nGlyphFlags |= GlyphItemFlags::ALLOW_KASHIDA;
                    rArgs.mnFlags |= SalLayoutFlags::KashidaJustification;
                }

#if HB_VERSION_ATLEAST(1, 5, 0)
                if (hb_glyph_info_get_glyph_flags(&pHbGlyphInfos[i]) & HB_GLYPH_FLAG_UNSAFE_TO_BREAK)
                    nGlyphFlags |= GlyphItemFlags::IS_UNSAFE_TO_BREAK;
@@ -657,24 +649,12 @@ void GenericSalLayout::GetCharWidths(std::vector<DeviceCoordinate>& rCharWidths)
    }
}

// A note on how Kashida justification is implemented (because it took me 5
// years to figure it out):
// The decision to insert Kashidas, where and how much is taken by Writer.
// This decision is communicated to us in a very indirect way; by increasing
// the width of the character after which Kashidas should be inserted by the
// desired amount.
//
// Writer eventually calls IsKashidaPosValid() to check whether it can insert a
// Kashida between two characters or not.
//
// Here we do:
// - In LayoutText() set KashidaJustification flag based on text script.
// - In ApplyDXArray():
//   * Check the above flag to decide whether to insert Kashidas or not.
//   * For any RTL glyph that has DX adjustment, insert enough Kashidas to
//     fill in the added space.
// - pDXArray: is the adjustments to glyph advances (usually due to
//   justification).
// - pKashidaArray: is the places where kashidas are inserted (for Arabic
//   justification). The number of kashidas is calculated from the pDXArray.
template<typename DC>
void GenericSalLayout::ApplyDXArray(const DC* pDXArray, SalLayoutFlags nLayoutFlags)
void GenericSalLayout::ApplyDXArray(const DC* pDXArray, const sal_Bool* pKashidaArray)
{
    int nCharCount = mnEndCharPos - mnMinCharPos;
    std::vector<DeviceCoordinate> aOldCharWidths;
@@ -692,18 +672,6 @@ void GenericSalLayout::ApplyDXArray(const DC* pDXArray, SalLayoutFlags nLayoutFl
            pNewCharWidths[i] = pDXArray[i] - pDXArray[i - 1];
    }

    bool bKashidaJustify = false;
    DeviceCoordinate nKashidaWidth = 0;
    hb_codepoint_t nKashidaIndex = 0;
    if (nLayoutFlags & SalLayoutFlags::KashidaJustification)
    {
        hb_font_t *pHbFont = GetFont().GetHbFont();
        // Find Kashida glyph width and index.
        if (hb_font_get_glyph(pHbFont, 0x0640, 0, &nKashidaIndex))
            nKashidaWidth = GetFont().GetKashidaWidth();
        bKashidaJustify = nKashidaWidth != 0;
    }

    // Map of Kashida insertion points (in the glyph items vector) and the
    // requested width.
    std::map<size_t, DeviceCoordinate> pKashidas;
@@ -743,15 +711,15 @@ void GenericSalLayout::ApplyDXArray(const DC* pDXArray, SalLayoutFlags nLayoutFl
            // loop below.
            i++;
        }
        else
        else // RTL
        {
            // Adjust the width and position of the first (rightmost) glyph in
            // the cluster.
            // For RTL, we put all the adjustment to the left of the glyph.
            // the cluster. This is RTL, so we put all the adjustment to the
            // left of the glyph.
            m_GlyphItems[i].addNewWidth(nDiff);
            m_GlyphItems[i].adjustLinearPosX(nDelta + nDiff);

            // Adjust the X position of all glyphs in the cluster.
            // Adjust the X position of the rest of the glyphs in the cluster.
            size_t j = i;
            while (j > 0)
            {
@@ -761,37 +729,45 @@ void GenericSalLayout::ApplyDXArray(const DC* pDXArray, SalLayoutFlags nLayoutFl
                m_GlyphItems[j].adjustLinearPosX(nDelta + nDiff);
            }

            // If this glyph is Kashida-justifiable, then mark this as a
            // Kashida position. Since this must be a RTL glyph, we mark the
            // last glyph in the cluster not the first as this would be the
            // base glyph.
            if (bKashidaJustify && m_GlyphItems[i].AllowKashida() &&
                nDiff > m_GlyphItems[i].charCount()) // Rounding errors, 1 pixel per character!
            // Move any non-spacing marks to keep attached to this cluster.
            while (j > 0)
            {
                pKashidas[i] = nDiff;
                // Move any non-spacing marks attached to this cluster as well.
                // Looping backward because this is RTL glyph.
                while (j > 0)
                {
                    if (!m_GlyphItems[j].IsDiacritic())
                        break;
                    m_GlyphItems[j--].adjustLinearPosX(nDiff);
                }
                if (!m_GlyphItems[j].IsDiacritic())
                    break;
                m_GlyphItems[j--].adjustLinearPosX(nDiff);
            }

            // This is a Kashida insertion position, mark it. Kashida glyphs
            // will be inserted below.
            if (pKashidaArray && pKashidaArray[nCharPos])
                pKashidas[i] = nDiff;

            i++;
        }

        // Increment the delta, the loop above makes sure we do so only once
        // for every character (cluster) not for every glyph (otherwise we
        // would apply it multiple times for each glyphs belonging to the same
        // character which is wrong since DX adjustments are character based).
        // would apply it multiple times for each glyph belonging to the same
        // character which is wrong as DX adjustments are character based).
        nDelta += nDiff;
    }

    // Insert Kashida glyphs.
    if (!bKashidaJustify || pKashidas.empty())
    if (pKashidas.empty())
        return;

    // Find Kashida glyph width and index.
    DeviceCoordinate nKashidaWidth = 0;
    hb_codepoint_t nKashidaIndex = 0;
    if (hb_font_get_glyph(GetFont().GetHbFont(), 0x0640, 0, &nKashidaIndex))
        nKashidaWidth = GetFont().GetKashidaWidth();

    if (nKashidaWidth <= 0)
    {
        SAL_WARN("vcl.gdi", "Asked to insert Kashidas in a font with zero-width Kashida");
        return;
    }

    size_t nInserted = 0;
    for (auto const& pKashida : pKashidas)
    {
diff --git a/vcl/source/gdi/gdimtf.cxx b/vcl/source/gdi/gdimtf.cxx
index 4d889f6..e15cf05b 100644
--- a/vcl/source/gdi/gdimtf.cxx
+++ b/vcl/source/gdi/gdimtf.cxx
@@ -978,7 +978,7 @@ void GDIMetaFile::Rotate( Degree10 nAngle10 )
            {
                MetaTextArrayAction* pAct = static_cast<MetaTextArrayAction*>(pAction);
                aMtf.AddAction( new MetaTextArrayAction( ImplGetRotatedPoint( pAct->GetPoint(), aRotAnchor, aRotOffset, fSin, fCos ),
                                                                              pAct->GetText(), pAct->GetDXArray(), pAct->GetIndex(), pAct->GetLen() ) );
                                                                              pAct->GetText(), pAct->GetDXArray(), pAct->GetKashidaArray(), pAct->GetIndex(), pAct->GetLen() ) );
            }
            break;

@@ -1452,7 +1452,7 @@ tools::Rectangle GDIMetaFile::GetBoundRect( OutputDevice& i_rReference ) const
            tools::Rectangle aRect;
            // hdu said base = index
            aMapVDev->GetTextBoundRect( aRect, pAct->GetText(), pAct->GetIndex(), pAct->GetIndex(), pAct->GetLen(),
                                       0, pAct->GetDXArray() );
                                       0, pAct->GetDXArray(), pAct->GetKashidaArray() );
            Point aPt( pAct->GetPoint() );
            aRect.Move( aPt.X(), aPt.Y() );
            ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack );
diff --git a/vcl/source/gdi/impglyphitem.cxx b/vcl/source/gdi/impglyphitem.cxx
index 51bff59..6cbb245 100644
--- a/vcl/source/gdi/impglyphitem.cxx
+++ b/vcl/source/gdi/impglyphitem.cxx
@@ -297,27 +297,6 @@ static SalLayoutGlyphs makeGlyphsSubset(const SalLayoutGlyphs& source,
        // would assert on flags being different.
        cloned->SetFlags(cloned->GetFlags()
                         | outputDevice->GetBiDiLayoutFlags(text, index, index + len));
        // SalLayoutFlags::KashidaJustification is set only if any glyph
        // in the range has GlyphItemFlags::ALLOW_KASHIDA (otherwise unset it).
        if (cloned->GetFlags() & SalLayoutFlags::KashidaJustification)
        {
            bool hasKashida = false;
            for (const GlyphItem& item : *cloned)
            {
                if (item.AllowKashida())
                {
                    hasKashida = true;
                    break;
                }
            }
            if (!hasKashida)
                cloned->SetFlags(cloned->GetFlags() & ~SalLayoutFlags::KashidaJustification);
        }
#ifdef DBG_UTIL
        else
            for (const GlyphItem& item : *cloned)
                assert(!item.AllowKashida());
#endif
        ret.AppendImpl(cloned);
    }
    return ret;
@@ -433,7 +412,7 @@ SalLayoutGlyphsCache::GetLayoutGlyphs(VclPtr<const OutputDevice> outputDevice, c
                // Check if the subset result really matches what we would get normally,
                // to make sure corner cases are handled well (see SalLayoutGlyphsImpl::cloneCharRange()).
                std::unique_ptr<SalLayout> layout
                    = outputDevice->ImplLayout(text, nIndex, nLen, Point(0, 0), nLogicWidth, {},
                    = outputDevice->ImplLayout(text, nIndex, nLen, Point(0, 0), nLogicWidth, {}, {},
                                               SalLayoutFlags::GlyphItemsOnly, layoutCache);
                assert(layout);
                checkGlyphsEqual(mLastTemporaryGlyphs, layout->GetGlyphs());
@@ -458,7 +437,7 @@ SalLayoutGlyphsCache::GetLayoutGlyphs(VclPtr<const OutputDevice> outputDevice, c
        layoutCache = tmpLayoutCache.get();
    }
    std::unique_ptr<SalLayout> layout
        = outputDevice->ImplLayout(text, nIndex, nLen, Point(0, 0), nLogicWidth, {},
        = outputDevice->ImplLayout(text, nIndex, nLen, Point(0, 0), nLogicWidth, {}, {},
                                   SalLayoutFlags::GlyphItemsOnly, layoutCache);
    if (layout)
    {
diff --git a/vcl/source/gdi/metaact.cxx b/vcl/source/gdi/metaact.cxx
index 4d6705e..cd06b01 100644
--- a/vcl/source/gdi/metaact.cxx
+++ b/vcl/source/gdi/metaact.cxx
@@ -601,6 +601,7 @@ MetaTextArrayAction::MetaTextArrayAction( const MetaTextArrayAction& rAction ) :
    maStartPt   ( rAction.maStartPt ),
    maStr       ( rAction.maStr ),
    maDXAry     ( rAction.maDXAry ),
    maKashidaAry( rAction.maKashidaAry ),
    mnIndex     ( rAction.mnIndex ),
    mnLen       ( rAction.mnLen )
{
@@ -609,12 +610,14 @@ MetaTextArrayAction::MetaTextArrayAction( const MetaTextArrayAction& rAction ) :
MetaTextArrayAction::MetaTextArrayAction( const Point& rStartPt,
                                          OUString aStr,
                                          std::vector<sal_Int32> aDXAry,
                                          std::vector<sal_Bool> aKashidaAry,
                                          sal_Int32 nIndex,
                                          sal_Int32 nLen ) :
    MetaAction  ( MetaActionType::TEXTARRAY ),
    maStartPt   ( rStartPt ),
    maStr       (std::move( aStr )),
    maDXAry     (std::move( aDXAry )),
    maKashidaAry(std::move( aKashidaAry )),
    mnIndex     ( nIndex ),
    mnLen       ( nLen )
{
@@ -623,12 +626,14 @@ MetaTextArrayAction::MetaTextArrayAction( const Point& rStartPt,
MetaTextArrayAction::MetaTextArrayAction( const Point& rStartPt,
                                          OUString aStr,
                                          o3tl::span<const sal_Int32> pDXAry,
                                          o3tl::span<const sal_Bool> pKashidaAry,
                                          sal_Int32 nIndex,
                                          sal_Int32 nLen ) :
    MetaAction  ( MetaActionType::TEXTARRAY ),
    maStartPt   ( rStartPt ),
    maStr       (std::move( aStr )),
    maDXAry     ( pDXAry.begin(), pDXAry.end() ),
    maKashidaAry( pKashidaAry.begin(), pKashidaAry.end() ),
    mnIndex     ( nIndex ),
    mnLen       ( nLen )
{
@@ -640,7 +645,7 @@ MetaTextArrayAction::~MetaTextArrayAction()

void MetaTextArrayAction::Execute( OutputDevice* pOut )
{
    pOut->DrawTextArray( maStartPt, maStr, maDXAry, mnIndex, mnLen );
    pOut->DrawTextArray( maStartPt, maStr, maDXAry, maKashidaAry, mnIndex, mnLen );
}

rtl::Reference<MetaAction> MetaTextArrayAction::Clone() const
@@ -669,6 +674,11 @@ void MetaTextArrayAction::SetDXArray(std::vector<sal_Int32> aArray)
    maDXAry = std::move(aArray);
}

void MetaTextArrayAction::SetKashidaArray(std::vector<sal_Bool> aArray)
{
    maKashidaAry = std::move(aArray);
}

MetaStretchTextAction::MetaStretchTextAction() :
    MetaAction  ( MetaActionType::STRETCHTEXT ),
    mnWidth     ( 0 ),
diff --git a/vcl/source/gdi/pdfwriter.cxx b/vcl/source/gdi/pdfwriter.cxx
index 1a8d407..3e20c12 100644
--- a/vcl/source/gdi/pdfwriter.cxx
+++ b/vcl/source/gdi/pdfwriter.cxx
@@ -82,10 +82,11 @@ void PDFWriter::DrawTextArray(
                              const Point& rStartPt,
                              const OUString& rStr,
                              o3tl::span<const sal_Int32> pDXAry,
                              o3tl::span<const sal_Bool> pKashidaAry,
                              sal_Int32 nIndex,
                              sal_Int32 nLen )
{
    xImplementation->drawTextArray( rStartPt, rStr, pDXAry, nIndex, nLen );
    xImplementation->drawTextArray( rStartPt, rStr, pDXAry, pKashidaAry, nIndex, nLen );
}

void PDFWriter::DrawStretchText(
diff --git a/vcl/source/gdi/pdfwriter_impl.cxx b/vcl/source/gdi/pdfwriter_impl.cxx
index f16bdc0..f0413d6 100644
--- a/vcl/source/gdi/pdfwriter_impl.cxx
+++ b/vcl/source/gdi/pdfwriter_impl.cxx
@@ -6616,14 +6616,14 @@ void PDFWriterImpl::drawText( const Point& rPos, const OUString& rText, sal_Int3
    const SalLayoutGlyphs* layoutGlyphs = SalLayoutGlyphsCache::self()->
        GetLayoutGlyphs( this, rText, nIndex, nLen );
    std::unique_ptr<SalLayout> pLayout = ImplLayout( rText, nIndex, nLen, rPos,
        0, {}, SalLayoutFlags::NONE, nullptr, layoutGlyphs );
        0, {}, {}, SalLayoutFlags::NONE, nullptr, layoutGlyphs );
    if( pLayout )
    {
        drawLayout( *pLayout, rText, bTextLines );
    }
}

void PDFWriterImpl::drawTextArray( const Point& rPos, const OUString& rText, o3tl::span<const sal_Int32> pDXArray, sal_Int32 nIndex, sal_Int32 nLen )
void PDFWriterImpl::drawTextArray( const Point& rPos, const OUString& rText, o3tl::span<const sal_Int32> pDXArray, o3tl::span<const sal_Bool> pKashidaArray, sal_Int32 nIndex, sal_Int32 nLen )
{
    MARK( "drawText with array" );

@@ -6633,7 +6633,7 @@ void PDFWriterImpl::drawTextArray( const Point& rPos, const OUString& rText, o3t
    // this also enforces font substitution and sets the font on SalGraphics
    const SalLayoutGlyphs* layoutGlyphs = SalLayoutGlyphsCache::self()->
        GetLayoutGlyphs( this, rText, nIndex, nLen );
    std::unique_ptr<SalLayout> pLayout = ImplLayout( rText, nIndex, nLen, rPos, 0, pDXArray,
    std::unique_ptr<SalLayout> pLayout = ImplLayout( rText, nIndex, nLen, rPos, 0, pDXArray, pKashidaArray,
        SalLayoutFlags::NONE, nullptr, layoutGlyphs );
    if( pLayout )
    {
@@ -6652,7 +6652,7 @@ void PDFWriterImpl::drawStretchText( const Point& rPos, sal_uLong nWidth, const 
    const SalLayoutGlyphs* layoutGlyphs = SalLayoutGlyphsCache::self()->
        GetLayoutGlyphs( this, rText, nIndex, nLen, nWidth );
    std::unique_ptr<SalLayout> pLayout = ImplLayout( rText, nIndex, nLen, rPos, nWidth,
        {}, SalLayoutFlags::NONE, nullptr, layoutGlyphs );
        {}, {}, SalLayoutFlags::NONE, nullptr, layoutGlyphs );
    if( pLayout )
    {
        drawLayout( *pLayout, rText, true );
diff --git a/vcl/source/gdi/pdfwriter_impl2.cxx b/vcl/source/gdi/pdfwriter_impl2.cxx
index f4c60a6..cac3813 100644
--- a/vcl/source/gdi/pdfwriter_impl2.cxx
+++ b/vcl/source/gdi/pdfwriter_impl2.cxx
@@ -813,7 +813,7 @@ void PDFWriterImpl::playMetafile( const GDIMetaFile& i_rMtf, vcl::PDFExtOutDevDa
                case MetaActionType::TEXTARRAY:
                {
                    const MetaTextArrayAction* pA = static_cast<const MetaTextArrayAction*>(pAction);
                    m_rOuterFace.DrawTextArray( pA->GetPoint(), pA->GetText(), pA->GetDXArray(), pA->GetIndex(), pA->GetLen() );
                    m_rOuterFace.DrawTextArray( pA->GetPoint(), pA->GetText(), pA->GetDXArray(), pA->GetKashidaArray(), pA->GetIndex(), pA->GetLen() );
                }
                break;

diff --git a/vcl/source/gdi/textlayout.cxx b/vcl/source/gdi/textlayout.cxx
index 416c683..25bf477 100644
--- a/vcl/source/gdi/textlayout.cxx
+++ b/vcl/source/gdi/textlayout.cxx
@@ -209,7 +209,7 @@ namespace vcl

        std::vector<sal_Int32> aCharWidths;
        tools::Long nTextWidth = GetTextArray( _rText, &aCharWidths, _nStartIndex, _nLength );
        m_rTargetDevice.DrawTextArray( _rStartPoint, _rText, aCharWidths, _nStartIndex, _nLength );
        m_rTargetDevice.DrawTextArray( _rStartPoint, _rText, aCharWidths, {}, _nStartIndex, _nLength );

        m_aCompleteTextRect.Union( tools::Rectangle( _rStartPoint, Size( nTextWidth, m_rTargetDevice.GetTextHeight() ) ) );
    }
diff --git a/vcl/source/outdev/text.cxx b/vcl/source/outdev/text.cxx
index 2f7199f..06f43fa 100644
--- a/vcl/source/outdev/text.cxx
+++ b/vcl/source/outdev/text.cxx
@@ -872,7 +872,7 @@ void OutputDevice::DrawText( const Point& rStartPt, const OUString& rStr,
        if(mpFontInstance->mpConversion)
            pLayoutCache = nullptr;

    std::unique_ptr<SalLayout> pSalLayout = ImplLayout(rStr, nIndex, nLen, rStartPt, 0, {}, eDefaultLayout, nullptr, pLayoutCache);
    std::unique_ptr<SalLayout> pSalLayout = ImplLayout(rStr, nIndex, nLen, rStartPt, 0, {}, {}, eDefaultLayout, nullptr, pLayoutCache);
    if(pSalLayout)
    {
        ImplDrawText( *pSalLayout );
@@ -921,6 +921,7 @@ float OutputDevice::approximate_digit_width() const

void OutputDevice::DrawTextArray( const Point& rStartPt, const OUString& rStr,
                                  o3tl::span<const sal_Int32> pDXAry,
                                  o3tl::span<const sal_Bool> pKashidaAry,
                                  sal_Int32 nIndex, sal_Int32 nLen, SalLayoutFlags flags,
                                  const SalLayoutGlyphs* pSalLayoutCache )
{
@@ -931,7 +932,7 @@ void OutputDevice::DrawTextArray( const Point& rStartPt, const OUString& rStr,
        nLen = rStr.getLength() - nIndex;
    }
    if ( mpMetaFile )
        mpMetaFile->AddAction( new MetaTextArrayAction( rStartPt, rStr, pDXAry, nIndex, nLen ) );
        mpMetaFile->AddAction( new MetaTextArrayAction( rStartPt, rStr, pDXAry, pKashidaAry, nIndex, nLen ) );

    if ( !IsDeviceOutputNecessary() )
        return;
@@ -943,14 +944,14 @@ void OutputDevice::DrawTextArray( const Point& rStartPt, const OUString& rStr,
    if( mbOutputClipped )
        return;

    std::unique_ptr<SalLayout> pSalLayout = ImplLayout(rStr, nIndex, nLen, rStartPt, 0, pDXAry, flags, nullptr, pSalLayoutCache);
    std::unique_ptr<SalLayout> pSalLayout = ImplLayout(rStr, nIndex, nLen, rStartPt, 0, pDXAry, pKashidaAry, flags, nullptr, pSalLayoutCache);
    if( pSalLayout )
    {
        ImplDrawText( *pSalLayout );
    }

    if( mpAlphaVDev )
        mpAlphaVDev->DrawTextArray( rStartPt, rStr, pDXAry, nIndex, nLen, flags );
        mpAlphaVDev->DrawTextArray( rStartPt, rStr, pDXAry, pKashidaAry, nIndex, nLen, flags );
}

tools::Long OutputDevice::GetTextArray( const OUString& rStr, std::vector<sal_Int32>* pDXAry,
@@ -968,7 +969,7 @@ tools::Long OutputDevice::GetTextArray( const OUString& rStr, std::vector<sal_In

    // do layout
    std::unique_ptr<SalLayout> pSalLayout = ImplLayout(rStr, nIndex, nLen,
            Point(0,0), 0, {}, eDefaultLayout, pLayoutCache, pSalLayoutCache);
            Point(0,0), 0, {}, {}, eDefaultLayout, pLayoutCache, pSalLayoutCache);
    if( !pSalLayout )
    {
        // The caller expects this to init the elements of pDXAry.
@@ -1076,7 +1077,7 @@ void OutputDevice::GetCaretPositions( const OUString& rStr, sal_Int32* pCaretXAr
        nLen = rStr.getLength() - nIndex;

    // layout complex text
    std::unique_ptr<SalLayout> pSalLayout = ImplLayout(rStr, nIndex, nLen, Point(0, 0), 0, {},
    std::unique_ptr<SalLayout> pSalLayout = ImplLayout(rStr, nIndex, nLen, Point(0, 0), 0, {}, {},
                                                       eDefaultLayout, nullptr, pGlyphs);
    if( !pSalLayout )
    {
@@ -1312,7 +1313,9 @@ OutputDevice::FontMappingUseData OutputDevice::FinishTrackingFontMappingUse()
std::unique_ptr<SalLayout> OutputDevice::ImplLayout(const OUString& rOrigStr,
                                    sal_Int32 nMinIndex, sal_Int32 nLen,
                                    const Point& rLogicalPos, tools::Long nLogicalWidth,
                                    o3tl::span<const sal_Int32> pDXArray, SalLayoutFlags flags,
                                    o3tl::span<const sal_Int32> pDXArray,
                                    o3tl::span<const sal_Bool> pKashidaArray,
                                    SalLayoutFlags flags,
         vcl::text::TextLayoutCache const* pLayoutCache,
         const SalLayoutGlyphs* pGlyphs) const
{
@@ -1427,6 +1430,9 @@ std::unique_ptr<SalLayout> OutputDevice::ImplLayout(const OUString& rOrigStr,
        }
    }

    if (!pKashidaArray.empty())
        aLayoutArgs.SetKashidaArray(pKashidaArray.data());

    // get matching layout object for base font
    std::unique_ptr<SalLayout> pSalLayout = mpGraphics->GetTextLayout(0);

@@ -1505,7 +1511,7 @@ sal_Int32 OutputDevice::GetTextBreak( const OUString& rStr, tools::Long nTextWid
         const SalLayoutGlyphs* pGlyphs) const
{
    std::unique_ptr<SalLayout> pSalLayout = ImplLayout( rStr, nIndex, nLen,
            Point(0,0), 0, {}, eDefaultLayout, pLayoutCache, pGlyphs);
            Point(0,0), 0, {}, {}, eDefaultLayout, pLayoutCache, pGlyphs);
    sal_Int32 nRetVal = -1;
    if( pSalLayout )
    {
@@ -1539,7 +1545,7 @@ sal_Int32 OutputDevice::GetTextBreak( const OUString& rStr, tools::Long nTextWid
    rHyphenPos = -1;

    std::unique_ptr<SalLayout> pSalLayout = ImplLayout( rStr, nIndex, nLen,
            Point(0,0), 0, {}, eDefaultLayout, pLayoutCache, pGlyphs);
            Point(0,0), 0, {}, {}, eDefaultLayout, pLayoutCache, pGlyphs);
    sal_Int32 nRetVal = -1;
    if( pSalLayout )
    {
@@ -2397,6 +2403,7 @@ bool OutputDevice::GetTextBoundRect( tools::Rectangle& rRect,
                                         const OUString& rStr, sal_Int32 nBase,
                                         sal_Int32 nIndex, sal_Int32 nLen,
                                         sal_uLong nLayoutWidth, o3tl::span<const sal_Int32> pDXAry,
                                         o3tl::span<const sal_Bool> pKashidaAry,
                                         const SalLayoutGlyphs* pGlyphs ) const
{
    bool bRet = false;
@@ -2410,7 +2417,7 @@ bool OutputDevice::GetTextBoundRect( tools::Rectangle& rRect,
    {
        sal_Int32 nStart = std::min( nBase, nIndex );
        sal_Int32 nOfsLen = std::max( nBase, nIndex ) - nStart;
        pSalLayout = ImplLayout( rStr, nStart, nOfsLen, aPoint, nLayoutWidth, pDXAry );
        pSalLayout = ImplLayout( rStr, nStart, nOfsLen, aPoint, nLayoutWidth, pDXAry, pKashidaAry );
        if( pSalLayout )
        {
            nXOffset = pSalLayout->GetTextWidth();
@@ -2421,7 +2428,7 @@ bool OutputDevice::GetTextBoundRect( tools::Rectangle& rRect,
        }
    }

    pSalLayout = ImplLayout(rStr, nIndex, nLen, aPoint, nLayoutWidth, pDXAry, eDefaultLayout,
    pSalLayout = ImplLayout(rStr, nIndex, nLen, aPoint, nLayoutWidth, pDXAry, pKashidaAry, eDefaultLayout,
                            nullptr, pGlyphs);
    if( pSalLayout )
    {
@@ -2461,7 +2468,9 @@ bool OutputDevice::GetTextBoundRect( tools::Rectangle& rRect,
bool OutputDevice::GetTextOutlines( basegfx::B2DPolyPolygonVector& rVector,
                                        const OUString& rStr, sal_Int32 nBase,
                                        sal_Int32 nIndex, sal_Int32 nLen,
                                        sal_uLong nLayoutWidth, o3tl::span<const sal_Int32> pDXArray ) const
                                        sal_uLong nLayoutWidth,
                                        o3tl::span<const sal_Int32> pDXArray,
                                        o3tl::span<const sal_Bool> pKashidaArray ) const
{
    if (!InitFont())
        return false;
@@ -2491,7 +2500,7 @@ bool OutputDevice::GetTextOutlines( basegfx::B2DPolyPolygonVector& rVector,
    {
        sal_Int32 nStart = std::min( nBase, nIndex );
        sal_Int32 nOfsLen = std::max( nBase, nIndex ) - nStart;
        pSalLayout = ImplLayout( rStr, nStart, nOfsLen, Point(0,0), nLayoutWidth, pDXArray );
        pSalLayout = ImplLayout( rStr, nStart, nOfsLen, Point(0,0), nLayoutWidth, pDXArray, pKashidaArray);
        if( pSalLayout )
        {
            nXOffset = pSalLayout->GetTextWidth();
@@ -2502,7 +2511,7 @@ bool OutputDevice::GetTextOutlines( basegfx::B2DPolyPolygonVector& rVector,
        }
    }

    pSalLayout = ImplLayout( rStr, nIndex, nLen, Point(0,0), nLayoutWidth, pDXArray );
    pSalLayout = ImplLayout( rStr, nIndex, nLen, Point(0,0), nLayoutWidth, pDXArray, pKashidaArray );
    if( pSalLayout )
    {
        bRet = pSalLayout->GetOutline(rVector);
@@ -2548,14 +2557,15 @@ bool OutputDevice::GetTextOutlines( basegfx::B2DPolyPolygonVector& rVector,
bool OutputDevice::GetTextOutlines( PolyPolyVector& rResultVector,
                                        const OUString& rStr, sal_Int32 nBase,
                                        sal_Int32 nIndex, sal_Int32 nLen,
                                        sal_uLong nLayoutWidth, o3tl::span<const sal_Int32> pDXArray ) const
                                        sal_uLong nLayoutWidth, o3tl::span<const sal_Int32> pDXArray,
                                        o3tl::span<const sal_Bool> pKashidaArray ) const
{
    rResultVector.clear();

    // get the basegfx polypolygon vector
    basegfx::B2DPolyPolygonVector aB2DPolyPolyVector;
    if( !GetTextOutlines( aB2DPolyPolyVector, rStr, nBase, nIndex, nLen,
                         nLayoutWidth, pDXArray ) )
                         nLayoutWidth, pDXArray, pKashidaArray ) )
        return false;

    // convert to a tool polypolygon vector
diff --git a/vcl/source/outdev/transparent.cxx b/vcl/source/outdev/transparent.cxx
index a4019ca..5364987 100644
--- a/vcl/source/outdev/transparent.cxx
+++ b/vcl/source/outdev/transparent.cxx
@@ -1193,7 +1193,7 @@ tools::Rectangle ImplCalcActionBounds( const MetaAction& rAct, const OutputDevic
                // #105987# ImplLayout takes everything in logical coordinates
                std::unique_ptr<SalLayout> pSalLayout = rOut.ImplLayout( rTextAct.GetText(), rTextAct.GetIndex(),
                                                         rTextAct.GetLen(), rTextAct.GetPoint(),
                                                         0, rTextAct.GetDXArray());
                                                         0, rTextAct.GetDXArray(), rTextAct.GetKashidaArray() );
                if( pSalLayout )
                {
                    tools::Rectangle aBoundRect( rOut.ImplGetTextBoundRect( *pSalLayout ) );
diff --git a/vcl/source/text/ImplLayoutArgs.cxx b/vcl/source/text/ImplLayoutArgs.cxx
index 80e85a6..45b951a 100644
--- a/vcl/source/text/ImplLayoutArgs.cxx
+++ b/vcl/source/text/ImplLayoutArgs.cxx
@@ -39,6 +39,7 @@ ImplLayoutArgs::ImplLayoutArgs(const OUString& rStr, int nMinCharPos, int nEndCh
    , m_pTextLayoutCache(pLayoutCache)
    , mpDXArray(nullptr)
    , mpAltNaturalDXArray(nullptr)
    , mpKashidaArray(nullptr)
    , mnLayoutWidth(0)
    , mnOrientation(0)
{
@@ -97,6 +98,11 @@ void ImplLayoutArgs::SetAltNaturalDXArray(double const* pDXArray)
    mpAltNaturalDXArray = pDXArray;
}

void ImplLayoutArgs::SetKashidaArray(sal_Bool const* pKashidaArray)
{
    mpKashidaArray = pKashidaArray;
}

void ImplLayoutArgs::SetOrientation(Degree10 nOrientation) { mnOrientation = nOrientation; }

void ImplLayoutArgs::ResetPos() { maRuns.ResetPos(); }
@@ -269,7 +275,6 @@ std::ostream& operator<<(std::ostream& s, vcl::text::ImplLayoutArgs const& rArgs
        TEST(DisableKerning);
        TEST(KerningAsian);
        TEST(Vertical);
        TEST(KashidaJustification);
        TEST(ForFallback);
#undef TEST
        s << "}";
@@ -333,6 +338,31 @@ std::ostream& operator<<(std::ostream& s, vcl::text::ImplLayoutArgs const& rArgs
    else
        s << "NULL";

    s << ",KashidaArray=";
    if (rArgs.mpKashidaArray)
    {
        s << "[";
        int count = rArgs.mnEndCharPos - rArgs.mnMinCharPos;
        lim = count;
        if (lim > 10)
            lim = 7;
        for (int i = 0; i < lim; i++)
        {
            s << rArgs.mpKashidaArray[i];
            if (i < lim - 1)
                s << ",";
        }
        if (count > lim)
        {
            if (count > lim + 1)
                s << "...";
            s << rArgs.mpKashidaArray[count - 1];
        }
        s << "]";
    }
    else
        s << "NULL";

    s << ",LayoutWidth=" << rArgs.mnLayoutWidth;

    s << "}";
diff --git a/vcl/source/window/menuitemlist.cxx b/vcl/source/window/menuitemlist.cxx
index 173a620..0efae5b 100644
--- a/vcl/source/window/menuitemlist.cxx
+++ b/vcl/source/window/menuitemlist.cxx
@@ -48,7 +48,7 @@ SalLayoutGlyphs* MenuItemData::GetTextGlyphs(const OutputDevice* pOutputDevice)
    OUString aNonMnemonicString = OutputDevice::GetNonMnemonicString(aText);
    std::unique_ptr<SalLayout> pLayout
        = pOutputDevice->ImplLayout(aNonMnemonicString, 0, aNonMnemonicString.getLength(),
                                    Point(0, 0), 0, {}, SalLayoutFlags::GlyphItemsOnly);
                                    Point(0, 0), 0, {}, {}, SalLayoutFlags::GlyphItemsOnly);
    if (!pLayout)
        return nullptr;

diff --git a/vcl/source/window/status.cxx b/vcl/source/window/status.cxx
index 8c701b4..81ce41d 100644
--- a/vcl/source/window/status.cxx
+++ b/vcl/source/window/status.cxx
@@ -79,7 +79,7 @@ SalLayoutGlyphs* ImplStatusItem::GetTextGlyphs(const OutputDevice* outputDevice)
    if(!mLayoutGlyphsCache.has_value())
    {
        std::unique_ptr<SalLayout> pSalLayout = outputDevice->ImplLayout(
            maText, 0, -1, Point(0, 0), 0, {}, SalLayoutFlags::GlyphItemsOnly);
            maText, 0, -1, Point(0, 0), 0, {}, {}, SalLayoutFlags::GlyphItemsOnly);
        mLayoutGlyphsCache = pSalLayout ? pSalLayout->GetGlyphs() : SalLayoutGlyphs();
    }
    return mLayoutGlyphsCache->IsValid() ? &mLayoutGlyphsCache.value() : nullptr;