labplot  2.8.2
About: LabPlot is an application for plotting and analysis of 2D and 3D functions and data. It is a complete rewrite of LabPlot1 and lacks in the first release a lot of features available in the predecessor. On the other hand, the GUI and the usability is more superior.
  Fossies Dox: labplot-2.8.2.tar.gz  ("unofficial" and yet experimental doxygen-generated source code documentation)  

TeXRenderer.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  File : TeXRenderer.cc
3  Project : LabPlot
4  Description : TeX renderer class
5  --------------------------------------------------------------------
6  Copyright : (C) 2008-2016 by Alexander Semke (alexander.semke@web.de)
7  Copyright : (C) 2012 by Stefan Gerlach (stefan.gerlach@uni-konstanz.de)
8 
9  ***************************************************************************/
10 
11 /***************************************************************************
12  * *
13  * This program is free software; you can redistribute it and/or modify *
14  * it under the terms of the GNU General Public License as published by *
15  * the Free Software Foundation; either version 2 of the License, or *
16  * (at your option) any later version. *
17  * *
18  * This program is distributed in the hope that it will be useful, *
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
21  * GNU General Public License for more details. *
22  * *
23  * You should have received a copy of the GNU General Public License *
24  * along with this program; if not, write to the Free Software *
25  * Foundation, Inc., 51 Franklin Street, Fifth Floor, *
26  * Boston, MA 02110-1301 USA *
27  * *
28  ***************************************************************************/
29 #include "TeXRenderer.h"
30 #include "backend/lib/macros.h"
31 
32 #include <KConfigGroup>
33 #include <KSharedConfig>
34 
35 #include <QColor>
36 #include <QDir>
37 #include <QImage>
38 #include <QProcess>
39 #include <QStandardPaths>
40 #include <QTemporaryFile>
41 #include <QTextStream>
42 
43 /*!
44  \class TeXRenderer
45  \brief Implements rendering of latex code to a PNG image.
46 
47  Uses latex engine specified by the user (default xelatex) to render LaTeX text
48 
49  \ingroup tools
50 */
51 QImage TeXRenderer::renderImageLaTeX(const QString& teXString, bool* success, const TeXRenderer::Formatting& format) {
52  const QColor& fontColor = format.fontColor;
53  const QColor& backgroundColor = format.backgroundColor;
54  const int fontSize = format.fontSize;
55  const QString& fontFamily = format.fontFamily;
56  const int dpi = format.dpi;
57 
58  //determine the temp directory where the produced files are going to be created
59  QString tempPath;
60 #ifdef Q_OS_LINUX
61  //on linux try to use shared memory device first if available
62  static bool useShm = QDir("/dev/shm/").exists();
63  if (useShm)
64  tempPath = "/dev/shm/";
65  else
66  tempPath = QDir::tempPath();
67 #else
68  tempPath = QDir::tempPath();
69 #endif
70 
71  // make sure we have preview.sty available
72  if (!tempPath.contains(QLatin1String("preview.sty"))) {
73  QString file = QStandardPaths::locate(QStandardPaths::AppDataLocation, QLatin1String("latex/preview.sty"));
74  if (file.isEmpty()) {
75  WARN("Couldn't find preview.sty.");
76  *success = false;
77  return QImage();
78  }
79  else
80  QFile::copy(file, tempPath + QLatin1String("/") + QLatin1String("preview.sty"));
81  }
82 
83  //create a temporary file
84  QTemporaryFile file(tempPath + QLatin1String("/") + "labplot_XXXXXX.tex");
85  // FOR DEBUG: file.setAutoRemove(false);
86  // DEBUG("temp file path = " << file.fileName().toUtf8().constData());
87  if (file.open()) {
88  QDir::setCurrent(tempPath);
89  } else {
90  WARN("Couldn't open the file " << STDSTRING(file.fileName()));
91  *success = false;
92  return QImage();
93  }
94 
95  //determine latex engine to be used
96  KConfigGroup group = KSharedConfig::openConfig()->group("Settings_Worksheet");
97  QString engine = group.readEntry("LaTeXEngine", "pdflatex");
98 
99  // create latex code
100  QTextStream out(&file);
101  int headerIndex = teXString.indexOf("\\begin{document}");
102  QString body;
103  if (headerIndex != -1) {
104  //user provided a complete latex document -> extract the document header and body
105  QString header = teXString.left(headerIndex);
106  int footerIndex = teXString.indexOf("\\end{document}");
107  body = teXString.mid(headerIndex + 16, footerIndex - headerIndex - 16);
108  out << header;
109  } else {
110  //user simply provided a document body (assume it's a math. expression) -> add a minimal header
111  out << "\\documentclass{minimal}";
112  if (teXString.indexOf('$') == -1)
113  body = '$' + teXString + '$';
114  else
115  body = teXString;
116 
117  //replace line breaks with tex command for a line break '\\'
118  body = body.replace(QLatin1String("\n"), QLatin1String("\\\\"));
119  }
120 
121  if (engine == "xelatex" || engine == "lualatex") {
122  out << "\\usepackage{xltxtra}";
123  out << "\\defaultfontfeatures{Ligatures=TeX}";
124  if (!fontFamily.isEmpty())
125  out << "\\setmainfont[Mapping=tex-text]{" << fontFamily << "}";
126  }
127 
128  out << "\\usepackage{color}";
129  out << "\\usepackage[active,displaymath,textmath,tightpage]{preview}";
130  // TODO: this fails with pdflatex
131  //out << "\\usepackage{mathtools}";
132  out << "\\begin{document}";
133  out << "\\begin{preview}";
134  out << "\\colorbox[rgb]{" << backgroundColor.redF() << ',' << backgroundColor.greenF() << ',' << backgroundColor.blueF() << "}{";
135  out << "\\fontsize{" << QString::number(fontSize) << "}{" << QString::number(fontSize) << "}\\selectfont";
136  out << "\\color[rgb]{" << fontColor.redF() << ',' << fontColor.greenF() << ',' << fontColor.blueF() << "}";
137  out << body;
138  out << "}";
139  out << "\\end{preview}";
140  out << "\\end{document}";
141  out.flush();
142  if (engine == "latex")
143  return imageFromDVI(file, dpi, success);
144  else
145  return imageFromPDF(file, dpi, engine, success);
146 }
147 
148 // TEX -> PDF -> PNG
149 QImage TeXRenderer::imageFromPDF(const QTemporaryFile& file, const int dpi, const QString& engine, bool* success) {
150  QFileInfo fi(file.fileName());
151  const QString& baseName = fi.completeBaseName();
152 
153  // pdflatex: produce the PDF file
154  QProcess latexProcess;
155 #if defined(HAVE_WINDOWS)
156  latexProcess.setNativeArguments("-interaction=batchmode " + file.fileName());
157  latexProcess.start(engine, QStringList() << QString());
158 #else
159  latexProcess.start(engine, QStringList() << "-interaction=batchmode" << file.fileName());
160 #endif
161 
162  if (!latexProcess.waitForFinished() || latexProcess.exitCode() != 0) {
163  WARN("pdflatex process failed, exit code = " << latexProcess.exitCode());
164  *success = false;
165  QFile::remove(baseName + ".aux");
166  QFile::remove(baseName + ".log");
167  return QImage();
168  }
169 
170  // convert: PDF -> PNG
171  QProcess convertProcess;
172 #if defined(HAVE_WINDOWS)
173  // need to set path to magick coder modules (which are in the labplot2 directory)
174  QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
175  env.insert("MAGICK_CODER_MODULE_PATH", qPrintable(qgetenv("PROGRAMFILES") + QString("\\labplot2")));
176  convertProcess.setProcessEnvironment(env);
177 #endif
178 
179  const QStringList params{"-density", QString::number(dpi), baseName + ".pdf", baseName + ".png"};
180  convertProcess.start("convert", params);
181 
182  if (!convertProcess.waitForFinished() || convertProcess.exitCode() != 0) {
183  WARN("convert process failed, exit code = " << convertProcess.exitCode());
184  *success = false;
185  QFile::remove(baseName + ".aux");
186  QFile::remove(baseName + ".log");
187  QFile::remove(baseName + ".pdf");
188  return QImage();
189  }
190 
191  // read png file
192  QImage image(baseName + ".png", "png");
193 
194  // final clean up
195  QFile::remove(baseName + ".aux");
196  QFile::remove(baseName + ".log");
197  QFile::remove(baseName + ".pdf");
198  QFile::remove(baseName + ".png");
199 
200  *success = true;
201  return image;
202 }
203 
204 // TEX -> DVI -> PS -> PNG
205 QImage TeXRenderer::imageFromDVI(const QTemporaryFile& file, const int dpi, bool* success) {
206  QFileInfo fi(file.fileName());
207  const QString& baseName = fi.completeBaseName();
208 
209  //latex: produce the DVI file
210  QProcess latexProcess;
211  latexProcess.start("latex", QStringList() << "-interaction=batchmode" << file.fileName());
212  if (!latexProcess.waitForFinished() || latexProcess.exitCode() != 0) {
213  WARN("latex process failed, exit code = " << latexProcess.exitCode());
214  *success = false;
215  QFile::remove(baseName + ".aux");
216  QFile::remove(baseName + ".log");
217  return QImage();
218  }
219 
220  // dvips: DVI -> PS
221  QProcess dvipsProcess;
222  dvipsProcess.start("dvips", QStringList() << "-E" << baseName);
223  if (!dvipsProcess.waitForFinished() || dvipsProcess.exitCode() != 0) {
224  WARN("dvips process failed, exit code = " << dvipsProcess.exitCode());
225  *success = false;
226  QFile::remove(baseName + ".aux");
227  QFile::remove(baseName + ".log");
228  QFile::remove(baseName + ".dvi");
229  return QImage();
230  }
231 
232  // convert: PS -> PNG
233  QProcess convertProcess;
234 #if defined(HAVE_WINDOWS)
235  // need to set path to magick coder modules (which are in the labplot2 directory)
236  QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
237  env.insert("MAGICK_CODER_MODULE_PATH", qPrintable(qgetenv("PROGRAMFILES") + QString("\\labplot2")));
238  convertProcess.setProcessEnvironment(env);
239 #endif
240 
241  const QStringList params{"-density", QString::number(dpi), baseName + ".ps", baseName + ".png"};
242  convertProcess.start("convert", params);
243 
244  if (!convertProcess.waitForFinished() || convertProcess.exitCode() != 0) {
245  WARN("convert process failed, exit code = " << convertProcess.exitCode());
246  *success = false;
247  QFile::remove(baseName + ".aux");
248  QFile::remove(baseName + ".log");
249  QFile::remove(baseName + ".dvi");
250  QFile::remove(baseName + ".ps");
251  return QImage();
252  }
253 
254  // read png file
255  QImage image(baseName + ".png", "png");
256 
257  // final clean up
258  QFile::remove(baseName + ".aux");
259  QFile::remove(baseName + ".log");
260  QFile::remove(baseName + ".dvi");
261  QFile::remove(baseName + ".ps");
262  QFile::remove(baseName + ".png");
263 
264  *success = true;
265  return image;
266 }
267 
269  KConfigGroup group = KSharedConfig::openConfig()->group("Settings_Worksheet");
270  QString engine = group.readEntry("LaTeXEngine", "");
271  if (engine.isEmpty()) {
272  //empty string was found in the settings (either the settings never saved or no tex engine was available during the last save)
273  //->check whether the latex environment was installed in the meantime
274  engine = QLatin1String("xelatex");
275  if (!executableExists(engine)) {
276  engine = QLatin1String("lualatex");
277  if (!executableExists(engine)) {
278  engine = QLatin1String("pdflatex");
279  if (!executableExists(engine))
280  engine = QLatin1String("latex");
281  }
282  }
283 
284  if (!engine.isEmpty()) {
285  //one of the tex engines was found -> automatically save it in the settings without any user action
286  group.writeEntry(QLatin1String("LaTeXEngine"), engine);
287  group.sync();
288  }
289  } else if (!executableExists(engine)) {
290  WARN("LaTeX engine does not exist");
291  return false;
292  }
293 
294  //engine found, check the presence of other required tools (s.a. TeXRenderer.cpp):
295  //to convert the generated PDF/PS files to PNG we need 'convert' from the ImageMagic package
296  if (!executableExists(QLatin1String("convert"))) {
297  WARN("program \"convert\" does not exist");
298  return false;
299  }
300 
301  //to convert the generated PS files to DVI we need 'dvips'
302  if (engine == "latex") {
303  if (!executableExists(QLatin1String("dvips"))) {
304  WARN("program \"dvips\" does not exist");
305  return false;
306  }
307  }
308 
309 #if defined(_WIN64)
310  if (!executableExists(QLatin1String("gswin64c")) && !QDir(qgetenv("PROGRAMFILES") + QString("/gs")).exists()
311  && !QDir(qgetenv("PROGRAMFILES(X86)") + QString("/gs")).exists()) {
312  WARN("ghostscript (64bit) does not exist");
313  return false;
314  }
315 #elif defined(HAVE_WINDOWS)
316  if (!executableExists(QLatin1String("gswin32c")) && !QDir(qgetenv("PROGRAMFILES") + QString("/gs")).exists()) {
317  WARN("ghostscript (32bit) does not exist");
318  return false;
319  }
320 #endif
321 
322  return true;
323 }
324 
325 bool TeXRenderer::executableExists(const QString& exe) {
326  return !QStandardPaths::findExecutable(exe).isEmpty();
327 }
static QImage renderImageLaTeX(const QString &, bool *success, const TeXRenderer::Formatting &)
Definition: TeXRenderer.cpp:51
static QImage imageFromDVI(const QTemporaryFile &, const int dpi, bool *success)
static bool executableExists(const QString &)
static bool enabled()
static QImage imageFromPDF(const QTemporaryFile &, const int dpi, const QString &engine, bool *success)
#define WARN(x)
Definition: macros.h:43
#define STDSTRING(qstr)
Definition: macros.h:67