File indexing completed on 2024-05-12 16:35:04
0001 /* This file is part of the KDE project 0002 * 0003 * Copyright (C) 2009 - 2011 Inge Wallin <inge@lysator.liu.se> 0004 * Copyright (C) 2011 Boudewijn Rempt <boud@valdyas.org> 0005 * 0006 * This library is free software; you can redistribute it and/or 0007 * modify it under the terms of the GNU Library General Public 0008 * License as published by the Free Software Foundation; either 0009 * version 2 of the License, or (at your option) any later version. 0010 * 0011 * This library is distributed in the hope that it will be useful, 0012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0014 * Library General Public License for more details. 0015 * 0016 * You should have received a copy of the GNU Library General Public License 0017 * along with this library; see the file COPYING.LIB. If not, write to 0018 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 0019 * Boston, MA 02110-1301, USA. 0020 */ 0021 0022 0023 // Own 0024 #include "VectorShape.h" 0025 0026 // Posix 0027 #include <math.h> 0028 0029 // Qt 0030 #include <QFontDatabase> 0031 #include <QPen> 0032 #include <QPainter> 0033 #include <QBuffer> 0034 #include <QDataStream> 0035 #include <QMutexLocker> 0036 #include <QThreadPool> 0037 #include <QSvgRenderer> 0038 0039 // Calligra 0040 #include "KoUnit.h" 0041 #include "KoStore.h" 0042 #include "KoXmlNS.h" 0043 #include "KoXmlReader.h" 0044 #include "KoXmlWriter.h" 0045 #include <KoEmbeddedDocumentSaver.h> 0046 #include <KoShapeLoadingContext.h> 0047 #include <KoOdfLoadingContext.h> 0048 #include <KoShapeSavingContext.h> 0049 #include <KoViewConverter.h> 0050 0051 // Wmf support 0052 #include "WmfPainterBackend.h" 0053 0054 // Vector shape 0055 #include "VectorDebug.h" 0056 #include "EmfParser.h" 0057 #include "EmfOutputPainterStrategy.h" 0058 #include "EmfOutputDebugStrategy.h" 0059 #include "SvmParser.h" 0060 #include "SvmPainterBackend.h" 0061 0062 // Comment out to get uncached painting, which is good for debugging 0063 //#define VECTORSHAPE_PAINT_UNCACHED 0064 0065 // Comment out to get unthreaded painting, which is good for debugging 0066 //#define VECTORSHAPE_PAINT_UNTHREADED 0067 0068 VectorShape::VectorShape() 0069 : KoFrameShape( KoXmlNS::draw, "image" ) 0070 , m_type(VectorTypeNone) 0071 , m_isRendering(false) 0072 { 0073 setShapeId(VectorShape_SHAPEID); 0074 // Default size of the shape. 0075 KoShape::setSize( QSizeF( CM_TO_POINT( 8 ), CM_TO_POINT( 5 ) ) ); 0076 m_cache.setMaxCost(3); 0077 } 0078 0079 VectorShape::~VectorShape() 0080 { 0081 // Wait for the render-thread to finish before the shape is allowed to be 0082 // destroyed so we can make sure to prevent crashes or unwanted 0083 // side-effects. Maybe as alternate we could just kill the render-thread... 0084 QMutexLocker locker(&m_mutex); 0085 } 0086 0087 // Methods specific to the vector shape. 0088 QByteArray VectorShape::compressedContents() const 0089 { 0090 return m_contents; 0091 } 0092 0093 VectorShape::VectorType VectorShape::vectorType() const 0094 { 0095 return m_type; 0096 } 0097 0098 void VectorShape::setCompressedContents(const QByteArray &newContents, VectorType vectorType) 0099 { 0100 QMutexLocker locker(&m_mutex); 0101 0102 m_contents = newContents; 0103 m_type = vectorType; 0104 m_cache.clear(); 0105 update(); 0106 } 0107 0108 // ---------------------------------------------------------------- 0109 // Painting 0110 0111 RenderThread::RenderThread(const QByteArray &contents, VectorShape::VectorType type, 0112 const QSizeF &size, const QSize &boundingSize, qreal zoomX, qreal zoomY) 0113 : QObject(), QRunnable(), 0114 m_contents(contents), m_type(type), 0115 m_size(size), m_boundingSize(boundingSize), m_zoomX(zoomX), m_zoomY(zoomY) 0116 { 0117 setAutoDelete(true); 0118 } 0119 0120 RenderThread::~RenderThread() 0121 { 0122 } 0123 0124 void RenderThread::run() 0125 { 0126 QImage *image = new QImage(m_boundingSize, QImage::Format_ARGB32); 0127 image->fill(0); 0128 QPainter painter; 0129 if (!painter.begin(image)) { 0130 warnVector << "Failed to create image-cache"; 0131 delete image; 0132 image = 0; 0133 } else { 0134 painter.scale(m_zoomX, m_zoomY); 0135 draw(painter); 0136 painter.end(); 0137 } 0138 emit finished(m_boundingSize, image); 0139 } 0140 0141 void RenderThread::draw(QPainter &painter) 0142 { 0143 // If the data is uninitialized, e.g. because loading failed, draw the null shape. 0144 if (m_contents.isEmpty()) { 0145 drawNull(painter); 0146 return; 0147 } 0148 0149 // Actually draw the contents 0150 switch (m_type) { 0151 case VectorShape::VectorTypeWmf: 0152 drawWmf(painter); 0153 break; 0154 case VectorShape::VectorTypeEmf: 0155 drawEmf(painter); 0156 break; 0157 case VectorShape::VectorTypeSvm: 0158 drawSvm(painter); 0159 break; 0160 case VectorShape::VectorTypeSvg: 0161 drawSvg(painter); 0162 break; 0163 case VectorShape::VectorTypeNone: 0164 default: 0165 drawNull(painter); 0166 } 0167 } 0168 0169 void RenderThread::drawNull(QPainter &painter) const 0170 { 0171 QRectF rect(QPointF(0,0), m_size); 0172 painter.save(); 0173 0174 // Draw a simple cross in a rectangle just to indicate that there is something here. 0175 painter.setPen(QPen(QColor(172, 196, 206), 0)); 0176 painter.drawRect(rect); 0177 painter.drawLine(rect.topLeft(), rect.bottomRight()); 0178 painter.drawLine(rect.bottomLeft(), rect.topRight()); 0179 0180 painter.restore(); 0181 } 0182 0183 void RenderThread::drawWmf(QPainter &painter) const 0184 { 0185 Libwmf::WmfPainterBackend wmfPainter(&painter, m_size); 0186 if (!wmfPainter.load(m_contents)) { 0187 drawNull(painter); 0188 return; 0189 } 0190 painter.save(); 0191 // Actually paint the WMF. 0192 wmfPainter.play(); 0193 painter.restore(); 0194 } 0195 0196 void RenderThread::drawEmf(QPainter &painter) const 0197 { 0198 // FIXME: Make emfOutput use QSizeF 0199 QSize shapeSizeInt( m_size.width(), m_size.height() ); 0200 //debugVector << "-------------------------------------------"; 0201 //debugVector << "size: " << shapeSizeInt << m_size; 0202 //debugVector << "position: " << position(); 0203 //debugVector << "-------------------------------------------"; 0204 0205 Libemf::Parser emfParser; 0206 0207 #ifndef LIBEMF_DEBUG 0208 // Create a new painter output strategy. Last param = true means keep aspect ratio. 0209 Libemf::OutputPainterStrategy emfPaintOutput( painter, shapeSizeInt, true ); 0210 emfParser.setOutput( &emfPaintOutput ); 0211 #else 0212 Libemf::OutputDebugStrategy emfDebugOutput; 0213 emfParser.setOutput( &emfDebugOutput ); 0214 #endif 0215 emfParser.load(m_contents); 0216 } 0217 0218 void RenderThread::drawSvm(QPainter &painter) const 0219 { 0220 QSize shapeSizeInt( m_size.width(), m_size.height() ); 0221 0222 Libsvm::SvmParser svmParser; 0223 0224 // Create a new painter backend. 0225 Libsvm::SvmPainterBackend svmPaintOutput(&painter, shapeSizeInt); 0226 svmParser.setBackend(&svmPaintOutput); 0227 svmParser.parse(m_contents); 0228 } 0229 0230 void RenderThread::drawSvg(QPainter &painter) const 0231 { 0232 QSvgRenderer renderer(m_contents); 0233 renderer.render(&painter, QRectF(0, 0, m_size.width(), m_size.height())); 0234 } 0235 0236 void VectorShape::paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &) 0237 { 0238 #ifdef VECTORSHAPE_PAINT_UNCACHED 0239 bool useCache = false; 0240 #else 0241 bool useCache = true; 0242 #endif 0243 0244 #ifdef VECTORSHAPE_PAINT_UNTHREADED 0245 bool asynchronous = false; 0246 #else 0247 // Since the backends may use QPainter::drawText we need to make sure to only 0248 // use threads if the font-backend supports that what is in most cases. 0249 bool asynchronous = QFontDatabase::supportsThreadedFontRendering(); 0250 #endif 0251 0252 QImage *cache = render(converter, asynchronous, useCache); 0253 if (cache) { // paint cached image 0254 Q_ASSERT(!cache->isNull()); 0255 QVector<QRect> clipRects = painter.clipRegion().rects(); 0256 foreach (const QRect &rc, clipRects) { 0257 painter.drawImage(rc.topLeft(), *cache, rc); 0258 } 0259 } 0260 } 0261 0262 void VectorShape::renderFinished(const QSize &boundingSize, QImage *image) 0263 { 0264 if (image) { 0265 m_cache.insert(boundingSize.height(), image); 0266 update(); 0267 } 0268 m_isRendering = false; 0269 } 0270 0271 0272 // ---------------------------------------------------------------- 0273 // Loading and Saving 0274 0275 0276 void VectorShape::saveOdf(KoShapeSavingContext & context) const 0277 { 0278 QMutexLocker locker(&m_mutex); 0279 0280 KoEmbeddedDocumentSaver &fileSaver = context.embeddedSaver(); 0281 KoXmlWriter &xmlWriter = context.xmlWriter(); 0282 0283 QString fileName = fileSaver.getFilename("VectorImages/Image"); 0284 QByteArray mimeType; 0285 0286 switch (m_type) { 0287 case VectorTypeWmf: 0288 mimeType = "image/x-wmf"; 0289 break; 0290 case VectorTypeEmf: 0291 mimeType = "image/x-emf"; 0292 break; 0293 case VectorTypeSvm: 0294 mimeType = "image/x-svm"; // mimetype as used inside LO/AOO 0295 break; 0296 case VectorTypeSvg: 0297 mimeType = "image/svg+xml"; 0298 default: 0299 // FIXME: What here? 0300 mimeType = "application/x-what"; 0301 break; 0302 } 0303 0304 xmlWriter.startElement("draw:frame"); 0305 saveOdfAttributes(context, OdfAllAttributes); 0306 fileSaver.embedFile(xmlWriter, "draw:image", fileName, mimeType, qUncompress(m_contents)); 0307 xmlWriter.endElement(); // draw:frame 0308 } 0309 0310 bool VectorShape::loadOdf(const KoXmlElement & element, KoShapeLoadingContext &context) 0311 { 0312 //debugVector <<"Loading ODF frame in the vector shape. Element = " << element.tagName(); 0313 loadOdfAttributes(element, context, OdfAllAttributes); 0314 return loadOdfFrame(element, context); 0315 } 0316 0317 0318 inline static int read32(const char *buffer, const int offset) 0319 { 0320 // little endian 0321 int result = (int) buffer[offset]; 0322 result |= (int) buffer[offset+1] << 8; 0323 result |= (int) buffer[offset+2] << 16; 0324 result |= (int) buffer[offset+3] << 24; 0325 0326 return result; 0327 } 0328 0329 // Load the actual contents within the vector shape. 0330 bool VectorShape::loadOdfFrameElement(const KoXmlElement & element, 0331 KoShapeLoadingContext &context) 0332 { 0333 //debugVector <<"Loading ODF element: " << element.tagName(); 0334 QMutexLocker locker(&m_mutex); 0335 0336 // Get the reference to the vector file. If there is no href, then just return. 0337 const QString href = element.attribute("href"); 0338 if (href.isEmpty()) 0339 return false; 0340 0341 // Try to open the embedded file. 0342 KoStore *store = context.odfLoadingContext().store(); 0343 bool result = store->open(href); 0344 0345 if (!result) { 0346 return false; 0347 } 0348 0349 int size = store->size(); 0350 if (size < 88) { 0351 store->close(); 0352 return false; 0353 } 0354 0355 m_contents = store->read(size); 0356 store->close(); 0357 if (m_contents.count() < size) { 0358 debugVector << "Too few bytes read: " << m_contents.count() << " instead of " << size; 0359 return false; 0360 } 0361 0362 // Try to recognize the type. We should do this before the 0363 // compression below, because that's a semi-expensive operation. 0364 m_type = vectorType(m_contents); 0365 0366 // Return false if we didn't manage to identify the type. 0367 if (m_type == VectorTypeNone) 0368 return false; 0369 0370 // Compress for biiiig memory savings. 0371 m_contents = qCompress(m_contents); 0372 0373 return true; 0374 } 0375 0376 void VectorShape::waitUntilReady(const KoViewConverter &converter, bool asynchronous) const 0377 { 0378 render(converter, asynchronous, true); 0379 } 0380 0381 QImage* VectorShape::render(const KoViewConverter &converter, bool asynchronous, bool useCache) const 0382 { 0383 QRectF rect = converter.documentToView(boundingRect()); 0384 int id = rect.size().toSize().height(); 0385 QImage *cache = useCache ? m_cache[id] : 0; 0386 0387 if (!cache || cache->isNull()) { // recreate the cached image 0388 cache = 0; 0389 if (!m_isRendering) { 0390 m_isRendering = true; 0391 qreal zoomX, zoomY; 0392 converter.zoom(&zoomX, &zoomY); 0393 QMutexLocker locker(&m_mutex); 0394 const QByteArray uncompressedContents = 0395 m_type != VectorShape::VectorTypeNone ? qUncompress(m_contents) : QByteArray(); 0396 RenderThread *t = new RenderThread(uncompressedContents, m_type, size(), rect.size().toSize(), zoomX, zoomY); 0397 connect(t, SIGNAL(finished(QSize,QImage*)), this, SLOT(renderFinished(QSize,QImage*))); 0398 if (asynchronous) { // render and paint the image threaded 0399 QThreadPool::globalInstance()->start(t); 0400 } else { // non-threaded rendering and painting of the image 0401 t->run(); 0402 cache = m_cache[id]; 0403 } 0404 } 0405 } 0406 0407 return cache; 0408 } 0409 0410 VectorShape::VectorType VectorShape::vectorType(const QByteArray &newContents) 0411 { 0412 VectorType vectorType; 0413 0414 if (isWmf(newContents)) { 0415 vectorType = VectorShape::VectorTypeWmf; 0416 } else if (isEmf(newContents)) { 0417 vectorType = VectorShape::VectorTypeEmf; 0418 } else if (isSvm(newContents)) { 0419 vectorType = VectorShape::VectorTypeSvm; 0420 } else if (isSvg(newContents)) { 0421 vectorType = VectorShape::VectorTypeSvg; 0422 } else { 0423 vectorType = VectorShape::VectorTypeNone; 0424 } 0425 0426 return vectorType; 0427 } 0428 0429 bool VectorShape::isWmf(const QByteArray &bytes) 0430 { 0431 debugVector << "Check for WMF"; 0432 0433 const char *data = bytes.constData(); 0434 const int size = bytes.count(); 0435 0436 if (size < 10) 0437 return false; 0438 0439 // This is how the 'file' command identifies a WMF. 0440 if (data[0] == '\327' && data[1] == '\315' && data[2] == '\306' && data[3] == '\232') 0441 { 0442 // FIXME: Is this a compressed wmf? Check it up. 0443 debugVector << "WMF identified: header 1"; 0444 return true; 0445 } 0446 0447 if (data[0] == '\002' && data[1] == '\000' && data[2] == '\011' && data[3] == '\000') 0448 { 0449 debugVector << "WMF identified: header 2"; 0450 return true; 0451 } 0452 0453 if (data[0] == '\001' && data[1] == '\000' && data[2] == '\011' && data[3] == '\000') 0454 { 0455 debugVector << "WMF identified: header 3"; 0456 return true; 0457 } 0458 0459 return false; 0460 } 0461 0462 bool VectorShape::isEmf(const QByteArray &bytes) 0463 { 0464 debugVector << "Check for EMF"; 0465 0466 const char *data = bytes.constData(); 0467 const int size = bytes.count(); 0468 0469 // This is how the 'file' command identifies an EMF. 0470 // 1. Check type 0471 qint32 mark = read32(data, 0); 0472 if (mark != 0x00000001) { 0473 //debugVector << "Not an EMF: mark = " << mark << " instead of 0x00000001"; 0474 return false; 0475 } 0476 0477 // 2. An EMF has the string " EMF" at the start + offset 40. 0478 if (size > 44 0479 && data[40] == ' ' && data[41] == 'E' && data[42] == 'M' && data[43] == 'F') 0480 { 0481 debugVector << "EMF identified"; 0482 return true; 0483 } 0484 0485 return false; 0486 } 0487 0488 bool VectorShape::isSvm(const QByteArray &bytes) 0489 { 0490 debugVector << "Check for SVM"; 0491 0492 // Check the SVM signature. 0493 if (bytes.startsWith("VCLMTF")) { 0494 debugVector << "SVM identified"; 0495 return true; 0496 } 0497 0498 return false; 0499 } 0500 0501 bool VectorShape::isSvg(const QByteArray &bytes) 0502 { 0503 debugVector << "Check for SVG"; 0504 return (bytes.contains("svg")); 0505 }