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 }