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 QImage img; 00085 int ret = tryScaleWithGhostScript(img, size, resolutionx, resolutiony, "png16m"); 00086 if ( ret == -1 ) 00087 { 00088 ret = tryScaleWithGhostScript(img, size, resolutionx, resolutiony, "bmp16m"); 00089 if ( ret == -1 ) 00090 { 00091 ret = tryScaleWithGhostScript(img, size, resolutionx, resolutiony, "ppm"); 00092 if ( ret == -1 ) 00093 kdError(30003) << "Image from GhostScript cannot be loaded (in KoPictureEps::scaleWithGhostScript)" << endl; 00094 } 00095 } 00096 return img; 00097 } 00098 00099 // Helper method for scaleWithGhostScript. Returns 1 on success, 0 on error, -1 if nothing generated 00100 // (in which case another 'output device' can be tried) 00101 int KoPictureEps::tryScaleWithGhostScript(QImage &image, const QSize& size, const int resolutionx, const int resolutiony, const char* device ) 00102 // Based on the code of the file kdelibs/kimgio/eps.cpp 00103 { 00104 kdDebug(30003) << "Sampling with GhostScript, using device " << device << " (in KoPictureEps::tryScaleWithGhostScript)" << endl; 00105 00106 KTempFile tmpFile; 00107 tmpFile.setAutoDelete(true); 00108 00109 if ( tmpFile.status() ) 00110 { 00111 kdError(30003) << "No KTempFile! (in KoPictureEps::tryScaleWithGhostScript)" << endl; 00112 return 0; // error 00113 } 00114 00115 const int wantedWidth = size.width(); 00116 const int wantedHeight = size.height(); 00117 const double xScale = double(size.width()) / double(m_boundingBox.width()); 00118 const double yScale = double(size.height()) / double(m_boundingBox.height()); 00119 00120 // create GS command line 00121 00122 QString cmdBuf ( "gs -sOutputFile=" ); 00123 cmdBuf += KProcess::quote(tmpFile.name()); 00124 cmdBuf += " -q -g"; 00125 cmdBuf += QString::number( wantedWidth ); 00126 cmdBuf += "x"; 00127 cmdBuf += QString::number( wantedHeight ); 00128 00129 if ( ( resolutionx > 0) && ( resolutiony > 0) ) 00130 { 00131 #if 0 00132 // Do not play with resolution for now. 00133 // It brings more problems at print than solutions 00134 cmdBuf += " -r"; 00135 cmdBuf += QString::number( resolutionx ); 00136 cmdBuf += "x"; 00137 cmdBuf += QString::number( resolutiony ); 00138 #endif 00139 } 00140 00141 cmdBuf += " -dSAFER -dPARANOIDSAFER -dNOPAUSE -sDEVICE="; 00142 cmdBuf += device; 00143 //cmdBuf += " -c 255 255 255 setrgbcolor fill 0 0 0 setrgbcolor"; 00144 cmdBuf += " -"; 00145 cmdBuf += " -c showpage quit"; 00146 00147 // run ghostview 00148 00149 FILE* ghostfd = popen (QFile::encodeName(cmdBuf), "w"); 00150 00151 if ( ghostfd == 0 ) 00152 { 00153 kdError(30003) << "No connection to GhostScript (in KoPictureEps::tryScaleWithGhostScript)" << endl; 00154 return 0; // error 00155 } 00156 00157 // The translation is needed as GhostScript (7.07) cannot handle negative values in the bounding box otherwise. 00158 fprintf (ghostfd, "\n%d %d translate\n", -qRound(m_boundingBox.left()*xScale), -qRound(m_boundingBox.top()*yScale)); 00159 fprintf (ghostfd, "%g %g scale\n", xScale, yScale); 00160 00161 // write image to gs 00162 00163 fwrite( m_rawData.data() + m_psStreamStart, sizeof(char), m_psStreamLength, ghostfd); 00164 00165 pclose ( ghostfd ); 00166 00167 // load image 00168 if( !image.load (tmpFile.name()) ) 00169 { 00170 // It failed - maybe the device isn't supported by gs 00171 return -1; 00172 } 00173 if ( image.size() != size ) // this can happen due to rounding problems 00174 { 00175 //kdDebug(30003) << "fixing size to " << size.width() << "x" << size.height() 00176 // << " (was " << image.width() << "x" << image.height() << ")" << endl; 00177 image = image.scale( size ); // hmm, smoothScale instead? 00178 } 00179 kdDebug(30003) << "Image parameters: " << image.width() << "x" << image.height() << "x" << image.depth() << endl; 00180 return 1; // success 00181 } 00182 00183 void KoPictureEps::scaleAndCreatePixmap(const QSize& size, bool fastMode, const int resolutionx, const int resolutiony ) 00184 { 00185 kdDebug(30003) << "KoPictureEps::scaleAndCreatePixmap " << size << " " << (fastMode?QString("fast"):QString("slow")) 00186 << " resolutionx: " << resolutionx << " resolutiony: " << resolutiony << endl; 00187 if ((size==m_cachedSize) 00188 && ((fastMode) || (!m_cacheIsInFastMode))) 00189 { 00190 // The cached pixmap has already the right size 00191 // and: 00192 // - we are in fast mode (We do not care if the re-size was done slowly previously) 00193 // - the re-size was already done in slow mode 00194 kdDebug(30003) << "Already cached!" << endl; 00195 return; 00196 } 00197 00198 // Slow mode can be very slow, especially at high zoom levels -> configurable 00199 if ( !isSlowResizeModeAllowed() ) 00200 { 00201 kdDebug(30003) << "User has disallowed slow mode!" << endl; 00202 fastMode = true; 00203 } 00204 00205 // We cannot use fast mode, if nothing was ever cached. 00206 if ( fastMode && !m_cachedSize.isEmpty()) 00207 { 00208 kdDebug(30003) << "Fast scaling!" << endl; 00209 // Slower than caching a QImage, but faster than re-sampling! 00210 QImage image( m_cachedPixmap.convertToImage() ); 00211 m_cachedPixmap=image.scale( size ); 00212 m_cacheIsInFastMode=true; 00213 m_cachedSize=size; 00214 } 00215 else 00216 { 00217 QTime time; 00218 time.start(); 00219 00220 QApplication::setOverrideCursor( Qt::waitCursor ); 00221 m_cachedPixmap = scaleWithGhostScript( size, resolutionx, resolutiony ); 00222 QApplication::restoreOverrideCursor(); 00223 m_cacheIsInFastMode=false; 00224 m_cachedSize=size; 00225 00226 kdDebug(30003) << "Time: " << (time.elapsed()/1000.0) << " s" << endl; 00227 } 00228 kdDebug(30003) << "New size: " << size << endl; 00229 } 00230 00231 void KoPictureEps::draw(QPainter& painter, int x, int y, int width, int height, int sx, int sy, int sw, int sh, bool fastMode) 00232 { 00233 if ( !width || !height ) 00234 return; 00235 00236 QSize screenSize( width, height ); 00237 //kdDebug() << "KoPictureEps::draw screenSize=" << screenSize.width() << "x" << screenSize.height() << endl; 00238 00239 QPaintDeviceMetrics metrics (painter.device()); 00240 kdDebug(30003) << "Metrics: X: " << metrics.logicalDpiX() << " x Y: " << metrics.logicalDpiX() << " (in KoPictureEps::draw)" << endl; 00241 00242 if ( painter.device()->isExtDev() ) // Is it an external device (i.e. printer) 00243 { 00244 kdDebug(30003) << "Drawing for a printer (in KoPictureEps::draw)" << endl; 00245 // For printing, always re-sample the image, as a printer has never the same resolution than a display. 00246 QImage image( scaleWithGhostScript( screenSize, metrics.logicalDpiX(), metrics.logicalDpiY() ) ); 00247 // sx,sy,sw,sh is meant to be used as a cliprect on the pixmap, but drawImage 00248 // translates it to the (x,y) point -> we need (x+sx, y+sy). 00249 painter.drawImage( x + sx, y + sy, image, sx, sy, sw, sh ); 00250 } 00251 else // No, it is simply a display 00252 { 00253 scaleAndCreatePixmap(screenSize, fastMode, metrics.logicalDpiX(), metrics.logicalDpiY() ); 00254 00255 // sx,sy,sw,sh is meant to be used as a cliprect on the pixmap, but drawPixmap 00256 // translates it to the (x,y) point -> we need (x+sx, y+sy). 00257 painter.drawPixmap( x + sx, y + sy, m_cachedPixmap, sx, sy, sw, sh ); 00258 } 00259 } 00260 00261 bool KoPictureEps::extractPostScriptStream( void ) 00262 { 00263 kdDebug(30003) << "KoPictureEps::extractPostScriptStream" << endl; 00264 QDataStream data( m_rawData, IO_ReadOnly ); 00265 data.setByteOrder( QDataStream::LittleEndian ); 00266 Q_UINT32 magic, offset, length; 00267 data >> magic; 00268 data >> offset; 00269 data >> length; 00270 if ( !length ) 00271 { 00272 kdError(30003) << "Length of PS stream is zero!" << endl; 00273 return false; 00274 } 00275 if ( offset+length>m_rawData.size() ) 00276 { 00277 kdError(30003) << "Data stream of the EPSF file is longer than file: " << offset << "+" << length << ">" << m_rawData.size() << endl; 00278 return false; 00279 } 00280 m_psStreamStart = offset; 00281 m_psStreamLength = length; 00282 return true; 00283 } 00284 00285 QString KoPictureEps::readLine( const QByteArray& array, const uint start, const uint length, uint& pos, bool& lastCharWasCr ) 00286 { 00287 QString strLine; 00288 const uint finish = kMin( start + length, array.size() ); 00289 for ( ; pos < finish; ++pos ) // We are starting at pos 00290 { 00291 const char ch = array[ pos ]; // Read one character 00292 if ( ch == '\n' ) 00293 { 00294 if ( lastCharWasCr ) 00295 { 00296 // We have a line feed following a Carriage Return 00297 // As the Carriage Return has already ended the previous line, 00298 // discard this Line Feed. 00299 lastCharWasCr = false; 00300 } 00301 else 00302 { 00303 // We have a normal Line Feed, therefore we end the line 00304 break; 00305 } 00306 } 00307 else if ( ch == '\r' ) 00308 { 00309 // We have a Carriage Return, therefore we end the line 00310 lastCharWasCr = true; 00311 break; 00312 } 00313 else if ( ch == char(12) ) // Form Feed 00314 { // ### TODO: can a FF happen in PostScript? 00315 // Ignore the form feed 00316 continue; 00317 } 00318 else 00319 { 00320 strLine += ch; 00321 lastCharWasCr = false; 00322 } 00323 } 00324 return strLine; 00325 } 00326 00327 00328 bool KoPictureEps::load(const QByteArray& array, const QString& /* extension */ ) 00329 { 00330 00331 kdDebug(30003) << "KoPictureEps::load" << endl; 00332 // First, read the raw data 00333 m_rawData=array; 00334 00335 if (m_rawData.isNull()) 00336 { 00337 kdError(30003) << "No data was loaded!" << endl; 00338 return false; 00339 } 00340 00341 if ( ( m_rawData[0]==char(0xc5) ) && ( m_rawData[1]==char(0xd0) ) 00342 && ( m_rawData[2]==char(0xd3) ) && ( m_rawData[3]==char(0xc6) ) ) 00343 { 00344 // We have a so-called "MS-DOS EPS file", we have to extract the PostScript stream 00345 if (!extractPostScriptStream()) // Changes m_rawData 00346 return false; 00347 } 00348 else 00349 { 00350 m_psStreamStart = 0; 00351 m_psStreamLength = m_rawData.size(); 00352 } 00353 00354 QString lineBox; // Line with the bounding box 00355 bool lastWasCr = false; // Was the last character of the line a carriage return? 00356 uint pos = m_psStreamStart; // We start to search the bounding box at the start of the PostScript stream 00357 QString line( readLine( m_rawData, m_psStreamStart, m_psStreamLength, pos, lastWasCr ) ); 00358 kdDebug(30003) << "Header: " << line << endl; 00359 if (!line.startsWith("%!")) 00360 { 00361 kdError(30003) << "Not a PostScript file!" << endl; 00362 return false; 00363 } 00364 QRect rect; 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 kdDebug(30003) << "Checking line: " << line << endl; 00370 // ### TODO: it seems that the bounding box can be delayed in the trailer (GhostScript 7.07 does not support it either.) 00371 if (line.startsWith("%%BoundingBox:")) 00372 { 00373 lineBox=line; 00374 break; 00375 } 00376 else if (!line.startsWith("%%")) 00377 break; // Not a EPS comment anymore, so abort as we are not in the EPS header anymore 00378 } 00379 if (lineBox.isEmpty()) 00380 { 00381 kdError(30003) << "KoPictureEps::load: could not find bounding box!" << endl; 00382 return false; 00383 } 00384 QRegExp exp("(\\-?[0-9]+\\.?[0-9]*)\\s(\\-?[0-9]+\\.?[0-9]*)\\s(\\-?[0-9]+\\.?[0-9]*)\\s(\\-?[0-9]+\\.?[0-9]*)"); 00385 // ### FIXME: check return value of search, as the bounding box could be marked as "delayed" (What is the exact text then?) 00386 exp.search(lineBox); 00387 kdDebug(30003) << "Reg. Exp. Found: " << exp.capturedTexts() << endl; 00388 rect.setLeft((int)exp.cap(1).toDouble()); 00389 rect.setTop((int)exp.cap(2).toDouble()); 00390 rect.setRight((int)exp.cap(3).toDouble()); 00391 rect.setBottom((int)exp.cap(4).toDouble()); 00392 m_boundingBox=rect; 00393 m_originalSize=rect.size(); 00394 kdDebug(30003) << "Rect: " << rect << " Size: " << m_originalSize << endl; 00395 return true; 00396 } 00397 00398 bool KoPictureEps::save(QIODevice* io) 00399 { 00400 // We save the raw data, to avoid damaging the file by many load/save cycles 00401 Q_ULONG size=io->writeBlock(m_rawData); // WARNING: writeBlock returns Q_LONG but size() Q_ULONG! 00402 return (size==m_rawData.size()); 00403 } 00404 00405 QSize KoPictureEps::getOriginalSize(void) const 00406 { 00407 return m_originalSize; 00408 } 00409 00410 QPixmap KoPictureEps::generatePixmap(const QSize& size, bool smoothScale) 00411 { 00412 scaleAndCreatePixmap(size,!smoothScale, 0, 0); 00413 return m_cachedPixmap; 00414 } 00415 00416 QString KoPictureEps::getMimeType(const QString&) const 00417 { 00418 return "image/x-eps"; 00419 } 00420 00421 QImage KoPictureEps::generateImage(const QSize& size) 00422 { 00423 // 0, 0 == resolution unknown 00424 return scaleWithGhostScript(size, 0, 0); 00425 } 00426 00427 void KoPictureEps::clearCache(void) 00428 { 00429 m_cachedPixmap.resize(0, 0); 00430 m_cacheIsInFastMode=true; 00431 m_cachedSize=QSize(); 00432 }
KDE Logo
This file is part of the documentation for lib Library Version 1.3.3.
Documentation copyright © 1996-2004 the KDE developers.
Generated on Fri Sep 24 18:22:25 2004 by doxygen 1.3.7 written by Dimitri van Heesch, © 1997-2003