lib Library API Documentation

koPictureEps.cc

00001 /* This file is part of the KDE project
00002    Copyright (c) 2001 Simon Hausmann <hausmann@kde.org>
00003    Copyright (C) 2002, 2003, 2004 Nicolas GOUTTE <goutte@kde.org>
00004 
00005    This library is free software; you can redistribute it and/or
00006    modify it under the terms of the GNU Library General Public
00007    License as published by the Free Software Foundation; either
00008    version 2 of the License, or (at your option) any later version.
00009 
00010    This library is distributed in the hope that it will be useful,
00011    but WITHOUT ANY WARRANTY; without even the implied warranty of
00012    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00013    Library General Public License for more details.
00014 
00015    You should have received a copy of the GNU Library General Public License
00016    along with this library; see the file COPYING.LIB.  If not, write to
00017    the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
00018    Boston, MA 02111-1307, USA.
00019 */
00020 
00021 #include <unistd.h>
00022 #include <stdio.h>
00023 
00024 #include <qbuffer.h>
00025 #include <qpainter.h>
00026 #include <qpaintdevicemetrics.h>
00027 #include <qfile.h>
00028 #include <qtextstream.h>
00029 #include <qregexp.h>
00030 #include <qimage.h>
00031 #include <qpixmap.h>
00032 #include <qapplication.h>
00033 #include <qdragobject.h>
00034 
00035 #include <kglobal.h>
00036 #include <kdebug.h>
00037 #include <kdeversion.h>
00038 #if ! KDE_IS_VERSION( 3,1,90 )
00039 #include <kdebugclasses.h>
00040 #endif
00041 #include <ktempfile.h>
00042 #include <kprocess.h>
00043 
00044 #include "koPictureKey.h"
00045 #include "koPictureBase.h"
00046 #include "koPictureEps.h"
00047 
00048 
00049 KoPictureEps::KoPictureEps(void) : m_psStreamStart(0), m_psStreamLength(0), m_cacheIsInFastMode(true)
00050 {
00051     // Forbid QPixmap to cache the X-Window resources (Yes, it is slower!)
00052     m_cachedPixmap.setOptimization(QPixmap::MemoryOptim);
00053 }
00054 
00055 KoPictureEps::~KoPictureEps(void)
00056 {
00057 }
00058 
00059 KoPictureBase* KoPictureEps::newCopy(void) const
00060 {
00061     return new KoPictureEps(*this);
00062 }
00063 
00064 KoPictureType::Type KoPictureEps::getType(void) const
00065 {
00066     return KoPictureType::TypeEps;
00067 }
00068 
00069 bool KoPictureEps::isNull(void) const
00070 {
00071     return m_rawData.isNull();
00072 }
00073 
00074 QImage KoPictureEps::scaleWithGhostScript(const QSize& size, const int resolutionx, const int resolutiony )
00075 {
00076     if (!m_boundingBox.width() || !m_boundingBox.height())
00077     {
00078         kdDebug(30003) << "EPS image has a null size! (in KoPictureEps::scaleWithGhostScript)" << endl;
00079         return QImage();
00080     }
00081 
00082     // ### 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)
00083     // png16m is better, but not always available -> fallback to bmp16m, then fallback to ppm (256 colors)
00084     // ### TODO: pcx24b is also a true colour format
00085     // ### TODO: support alpha (other gs devices needed)
00086 
00087     const char* deviceTable[] = { "png16m", "bmp16m", "ppm", 0 };
00088 
00089     QImage img;
00090 
00091     for ( int i = 0; deviceTable[i]; ++i)
00092     {
00093         if ( tryScaleWithGhostScript( img, size, resolutionx, resolutiony, deviceTable[i] ) != -1 )
00094         {
00095             return img;
00096         }
00097 
00098     }
00099 
00100     kdError(30003) << "Image from GhostScript cannot be loaded (in KoPictureEps::scaleWithGhostScript)" << endl;
00101     return img;
00102 }
00103 
00104 // Helper method for scaleWithGhostScript. Returns 1 on success, 0 on error, -1 if nothing generated
00105 // (in which case another 'output device' can be tried)
00106 int KoPictureEps::tryScaleWithGhostScript(QImage &image, const QSize& size, const int resolutionx, const int resolutiony, const char* device )
00107 // Based on the code of the file kdelibs/kimgio/eps.cpp
00108 {
00109     kdDebug(30003) << "Sampling with GhostScript, using device \"" << device << "\" (in KoPictureEps::tryScaleWithGhostScript)" << endl;
00110 
00111     KTempFile tmpFile;
00112     tmpFile.setAutoDelete(true);
00113 
00114     if ( tmpFile.status() )
00115     {
00116         kdError(30003) << "No KTempFile! (in KoPictureEps::tryScaleWithGhostScript)" << endl;
00117         return 0; // error
00118     }
00119 
00120     const int wantedWidth = size.width();
00121     const int wantedHeight = size.height();
00122     const double xScale = double(size.width()) / double(m_boundingBox.width());
00123     const double yScale = double(size.height()) / double(m_boundingBox.height());
00124 
00125     // create GS command line
00126 
00127     QString cmdBuf ( "gs -sOutputFile=" );
00128     cmdBuf += KProcess::quote(tmpFile.name());
00129     cmdBuf += " -q -g";
00130     cmdBuf += QString::number( wantedWidth );
00131     cmdBuf += "x";
00132     cmdBuf += QString::number( wantedHeight );
00133 
00134     if ( ( resolutionx > 0) && ( resolutiony > 0) )
00135     {
00136 #if 0
00137         // Do not play with resolution for now.
00138         // It brings more problems at print than solutions
00139         cmdBuf += " -r";
00140         cmdBuf += QString::number( resolutionx );
00141         cmdBuf += "x";
00142         cmdBuf += QString::number( resolutiony );
00143 #endif
00144     }
00145 
00146     cmdBuf += " -dSAFER -dPARANOIDSAFER -dNOPAUSE -sDEVICE=";
00147     cmdBuf += device;
00148     //cmdBuf += " -c 255 255 255 setrgbcolor fill 0 0 0 setrgbcolor";
00149     cmdBuf += " -";
00150     cmdBuf += " -c showpage quit";
00151 
00152     // run ghostview
00153 
00154     FILE* ghostfd = popen (QFile::encodeName(cmdBuf), "w");
00155 
00156     if ( ghostfd == 0 )
00157     {
00158         kdError(30003) << "No connection to GhostScript (in KoPictureEps::tryScaleWithGhostScript)" << endl;
00159         return 0; // error
00160     }
00161 
00162     // The translation is needed as GhostScript (7.07) cannot handle negative values in the bounding box otherwise.
00163     fprintf (ghostfd, "\n%d %d translate\n", -qRound(m_boundingBox.left()*xScale), -qRound(m_boundingBox.top()*yScale));
00164     fprintf (ghostfd, "%g %g scale\n", xScale, yScale);
00165 
00166     // write image to gs
00167 
00168     fwrite( m_rawData.data() + m_psStreamStart, sizeof(char), m_psStreamLength, ghostfd);
00169 
00170     pclose ( ghostfd );
00171 
00172     // load image
00173     if( !image.load (tmpFile.name()) )
00174     {
00175         // It failed - maybe the device isn't supported by gs
00176         return -1;
00177     }
00178     if ( image.size() != size ) // this can happen due to rounding problems
00179     {
00180         //kdDebug(30003) << "fixing size to " << size.width() << "x" << size.height()
00181         //          << " (was " << image.width() << "x" << image.height() << ")" << endl;
00182         image = image.scale( size ); // hmm, smoothScale instead?
00183     }
00184     kdDebug(30003) << "Image parameters: " << image.width() << "x" << image.height() << "x" << image.depth() << endl;
00185     return 1; // success
00186 }
00187 
00188 void KoPictureEps::scaleAndCreatePixmap(const QSize& size, bool fastMode, const int resolutionx, const int resolutiony )
00189 {
00190     kdDebug(30003) << "KoPictureEps::scaleAndCreatePixmap " << size << " " << (fastMode?QString("fast"):QString("slow"))
00191         << " resolutionx: " << resolutionx << " resolutiony: " << resolutiony << endl;
00192     if ((size==m_cachedSize)
00193         && ((fastMode) || (!m_cacheIsInFastMode)))
00194     {
00195         // The cached pixmap has already the right size
00196         // and:
00197         // - we are in fast mode (We do not care if the re-size was done slowly previously)
00198         // - the re-size was already done in slow mode
00199         kdDebug(30003) << "Already cached!" << endl;
00200         return;
00201     }
00202 
00203     // Slow mode can be very slow, especially at high zoom levels -> configurable
00204     if ( !isSlowResizeModeAllowed() )
00205     {
00206         kdDebug(30003) << "User has disallowed slow mode!" << endl;
00207         fastMode = true;
00208     }
00209 
00210     // We cannot use fast mode, if nothing was ever cached.
00211     if ( fastMode && !m_cachedSize.isEmpty())
00212     {
00213         kdDebug(30003) << "Fast scaling!" << endl;
00214         // Slower than caching a QImage, but faster than re-sampling!
00215         QImage image( m_cachedPixmap.convertToImage() );
00216         m_cachedPixmap=image.scale( size );
00217         m_cacheIsInFastMode=true;
00218         m_cachedSize=size;
00219     }
00220     else
00221     {
00222         QTime time;
00223         time.start();
00224 
00225         QApplication::setOverrideCursor( Qt::waitCursor );
00226         m_cachedPixmap = scaleWithGhostScript( size, resolutionx, resolutiony );
00227         QApplication::restoreOverrideCursor();
00228         m_cacheIsInFastMode=false;
00229         m_cachedSize=size;
00230 
00231         kdDebug(30003) << "Time: " << (time.elapsed()/1000.0) << " s" << endl;
00232     }
00233     kdDebug(30003) << "New size: " << size << endl;
00234 }
00235 
00236 void KoPictureEps::draw(QPainter& painter, int x, int y, int width, int height, int sx, int sy, int sw, int sh, bool fastMode)
00237 {
00238     if ( !width || !height )
00239         return;
00240 
00241     QSize screenSize( width, height );
00242     //kdDebug() << "KoPictureEps::draw screenSize=" << screenSize.width() << "x" << screenSize.height() << endl;
00243 
00244     QPaintDeviceMetrics metrics (painter.device());
00245     kdDebug(30003) << "Metrics: X: " << metrics.logicalDpiX() << " x Y: " << metrics.logicalDpiX() << " (in KoPictureEps::draw)" << endl;
00246 
00247     if ( painter.device()->isExtDev() ) // Is it an external device (i.e. printer)
00248     {
00249         kdDebug(30003) << "Drawing for a printer (in KoPictureEps::draw)" << endl;
00250         // For printing, always re-sample the image, as a printer has never the same resolution than a display.
00251         QImage image( scaleWithGhostScript( screenSize, metrics.logicalDpiX(), metrics.logicalDpiY() ) );
00252         // sx,sy,sw,sh is meant to be used as a cliprect on the pixmap, but drawImage
00253         // translates it to the (x,y) point -> we need (x+sx, y+sy).
00254         painter.drawImage( x + sx, y + sy, image, sx, sy, sw, sh );
00255     }
00256     else // No, it is simply a display
00257     {
00258         scaleAndCreatePixmap(screenSize, fastMode, metrics.logicalDpiX(), metrics.logicalDpiY() );
00259 
00260         // sx,sy,sw,sh is meant to be used as a cliprect on the pixmap, but drawPixmap
00261         // translates it to the (x,y) point -> we need (x+sx, y+sy).
00262         painter.drawPixmap( x + sx, y + sy, m_cachedPixmap, sx, sy, sw, sh );
00263     }
00264 }
00265 
00266 bool KoPictureEps::extractPostScriptStream( void )
00267 {
00268     kdDebug(30003) << "KoPictureEps::extractPostScriptStream" << endl;
00269     QDataStream data( m_rawData, IO_ReadOnly );
00270     data.setByteOrder( QDataStream::LittleEndian );
00271     Q_UINT32 magic, offset, length;
00272     data >> magic;
00273     data >> offset;
00274     data >> length;
00275     if ( !length )
00276     {
00277         kdError(30003) << "Length of PS stream is zero!" << endl;
00278         return false;
00279     }
00280     if ( offset+length>m_rawData.size() )
00281     {
00282         kdError(30003) << "Data stream of the EPSF file is longer than file: " << offset << "+" << length << ">" << m_rawData.size() << endl;
00283         return false;
00284     }
00285     m_psStreamStart = offset;
00286     m_psStreamLength = length;
00287     return true;
00288 }
00289 
00290 QString KoPictureEps::readLine( const QByteArray& array, const uint start, const uint length, uint& pos, bool& lastCharWasCr )
00291 {
00292     QString strLine;
00293     const uint finish = kMin( start + length, array.size() );
00294     for ( ; pos < finish; ++pos ) // We are starting at pos
00295     {
00296         const char ch = array[ pos ]; // Read one character
00297         if ( ch == '\n' )
00298         {
00299             if ( lastCharWasCr )
00300             {
00301                 // We have a line feed following a Carriage Return
00302                 // As the Carriage Return has already ended the previous line,
00303                 // discard this Line Feed.
00304                 lastCharWasCr = false;
00305             }
00306             else
00307             {
00308                 // We have a normal Line Feed, therefore we end the line
00309                 break;
00310             }
00311         }
00312         else if ( ch == '\r' )
00313         {
00314             // We have a Carriage Return, therefore we end the line
00315             lastCharWasCr = true;
00316             break;
00317         }
00318         else if ( ch == char(12) ) // Form Feed
00319         { // ### TODO: can a FF happen in PostScript?
00320             // Ignore the form feed
00321             continue;
00322         }
00323         else
00324         {
00325             strLine += ch;
00326             lastCharWasCr = false;
00327         }
00328     }
00329     return strLine;
00330 }
00331 
00332 
00333 bool KoPictureEps::loadData(const QByteArray& array, const QString& /* extension */ )
00334 {
00335 
00336     kdDebug(30003) << "KoPictureEps::load" << endl;
00337     // First, read the raw data
00338     m_rawData=array;
00339 
00340     if (m_rawData.isNull())
00341     {
00342         kdError(30003) << "No data was loaded!" << endl;
00343         return false;
00344     }
00345 
00346     if ( ( m_rawData[0]==char(0xc5) ) && ( m_rawData[1]==char(0xd0) )
00347         && ( m_rawData[2]==char(0xd3) ) && ( m_rawData[3]==char(0xc6) ) )
00348     {
00349         // We have a so-called "MS-DOS EPS file", we have to extract the PostScript stream
00350         if (!extractPostScriptStream()) // Changes m_rawData
00351             return false;
00352     }
00353     else
00354     {
00355         m_psStreamStart = 0;
00356         m_psStreamLength = m_rawData.size();
00357     }
00358 
00359     QString lineBox; // Line with the bounding box
00360     bool lastWasCr = false; // Was the last character of the line a carriage return?
00361     uint pos = m_psStreamStart; // We start to search the bounding box at the start of the PostScript stream
00362     QString line( readLine( m_rawData, m_psStreamStart, m_psStreamLength, pos, lastWasCr ) );
00363     kdDebug(30003) << "Header: " << line << endl;
00364     if (!line.startsWith("%!"))
00365     {
00366         kdError(30003) << "Not a PostScript file!" << endl;
00367         return false;
00368     }
00369     QRect rect;
00370     bool lineIsBoundingBox = false; // Does "line" has a %%BoundingBox line?
00371     for(;;)
00372     {
00373         ++pos; // Get over the previous line end (CR or LF)
00374         line = readLine( m_rawData,  m_psStreamStart, m_psStreamLength, pos, lastWasCr );
00375         kdDebug(30003) << "Checking line: " << line << endl;
00376         // ### TODO: it seems that the bounding box can be delayed with "(atend)" in the trailer (GhostScript 7.07 does not support it either.)
00377         if (line.startsWith("%%BoundingBox:"))
00378         {
00379             lineIsBoundingBox = true;
00380             break;
00381         }
00382         // ### TODO: also abort on %%EndComments
00383         // ### TODO: %? , where ? is non-white-space printable, does not end the comment!
00384         else if (!line.startsWith("%%"))
00385             break; // Not a EPS comment anymore, so abort as we are not in the EPS header anymore
00386     }
00387     if ( !lineIsBoundingBox )
00388     {
00389         kdError(30003) << "KoPictureEps::load: could not find a bounding box!" << endl;
00390         return false;
00391     }
00392     // Floating point values are not allowed in a Bounding Box, but ther are many such files out there...
00393     QRegExp exp("(\\-?[0-9]+\\.?[0-9]*)\\s(\\-?[0-9]+\\.?[0-9]*)\\s(\\-?[0-9]+\\.?[0-9]*)\\s(\\-?[0-9]+\\.?[0-9]*)");
00394     if ( exp.search(line) == -1 )
00395     {
00396         // ### TODO: it might be an "(atend)" and the bounding box is in the trailer
00397         // (but GhostScript 7.07 does not support a bounding box in the trailer.)
00398         // Note: in Trailer, it is the last BoundingBox that counts not the first!
00399         kdError(30003) << "Not standard bounding box: " << line << endl;
00400         return false;
00401     }
00402     kdDebug(30003) << "Reg. Exp. Found: " << exp.capturedTexts() << endl;
00403     rect.setLeft((int)exp.cap(1).toDouble());
00404     rect.setTop((int)exp.cap(2).toDouble());
00405     rect.setRight((int)exp.cap(3).toDouble());
00406     rect.setBottom((int)exp.cap(4).toDouble());
00407     m_boundingBox=rect;
00408     m_originalSize=rect.size();
00409     kdDebug(30003) << "Rect: " << rect << " Size: "  << m_originalSize << endl;
00410     return true;
00411 }
00412 
00413 bool KoPictureEps::save(QIODevice* io) const
00414 {
00415     // We save the raw data, to avoid damaging the file by many load/save cycles
00416     Q_ULONG size=io->writeBlock(m_rawData); // WARNING: writeBlock returns Q_LONG but size() Q_ULONG!
00417     return (size==m_rawData.size());
00418 }
00419 
00420 QSize KoPictureEps::getOriginalSize(void) const
00421 {
00422     return m_originalSize;
00423 }
00424 
00425 QPixmap KoPictureEps::generatePixmap(const QSize& size, bool smoothScale)
00426 {
00427     scaleAndCreatePixmap(size,!smoothScale, 0, 0);
00428     return m_cachedPixmap;
00429 }
00430 
00431 QString KoPictureEps::getMimeType(const QString&) const
00432 {
00433     return "image/x-eps";
00434 }
00435 
00436 QImage KoPictureEps::generateImage(const QSize& size)
00437 {
00438     // 0, 0 == resolution unknown
00439     return scaleWithGhostScript(size, 0, 0);
00440 }
00441 
00442 void KoPictureEps::clearCache(void)
00443 {
00444     m_cachedPixmap.resize(0, 0);
00445     m_cacheIsInFastMode=true;
00446     m_cachedSize=QSize();
00447 }
KDE Logo
This file is part of the documentation for lib Library Version 1.4.2.
Documentation copyright © 1996-2004 the KDE developers.
Generated on Mon Feb 13 09:40:04 2006 by doxygen 1.4.2 written by Dimitri van Heesch, © 1997-2003