lib

KoPictureEps.cpp
1/* This file is part of the KDE project
2 Copyright (c) 2001 Simon Hausmann <hausmann@kde.org>
3 Copyright (C) 2002, 2003, 2004 Nicolas GOUTTE <goutte@kde.org>
4
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Library General Public
7 License as published by the Free Software Foundation; either
8 version 2 of the License, or (at your option) any later version.
9
10 This library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Library General Public License for more details.
14
15 You should have received a copy of the GNU Library General Public License
16 along with this library; see the file COPYING.LIB. If not, write to
17 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 * Boston, MA 02110-1301, USA.
19*/
20
21#include <unistd.h>
22#include <stdio.h>
23
24#include <tqbuffer.h>
25#include <tqpainter.h>
26#include <tqpaintdevicemetrics.h>
27#include <tqfile.h>
28#include <tqtextstream.h>
29#include <tqregexp.h>
30#include <tqimage.h>
31#include <tqpixmap.h>
32#include <tqapplication.h>
33#include <tqdragobject.h>
34
35#include <tdeglobal.h>
36#include <kdebug.h>
37#include <tdetempfile.h>
38#include <kprocess.h>
39
40#include "KoPictureKey.h"
41#include "KoPictureBase.h"
42#include "KoPictureEps.h"
43
44
45KoPictureEps::KoPictureEps(void) : m_psStreamStart(0), m_psStreamLength(0), m_cacheIsInFastMode(true)
46{
47 // Forbid TQPixmap to cache the X-Window resources (Yes, it is slower!)
48 m_cachedPixmap.setOptimization(TQPixmap::MemoryOptim);
49}
50
51KoPictureEps::~KoPictureEps(void)
52{
53}
54
55KoPictureBase* KoPictureEps::newCopy(void) const
56{
57 return new KoPictureEps(*this);
58}
59
60KoPictureType::Type KoPictureEps::getType(void) const
61{
62 return KoPictureType::TypeEps;
63}
64
65bool KoPictureEps::isNull(void) const
66{
67 return m_rawData.isNull();
68}
69
70TQImage KoPictureEps::scaleWithGhostScript(const TQSize& size, const int resolutionx, const int resolutiony )
71{
72 if (!m_boundingBox.width() || !m_boundingBox.height())
73 {
74 kdDebug(30003) << "EPS image has a null size! (in KoPictureEps::scaleWithGhostScript)" << endl;
75 return TQImage();
76 }
77
78 // ### 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)
79 // png16m is better, but not always available -> fallback to bmp16m, then fallback to ppm (256 colors)
80 // ### TODO: pcx24b is also a true colour format
81 // ### TODO: support alpha (other gs devices needed)
82
83 const char* deviceTable[] = { "png16m", "bmp16m", "ppm", 0 };
84
85 TQImage img;
86
87 for ( int i = 0; deviceTable[i]; ++i)
88 {
89 if ( tryScaleWithGhostScript( img, size, resolutionx, resolutiony, deviceTable[i] ) != -1 )
90 {
91 return img;
92 }
93
94 }
95
96 kdError(30003) << "Image from GhostScript cannot be loaded (in KoPictureEps::scaleWithGhostScript)" << endl;
97 return img;
98}
99
100// Helper method for scaleWithGhostScript. Returns 1 on success, 0 on error, -1 if nothing generated
101// (in which case another 'output device' can be tried)
102int KoPictureEps::tryScaleWithGhostScript(TQImage &image, const TQSize& size, const int resolutionx, const int resolutiony, const char* device )
103// Based on the code of the file tdelibs/kimgio/eps.cpp
104{
105 kdDebug(30003) << "Sampling with GhostScript, using device \"" << device << "\" (in KoPictureEps::tryScaleWithGhostScript)" << endl;
106
107 KTempFile tmpFile;
108 tmpFile.setAutoDelete(true);
109
110 if ( tmpFile.status() )
111 {
112 kdError(30003) << "No KTempFile! (in KoPictureEps::tryScaleWithGhostScript)" << endl;
113 return 0; // error
114 }
115
116 const int wantedWidth = size.width();
117 const int wantedHeight = size.height();
118 const double xScale = double(size.width()) / double(m_boundingBox.width());
119 const double yScale = double(size.height()) / double(m_boundingBox.height());
120
121 // create GS command line
122
123 TQString cmdBuf ( "gs -sOutputFile=" );
124 cmdBuf += TDEProcess::quote(tmpFile.name());
125 cmdBuf += " -q -g";
126 cmdBuf += TQString::number( wantedWidth );
127 cmdBuf += "x";
128 cmdBuf += TQString::number( wantedHeight );
129
130 if ( ( resolutionx > 0) && ( resolutiony > 0) )
131 {
132#if 0
133 // Do not play with resolution for now.
134 // It brings more problems at print than solutions
135 cmdBuf += " -r";
136 cmdBuf += TQString::number( resolutionx );
137 cmdBuf += "x";
138 cmdBuf += TQString::number( resolutiony );
139#endif
140 }
141
142 cmdBuf += " -dSAFER -dPARANOIDSAFER -dNOPAUSE -sDEVICE=";
143 cmdBuf += device;
144 //cmdBuf += " -c 255 255 255 setrgbcolor fill 0 0 0 setrgbcolor";
145 cmdBuf += " -";
146 cmdBuf += " -c showpage quit";
147
148 // run ghostview
149
150 FILE* ghostfd = popen (TQFile::encodeName(cmdBuf), "w");
151
152 if ( ghostfd == 0 )
153 {
154 kdError(30003) << "No connection to GhostScript (in KoPictureEps::tryScaleWithGhostScript)" << endl;
155 return 0; // error
156 }
157
158 // The translation is needed as GhostScript (7.07) cannot handle negative values in the bounding box otherwise.
159 fprintf (ghostfd, "\n%d %d translate\n", -tqRound(m_boundingBox.left()*xScale), -tqRound(m_boundingBox.top()*yScale));
160 fprintf (ghostfd, "%g %g scale\n", xScale, yScale);
161
162 // write image to gs
163
164 fwrite( m_rawData.data() + m_psStreamStart, sizeof(char), m_psStreamLength, ghostfd);
165
166 pclose ( ghostfd );
167
168 // load image
169 if( !image.load (tmpFile.name()) )
170 {
171 // It failed - maybe the device isn't supported by gs
172 return -1;
173 }
174 if ( image.size() != size ) // this can happen due to rounding problems
175 {
176 //kdDebug(30003) << "fixing size to " << size.width() << "x" << size.height()
177 // << " (was " << image.width() << "x" << image.height() << ")" << endl;
178 image = image.scale( size ); // hmm, smoothScale instead?
179 }
180 kdDebug(30003) << "Image parameters: " << image.width() << "x" << image.height() << "x" << image.depth() << endl;
181 return 1; // success
182}
183
184void KoPictureEps::scaleAndCreatePixmap(const TQSize& size, bool fastMode, const int resolutionx, const int resolutiony )
185{
186 kdDebug(30003) << "KoPictureEps::scaleAndCreatePixmap " << size << " " << (fastMode?TQString("fast"):TQString("slow"))
187 << " resolutionx: " << resolutionx << " resolutiony: " << resolutiony << endl;
188 if ((size==m_cachedSize)
189 && ((fastMode) || (!m_cacheIsInFastMode)))
190 {
191 // The cached pixmap has already the right size
192 // and:
193 // - we are in fast mode (We do not care if the re-size was done slowly previously)
194 // - the re-size was already done in slow mode
195 kdDebug(30003) << "Already cached!" << endl;
196 return;
197 }
198
199 // Slow mode can be very slow, especially at high zoom levels -> configurable
200 if ( !isSlowResizeModeAllowed() )
201 {
202 kdDebug(30003) << "User has disallowed slow mode!" << endl;
203 fastMode = true;
204 }
205
206 // We cannot use fast mode, if nothing was ever cached.
207 if ( fastMode && !m_cachedSize.isEmpty())
208 {
209 kdDebug(30003) << "Fast scaling!" << endl;
210 // Slower than caching a TQImage, but faster than re-sampling!
211 TQImage image( m_cachedPixmap.convertToImage() );
212 m_cachedPixmap=image.scale( size );
213 m_cacheIsInFastMode=true;
214 m_cachedSize=size;
215 }
216 else
217 {
218 TQTime time;
219 time.start();
220
221 TQApplication::setOverrideCursor( TQt::waitCursor );
222 m_cachedPixmap = scaleWithGhostScript( size, resolutionx, resolutiony );
223 TQApplication::restoreOverrideCursor();
224 m_cacheIsInFastMode=false;
225 m_cachedSize=size;
226
227 kdDebug(30003) << "Time: " << (time.elapsed()/1000.0) << " s" << endl;
228 }
229 kdDebug(30003) << "New size: " << size << endl;
230}
231
232void KoPictureEps::draw(TQPainter& painter, int x, int y, int width, int height, int sx, int sy, int sw, int sh, bool fastMode)
233{
234 if ( !width || !height )
235 return;
236
237 TQSize screenSize( width, height );
238 //kdDebug() << "KoPictureEps::draw screenSize=" << screenSize.width() << "x" << screenSize.height() << endl;
239
240 TQPaintDeviceMetrics metrics (painter.device());
241 kdDebug(30003) << "Metrics: X: " << metrics.logicalDpiX() << " x Y: " << metrics.logicalDpiX() << " (in KoPictureEps::draw)" << endl;
242
243 if ( painter.device()->isExtDev() ) // Is it an external device (i.e. printer)
244 {
245 kdDebug(30003) << "Drawing for a printer (in KoPictureEps::draw)" << endl;
246 // For printing, always re-sample the image, as a printer has never the same resolution than a display.
247 TQImage image( scaleWithGhostScript( screenSize, metrics.logicalDpiX(), metrics.logicalDpiY() ) );
248 // sx,sy,sw,sh is meant to be used as a cliprect on the pixmap, but drawImage
249 // translates it to the (x,y) point -> we need (x+sx, y+sy).
250 painter.drawImage( x + sx, y + sy, image, sx, sy, sw, sh );
251 }
252 else // No, it is simply a display
253 {
254 scaleAndCreatePixmap(screenSize, fastMode, metrics.logicalDpiX(), metrics.logicalDpiY() );
255
256 // sx,sy,sw,sh is meant to be used as a cliprect on the pixmap, but drawPixmap
257 // translates it to the (x,y) point -> we need (x+sx, y+sy).
258 painter.drawPixmap( x + sx, y + sy, m_cachedPixmap, sx, sy, sw, sh );
259 }
260}
261
262bool KoPictureEps::extractPostScriptStream( void )
263{
264 kdDebug(30003) << "KoPictureEps::extractPostScriptStream" << endl;
265 TQDataStream data( m_rawData, IO_ReadOnly );
266 data.setByteOrder( TQDataStream::LittleEndian );
267 TQ_UINT32 magic, offset, length;
268 data >> magic;
269 data >> offset;
270 data >> length;
271 if ( !length )
272 {
273 kdError(30003) << "Length of PS stream is zero!" << endl;
274 return false;
275 }
276 if ( offset+length>m_rawData.size() )
277 {
278 kdError(30003) << "Data stream of the EPSF file is longer than file: " << offset << "+" << length << ">" << m_rawData.size() << endl;
279 return false;
280 }
281 m_psStreamStart = offset;
282 m_psStreamLength = length;
283 return true;
284}
285
286TQString KoPictureEps::readLine( const TQByteArray& array, const uint start, const uint length, uint& pos, bool& lastCharWasCr )
287{
288 TQString strLine;
289 const uint finish = kMin( start + length, (uint)array.size() );
290 for ( ; pos < finish; ++pos ) // We are starting at pos
291 {
292 const char ch = array[ pos ]; // Read one character
293 if ( ch == '\n' )
294 {
295 if ( lastCharWasCr )
296 {
297 // We have a line feed following a Carriage Return
298 // As the Carriage Return has already ended the previous line,
299 // discard this Line Feed.
300 lastCharWasCr = false;
301 }
302 else
303 {
304 // We have a normal Line Feed, therefore we end the line
305 break;
306 }
307 }
308 else if ( ch == '\r' )
309 {
310 // We have a Carriage Return, therefore we end the line
311 lastCharWasCr = true;
312 break;
313 }
314 else if ( ch == char(12) ) // Form Feed
315 { // ### TODO: can a FF happen in PostScript?
316 // Ignore the form feed
317 continue;
318 }
319 else
320 {
321 strLine += ch;
322 lastCharWasCr = false;
323 }
324 }
325 return strLine;
326}
327
328
329bool KoPictureEps::loadData(const TQByteArray& array, const TQString& /* extension */ )
330{
331
332 kdDebug(30003) << "KoPictureEps::load" << endl;
333 // First, read the raw data
334 m_rawData=array;
335
336 if (m_rawData.isNull())
337 {
338 kdError(30003) << "No data was loaded!" << endl;
339 return false;
340 }
341
342 if ( ( m_rawData[0]==char(0xc5) ) && ( m_rawData[1]==char(0xd0) )
343 && ( m_rawData[2]==char(0xd3) ) && ( m_rawData[3]==char(0xc6) ) )
344 {
345 // We have a so-called "MS-DOS EPS file", we have to extract the PostScript stream
346 if (!extractPostScriptStream()) // Changes m_rawData
347 return false;
348 }
349 else
350 {
351 m_psStreamStart = 0;
352 m_psStreamLength = m_rawData.size();
353 }
354
355 TQString lineBox; // Line with the bounding box
356 bool lastWasCr = false; // Was the last character of the line a carriage return?
357 uint pos = m_psStreamStart; // We start to search the bounding box at the start of the PostScript stream
358 TQString line( readLine( m_rawData, m_psStreamStart, m_psStreamLength, pos, lastWasCr ) );
359 kdDebug(30003) << "Header: " << line << endl;
360 if (!line.startsWith("%!"))
361 {
362 kdError(30003) << "Not a PostScript file!" << endl;
363 return false;
364 }
365 TQRect rect;
366 bool lineIsBoundingBox = false; // Does "line" has a %%BoundingBox line?
367 for(;;)
368 {
369 ++pos; // Get over the previous line end (CR or LF)
370 line = readLine( m_rawData, m_psStreamStart, m_psStreamLength, pos, lastWasCr );
371 kdDebug(30003) << "Checking line: " << line << endl;
372 // ### TODO: it seems that the bounding box can be delayed with "(atend)" in the trailer (GhostScript 7.07 does not support it either.)
373 if (line.startsWith("%%BoundingBox:"))
374 {
375 lineIsBoundingBox = true;
376 break;
377 }
378 // ### TODO: also abort on %%EndComments
379 // ### TODO: %? , where ? is non-white-space printable, does not end the comment!
380 else if (!line.startsWith("%%"))
381 break; // Not a EPS comment anymore, so abort as we are not in the EPS header anymore
382 }
383 if ( !lineIsBoundingBox )
384 {
385 kdError(30003) << "KoPictureEps::load: could not find a bounding box!" << endl;
386 return false;
387 }
388 // Floating point values are not allowed in a Bounding Box, but ther are many such files out there...
389 TQRegExp exp("(\\-?[0-9]+\\.?[0-9]*)\\s(\\-?[0-9]+\\.?[0-9]*)\\s(\\-?[0-9]+\\.?[0-9]*)\\s(\\-?[0-9]+\\.?[0-9]*)");
390 if ( exp.search(line) == -1 )
391 {
392 // ### TODO: it might be an "(atend)" and the bounding box is in the trailer
393 // (but GhostScript 7.07 does not support a bounding box in the trailer.)
394 // Note: in Trailer, it is the last BoundingBox that counts not the first!
395 kdError(30003) << "Not standard bounding box: " << line << endl;
396 return false;
397 }
398 kdDebug(30003) << "Reg. Exp. Found: " << exp.capturedTexts() << endl;
399 rect.setLeft((int)exp.cap(1).toDouble());
400 rect.setTop((int)exp.cap(2).toDouble());
401 rect.setRight((int)exp.cap(3).toDouble());
402 rect.setBottom((int)exp.cap(4).toDouble());
403 m_boundingBox=rect;
404 m_originalSize=rect.size();
405 kdDebug(30003) << "Rect: " << rect << " Size: " << m_originalSize << endl;
406 return true;
407}
408
409bool KoPictureEps::save(TQIODevice* io) const
410{
411 // We save the raw data, to avoid damaging the file by many load/save cycles
412 TQ_ULONG size=io->writeBlock(m_rawData); // WARNING: writeBlock returns TQ_LONG but size() TQ_ULONG!
413 return (size==m_rawData.size());
414}
415
416TQSize KoPictureEps::getOriginalSize(void) const
417{
418 return m_originalSize;
419}
420
421TQPixmap KoPictureEps::generatePixmap(const TQSize& size, bool smoothScale)
422{
423 scaleAndCreatePixmap(size,!smoothScale, 0, 0);
424 return m_cachedPixmap;
425}
426
427TQString KoPictureEps::getMimeType(const TQString&) const
428{
429 return "image/x-eps";
430}
431
432TQImage KoPictureEps::generateImage(const TQSize& size)
433{
434 // 0, 0 == resolution unknown
435 return scaleWithGhostScript(size, 0, 0);
436}
437
438void KoPictureEps::clearCache(void)
439{
440 m_cachedPixmap.resize(0, 0);
441 m_cacheIsInFastMode=true;
442 m_cachedSize=TQSize();
443}