Home
Phabricator
Search
Log In
Files
F16131074
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Subscribers
None
File Metadata
Details
File Info
Storage
Attached
Created
Sat, May 4, 10:51 PM
Size
46 KB
Mime Type
application/octet-stream
Expires
Mon, May 6, 10:51 PM (2 d)
Engine
blob
Format
Raw Data
Handle
8976868
Attached To
R55 Cantor
View Options
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
Log In to Comment