File indexing completed on 2024-05-12 16:06:39
0001 // -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; c-brace-offset: 0; -*- 0002 // 0003 // ghostscript_interface 0004 // 0005 // Part of KDVI - A framework for multipage text/gfx viewers 0006 // 0007 // SPDX-FileCopyrightText: 2004 Stefan Kebekus 0008 // SPDX-License-Identifier: GPL-2.0-or-later 0009 0010 #include <config.h> 0011 0012 #include "debug_dvi.h" 0013 #include "dviFile.h" 0014 #include "pageNumber.h" 0015 #include "psgs.h" 0016 #include "psheader.cpp" 0017 0018 #include <KLocalizedString> 0019 #include <KProcess> 0020 #include <QTemporaryFile> 0021 #include <QUrl> 0022 0023 #include <QDir> 0024 #include <QLoggingCategory> 0025 #include <QPainter> 0026 #include <QPixmap> 0027 #include <QStandardPaths> 0028 #include <QTextStream> 0029 #include <QTimer> 0030 0031 //#define DEBUG_PSGS 0032 0033 // extern char psheader[]; 0034 0035 pageInfo::pageInfo(const QString &_PostScriptString) 0036 { 0037 PostScriptString = new QString(_PostScriptString); 0038 background = Qt::white; 0039 permanentBackground = Qt::white; 0040 } 0041 0042 pageInfo::~pageInfo() 0043 { 0044 if (PostScriptString != nullptr) { 0045 delete PostScriptString; 0046 } 0047 } 0048 0049 // ====================================================== 0050 0051 ghostscript_interface::ghostscript_interface() 0052 { 0053 PostScriptHeaderString = new QString(); 0054 0055 knownDevices.append(QStringLiteral("png16m")); 0056 knownDevices.append(QStringLiteral("jpeg")); 0057 knownDevices.append(QStringLiteral("pnn")); 0058 knownDevices.append(QStringLiteral("pnnraw")); 0059 gsDevice = knownDevices.begin(); 0060 } 0061 0062 ghostscript_interface::~ghostscript_interface() 0063 { 0064 if (PostScriptHeaderString != nullptr) { 0065 delete PostScriptHeaderString; 0066 } 0067 qDeleteAll(pageList); 0068 } 0069 0070 void ghostscript_interface::setPostScript(const quint16 page, const QString &PostScript) 0071 { 0072 #ifdef DEBUG_PSGS 0073 qCDebug(OkularDviDebug) << "ghostscript_interface::setPostScript( " << page << ", ... )"; 0074 #endif 0075 0076 if (pageList.value(page) == nullptr) { 0077 pageInfo *info = new pageInfo(PostScript); 0078 // Check if dict is big enough 0079 if (pageList.count() > pageList.capacity() - 2) { 0080 pageList.reserve(pageList.capacity() * 2); 0081 } 0082 pageList.insert(page, info); 0083 } else { 0084 *(pageList.value(page)->PostScriptString) = PostScript; 0085 } 0086 } 0087 0088 void ghostscript_interface::setIncludePath(const QString &_includePath) 0089 { 0090 if (_includePath.isEmpty()) { 0091 includePath = QLatin1Char('*'); // Allow all files 0092 } else { 0093 includePath = _includePath + QStringLiteral("/*"); 0094 } 0095 } 0096 0097 void ghostscript_interface::setBackgroundColor(const quint16 page, const QColor &background_color, bool permanent) 0098 { 0099 #ifdef DEBUG_PSGS 0100 qCDebug(OkularDviDebug) << "ghostscript_interface::setBackgroundColor( " << page << ", " << background_color << " )"; 0101 #endif 0102 0103 if (pageList.value(page) == nullptr) { 0104 pageInfo *info = new pageInfo(QString()); 0105 info->background = background_color; 0106 if (permanent) { 0107 info->permanentBackground = background_color; 0108 } 0109 // Check if dict is big enough 0110 if (pageList.count() > pageList.capacity() - 2) { 0111 pageList.reserve(pageList.capacity() * 2); 0112 } 0113 pageList.insert(page, info); 0114 } else { 0115 pageList.value(page)->background = background_color; 0116 if (permanent) { 0117 pageList.value(page)->permanentBackground = background_color; 0118 } 0119 } 0120 } 0121 0122 void ghostscript_interface::restoreBackgroundColor(const quint16 page) 0123 { 0124 #ifdef DEBUG_PSGS 0125 qCDebug(OkularDviDebug) << "ghostscript_interface::restoreBackgroundColor( " << page << " )"; 0126 #endif 0127 if (pageList.value(page) == nullptr) { 0128 return; 0129 } 0130 0131 pageInfo *info = pageList.value(page); 0132 info->background = info->permanentBackground; 0133 } 0134 0135 // Returns the background color for a certain page. This color is 0136 // always guaranteed to be valid 0137 0138 QColor ghostscript_interface::getBackgroundColor(const quint16 page) const 0139 { 0140 #ifdef DEBUG_PSGS 0141 qCDebug(OkularDviDebug) << "ghostscript_interface::getBackgroundColor( " << page << " )"; 0142 #endif 0143 0144 if (pageList.value(page) == nullptr) { 0145 return Qt::white; 0146 } else { 0147 return pageList.value(page)->background; 0148 } 0149 } 0150 0151 void ghostscript_interface::clear() 0152 { 0153 PostScriptHeaderString->truncate(0); 0154 0155 // Deletes all items, removes temporary files, etc. 0156 qDeleteAll(pageList); 0157 pageList.clear(); 0158 } 0159 0160 void ghostscript_interface::gs_generate_graphics_file(const quint16 page, const QString &filename, long magnification) 0161 { 0162 #ifdef DEBUG_PSGS 0163 qCDebug(OkularDviDebug) << "ghostscript_interface::gs_generate_graphics_file( " << page << ", " << filename << " )"; 0164 #endif 0165 0166 if (knownDevices.isEmpty()) { 0167 qCCritical(OkularDviDebug) << "No known devices found"; 0168 return; 0169 } 0170 0171 // Make sure gs is in PATH and not just in the CWD 0172 static const QString gsFullPath = QStandardPaths::findExecutable(QStringLiteral("gs")); 0173 if (gsFullPath.isEmpty()) { 0174 qCCritical(OkularDviDebug) << "gs is not in path"; 0175 return; 0176 } 0177 0178 pageInfo *info = pageList.value(page); 0179 0180 // Generate a PNG-file 0181 // Step 1: Write the PostScriptString to a File 0182 QTemporaryFile PSfile(QDir::tempPath() + QLatin1String("/okular_XXXXXX.ps")); 0183 PSfile.setAutoRemove(false); 0184 PSfile.open(); 0185 const QString PSfileName = PSfile.fileName(); 0186 0187 QTextStream os(&PSfile); 0188 os << "%!PS-Adobe-2.0\n" 0189 << "%%Creator: kdvi\n" 0190 << "%%Title: KDVI temporary PostScript\n" 0191 << "%%Pages: 1\n" 0192 << "%%PageOrder: Ascend\n" 0193 // HSize and VSize in 1/72 inch 0194 << "%%BoundingBox: 0 0 " << (qint32)(72 * (pixel_page_w / resolution)) << ' ' << (qint32)(72 * (pixel_page_h / resolution)) << '\n' 0195 << "%%EndComments\n" 0196 << "%!\n" 0197 << psheader 0198 << "TeXDict begin " 0199 // HSize in (1/(65781.76*72))inch 0200 << (qint32)(72 * 65781 * (pixel_page_w / resolution)) 0201 << ' ' 0202 // VSize in (1/(65781.76*72))inch 0203 << (qint32)(72 * 65781 * (pixel_page_h / resolution)) 0204 << ' ' 0205 // Magnification 0206 << (qint32)(magnification) 0207 // dpi and vdpi 0208 << " 300 300" 0209 // Name 0210 << " (test.dvi)" 0211 << " @start end\n" 0212 << "TeXDict begin\n" 0213 // Start page 0214 << "1 0 bop 0 0 a \n"; 0215 0216 if (!PostScriptHeaderString->toLatin1().isNull()) { 0217 os << PostScriptHeaderString->toLatin1(); 0218 } 0219 0220 if (info->background != Qt::white) { 0221 QString colorCommand = QStringLiteral("gsave %1 %2 %3 setrgbcolor clippath fill grestore\n").arg(info->background.red() / 255.0).arg(info->background.green() / 255.0).arg(info->background.blue() / 255.0); 0222 os << colorCommand.toLatin1(); 0223 } 0224 0225 if (!info->PostScriptString->isNull()) { 0226 os << *(info->PostScriptString); 0227 } 0228 0229 os << "end\n" 0230 << "showpage \n"; 0231 0232 PSfile.close(); 0233 0234 // Step 2: Call GS with the File 0235 QFile::remove(filename); 0236 KProcess proc; 0237 proc.setOutputChannelMode(KProcess::SeparateChannels); 0238 QStringList argus; 0239 argus << gsFullPath; 0240 argus << QStringLiteral("-dSAFER") << QStringLiteral("-dPARANOIDSAFER") << QStringLiteral("-dDELAYSAFER") << QStringLiteral("-dNOPAUSE") << QStringLiteral("-dBATCH"); 0241 argus << QStringLiteral("-sDEVICE=%1").arg(*gsDevice); 0242 argus << QStringLiteral("-sOutputFile=%1").arg(filename); 0243 argus << QStringLiteral("-sExtraIncludePath=%1").arg(includePath); 0244 argus << QStringLiteral("-g%1x%2").arg(pixel_page_w).arg(pixel_page_h); // page size in pixels 0245 argus << QStringLiteral("-r%1").arg(resolution); // resolution in dpi 0246 argus << QStringLiteral("-dTextAlphaBits=4 -dGraphicsAlphaBits=2"); // Antialiasing 0247 argus << QStringLiteral("-c") << QStringLiteral("<< /PermitFileReading [ ExtraIncludePath ] /PermitFileWriting [] /PermitFileControl [] >> setuserparams .locksafe"); 0248 argus << QStringLiteral("-f") << PSfileName; 0249 0250 #ifdef DEBUG_PSGS 0251 qCDebug(OkularDviDebug) << argus.join(" "); 0252 #endif 0253 0254 proc << argus; 0255 int res = proc.execute(); 0256 0257 if (res) { 0258 // Starting ghostscript did not work. 0259 // TODO: Issue error message, switch PS support off. 0260 qCCritical(OkularDviDebug) << "ghostview could not be started"; 0261 } 0262 0263 PSfile.remove(); 0264 0265 // Check if gs has indeed produced a file. 0266 if (QFile::exists(filename) == false) { 0267 qCCritical(OkularDviDebug) << "GS did not produce output."; 0268 0269 // No. Check is the reason is that the device is not compiled into 0270 // ghostscript. If so, try again with another device. 0271 QString GSoutput; 0272 proc.setReadChannel(QProcess::StandardOutput); 0273 while (proc.canReadLine()) { 0274 GSoutput = QString::fromLocal8Bit(proc.readLine()); 0275 if (GSoutput.contains(QStringLiteral("Unknown device"))) { 0276 qCDebug(OkularDviDebug) << QString::fromLatin1( 0277 "The version of ghostview installed on this computer does not support " 0278 "the '%1' ghostview device driver.") 0279 .arg(*gsDevice); 0280 knownDevices.erase(gsDevice); 0281 gsDevice = knownDevices.begin(); 0282 if (knownDevices.isEmpty()) { 0283 // TODO: show a requestor of some sort. 0284 Q_EMIT error(i18n("The version of Ghostview that is installed on this computer does not contain " 0285 "any of the Ghostview device drivers that are known to Okular. PostScript " 0286 "support has therefore been turned off in Okular."), 0287 -1); 0288 } else { 0289 qCDebug(OkularDviDebug) << QStringLiteral("Okular will now try to use the '%1' device driver.").arg(*gsDevice); 0290 gs_generate_graphics_file(page, filename, magnification); 0291 } 0292 return; 0293 } 0294 } 0295 } 0296 } 0297 0298 void ghostscript_interface::graphics(const quint16 page, double dpi, long magnification, QPainter *paint) 0299 { 0300 #ifdef DEBUG_PSGS 0301 qCDebug(OkularDviDebug) << "ghostscript_interface::graphics( " << page << ", " << dpi << ", ... ) called."; 0302 #endif 0303 0304 if (paint == nullptr) { 0305 qCCritical(OkularDviDebug) << "ghostscript_interface::graphics(PageNumber page, double dpi, long magnification, QPainter *paint) called with paint == 0"; 0306 return; 0307 } 0308 0309 resolution = dpi; 0310 0311 pixel_page_w = paint->viewport().width(); 0312 pixel_page_h = paint->viewport().height(); 0313 0314 pageInfo *info = pageList.value(page); 0315 0316 // No PostScript? Then return immediately. 0317 if ((info == nullptr) || (info->PostScriptString->isEmpty())) { 0318 #ifdef DEBUG_PSGS 0319 qCDebug(OkularDviDebug) << "No PostScript found. Not drawing anything."; 0320 #endif 0321 return; 0322 } 0323 0324 QTemporaryFile gfxFile; 0325 gfxFile.open(); 0326 const QString gfxFileName = gfxFile.fileName(); 0327 // We are want the filename, not the file. 0328 gfxFile.close(); 0329 0330 gs_generate_graphics_file(page, gfxFileName, magnification); 0331 0332 QImage MemoryCopy(gfxFileName); 0333 paint->drawImage(0, 0, MemoryCopy); 0334 return; 0335 } 0336 0337 QString ghostscript_interface::locateEPSfile(const QString &filename, const QUrl &base) 0338 { 0339 // If the base URL indicates that the DVI file is local, try to find 0340 // the graphics file in the directory where the DVI file resides 0341 if (base.isLocalFile()) { 0342 QString path = base.path(); // -> "/bar/foo.dvi" 0343 QFileInfo fi1(path); 0344 QFileInfo fi2(fi1.dir(), filename); 0345 if (fi2.exists()) { 0346 return fi2.absoluteFilePath(); 0347 } 0348 } 0349 0350 // Otherwise, use kpsewhich to find the eps file. 0351 // Make sure kpsewhich is in PATH and not just in the CWD 0352 static const QString fullPath = QStandardPaths::findExecutable(QStringLiteral("kpsewhich")); 0353 if (fullPath.isEmpty()) { 0354 return {}; 0355 } 0356 0357 KProcess proc; 0358 proc << fullPath << filename; 0359 proc.execute(); 0360 return QString::fromLocal8Bit(proc.readLine().trimmed()); 0361 }