klfbackend.cpp

00001 /***************************************************************************
00002  *   file klfbackend.cpp
00003  *   This file is part of the KLatexFormula Project.
00004  *   Copyright (C) 2007 by Philippe Faist
00005  *   philippe.faist at bluewin.ch
00006  *                                                                         *
00007  *   This program is free software; you can redistribute it and/or modify  *
00008  *   it under the terms of the GNU General Public License as published by  *
00009  *   the Free Software Foundation; either version 2 of the License, or     *
00010  *   (at your option) any later version.                                   *
00011  *                                                                         *
00012  *   This program is distributed in the hope that it will be useful,       *
00013  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
00014  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
00015  *   GNU General Public License for more details.                          *
00016  *                                                                         *
00017  *   You should have received a copy of the GNU General Public License     *
00018  *   along with this program; if not, write to the                         *
00019  *   Free Software Foundation, Inc.,                                       *
00020  *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
00021  ***************************************************************************/
00022 
00023 #include <stdlib.h>
00024 
00025 #include <qregexp.h>
00026 #include <qfile.h>
00027 #include <qdatetime.h>
00028 #include <qtextstream.h>
00029 #include <qdir.h>
00030 
00031 #include "klfblockprocess.h"
00032 #include "klfbackend.h"
00033 
00034 #if defined (Q_WS_MAC) || defined (_POSIX_) || defined (__USE_POSIX)
00035   extern char **environ;
00036 #else
00037   _CRTIMP extern char **_environ;
00038 #  define environ _environ
00039 #endif
00040 
00041 
00042 
00043 
00044 
00045 KLFBackend::KLFBackend()
00046 {
00047 }
00048 
00049 
00050 // Utility function
00051 QString progErrorMsg(QString progname, int exitstatus, QString stderrstr, QString stdoutstr)
00052 {
00053   QString stdouthtml = stdoutstr;
00054   QString stderrhtml = stderrstr;
00055   stdouthtml.replace("&", "&amp;");
00056   stdouthtml.replace("<", "&lt;");
00057   stdouthtml.replace(">", "&gt;");
00058   stderrhtml.replace("&", "&amp;");
00059   stderrhtml.replace("<", "&lt;");
00060   stderrhtml.replace(">", "&gt;");
00061 
00062   if (stderrstr.isEmpty() && stdoutstr.isEmpty())
00063     return QObject::tr("<p><b>%1</b> reported an error (exit status %2). No Output was generated.</p>", "KLFBackend")
00064     .arg(progname).arg(exitstatus);
00065   if (stderrstr.isEmpty())
00066     return QObject::tr("<p><b>%1</b> reported an error (exit status %2). Here is full stdout output:</p>\n"
00067                "<pre>\n%3</pre>", "KLFBackend")
00068       .arg(progname).arg(exitstatus).arg(stdouthtml);
00069   if (stdoutstr.isEmpty())
00070     return QObject::tr("<p><b>%1</b> reported an error (exit status %2). Here is full stderr output:</p>\n"
00071                "<pre>\n%3</pre>", "KLFBackend")
00072       .arg(progname).arg(exitstatus).arg(stderrhtml);
00073   
00074   return QObject::tr("<p><b>%1</b> reported an error (exit status %2). Here is full stderr output:</p>\n"
00075              "<pre>\n%3</pre><p>And here is full stdout output:</p><pre>\n%4</pre>", "KLFBackend")
00076     .arg(progname).arg(exitstatus).arg(stderrhtml).arg(stdouthtml);
00077 }
00078 
00079 
00080 KLFBackend::klfOutput KLFBackend::getLatexFormula(const klfInput& in, const klfSettings& settings)
00081 {
00082   klfOutput res;
00083   res.status = KLFERR_NOERROR;
00084   res.errorstr = QString();
00085   res.result = QImage();
00086   res.pngdata = QByteArray();
00087   res.epsdata = QByteArray();
00088   res.pdfdata = QByteArray();
00089 
00090   // PROCEDURE:
00091   // - generate LaTeX-file
00092   // - latex --> get DVI file
00093   // - dvips -E file.dvi it to get an EPS file
00094   // - Run gs:  gs -dNOPAUSE -dSAFER -dEPSCrop -r600 -dTextAlphaBits=4 -dGraphicsAlphaBits=4
00095   //              -sDEVICE=pngalpha|png16m -sOutputFile=xxxxxx.png -q -dBATCH xxxxxx.eps   to eventually get PNG data
00096   // - if epstopdfexec is not empty, run epstopdf and get PDF file.
00097 
00098   QString tempfname = settings.tempdir + "/klatexformulatmp2-"
00099     + QDateTime::currentDateTime().toString("hh-mm-ss");
00100 
00101 
00102 #ifdef KLFBACKEND_QT4
00103   QString latexsimplified = in.latex.trimmed();
00104 #else
00105   QString latexsimplified = in.latex.stripWhiteSpace();
00106 #endif
00107   if (latexsimplified.isEmpty()) {
00108     res.errorstr = QObject::tr("You must specify a LaTeX formula!", "KLFBackend");
00109     res.status = KLFERR_MISSINGLATEXFORMULA;
00110     return res;
00111   }
00112 
00113   QString latexin;
00114   if (in.mathmode.contains("...") == 0) {
00115     res.status = KLFERR_MISSINGMATHMODETHREEDOTS;
00116     res.errorstr = QObject::tr("The math mode string doesn't contain '...'!", "KLFBackend");
00117     return res;
00118   }
00119   latexin = in.mathmode;
00120   latexin.replace("...", in.latex);
00121 
00122   {
00123     QFile file(tempfname+".tex");
00124 #ifdef KLFBACKEND_QT4
00125     bool r = file.open(QIODevice::WriteOnly);
00126 #else
00127     bool r = file.open(IO_WriteOnly);
00128 #endif
00129     if ( ! r ) {
00130       res.status = KLFERR_TEXWRITEFAIL;
00131       res.errorstr = QObject::tr("Can't open file for writing: '%1'!", "KLFBackend").arg(tempfname+".tex");
00132       return res;
00133     }
00134     QTextStream stream(&file);
00135     stream << "\\documentclass{article}\n"
00136        << "\\usepackage[dvips]{color}\n"
00137        << in.preamble << "\n"
00138        << "\\begin{document}\n"
00139        << "\\thispagestyle{empty}\n"
00140        << QString("\\definecolor{klffgcolor}{rgb}{%1,%2,%3}\n").arg(qRed(in.fg_color)/255.0)
00141       .arg(qGreen(in.fg_color)/255.0).arg(qBlue(in.fg_color)/255.0)
00142        << QString("\\definecolor{klfbgcolor}{rgb}{%1,%2,%3}\n").arg(qRed(in.bg_color)/255.0)
00143       .arg(qGreen(in.bg_color)/255.0).arg(qBlue(in.bg_color)/255.0)
00144        << ( (qAlpha(in.bg_color)>0) ? "\\pagecolor{klfbgcolor}\n" : "" )
00145        << "{\\color{klffgcolor} " << latexin << " }\n"
00146        << "\\end{document}\n";
00147   }
00148 
00149   { // execute latex
00150 
00151     KLFBlockProcess proc;
00152     QStringList args;
00153     QStringList env;
00154 
00155     proc.setWorkingDirectory(settings.tempdir);
00156 
00157     args << settings.latexexec << QDir::toNativeSeparators(tempfname+".tex");
00158 
00159     bool r = proc.startProcess(args, env);
00160 
00161     if (!r) {
00162       res.status = KLFERR_NOLATEXPROG;
00163       res.errorstr = QObject::tr("Unable to start Latex program!", "KLFBackend");
00164       return res;
00165     }
00166     if (!proc.normalExit()) {
00167       res.status = KLFERR_LATEXNONORMALEXIT;
00168       res.errorstr = QObject::tr("Latex was killed!", "KLFBackend");
00169       return res;
00170     }
00171     if (proc.exitStatus() != 0) {
00172       cleanup(tempfname);
00173       res.status = KLFERR_PROGERR_LATEX;
00174       res.errorstr = progErrorMsg("LaTeX", proc.exitStatus(), proc.readStderrString(), proc.readStdoutString());
00175       return res;
00176     }
00177 
00178     if (!QFile::exists(tempfname + ".dvi")) {
00179       res.status = KLFERR_NODVIFILE;
00180       res.errorstr = QObject::tr("DVI file didn't appear after having called Latex!", "KLFBackend");
00181       return res;
00182     }
00183 
00184   } // end of 'latex' block
00185 
00186   { // execute dvips -E
00187 
00188     KLFBlockProcess proc;
00189     QStringList args;
00190     args << settings.dvipsexec << "-E" << QDir::toNativeSeparators(tempfname+".dvi")
00191          << "-o" << QDir::toNativeSeparators(tempfname+".eps");
00192 
00193     bool r = proc.startProcess(args);
00194 
00195     if ( ! r ) {
00196       res.status = KLFERR_NODVIPSPROG;
00197       res.errorstr = QObject::tr("Unable to start dvips!\n", "KLFBackend");
00198       return res;
00199     }
00200     if ( !proc.normalExit() ) {
00201       res.status = KLFERR_DVIPSNONORMALEXIT;
00202       res.errorstr = QObject::tr("Dvips was mercilessly killed!\n", "KLFBackend");
00203       return res;
00204     }
00205     if ( proc.exitStatus() != 0) {
00206       res.status = KLFERR_PROGERR_DVIPS;
00207       res.errorstr = progErrorMsg("dvips", proc.exitStatus(), proc.readStderrString(), proc.readStdoutString());
00208       return res;
00209     }
00210     if (!QFile::exists(tempfname + ".eps")) {
00211       res.status = KLFERR_NOEPSFILE;
00212       res.errorstr = QObject::tr("EPS file didn't appear after dvips call!\n", "KLFBackend");
00213       return res;
00214     }
00215 
00216     // add some space on bounding-box to avoid some too tight bounding box bugs
00217     // read eps file
00218     QFile epsfile(tempfname+".eps");
00219 #ifdef KLFBACKEND_QT4
00220     r = epsfile.open(QIODevice::ReadOnly);
00221 #else
00222     r = epsfile.open(IO_ReadOnly);
00223 #endif
00224     if ( ! r ) {
00225       res.status = KLFERR_EPSREADFAIL;
00226       res.errorstr = QObject::tr("Can't read file '%1'!\n", "KLFBackend").arg(tempfname+".eps");
00227       return res;
00228     }
00229     QByteArray epscontent = epsfile.readAll();
00230 #ifdef KLFBACKEND_QT4
00231     int i = epscontent.indexOf("%%BoundingBox: ");
00232     if ( i == -1 ) {
00233       res.status = KLFERR_NOEPSBBOX -11;
00234       res.errorstr = QObject::tr("File '%1' does not contain line \"%%BoundingBox: ... \" !", "KLFBackend")
00235     .arg(tempfname+".eps");
00236     }
00237     int ax, ay, bx, by;
00238     char temp[250];
00239     int k = i;
00240     i += strlen("%%BoundingBox:");
00241     int n = sscanf(epscontent.constData()+i, "%d %d %d %d", &ax, &ay, &bx, &by);
00242     if ( n != 4 ) {
00243       res.status = KLFERR_BADEPSBBOX;
00244       res.errorstr = QObject::tr("file %1: Line %%BoundingBox: can't read values!\n", "KLFBackend")
00245     .arg(tempfname+".eps");
00246       return res;
00247     }
00248     // Don't forget: '%' in printf has special meaning
00249     sprintf(temp, "%%%%BoundingBox: %d %d %d %d", ax-settings.lborderoffset, ay-settings.bborderoffset,
00250         bx+settings.rborderoffset, by+settings.tborderoffset); // grow bbox by settings.?borderoffset points
00251     QString chunk = QString::fromLocal8Bit(epscontent.constData()+k);
00252     QRegExp rx("^%%BoundingBox: [0-9]+ [0-9]+ [0-9]+ [0-9]+");
00253     (void) rx.indexIn(chunk);
00254     int l = rx.matchedLength();
00255     epscontent.replace(k, l, temp);
00256     res.epsdata = epscontent;
00257 #else
00258     QCString epscontent_s(epscontent.data(), epscontent.size());
00259     // process file data and transform it
00260     int i = epscontent_s.find("%%BoundingBox: ");
00261     if ( i == -1 ) {
00262       res.status = KLFERR_NOEPSBBOX;
00263       res.errorstr = QObject::tr("File '%1' does not contain line \"%%BoundingBox: ... \" !", "KLFBackend")
00264     .arg(tempfname+".eps");
00265       return res;
00266     }
00267     int ax, ay, bx, by;
00268     char temp[250];
00269     i += strlen("%%BoundingBox:");
00270     int n = sscanf((const char*)epscontent_s+i, "%d %d %d %d", &ax, &ay, &bx, &by);
00271     if ( n != 4 ) {
00272       res.status = KLFERR_BADEPSBBOX;
00273       res.errorstr = QObject::tr("file %1: Line %%BoundingBox: can't read values!\n", "KLFBackend")
00274     .arg(tempfname+".eps");
00275       return res;
00276     }
00277     sprintf(temp, "%%%%BoundingBox: %d %d %d %d", ax-settings.lborderoffset, ay-settings.bborderoffset,
00278         bx+settings.rborderoffset, by+settings.tborderoffset); // grow bbox by settings.?borderoffset points
00279     epscontent_s.replace(QRegExp("%%BoundingBox: [0-9]+ [0-9]+ [0-9]+ [0-9]+"), temp);
00280     res.epsdata.duplicate(epscontent_s.data(), epscontent_s.length());
00281 #endif
00282     // write content back to second file
00283     QFile epsgoodfile(tempfname+"-good.eps");
00284 #ifdef KLFBACKEND_QT4
00285     r = epsgoodfile.open(QIODevice::WriteOnly);
00286 #else
00287     r = epsgoodfile.open(IO_WriteOnly);
00288 #endif
00289     if ( ! r ) {
00290       res.status = KLFERR_EPSWRITEFAIL;
00291       res.errorstr = QObject::tr("Can't write to file '%1'!\n", "KLFBackend")
00292     .arg(tempfname+"-good.eps");
00293       return res;
00294     }
00295 #ifdef KLFBACKEND_QT4
00296     epsgoodfile.write(res.epsdata);
00297 #else
00298     epsgoodfile.writeBlock(res.epsdata);
00299 #endif
00300     // res.epsdata is now set.
00301 
00302   } // end of block "make EPS"
00303 
00304   { // run 'gs' to get png
00305     KLFBlockProcess proc;
00306     QStringList args;
00307     args << settings.gsexec << "-dNOPAUSE" << "-dSAFER" << "-dEPSCrop" << "-r"+QString::number(in.dpi)
00308      << "-dTextAlphaBits=4" << "-dGraphicsAlphaBits=4";
00309     if (qAlpha(in.bg_color) > 0) { // we're forcing a background color
00310       args << "-sDEVICE=png16m";
00311     } else {
00312       args << "-sDEVICE=pngalpha";
00313     }
00314     args << "-sOutputFile="+QDir::toNativeSeparators(tempfname+".png") << "-q" << "-dBATCH"
00315          << QDir::toNativeSeparators(tempfname+"-good.eps");
00316 
00317     bool r = proc.startProcess(args);
00318   
00319     if ( ! r ) {
00320       res.status = KLFERR_NOGSPROG;
00321       res.errorstr = QObject::tr("Unable to start gs!\n", "KLFBackend");
00322       return res;
00323     }
00324     if ( !proc.normalExit() ) {
00325       res.status = KLFERR_GSNONORMALEXIT;
00326       res.errorstr = QObject::tr("gs died abnormally!\n", "KLFBackend");
00327       return res;
00328     }
00329     if ( proc.exitStatus() != 0) {
00330       res.status = KLFERR_PROGERR_GS;
00331       res.errorstr = progErrorMsg("gs", proc.exitStatus(), proc.readStderrString(), proc.readStdoutString());
00332       return res;
00333     }
00334     if (!QFile::exists(tempfname + ".png")) {
00335       res.status = KLFERR_NOPNGFILE;
00336       res.errorstr = QObject::tr("PNG file didn't appear after call to gs!\n", "KLFBackend");
00337       return res;
00338     }
00339 
00340     // get and save PNG to memory
00341     QFile pngfile(tempfname+".png");
00342 #ifdef KLFBACKEND_QT4
00343     r = pngfile.open(QIODevice::ReadOnly);
00344 #else
00345     r = pngfile.open(IO_ReadOnly);
00346 #endif
00347     if ( ! r ) {
00348       res.status = KLFERR_PNGREADFAIL;
00349       res.errorstr = QObject::tr("Unable to read file %1!\n", "KLFBackend")
00350     .arg(tempfname+".png");
00351       return res;
00352     }
00353     res.pngdata = pngfile.readAll();
00354     pngfile.close();
00355     // res.pngdata is now set.
00356     res.result.loadFromData(res.pngdata, "PNG");
00357 
00358   }
00359 
00360   if (!settings.epstopdfexec.isEmpty()) {
00361     // if we have epstopdf functionality, then we'll take advantage of it to generate pdf:
00362     KLFBlockProcess proc;
00363     QStringList args;
00364     args << settings.epstopdfexec << (tempfname+"-good.eps") << ("--outfile="+QDir::toNativeSeparators(tempfname+".pdf"));
00365 
00366     bool r = proc.startProcess(args);
00367 
00368     if ( ! r ) {
00369       res.status = KLFERR_NOEPSTOPDFPROG;
00370       res.errorstr = QObject::tr("Unable to start epstopdf!\n", "KLFBackend");
00371       return res;
00372     }
00373     if ( !proc.normalExit() ) {
00374       res.status = KLFERR_EPSTOPDFNONORMALEXIT;
00375       res.errorstr = QObject::tr("epstopdf died nastily!\n", "KLFBackend");
00376       return res;
00377     }
00378     if ( proc.exitStatus() != 0) {
00379       res.status = KLFERR_PROGERR_EPSTOPDF;
00380       res.errorstr = progErrorMsg("epstopdf", proc.exitStatus(), proc.readStderrString(), proc.readStdoutString());
00381       return res;
00382     }
00383     if (!QFile::exists(tempfname + ".pdf")) {
00384       res.status = KLFERR_NOPDFFILE;
00385       res.errorstr = QObject::tr("PDF file didn't appear after call to epstopdf!\n", "KLFBackend");
00386       return res;
00387     }
00388 
00389     // get and save PDF to memory
00390     QFile pdffile(tempfname+".pdf");
00391 #ifdef KLFBACKEND_QT4
00392     r = pdffile.open(QIODevice::ReadOnly);
00393 #else
00394     r = pdffile.open(IO_ReadOnly);
00395 #endif
00396     if ( ! r ) {
00397       res.status = KLFERR_PDFREADFAIL;
00398       res.errorstr = QObject::tr("Unable to read file %1!\n", "KLFBackend")
00399     .arg(tempfname+".pdf");
00400       return res;
00401     }
00402     res.pdfdata = pdffile.readAll();
00403 
00404   }
00405 
00406   // clean up our mess
00407   cleanup(tempfname);
00408 
00409 
00410   return res;
00411 }
00412 
00413 
00414 void KLFBackend::cleanup(QString tempfname)
00415 {
00416   if (QFile::exists(tempfname+".tex")) QFile::remove(tempfname+".tex");
00417   if (QFile::exists(tempfname+".dvi")) QFile::remove(tempfname+".dvi");
00418   if (QFile::exists(tempfname+".aux")) QFile::remove(tempfname+".aux");
00419   if (QFile::exists(tempfname+".log")) QFile::remove(tempfname+".log");
00420   if (QFile::exists(tempfname+".toc")) QFile::remove(tempfname+".toc");
00421   if (QFile::exists(tempfname+".eps")) QFile::remove(tempfname+".eps");
00422   if (QFile::exists(tempfname+"-good.eps")) QFile::remove(tempfname+"-good.eps");
00423   if (QFile::exists(tempfname+".png")) QFile::remove(tempfname+".png");
00424   if (QFile::exists(tempfname+".pdf")) QFile::remove(tempfname+".pdf");
00425 }
00426 

Generated on Mon May 4 01:27:08 2009 for KLFBackend by  doxygen 1.5.3