File indexing completed on 2024-05-12 04:20:03
0001 /* This file is part of the KDE libraries 0002 Copyright (C) 2001 Malte Starostik <malte@kde.org> 0003 0004 Handling of EPS previews Copyright (C) 2003 Philipp Hullmann <phull@gmx.de> 0005 0006 This library is free software; you can redistribute it and/or 0007 modify it under the terms of the GNU Library General Public 0008 License as published by the Free Software Foundation; either 0009 version 2 of the License, or (at your option) any later version. 0010 0011 This library is distributed in the hope that it will be useful, 0012 but WITHOUT ANY WARRANTY; without even the implied warranty of 0013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0014 Library General Public License for more details. 0015 0016 You should have received a copy of the GNU Library General Public License 0017 along with this library; see the file COPYING.LIB. If not, write to 0018 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 0019 Boston, MA 02110-1301, USA. 0020 */ 0021 0022 /* This function gets a path of a DVI, EPS, PS or PDF file and 0023 produces a PNG-Thumbnail which is stored as a QImage 0024 0025 The program works as follows 0026 0027 1. Test if file is a DVI file 0028 0029 2. Create a child process (1), in which the 0030 file is to be changed into a PNG 0031 0032 3. Child-process (1) : 0033 0034 4. If file is DVI continue with 6 0035 0036 5. If file is no DVI continue with 9 0037 0038 6. Create another child process (2), in which the DVI is 0039 turned into PS using dvips 0040 0041 7. Parent process (2) : 0042 Turn the recently created PS file into a PNG file using gs 0043 0044 8. continue with 10 0045 0046 9. Turn the PS,PDF or EPS file into a PNG file using gs 0047 0048 10. Parent process (1) 0049 store data in a QImage 0050 */ 0051 0052 #ifdef HAVE_CONFIG_H 0053 #include <config.h> 0054 #endif 0055 0056 0057 #include <assert.h> 0058 #include <ctype.h> 0059 #include <stdlib.h> 0060 #include <stdio.h> 0061 #include <unistd.h> 0062 #include <signal.h> 0063 #ifdef HAVE_SYS_SELECT_H 0064 #include <sys/select.h> 0065 #endif 0066 #include <sys/time.h> 0067 #include <sys/wait.h> 0068 #include <fcntl.h> 0069 #include <errno.h> 0070 0071 #include <QColor> 0072 #include <QFile> 0073 #include <QImage> 0074 #include <QVector> 0075 0076 0077 #include "gscreator.h" 0078 #include "dscparse.h" 0079 0080 #include <KPluginFactory> 0081 0082 K_PLUGIN_CLASS_WITH_JSON(GSCreator, "gsthumbnail.json") 0083 0084 // This PS snippet will be prepended to the actual file so that only 0085 // the first page is output. 0086 static const char *psprolog = 0087 "%!PS-Adobe-3.0\n" 0088 "/.showpage.orig /showpage load def\n" 0089 "/.showpage.firstonly {\n" 0090 " .showpage.orig\n" 0091 " quit\n" 0092 "} def\n" 0093 "/showpage { .showpage.firstonly } def\n"; 0094 0095 // This is the code recommended by Adobe tech note 5002 for including 0096 // EPS files. 0097 static const char *epsprolog = 0098 "%!PS-Adobe-3.0\n" 0099 "userdict begin /pagelevel save def /showpage { } def\n" 0100 "0 setgray 0 setlinecap 1 setlinewidth 0 setlinejoin 10 setmiterlimit\n" 0101 "[ ] 0 setdash newpath false setoverprint false setstrokeadjust\n"; 0102 0103 static const char * gsargs_ps[] = { 0104 "gs", 0105 "-sDEVICE=png16m", 0106 "-sOutputFile=-", 0107 "-dSAFER", 0108 "-dPARANOIDSAFER", 0109 "-dNOPAUSE", 0110 "-dFirstPage=1", 0111 "-dLastPage=1", 0112 "-q", 0113 "-", 0114 nullptr, // file name 0115 "-c", 0116 "showpage", 0117 "-c", 0118 "quit", 0119 nullptr 0120 }; 0121 0122 static const char * gsargs_eps[] = { 0123 "gs", 0124 "-sDEVICE=png16m", 0125 "-sOutputFile=-", 0126 "-dSAFER", 0127 "-dPARANOIDSAFER", 0128 "-dNOPAUSE", 0129 nullptr, // page size 0130 nullptr, // resolution 0131 "-q", 0132 "-", 0133 nullptr, // file name 0134 "-c", 0135 "pagelevel", 0136 "-c", 0137 "restore", 0138 "-c", 0139 "end", 0140 "-c", 0141 "showpage", 0142 "-c", 0143 "quit", 0144 nullptr 0145 }; 0146 0147 static const char *dvipsargs[] = { 0148 "dvips", 0149 "-n", 0150 "1", 0151 "-q", 0152 "-o", 0153 "-", 0154 nullptr, // file name 0155 nullptr 0156 }; 0157 0158 static bool correctDVI(const QString& filename); 0159 0160 0161 namespace { 0162 bool got_sig_term = false; 0163 void handle_sigterm( int ) { 0164 got_sig_term = true; 0165 } 0166 } 0167 0168 GSCreator::GSCreator(QObject *parent, const QVariantList &args) 0169 : KIO::ThumbnailCreator(parent, args) 0170 { 0171 } 0172 0173 KIO::ThumbnailResult GSCreator::create(const KIO::ThumbnailRequest &request) 0174 { 0175 const QString path = request.url().toLocalFile(); 0176 const int width = request.targetSize().width(); 0177 const int height = request.targetSize().height(); 0178 // The code in the loop (when testing whether got_sig_term got set) 0179 // should read some variation of: 0180 // parentJob()->wasKilled() 0181 // 0182 // Unfortunatelly, that's currently impossible without breaking BIC. 0183 // So we need to catch the signal ourselves. 0184 // Otherwise, on certain funny PS files (for example 0185 // http://www.tjhsst.edu/~Eedanaher/pslife/life.ps ) 0186 // gs would run forever after we were dead. 0187 // #### Reconsider for KDE 4 ### 0188 // (24/12/03 - luis_pedro) 0189 // 0190 typedef void ( *sighandler_t )( int ); 0191 // according to linux's "man signal" the above typedef is a gnu extension 0192 sighandler_t oldhandler = signal( SIGTERM, handle_sigterm ); 0193 0194 int input[2]; 0195 int output[2]; 0196 int dvipipe[2]; 0197 0198 QByteArray data(1024, '\0'); 0199 0200 bool ok = false; 0201 0202 // Test if file is DVI 0203 bool no_dvi =!correctDVI(request.url().toLocalFile()); 0204 0205 if (pipe(input) == -1) { 0206 return KIO::ThumbnailResult::fail(); 0207 } 0208 if (pipe(output) == -1) { 0209 close(input[0]); 0210 close(input[1]); 0211 return KIO::ThumbnailResult::fail(); 0212 } 0213 0214 KDSC dsc; 0215 endComments = false; 0216 dsc.setCommentHandler(this); 0217 0218 if (no_dvi) 0219 { 0220 FILE* fp = fopen(QFile::encodeName(path), "r"); 0221 if (fp == nullptr) return KIO::ThumbnailResult::fail(); 0222 0223 char buf[4096]; 0224 int count; 0225 while ((count = fread(buf, sizeof(char), 4096, fp)) != 0 0226 && !endComments) { 0227 dsc.scanData(buf, count); 0228 } 0229 fclose(fp); 0230 0231 if (dsc.pjl() || dsc.ctrld()) { 0232 // this file is a mess. 0233 return KIO::ThumbnailResult::fail(); 0234 } 0235 } 0236 0237 std::unique_ptr<KDSCBBOX> bbox = dsc.bbox(); 0238 0239 const bool is_encapsulated = no_dvi 0240 && (path.endsWith(QLatin1String(".eps"), Qt::CaseInsensitive) 0241 || path.endsWith(QLatin1String(".epsi"), Qt::CaseInsensitive)) 0242 && bbox.get() != nullptr 0243 && (bbox->width() > 0) 0244 && (bbox->height() > 0) 0245 && (dsc.page_count() <= 1); 0246 0247 char translation[64] = ""; 0248 char pagesize[32] = ""; 0249 char resopt[32] = ""; 0250 0251 if (is_encapsulated) { 0252 // GhostScript's rendering at the extremely low resolutions 0253 // required for thumbnails leaves something to be desired. To 0254 // get nicer images, we render to four times the required 0255 // resolution and let QImage scale the result. 0256 const int hres = (width * 72) / bbox->width(); 0257 const int vres = (height * 72) / bbox->height(); 0258 const int resolution = (hres > vres ? vres : hres) * 4; 0259 const int gswidth = ((bbox->urx() - bbox->llx()) * resolution) / 72; 0260 const int gsheight = ((bbox->ury() - bbox->lly()) * resolution) / 72; 0261 0262 snprintf(pagesize, 31, "-g%ix%i", gswidth, gsheight); 0263 snprintf(resopt, 31, "-r%i", resolution); 0264 snprintf(translation, 63, 0265 " 0 %i sub 0 %i sub translate\n", bbox->llx(), 0266 bbox->lly()); 0267 } 0268 0269 const CDSC_PREVIEW_TYPE previewType = 0270 static_cast<CDSC_PREVIEW_TYPE>(dsc.preview()); 0271 0272 switch (previewType) { 0273 case CDSC_TIFF: 0274 case CDSC_WMF: 0275 case CDSC_PICT: 0276 // FIXME: these should take precedence, since they can hold 0277 // color previews, which EPSI can't (or can it?). 0278 break; 0279 case CDSC_EPSI: 0280 { 0281 const int xscale = bbox->width() / width; 0282 const int yscale = bbox->height() / height; 0283 const int scale = xscale < yscale ? xscale : yscale; 0284 if (scale == 0) break; 0285 if (auto result = getEPSIPreview(path, 0286 dsc.beginpreview(), 0287 dsc.endpreview(), 0288 bbox->width() / scale, 0289 bbox->height() / scale); result.isValid()) 0290 return result; 0291 // If the preview extraction routine fails, gs is used to 0292 // create a thumbnail. 0293 } 0294 break; 0295 case CDSC_NOPREVIEW: 0296 default: 0297 // need to run ghostscript in these cases 0298 break; 0299 } 0300 0301 pid_t pid = fork(); 0302 if (pid == 0) { 0303 // Child process (1) 0304 0305 // close(STDERR_FILENO); 0306 0307 // find first zero entry in gsargs and put the filename 0308 // or - (stdin) there, if DVI 0309 const char **gsargs = gsargs_ps; 0310 const char **arg = gsargs; 0311 0312 if (no_dvi && is_encapsulated) { 0313 gsargs = gsargs_eps; 0314 arg = gsargs; 0315 0316 // find first zero entry and put page size there 0317 while (*arg) ++arg; 0318 *arg = pagesize; 0319 0320 // find second zero entry and put resolution there 0321 while (*arg) ++arg; 0322 *arg = resopt; 0323 } 0324 0325 // find next zero entry and put the filename there 0326 QByteArray fname = QFile::encodeName( path ); 0327 while (*arg) 0328 ++arg; 0329 if( no_dvi ) 0330 *arg = fname.data(); 0331 else 0332 *arg = "-"; 0333 0334 // find first zero entry in dvipsargs and put the filename there 0335 arg = dvipsargs; 0336 while (*arg) 0337 ++arg; 0338 *arg = fname.data(); 0339 0340 if( !no_dvi ){ 0341 pipe(dvipipe); 0342 pid_t pid_two = fork(); 0343 if( pid_two == 0 ){ 0344 // Child process (2), reopen stdout on the pipe "dvipipe" and exec dvips 0345 0346 close(input[0]); 0347 close(input[1]); 0348 close(output[0]); 0349 close(output[1]); 0350 close(dvipipe[0]); 0351 0352 dup2( dvipipe[1], STDOUT_FILENO); 0353 0354 execvp(dvipsargs[0], const_cast<char *const *>(dvipsargs)); 0355 _exit(1); 0356 } 0357 else if(pid_two != -1){ 0358 close(input[1]); 0359 close(output[0]); 0360 close(dvipipe[1]); 0361 0362 dup2( dvipipe[0], STDIN_FILENO); 0363 dup2( output[1], STDOUT_FILENO); 0364 0365 execvp(gsargs[0], const_cast<char *const *>(gsargs)); 0366 _exit(1); 0367 } 0368 else{ 0369 // fork() (2) failed, close these 0370 close(dvipipe[0]); 0371 close(dvipipe[1]); 0372 } 0373 0374 } 0375 else if( no_dvi ){ 0376 // Reopen stdin/stdout on the pipes and exec gs 0377 close(input[1]); 0378 close(output[0]); 0379 0380 dup2(input[0], STDIN_FILENO); 0381 dup2(output[1], STDOUT_FILENO); 0382 0383 execvp(gsargs[0], const_cast<char *const *>(gsargs)); 0384 _exit(1); 0385 } 0386 } 0387 else if (pid != -1) { 0388 // Parent process, write first-page-only-hack (the hack is not 0389 // used if DVI) and read the png output 0390 close(input[0]); 0391 close(output[1]); 0392 const char *prolog; 0393 if (is_encapsulated) 0394 prolog = epsprolog; 0395 else 0396 prolog = psprolog; 0397 int count = write(input[1], prolog, strlen(prolog)); 0398 if (is_encapsulated) 0399 write(input[1], translation, strlen(translation)); 0400 0401 close(input[1]); 0402 if (count == static_cast<int>(strlen(prolog))) { 0403 int offset = 0; 0404 while (!ok) { 0405 fd_set fds; 0406 FD_ZERO(&fds); 0407 FD_SET(output[0], &fds); 0408 struct timeval tv; 0409 tv.tv_sec = 20; 0410 tv.tv_usec = 0; 0411 0412 got_sig_term = false; 0413 if (select(output[0] + 1, &fds, nullptr, nullptr, &tv) <= 0) { 0414 if ( ( errno == EINTR || errno == EAGAIN ) && !got_sig_term ) continue; 0415 break; // error, timeout or master wants us to quit (SIGTERM) 0416 } 0417 if (FD_ISSET(output[0], &fds)) { 0418 count = read(output[0], data.data() + offset, 1024); 0419 if (count == -1) 0420 break; 0421 else 0422 if (count) // prepare for next block 0423 { 0424 offset += count; 0425 data.resize(offset + 1024); 0426 } 0427 else // got all data 0428 { 0429 data.resize(offset); 0430 ok = true; 0431 } 0432 } 0433 } 0434 } 0435 if (!ok) // error or timeout, gs probably didn't exit yet 0436 { 0437 kill(pid, SIGTERM); 0438 } 0439 0440 int status = 0; 0441 int ret; 0442 do { 0443 ret = waitpid(pid, &status, 0); 0444 } while (ret == -1 && errno == EINTR); 0445 if (ret != pid || (status != 0 && status != 256) ) 0446 ok = false; 0447 } 0448 else { 0449 // fork() (1) failed, close these 0450 close(input[0]); 0451 close(input[1]); 0452 close(output[1]); 0453 } 0454 close(output[0]); 0455 0456 QImage img; 0457 bool loaded = img.loadFromData( data ); 0458 0459 if (!loaded) { 0460 // Sometimes gs spits some warning messages before the actual image 0461 // try to skip them 0462 const QByteArray pngHeader = "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A"; 0463 const int pngMarkerIndex = data.indexOf(pngHeader); 0464 if (pngMarkerIndex > 0) { 0465 data = data.mid(pngMarkerIndex); 0466 loaded = img.loadFromData( data ); 0467 } 0468 } 0469 0470 if ( got_sig_term && 0471 oldhandler != SIG_ERR && 0472 oldhandler != SIG_DFL && 0473 oldhandler != SIG_IGN ) { 0474 oldhandler( SIGTERM ); // propagate the signal. Other things might rely on it 0475 } 0476 if ( oldhandler != SIG_ERR ) signal( SIGTERM, oldhandler ); 0477 0478 if (loaded) { 0479 return KIO::ThumbnailResult::pass(img); 0480 } 0481 0482 return KIO::ThumbnailResult::fail(); 0483 } 0484 0485 void GSCreator::comment(Name name) 0486 { 0487 switch (name) { 0488 case EndPreview: 0489 case BeginProlog: 0490 case Page: 0491 endComments = true; 0492 break; 0493 0494 default: 0495 break; 0496 } 0497 } 0498 0499 // Quick function to check if the filename corresponds to a valid DVI 0500 // file. Returns true if <filename> is a DVI file, false otherwise. 0501 0502 static bool correctDVI(const QString& filename) 0503 { 0504 QFile f(filename); 0505 if (!f.open(QIODevice::ReadOnly)) 0506 return false; 0507 0508 unsigned char test[4]; 0509 if ( f.read( (char *)test,2)<2 || test[0] != 247 || test[1] != 2 ) 0510 return false; 0511 0512 int n = f.size(); 0513 if ( n < 134 ) // Too short for a dvi file 0514 return false; 0515 f.seek( n-4 ); 0516 0517 unsigned char trailer[4] = { 0xdf,0xdf,0xdf,0xdf }; 0518 0519 if ( f.read( (char *)test, 4 )<4 || strncmp( (char *)test, (char*) trailer, 4 ) ) 0520 return false; 0521 // We suppose now that the dvi file is complete and OK 0522 return true; 0523 } 0524 0525 KIO::ThumbnailResult GSCreator::getEPSIPreview(const QString &path, long start, long 0526 end, int imgwidth, int imgheight) 0527 { 0528 FILE *fp; 0529 fp = fopen(QFile::encodeName(path), "r"); 0530 if (fp == nullptr) return KIO::ThumbnailResult::fail(); 0531 0532 const long previewsize = end - start + 1; 0533 0534 char *buf = (char *) malloc(previewsize); 0535 fseek(fp, start, SEEK_SET); 0536 int count = fread(buf, sizeof(char), previewsize - 1, fp); 0537 fclose(fp); 0538 buf[previewsize - 1] = 0; 0539 if (count != previewsize - 1) 0540 { 0541 free(buf); 0542 return KIO::ThumbnailResult::fail(); 0543 } 0544 0545 QString previewstr = QString::fromLatin1(buf); 0546 free(buf); 0547 0548 int offset = 0; 0549 while ((offset < previewsize) && !(previewstr[offset].isDigit())) offset++; 0550 int digits = 0; 0551 while ((offset + digits < previewsize) && previewstr[offset + digits].isDigit()) digits++; 0552 int width = previewstr.mid(offset, digits).toInt(); 0553 offset += digits + 1; 0554 while ((offset < previewsize) && !(previewstr[offset].isDigit())) offset++; 0555 digits = 0; 0556 while ((offset + digits < previewsize) && previewstr[offset + digits].isDigit()) digits++; 0557 int height = previewstr.mid(offset, digits).toInt(); 0558 offset += digits + 1; 0559 while ((offset < previewsize) && !(previewstr[offset].isDigit())) offset++; 0560 digits = 0; 0561 while ((offset + digits < previewsize) && previewstr[offset + digits].isDigit()) digits++; 0562 int depth = previewstr.mid(offset, digits).toInt(); 0563 0564 // skip over the rest of the BeginPreview comment 0565 while ((offset < previewsize) && 0566 previewstr[offset] != QLatin1Char('\n') && 0567 previewstr[offset] != QLatin1Char('\r')) offset++; 0568 while ((offset < previewsize) && previewstr[offset] != QLatin1Char('%')) offset++; 0569 0570 unsigned int imagedepth; 0571 switch (depth) { 0572 case 1: 0573 case 2: 0574 case 4: 0575 case 8: 0576 imagedepth = 8; 0577 break; 0578 case 12: // valid, but not (yet) supported 0579 default: // illegal value 0580 return KIO::ThumbnailResult::fail(); 0581 } 0582 0583 unsigned int colors = (1U << depth); 0584 QImage img(width, height, QImage::Format_Indexed8); 0585 img.setColorCount(colors); 0586 0587 if (imagedepth <= 8) { 0588 for (unsigned int gray = 0; gray < colors; gray++) { 0589 unsigned int grayvalue = (255U * (colors - 1 - gray)) / 0590 (colors - 1); 0591 img.setColor(gray, qRgb(grayvalue, grayvalue, grayvalue)); 0592 } 0593 } 0594 0595 const unsigned int bits_per_scan_line = width * depth; 0596 unsigned int bytes_per_scan_line = bits_per_scan_line / 8; 0597 if (bits_per_scan_line % 8) bytes_per_scan_line++; 0598 const unsigned int bindatabytes = height * bytes_per_scan_line; 0599 QVector<unsigned char> bindata(bindatabytes); 0600 0601 for (unsigned int i = 0; i < bindatabytes; i++) { 0602 if (offset >= previewsize) 0603 return KIO::ThumbnailResult::fail(); 0604 0605 while (!isxdigit(previewstr[offset].toLatin1()) && 0606 offset < previewsize) 0607 offset++; 0608 0609 bool ok = false; 0610 bindata[i] = static_cast<unsigned char>(previewstr.mid(offset, 2).toUInt(&ok, 16)); 0611 if (!ok) 0612 return KIO::ThumbnailResult::fail(); 0613 0614 offset += 2; 0615 } 0616 0617 for (int scanline = 0; scanline < height; scanline++) { 0618 unsigned char *scanlineptr = img.scanLine(scanline); 0619 0620 for (int pixelindex = 0; pixelindex < width; pixelindex++) { 0621 unsigned char pixelvalue = 0; 0622 const unsigned int bitoffset = 0623 scanline * bytes_per_scan_line * 8U + pixelindex * depth; 0624 for (int depthindex = 0; depthindex < depth; 0625 depthindex++) { 0626 const unsigned int byteindex = (bitoffset + depthindex) / 8U; 0627 const unsigned int bitindex = 0628 7 - ((bitoffset + depthindex) % 8U); 0629 const unsigned char bitvalue = 0630 (bindata[byteindex] & static_cast<unsigned char>(1U << bitindex)) >> bitindex; 0631 pixelvalue |= (bitvalue << depthindex); 0632 } 0633 scanlineptr[pixelindex] = pixelvalue; 0634 } 0635 } 0636 0637 QImage outimg = img.convertToFormat(QImage::Format_RGB32).scaled(imgwidth, imgheight, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); 0638 0639 return !outimg.isNull() ? KIO::ThumbnailResult::pass(outimg) : KIO::ThumbnailResult::fail(); 0640 } 0641 0642 #include "gscreator.moc"