F:/KPlato/koffice/libs/kofficecore/KoPictureEps.cpp

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

Généré le Wed Nov 22 23:41:03 2006 pour KPlato par  doxygen 1.5.1-p1