No OneTemporary

File Metadata

Created
Sat, May 4, 10:51 PM
diff --git a/src/lib/latexrenderer.cpp b/src/lib/latexrenderer.cpp
index 648f4cda..e5b6c8fe 100644
--- a/src/lib/latexrenderer.cpp
+++ b/src/lib/latexrenderer.cpp
@@ -1,328 +1,329 @@
/*
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) 2011 Alexander Rieder <alexanderrieder@gmail.com>
*/
#include "latexrenderer.h"
using namespace Cantor;
#include <QProcess>
#include <QDebug>
#include <QDir>
#include <QFileInfo>
#include <QEventLoop>
#include <QTemporaryFile>
#include <KColorScheme>
#include <QUuid>
#include <QApplication>
#include <config-cantorlib.h>
#include "settings.h"
class Cantor::LatexRendererPrivate
{
public:
QString latexCode;
QString header;
LatexRenderer::Method method;
bool isEquationOnly;
LatexRenderer::EquationType equationType;
QString errorMessage;
bool success;
QString latexFilename;
QString epsFilename;
QString uuid;
QTemporaryFile* texFile;
};
static const QLatin1String tex("\\documentclass[fleqn]{article}"\
"\\usepackage{latexsym,amsfonts,amssymb,ulem}"\
"\\usepackage{amsmath}"\
"\\usepackage[dvips]{graphicx}"\
"\\usepackage[utf8]{inputenc}"\
"\\usepackage{xcolor}"\
"\\setlength\\textwidth{5in}"\
"\\setlength{\\parindent}{0pt}"\
"%1"\
"\\pagecolor[rgb]{%2,%3,%4}"\
"\\pagestyle{empty}"\
"\\begin{document}"\
"\\color[rgb]{%5,%6,%7}"\
"\\fontsize{%8}{%8}\\selectfont\n"\
"%9\n"\
"\\end{document}");
static const QLatin1String eqnHeader("\\begin{eqnarray*}%1\\end{eqnarray*}");
static const QLatin1String inlineEqnHeader("$%1$");
LatexRenderer::LatexRenderer(QObject* parent) : QObject(parent),
d(new LatexRendererPrivate)
{
d->method=LatexMethod;
d->isEquationOnly=false;
d->equationType=InlineEquation;
d->success=false;
d->texFile=nullptr;
}
LatexRenderer::~LatexRenderer()
{
delete d;
}
QString LatexRenderer::latexCode() const
{
return d->latexCode;
}
void LatexRenderer::setLatexCode(const QString& src)
{
d->latexCode=src;
}
QString LatexRenderer::header() const
{
return d->header;
}
void LatexRenderer::addHeader(const QString& header)
{
d->header.append(header);
}
void LatexRenderer::setHeader(const QString& header)
{
d->header=header;
}
LatexRenderer::Method LatexRenderer::method() const
{
return d->method;
}
void LatexRenderer::setMethod(LatexRenderer::Method method)
{
d->method=method;
}
void LatexRenderer::setEquationType(LatexRenderer::EquationType type)
{
d->equationType=type;
}
LatexRenderer::EquationType LatexRenderer::equationType() const
{
return d->equationType;
}
void LatexRenderer::setErrorMessage(const QString& msg)
{
d->errorMessage=msg;
}
QString LatexRenderer::errorMessage() const
{
return d->errorMessage;
}
bool LatexRenderer::renderingSuccessful() const
{
return d->success;
}
void LatexRenderer::setEquationOnly(bool isEquationOnly)
{
d->isEquationOnly=isEquationOnly;
}
bool LatexRenderer::isEquationOnly() const
{
return d->isEquationOnly;
}
QString LatexRenderer::imagePath() const
{
return d->epsFilename;
}
QString Cantor::LatexRenderer::uuid() const
{
return d->uuid;
}
bool LatexRenderer::render()
{
switch(d->method)
{
case LatexRenderer::LatexMethod:
return renderWithLatex();
case LatexRenderer::MmlMethod:
return renderWithMml();
default:
return false;
};
}
void LatexRenderer::renderBlocking()
{
QEventLoop event;
connect(this, &LatexRenderer::done, &event, &QEventLoop::quit);
connect(this, &LatexRenderer::error, &event, &QEventLoop::quit);
bool success = render();
// We can't emit error before running event loop, so exit by passing false as an error indicator
if (success)
event.exec();
else
return;
}
bool LatexRenderer::renderWithLatex()
{
qDebug()<<"rendering using latex method";
QString dir=QStandardPaths::writableLocation(QStandardPaths::TempLocation);
if (d->texFile)
delete d->texFile;
d->texFile=new QTemporaryFile(dir + QDir::separator() + QLatin1String("cantor_tex-XXXXXX.tex"));
d->texFile->open();
KColorScheme scheme(QPalette::Active);
const QColor &backgroundColor=scheme.background().color();
const QColor &foregroundColor=scheme.foreground().color();
QString expressionTex=tex;
expressionTex=expressionTex.arg(d->header)
.arg(backgroundColor.redF()).arg(backgroundColor.greenF()).arg(backgroundColor.blueF())
.arg(foregroundColor.redF()).arg(foregroundColor.greenF()).arg(foregroundColor.blueF());
int fontPointSize = QApplication::font().pointSize();
expressionTex=expressionTex.arg(fontPointSize);
if(isEquationOnly())
{
switch(equationType())
{
case FullEquation: expressionTex=expressionTex.arg(eqnHeader); break;
case InlineEquation: expressionTex=expressionTex.arg(inlineEqnHeader); break;
+ case CustomEquation: expressionTex=expressionTex.arg(QLatin1String("%1")); break;
}
}
expressionTex=expressionTex.arg(d->latexCode);
// qDebug()<<"full tex:\n"<<expressionTex;
d->texFile->write(expressionTex.toUtf8());
d->texFile->flush();
QString fileName = d->texFile->fileName();
qDebug()<<"fileName: "<<fileName;
d->latexFilename=fileName;
QProcess *p=new QProcess( this );
p->setWorkingDirectory(dir);
d->uuid = genUuid();
qDebug() << Settings::self()->latexCommand();
QFileInfo info(Settings::self()->latexCommand());
if (info.exists() && info.isExecutable())
{
p->setProgram(Settings::self()->latexCommand());
p->setArguments({QStringLiteral("-jobname=cantor_") + d->uuid, QStringLiteral("-halt-on-error"), fileName});
connect(p, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(convertToPs()) );
p->start();
return true;
}
else
{
setErrorMessage(QStringLiteral("failed to find latex executable"));
return false;
}
}
void LatexRenderer::convertToPs()
{
const QString& dir=QStandardPaths::writableLocation(QStandardPaths::TempLocation);
QString dviFile = dir + QDir::separator() + QStringLiteral("cantor_") + d->uuid + QStringLiteral(".dvi");
d->epsFilename = dir + QDir::separator() + QLatin1String("cantor_")+d->uuid+QLatin1String(".eps");
QProcess *p=new QProcess( this );
qDebug()<<"converting to eps: "<<Settings::self()->dvipsCommand()<<"-E"<<"-o"<<d->epsFilename<<dviFile;
QFileInfo info(Settings::self()->dvipsCommand());
if (info.exists() && info.isExecutable())
{
p->setProgram(Settings::self()->dvipsCommand());
p->setArguments({QStringLiteral("-E"), QStringLiteral("-q"), QStringLiteral("-o"), d->epsFilename, dviFile});
connect(p, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(convertingDone()) );
p->start();
}
else
{
setErrorMessage(QStringLiteral("failed to find dvips executable"));
emit error();
}
}
void LatexRenderer::convertingDone()
{
QFileInfo info(d->epsFilename);
qDebug() <<"remove temporary files for " << d->latexFilename;
QString pathWithoutExtension = info.path() + QDir::separator() + info.completeBaseName();
QFile::remove(pathWithoutExtension + QLatin1String(".log"));
QFile::remove(pathWithoutExtension + QLatin1String(".aux"));
QFile::remove(pathWithoutExtension + QLatin1String(".dvi"));
if(info.exists())
{
delete d->texFile;
d->texFile = nullptr;
d->success=true;
emit done();
}
else
{
d->success=false;
setErrorMessage(QStringLiteral("failed to create the latex preview image"));
emit error();
}
}
bool LatexRenderer::renderWithMml()
{
qWarning()<<"WARNING: MML rendering not implemented yet!";
emit error();
return false;
}
QString LatexRenderer::genUuid()
{
QString uuid = QUuid::createUuid().toString();
uuid.remove(0, 1);
uuid.chop(1);
uuid.replace(QLatin1Char('-'), QLatin1Char('_'));
return uuid;
}
diff --git a/src/lib/latexrenderer.h b/src/lib/latexrenderer.h
index 2dca186f..b09a4e6a 100644
--- a/src/lib/latexrenderer.h
+++ b/src/lib/latexrenderer.h
@@ -1,81 +1,81 @@
/*
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) 2011 Alexander Rieder <alexanderrieder@gmail.com>
*/
#ifndef _LATEXRENDERER_H
#define _LATEXRENDERER_H
#include <QObject>
#include "cantor_export.h"
namespace Cantor{
class LatexRendererPrivate;
class CANTOR_EXPORT LatexRenderer : public QObject
{
Q_OBJECT
public:
enum Method{ LatexMethod = 0, MmlMethod = 1};
- enum EquationType{ InlineEquation = 0, FullEquation = 1};
+ enum EquationType{ InlineEquation = 0, FullEquation = 1, CustomEquation = 2};
explicit LatexRenderer( QObject* parent = nullptr);
~LatexRenderer() override;
QString latexCode() const;
void setLatexCode(const QString& src);
QString header() const;
void addHeader(const QString& header);
void setHeader(const QString& header);
Method method() const;
void setMethod( Method method);
void setEquationOnly(bool isEquationOnly);
bool isEquationOnly() const;
void setEquationType(EquationType type);
EquationType equationType() const;
QString errorMessage() const;
bool renderingSuccessful() const;
QString imagePath() const;
QString uuid() const;
static QString genUuid();
Q_SIGNALS:
void done();
void error();
public Q_SLOTS:
bool render();
void renderBlocking();
private:
void setErrorMessage(const QString& msg);
private Q_SLOTS:
bool renderWithLatex();
bool renderWithMml();
void convertToPs();
void convertingDone();
private:
LatexRendererPrivate* d;
};
}
#endif /* _LATEXRENDERER_H */
diff --git a/src/markdownentry.cpp b/src/markdownentry.cpp
index e6bda485..bce431e0 100644
--- a/src/markdownentry.cpp
+++ b/src/markdownentry.cpp
@@ -1,701 +1,718 @@
/*
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) 2018 Yifei Wu <kqwyfg@gmail.com>
*/
#include "markdownentry.h"
#include <QJsonArray>
#include <QJsonObject>
#include <QJsonValue>
#include <QImage>
#include <QImageReader>
#include <QBuffer>
#include <KLocalizedString>
#include <QDebug>
#include <QStandardPaths>
#include <QDir>
#include <QFileDialog>
#include <KMessageBox>
#include "jupyterutils.h"
#include "mathrender.h"
#include <config-cantor.h>
#include "settings.h"
#ifdef Discount_FOUND
extern "C" {
#include <mkdio.h>
}
#endif
MarkdownEntry::MarkdownEntry(Worksheet* worksheet) : WorksheetEntry(worksheet), m_textItem(new WorksheetTextItem(this, Qt::TextEditorInteraction)), rendered(false)
{
m_textItem->enableRichText(false);
m_textItem->setOpenExternalLinks(true);
m_textItem->installEventFilter(this);
connect(m_textItem, &WorksheetTextItem::moveToPrevious, this, &MarkdownEntry::moveToPreviousEntry);
connect(m_textItem, &WorksheetTextItem::moveToNext, this, &MarkdownEntry::moveToNextEntry);
connect(m_textItem, SIGNAL(execute()), this, SLOT(evaluate()));
}
void MarkdownEntry::populateMenu(QMenu* menu, QPointF pos)
{
if (!rendered)
menu->addAction(i18n("Insert Image Attachment"), this, &MarkdownEntry::insertImage);
if (attachedImages.size() != 0)
menu->addAction(i18n("Clear Attachments"), this, &MarkdownEntry::clearAttachments);
WorksheetEntry::populateMenu(menu, pos);
}
bool MarkdownEntry::isEmpty()
{
return m_textItem->document()->isEmpty();
}
int MarkdownEntry::type() const
{
return Type;
}
bool MarkdownEntry::acceptRichText()
{
return false;
}
bool MarkdownEntry::focusEntry(int pos, qreal xCoord)
{
if (aboutToBeRemoved())
return false;
m_textItem->setFocusAt(pos, xCoord);
return true;
}
void MarkdownEntry::setContent(const QString& content)
{
rendered = false;
plain = content;
setPlainText(plain);
}
void MarkdownEntry::setContent(const QDomElement& content, const KZip& file)
{
rendered = content.attribute(QLatin1String("rendered"), QLatin1String("1")) == QLatin1String("1");
QDomElement htmlEl = content.firstChildElement(QLatin1String("HTML"));
if(!htmlEl.isNull())
html = htmlEl.text();
else
{
html = QLatin1String("");
rendered = false; // No html provided. Assume that it hasn't been rendered.
}
QDomElement plainEl = content.firstChildElement(QLatin1String("Plain"));
if(!plainEl.isNull())
plain = plainEl.text();
else
{
plain = QLatin1String("");
html = QLatin1String(""); // No plain text provided. The entry shouldn't render anything, or the user can't re-edit it.
}
const QDomNodeList& attachments = content.elementsByTagName(QLatin1String("Attachment"));
for (int x = 0; x < attachments.count(); x++)
{
const QDomElement& attachment = attachments.at(x).toElement();
QUrl url(attachment.attribute(QLatin1String("url")));
const QString& base64 = attachment.text();
QImage image;
image.loadFromData(QByteArray::fromBase64(base64.toLatin1()), "PNG");
attachedImages.push_back(std::make_pair(url, QLatin1String("image/png")));
m_textItem->document()->addResource(QTextDocument::ImageResource, url, QVariant(image));
}
if(rendered)
setRenderedHtml(html);
else
setPlainText(plain);
// Handle math after setting html
const QDomNodeList& maths = content.elementsByTagName(QLatin1String("EmbeddedMath"));
foundMath.clear();
for (int i = 0; i < maths.count(); i++)
{
const QDomElement& math = maths.at(i).toElement();
const QString mathCode = math.text();
foundMath.push_back(std::make_pair(mathCode, false));
}
if (rendered)
{
markUpMath();
for (int i = 0; i < maths.count(); i++)
{
const QDomElement& math = maths.at(i).toElement();
bool mathRendered = math.attribute(QLatin1String("rendered")).toInt();
const QString mathCode = math.text();
if (mathRendered)
{
const KArchiveEntry* imageEntry=file.directory()->entry(math.attribute(QLatin1String("path")));
if (imageEntry && imageEntry->isFile())
{
const KArchiveFile* imageFile=static_cast<const KArchiveFile*>(imageEntry);
const QString& dir=QStandardPaths::writableLocation(QStandardPaths::TempLocation);
imageFile->copyTo(dir);
const QString& pdfPath = dir + QDir::separator() + imageFile->name();
QString latex;
Cantor::LatexRenderer::EquationType type;
std::tie(latex, type) = parseMathCode(mathCode);
// Get uuid by removing 'cantor_' and '.pdf' extension
// len('cantor_') == 7, len('.pdf') == 4
QString uuid = pdfPath;
uuid.remove(0, 7);
uuid.chop(4);
bool success;
const auto& data = worksheet()->mathRenderer()->renderExpressionFromPdf(pdfPath, uuid, latex, type, &success);
if (success)
{
QUrl internal;
internal.setScheme(QLatin1String("internal"));
internal.setPath(uuid);
setRenderedMath(i+1, data.first, internal, data.second);
}
}
else
renderMathExpression(i+1, mathCode);
}
}
}
// Because, all previous actions was on load stage,
// them shoudl unconverted by user
m_textItem->document()->clearUndoRedoStacks();
}
void MarkdownEntry::setContentFromJupyter(const QJsonObject& cell)
{
if (!Cantor::JupyterUtils::isMarkdownCell(cell))
return;
// https://nbformat.readthedocs.io/en/latest/format_description.html#cell-metadata
// There isn't Jupyter metadata for markdown cells, which could be handled by Cantor
// So we just store it
setJupyterMetadata(Cantor::JupyterUtils::getMetadata(cell));
const QJsonObject attachments = cell.value(QLatin1String("attachments")).toObject();
for (const QString& key : attachments.keys())
{
const QJsonValue& attachment = attachments.value(key);
const QString& mimeKey = Cantor::JupyterUtils::firstImageKey(attachment);
if (!mimeKey.isEmpty())
{
const QImage& image = Cantor::JupyterUtils::loadImage(attachment, mimeKey);
QUrl resourceUrl;
resourceUrl.setUrl(QLatin1String("attachment:")+key);
attachedImages.push_back(std::make_pair(resourceUrl, mimeKey));
m_textItem->document()->addResource(QTextDocument::ImageResource, resourceUrl, QVariant(image));
}
}
setPlainText(Cantor::JupyterUtils::getSource(cell));
m_textItem->document()->clearUndoRedoStacks();
}
QDomElement MarkdownEntry::toXml(QDomDocument& doc, KZip* archive)
{
if(!rendered)
plain = m_textItem->toPlainText();
QDomElement el = doc.createElement(QLatin1String("Markdown"));
el.setAttribute(QLatin1String("rendered"), (int)rendered);
QDomElement plainEl = doc.createElement(QLatin1String("Plain"));
plainEl.appendChild(doc.createTextNode(plain));
el.appendChild(plainEl);
QDomElement htmlEl = doc.createElement(QLatin1String("HTML"));
htmlEl.appendChild(doc.createTextNode(html));
el.appendChild(htmlEl);
QUrl url;
QString key;
for (const auto& data : attachedImages)
{
std::tie(url, key) = std::move(data);
QDomElement attachmentEl = doc.createElement(QLatin1String("Attachment"));
attachmentEl.setAttribute(QStringLiteral("url"), url.toString());
const QImage& image = m_textItem->document()->resource(QTextDocument::ImageResource, url).value<QImage>();
QByteArray ba;
QBuffer buffer(&ba);
buffer.open(QIODevice::WriteOnly);
image.save(&buffer, "PNG");
attachmentEl.appendChild(doc.createTextNode(QString::fromLatin1(ba.toBase64())));
el.appendChild(attachmentEl);
}
// If math rendered, then append result .pdf to archive
QTextCursor cursor = m_textItem->document()->find(QString(QChar::ObjectReplacementCharacter));
for (const auto& data : foundMath)
{
QDomElement mathEl = doc.createElement(QLatin1String("EmbeddedMath"));
mathEl.setAttribute(QStringLiteral("rendered"), data.second);
mathEl.appendChild(doc.createTextNode(data.first));
if (data.second)
{
bool foundNeededImage = false;
while(!cursor.isNull() && !foundNeededImage)
{
QTextImageFormat format=cursor.charFormat().toImageFormat();
if (format.hasProperty(Cantor::Renderer::CantorFormula))
{
const QString& latex = format.property(Cantor::Renderer::Code).toString();
const QString& delimiter = format.property(Cantor::Renderer::Delimiter).toString();
const QString& code = delimiter + latex + delimiter;
if (code == data.first)
{
const QUrl& url = QUrl::fromLocalFile(format.property(Cantor::Renderer::ImagePath).toString());
archive->addLocalFile(url.toLocalFile(), url.fileName());
mathEl.setAttribute(QStringLiteral("path"), url.fileName());
foundNeededImage = true;
}
}
cursor = m_textItem->document()->find(QString(QChar::ObjectReplacementCharacter), cursor);
}
}
el.appendChild(mathEl);
}
return el;
}
QJsonValue MarkdownEntry::toJupyterJson()
{
QJsonObject entry;
entry.insert(QLatin1String("cell_type"), QLatin1String("markdown"));
entry.insert(QLatin1String("metadata"), jupyterMetadata());
QJsonObject attachments;
QUrl url;
QString key;
for (const auto& data : attachedImages)
{
std::tie(url, key) = std::move(data);
const QImage& image = m_textItem->document()->resource(QTextDocument::ImageResource, url).value<QImage>();
QString attachmentKey = url.toString().remove(QLatin1String("attachment:"));
attachments.insert(attachmentKey, Cantor::JupyterUtils::packMimeBundle(image, key));
}
if (!attachments.isEmpty())
entry.insert(QLatin1String("attachments"), attachments);
Cantor::JupyterUtils::setSource(entry, plain);
return entry;
}
QString MarkdownEntry::toPlain(const QString& commandSep, const QString& commentStartingSeq, const QString& commentEndingSeq)
{
Q_UNUSED(commandSep);
if (commentStartingSeq.isEmpty())
return QString();
QString text(plain);
if (!commentEndingSeq.isEmpty())
return commentStartingSeq + text + commentEndingSeq + QLatin1String("\n");
return commentStartingSeq + text.replace(QLatin1String("\n"), QLatin1String("\n") + commentStartingSeq) + QLatin1String("\n");
}
void MarkdownEntry::interruptEvaluation()
{
}
bool MarkdownEntry::evaluate(EvaluationOption evalOp)
{
if(!rendered)
{
if (m_textItem->toPlainText() == plain && !html.isEmpty())
{
setRenderedHtml(html);
rendered = true;
for (auto iter = foundMath.begin(); iter != foundMath.end(); iter++)
iter->second = false;
markUpMath();
}
else
{
plain = m_textItem->toPlainText();
rendered = renderMarkdown(plain);
}
m_textItem->document()->clearUndoRedoStacks();
}
if (rendered && worksheet()->embeddedMathEnabled())
renderMath();
evaluateNext(evalOp);
return true;
}
bool MarkdownEntry::renderMarkdown(QString& plain)
{
#ifdef Discount_FOUND
QByteArray mdCharArray = plain.toUtf8();
MMIOT* mdHandle = mkd_string(mdCharArray.data(), mdCharArray.size()+1, 0);
if(!mkd_compile(mdHandle, MKD_LATEX | MKD_FENCEDCODE | MKD_GITHUBTAGS))
{
qDebug()<<"Failed to compile the markdown document";
mkd_cleanup(mdHandle);
return false;
}
char *htmlDocument;
int htmlSize = mkd_document(mdHandle, &htmlDocument);
html = QString::fromUtf8(htmlDocument, htmlSize);
char *latexData;
int latexDataSize = mkd_latextext(mdHandle, &latexData);
QStringList latexUnits = QString::fromUtf8(latexData, latexDataSize).split(QLatin1Char(31), QString::SkipEmptyParts);
foundMath.clear();
mkd_cleanup(mdHandle);
setRenderedHtml(html);
QTextCursor cursor(m_textItem->document());
for (const QString& latex : latexUnits)
foundMath.push_back(std::make_pair(latex, false));
markUpMath();
return true;
#else
Q_UNUSED(plain);
return false;
#endif
}
void MarkdownEntry::updateEntry()
{
QTextCursor cursor = m_textItem->document()->find(QString(QChar::ObjectReplacementCharacter));
while(!cursor.isNull())
{
QTextImageFormat format=cursor.charFormat().toImageFormat();
if (format.hasProperty(Cantor::Renderer::CantorFormula))
worksheet()->mathRenderer()->rerender(m_textItem->document(), format);
cursor = m_textItem->document()->find(QString(QChar::ObjectReplacementCharacter), cursor);
}
}
WorksheetCursor MarkdownEntry::search(const QString& pattern, unsigned flags,
QTextDocument::FindFlags qt_flags,
const WorksheetCursor& pos)
{
if (!(flags & WorksheetEntry::SearchText) ||
(pos.isValid() && pos.entry() != this))
return WorksheetCursor();
QTextCursor textCursor = m_textItem->search(pattern, qt_flags, pos);
if (textCursor.isNull())
return WorksheetCursor();
else
return WorksheetCursor(this, m_textItem, textCursor);
}
void MarkdownEntry::layOutForWidth(qreal w, bool force)
{
if (size().width() == w && !force)
return;
m_textItem->setGeometry(0, 0, w);
setSize(QSizeF(m_textItem->width(), m_textItem->height() + VerticalMargin));
}
bool MarkdownEntry::eventFilter(QObject* object, QEvent* event)
{
if(object == m_textItem)
{
if(event->type() == QEvent::GraphicsSceneMouseDoubleClick)
{
QGraphicsSceneMouseEvent* mouseEvent = dynamic_cast<QGraphicsSceneMouseEvent*>(event);
if(!mouseEvent) return false;
if(mouseEvent->button() == Qt::LeftButton)
{
if (rendered)
{
setPlainText(plain);
m_textItem->setCursorPosition(mouseEvent->pos());
m_textItem->textCursor().clearSelection();
rendered = false;
return true;
}
}
}
}
return false;
}
bool MarkdownEntry::wantToEvaluate()
{
return !rendered;
}
void MarkdownEntry::setRenderedHtml(const QString& html)
{
m_textItem->setHtml(html);
m_textItem->denyEditing();
}
void MarkdownEntry::setPlainText(const QString& plain)
{
QTextDocument* doc = m_textItem->document();
doc->setPlainText(plain);
m_textItem->setDocument(doc);
m_textItem->allowEditing();
}
void MarkdownEntry::renderMath()
{
QTextCursor cursor(m_textItem->document());
for (int i = 0; i < (int)foundMath.size(); i++)
if (foundMath[i].second == false)
renderMathExpression(i+1, foundMath[i].first);
}
void MarkdownEntry::handleMathRender(QSharedPointer<MathRenderResult> result)
{
if (!result->successful)
{
if (Settings::self()->showMathRenderError())
KMessageBox::error(worksheetView(), result->errorMessage, i18n("Cantor Math Error"));
else
qDebug() << "MarkdownEntry: math render failed with message" << result->errorMessage;
return;
}
setRenderedMath(result->jobId, result->renderedMath, result->uniqueUrl, result->image);
}
void MarkdownEntry::renderMathExpression(int jobId, QString mathCode)
{
QString latex;
Cantor::LatexRenderer::EquationType type;
std::tie(latex, type) = parseMathCode(mathCode);
if (!latex.isNull())
worksheet()->mathRenderer()->renderExpression(jobId, latex, type, this, SLOT(handleMathRender(QSharedPointer<MathRenderResult>)));
}
std::pair<QString, Cantor::LatexRenderer::EquationType> MarkdownEntry::parseMathCode(QString mathCode)
{
static const QLatin1String inlineDelimiter("$");
static const QLatin1String displayedDelimiter("$$");
- if (mathCode.startsWith(displayedDelimiter) && mathCode.endsWith(displayedDelimiter))
+ if (mathCode.startsWith(displayedDelimiter) && mathCode.endsWith(displayedDelimiter))
{
mathCode.remove(0, 2);
mathCode.chop(2);
if (mathCode[0] == QChar(6))
mathCode.remove(0, 1);
return std::make_pair(mathCode, Cantor::LatexRenderer::FullEquation);
}
else if (mathCode.startsWith(inlineDelimiter) && mathCode.endsWith(inlineDelimiter))
{
mathCode.remove(0, 1);
mathCode.chop(1);
if (mathCode[0] == QChar(6))
mathCode.remove(0, 1);
return std::make_pair(mathCode, Cantor::LatexRenderer::InlineEquation);
}
+ else if (mathCode.startsWith(QString::fromUtf8("\\begin{")) && mathCode.endsWith(QLatin1Char('}')))
+ {
+ if (mathCode[1] == QChar(6))
+ mathCode.remove(1, 1);
+
+ return std::make_pair(mathCode, Cantor::LatexRenderer::CustomEquation);
+ }
else
return std::make_pair(QString(), Cantor::LatexRenderer::InlineEquation);
}
void MarkdownEntry::setRenderedMath(int jobId, const QTextImageFormat& format, const QUrl& internal, const QImage& image)
{
if ((int)foundMath.size() < jobId)
return;
const auto& iter = foundMath.begin() + jobId-1;
QTextCursor cursor = findMath(jobId);
const QString delimiter = format.property(Cantor::Renderer::Delimiter).toString();
QString searchText = delimiter + format.property(Cantor::Renderer::Code).toString() + delimiter;
+ Cantor::LatexRenderer::EquationType type
+ = (Cantor::LatexRenderer::EquationType)format.intProperty(Cantor::Renderer::CantorFormula);
+
// From findMath we will be first symbol of math expression
// So in order to select all symbols of the expression, we need to go to previous symbol first
// But it working strange sometimes: some times we need to go to previous character, sometimes not
// So the code tests that we on '$' symbol and if it isn't true, then we revert back
cursor.movePosition(QTextCursor::PreviousCharacter);
- if (m_textItem->document()->characterAt(cursor.position()) != QLatin1Char('$'))
+ bool withDollarDelimiter = type == Cantor::LatexRenderer::InlineEquation || type == Cantor::LatexRenderer::FullEquation;
+ if (withDollarDelimiter && m_textItem->document()->characterAt(cursor.position()) != QLatin1Char('$'))
+ cursor.movePosition(QTextCursor::NextCharacter);
+ else if (type == Cantor::LatexRenderer::CustomEquation && m_textItem->document()->characterAt(cursor.position()) != QLatin1Char('\\') )
cursor.movePosition(QTextCursor::NextCharacter);
cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, searchText.size());
if (!cursor.isNull())
{
m_textItem->document()->addResource(QTextDocument::ImageResource, internal, QVariant(image));
- Cantor::LatexRenderer::EquationType type = (Cantor::LatexRenderer::EquationType)format.intProperty(Cantor::Renderer::CantorFormula);
// Dont add new line for $$...$$ on document's begin and end
// And if we in block, which haven't non-space characters except out math expression
// In another sitation, Cantor will move rendered image into another QTextBlock
QTextCursor prevSymCursor = m_textItem->document()->find(QRegExp(QLatin1String("[^\\s]")), cursor, QTextDocument::FindBackward);
if (type == Cantor::LatexRenderer::FullEquation
&& cursor.selectionStart() != 0
&& prevSymCursor.block() == cursor.block()
)
{
cursor.insertBlock();
cursor.setPosition(prevSymCursor.position()+2, QTextCursor::KeepAnchor);
cursor.removeSelectedText();
}
cursor.insertText(QString(QChar::ObjectReplacementCharacter), format);
bool atDocEnd = cursor.position() == m_textItem->document()->characterCount()-1;
QTextCursor nextSymCursor = m_textItem->document()->find(QRegExp(QLatin1String("[^\\s]")), cursor);
if (type == Cantor::LatexRenderer::FullEquation && !atDocEnd && nextSymCursor.block() == cursor.block())
{
cursor.setPosition(nextSymCursor.position()-1, QTextCursor::KeepAnchor);
cursor.removeSelectedText();
cursor.insertBlock();
}
// Set that the formulas is rendered
iter->second = true;
m_textItem->document()->clearUndoRedoStacks();
}
}
QTextCursor MarkdownEntry::findMath(int id)
{
QTextCursor cursor(m_textItem->document());
do
{
QTextCharFormat format = cursor.charFormat();
if (format.intProperty(JobProperty) == id)
break;
}
while (cursor.movePosition(QTextCursor::NextCharacter));
return cursor;
}
void MarkdownEntry::markUpMath()
{
QTextCursor cursor(m_textItem->document());
for (int i = 0; i < (int)foundMath.size(); i++)
{
if (foundMath[i].second)
continue;
QString searchText = foundMath[i].first;
searchText.replace(QRegExp(QLatin1String("\\s+")), QLatin1String(" "));
cursor = m_textItem->document()->find(searchText, cursor);
// Mark up founded math code
QTextCharFormat format = cursor.charFormat();
// Use index+1 in math array as property tag
format.setProperty(JobProperty, i+1);
// We found the math expression, so remove 'marker' (ACII symbol 'Acknowledgement')
// The marker have been placed after "$" or "$$"
// We remove the marker, only if it presents
QString codeWithoutMarker = foundMath[i].first;
if (searchText.startsWith(QLatin1String("$$")))
{
if (codeWithoutMarker[2] == QChar(6))
codeWithoutMarker.remove(2, 1);
}
else if (searchText.startsWith(QLatin1String("$")))
{
if (codeWithoutMarker[1] == QChar(6))
codeWithoutMarker.remove(1, 1);
}
+ else if (searchText.startsWith(QLatin1String("\\")))
+ {
+ if (codeWithoutMarker[1] == QChar(6))
+ codeWithoutMarker.remove(1, 1);
+ }
cursor.insertText(codeWithoutMarker, format);
}
}
void MarkdownEntry::insertImage()
{
const QString& filename = QFileDialog::getOpenFileName(worksheet()->worksheetView(), i18n("Choose Image"), QString(), i18n("Images (*.png *.bmp *.jpg *.svg)"));
if (!filename.isEmpty())
{
QImageReader reader(filename);
const QImage img = reader.read();
if (!img.isNull())
{
const QString& name = QFileInfo(filename).fileName();
QUrl url;
url.setScheme(QLatin1String("attachment"));
url.setPath(name);
attachedImages.push_back(std::make_pair(url, QLatin1String("image/png")));
m_textItem->document()->addResource(QTextDocument::ImageResource, url, QVariant(img));
QTextCursor cursor = m_textItem->textCursor();
cursor.insertText(QString::fromLatin1("![%1](attachment:%1)").arg(name));
animateSizeChange();
}
else
KMessageBox::error(worksheetView(), i18n("Cantor failed to read image with error \"%1\"", reader.errorString()), i18n("Cantor"));
}
}
void MarkdownEntry::clearAttachments()
{
for (auto& attachment: attachedImages)
{
const QUrl& url = attachment.first;
m_textItem->document()->addResource(QTextDocument::ImageResource, url, QVariant());
}
attachedImages.clear();
animateSizeChange();
}
diff --git a/src/mathrendertask.cpp b/src/mathrendertask.cpp
index 0ddcc82b..bf2a7444 100644
--- a/src/mathrendertask.cpp
+++ b/src/mathrendertask.cpp
@@ -1,224 +1,235 @@
/*
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 <warquark@gmail.com>
*/
#include "mathrendertask.h"
#include <QTemporaryFile>
#include <QStandardPaths>
#include <QUuid>
#include <QDir>
#include <KColorScheme>
#include <KProcess>
#include <QScopedPointer>
#include <QMutex>
#include <QApplication>
#include <QDebug>
#include "lib/renderer.h"
#include "lib/latexrenderer.h"
-static const QLatin1String mathTex("\\documentclass{standalone}"\
+static const QLatin1String mathTex("\\documentclass%9{standalone}"\
"\\usepackage{amsfonts,amssymb}"\
"\\usepackage{amsmath}"\
"\\usepackage[utf8]{inputenc}"\
"\\usepackage{color}"\
/*
"\\setlength\\textwidth{5in}"\
"\\setlength{\\parindent}{0pt}"\
"\\pagestyle{empty}"\
*/
"\\begin{document}"\
"\\pagecolor[rgb]{%1,%2,%3}"\
"\\color[rgb]{%4,%5,%6}"\
"\\fontsize{%7}{%7}\\selectfont"\
"%8"\
"\\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)
{}
void MathRenderTask::setHandler(const QObject* receiver, const char* resultHandler)
{
connect(this, SIGNAL(finish(QSharedPointer<MathRenderResult>)), receiver, resultHandler);
}
void MathRenderTask::run()
{
QSharedPointer<MathRenderResult> result(new MathRenderResult());
const QString& tempDir=QStandardPaths::writableLocation(QStandardPaths::TempLocation);
QTemporaryFile texFile(tempDir + QDir::separator() + QLatin1String("cantor_tex-XXXXXX.tex"));
texFile.open();
// Verify that standalone.cls available for rendering and could be founded
if (!tempDir.contains(QLatin1String("standalone.cls")))
{
QString file = QStandardPaths::locate(QStandardPaths::AppDataLocation, QLatin1String("latex/standalone.cls"));
if (file.isEmpty())
file = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("cantor/latex/standalone.cls"));
if (file.isEmpty())
{
result->successful = false;
result->errorMessage = QString::fromLatin1("needed for math render standalone.cls file not found in Cantor data directory");
finalize(result);
return;
}
else
QFile::copy(file, tempDir + QDir::separator() + QLatin1String("standalone.cls"));
}
QString expressionTex=mathTex;
KColorScheme scheme(QPalette::Active);
const QColor &backgroundColor=scheme.background().color();
const QColor &foregroundColor=scheme.foreground().color();
expressionTex=expressionTex
.arg(backgroundColor.redF()).arg(backgroundColor.greenF()).arg(backgroundColor.blueF())
.arg(foregroundColor.redF()).arg(foregroundColor.greenF()).arg(foregroundColor.blueF());
int fontPointSize = QApplication::font().pointSize();
expressionTex=expressionTex.arg(fontPointSize);
switch(m_type)
{
- case Cantor::LatexRenderer::FullEquation: expressionTex=expressionTex.arg(eqnHeader); break;
- case Cantor::LatexRenderer::InlineEquation: expressionTex=expressionTex.arg(inlineEqnHeader); break;
+ 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;
}
expressionTex=expressionTex.arg(m_code);
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); //Usefull 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"));
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<MathRenderResult> result)
{
emit finish(result);
deleteLater();
}
std::pair<QTextImageFormat, QImage> 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));
}
diff --git a/thirdparty/discount-2.2.6-patched.tar b/thirdparty/discount-2.2.6-patched.tar
index 73d517ee..28da1cf3 100644
Binary files a/thirdparty/discount-2.2.6-patched.tar and b/thirdparty/discount-2.2.6-patched.tar differ