[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