[KLF Backend][KLF Tools][KLF Home]
KLatexFormula Project
klfbackend.cpp
1/***************************************************************************
2 * file klfbackend.cpp
3 * This file is part of the KLatexFormula Project.
4 * Copyright (C) 2011 by Philippe Faist
5 * philippe.faist at bluewin.ch
6 * *
7 * This program is free software; you can redistribute it and/or modify *
8 * it under the terms of the GNU General Public License as published by *
9 * the Free Software Foundation; either version 2 of the License, or *
10 * (at your option) any later version. *
11 * *
12 * This program is distributed in the hope that it will be useful, *
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
15 * GNU General Public License for more details. *
16 * *
17 * You should have received a copy of the GNU General Public License *
18 * along with this program; if not, write to the *
19 * Free Software Foundation, Inc., *
20 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
21 ***************************************************************************/
22/* $Id$ */
23
24#include <stdio.h>
25#include <stdlib.h>
26#include <ctype.h> // isspace()
27#include <sys/time.h>
28#include <math.h> // fabs()
29
30#include <QtGlobal>
31#include <QByteArray>
32#include <QSet>
33#include <QCoreApplication>
34#include <QRegExp>
35#include <QFile>
36#include <QDateTime>
37#include <QTextStream>
38#include <QBuffer>
39#include <QDir>
40#include <QColor>
41#include <QTextDocument>
42#include <QImageWriter>
43#include <QTextCodec>
44#include <QTemporaryDir>
45
46#include <klfutil.h>
47#include <klfsysinfo.h>
48#include <klfdatautil.h>
49
50#include "klfblockprocess.h"
51#include "klffilterprocess.h"
52#include "klfuserscript.h"
53#include "klfbackend.h"
54#include "klfbackend_p.h"
55
56
57
82// some standard guess settings for system configurations
83
84#ifdef KLF_EXTRA_SEARCH_PATHS
85# define EXTRA_PATHS_PRE KLF_EXTRA_SEARCH_PATHS ,
86//# define EXTRA_PATHS KLF_EXTRA_SEARCH_PATHS
87#else
88# define EXTRA_PATHS_PRE
89//# define EXTRA_PATHS
90#endif
91
92
93#if defined(Q_OS_WIN32) || defined(Q_OS_WIN64)
94QStringList progLATEX = QStringList() << "latex.exe";
95QStringList progDVIPS = QStringList() << "dvips.exe";
96QStringList progGS = QStringList() << "gswin32c.exe" << "gswin64c.exe" << "mgs.exe";
97//QStringList progEPSTOPDF = QStringList() << "epstopdf.exe";
98static const char * standard_extra_paths[] = {
99 EXTRA_PATHS_PRE
100 "C:\\Program Files*\\MiKTeX*\\miktex\\bin",
101 "C:\\texlive\\*\\bin\\win*",
102 "C:\\Program Files*\\gs*\\gs*\\bin",
103 NULL
104};
105#elif defined(KLF_WS_MAC)
106QStringList progLATEX = QStringList() << "latex";
107QStringList progDVIPS = QStringList() << "dvips";
108QStringList progGS = QStringList() << "gs";
109//QStringList progEPSTOPDF = QStringList() << "epstopdf";
110static const char * standard_extra_paths[] = {
111 EXTRA_PATHS_PRE
112 "/usr/texbin:/Library/TeX/texbin:/usr/local/bin:/opt/local/bin:/sw/bin:/sw/usr/bin",
113 NULL
114};
115#else
116QStringList progLATEX = QStringList() << "latex";
117QStringList progDVIPS = QStringList() << "dvips";
118QStringList progGS = QStringList() << "gs";
119//QStringList progEPSTOPDF = QStringList() << "epstopdf";
120static const char * standard_extra_paths[] = {
121 EXTRA_PATHS_PRE
122 NULL
123};
124#endif
125
126
127
128// ---------------------------------
129
130KLFAbstractLatexMetaInfo::KLFAbstractLatexMetaInfo()
131{
132}
133KLFAbstractLatexMetaInfo::~KLFAbstractLatexMetaInfo()
134{
135}
136
137void KLFAbstractLatexMetaInfo::saveMetaInfo(const KLFBackend::klfInput& in,
138 const KLFBackend::klfSettings& settings)
139{
140 static QString boolstr[2] = { QLatin1String("true"), QLatin1String("false") } ;
141
142 saveField("AppVersion", QString::fromLatin1("KLatexFormula " KLF_VERSION_STRING));
143 saveField("Application",
144 QObject::tr("Created with KLatexFormula version %1", "KLFBackend::saveOutputToFile")
145 .arg(KLF_VERSION_STRING));
146 saveField("Software", QString::fromLatin1("KLatexFormula " KLF_VERSION_STRING));
147 saveField("InputLatex", in.latex);
148 saveField("InputMathMode", in.mathmode);
149 saveField("InputPreamble", in.preamble);
150 saveField("InputFontSize", QString::number(in.fontsize, 'g', 2));
151 saveField("InputFgColor", QString("rgb(%1, %2, %3)").arg(qRed(in.fg_color))
152 .arg(qGreen(in.fg_color)).arg(qBlue(in.fg_color)));
153 saveField("InputBgColor", QString("rgba(%1, %2, %3, %4)").arg(qRed(in.bg_color))
154 .arg(qGreen(in.bg_color)).arg(qBlue(in.bg_color))
155 .arg(qAlpha(in.bg_color)));
156 saveField("InputDPI", QString::number(in.dpi));
157 saveField("InputVectorScale", QString::number(in.vectorscale, 'g', 4));
158 saveField("InputBypassTemplate", boolstr[(int)in.bypassTemplate]);
159 saveField("InputUserScript", QFileInfo(in.userScript).fileName());
160 QString usparams;
162 saveField("InputUserScriptParams", usparams);
163 saveField("SettingsTBorderOffset", QString::number(settings.tborderoffset));
164 saveField("SettingsRBorderOffset", QString::number(settings.rborderoffset));
165 saveField("SettingsBBorderOffset", QString::number(settings.bborderoffset));
166 saveField("SettingsLBorderOffset", QString::number(settings.lborderoffset));
167 saveField("SettingsOutlineFonts", boolstr[(int)settings.outlineFonts]);
168 saveField("SettingsCalcEpsBoundingBox", boolstr[(int)settings.calcEpsBoundingBox]);
169 saveField("SettingsWantRaw", boolstr[(int)settings.wantRaw]);
170 saveField("SettingsWantPDF", boolstr[(int)settings.wantPDF]);
171 saveField("SettingsWantSVG", boolstr[(int)settings.wantSVG]);
172
173 klfDbg("saved meta-info.") ;
174}
175
176
177KLFImageLatexMetaInfo::KLFImageLatexMetaInfo(QImage *imgwrite) : _w(imgwrite) { }
178
179void KLFImageLatexMetaInfo::saveField(const QString& k, const QString& v)
180{
181 // QImageWriter::setText() uses QString::simplified() and does not save whitespace properly :(
182 // so encode text in some appropriate way.
183 _w->setText(k, klfDataToEscaped(v.toUtf8(), '%'));
184}
185QString KLFImageLatexMetaInfo::loadField(const QString &k) {
186 return QString::fromUtf8(klfEscapedToData(_w->text(k).toLatin1(), '%'));
187}
188
189
190KLF_EXPORT QByteArray klf_escape_ps_string(const QString& v)
191{
192 // write escape codes
193 int i;
194 // if v is just ascii, no need to encode it in unicode
195 bool isascii = true;
196 for (i = 0; i < v.length(); ++i) {
197 if (v[i] < 0 || v[i] > 126) {
198 isascii = false;
199 break;
200 }
201 }
202 QByteArray vdata;
203 if (isascii) {
204 vdata = v.toLatin1();
205
206 QByteArray escaped;
207 for (i = 0; i < vdata.size(); ++i) {
208 char c = vdata[i];
209 klfDbg("Char: "<<c);
210 if (QChar(vdata[i]).isLetterOrNumber() || c == ' ' || c == '.' || c == ',' || c == '/')
211 escaped += c;
212 else if (c == '\n')
213 escaped += "\\n";
214 else if (c == '\r')
215 escaped += "\\r";
216 else if (c == '\t')
217 escaped += "\\t";
218 else if (c == '\\')
219 escaped += "\\\\";
220 else if (c == '(')
221 escaped += "\\(";
222 else if (c == ')')
223 escaped += "\\)";
224 else {
225 klfDbg("escaping char: (int)c="<<(int)c<<" (uint)c="<<uint(c)<<", octal="<<klfFmtCC("%03o", (uint)c));
226 escaped += QString("\\%1").arg((unsigned int)(unsigned char)c, 3, 8, QChar('0')).toLatin1();
227 }
228 }
229
230 return "("+escaped+")";
231 }
232
233 // otherwise, do unicode encoding
234
235 QTextCodec *codec = QTextCodec::codecForName("UTF-16BE");
236 vdata = codec->fromUnicode(v);
237 klfDbg("vdata is "<<klfDataToEscaped(vdata));
238
239 QByteArray hex;
240 for (i = 0; i < (vdata.size()-1); i += 2) {
241 hex += klfFmt("%02x%02x ", (unsigned int)(unsigned char)vdata[i], (unsigned int)(unsigned char)vdata[i+1]);
242 }
243 return "<" + hex + ">";
244}
245
246
247
248KLFPdfmarksWriteLatexMetaInfo::KLFPdfmarksWriteLatexMetaInfo(QByteArray * string)
249 : _s(string)
250{
251 // See the following for more info:
252 // http://stackoverflow.com/questions/3010015/pdfmark-for-docinfo-metadata-in-pdf-is-not-accepting-accented-characters-in-keyw
253 // http://www.justskins.com/forums/adding-metadata-to-pdf-68647.html
254
255 _s->append( // ensure pdfmark symbol defined in postscript
256 "/pdfmark where { pop } { /globaldict where { pop globaldict } { userdict } ifelse "
257 "/pdfmark /cleartomark load put } ifelse\n"
258 // now the proper PDFmarks DOCINFO dictionary
259 "[ "
260 );
261}
262
264{
265 KLF_ASSERT_CONDITION(false, "N/A.", return QString(); ) ;
266}
267void KLFPdfmarksWriteLatexMetaInfo::saveField(const QString& k, const QString& v)
268{
269 savePDFField("KLF"+k, v);
270}
271void KLFPdfmarksWriteLatexMetaInfo::finish()
272{
273 _s->append(" /DOCINFO pdfmark\n");
274}
276{
277 QByteArray datavalue = klf_escape_ps_string(v);
278
279 _s->append( " /"+k+" " + datavalue + "\n");
280}
281
282
283
284
285
286
287// ---------------------------------
288
289
290static QMutex klf_mutex;
291
292struct GsInfo
293{
294 GsInfo() { }
295
296 QString version;
297 int version_maj;
298 int version_min;
299 QString help;
300 QSet<QString> availdevices;
301};
302
303// cache gs version/help/etc. information (for each gs executable, in case there are several)
305
306static void initGsInfo(const KLFBackend::klfSettings *settings, bool isMainThread);
307
308
309
310
311
312
313
314// ---------------------------------
315
316
317KLFBackend::TemplateGenerator::TemplateGenerator()
318{
319}
320KLFBackend::TemplateGenerator::~TemplateGenerator()
321{
322}
323
324KLFBackend::DefaultTemplateGenerator::DefaultTemplateGenerator()
325{
326}
327KLFBackend::DefaultTemplateGenerator::~DefaultTemplateGenerator()
328{
329}
330
332 const klfSettings& /*settings*/)
333{
335 QString s;
336
337 latexin = in.mathmode;
338 latexin.replace("...", in.latex);
339
341 s += "\\documentclass{article}\n"
342 "\\usepackage[dvips]{color}\n";
343 s += in.preamble;
344 s += "\n"
345 "\\begin{document}\n"
346 "\\thispagestyle{empty}\n";
347 if (in.fontsize > 0) {
348 s += QString("\\fontsize{%1}{%2}\\selectfont\n").arg(in.fontsize, 0, 'f', 2).arg(in.fontsize*1.2, 0, 'f', 2);
349 }
350 s += QString("\\definecolor{klffgcolor}{rgb}{%1,%2,%3}\n").arg(qRed(in.fg_color)/255.0)
351 .arg(qGreen(in.fg_color)/255.0).arg(qBlue(in.fg_color)/255.0);
352 s += QString("\\definecolor{klfbgcolor}{rgb}{%1,%2,%3}\n").arg(qRed(in.bg_color)/255.0)
353 .arg(qGreen(in.bg_color)/255.0).arg(qBlue(in.bg_color)/255.0);
354 if (qAlpha(in.bg_color)>0)
355 s += "\\pagecolor{klfbgcolor}\n";
356 s += "{\\color{klffgcolor} ";
357 s += latexin;
358 s += "%\n"
359 "}\n"
360 "\\end{document}\n";
361
362 return s;
363}
364
365
366
367
368// ---------------------------------
369
370KLFBackend::KLFBackend()
371{
372}
373
374
375
376
377#define D_RX "([0-9eE.-]+)"
378
379// A Bounding Box
380struct klfbbox {
381 double x1, x2, y1, y2;
382};
383
384
385
386static bool calculate_gs_eps_bbox(const QByteArray& epsdata, const QString& epsFile, klfbbox *bbox,
388 bool isMainThread);
389static bool read_eps_bbox(const QByteArray& epsdata, klfbbox *bbox, KLFBackend::klfOutput * resError);
390static void correct_eps_bbox(const QByteArray& epsdata,
391 const klfbbox& bbox_corrected, const klfbbox& bbox_orig,
392 double vectorscale, QRgb bgcolor, QByteArray * epsdatacorrected);
393
394static void replace_svg_width_or_height(QByteArray *svgdata, const char * attr, double val);
395
396
397static inline bool has_userscript_output(const QSet<QString>& fmts, const QString& format)
398{
399 return fmts.contains(format);
400 // if (!fmts.contains(format))
401 // return false;
402 // return fn.isEmpty() ? true : QFile::exists(fn);
403}
404
405
406
408
409KLF_EXPORT KLFStringSet klfbackend_fmts =
411 /* */ << "latex" << "dvi" << "eps-raw" << "eps-bbox" << "eps-processed"
412/* */ << "png" << "pdf" << "svg-gs" << "svg" ;
413
414
415KLF_EXPORT KLFStringSet klfbackend_dependencies(const QString& fmt, bool recursive = false)
416{
418
419 if (fn_lock.contains(fmt)) {
420 klfWarning("Dependency loop detected for format "<<fmt) ;
421 return KLFStringSet();
422 }
423 fn_lock << fmt;
424
425 KLFStringSet s;
426 if (fmt == QLatin1String("tex") || fmt == QLatin1String("latex")) {
427 // no dependency
428 } else if (fmt == QLatin1String("dvi")) {
429 s << "latex";
430 } else if (fmt == QLatin1String("eps-raw")) {
431 s << "dvi";
432 } else if (fmt == QLatin1String("eps-bbox")) {
433 s << "eps-raw";
434 } else if (fmt == QLatin1String("eps-processed")) {
435 s << "eps-bbox";
436 } else if (fmt == QLatin1String("png")) {
437 s << "eps-processed";
438 } else if (fmt == QLatin1String("pdf")) {
439 s << "eps-processed";
440 } else if (fmt == QLatin1String("svg-gs")) {
441 s << "eps-processed";
442 } else if (fmt == QLatin1String("svg")) {
443 s << "svg-gs";
444 } else {
445 klfWarning("Unknown format : "<<fmt) ;
446 }
447 if (!recursive) {
449 return s;
450 }
451 // explore dependencies recursively
453 foreach (QString str, basedeps) {
454 KLFStringSet subdeps = klfbackend_dependencies(str, true);
455 foreach (QString subdep, subdeps) {
456 s << subdep;
457 }
458 }
459
461 return s;
462}
463
464static inline bool assert_have_formats_for(const KLFStringSet& outputs, const KLFStringSet& skipfmts,
465 const QString& forwhat)
466{
467 KLFStringSet fmtlist = klfbackend_dependencies(forwhat);
468 foreach (QString s, fmtlist) {
469 if (skipfmts.contains(s) && !outputs.contains(s)) {
470 klfWarning("User Script Skipped format "<<s<<" which is necessary for "<<forwhat) ;
471 return false;
472 }
473 }
474 return true;
475}
476
477#define ASSERT_HAVE_FORMATS_FOR(forwhat) \
478 { if (!assert_have_formats_for(us_outputs, us_skipfmts, forwhat)) { \
479 res.status = KLFERR_USERSCRIPT_BADSKIPFORMATS; \
480 res.errorstr = QObject::tr("User Script broke dependencies in skip-formats list", "KLFBackend"); \
481 return res; \
482 } \
483 }
484
485
486
487
489 bool isMainThread)
490{
491 // ALLOW ONLY ONE RUNNING getLatexFormula() AT A TIME
492 QMutexLocker mutexlocker(&klf_mutex);
493
495
496 klfSettings settings;
497 settings = usersettings;
498
499 klfInput in;
500 in = input;
501
502 bool ok;
503
504 klfDbg("called. latex="<<in.latex);
505
506 { // get full, expanded exec environment
508 klfDbg("current environment is "<<curenv) ;
509 settings.execenv = klfMergeEnvironment(curenv, settings.execenv,
510 QStringList() << "PATH" << "TEXINPUTS" << "BIBINPUTS",
512 }
513
514 klfDbg("execution environment for sub-processes is "<<settings.execenv) ;
515
516
517 klfOutput res;
519 res.errorstr = QString();
520 res.result = QImage();
521 res.pngdata_raw = QByteArray();
522 res.pngdata = QByteArray();
523 res.dvidata = QByteArray();
524 res.epsdata_raw = QByteArray();
525 res.epsdata = QByteArray();
526 res.pdfdata = QByteArray();
527 res.svgdata = QByteArray();
528 res.input = in;
529 res.settings = settings;
530
531
532 // read GS version, will need later
533 initGsInfo(&settings, isMainThread);
534 if (!gsInfo.contains(settings.gsexec)) {
536 res.errorstr = QObject::tr("Can't query version of ghostscript located at `%1'.", "KLFBackend")
537 .arg(settings.gsexec);
538 return res;
539 }
540
541 const GsInfo thisGsInfo = gsInfo.value(settings.gsexec);
542
543 klfDebugf(("%s: queried ghostscript version: %s", KLF_FUNC_NAME, qPrintable(thisGsInfo.version))) ;
544
545 // force some rules on settings
546
547 // if calcEpsBoundingBox is being used, we need to add bg color at "correcting bbox time"
549 klfDebugf(("%s: settings.calcEpsBoundingBox=%d, in.bg_color=[RGBA %d,%d,%d,%d]", KLF_FUNC_NAME,
550 settings.calcEpsBoundingBox, qRed(in.bg_color), qGreen(in.bg_color), qBlue(in.bg_color),
551 qAlpha(in.bg_color)));
552
553 if (settings.calcEpsBoundingBox &&
554 qAlpha(in.bg_color) != 0 && (in.bg_color & qRgb(255,255,255)) != qRgb(255,255,255)) {
556 in.bg_color = qRgba(0,0,0,0);
557 }
558
559
560 // PROCEDURE (V3.3)
561 //
562 // EACH STEP MIGHT BE DONE BY A USER SCRIPT INSTEAD IF THAT IS REQUESTED.
563 //
564 // - generate LaTeX file
565 //
566 // - latex --> get DVI file
567 //
568 // - dvips -E file.dvi -o file.eps --> get (first) EPS file
569 //
570 // - gs -dNOPAUSE -dSAFER -sDEVICE=bbox -q -dBATCH file.eps --> calculate correct bbox for EPS file
571 //
572 // will output something like
573 // %%BoundingBox: int(X1) int(Y1) int(X2) int(Y2)
574 // %%HiResBoundingBox: X1 Y1 X2 Y2
575 //
576 // - read file.eps, modify e.g. as file-bbox.eps: replace
577 // %%BoundingBox ***
578 // by
579 // %%HiResBoundingBox: 0 0 (X2-X1) (Y2-Y1)
580 // -X1 -Y1 translate
581 // while of course taking into account manual corrections given by [lrtb]borderoffset settings/overrides
582 //
583 // EITHER (gs >= 9.01 && !outlinefonts)
584 // ### PhF: update this doc!! it's wrong!!
585 // - gs -dNOPAUSE -dSAFER -dSetPageSize -sDEVICE=ps2write -dEPSCrop -sOutputFile=file-corrected.(e)ps
586 // -q -dBATCH file-bbox.eps --> generate (E)PS file w/ correct page size
587 // OR
588 // - gs -dNOCACHE -dNOPAUSE -dSAFER -sDEVICE=pswrite -dEPSCrop -sOutputFile=file-corrected.eps -q -dBATCH
589 // file-bbox.eps --> generate post-processed (E)PS file
590 //
591 // - gs -dNOPAUSE -dSAFER -sDEVICE=pdfwrite -sOutputFile=file.pdf -q -dBATCH file-corrected.eps
592 // with added pdfmarks..
593 //
594 // - if (reports-has-device(svg)) {
595 //
596 // - gs -dNOPAUSE -dSAFER -sDEVICE=svg -r72x72 -sOutputFile=file.svg -q -dBATCH file-corrected.eps
597 //
598 // - modify SVG file to replace width='WWpt' height='HHpt' by
599 // width='(X2-X1)px' height='(Y2-Y1)px'
600 // with data given by gs before, with full precision
601 //
602 // - }
603
605 ver.replace(".", "x"); // make friendly names with chars in [a-zA-Z0-9]
606 // the base name for all our temp files
607 // QString tempfname = settings.tempdir + "/klftmp" + ver + "T" + QDateTime::currentDateTime().toString("hhmmss")
608 // + "p"+ QString("%1").arg(QCoreApplication::applicationPid(), 0, 26);
609 QString temptemplate = settings.tempdir + "/klftmp"+ver+"-XXXXXX";
610
611 // create the temporary directory
613
614 if (!tempdir.isValid()) {
615 // failed to create temporary directory
616 res.errorstr = QObject::tr("Failed to create temporary directory inside `%1'",
617 "KLFBackend").arg(settings.tempdir);
619 return res;
620 }
621
622 QString tempfname = tempdir.path() + "/klftemp";
623
624 klfDbg("Temp location base name is "<<tempfname) ;
625
626 QString fnTex = tempfname + ".tex";
627 QString fnDvi = tempfname + ".dvi";
628 QString fnRawEps = tempfname + ".eps";
629 QString fnBBoxEps = tempfname + "-bbox.eps";
630 QString fnProcessedEps = tempfname + "-processed.eps";
631 QString fnRawPng = tempfname + "-raw.png";
632 QString fnPdfMarks = tempfname + ".pdfmarks";
633 QString fnPdf = tempfname + ".pdf";
634 QString fnGsSvg = tempfname + "-gs.svg";
635 // some user scripts may provide directly .svg (even though the default process chain
636 // processes raw svg in memory, not generating final svg file)
637 QString fnSvg = tempfname + ".svg";
638
639 // we need non-outlinedfont EPS data anyway.
643
644
645 QString latexsimplified = in.latex.trimmed();
646 if (latexsimplified.isEmpty()) {
647 res.errorstr = QObject::tr("You must specify a LaTeX formula!", "KLFBackend");
649 return res;
650 }
651
652 if (!in.bypassTemplate) {
653 if (in.mathmode.contains("...") == 0) {
655 res.errorstr = QObject::tr("The math mode string doesn't contain '...'!", "KLFBackend");
656 return res;
657 }
658 }
659
660 // prepare LaTeX file
661 {
662 QFile file(fnTex);
663 bool r = file.open(QIODevice::WriteOnly);
664 if ( ! r ) {
666 res.errorstr = QObject::tr("Can't open file for writing: '%1'!", "KLFBackend").arg(fnTex);
667 return res;
668 }
669 QTextStream stream(&file);
670 if (!in.bypassTemplate) {
673 if (settings.templateGenerator != NULL) {
674 klfDbg("using custom template generator") ;
675 t = settings.templateGenerator;
676 KLF_ASSERT_NOT_NULL(t, "Template Generator is NULL! Using default!", t = &deft; ) ;
677 } else {
678 t = &deft;
679 }
680 stream << t->generateTemplate(in, settings);
681 } else {
682 stream << in.latex;
683 }
684 }
685
689
690 if (!in.userScript.isEmpty()) {
691 // user has provided us a wrapper script. Query it and use it
692
694
695 if (scriptinfo.scriptInfoError() != KLFERR_NOERROR) {
696 res.status = scriptinfo.scriptInfoError();
697 res.errorstr = scriptinfo.scriptInfoErrorString();
698 return res;
699 }
700
701 if ( (!scriptinfo.klfMinVersion().isEmpty()
702 && klfVersionCompare(scriptinfo.klfMinVersion(), KLF_VERSION_STRING) > 0) ||
703 (!scriptinfo.klfMaxVersion().isEmpty()
704 && klfVersionCompare(scriptinfo.klfMaxVersion(), KLF_VERSION_STRING) < 0) ) {
705 res.status = KLFERR_USERSCRIPT_BADKLFVERSION;
706 res.errorstr = QObject::tr("User Script `%1' is not compatible with current version of KLatexFormula.",
707 "KLFBackend").arg(scriptinfo.name());
708 return res;
709 }
710
711 if (scriptinfo.category() != QLatin1String("klf-backend-engine")) {
712 res.status = KLFERR_USERSCRIPT_BADCATEGORY;
713 res.errorstr = QObject::tr("User Script `%1' is not usable as backend latex engine!",
714 "KLFBackend").arg(scriptinfo.name());
715 return res;
716 }
717
718 // and run the script with the latex input
720 addenv
721 // program executables
722 << "KLF_TEMPDIR=" + settings.tempdir
723 << "KLF_LATEX=" + settings.latexexec
724 << "KLF_DVIPS=" + settings.dvipsexec
725 << "KLF_GS=" + settings.gsexec
726 << "KLF_GS_VERSION=" + thisGsInfo.version
727 << "KLF_GS_DEVICES=" + QStringList(thisGsInfo.availdevices.toList()).join(",")
728 // input
729 << klfInputToEnvironmentForUserScript(in)
730 // more advanced settings
731 << klfSettingsToEnvironmentForUserScript(settings)
732 // file names (all formed with same basename...) to access by the script
733 << "KLF_TEMPFNAME=" + tempfname // the base name for all our temp files
734 << "KLF_FN_TEX=" + fnTex
735 << "KLF_FN_LATEX=" + fnTex
736 << "KLF_FN_DVI=" + fnDvi
737 << "KLF_FN_EPS_RAW=" + fnRawEps
738 << "KLF_FN_EPS_BBOX=" + fnBBoxEps
739 << "KLF_FN_EPS_PROCESSED=" + fnProcessedEps
740 << "KLF_FN_PNG=" + fnRawPng
741 << "KLF_FN_PDFMARKS=" + fnPdfMarks
742 << "KLF_FN_PDF=" + fnPdf
743 << "KLF_FN_SVG_GS=" + fnGsSvg
744 << "KLF_FN_SVG=" + fnSvg
745 ;
746
747 { // now run the script
748 KLFUserScriptFilterProcess p(scriptinfo.userScriptPath(), &settings);
749
750 p.addExecEnviron(addenv);
751
752 p.setProcessAppEvents(false);
753
756 p.collectStderrTo(&stderrdata);
757 p.collectStdoutTo(&stdoutdata);
758
760
762 QStringList outfmts = scriptinfo.spitsOut();
763 foreach (QString fmt, outfmts) {
764 us_outputs << fmt;
765 if (fmt == QLatin1String("latex")) {
766 // user script overwrote the tex/latex file, don't collect the tex file data as it is not
767 // needed (not considered as useful output!). This new tex file will be seen and accessed
768 // by 'latex' in the next process block after userscript if needed.
769 } else if (fmt == QLatin1String("dvi")) {
770 outdata[fnDvi] = &res.dvidata;
771 } else if (fmt == QLatin1String("eps-raw")) {
772 // if we don't need this, it will be removed below from the list
774 } else if (fmt == QLatin1String("eps-bbox")) {
776 } else if (fmt == QLatin1String("eps-processed")) {
778 } else if (fmt == QLatin1String("png")) {
779 if (settings.wantRaw) {
781 }
782 } else if (fmt == QLatin1String("pdf")) {
783 if (settings.wantPDF) {
784 outdata[fnPdf] = &res.pdfdata;
785 }
786 } else if (fmt == QLatin1String("svg-gs")) {
787 // ignore this data, not returned in klfOutput but the created file is used to generate
788 // the processed SVG
790 } else if (fmt == QLatin1String("svg")) {
791 if (settings.wantSVG) {
792 outdata[fnSvg] = &res.svgdata;
793 }
794 } else {
795 klfWarning("Can't handle output format from user script: "<<fmt) ;
796 }
797 }
798 if (us_outputs.isEmpty()) {
799 us_outputs << "dvi"; // by default, the script is assumed to provide DVI.
800 }
801 if (us_outputs.contains("eps-bbox") && !settings.wantRaw) {
802 // don't need to fetch initial raw eps data
803 if (outdata.contains("eps-raw"))
805 }
806 if (us_outputs.contains("eps-processed") && !settings.wantRaw) {
807 // don't need to fetch bbox raw eps data
808 if (outdata.contains("eps-bbox"))
810 }
811
812 QStringList skipfmts = scriptinfo.skipFormats();
813 bool invert = false;
814 int tempi;
815 if ((tempi = skipfmts.indexOf("ALL_EXCEPT")) >= 0) {
816 invert = true;
817 skipfmts.removeAt(tempi);
818 foreach (QString f, klfbackend_fmts) { us_skipfmts << f; }
819 }
820 foreach (QString fmt, skipfmts) {
821 if (!klfbackend_fmts.contains(fmt)) {
822 klfWarning("User Script Info: Unknown format to skip: "<<fmt) ;
823 }
824 if (!invert) {
825 us_skipfmts << fmt;
826 } else {
828 }
829 }
831 foreach (QString fmt, outfmts) {
833 klfWarning("User Script Info: format " << fmt << " provided by script is also marked "
834 "as to be skipped!") ;
835 }
836 }
837
838 if ((us_outputs.contains("eps-processed") || our_skipfmts.contains("eps-processed")) && !settings.wantRaw) {
839 our_skipfmts << "eps-bbox";
840 }
841 if ((us_outputs.contains("eps-bbox") || our_skipfmts.contains("eps-bbox")) && !settings.wantRaw) {
842 our_skipfmts << "eps-raw";
843 }
844 if (us_outputs.contains("svg") || our_skipfmts.contains("svg")) {
845 our_skipfmts << "svg-gs"; // don't need svg-gs if we're not generating svg
846 }
847
848 klfDbg("us_skipfmts = " << us_skipfmts) ;
849
850 ok = p.run(outdata);
851
852 if (!ok) {
853 res.errorstr = p.resultErrorString();
854 switch (p.resultStatus()) {
855 case KLFFP_NOSTART: res.status = KLFERR_USERSCRIPT_NORUN; break;
856 case KLFFP_NOEXIT: res.status = KLFERR_USERSCRIPT_NONORMALEXIT; break;
857 case KLFFP_NOSUCCESSEXIT: res.status = KLFERR_PROGERR_USERSCRIPT; break;
858 case KLFFP_NODATA: res.status = KLFERR_USERSCRIPT_NOOUTPUT; break;
859 case KLFFP_DATAREADFAIL: res.status = KLFERR_USERSCRIPT_OUTPUTREADFAIL; break;
860 default:
861 res.status = p.resultStatus();
862 }
863 return res;
864 }
865
866 // make sure all promised files have appeared
867 foreach (QString fmt, outfmts) {
869 if (fmt == QLatin1String("latex")) {
871 } else if (fmt == QLatin1String("dvi")) {
873 } else if (fmt == QLatin1String("eps-raw")) {
875 } else if (fmt == QLatin1String("eps-bbox")) {
877 } else if (fmt == QLatin1String("eps-processed")) {
879 } else if (fmt == QLatin1String("png")) {
881 } else if (fmt == QLatin1String("pdf")) {
883 } else if (fmt == QLatin1String("svg-gs")) {
885 } else if (fmt == QLatin1String("svg")) {
887 } else {
888 klfWarning("Unknown format: " << fmt) ;
889 continue;
890 }
891 if (!QFile::exists(corrfname)) {
892 klfWarning("Promised format " << fmt << " did not appear after calling user script.") ;
893 }
894 }
895 }
896 }
897
898 klfDbg("our_skipfmts = " << our_skipfmts) ;
899
900
901 if (!has_userscript_output(us_outputs, "dvi") && !our_skipfmts.contains("dvi")) {
902 // execute latex
903 klfDbg("preparing to launch latex.") ;
904
905 if (settings.latexexec.isEmpty()) {
907 res.errorstr = QObject::tr("No latex executable given!\n", "KLFBackend");
908 return res;
909 }
910
911 KLFBackendFilterProgram p(QLatin1String("LaTeX"), &settings, isMainThread, tempdir.path());
912 p.resErrCodes[KLFFP_NOSTART] = KLFERR_LATEX_NORUN;
913 p.resErrCodes[KLFFP_NOEXIT] = KLFERR_LATEX_NONORMALEXIT;
914 p.resErrCodes[KLFFP_NOSUCCESSEXIT] = KLFERR_PROGERR_LATEX;
915 p.resErrCodes[KLFFP_NODATA] = KLFERR_LATEX_NOOUTPUT;
916 p.resErrCodes[KLFFP_DATAREADFAIL] = KLFERR_LATEX_OUTPUTREADFAIL;
917
918 p.setArgv(QStringList() << settings.latexexec << QDir::toNativeSeparators(fnTex));
919
921
922 ok = p.run(userinputforerrors, fnDvi, &res.dvidata);
923 if (!ok) {
924 p.errorToOutput(&res);
925 return res;
926 }
927 }
928
929 if (!has_userscript_output(us_outputs, "eps-raw") && !our_skipfmts.contains("eps-raw")) {
930
931 ASSERT_HAVE_FORMATS_FOR("eps-raw") ;
932
933 if (settings.dvipsexec.isEmpty()) {
935 res.errorstr = QObject::tr("No dvips executable given!\n", "KLFBackend");
936 return res;
937 }
938
939 // execute dvips -E
940 KLFBackendFilterProgram p(QLatin1String("dvips"), &settings, isMainThread, tempdir.path());
941 p.resErrCodes[KLFFP_NOSTART] = KLFERR_DVIPS_NORUN;
942 p.resErrCodes[KLFFP_NOEXIT] = KLFERR_DVIPS_NONORMALEXIT;
943 p.resErrCodes[KLFFP_NOSUCCESSEXIT] = KLFERR_PROGERR_DVIPS;
944 p.resErrCodes[KLFFP_NODATA] = KLFERR_DVIPS_NOOUTPUT;
945 p.resErrCodes[KLFFP_DATAREADFAIL] = KLFERR_DVIPS_OUTPUTREADFAIL;
946
947 QFileInfo dvipsinf(settings.dvipsexec);
948 if (!dvipsinf.filePath().isEmpty()) {
949 // add the explicit dvips path to the PATH environment, in case dvips needs to
950 // execute helpers such as mktexpk
951 p.addExecEnviron(QStringList() << (
952 QLatin1String("PATH=") + dvipsinf.absoluteFilePath() + QLatin1String(":") +
954 )) ;
955 }
956
957 p.setArgv(QStringList() << settings.dvipsexec << "-E" << QDir::toNativeSeparators(fnDvi)
959
960 ok = p.run(fnRawEps, &rawepsdata);
961
962 if (!ok) {
963 p.errorToOutput(&res);
964 return res;
965 }
966 klfDbg("read raw EPS; rawepsdata/length="<<rawepsdata.size()) ;
967 } // end of 'dvips' block
968
969 // the settings requires, save the intermediary data in to result output
970 if (settings.wantRaw)
972
973 // This now also returned in 'res', directly saved there.
974 // // width and height of the (final) EPS bbox in postscript points
975 // double width_pt = 0, height_pt = 0;
976
977 if (!has_userscript_output(us_outputs, "eps-bbox") && !our_skipfmts.contains("eps-bbox")) {
978 // find correct bounding box of EPS file, and modify EPS data manually to add boffset and
979 // translate to (0,0,width,height)
980
981 ASSERT_HAVE_FORMATS_FOR("eps-bbox") ;
982
983 klfbbox bbox, bbox_corrected;
984
985 if (settings.calcEpsBoundingBox) {
986 bool ok = calculate_gs_eps_bbox(QByteArray(), fnRawEps, &bbox, &res, settings, isMainThread);
987 if (!ok)
988 return res; // res was set by the function
989 } else {
990 bool ok = read_eps_bbox(rawepsdata, &bbox, &res);
991 if (!ok)
992 return res; // res was set by the function
993 }
994
995 bbox.x1 -= settings.lborderoffset;
996 bbox.y1 -= settings.bborderoffset;
997 bbox.x2 += settings.rborderoffset;
998 bbox.y2 += settings.tborderoffset;
999
1000 res.width_pt = bbox.x2 - bbox.x1;
1001 res.height_pt = bbox.y2 - bbox.y1;
1002
1003 // now correct the bbox to (0,0,width,height)
1004
1005 bbox_corrected.x1 = 0;
1006 bbox_corrected.y1 = 0;
1007 bbox_corrected.x2 = res.width_pt;
1008 bbox_corrected.y2 = res.height_pt;
1009
1010 // and generate corrected raw EPS
1011
1012 correct_eps_bbox(rawepsdata, bbox_corrected, bbox, in.vectorscale,
1014
1015 klfDbg("corrected bbox to "<<bbox.x1<<","<<bbox.y1<<","<<bbox.x2<<","<<bbox.y2);
1016 } else if (!our_skipfmts.contains("eps-bbox")) {
1017 // userscript generated bbox-corrected EPS for us, but we still
1018 // need to set width_pt and height_pt appropriately.
1019
1020 klfbbox bb;
1021
1022 // read from fnRawEps, fnBBoxEps or fnProcessedEps ?
1023 QString fn;
1024 //QByteArray *dat;
1025 if (us_outputs.contains("eps-processed")) {
1027 //dat = & res.epsdata;
1028 } else {
1029 fn = fnBBoxEps;
1030 //dat = & bboxepsdata;
1031 }
1032
1033 if (settings.calcEpsBoundingBox) {
1034 bool ok = calculate_gs_eps_bbox(QByteArray(), fn, &bb, &res, settings, isMainThread);
1035 if (!ok)
1036 return res; // res was set by the function
1037 } else {
1038 bool ok = read_eps_bbox(bboxepsdata, &bb, &res);
1039 if (!ok)
1040 return res; // res was set by the function
1041 }
1042
1043 res.width_pt = bb.x2 - bb.x1;
1044 res.height_pt = bb.y2 - bb.y1;
1045 } // end 'correct bbox in eps' block
1046
1047 // the settings requires, save the intermediary data in to result output
1048 if (settings.wantRaw)
1050
1051 if (!has_userscript_output(us_outputs, "eps-processed") && !our_skipfmts.contains("eps-processed")) {
1052 // need to process EPS, i.e. outline fonts
1053
1054 ASSERT_HAVE_FORMATS_FOR("eps-processed") ;
1055
1056 if (settings.outlineFonts) {
1057 // post-process EPS file to outline fonts if requested
1058
1059 if (settings.gsexec.isEmpty()) {
1060 res.status = KLFERR_NOGSPROG;
1061 res.errorstr = QObject::tr("No gs executable given!\n", "KLFBackend");
1062 return res;
1063 }
1064
1065 KLFBackendFilterProgram p(QLatin1String("gs (EPS Post-Processing Outline Fonts)"), &settings, isMainThread,
1066 tempdir.path());
1067 p.resErrCodes[KLFFP_NOSTART] = KLFERR_GSPOSTPROC_NORUN;
1068 p.resErrCodes[KLFFP_NOEXIT] = KLFERR_GSPOSTPROC_NONORMALEXIT;
1069 p.resErrCodes[KLFFP_NOSUCCESSEXIT] = KLFERR_PROGERR_GSPOSTPROC;
1070 p.resErrCodes[KLFFP_NODATA] = KLFERR_GSPOSTPROC_NOOUTPUT;
1071 p.resErrCodes[KLFFP_DATAREADFAIL] = KLFERR_GSPOSTPROC_OUTPUTREADFAIL;
1072
1075 const char *env_gs_device = getenv("KLFBACKEND_GS_PS_DEVICE");
1076 if (env_gs_device != NULL) {
1078 gsoptions = QStringList() << "-dNOCACHE -dNoOutputFonts";
1079 } else if (thisGsInfo.version_maj < 9 ||
1080 (thisGsInfo.version_maj == 9 && thisGsInfo.version_min <= 7)) {
1081 // until 9.07, we can still use 'pswrite' with -dNOCACHE
1082 psdevice = QLatin1String("pswrite");
1083 gsoptions = QStringList() << "-dNOCACHE";
1084 } else if (thisGsInfo.version_maj > 9 ||
1085 (thisGsInfo.version_maj == 9 && thisGsInfo.version_min >= 15)) {
1086 // Ghostscript removed the pswrite device after 9.07; The ghostscript developers
1087 // were very responsive and helpful to my feedback, and thanks to their prompt
1088 // reaction, starting from 9.15, we can use the ps2write device with the option
1089 // "-dNoOutputFonts".
1090 psdevice = QLatin1String("ps2write");
1091 gsoptions = QStringList() << "-dNoOutputFonts";
1092 } else {
1094 res.errorstr = QObject::tr("Installed Ghostscript version %1 may not be used to create font outlines."
1095 " Please upgrade to gs >= 9.15 (or downgrade to gs <= 9.07).",
1096 "KLFBackend").arg(thisGsInfo.version);
1097 return res;
1098 }
1099
1100 p.addArgv(settings.gsexec);
1101 p.addArgv(QStringList() << gsoptions
1102 << "-dNOPAUSE" << "-dSAFER" << "-dEPSCrop" << QString::fromLatin1("-sDEVICE=%1").arg(psdevice)
1103 << "-sOutputFile="+QDir::toNativeSeparators(fnProcessedEps)
1104 << "-q" << "-dBATCH" << "-");
1105
1106 ok = p.run(bboxepsdata, fnProcessedEps, &res.epsdata);
1107 if (!ok) {
1108 p.errorToOutput(&res);
1109 return res;
1110 }
1111
1112 klfDebugf(("%s: res.epsdata has length=%d", KLF_FUNC_NAME, res.epsdata.size())) ;
1113
1114 } else {
1115 // no post-processed EPS, copy raw (bbox-corrected) EPS data:
1116 res.epsdata = bboxepsdata;
1117 }
1118 }
1119
1120 if (!has_userscript_output(us_outputs, "png") && !our_skipfmts.contains("png")) {
1121
1122 ASSERT_HAVE_FORMATS_FOR("png") ;
1123
1124 if (settings.gsexec.isEmpty()) {
1125 res.status = KLFERR_NOGSPROG;
1126 res.errorstr = QObject::tr("No gs executable given!\n", "KLFBackend");
1127 return res;
1128 }
1129
1130 // run 'gs' to get PNG data
1131 KLFBackendFilterProgram p(QLatin1String("gs (PNG)"), &settings, isMainThread, tempdir.path());
1132 p.resErrCodes[KLFFP_NOSTART] = KLFERR_GSPNG_NORUN;
1133 p.resErrCodes[KLFFP_NOEXIT] = KLFERR_GSPNG_NONORMALEXIT;
1134 p.resErrCodes[KLFFP_NOSUCCESSEXIT] = KLFERR_PROGERR_GSPNG;
1135 p.resErrCodes[KLFFP_NODATA] = KLFERR_GSPNG_NOOUTPUT;
1136 p.resErrCodes[KLFFP_DATAREADFAIL] = KLFERR_GSPNG_OUTPUTREADFAIL;
1137
1142 // ### wait... do we want vector scaling to apply to the PNG as well??
1143
1144 p.setArgv(QStringList() << settings.gsexec
1145 << "-dNOPAUSE" << "-dSAFER" << "-dTextAlphaBits=4" << "-dGraphicsAlphaBits=4"
1146 << "-r"+QString::number(in.dpi) << "-dEPSCrop" << "-dMaxBitmap=2147483647");
1147 if (qAlpha(in.bg_color) > 0) { // we're forcing a background color
1148 p.addArgv("-sDEVICE=png16m");
1149 } else {
1150 p.addArgv("-sDEVICE=pngalpha");
1151 }
1152 p.addArgv(QStringList() << "-sOutputFile="+QDir::toNativeSeparators(fnRawPng) << "-q" << "-dBATCH" << "-");
1153
1154 ok = p.run(bboxepsdata, fnRawPng, &res.pngdata_raw);
1155 if (!ok) {
1156 p.errorToOutput(&res);
1157 return res;
1158 }
1159
1160 res.result.loadFromData(res.pngdata_raw, "PNG");
1161 } // raw PNG
1162 else {
1163 if (us_skipfmts.contains("png")) {
1164 klfWarning("PNG format was skipped by user script. The QImage object will be invalid.") ;
1165 res.result = QImage();
1166 res.pngdata = QByteArray();
1167 res.pngdata_raw = QByteArray();
1168 }
1169 if (!has_userscript_output(us_outputs, "png") || !QFile::exists(fnRawPng)) {
1170 klfWarning("PNG format is required to initialize the QImage object, but was not generated by user script.") ;
1171 res.result = QImage();
1172 } else {
1173 // load PNG into res.result
1174 res.result.load(fnRawPng);
1175 }
1176 }
1177
1178 if (!our_skipfmts.contains("png")) { // generate tagged/labeled PNG
1179
1180 // store some meta-information into result
1182 metainfo.saveMetaInfo(in, settings);
1183
1184 { // create "final" PNG data
1185 QBuffer buf(&res.pngdata);
1186 buf.open(QIODevice::WriteOnly);
1187
1188 bool r = res.result.save(&buf, "PNG");
1189 if (!r) {
1190 klfWarning("Can't save \"final\" PNG data.") ;
1191 res.pngdata = res.pngdata_raw;
1192 }
1193 }
1194
1195 klfDbg("prepared final PNG data.") ;
1196 }
1197
1198 if ( settings.wantPDF && !has_userscript_output(us_outputs, "pdf") && !our_skipfmts.contains("pdf") ) {
1199
1200 ASSERT_HAVE_FORMATS_FOR("pdf") ;
1201
1202 // prepare PDFMarks
1204 bool r = fpdfmarks.open(QIODevice::WriteOnly);
1205 if ( ! r ) {
1207 res.errorstr = QObject::tr("Can't open file for writing: '%1'!", "KLFBackend").arg(fnPdfMarks);
1208 return res;
1209 }
1212 pdfmetainfo.savePDFField("Title", in.latex);
1213 pdfmetainfo.savePDFField("Keywords", "KLatexFormula KLF LaTeX equation formula");
1214 pdfmetainfo.savePDFField("Creator", "KLatexFormula " KLF_VERSION_STRING);
1215 pdfmetainfo.saveMetaInfo(in, settings);
1216 pdfmetainfo.finish();
1217 fpdfmarks.write(pdfmarkstr);
1218 // file is ready.
1219 }
1220
1221 if (settings.gsexec.isEmpty()) {
1222 res.status = KLFERR_NOGSPROG;
1223 res.errorstr = QObject::tr("No gs executable given!\n", "KLFBackend");
1224 return res;
1225 }
1226
1227 // run 'gs' to get PDF data
1228 KLFBackendFilterProgram p(QLatin1String("gs (PDF)"), &settings, isMainThread, tempdir.path());
1229 p.resErrCodes[KLFFP_NOSTART] = KLFERR_GSPDF_NORUN;
1230 p.resErrCodes[KLFFP_NOEXIT] = KLFERR_GSPDF_NONORMALEXIT;
1231 p.resErrCodes[KLFFP_NOSUCCESSEXIT] = KLFERR_PROGERR_GSPDF;
1232 p.resErrCodes[KLFFP_NODATA] = KLFERR_GSPDF_NOOUTPUT;
1233 p.resErrCodes[KLFFP_DATAREADFAIL] = KLFERR_GSPDF_OUTPUTREADFAIL;
1234
1235 p.setArgv(QStringList() << settings.gsexec
1236 << "-dNOPAUSE" << "-dSAFER" << "-sDEVICE=pdfwrite"
1237 << "-sOutputFile="+QDir::toNativeSeparators(fnPdf)
1238 << "-q" << "-dBATCH" << "-" << fnPdfMarks);
1239
1240 // input: res.epsdata is the processed EPS file, or the raw EPS + bbox/page correction if no post-processing
1241 ok = p.run(res.epsdata, fnPdf, &res.pdfdata);
1242 if (!ok) {
1243 p.errorToOutput(&res);
1244 return res;
1245 }
1246 }
1247
1248 if (settings.wantSVG) {
1249
1250 if (!has_userscript_output(us_outputs, "svg-gs") &&
1251 !our_skipfmts.contains("svg-gs")) {
1252
1253 ASSERT_HAVE_FORMATS_FOR("svg-gs") ;
1254
1255 // run 'gs' to get SVG (raw from gs)
1256 if (!thisGsInfo.availdevices.contains("svg")) {
1257 // not OK to get SVG...
1258 klfWarning("ghostscript cannot create SVG");
1260 res.errorstr = QObject::tr("This ghostscript (%1) cannot generate SVG.", "KLFBackend").arg(settings.gsexec);
1261 return res;
1262 }
1263
1264 if (settings.gsexec.isEmpty()) {
1265 res.status = KLFERR_NOGSPROG;
1266 res.errorstr = QObject::tr("No gs executable given!\n", "KLFBackend");
1267 return res;
1268 }
1269
1270 KLFBackendFilterProgram p(QLatin1String("gs (SVG)"), &settings, isMainThread, tempdir.path());
1271 p.resErrCodes[KLFFP_NOSTART] = KLFERR_GSSVG_NORUN;
1272 p.resErrCodes[KLFFP_NOEXIT] = KLFERR_GSSVG_NONORMALEXIT;
1273 p.resErrCodes[KLFFP_NOSUCCESSEXIT] = KLFERR_PROGERR_GSSVG;
1274 p.resErrCodes[KLFFP_NODATA] = KLFERR_GSSVG_NOOUTPUT;
1275 p.resErrCodes[KLFFP_DATAREADFAIL] = KLFERR_GSSVG_OUTPUTREADFAIL;
1276
1277 p.addArgv(settings.gsexec);
1278 // unconditionally outline fonts, otherwise output is horrible
1279 p.addArgv(QStringList() << "-dNOCACHE" << "-dNOPAUSE" << "-dSAFER" << "-dEPSCrop" << "-sDEVICE=svg"
1280 << "-sOutputFile="+QDir::toNativeSeparators(fnGsSvg)
1281 << "-q" << "-dBATCH" << "-");
1282
1283 // input: res.epsdata is the processed EPS file, or the raw EPS file if no post-processing
1284 ok = p.run(bboxepsdata, fnGsSvg, &gssvgdata);
1285 if (!ok) {
1286 p.errorToOutput(&res);
1287 return res;
1288 }
1289 }
1290
1291 if (!has_userscript_output(us_outputs, "svg") && !our_skipfmts.contains("svg")) {
1292
1293 ASSERT_HAVE_FORMATS_FOR("svg") ;
1294
1295 // and now re-touch SVG generated by ghostscript that is not very clean...
1296 // find the first occurences of width='' and height='' and set them to the
1297 // appropriate width and heights given by BBox read earlier
1298
1299 klfDebugf(("%s: gssvgdata/length=%d", KLF_FUNC_NAME, gssvgdata.size())) ;
1300
1301 replace_svg_width_or_height(&gssvgdata, "width=", res.width_pt);
1302 replace_svg_width_or_height(&gssvgdata, "height=", res.height_pt);
1303
1304 klfDebugf(("%s: now, gssvgdata/length=%d", KLF_FUNC_NAME, gssvgdata.size())) ;
1305
1306 res.svgdata = gssvgdata;
1307 }
1308 } // end if(wantSVG)
1309
1310 klfDbg("end of function.") ;
1311
1312 return res;
1313}
1314
1315
1316
1317static bool calculate_gs_eps_bbox(const QByteArray& epsData, const QString& epsFile, klfbbox *bbox,
1319 bool isMainThread)
1320{
1321 KLF_DEBUG_BLOCK(KLF_FUNC_NAME) ;
1322 // find correct bounding box of EPS file, using ghostscript
1323
1324 int i;
1325
1326 if (settings.gsexec.isEmpty()) {
1327 resError->status = KLFERR_NOGSPROG;
1328 resError->errorstr = QObject::tr("No gs executable given!\n", "KLFBackend");
1329 return false;
1330 }
1331
1332 KLFBackendFilterProgram p(QLatin1String("GhostScript (bbox)"), &settings, isMainThread, settings.tempdir);
1333 p.resErrCodes[KLFFP_NOSTART] = KLFERR_GSBBOX_NORUN;
1334 p.resErrCodes[KLFFP_NOEXIT] = KLFERR_GSBBOX_NONORMALEXIT;
1335 p.resErrCodes[KLFFP_NOSUCCESSEXIT] = KLFERR_PROGERR_GSBBOX;
1336 p.resErrCodes[KLFFP_NODATA] = KLFERR_GSBBOX_NOOUTPUT;
1337 // p.resErrCodes[KLFFP_DATAREADFAIL] unused (used only for reading output files)
1338
1339 p.setOutputStdout(true);
1340 p.setOutputStderr(true);
1341
1343
1344 p.setArgv(QStringList() << settings.gsexec << "-dNOPAUSE" << "-dSAFER" << "-sDEVICE=bbox" << "-q" << "-dBATCH"
1345 << (epsFile.isEmpty() ? QString::fromLatin1("-") : epsFile));
1346
1347 bool ok = p.run(epsData /*stdin*/, QString() /*no output file*/, &bboxdata/*collect stdout*/);
1348 if (!ok) {
1349 p.errorToOutput(resError);
1350 return false;
1351 }
1352
1353 klfDbg("gs provided output:\n"<<bboxdata);
1354
1355 // parse gs' bbox data
1356 QRegExp rx_gsbbox("%%HiResBoundingBox\\s*:\\s+" D_RX "\\s+" D_RX "\\s+" D_RX "\\s+" D_RX "");
1358 if (i < 0) {
1360 resError->errorstr = QObject::tr("Ghostscript did not provide parsable BBox output!", "KLFBackend");
1361 return false;
1362 }
1363 bbox->x1 = rx_gsbbox.cap(1).toDouble();
1364 bbox->y1 = rx_gsbbox.cap(2).toDouble();
1365 bbox->x2 = rx_gsbbox.cap(3).toDouble();
1366 bbox->y2 = rx_gsbbox.cap(4).toDouble();
1367
1368 return true;
1369}
1370
1371
1372static bool parse_bbox_values(const QString& str, klfbbox *bbox)
1373{
1374 // parse bbox values
1375 QRegExp rx_bbvalues("" D_RX "\\s+" D_RX "\\s+" D_RX "\\s+" D_RX "");
1376 int i = rx_bbvalues.indexIn(str);
1377 if (i < 0) {
1378 return false;
1379 }
1380 bbox->x1 = rx_bbvalues.cap(1).toDouble();
1381 bbox->y1 = rx_bbvalues.cap(2).toDouble();
1382 bbox->x2 = rx_bbvalues.cap(3).toDouble();
1383 bbox->y2 = rx_bbvalues.cap(4).toDouble();
1384 return true;
1385}
1386
1387static bool s_starts_with(const char * x, int len_x, const char *test, int len_test)
1388{
1389 if (len_x < len_test)
1390 return false;
1391 return !strncmp(x, test, len_test);
1392}
1393
1394static bool read_eps_bbox(const QByteArray& epsdata, klfbbox *bbox, KLFBackend::klfOutput * resError)
1395{
1396 KLF_DEBUG_BLOCK(KLF_FUNC_NAME) ;
1397
1398 static const char * hibboxtag = "%%HiResBoundingBox:";
1399 static const char * bboxtag = "%%BoundingBox:";
1400 static const int hibboxtaglen = strlen(hibboxtag);
1401 static const int bboxtaglen = strlen(bboxtag);
1402
1403 // Read dvips' bounding box.
1404 QBuffer buf;
1405 buf.setData(epsdata);
1406 bool r = buf.open(QIODevice::ReadOnly | QIODevice::Text);
1407 if (!r) {
1408 klfWarning("What's going on!!?! can't open buffer for reading? Will Fail!!!") ;
1409 }
1410
1412 QObject::tr("DVIPS did not provide parsable %%BoundingBox: in its output!", "KLFBackend");
1413
1414 char linebuffer[512];
1415 int n;
1416 bool gotepsbbox = false;
1418 while ((n = buf.readLine(linebuffer, sizeof(linebuffer)-1)) > 0) {
1420 // if we already got the %BoundingBox, and we've been looking at more than a certian number of lines
1421 // after that, abort because usually %BoundingBox and %HiResBoundingBox are together...
1422 klfDbg("stopped looking for hires-bbox.") ;
1423 break;
1424 }
1425 if (s_starts_with(linebuffer, n, hibboxtag, hibboxtaglen)) {
1426 // got hi-res bounding-box
1427 bool ok = parse_bbox_values(QString::fromLatin1(linebuffer+hibboxtaglen), bbox);
1428 if (!ok) {
1429 if (resError) {
1431 resError->errorstr = nobboxerrstr;
1432 }
1433 return false;
1434 }
1435 klfDbg("got hires-bbox.") ;
1436 // all ok, got hi-res bbox
1437 return true;
1438 }
1439 if (s_starts_with(linebuffer, n, bboxtag, bboxtaglen)) {
1440 // got bounding-box.
1441 bool ok = parse_bbox_values(QString::fromLatin1(linebuffer+bboxtaglen), bbox);
1442 if (!ok) {
1443 continue;
1444 }
1445 // stand by, continue in case we have a hi-res bbox.
1446 gotepsbbox = true;
1447 klfDbg("got normal bbox.") ;
1448 continue;
1449 }
1450 }
1451
1452 // didn't get a hi-res bbox. see if we still got a regular %BoundingBox: and return that.
1453 if (gotepsbbox) {
1454 // bbox pointer is already set
1455 return true;
1456 }
1457
1458 if (resError) {
1460 resError->errorstr = nobboxerrstr;
1461 }
1462 return false;
1463}
1464
1470static void correct_eps_bbox(const QByteArray& rawepsdata, const klfbbox& bbox_corrected,
1471 const klfbbox& bbox_orig, double vectorscale, QRgb bgcolor,
1473{
1474 KLF_DEBUG_BLOCK(KLF_FUNC_NAME) ;
1475
1476 static const char * bboxdecl = "%%BoundingBox:";
1477 static int bboxdecl_len = strlen(bboxdecl);
1478
1479 double offx = bbox_corrected.x1 - bbox_orig.x1;
1480 double offy = bbox_corrected.y1 - bbox_orig.y1;
1481
1482 // in raw EPS data, find '%%BoundingBox:' and length of the full BoundingBox instruction
1483 int i, len;
1484 char nl[] = "\0\0\0";
1485 i = rawepsdata.indexOf(bboxdecl);
1486 if (i < 0) {
1487 i = 0;
1488 len = 0;
1489 } else {
1490 int j = i+bboxdecl_len;
1491 while (j < (int)rawepsdata.size() && rawepsdata[j] != '\r' && rawepsdata[j] != '\n')
1492 ++j;
1493 len = j-i;
1494 if (rawepsdata[j] == '\r' && j < (int)rawepsdata.size()-1 && rawepsdata[j+1] == '\n') {
1495 nl[0] = '\r', nl[1] = '\n';
1496 } else {
1497 nl[0] = rawepsdata[j];
1498 }
1499 }
1500
1501 double dwi = bbox_corrected.x2 * vectorscale;
1502 double dhi = bbox_corrected.y2 * vectorscale;
1503 int wi = (int)(dwi + 0.99999) ;
1504 int hi = (int)(dhi + 0.99999) ;
1505 char buffer[1024];
1506 int buffer_len;
1507 // recall that '%%' in printf is replaced by a single '%'...
1508 snprintf(buffer, sizeof(buffer)-1,
1509 "%%%%BoundingBox: 0 0 %d %d%s"
1510 "%%%%HiResBoundingBox: 0 0 %s %s%s",
1511 wi, hi, nl,
1512 klfFmtDoubleCC(dwi, 'g', 6), klfFmtDoubleCC(dhi, 'g', 6), nl);
1513 buffer_len = strlen(buffer);
1514
1515 char backgroundfillps[1024] = "";
1516 if (qAlpha(bgcolor) > 0) {
1517 klfDbg("we have a bg color, so draw the background. bgcolor="<<klfFmt("%#10x", (uint)bgcolor));
1519 // draw the background color, if any
1520 "newpath "
1521 "-2 -2 moveto "
1522 "%s -2 lineto "
1523 "%s %s lineto "
1524 "-2 %s lineto "
1525 "closepath "
1526 "gsave "
1527 "%s %s %s setrgbcolor "
1528 "fill "
1529 "grestore %s",
1530 klfFmtDoubleCC(dwi+1, 'g', 6),
1531 klfFmtDoubleCC(dwi+1, 'g', 6), klfFmtDoubleCC(dhi+1, 'g', 6),
1532 klfFmtDoubleCC(dhi+1, 'g', 6),
1533 // and the color, in RGB components:
1534 klfFmtDoubleCC(qRed(bgcolor)/255.0, 'f', 6),
1535 klfFmtDoubleCC(qGreen(bgcolor)/255.0, 'f', 6),
1536 klfFmtDoubleCC(qBlue(bgcolor)/255.0, 'f', 6),
1537 nl
1538 );
1539 }
1540
1541 char buffer2[1024];
1542 //int buffer2_len;
1543 snprintf(buffer2, sizeof(buffer2)-1,
1544 "%s"
1545 "%%%%Page 1 1%s"
1546 "%%%%PageBoundingBox 0 0 %d %d%s"
1547 "<< /PageSize [%d %d] >> setpagedevice%s"
1548 "%s"
1549 "%s %s scale%s"
1550 "%s %s translate%s"
1551 ,
1552 nl,
1553 nl,
1554 wi, hi, nl,
1555 wi, hi, nl,
1557 klfFmtDoubleCC(vectorscale, 'f'), klfFmtDoubleCC(vectorscale, 'f'), nl,
1558 klfFmtDoubleCC(offx, 'f'), klfFmtDoubleCC(offy, 'f'), nl);
1559 //buffer2_len = strlen(buffer2);
1560
1561 // char buffer2[128];
1562 // snprintf(buffer2, 127, "%sgrestore%s", nl, nl);
1563
1564 klfDbg("buffer is `"<<buffer<<"', length="<<buffer_len) ;
1565 klfDbg("rawepsdata has length="<<rawepsdata.size()) ;
1566
1567 // and modify the raw EPS data, to replace "%%BoundingBox:" instruction by our stuff...
1570 neweps.replace(i, len, buffer);
1571
1572 const char * endsetupstr = "%%EndSetup";
1573 int i2 = neweps.indexOf(endsetupstr);
1574 if (i2 < 0)
1575 i2 = i + buffer_len; // add our info after modified %%BoundingBox'es instructions if %%EndSetup not found
1576 else
1577 i2 += strlen(endsetupstr);
1578
1579 neweps.replace(i2, 0, buffer2);
1580
1581 klfDbg("neweps has now length="<<neweps.size());
1582 klfDebugf(("New eps bbox is [0 0 %.6g %.6g] with translate [%.6g %.6g] and scale %.6g.",
1583 dwi, dhi, offx, offy, vectorscale));
1584
1586}
1587
1588
1589static void replace_svg_width_or_height(QByteArray *svgdata, const char * attreq, double val)
1590{
1591 QByteArray & svgdataref = * svgdata;
1592
1593 int i = svgdataref.indexOf(attreq);
1594 int j = i;
1595 while (j < (int)svgdataref.size() && (!isspace(svgdataref[j]) && svgdataref[j] != '>'))
1596 ++j;
1597
1598 char buffer[1024];
1599 snprintf(buffer, sizeof(buffer)-1, "%s'%s'", attreq, klfFmtDoubleCC(val, 'f', 3));
1600
1601 svgdata->replace(i, j-i, buffer);
1602}
1603
1604
1605
1606
1607
1609{
1610 return a.latex == b.latex &&
1611 a.mathmode == b.mathmode &&
1612 a.preamble == b.preamble &&
1613 fabs(a.fontsize - b.fontsize) < 0.001 &&
1614 a.fg_color == b.fg_color &&
1615 a.bg_color == b.bg_color &&
1616 a.dpi == b.dpi &&
1617 fabs(a.vectorscale - b.vectorscale) < 0.001 &&
1618 a.bypassTemplate == b.bypassTemplate &&
1619 a.userScript == b.userScript;
1620}
1621
1623{
1624 return
1625 a.tempdir == b.tempdir &&
1626 a.latexexec == b.latexexec &&
1627 a.dvipsexec == b.dvipsexec &&
1628 a.gsexec == b.gsexec &&
1629 a.epstopdfexec == b.epstopdfexec &&
1630 a.tborderoffset == b.tborderoffset &&
1631 a.rborderoffset == b.rborderoffset &&
1632 a.bborderoffset == b.bborderoffset &&
1633 a.lborderoffset == b.lborderoffset &&
1634 a.calcEpsBoundingBox == b.calcEpsBoundingBox &&
1635 a.outlineFonts == b.outlineFonts &&
1636 a.wantRaw == b.wantRaw &&
1637 a.wantPDF == b.wantPDF &&
1638 a.wantSVG == b.wantSVG &&
1639 a.execenv == b.execenv &&
1640 a.templateGenerator == b.templateGenerator ;
1641}
1642
1643
1645{
1646 if (output != NULL)
1648
1650 fmts << "PNG" << "PS" << "EPS" << "DVI" << "PDF" << "SVG";
1651
1653 foreach (QByteArray f, imgfmts) {
1654 f = f.trimmed().toUpper();
1655 if (f == "JPG")
1656 f = "JPEG"; // only report "JPEG", not both "JPG" and "JPEG"
1657 if (fmts.contains(f))
1658 continue;
1660 }
1661 return fmts;
1662}
1663
1665{
1666 QStringList formats;
1667 // Most popular formats on top of list (pdf, png)
1668 if (!klfoutput.pdfdata.isEmpty())
1669 formats << "PDF";
1670 if (!klfoutput.pngdata.isEmpty())
1671 formats << "PNG";
1672 if (!klfoutput.svgdata.isEmpty())
1673 formats << "SVG";
1674 if (!klfoutput.epsdata.isEmpty())
1675 formats << "PS" << "EPS";
1676 if (!klfoutput.dvidata.isEmpty())
1677 formats << "DVI";
1678 // and, of course, all Qt-available image formats
1680 foreach (QByteArray f, imgfmts) {
1681 f = f.trimmed().toUpper();
1682 if (f == "JPG")
1683 f = "JPEG"; // only report "JPEG", not both "JPG" and "JPEG"
1684 if (formats.contains(f))
1685 continue;
1686 formats << QString::fromLatin1(f);
1687 }
1688 return formats;
1689}
1690
1693{
1694 QString format = fmt.trimmed().toUpper();
1695
1696 // now choose correct data source and write to fout
1697 if (format == "PNG") {
1698 device->write(klfoutput.pngdata);
1699 } else if (format == "EPS" || format == "PS") {
1700 device->write(klfoutput.epsdata);
1701 } else if (format == "DVI") {
1702 device->write(klfoutput.dvidata);
1703 } else if (format == "PDF") {
1704 if (klfoutput.pdfdata.isEmpty()) {
1705 QString error = QObject::tr("PDF format is not available!",
1706 "KLFBackend::saveOutputToFile");
1707 qWarning("%s", qPrintable(error));
1708 if (errorStringPtr != NULL)
1709 errorStringPtr->operator=(error);
1710 return false;
1711 }
1712 device->write(klfoutput.pdfdata);
1713 } else if (format == "SVG") {
1714 if (klfoutput.svgdata.isEmpty()) {
1715 QString error = QObject::tr("SVG format is not available!",
1716 "KLFBackend::saveOutputToFile");
1717 qWarning("%s", qPrintable(error));
1718 if (errorStringPtr != NULL)
1719 errorStringPtr->operator=(error);
1720 return false;
1721 }
1722 device->write(klfoutput.svgdata);
1723 } else {
1724 bool res = klfoutput.result.save(device, format.toLatin1());
1725 if ( ! res ) {
1726 QString errstr = QObject::tr("Unable to save image in format `%1'!",
1727 "KLFBackend::saveOutputToDevice").arg(format);
1728 qWarning("%s", qPrintable(errstr));
1729 if (errorStringPtr != NULL)
1731 return false;
1732 }
1733 }
1734
1735 return true;
1736}
1737
1740{
1741 QString format = fmt;
1742 // determine format first
1743 if (format.isEmpty() && !fileName.isEmpty()) {
1744 QFileInfo fi(fileName);
1745 if ( ! fi.suffix().isEmpty() )
1746 format = fi.suffix();
1747 }
1748 if (format.isEmpty())
1749 format = QLatin1String("PNG");
1750 format = format.trimmed().toUpper();
1751 // got format. choose output now and prepare write
1752 QFile fout;
1753 if (fileName.isEmpty() || fileName == "-") {
1754 if ( ! fout.open(stdout, QIODevice::WriteOnly) ) {
1755 QString error = QObject::tr("Unable to open stdout for write! Error: %1",
1756 "KLFBackend::saveOutputToFile").arg(fout.error());
1757 qWarning("%s", qPrintable(error));
1758 if (errorStringPtr != NULL)
1759 *errorStringPtr = error;
1760 return false;
1761 }
1762 } else {
1763 fout.setFileName(fileName);
1764 if ( ! fout.open(QIODevice::WriteOnly) ) {
1765 QString error = QObject::tr("Unable to write to file `%1'! Error: %2",
1766 "KLFBackend::saveOutputToFile")
1767 .arg(fileName).arg(fout.error());
1768 qWarning("%s", qPrintable(error));
1769 if (errorStringPtr != NULL)
1770 *errorStringPtr = error;
1771 return false;
1772 }
1773 }
1774
1776}
1777
1778
1780{
1782
1784 int k, j;
1785 for (k = 0; standard_extra_paths[k] != NULL; ++k) {
1786 stdextrapaths.append(standard_extra_paths[k]);
1787 }
1789 if (!extraPath.isEmpty())
1791
1792 // temp dir
1794
1795 // sensible defaults
1796 settings->lborderoffset = 1;
1797 settings->tborderoffset = 1;
1798 settings->rborderoffset = 1;
1799 settings->bborderoffset = 1;
1800
1801 settings->epstopdfexec = QString(); // obsolete, no longer used
1802
1803 // you'll want PDF
1804 settings->wantPDF = true;
1805
1806 settings->wantSVG = false; // will be set to TRUE once we verify 'gs' available devices information
1807
1808 // find executables
1810 { & settings->latexexec, progLATEX },
1811 { & settings->dvipsexec, progDVIPS },
1812 { & settings->gsexec, progGS },
1813 // { & settings->epstopdfexec, progEPSTOPDF },
1814 { NULL, QStringList() }
1815 };
1816 // replace @executable_path in extra_paths
1817 klfDbg(klfFmtCC("Our base extra paths are: %s", qPrintable(extra_paths))) ;
1819 ourextrapaths.replace("@executable_path", qApp->applicationDirPath());
1820 klfDbg(klfFmtCC("Our extra paths are: %s", qPrintable(ourextrapaths))) ;
1821 // and actually search for those executables
1822 for (k = 0; progs_to_find[k].target_setting != NULL; ++k) {
1823 klfDbg("Looking for "+progs_to_find[k].prog_names.join(" or ")) ;
1824 for (j = 0; j < (int)progs_to_find[k].prog_names.size(); ++j) {
1825 klfDbg("Testing `"+progs_to_find[k].prog_names[j]+"'") ;
1826 *progs_to_find[k].target_setting
1829 klfDbg("Found! at `"+ *progs_to_find[k].target_setting+"'") ;
1830 break; // found a program
1831 }
1832 }
1833 }
1834
1835 bool r1 = detectOptionSettings(settings, isMainThread);
1836
1837 bool result_failure =
1838 settings->tempdir.isEmpty() || settings->latexexec.isEmpty() || settings->dvipsexec.isEmpty() ||
1839 settings->gsexec.isEmpty() || !r1;
1840
1841 return !result_failure;
1842}
1843
1845{
1847
1848 bool r0 = klf_detect_execenv(settings);
1849 if (!r0) {
1850 return false;
1851 }
1852
1853 settings->wantSVG = false;
1854
1855 bool ok = true;
1856 if (settings->gsexec.length()) {
1857 initGsInfo(settings, isMainThread);
1858 if (!gsInfo.contains(settings->gsexec)) {
1859 klfWarning("Cannot get 'gs' devices information with "<<(settings->gsexec+" --version/--help"));
1860 ok = false;
1861 } else if (gsInfo[settings->gsexec].availdevices.contains("svg")) {
1862 settings->wantSVG = true;
1863 }
1864 }
1865
1866 return ok;
1867}
1868
1869
1871{
1873
1874 // detect mgs.exe as ghostscript and setup its environment properly
1875 QFileInfo gsfi(settings->gsexec);
1876 if (gsfi.fileName() == "mgs.exe") {
1877 QString mgsenv = QString("")
1878 + QDir::toNativeSeparators(gsfi.absolutePath()+"/../../ghostscript/base")
1880 + QDir::toNativeSeparators(gsfi.absolutePath()+"/../../fonts");
1881 settings->execenv = klfSetEnvironmentVariable(settings->execenv, "MIKTEX_GS_LIB", mgsenv);
1882 klfDbg("Adjusting environment for mgs.exe: `MIKTEX_GS_LIB="+mgsenv+"'") ;
1883 }
1884
1885 return true;
1886}
1887
1888
1889
1890
1891// static
1892void initGsInfo(const KLFBackend::klfSettings *settings, bool isMainThread)
1893{
1894 KLF_DEBUG_TIME_BLOCK(KLF_FUNC_NAME) ;
1895
1896 if (gsInfo.contains(settings->gsexec)) // info already cached
1897 return;
1898
1899 if (settings->gsexec.isEmpty()) {
1900 // no GS executable given
1901 return;
1902 }
1903
1904 QString gsver;
1905 { // test 'gs' version, to see if we can provide SVG data
1906 KLFBackendFilterProgram p(QLatin1String("gs (test version)"), settings, isMainThread, settings->tempdir);
1907 // p.resErrCodes[KLFFP_NOSTART] = ;
1908 // p.resErrCodes[KLFFP_NOEXIT] = ;
1909 // p.resErrCodes[KLFFP_NOSUCCESSEXIT] = ;
1910 // p.resErrCodes[KLFFP_NODATA] = ;
1911 // p.resErrCodes[KLFFP_DATAREADFAIL] = ;
1912
1913 p.setExecEnviron(settings->execenv);
1914
1915 // I think there is a problem with app events processing if this is run during application start-up
1916 p.setProcessAppEvents(false);
1917
1918 p.setArgv(QStringList() << settings->gsexec << "--version");
1919
1921 bool ok = p.run(QString(), &ba_gsver);
1922 if (ok) {
1924 klfDbg("gs version text (untrimmed): "<<gsver) ;
1925
1926 gsver = gsver.trimmed();
1927 }
1928 }
1929
1931 KLFStringSet availdevices;
1932 { // test 'gs' version, to see if we can provide SVG data
1933 KLFBackendFilterProgram p(QLatin1String("gs (query help)"), settings, isMainThread, settings->tempdir);
1934 // p.resErrCodes[KLFFP_NOSTART] = ;
1935 // p.resErrCodes[KLFFP_NOEXIT] = ;
1936 // p.resErrCodes[KLFFP_NOSUCCESSEXIT] = ;
1937 // p.resErrCodes[KLFFP_NODATA] = ;
1938 // p.resErrCodes[KLFFP_DATAREADFAIL] = ;
1939
1940 QStringList ee = settings->execenv;
1941 // make sure we have gs' output in english
1942 ee = klfSetEnvironmentVariable(ee, QLatin1String("LANG"), QLatin1String("en_US.UTF-8"));
1943 p.setExecEnviron(ee);
1944
1945 // I think there is a problem with app events processing if this is run during application start-up
1946 p.setProcessAppEvents(false);
1947
1948 p.setArgv(QStringList() << settings->gsexec << "--help");
1949
1951 bool ok = p.run(QString(), &ba_gshelp);
1952 if (ok) {
1954
1955 klfDbg("gs help text: "<<gshelp) ;
1956 // parse available devices
1957 const char * availdevstr = "Available devices:";
1958 int k = gshelp.indexOf(availdevstr, 0, Qt::CaseInsensitive) ;
1959 if (k == -1) {
1960 klfWarning("Unable to parse gs' available devices.") ;
1961 } else {
1962 k += strlen(availdevstr); // point to after 'available devices:' string
1963 // find end of available devices list, given by a line not starting with whitespace
1964 int kend = gshelp.indexOf(QRegExp("\\n\\S"), k);
1965 if (kend == -1)
1966 kend = gshelp.length();
1967 // now split this large string into the devices list
1968 QStringList devlist = gshelp.mid(k, kend-k).split(QRegExp("(\\s|[\r\n])+"), QString::SkipEmptyParts);
1969 availdevices = KLFStringSet::fromList(devlist);
1970 klfDbg("Detected devices: "<<availdevices) ;
1971 }
1972 }
1973 }
1974
1975 int gsvermaj = -1;
1976 int gsvermin = -1;
1977 QRegExp rx_version("^(\\d+)\\.(\\d+)");
1978 int foundver = rx_version.indexIn(gsver);
1979 if (foundver >= 0) {
1980 gsvermaj = rx_version.cap(1).toInt();
1981 gsvermin = rx_version.cap(2).toInt();
1982 }
1983
1984 klfDbg("GS Version: "<<gsver<<" = "<<gsvermaj<<"."<<gsvermin);
1985
1986 GsInfo i;
1987 i.version = gsver;
1988 i.version_maj = gsvermaj;
1989 i.version_min = gsvermin;
1990 i.help = gshelp;
1991 i.availdevices = availdevices;
1992
1993 gsInfo[settings->gsexec] = i;
1994}
1995
1996
1997
1998
1999
2000KLF_EXPORT QStringList klfSettingsToEnvironmentForUserScript(const KLFBackend::klfSettings& settings)
2001{
2003 env << "KLF_SETTINGS_BORDEROFFSET=" + klfFmt("%.3g,%.3g,%.3g,%.3g pt", settings.tborderoffset,
2004 settings.rborderoffset, settings.bborderoffset, settings.lborderoffset)
2005 << "KLF_SETTINGS_OUTLINEFONTS=" + QString::fromLatin1(settings.outlineFonts ? "1" : "0")
2006 << "KLF_SETTINGS_CALCEPSBOUNDINGBOX=" + QString::fromLatin1(settings.calcEpsBoundingBox ? "1" : "0")
2007 << "KLF_SETTINGS_WANT_RAW=" + QString::fromLatin1(settings.wantRaw ? "1" : "0")
2008 << "KLF_SETTINGS_WANT_PDF=" + QString::fromLatin1(settings.wantPDF ? "1" : "0")
2009 << "KLF_SETTINGS_WANT_SVG=" + QString::fromLatin1(settings.wantSVG ? "1" : "0");
2010 return env;
2011}
2012
2013KLF_EXPORT QStringList klfInputToEnvironmentForUserScript(const KLFBackend::klfInput& in)
2014{
2016 QString fgcol = QString("rgba(%1,%2,%3,%4)").arg(qRed(in.fg_color))
2017 .arg(qGreen(in.fg_color)).arg(qBlue(in.fg_color)).arg(qAlpha(in.fg_color));
2018 QString bgcol = QString("rgba(%1,%2,%3,%4)").arg(qRed(in.bg_color))
2019 .arg(qGreen(in.bg_color)).arg(qBlue(in.bg_color)).arg(qAlpha(in.bg_color));
2020 env << "KLF_INPUT_LATEX=" + in.latex
2021 << "KLF_INPUT_MATHMODE=" + in.mathmode
2022 << "KLF_INPUT_PREAMBLE=" + in.preamble
2023 << "KLF_INPUT_FONTSIZE=" + QString::number(in.fontsize)
2024 << "KLF_INPUT_FG_COLOR_WEB=" + QColor(in.fg_color).name()
2025 << "KLF_INPUT_FG_COLOR_RGBA=" + fgcol
2026 << "KLF_INPUT_BG_COLOR_TRANSPARENT=" + QString::fromLatin1(qAlpha(in.bg_color) > 50 ? "0" : "1")
2027 << "KLF_INPUT_BG_COLOR_WEB=" + QColor(in.bg_color).name()
2028 << "KLF_INPUT_BG_COLOR_RGBA=" + bgcol
2029 << "KLF_INPUT_DPI=" + QString::number(in.dpi)
2030 << "KLF_INPUT_VECTORSCALE=" + klfFmt("%.6g", in.vectorscale)
2031 << "KLF_INPUT_USERSCRIPT=" + in.userScript
2032 << "KLF_INPUT_BYPASS_TEMPLATE=" + QString::number(in.bypassTemplate)
2033 ;
2034
2035 // and add custom user parameters
2037 for (cit = in.userScriptParam.constBegin(); cit != in.userScriptParam.constEnd(); ++cit) {
2038 env << "KLF_ARG_"+cit.key() + "=" + cit.value();
2039 }
2040
2041 return env;
2042}
virtual QString generateTemplate(const klfInput &input, const klfSettings &settings)
static QStringList availableSaveFormats(const klfOutput *output=NULL)
Get a list of available output formats.
static bool detectOptionSettings(klfSettings *settings, bool isMainThread=true)
Detects additional options (e.g. klfSettings::wantSVG) that depend on specific program versions.
static bool detectSettings(klfSettings *settings, const QString &extraPath=QString(), bool isMainThread=true)
Detects the system settings and stores the guessed values in settings.
static klfOutput getLatexFormula(const klfInput &in, const klfSettings &settings, bool isMainThread=true)
The function that processes everything.
static bool saveOutputToDevice(const klfOutput &output, QIODevice *device, const QString &format=QString("PNG"), QString *errorString=NULL)
Saves the given output into the given device.
static bool saveOutputToFile(const klfOutput &output, const QString &fileName, const QString &format=QString(), QString *errorString=NULL)
Save the output to image file.
Write metainfo to PDF files via pdfmarks for ghostscript.
Definition klfbackend.h:729
QString loadField(const QString &)
void savePDFField(const QString &k, const QString &v)
Saves the field without prepending 'KLF' to the key.
Definition of class KLFBackend.
#define KLFERR_GSPOSTPROC_NOOUTPUT
Program 'gs' didn't provide any data after post-processing EPS file.
Definition klfbackend.h:105
#define KLFERR_GSPNG_NONORMALEXIT
Program 'gs' didn't exit noramally (crashed) while generating PNG (see also KLFERR_PROGERR_GSPNG)
Definition klfbackend.h:119
#define KLFERR_PROGERR_GSPOSTPROC
gs exited with non-zero status while post-processing EPS file (page size, font outlines)
Definition klfbackend.h:179
#define KLFERR_GSPDF_NOOUTPUT
No PDF file appeared after running 'gs'.
Definition klfbackend.h:139
#define KLFERR_LATEX_NOOUTPUT
No .dvi file appeared after runnig latex program.
Definition klfbackend.h:65
#define KLFERR_GSBBOX_NOOUTPUT
Program 'gs' didn't provide any output.
Definition klfbackend.h:97
#define KLFERR_GSPOSTPROC_NONORMALEXIT
Program 'gs' crashed while post-processing EPS file (see also KLFERR_PROGERR_GSPOSTPROC)
Definition klfbackend.h:103
#define KLFERR_GSSVG_NONORMALEXIT
Program 'gs' didn't exit noramally (crashed) while generating SVG (see also KLFERR_PROGERR_GSSVG)
Definition klfbackend.h:153
#define KLFERR_PROGERR_LATEX
latex exited with a non-zero status
Definition klfbackend.h:173
#define KLFERR_MISSINGMATHMODETHREEDOTS
The "..." is missing in math mode string.
Definition klfbackend.h:53
#define KLFERR_PROGERR_GSPDF
gs exited with non-zero status while producing PDF
Definition klfbackend.h:187
#define KLFERR_GSBBOX_NOBBOX
Program 'gs' calculating bbox didn't provide parsable output.
Definition klfbackend.h:99
#define KLFERR_GSBBOX_NORUN
Program 'gs' cannot be executed to calculate bounding box.
Definition klfbackend.h:93
#define KLFERR_LATEX_OUTPUTREADFAIL
Error while opening .dvi file for reading.
Definition klfbackend.h:69
#define KLFERR_PROGERR_GSPNG
gs exited with a non-zero status while producing PNG
Definition klfbackend.h:183
#define KLFERR_NOLATEXPROG
obsolete, same as KLFERR_LATEX_NORUN
Definition klfbackend.h:59
#define KLFERR_LATEX_NORUN
Error while launching the given latex program.
Definition klfbackend.h:57
#define KLFERR_DVIPS_OUTPUTREADFAIL
Error while opening .eps file for reading.
Definition klfbackend.h:85
#define KLFERR_GSPOSTPROC_NORUN
Program 'gs' cannot be executed to post-process EPS file (page size, outline fonts)
Definition klfbackend.h:101
#define KLFERR_GSSVG_NOOUTPUT
No SVG file appeared after running 'gs'.
Definition klfbackend.h:155
#define KLFERR_GSSVG_NORUN
Program 'gs' couldn't be executed to generate SVG.
Definition klfbackend.h:151
#define KLFERR_NODVIPSPROG
obsolete, same as KLFERR_DVIPS_NORUN
Definition klfbackend.h:75
#define KLFERR_PROGERR_USERSCRIPT
user wrapper script exited with non-zero status
Definition klfbackend.h:193
#define KLFERR_DVIPS_OUTPUTNOBBOX
Error while reading/parsing %BoundingBox: in dvips output.
Definition klfbackend.h:89
#define KLFERR_DVIPS_NORUN
Error while launching the given dvips program.
Definition klfbackend.h:73
#define KLFERR_GSPNG_NOOUTPUT
No PNG file appeared after running 'gs'.
Definition klfbackend.h:123
#define KLFERR_USERSCRIPT_NORUN
Failed to execute user wrapper script.
Definition klfbackend.h:159
#define KLFERR_GSPNG_OUTPUTREADFAIL
Failed to read PNG file produced by 'gs'.
Definition klfbackend.h:127
#define KLFERR_DVIPS_NONORMALEXIT
dvips program did not exit properly (program killed) (see also KLFERR_PROGERR_DVIPS)
Definition klfbackend.h:77
#define KLFERR_GSBBOX_NONORMALEXIT
Program 'gs' crashed while calculating bbox (see also KLFERR_PROGERR_GSBBOX)
Definition klfbackend.h:95
#define KLFERR_GSPNG_NORUN
Program 'gs' couldn't be executed to generate PNG.
Definition klfbackend.h:115
#define KLFERR_NOERROR
No Error.
Definition klfbackend.h:46
#define KLFERR_PROGERR_DVIPS
dvips exited with a non-zero status
Definition klfbackend.h:175
#define KLFERR_GSPDF_NORUN
Program 'gs' couldn't be executed to generate PDF.
Definition klfbackend.h:133
#define KLFERR_PDFMARKSWRITEFAIL
Error while opening pdfmarks file for writing.
Definition klfbackend.h:131
#define KLFERR_GSPDF_NONORMALEXIT
Program 'gs' didn't exit noramally (crashed) while generating PDF (see also KLFERR_PROGERR_GSPDF)
Definition klfbackend.h:135
#define KLFERR_GSSVG_OUTPUTREADFAIL
Failed to read SVG file produced by 'gs'.
Definition klfbackend.h:157
KLF_EXPORT bool operator==(const KLFBackend::klfInput &a, const KLFBackend::klfInput &b)
#define KLFERR_GSPDF_OUTPUTREADFAIL
Failed to read PDF file produced by 'gs'.
Definition klfbackend.h:143
#define KLFERR_PROGERR_GSBBOX
gs exited with non-zero status while calculating bbox of EPS file generated by dvips
Definition klfbackend.h:177
KLF_EXPORT bool klf_detect_execenv(KLFBackend::klfSettings *settings)
detects any additional settings to environment variables
#define KLFERR_NOGSVERSION
Failed to query gs version.
Definition klfbackend.h:147
#define KLFERR_LATEX_NONORMALEXIT
latex program did not exit properly (program killed) (see also KLFERR_PROGERR_LATEX)
Definition klfbackend.h:61
#define KLFERR_GSPOSTPROC_NOOUTLINEFONTS
'gs' cannot outline fonts: need version <= 9.07 (pswrite -dNOCACHE) or >= 9.15 (ps2write -dNoOutputFo...
Definition klfbackend.h:107
#define KLFERR_DVIPS_NOOUTPUT
no .eps file appeared after running dvips program
Definition klfbackend.h:81
#define KLFERR_TEMPDIR_FAIL
Failed to create the temporary directory.
Definition klfbackend.h:49
#define KLFERR_NOGSPROG
obsolete, same as KLFERR_GSPNG_NORUN
Definition klfbackend.h:117
#define KLFERR_MISSINGLATEXFORMULA
No LaTeX formula is specified (empty string)
Definition klfbackend.h:51
#define KLFERR_PROGERR_GSSVG
gs exited with non-zero status while producing SVG
Definition klfbackend.h:191
#define KLFERR_GSSVG_NOSVG
This version of gs cannot produce SVG.
Definition klfbackend.h:149
#define KLFERR_GSPOSTPROC_OUTPUTREADFAIL
Couldn't read output provided by 'gs' program after post-processing EPS file.
Definition klfbackend.h:111
#define KLFERR_TEXWRITEFAIL
Error while opening .tex file for writing.
Definition klfbackend.h:55
Defines the KLFBlockProcess class.
KLF_EXPORT QByteArray klfEscapedToData(const QByteArray &data, char escapechar)
const char * format
KLF_EXPORT QByteArray klfDataToEscaped(const QByteArray &value_ba, char escapechar)
KLF_EXPORT QByteArray klfSaveVariantToText(const QVariant &value, bool saveListAndMapsAsXML, QByteArray *savedType, QByteArray *savedListOrMapType)
#define KLF_DEBUG_TIME_BLOCK(msg)
#define klfWarning(streamableItems)
#define KLF_DEBUG_BLOCK(msg)
#define KLF_ASSERT_NOT_NULL(ptr, msg, failaction)
#define KLF_ASSERT_CONDITION(expr, msg, failaction)
#define KLF_FUNC_NAME
#define klfDbg(streamableItems)
#define klfDebugf(arglist)
KLF_EXPORT int klfVersionCompare(const QString &v1, const QString &v2)
KLF_EXPORT QByteArray klfFmt(const char *fmt, va_list pp)
#define klfFmtDoubleCC
#define klfFmtCC
#define KLF_EXPORT
#define KLF_PATH_SEP
KLF_EXPORT void klfSetEnvironmentVariable(QStringList *env, const QString &var, const QString &value)
void klfMergeEnvironment(QStringList *env, const QStringList &addvars, const QStringList &pathvars, uint actions)
KLF_EXPORT QStringList klfCurrentEnvironment()
KLF_EXPORT QString klfSearchPath(const QString &programName, const QString &extra_path)
QVariantMap klfMapToVariantMap(const QMap< QString, T > &map)
KlfEnvPathPrepend
KlfEnvMergeExpandVars
QByteArray & append(char ch)
QByteArray & replace(int pos, int len, const char *after)
int size() const
QString name() const
QString fromNativeSeparators(const QString &pathName)
QString tempPath()
QString toNativeSeparators(const QString &pathName)
bool exists() const
virtual bool open(OpenMode mode)
QString fileName() const
bool load(QIODevice *device, const char *format)
bool loadFromData(const uchar *data, int len, const char *format)
bool save(const QString &fileName, const char *format, int quality) const
void setText(const QString &key, const QString &text)
QString text(const QString &key) const
QList< QByteArray > supportedImageFormats()
qint64 write(const char *data, qint64 maxSize)
const_iterator constBegin() const
const_iterator constEnd() const
bool contains(const Key &key) const
bool isEmpty() const
const Key key(const T &value, const Key &defaultKey) const
int remove(const Key &key)
int size() const
const T value(const Key &key, const T &defaultValue) const
QString tr(const char *sourceText, const char *disambiguation, int n)
QProcessEnvironment systemEnvironment()
bool contains(const T &value) const
QSet< T > fromList(const QList< T > &list)
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const
QString fromLatin1(const char *str, int size)
QString fromUtf8(const char *str, int size)
bool isEmpty() const
int length() const
QString number(int n, int base)
QByteArray toLatin1() const
QByteArray toUtf8() const
bool contains(const QString &str, Qt::CaseSensitivity cs) const
QString join(const QString &separator) const
bool isValid() const
QString path() const
QTextCodec * codecForName(const QByteArray &name)
QByteArray fromUnicode(const QString &str) const
Specific input to KLFBackend::getLatexFormula()
Definition klfbackend.h:307
QMap< QString, QString > userScriptParam
Arbitrary parameters to pass to user script.
Definition klfbackend.h:364
unsigned long bg_color
Definition klfbackend.h:334
QString userScript
A Path to a user script that acts as wrapper around LaTeX.
Definition klfbackend.h:356
KLFBackend::getLatexFormula() result.
Definition klfbackend.h:371
klfSettings settings
The settings that this output was generated with.
Definition klfbackend.h:398
double height_pt
Width in points of the resulting equation.
Definition klfbackend.h:453
QByteArray pdfdata
data for a pdf file
Definition klfbackend.h:447
QByteArray epsdata_raw
data for an (eps-)postscript file.
Definition klfbackend.h:434
QByteArray epsdata_bbox
data for an (eps-)postscript file.
Definition klfbackend.h:441
QByteArray pngdata
the data for a png file (re-processed with meta information)
Definition klfbackend.h:428
int status
A code describing the status of the request.
Definition klfbackend.h:381
klfInput input
The input parameters used to generate this output.
Definition klfbackend.h:396
QByteArray epsdata
data for an (eps-)postscript file.
Definition klfbackend.h:445
QByteArray pngdata_raw
the data for a png file (exact gs output content)
Definition klfbackend.h:408
double width_pt
Width in points of the resulting equation.
Definition klfbackend.h:451
QImage result
The actual resulting image.
Definition klfbackend.h:393
QByteArray svgdata
data for a SVG file, if ghostscript supports SVG
Definition klfbackend.h:449
QByteArray dvidata
The DVI file data outputted by latex executable.
Definition klfbackend.h:401
QString errorstr
An explicit error string.
Definition klfbackend.h:390
General settings for KLFBackend::getLatexFormula()
Definition klfbackend.h:219
TemplateGenerator * templateGenerator
Definition klfbackend.h:294

Generated by doxygen 1.9.8