File indexing completed on 2024-04-28 15:14:04
0001 /*************************************************************************** 0002 File : TeXRenderer.cc 0003 Project : LabPlot 0004 Description : TeX renderer class 0005 -------------------------------------------------------------------- 0006 Copyright : (C) 2008-2016 by Alexander Semke (alexander.semke@web.de) 0007 Copyright : (C) 2012 by Stefan Gerlach (stefan.gerlach@uni-konstanz.de) 0008 0009 ***************************************************************************/ 0010 0011 /*************************************************************************** 0012 * * 0013 * This program is free software; you can redistribute it and/or modify * 0014 * it under the terms of the GNU General Public License as published by * 0015 * the Free Software Foundation; either version 2 of the License, or * 0016 * (at your option) any later version. * 0017 * * 0018 * This program is distributed in the hope that it will be useful, * 0019 * but WITHOUT ANY WARRANTY; without even the implied warranty of * 0020 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 0021 * GNU General Public License for more details. * 0022 * * 0023 * You should have received a copy of the GNU General Public License * 0024 * along with this program; if not, write to the Free Software * 0025 * Foundation, Inc., 51 Franklin Street, Fifth Floor, * 0026 * Boston, MA 02110-1301 USA * 0027 * * 0028 ***************************************************************************/ 0029 #include "TeXRenderer.h" 0030 #include "backend/lib/macros.h" 0031 0032 #include <KConfigGroup> 0033 #include <KSharedConfig> 0034 0035 #include <QColor> 0036 #include <QDir> 0037 #include <QImage> 0038 #include <QProcess> 0039 #include <QStandardPaths> 0040 #include <QTemporaryFile> 0041 #include <QTextStream> 0042 0043 /*! 0044 \class TeXRenderer 0045 \brief Implements rendering of latex code to a PNG image. 0046 0047 Uses latex engine specified by the user (default xelatex) to render LaTeX text 0048 0049 \ingroup tools 0050 */ 0051 QImage TeXRenderer::renderImageLaTeX(const QString& teXString, bool* success, const TeXRenderer::Formatting& format) { 0052 const QColor& fontColor = format.fontColor; 0053 const QColor& backgroundColor = format.backgroundColor; 0054 const int fontSize = format.fontSize; 0055 const QString& fontFamily = format.fontFamily; 0056 const int dpi = format.dpi; 0057 0058 //determine the temp directory where the produced files are going to be created 0059 QString tempPath; 0060 #ifdef Q_OS_LINUX 0061 //on linux try to use shared memory device first if available 0062 static bool useShm = QDir("/dev/shm/").exists(); 0063 if (useShm) 0064 tempPath = "/dev/shm/"; 0065 else 0066 tempPath = QDir::tempPath(); 0067 #else 0068 tempPath = QDir::tempPath(); 0069 #endif 0070 0071 // make sure we have preview.sty available 0072 if (!tempPath.contains(QLatin1String("preview.sty"))) { 0073 QString file = QStandardPaths::locate(QStandardPaths::AppDataLocation, QLatin1String("latex/preview.sty")); 0074 if (file.isEmpty()) { 0075 WARN("Couldn't find preview.sty."); 0076 *success = false; 0077 return QImage(); 0078 } 0079 else 0080 QFile::copy(file, tempPath + QLatin1String("/") + QLatin1String("preview.sty")); 0081 } 0082 0083 //create a temporary file 0084 QTemporaryFile file(tempPath + QLatin1String("/") + "labplot_XXXXXX.tex"); 0085 // FOR DEBUG: file.setAutoRemove(false); 0086 // DEBUG("temp file path = " << file.fileName().toUtf8().constData()); 0087 if (file.open()) { 0088 QDir::setCurrent(tempPath); 0089 } else { 0090 WARN("Couldn't open the file " << STDSTRING(file.fileName())); 0091 *success = false; 0092 return QImage(); 0093 } 0094 0095 //determine latex engine to be used 0096 KConfigGroup group = KSharedConfig::openConfig()->group("Settings_Worksheet"); 0097 QString engine = group.readEntry("LaTeXEngine", "pdflatex"); 0098 0099 // create latex code 0100 QTextStream out(&file); 0101 int headerIndex = teXString.indexOf("\\begin{document}"); 0102 QString body; 0103 if (headerIndex != -1) { 0104 //user provided a complete latex document -> extract the document header and body 0105 QString header = teXString.left(headerIndex); 0106 int footerIndex = teXString.indexOf("\\end{document}"); 0107 body = teXString.mid(headerIndex + 16, footerIndex - headerIndex - 16); 0108 out << header; 0109 } else { 0110 //user simply provided a document body (assume it's a math. expression) -> add a minimal header 0111 out << "\\documentclass{minimal}"; 0112 if (teXString.indexOf('$') == -1) 0113 body = '$' + teXString + '$'; 0114 else 0115 body = teXString; 0116 0117 //replace line breaks with tex command for a line break '\\' 0118 body = body.replace(QLatin1String("\n"), QLatin1String("\\\\")); 0119 } 0120 0121 if (engine == "xelatex" || engine == "lualatex") { 0122 out << "\\usepackage{xltxtra}"; 0123 out << "\\defaultfontfeatures{Ligatures=TeX}"; 0124 if (!fontFamily.isEmpty()) 0125 out << "\\setmainfont[Mapping=tex-text]{" << fontFamily << "}"; 0126 } 0127 0128 out << "\\usepackage{color}"; 0129 out << "\\usepackage[active,displaymath,textmath,tightpage]{preview}"; 0130 // TODO: this fails with pdflatex 0131 //out << "\\usepackage{mathtools}"; 0132 out << "\\begin{document}"; 0133 out << "\\begin{preview}"; 0134 out << "\\colorbox[rgb]{" << backgroundColor.redF() << ',' << backgroundColor.greenF() << ',' << backgroundColor.blueF() << "}{"; 0135 out << "\\fontsize{" << QString::number(fontSize) << "}{" << QString::number(fontSize) << "}\\selectfont"; 0136 out << "\\color[rgb]{" << fontColor.redF() << ',' << fontColor.greenF() << ',' << fontColor.blueF() << "}"; 0137 out << body; 0138 out << "}"; 0139 out << "\\end{preview}"; 0140 out << "\\end{document}"; 0141 out.flush(); 0142 if (engine == "latex") 0143 return imageFromDVI(file, dpi, success); 0144 else 0145 return imageFromPDF(file, dpi, engine, success); 0146 } 0147 0148 // TEX -> PDF -> PNG 0149 QImage TeXRenderer::imageFromPDF(const QTemporaryFile& file, const int dpi, const QString& engine, bool* success) { 0150 QFileInfo fi(file.fileName()); 0151 const QString& baseName = fi.completeBaseName(); 0152 0153 // pdflatex: produce the PDF file 0154 QProcess latexProcess; 0155 #if defined(HAVE_WINDOWS) 0156 latexProcess.setNativeArguments("-interaction=batchmode " + file.fileName()); 0157 latexProcess.start(engine, QStringList() << QString()); 0158 #else 0159 latexProcess.start(engine, QStringList() << "-interaction=batchmode" << file.fileName()); 0160 #endif 0161 0162 if (!latexProcess.waitForFinished() || latexProcess.exitCode() != 0) { 0163 WARN("pdflatex process failed, exit code = " << latexProcess.exitCode()); 0164 *success = false; 0165 QFile::remove(baseName + ".aux"); 0166 QFile::remove(baseName + ".log"); 0167 return QImage(); 0168 } 0169 0170 // convert: PDF -> PNG 0171 QProcess convertProcess; 0172 #if defined(HAVE_WINDOWS) 0173 // need to set path to magick coder modules (which are in the labplot2 directory) 0174 QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); 0175 env.insert("MAGICK_CODER_MODULE_PATH", qPrintable(qgetenv("PROGRAMFILES") + QString("\\labplot2"))); 0176 convertProcess.setProcessEnvironment(env); 0177 #endif 0178 0179 const QStringList params{"-density", QString::number(dpi), baseName + ".pdf", baseName + ".png"}; 0180 convertProcess.start("convert", params); 0181 0182 if (!convertProcess.waitForFinished() || convertProcess.exitCode() != 0) { 0183 WARN("convert process failed, exit code = " << convertProcess.exitCode()); 0184 *success = false; 0185 QFile::remove(baseName + ".aux"); 0186 QFile::remove(baseName + ".log"); 0187 QFile::remove(baseName + ".pdf"); 0188 return QImage(); 0189 } 0190 0191 // read png file 0192 QImage image(baseName + ".png", "png"); 0193 0194 // final clean up 0195 QFile::remove(baseName + ".aux"); 0196 QFile::remove(baseName + ".log"); 0197 QFile::remove(baseName + ".pdf"); 0198 QFile::remove(baseName + ".png"); 0199 0200 *success = true; 0201 return image; 0202 } 0203 0204 // TEX -> DVI -> PS -> PNG 0205 QImage TeXRenderer::imageFromDVI(const QTemporaryFile& file, const int dpi, bool* success) { 0206 QFileInfo fi(file.fileName()); 0207 const QString& baseName = fi.completeBaseName(); 0208 0209 //latex: produce the DVI file 0210 QProcess latexProcess; 0211 latexProcess.start("latex", QStringList() << "-interaction=batchmode" << file.fileName()); 0212 if (!latexProcess.waitForFinished() || latexProcess.exitCode() != 0) { 0213 WARN("latex process failed, exit code = " << latexProcess.exitCode()); 0214 *success = false; 0215 QFile::remove(baseName + ".aux"); 0216 QFile::remove(baseName + ".log"); 0217 return QImage(); 0218 } 0219 0220 // dvips: DVI -> PS 0221 QProcess dvipsProcess; 0222 dvipsProcess.start("dvips", QStringList() << "-E" << baseName); 0223 if (!dvipsProcess.waitForFinished() || dvipsProcess.exitCode() != 0) { 0224 WARN("dvips process failed, exit code = " << dvipsProcess.exitCode()); 0225 *success = false; 0226 QFile::remove(baseName + ".aux"); 0227 QFile::remove(baseName + ".log"); 0228 QFile::remove(baseName + ".dvi"); 0229 return QImage(); 0230 } 0231 0232 // convert: PS -> PNG 0233 QProcess convertProcess; 0234 #if defined(HAVE_WINDOWS) 0235 // need to set path to magick coder modules (which are in the labplot2 directory) 0236 QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); 0237 env.insert("MAGICK_CODER_MODULE_PATH", qPrintable(qgetenv("PROGRAMFILES") + QString("\\labplot2"))); 0238 convertProcess.setProcessEnvironment(env); 0239 #endif 0240 0241 const QStringList params{"-density", QString::number(dpi), baseName + ".ps", baseName + ".png"}; 0242 convertProcess.start("convert", params); 0243 0244 if (!convertProcess.waitForFinished() || convertProcess.exitCode() != 0) { 0245 WARN("convert process failed, exit code = " << convertProcess.exitCode()); 0246 *success = false; 0247 QFile::remove(baseName + ".aux"); 0248 QFile::remove(baseName + ".log"); 0249 QFile::remove(baseName + ".dvi"); 0250 QFile::remove(baseName + ".ps"); 0251 return QImage(); 0252 } 0253 0254 // read png file 0255 QImage image(baseName + ".png", "png"); 0256 0257 // final clean up 0258 QFile::remove(baseName + ".aux"); 0259 QFile::remove(baseName + ".log"); 0260 QFile::remove(baseName + ".dvi"); 0261 QFile::remove(baseName + ".ps"); 0262 QFile::remove(baseName + ".png"); 0263 0264 *success = true; 0265 return image; 0266 } 0267 0268 bool TeXRenderer::enabled() { 0269 KConfigGroup group = KSharedConfig::openConfig()->group("Settings_Worksheet"); 0270 QString engine = group.readEntry("LaTeXEngine", ""); 0271 if (engine.isEmpty()) { 0272 //empty string was found in the settings (either the settings never saved or no tex engine was available during the last save) 0273 //->check whether the latex environment was installed in the meantime 0274 engine = QLatin1String("xelatex"); 0275 if (!executableExists(engine)) { 0276 engine = QLatin1String("lualatex"); 0277 if (!executableExists(engine)) { 0278 engine = QLatin1String("pdflatex"); 0279 if (!executableExists(engine)) 0280 engine = QLatin1String("latex"); 0281 } 0282 } 0283 0284 if (!engine.isEmpty()) { 0285 //one of the tex engines was found -> automatically save it in the settings without any user action 0286 group.writeEntry(QLatin1String("LaTeXEngine"), engine); 0287 group.sync(); 0288 } 0289 } else if (!executableExists(engine)) { 0290 WARN("LaTeX engine does not exist"); 0291 return false; 0292 } 0293 0294 //engine found, check the presence of other required tools (s.a. TeXRenderer.cpp): 0295 //to convert the generated PDF/PS files to PNG we need 'convert' from the ImageMagic package 0296 if (!executableExists(QLatin1String("convert"))) { 0297 WARN("program \"convert\" does not exist"); 0298 return false; 0299 } 0300 0301 //to convert the generated PS files to DVI we need 'dvips' 0302 if (engine == "latex") { 0303 if (!executableExists(QLatin1String("dvips"))) { 0304 WARN("program \"dvips\" does not exist"); 0305 return false; 0306 } 0307 } 0308 0309 #if defined(_WIN64) 0310 if (!executableExists(QLatin1String("gswin64c")) && !QDir(qgetenv("PROGRAMFILES") + QString("/gs")).exists() 0311 && !QDir(qgetenv("PROGRAMFILES(X86)") + QString("/gs")).exists()) { 0312 WARN("ghostscript (64bit) does not exist"); 0313 return false; 0314 } 0315 #elif defined(HAVE_WINDOWS) 0316 if (!executableExists(QLatin1String("gswin32c")) && !QDir(qgetenv("PROGRAMFILES") + QString("/gs")).exists()) { 0317 WARN("ghostscript (32bit) does not exist"); 0318 return false; 0319 } 0320 #endif 0321 0322 return true; 0323 } 0324 0325 bool TeXRenderer::executableExists(const QString& exe) { 0326 return !QStandardPaths::findExecutable(exe).isEmpty(); 0327 }