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

0001 /*
0002     SPDX-License-Identifier: GPL-2.0-or-later
0003     SPDX-FileCopyrightText: 2019 Sirgienko Nikita <warquark@gmail.com>
0004 */
0005 
0006 #include "mathrendertask.h"
0007 
0008 #include <QTemporaryFile>
0009 #include <QStandardPaths>
0010 #include <QUuid>
0011 #include <QDir>
0012 #include <KColorScheme>
0013 #include <KProcess>
0014 #include <QScopedPointer>
0015 #include <QApplication>
0016 #include <QDebug>
0017 
0018 #include "lib/renderer.h"
0019 
0020 static const QLatin1String mathTex("\\documentclass%9{minimal}"\
0021                          "\\usepackage{amsfonts,amssymb}"\
0022                          "\\usepackage{amsmath}"\
0023                          "\\usepackage[utf8]{inputenc}"\
0024                          "\\usepackage[active,displaymath,textmath,tightpage]{preview}"\
0025                          "\\usepackage{color}"\
0026                          "\\setlength\\PreviewBorder{0pt}"\
0027                          "\\begin{document}"\
0028                          "\\begin{preview}"\
0029                          "\\setlength{\\fboxsep}{0.2pt}"\
0030                          "$"\
0031                          "\\colorbox[rgb]{%1,%2,%3}{"\
0032                          "\\color[rgb]{%4,%5,%6}"\
0033                          "\\fontsize{%7}{%7}\\selectfont"\
0034                          "%8}"\
0035                          "$"\
0036                          "\\end{preview}"
0037                          "\\end{document}");
0038 
0039 static const QLatin1String eqnHeader("$\\displaystyle %1$");
0040 static const QLatin1String inlineEqnHeader("$%1$");
0041 
0042 MathRenderTask::MathRenderTask(
0043         int jobId,
0044         const QString& code,
0045         Cantor::LatexRenderer::EquationType type,
0046         double scale,
0047         bool highResolution
0048     ): m_jobId(jobId), m_code(code), m_type(type), m_scale(scale), m_highResolution(highResolution)
0049 {
0050 
0051     KColorScheme scheme(QPalette::Active);
0052     m_backgroundColor = scheme.background().color();
0053     m_foregroundColor = scheme.foreground().color();
0054 }
0055 
0056 void MathRenderTask::setHandler(const QObject* receiver, const char* resultHandler)
0057 {
0058     connect(this, SIGNAL(finish(QSharedPointer<MathRenderResult>)), receiver, resultHandler);
0059 }
0060 
0061 void MathRenderTask::run()
0062 {
0063     qDebug()<<"MathRenderTask::run " << m_jobId;
0064     QSharedPointer<MathRenderResult> result(new MathRenderResult());
0065 
0066     const QString& tempDir=QStandardPaths::writableLocation(QStandardPaths::TempLocation);
0067 
0068     QTemporaryFile texFile(tempDir + QDir::separator() + QLatin1String("cantor_tex-XXXXXX.tex"));
0069     texFile.open();
0070 
0071     // make sure we have preview.sty available
0072     if (!tempDir.contains(QLatin1String("preview.sty")))
0073     {
0074         QString file = QStandardPaths::locate(QStandardPaths::AppDataLocation, QLatin1String("latex/preview.sty"));
0075 
0076         if (file.isEmpty())
0077             file = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("cantor/latex/preview.sty"));
0078 
0079         if (file.isEmpty())
0080         {
0081             result->successful = false;
0082             result->errorMessage = QString::fromLatin1("LaTeX style file preview.sty not found.");
0083             finalize(result);
0084             return;
0085         }
0086         else
0087             QFile::copy(file, tempDir + QDir::separator() + QLatin1String("preview.sty"));
0088     }
0089     QString expressionTex=mathTex;
0090 
0091     expressionTex=expressionTex
0092                             .arg(m_backgroundColor.redF()).arg(m_backgroundColor.greenF()).arg(m_backgroundColor.blueF())
0093                             .arg(m_foregroundColor.redF()).arg(m_foregroundColor.greenF()).arg(m_foregroundColor.blueF());
0094     int fontPointSize = QApplication::font().pointSize();
0095     expressionTex=expressionTex.arg(fontPointSize);
0096 
0097     switch(m_type)
0098     {
0099         case Cantor::LatexRenderer::FullEquation:
0100             expressionTex=expressionTex.arg(eqnHeader, QString());
0101             break;
0102         case Cantor::LatexRenderer::InlineEquation:
0103             expressionTex=expressionTex.arg(inlineEqnHeader, QString());
0104             break;
0105         case Cantor::LatexRenderer::CustomEquation:
0106             expressionTex=expressionTex.arg(QLatin1String("%1"), QLatin1String("[preview]"));
0107             break;
0108     }
0109 
0110     QString latex = m_code;
0111     // Looks hacky, but no sure, how do it better without overhead (like new latex type in lib/latexrender)
0112     static const QString& equationBegin = QLatin1String("\\begin{equation}");
0113     static const QString& equationEnd = QLatin1String("\\end{equation}");
0114     if (latex.startsWith(equationBegin) && latex.endsWith(equationEnd))
0115     {
0116         latex.remove(0, equationBegin.size());
0117         latex.chop(equationEnd.size());
0118         latex = QLatin1String("\\begin{equation*}") + latex + QLatin1String("\\end{equation*}");
0119     }
0120 
0121     expressionTex=expressionTex.arg(latex);
0122 
0123     texFile.write(expressionTex.toUtf8());
0124     texFile.flush();
0125 
0126     QProcess p;
0127     p.setWorkingDirectory(tempDir);
0128 
0129     // Create unique uuid for this job
0130     // It will be used as pdf filename, for preventing names collisions
0131     // And as internal url path too
0132     const QString& uuid = Cantor::LatexRenderer::genUuid();
0133 
0134     const QString& pdflatex = QStandardPaths::findExecutable(QLatin1String("pdflatex"));
0135     p.setProgram(pdflatex);
0136     p.setArguments({QStringLiteral("-jobname=cantor_") + uuid, QStringLiteral("-halt-on-error"), texFile.fileName()});
0137 
0138     p.start();
0139     p.waitForFinished();
0140 
0141     if (p.exitCode() != 0)
0142     {
0143         // pdflatex render failed and we haven't pdf file
0144         result->successful = false;
0145 
0146         QString renderErrorText = QString::fromUtf8(p.readAllStandardOutput());
0147         renderErrorText.remove(0, renderErrorText.indexOf(QLatin1Char('!')));
0148         renderErrorText.remove(renderErrorText.indexOf(QLatin1String("!  ==> Fatal error occurred")), renderErrorText.size());
0149         renderErrorText = renderErrorText.trimmed();
0150         result->errorMessage = renderErrorText;
0151 
0152         finalize(result);
0153         texFile.setAutoRemove(false); //Useful for debug
0154         return;
0155     }
0156 
0157     //Clean up .aux and .log files
0158     QString pathWithoutExtension = tempDir + QDir::separator() + QLatin1String("cantor_")+uuid;
0159     QFile::remove(pathWithoutExtension + QLatin1String(".log"));
0160     QFile::remove(pathWithoutExtension + QLatin1String(".aux"));
0161 
0162     // We shouldn't remove pdf file, because this file used in future in an another parts of Cantor
0163     // For example, this pdf will copied into .cws file on save
0164     const QString& pdfFileName = pathWithoutExtension + QLatin1String(".pdf");
0165 
0166     bool success; QString errorMessage;
0167     const auto& data = renderPdfToFormat(pdfFileName, m_code, uuid, m_type, m_scale, m_highResolution, &success, &errorMessage);
0168     result->successful = success;
0169     result->errorMessage = errorMessage;
0170     if (success == false)
0171     {
0172         finalize(result);
0173         return;
0174     }
0175 
0176     result->renderedMath = data.first;
0177     result->image = data.second;
0178     result->jobId = m_jobId;
0179 
0180     QUrl internal;
0181     internal.setScheme(QLatin1String("internal"));
0182     internal.setPath(uuid);
0183     result->uniqueUrl = internal;
0184 
0185     finalize(result);
0186 }
0187 
0188 void MathRenderTask::finalize(QSharedPointer<MathRenderResult> result)
0189 {
0190     emit finish(result);
0191     deleteLater();
0192 }
0193 
0194 std::pair<QTextImageFormat, QImage> MathRenderTask::renderPdfToFormat(const QString& filename, const QString& code, const QString uuid, Cantor::LatexRenderer::EquationType type, double scale, bool highResulution, bool* success, QString* errorReason)
0195 {
0196     QSizeF size;
0197     const QImage& image = Cantor::Renderer::pdfRenderToImage(QUrl::fromLocalFile(filename), scale, highResulution, &size, errorReason);
0198     if (success)
0199         *success = image.isNull() == false;
0200 
0201     if (success && *success == false)
0202         return std::make_pair(QTextImageFormat(), QImage());
0203 
0204     QTextImageFormat format;
0205 
0206     QUrl internal;
0207     internal.setScheme(QLatin1String("internal"));
0208     internal.setPath(uuid);
0209 
0210     format.setName(internal.url());
0211     format.setWidth(size.width());
0212     format.setHeight(size.height());
0213     format.setProperty(Cantor::Renderer::CantorFormula, type);
0214     format.setProperty(Cantor::Renderer::ImagePath, filename);
0215     format.setProperty(Cantor::Renderer::Code, code);
0216     format.setVerticalAlignment(QTextCharFormat::AlignBaseline);
0217 
0218     switch(type)
0219     {
0220         case Cantor::LatexRenderer::FullEquation:
0221             format.setProperty(Cantor::Renderer::Delimiter, QLatin1String("$$"));
0222             break;
0223 
0224         case Cantor::LatexRenderer::InlineEquation:
0225             format.setProperty(Cantor::Renderer::Delimiter, QLatin1String("$"));
0226             break;
0227 
0228         case Cantor::LatexRenderer::CustomEquation:
0229             format.setProperty(Cantor::Renderer::Delimiter, QLatin1String(""));
0230             break;
0231     }
0232 
0233     return std::make_pair(std::move(format), std::move(image));
0234 }