update
[kdegraphics.git] / strigi-analyzer / ps / gscreator.cpp
blobc5eb71acc46b4fd783eb2f0b43728978b117344e
1 /* This file is part of the KDE libraries
2 Copyright (C) 2001 Malte Starostik <malte@kde.org>
4 Handling of EPS previews Copyright (C) 2003 Philipp Hullmann <phull@gmx.de>
6 This library is free software; you can redistribute it and/or
7 modify it under the terms of the GNU Library General Public
8 License as published by the Free Software Foundation; either
9 version 2 of the License, or (at your option) any later version.
11 This library is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Library General Public License for more details.
16 You should have received a copy of the GNU Library General Public License
17 along with this library; see the file COPYING.LIB. If not, write to
18 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 Boston, MA 02110-1301, USA.
22 /* This function gets a path of a DVI, EPS, PS or PDF file and
23 produces a PNG-Thumbnail which is stored as a QImage
25 The program works as follows
27 1. Test if file is a DVI file
29 2. Create a child process (1), in which the
30 file is to be changed into a PNG
32 3. Child-process (1) :
34 4. If file is DVI continue with 6
36 5. If file is no DVI continue with 9
38 6. Create another child process (2), in which the DVI is
39 turned into PS using dvips
41 7. Parent process (2) :
42 Turn the recently created PS file into a PNG file using gs
44 8. continue with 10
46 9. Turn the PS,PDF or EPS file into a PNG file using gs
48 10. Parent process (1)
49 store data in a QImage
52 #ifdef HAVE_CONFIG_H
53 #include <config.h>
54 #endif
57 #include <assert.h>
58 #include <ctype.h>
59 #include <stdlib.h>
60 #include <stdio.h>
61 #include <unistd.h>
62 #include <signal.h>
63 #ifdef HAVE_SYS_SELECT_H
64 #include <sys/select.h>
65 #endif
66 #include <sys/time.h>
67 #include <sys/wait.h>
68 #include <fcntl.h>
69 #include <errno.h>
70 #include <kdemacros.h>
72 #include <qcolor.h>
73 #include <qfile.h>
74 #include <qimage.h>
75 #include <qregexp.h>
76 #include <QVector>
79 #include "gscreator.h"
80 #include "dscparse_adapter.h"
81 #include "dscparse.h"
83 extern "C"
85 KDE_EXPORT ThumbCreator *new_creator()
87 return new GSCreator;
91 // This PS snippet will be prepended to the actual file so that only
92 // the first page is output.
93 static const char *psprolog =
94 "%!PS-Adobe-3.0\n"
95 "/.showpage.orig /showpage load def\n"
96 "/.showpage.firstonly {\n"
97 " .showpage.orig\n"
98 " quit\n"
99 "} def\n"
100 "/showpage { .showpage.firstonly } def\n";
102 // This is the code recommended by Adobe tech note 5002 for including
103 // EPS files.
104 static const char *epsprolog =
105 "%!PS-Adobe-3.0\n"
106 "userdict begin /pagelevel save def /showpage { } def\n"
107 "0 setgray 0 setlinecap 1 setlinewidth 0 setlinejoin 10 setmiterlimit\n"
108 "[ ] 0 setdash newpath false setoverprint false setstrokeadjust\n";
110 static const char * gsargs_ps[] = {
111 "gs",
112 "-sDEVICE=png16m",
113 "-sOutputFile=-",
114 "-dSAFER",
115 "-dPARANOIDSAFER",
116 "-dNOPAUSE",
117 "-dFirstPage=1",
118 "-dLastPage=1",
119 "-q",
120 "-",
121 0, // file name
122 "-c",
123 "showpage",
124 "-c",
125 "quit",
129 static const char * gsargs_eps[] = {
130 "gs",
131 "-sDEVICE=png16m",
132 "-sOutputFile=-",
133 "-dSAFER",
134 "-dPARANOIDSAFER",
135 "-dNOPAUSE",
136 0, // page size
137 0, // resolution
138 "-q",
139 "-",
140 0, // file name
141 "-c",
142 "pagelevel",
143 "-c",
144 "restore",
145 "-c",
146 "end",
147 "-c",
148 "showpage",
149 "-c",
150 "quit",
154 static const char *dvipsargs[] = {
155 "dvips",
156 "-n",
157 "1",
158 "-q",
159 "-o",
160 "-",
161 0, // file name
165 static bool correctDVI(const QString& filename);
168 namespace {
169 bool got_sig_term = false;
170 void handle_sigterm( int ) {
171 got_sig_term = true;
176 bool GSCreator::create(const QString &path, int width, int height, QImage &img)
178 // The code in the loop (when testing whether got_sig_term got set)
179 // should read some variation of:
180 // parentJob()->wasKilled()
182 // Unfortunatelly, that's currently impossible without breaking BIC.
183 // So we need to catch the signal ourselves.
184 // Otherwise, on certain funny PS files (for example
185 // http://www.tjhsst.edu/~Eedanaher/pslife/life.ps )
186 // gs would run forever after we were dead.
187 // #### Reconsider for KDE 4 ###
188 // (24/12/03 - luis_pedro)
190 typedef void ( *sighandler_t )( int );
191 // according to linux's "man signal" the above typedef is a gnu extension
192 sighandler_t oldhandler = signal( SIGTERM, handle_sigterm );
194 int input[2];
195 int output[2];
196 int dvipipe[2];
198 QByteArray data(1024, '\0');
200 bool ok = false;
202 // Test if file is DVI
203 bool no_dvi =!correctDVI(path);
205 if (pipe(input) == -1) {
206 return false;
208 if (pipe(output) == -1) {
209 close(input[0]);
210 close(input[1]);
211 return false;
214 KDSC dsc;
215 endComments = false;
216 dsc.setCommentHandler(this);
218 if (no_dvi)
220 FILE* fp = fopen(QFile::encodeName(path), "r");
221 if (fp == 0) return false;
223 char buf[4096];
224 int count;
225 while ((count = fread(buf, sizeof(char), 4096, fp)) != 0
226 && !endComments) {
227 dsc.scanData(buf, count);
229 fclose(fp);
231 if (dsc.pjl() || dsc.ctrld()) {
232 // this file is a mess.
233 return false;
237 const bool is_encapsulated = no_dvi &&
238 (path.indexOf(QRegExp("\\.epsi?$", Qt::CaseInsensitive)) > 0) &&
239 (dsc.bbox()->width() > 0) && (dsc.bbox()->height() > 0) &&
240 (dsc.page_count() <= 1);
242 char translation[64] = "";
243 char pagesize[32] = "";
244 char resopt[32] = "";
245 std::auto_ptr<KDSCBBOX> bbox = dsc.bbox();
246 if (is_encapsulated) {
247 // GhostScript's rendering at the extremely low resolutions
248 // required for thumbnails leaves something to be desired. To
249 // get nicer images, we render to four times the required
250 // resolution and let QImage scale the result.
251 const int hres = (width * 72) / bbox->width();
252 const int vres = (height * 72) / bbox->height();
253 const int resolution = (hres > vres ? vres : hres) * 4;
254 const int gswidth = ((bbox->urx() - bbox->llx()) * resolution) / 72;
255 const int gsheight = ((bbox->ury() - bbox->lly()) * resolution) / 72;
257 snprintf(pagesize, 31, "-g%ix%i", gswidth, gsheight);
258 snprintf(resopt, 31, "-r%i", resolution);
259 snprintf(translation, 63,
260 " 0 %i sub 0 %i sub translate\n", bbox->llx(),
261 bbox->lly());
264 const CDSC_PREVIEW_TYPE previewType =
265 static_cast<CDSC_PREVIEW_TYPE>(dsc.preview());
267 switch (previewType) {
268 case CDSC_TIFF:
269 case CDSC_WMF:
270 case CDSC_PICT:
271 // FIXME: these should take precedence, since they can hold
272 // color previews, which EPSI can't (or can it?).
273 break;
274 case CDSC_EPSI:
276 const int xscale = bbox->width() / width;
277 const int yscale = bbox->height() / height;
278 const int scale = xscale < yscale ? xscale : yscale;
279 if (getEPSIPreview(path,
280 dsc.beginpreview(),
281 dsc.endpreview(),
282 img,
283 bbox->width() / scale,
284 bbox->height() / scale))
285 return true;
286 // If the preview extraction routine fails, gs is used to
287 // create a thumbnail.
289 break;
290 case CDSC_NOPREVIEW:
291 default:
292 // need to run ghostscript in these cases
293 break;
296 pid_t pid = fork();
297 if (pid == 0) {
298 // Child process (1)
300 // close(STDERR_FILENO);
302 // find first zero entry in gsargs and put the filename
303 // or - (stdin) there, if DVI
304 const char **gsargs = gsargs_ps;
305 const char **arg = gsargs;
307 if (no_dvi && is_encapsulated) {
308 gsargs = gsargs_eps;
309 arg = gsargs;
311 // find first zero entry and put page size there
312 while (*arg) ++arg;
313 *arg = pagesize;
315 // find second zero entry and put resolution there
316 while (*arg) ++arg;
317 *arg = resopt;
320 // find next zero entry and put the filename there
321 QByteArray fname = QFile::encodeName( path );
322 while (*arg)
323 ++arg;
324 if( no_dvi )
325 *arg = fname.data();
326 else
327 *arg = "-";
329 // find first zero entry in dvipsargs and put the filename there
330 arg = dvipsargs;
331 while (*arg)
332 ++arg;
333 *arg = fname.data();
335 if( !no_dvi ){
336 pipe(dvipipe);
337 pid_t pid_two = fork();
338 if( pid_two == 0 ){
339 // Child process (2), reopen stdout on the pipe "dvipipe" and exec dvips
341 close(input[0]);
342 close(input[1]);
343 close(output[0]);
344 close(output[1]);
345 close(dvipipe[0]);
347 dup2( dvipipe[1], STDOUT_FILENO);
349 execvp(dvipsargs[0], const_cast<char *const *>(dvipsargs));
350 exit(1);
352 else if(pid_two != -1){
353 close(input[1]);
354 close(output[0]);
355 close(dvipipe[1]);
357 dup2( dvipipe[0], STDIN_FILENO);
358 dup2( output[1], STDOUT_FILENO);
360 execvp(gsargs[0], const_cast<char *const *>(gsargs));
361 exit(1);
363 else{
364 // fork() (2) failed, close these
365 close(dvipipe[0]);
366 close(dvipipe[1]);
370 else if( no_dvi ){
371 // Reopen stdin/stdout on the pipes and exec gs
372 close(input[1]);
373 close(output[0]);
375 dup2(input[0], STDIN_FILENO);
376 dup2(output[1], STDOUT_FILENO);
378 execvp(gsargs[0], const_cast<char *const *>(gsargs));
379 exit(1);
382 else if (pid != -1) {
383 // Parent process, write first-page-only-hack (the hack is not
384 // used if DVI) and read the png output
385 close(input[0]);
386 close(output[1]);
387 const char *prolog;
388 if (is_encapsulated)
389 prolog = epsprolog;
390 else
391 prolog = psprolog;
392 int count = write(input[1], prolog, strlen(prolog));
393 if (is_encapsulated)
394 write(input[1], translation, strlen(translation));
396 close(input[1]);
397 if (count == static_cast<int>(strlen(prolog))) {
398 int offset = 0;
399 while (!ok) {
400 fd_set fds;
401 FD_ZERO(&fds);
402 FD_SET(output[0], &fds);
403 struct timeval tv;
404 tv.tv_sec = 20;
405 tv.tv_usec = 0;
407 got_sig_term = false;
408 if (select(output[0] + 1, &fds, 0, 0, &tv) <= 0) {
409 if ( ( errno == EINTR || errno == EAGAIN ) && !got_sig_term ) continue;
410 break; // error, timeout or master wants us to quit (SIGTERM)
412 if (FD_ISSET(output[0], &fds)) {
413 count = read(output[0], data.data() + offset, 1024);
414 if (count == -1)
415 break;
416 else
417 if (count) // prepare for next block
419 offset += count;
420 data.resize(offset + 1024);
422 else // got all data
424 data.resize(offset);
425 ok = true;
430 if (!ok) // error or timeout, gs probably didn't exit yet
432 kill(pid, SIGTERM);
435 int status = 0;
436 if (waitpid(pid, &status, 0) != pid || (status != 0 && status != 256) )
437 ok = false;
439 else {
440 // fork() (1) failed, close these
441 close(input[0]);
442 close(input[1]);
443 close(output[1]);
445 close(output[0]);
447 int l = img.loadFromData( data );
449 if ( got_sig_term &&
450 oldhandler != SIG_ERR &&
451 oldhandler != SIG_DFL &&
452 oldhandler != SIG_IGN ) {
453 oldhandler( SIGTERM ); // propagate the signal. Other things might rely on it
455 if ( oldhandler != SIG_ERR ) signal( SIGTERM, oldhandler );
457 return ok && l;
460 ThumbCreator::Flags GSCreator::flags() const
462 return static_cast<Flags>(DrawFrame);
465 void GSCreator::comment(Name name)
467 switch (name) {
468 case EndPreview:
469 case BeginProlog:
470 case Page:
471 endComments = true;
472 break;
474 default:
475 break;
479 // Quick function to check if the filename corresponds to a valid DVI
480 // file. Returns true if <filename> is a DVI file, false otherwise.
482 static bool correctDVI(const QString& filename)
484 QFile f(filename);
485 if (!f.open(QIODevice::ReadOnly))
486 return false;
488 unsigned char test[4];
489 if ( f.read( (char *)test,2)<2 || test[0] != 247 || test[1] != 2 )
490 return false;
492 int n = f.size();
493 if ( n < 134 ) // Too short for a dvi file
494 return false;
495 f.seek( n-4 );
497 unsigned char trailer[4] = { 0xdf,0xdf,0xdf,0xdf };
499 if ( f.read( (char *)test, 4 )<4 || strncmp( (char *)test, (char*) trailer, 4 ) )
500 return false;
501 // We suppose now that the dvi file is complete and OK
502 return true;
505 bool GSCreator::getEPSIPreview(const QString &path, long start, long
506 end, QImage &outimg, int imgwidth, int imgheight)
508 FILE *fp;
509 fp = fopen(QFile::encodeName(path), "r");
510 if (fp == 0) return false;
512 const long previewsize = end - start + 1;
514 char *buf = (char *) malloc(previewsize);
515 fseek(fp, start, SEEK_SET);
516 int count = fread(buf, sizeof(char), previewsize - 1, fp);
517 fclose(fp);
518 buf[previewsize - 1] = 0;
519 if (count != previewsize - 1)
521 free(buf);
522 return false;
525 QString previewstr = QString::fromLatin1(buf);
526 free(buf);
528 int offset = 0;
529 while ((offset < previewsize) && !(previewstr[offset].isDigit())) offset++;
530 int digits = 0;
531 while ((offset + digits < previewsize) && previewstr[offset + digits].isDigit()) digits++;
532 int width = previewstr.mid(offset, digits).toInt();
533 offset += digits + 1;
534 while ((offset < previewsize) && !(previewstr[offset].isDigit())) offset++;
535 digits = 0;
536 while ((offset + digits < previewsize) && previewstr[offset + digits].isDigit()) digits++;
537 int height = previewstr.mid(offset, digits).toInt();
538 offset += digits + 1;
539 while ((offset < previewsize) && !(previewstr[offset].isDigit())) offset++;
540 digits = 0;
541 while ((offset + digits < previewsize) && previewstr[offset + digits].isDigit()) digits++;
542 int depth = previewstr.mid(offset, digits).toInt();
544 // skip over the rest of the BeginPreview comment
545 while ((offset < previewsize) &&
546 previewstr[offset] != '\n' &&
547 previewstr[offset] != '\r') offset++;
548 while ((offset < previewsize) && previewstr[offset] != '%') offset++;
550 unsigned int imagedepth;
551 switch (depth) {
552 case 1:
553 case 2:
554 case 4:
555 case 8:
556 imagedepth = 8;
557 break;
558 case 12: // valid, but not (yet) supported
559 default: // illegal value
560 return false;
563 unsigned int colors = (1U << depth);
564 QImage img(width, height, imagedepth, colors);
565 img.setAlphaBuffer(false);
567 if (imagedepth <= 8) {
568 for (unsigned int gray = 0; gray < colors; gray++) {
569 unsigned int grayvalue = (255U * (colors - 1 - gray)) /
570 (colors - 1);
571 img.setColor(gray, qRgb(grayvalue, grayvalue, grayvalue));
575 const unsigned int bits_per_scan_line = width * depth;
576 unsigned int bytes_per_scan_line = bits_per_scan_line / 8;
577 if (bits_per_scan_line % 8) bytes_per_scan_line++;
578 const unsigned int bindatabytes = height * bytes_per_scan_line;
579 QVector<unsigned char> bindata(bindatabytes);
581 for (unsigned int i = 0; i < bindatabytes; i++) {
582 if (offset >= previewsize)
583 return false;
585 while (!isxdigit(previewstr[offset].toLatin1()) &&
586 offset < previewsize)
587 offset++;
589 bool ok = false;
590 bindata[i] = static_cast<unsigned char>(previewstr.mid(offset, 2).toUInt(&ok, 16));
591 if (!ok)
592 return false;
594 offset += 2;
597 for (int scanline = 0; scanline < height; scanline++) {
598 unsigned char *scanlineptr = img.scanLine(scanline);
600 for (int pixelindex = 0; pixelindex < width; pixelindex++) {
601 unsigned char pixelvalue = 0;
602 const unsigned int bitoffset =
603 scanline * bytes_per_scan_line * 8U + pixelindex * depth;
604 for (int depthindex = 0; depthindex < depth;
605 depthindex++) {
606 const unsigned int byteindex = (bitoffset + depthindex) / 8U;
607 const unsigned int bitindex =
608 7 - ((bitoffset + depthindex) % 8U);
609 const unsigned char bitvalue =
610 (bindata[byteindex] & static_cast<unsigned char>(1U << bitindex)) >> bitindex;
611 pixelvalue |= (bitvalue << depthindex);
613 scanlineptr[pixelindex] = pixelvalue;
617 outimg = img.convertDepth(32).scaled(imgwidth, imgheight, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
619 return true;