    /* -*- 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/types.h>
    #include <sal/log.hxx>

    #include <tools/helpers.hxx>

    #include <vcl/virdev.hxx>
    #include <vcl/print.hxx>

    #include <comphelper/processfactory.hxx>

    #include <salinst.hxx>
    #include <salvd.hxx>
    #include <salgdi.hxx>
    #include <salptype.hxx>
    #include <salprn.hxx>
    #include <svdata.hxx>
    #include <print.hrc>
    #include <strings.hrc>
    #include <jobset.h>
    #include <outdev.h>
    #include <PhysicalFontCollection.hxx>
    #include <print.h>

    #include <com/sun/star/beans/XPropertySet.hpp>
    #include <com/sun/star/configuration/theDefaultProvider.hpp>
    #include <com/sun/star/container/XNameAccess.hpp>
    #include <com/sun/star/lang/XMultiServiceFactory.hpp>
    #include <com/sun/star/uno/Sequence.h>

    int nImplSysDialog = 0;

    namespace
    {
        Paper ImplGetPaperFormat( long nWidth100thMM, long nHeight100thMM )
        {
            PaperInfo aInfo(nWidth100thMM, nHeight100thMM);
            aInfo.doSloppyFit();
            return aInfo.getPaper();
        }

        const PaperInfo& ImplGetEmptyPaper()
        {
            static PaperInfo aInfo(PAPER_USER);
            return aInfo;
        }
    }

    void ImplUpdateJobSetupPaper( JobSetup& rJobSetup )
    {
        const ImplJobSetup& rConstData = rJobSetup.ImplGetConstData();

        if ( !rConstData.GetPaperWidth() || !rConstData.GetPaperHeight() )
        {
            if ( rConstData.GetPaperFormat() != PAPER_USER )
            {
                PaperInfo aInfo(rConstData.GetPaperFormat());

                ImplJobSetup& rData = rJobSetup.ImplGetData();
                rData.SetPaperWidth( aInfo.getWidth() );
                rData.SetPaperHeight( aInfo.getHeight() );
            }
        }
        else if ( rConstData.GetPaperFormat() == PAPER_USER )
        {
            Paper ePaper = ImplGetPaperFormat( rConstData.GetPaperWidth(), rConstData.GetPaperHeight() );
            if ( ePaper != PAPER_USER )
                rJobSetup.ImplGetData().SetPaperFormat(ePaper);
        }
    }

    // PrinterOptions
    PrinterOptions::PrinterOptions() :
        mbReduceTransparency( false ),
        meReducedTransparencyMode( PrinterTransparencyMode::Auto ),
        mbReduceGradients( false ),
        meReducedGradientsMode( PrinterGradientMode::Stripes ),
        mnReducedGradientStepCount( 64 ),
        mbReduceBitmaps( false ),
        meReducedBitmapMode( PrinterBitmapMode::Normal ),
        mnReducedBitmapResolution( 200 ),
        mbReducedBitmapsIncludeTransparency( true ),
        mbConvertToGreyscales( false ),
        mbPDFAsStandardPrintJobFormat( false )
    {
    }

    void PrinterOptions::ReadFromConfig( bool i_bFile )
    {
        bool bSuccess = false;
        // save old state in case something goes wrong
        PrinterOptions aOldValues( *this );

        // get the configuration service
        css::uno::Reference< css::lang::XMultiServiceFactory > xConfigProvider;
        css::uno::Reference< css::container::XNameAccess > xConfigAccess;
        try
        {
            // get service provider
            css::uno::Reference< css::uno::XComponentContext > xContext( comphelper::getProcessComponentContext() );
            // create configuration hierarchical access name
            try
            {
                xConfigProvider = css::configuration::theDefaultProvider::get( xContext );

                css::uno::Sequence< css::uno::Any > aArgs(1);
                css::beans::PropertyValue aVal;
                aVal.Name = "nodepath";
                if( i_bFile )
                    aVal.Value <<= OUString( "/org.openoffice.Office.Common/Print/Option/File" );
                else
                    aVal.Value <<= OUString( "/org.openoffice.Office.Common/Print/Option/Printer" );
                aArgs.getArray()[0] <<= aVal;
                xConfigAccess.set(
                        xConfigProvider->createInstanceWithArguments(
                            "com.sun.star.configuration.ConfigurationAccess", aArgs ),
                            css::uno::UNO_QUERY );
                if( xConfigAccess.is() )
                {
                    css::uno::Reference< css::beans::XPropertySet > xSet( xConfigAccess, css::uno::UNO_QUERY );
                    if( xSet.is() )
                    {
                        sal_Int32 nValue = 0;
                        bool  bValue = false;
                        if( xSet->getPropertyValue("ReduceTransparency") >>= bValue )
                            SetReduceTransparency( bValue );
                        if( xSet->getPropertyValue("ReducedTransparencyMode") >>= nValue )
                            SetReducedTransparencyMode( static_cast<PrinterTransparencyMode>(nValue) );
                        if( xSet->getPropertyValue("ReduceGradients") >>= bValue )
                            SetReduceGradients( bValue );
                        if( xSet->getPropertyValue("ReducedGradientMode") >>= nValue )
                            SetReducedGradientMode( static_cast<PrinterGradientMode>(nValue) );
                        if( xSet->getPropertyValue("ReducedGradientStepCount") >>= nValue )
                            SetReducedGradientStepCount( static_cast<sal_uInt16>(nValue) );
                        if( xSet->getPropertyValue("ReduceBitmaps") >>= bValue )
                            SetReduceBitmaps( bValue );
                        if( xSet->getPropertyValue("ReducedBitmapMode") >>= nValue )
                            SetReducedBitmapMode( static_cast<PrinterBitmapMode>(nValue) );
                        if( xSet->getPropertyValue("ReducedBitmapResolution") >>= nValue )
                            SetReducedBitmapResolution( static_cast<sal_uInt16>(nValue) );
                        if( xSet->getPropertyValue("ReducedBitmapIncludesTransparency") >>= bValue )
                            SetReducedBitmapIncludesTransparency( bValue );
                        if( xSet->getPropertyValue("ConvertToGreyscales") >>= bValue )
                            SetConvertToGreyscales( bValue );
                        if( xSet->getPropertyValue("PDFAsStandardPrintJobFormat") >>= bValue )
                            SetPDFAsStandardPrintJobFormat( bValue );

                        bSuccess = true;
                    }
                }
            }
            catch( const css::uno::Exception& )
            {
            }
        }
        catch( const css::lang::WrappedTargetException& )
        {
        }

        if( ! bSuccess )
            *this = aOldValues;
    }

    bool Printer::DrawTransformBitmapExDirect(
        const basegfx::B2DHomMatrix& /*aFullTransform*/,
        const BitmapEx& /*rBitmapEx*/)
    {
        // printers can't draw bitmaps directly
        return false;
    }

    bool Printer::TransformAndReduceBitmapExToTargetRange(
        const basegfx::B2DHomMatrix& /*aFullTransform*/,
        basegfx::B2DRange& /*aVisibleRange*/,
        double& /*fMaximumArea*/)
    {
        // deliberately do nothing - you can't reduce the
        // target range for a printer at all
        return true;
    }

    void Printer::DrawDeviceBitmap( const Point& rDestPt, const Size& rDestSize,
                                    const Point& rSrcPtPixel, const Size& rSrcSizePixel,
                                    BitmapEx& rBmpEx )
    {
        if( rBmpEx.IsAlpha() )
        {
            // #107169# For true alpha bitmaps, no longer masking the
            // bitmap, but perform a full alpha blend against a white
            // background here.
            Bitmap aBmp( rBmpEx.GetBitmap() );
            aBmp.Blend( rBmpEx.GetAlpha(), COL_WHITE );
            DrawBitmap( rDestPt, rDestSize, rSrcPtPixel, rSrcSizePixel, aBmp );
        }
        else
        {
            Bitmap aBmp( rBmpEx.GetBitmap() ), aMask( rBmpEx.GetMask() );
            aBmp.Replace( aMask, COL_WHITE );
            ImplPrintTransparent( aBmp, aMask, rDestPt, rDestSize, rSrcPtPixel, rSrcSizePixel );
        }
    }

    void Printer::EmulateDrawTransparent ( const tools::PolyPolygon& rPolyPoly,
                                           sal_uInt16 nTransparencePercent )
    {
        // #110958# Disable alpha VDev, we perform the necessary
        VirtualDevice* pOldAlphaVDev = mpAlphaVDev;

        // operation explicitly further below.
        if( mpAlphaVDev )
            mpAlphaVDev = nullptr;

        GDIMetaFile* pOldMetaFile = mpMetaFile;
        mpMetaFile = nullptr;

        mpMetaFile = pOldMetaFile;

        // #110958# Restore disabled alpha VDev
        mpAlphaVDev = pOldAlphaVDev;

        tools::Rectangle       aPolyRect( LogicToPixel( rPolyPoly ).GetBoundRect() );
        const Size      aDPISize( LogicToPixel(Size(1, 1), MapMode(MapUnit::MapInch)) );
        const long      nBaseExtent = std::max( FRound( aDPISize.Width() / 300. ), 1L );
        long            nMove;
        const sal_uInt16    nTrans = ( nTransparencePercent < 13 ) ? 0 :
            ( nTransparencePercent < 38 ) ? 25 :
            ( nTransparencePercent < 63 ) ? 50 :
            ( nTransparencePercent < 88 ) ? 75 : 100;

        switch( nTrans )
        {
            case 25: nMove = nBaseExtent * 3; break;
            case 50: nMove = nBaseExtent * 4; break;
            case 75: nMove = nBaseExtent * 6; break;

                // #i112959#  very transparent (88 < nTransparencePercent <= 99)
            case 100: nMove = nBaseExtent * 8; break;

                // #i112959# not transparent (nTransparencePercent < 13)
            default:    nMove = 0; break;
        }

        Push( PushFlags::CLIPREGION | PushFlags::LINECOLOR );
        IntersectClipRegion(vcl::Region(rPolyPoly));
        SetLineColor( GetFillColor() );
        const bool bOldMap = mbMap;
        EnableMapMode( false );

        if(nMove)
        {
            tools::Rectangle aRect( aPolyRect.TopLeft(), Size( aPolyRect.GetWidth(), nBaseExtent ) );
            while( aRect.Top() <= aPolyRect.Bottom() )
            {
                DrawRect( aRect );
                aRect.Move( 0, nMove );
            }

            aRect = tools::Rectangle( aPolyRect.TopLeft(), Size( nBaseExtent, aPolyRect.GetHeight() ) );
            while( aRect.Left() <= aPolyRect.Right() )
            {
                DrawRect( aRect );
                aRect.Move( nMove, 0 );
            }
        }
        else
        {
            // #i112959# if not transparent, draw full rectangle in clip region
            DrawRect( aPolyRect );
        }

        EnableMapMode( bOldMap );
        Pop();

        mpMetaFile = pOldMetaFile;

        // #110958# Restore disabled alpha VDev
        mpAlphaVDev = pOldAlphaVDev;
    }

    void Printer::DrawOutDev( const Point& /*rDestPt*/, const Size& /*rDestSize*/,
                                   const Point& /*rSrcPt*/,  const Size& /*rSrcSize*/ )
    {
        SAL_WARN( "vcl.gdi", "Don't use OutputDevice::DrawOutDev(...) with printer devices!" );
    }

    void Printer::DrawOutDev( const Point& /*rDestPt*/, const Size& /*rDestSize*/,
                                   const Point& /*rSrcPt*/,  const Size& /*rSrcSize*/,
                                   const OutputDevice& /*rOutDev*/ )
    {
        SAL_WARN( "vcl.gdi", "Don't use OutputDevice::DrawOutDev(...) with printer devices!" );
    }

    void Printer::CopyArea( const Point& /*rDestPt*/,
                            const Point& /*rSrcPt*/,  const Size& /*rSrcSize*/,
                            bool /*bWindowInvalidate*/ )
    {
        SAL_WARN( "vcl.gdi", "Don't use OutputDevice::CopyArea(...) with printer devices!" );
    }

    void Printer::SetPrinterOptions( const PrinterOptions& i_rOptions )
    {
        *mpPrinterOptions = i_rOptions;
    }

    bool Printer::HasMirroredGraphics() const
    {
        // due to a "hotfix" for AOO bug i55719, this needs to return false
        return false;
    }

    // QueueInfo
    QueueInfo::QueueInfo()
    {
        mnStatus    = PrintQueueFlags::NONE;
        mnJobs      = 0;
    }

    SalPrinterQueueInfo::SalPrinterQueueInfo()
    {
        mnStatus    = PrintQueueFlags::NONE;
        mnJobs      = QUEUE_JOBS_DONTKNOW;
    }

    SalPrinterQueueInfo::~SalPrinterQueueInfo()
    {
    }

    ImplPrnQueueList::~ImplPrnQueueList()
    {
        ImplSVData*         pSVData = ImplGetSVData();
        for(ImplPrnQueueData & rQueueInfo : m_aQueueInfos)
        {
            delete rQueueInfo.mpQueueInfo;
            pSVData->mpDefInst->DeletePrinterQueueInfo( rQueueInfo.mpSalQueueInfo );
        }
    }

    void ImplPrnQueueList::Add( SalPrinterQueueInfo* pData )
    {
        std::unordered_map< OUString, sal_Int32 >::iterator it =
            m_aNameToIndex.find( pData->maPrinterName );
        if( it == m_aNameToIndex.end() )
        {
            m_aNameToIndex[ pData->maPrinterName ] = m_aQueueInfos.size();
            m_aQueueInfos.emplace_back( );
            m_aQueueInfos.back().mpQueueInfo = nullptr;
            m_aQueueInfos.back().mpSalQueueInfo = pData;
            m_aPrinterList.push_back( pData->maPrinterName );
        }
        else // this should not happen, but ...
        {
            ImplPrnQueueData& rData = m_aQueueInfos[ it->second ];
            delete rData.mpQueueInfo;
            rData.mpQueueInfo = nullptr;
            ImplGetSVData()->mpDefInst->DeletePrinterQueueInfo( rData.mpSalQueueInfo );
            rData.mpSalQueueInfo = pData;
        }
    }

    ImplPrnQueueData* ImplPrnQueueList::Get( const OUString& rPrinter )
    {
        ImplPrnQueueData* pData = nullptr;
        std::unordered_map<OUString,sal_Int32>::iterator it =
            m_aNameToIndex.find( rPrinter );
        if( it != m_aNameToIndex.end() )
            pData = &m_aQueueInfos[it->second];
        return pData;
    }

    static void ImplInitPrnQueueList()
    {
        ImplSVData* pSVData = ImplGetSVData();

        pSVData->maGDIData.mpPrinterQueueList = new ImplPrnQueueList;

        static const char* pEnv = getenv( "SAL_DISABLE_PRINTERLIST" );
        if( !pEnv || !*pEnv )
            pSVData->mpDefInst->GetPrinterQueueInfo( pSVData->maGDIData.mpPrinterQueueList );
    }

    void ImplDeletePrnQueueList()
    {
        ImplSVData*         pSVData = ImplGetSVData();
        ImplPrnQueueList*   pPrnList = pSVData->maGDIData.mpPrinterQueueList;

        if ( pPrnList )
        {
            delete pPrnList;
            pSVData->maGDIData.mpPrinterQueueList = nullptr;
        }
    }

    const std::vector<OUString>& Printer::GetPrinterQueues()
    {
        ImplSVData* pSVData = ImplGetSVData();
        if ( !pSVData->maGDIData.mpPrinterQueueList )
            ImplInitPrnQueueList();
        return pSVData->maGDIData.mpPrinterQueueList->m_aPrinterList;
    }

    const QueueInfo* Printer::GetQueueInfo( const OUString& rPrinterName, bool bStatusUpdate )
    {
        ImplSVData* pSVData = ImplGetSVData();

        if ( !pSVData->maGDIData.mpPrinterQueueList )
            ImplInitPrnQueueList();

        if ( !pSVData->maGDIData.mpPrinterQueueList )
            return nullptr;

        ImplPrnQueueData* pInfo = pSVData->maGDIData.mpPrinterQueueList->Get( rPrinterName );
        if( pInfo )
        {
            if( !pInfo->mpQueueInfo || bStatusUpdate )
                pSVData->mpDefInst->GetPrinterQueueState( pInfo->mpSalQueueInfo );

            if ( !pInfo->mpQueueInfo )
                pInfo->mpQueueInfo = new QueueInfo;

            pInfo->mpQueueInfo->maPrinterName   = pInfo->mpSalQueueInfo->maPrinterName;
            pInfo->mpQueueInfo->maDriver        = pInfo->mpSalQueueInfo->maDriver;
            pInfo->mpQueueInfo->maLocation      = pInfo->mpSalQueueInfo->maLocation;
            pInfo->mpQueueInfo->maComment       = pInfo->mpSalQueueInfo->maComment;
            pInfo->mpQueueInfo->mnStatus        = pInfo->mpSalQueueInfo->mnStatus;
            pInfo->mpQueueInfo->mnJobs          = pInfo->mpSalQueueInfo->mnJobs;
            return pInfo->mpQueueInfo;
        }
        return nullptr;
    }

    OUString Printer::GetDefaultPrinterName()
    {
        static const char* pEnv = getenv( "SAL_DISABLE_DEFAULTPRINTER" );
        if( !pEnv || !*pEnv )
        {
            ImplSVData* pSVData = ImplGetSVData();

            return pSVData->mpDefInst->GetDefaultPrinter();
        }
        return OUString();
    }

    void Printer::ImplInitData()
    {
        mbDevOutput         = false;
        meOutDevType        = OUTDEV_PRINTER;
        mbDefPrinter        = false;
        mnError             = ERRCODE_NONE;
        mnPageQueueSize     = 0;
        mnCopyCount         = 1;
        mbCollateCopy       = false;
        mbPrinting          = false;
        mbJobActive         = false;
        mbPrintFile         = false;
        mbInPrintPage       = false;
        mbNewJobSetup       = false;
        mpInfoPrinter       = nullptr;
        mpPrinter           = nullptr;
        mpDisplayDev        = nullptr;
        mpPrinterOptions.reset(new PrinterOptions);

        // Add printer to the list
        ImplSVData* pSVData = ImplGetSVData();
        mpNext = pSVData->maGDIData.mpFirstPrinter;
        mpPrev = nullptr;
        if ( mpNext )
            mpNext->mpPrev = this;
        else
            pSVData->maGDIData.mpLastPrinter = this;
        pSVData->maGDIData.mpFirstPrinter = this;
    }

    bool Printer::AcquireGraphics() const
    {
        DBG_TESTSOLARMUTEX();

        if ( mpGraphics )
            return true;

        mbInitLineColor     = true;
        mbInitFillColor     = true;
        mbInitFont          = true;
        mbInitTextColor     = true;
        mbInitClipRegion    = true;

        ImplSVData* pSVData = ImplGetSVData();

        if ( mpJobGraphics )
            mpGraphics = mpJobGraphics;
        else if ( mpDisplayDev )
        {
            const VirtualDevice* pVirDev = mpDisplayDev;
            mpGraphics = pVirDev->mpVirDev->AcquireGraphics();
            // if needed retry after releasing least recently used virtual device graphics
            while ( !mpGraphics )
            {
                if ( !pSVData->maGDIData.mpLastVirGraphics )
                    break;
                pSVData->maGDIData.mpLastVirGraphics->ReleaseGraphics();
                mpGraphics = pVirDev->mpVirDev->AcquireGraphics();
            }
            // update global LRU list of virtual device graphics
            if ( mpGraphics )
            {
                mpNextGraphics = pSVData->maGDIData.mpFirstVirGraphics;
                pSVData->maGDIData.mpFirstVirGraphics = const_cast<Printer*>(this);
                if ( mpNextGraphics )
                    mpNextGraphics->mpPrevGraphics = const_cast<Printer*>(this);
                if ( !pSVData->maGDIData.mpLastVirGraphics )
                    pSVData->maGDIData.mpLastVirGraphics = const_cast<Printer*>(this);
            }
        }
        else
        {
            mpGraphics = mpInfoPrinter->AcquireGraphics();
            // if needed retry after releasing least recently used printer graphics
            while ( !mpGraphics )
            {
                if ( !pSVData->maGDIData.mpLastPrnGraphics )
                    break;
                pSVData->maGDIData.mpLastPrnGraphics->ReleaseGraphics();
                mpGraphics = mpInfoPrinter->AcquireGraphics();
            }
            // update global LRU list of printer graphics
            if ( mpGraphics )
            {
                mpNextGraphics = pSVData->maGDIData.mpFirstPrnGraphics;
                pSVData->maGDIData.mpFirstPrnGraphics = const_cast<Printer*>(this);
                if ( mpNextGraphics )
                    mpNextGraphics->mpPrevGraphics = const_cast<Printer*>(this);
                if ( !pSVData->maGDIData.mpLastPrnGraphics )
                    pSVData->maGDIData.mpLastPrnGraphics = const_cast<Printer*>(this);
            }
        }

        if ( mpGraphics )
        {
            mpGraphics->SetXORMode( (RasterOp::Invert == meRasterOp) || (RasterOp::Xor == meRasterOp) );
            mpGraphics->setAntiAliasB2DDraw(bool(mnAntialiasing & AntialiasingFlags::EnableB2dDraw));
        }

        return mpGraphics != nullptr;
    }

    void Printer::ImplReleaseFonts()
    {
    #ifdef UNX
        // HACK to fix an urgent P1 printing issue fast
        // WinSalPrinter does not respect GetGraphics/ReleaseGraphics conventions
        // so Printer::mpGraphics often points to a dead WinSalGraphics
        // TODO: fix WinSalPrinter's GetGraphics/ReleaseGraphics handling
        mpGraphics->ReleaseFonts();
    #endif
        mbNewFont = true;
        mbInitFont = true;

        mpFontInstance.clear();
        mpDeviceFontList.reset();
        mpDeviceFontSizeList.reset();
    }

    void Printer::ReleaseGraphics( bool bRelease )
    {
        DBG_TESTSOLARMUTEX();

        if ( !mpGraphics )
            return;

        // release the fonts of the physically released graphics device
        if( bRelease )
            ImplReleaseFonts();

        ImplSVData* pSVData = ImplGetSVData();

        Printer* pPrinter = this;

        if ( !pPrinter->mpJobGraphics )
        {
            if ( pPrinter->mpDisplayDev )
            {
                VirtualDevice* pVirDev = pPrinter->mpDisplayDev;
                if ( bRelease )
                    pVirDev->mpVirDev->ReleaseGraphics( mpGraphics );
                // remove from global LRU list of virtual device graphics
                if ( mpPrevGraphics )
                    mpPrevGraphics->mpNextGraphics = mpNextGraphics;
                else
                    pSVData->maGDIData.mpFirstVirGraphics = mpNextGraphics;
                if ( mpNextGraphics )
                    mpNextGraphics->mpPrevGraphics = mpPrevGraphics;
                else
                    pSVData->maGDIData.mpLastVirGraphics = mpPrevGraphics;
            }
            else
            {
                if ( bRelease )
                    pPrinter->mpInfoPrinter->ReleaseGraphics( mpGraphics );
                // remove from global LRU list of printer graphics
                if ( mpPrevGraphics )
                    mpPrevGraphics->mpNextGraphics = mpNextGraphics;
                else
                    pSVData->maGDIData.mpFirstPrnGraphics = mpNextGraphics;
                if ( mpNextGraphics )
                    mpNextGraphics->mpPrevGraphics = mpPrevGraphics;
                else
                    pSVData->maGDIData.mpLastPrnGraphics = mpPrevGraphics;
            }
        }

        mpGraphics      = nullptr;
        mpPrevGraphics  = nullptr;
        mpNextGraphics  = nullptr;
    }

    void Printer::ImplInit( SalPrinterQueueInfo* pInfo )
    {
        ImplSVData* pSVData = ImplGetSVData();
        // #i74084# update info for this specific SalPrinterQueueInfo
        pSVData->mpDefInst->GetPrinterQueueState( pInfo );

        // Test whether the driver actually matches the JobSetup
        ImplJobSetup& rData = maJobSetup.ImplGetData();
        if ( rData.GetDriverData() )
        {
            if ( rData.GetPrinterName() != pInfo->maPrinterName ||
                 rData.GetDriver() != pInfo->maDriver )
            {
                std::free( const_cast<sal_uInt8*>(rData.GetDriverData()) );
                rData.SetDriverData(nullptr);
                rData.SetDriverDataLen(0);
            }
        }

        // Remember printer name
        maPrinterName = pInfo->maPrinterName;
        maDriver = pInfo->maDriver;

        // Add printer name to JobSetup
        rData.SetPrinterName( maPrinterName );
        rData.SetDriver( maDriver );

        mpInfoPrinter   = pSVData->mpDefInst->CreateInfoPrinter( pInfo, &rData );
        mpPrinter       = nullptr;
        mpJobGraphics   = nullptr;
        ImplUpdateJobSetupPaper( maJobSetup );

        if ( !mpInfoPrinter )
        {
            ImplInitDisplay();
            return;
        }

        // we need a graphics
        if ( !AcquireGraphics() )
        {
            ImplInitDisplay();
            return;
        }

        // Init data
        ImplUpdatePageData();
        mxFontCollection.reset(new PhysicalFontCollection);
        mxFontCache.reset(new ImplFontCache);
        mpGraphics->GetDevFontList(mxFontCollection.get());
    }

    void Printer::ImplInitDisplay()
    {
        ImplSVData* pSVData = ImplGetSVData();

        mpInfoPrinter       = nullptr;
        mpPrinter           = nullptr;
        mpJobGraphics       = nullptr;

        mpDisplayDev = VclPtr<VirtualDevice>::Create();
        mxFontCollection    = pSVData->maGDIData.mxScreenFontList;
        mxFontCache         = pSVData->maGDIData.mxScreenFontCache;
        mnDPIX              = mpDisplayDev->mnDPIX;
        mnDPIY              = mpDisplayDev->mnDPIY;
    }

    void Printer::DrawDeviceMask( const Bitmap& rMask, const Color& rMaskColor,
                             const Point& rDestPt, const Size& rDestSize,
                             const Point& rSrcPtPixel, const Size& rSrcSizePixel )
    {
        Point       aDestPt( LogicToPixel( rDestPt ) );
        Size        aDestSz( LogicToPixel( rDestSize ) );
        tools::Rectangle   aSrcRect( rSrcPtPixel, rSrcSizePixel );

        aSrcRect.Justify();

        if( !(!rMask.IsEmpty() && aSrcRect.GetWidth() && aSrcRect.GetHeight() && aDestSz.Width() && aDestSz.Height()) )
            return;

        Bitmap  aMask( rMask );
        BmpMirrorFlags nMirrFlags = BmpMirrorFlags::NONE;

        if( aMask.GetBitCount() > 1 )
            aMask.Convert( BmpConversion::N1BitThreshold );

        // mirrored horizontically
        if( aDestSz.Width() < 0 )
        {
            aDestSz.setWidth( -aDestSz.Width() );
            aDestPt.AdjustX( -( aDestSz.Width() - 1 ) );
            nMirrFlags |= BmpMirrorFlags::Horizontal;
        }

        // mirrored vertically
        if( aDestSz.Height() < 0 )
        {
            aDestSz.setHeight( -aDestSz.Height() );
            aDestPt.AdjustY( -( aDestSz.Height() - 1 ) );
            nMirrFlags |= BmpMirrorFlags::Vertical;
        }

        // source cropped?
        if( aSrcRect != tools::Rectangle( Point(), aMask.GetSizePixel() ) )
            aMask.Crop( aSrcRect );

        // destination mirrored
        if( nMirrFlags != BmpMirrorFlags::NONE)
            aMask.Mirror( nMirrFlags );

        // do painting
        const long      nSrcWidth = aSrcRect.GetWidth(), nSrcHeight = aSrcRect.GetHeight();
        long            nX, nY; //, nWorkX, nWorkY, nWorkWidth, nWorkHeight;
        std::unique_ptr<long[]> pMapX( new long[ nSrcWidth + 1 ] );
        std::unique_ptr<long[]> pMapY( new long[ nSrcHeight + 1 ] );
        GDIMetaFile*    pOldMetaFile = mpMetaFile;
        const bool      bOldMap = mbMap;

        mpMetaFile = nullptr;
        mbMap = false;
        Push( PushFlags::FILLCOLOR | PushFlags::LINECOLOR );
        SetLineColor( rMaskColor );
        SetFillColor( rMaskColor );
        InitLineColor();
        InitFillColor();

        // create forward mapping tables
        for( nX = 0; nX <= nSrcWidth; nX++ )
            pMapX[ nX ] = aDestPt.X() + FRound( static_cast<double>(aDestSz.Width()) * nX / nSrcWidth );

        for( nY = 0; nY <= nSrcHeight; nY++ )
            pMapY[ nY ] = aDestPt.Y() + FRound( static_cast<double>(aDestSz.Height()) * nY / nSrcHeight );

        // walk through all rectangles of mask
        const vcl::Region aWorkRgn(aMask.CreateRegion(COL_BLACK, tools::Rectangle(Point(), aMask.GetSizePixel())));
        RectangleVector aRectangles;
        aWorkRgn.GetRegionRectangles(aRectangles);

        for (auto const& rectangle : aRectangles)
        {
            const Point aMapPt(pMapX[rectangle.Left()], pMapY[rectangle.Top()]);
            const Size aMapSz(
                pMapX[rectangle.Right() + 1] - aMapPt.X(),      // pMapX[L + W] -> L + ((R - L) + 1) -> R + 1
                pMapY[rectangle.Bottom() + 1] - aMapPt.Y());    // same for Y

            DrawRect(tools::Rectangle(aMapPt, aMapSz));
        }

        Pop();
        mbMap = bOldMap;
        mpMetaFile = pOldMetaFile;
    }

    SalPrinterQueueInfo* Printer::ImplGetQueueInfo( const OUString& rPrinterName,
                                                    const OUString* pDriver )
    {
        ImplSVData* pSVData = ImplGetSVData();
        if ( !pSVData->maGDIData.mpPrinterQueueList )
            ImplInitPrnQueueList();

        ImplPrnQueueList* pPrnList = pSVData->maGDIData.mpPrinterQueueList;
        if ( pPrnList && pPrnList->m_aQueueInfos.size() )
        {
            // first search for the printer name directly
            ImplPrnQueueData* pInfo = pPrnList->Get( rPrinterName );
            if( pInfo )
                return pInfo->mpSalQueueInfo;

            // then search case insensitive
            for(ImplPrnQueueData & rQueueInfo : pPrnList->m_aQueueInfos)
            {
                if( rQueueInfo.mpSalQueueInfo->maPrinterName.equalsIgnoreAsciiCase( rPrinterName ) )
                    return rQueueInfo.mpSalQueueInfo;
            }

            // then search for driver name
            if ( pDriver )
            {
                for(ImplPrnQueueData & rQueueInfo : pPrnList->m_aQueueInfos)
                {
                    if( rQueueInfo.mpSalQueueInfo->maDriver == *pDriver )
                        return rQueueInfo.mpSalQueueInfo;
                }
            }

            // then the default printer
            pInfo = pPrnList->Get( GetDefaultPrinterName() );
            if( pInfo )
                return pInfo->mpSalQueueInfo;

            // last chance: the first available printer
            return pPrnList->m_aQueueInfos[0].mpSalQueueInfo;
        }

        return nullptr;
    }

    void Printer::ImplUpdatePageData()
    {
        // we need a graphics
        if ( !AcquireGraphics() )
            return;

        mpGraphics->GetResolution( mnDPIX, mnDPIY );
        mpInfoPrinter->GetPageInfo( &maJobSetup.ImplGetConstData(),
                                    mnOutWidth, mnOutHeight,
                                    maPageOffset,
                                    maPaperSize );
    }

    void Printer::ImplUpdateFontList()
    {
        ImplUpdateFontData();
    }

    long Printer::GetGradientStepCount( long nMinRect )
    {
        // use display-equivalent step size calculation
        long nInc = (nMinRect < 800) ? 10 : 20;

        return nInc;
    }

    Printer::Printer()
    {
        ImplInitData();
        SalPrinterQueueInfo* pInfo = ImplGetQueueInfo( GetDefaultPrinterName(), nullptr );
        if ( pInfo )
        {
            ImplInit( pInfo );
            if ( !IsDisplayPrinter() )
                mbDefPrinter = true;
        }
        else
            ImplInitDisplay();
    }

    Printer::Printer( const JobSetup& rJobSetup ) :
        maJobSetup( rJobSetup )
    {
        ImplInitData();
        const ImplJobSetup& rConstData = rJobSetup.ImplGetConstData();
        OUString aDriver = rConstData.GetDriver();
        SalPrinterQueueInfo* pInfo = ImplGetQueueInfo( rConstData.GetPrinterName(),
                                                       &aDriver );
        if ( pInfo )
        {
            ImplInit( pInfo );
            SetJobSetup( rJobSetup );
        }
        else
        {
            ImplInitDisplay();
            maJobSetup = JobSetup();
        }
    }

    Printer::Printer( const QueueInfo& rQueueInfo )
    {
        ImplInitData();
        SalPrinterQueueInfo* pInfo = ImplGetQueueInfo( rQueueInfo.GetPrinterName(),
                                                       &rQueueInfo.GetDriver() );
        if ( pInfo )
            ImplInit( pInfo );
        else
            ImplInitDisplay();
    }

    Printer::Printer( const OUString& rPrinterName )
    {
        ImplInitData();
        SalPrinterQueueInfo* pInfo = ImplGetQueueInfo( rPrinterName, nullptr );
        if ( pInfo )
            ImplInit( pInfo );
        else
            ImplInitDisplay();
    }

    Printer::~Printer()
    {
        disposeOnce();
    }

    void Printer::dispose()
    {
        SAL_WARN_IF( IsPrinting(), "vcl.gdi", "Printer::~Printer() - Job is printing" );
        SAL_WARN_IF( IsJobActive(), "vcl.gdi", "Printer::~Printer() - Job is active" );

        mpPrinterOptions.reset();

        ReleaseGraphics();
        if ( mpInfoPrinter )
            ImplGetSVData()->mpDefInst->DestroyInfoPrinter( mpInfoPrinter );
        if ( mpDisplayDev )
            mpDisplayDev.disposeAndClear();
        else
        {
            // OutputDevice Dtor is trying the same thing; that why we need to set
            // the FontEntry to NULL here
            // TODO: consolidate duplicate cleanup by Printer and OutputDevice
            mpFontInstance.clear();
            mpDeviceFontList.reset();
            mpDeviceFontSizeList.reset();
            mxFontCache.reset();
            // font list deleted by OutputDevice dtor
        }

        // Add printer from the list
        ImplSVData* pSVData = ImplGetSVData();
        if ( mpPrev )
            mpPrev->mpNext = mpNext;
        else
            pSVData->maGDIData.mpFirstPrinter = mpNext;
        if ( mpNext )
            mpNext->mpPrev = mpPrev;
        else
            pSVData->maGDIData.mpLastPrinter = mpPrev;

        mpPrev.clear();
        mpNext.clear();
        OutputDevice::dispose();
    }

    sal_uInt32 Printer::GetCapabilities( PrinterCapType nType ) const
    {
        if ( IsDisplayPrinter() )
            return 0;

        if( mpInfoPrinter )
            return mpInfoPrinter->GetCapabilities( &maJobSetup.ImplGetConstData(), nType );
        else
            return 0;
    }

    bool Printer::HasSupport( PrinterSupport eFeature ) const
    {
        switch ( eFeature )
        {
            case PrinterSupport::SetOrientation:
                return GetCapabilities( PrinterCapType::SetOrientation ) != 0;
            case PrinterSupport::SetPaperSize:
                return GetCapabilities( PrinterCapType::SetPaperSize ) != 0;
            case PrinterSupport::SetPaper:
                return GetCapabilities( PrinterCapType::SetPaper ) != 0;
            case PrinterSupport::CollateCopy:
                return (GetCapabilities( PrinterCapType::CollateCopies ) != 0);
            case PrinterSupport::SetupDialog:
                return GetCapabilities( PrinterCapType::SupportDialog ) != 0;
        }

        return true;
    }

    bool Printer::SetJobSetup( const JobSetup& rSetup )
    {
        if ( IsDisplayPrinter() || mbInPrintPage )
            return false;

        JobSetup aJobSetup = rSetup;

        ReleaseGraphics();
        if ( mpInfoPrinter->SetPrinterData( &aJobSetup.ImplGetData() ) )
        {
            ImplUpdateJobSetupPaper( aJobSetup );
            mbNewJobSetup = true;
            maJobSetup = aJobSetup;
            ImplUpdatePageData();
            ImplUpdateFontList();
            return true;
        }

        return false;
    }

    bool Printer::Setup(weld::Window* pWindow, PrinterSetupMode eMode)
    {
        if ( IsDisplayPrinter() )
            return false;

        if ( IsJobActive() || IsPrinting() )
            return false;

        JobSetup aJobSetup = maJobSetup;
        ImplJobSetup& rData = aJobSetup.ImplGetData();
        rData.SetPrinterSetupMode( eMode );
        // TODO: orig page size

        if (!pWindow)
        {
            vcl::Window* pDefWin = ImplGetDefaultWindow();
            pWindow = pDefWin ? pDefWin->GetFrameWeld() : nullptr;
        }
        if( !pWindow )
            return false;

        ReleaseGraphics();
        ImplSVData* pSVData = ImplGetSVData();
        pSVData->maAppData.mnModalMode++;
        nImplSysDialog++;
        bool bSetup = mpInfoPrinter->Setup(pWindow, &rData);
        pSVData->maAppData.mnModalMode--;
        nImplSysDialog--;
        if ( bSetup )
        {
            ImplUpdateJobSetupPaper( aJobSetup );
            mbNewJobSetup = true;
            maJobSetup = aJobSetup;
            ImplUpdatePageData();
            ImplUpdateFontList();
            return true;
        }
        return false;
    }

    bool Printer::SetPrinterProps( const Printer* pPrinter )
    {
        if ( IsJobActive() || IsPrinting() )
            return false;

        ImplSVData* pSVData = ImplGetSVData();

        mbDefPrinter        = pPrinter->mbDefPrinter;
        maPrintFile         = pPrinter->maPrintFile;
        mbPrintFile         = pPrinter->mbPrintFile;
        mnCopyCount         = pPrinter->mnCopyCount;
        mbCollateCopy       = pPrinter->mbCollateCopy;
        mnPageQueueSize     = pPrinter->mnPageQueueSize;
        *mpPrinterOptions   = *pPrinter->mpPrinterOptions;

        if ( pPrinter->IsDisplayPrinter() )
        {
            // Destroy old printer
            if ( !IsDisplayPrinter() )
            {
                ReleaseGraphics();
                pSVData->mpDefInst->DestroyInfoPrinter( mpInfoPrinter );
                mpFontInstance.clear();
                mpDeviceFontList.reset();
                mpDeviceFontSizeList.reset();
                // clean up font list
                mxFontCache.reset();
                mxFontCollection.reset();

                mbInitFont = true;
                mbNewFont = true;
                mpInfoPrinter = nullptr;
            }

            // Construct new printer
            ImplInitDisplay();
            return true;
        }

        // Destroy old printer?
        if ( GetName() != pPrinter->GetName() )
        {
            ReleaseGraphics();
            if ( mpDisplayDev )
            {
                mpDisplayDev.disposeAndClear();
            }
            else
            {
                pSVData->mpDefInst->DestroyInfoPrinter( mpInfoPrinter );

                mpFontInstance.clear();
                mpDeviceFontList.reset();
                mpDeviceFontSizeList.reset();
                mxFontCache.reset();
                mxFontCollection.reset();
                mbInitFont = true;
                mbNewFont = true;
                mpInfoPrinter = nullptr;
            }

            // Construct new printer
            OUString aDriver = pPrinter->GetDriverName();
            SalPrinterQueueInfo* pInfo = ImplGetQueueInfo( pPrinter->GetName(), &aDriver );
            if ( pInfo )
            {
                ImplInit( pInfo );
                SetJobSetup( pPrinter->GetJobSetup() );
            }
            else
                ImplInitDisplay();
        }
        else
            SetJobSetup( pPrinter->GetJobSetup() );

        return false;
    }

    bool Printer::SetOrientation( Orientation eOrientation )
    {
        if ( mbInPrintPage )
            return false;

        if ( maJobSetup.ImplGetConstData().GetOrientation() != eOrientation )
        {
            JobSetup      aJobSetup = maJobSetup;
            ImplJobSetup& rData = aJobSetup.ImplGetData();

            rData.SetOrientation(eOrientation);

            if ( IsDisplayPrinter() )
            {
                mbNewJobSetup = true;
                maJobSetup = aJobSetup;
                return true;
            }

            ReleaseGraphics();
            if ( mpInfoPrinter->SetData( JobSetFlags::ORIENTATION, &rData ) )
            {
                ImplUpdateJobSetupPaper( aJobSetup );
                mbNewJobSetup = true;
                maJobSetup = aJobSetup;
                ImplUpdatePageData();
                ImplUpdateFontList();
                return true;
            }
            else
                return false;
        }

        return true;
    }

    Orientation Printer::GetOrientation() const
    {
        return maJobSetup.ImplGetConstData().GetOrientation();
    }

    bool Printer::SetPaperBin( sal_uInt16 nPaperBin )
    {
        if ( mbInPrintPage )
            return false;

        if ( maJobSetup.ImplGetConstData().GetPaperBin() != nPaperBin &&
             nPaperBin < GetPaperBinCount() )
        {
            JobSetup      aJobSetup = maJobSetup;
            ImplJobSetup& rData = aJobSetup.ImplGetData();
            rData.SetPaperBin(nPaperBin);

            if ( IsDisplayPrinter() )
            {
                mbNewJobSetup = true;
                maJobSetup = aJobSetup;
                return true;
            }

            ReleaseGraphics();
            if ( mpInfoPrinter->SetData( JobSetFlags::PAPERBIN, &rData ) )
            {
                ImplUpdateJobSetupPaper( aJobSetup );
                mbNewJobSetup = true;
                maJobSetup = aJobSetup;
                ImplUpdatePageData();
                ImplUpdateFontList();
                return true;
            }
            else
                return false;
        }

        return true;
    }

    sal_uInt16 Printer::GetPaperBin() const
    {
        return maJobSetup.ImplGetConstData().GetPaperBin();
    }

    bool Printer::GetPrinterSettingsPreferred() const
    {
        return maJobSetup.ImplGetConstData().GetPapersizeFromSetup();
    }

    // dear loplugins, DO NOT REMOVE this code
    // it will be used in follow-up commits
    void Printer::SetPrinterSettingsPreferred( bool bPaperSizeFromSetup)
    {
        if ( maJobSetup.ImplGetConstData().GetPapersizeFromSetup() != bPaperSizeFromSetup )
        {
            JobSetup      aJobSetup = maJobSetup;
            ImplJobSetup& rData = aJobSetup.ImplGetData();
            rData.SetPapersizeFromSetup(bPaperSizeFromSetup);

            mbNewJobSetup = true;
            maJobSetup = aJobSetup;
        }
    }

    // Map user paper format to a available printer paper formats
    void Printer::ImplFindPaperFormatForUserSize( JobSetup& aJobSetup, bool bMatchNearest )
    {
        ImplJobSetup& rData = aJobSetup.ImplGetData();

        // The angle that a landscape page will be turned counterclockwise wrt to portrait.
        int     nLandscapeAngle = mpInfoPrinter ? mpInfoPrinter->GetLandscapeAngle( &maJobSetup.ImplGetConstData() ) : 900;

        int     nPaperCount     = GetPaperInfoCount();
        bool    bFound = false;

        PaperInfo aInfo(rData.GetPaperWidth(), rData.GetPaperHeight());

        // Compare all paper formats and get the appropriate one
        for ( int i = 0; i < nPaperCount; i++ )
        {
            const PaperInfo& rPaperInfo = GetPaperInfo( i );

            if ( aInfo.sloppyEqual(rPaperInfo) )
            {
                rData.SetPaperFormat(
                    ImplGetPaperFormat( rPaperInfo.getWidth(),
                        rPaperInfo.getHeight() ));
                rData.SetOrientation( Orientation::Portrait );
                bFound = true;
                break;
            }
        }

        // If the printer supports landscape orientation, check paper sizes again
        // with landscape orientation. This is necessary as a printer driver provides
        // all paper sizes with portrait orientation only!!
        if ( rData.GetPaperFormat() == PAPER_USER &&
             nLandscapeAngle != 0 &&
             HasSupport( PrinterSupport::SetOrientation ))
        {
            const long nRotatedWidth = rData.GetPaperHeight();
            const long nRotatedHeight = rData.GetPaperWidth();
            PaperInfo aRotatedInfo(nRotatedWidth, nRotatedHeight);

            for ( int i = 0; i < nPaperCount; i++ )
            {
                const PaperInfo& rPaperInfo = GetPaperInfo( i );

                if ( aRotatedInfo.sloppyEqual( rPaperInfo ) )
                {
                    rData.SetPaperFormat(
                        ImplGetPaperFormat( rPaperInfo.getWidth(),
                            rPaperInfo.getHeight() ));
                    rData.SetOrientation( Orientation::Landscape );
                    bFound = true;
                    break;
                }
            }
        }

        if( ! bFound && bMatchNearest )
        {
             sal_Int64 nBestMatch = SAL_MAX_INT64;
             int nBestIndex = 0;
             Orientation eBestOrientation = Orientation::Portrait;
             for( int i = 0; i < nPaperCount; i++ )
             {
                 const PaperInfo& rPaperInfo = GetPaperInfo( i );

                 // check portrait match
                 sal_Int64 nDX = rData.GetPaperWidth() - rPaperInfo.getWidth();
                 sal_Int64 nDY = rData.GetPaperHeight() - rPaperInfo.getHeight();
                 sal_Int64 nMatch = nDX*nDX + nDY*nDY;
                 if( nMatch < nBestMatch )
                 {
                     nBestMatch = nMatch;
                     nBestIndex = i;
                     eBestOrientation = Orientation::Portrait;
                 }

                 // check landscape match
                 nDX = rData.GetPaperWidth() - rPaperInfo.getHeight();
                 nDY = rData.GetPaperHeight() - rPaperInfo.getWidth();
                 nMatch = nDX*nDX + nDY*nDY;
                 if( nMatch < nBestMatch )
                 {
                     nBestMatch = nMatch;
                     nBestIndex = i;
                     eBestOrientation = Orientation::Landscape;
                 }
             }
             const PaperInfo& rBestInfo = GetPaperInfo( nBestIndex );
             rData.SetPaperFormat(
                ImplGetPaperFormat( rBestInfo.getWidth(),
                    rBestInfo.getHeight() ));
             rData.SetOrientation(eBestOrientation);
        }
    }

    void Printer::SetPaper( Paper ePaper )
    {
        if ( mbInPrintPage )
            return;

        if ( maJobSetup.ImplGetConstData().GetPaperFormat() != ePaper )
        {
            JobSetup      aJobSetup = maJobSetup;
            ImplJobSetup& rData = aJobSetup.ImplGetData();

            rData.SetPaperFormat( ePaper );
            if ( ePaper != PAPER_USER )
            {
                PaperInfo aInfo(ePaper);
                rData.SetPaperWidth( aInfo.getWidth() );
                rData.SetPaperHeight( aInfo.getHeight() );
            }

            if ( IsDisplayPrinter() )
            {
                mbNewJobSetup = true;
                maJobSetup = aJobSetup;
                return;
            }

            ReleaseGraphics();
            if ( ePaper == PAPER_USER )
                ImplFindPaperFormatForUserSize( aJobSetup, false );
            if ( mpInfoPrinter->SetData( JobSetFlags::PAPERSIZE | JobSetFlags::ORIENTATION, &rData ))
            {
                ImplUpdateJobSetupPaper( aJobSetup );
                mbNewJobSetup = true;
                maJobSetup = aJobSetup;
                ImplUpdatePageData();
                ImplUpdateFontList();
            }
        }
    }

    bool Printer::SetPaperSizeUser( const Size& rSize )
    {
        return SetPaperSizeUser( rSize, false );
    }

    bool Printer::SetPaperSizeUser( const Size& rSize, bool bMatchNearest )
    {
        if ( mbInPrintPage )
            return false;

        const Size aPixSize = LogicToPixel( rSize );
        const Size aPageSize = PixelToLogic(aPixSize, MapMode(MapUnit::Map100thMM));
        bool bNeedToChange(maJobSetup.ImplGetConstData().GetPaperWidth() != aPageSize.Width() ||
            maJobSetup.ImplGetConstData().GetPaperHeight() != aPageSize.Height());

        if(!bNeedToChange)
        {
            // #i122984# only need to change when Paper is different from PAPER_USER and
            // the mapped Paper which will created below in the call to ImplFindPaperFormatForUserSize
            // and will replace maJobSetup.ImplGetConstData()->GetPaperFormat(). This leads to
            // unnecessary JobSetups, e.g. when printing a multi-page fax, but also with
            // normal print
            const Paper aPaper = ImplGetPaperFormat(aPageSize.Width(), aPageSize.Height());

            bNeedToChange = maJobSetup.ImplGetConstData().GetPaperFormat() != PAPER_USER &&
                maJobSetup.ImplGetConstData().GetPaperFormat() != aPaper;
        }

        if(bNeedToChange)
        {
            JobSetup      aJobSetup = maJobSetup;
            ImplJobSetup& rData = aJobSetup.ImplGetData();
            rData.SetPaperFormat( PAPER_USER );
            rData.SetPaperWidth( aPageSize.Width() );
            rData.SetPaperHeight( aPageSize.Height() );

            if ( IsDisplayPrinter() )
            {
                mbNewJobSetup = true;
                maJobSetup = aJobSetup;
                return true;
            }

            ReleaseGraphics();
            ImplFindPaperFormatForUserSize( aJobSetup, bMatchNearest );

            // Changing the paper size can also change the orientation!
            if ( mpInfoPrinter->SetData( JobSetFlags::PAPERSIZE | JobSetFlags::ORIENTATION, &rData ))
            {
                ImplUpdateJobSetupPaper( aJobSetup );
                mbNewJobSetup = true;
                maJobSetup = aJobSetup;
                ImplUpdatePageData();
                ImplUpdateFontList();
                return true;
            }
            else
                return false;
        }

        return true;
    }

    int Printer::GetPaperInfoCount() const
    {
        if( ! mpInfoPrinter )
            return 0;
        if( ! mpInfoPrinter->m_bPapersInit )
            mpInfoPrinter->InitPaperFormats( &maJobSetup.ImplGetConstData() );
        return mpInfoPrinter->m_aPaperFormats.size();
    }

    OUString Printer::GetPaperName( Paper ePaper )
    {
        ImplSVData* pSVData = ImplGetSVData();
        if( pSVData->maPaperNames.empty() )
        {
            static const int PaperIndex[] =
            {
                PAPER_A0, PAPER_A1, PAPER_A2, PAPER_A3, PAPER_A4, PAPER_A5,
                PAPER_B4_ISO, PAPER_B5_ISO, PAPER_LETTER, PAPER_LEGAL, PAPER_TABLOID,
                PAPER_USER, PAPER_B6_ISO, PAPER_ENV_C4, PAPER_ENV_C5, PAPER_ENV_C6, PAPER_ENV_C65,
                PAPER_ENV_DL, PAPER_SLIDE_DIA, PAPER_C, PAPER_D, PAPER_E,
                PAPER_EXECUTIVE, PAPER_FANFOLD_LEGAL_DE, PAPER_ENV_MONARCH, PAPER_ENV_PERSONAL,
                PAPER_ENV_9, PAPER_ENV_10, PAPER_ENV_11, PAPER_ENV_12, PAPER_KAI16,
                PAPER_KAI32, PAPER_KAI32BIG, PAPER_B4_JIS, PAPER_B5_JIS, PAPER_B6_JIS,
                PAPER_POSTCARD_JP
            };
            assert(SAL_N_ELEMENTS(PaperIndex) == SAL_N_ELEMENTS(RID_STR_PAPERNAMES) && "localized paper name count wrong");
            for (size_t i = 0; i < SAL_N_ELEMENTS(PaperIndex); ++i)
                pSVData->maPaperNames[PaperIndex[i]] = VclResId(RID_STR_PAPERNAMES[i]);
        }

        std::unordered_map<int,OUString>::const_iterator it = pSVData->maPaperNames.find( static_cast<int>(ePaper) );
        return (it != pSVData->maPaperNames.end()) ? it->second : OUString();
    }

    OUString Printer::GetPaperName() const
    {
        Size  aPageSize = PixelToLogic( GetPaperSizePixel(), MapMode(MapUnit::Map100thMM));
        Paper ePaper    = ImplGetPaperFormat( aPageSize.Width(), aPageSize.Height() );
        if( ePaper == PAPER_USER )
            ePaper = ImplGetPaperFormat( aPageSize.Height(), aPageSize.Width() );
        return (ePaper != PAPER_USER) ? GetPaperName( ePaper ) : OUString();
    }

    const PaperInfo& Printer::GetPaperInfo( int nPaper ) const
    {
        if( ! mpInfoPrinter )
            return ImplGetEmptyPaper();
        if( ! mpInfoPrinter->m_bPapersInit )
            mpInfoPrinter->InitPaperFormats( &maJobSetup.ImplGetConstData() );
        if( mpInfoPrinter->m_aPaperFormats.empty() || nPaper < 0 || nPaper >= int(mpInfoPrinter->m_aPaperFormats.size()) )
            return ImplGetEmptyPaper();
        return mpInfoPrinter->m_aPaperFormats[nPaper];
    }

    void Printer::SetDuplexMode( DuplexMode eDuplex )
    {
        if ( mbInPrintPage )
            return;

        if ( maJobSetup.ImplGetConstData().GetDuplexMode() != eDuplex )
        {
            JobSetup      aJobSetup = maJobSetup;
            ImplJobSetup& rData = aJobSetup.ImplGetData();

            rData.SetDuplexMode( eDuplex );

            if ( IsDisplayPrinter() )
            {
                mbNewJobSetup = true;
                maJobSetup = aJobSetup;
                return;
            }

            ReleaseGraphics();
            if ( mpInfoPrinter->SetData( JobSetFlags::DUPLEXMODE, &rData ) )
            {
                ImplUpdateJobSetupPaper( aJobSetup );
                mbNewJobSetup = true;
                maJobSetup = aJobSetup;
                ImplUpdatePageData();
                ImplUpdateFontList();
            }
        }
    }

    Paper Printer::GetPaper() const
    {
        return maJobSetup.ImplGetConstData().GetPaperFormat();
    }

    sal_uInt16 Printer::GetPaperBinCount() const
    {
        if ( IsDisplayPrinter() )
            return 0;

        return mpInfoPrinter->GetPaperBinCount( &maJobSetup.ImplGetConstData() );
    }

    OUString Printer::GetPaperBinName( sal_uInt16 nPaperBin ) const
    {
        if ( IsDisplayPrinter() )
            return OUString();

        if ( nPaperBin < GetPaperBinCount() )
            return mpInfoPrinter->GetPaperBinName( &maJobSetup.ImplGetConstData(), nPaperBin );
        else
            return OUString();
    }

    void Printer::SetCopyCount( sal_uInt16 nCopy, bool bCollate )
    {
        mnCopyCount = nCopy;
        mbCollateCopy = bCollate;
    }

    ErrCode Printer::ImplSalPrinterErrorCodeToVCL( SalPrinterError nError )
    {
        ErrCode nVCLError;
        switch ( nError )
        {
            case SalPrinterError::NONE:
                nVCLError = ERRCODE_NONE;
                break;
            case SalPrinterError::Abort:
                nVCLError = PRINTER_ABORT;
                break;
            default:
                nVCLError = PRINTER_GENERALERROR;
                break;
        }

        return nVCLError;
    }

    void Printer::EndJob()
    {
        if ( !IsJobActive() )
            return;

        SAL_WARN_IF( mbInPrintPage, "vcl.gdi", "Printer::EndJob() - StartPage() without EndPage() called" );

        mbJobActive = false;

        if ( mpPrinter )
        {
            ReleaseGraphics();

            mbPrinting      = false;
            maJobName.clear();

            mbDevOutput = false;
            mpPrinter->EndJob();
            // FIXME: Do not destroy the printer asynchronously as Win95
            // can't handle destroying a printer object and printing
            // at the same time
            mpPrinter.reset();
        }
    }

    void Printer::ImplStartPage()
    {
        if ( !IsJobActive() )
            return;

        if ( mpPrinter )
        {
            SalGraphics* pGraphics = mpPrinter->StartPage( &maJobSetup.ImplGetData(),
                                                           mbNewJobSetup );
            if ( pGraphics )
            {
                ReleaseGraphics();
                mpJobGraphics = pGraphics;
            }
            mbDevOutput = true;

            // PrintJob not aborted ???
            if ( IsJobActive() )
                mbInPrintPage = true;
        }
    }

    void Printer::ImplEndPage()
    {
        if ( !IsJobActive() )
            return;

        mbInPrintPage = false;

        if ( mpPrinter )
        {
            mpPrinter->EndPage();
            ReleaseGraphics();
            mbDevOutput = false;

            mpJobGraphics = nullptr;
            mbNewJobSetup = false;
        }
    }

    void Printer::updatePrinters()
    {
        ImplSVData*         pSVData = ImplGetSVData();
        ImplPrnQueueList*   pPrnList = pSVData->maGDIData.mpPrinterQueueList;

        if ( pPrnList )
        {
            ImplPrnQueueList* pNewList = new ImplPrnQueueList;
            pSVData->mpDefInst->GetPrinterQueueInfo( pNewList );

            bool bChanged = pPrnList->m_aQueueInfos.size() != pNewList->m_aQueueInfos.size();
            for( decltype(pPrnList->m_aQueueInfos)::size_type i = 0; ! bChanged && i < pPrnList->m_aQueueInfos.size(); i++ )
            {
                ImplPrnQueueData& rInfo     = pPrnList->m_aQueueInfos[i];
                ImplPrnQueueData& rNewInfo  = pNewList->m_aQueueInfos[i];
                if( ! rInfo.mpSalQueueInfo || ! rNewInfo.mpSalQueueInfo || // sanity check
                    rInfo.mpSalQueueInfo->maPrinterName != rNewInfo.mpSalQueueInfo->maPrinterName )
                {
                    bChanged = true;
                }
            }
            if( bChanged )
            {
                ImplDeletePrnQueueList();
                pSVData->maGDIData.mpPrinterQueueList = pNewList;

                Application* pApp = GetpApp();
                if( pApp )
                {
                    DataChangedEvent aDCEvt( DataChangedEventType::PRINTER );
                    Application::ImplCallEventListenersApplicationDataChanged(&aDCEvt);
                    Application::NotifyAllWindows( aDCEvt );
                }
            }
            else
                delete pNewList;
        }
    }

    bool Printer::UsePolyPolygonForComplexGradient()
    {
        return true;
    }

    void Printer::ClipAndDrawGradientMetafile ( const Gradient &rGradient, const tools::PolyPolygon &rPolyPoly )
    {
        const tools::Rectangle aBoundRect( rPolyPoly.GetBoundRect() );

        Push( PushFlags::CLIPREGION );
        IntersectClipRegion(vcl::Region(rPolyPoly));
        DrawGradient( aBoundRect, rGradient );
        Pop();
    }

    void Printer::InitFont() const
    {
        DBG_TESTSOLARMUTEX();

        if (!mpFontInstance)
            return;

        if ( mbInitFont )
        {
            // select font in the device layers
            mpGraphics->SetFont(mpFontInstance.get(), 0);
            mbInitFont = false;
        }
    }

    void Printer::SetFontOrientation( LogicalFontInstance* const pFontEntry ) const
    {
        pFontEntry->mnOrientation = pFontEntry->mxFontMetric->GetOrientation();
    }

    Bitmap Printer::GetBitmap( const Point& rSrcPt, const Size& rSize ) const
    {
        SAL_WARN("vcl.gdi", "GetBitmap(): This should never be called on by a Printer instance");

        return OutputDevice::GetBitmap( rSrcPt, rSize );
    }

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

↑ V1004 The 'pSVData->maGDIData.mpPrinterQueueList' pointer was used unsafely after it was verified against nullptr. Check lines: 414, 416.

↑ V581 The conditional expressions of the 'if' statements situated alongside each other are identical. Check lines: 423, 426.