/* -*- 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if ENABLE_CPDB #include #endif #if ENABLE_CUPS #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if ENABLE_CUPS #include #endif #include #include #include 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 > aAllParsers; std::optional> 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 mpFileStream; std::unique_ptr 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(aLine[0]) != 0x1f || static_cast(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(static_cast(rMgr).createCUPSParser( aFile )); #endif } else if ( rMgr.getType() == PrinterInfoManager::Type::CPD ) { #if ENABLE_CPDB pNewParser = const_cast(static_cast(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 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& 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(key) ); } // fill in shortcuts const PPDKey* pKey; pKey = getKey( u"PageSize"_ustr ); if ( pKey ) { std::unique_ptr pImageableAreas(new PPDKey(u"ImageableArea"_ustr)); std::unique_ptr 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 = ""; 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 = ""; 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 : "") << "\" *\"" << 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 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(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 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(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(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(ImLLx + 0.5); rLower = static_cast(ImLLy + 0.5); rUpper = static_cast(PDHeight - ImURy + 0.5); rRight = static_cast(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(PDHeight + 0.5); rWidth = static_cast(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(nWidth); PDHeight /= static_cast(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 &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; 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: */