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