/* This file is part of the KDE project
Copyright ( c ) 2001 Simon Hausmann < hausmann @ kde . org >
Copyright ( C ) 2002 , 2003 , 2004 Nicolas GOUTTE < goutte @ kde . org >
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 .
*/
# include <unistd.h>
# include <stdio.h>
# include <tqbuffer.h>
# include <tqpainter.h>
# include <tqpaintdevicemetrics.h>
# include <tqfile.h>
# include <tqtextstream.h>
# include <tqregexp.h>
# include <tqimage.h>
# include <tqpixmap.h>
# include <tqapplication.h>
# include <tqdragobject.h>
# include <kglobal.h>
# include <kdebug.h>
# include <kdeversion.h>
# if ! KDE_IS_VERSION( 3,1,90 )
# include <kdebugclasses.h>
# endif
# include <ktempfile.h>
# include <kprocess.h>
# include "KoPictureKey.h"
# include "KoPictureBase.h"
# include "KoPictureEps.h"
KoPictureEps : : KoPictureEps ( void ) : m_psStreamStart ( 0 ) , m_psStreamLength ( 0 ) , m_cacheIsInFastMode ( true )
{
// Forbid TQPixmap to cache the X-Window resources (Yes, it is slower!)
m_cachedPixmap . setOptimization ( TQPixmap : : MemoryOptim ) ;
}
KoPictureEps : : ~ KoPictureEps ( void )
{
}
KoPictureBase * KoPictureEps : : newCopy ( void ) const
{
return new KoPictureEps ( * this ) ;
}
KoPictureType : : Type KoPictureEps : : getType ( void ) const
{
return KoPictureType : : TypeEps ;
}
bool KoPictureEps : : isNull ( void ) const
{
return m_rawData . isNull ( ) ;
}
TQImage KoPictureEps : : scaleWithGhostScript ( const TQSize & size , const int resolutionx , const int resolutiony )
{
if ( ! m_boundingBox . width ( ) | | ! m_boundingBox . height ( ) )
{
kdDebug ( 30003 ) < < " EPS image has a null size! (in KoPictureEps::scaleWithGhostScript) " < < endl ;
return TQImage ( ) ;
}
// ### TODO: do not call GhostScript up to three times for each re-scaling (one call of GhostScript should be enough to know which device is available: gs --help)
// png16m is better, but not always available -> fallback to bmp16m, then fallback to ppm (256 colors)
// ### TODO: pcx24b is also a true colour format
// ### TODO: support alpha (other gs devices needed)
const char * deviceTable [ ] = { " png16m " , " bmp16m " , " ppm " , 0 } ;
TQImage img ;
for ( int i = 0 ; deviceTable [ i ] ; + + i )
{
if ( tryScaleWithGhostScript ( img , size , resolutionx , resolutiony , deviceTable [ i ] ) ! = - 1 )
{
return img ;
}
}
kdError ( 30003 ) < < " Image from GhostScript cannot be loaded (in KoPictureEps::scaleWithGhostScript) " < < endl ;
return img ;
}
// Helper method for scaleWithGhostScript. Returns 1 on success, 0 on error, -1 if nothing generated
// (in which case another 'output device' can be tried)
int KoPictureEps : : tryScaleWithGhostScript ( TQImage & image , const TQSize & size , const int resolutionx , const int resolutiony , const char * device )
// Based on the code of the file kdelibs/kimgio/eps.cpp
{
kdDebug ( 30003 ) < < " Sampling with GhostScript, using device \" " < < device < < " \" (in KoPictureEps::tryScaleWithGhostScript) " < < endl ;
KTempFile tmpFile ;
tmpFile . setAutoDelete ( true ) ;
if ( tmpFile . status ( ) )
{
kdError ( 30003 ) < < " No KTempFile! (in KoPictureEps::tryScaleWithGhostScript) " < < endl ;
return 0 ; // error
}
const int wantedWidth = size . width ( ) ;
const int wantedHeight = size . height ( ) ;
const double xScale = double ( size . width ( ) ) / double ( m_boundingBox . width ( ) ) ;
const double yScale = double ( size . height ( ) ) / double ( m_boundingBox . height ( ) ) ;
// create GS command line
TQString cmdBuf ( " gs -sOutputFile= " ) ;
cmdBuf + = KProcess : : quote ( tmpFile . name ( ) ) ;
cmdBuf + = " -q -g " ;
cmdBuf + = TQString : : number ( wantedWidth ) ;
cmdBuf + = " x " ;
cmdBuf + = TQString : : number ( wantedHeight ) ;
if ( ( resolutionx > 0 ) & & ( resolutiony > 0 ) )
{
#if 0
// Do not play with resolution for now.
// It brings more problems at print than solutions
cmdBuf + = " -r " ;
cmdBuf + = TQString : : number ( resolutionx ) ;
cmdBuf + = " x " ;
cmdBuf + = TQString : : number ( resolutiony ) ;
# endif
}
cmdBuf + = " -dSAFER -dPARANOIDSAFER -dNOPAUSE -sDEVICE= " ;
cmdBuf + = device ;
//cmdBuf += " -c 255 255 255 setrgbcolor fill 0 0 0 setrgbcolor";
cmdBuf + = " - " ;
cmdBuf + = " -c showpage quit " ;
// run ghostview
FILE * ghostfd = popen ( TQFile : : encodeName ( cmdBuf ) , " w " ) ;
if ( ghostfd = = 0 )
{
kdError ( 30003 ) < < " No connection to GhostScript (in KoPictureEps::tryScaleWithGhostScript) " < < endl ;
return 0 ; // error
}
// The translation is needed as GhostScript (7.07) cannot handle negative values in the bounding box otherwise.
fprintf ( ghostfd , " \n %d %d translate \n " , - tqRound ( m_boundingBox . left ( ) * xScale ) , - tqRound ( m_boundingBox . top ( ) * yScale ) ) ;
fprintf ( ghostfd , " %g %g scale \n " , xScale , yScale ) ;
// write image to gs
fwrite ( m_rawData . data ( ) + m_psStreamStart , sizeof ( char ) , m_psStreamLength , ghostfd ) ;
pclose ( ghostfd ) ;
// load image
if ( ! image . load ( tmpFile . name ( ) ) )
{
// It failed - maybe the device isn't supported by gs
return - 1 ;
}
if ( image . size ( ) ! = size ) // this can happen due to rounding problems
{
//kdDebug(30003) << "fixing size to " << size.width() << "x" << size.height()
// << " (was " << image.width() << "x" << image.height() << ")" << endl;
image = image . scale ( size ) ; // hmm, smoothScale instead?
}
kdDebug ( 30003 ) < < " Image parameters: " < < image . width ( ) < < " x " < < image . height ( ) < < " x " < < image . depth ( ) < < endl ;
return 1 ; // success
}
void KoPictureEps : : scaleAndCreatePixmap ( const TQSize & size , bool fastMode , const int resolutionx , const int resolutiony )
{
kdDebug ( 30003 ) < < " KoPictureEps::scaleAndCreatePixmap " < < size < < " " < < ( fastMode ? TQString ( " fast " ) : TQString ( " slow " ) )
< < " resolutionx: " < < resolutionx < < " resolutiony: " < < resolutiony < < endl ;
if ( ( size = = m_cachedSize )
& & ( ( fastMode ) | | ( ! m_cacheIsInFastMode ) ) )
{
// The cached pixmap has already the right size
// and:
// - we are in fast mode (We do not care if the re-size was done slowly previously)
// - the re-size was already done in slow mode
kdDebug ( 30003 ) < < " Already cached! " < < endl ;
return ;
}
// Slow mode can be very slow, especially at high zoom levels -> configurable
if ( ! isSlowResizeModeAllowed ( ) )
{
kdDebug ( 30003 ) < < " User has disallowed slow mode! " < < endl ;
fastMode = true ;
}
// We cannot use fast mode, if nothing was ever cached.
if ( fastMode & & ! m_cachedSize . isEmpty ( ) )
{
kdDebug ( 30003 ) < < " Fast scaling! " < < endl ;
// Slower than caching a TQImage, but faster than re-sampling!
TQImage image ( m_cachedPixmap . convertToImage ( ) ) ;
m_cachedPixmap = image . scale ( size ) ;
m_cacheIsInFastMode = true ;
m_cachedSize = size ;
}
else
{
TQTime time ;
time . start ( ) ;
TQApplication : : setOverrideCursor ( TQt : : waitCursor ) ;
m_cachedPixmap = scaleWithGhostScript ( size , resolutionx , resolutiony ) ;
TQApplication : : restoreOverrideCursor ( ) ;
m_cacheIsInFastMode = false ;
m_cachedSize = size ;
kdDebug ( 30003 ) < < " Time: " < < ( time . elapsed ( ) / 1000.0 ) < < " s " < < endl ;
}
kdDebug ( 30003 ) < < " New size: " < < size < < endl ;
}
void KoPictureEps : : draw ( TQPainter & painter , int x , int y , int width , int height , int sx , int sy , int sw , int sh , bool fastMode )
{
if ( ! width | | ! height )
return ;
TQSize screenSize ( width , height ) ;
//kdDebug() << "KoPictureEps::draw screenSize=" << screenSize.width() << "x" << screenSize.height() << endl;
TQPaintDeviceMetrics metrics ( painter . device ( ) ) ;
kdDebug ( 30003 ) < < " Metrics: X: " < < metrics . logicalDpiX ( ) < < " x Y: " < < metrics . logicalDpiX ( ) < < " (in KoPictureEps::draw) " < < endl ;
if ( painter . tqdevice ( ) - > isExtDev ( ) ) // Is it an external device (i.e. printer)
{
kdDebug ( 30003 ) < < " Drawing for a printer (in KoPictureEps::draw) " < < endl ;
// For printing, always re-sample the image, as a printer has never the same resolution than a display.
TQImage image ( scaleWithGhostScript ( screenSize , metrics . logicalDpiX ( ) , metrics . logicalDpiY ( ) ) ) ;
// sx,sy,sw,sh is meant to be used as a cliprect on the pixmap, but drawImage
// translates it to the (x,y) point -> we need (x+sx, y+sy).
painter . drawImage ( x + sx , y + sy , image , sx , sy , sw , sh ) ;
}
else // No, it is simply a display
{
scaleAndCreatePixmap ( screenSize , fastMode , metrics . logicalDpiX ( ) , metrics . logicalDpiY ( ) ) ;
// sx,sy,sw,sh is meant to be used as a cliprect on the pixmap, but drawPixmap
// translates it to the (x,y) point -> we need (x+sx, y+sy).
painter . drawPixmap ( x + sx , y + sy , m_cachedPixmap , sx , sy , sw , sh ) ;
}
}
bool KoPictureEps : : extractPostScriptStream ( void )
{
kdDebug ( 30003 ) < < " KoPictureEps::extractPostScriptStream " < < endl ;
TQDataStream data ( m_rawData , IO_ReadOnly ) ;
data . setByteOrder ( TQDataStream : : LittleEndian ) ;
TQ_UINT32 magic , offset , length ;
data > > magic ;
data > > offset ;
data > > length ;
if ( ! length )
{
kdError ( 30003 ) < < " Length of PS stream is zero! " < < endl ;
return false ;
}
if ( offset + length > m_rawData . size ( ) )
{
kdError ( 30003 ) < < " Data stream of the EPSF file is longer than file: " < < offset < < " + " < < length < < " > " < < m_rawData . size ( ) < < endl ;
return false ;
}
m_psStreamStart = offset ;
m_psStreamLength = length ;
return true ;
}
TQString KoPictureEps : : readLine ( const TQByteArray & array , const uint start , const uint length , uint & pos , bool & lastCharWasCr )
{
TQString strLine ;
const uint finish = kMin ( start + length , ( uint ) array . size ( ) ) ;
for ( ; pos < finish ; + + pos ) // We are starting at pos
{
const char ch = array [ pos ] ; // Read one character
if ( ch = = ' \n ' )
{
if ( lastCharWasCr )
{
// We have a line feed following a Carriage Return
// As the Carriage Return has already ended the previous line,
// discard this Line Feed.
lastCharWasCr = false ;
}
else
{
// We have a normal Line Feed, therefore we end the line
break ;
}
}
else if ( ch = = ' \r ' )
{
// We have a Carriage Return, therefore we end the line
lastCharWasCr = true ;
break ;
}
else if ( ch = = char ( 12 ) ) // Form Feed
{ // ### TODO: can a FF happen in PostScript?
// Ignore the form feed
continue ;
}
else
{
strLine + = ch ;
lastCharWasCr = false ;
}
}
return strLine ;
}
bool KoPictureEps : : loadData ( const TQByteArray & array , const TQString & /* extension */ )
{
kdDebug ( 30003 ) < < " KoPictureEps::load " < < endl ;
// First, read the raw data
m_rawData = array ;
if ( m_rawData . isNull ( ) )
{
kdError ( 30003 ) < < " No data was loaded! " < < endl ;
return false ;
}
if ( ( m_rawData [ 0 ] = = char ( 0xc5 ) ) & & ( m_rawData [ 1 ] = = char ( 0xd0 ) )
& & ( m_rawData [ 2 ] = = char ( 0xd3 ) ) & & ( m_rawData [ 3 ] = = char ( 0xc6 ) ) )
{
// We have a so-called "MS-DOS EPS file", we have to extract the PostScript stream
if ( ! extractPostScriptStream ( ) ) // Changes m_rawData
return false ;
}
else
{
m_psStreamStart = 0 ;
m_psStreamLength = m_rawData . size ( ) ;
}
TQString lineBox ; // Line with the bounding box
bool lastWasCr = false ; // Was the last character of the line a carriage return?
uint pos = m_psStreamStart ; // We start to search the bounding box at the start of the PostScript stream
TQString line ( readLine ( m_rawData , m_psStreamStart , m_psStreamLength , pos , lastWasCr ) ) ;
kdDebug ( 30003 ) < < " Header: " < < line < < endl ;
if ( ! line . startsWith ( " %! " ) )
{
kdError ( 30003 ) < < " Not a PostScript file! " < < endl ;
return false ;
}
TQRect rect ;
bool lineIsBoundingBox = false ; // Does "line" has a %%BoundingBox line?
for ( ; ; )
{
+ + pos ; // Get over the previous line end (CR or LF)
line = readLine ( m_rawData , m_psStreamStart , m_psStreamLength , pos , lastWasCr ) ;
kdDebug ( 30003 ) < < " Checking line: " < < line < < endl ;
// ### TODO: it seems that the bounding box can be delayed with "(atend)" in the trailer (GhostScript 7.07 does not support it either.)
if ( line . startsWith ( " %%BoundingBox: " ) )
{
lineIsBoundingBox = true ;
break ;
}
// ### TODO: also abort on %%EndComments
// ### TODO: %? , where ? is non-white-space printable, does not end the comment!
else if ( ! line . startsWith ( " %% " ) )
break ; // Not a EPS comment anymore, so abort as we are not in the EPS header anymore
}
if ( ! lineIsBoundingBox )
{
kdError ( 30003 ) < < " KoPictureEps::load: could not find a bounding box! " < < endl ;
return false ;
}
// Floating point values are not allowed in a Bounding Box, but ther are many such files out there...
TQRegExp exp ( " ( \\ -?[0-9]+ \\ .?[0-9]*) \ \ s ( \ \ - ? [ 0 - 9 ] + \ \ . ? [ 0 - 9 ] * ) \ \ s ( \ \ - ? [ 0 - 9 ] + \ \ . ? [ 0 - 9 ] * ) \ \ s ( \ \ - ? [ 0 - 9 ] + \ \ . ? [ 0 - 9 ] * ) " ) ;
if ( exp . search ( line ) = = - 1 )
{
// ### TODO: it might be an "(atend)" and the bounding box is in the trailer
// (but GhostScript 7.07 does not support a bounding box in the trailer.)
// Note: in Trailer, it is the last BoundingBox that counts not the first!
kdError ( 30003 ) < < " Not standard bounding box: " < < line < < endl ;
return false ;
}
kdDebug ( 30003 ) < < " Reg. Exp. Found: " < < exp . tqcapturedTexts ( ) < < endl ;
rect . setLeft ( ( int ) exp . cap ( 1 ) . toDouble ( ) ) ;
rect . setTop ( ( int ) exp . cap ( 2 ) . toDouble ( ) ) ;
rect . setRight ( ( int ) exp . cap ( 3 ) . toDouble ( ) ) ;
rect . setBottom ( ( int ) exp . cap ( 4 ) . toDouble ( ) ) ;
m_boundingBox = rect ;
m_originalSize = rect . size ( ) ;
kdDebug ( 30003 ) < < " Rect: " < < rect < < " Size: " < < m_originalSize < < endl ;
return true ;
}
bool KoPictureEps : : save ( TQIODevice * io ) const
{
// We save the raw data, to avoid damaging the file by many load/save cycles
TQ_ULONG size = io - > writeBlock ( m_rawData ) ; // WARNING: writeBlock returns TQ_LONG but size() TQ_ULONG!
return ( size = = m_rawData . size ( ) ) ;
}
TQSize KoPictureEps : : getOriginalSize ( void ) const
{
return m_originalSize ;
}
TQPixmap KoPictureEps : : generatePixmap ( const TQSize & size , bool smoothScale )
{
scaleAndCreatePixmap ( size , ! smoothScale , 0 , 0 ) ;
return m_cachedPixmap ;
}
TQString KoPictureEps : : getMimeType ( const TQString & ) const
{
return " image/x-eps " ;
}
TQImage KoPictureEps : : generateImage ( const TQSize & size )
{
// 0, 0 == resolution unknown
return scaleWithGhostScript ( size , 0 , 0 ) ;
}
void KoPictureEps : : clearCache ( void )
{
m_cachedPixmap . resize ( 0 , 0 ) ;
m_cacheIsInFastMode = true ;
m_cachedSize = TQSize ( ) ;
}