File indexing completed on 2024-04-28 04:32:42

0001 /*
0002     SPDX-FileCopyrightText: 2007, 2010 John Layt <john@layt.net>
0003 
0004     FilePrinterPreview based on KPrintPreview (originally LGPL)
0005     SPDX-FileCopyrightText: 2007 Alex Merry <huntedhacker@tiscali.co.uk>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 
0010 #include "fileprinter.h"
0011 
0012 #include <QFile>
0013 #include <QFileInfo>
0014 #include <QLabel>
0015 #include <QPrintEngine>
0016 #include <QShowEvent>
0017 #include <QSize>
0018 #include <QStringList>
0019 #include <QTcpSocket>
0020 
0021 #include <KProcess>
0022 #include <KShell>
0023 #include <QDebug>
0024 #include <QStandardPaths>
0025 
0026 #include "debug_p.h"
0027 
0028 using namespace Okular;
0029 
0030 Document::PrintError
0031 FilePrinter::printFile(QPrinter &printer, const QString &file, QPageLayout::Orientation documentOrientation, FileDeletePolicy fileDeletePolicy, PageSelectPolicy pageSelectPolicy, const QString &pageRange, ScaleMode scaleMode)
0032 {
0033     FilePrinter fp;
0034     return fp.doPrintFiles(printer, QStringList(file), fileDeletePolicy, pageSelectPolicy, pageRange, documentOrientation, scaleMode);
0035 }
0036 
0037 static Document::PrintError doKProcessExecute(const QString &exe, const QStringList &argList)
0038 {
0039     const int ret = KProcess::execute(exe, argList);
0040     if (ret == -1) {
0041         return Document::PrintingProcessCrashPrintError;
0042     }
0043     if (ret == -2) {
0044         return Document::PrintingProcessStartPrintError;
0045     }
0046     if (ret < 0) {
0047         return Document::UnknownPrintError;
0048     }
0049 
0050     return Document::NoPrintError;
0051 }
0052 
0053 Document::PrintError
0054 FilePrinter::doPrintFiles(QPrinter &printer, const QStringList &fileList, FileDeletePolicy fileDeletePolicy, PageSelectPolicy pageSelectPolicy, const QString &pageRange, QPageLayout::Orientation documentOrientation, ScaleMode scaleMode)
0055 {
0056     if (fileList.size() < 1) {
0057         return Document::NoFileToPrintError;
0058     }
0059 
0060     for (QStringList::ConstIterator it = fileList.constBegin(); it != fileList.constEnd(); ++it) {
0061         if (!QFile::exists(*it)) {
0062             return Document::UnableToFindFilePrintError;
0063         }
0064     }
0065 
0066     if (printer.printerState() == QPrinter::Aborted || printer.printerState() == QPrinter::Error) {
0067         return Document::InvalidPrinterStatePrintError;
0068     }
0069 
0070     QString exe;
0071     QStringList argList;
0072     Document::PrintError ret;
0073 
0074     // Print to File if a filename set, assumes there must be only 1 file
0075     if (!printer.outputFileName().isEmpty()) {
0076         if (QFile::exists(printer.outputFileName())) {
0077             QFile::remove(printer.outputFileName());
0078         }
0079 
0080         QFileInfo inputFileInfo = QFileInfo(fileList[0]);
0081         QFileInfo outputFileInfo = QFileInfo(printer.outputFileName());
0082 
0083         bool doDeleteFile = (fileDeletePolicy == FilePrinter::SystemDeletesFiles);
0084         if (inputFileInfo.suffix() == outputFileInfo.suffix()) {
0085             if (doDeleteFile) {
0086                 bool res = QFile::rename(fileList[0], printer.outputFileName());
0087                 if (res) {
0088                     doDeleteFile = false;
0089                     ret = Document::NoPrintError;
0090                 } else {
0091                     ret = Document::PrintToFilePrintError;
0092                 }
0093             } else {
0094                 bool res = QFile::copy(fileList[0], printer.outputFileName());
0095                 if (res) {
0096                     ret = Document::NoPrintError;
0097                 } else {
0098                     ret = Document::PrintToFilePrintError;
0099                 }
0100             }
0101         } else if (inputFileInfo.suffix() == QLatin1String("ps") && printer.outputFormat() == QPrinter::PdfFormat && ps2pdfAvailable()) {
0102             exe = QStringLiteral("ps2pdf");
0103             argList << fileList[0] << printer.outputFileName();
0104             qCDebug(OkularCoreDebug) << "Executing" << exe << "with arguments" << argList;
0105             ret = doKProcessExecute(exe, argList);
0106         } else if (inputFileInfo.suffix() == QLatin1String("pdf") && printer.outputFormat() == QPrinter::NativeFormat && pdf2psAvailable()) {
0107             exe = QStringLiteral("pdf2ps");
0108             argList << fileList[0] << printer.outputFileName();
0109             qCDebug(OkularCoreDebug) << "Executing" << exe << "with arguments" << argList;
0110             ret = doKProcessExecute(exe, argList);
0111         } else {
0112             ret = Document::PrintToFilePrintError;
0113         }
0114 
0115         if (doDeleteFile) {
0116             QFile::remove(fileList[0]);
0117         }
0118 
0119     } else { // Print to a printer via lpr command
0120 
0121         // Decide what executable to use to print with, need the CUPS version of lpr if available
0122         // Some distros name the CUPS version of lpr as lpr-cups or lpr.cups so try those first
0123         // before default to lpr, or failing that to lp
0124 
0125         if (!QStandardPaths::findExecutable(QStringLiteral("lpr-cups")).isEmpty()) {
0126             exe = QStringLiteral("lpr-cups");
0127         } else if (!QStandardPaths::findExecutable(QStringLiteral("lpr.cups")).isEmpty()) {
0128             exe = QStringLiteral("lpr.cups");
0129         } else if (!QStandardPaths::findExecutable(QStringLiteral("lpr")).isEmpty()) {
0130             exe = QStringLiteral("lpr");
0131         } else if (!QStandardPaths::findExecutable(QStringLiteral("lp")).isEmpty()) {
0132             exe = QStringLiteral("lp");
0133         } else {
0134             return Document::NoBinaryToPrintError;
0135         }
0136 
0137         bool useCupsOptions = cupsAvailable();
0138         argList = printArguments(printer, fileDeletePolicy, pageSelectPolicy, useCupsOptions, pageRange, exe, documentOrientation, scaleMode) << fileList;
0139         qCDebug(OkularCoreDebug) << "Executing" << exe << "with arguments" << argList;
0140 
0141         ret = doKProcessExecute(exe, argList);
0142     }
0143 
0144     return ret;
0145 }
0146 
0147 QList<int> FilePrinter::pageList(QPrinter &printer, int lastPage, int currentPage, const QList<int> &selectedPageList)
0148 {
0149     if (printer.printRange() == QPrinter::Selection) {
0150         return selectedPageList;
0151     }
0152 
0153     int startPage, endPage;
0154     QList<int> list;
0155 
0156     if (printer.printRange() == QPrinter::PageRange) {
0157         startPage = printer.fromPage();
0158         endPage = printer.toPage();
0159     } else if (printer.printRange() == QPrinter::CurrentPage) {
0160         startPage = currentPage;
0161         endPage = currentPage;
0162     } else { // AllPages
0163         startPage = 1;
0164         endPage = lastPage;
0165     }
0166 
0167     for (int i = startPage; i <= endPage; i++) {
0168         list << i;
0169     }
0170 
0171     return list;
0172 }
0173 
0174 bool FilePrinter::ps2pdfAvailable()
0175 {
0176     return (!QStandardPaths::findExecutable(QStringLiteral("ps2pdf")).isEmpty());
0177 }
0178 
0179 bool FilePrinter::pdf2psAvailable()
0180 {
0181     return (!QStandardPaths::findExecutable(QStringLiteral("pdf2ps")).isEmpty());
0182 }
0183 
0184 bool FilePrinter::cupsAvailable()
0185 {
0186 #if defined(Q_OS_UNIX) && !defined(Q_OS_OSX)
0187     // Ideally we would have access to the private Qt method
0188     // QCUPSSupport::cupsAvailable() to do this as it is very complex routine.
0189     // However, if CUPS is available then QPrinter::supportsMultipleCopies() will always return true
0190     // whereas if CUPS is not available it will return false.
0191     // This behaviour is guaranteed never to change, so we can use it as a reliable substitute.
0192     QPrinter testPrinter;
0193     return testPrinter.supportsMultipleCopies();
0194 #else
0195     return false;
0196 #endif
0197 }
0198 
0199 QStringList FilePrinter::printArguments(QPrinter &printer,
0200                                         FileDeletePolicy fileDeletePolicy,
0201                                         PageSelectPolicy pageSelectPolicy,
0202                                         bool useCupsOptions,
0203                                         const QString &pageRange,
0204                                         const QString &version,
0205                                         QPageLayout::Orientation documentOrientation,
0206                                         ScaleMode scaleMode)
0207 {
0208     QStringList argList;
0209 
0210     if (!destination(printer, version).isEmpty()) {
0211         argList << destination(printer, version);
0212     }
0213 
0214     if (!copies(printer, version).isEmpty()) {
0215         argList << copies(printer, version);
0216     }
0217 
0218     if (!jobname(printer, version).isEmpty()) {
0219         argList << jobname(printer, version);
0220     }
0221 
0222     if (!pages(printer, pageSelectPolicy, pageRange, useCupsOptions, version).isEmpty()) {
0223         argList << pages(printer, pageSelectPolicy, pageRange, useCupsOptions, version);
0224     }
0225 
0226     if (useCupsOptions && !cupsOptions(printer, documentOrientation, scaleMode).isEmpty()) {
0227         argList << cupsOptions(printer, documentOrientation, scaleMode);
0228     }
0229 
0230     if (!deleteFile(printer, fileDeletePolicy, version).isEmpty()) {
0231         argList << deleteFile(printer, fileDeletePolicy, version);
0232     }
0233 
0234     if (version == QLatin1String("lp")) {
0235         argList << QStringLiteral("--");
0236     }
0237 
0238     return argList;
0239 }
0240 
0241 QStringList FilePrinter::destination(QPrinter &printer, const QString &version)
0242 {
0243     if (version == QLatin1String("lp")) {
0244         return QStringList(QStringLiteral("-d")) << printer.printerName();
0245     }
0246 
0247     if (version.startsWith(QLatin1String("lpr"))) {
0248         return QStringList(QStringLiteral("-P")) << printer.printerName();
0249     }
0250 
0251     return QStringList();
0252 }
0253 
0254 QStringList FilePrinter::copies(QPrinter &printer, const QString &version)
0255 {
0256     int cp = printer.copyCount();
0257 
0258     if (version == QLatin1String("lp")) {
0259         return QStringList(QStringLiteral("-n")) << QStringLiteral("%1").arg(cp);
0260     }
0261 
0262     if (version.startsWith(QLatin1String("lpr"))) {
0263         return QStringList() << QStringLiteral("-#%1").arg(cp);
0264     }
0265 
0266     return QStringList();
0267 }
0268 
0269 QStringList FilePrinter::jobname(QPrinter &printer, const QString &version)
0270 {
0271     if (!printer.docName().isEmpty()) {
0272         if (version == QLatin1String("lp")) {
0273             return QStringList(QStringLiteral("-t")) << printer.docName();
0274         }
0275 
0276         if (version.startsWith(QLatin1String("lpr"))) {
0277             const QString shortenedDocName = QString::fromUtf8(printer.docName().toUtf8().left(255));
0278             return QStringList(QStringLiteral("-J")) << shortenedDocName;
0279         }
0280     }
0281 
0282     return QStringList();
0283 }
0284 
0285 QStringList FilePrinter::deleteFile(QPrinter &, FileDeletePolicy fileDeletePolicy, const QString &version)
0286 {
0287     if (fileDeletePolicy == FilePrinter::SystemDeletesFiles && version.startsWith(QLatin1String("lpr"))) {
0288         return QStringList(QStringLiteral("-r"));
0289     }
0290 
0291     return QStringList();
0292 }
0293 
0294 QStringList FilePrinter::pages(QPrinter &printer, PageSelectPolicy pageSelectPolicy, const QString &pageRange, bool useCupsOptions, const QString &version)
0295 {
0296     if (pageSelectPolicy == FilePrinter::SystemSelectsPages) {
0297         if (printer.printRange() == QPrinter::Selection && !pageRange.isEmpty()) {
0298             if (version == QLatin1String("lp")) {
0299                 return QStringList(QStringLiteral("-P")) << pageRange;
0300             }
0301 
0302             if (version.startsWith(QLatin1String("lpr")) && useCupsOptions) {
0303                 return QStringList(QStringLiteral("-o")) << QStringLiteral("page-ranges=%1").arg(pageRange);
0304             }
0305         }
0306 
0307         if (printer.printRange() == QPrinter::PageRange) {
0308             if (version == QLatin1String("lp")) {
0309                 return QStringList(QStringLiteral("-P")) << QStringLiteral("%1-%2").arg(printer.fromPage()).arg(printer.toPage());
0310             }
0311 
0312             if (version.startsWith(QLatin1String("lpr")) && useCupsOptions) {
0313                 return QStringList(QStringLiteral("-o")) << QStringLiteral("page-ranges=%1-%2").arg(printer.fromPage()).arg(printer.toPage());
0314             }
0315         }
0316     }
0317 
0318     return QStringList(); // AllPages
0319 }
0320 
0321 QStringList FilePrinter::cupsOptions(QPrinter &printer, QPageLayout::Orientation documentOrientation, ScaleMode scaleMode)
0322 {
0323     QStringList optionList;
0324 
0325     if (!optionMedia(printer).isEmpty()) {
0326         optionList << optionMedia(printer);
0327     }
0328 
0329     if (!optionOrientation(printer, documentOrientation).isEmpty()) {
0330         optionList << optionOrientation(printer, documentOrientation);
0331     }
0332 
0333     if (!optionDoubleSidedPrinting(printer).isEmpty()) {
0334         optionList << optionDoubleSidedPrinting(printer);
0335     }
0336 
0337     if (!optionPageOrder(printer).isEmpty()) {
0338         optionList << optionPageOrder(printer);
0339     }
0340 
0341     if (!optionCollateCopies(printer).isEmpty()) {
0342         optionList << optionCollateCopies(printer);
0343     }
0344 
0345     if (!optionPageMargins(printer, scaleMode).isEmpty()) {
0346         optionList << optionPageMargins(printer, scaleMode);
0347     }
0348 
0349     optionList << optionCupsProperties(printer);
0350 
0351     return optionList;
0352 }
0353 
0354 QStringList FilePrinter::optionMedia(QPrinter &printer)
0355 {
0356     if (!mediaPageSize(printer).isEmpty() && !mediaPaperSource(printer).isEmpty()) {
0357         return QStringList(QStringLiteral("-o")) << QStringLiteral("media=%1,%2").arg(mediaPageSize(printer), mediaPaperSource(printer));
0358     }
0359 
0360     if (!mediaPageSize(printer).isEmpty()) {
0361         return QStringList(QStringLiteral("-o")) << QStringLiteral("media=%1").arg(mediaPageSize(printer));
0362     }
0363 
0364     if (!mediaPaperSource(printer).isEmpty()) {
0365         return QStringList(QStringLiteral("-o")) << QStringLiteral("media=%1").arg(mediaPaperSource(printer));
0366     }
0367 
0368     return QStringList();
0369 }
0370 
0371 QString FilePrinter::mediaPageSize(QPrinter &printer)
0372 {
0373     switch (printer.pageLayout().pageSize().id()) {
0374     case QPageSize::A0:
0375         return QStringLiteral("A0");
0376     case QPageSize::A1:
0377         return QStringLiteral("A1");
0378     case QPageSize::A2:
0379         return QStringLiteral("A2");
0380     case QPageSize::A3:
0381         return QStringLiteral("A3");
0382     case QPageSize::A4:
0383         return QStringLiteral("A4");
0384     case QPageSize::A5:
0385         return QStringLiteral("A5");
0386     case QPageSize::A6:
0387         return QStringLiteral("A6");
0388     case QPageSize::A7:
0389         return QStringLiteral("A7");
0390     case QPageSize::A8:
0391         return QStringLiteral("A8");
0392     case QPageSize::A9:
0393         return QStringLiteral("A9");
0394     case QPageSize::B0:
0395         return QStringLiteral("B0");
0396     case QPageSize::B1:
0397         return QStringLiteral("B1");
0398     case QPageSize::B10:
0399         return QStringLiteral("B10");
0400     case QPageSize::B2:
0401         return QStringLiteral("B2");
0402     case QPageSize::B3:
0403         return QStringLiteral("B3");
0404     case QPageSize::B4:
0405         return QStringLiteral("B4");
0406     case QPageSize::B5:
0407         return QStringLiteral("B5");
0408     case QPageSize::B6:
0409         return QStringLiteral("B6");
0410     case QPageSize::B7:
0411         return QStringLiteral("B7");
0412     case QPageSize::B8:
0413         return QStringLiteral("B8");
0414     case QPageSize::B9:
0415         return QStringLiteral("B9");
0416     case QPageSize::C5E:
0417         return QStringLiteral("C5"); // Correct Translation?
0418     case QPageSize::Comm10E:
0419         return QStringLiteral("Comm10"); // Correct Translation?
0420     case QPageSize::DLE:
0421         return QStringLiteral("DL"); // Correct Translation?
0422     case QPageSize::Executive:
0423         return QStringLiteral("Executive");
0424     case QPageSize::Folio:
0425         return QStringLiteral("Folio");
0426     case QPageSize::Ledger:
0427         return QStringLiteral("Ledger");
0428     case QPageSize::Legal:
0429         return QStringLiteral("Legal");
0430     case QPageSize::Letter:
0431         return QStringLiteral("Letter");
0432     case QPageSize::Tabloid:
0433         return QStringLiteral("Tabloid");
0434     case QPageSize::Custom:
0435         return QStringLiteral("Custom.%1x%2mm").arg(printer.widthMM()).arg(printer.heightMM());
0436     default:
0437         return QString();
0438     }
0439 }
0440 
0441 // What about Upper and MultiPurpose?  And others in PPD???
0442 QString FilePrinter::mediaPaperSource(QPrinter &printer)
0443 {
0444     switch (printer.paperSource()) {
0445     case QPrinter::Auto:
0446         return QString();
0447     case QPrinter::Cassette:
0448         return QStringLiteral("Cassette");
0449     case QPrinter::Envelope:
0450         return QStringLiteral("Envelope");
0451     case QPrinter::EnvelopeManual:
0452         return QStringLiteral("EnvelopeManual");
0453     case QPrinter::FormSource:
0454         return QStringLiteral("FormSource");
0455     case QPrinter::LargeCapacity:
0456         return QStringLiteral("LargeCapacity");
0457     case QPrinter::LargeFormat:
0458         return QStringLiteral("LargeFormat");
0459     case QPrinter::Lower:
0460         return QStringLiteral("Lower");
0461     case QPrinter::MaxPageSource:
0462         return QStringLiteral("MaxPageSource");
0463     case QPrinter::Middle:
0464         return QStringLiteral("Middle");
0465     case QPrinter::Manual:
0466         return QStringLiteral("Manual");
0467     case QPrinter::OnlyOne:
0468         return QStringLiteral("OnlyOne");
0469     case QPrinter::Tractor:
0470         return QStringLiteral("Tractor");
0471     case QPrinter::SmallFormat:
0472         return QStringLiteral("SmallFormat");
0473     default:
0474         return QString();
0475     }
0476 }
0477 
0478 QStringList FilePrinter::optionOrientation(QPrinter &printer, QPageLayout::Orientation documentOrientation)
0479 {
0480     // portrait and landscape options rotate the document according to the document orientation
0481     // If we want to print a landscape document as one would expect it, we have to pass the
0482     // portrait option so that the document is not rotated additionally
0483     if (printer.pageLayout().orientation() == documentOrientation) {
0484         // the user wants the document printed as is
0485         return QStringList(QStringLiteral("-o")) << QStringLiteral("portrait");
0486     } else {
0487         // the user expects the document being rotated by 90 degrees
0488         return QStringList(QStringLiteral("-o")) << QStringLiteral("landscape");
0489     }
0490 }
0491 
0492 QStringList FilePrinter::optionDoubleSidedPrinting(QPrinter &printer)
0493 {
0494     switch (printer.duplex()) {
0495     case QPrinter::DuplexNone:
0496         return QStringList(QStringLiteral("-o")) << QStringLiteral("sides=one-sided");
0497     case QPrinter::DuplexAuto:
0498         if (printer.pageLayout().orientation() == QPageLayout::Landscape) {
0499             return QStringList(QStringLiteral("-o")) << QStringLiteral("sides=two-sided-short-edge");
0500         } else {
0501             return QStringList(QStringLiteral("-o")) << QStringLiteral("sides=two-sided-long-edge");
0502         }
0503     case QPrinter::DuplexLongSide:
0504         return QStringList(QStringLiteral("-o")) << QStringLiteral("sides=two-sided-long-edge");
0505     case QPrinter::DuplexShortSide:
0506         return QStringList(QStringLiteral("-o")) << QStringLiteral("sides=two-sided-short-edge");
0507     default:
0508         return QStringList(); // Use printer default
0509     }
0510 }
0511 
0512 QStringList FilePrinter::optionPageOrder(QPrinter &printer)
0513 {
0514     if (printer.pageOrder() == QPrinter::LastPageFirst) {
0515         return QStringList(QStringLiteral("-o")) << QStringLiteral("outputorder=reverse");
0516     }
0517     return QStringList(QStringLiteral("-o")) << QStringLiteral("outputorder=normal");
0518 }
0519 
0520 QStringList FilePrinter::optionCollateCopies(QPrinter &printer)
0521 {
0522     if (printer.collateCopies()) {
0523         return QStringList(QStringLiteral("-o")) << QStringLiteral("Collate=True");
0524     }
0525     return QStringList(QStringLiteral("-o")) << QStringLiteral("Collate=False");
0526 }
0527 
0528 QStringList FilePrinter::optionPageMargins(QPrinter &printer, ScaleMode scaleMode)
0529 {
0530     if (printer.printEngine()->property(QPrintEngine::PPK_PageMargins).isNull()) {
0531         return QStringList();
0532     } else {
0533         qreal l(0), t(0), r(0), b(0);
0534         if (!printer.fullPage()) {
0535             auto marginsf = printer.pageLayout().margins(QPageLayout::Point);
0536             l = marginsf.left();
0537             t = marginsf.top();
0538             r = marginsf.right();
0539             b = marginsf.bottom();
0540         }
0541         QStringList marginOptions;
0542         marginOptions << (QStringLiteral("-o")) << QStringLiteral("page-left=%1").arg(l) << QStringLiteral("-o") << QStringLiteral("page-top=%1").arg(t) << QStringLiteral("-o") << QStringLiteral("page-right=%1").arg(r)
0543                       << QStringLiteral("-o") << QStringLiteral("page-bottom=%1").arg(b);
0544         if (scaleMode == ScaleMode::FitToPrintArea) {
0545             marginOptions << QStringLiteral("-o") << QStringLiteral("fit-to-page");
0546         }
0547 
0548         return marginOptions;
0549     }
0550 }
0551 
0552 QStringList FilePrinter::optionCupsProperties(QPrinter &printer)
0553 {
0554     QStringList dialogOptions = printer.printEngine()->property(QPrintEngine::PrintEnginePropertyKey(0xfe00)).toStringList();
0555     QStringList cupsOptions;
0556 
0557     for (int i = 0; i < dialogOptions.count(); i = i + 2) {
0558         if (dialogOptions[i + 1].isEmpty()) {
0559             cupsOptions << QStringLiteral("-o") << dialogOptions[i];
0560         } else {
0561             cupsOptions << QStringLiteral("-o") << dialogOptions[i] + QLatin1Char('=') + dialogOptions[i + 1];
0562         }
0563     }
0564 
0565     return cupsOptions;
0566 }
0567 
0568 /* kate: replace-tabs on; indent-width 4; */