tdf#108748 generate PDF preview on SwapIn

When including a PDF as an image, it's represented internally as
a Bitmap with additional PDF data. On SwapIn, LibreOffice just
imported the PDF data missing the PDF preview. The Graphic also
gad the wrong image type, which results in a busy loop on master,
with a strange / unhelpful STR_COMCORE_READERROR generated by
SwNoTextFrame::PaintPicture.

This is a workaround to generate the Bitmap on SwapIn, which
will really slow down LibreOffice when importing many PDFs.

I guess the job of generating the PDF previews should probably
be deferred to a thread or a low priority Scheduler task, just
like the general image loading is handled.

Change-Id: I8084e4533995ecddc5b03ef19cb0c6a2dbf60ebd
Reviewed-on: https://gerrit.libreoffice.org/43906
Tested-by: Jenkins <ci@libreoffice.org>
Reviewed-by: Jan-Marek Glogowski <glogow@fbihome.de>
diff --git a/include/tools/stream.hxx b/include/tools/stream.hxx
index 00aa872..64823f4 100644
--- a/include/tools/stream.hxx
+++ b/include/tools/stream.hxx
@@ -252,6 +252,7 @@ public:
    SvStream&       WriteOString(const OString& rStr)
                        { return WriteCharPtr(rStr.getStr()); }
    SvStream&       WriteStream( SvStream& rStream );
    sal_uInt64      WriteStream( SvStream& rStream, sal_uInt64 nSize );

    SvStream&       WriteBool( bool b )
                        { return WriteUChar(static_cast<unsigned char>(b)); }
diff --git a/tools/source/stream/stream.cxx b/tools/source/stream/stream.cxx
index 4f95afd..4c363dc 100644
--- a/tools/source/stream/stream.cxx
+++ b/tools/source/stream/stream.cxx
@@ -1179,6 +1179,27 @@ SvStream& SvStream::WriteStream( SvStream& rStream )
    return *this;
}

sal_uInt64 SvStream::WriteStream( SvStream& rStream, sal_uInt64 nSize )
{
    const sal_uInt32 cBufLen = 0x8000;
    std::unique_ptr<char[]> pBuf( new char[ cBufLen ] );
    sal_uInt32 nCurBufLen = cBufLen;
    sal_uInt32 nCount;
    sal_uInt64 nWriteSize = nSize;

    do {
        if ( nSize >= nCurBufLen )
            nWriteSize -= nCurBufLen;
        else
            nCurBufLen = nWriteSize;
        nCount = rStream.ReadBytes( pBuf.get(), nCurBufLen );
        WriteBytes( pBuf.get(), nCount );
    }
    while( nWriteSize && nCount == nCurBufLen );

    return nSize - nWriteSize;
}

OUString SvStream::ReadUniOrByteString( rtl_TextEncoding eSrcCharSet )
{
    // read UTF-16 string directly from stream ?
diff --git a/vcl/source/filter/ipdf/pdfread.hxx b/vcl/inc/pdfread.hxx
similarity index 73%
rename from vcl/source/filter/ipdf/pdfread.hxx
rename to vcl/inc/pdfread.hxx
index 2cb3abd..e873c49 100644
--- a/vcl/source/filter/ipdf/pdfread.hxx
+++ b/vcl/inc/pdfread.hxx
@@ -17,6 +17,10 @@ namespace vcl
{

/// Imports a PDF stream into rGraphic as a GDIMetaFile.
VCL_DLLPUBLIC bool ImportPDF(SvStream& rStream, Bitmap &rBitmap,
                             css::uno::Sequence<sal_Int8> &rPdfFata,
                             sal_uInt64 nPos = STREAM_SEEK_TO_BEGIN,
                             sal_uInt64 nSize = STREAM_SEEK_TO_END);
VCL_DLLPUBLIC bool ImportPDF(SvStream& rStream, Graphic& rGraphic);

}
diff --git a/vcl/source/filter/graphicfilter.cxx b/vcl/source/filter/graphicfilter.cxx
index 776ef71..062f5ee 100644
--- a/vcl/source/filter/graphicfilter.cxx
+++ b/vcl/source/filter/graphicfilter.cxx
@@ -44,7 +44,7 @@
#include <vcl/wmf.hxx>
#include <vcl/settings.hxx>
#include "igif/gifread.hxx"
#include "ipdf/pdfread.hxx"
#include <pdfread.hxx>
#include "jpeg/jpeg.hxx"
#include "ixbm/xbmread.hxx"
#include "ixpm/xpmread.hxx"
diff --git a/vcl/source/filter/ipdf/pdfread.cxx b/vcl/source/filter/ipdf/pdfread.cxx
index 254e2adf..59a7b1f 100644
--- a/vcl/source/filter/ipdf/pdfread.cxx
+++ b/vcl/source/filter/ipdf/pdfread.cxx
@@ -7,7 +7,7 @@
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#include "pdfread.hxx"
#include <pdfread.hxx>

#include <config_features.h>

@@ -56,7 +56,8 @@ double pointToPixel(double fPoint)
}

/// Does PDF to bitmap conversion using pdfium.
bool generatePreview(SvStream& rStream, Graphic& rGraphic)
bool generatePreview(SvStream& rStream, Bitmap& rBitmap,
                     sal_uInt64 nPos, sal_uInt64 nSize)
{
    FPDF_LIBRARY_CONFIG aConfig;
    aConfig.version = 2;
@@ -67,7 +68,8 @@ bool generatePreview(SvStream& rStream, Graphic& rGraphic)

    // Read input into a buffer.
    SvMemoryStream aInBuffer;
    aInBuffer.WriteStream(rStream);
    rStream.Seek(nPos);
    aInBuffer.WriteStream(rStream, nSize);

    // Load the buffer using pdfium.
    FPDF_DOCUMENT pPdfDocument = FPDF_LoadMemDocument(aInBuffer.GetData(), aInBuffer.GetSize(), /*password=*/nullptr);
@@ -103,7 +105,7 @@ bool generatePreview(SvStream& rStream, Graphic& rGraphic)
            pWriteAccess->CopyScanline(nRow, pPdfLine, ScanlineFormat::N32BitTcBgra, nStride);
        }
    }
    rGraphic = aBitmap;
    rBitmap = aBitmap;

    FPDFBitmap_Destroy(pPdfBitmap);
    FPDF_ClosePage(pPdfPage);
@@ -114,11 +116,14 @@ bool generatePreview(SvStream& rStream, Graphic& rGraphic)
}

/// Decide if PDF data is old enough to be compatible.
bool isCompatible(SvStream& rInStream)
bool isCompatible(SvStream& rInStream, sal_uInt64 nPos, sal_uInt64 nSize)
{
    if (nSize < 8)
        return false;

    // %PDF-x.y
    sal_uInt8 aFirstBytes[8];
    rInStream.Seek(STREAM_SEEK_TO_BEGIN);
    rInStream.Seek(nPos);
    sal_uLong nRead = rInStream.ReadBytes(aFirstBytes, 8);
    if (nRead < 8)
        return false;
@@ -133,13 +138,14 @@ bool isCompatible(SvStream& rInStream)

/// Takes care of transparently downgrading the version of the PDF stream in
/// case it's too new for our PDF export.
bool getCompatibleStream(SvStream& rInStream, SvStream& rOutStream)
bool getCompatibleStream(SvStream& rInStream, SvStream& rOutStream,
                         sal_uInt64 nPos, sal_uInt64 nSize)
{
    bool bCompatible = isCompatible(rInStream);
    rInStream.Seek(STREAM_SEEK_TO_BEGIN);
    bool bCompatible = isCompatible(rInStream, nPos, nSize);
    rInStream.Seek( nPos );
    if (bCompatible)
        // Not converting.
        rOutStream.WriteStream(rInStream);
        rOutStream.WriteStream(rInStream, nSize);
    else
    {
        // Downconvert to PDF-1.4.
@@ -152,7 +158,7 @@ bool getCompatibleStream(SvStream& rInStream, SvStream& rOutStream)

        // Read input into a buffer.
        SvMemoryStream aInBuffer;
        aInBuffer.WriteStream(rInStream);
        aInBuffer.WriteStream(rInStream, nSize);

        // Load the buffer using pdfium.
        FPDF_DOCUMENT pPdfDocument = FPDF_LoadMemDocument(aInBuffer.GetData(), aInBuffer.GetSize(), /*password=*/nullptr);
@@ -174,18 +180,22 @@ bool getCompatibleStream(SvStream& rInStream, SvStream& rOutStream)
    return rOutStream.good();
}
#else
bool generatePreview(SvStream& rStream, Graphic& rGraphic)
bool generatePreview(SvStream& rStream, Bitmap& rBitmap
                     sal_uInt64 nPos, sal_uInt64 nSize)
{
    (void)rStream;
    (void)rGraphic;
    (void)rBitmap;
    (void)nPos;
    (void)nSize;

    return true;
}

bool getCompatibleStream(SvStream& rInStream, SvStream& rOutStream)
bool getCompatibleStream(SvStream& rInStream, SvStream& rOutStream,
                         sal_uInt64 nPos, sal_uInt64 nSize)
{
    rInStream.Seek(STREAM_SEEK_TO_BEGIN);
    rOutStream.WriteStream(rInStream);
    rInStream.Seek(nPos);
    rOutStream.WriteStream(rInStream, nSize);
    return rOutStream.good();
}
#endif // HAVE_FEATURE_PDFIUM
@@ -195,26 +205,38 @@ bool getCompatibleStream(SvStream& rInStream, SvStream& rOutStream)
namespace vcl
{

bool ImportPDF(SvStream& rStream, Graphic& rGraphic)
bool ImportPDF(SvStream& rStream, Bitmap &rBitmap,
               css::uno::Sequence<sal_Int8> &rPdfData,
               sal_uInt64 nPos, sal_uInt64 nSize)
{
    // Get the preview of the first page.
    if (!generatePreview(rStream, rGraphic))
    if (!generatePreview(rStream, rBitmap, nPos, nSize))
        return false;

    // Save the original PDF stream for later use.
    SvMemoryStream aMemoryStream;
    if (!getCompatibleStream(rStream, aMemoryStream))
    if (!getCompatibleStream(rStream, aMemoryStream, nPos, nSize))
        return false;

    aMemoryStream.Seek(STREAM_SEEK_TO_END);
    uno::Sequence<sal_Int8> aPdfData(aMemoryStream.Tell());
    rPdfData = css::uno::Sequence<sal_Int8>(aMemoryStream.Tell());
    aMemoryStream.Seek(STREAM_SEEK_TO_BEGIN);
    aMemoryStream.ReadBytes(aPdfData.getArray(), aPdfData.getLength());
    rGraphic.setPdfData(aPdfData);
    aMemoryStream.ReadBytes(rPdfData.getArray(), rPdfData.getLength());

    return true;
}


bool ImportPDF(SvStream& rStream, Graphic& rGraphic)
{
    uno::Sequence<sal_Int8> aPdfData;
    Bitmap aBitmap;
    bool bRet = ImportPDF(rStream, aBitmap, aPdfData);
    rGraphic = aBitmap;
    rGraphic.setPdfData(aPdfData);
    return bRet;
}

}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/impgraph.cxx b/vcl/source/gdi/impgraph.cxx
index b0b472c..983eb46 100644
--- a/vcl/source/gdi/impgraph.cxx
+++ b/vcl/source/gdi/impgraph.cxx
@@ -41,6 +41,8 @@
#include <o3tl/make_unique.hxx>
#include <vcl/gdimetafiletools.hxx>

#include <pdfread.hxx>

#define GRAPHIC_MTFTOBMP_MAXEXT     2048
#define GRAPHIC_STREAMBUFSIZE       8192UL

@@ -1574,13 +1576,14 @@ void ReadImpGraphic( SvStream& rIStm, ImpGraphic& rImpGraphic )
                // Stream in PDF data.
                sal_uInt32 nPdfDataLength = 0;
                rIStm.ReadUInt32(nPdfDataLength);
                Bitmap aBitmap;

                if (nPdfDataLength)
                if (nPdfDataLength && !rIStm.GetError() &&
                    vcl::ImportPDF(rIStm, aBitmap, rImpGraphic.maPdfData,
                                   rIStm.Tell(), nPdfDataLength))
                {
                    uno::Sequence<sal_Int8> aPdfData(nPdfDataLength);
                    rIStm.ReadBytes(aPdfData.getArray(), nPdfDataLength);
                    if (!rIStm.GetError())
                        rImpGraphic.maPdfData = aPdfData;
                    rImpGraphic.maEx = aBitmap;
                    rImpGraphic.meType = GraphicType::Bitmap;
                }
            }
            else