/* -*- 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 <memory>
#include <basegfx/matrix/b2dhommatrix.hxx>
 
#include <com/sun/star/i18n/WordType.hpp>
#include <com/sun/star/i18n/XBreakIterator.hpp>
#include <com/sun/star/linguistic2/LinguServiceManager.hpp>
 
#include <comphelper/processfactory.hxx>
#include <osl/file.h>
#include <sal/log.hxx>
#include <vcl/gdimtf.hxx>
#include <vcl/metaact.hxx>
#include <vcl/textrectinfo.hxx>
#include <vcl/virdev.hxx>
#include <vcl/bitmapaccess.hxx>
#include <vcl/sysdata.hxx>
#include <vcl/unohelp.hxx>
#include <vcl/controllayout.hxx>
#include <vcl/opengl/OpenGLHelper.hxx>
 
#include <outdata.hxx>
#include <outdev.h>
#include <salgdi.hxx>
#include <svdata.hxx>
#include <textlayout.hxx>
#include <textlineinfo.hxx>
 
#define TEXT_DRAW_ELLIPSIS  (DrawTextFlags::EndEllipsis | DrawTextFlags::PathEllipsis | DrawTextFlags::NewsEllipsis)
 
ImplMultiTextLineInfo::ImplMultiTextLineInfo()
{
}
 
ImplMultiTextLineInfo::~ImplMultiTextLineInfo()
{
}
 
void ImplMultiTextLineInfo::AddLine( ImplTextLineInfo* pLine )
{
    mvLines.push_back(std::unique_ptr<ImplTextLineInfo>(pLine));
}
 
void ImplMultiTextLineInfo::Clear()
{
    mvLines.clear();
}
 
void OutputDevice::ImplInitTextColor()
{
    DBG_TESTSOLARMUTEX();
 
    if ( mbInitTextColor )
    {
        mpGraphics->SetTextColor( GetTextColor() );
        mbInitTextColor = false;
    }
}
 
void OutputDevice::ImplDrawTextRect( long nBaseX, long nBaseY,
                                     long nDistX, long nDistY, long nWidth, long nHeight )
{
    long nX = nDistX;
    long nY = nDistY;
 
    short nOrientation = mpFontInstance->mnOrientation;
    if ( nOrientation )
    {
        // Rotate rect without rounding problems for 90 degree rotations
        if ( !(nOrientation % 900) )
        {
            if ( nOrientation == 900 )
            {
                long nTemp = nX;
                nX = nY;
                nY = -nTemp;
                nTemp = nWidth;
                nWidth = nHeight;
                nHeight = nTemp;
                nY -= nHeight;
            }
            else if ( nOrientation == 1800 )
            {
                nX = -nX;
                nY = -nY;
                nX -= nWidth;
                nY -= nHeight;
            }
            else /* ( nOrientation == 2700 ) */
            {
                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 long nWidth = rSalLayout.GetTextWidth() / rSalLayout.GetUnitsPerPixel();
    const Point aBase = rSalLayout.DrawBase();
    const long nX = aBase.X();
    const long nY = aBase.Y();
 
    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 )
{
    Point aPoint = rSalLayout.GetDrawPosition();
    long nX = aPoint.X();
    long nY = aPoint.Y();
 
    long nWidth = rSalLayout.GetTextWidth();
    long nHeight = mpFontInstance->mnLineHeight + mnEmphasisAscent + mnEmphasisDescent;
 
    nY -= mpFontInstance->mxFontMetric->GetAscent() + mnEmphasisAscent;
 
    if ( mpFontInstance->mnOrientation )
    {
        long nBaseX = nX, nBaseY = nY;
        if ( !(mpFontInstance->mnOrientation % 900) )
        {
            long nX2 = nX+nWidth;
            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 )
{
    long nX = rSalLayout.DrawBase().X();
    long nY = rSalLayout.DrawBase().Y();
 
    tools::Rectangle aBoundRect;
    rSalLayout.DrawBase() = Point( 0, 0 );
    rSalLayout.DrawOffset() = Point( 0, 0 );
    if( !rSalLayout.GetBoundRect( *mpGraphics, aBoundRect ) )
    {
        // guess vertical text extents if GetBoundRect failed
        long nRight = rSalLayout.GetTextWidth();
        long nTop = mpFontInstance->mxFontMetric->GetAscent() + mnEmphasisAscent;
        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, DeviceFormat::BITMASK);
    VirtualDevice* pVDev = mpOutDevData->mpRotateDev;
 
    // size it accordingly
    if( !pVDev->SetOutputSizePixel( aBoundRect.GetSize() ) )
        return false;
 
    const FontSelectPattern& rPattern = mpFontInstance->GetFontSelectPattern();
    vcl::Font aFont( GetFont() );
    aFont.SetOrientation( 0 );
    aFont.SetFontSize( Size( rPattern.mnWidth, rPattern.mnHeight ) );
    pVDev->SetFont( aFont );
    pVDev->SetTextColor( COL_BLACK );
    pVDev->SetTextFillColor();
    pVDev->ImplNewFont();
    pVDev->InitFont();
    pVDev->ImplInitTextColor();
 
    // draw text into upper left corner
    rSalLayout.DrawBase() -= aBoundRect.TopLeft();
    rSalLayout.DrawText( *pVDev->mpGraphics );
 
    Bitmap aBmp = pVDev->GetBitmap( Point(), aBoundRect.GetSize() );
    if ( !aBmp || !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;
    long nOldOffX = mnOutOffX;
    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;
 
    long nOldX = rSalLayout.DrawBase().X();
    if( HasMirroredGraphics() )
    {
        long w = meOutDevType == OUTDEV_VIRDEV ? mnOutWidth : mpGraphics->GetGraphicsWidth();
        long x = rSalLayout.DrawBase().X();
           rSalLayout.DrawBase().setX( w - 1 - x );
        if( !IsRTLEnabled() )
        {
            OutputDevice *pOutDevRef = this;
            // mirror this window back
            long devX = w-pOutDevRef->mnOutWidth-pOutDevRef->mnOutOffX;   // re-mirrored mnOutOffX
            rSalLayout.DrawBase().setX( devX + ( pOutDevRef->mnOutWidth - 1 - (rSalLayout.DrawBase().X() - devX) ) ) ;
        }
    }
    else if( IsRTLEnabled() )
    {
        OutputDevice *pOutDevRef = this;
 
        // mirror this window back
        long devX = pOutDevRef->mnOutOffX;   // re-mirrored mnOutOffX
        rSalLayout.DrawBase().setX( pOutDevRef->mnOutWidth - 1 - (rSalLayout.DrawBase().X() - devX) + devX );
    }
 
    rSalLayout.DrawText( *mpGraphics );
    rSalLayout.DrawBase().setX( nOldX );
 
    if( bTextLines )
        ImplDrawTextLines( rSalLayout,
            maFont.GetStrikeout(), maFont.GetUnderline(), maFont.GetOverline(),
            maFont.IsWordLineMode(), ImplIsUnderlineAbove( maFont ) );
 
    // 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();
 
    Point aOrigPos = rSalLayout.DrawBase();
    if ( eRelief != FontRelief::NONE )
    {
        Color   aReliefColor( COL_LIGHTGRAY );
        Color   aTextColor( aOldColor );
 
        Color   aTextLineColor( aOldTextLineColor );
        Color   aOverlineColor( aOldOverlineColor );
 
        // we don't have a 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
        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
        long nOff = 1;
        nOff += mnDPIX/300;
 
        if ( eRelief == FontRelief::Engraved )
            nOff = -nOff;
        rSalLayout.DrawOffset() += Point( nOff, nOff);
        ImplDrawTextDirect( rSalLayout, mbTextLines );
        rSalLayout.DrawOffset() -= Point( nOff, nOff);
 
        SetTextLineColor( aTextLineColor );
        SetOverlineColor( aOverlineColor );
        SetTextColor( aTextColor );
        ImplInitTextColor();
        ImplDrawTextDirect( rSalLayout, mbTextLines );
 
        SetTextLineColor( aOldTextLineColor );
        SetOverlineColor( aOldOverlineColor );
 
        if ( aTextColor != aOldColor )
        {
            SetTextColor( aOldColor );
            ImplInitTextColor();
        }
    }
    else
    {
        if ( maFont.IsShadow() )
        {
            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() += Point( nOff, nOff );
            ImplDrawTextDirect( rSalLayout, mbTextLines );
            rSalLayout.DrawBase() -= Point( nOff, nOff );
            SetTextColor( aOldColor );
            SetTextLineColor( aOldTextLineColor );
            SetOverlineColor( aOldOverlineColor );
            ImplInitTextColor();
 
            if ( !maFont.IsOutline() )
                ImplDrawTextDirect( rSalLayout, mbTextLines );
        }
 
        if ( maFont.IsOutline() )
        {
            rSalLayout.DrawBase() = aOrigPos + Point(-1,-1);
            ImplDrawTextDirect( rSalLayout, mbTextLines );
            rSalLayout.DrawBase() = aOrigPos + Point(+1,+1);
            ImplDrawTextDirect( rSalLayout, mbTextLines );
            rSalLayout.DrawBase() = aOrigPos + Point(-1,+0);
            ImplDrawTextDirect( rSalLayout, mbTextLines );
            rSalLayout.DrawBase() = aOrigPos + Point(-1,+1);
            ImplDrawTextDirect( rSalLayout, mbTextLines );
            rSalLayout.DrawBase() = aOrigPos + Point(+0,+1);
            ImplDrawTextDirect( rSalLayout, mbTextLines );
            rSalLayout.DrawBase() = aOrigPos + Point(+0,-1);
            ImplDrawTextDirect( rSalLayout, mbTextLines );
            rSalLayout.DrawBase() = aOrigPos + Point(+1,-1);
            ImplDrawTextDirect( rSalLayout, mbTextLines );
            rSalLayout.DrawBase() = aOrigPos + Point(+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() += Point( mnTextOffX, mnTextOffY );
 
    if( IsTextFillColor() )
        ImplDrawTextBackground( rSalLayout );
 
    if( mbTextSpecial )
        ImplDrawSpecialText( rSalLayout );
    else
        ImplDrawTextDirect( rSalLayout, mbTextLines );
}
 
long OutputDevice::ImplGetTextLines( ImplMultiTextLineInfo& rLineInfo,
                                     long nWidth, const OUString& rStr,
                                     DrawTextFlags nStyle, const vcl::ITextLayout& _rLayout )
{
    SAL_WARN_IF( nWidth <= 0, "vcl", "ImplGetTextLines: nWidth <= 0!" );
 
    if ( nWidth <= 0 )
        nWidth = 1;
 
    long nMaxLineWidth  = 0;
    rLineInfo.Clear();
    if ( !rStr.isEmpty() && (nWidth > 0) )
    {
        const bool bHyphenate = (nStyle & DrawTextFlags::WordBreakHyphenation) == DrawTextFlags::WordBreakHyphenation;
        css::uno::Reference< css::linguistic2::XHyphenator > xHyph;
        if (bHyphenate)
        {
            // get service provider
            css::uno::Reference<css::uno::XComponentContext> xContext(comphelper::getProcessComponentContext());
            css::uno::Reference<css::linguistic2::XLinguServiceManager2> xLinguMgr = css::linguistic2::LinguServiceManager::create(xContext);
            xHyph = xLinguMgr->getHyphenator();
        }
 
        css::uno::Reference<css::i18n::XBreakIterator> xBI;
        sal_Int32 nPos = 0;
        sal_Int32 nLen = rStr.getLength();
        while ( nPos < nLen )
        {
            sal_Int32 nBreakPos = nPos;
 
            while ( ( nBreakPos < nLen ) && ( rStr[ nBreakPos ] != '\r' ) && ( rStr[ nBreakPos ] != '\n' ) )
                nBreakPos++;
 
            long nLineWidth = _rLayout.GetTextWidth( rStr, nPos, nBreakPos-nPos );
            if ( ( nLineWidth > nWidth ) && ( nStyle & DrawTextFlags::WordBreak ) )
            {
                if ( !xBI.is() )
                    xBI = vcl::unohelper::CreateBreakIterator();
 
                if ( xBI.is() )
                {
                    const css::lang::Locale& rDefLocale(Application::GetSettings().GetUILanguageTag().getLocale());
                    sal_Int32 nSoftBreak = _rLayout.GetTextBreak( rStr, nWidth, nPos, nBreakPos - nPos );
                    if (nSoftBreak == -1)
                    {
                        nSoftBreak = nPos;
                    }
                    SAL_WARN_IF( nSoftBreak >= nBreakPos, "vcl", "Break?!" );
                    css::i18n::LineBreakHyphenationOptions aHyphOptions( xHyph, css::uno::Sequence <css::beans::PropertyValue>(), 1 );
                    css::i18n::LineBreakUserOptions aUserOptions;
                    css::i18n::LineBreakResults aLBR = xBI->getLineBreak( rStr, nSoftBreak, rDefLocale, nPos, aHyphOptions, aUserOptions );
                    nBreakPos = aLBR.breakIndex;
                    if ( nBreakPos <= nPos )
                        nBreakPos = nSoftBreak;
                    if ( bHyphenate )
                    {
                        // Whether hyphen or not: Put the word after the hyphen through
                        // word boundary.
 
                        // nMaxBreakPos the last char that fits into the line
                        // nBreakPos is the word's start
 
                        // We run into a problem if the doc is so narrow, that a word
                        // is broken into more than two lines ...
                        if ( xHyph.is() )
                        {
                            sal_Unicode cAlternateReplChar = 0;
                            css::i18n::Boundary aBoundary = xBI->getWordBoundary( rStr, nBreakPos, rDefLocale, css::i18n::WordType::DICTIONARY_WORD, true );
                            sal_Int32 nWordStart = nPos;
                            sal_Int32 nWordEnd = aBoundary.endPos;
                            SAL_WARN_IF( nWordEnd <= nWordStart, "vcl", "ImpBreakLine: Start >= End?" );
 
                            sal_Int32 nWordLen = nWordEnd - nWordStart;
                            if ( ( nWordEnd >= nSoftBreak ) && ( nWordLen > 3 ) )
                            {
                                // #104415# May happen, because getLineBreak may differ from getWordBoudary with DICTIONARY_WORD
                                // SAL_WARN_IF( nWordEnd < nMaxBreakPos, "vcl", "Hyph: Break?" );
                                OUString aWord = rStr.copy( nWordStart, nWordLen );
                                sal_Int32 nMinTrail = nWordEnd-nSoftBreak+1;  //+1: Before the "broken off" char
                                css::uno::Reference< css::linguistic2::XHyphenatedWord > xHyphWord;
                                if (xHyph.is())
                                    xHyphWord = xHyph->hyphenate( aWord, rDefLocale, aWord.getLength() - nMinTrail, css::uno::Sequence< css::beans::PropertyValue >() );
                                if (xHyphWord.is())
                                {
                                    bool bAlternate = xHyphWord->isAlternativeSpelling();
                                    sal_Int32 _nWordLen = 1 + xHyphWord->getHyphenPos();
 
                                    if ( ( _nWordLen >= 2 ) && ( (nWordStart+_nWordLen) >= 2 ) )
                                    {
                                        if ( !bAlternate )
                                        {
                                            nBreakPos = nWordStart + _nWordLen;
                                        }
                                        else
                                        {
                                            OUString aAlt( xHyphWord->getHyphenatedWord() );
 
                                            // We can have two cases:
                                            // 1) "packen" turns into "pak-ken"
                                            // 2) "Schiffahrt" turns into "Schiff-fahrt"
 
                                            // In case 1 we need to replace a char
                                            // In case 2 we add a char
 
                                            // Correct recognition is made harder by words such as
                                            // "Schiffahrtsbrennesseln", as the Hyphenator splits all
                                            // positions of the word and comes up with "Schifffahrtsbrennnesseln"
                                            // Thus, we cannot infer the aWord from the AlternativeWord's
                                            // index.
                                            // TODO: The whole junk will be made easier by a function in
                                            // the Hyphenator, as soon as AMA adds it.
                                            sal_Int32 nAltStart = _nWordLen - 1;
                                            sal_Int32 nTxtStart = nAltStart - (aAlt.getLength() - aWord.getLength());
                                            sal_Int32 nTxtEnd = nTxtStart;
                                            sal_Int32 nAltEnd = nAltStart;
 
                                            // The area between nStart and nEnd is the difference
                                            // between AlternativeString and OriginalString
                                            while( nTxtEnd < aWord.getLength() && nAltEnd < aAlt.getLength() &&
                                                   aWord[nTxtEnd] != aAlt[nAltEnd] )
                                            {
                                                ++nTxtEnd;
                                                ++nAltEnd;
                                            }
 
                                            // If a char was added, we notice it now:
                                            if( nAltEnd > nTxtEnd && nAltStart == nAltEnd &&
                                                aWord[ nTxtEnd ] == aAlt[nAltEnd] )
                                            {
                                                ++nAltEnd;
                                                ++nTxtStart;
                                                ++nTxtEnd;
                                            }
 
                                            SAL_WARN_IF( ( nAltEnd - nAltStart ) != 1, "vcl", "Alternate: Wrong assumption!" );
 
                                            if ( nTxtEnd > nTxtStart )
                                                cAlternateReplChar = aAlt[ nAltStart ];
 
                                            nBreakPos = nWordStart + nTxtStart;
                                            if ( cAlternateReplChar )
                                                nBreakPos++;
                                        }
                                    }
                                }
                            }
                        }
                    }
                    nLineWidth = _rLayout.GetTextWidth( rStr, nPos, nBreakPos-nPos );
                }
                else
                {
                    // fallback to something really simple
                    sal_Int32 nSpacePos = rStr.getLength();
                    long nW = 0;
                    do
                    {
                        nSpacePos = rStr.lastIndexOf( ' ', nSpacePos );
                        if( nSpacePos != -1 )
                        {
                            if( nSpacePos > nPos )
                                nSpacePos--;
                            nW = _rLayout.GetTextWidth( rStr, nPos, nSpacePos-nPos );
                        }
                    } while( nW > nWidth );
 
                    if( nSpacePos != -1 )
                    {
                        nBreakPos = nSpacePos;
                        nLineWidth = _rLayout.GetTextWidth( rStr, nPos, nBreakPos-nPos );
                        if( nBreakPos < rStr.getLength()-1 )
                            nBreakPos++;
                    }
                }
            }
 
            if ( nLineWidth > nMaxLineWidth )
                nMaxLineWidth = nLineWidth;
 
            rLineInfo.AddLine( new ImplTextLineInfo( nLineWidth, nPos, nBreakPos-nPos ) );
 
            if ( nBreakPos == nPos )
                nBreakPos++;
            nPos = nBreakPos;
 
            if ( nPos < nLen && ( ( rStr[ nPos ] == '\r' ) || ( rStr[ nPos ] == '\n' ) ) )
            {
                nPos++;
                // CR/LF?
                if ( ( nPos < nLen ) && ( rStr[ nPos ] == '\n' ) && ( rStr[ nPos-1 ] == '\r' ) )
                    nPos++;
            }
        }
    }
#ifdef DBG_UTIL
    for ( sal_Int32 nL = 0; nL < rLineInfo.Count(); nL++ )
    {
        ImplTextLineInfo* pLine = rLineInfo.GetLine( nL );
        OUString aLine = rStr.copy( pLine->GetIndex(), pLine->GetLen() );
        SAL_WARN_IF( aLine.indexOf( '\r' ) != -1, "vcl", "ImplGetTextLines - Found CR!" );
        SAL_WARN_IF( aLine.indexOf( '\n' ) != -1, "vcl", "ImplGetTextLines - Found LF!" );
    }
#endif
 
    return nMaxLineWidth;
}
 
void OutputDevice::SetTextColor( const Color& rColor )
{
 
    Color aColor( rColor );
 
    if ( mnDrawMode & ( DrawModeFlags::BlackText | DrawModeFlags::WhiteText |
                        DrawModeFlags::GrayText | DrawModeFlags::GhostedText |
                        DrawModeFlags::SettingsText ) )
    {
        if ( mnDrawMode & DrawModeFlags::BlackText )
            aColor = COL_BLACK;
        else if ( mnDrawMode & DrawModeFlags::WhiteText )
            aColor = COL_WHITE;
        else if ( mnDrawMode & DrawModeFlags::GrayText )
        {
            const sal_uInt8 cLum = aColor.GetLuminance();
            aColor = Color( cLum, cLum, cLum );
        }
        else if ( mnDrawMode & DrawModeFlags::SettingsText )
            aColor = GetSettings().GetStyleSettings().GetFontColor();
 
        if ( mnDrawMode & DrawModeFlags::GhostedText )
        {
            aColor = Color( (aColor.GetRed() >> 1) | 0x80,
                            (aColor.GetGreen() >> 1) | 0x80,
                            (aColor.GetBlue() >> 1) | 0x80 );
        }
    }
 
    if ( mpMetaFile )
        mpMetaFile->AddAction( new MetaTextColorAction( aColor ) );
 
    if ( maTextColor != aColor )
    {
        maTextColor = aColor;
        mbInitTextColor = true;
    }
 
    if( mpAlphaVDev )
        mpAlphaVDev->SetTextColor( COL_BLACK );
}
 
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 );
 
    if( mpAlphaVDev )
        mpAlphaVDev->SetTextFillColor();
}
 
void OutputDevice::SetTextFillColor( const Color& rColor )
{
    Color aColor( rColor );
    bool bTransFill = ImplIsColorTransparent( aColor );
 
    if ( !bTransFill )
    {
        if ( mnDrawMode & ( DrawModeFlags::BlackFill | DrawModeFlags::WhiteFill |
                            DrawModeFlags::GrayFill | DrawModeFlags::NoFill |
                            DrawModeFlags::GhostedFill | DrawModeFlags::SettingsFill ) )
        {
            if ( mnDrawMode & DrawModeFlags::BlackFill )
                aColor = COL_BLACK;
            else if ( mnDrawMode & DrawModeFlags::WhiteFill )
                aColor = COL_WHITE;
            else if ( mnDrawMode & DrawModeFlags::GrayFill )
            {
                const sal_uInt8 cLum = aColor.GetLuminance();
                aColor = Color( cLum, cLum, cLum );
            }
            else if( mnDrawMode & DrawModeFlags::SettingsFill )
                aColor = GetSettings().GetStyleSettings().GetWindowColor();
            else if ( mnDrawMode & DrawModeFlags::NoFill )
            {
                aColor = COL_TRANSPARENT;
                bTransFill = true;
            }
 
            if ( !bTransFill && (mnDrawMode & DrawModeFlags::GhostedFill) )
            {
                aColor = Color( (aColor.GetRed() >> 1) | 0x80,
                                (aColor.GetGreen() >> 1) | 0x80,
                                (aColor.GetBlue() >> 1) | 0x80 );
            }
        }
    }
 
    if ( mpMetaFile )
        mpMetaFile->AddAction( new MetaTextFillColorAction( aColor, true ) );
 
    if ( maFont.GetFillColor() != aColor )
        maFont.SetFillColor( aColor );
    if ( maFont.IsTransparent() != bTransFill )
        maFont.SetTransparent( bTransFill );
 
    if( mpAlphaVDev )
        mpAlphaVDev->SetTextFillColor( COL_BLACK );
}
 
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;
    }
 
    if( mpAlphaVDev )
        mpAlphaVDev->SetTextAlign( eAlign );
}
 
void OutputDevice::DrawText( const Point& rStartPt, const OUString& rStr,
                             sal_Int32 nIndex, sal_Int32 nLen,
                             MetricVector* 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( GetClipRegion() );
        if( meOutDevType == OUTDEV_WINDOW )
            aClip.Intersect( tools::Rectangle( Point(), GetOutputSize() ) );
        if (mpOutDevData->mpRecordLayout)
        {
            mpOutDevData->mpRecordLayout->m_aLineIndices.push_back( mpOutDevData->mpRecordLayout->m_aDisplayText.getLength() );
            aClip.Intersect( mpOutDevData->maRecordRect );
        }
        if( ! aClip.IsNull() )
        {
            MetricVector aTmp;
            GetGlyphBoundRects( rStartPt, rStr, nIndex, nLen, aTmp );
 
            bool bInserted = false;
            for( MetricVector::const_iterator it = aTmp.begin(); it != aTmp.end(); ++it, nIndex++ )
            {
                bool bAppend = false;
 
                if( aClip.IsOver( *it ) )
                    bAppend = true;
                else if( rStr[ nIndex ] == ' ' && bInserted )
                {
                    MetricVector::const_iterator next = it;
                    ++next;
                    if( next != aTmp.end() && aClip.IsOver( *next ) )
                        bAppend = true;
                }
 
                if( bAppend )
                {
                    pVector->push_back( *it );
                    if( pDisplayText )
                        *pDisplayText += OUStringLiteral1(rStr[ nIndex ]);
                    bInserted = true;
                }
            }
        }
        else
        {
            GetGlyphBoundRects( rStartPt, rStr, nIndex, nLen, *pVector );
            if( pDisplayText )
                *pDisplayText += rStr.copy( nIndex, nLen );
        }
    }
 
    if ( !IsDeviceOutputNecessary() || pVector )
        return;
 
    if(mpFontInstance)
        // do not use cache with modified string
        if(mpFontInstance->mpConversion)
            pLayoutCache = nullptr;
 
    #ifdef MACOSX
        // FIXME: tdf#112990
        // Cache text layout crashes on mac with OpenGL enabled
        // Force it to not use the cache
        if(OpenGLHelper::isVCLOpenGLEnabled())
            pLayoutCache = nullptr;
    #endif
 
    std::unique_ptr<SalLayout> pSalLayout = ImplLayout(rStr, nIndex, nLen, rStartPt, 0, nullptr, SalLayoutFlags::NONE, nullptr, pLayoutCache);
    if(pSalLayout)
    {
        ImplDrawText( *pSalLayout );
    }
 
    if( mpAlphaVDev )
        mpAlphaVDev->DrawText( rStartPt, rStr, nIndex, nLen, pVector, pDisplayText );
}
 
long OutputDevice::GetTextWidth( const OUString& rStr, sal_Int32 nIndex, sal_Int32 nLen,
     vcl::TextLayoutCache const*const pLayoutCache,
     SalLayoutGlyphs const*const pSalLayoutCache) const
{
 
    long nWidth = GetTextArray( rStr, nullptr, nIndex,
            nLen, pLayoutCache, pSalLayoutCache );
 
    return nWidth;
}
 
long OutputDevice::GetTextHeight() const
{
 
    if( mbNewFont )
        if( !ImplNewFont() )
            return 0;
    if( mbInitFont )
        if( !ImplNewFont() )
            return 0;
 
    long nHeight = mpFontInstance->mnLineHeight + mnEmphasisAscent + mnEmphasisDescent;
 
    if ( mbMap )
        nHeight = ImplDevicePixelToLogicHeight( nHeight );
 
    return 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("aemnnxEM") / 8.0;
}
 
float OutputDevice::approximate_digit_width() const
{
    return GetTextWidth("0123456789") / 10.0;
}
 
void OutputDevice::DrawTextArray( const Point& rStartPt, const OUString& rStr,
                                  const long* pDXAry,
                                  sal_Int32 nIndex, sal_Int32 nLen, SalLayoutFlags flags,
                                  vcl::TextLayoutCache const*const pLayoutCache,
                                  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, nIndex, nLen ) );
 
    if ( !IsDeviceOutputNecessary() )
        return;
    if( !mpGraphics && !AcquireGraphics() )
        return;
    if( mbInitClipRegion )
        InitClipRegion();
    if( mbOutputClipped )
        return;
 
    std::unique_ptr<SalLayout> pSalLayout = ImplLayout(rStr, nIndex, nLen, rStartPt, 0, pDXAry, flags, pLayoutCache, pSalLayoutCache);
    if( pSalLayout )
    {
        ImplDrawText( *pSalLayout );
    }
 
    if( mpAlphaVDev )
        mpAlphaVDev->DrawTextArray( rStartPt, rStr, pDXAry, nIndex, nLen, flags );
}
 
long OutputDevice::GetTextArray( const OUString& rStr, long* pDXAry,
                                 sal_Int32 nIndex, sal_Int32 nLen,
                                 vcl::TextLayoutCache const*const pLayoutCache,
                                 SalLayoutGlyphs const*const pSalLayoutCache) const
{
    if( nIndex >= rStr.getLength() )
        return 0; // TODO: this looks like a buggy caller?
 
    if( nLen < 0 || nIndex + nLen >= rStr.getLength() )
    {
        nLen = rStr.getLength() - nIndex;
    }
 
    // do layout
    std::unique_ptr<SalLayout> pSalLayout = ImplLayout(rStr, nIndex, nLen,
            Point(0,0), 0, nullptr, SalLayoutFlags::NONE, pLayoutCache, pSalLayoutCache);
    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)
        {
            memset(pDXAry, 0, nLen * sizeof(*pDXAry));
        }
        return 0;
    }
 
#if VCL_FLOAT_DEVICE_PIXEL
    std::unique_ptr<DeviceCoordinate[]> pDXPixelArray;
    if(pDXAry)
    {
        pDXPixelArray.reset(new DeviceCoordinate[nLen]);
    }
    DeviceCoordinate nWidth = pSalLayout->FillDXArray( pDXPixelArray.get() );
    int nWidthFactor = pSalLayout->GetUnitsPerPixel();
 
    // convert virtual char widths to virtual absolute positions
    if( pDXPixelArray )
    {
        for( int i = 1; i < nLen; ++i )
        {
            pDXPixelArray[ i ] += pDXPixelArray[ i-1 ];
        }
    }
    if( mbMap )
    {
        if( pDXPixelArray )
        {
            for( int i = 0; i < nLen; ++i )
            {
                pDXPixelArray[i] = ImplDevicePixelToLogicWidth( pDXPixelArray[i] );
            }
        }
        nWidth = ImplDevicePixelToLogicWidth( nWidth );
    }
    if( nWidthFactor > 1 )
    {
        if( pDXPixelArray )
        {
            for( int i = 0; i < nLen; ++i )
            {
                pDXPixelArray[i] /= nWidthFactor;
            }
        }
        nWidth /= nWidthFactor;
    }
    if(pDXAry)
    {
        for( int i = 0; i < nLen; ++i )
        {
            pDXAry[i] = basegfx::fround(pDXPixelArray[i]);
        }
    }
    return basegfx::fround(nWidth);
 
#else /* ! VCL_FLOAT_DEVICE_PIXEL */
 
    long nWidth = pSalLayout->FillDXArray( pDXAry );
    int nWidthFactor = pSalLayout->GetUnitsPerPixel();
 
    // convert virtual char widths to virtual absolute positions
    if( pDXAry )
        for( int i = 1; i < nLen; ++i )
            pDXAry[ i ] += pDXAry[ i-1 ];
 
    // convert from font units to logical units
    if( mbMap )
    {
        if( pDXAry )
            for( int i = 0; i < nLen; ++i )
                pDXAry[i] = ImplDevicePixelToLogicWidth( pDXAry[i] );
        nWidth = ImplDevicePixelToLogicWidth( nWidth );
    }
 
    if( nWidthFactor > 1 )
    {
        if( pDXAry )
            for( int i = 0; i < nLen; ++i )
                pDXAry[i] /= nWidthFactor;
        nWidth /= nWidthFactor;
    }
    return nWidth;
#endif /* VCL_FLOAT_DEVICE_PIXEL */
}
 
void OutputDevice::GetCaretPositions( const OUString& rStr, long* pCaretXArray,
                                      sal_Int32 nIndex, sal_Int32 nLen,
                                      const SalLayoutGlyphs* pGlyphs ) const
{
 
    if( nIndex >= rStr.getLength() )
        return;
    if( nIndex+nLen >= rStr.getLength() )
        nLen = rStr.getLength() - nIndex;
 
    // layout complex text
    std::unique_ptr<SalLayout> pSalLayout = ImplLayout(rStr, nIndex, nLen, Point(0, 0), 0, nullptr,
                                                       SalLayoutFlags::NONE, nullptr, pGlyphs);
    if( !pSalLayout )
        return;
 
    int nWidthFactor = pSalLayout->GetUnitsPerPixel();
    pSalLayout->GetCaretPositions( 2*nLen, pCaretXArray );
    long nWidth = pSalLayout->GetTextWidth();
 
    // fixup unknown caret positions
    int i;
    for( i = 0; i < 2 * nLen; ++i )
        if( pCaretXArray[ i ] >= 0 )
            break;
    long nXPos = pCaretXArray[ i ];
    for( i = 0; i < 2 * nLen; ++i )
    {
        if( pCaretXArray[ i ] >= 0 )
            nXPos = pCaretXArray[ i ];
        else
            pCaretXArray[ i ] = nXPos;
    }
 
    // handle window mirroring
    if( IsRTLEnabled() )
    {
        for( i = 0; i < 2 * nLen; ++i )
            pCaretXArray[i] = nWidth - pCaretXArray[i] - 1;
    }
 
    // convert from font units to logical units
    if( mbMap )
    {
        for( i = 0; i < 2*nLen; ++i )
            pCaretXArray[i] = ImplDevicePixelToLogicWidth( pCaretXArray[i] );
    }
 
    if( nWidthFactor != 1 )
    {
        for( i = 0; i < 2*nLen; ++i )
            pCaretXArray[i] /= nWidthFactor;
    }
}
 
void OutputDevice::DrawStretchText( const Point& rStartPt, sal_uLong 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 );
    }
 
    if( mpAlphaVDev )
        mpAlphaVDev->DrawStretchText( rStartPt, nWidth, rStr, nIndex, nLen );
}
 
ImplLayoutArgs OutputDevice::ImplPrepareLayoutArgs( OUString& rStr,
                                                    const sal_Int32 nMinIndex, const sal_Int32 nLen,
                                                    DeviceCoordinate nPixelWidth, const DeviceCoordinate* pDXArray,
                                                    SalLayoutFlags nLayoutFlags,
         vcl::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;
 
    if( mnTextLayoutMode & ComplexTextLayoutFlags::BiDiRtl )
        nLayoutFlags |= SalLayoutFlags::BiDiRtl;
    if( mnTextLayoutMode & ComplexTextLayoutFlags::BiDiStrong )
        nLayoutFlags |= SalLayoutFlags::BiDiStrong;
    else if( !(mnTextLayoutMode & 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;
    }
 
    if( !maFont.IsKerning() )
        nLayoutFlags |= SalLayoutFlags::DisableKerning;
    if( maFont.GetKerning() & FontKerning::Asian )
        nLayoutFlags |= SalLayoutFlags::KerningAsian;
    if( maFont.IsVertical() )
        nLayoutFlags |= SalLayoutFlags::Vertical;
 
    if( meTextLanguage ) //TODO: (mnTextLayoutMode & ComplexTextLayoutFlags::SubstituteDigits)
    {
        // disable character localization when no digits used
        const sal_Unicode* pBase = rStr.getStr();
        const sal_Unicode* pStr = pBase + nMinIndex;
        const sal_Unicode* pEnd = pBase + nEndIndex;
        OUStringBuffer sTmpStr(rStr);
        for( ; pStr < pEnd; ++pStr )
        {
            // TODO: are there non-digit localizations?
            if( (*pStr >= '0') && (*pStr <= '9') )
            {
                // translate characters to local preference
                sal_UCS4 cChar = GetLocalizedChar( *pStr, meTextLanguage );
                if( cChar != *pStr )
                    // TODO: are the localized digit surrogates?
                    sTmpStr[pStr - pBase] = cChar;
            }
        }
        rStr = sTmpStr.makeStringAndClear();
    }
 
    // right align for RTL text, DRAWPOS_REVERSED, RTL window style
    bool bRightAlign = bool(mnTextLayoutMode & ComplexTextLayoutFlags::BiDiRtl);
    if( mnTextLayoutMode & ComplexTextLayoutFlags::TextOriginLeft )
        bRightAlign = false;
    else if ( mnTextLayoutMode & 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
    ImplLayoutArgs aLayoutArgs(rStr, nMinIndex, nEndIndex, nLayoutFlags, maFont.GetLanguageTag(), pLayoutCache);
 
    int nOrientation = mpFontInstance ? mpFontInstance->mnOrientation : 0;
    aLayoutArgs.SetOrientation( nOrientation );
 
    aLayoutArgs.SetLayoutWidth( nPixelWidth );
    aLayoutArgs.SetDXArray( pDXArray );
 
    return aLayoutArgs;
}
 
std::unique_ptr<SalLayout> OutputDevice::ImplLayout(const OUString& rOrigStr,
                                    sal_Int32 nMinIndex, sal_Int32 nLen,
                                    const Point& rLogicalPos, long nLogicalWidth,
                                    const long* pDXArray, SalLayoutFlags flags,
         vcl::TextLayoutCache const* pLayoutCache,
         const SalLayoutGlyphs* pGlyphs) const
{
    // we need a graphics
    if( !mpGraphics )
        if( !AcquireGraphics() )
            return nullptr;
 
    // initialize font if needed
    if( mbNewFont )
        if( !ImplNewFont() )
            return nullptr;
    if( mbInitFont )
        InitFont();
 
    // 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;
 
    // convert from logical units to physical units
    // 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;
    }
    DeviceCoordinate nPixelWidth = static_cast<DeviceCoordinate>(nLogicalWidth);
    std::unique_ptr<DeviceCoordinate[]> xDXPixelArray;
    DeviceCoordinate* pDXPixelArray(nullptr);
    if( nLogicalWidth && mbMap )
    {
        nPixelWidth = LogicWidthToDeviceCoordinate( nLogicalWidth );
    }
 
    if( pDXArray)
    {
        if(mbMap)
        {
            // convert from logical units to font units using a temporary array
            xDXPixelArray.reset(new DeviceCoordinate[nLen]);
            pDXPixelArray = xDXPixelArray.get();
            // using base position for better rounding a.k.a. "dancing characters"
            DeviceCoordinate nPixelXOfs = LogicWidthToDeviceCoordinate( rLogicalPos.X() );
            for( int i = 0; i < nLen; ++i )
            {
                pDXPixelArray[i] = LogicWidthToDeviceCoordinate( rLogicalPos.X() + pDXArray[i] ) - nPixelXOfs;
            }
        }
        else
        {
#if VCL_FLOAT_DEVICE_PIXEL
            xDXPixelArray.reset(new DeviceCoordinate[nLen]);
            pDXPixelArray = xDXPixelArray.get();
            for( int i = 0; i < nLen; ++i )
            {
                pDXPixelArray[i] = pDXArray[i];
            }
#else /* !VCL_FLOAT_DEVICE_PIXEL */
            pDXPixelArray = const_cast<DeviceCoordinate*>(pDXArray);
#endif /* !VCL_FLOAT_DEVICE_PIXEL */
        }
    }
 
    ImplLayoutArgs aLayoutArgs = ImplPrepareLayoutArgs( aStr, nMinIndex, nLen,
            nPixelWidth, pDXPixelArray, flags, pLayoutCache);
 
    // get matching layout object for base font
    std::unique_ptr<SalLayout> pSalLayout = mpGraphics->GetTextLayout( aLayoutArgs, 0 );
 
    // layout text
    if( pSalLayout && !pSalLayout->LayoutText( aLayoutArgs, pGlyphs ) )
    {
        pSalLayout.reset();
    }
 
    if( !pSalLayout )
        return nullptr;
 
    // do glyph fallback if needed
    // #105768# avoid fallback for very small font sizes
    if (aLayoutArgs.NeedFallback() && mpFontInstance->GetFontSelectPattern().mnHeight >= 3)
        pSalLayout = ImplGlyphFallbackLayout(std::move(pSalLayout), aLayoutArgs);
 
    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 );
    pSalLayout->DrawBase() = ImplLogicToDevicePixel( rLogicalPos );
    // adjust to right alignment if necessary
    if( aLayoutArgs.mnFlags & SalLayoutFlags::RightAlign )
    {
        DeviceCoordinate nRTLOffset;
        if( pDXPixelArray )
            nRTLOffset = pDXPixelArray[ nLen - 1 ];
        else if( nPixelWidth )
            nRTLOffset = nPixelWidth;
        else
            nRTLOffset = pSalLayout->GetTextWidth() / pSalLayout->GetUnitsPerPixel();
        pSalLayout->DrawOffset().setX( 1 - nRTLOffset );
    }
 
    return pSalLayout;
}
 
std::shared_ptr<vcl::TextLayoutCache> OutputDevice::CreateTextLayoutCache(
        OUString const& rString) const
{
    if (!mpGraphics) // can happen in e.g Insert Index/Table dialog
        return nullptr;
    OUString copyBecausePrepareModifiesIt(rString);
    ImplLayoutArgs aLayoutArgs = ImplPrepareLayoutArgs(copyBecausePrepareModifiesIt,
            0, rString.getLength(), 0, nullptr);
 
    std::unique_ptr<SalLayout> pSalLayout = mpGraphics->GetTextLayout( aLayoutArgs, 0 );
    if (!pSalLayout)
        return nullptr;
    std::shared_ptr<vcl::TextLayoutCache> const ret(
            pSalLayout->CreateTextLayoutCache(copyBecausePrepareModifiesIt));
    return ret;
}
 
bool OutputDevice::GetTextIsRTL( const OUString& rString, sal_Int32 nIndex, sal_Int32 nLen ) const
{
    OUString aStr( rString );
    ImplLayoutArgs aArgs = ImplPrepareLayoutArgs( aStr, nIndex, nLen, 0, nullptr );
    bool bRTL = false;
    int nCharPos = -1;
    if (!aArgs.GetNextPos(&nCharPos, &bRTL))
        return false;
    return (nCharPos != nIndex);
}
 
sal_Int32 OutputDevice::GetTextBreak( const OUString& rStr, long nTextWidth,
                                       sal_Int32 nIndex, sal_Int32 nLen,
                                       long nCharExtra,
         vcl::TextLayoutCache const*const pLayoutCache,
         const SalLayoutGlyphs* pGlyphs) const
{
    std::unique_ptr<SalLayout> pSalLayout = ImplLayout( rStr, nIndex, nLen,
            Point(0,0), 0, nullptr, SalLayoutFlags::NONE, 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
        long nWidthFactor = pSalLayout->GetUnitsPerPixel();
        long nSubPixelFactor = (nWidthFactor < 64 ) ? 64 : 1;
        nTextWidth *= nWidthFactor * nSubPixelFactor;
        DeviceCoordinate nTextPixelWidth = LogicWidthToDeviceCoordinate( nTextWidth );
        DeviceCoordinate nExtraPixelWidth = 0;
        if( nCharExtra != 0 )
        {
            nCharExtra *= nWidthFactor * nSubPixelFactor;
            nExtraPixelWidth = LogicWidthToDeviceCoordinate( nCharExtra );
        }
        nRetVal = pSalLayout->GetTextBreak( nTextPixelWidth, nExtraPixelWidth, nSubPixelFactor );
    }
 
    return nRetVal;
}
 
sal_Int32 OutputDevice::GetTextBreak( const OUString& rStr, long nTextWidth,
                                       sal_Unicode nHyphenChar, sal_Int32& rHyphenPos,
                                       sal_Int32 nIndex, sal_Int32 nLen,
                                       long nCharExtra,
         vcl::TextLayoutCache const*const pLayoutCache) const
{
    rHyphenPos = -1;
 
    std::unique_ptr<SalLayout> pSalLayout = ImplLayout( rStr, nIndex, nLen,
            Point(0,0), 0, nullptr, SalLayoutFlags::NONE, pLayoutCache);
    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
        long nWidthFactor = pSalLayout->GetUnitsPerPixel();
        long nSubPixelFactor = (nWidthFactor < 64 ) ? 64 : 1;
 
        nTextWidth *= nWidthFactor * nSubPixelFactor;
        DeviceCoordinate nTextPixelWidth = LogicWidthToDeviceCoordinate( nTextWidth );
        DeviceCoordinate nExtraPixelWidth = 0;
        if( nCharExtra != 0 )
        {
            nCharExtra *= nWidthFactor * nSubPixelFactor;
            nExtraPixelWidth = LogicWidthToDeviceCoordinate( nCharExtra );
        }
 
        // calculate un-hyphenated break position
        nRetVal = pSalLayout->GetTextBreak( nTextPixelWidth, nExtraPixelWidth, nSubPixelFactor );
 
        // calculate hyphenated break position
        OUString aHyphenStr(nHyphenChar);
        std::unique_ptr<SalLayout> pHyphenLayout = ImplLayout( aHyphenStr, 0, 1 );
        if( pHyphenLayout )
        {
            // calculate subpixel width of hyphenation character
            long nHyphenPixelWidth = pHyphenLayout->GetTextWidth() * nSubPixelFactor;
 
            // calculate hyphenated break position
            nTextPixelWidth -= nHyphenPixelWidth;
            if( nExtraPixelWidth > 0 )
                nTextPixelWidth -= nExtraPixelWidth;
 
            rHyphenPos = pSalLayout->GetTextBreak(nTextPixelWidth, nExtraPixelWidth, nSubPixelFactor);
 
            if( rHyphenPos > nRetVal )
                rHyphenPos = nRetVal;
        }
    }
 
    return nRetVal;
}
 
void OutputDevice::ImplDrawText( OutputDevice& rTargetDevice, const tools::Rectangle& rRect,
                                 const OUString& rOrigStr, DrawTextFlags nStyle,
                                 MetricVector* pVector, OUString* pDisplayText,
                                 vcl::ITextLayout& _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() );
        }
    }
 
    long        nWidth          = rRect.GetWidth();
    long        nHeight         = rRect.GetHeight();
 
    if ( ((nWidth <= 0) || (nHeight <= 0)) && (nStyle & DrawTextFlags::Clip) )
        return;
 
    Point       aPos            = rRect.TopLeft();
 
    long        nTextHeight     = rTargetDevice.GetTextHeight();
    TextAlign   eAlign          = rTargetDevice.GetTextAlign();
    sal_Int32   nMnemonicPos    = -1;
 
    OUString aStr = rOrigStr;
    if ( nStyle & DrawTextFlags::Mnemonic )
        aStr = GetNonMnemonicString( aStr, nMnemonicPos );
 
    const bool bDrawMnemonics = !(rTargetDevice.GetSettings().GetStyleSettings().GetOptions() & StyleSettingsOptions::NoMnemonics) && !pVector;
 
    // We treat multiline text differently
    if ( nStyle & DrawTextFlags::MultiLine )
    {
 
        OUString                aLastLine;
        ImplMultiTextLineInfo   aMultiLineInfo;
        ImplTextLineInfo*       pLineInfo;
        sal_Int32               i;
        sal_Int32               nLines;
        sal_Int32               nFormatLines;
 
        if ( nTextHeight )
        {
            long nMaxTextWidth = ImplGetTextLines( aMultiLineInfo, nWidth, aStr, nStyle, _rLayout );
            nLines = static_cast<sal_Int32>(nHeight/nTextHeight);
            nFormatLines = aMultiLineInfo.Count();
            if (nLines <= 0)
                nLines = 1;
            if ( nFormatLines > nLines )
            {
                if ( nStyle & DrawTextFlags::EndEllipsis )
                {
                    // Create last line and shorten it
                    nFormatLines = nLines-1;
 
                    pLineInfo = aMultiLineInfo.GetLine( nFormatLines );
                    aLastLine = convertLineEnd(aStr.copy(pLineInfo->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 = ImplGetEllipsisString( rTargetDevice, aLastLine, nWidth, nStyle, _rLayout );
                    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( 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++ )
            {
                pLineInfo = aMultiLineInfo.GetLine( i );
                if ( nStyle & DrawTextFlags::Right )
                    aPos.AdjustX(nWidth-pLineInfo->GetWidth() );
                else if ( nStyle & DrawTextFlags::Center )
                    aPos.AdjustX((nWidth-pLineInfo->GetWidth())/2 );
                sal_Int32 nIndex   = pLineInfo->GetIndex();
                sal_Int32 nLineLen = pLineInfo->GetLen();
                _rLayout.DrawText( aPos, aStr, nIndex, nLineLen, pVector, pDisplayText );
                if ( bDrawMnemonics )
                {
                    if ( (nMnemonicPos >= nIndex) && (nMnemonicPos < nIndex+nLineLen) )
                    {
                        long        nMnemonicX;
                        long        nMnemonicY;
                        DeviceCoordinate nMnemonicWidth;
 
                        std::unique_ptr<long[]> const pCaretXArray(new long[2 * nLineLen]);
                        /*sal_Bool bRet =*/ _rLayout.GetCaretPositions( aStr, pCaretXArray.get(),
                                                nIndex, nLineLen );
                        long lc_x1 = pCaretXArray[2*(nMnemonicPos - nIndex)];
                        long lc_x2 = pCaretXArray[2*(nMnemonicPos - nIndex)+1];
                        nMnemonicWidth = rTargetDevice.LogicWidthToDeviceCoordinate( 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
    {
        long nTextWidth = _rLayout.GetTextWidth( aStr, 0, -1 );
 
        // Clip text if needed
        if ( nTextWidth > nWidth )
        {
            if ( nStyle & TEXT_DRAW_ELLIPSIS )
            {
                aStr = ImplGetEllipsisString( rTargetDevice, aStr, nWidth, nStyle, _rLayout );
                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 );
 
        long nMnemonicX = 0;
        long nMnemonicY = 0;
        DeviceCoordinate nMnemonicWidth = 0;
        if ( nMnemonicPos != -1 )
        {
            std::unique_ptr<long[]> const pCaretXArray(new long[2 * aStr.getLength()]);
            /*sal_Bool bRet =*/ _rLayout.GetCaretPositions( aStr, pCaretXArray.get(), 0, aStr.getLength() );
            long lc_x1 = pCaretXArray[2*nMnemonicPos];
            long lc_x2 = pCaretXArray[2*nMnemonicPos+1];
            nMnemonicWidth = rTargetDevice.LogicWidthToDeviceCoordinate( 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 )
        {
            rTargetDevice.Push( PushFlags::CLIPREGION );
            rTargetDevice.IntersectClipRegion( rRect );
            _rLayout.DrawText( aPos, aStr, 0, aStr.getLength(), pVector, pDisplayText );
            if ( bDrawMnemonics )
            {
                if ( nMnemonicPos != -1 )
                    rTargetDevice.ImplDrawMnemonicLine( nMnemonicX, nMnemonicY, nMnemonicWidth );
            }
            rTargetDevice.Pop();
        }
        else
        {
            _rLayout.DrawText( aPos, aStr, 0, aStr.getLength(), pVector, pDisplayText );
            if ( bDrawMnemonics )
            {
                if ( 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;
    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,
                             MetricVector* pVector, OUString* pDisplayText,
                             vcl::ITextLayout* _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;
    if( mbInitClipRegion )
        InitClipRegion();
    if( mbOutputClipped && !bDecomposeTextRectAction )
        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;
 
    if( mpAlphaVDev )
        mpAlphaVDev->DrawText( rRect, rOrigStr, nStyle, pVector, pDisplayText );
}
 
tools::Rectangle OutputDevice::GetTextRect( const tools::Rectangle& rRect,
                                     const OUString& rStr, DrawTextFlags nStyle,
                                     TextRectInfo* pInfo,
                                     const vcl::ITextLayout* _pTextLayout ) const
{
 
    tools::Rectangle           aRect = rRect;
    sal_Int32           nLines;
    long                nWidth = rRect.GetWidth();
    long                nMaxWidth;
    long                nTextHeight = GetTextHeight();
 
    OUString aStr = rStr;
    if ( nStyle & DrawTextFlags::Mnemonic )
        aStr = GetNonMnemonicString( aStr );
 
    if ( nStyle & DrawTextFlags::MultiLine )
    {
        ImplMultiTextLineInfo   aMultiLineInfo;
        ImplTextLineInfo*       pLineInfo;
        sal_Int32               nFormatLines;
        sal_Int32               i;
 
        nMaxWidth = 0;
        vcl::DefaultTextLayout aDefaultLayout( *const_cast< OutputDevice* >( this ) );
        ImplGetTextLines( aMultiLineInfo, nWidth, aStr, nStyle, _pTextLayout ? *_pTextLayout : aDefaultLayout );
        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++ )
            {
                pLineInfo = aMultiLineInfo.GetLine( i );
                if ( bMaxWidth && (pLineInfo->GetWidth() > nMaxWidth) )
                    nMaxWidth = pLineInfo->GetWidth();
                if ( pLineInfo->GetWidth() > pInfo->mnMaxWidth )
                    pInfo->mnMaxWidth = pLineInfo->GetWidth();
            }
        }
        else if ( !nMaxWidth )
        {
            for ( i = 0; i < nLines; i++ )
            {
                pLineInfo = aMultiLineInfo.GetLine( i );
                if ( pLineInfo->GetWidth() > nMaxWidth )
                    nMaxWidth = pLineInfo->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 );
    return aRect;
}
 
static bool ImplIsCharIn( sal_Unicode c, const sal_Char* pStr )
{
    while ( *pStr )
    {
        if ( *pStr == c )
            return true;
        pStr++;
    }
 
    return false;
}
 
OUString OutputDevice::GetEllipsisString( const OUString& rOrigStr, long nMaxWidth,
                                        DrawTextFlags nStyle ) const
{
    vcl::DefaultTextLayout aTextLayout( *const_cast< OutputDevice* >( this ) );
    return ImplGetEllipsisString( *this, rOrigStr, nMaxWidth, nStyle, aTextLayout );
}
 
OUString OutputDevice::ImplGetEllipsisString( const OutputDevice& rTargetDevice, const OUString& rOrigStr, long nMaxWidth,
                                               DrawTextFlags nStyle, const vcl::ITextLayout& _rLayout )
{
    OUString aStr = rOrigStr;
    sal_Int32 nIndex = _rLayout.GetTextBreak( aStr, nMaxWidth, 0, aStr.getLength() );
 
    if ( nIndex != -1 )
    {
        if( (nStyle & DrawTextFlags::CenterEllipsis) == DrawTextFlags::CenterEllipsis )
        {
            OUStringBuffer aTmpStr( aStr );
            // speed it up by removing all but 1.33x as many as the break pos.
            sal_Int32 nEraseChars = std::max<sal_Int32>(4, aStr.getLength() - (nIndex*4)/3);
            while( nEraseChars < aStr.getLength() && _rLayout.GetTextWidth( aTmpStr.toString(), 0, aTmpStr.getLength() ) > nMaxWidth )
            {
                aTmpStr = aStr;
                sal_Int32 i = (aTmpStr.getLength() - nEraseChars)/2;
                aTmpStr.remove(i, nEraseChars++);
                aTmpStr.insert(i, "...");
            }
            aStr = aTmpStr.makeStringAndClear();
        }
        else if ( nStyle & DrawTextFlags::EndEllipsis )
        {
            aStr = aStr.copy(0, nIndex);
            if ( nIndex > 1 )
            {
                aStr += "...";
                while ( !aStr.isEmpty() && (_rLayout.GetTextWidth( aStr, 0, aStr.getLength() ) > nMaxWidth) )
                {
                    if ( (nIndex > 1) || (nIndex == aStr.getLength()) )
                        nIndex--;
                    aStr = aStr.replaceAt( nIndex, 1, "");
                }
            }
 
            if ( aStr.isEmpty() && (nStyle & DrawTextFlags::Clip) )
                aStr += OUStringLiteral1(rOrigStr[ 0 ]);
        }
        else if ( nStyle & DrawTextFlags::PathEllipsis )
        {
            OUString aPath( rOrigStr );
            OUString aAbbreviatedPath;
            osl_abbreviateSystemPath( aPath.pData, &aAbbreviatedPath.pData, nIndex, nullptr );
            aStr = aAbbreviatedPath;
        }
        else if ( nStyle & DrawTextFlags::NewsEllipsis )
        {
            static sal_Char const   pSepChars[] = ".";
            // Determine last section
            sal_Int32 nLastContent = aStr.getLength();
            while ( nLastContent )
            {
                nLastContent--;
                if ( ImplIsCharIn( aStr[ nLastContent ], pSepChars ) )
                    break;
            }
            while ( nLastContent &&
                    ImplIsCharIn( aStr[ nLastContent-1 ], pSepChars ) )
                nLastContent--;
 
            OUString aLastStr = aStr.copy(nLastContent);
            OUString aTempLastStr1( "..." );
            aTempLastStr1 += aLastStr;
            if ( _rLayout.GetTextWidth( aTempLastStr1, 0, aTempLastStr1.getLength() ) > nMaxWidth )
                aStr = OutputDevice::ImplGetEllipsisString( rTargetDevice, aStr, nMaxWidth, nStyle | DrawTextFlags::EndEllipsis, _rLayout );
            else
            {
                sal_Int32 nFirstContent = 0;
                while ( nFirstContent < nLastContent )
                {
                    nFirstContent++;
                    if ( ImplIsCharIn( aStr[ nFirstContent ], pSepChars ) )
                        break;
                }
                while ( (nFirstContent < nLastContent) &&
                        ImplIsCharIn( aStr[ nFirstContent ], pSepChars ) )
                    nFirstContent++;
                // MEM continue here
                if ( nFirstContent >= nLastContent )
                    aStr = OutputDevice::ImplGetEllipsisString( rTargetDevice, aStr, nMaxWidth, nStyle | DrawTextFlags::EndEllipsis, _rLayout );
                else
                {
                    if ( nFirstContent > 4 )
                        nFirstContent = 4;
                    OUString aFirstStr = aStr.copy( 0, nFirstContent );
                    aFirstStr += "...";
                    OUString aTempStr = aFirstStr + aLastStr;
                    if ( _rLayout.GetTextWidth( aTempStr, 0, aTempStr.getLength() ) > nMaxWidth )
                        aStr = OutputDevice::ImplGetEllipsisString( rTargetDevice, aStr, nMaxWidth, nStyle | DrawTextFlags::EndEllipsis, _rLayout );
                    else
                    {
                        do
                        {
                            aStr = aTempStr;
                            if( nLastContent > aStr.getLength() )
                                nLastContent = aStr.getLength();
                            while ( nFirstContent < nLastContent )
                            {
                                nLastContent--;
                                if ( ImplIsCharIn( aStr[ nLastContent ], pSepChars ) )
                                    break;
 
                            }
                            while ( (nFirstContent < nLastContent) &&
                                    ImplIsCharIn( aStr[ nLastContent-1 ], pSepChars ) )
                                nLastContent--;
 
                            if ( nFirstContent < nLastContent )
                            {
                                OUString aTempLastStr = aStr.copy( nLastContent );
                                aTempStr = aFirstStr + aTempLastStr;
 
                                if ( _rLayout.GetTextWidth( aTempStr, 0, aTempStr.getLength() ) > nMaxWidth )
                                    break;
                            }
                        }
                        while ( nFirstContent < nLastContent );
                    }
                }
            }
        }
    }
 
    return aStr;
}
 
void OutputDevice::DrawCtrlText( const Point& rPos, const OUString& rStr,
                                 sal_Int32 nIndex, sal_Int32 nLen,
                                 DrawTextFlags nStyle, MetricVector* pVector, OUString* pDisplayText,
                                 const SalLayoutGlyphs* pGlyphs )
{
    assert(!is_double_buffered_window());
 
    if( (nLen < 0) || (nIndex + nLen >= rStr.getLength()))
    {
        nLen = rStr.getLength() - nIndex;
    }
 
    if ( !IsDeviceOutputNecessary() || (nIndex >= rStr.getLength()) )
        return;
 
    // better get graphics here because ImplDrawMnemonicLine() will not
    // we need a graphics
    if( !mpGraphics && !AcquireGraphics() )
        return;
    if( mbInitClipRegion )
        InitClipRegion();
    if ( mbOutputClipped )
        return;
 
    if( nIndex >= rStr.getLength() )
        return;
 
    if( (nLen < 0) || (nIndex + nLen >= rStr.getLength()))
    {
        nLen = rStr.getLength() - nIndex;
    }
    OUString   aStr = rStr;
    sal_Int32  nMnemonicPos = -1;
 
    long        nMnemonicX = 0;
    long        nMnemonicY = 0;
    long        nMnemonicWidth = 0;
    if ( (nStyle & DrawTextFlags::Mnemonic) && nLen > 1 )
    {
        aStr = GetNonMnemonicString( aStr, nMnemonicPos );
        if ( nMnemonicPos != -1 )
        {
            if( nMnemonicPos < nIndex )
            {
                --nIndex;
            }
            else
            {
                if( nMnemonicPos < (nIndex+nLen) )
                    --nLen;
                SAL_WARN_IF( nMnemonicPos >= (nIndex+nLen), "vcl", "Mnemonic underline marker after last character" );
            }
            bool bInvalidPos = false;
 
            if( nMnemonicPos >= nLen )
            {
                // 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 = nLen-1;
            }
 
            std::unique_ptr<long[]> const pCaretXArray(new long[2 * nLen]);
            /*sal_Bool bRet =*/ GetCaretPositions( aStr, pCaretXArray.get(), nIndex, nLen, pGlyphs );
            long lc_x1 = pCaretXArray[ 2*(nMnemonicPos - nIndex) ];
            long lc_x2 = pCaretXArray[ 2*(nMnemonicPos - nIndex)+1 ];
            nMnemonicWidth = ::abs(static_cast<int>(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();
        }
    }
 
    bool accel = ImplGetSVData()->maNWFData.mbEnableAccel;
    bool autoacc = ImplGetSVData()->maNWFData.mbAutoAccel;
 
    if ( nStyle & DrawTextFlags::Disable && ! pVector )
    {
        Color aOldTextColor;
        Color aOldTextFillColor;
        bool  bRestoreFillColor;
        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();
            }
        }
 
        aOldTextColor = GetTextColor();
        if ( IsTextFillColor() )
        {
            bRestoreFillColor = true;
            aOldTextFillColor = GetTextFillColor();
        }
        else
            bRestoreFillColor = false;
 
        if( bHighContrastBlack )
            SetTextColor( COL_GREEN );
        else if( bHighContrastWhite )
            SetTextColor( COL_LIGHTGREEN );
        else
            SetTextColor( GetSettings().GetStyleSettings().GetDisableColor() );
 
        DrawText( rPos, aStr, nIndex, nLen, pVector, pDisplayText );
        if ( !(GetSettings().GetStyleSettings().GetOptions() & StyleSettingsOptions::NoMnemonics) && !pVector
            && accel && (!autoacc || !(nStyle & DrawTextFlags::HideMnemonic)) )
        {
            if ( nMnemonicPos != -1 )
                ImplDrawMnemonicLine( nMnemonicX, nMnemonicY, nMnemonicWidth );
        }
        SetTextColor( aOldTextColor );
        if ( bRestoreFillColor )
            SetTextFillColor( aOldTextFillColor );
    }
    else
    {
        DrawText( rPos, aStr, nIndex, nLen, pVector, pDisplayText, pGlyphs );
        if ( !(GetSettings().GetStyleSettings().GetOptions() & StyleSettingsOptions::NoMnemonics) && !pVector
            && accel && (!autoacc || !(nStyle & DrawTextFlags::HideMnemonic)) )
        {
            if ( nMnemonicPos != -1 )
                ImplDrawMnemonicLine( nMnemonicX, nMnemonicY, nMnemonicWidth );
        }
    }
 
    if( mpAlphaVDev )
        mpAlphaVDev->DrawCtrlText( rPos, rStr, nIndex, nLen, nStyle, pVector, pDisplayText );
}
 
long OutputDevice::GetCtrlTextWidth( const OUString& rStr, const SalLayoutGlyphs* pGlyphs ) const
{
    sal_Int32 nLen = rStr.getLength();
    sal_Int32 nIndex = 0;
 
    sal_Int32  nMnemonicPos;
    OUString   aStr = GetNonMnemonicString( rStr, nMnemonicPos );
    if ( nMnemonicPos != -1 )
    {
        if ( nMnemonicPos < nIndex )
            nIndex--;
        else if ( (nMnemonicPos >= nIndex) && (static_cast<sal_uLong>(nMnemonicPos) < static_cast<sal_uLong>(nIndex+nLen)) )
            nLen--;
    }
    return GetTextWidth( aStr, nIndex, nLen, nullptr, pGlyphs );
}
 
OUString OutputDevice::GetNonMnemonicString( const OUString& rStr, sal_Int32& rMnemonicPos )
{
    OUString   aStr    = rStr;
    sal_Int32  nLen    = aStr.getLength();
    sal_Int32  i       = 0;
 
    rMnemonicPos = -1;
    while ( i < nLen )
    {
        if ( aStr[ i ] == '~' )
        {
            if ( nLen <= i+1 )
                break;
 
            if ( aStr[ i+1 ] != '~' )
            {
                if ( rMnemonicPos == -1 )
                    rMnemonicPos = i;
                aStr = aStr.replaceAt( i, 1, "" );
                nLen--;
            }
            else
            {
                aStr = aStr.replaceAt( i, 1, "" );
                nLen--;
                i++;
            }
        }
        else
            i++;
    }
 
    return aStr;
}
 
/** OutputDevice::GetSysTextLayoutData
 *
 * @param rStartPt Start point of the text
 * @param rStr Text string that will be transformed into layout of glyphs
 * @param nIndex Position in the string from where layout will be done
 * @param nLen Length of the string
 * @param pDXAry Custom layout adjustment data
 *
 * Export finalized glyph layout data as platform independent SystemTextLayoutData
 * (see vcl/inc/vcl/sysdata.hxx)
 *
 * Only parameters rStartPt and rStr are mandatory, the rest is optional
 * (default values will be used)
 *
 * @return SystemTextLayoutData
 **/
SystemTextLayoutData OutputDevice::GetSysTextLayoutData(const Point& rStartPt, const OUString& rStr, sal_Int32 nIndex, sal_Int32 nLen,
                                                        const long* pDXAry) const
{
    if( (nLen < 0) || (nIndex + nLen >= rStr.getLength()))
    {
        nLen = rStr.getLength() - nIndex;
    }
 
    SystemTextLayoutData aSysLayoutData;
    aSysLayoutData.rGlyphData.reserve( 256 );
    aSysLayoutData.orientation = 0;
 
    if ( mpMetaFile )
    {
        if (pDXAry)
            mpMetaFile->AddAction( new MetaTextArrayAction( rStartPt, rStr, pDXAry, nIndex, nLen ) );
        else
            mpMetaFile->AddAction( new MetaTextAction( rStartPt, rStr, nIndex, nLen ) );
    }
 
    if ( !IsDeviceOutputNecessary() ) return aSysLayoutData;
 
    std::unique_ptr<SalLayout> pLayout = ImplLayout(rStr, nIndex, nLen, rStartPt, 0, pDXAry);
 
    if ( !pLayout ) return aSysLayoutData;
 
    // setup glyphs
    Point aPos;
    const GlyphItem* pGlyph;
    int nStart = 0;
    while (pLayout->GetNextGlyph(&pGlyph, aPos, nStart))
    {
        SystemGlyphData aSystemGlyph;
        aSystemGlyph.index = pGlyph->maGlyphId;
        aSystemGlyph.x = aPos.X();
        aSystemGlyph.y = aPos.Y();
        int nLevel = pGlyph->mnFallbackLevel;
        aSystemGlyph.fallbacklevel = nLevel < MAX_FALLBACK ? nLevel : 0;
        aSysLayoutData.rGlyphData.push_back(aSystemGlyph);
    }
 
    // Get font data
    aSysLayoutData.orientation = pLayout->GetOrientation();
 
    return aSysLayoutData;
}
 
bool OutputDevice::GetTextBoundRect( tools::Rectangle& rRect,
                                         const OUString& rStr, sal_Int32 nBase,
                                         sal_Int32 nIndex, sal_Int32 nLen,
                                         sal_uLong nLayoutWidth, const long* pDXAry,
                                         const SalLayoutGlyphs* pGlyphs ) const
{
    bool bRet = false;
    rRect.SetEmpty();
 
    std::unique_ptr<SalLayout> pSalLayout;
    const Point aPoint;
    // calculate offset when nBase!=nIndex
    long 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 );
        if( pSalLayout )
        {
            nXOffset = pSalLayout->GetTextWidth();
            nXOffset /= pSalLayout->GetUnitsPerPixel();
            // TODO: fix offset calculation for Bidi case
            if( nBase < nIndex)
                nXOffset = -nXOffset;
        }
    }
 
    pSalLayout = ImplLayout(rStr, nIndex, nLen, aPoint, nLayoutWidth, pDXAry, SalLayoutFlags::NONE,
                            nullptr, pGlyphs);
    tools::Rectangle aPixelRect;
    if( pSalLayout )
    {
        bRet = pSalLayout->GetBoundRect( *mpGraphics, aPixelRect );
 
        if( bRet )
        {
            int nWidthFactor = pSalLayout->GetUnitsPerPixel();
 
            if( nWidthFactor > 1 )
            {
                double fFactor = 1.0 / nWidthFactor;
                aPixelRect.SetLeft(
                    static_cast< long >(aPixelRect.Left() * fFactor) );
                aPixelRect.SetRight(
                    static_cast< long >(aPixelRect.Right() * fFactor) );
                aPixelRect.SetTop(
                    static_cast< long >(aPixelRect.Top() * fFactor) );
                aPixelRect.SetBottom(
                    static_cast< long >(aPixelRect.Bottom() * fFactor) );
            }
 
            Point aRotatedOfs( mnTextOffX, mnTextOffY );
            aRotatedOfs -= pSalLayout->GetDrawPosition( Point( nXOffset, 0 ) );
            aPixelRect += aRotatedOfs;
            rRect = PixelToLogic( aPixelRect );
            if( mbMap )
                rRect += Point( 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, const long* pDXArray ) const
{
    // the fonts need to be initialized
    if( mbNewFont )
        ImplNewFont();
    if( mbInitFont )
        InitFont();
    if( !mpFontInstance )
        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
    long 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 );
        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 );
    if( pSalLayout )
    {
        bRet = pSalLayout->GetOutline( *mpGraphics, rVector );
        if( bRet )
        {
            // transform polygon to pixel units
            basegfx::B2DHomMatrix aMatrix;
 
            int nWidthFactor = pSalLayout->GetUnitsPerPixel();
            if( nXOffset | mnTextOffX | mnTextOffY )
            {
                Point aRotatedOfs( mnTextOffX*nWidthFactor, mnTextOffY*nWidthFactor );
                aRotatedOfs -= pSalLayout->GetDrawPosition( Point( nXOffset, 0 ) );
                aMatrix.translate( aRotatedOfs.X(), aRotatedOfs.Y() );
            }
 
            if( nWidthFactor > 1 )
            {
                double fFactor = 1.0 / nWidthFactor;
                aMatrix.scale( fFactor, fFactor );
            }
 
            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 nTWidth, const long* pDXArray ) const
{
    rResultVector.clear();
 
    // get the basegfx polypolygon vector
    basegfx::B2DPolyPolygonVector aB2DPolyPolyVector;
    if( !GetTextOutlines( aB2DPolyPolyVector, rStr, nBase, nIndex, nLen,
                         nTWidth, pDXArray ) )
        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,
                                       sal_Int32 nLen,
                                       sal_uLong nTWidth, const long* pDXArray ) const
{
    rPolyPoly.Clear();
 
    // get the basegfx polypolygon vector
    basegfx::B2DPolyPolygonVector aB2DPolyPolyVector;
    if( !GetTextOutlines( aB2DPolyPolyVector, rStr, 0/*nBase*/, 0/*nIndex*/, nLen,
                         nTWidth, pDXArray ) )
        return false;
 
    // convert and merge into a tool polypolygon
    for (auto const& elem : aB2DPolyPolyVector)
        for( unsigned int i = 0; i < elem.count(); ++i )
            rPolyPoly.Insert(tools::Polygon(elem.getB2DPolygon( i ))); // #i76339#
 
    return true;
}
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V560 A part of conditional expression is always true: (nMnemonicPos >= nIndex).

V560 A part of conditional expression is always true: !pVector.

V560 A part of conditional expression is always true: (nWidth > 0).