File indexing completed on 2024-12-08 12:55:55

0001 /* This file is part of the KDE project
0002    Copyright (C) 2009 KO GmbH <jos.van.den.oever@kogmbh.com>
0003 
0004    This library is free software; you can redistribute it and/or
0005    modify it under the terms of the GNU Library General Public
0006    License as published by the Free Software Foundation; either
0007    version 2 of the License, or (at your option) any later version.
0008 
0009    This library is distributed in the hope that it will be useful,
0010    but WITHOUT ANY WARRANTY; without even the implied warranty of
0011    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0012    Library General Public License for more details.
0013 
0014    You should have received a copy of the GNU Library General Public License
0015    along with this library; see the file COPYING.LIB.  If not, write to
0016    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0017    Boston, MA 02110-1301, USA.
0018 */
0019 #include "pictures.h"
0020 
0021 #include <KoStore.h>
0022 #include <KoXmlWriter.h>
0023 
0024 #include <zlib.h>
0025 #include <cstdio>
0026 #include <iostream>
0027 #include <QDebug>
0028 #include <QImage>
0029 #include <QBuffer>
0030 
0031 //#define DEBUG_PICTURES
0032 
0033 // Use anonymous namespace to cover following functions
0034 namespace
0035 {
0036 
0037 static inline quint16 readU16(const void* p)
0038 {
0039     const unsigned char* ptr = (const unsigned char*) p;
0040     return ptr[0] + (ptr[1] << 8);
0041 }
0042 
0043 static inline quint32 readU32(const void* p)
0044 {
0045     const unsigned char* ptr = (const unsigned char*) p;
0046     return ptr[0] + (ptr[1] << 8) + (ptr[2] << 16) + (ptr[3] << 24);
0047 }
0048 
0049 void saveStream(POLE::Stream& stream, quint32 size, KoStore* out)
0050 {
0051     const quint16 bufferSize = 1024;
0052     unsigned char buffer[bufferSize];
0053     unsigned long nread = stream.read(buffer,
0054                                       (bufferSize < size) ? bufferSize : size);
0055     while (nread > 0) {
0056         out->write((char*)buffer, nread);
0057         size -= nread;
0058         nread = stream.read(buffer, (bufferSize < size) ? bufferSize : size);
0059     }
0060 }
0061 
0062 bool saveDecompressedStream(POLE::Stream& stream, quint32 size, KoStore* out)
0063 {
0064     const quint16 bufferSize = 1024;
0065     unsigned char bufin[bufferSize];
0066     unsigned char bufout[bufferSize];
0067 
0068     // initialize for decompressing ZLIB format
0069     z_stream_s zstream;
0070     zstream.zalloc = Z_NULL;
0071     zstream.zfree = Z_NULL;
0072     zstream.opaque = Z_NULL;
0073     zstream.avail_in = 0;
0074     zstream.next_in = Z_NULL;
0075     int r = inflateInit(&zstream);
0076     if (r != Z_OK) {
0077         inflateEnd(&zstream);
0078         return false;
0079     }
0080 
0081     unsigned long nread = stream.read(bufin,
0082                                       (bufferSize < size) ? bufferSize : size);
0083     while (nread > 0) { // loop over the available data
0084         size -= nread;
0085         zstream.next_in = (Bytef*)bufin;
0086         zstream.avail_in = nread;
0087         do { // loop until the data in bufin has all been decompressed
0088             zstream.next_out = (Bytef*)bufout;
0089             zstream.avail_out = bufferSize;
0090             int r = inflate(&zstream, Z_SYNC_FLUSH);
0091             qint32 nwritten = bufferSize - zstream.avail_out;
0092             if (r != Z_STREAM_END && r != Z_OK) {
0093                 inflateEnd(&zstream);
0094                 return false;
0095             }
0096             out->write((char*)bufout, nwritten);
0097             if (r == Z_STREAM_END) {
0098                 inflateEnd(&zstream);
0099                 return true; // successfully reached the end
0100             }
0101         } while (zstream.avail_in > 0);
0102         nread = stream.read(bufin, (bufferSize < size) ? bufferSize : size);
0103     }
0104 
0105     inflateEnd(&zstream);
0106     return false; // the stream was incomplete
0107 }
0108 
0109 const char* getMimetype(quint16 type)
0110 {
0111     switch (type) {
0112     case officeArtBlipEMF: return "image/x-emf";
0113     case officeArtBlipWMF: return "image/x-wmf";
0114     case officeArtBlipPICT: return "image/pict";
0115     case officeArtBlipJPEG: return "image/jpeg";
0116     case officeArtBlipPNG: return "image/png";
0117     case officeArtBlipDIB: return "application/octet-stream";
0118     case officeArtBlipTIFF: return "image/tiff";
0119     case officeArtBlipJPEG2: return "image/jpeg";
0120     }
0121     return "";
0122 }
0123 
0124 const char* getSuffix(quint16 type)
0125 {
0126     switch (type) {
0127     case officeArtBlipEMF: return ".emf";
0128     case officeArtBlipWMF: return ".wmf";
0129     case officeArtBlipPICT: return ".pict";
0130     case officeArtBlipJPEG: return ".jpg";
0131     case officeArtBlipPNG: return ".png";
0132     case officeArtBlipDIB: return ".dib";
0133     case officeArtBlipTIFF: return ".tiff";
0134     case officeArtBlipJPEG2: return ".jpg";
0135     }
0136     return "";
0137 }
0138 
0139 template<class T> void savePicture(PictureReference& ref, const T* a, KoStore* out)
0140 {
0141     if (!a) return;
0142     ref.uid = a->rgbUid1 + a->rgbUid2;
0143     ref.name.clear();
0144 
0145     QByteArray imagePixelBytes = a->BLIPFileData;
0146     if (a->rh.recType == officeArtBlipDIB) {
0147         // convert to QImage
0148         QImage image;
0149         bool result = dibToBmp(image, imagePixelBytes.data(), imagePixelBytes.size());
0150         if (!result) {
0151             return; // empty name reports an error
0152         }
0153 
0154         // writes image into ba in PNG format
0155         QByteArray ba;
0156         QBuffer buffer(&ba);
0157         buffer.open(QIODevice::WriteOnly);
0158         result = image.save(&buffer, "PNG");
0159         if (!result) {
0160             return; // empty name reports an error
0161         }
0162 
0163         imagePixelBytes = ba;
0164         // save as png
0165         ref.name = ref.uid.toHex() + getSuffix(officeArtBlipPNG);
0166         ref.mimetype = getMimetype(officeArtBlipPNG);
0167     } else {
0168         ref.name = ref.uid.toHex() + getSuffix(a->rh.recType);
0169         ref.mimetype = getMimetype(a->rh.recType);
0170     }
0171 
0172     if (!out->open(ref.name.toLocal8Bit())) {
0173         ref.name.clear();
0174         ref.uid.clear();
0175         return; // empty name reports an error
0176     }
0177 
0178     out->write(imagePixelBytes.data(), imagePixelBytes.size());
0179     out->close();
0180 }
0181 
0182 template<class T> void saveDecompressedPicture(PictureReference& ref, const T* a, KoStore* store)
0183 {
0184     if (!a) return;
0185 
0186     QByteArray buff = a->BLIPFileData;
0187     bool compressed = a->metafileHeader.compression == 0;
0188 
0189     if (compressed) {
0190         quint32 cbSize = a->metafileHeader.cbSize;
0191         char tmp[4];
0192 
0193         //big-endian byte order required
0194         tmp[3] = (cbSize & 0x000000ff);
0195         tmp[2] = ((cbSize >> 8) & 0x0000ff);
0196         tmp[1] = ((cbSize >> 16) & 0x00ff);
0197         tmp[0] = (cbSize >> 24);
0198         buff.prepend((char*) tmp, 4);
0199         buff = qUncompress(buff);
0200 
0201         if ((uint)buff.size() != cbSize) {
0202             qDebug() << "Warning: uncompressed size of the metafile differs";
0203         }
0204     }
0205     //reuse the savePicture code
0206     ref.uid = a->rgbUid1 + a->rgbUid2;
0207     ref.name = ref.uid.toHex() + getSuffix(a->rh.recType);
0208     if (!store->open(ref.name.toLocal8Bit())) {
0209         ref.name.clear();
0210         ref.uid.clear();
0211         return; // empty name reports an error
0212     }
0213     store->write(buff.data(), buff.size());
0214     ref.mimetype = getMimetype(a->rh.recType);
0215     store->close();
0216 }
0217 
0218 PictureReference savePicture(const MSO::OfficeArtBlip& a, KoStore* store)
0219 {
0220     PictureReference ref;
0221     store->setCompressionEnabled(true);
0222     // only one of these calls will actually save a picture
0223     saveDecompressedPicture(ref, a.anon.get<MSO::OfficeArtBlipEMF>(), store);
0224     saveDecompressedPicture(ref, a.anon.get<MSO::OfficeArtBlipWMF>(), store);
0225     saveDecompressedPicture(ref, a.anon.get<MSO::OfficeArtBlipPICT>(), store);
0226     // formats below are typically not very compressible
0227     store->setCompressionEnabled(false);
0228     savePicture(ref, a.anon.get<MSO::OfficeArtBlipJPEG>(), store);
0229     savePicture(ref, a.anon.get<MSO::OfficeArtBlipPNG>(), store);
0230     savePicture(ref, a.anon.get<MSO::OfficeArtBlipDIB>(), store);
0231     savePicture(ref, a.anon.get<MSO::OfficeArtBlipTIFF>(), store);
0232     return ref;
0233 }
0234 } //namespace
0235 
0236 PictureReference savePicture(POLE::Stream& stream, KoStore* out)
0237 {
0238     PictureReference ref;
0239     const quint16 bufferSize = 1024;
0240     unsigned char buffer[bufferSize];
0241     if (stream.read(buffer, 8) != 8) return ref;
0242 
0243     quint16 instance = readU16(buffer) >> 4;
0244     quint16 type = readU16(buffer + 2);
0245     quint32 size = readU32(buffer + 4);
0246 
0247     if (type == 0xF007) { // OfficeArtFBSE
0248         if (stream.read(buffer, 36) != 36) return ref;
0249         quint16 cbName = *(buffer + 33);
0250         if (cbName > bufferSize || stream.read(buffer, cbName) != cbName) {
0251             return ref;
0252         }
0253         size = size - 36 - cbName;
0254         // read embedded BLIP
0255         if (stream.read(buffer, 8) != 8) return ref;
0256         instance = readU16(buffer) >> 4;
0257         type = readU16(buffer + 2);
0258         size = readU32(buffer + 4);
0259     }
0260 
0261     // Image data is stored raw in the Pictures stream
0262     // The offset to the data differs per image type.
0263     quint16 offset;
0264     switch (type) {
0265     case officeArtBlipEMF: offset = (instance == 0x3D4) ? 50 : 66; break;
0266     case officeArtBlipWMF: offset = (instance == 0x216) ? 50 : 66; break;
0267     case officeArtBlipPICT: offset = (instance == 0x542) ? 50 : 66; break;
0268     case officeArtBlipJPEG: offset = (instance == 0x46A) ? 17 : 33; break;
0269     case officeArtBlipPNG: offset = (instance == 0x6E0) ? 17 : 33; break;
0270     case officeArtBlipDIB: offset = (instance == 0x7A8) ? 17 : 33; break;
0271     case officeArtBlipTIFF: offset = (instance == 0x6E4) ? 17 : 33; break;
0272     case officeArtBlipJPEG2: offset = (instance == 0x46A) ? 17 : 33; break;
0273     default: return ref;
0274     }
0275     const char* namesuffix = getSuffix(type);
0276     ref.mimetype = getMimetype(type);
0277 
0278     // skip offset
0279     if (offset != 0 && stream.read(buffer, offset) != offset) return ref;
0280     size -= offset;
0281 
0282     bool compressed = false;
0283     if (type == officeArtBlipEMF || type == officeArtBlipWMF || type == officeArtBlipPICT) {
0284         // read the compressed field from the OfficeArtMetafileHeader
0285         compressed = buffer[offset-2] == 0;
0286     }
0287     ref.uid = QByteArray((const char*)buffer, 16);
0288     ref.name = ref.uid.toHex() + namesuffix;
0289     if (!out->open(ref.name.toLocal8Bit())) {
0290         ref.name.clear();
0291         ref.uid.clear();
0292         return ref; // empty name reports an error
0293     }
0294     unsigned long next = stream.tell() + size;
0295     if (compressed) {
0296         saveDecompressedStream(stream, size, out);
0297     } else {
0298         saveStream(stream, size, out);
0299     }
0300     stream.seek(next);
0301     out->close();
0302 
0303     return ref;
0304 }
0305 
0306 PictureReference savePicture(const MSO::OfficeArtBStoreContainerFileBlock& a, KoStore* store)
0307 {
0308     const MSO::OfficeArtBlip* blip = a.anon.get<MSO::OfficeArtBlip>();
0309     const MSO::OfficeArtFBSE* fbse = a.anon.get<MSO::OfficeArtFBSE>();
0310     if (blip) {
0311         return savePicture(*blip, store);
0312     }
0313     if (fbse && fbse->embeddedBlip) {
0314         return savePicture(*fbse->embeddedBlip, store);
0315     }
0316     return PictureReference();
0317 }
0318 
0319 QByteArray getRgbUid(const MSO::OfficeArtDggContainer& dgg, quint32 pib, quint32& offset)
0320 {
0321     quint32 n = pib - 1;
0322     // return 16 byte rgbuid for this given blip id
0323     if (dgg.blipStore) {
0324         const MSO::OfficeArtBStoreContainer* b = dgg.blipStore.data();
0325         if (n < (quint32) b->rgfb.size() &&
0326             b->rgfb[n].anon.is<MSO::OfficeArtFBSE>())
0327         {
0328             const MSO::OfficeArtFBSE* fbse = b->rgfb[n].anon.get<MSO::OfficeArtFBSE>();
0329 #ifdef DEBUG_PICTURES
0330             qDebug() << "rgfb.size():" << b->rgfb.size();
0331             qDebug() << "pib:" << pib;
0332             qDebug() << "OfficeArtFBSE: DEBUG";
0333             qDebug() << "rgbUid:" << fbse->rgbUid.toHex();
0334             qDebug() << "tag:" << fbse->tag;
0335             qDebug() << "cRef:" << fbse->cRef;
0336             qDebug() << "foDelay:" << fbse->foDelay;
0337             qDebug() << "embeddeBlip:" << fbse->embeddedBlip;
0338 #endif
0339             offset = fbse->foDelay;
0340             return fbse->rgbUid;
0341         }
0342     }
0343     if (pib != 0xFFFF && pib != 0) {
0344 #ifdef DEBUG_PICTURES
0345         qDebug() << "Could not find image for pib " << pib;
0346 #endif
0347     }
0348     return QByteArray();
0349 }
0350 
0351 QMap<QByteArray, QString> createPictures(KoStore* store, KoXmlWriter* manifest, const QList<MSO::OfficeArtBStoreContainerFileBlock>* rgfb)
0352 {
0353     PictureReference ref;
0354     QMap<QByteArray, QString> fileNames;
0355 
0356     if (!rgfb) return fileNames;
0357 
0358     foreach (const MSO::OfficeArtBStoreContainerFileBlock& block, *rgfb) {
0359         ref = savePicture(block, store);
0360 
0361         if (ref.name.length() == 0) {
0362 #ifdef DEBUG_PICTURES
0363             qDebug() << "Empty picture reference, probably an empty slot";
0364 #endif
0365             continue;
0366         }
0367         //check if the MD4 digest is up2date
0368         if (block.anon.is<MSO::OfficeArtFBSE>()) {
0369             const MSO::OfficeArtFBSE* fbse = block.anon.get<MSO::OfficeArtFBSE>();
0370             if (fbse->rgbUid != ref.uid) {
0371                 ref.uid = fbse->rgbUid;
0372             }
0373         }
0374 
0375         if (manifest) {
0376             manifest->addManifestEntry("Pictures/" + ref.name, ref.mimetype);
0377         }
0378 
0379         fileNames[ref.uid] = ref.name;
0380     }
0381 #ifdef DEBUG_PICTURES
0382     qDebug() << "fileNames: DEBUG";
0383     QMap<QByteArray, QString>::const_iterator i = fileNames.constBegin();
0384     while (i != fileNames.constEnd()) {
0385         qDebug() << i.key().toHex() << ": " << i.value();
0386         ++i;
0387     }
0388 #endif
0389     return fileNames;
0390 }
0391 
0392 // NOTE: copied from qwmf.cc, I just changed WORD -> short and DWORD -> int,
0393 // removed some commented code, and changed the kWarning to qDebug here
0394 bool dibToBmp(QImage& bmp, const char* dib, long int size)
0395 {
0396     typedef struct _BMPFILEHEADER {
0397         short bmType;
0398         int bmSize;
0399         short bmReserved1;
0400         short bmReserved2;
0401         int bmOffBits;
0402     }  BMPFILEHEADER;
0403 
0404     int sizeBmp = size + 14;
0405     QByteArray pattern;       // BMP header and DIB data
0406     pattern.fill(0, sizeBmp);    //resize and fill
0407     pattern.insert(14, QByteArray::fromRawData(dib, size));
0408 
0409     // add BMP header
0410     BMPFILEHEADER* bmpHeader;
0411     bmpHeader = (BMPFILEHEADER*)((const char*)pattern);
0412     bmpHeader->bmType = 0x4D42;
0413     bmpHeader->bmSize = sizeBmp;
0414 
0415     if (!bmp.loadFromData((const uchar*)bmpHeader, pattern.size(), "BMP")) {
0416         qDebug() << "dibToBmp: invalid bitmap";
0417         return false;
0418     } else {
0419         return true;
0420     }
0421 }