File indexing completed on 2024-04-28 11:20:57
0001 /* 0002 SPDX-License-Identifier: GPL-2.0-or-later 0003 SPDX-FileCopyrightText: 2009 Alexander Rieder <alexanderrieder@gmail.com> 0004 SPDX-FileCopyrightText: 2012 Martin Kuettler <martin.kuettler@gmail.com> 0005 */ 0006 0007 #include "latexentry.h" 0008 0009 #include "worksheet.h" 0010 #include "lib/renderer.h" 0011 #include "lib/jupyterutils.h" 0012 #include "lib/defaulthighlighter.h" 0013 #include "lib/latexrenderer.h" 0014 #include "config-cantor.h" 0015 0016 #include <QTextCursor> 0017 #include <QStandardPaths> 0018 #include <QDir> 0019 #include <QDebug> 0020 #include <QBuffer> 0021 #include <QUuid> 0022 #include <QJsonValue> 0023 #include <QJsonObject> 0024 #include <QJsonArray> 0025 0026 #include <KZip> 0027 #include <KLocalizedString> 0028 0029 LatexEntry::LatexEntry(Worksheet* worksheet) : WorksheetEntry(worksheet), m_textItem(new WorksheetTextItem(this, Qt::TextEditorInteraction)) 0030 { 0031 m_textItem->installEventFilter(this); 0032 0033 connect(m_textItem, &WorksheetTextItem::moveToPrevious, this, &LatexEntry::moveToPreviousEntry); 0034 connect(m_textItem, &WorksheetTextItem::moveToNext, this, &LatexEntry::moveToNextEntry); 0035 connect(m_textItem, SIGNAL(execute()), this, SLOT(evaluate())); 0036 } 0037 0038 void LatexEntry::populateMenu(QMenu* menu, QPointF pos) 0039 { 0040 bool imageSelected = false; 0041 QTextCursor cursor = m_textItem->textCursor(); 0042 const QChar repl = QChar::ObjectReplacementCharacter; 0043 if (cursor.hasSelection()) { 0044 QString selection = m_textItem->textCursor().selectedText(); 0045 imageSelected = selection.contains(repl); 0046 } else { 0047 // we need to try both the current cursor and the one after the that 0048 cursor = m_textItem->cursorForPosition(pos); 0049 for (int i = 2; i; --i) { 0050 int p = cursor.position(); 0051 if (m_textItem->document()->characterAt(p-1) == repl && 0052 cursor.charFormat().hasProperty(Cantor::Renderer::CantorFormula)) { 0053 m_textItem->setTextCursor(cursor); 0054 imageSelected = true; 0055 break; 0056 } 0057 cursor.movePosition(QTextCursor::NextCharacter); 0058 } 0059 } 0060 if (imageSelected) { 0061 menu->addAction(i18n("Show LaTeX code"), this, SLOT(resolveImagesAtCursor())); 0062 menu->addSeparator(); 0063 } 0064 WorksheetEntry::populateMenu(menu, pos); 0065 } 0066 0067 int LatexEntry::type() const 0068 { 0069 return Type; 0070 } 0071 0072 bool LatexEntry::isEmpty() 0073 { 0074 return m_textItem->document()->isEmpty(); 0075 } 0076 0077 bool LatexEntry::acceptRichText() 0078 { 0079 return false; 0080 } 0081 0082 bool LatexEntry::focusEntry(int pos, qreal xCoord) 0083 { 0084 if (aboutToBeRemoved()) 0085 return false; 0086 m_textItem->setFocusAt(pos, xCoord); 0087 return true; 0088 } 0089 0090 void LatexEntry::setContent(const QString& content) 0091 { 0092 m_latex = content; 0093 m_textItem->setPlainText(m_latex); 0094 } 0095 0096 void LatexEntry::setContent(const QDomElement& content, const KZip& file) 0097 { 0098 m_latex = content.text(); 0099 qDebug() << m_latex; 0100 0101 m_textItem->document()->clear(); 0102 QTextCursor cursor = m_textItem->textCursor(); 0103 cursor.movePosition(QTextCursor::Start); 0104 0105 QString imagePath; 0106 bool useLatexCode = true; 0107 0108 if(content.hasAttribute(QLatin1String("filename"))) 0109 { 0110 const KArchiveEntry* imageEntry=file.directory()->entry(content.attribute(QLatin1String("filename"))); 0111 if (imageEntry&&imageEntry->isFile()) 0112 { 0113 const KArchiveFile* imageFile=static_cast<const KArchiveFile*>(imageEntry); 0114 const QString& dir=QStandardPaths::writableLocation(QStandardPaths::TempLocation); 0115 imageFile->copyTo(dir); 0116 imagePath = dir + QDir::separator() + imageFile->name(); 0117 0118 #ifdef LIBSPECTRE_FOUND 0119 QString uuid = Cantor::LatexRenderer::genUuid(); 0120 m_renderedFormat = worksheet()->renderer()->render(m_textItem->document(), Cantor::Renderer::EPS, QUrl::fromLocalFile(imagePath), uuid); 0121 qDebug()<<"rendering successful? " << !m_renderedFormat.name().isEmpty(); 0122 0123 m_renderedFormat.setProperty(Cantor::Renderer::CantorFormula, Cantor::Renderer::LatexFormula); 0124 m_renderedFormat.setProperty(Cantor::Renderer::ImagePath, imagePath); 0125 m_renderedFormat.setProperty(Cantor::Renderer::Code, m_latex); 0126 0127 cursor.insertText(QString(QChar::ObjectReplacementCharacter), m_renderedFormat); 0128 useLatexCode = false; 0129 m_textItem->denyEditing(); 0130 #endif 0131 } 0132 } 0133 0134 if (useLatexCode && content.hasAttribute(QLatin1String("image"))) 0135 { 0136 const QByteArray& ba = QByteArray::fromBase64(content.attribute(QLatin1String("image")).toLatin1()); 0137 QImage image; 0138 if (image.loadFromData(ba)) 0139 { 0140 // Create unique internal url for this loaded image 0141 QUrl internal; 0142 internal.setScheme(QLatin1String("internal")); 0143 internal.setPath(QUuid::createUuid().toString()); 0144 0145 m_textItem->document()->addResource(QTextDocument::ImageResource, internal, QVariant(image)); 0146 0147 m_renderedFormat.setName(internal.url()); 0148 m_renderedFormat.setWidth(image.width()); 0149 m_renderedFormat.setHeight(image.height()); 0150 0151 m_renderedFormat.setProperty(Cantor::Renderer::CantorFormula, Cantor::Renderer::LatexFormula); 0152 if (!imagePath.isEmpty()) 0153 m_renderedFormat.setProperty(Cantor::Renderer::ImagePath, imagePath); 0154 m_renderedFormat.setProperty(Cantor::Renderer::Code, m_latex); 0155 0156 cursor.insertText(QString(QChar::ObjectReplacementCharacter), m_renderedFormat); 0157 useLatexCode = false; 0158 m_textItem->denyEditing(); 0159 } 0160 } 0161 0162 if (useLatexCode) 0163 cursor.insertText(m_latex); 0164 } 0165 0166 void LatexEntry::setContentFromJupyter(const QJsonObject& cell) 0167 { 0168 if (!Cantor::JupyterUtils::isCodeCell(cell)) 0169 return; 0170 0171 m_textItem->document()->clear(); 0172 QTextCursor cursor = m_textItem->textCursor(); 0173 cursor.movePosition(QTextCursor::Start); 0174 0175 bool useLatexCode = true; 0176 0177 QString source = Cantor::JupyterUtils::getSource(cell); 0178 m_latex = source.remove(QLatin1String("%%latex\n")); 0179 0180 QJsonArray outputs = cell.value(Cantor::JupyterUtils::outputsKey).toArray(); 0181 if (outputs.size() == 1 && Cantor::JupyterUtils::isJupyterDisplayOutput(outputs[0])) 0182 { 0183 const QJsonObject data = outputs[0].toObject().value(Cantor::JupyterUtils::dataKey).toObject(); 0184 const QImage& image = Cantor::JupyterUtils::loadImage(data, Cantor::JupyterUtils::pngMime); 0185 if (!image.isNull()) 0186 { 0187 QUrl internal; 0188 internal.setScheme(QLatin1String("internal")); 0189 internal.setPath(QUuid::createUuid().toString()); 0190 0191 m_textItem->document()->addResource(QTextDocument::ImageResource, internal, QVariant(image)); 0192 0193 m_renderedFormat.setName(internal.url()); 0194 m_renderedFormat.setWidth(image.width()); 0195 m_renderedFormat.setHeight(image.height()); 0196 0197 m_renderedFormat.setProperty(Cantor::Renderer::CantorFormula, Cantor::Renderer::LatexFormula); 0198 m_renderedFormat.setProperty(Cantor::Renderer::Code, m_latex); 0199 0200 cursor.insertText(QString(QChar::ObjectReplacementCharacter), m_renderedFormat); 0201 useLatexCode = false; 0202 m_textItem->denyEditing(); 0203 } 0204 } 0205 0206 if (useLatexCode) 0207 { 0208 cursor.insertText(m_latex); 0209 m_latex.clear(); // We don't render image, so clear latex code cache 0210 } 0211 } 0212 0213 QJsonValue LatexEntry::toJupyterJson() 0214 { 0215 QJsonObject entry; 0216 entry.insert(Cantor::JupyterUtils::cellTypeKey, QLatin1String("code")); 0217 entry.insert(Cantor::JupyterUtils::executionCountKey, QJsonValue()); 0218 0219 QJsonObject metadata, cantorMetadata; 0220 cantorMetadata.insert(QLatin1String("latex_entry"), true); 0221 metadata.insert(Cantor::JupyterUtils::cantorMetadataKey, cantorMetadata); 0222 entry.insert(Cantor::JupyterUtils::metadataKey, metadata); 0223 0224 QJsonArray outputs; 0225 0226 QTextCursor cursor = m_textItem->document()->find(QString(QChar::ObjectReplacementCharacter)); 0227 if (!cursor.isNull()) 0228 { 0229 QTextImageFormat format=cursor.charFormat().toImageFormat(); 0230 0231 QUrl internal; 0232 internal.setUrl(format.name()); 0233 const QImage& image = m_textItem->document()->resource(QTextDocument::ImageResource, internal).value<QImage>(); 0234 if (!image.isNull()) 0235 { 0236 QByteArray ba; 0237 QBuffer buffer(&ba); 0238 buffer.open(QIODevice::WriteOnly); 0239 image.save(&buffer, "PNG"); 0240 0241 // Add image result with latex rendered image to this Jupyter code cell 0242 QJsonObject imageResult; 0243 imageResult.insert(Cantor::JupyterUtils::outputTypeKey, QLatin1String("display_data")); 0244 0245 QJsonObject data; 0246 data.insert(Cantor::JupyterUtils::pngMime, Cantor::JupyterUtils::toJupyterMultiline(QString::fromLatin1(ba.toBase64()))); 0247 imageResult.insert(QLatin1String("data"), data); 0248 0249 imageResult.insert(Cantor::JupyterUtils::metadataKey, QJsonObject()); 0250 0251 outputs.append(imageResult); 0252 } 0253 } 0254 entry.insert(Cantor::JupyterUtils::outputsKey, outputs); 0255 0256 const QString& latex = latexCode(); 0257 Cantor::JupyterUtils::setSource(entry, QLatin1String("%%latex\n") + latex); 0258 0259 return entry; 0260 } 0261 0262 QDomElement LatexEntry::toXml(QDomDocument& doc, KZip* archive) 0263 { 0264 QDomElement el = doc.createElement(QLatin1String("Latex")); 0265 el.appendChild( doc.createTextNode( latexCode() )); 0266 0267 QTextCursor cursor = m_textItem->document()->find(QString(QChar::ObjectReplacementCharacter)); 0268 if (!cursor.isNull()) 0269 { 0270 QTextImageFormat format=cursor.charFormat().toImageFormat(); 0271 QString fileName = format.property(Cantor::Renderer::ImagePath).toString(); 0272 // Check, if eps file exists, and if not true, rerender latex code 0273 bool isEpsFileExists = QFile::exists(fileName); 0274 0275 #ifdef LIBSPECTRE_FOUND 0276 if (!isEpsFileExists && renderLatexCode()) 0277 { 0278 cursor = m_textItem->document()->find(QString(QChar::ObjectReplacementCharacter)); 0279 format=cursor.charFormat().toImageFormat(); 0280 fileName = format.property(Cantor::Renderer::ImagePath).toString(); 0281 isEpsFileExists = QFile::exists(fileName); 0282 } 0283 #endif 0284 0285 if (isEpsFileExists && archive) 0286 { 0287 const QUrl& url=QUrl::fromLocalFile(fileName); 0288 archive->addLocalFile(url.toLocalFile(), url.fileName()); 0289 el.setAttribute(QLatin1String("filename"), url.fileName()); 0290 } 0291 0292 // Save also rendered QImage, if exist. 0293 QUrl internal; 0294 internal.setUrl(format.name()); 0295 0296 const QImage& image = m_textItem->document()->resource(QTextDocument::ImageResource, internal).value<QImage>(); 0297 if (!image.isNull()) 0298 { 0299 QByteArray ba; 0300 QBuffer buffer(&ba); 0301 buffer.open(QIODevice::WriteOnly); 0302 image.save(&buffer, "PNG"); 0303 el.setAttribute(QLatin1String("image"), QString::fromLatin1(ba.toBase64())); 0304 } 0305 } 0306 0307 return el; 0308 } 0309 0310 QString LatexEntry::toPlain(const QString& commandSep, const QString& commentStartingSeq, const QString& commentEndingSeq) 0311 { 0312 Q_UNUSED(commandSep); 0313 0314 if (commentStartingSeq.isEmpty()) 0315 return QString(); 0316 0317 QString text = latexCode(); 0318 if (!commentEndingSeq.isEmpty()) 0319 return commentStartingSeq + text + commentEndingSeq + QLatin1String("\n"); 0320 return commentStartingSeq + text.replace(QLatin1String("\n"), QLatin1String("\n") + commentStartingSeq) + QLatin1String("\n"); 0321 } 0322 0323 bool LatexEntry::evaluate(EvaluationOption evalOp) 0324 { 0325 bool success = false; 0326 0327 if (isOneImageOnly()) 0328 { 0329 success = true; 0330 } 0331 else 0332 { 0333 if (m_latex == latexCode()) 0334 { 0335 bool renderWasSuccessful = !m_renderedFormat.name().isEmpty(); 0336 if (renderWasSuccessful) 0337 { 0338 QTextCursor cursor = m_textItem->textCursor(); 0339 cursor.movePosition(QTextCursor::Start); 0340 cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor); 0341 cursor.insertText(QString(QChar::ObjectReplacementCharacter), m_renderedFormat); 0342 m_textItem->denyEditing(); 0343 } 0344 else 0345 { 0346 success = renderLatexCode(); 0347 } 0348 } 0349 else 0350 { 0351 m_latex = latexCode(); 0352 success = renderLatexCode(); 0353 } 0354 } 0355 0356 qDebug()<<"rendering successful? "<<success; 0357 0358 evaluateNext(evalOp); 0359 return success; 0360 } 0361 0362 void LatexEntry::updateEntry() 0363 { 0364 QTextCursor cursor = m_textItem->document()->find(QString(QChar::ObjectReplacementCharacter)); 0365 while (!cursor.isNull()) 0366 { 0367 qDebug()<<"found a formula... rendering the eps..."; 0368 QTextImageFormat format=cursor.charFormat().toImageFormat(); 0369 const QUrl& url=QUrl::fromLocalFile(format.property(Cantor::Renderer::ImagePath).toString()); 0370 QSizeF s = worksheet()->renderer()->renderToResource(m_textItem->document(), Cantor::Renderer::EPS, url, QUrl(format.name())); 0371 qDebug()<<"rendering successful? "<< s.isValid(); 0372 0373 cursor.movePosition(QTextCursor::NextCharacter); 0374 0375 cursor = m_textItem->document()->find(QString(QChar::ObjectReplacementCharacter), cursor); 0376 } 0377 } 0378 0379 bool LatexEntry::eventFilter(QObject* object, QEvent* event) 0380 { 0381 if(object == m_textItem) 0382 { 0383 if (event->type() == QEvent::GraphicsSceneMouseDoubleClick) 0384 { 0385 // One image if we have rendered entry 0386 if (isOneImageOnly()) 0387 { 0388 QTextCursor cursor = m_textItem->textCursor(); 0389 if (!cursor.hasSelection()) 0390 cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor); 0391 0392 cursor.insertText(m_textItem->resolveImages(cursor)); 0393 m_textItem->allowEditing(); 0394 return true; 0395 } 0396 } 0397 else if (event->type() == QEvent::KeyPress) 0398 { 0399 auto* key_event = static_cast<QKeyEvent*>(event); 0400 if (key_event->matches(QKeySequence::Cancel)) 0401 { 0402 QTextCursor cursor = m_textItem->textCursor(); 0403 cursor.movePosition(QTextCursor::Start); 0404 cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor); 0405 cursor.insertText(QString(QChar::ObjectReplacementCharacter), m_renderedFormat); 0406 m_textItem->denyEditing(); 0407 return true; 0408 } 0409 } 0410 } 0411 return false; 0412 } 0413 0414 QString LatexEntry::latexCode() 0415 { 0416 QTextCursor cursor = m_textItem->textCursor(); 0417 cursor.movePosition(QTextCursor::Start); 0418 cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor); 0419 0420 QString code = m_textItem->resolveImages(cursor); 0421 code.replace(QChar::ParagraphSeparator, QLatin1Char('\n')); //Replace the U+2029 paragraph break by a Normal Newline 0422 code.replace(QChar::LineSeparator, QLatin1Char('\n')); //Replace the line break by a Normal Newline 0423 return code; 0424 } 0425 0426 bool LatexEntry::isOneImageOnly() 0427 { 0428 QTextCursor cursor = m_textItem->textCursor(); 0429 cursor.movePosition(QTextCursor::Start); 0430 cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor); 0431 0432 return (cursor.selectionEnd() == 1 && cursor.selectedText() == QString(QChar::ObjectReplacementCharacter)); 0433 } 0434 0435 int LatexEntry::searchText(const QString& text, const QString& pattern, 0436 QTextDocument::FindFlags qt_flags) 0437 { 0438 Qt::CaseSensitivity caseSensitivity; 0439 if (qt_flags & QTextDocument::FindCaseSensitively) 0440 caseSensitivity = Qt::CaseSensitive; 0441 else 0442 caseSensitivity = Qt::CaseInsensitive; 0443 0444 int position; 0445 if (qt_flags & QTextDocument::FindBackward) 0446 position = text.lastIndexOf(pattern, -1, caseSensitivity); 0447 else 0448 position = text.indexOf(pattern, 0, caseSensitivity); 0449 0450 return position; 0451 } 0452 0453 WorksheetCursor LatexEntry::search(const QString& pattern, unsigned flags, 0454 QTextDocument::FindFlags qt_flags, 0455 const WorksheetCursor& pos) 0456 { 0457 if (!(flags & WorksheetEntry::SearchLaTeX)) 0458 return WorksheetCursor(); 0459 if (pos.isValid() && (pos.entry() != this || pos.textItem() != m_textItem)) 0460 return WorksheetCursor(); 0461 0462 QTextCursor textCursor = m_textItem->search(pattern, qt_flags, pos); 0463 int position = 0; 0464 QString latex; 0465 const QString repl = QString(QChar::ObjectReplacementCharacter); 0466 QTextCursor latexCursor = m_textItem->search(repl, qt_flags, pos); 0467 0468 while (!latexCursor.isNull()) { 0469 latex = m_textItem->resolveImages(latexCursor); 0470 position = searchText(latex, pattern, qt_flags); 0471 if (position >= 0) { 0472 break; 0473 } 0474 WorksheetCursor c(this, m_textItem, latexCursor); 0475 latexCursor = m_textItem->search(repl, qt_flags, c); 0476 } 0477 0478 if (latexCursor.isNull()) { 0479 if (textCursor.isNull()) 0480 return WorksheetCursor(); 0481 else 0482 return WorksheetCursor(this, m_textItem, textCursor); 0483 } else { 0484 if (textCursor.isNull() || latexCursor < textCursor) { 0485 int start = latexCursor.selectionStart(); 0486 latexCursor.insertText(latex); 0487 QTextCursor c = m_textItem->textCursor(); 0488 c.setPosition(start + position); 0489 c.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, 0490 pattern.length()); 0491 return WorksheetCursor(this, m_textItem, c); 0492 } else { 0493 return WorksheetCursor(this, m_textItem, textCursor); 0494 } 0495 } 0496 } 0497 0498 void LatexEntry::layOutForWidth(qreal entry_zone_x, qreal w, bool force) 0499 { 0500 if (size().width() == w && m_textItem->pos().x() == entry_zone_x && !force) 0501 return; 0502 0503 const qreal margin = worksheet()->isPrinting() ? 0 : RightMargin; 0504 0505 m_textItem->setGeometry(entry_zone_x, 0, w - margin - entry_zone_x); 0506 setSize(QSizeF(m_textItem->width() + margin + entry_zone_x, m_textItem->height() + VerticalMargin)); 0507 } 0508 0509 bool LatexEntry::wantToEvaluate() 0510 { 0511 return !isOneImageOnly(); 0512 } 0513 0514 bool LatexEntry::renderLatexCode() 0515 { 0516 bool success = false; 0517 QString latex = latexCode(); 0518 m_renderedFormat = QTextImageFormat(); // clear rendered image 0519 Cantor::LatexRenderer* renderer = new Cantor::LatexRenderer(this); 0520 renderer->setLatexCode(latex); 0521 renderer->setEquationOnly(false); 0522 renderer->setMethod(Cantor::LatexRenderer::LatexMethod); 0523 renderer->renderBlocking(); 0524 0525 if (renderer->renderingSuccessful()) 0526 { 0527 Cantor::Renderer* epsRend = worksheet()->renderer(); 0528 m_renderedFormat = epsRend->render(m_textItem->document(), renderer); 0529 success = !m_renderedFormat.name().isEmpty(); 0530 } 0531 else 0532 qWarning() << "Fail to render LatexEntry with error " << renderer->errorMessage(); 0533 0534 if(success) 0535 { 0536 QTextCursor cursor = m_textItem->textCursor(); 0537 cursor.movePosition(QTextCursor::Start); 0538 cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor); 0539 cursor.insertText(QString(QChar::ObjectReplacementCharacter), m_renderedFormat); 0540 m_textItem->denyEditing(); 0541 } 0542 0543 delete renderer; 0544 return success; 0545 } 0546 0547 bool LatexEntry::isConvertableToLatexEntry(const QJsonObject& cell) 0548 { 0549 if (!Cantor::JupyterUtils::isCodeCell(cell)) 0550 return false; 0551 0552 const QString& source = Cantor::JupyterUtils::getSource(cell); 0553 0554 return source.startsWith(QLatin1String("%%latex\n")); 0555 } 0556 0557 void LatexEntry::resolveImagesAtCursor() 0558 { 0559 QTextCursor cursor = m_textItem->textCursor(); 0560 if (!cursor.hasSelection()) 0561 cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor); 0562 cursor.insertText(m_textItem->resolveImages(cursor)); 0563 } 0564 0565 QString LatexEntry::plain() const 0566 { 0567 return m_textItem->toPlainText(); 0568 }