diff --git a/org.kde.cantor.appdata.xml b/org.kde.cantor.appdata.xml index 2ba8d30c..a0d5b5a3 100644 --- a/org.kde.cantor.appdata.xml +++ b/org.kde.cantor.appdata.xml @@ -1,126 +1,131 @@ org.kde.cantor.desktop CC0-1.0 GPL-2.0+ Cantor Cantor Cantor Cantor Cantor Cantor Cantor Cantor Cantor Cantor Cantor Cantor Cantor Cantor Cantor Cantor Cantor Cantor Cantor Cantor Cantor Cantor Cantor Cantor Cantor Cantor xxCantorxx Cantor Cantor KDE Frontend to Mathematical Software Frontal del KDE per a programari matemàtic Frontal del KDE per a programari matemàtic Uživatelské rozhraní matematického softwaru pro KDE Eine KDE-Oberfläche für mathematische Software KDE Frontend to Mathematical Software Interfaz de KDE para software matemático KDE matemaatikatarkvara kasutajaliides KDE:n käyttöliittymä matemaattisille ohjelmistoille Interface KDE pour les logiciels de mathématiques Interface de KDE de software matemático Frontend KDE untuk Software Matematika Interfaccia KDE per software matematico KDE-Böversiet för Rekenprogrammen KDE-frontend tot wiskundige software KDE-grensesnitt til matematikkprogram Nakładka KDE dla oprogramowania matematycznego Interface para KDE de Aplicações Matemáticas Interface do KDE para software matemático Оболочка к системам компьютерной алгебры для KDE KDE Frontend pre matematický softvér KDE-jevo začelje za matematično programsko opremo KDE-gränssnitt till matematisk programvara Matematiksel Yazılım için bir KDE Önyüzü. Графічна оболонка KDE до математичного програмного забезпечення xxKDE Frontend to Mathematical Softwarexx 数学软件的 KDE 前端 KDE 數學軟體的前端介面

Cantor is a front-end to powerful mathematics and statistics packages. Cantor integrates them into the KDE Platform and provides a nice, worksheet-based, graphical user interface. It supports environments for KAlgebra, Lua, Maxima, R, Sage, Octave, Python, Scilab, and Qalculate!

El Cantor és un frontal a paquets matemàtics i estadístics potents. El Cantor els integra en la plataforma del KDE i proporciona una interfície gràfica d'usuari agradable i basada en fulls de càlcul. Admet entorns pel KAlgebra, el Lua, el Maxima, el R, el Sage, l'Octave, el Python, l'Scilab, i el Qalculate.

El Cantor és un frontal a paquets matemàtics i estadístics potents. El Cantor els integra en la plataforma del KDE i proporciona una interfície gràfica d'usuari agradable i basada en fulls de càlcul. Admet entorns pel KAlgebra, el Lua, el Maxima, el R, el Sage, l'Octave, el Python, l'Scilab, i el Qalculate.

Cantor ist ein Bedienoberfläche zu leistungsfähigen Mathematik- und Statistik-Paketen. Cantor integriert sie in die KDE-Plattform und bietet eine grafische Benutzeroberfläche auf der Grundlage von Arbeitsblättern. Es unterstützt Umgebungen für KAlgebra, Lua, Maxima, R, Sage, Octave, Python, Scilab, und Qalculate!

Cantor is a front-end to powerful mathematics and statistics packages. Cantor integrates them into the KDE Platform and provides a nice, worksheet-based, graphical user interface. It supports environments for KAlgebra, Lua, Maxima, R, Sage, Octave, Python, Scilab, and Qalculate!

Cantor es una interfaz para potentes paquetes matemáticos y estadísticos. Cantor los integra en la Plataforma de KDE y proporciona una elegante interfaz de usuario gráfica basada en hojas de trabajo. Permite usar entornos para KAlgebra, Lua, Maxima, R, Sage, Octave, Python, Scilab y Qalculate!

Cantor on võimsate matemaatika- ja statistikapakettide kasutajaliides. Cantor lõimib nad KDE platvormi ja pakub neile kena, töölehepõhise graafilise kasutajaliidese. Toetatud on KAlgebra, Lua, Maxima, R-i, Sage, Octave'i, Pythoni, Scilabi ja Qalculate! keskkonnad.

Cantor on käyttöliittymä tehokkaille matematiikka- ja tilastolaskentapaketeille. Cantor integroi ne KDE-ympäristöön ja tarjoaa mukavan työkirjapohjaisen graafisen käyttöliittymän. Cantor tukee seuraavia ympäristöjä: KAlgebra, Lua, Maxima, R, Sage, Octave, Python, Scilab, ja Qalculate!

Cantor est une interface pour des outils mathématiques puissants. Il permet d'intégrer ces outils au sein de la plate-forme KDE et fournit une interface graphique agréable. Il prend en charge les environnements pour KAlgebra, Lua, Maxima, R, Sage, Octave, Python, Scilab and Qalculate !

Cantor é unha interface gráfica para potentes paquetes de matemática e estatística. Cantor intégraos na plataforma de KDE e fornece unha boa interface gráfica de usuario baseada en follas de cálculo. É compatíbel com ambientes para KAlgebra, Lua, Maxima, R, Sage, Octave, Python, Scilab, e Qalculate!

Cantor adalah ujung depan (frontend) paket matematika dan statistik yang unggul. Cantor mengintegrasikan mereka ke dalam Platform KDE dan menyediakan antarmuka pengguna grafis yang bagus, berbasis lembar-kerja (worksheet). Ini mendukung lingkungan untuk KAlgebra, Lua, Maxima, R, Sage, Oktaf, Python, Scilab, dan Qalculate!

Cantor è un'interfaccia a potenti pacchetti di matematica e statistica. Cantor li integra nella piattaforma di KDE e fornisce una gradevole interfaccia grafica basata su fogli di lavoro. Supporta KAlgebra, Lua, Maxima, R, Sage, Octave, Python, Scilab e Qalculate!

Cantor is en Böversiet för en Reeg deegt Mathematik- un Statistik-Paketen. Cantor integreert de na KDE un stellt en smuck graafsche Böversiet mit Arbeitblääd praat. Dat ünnerstütt Ümgeven för KAlgebra, Lua, Maxima, R, Sage, Octave, Python, Scilab, un Qalculate!

Cantor is een zogenaamd frontend tussen de gebruiker en krachtige pakketten voor wiskunde en statistiek. Cantor integreert die in het KDE-platform en is hiervoor een geschikte grafische tussenlaag (interface) op basis van een rekenblad (spreadsheet). Ondersteund worden omgevingen voor KAlgebra, Lua, Maxima, R, Sage, Octave, Python, Scilab, and Qalculate!

Cantor jest nakładką na zaawansowane pakiety matematyczne i statystyczne. Cantor integruje je z Platformą KDE i dostarcza przyjemnego, opartego na arkuszach roboczych, graficznego układu sterowania. Obsługuje środowiska takie jak: KAlgebra, Lua, Maxima, R, Sage, Octave, Python, Scilab oraz Qalculate!

O Cantor é uma interface para diversos pacotes poderosos de matemática e estatística. O Cantor integra-os na Plataforma do KDE e oferece uma interface gráfica agradável e baseada em folhas de trabalho. Suporta ambientes para o KAlgebra, Lua, Maxima, R, Sage, Octave, Python, Scilab e Qalculate!

Cantor é uma interface para os pacotes de matemática e estatísticas poderosa. O Cantor integra-as na plataforma do KDE e oferece uma interface gráfica agradável e baseada numa folha de trabalho. Ele tem suporte aos ambientes para KAlgebra, Lua, Maxima, R, Sage, Octave, Python, Scilab e Qalculate!

Cantor — программа для удобной работы с математическими и статистическими пакетами. Обладает графическим интерфейсом, позволяющим параллельно работать с несколькими документами. Поддерживаются системы компьютерной алгебры и языки KAlgebra, Lua, Maxima, R, Sage, Octave, Python, Scilab и Qalculate!

Cantor je frontend pre mocné matematické a štatistické balíky. Integruje ich do KDE platformy a poskytuje pekné, listovo založené grafické používateľské prostredie. Podporuje prostredia pre KAlgebra, Lua, Maxima, R, Sage, Octave, Python, Scilab a Qalculate!

Cantor je začelje za zmogljive matematične in statistične pakete. Cantor jih vgradi v okolje KDE in ponuja lep grafični uporabniški vmesnik, ki temelji na preglednicah. Podpira okolja za KAlgebra, Lua, Maxima, R, Sage, Octave, Python, Scilab, in Qalculate!

Cantor är ett gränssnitt till kraftfulla matematiska och statistiska paket. Cantor integrerar dem med KDE:s plattform och tillhandahåller ett snyggt, arbetsbladsbaserat, grafiskt användargränssnitt. Det stödjer miljöer för Kalgebra, Lua, Maxima, R, Sage, Octave, Python, Scilab och Qalculate!.

Cantor güçlü matematik ve istatistik paketleri için bir önyüzdür. Cantor onları KDE Platformunda tümleştirir ve güzel, çalışma sayfası temelli bir grafik arayüz sağlar. KAlgebra, Lua, Maxima, R, Sage, Octave, Python, Scilab, ve Qalculate gibi ortamları destekler.

Cantor> — графічна оболонка до потужних програм обробки математичних та статистичних даних. Cantor інтегрує ці програми до платформи KDE і надає користувачами зручний заснований на обчислювальних аркушах графічний інтерфейс. Передбачено підтримку роботи у режимі оболонок до KAlgebra, Lua, Maxima, R, Sage, Octave, Python, Scilab та Qalculate!

xxCantor is a front-end to powerful mathematics and statistics packages. Cantor integrates them into the KDE Platform and provides a nice, worksheet-based, graphical user interface. It supports environments for KAlgebra, Lua, Maxima, R, Sage, Octave, Python, Scilab, and Qalculate!xx

Cantor 是一个强大的数学和统计学工具包的前端。Cantor 将它们集成到 KDE 平台并提供了美观,基于工作表的图形用户界面。它支持 KAlgebra,Lua,Maxima,R,Sage,Octave,Python,Scilab 和 Qalculate 环境!

Cantor 是一套強大的數學與統計軟體套件的前端介面。Cantor 會將它們整合到 KDE 並提供一套工作表式的圖形介面。它之援 KAlgebra, Lua, Maxima, R, Sage, Octave, Python, Scilab 與 Qalculate 的環境。

https://cantor.kde.org/ https://bugs.kde.org/enter_bug.cgi?format=guided&product=cantor https://docs.kde.org/stable/en/kdeedu/cantor/index.html https://www.kde.org/community/donations/?app=cantor&source=appdata Using Maxima backend of Cantor Usant el dorsal Maxima del Cantor Usant el dorsal Maxima del Cantor Použití podpůrné vrstvy Cantoru Maxima Maxima-Modul in Cantor Using Maxima backend of Cantor Usando el motor Maxima de Cantor Cantori kasutamine Maxima taustaprogrammiga Utilisation du moteur Maxima de Cantor Usando a infraestrutura de Maxima de Cantor Menggunakan Maxima backend Cantor Usare il backend Maxima di Cantor De Maxima-backend van Cantor gebruiken Używa silnika Maxima Cantora Uso da infra-estrutura de Maxima do Cantor Usando a infraestrutura Maxima do Cantor Používa sa Maxima backend z Cantor Användning av bakgrundsprogrammet Maxima i Cantor Використання модуля Maxima Cantor xxUsing Maxima backend of Cantorxx 使用 Cantor 的 Maxima 后端 使用 Cantor 的 Maxima 後端 https://cdn.kde.org/screenshots/cantor/cantor.png KDE cantor + + + + +
diff --git a/src/backends/sage/sagesession.cpp b/src/backends/sage/sagesession.cpp index f3db4a38..fb650c5d 100644 --- a/src/backends/sage/sagesession.cpp +++ b/src/backends/sage/sagesession.cpp @@ -1,528 +1,533 @@ /* 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) 2009 Alexander Rieder */ #include "sagesession.h" #include "sageexpression.h" #include "sagecompletionobject.h" #include "sagehighlighter.h" #include #include #include #include #include #include #include "settings.h" #ifndef Q_OS_WIN #include #endif const QByteArray SageSession::SagePrompt="sage: "; //Text, sage outputs after each command const QByteArray SageSession::SageAlternativePrompt="....: "; //Text, sage outputs when it expects further input //some commands that are run after login static QByteArray initCmd="os.environ['PAGER'] = 'cat' \n "\ "sage.misc.pager.EMBEDDED_MODE = True \n "\ "sage.misc.viewer.BROWSER='' \n "\ "sage.misc.viewer.viewer.png_viewer('false') \n" \ "sage.plot.plot3d.base.SHOW_DEFAULTS['viewer'] = 'tachyon' \n"\ "sage.misc.latex.EMBEDDED_MODE = True \n "\ "os.environ['PAGER'] = 'cat' \n "\ "%colors nocolor \n "\ - "print '____TMP_DIR____', sage.misc.misc.SAGE_TMP\n"; + "print('%s %s' % ('____TMP_DIR____', sage.misc.misc.SAGE_TMP))\n"; static QByteArray newInitCmd= "__CANTOR_IPYTHON_SHELL__=get_ipython() \n "\ "__CANTOR_IPYTHON_SHELL__.autoindent=False\n "; static QByteArray legacyInitCmd= "__CANTOR_IPYTHON_SHELL__=__IPYTHON__ \n " \ "__CANTOR_IPYTHON_SHELL__.autoindent=False\n "; -static QByteArray endOfInitMarker="print '____END_OF_INIT____'\n "; +static QByteArray endOfInitMarker="print('____END_OF_INIT____')\n "; SageSession::VersionInfo::VersionInfo(int major, int minor) { m_major=major; m_minor=minor; } int SageSession::VersionInfo::majorVersion() const { return m_major; } int SageSession::VersionInfo::minorVersion() const { return m_minor; } bool SageSession::VersionInfo::operator==(VersionInfo other) const { return m_major==other.m_major&&m_minor==other.m_minor; } bool SageSession::VersionInfo::operator<(VersionInfo other) const { return (m_major!= -1 && other.m_major==-1) || ( ((m_major!=-1 && other.m_major!=-1) || (m_major==other.m_major && m_major==-1) ) && ( m_major(SageSession::VersionInfo other) const { return !( (*this <= other )); } bool SageSession::VersionInfo::operator>=(SageSession::VersionInfo other) const { return !( *this < other); } SageSession::SageSession(Cantor::Backend* backend) : Session(backend), m_process(nullptr), m_isInitialized(false), m_waitingForPrompt(false), m_haveSentInitCmd(false) { connect( &m_dirWatch, SIGNAL(created(QString)), this, SLOT(fileCreated(QString)) ); } SageSession::~SageSession() { if (m_process) { m_process->kill(); m_process->deleteLater(); m_process = nullptr; } } void SageSession::login() { qDebug()<<"login"; if (m_process) return; emit loginStarted(); m_process=new KPtyProcess(this); updateSageVersion(); const QString& sageExecFile = SageSettings::self()->path().toLocalFile(); if (false) // Reenable when https://trac.sagemath.org/ticket/25363 is merged // if (m_sageVersion >= SageSession::VersionInfo(8, 4)) m_process->setProgram(sageExecFile, QStringList() << QLatin1String("--simple-prompt")); else { const QString& sageStartScript = locateCantorFile(QLatin1String("sagebackend/cantor-execsage")); m_process->setProgram(sageStartScript, QStringList(sageExecFile)); } m_process->setOutputChannelMode(KProcess::SeparateChannels); m_process->setPtyChannels(KPtyProcess::AllChannels); m_process->pty()->setEcho(false); connect(m_process->pty(), SIGNAL(readyRead()), this, SLOT(readStdOut())); connect(m_process, SIGNAL(readyReadStandardError()), this, SLOT(readStdErr())); connect(m_process, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(processFinished(int,QProcess::ExitStatus))); connect(m_process, SIGNAL(error(QProcess::ProcessError)), this, SLOT(reportProcessError(QProcess::ProcessError))); m_process->start(); m_process->waitForStarted(); m_process->pty()->write(initCmd); + //save the path to the worksheet as variable "__file__" + //this variable is usually set by the "os" package when running a script + //but when it is run in an interpreter (like sage server) it is not set + if (!m_worksheetPath.isEmpty()) + { + const QString cmd = QLatin1String("__file__ = '%1'"); + evaluateExpression(cmd.arg(m_worksheetPath), Cantor::Expression::DeleteOnFinish, true); + } + if(!SageSettings::self()->autorunScripts().isEmpty()){ QString autorunScripts = SageSettings::self()->autorunScripts().join(QLatin1String("\n")); evaluateExpression(autorunScripts, SageExpression::DeleteOnFinish, true); } changeStatus(Session::Done); emit loginDone(); } void SageSession::logout() { qDebug()<<"logout"; if (!m_process) return; if(status() == Cantor::Session::Running) interrupt(); disconnect(m_process, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(processFinished(int,QProcess::ExitStatus))); m_process->pty()->write("exit\n"); if(!m_process->waitForFinished(1000)) m_process->kill(); m_process->deleteLater(); m_process = nullptr; //Run sage-cleaner to kill all the orphans KProcess::startDetached(SageSettings::self()->path().toLocalFile(),QStringList()<setFinishingBehavior(behave); expr->setCommand(cmd); expr->evaluate(); return expr; } void SageSession::readStdOut() { m_outputCache.append(QString::fromUtf8(m_process->pty()->readAll())); qDebug()<<"out: "<= SageSession::VersionInfo(7,4)) { const QString message = i18n( "Sage version %1.%2 is unsupported. Please update your installation "\ "to the supported versions to make it work with Cantor.", m_sageVersion.majorVersion(), m_sageVersion.minorVersion()); KMessageBox::error(nullptr, message, i18n("Cantor")); interrupt(); logout(); } else if(m_sageVersion<=SageSession::VersionInfo(5, 7)) { qDebug()<<"using an old version of sage: "<pty()->write(legacyInitCmd); defineCustomFunctions(); m_process->pty()->write(endOfInitMarker); m_haveSentInitCmd=true; } } else { qDebug()<<"using the current set of commands"; if(!m_haveSentInitCmd) { m_process->pty()->write(newInitCmd); defineCustomFunctions(); m_process->pty()->write(endOfInitMarker); m_haveSentInitCmd=true; } } } else { const QString message = i18n( "Failed to determine the version of Sage. Please check your installation and the output of 'sage -v'."); KMessageBox::error(nullptr, message, i18n("Cantor")); interrupt(); logout(); } } int indexOfEOI=m_outputCache.indexOf(QLatin1String("____END_OF_INIT____")); if(indexOfEOI!=-1&&m_outputCache.indexOf(QLatin1String(SagePrompt), indexOfEOI)!=-1) { qDebug()<<"initialized"; //out.remove("____END_OF_INIT____"); //out.remove(SagePrompt); m_isInitialized=true; m_waitingForPrompt=false; runFirstExpression(); m_outputCache.clear(); } //If we are waiting for another prompt, drop every output //until a prompt is found if(m_isInitialized&&m_waitingForPrompt) { qDebug()<<"waiting for prompt"; if(m_outputCache.contains(QLatin1String(SagePrompt))) m_waitingForPrompt=false; m_outputCache.clear(); return; } if(m_isInitialized) { if (!expressionQueue().isEmpty()) { SageExpression* expr = static_cast(expressionQueue().first()); expr->parseOutput(m_outputCache); } m_outputCache.clear(); } } void SageSession::readStdErr() { qDebug()<<"reading stdErr"; QString out=QLatin1String(m_process->readAllStandardError()); qDebug()<<"err: "<(expressionQueue().first()); expr->parseError(out); } } void SageSession::currentExpressionChangedStatus(Cantor::Expression::Status status) { switch (status) { case Cantor::Expression::Done: case Cantor::Expression::Error: finishFirstExpression(); default: break; } } void SageSession::processFinished(int exitCode, QProcess::ExitStatus exitStatus) { Q_UNUSED(exitCode); if(exitStatus==QProcess::CrashExit) { if(!expressionQueue().isEmpty()) { static_cast(expressionQueue().last()) ->onProcessError(i18n("The Sage process crashed while evaluating this expression")); }else { //We don't have an actual command. it crashed for some other reason, just show a plain error message box KMessageBox::error(nullptr, i18n("The Sage process crashed"), i18n("Cantor")); } }else { if(!expressionQueue().isEmpty()) { static_cast(expressionQueue().last()) ->onProcessError(i18n("The Sage process exited while evaluating this expression")); }else { //We don't have an actual command. it crashed for some other reason, just show a plain error message box KMessageBox::error(nullptr, i18n("The Sage process exited"), i18n("Cantor")); } } } void SageSession::reportProcessError(QProcess::ProcessError e) { if(e==QProcess::FailedToStart) { changeStatus(Cantor::Session::Done); emit error(i18n("Failed to start Sage")); } } void SageSession::runFirstExpression() { if(!expressionQueue().isEmpty()) { SageExpression* expr = static_cast(expressionQueue().first()); if (m_isInitialized) { connect(expr, SIGNAL(statusChanged(Cantor::Expression::Status)), this, SLOT(currentExpressionChangedStatus(Cantor::Expression::Status))); QString command=expr->command(); if(command.endsWith(QLatin1Char('?')) && !command.endsWith(QLatin1String("??"))) command=QLatin1String("help(")+command.left(command.size()-1)+QLatin1Char(')'); if(command.startsWith(QLatin1Char('?'))) command=QLatin1String("help(")+command.mid(1)+QLatin1Char(')'); command.append(QLatin1String("\n\n")); qDebug()<<"writing "<setStatus(Cantor::Expression::Computing); m_process->pty()->write(command.toUtf8()); } else if (expressionQueue().size() == 1) // If queue contains one expression, it means, what we run this expression immediately (drop setting queued status) // TODO: Sage login is slow, so, maybe better mark this expression as queued for a login time expr->setStatus(Cantor::Expression::Queued); } } void SageSession::interrupt() { if(!expressionQueue().isEmpty()) { qDebug()<<"interrupting " << expressionQueue().first()->command(); if(m_process && m_process->state() != QProcess::NotRunning) { #ifndef Q_OS_WIN const int pid=m_process->pid(); kill(pid, SIGINT); #else ; //TODO: interrupt the process on windows #endif } foreach (Cantor::Expression* expression, expressionQueue()) expression->setStatus(Cantor::Expression::Interrupted); expressionQueue().clear(); m_outputCache.clear(); qDebug()<<"done interrupting"; } changeStatus(Cantor::Session::Done); } void SageSession::sendInputToProcess(const QString& input) { m_process->pty()->write(input.toUtf8()); } void SageSession::fileCreated( const QString& path ) { qDebug()<<"got a file "<(expressionQueue().first()); if ( expr ) expr->addFileResult( path ); } } void SageSession::setTypesettingEnabled(bool enable) { Cantor::Session::setTypesettingEnabled(enable); // We have problems with Sage latex output (generates invalid code sometimes), so disable sage // latex output until this not be solved. Users can enable sage latex by hands using %display // sage magic. //tell the sage server to enable/disable pretty_print //const QString cmd=QLatin1String("__cantor_enable_typesetting(%1)"); //evaluateExpression(cmd.arg(enable ? QLatin1String("true"):QLatin1String("false")), Cantor::Expression::DeleteOnFinish); } void SageSession::setWorksheetPath(const QString& path) { - //save the path to the worksheet as variable "__file__" - //this variable is usually set by the "os" package when running a script - //but when it is run in an interpreter (like sage server) it is not set - const QString cmd = QLatin1String("__file__ = '%1'"); - evaluateExpression(cmd.arg(path), Cantor::Expression::DeleteOnFinish, true); + m_worksheetPath = path; } Cantor::CompletionObject* SageSession::completionFor(const QString& command, int index) { return new SageCompletionObject(command, index, this); } QSyntaxHighlighter* SageSession::syntaxHighlighter(QObject* parent) { return new SageHighlighter(parent); } SageSession::VersionInfo SageSession::sageVersion() { return m_sageVersion; } void SageSession::defineCustomFunctions() { //typesetting QString cmd=QLatin1String("def __cantor_enable_typesetting(enable):\n"); if(m_sageVersion VersionInfo(5, 7) && m_sageVersion< VersionInfo(5, 12)) { cmd+=QLatin1String("\t sage.misc.latex.pretty_print_default(enable)\n\n"); }else { cmd+=QLatin1String("\t if(enable==true):\n "\ "\t \t %display typeset \n"\ "\t else: \n" \ "\t \t %display simple \n\n"); } sendInputToProcess(cmd); } bool SageSession::updateSageVersion() { QProcess get_sage_version; get_sage_version.setProgram(SageSettings::self()->path().toLocalFile()); get_sage_version.setArguments(QStringList()< */ #ifndef _SAGESESSION_H #define _SAGESESSION_H #include "session.h" #include "expression.h" #include #include class SageExpression; class KPtyProcess; class SageSession : public Cantor::Session { Q_OBJECT public: static const QByteArray SagePrompt; static const QByteArray SageAlternativePrompt; //small helper class to deal with sage versions //Note: major version -1 is treated as most current class VersionInfo{ public: explicit VersionInfo(int major = -1, int minor = -1); //bool operator <=(VersionInfo v2); bool operator <(VersionInfo other) const; bool operator <=(VersionInfo other) const; bool operator >(VersionInfo other) const; bool operator >=(VersionInfo other) const; bool operator ==( VersionInfo other) const; // These are not called major() and minor() because some libc's have // macros with those names. int majorVersion() const; int minorVersion() const; private: int m_major; int m_minor; }; explicit SageSession( Cantor::Backend* backend); ~SageSession() override; void login() override; void logout() override; Cantor::Expression* evaluateExpression(const QString& command,Cantor::Expression::FinishingBehavior behave = Cantor::Expression::FinishingBehavior::DoNotDelete, bool internal = false) override; void runFirstExpression() override; void interrupt() override; void sendInputToProcess(const QString& input); void setTypesettingEnabled(bool enable) override; void setWorksheetPath(const QString& path) override; Cantor::CompletionObject* completionFor(const QString& command, int index=-1) override; QSyntaxHighlighter* syntaxHighlighter(QObject* parent) override; VersionInfo sageVersion(); public Q_SLOTS: void readStdOut(); void readStdErr(); private Q_SLOTS: void currentExpressionChangedStatus(Cantor::Expression::Status); void processFinished(int exitCode, QProcess::ExitStatus); void reportProcessError(QProcess::ProcessError); void fileCreated(const QString& path); private: void defineCustomFunctions(); bool updateSageVersion(); private: KPtyProcess* m_process; bool m_isInitialized; QString m_tmpPath; KDirWatch m_dirWatch; bool m_waitingForPrompt; QString m_outputCache; VersionInfo m_sageVersion; bool m_haveSentInitCmd; + QString m_worksheetPath; }; #endif /* _SAGESESSION_H */ diff --git a/src/latexentry.cpp b/src/latexentry.cpp index 467249af..a1966523 100644 --- a/src/latexentry.cpp +++ b/src/latexentry.cpp @@ -1,571 +1,587 @@ /* 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) 2009 Alexander Rieder Copyright (C) 2012 Martin Kuettler */ #include "latexentry.h" #include "worksheetentry.h" #include "worksheet.h" #include "lib/renderer.h" #include "lib/jupyterutils.h" #include "lib/defaulthighlighter.h" #include "lib/latexrenderer.h" #include "config-cantor.h" #include #include #include #include #include #include #include #include #include #include LatexEntry::LatexEntry(Worksheet* worksheet) : WorksheetEntry(worksheet), m_textItem(new WorksheetTextItem(this, Qt::TextEditorInteraction)) { m_textItem->installEventFilter(this); connect(m_textItem, &WorksheetTextItem::moveToPrevious, this, &LatexEntry::moveToPreviousEntry); connect(m_textItem, &WorksheetTextItem::moveToNext, this, &LatexEntry::moveToNextEntry); connect(m_textItem, SIGNAL(execute()), this, SLOT(evaluate())); } void LatexEntry::populateMenu(QMenu* menu, QPointF pos) { bool imageSelected = false; QTextCursor cursor = m_textItem->textCursor(); const QChar repl = QChar::ObjectReplacementCharacter; if (cursor.hasSelection()) { QString selection = m_textItem->textCursor().selectedText(); imageSelected = selection.contains(repl); } else { // we need to try both the current cursor and the one after the that cursor = m_textItem->cursorForPosition(pos); for (int i = 2; i; --i) { int p = cursor.position(); if (m_textItem->document()->characterAt(p-1) == repl && cursor.charFormat().hasProperty(Cantor::Renderer::CantorFormula)) { m_textItem->setTextCursor(cursor); imageSelected = true; break; } cursor.movePosition(QTextCursor::NextCharacter); } } if (imageSelected) { menu->addAction(i18n("Show LaTeX code"), this, SLOT(resolveImagesAtCursor())); menu->addSeparator(); } WorksheetEntry::populateMenu(menu, pos); } int LatexEntry::type() const { return Type; } bool LatexEntry::isEmpty() { return m_textItem->document()->isEmpty(); } bool LatexEntry::acceptRichText() { return false; } bool LatexEntry::focusEntry(int pos, qreal xCoord) { if (aboutToBeRemoved()) return false; m_textItem->setFocusAt(pos, xCoord); return true; } void LatexEntry::setContent(const QString& content) { m_latex = content; m_textItem->setPlainText(m_latex); } void LatexEntry::setContent(const QDomElement& content, const KZip& file) { m_latex = content.text(); qDebug() << m_latex; m_textItem->document()->clear(); QTextCursor cursor = m_textItem->textCursor(); cursor.movePosition(QTextCursor::Start); QString imagePath; bool useLatexCode = true; if(content.hasAttribute(QLatin1String("filename"))) { const KArchiveEntry* imageEntry=file.directory()->entry(content.attribute(QLatin1String("filename"))); if (imageEntry&&imageEntry->isFile()) { const KArchiveFile* imageFile=static_cast(imageEntry); const QString& dir=QStandardPaths::writableLocation(QStandardPaths::TempLocation); imageFile->copyTo(dir); imagePath = dir + QDir::separator() + imageFile->name(); #ifdef LIBSPECTRE_FOUND QString uuid = Cantor::LatexRenderer::genUuid(); m_renderedFormat = worksheet()->renderer()->render(m_textItem->document(), Cantor::Renderer::EPS, QUrl::fromLocalFile(imagePath), uuid); qDebug()<<"rendering successful? " << !m_renderedFormat.name().isEmpty(); m_renderedFormat.setProperty(Cantor::Renderer::CantorFormula, Cantor::Renderer::LatexFormula); m_renderedFormat.setProperty(Cantor::Renderer::ImagePath, imagePath); m_renderedFormat.setProperty(Cantor::Renderer::Code, m_latex); cursor.insertText(QString(QChar::ObjectReplacementCharacter), m_renderedFormat); useLatexCode = false; m_textItem->denyEditing(); #endif } } if (useLatexCode && content.hasAttribute(QLatin1String("image"))) { const QByteArray& ba = QByteArray::fromBase64(content.attribute(QLatin1String("image")).toLatin1()); QImage image; if (image.loadFromData(ba)) { // Create unique internal url for this loaded image QUrl internal; internal.setScheme(QLatin1String("internal")); internal.setPath(QUuid::createUuid().toString()); m_textItem->document()->addResource(QTextDocument::ImageResource, internal, QVariant(image)); m_renderedFormat.setName(internal.url()); m_renderedFormat.setWidth(image.width()); m_renderedFormat.setHeight(image.height()); m_renderedFormat.setProperty(Cantor::Renderer::CantorFormula, Cantor::Renderer::LatexFormula); if (!imagePath.isEmpty()) m_renderedFormat.setProperty(Cantor::Renderer::ImagePath, imagePath); m_renderedFormat.setProperty(Cantor::Renderer::Code, m_latex); cursor.insertText(QString(QChar::ObjectReplacementCharacter), m_renderedFormat); useLatexCode = false; m_textItem->denyEditing(); } } if (useLatexCode) cursor.insertText(m_latex); } void LatexEntry::setContentFromJupyter(const QJsonObject& cell) { if (!Cantor::JupyterUtils::isCodeCell(cell)) return; m_textItem->document()->clear(); QTextCursor cursor = m_textItem->textCursor(); cursor.movePosition(QTextCursor::Start); bool useLatexCode = true; QString source = Cantor::JupyterUtils::getSource(cell); m_latex = source.remove(QLatin1String("%%latex\n")); QJsonArray outputs = cell.value(Cantor::JupyterUtils::outputsKey).toArray(); if (outputs.size() == 1 && Cantor::JupyterUtils::isJupyterDisplayOutput(outputs[0])) { const QJsonObject data = outputs[0].toObject().value(Cantor::JupyterUtils::dataKey).toObject(); const QImage& image = Cantor::JupyterUtils::loadImage(data, Cantor::JupyterUtils::pngMime); if (!image.isNull()) { QUrl internal; internal.setScheme(QLatin1String("internal")); internal.setPath(QUuid::createUuid().toString()); m_textItem->document()->addResource(QTextDocument::ImageResource, internal, QVariant(image)); m_renderedFormat.setName(internal.url()); m_renderedFormat.setWidth(image.width()); m_renderedFormat.setHeight(image.height()); m_renderedFormat.setProperty(Cantor::Renderer::CantorFormula, Cantor::Renderer::LatexFormula); m_renderedFormat.setProperty(Cantor::Renderer::Code, m_latex); cursor.insertText(QString(QChar::ObjectReplacementCharacter), m_renderedFormat); useLatexCode = false; m_textItem->denyEditing(); } } if (useLatexCode) { cursor.insertText(m_latex); m_latex.clear(); // We don't render image, so clear latex code cache } } QJsonValue LatexEntry::toJupyterJson() { QJsonObject entry; entry.insert(Cantor::JupyterUtils::cellTypeKey, QLatin1String("code")); entry.insert(Cantor::JupyterUtils::executionCountKey, QJsonValue()); QJsonObject metadata, cantorMetadata; cantorMetadata.insert(QLatin1String("latex_entry"), true); metadata.insert(Cantor::JupyterUtils::cantorMetadataKey, cantorMetadata); entry.insert(Cantor::JupyterUtils::metadataKey, metadata); QJsonArray outputs; QTextCursor cursor = m_textItem->document()->find(QString(QChar::ObjectReplacementCharacter)); if (!cursor.isNull()) { QTextImageFormat format=cursor.charFormat().toImageFormat(); QUrl internal; internal.setUrl(format.name()); const QImage& image = m_textItem->document()->resource(QTextDocument::ImageResource, internal).value(); if (!image.isNull()) { QByteArray ba; QBuffer buffer(&ba); buffer.open(QIODevice::WriteOnly); image.save(&buffer, "PNG"); // Add image result with latex rendered image to this Jupyter code cell QJsonObject imageResult; imageResult.insert(Cantor::JupyterUtils::outputTypeKey, QLatin1String("display_data")); QJsonObject data; data.insert(Cantor::JupyterUtils::pngMime, Cantor::JupyterUtils::toJupyterMultiline(QString::fromLatin1(ba.toBase64()))); imageResult.insert(QLatin1String("data"), data); imageResult.insert(Cantor::JupyterUtils::metadataKey, QJsonObject()); outputs.append(imageResult); } } entry.insert(Cantor::JupyterUtils::outputsKey, outputs); const QString& latex = latexCode(); Cantor::JupyterUtils::setSource(entry, QLatin1String("%%latex\n") + latex); return entry; } QDomElement LatexEntry::toXml(QDomDocument& doc, KZip* archive) { QDomElement el = doc.createElement(QLatin1String("Latex")); el.appendChild( doc.createTextNode( latexCode() )); QTextCursor cursor = m_textItem->document()->find(QString(QChar::ObjectReplacementCharacter)); if (!cursor.isNull()) { QTextImageFormat format=cursor.charFormat().toImageFormat(); QString fileName = format.property(Cantor::Renderer::ImagePath).toString(); // Check, if eps file exists, and if not true, rerender latex code bool isEpsFileExists = QFile::exists(fileName); #ifdef LIBSPECTRE_FOUND if (!isEpsFileExists && renderLatexCode()) { cursor = m_textItem->document()->find(QString(QChar::ObjectReplacementCharacter)); format=cursor.charFormat().toImageFormat(); fileName = format.property(Cantor::Renderer::ImagePath).toString(); isEpsFileExists = QFile::exists(fileName); } #endif if (isEpsFileExists && archive) { const QUrl& url=QUrl::fromLocalFile(fileName); archive->addLocalFile(url.toLocalFile(), url.fileName()); el.setAttribute(QLatin1String("filename"), url.fileName()); } // Save also rendered QImage, if exist. QUrl internal; internal.setUrl(format.name()); const QImage& image = m_textItem->document()->resource(QTextDocument::ImageResource, internal).value(); if (!image.isNull()) { QByteArray ba; QBuffer buffer(&ba); buffer.open(QIODevice::WriteOnly); image.save(&buffer, "PNG"); el.setAttribute(QLatin1String("image"), QString::fromLatin1(ba.toBase64())); } } return el; } QString LatexEntry::toPlain(const QString& commandSep, const QString& commentStartingSeq, const QString& commentEndingSeq) { Q_UNUSED(commandSep); if (commentStartingSeq.isEmpty()) return QString(); QString text = latexCode(); if (!commentEndingSeq.isEmpty()) return commentStartingSeq + text + commentEndingSeq + QLatin1String("\n"); return commentStartingSeq + text.replace(QLatin1String("\n"), QLatin1String("\n") + commentStartingSeq) + QLatin1String("\n"); } void LatexEntry::interruptEvaluation() { } bool LatexEntry::evaluate(EvaluationOption evalOp) { bool success = false; if (isOneImageOnly()) { success = true; } else { if (m_latex == latexCode()) { bool renderWasSuccessful = !m_renderedFormat.name().isEmpty(); if (renderWasSuccessful) { QTextCursor cursor = m_textItem->textCursor(); cursor.movePosition(QTextCursor::Start); cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor); cursor.insertText(QString(QChar::ObjectReplacementCharacter), m_renderedFormat); m_textItem->denyEditing(); } else { success = renderLatexCode(); } } else { m_latex = latexCode(); success = renderLatexCode(); } } qDebug()<<"rendering successful? "<document()->find(QString(QChar::ObjectReplacementCharacter)); while (!cursor.isNull()) { qDebug()<<"found a formula... rendering the eps..."; QTextImageFormat format=cursor.charFormat().toImageFormat(); const QUrl& url=QUrl::fromLocalFile(format.property(Cantor::Renderer::ImagePath).toString()); QSizeF s = worksheet()->renderer()->renderToResource(m_textItem->document(), Cantor::Renderer::EPS, url, QUrl(format.name())); qDebug()<<"rendering successful? "<< s.isValid(); cursor.movePosition(QTextCursor::NextCharacter); cursor = m_textItem->document()->find(QString(QChar::ObjectReplacementCharacter), cursor); } } bool LatexEntry::eventFilter(QObject* object, QEvent* event) { - if(object == m_textItem && event->type() == QEvent::GraphicsSceneMouseDoubleClick) + if(object == m_textItem) { - // One image if we have rendered entry - if (isOneImageOnly()) + if (event->type() == QEvent::GraphicsSceneMouseDoubleClick) { - QTextCursor cursor = m_textItem->textCursor(); - if (!cursor.hasSelection()) - cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor); + // One image if we have rendered entry + if (isOneImageOnly()) + { + QTextCursor cursor = m_textItem->textCursor(); + if (!cursor.hasSelection()) + cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor); - cursor.insertText(m_textItem->resolveImages(cursor)); - m_textItem->allowEditing(); - return true; + cursor.insertText(m_textItem->resolveImages(cursor)); + m_textItem->allowEditing(); + return true; + } + } + else if (event->type() == QEvent::KeyPress) + { + auto* key_event = static_cast(event); + if (key_event->matches(QKeySequence::Cancel)) + { + QTextCursor cursor = m_textItem->textCursor(); + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor); + cursor.insertText(QString(QChar::ObjectReplacementCharacter), m_renderedFormat); + m_textItem->denyEditing(); + return true; + } } } return false; } QString LatexEntry::latexCode() { QTextCursor cursor = m_textItem->textCursor(); cursor.movePosition(QTextCursor::Start); cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor); QString code = m_textItem->resolveImages(cursor); code.replace(QChar::ParagraphSeparator, QLatin1Char('\n')); //Replace the U+2029 paragraph break by a Normal Newline code.replace(QChar::LineSeparator, QLatin1Char('\n')); //Replace the line break by a Normal Newline return code; } bool LatexEntry::isOneImageOnly() { QTextCursor cursor = m_textItem->textCursor(); cursor.movePosition(QTextCursor::Start); cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor); return (cursor.selectionEnd() == 1 && cursor.selectedText() == QString(QChar::ObjectReplacementCharacter)); } int LatexEntry::searchText(const QString& text, const QString& pattern, QTextDocument::FindFlags qt_flags) { Qt::CaseSensitivity caseSensitivity; if (qt_flags & QTextDocument::FindCaseSensitively) caseSensitivity = Qt::CaseSensitive; else caseSensitivity = Qt::CaseInsensitive; int position; if (qt_flags & QTextDocument::FindBackward) position = text.lastIndexOf(pattern, -1, caseSensitivity); else position = text.indexOf(pattern, 0, caseSensitivity); return position; } WorksheetCursor LatexEntry::search(const QString& pattern, unsigned flags, QTextDocument::FindFlags qt_flags, const WorksheetCursor& pos) { if (!(flags & WorksheetEntry::SearchLaTeX)) return WorksheetCursor(); if (pos.isValid() && (pos.entry() != this || pos.textItem() != m_textItem)) return WorksheetCursor(); QTextCursor textCursor = m_textItem->search(pattern, qt_flags, pos); int position = 0; QString latex; const QString repl = QString(QChar::ObjectReplacementCharacter); QTextCursor latexCursor = m_textItem->search(repl, qt_flags, pos); while (!latexCursor.isNull()) { latex = m_textItem->resolveImages(latexCursor); position = searchText(latex, pattern, qt_flags); if (position >= 0) { break; } WorksheetCursor c(this, m_textItem, latexCursor); latexCursor = m_textItem->search(repl, qt_flags, c); } if (latexCursor.isNull()) { if (textCursor.isNull()) return WorksheetCursor(); else return WorksheetCursor(this, m_textItem, textCursor); } else { if (textCursor.isNull() || latexCursor < textCursor) { int start = latexCursor.selectionStart(); latexCursor.insertText(latex); QTextCursor c = m_textItem->textCursor(); c.setPosition(start + position); c.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, pattern.length()); return WorksheetCursor(this, m_textItem, c); } else { return WorksheetCursor(this, m_textItem, textCursor); } } } void LatexEntry::layOutForWidth(qreal w, bool force) { if (size().width() == w && !force) return; const qreal margin = worksheet()->isPrinting() ? 0 : RightMargin; m_textItem->setGeometry(0, 0, w - margin); setSize(QSizeF(m_textItem->width() + margin, m_textItem->height() + VerticalMargin)); } bool LatexEntry::wantToEvaluate() { return !isOneImageOnly(); } bool LatexEntry::renderLatexCode() { bool success = false; QString latex = latexCode(); m_renderedFormat = QTextImageFormat(); // clear rendered image Cantor::LatexRenderer* renderer = new Cantor::LatexRenderer(this); renderer->setLatexCode(latex); renderer->setEquationOnly(false); renderer->setMethod(Cantor::LatexRenderer::LatexMethod); renderer->renderBlocking(); if (renderer->renderingSuccessful()) { Cantor::Renderer* epsRend = worksheet()->renderer(); m_renderedFormat = epsRend->render(m_textItem->document(), renderer); success = !m_renderedFormat.name().isEmpty(); } else qWarning() << "Fail to render LatexEntry with error " << renderer->errorMessage(); if(success) { QTextCursor cursor = m_textItem->textCursor(); cursor.movePosition(QTextCursor::Start); cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor); cursor.insertText(QString(QChar::ObjectReplacementCharacter), m_renderedFormat); m_textItem->denyEditing(); } delete renderer; return success; } bool LatexEntry::isConvertableToLatexEntry(const QJsonObject& cell) { if (!Cantor::JupyterUtils::isCodeCell(cell)) return false; const QString& source = Cantor::JupyterUtils::getSource(cell); return source.startsWith(QLatin1String("%%latex\n")); } void LatexEntry::resolveImagesAtCursor() { QTextCursor cursor = m_textItem->textCursor(); if (!cursor.hasSelection()) cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor); cursor.insertText(m_textItem->resolveImages(cursor)); } QString LatexEntry::plain() const { return m_textItem->toPlainText(); } diff --git a/src/mathrendertask.cpp b/src/mathrendertask.cpp index 05ccdb5b..bee1ab05 100644 --- a/src/mathrendertask.cpp +++ b/src/mathrendertask.cpp @@ -1,246 +1,248 @@ /* 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 "lib/renderer.h" #include "lib/latexrenderer.h" static const QLatin1String mathTex("\\documentclass%9{minimal}"\ "\\usepackage{amsfonts,amssymb}"\ "\\usepackage{amsmath}"\ "\\usepackage[utf8]{inputenc}"\ "\\usepackage[active,displaymath,textmath,tightpage]{preview}"\ "\\usepackage{color}"\ "\\begin{document}"\ "\\begin{preview}"\ "$"\ "\\colorbox[rgb]{%1,%2,%3}{"\ "\\color[rgb]{%4,%5,%6}"\ "\\fontsize{%7}{%7}\\selectfont"\ "%8}"\ "$"\ "\\end{preview}" "\\end{document}"); static const QLatin1String eqnHeader("$\\displaystyle %1$"); static const QLatin1String inlineEqnHeader("$%1$"); MathRenderTask::MathRenderTask( int jobId, const QString& code, Cantor::LatexRenderer::EquationType type, double scale, bool highResolution ): m_jobId(jobId), m_code(code), m_type(type), m_scale(scale), m_highResolution(highResolution) { KColorScheme scheme(QPalette::Active); m_backgroundColor = scheme.background().color(); m_foregroundColor = scheme.foreground().color(); } void MathRenderTask::setHandler(const QObject* receiver, const char* resultHandler) { connect(this, SIGNAL(finish(QSharedPointer)), receiver, resultHandler); } void MathRenderTask::run() { qDebug()<<"MathRenderTask::run " << m_jobId; QSharedPointer result(new MathRenderResult()); const QString& tempDir=QStandardPaths::writableLocation(QStandardPaths::TempLocation); QTemporaryFile texFile(tempDir + QDir::separator() + QLatin1String("cantor_tex-XXXXXX.tex")); texFile.open(); // make sure we have preview.sty available if (!tempDir.contains(QLatin1String("preview.sty"))) { QString file = QStandardPaths::locate(QStandardPaths::AppDataLocation, QLatin1String("latex/preview.sty")); if (file.isEmpty()) file = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("cantor/latex/preview.sty")); if (file.isEmpty()) { result->successful = false; result->errorMessage = QString::fromLatin1("LaTeX style file preview.sty not found."); finalize(result); return; } else QFile::copy(file, tempDir + QDir::separator() + QLatin1String("preview.sty")); } QString expressionTex=mathTex; expressionTex=expressionTex .arg(m_backgroundColor.redF()).arg(m_backgroundColor.greenF()).arg(m_backgroundColor.blueF()) .arg(m_foregroundColor.redF()).arg(m_foregroundColor.greenF()).arg(m_foregroundColor.blueF()); int fontPointSize = QApplication::font().pointSize(); expressionTex=expressionTex.arg(fontPointSize); switch(m_type) { case Cantor::LatexRenderer::FullEquation: expressionTex=expressionTex.arg(eqnHeader, QString()); break; case Cantor::LatexRenderer::InlineEquation: expressionTex=expressionTex.arg(inlineEqnHeader, QString()); break; case Cantor::LatexRenderer::CustomEquation: expressionTex=expressionTex.arg(QLatin1String("%1"), QLatin1String("[preview]")); break; } QString latex = m_code; // Looks hacky, but no sure, how do it better without overhead (like new latex type in lib/latexrender) static const QString& equationBegin = QLatin1String("\\begin{equation}"); static const QString& equationEnd = QLatin1String("\\end{equation}"); if (latex.startsWith(equationBegin) && latex.endsWith(equationEnd)) { latex.remove(0, equationBegin.size()); latex.chop(equationEnd.size()); latex = QLatin1String("\\begin{equation*}") + latex + QLatin1String("\\end{equation*}"); } expressionTex=expressionTex.arg(latex); texFile.write(expressionTex.toUtf8()); texFile.flush(); QProcess p; p.setWorkingDirectory(tempDir); // Create unique uuid for this job // It will be used as pdf filename, for preventing names collisions // And as internal url path too const QString& uuid = Cantor::LatexRenderer::genUuid(); const QString& pdflatex = QStandardPaths::findExecutable(QLatin1String("pdflatex")); p.setProgram(pdflatex); p.setArguments({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->successful = false; QString renderErrorText = QString::fromUtf8(p.readAllStandardOutput()); renderErrorText.remove(0, renderErrorText.indexOf(QLatin1Char('!'))); renderErrorText.remove(renderErrorText.indexOf(QLatin1String("! ==> Fatal error occurred")), renderErrorText.size()); renderErrorText = renderErrorText.trimmed(); result->errorMessage = renderErrorText; finalize(result); texFile.setAutoRemove(false); //Useful for debug return; } //Clean up .aux and .log files QString pathWithoutExtension = tempDir + QDir::separator() + QLatin1String("cantor_")+uuid; QFile::remove(pathWithoutExtension + QLatin1String(".log")); QFile::remove(pathWithoutExtension + QLatin1String(".aux")); + // We shouldn't remove pdf file, because this file used in future in an another parts of Cantor + // For example, this pdf will copied into .cws file on save const QString& pdfFileName = pathWithoutExtension + QLatin1String(".pdf"); bool success; QString errorMessage; QSizeF size; const auto& data = renderPdfToFormat(pdfFileName, m_code, uuid, m_type, m_scale, m_highResolution, &success, &errorMessage); result->successful = success; result->errorMessage = errorMessage; if (success == false) { finalize(result); return; } result->renderedMath = data.first; result->image = data.second; result->jobId = m_jobId; QUrl internal; internal.setScheme(QLatin1String("internal")); internal.setPath(uuid); result->uniqueUrl = internal; finalize(result); } void MathRenderTask::finalize(QSharedPointer result) { emit finish(result); deleteLater(); } std::pair MathRenderTask::renderPdfToFormat(const QString& filename, const QString& code, const QString uuid, Cantor::LatexRenderer::EquationType type, double scale, bool highResulution, bool* success, QString* errorReason) { QSizeF size; const QImage& image = Cantor::Renderer::pdfRenderToImage(QUrl::fromLocalFile(filename), scale, highResulution, &size, errorReason); if (success) *success = image.isNull() == false; if (success && *success == false) return std::make_pair(QTextImageFormat(), QImage()); 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(Cantor::Renderer::CantorFormula, type); format.setProperty(Cantor::Renderer::ImagePath, filename); format.setProperty(Cantor::Renderer::Code, code); format.setVerticalAlignment(QTextCharFormat::AlignBaseline); switch(type) { case Cantor::LatexRenderer::FullEquation: format.setProperty(Cantor::Renderer::Delimiter, QLatin1String("$$")); break; case Cantor::LatexRenderer::InlineEquation: format.setProperty(Cantor::Renderer::Delimiter, QLatin1String("$")); break; case Cantor::LatexRenderer::CustomEquation: format.setProperty(Cantor::Renderer::Delimiter, QLatin1String("")); break; } return std::make_pair(std::move(format), std::move(image)); }