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 }