Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/LibreOffice/vcl/source/outdev/   (Office von Apache Version 25.8.3.2©)  Datei vom 5.10.2025 mit Größe 80 kB image not shown  

Quelle  text.cxx   Sprache: C

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


#include <sal/config.h>

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

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

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

#include <memory>
#include <optional>

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

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

    mnTextLayoutMode = nTextLayoutMode;

    if( mpAlphaVDev )
        mpAlphaVDev->SetLayoutMode( nTextLayoutMode );
}

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

    meTextLanguage = eTextLanguage;

    if( mpAlphaVDev )
        mpAlphaVDev->SetDigitLanguage( eTextLanguage );
}

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

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

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

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

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

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

}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    DrawMask( aPoint, aBmp, GetTextColor() );

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

    return true;
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        SetTextLineColor( aOldTextLineColor );
        SetOverlineColor( aOldOverlineColor );

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

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

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

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

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

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

    if( IsTextFillColor() )
        ImplDrawTextBackground( rSalLayout );

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

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

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

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

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

    if( mpAlphaVDev )
        mpAlphaVDev->SetTextColor( COL_ALPHA_OPAQUE );
}

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(vcl::drawmode::GetFillColor(rColor, GetDrawMode(), GetSettings().GetStyleSettings()));

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

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

    if( mpAlphaVDev )
        mpAlphaVDev->SetTextFillColor( COL_ALPHA_OPAQUE );
}

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 );
}

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

const SalLayoutFlags eDefaultLayout = SalLayoutFlags::NONE;

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

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

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

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

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

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

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

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

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

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

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

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

    if( mpAlphaVDev )
        mpAlphaVDev->DrawText( rStartPt, rStr, nIndex, nLen, pVector, pDisplayText );
}

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

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

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

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

    if ( mbMap )
        nHeight = ImplDevicePixelToLogicHeight( nHeight );

    return nHeight;
}

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

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

    return ImplDevicePixelToLogicHeightDouble(nHeight);
}

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

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

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

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

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

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

    if (!IsDeviceOutputNecessary())
        return;

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

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

    if (mbOutputClipped)
        return;

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

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

    if (mpAlphaVDev)
    {
        mpAlphaVDev->DrawPartialTextArray(rStartPt, rStr, pDXArray, pKashidaArray, nIndex, nLen,
                                          nPartIndex, nPartLen, flags, pLayoutCache);
    }
}

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

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

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

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

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

vcl::TextArrayMetrics
OutputDevice::GetTextArray(const OUString& rStr, KernArray* pKernArray, sal_Int32 nIndex,
                           sal_Int32 nLen, bool bCaret,
                           vcl::text::TextLayoutCache constconst pLayoutCache,
                           SalLayoutGlyphs constconst pSalLayoutCache, bool bBounds) const
{
    return GetPartialTextArray(rStr, pKernArray, nIndex, nLen, nIndex, nLen, bCaret, pLayoutCache,
                               pSalLayoutCache, bBounds);
}

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

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

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

    KernArray* pDXAry = pKernArray;

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

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

        return {};
    }

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

    double nWidth = 0.0;

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

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

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

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

    vcl::TextArrayMetrics stReturnValue;

    if (bBounds)
    {
        basegfx::B2DRectangle stRect;
        if (pSalLayout->GetBoundRect(stRect))
        {
            auto stRect2 = SalLayout::BoundRect2Rectangle(stRect);
            stReturnValue.aBounds = ImplDevicePixelToLogic(stRect2);
        }
    }

    stReturnValue.nWidth = ImplDevicePixelToLogicWidthDouble(nWidth);

    return stReturnValue;
}

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

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

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

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

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

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

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

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

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

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

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

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

    if ( !IsDeviceOutputNecessary() )
        return;

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

    if( mpAlphaVDev )
        mpAlphaVDev->DrawStretchText( rStartPt, nWidth, rStr, nIndex, nLen );
}

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

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

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

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

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

    if( meTextLanguage ) //TODO: (mnTextLayoutMode & vcl::text::ComplexTextLayoutFlags::SubstituteDigits)
    {
        // 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;
        std::optional<OUStringBuffer> xTmpStr;
        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 )
                {
                    if (!xTmpStr)
                        xTmpStr = OUStringBuffer(rStr);
                    // TODO: are the localized digit surrogates?
                    (*xTmpStr)[pStr - pBase] = cChar;
                }
            }
        }
        if (xTmpStr)
            rStr = (*xTmpStr).makeStringAndClear();
    }

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

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

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

    aLayoutArgs.SetLayoutWidth( nPixelWidth );

    return aLayoutArgs;
}

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

static OutputDevice::FontMappingUseData* fontMappingUseData = nullptr;

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

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

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

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

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

    if (!InitFont())
        return nullptr;

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

    OUString aStr = rOrigStr;

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

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

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

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

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

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

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

        JustificationData stJustification{ nJustMinCluster, nJustLen };

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

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

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

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

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

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

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

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

    if( !pSalLayout )
        return nullptr;

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

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

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

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

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

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

    return pSalLayout;
}

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

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

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

    return nRetVal;
}

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

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

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

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

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

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

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

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

    return nRetVal;
}

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

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

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

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

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

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

    Point       aPos            = rRect.TopLeft();

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

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

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

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

        ImplMultiTextLineInfo   aMultiLineInfo;
        sal_Int32               i;
        sal_Int32               nFormatLines;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    mpMetaFile = &rMtf;
    EnableOutput( false );

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

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

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

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

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

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

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

--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=92 H=95 G=93

¤ Dauer der Verarbeitung: 0.23 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.