You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
448 lines
15 KiB
448 lines
15 KiB
/* This file is part of the KDE project
|
|
Copyright (c) 2001 Simon Hausmann <hausmann@kde.org>
|
|
Copyright (C) 2002, 2003, 2004 Nicolas GOUTTE <goutte@kde.org>
|
|
|
|
This library is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU Library General Public
|
|
License as published by the Free Software Foundation; either
|
|
version 2 of the License, or (at your option) any later version.
|
|
|
|
This library is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
Library General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Library General Public License
|
|
along with this library; see the file COPYING.LIB. If not, write to
|
|
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
|
* Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#include <unistd.h>
|
|
#include <stdio.h>
|
|
|
|
#include <tqbuffer.h>
|
|
#include <tqpainter.h>
|
|
#include <tqpaintdevicemetrics.h>
|
|
#include <tqfile.h>
|
|
#include <tqtextstream.h>
|
|
#include <tqregexp.h>
|
|
#include <tqimage.h>
|
|
#include <tqpixmap.h>
|
|
#include <tqapplication.h>
|
|
#include <tqdragobject.h>
|
|
|
|
#include <kglobal.h>
|
|
#include <kdebug.h>
|
|
#include <kdeversion.h>
|
|
#if ! KDE_IS_VERSION( 3,1,90 )
|
|
#include <kdebugclasses.h>
|
|
#endif
|
|
#include <ktempfile.h>
|
|
#include <kprocess.h>
|
|
|
|
#include "KoPictureKey.h"
|
|
#include "KoPictureBase.h"
|
|
#include "KoPictureEps.h"
|
|
|
|
|
|
KoPictureEps::KoPictureEps(void) : m_psStreamStart(0), m_psStreamLength(0), m_cacheIsInFastMode(true)
|
|
{
|
|
// Forbid TQPixmap to cache the X-Window resources (Yes, it is slower!)
|
|
m_cachedPixmap.setOptimization(TQPixmap::MemoryOptim);
|
|
}
|
|
|
|
KoPictureEps::~KoPictureEps(void)
|
|
{
|
|
}
|
|
|
|
KoPictureBase* KoPictureEps::newCopy(void) const
|
|
{
|
|
return new KoPictureEps(*this);
|
|
}
|
|
|
|
KoPictureType::Type KoPictureEps::getType(void) const
|
|
{
|
|
return KoPictureType::TypeEps;
|
|
}
|
|
|
|
bool KoPictureEps::isNull(void) const
|
|
{
|
|
return m_rawData.isNull();
|
|
}
|
|
|
|
TQImage KoPictureEps::scaleWithGhostScript(const TQSize& size, const int resolutionx, const int resolutiony )
|
|
{
|
|
if (!m_boundingBox.width() || !m_boundingBox.height())
|
|
{
|
|
kdDebug(30003) << "EPS image has a null size! (in KoPictureEps::scaleWithGhostScript)" << endl;
|
|
return TQImage();
|
|
}
|
|
|
|
// ### 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)
|
|
// png16m is better, but not always available -> fallback to bmp16m, then fallback to ppm (256 colors)
|
|
// ### TODO: pcx24b is also a true colour format
|
|
// ### TODO: support alpha (other gs devices needed)
|
|
|
|
const char* deviceTable[] = { "png16m", "bmp16m", "ppm", 0 };
|
|
|
|
TQImage img;
|
|
|
|
for ( int i = 0; deviceTable[i]; ++i)
|
|
{
|
|
if ( tryScaleWithGhostScript( img, size, resolutionx, resolutiony, deviceTable[i] ) != -1 )
|
|
{
|
|
return img;
|
|
}
|
|
|
|
}
|
|
|
|
kdError(30003) << "Image from GhostScript cannot be loaded (in KoPictureEps::scaleWithGhostScript)" << endl;
|
|
return img;
|
|
}
|
|
|
|
// Helper method for scaleWithGhostScript. Returns 1 on success, 0 on error, -1 if nothing generated
|
|
// (in which case another 'output device' can be tried)
|
|
int KoPictureEps::tryScaleWithGhostScript(TQImage &image, const TQSize& size, const int resolutionx, const int resolutiony, const char* device )
|
|
// Based on the code of the file kdelibs/kimgio/eps.cpp
|
|
{
|
|
kdDebug(30003) << "Sampling with GhostScript, using device \"" << device << "\" (in KoPictureEps::tryScaleWithGhostScript)" << endl;
|
|
|
|
KTempFile tmpFile;
|
|
tmpFile.setAutoDelete(true);
|
|
|
|
if ( tmpFile.status() )
|
|
{
|
|
kdError(30003) << "No KTempFile! (in KoPictureEps::tryScaleWithGhostScript)" << endl;
|
|
return 0; // error
|
|
}
|
|
|
|
const int wantedWidth = size.width();
|
|
const int wantedHeight = size.height();
|
|
const double xScale = double(size.width()) / double(m_boundingBox.width());
|
|
const double yScale = double(size.height()) / double(m_boundingBox.height());
|
|
|
|
// create GS command line
|
|
|
|
TQString cmdBuf ( "gs -sOutputFile=" );
|
|
cmdBuf += KProcess::quote(tmpFile.name());
|
|
cmdBuf += " -q -g";
|
|
cmdBuf += TQString::number( wantedWidth );
|
|
cmdBuf += "x";
|
|
cmdBuf += TQString::number( wantedHeight );
|
|
|
|
if ( ( resolutionx > 0) && ( resolutiony > 0) )
|
|
{
|
|
#if 0
|
|
// Do not play with resolution for now.
|
|
// It brings more problems at print than solutions
|
|
cmdBuf += " -r";
|
|
cmdBuf += TQString::number( resolutionx );
|
|
cmdBuf += "x";
|
|
cmdBuf += TQString::number( resolutiony );
|
|
#endif
|
|
}
|
|
|
|
cmdBuf += " -dSAFER -dPARANOIDSAFER -dNOPAUSE -sDEVICE=";
|
|
cmdBuf += device;
|
|
//cmdBuf += " -c 255 255 255 setrgbcolor fill 0 0 0 setrgbcolor";
|
|
cmdBuf += " -";
|
|
cmdBuf += " -c showpage quit";
|
|
|
|
// run ghostview
|
|
|
|
FILE* ghostfd = popen (TQFile::encodeName(cmdBuf), "w");
|
|
|
|
if ( ghostfd == 0 )
|
|
{
|
|
kdError(30003) << "No connection to GhostScript (in KoPictureEps::tryScaleWithGhostScript)" << endl;
|
|
return 0; // error
|
|
}
|
|
|
|
// The translation is needed as GhostScript (7.07) cannot handle negative values in the bounding box otherwise.
|
|
fprintf (ghostfd, "\n%d %d translate\n", -tqRound(m_boundingBox.left()*xScale), -tqRound(m_boundingBox.top()*yScale));
|
|
fprintf (ghostfd, "%g %g scale\n", xScale, yScale);
|
|
|
|
// write image to gs
|
|
|
|
fwrite( m_rawData.data() + m_psStreamStart, sizeof(char), m_psStreamLength, ghostfd);
|
|
|
|
pclose ( ghostfd );
|
|
|
|
// load image
|
|
if( !image.load (tmpFile.name()) )
|
|
{
|
|
// It failed - maybe the device isn't supported by gs
|
|
return -1;
|
|
}
|
|
if ( image.size() != size ) // this can happen due to rounding problems
|
|
{
|
|
//kdDebug(30003) << "fixing size to " << size.width() << "x" << size.height()
|
|
// << " (was " << image.width() << "x" << image.height() << ")" << endl;
|
|
image = image.scale( size ); // hmm, smoothScale instead?
|
|
}
|
|
kdDebug(30003) << "Image parameters: " << image.width() << "x" << image.height() << "x" << image.depth() << endl;
|
|
return 1; // success
|
|
}
|
|
|
|
void KoPictureEps::scaleAndCreatePixmap(const TQSize& size, bool fastMode, const int resolutionx, const int resolutiony )
|
|
{
|
|
kdDebug(30003) << "KoPictureEps::scaleAndCreatePixmap " << size << " " << (fastMode?TQString("fast"):TQString("slow"))
|
|
<< " resolutionx: " << resolutionx << " resolutiony: " << resolutiony << endl;
|
|
if ((size==m_cachedSize)
|
|
&& ((fastMode) || (!m_cacheIsInFastMode)))
|
|
{
|
|
// The cached pixmap has already the right size
|
|
// and:
|
|
// - we are in fast mode (We do not care if the re-size was done slowly previously)
|
|
// - the re-size was already done in slow mode
|
|
kdDebug(30003) << "Already cached!" << endl;
|
|
return;
|
|
}
|
|
|
|
// Slow mode can be very slow, especially at high zoom levels -> configurable
|
|
if ( !isSlowResizeModeAllowed() )
|
|
{
|
|
kdDebug(30003) << "User has disallowed slow mode!" << endl;
|
|
fastMode = true;
|
|
}
|
|
|
|
// We cannot use fast mode, if nothing was ever cached.
|
|
if ( fastMode && !m_cachedSize.isEmpty())
|
|
{
|
|
kdDebug(30003) << "Fast scaling!" << endl;
|
|
// Slower than caching a TQImage, but faster than re-sampling!
|
|
TQImage image( m_cachedPixmap.convertToImage() );
|
|
m_cachedPixmap=image.scale( size );
|
|
m_cacheIsInFastMode=true;
|
|
m_cachedSize=size;
|
|
}
|
|
else
|
|
{
|
|
TQTime time;
|
|
time.start();
|
|
|
|
TQApplication::setOverrideCursor( TQt::waitCursor );
|
|
m_cachedPixmap = scaleWithGhostScript( size, resolutionx, resolutiony );
|
|
TQApplication::restoreOverrideCursor();
|
|
m_cacheIsInFastMode=false;
|
|
m_cachedSize=size;
|
|
|
|
kdDebug(30003) << "Time: " << (time.elapsed()/1000.0) << " s" << endl;
|
|
}
|
|
kdDebug(30003) << "New size: " << size << endl;
|
|
}
|
|
|
|
void KoPictureEps::draw(TQPainter& painter, int x, int y, int width, int height, int sx, int sy, int sw, int sh, bool fastMode)
|
|
{
|
|
if ( !width || !height )
|
|
return;
|
|
|
|
TQSize screenSize( width, height );
|
|
//kdDebug() << "KoPictureEps::draw screenSize=" << screenSize.width() << "x" << screenSize.height() << endl;
|
|
|
|
TQPaintDeviceMetrics metrics (painter.device());
|
|
kdDebug(30003) << "Metrics: X: " << metrics.logicalDpiX() << " x Y: " << metrics.logicalDpiX() << " (in KoPictureEps::draw)" << endl;
|
|
|
|
if ( painter.tqdevice()->isExtDev() ) // Is it an external device (i.e. printer)
|
|
{
|
|
kdDebug(30003) << "Drawing for a printer (in KoPictureEps::draw)" << endl;
|
|
// For printing, always re-sample the image, as a printer has never the same resolution than a display.
|
|
TQImage image( scaleWithGhostScript( screenSize, metrics.logicalDpiX(), metrics.logicalDpiY() ) );
|
|
// sx,sy,sw,sh is meant to be used as a cliprect on the pixmap, but drawImage
|
|
// translates it to the (x,y) point -> we need (x+sx, y+sy).
|
|
painter.drawImage( x + sx, y + sy, image, sx, sy, sw, sh );
|
|
}
|
|
else // No, it is simply a display
|
|
{
|
|
scaleAndCreatePixmap(screenSize, fastMode, metrics.logicalDpiX(), metrics.logicalDpiY() );
|
|
|
|
// sx,sy,sw,sh is meant to be used as a cliprect on the pixmap, but drawPixmap
|
|
// translates it to the (x,y) point -> we need (x+sx, y+sy).
|
|
painter.drawPixmap( x + sx, y + sy, m_cachedPixmap, sx, sy, sw, sh );
|
|
}
|
|
}
|
|
|
|
bool KoPictureEps::extractPostScriptStream( void )
|
|
{
|
|
kdDebug(30003) << "KoPictureEps::extractPostScriptStream" << endl;
|
|
TQDataStream data( m_rawData, IO_ReadOnly );
|
|
data.setByteOrder( TQDataStream::LittleEndian );
|
|
TQ_UINT32 magic, offset, length;
|
|
data >> magic;
|
|
data >> offset;
|
|
data >> length;
|
|
if ( !length )
|
|
{
|
|
kdError(30003) << "Length of PS stream is zero!" << endl;
|
|
return false;
|
|
}
|
|
if ( offset+length>m_rawData.size() )
|
|
{
|
|
kdError(30003) << "Data stream of the EPSF file is longer than file: " << offset << "+" << length << ">" << m_rawData.size() << endl;
|
|
return false;
|
|
}
|
|
m_psStreamStart = offset;
|
|
m_psStreamLength = length;
|
|
return true;
|
|
}
|
|
|
|
TQString KoPictureEps::readLine( const TQByteArray& array, const uint start, const uint length, uint& pos, bool& lastCharWasCr )
|
|
{
|
|
TQString strLine;
|
|
const uint finish = kMin( start + length, (uint)array.size() );
|
|
for ( ; pos < finish; ++pos ) // We are starting at pos
|
|
{
|
|
const char ch = array[ pos ]; // Read one character
|
|
if ( ch == '\n' )
|
|
{
|
|
if ( lastCharWasCr )
|
|
{
|
|
// We have a line feed following a Carriage Return
|
|
// As the Carriage Return has already ended the previous line,
|
|
// discard this Line Feed.
|
|
lastCharWasCr = false;
|
|
}
|
|
else
|
|
{
|
|
// We have a normal Line Feed, therefore we end the line
|
|
break;
|
|
}
|
|
}
|
|
else if ( ch == '\r' )
|
|
{
|
|
// We have a Carriage Return, therefore we end the line
|
|
lastCharWasCr = true;
|
|
break;
|
|
}
|
|
else if ( ch == char(12) ) // Form Feed
|
|
{ // ### TODO: can a FF happen in PostScript?
|
|
// Ignore the form feed
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
strLine += ch;
|
|
lastCharWasCr = false;
|
|
}
|
|
}
|
|
return strLine;
|
|
}
|
|
|
|
|
|
bool KoPictureEps::loadData(const TQByteArray& array, const TQString& /* extension */ )
|
|
{
|
|
|
|
kdDebug(30003) << "KoPictureEps::load" << endl;
|
|
// First, read the raw data
|
|
m_rawData=array;
|
|
|
|
if (m_rawData.isNull())
|
|
{
|
|
kdError(30003) << "No data was loaded!" << endl;
|
|
return false;
|
|
}
|
|
|
|
if ( ( m_rawData[0]==char(0xc5) ) && ( m_rawData[1]==char(0xd0) )
|
|
&& ( m_rawData[2]==char(0xd3) ) && ( m_rawData[3]==char(0xc6) ) )
|
|
{
|
|
// We have a so-called "MS-DOS EPS file", we have to extract the PostScript stream
|
|
if (!extractPostScriptStream()) // Changes m_rawData
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
m_psStreamStart = 0;
|
|
m_psStreamLength = m_rawData.size();
|
|
}
|
|
|
|
TQString lineBox; // Line with the bounding box
|
|
bool lastWasCr = false; // Was the last character of the line a carriage return?
|
|
uint pos = m_psStreamStart; // We start to search the bounding box at the start of the PostScript stream
|
|
TQString line( readLine( m_rawData, m_psStreamStart, m_psStreamLength, pos, lastWasCr ) );
|
|
kdDebug(30003) << "Header: " << line << endl;
|
|
if (!line.startsWith("%!"))
|
|
{
|
|
kdError(30003) << "Not a PostScript file!" << endl;
|
|
return false;
|
|
}
|
|
TQRect rect;
|
|
bool lineIsBoundingBox = false; // Does "line" has a %%BoundingBox line?
|
|
for(;;)
|
|
{
|
|
++pos; // Get over the previous line end (CR or LF)
|
|
line = readLine( m_rawData, m_psStreamStart, m_psStreamLength, pos, lastWasCr );
|
|
kdDebug(30003) << "Checking line: " << line << endl;
|
|
// ### TODO: it seems that the bounding box can be delayed with "(atend)" in the trailer (GhostScript 7.07 does not support it either.)
|
|
if (line.startsWith("%%BoundingBox:"))
|
|
{
|
|
lineIsBoundingBox = true;
|
|
break;
|
|
}
|
|
// ### TODO: also abort on %%EndComments
|
|
// ### TODO: %? , where ? is non-white-space printable, does not end the comment!
|
|
else if (!line.startsWith("%%"))
|
|
break; // Not a EPS comment anymore, so abort as we are not in the EPS header anymore
|
|
}
|
|
if ( !lineIsBoundingBox )
|
|
{
|
|
kdError(30003) << "KoPictureEps::load: could not find a bounding box!" << endl;
|
|
return false;
|
|
}
|
|
// Floating point values are not allowed in a Bounding Box, but ther are many such files out there...
|
|
TQRegExp exp("(\\-?[0-9]+\\.?[0-9]*)\\s(\\-?[0-9]+\\.?[0-9]*)\\s(\\-?[0-9]+\\.?[0-9]*)\\s(\\-?[0-9]+\\.?[0-9]*)");
|
|
if ( exp.search(line) == -1 )
|
|
{
|
|
// ### TODO: it might be an "(atend)" and the bounding box is in the trailer
|
|
// (but GhostScript 7.07 does not support a bounding box in the trailer.)
|
|
// Note: in Trailer, it is the last BoundingBox that counts not the first!
|
|
kdError(30003) << "Not standard bounding box: " << line << endl;
|
|
return false;
|
|
}
|
|
kdDebug(30003) << "Reg. Exp. Found: " << exp.tqcapturedTexts() << endl;
|
|
rect.setLeft((int)exp.cap(1).toDouble());
|
|
rect.setTop((int)exp.cap(2).toDouble());
|
|
rect.setRight((int)exp.cap(3).toDouble());
|
|
rect.setBottom((int)exp.cap(4).toDouble());
|
|
m_boundingBox=rect;
|
|
m_originalSize=rect.size();
|
|
kdDebug(30003) << "Rect: " << rect << " Size: " << m_originalSize << endl;
|
|
return true;
|
|
}
|
|
|
|
bool KoPictureEps::save(TQIODevice* io) const
|
|
{
|
|
// We save the raw data, to avoid damaging the file by many load/save cycles
|
|
TQ_ULONG size=io->writeBlock(m_rawData); // WARNING: writeBlock returns TQ_LONG but size() TQ_ULONG!
|
|
return (size==m_rawData.size());
|
|
}
|
|
|
|
TQSize KoPictureEps::getOriginalSize(void) const
|
|
{
|
|
return m_originalSize;
|
|
}
|
|
|
|
TQPixmap KoPictureEps::generatePixmap(const TQSize& size, bool smoothScale)
|
|
{
|
|
scaleAndCreatePixmap(size,!smoothScale, 0, 0);
|
|
return m_cachedPixmap;
|
|
}
|
|
|
|
TQString KoPictureEps::getMimeType(const TQString&) const
|
|
{
|
|
return "image/x-eps";
|
|
}
|
|
|
|
TQImage KoPictureEps::generateImage(const TQSize& size)
|
|
{
|
|
// 0, 0 == resolution unknown
|
|
return scaleWithGhostScript(size, 0, 0);
|
|
}
|
|
|
|
void KoPictureEps::clearCache(void)
|
|
{
|
|
m_cachedPixmap.resize(0, 0);
|
|
m_cacheIsInFastMode=true;
|
|
m_cachedSize=TQSize();
|
|
}
|