1/**
2* QImageIO Routines to read/write EPS images.
3* copyright (c) 1998 Dirk Schoenberger <dirk.schoenberger@freenet.de>
4*
5* This library is distributed under the conditions of the GNU LGPL.
6*/
7#include "eps.h"
8#include <unistd.h>
9#include <stdio.h>
10#include <QtGui/QImage>
11#include <QtCore/QFile>
12#include <QtGui/QPainter>
13#include <QtGui/QPrinter>
14#include <QtCore/QTextStream>
15#include <QtCore/QTemporaryFile>
16#include <kapplication.h>
17#include <kdebug.h>
18
19#define BUFLEN 200
20
21#define BBOX "%%BoundingBox:"
22#define BBOX_LEN strlen(BBOX)
23
24static bool seekToCodeStart( QIODevice * io, quint32 & ps_offset, quint32 & ps_size )
25{
26 char buf[4]; // We at most need to read 4 bytes at a time
27 ps_offset=0L;
28 ps_size=0L;
29
30 if ( io->read(buf, 2)!=2 ) // Read first two bytes
31 {
32 kError(399) << "kimgio EPS: EPS file has less than 2 bytes." << endl;
33 return false;
34 }
35
36 if ( buf[0]=='%' && buf[1]=='!' ) // Check %! magic
37 {
38 kDebug(399) << "kimgio EPS: normal EPS file";
39 }
40 else if ( buf[0]==char(0xc5) && buf[1]==char(0xd0) ) // Check start of MS-DOS EPS magic
41 { // May be a MS-DOS EPS file
42 if ( io->read(buf+2, 2)!=2 ) // Read further bytes of MS-DOS EPS magic
43 {
44 kError(399) << "kimgio EPS: potential MS-DOS EPS file has less than 4 bytes." << endl;
45 return false;
46 }
47 if ( buf[2]==char(0xd3) && buf[3]==char(0xc6) ) // Check last bytes of MS-DOS EPS magic
48 {
49 if (io->read(buf, 4)!=4) // Get offset of PostScript code in the MS-DOS EPS file.
50 {
51 kError(399) << "kimgio EPS: cannot read offset of MS-DOS EPS file" << endl;
52 return false;
53 }
54 ps_offset // Offset is in little endian
55 = ((unsigned char) buf[0])
56 + ((unsigned char) buf[1] << 8)
57 + ((unsigned char) buf[2] << 16)
58 + ((unsigned char) buf[3] << 24);
59 if (io->read(buf, 4)!=4) // Get size of PostScript code in the MS-DOS EPS file.
60 {
61 kError(399) << "kimgio EPS: cannot read size of MS-DOS EPS file" << endl;
62 return false;
63 }
64 ps_size // Size is in little endian
65 = ((unsigned char) buf[0])
66 + ((unsigned char) buf[1] << 8)
67 + ((unsigned char) buf[2] << 16)
68 + ((unsigned char) buf[3] << 24);
69 kDebug(399) << "kimgio EPS: Offset: " << ps_offset <<" Size: " << ps_size;
70 if ( !io->seek(ps_offset) ) // Get offset of PostScript code in the MS-DOS EPS file.
71 {
72 kError(399) << "kimgio EPS: cannot seek in MS-DOS EPS file" << endl;
73 return false;
74 }
75 if ( io->read(buf, 2)!=2 ) // Read first two bytes of what should be the Postscript code
76 {
77 kError(399) << "kimgio EPS: PostScript code has less than 2 bytes." << endl;
78 return false;
79 }
80 if ( buf[0]=='%' && buf[1]=='!' ) // Check %! magic
81 {
82 kDebug(399) << "kimgio EPS: MS-DOS EPS file";
83 }
84 else
85 {
86 kError(399) << "kimgio EPS: supposed Postscript code of a MS-DOS EPS file doe not start with %!." << endl;
87 return false;
88 }
89 }
90 else
91 {
92 kError(399) << "kimgio EPS: wrong magic for potential MS-DOS EPS file!" << endl;
93 return false;
94 }
95 }
96 else
97 {
98 kError(399) << "kimgio EPS: not an EPS file!" << endl;
99 return false;
100 }
101 return true;
102}
103
104static bool bbox ( QIODevice *io, int *x1, int *y1, int *x2, int *y2)
105{
106 char buf[BUFLEN+1];
107
108 bool ret = false;
109
110 while (io->readLine(buf, BUFLEN) > 0)
111 {
112 if (strncmp (buf, BBOX, BBOX_LEN) == 0)
113 {
114 // Some EPS files have non-integer values for the bbox
115 // We don't support that currently, but at least we parse it
116 float _x1, _y1, _x2, _y2;
117 if ( sscanf (buf, "%*s %f %f %f %f",
118 &_x1, &_y1, &_x2, &_y2) == 4) {
119 kDebug(399) << "kimgio EPS BBOX: " << _x1 << " " << _y1 << " " << _x2 << " " << _y2;
120 *x1=(int)_x1; *y1=(int)_y1; *x2=(int)_x2; *y2=(int)_y2;
121 ret = true;
122 break;
123 }
124 }
125 }
126
127 return ret;
128}
129
130EPSHandler::EPSHandler()
131{
132}
133
134bool EPSHandler::canRead() const
135{
136 if (canRead(device())) {
137 setFormat("eps");
138 return true;
139 }
140 return false;
141}
142
143bool EPSHandler::read(QImage *image)
144{
145 kDebug(399) << "kimgio EPS: starting...";
146
147 FILE * ghostfd;
148 int x1, y1, x2, y2;
149 //QTime dt;
150 //dt.start();
151
152 QString cmdBuf;
153 QString tmp;
154
155 QIODevice* io = device();
156 quint32 ps_offset, ps_size;
157
158 // find start of PostScript code
159 if ( !seekToCodeStart(io, ps_offset, ps_size) )
160 return false;
161
162 // find bounding box
163 if ( !bbox (io, &x1, &y1, &x2, &y2)) {
164 kError(399) << "kimgio EPS: no bounding box found!" << endl;
165 return false;
166 }
167
168 QTemporaryFile tmpFile;
169 if( !tmpFile.open() ) {
170 kError(399) << "kimgio EPS: no temp file!" << endl;
171 return false;
172 }
173
174 // x1, y1 -> translation
175 // x2, y2 -> new size
176
177 x2 -= x1;
178 y2 -= y1;
179 //kDebug(399) << "origin point: " << x1 << "," << y1 << " size:" << x2 << "," << y2;
180 double xScale = 1.0;
181 double yScale = 1.0;
182 int wantedWidth = x2;
183 int wantedHeight = y2;
184
185 // create GS command line
186
187 cmdBuf = "gs -sOutputFile=";
188 cmdBuf += tmpFile.fileName();
189 cmdBuf += " -q -g";
190 tmp.setNum( wantedWidth );
191 cmdBuf += tmp;
192 tmp.setNum( wantedHeight );
193 cmdBuf += 'x';
194 cmdBuf += tmp;
195 cmdBuf += " -dSAFER -dPARANOIDSAFER -dNOPAUSE -sDEVICE=ppm -c "
196 "0 0 moveto "
197 "1000 0 lineto "
198 "1000 1000 lineto "
199 "0 1000 lineto "
200 "1 1 254 255 div setrgbcolor fill "
201 "0 0 0 setrgbcolor - -c showpage quit";
202
203 // run ghostview
204
205 ghostfd = popen (QFile::encodeName(cmdBuf), "w");
206
207 if ( ghostfd == 0 ) {
208 kError(399) << "kimgio EPS: no GhostScript?" << endl;
209 return false;
210 }
211
212 fprintf (ghostfd, "\n%d %d translate\n", -qRound(x1*xScale), -qRound(y1*yScale));
213
214 // write image to gs
215
216 io->reset(); // Go back to start of file to give all the file to GhostScript
217 if (ps_offset>0L) // We have an offset
218 io->seek(ps_offset);
219 QByteArray buffer ( io->readAll() );
220
221 // If we have no MS-DOS EPS file or if the size seems wrong, then choose the buffer size
222 if (ps_size<=0 || ps_size>(unsigned int)buffer.size())
223 ps_size=buffer.size();
224
225 fwrite(buffer.data(), sizeof(char), ps_size, ghostfd);
226 buffer.resize(0);
227
228 pclose ( ghostfd );
229
230 // load image
231 if( image->load (tmpFile.fileName()) ) {
232 kDebug(399) << "kimgio EPS: success!";
233 //kDebug(399) << "Loading EPS took " << (float)(dt.elapsed()) / 1000 << " seconds";
234 return true;
235 }
236
237 kError(399) << "kimgio EPS: no image!" << endl;
238 return false;
239}
240
241
242// Sven Wiegand <SWiegand@tfh-berlin.de> -- eps output filter (from KSnapshot)
243bool EPSHandler::write(const QImage &image)
244{
245 QPrinter psOut(QPrinter::PrinterResolution);
246 QPainter p;
247
248 // making some definitions (papersize, output to file, filename):
249 psOut.setCreator( "KDE " KDE_VERSION_STRING );
250 if ( psOut.outputFileName().isEmpty() )
251 psOut.setOutputFileName( "untitled_printer_document" );
252
253 // Extension must be .eps so that Qt generates EPS file
254 QTemporaryFile tmpFile("XXXXXXXX.eps");
255 if ( !tmpFile.open() )
256 return false;
257
258 psOut.setOutputFileName(tmpFile.fileName());
259 psOut.setOutputFormat(QPrinter::PostScriptFormat);
260 psOut.setFullPage(true);
261 psOut.setPaperSize(image.size(), QPrinter::DevicePixel);
262
263 // painting the pixmap to the "printer" which is a file
264 p.begin( &psOut );
265 p.drawImage( QPoint( 0, 0 ), image );
266 p.end();
267
268 // Copy file to imageio struct
269 QFile inFile(tmpFile.fileName());
270 if ( !inFile.open( QIODevice::ReadOnly ) )
271 return false;
272
273 QTextStream in( &inFile );
274 in.setCodec( "ISO-8859-1" );
275 QTextStream out( device() );
276 out.setCodec( "ISO-8859-1" );
277
278 QString szInLine = in.readLine();
279 out << szInLine << '\n';
280
281 while( !in.atEnd() ){
282 szInLine = in.readLine();
283 out << szInLine << '\n';
284 }
285
286 inFile.close();
287
288 return true;
289}
290
291QByteArray EPSHandler::name() const
292{
293 return "eps";
294}
295
296bool EPSHandler::canRead(QIODevice *device)
297{
298 if (!device) {
299 qWarning("EPSHandler::canRead() called with no device");
300 return false;
301 }
302
303 qint64 oldPos = device->pos();
304
305 QByteArray head = device->readLine(64);
306 int readBytes = head.size();
307 if (device->isSequential()) {
308 while (readBytes > 0)
309 device->ungetChar(head[readBytes-- - 1]);
310 } else {
311 device->seek(oldPos);
312 }
313
314 return head.contains("%!PS-Adobe");
315}
316
317class EPSPlugin : public QImageIOPlugin
318{
319public:
320 QStringList keys() const;
321 Capabilities capabilities(QIODevice *device, const QByteArray &format) const;
322 QImageIOHandler *create(QIODevice *device, const QByteArray &format = QByteArray()) const;
323};
324
325QStringList EPSPlugin::keys() const
326{
327 return QStringList() << "eps" << "EPS" << "epsi" << "EPSI" << "epsf" << "EPSF";
328}
329
330QImageIOPlugin::Capabilities EPSPlugin::capabilities(QIODevice *device, const QByteArray &format) const
331{
332 if (format == "eps" || format == "epsi" || format == "EPS" || format == "EPSI" ||
333 format == "epsf" || format == "EPSF")
334 return Capabilities(CanRead | CanWrite);
335 if (!format.isEmpty())
336 return 0;
337 if (!device->isOpen())
338 return 0;
339
340 Capabilities cap;
341 if (device->isReadable() && EPSHandler::canRead(device))
342 cap |= CanRead;
343 if (device->isWritable())
344 cap |= CanWrite;
345 return cap;
346}
347
348QImageIOHandler *EPSPlugin::create(QIODevice *device, const QByteArray &format) const
349{
350 QImageIOHandler *handler = new EPSHandler;
351 handler->setDevice(device);
352 handler->setFormat(format);
353 return handler;
354}
355
356Q_EXPORT_STATIC_PLUGIN(EPSPlugin)
357Q_EXPORT_PLUGIN2(eps, EPSPlugin)
358