vcl: add Graphic serialization (writing) to TypeSerializer + tests

Change-Id: I3c4845550e776c4c2c891d94db71bacea27c9a37
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/93328
Tested-by: Jenkins
Reviewed-by: Tomaž Vajngerl <quikee@gmail.com>
diff --git a/vcl/inc/TypeSerializer.hxx b/vcl/inc/TypeSerializer.hxx
index befd4ed..4ac7e06 100644
--- a/vcl/inc/TypeSerializer.hxx
+++ b/vcl/inc/TypeSerializer.hxx
@@ -24,6 +24,7 @@
#include <tools/GenericTypeSerializer.hxx>
#include <vcl/gradient.hxx>
#include <vcl/gfxlink.hxx>
#include <vcl/graph.hxx>

class VCL_DLLPUBLIC TypeSerializer : public tools::GenericTypeSerializer
{
@@ -35,6 +36,9 @@ public:

    void readGfxLink(GfxLink& rGfxLink);
    void writeGfxLink(const GfxLink& rGfxLink);

    static void readGraphic(Graphic& rGraphic);
    void writeGraphic(const Graphic& rGraphic);
};

#endif
diff --git a/vcl/qa/cppunit/TypeSerializerTest.cxx b/vcl/qa/cppunit/TypeSerializerTest.cxx
index e5d3a25..0c737a4 100644
--- a/vcl/qa/cppunit/TypeSerializerTest.cxx
+++ b/vcl/qa/cppunit/TypeSerializerTest.cxx
@@ -130,6 +130,43 @@ void TypeSerializerTest::testGraphic()
        CPPUNIT_ASSERT_EQUAL(std::string("c2bed2099ce617f1cc035701de5186f0d43e3064"),
                             toHexString(aHash));
    }

    // Test TypeSerializer - Native Format 5
    {
        SvMemoryStream aMemoryStream;
        aMemoryStream.SetVersion(SOFFICE_FILEFORMAT_50);
        aMemoryStream.SetCompressMode(SvStreamCompressFlags::NATIVE);
        {
            TypeSerializer aSerializer(aMemoryStream);
            aSerializer.writeGraphic(aGraphic);
        }
        aMemoryStream.Seek(STREAM_SEEK_TO_BEGIN);

        CPPUNIT_ASSERT_EQUAL(sal_uInt64(290), aMemoryStream.remainingSize());
        std::vector<unsigned char> aHash = calculateHash(aMemoryStream);
        CPPUNIT_ASSERT_EQUAL(std::string("ee55ab6faa73b61b68bc3d5628d95f0d3c528e2a"),
                             toHexString(aHash));

        aMemoryStream.Seek(STREAM_SEEK_TO_BEGIN);
        sal_uInt32 nType;
        aMemoryStream.ReadUInt32(nType);
        CPPUNIT_ASSERT_EQUAL(COMPAT_FORMAT('N', 'A', 'T', '5'), nType);
    }

    // Test TypeSerializer - Normal
    {
        SvMemoryStream aMemoryStream;
        {
            TypeSerializer aSerializer(aMemoryStream);
            aSerializer.writeGraphic(aGraphic);
        }
        aMemoryStream.Seek(STREAM_SEEK_TO_BEGIN);

        CPPUNIT_ASSERT_EQUAL(sal_uInt64(233), aMemoryStream.remainingSize());
        std::vector<unsigned char> aHash = calculateHash(aMemoryStream);
        CPPUNIT_ASSERT_EQUAL(std::string("c2bed2099ce617f1cc035701de5186f0d43e3064"),
                             toHexString(aHash));
    }
}

void TypeSerializerTest::testGraphic_Bitmap_NoGfxLink()
@@ -155,6 +192,26 @@ void TypeSerializerTest::testGraphic_Bitmap_NoGfxLink()
        aMemoryStream.ReadUInt16(nType);
        CPPUNIT_ASSERT_EQUAL(sal_uInt16(0x4D42), nType); // Magic written with WriteDIBBitmapEx
    }

    // Test TypeSerializer
    {
        SvMemoryStream aMemoryStream;
        {
            TypeSerializer aSerializer(aMemoryStream);
            aSerializer.writeGraphic(aGraphic);
        }
        aMemoryStream.Seek(STREAM_SEEK_TO_BEGIN);

        CPPUNIT_ASSERT_EQUAL(sal_uInt64(383), aMemoryStream.remainingSize());
        std::vector<unsigned char> aHash = calculateHash(aMemoryStream);
        CPPUNIT_ASSERT_EQUAL(std::string("da831418499146d51bf245fadf60b9111faa76c2"),
                             toHexString(aHash));

        aMemoryStream.Seek(STREAM_SEEK_TO_BEGIN);
        sal_uInt16 nType;
        aMemoryStream.ReadUInt16(nType);
        CPPUNIT_ASSERT_EQUAL(sal_uInt16(0x4D42), nType); // Magic written with WriteDIBBitmapEx
    }
}

} // namespace
diff --git a/vcl/source/gdi/TypeSerializer.cxx b/vcl/source/gdi/TypeSerializer.cxx
index ad2f140..8266c6a 100644
--- a/vcl/source/gdi/TypeSerializer.cxx
+++ b/vcl/source/gdi/TypeSerializer.cxx
@@ -20,6 +20,8 @@
#include <TypeSerializer.hxx>
#include <tools/vcompat.hxx>
#include <sal/log.hxx>
#include <comphelper/fileformat.h>
#include <vcl/gdimtf.hxx>

TypeSerializer::TypeSerializer(SvStream& rStream)
    : GenericTypeSerializer(rStream)
@@ -148,4 +150,119 @@ void TypeSerializer::writeGfxLink(const GfxLink& rGfxLink)
    }
}

namespace
{
constexpr sal_uInt32 constSvgMagic((sal_uInt32('s') << 24) | (sal_uInt32('v') << 16)
                                   | (sal_uInt32('g') << 8) | sal_uInt32('0'));
constexpr sal_uInt32 constWmfMagic((sal_uInt32('w') << 24) | (sal_uInt32('m') << 16)
                                   | (sal_uInt32('f') << 8) | sal_uInt32('0'));
constexpr sal_uInt32 constEmfMagic((sal_uInt32('e') << 24) | (sal_uInt32('m') << 16)
                                   | (sal_uInt32('f') << 8) | sal_uInt32('0'));
constexpr sal_uInt32 constPdfMagic((sal_uInt32('s') << 24) | (sal_uInt32('v') << 16)
                                   | (sal_uInt32('g') << 8) | sal_uInt32('0'));

#define NATIVE_FORMAT_50 COMPAT_FORMAT('N', 'A', 'T', '5')

} // end anonymous namespace

void TypeSerializer::readGraphic(Graphic& /*rGraphic*/) {}

void TypeSerializer::writeGraphic(const Graphic& rGraphic)
{
    Graphic aGraphic(rGraphic);

    if (!aGraphic.makeAvailable())
        return;

    auto pGfxLink = aGraphic.GetSharedGfxLink();

    if (mrStream.GetVersion() >= SOFFICE_FILEFORMAT_50
        && (mrStream.GetCompressMode() & SvStreamCompressFlags::NATIVE) && pGfxLink
        && pGfxLink->IsNative())
    {
        // native format
        mrStream.WriteUInt32(NATIVE_FORMAT_50);

        // write compat info, destructor writes stuff into the header
        {
            VersionCompat aCompat(mrStream, StreamMode::WRITE, 1);
        }
        pGfxLink->SetPrefMapMode(aGraphic.GetPrefMapMode());
        pGfxLink->SetPrefSize(aGraphic.GetPrefSize());
        writeGfxLink(*pGfxLink);
    }
    else
    {
        // own format
        const SvStreamEndian nOldFormat = mrStream.GetEndian();
        mrStream.SetEndian(SvStreamEndian::LITTLE);

        switch (aGraphic.GetType())
        {
            case GraphicType::NONE:
            case GraphicType::Default:
                break;

            case GraphicType::Bitmap:
            {
                auto pVectorGraphicData = aGraphic.getVectorGraphicData();
                if (pVectorGraphicData)
                {
                    // stream out Vector Graphic defining data (length, byte array and evtl. path)
                    // this is used e.g. in swapping out graphic data and in transporting it over UNO API
                    // as sequence of bytes, but AFAIK not written anywhere to any kind of file, so it should be
                    // no problem to extend it; only used at runtime
                    switch (pVectorGraphicData->getVectorGraphicDataType())
                    {
                        case VectorGraphicDataType::Wmf:
                        {
                            mrStream.WriteUInt32(constWmfMagic);
                            break;
                        }
                        case VectorGraphicDataType::Emf:
                        {
                            mrStream.WriteUInt32(constEmfMagic);
                            break;
                        }
                        case VectorGraphicDataType::Svg:
                        {
                            mrStream.WriteUInt32(constSvgMagic);
                            break;
                        }
                        case VectorGraphicDataType::Pdf:
                        {
                            mrStream.WriteUInt32(constPdfMagic);
                            break;
                        }
                    }

                    sal_uInt32 nSize = pVectorGraphicData->getVectorGraphicDataArrayLength();
                    mrStream.WriteUInt32(nSize);
                    mrStream.WriteBytes(
                        pVectorGraphicData->getVectorGraphicDataArray().getConstArray(), nSize);
                    mrStream.WriteUniOrByteString(pVectorGraphicData->getPath(),
                                                  mrStream.GetStreamCharSet());
                }
                else if (aGraphic.IsAnimated())
                {
                    WriteAnimation(mrStream, aGraphic.GetAnimation());
                }
                else
                {
                    WriteDIBBitmapEx(aGraphic.GetBitmapEx(), mrStream);
                }
            }
            break;

            default:
            {
                if (aGraphic.IsSupportedGraphic())
                    WriteGDIMetaFile(mrStream, rGraphic.GetGDIMetaFile());
            }
            break;
        }
        mrStream.SetEndian(nOldFormat);
    }
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */