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
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
63 #ifdef HAVE_SYS_SELECT_H
64 #include <sys/select.h>
70 #include <kdemacros.h>
79 #include "gscreator.h"
80 #include "dscparse_adapter.h"
85 KDE_EXPORT ThumbCreator
*new_creator()
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
=
95 "/.showpage.orig /showpage load def\n"
96 "/.showpage.firstonly {\n"
100 "/showpage { .showpage.firstonly } def\n";
102 // This is the code recommended by Adobe tech note 5002 for including
104 static const char *epsprolog
=
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
[] = {
129 static const char * gsargs_eps
[] = {
154 static const char *dvipsargs
[] = {
165 static bool correctDVI(const QString
& filename
);
169 bool got_sig_term
= false;
170 void handle_sigterm( int ) {
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
);
198 QByteArray
data(1024, '\0');
202 // Test if file is DVI
203 bool no_dvi
=!correctDVI(path
);
205 if (pipe(input
) == -1) {
208 if (pipe(output
) == -1) {
216 dsc
.setCommentHandler(this);
220 FILE* fp
= fopen(QFile::encodeName(path
), "r");
221 if (fp
== 0) return false;
225 while ((count
= fread(buf
, sizeof(char), 4096, fp
)) != 0
227 dsc
.scanData(buf
, count
);
231 if (dsc
.pjl() || dsc
.ctrld()) {
232 // this file is a mess.
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(),
264 const CDSC_PREVIEW_TYPE previewType
=
265 static_cast<CDSC_PREVIEW_TYPE
>(dsc
.preview());
267 switch (previewType
) {
271 // FIXME: these should take precedence, since they can hold
272 // color previews, which EPSI can't (or can it?).
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
,
283 bbox
->width() / scale
,
284 bbox
->height() / scale
))
286 // If the preview extraction routine fails, gs is used to
287 // create a thumbnail.
292 // need to run ghostscript in these cases
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
) {
311 // find first zero entry and put page size there
315 // find second zero entry and put resolution there
320 // find next zero entry and put the filename there
321 QByteArray fname
= QFile::encodeName( path
);
329 // find first zero entry in dvipsargs and put the filename there
337 pid_t pid_two
= fork();
339 // Child process (2), reopen stdout on the pipe "dvipipe" and exec dvips
347 dup2( dvipipe
[1], STDOUT_FILENO
);
349 execvp(dvipsargs
[0], const_cast<char *const *>(dvipsargs
));
352 else if(pid_two
!= -1){
357 dup2( dvipipe
[0], STDIN_FILENO
);
358 dup2( output
[1], STDOUT_FILENO
);
360 execvp(gsargs
[0], const_cast<char *const *>(gsargs
));
364 // fork() (2) failed, close these
371 // Reopen stdin/stdout on the pipes and exec gs
375 dup2(input
[0], STDIN_FILENO
);
376 dup2(output
[1], STDOUT_FILENO
);
378 execvp(gsargs
[0], const_cast<char *const *>(gsargs
));
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
392 int count
= write(input
[1], prolog
, strlen(prolog
));
394 write(input
[1], translation
, strlen(translation
));
397 if (count
== static_cast<int>(strlen(prolog
))) {
402 FD_SET(output
[0], &fds
);
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);
417 if (count
) // prepare for next block
420 data
.resize(offset
+ 1024);
430 if (!ok
) // error or timeout, gs probably didn't exit yet
436 if (waitpid(pid
, &status
, 0) != pid
|| (status
!= 0 && status
!= 256) )
440 // fork() (1) failed, close these
447 int l
= img
.loadFromData( data
);
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
);
460 ThumbCreator::Flags
GSCreator::flags() const
462 return static_cast<Flags
>(DrawFrame
);
465 void GSCreator::comment(Name name
)
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
)
485 if (!f
.open(QIODevice::ReadOnly
))
488 unsigned char test
[4];
489 if ( f
.read( (char *)test
,2)<2 || test
[0] != 247 || test
[1] != 2 )
493 if ( n
< 134 ) // Too short for a dvi file
497 unsigned char trailer
[4] = { 0xdf,0xdf,0xdf,0xdf };
499 if ( f
.read( (char *)test
, 4 )<4 || strncmp( (char *)test
, (char*) trailer
, 4 ) )
501 // We suppose now that the dvi file is complete and OK
505 bool GSCreator::getEPSIPreview(const QString
&path
, long start
, long
506 end
, QImage
&outimg
, int imgwidth
, int imgheight
)
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
);
518 buf
[previewsize
- 1] = 0;
519 if (count
!= previewsize
- 1)
525 QString previewstr
= QString::fromLatin1(buf
);
529 while ((offset
< previewsize
) && !(previewstr
[offset
].isDigit())) offset
++;
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
++;
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
++;
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
;
558 case 12: // valid, but not (yet) supported
559 default: // illegal value
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
)) /
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
)
585 while (!isxdigit(previewstr
[offset
].toLatin1()) &&
586 offset
< previewsize
)
590 bindata
[i
] = static_cast<unsigned char>(previewstr
.mid(offset
, 2).toUInt(&ok
, 16));
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
;
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
);