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 }