1 // -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; c-brace-offset: 0; -*-
2 
3 // special.cpp
4 
5 // Methods for dviRenderer which deal with "\special" commands found in the
6 // DVI file
7 
8 // Copyright 2000--2004, Stefan Kebekus (kebekus@kde.org).
9 
10 #include <config.h>
11 
12 #include "debug_dvi.h"
13 #include "dviFile.h"
14 #include "dviRenderer.h"
15 #include "hyperlink.h"
16 #include "psgs.h"
17 //#include "renderedDocumentPage.h"
18 
19 #include <KLocalizedString>
20 #include <QMimeDatabase>
21 #include <QMimeType>
22 
23 #include "debug_dvi.h"
24 #include <QByteArray>
25 #include <QFile>
26 #include <QFontDatabase>
27 #include <QImage>
28 #include <QPainter>
29 
printErrorMsgForSpecials(const QString & msg)30 void dviRenderer::printErrorMsgForSpecials(const QString &msg)
31 {
32     if (dviFile->errorCounter < 25) {
33         qCCritical(OkularDviDebug) << msg << endl;
34         dviFile->errorCounter++;
35         if (dviFile->errorCounter == 25)
36             qCCritical(OkularDviDebug) << i18n("That makes 25 errors. Further error messages will not be printed.") << endl;
37     }
38 }
39 
40 // Parses a color specification, as explained in the manual to
41 // dvips. If the spec could not be parsed, an invalid color will be
42 // returned.
43 
parseColorSpecification(const QString & colorSpec)44 QColor dviRenderer::parseColorSpecification(const QString &colorSpec)
45 {
46     // Initialize the map of known colors, if that is not done yet.
47     if (namedColors.isEmpty()) {
48         namedColors[QStringLiteral("Red")] = QColor((int)(255.0 * 1), (int)(255.0 * 0), (int)(255.0 * 0));
49         namedColors[QStringLiteral("Tan")] = QColor((int)(255.0 * 0.86), (int)(255.0 * 0.58), (int)(255.0 * 0.44));
50         namedColors[QStringLiteral("Blue")] = QColor((int)(255.0 * 0), (int)(255.0 * 0), (int)(255.0 * 1));
51         namedColors[QStringLiteral("Cyan")] = QColor((int)(255.0 * 0), (int)(255.0 * 1), (int)(255.0 * 1));
52         namedColors[QStringLiteral("Gray")] = QColor((int)(255.0 * 0.5), (int)(255.0 * 0.5), (int)(255.0 * 0.5));
53         namedColors[QStringLiteral("Plum")] = QColor((int)(255.0 * 0.5), (int)(255.0 * 0), (int)(255.0 * 1));
54         namedColors[QStringLiteral("Black")] = QColor((int)(255.0 * 0), (int)(255.0 * 0), (int)(255.0 * 0));
55         namedColors[QStringLiteral("Brown")] = QColor((int)(255.0 * 0.4), (int)(255.0 * 0), (int)(255.0 * 0));
56         namedColors[QStringLiteral("Green")] = QColor((int)(255.0 * 0), (int)(255.0 * 1), (int)(255.0 * 0));
57         namedColors[QStringLiteral("Melon")] = QColor((int)(255.0 * 1), (int)(255.0 * 0.54), (int)(255.0 * 0.5));
58         namedColors[QStringLiteral("Peach")] = QColor((int)(255.0 * 1), (int)(255.0 * 0.5), (int)(255.0 * 0.3));
59         namedColors[QStringLiteral("Sepia")] = QColor((int)(255.0 * 0.3), (int)(255.0 * 0), (int)(255.0 * 0));
60         namedColors[QStringLiteral("White")] = QColor((int)(255.0 * 1), (int)(255.0 * 1), (int)(255.0 * 1));
61         namedColors[QStringLiteral("Maroon")] = QColor((int)(255.0 * 0.68), (int)(255.0 * 0), (int)(255.0 * 0));
62         namedColors[QStringLiteral("Orange")] = QColor((int)(255.0 * 1), (int)(255.0 * 0.39), (int)(255.0 * 0.13));
63         namedColors[QStringLiteral("Orchid")] = QColor((int)(255.0 * 0.68), (int)(255.0 * 0.36), (int)(255.0 * 1));
64         namedColors[QStringLiteral("Purple")] = QColor((int)(255.0 * 0.55), (int)(255.0 * 0.14), (int)(255.0 * 1));
65         namedColors[QStringLiteral("Salmon")] = QColor((int)(255.0 * 1), (int)(255.0 * 0.47), (int)(255.0 * 0.62));
66         namedColors[QStringLiteral("Violet")] = QColor((int)(255.0 * 0.21), (int)(255.0 * 0.12), (int)(255.0 * 1));
67         namedColors[QStringLiteral("Yellow")] = QColor((int)(255.0 * 1), (int)(255.0 * 1), (int)(255.0 * 0));
68         namedColors[QStringLiteral("Apricot")] = QColor((int)(255.0 * 1), (int)(255.0 * 0.68), (int)(255.0 * 0.48));
69         namedColors[QStringLiteral("Emerald")] = QColor((int)(255.0 * 0), (int)(255.0 * 1), (int)(255.0 * 0.5));
70         namedColors[QStringLiteral("Fuchsia")] = QColor((int)(255.0 * 0.45), (int)(255.0 * 0.01), (int)(255.0 * 0.92));
71         namedColors[QStringLiteral("Magenta")] = QColor((int)(255.0 * 1), (int)(255.0 * 0), (int)(255.0 * 1));
72         namedColors[QStringLiteral("SkyBlue")] = QColor((int)(255.0 * 0.38), (int)(255.0 * 1), (int)(255.0 * 0.88));
73         namedColors[QStringLiteral("Thistle")] = QColor((int)(255.0 * 0.88), (int)(255.0 * 0.41), (int)(255.0 * 1));
74         namedColors[QStringLiteral("BrickRed")] = QColor((int)(255.0 * 0.72), (int)(255.0 * 0), (int)(255.0 * 0));
75         namedColors[QStringLiteral("Cerulean")] = QColor((int)(255.0 * 0.06), (int)(255.0 * 0.89), (int)(255.0 * 1));
76         namedColors[QStringLiteral("Lavender")] = QColor((int)(255.0 * 1), (int)(255.0 * 0.52), (int)(255.0 * 1));
77         namedColors[QStringLiteral("Mahogany")] = QColor((int)(255.0 * 0.65), (int)(255.0 * 0), (int)(255.0 * 0));
78         namedColors[QStringLiteral("Mulberry")] = QColor((int)(255.0 * 0.64), (int)(255.0 * 0.08), (int)(255.0 * 0.98));
79         namedColors[QStringLiteral("NavyBlue")] = QColor((int)(255.0 * 0.06), (int)(255.0 * 0.46), (int)(255.0 * 1));
80         namedColors[QStringLiteral("SeaGreen")] = QColor((int)(255.0 * 0.31), (int)(255.0 * 1), (int)(255.0 * 0.5));
81         namedColors[QStringLiteral("TealBlue")] = QColor((int)(255.0 * 0.12), (int)(255.0 * 0.98), (int)(255.0 * 0.64));
82         namedColors[QStringLiteral("BlueGreen")] = QColor((int)(255.0 * 0.15), (int)(255.0 * 1), (int)(255.0 * 0.67));
83         namedColors[QStringLiteral("CadetBlue")] = QColor((int)(255.0 * 0.38), (int)(255.0 * 0.43), (int)(255.0 * 0.77));
84         namedColors[QStringLiteral("Dandelion")] = QColor((int)(255.0 * 1), (int)(255.0 * 0.71), (int)(255.0 * 0.16));
85         namedColors[QStringLiteral("Goldenrod")] = QColor((int)(255.0 * 1), (int)(255.0 * 0.9), (int)(255.0 * 0.16));
86         namedColors[QStringLiteral("LimeGreen")] = QColor((int)(255.0 * 0.5), (int)(255.0 * 1), (int)(255.0 * 0));
87         namedColors[QStringLiteral("OrangeRed")] = QColor((int)(255.0 * 1), (int)(255.0 * 0), (int)(255.0 * 0.5));
88         namedColors[QStringLiteral("PineGreen")] = QColor((int)(255.0 * 0), (int)(255.0 * 0.75), (int)(255.0 * 0.16));
89         namedColors[QStringLiteral("RawSienna")] = QColor((int)(255.0 * 0.55), (int)(255.0 * 0), (int)(255.0 * 0));
90         namedColors[QStringLiteral("RedOrange")] = QColor((int)(255.0 * 1), (int)(255.0 * 0.23), (int)(255.0 * 0.13));
91         namedColors[QStringLiteral("RedViolet")] = QColor((int)(255.0 * 0.59), (int)(255.0 * 0), (int)(255.0 * 0.66));
92         namedColors[QStringLiteral("Rhodamine")] = QColor((int)(255.0 * 1), (int)(255.0 * 0.18), (int)(255.0 * 1));
93         namedColors[QStringLiteral("RoyalBlue")] = QColor((int)(255.0 * 0), (int)(255.0 * 0.5), (int)(255.0 * 1));
94         namedColors[QStringLiteral("RubineRed")] = QColor((int)(255.0 * 1), (int)(255.0 * 0), (int)(255.0 * 0.87));
95         namedColors[QStringLiteral("Turquoise")] = QColor((int)(255.0 * 0.15), (int)(255.0 * 1), (int)(255.0 * 0.8));
96         namedColors[QStringLiteral("VioletRed")] = QColor((int)(255.0 * 1), (int)(255.0 * 0.19), (int)(255.0 * 1));
97         namedColors[QStringLiteral("Aquamarine")] = QColor((int)(255.0 * 0.18), (int)(255.0 * 1), (int)(255.0 * 0.7));
98         namedColors[QStringLiteral("BlueViolet")] = QColor((int)(255.0 * 0.1), (int)(255.0 * 0.05), (int)(255.0 * 0.96));
99         namedColors[QStringLiteral("DarkOrchid")] = QColor((int)(255.0 * 0.6), (int)(255.0 * 0.2), (int)(255.0 * 0.8));
100         namedColors[QStringLiteral("OliveGreen")] = QColor((int)(255.0 * 0), (int)(255.0 * 0.6), (int)(255.0 * 0));
101         namedColors[QStringLiteral("Periwinkle")] = QColor((int)(255.0 * 0.43), (int)(255.0 * 0.45), (int)(255.0 * 1));
102         namedColors[QStringLiteral("Bittersweet")] = QColor((int)(255.0 * 0.76), (int)(255.0 * 0.01), (int)(255.0 * 0));
103         namedColors[QStringLiteral("BurntOrange")] = QColor((int)(255.0 * 1), (int)(255.0 * 0.49), (int)(255.0 * 0));
104         namedColors[QStringLiteral("ForestGreen")] = QColor((int)(255.0 * 0), (int)(255.0 * 0.88), (int)(255.0 * 0));
105         namedColors[QStringLiteral("GreenYellow")] = QColor((int)(255.0 * 0.85), (int)(255.0 * 1), (int)(255.0 * 0.31));
106         namedColors[QStringLiteral("JungleGreen")] = QColor((int)(255.0 * 0.01), (int)(255.0 * 1), (int)(255.0 * 0.48));
107         namedColors[QStringLiteral("ProcessBlue")] = QColor((int)(255.0 * 0.04), (int)(255.0 * 1), (int)(255.0 * 1));
108         namedColors[QStringLiteral("RoyalPurple")] = QColor((int)(255.0 * 0.25), (int)(255.0 * 0.1), (int)(255.0 * 1));
109         namedColors[QStringLiteral("SpringGreen")] = QColor((int)(255.0 * 0.74), (int)(255.0 * 1), (int)(255.0 * 0.24));
110         namedColors[QStringLiteral("YellowGreen")] = QColor((int)(255.0 * 0.56), (int)(255.0 * 1), (int)(255.0 * 0.26));
111         namedColors[QStringLiteral("MidnightBlue")] = QColor((int)(255.0 * 0), (int)(255.0 * 0.44), (int)(255.0 * 0.57));
112         namedColors[QStringLiteral("YellowOrange")] = QColor((int)(255.0 * 1), (int)(255.0 * 0.58), (int)(255.0 * 0));
113         namedColors[QStringLiteral("CarnationPink")] = QColor((int)(255.0 * 1), (int)(255.0 * 0.37), (int)(255.0 * 1));
114         namedColors[QStringLiteral("CornflowerBlue")] = QColor((int)(255.0 * 0.35), (int)(255.0 * 0.87), (int)(255.0 * 1));
115         namedColors[QStringLiteral("WildStrawberry")] = QColor((int)(255.0 * 1), (int)(255.0 * 0.04), (int)(255.0 * 0.61));
116     }
117 
118     QString specType = colorSpec.section(QLatin1Char(' '), 0, 0);
119 
120     if (specType.indexOf(QStringLiteral("rgb"), 0, Qt::CaseInsensitive) == 0) {
121         bool ok;
122 
123         double r = colorSpec.section(QLatin1Char(' '), 1, 1).toDouble(&ok);
124         if ((ok == false) || (r < 0.0) || (r > 1.0))
125             return QColor();
126 
127         double g = colorSpec.section(QLatin1Char(' '), 2, 2).toDouble(&ok);
128         if ((ok == false) || (g < 0.0) || (g > 1.0))
129             return QColor();
130 
131         double b = colorSpec.section(QLatin1Char(' '), 3, 3).toDouble(&ok);
132         if ((ok == false) || (b < 0.0) || (b > 1.0))
133             return QColor();
134 
135         return QColor((int)(r * 255.0 + 0.5), (int)(g * 255.0 + 0.5), (int)(b * 255.0 + 0.5));
136     }
137 
138     if (specType.indexOf(QStringLiteral("hsb"), 0, Qt::CaseInsensitive) == 0) {
139         bool ok;
140 
141         double h = colorSpec.section(QLatin1Char(' '), 1, 1).toDouble(&ok);
142         if ((ok == false) || (h < 0.0) || (h > 1.0))
143             return QColor();
144 
145         double s = colorSpec.section(QLatin1Char(' '), 2, 2).toDouble(&ok);
146         if ((ok == false) || (s < 0.0) || (s > 1.0))
147             return QColor();
148 
149         double b = colorSpec.section(QLatin1Char(' '), 3, 3).toDouble(&ok);
150         if ((ok == false) || (b < 0.0) || (b > 1.0))
151             return QColor();
152 
153         return QColor::fromHsv((int)(h * 359.0 + 0.5), (int)(s * 255.0 + 0.5), (int)(b * 255.0 + 0.5));
154     }
155 
156     if (specType.indexOf(QStringLiteral("cmyk"), 0, Qt::CaseInsensitive) == 0) {
157         bool ok;
158 
159         double c = colorSpec.section(QLatin1Char(' '), 1, 1).toDouble(&ok);
160         if ((ok == false) || (c < 0.0) || (c > 1.0))
161             return QColor();
162 
163         double m = colorSpec.section(QLatin1Char(' '), 2, 2).toDouble(&ok);
164         if ((ok == false) || (m < 0.0) || (m > 1.0))
165             return QColor();
166 
167         double y = colorSpec.section(QLatin1Char(' '), 3, 3).toDouble(&ok);
168         if ((ok == false) || (y < 0.0) || (y > 1.0))
169             return QColor();
170 
171         double k = colorSpec.section(QLatin1Char(' '), 3, 3).toDouble(&ok);
172         if ((ok == false) || (k < 0.0) || (k > 1.0))
173             return QColor();
174 
175         // Convert cmyk coordinates to rgb.
176         double r = 1.0 - c - k;
177         if (r < 0.0)
178             r = 0.0;
179         double g = 1.0 - m - k;
180         if (g < 0.0)
181             g = 0.0;
182         double b = 1.0 - y - k;
183         if (b < 0.0)
184             b = 0.0;
185 
186         return QColor((int)(r * 255.0 + 0.5), (int)(g * 255.0 + 0.5), (int)(b * 255.0 + 0.5));
187     }
188 
189     if (specType.indexOf(QStringLiteral("gray"), 0, Qt::CaseInsensitive) == 0) {
190         bool ok;
191 
192         double g = colorSpec.section(QLatin1Char(' '), 1, 1).toDouble(&ok);
193         if ((ok == false) || (g < 0.0) || (g > 1.0))
194             return QColor();
195 
196         return QColor((int)(g * 255.0 + 0.5), (int)(g * 255.0 + 0.5), (int)(g * 255.0 + 0.5));
197     }
198 
199     // Check if the color is one of the known named colors.
200     QMap<QString, QColor>::Iterator f = namedColors.find(specType);
201     if (f != namedColors.end())
202         return *f;
203 
204     return QColor(specType);
205 }
206 
color_special(const QString & msg)207 void dviRenderer::color_special(const QString &msg)
208 {
209     QString const cp = msg.trimmed();
210 
211     QString command = cp.section(QLatin1Char(' '), 0, 0);
212 
213     if (command == QLatin1String("pop")) {
214         // Take color off the stack
215         if (colorStack.isEmpty())
216             printErrorMsgForSpecials(i18n("Error in DVIfile '%1', page %2. Color pop command issued when the color stack is empty.", dviFile->filename, current_page));
217         else
218             colorStack.pop();
219         return;
220     }
221 
222     if (command == QLatin1String("push")) {
223         // Get color specification
224         const QColor col = parseColorSpecification(cp.section(QLatin1Char(' '), 1));
225         // Set color
226         if (col.isValid())
227             colorStack.push(col);
228         else
229             colorStack.push(Qt::black);
230         return;
231     }
232 
233     // Get color specification and set the color for the rest of this
234     // page
235     QColor col = parseColorSpecification(cp);
236     // Set color
237     if (col.isValid())
238         globalColor = col;
239     else
240         globalColor = Qt::black;
241     return;
242 }
243 
html_href_special(const QString & msg)244 void dviRenderer::html_href_special(const QString &msg)
245 {
246     QString cp = msg;
247     cp.truncate(cp.indexOf(QLatin1Char('"')));
248 
249 #ifdef DEBUG_SPECIAL
250     qCDebug(OkularDviDebug) << "HTML-special, href " << cp.toLatin1();
251 #endif
252     HTML_href = new QString(cp);
253 }
254 
html_anchor_end()255 void dviRenderer::html_anchor_end()
256 {
257 #ifdef DEBUG_SPECIAL
258     qCDebug(OkularDviDebug) << "HTML-special, anchor-end";
259 #endif
260 
261     if (HTML_href != nullptr) {
262         delete HTML_href;
263         HTML_href = nullptr;
264     }
265 }
266 
source_special(const QString & cp)267 void dviRenderer::source_special(const QString &cp)
268 {
269     // only when rendering really takes place: set source_href to the
270     // current special string. When characters are rendered, the
271     // rendering routine will then generate a DVI_HyperLink and add it
272     // to the proper list. This DVI_HyperLink is used to match mouse
273     // positions with the hyperlinks for inverse search.
274     if (source_href)
275         *source_href = cp;
276     else
277         source_href = new QString(cp);
278 }
279 
parse_special_argument(const QString & strg,const char * argument_name,int * variable)280 void parse_special_argument(const QString &strg, const char *argument_name, int *variable)
281 {
282     int index = strg.indexOf(QString::fromLocal8Bit(argument_name));
283     if (index >= 0) {
284         QString tmp = strg.mid(index + strlen(argument_name));
285         index = tmp.indexOf(QLatin1Char(' '));
286         if (index >= 0)
287             tmp.truncate(index);
288 
289         bool OK;
290         float const tmp_float = tmp.toFloat(&OK);
291 
292         if (OK)
293             *variable = int(tmp_float + 0.5);
294         else
295             // Maybe we should open a dialog here.
296             qCCritical(OkularDviDebug) << i18n(
297                                               "Malformed parameter in the epsf special command.\n"
298                                               "Expected a float to follow %1 in %2",
299                                               QString::fromLocal8Bit(argument_name),
300                                               strg)
301                                        << endl;
302     }
303 }
304 
epsf_special(const QString & cp)305 void dviRenderer::epsf_special(const QString &cp)
306 {
307 #ifdef DEBUG_SPECIAL
308     qCDebug(OkularDviDebug) << "epsf-special: psfile=" << cp;
309 #endif
310 
311     QString include_command = cp.simplified();
312 
313     // The line is supposed to start with "..ile=", and then comes the
314     // filename. Figure out what the filename is and stow it away. Of
315     // course, this does not work if the filename contains spaces
316     // (already the simplified() above is wrong). If you have
317     // files like this, go away.
318     QString EPSfilename_orig = include_command;
319     EPSfilename_orig.truncate(EPSfilename_orig.indexOf(QLatin1Char(' ')));
320 
321     // Strip enclosing quotation marks which are included by some LaTeX
322     // macro packages (but not by others). This probably means that
323     // graphic files are no longer found if the filename really does
324     // contain quotes, but we don't really care that much.
325     if ((EPSfilename_orig.at(0) == QLatin1Char('\"')) && (EPSfilename_orig.at(EPSfilename_orig.length() - 1) == QLatin1Char('\"'))) {
326         EPSfilename_orig = EPSfilename_orig.mid(1, EPSfilename_orig.length() - 2);
327     }
328     QString EPSfilename = ghostscript_interface::locateEPSfile(EPSfilename_orig, baseURL);
329 
330     // Now parse the arguments.
331     int llx = 0;
332     int lly = 0;
333     int urx = 0;
334     int ury = 0;
335     int rwi = 0;
336     int rhi = 0;
337     int angle = 0;
338 
339     // just to avoid ambiguities; the filename could contain keywords
340     include_command = include_command.mid(include_command.indexOf(QLatin1Char(' ')));
341 
342     parse_special_argument(include_command, "llx=", &llx);
343     parse_special_argument(include_command, "lly=", &lly);
344     parse_special_argument(include_command, "urx=", &urx);
345     parse_special_argument(include_command, "ury=", &ury);
346     parse_special_argument(include_command, "rwi=", &rwi);
347     parse_special_argument(include_command, "rhi=", &rhi);
348     parse_special_argument(include_command, "angle=", &angle);
349 
350     // If we have a png, gif, jpeg or mng file, we need to draw it here.
351     QMimeDatabase db;
352     QMimeType const mime_type = db.mimeTypeForFile(EPSfilename, QMimeDatabase::MatchContent);
353     QString const &mime_type_name = mime_type.isValid() ? mime_type.name() : QString();
354     bool const isGFX = (mime_type_name == QLatin1String("image/png") || mime_type_name == QLatin1String("image/gif") || mime_type_name == QLatin1String("image/jpeg") || mime_type_name == QLatin1String("video/x-mng"));
355 
356     // So, if we do not have a PostScript file, but a graphics file, and
357     // if that file exists, we draw it here.
358     if (isGFX && QFile::exists(EPSfilename)) {
359         // Don't show PostScript, just draw the bounding box. For this,
360         // calculate the size of the bounding box in Pixels.
361         double bbox_width = urx - llx;
362         double bbox_height = ury - lly;
363 
364         if ((rwi != 0) && (bbox_width != 0)) {
365             bbox_height *= rwi / bbox_width;
366             bbox_width = rwi;
367         }
368         if ((rhi != 0) && (bbox_height != 0)) {
369             bbox_width *= rhi / bbox_height;
370             bbox_height = rhi;
371         }
372 
373         double fontPixelPerDVIunit = dviFile->getCmPerDVIunit() * 1200.0 / 2.54;
374 
375         bbox_width *= 0.1 * 65536.0 * fontPixelPerDVIunit / shrinkfactor;
376         bbox_height *= 0.1 * 65536.0 * fontPixelPerDVIunit / shrinkfactor;
377 
378         QImage image(EPSfilename);
379         image = image.scaled((int)(bbox_width), (int)(bbox_height), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
380         foreGroundPainter->drawImage(((int)((currinf.data.dvi_h) / (shrinkfactor * 65536))), currinf.data.pxl_v - (int)bbox_height, image);
381         return;
382     }
383 
384     if (!_postscript || !QFile::exists(EPSfilename)) {
385         // Don't show PostScript, just draw the bounding box. For this,
386         // calculate the size of the bounding box in Pixels.
387         double bbox_width = urx - llx;
388         double bbox_height = ury - lly;
389 
390         if ((rwi != 0) && (bbox_width != 0)) {
391             bbox_height *= rwi / bbox_width;
392             bbox_width = rwi;
393         }
394         if ((rhi != 0) && (bbox_height != 0)) {
395             bbox_width *= rhi / bbox_height;
396             bbox_height = rhi;
397         }
398 
399         double fontPixelPerDVIunit = dviFile->getCmPerDVIunit() * 1200.0 / 2.54;
400 
401         bbox_width *= 0.1 * 65536.0 * fontPixelPerDVIunit / shrinkfactor;
402         bbox_height *= 0.1 * 65536.0 * fontPixelPerDVIunit / shrinkfactor;
403 
404         QRect bbox(((int)((currinf.data.dvi_h) / (shrinkfactor * 65536))), currinf.data.pxl_v - (int)bbox_height, (int)bbox_width, (int)bbox_height);
405 
406         foreGroundPainter->save();
407 
408         if (QFile::exists(EPSfilename))
409             foreGroundPainter->setBrush(Qt::lightGray);
410         else
411             foreGroundPainter->setBrush(Qt::red);
412         foreGroundPainter->setPen(Qt::black);
413         foreGroundPainter->drawRoundedRect(bbox, 2, 2);
414         QFont f = foreGroundPainter->font();
415         f.setPointSize(8);
416         foreGroundPainter->setFont(f);
417         /* if the fonts are mapped for some reason to X bitmap fonts,
418            the call to drawText() in the non-GUI thread will produce a crash.
419            Ensure that the rendering of the text is performed only if
420            the threaded font rendering is available */
421         if (QFile::exists(EPSfilename))
422             foreGroundPainter->drawText(bbox, (int)(Qt::AlignCenter), EPSfilename);
423         else
424             foreGroundPainter->drawText(bbox, (int)(Qt::AlignCenter), i18n("File not found: \n %1", EPSfilename_orig));
425         foreGroundPainter->restore();
426     }
427 
428     return;
429 }
430 
TPIC_flushPath_special()431 void dviRenderer::TPIC_flushPath_special()
432 {
433 #ifdef DEBUG_SPECIAL
434     qCDebug(OkularDviDebug) << "TPIC special flushPath";
435 #endif
436 
437     if (number_of_elements_in_path == 0) {
438         printErrorMsgForSpecials(QStringLiteral("TPIC special flushPath called when path was empty."));
439         return;
440     }
441 
442     QPen pen(Qt::black, (int)(penWidth_in_mInch * resolutionInDPI / 1000.0 + 0.5)); // Sets the pen size in milli-inches
443     foreGroundPainter->setPen(pen);
444     foreGroundPainter->drawPolyline(TPIC_path.constData(), number_of_elements_in_path);
445     number_of_elements_in_path = 0;
446 }
447 
TPIC_addPath_special(const QString & cp)448 void dviRenderer::TPIC_addPath_special(const QString &cp)
449 {
450 #ifdef DEBUG_SPECIAL
451     qCDebug(OkularDviDebug) << "TPIC special addPath: " << cp;
452 #endif
453 
454     // Adds a point to the path list
455     QString cp_noWhiteSpace = cp.trimmed();
456     bool ok;
457     float xKoord = cp_noWhiteSpace.section(QLatin1Char(' '), 0, 0).toFloat(&ok);
458     if (ok == false) {
459         printErrorMsgForSpecials(QStringLiteral("TPIC special; cannot parse first argument in 'pn %1'.").arg(cp));
460         return;
461     }
462     float yKoord = cp_noWhiteSpace.section(QLatin1Char(' '), 1, 1).toFloat(&ok);
463     if (ok == false) {
464         printErrorMsgForSpecials(QStringLiteral("TPIC special; cannot parse second argument in 'pn %1'.").arg(cp));
465         return;
466     }
467 
468     float mag = dviFile->getMagnification() / 1000.0;
469 
470     int x = (int)(currinf.data.dvi_h / (shrinkfactor * 65536.0) + mag * xKoord * resolutionInDPI / 1000.0 + 0.5);
471     int y = (int)(currinf.data.pxl_v + mag * yKoord * resolutionInDPI / 1000.0 + 0.5);
472 
473     // Initialize the point array used to store the path
474     if (TPIC_path.size() == 0)
475         number_of_elements_in_path = 0;
476     if (TPIC_path.size() == number_of_elements_in_path)
477         TPIC_path.resize(number_of_elements_in_path + 100);
478     TPIC_path.setPoint(number_of_elements_in_path++, x, y);
479 }
480 
TPIC_setPen_special(const QString & cp)481 void dviRenderer::TPIC_setPen_special(const QString &cp)
482 {
483 #ifdef DEBUG_SPECIAL
484     qCDebug(OkularDviDebug) << "TPIC special setPen: " << cp;
485 #endif
486 
487     // Sets the pen size in milli-inches
488     bool ok;
489     penWidth_in_mInch = cp.trimmed().toFloat(&ok);
490     if (ok == false) {
491         printErrorMsgForSpecials(QStringLiteral("TPIC special; cannot parse argument in 'pn %1'.").arg(cp));
492         penWidth_in_mInch = 0.0;
493         return;
494     }
495 }
496 
applicationDoSpecial(char * cp)497 void dviRenderer::applicationDoSpecial(char *cp)
498 {
499     QString special_command = QString::fromLocal8Bit(cp);
500 
501     // First come specials which is only interpreted during rendering,
502     // and NOT during the prescan phase
503 
504     // font color specials
505     if (qstrnicmp(cp, "color", 5) == 0) {
506         color_special(special_command.mid(5));
507         return;
508     }
509 
510     // HTML reference
511     if (qstrnicmp(cp, "html:<A href=", 13) == 0) {
512         html_href_special(special_command.mid(14));
513         return;
514     }
515 
516     // HTML anchor end
517     if (qstrnicmp(cp, "html:</A>", 9) == 0) {
518         html_anchor_end();
519         return;
520     }
521 
522     // TPIC specials
523     if (qstrnicmp(cp, "pn", 2) == 0) {
524         TPIC_setPen_special(special_command.mid(2));
525         return;
526     }
527     if (qstrnicmp(cp, "pa ", 3) == 0) {
528         TPIC_addPath_special(special_command.mid(3));
529         return;
530     }
531     if (qstrnicmp(cp, "fp", 2) == 0) {
532         TPIC_flushPath_special();
533         return;
534     }
535 
536     // Encapsulated Postscript File
537     if (qstrnicmp(cp, "PSfile=", 7) == 0) {
538         epsf_special(special_command.mid(7));
539         return;
540     }
541 
542     // source special
543     if (qstrnicmp(cp, "src:", 4) == 0) {
544         source_special(special_command.mid(4));
545         return;
546     }
547 
548     // Unfortunately, in some TeX distribution the hyperref package uses
549     // the dvips driver by default, rather than the hypertex driver. As
550     // a result, the DVI files produced are full of PostScript that
551     // specifies links and anchors, and KDVI would call the ghostscript
552     // interpreter for every page which makes it really slow. This is a
553     // major nuisance, so that we try to filter and interpret the
554     // hypertex generated PostScript here.
555     if (special_command.startsWith(QLatin1String("ps:SDict begin"))) {
556         // Hyperref: start of hyperref rectangle. At this stage it is not
557         // yet clear if the rectangle will contain a hyperlink, an anchor,
558         // or another type of object. We suspect that this rectangle will
559         // define a hyperlink, allocate a QString and set HTML_href to
560         // point to this string. The string contains the name of the
561         // destination which ---due to the nature of the PostScript
562         // language--- will be defined only after characters are drawn and
563         // the hyperref rectangle has been closed. We use "glopglyph" as a
564         // temporary name. Since the pointer HTML_href is not NULL, the
565         // character drawing routines will now underline all characters in
566         // blue to point out that they correspond to a hyperlink. Also, as
567         // soon as characters are drawn, the drawing routines will
568         // allocate a Hyperlink and add it to the top of the vector
569         // currentlyDrawnPage->hyperLinkList.
570         if (special_command == QLatin1String("ps:SDict begin H.S end")) {
571             // At this stage, the vector 'hyperLinkList' should not contain
572             // links with unspecified destinations (i.e. destination set to
573             // 'glopglyph'). As a protection against bad DVI files, we make
574             // sure to remove all link rectangles which point to
575             // 'glopglyph'.
576             while (!currentlyDrawnPage->hyperLinkList.isEmpty())
577                 if (currentlyDrawnPage->hyperLinkList.last().linkText == QLatin1String("glopglyph"))
578                     currentlyDrawnPage->hyperLinkList.pop_back();
579                 else
580                     break;
581 
582             HTML_href = new QString(QStringLiteral("glopglyph"));
583             return;
584         }
585 
586         // Hyperref: end of hyperref rectangle of unknown type or hyperref
587         // link rectangle. In these cases we set HTML_href to NULL, which
588         // causes the character drawing routines to stop drawing
589         // characters underlined in blue. Note that the name of the
590         // destination is still set to "glopglyph". In a well-formed DVI
591         // file, this special command is immediately followed by another
592         // special, where the destination is specified. This special is
593         // treated below.
594         if ((special_command == QLatin1String("ps:SDict begin H.R end")) || special_command.endsWith(QLatin1String("H.L end"))) {
595             if (HTML_href != nullptr) {
596                 delete HTML_href;
597                 HTML_href = nullptr;
598             }
599             return; // end of hyperref rectangle
600         }
601 
602         // Hyperref: end of anchor rectangle. If this special is
603         // encountered, the rectangle, which was started with "ps:SDict
604         // begin H.S end" does not contain a link, but an anchor for a
605         // link. Anchors, however, have already been dealt with in the
606         // prescan phase and will not be considered here. Thus, we set
607         // HTML_href to NULL so that character drawing routines will no
608         // longer underline hyperlinks in blue, and remove the link from
609         // the hyperLinkList. NOTE: in a well-formed DVI file, the "H.A"
610         // special comes directly after the "H.S" special. A
611         // hyperlink-anchor rectangle therefore never contains characters,
612         // so no character will by accidentally underlined in blue.
613         if (special_command.endsWith(QLatin1String("H.A end"))) {
614             if (HTML_href != nullptr) {
615                 delete HTML_href;
616                 HTML_href = nullptr;
617             }
618             while (!currentlyDrawnPage->hyperLinkList.isEmpty())
619                 if (currentlyDrawnPage->hyperLinkList.last().linkText == QLatin1String("glopglyph"))
620                     currentlyDrawnPage->hyperLinkList.pop_back();
621                 else
622                     break;
623             return; // end of hyperref anchor
624         }
625 
626         // Hyperref: specification of a hyperref link rectangle's
627         // destination. As mentioned above, the destination of a hyperlink
628         // is specified only AFTER the rectangle has been specified. We
629         // will therefore go through the list of rectangles stored in
630         // currentlyDrawnPage->hyperLinkList, find those whose destination
631         // is open and fill in the value found here. NOTE: the character
632         // drawing routines sometimes split a single hyperlink rectangle
633         // into several rectangles (e.g. if the font changes, or when a
634         // line break is encountered)
635         if (special_command.startsWith(QLatin1String("ps:SDict begin [")) && special_command.endsWith(QLatin1String(" pdfmark end"))) {
636             if (!currentlyDrawnPage->hyperLinkList.isEmpty()) {
637                 QString targetName = special_command.section(QLatin1Char('('), 1, 1).section(QLatin1Char(')'), 0, 0);
638                 QVector<Hyperlink>::iterator it;
639                 for (it = currentlyDrawnPage->hyperLinkList.begin(); it != currentlyDrawnPage->hyperLinkList.end(); ++it)
640                     if (it->linkText == QLatin1String("glopglyph"))
641                         it->linkText = targetName;
642             }
643             return; // hyperref definition of link/anchor/bookmark/etc
644         }
645     }
646 
647     // Detect text rotation specials that are included by the graphicx
648     // package. If one of these specials is found, the state of the
649     // painter is saved, and the coordinate system is rotated
650     // accordingly
651     if (special_command.startsWith(QLatin1String("ps: gsave currentpoint currentpoint translate ")) && special_command.endsWith(QLatin1String(" neg rotate neg exch neg exch translate"))) {
652         bool ok;
653         double angle = special_command.section(QLatin1Char(' '), 5, 5).toDouble(&ok);
654         if (ok == true) {
655             int x = ((int)((currinf.data.dvi_h) / (shrinkfactor * 65536)));
656             int y = currinf.data.pxl_v;
657 
658             foreGroundPainter->save();
659             // Rotate about the current point
660             foreGroundPainter->translate(x, y);
661             foreGroundPainter->rotate(-angle);
662             foreGroundPainter->translate(-x, -y);
663         } else
664             printErrorMsgForSpecials(i18n("Error in DVIfile '%1', page %2. Could not interpret angle in text rotation special.", dviFile->filename, current_page));
665     }
666 
667     // The graphicx package marks the end of rotated text with this
668     // special. The state of the painter is restored.
669     if (special_command == QLatin1String("ps: currentpoint grestore moveto")) {
670         foreGroundPainter->restore();
671     }
672 
673     // The following special commands are not used here; they are of
674     // interest only during the prescan phase. We recognize them here
675     // anyway, to make sure that KDVI doesn't complain about
676     // unrecognized special commands.
677     if ((cp[0] == '!') || (cp[0] == '"') || (qstrnicmp(cp, "html:<A name=", 13) == 0) || (qstrnicmp(cp, "ps:", 3) == 0) || (qstrnicmp(cp, "papersize", 9) == 0) || (qstrnicmp(cp, "header", 6) == 0) || (qstrnicmp(cp, "background", 10) == 0))
678         return;
679 
680     printErrorMsgForSpecials(i18n("The special command '%1' is not implemented.", special_command));
681     return;
682 }
683