Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/gfx/cairo/cairo/src/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 3 kB image not shown  

Quelle  ppdparser.cxx   Sprache: C

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


#include <sal/config.h>
#include <config_cpdb.h>
#include <config_cups.h>

#include <stdlib.h>

#include <comphelper/string.hxx>
#include <o3tl/string_view.hxx>
#include <i18nlangtag/languagetag.hxx>
#include <jobdata.hxx>
#include <ppdparser.hxx>
#include <printerinfomanager.hxx>
#include <strhelper.hxx>
#include <utility>
#include <vcl/svapp.hxx>
#include <vcl/settings.hxx>

#include <unx/helper.hxx>

#if ENABLE_CPDB
#include <unx/cpdmgr.hxx>
#endif

#if ENABLE_CUPS
#include <unx/cupsmgr.hxx>
#endif

#include <tools/urlobj.hxx>
#include <tools/stream.hxx>
#include <tools/zcodec.hxx>
#include <o3tl/safeint.hxx>
#include <osl/file.hxx>
#include <osl/process.h>
#include <osl/thread.h>
#include <rtl/strbuf.hxx>
#include <rtl/ustrbuf.hxx>
#include <sal/log.hxx>
#include <salhelper/linkhelper.hxx>

#include <com/sun/star/lang/Locale.hpp>

#include <mutex>
#include <unordered_map>

#if ENABLE_CUPS
#include <cups/cups.h>
#endif

#include <config_dbus.h>
#include <config_gio.h>
#include <o3tl/hash_combine.hxx>

namespace psp
{
    class PPDTranslator
    {
        struct LocaleEqual
        {
            bool operator()(const css::lang::Locale& i_rLeft,
                            const css::lang::Locale& i_rRight) const
            {
                return i_rLeft.Language == i_rRight.Language &&
                    i_rLeft.Country == i_rRight.Country &&
                    i_rLeft.Variant == i_rRight.Variant;
            }
        };

        struct LocaleHash
        {
            size_t operator()(const css::lang::Locale& rLocale) const
            {
                std::size_t seed = 0;
                o3tl::hash_combine(seed, rLocale.Language.hashCode());
                o3tl::hash_combine(seed, rLocale.Country.hashCode());
                o3tl::hash_combine(seed, rLocale.Variant.hashCode());
                return seed;
            }
        };

        typedef std::unordered_map< css::lang::Locale, OUString, LocaleHash, LocaleEqual > translation_map;
        typedef std::unordered_map< OUString, translation_map > key_translation_map;

        key_translation_map     m_aTranslations;
        public:
        PPDTranslator() {}

        void insertValue(
            std::u16string_view i_rKey,
            std::u16string_view i_rOption,
            std::u16string_view i_rValue,
            const OUString& i_rTranslation,
            const css::lang::Locale& i_rLocale
            );

        void insertOption( std::u16string_view i_rKey,
                           std::u16string_view i_rOption,
                           const OUString& i_rTranslation,
                           const css::lang::Locale& i_rLocale )
        {
            insertValue( i_rKey, i_rOption, u"", i_rTranslation, i_rLocale );
        }

        void insertKey( std::u16string_view i_rKey,
                        const OUString& i_rTranslation,
                        const css::lang::Locale& i_rLocale = css::lang::Locale() )
        {
            insertValue( i_rKey, u"", u"", i_rTranslation, i_rLocale );
        }

        OUString translateValue(
            std::u16string_view i_rKey,
            std::u16string_view i_rOption
            ) const;

        OUString translateOption( std::u16string_view i_rKey,
                                       std::u16string_view i_rOption ) const
        {
            return translateValue( i_rKey, i_rOption  );
        }

        OUString translateKey( std::u16string_view i_rKey ) const
        {
            return translateValue( i_rKey, u"" );
        }
    };

    static css::lang::Locale normalizeInputLocale(
        const css::lang::Locale& i_rLocale
        )
    {
        css::lang::Locale aLoc( i_rLocale );
        if( aLoc.Language.isEmpty() )
        {
            // empty locale requested, fill in application UI locale
            aLoc = Application::GetSettings().GetUILanguageTag().getLocale();

            #if OSL_DEBUG_LEVEL > 1
            static const char* pEnvLocale = getenv( "SAL_PPDPARSER_LOCALE" );
            if( pEnvLocale && *pEnvLocale )
            {
                OString aStr( pEnvLocale );
                sal_Int32 nLen = aStr.getLength();
                aLoc.Language = OStringToOUString( aStr.copy( 0, std::min(nLen, 2) ), RTL_TEXTENCODING_MS_1252 );
                if( nLen >=5 && aStr[2] == '_' )
                    aLoc.Country = OStringToOUString( aStr.copy( 3, 2 ), RTL_TEXTENCODING_MS_1252 );
                else
                    aLoc.Country.clear();
                aLoc.Variant.clear();
            }
            #endif
        }
        /* FIXME-BCP47: using Variant, uppercase? */
        aLoc.Language = aLoc.Language.toAsciiLowerCase();
        aLoc.Country  = aLoc.Country.toAsciiUpperCase();
        aLoc.Variant  = aLoc.Variant.toAsciiUpperCase();

        return aLoc;
    }

    void PPDTranslator::insertValue(
        std::u16string_view i_rKey,
        std::u16string_view i_rOption,
        std::u16string_view i_rValue,
        const OUString& i_rTranslation,
        const css::lang::Locale& i_rLocale
        )
    {
        OUStringBuffer aKey( i_rKey.size() + i_rOption.size() + i_rValue.size() + 2 );
        aKey.append( i_rKey );
        if( !i_rOption.empty() || !i_rValue.empty() )
        {
            aKey.append( OUString::Concat(":") + i_rOption );
        }
        if( !i_rValue.empty() )
        {
            aKey.append( OUString::Concat(":") + i_rValue );
        }
        if( !aKey.isEmpty() && !i_rTranslation.isEmpty() )
        {
            OUString aK( aKey.makeStringAndClear() );
            css::lang::Locale aLoc;
            /* FIXME-BCP47: using Variant, uppercase? */
            aLoc.Language = i_rLocale.Language.toAsciiLowerCase();
            aLoc.Country  = i_rLocale.Country.toAsciiUpperCase();
            aLoc.Variant  = i_rLocale.Variant.toAsciiUpperCase();
            m_aTranslations[ aK ][ aLoc ] = i_rTranslation;
        }
    }

    OUString PPDTranslator::translateValue(
        std::u16string_view i_rKey,
        std::u16string_view i_rOption
        ) const
    {
        OUString aResult;

        OUStringBuffer aKey( i_rKey.size() + i_rOption.size() + 2 );
        aKey.append( i_rKey );
        if( !i_rOption.empty() )
        {
            aKey.append( OUString::Concat(":") + i_rOption );
        }
        if( !aKey.isEmpty() )
        {
            OUString aK( aKey.makeStringAndClear() );
            key_translation_map::const_iterator it = m_aTranslations.find( aK );
            if( it != m_aTranslations.end() )
            {
                const translation_map& rMap( it->second );

                css::lang::Locale aLoc( normalizeInputLocale( css::lang::Locale() ) );
                /* FIXME-BCP47: use LanguageTag::getFallbackStrings()? */
                forint nTry = 0; nTry < 4; nTry++ )
                {
                    translation_map::const_iterator tr = rMap.find( aLoc );
                    if( tr != rMap.end() )
                    {
                        aResult = tr->second;
                        break;
                    }
                    switch( nTry )
                    {
                    case 0: aLoc.Variant.clear();break;
                    case 1: aLoc.Country.clear();break;
                    case 2: aLoc.Language.clear();break;
                    }
                }
            }
        }
        return aResult;
    }

    class PPDCache
    {
    public:
        std::vector< std::unique_ptr<PPDParser> > aAllParsers;
        std::optional<std::unordered_map< OUString, OUString >> xAllPPDFiles;
    };
}

using namespace psp;

namespace
{
    PPDCache& getPPDCache()
    {
        static PPDCache thePPDCache;
        return thePPDCache;
    }

class PPDDecompressStream
{
private:
    PPDDecompressStream(const PPDDecompressStream&) = delete;
    PPDDecompressStream& operator=(const PPDDecompressStream&) = delete;

    std::unique_ptr<SvFileStream>   mpFileStream;
    std::unique_ptr<SvMemoryStream> mpMemStream;
    OUString       maFileName;

public:
    explicit PPDDecompressStream( const OUString& rFile );
    ~PPDDecompressStream();

    bool IsOpen() const;
    bool eof() const;
    OString ReadLine();
    void Open( const OUString& i_rFile );
    void Close();
    const OUString& GetFileName() const { return maFileName; }
};

}

PPDDecompressStream::PPDDecompressStream( const OUString& i_rFile )
{
    Open( i_rFile );
}

PPDDecompressStream::~PPDDecompressStream()
{
    Close();
}

void PPDDecompressStream::Open( const OUString& i_rFile )
{
    Close();

    mpFileStream.reset( new SvFileStream( i_rFile, StreamMode::READ ) );
    maFileName = mpFileStream->GetFileName();

    if( ! mpFileStream->IsOpen() )
    {
        Close();
        return;
    }

    OString aLine;
    mpFileStream->ReadLine( aLine );
    mpFileStream->Seek( 0 );

    // check for compress'ed or gzip'ed file
    if( aLine.getLength() <= 1 ||
        static_cast<unsigned char>(aLine[0]) != 0x1f ||
        static_cast<unsigned char>(aLine[1]) != 0x8b /* check for gzip */ )
        return;

    // so let's try to decompress the stream
    mpMemStream.reset( new SvMemoryStream( 4096, 4096 ) );
    ZCodec aCodec;
    aCodec.BeginCompression( ZCODEC_DEFAULT_COMPRESSION, /*gzLib*/true );
    tools::Long nComp = aCodec.Decompress( *mpFileStream, *mpMemStream );
    aCodec.EndCompression();
    if( nComp < 0 )
    {
        // decompression failed, must be an uncompressed stream after all
        mpMemStream.reset();
        mpFileStream->Seek( 0 );
    }
    else
    {
        // compression successful, can get rid of file stream
        mpFileStream.reset();
        mpMemStream->Seek( 0 );
    }
}

void PPDDecompressStream::Close()
{
    mpMemStream.reset();
    mpFileStream.reset();
}

bool PPDDecompressStream::IsOpen() const
{
    return (mpMemStream || (mpFileStream && mpFileStream->IsOpen()));
}

bool PPDDecompressStream::eof() const
{
    return ( mpMemStream ? mpMemStream->eof() : ( mpFileStream == nullptr || mpFileStream->eof() ) );
}

OString PPDDecompressStream::ReadLine()
{
    OString o_rLine;
    if( mpMemStream )
        mpMemStream->ReadLine( o_rLine );
    else if( mpFileStream )
        mpFileStream->ReadLine( o_rLine );
    return o_rLine;
}

static osl::FileBase::RC resolveLink( const OUString& i_rURL, OUString& o_rResolvedURL, OUString& o_rBaseName, osl::FileStatus::Type& o_rType)
{
    salhelper::LinkResolver aResolver(osl_FileStatus_Mask_FileName |
                                      osl_FileStatus_Mask_Type |
                                      osl_FileStatus_Mask_FileURL);

    osl::FileBase::RC aRet = aResolver.fetchFileStatus(i_rURL, 10/*nLinkLevel*/);

    if (aRet  == osl::FileBase::E_None)
    {
        o_rResolvedURL = aResolver.m_aStatus.getFileURL();
        o_rBaseName = aResolver.m_aStatus.getFileName();
        o_rType = aResolver.m_aStatus.getFileType();
    }

    return aRet;
}

void PPDParser::scanPPDDir( const OUString& rDir )
{
    static struct suffix_t
    {
        const char* pSuffix;
        const sal_Int32 nSuffixLen;
    } const pSuffixes[] =
    { { ".PS", 3 },  { ".PPD", 4 }, { ".PS.GZ", 6 }, { ".PPD.GZ", 7 } };

    PPDCache &rPPDCache = getPPDCache();

    osl::Directory aDir( rDir );
    if ( aDir.open() != osl::FileBase::E_None )
        return;

    osl::DirectoryItem aItem;

    INetURLObject aPPDDir(rDir);
    while( aDir.getNextItem( aItem ) == osl::FileBase::E_None )
    {
        osl::FileStatus aStatus( osl_FileStatus_Mask_FileName );
        if( aItem.getFileStatus( aStatus ) == osl::FileBase::E_None )
        {
            OUString aFileURL, aFileName;
            osl::FileStatus::Type eType = osl::FileStatus::Unknown;
            OUString aURL = rDir + "/" + aStatus.getFileName();

            if(resolveLink( aURL, aFileURL, aFileName, eType ) == osl::FileBase::E_None)
            {
                if( eType == osl::FileStatus::Regular )
                {
                    INetURLObject aPPDFile = aPPDDir;
                    aPPDFile.Append( aFileName );

                    // match extension
                    for(const suffix_t & rSuffix : pSuffixes)
                    {
                        if( aFileName.getLength() > rSuffix.nSuffixLen )
                        {
                            if( aFileName.endsWithIgnoreAsciiCaseAsciiL( rSuffix.pSuffix, rSuffix.nSuffixLen ) )
                            {
                                (*rPPDCache.xAllPPDFiles)[ aFileName.copy( 0, aFileName.getLength() - rSuffix.nSuffixLen ) ] = aPPDFile.PathToFileName();
                                break;
                            }
                        }
                    }
                }
                else if( eType == osl::FileStatus::Directory )
                {
                    scanPPDDir( aFileURL );
                }
            }
        }
    }
    aDir.close();
}

void PPDParser::initPPDFiles(PPDCache &rPPDCache)
{
    if( rPPDCache.xAllPPDFiles )
        return;

    rPPDCache.xAllPPDFiles.emplace();

    // check installation directories
    std::vector< OUString > aPathList;
    psp::getPrinterPathList( aPathList, PRINTER_PPDDIR );
    for (auto const& path : aPathList)
    {
        INetURLObject aPPDDir( path, INetProtocol::File, INetURLObject::EncodeMechanism::All );
        scanPPDDir( aPPDDir.GetMainURL( INetURLObject::DecodeMechanism::NONE ) );
    }
    if( rPPDCache.xAllPPDFiles->find( u"SGENPRT"_ustr ) != rPPDCache.xAllPPDFiles->end() )
        return;

    // last try: search in directory of executable (mainly for setup)
    OUString aExe;
    if( osl_getExecutableFile( &aExe.pData ) == osl_Process_E_None )
    {
        INetURLObject aDir( aExe );
        aDir.removeSegment();
        SAL_INFO("vcl.unx.print""scanning last chance dir: "
                << aDir.GetMainURL(INetURLObject::DecodeMechanism::NONE));
        scanPPDDir( aDir.GetMainURL( INetURLObject::DecodeMechanism::NONE ) );
        SAL_INFO("vcl.unx.print""SGENPRT "
                << (rPPDCache.xAllPPDFiles->find(u"SGENPRT"_ustr) ==
                    rPPDCache.xAllPPDFiles->end() ? "not found" : "found"));
    }
}

OUString PPDParser::getPPDFile( const OUString& rFile )
{
    INetURLObject aPPD( rFile, INetProtocol::File, INetURLObject::EncodeMechanism::All );
    // someone might enter a full qualified name here
    PPDDecompressStream aStream( aPPD.PathToFileName() );
    if( ! aStream.IsOpen() )
    {
        std::unordered_map< OUString, OUString >::const_iterator it;
        PPDCache &rPPDCache = getPPDCache();

        bool bRetry = true;
        do
        {
            initPPDFiles(rPPDCache);
            // some PPD files contain dots beside the extension, so try name first
            // and cut of points after that
            OUString aBase( rFile );
            sal_Int32 nLastIndex = aBase.lastIndexOf( '/' );
            if( nLastIndex >= 0 )
                aBase = aBase.copy( nLastIndex+1 );
            do
            {
                it = rPPDCache.xAllPPDFiles->find( aBase );
                nLastIndex = aBase.lastIndexOf( '.' );
                if( nLastIndex > 0 )
                    aBase = aBase.copy( 0, nLastIndex );
            } while( it == rPPDCache.xAllPPDFiles->end() && nLastIndex > 0 );

            if( it == rPPDCache.xAllPPDFiles->end() && bRetry )
            {
                // a new file ? rehash
                rPPDCache.xAllPPDFiles.reset();
                bRetry = false;
                // note this is optimized for office start where
                // no new files occur and initPPDFiles is called only once
            }
        } while( ! rPPDCache.xAllPPDFiles );

        if( it != rPPDCache.xAllPPDFiles->end() )
            aStream.Open( it->second );
    }

    OUString aRet;
    if( aStream.IsOpen() )
    {
        OString aLine = aStream.ReadLine();
        if (aLine.startsWith("*PPD-Adobe"))
            aRet = aStream.GetFileName();
        else
        {
            // our *Include hack does usually not begin
            // with *PPD-Adobe, so try some lines for *Include
            int nLines = 10;
            while (aLine.indexOf("*Include") != 0 && --nLines)
                aLine = aStream.ReadLine();
            if( nLines )
                aRet = aStream.GetFileName();
        }
    }

    return aRet;
}

const PPDParser* PPDParser::getParser( const OUString& rFile )
{
    // Recursive because we can get re-entered via CUPSManager::createCUPSParser
    static std::recursive_mutex aMutex;
    std::scoped_lock aGuard( aMutex );

    OUString aFile = rFile;
    if( !rFile.startsWith( "CUPS:" ) && !rFile.startsWith( "CPD:" ) )
        aFile = getPPDFile( rFile );
    if( aFile.isEmpty() )
    {
        SAL_INFO("vcl.unx.print""Could not get printer PPD file \""
                << rFile << "\" !");
        return nullptr;
    }
    else
        SAL_INFO("vcl.unx.print""Parsing printer info from \""
                 << rFile << "\" !");


    PPDCache &rPPDCache = getPPDCache();
    forauto const & i : rPPDCache.aAllParsers )
        if( i->m_aFile == aFile )
            return i.get();

    PPDParser* pNewParser = nullptr;
    if( !aFile.startsWith( "CUPS:" ) && !aFile.startsWith( "CPD:" ) )
        pNewParser = new PPDParser( aFile );
    else
    {
        PrinterInfoManager& rMgr = PrinterInfoManager::get();
        if( rMgr.getType() == PrinterInfoManager::Type::CUPS )
        {
#if ENABLE_CUPS
            pNewParser = const_cast<PPDParser*>(static_cast<CUPSManager&>(rMgr).createCUPSParser( aFile ));
#endif
        } else if ( rMgr.getType() == PrinterInfoManager::Type::CPD )
        {
#if ENABLE_CPDB
            pNewParser = const_cast<PPDParser*>(static_cast<CPDManager&>(rMgr).createCPDParser( aFile ));
#endif
        }
    }
    if( pNewParser )
    {
        // this may actually be the SGENPRT parser,
        // so ensure uniqueness here (but don't remove last we delete us!)
        if (std::none_of(
                    rPPDCache.aAllParsers.begin(),
                    rPPDCache.aAllParsers.end(),
                    [pNewParser] (std::unique_ptr<PPDParser> const & x) { return x.get() == pNewParser; } ))
        {
            // insert new parser to vector
            rPPDCache.aAllParsers.emplace_back(pNewParser);
        }
    }
    return pNewParser;
}

PPDParser::PPDParser(OUString aFile, const std::vector<PPDKey*>& keys)
    : m_aFile(std::move(aFile))
    , m_aFileEncoding(RTL_TEXTENCODING_MS_1252)
    , m_pImageableAreas(nullptr)
    , m_pDefaultPaperDimension(nullptr)
    , m_pPaperDimensions(nullptr)
    , m_pDefaultInputSlot(nullptr)
    , m_pDefaultResolution(nullptr)
    , m_pTranslator(new PPDTranslator())
{
    for (auto & key: keys)
    {
        insertKey( std::unique_ptr<PPDKey>(key) );
    }

    // fill in shortcuts
    const PPDKey* pKey;

    pKey = getKey( u"PageSize"_ustr );

    if ( pKey ) {
        std::unique_ptr<PPDKey> pImageableAreas(new PPDKey(u"ImageableArea"_ustr));
        std::unique_ptr<PPDKey> pPaperDimensions(new PPDKey(u"PaperDimension"_ustr));
#if ENABLE_CUPS
        for (int i = 0; i < pKey->countValues(); i++) {
            const PPDValue* pValue = pKey -> getValue(i);
            OUString aValueName = pValue -> m_aOption;
            PPDValue* pImageableAreaValue
                = pImageableAreas->insertValue(aValueName, PPDValueType::Quoted);
            PPDValue* pPaperDimensionValue
                = pPaperDimensions->insertValue(aValueName, PPDValueType::Quoted);
            rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
            OString o = OUStringToOString( aValueName, aEncoding );
            pwg_media_t *pPWGMedia = pwgMediaForPWG(o.pData->buffer);
            if (pPWGMedia != nullptr) {
                if ( pImageableAreaValue )
                    pImageableAreaValue->m_aValue =
                        "0 0 " +
                        OUString::number(PWG_TO_POINTS(pPWGMedia -> width)) +
                        " " +
                        OUString::number(PWG_TO_POINTS(pPWGMedia -> length));
                if ( pPaperDimensionValue )
                    pPaperDimensionValue->m_aValue =
                        OUString::number(PWG_TO_POINTS(pPWGMedia -> width))
                        + " "
                        + OUString::number(PWG_TO_POINTS(pPWGMedia -> length));
                if (aValueName.equals(pKey -> getDefaultValue() -> m_aOption)) {
                    pImageableAreas -> m_pDefaultValue = pImageableAreaValue;
                    pPaperDimensions -> m_pDefaultValue = pPaperDimensionValue;
                }
            }
        }
#endif
        insertKey(std::move(pImageableAreas));
        insertKey(std::move(pPaperDimensions));
    }

    m_pImageableAreas = getKey( u"ImageableArea"_ustr );
    const PPDValue* pDefaultImageableArea = nullptr;
    if( m_pImageableAreas )
        pDefaultImageableArea = m_pImageableAreas->getDefaultValue();
    if (m_pImageableAreas == nullptr) {
        SAL_WARN( "vcl.unx.print""no ImageableArea in " << m_aFile);
    }
    if (pDefaultImageableArea == nullptr) {
        SAL_WARN( "vcl.unx.print""no DefaultImageableArea in " << m_aFile);
    }

    m_pPaperDimensions = getKey( u"PaperDimension"_ustr );
    if( m_pPaperDimensions )
        m_pDefaultPaperDimension = m_pPaperDimensions->getDefaultValue();
    if (m_pPaperDimensions == nullptr) {
        SAL_WARN( "vcl.unx.print""no PaperDimensions in " << m_aFile);
    }
    if (m_pDefaultPaperDimension == nullptr) {
        SAL_WARN( "vcl.unx.print""no DefaultPaperDimensions in " << m_aFile);
    }

    auto pResolutions = getKey( u"Resolution"_ustr );
    if( pResolutions )
        m_pDefaultResolution = pResolutions->getDefaultValue();
    if (pResolutions == nullptr) {
        SAL_INFO( "vcl.unx.print""no Resolution in " << m_aFile);
    }
    SAL_INFO_IF(!m_pDefaultResolution, "vcl.unx.print""no DefaultResolution in " + m_aFile);

    auto pInputSlots = getKey( u"InputSlot"_ustr );
    if( pInputSlots )
        m_pDefaultInputSlot = pInputSlots->getDefaultValue();
    SAL_INFO_IF(!pInputSlots, "vcl.unx.print""no InputSlot in " << m_aFile);
    SAL_INFO_IF(!m_pDefaultInputSlot, "vcl.unx.print""no DefaultInputSlot in " << m_aFile);
}

PPDParser::PPDParser( OUString aFile ) :
        m_aFile(std::move( aFile )),
        m_aFileEncoding( RTL_TEXTENCODING_MS_1252 ),
        m_pImageableAreas( nullptr ),
        m_pDefaultPaperDimension( nullptr ),
        m_pPaperDimensions( nullptr ),
        m_pDefaultInputSlot( nullptr ),
        m_pDefaultResolution( nullptr ),
        m_pTranslator( new PPDTranslator() )
{
    // read in the file
    std::vector< OString > aLines;
    PPDDecompressStream aStream( m_aFile );
    if( aStream.IsOpen() )
    {
        bool bLanguageEncoding = false;
        while( ! aStream.eof() )
        {
            OString aCurLine = aStream.ReadLine();
            if( aCurLine.startsWith("*") )
            {
                if (aCurLine.matchIgnoreAsciiCase("*include:"))
                {
                    aCurLine = aCurLine.copy(9);
                    aCurLine = comphelper::string::strip(aCurLine, ' ');
                    aCurLine = comphelper::string::strip(aCurLine, '\t');
                    aCurLine = comphelper::string::stripEnd(aCurLine, '\r');
                    aCurLine = comphelper::string::stripEnd(aCurLine, '\n');
                    aCurLine = comphelper::string::strip(aCurLine, '"');
                    aStream.Close();
                    aStream.Open(getPPDFile(OStringToOUString(aCurLine, m_aFileEncoding)));
                    continue;
                }
                else if( ! bLanguageEncoding &&
                         aCurLine.matchIgnoreAsciiCase("*languageencoding") )
                {
                    bLanguageEncoding = true// generally only the first one counts
                    OString aLower = aCurLine.toAsciiLowerCase();
                    if( aLower.indexOf("isolatin1", 17 ) != -1 ||
                        aLower.indexOf("windowsansi", 17 ) != -1 )
                        m_aFileEncoding = RTL_TEXTENCODING_MS_1252;
                    else if( aLower.indexOf("isolatin2", 17 ) != -1 )
                        m_aFileEncoding = RTL_TEXTENCODING_ISO_8859_2;
                    else if( aLower.indexOf("isolatin5", 17 ) != -1 )
                        m_aFileEncoding = RTL_TEXTENCODING_ISO_8859_5;
                    else if( aLower.indexOf("jis83-rksj", 17 ) != -1 )
                        m_aFileEncoding = RTL_TEXTENCODING_SHIFT_JIS;
                    else if( aLower.indexOf("macstandard", 17 ) != -1 )
                        m_aFileEncoding = RTL_TEXTENCODING_APPLE_ROMAN;
                    else if( aLower.indexOf("utf-8", 17 ) != -1 )
                        m_aFileEncoding = RTL_TEXTENCODING_UTF8;
                }
            }
            aLines.push_back( aCurLine );
        }
    }
    aStream.Close();

    // now get the Values
    parse( aLines );
#if OSL_DEBUG_LEVEL > 1
    SAL_INFO("vcl.unx.print""acquired " << m_aKeys.size()
            << " Keys from PPD " << m_aFile << ":");
    for (auto const& key : m_aKeys)
    {
        const PPDKey* pKey = key.second.get();
        char const* pSetupType = "";
        switch( pKey->m_eSetupType )
        {
            case PPDKey::SetupType::ExitServer:        pSetupType = "ExitServer";break;
            case PPDKey::SetupType::Prolog:            pSetupType = "Prolog";break;
            case PPDKey::SetupType::DocumentSetup:     pSetupType = "DocumentSetup";break;
            case PPDKey::SetupType::PageSetup:         pSetupType = "PageSetup";break;
            case PPDKey::SetupType::JCLSetup:          pSetupType = "JCLSetup";break;
            case PPDKey::SetupType::AnySetup:          pSetupType = "AnySetup";break;
            defaultbreak;
        }
        SAL_INFO("vcl.unx.print""\t\"" << pKey->getKey() << "\" ("
                << pKey->countValues() << "values) OrderDependency: "
                << pKey->m_nOrderDependency << pSetupType );
        forint j = 0; j < pKey->countValues(); j++ )
        {
            const PPDValue* pValue = pKey->getValue( j );
            char const* pVType = "";
            switch( pValue->m_eType )
            {
                case PPDValueType::Invocation:
                    pVType = "invocation";
                    break;
                case PPDValueType::Quoted:
                    pVType = "quoted";
                    break;
                case PPDValueType::String:
                    pVType = "string";
                    break;
                case PPDValueType::Symbol:
                    pVType = "symbol";
                    break;
                case PPDValueType::No:
                    pVType = "no";
                    break;
                defaultbreak;
            }
            SAL_INFO("vcl.unx.print""\t\t"
                << (pValue == pKey->m_pDefaultValue ? "(Default:) " : "")
                << "option: \"" << pValue->m_aOption
                << "\", value: type " << pVType << " \""
                << pValue->m_aValue << "\"");
        }
    }
    SAL_INFO("vcl.unx.print",
            "constraints: (" << m_aConstraints.size() << " found)");
    for (auto const& constraint : m_aConstraints)
    {
        SAL_INFO("vcl.unx.print""*\"" << constraint.m_pKey1->getKey() << "\" \""
                << (constraint.m_pOption1 ? constraint.m_pOption1->m_aOption : "")
                << "\" *\"" << constraint.m_pKey2->getKey() << "\" \""
                << (constraint.m_pOption2 ? constraint.m_pOption2->m_aOption : "")
                << "\"");
    }
#endif

    m_pImageableAreas = getKey( u"ImageableArea"_ustr );
    const PPDValue * pDefaultImageableArea = nullptr;
    if( m_pImageableAreas )
        pDefaultImageableArea = m_pImageableAreas->getDefaultValue();
    if (m_pImageableAreas == nullptr) {
        SAL_WARN( "vcl.unx.print""no ImageableArea in " << m_aFile);
    }
    if (pDefaultImageableArea == nullptr) {
        SAL_WARN( "vcl.unx.print""no DefaultImageableArea in " << m_aFile);
    }

    m_pPaperDimensions = getKey( u"PaperDimension"_ustr );
    if( m_pPaperDimensions )
        m_pDefaultPaperDimension = m_pPaperDimensions->getDefaultValue();
    if (m_pPaperDimensions == nullptr) {
        SAL_WARN( "vcl.unx.print""no PaperDimensions in " << m_aFile);
    }
    if (m_pDefaultPaperDimension == nullptr) {
        SAL_WARN( "vcl.unx.print""no DefaultPaperDimensions in " << m_aFile);
    }

    auto pResolutions = getKey( u"Resolution"_ustr );
    if( pResolutions )
        m_pDefaultResolution = pResolutions->getDefaultValue();
    if (pResolutions == nullptr) {
        SAL_INFO( "vcl.unx.print""no Resolution in " << m_aFile);
    }
    SAL_INFO_IF(!m_pDefaultResolution, "vcl.unx.print""no DefaultResolution in " + m_aFile);

    auto pInputSlots = getKey( u"InputSlot"_ustr );
    if( pInputSlots )
        m_pDefaultInputSlot = pInputSlots->getDefaultValue();
    SAL_INFO_IF(!pInputSlots, "vcl.unx.print""no InputSlot in " << m_aFile);
    SAL_INFO_IF(!m_pDefaultInputSlot, "vcl.unx.print""no DefaultInputSlot in " << m_aFile);
}

PPDParser::~PPDParser()
{
    m_pTranslator.reset();
}

void PPDParser::insertKey( std::unique_ptr<PPDKey> pKey )
{
    m_aOrderedKeys.push_back( pKey.get() );
    m_aKeys[ pKey->getKey() ] = std::move(pKey);
}

const PPDKey* PPDParser::getKey( int n ) const
{
    return (n >= 0 && o3tl::make_unsigned(n) < m_aOrderedKeys.size()) ? m_aOrderedKeys[n] : nullptr;
}

const PPDKey* PPDParser::getKey( const OUString& rKey ) const
{
    PPDParser::hash_type::const_iterator it = m_aKeys.find( rKey );
    return it != m_aKeys.end() ? it->second.get() : nullptr;
}

bool PPDParser::hasKey( const PPDKey* pKey ) const
{
    return pKey && ( m_aKeys.find( pKey->getKey() ) != m_aKeys.end() );
}

static sal_uInt8 getNibble( char cChar )
{
    sal_uInt8 nRet = 0;
    if( cChar >= '0' && cChar <= '9' )
        nRet = sal_uInt8( cChar - '0' );
    else if( cChar >= 'A' && cChar <= 'F' )
        nRet = 10 + sal_uInt8( cChar - 'A' );
    else if( cChar >= 'a' && cChar <= 'f' )
        nRet = 10 + sal_uInt8( cChar - 'a' );
    return nRet;
}

OUString PPDParser::handleTranslation(const OString& i_rString, bool bIsGlobalized)
{
    sal_Int32 nOrigLen = i_rString.getLength();
    OStringBuffer aTrans( nOrigLen );
    const char* pStr = i_rString.getStr();
    const char* pEnd = pStr + nOrigLen;
    while( pStr < pEnd )
    {
        if( *pStr == '<' )
        {
            pStr++;
            char cChar;
            while( *pStr != '>' && pStr < pEnd-1 )
            {
                cChar = getNibble( *pStr++ ) << 4;
                cChar |= getNibble( *pStr++ );
                aTrans.append( cChar );
            }
            pStr++;
        }
        else
            aTrans.append( *pStr++ );
    }
    return OStringToOUString( aTrans, bIsGlobalized ? RTL_TEXTENCODING_UTF8 : m_aFileEncoding );
}

namespace
{
    bool oddDoubleQuoteCount(OStringBuffer &rBuffer)
    {
        bool bHasOddCount = false;
        for (sal_Int32 i = 0; i < rBuffer.getLength(); ++i)
        {
            if (rBuffer[i] == '"')
                bHasOddCount = !bHasOddCount;
        }
        return bHasOddCount;
    }
}

void PPDParser::parse( ::std::vector< OString >& rLines )
{
    // Name for PPD group into which all options are put for which the PPD
    // does not explicitly define a group.
    // This is similar to how CUPS handles it,
    // s. Sweet, Michael R. (2001): Common UNIX Printing System, p. 251:
    // "Each option in turn is associated with a group stored in the
    // ppd_group_t structure. Groups can be specified in the PPD file; if an
    // option is not associated with a group, it is put in a "General" or
    // "Extra" group depending on the option.
    static constexpr OString aDefaultPPDGroupName("General"_ostr);

    std::vector< OString >::iterator line = rLines.begin();
    PPDParser::hash_type::const_iterator keyit;

    // name of the PPD group that is currently being processed
    OString aCurrentGroup = aDefaultPPDGroupName;

    while( line != rLines.end() )
    {
        OString aCurrentLine( *line );
        ++line;

        SAL_INFO("vcl.unx.print""Parse line '" << aCurrentLine << "'");

        if (aCurrentLine.getLength() < 2 || aCurrentLine[0] != '*')
            continue;
        if( aCurrentLine[1] == '%' )
            continue;

        OString aKey = GetCommandLineToken( 0, aCurrentLine.getToken(0, ':') );
        sal_Int32 nPos = aKey.indexOf('/');
        if (nPos != -1)
            aKey = aKey.copy(0, nPos);
        if(!aKey.isEmpty())
        {
            aKey = aKey.copy(1); // remove the '*'
        }
        if(aKey.isEmpty())
        {
            continue;
        }

        if (aKey == "CloseGroup")
        {
            aCurrentGroup = aDefaultPPDGroupName;
            continue;
        }
        if (aKey == "OpenGroup")
        {
            OString aGroupName = aCurrentLine;
            sal_Int32 nPosition = aGroupName.indexOf('/');
            if (nPosition != -1)
            {
                aGroupName = aGroupName.copy(0, nPosition);
            }

            aCurrentGroup = GetCommandLineToken(1, aGroupName);
            continue;
        }
        if ((aKey == "CloseUI") ||
            (aKey == "JCLCloseUI") ||
            (aKey == "End") ||
            (aKey == "JCLEnd") ||
            (aKey == "OpenSubGroup") ||
            (aKey == "CloseSubGroup"))
        {
            continue;
        }

        if ((aKey == "OpenUI") || (aKey == "JCLOpenUI"))
        {
            parseOpenUI( aCurrentLine, aCurrentGroup);
            continue;
        }
        else if (aKey == "OrderDependency")
        {
            parseOrderDependency( aCurrentLine );
            continue;
        }
        else if (aKey == "UIConstraints" ||
                 aKey == "NonUIConstraints")
        {
            continue// parsed in pass 2
        }
        else if( aKey == "CustomPageSize" ) // currently not handled
            continue;
        else if ( std::string_view rest; aKey.startsWith("Custom", &rest) )
        {
            //fdo#43049 very basic support for Custom entries, we ignore the
            //validation params and types
            OUString aUniKey(OStringToOUString(rest, RTL_TEXTENCODING_MS_1252));
            keyit = m_aKeys.find( aUniKey );
            if(keyit != m_aKeys.end())
            {
                PPDKey* pKey = keyit->second.get();
                pKey->insertValue(u"Custom"_ustr, PPDValueType::Invocation, true);
            }
            continue;
        }

        // default values are parsed in pass 2
        if (aKey.startsWith("Default"))
            continue;

        bool bQuery     = false;
        if (aKey[0] == '?')
        {
            aKey = aKey.copy(1);
            bQuery = true;
        }

        OUString aUniKey(OStringToOUString(aKey, RTL_TEXTENCODING_MS_1252));
        // handle CUPS extension for globalized PPDs
        /* FIXME-BCP47: really only ISO 639-1 two character language codes?
         * goodnight... */

        bool bIsGlobalizedLine = false;
        css::lang::Locale aTransLocale;
        if( ( aUniKey.getLength() > 3 && aUniKey[ 2 ] == '.' ) ||
            ( aUniKey.getLength() > 5 && aUniKey[ 2 ] == '_' && aUniKey[ 5 ] == '.' ) )
        {
            if( aUniKey[ 2 ] == '.' )
            {
                aTransLocale.Language = aUniKey.copy( 0, 2 );
                aUniKey = aUniKey.copy( 3 );
            }
            else
            {
                aTransLocale.Language = aUniKey.copy( 0, 2 );
                aTransLocale.Country = aUniKey.copy( 3, 2 );
                aUniKey = aUniKey.copy( 6 );
            }
            bIsGlobalizedLine = true;
        }

        OUString aOption;
        nPos = aCurrentLine.indexOf(':');
        if( nPos != -1 )
        {
            aOption = OStringToOUString(
                aCurrentLine.subView( 1, nPos-1 ), RTL_TEXTENCODING_MS_1252 );
            aOption = GetCommandLineToken( 1, aOption );
            sal_Int32 nTransPos = aOption.indexOf( '/' );
            if( nTransPos != -1 )
                aOption = aOption.copy(0,  nTransPos);
        }

        PPDValueType eType = PPDValueType::No;
        OUString aValue;
        OUString aOptionTranslation;
        OUString aValueTranslation;
        if( nPos != -1 )
        {
            // found a colon, there may be an option
            OString aLine = aCurrentLine.copy( 1, nPos-1 );
            aLine = WhitespaceToSpace( aLine );
            sal_Int32 nTransPos = aLine.indexOf('/');
            if (nTransPos != -1)
                aOptionTranslation = handleTranslation( aLine.copy(nTransPos+1), bIsGlobalizedLine );

            // read in more lines if necessary for multiline values
            aLine = aCurrentLine.copy( nPos+1 );
            if (!aLine.isEmpty())
            {
                OStringBuffer aBuffer(aLine);
                while (line != rLines.end() && oddDoubleQuoteCount(aBuffer))
                {
                    // copy the newlines also
                    aBuffer.append("\n" + *line);
                    ++line;
                }
                aLine = aBuffer.makeStringAndClear();
            }
            aLine = WhitespaceToSpace( aLine );

            // #i100644# handle a missing value (actually a broken PPD)
            if( aLine.isEmpty() )
            {
                if( !aOption.isEmpty() &&
                    !aUniKey.startsWith( "JCL" ) )
                    eType = PPDValueType::Invocation;
                else
                    eType = PPDValueType::Quoted;
            }
            // check for invocation or quoted value
            else if(aLine[0] == '"')
            {
                aLine = aLine.copy(1);
                nTransPos = aLine.indexOf('"');
                if (nTransPos == -1)
                    nTransPos = aLine.getLength();
                aValue = OStringToOUString(aLine.subView(0, nTransPos), RTL_TEXTENCODING_MS_1252);
                // after the second doublequote can follow a / and a translation
                if (nTransPos < aLine.getLength() - 2)
                {
                    aValueTranslation = handleTranslation( aLine.copy( nTransPos+2 ), bIsGlobalizedLine );
                }
                // check for quoted value
                if( !aOption.isEmpty() &&
                    !aUniKey.startsWith( "JCL" ) )
                    eType = PPDValueType::Invocation;
                else
                    eType = PPDValueType::Quoted;
            }
            // check for symbol value
            else if(aLine[0] == '^')
            {
                aLine = aLine.copy(1);
                aValue = OStringToOUString(aLine, RTL_TEXTENCODING_MS_1252);
                eType = PPDValueType::Symbol;
            }
            else
            {
                // must be a string value then
                // strictly this is false because string values
                // can contain any whitespace which is reduced
                // to one space by now
                // who cares ...
                nTransPos = aLine.indexOf('/');
                if (nTransPos == -1)
                    nTransPos = aLine.getLength();
                aValue = OStringToOUString(aLine.subView(0, nTransPos), RTL_TEXTENCODING_MS_1252);
                if (nTransPos+1 < aLine.getLength())
                    aValueTranslation = handleTranslation( aLine.copy( nTransPos+1 ), bIsGlobalizedLine );
                eType = PPDValueType::String;
            }
        }

        // handle globalized PPD entries
        if( bIsGlobalizedLine )
        {
            // handle main key translations of form:
            // *ll_CC.Translation MainKeyword/translated text: ""
            if( aUniKey == "Translation" )
            {
                m_pTranslator->insertKey( aOption, aOptionTranslation, aTransLocale );
            }
            // handle options translations of for:
            // *ll_CC.MainKeyword OptionKeyword/translated text: ""
            else
            {
                m_pTranslator->insertOption( aUniKey, aOption, aOptionTranslation, aTransLocale );
            }
            continue;
        }

        PPDKey* pKey = nullptr;
        keyit = m_aKeys.find( aUniKey );
        if( keyit == m_aKeys.end() )
        {
            pKey = new PPDKey( aUniKey );
            insertKey( std::unique_ptr<PPDKey>(pKey) );
        }
        else
            pKey = keyit->second.get();

        if (eType == PPDValueType::No && bQuery)
            continue;

        PPDValue* pValue = pKey->insertValue( aOption, eType );
        if( ! pValue )
            continue;
        pValue->m_aValue = aValue;

        if( !aOptionTranslation.isEmpty() )
            m_pTranslator->insertOption( aUniKey, aOption, aOptionTranslation, aTransLocale );
        if( !aValueTranslation.isEmpty() )
            m_pTranslator->insertValue( aUniKey, aOption, aValue, aValueTranslation, aTransLocale );

        // eventually update query and remove from option list
        if( bQuery && !pKey->m_bQueryValue )
        {
            pKey->m_bQueryValue = true;
            pKey->eraseValue( pValue->m_aOption );
        }
    }

    // second pass: fill in defaults
    forconst auto& aLine : rLines )
    {
        if (aLine.startsWith("*Default"))
        {
            SAL_INFO("vcl.unx.print""Found a default: '" << aLine << "'");
            OUString aKey(OStringToOUString(aLine.subView(8), RTL_TEXTENCODING_MS_1252));
            sal_Int32 nPos = aKey.indexOf( ':' );
            if( nPos != -1 )
            {
                aKey = aKey.copy(0, nPos);
                OUString aOption(OStringToOUString(
                    WhitespaceToSpace(aLine.subView(nPos+9)),
                    RTL_TEXTENCODING_MS_1252));
                keyit = m_aKeys.find( aKey );
                if( keyit != m_aKeys.end() )
                {
                    PPDKey* pKey = keyit->second.get();
                    const PPDValue* pDefValue = pKey->getValue( aOption );
                    if( pKey->m_pDefaultValue == nullptr )
                        pKey->m_pDefaultValue = pDefValue;
                }
                else
                {
                    // some PPDs contain defaults for keys that
                    // do not exist otherwise
                    // (example: DefaultResolution)
                    // so invent that key here and have a default value
                    std::unique_ptr<PPDKey> pKey(new PPDKey( aKey ));
                    pKey->insertValue(aOption, PPDValueType::Invocation /*or what ?*/);
                    pKey->m_pDefaultValue = pKey->getValue( aOption );
                    insertKey( std::move(pKey) );
                }
            }
        }
        else if (aLine.startsWith("*UIConstraints") ||
                 aLine.startsWith("*NonUIConstraints"))
        {
            parseConstraint( aLine );
        }
    }
}

void PPDParser::parseOpenUI(const OString& rLine, std::string_view rPPDGroup)
{
    OUString aTranslation;
    OString aKey = rLine;

    sal_Int32 nPos = aKey.indexOf(':');
    if( nPos != -1 )
        aKey = aKey.copy(0, nPos);
    nPos = aKey.indexOf('/');
    if( nPos != -1 )
    {
        aTranslation = handleTranslation( aKey.copy( nPos + 1 ), false );
        aKey = aKey.copy(0, nPos);
    }
    aKey = GetCommandLineToken( 1, aKey );
    aKey = aKey.copy(1);

    OUString aUniKey(OStringToOUString(aKey, RTL_TEXTENCODING_MS_1252));
    PPDParser::hash_type::const_iterator keyit = m_aKeys.find( aUniKey );
    PPDKey* pKey;
    if( keyit == m_aKeys.end() )
    {
        pKey = new PPDKey( aUniKey );
        insertKey( std::unique_ptr<PPDKey>(pKey) );
    }
    else
        pKey = keyit->second.get();

    pKey->m_bUIOption = true;
    m_pTranslator->insertKey( pKey->getKey(), aTranslation );

    pKey->m_aGroup = OStringToOUString(rPPDGroup, RTL_TEXTENCODING_MS_1252);
}

void PPDParser::parseOrderDependency(const OString& rLine)
{
    OString aLine(rLine);
    sal_Int32 nPos = aLine.indexOf(':');
    if( nPos != -1 )
        aLine = aLine.copy( nPos+1 );

    sal_Int32 nOrder = GetCommandLineToken( 0, aLine ).toInt32();
    OUString aKey(OStringToOUString(GetCommandLineToken(2, aLine), RTL_TEXTENCODING_MS_1252));
    if( aKey[ 0 ] != '*' )
        return// invalid order dependency
    aKey = aKey.replaceAt( 0, 1, u"" );

    PPDKey* pKey;
    PPDParser::hash_type::const_iterator keyit = m_aKeys.find( aKey );
    if( keyit == m_aKeys.end() )
    {
        pKey = new PPDKey( aKey );
        insertKey( std::unique_ptr<PPDKey>(pKey) );
    }
    else
        pKey = keyit->second.get();

    pKey->m_nOrderDependency = nOrder;
}

void PPDParser::parseConstraint( const OString& rLine )
{
    bool bFailed = false;

    OUString aLine(OStringToOUString(rLine, RTL_TEXTENCODING_MS_1252));
    sal_Int32 nIdx = rLine.indexOf(':');
    if (nIdx != -1)
        aLine = aLine.replaceAt(0, nIdx + 1, u"");
    PPDConstraint aConstraint;
    int nTokens = GetCommandLineTokenCount( aLine );
    forint i = 0; i < nTokens; i++ )
    {
        OUString aToken = GetCommandLineToken( i, aLine );
        if( !aToken.isEmpty() && aToken[ 0 ] == '*' )
        {
            aToken = aToken.replaceAt( 0, 1, u"" );
            if( aConstraint.m_pKey1 )
                aConstraint.m_pKey2 = getKey( aToken );
            else
                aConstraint.m_pKey1 = getKey( aToken );
        }
        else
        {
            if( aConstraint.m_pKey2 )
            {
                if( ! ( aConstraint.m_pOption2 = aConstraint.m_pKey2->getValue( aToken ) ) )
                    bFailed = true;
            }
            else if( aConstraint.m_pKey1 )
            {
                if( ! ( aConstraint.m_pOption1 = aConstraint.m_pKey1->getValue( aToken ) ) )
                    bFailed = true;
            }
            else
                // constraint for nonexistent keys; this happens
                // e.g. in HP4PLUS3
                bFailed = true;
        }
    }
    // there must be two keywords
    if( ! aConstraint.m_pKey1 || ! aConstraint.m_pKey2 || bFailed )
    {
        SAL_INFO("vcl.unx.print",
                "Warning: constraint \"" << rLine << "\" is invalid");
    }
    else
        m_aConstraints.push_back( aConstraint );
}

const OUString & PPDParser::getDefaultPaperDimension() const
{
    if( m_pDefaultPaperDimension )
        return m_pDefaultPaperDimension->m_aOption;

    return EMPTY_OUSTRING;
}

bool PPDParser::getMargins(
                           std::u16string_view rPaperName,
                           int& rLeft, int& rRight,
                           int& rUpper, int& rLower ) const
{
    if( ! m_pImageableAreas || ! m_pPaperDimensions )
        return false;

    int nPDim=-1, nImArea=-1, i;
    for( i = 0; i < m_pImageableAreas->countValues(); i++ )
        if( rPaperName == m_pImageableAreas->getValue( i )->m_aOption )
            nImArea = i;
    for( i = 0; i < m_pPaperDimensions->countValues(); i++ )
        if( rPaperName == m_pPaperDimensions->getValue( i )->m_aOption )
            nPDim = i;
    if( nPDim == -1 || nImArea == -1 )
        return false;

    double ImLLx, ImLLy, ImURx, ImURy;
    double PDWidth, PDHeight;
    OUString aArea = m_pImageableAreas->getValue( nImArea )->m_aValue;
    ImLLx = StringToDouble( GetCommandLineToken( 0, aArea ) );
    ImLLy = StringToDouble( GetCommandLineToken( 1, aArea ) );
    ImURx = StringToDouble( GetCommandLineToken( 2, aArea ) );
    ImURy = StringToDouble( GetCommandLineToken( 3, aArea ) );
    aArea = m_pPaperDimensions->getValue( nPDim )->m_aValue;
    PDWidth     = StringToDouble( GetCommandLineToken( 0, aArea ) );
    PDHeight    = StringToDouble( GetCommandLineToken( 1, aArea ) );
    rLeft  = static_cast<int>(ImLLx + 0.5);
    rLower = static_cast<int>(ImLLy + 0.5);
    rUpper = static_cast<int>(PDHeight - ImURy + 0.5);
    rRight = static_cast<int>(PDWidth - ImURx + 0.5);

    return true;
}

bool PPDParser::getPaperDimension(
                                  std::u16string_view rPaperName,
                                  int& rWidth, int& rHeight ) const
{
    if( ! m_pPaperDimensions )
        return false;

    int nPDim=-1;
    forint i = 0; i < m_pPaperDimensions->countValues(); i++ )
        if( rPaperName == m_pPaperDimensions->getValue( i )->m_aOption )
            nPDim = i;
    if( nPDim == -1 )
        return false;

    double PDWidth, PDHeight;
    OUString aArea = m_pPaperDimensions->getValue( nPDim )->m_aValue;
    PDWidth     = StringToDouble( GetCommandLineToken( 0, aArea ) );
    PDHeight    = StringToDouble( GetCommandLineToken( 1, aArea ) );
    rHeight = static_cast<int>(PDHeight + 0.5);
    rWidth  = static_cast<int>(PDWidth + 0.5);

    return true;
}

OUString PPDParser::matchPaperImpl(int nWidth, int nHeight, bool bSwapped, psp::orientation* pOrientation) const
{
    if( ! m_pPaperDimensions )
        return OUString();

    int nPDim = -1;
    double fSort = 2e36, fNewSort;

    forint i = 0; i < m_pPaperDimensions->countValues(); i++ )
    {
        OUString aArea =  m_pPaperDimensions->getValue( i )->m_aValue;
        double PDWidth     = StringToDouble( GetCommandLineToken( 0, aArea ) );
        double PDHeight    = StringToDouble( GetCommandLineToken( 1, aArea ) );
        PDWidth     /= static_cast<double>(nWidth);
        PDHeight    /= static_cast<double>(nHeight);
        if( PDWidth >= 0.9      &&  PDWidth <= 1.1      &&
            PDHeight >= 0.9     &&  PDHeight <= 1.1         )
        {
            fNewSort =
                (1.0-PDWidth)*(1.0-PDWidth) + (1.0-PDHeight)*(1.0-PDHeight);
            if( fNewSort == 0.0 ) // perfect match
                return m_pPaperDimensions->getValue( i )->m_aOption;

            if( fNewSort < fSort )
            {
                fSort = fNewSort;
                nPDim = i;
            }
        }
    }

    if (nPDim == -1 && !bSwapped)
    {
        // swap portrait/landscape and try again
        return matchPaperImpl(nHeight, nWidth, true, pOrientation);
    }

    if (nPDim == -1)
        return OUString();

    if (bSwapped && pOrientation)
    {
        switch (*pOrientation)
        {
            case psp::orientation::Portrait:
                *pOrientation = psp::orientation::Landscape;
            break;
            case psp::orientation::Landscape:
                *pOrientation = psp::orientation::Portrait;
            break;
        }
    }

    return m_pPaperDimensions->getValue( nPDim )->m_aOption;
}

OUString PPDParser::matchPaper(int nWidth, int nHeight, psp::orientation* pOrientationconst
{
    return matchPaperImpl(nHeight, nWidth, false, pOrientation);
}

const OUString & PPDParser::getDefaultInputSlot() const
{
    if( m_pDefaultInputSlot )
        return m_pDefaultInputSlot->m_aValue;
    return EMPTY_OUSTRING;
}

void PPDParser::getResolutionFromString(std::u16string_view rString,
                                        int& rXRes, int& rYRes )
{
    rXRes = rYRes = 300;

    const size_t nDPIPos {rString.find( u"dpi" )};
    if( nDPIPos != std::u16string_view::npos )
    {
        const size_t nPos {rString.find( 'x' )};
        if( nPos != std::u16string_view::npos )
        {
            rXRes = o3tl::toInt32(rString.substr( 0, nPos ));
            rYRes = o3tl::toInt32(rString.substr(nPos+1, nDPIPos - nPos - 1));
        }
        else
            rXRes = rYRes = o3tl::toInt32(rString.substr( 0, nDPIPos ));
    }
}

void PPDParser::getDefaultResolution( int& rXRes, int& rYRes ) const
{
    if( m_pDefaultResolution )
    {
        getResolutionFromString( m_pDefaultResolution->m_aValue, rXRes, rYRes );
        return;
    }

    rXRes = 300;
    rYRes = 300;
}

OUString PPDParser::translateKey( const OUString& i_rKey ) const
{
    OUString aResult( m_pTranslator->translateKey( i_rKey ) );
    if( aResult.isEmpty() )
        aResult = i_rKey;
    return aResult;
}

OUString PPDParser::translateOption( std::u16string_view i_rKey,
                                          const OUString& i_rOption ) const
{
    OUString aResult( m_pTranslator->translateOption( i_rKey, i_rOption ) );
    if( aResult.isEmpty() )
        aResult = i_rOption;
    return aResult;
}

/*
 *  PPDKey
 */


PPDKey::PPDKey( OUString aKey ) :
        m_aKey(std::move( aKey )),
        m_pDefaultValue( nullptr ),
        m_bQueryValue( false ),
        m_bUIOption( false ),
        m_nOrderDependency( 100 )
{
}

PPDKey::~PPDKey()
{
}

const PPDValue* PPDKey::getValue( int n ) const
{
    return (n >= 0 && o3tl::make_unsigned(n) < m_aOrderedValues.size()) ? m_aOrderedValues[n] : nullptr;
}

const PPDValue* PPDKey::getValue( const OUString& rOption ) const
{
    PPDKey::hash_type::const_iterator it = m_aValues.find( rOption );
    return it != m_aValues.end() ? &it->second : nullptr;
}

const PPDValue* PPDKey::getValueCaseInsensitive( const OUString& rOption ) const
{
    const PPDValue* pValue = getValue( rOption );
    if( ! pValue )
    {
        for( size_t n = 0; n < m_aOrderedValues.size() && ! pValue; n++ )
            if( m_aOrderedValues[n]->m_aOption.equalsIgnoreAsciiCase( rOption ) )
                pValue = m_aOrderedValues[n];
    }

    return pValue;
}

void PPDKey::eraseValue( const OUString& rOption )
{
    PPDKey::hash_type::iterator it = m_aValues.find( rOption );
    if( it == m_aValues.end() )
        return;

    auto vit = std::find(m_aOrderedValues.begin(), m_aOrderedValues.end(), &(it->second ));
    if( vit != m_aOrderedValues.end() )
        m_aOrderedValues.erase( vit );

    m_aValues.erase( it );
}

PPDValue* PPDKey::insertValue(const OUString& rOption, PPDValueType eType, bool bCustomOption)
{
    if( m_aValues.find( rOption ) != m_aValues.end() )
        return nullptr;

    PPDValue aValue;
    aValue.m_aOption = rOption;
    aValue.m_bCustomOption = bCustomOption;
    aValue.m_bCustomOptionSetViaApp = false;
    aValue.m_eType = eType;
    m_aValues[rOption] = std::move(aValue);
    PPDValue* pValue = &m_aValues[rOption];
    m_aOrderedValues.push_back( pValue );
    return pValue;
}

/*
 * PPDContext
 */


PPDContext::PPDContext() :
        m_pParser( nullptr )
{
}

PPDContext& PPDContext::operator=( PPDContext&& rCopy )
{
    std::swap(m_pParser, rCopy.m_pParser);
    std::swap(m_aCurrentValues, rCopy.m_aCurrentValues);
    return *this;
}

const PPDKey* PPDContext::getModifiedKey( std::size_t n ) const
{
    if( m_aCurrentValues.size() <= n )
        return nullptr;

    hash_type::const_iterator it = m_aCurrentValues.begin();
    std::advance(it, n);
    return it->first;
}

void PPDContext::setParser( const PPDParser* pParser )
{
    if( pParser != m_pParser )
    {
        m_aCurrentValues.clear();
        m_pParser = pParser;
    }
}

const PPDValue* PPDContext::getValue( const PPDKey* pKey ) const
{
    if( ! m_pParser )
        return nullptr;

    hash_type::const_iterator it = m_aCurrentValues.find( pKey );
    if( it != m_aCurrentValues.end() )
        return it->second;

    if( ! m_pParser->hasKey( pKey ) )
        return nullptr;

    const PPDValue* pValue = pKey->getDefaultValue();
    if( ! pValue )
        pValue = pKey->getValue( 0 );

    return pValue;
}

const PPDValue* PPDContext::setValue( const PPDKey* pKey, const PPDValue* pValue, bool bDontCareForConstraints )
{
    if( ! m_pParser || ! pKey )
        return nullptr;

    // pValue can be NULL - it means ignore this option

    if( ! m_pParser->hasKey( pKey ) )
        return nullptr;

    // check constraints
    if( pValue )
    {
        if( bDontCareForConstraints )
        {
            m_aCurrentValues[ pKey ] = pValue;
        }
        else if( checkConstraints( pKey, pValue, true ) )
        {
            m_aCurrentValues[ pKey ] = pValue;

            // after setting this value, check all constraints !
            hash_type::iterator it = m_aCurrentValues.begin();
            while(  it != m_aCurrentValues.end() )
            {
                if( it->first != pKey &&
                    ! checkConstraints( it->first, it->second, false ) )
                {
                    SAL_INFO("vcl.unx.print""PPDContext::setValue: option "
                         << it->first->getKey()
                         << " (" << it->second->m_aOption
                         << ") is constrained after setting "
                         << pKey->getKey()
                         << " to " << pValue->m_aOption);
                    resetValue( it->first, true );
                    it = m_aCurrentValues.begin();
                }
                else
                    ++it;
            }
        }
    }
    else
        m_aCurrentValues[ pKey ] = nullptr;

    return pValue;
}

bool PPDContext::checkConstraints( const PPDKey* pKey, const PPDValue* pValue )
{
    if( ! m_pParser || ! pKey || ! pValue )
        return false;

    // ensure that this key is already in the list if it exists at all
    if( m_aCurrentValues.find( pKey ) != m_aCurrentValues.end() )
        return checkConstraints( pKey, pValue, false );

    // it is not in the list, insert it temporarily
    bool bRet = false;
    if( m_pParser->hasKey( pKey ) )
    {
        const PPDValue* pDefValue = pKey->getDefaultValue();
        m_aCurrentValues[ pKey ] = pDefValue;
        bRet = checkConstraints( pKey, pValue, false );
        m_aCurrentValues.erase( pKey );
    }

    return bRet;
}

bool PPDContext::resetValue( const PPDKey* pKey, bool bDefaultable )
{
    if( ! pKey || ! m_pParser || ! m_pParser->hasKey( pKey ) )
        return false;

    const PPDValue* pResetValue = pKey->getValue( u"None"_ustr );
    if( ! pResetValue )
        pResetValue = pKey->getValue( u"False"_ustr );
    if( ! pResetValue && bDefaultable )
        pResetValue = pKey->getDefaultValue();

    bool bRet = pResetValue && ( setValue( pKey, pResetValue ) == pResetValue );

    return bRet;
}

bool PPDContext::checkConstraints( const PPDKey* pKey, const PPDValue* pNewValue, bool bDoReset )
{
    if( ! pNewValue )
        return true;

    // sanity checks
    if( ! m_pParser )
        return false;

    if( pKey->getValue( pNewValue->m_aOption ) != pNewValue )
        return false;

    // None / False and the default can always be set, but be careful !
    // setting them might influence constrained values
    if( pNewValue->m_aOption == "None" || pNewValue->m_aOption == "False" ||
        pNewValue == pKey->getDefaultValue() )
        return true;

    const ::std::vector< PPDParser::PPDConstraint >& rConstraints( m_pParser->getConstraints() );
    for (auto const& constraint : rConstraints)
    {
        const PPDKey* pLeft     = constraint.m_pKey1;
        const PPDKey* pRight    = constraint.m_pKey2;
        if( ! pLeft || ! pRight || ( pKey != pLeft && pKey != pRight ) )
            continue;

        const PPDKey* pOtherKey = pKey == pLeft ? pRight : pLeft;
        const PPDValue* pOtherKeyOption = pKey == pLeft ? constraint.m_pOption2 : constraint.m_pOption1;
        const PPDValue* pKeyOption = pKey == pLeft ? constraint.m_pOption1 : constraint.m_pOption2;

        // syntax *Key1 option1 *Key2 option2
        if( pKeyOption && pOtherKeyOption )
        {
            if( pNewValue != pKeyOption )
                continue;
            if( pOtherKeyOption == getValue( pOtherKey ) )
            {
                return false;
            }
        }
        // syntax *Key1 option *Key2  or  *Key1 *Key2 option
        else if( pOtherKeyOption || pKeyOption )
        {
            if( pKeyOption )
            {
                if( ! ( pOtherKeyOption = getValue( pOtherKey ) ) )
                    continue// this should not happen, PPD broken

                if( pKeyOption == pNewValue &&
                    pOtherKeyOption->m_aOption != "None" &&
                    pOtherKeyOption->m_aOption != "False" )
                {
                    // check if the other value can be reset and
                    // do so if possible
                    if( bDoReset && resetValue( pOtherKey ) )
                        continue;

                    return false;
                }
            }
            else if( pOtherKeyOption )
            {
                if( getValue( pOtherKey ) == pOtherKeyOption &&
                    pNewValue->m_aOption != "None" &&
                    pNewValue->m_aOption != "False" )
                    return false;
            }
            else
            {
                // this should not happen, PPD is broken
            }
        }
        // syntax *Key1 *Key2
        else
        {
            const PPDValue* pOtherValue = getValue( pOtherKey );
            if( pOtherValue->m_aOption != "None"  &&
                pOtherValue->m_aOption != "False" &&
                pNewValue->m_aOption != "None"    &&
                pNewValue->m_aOption != "False" )
                return false;
        }
    }
    return true;
}

char* PPDContext::getStreamableBuffer( sal_uLong& rBytes ) const
{
    rBytes = 0;
    if( m_aCurrentValues.empty() )
        return nullptr;
    for (auto const& elem : m_aCurrentValues)
    {
        OString aCopy(OUStringToOString(elem.first->getKey(), RTL_TEXTENCODING_MS_1252));
        rBytes += aCopy.getLength();
        rBytes += 1; // for ':'
        if( elem.second )
        {
            aCopy = OUStringToOString(elem.second->m_aOption, RTL_TEXTENCODING_MS_1252);
            rBytes += aCopy.getLength();
        }
        else
            rBytes += 4;
        rBytes += 1; // for '\0'
    }
    rBytes += 1;
    char* pBuffer = new char[ rBytes ];
    memset( pBuffer, 0, rBytes );
    char* pRun = pBuffer;
    for (auto const& elem : m_aCurrentValues)
    {
        OString aCopy(OUStringToOString(elem.first->getKey(), RTL_TEXTENCODING_MS_1252));
        int nBytes = aCopy.getLength();
        memcpy( pRun, aCopy.getStr(), nBytes );
        pRun += nBytes;
        *pRun++ = ':';
        if( elem.second )
            aCopy = OUStringToOString(elem.second->m_aOption, RTL_TEXTENCODING_MS_1252);
        else
            aCopy = "*nil"_ostr;
        nBytes = aCopy.getLength();
        memcpy( pRun, aCopy.getStr(), nBytes );
        pRun += nBytes;

        *pRun++ = 0;
    }
    return pBuffer;
}

void PPDContext::rebuildFromStreamBuffer(const std::vector<char> &rBuffer)
{
    if( ! m_pParser )
        return;

    m_aCurrentValues.clear();

    const size_t nBytes = rBuffer.size() - 1;
    size_t nRun = 0;
    while (nRun < nBytes && rBuffer[nRun])
    {
        OString aLine(rBuffer.data() + nRun);
        sal_Int32 nPos = aLine.indexOf(':');
        if( nPos != -1 )
        {
            const PPDKey* pKey = m_pParser->getKey( OStringToOUString( aLine.subView( 0, nPos ), RTL_TEXTENCODING_MS_1252 ) );
            if( pKey )
            {
                const PPDValue* pValue = nullptr;
                OUString aOption(
                    OStringToOUString(aLine.subView(nPos+1), RTL_TEXTENCODING_MS_1252));
                if (aOption != "*nil")
                    pValue = pKey->getValue( aOption );
                m_aCurrentValues[ pKey ] = pValue;
                SAL_INFO("vcl.unx.print",
                    "PPDContext::rebuildFromStreamBuffer: read PPDKeyValue { "
                    << pKey->getKey() << " , "
                    << (pValue ? aOption : u""_ustr)
                    << " }");
            }
        }
        nRun += aLine.getLength()+1;
    }
}

int PPDContext::getRenderResolution() const
{
    // initialize to reasonable default, if parser is not set
    int nDPI = 300;
    if( m_pParser )
    {
        int nDPIx = 300, nDPIy = 300;
        const PPDKey* pKey = m_pParser->getKey( u"Resolution"_ustr );
        if( pKey )
        {
            const PPDValue* pValue = getValue( pKey );
            if( pValue )
                PPDParser::getResolutionFromString( pValue->m_aOption, nDPIx, nDPIy );
            else
                m_pParser->getDefaultResolution( nDPIx, nDPIy );
        }
        else
            m_pParser->getDefaultResolution( nDPIx, nDPIy );

        nDPI = std::max(nDPIx, nDPIy);
    }
    return  nDPI;
}

void PPDContext::getPageSize( OUString& rPaper, int& rWidth, int& rHeight ) const
{
    // initialize to reasonable default, if parser is not set
    rPaper  = "A4";
    rWidth  = 595;
    rHeight = 842;
    if( !m_pParser )
        return;

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

--> maximum size reached

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

Messung V0.5
C=94 H=94 G=93

¤ Diese beiden folgenden Angebotsgruppen bietet das Unternehmen0.29Angebot  Wie Sie bei der Firma Beratungs- und Dienstleistungen beauftragen können  ¤

*Eine klare Vorstellung vom Zielzustand






Versionsinformation zu Columbo

Bemerkung:

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Anfrage:

Dauer der Verarbeitung:

Sekunden

sprechenden Kalenders