xref: /core/vcl/unx/generic/printer/ppdparser.cxx (revision e142fa40)
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
11  *   Licensed to the Apache Software Foundation (ASF) under one or more
12  *   contributor license agreements. See the NOTICE file distributed
13  *   with this work for additional information regarding copyright
14  *   ownership. The ASF licenses this file to you under the Apache
15  *   License, Version 2.0 (the "License"); you may not use this file
16  *   except in compliance with the License. You may obtain a copy of
17  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18  */
19 
20 #include <sal/config.h>
21 #include <config_cpdb.h>
22 
23 #include <stdlib.h>
24 
25 #include <comphelper/string.hxx>
26 #include <o3tl/string_view.hxx>
27 #include <i18nlangtag/languagetag.hxx>
28 #include <ppdparser.hxx>
29 #include <strhelper.hxx>
30 #include <utility>
31 #include <vcl/svapp.hxx>
32 #include <vcl/settings.hxx>
33 
34 #include <unx/helper.hxx>
35 #include <unx/cupsmgr.hxx>
36 
37 #if ENABLE_CPDB
38 #include <unx/cpdmgr.hxx>
39 #endif
40 
41 #include <tools/urlobj.hxx>
42 #include <tools/stream.hxx>
43 #include <tools/zcodec.hxx>
44 #include <o3tl/safeint.hxx>
45 #include <osl/file.hxx>
46 #include <osl/process.h>
47 #include <osl/thread.h>
48 #include <rtl/strbuf.hxx>
49 #include <rtl/ustrbuf.hxx>
50 #include <sal/log.hxx>
51 #include <salhelper/linkhelper.hxx>
52 
53 #include <com/sun/star/lang/Locale.hpp>
54 
55 #include <mutex>
56 #include <unordered_map>
57 
58 #ifdef ENABLE_CUPS
59 #include <cups/cups.h>
60 #endif
61 
62 #include <config_dbus.h>
63 #include <config_gio.h>
64 #include <o3tl/hash_combine.hxx>
65 
66 namespace psp
67 {
68     class PPDTranslator
69     {
70         struct LocaleEqual
71         {
operator ()psp::PPDTranslator::LocaleEqual72             bool operator()(const css::lang::Locale& i_rLeft,
73                             const css::lang::Locale& i_rRight) const
74             {
75                 return i_rLeft.Language == i_rRight.Language &&
76                     i_rLeft.Country == i_rRight.Country &&
77                     i_rLeft.Variant == i_rRight.Variant;
78             }
79         };
80 
81         struct LocaleHash
82         {
operator ()psp::PPDTranslator::LocaleHash83             size_t operator()(const css::lang::Locale& rLocale) const
84             {
85                 std::size_t seed = 0;
86                 o3tl::hash_combine(seed, rLocale.Language.hashCode());
87                 o3tl::hash_combine(seed, rLocale.Country.hashCode());
88                 o3tl::hash_combine(seed, rLocale.Variant.hashCode());
89                 return seed;
90             }
91         };
92 
93         typedef std::unordered_map< css::lang::Locale, OUString, LocaleHash, LocaleEqual > translation_map;
94         typedef std::unordered_map< OUString, translation_map > key_translation_map;
95 
96         key_translation_map     m_aTranslations;
97         public:
PPDTranslator()98         PPDTranslator() {}
99 
100         void insertValue(
101             std::u16string_view i_rKey,
102             std::u16string_view i_rOption,
103             std::u16string_view i_rValue,
104             const OUString& i_rTranslation,
105             const css::lang::Locale& i_rLocale
106             );
107 
insertOption(std::u16string_view i_rKey,std::u16string_view i_rOption,const OUString & i_rTranslation,const css::lang::Locale & i_rLocale)108         void insertOption( std::u16string_view i_rKey,
109                            std::u16string_view i_rOption,
110                            const OUString& i_rTranslation,
111                            const css::lang::Locale& i_rLocale )
112         {
113             insertValue( i_rKey, i_rOption, u"", i_rTranslation, i_rLocale );
114         }
115 
insertKey(std::u16string_view i_rKey,const OUString & i_rTranslation,const css::lang::Locale & i_rLocale=css::lang::Locale ())116         void insertKey( std::u16string_view i_rKey,
117                         const OUString& i_rTranslation,
118                         const css::lang::Locale& i_rLocale = css::lang::Locale() )
119         {
120             insertValue( i_rKey, u"", u"", i_rTranslation, i_rLocale );
121         }
122 
123         OUString translateValue(
124             std::u16string_view i_rKey,
125             std::u16string_view i_rOption
126             ) const;
127 
translateOption(std::u16string_view i_rKey,std::u16string_view i_rOption) const128         OUString translateOption( std::u16string_view i_rKey,
129                                        std::u16string_view i_rOption ) const
130         {
131             return translateValue( i_rKey, i_rOption  );
132         }
133 
translateKey(std::u16string_view i_rKey) const134         OUString translateKey( std::u16string_view i_rKey ) const
135         {
136             return translateValue( i_rKey, u"" );
137         }
138     };
139 
normalizeInputLocale(const css::lang::Locale & i_rLocale)140     static css::lang::Locale normalizeInputLocale(
141         const css::lang::Locale& i_rLocale
142         )
143     {
144         css::lang::Locale aLoc( i_rLocale );
145         if( aLoc.Language.isEmpty() )
146         {
147             // empty locale requested, fill in application UI locale
148             aLoc = Application::GetSettings().GetUILanguageTag().getLocale();
149 
150             #if OSL_DEBUG_LEVEL > 1
151             static const char* pEnvLocale = getenv( "SAL_PPDPARSER_LOCALE" );
152             if( pEnvLocale && *pEnvLocale )
153             {
154                 OString aStr( pEnvLocale );
155                 sal_Int32 nLen = aStr.getLength();
156                 aLoc.Language = OStringToOUString( aStr.copy( 0, std::min(nLen, 2) ), RTL_TEXTENCODING_MS_1252 );
157                 if( nLen >=5 && aStr[2] == '_' )
158                     aLoc.Country = OStringToOUString( aStr.copy( 3, 2 ), RTL_TEXTENCODING_MS_1252 );
159                 else
160                     aLoc.Country.clear();
161                 aLoc.Variant.clear();
162             }
163             #endif
164         }
165         /* FIXME-BCP47: using Variant, uppercase? */
166         aLoc.Language = aLoc.Language.toAsciiLowerCase();
167         aLoc.Country  = aLoc.Country.toAsciiUpperCase();
168         aLoc.Variant  = aLoc.Variant.toAsciiUpperCase();
169 
170         return aLoc;
171     }
172 
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)173     void PPDTranslator::insertValue(
174         std::u16string_view i_rKey,
175         std::u16string_view i_rOption,
176         std::u16string_view i_rValue,
177         const OUString& i_rTranslation,
178         const css::lang::Locale& i_rLocale
179         )
180     {
181         OUStringBuffer aKey( i_rKey.size() + i_rOption.size() + i_rValue.size() + 2 );
182         aKey.append( i_rKey );
183         if( !i_rOption.empty() || !i_rValue.empty() )
184         {
185             aKey.append( OUString::Concat(":") + i_rOption );
186         }
187         if( !i_rValue.empty() )
188         {
189             aKey.append( OUString::Concat(":") + i_rValue );
190         }
191         if( !aKey.isEmpty() && !i_rTranslation.isEmpty() )
192         {
193             OUString aK( aKey.makeStringAndClear() );
194             css::lang::Locale aLoc;
195             /* FIXME-BCP47: using Variant, uppercase? */
196             aLoc.Language = i_rLocale.Language.toAsciiLowerCase();
197             aLoc.Country  = i_rLocale.Country.toAsciiUpperCase();
198             aLoc.Variant  = i_rLocale.Variant.toAsciiUpperCase();
199             m_aTranslations[ aK ][ aLoc ] = i_rTranslation;
200         }
201     }
202 
translateValue(std::u16string_view i_rKey,std::u16string_view i_rOption) const203     OUString PPDTranslator::translateValue(
204         std::u16string_view i_rKey,
205         std::u16string_view i_rOption
206         ) const
207     {
208         OUString aResult;
209 
210         OUStringBuffer aKey( i_rKey.size() + i_rOption.size() + 2 );
211         aKey.append( i_rKey );
212         if( !i_rOption.empty() )
213         {
214             aKey.append( OUString::Concat(":") + i_rOption );
215         }
216         if( !aKey.isEmpty() )
217         {
218             OUString aK( aKey.makeStringAndClear() );
219             key_translation_map::const_iterator it = m_aTranslations.find( aK );
220             if( it != m_aTranslations.end() )
221             {
222                 const translation_map& rMap( it->second );
223 
224                 css::lang::Locale aLoc( normalizeInputLocale( css::lang::Locale() ) );
225                 /* FIXME-BCP47: use LanguageTag::getFallbackStrings()? */
226                 for( int nTry = 0; nTry < 4; nTry++ )
227                 {
228                     translation_map::const_iterator tr = rMap.find( aLoc );
229                     if( tr != rMap.end() )
230                     {
231                         aResult = tr->second;
232                         break;
233                     }
234                     switch( nTry )
235                     {
236                     case 0: aLoc.Variant.clear();break;
237                     case 1: aLoc.Country.clear();break;
238                     case 2: aLoc.Language.clear();break;
239                     }
240                 }
241             }
242         }
243         return aResult;
244     }
245 
246     class PPDCache
247     {
248     public:
249         std::vector< std::unique_ptr<PPDParser> > aAllParsers;
250         std::optional<std::unordered_map< OUString, OUString >> xAllPPDFiles;
251     };
252 }
253 
254 using namespace psp;
255 
256 namespace
257 {
getPPDCache()258     PPDCache& getPPDCache()
259     {
260         static PPDCache thePPDCache;
261         return thePPDCache;
262     }
263 
264 class PPDDecompressStream
265 {
266 private:
267     PPDDecompressStream(const PPDDecompressStream&) = delete;
268     PPDDecompressStream& operator=(const PPDDecompressStream&) = delete;
269 
270     std::unique_ptr<SvFileStream>   mpFileStream;
271     std::unique_ptr<SvMemoryStream> mpMemStream;
272     OUString       maFileName;
273 
274 public:
275     explicit PPDDecompressStream( const OUString& rFile );
276     ~PPDDecompressStream();
277 
278     bool IsOpen() const;
279     bool eof() const;
280     OString ReadLine();
281     void Open( const OUString& i_rFile );
282     void Close();
GetFileName() const283     const OUString& GetFileName() const { return maFileName; }
284 };
285 
286 }
287 
PPDDecompressStream(const OUString & i_rFile)288 PPDDecompressStream::PPDDecompressStream( const OUString& i_rFile )
289 {
290     Open( i_rFile );
291 }
292 
~PPDDecompressStream()293 PPDDecompressStream::~PPDDecompressStream()
294 {
295     Close();
296 }
297 
Open(const OUString & i_rFile)298 void PPDDecompressStream::Open( const OUString& i_rFile )
299 {
300     Close();
301 
302     mpFileStream.reset( new SvFileStream( i_rFile, StreamMode::READ ) );
303     maFileName = mpFileStream->GetFileName();
304 
305     if( ! mpFileStream->IsOpen() )
306     {
307         Close();
308         return;
309     }
310 
311     OString aLine;
312     mpFileStream->ReadLine( aLine );
313     mpFileStream->Seek( 0 );
314 
315     // check for compress'ed or gzip'ed file
316     if( aLine.getLength() <= 1 ||
317         static_cast<unsigned char>(aLine[0]) != 0x1f ||
318         static_cast<unsigned char>(aLine[1]) != 0x8b /* check for gzip */ )
319         return;
320 
321     // so let's try to decompress the stream
322     mpMemStream.reset( new SvMemoryStream( 4096, 4096 ) );
323     ZCodec aCodec;
324     aCodec.BeginCompression( ZCODEC_DEFAULT_COMPRESSION, /*gzLib*/true );
325     tools::Long nComp = aCodec.Decompress( *mpFileStream, *mpMemStream );
326     aCodec.EndCompression();
327     if( nComp < 0 )
328     {
329         // decompression failed, must be an uncompressed stream after all
330         mpMemStream.reset();
331         mpFileStream->Seek( 0 );
332     }
333     else
334     {
335         // compression successful, can get rid of file stream
336         mpFileStream.reset();
337         mpMemStream->Seek( 0 );
338     }
339 }
340 
Close()341 void PPDDecompressStream::Close()
342 {
343     mpMemStream.reset();
344     mpFileStream.reset();
345 }
346 
IsOpen() const347 bool PPDDecompressStream::IsOpen() const
348 {
349     return (mpMemStream || (mpFileStream && mpFileStream->IsOpen()));
350 }
351 
eof() const352 bool PPDDecompressStream::eof() const
353 {
354     return ( mpMemStream ? mpMemStream->eof() : ( mpFileStream == nullptr || mpFileStream->eof() ) );
355 }
356 
ReadLine()357 OString PPDDecompressStream::ReadLine()
358 {
359     OString o_rLine;
360     if( mpMemStream )
361         mpMemStream->ReadLine( o_rLine );
362     else if( mpFileStream )
363         mpFileStream->ReadLine( o_rLine );
364     return o_rLine;
365 }
366 
resolveLink(const OUString & i_rURL,OUString & o_rResolvedURL,OUString & o_rBaseName,osl::FileStatus::Type & o_rType)367 static osl::FileBase::RC resolveLink( const OUString& i_rURL, OUString& o_rResolvedURL, OUString& o_rBaseName, osl::FileStatus::Type& o_rType)
368 {
369     salhelper::LinkResolver aResolver(osl_FileStatus_Mask_FileName |
370                                       osl_FileStatus_Mask_Type |
371                                       osl_FileStatus_Mask_FileURL);
372 
373     osl::FileBase::RC aRet = aResolver.fetchFileStatus(i_rURL, 10/*nLinkLevel*/);
374 
375     if (aRet  == osl::FileBase::E_None)
376     {
377         o_rResolvedURL = aResolver.m_aStatus.getFileURL();
378         o_rBaseName = aResolver.m_aStatus.getFileName();
379         o_rType = aResolver.m_aStatus.getFileType();
380     }
381 
382     return aRet;
383 }
384 
scanPPDDir(const OUString & rDir)385 void PPDParser::scanPPDDir( const OUString& rDir )
386 {
387     static struct suffix_t
388     {
389         const char* pSuffix;
390         const sal_Int32 nSuffixLen;
391     } const pSuffixes[] =
392     { { ".PS", 3 },  { ".PPD", 4 }, { ".PS.GZ", 6 }, { ".PPD.GZ", 7 } };
393 
394     PPDCache &rPPDCache = getPPDCache();
395 
396     osl::Directory aDir( rDir );
397     if ( aDir.open() != osl::FileBase::E_None )
398         return;
399 
400     osl::DirectoryItem aItem;
401 
402     INetURLObject aPPDDir(rDir);
403     while( aDir.getNextItem( aItem ) == osl::FileBase::E_None )
404     {
405         osl::FileStatus aStatus( osl_FileStatus_Mask_FileName );
406         if( aItem.getFileStatus( aStatus ) == osl::FileBase::E_None )
407         {
408             OUString aFileURL, aFileName;
409             osl::FileStatus::Type eType = osl::FileStatus::Unknown;
410             OUString aURL = rDir + "/" + aStatus.getFileName();
411 
412             if(resolveLink( aURL, aFileURL, aFileName, eType ) == osl::FileBase::E_None)
413             {
414                 if( eType == osl::FileStatus::Regular )
415                 {
416                     INetURLObject aPPDFile = aPPDDir;
417                     aPPDFile.Append( aFileName );
418 
419                     // match extension
420                     for(const suffix_t & rSuffix : pSuffixes)
421                     {
422                         if( aFileName.getLength() > rSuffix.nSuffixLen )
423                         {
424                             if( aFileName.endsWithIgnoreAsciiCaseAsciiL( rSuffix.pSuffix, rSuffix.nSuffixLen ) )
425                             {
426                                 (*rPPDCache.xAllPPDFiles)[ aFileName.copy( 0, aFileName.getLength() - rSuffix.nSuffixLen ) ] = aPPDFile.PathToFileName();
427                                 break;
428                             }
429                         }
430                     }
431                 }
432                 else if( eType == osl::FileStatus::Directory )
433                 {
434                     scanPPDDir( aFileURL );
435                 }
436             }
437         }
438     }
439     aDir.close();
440 }
441 
initPPDFiles(PPDCache & rPPDCache)442 void PPDParser::initPPDFiles(PPDCache &rPPDCache)
443 {
444     if( rPPDCache.xAllPPDFiles )
445         return;
446 
447     rPPDCache.xAllPPDFiles.emplace();
448 
449     // check installation directories
450     std::vector< OUString > aPathList;
451     psp::getPrinterPathList( aPathList, PRINTER_PPDDIR );
452     for (auto const& path : aPathList)
453     {
454         INetURLObject aPPDDir( path, INetProtocol::File, INetURLObject::EncodeMechanism::All );
455         scanPPDDir( aPPDDir.GetMainURL( INetURLObject::DecodeMechanism::NONE ) );
456     }
457     if( rPPDCache.xAllPPDFiles->find( u"SGENPRT"_ustr ) != rPPDCache.xAllPPDFiles->end() )
458         return;
459 
460     // last try: search in directory of executable (mainly for setup)
461     OUString aExe;
462     if( osl_getExecutableFile( &aExe.pData ) == osl_Process_E_None )
463     {
464         INetURLObject aDir( aExe );
465         aDir.removeSegment();
466         SAL_INFO("vcl.unx.print", "scanning last chance dir: "
467                 << aDir.GetMainURL(INetURLObject::DecodeMechanism::NONE));
468         scanPPDDir( aDir.GetMainURL( INetURLObject::DecodeMechanism::NONE ) );
469         SAL_INFO("vcl.unx.print", "SGENPRT "
470                 << (rPPDCache.xAllPPDFiles->find(u"SGENPRT"_ustr) ==
471                     rPPDCache.xAllPPDFiles->end() ? "not found" : "found"));
472     }
473 }
474 
getPPDFile(const OUString & rFile)475 OUString PPDParser::getPPDFile( const OUString& rFile )
476 {
477     INetURLObject aPPD( rFile, INetProtocol::File, INetURLObject::EncodeMechanism::All );
478     // someone might enter a full qualified name here
479     PPDDecompressStream aStream( aPPD.PathToFileName() );
480     if( ! aStream.IsOpen() )
481     {
482         std::unordered_map< OUString, OUString >::const_iterator it;
483         PPDCache &rPPDCache = getPPDCache();
484 
485         bool bRetry = true;
486         do
487         {
488             initPPDFiles(rPPDCache);
489             // some PPD files contain dots beside the extension, so try name first
490             // and cut of points after that
491             OUString aBase( rFile );
492             sal_Int32 nLastIndex = aBase.lastIndexOf( '/' );
493             if( nLastIndex >= 0 )
494                 aBase = aBase.copy( nLastIndex+1 );
495             do
496             {
497                 it = rPPDCache.xAllPPDFiles->find( aBase );
498                 nLastIndex = aBase.lastIndexOf( '.' );
499                 if( nLastIndex > 0 )
500                     aBase = aBase.copy( 0, nLastIndex );
501             } while( it == rPPDCache.xAllPPDFiles->end() && nLastIndex > 0 );
502 
503             if( it == rPPDCache.xAllPPDFiles->end() && bRetry )
504             {
505                 // a new file ? rehash
506                 rPPDCache.xAllPPDFiles.reset();
507                 bRetry = false;
508                 // note this is optimized for office start where
509                 // no new files occur and initPPDFiles is called only once
510             }
511         } while( ! rPPDCache.xAllPPDFiles );
512 
513         if( it != rPPDCache.xAllPPDFiles->end() )
514             aStream.Open( it->second );
515     }
516 
517     OUString aRet;
518     if( aStream.IsOpen() )
519     {
520         OString aLine = aStream.ReadLine();
521         if (aLine.startsWith("*PPD-Adobe"))
522             aRet = aStream.GetFileName();
523         else
524         {
525             // our *Include hack does usually not begin
526             // with *PPD-Adobe, so try some lines for *Include
527             int nLines = 10;
528             while (aLine.indexOf("*Include") != 0 && --nLines)
529                 aLine = aStream.ReadLine();
530             if( nLines )
531                 aRet = aStream.GetFileName();
532         }
533     }
534 
535     return aRet;
536 }
537 
getParser(const OUString & rFile)538 const PPDParser* PPDParser::getParser( const OUString& rFile )
539 {
540     // Recursive because we can get re-entered via CUPSManager::createCUPSParser
541     static std::recursive_mutex aMutex;
542     std::scoped_lock aGuard( aMutex );
543 
544     OUString aFile = rFile;
545     if( !rFile.startsWith( "CUPS:" ) && !rFile.startsWith( "CPD:" ) )
546         aFile = getPPDFile( rFile );
547     if( aFile.isEmpty() )
548     {
549         SAL_INFO("vcl.unx.print", "Could not get printer PPD file \""
550                 << rFile << "\" !");
551         return nullptr;
552     }
553     else
554         SAL_INFO("vcl.unx.print", "Parsing printer info from \""
555                  << rFile << "\" !");
556 
557 
558     PPDCache &rPPDCache = getPPDCache();
559     for( auto const & i : rPPDCache.aAllParsers )
560         if( i->m_aFile == aFile )
561             return i.get();
562 
563     PPDParser* pNewParser = nullptr;
564     if( !aFile.startsWith( "CUPS:" ) && !aFile.startsWith( "CPD:" ) )
565         pNewParser = new PPDParser( aFile );
566     else
567     {
568         PrinterInfoManager& rMgr = PrinterInfoManager::get();
569         if( rMgr.getType() == PrinterInfoManager::Type::CUPS )
570         {
571 #ifdef ENABLE_CUPS
572             pNewParser = const_cast<PPDParser*>(static_cast<CUPSManager&>(rMgr).createCUPSParser( aFile ));
573 #endif
574         } else if ( rMgr.getType() == PrinterInfoManager::Type::CPD )
575         {
576 #if ENABLE_CPDB
577             pNewParser = const_cast<PPDParser*>(static_cast<CPDManager&>(rMgr).createCPDParser( aFile ));
578 #endif
579         }
580     }
581     if( pNewParser )
582     {
583         // this may actually be the SGENPRT parser,
584         // so ensure uniqueness here (but don't remove last we delete us!)
585         if (std::none_of(
586                     rPPDCache.aAllParsers.begin(),
587                     rPPDCache.aAllParsers.end(),
588                     [pNewParser] (std::unique_ptr<PPDParser> const & x) { return x.get() == pNewParser; } ))
589         {
590             // insert new parser to vector
591             rPPDCache.aAllParsers.emplace_back(pNewParser);
592         }
593     }
594     return pNewParser;
595 }
596 
PPDParser(OUString aFile,const std::vector<PPDKey * > & keys)597 PPDParser::PPDParser(OUString aFile, const std::vector<PPDKey*>& keys)
598     : m_aFile(std::move(aFile))
599     , m_aFileEncoding(RTL_TEXTENCODING_MS_1252)
600     , m_pImageableAreas(nullptr)
601     , m_pDefaultPaperDimension(nullptr)
602     , m_pPaperDimensions(nullptr)
603     , m_pDefaultInputSlot(nullptr)
604     , m_pDefaultResolution(nullptr)
605     , m_pTranslator(new PPDTranslator())
606 {
607     for (auto & key: keys)
608     {
609         insertKey( std::unique_ptr<PPDKey>(key) );
610     }
611 
612     // fill in shortcuts
613     const PPDKey* pKey;
614 
615     pKey = getKey( u"PageSize"_ustr );
616 
617     if ( pKey ) {
618         std::unique_ptr<PPDKey> pImageableAreas(new PPDKey(u"ImageableArea"_ustr));
619         std::unique_ptr<PPDKey> pPaperDimensions(new PPDKey(u"PaperDimension"_ustr));
620 #if defined(CUPS_VERSION_MAJOR)
621 #if (CUPS_VERSION_MAJOR == 1 && CUPS_VERSION_MINOR >= 7) || CUPS_VERSION_MAJOR > 1
622         for (int i = 0; i < pKey->countValues(); i++) {
623             const PPDValue* pValue = pKey -> getValue(i);
624             OUString aValueName = pValue -> m_aOption;
625             PPDValue* pImageableAreaValue = pImageableAreas -> insertValue( aValueName, eQuoted );
626             PPDValue* pPaperDimensionValue = pPaperDimensions -> insertValue( aValueName, eQuoted );
627             rtl_TextEncoding aEncoding = osl_getThreadTextEncoding();
628             OString o = OUStringToOString( aValueName, aEncoding );
629             pwg_media_t *pPWGMedia = pwgMediaForPWG(o.pData->buffer);
630             if (pPWGMedia != nullptr) {
631                 OUStringBuffer aBuf( 256 );
632                 aBuf = "0 0 " +
633                     OUString::number(PWG_TO_POINTS(pPWGMedia -> width)) +
634                     " " +
635                     OUString::number(PWG_TO_POINTS(pPWGMedia -> length));
636                 if ( pImageableAreaValue )
637                     pImageableAreaValue->m_aValue = aBuf.makeStringAndClear();
638                 aBuf.append( OUString::number(PWG_TO_POINTS(pPWGMedia -> width))
639                     + " "
640                     + OUString::number(PWG_TO_POINTS(pPWGMedia -> length) ));
641                 if ( pPaperDimensionValue )
642                     pPaperDimensionValue->m_aValue = aBuf.makeStringAndClear();
643                 if (aValueName.equals(pKey -> getDefaultValue() -> m_aOption)) {
644                     pImageableAreas -> m_pDefaultValue = pImageableAreaValue;
645                     pPaperDimensions -> m_pDefaultValue = pPaperDimensionValue;
646                 }
647             }
648         }
649 #endif // HAVE_CUPS_API_1_7
650 #endif
651         insertKey(std::move(pImageableAreas));
652         insertKey(std::move(pPaperDimensions));
653     }
654 
655     m_pImageableAreas = getKey( u"ImageableArea"_ustr );
656     const PPDValue* pDefaultImageableArea = nullptr;
657     if( m_pImageableAreas )
658         pDefaultImageableArea = m_pImageableAreas->getDefaultValue();
659     if (m_pImageableAreas == nullptr) {
660         SAL_WARN( "vcl.unx.print", "no ImageableArea in " << m_aFile);
661     }
662     if (pDefaultImageableArea == nullptr) {
663         SAL_WARN( "vcl.unx.print", "no DefaultImageableArea in " << m_aFile);
664     }
665 
666     m_pPaperDimensions = getKey( u"PaperDimension"_ustr );
667     if( m_pPaperDimensions )
668         m_pDefaultPaperDimension = m_pPaperDimensions->getDefaultValue();
669     if (m_pPaperDimensions == nullptr) {
670         SAL_WARN( "vcl.unx.print", "no PaperDimensions in " << m_aFile);
671     }
672     if (m_pDefaultPaperDimension == nullptr) {
673         SAL_WARN( "vcl.unx.print", "no DefaultPaperDimensions in " << m_aFile);
674     }
675 
676     auto pResolutions = getKey( u"Resolution"_ustr );
677     if( pResolutions )
678         m_pDefaultResolution = pResolutions->getDefaultValue();
679     if (pResolutions == nullptr) {
680         SAL_INFO( "vcl.unx.print", "no Resolution in " << m_aFile);
681     }
682     SAL_INFO_IF(!m_pDefaultResolution, "vcl.unx.print", "no DefaultResolution in " + m_aFile);
683 
684     auto pInputSlots = getKey( u"InputSlot"_ustr );
685     if( pInputSlots )
686         m_pDefaultInputSlot = pInputSlots->getDefaultValue();
687     SAL_INFO_IF(!pInputSlots, "vcl.unx.print", "no InputSlot in " << m_aFile);
688     SAL_INFO_IF(!m_pDefaultInputSlot, "vcl.unx.print", "no DefaultInputSlot in " << m_aFile);
689 }
690 
PPDParser(OUString aFile)691 PPDParser::PPDParser( OUString aFile ) :
692         m_aFile(std::move( aFile )),
693         m_aFileEncoding( RTL_TEXTENCODING_MS_1252 ),
694         m_pImageableAreas( nullptr ),
695         m_pDefaultPaperDimension( nullptr ),
696         m_pPaperDimensions( nullptr ),
697         m_pDefaultInputSlot( nullptr ),
698         m_pDefaultResolution( nullptr ),
699         m_pTranslator( new PPDTranslator() )
700 {
701     // read in the file
702     std::vector< OString > aLines;
703     PPDDecompressStream aStream( m_aFile );
704     if( aStream.IsOpen() )
705     {
706         bool bLanguageEncoding = false;
707         while( ! aStream.eof() )
708         {
709             OString aCurLine = aStream.ReadLine();
710             if( aCurLine.startsWith("*") )
711             {
712                 if (aCurLine.matchIgnoreAsciiCase("*include:"))
713                 {
714                     aCurLine = aCurLine.copy(9);
715                     aCurLine = comphelper::string::strip(aCurLine, ' ');
716                     aCurLine = comphelper::string::strip(aCurLine, '\t');
717                     aCurLine = comphelper::string::stripEnd(aCurLine, '\r');
718                     aCurLine = comphelper::string::stripEnd(aCurLine, '\n');
719                     aCurLine = comphelper::string::strip(aCurLine, '"');
720                     aStream.Close();
721                     aStream.Open(getPPDFile(OStringToOUString(aCurLine, m_aFileEncoding)));
722                     continue;
723                 }
724                 else if( ! bLanguageEncoding &&
725                          aCurLine.matchIgnoreAsciiCase("*languageencoding") )
726                 {
727                     bLanguageEncoding = true; // generally only the first one counts
728                     OString aLower = aCurLine.toAsciiLowerCase();
729                     if( aLower.indexOf("isolatin1", 17 ) != -1 ||
730                         aLower.indexOf("windowsansi", 17 ) != -1 )
731                         m_aFileEncoding = RTL_TEXTENCODING_MS_1252;
732                     else if( aLower.indexOf("isolatin2", 17 ) != -1 )
733                         m_aFileEncoding = RTL_TEXTENCODING_ISO_8859_2;
734                     else if( aLower.indexOf("isolatin5", 17 ) != -1 )
735                         m_aFileEncoding = RTL_TEXTENCODING_ISO_8859_5;
736                     else if( aLower.indexOf("jis83-rksj", 17 ) != -1 )
737                         m_aFileEncoding = RTL_TEXTENCODING_SHIFT_JIS;
738                     else if( aLower.indexOf("macstandard", 17 ) != -1 )
739                         m_aFileEncoding = RTL_TEXTENCODING_APPLE_ROMAN;
740                     else if( aLower.indexOf("utf-8", 17 ) != -1 )
741                         m_aFileEncoding = RTL_TEXTENCODING_UTF8;
742                 }
743             }
744             aLines.push_back( aCurLine );
745         }
746     }
747     aStream.Close();
748 
749     // now get the Values
750     parse( aLines );
751 #if OSL_DEBUG_LEVEL > 1
752     SAL_INFO("vcl.unx.print", "acquired " << m_aKeys.size()
753             << " Keys from PPD " << m_aFile << ":");
754     for (auto const& key : m_aKeys)
755     {
756         const PPDKey* pKey = key.second.get();
757         char const* pSetupType = "<unknown>";
758         switch( pKey->m_eSetupType )
759         {
760             case PPDKey::SetupType::ExitServer:        pSetupType = "ExitServer";break;
761             case PPDKey::SetupType::Prolog:            pSetupType = "Prolog";break;
762             case PPDKey::SetupType::DocumentSetup:     pSetupType = "DocumentSetup";break;
763             case PPDKey::SetupType::PageSetup:         pSetupType = "PageSetup";break;
764             case PPDKey::SetupType::JCLSetup:          pSetupType = "JCLSetup";break;
765             case PPDKey::SetupType::AnySetup:          pSetupType = "AnySetup";break;
766             default: break;
767         }
768         SAL_INFO("vcl.unx.print", "\t\"" << pKey->getKey() << "\" ("
769                 << pKey->countValues() << "values) OrderDependency: "
770                 << pKey->m_nOrderDependency << pSetupType );
771         for( int j = 0; j < pKey->countValues(); j++ )
772         {
773             const PPDValue* pValue = pKey->getValue( j );
774             char const* pVType = "<unknown>";
775             switch( pValue->m_eType )
776             {
777                 case eInvocation:       pVType = "invocation";break;
778                 case eQuoted:           pVType = "quoted";break;
779                 case eString:           pVType = "string";break;
780                 case eSymbol:           pVType = "symbol";break;
781                 case eNo:               pVType = "no";break;
782                 default: break;
783             }
784             SAL_INFO("vcl.unx.print", "\t\t"
785                 << (pValue == pKey->m_pDefaultValue ? "(Default:) " : "")
786                 << "option: \"" << pValue->m_aOption
787                 << "\", value: type " << pVType << " \""
788                 << pValue->m_aValue << "\"");
789         }
790     }
791     SAL_INFO("vcl.unx.print",
792             "constraints: (" << m_aConstraints.size() << " found)");
793     for (auto const& constraint : m_aConstraints)
794     {
795         SAL_INFO("vcl.unx.print", "*\"" << constraint.m_pKey1->getKey() << "\" \""
796                 << (constraint.m_pOption1 ? constraint.m_pOption1->m_aOption : "<nil>")
797                 << "\" *\"" << constraint.m_pKey2->getKey() << "\" \""
798                 << (constraint.m_pOption2 ? constraint.m_pOption2->m_aOption : "<nil>")
799                 << "\"");
800     }
801 #endif
802 
803     m_pImageableAreas = getKey( u"ImageableArea"_ustr );
804     const PPDValue * pDefaultImageableArea = nullptr;
805     if( m_pImageableAreas )
806         pDefaultImageableArea = m_pImageableAreas->getDefaultValue();
807     if (m_pImageableAreas == nullptr) {
808         SAL_WARN( "vcl.unx.print", "no ImageableArea in " << m_aFile);
809     }
810     if (pDefaultImageableArea == nullptr) {
811         SAL_WARN( "vcl.unx.print", "no DefaultImageableArea in " << m_aFile);
812     }
813 
814     m_pPaperDimensions = getKey( u"PaperDimension"_ustr );
815     if( m_pPaperDimensions )
816         m_pDefaultPaperDimension = m_pPaperDimensions->getDefaultValue();
817     if (m_pPaperDimensions == nullptr) {
818         SAL_WARN( "vcl.unx.print", "no PaperDimensions in " << m_aFile);
819     }
820     if (m_pDefaultPaperDimension == nullptr) {
821         SAL_WARN( "vcl.unx.print", "no DefaultPaperDimensions in " << m_aFile);
822     }
823 
824     auto pResolutions = getKey( u"Resolution"_ustr );
825     if( pResolutions )
826         m_pDefaultResolution = pResolutions->getDefaultValue();
827     if (pResolutions == nullptr) {
828         SAL_INFO( "vcl.unx.print", "no Resolution in " << m_aFile);
829     }
830     SAL_INFO_IF(!m_pDefaultResolution, "vcl.unx.print", "no DefaultResolution in " + m_aFile);
831 
832     auto pInputSlots = getKey( u"InputSlot"_ustr );
833     if( pInputSlots )
834         m_pDefaultInputSlot = pInputSlots->getDefaultValue();
835     SAL_INFO_IF(!pInputSlots, "vcl.unx.print", "no InputSlot in " << m_aFile);
836     SAL_INFO_IF(!m_pDefaultInputSlot, "vcl.unx.print", "no DefaultInputSlot in " << m_aFile);
837 }
838 
~PPDParser()839 PPDParser::~PPDParser()
840 {
841     m_pTranslator.reset();
842 }
843 
insertKey(std::unique_ptr<PPDKey> pKey)844 void PPDParser::insertKey( std::unique_ptr<PPDKey> pKey )
845 {
846     m_aOrderedKeys.push_back( pKey.get() );
847     m_aKeys[ pKey->getKey() ] = std::move(pKey);
848 }
849 
getKey(int n) const850 const PPDKey* PPDParser::getKey( int n ) const
851 {
852     return (n >= 0 && o3tl::make_unsigned(n) < m_aOrderedKeys.size()) ? m_aOrderedKeys[n] : nullptr;
853 }
854 
getKey(const OUString & rKey) const855 const PPDKey* PPDParser::getKey( const OUString& rKey ) const
856 {
857     PPDParser::hash_type::const_iterator it = m_aKeys.find( rKey );
858     return it != m_aKeys.end() ? it->second.get() : nullptr;
859 }
860 
hasKey(const PPDKey * pKey) const861 bool PPDParser::hasKey( const PPDKey* pKey ) const
862 {
863     return pKey && ( m_aKeys.find( pKey->getKey() ) != m_aKeys.end() );
864 }
865 
getNibble(char cChar)866 static sal_uInt8 getNibble( char cChar )
867 {
868     sal_uInt8 nRet = 0;
869     if( cChar >= '0' && cChar <= '9' )
870         nRet = sal_uInt8( cChar - '0' );
871     else if( cChar >= 'A' && cChar <= 'F' )
872         nRet = 10 + sal_uInt8( cChar - 'A' );
873     else if( cChar >= 'a' && cChar <= 'f' )
874         nRet = 10 + sal_uInt8( cChar - 'a' );
875     return nRet;
876 }
877 
handleTranslation(const OString & i_rString,bool bIsGlobalized)878 OUString PPDParser::handleTranslation(const OString& i_rString, bool bIsGlobalized)
879 {
880     sal_Int32 nOrigLen = i_rString.getLength();
881     OStringBuffer aTrans( nOrigLen );
882     const char* pStr = i_rString.getStr();
883     const char* pEnd = pStr + nOrigLen;
884     while( pStr < pEnd )
885     {
886         if( *pStr == '<' )
887         {
888             pStr++;
889             char cChar;
890             while( *pStr != '>' && pStr < pEnd-1 )
891             {
892                 cChar = getNibble( *pStr++ ) << 4;
893                 cChar |= getNibble( *pStr++ );
894                 aTrans.append( cChar );
895             }
896             pStr++;
897         }
898         else
899             aTrans.append( *pStr++ );
900     }
901     return OStringToOUString( aTrans, bIsGlobalized ? RTL_TEXTENCODING_UTF8 : m_aFileEncoding );
902 }
903 
904 namespace
905 {
oddDoubleQuoteCount(OStringBuffer & rBuffer)906     bool oddDoubleQuoteCount(OStringBuffer &rBuffer)
907     {
908         bool bHasOddCount = false;
909         for (sal_Int32 i = 0; i < rBuffer.getLength(); ++i)
910         {
911             if (rBuffer[i] == '"')
912                 bHasOddCount = !bHasOddCount;
913         }
914         return bHasOddCount;
915     }
916 }
917 
parse(::std::vector<OString> & rLines)918 void PPDParser::parse( ::std::vector< OString >& rLines )
919 {
920     // Name for PPD group into which all options are put for which the PPD
921     // does not explicitly define a group.
922     // This is similar to how CUPS handles it,
923     // s. Sweet, Michael R. (2001): Common UNIX Printing System, p. 251:
924     // "Each option in turn is associated with a group stored in the
925     // ppd_group_t structure. Groups can be specified in the PPD file; if an
926     // option is not associated with a group, it is put in a "General" or
927     // "Extra" group depending on the option.
928     static constexpr OString aDefaultPPDGroupName("General"_ostr);
929 
930     std::vector< OString >::iterator line = rLines.begin();
931     PPDParser::hash_type::const_iterator keyit;
932 
933     // name of the PPD group that is currently being processed
934     OString aCurrentGroup = aDefaultPPDGroupName;
935 
936     while( line != rLines.end() )
937     {
938         OString aCurrentLine( *line );
939         ++line;
940 
941         SAL_INFO("vcl.unx.print", "Parse line '" << aCurrentLine << "'");
942 
943         if (aCurrentLine.getLength() < 2 || aCurrentLine[0] != '*')
944             continue;
945         if( aCurrentLine[1] == '%' )
946             continue;
947 
948         OString aKey = GetCommandLineToken( 0, aCurrentLine.getToken(0, ':') );
949         sal_Int32 nPos = aKey.indexOf('/');
950         if (nPos != -1)
951             aKey = aKey.copy(0, nPos);
952         if(!aKey.isEmpty())
953         {
954             aKey = aKey.copy(1); // remove the '*'
955         }
956         if(aKey.isEmpty())
957         {
958             continue;
959         }
960 
961         if (aKey == "CloseGroup")
962         {
963             aCurrentGroup = aDefaultPPDGroupName;
964             continue;
965         }
966         if (aKey == "OpenGroup")
967         {
968             OString aGroupName = aCurrentLine;
969             sal_Int32 nPosition = aGroupName.indexOf('/');
970             if (nPosition != -1)
971             {
972                 aGroupName = aGroupName.copy(0, nPosition);
973             }
974 
975             aCurrentGroup = GetCommandLineToken(1, aGroupName);
976             continue;
977         }
978         if ((aKey == "CloseUI") ||
979             (aKey == "JCLCloseUI") ||
980             (aKey == "End") ||
981             (aKey == "JCLEnd") ||
982             (aKey == "OpenSubGroup") ||
983             (aKey == "CloseSubGroup"))
984         {
985             continue;
986         }
987 
988         if ((aKey == "OpenUI") || (aKey == "JCLOpenUI"))
989         {
990             parseOpenUI( aCurrentLine, aCurrentGroup);
991             continue;
992         }
993         else if (aKey == "OrderDependency")
994         {
995             parseOrderDependency( aCurrentLine );
996             continue;
997         }
998         else if (aKey == "UIConstraints" ||
999                  aKey == "NonUIConstraints")
1000         {
1001             continue; // parsed in pass 2
1002         }
1003         else if( aKey == "CustomPageSize" ) // currently not handled
1004             continue;
1005         else if (aKey.startsWith("Custom", &aKey) )
1006         {
1007             //fdo#43049 very basic support for Custom entries, we ignore the
1008             //validation params and types
1009             OUString aUniKey(OStringToOUString(aKey, RTL_TEXTENCODING_MS_1252));
1010             keyit = m_aKeys.find( aUniKey );
1011             if(keyit != m_aKeys.end())
1012             {
1013                 PPDKey* pKey = keyit->second.get();
1014                 pKey->insertValue(u"Custom"_ustr, eInvocation, true);
1015             }
1016             continue;
1017         }
1018 
1019         // default values are parsed in pass 2
1020         if (aKey.startsWith("Default"))
1021             continue;
1022 
1023         bool bQuery     = false;
1024         if (aKey[0] == '?')
1025         {
1026             aKey = aKey.copy(1);
1027             bQuery = true;
1028         }
1029 
1030         OUString aUniKey(OStringToOUString(aKey, RTL_TEXTENCODING_MS_1252));
1031         // handle CUPS extension for globalized PPDs
1032         /* FIXME-BCP47: really only ISO 639-1 two character language codes?
1033          * goodnight... */
1034         bool bIsGlobalizedLine = false;
1035         css::lang::Locale aTransLocale;
1036         if( ( aUniKey.getLength() > 3 && aUniKey[ 2 ] == '.' ) ||
1037             ( aUniKey.getLength() > 5 && aUniKey[ 2 ] == '_' && aUniKey[ 5 ] == '.' ) )
1038         {
1039             if( aUniKey[ 2 ] == '.' )
1040             {
1041                 aTransLocale.Language = aUniKey.copy( 0, 2 );
1042                 aUniKey = aUniKey.copy( 3 );
1043             }
1044             else
1045             {
1046                 aTransLocale.Language = aUniKey.copy( 0, 2 );
1047                 aTransLocale.Country = aUniKey.copy( 3, 2 );
1048                 aUniKey = aUniKey.copy( 6 );
1049             }
1050             bIsGlobalizedLine = true;
1051         }
1052 
1053         OUString aOption;
1054         nPos = aCurrentLine.indexOf(':');
1055         if( nPos != -1 )
1056         {
1057             aOption = OStringToOUString(
1058                 aCurrentLine.subView( 1, nPos-1 ), RTL_TEXTENCODING_MS_1252 );
1059             aOption = GetCommandLineToken( 1, aOption );
1060             sal_Int32 nTransPos = aOption.indexOf( '/' );
1061             if( nTransPos != -1 )
1062                 aOption = aOption.copy(0,  nTransPos);
1063         }
1064 
1065         PPDValueType eType = eNo;
1066         OUString aValue;
1067         OUString aOptionTranslation;
1068         OUString aValueTranslation;
1069         if( nPos != -1 )
1070         {
1071             // found a colon, there may be an option
1072             OString aLine = aCurrentLine.copy( 1, nPos-1 );
1073             aLine = WhitespaceToSpace( aLine );
1074             sal_Int32 nTransPos = aLine.indexOf('/');
1075             if (nTransPos != -1)
1076                 aOptionTranslation = handleTranslation( aLine.copy(nTransPos+1), bIsGlobalizedLine );
1077 
1078             // read in more lines if necessary for multiline values
1079             aLine = aCurrentLine.copy( nPos+1 );
1080             if (!aLine.isEmpty())
1081             {
1082                 OStringBuffer aBuffer(aLine);
1083                 while (line != rLines.end() && oddDoubleQuoteCount(aBuffer))
1084                 {
1085                     // copy the newlines also
1086                     aBuffer.append("\n" + *line);
1087                     ++line;
1088                 }
1089                 aLine = aBuffer.makeStringAndClear();
1090             }
1091             aLine = WhitespaceToSpace( aLine );
1092 
1093             // #i100644# handle a missing value (actually a broken PPD)
1094             if( aLine.isEmpty() )
1095             {
1096                 if( !aOption.isEmpty() &&
1097                     !aUniKey.startsWith( "JCL" ) )
1098                     eType = eInvocation;
1099                 else
1100                     eType = eQuoted;
1101             }
1102             // check for invocation or quoted value
1103             else if(aLine[0] == '"')
1104             {
1105                 aLine = aLine.copy(1);
1106                 nTransPos = aLine.indexOf('"');
1107                 if (nTransPos == -1)
1108                     nTransPos = aLine.getLength();
1109                 aValue = OStringToOUString(aLine.subView(0, nTransPos), RTL_TEXTENCODING_MS_1252);
1110                 // after the second doublequote can follow a / and a translation
1111                 if (nTransPos < aLine.getLength() - 2)
1112                 {
1113                     aValueTranslation = handleTranslation( aLine.copy( nTransPos+2 ), bIsGlobalizedLine );
1114                 }
1115                 // check for quoted value
1116                 if( !aOption.isEmpty() &&
1117                     !aUniKey.startsWith( "JCL" ) )
1118                     eType = eInvocation;
1119                 else
1120                     eType = eQuoted;
1121             }
1122             // check for symbol value
1123             else if(aLine[0] == '^')
1124             {
1125                 aLine = aLine.copy(1);
1126                 aValue = OStringToOUString(aLine, RTL_TEXTENCODING_MS_1252);
1127                 eType = eSymbol;
1128             }
1129             else
1130             {
1131                 // must be a string value then
1132                 // strictly this is false because string values
1133                 // can contain any whitespace which is reduced
1134                 // to one space by now
1135                 // who cares ...
1136                 nTransPos = aLine.indexOf('/');
1137                 if (nTransPos == -1)
1138                     nTransPos = aLine.getLength();
1139                 aValue = OStringToOUString(aLine.subView(0, nTransPos), RTL_TEXTENCODING_MS_1252);
1140                 if (nTransPos+1 < aLine.getLength())
1141                     aValueTranslation = handleTranslation( aLine.copy( nTransPos+1 ), bIsGlobalizedLine );
1142                 eType = eString;
1143             }
1144         }
1145 
1146         // handle globalized PPD entries
1147         if( bIsGlobalizedLine )
1148         {
1149             // handle main key translations of form:
1150             // *ll_CC.Translation MainKeyword/translated text: ""
1151             if( aUniKey == "Translation" )
1152             {
1153                 m_pTranslator->insertKey( aOption, aOptionTranslation, aTransLocale );
1154             }
1155             // handle options translations of for:
1156             // *ll_CC.MainKeyword OptionKeyword/translated text: ""
1157             else
1158             {
1159                 m_pTranslator->insertOption( aUniKey, aOption, aOptionTranslation, aTransLocale );
1160             }
1161             continue;
1162         }
1163 
1164         PPDKey* pKey = nullptr;
1165         keyit = m_aKeys.find( aUniKey );
1166         if( keyit == m_aKeys.end() )
1167         {
1168             pKey = new PPDKey( aUniKey );
1169             insertKey( std::unique_ptr<PPDKey>(pKey) );
1170         }
1171         else
1172             pKey = keyit->second.get();
1173 
1174         if( eType == eNo && bQuery )
1175             continue;
1176 
1177         PPDValue* pValue = pKey->insertValue( aOption, eType );
1178         if( ! pValue )
1179             continue;
1180         pValue->m_aValue = aValue;
1181 
1182         if( !aOptionTranslation.isEmpty() )
1183             m_pTranslator->insertOption( aUniKey, aOption, aOptionTranslation, aTransLocale );
1184         if( !aValueTranslation.isEmpty() )
1185             m_pTranslator->insertValue( aUniKey, aOption, aValue, aValueTranslation, aTransLocale );
1186 
1187         // eventually update query and remove from option list
1188         if( bQuery && !pKey->m_bQueryValue )
1189         {
1190             pKey->m_bQueryValue = true;
1191             pKey->eraseValue( pValue->m_aOption );
1192         }
1193     }
1194 
1195     // second pass: fill in defaults
1196     for( const auto& aLine : rLines )
1197     {
1198         if (aLine.startsWith("*Default"))
1199         {
1200             SAL_INFO("vcl.unx.print", "Found a default: '" << aLine << "'");
1201             OUString aKey(OStringToOUString(aLine.subView(8), RTL_TEXTENCODING_MS_1252));
1202             sal_Int32 nPos = aKey.indexOf( ':' );
1203             if( nPos != -1 )
1204             {
1205                 aKey = aKey.copy(0, nPos);
1206                 OUString aOption(OStringToOUString(
1207                     WhitespaceToSpace(aLine.subView(nPos+9)),
1208                     RTL_TEXTENCODING_MS_1252));
1209                 keyit = m_aKeys.find( aKey );
1210                 if( keyit != m_aKeys.end() )
1211                 {
1212                     PPDKey* pKey = keyit->second.get();
1213                     const PPDValue* pDefValue = pKey->getValue( aOption );
1214                     if( pKey->m_pDefaultValue == nullptr )
1215                         pKey->m_pDefaultValue = pDefValue;
1216                 }
1217                 else
1218                 {
1219                     // some PPDs contain defaults for keys that
1220                     // do not exist otherwise
1221                     // (example: DefaultResolution)
1222                     // so invent that key here and have a default value
1223                     std::unique_ptr<PPDKey> pKey(new PPDKey( aKey ));
1224                     pKey->insertValue( aOption, eInvocation /*or what ?*/ );
1225                     pKey->m_pDefaultValue = pKey->getValue( aOption );
1226                     insertKey( std::move(pKey) );
1227                 }
1228             }
1229         }
1230         else if (aLine.startsWith("*UIConstraints") ||
1231                  aLine.startsWith("*NonUIConstraints"))
1232         {
1233             parseConstraint( aLine );
1234         }
1235     }
1236 }
1237 
parseOpenUI(const OString & rLine,std::string_view rPPDGroup)1238 void PPDParser::parseOpenUI(const OString& rLine, std::string_view rPPDGroup)
1239 {
1240     OUString aTranslation;
1241     OString aKey = rLine;
1242 
1243     sal_Int32 nPos = aKey.indexOf(':');
1244     if( nPos != -1 )
1245         aKey = aKey.copy(0, nPos);
1246     nPos = aKey.indexOf('/');
1247     if( nPos != -1 )
1248     {
1249         aTranslation = handleTranslation( aKey.copy( nPos + 1 ), false );
1250         aKey = aKey.copy(0, nPos);
1251     }
1252     aKey = GetCommandLineToken( 1, aKey );
1253     aKey = aKey.copy(1);
1254 
1255     OUString aUniKey(OStringToOUString(aKey, RTL_TEXTENCODING_MS_1252));
1256     PPDParser::hash_type::const_iterator keyit = m_aKeys.find( aUniKey );
1257     PPDKey* pKey;
1258     if( keyit == m_aKeys.end() )
1259     {
1260         pKey = new PPDKey( aUniKey );
1261         insertKey( std::unique_ptr<PPDKey>(pKey) );
1262     }
1263     else
1264         pKey = keyit->second.get();
1265 
1266     pKey->m_bUIOption = true;
1267     m_pTranslator->insertKey( pKey->getKey(), aTranslation );
1268 
1269     pKey->m_aGroup = OStringToOUString(rPPDGroup, RTL_TEXTENCODING_MS_1252);
1270 }
1271 
parseOrderDependency(const OString & rLine)1272 void PPDParser::parseOrderDependency(const OString& rLine)
1273 {
1274     OString aLine(rLine);
1275     sal_Int32 nPos = aLine.indexOf(':');
1276     if( nPos != -1 )
1277         aLine = aLine.copy( nPos+1 );
1278 
1279     sal_Int32 nOrder = GetCommandLineToken( 0, aLine ).toInt32();
1280     OUString aKey(OStringToOUString(GetCommandLineToken(2, aLine), RTL_TEXTENCODING_MS_1252));
1281     if( aKey[ 0 ] != '*' )
1282         return; // invalid order dependency
1283     aKey = aKey.replaceAt( 0, 1, u"" );
1284 
1285     PPDKey* pKey;
1286     PPDParser::hash_type::const_iterator keyit = m_aKeys.find( aKey );
1287     if( keyit == m_aKeys.end() )
1288     {
1289         pKey = new PPDKey( aKey );
1290         insertKey( std::unique_ptr<PPDKey>(pKey) );
1291     }
1292     else
1293         pKey = keyit->second.get();
1294 
1295     pKey->m_nOrderDependency = nOrder;
1296 }
1297 
parseConstraint(const OString & rLine)1298 void PPDParser::parseConstraint( const OString& rLine )
1299 {
1300     bool bFailed = false;
1301 
1302     OUString aLine(OStringToOUString(rLine, RTL_TEXTENCODING_MS_1252));
1303     sal_Int32 nIdx = rLine.indexOf(':');
1304     if (nIdx != -1)
1305         aLine = aLine.replaceAt(0, nIdx + 1, u"");
1306     PPDConstraint aConstraint;
1307     int nTokens = GetCommandLineTokenCount( aLine );
1308     for( int i = 0; i < nTokens; i++ )
1309     {
1310         OUString aToken = GetCommandLineToken( i, aLine );
1311         if( !aToken.isEmpty() && aToken[ 0 ] == '*' )
1312         {
1313             aToken = aToken.replaceAt( 0, 1, u"" );
1314             if( aConstraint.m_pKey1 )
1315                 aConstraint.m_pKey2 = getKey( aToken );
1316             else
1317                 aConstraint.m_pKey1 = getKey( aToken );
1318         }
1319         else
1320         {
1321             if( aConstraint.m_pKey2 )
1322             {
1323                 if( ! ( aConstraint.m_pOption2 = aConstraint.m_pKey2->getValue( aToken ) ) )
1324                     bFailed = true;
1325             }
1326             else if( aConstraint.m_pKey1 )
1327             {
1328                 if( ! ( aConstraint.m_pOption1 = aConstraint.m_pKey1->getValue( aToken ) ) )
1329                     bFailed = true;
1330             }
1331             else
1332                 // constraint for nonexistent keys; this happens
1333                 // e.g. in HP4PLUS3
1334                 bFailed = true;
1335         }
1336     }
1337     // there must be two keywords
1338     if( ! aConstraint.m_pKey1 || ! aConstraint.m_pKey2 || bFailed )
1339     {
1340         SAL_INFO("vcl.unx.print",
1341                 "Warning: constraint \"" << rLine << "\" is invalid");
1342     }
1343     else
1344         m_aConstraints.push_back( aConstraint );
1345 }
1346 
getDefaultPaperDimension() const1347 OUString PPDParser::getDefaultPaperDimension() const
1348 {
1349     if( m_pDefaultPaperDimension )
1350         return m_pDefaultPaperDimension->m_aOption;
1351 
1352     return OUString();
1353 }
1354 
getMargins(std::u16string_view rPaperName,int & rLeft,int & rRight,int & rUpper,int & rLower) const1355 bool PPDParser::getMargins(
1356                            std::u16string_view rPaperName,
1357                            int& rLeft, int& rRight,
1358                            int& rUpper, int& rLower ) const
1359 {
1360     if( ! m_pImageableAreas || ! m_pPaperDimensions )
1361         return false;
1362 
1363     int nPDim=-1, nImArea=-1, i;
1364     for( i = 0; i < m_pImageableAreas->countValues(); i++ )
1365         if( rPaperName == m_pImageableAreas->getValue( i )->m_aOption )
1366             nImArea = i;
1367     for( i = 0; i < m_pPaperDimensions->countValues(); i++ )
1368         if( rPaperName == m_pPaperDimensions->getValue( i )->m_aOption )
1369             nPDim = i;
1370     if( nPDim == -1 || nImArea == -1 )
1371         return false;
1372 
1373     double ImLLx, ImLLy, ImURx, ImURy;
1374     double PDWidth, PDHeight;
1375     OUString aArea = m_pImageableAreas->getValue( nImArea )->m_aValue;
1376     ImLLx = StringToDouble( GetCommandLineToken( 0, aArea ) );
1377     ImLLy = StringToDouble( GetCommandLineToken( 1, aArea ) );
1378     ImURx = StringToDouble( GetCommandLineToken( 2, aArea ) );
1379     ImURy = StringToDouble( GetCommandLineToken( 3, aArea ) );
1380     aArea = m_pPaperDimensions->getValue( nPDim )->m_aValue;
1381     PDWidth     = StringToDouble( GetCommandLineToken( 0, aArea ) );
1382     PDHeight    = StringToDouble( GetCommandLineToken( 1, aArea ) );
1383     rLeft  = static_cast<int>(ImLLx + 0.5);
1384     rLower = static_cast<int>(ImLLy + 0.5);
1385     rUpper = static_cast<int>(PDHeight - ImURy + 0.5);
1386     rRight = static_cast<int>(PDWidth - ImURx + 0.5);
1387 
1388     return true;
1389 }
1390 
getPaperDimension(std::u16string_view rPaperName,int & rWidth,int & rHeight) const1391 bool PPDParser::getPaperDimension(
1392                                   std::u16string_view rPaperName,
1393                                   int& rWidth, int& rHeight ) const
1394 {
1395     if( ! m_pPaperDimensions )
1396         return false;
1397 
1398     int nPDim=-1;
1399     for( int i = 0; i < m_pPaperDimensions->countValues(); i++ )
1400         if( rPaperName == m_pPaperDimensions->getValue( i )->m_aOption )
1401             nPDim = i;
1402     if( nPDim == -1 )
1403         return false;
1404 
1405     double PDWidth, PDHeight;
1406     OUString aArea = m_pPaperDimensions->getValue( nPDim )->m_aValue;
1407     PDWidth     = StringToDouble( GetCommandLineToken( 0, aArea ) );
1408     PDHeight    = StringToDouble( GetCommandLineToken( 1, aArea ) );
1409     rHeight = static_cast<int>(PDHeight + 0.5);
1410     rWidth  = static_cast<int>(PDWidth + 0.5);
1411 
1412     return true;
1413 }
1414 
matchPaperImpl(int nWidth,int nHeight,psp::orientation * pOrientation) const1415 OUString PPDParser::matchPaperImpl(int nWidth, int nHeight, psp::orientation* pOrientation) const
1416 {
1417     if( ! m_pPaperDimensions )
1418         return OUString();
1419 
1420     int nPDim = -1;
1421     double fSort = 2e36, fNewSort;
1422 
1423     for( int i = 0; i < m_pPaperDimensions->countValues(); i++ )
1424     {
1425         OUString aArea =  m_pPaperDimensions->getValue( i )->m_aValue;
1426         double PDWidth     = StringToDouble( GetCommandLineToken( 0, aArea ) );
1427         double PDHeight    = StringToDouble( GetCommandLineToken( 1, aArea ) );
1428         PDWidth     /= static_cast<double>(nWidth);
1429         PDHeight    /= static_cast<double>(nHeight);
1430         if( PDWidth >= 0.9      &&  PDWidth <= 1.1      &&
1431             PDHeight >= 0.9     &&  PDHeight <= 1.1         )
1432         {
1433             fNewSort =
1434                 (1.0-PDWidth)*(1.0-PDWidth) + (1.0-PDHeight)*(1.0-PDHeight);
1435             if( fNewSort == 0.0 ) // perfect match
1436                 return m_pPaperDimensions->getValue( i )->m_aOption;
1437 
1438             if( fNewSort < fSort )
1439             {
1440                 fSort = fNewSort;
1441                 nPDim = i;
1442             }
1443         }
1444     }
1445 
1446     if (nPDim == -1)
1447         return OUString();
1448 
1449     if (pOrientation)
1450     {
1451         switch (*pOrientation)
1452         {
1453             case psp::orientation::Portrait:
1454                 *pOrientation = psp::orientation::Landscape;
1455             break;
1456             case psp::orientation::Landscape:
1457                 *pOrientation = psp::orientation::Portrait;
1458             break;
1459         }
1460     }
1461 
1462     return m_pPaperDimensions->getValue( nPDim )->m_aOption;
1463 }
1464 
matchPaper(int nWidth,int nHeight,psp::orientation * pOrientation) const1465 OUString PPDParser::matchPaper(int nWidth, int nHeight, psp::orientation* pOrientation) const
1466 {
1467     return matchPaperImpl(nHeight, nWidth, pOrientation);
1468 }
1469 
getDefaultInputSlot() const1470 OUString PPDParser::getDefaultInputSlot() const
1471 {
1472     if( m_pDefaultInputSlot )
1473         return m_pDefaultInputSlot->m_aValue;
1474     return OUString();
1475 }
1476 
getResolutionFromString(std::u16string_view rString,int & rXRes,int & rYRes)1477 void PPDParser::getResolutionFromString(std::u16string_view rString,
1478                                         int& rXRes, int& rYRes )
1479 {
1480     rXRes = rYRes = 300;
1481 
1482     const size_t nDPIPos {rString.find( u"dpi" )};
1483     if( nDPIPos != std::u16string_view::npos )
1484     {
1485         const size_t nPos {rString.find( 'x' )};
1486         if( nPos != std::u16string_view::npos )
1487         {
1488             rXRes = o3tl::toInt32(rString.substr( 0, nPos ));
1489             rYRes = o3tl::toInt32(rString.substr(nPos+1, nDPIPos - nPos - 1));
1490         }
1491         else
1492             rXRes = rYRes = o3tl::toInt32(rString.substr( 0, nDPIPos ));
1493     }
1494 }
1495 
getDefaultResolution(int & rXRes,int & rYRes) const1496 void PPDParser::getDefaultResolution( int& rXRes, int& rYRes ) const
1497 {
1498     if( m_pDefaultResolution )
1499     {
1500         getResolutionFromString( m_pDefaultResolution->m_aValue, rXRes, rYRes );
1501         return;
1502     }
1503 
1504     rXRes = 300;
1505     rYRes = 300;
1506 }
1507 
translateKey(const OUString & i_rKey) const1508 OUString PPDParser::translateKey( const OUString& i_rKey ) const
1509 {
1510     OUString aResult( m_pTranslator->translateKey( i_rKey ) );
1511     if( aResult.isEmpty() )
1512         aResult = i_rKey;
1513     return aResult;
1514 }
1515 
translateOption(std::u16string_view i_rKey,const OUString & i_rOption) const1516 OUString PPDParser::translateOption( std::u16string_view i_rKey,
1517                                           const OUString& i_rOption ) const
1518 {
1519     OUString aResult( m_pTranslator->translateOption( i_rKey, i_rOption ) );
1520     if( aResult.isEmpty() )
1521         aResult = i_rOption;
1522     return aResult;
1523 }
1524 
1525 /*
1526  *  PPDKey
1527  */
1528 
PPDKey(OUString aKey)1529 PPDKey::PPDKey( OUString aKey ) :
1530         m_aKey(std::move( aKey )),
1531         m_pDefaultValue( nullptr ),
1532         m_bQueryValue( false ),
1533         m_bUIOption( false ),
1534         m_nOrderDependency( 100 )
1535 {
1536 }
1537 
~PPDKey()1538 PPDKey::~PPDKey()
1539 {
1540 }
1541 
getValue(int n) const1542 const PPDValue* PPDKey::getValue( int n ) const
1543 {
1544     return (n >= 0 && o3tl::make_unsigned(n) < m_aOrderedValues.size()) ? m_aOrderedValues[n] : nullptr;
1545 }
1546 
getValue(const OUString & rOption) const1547 const PPDValue* PPDKey::getValue( const OUString& rOption ) const
1548 {
1549     PPDKey::hash_type::const_iterator it = m_aValues.find( rOption );
1550     return it != m_aValues.end() ? &it->second : nullptr;
1551 }
1552 
getValueCaseInsensitive(const OUString & rOption) const1553 const PPDValue* PPDKey::getValueCaseInsensitive( const OUString& rOption ) const
1554 {
1555     const PPDValue* pValue = getValue( rOption );
1556     if( ! pValue )
1557     {
1558         for( size_t n = 0; n < m_aOrderedValues.size() && ! pValue; n++ )
1559             if( m_aOrderedValues[n]->m_aOption.equalsIgnoreAsciiCase( rOption ) )
1560                 pValue = m_aOrderedValues[n];
1561     }
1562 
1563     return pValue;
1564 }
1565 
eraseValue(const OUString & rOption)1566 void PPDKey::eraseValue( const OUString& rOption )
1567 {
1568     PPDKey::hash_type::iterator it = m_aValues.find( rOption );
1569     if( it == m_aValues.end() )
1570         return;
1571 
1572     auto vit = std::find(m_aOrderedValues.begin(), m_aOrderedValues.end(), &(it->second ));
1573     if( vit != m_aOrderedValues.end() )
1574         m_aOrderedValues.erase( vit );
1575 
1576     m_aValues.erase( it );
1577 }
1578 
insertValue(const OUString & rOption,PPDValueType eType,bool bCustomOption)1579 PPDValue* PPDKey::insertValue(const OUString& rOption, PPDValueType eType, bool bCustomOption)
1580 {
1581     if( m_aValues.find( rOption ) != m_aValues.end() )
1582         return nullptr;
1583 
1584     PPDValue aValue;
1585     aValue.m_aOption = rOption;
1586     aValue.m_bCustomOption = bCustomOption;
1587     aValue.m_bCustomOptionSetViaApp = false;
1588     aValue.m_eType = eType;
1589     m_aValues[ rOption ] = aValue;
1590     PPDValue* pValue = &m_aValues[rOption];
1591     m_aOrderedValues.push_back( pValue );
1592     return pValue;
1593 }
1594 
1595 /*
1596  * PPDContext
1597  */
1598 
PPDContext()1599 PPDContext::PPDContext() :
1600         m_pParser( nullptr )
1601 {
1602 }
1603 
operator =(PPDContext && rCopy)1604 PPDContext& PPDContext::operator=( PPDContext&& rCopy )
1605 {
1606     std::swap(m_pParser, rCopy.m_pParser);
1607     std::swap(m_aCurrentValues, rCopy.m_aCurrentValues);
1608     return *this;
1609 }
1610 
getModifiedKey(std::size_t n) const1611 const PPDKey* PPDContext::getModifiedKey( std::size_t n ) const
1612 {
1613     if( m_aCurrentValues.size() <= n )
1614         return nullptr;
1615 
1616     hash_type::const_iterator it = m_aCurrentValues.begin();
1617     std::advance(it, n);
1618     return it->first;
1619 }
1620 
setParser(const PPDParser * pParser)1621 void PPDContext::setParser( const PPDParser* pParser )
1622 {
1623     if( pParser != m_pParser )
1624     {
1625         m_aCurrentValues.clear();
1626         m_pParser = pParser;
1627     }
1628 }
1629 
getValue(const PPDKey * pKey) const1630 const PPDValue* PPDContext::getValue( const PPDKey* pKey ) const
1631 {
1632     if( ! m_pParser )
1633         return nullptr;
1634 
1635     hash_type::const_iterator it = m_aCurrentValues.find( pKey );
1636     if( it != m_aCurrentValues.end() )
1637         return it->second;
1638 
1639     if( ! m_pParser->hasKey( pKey ) )
1640         return nullptr;
1641 
1642     const PPDValue* pValue = pKey->getDefaultValue();
1643     if( ! pValue )
1644         pValue = pKey->getValue( 0 );
1645 
1646     return pValue;
1647 }
1648 
setValue(const PPDKey * pKey,const PPDValue * pValue,bool bDontCareForConstraints)1649 const PPDValue* PPDContext::setValue( const PPDKey* pKey, const PPDValue* pValue, bool bDontCareForConstraints )
1650 {
1651     if( ! m_pParser || ! pKey )
1652         return nullptr;
1653 
1654     // pValue can be NULL - it means ignore this option
1655 
1656     if( ! m_pParser->hasKey( pKey ) )
1657         return nullptr;
1658 
1659     // check constraints
1660     if( pValue )
1661     {
1662         if( bDontCareForConstraints )
1663         {
1664             m_aCurrentValues[ pKey ] = pValue;
1665         }
1666         else if( checkConstraints( pKey, pValue, true ) )
1667         {
1668             m_aCurrentValues[ pKey ] = pValue;
1669 
1670             // after setting this value, check all constraints !
1671             hash_type::iterator it = m_aCurrentValues.begin();
1672             while(  it != m_aCurrentValues.end() )
1673             {
1674                 if( it->first != pKey &&
1675                     ! checkConstraints( it->first, it->second, false ) )
1676                 {
1677                     SAL_INFO("vcl.unx.print", "PPDContext::setValue: option "
1678                          << it->first->getKey()
1679                          << " (" << it->second->m_aOption
1680                          << ") is constrained after setting "
1681                          << pKey->getKey()
1682                          << " to " << pValue->m_aOption);
1683                     resetValue( it->first, true );
1684                     it = m_aCurrentValues.begin();
1685                 }
1686                 else
1687                     ++it;
1688             }
1689         }
1690     }
1691     else
1692         m_aCurrentValues[ pKey ] = nullptr;
1693 
1694     return pValue;
1695 }
1696 
checkConstraints(const PPDKey * pKey,const PPDValue * pValue)1697 bool PPDContext::checkConstraints( const PPDKey* pKey, const PPDValue* pValue )
1698 {
1699     if( ! m_pParser || ! pKey || ! pValue )
1700         return false;
1701 
1702     // ensure that this key is already in the list if it exists at all
1703     if( m_aCurrentValues.find( pKey ) != m_aCurrentValues.end() )
1704         return checkConstraints( pKey, pValue, false );
1705 
1706     // it is not in the list, insert it temporarily
1707     bool bRet = false;
1708     if( m_pParser->hasKey( pKey ) )
1709     {
1710         const PPDValue* pDefValue = pKey->getDefaultValue();
1711         m_aCurrentValues[ pKey ] = pDefValue;
1712         bRet = checkConstraints( pKey, pValue, false );
1713         m_aCurrentValues.erase( pKey );
1714     }
1715 
1716     return bRet;
1717 }
1718 
resetValue(const PPDKey * pKey,bool bDefaultable)1719 bool PPDContext::resetValue( const PPDKey* pKey, bool bDefaultable )
1720 {
1721     if( ! pKey || ! m_pParser || ! m_pParser->hasKey( pKey ) )
1722         return false;
1723 
1724     const PPDValue* pResetValue = pKey->getValue( u"None"_ustr );
1725     if( ! pResetValue )
1726         pResetValue = pKey->getValue( u"False"_ustr );
1727     if( ! pResetValue && bDefaultable )
1728         pResetValue = pKey->getDefaultValue();
1729 
1730     bool bRet = pResetValue && ( setValue( pKey, pResetValue ) == pResetValue );
1731 
1732     return bRet;
1733 }
1734 
checkConstraints(const PPDKey * pKey,const PPDValue * pNewValue,bool bDoReset)1735 bool PPDContext::checkConstraints( const PPDKey* pKey, const PPDValue* pNewValue, bool bDoReset )
1736 {
1737     if( ! pNewValue )
1738         return true;
1739 
1740     // sanity checks
1741     if( ! m_pParser )
1742         return false;
1743 
1744     if( pKey->getValue( pNewValue->m_aOption ) != pNewValue )
1745         return false;
1746 
1747     // None / False and the default can always be set, but be careful !
1748     // setting them might influence constrained values
1749     if( pNewValue->m_aOption == "None" || pNewValue->m_aOption == "False" ||
1750         pNewValue == pKey->getDefaultValue() )
1751         return true;
1752 
1753     const ::std::vector< PPDParser::PPDConstraint >& rConstraints( m_pParser->getConstraints() );
1754     for (auto const& constraint : rConstraints)
1755     {
1756         const PPDKey* pLeft     = constraint.m_pKey1;
1757         const PPDKey* pRight    = constraint.m_pKey2;
1758         if( ! pLeft || ! pRight || ( pKey != pLeft && pKey != pRight ) )
1759             continue;
1760 
1761         const PPDKey* pOtherKey = pKey == pLeft ? pRight : pLeft;
1762         const PPDValue* pOtherKeyOption = pKey == pLeft ? constraint.m_pOption2 : constraint.m_pOption1;
1763         const PPDValue* pKeyOption = pKey == pLeft ? constraint.m_pOption1 : constraint.m_pOption2;
1764 
1765         // syntax *Key1 option1 *Key2 option2
1766         if( pKeyOption && pOtherKeyOption )
1767         {
1768             if( pNewValue != pKeyOption )
1769                 continue;
1770             if( pOtherKeyOption == getValue( pOtherKey ) )
1771             {
1772                 return false;
1773             }
1774         }
1775         // syntax *Key1 option *Key2  or  *Key1 *Key2 option
1776         else if( pOtherKeyOption || pKeyOption )
1777         {
1778             if( pKeyOption )
1779             {
1780                 if( ! ( pOtherKeyOption = getValue( pOtherKey ) ) )
1781                     continue; // this should not happen, PPD broken
1782 
1783                 if( pKeyOption == pNewValue &&
1784                     pOtherKeyOption->m_aOption != "None" &&
1785                     pOtherKeyOption->m_aOption != "False" )
1786                 {
1787                     // check if the other value can be reset and
1788                     // do so if possible
1789                     if( bDoReset && resetValue( pOtherKey ) )
1790                         continue;
1791 
1792                     return false;
1793                 }
1794             }
1795             else if( pOtherKeyOption )
1796             {
1797                 if( getValue( pOtherKey ) == pOtherKeyOption &&
1798                     pNewValue->m_aOption != "None" &&
1799                     pNewValue->m_aOption != "False" )
1800                     return false;
1801             }
1802             else
1803             {
1804                 // this should not happen, PPD is broken
1805             }
1806         }
1807         // syntax *Key1 *Key2
1808         else
1809         {
1810             const PPDValue* pOtherValue = getValue( pOtherKey );
1811             if( pOtherValue->m_aOption != "None"  &&
1812                 pOtherValue->m_aOption != "False" &&
1813                 pNewValue->m_aOption != "None"    &&
1814                 pNewValue->m_aOption != "False" )
1815                 return false;
1816         }
1817     }
1818     return true;
1819 }
1820 
getStreamableBuffer(sal_uLong & rBytes) const1821 char* PPDContext::getStreamableBuffer( sal_uLong& rBytes ) const
1822 {
1823     rBytes = 0;
1824     if( m_aCurrentValues.empty() )
1825         return nullptr;
1826     for (auto const& elem : m_aCurrentValues)
1827     {
1828         OString aCopy(OUStringToOString(elem.first->getKey(), RTL_TEXTENCODING_MS_1252));
1829         rBytes += aCopy.getLength();
1830         rBytes += 1; // for ':'
1831         if( elem.second )
1832         {
1833             aCopy = OUStringToOString(elem.second->m_aOption, RTL_TEXTENCODING_MS_1252);
1834             rBytes += aCopy.getLength();
1835         }
1836         else
1837             rBytes += 4;
1838         rBytes += 1; // for '\0'
1839     }
1840     rBytes += 1;
1841     char* pBuffer = new char[ rBytes ];
1842     memset( pBuffer, 0, rBytes );
1843     char* pRun = pBuffer;
1844     for (auto const& elem : m_aCurrentValues)
1845     {
1846         OString aCopy(OUStringToOString(elem.first->getKey(), RTL_TEXTENCODING_MS_1252));
1847         int nBytes = aCopy.getLength();
1848         memcpy( pRun, aCopy.getStr(), nBytes );
1849         pRun += nBytes;
1850         *pRun++ = ':';
1851         if( elem.second )
1852             aCopy = OUStringToOString(elem.second->m_aOption, RTL_TEXTENCODING_MS_1252);
1853         else
1854             aCopy = "*nil"_ostr;
1855         nBytes = aCopy.getLength();
1856         memcpy( pRun, aCopy.getStr(), nBytes );
1857         pRun += nBytes;
1858 
1859         *pRun++ = 0;
1860     }
1861     return pBuffer;
1862 }
1863 
rebuildFromStreamBuffer(const std::vector<char> & rBuffer)1864 void PPDContext::rebuildFromStreamBuffer(const std::vector<char> &rBuffer)
1865 {
1866     if( ! m_pParser )
1867         return;
1868 
1869     m_aCurrentValues.clear();
1870 
1871     const size_t nBytes = rBuffer.size() - 1;
1872     size_t nRun = 0;
1873     while (nRun < nBytes && rBuffer[nRun])
1874     {
1875         OString aLine(rBuffer.data() + nRun);
1876         sal_Int32 nPos = aLine.indexOf(':');
1877         if( nPos != -1 )
1878         {
1879             const PPDKey* pKey = m_pParser->getKey( OStringToOUString( aLine.subView( 0, nPos ), RTL_TEXTENCODING_MS_1252 ) );
1880             if( pKey )
1881             {
1882                 const PPDValue* pValue = nullptr;
1883                 OUString aOption(
1884                     OStringToOUString(aLine.subView(nPos+1), RTL_TEXTENCODING_MS_1252));
1885                 if (aOption != "*nil")
1886                     pValue = pKey->getValue( aOption );
1887                 m_aCurrentValues[ pKey ] = pValue;
1888                 SAL_INFO("vcl.unx.print",
1889                     "PPDContext::rebuildFromStreamBuffer: read PPDKeyValue { "
1890                     << pKey->getKey() << " , "
1891                     << (pValue ? aOption : u"<nil>"_ustr)
1892                     << " }");
1893             }
1894         }
1895         nRun += aLine.getLength()+1;
1896     }
1897 }
1898 
getRenderResolution() const1899 int PPDContext::getRenderResolution() const
1900 {
1901     // initialize to reasonable default, if parser is not set
1902     int nDPI = 300;
1903     if( m_pParser )
1904     {
1905         int nDPIx = 300, nDPIy = 300;
1906         const PPDKey* pKey = m_pParser->getKey( u"Resolution"_ustr );
1907         if( pKey )
1908         {
1909             const PPDValue* pValue = getValue( pKey );
1910             if( pValue )
1911                 PPDParser::getResolutionFromString( pValue->m_aOption, nDPIx, nDPIy );
1912             else
1913                 m_pParser->getDefaultResolution( nDPIx, nDPIy );
1914         }
1915         else
1916             m_pParser->getDefaultResolution( nDPIx, nDPIy );
1917 
1918         nDPI = std::max(nDPIx, nDPIy);
1919     }
1920     return  nDPI;
1921 }
1922 
getPageSize(OUString & rPaper,int & rWidth,int & rHeight) const1923 void PPDContext::getPageSize( OUString& rPaper, int& rWidth, int& rHeight ) const
1924 {
1925     // initialize to reasonable default, if parser is not set
1926     rPaper  = "A4";
1927     rWidth  = 595;
1928     rHeight = 842;
1929     if( !m_pParser )
1930         return;
1931 
1932     const PPDKey* pKey = m_pParser->getKey( u"PageSize"_ustr );
1933     if( !pKey )
1934         return;
1935 
1936     const PPDValue* pValue = getValue( pKey );
1937     if( pValue )
1938     {
1939         rPaper = pValue->m_aOption;
1940         m_pParser->getPaperDimension( rPaper, rWidth, rHeight );
1941     }
1942     else
1943     {
1944         rPaper = m_pParser->getDefaultPaperDimension();
1945         m_pParser->getDefaultPaperDimension( rWidth, rHeight );
1946     }
1947 }
1948 
1949 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
1950