[KLF Backend][KLF Tools][KLF Home]
KLatexFormula Project
src/klfbackend/klfbackend.cpp
00001 /***************************************************************************
00002  *   file klfbackend.cpp
00003  *   This file is part of the KLatexFormula Project.
00004  *   Copyright (C) 2011 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 /* $Id$ */
00023 
00024 #include <stdio.h>
00025 #include <stdlib.h>
00026 #include <ctype.h> // isspace()
00027 #include <sys/time.h>
00028 #include <math.h> // fabs()
00029 
00030 #include <QtGlobal>
00031 #include <QByteArray>
00032 #include <QSet>
00033 #include <QCoreApplication>
00034 #include <QRegExp>
00035 #include <QFile>
00036 #include <QDateTime>
00037 #include <QTextStream>
00038 #include <QBuffer>
00039 #include <QDir>
00040 #include <QColor>
00041 #include <QTextDocument>
00042 #include <QImageWriter>
00043 #include <QTextCodec>
00044 #include <QTemporaryDir>
00045 
00046 #include <klfutil.h>
00047 #include <klfsysinfo.h>
00048 #include <klfdatautil.h>
00049 
00050 #include "klfblockprocess.h"
00051 #include "klffilterprocess.h"
00052 #include "klfuserscript.h"
00053 #include "klfbackend.h"
00054 #include "klfbackend_p.h"
00055 
00056 
00057 
00082 // some standard guess settings for system configurations
00083 
00084 #ifdef KLF_EXTRA_SEARCH_PATHS
00085 #  define EXTRA_PATHS_PRE       KLF_EXTRA_SEARCH_PATHS ,
00086 //#  define EXTRA_PATHS         KLF_EXTRA_SEARCH_PATHS
00087 #else
00088 #  define EXTRA_PATHS_PRE
00089 //#  define EXTRA_PATHS
00090 #endif
00091 
00092 
00093 #if defined(Q_OS_WIN32) || defined(Q_OS_WIN64)
00094 QStringList progLATEX = QStringList() << "latex.exe";
00095 QStringList progDVIPS = QStringList() << "dvips.exe";
00096 QStringList progGS = QStringList() << "gswin32c.exe" << "gswin64c.exe" << "mgs.exe";
00097 //QStringList progEPSTOPDF = QStringList() << "epstopdf.exe";
00098 static const char * standard_extra_paths[] = {
00099   EXTRA_PATHS_PRE
00100   "C:\\Program Files*\\MiKTeX*\\miktex\\bin",
00101   "C:\\texlive\\*\\bin\\win*",
00102   "C:\\Program Files*\\gs*\\gs*\\bin",
00103   NULL
00104 };
00105 #elif defined(KLF_WS_MAC)
00106 QStringList progLATEX = QStringList() << "latex";
00107 QStringList progDVIPS = QStringList() << "dvips";
00108 QStringList progGS = QStringList() << "gs";
00109 //QStringList progEPSTOPDF = QStringList() << "epstopdf";
00110 static const char * standard_extra_paths[] = {
00111   EXTRA_PATHS_PRE
00112   "/usr/texbin:/Library/TeX/texbin:/usr/local/bin:/opt/local/bin:/sw/bin:/sw/usr/bin",
00113   NULL
00114 };
00115 #else
00116 QStringList progLATEX = QStringList() << "latex";
00117 QStringList progDVIPS = QStringList() << "dvips";
00118 QStringList progGS = QStringList() << "gs";
00119 //QStringList progEPSTOPDF = QStringList() << "epstopdf";
00120 static const char * standard_extra_paths[] = {
00121   EXTRA_PATHS_PRE
00122   NULL
00123 };
00124 #endif
00125 
00126 
00127 
00128 // ---------------------------------
00129 
00130 KLFAbstractLatexMetaInfo::KLFAbstractLatexMetaInfo()
00131 {
00132 }
00133 KLFAbstractLatexMetaInfo::~KLFAbstractLatexMetaInfo()
00134 {
00135 }
00136 
00137 void KLFAbstractLatexMetaInfo::saveMetaInfo(const KLFBackend::klfInput& in,
00138                                             const KLFBackend::klfSettings& settings)
00139 {
00140   static QString boolstr[2] = { QLatin1String("true"), QLatin1String("false") } ;
00141   
00142   saveField("AppVersion", QString::fromLatin1("KLatexFormula " KLF_VERSION_STRING));
00143   saveField("Application",
00144                QObject::tr("Created with KLatexFormula version %1", "KLFBackend::saveOutputToFile")
00145                .arg(KLF_VERSION_STRING));
00146   saveField("Software", QString::fromLatin1("KLatexFormula " KLF_VERSION_STRING));
00147   saveField("InputLatex", in.latex);
00148   saveField("InputMathMode", in.mathmode);
00149   saveField("InputPreamble", in.preamble);
00150   saveField("InputFontSize", QString::number(in.fontsize, 'g', 2));
00151   saveField("InputFgColor", QString("rgb(%1, %2, %3)").arg(qRed(in.fg_color))
00152                .arg(qGreen(in.fg_color)).arg(qBlue(in.fg_color)));
00153   saveField("InputBgColor", QString("rgba(%1, %2, %3, %4)").arg(qRed(in.bg_color))
00154                .arg(qGreen(in.bg_color)).arg(qBlue(in.bg_color))
00155                .arg(qAlpha(in.bg_color)));
00156   saveField("InputDPI", QString::number(in.dpi));
00157   saveField("InputVectorScale", QString::number(in.vectorscale, 'g', 4));
00158   saveField("InputBypassTemplate", boolstr[(int)in.bypassTemplate]);
00159   saveField("InputUserScript", QFileInfo(in.userScript).fileName());
00160   QString usparams;
00161   klfSaveVariantToText(QVariant(klfMapToVariantMap(in.userScriptParam)), true);
00162   saveField("InputUserScriptParams", usparams);
00163   saveField("SettingsTBorderOffset", QString::number(settings.tborderoffset));
00164   saveField("SettingsRBorderOffset", QString::number(settings.rborderoffset));
00165   saveField("SettingsBBorderOffset", QString::number(settings.bborderoffset));
00166   saveField("SettingsLBorderOffset", QString::number(settings.lborderoffset));
00167   saveField("SettingsOutlineFonts", boolstr[(int)settings.outlineFonts]);
00168   saveField("SettingsCalcEpsBoundingBox", boolstr[(int)settings.calcEpsBoundingBox]);
00169   saveField("SettingsWantRaw", boolstr[(int)settings.wantRaw]);
00170   saveField("SettingsWantPDF", boolstr[(int)settings.wantPDF]);
00171   saveField("SettingsWantSVG", boolstr[(int)settings.wantSVG]);
00172 
00173   klfDbg("saved meta-info.") ;
00174 }
00175 
00176 
00177 KLFImageLatexMetaInfo::KLFImageLatexMetaInfo(QImage *imgwrite) : _w(imgwrite) { }
00178 
00179 void KLFImageLatexMetaInfo::saveField(const QString& k, const QString& v)
00180 {
00181   // QImageWriter::setText() uses QString::simplified() and does not save whitespace properly :(
00182   // so encode text in some appropriate way.
00183   _w->setText(k, klfDataToEscaped(v.toUtf8(), '%'));
00184 }
00185 QString KLFImageLatexMetaInfo::loadField(const QString &k) {
00186   return QString::fromUtf8(klfEscapedToData(_w->text(k).toLatin1(), '%'));
00187 }
00188 
00189 
00190 KLF_EXPORT QByteArray klf_escape_ps_string(const QString& v)
00191 {
00192   // write escape codes
00193   int i;
00194   // if v is just ascii, no need to encode it in unicode
00195   bool isascii = true;
00196   for (i = 0; i < v.length(); ++i) {
00197     if (v[i] < 0 || v[i] > 126) {
00198       isascii = false;
00199       break;
00200     }
00201   }
00202   QByteArray vdata;
00203   if (isascii) {
00204     vdata = v.toLatin1();
00205     
00206     QByteArray escaped;
00207     for (i = 0; i < vdata.size(); ++i) {
00208       char c = vdata[i];
00209       klfDbg("Char: "<<c);
00210       if (QChar(vdata[i]).isLetterOrNumber() || c == ' ' || c == '.' || c == ',' || c == '/')
00211         escaped += c;
00212       else if (c == '\n')
00213         escaped += "\\n";
00214       else if (c == '\r')
00215         escaped += "\\r";
00216       else if (c == '\t')
00217         escaped += "\\t";
00218       else if (c == '\\')
00219         escaped += "\\\\";
00220       else if (c == '(')
00221         escaped += "\\(";
00222       else if (c == ')')
00223         escaped += "\\)";
00224       else {
00225         klfDbg("escaping char: (int)c="<<(int)c<<" (uint)c="<<uint(c)<<", octal="<<klfFmtCC("%03o", (uint)c));
00226         escaped += QString("\\%1").arg((unsigned int)(unsigned char)c, 3, 8, QChar('0')).toLatin1();
00227       }
00228     }
00229     
00230     return "("+escaped+")";
00231   }
00232 
00233   // otherwise, do unicode encoding
00234   
00235   QTextCodec *codec = QTextCodec::codecForName("UTF-16BE");
00236   vdata = codec->fromUnicode(v);
00237   klfDbg("vdata is "<<klfDataToEscaped(vdata));
00238   
00239   QByteArray hex;
00240   for (i = 0; i < (vdata.size()-1); i += 2) {
00241     hex += klfFmt("%02x%02x ", (unsigned int)(unsigned char)vdata[i], (unsigned int)(unsigned char)vdata[i+1]);
00242   }
00243   return "<" + hex + ">";
00244 }
00245 
00246 
00247 
00248 KLFPdfmarksWriteLatexMetaInfo::KLFPdfmarksWriteLatexMetaInfo(QByteArray * string)
00249   : _s(string)
00250 {
00251   // See the following for more info:
00252   // http://stackoverflow.com/questions/3010015/pdfmark-for-docinfo-metadata-in-pdf-is-not-accepting-accented-characters-in-keyw
00253   // http://www.justskins.com/forums/adding-metadata-to-pdf-68647.html
00254   
00255   _s->append( // ensure pdfmark symbol defined in postscript
00256              "/pdfmark where { pop } { /globaldict where { pop globaldict } { userdict } ifelse "
00257              "/pdfmark /cleartomark load put } ifelse\n"
00258              // now the proper PDFmarks DOCINFO dictionary
00259              "[ "
00260              );
00261 }
00262 
00263 QString KLFPdfmarksWriteLatexMetaInfo::loadField(const QString& )
00264 {
00265   KLF_ASSERT_CONDITION(false, "N/A.", return QString(); ) ;
00266 }
00267 void KLFPdfmarksWriteLatexMetaInfo::saveField(const QString& k, const QString& v)
00268 {
00269   savePDFField("KLF"+k, v);
00270 }
00271 void KLFPdfmarksWriteLatexMetaInfo::finish()
00272 {
00273   _s->append("  /DOCINFO pdfmark\n");
00274 }
00275 void KLFPdfmarksWriteLatexMetaInfo::savePDFField(const QString& k, const QString& v)
00276 {
00277   QByteArray datavalue = klf_escape_ps_string(v);
00278   
00279   _s->append( "  /"+k+" " + datavalue + "\n");
00280 }
00281 
00282 
00283 
00284 
00285 
00286 
00287 // ---------------------------------
00288 
00289 
00290 static QMutex klf_mutex;
00291 
00292 struct GsInfo
00293 {
00294   GsInfo() { }
00295 
00296   QString version;
00297   int version_maj;
00298   int version_min;
00299   QString help;
00300   QSet<QString> availdevices;
00301 };
00302 
00303 // cache gs version/help/etc. information (for each gs executable, in case there are several)
00304 static QMap<QString,GsInfo> gsInfo = QMap<QString,GsInfo>();
00305 
00306 static void initGsInfo(const KLFBackend::klfSettings *settings, bool isMainThread);
00307 
00308 
00309 
00310 
00311 
00312 
00313 
00314 // ---------------------------------
00315 
00316 
00317 KLFBackend::TemplateGenerator::TemplateGenerator()
00318 {
00319 }
00320 KLFBackend::TemplateGenerator::~TemplateGenerator()
00321 {
00322 }
00323 
00324 KLFBackend::DefaultTemplateGenerator::DefaultTemplateGenerator()
00325 {
00326 }
00327 KLFBackend::DefaultTemplateGenerator::~DefaultTemplateGenerator()
00328 {
00329 }
00330 
00331 QString KLFBackend::DefaultTemplateGenerator::generateTemplate(const klfInput& in,
00332                                                                const klfSettings& /*settings*/)
00333 {
00334   QString latexin;
00335   QString s;
00336 
00337   latexin = in.mathmode;
00338   latexin.replace("...", in.latex);
00339 
00341   s += "\\documentclass{article}\n"
00342     "\\usepackage[dvips]{color}\n";
00343   s += in.preamble;
00344   s += "\n"
00345     "\\begin{document}\n"
00346     "\\thispagestyle{empty}\n";
00347   if (in.fontsize > 0) {
00348     s += QString("\\fontsize{%1}{%2}\\selectfont\n").arg(in.fontsize, 0, 'f', 2).arg(in.fontsize*1.2, 0, 'f', 2);
00349   }
00350   s += QString("\\definecolor{klffgcolor}{rgb}{%1,%2,%3}\n").arg(qRed(in.fg_color)/255.0)
00351     .arg(qGreen(in.fg_color)/255.0).arg(qBlue(in.fg_color)/255.0);
00352   s += QString("\\definecolor{klfbgcolor}{rgb}{%1,%2,%3}\n").arg(qRed(in.bg_color)/255.0)
00353     .arg(qGreen(in.bg_color)/255.0).arg(qBlue(in.bg_color)/255.0);
00354   if (qAlpha(in.bg_color)>0)
00355     s += "\\pagecolor{klfbgcolor}\n";
00356   s += "{\\color{klffgcolor} ";
00357   s += latexin;
00358   s += "%\n"
00359     "}\n"
00360     "\\end{document}\n";
00361 
00362   return s;
00363 }
00364 
00365 
00366 
00367 
00368 // ---------------------------------
00369 
00370 KLFBackend::KLFBackend()
00371 {
00372 }
00373 
00374 
00375 
00376 
00377 #define D_RX "([0-9eE.-]+)"
00378 
00379 // A Bounding Box
00380 struct klfbbox {
00381   double x1, x2, y1, y2;
00382 };
00383 
00384 
00385 
00386 static bool calculate_gs_eps_bbox(const QByteArray& epsdata, const QString& epsFile, klfbbox *bbox,
00387                                   KLFBackend::klfOutput * resError, const KLFBackend::klfSettings& settings,
00388                                   bool isMainThread);
00389 static bool read_eps_bbox(const QByteArray& epsdata, klfbbox *bbox, KLFBackend::klfOutput * resError);
00390 static void correct_eps_bbox(const QByteArray& epsdata,
00391                              const klfbbox& bbox_corrected, const klfbbox& bbox_orig,
00392                              double vectorscale, QRgb bgcolor, QByteArray * epsdatacorrected);
00393 
00394 static void replace_svg_width_or_height(QByteArray *svgdata, const char * attr, double val);
00395 
00396 
00397 static inline bool has_userscript_output(const QSet<QString>& fmts, const QString& format)
00398 {
00399   return fmts.contains(format);
00400   //  if (!fmts.contains(format))
00401   //    return false;
00402   //  return fn.isEmpty() ? true : QFile::exists(fn);
00403 }
00404 
00405 
00406 
00407 typedef QSet<QString> KLFStringSet;
00408 
00409 KLF_EXPORT KLFStringSet klfbackend_fmts =
00410   KLFStringSet()
00411   /* */   << "latex" << "dvi" << "eps-raw" << "eps-bbox" << "eps-processed"
00412 /*   */   << "png" << "pdf" << "svg-gs" << "svg" ;
00413 
00414 
00415 KLF_EXPORT KLFStringSet klfbackend_dependencies(const QString& fmt, bool recursive = false)
00416 {
00417   static KLFStringSet fn_lock = KLFStringSet();
00418 
00419   if (fn_lock.contains(fmt)) {
00420     klfWarning("Dependency loop detected for format "<<fmt) ;
00421     return KLFStringSet();
00422   }
00423   fn_lock << fmt;
00424 
00425   KLFStringSet s;
00426   if (fmt == QLatin1String("tex") || fmt == QLatin1String("latex")) {
00427     // no dependency
00428   } else if (fmt == QLatin1String("dvi")) {
00429     s << "latex";
00430   } else if (fmt == QLatin1String("eps-raw")) {
00431     s << "dvi";
00432   } else if (fmt == QLatin1String("eps-bbox")) {
00433     s << "eps-raw";
00434   } else if (fmt == QLatin1String("eps-processed")) {
00435     s << "eps-bbox";
00436   } else if (fmt == QLatin1String("png")) {
00437     s << "eps-processed";
00438   } else if (fmt == QLatin1String("pdf")) {
00439     s << "eps-processed";
00440   } else if (fmt == QLatin1String("svg-gs")) {
00441     s << "eps-processed";
00442   } else if (fmt == QLatin1String("svg")) {
00443     s << "svg-gs";
00444   } else {
00445     klfWarning("Unknown format : "<<fmt) ;
00446   }
00447   if (!recursive) {
00448     fn_lock.remove(fmt);
00449     return s;
00450   }
00451   // explore dependencies recursively 
00452   KLFStringSet basedeps = s;
00453   foreach (QString str, basedeps) {
00454     KLFStringSet subdeps = klfbackend_dependencies(str, true);
00455     foreach (QString subdep, subdeps) {
00456       s << subdep;
00457     }
00458   }
00459 
00460   fn_lock.remove(fmt);
00461   return s;
00462 }
00463 
00464 static inline bool assert_have_formats_for(const KLFStringSet& outputs, const KLFStringSet& skipfmts,
00465                                            const QString& forwhat)
00466 {
00467   KLFStringSet fmtlist = klfbackend_dependencies(forwhat);
00468   foreach (QString s, fmtlist) {
00469     if (skipfmts.contains(s) && !outputs.contains(s)) {
00470       klfWarning("User Script Skipped format "<<s<<" which is necessary for "<<forwhat) ;
00471       return false;
00472     }
00473   }
00474   return true;
00475 }
00476 
00477 #define ASSERT_HAVE_FORMATS_FOR(forwhat)                                \
00478   { if (!assert_have_formats_for(us_outputs, us_skipfmts, forwhat)) {   \
00479       res.status = KLFERR_USERSCRIPT_BADSKIPFORMATS;                    \
00480       res.errorstr = QObject::tr("User Script broke dependencies in skip-formats list", "KLFBackend"); \
00481       return res;                                                       \
00482     }                                                                   \
00483   }
00484 
00485 
00486 
00487 
00488 KLFBackend::klfOutput KLFBackend::getLatexFormula(const klfInput& input, const klfSettings& usersettings,
00489                                                   bool isMainThread)
00490 {
00491   // ALLOW ONLY ONE RUNNING getLatexFormula() AT A TIME 
00492   QMutexLocker mutexlocker(&klf_mutex);
00493 
00494   KLF_DEBUG_TIME_BLOCK(KLF_FUNC_NAME) ;
00495 
00496   klfSettings settings;
00497   settings = usersettings;
00498 
00499   klfInput in;
00500   in = input;
00501 
00502   bool ok;
00503 
00504   klfDbg("called. latex="<<in.latex);
00505 
00506   { // get full, expanded exec environment
00507     QStringList curenv = klfCurrentEnvironment();
00508     klfDbg("current environment is "<<curenv) ;
00509     settings.execenv = klfMergeEnvironment(curenv, settings.execenv,
00510                                            QStringList() << "PATH" << "TEXINPUTS" << "BIBINPUTS",
00511                                            KlfEnvPathPrepend|KlfEnvMergeExpandVars);
00512   }
00513 
00514   klfDbg("execution environment for sub-processes is "<<settings.execenv) ;
00515 
00516   
00517   klfOutput res;
00518   res.status = KLFERR_NOERROR;
00519   res.errorstr = QString();
00520   res.result = QImage();
00521   res.pngdata_raw = QByteArray();
00522   res.pngdata = QByteArray();
00523   res.dvidata = QByteArray();
00524   res.epsdata_raw = QByteArray();
00525   res.epsdata = QByteArray();
00526   res.pdfdata = QByteArray();
00527   res.svgdata = QByteArray();
00528   res.input = in;
00529   res.settings = settings;
00530 
00531 
00532   // read GS version, will need later
00533   initGsInfo(&settings, isMainThread);
00534   if (!gsInfo.contains(settings.gsexec)) {
00535     res.status = KLFERR_NOGSVERSION;
00536     res.errorstr = QObject::tr("Can't query version of ghostscript located at `%1'.", "KLFBackend")
00537       .arg(settings.gsexec);
00538     return res;
00539   }
00540 
00541   const GsInfo thisGsInfo = gsInfo.value(settings.gsexec);
00542 
00543   klfDebugf(("%s: queried ghostscript version: %s", KLF_FUNC_NAME, qPrintable(thisGsInfo.version))) ;
00544 
00545   // force some rules on settings
00546 
00547   // if calcEpsBoundingBox is being used, we need to add bg color at "correcting bbox time"
00548   QRgb bgcolor_when_correcting_bbox = qRgba(0,0,0,0);
00549   klfDebugf(("%s: settings.calcEpsBoundingBox=%d, in.bg_color=[RGBA %d,%d,%d,%d]", KLF_FUNC_NAME,
00550              settings.calcEpsBoundingBox, qRed(in.bg_color), qGreen(in.bg_color), qBlue(in.bg_color),
00551              qAlpha(in.bg_color)));
00552 
00553   if (settings.calcEpsBoundingBox &&
00554       qAlpha(in.bg_color) != 0 && (in.bg_color & qRgb(255,255,255)) != qRgb(255,255,255)) {
00555     bgcolor_when_correcting_bbox = in.bg_color;
00556     in.bg_color = qRgba(0,0,0,0);
00557   }
00558 
00559 
00560   // PROCEDURE (V3.3)
00561   //
00562   // EACH STEP MIGHT BE DONE BY A USER SCRIPT INSTEAD IF THAT IS REQUESTED.
00563   //
00564   // - generate LaTeX file
00565   //
00566   // - latex                            --> get DVI file
00567   //
00568   // - dvips -E file.dvi -o file.eps    --> get (first) EPS file
00569   //
00570   // - gs -dNOPAUSE -dSAFER -sDEVICE=bbox -q -dBATCH file.eps      --> calculate correct bbox for EPS file
00571   //
00572   //   will output something like
00573   //     %%BoundingBox: int(X1) int(Y1) int(X2) int(Y2)
00574   //     %%HiResBoundingBox: X1 Y1 X2 Y2
00575   //
00576   // - read file.eps, modify e.g. as file-bbox.eps: replace
00577   //     %%BoundingBox ***
00578   //   by
00579   //     %%HiResBoundingBox: 0 0 (X2-X1) (Y2-Y1)
00580   //     -X1 -Y1 translate
00581   //   while of course taking into account manual corrections given by [lrtb]borderoffset settings/overrides
00582   //
00583   // EITHER (gs >= 9.01 && !outlinefonts)
00584   //   ### PhF: update this doc!! it's wrong!!
00585   //  - gs -dNOPAUSE -dSAFER -dSetPageSize -sDEVICE=ps2write -dEPSCrop -sOutputFile=file-corrected.(e)ps
00586   //       -q -dBATCH  file-bbox.eps    --> generate (E)PS file w/ correct page size
00587   // OR
00588   //  - gs -dNOCACHE -dNOPAUSE -dSAFER -sDEVICE=pswrite -dEPSCrop -sOutputFile=file-corrected.eps -q -dBATCH
00589   //       file-bbox.eps                --> generate post-processed (E)PS file
00590   //
00591   // - gs -dNOPAUSE -dSAFER -sDEVICE=pdfwrite -sOutputFile=file.pdf -q -dBATCH file-corrected.eps
00592   //       with added pdfmarks..
00593   //
00594   // - if (reports-has-device(svg)) {
00595   //
00596   //     - gs -dNOPAUSE -dSAFER -sDEVICE=svg -r72x72 -sOutputFile=file.svg -q -dBATCH file-corrected.eps
00597   //
00598   //     - modify SVG file to replace  width='WWpt' height='HHpt' by 
00599   //         width='(X2-X1)px' height='(Y2-Y1)px'
00600   //       with data given by gs before, with full precision
00601   //
00602   // - }
00603 
00604   QString ver = KLF_VERSION_STRING;
00605   ver.replace(".", "x"); // make friendly names with chars in [a-zA-Z0-9]
00606   // the base name for all our temp files
00607   //  QString tempfname = settings.tempdir + "/klftmp" + ver + "T" + QDateTime::currentDateTime().toString("hhmmss")
00608   //    + "p"+ QString("%1").arg(QCoreApplication::applicationPid(), 0, 26);
00609   QString temptemplate = settings.tempdir + "/klftmp"+ver+"-XXXXXX";
00610 
00611   // create the temporary directory
00612   QTemporaryDir tempdir(temptemplate);
00613 
00614   if (!tempdir.isValid()) {
00615     // failed to create temporary directory
00616     res.errorstr = QObject::tr("Failed to create temporary directory inside `%1'",
00617                                "KLFBackend").arg(settings.tempdir);
00618     res.status = KLFERR_TEMPDIR_FAIL;
00619     return res;
00620   }
00621 
00622   QString tempfname = tempdir.path() + "/klftemp";
00623 
00624   klfDbg("Temp location base name is "<<tempfname) ;
00625 
00626   QString fnTex = tempfname + ".tex";
00627   QString fnDvi = tempfname + ".dvi";
00628   QString fnRawEps = tempfname + ".eps";
00629   QString fnBBoxEps = tempfname + "-bbox.eps";
00630   QString fnProcessedEps = tempfname + "-processed.eps";
00631   QString fnRawPng = tempfname + "-raw.png";
00632   QString fnPdfMarks = tempfname + ".pdfmarks";
00633   QString fnPdf = tempfname + ".pdf";
00634   QString fnGsSvg = tempfname + "-gs.svg";
00635   // some user scripts may provide directly .svg (even though the default process chain
00636   // processes raw svg in memory, not generating final svg file)
00637   QString fnSvg = tempfname + ".svg";
00638 
00639   // we need non-outlinedfont EPS data anyway.
00640   QByteArray rawepsdata;
00641   QByteArray bboxepsdata;
00642   QByteArray gssvgdata;
00643 
00644 
00645   QString latexsimplified = in.latex.trimmed();
00646   if (latexsimplified.isEmpty()) {
00647     res.errorstr = QObject::tr("You must specify a LaTeX formula!", "KLFBackend");
00648     res.status = KLFERR_MISSINGLATEXFORMULA;
00649     return res;
00650   }
00651 
00652   if (!in.bypassTemplate) {
00653     if (in.mathmode.contains("...") == 0) {
00654       res.status = KLFERR_MISSINGMATHMODETHREEDOTS;
00655       res.errorstr = QObject::tr("The math mode string doesn't contain '...'!", "KLFBackend");
00656       return res;
00657     }
00658   }
00659 
00660   // prepare LaTeX file
00661   {
00662     QFile file(fnTex);
00663     bool r = file.open(QIODevice::WriteOnly);
00664     if ( ! r ) {
00665       res.status = KLFERR_TEXWRITEFAIL;
00666       res.errorstr = QObject::tr("Can't open file for writing: '%1'!", "KLFBackend").arg(fnTex);
00667       return res;
00668     }
00669     QTextStream stream(&file);
00670     if (!in.bypassTemplate) {
00671       TemplateGenerator *t = NULL;
00672       DefaultTemplateGenerator deft;
00673       if (settings.templateGenerator != NULL) {
00674         klfDbg("using custom template generator") ;
00675         t = settings.templateGenerator;
00676         KLF_ASSERT_NOT_NULL(t, "Template Generator is NULL! Using default!",  t = &deft; ) ;
00677       } else {
00678         t = &deft;
00679       }
00680       stream << t->generateTemplate(in, settings);
00681     } else {
00682       stream << in.latex;
00683     }
00684   }
00685 
00686   KLFStringSet us_outputs;
00687   KLFStringSet us_skipfmts;
00688   KLFStringSet our_skipfmts;
00689 
00690   if (!in.userScript.isEmpty()) {
00691     // user has provided us a wrapper script. Query it and use it
00692 
00693     KLFBackendEngineUserScriptInfo scriptinfo(in.userScript);
00694 
00695     if (scriptinfo.scriptInfoError() != KLFERR_NOERROR) {
00696       res.status = scriptinfo.scriptInfoError();
00697       res.errorstr = scriptinfo.scriptInfoErrorString();
00698       return res;
00699     }
00700 
00701     if ( (!scriptinfo.klfMinVersion().isEmpty()
00702           && klfVersionCompare(scriptinfo.klfMinVersion(), KLF_VERSION_STRING) > 0) ||
00703          (!scriptinfo.klfMaxVersion().isEmpty()
00704           && klfVersionCompare(scriptinfo.klfMaxVersion(), KLF_VERSION_STRING) < 0) ) {
00705       res.status = KLFERR_USERSCRIPT_BADKLFVERSION;
00706       res.errorstr = QObject::tr("User Script `%1' is not compatible with current version of KLatexFormula.",
00707                                  "KLFBackend").arg(scriptinfo.name());
00708       return res;
00709     }
00710 
00711     if (scriptinfo.category() != QLatin1String("klf-backend-engine")) {
00712       res.status = KLFERR_USERSCRIPT_BADCATEGORY;
00713       res.errorstr = QObject::tr("User Script `%1' is not usable as backend latex engine!",
00714                                  "KLFBackend").arg(scriptinfo.name());
00715       return res;
00716     }
00717 
00718     // and run the script with the latex input
00719     QStringList addenv;
00720     addenv
00721       // program executables
00722       << "KLF_TEMPDIR=" + settings.tempdir
00723       << "KLF_LATEX=" + settings.latexexec
00724       << "KLF_DVIPS=" + settings.dvipsexec
00725       << "KLF_GS=" + settings.gsexec
00726       << "KLF_GS_VERSION=" + thisGsInfo.version
00727       << "KLF_GS_DEVICES=" + QStringList(thisGsInfo.availdevices.toList()).join(",")
00728       // input
00729       << klfInputToEnvironmentForUserScript(in)
00730       // more advanced settings
00731       << klfSettingsToEnvironmentForUserScript(settings)
00732       // file names (all formed with same basename...) to access by the script
00733       << "KLF_TEMPFNAME=" + tempfname // the base name for all our temp files
00734       << "KLF_FN_TEX=" + fnTex
00735       << "KLF_FN_LATEX=" + fnTex
00736       << "KLF_FN_DVI=" + fnDvi
00737       << "KLF_FN_EPS_RAW=" + fnRawEps
00738       << "KLF_FN_EPS_BBOX=" + fnBBoxEps
00739       << "KLF_FN_EPS_PROCESSED=" + fnProcessedEps
00740       << "KLF_FN_PNG=" + fnRawPng
00741       << "KLF_FN_PDFMARKS=" + fnPdfMarks
00742       << "KLF_FN_PDF=" + fnPdf
00743       << "KLF_FN_SVG_GS=" + fnGsSvg
00744       << "KLF_FN_SVG=" + fnSvg
00745       ;
00746 
00747     { // now run the script
00748       KLFUserScriptFilterProcess p(scriptinfo.userScriptPath(), &settings);
00749 
00750       p.addExecEnviron(addenv);
00751 
00752       p.setProcessAppEvents(false);
00753 
00754       QByteArray stderrdata;
00755       QByteArray stdoutdata;
00756       p.collectStderrTo(&stderrdata);
00757       p.collectStdoutTo(&stdoutdata);
00758 
00759       p.addArgv(QDir::toNativeSeparators(fnTex));
00760 
00761       QMap<QString,QByteArray*> outdata;
00762       QStringList outfmts = scriptinfo.spitsOut();
00763       foreach (QString fmt, outfmts) {
00764         us_outputs << fmt;
00765         if (fmt == QLatin1String("latex")) {
00766           // user script overwrote the tex/latex file, don't collect the tex file data as it is not
00767           // needed (not considered as useful output!). This new tex file will be seen and accessed
00768           // by 'latex' in the next process block after userscript if needed.
00769         } else if (fmt == QLatin1String("dvi")) {
00770           outdata[fnDvi] = &res.dvidata;
00771         } else if (fmt == QLatin1String("eps-raw")) {
00772           // if we don't need this, it will be removed below from the list
00773           outdata[fnRawEps] = &rawepsdata;
00774         } else if (fmt == QLatin1String("eps-bbox")) {
00775           outdata[fnBBoxEps] = &bboxepsdata;
00776         } else if (fmt == QLatin1String("eps-processed")) {
00777           outdata[fnProcessedEps] = &res.epsdata;
00778         } else if (fmt == QLatin1String("png")) {
00779           if (settings.wantRaw) {
00780             outdata[fnRawPng] = &res.pngdata_raw;
00781           }
00782         } else if (fmt == QLatin1String("pdf")) {
00783           if (settings.wantPDF) {
00784             outdata[fnPdf] = &res.pdfdata;
00785           }
00786         } else if (fmt == QLatin1String("svg-gs")) {
00787           // ignore this data, not returned in klfOutput but the created file is used to generate
00788           // the processed SVG
00789           outdata[fnGsSvg] = & gssvgdata;
00790         } else if (fmt == QLatin1String("svg")) {
00791           if (settings.wantSVG) {
00792             outdata[fnSvg] = &res.svgdata;
00793           }
00794         } else {
00795           klfWarning("Can't handle output format from user script: "<<fmt) ;
00796         }
00797       }
00798       if (us_outputs.isEmpty()) {
00799         us_outputs << "dvi"; // by default, the script is assumed to provide DVI.
00800       }
00801       if (us_outputs.contains("eps-bbox") && !settings.wantRaw) {
00802         // don't need to fetch initial raw eps data
00803         if (outdata.contains("eps-raw"))
00804           outdata.remove(fnRawEps);
00805       }
00806       if (us_outputs.contains("eps-processed") && !settings.wantRaw) {
00807         // don't need to fetch bbox raw eps data
00808         if (outdata.contains("eps-bbox"))
00809           outdata.remove(fnBBoxEps);
00810       }
00811 
00812       QStringList skipfmts = scriptinfo.skipFormats();
00813       bool invert = false;
00814       int tempi;
00815       if ((tempi = skipfmts.indexOf("ALL_EXCEPT")) >= 0) {
00816         invert = true;
00817         skipfmts.removeAt(tempi);
00818         foreach (QString f, klfbackend_fmts) { us_skipfmts << f; }
00819       }
00820       foreach (QString fmt, skipfmts) {
00821         if (!klfbackend_fmts.contains(fmt)) {
00822           klfWarning("User Script Info: Unknown format to skip: "<<fmt) ;
00823         }
00824         if (!invert) {
00825           us_skipfmts << fmt;
00826         } else {
00827           us_skipfmts.remove(fmt);
00828         }
00829       }
00830       our_skipfmts = us_skipfmts;
00831       foreach (QString fmt, outfmts) {
00832         if (our_skipfmts.contains(fmt)) {
00833           klfWarning("User Script Info: format " << fmt << " provided by script is also marked "
00834                      "as to be skipped!") ;
00835         }
00836       }
00837 
00838       if ((us_outputs.contains("eps-processed") || our_skipfmts.contains("eps-processed")) && !settings.wantRaw) {
00839         our_skipfmts << "eps-bbox";
00840       }
00841       if ((us_outputs.contains("eps-bbox") || our_skipfmts.contains("eps-bbox")) && !settings.wantRaw) {
00842         our_skipfmts << "eps-raw";
00843       }
00844       if (us_outputs.contains("svg") || our_skipfmts.contains("svg")) {
00845         our_skipfmts << "svg-gs"; // don't need svg-gs if we're not generating svg
00846       }
00847 
00848       klfDbg("us_skipfmts = " << us_skipfmts) ;
00849 
00850       ok = p.run(outdata);
00851 
00852       if (!ok) {
00853         res.errorstr = p.resultErrorString();
00854         switch (p.resultStatus()) {
00855         case KLFFP_NOSTART:       res.status = KLFERR_USERSCRIPT_NORUN; break;
00856         case KLFFP_NOEXIT:        res.status = KLFERR_USERSCRIPT_NONORMALEXIT; break;
00857         case KLFFP_NOSUCCESSEXIT: res.status = KLFERR_PROGERR_USERSCRIPT; break;
00858         case KLFFP_NODATA:        res.status = KLFERR_USERSCRIPT_NOOUTPUT; break;
00859         case KLFFP_DATAREADFAIL:  res.status = KLFERR_USERSCRIPT_OUTPUTREADFAIL; break;
00860         default:
00861           res.status = p.resultStatus();
00862         }
00863         return res;
00864       }
00865 
00866       // make sure all promised files have appeared
00867       foreach (QString fmt, outfmts) {
00868         QString corrfname;
00869         if (fmt == QLatin1String("latex")) {
00870           corrfname = fnTex;
00871         } else if (fmt == QLatin1String("dvi")) {
00872           corrfname = fnDvi;
00873         } else if (fmt == QLatin1String("eps-raw")) {
00874           corrfname = fnRawEps;
00875         } else if (fmt == QLatin1String("eps-bbox")) {
00876           corrfname = fnBBoxEps;
00877         } else if (fmt == QLatin1String("eps-processed")) {
00878           corrfname = fnProcessedEps;
00879         } else if (fmt == QLatin1String("png")) {
00880           corrfname = fnRawPng;
00881         } else if (fmt == QLatin1String("pdf")) {
00882           corrfname = fnPdf;
00883         } else if (fmt == QLatin1String("svg-gs")) {
00884           corrfname = fnGsSvg;
00885         } else if (fmt == QLatin1String("svg")) {
00886           corrfname = fnSvg;
00887         } else {
00888           klfWarning("Unknown format: " << fmt) ;
00889           continue;
00890         }
00891         if (!QFile::exists(corrfname)) {
00892           klfWarning("Promised format " << fmt << " did not appear after calling user script.") ;
00893         }
00894       }
00895     }
00896   }
00897 
00898   klfDbg("our_skipfmts = " << our_skipfmts) ;
00899 
00900 
00901   if (!has_userscript_output(us_outputs, "dvi") && !our_skipfmts.contains("dvi")) {
00902     // execute latex
00903     klfDbg("preparing to launch latex.") ;
00904 
00905     if (settings.latexexec.isEmpty()) {
00906       res.status = KLFERR_NOLATEXPROG;
00907       res.errorstr = QObject::tr("No latex executable given!\n", "KLFBackend");
00908       return res;
00909     }
00910 
00911     KLFBackendFilterProgram p(QLatin1String("LaTeX"), &settings, isMainThread, tempdir.path());
00912     p.resErrCodes[KLFFP_NOSTART] = KLFERR_LATEX_NORUN;
00913     p.resErrCodes[KLFFP_NOEXIT] = KLFERR_LATEX_NONORMALEXIT;
00914     p.resErrCodes[KLFFP_NOSUCCESSEXIT] = KLFERR_PROGERR_LATEX;
00915     p.resErrCodes[KLFFP_NODATA] = KLFERR_LATEX_NOOUTPUT;
00916     p.resErrCodes[KLFFP_DATAREADFAIL] = KLFERR_LATEX_OUTPUTREADFAIL;
00917 
00918     p.setArgv(QStringList() << settings.latexexec << QDir::toNativeSeparators(fnTex));
00919 
00920     QByteArray userinputforerrors = "h\nr\n";
00921     
00922     ok = p.run(userinputforerrors, fnDvi, &res.dvidata);
00923     if (!ok) {
00924       p.errorToOutput(&res);
00925       return res;
00926     }
00927   }
00928 
00929   if (!has_userscript_output(us_outputs, "eps-raw") && !our_skipfmts.contains("eps-raw")) {
00930 
00931     ASSERT_HAVE_FORMATS_FOR("eps-raw") ;
00932 
00933     if (settings.dvipsexec.isEmpty()) {
00934       res.status = KLFERR_NODVIPSPROG;
00935       res.errorstr = QObject::tr("No dvips executable given!\n", "KLFBackend");
00936       return res;
00937     }
00938 
00939     // execute dvips -E
00940     KLFBackendFilterProgram p(QLatin1String("dvips"), &settings, isMainThread, tempdir.path());
00941     p.resErrCodes[KLFFP_NOSTART] = KLFERR_DVIPS_NORUN;
00942     p.resErrCodes[KLFFP_NOEXIT] = KLFERR_DVIPS_NONORMALEXIT;
00943     p.resErrCodes[KLFFP_NOSUCCESSEXIT] = KLFERR_PROGERR_DVIPS;
00944     p.resErrCodes[KLFFP_NODATA] = KLFERR_DVIPS_NOOUTPUT;
00945     p.resErrCodes[KLFFP_DATAREADFAIL] = KLFERR_DVIPS_OUTPUTREADFAIL;
00946 
00947     QFileInfo dvipsinf(settings.dvipsexec);
00948     if (!dvipsinf.filePath().isEmpty()) {
00949       // add the explicit dvips path to the PATH environment, in case dvips needs to
00950       // execute helpers such as mktexpk
00951       p.addExecEnviron(QStringList() << (
00952                            QLatin1String("PATH=") + dvipsinf.absoluteFilePath() + QLatin1String(":") +
00953                            QProcessEnvironment::systemEnvironment().value("PATH")
00954                            )) ;
00955     }
00956 
00957     p.setArgv(QStringList() << settings.dvipsexec << "-E" << QDir::toNativeSeparators(fnDvi)
00958               << "-o" << QDir::toNativeSeparators(fnRawEps));
00959 
00960     ok = p.run(fnRawEps, &rawepsdata);
00961 
00962     if (!ok) {
00963       p.errorToOutput(&res);
00964       return res;
00965     }
00966     klfDbg("read raw EPS; rawepsdata/length="<<rawepsdata.size()) ;
00967   } // end of 'dvips' block
00968 
00969   // the settings requires, save the intermediary data in to result output
00970   if (settings.wantRaw)
00971     res.epsdata_raw = rawepsdata;
00972 
00973   // This now also returned in 'res', directly saved there.
00974   //  // width and height of the (final) EPS bbox in postscript points
00975   //  double width_pt = 0, height_pt = 0;
00976 
00977   if (!has_userscript_output(us_outputs, "eps-bbox") && !our_skipfmts.contains("eps-bbox")) {
00978     // find correct bounding box of EPS file, and modify EPS data manually to add boffset and
00979     // translate to (0,0,width,height)
00980 
00981     ASSERT_HAVE_FORMATS_FOR("eps-bbox") ;
00982 
00983     klfbbox bbox, bbox_corrected;
00984 
00985     if (settings.calcEpsBoundingBox) {
00986       bool ok = calculate_gs_eps_bbox(QByteArray(), fnRawEps, &bbox, &res, settings, isMainThread);
00987       if (!ok)
00988         return res; // res was set by the function
00989     } else {
00990       bool ok = read_eps_bbox(rawepsdata, &bbox, &res);
00991       if (!ok)
00992         return res; // res was set by the function
00993     }
00994 
00995     bbox.x1 -= settings.lborderoffset;
00996     bbox.y1 -= settings.bborderoffset;
00997     bbox.x2 += settings.rborderoffset;
00998     bbox.y2 += settings.tborderoffset;
00999 
01000     res.width_pt = bbox.x2 - bbox.x1;
01001     res.height_pt = bbox.y2 - bbox.y1;
01002 
01003     // now correct the bbox to (0,0,width,height)
01004 
01005     bbox_corrected.x1 = 0;
01006     bbox_corrected.y1 = 0;
01007     bbox_corrected.x2 = res.width_pt;
01008     bbox_corrected.y2 = res.height_pt;
01009 
01010     // and generate corrected raw EPS
01011 
01012     correct_eps_bbox(rawepsdata, bbox_corrected, bbox, in.vectorscale,
01013                      bgcolor_when_correcting_bbox,  &bboxepsdata);
01014 
01015     klfDbg("corrected bbox to "<<bbox.x1<<","<<bbox.y1<<","<<bbox.x2<<","<<bbox.y2);
01016   } else if (!our_skipfmts.contains("eps-bbox")) {
01017     // userscript generated bbox-corrected EPS for us, but we still
01018     // need to set width_pt and height_pt appropriately.
01019 
01020     klfbbox bb;
01021 
01022     // read from fnRawEps, fnBBoxEps or fnProcessedEps ?
01023     QString fn;
01024     //QByteArray *dat;
01025     if (us_outputs.contains("eps-processed")) {
01026       fn = fnProcessedEps;
01027       //dat = & res.epsdata;
01028     } else {
01029       fn = fnBBoxEps;
01030       //dat = & bboxepsdata;
01031     }
01032 
01033     if (settings.calcEpsBoundingBox) {
01034       bool ok = calculate_gs_eps_bbox(QByteArray(), fn, &bb, &res, settings, isMainThread);
01035       if (!ok)
01036         return res; // res was set by the function
01037     } else {
01038       bool ok = read_eps_bbox(bboxepsdata, &bb, &res);
01039       if (!ok)
01040         return res; // res was set by the function
01041     }
01042 
01043     res.width_pt = bb.x2 - bb.x1;
01044     res.height_pt = bb.y2 - bb.y1;
01045   } // end 'correct bbox in eps' block
01046 
01047   // the settings requires, save the intermediary data in to result output
01048   if (settings.wantRaw)
01049     res.epsdata_bbox = bboxepsdata;
01050   
01051   if (!has_userscript_output(us_outputs, "eps-processed") && !our_skipfmts.contains("eps-processed")) {
01052     // need to process EPS, i.e. outline fonts
01053 
01054     ASSERT_HAVE_FORMATS_FOR("eps-processed") ;
01055 
01056     if (settings.outlineFonts) {
01057       // post-process EPS file to outline fonts if requested
01058 
01059       if (settings.gsexec.isEmpty()) {
01060         res.status = KLFERR_NOGSPROG;
01061         res.errorstr = QObject::tr("No gs executable given!\n", "KLFBackend");
01062         return res;
01063       }
01064 
01065       KLFBackendFilterProgram p(QLatin1String("gs (EPS Post-Processing Outline Fonts)"), &settings, isMainThread,
01066                                 tempdir.path());
01067       p.resErrCodes[KLFFP_NOSTART] = KLFERR_GSPOSTPROC_NORUN;
01068       p.resErrCodes[KLFFP_NOEXIT] = KLFERR_GSPOSTPROC_NONORMALEXIT;
01069       p.resErrCodes[KLFFP_NOSUCCESSEXIT] = KLFERR_PROGERR_GSPOSTPROC;
01070       p.resErrCodes[KLFFP_NODATA] = KLFERR_GSPOSTPROC_NOOUTPUT;
01071       p.resErrCodes[KLFFP_DATAREADFAIL] = KLFERR_GSPOSTPROC_OUTPUTREADFAIL;
01072 
01073       QString psdevice;
01074       QStringList gsoptions;
01075       const char *env_gs_device = getenv("KLFBACKEND_GS_PS_DEVICE");
01076       if (env_gs_device != NULL) {
01077         psdevice = QString::fromLatin1(env_gs_device);
01078         gsoptions = QStringList() << "-dNOCACHE -dNoOutputFonts";
01079       } else if (thisGsInfo.version_maj < 9 ||
01080                  (thisGsInfo.version_maj == 9 && thisGsInfo.version_min <= 7)) {
01081         // until 9.07, we can still use 'pswrite' with -dNOCACHE
01082         psdevice = QLatin1String("pswrite");
01083         gsoptions = QStringList() << "-dNOCACHE";
01084       } else if (thisGsInfo.version_maj > 9 ||
01085                  (thisGsInfo.version_maj == 9 && thisGsInfo.version_min >= 15)) {
01086         // Ghostscript removed the pswrite device after 9.07; The ghostscript developers
01087         // were very responsive and helpful to my feedback, and thanks to their prompt
01088         // reaction, starting from 9.15, we can use the ps2write device with the option
01089         // "-dNoOutputFonts".
01090         psdevice = QLatin1String("ps2write");
01091         gsoptions = QStringList() << "-dNoOutputFonts";
01092       } else {
01093         res.status = KLFERR_GSPOSTPROC_NOOUTLINEFONTS;
01094         res.errorstr = QObject::tr("Installed Ghostscript version %1 may not be used to create font outlines."
01095                                    " Please upgrade to gs >= 9.15 (or downgrade to gs <= 9.07).",
01096                                    "KLFBackend").arg(thisGsInfo.version);
01097         return res;
01098       }
01099 
01100       p.addArgv(settings.gsexec);
01101       p.addArgv(QStringList() << gsoptions
01102                 << "-dNOPAUSE" << "-dSAFER" << "-dEPSCrop" << QString::fromLatin1("-sDEVICE=%1").arg(psdevice)
01103                 << "-sOutputFile="+QDir::toNativeSeparators(fnProcessedEps)
01104                 << "-q" << "-dBATCH" << "-");
01105       
01106       ok = p.run(bboxepsdata, fnProcessedEps, &res.epsdata);
01107       if (!ok) {
01108         p.errorToOutput(&res);
01109         return res;
01110       }
01111 
01112       klfDebugf(("%s: res.epsdata has length=%d", KLF_FUNC_NAME, res.epsdata.size())) ;
01113 
01114     } else {
01115       // no post-processed EPS, copy raw (bbox-corrected) EPS data:
01116       res.epsdata = bboxepsdata;
01117     }
01118   }
01119 
01120   if (!has_userscript_output(us_outputs, "png") && !our_skipfmts.contains("png")) {
01121 
01122     ASSERT_HAVE_FORMATS_FOR("png") ;
01123 
01124     if (settings.gsexec.isEmpty()) {
01125       res.status = KLFERR_NOGSPROG;
01126       res.errorstr = QObject::tr("No gs executable given!\n", "KLFBackend");
01127       return res;
01128     }
01129 
01130     // run 'gs' to get PNG data
01131     KLFBackendFilterProgram p(QLatin1String("gs (PNG)"), &settings, isMainThread, tempdir.path());
01132     p.resErrCodes[KLFFP_NOSTART] = KLFERR_GSPNG_NORUN;
01133     p.resErrCodes[KLFFP_NOEXIT] = KLFERR_GSPNG_NONORMALEXIT;
01134     p.resErrCodes[KLFFP_NOSUCCESSEXIT] = KLFERR_PROGERR_GSPNG;
01135     p.resErrCodes[KLFFP_NODATA] = KLFERR_GSPNG_NOOUTPUT;
01136     p.resErrCodes[KLFFP_DATAREADFAIL] = KLFERR_GSPNG_OUTPUTREADFAIL;
01137 
01142     // ### wait... do we want vector scaling to apply to the PNG as well??
01143 
01144     p.setArgv(QStringList() << settings.gsexec
01145               << "-dNOPAUSE" << "-dSAFER" << "-dTextAlphaBits=4" << "-dGraphicsAlphaBits=4"
01146               << "-r"+QString::number(in.dpi) << "-dEPSCrop" << "-dMaxBitmap=2147483647");
01147     if (qAlpha(in.bg_color) > 0) { // we're forcing a background color
01148       p.addArgv("-sDEVICE=png16m");
01149     } else {
01150       p.addArgv("-sDEVICE=pngalpha");
01151     }
01152     p.addArgv(QStringList() << "-sOutputFile="+QDir::toNativeSeparators(fnRawPng) << "-q" << "-dBATCH" << "-");
01153 
01154     ok = p.run(bboxepsdata, fnRawPng, &res.pngdata_raw);
01155     if (!ok) {
01156       p.errorToOutput(&res);
01157       return res;
01158     }
01159 
01160     res.result.loadFromData(res.pngdata_raw, "PNG");
01161   } // raw PNG
01162   else {
01163     if (us_skipfmts.contains("png")) {
01164       klfWarning("PNG format was skipped by user script. The QImage object will be invalid.") ;
01165       res.result = QImage();
01166       res.pngdata = QByteArray();
01167       res.pngdata_raw = QByteArray();
01168     }
01169     if (!has_userscript_output(us_outputs, "png") || !QFile::exists(fnRawPng)) {
01170       klfWarning("PNG format is required to initialize the QImage object, but was not generated by user script.") ;
01171       res.result = QImage();
01172     } else {
01173       // load PNG into res.result
01174       res.result.load(fnRawPng);
01175     }
01176   }
01177 
01178   if (!our_skipfmts.contains("png")) { // generate tagged/labeled PNG
01179 
01180     // store some meta-information into result
01181     KLFImageLatexMetaInfo metainfo(&res.result);
01182     metainfo.saveMetaInfo(in, settings);
01183     
01184     { // create "final" PNG data
01185       QBuffer buf(&res.pngdata);
01186       buf.open(QIODevice::WriteOnly);
01187 
01188       bool r = res.result.save(&buf, "PNG");
01189       if (!r) {
01190         klfWarning("Can't save \"final\" PNG data.") ;
01191         res.pngdata = res.pngdata_raw;
01192       }
01193     }
01194 
01195     klfDbg("prepared final PNG data.") ;
01196   }
01197 
01198   if ( settings.wantPDF && !has_userscript_output(us_outputs, "pdf") && !our_skipfmts.contains("pdf") ) {
01199 
01200     ASSERT_HAVE_FORMATS_FOR("pdf") ;
01201 
01202     // prepare PDFMarks
01203     { QFile fpdfmarks(fnPdfMarks);
01204       bool r = fpdfmarks.open(QIODevice::WriteOnly);
01205       if ( ! r ) {
01206         res.status = KLFERR_PDFMARKSWRITEFAIL;
01207         res.errorstr = QObject::tr("Can't open file for writing: '%1'!", "KLFBackend").arg(fnPdfMarks);
01208         return res;
01209       }
01210       QByteArray pdfmarkstr;
01211       KLFPdfmarksWriteLatexMetaInfo pdfmetainfo(&pdfmarkstr);
01212       pdfmetainfo.savePDFField("Title", in.latex);
01213       pdfmetainfo.savePDFField("Keywords", "KLatexFormula KLF LaTeX equation formula");
01214       pdfmetainfo.savePDFField("Creator", "KLatexFormula " KLF_VERSION_STRING);
01215       pdfmetainfo.saveMetaInfo(in, settings);
01216       pdfmetainfo.finish();
01217       fpdfmarks.write(pdfmarkstr);
01218       // file is ready.
01219     }
01220 
01221     if (settings.gsexec.isEmpty()) {
01222       res.status = KLFERR_NOGSPROG;
01223       res.errorstr = QObject::tr("No gs executable given!\n", "KLFBackend");
01224       return res;
01225     }
01226 
01227     // run 'gs' to get PDF data
01228     KLFBackendFilterProgram p(QLatin1String("gs (PDF)"), &settings, isMainThread, tempdir.path());
01229     p.resErrCodes[KLFFP_NOSTART] = KLFERR_GSPDF_NORUN;
01230     p.resErrCodes[KLFFP_NOEXIT] = KLFERR_GSPDF_NONORMALEXIT;
01231     p.resErrCodes[KLFFP_NOSUCCESSEXIT] = KLFERR_PROGERR_GSPDF;
01232     p.resErrCodes[KLFFP_NODATA] = KLFERR_GSPDF_NOOUTPUT;
01233     p.resErrCodes[KLFFP_DATAREADFAIL] = KLFERR_GSPDF_OUTPUTREADFAIL;
01234 
01235     p.setArgv(QStringList() << settings.gsexec
01236               << "-dNOPAUSE" << "-dSAFER" << "-sDEVICE=pdfwrite"
01237               << "-sOutputFile="+QDir::toNativeSeparators(fnPdf)
01238               << "-q" << "-dBATCH" << "-" << fnPdfMarks);
01239 
01240     // input: res.epsdata is the processed EPS file, or the raw EPS + bbox/page correction if no post-processing
01241     ok = p.run(res.epsdata, fnPdf, &res.pdfdata);
01242     if (!ok) {
01243       p.errorToOutput(&res);
01244       return res;
01245     }
01246   }
01247 
01248   if (settings.wantSVG) {
01249 
01250     if (!has_userscript_output(us_outputs, "svg-gs") &&
01251         !our_skipfmts.contains("svg-gs")) {
01252 
01253       ASSERT_HAVE_FORMATS_FOR("svg-gs") ;
01254 
01255       // run 'gs' to get SVG (raw from gs)
01256       if (!thisGsInfo.availdevices.contains("svg")) {
01257         // not OK to get SVG...
01258         klfWarning("ghostscript cannot create SVG");
01259         res.status = KLFERR_GSSVG_NOSVG;
01260         res.errorstr = QObject::tr("This ghostscript (%1) cannot generate SVG.", "KLFBackend").arg(settings.gsexec);
01261         return res;
01262       }
01263 
01264       if (settings.gsexec.isEmpty()) {
01265         res.status = KLFERR_NOGSPROG;
01266         res.errorstr = QObject::tr("No gs executable given!\n", "KLFBackend");
01267         return res;
01268       }
01269 
01270       KLFBackendFilterProgram p(QLatin1String("gs (SVG)"), &settings, isMainThread, tempdir.path());
01271       p.resErrCodes[KLFFP_NOSTART] = KLFERR_GSSVG_NORUN;
01272       p.resErrCodes[KLFFP_NOEXIT] = KLFERR_GSSVG_NONORMALEXIT;
01273       p.resErrCodes[KLFFP_NOSUCCESSEXIT] = KLFERR_PROGERR_GSSVG;
01274       p.resErrCodes[KLFFP_NODATA] = KLFERR_GSSVG_NOOUTPUT;
01275       p.resErrCodes[KLFFP_DATAREADFAIL] = KLFERR_GSSVG_OUTPUTREADFAIL;
01276       
01277       p.addArgv(settings.gsexec);
01278       // unconditionally outline fonts, otherwise output is horrible
01279       p.addArgv(QStringList() << "-dNOCACHE" << "-dNOPAUSE" << "-dSAFER" << "-dEPSCrop" << "-sDEVICE=svg"
01280                 << "-sOutputFile="+QDir::toNativeSeparators(fnGsSvg)
01281                 << "-q" << "-dBATCH" << "-");
01282 
01283       // input: res.epsdata is the processed EPS file, or the raw EPS file if no post-processing
01284       ok = p.run(bboxepsdata, fnGsSvg, &gssvgdata);
01285       if (!ok) {
01286         p.errorToOutput(&res);
01287         return res;
01288       }
01289     }
01290 
01291     if (!has_userscript_output(us_outputs, "svg") && !our_skipfmts.contains("svg")) {
01292 
01293       ASSERT_HAVE_FORMATS_FOR("svg") ;
01294 
01295       // and now re-touch SVG generated by ghostscript that is not very clean...
01296       // find the first occurences of width='' and height='' and set them to the
01297       // appropriate width and heights given by BBox read earlier
01298 
01299       klfDebugf(("%s: gssvgdata/length=%d", KLF_FUNC_NAME, gssvgdata.size())) ;
01300 
01301       replace_svg_width_or_height(&gssvgdata, "width=", res.width_pt);
01302       replace_svg_width_or_height(&gssvgdata, "height=", res.height_pt);
01303 
01304       klfDebugf(("%s: now, gssvgdata/length=%d", KLF_FUNC_NAME, gssvgdata.size())) ;
01305 
01306       res.svgdata = gssvgdata;
01307     }
01308   } // end if(wantSVG)
01309 
01310   klfDbg("end of function.") ;
01311 
01312   return res;
01313 }
01314 
01315 
01316 
01317 static bool calculate_gs_eps_bbox(const QByteArray& epsData, const QString& epsFile, klfbbox *bbox,
01318                                   KLFBackend::klfOutput * resError, const KLFBackend::klfSettings& settings,
01319                                   bool isMainThread)
01320 {
01321   KLF_DEBUG_BLOCK(KLF_FUNC_NAME) ;
01322   // find correct bounding box of EPS file, using ghostscript
01323 
01324   int i;
01325 
01326   if (settings.gsexec.isEmpty()) {
01327     resError->status = KLFERR_NOGSPROG;
01328     resError->errorstr = QObject::tr("No gs executable given!\n", "KLFBackend");
01329     return false;
01330   }
01331 
01332   KLFBackendFilterProgram p(QLatin1String("GhostScript (bbox)"), &settings, isMainThread, settings.tempdir);
01333   p.resErrCodes[KLFFP_NOSTART] = KLFERR_GSBBOX_NORUN;
01334   p.resErrCodes[KLFFP_NOEXIT] = KLFERR_GSBBOX_NONORMALEXIT;
01335   p.resErrCodes[KLFFP_NOSUCCESSEXIT] = KLFERR_PROGERR_GSBBOX;
01336   p.resErrCodes[KLFFP_NODATA] = KLFERR_GSBBOX_NOOUTPUT;
01337   // p.resErrCodes[KLFFP_DATAREADFAIL]  unused (used only for reading output files)
01338 
01339   p.setOutputStdout(true);
01340   p.setOutputStderr(true);
01341   
01342   QByteArray bboxdata;
01343 
01344   p.setArgv(QStringList() << settings.gsexec << "-dNOPAUSE" << "-dSAFER" << "-sDEVICE=bbox" << "-q" << "-dBATCH"
01345             << (epsFile.isEmpty() ? QString::fromLatin1("-") : epsFile));
01346 
01347   bool ok = p.run(epsData /*stdin*/, QString() /*no output file*/, &bboxdata/*collect stdout*/);
01348   if (!ok) {
01349     p.errorToOutput(resError);
01350     return false;
01351   }
01352   
01353   klfDbg("gs provided output:\n"<<bboxdata);
01354 
01355   // parse gs' bbox data
01356   QRegExp rx_gsbbox("%%HiResBoundingBox\\s*:\\s+" D_RX "\\s+" D_RX "\\s+" D_RX "\\s+" D_RX "");
01357   i = rx_gsbbox.indexIn(QString::fromLatin1(bboxdata));
01358   if (i < 0) {
01359     resError->status = KLFERR_GSBBOX_NOBBOX;
01360     resError->errorstr = QObject::tr("Ghostscript did not provide parsable BBox output!", "KLFBackend");
01361     return false;
01362   }
01363   bbox->x1 = rx_gsbbox.cap(1).toDouble();
01364   bbox->y1 = rx_gsbbox.cap(2).toDouble();
01365   bbox->x2 = rx_gsbbox.cap(3).toDouble();
01366   bbox->y2 = rx_gsbbox.cap(4).toDouble();
01367 
01368   return true;
01369 }
01370 
01371 
01372 static bool parse_bbox_values(const QString& str, klfbbox *bbox)
01373 {
01374   // parse bbox values
01375   QRegExp rx_bbvalues("" D_RX "\\s+" D_RX "\\s+" D_RX "\\s+" D_RX "");
01376   int i = rx_bbvalues.indexIn(str);
01377   if (i < 0) {
01378     return false;
01379   }
01380   bbox->x1 = rx_bbvalues.cap(1).toDouble();
01381   bbox->y1 = rx_bbvalues.cap(2).toDouble();
01382   bbox->x2 = rx_bbvalues.cap(3).toDouble();
01383   bbox->y2 = rx_bbvalues.cap(4).toDouble();
01384   return true;
01385 }
01386 
01387 static bool s_starts_with(const char * x, int len_x, const char *test, int len_test)
01388 {
01389   if (len_x < len_test)
01390     return false;
01391   return !strncmp(x, test, len_test);
01392 }
01393 
01394 static bool read_eps_bbox(const QByteArray& epsdata, klfbbox *bbox, KLFBackend::klfOutput * resError)
01395 {
01396   KLF_DEBUG_BLOCK(KLF_FUNC_NAME) ;
01397 
01398   static const char * hibboxtag = "%%HiResBoundingBox:";
01399   static const char * bboxtag = "%%BoundingBox:";
01400   static const int hibboxtaglen = strlen(hibboxtag);
01401   static const int bboxtaglen = strlen(bboxtag);
01402 
01403   // Read dvips' bounding box.
01404   QBuffer buf;
01405   buf.setData(epsdata);
01406   bool r = buf.open(QIODevice::ReadOnly | QIODevice::Text);
01407   if (!r) {
01408     klfWarning("What's going on!!?! can't open buffer for reading? Will Fail!!!") ;
01409   }
01410 
01411   QString nobboxerrstr =
01412     QObject::tr("DVIPS did not provide parsable %%BoundingBox: in its output!", "KLFBackend");
01413 
01414   char linebuffer[512];
01415   int n;
01416   bool gotepsbbox = false;
01417   int still_look_for_hiresbbox_lines = 5;
01418   while ((n = buf.readLine(linebuffer, sizeof(linebuffer)-1)) > 0) {
01419     if (gotepsbbox && still_look_for_hiresbbox_lines-- < 0) {
01420       // if we already got the %BoundingBox, and we've been looking at more than a certian number of lines
01421       // after that, abort because usually %BoundingBox and %HiResBoundingBox are together...
01422       klfDbg("stopped looking for hires-bbox.") ;
01423       break;
01424     }
01425     if (s_starts_with(linebuffer, n, hibboxtag, hibboxtaglen)) {
01426       // got hi-res bounding-box
01427       bool ok = parse_bbox_values(QString::fromLatin1(linebuffer+hibboxtaglen), bbox);
01428       if (!ok) {
01429         if (resError) {
01430           resError->status = KLFERR_DVIPS_OUTPUTNOBBOX;
01431           resError->errorstr = nobboxerrstr;
01432         }
01433         return false;
01434       }
01435       klfDbg("got hires-bbox.") ;
01436       // all ok, got hi-res bbox
01437       return true;
01438     }
01439     if (s_starts_with(linebuffer, n, bboxtag, bboxtaglen)) {
01440       // got bounding-box.
01441       bool ok = parse_bbox_values(QString::fromLatin1(linebuffer+bboxtaglen), bbox);
01442       if (!ok) {
01443         continue;
01444       }
01445       // stand by, continue in case we have a hi-res bbox.
01446       gotepsbbox = true;
01447       klfDbg("got normal bbox.") ;
01448       continue;
01449     }
01450   }
01451 
01452   // didn't get a hi-res bbox. see if we still got a regular %BoundingBox: and return that.
01453   if (gotepsbbox) {
01454     // bbox pointer is already set
01455     return true;
01456   }
01457 
01458   if (resError) {
01459     resError->status = KLFERR_DVIPS_OUTPUTNOBBOX;
01460     resError->errorstr = nobboxerrstr;
01461   }
01462   return false;
01463 }
01464 
01470 static void correct_eps_bbox(const QByteArray& rawepsdata, const klfbbox& bbox_corrected,
01471                              const klfbbox& bbox_orig, double vectorscale, QRgb bgcolor,
01472                              QByteArray * epsdatacorrected)
01473 {
01474   KLF_DEBUG_BLOCK(KLF_FUNC_NAME) ;
01475 
01476   static const char * bboxdecl = "%%BoundingBox:";
01477   static int bboxdecl_len = strlen(bboxdecl);
01478 
01479   double offx = bbox_corrected.x1 - bbox_orig.x1;
01480   double offy = bbox_corrected.y1 - bbox_orig.y1;
01481 
01482   // in raw EPS data, find '%%BoundingBox:' and length of the full BoundingBox instruction
01483   int i, len;
01484   char nl[] = "\0\0\0";
01485   i = rawepsdata.indexOf(bboxdecl);
01486   if (i < 0) {
01487     i = 0;
01488     len = 0;
01489   } else {
01490     int j = i+bboxdecl_len;
01491     while (j < (int)rawepsdata.size() && rawepsdata[j] != '\r' && rawepsdata[j] != '\n')
01492       ++j;
01493     len = j-i;
01494     if (rawepsdata[j] == '\r' && j < (int)rawepsdata.size()-1 && rawepsdata[j+1] == '\n') {
01495       nl[0] = '\r', nl[1] = '\n';
01496     } else {
01497       nl[0] = rawepsdata[j];
01498     }
01499   }
01500 
01501   double dwi = bbox_corrected.x2 * vectorscale;
01502   double dhi = bbox_corrected.y2 * vectorscale;
01503   int wi = (int)(dwi + 0.99999) ;
01504   int hi = (int)(dhi + 0.99999) ;
01505   char buffer[1024];
01506   int buffer_len;
01507   // recall that '%%' in printf is replaced by a single '%'...
01508   snprintf(buffer, sizeof(buffer)-1,
01509            "%%%%BoundingBox: 0 0 %d %d%s"
01510            "%%%%HiResBoundingBox: 0 0 %s %s%s",
01511            wi, hi, nl,
01512            klfFmtDoubleCC(dwi, 'g', 6), klfFmtDoubleCC(dhi, 'g', 6), nl);
01513   buffer_len = strlen(buffer);
01514 
01515   char backgroundfillps[1024] = "";
01516   if (qAlpha(bgcolor) > 0) {
01517     klfDbg("we have a bg color, so draw the background. bgcolor="<<klfFmt("%#10x", (uint)bgcolor));
01518     snprintf(backgroundfillps, sizeof(backgroundfillps)-1,
01519             // draw the background color, if any
01520             "newpath "
01521             "-2 -2 moveto "
01522             "%s -2 lineto "
01523             "%s %s lineto "
01524             "-2 %s lineto "
01525             "closepath "
01526             "gsave "
01527             "%s %s %s setrgbcolor "
01528             "fill "
01529             "grestore %s",
01530             klfFmtDoubleCC(dwi+1, 'g', 6),
01531             klfFmtDoubleCC(dwi+1, 'g', 6), klfFmtDoubleCC(dhi+1, 'g', 6),
01532             klfFmtDoubleCC(dhi+1, 'g', 6),
01533             // and the color, in RGB components:
01534             klfFmtDoubleCC(qRed(bgcolor)/255.0, 'f', 6),
01535             klfFmtDoubleCC(qGreen(bgcolor)/255.0, 'f', 6),
01536             klfFmtDoubleCC(qBlue(bgcolor)/255.0, 'f', 6),
01537             nl
01538           );
01539   }
01540 
01541   char buffer2[1024];
01542   //int buffer2_len;
01543   snprintf(buffer2, sizeof(buffer2)-1,
01544            "%s"
01545            "%%%%Page 1 1%s"
01546            "%%%%PageBoundingBox 0 0 %d %d%s"
01547            "<< /PageSize [%d %d] >> setpagedevice%s"
01548            "%s"
01549            "%s %s scale%s"
01550            "%s %s translate%s"
01551            ,
01552            nl,
01553            nl,
01554            wi, hi, nl,
01555            wi, hi, nl,
01556            backgroundfillps,
01557            klfFmtDoubleCC(vectorscale, 'f'), klfFmtDoubleCC(vectorscale, 'f'), nl,
01558            klfFmtDoubleCC(offx, 'f'), klfFmtDoubleCC(offy, 'f'), nl);
01559   //buffer2_len = strlen(buffer2);
01560 
01561   //    char buffer2[128];
01562   //    snprintf(buffer2, 127, "%sgrestore%s", nl, nl);
01563 
01564   klfDbg("buffer is `"<<buffer<<"', length="<<buffer_len) ;
01565   klfDbg("rawepsdata has length="<<rawepsdata.size()) ;
01566 
01567   // and modify the raw EPS data, to replace "%%BoundingBox:" instruction by our stuff...
01568   QByteArray neweps;
01569   neweps = rawepsdata;
01570   neweps.replace(i, len, buffer);
01571 
01572   const char * endsetupstr = "%%EndSetup";
01573   int i2 = neweps.indexOf(endsetupstr);
01574   if (i2 < 0)
01575     i2 = i + buffer_len; // add our info after modified %%BoundingBox'es instructions if %%EndSetup not found
01576   else
01577     i2 +=  strlen(endsetupstr);
01578 
01579   neweps.replace(i2, 0, buffer2);
01580   
01581   klfDbg("neweps has now length="<<neweps.size());
01582   klfDebugf(("New eps bbox is [0 0 %.6g %.6g] with translate [%.6g %.6g] and scale %.6g.",
01583              dwi, dhi, offx, offy, vectorscale));
01584 
01585   *epsdatacorrected = neweps;
01586 }
01587 
01588 
01589 static void replace_svg_width_or_height(QByteArray *svgdata, const char * attreq, double val)
01590 {
01591   QByteArray & svgdataref = * svgdata;
01592 
01593   int i = svgdataref.indexOf(attreq);
01594   int j = i;
01595   while (j < (int)svgdataref.size() && (!isspace(svgdataref[j]) && svgdataref[j] != '>'))
01596     ++j;
01597 
01598   char buffer[1024];
01599   snprintf(buffer, sizeof(buffer)-1, "%s'%s'", attreq, klfFmtDoubleCC(val, 'f', 3));
01600 
01601   svgdata->replace(i, j-i, buffer);
01602 }
01603    
01604 
01605 
01606 
01607 
01608 KLF_EXPORT bool operator==(const KLFBackend::klfInput& a, const KLFBackend::klfInput& b)
01609 {
01610   return a.latex == b.latex &&
01611     a.mathmode == b.mathmode &&
01612     a.preamble == b.preamble &&
01613     fabs(a.fontsize - b.fontsize) < 0.001 &&
01614     a.fg_color == b.fg_color &&
01615     a.bg_color == b.bg_color &&
01616     a.dpi == b.dpi &&
01617     fabs(a.vectorscale - b.vectorscale) < 0.001 &&
01618     a.bypassTemplate == b.bypassTemplate &&
01619     a.userScript == b.userScript;
01620 }
01621 
01622 KLF_EXPORT bool operator==(const KLFBackend::klfSettings& a, const KLFBackend::klfSettings& b)
01623 {
01624   return
01625     a.tempdir == b.tempdir &&
01626     a.latexexec == b.latexexec &&
01627     a.dvipsexec == b.dvipsexec &&
01628     a.gsexec == b.gsexec &&
01629     a.epstopdfexec == b.epstopdfexec &&
01630     a.tborderoffset == b.tborderoffset &&
01631     a.rborderoffset == b.rborderoffset &&
01632     a.bborderoffset == b.bborderoffset &&
01633     a.lborderoffset == b.lborderoffset &&
01634     a.calcEpsBoundingBox == b.calcEpsBoundingBox &&
01635     a.outlineFonts == b.outlineFonts &&
01636     a.wantRaw == b.wantRaw &&
01637     a.wantPDF == b.wantPDF &&
01638     a.wantSVG == b.wantSVG &&
01639     a.execenv == b.execenv &&
01640     a.templateGenerator == b.templateGenerator ;
01641 }
01642 
01643 
01644 QStringList KLFBackend::availableSaveFormats(const klfOutput * output)
01645 {
01646   if (output != NULL)
01647     return availableSaveFormats(*output);
01648 
01649   QStringList fmts;
01650   fmts << "PNG" << "PS" << "EPS" << "DVI" << "PDF" << "SVG";
01651 
01652   QList<QByteArray> imgfmts = QImageWriter::supportedImageFormats();
01653   foreach (QByteArray f, imgfmts) {
01654     f = f.trimmed().toUpper();
01655     if (f == "JPG")
01656       f = "JPEG"; // only report "JPEG", not both "JPG" and "JPEG"
01657     if (fmts.contains(f))
01658       continue;
01659     fmts << QString::fromLatin1(f);
01660   }
01661   return fmts;
01662 }
01663 
01664 QStringList KLFBackend::availableSaveFormats(const klfOutput& klfoutput)
01665 {
01666   QStringList formats;
01667   // Most popular formats on top of list (pdf, png)
01668   if (!klfoutput.pdfdata.isEmpty())
01669     formats << "PDF";
01670   if (!klfoutput.pngdata.isEmpty())
01671     formats << "PNG";
01672   if (!klfoutput.svgdata.isEmpty())
01673     formats << "SVG";
01674   if (!klfoutput.epsdata.isEmpty())
01675     formats << "PS" << "EPS";
01676   if (!klfoutput.dvidata.isEmpty())
01677     formats << "DVI";
01678   // and, of course, all Qt-available image formats
01679   QList<QByteArray> imgfmts = QImageWriter::supportedImageFormats();
01680   foreach (QByteArray f, imgfmts) {
01681     f = f.trimmed().toUpper();
01682     if (f == "JPG")
01683       f = "JPEG"; // only report "JPEG", not both "JPG" and "JPEG"
01684     if (formats.contains(f))
01685       continue;
01686     formats << QString::fromLatin1(f);
01687   }
01688   return formats;
01689 }
01690 
01691 bool KLFBackend::saveOutputToDevice(const klfOutput& klfoutput, QIODevice *device,
01692                                     const QString& fmt, QString *errorStringPtr)
01693 {
01694   QString format = fmt.trimmed().toUpper();
01695 
01696   // now choose correct data source and write to fout
01697   if (format == "PNG") {
01698     device->write(klfoutput.pngdata);
01699   } else if (format == "EPS" || format == "PS") {
01700     device->write(klfoutput.epsdata);
01701   } else if (format == "DVI") {
01702     device->write(klfoutput.dvidata);
01703   } else if (format == "PDF") {
01704     if (klfoutput.pdfdata.isEmpty()) {
01705       QString error = QObject::tr("PDF format is not available!",
01706                                   "KLFBackend::saveOutputToFile");
01707       qWarning("%s", qPrintable(error));
01708       if (errorStringPtr != NULL)
01709         errorStringPtr->operator=(error);
01710       return false;
01711     }
01712     device->write(klfoutput.pdfdata);
01713   } else if (format == "SVG") {
01714     if (klfoutput.svgdata.isEmpty()) {
01715       QString error = QObject::tr("SVG format is not available!",
01716                                   "KLFBackend::saveOutputToFile");
01717       qWarning("%s", qPrintable(error));
01718       if (errorStringPtr != NULL)
01719         errorStringPtr->operator=(error);
01720       return false;
01721     }
01722     device->write(klfoutput.svgdata);
01723  } else {
01724     bool res = klfoutput.result.save(device, format.toLatin1());
01725     if ( ! res ) {
01726       QString errstr = QObject::tr("Unable to save image in format `%1'!",
01727                                    "KLFBackend::saveOutputToDevice").arg(format);
01728       qWarning("%s", qPrintable(errstr));
01729       if (errorStringPtr != NULL)
01730         *errorStringPtr = errstr;
01731       return false;
01732     }
01733   }
01734 
01735   return true;
01736 }
01737 
01738 bool KLFBackend::saveOutputToFile(const klfOutput& klfoutput, const QString& fileName,
01739                                   const QString& fmt, QString *errorStringPtr)
01740 {
01741   QString format = fmt;
01742   // determine format first
01743   if (format.isEmpty() && !fileName.isEmpty()) {
01744     QFileInfo fi(fileName);
01745     if ( ! fi.suffix().isEmpty() )
01746       format = fi.suffix();
01747   }
01748   if (format.isEmpty())
01749     format = QLatin1String("PNG");
01750   format = format.trimmed().toUpper();
01751   // got format. choose output now and prepare write
01752   QFile fout;
01753   if (fileName.isEmpty() || fileName == "-") {
01754     if ( ! fout.open(stdout, QIODevice::WriteOnly) ) {
01755       QString error = QObject::tr("Unable to open stdout for write! Error: %1",
01756                                   "KLFBackend::saveOutputToFile").arg(fout.error());
01757       qWarning("%s", qPrintable(error));
01758       if (errorStringPtr != NULL)
01759         *errorStringPtr = error;
01760       return false;
01761     }
01762   } else {
01763     fout.setFileName(fileName);
01764     if ( ! fout.open(QIODevice::WriteOnly) ) {
01765       QString error = QObject::tr("Unable to write to file `%1'! Error: %2",
01766                                   "KLFBackend::saveOutputToFile")
01767         .arg(fileName).arg(fout.error());
01768       qWarning("%s", qPrintable(error));
01769       if (errorStringPtr != NULL)
01770         *errorStringPtr = error;
01771       return false;
01772     }
01773   }
01774 
01775   return saveOutputToDevice(klfoutput, &fout, format, errorStringPtr);
01776 }
01777 
01778 
01779 bool KLFBackend::detectSettings(klfSettings *settings, const QString& extraPath, bool isMainThread)
01780 {
01781   KLF_DEBUG_TIME_BLOCK(KLF_FUNC_NAME) ;
01782 
01783   QStringList stdextrapaths;
01784   int k, j;
01785   for (k = 0; standard_extra_paths[k] != NULL; ++k) {
01786     stdextrapaths.append(standard_extra_paths[k]);
01787   }
01788   QString extra_paths = stdextrapaths.join(QString("")+KLF_PATH_SEP);
01789   if (!extraPath.isEmpty())
01790     extra_paths += KLF_PATH_SEP + extraPath;
01791 
01792   // temp dir
01793   settings->tempdir = QDir::fromNativeSeparators(QDir::tempPath());
01794 
01795   // sensible defaults
01796   settings->lborderoffset = 1;
01797   settings->tborderoffset = 1;
01798   settings->rborderoffset = 1;
01799   settings->bborderoffset = 1;
01800   
01801   settings->epstopdfexec = QString(); // obsolete, no longer used
01802 
01803   // you'll want PDF
01804   settings->wantPDF = true;
01805 
01806   settings->wantSVG = false; // will be set to TRUE once we verify 'gs' available devices information
01807 
01808   // find executables
01809   struct { QString * target_setting; QStringList prog_names; }  progs_to_find[] = {
01810     { & settings->latexexec, progLATEX },
01811     { & settings->dvipsexec, progDVIPS },
01812     { & settings->gsexec, progGS },
01813     //    { & settings->epstopdfexec, progEPSTOPDF },
01814     { NULL, QStringList() }
01815   };
01816   // replace @executable_path in extra_paths
01817   klfDbg(klfFmtCC("Our base extra paths are: %s", qPrintable(extra_paths))) ;
01818   QString ourextrapaths = extra_paths;
01819   ourextrapaths.replace("@executable_path", qApp->applicationDirPath());
01820   klfDbg(klfFmtCC("Our extra paths are: %s", qPrintable(ourextrapaths))) ;
01821   // and actually search for those executables
01822   for (k = 0; progs_to_find[k].target_setting != NULL; ++k) {
01823     klfDbg("Looking for "+progs_to_find[k].prog_names.join(" or ")) ;
01824     for (j = 0; j < (int)progs_to_find[k].prog_names.size(); ++j) {
01825       klfDbg("Testing `"+progs_to_find[k].prog_names[j]+"'") ;
01826       *progs_to_find[k].target_setting
01827         = klfSearchPath(progs_to_find[k].prog_names[j], ourextrapaths);
01828       if (!progs_to_find[k].target_setting->isEmpty()) {
01829         klfDbg("Found! at `"+ *progs_to_find[k].target_setting+"'") ;
01830         break; // found a program
01831       }
01832     }
01833   }
01834 
01835   bool r1 = detectOptionSettings(settings, isMainThread);
01836 
01837   bool result_failure =
01838     settings->tempdir.isEmpty() || settings->latexexec.isEmpty() || settings->dvipsexec.isEmpty() ||
01839     settings->gsexec.isEmpty() || !r1;
01840 
01841   return !result_failure;
01842 }
01843 
01844 KLF_EXPORT bool KLFBackend::detectOptionSettings(klfSettings * settings, bool isMainThread)
01845 {
01846   KLF_DEBUG_TIME_BLOCK(KLF_FUNC_NAME) ;
01847 
01848   bool r0 = klf_detect_execenv(settings);
01849   if (!r0) {
01850     return false;
01851   }
01852 
01853   settings->wantSVG = false;
01854 
01855   bool ok = true;
01856   if (settings->gsexec.length()) {
01857     initGsInfo(settings, isMainThread);
01858     if (!gsInfo.contains(settings->gsexec)) {
01859       klfWarning("Cannot get 'gs' devices information with "<<(settings->gsexec+" --version/--help"));
01860       ok = false;
01861     } else if (gsInfo[settings->gsexec].availdevices.contains("svg")) {
01862       settings->wantSVG = true;
01863     }
01864   }
01865 
01866   return ok;
01867 }
01868 
01869 
01870 KLF_EXPORT bool klf_detect_execenv(KLFBackend::klfSettings *settings)
01871 {
01872   KLF_DEBUG_TIME_BLOCK(KLF_FUNC_NAME) ;
01873 
01874   // detect mgs.exe as ghostscript and setup its environment properly
01875   QFileInfo gsfi(settings->gsexec);
01876   if (gsfi.fileName() == "mgs.exe") {
01877     QString mgsenv = QString("")
01878       + QDir::toNativeSeparators(gsfi.absolutePath()+"/../../ghostscript/base")
01879       + QString(KLF_PATH_SEP)
01880       + QDir::toNativeSeparators(gsfi.absolutePath()+"/../../fonts");
01881     settings->execenv = klfSetEnvironmentVariable(settings->execenv, "MIKTEX_GS_LIB", mgsenv);
01882     klfDbg("Adjusting environment for mgs.exe: `MIKTEX_GS_LIB="+mgsenv+"'") ;
01883   }
01884 
01885   return true;
01886 }
01887 
01888 
01889 
01890 
01891 // static 
01892 void initGsInfo(const KLFBackend::klfSettings *settings, bool isMainThread)
01893 {
01894   KLF_DEBUG_TIME_BLOCK(KLF_FUNC_NAME) ;
01895 
01896   if (gsInfo.contains(settings->gsexec)) // info already cached
01897     return;
01898 
01899   if (settings->gsexec.isEmpty()) {
01900     // no GS executable given
01901     return;
01902   }
01903 
01904   QString gsver;
01905   { // test 'gs' version, to see if we can provide SVG data
01906     KLFBackendFilterProgram p(QLatin1String("gs (test version)"), settings, isMainThread, settings->tempdir);
01907     //    p.resErrCodes[KLFFP_NOSTART] = ;
01908     //     p.resErrCodes[KLFFP_NOEXIT] = ;
01909     //     p.resErrCodes[KLFFP_NOSUCCESSEXIT] = ;
01910     //     p.resErrCodes[KLFFP_NODATA] = ;
01911     //     p.resErrCodes[KLFFP_DATAREADFAIL] = ;
01912     
01913     p.setExecEnviron(settings->execenv);
01914     
01915     // I think there is a problem with app events processing if this is run during application start-up
01916     p.setProcessAppEvents(false);
01917 
01918     p.setArgv(QStringList() << settings->gsexec << "--version");
01919     
01920     QByteArray ba_gsver;
01921     bool ok = p.run(QString(), &ba_gsver);
01922     if (ok) {
01923       gsver = QString::fromLatin1(ba_gsver);
01924       klfDbg("gs version text (untrimmed): "<<gsver) ;
01925 
01926       gsver = gsver.trimmed();
01927     }
01928   }
01929 
01930   QString gshelp;
01931   KLFStringSet availdevices;
01932   { // test 'gs' version, to see if we can provide SVG data
01933     KLFBackendFilterProgram p(QLatin1String("gs (query help)"), settings, isMainThread, settings->tempdir);
01934     //    p.resErrCodes[KLFFP_NOSTART] = ;
01935     //     p.resErrCodes[KLFFP_NOEXIT] = ;
01936     //     p.resErrCodes[KLFFP_NOSUCCESSEXIT] = ;
01937     //     p.resErrCodes[KLFFP_NODATA] = ;
01938     //     p.resErrCodes[KLFFP_DATAREADFAIL] = ;
01939     
01940     QStringList ee = settings->execenv;
01941     // make sure we have gs' output in english
01942     ee = klfSetEnvironmentVariable(ee, QLatin1String("LANG"), QLatin1String("en_US.UTF-8"));
01943     p.setExecEnviron(ee);
01944     
01945     // I think there is a problem with app events processing if this is run during application start-up
01946     p.setProcessAppEvents(false);
01947 
01948     p.setArgv(QStringList() << settings->gsexec << "--help");
01949     
01950     QByteArray ba_gshelp;
01951     bool ok = p.run(QString(), &ba_gshelp);
01952     if (ok) {
01953       gshelp = QString::fromLatin1(ba_gshelp);
01954 
01955       klfDbg("gs help text: "<<gshelp) ;
01956       // parse available devices
01957       const char * availdevstr = "Available devices:";
01958       int k = gshelp.indexOf(availdevstr, 0, Qt::CaseInsensitive) ;
01959       if (k == -1) {
01960         klfWarning("Unable to parse gs' available devices.") ;
01961       } else {
01962         k += strlen(availdevstr); // point to after 'available devices:' string
01963         // find end of available devices list, given by a line not starting with whitespace
01964         int kend = gshelp.indexOf(QRegExp("\\n\\S"), k);
01965         if (kend == -1)
01966           kend = gshelp.length();
01967         // now split this large string into the devices list
01968         QStringList devlist = gshelp.mid(k, kend-k).split(QRegExp("(\\s|[\r\n])+"), QString::SkipEmptyParts);
01969         availdevices = KLFStringSet::fromList(devlist);
01970         klfDbg("Detected devices: "<<availdevices) ;
01971       }
01972     }
01973   }
01974 
01975   int gsvermaj = -1;
01976   int gsvermin = -1;
01977   QRegExp rx_version("^(\\d+)\\.(\\d+)");
01978   int foundver = rx_version.indexIn(gsver);
01979   if (foundver >= 0) {
01980     gsvermaj = rx_version.cap(1).toInt();
01981     gsvermin = rx_version.cap(2).toInt();
01982   }
01983 
01984   klfDbg("GS Version: "<<gsver<<" = "<<gsvermaj<<"."<<gsvermin);
01985 
01986   GsInfo i;
01987   i.version = gsver;
01988   i.version_maj = gsvermaj;
01989   i.version_min = gsvermin;
01990   i.help = gshelp;
01991   i.availdevices = availdevices;
01992 
01993   gsInfo[settings->gsexec] = i;
01994 }
01995 
01996 
01997 
01998 
01999 
02000 KLF_EXPORT QStringList klfSettingsToEnvironmentForUserScript(const KLFBackend::klfSettings& settings)
02001 {
02002   QStringList env;
02003   env << "KLF_SETTINGS_BORDEROFFSET=" + klfFmt("%.3g,%.3g,%.3g,%.3g pt", settings.tborderoffset,
02004                                                settings.rborderoffset, settings.bborderoffset, settings.lborderoffset)
02005       << "KLF_SETTINGS_OUTLINEFONTS=" + QString::fromLatin1(settings.outlineFonts ? "1" : "0")
02006       << "KLF_SETTINGS_CALCEPSBOUNDINGBOX=" + QString::fromLatin1(settings.calcEpsBoundingBox ? "1" : "0")
02007       << "KLF_SETTINGS_WANT_RAW=" + QString::fromLatin1(settings.wantRaw ? "1" : "0")
02008       << "KLF_SETTINGS_WANT_PDF=" + QString::fromLatin1(settings.wantPDF ? "1" : "0")
02009       << "KLF_SETTINGS_WANT_SVG=" + QString::fromLatin1(settings.wantSVG ? "1" : "0");
02010   return env;
02011 }
02012 
02013 KLF_EXPORT QStringList klfInputToEnvironmentForUserScript(const KLFBackend::klfInput& in)
02014 {
02015   QStringList env;
02016   QString fgcol = QString("rgba(%1,%2,%3,%4)").arg(qRed(in.fg_color))
02017     .arg(qGreen(in.fg_color)).arg(qBlue(in.fg_color)).arg(qAlpha(in.fg_color));
02018   QString bgcol = QString("rgba(%1,%2,%3,%4)").arg(qRed(in.bg_color))
02019     .arg(qGreen(in.bg_color)).arg(qBlue(in.bg_color)).arg(qAlpha(in.bg_color));
02020   env << "KLF_INPUT_LATEX=" + in.latex
02021       << "KLF_INPUT_MATHMODE=" + in.mathmode
02022       << "KLF_INPUT_PREAMBLE=" + in.preamble
02023       << "KLF_INPUT_FONTSIZE=" + QString::number(in.fontsize)
02024       << "KLF_INPUT_FG_COLOR_WEB=" + QColor(in.fg_color).name()
02025       << "KLF_INPUT_FG_COLOR_RGBA=" + fgcol
02026       << "KLF_INPUT_BG_COLOR_TRANSPARENT=" + QString::fromLatin1(qAlpha(in.bg_color) > 50 ? "0" : "1")
02027       << "KLF_INPUT_BG_COLOR_WEB=" + QColor(in.bg_color).name()
02028       << "KLF_INPUT_BG_COLOR_RGBA=" + bgcol
02029       << "KLF_INPUT_DPI=" + QString::number(in.dpi)
02030       << "KLF_INPUT_VECTORSCALE=" + klfFmt("%.6g", in.vectorscale)
02031       << "KLF_INPUT_USERSCRIPT=" + in.userScript
02032       << "KLF_INPUT_BYPASS_TEMPLATE=" + QString::number(in.bypassTemplate)
02033     ;
02034   
02035   // and add custom user parameters
02036   QMap<QString,QString>::const_iterator cit;
02037   for (cit = in.userScriptParam.constBegin(); cit != in.userScriptParam.constEnd(); ++cit) {
02038     env << "KLF_ARG_"+cit.key() + "=" + cit.value();
02039   }
02040 
02041   return env;
02042 }

Generated by doxygen 1.7.6.1. The KLatexFormula website is hosted on sourceforge.net