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  * SPDX-FileCopyrightText: 1994 Paul Vojta. All rights reserved.
0004  *
0005  * SPDX-License-Identifier: BSD-2-Clause
0006  *
0007  * NOTE:
0008  *        xdvi is based on prior work as noted in the modification history, below.
0009  */
0010 
0011 /*
0012  * DVI previewer for X.
0013  *
0014  * Eric Cooper, CMU, September 1985.
0015  *
0016  * Code derived from dvi-imagen.c.
0017  *
0018  * Modification history:
0019  * 1/1986        Modified for X.10        --Bob Scheifler, MIT LCS.
0020  * 7/1988        Modified for X.11        --Mark Eichin, MIT
0021  * 12/1988        Added 'R' option, toolkit, magnifying glass
0022  *                                        --Paul Vojta, UC Berkeley.
0023  * 2/1989        Added tpic support       --Jeffrey Lee, U of Toronto
0024  * 4/1989        Modified for System V    --Donald Richardson, Clarkson Univ.
0025  * 3/1990        Added VMS support        --Scott Allendorf, U of Iowa
0026  * 7/1990        Added reflection mode    --Michael Pak, Hebrew U of Jerusalem
0027  * 1/1992        Added greyscale code     --Till Brychcy, Techn. Univ. Muenchen
0028  *                                          and Lee Hetherington, MIT
0029  * 4/1994        Added DPS support, bounding box
0030  *                                        --Ricardo Telichevesky
0031  *                                          and Luis Miguel Silveira, MIT RLE.
0032  */
0033 
0034 #include <config.h>
0035 
0036 #include "debug_dvi.h"
0037 #include "dvi.h"
0038 #include "dviFile.h"
0039 #include "fontpool.h"
0040 #include "pageSize.h"
0041 
0042 #include <KLocalizedString>
0043 
0044 #include <QLoggingCategory>
0045 #include <QProcess>
0046 #include <QStandardPaths>
0047 #include <QSysInfo>
0048 #include <QTemporaryFile>
0049 
0050 #include <cstdlib>
0051 
0052 dvifile::dvifile(const dvifile *old, fontPool *fp)
0053 {
0054     errorMsg.clear();
0055     errorCounter = 0;
0056     page_offset.clear();
0057     suggestedPageSize = nullptr;
0058     numberOfExternalPSFiles = 0;
0059     numberOfExternalNONPSFiles = 0;
0060     sourceSpecialMarker = old->sourceSpecialMarker;
0061     have_complainedAboutMissingPDF2PS = false;
0062 
0063     dviData = old->dviData;
0064 
0065     filename = old->filename;
0066     size_of_file = old->size_of_file;
0067     end_pointer = dvi_Data() + size_of_file;
0068     if (dvi_Data() == nullptr) {
0069         qCCritical(OkularDviDebug) << "Not enough memory to copy the DVI-file.";
0070         return;
0071     }
0072 
0073     font_pool = fp;
0074     filename = old->filename;
0075     generatorString = old->generatorString;
0076     total_pages = old->total_pages;
0077 
0078     tn_table.clear();
0079     process_preamble();
0080     find_postamble();
0081     read_postamble();
0082     prepare_pages();
0083 }
0084 
0085 void dvifile::process_preamble()
0086 {
0087     command_pointer = dvi_Data();
0088 
0089     quint8 magic_number = readUINT8();
0090     if (magic_number != PRE) {
0091         errorMsg = i18n("The DVI file does not start with the preamble.");
0092         return;
0093     }
0094     magic_number = readUINT8();
0095     if (magic_number != 2) {
0096         errorMsg = i18n(
0097             "The DVI file contains the wrong version of DVI output for this program. "
0098             "Hint: If you use the typesetting system Omega, you have to use a special "
0099             "program, such as oxdvi.");
0100         return;
0101     }
0102 
0103     /** numerator, denominator and the magnification value that describe
0104         how many centimeters there are in one TeX unit, as explained in
0105         section A.3 of the DVI driver standard, Level 0, published by
0106         the TUG DVI driver standards committee. */
0107     quint32 numerator = readUINT32();
0108     quint32 denominator = readUINT32();
0109     _magnification = readUINT32();
0110 
0111     cmPerDVIunit = (double(numerator) / double(denominator)) * (double(_magnification) / 1000.0) * (1.0 / 1e5);
0112 
0113     // Read the generatorString (such as "TeX output ..." from the
0114     // DVI-File). The variable "magic_number" holds the length of the
0115     // string.
0116     char job_id[300];
0117     magic_number = readUINT8();
0118     strncpy(job_id, (char *)command_pointer, magic_number);
0119     job_id[magic_number] = '\0';
0120     generatorString = QString::fromLocal8Bit(job_id);
0121 }
0122 
0123 /** find_postamble locates the beginning of the postamble and leaves
0124     the file ready to start reading at that location. */
0125 
0126 void dvifile::find_postamble()
0127 {
0128     // Move backwards through the TRAILER bytes
0129     command_pointer = dvi_Data() + size_of_file - 1;
0130     while ((*command_pointer == TRAILER) && (command_pointer > dvi_Data())) {
0131         command_pointer--;
0132     }
0133     if (command_pointer == dvi_Data()) {
0134         errorMsg = i18n("The DVI file is badly corrupted. Okular was not able to find the postamble.");
0135         return;
0136     }
0137 
0138     // And this is finally the pointer to the beginning of the postamble
0139     command_pointer -= 4;
0140     beginning_of_postamble = readUINT32();
0141     command_pointer = dvi_Data() + beginning_of_postamble;
0142 }
0143 
0144 void dvifile::read_postamble()
0145 {
0146     quint8 magic_byte = readUINT8();
0147     if (magic_byte != POST) {
0148         errorMsg = i18n("The postamble does not begin with the POST command.");
0149         return;
0150     }
0151     last_page_offset = readUINT32();
0152 
0153     // Skip the numerator, denominator and magnification, the largest
0154     // box height and width and the maximal depth of the stack. These
0155     // are not used at the moment.
0156     command_pointer += 4 + 4 + 4 + 4 + 4 + 2;
0157 
0158     // The number of pages is more interesting for us.
0159     total_pages = readUINT16();
0160 
0161     // As a next step, read the font definitions.
0162     quint8 cmnd = readUINT8();
0163     while (cmnd >= FNTDEF1 && cmnd <= FNTDEF4) {
0164         quint32 TeXnumber = readUINT(cmnd - FNTDEF1 + 1);
0165         quint32 checksum = readUINT32(); // Checksum of the font, as found by TeX in the TFM file
0166 
0167         // Read scale and design factor, and the name of the font. All
0168         // these are explained in section A.4 of the DVI driver standard,
0169         // Level 0, published by the TUG DVI driver standards committee
0170         quint32 scale = readUINT32();
0171         quint32 design = readUINT32();
0172         quint16 len = readUINT8() + readUINT8(); // Length of the font name, including the directory name
0173         QByteArray fontname((char *)command_pointer, len);
0174         command_pointer += len;
0175 
0176 #ifdef DEBUG_FONTS
0177         qCDebug(OkularDviDebug) << "Postamble: define font \"" << fontname << "\" scale=" << scale << " design=" << design;
0178 #endif
0179 
0180         // According to section A.4 of the DVI driver standard, this font
0181         // shall be enlarged by the following factor before it is used.
0182         double enlargement_factor = (double(scale) * double(_magnification)) / (double(design) * 1000.0);
0183 
0184         if (font_pool != nullptr) {
0185             TeXFontDefinition *fontp = font_pool->appendx(QString::fromLocal8Bit(fontname), checksum, scale, enlargement_factor);
0186 
0187             // Insert font in dictionary and make sure the dictionary is big
0188             // enough.
0189             if (tn_table.capacity() - 2 <= tn_table.count()) {
0190                 // Not quite optimal. The size of the dictionary should be a
0191                 // prime for optimal performance. I don't care.
0192                 tn_table.reserve(tn_table.capacity() * 2);
0193             }
0194             tn_table.insert(TeXnumber, fontp);
0195         }
0196 
0197         // Read the next command
0198         cmnd = readUINT8();
0199     }
0200 
0201     if (cmnd != POSTPOST) {
0202         errorMsg = i18n("The postamble contained a command other than FNTDEF.");
0203         return;
0204     }
0205 
0206     // Now we remove all those fonts from the memory which are no longer
0207     // in use.
0208     if (font_pool != nullptr) {
0209         font_pool->release_fonts();
0210     }
0211 }
0212 
0213 void dvifile::prepare_pages()
0214 {
0215 #ifdef DEBUG_DVIFILE
0216     qCDebug(OkularDviDebug) << "prepare_pages";
0217 #endif
0218     if (total_pages == 0) {
0219         return;
0220     }
0221 
0222     page_offset.resize(total_pages + 1);
0223     if (page_offset.size() < (total_pages + 1)) {
0224         qCCritical(OkularDviDebug) << "No memory for page list!";
0225         return;
0226     }
0227     for (int i = 0; i <= total_pages; i++) {
0228         page_offset[i] = 0;
0229     }
0230 
0231     page_offset[int(total_pages)] = beginning_of_postamble;
0232     int j = total_pages - 1;
0233     page_offset[j] = last_page_offset;
0234 
0235     // Follow back pointers through pages in the DVI file, storing the
0236     // offsets in the page_offset table.
0237     while (j > 0) {
0238         command_pointer = dvi_Data() + page_offset[j--];
0239         if (readUINT8() != BOP) {
0240             errorMsg = i18n("The page %1 does not start with the BOP command.", j + 1);
0241             return;
0242         }
0243         command_pointer += 10 * 4;
0244         page_offset[j] = readUINT32();
0245         if ((dvi_Data() + page_offset[j] < dvi_Data()) || (dvi_Data() + page_offset[j] > dvi_Data() + size_of_file)) {
0246             break;
0247         }
0248     }
0249 }
0250 
0251 dvifile::dvifile(const QString &fname, fontPool *pool)
0252 {
0253 #ifdef DEBUG_DVIFILE
0254     qCDebug(OkularDviDebug) << "init_dvi_file: " << fname;
0255 #endif
0256 
0257     errorMsg.clear();
0258     errorCounter = 0;
0259     page_offset.clear();
0260     suggestedPageSize = nullptr;
0261     numberOfExternalPSFiles = 0;
0262     numberOfExternalNONPSFiles = 0;
0263     font_pool = pool;
0264     sourceSpecialMarker = true;
0265     have_complainedAboutMissingPDF2PS = false;
0266 
0267     QFile file(fname);
0268     filename = file.fileName();
0269     file.open(QIODevice::ReadOnly);
0270     size_of_file = file.size();
0271     dviData.resize(size_of_file);
0272     // Sets the end pointer for the bigEndianByteReader so that the
0273     // whole memory buffer is readable
0274     end_pointer = dvi_Data() + size_of_file;
0275     if (dvi_Data() == nullptr) {
0276         qCCritical(OkularDviDebug) << "Not enough memory to load the DVI-file.";
0277         return;
0278     }
0279     file.read((char *)dvi_Data(), size_of_file);
0280     file.close();
0281     if (file.error() != QFile::NoError) {
0282         qCCritical(OkularDviDebug) << "Could not load the DVI-file.";
0283         return;
0284     }
0285 
0286     tn_table.clear();
0287 
0288     total_pages = 0;
0289     process_preamble();
0290     find_postamble();
0291     read_postamble();
0292     prepare_pages();
0293 
0294     return;
0295 }
0296 
0297 dvifile::~dvifile()
0298 {
0299 #ifdef DEBUG_DVIFILE
0300     qCDebug(OkularDviDebug) << "destroy dvi-file";
0301 #endif
0302 
0303     // Delete converted PDF files
0304     QMapIterator<QString, QString> i(convertedFiles);
0305     while (i.hasNext()) {
0306         i.next();
0307         QFile::remove(i.value());
0308     }
0309 
0310     if (suggestedPageSize != nullptr) {
0311         delete suggestedPageSize;
0312     }
0313     if (font_pool != nullptr) {
0314         font_pool->mark_fonts_as_unused();
0315     }
0316 }
0317 
0318 void dvifile::renumber()
0319 {
0320     dviData.detach();
0321 
0322     // Write the page number to the file, taking good care of byte
0323     // orderings.
0324     bool bigEndian = (QSysInfo::ByteOrder == QSysInfo::BigEndian);
0325 
0326     for (int i = 1; i <= total_pages; i++) {
0327         quint8 *ptr = dviData.data() + page_offset[i - 1] + 1;
0328         quint8 *num = (quint8 *)&i;
0329         for (quint8 j = 0; j < 4; j++) {
0330             if (bigEndian) {
0331                 *(ptr++) = num[0];
0332                 *(ptr++) = num[1];
0333                 *(ptr++) = num[2];
0334                 *(ptr++) = num[3];
0335             } else {
0336                 *(ptr++) = num[3];
0337                 *(ptr++) = num[2];
0338                 *(ptr++) = num[1];
0339                 *(ptr++) = num[0];
0340             }
0341         }
0342     }
0343 }
0344 
0345 void dvifile::pdf2psNotFound(const QString &PDFFilename, QString *converrorms)
0346 {
0347     // Indicates that conversion failed, won't try again.
0348     convertedFiles[PDFFilename].clear();
0349     if (converrorms != nullptr && !have_complainedAboutMissingPDF2PS) {
0350         *converrorms = i18n(
0351             "<qt><p>The external program <strong>pdf2ps</strong> could not be started. As a result, "
0352             "the PDF-file %1 could not be converted to PostScript. Some graphic elements in your "
0353             "document will therefore not be displayed.</p>"
0354             "<p><b>Possible reason:</b> The program <strong>pdf2ps</strong> may not be installed "
0355             "on your system, or cannot be found in the current search path.</p>"
0356             "<p><b>What you can do:</b> The program <strong>pdf2ps</strong> is normally "
0357             "contained in distributions of the ghostscript PostScript interpreter system. If "
0358             "ghostscript is not installed on your system, you could install it now. "
0359             "If you are sure that ghostscript is installed, try to use <strong>pdf2ps</strong> "
0360             "from the command line to check if it really works.</p><p><em>PATH:</em> %2</p></qt>",
0361             PDFFilename,
0362             QString::fromLocal8Bit(qgetenv("PATH")));
0363         have_complainedAboutMissingPDF2PS = true;
0364     }
0365 }
0366 
0367 QString dvifile::convertPDFtoPS(const QString &PDFFilename, QString *converrorms)
0368 {
0369     // Check if the PDFFile is known
0370     QMap<QString, QString>::Iterator it = convertedFiles.find(PDFFilename);
0371     if (it != convertedFiles.end()) {
0372         // PDF-File is known. Good.
0373         return it.value();
0374     }
0375 
0376     // Make sure pdf2ps is in PATH and not just in the CWD
0377     static const QString fullPath = QStandardPaths::findExecutable(QStringLiteral("pdf2ps"));
0378     if (!fullPath.isEmpty()) {
0379         pdf2psNotFound(PDFFilename, converrorms);
0380         return QString();
0381     }
0382 
0383     // Get the name of a temporary file.
0384     // Must open the QTemporaryFile to access the name.
0385     QTemporaryFile tmpfile;
0386     tmpfile.open();
0387     const QString convertedFileName = tmpfile.fileName();
0388     tmpfile.close();
0389 
0390     // Use pdf2ps to do the conversion
0391     QProcess pdf2ps;
0392     pdf2ps.setProcessChannelMode(QProcess::MergedChannels);
0393     pdf2ps.start(fullPath, QStringList() << PDFFilename << convertedFileName, QIODevice::ReadOnly | QIODevice::Text);
0394 
0395     if (!pdf2ps.waitForStarted()) {
0396         pdf2psNotFound(PDFFilename, converrorms);
0397         return QString();
0398     }
0399 
0400     // We wait here while the external program runs concurrently.
0401     pdf2ps.waitForFinished(-1);
0402 
0403     if (!QFile::exists(convertedFileName) || pdf2ps.exitCode() != 0) {
0404         // Indicates that conversion failed, won't try again.
0405         convertedFiles[PDFFilename].clear();
0406         if (converrorms != nullptr) {
0407             const QString output = QString::fromLocal8Bit(pdf2ps.readAll());
0408 
0409             *converrorms = i18n(
0410                 "<qt><p>The PDF-file %1 could not be converted to PostScript. Some graphic elements in your "
0411                 "document will therefore not be displayed.</p>"
0412                 "<p><b>Possible reason:</b> The file %1 might be broken, or might not be a PDF-file at all. "
0413                 "This is the output of the <strong>pdf2ps</strong> program that Okular used:</p>"
0414                 "<p><strong>%2</strong></p></qt>",
0415                 PDFFilename,
0416                 output);
0417         }
0418         return QString();
0419     }
0420     // Save name of converted file to buffer, so PDF file won't be
0421     // converted again, and files can be deleted when *this is
0422     // deconstructed.
0423     convertedFiles[PDFFilename] = convertedFileName;
0424 
0425     tmpfile.setAutoRemove(false);
0426     return convertedFileName;
0427 }
0428 
0429 bool dvifile::saveAs(const QString &filename)
0430 {
0431     if (dvi_Data() == nullptr) {
0432         return false;
0433     }
0434 
0435     QFile out(filename);
0436     if (out.open(QIODevice::WriteOnly) == false) {
0437         return false;
0438     }
0439     if (out.write((char *)(dvi_Data()), size_of_file) == -1) {
0440         return false;
0441     }
0442     out.close();
0443     return true;
0444 }