File indexing completed on 2024-05-12 04:33:55

0001 // -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; c-brace-offset: 0; -*-
0002 /**
0003  * \file dviexport.h
0004  * Distributed under the GNU GPL version 2 or (at your option)
0005  * any later version. See accompanying file COPYING or copy at
0006  * http://www.gnu.org/copyleft/gpl.html
0007  *
0008  * \author Angus Leeming
0009  * \author Stefan Kebekus
0010  *
0011  * Classes DVIExportToPDF and DVIExportToPS control the export
0012  * of a DVI file to PDF or PostScript format, respectively.
0013  * Common functionality is factored out into a common base class,
0014  * DVIExport which itself derives from QSharedData allowing easy,
0015  * polymorphic storage of multiple QExplicitlySharedDataPointer<DVIExport> variables
0016  * in a container of all exported processes.
0017  */
0018 
0019 #include <config.h>
0020 #include <core/fileprinter.h>
0021 
0022 #include "dviexport.h"
0023 
0024 #include "debug_dvi.h"
0025 #include "dviFile.h"
0026 #include "dviRenderer.h"
0027 
0028 #include <KLocalizedString>
0029 #include <KMessageBox>
0030 #include <KProcess>
0031 
0032 #include <QEventLoop>
0033 #include <QFileInfo>
0034 #include <QLabel>
0035 #include <QTemporaryFile>
0036 
0037 #include <QStandardPaths>
0038 #include <cassert>
0039 
0040 DVIExport::DVIExport(dviRenderer &parent)
0041     : started_(false)
0042     , process_(nullptr)
0043     , parent_(&parent)
0044 {
0045     connect(this, &DVIExport::error, &parent, &dviRenderer::error);
0046 }
0047 
0048 DVIExport::~DVIExport()
0049 {
0050     delete process_;
0051 }
0052 
0053 void DVIExport::start(const QString &command, const QStringList &args, const QString &working_directory, const QString &error_message)
0054 {
0055     assert(!process_);
0056 
0057     process_ = new KProcess;
0058     process_->setOutputChannelMode(KProcess::MergedChannels);
0059     process_->setNextOpenMode(QIODevice::Text);
0060     connect(process_, &KProcess::readyReadStandardOutput, this, &DVIExport::output_receiver);
0061     connect(process_, QOverload<int, QProcess::ExitStatus>::of(&KProcess::finished), this, &DVIExport::finished);
0062 
0063     *process_ << command << args;
0064 
0065     if (!working_directory.isEmpty()) {
0066         process_->setWorkingDirectory(working_directory);
0067     }
0068 
0069     error_message_ = error_message;
0070 
0071     process_->start();
0072     if (!process_->waitForStarted(-1)) {
0073         qCCritical(OkularDviDebug) << command << " failed to start";
0074     } else {
0075         started_ = true;
0076     }
0077 
0078     if (parent_->m_eventLoop) {
0079         parent_->m_eventLoop->exec();
0080     }
0081 }
0082 
0083 void DVIExport::abort_process_impl()
0084 {
0085     // deleting process_ kills the external process itself
0086     // if it's still running.
0087     delete process_;
0088     process_ = nullptr;
0089 }
0090 
0091 void DVIExport::finished_impl(int exit_code)
0092 {
0093     if (process_ && exit_code != 0) {
0094         Q_EMIT error(error_message_, -1);
0095     }
0096     // Remove this from the store of all export processes.
0097     parent_->m_eventLoop->exit(exit_code);
0098     parent_->export_finished(this);
0099 }
0100 
0101 void DVIExport::output_receiver()
0102 {
0103     if (process_) {
0104         process_->readAllStandardOutput();
0105     }
0106 }
0107 
0108 DVIExportToPDF::DVIExportToPDF(dviRenderer &parent, const QString &output_name)
0109     : DVIExport(parent)
0110 {
0111     // Neither of these should happen. Paranoia checks.
0112     if (!parent.dviFile) {
0113         return;
0114     }
0115     const dvifile &dvi = *(parent.dviFile);
0116 
0117     const QFileInfo input(dvi.filename);
0118     if (!input.exists() || !input.isReadable()) {
0119         return;
0120     }
0121 
0122     if (QStandardPaths::findExecutable(QStringLiteral("dvipdfm")).isEmpty()) {
0123         Q_EMIT error(i18n("<qt><p>Okular could not locate the program <em>dvipdfm</em> on your computer. This program is "
0124                           "essential for the export function to work. You can, however, convert "
0125                           "the DVI-file to PDF using the print function of Okular, but that will often "
0126                           "produce documents which print okay, but are of inferior quality if viewed in "
0127                           "Acrobat Reader. It may be wise to upgrade to a more recent version of your "
0128                           "TeX distribution which includes the <em>dvipdfm</em> program.</p>"
0129                           "<p>Hint to the perplexed system administrator: Okular uses the PATH environment variable "
0130                           "when looking for programs.</p></qt>"),
0131                      -1);
0132         return;
0133     }
0134 
0135     if (output_name.isEmpty()) {
0136         return;
0137     }
0138 
0139     start(QStringLiteral("dvipdfm"),
0140           QStringList() << QStringLiteral("-o") << output_name << dvi.filename,
0141           QFileInfo(dvi.filename).absolutePath(),
0142           i18n("<qt>The external program 'dvipdfm', which was used to export the file, reported an error. "
0143                "You might wish to look at the <strong>document info dialog</strong> which you will "
0144                "find in the File-Menu for a precise error report.</qt>"));
0145 }
0146 
0147 DVIExportToPS::DVIExportToPS(dviRenderer &parent, const QString &output_name, const QStringList &options, QPrinter *printer, bool useFontHinting, QPageLayout::Orientation orientation)
0148     : DVIExport(parent)
0149     , printer_(printer)
0150     , orientation_(orientation)
0151 {
0152     // None of these should happen. Paranoia checks.
0153     if (!parent.dviFile) {
0154         return;
0155     }
0156     const dvifile &dvi = *(parent.dviFile);
0157 
0158     const QFileInfo input(dvi.filename);
0159     if (!input.exists() || !input.isReadable()) {
0160         return;
0161     }
0162 
0163     if (dvi.page_offset.isEmpty()) {
0164         return;
0165     }
0166 
0167     if (dvi.numberOfExternalNONPSFiles != 0) {
0168         Q_EMIT error(i18n("<qt>This DVI file refers to external graphic files which are not in PostScript format, and cannot be handled by the "
0169                           "<em>dvips</em> program that Okular uses internally to print or to export to PostScript. The functionality that "
0170                           "you require is therefore unavailable in this version of Okular.</qt>"),
0171                      -1);
0172         return;
0173     }
0174 
0175     if (QStandardPaths::findExecutable(QStringLiteral("dvips")).isEmpty()) {
0176         Q_EMIT error(i18n("<qt><p>Okular could not locate the program <em>dvips</em> on your computer. "
0177                           "That program is essential for the export function to work.</p>"
0178                           "<p>Hint to the perplexed system administrator: Okular uses the PATH environment "
0179                           "variable when looking for programs.</p></qt>"),
0180                      -1);
0181         return;
0182     }
0183 
0184     if (output_name.isEmpty()) {
0185         return;
0186     }
0187 
0188     output_name_ = output_name;
0189 
0190     // There is a major problem with dvips, at least 5.86 and lower: the
0191     // arguments of the option "-pp" refer to TeX-pages, not to
0192     // sequentially numbered pages. For instance "-pp 7" may refer to 3
0193     // or more pages: one page "VII" in the table of contents, a page
0194     // "7" in the text body, and any number of pages "7" in various
0195     // appendices, indices, bibliographies, and so forth. KDVI currently
0196     // uses the following disgusting workaround: if the "options"
0197     // variable is used, the DVI-file is copied to a temporary file, and
0198     // all the page numbers are changed into a sequential ordering
0199     // (using UNIX files, and taking manually care of CPU byte
0200     // ordering). Finally, dvips is then called with the new file, and
0201     // the file is afterwards deleted. Isn't that great?
0202 
0203     // A similar problem occurs with DVI files that contain page size
0204     // information. On these files, dvips pointblank refuses to change
0205     // the page orientation or set another page size. Thus, if the
0206     // DVI-file does contain page size information, we remove that
0207     // information first.
0208 
0209     // input_name is the name of the DVI which is used by dvips, either
0210     // the original file, or a temporary file with a new numbering.
0211     QString input_name = dvi.filename;
0212     if (!options.isEmpty() || dvi.suggestedPageSize != nullptr) {
0213         // Get a name for a temporary file.
0214         // Must open the QTemporaryFile to access the name.
0215         QTemporaryFile tmpfile;
0216         tmpfile.setAutoRemove(false);
0217         tmpfile.open();
0218         tmpfile_name_ = tmpfile.fileName();
0219         tmpfile.close();
0220 
0221         input_name = tmpfile_name_;
0222 
0223         fontPool fp(useFontHinting);
0224         dvifile newFile(&dvi, &fp);
0225 
0226         // Renumber pages
0227         newFile.renumber();
0228 
0229         const quint16 saved_current_page = parent.current_page;
0230         dvifile *saved_dvi = parent.dviFile;
0231         parent.dviFile = &newFile;
0232         parent.errorMsg = QString();
0233 
0234         // Remove any page size information from the file
0235         for (parent.current_page = 0; parent.current_page < newFile.total_pages; parent.current_page++) {
0236             if (parent.current_page < newFile.total_pages) {
0237                 parent.command_pointer = newFile.dvi_Data() + parent.dviFile->page_offset[int(parent.current_page)];
0238                 parent.end_pointer = newFile.dvi_Data() + parent.dviFile->page_offset[int(parent.current_page + 1)];
0239             } else {
0240                 parent.command_pointer = nullptr;
0241                 parent.end_pointer = nullptr;
0242             }
0243 
0244             memset((char *)&parent.currinf.data, 0, sizeof(parent.currinf.data));
0245             parent.currinf.fonttable = &(parent.dviFile->tn_table);
0246             parent.currinf._virtual = nullptr;
0247             parent.prescan(&dviRenderer::prescan_removePageSizeInfo);
0248         }
0249 
0250         parent.current_page = saved_current_page;
0251         parent.dviFile = saved_dvi;
0252         newFile.saveAs(input_name);
0253     }
0254 
0255     QStringList args;
0256     if (!printer) {
0257         // Export hyperlinks
0258         args << QStringLiteral("-z");
0259     }
0260 
0261     if (!options.isEmpty()) {
0262         args += options;
0263     }
0264 
0265     args << input_name << QStringLiteral("-o") << output_name_;
0266 
0267     start(QStringLiteral("dvips"),
0268           args,
0269           QFileInfo(dvi.filename).absolutePath(),
0270           i18n("<qt>The external program 'dvips', which was used to export the file, reported an error. "
0271                "You might wish to look at the <strong>document info dialog</strong> which you will "
0272                "find in the File-Menu for a precise error report.</qt>"));
0273 }
0274 
0275 void DVIExportToPS::finished_impl(int exit_code)
0276 {
0277     if (printer_ && !output_name_.isEmpty()) {
0278         const QFileInfo output(output_name_);
0279         if (output.exists() && output.isReadable()) {
0280             // I'm not 100% sure on this, think we still need to select pages in export to ps above
0281             Okular::FilePrinter::printFile((*printer_), output_name_, orientation_, Okular::FilePrinter::ApplicationDeletesFiles, Okular::FilePrinter::ApplicationSelectsPages, QString());
0282         }
0283     }
0284 
0285     if (!tmpfile_name_.isEmpty()) {
0286         // Delete the file.
0287         QFile(tmpfile_name_).remove();
0288         tmpfile_name_.clear();
0289     }
0290 
0291     DVIExport::finished_impl(exit_code);
0292 }
0293 
0294 void DVIExportToPS::abort_process_impl()
0295 {
0296     if (!tmpfile_name_.isEmpty()) {
0297         // Delete the file.
0298         QFile(tmpfile_name_).remove();
0299         tmpfile_name_.clear();
0300     }
0301 
0302     printer_ = nullptr;
0303 
0304     DVIExport::abort_process_impl();
0305 }