diff options
Diffstat (limited to 'kfile-plugins/ps/gscreator.cpp')
-rw-r--r-- | kfile-plugins/ps/gscreator.cpp | 619 |
1 files changed, 619 insertions, 0 deletions
diff --git a/kfile-plugins/ps/gscreator.cpp b/kfile-plugins/ps/gscreator.cpp new file mode 100644 index 00000000..33cbc141 --- /dev/null +++ b/kfile-plugins/ps/gscreator.cpp @@ -0,0 +1,619 @@ +/* This file is part of the KDE libraries + Copyright (C) 2001 Malte Starostik <[email protected]> + + Handling of EPS previews Copyright (C) 2003 Philipp Hullmann <[email protected]> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +/* This function gets a path of a DVI, EPS, PS or PDF file and + produces a PNG-Thumbnail which is stored as a QImage + + The program works as follows + + 1. Test if file is a DVI file + + 2. Create a child process (1), in which the + file is to be changed into a PNG + + 3. Child-process (1) : + + 4. If file is DVI continue with 6 + + 5. If file is no DVI continue with 9 + + 6. Create another child process (2), in which the DVI is + turned into PS using dvips + + 7. Parent process (2) : + Turn the recently created PS file into a PNG file using gs + + 8. continue with 10 + + 9. Turn the PS,PDF or EPS file into a PNG file using gs + + 10. Parent process (1) + store data in a QImage +*/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + + +#include <assert.h> +#include <ctype.h> +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <signal.h> +#ifdef HAVE_SYS_SELECT_H +#include <sys/select.h> +#endif +#include <sys/time.h> +#include <sys/wait.h> +#include <fcntl.h> +#include <errno.h> +#include <kdemacros.h> + +#include <qcolor.h> +#include <qfile.h> +#include <qimage.h> +#include <qregexp.h> + + +#include "gscreator.h" +#include "dscparse_adapter.h" +#include "dscparse.h" + +extern "C" +{ + KDE_EXPORT ThumbCreator *new_creator() + { + return new GSCreator; + } +} + +// This PS snippet will be prepended to the actual file so that only +// the first page is output. +static const char *psprolog = + "%!PS-Adobe-3.0\n" + "/.showpage.orig /showpage load def\n" + "/.showpage.firstonly {\n" + " .showpage.orig\n" + " quit\n" + "} def\n" + "/showpage { .showpage.firstonly } def\n"; + +// This is the code recommended by Adobe tech note 5002 for including +// EPS files. +static const char *epsprolog = + "%!PS-Adobe-3.0\n" + "userdict begin /pagelevel save def /showpage { } def\n" + "0 setgray 0 setlinecap 1 setlinewidth 0 setlinejoin 10 setmiterlimit\n" + "[ ] 0 setdash newpath false setoverprint false setstrokeadjust\n"; + +static const char * gsargs_ps[] = { + "gs", + "-sDEVICE=png16m", + "-sOutputFile=-", + "-dSAFER", + "-dPARANOIDSAFER", + "-dNOPAUSE", + "-dFirstPage=1", + "-dLastPage=1", + "-q", + "-", + 0, // file name + "-c", + "showpage", + "-c", + "quit", + 0 +}; + +static const char * gsargs_eps[] = { + "gs", + "-sDEVICE=png16m", + "-sOutputFile=-", + "-dSAFER", + "-dPARANOIDSAFER", + "-dNOPAUSE", + 0, // page size + 0, // resolution + "-q", + "-", + 0, // file name + "-c", + "pagelevel", + "-c", + "restore", + "-c", + "end", + "-c", + "showpage", + "-c", + "quit", + 0 +}; + +static const char *dvipsargs[] = { + "dvips", + "-n", + "1", + "-q", + "-o", + "-", + 0, // file name + 0 +}; + +static bool correctDVI(const QString& filename); + + +namespace { + bool got_sig_term = false; + void handle_sigterm( int ) { + got_sig_term = true; + } +} + + +bool GSCreator::create(const QString &path, int width, int height, QImage &img) +{ +// The code in the loop (when testing whether got_sig_term got set) +// should read some variation of: +// parentJob()->wasKilled() +// +// Unfortunatelly, that's currently impossible without breaking BIC. +// So we need to catch the signal ourselves. +// Otherwise, on certain funny PS files (for example +// http://www.tjhsst.edu/~Eedanaher/pslife/life.ps ) +// gs would run forever after we were dead. +// #### Reconsider for KDE 4 ### +// (24/12/03 - luis_pedro) +// + typedef void ( *sighandler_t )( int ); + // according to linux's "man signal" the above typedef is a gnu extension + sighandler_t oldhandler = signal( SIGTERM, handle_sigterm ); + + int input[2]; + int output[2]; + int dvipipe[2]; + + QByteArray data(1024); + + bool ok = false; + + // Test if file is DVI + bool no_dvi =!correctDVI(path); + + if (pipe(input) == -1) { + return false; + } + if (pipe(output) == -1) { + close(input[0]); + close(input[1]); + return false; + } + + KDSC dsc; + endComments = false; + dsc.setCommentHandler(this); + + if (no_dvi) + { + FILE* fp = fopen(QFile::encodeName(path), "r"); + if (fp == 0) return false; + + char buf[4096]; + int count; + while ((count = fread(buf, sizeof(char), 4096, fp)) != 0 + && !endComments) { + dsc.scanData(buf, count); + } + fclose(fp); + + if (dsc.pjl() || dsc.ctrld()) { + // this file is a mess. + return false; + } + } + + const bool is_encapsulated = no_dvi && + (path.find(QRegExp("\\.epsi?$", false, false)) > 0) && + (dsc.bbox()->width() > 0) && (dsc.bbox()->height() > 0) && + (dsc.page_count() <= 1); + + char translation[64] = ""; + char pagesize[32] = ""; + char resopt[32] = ""; + std::auto_ptr<KDSCBBOX> bbox = dsc.bbox(); + if (is_encapsulated) { + // GhostScript's rendering at the extremely low resolutions + // required for thumbnails leaves something to be desired. To + // get nicer images, we render to four times the required + // resolution and let QImage scale the result. + const int hres = (width * 72) / bbox->width(); + const int vres = (height * 72) / bbox->height(); + const int resolution = (hres > vres ? vres : hres) * 4; + const int gswidth = ((bbox->urx() - bbox->llx()) * resolution) / 72; + const int gsheight = ((bbox->ury() - bbox->lly()) * resolution) / 72; + + snprintf(pagesize, 31, "-g%ix%i", gswidth, gsheight); + snprintf(resopt, 31, "-r%i", resolution); + snprintf(translation, 63, + " 0 %i sub 0 %i sub translate\n", bbox->llx(), + bbox->lly()); + } + + const CDSC_PREVIEW_TYPE previewType = + static_cast<CDSC_PREVIEW_TYPE>(dsc.preview()); + + switch (previewType) { + case CDSC_TIFF: + case CDSC_WMF: + case CDSC_PICT: + // FIXME: these should take precedence, since they can hold + // color previews, which EPSI can't (or can it?). + break; + case CDSC_EPSI: + { + const int xscale = bbox->width() / width; + const int yscale = bbox->height() / height; + const int scale = xscale < yscale ? xscale : yscale; + if (getEPSIPreview(path, + dsc.beginpreview(), + dsc.endpreview(), + img, + bbox->width() / scale, + bbox->height() / scale)) + return true; + // If the preview extraction routine fails, gs is used to + // create a thumbnail. + } + break; + case CDSC_NOPREVIEW: + default: + // need to run ghostscript in these cases + break; + } + + pid_t pid = fork(); + if (pid == 0) { + // Child process (1) + + // close(STDERR_FILENO); + + // find first zero entry in gsargs and put the filename + // or - (stdin) there, if DVI + const char **gsargs = gsargs_ps; + const char **arg = gsargs; + + if (no_dvi && is_encapsulated) { + gsargs = gsargs_eps; + arg = gsargs; + + // find first zero entry and put page size there + while (*arg) ++arg; + *arg = pagesize; + + // find second zero entry and put resolution there + while (*arg) ++arg; + *arg = resopt; + } + + // find next zero entry and put the filename there + QCString fname = QFile::encodeName( path ); + while (*arg) + ++arg; + if( no_dvi ) + *arg = fname.data(); + else + *arg = "-"; + + // find first zero entry in dvipsargs and put the filename there + arg = dvipsargs; + while (*arg) + ++arg; + *arg = fname.data(); + + if( !no_dvi ){ + pipe(dvipipe); + pid_t pid_two = fork(); + if( pid_two == 0 ){ + // Child process (2), reopen stdout on the pipe "dvipipe" and exec dvips + + close(input[0]); + close(input[1]); + close(output[0]); + close(output[1]); + close(dvipipe[0]); + + dup2( dvipipe[1], STDOUT_FILENO); + + execvp(dvipsargs[0], const_cast<char *const *>(dvipsargs)); + exit(1); + } + else if(pid_two != -1){ + close(input[1]); + close(output[0]); + close(dvipipe[1]); + + dup2( dvipipe[0], STDIN_FILENO); + dup2( output[1], STDOUT_FILENO); + + execvp(gsargs[0], const_cast<char *const *>(gsargs)); + exit(1); + } + else{ + // fork() (2) failed, close these + close(dvipipe[0]); + close(dvipipe[1]); + } + + } + else if( no_dvi ){ + // Reopen stdin/stdout on the pipes and exec gs + close(input[1]); + close(output[0]); + + dup2(input[0], STDIN_FILENO); + dup2(output[1], STDOUT_FILENO); + + execvp(gsargs[0], const_cast<char *const *>(gsargs)); + exit(1); + } + } + else if (pid != -1) { + // Parent process, write first-page-only-hack (the hack is not + // used if DVI) and read the png output + close(input[0]); + close(output[1]); + const char *prolog; + if (is_encapsulated) + prolog = epsprolog; + else + prolog = psprolog; + int count = write(input[1], prolog, strlen(prolog)); + if (is_encapsulated) + write(input[1], translation, strlen(translation)); + + close(input[1]); + if (count == static_cast<int>(strlen(prolog))) { + int offset = 0; + while (!ok) { + fd_set fds; + FD_ZERO(&fds); + FD_SET(output[0], &fds); + struct timeval tv; + tv.tv_sec = 20; + tv.tv_usec = 0; + + got_sig_term = false; + if (select(output[0] + 1, &fds, 0, 0, &tv) <= 0) { + if ( ( errno == EINTR || errno == EAGAIN ) && !got_sig_term ) continue; + break; // error, timeout or master wants us to quit (SIGTERM) + } + if (FD_ISSET(output[0], &fds)) { + count = read(output[0], data.data() + offset, 1024); + if (count == -1) + break; + else + if (count) // prepare for next block + { + offset += count; + data.resize(offset + 1024); + } + else // got all data + { + data.resize(offset); + ok = true; + } + } + } + } + if (!ok) // error or timeout, gs probably didn't exit yet + { + kill(pid, SIGTERM); + } + + int status = 0; + if (waitpid(pid, &status, 0) != pid || (status != 0 && status != 256) ) + ok = false; + } + else { + // fork() (1) failed, close these + close(input[0]); + close(input[1]); + close(output[1]); + } + close(output[0]); + + int l = img.loadFromData( data ); + + if ( got_sig_term && + oldhandler != SIG_ERR && + oldhandler != SIG_DFL && + oldhandler != SIG_IGN ) { + oldhandler( SIGTERM ); // propagate the signal. Other things might rely on it + } + if ( oldhandler != SIG_ERR ) signal( SIGTERM, oldhandler ); + + return ok && l; +} + +ThumbCreator::Flags GSCreator::flags() const +{ + return static_cast<Flags>(DrawFrame); +} + +void GSCreator::comment(Name name) +{ + switch (name) { + case EndPreview: + case BeginProlog: + case Page: + endComments = true; + break; + + default: + break; + } +} + +// Quick function to check if the filename corresponds to a valid DVI +// file. Returns true if <filename> is a DVI file, false otherwise. + +static bool correctDVI(const QString& filename) +{ + QFile f(filename); + if (!f.open(IO_ReadOnly)) + return FALSE; + + unsigned char test[4]; + if ( f.readBlock( (char *)test,2)<2 || test[0] != 247 || test[1] != 2 ) + return FALSE; + + int n = f.size(); + if ( n < 134 ) // Too short for a dvi file + return FALSE; + f.at( n-4 ); + + unsigned char trailer[4] = { 0xdf,0xdf,0xdf,0xdf }; + + if ( f.readBlock( (char *)test, 4 )<4 || strncmp( (char *)test, (char*) trailer, 4 ) ) + return FALSE; + // We suppose now that the dvi file is complete and OK + return TRUE; +} + +bool GSCreator::getEPSIPreview(const QString &path, long start, long + end, QImage &outimg, int imgwidth, int imgheight) +{ + FILE *fp; + fp = fopen(QFile::encodeName(path), "r"); + if (fp == 0) return false; + + const long previewsize = end - start + 1; + + char *buf = (char *) malloc(previewsize); + fseek(fp, start, SEEK_SET); + int count = fread(buf, sizeof(char), previewsize - 1, fp); + fclose(fp); + buf[previewsize - 1] = 0; + if (count != previewsize - 1) + { + free(buf); + return false; + } + + QString previewstr = QString::fromLatin1(buf); + free(buf); + + int offset = 0; + while ((offset < previewsize) && !(previewstr[offset].isDigit())) offset++; + int digits = 0; + while ((offset + digits < previewsize) && previewstr[offset + digits].isDigit()) digits++; + int width = previewstr.mid(offset, digits).toInt(); + offset += digits + 1; + while ((offset < previewsize) && !(previewstr[offset].isDigit())) offset++; + digits = 0; + while ((offset + digits < previewsize) && previewstr[offset + digits].isDigit()) digits++; + int height = previewstr.mid(offset, digits).toInt(); + offset += digits + 1; + while ((offset < previewsize) && !(previewstr[offset].isDigit())) offset++; + digits = 0; + while ((offset + digits < previewsize) && previewstr[offset + digits].isDigit()) digits++; + int depth = previewstr.mid(offset, digits).toInt(); + + // skip over the rest of the BeginPreview comment + while ((offset < previewsize) && + previewstr[offset] != '\n' && + previewstr[offset] != '\r') offset++; + while ((offset < previewsize) && previewstr[offset] != '%') offset++; + + unsigned int imagedepth; + switch (depth) { + case 1: + case 2: + case 4: + case 8: + imagedepth = 8; + break; + case 12: // valid, but not (yet) supported + default: // illegal value + return false; + } + + unsigned int colors = (1U << depth); + QImage img(width, height, imagedepth, colors); + img.setAlphaBuffer(false); + + if (imagedepth <= 8) { + for (unsigned int gray = 0; gray < colors; gray++) { + unsigned int grayvalue = (255U * (colors - 1 - gray)) / + (colors - 1); + img.setColor(gray, qRgb(grayvalue, grayvalue, grayvalue)); + } + } + + const unsigned int bits_per_scan_line = width * depth; + unsigned int bytes_per_scan_line = bits_per_scan_line / 8; + if (bits_per_scan_line % 8) bytes_per_scan_line++; + const unsigned int bindatabytes = height * bytes_per_scan_line; + QMemArray<unsigned char> bindata(bindatabytes); + + for (unsigned int i = 0; i < bindatabytes; i++) { + if (offset >= previewsize) + return false; + + while (!isxdigit(previewstr[offset].latin1()) && + offset < previewsize) + offset++; + + bool ok = false; + bindata[i] = static_cast<unsigned char>(previewstr.mid(offset, 2).toUInt(&ok, 16)); + if (!ok) + return false; + + offset += 2; + } + + for (int scanline = 0; scanline < height; scanline++) { + unsigned char *scanlineptr = img.scanLine(scanline); + + for (int pixelindex = 0; pixelindex < width; pixelindex++) { + unsigned char pixelvalue = 0; + const unsigned int bitoffset = + scanline * bytes_per_scan_line * 8U + pixelindex * depth; + for (int depthindex = 0; depthindex < depth; + depthindex++) { + const unsigned int byteindex = (bitoffset + depthindex) / 8U; + const unsigned int bitindex = + 7 - ((bitoffset + depthindex) % 8U); + const unsigned char bitvalue = + (bindata[byteindex] & static_cast<unsigned char>(1U << bitindex)) >> bitindex; + pixelvalue |= (bitvalue << depthindex); + } + scanlineptr[pixelindex] = pixelvalue; + } + } + + outimg = img.convertDepth(32).smoothScale(imgwidth, imgheight); + + return true; +} |