File indexing completed on 2024-04-28 11:20:47

0001 /*
0002     SPDX-License-Identifier: GPL-2.0-or-later
0003     SPDX-FileCopyrightText: 2011 Alexander Rieder <alexanderrieder@gmail.com>
0004 */
0005 
0006 #include "latexrenderer.h"
0007 using namespace Cantor;
0008 
0009 #include <QProcess>
0010 #include <QDebug>
0011 #include <QDir>
0012 #include <QFileInfo>
0013 #include <QEventLoop>
0014 #include <QTemporaryFile>
0015 #include <KColorScheme>
0016 #include <QUuid>
0017 #include <QApplication>
0018 
0019 #include <config-cantorlib.h>
0020 #include "settings.h"
0021 
0022 class Cantor::LatexRendererPrivate
0023 {
0024   public:
0025     QString latexCode;
0026     QString header;
0027     LatexRenderer::Method method;
0028     bool isEquationOnly;
0029     LatexRenderer::EquationType equationType;
0030     QString errorMessage;
0031     bool success;
0032     QString latexFilename;
0033     QString epsFilename;
0034     QString uuid;
0035     QTemporaryFile* texFile;
0036 };
0037 
0038 static const QLatin1String tex("\\documentclass[fleqn]{article}"\
0039                          "\\usepackage{latexsym,amsfonts,amssymb,ulem}"\
0040                          "\\usepackage{amsmath}"\
0041                          "\\usepackage[dvips]{graphicx}"\
0042                          "\\usepackage[utf8]{inputenc}"\
0043                          "\\usepackage{xcolor}"\
0044                          "\\setlength\\textwidth{5in}"\
0045                          "\\setlength{\\parindent}{0pt}"\
0046                          "%1"\
0047                          "\\pagecolor[rgb]{%2,%3,%4}"\
0048                          "\\pagestyle{empty}"\
0049                          "\\begin{document}"\
0050                          "\\color[rgb]{%5,%6,%7}"\
0051                          "\\fontsize{%8}{%8}\\selectfont\n"\
0052                          "%9\n"\
0053                          "\\end{document}");
0054 
0055 static const QLatin1String eqnHeader("\\begin{eqnarray*}%1\\end{eqnarray*}");
0056 static const QLatin1String inlineEqnHeader("$%1$");
0057 
0058 LatexRenderer::LatexRenderer(QObject* parent) : QObject(parent),
0059                                                 d(new LatexRendererPrivate)
0060 {
0061     d->method=LatexMethod;
0062     d->isEquationOnly=false;
0063     d->equationType=InlineEquation;
0064     d->success=false;
0065     d->texFile=nullptr;
0066 }
0067 
0068 LatexRenderer::~LatexRenderer()
0069 {
0070     delete d;
0071 }
0072 
0073 QString LatexRenderer::latexCode() const
0074 {
0075     return d->latexCode;
0076 }
0077 
0078 void LatexRenderer::setLatexCode(const QString& src)
0079 {
0080     d->latexCode=src;
0081 }
0082 
0083 QString LatexRenderer::header() const
0084 {
0085     return d->header;
0086 }
0087 
0088 void LatexRenderer::addHeader(const QString& header)
0089 {
0090     d->header.append(header);
0091 }
0092 
0093 void LatexRenderer::setHeader(const QString& header)
0094 {
0095     d->header=header;
0096 }
0097 
0098 LatexRenderer::Method LatexRenderer::method() const
0099 {
0100     return d->method;
0101 }
0102 
0103 void LatexRenderer::setMethod(LatexRenderer::Method method)
0104 {
0105     d->method=method;
0106 }
0107 
0108 void LatexRenderer::setEquationType(LatexRenderer::EquationType type)
0109 {
0110     d->equationType=type;
0111 }
0112 
0113 LatexRenderer::EquationType LatexRenderer::equationType() const
0114 {
0115     return d->equationType;
0116 }
0117 
0118 
0119 void LatexRenderer::setErrorMessage(const QString& msg)
0120 {
0121     d->errorMessage=msg;
0122 }
0123 
0124 QString LatexRenderer::errorMessage() const
0125 {
0126     return d->errorMessage;
0127 }
0128 
0129 bool LatexRenderer::renderingSuccessful() const
0130 {
0131     return d->success;
0132 }
0133 
0134 void LatexRenderer::setEquationOnly(bool isEquationOnly)
0135 {
0136     d->isEquationOnly=isEquationOnly;
0137 }
0138 
0139 bool LatexRenderer::isEquationOnly() const
0140 {
0141     return d->isEquationOnly;
0142 }
0143 
0144 
0145 QString LatexRenderer::imagePath() const
0146 {
0147     return d->epsFilename;
0148 }
0149 
0150 QString Cantor::LatexRenderer::uuid() const
0151 {
0152     return d->uuid;
0153 }
0154 
0155 bool LatexRenderer::render()
0156 {
0157     switch(d->method)
0158     {
0159         case LatexRenderer::LatexMethod:
0160             return renderWithLatex();
0161 
0162         case LatexRenderer::MmlMethod:
0163             return renderWithMml();
0164 
0165         default:
0166             return false;
0167     };
0168 }
0169 
0170 void LatexRenderer::renderBlocking()
0171 {
0172     QEventLoop event;
0173     connect(this, &LatexRenderer::done, &event, &QEventLoop::quit);
0174     connect(this, &LatexRenderer::error, &event, &QEventLoop::quit);
0175 
0176     bool success = render();
0177     // We can't emit error before running event loop, so exit by passing false as an error indicator
0178     if (success)
0179         event.exec();
0180     else
0181         return;
0182 }
0183 
0184 bool LatexRenderer::renderWithLatex()
0185 {
0186     qDebug()<<"rendering using latex method";
0187     QString dir=QStandardPaths::writableLocation(QStandardPaths::TempLocation);
0188 
0189     if (d->texFile)
0190         delete d->texFile;
0191 
0192     d->texFile=new QTemporaryFile(dir + QDir::separator() + QLatin1String("cantor_tex-XXXXXX.tex"));
0193     d->texFile->open();
0194 
0195     KColorScheme scheme(QPalette::Active);
0196     const QColor backgroundColor=scheme.background().color();
0197     const QColor foregroundColor=scheme.foreground().color();
0198     QString expressionTex=tex;
0199     expressionTex=expressionTex.arg(d->header)
0200                                .arg(backgroundColor.redF()).arg(backgroundColor.greenF()).arg(backgroundColor.blueF())
0201                                .arg(foregroundColor.redF()).arg(foregroundColor.greenF()).arg(foregroundColor.blueF());
0202 
0203     int fontPointSize = QApplication::font().pointSize();
0204     expressionTex=expressionTex.arg(fontPointSize);
0205 
0206     if(isEquationOnly())
0207     {
0208         switch(equationType())
0209         {
0210             case FullEquation: expressionTex=expressionTex.arg(eqnHeader); break;
0211             case InlineEquation: expressionTex=expressionTex.arg(inlineEqnHeader); break;
0212             case CustomEquation: expressionTex=expressionTex.arg(QLatin1String("%1")); break;
0213         }
0214     }
0215     expressionTex=expressionTex.arg(d->latexCode);
0216 
0217     // qDebug()<<"full tex:\n"<<expressionTex;
0218 
0219     d->texFile->write(expressionTex.toUtf8());
0220     d->texFile->flush();
0221 
0222     QString fileName = d->texFile->fileName();
0223     qDebug()<<"fileName: "<<fileName;
0224     d->latexFilename=fileName;
0225     QProcess *p=new QProcess( this );
0226     p->setWorkingDirectory(dir);
0227 
0228     d->uuid = genUuid();
0229 
0230     qDebug() << Settings::self()->latexCommand();
0231     QFileInfo info(Settings::self()->latexCommand());
0232     if (info.exists() && info.isExecutable())
0233     {
0234         p->setProgram(Settings::self()->latexCommand());
0235         p->setArguments({QStringLiteral("-jobname=cantor_") + d->uuid, QStringLiteral("-halt-on-error"), fileName});
0236 
0237         connect(p, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(convertToPs()) );
0238         p->start();
0239         return true;
0240     }
0241     else
0242     {
0243         setErrorMessage(QStringLiteral("failed to find latex executable"));
0244         return false;
0245     }
0246 }
0247 
0248 void LatexRenderer::convertToPs()
0249 {
0250     const QString& dir=QStandardPaths::writableLocation(QStandardPaths::TempLocation);
0251 
0252     QString dviFile = dir + QDir::separator() + QStringLiteral("cantor_") + d->uuid + QStringLiteral(".dvi");
0253     d->epsFilename = dir + QDir::separator() + QLatin1String("cantor_")+d->uuid+QLatin1String(".eps");
0254 
0255     QProcess *p=new QProcess( this );
0256     qDebug()<<"converting to eps: "<<Settings::self()->dvipsCommand()<<"-E"<<"-o"<<d->epsFilename<<dviFile;
0257 
0258     QFileInfo info(Settings::self()->dvipsCommand());
0259     if (info.exists() && info.isExecutable())
0260     {
0261         p->setProgram(Settings::self()->dvipsCommand());
0262         p->setArguments({QStringLiteral("-E"), QStringLiteral("-q"), QStringLiteral("-o"), d->epsFilename, dviFile});
0263 
0264         connect(p, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(convertingDone()) );
0265         p->start();
0266     }
0267     else
0268     {
0269         setErrorMessage(QStringLiteral("failed to find dvips executable"));
0270         emit error();
0271     }
0272 }
0273 
0274 void LatexRenderer::convertingDone()
0275 {
0276     QFileInfo info(d->epsFilename);
0277     qDebug() <<"remove temporary files for " << d->latexFilename;
0278 
0279     QString pathWithoutExtension = info.path() + QDir::separator() + info.completeBaseName();
0280     QFile::remove(pathWithoutExtension + QLatin1String(".log"));
0281     QFile::remove(pathWithoutExtension + QLatin1String(".aux"));
0282     QFile::remove(pathWithoutExtension + QLatin1String(".dvi"));
0283 
0284     if(info.exists())
0285     {
0286         delete d->texFile;
0287         d->texFile = nullptr;
0288 
0289         d->success=true;
0290         emit done();
0291     }
0292     else
0293     {
0294         d->success=false;
0295         setErrorMessage(QStringLiteral("failed to create the latex preview image"));
0296         emit error();
0297     }
0298 }
0299 
0300 bool LatexRenderer::renderWithMml()
0301 {
0302     qWarning()<<"WARNING: MML rendering not implemented yet!";
0303     emit error();
0304     return false;
0305 }
0306 
0307 QString LatexRenderer::genUuid()
0308 {
0309     QString uuid = QUuid::createUuid().toString();
0310     uuid.remove(0, 1);
0311     uuid.chop(1);
0312     uuid.replace(QLatin1Char('-'), QLatin1Char('_'));
0313     return uuid;
0314 }
0315 
0316 bool Cantor::LatexRenderer::isLatexAvailable()
0317 {
0318     QFileInfo infoLatex(Settings::self()->latexCommand());
0319     QFileInfo infoPs(Settings::self()->dvipsCommand());
0320     return infoLatex.exists() && infoLatex.isExecutable() && infoPs.exists() && infoPs.isExecutable();
0321 }