2021-05-24 07:25:56 +00:00
/*
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
*/
2011-09-22 17:09:26 +00:00
# include "latexrenderer.h"
2018-08-31 09:23:45 +00:00
# include <QDebug>
2014-10-17 21:30:54 +00:00
2020-07-08 11:54:37 +00:00
# include <KProcess>
2011-09-22 17:09:26 +00:00
2011-09-22 17:11:19 +00:00
# include <QColor>
# include <QDir>
# include <QFileInfo>
# include <QImage>
2020-01-26 11:40:33 +00:00
# include <QRegularExpression>
2014-10-17 21:30:54 +00:00
# include <QStandardPaths>
2020-07-08 11:54:37 +00:00
# include <QTemporaryFile>
2011-09-22 17:11:19 +00:00
# include <QTextStream>
2022-01-26 16:27:42 +00:00
# include "gui/debug_ui.h"
2014-09-11 19:12:27 +00:00
2011-09-22 17:09:26 +00:00
namespace GuiUtils
{
LatexRenderer : : LatexRenderer ( )
{
}
2011-09-22 18:20:53 +00:00
LatexRenderer : : ~ LatexRenderer ( )
{
2019-12-09 13:39:47 +00:00
for ( const QString & file : qAsConst ( m_fileList ) ) {
2011-09-22 18:20:53 +00:00
QFile : : remove ( file ) ;
}
}
2011-09-22 17:09:26 +00:00
LatexRenderer : : Error LatexRenderer : : renderLatexInHtml ( QString & html , const QColor & textColor , int fontSize , int resolution , QString & latexOutput )
{
2015-10-29 12:37:11 +00:00
if ( ! html . contains ( QStringLiteral ( " $$ " ) ) ) {
2011-09-22 17:09:26 +00:00
return NoError ;
2022-03-08 10:10:43 +00:00
}
2011-09-22 17:09:26 +00:00
2020-01-26 11:40:33 +00:00
// this searches for $$formula$$
QRegularExpression rg ( QStringLiteral ( " \\ $ \\ $.+? \\ $ \\ $ " ) ) ;
QRegularExpressionMatchIterator it = rg . globalMatch ( html ) ;
2011-09-22 17:09:26 +00:00
QMap < QString , QString > replaceMap ;
2020-01-26 11:40:33 +00:00
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 ;
2011-09-22 17:09:26 +00:00
}
2020-01-26 11:40:33 +00:00
// 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 ;
2011-09-22 17:09:26 +00:00
}
2020-01-26 11:40:33 +00:00
2011-09-22 17:09:26 +00:00
if ( replaceMap . isEmpty ( ) ) { // we haven't found any LaTeX strings
return NoError ;
2022-03-08 10:10:43 +00:00
}
2020-07-10 22:15:05 +00:00
2011-09-22 17:09:26 +00:00
int imagePxWidth , imagePxHeight ;
for ( QMap < QString , QString > : : ConstIterator it = replaceMap . constBegin ( ) ; it ! = replaceMap . constEnd ( ) ; + + it ) {
QImage theImage ( * it ) ;
if ( theImage . isNull ( ) ) {
continue ;
2022-03-08 10:10:43 +00:00
}
2011-09-22 17:09:26 +00:00
imagePxWidth = theImage . width ( ) ;
imagePxHeight = theImage . height ( ) ;
2016-07-11 20:05:18 +00:00
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 ( " \" /> " ) ) ;
2011-09-22 17:09:26 +00:00
}
return NoError ;
}
bool LatexRenderer : : mightContainLatex ( const QString & text )
{
2015-10-29 12:37:11 +00:00
if ( ! text . contains ( QStringLiteral ( " $$ " ) ) ) {
2011-09-22 17:09:26 +00:00
return false ;
2022-03-08 10:10:43 +00:00
}
2011-09-22 17:09:26 +00:00
// this searches for $$formula$$
2020-01-26 11:40:33 +00:00
QRegularExpression rg ( QStringLiteral ( " \\ $ \\ $.+? \\ $ \\ $ " ) ) ;
if ( ! rg . match ( text ) . hasMatch ( ) ) {
2011-09-22 17:09:26 +00:00
return false ;
2022-03-08 10:10:43 +00:00
}
2011-09-22 17:09:26 +00:00
return true ;
}
LatexRenderer : : Error LatexRenderer : : handleLatex ( QString & fileName , const QString & latexFormula , const QColor & textColor , int fontSize , int resolution , QString & latexOutput )
{
KProcess latexProc ;
KProcess dvipngProc ;
2014-09-17 22:30:39 +00:00
QTemporaryFile * tempFile = new QTemporaryFile ( QDir : : tempPath ( ) + QLatin1String ( " /okular_kdelatex-XXXXXX.tex " ) ) ;
2011-09-22 17:09:26 +00:00
tempFile - > open ( ) ;
QString tempFileName = tempFile - > fileName ( ) ;
QFileInfo * tempFileInfo = new QFileInfo ( tempFileName ) ;
2022-04-02 21:21:33 +00:00
QString tempFileNameNS = tempFileInfo - > absolutePath ( ) + QLatin1Char ( ' / ' ) + tempFileInfo - > baseName ( ) ;
2011-09-22 17:09:26 +00:00
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 ( ) ;
2015-10-29 12:37:11 +00:00
QString latexExecutable = QStandardPaths : : findExecutable ( QStringLiteral ( " latex " ) ) ;
2011-09-22 17:09:26 +00:00
if ( latexExecutable . isEmpty ( ) ) {
2014-09-11 17:36:01 +00:00
qCDebug ( OkularUiDebug ) < < " Could not find latex! " ;
2011-09-22 17:09:26 +00:00
delete tempFile ;
2011-09-22 17:16:03 +00:00
fileName = QString ( ) ;
2011-09-22 17:09:26 +00:00
return LatexNotFound ;
}
2015-10-29 12:37:11 +00:00
latexProc < < latexExecutable < < QStringLiteral ( " -interaction=nonstopmode " ) < < QStringLiteral ( " -halt-on-error " ) < < QStringLiteral ( " -output-directory=%1 " ) . arg ( tempFilePath ) < < tempFile - > fileName ( ) ;
2011-09-22 17:09:26 +00:00
latexProc . setOutputChannelMode ( KProcess : : MergedChannels ) ;
latexProc . execute ( ) ;
2016-07-11 20:05:18 +00:00
latexOutput = QString : : fromLocal8Bit ( latexProc . readAll ( ) ) ;
2011-09-22 17:09:26 +00:00
tempFile - > remove ( ) ;
2015-10-29 12:37:11 +00:00
QFile : : remove ( tempFileNameNS + QStringLiteral ( " .log " ) ) ;
QFile : : remove ( tempFileNameNS + QStringLiteral ( " .aux " ) ) ;
2011-09-22 17:09:26 +00:00
delete tempFile ;
2015-10-29 12:37:11 +00:00
if ( ! QFile : : exists ( tempFileNameNS + QStringLiteral ( " .dvi " ) ) ) {
2011-09-22 17:16:03 +00:00
fileName = QString ( ) ;
2011-09-22 17:09:26 +00:00
return LatexFailed ;
}
2015-10-29 12:37:11 +00:00
QString dvipngExecutable = QStandardPaths : : findExecutable ( QStringLiteral ( " dvipng " ) ) ;
2011-09-22 17:09:26 +00:00
if ( dvipngExecutable . isEmpty ( ) ) {
2014-09-11 17:36:01 +00:00
qCDebug ( OkularUiDebug ) < < " Could not find dvipng! " ;
2011-09-22 17:16:03 +00:00
fileName = QString ( ) ;
2011-09-22 17:09:26 +00:00
return DvipngNotFound ;
}
2015-10-29 12:37:11 +00:00
dvipngProc < < dvipngExecutable < < QStringLiteral ( " -o%1 " ) . arg ( tempFileNameNS + QStringLiteral ( " .png " ) ) < < QStringLiteral ( " -Ttight " ) < < QStringLiteral ( " -bgTransparent " ) < < QStringLiteral ( " -D %1 " ) . arg ( resolution )
< < QStringLiteral ( " %1 " ) . arg ( tempFileNameNS + QStringLiteral ( " .dvi " ) ) ;
2011-09-22 17:09:26 +00:00
dvipngProc . setOutputChannelMode ( KProcess : : MergedChannels ) ;
dvipngProc . execute ( ) ;
2015-10-29 12:37:11 +00:00
QFile : : remove ( tempFileNameNS + QStringLiteral ( " .dvi " ) ) ;
2020-07-10 22:15:05 +00:00
2015-10-29 12:37:11 +00:00
if ( ! QFile : : exists ( tempFileNameNS + QStringLiteral ( " .png " ) ) ) {
2011-09-22 17:16:03 +00:00
fileName = QString ( ) ;
2011-09-22 17:09:26 +00:00
return DvipngFailed ;
}
2015-10-29 12:37:11 +00:00
fileName = tempFileNameNS + QStringLiteral ( " .png " ) ;
2011-09-22 18:20:53 +00:00
m_fileList < < fileName ;
2011-09-22 17:09:26 +00:00
return NoError ;
}
bool LatexRenderer : : securityCheck ( const QString & latexFormula )
{
2020-01-26 11:40:33 +00:00
return ! latexFormula . contains (
QRegularExpression ( QString : : fromLatin1 ( " \\ \\ (def|let|futurelet|newcommand|renewcommand|else|fi|write|input|include "
2011-09-22 17:09:26 +00:00
" |chardef|catcode|makeatletter|noexpand|toksdef|every|errhelp|errorstopmode|scrollmode|nonstopmode|batchmode "
" |read|csname|newhelp|relax|afterground|afterassignment|expandafter|noexpand|special|command|loop|repeat|toks "
2015-10-29 12:37:11 +00:00
" |output|line|mathcode|name|item|section|mbox|DeclareRobustCommand)[^a-zA-Z] " ) ) ) ;
2011-09-22 17:09:26 +00:00
}
2011-09-22 17:11:19 +00:00
}