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

#include <sal/config.h>

#include <sal/log.hxx>
#include <basegfx/matrix/b2dhommatrix.hxx>
#include <basegfx/matrix/b2dhommatrixtools.hxx>
#include <tools/lineend.hxx>
#include <tools/debug.hxx>
#include <comphelper/configuration.hxx>

#include <vcl/ctrl.hxx>
#include <vcl/metaact.hxx>
#include <vcl/metric.hxx>
#include <vcl/mnemonic.hxx>
#include <vcl/textrectinfo.hxx>
#include <vcl/virdev.hxx>
#include <vcl/sysdata.hxx>

#include <ImplLayoutArgs.hxx>
#include <ImplOutDevData.hxx>
#include <drawmode.hxx>
#include <salgdi.hxx>
#include <svdata.hxx>
#include <textlayout.hxx>
#include <textlineinfo.hxx>
#include <impglyphitem.hxx>
#include <TextLayoutCache.hxx>
#include <font/PhysicalFontFace.hxx>

#include <memory>
#include <optional>

#define TEXT_DRAW_ELLIPSIS  (DrawTextFlags::EndEllipsis | DrawTextFlags::PathEllipsis | DrawTextFlags::NewsEllipsis)

void OutputDevice::SetLayoutMode( vcl::text::ComplexTextLayoutFlags nTextLayoutMode )
{
    if( mpMetaFile )
        mpMetaFile->AddAction( new MetaLayoutModeAction( nTextLayoutMode ) );

    mnTextLayoutMode = nTextLayoutMode;
}

void OutputDevice::SetDigitLanguage( LanguageType eTextLanguage )
{
    if( mpMetaFile )
        mpMetaFile->AddAction( new MetaTextLanguageAction( eTextLanguage ) );

    meTextLanguage = eTextLanguage;
}

void OutputDevice::ImplInitTextColor()
{
    DBG_TESTSOLARMUTEX();

    if ( mbInitTextColor )
    {
        mpGraphics->SetTextColor( GetTextColor() );
        mbInitTextColor = false;
    }
}

OUString OutputDevice::GetEllipsisString( const OUString& rOrigStr, tools::Long nMaxWidth,
                                        DrawTextFlags nStyle ) const
{
    vcl::DefaultTextLayout aTextLayout(*const_cast< OutputDevice* >(this));
    return aTextLayout.GetEllipsisString(rOrigStr, nMaxWidth, nStyle);
}

void OutputDevice::ImplDrawTextRect( tools::Long nBaseX, tools::Long nBaseY,
                                     tools::Long nDistX, tools::Long nDistY, tools::Long nWidth, tools::Long nHeight )
{
    tools::Long nX = nDistX;
    tools::Long nY = nDistY;

    Degree10 nOrientation = mpFontInstance->mnOrientation;
    if ( nOrientation )
    {
        // Rotate rect without rounding problems for 90 degree rotations
        if ( !(nOrientation % 900_deg10) )
        {
            if ( nOrientation == 900_deg10 )
            {
                tools::Long nTemp = nX;
                nX = nY;
                nY = -nTemp;
                nTemp = nWidth;
                nWidth = nHeight;
                nHeight = nTemp;
                nY -= nHeight;
            }
            else if ( nOrientation == 1800_deg10 )
            {
                nX = -nX;
                nY = -nY;
                nX -= nWidth;
                nY -= nHeight;
            }
            else /* ( nOrientation == 2700 ) */
            {
                tools::Long nTemp = nX;
                nX = -nY;
                nY = nTemp;
                nTemp = nWidth;
                nWidth = nHeight;
                nHeight = nTemp;
                nX -= nWidth;
            }
        }
        else
        {
            nX += nBaseX;
            nY += nBaseY;
            // inflate because polygons are drawn smaller
            tools::Rectangle aRect( Point( nX, nY ), Size( nWidth+1, nHeight+1 ) );
            tools::Polygon   aPoly( aRect );
            aPoly.Rotate( Point( nBaseX, nBaseY ), mpFontInstance->mnOrientation );
            ImplDrawPolygon( aPoly );
            return;
        }
    }

    nX += nBaseX;
    nY += nBaseY;
    mpGraphics->DrawRect( nX, nY, nWidth, nHeight, *this ); // original code

}

void OutputDevice::ImplDrawTextBackground( const SalLayout& rSalLayout )
{
    const double nWidth = rSalLayout.GetTextWidth();
    const basegfx::B2DPoint aBase = rSalLayout.DrawBase();
    const tools::Long nX = aBase.getX();
    const tools::Long nY = aBase.getY();

    if ( mbLineColor || mbInitLineColor )
    {
        mpGraphics->SetLineColor();
        mbInitLineColor = true;
    }
    mpGraphics->SetFillColor( GetTextFillColor() );
    mbInitFillColor = true;

    ImplDrawTextRect( nX, nY, 0, -(mpFontInstance->mxFontMetric->GetAscent() + mnEmphasisAscent),
                      nWidth,
                      mpFontInstance->mnLineHeight+mnEmphasisAscent+mnEmphasisDescent );
}

tools::Rectangle OutputDevice::ImplGetTextBoundRect( const SalLayout& rSalLayout ) const
{
    basegfx::B2DPoint aPoint = rSalLayout.GetDrawPosition();
    tools::Long nX = aPoint.getX();
    tools::Long nY = aPoint.getY();

    double nWidth = rSalLayout.GetTextWidth();
    tools::Long nHeight = mpFontInstance->mnLineHeight + mnEmphasisAscent + mnEmphasisDescent;

    nY -= mpFontInstance->mxFontMetric->GetAscent() + mnEmphasisAscent;

    if ( mpFontInstance->mnOrientation )
    {
        tools::Long nBaseX = nX, nBaseY = nY;
        if ( !(mpFontInstance->mnOrientation % 900_deg10) )
        {
            tools::Long nX2 = nX+nWidth;
            tools::Long nY2 = nY+nHeight;

            Point aBasePt( nBaseX, nBaseY );
            aBasePt.RotateAround( nX, nY, mpFontInstance->mnOrientation );
            aBasePt.RotateAround( nX2, nY2, mpFontInstance->mnOrientation );
            nWidth = nX2-nX;
            nHeight = nY2-nY;
        }
        else
        {
            // inflate by +1+1 because polygons are drawn smaller
            tools::Rectangle aRect( Point( nX, nY ), Size( nWidth+1, nHeight+1 ) );
            tools::Polygon   aPoly( aRect );
            aPoly.Rotate( Point( nBaseX, nBaseY ), mpFontInstance->mnOrientation );
            return aPoly.GetBoundRect();
        }
    }

    return tools::Rectangle( Point( nX, nY ), Size( nWidth, nHeight ) );
}

bool OutputDevice::ImplDrawRotateText( SalLayout& rSalLayout )
{
    tools::Long nX = rSalLayout.DrawBase().getX();
    tools::Long nY = rSalLayout.DrawBase().getY();

    tools::Rectangle aBoundRect;
    rSalLayout.DrawBase() = basegfx::B2DPoint( 0, 0 );
    rSalLayout.DrawOffset() = basegfx::B2DPoint{ 0, 0 };
    if (basegfx::B2DRectangle r; rSalLayout.GetBoundRect(r))
    {
        aBoundRect = SalLayout::BoundRect2Rectangle(r);
    }
    else
    {
        // guess vertical text extents if GetBoundRect failed
        double nRight = rSalLayout.GetTextWidth();
        tools::Long nTop = mpFontInstance->mxFontMetric->GetAscent() + mnEmphasisAscent;
        tools::Long nHeight = mpFontInstance->mnLineHeight + mnEmphasisAscent + mnEmphasisDescent;
        aBoundRect = tools::Rectangle( 0, -nTop, nRight, nHeight - nTop );
    }

    // cache virtual device for rotation
    if (!mpOutDevData->mpRotateDev)
        mpOutDevData->mpRotateDev = VclPtr<VirtualDevice>::Create(*this);
    VirtualDevice* pVDev = mpOutDevData->mpRotateDev;

    // size it accordingly
    if( !pVDev->SetOutputSizePixel( aBoundRect.GetSize() ) )
        return false;

    const vcl::font::FontSelectPattern& rPattern = mpFontInstance->GetFontSelectPattern();
    vcl::Font aFont( GetFont() );
    aFont.SetOrientation( 0_deg10 );
    aFont.SetFontSize( Size( rPattern.mnWidth, rPattern.mnHeight ) );
    pVDev->SetFont( aFont );
    pVDev->SetTextColor( COL_BLACK );
    pVDev->SetTextFillColor();
    if (!pVDev->InitFont())
        return false;
    pVDev->ImplInitTextColor();

    // draw text into upper left corner
    rSalLayout.DrawBase().adjustX(-aBoundRect.Left());
    rSalLayout.DrawBase().adjustY(-aBoundRect.Top());
    rSalLayout.DrawText( *pVDev->mpGraphics );

    Bitmap aBmp = pVDev->GetBitmap( Point(), aBoundRect.GetSize() );
    if ( aBmp.IsEmpty() || !aBmp.Rotate( mpFontInstance->mnOwnOrientation, COL_WHITE ) )
        return false;

    // calculate rotation offset
    tools::Polygon aPoly( aBoundRect );
    aPoly.Rotate( Point(), mpFontInstance->mnOwnOrientation );
    Point aPoint = aPoly.GetBoundRect().TopLeft();
    aPoint += Point( nX, nY );

    // mask output with text colored bitmap
    GDIMetaFile* pOldMetaFile = mpMetaFile;
    tools::Long nOldOffX = mnOutOffX;
    tools::Long nOldOffY = mnOutOffY;
    bool bOldMap = mbMap;

    mnOutOffX   = 0;
    mnOutOffY   = 0;
    mpMetaFile  = nullptr;
    EnableMapMode( false );

    DrawMask( aPoint, aBmp, GetTextColor() );

    EnableMapMode( bOldMap );
    mnOutOffX   = nOldOffX;
    mnOutOffY   = nOldOffY;
    mpMetaFile  = pOldMetaFile;

    return true;
}

void OutputDevice::ImplDrawTextDirect( SalLayout& rSalLayout,
                                       bool bTextLines)
{
    if( mpFontInstance->mnOwnOrientation )
        if( ImplDrawRotateText( rSalLayout ) )
            return;

    auto nOldX = rSalLayout.DrawBase().getX();
    if( HasMirroredGraphics() )
    {
        tools::Long w = IsVirtual() ? mnOutWidth : mpGraphics->GetGraphicsWidth();
        auto x = rSalLayout.DrawBase().getX();
        rSalLayout.DrawBase().setX( w - 1 - x );
        if( !IsRTLEnabled() )
        {
            OutputDevice *pOutDevRef = this;
            // mirror this window back
            tools::Long devX = w-pOutDevRef->mnOutWidth-pOutDevRef->mnOutOffX;   // re-mirrored mnOutOffX
            rSalLayout.DrawBase().setX( devX + ( pOutDevRef->mnOutWidth - 1 - (rSalLayout.DrawBase().getX() - devX) ) ) ;
        }
    }
    else if( IsRTLEnabled() )
    {
        OutputDevice *pOutDevRef = this;

        // mirror this window back
        tools::Long devX = pOutDevRef->mnOutOffX;   // re-mirrored mnOutOffX
        rSalLayout.DrawBase().setX( pOutDevRef->mnOutWidth - 1 - (rSalLayout.DrawBase().getX() - devX) + devX );
    }

    rSalLayout.DrawText( *mpGraphics );
    rSalLayout.DrawBase().setX( nOldX );

    if( bTextLines )
        ImplDrawTextLines( rSalLayout,
            maFont.GetStrikeout(), maFont.GetUnderline(), maFont.GetOverline(),
            maFont.IsWordLineMode(), maFont.IsUnderlineAbove() );

    // emphasis marks
    if( maFont.GetEmphasisMark() & FontEmphasisMark::Style )
        ImplDrawEmphasisMarks( rSalLayout );
}

void OutputDevice::ImplDrawSpecialText( SalLayout& rSalLayout )
{
    Color       aOldColor           = GetTextColor();
    Color       aOldTextLineColor   = GetTextLineColor();
    Color       aOldOverlineColor   = GetOverlineColor();
    FontRelief  eRelief             = maFont.GetRelief();

    basegfx::B2DPoint aOrigPos = rSalLayout.DrawBase();
    if ( eRelief != FontRelief::NONE )
    {
        Color   aReliefColor( COL_LIGHTGRAY );
        Color   aTextColor( aOldColor );

        Color   aTextLineColor( aOldTextLineColor );
        Color   aOverlineColor( aOldOverlineColor );

        // we don't have an automatic color, so black is always drawn on white
        if ( aTextColor == COL_BLACK )
            aTextColor = COL_WHITE;
        if ( aTextLineColor == COL_BLACK )
            aTextLineColor = COL_WHITE;
        if ( aOverlineColor == COL_BLACK )
            aOverlineColor = COL_WHITE;

        // relief-color is black for white text, in all other cases
        // we set this to LightGray
        // coverity[copy_paste_error: FALSE] - this is intentional
        if ( aTextColor == COL_WHITE )
            aReliefColor = COL_BLACK;
        SetTextLineColor( aReliefColor );
        SetOverlineColor( aReliefColor );
        SetTextColor( aReliefColor );
        ImplInitTextColor();

        // calculate offset - for high resolution printers the offset
        // should be greater so that the effect is visible
        tools::Long nOff = 1;
        nOff += mnDPIX/300;

        if ( eRelief == FontRelief::Engraved )
            nOff = -nOff;

        auto aPrevOffset = rSalLayout.DrawOffset();
        rSalLayout.DrawOffset()
            += basegfx::B2DPoint{ static_cast<double>(nOff), static_cast<double>(nOff) };
        ImplDrawTextDirect(rSalLayout, mbTextLines);
        rSalLayout.DrawOffset() = aPrevOffset;

        SetTextLineColor( aTextLineColor );
        SetOverlineColor( aOverlineColor );
        SetTextColor( aTextColor );
        ImplInitTextColor();
        ImplDrawTextDirect( rSalLayout, mbTextLines );

        SetTextLineColor( aOldTextLineColor );
        SetOverlineColor( aOldOverlineColor );

        if ( aTextColor != aOldColor )
        {
            SetTextColor( aOldColor );
            ImplInitTextColor();
        }
    }
    else
    {
        if ( maFont.IsShadow() )
        {
            tools::Long nOff = 1 + ((mpFontInstance->mnLineHeight-24)/24);
            if ( maFont.IsOutline() )
                nOff++;
            SetTextLineColor();
            SetOverlineColor();
            if ( (GetTextColor() == COL_BLACK)
            ||   (GetTextColor().GetLuminance() < 8) )
                SetTextColor( COL_LIGHTGRAY );
            else
                SetTextColor( COL_BLACK );
            ImplInitTextColor();
            rSalLayout.DrawBase() += basegfx::B2DPoint( nOff, nOff );
            ImplDrawTextDirect( rSalLayout, mbTextLines );
            rSalLayout.DrawBase() -= basegfx::B2DPoint( nOff, nOff );
            SetTextColor( aOldColor );
            SetTextLineColor( aOldTextLineColor );
            SetOverlineColor( aOldOverlineColor );
            ImplInitTextColor();

            if ( !maFont.IsOutline() )
                ImplDrawTextDirect( rSalLayout, mbTextLines );
        }

        if ( maFont.IsOutline() )
        {
            rSalLayout.DrawBase() = aOrigPos + basegfx::B2DPoint(-1,-1);
            ImplDrawTextDirect( rSalLayout, mbTextLines );
            rSalLayout.DrawBase() = aOrigPos + basegfx::B2DPoint(+1,+1);
            ImplDrawTextDirect( rSalLayout, mbTextLines );
            rSalLayout.DrawBase() = aOrigPos + basegfx::B2DPoint(-1,+0);
            ImplDrawTextDirect( rSalLayout, mbTextLines );
            rSalLayout.DrawBase() = aOrigPos + basegfx::B2DPoint(-1,+1);
            ImplDrawTextDirect( rSalLayout, mbTextLines );
            rSalLayout.DrawBase() = aOrigPos + basegfx::B2DPoint(+0,+1);
            ImplDrawTextDirect( rSalLayout, mbTextLines );
            rSalLayout.DrawBase() = aOrigPos + basegfx::B2DPoint(+0,-1);
            ImplDrawTextDirect( rSalLayout, mbTextLines );
            rSalLayout.DrawBase() = aOrigPos + basegfx::B2DPoint(+1,-1);
            ImplDrawTextDirect( rSalLayout, mbTextLines );
            rSalLayout.DrawBase() = aOrigPos + basegfx::B2DPoint(+1,+0);
            ImplDrawTextDirect( rSalLayout, mbTextLines );
            rSalLayout.DrawBase() = aOrigPos;

            SetTextColor( COL_WHITE );
            SetTextLineColor( COL_WHITE );
            SetOverlineColor( COL_WHITE );
            ImplInitTextColor();
            ImplDrawTextDirect( rSalLayout, mbTextLines );
            SetTextColor( aOldColor );
            SetTextLineColor( aOldTextLineColor );
            SetOverlineColor( aOldOverlineColor );
            ImplInitTextColor();
        }
    }
}

void OutputDevice::ImplDrawText( SalLayout& rSalLayout )
{
    if( mbInitClipRegion )
        InitClipRegion();
    if( mbOutputClipped )
        return;
    if( mbInitTextColor )
        ImplInitTextColor();

    rSalLayout.DrawBase() += basegfx::B2DPoint(mnTextOffX, mnTextOffY);

    if( IsTextFillColor() )
        ImplDrawTextBackground( rSalLayout );

    if( mbTextSpecial )
        ImplDrawSpecialText( rSalLayout );
    else
        ImplDrawTextDirect( rSalLayout, mbTextLines );
}

void OutputDevice::SetTextColor( const Color& rColor )
{

    Color aColor(vcl::drawmode::GetTextColor(rColor, GetDrawMode(), GetSettings().GetStyleSettings()));

    if ( mpMetaFile )
        mpMetaFile->AddAction( new MetaTextColorAction( aColor ) );

    if ( maTextColor != aColor )
    {
        maTextColor = aColor;
        mbInitTextColor = true;
    }
}

void OutputDevice::SetTextFillColor()
{
    if ( mpMetaFile )
        mpMetaFile->AddAction( new MetaTextFillColorAction( Color(), false ) );

    if ( maFont.GetColor() != COL_TRANSPARENT ) {
        maFont.SetFillColor( COL_TRANSPARENT );
    }
    if ( !maFont.IsTransparent() )
        maFont.SetTransparent( true );
}

void OutputDevice::SetTextFillColor( const Color& rColor )
{
    Color aColor(vcl::drawmode::GetFillColor(rColor, GetDrawMode(), GetSettings().GetStyleSettings()));

    if ( mpMetaFile )
        mpMetaFile->AddAction( new MetaTextFillColorAction( aColor, true ) );

    if ( maFont.GetFillColor() != aColor )
        maFont.SetFillColor( aColor );
    if ( maFont.IsTransparent() != rColor.IsTransparent() )
        maFont.SetTransparent( rColor.IsTransparent() );
}

Color OutputDevice::GetTextFillColor() const
{
    if ( maFont.IsTransparent() )
        return COL_TRANSPARENT;
    else
        return maFont.GetFillColor();
}

void OutputDevice::SetTextAlign( TextAlign eAlign )
{
    if ( mpMetaFile )
        mpMetaFile->AddAction( new MetaTextAlignAction( eAlign ) );

    if ( maFont.GetAlignment() != eAlign )
    {
        maFont.SetAlignment( eAlign );
        mbNewFont = true;
    }
}

vcl::Region OutputDevice::GetOutputBoundsClipRegion() const
{
    return GetClipRegion();
}

const SalLayoutFlags eDefaultLayout = SalLayoutFlags::NONE;

void OutputDevice::DrawText( const Point& rStartPt, const OUString& rStr,
                             sal_Int32 nIndex, sal_Int32 nLen,
                             std::vector< tools::Rectangle >* pVector, OUString* pDisplayText,
                             const SalLayoutGlyphs* pLayoutCache
                             )
{
    assert(!is_double_buffered_window());

    if( (nLen < 0) || (nIndex + nLen > rStr.getLength()))
    {
        nLen = rStr.getLength() - nIndex;
    }

    if (mpOutDevData->mpRecordLayout)
    {
        pVector = &mpOutDevData->mpRecordLayout->m_aUnicodeBoundRects;
        pDisplayText = &mpOutDevData->mpRecordLayout->m_aDisplayText;
    }

#if OSL_DEBUG_LEVEL > 2
    SAL_INFO("vcl.gdi", "OutputDevice::DrawText(\"" << rStr << "\")");
#endif

    if ( mpMetaFile )
        mpMetaFile->AddAction( new MetaTextAction( rStartPt, rStr, nIndex, nLen ) );
    if( pVector )
    {
        vcl::Region aClip(GetOutputBoundsClipRegion());

        if (mpOutDevData->mpRecordLayout)
        {
            mpOutDevData->mpRecordLayout->m_aLineIndices.push_back( mpOutDevData->mpRecordLayout->m_aDisplayText.getLength() );
            aClip.Intersect( mpOutDevData->maRecordRect );
        }
        if( ! aClip.IsNull() )
        {
            std::vector< tools::Rectangle > aTmp;
            GetGlyphBoundRects( rStartPt, rStr, nIndex, nLen, aTmp );

            bool bInserted = false;
            for( std::vector< tools::Rectangle >::const_iterator it = aTmp.begin(); it != aTmp.end(); ++it, nIndex++ )
            {
                bool bAppend = false;

                if( aClip.Overlaps( *it ) )
                    bAppend = true;
                else if( rStr[ nIndex ] == ' ' && bInserted )
                {
                    std::vector< tools::Rectangle >::const_iterator next = it;
                    ++next;
                    if( next != aTmp.end() && aClip.Overlaps( *next ) )
                        bAppend = true;
                }

                if( bAppend )
                {
                    pVector->push_back( *it );
                    if( pDisplayText )
                        *pDisplayText += OUStringChar(rStr[ nIndex ]);
                    bInserted = true;
                }
            }
        }
        else
        {
            GetGlyphBoundRects( rStartPt, rStr, nIndex, nLen, *pVector );
            if( pDisplayText )
                *pDisplayText += rStr.subView( nIndex, nLen );
        }
    }

    if ( !IsDeviceOutputNecessary() || pVector )
        return;

    if(mpFontInstance)
        // do not use cache with modified string
        if(mpFontInstance->mpConversion)
            pLayoutCache = nullptr;

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

tools::Long OutputDevice::GetTextWidth( const OUString& rStr, sal_Int32 nIndex, sal_Int32 nLen,
     vcl::text::TextLayoutCache const*const pLayoutCache,
     SalLayoutGlyphs const*const pSalLayoutCache) const
{
    double nWidth = GetTextWidthDouble(rStr, nIndex, nLen, pLayoutCache, pSalLayoutCache);
    return basegfx::fround<tools::Long>(nWidth);
}

double OutputDevice::GetTextWidthDouble(const OUString& rStr, sal_Int32 nIndex, sal_Int32 nLen,
                                        vcl::text::TextLayoutCache const* const pLayoutCache,
                                        SalLayoutGlyphs const* const pSalLayoutCache) const
{
    return GetTextArray(rStr, nullptr, nIndex, nLen, false, pLayoutCache, pSalLayoutCache);
}

tools::Long OutputDevice::GetTextHeight() const
{
    if (!InitFont())
        return 0;

    tools::Long nHeight = mpFontInstance->mnLineHeight + mnEmphasisAscent + mnEmphasisDescent;

    if ( mbMap )
        nHeight = ImplDevicePixelToLogicHeight( nHeight );

    return nHeight;
}

double OutputDevice::GetTextHeightDouble() const
{
    if (!InitFont())
        return 0;

    tools::Long nHeight = mpFontInstance->mnLineHeight + mnEmphasisAscent + mnEmphasisDescent;

    return ImplDevicePixelToLogicHeightDouble(nHeight);
}

float OutputDevice::approximate_char_width() const
{
    //note pango uses "The quick brown fox jumps over the lazy dog." for english
    //and has a bunch of per-language strings which corresponds somewhat with
    //makeRepresentativeText in include/svtools/sampletext.hxx
    return GetTextWidth(u"aemnnxEM"_ustr) / 8.0;
}

float OutputDevice::approximate_digit_width() const
{
    return GetTextWidth(u"0123456789"_ustr) / 10.0;
}

void OutputDevice::DrawPartialTextArray(const Point& rStartPt, const OUString& rStr,
                                        KernArraySpan pDXArray,
                                        std::span<const sal_Bool> pKashidaArray, sal_Int32 nIndex,
                                        sal_Int32 nLen, sal_Int32 nPartIndex, sal_Int32 nPartLen,
                                        SalLayoutFlags flags, const SalLayoutGlyphs* pLayoutCache)
{
    assert(!is_double_buffered_window());

    if (nLen < 0 || nIndex + nLen >= rStr.getLength())
    {
        nLen = rStr.getLength() - nIndex;
    }

    if (nPartLen < 0 || nPartIndex + nPartLen >= rStr.getLength())
    {
        nPartLen = rStr.getLength() - nPartIndex;
    }

    if (mpMetaFile)
    {
        mpMetaFile->AddAction(new MetaTextArrayAction(rStartPt, rStr, pDXArray, pKashidaArray,
                                                      nPartIndex, nPartLen, nIndex, nLen));
    }

    if (!IsDeviceOutputNecessary())
        return;

    if (!mpGraphics && !AcquireGraphics())
        return;

    assert(mpGraphics);
    if (mbInitClipRegion)
        InitClipRegion();

    if (mbOutputClipped)
        return;

    // Adding the UnclusteredGlyphs flag during layout enables per-glyph styling.
    std::unique_ptr<SalLayout> pSalLayout
        = ImplLayout(rStr, nIndex, nLen, rStartPt, 0, pDXArray, pKashidaArray,
                     flags | SalLayoutFlags::UnclusteredGlyphs, nullptr, pLayoutCache,
                     /*pivot cluster*/ nPartIndex,
                     /*min cluster*/ nPartIndex,
                     /*end cluster*/ nPartIndex + nPartLen);

    if (pSalLayout)
    {
        ImplDrawText(*pSalLayout);
    }
}

void OutputDevice::DrawTextArray( const Point& rStartPt, const OUString& rStr,
                                  KernArraySpan pDXAry,
                                  std::span<const sal_Bool> pKashidaAry,
                                  sal_Int32 nIndex, sal_Int32 nLen, SalLayoutFlags flags,
                                  const SalLayoutGlyphs* pSalLayoutCache )
{
    assert(!is_double_buffered_window());

    if( nLen < 0 || nIndex + nLen >= rStr.getLength() )
    {
        nLen = rStr.getLength() - nIndex;
    }
    if ( mpMetaFile )
        mpMetaFile->AddAction( new MetaTextArrayAction( rStartPt, rStr, pDXAry, pKashidaAry, nIndex, nLen ) );

    if ( !IsDeviceOutputNecessary() )
        return;
    if( !mpGraphics && !AcquireGraphics() )
        return;
    assert(mpGraphics);
    if( mbInitClipRegion )
        InitClipRegion();
    if( mbOutputClipped )
        return;

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

double
OutputDevice::GetTextArray(const OUString& rStr, KernArray* pKernArray, sal_Int32 nIndex,
                           sal_Int32 nLen, bool bCaret,
                           vcl::text::TextLayoutCache const* const pLayoutCache,
                           SalLayoutGlyphs const* const pSalLayoutCache, std::optional<tools::Rectangle>* pBounds) const
{
    return GetPartialTextArray(rStr, pKernArray, nIndex, nLen, nIndex, nLen, bCaret, pLayoutCache,
                               pSalLayoutCache, pBounds);
}

double
OutputDevice::GetPartialTextArray(const OUString& rStr, KernArray* pKernArray, sal_Int32 nIndex,
                                  sal_Int32 nLen, sal_Int32 nPartIndex, sal_Int32 nPartLen,
                                  bool bCaret, const vcl::text::TextLayoutCache* pLayoutCache,
                                  const SalLayoutGlyphs* pSalLayoutCache, std::optional<tools::Rectangle>* pBounds) const
{
    if (nIndex >= rStr.getLength())
    {
        return {}; // TODO: this looks like a buggy caller?
    }

    if( nLen < 0 || nIndex + nLen >= rStr.getLength() )
    {
        nLen = rStr.getLength() - nIndex;
    }

    if (nPartLen < 0 || nPartIndex + nPartLen >= rStr.getLength())
    {
        nPartLen = rStr.getLength() - nPartIndex;
    }

    KernArray* pDXAry = pKernArray;

    // do layout
    std::unique_ptr<SalLayout> pSalLayout;
    if (nIndex == nPartIndex && nLen == nPartLen)
    {
        pSalLayout = ImplLayout(rStr, nIndex, nLen, Point{ 0, 0 }, 0, {}, {}, eDefaultLayout,
                                pLayoutCache, pSalLayoutCache);
    }
    else
    {
        pSalLayout = ImplLayout(rStr, nIndex, nLen, Point{ 0, 0 }, 0, {}, {}, eDefaultLayout,
                                pLayoutCache, pSalLayoutCache,
                                /*pivot cluster*/ nPartIndex,
                                /*min cluster*/ nPartIndex,
                                /*end cluster*/ nPartIndex + nPartLen);
    }

    if( !pSalLayout )
    {
        // The caller expects this to init the elements of pDXAry.
        // Adapting all the callers to check that GetTextArray succeeded seems
        // too much work.
        // Init here to 0 only in the (rare) error case, so that any missing
        // element init in the happy case will still be found by tools,
        // and hope that is sufficient.
        if (pDXAry)
        {
            pDXAry->resize(nPartLen);
            std::fill(pDXAry->begin(), pDXAry->end(), 0);
        }

        return {};
    }

    std::vector<double> aDXPixelArray;
    std::vector<double>* pDXPixelArray = nullptr;
    if(pDXAry)
    {
        aDXPixelArray.resize(nPartLen);
        pDXPixelArray = &aDXPixelArray;
    }

    double nWidth = 0.0;

    // Fall back to the unbounded DX array when there is no expanded layout context. This is
    // necessary for certain situations where characters are appended to the input string, such as
    // automatic ellipsis.
    if (nIndex == nPartIndex && nLen == nPartLen)
    {
        nWidth = pSalLayout->FillDXArray(pDXPixelArray, bCaret ? rStr : OUString());
    }
    else
    {
        nWidth = pSalLayout->FillPartialDXArray(pDXPixelArray, bCaret ? rStr : OUString(),
                                                nPartIndex - nIndex, nPartLen);
    }

    // convert virtual char widths to virtual absolute positions
    if( pDXPixelArray )
    {
        for (int i = 1; i < nPartLen; ++i)
        {
            (*pDXPixelArray)[i] += (*pDXPixelArray)[i - 1];
        }
    }

    // convert from font units to logical units
    if (pDXPixelArray)
    {
        assert(pKernArray && "pDXPixelArray depends on pKernArray existing");
        if (mbMap)
        {
            for (int i = 0; i < nPartLen; ++i)
                (*pDXPixelArray)[i] = ImplDevicePixelToLogicWidthDouble((*pDXPixelArray)[i]);
        }
    }

    if (pDXAry)
    {
        pDXAry->resize(nPartLen);
        for (int i = 0; i < nPartLen; ++i)
            (*pDXAry)[i] = (*pDXPixelArray)[i];
    }

    if (pBounds)
    {
        basegfx::B2DRectangle stRect;
        if (pSalLayout->GetBoundRect(stRect))
        {
            auto stRect2 = SalLayout::BoundRect2Rectangle(stRect);
            *pBounds = ImplDevicePixelToLogic(stRect2);
        }
    }

    return ImplDevicePixelToLogicWidthDouble(nWidth);
}

void OutputDevice::GetCaretPositions( const OUString& rStr, KernArray& rCaretPos,
                                      sal_Int32 nIndex, sal_Int32 nLen,
                                      const SalLayoutGlyphs* pGlyphs ) const
{

    if( nIndex >= rStr.getLength() )
        return;
    if( nIndex+nLen >= rStr.getLength() )
        nLen = rStr.getLength() - nIndex;

    sal_Int32 nCaretPos = nLen * 2;
    rCaretPos.resize(nCaretPos);

    // do layout
    std::unique_ptr<SalLayout> pSalLayout = ImplLayout(rStr, nIndex, nLen, Point(0, 0), 0, {}, {},
                                                       eDefaultLayout, nullptr, pGlyphs);
    if( !pSalLayout )
    {
        std::fill(rCaretPos.begin(), rCaretPos.end(), -1);
        return;
    }

    std::vector<double> aCaretPixelPos;
    pSalLayout->GetCaretPositions(aCaretPixelPos, rStr);

    // fixup unknown caret positions
    int i;
    for (i = 0; i < nCaretPos; ++i)
        if (aCaretPixelPos[i] >= 0)
            break;
    double nXPos = (i < nCaretPos) ? aCaretPixelPos[i] : -1;
    for (i = 0; i < nCaretPos; ++i)
    {
        if (aCaretPixelPos[i] >= 0)
            nXPos = aCaretPixelPos[i];
        else
            aCaretPixelPos[i] = nXPos;
    }

    // handle window mirroring
    if( IsRTLEnabled() )
    {
        double nWidth = pSalLayout->GetTextWidth();
        for (i = 0; i < nCaretPos; ++i)
            aCaretPixelPos[i] = nWidth - aCaretPixelPos[i] - 1;
    }

    // convert from font units to logical units
    if( mbMap )
    {
        for (i = 0; i < nCaretPos; ++i)
            aCaretPixelPos[i] = ImplDevicePixelToLogicWidthDouble(aCaretPixelPos[i]);
    }

    for (i = 0; i < nCaretPos; ++i)
        rCaretPos[i] = aCaretPixelPos[i];
}

void OutputDevice::DrawStretchText( const Point& rStartPt, sal_Int32 nWidth,
                                    const OUString& rStr,
                                    sal_Int32 nIndex, sal_Int32 nLen)
{
    assert(!is_double_buffered_window());

    if( (nLen < 0) || (nIndex + nLen >= rStr.getLength()))
    {
        nLen = rStr.getLength() - nIndex;
    }

    if ( mpMetaFile )
        mpMetaFile->AddAction( new MetaStretchTextAction( rStartPt, nWidth, rStr, nIndex, nLen ) );

    if ( !IsDeviceOutputNecessary() )
        return;

    std::unique_ptr<SalLayout> pSalLayout = ImplLayout(rStr, nIndex, nLen, rStartPt, nWidth);
    if( pSalLayout )
    {
        ImplDrawText( *pSalLayout );
    }
}

vcl::text::ImplLayoutArgs OutputDevice::ImplPrepareLayoutArgs( OUString& rStr,
                                                    const sal_Int32 nMinIndex, const sal_Int32 nLen,
                                                    double nPixelWidth,
                                                    SalLayoutFlags nLayoutFlags,
         vcl::text::TextLayoutCache const*const pLayoutCache) const
{
    assert(nMinIndex >= 0);
    assert(nLen >= 0);

    // get string length for calculating extents
    sal_Int32 nEndIndex = rStr.getLength();
    if( nMinIndex + nLen < nEndIndex )
        nEndIndex = nMinIndex + nLen;

    // don't bother if there is nothing to do
    if( nEndIndex < nMinIndex )
        nEndIndex = nMinIndex;

    nLayoutFlags |= GetBiDiLayoutFlags( rStr, nMinIndex, nEndIndex );

    if( !maFont.IsKerning() )
        nLayoutFlags |= SalLayoutFlags::DisableKerning;
    if( maFont.GetKerning() & FontKerning::Asian )
        nLayoutFlags |= SalLayoutFlags::KerningAsian;
    if( maFont.IsVertical() )
        nLayoutFlags |= SalLayoutFlags::Vertical;
    if( maFont.IsFixKerning() ||
        ( mpFontInstance && mpFontInstance->GetFontSelectPattern().GetPitch() == PITCH_FIXED ) )
        nLayoutFlags |= SalLayoutFlags::DisableLigatures;

    if( meTextLanguage ) //TODO: (mnTextLayoutMode & vcl::text::ComplexTextLayoutFlags::SubstituteDigits)
    {
        sal_Int32 nSubstringLen = nEndIndex - nMinIndex;
        rStr = LocalizeDigitsInString(rStr, meTextLanguage, nMinIndex, nSubstringLen);
        nEndIndex = nMinIndex + nSubstringLen;
    }

    // right align for RTL text, DRAWPOS_REVERSED, RTL window style
    bool bRightAlign = bool(mnTextLayoutMode & vcl::text::ComplexTextLayoutFlags::BiDiRtl);
    if( mnTextLayoutMode & vcl::text::ComplexTextLayoutFlags::TextOriginLeft )
        bRightAlign = false;
    else if ( mnTextLayoutMode & vcl::text::ComplexTextLayoutFlags::TextOriginRight )
        bRightAlign = true;
    // SSA: hack for western office, ie text get right aligned
    //      for debugging purposes of mirrored UI
    bool bRTLWindow = IsRTLEnabled();
    bRightAlign ^= bRTLWindow;
    if( bRightAlign )
        nLayoutFlags |= SalLayoutFlags::RightAlign;

    // set layout options
    vcl::text::ImplLayoutArgs aLayoutArgs(rStr, nMinIndex, nEndIndex, nLayoutFlags, maFont.GetLanguageTag(), pLayoutCache);

    Degree10 nOrientation = mpFontInstance ? mpFontInstance->mnOrientation : 0_deg10;
    aLayoutArgs.SetOrientation( nOrientation );

    aLayoutArgs.SetLayoutWidth( nPixelWidth );

    return aLayoutArgs;
}

SalLayoutFlags OutputDevice::GetBiDiLayoutFlags( std::u16string_view rStr,
                                                 const sal_Int32 nMinIndex,
                                                 const sal_Int32 nEndIndex ) const
{
    SalLayoutFlags nLayoutFlags = SalLayoutFlags::NONE;
    if( mnTextLayoutMode & vcl::text::ComplexTextLayoutFlags::BiDiRtl )
        nLayoutFlags |= SalLayoutFlags::BiDiRtl;
    if( mnTextLayoutMode & vcl::text::ComplexTextLayoutFlags::BiDiStrong )
        nLayoutFlags |= SalLayoutFlags::BiDiStrong;
    else if( !(mnTextLayoutMode & vcl::text::ComplexTextLayoutFlags::BiDiRtl) )
    {
        // Disable Bidi if no RTL hint and only known LTR codes used.
        bool bAllLtr = true;
        for (sal_Int32 i = nMinIndex; i < nEndIndex; i++)
        {
            // [0x0000, 0x052F] are Latin, Greek and Cyrillic.
            // [0x0370, 0x03FF] has a few holes as if Unicode 10.0.0, but
            //                  hopefully no RTL character will be encoded there.
            if (rStr[i] > 0x052F)
            {
                bAllLtr = false;
                break;
            }
        }
        if (bAllLtr)
            nLayoutFlags |= SalLayoutFlags::BiDiStrong;
    }
    return nLayoutFlags;
}

static OutputDevice::FontMappingUseData* fontMappingUseData = nullptr;

static inline bool IsTrackingFontMappingUse()
{
    return fontMappingUseData != nullptr;
}

static void TrackFontMappingUse( const vcl::Font& originalFont, const SalLayout* salLayout)
{
    assert(fontMappingUseData);
    OUString originalName = originalFont.GetStyleName().isEmpty()
        ? originalFont.GetFamilyName()
        : originalFont.GetFamilyName() + "/" + originalFont.GetStyleName();
    std::vector<OUString> usedFontNames;
    SalLayoutGlyphs glyphs = salLayout->GetGlyphs(); // includes all font fallbacks
    int level = 0;
    while( const SalLayoutGlyphsImpl* impl = glyphs.Impl(level++))
    {
        const vcl::font::PhysicalFontFace* face = impl->GetFont()->GetFontFace();
        OUString name = face->GetStyleName().isEmpty()
            ? face->GetFamilyName()
            : face->GetFamilyName() + "/" + face->GetStyleName();
        usedFontNames.push_back( name );
    }
    for( OutputDevice::FontMappingUseItem& item : *fontMappingUseData )
    {
        if( item.mOriginalFont == originalName && item.mUsedFonts == usedFontNames )
        {
            ++item.mCount;
            return;
        }
    }
    fontMappingUseData->push_back( { originalName, std::move(usedFontNames), 1 } );
}

void OutputDevice::StartTrackingFontMappingUse()
{
    delete fontMappingUseData;
    fontMappingUseData = new FontMappingUseData;
}

OutputDevice::FontMappingUseData OutputDevice::FinishTrackingFontMappingUse()
{
    if(!fontMappingUseData)
        return {};
    FontMappingUseData ret = std::move( *fontMappingUseData );
    delete fontMappingUseData;
    fontMappingUseData = nullptr;
    return ret;
}

std::unique_ptr<SalLayout> OutputDevice::ImplLayout(
    const OUString& rOrigStr, sal_Int32 nMinIndex, sal_Int32 nLen, const Point& rLogicalPos,
    tools::Long nLogicalWidth, KernArraySpan pDXArray, std::span<const sal_Bool> pKashidaArray,
    SalLayoutFlags flags, vcl::text::TextLayoutCache const* pLayoutCache,
    const SalLayoutGlyphs* pGlyphs, std::optional<sal_Int32> nDrawOriginCluster,
    std::optional<sal_Int32> nDrawMinCharPos, std::optional<sal_Int32> nDrawEndCharPos) const
{
    if (pGlyphs && !pGlyphs->IsValid())
    {
        SAL_WARN("vcl", "Trying to setup invalid cached glyphs - falling back to relayout!");
        pGlyphs = nullptr;
    }
#ifdef DBG_UTIL
    if (pGlyphs)
    {
        for( int level = 0;; ++level )
        {
            SalLayoutGlyphsImpl* glyphsImpl = pGlyphs->Impl(level);
            if(glyphsImpl == nullptr)
                break;
            // It is allowed to reuse only glyphs created with SalLayoutFlags::GlyphItemsOnly.
            // If the glyphs have already been used, the AdjustLayout() call below might have
            // altered them (MultiSalLayout::ImplAdjustMultiLayout() drops glyphs that need
            // fallback from the base layout, but then GenericSalLayout::LayoutText()
            // would not know to call SetNeedFallback()).
            assert(glyphsImpl->GetFlags() & SalLayoutFlags::GlyphItemsOnly);
        }
    }
#endif

    if (!InitFont())
        return nullptr;

    // check string index and length
    if( -1 == nLen || nMinIndex + nLen > rOrigStr.getLength() )
    {
        const sal_Int32 nNewLen = rOrigStr.getLength() - nMinIndex;
        if( nNewLen <= 0 )
            return nullptr;
        nLen = nNewLen;
    }

    OUString aStr = rOrigStr;

    // recode string if needed
    if( mpFontInstance->mpConversion ) {
        mpFontInstance->mpConversion->RecodeString( aStr, 0, aStr.getLength() );
        pLayoutCache = nullptr; // don't use cache with modified string!
        pGlyphs = nullptr;
    }

    double nPixelWidth = nLogicalWidth;
    if( nLogicalWidth && mbMap )
    {
        // convert from logical units to physical units
        nPixelWidth = ImplLogicWidthToDeviceSubPixel(nLogicalWidth);
    }

    vcl::text::ImplLayoutArgs aLayoutArgs = ImplPrepareLayoutArgs( aStr, nMinIndex, nLen,
            nPixelWidth, flags, pLayoutCache);

    if (nDrawOriginCluster.has_value())
    {
        aLayoutArgs.mnDrawOriginCluster = *nDrawOriginCluster;
    }

    if (nDrawMinCharPos.has_value())
    {
        aLayoutArgs.mnDrawMinCharPos = *nDrawMinCharPos;
    }

    if (nDrawEndCharPos.has_value())
    {
        aLayoutArgs.mnDrawEndCharPos = *nDrawEndCharPos;
    }

    double nEndGlyphCoord(0);
    if (!pDXArray.empty() || !pKashidaArray.empty())
    {
        // The provided advance and kashida arrays are indexed relative to the first visible cluster
        auto nJustMinCluster = nDrawMinCharPos.value_or(nMinIndex);
        auto nJustLen = nLen;
        if (nDrawEndCharPos.has_value())
        {
            nJustLen = *nDrawEndCharPos - nJustMinCluster;
        }

        JustificationData stJustification{ nJustMinCluster, nJustLen };

        if (!pDXArray.empty() && mbMap)
        {
            // convert from logical units to font units without rounding,
            // keeping accuracy for lower levels
            for (int i = 0; i < nJustLen; ++i)
            {
                stJustification.SetTotalAdvance(
                    nJustMinCluster + i,
                    ImplLogicWidthToDeviceSubPixel(pDXArray[i]));
            }

            nEndGlyphCoord = stJustification.GetTotalAdvance(nJustMinCluster + nJustLen - 1);
        }
        else if (!pDXArray.empty())
        {
            for (int i = 0; i < nJustLen; ++i)
            {
                stJustification.SetTotalAdvance(nJustMinCluster + i, pDXArray[i]);
            }

            nEndGlyphCoord
                = std::round(stJustification.GetTotalAdvance(nJustMinCluster + nJustLen - 1));
        }

        if (!pKashidaArray.empty())
        {
            for (sal_Int32 i = 0; i < static_cast<sal_Int32>(pKashidaArray.size()); ++i)
            {
                stJustification.SetKashidaPosition(nJustMinCluster + i,
                                                   static_cast<bool>(pKashidaArray[i]));
            }
        }

        aLayoutArgs.SetJustificationData(std::move(stJustification));
    }

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

    if (pSalLayout)
    {
        const bool bActivateSubpixelPositioning(IsMapModeEnabled() || isSubpixelPositioning());
        // tdf#168002
        // SubpixelPositioning was until now activated when *any* MapMode was set, but
        // there is another case this is needed: When a TextSimplePortionPrimitive2D
        // is rendered by a SDPR.
        // In that case a TextLayouterDevice is used (to isolate all Text-related stuff
        // that should not be at OutputDevice) combined with a 'empty' OutDev -> no
        // MapMode used. It now gets SubpixelPositioning at it's OutDev to allow
        // checking/usage here.
        // The DXArray for Primitives (see that TextPrimitive) is defined in the
        // Unit-Text_Coordinate-System, thus in (0..1) ranges. That allows to
        // have the DXArray transformation-independent and thus re-usable and
        // is used since the TextPrimitive was created.
        // If there is a DXArray missing at the text Primitive (as is the case
        // with SVG imported ones, but allowed in general) one gets automatically
        // created during the SalLayout creation for rendering. Unfortunately there
        // (see GenericSalLayout::LayoutText, usages of GetSubpixelPositioning) the
        // coordinates get std::round'ed, so all up to that point correctly
        // calculated metric information in that double-precision dependent coordinate
        // space gets *shredded*.
        // To avoid that, SubpixelPositioning  has to be activated. While this might
        // be done in the future for all cases (SubpixelPositioning == true) for now
        // just add this case with the Primitives to not break stuff.
        pSalLayout->SetSubpixelPositioning(bActivateSubpixelPositioning);
    }

    // layout text
    if( pSalLayout && !pSalLayout->LayoutText( aLayoutArgs, pGlyphs ? pGlyphs->Impl(0) : nullptr ) )
    {
        pSalLayout.reset();
    }

    if( !pSalLayout )
        return nullptr;

    // do glyph fallback if needed
    // #105768# avoid fallback for very small font sizes
    if (aLayoutArgs.HasFallbackRun() && mpFontInstance->GetFontSelectPattern().mnHeight >= 3)
        pSalLayout = ImplGlyphFallbackLayout(std::move(pSalLayout), aLayoutArgs, pGlyphs);

    if (flags & SalLayoutFlags::GlyphItemsOnly)
        // Return glyph items only after fallback handling. Otherwise they may
        // contain invalid glyph IDs.
        return pSalLayout;

    // position, justify, etc. the layout
    pSalLayout->AdjustLayout( aLayoutArgs );

    // default to on for pdf export, which uses SubPixelToLogic to convert back to
    // the logical coord space, of if we are scaling/mapping
    if (mbMap || meOutDevType == OUTDEV_PDF)
        pSalLayout->DrawBase() = ImplLogicToDeviceSubPixel(rLogicalPos);
    else
    {
        Point aDevicePos = ImplLogicToDevicePixel(rLogicalPos);
        pSalLayout->DrawBase() = basegfx::B2DPoint(aDevicePos.X(), aDevicePos.Y());
    }

    // adjust to right alignment if necessary
    if( aLayoutArgs.mnFlags & SalLayoutFlags::RightAlign )
    {
        double nRTLOffset;
        if (!pDXArray.empty())
            nRTLOffset = nEndGlyphCoord;
        else if( nPixelWidth )
            nRTLOffset = nPixelWidth;
        else
            nRTLOffset = pSalLayout->GetTextWidth();
        pSalLayout->DrawOffset().setX( 1 - nRTLOffset );
    }

    if(IsTrackingFontMappingUse())
        TrackFontMappingUse(GetFont(), pSalLayout.get());

    return pSalLayout;
}

std::shared_ptr<const vcl::text::TextLayoutCache> OutputDevice::CreateTextLayoutCache(
        OUString const& rString)
{
    return vcl::text::TextLayoutCache::Create(rString);
}

bool OutputDevice::GetTextIsRTL( const OUString& rString, sal_Int32 nIndex, sal_Int32 nLen ) const
{
    OUString aStr( rString );
    vcl::text::ImplLayoutArgs aArgs = ImplPrepareLayoutArgs(aStr, nIndex, nLen, 0);
    bool bRTL = false;
    int nCharPos = -1;
    if (!aArgs.GetNextPos(&nCharPos, &bRTL))
        return false;
    return (nCharPos != nIndex);
}

sal_Int32 OutputDevice::GetTextBreak( const OUString& rStr, tools::Long nTextWidth,
                                       sal_Int32 nIndex, sal_Int32 nLen,
                                       tools::Long nCharExtra,
         vcl::text::TextLayoutCache const*const pLayoutCache,
         const SalLayoutGlyphs* pGlyphs) const
{
    std::unique_ptr<SalLayout> pSalLayout = ImplLayout( rStr, nIndex, nLen,
            Point(0,0), 0, {}, {}, eDefaultLayout, pLayoutCache, pGlyphs);
    sal_Int32 nRetVal = -1;
    if( pSalLayout )
    {
        // convert logical widths into layout units
        // NOTE: be very careful to avoid rounding errors for nCharExtra case
        // problem with rounding errors especially for small nCharExtras
        // TODO: remove when layout units have subpixel granularity
        tools::Long nSubPixelFactor = 1;
        if (!mbMap)
            nSubPixelFactor = 64;
        double nTextPixelWidth = ImplLogicWidthToDeviceSubPixel(nTextWidth * nSubPixelFactor);
        double nExtraPixelWidth = 0;
        if( nCharExtra != 0 )
            nExtraPixelWidth = ImplLogicWidthToDeviceSubPixel(nCharExtra * nSubPixelFactor);
        nRetVal = pSalLayout->GetTextBreak( nTextPixelWidth, nExtraPixelWidth, nSubPixelFactor );
    }

    return nRetVal;
}

sal_Int32 OutputDevice::GetTextBreakArray(const OUString& rStr, tools::Long nTextWidth,
                                          std::optional<sal_Unicode> nHyphenChar,
                                          std::optional<sal_Int32*> pHyphenPos, sal_Int32 nIndex,
                                          sal_Int32 nLen, tools::Long nCharExtra,
                                          KernArraySpan aKernArray,
                                          vcl::text::TextLayoutCache const* const pLayoutCache,
                                          const SalLayoutGlyphs* pGlyphs) const
{
    if (pHyphenPos.has_value())
    {
        **pHyphenPos = -1;
    }

    std::unique_ptr<SalLayout> pSalLayout = ImplLayout(
        rStr, nIndex, nLen, Point(0, 0), 0, aKernArray, {}, eDefaultLayout, pLayoutCache, pGlyphs);
    sal_Int32 nRetVal = -1;
    if( pSalLayout )
    {
        // convert logical widths into layout units
        // NOTE: be very careful to avoid rounding errors for nCharExtra case
        // problem with rounding errors especially for small nCharExtras
        // TODO: remove when layout units have subpixel granularity
        tools::Long nSubPixelFactor = 1;
        if (!mbMap)
            nSubPixelFactor = 64;

        double nTextPixelWidth = ImplLogicWidthToDeviceSubPixel(nTextWidth * nSubPixelFactor);
        double nExtraPixelWidth = 0;
        if( nCharExtra != 0 )
            nExtraPixelWidth = ImplLogicWidthToDeviceSubPixel(nCharExtra * nSubPixelFactor);

        // calculate un-hyphenated break position
        nRetVal = pSalLayout->GetTextBreak( nTextPixelWidth, nExtraPixelWidth, nSubPixelFactor );

        // calculate hyphenated break position
        if (nHyphenChar.has_value())
        {
            OUString aHyphenStr(*nHyphenChar);
            std::unique_ptr<SalLayout> pHyphenLayout = ImplLayout(aHyphenStr, 0, 1);
            if (pHyphenLayout)
            {
                // calculate subpixel width of hyphenation character
                double nHyphenPixelWidth = pHyphenLayout->GetTextWidth() * nSubPixelFactor;

                // calculate hyphenated break position
                nTextPixelWidth -= nHyphenPixelWidth;
                if (nExtraPixelWidth > 0)
                    nTextPixelWidth -= nExtraPixelWidth;

                if (pHyphenPos.has_value())
                {
                    **pHyphenPos = pSalLayout->GetTextBreak(nTextPixelWidth, nExtraPixelWidth,
                                                            nSubPixelFactor);

                    if (**pHyphenPos > nRetVal)
                    {
                        **pHyphenPos = nRetVal;
                    }
                }
            }
        }
    }

    return nRetVal;
}

void OutputDevice::ImplDrawText( OutputDevice& rTargetDevice, const tools::Rectangle& rRect,
                                 const OUString& rOrigStr, DrawTextFlags nStyle,
                                 std::vector< tools::Rectangle >* pVector, OUString* pDisplayText,
                                 vcl::TextLayoutCommon& _rLayout )
{

    Color aOldTextColor;
    Color aOldTextFillColor;
    bool  bRestoreFillColor = false;
    if ( (nStyle & DrawTextFlags::Disable) && ! pVector )
    {
        bool  bHighContrastBlack = false;
        bool  bHighContrastWhite = false;
        const StyleSettings& rStyleSettings( rTargetDevice.GetSettings().GetStyleSettings() );
        if( rStyleSettings.GetHighContrastMode() )
        {
            Color aCol;
            if( rTargetDevice.IsBackground() )
                aCol = rTargetDevice.GetBackground().GetColor();
            else
                // best guess is the face color here
                // but it may be totally wrong. the background color
                // was typically already reset
                aCol = rStyleSettings.GetFaceColor();

            bHighContrastBlack = aCol.IsDark();
            bHighContrastWhite = aCol.IsBright();
        }

        aOldTextColor = rTargetDevice.GetTextColor();
        if ( rTargetDevice.IsTextFillColor() )
        {
            bRestoreFillColor = true;
            aOldTextFillColor = rTargetDevice.GetTextFillColor();
        }
        if( bHighContrastBlack )
            rTargetDevice.SetTextColor( COL_GREEN );
        else if( bHighContrastWhite )
            rTargetDevice.SetTextColor( COL_LIGHTGREEN );
        else
        {
            // draw disabled text always without shadow
            // as it fits better with native look
            rTargetDevice.SetTextColor( rTargetDevice.GetSettings().GetStyleSettings().GetDisableColor() );
        }
    }

    tools::Long        nWidth          = rRect.GetWidth();
    tools::Long        nHeight         = rRect.GetHeight();

    if (nWidth <= 0 || nHeight <= 0)
    {
        if (nStyle & DrawTextFlags::Clip)
            return;
        static bool bFuzzing = comphelper::IsFuzzing();
        SAL_WARN_IF(bFuzzing, "vcl", "skipping negative rectangle of: " << nWidth << " x " << nHeight);
        if (bFuzzing)
            return;
    }

    Point       aPos            = rRect.TopLeft();

    tools::Long        nTextHeight     = rTargetDevice.GetTextHeight();
    TextAlign   eAlign          = rTargetDevice.GetTextAlign();
    sal_Int32   nMnemonicPos    = -1;

    OUString aStr = rOrigStr;
    if ( nStyle & DrawTextFlags::Mnemonic )
        aStr = removeMnemonicFromString( aStr, nMnemonicPos );

    const bool bDrawMnemonics = !(rTargetDevice.GetSettings().GetStyleSettings().GetOptions() & StyleSettingsOptions::NoMnemonics) && !pVector;

    // We treat multiline text differently
    if ( nStyle & DrawTextFlags::MultiLine )
    {

        ImplMultiTextLineInfo   aMultiLineInfo;
        sal_Int32               i;
        sal_Int32               nFormatLines;

        if ( nTextHeight )
        {
            tools::Long nMaxTextWidth = _rLayout.GetTextLines(rRect, nTextHeight, aMultiLineInfo, nWidth, aStr, nStyle);
            sal_Int32 nLines = static_cast<sal_Int32>(nHeight/nTextHeight);
            OUString aLastLine;
            nFormatLines = aMultiLineInfo.Count();
            if (nLines <= 0)
                nLines = 1;
            if ( nFormatLines > nLines )
            {
                if ( nStyle & DrawTextFlags::EndEllipsis )
                {
                    // Create last line and shorten it
                    nFormatLines = nLines-1;

                    ImplTextLineInfo& rLineInfo = aMultiLineInfo.GetLine( nFormatLines );
                    aLastLine = convertLineEnd(aStr.copy(rLineInfo.GetIndex()), LINEEND_LF);
                    // Replace all LineFeeds with Spaces
                    OUStringBuffer aLastLineBuffer(aLastLine);
                    sal_Int32 nLastLineLen = aLastLineBuffer.getLength();
                    for ( i = 0; i < nLastLineLen; i++ )
                    {
                        if ( aLastLineBuffer[ i ] == '\n' )
                            aLastLineBuffer[ i ] = ' ';
                    }
                    aLastLine = aLastLineBuffer.makeStringAndClear();
                    aLastLine = _rLayout.GetEllipsisString(aLastLine, nWidth, nStyle);
                    nStyle &= ~DrawTextFlags(DrawTextFlags::VCenter | DrawTextFlags::Bottom);
                    nStyle |= DrawTextFlags::Top;
                }
            }
            else
            {
                if ( nMaxTextWidth <= nWidth )
                    nStyle &= ~DrawTextFlags::Clip;
            }

            // Do we need to clip the height?
            if ( nFormatLines*nTextHeight > nHeight )
                nStyle |= DrawTextFlags::Clip;

            // Set clipping
            if ( nStyle & DrawTextFlags::Clip )
            {
                rTargetDevice.Push( vcl::PushFlags::CLIPREGION );
                rTargetDevice.IntersectClipRegion( rRect );
            }

            // Vertical alignment
            if ( nStyle & DrawTextFlags::Bottom )
                aPos.AdjustY(nHeight-(nFormatLines*nTextHeight) );
            else if ( nStyle & DrawTextFlags::VCenter )
                aPos.AdjustY((nHeight-(nFormatLines*nTextHeight))/2 );

            // Font alignment
            if ( eAlign == ALIGN_BOTTOM )
                aPos.AdjustY(nTextHeight );
            else if ( eAlign == ALIGN_BASELINE )
                aPos.AdjustY(rTargetDevice.GetFontMetric().GetAscent() );

            // Output all lines except for the last one
            for ( i = 0; i < nFormatLines; i++ )
            {
                ImplTextLineInfo& rLineInfo = aMultiLineInfo.GetLine( i );
                if ( nStyle & DrawTextFlags::Right )
                    aPos.AdjustX(nWidth-rLineInfo.GetWidth() );
                else if ( nStyle & DrawTextFlags::Center )
                    aPos.AdjustX((nWidth-rLineInfo.GetWidth())/2 );
                sal_Int32 nIndex   = rLineInfo.GetIndex();
                sal_Int32 nLineLen = rLineInfo.GetLen();
                _rLayout.DrawText( aPos, aStr, nIndex, nLineLen, pVector, pDisplayText );
                if ( bDrawMnemonics )
                {
                    if ( (nMnemonicPos >= nIndex) && (nMnemonicPos < nIndex+nLineLen) )
                    {
                        tools::Long        nMnemonicX;
                        tools::Long        nMnemonicY;

                        KernArray aDXArray;
                        _rLayout.GetTextArray(aStr, &aDXArray, nIndex, nLineLen, true);
                        sal_Int32 nPos = nMnemonicPos - nIndex;
                        sal_Int32 lc_x1 = nPos ? aDXArray[nPos - 1] : 0;
                        sal_Int32 lc_x2 = aDXArray[nPos];
                        double nMnemonicWidth = rTargetDevice.ImplLogicWidthToDeviceSubPixel(std::abs(lc_x1 - lc_x2));

                        Point       aTempPos = rTargetDevice.LogicToPixel( aPos );
                        nMnemonicX = rTargetDevice.GetOutOffXPixel() + aTempPos.X() + rTargetDevice.ImplLogicWidthToDevicePixel( std::min( lc_x1, lc_x2 ) );
                        nMnemonicY = rTargetDevice.GetOutOffYPixel() + aTempPos.Y() + rTargetDevice.ImplLogicWidthToDevicePixel( rTargetDevice.GetFontMetric().GetAscent() );
                        rTargetDevice.ImplDrawMnemonicLine( nMnemonicX, nMnemonicY, nMnemonicWidth );
                    }
                }
                aPos.AdjustY(nTextHeight );
                aPos.setX( rRect.Left() );
            }

            // If there still is a last line, we output it left-aligned as the line would be clipped
            if ( !aLastLine.isEmpty() )
                _rLayout.DrawText( aPos, aLastLine, 0, aLastLine.getLength(), pVector, pDisplayText );

            // Reset clipping
            if ( nStyle & DrawTextFlags::Clip )
                rTargetDevice.Pop();
        }
    }
    else
    {
        tools::Long nTextWidth = _rLayout.GetTextWidth( aStr, 0, -1 );

        // Clip text if needed
        if ( nTextWidth > nWidth )
        {
            if ( nStyle & TEXT_DRAW_ELLIPSIS )
            {
                aStr = _rLayout.GetEllipsisString(aStr, nWidth, nStyle);
                nStyle &= ~DrawTextFlags(DrawTextFlags::Center | DrawTextFlags::Right);
                nStyle |= DrawTextFlags::Left;
                nTextWidth = _rLayout.GetTextWidth( aStr, 0, aStr.getLength() );
            }
        }
        else
        {
            if ( nTextHeight <= nHeight )
                nStyle &= ~DrawTextFlags::Clip;
        }

        // horizontal text alignment
        if ( nStyle & DrawTextFlags::Right )
            aPos.AdjustX(nWidth-nTextWidth );
        else if ( nStyle & DrawTextFlags::Center )
            aPos.AdjustX((nWidth-nTextWidth)/2 );

        // vertical font alignment
        if ( eAlign == ALIGN_BOTTOM )
            aPos.AdjustY(nTextHeight );
        else if ( eAlign == ALIGN_BASELINE )
            aPos.AdjustY(rTargetDevice.GetFontMetric().GetAscent() );

        if ( nStyle & DrawTextFlags::Bottom )
            aPos.AdjustY(nHeight-nTextHeight );
        else if ( nStyle & DrawTextFlags::VCenter )
            aPos.AdjustY((nHeight-nTextHeight)/2 );

        tools::Long nMnemonicX = 0;
        tools::Long nMnemonicY = 0;
        double nMnemonicWidth = 0;
        if (nMnemonicPos != -1 && nMnemonicPos < aStr.getLength())
        {
            KernArray aDXArray;
            _rLayout.GetTextArray(aStr, &aDXArray, 0, aStr.getLength(), true);
            tools::Long lc_x1 = nMnemonicPos? aDXArray[nMnemonicPos - 1] : 0;
            tools::Long lc_x2 = aDXArray[nMnemonicPos];
            nMnemonicWidth = rTargetDevice.ImplLogicWidthToDeviceSubPixel(std::abs(lc_x1 - lc_x2));

            Point aTempPos = rTargetDevice.LogicToPixel( aPos );
            nMnemonicX = rTargetDevice.GetOutOffXPixel() + aTempPos.X() + rTargetDevice.ImplLogicWidthToDevicePixel( std::min(lc_x1, lc_x2) );
            nMnemonicY = rTargetDevice.GetOutOffYPixel() + aTempPos.Y() + rTargetDevice.ImplLogicWidthToDevicePixel( rTargetDevice.GetFontMetric().GetAscent() );
        }

        if ( nStyle & DrawTextFlags::Clip )
        {
            auto popIt = rTargetDevice.ScopedPush(vcl::PushFlags::CLIPREGION);
            rTargetDevice.IntersectClipRegion( rRect );
            _rLayout.DrawText( aPos, aStr, 0, aStr.getLength(), pVector, pDisplayText );
            if ( bDrawMnemonics && nMnemonicPos != -1 )
                rTargetDevice.ImplDrawMnemonicLine( nMnemonicX, nMnemonicY, nMnemonicWidth );
        }
        else
        {
            _rLayout.DrawText( aPos, aStr, 0, aStr.getLength(), pVector, pDisplayText );
            if ( bDrawMnemonics && nMnemonicPos != -1 )
                rTargetDevice.ImplDrawMnemonicLine( nMnemonicX, nMnemonicY, nMnemonicWidth );
        }
    }

    if ( nStyle & DrawTextFlags::Disable && !pVector )
    {
        rTargetDevice.SetTextColor( aOldTextColor );
        if ( bRestoreFillColor )
            rTargetDevice.SetTextFillColor( aOldTextFillColor );
    }
}

void OutputDevice::AddTextRectActions( const tools::Rectangle& rRect,
                                       const OUString&  rOrigStr,
                                       DrawTextFlags    nStyle,
                                       GDIMetaFile&     rMtf )
{

    if ( rOrigStr.isEmpty() || rRect.IsEmpty() )
        return;

    // we need a graphics
    if( !mpGraphics && !AcquireGraphics() )
        return;
    assert(mpGraphics);
    if( mbInitClipRegion )
        InitClipRegion();

    // temporarily swap in passed mtf for action generation, and
    // disable output generation.
    const bool bOutputEnabled( IsOutputEnabled() );
    GDIMetaFile* pMtf = mpMetaFile;

    mpMetaFile = &rMtf;
    EnableOutput( false );

    // #i47157# Factored out to ImplDrawTextRect(), to be shared
    // between us and DrawText()
    vcl::DefaultTextLayout aLayout( *this );
    ImplDrawText( *this, rRect, rOrigStr, nStyle, nullptr, nullptr, aLayout );

    // and restore again
    EnableOutput( bOutputEnabled );
    mpMetaFile = pMtf;
}

void OutputDevice::DrawText( const tools::Rectangle& rRect, const OUString& rOrigStr, DrawTextFlags nStyle,
                             std::vector< tools::Rectangle >* pVector, OUString* pDisplayText,
                             vcl::TextLayoutCommon* _pTextLayout )
{
    assert(!is_double_buffered_window());

    if (mpOutDevData->mpRecordLayout)
    {
        pVector = &mpOutDevData->mpRecordLayout->m_aUnicodeBoundRects;
        pDisplayText = &mpOutDevData->mpRecordLayout->m_aDisplayText;
    }

    bool bDecomposeTextRectAction = ( _pTextLayout != nullptr ) && _pTextLayout->DecomposeTextRectAction();
    if ( mpMetaFile && !bDecomposeTextRectAction )
        mpMetaFile->AddAction( new MetaTextRectAction( rRect, rOrigStr, nStyle ) );

    if ( ( !IsDeviceOutputNecessary() && !pVector && !bDecomposeTextRectAction ) || rOrigStr.isEmpty() || rRect.IsEmpty() )
        return;

    // we need a graphics
    if( !mpGraphics && !AcquireGraphics() )
        return;
    assert(mpGraphics);
    if( mbInitClipRegion )
        InitClipRegion();
    if (mbOutputClipped && !bDecomposeTextRectAction && !pDisplayText)
        return;

    // temporarily disable mtf action generation (ImplDrawText _does_
    // create MetaActionType::TEXTs otherwise)
    GDIMetaFile* pMtf = mpMetaFile;
    if ( !bDecomposeTextRectAction )
        mpMetaFile = nullptr;

    // #i47157# Factored out to ImplDrawText(), to be used also
    // from AddTextRectActions()
    vcl::DefaultTextLayout aDefaultLayout( *this );
    ImplDrawText( *this, rRect, rOrigStr, nStyle, pVector, pDisplayText, _pTextLayout ? *_pTextLayout : aDefaultLayout );

    // and enable again
    mpMetaFile = pMtf;
}

tools::Rectangle OutputDevice::GetTextRect( const tools::Rectangle& rRect,
                                     const OUString& rStr, DrawTextFlags nStyle,
                                     TextRectInfo* pInfo,
                                     const vcl::TextLayoutCommon* _pTextLayout ) const
{

    tools::Rectangle           aRect = rRect;
    sal_Int32           nLines;
    tools::Long                nWidth = rRect.GetWidth();
    tools::Long                nMaxWidth;
    tools::Long                nTextHeight = GetTextHeight();

    OUString aStr = rStr;
    if ( nStyle & DrawTextFlags::Mnemonic )
        aStr = removeMnemonicFromString( aStr );

    if ( nStyle & DrawTextFlags::MultiLine )
    {
        ImplMultiTextLineInfo   aMultiLineInfo;
        sal_Int32               nFormatLines;
        sal_Int32               i;

        nMaxWidth = 0;
        vcl::DefaultTextLayout aDefaultLayout( *const_cast< OutputDevice* >( this ) );

        if (_pTextLayout)
            const_cast<vcl::TextLayoutCommon*>(_pTextLayout)->GetTextLines(rRect, nTextHeight, aMultiLineInfo, nWidth, aStr, nStyle);
        else
            aDefaultLayout.GetTextLines(rRect, nTextHeight, aMultiLineInfo, nWidth, aStr, nStyle);

        nFormatLines = aMultiLineInfo.Count();
        if ( !nTextHeight )
            nTextHeight = 1;
        nLines = static_cast<sal_uInt16>(aRect.GetHeight()/nTextHeight);
        if ( pInfo )
            pInfo->mnLineCount = nFormatLines;
        if ( !nLines )
            nLines = 1;
        if ( nFormatLines <= nLines )
            nLines = nFormatLines;
        else
        {
            if ( !(nStyle & DrawTextFlags::EndEllipsis) )
                nLines = nFormatLines;
            else
            {
                if ( pInfo )
                    pInfo->mbEllipsis = true;
                nMaxWidth = nWidth;
            }
        }
        if ( pInfo )
        {
            bool bMaxWidth = nMaxWidth == 0;
            pInfo->mnMaxWidth = 0;
            for ( i = 0; i < nLines; i++ )
            {
                ImplTextLineInfo& rLineInfo = aMultiLineInfo.GetLine( i );
                if ( bMaxWidth && (rLineInfo.GetWidth() > nMaxWidth) )
                    nMaxWidth = rLineInfo.GetWidth();
                if ( rLineInfo.GetWidth() > pInfo->mnMaxWidth )
                    pInfo->mnMaxWidth = rLineInfo.GetWidth();
            }
        }
        else if ( !nMaxWidth )
        {
            for ( i = 0; i < nLines; i++ )
            {
                ImplTextLineInfo& rLineInfo = aMultiLineInfo.GetLine( i );
                if ( rLineInfo.GetWidth() > nMaxWidth )
                    nMaxWidth = rLineInfo.GetWidth();
            }
        }
    }
    else
    {
        nLines      = 1;
        nMaxWidth   = _pTextLayout ? _pTextLayout->GetTextWidth( aStr, 0, aStr.getLength() ) : GetTextWidth( aStr );

        if ( pInfo )
        {
            pInfo->mnLineCount  = 1;
            pInfo->mnMaxWidth   = nMaxWidth;
        }

        if ( (nMaxWidth > nWidth) && (nStyle & TEXT_DRAW_ELLIPSIS) )
        {
            if ( pInfo )
                pInfo->mbEllipsis = true;
            nMaxWidth = nWidth;
        }
    }

    if ( nStyle & DrawTextFlags::Right )
        aRect.SetLeft( aRect.Right()-nMaxWidth+1 );
    else if ( nStyle & DrawTextFlags::Center )
    {
        aRect.AdjustLeft((nWidth-nMaxWidth)/2 );
        aRect.SetRight( aRect.Left()+nMaxWidth-1 );
    }
    else
        aRect.SetRight( aRect.Left()+nMaxWidth-1 );

    if ( nStyle & DrawTextFlags::Bottom )
        aRect.SetTop( aRect.Bottom()-(nTextHeight*nLines)+1 );
    else if ( nStyle & DrawTextFlags::VCenter )
    {
        aRect.AdjustTop((aRect.GetHeight()-(nTextHeight*nLines))/2 );
        aRect.SetBottom( aRect.Top()+(nTextHeight*nLines)-1 );
    }
    else
        aRect.SetBottom( aRect.Top()+(nTextHeight*nLines)-1 );

    // #99188# get rid of rounding problems when using this rect later
    if (nStyle & DrawTextFlags::Right)
        aRect.AdjustLeft( -1 );
    else
        aRect.AdjustRight( 1 );

    if (maFont.GetOrientation() != 0_deg10)
    {
        tools::Polygon aRotatedPolygon(aRect);
        aRotatedPolygon.Rotate(Point(aRect.GetWidth() / 2, aRect.GetHeight() / 2), maFont.GetOrientation());
        return aRotatedPolygon.GetBoundRect();
    }

    return aRect;
}

void OutputDevice::DrawCtrlText( const Point& rPos, const OUString& rStr,
                                 const sal_Int32 nIndex, const sal_Int32 nLen,
                                 DrawTextFlags nStyle, std::vector< tools::Rectangle >* pVector, OUString* pDisplayText,
                                 const SalLayoutGlyphs* pGlyphs )
{
    assert(!is_double_buffered_window());

    if ( !IsDeviceOutputNecessary() || (nIndex >= rStr.getLength()) )
        return;

    // better get graphics here because ImplDrawMnemonicLine() will not
    // we need a graphics
    if( !mpGraphics && !AcquireGraphics() )
        return;
    assert(mpGraphics);
    if( mbInitClipRegion )
        InitClipRegion();
    if ( mbOutputClipped )
        return;

    // nIndex and nLen must go to mpAlphaVDev->DrawCtrlText unchanged
    sal_Int32 nCorrectedIndex = nIndex;
    sal_Int32 nCorrectedLen = nLen;
    if ((nCorrectedLen < 0) || (nCorrectedIndex + nCorrectedLen >= rStr.getLength()))
    {
        nCorrectedLen = rStr.getLength() - nCorrectedIndex;
    }
    sal_Int32  nMnemonicPos = -1;

    tools::Long        nMnemonicX = 0;
    tools::Long        nMnemonicY = 0;
    tools::Long        nMnemonicWidth = 0;
    const OUString aStr = removeMnemonicFromString(rStr, nMnemonicPos); // Strip mnemonics always
    if (nMnemonicPos != -1)
    {
        if (nMnemonicPos < nCorrectedIndex)
        {
            --nCorrectedIndex;
        }
        else
        {
            if (nMnemonicPos < (nCorrectedIndex + nCorrectedLen))
                --nCorrectedLen;
        }
        if (nStyle & DrawTextFlags::Mnemonic && !pVector
            && !(GetSettings().GetStyleSettings().GetOptions() & StyleSettingsOptions::NoMnemonics))
        {
            SAL_WARN_IF( nMnemonicPos >= (nCorrectedIndex+nCorrectedLen), "vcl", "Mnemonic underline marker after last character" );
            bool bInvalidPos = false;

            if (nMnemonicPos >= nCorrectedLen)
            {
                // may occur in BiDi-Strings: the '~' is sometimes found behind the last char
                // due to some strange BiDi text editors
                // -> place the underline behind the string to indicate a failure
                bInvalidPos = true;
                nMnemonicPos = nCorrectedLen - 1;
            }

            KernArray aDXArray;
            GetTextArray(aStr, &aDXArray, nCorrectedIndex, nCorrectedLen, true, nullptr, pGlyphs);
            sal_Int32 nPos = nMnemonicPos - nCorrectedIndex;
            sal_Int32 lc_x1 = nPos ? aDXArray[nPos - 1] : 0;
            sal_Int32 lc_x2 = aDXArray[nPos];
            nMnemonicWidth = std::abs(lc_x1 - lc_x2);

            Point aTempPos( std::min(lc_x1,lc_x2), GetFontMetric().GetAscent() );
            if( bInvalidPos )  // #106952#, place behind the (last) character
                aTempPos = Point( std::max(lc_x1,lc_x2), GetFontMetric().GetAscent() );

            aTempPos += rPos;
            aTempPos = LogicToPixel( aTempPos );
            nMnemonicX = mnOutOffX + aTempPos.X();
            nMnemonicY = mnOutOffY + aTempPos.Y();
        }
        else
            nMnemonicPos = -1; // Reset - we don't show the mnemonic
    }

    std::optional<Color> oOldTextColor;
    std::optional<Color> oOldTextFillColor;
    if ( nStyle & DrawTextFlags::Disable && ! pVector )
    {
        bool  bHighContrastBlack = false;
        bool  bHighContrastWhite = false;
        const StyleSettings& rStyleSettings( GetSettings().GetStyleSettings() );
        if( rStyleSettings.GetHighContrastMode() )
        {
            if( IsBackground() )
            {
                Wallpaper aWall = GetBackground();
                Color aCol = aWall.GetColor();
                bHighContrastBlack = aCol.IsDark();
                bHighContrastWhite = aCol.IsBright();
            }
        }

        oOldTextColor = GetTextColor();
        if ( IsTextFillColor() )
            oOldTextFillColor = GetTextFillColor();

        if( bHighContrastBlack )
            SetTextColor( COL_GREEN );
        else if( bHighContrastWhite )
            SetTextColor( COL_LIGHTGREEN );
        else
            SetTextColor( GetSettings().GetStyleSettings().GetDisableColor() );
    }

    DrawText(rPos, aStr, nCorrectedIndex, nCorrectedLen, pVector, pDisplayText, pGlyphs);
    if (nMnemonicPos != -1)
        ImplDrawMnemonicLine(nMnemonicX, nMnemonicY, nMnemonicWidth);

    if (oOldTextColor)
        SetTextColor( *oOldTextColor );
    if (oOldTextFillColor)
        SetTextFillColor(*oOldTextFillColor);
}

tools::Long OutputDevice::GetCtrlTextWidth( const OUString& rStr, const SalLayoutGlyphs* pGlyphs ) const
{
    sal_Int32 nLen = rStr.getLength();
    sal_Int32 nIndex = 0;

    sal_Int32 nMnemonicPos;
    OUString aStr = removeMnemonicFromString( rStr, nMnemonicPos );
    if ( nMnemonicPos != -1 )
    {
        if ( nMnemonicPos < nIndex )
            nIndex--;
        else if (static_cast<sal_uLong>(nMnemonicPos) < static_cast<sal_uLong>(nIndex+nLen))
            nLen--;
    }
    return GetTextWidth( aStr, nIndex, nLen, nullptr, pGlyphs );
}

bool OutputDevice::GetTextBoundRect( tools::Rectangle& rRect,
                                         const OUString& rStr, sal_Int32 nBase,
                                         sal_Int32 nIndex, sal_Int32 nLen,
                                         sal_uLong nLayoutWidth, KernArraySpan pDXAry,
                                         std::span<const sal_Bool> pKashidaAry,
                                         const SalLayoutGlyphs* pGlyphs ) const
{
    basegfx::B2DRectangle aRect;
    bool bRet = GetTextBoundRect(aRect, rStr, nBase, nIndex, nLen, nLayoutWidth, pDXAry,
                                 pKashidaAry, pGlyphs);
    rRect = SalLayout::BoundRect2Rectangle(aRect);
    return bRet;
}

bool OutputDevice::GetTextBoundRect(basegfx::B2DRectangle& rRect, const OUString& rStr,
                                    sal_Int32 nBase, sal_Int32 nIndex, sal_Int32 nLen,
                                    sal_uLong nLayoutWidth, KernArraySpan pDXAry,
                                    std::span<const sal_Bool> pKashidaAry,
                                    const SalLayoutGlyphs* pGlyphs) const
{
    bool bRet = false;
    rRect.reset();

    std::unique_ptr<SalLayout> pSalLayout;
    const Point aPoint;
    // calculate offset when nBase!=nIndex
    double nXOffset = 0;
    if( nBase != nIndex )
    {
        sal_Int32 nStart = std::min( nBase, nIndex );
        sal_Int32 nOfsLen = std::max( nBase, nIndex ) - nStart;
        pSalLayout = ImplLayout( rStr, nStart, nOfsLen, aPoint, nLayoutWidth, pDXAry, pKashidaAry );
        if( pSalLayout )
        {
            nXOffset = pSalLayout->GetTextWidth();
            // TODO: fix offset calculation for Bidi case
            if( nBase < nIndex)
                nXOffset = -nXOffset;
        }
    }

    pSalLayout = ImplLayout(rStr, nIndex, nLen, aPoint, nLayoutWidth, pDXAry, pKashidaAry, eDefaultLayout,
                            nullptr, pGlyphs);
    if( pSalLayout )
    {
        basegfx::B2DRectangle aPixelRect;
        bRet = pSalLayout->GetBoundRect(aPixelRect);

        if( bRet )
        {
            basegfx::B2DPoint aPos = pSalLayout->GetDrawPosition(basegfx::B2DPoint(nXOffset, 0));
            aPixelRect.translate(mnTextOffX - aPos.getX(), mnTextOffY - aPos.getY());
            rRect = PixelToLogic( aPixelRect );
            if (mbMap)
            {
                rRect.translate(maMapRes.mnMapOfsX, maMapRes.mnMapOfsY);
            }
        }
    }

    return bRet;
}

bool OutputDevice::GetTextOutlines( basegfx::B2DPolyPolygonVector& rVector,
                                        const OUString& rStr, sal_Int32 nBase,
                                        sal_Int32 nIndex, sal_Int32 nLen,
                                        sal_uLong nLayoutWidth,
                                        KernArraySpan pDXArray,
                                        std::span<const sal_Bool> pKashidaArray ) const
{
    if (!InitFont())
        return false;

    bool bRet = false;
    rVector.clear();
    if( nLen < 0 )
    {
        nLen = rStr.getLength() - nIndex;
    }
    rVector.reserve( nLen );

    // we want to get the Rectangle in logical units, so to
    // avoid rounding errors we just size the font in logical units
    bool bOldMap = mbMap;
    if( bOldMap )
    {
        const_cast<OutputDevice&>(*this).mbMap = false;
        const_cast<OutputDevice&>(*this).mbNewFont = true;
    }

    std::unique_ptr<SalLayout> pSalLayout;

    // calculate offset when nBase!=nIndex
    double nXOffset = 0;
    if( nBase != nIndex )
    {
        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, pKashidaArray);
        if( pSalLayout )
        {
            nXOffset = pSalLayout->GetTextWidth();
            pSalLayout.reset();
            // TODO: fix offset calculation for Bidi case
            if( nBase > nIndex)
                nXOffset = -nXOffset;
        }
    }

    pSalLayout = ImplLayout( rStr, nIndex, nLen, Point(0,0), nLayoutWidth, pDXArray, pKashidaArray );
    if( pSalLayout )
    {
        bRet = pSalLayout->GetOutline(rVector);
        if( bRet )
        {
            // transform polygon to pixel units
            basegfx::B2DHomMatrix aMatrix;

            if (nXOffset || mnTextOffX || mnTextOffY)
            {
                basegfx::B2DPoint aRotatedOfs(mnTextOffX, mnTextOffY);
                aRotatedOfs -= pSalLayout->GetDrawPosition(basegfx::B2DPoint(nXOffset, 0));
                aMatrix.translate( aRotatedOfs.getX(), aRotatedOfs.getY() );
            }

            if( !aMatrix.isIdentity() )
            {
                for (auto & elem : rVector)
                    elem.transform( aMatrix );
            }
        }

        pSalLayout.reset();
    }

    if( bOldMap )
    {
        // restore original font size and map mode
        const_cast<OutputDevice&>(*this).mbMap = bOldMap;
        const_cast<OutputDevice&>(*this).mbNewFont = true;
    }

    return bRet;
}

bool OutputDevice::GetTextOutlines( PolyPolyVector& rResultVector,
                                        const OUString& rStr, sal_Int32 nBase,
                                        sal_Int32 nIndex, sal_Int32 nLen,
                                        sal_uLong nLayoutWidth, KernArraySpan pDXArray,
                                        std::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, pKashidaArray ) )
        return false;

    // convert to a tool polypolygon vector
    rResultVector.reserve( aB2DPolyPolyVector.size() );
    for (auto const& elem : aB2DPolyPolyVector)
        rResultVector.emplace_back(elem); // #i76339#

    return true;
}

bool OutputDevice::GetTextOutline( tools::PolyPolygon& rPolyPoly, const OUString& rStr ) const
{
    rPolyPoly.Clear();

    // get the basegfx polypolygon vector
    basegfx::B2DPolyPolygonVector aB2DPolyPolyVector;
    if( !GetTextOutlines( aB2DPolyPolyVector, rStr, 0/*nBase*/, 0/*nIndex*/, /*nLen*/-1,
                         /*nLayoutWidth*/0, /*pDXArray*/{} ) )
        return false;

    // convert and merge into a tool polypolygon
    for (auto const& elem : aB2DPolyPolyVector)
        for(auto const& rB2DPolygon : elem)
            rPolyPoly.Insert(tools::Polygon(rB2DPolygon)); // #i76339#

    return true;
}

void OutputDevice::SetSystemTextColor(SystemTextColorFlags nFlags, bool bEnabled)
{
    if (nFlags & SystemTextColorFlags::Mono)
    {
        SetTextColor(COL_BLACK);
    }
    else
    {
        if (!bEnabled)
        {
            const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
            SetTextColor(rStyleSettings.GetDisableColor());
        }
    }
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
