diff --git a/src/lib/latexresult.cpp b/src/lib/latexresult.cpp index 0578326b..56667a76 100644 --- a/src/lib/latexresult.cpp +++ b/src/lib/latexresult.cpp @@ -1,173 +1,171 @@ /* 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 "latexresult.h" using namespace Cantor; #include #include #include #include #include class Cantor::LatexResultPrivate { public: LatexResultPrivate() { showCode=false; } bool showCode; QString code; QString plain; }; -LatexResult::LatexResult(const QString& code, const QUrl &url, const QString& plain) : EpsResult( url ), +LatexResult::LatexResult(const QString& code, const QUrl &url, const QString& plain, const QImage& image) : EpsResult( url, image ), d(new LatexResultPrivate) { d->code=code; d->plain=plain; } LatexResult::~LatexResult() { delete d; } int LatexResult::type() { return LatexResult::Type; } QString LatexResult::mimeType() { if(isCodeShown()) return QStringLiteral("text/plain"); else return EpsResult::mimeType(); } QString LatexResult::code() { return d->code; } QString LatexResult::plain() { return d->plain; } bool LatexResult::isCodeShown() { return d->showCode; } void LatexResult::showCode() { d->showCode=true; } void LatexResult::showRendered() { d->showCode=false; } QVariant LatexResult::data() { if(isCodeShown()) return QVariant(code()); else return EpsResult::data(); } QString LatexResult::toHtml() { if (isCodeShown()) { QString s=code(); return s.toHtmlEscaped(); } else { return EpsResult::toHtml(); } } QString LatexResult::toLatex() { return code(); } QDomElement LatexResult::toXml(QDomDocument& doc) { qDebug()<<"saving textresult "<plain)); data.insert(QLatin1String("text/latex"), toJupyterMultiline(d->code)); root.insert(QLatin1String("data"), data); root.insert(QLatin1String("metadata"), jupyterMetadata()); return root; } void LatexResult::save(const QString& filename) { if(isCodeShown()) { QFile file(filename); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) return; QTextStream stream(&file); stream< */ #ifndef _LATEXRESULT_H #define _LATEXRESULT_H #include "epsresult.h" #include "cantor_export.h" namespace Cantor{ class LatexResultPrivate; /**Class used for LaTeX results, it is basically an Eps result, but it exports a different type, and additionally stores the LaTeX code, used to generate the Eps, so it can be retrieved later **/ class CANTOR_EXPORT LatexResult : public EpsResult { public: enum {Type=7}; - LatexResult( const QString& code, const QUrl& url, const QString& plain = QString()); + LatexResult( const QString& code, const QUrl& url, const QString& plain = QString(), const QImage& image = QImage()); ~LatexResult() override; int type() override; QString mimeType() override; bool isCodeShown(); void showCode(); void showRendered(); QString code(); QString plain(); QString toHtml() override; QString toLatex() override; QVariant data() override; QDomElement toXml(QDomDocument& doc) override; QJsonValue toJupyterJson() override; void save(const QString& filename) override; private: LatexResultPrivate* d; }; } #endif /* _LATEXRESULT_H */ diff --git a/src/loadedexpression.cpp b/src/loadedexpression.cpp index 7dd6f49b..387a8dc8 100644 --- a/src/loadedexpression.cpp +++ b/src/loadedexpression.cpp @@ -1,313 +1,319 @@ /* 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 "loadedexpression.h" #include "jupyterutils.h" #include "lib/imageresult.h" #include "lib/epsresult.h" #include "lib/textresult.h" #include "lib/latexresult.h" #include "lib/animationresult.h" #include "lib/latexrenderer.h" #include "lib/mimeresult.h" #include "lib/htmlresult.h" #include #include #include #include #include #include #include #include LoadedExpression::LoadedExpression( Cantor::Session* session ) : Cantor::Expression( session, false, -1) { } void LoadedExpression::interrupt() { //Do nothing } void LoadedExpression::evaluate() { //Do nothing } void LoadedExpression::loadFromXml(const QDomElement& xml, const KZip& file) { setCommand(xml.firstChildElement(QLatin1String("Command")).text()); const QDomNodeList& results = xml.elementsByTagName(QLatin1String("Result")); for (int i = 0; i < results.size(); i++) { const QDomElement& resultElement = results.at(i).toElement(); const QString& type = resultElement.attribute(QLatin1String("type")); qDebug() << "type" << type; if ( type == QLatin1String("text")) { const QString& format = resultElement.attribute(QLatin1String("format")); bool isStderr = resultElement.attribute(QLatin1String("stderr")).toInt(); Cantor::TextResult* result = new Cantor::TextResult(resultElement.text()); if (format == QLatin1String("latex")) result->setFormat(Cantor::TextResult::LatexFormat); result->setStdErr(isStderr); addResult(result); } else if (type == QLatin1String("mime")) { const QDomElement& resultElement = results.at(i).toElement(); QJsonObject mimeBundle; const QDomNodeList& contents = resultElement.elementsByTagName(QLatin1String("Content")); for (int x = 0; x < contents.count(); x++) { const QDomElement& content = contents.at(x).toElement(); const QString& mimeType = content.attribute(QLatin1String("key")); QJsonDocument jsonDoc = QJsonDocument::fromJson(content.text().toUtf8());; const QJsonValue& value = jsonDoc.object().value(QLatin1String("content")); mimeBundle.insert(mimeType, value); } addResult(new Cantor::MimeResult(mimeBundle)); } else if (type == QLatin1String("html")) { const QString& formatString = resultElement.attribute(QLatin1String("showCode")); Cantor::HtmlResult::Format format = Cantor::HtmlResult::Html; if (formatString == QLatin1String("htmlSource")) format = Cantor::HtmlResult::HtmlSource; else if (formatString == QLatin1String("plain")) format = Cantor::HtmlResult::PlainAlternative; const QString& plain = resultElement.firstChildElement(QLatin1String("Plain")).text(); const QString& html = resultElement.firstChildElement(QLatin1String("Html")).text(); std::map alternatives; const QDomNodeList& alternativeElms = resultElement.elementsByTagName(QLatin1String("Alternative")); for (int x = 0; x < alternativeElms.count(); x++) { const QDomElement& content = alternativeElms.at(x).toElement(); const QString& mimeType = content.attribute(QLatin1String("key")); QJsonDocument jsonDoc = QJsonDocument::fromJson(content.text().toUtf8());; const QJsonValue& value = jsonDoc.object().value(QLatin1String("root")); alternatives[mimeType] = value; } Cantor::HtmlResult* result = new Cantor::HtmlResult(html, plain, alternatives); result->setFormat(format); addResult(result); } else if (type == QLatin1String("image") || type == QLatin1String("latex") || type == QLatin1String("animation")) { const KArchiveEntry* imageEntry=file.directory()->entry(resultElement.attribute(QLatin1String("filename"))); if (imageEntry&&imageEntry->isFile()) { const KArchiveFile* imageFile=static_cast(imageEntry); QString dir=QStandardPaths::writableLocation(QStandardPaths::TempLocation); imageFile->copyTo(dir); QUrl imageUrl = QUrl::fromLocalFile(QDir(dir).absoluteFilePath(imageFile->name())); if(type==QLatin1String("latex")) { - addResult(new Cantor::LatexResult(resultElement.text(), imageUrl)); + const QByteArray& ba = QByteArray::fromBase64(resultElement.attribute(QLatin1String("image")).toLatin1()); + QImage image; + image.loadFromData(ba); + addResult(new Cantor::LatexResult(resultElement.text(), imageUrl, QString(), image)); }else if(type==QLatin1String("animation")) { addResult(new Cantor::AnimationResult(imageUrl)); }else if(imageFile->name().endsWith(QLatin1String(".eps"))) { const QByteArray& ba = QByteArray::fromBase64(resultElement.attribute(QLatin1String("image")).toLatin1()); QImage image; image.loadFromData(ba); addResult(new Cantor::EpsResult(imageUrl, image)); }else { addResult(new Cantor::ImageResult(imageUrl, resultElement.text())); } } } } const QDomElement& errElem = xml.firstChildElement(QLatin1String("Error")); if (!errElem.isNull()) { setErrorMessage(errElem.text()); setStatus(Error); } else setStatus(Done); } void LoadedExpression::loadFromJupyter(const QJsonObject& cell) { setCommand(JupyterUtils::getSource(cell)); const QJsonValue idObject = cell.value(QLatin1String("execution_count")); if (!idObject.isUndefined() && !idObject.isNull()) setId(idObject.toInt()); const QJsonArray& outputs = cell.value(QLatin1String("outputs")).toArray(); for (QJsonArray::const_iterator iter = outputs.begin(); iter != outputs.end(); iter++) { if (!JupyterUtils::isJupyterOutput(*iter)) continue; const QJsonObject& output = iter->toObject(); const QString& outputType = JupyterUtils::getOutputType(output); if (JupyterUtils::isJupyterTextOutput(output)) { const QString& text = JupyterUtils::fromJupyterMultiline(output.value(QLatin1String("text"))); bool isStderr = output.value(QLatin1String("name")).toString() == QLatin1String("stderr"); Cantor::TextResult* result = new Cantor::TextResult(text); result->setStdErr(isStderr); addResult(result); } else if (JupyterUtils::isJupyterErrorOutput(output)) { const QJsonArray& tracebackLineArray = output.value(QLatin1String("traceback")).toArray(); QString traceback; // Looks like the traceback in Jupyter joined with '\n', no '' // So, manually add it for (const QJsonValue& line : tracebackLineArray) traceback += line.toString() + QLatin1Char('\n'); traceback.chop(1); // IPython returns error with terminal colors, we handle it here, but should we? static const QChar ESC(0x1b); traceback.remove(QRegExp(QString(ESC)+QLatin1String("\\[[0-9;]*m"))); setErrorMessage(traceback); } else if (JupyterUtils::isJupyterDisplayOutput(output) || JupyterUtils::isJupyterExecutionResult(output)) { const QJsonObject& data = output.value(QLatin1String("data")).toObject(); QJsonObject metadata = JupyterUtils::getMetadata(output); const QString& text = JupyterUtils::fromJupyterMultiline(data.value(JupyterUtils::textMime)); const QString& mainKey = JupyterUtils::mainBundleKey(data); Cantor::Result* result = nullptr; if (mainKey == JupyterUtils::gifMime) { const QByteArray& bytes = QByteArray::fromBase64(data.value(mainKey).toString().toLatin1()); QTemporaryFile file; file.setAutoRemove(false); file.open(); file.write(bytes); file.close(); result = new Cantor::AnimationResult(QUrl::fromLocalFile(file.fileName()), text); } else if (mainKey == JupyterUtils::textMime) { result = new Cantor::TextResult(text); } else if (mainKey == JupyterUtils::htmlMime) { const QString& html = JupyterUtils::fromJupyterMultiline(data.value(JupyterUtils::htmlMime)); // Some backends places gif animation in hmlt (img tag), for example, Sage if (JupyterUtils::isGifHtml(html)) { result = new Cantor::AnimationResult(JupyterUtils::loadGifHtml(html), text); } else { // Load alternative content types too std::map alternatives; for (const QString& key : data.keys()) if (key != JupyterUtils::htmlMime && key != JupyterUtils::textMime) alternatives[key] = data[key]; result = new Cantor::HtmlResult(html, text, alternatives); } } else if (mainKey == JupyterUtils::latexMime) { + // Some latex results contains alreadey rendered images, so use them, if presents + const QImage& image = JupyterUtils::loadImage(data, JupyterUtils::pngMime); + QString latex = JupyterUtils::fromJupyterMultiline(data.value(mainKey)); QScopedPointer renderer(new Cantor::LatexRenderer(this)); renderer->setLatexCode(latex); renderer->setEquationOnly(false); renderer->setMethod(Cantor::LatexRenderer::LatexMethod); renderer->renderBlocking(); - result = new Cantor::LatexResult(latex, QUrl::fromLocalFile(renderer->imagePath()), text); + result = new Cantor::LatexResult(latex, QUrl::fromLocalFile(renderer->imagePath()), text, image); // If we have failed to render LaTeX i think Cantor should show the latex code at least if (!renderer->renderingSuccessful()) static_cast(result)->showCode(); } // So this is image else if (JupyterUtils::imageKeys(data).contains(mainKey)) { const QImage& image = JupyterUtils::loadImage(data, mainKey); result = new Cantor::ImageResult(image, text); const QJsonValue size = metadata.value(mainKey); if (size.isObject()) { int w = size.toObject().value(QLatin1String("width")).toInt(-1); int h = size.toObject().value(QLatin1String("height")).toInt(-1); if (w != -1 && h != -1) { static_cast(result)->setDisplaySize(QSize(w, h)); // Remove size information, because we don't need it after setting display size // Also, we encode image to 'image/png' on saving as .ipynb, even original image don't png // So, without removing the size info here, after loading for example 'image/tiff' to Cantor from .ipynb and saving the worksheet // (which means, that the image will be saved as png and not as tiff). // We will have outdated key 'image/tiff' in metadata. metadata.remove(mainKey); } } } else if (data.keys().size() == 1 && data.keys()[0] == JupyterUtils::textMime) result = new Cantor::TextResult(text); // Cantor don't know, how handle this, so pack into mime container result else { qDebug() << "Found unsupported " << outputType << "result with mimes" << data.keys() << ", so add them to mime container result"; result = new Cantor::MimeResult(data); } if (result) { result->setJupyterMetadata(metadata); int resultIndex = output.value(QLatin1String("execution_count")).toInt(-1); if (resultIndex != -1) result->setExecutionIndex(resultIndex); addResult(result); } } } if (errorMessage().isEmpty()) setStatus(Done); else setStatus(Error); } diff --git a/src/textresultitem.cpp b/src/textresultitem.cpp index c858652c..8eb0badf 100644 --- a/src/textresultitem.cpp +++ b/src/textresultitem.cpp @@ -1,203 +1,236 @@ /* 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) 2012 Martin Kuettler */ #include "textresultitem.h" #include "commandentry.h" #include "lib/result.h" #include "lib/textresult.h" #include "lib/latexresult.h" #include "lib/epsrenderer.h" #include "lib/mimeresult.h" #include "lib/htmlresult.h" +#include "mathrendertask.h" +#include "config-cantor.h" #include #include #include #include #include TextResultItem::TextResultItem(QGraphicsObject* parent, Cantor::Result* result) : WorksheetTextItem(parent), ResultItem(result) { setTextInteractionFlags(Qt::TextSelectableByMouse); update(); } double TextResultItem::setGeometry(double x, double y, double w) { return WorksheetTextItem::setGeometry(x, y, w); } void TextResultItem::populateMenu(QMenu* menu, QPointF pos) { QAction * copy = KStandardAction::copy(this, SLOT(copy()), menu); if (!textCursor().hasSelection()) copy->setEnabled(false); menu->addAction(copy); ResultItem::addCommonActions(this, menu); Cantor::Result* res = result(); if (res->type() == Cantor::LatexResult::Type) { QAction* showCodeAction = nullptr; Cantor::LatexResult* lres = dynamic_cast(res); if (lres->isCodeShown()) showCodeAction = menu->addAction(i18n("Show Rendered")); else showCodeAction = menu->addAction(i18n("Show Code")); connect(showCodeAction, &QAction::triggered, this, &TextResultItem::toggleLatexCode); } else if (res->type() == Cantor::HtmlResult::Type) { Cantor::HtmlResult* hres = static_cast(res); switch (hres->format()) { case Cantor::HtmlResult::Html: connect(menu->addAction(i18n("Show Html Code")), &QAction::triggered, this, &TextResultItem::showHtmlSource); if (!hres->plain().isEmpty()) connect(menu->addAction(i18n("Show Plain Alternative")), &QAction::triggered, this, &TextResultItem::showPlain); break; case Cantor::HtmlResult::HtmlSource: connect(menu->addAction(i18n("Show Html")), &QAction::triggered, this, &TextResultItem::showHtml); if (!hres->plain().isEmpty()) connect(menu->addAction(i18n("Show Plain Alternative")), &QAction::triggered, this, &TextResultItem::showPlain); break; case Cantor::HtmlResult::PlainAlternative: connect(menu->addAction(i18n("Show Html")), &QAction::triggered, this, &TextResultItem::showHtml); connect(menu->addAction(i18n("Show Html Code")), &QAction::triggered, this, &TextResultItem::showHtmlSource); break; } } menu->addSeparator(); qDebug() << "populate Menu"; emit menuCreated(menu, mapToParent(pos)); } void TextResultItem::update() { Q_ASSERT( m_result->type() == Cantor::TextResult::Type || m_result->type() == Cantor::LatexResult::Type || m_result->type() == Cantor::MimeResult::Type || m_result->type() == Cantor::HtmlResult::Type ); switch(m_result->type()) { case Cantor::TextResult::Type: case Cantor::MimeResult::Type: case Cantor::HtmlResult::Type: setHtml(m_result->toHtml()); break; case Cantor::LatexResult::Type: setLatex(dynamic_cast(m_result)); break; default: break; } } void TextResultItem::setLatex(Cantor::LatexResult* result) { QTextCursor cursor = textCursor(); cursor.movePosition(QTextCursor::Start); cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor); QString latex = result->toLatex().trimmed(); if (latex.startsWith(QLatin1String("\\begin{eqnarray*}")) && latex.endsWith(QLatin1String("\\end{eqnarray*}"))) { latex = latex.mid(17); latex = latex.left(latex.size() - 15); } + +#ifdef WITH_EPS if (result->isCodeShown()) { if (latex.isEmpty()) cursor.removeSelectedText(); else cursor.insertText(latex); } else { QTextImageFormat format; - Cantor::EpsRenderer* renderer = qobject_cast(scene())->epsRenderer();; - format = renderer->render(cursor.document(), - result->data().toUrl()); - format.setProperty(Cantor::EpsRenderer::CantorFormula, - Cantor::EpsRenderer::LatexFormula); - format.setProperty(Cantor::EpsRenderer::Code, latex); - format.setProperty(Cantor::EpsRenderer::Delimiter, QLatin1String("$$")); - if(format.isValid()) - cursor.insertText(QString(QChar::ObjectReplacementCharacter), format); + + if (!result->image().isNull() && worksheet()->epsRenderer()->scale() == 1.0) + { + cursor.insertText(QString(QChar::ObjectReplacementCharacter), toFormat(result->image(), latex)); + } else - cursor.insertText(i18n("Cannot render Eps file. You may need additional packages")); + { + Cantor::EpsRenderer* renderer = qobject_cast(scene())->epsRenderer();; + format = renderer->render(cursor.document(), result->url()); + format.setProperty(Cantor::EpsRenderer::CantorFormula, + Cantor::EpsRenderer::LatexFormula); + format.setProperty(Cantor::EpsRenderer::Code, latex); + format.setProperty(Cantor::EpsRenderer::Delimiter, QLatin1String("$$")); + if(format.isValid()) + cursor.insertText(QString(QChar::ObjectReplacementCharacter), format); + else + cursor.insertText(i18n("Cannot render Eps file. You may need additional packages")); + } } +#else + cursor.insertText(QString(QChar::ObjectReplacementCharacter), toFormat(result->image(), latex)); +#endif } double TextResultItem::width() const { return WorksheetTextItem::width(); } double TextResultItem::height() const { return WorksheetTextItem::height(); } void TextResultItem::toggleLatexCode() { Cantor::LatexResult* lr = dynamic_cast(result()); if(lr->isCodeShown()) lr->showRendered(); else lr->showCode(); parentEntry()->updateEntry(); } void TextResultItem::showHtml() { Cantor::HtmlResult* hr = static_cast(result()); hr->setFormat(Cantor::HtmlResult::Html); parentEntry()->updateEntry(); } void TextResultItem::showHtmlSource() { Cantor::HtmlResult* hr = static_cast(result()); hr->setFormat(Cantor::HtmlResult::HtmlSource); parentEntry()->updateEntry(); } void TextResultItem::showPlain() { Cantor::HtmlResult* hr = static_cast(result()); hr->setFormat(Cantor::HtmlResult::PlainAlternative); parentEntry()->updateEntry(); } void TextResultItem::saveResult() { Cantor::Result* res = result(); const QString& filename = QFileDialog::getSaveFileName(worksheet()->worksheetView(), i18n("Save result"), QString(), res->mimeType()); qDebug() << "saving result to " << filename; res->save(filename); } void TextResultItem::deleteLater() { WorksheetTextItem::deleteLater(); } + +QTextImageFormat TextResultItem::toFormat(const QImage& image, const QString& latex) +{ + QTextImageFormat format; + + QUrl internal; + internal.setScheme(QLatin1String("internal")); + internal.setPath(MathRenderTask::genUuid()); + + document()->addResource(QTextDocument::ImageResource, internal, QVariant(image) ); + + format.setName(internal.url()); + format.setProperty(Cantor::EpsRenderer::CantorFormula, Cantor::EpsRenderer::LatexFormula); + //format.setProperty(Cantor::EpsRenderer::ImagePath, filename); + format.setProperty(Cantor::EpsRenderer::Code, latex); + format.setProperty(Cantor::EpsRenderer::Delimiter, QLatin1String("$$")); + + return format; +} diff --git a/src/textresultitem.h b/src/textresultitem.h index 0ca77436..9e186230 100644 --- a/src/textresultitem.h +++ b/src/textresultitem.h @@ -1,58 +1,59 @@ /* 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) 2012 Martin Kuettler */ #ifndef TEXTRESULTITEM_H #define TEXTRESULTITEM_H #include "resultitem.h" #include "worksheettextitem.h" #include "worksheetentry.h" #include "lib/latexresult.h" class TextResultItem : public WorksheetTextItem, public ResultItem { Q_OBJECT public: explicit TextResultItem(QGraphicsObject* parent, Cantor::Result* result); ~TextResultItem() override = default; using WorksheetTextItem::setGeometry; double setGeometry(double x, double y, double w) override; void populateMenu(QMenu* menu, QPointF pos) override; void update() override; void setLatex(Cantor::LatexResult* result); + QTextImageFormat toFormat(const QImage& image, const QString& latex); double width() const override; double height() const override; void deleteLater() override; protected Q_SLOTS: void toggleLatexCode(); void showHtml(); void showHtmlSource(); void showPlain(); void saveResult(); }; #endif //TEXTRESULTITEM_H