File indexing completed on 2024-04-28 11:20:47

0001 /*
0002     SPDX-License-Identifier: GPL-2.0-or-later
0003     SPDX-FileCopyrightText: 2009 Alexander Rieder <alexanderrieder@gmail.com>
0004     SPDX-FileCopyrightText: 2022 by Alexander Semke (alexander.semke@web.de)
0005 */
0006 
0007 #include "imageresult.h"
0008 #include "jupyterutils.h"
0009 using namespace Cantor;
0010 
0011 #include <QApplication>
0012 #include <QDesktopWidget>
0013 #include <QDebug>
0014 #include <QFile>
0015 #include <QImage>
0016 #include <QImageWriter>
0017 #include <QPainter>
0018 #include <QSvgRenderer>
0019 #include <QTemporaryFile>
0020 
0021 #include <KZip>
0022 
0023 #include <poppler-qt5.h>
0024 
0025 class Cantor::ImageResultPrivate
0026 {
0027   public:
0028     ImageResultPrivate() = default;
0029 
0030     QUrl url;
0031     QImage img;
0032     QString alt;
0033     QSize displaySize;
0034     QString extension;
0035     QByteArray data; // byte array used to store the contnent of PDF and SVG files
0036 
0037     QString originalFormat{JupyterUtils::pngMime};
0038     QString svgContent; // HACK: qt can't easily render svg, so, if we load the result from Jupyter svg image, store original svg
0039 };
0040 
0041 ImageResult::ImageResult(const QUrl &url, const QString& alt) :  d(new ImageResultPrivate)
0042 {
0043     d->url = url;
0044     d->alt = alt;
0045     d->extension = url.toLocalFile().right(3).toLower();
0046 
0047     if (d->extension == QLatin1String("pdf") || d->extension == QLatin1String("svg")) // vector formats
0048     {
0049         QFile file(url.toLocalFile());
0050         if (!file.open(QIODevice::ReadOnly))
0051             return;
0052 
0053         d->data = file.readAll();
0054         if (d->data.isEmpty())
0055             return;
0056 
0057         if (d->extension == QLatin1String("pdf"))
0058         {
0059             auto* document = Poppler::Document::loadFromData(d->data);
0060             if (!document) {
0061                 qDebug()<< "Failed to process the byte array of the PDF file " << url.toLocalFile();
0062                 delete document;
0063                 return;
0064             }
0065 
0066             auto* page = document->page(0);
0067             if (!page) {
0068                 qDebug() << "Failed to process the first page in the PDF file.";
0069                 delete document;
0070                 return;
0071             }
0072 
0073             document->setRenderHint(Poppler::Document::TextAntialiasing);
0074             document->setRenderHint(Poppler::Document::Antialiasing);
0075             document->setRenderHint(Poppler::Document::TextHinting);
0076             document->setRenderHint(Poppler::Document::TextSlightHinting);
0077             document->setRenderHint(Poppler::Document::ThinLineSolid);
0078 
0079             const static int dpi = QApplication::desktop()->logicalDpiX();
0080             d->img = page->renderToImage(dpi, dpi);
0081 
0082             delete page;
0083             delete document;
0084         }
0085         else
0086         {
0087             QSvgRenderer renderer(d->data);
0088 
0089             // SVG document size is in points, convert to pixels
0090             const auto& size = renderer.defaultSize();
0091             int w = size.width() / 72 * QApplication::desktop()->physicalDpiX();
0092             int h = size.height() / 72 * QApplication::desktop()->physicalDpiX();
0093             d->img = QImage(w, h, QImage::Format_ARGB32);
0094 
0095             // render
0096             QPainter painter;
0097             painter.begin(&d->img);
0098             renderer.render(&painter);
0099             painter.end();
0100         }
0101     }
0102     else // raster formats
0103         d->img.load(d->url.toLocalFile());
0104 }
0105 
0106 Cantor::ImageResult::ImageResult(const QImage& image, const QString& alt) :  d(new ImageResultPrivate)
0107 {
0108     d->img = image;
0109     d->alt = alt;
0110 
0111     QTemporaryFile imageFile;
0112     imageFile.setAutoRemove(false);
0113     if (imageFile.open())
0114     {
0115         d->img.save(imageFile.fileName(), "PNG");
0116         d->url = QUrl::fromLocalFile(imageFile.fileName());
0117     }
0118 }
0119 
0120 ImageResult::~ImageResult()
0121 {
0122     delete d;
0123 }
0124 
0125 QString ImageResult::toHtml()
0126 {
0127     return QStringLiteral("<img src=\"%1\" alt=\"%2\"/>").arg(d->url.toLocalFile(), d->alt);
0128 }
0129 
0130 QString ImageResult::toLatex()
0131 {
0132     return QStringLiteral(" \\begin{center} \n \\includegraphics[width=12cm]{%1} \n \\end{center}").arg(d->url.fileName());
0133 }
0134 
0135 QVariant ImageResult::data()
0136 {
0137     return QVariant(d->img);
0138 }
0139 
0140 QUrl ImageResult::url()
0141 {
0142     return d->url;
0143 }
0144 
0145 int ImageResult::type()
0146 {
0147     return ImageResult::Type;
0148 }
0149 
0150 QString ImageResult::mimeType()
0151 {
0152     QString mimetype;
0153     for (const auto& format : QImageWriter::supportedImageFormats())
0154         mimetype += QLatin1String("image/" + format.toLower() + ' ');
0155 
0156     return mimetype;
0157 }
0158 
0159 QString ImageResult::extension()
0160 {
0161     return d->extension;
0162 }
0163 
0164 QDomElement ImageResult::toXml(QDomDocument& doc)
0165 {
0166     auto e = doc.createElement(QStringLiteral("Result"));
0167     e.setAttribute(QStringLiteral("type"), QStringLiteral("image"));
0168     e.setAttribute(QStringLiteral("filename"), d->url.fileName());
0169 
0170     if (!d->alt.isEmpty())
0171         e.appendChild(doc.createTextNode(d->alt));
0172 
0173     return e;
0174 }
0175 
0176 QJsonValue Cantor::ImageResult::toJupyterJson()
0177 {
0178     QJsonObject root;
0179 
0180     if (executionIndex() != -1)
0181     {
0182         root.insert(QLatin1String("output_type"), QLatin1String("execute_result"));
0183         root.insert(QLatin1String("execution_count"), executionIndex());
0184     }
0185     else
0186         root.insert(QLatin1String("output_type"), QLatin1String("display_data"));
0187 
0188     QImage image;
0189     if (d->img.isNull())
0190         image.load(d->url.toLocalFile());
0191     else
0192         image = d->img;
0193 
0194     QJsonObject data;
0195 
0196     // HACK: see ImageResultPrivate::svgContent
0197     if (d->originalFormat == JupyterUtils::svgMime)
0198         data.insert(JupyterUtils::svgMime, JupyterUtils::toJupyterMultiline(d->svgContent));
0199     else
0200         data = JupyterUtils::packMimeBundle(d->img, d->originalFormat);
0201 
0202     data.insert(JupyterUtils::textMime, JupyterUtils::toJupyterMultiline(d->alt));
0203 
0204 
0205     root.insert(QLatin1String("data"), data);
0206 
0207     QJsonObject metadata(jupyterMetadata());
0208     if (d->displaySize.isValid())
0209     {
0210         QJsonObject size;
0211         size.insert(QLatin1String("width"), displaySize().width());
0212         size.insert(QLatin1String("height"), displaySize().height());
0213         metadata.insert(d->originalFormat, size);
0214     }
0215     root.insert(QLatin1String("metadata"), metadata);
0216 
0217     return root;
0218 }
0219 
0220 void ImageResult::saveAdditionalData(KZip* archive)
0221 {
0222     archive->addLocalFile(d->url.toLocalFile(), d->url.fileName());
0223 }
0224 
0225 void ImageResult::save(const QString& fileName)
0226 {
0227     bool rc = false;
0228     if (d->extension == QLatin1String("pdf") || d->extension == QLatin1String("svg"))
0229     {
0230         QFile file(fileName);
0231         if (file.open(QIODevice::WriteOnly))
0232         {
0233             if (file.write(d->data) > 0)
0234                 rc = true;
0235             file.close();
0236         }
0237     }
0238     else
0239         rc = d->img.save(fileName);
0240 
0241     if (!rc)
0242         qDebug()<<"saving to " << fileName << " failed.";
0243 }
0244 
0245 QSize Cantor::ImageResult::displaySize()
0246 {
0247     return d->displaySize;
0248 }
0249 
0250 void Cantor::ImageResult::setDisplaySize(QSize size)
0251 {
0252     d->displaySize = size;
0253 }
0254 
0255 void Cantor::ImageResult::setOriginalFormat(const QString& format)
0256 {
0257     d->originalFormat = format;
0258 }
0259 
0260 QString Cantor::ImageResult::originalFormat()
0261 {
0262     return d->originalFormat;
0263 }
0264 
0265 void Cantor::ImageResult::setSvgContent(const QString& svgContent)
0266 {
0267     d->svgContent = svgContent;
0268 }