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 }