/* -*- 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()? */
for( int 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();
for( auto 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, eQuoted );
PPDValue* pPaperDimensionValue = pPaperDimensions -> insertValue( aValueName, eQuoted );
rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
OString o = OUStringToOString( aValueName, aEncoding );
pwg_media_t *pPWGMedia = pwgMediaForPWG(o.pData->buffer);
if (pPWGMedia != nullptr) {
OUStringBuffer aBuf( 256 );
aBuf = "0 0 " +
OUString::number(PWG_TO_POINTS(pPWGMedia -> width)) +
" " +
OUString::number(PWG_TO_POINTS(pPWGMedia -> length));
if ( pImageableAreaValue )
pImageableAreaValue->m_aValue = aBuf.makeStringAndClear();
aBuf.append( OUString::number(PWG_TO_POINTS(pPWGMedia -> width))
+ " "
+ OUString::number(PWG_TO_POINTS(pPWGMedia -> length) ));
if ( pPaperDimensionValue )
pPaperDimensionValue->m_aValue = aBuf.makeStringAndClear();
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 = "<unknown>";
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;
default: break;
}
SAL_INFO("vcl.unx.print", "\t\"" << pKey->getKey() << "\" ("
<< pKey->countValues() << "values) OrderDependency: "
<< pKey->m_nOrderDependency << pSetupType );
for( int j = 0; j < pKey->countValues(); j++ )
{
const PPDValue* pValue = pKey->getValue( j );
char const* pVType = "<unknown>";
switch( pValue->m_eType )
{
case eInvocation: pVType = "invocation";break;
case eQuoted: pVType = "quoted";break;
case eString: pVType = "string";break;
case eSymbol: pVType = "symbol";break;
case eNo: pVType = "no";break;
default: break;
}
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 : "<nil>")
<< "\" *\"" << constraint.m_pKey2->getKey() << "\" \""
<< (constraint.m_pOption2 ? constraint.m_pOption2->m_aOption : "<nil>")
<< "\"");
}
#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, eInvocation, 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 = eNo;
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 = eInvocation;
else
eType = eQuoted;
}
// 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 = eInvocation;
else
eType = eQuoted;
}
// check for symbol value
else if(aLine[0] == '^')
{
aLine = aLine.copy(1);
aValue = OStringToOUString(aLine, RTL_TEXTENCODING_MS_1252);
eType = eSymbol;
}
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 = eString;
}
}
// 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 == eNo && 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
for( const 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, eInvocation /*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 );
for( int 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;
for( int 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;
for( int 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* pOrientation) const
{
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"<nil>"_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;
const PPDKey* pKey = m_pParser->getKey( u"PageSize"_ustr );
if( !pKey )
return;
const PPDValue* pValue = getValue( pKey );
if( pValue )
{
rPaper = pValue->m_aOption;
m_pParser->getPaperDimension( rPaper, rWidth, rHeight );
}
else
{
rPaper = m_pParser->getDefaultPaperDimension();
m_pParser->getDefaultPaperDimension( rWidth, rHeight );
}
}
/* 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.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.