diff --git a/CMakeLists.txt b/CMakeLists.txt index 165d3a81..09ca9ec0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,130 +1,130 @@ project(cantor) cmake_minimum_required (VERSION 3.5 FATAL_ERROR) set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED ON) # KDE Application Version, managed by release script set (KDE_APPLICATIONS_VERSION_MAJOR "19") set (KDE_APPLICATIONS_VERSION_MINOR "11") set (KDE_APPLICATIONS_VERSION_MICRO "70") set (KDE_APPLICATIONS_VERSION "${KDE_APPLICATIONS_VERSION_MAJOR}.${KDE_APPLICATIONS_VERSION_MINOR}.${KDE_APPLICATIONS_VERSION_MICRO}") set(KF5_MIN_VERSION "5.49.0") find_package(ECM 5.15.0 REQUIRED CONFIG) set(CMAKE_MODULE_PATH ${cantor_SOURCE_DIR}/cmake ${CMAKE_MODULE_PATH} ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR}) find_package(Qt5 5.6.0 CONFIG REQUIRED Core Widgets PrintSupport Svg Xml XmlPatterns Test) find_package(KF5 ${KF5_MIN_VERSION} REQUIRED Config Crash Completion DocTools NewStuff IconThemes TextEditor CoreAddons Archive Parts SyntaxHighlighting TextWidgets KIO XmlGui I18n) -find_package(Poppler "0.73.0" REQUIRED COMPONENTS Qt5) +find_package(Poppler "0.62.0" REQUIRED COMPONENTS Qt5) if(NOT WIN32) find_package(KF5 ${KF5_MIN_VERSION} REQUIRED Pty) endif() include(FeatureSummary) include(ECMInstallIcons) include(ECMSetupVersion) include(KDEInstallDirs) include(KDECompilerSettings NO_POLICY_SCOPE) include(KDECMakeSettings) include(KDEFrameworkCompilerSettings) include(ECMAddAppIcon) include(GenerateExportHeader) include(thirdparty/CMakeLists.txt) if(NOT WIN32) set_package_properties(LibSpectre PROPERTIES DESCRIPTION "A PostScript rendering library" URL "http://libspectre.freedesktop.org/wiki/" TYPE OPTIONAL PURPOSE "Support for rendering EPS files in Cantor") find_package(LibSpectre) if(LIBSPECTRE_FOUND) set(WITH_EPS On) else(LIBSPECTRE_FOUND) set(WITH_EPS Off) endif(LIBSPECTRE_FOUND) else(NOT WIN32) set(WITH_EPS Off) endif(NOT WIN32) #[[ find_package(Discount 2.2.0) set_package_properties(Discount PROPERTIES DESCRIPTION "A C implementation of the Markdown markup language" URL "https://www.pell.portland.or.us/~orc/Code/discount/" TYPE OPTIONAL PURPOSE "Used for Markdown entries in Cantor") ]]# add_definitions(-DQT_USE_FAST_CONCATENATION -DQT_USE_FAST_OPERATOR_PLUS) string(TOLOWER "${CMAKE_BUILD_TYPE}" BUILD_NAME) if (BUILD_NAME STREQUAL "release" OR BUILD_NAME STREQUAL "") add_definitions(-DQT_NO_DEBUG_OUTPUT) endif() kde_enable_exceptions() add_subdirectory(doc) add_subdirectory(src) add_subdirectory(icons) set(CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/Cantor") configure_package_config_file( ${CMAKE_CURRENT_SOURCE_DIR}/CantorConfig.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/CantorConfig.cmake INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR}/ #PATH_VARS INCLUDE_INSTALL_DIR SYSCONFIG_INSTALL_DIR ) ecm_setup_version(${KDE_APPLICATIONS_VERSION} VARIABLE_PREFIX CANTOR VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/cantor_version.h" PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/CantorConfigVersion.cmake" ) install( FILES ${CMAKE_CURRENT_BINARY_DIR}/CantorConfig.cmake ${CMAKE_CURRENT_BINARY_DIR}/CantorConfigVersion.cmake DESTINATION ${CMAKECONFIG_INSTALL_DIR} COMPONENT Devel ) install(EXPORT CantorTargets DESTINATION "${CMAKECONFIG_INSTALL_DIR}" FILE CantorTargets.cmake NAMESPACE Cantor:: ) install(FILES org.kde.cantor.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR}) feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/src/mathrender.cpp b/src/mathrender.cpp index 04431352..e468c9a7 100644 --- a/src/mathrender.cpp +++ b/src/mathrender.cpp @@ -1,87 +1,87 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. --- Copyright (C) 2019 Sirgienko Nikita */ #include "mathrender.h" #include #include #include #include #include "mathrendertask.h" #include "epsrenderer.h" MathRenderer::MathRenderer(): m_scale(1.0), m_useHighRes(false) { qRegisterMetaType>(); } MathRenderer::~MathRenderer() { } bool MathRenderer::mathRenderAvailable() { return QStandardPaths::findExecutable(QLatin1String("pdflatex")).isEmpty() == false; } qreal MathRenderer::scale() { return m_scale; } void MathRenderer::setScale(qreal scale) { m_scale = scale; } void MathRenderer::useHighResolution(bool b) { m_useHighRes = b; } void MathRenderer::renderExpression(const QString& mathExpression, Cantor::LatexRenderer::EquationType type, const QObject* receiver, const char* resultHandler) { - MathRenderTask* task = new MathRenderTask(mathExpression, type, m_scale, m_useHighRes); + MathRenderTask* task = new MathRenderTask(mathExpression, type, m_scale, m_useHighRes, &popplerMutex); task->setHandler(receiver, resultHandler); task->setAutoDelete(false); QThreadPool::globalInstance()->start(task); } void MathRenderer::rerender(QTextDocument* document, const QTextImageFormat& math) { const QString& filename = math.property(EpsRenderer::ImagePath).toString(); if (!QFile::exists(filename)) return; bool success; QString errorMessage; - QImage img = MathRenderTask::renderPdf(filename, m_scale, m_useHighRes, &success, nullptr, &errorMessage); + QImage img = MathRenderTask::renderPdf(filename, m_scale, m_useHighRes, &success, nullptr, &errorMessage, &popplerMutex); if (success) { QUrl internal(math.name()); document->addResource(QTextDocument::ImageResource, internal, QVariant(img)); } else { qDebug() << "Rerender embedded math failed with message: " << errorMessage; } } diff --git a/src/mathrender.h b/src/mathrender.h index 9f6efecc..5baeb233 100644 --- a/src/mathrender.h +++ b/src/mathrender.h @@ -1,72 +1,76 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. --- Copyright (C) 2019 Sirgienko Nikita */ #ifndef MATHRENDER_H #define MATHRENDER_H #include #include +#include #include "lib/latexrenderer.h" /** * Special class for renderning embeded math in MarkdownEntry and TextEntry * Instead of LatexRenderer+EpsRenderer provide all needed functianality in one class * Even if we add some speed optimization in future, API of the class probably won't change */ class MathRenderer : public QObject { Q_OBJECT public: MathRenderer(); ~MathRenderer(); bool mathRenderAvailable(); // Resulution contol void setScale(qreal scale); qreal scale(); void useHighResolution(bool b); /** * This function will run render task in Qt thread pool and * call resultHandler SLOT with MathRenderResult* argument on finish * receiver will be managed about pointer, task only create it */ void renderExpression( const QString& mathExpression, Cantor::LatexRenderer::EquationType type, const QObject *receiver, const char *resultHandler); /** * Rerender renderer math expression in document * Unlike MathRender::renderExpression this method isn't async, because * rerender already rendered math is not long operation */ void rerender(QTextDocument* document, const QTextImageFormat& math); private: double m_scale; bool m_useHighRes; + // We need this, because poppler-qt5 not threadsafe before 0.73.0 and 0.73.0 is too new + // and not common widespread in repositories + QMutex popplerMutex; }; #endif /* MATHRENDER_H */ diff --git a/src/mathrendertask.cpp b/src/mathrendertask.cpp index 7afdd462..f64da95c 100644 --- a/src/mathrendertask.cpp +++ b/src/mathrendertask.cpp @@ -1,233 +1,247 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. --- Copyright (C) 2019 Sirgienko Nikita */ #include "mathrendertask.h" #include #include #include #include #include #include #include +#include #include #include "epsrenderer.h" static const QLatin1String mathTex("\\documentclass{minimal}"\ "\\usepackage{latexsym,amsfonts,amssymb,ulem}"\ "\\usepackage{amsmath}"\ "\\usepackage[dvips]{graphicx}"\ "\\usepackage[utf8]{inputenc}"\ "\\usepackage{xcolor}"\ "\\usepackage[active,displaymath,textmath,tightpage]{preview}"\ "\\setlength\\textwidth{5in}"\ "\\setlength{\\parindent}{0pt}"\ "\\pagecolor[rgb]{%1,%2,%3}"\ "\\pagestyle{empty}"\ "\\begin{document}"\ "\\begin{preview}"\ "\\color[rgb]{%4,%5,%6}"\ "%7"\ "\\end{preview}"\ "\\end{document}"); static const QLatin1String eqnHeader("\\begin{eqnarray*}%1\\end{eqnarray*}"); static const QLatin1String inlineEqnHeader("$%1$"); MathRenderTask::MathRenderTask( const QString& code, Cantor::LatexRenderer::EquationType type, double scale, - bool highResolution - ): m_code(code), m_type(type), m_scale(scale), m_highResolution(highResolution) + bool highResolution, + QMutex* mutex + ): m_code(code), m_type(type), m_scale(scale), m_highResolution(highResolution), m_mutex(mutex) {} void MathRenderTask::setHandler(const QObject* receiver, const char* resultHandler) { connect(this, SIGNAL(finish(QSharedPointer)), receiver, resultHandler); } void MathRenderTask::run() { QSharedPointer result(new MathRenderResult()); const QString& dir=QStandardPaths::writableLocation(QStandardPaths::TempLocation); QTemporaryFile texFile(dir + QDir::separator() + QLatin1String("cantor_tex-XXXXXX.tex")); texFile.open(); KColorScheme scheme(QPalette::Active); const QColor &backgroundColor=scheme.background().color(); const QColor &foregroundColor=scheme.foreground().color(); QString expressionTex=mathTex; expressionTex=expressionTex .arg(backgroundColor.redF()).arg(backgroundColor.greenF()).arg(backgroundColor.blueF()) .arg(foregroundColor.redF()).arg(foregroundColor.greenF()).arg(foregroundColor.blueF()); switch(m_type) { case Cantor::LatexRenderer::FullEquation: expressionTex=expressionTex.arg(eqnHeader); break; case Cantor::LatexRenderer::InlineEquation: expressionTex=expressionTex.arg(inlineEqnHeader); break; } expressionTex=expressionTex.arg(m_code); texFile.write(expressionTex.toUtf8()); texFile.flush(); KProcess p; p.setWorkingDirectory(dir); // Create unique uuid for this job // It will be used as pdf filename, for preventing names collisions // And as internal url path too QString uuid = QUuid::createUuid().toString(); uuid.remove(0, 1); uuid.chop(1); uuid.replace(QLatin1Char('-'), QLatin1Char('_')); const QString& pdflatex = QStandardPaths::findExecutable(QLatin1String("pdflatex")); p << pdflatex << QStringLiteral("-interaction=batchmode") << QStringLiteral("-jobname=cantor_") + uuid << QStringLiteral("-halt-on-error") << texFile.fileName(); p.start(); p.waitForFinished(); if (p.exitCode() != 0) { // pdflatex render failed and we haven't pdf file result->successfull = false; result->errorMessage = QString::fromLatin1("pdflatex failed to render pdf and exit with code %1").arg(p.exitCode()); finalize(result); return; } //Clean up .aux and .log files QString pathWithoutExtention = dir + QDir::separator() + QLatin1String("cantor_")+uuid; QFile::remove(pathWithoutExtention + QLatin1String(".log")); QFile::remove(pathWithoutExtention + QLatin1String(".aux")); const QString& pdfFileName = pathWithoutExtention + QLatin1String(".pdf"); bool success; QString errorMessage; QSizeF size; - result->image = renderPdf(pdfFileName, m_scale, m_highResolution, &success, &size, &errorMessage); + result->image = renderPdf(pdfFileName, m_scale, m_highResolution, &success, &size, &errorMessage, m_mutex); result->successfull = success; result->errorMessage = errorMessage; if (success == false) { finalize(result); return; } QTextImageFormat format; QUrl internal; internal.setScheme(QLatin1String("internal")); internal.setPath(uuid); format.setName(internal.url()); format.setWidth(size.width()); format.setHeight(size.height()); format.setProperty(EpsRenderer::CantorFormula, m_type); format.setProperty(EpsRenderer::ImagePath, pdfFileName); format.setProperty(EpsRenderer::Code, m_code); switch(m_type) { case Cantor::LatexRenderer::FullEquation: format.setProperty(EpsRenderer::Delimiter, QLatin1String("$$")); break; case Cantor::LatexRenderer::InlineEquation: format.setProperty(EpsRenderer::Delimiter, QLatin1String("$")); break; } result->renderedMath = format; result->uniqueUrl = internal; finalize(result); } void MathRenderTask::finalize(QSharedPointer result) { emit finish(result); deleteLater(); } -QImage MathRenderTask::renderPdf(const QString& filename, double scale, bool highResolution, bool* success, QSizeF* size, QString* errorReason) +QImage MathRenderTask::renderPdf(const QString& filename, double scale, bool highResolution, bool* success, QSizeF* size, QString* errorReason, QMutex* mutex) { - QScopedPointer document(Poppler::Document::load(filename)); + if (mutex) + mutex->lock(); + Poppler::Document* document = Poppler::Document::load(filename); + if (mutex) + mutex->unlock(); if (document == nullptr) { if (success) *success = false; if (errorReason) *errorReason = QString::fromLatin1("Poppler library have failed to open file % as pdf").arg(filename); return QImage(); } - QScopedPointer pdfPage(document->page(0)); + Poppler::Page* pdfPage = document->page(0); if (pdfPage == nullptr) { if (success) *success = false; if (errorReason) *errorReason = QString::fromLatin1("Poppler library failed to access first page of %1 document").arg(filename); + delete document; return QImage(); } QSize pageSize = pdfPage->pageSize(); double realSclae; qreal w, h; if(highResolution) { realSclae = 1.2 * 4.8 * 1.8; w = 1.2 * pageSize.width(); h = 1.2 * pageSize.height(); } else { realSclae = 2.3 * scale * 1.8; w = 2.3 * pageSize.width(); h = 2.3 * pageSize.height(); } QImage image = pdfPage->renderToImage(72.0*realSclae, 72.0*realSclae); + delete pdfPage; + if (mutex) + mutex->lock(); + delete document; + if (mutex) + mutex->unlock(); + if (image.isNull()) { if (success) *success = false; if (errorReason) *errorReason = QString::fromLatin1("Poppler library failed to render pdf %1 to image").arg(filename); return image; } // Resize with smooth transformation for more beautiful result image = image.convertToFormat(QImage::Format_ARGB32).scaled(image.size()/1.8, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); if (success) *success = true; if (size) *size = QSizeF(w, h); return image; } diff --git a/src/mathrendertask.h b/src/mathrendertask.h index f44032dc..89927a7f 100644 --- a/src/mathrendertask.h +++ b/src/mathrendertask.h @@ -1,74 +1,78 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. --- Copyright (C) 2019 Sirgienko Nikita */ #ifndef MATHRENDERTASK_H #define MATHRENDERTASK_H #include #include #include #include #include #include #include #include "lib/latexrenderer.h" +class QMutex; + struct MathRenderResult { bool successfull; QString errorMessage; QTextImageFormat renderedMath; QUrl uniqueUrl; QImage image; }; Q_DECLARE_METATYPE(MathRenderResult) Q_DECLARE_METATYPE(QSharedPointer) class MathRenderTask : public QObject, public QRunnable { Q_OBJECT public: MathRenderTask( const QString& code, Cantor::LatexRenderer::EquationType type, double scale, - bool highResolution + bool highResolution, + QMutex* mutex ); void setHandler(const QObject *receiver, const char *resultHandler); void run() override; - static QImage renderPdf(const QString& filename, double scale, bool highResulution, bool* success = nullptr, QSizeF* size = nullptr, QString* errorReason = nullptr); + static QImage renderPdf(const QString& filename, double scale, bool highResulution, bool* success = nullptr, QSizeF* size = nullptr, QString* errorReason = nullptr, QMutex* mutex = nullptr); Q_SIGNALS: void finish(QSharedPointer result); private: void finalize(QSharedPointer result); private: QString m_code; Cantor::LatexRenderer::EquationType m_type; double m_scale; bool m_highResolution; + QMutex* m_mutex; }; #endif /* MATHRENDERTASK_H */