/* -*- 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 .
 */
 
/**
  this file implements the sal printer interface (SalPrinter, SalInfoPrinter
  and some printer relevant methods of SalInstance and SalGraphicsData)
 
  as underlying library the printer features of psprint are used.
 
  The query methods of a SalInfoPrinter are implemented by querying psprint
 
  The job methods of a SalPrinter are implemented by calling psprint
  printer job functions.
 */
 
#include <sal/config.h>
 
#include <string_view>
 
// For spawning PDF and FAX generation
#include <unistd.h>
#include <sys/wait.h>
#include <sys/stat.h>
 
#include <comphelper/fileurl.hxx>
#include <o3tl/safeint.hxx>
#include <rtl/ustrbuf.hxx>
#include <rtl/ustring.hxx>
#include <sal/log.hxx>
#include <osl/diagnose.h>
#include <osl/file.hxx>
 
#include <utility>
#include <vcl/gdimtf.hxx>
#include <vcl/idle.hxx>
#include <vcl/printer/Options.hxx>
#include <vcl/print.hxx>
#include <vcl/QueueInfo.hxx>
#include <vcl/pdfwriter.hxx>
#include <printerinfomanager.hxx>
#include <vcl/svapp.hxx>
#include <vcl/settings.hxx>
#include <vcl/weld.hxx>
#include <strings.hrc>
#include <unx/genprn.h>
#include <unx/geninst.h>
#include <unx/genpspgraphics.h>
 
#include <jobset.h>
#include <print.h>
#include "prtsetup.hxx"
#include <salptype.hxx>
 
#include <com/sun/star/beans/PropertyValue.hpp>
 
using namespace psp;
using namespace com::sun::star;
 
static bool getPdfDir( const PrinterInfo& rInfo, OUString &rDir )
{
    sal_Int32 nIndex = 0;
    while( nIndex != -1 )
    {
        OUString aToken( rInfo.m_aFeatures.getToken( 0, ',', nIndex ) );
        if( aToken.startsWith( "pdf=" ) )
        {
            sal_Int32 nPos = 0;
            rDir = aToken.getToken( 1, '=', nPos );
            if( rDir.isEmpty() && getenv( "HOME" ) )
                rDir = OUString( getenv( "HOME" ), strlen( getenv( "HOME" ) ), osl_getThreadTextEncoding() );
            return true;
        }
    }
    return false;
}
 
namespace
{
    class QueryString : public weld::GenericDialogController
    {
    private:
        OUString&           m_rReturnValue;
 
        std::unique_ptr<weld::Button> m_xOKButton;
        std::unique_ptr<weld::Label> m_xFixedText;
        std::unique_ptr<weld::Entry> m_xEdit;
 
        DECL_LINK( ClickBtnHdl, weld::Button&, void );
 
    public:
        // parent window, Query text, initial value
        QueryString(weld::Window*, OUString const &, OUString &);
    };
 
    /*
     *  QueryString
     */
    QueryString::QueryString(weld::Window* pParent, OUString const & rQuery, OUString& rRet)
        : GenericDialogController(pParent, u"vcl/ui/querydialog.ui"_ustr, u"QueryDialog"_ustr)
        , m_rReturnValue( rRet )
        , m_xOKButton(m_xBuilder->weld_button(u"ok"_ustr))
        , m_xFixedText(m_xBuilder->weld_label(u"label"_ustr))
        , m_xEdit(m_xBuilder->weld_entry(u"entry"_ustr))
    {
        m_xOKButton->connect_clicked(LINK(this, QueryString, ClickBtnHdl));
        m_xFixedText->set_label(rQuery);
        m_xEdit->set_text(m_rReturnValue);
        m_xDialog->set_title(rQuery);
    }
 
    IMPL_LINK(QueryString, ClickBtnHdl, weld::Button&, rButton, void)
    {
        if (&rButton == m_xOKButton.get())
        {
            m_rReturnValue = m_xEdit->get_text();
            m_xDialog->response(RET_OK);
        }
        else
            m_xDialog->response(RET_CANCEL);
    }
 
    int QueryFaxNumber(OUString& rNumber)
    {
        QueryString aQuery(Application::GetDefDialogParent(), VclResId(SV_PRINT_QUERYFAXNUMBER_TXT), rNumber);
        return aQuery.run();
    }
}
 
static int PtTo10Mu( int nPoints ) { return static_cast<int>((static_cast<double>(nPoints)*35.27777778)+0.5); }
 
static int TenMuToPt( int nUnits ) { return static_cast<int>((static_cast<double>(nUnits)/35.27777778)+0.5); }
 
static void copyJobDataToJobSetup( ImplJobSetup* pJobSetup, JobData& rData )
{
    pJobSetup->SetOrientation( rData.m_eOrientation == orientation::Landscape ?
        Orientation::Landscape : Orientation::Portrait );
 
    // copy page size
    OUString aPaper;
    int width, height;
 
    rData.m_aContext.getPageSize( aPaper, width, height );
    pJobSetup->SetPaperFormat( PaperInfo::fromPSName(
        OUStringToOString( aPaper, RTL_TEXTENCODING_ISO_8859_1 )));
 
    pJobSetup->SetPaperWidth( 0 );
    pJobSetup->SetPaperHeight( 0 );
    if( pJobSetup->GetPaperFormat() == PAPER_USER )
    {
        // transform to 100dth mm
        width               = PtTo10Mu( width );
        height              = PtTo10Mu( height );
 
        if( rData.m_eOrientation == psp::orientation::Portrait )
        {
            pJobSetup->SetPaperWidth( width );
            pJobSetup->SetPaperHeight( height );
        }
        else
        {
            pJobSetup->SetPaperWidth( height );
            pJobSetup->SetPaperHeight( width );
        }
    }
 
    // copy input slot
    const PPDKey* pKey = nullptr;
    const PPDValue* pValue = nullptr;
 
    pJobSetup->SetPaperBin( 0 );
    if( rData.m_pParser )
        pKey                    = rData.m_pParser->getKey( u"InputSlot"_ustr );
    if( pKey )
        pValue                  = rData.m_aContext.getValue( pKey );
    if( pKey && pValue )
    {
        int nPaperBin;
        for( nPaperBin = 0;
             pValue != pKey->getValue( nPaperBin ) &&
                 nPaperBin < pKey->countValues();
             nPaperBin++);
        pJobSetup->SetPaperBin(
            nPaperBin == pKey->countValues() ? 0 : nPaperBin);
    }
 
    // copy duplex
    pKey = nullptr;
    pValue = nullptr;
 
    pJobSetup->SetDuplexMode( DuplexMode::Unknown );
    if( rData.m_pParser )
        pKey = rData.m_pParser->getKey( u"Duplex"_ustr );
    if( pKey )
        pValue = rData.m_aContext.getValue( pKey );
    if( pKey && pValue )
    {
        if( pValue->m_aOption.equalsIgnoreAsciiCase( "None" ) ||
            pValue->m_aOption.startsWithIgnoreAsciiCase( "Simplex" )
           )
        {
            pJobSetup->SetDuplexMode( DuplexMode::Off);
        }
        else if( pValue->m_aOption.equalsIgnoreAsciiCase( "DuplexNoTumble" ) )
        {
            pJobSetup->SetDuplexMode( DuplexMode::LongEdge );
        }
        else if( pValue->m_aOption.equalsIgnoreAsciiCase( "DuplexTumble" ) )
        {
            pJobSetup->SetDuplexMode( DuplexMode::ShortEdge );
        }
    }
 
    // copy the whole context
 
    sal_uInt32 nBytes;
    std::unique_ptr<sal_uInt8[]> pBuffer;
    if( rData.getStreamBuffer( pBuffer, nBytes ) )
    {
        pJobSetup->SetDriverData( std::move(pBuffer), nBytes );
    }
    else
    {
        pJobSetup->SetDriverData( nullptr, 0 );
    }
    pJobSetup->SetPapersizeFromSetup( rData.m_bPapersizeFromSetup );
}
 
static std::vector<OUString> getFaxNumbers()
{
    std::vector<OUString> aFaxNumbers;
 
    OUString aNewNr;
    if (QueryFaxNumber(aNewNr))
    {
        for (sal_Int32 nIndex {0}; nIndex >= 0; )
            aFaxNumbers.push_back(aNewNr.getToken( 0, ';', nIndex ));
    }
 
    return aFaxNumbers;
}
 
/*
 *  SalInstance
 */
 
void SalGenericInstance::configurePspInfoPrinter(PspSalInfoPrinter *pPrinter,
    SalPrinterQueueInfo const * pQueueInfo, ImplJobSetup* pJobSetup)
{
    if( !pJobSetup )
        return;
 
    PrinterInfoManager& rManager( PrinterInfoManager::get() );
    PrinterInfo aInfo( rManager.getPrinterInfo( pQueueInfo->maPrinterName ) );
    pPrinter->m_aJobData = aInfo;
 
    if( pJobSetup->GetDriverData() )
        JobData::constructFromStreamBuffer( pJobSetup->GetDriverData(),
                                            pJobSetup->GetDriverDataLen(), aInfo );
 
    pJobSetup->SetSystem( JOBSETUP_SYSTEM_UNIX );
    pJobSetup->SetPrinterName( pQueueInfo->maPrinterName );
    pJobSetup->SetDriver( aInfo.m_aDriverName );
    copyJobDataToJobSetup( pJobSetup, aInfo );
}
 
SalInfoPrinter* SalGenericInstance::CreateInfoPrinter( SalPrinterQueueInfo*    pQueueInfo,
                                                       ImplJobSetup*           pJobSetup )
{
    mbPrinterInit = true;
    // create and initialize SalInfoPrinter
    PspSalInfoPrinter* pPrinter = new PspSalInfoPrinter();
    configurePspInfoPrinter(pPrinter, pQueueInfo, pJobSetup);
    return pPrinter;
}
 
void SalGenericInstance::DestroyInfoPrinter( SalInfoPrinter* pPrinter )
{
    delete pPrinter;
}
 
std::unique_ptr<SalPrinter> SalGenericInstance::CreatePrinter( SalInfoPrinter* pInfoPrinter )
{
    mbPrinterInit = true;
    // create and initialize SalPrinter
    PspSalPrinter* pPrinter = new PspSalPrinter( pInfoPrinter );
    pPrinter->m_aJobData = static_cast<PspSalInfoPrinter*>(pInfoPrinter)->m_aJobData;
 
    return std::unique_ptr<SalPrinter>(pPrinter);
}
 
void SalGenericInstance::GetPrinterQueueInfo( ImplPrnQueueList* pList )
{
    mbPrinterInit = true;
    PrinterInfoManager& rManager( PrinterInfoManager::get() );
    static const char* pNoSyncDetection = getenv( "SAL_DISABLE_SYNCHRONOUS_PRINTER_DETECTION" );
    if( ! pNoSyncDetection || ! *pNoSyncDetection )
    {
        // #i62663# synchronize possible asynchronouse printer detection now
        rManager.checkPrintersChanged( true );
    }
    ::std::vector< OUString > aPrinters;
    rManager.listPrinters( aPrinters );
 
    for (auto const& printer : aPrinters)
    {
        const PrinterInfo& rInfo( rManager.getPrinterInfo(printer) );
        // create new entry
        std::unique_ptr<SalPrinterQueueInfo> pInfo(new SalPrinterQueueInfo);
        pInfo->maPrinterName    = printer;
        pInfo->maDriver         = rInfo.m_aDriverName;
        pInfo->maLocation       = rInfo.m_aLocation;
        pInfo->maComment        = rInfo.m_aComment;
 
        OUString sPdfDir;
        if (getPdfDir(rInfo, sPdfDir))
            pInfo->maLocation = sPdfDir;
 
        pList->Add( std::move(pInfo) );
    }
}
 
void SalGenericInstance::GetPrinterQueueState( SalPrinterQueueInfo* )
{
    mbPrinterInit = true;
}
 
OUString SalGenericInstance::GetDefaultPrinter()
{
    mbPrinterInit = true;
    PrinterInfoManager& rManager( PrinterInfoManager::get() );
    return rManager.getDefaultPrinter();
}
 
PspSalInfoPrinter::PspSalInfoPrinter()
{
}
 
PspSalInfoPrinter::~PspSalInfoPrinter()
{
}
 
void PspSalInfoPrinter::InitPaperFormats( const ImplJobSetup* )
{
    m_aPaperFormats.clear();
    m_bPapersInit = true;
 
    if( !m_aJobData.m_pParser )
        return;
 
    const PPDKey* pKey = m_aJobData.m_pParser->getKey( u"PageSize"_ustr );
    if( pKey )
    {
        int nValues = pKey->countValues();
        for( int i = 0; i < nValues; i++ )
        {
            const PPDValue* pValue = pKey->getValue( i );
            int nWidth = 0, nHeight = 0;
            m_aJobData.m_pParser->getPaperDimension( pValue->m_aOption, nWidth, nHeight );
            PaperInfo aInfo(PtTo10Mu( nWidth ), PtTo10Mu( nHeight ));
            m_aPaperFormats.push_back( aInfo );
        }
    }
}
 
int PspSalInfoPrinter::GetLandscapeAngle( const ImplJobSetup* )
{
    return 900;
}
 
SalGraphics* PspSalInfoPrinter::AcquireGraphics()
{
    // return a valid pointer only once
    // the reasoning behind this is that we could have different
    // SalGraphics that can run in multiple threads
    // (future plans)
    SalGraphics* pRet = nullptr;
    if( ! m_pGraphics )
    {
        m_pGraphics = GetGenericInstance()->CreatePrintGraphics();
        m_pGraphics->Init(&m_aJobData);
        pRet = m_pGraphics.get();
    }
    return pRet;
}
 
void PspSalInfoPrinter::ReleaseGraphics( SalGraphics* pGraphics )
{
    if( m_pGraphics.get() == pGraphics )
    {
        m_pGraphics.reset();
    }
}
 
bool PspSalInfoPrinter::Setup( weld::Window* pFrame, ImplJobSetup* pJobSetup )
{
    if( ! pFrame || ! pJobSetup )
        return false;
 
    PrinterInfoManager& rManager = PrinterInfoManager::get();
 
    PrinterInfo aInfo( rManager.getPrinterInfo( pJobSetup->GetPrinterName() ) );
    if ( pJobSetup->GetDriverData() )
    {
        SetData( JobSetFlags::ALL, pJobSetup );
        JobData::constructFromStreamBuffer( pJobSetup->GetDriverData(), pJobSetup->GetDriverDataLen(), aInfo );
    }
    aInfo.m_bPapersizeFromSetup = pJobSetup->GetPapersizeFromSetup();
    aInfo.meSetupMode = pJobSetup->GetPrinterSetupMode();
 
    if (SetupPrinterDriver(pFrame, aInfo))
    {
        pJobSetup->SetDriverData( nullptr, 0 );
 
        sal_uInt32 nBytes;
        std::unique_ptr<sal_uInt8[]> pBuffer;
        aInfo.getStreamBuffer( pBuffer, nBytes );
        pJobSetup->SetDriverData( std::move(pBuffer), nBytes );
 
        // copy everything to job setup
        copyJobDataToJobSetup( pJobSetup, aInfo );
        JobData::constructFromStreamBuffer( pJobSetup->GetDriverData(), pJobSetup->GetDriverDataLen(), m_aJobData );
        return true;
    }
    return false;
}
 
// This function gets the driver data and puts it into pJobSetup
// If pJobSetup->GetDriverData() is NOT NULL, then the independent
// data should be merged into the driver data
// If pJobSetup->GetDriverData() IS NULL, then the driver defaults
// should be merged into the independent data
bool PspSalInfoPrinter::SetPrinterData( ImplJobSetup* pJobSetup )
{
    if( pJobSetup->GetDriverData() )
        return SetData( JobSetFlags::ALL, pJobSetup );
 
    copyJobDataToJobSetup( pJobSetup, m_aJobData );
 
    return true;
}
 
// This function merges the independent driver data
// and sets the new independent data in pJobSetup
// Only the data must be changed, where the bit
// in nGetDataFlags is set
bool PspSalInfoPrinter::SetData(
    JobSetFlags nSetDataFlags,
    ImplJobSetup* pJobSetup )
{
    JobData aData;
    JobData::constructFromStreamBuffer( pJobSetup->GetDriverData(), pJobSetup->GetDriverDataLen(), aData );
 
    if( aData.m_pParser )
    {
        const PPDKey* pKey;
        const PPDValue* pValue;
 
        // merge orientation if necessary
        if( nSetDataFlags & JobSetFlags::ORIENTATION )
            aData.m_eOrientation = pJobSetup->GetOrientation() == Orientation::Landscape ? orientation::Landscape : orientation::Portrait;
 
        // merge papersize if necessary
        if( nSetDataFlags & JobSetFlags::PAPERSIZE )
        {
            OUString aPaper;
 
            if( pJobSetup->GetPaperFormat() == PAPER_USER )
                aPaper = aData.m_pParser->matchPaper(
                    TenMuToPt( pJobSetup->GetPaperWidth() ),
                    TenMuToPt( pJobSetup->GetPaperHeight() ),
                    &aData.m_eOrientation );
            else
                aPaper = OStringToOUString(PaperInfo::toPSName(pJobSetup->GetPaperFormat()), RTL_TEXTENCODING_ISO_8859_1);
 
            pKey = aData.m_pParser->getKey( u"PageSize"_ustr );
            pValue = pKey ? pKey->getValueCaseInsensitive( aPaper ) : nullptr;
 
            // some PPD files do not specify the standard paper names (e.g. C5 instead of EnvC5)
            // try to find the correct paper anyway using the size
            if( pKey && ! pValue && pJobSetup->GetPaperFormat() != PAPER_USER )
            {
                PaperInfo aInfo( pJobSetup->GetPaperFormat() );
                aPaper = aData.m_pParser->matchPaper(
                    TenMuToPt( aInfo.getWidth() ),
                    TenMuToPt( aInfo.getHeight() ),
                    &aData.m_eOrientation );
                pValue = pKey->getValueCaseInsensitive( aPaper );
            }
 
            if( ! ( pKey && pValue && aData.m_aContext.setValue( pKey, pValue ) == pValue ) )
                return false;
        }
 
        // merge paperbin if necessary
        if( nSetDataFlags & JobSetFlags::PAPERBIN )
        {
            pKey = aData.m_pParser->getKey( u"InputSlot"_ustr );
            if( pKey )
            {
                int nPaperBin = pJobSetup->GetPaperBin();
                if( nPaperBin >= pKey->countValues() )
                    pValue = pKey->getDefaultValue();
                else
                    pValue = pKey->getValue( pJobSetup->GetPaperBin() );
 
                // may fail due to constraints;
                // real paper bin is copied back to jobsetup in that case
                aData.m_aContext.setValue( pKey, pValue );
            }
            // if printer has no InputSlot key simply ignore this setting
            // (e.g. SGENPRT has no InputSlot)
        }
 
        // merge duplex if necessary
        if( nSetDataFlags & JobSetFlags::DUPLEXMODE )
        {
            pKey = aData.m_pParser->getKey( u"Duplex"_ustr );
            if( pKey )
            {
                pValue = nullptr;
                switch( pJobSetup->GetDuplexMode() )
                {
                case DuplexMode::Off:
                    pValue = pKey->getValue( u"None"_ustr );
                    if( pValue == nullptr )
                        pValue = pKey->getValue( u"SimplexNoTumble"_ustr );
                    break;
                case DuplexMode::ShortEdge:
                    pValue = pKey->getValue( u"DuplexTumble"_ustr );
                    break;
                case DuplexMode::LongEdge:
                    pValue = pKey->getValue( u"DuplexNoTumble"_ustr );
                    break;
                case DuplexMode::Unknown:
                default:
                    pValue = nullptr;
                    break;
                }
                if( ! pValue )
                    pValue = pKey->getDefaultValue();
                aData.m_aContext.setValue( pKey, pValue );
            }
        }
        aData.m_bPapersizeFromSetup = pJobSetup->GetPapersizeFromSetup();
 
        m_aJobData = aData;
        copyJobDataToJobSetup( pJobSetup, aData );
        return true;
    }
 
    return false;
}
 
void PspSalInfoPrinter::GetPageInfo(
    const ImplJobSetup* pJobSetup,
    tools::Long& rOutWidth, tools::Long& rOutHeight,
    Point& rPageOffset,
    Size& rPaperSize )
{
    if( ! pJobSetup )
        return;
 
    JobData aData;
    JobData::constructFromStreamBuffer( pJobSetup->GetDriverData(), pJobSetup->GetDriverDataLen(), aData );
 
    // get the selected page size
    if( !aData.m_pParser )
        return;
 
 
    OUString aPaper;
    int width, height;
    int left = 0, top = 0, right = 0, bottom = 0;
    int nDPI = aData.m_aContext.getRenderResolution();
 
    if( aData.m_eOrientation == psp::orientation::Portrait )
    {
        aData.m_aContext.getPageSize( aPaper, width, height );
        aData.m_pParser->getMargins( aPaper, left, right, top, bottom );
    }
    else
    {
        aData.m_aContext.getPageSize( aPaper, height, width );
        aData.m_pParser->getMargins( aPaper, top, bottom, right, left );
    }
 
    rPaperSize.setWidth( width * nDPI / 72 );
    rPaperSize.setHeight( height * nDPI / 72 );
    rPageOffset.setX( left * nDPI / 72 );
    rPageOffset.setY( top * nDPI / 72 );
    rOutWidth   = ( width  - left - right ) * nDPI / 72;
    rOutHeight  = ( height - top  - bottom ) * nDPI / 72;
 
}
 
sal_uInt16 PspSalInfoPrinter::GetPaperBinCount( const ImplJobSetup* pJobSetup )
{
    if( ! pJobSetup )
        return 0;
 
    JobData aData;
    JobData::constructFromStreamBuffer( pJobSetup->GetDriverData(), pJobSetup->GetDriverDataLen(), aData );
 
    const PPDKey* pKey = aData.m_pParser ? aData.m_pParser->getKey( u"InputSlot"_ustr ): nullptr;
    return pKey ? pKey->countValues() : 0;
}
 
OUString PspSalInfoPrinter::GetPaperBinName( const ImplJobSetup* pJobSetup, sal_uInt16 nPaperBin )
{
    JobData aData;
    JobData::constructFromStreamBuffer( pJobSetup->GetDriverData(), pJobSetup->GetDriverDataLen(), aData );
 
    if( aData.m_pParser )
    {
        const PPDKey* pKey = aData.m_pParser ? aData.m_pParser->getKey( u"InputSlot"_ustr ): nullptr;
        if( ! pKey || nPaperBin >= o3tl::make_unsigned(pKey->countValues()) )
            return aData.m_pParser->getDefaultInputSlot();
        const PPDValue* pValue = pKey->getValue( nPaperBin );
        if( pValue )
            return aData.m_pParser->translateOption( pKey->getKey(), pValue->m_aOption );
    }
 
    return OUString();
}
 
sal_uInt16 PspSalInfoPrinter::GetPaperBinBySourceIndex( const ImplJobSetup*, sal_uInt16 )
{
    return 0xffff;
}
 
sal_uInt16  PspSalInfoPrinter::GetSourceIndexByPaperBin(const ImplJobSetup*, sal_uInt16)
{
    return 0;
}
 
sal_uInt32 PspSalInfoPrinter::GetCapabilities( const ImplJobSetup* pJobSetup, PrinterCapType nType )
{
    switch( nType )
    {
        case PrinterCapType::SupportDialog:
            return 1;
        case PrinterCapType::Copies:
            return 0xffff;
        case PrinterCapType::CollateCopies:
        {
            // PPDs don't mention the number of possible collated copies.
            // so let's guess as many as we want ?
            return 0xffff;
        }
        case PrinterCapType::SetOrientation:
            return 1;
        case PrinterCapType::SetPaperSize:
            return 1;
        case PrinterCapType::SetPaper:
            return 0;
        case PrinterCapType::Fax:
            {
                // see if the PPD contains the fax4CUPS "Dial" option and that it's not set
                // to "manually"
                JobData aData = PrinterInfoManager::get().getPrinterInfo(pJobSetup->GetPrinterName());
                if( pJobSetup->GetDriverData() )
                    JobData::constructFromStreamBuffer( pJobSetup->GetDriverData(), pJobSetup->GetDriverDataLen(), aData );
                const PPDKey* pKey = aData.m_pParser ? aData.m_pParser->getKey(u"Dial"_ustr) : nullptr;
                const PPDValue* pValue = pKey ? aData.m_aContext.getValue(pKey) : nullptr;
                if (pValue && !pValue->m_aOption.equalsIgnoreAsciiCase("Manually"))
                    return 1;
                return 0;
            }
 
        case PrinterCapType::PDF:
            return 1;
        case PrinterCapType::ExternalDialog:
            return PrinterInfoManager::get().checkFeatureToken( pJobSetup->GetPrinterName(), "external_dialog" ) ? 1 : 0;
        case PrinterCapType::UsePullModel:
            return 1;
        default: break;
    }
    return 0;
}
 
/*
 *  SalPrinter
 */
PspSalPrinter::PspSalPrinter( SalInfoPrinter* pInfoPrinter )
    : m_pInfoPrinter( pInfoPrinter )
{
}
 
PspSalPrinter::~PspSalPrinter()
{
}
 
bool PspSalPrinter::StartJob(
    const OUString* /*pFileName*/,
    const OUString& /*rJobName*/,
    const OUString& /*rAppName*/,
    sal_uInt32 /*nCopies*/,
    bool /*bCollate*/,
    bool /*bDirect*/,
    ImplJobSetup* /*pJobSetup*/ )
{
    OSL_FAIL( "should never be called" );
    return false;
}
 
bool PspSalPrinter::EndJob()
{
    GetSalInstance()->jobEndedPrinterUpdate();
    return true;
}
 
SalGraphics* PspSalPrinter::StartPage( ImplJobSetup*, bool )
{
    OSL_FAIL( "should never be called" );
    return nullptr;
}
 
void PspSalPrinter::EndPage()
{
    OSL_FAIL( "should never be called" );
}
 
namespace {
 
struct PDFNewJobParameters
{
    Size        maPageSize;
    sal_uInt16      mnPaperBin;
 
    PDFNewJobParameters( const Size& i_rSize = Size(),
                         sal_uInt16 i_nPaperBin = 0xffff )
    : maPageSize( i_rSize ), mnPaperBin( i_nPaperBin ) {}
 
    bool operator==(const PDFNewJobParameters& rComp ) const
    {
        const tools::Long nRotatedWidth = rComp.maPageSize.Height();
        const tools::Long nRotatedHeight = rComp.maPageSize.Width();
        Size aCompLSSize(nRotatedWidth, nRotatedHeight);
        return
            (maPageSize == rComp.maPageSize || maPageSize == aCompLSSize)
        &&  mnPaperBin == rComp.mnPaperBin
        ;
    }
 
    bool operator!=(const PDFNewJobParameters& rComp) const
    {
        return ! operator==(rComp);
    }
};
 
struct PDFPrintFile
{
    OUString       maTmpURL;
    PDFNewJobParameters maParameters;
 
    PDFPrintFile( OUString i_URL, const PDFNewJobParameters& i_rNewParameters )
    : maTmpURL(std::move( i_URL ))
    , maParameters( i_rNewParameters ) {}
};
 
}
 
bool PspSalPrinter::StartJob( const OUString* i_pFileName, const OUString& i_rJobName, const OUString& i_rAppName,
                              ImplJobSetup* i_pSetupData, vcl::PrinterController& i_rController )
{
    SAL_INFO( "vcl.unx.print", "StartJob with controller: pFilename = " << (i_pFileName ? *i_pFileName : u"<nil>"_ustr) );
    // reset IsLastPage
    i_rController.setLastPage( false );
    // is this a fax device
    bool bFax = m_pInfoPrinter->GetCapabilities(i_pSetupData, PrinterCapType::Fax) == 1;
 
    // update job data
    if( i_pSetupData )
        JobData::constructFromStreamBuffer( i_pSetupData->GetDriverData(), i_pSetupData->GetDriverDataLen(), m_aJobData );
 
    // possibly create one job for collated output
    int nCopies = i_rController.getPrinter()->GetCopyCount();
    bool bCollate = i_rController.getPrinter()->IsCollateCopy();
    bool bSinglePrintJobs = i_rController.getPrinter()->IsSinglePrintJobs();
 
    // notify start of real print job
    i_rController.jobStarted();
 
    // setup PDFWriter context
    vcl::PDFWriter::PDFWriterContext aContext;
    aContext.Version            = vcl::PDFWriter::PDFVersion::PDF_1_4;
    aContext.Tagged             = false;
    aContext.DocumentLocale     = Application::GetSettings().GetLanguageTag().getLocale();
    aContext.ColorMode          = i_rController.getPrinter()->GetPrinterOptions().IsConvertToGreyscales()
    ? vcl::PDFWriter::DrawGreyscale : vcl::PDFWriter::DrawColor;
 
    // prepare doc info
    aContext.DocumentInfo.Title              = i_rJobName;
    aContext.DocumentInfo.Creator            = i_rAppName;
    aContext.DocumentInfo.Producer           = i_rAppName;
 
    // define how we handle metafiles in PDFWriter
    vcl::PDFWriter::PlayMetafileContext aMtfContext;
    aMtfContext.m_bOnlyLosslessCompression = true;
 
    std::shared_ptr<vcl::PDFWriter> xWriter;
    std::vector< PDFPrintFile > aPDFFiles;
    VclPtr<Printer> xPrinter( i_rController.getPrinter() );
    int nAllPages = i_rController.getFilteredPageCount();
    i_rController.createProgressDialog();
    bool bAborted = false;
    PDFNewJobParameters aLastParm;
 
    aContext.DPIx = xPrinter->GetDPIX();
    aContext.DPIy = xPrinter->GetDPIY();
    for( int nPage = 0; nPage < nAllPages && ! bAborted; nPage++ )
    {
        if( nPage == nAllPages-1 )
            i_rController.setLastPage( true );
 
        // get the page's metafile
        GDIMetaFile aPageFile;
        vcl::PrinterController::PageSize aPageSize = i_rController.getFilteredPageFile( nPage, aPageFile );
        if( i_rController.isProgressCanceled() )
        {
            bAborted = true;
            if( nPage != nAllPages-1 )
            {
                i_rController.createProgressDialog();
                i_rController.setLastPage( true );
                i_rController.getFilteredPageFile( nPage, aPageFile );
            }
        }
        else
        {
            xPrinter->SetMapMode( MapMode( MapUnit::Map100thMM ) );
            xPrinter->SetPaperSizeUser( aPageSize.aSize );
            PDFNewJobParameters aNewParm(xPrinter->GetPaperSize(), xPrinter->GetPaperBin());
 
            // create PDF writer on demand
            // either on first page
            // or on paper format change - cups does not support multiple paper formats per job (yet?)
            // so we need to start a new job to get a new paper format from the printer
            // orientation switches (that is switch of height and width) is handled transparently by CUPS
            if( ! xWriter ||
                (aNewParm != aLastParm && ! i_pFileName ) )
            {
                if( xWriter )
                {
                    xWriter->Emit();
                }
                // produce PDF file
                OUString aPDFUrl;
                if( i_pFileName )
                    aPDFUrl = *i_pFileName;
                else
                    osl_createTempFile( nullptr, nullptr, &aPDFUrl.pData );
                // normalize to file URL
                if( !comphelper::isFileUrl(aPDFUrl) )
                {
                    // this is not a file URL, but it should
                    // form it into an osl friendly file URL
                    OUString aTmp;
                    osl_getFileURLFromSystemPath( aPDFUrl.pData, &aTmp.pData );
                    aPDFUrl = aTmp;
                }
                // save current file and paper format
                aLastParm = aNewParm;
                aPDFFiles.emplace_back( aPDFUrl, aNewParm );
                // update context
                aContext.URL = aPDFUrl;
 
                // create and initialize PDFWriter
                xWriter = std::make_shared<vcl::PDFWriter>( aContext, uno::Reference< beans::XMaterialHolder >() );
            }
 
            xWriter->NewPage( TenMuToPt( aNewParm.maPageSize.Width() ),
                              TenMuToPt( aNewParm.maPageSize.Height() ),
                              vcl::PDFWriter::Orientation::Portrait );
 
            xWriter->PlayMetafile( aPageFile, aMtfContext );
        }
    }
 
    // emit the last file
    if( xWriter )
        xWriter->Emit();
 
    // handle collate, copy count and multiple jobs correctly
    int nOuterJobs = 1;
    if( bSinglePrintJobs )
    {
        nOuterJobs = nCopies;
        m_aJobData.m_nCopies = 1;
    }
    else
    {
        if( bCollate )
        {
            if (aPDFFiles.size() == 1 && xPrinter->HasSupport(PrinterSupport::CollateCopy))
            {
                m_aJobData.setCollate( true );
                m_aJobData.m_nCopies = nCopies;
            }
            else
            {
                nOuterJobs = nCopies;
                m_aJobData.m_nCopies = 1;
            }
        }
        else
        {
            m_aJobData.setCollate( false );
            m_aJobData.m_nCopies = nCopies;
        }
    }
 
    std::vector<OUString> aFaxNumbers;
 
    // check for fax numbers
    if (!bAborted && bFax)
    {
        aFaxNumbers = getFaxNumbers();
        bAborted = aFaxNumbers.empty();
    }
 
    bool bSuccess(true);
    // spool files
    if( ! i_pFileName && ! bAborted )
    {
        do
        {
            OUString sFaxNumber;
            if (!aFaxNumbers.empty())
            {
                sFaxNumber = aFaxNumbers.back();
                aFaxNumbers.pop_back();
            }
 
            bool bFirstJob = true;
            for( int nCurJob = 0; nCurJob < nOuterJobs; nCurJob++ )
            {
                for( size_t i = 0; i < aPDFFiles.size(); i++ )
                {
                    oslFileHandle pFile = nullptr;
                    osl_openFile( aPDFFiles[i].maTmpURL.pData, &pFile, osl_File_OpenFlag_Read );
                    if (pFile && (osl_setFilePos(pFile, osl_Pos_Absolut, 0) == osl_File_E_None))
                    {
                        std::vector< char > buffer( 0x10000, 0 );
                        // update job data with current page size
                        Size aPageSize( aPDFFiles[i].maParameters.maPageSize );
                        m_aJobData.setPaper( TenMuToPt( aPageSize.Width() ), TenMuToPt( aPageSize.Height() ) );
                        // update job data with current paperbin
                        m_aJobData.setPaperBin( aPDFFiles[i].maParameters.mnPaperBin );
 
                        // spool current file
                        FILE* fp = PrinterInfoManager::get().startSpool(xPrinter->GetName(), i_rController.isDirectPrint());
                        if( fp )
                        {
                            sal_uInt64 nBytesRead = 0;
                            do
                            {
                                osl_readFile( pFile, buffer.data(), buffer.size(), &nBytesRead );
                                if( nBytesRead > 0 )
                                {
                                    size_t nBytesWritten = fwrite(buffer.data(), 1, nBytesRead, fp);
                                    OSL_ENSURE(nBytesRead == nBytesWritten, "short write");
                                    if (nBytesRead != nBytesWritten)
                                        break;
                                }
                            } while( nBytesRead == buffer.size() );
                            OUStringBuffer aBuf( i_rJobName.getLength() + 8 );
                            aBuf.append( i_rJobName );
                            if( i > 0 || nCurJob > 0 )
                            {
                                aBuf.append( ' ' );
                                aBuf.append( sal_Int32( i + nCurJob * aPDFFiles.size() ) );
                            }
                            bSuccess &=
                            PrinterInfoManager::get().endSpool(xPrinter->GetName(), aBuf.makeStringAndClear(), fp, m_aJobData, bFirstJob, sFaxNumber);
                            bFirstJob = false;
                        }
                    }
                    osl_closeFile( pFile );
                }
            }
        }
        while (!aFaxNumbers.empty());
    }
 
    // job has been spooled
    i_rController.setJobState( bAborted
            ? view::PrintableState_JOB_ABORTED
            : (bSuccess ? view::PrintableState_JOB_SPOOLED
                          : view::PrintableState_JOB_SPOOLING_FAILED));
 
    // clean up the temporary PDF files
    if( ! i_pFileName || bAborted )
    {
        for(PDFPrintFile & rPDFFile : aPDFFiles)
        {
            osl_removeFile( rPDFFile.maTmpURL.pData );
            SAL_INFO( "vcl.unx.print", "removed print PDF file " << rPDFFile.maTmpURL );
        }
    }
 
    return true;
}
 
void SalGenericInstance::updatePrinterUpdate()
{
    if( Application::GetSettings().GetMiscSettings().GetDisablePrinting() )
        return;
 
    if (!isPrinterInit())
    {
        // #i45389# start background printer detection
        psp::PrinterInfoManager::get();
        return;
    }
 
    ::psp::PrinterInfoManager& rManager( ::psp::PrinterInfoManager::get() );
    if (rManager.checkPrintersChanged(false))
        PostPrintersChanged();
}
 
void SalGenericInstance::jobEndedPrinterUpdate()
{
}
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V547 Expression 'aData.m_pParser' is always true.