You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
201 lines
7.2 KiB
201 lines
7.2 KiB
/* |
|
SPDX-FileCopyrightText: 2004 Duncan Mac-Vicar Prett <duncan@kde.org> |
|
SPDX-FileCopyrightText: 2004-2005 Olivier Goffart <ogoffart@kde.org> |
|
SPDX-FileCopyrightText: 2011 Niels Ole Salscheider |
|
<niels_ole@salscheider-online.de> |
|
|
|
SPDX-License-Identifier: GPL-2.0-or-later |
|
*/ |
|
|
|
#include "latexrenderer.h" |
|
|
|
#include <QDebug> |
|
|
|
#include <KProcess> |
|
|
|
#include <QColor> |
|
#include <QDir> |
|
#include <QFileInfo> |
|
#include <QImage> |
|
#include <QRegularExpression> |
|
#include <QStandardPaths> |
|
#include <QTemporaryFile> |
|
#include <QTextStream> |
|
|
|
#include "gui/debug_ui.h" |
|
|
|
namespace GuiUtils |
|
{ |
|
LatexRenderer::LatexRenderer() |
|
{ |
|
} |
|
|
|
LatexRenderer::~LatexRenderer() |
|
{ |
|
for (const QString &file : qAsConst(m_fileList)) { |
|
QFile::remove(file); |
|
} |
|
} |
|
|
|
LatexRenderer::Error LatexRenderer::renderLatexInHtml(QString &html, const QColor &textColor, int fontSize, int resolution, QString &latexOutput) |
|
{ |
|
if (!html.contains(QStringLiteral("$$"))) { |
|
return NoError; |
|
} |
|
|
|
// this searches for $$formula$$ |
|
QRegularExpression rg(QStringLiteral("\\$\\$.+?\\$\\$")); |
|
QRegularExpressionMatchIterator it = rg.globalMatch(html); |
|
|
|
QMap<QString, QString> replaceMap; |
|
while (it.hasNext()) { |
|
QRegularExpressionMatch match = it.next(); |
|
const QString matchedString = match.captured(0); |
|
|
|
QString formul = matchedString; |
|
// first remove the $$ delimiters on start and end |
|
formul.remove(QStringLiteral("$$")); |
|
// then trim the result, so we can skip totally empty/whitespace-only formulas |
|
formul = formul.trimmed(); |
|
if (formul.isEmpty() || !securityCheck(formul)) { |
|
continue; |
|
} |
|
|
|
// unescape formula |
|
formul.replace(QLatin1String(">"), QLatin1String(">")); |
|
formul.replace(QLatin1String("<"), QLatin1String("<")); |
|
formul.replace(QLatin1String("&"), QLatin1String("&")); |
|
formul.replace(QLatin1String("""), QLatin1String("\"")); |
|
formul.replace(QLatin1String("'"), QLatin1String("\'")); |
|
formul.replace(QLatin1String("<br>"), QLatin1String(" ")); |
|
|
|
QString fileName; |
|
Error returnCode = handleLatex(fileName, formul, textColor, fontSize, resolution, latexOutput); |
|
if (returnCode != NoError) { |
|
return returnCode; |
|
} |
|
|
|
replaceMap[matchedString] = fileName; |
|
} |
|
|
|
if (replaceMap.isEmpty()) { // we haven't found any LaTeX strings |
|
return NoError; |
|
} |
|
|
|
int imagePxWidth, imagePxHeight; |
|
for (QMap<QString, QString>::ConstIterator it = replaceMap.constBegin(); it != replaceMap.constEnd(); ++it) { |
|
QImage theImage(*it); |
|
if (theImage.isNull()) { |
|
continue; |
|
} |
|
imagePxWidth = theImage.width(); |
|
imagePxHeight = theImage.height(); |
|
QString escapedLATEX = it.key().toHtmlEscaped().replace(QLatin1Char('"'), QLatin1String(""")); // we need the escape quotes because that string will be in a title="" argument, but not the \n |
|
html.replace(it.key(), |
|
QStringLiteral(" <img width=\"") + QString::number(imagePxWidth) + QStringLiteral("\" height=\"") + QString::number(imagePxHeight) + QStringLiteral("\" align=\"middle\" src=\"") + (*it) + QStringLiteral("\" alt=\"") + |
|
escapedLATEX + QStringLiteral("\" title=\"") + escapedLATEX + QStringLiteral("\" /> ")); |
|
} |
|
return NoError; |
|
} |
|
|
|
bool LatexRenderer::mightContainLatex(const QString &text) |
|
{ |
|
if (!text.contains(QStringLiteral("$$"))) { |
|
return false; |
|
} |
|
|
|
// this searches for $$formula$$ |
|
QRegularExpression rg(QStringLiteral("\\$\\$.+?\\$\\$")); |
|
if (!rg.match(text).hasMatch()) { |
|
return false; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
LatexRenderer::Error LatexRenderer::handleLatex(QString &fileName, const QString &latexFormula, const QColor &textColor, int fontSize, int resolution, QString &latexOutput) |
|
{ |
|
KProcess latexProc; |
|
KProcess dvipngProc; |
|
|
|
QTemporaryFile *tempFile = new QTemporaryFile(QDir::tempPath() + QLatin1String("/okular_kdelatex-XXXXXX.tex")); |
|
tempFile->open(); |
|
QString tempFileName = tempFile->fileName(); |
|
QFileInfo *tempFileInfo = new QFileInfo(tempFileName); |
|
QString tempFileNameNS = tempFileInfo->absolutePath() + '/' + tempFileInfo->baseName(); |
|
QString tempFilePath = tempFileInfo->absolutePath(); |
|
delete tempFileInfo; |
|
QTextStream tempStream(tempFile); |
|
|
|
tempStream << "\ |
|
\\documentclass[" |
|
<< fontSize << "pt]{article} \ |
|
\\usepackage{color} \ |
|
\\usepackage{amsmath,latexsym,amsfonts,amssymb,ulem} \ |
|
\\pagestyle{empty} \ |
|
\\begin{document} \ |
|
{\\color[rgb]{" << textColor.redF() |
|
<< "," << textColor.greenF() << "," << textColor.blueF() << "} \ |
|
\\begin{eqnarray*} \ |
|
" << latexFormula |
|
<< " \ |
|
\\end{eqnarray*}} \ |
|
\\end{document}"; |
|
|
|
tempFile->close(); |
|
QString latexExecutable = QStandardPaths::findExecutable(QStringLiteral("latex")); |
|
if (latexExecutable.isEmpty()) { |
|
qCDebug(OkularUiDebug) << "Could not find latex!"; |
|
delete tempFile; |
|
fileName = QString(); |
|
return LatexNotFound; |
|
} |
|
latexProc << latexExecutable << QStringLiteral("-interaction=nonstopmode") << QStringLiteral("-halt-on-error") << QStringLiteral("-output-directory=%1").arg(tempFilePath) << tempFile->fileName(); |
|
latexProc.setOutputChannelMode(KProcess::MergedChannels); |
|
latexProc.execute(); |
|
latexOutput = QString::fromLocal8Bit(latexProc.readAll()); |
|
tempFile->remove(); |
|
|
|
QFile::remove(tempFileNameNS + QStringLiteral(".log")); |
|
QFile::remove(tempFileNameNS + QStringLiteral(".aux")); |
|
delete tempFile; |
|
|
|
if (!QFile::exists(tempFileNameNS + QStringLiteral(".dvi"))) { |
|
fileName = QString(); |
|
return LatexFailed; |
|
} |
|
|
|
QString dvipngExecutable = QStandardPaths::findExecutable(QStringLiteral("dvipng")); |
|
if (dvipngExecutable.isEmpty()) { |
|
qCDebug(OkularUiDebug) << "Could not find dvipng!"; |
|
fileName = QString(); |
|
return DvipngNotFound; |
|
} |
|
|
|
dvipngProc << dvipngExecutable << QStringLiteral("-o%1").arg(tempFileNameNS + QStringLiteral(".png")) << QStringLiteral("-Ttight") << QStringLiteral("-bgTransparent") << QStringLiteral("-D %1").arg(resolution) |
|
<< QStringLiteral("%1").arg(tempFileNameNS + QStringLiteral(".dvi")); |
|
dvipngProc.setOutputChannelMode(KProcess::MergedChannels); |
|
dvipngProc.execute(); |
|
|
|
QFile::remove(tempFileNameNS + QStringLiteral(".dvi")); |
|
|
|
if (!QFile::exists(tempFileNameNS + QStringLiteral(".png"))) { |
|
fileName = QString(); |
|
return DvipngFailed; |
|
} |
|
|
|
fileName = tempFileNameNS + QStringLiteral(".png"); |
|
m_fileList << fileName; |
|
return NoError; |
|
} |
|
|
|
bool LatexRenderer::securityCheck(const QString &latexFormula) |
|
{ |
|
return !latexFormula.contains( |
|
QRegularExpression(QString::fromLatin1("\\\\(def|let|futurelet|newcommand|renewcommand|else|fi|write|input|include" |
|
"|chardef|catcode|makeatletter|noexpand|toksdef|every|errhelp|errorstopmode|scrollmode|nonstopmode|batchmode" |
|
"|read|csname|newhelp|relax|afterground|afterassignment|expandafter|noexpand|special|command|loop|repeat|toks" |
|
"|output|line|mathcode|name|item|section|mbox|DeclareRobustCommand)[^a-zA-Z]"))); |
|
} |
|
|
|
}
|
|
|