You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
620 lines
16 KiB
620 lines
16 KiB
/* This file is part of the KDE libraries
|
|
Copyright (C) 2001 Malte Starostik <malte@kde.org>
|
|
|
|
Handling of EPS previews Copyright (C) 2003 Philipp Hullmann <phull@gmx.de>
|
|
|
|
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 TQImage
|
|
|
|
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 TQImage
|
|
*/
|
|
|
|
#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 <tqcolor.h>
|
|
#include <tqfile.h>
|
|
#include <tqimage.h>
|
|
#include <tqregexp.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 TQString& filename);
|
|
|
|
|
|
namespace {
|
|
bool got_sig_term = false;
|
|
void handle_sigterm( int ) {
|
|
got_sig_term = true;
|
|
}
|
|
}
|
|
|
|
|
|
bool GSCreator::create(const TQString &path, int width, int height, TQImage &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];
|
|
|
|
TQByteArray 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(TQFile::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(TQRegExp("\\.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::unique_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 TQImage 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
|
|
TQCString fname = TQFile::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 TQString& filename)
|
|
{
|
|
TQFile 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 TQString &path, long start, long
|
|
end, TQImage &outimg, int imgwidth, int imgheight)
|
|
{
|
|
FILE *fp;
|
|
fp = fopen(TQFile::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;
|
|
}
|
|
|
|
TQString previewstr = TQString::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);
|
|
TQImage 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, tqRgb(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;
|
|
TQMemArray<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;
|
|
}
|