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