File indexing completed on 2025-01-12 10:34:21

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