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

Quelle  gdiimpl.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 <cstdlib>
#include <memory>
#include <numeric>

#include <svsys.h>

#include "gdiimpl.hxx"

#include <string.h>
#include <rtl/strbuf.hxx>
#include <sal/log.hxx>
#include <tools/poly.hxx>
#include <basegfx/polygon/b2dpolygon.hxx>
#include <basegfx/polygon/b2dpolygontools.hxx>
#include <basegfx/polygon/b2dpolypolygontools.hxx>
#include <win/wincomp.hxx>
#include <win/saldata.hxx>
#include <win/salgdi.h>
#include <win/salbmp.h>
#include <win/scoped_gdi.hxx>
#include <vcl/BitmapAccessMode.hxx>
#include <vcl/BitmapBuffer.hxx>
#include <vcl/BitmapPalette.hxx>
#include <win/salframe.h>
#include <basegfx/matrix/b2dhommatrixtools.hxx>
#include <basegfx/utils/systemdependentdata.hxx>

#include <win/salids.hrc>
#include <ControlCacheKey.hxx>

#include <prewin.h>

#include <gdiplus.h>
#include <gdiplusenums.h>
#include <gdipluscolor.h>

#include <postwin.h>

#define SAL_POLYPOLYCOUNT_STACKBUF          8
#define SAL_POLYPOLYPOINTS_STACKBUF         64

#define SAL_POLY_STACKBUF       32

namespace {

// #100127# Fill point and flag memory from array of points which
// might also contain bezier control points for the PolyDraw() GDI method
// Make sure pWinPointAry and pWinFlagAry are big enough
void ImplPreparePolyDraw( sal_uLong                 nPoly,
                          const sal_uInt32*         pPoints,
                          const Point* const*    pPtAry,
                          const PolyFlags* const*   pFlgAry,
                          POINT*                    pWinPointAry,
                          BYTE*                     pWinFlagAry     )
{
    sal_uLong nCurrPoly;
    for( nCurrPoly=0; nCurrPoly<nPoly; ++nCurrPoly )
    {
        const Point* pCurrPoint = *pPtAry++;
        const PolyFlags* pCurrFlag = *pFlgAry++;
        const sal_uInt32 nCurrPoints = *pPoints++;
        const bool bHaveFlagArray( pCurrFlag );
        sal_uLong nCurrPoint;

        if( nCurrPoints )
        {
            // start figure
            *pWinPointAry++ = POINT { static_cast<LONG>(pCurrPoint->getX()), static_cast<LONG>(pCurrPoint->getY()) };
            pCurrPoint++;
            *pWinFlagAry++  = PT_MOVETO;
            ++pCurrFlag;

            for( nCurrPoint=1; nCurrPoint<nCurrPoints; )
            {
                // #102067# Check existence of flag array
                if( bHaveFlagArray &&
                    ( nCurrPoint + 2 ) < nCurrPoints )
                {
                    PolyFlags P4( pCurrFlag[ 2 ] );

                    if( ( PolyFlags::Control == pCurrFlag[ 0 ] ) &&
                        ( PolyFlags::Control == pCurrFlag[ 1 ] ) &&
                        ( PolyFlags::Normal == P4 || PolyFlags::Smooth == P4 || PolyFlags::Symmetric == P4 ) )
                    {
                        // control point one
                        *pWinPointAry++ = POINT { static_cast<LONG>(pCurrPoint->getX()), static_cast<LONG>(pCurrPoint->getY()) };
                        pCurrPoint++;
                        *pWinFlagAry++  = PT_BEZIERTO;

                        // control point two
                        *pWinPointAry++ = POINT { static_cast<LONG>(pCurrPoint->getX()), static_cast<LONG>(pCurrPoint->getY()) };
                        pCurrPoint++;
                        *pWinFlagAry++  = PT_BEZIERTO;

                        // end point
                        *pWinPointAry++ = POINT { static_cast<LONG>(pCurrPoint->getX()), static_cast<LONG>(pCurrPoint->getY()) };
                        pCurrPoint++;
                        *pWinFlagAry++  = PT_BEZIERTO;

                        nCurrPoint += 3;
                        pCurrFlag += 3;
                        continue;
                    }
                }

                // regular line point
                *pWinPointAry++ = POINT { static_cast<LONG>(pCurrPoint->getX()), static_cast<LONG>(pCurrPoint->getY()) };
                pCurrPoint++;
                *pWinFlagAry++  = PT_LINETO;
                ++pCurrFlag;
                ++nCurrPoint;
            }

            // end figure
            pWinFlagAry[-1] |= PT_CLOSEFIGURE;
        }
    }
}

Color ImplGetROPColor( SalROPColor nROPColor )
{
    Color nColor;
    if ( nROPColor == SalROPColor::N0 )
        nColor = Color( 0, 0, 0 );
    else
        nColor = Color( 255, 255, 255 );
    return nColor;
}

// namespace

WinSalGraphicsImpl::WinSalGraphicsImpl(WinSalGraphics& rParent):
    mrParent(rParent),
    mbXORMode(false),
    mbPen(false),
    mhPen(nullptr),
    mbStockPen(false),
    mbBrush(false),
    mbStockBrush(false),
    mhBrush(nullptr)
{
}

WinSalGraphicsImpl::~WinSalGraphicsImpl()
{
    if ( mhPen )
    {
        if ( !mbStockPen )
            DeletePen( mhPen );
    }

    if ( mhBrush )
    {
        if ( !mbStockBrush )
            DeleteBrush( mhBrush );
    }
}

void WinSalGraphicsImpl::copyBits( const SalTwoRect& rPosAry, SalGraphics* pSrcGraphics )
{
    HDC     hSrcDC;
    DWORD   nRop;

    if ( pSrcGraphics )
        hSrcDC = static_cast<WinSalGraphics*>(pSrcGraphics)->getHDC();
    else
        hSrcDC = mrParent.getHDC();

    if ( mbXORMode )
        nRop = SRCINVERT;
    else
        nRop = SRCCOPY;

    if ( (rPosAry.mnSrcWidth  == rPosAry.mnDestWidth) &&
         (rPosAry.mnSrcHeight == rPosAry.mnDestHeight) )
    {
        BitBlt( mrParent.getHDC(),
                static_cast<int>(rPosAry.mnDestX), static_cast<int>(rPosAry.mnDestY),
                static_cast<int>(rPosAry.mnDestWidth), static_cast<int>(rPosAry.mnDestHeight),
                hSrcDC,
                static_cast<int>(rPosAry.mnSrcX), static_cast<int>(rPosAry.mnSrcY),
                nRop );
    }
    else
    {
        int nOldStretchMode = SetStretchBltMode( mrParent.getHDC(), STRETCH_DELETESCANS );
        StretchBlt( mrParent.getHDC(),
                    static_cast<int>(rPosAry.mnDestX), static_cast<int>(rPosAry.mnDestY),
                    static_cast<int>(rPosAry.mnDestWidth), static_cast<int>(rPosAry.mnDestHeight),
                    hSrcDC,
                    static_cast<int>(rPosAry.mnSrcX), static_cast<int>(rPosAry.mnSrcY),
                    static_cast<int>(rPosAry.mnSrcWidth), static_cast<int>(rPosAry.mnSrcHeight),
                    nRop );
        SetStretchBltMode( mrParent.getHDC(), nOldStretchMode );
    }
}

namespace
{

void MakeInvisibleArea(const RECT& rSrcRect,
                       int nLeft, int nTop, int nRight, int nBottom,
                       HRGN& rhInvalidateRgn)
{
    if (!rhInvalidateRgn)
    {
        rhInvalidateRgn = CreateRectRgnIndirect(&rSrcRect);
    }

    ScopedHRGN hTempRgn(CreateRectRgn(nLeft, nTop, nRight, nBottom));
    CombineRgn(rhInvalidateRgn, rhInvalidateRgn, hTempRgn.get(), RGN_DIFF);
}

void ImplCalcOutSideRgn( const RECT& rSrcRect,
                         int nLeft, int nTop, int nRight, int nBottom,
                         HRGN& rhInvalidateRgn )
{
    // calculate area outside the visible region
    if (rSrcRect.left < nLeft)
    {
        MakeInvisibleArea(rSrcRect, -31999, 0, nLeft, 31999, rhInvalidateRgn);
    }
    if (rSrcRect.top < nTop)
    {
        MakeInvisibleArea(rSrcRect, 0, -31999, 31999, nTop, rhInvalidateRgn);
    }
    if (rSrcRect.right > nRight)
    {
        MakeInvisibleArea(rSrcRect, nRight, 0, 31999, 31999, rhInvalidateRgn);
    }
    if (rSrcRect.bottom > nBottom)
    {
        MakeInvisibleArea(rSrcRect, 0, nBottom, 31999, 31999, rhInvalidateRgn);
    }
}

// namespace

void WinSalGraphicsImpl::copyArea( tools::Long nDestX, tools::Long nDestY,
                            tools::Long nSrcX, tools::Long nSrcY,
                            tools::Long nSrcWidth, tools::Long nSrcHeight,
                            bool bWindowInvalidate )
{
    bool    bRestoreClipRgn = false;
    HRGN    hOldClipRgn = nullptr;
    int     nOldClipRgnType = ERROR;
    HRGN    hInvalidateRgn = nullptr;

    // do we have to invalidate also the overlapping regions?
    if ( bWindowInvalidate && mrParent.isWindow() )
    {
        // compute and invalidate those parts that were either off-screen or covered by other windows
        //  while performing the above BitBlt
        // those regions then have to be invalidated as they contain useless/wrong data
        RECT    aSrcRect;
        RECT    aClipRect;
        RECT    aTempRect;
        RECT    aTempRect2;
        HRGN    hTempRgn;
        HWND    hWnd;

        // restrict srcRect to this window (calc intersection)
        aSrcRect.left   = static_cast<int>(nSrcX);
        aSrcRect.top    = static_cast<int>(nSrcY);
        aSrcRect.right  = aSrcRect.left+static_cast<int>(nSrcWidth);
        aSrcRect.bottom = aSrcRect.top+static_cast<int>(nSrcHeight);
        GetClientRect( mrParent.gethWnd(), &aClipRect );
        if ( IntersectRect( &aSrcRect, &aSrcRect, &aClipRect ) )
        {
            // transform srcRect to screen coordinates
            POINT aPt;
            aPt.x = 0;
            aPt.y = 0;
            ClientToScreen( mrParent.gethWnd(), &aPt );
            aSrcRect.left   += aPt.x;
            aSrcRect.top    += aPt.y;
            aSrcRect.right  += aPt.x;
            aSrcRect.bottom += aPt.y;
            hInvalidateRgn = nullptr;

            // compute the parts that are off screen (ie invisible)
            RECT theScreen;
            ImplSalGetWorkArea( nullptr, &theScreen, nullptr );  // find the screen area taking multiple monitors into account
            ImplCalcOutSideRgn( aSrcRect, theScreen.left, theScreen.top, theScreen.right, theScreen.bottom, hInvalidateRgn );

            // calculate regions that are covered by other windows
            HRGN hTempRgn2 = nullptr;
            HWND hWndTopWindow = mrParent.gethWnd();
            // Find the TopLevel Window, because only Windows which are in
            // in the foreground of our TopLevel window must be considered
            if ( GetWindowStyle( hWndTopWindow ) & WS_CHILD )
            {
                RECT aTempRect3 = aSrcRect;
                do
                {
                    hWndTopWindow = ::GetParent( hWndTopWindow );

                    // Test if the Parent clips our window
                    GetClientRect( hWndTopWindow, &aTempRect );
                    POINT aPt2;
                    aPt2.x = 0;
                    aPt2.y = 0;
                    ClientToScreen( hWndTopWindow, &aPt2 );
                    aTempRect.left   += aPt2.x;
                    aTempRect.top    += aPt2.y;
                    aTempRect.right  += aPt2.x;
                    aTempRect.bottom += aPt2.y;
                    IntersectRect( &aTempRect3, &aTempRect3, &aTempRect );
                }
                while ( GetWindowStyle( hWndTopWindow ) & WS_CHILD );

                // If one or more Parents clip our window, then we must
                // calculate the outside area
                if ( !EqualRect( &aSrcRect, &aTempRect3 ) )
                {
                    ImplCalcOutSideRgn( aSrcRect,
                                        aTempRect3.left, aTempRect3.top,
                                        aTempRect3.right, aTempRect3.bottom,
                                        hInvalidateRgn );
                }
            }
            // retrieve the top-most (z-order) child window
            hWnd = GetWindow( GetDesktopWindow(), GW_CHILD );
            while ( hWnd )
            {
                if ( hWnd == hWndTopWindow )
                    break;
                if ( IsWindowVisible( hWnd ) && !IsIconic( hWnd ) )
                {
                    GetWindowRect( hWnd, &aTempRect );
                    if ( IntersectRect( &aTempRect2, &aSrcRect, &aTempRect ) )
                    {
                        // hWnd covers part or all of aSrcRect
                        if ( !hInvalidateRgn )
                            hInvalidateRgn = CreateRectRgnIndirect( &aSrcRect );

                        // get full bounding box of hWnd
                        hTempRgn = CreateRectRgnIndirect( &aTempRect );

                        // get region of hWnd (the window may be shaped)
                        if ( !hTempRgn2 )
                            hTempRgn2 = CreateRectRgn( 0, 0, 0, 0 );
                        int nRgnType = GetWindowRgn( hWnd, hTempRgn2 );
                        if ( (nRgnType != ERROR) && (nRgnType != NULLREGION) )
                        {
                            // convert window region to screen coordinates
                            OffsetRgn( hTempRgn2, aTempRect.left, aTempRect.top );
                            // and intersect with the window's bounding box
                            CombineRgn( hTempRgn, hTempRgn, hTempRgn2, RGN_AND );
                        }
                        // finally compute that part of aSrcRect which is not covered by any parts of hWnd
                        CombineRgn( hInvalidateRgn, hInvalidateRgn, hTempRgn, RGN_DIFF );
                        DeleteRegion( hTempRgn );
                    }
                }
                // retrieve the next window in the z-order, i.e. the window below hwnd
                hWnd = GetWindow( hWnd, GW_HWNDNEXT );
            }
            if ( hTempRgn2 )
                DeleteRegion( hTempRgn2 );
            if ( hInvalidateRgn )
            {
                // hInvalidateRgn contains the fully visible parts of the original srcRect
                hTempRgn = CreateRectRgnIndirect( &aSrcRect );
                // subtract it from the original rect to get the occluded parts
                int nRgnType = CombineRgn( hInvalidateRgn, hTempRgn, hInvalidateRgn, RGN_DIFF );
                DeleteRegion( hTempRgn );

                if ( (nRgnType != ERROR) && (nRgnType != NULLREGION) )
                {
                    // move the occluded parts to the destination pos
                    int nOffX = static_cast<int>(nDestX-nSrcX);
                    int nOffY = static_cast<int>(nDestY-nSrcY);
                    OffsetRgn( hInvalidateRgn, nOffX-aPt.x, nOffY-aPt.y );

                    // by excluding hInvalidateRgn from the system's clip region
                    // we will prevent bitblt from copying useless data
                    // especially now shadows from overlapping windows will appear (#i36344)
                    hOldClipRgn = CreateRectRgn( 0, 0, 0, 0 );
                    nOldClipRgnType = GetClipRgn( mrParent.getHDC(), hOldClipRgn );

                    bRestoreClipRgn = true// indicate changed clipregion and force invalidate
                    ExtSelectClipRgn( mrParent.getHDC(), hInvalidateRgn, RGN_DIFF );
                }
            }
        }
    }

    BitBlt( mrParent.getHDC(),
            static_cast<int>(nDestX), static_cast<int>(nDestY),
            static_cast<int>(nSrcWidth), static_cast<int>(nSrcHeight),
            mrParent.getHDC(),
            static_cast<int>(nSrcX), static_cast<int>(nSrcY),
            SRCCOPY );

    if( bRestoreClipRgn )
    {
        // restore old clip region
        if( nOldClipRgnType != ERROR )
            SelectClipRgn( mrParent.getHDC(), hOldClipRgn);
        DeleteRegion( hOldClipRgn );

        // invalidate regions that were not copied
        bool    bInvalidate = true;

        // Combine Invalidate vcl::Region with existing ClipRegion
        HRGN    hTempRgn = CreateRectRgn( 0, 0, 0, 0 );
        if ( GetClipRgn( mrParent.getHDC(), hTempRgn ) == 1 )
        {
            int nRgnType = CombineRgn( hInvalidateRgn, hTempRgn, hInvalidateRgn, RGN_AND );
            if ( (nRgnType == ERROR) || (nRgnType == NULLREGION) )
                bInvalidate = false;
        }
        DeleteRegion( hTempRgn );

        if ( bInvalidate )
        {
            InvalidateRgn( mrParent.gethWnd(), hInvalidateRgn, TRUE );
            // here we only initiate an update if this is the MainThread,
            // so that there is no deadlock when handling the Paint event,
            // as the SolarMutex is already held by this Thread
            SalData*    pSalData = GetSalData();
            DWORD       nCurThreadId = GetCurrentThreadId();
            if ( pSalData->mnAppThreadId == nCurThreadId )
                UpdateWindow( mrParent.gethWnd() );
        }

        DeleteRegion( hInvalidateRgn );
    }

}

namespace {

void ImplDrawBitmap( HDC hDC, const SalTwoRect& rPosAry, const WinSalBitmap& rSalBitmap,
                     bool bPrinter, int nDrawMode )
{
    if( !hDC )
        return;

    HGLOBAL     hDrawDIB;
    HBITMAP     hDrawDDB = rSalBitmap.ImplGethDDB();
    std::optional<WinSalBitmap> xTmpSalBmp;
    bool        bPrintDDB = ( bPrinter && hDrawDDB );

    if( bPrintDDB )
    {
        xTmpSalBmp.emplace();
        xTmpSalBmp->Create(rSalBitmap, vcl::bitDepthToPixelFormat(rSalBitmap.GetBitCount()));
        hDrawDIB = xTmpSalBmp->ImplGethDIB();
    }
    else
        hDrawDIB = rSalBitmap.ImplGethDIB();

    if( hDrawDIB )
    {
        if (PBITMAPINFO pBI = static_cast<PBITMAPINFO>(GlobalLock( hDrawDIB )))
        {
            PBYTE               pBits = reinterpret_cast<PBYTE>(pBI) + pBI->bmiHeader.biSize +
                                        WinSalBitmap::ImplGetDIBColorCount( hDrawDIB ) * sizeof( RGBQUAD );
            const int           nOldStretchMode = SetStretchBltMode( hDC, STRETCH_DELETESCANS );

            StretchDIBits( hDC,
                           static_cast<int>(rPosAry.mnDestX), static_cast<int>(rPosAry.mnDestY),
                           static_cast<int>(rPosAry.mnDestWidth), static_cast<int>(rPosAry.mnDestHeight),
                           static_cast<int>(rPosAry.mnSrcX), static_cast<int>(pBI->bmiHeader.biHeight - rPosAry.mnSrcHeight - rPosAry.mnSrcY),
                           static_cast<int>(rPosAry.mnSrcWidth), static_cast<int>(rPosAry.mnSrcHeight),
                           pBits, pBI, DIB_RGB_COLORS, nDrawMode );

            GlobalUnlock( hDrawDIB );
            SetStretchBltMode( hDC, nOldStretchMode );
        }
    }
    else if( hDrawDDB && !bPrintDDB )
    {
        ScopedCachedHDC<CACHED_HDC_DRAW> hBmpDC(hDrawDDB);

        COLORREF    nOldBkColor = RGB(0xFF,0xFF,0xFF);
        COLORREF    nOldTextColor = RGB(0,0,0);
        bool        bMono = ( rSalBitmap.GetBitCount() == 1 );

        if( bMono )
        {
            COLORREF nBkColor = RGB( 0xFF, 0xFF, 0xFF );
            COLORREF nTextColor = RGB( 0x00, 0x00, 0x00 );
            //fdo#33455 handle 1 bit depth pngs with palette entries
            //to set fore/back colors
            if (BitmapBuffer* pBitmapBuffer = const_cast<WinSalBitmap&>(rSalBitmap).AcquireBuffer(BitmapAccessMode::Info))
            {
                const BitmapPalette& rPalette = pBitmapBuffer->maPalette;
                if (rPalette.GetEntryCount() == 2)
                {
                    Color nCol = rPalette[0];
                    nTextColor = RGB( nCol.GetRed(), nCol.GetGreen(), nCol.GetBlue() );
                    nCol = rPalette[1];
                    nBkColor = RGB( nCol.GetRed(), nCol.GetGreen(), nCol.GetBlue() );
                }
                const_cast<WinSalBitmap&>(rSalBitmap).ReleaseBuffer(pBitmapBuffer, BitmapAccessMode::Info);
            }
            nOldBkColor = SetBkColor( hDC, nBkColor );
            nOldTextColor = ::SetTextColor( hDC, nTextColor );
        }

        if ( (rPosAry.mnSrcWidth  == rPosAry.mnDestWidth) &&
             (rPosAry.mnSrcHeight == rPosAry.mnDestHeight) )
        {
            BitBlt( hDC,
                    static_cast<int>(rPosAry.mnDestX), static_cast<int>(rPosAry.mnDestY),
                    static_cast<int>(rPosAry.mnDestWidth), static_cast<int>(rPosAry.mnDestHeight),
                    hBmpDC.get(),
                    static_cast<int>(rPosAry.mnSrcX), static_cast<int>(rPosAry.mnSrcY),
                    nDrawMode );
        }
        else
        {
            const int nOldStretchMode = SetStretchBltMode( hDC, STRETCH_DELETESCANS );

            StretchBlt( hDC,
                        static_cast<int>(rPosAry.mnDestX), static_cast<int>(rPosAry.mnDestY),
                        static_cast<int>(rPosAry.mnDestWidth), static_cast<int>(rPosAry.mnDestHeight),
                        hBmpDC.get(),
                        static_cast<int>(rPosAry.mnSrcX), static_cast<int>(rPosAry.mnSrcY),
                        static_cast<int>(rPosAry.mnSrcWidth), static_cast<int>(rPosAry.mnSrcHeight),
                        nDrawMode );

            SetStretchBltMode( hDC, nOldStretchMode );
        }

        if( bMono )
        {
            SetBkColor( hDC, nOldBkColor );
            ::SetTextColor( hDC, nOldTextColor );
        }
    }
}

// namespace

void WinSalGraphicsImpl::drawBitmap(const SalTwoRect& rPosAry, const SalBitmap&&nbsp;rSalBitmap)
{
    bool bTryDirectPaint(!mrParent.isPrinter() && !mbXORMode);

    if(bTryDirectPaint)
    {
        // only paint direct when no scaling and no MapMode, else the
        // more expensive conversions may be done for short-time Bitmap/BitmapEx
        // used for buffering only
        if(rPosAry.mnSrcWidth == rPosAry.mnDestWidth && rPosAry.mnSrcHeight == rPosAry.mnDestHeight)
        {
            bTryDirectPaint = false;
        }
    }

    // try to draw using GdiPlus directly
    if(bTryDirectPaint && TryDrawBitmapGDIPlus(rPosAry, rSalBitmap))
    {
        return;
    }

    // fall back old stuff
    assert(dynamic_cast<const WinSalBitmap*>(&rSalBitmap));

    ImplDrawBitmap(mrParent.getHDC(), rPosAry, static_cast<const WinSalBitmap&>(rSalBitmap),
        mrParent.isPrinter(),
        mbXORMode ? SRCINVERT : SRCCOPY );
}

void WinSalGraphicsImpl::drawBitmap( const SalTwoRect& rPosAry,
                              const SalBitmap& rSSalBitmap,
                              const SalBitmap& rSTransparentBitmap )
{
    SAL_WARN_IF( mrParent.isPrinter(), "vcl""No transparency print possible!" );
    bool bTryDirectPaint(!mrParent.isPrinter() && !mbXORMode);

    // try to draw using GdiPlus directly
    if(bTryDirectPaint && drawAlphaBitmap(rPosAry, rSSalBitmap, rSTransparentBitmap))
    {
        return;
    }

    assert(dynamic_cast<const WinSalBitmap*>(&rSSalBitmap));
    assert(dynamic_cast<const WinSalBitmap*>(&rSTransparentBitmap));

    const WinSalBitmap& rSalBitmap = static_cast<const WinSalBitmap&>(rSSalBitmap);
    const WinSalBitmap& rTransparentBitmap = static_cast<const WinSalBitmap&>(rSTransparentBitmap);

    SalTwoRect  aPosAry = rPosAry;
    int         nDstX = static_cast<int>(aPosAry.mnDestX);
    int         nDstY = static_cast<int>(aPosAry.mnDestY);
    int         nDstWidth = static_cast<int>(aPosAry.mnDestWidth);
    int         nDstHeight = static_cast<int>(aPosAry.mnDestHeight);
    HDC         hDC = mrParent.getHDC();

    ScopedHBITMAP hMemBitmap;
    ScopedHBITMAP hMaskBitmap;

    if( ( nDstWidth > CACHED_HDC_DEFEXT ) || ( nDstHeight > CACHED_HDC_DEFEXT ) )
    {
        hMemBitmap.reset(CreateCompatibleBitmap(hDC, nDstWidth, nDstHeight));
        hMaskBitmap.reset(CreateCompatibleBitmap(hDC, nDstWidth, nDstHeight));
    }

    ScopedCachedHDC<CACHED_HDC_1> hMemDC(hMemBitmap.get());
    ScopedCachedHDC<CACHED_HDC_2> hMaskDC(hMaskBitmap.get());

    aPosAry.mnDestX = aPosAry.mnDestY = 0;
    BitBlt( hMemDC.get(), 0, 0, nDstWidth, nDstHeight, hDC, nDstX, nDstY, SRCCOPY );

    // WIN/WNT seems to have a minor problem mapping the correct color of the
    // mask to the palette if we draw the DIB directly ==> draw DDB
    if( ( GetBitCount() <= 8 ) && rTransparentBitmap.ImplGethDIB() && rTransparentBitmap.GetBitCount() == 1 )
    {
        WinSalBitmap aTmp;

        if( aTmp.Create( rTransparentBitmap, &mrParent ) )
            ImplDrawBitmap( hMaskDC.get(), aPosAry, aTmp, false, SRCCOPY );
    }
    else
        ImplDrawBitmap( hMaskDC.get(), aPosAry, rTransparentBitmap, false, SRCCOPY );

    // now MemDC contains background, MaskDC the transparency mask

    // #105055# Respect XOR mode
    if( mbXORMode )
    {
        ImplDrawBitmap( hMaskDC.get(), aPosAry, rSalBitmap, false, SRCERASE );
        // now MaskDC contains the bitmap area with black background
        BitBlt( hMemDC.get(), 0, 0, nDstWidth, nDstHeight, hMaskDC.get(), 0, 0, SRCINVERT );
        // now MemDC contains background XORed bitmap area on top
    }
    else
    {
        BitBlt( hMemDC.get(), 0, 0, nDstWidth, nDstHeight, hMaskDC.get(), 0, 0, SRCAND );
        // now MemDC contains background with masked-out bitmap area
        ImplDrawBitmap( hMaskDC.get(), aPosAry, rSalBitmap, false, SRCERASE );
        // now MaskDC contains the bitmap area with black background
        BitBlt( hMemDC.get(), 0, 0, nDstWidth, nDstHeight, hMaskDC.get(), 0, 0, SRCPAINT );
        // now MemDC contains background and bitmap merged together
    }
    // copy to output DC
    BitBlt( hDC, nDstX, nDstY, nDstWidth, nDstHeight, hMemDC.get(), 0, 0, SRCCOPY );
}

bool WinSalGraphicsImpl::drawAlphaRect( tools::Long nX, tools::Long nY, tools::Long nWidth,
                                    tools::Long nHeight, sal_uInt8 nTransparency )
{
    if( mbPen || !mbBrush || mbXORMode )
        return false// can only perform solid fills without XOR.

    ScopedCachedHDC<CACHED_HDC_1> hMemDC(nullptr);
    SetPixel( hMemDC.get(), int(0), int(0), mnBrushColor );

    BLENDFUNCTION aFunc = {
        AC_SRC_OVER,
        0,
        sal::static_int_cast<sal_uInt8>(255 - 255L*nTransparency/100),
        0
    };

    // hMemDC contains a 1x1 bitmap of the right color - stretch-blit
    // that to dest hdc
    bool bRet = GdiAlphaBlend(mrParent.getHDC(), nX, nY, nWidth, nHeight,
                              hMemDC.get(), 0,0,1,1,
                              aFunc ) == TRUE;

    return bRet;
}

void WinSalGraphicsImpl::drawMask(const SalTwoRect& rPosAry,
                                  const SalBitmap& rSSalBitmap,
                                  Color nMaskColor)
{
    SAL_WARN_IF( mrParent.isPrinter(), "vcl""No transparency print possible!" );

    assert(dynamic_cast<const WinSalBitmap*>(&rSSalBitmap));

    const WinSalBitmap& rSalBitmap = static_cast<const WinSalBitmap&>(rSSalBitmap);

    SalTwoRect  aPosAry = rPosAry;
    const HDC hDC = mrParent.getHDC();

    ScopedSelectedHBRUSH hBrush(hDC, CreateSolidBrush(RGB(nMaskColor.GetRed(),
                                                          nMaskColor.GetGreen(),
                                                          nMaskColor.GetBlue())));

    // WIN/WNT seems to have a minor problem mapping the correct color of the
    // mask to the palette if we draw the DIB directly ==> draw DDB
    if( ( GetBitCount() <= 8 ) && rSalBitmap.ImplGethDIB() && rSalBitmap.GetBitCount() == 1 )
    {
        WinSalBitmap aTmp;

        if( aTmp.Create( rSalBitmap, &mrParent ) )
            ImplDrawBitmap( hDC, aPosAry, aTmp, false, 0x00B8074AUL ); // raster operation PSDPxax
    }
    else
        ImplDrawBitmap( hDC, aPosAry, rSalBitmap, false, 0x00B8074AUL );// raster operation PSDPxax
}

std::shared_ptr<SalBitmap> WinSalGraphicsImpl::getBitmap( tools::Long nX, tools::Long nY, tools::Long nDX, tools::Long nDY )
{
    SAL_WARN_IF( mrParent.isPrinter(), "vcl""No ::GetBitmap() from printer possible!" );

    std::shared_ptr<WinSalBitmap> pSalBitmap;

    nDX = std::abs( nDX );
    nDY = std::abs( nDY );

    HDC     hDC = mrParent.getHDC();
    HBITMAP hBmpBitmap = CreateCompatibleBitmap( hDC, nDX, nDY );
    bool    bRet;

    {
        ScopedCachedHDC<CACHED_HDC_1> hBmpDC(hBmpBitmap);

        bRet = BitBlt(hBmpDC.get(), 0, 0,
                      static_cast<int>(nDX), static_cast<int>(nDY), hDC,
                      static_cast<int>(nX), static_cast<int>(nY), SRCCOPY) ? TRUE : FALSE;
    }

    if( bRet )
    {
        pSalBitmap = std::make_shared<WinSalBitmap>();

        if( !pSalBitmap->Create( hBmpBitmap ) )
        {
            pSalBitmap.reset();
        }
    }
    else
    {
        // #124826# avoid resource leak! Happens when running without desktop access (remote desktop, service, may be screensavers)
        DeleteBitmap( hBmpBitmap );
    }

    return pSalBitmap;
}

Color WinSalGraphicsImpl::getPixel( tools::Long nX, tools::Long nY )
{
    COLORREF aWinCol = ::GetPixel( mrParent.getHDC(), static_cast<int>(nX), static_cast<int>(nY) );

    if ( CLR_INVALID == aWinCol )
        return Color( 0, 0, 0 );
    else
        return Color( GetRValue( aWinCol ),
                              GetGValue( aWinCol ),
                              GetBValue( aWinCol ) );
}

namespace
{

HBRUSH Get50PercentBrush()
{
    SalData* pSalData = GetSalData();
    if ( !pSalData->mh50Brush )
    {
        if ( !pSalData->mh50Bmp )
            pSalData->mh50Bmp = ImplLoadSalBitmap( SAL_RESID_BITMAP_50 );
        pSalData->mh50Brush = CreatePatternBrush( pSalData->mh50Bmp );
    }

    return pSalData->mh50Brush;
}

// namespace

void WinSalGraphicsImpl::invert( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight, SalInvert nFlags )
{
    if ( nFlags & SalInvert::TrackFrame )
    {
        HPEN    hDotPen = CreatePen( PS_DOT, 0, 0 );
        HPEN    hOldPen = SelectPen( mrParent.getHDC(), hDotPen );
        HBRUSH  hOldBrush = SelectBrush( mrParent.getHDC(), GetStockBrush( NULL_BRUSH ) );
        int     nOldROP = SetROP2( mrParent.getHDC(), R2_NOT );

        Rectangle( mrParent.getHDC(), static_cast<int>(nX), static_cast<int>(nY), static_cast<int>(nX+nWidth), static_cast<int>(nY+nHeight) );

        SetROP2( mrParent.getHDC(), nOldROP );
        SelectPen( mrParent.getHDC(), hOldPen );
        SelectBrush( mrParent.getHDC(), hOldBrush );
        DeletePen( hDotPen );
    }
    else if ( nFlags & SalInvert::N50 )
    {
        COLORREF nOldTextColor = ::SetTextColor( mrParent.getHDC(), 0 );
        HBRUSH hOldBrush = SelectBrush( mrParent.getHDC(), Get50PercentBrush() );
        PatBlt( mrParent.getHDC(), nX, nY, nWidth, nHeight, PATINVERT );
        ::SetTextColor( mrParent.getHDC(), nOldTextColor );
        SelectBrush( mrParent.getHDC(), hOldBrush );
    }
    else
    {
         RECT aRect;
         aRect.left      = static_cast<int>(nX);
         aRect.top       = static_cast<int>(nY);
         aRect.right     = static_cast<int>(nX)+nWidth;
         aRect.bottom    = static_cast<int>(nY)+nHeight;
         ::InvertRect( mrParent.getHDC(), &aRect );
    }
}

void WinSalGraphicsImpl::invert( sal_uInt32 nPoints, const Point* pPtAry, SalInvert nSalFlags )
{
    HPEN        hPen;
    HPEN        hOldPen;
    HBRUSH      hBrush;
    HBRUSH      hOldBrush = nullptr;
    COLORREF    nOldTextColor RGB(0,0,0);
    int         nOldROP = SetROP2( mrParent.getHDC(), R2_NOT );

    if ( nSalFlags & SalInvert::TrackFrame )
        hPen = CreatePen( PS_DOT, 0, 0 );
    else
    {

        if ( nSalFlags & SalInvert::N50 )
            hBrush = Get50PercentBrush();
        else
            hBrush = GetStockBrush( BLACK_BRUSH );

        hPen = GetStockPen( NULL_PEN );
        nOldTextColor = ::SetTextColor( mrParent.getHDC(), 0 );
        hOldBrush = SelectBrush( mrParent.getHDC(), hBrush );
    }
    hOldPen = SelectPen( mrParent.getHDC(), hPen );

    std::unique_ptr<POINT[]> pWinPtAry(new POINT[nPoints]);
    for (sal_uInt32 i=0; i<nPoints; ++i)
        pWinPtAry[i] = POINT { static_cast<LONG>(pPtAry[i].getX()), static_cast<LONG>(pPtAry[i].getY()) };

    if ( nSalFlags & SalInvert::TrackFrame )
        Polyline( mrParent.getHDC(), pWinPtAry.get(), static_cast<int>(nPoints) );
    else
        Polygon( mrParent.getHDC(), pWinPtAry.get(), static_cast<int>(nPoints) );

    SetROP2( mrParent.getHDC(), nOldROP );
    SelectPen( mrParent.getHDC(), hOldPen );

    if ( nSalFlags & SalInvert::TrackFrame )
        DeletePen( hPen );
    else
    {
        ::SetTextColor( mrParent.getHDC(), nOldTextColor );
        SelectBrush( mrParent.getHDC(), hOldBrush );
    }
}

sal_uInt16 WinSalGraphicsImpl::GetBitCount() const
{
    return static_cast<sal_uInt16>(GetDeviceCaps( mrParent.getHDC(), BITSPIXEL ));
}

tools::Long WinSalGraphicsImpl::GetGraphicsWidth() const
{
    if( !mrParent.gethWnd() || !IsWindow( mrParent.gethWnd() ) )
        return 0;

    WinSalFrame* pFrame = GetWindowPtr( mrParent.gethWnd() );
    if( !pFrame )
        return 0;

    if (pFrame->GetWidth())
        return pFrame->GetWidth();

    // TODO: perhaps not needed, width should always be up-to-date
    RECT aRect;
    GetClientRect( mrParent.gethWnd(), &aRect );
    return aRect.right;
}

void WinSalGraphicsImpl::ResetClipRegion()
{
    if ( mrParent.mhRegion )
    {
        DeleteRegion( mrParent.mhRegion );
        mrParent.mhRegion = nullptr;
    }

    SelectClipRgn( mrParent.getHDC(), nullptr );
}

static bool containsOnlyHorizontalAndVerticalEdges(const basegfx::B2DPolygon& rCandidate)
{
    if(rCandidate.areControlPointsUsed())
        return false;

    const sal_uInt32 nPointCount(rCandidate.count());

    if(nPointCount < 2)
        return true;

    const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount + 1 : nPointCount);
    basegfx::B2DPoint aLast(rCandidate.getB2DPoint(0));

    for(sal_uInt32 a(1); a < nEdgeCount; a++)
    {
        const sal_uInt32 nNextIndex(a % nPointCount);
        const basegfx::B2DPoint aCurrent(rCandidate.getB2DPoint(nNextIndex));

        if(!basegfx::fTools::equal(aLast.getX(), aCurrent.getX()) && !basegfx::fTools::equal(aLast.getY(), aCurrent.getY()))
        {
            return false;
        }

        aLast = aCurrent;
    }

    return true;
}

static bool containsOnlyHorizontalAndVerticalEdges(const basegfx::B2DPolyPolygon&&nbsp;rCandidate)
{
    if(rCandidate.areControlPointsUsed())
        return false;

    for(auto const& rPolygon : rCandidate)
    {
        if(!containsOnlyHorizontalAndVerticalEdges(rPolygon))
        {
            return false;
        }
    }

    return true;
}

void WinSalGraphicsImpl::setClipRegion( const vcl::Region& i_rClip )
{
    if ( mrParent.mhRegion )
    {
        DeleteRegion( mrParent.mhRegion );
        mrParent.mhRegion = nullptr;
    }

    bool bUsePolygon(i_rClip.HasPolyPolygonOrB2DPolyPolygon());
    static bool bTryToAvoidPolygon(true);

    // #i122149# try to avoid usage of tools::PolyPolygon ClipRegions when tools::PolyPolygon is no curve
    // and only contains horizontal/vertical edges. In that case, use the fallback
    // in GetRegionRectangles which will use vcl::Region::GetAsRegionBand() which will do
    // the correct polygon-to-RegionBand transformation.
    // Background is that when using the same Rectangle as rectangle or as Polygon
    // clip region will lead to different results; the polygon-based one will be
    // one pixel less to the right and down (see GDI docu for CreatePolygonRgn). This
    // again is because of the polygon-nature and it's classic handling when filling.
    // This also means that all cases which use a 'true' polygon-based incarnation of
    // a vcl::Region should know what they do - it may lead to repaint errors.
    if(bUsePolygon && bTryToAvoidPolygon)
    {
        const basegfx::B2DPolyPolygon aPolyPolygon( i_rClip.GetAsB2DPolyPolygon() );

        if(!aPolyPolygon.areControlPointsUsed())
        {
            if(containsOnlyHorizontalAndVerticalEdges(aPolyPolygon))
            {
                bUsePolygon = false;
            }
        }
    }

    if(bUsePolygon)
    {
        // #i122149# check the comment above to know that this may lead to potential repaint
        // problems. It may be solved (if needed) by scaling the polygon by one in X
        // and Y. Currently the workaround to only use it if really unavoidable will
        // solve most cases. When someone is really using polygon-based Regions he
        // should know what he is doing.
        // Added code to do that scaling to check if it works, testing it.
        const basegfx::B2DPolyPolygon aPolyPolygon( i_rClip.GetAsB2DPolyPolygon() );
        const sal_uInt32 nCount(aPolyPolygon.count());

        if( nCount )
        {
            std::vector< POINT > aPolyPoints;
            aPolyPoints.reserve( 1024 );
            std::vector< INT > aPolyCounts( nCount, 0 );
            basegfx::B2DHomMatrix aExpand;
            sal_uInt32 nTargetCount(0);
            static bool bExpandByOneInXandY(true);

            if(bExpandByOneInXandY)
            {
                const basegfx::B2DRange aRangeS(aPolyPolygon.getB2DRange());
                const basegfx::B2DRange aRangeT(aRangeS.getMinimum(), aRangeS.getMaximum() + basegfx::B2DTuple(1.0, 1.0));
                aExpand = basegfx::utils::createSourceRangeTargetRangeTransform(aRangeS, aRangeT);
            }

            for(auto const& rPolygon : aPolyPolygon)
            {
                const basegfx::B2DPolygon aPoly(
                    basegfx::utils::adaptiveSubdivideByDistance(
                        rPolygon,
                        1));
                const sal_uInt32 nPoints(aPoly.count());

                // tdf#40863 For CustomShapes there is a hack (see
                // f64ef72743e55389e446e0d4bc6febd475011023) that adds polygons
                // with a single point in top-left and bottom-right corner
                // of the BoundRect to be able to determine the correct BoundRect
                // in the slideshow. Unfortunately, CreatePolyPolygonRgn below
                // fails with polygons containing a single pixel, so clipping is
                // lost. For now, use only polygons with more than two points - the
                // ones that may have an area.
                // Note: polygons with one point which are curves may have an area,
                // but the polygon is already subdivided here, so no need to test
                // this.
                if(nPoints > 2)
                {
                    aPolyCounts[nTargetCount] = nPoints;
                    nTargetCount++;

                    for( sal_uInt32 b = 0; b < nPoints; b++ )
                    {
                        basegfx::B2DPoint aPt(aPoly.getB2DPoint(b));

                        if(bExpandByOneInXandY)
                        {
                            aPt = aExpand * aPt;
                        }

                        POINT aPOINT;
                        // #i122149# do correct rounding
                        aPOINT.x = basegfx::fround(aPt.getX());
                        aPOINT.y = basegfx::fround(aPt.getY());
                        aPolyPoints.push_back( aPOINT );
                    }
                }
            }

            if(nTargetCount)
            {
                mrParent.mhRegion = CreatePolyPolygonRgn( aPolyPoints.data(), aPolyCounts.data(), nTargetCount, ALTERNATE );
            }
        }
    }
    else
    {
        RectangleVector aRectangles;
        i_rClip.GetRegionRectangles(aRectangles);

        sal_uLong nRectBufSize = sizeof(RECT)*aRectangles.size();
        if ( aRectangles.size() < SAL_CLIPRECT_COUNT )
        {
            if ( !mrParent.mpStdClipRgnData )
                mrParent.mpStdClipRgnData = reinterpret_cast<RGNDATA*>(new BYTE[sizeof(RGNDATA)-1+(SAL_CLIPRECT_COUNT*sizeof(RECT))]);
            mrParent.mpClipRgnData = mrParent.mpStdClipRgnData;
        }
        else
            mrParent.mpClipRgnData = reinterpret_cast<RGNDATA*>(new BYTE[sizeof(RGNDATA)-1+nRectBufSize]);
        mrParent.mpClipRgnData->rdh.dwSize   = sizeof( RGNDATAHEADER );
        mrParent.mpClipRgnData->rdh.iType    = RDH_RECTANGLES;
        mrParent.mpClipRgnData->rdh.nCount   = aRectangles.size();
        mrParent.mpClipRgnData->rdh.nRgnSize = nRectBufSize;
        RECT*       pBoundRect = &(mrParent.mpClipRgnData->rdh.rcBound);
        SetRectEmpty( pBoundRect );
        RECT* pNextClipRect         = reinterpret_cast<RECT*>(&(mrParent.mpClipRgnData->Buffer));
        bool bFirstClipRect         = true;

        for (auto const& rectangle : aRectangles)
        {
            const tools::Long nW(rectangle.GetWidth());
            const tools::Long nH(rectangle.GetHeight());

            if(nW && nH)
            {
                const tools::Long nRight(rectangle.Left() + nW);
                const tools::Long nBottom(rectangle.Top() + nH);

                if(bFirstClipRect)
                {
                    pBoundRect->left = rectangle.Left();
                    pBoundRect->top = rectangle.Top();
                    pBoundRect->right = nRight;
                    pBoundRect->bottom = nBottom;
                    bFirstClipRect = false;
                }
                else
                {
                    if(rectangle.Left() < pBoundRect->left)
                    {
                        pBoundRect->left = static_cast<int>(rectangle.Left());
                    }

                    if(rectangle.Top() < pBoundRect->top)
                    {
                        pBoundRect->top = static_cast<int>(rectangle.Top());
                    }

                    if(nRight > pBoundRect->right)
                    {
                        pBoundRect->right = static_cast<int>(nRight);
                    }

                    if(nBottom > pBoundRect->bottom)
                    {
                        pBoundRect->bottom = static_cast<int>(nBottom);
                    }
                }

                pNextClipRect->left = static_cast<int>(rectangle.Left());
                pNextClipRect->top = static_cast<int>(rectangle.Top());
                pNextClipRect->right = static_cast<int>(nRight);
                pNextClipRect->bottom = static_cast<int>(nBottom);
                pNextClipRect++;
            }
            else
            {
                mrParent.mpClipRgnData->rdh.nCount--;
                mrParent.mpClipRgnData->rdh.nRgnSize -= sizeof( RECT );
            }
        }

        // create clip region from ClipRgnData
        if(0 == mrParent.mpClipRgnData->rdh.nCount)
        {
            // #i123585# region is empty; this may happen when e.g. a tools::PolyPolygon is given
            // that contains no polygons or only empty ones (no width/height). This is
            // perfectly fine and we are done, except setting it (see end of method)
        }
        else if(1 == mrParent.mpClipRgnData->rdh.nCount)
        {
            RECT* pRect = &(mrParent.mpClipRgnData->rdh.rcBound);
            mrParent.mhRegion = CreateRectRgn( pRect->left, pRect->top,
                                                     pRect->right, pRect->bottom );
        }
        else if(mrParent.mpClipRgnData->rdh.nCount > 1)
        {
            sal_uLong nSize = mrParent.mpClipRgnData->rdh.nRgnSize+sizeof(RGNDATAHEADER);
            mrParent.mhRegion = ExtCreateRegion( nullptr, nSize, mrParent.mpClipRgnData );

            // if ExtCreateRegion(...) is not supported
            if( !mrParent.mhRegion )
            {
                RGNDATAHEADER const & pHeader = mrParent.mpClipRgnData->rdh;

                if( pHeader.nCount )
                {
                    RECT* pRect = reinterpret_cast<RECT*>(mrParent.mpClipRgnData->Buffer);
                    mrParent.mhRegion = CreateRectRgn( pRect->left, pRect->top, pRect->right, pRect->bottom );
                    pRect++;

                    for( sal_uLong n = 1; n < pHeader.nCount; n++, pRect++ )
                    {
                        ScopedHRGN hRgn(CreateRectRgn(pRect->left, pRect->top, pRect->right, pRect->bottom));
                        CombineRgn( mrParent.mhRegion, mrParent.mhRegion, hRgn.get(), RGN_OR );
                    }
                }
            }

            if ( mrParent.mpClipRgnData != mrParent.mpStdClipRgnData )
                delete [] reinterpret_cast<BYTE*>(mrParent.mpClipRgnData);
        }
    }

    if( mrParent.mhRegion )
    {
        SelectClipRgn( mrParent.getHDC(), mrParent.mhRegion );

        // debug code if you want to check range of the newly applied ClipRegion
        //RECT aBound;
        //const int aRegionType = GetRgnBox(mrParent.mhRegion, &aBound);
    }
    else
    {
        // #i123585# See above, this is a valid case, execute it
        SelectClipRgn( mrParent.getHDC(), nullptr );
    }
}

void WinSalGraphicsImpl::SetLineColor()
{
    ResetPen(GetStockPen(NULL_PEN));

    // set new data
    mbPen       = false;
    mbStockPen  = true;
}

void WinSalGraphicsImpl::SetLineColor(Color nColor)
{
    COLORREF nPenColor = PALETTERGB(nColor.GetRed(),
                                    nColor.GetGreen(),
                                    nColor.GetBlue());
    bool bStockPen = false;

    HPEN hNewPen = SearchStockPen(nPenColor);
    if (hNewPen)
        bStockPen = true;
    else
        hNewPen = MakePen(nColor);

    ResetPen(hNewPen);

    // set new data
    mnPenColor  = nPenColor;
    maLineColor = nColor;
    mbPen       = true;
    mbStockPen  = bStockPen;
}

HPEN WinSalGraphicsImpl::SearchStockPen(COLORREF nPenColor)
{
    // Only screen, because printer has problems, when we use stock objects.
    if (mrParent.isPrinter())
        return nullptr;

    const SalData* pSalData = GetSalData();

    for (sal_uInt16 i = 0; i < pSalData->mnStockPenCount; i++)
    {
        if (nPenColor == pSalData->maStockPenColorAry[i])
            return pSalData->mhStockPenAry[i];
    }

    return nullptr;
}

HPEN WinSalGraphicsImpl::MakePen(Color nColor)
{
    COLORREF nPenColor = RGB(nColor.GetRed(),
                            nColor.GetGreen(),
                            nColor.GetBlue());

    return CreatePen(PS_SOLID, mrParent.mnPenWidth, nPenColor);
}

void WinSalGraphicsImpl::ResetPen(HPEN hNewPen)
{
    HPEN hOldPen = SelectPen(mrParent.getHDC(), hNewPen);

    if (mhPen)
    {
        if (!mbStockPen)
        {
            DeletePen(mhPen);
        }
    }
    else
    {
        mrParent.mhDefPen = hOldPen;
    }

    mhPen = hNewPen;
}

void WinSalGraphicsImpl::SetFillColor()
{
    ResetBrush(GetStockBrush(NULL_BRUSH));

    // set new data
    mbBrush     = false;
    mbStockBrush = true;
}

void WinSalGraphicsImpl::SetFillColor(Color nColor)
{
    COLORREF nBrushColor = PALETTERGB(nColor.GetRed(),
                                      nColor.GetGreen(),
                                      nColor.GetBlue());
    bool bStockBrush = false;

    HBRUSH hNewBrush = SearchStockBrush(nBrushColor);
    if (hNewBrush)
        bStockBrush = true;
    else
        hNewBrush = MakeBrush(nColor);

    ResetBrush(hNewBrush);

    // set new data
    mnBrushColor = nBrushColor;
    maFillColor = nColor;
    mbBrush     = true;
    mbStockBrush = bStockBrush;
}

HBRUSH WinSalGraphicsImpl::SearchStockBrush(COLORREF nBrushColor)
{
    // Only screen, because printer has problems, when we use stock objects.
    if (!mrParent.isPrinter())
    {
        const SalData* pSalData = GetSalData();

        for (sal_uInt16 i = 0; i < pSalData->mnStockBrushCount; i++)
        {
            if (nBrushColor == pSalData->maStockBrushColorAry[i])
                return pSalData->mhStockBrushAry[i];
        }
    }

    return nullptr;
}

HBRUSH WinSalGraphicsImpl::MakeBrush(Color nColor)
{
    const BYTE        nRed        = nColor.GetRed();
    const BYTE        nGreen      = nColor.GetGreen();
    const BYTE        nBlue       = nColor.GetBlue();
    const COLORREF    nBrushColor = RGB(nRed, nGreen, nBlue);

    return CreateSolidBrush(nBrushColor);
}

void WinSalGraphicsImpl::ResetBrush(HBRUSH hNewBrush)
{
    HBRUSH hOldBrush = SelectBrush(mrParent.getHDC(), hNewBrush);

    if (mhBrush)
    {
        if (!mbStockBrush)
        {
            DeleteBrush(mhBrush);
        }
    }
    else
    {
        mrParent.mhDefBrush = hOldBrush;
    }

    mhBrush = hNewBrush;
}

void WinSalGraphicsImpl::SetXORMode( bool bSet, bool )
{
    mbXORMode = bSet;
    ::SetROP2( mrParent.getHDC(), bSet ? R2_XORPEN : R2_COPYPEN );
}

void WinSalGraphicsImpl::SetROPLineColor( SalROPColor nROPColor )
{
    SetLineColor( ImplGetROPColor( nROPColor ) );
}

void WinSalGraphicsImpl::SetROPFillColor( SalROPColor nROPColor )
{
    SetFillColor( ImplGetROPColor( nROPColor ) );
}

void WinSalGraphicsImpl::DrawPixelImpl( tools::Long nX, tools::Long nY, COLORREF crColor )
{
    const HDC hDC = mrParent.getHDC();

    if (!mbXORMode)
    {
        SetPixel(hDC, static_cast<int>(nX), static_cast<int>(nY), crColor);
        return;
    }

    ScopedSelectedHBRUSH hBrush(hDC, CreateSolidBrush(crColor));
    PatBlt(hDC, static_cast<int>(nX), static_cast<int>(nY), int(1), int(1), PATINVERT);
}

void WinSalGraphicsImpl::drawPixel( tools::Long nX, tools::Long nY )
{
    DrawPixelImpl( nX, nY, mnPenColor );
}

void WinSalGraphicsImpl::drawPixel( tools::Long nX, tools::Long nY, Color nColor )
{
    COLORREF nCol = RGB( nColor.GetRed(),
                        nColor.GetGreen(),
                        nColor.GetBlue() );

    DrawPixelImpl( nX, nY, nCol );
}

void WinSalGraphicsImpl::drawLine( tools::Long nX1, tools::Long nY1, tools::Long nX2, tools::Long nY2 )
{
    MoveToEx( mrParent.getHDC(), static_cast<int>(nX1), static_cast<int>(nY1), nullptr );

    LineTo( mrParent.getHDC(), static_cast<int>(nX2), static_cast<int>(nY2) );

    // LineTo doesn't draw the last pixel
    if ( !mrParent.isPrinter() )
        DrawPixelImpl( nX2, nY2, mnPenColor );
}

void WinSalGraphicsImpl::drawRect( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight )
{
    if ( !mbPen )
    {
        if ( !mrParent.isPrinter() )
        {
            PatBlt( mrParent.getHDC(), static_cast<int>(nX), static_cast<int>(nY), static_cast<int>(nWidth), static_cast<int>(nHeight),
                    mbXORMode ? PATINVERT : PATCOPY );
        }
        else
        {
            RECT aWinRect;
            aWinRect.left   = nX;
            aWinRect.top    = nY;
            aWinRect.right  = nX+nWidth;
            aWinRect.bottom = nY+nHeight;
            ::FillRect( mrParent.getHDC(), &aWinRect, mhBrush );
        }
    }
    else
        Rectangle( mrParent.getHDC(), static_cast<int>(nX), static_cast<int>(nY), static_cast<int>(nX+nWidth), static_cast<int>(nY+nHeight) );
}

void WinSalGraphicsImpl::drawPolyLine( sal_uInt32 nPoints, const Point* pPtAry )
{
    std::unique_ptr<POINT[]> pWinPtAry(new POINT[nPoints]);
    for (sal_uInt32 i=0; i<nPoints; ++i)
        pWinPtAry[i] = POINT { static_cast<LONG>(pPtAry[i].getX()), static_cast<LONG>(pPtAry[i].getY()) };

    Polyline( mrParent.getHDC(), pWinPtAry.get(), static_cast<int>(nPoints) );

    // Polyline seems to uses LineTo, which doesn't paint the last pixel (see 87eb8f8ee)
    if ( !mrParent.isPrinter() )
        DrawPixelImpl( pWinPtAry[nPoints-1].x, pWinPtAry[nPoints-1].y, mnPenColor );
}

void WinSalGraphicsImpl::drawPolygon( sal_uInt32 nPoints, const Point* pPtAry )
{
    std::unique_ptr<POINT[]> pWinPtAry(new POINT[nPoints]);
    for (sal_uInt32 i=0; i<nPoints; ++i)
        pWinPtAry[i] = POINT { static_cast<LONG>(pPtAry[i].getX()), static_cast<LONG>(pPtAry[i].getY()) };

    Polygon( mrParent.getHDC(), pWinPtAry.get(), static_cast<int>(nPoints) );
}

void WinSalGraphicsImpl::drawPolyPolygon( sal_uInt32 nPoly, const sal_uInt32* pPoints,
                                   const Point** pPtAry )
{
    UINT    aWinPointAry[SAL_POLYPOLYCOUNT_STACKBUF];
    UINT*   pWinPointAry;
    UINT    nPolyPolyPoints = 0;
    UINT    nPoints;
    UINT    i;

    if ( nPoly <= SAL_POLYPOLYCOUNT_STACKBUF )
        pWinPointAry = aWinPointAry;
    else
        pWinPointAry = new UINT[nPoly];

    for ( i = 0; i < static_cast<UINT>(nPoly); i++ )
    {
        nPoints = static_cast<UINT>(pPoints[i])+1;
        pWinPointAry[i] = nPoints;
        nPolyPolyPoints += nPoints;
    }

    POINT  aWinPointAryAry[SAL_POLYPOLYPOINTS_STACKBUF];
    POINT* pWinPointAryAry;
    if ( nPolyPolyPoints <= SAL_POLYPOLYPOINTS_STACKBUF )
        pWinPointAryAry = aWinPointAryAry;
    else
        pWinPointAryAry = new POINT[nPolyPolyPoints];
    UINT            n = 0;
    for ( i = 0; i < static_cast<UINT>(nPoly); i++ )
    {
        nPoints = pWinPointAry[i];
        const Point* pPolyAry = pPtAry[i];
        for (sal_uInt32 j=0; j<nPoints-1; ++j)
            pWinPointAryAry[n+j] = POINT { static_cast<LONG>(pPolyAry[j].getX()), static_cast<LONG>(pPolyAry[j].getY()) };
        pWinPointAryAry[n+nPoints-1] = pWinPointAryAry[n];
        n += nPoints;
    }

    PolyPolygon( mrParent.getHDC(), pWinPointAryAry, reinterpret_cast<int*>(pWinPointAry)static_cast<UINT>(nPoly) );

    if ( pWinPointAry != aWinPointAry )
        delete [] pWinPointAry;
    if ( pWinPointAryAry != aWinPointAryAry )
        delete [] pWinPointAryAry;
}

bool WinSalGraphicsImpl::drawPolyLineBezier( sal_uInt32 nPoints, const Point* pPtAry, const PolyFlags* pFlgAry )
{
    // #100127# draw an array of points which might also contain bezier control points
    if (!nPoints)
        return true;

    const HDC hdc = mrParent.getHDC();

    // TODO: profile whether the following options are faster:
    // a) look ahead and draw consecutive bezier or line segments by PolyBezierTo/PolyLineTo resp.
    // b) convert our flag array to window's and use PolyDraw
    MoveToEx(hdc, static_cast<LONG>(pPtAry->getX()), static_cast<LONG>(pPtAry->getY()), nullptr);
    ++pPtAry;
    ++pFlgAry;

    for(sal_uInt32 i = 1; i < nPoints; ++i)
    {
        if(*pFlgAry != PolyFlags::Control)
        {
            LineTo(hdc, pPtAry->getX(), pPtAry->getY());
        }
        else if(nPoints - i > 2)
        {
            POINT bezierPoints[] = {
                POINT { static_cast<LONG>(pPtAry[0].getX()), static_cast<LONG>(pPtAry[0].getY()) },
                POINT { static_cast<LONG>(pPtAry[1].getX()), static_cast<LONG>(pPtAry[1].getY()) },
                POINT { static_cast<LONG>(pPtAry[2].getX()), static_cast<LONG>(pPtAry[2].getY()) },
            };
            PolyBezierTo(hdc, bezierPoints, 3);
            i += 2;
            pPtAry += 2;
            pFlgAry += 2;
        }

        ++pPtAry;
        ++pFlgAry;
    }

    return true;
}

bool WinSalGraphicsImpl::drawPolygonBezier( sal_uInt32 nPoints, const Point* pPtAry, const PolyFlags* pFlgAry )
{
    POINT   aStackAry1[SAL_POLY_STACKBUF];
    BYTE    aStackAry2[SAL_POLY_STACKBUF];
    POINT*  pWinPointAry;
    BYTE*   pWinFlagAry;
    if( nPoints > SAL_POLY_STACKBUF )
    {
        pWinPointAry = new POINT[ nPoints ];
        pWinFlagAry = new BYTE[ nPoints ];
    }
    else
    {
        pWinPointAry = aStackAry1;
        pWinFlagAry = aStackAry2;
    }

    sal_uInt32 nPoints_i32(nPoints);
    ImplPreparePolyDraw(1, &nPoints_i32, &pPtAry, &pFlgAry, pWinPointAry, pWinFlagAry);

    bool bRet( false );

    if( BeginPath( mrParent.getHDC() ) )
    {
        PolyDraw(mrParent.getHDC(), pWinPointAry, pWinFlagAry, nPoints);

        if( EndPath( mrParent.getHDC() ) )
        {
            if( StrokeAndFillPath( mrParent.getHDC() ) )
                bRet = true;
        }
    }

    if( pWinPointAry != aStackAry1 )
    {
        delete [] pWinPointAry;
        delete [] pWinFlagAry;
    }

    return bRet;
}

bool WinSalGraphicsImpl::drawPolyPolygonBezier( sal_uInt32 nPoly, const sal_uInt32* pPoints,
                                             const Point* const* pPtAry, const PolyFlags* const* pFlgAry )
{
    sal_uLong nCurrPoly, nTotalPoints;
    const sal_uInt32* pCurrPoints = pPoints;
    for( nCurrPoly=0, nTotalPoints=0; nCurrPoly<nPoly; ++nCurrPoly )
        nTotalPoints += *pCurrPoints++;

    POINT      aStackAry1[SAL_POLY_STACKBUF];
    BYTE       aStackAry2[SAL_POLY_STACKBUF];
    POINT*     pWinPointAry;
    BYTE*      pWinFlagAry;
    if( nTotalPoints > SAL_POLY_STACKBUF )
    {
        pWinPointAry = new POINT[ nTotalPoints ];
        pWinFlagAry = new BYTE[ nTotalPoints ];
    }
    else
    {
        pWinPointAry = aStackAry1;
        pWinFlagAry = aStackAry2;
    }

    ImplPreparePolyDraw(nPoly, pPoints, pPtAry, pFlgAry, pWinPointAry, pWinFlagAry);

    bool bRet( false );

    if( BeginPath( mrParent.getHDC() ) )
    {
        PolyDraw(mrParent.getHDC(), pWinPointAry, pWinFlagAry, nTotalPoints);

        if( EndPath( mrParent.getHDC() ) )
        {
            if( StrokeAndFillPath( mrParent.getHDC() ) )
                bRet = true;
        }
    }

    if( pWinPointAry != aStackAry1 )
    {
        delete [] pWinPointAry;
        delete [] pWinFlagAry;
    }

    return bRet;
}

static basegfx::B2DPoint impPixelSnap(
    const basegfx::B2DPolygon& rPolygon,
    const basegfx::B2DHomMatrix& rObjectToDevice,
    basegfx::B2DHomMatrix& rObjectToDeviceInv,
    sal_uInt32 nIndex)
{
    const sal_uInt32 nCount(rPolygon.count());

    // get the data
    const basegfx::B2ITuple aPrevTuple(basegfx::fround(rObjectToDevice * rPolygon.getB2DPoint((nIndex + nCount - 1) % nCount)));
    const basegfx::B2DPoint aCurrPoint(rObjectToDevice * rPolygon.getB2DPoint(nIndex));
    const basegfx::B2ITuple aCurrTuple(basegfx::fround(aCurrPoint));
    const basegfx::B2ITuple aNextTuple(basegfx::fround(rObjectToDevice * rPolygon.getB2DPoint((nIndex + 1) % nCount)));

    // get the states
    const bool bPrevVertical(aPrevTuple.getX() == aCurrTuple.getX());
    const bool bNextVertical(aNextTuple.getX() == aCurrTuple.getX());
    const bool bPrevHorizontal(aPrevTuple.getY() == aCurrTuple.getY());
    const bool bNextHorizontal(aNextTuple.getY() == aCurrTuple.getY());
    const bool bSnapX(bPrevVertical || bNextVertical);
    const bool bSnapY(bPrevHorizontal || bNextHorizontal);

    if(bSnapX || bSnapY)
    {
        basegfx::B2DPoint aSnappedPoint(
            bSnapX ? aCurrTuple.getX() : aCurrPoint.getX(),
            bSnapY ? aCurrTuple.getY() : aCurrPoint.getY());

        if(rObjectToDeviceInv.isIdentity())
        {
            rObjectToDeviceInv = rObjectToDevice;
            rObjectToDeviceInv.invert();
        }

        aSnappedPoint *= rObjectToDeviceInv;

        return aSnappedPoint;
    }

    return rPolygon.getB2DPoint(nIndex);
}

static void impAddB2DPolygonToGDIPlusGraphicsPathReal(
    Gdiplus::GraphicsPath& rGraphicsPath,
    const basegfx::B2DPolygon& rPolygon,
    const basegfx::B2DHomMatrix& rObjectToDevice,
    bool bNoLineJoin,
    bool bPixelSnapHairline)
{
    sal_uInt32 nCount(rPolygon.count());
    if(nCount == 0)
        return;

    const sal_uInt32 nEdgeCount(rPolygon.isClosed() ? nCount : nCount - 1);
    if(nEdgeCount == 0)
        return;

    const bool bControls(rPolygon.areControlPointsUsed());
    basegfx::B2DPoint aCurr(rPolygon.getB2DPoint(0));
    basegfx::B2DHomMatrix aObjectToDeviceInv;

    if(bPixelSnapHairline)
    {
        aCurr = impPixelSnap(rPolygon, rObjectToDevice, aObjectToDeviceInv, 0);
    }

    for(sal_uInt32 a(0); a < nEdgeCount; a++)
    {
        const sal_uInt32 nNextIndex((a + 1) % nCount);
        basegfx::B2DPoint aNext(rPolygon.getB2DPoint(nNextIndex));
        const bool b1stControlPointUsed(bControls && rPolygon.isNextControlPointUsed(a));
        const bool b2ndControlPointUsed(bControls && rPolygon.isPrevControlPointUsed(nNextIndex));

        if(bPixelSnapHairline)
        {
            aNext = impPixelSnap(rPolygon, rObjectToDevice, aObjectToDeviceInv, nNextIndex);
        }

        if(b1stControlPointUsed || b2ndControlPointUsed)
        {
            basegfx::B2DPoint aCa(rPolygon.getNextControlPoint(a));
            basegfx::B2DPoint aCb(rPolygon.getPrevControlPoint(nNextIndex));

            // tdf#99165 MS Gdiplus cannot handle creating correct extra geometry for fat lines
            // with LineCap or LineJoin when a bezier segment starts or ends trivial, e.g. has
            // no 1st or 2nd control point, despite that these are mathematically correct definitions
            // (basegfx can handle that).
            // Caution: This error (and it's correction) might be necessary for other graphical
            // sub-systems in a similar way.
            // tdf#101026 The 1st attempt to create a mathematically correct replacement control
            // vector was wrong. Best alternative is one as close as possible which means short.
            if(!b1stControlPointUsed)
            {
                aCa = aCurr + ((aCb - aCurr) * 0.0005);
            }
            else if(!b2ndControlPointUsed)
            {
                aCb = aNext + ((aCa - aNext) * 0.0005);
            }

            rGraphicsPath.AddBezier(
                static_cast< Gdiplus::REAL >(aCurr.getX()), static_cast< Gdiplus::REAL >(aCurr.getY()),
                static_cast< Gdiplus::REAL >(aCa.getX()), static_cast< Gdiplus::REAL >(aCa.getY()),
                static_cast< Gdiplus::REAL >(aCb.getX()), static_cast< Gdiplus::REAL >(aCb.getY()),
                static_cast< Gdiplus::REAL >(aNext.getX()), static_cast< Gdiplus::REAL >(aNext.getY()));
        }
        else
        {
            rGraphicsPath.AddLine(
                static_cast< Gdiplus::REAL >(aCurr.getX()), static_cast< Gdiplus::REAL >(aCurr.getY()),
                static_cast< Gdiplus::REAL >(aNext.getX()), static_cast< Gdiplus::REAL >(aNext.getY()));
        }

        if(a + 1 < nEdgeCount)
        {
            aCurr = aNext;

            if(bNoLineJoin)
            {
                rGraphicsPath.StartFigure();
            }
        }
    }
}

namespace {

class SystemDependentData_GraphicsPath : public basegfx::SystemDependentData
{
private:
    // the path data itself
    std::shared_ptr<Gdiplus::GraphicsPath>  mpGraphicsPath;

    // all other values the triangulation is based on and
    // need to be compared with to check for data validity
    bool                                    mbNoLineJoin;
    std::vector< double >                       maStroke;

public:
    SystemDependentData_GraphicsPath(
        std::shared_ptr<Gdiplus::GraphicsPath>& rpGraphicsPath,
        bool bNoLineJoin,
        const std::vector< double >* pStroke); // MM01

    // read access
    std::shared_ptr<Gdiplus::GraphicsPath>& getGraphicsPath() { return mpGraphicsPath; }
    bool getNoLineJoin() const { return mbNoLineJoin; }
    const std::vector< double >& getStroke() const { return maStroke; }

    virtual sal_Int64 estimateUsageInBytes() const override;
};

}

SystemDependentData_GraphicsPath::SystemDependentData_GraphicsPath(
    std::shared_ptr<Gdiplus::GraphicsPath>& rpGraphicsPath,
    bool bNoLineJoin,
    const std::vector< double >* pStroke)
:   basegfx::SystemDependentData(
        Application::GetSystemDependentDataManager(),
        basegfx::SDD_Type::SDDType_GraphicsPath),
    mpGraphicsPath(rpGraphicsPath),
    mbNoLineJoin(bNoLineJoin),
    maStroke()
{
    if(nullptr != pStroke)
    {
        maStroke = *pStroke;
    }
}

sal_Int64 SystemDependentData_GraphicsPath::estimateUsageInBytes() const
{
    sal_Int64 nRetval(0);

    if(!mpGraphicsPath)
        return 0;

    const INT nPointCount(mpGraphicsPath->GetPointCount());

    if(0 != nPointCount)
    {
        // Each point has
        // - 2 x sizeof(Gdiplus::REAL)
        // - 1 byte (see GetPathTypes in docu)
        nRetval = nPointCount * ((2 * sizeof(Gdiplus::REAL)) + 1);
    }

    return nRetval;
}

void WinSalGraphicsImpl::drawPolyPolygon(
    const basegfx::B2DHomMatrix& rObjectToDevice,
    const basegfx::B2DPolyPolygon& rPolyPolygon,
    double fTransparency)
{
    const sal_uInt32 nCount(rPolyPolygon.count());

    if(!mbBrush || 0 == nCount || fTransparency < 0.0 || fTransparency > 1.0)
    {
        return;
    }

    Gdiplus::Graphics aGraphics(mrParent.getHDC());
    const sal_uInt8 nAlpha(sal_uInt8(255) - static_cast<sal_uInt8>(basegfx::fround(fTransparency * 255.0)));
    const Gdiplus::Color aTestColor(nAlpha, maFillColor.GetRed(), maFillColor.GetGreen(), maFillColor.GetBlue());
    const Gdiplus::SolidBrush aSolidBrush(aTestColor.GetValue());

    // Set full (Object-to-Device) transformation - if used
    if(rObjectToDevice.isIdentity())
    {
        aGraphics.ResetTransform();
    }
    else
    {
        Gdiplus::Matrix aMatrix;

        aMatrix.SetElements(
            rObjectToDevice.get(0, 0),
            rObjectToDevice.get(1, 0),
            rObjectToDevice.get(0, 1),
            rObjectToDevice.get(1, 1),
            rObjectToDevice.get(0, 2),
            rObjectToDevice.get(1, 2));
        aGraphics.SetTransform(&aMatrix);
    }

    // prepare local instance of Gdiplus::GraphicsPath
    std::shared_ptr<Gdiplus::GraphicsPath> pGraphicsPath;

    // try to access buffered data
    std::shared_ptr<SystemDependentData_GraphicsPath> pSystemDependentData_GraphicsPath(
        rPolyPolygon.getSystemDependentData<SystemDependentData_GraphicsPath>(basegfx::SDD_Type::SDDType_GraphicsPath));

    if(pSystemDependentData_GraphicsPath)
    {
        // copy buffered data
        pGraphicsPath = pSystemDependentData_GraphicsPath->getGraphicsPath();
    }
--> --------------------

--> maximum size reached

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

Messung V0.5
C=95 H=96 G=95

¤ Dauer der Verarbeitung: 0.12 Sekunden  (vorverarbeitet)  ¤

*© 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.