1 /*
2     SPDX-License-Identifier: GPL-2.0-or-later
3     SPDX-FileCopyrightText: 2012 Martin Kuettler <martin.kuettler@gmail.com>
4 */
5 
6 #include "renderer.h"
7 
8 #include <QUuid>
9 #include <QDebug>
10 #include <QMutex>
11 
12 #include <config-cantorlib.h>
13 
14 #include <poppler-qt5.h>
15 #ifdef LIBSPECTRE_FOUND
16   #include "libspectre/spectre.h"
17 #endif
18 
19 
20 using namespace Cantor;
21 
22 // We need this, because poppler-qt5 not threadsafe before 0.73.0 and 0.73.0 is too new
23 // and not common widespread in repositories
24 static QMutex popplerMutex;
25 
26 class Cantor::RendererPrivate{
27   public:
28     double scale{1};
29     bool useHighRes{false};
30 };
31 
Renderer()32 Renderer::Renderer() : d(new RendererPrivate())
33 {
34 }
35 
~Renderer()36 Renderer::~Renderer()
37 {
38     delete d;
39 }
40 
setScale(qreal scale)41 void Renderer::setScale(qreal scale)
42 {
43     d->scale = scale;
44 }
45 
scale()46 qreal Renderer::scale()
47 {
48     return d->scale;
49 }
50 
useHighResolution(bool b)51 void Renderer::useHighResolution(bool b)
52 {
53     d->useHighRes = b;
54 }
55 
render(QTextDocument * document,Method method,const QUrl & url,const QString & uuid)56 QTextImageFormat Renderer::render(QTextDocument *document, Method method, const QUrl &url, const QString& uuid)
57 {
58     QTextImageFormat format;
59 
60     QUrl internal;
61     internal.setScheme(QLatin1String("internal"));
62     internal.setPath(uuid);
63 
64     QSizeF s = renderToResource(document, method, url, internal);
65 
66     if(s.isValid())
67     {
68         format.setName(internal.url());
69         format.setWidth(s.width());
70         format.setHeight(s.height());
71     }
72 
73     return format;
74 }
75 
render(QTextDocument * document,const Cantor::LatexRenderer * latex)76 QTextImageFormat Renderer::render(QTextDocument *document, const Cantor::LatexRenderer* latex)
77 {
78     QTextImageFormat format = render(document, Method::EPS, QUrl::fromLocalFile(latex->imagePath()), latex->uuid());
79 
80     if (!format.name().isEmpty()) {
81         format.setProperty(CantorFormula, latex->method());
82         format.setProperty(ImagePath, latex->imagePath());
83         format.setProperty(Code, latex->latexCode());
84     }
85 
86     return format;
87 }
88 
renderToResource(QTextDocument * document,Method method,const QUrl & url,const QUrl & internal)89 QSizeF Renderer::renderToResource(QTextDocument *document, Method method, const QUrl &url, const QUrl& internal)
90 {
91     QSizeF size;
92     QImage img = renderToImage(url, method, &size);
93 
94     qDebug() << internal;
95     document->addResource(QTextDocument::ImageResource, internal, QVariant(img) );
96     return size;
97 }
98 
epsRenderToImage(const QUrl & url,double scale,bool useHighRes,QSizeF * size,QString * errorReason)99 QImage Renderer::epsRenderToImage(const QUrl& url, double scale, bool useHighRes, QSizeF* size, QString* errorReason)
100 {
101 #ifdef LIBSPECTRE_FOUND
102     SpectreDocument* doc = spectre_document_new();
103     SpectreRenderContext* rc = spectre_render_context_new();
104 
105     qDebug() << "rendering eps file: " << url;
106     QByteArray local_file = url.toLocalFile().toUtf8();
107     spectre_document_load(doc, local_file.data());
108 
109     bool isEps = spectre_document_is_eps(doc);
110     if (!isEps)
111     {
112         if (errorReason)
113             *errorReason = QString::fromLatin1("Error: spectre document is not eps! It means, that url is invalid");
114         return QImage();
115     }
116 
117     int wdoc, hdoc;
118     qreal w, h;
119     double realScale;
120     spectre_document_get_page_size(doc, &wdoc, &hdoc);
121     if(useHighRes) {
122         realScale = 1.2*4.0; //1.2 scaling factor, to make it look nice, 4x for high resolution
123         w = 1.2 * wdoc;
124         h = 1.2 * hdoc;
125     } else {
126         realScale=1.8*scale;
127         w = 1.8 * wdoc;
128         h = 1.8 * hdoc;
129     }
130 
131     qDebug()<<"scale: "<<realScale;
132 
133     qDebug()<<"dimension: "<<w<<"x"<<h;
134     unsigned char* data;
135     int rowLength;
136 
137     spectre_render_context_set_scale(rc, realScale, realScale);
138     spectre_document_render_full( doc, rc, &data, &rowLength);
139 
140     QImage img(data, wdoc*realScale, hdoc*realScale, rowLength, QImage::Format_RGB32);
141     spectre_document_free(doc);
142     spectre_render_context_free(rc);
143     img = img.convertToFormat(QImage::Format_ARGB32);
144 
145     if (size)
146         *size = QSizeF(w,h);
147     return img;
148 #else
149     if (errorReason)
150         *errorReason = QString::fromLatin1("Render Eps on Cantor without eps support (libspectre)!");
151 
152     Q_UNUSED(url);
153     Q_UNUSED(scale);
154     Q_UNUSED(useHighRes);
155     Q_UNUSED(size);
156     return QImage();
157 #endif
158 }
159 
pdfRenderToImage(const QUrl & url,double scale,bool highResolution,QSizeF * size,QString * errorReason)160 QImage Renderer::pdfRenderToImage(const QUrl& url, double scale, bool highResolution, QSizeF* size, QString* errorReason)
161 {
162     popplerMutex.lock();
163     Poppler::Document* document = Poppler::Document::load(url.toLocalFile());
164     popplerMutex.unlock();
165     if (document == nullptr)
166     {
167         if (errorReason)
168             *errorReason = QString::fromLatin1("Poppler library have failed to open file % as pdf").arg(url.toLocalFile());
169         return QImage();
170     }
171 
172     Poppler::Page* pdfPage = document->page(0);
173     if (pdfPage == nullptr) {
174         if (errorReason)
175             *errorReason = QString::fromLatin1("Poppler library failed to access first page of %1 document").arg(url.toLocalFile());
176 
177         delete document;
178         return QImage();
179     }
180 
181     QSize pageSize = pdfPage->pageSize();
182 
183     double realScale = 1.7 * 1.8;
184     qreal w = 1.7 * pageSize.width();
185     qreal h = 1.7 * pageSize.height();
186     if(highResolution)
187         realScale *= 5;
188     else
189         realScale *= scale;
190 
191 
192     QImage image = pdfPage->renderToImage(72.0*realScale, 72.0*realScale);
193 
194     delete pdfPage;
195     popplerMutex.lock();
196     delete document;
197     popplerMutex.unlock();
198 
199     if (image.isNull())
200     {
201         if (errorReason)
202             *errorReason = QString::fromLatin1("Poppler library failed to render pdf %1 to image").arg(url.toLocalFile());
203 
204         return image;
205     }
206 
207     // Resize with smooth transformation for more beautiful result
208     image = image.convertToFormat(QImage::Format_ARGB32).scaled(image.size()/1.8, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
209 
210     if (size)
211         *size = QSizeF(w, h);
212     return image;
213 }
214 
215 
renderToImage(const QUrl & url,Method method,QSizeF * size)216 QImage Renderer::renderToImage(const QUrl& url, Method method, QSizeF* size)
217 {
218     switch(method)
219     {
220         case Method::PDF:
221             return pdfRenderToImage(url, d->scale, d->useHighRes, size);
222 
223         case Method::EPS:
224             return epsRenderToImage(url, d->scale, d->useHighRes, size);
225 
226         default:
227             return QImage();
228     }
229 }
230