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.
tdegraphics/kghostview/kgvdocument.cpp

865 lines
22 KiB

/**
* Copyright (C) 1997-2003 the KGhostView authors. See file AUTHORS.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <algorithm>
#include <memory>
#include <tqfileinfo.h>
#include <kconfig.h>
#include <kfiledialog.h>
#include <kfilterdev.h>
#include <kinstance.h>
#include <kmessagebox.h>
#include <kmimetype.h>
#include <kprinter.h>
#include <kprocess.h>
#include <ktempfile.h>
#include <kio/netaccess.h>
#include <klocale.h>
#include <kdebug.h>
#include "configuration.h"
#include "kdscerrordialog.h"
#include "kgv_miniwidget.h"
#include "marklist.h"
#include "kgvfactory.h"
extern "C" {
#include "ps.h"
}
#include "kgvdocument.h"
using namespace std;
using namespace KGV;
KGVDocument::KGVDocument( KGVPart* part, const char* name ) :
TQObject( part, name ),
_psFile( 0 ),
_part( part ),
_tmpUnzipped( 0 ),
_tmpFromPDF( 0 ),
_tmpDSC( 0 ),
_isFileOpen( false ),
_dsc( 0 )
{
readSettings();
_pdf2dsc = new Pdf2dsc( _interpreterPath, this );
connect( _pdf2dsc, TQT_SIGNAL( finished( bool ) ),
TQT_SLOT( openPDFFileContinue( bool ) ) );
}
KGVDocument::~KGVDocument()
{
close();
}
void KGVDocument::readSettings()
{
_interpreterPath = Configuration::interpreter();
}
/*- OPENING and READING ---------------------------------------------------*/
void KGVDocument::openFile( const TQString& name, const TQString& mimetype )
{
kdDebug(4500) << "KGVDocument::openFile" << endl;
close();
_fileName = name;
_mimetype = mimetype;
TQTimer::singleShot( 0, this, TQT_SLOT( doOpenFile() ) );
}
void KGVDocument::doOpenFile()
{
TQFileInfo fileInfo( _fileName );
if( !fileInfo.exists() )
{
KMessageBox::sorry( _part->widget(),
i18n( "<qt>Could not open <nobr><strong>%1</strong></nobr>: "
"File does not exist.</qt>" )
.arg( _fileName ) );
emit canceled( TQString() );
return;
}
if( !fileInfo.isReadable() )
{
KMessageBox::sorry( _part->widget(),
i18n( "<qt>Could not open <nobr><strong>%1</strong></nobr>: "
"Permission denied.</qt>" )
.arg( _fileName ) );
emit canceled( TQString() );
return;
}
if( uncompressFile() )
{
kdDebug( 4500 ) << "FILENAME: " << _fileName << endl;
KMimeType::Ptr mimetype = KMimeType::findByPath( _fileName );
kdDebug(4500) << "KGVDocument::mimetype: " << mimetype->name()
<< endl;
_mimetype = mimetype->name();
}
// If the file contains a PDF document, create a DSC description file
// of the PDF document. This can be passed to Ghostscript just like
// an ordinary PS file.
if( _mimetype == "application/pdf"
|| _mimetype == "application/x-pdf" ) // see bug:67474
{
_tmpDSC = new KTempFile( TQString::null, ".ps" );
Q_CHECK_PTR( _tmpDSC );
if( _tmpDSC->status() != 0 ) {
KMessageBox::error( _part->widget(),
i18n( "Could not create temporary file: %1" )
.arg( strerror( _tmpDSC->status() ) ) );
emit canceled( TQString() );
return;
}
// When pdf2dsc has finished the program will continue with
// openPDFFileContinue.
_pdf2dsc->run( _fileName, _tmpDSC->name() );
return;
}
else if( _mimetype == "application/postscript"
|| _mimetype == "application/x-postscript" // see bug:71546
|| _mimetype == "application/illustrator"
|| _mimetype == "image/x-eps"
|| _mimetype == "text/plain" )
{
_format = PS;
openPSFile();
return;
}
else
{
KMessageBox::sorry( _part->widget(),
i18n( "<qt>Could not open <nobr><strong>%1</strong></nobr> "
"which has type <strong>%2</strong>. KGhostview can "
"only load PostScript (.ps, .eps) and Portable "
"Document Format (.pdf) files.</qt>" )
.arg( _fileName )
.arg( _mimetype ) );
emit canceled( TQString() );
return;
}
}
bool KGVDocument::uncompressFile()
{
// If the file is gzipped, gunzip it to the temporary file _tmpUnzipped.
kdDebug(4500) << "KGVDocument::uncompressFile()" << endl;
auto_ptr<TQIODevice> filterDev( KFilterDev::deviceForFile( _fileName, _mimetype, true ) );
if ( !filterDev.get() ) {
KMimeType::Ptr mt = KMimeType::mimeType(_mimetype);
if ( (_fileName.right( 3 ) == ".gz") || mt->is("application/x-gzip") ) {
kdDebug(4500) << "KGVDocument::uncompressFile(): manually guessing gzip" << endl;
filterDev.reset( KFilterDev::deviceForFile( _fileName, "application/x-gzip", true ) );
} else if ( (_fileName.right( 4 ) == ".bz2") || mt->is("application/x-bzip2") ) {
kdDebug(4500) << "KGVDocument::uncompressFile(): manually guessing bzip2" << endl;
filterDev.reset( KFilterDev::deviceForFile( _fileName, "application/x-bzip2", true ) );
} else {
kdDebug( 4500 ) << "KGVDocument::uncompressFile(): Unable to guess " << _fileName << endl;
}
if ( !filterDev.get() )
return false;
}
if( !filterDev->open( IO_ReadOnly ) )
{
KMessageBox::error( _part->widget(),
i18n( "<qt>Could not uncompress <nobr><strong>%1</strong></nobr>.</qt>" )
.arg( _fileName ) );
emit canceled( TQString() );
return false;
}
_tmpUnzipped = new KTempFile;
Q_CHECK_PTR( _tmpUnzipped );
if( _tmpUnzipped->status() != 0 )
{
KMessageBox::error( _part->widget(),
i18n( "Could not create temporary file: %2" )
.arg( strerror( _tmpUnzipped->status() ) ) );
emit canceled( TQString() );
return false;
}
TQByteArray buf( 8192 );
int read = 0, wrtn = 0;
while( ( read = filterDev->readBlock( buf.data(), buf.size() ) )
> 0 )
{
wrtn = _tmpUnzipped->file()->writeBlock( buf.data(), read );
if( read != wrtn )
break;
}
if( read != 0 )
{
KMessageBox::error( _part->widget(),
i18n( "<qt>Could not uncompress <nobr><strong>%1</strong></nobr>.</qt>" )
.arg( _fileName ) );
emit canceled( TQString() );
return false;
}
_tmpUnzipped->close();
_fileName = _tmpUnzipped->name();
return true;
}
void KGVDocument::openPDFFileContinue( bool pdf2dscResult )
{
kdDebug(4500) << "KGVDocument::openPDFFileContinue" << endl;
if( !pdf2dscResult )
{
KMessageBox::error( _part->widget(),
i18n( "<qt>Could not open file <nobr><strong>%1</strong></nobr>.</qt>" )
.arg( _part->url().url() ) );
emit canceled( TQString() );
return;
}
_tmpDSC->close();
_format = PDF;
openPSFile(_tmpDSC->name());
}
void KGVDocument::openPSFile(const TQString &file)
{
TQString fileName = file.isEmpty() ? _fileName : file;
kdDebug(4500) << "KGVDocument::openPSFile (" << fileName << ")" << endl;
FILE* fp = fopen( TQFile::encodeName( fileName ), "r");
if( fp == 0 )
{
KMessageBox::error( _part->widget(),
i18n( "<qt>Error opening file <nobr><strong>%1</strong></nobr>: %2</qt>" )
.arg( _part->url().url() )
.arg( strerror( errno ) ) );
emit canceled( "" );
return;
}
else
{
_psFile = fp;
_isFileOpen = true;
scanDSC();
emit completed();
}
}
void KGVDocument::fileChanged( const TQString& name )
{
kdDebug(4500) << "KGVDocument: fileChanged " << name << endl;
if( !_psFile )
return;
// unsigned int savepage = _currentPage;
/*
if( openFile( name ) )
goToPage( savepage );
else
emit fileChangeFailed();
*/
}
void KGVDocument::scanDSC()
{
_dsc = new KDSC();
// Disable errorDialog for now while KDSCErrorDialog isn't fully
// implemented
// KDSCErrorDialog errorDialog;
// KDSCErrorThreshold errorHandler( 3, &errorDialog );
// _dsc->setErrorHandler( &errorHandler );
char buf[4096];
int count;
/*
TQTime clock;
clock.start();
*/
while( ( count = fread( buf, sizeof(char), sizeof( buf ), _psFile ) ) != 0 )
{
_dsc->scanData( buf, count );
/*
if( clock.elapsed() > 10 )
{
kapp->processEvents();
clock.start();
}
*/
}
_dsc->fixup();
// _dsc->setErrorHandler( 0 );
}
void KGVDocument::close()
{
_pdf2dsc->kill();
_isFileOpen = false;
delete _dsc;
_dsc = 0;
if( _psFile )
{
fclose( _psFile );
_psFile = 0;
}
clearTemporaryFiles();
}
bool KGVDocument::convertFromPDF( const TQString& saveFileName,
unsigned int firstPage,
unsigned int lastPage )
{
// TODO -- timeout/fail on this conversion (it can hang on a bad pdf)
// TODO -- use output from gs (leave out -q) to drive a progress bar
KProcess process;
process << _interpreterPath
<< "-q"
<< "-dNOPAUSE"
<< "-dBATCH"
<< "-dSAFER"
<< "-dPARANOIDSAFER"
<< "-sDEVICE=pswrite"
<< ( TQCString("-sOutputFile=")+TQFile::encodeName(saveFileName) )
<< ( TQString("-dFirstPage=")+TQString::number( firstPage ) )
<< ( TQString("-dLastPage=")+TQString::number( lastPage ) )
<< "-c"
<< "save"
<< "pop"
<< "-f"
<< TQFile::encodeName(_fileName);
/*TQValueList<TQCString> args = process.args();
TQValueList<TQCString>::Iterator it = args.begin();
for ( ; it != args.end() ; ++it )
kdDebug(4500) << ( *it ) << endl;*/
if( !process.start( KProcess::Block ) )
{
kdError() << "convertFromPDF: Couldn't start gs process" << endl;
// TODO -- error message (gs not found?)
return false;
}
if ( !process.normalExit() || process.exitStatus() != 0 )
{
kdError() << "convertFromPDF: normalExit=" << process.normalExit() << " exitStatus=" << process.exitStatus() << endl;
// TODO -- error message (can't open, strerr())
return false;
}
return true;
}
void KGVDocument::clearTemporaryFiles()
{
if( _tmpUnzipped ) {
_tmpUnzipped->setAutoDelete( true );
delete _tmpUnzipped;
_tmpUnzipped = 0;
}
if( _tmpFromPDF ) {
_tmpFromPDF->setAutoDelete( true );
delete _tmpFromPDF;
_tmpFromPDF = 0;
}
if( _tmpDSC ) {
_tmpDSC->setAutoDelete( true );
delete _tmpDSC;
_tmpDSC = 0;
}
}
/*- DOCUMENT --------------------------------------------------------------*/
TQStringList KGVDocument::mediaNames() const
{
TQStringList names;
const CDSCMEDIA* m = dsc_known_media;
while( m->name ) {
names << m->name;
m++;
}
if( isOpen() && dsc()->media() ) {
for( unsigned int i = 0; i < dsc()->media_count(); i++ ) {
if( dsc()->media()[i] && dsc()->media()[i]->name )
names << dsc()->media()[i]->name;
}
}
return names;
}
const CDSCMEDIA* KGVDocument::findMediaByName( const TQString& mediaName ) const
{
if( !isOpen() )
return 0;
if( dsc()->media() ) {
for( unsigned int i = 0; i < dsc()->media_count(); i++ ) {
if( dsc()->media()[i] && dsc()->media()[i]->name
&& qstricmp( mediaName.local8Bit(),
dsc()->media()[i]->name ) == 0 ) {
return dsc()->media()[i];
}
}
}
/* It didn't match %%DocumentPaperSizes: */
/* Try our known media */
const CDSCMEDIA *m = dsc_known_media;
while( m->name ) {
if( qstricmp( mediaName.local8Bit(), m->name ) == 0 ) {
return m;
}
m++;
}
return 0;
}
TQSize KGVDocument::computePageSize( const TQString& mediaName ) const
{
kdDebug(4500) << "KGVDocument::computePageSize( " << mediaName << " )" << endl;
if( mediaName == "BoundingBox" ) {
if( dsc()->bbox().get() != 0 )
return dsc()->bbox()->size();
else
return TQSize( 0, 0 );
}
const CDSCMEDIA* m = findMediaByName( mediaName );
Q_ASSERT( m );
return TQSize( static_cast<int>( m->width ), static_cast<int>( m->height ) );
}
/*- PRINTING and SAVING ---------------------------------------------------*/
TQString KGVDocument::pageListToRange( const PageList& pageList )
{
TQString range;
// Iterators marking the begin and end of a successive sequence
// of pages.
PageList::const_iterator bss( pageList.begin() );
PageList::const_iterator ess;
PageList::const_iterator it ( pageList.begin() );
while( it != pageList.end() )
{
ess = it++;
// If ess points to the end of a successive sequence of pages,
// add the stringrepresentation of the sequence to range and
// update bss.
if( it == pageList.end() || *it != (*ess) + 1 )
{
if( !range.isEmpty() )
range += ",";
if( bss == ess )
range += TQString::number( *ess );
else
range += TQString( "%1-%2" ).arg( *bss ).arg( *ess );
bss = it;
}
}
return range;
}
void KGVDocument::print()
{
if( !dsc() ) return;
KPrinter printer;
if( dsc()->isStructured() )
{
printer.setPageSelection( KPrinter::ApplicationSide );
printer.setCurrentPage( _part->miniWidget()->displayOptions().page() + 1 );
printer.setMinMax( 1, dsc()->page_count() );
printer.setOption( "kde-range",
pageListToRange( _part->markList()->markList() ) );
if( printer.setup( _part->widget(), i18n("Print %1").arg(_part->url().fileName()) ) )
{
KTempFile tf( TQString::null, ".ps" );
if( tf.status() == 0 )
{
if ( printer.pageList().empty() ) {
KMessageBox::sorry( 0,
i18n( "Printing failed because the list of "
"pages to be printed was empty." ),
i18n( "Error Printing" ) );
} else if ( savePages( tf.name(), printer.pageList() ) ) {
printer.printFiles( TQStringList( tf.name() ), true );
} else {
KMessageBox::error( 0, i18n( "<qt><strong>Printing failure:</strong><br>Could not convert to PostScript</qt>" ) );
}
}
else
{
// TODO: Proper error handling
;
}
}
}
else
{
printer.setPageSelection( KPrinter::SystemSide );
if( printer.setup( _part->widget(), i18n("Print %1").arg(_part->url().fileName()) ) )
printer.printFiles( _fileName );
}
}
void KGVDocument::saveAs()
{
if( !isOpen() )
return;
KURL saveURL = KFileDialog::getSaveURL(
_part->url().isLocalFile()
? _part->url().url()
: _part->url().fileName(),
TQString::null,
_part->widget(),
TQString::null );
if( !KIO::NetAccess::upload( _fileName,
saveURL,
static_cast<TQWidget*>( 0 ) ) ) {
// TODO: Proper error dialog
}
}
bool KGVDocument::savePages( const TQString& saveFileName,
const PageList& pageList )
{
if( pageList.empty() )
return true;
if( _format == PDF )
{
KTempFile psSaveFile( TQString::null, ".ps" );
psSaveFile.setAutoDelete( true );
if( psSaveFile.status() != 0 )
return false;
// Find the minimum and maximum pagenumber in pageList.
int minPage = pageList.first(), maxPage = pageList.first();
for( PageList::const_iterator ci = pageList.begin();
ci != pageList.end(); ++ci )
{
minPage = QMIN( *ci, minPage );
maxPage = QMAX( *ci, maxPage );
}
// TODO: Optimize "print whole document" case
//
// The convertion below from PDF to PS creates a temporary file which, in
// the case where we are printing the whole document will then be
// completelly copied to another temporary file.
//
// In very large files, the inefficiency is visible (measured in
// seconds).
//
// luis_pedro 4 Jun 2003
// Convert the pages in the range [minPage,maxPage] from PDF to
// PostScript.
if( !convertFromPDF( psSaveFile.name(),
minPage, maxPage ) )
return false;
// The page minPage in the original file becomes page 1 in the
// converted file. We still have to select the desired pages from
// this file, so we need to take into account that difference.
PageList normedPageList;
transform( pageList.begin(), pageList.end(),
back_inserter( normedPageList ),
bind2nd( minus<int>(), minPage - 1 ) );
// Finally select the desired pages from the converted file.
psCopyDoc( psSaveFile.name(), saveFileName, normedPageList );
}
else
{
psCopyDoc( _fileName, saveFileName, pageList );
}
return true;
}
// length calculates string length at compile time
// can only be used with character constants
#define length( a ) ( sizeof( a ) - 1 )
// Copy the headers, marked pages, and trailer to fp
bool KGVDocument::psCopyDoc( const TQString& inputFile,
const TQString& outputFile, const PageList& pageList )
{
FILE* from;
FILE* to;
char text[ PSLINELENGTH ];
char* comment;
bool pages_written = false;
bool pages_atend = false;
unsigned int i = 0;
unsigned int pages = 0;
long here;
kdDebug(4500) << "KGVDocument: Copying pages from " << inputFile << " to "
<< outputFile << endl;
pages = pageList.size();
if( pages == 0 ) {
KMessageBox::sorry( 0,
i18n( "Printing failed because the list of "
"pages to be printed was empty." ),
i18n( "Error Printing" ) );
return false;
}
from = fopen( TQFile::encodeName( inputFile ), "r" );
to = fopen( TQFile::encodeName( outputFile ), "w" );
// Hack in order to make printing of PDF files work. FIXME
CDSC* dsc;
if( _format == PS )
dsc = _dsc->cdsc();
else {
FILE* fp = fopen( TQFile::encodeName( inputFile ), "r");
char buf[256];
int count;
dsc = dsc_init( 0 );
while( ( count = fread( buf, 1, sizeof( buf ), fp ) ) != 0 )
dsc_scan_data( dsc, buf, count );
fclose( fp );
if( !dsc )
return false;
dsc_fixup( dsc );
}
here = dsc->begincomments;
while( ( comment = pscopyuntil( from, to, here,
dsc->endcomments, "%%Pages:" ) ) ) {
here = ftell( from );
if( pages_written || pages_atend ) {
free( comment );
continue;
}
sscanf( comment + length("%%Pages:" ), "%256s", text );
text[256] = 0; // Just in case of an overflow
if( strcmp( text, "(atend)" ) == 0 ) {
fputs( comment, to );
pages_atend = true;
}
else {
switch ( sscanf( comment + length( "%%Pages:" ), "%*d %u", &i ) ) {
case 1:
fprintf( to, "%%%%Pages: %d %d\n", pages, i );
break;
default:
fprintf( to, "%%%%Pages: %d\n", pages );
break;
}
pages_written = true;
}
free(comment);
}
pscopy( from, to, dsc->beginpreview, dsc->endpreview );
pscopy( from, to, dsc->begindefaults, dsc->enddefaults );
pscopy( from, to, dsc->beginprolog, dsc->endprolog );
pscopy( from, to, dsc->beginsetup, dsc->endsetup );
//TODO -- Check that a all dsc attributes are copied
unsigned int count = 1;
PageList::const_iterator it;
for( it = pageList.begin(); it != pageList.end(); ++it ) {
i = (*it) - 1;
comment = pscopyuntil( from, to, dsc->page[i].begin,
dsc->page[i].end, "%%Page:" );
if ( comment ) free( comment );
fprintf( to, "%%%%Page: %s %d\n", dsc->page[i].label,
count++ );
pscopy( from, to, -1, dsc->page[i].end );
}
here = dsc->begintrailer;
while( ( comment = pscopyuntil( from, to, here,
dsc->endtrailer, "%%Pages:" ) ) ) {
here = ftell( from );
if ( pages_written ) {
free( comment );
continue;
}
switch ( sscanf( comment + length( "%%Pages:" ), "%*d %u", &i ) ) {
case 1:
fprintf( to, "%%%%Pages: %d %d\n", pages, i );
break;
default:
fprintf( to, "%%%%Pages: %d\n", pages );
break;
}
pages_written = true;
free( comment );
}
fclose( from );
fclose( to );
if( _format == PDF )
dsc_free( dsc );
return true;
}
#undef length
/*- Conversion stuff ------------------------------------------------------*/
/*
void KGVDocument::runPdf2ps( const TQString& pdfName,
const TQString& dscName )
{
KProcess process;
process << _interpreterPath
<< "-dNODISPLAY"
<< "-dQUIET"
<< TQString( "-sPDFname=%1" ).arg( pdfName )
<< TQString( "-sDSCnamale locale( "kghostview" );
_fallBackPageMedia = pageSizeToString(
static_cast< TQPrinter::PageSize >( locale.pageSize() ) );
_usePageLabels = false;
e=%1" ).arg( dscName )
<< "pdf2dsc.ps"
<< "-c"
<< "quit";
connect( &process, TQT_SIGNAL( processExited( KProcess* ) ),
this, TQT_SLOT( pdf2psExited( KProcess* ) ) );
kdDebug(4500) << "KGVDocument: pdf2ps started" << endl;
process.start( KProcess::NotifyOnExit );
}
void KGVDocument::pdf2psExited( KProcess* process )
{
kdDebug(4500) << "KGVDocument: pdf2ps exited" << endl;
emit pdf2psFinished( process.normalExit() && process.exitStatus() != 0 );
}
*/
Pdf2dsc::Pdf2dsc( const TQString& ghostscriptPath, TQObject* parent, const char* name ) :
TQObject( parent, name ),
_process( 0 ),
_ghostscriptPath( ghostscriptPath )
{}
Pdf2dsc::~Pdf2dsc()
{
kill();
}
void Pdf2dsc::run( const TQString& pdfName, const TQString& dscName )
{
kill();
_process = new KProcess;
*_process << _ghostscriptPath
<< "-dSAFER"
<< "-dPARANOIDSAFER"
<< "-dDELAYSAFER"
<< "-dNODISPLAY"
<< "-dQUIET"
<< TQString( "-sPDFname=%1" ).arg( pdfName )
<< TQString( "-sDSCname=%1" ).arg( dscName )
<< "-c"
<< "<< /PermitFileReading [ PDFname ] /PermitFileWriting [ DSCname ] /PermitFileControl [] >> setuserparams .locksafe"
<< "-f"
<< "pdf2dsc.ps"
<< "-c"
<< "quit";
connect( _process, TQT_SIGNAL( processExited( KProcess* ) ),
this, TQT_SLOT( processExited() ) );
kdDebug(4500) << "Pdf2dsc: started" << endl;
_process->start( KProcess::NotifyOnExit );
}
void Pdf2dsc::kill()
{
if( _process != 0 )
{
kdDebug(4500) << "Pdf2dsc: killing current process" << endl;
delete _process;
_process = 0;
}
}
void Pdf2dsc::processExited()
{
kdDebug(4500) << "Pdf2dsc: process exited" << endl;
emit finished( _process->normalExit() && _process->exitStatus() == 0 );
delete _process;
_process = 0;
}
#include "kgvdocument.moc"
// vim:sw=4:sts=4:ts=8:sta:tw=78:noet