/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include "pdfioutdev_gpl.hxx"
#include "pnghelper.hxx"

#if defined __GNUC__ || defined __clang__
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wunused-parameter"
#elif defined _MSC_VER
#pragma warning(push)
#pragma warning(disable : 4100) // unreferenced formal parameter
#pragma warning(disable : 4121) // alignment of a member was sensitive to packing in Gfx.h/Operator
#endif

#include <Gfx.h>
#include <splash/SplashBitmap.h>
#include <SplashOutputDev.h>
#if defined __GNUC__ || defined __clang__
# pragma GCC diagnostic pop
#elif defined _MSC_VER
#pragma warning(pop)
#endif


#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <math.h>

#include <memory>
#include <vector>

// sigh, UTF8.h was removed in poppler-0.21.0 and put back in 0.21.1, then renamed to UnicodeMapFuncs.h in 0.62.0
// FIXME: we can't use #if POPPLER_CHECK_VERSION(0, 21, 0) && !POPPLER_CHECK_VERSION(0, 21, 1)
//        because the internal poppler does not provide poppler-version.h and the macro always returns 0
#if POPPLER_CHECK_VERSION(0, 62, 0)
#include <UnicodeMapFuncs.h>
#elif POPPLER_CHECK_VERSION(0, 21, 1)
#include <UTF8.h>
#elif POPPLER_CHECK_VERSION(0, 21, 0)
#include "UTF.h"
#else
#include "UTF8.h"
#endif

#ifdef _WIN32
# define snprintf _snprintf

#if defined __GNUC__
#pragma GCC diagnostic warning "-Wformat"
#pragma GCC diagnostic warning "-Wformat-extra-args"
#endif
#endif

/* SYNC STREAMS
   ============

   We stream human-readable tokens to stdout, and binary data (fonts,
   bitmaps) to g_binary_out. Another process reads from those pipes, and
   there lies the rub: things can deadlock, if the two involved
   processes access the pipes in different order. At any point in
   time, both processes must access the same pipe. To ensure this,
   data must be flushed to the OS before writing to a different pipe,
   otherwise not-yet-written data will leave the reading process
   waiting on the wrong pipe.
 */

namespace pdfi
{

/// cut off very small numbers & clamp value to zero
static double normalize( double val )
{
    return fabs(val) < 0.0000001 ? 0.0 : val;
}

namespace
{

/** Escapes line-ending characters (\n and \r) in input string.
  */
std::vector<char> lcl_escapeLineFeeds(const char* const i_pStr)
{
    size_t nLength(strlen(i_pStr));
    std::vector<char> aBuffer;
    aBuffer.reserve(2*nLength+1);

    const char* pRead = i_pStr;
    while( nLength-- )
    {
        if( *pRead == '\r' )
        {
            aBuffer.push_back('\\');
            aBuffer.push_back('r');
        }
        else if( *pRead == '\n' )
        {
            aBuffer.push_back('\\');
            aBuffer.push_back('n');
        }
        else if( *pRead == '\\' )
        {
            aBuffer.push_back('\\');
            aBuffer.push_back('\\');
        }
        else
            aBuffer.push_back(*pRead);
        pRead++;
    }
    aBuffer.push_back(0);

    return aBuffer;
}

}

/// for the temp char buffer the header gets snprintfed in
#define WRITE_BUFFER_SIZE 1024

/// for the initial std::vector capacity when copying stream from xpdf
#define WRITE_BUFFER_INITIAL_CAPACITY (1024*100)

static void initBuf(OutputBuffer& io_rBuffer)
{
    io_rBuffer.reserve(WRITE_BUFFER_INITIAL_CAPACITY);
}

static void writeBinaryBuffer( const OutputBuffer& rBuffer )
{
    // ---sync point--- see SYNC STREAMS above
    fflush(stdout);

    // put buffer to stderr
    if( !rBuffer.empty() )
        if( fwrite(rBuffer.data(), sizeof(char),
                   rBuffer.size(), g_binary_out) != static_cast<size_t>(rBuffer.size()) )
            exit(1); // error

    // ---sync point--- see SYNC STREAMS above
    fflush(g_binary_out);
}

static bool ExtractJpegData(Stream* str, OutputBuffer& outBuf)
{
    int bytesToMarker = 0;
    int bytesToLen = -1;
    bool collectBytes = false;
    int startOfScan = 0;
    int b1 = -1;
    for ( ; ; )
    {
        const int b2 = b1;
        b1 = str->getChar();

        if (b1 == -1)
            return false;

        if (collectBytes) {
     
            outBuf.push_back(static_cast<Output_t>(b1));

            bytesToMarker--;
            bytesToLen--;
        }

        if (bytesToMarker == 0)
        {
            if (startOfScan == 1)
            {
                bytesToMarker = -1;
                startOfScan = 2;
            }
            else if (b2 == 0xFF)
            {
                if (b1 == 0xD8)
                {
                    collectBytes = true;
                    bytesToMarker = 2;

                    outBuf.push_back(Output_t(0xFF));
                    outBuf.push_back(Output_t(0xD8));
                }
                else
                {
                    bytesToLen = 2;
                }
                if (b1 == 0xDA)
                {
                    startOfScan = 1;
                }
            }
            else if (collectBytes)
            {
                return false;
            }
        }

        if (bytesToLen == 0)
        {
            bytesToMarker = b2 * 256 + b1;
        }

        if (startOfScan == 2)
            if ((b2 == 0xFF) && (b1 == 0xD9))
                return true;
    }
}

static void writeJpeg_( OutputBuffer& o_rOutputBuf, Stream* str )
{
    // dump JPEG file as-is
#if POPPLER_CHECK_VERSION(0, 17, 3)
    str = str->getNextStream();
#else
    str = ((DCTStream *)str)->getRawStream();
#endif
#if POPPLER_CHECK_VERSION(26, 1, 0)
    if (!str->rewind())
        return;
#elif POPPLER_CHECK_VERSION(25, 2, 0)
    if (!str->reset())
        return;
#else
    str->reset();
#endif

    o_rOutputBuf.clear();
    ExtractJpegData(str, o_rOutputBuf);

    printf( " JPEG %d", static_cast<int>(o_rOutputBuf.size()) );
    printf("\n");

    str->close();
}

static void writePbm_(OutputBuffer& o_rOutputBuf, Stream* str, int width, int height, bool bInvert )
{
    // write as PBM (char by char, to avoid stdlib lineend messing)
    o_rOutputBuf.clear();
    o_rOutputBuf.resize(WRITE_BUFFER_SIZE);
    o_rOutputBuf[0] = 'P';
    o_rOutputBuf[1] = '4';
    o_rOutputBuf[2] = 0x0A;
    char *pAsCharPtr = reinterpret_cast<char *>(&o_rOutputBuf[3]);
    int nOutLen = snprintf(pAsCharPtr, WRITE_BUFFER_SIZE-10, "%d %d", width, height);
    if( nOutLen < 0 )
        nOutLen = WRITE_BUFFER_SIZE-10;
    o_rOutputBuf[3+nOutLen]  =0x0A;
    o_rOutputBuf[3+nOutLen+1]=0;

    const int header_size = 3+nOutLen+1;
    const int size = height * ((width + 7) / 8);

    printf( " PBM %d", size + header_size );
    printf("\n");

    // trim buffer to exact header length
    o_rOutputBuf.resize(header_size);

    // initialize stream
#if POPPLER_CHECK_VERSION(26, 1, 0)
    if (!str->rewind())
        return;
#elif POPPLER_CHECK_VERSION(25, 2, 0)
    if (!str->reset())
        return;
#else
    str->reset();
#endif

    // copy the raw stream
    if( bInvert )
    {
        for( int i=0; i<size; ++i)
            o_rOutputBuf.push_back(static_cast<char>(str->getChar() ^ 0xff));
    }
    else
    {
        for( int i=0; i<size; ++i)
            o_rOutputBuf.push_back(static_cast<char>(str->getChar()));
    }

    str->close();
}

static void writePpm_( OutputBuffer&     o_rOutputBuf,
                Stream*           str,
                int               width,
                int               height,
                GfxImageColorMap* colorMap )
{
    // write as PPM (char by char, to avoid stdlib lineend messing)
    o_rOutputBuf.clear();
    o_rOutputBuf.resize(WRITE_BUFFER_SIZE);
    o_rOutputBuf[0] = 'P';
    o_rOutputBuf[1] = '6';
    o_rOutputBuf[2] = '\n';
    char *pAsCharPtr = reinterpret_cast<char *>(&o_rOutputBuf[3]);
    int nOutLen = snprintf(pAsCharPtr, WRITE_BUFFER_SIZE-10, "%d %d", width, height);
    if( nOutLen < 0 )
        nOutLen = WRITE_BUFFER_SIZE-10;
    o_rOutputBuf[3+nOutLen]  ='\n';
    o_rOutputBuf[3+nOutLen+1]='2';
    o_rOutputBuf[3+nOutLen+2]='5';
    o_rOutputBuf[3+nOutLen+3]='5';
    o_rOutputBuf[3+nOutLen+4]='\n';
    o_rOutputBuf[3+nOutLen+5]=0;

    const int header_size = 3+nOutLen+5;
    const int size = width*height*3 + header_size;

    printf( " PPM %d", size );
    printf("\n");

    // trim buffer to exact header size
    o_rOutputBuf.resize(header_size);

    // initialize stream
    unsigned char *p;
    GfxRGB rgb;
    std::unique_ptr<ImageStream> imgStr(
        new ImageStream(str,
                        width,
                        colorMap->getNumPixelComps(),
                        colorMap->getBits()));
#if POPPLER_CHECK_VERSION(26, 1, 0)
    if (!imgStr->rewind())
        return;
#elif POPPLER_CHECK_VERSION(25, 2, 0)
    if (!imgStr->reset())
        return;
#else
    imgStr->reset();
#endif

    for( int y=0; y<height; ++y)
    {
        p = imgStr->getLine();
        for( int x=0; x<width; ++x)
        {
            colorMap->getRGB(p, &rgb);
            o_rOutputBuf.push_back(colToByte(rgb.r));
            o_rOutputBuf.push_back(colToByte(rgb.g));
            o_rOutputBuf.push_back(colToByte(rgb.b));

            p +=colorMap->getNumPixelComps();
        }
    }
}

// call this only for 1 bit image streams !
static void writePng_( OutputBuffer&     o_rOutputBuf,
                Stream*           str,
                int               width,
                int               height,
                GfxRGB const &    zeroColor,
                GfxRGB const &    oneColor,
                bool              bIsMask )
{
    o_rOutputBuf.clear();

    // get png image
    PngHelper::createPng( o_rOutputBuf, str, width, height, zeroColor, oneColor, bIsMask );

    printf( " PNG %d", static_cast<int>(o_rOutputBuf.size()) );
    printf("\n");
}

static void writePng_( OutputBuffer& o_rOutputBuf,
                Stream* str,
                int width, int height, GfxImageColorMap* colorMap,
                Stream* maskStr,
                int maskWidth, int maskHeight, GfxImageColorMap* maskColorMap )
{
    o_rOutputBuf.clear();

    // get png image
    PngHelper::createPng( o_rOutputBuf, str, width, height, colorMap, maskStr, maskWidth, maskHeight, maskColorMap );

    printf( " PNG %d", static_cast<int>(o_rOutputBuf.size()) );
    printf("\n");
}

static void writePng_( OutputBuffer& o_rOutputBuf,
                Stream* str,
                int width, int height, GfxImageColorMap* colorMap,
                Stream* maskStr,
                int maskWidth, int maskHeight, bool maskInvert )
{
    o_rOutputBuf.clear();

    // get png image
    PngHelper::createPng( o_rOutputBuf, str, width, height, colorMap, maskStr, maskWidth, maskHeight, maskInvert );

    printf( " PNG %d", static_cast<int>(o_rOutputBuf.size()) );
    printf("\n");
}

// stolen from ImageOutputDev.cc
static void writeMask_( OutputBuffer& o_rOutputBuf, Stream* str, int width, int height, bool bInvert )
{
    if( str->getKind() == strDCT )
        writeJpeg_(o_rOutputBuf, str);
    else
        writePbm_(o_rOutputBuf, str, width, height, bInvert );
}

static void writeImage_( OutputBuffer&     o_rOutputBuf,
                  Stream*           str,
                  int               width,
                  int               height,
                  GfxImageColorMap* colorMap )
{
    // dump JPEG file
    if( str->getKind() == strDCT &&
        (colorMap->getNumPixelComps() == 1 ||
         colorMap->getNumPixelComps() == 3) )
    {
        writeJpeg_(o_rOutputBuf, str);
    }
    else if (colorMap->getNumPixelComps() == 1 &&
             colorMap->getBits() == 1)
    {
        // this is a two color bitmap, write a png
        // provide default colors
        GfxRGB zeroColor = { 0, 0, 0 },
                oneColor = { byteToCol( 0xff ), byteToCol( 0xff ), byteToCol( 0xff ) };
        if( colorMap->getColorSpace()->getMode() == csIndexed || colorMap->getColorSpace()->getMode() == csDeviceGray )
        {
            unsigned char nIndex = 0;
            colorMap->getRGB( &nIndex, &zeroColor );
            nIndex = 1;
            colorMap->getRGB( &nIndex, &oneColor );
        }
        writePng_( o_rOutputBuf, str, width, height, zeroColor, oneColor, false);
    }
    else
        writePpm_( o_rOutputBuf, str, width, height, colorMap );
}

// forwarders
static void writeImageLF( OutputBuffer&     o_rOutputBuf,
                          Stream*           str,
                          int               width,
                          int               height,
                          GfxImageColorMap* colorMap ) { writeImage_(o_rOutputBuf,str,width,height,colorMap); }
static void writeMaskLF( OutputBuffer&     o_rOutputBuf,
                         Stream*           str,
                         int               width,
                         int               height,
                         bool              bInvert ) { writeMask_(o_rOutputBuf,str,width,height,bInvert); }

// Vertically flip the bitmap
static void flipSplashBitmap(SplashBitmap *pBitmap)
{
    if (pBitmap->getRowSize() <= 0)
        return;

    auto nBitmapHeight = static_cast<size_t>(pBitmap->getHeight());
    auto nRowSize = static_cast<size_t>(pBitmap->getRowSize());
    auto nAlphaRowSize = static_cast<size_t>(pBitmap->getAlphaRowSize());

    std::unique_ptr<unsigned char[]> aTmpRow(new unsigned char[nRowSize]);
    std::unique_ptr<unsigned char[]> aTmpAlphaRow(new unsigned char[nAlphaRowSize]);

    auto pBitmapData = pBitmap->getDataPtr();
    auto pAlphaData = pBitmap->getAlphaPtr();

    // Set up pairs of pointers working from each end of the bitmap
    auto pCurRowA = pBitmapData;
    auto pCurAlphaA = pAlphaData;
    auto pCurRowB = pBitmapData+nRowSize*(nBitmapHeight-1);
    auto pCurAlphaB = pAlphaData+nAlphaRowSize*(nBitmapHeight-1);

    for (size_t nCur = 0;
         nCur < nBitmapHeight/2;
         nCur++, pCurRowA+=nRowSize, pCurRowB-=nRowSize,
         pCurAlphaA+=nAlphaRowSize, pCurAlphaB-=nAlphaRowSize)
    {
        memcpy(aTmpRow.get(), pCurRowA, nRowSize);
        memcpy(pCurRowA, pCurRowB, nRowSize);
        memcpy(pCurRowB, aTmpRow.get(), nRowSize);

        memcpy(aTmpAlphaRow.get(), pCurAlphaA, nAlphaRowSize);
        memcpy(pCurAlphaA, pCurAlphaB, nAlphaRowSize);
        memcpy(pCurAlphaB, aTmpAlphaRow.get(), nAlphaRowSize);
    }
}

int PDFOutDev::parseFont( long long nNewId, GfxFont* gfxFont, const GfxState* state ) const
{
    FontAttributes aNewFont;
    int nSize = 0;

#if POPPLER_CHECK_VERSION(20, 12, 0)
    std::string familyName( gfxFont->getNameWithoutSubsetTag() );
#else
#if POPPLER_CHECK_VERSION(0, 71, 0) // GooString::toStr()
    std::string familyName( gfxFont->getName()->toStr() );
#else
    const GooString* gooString = gfxFont->getName();
    std::string familyName( std::string(gooString->getCString(), gooString->getLength()) );
#endif
    if (familyName.length() > 7 && familyName.at(6) == '+')
    {
        familyName = familyName.substr(7);
    }
#endif
    if( familyName != "" )
    {
        aNewFont.familyName.clear();
#if POPPLER_CHECK_VERSION(0, 83, 0) // GooString::append(const std::string&)
        aNewFont.familyName.append( familyName );
#else
        aNewFont.familyName.append( familyName.c_str() );
#endif
    }
    else
    {
        aNewFont.familyName.clear();
        aNewFont.familyName.append( "Arial" );
    }

    aNewFont.maFontWeight  = gfxFont->getWeight();
    aNewFont.isItalic      = gfxFont->isItalic();
#if POPPLER_CHECK_VERSION(0, 83, 0) // const added to getTransformedFontSize
    aNewFont.size          = state->getTransformedFontSize();
#else
    aNewFont.size          = const_cast<GfxState*>(state)->getTransformedFontSize();
#endif
    aNewFont.isUnderline   = false;

    if( gfxFont->getType() == fontTrueType || gfxFont->getType() == fontType1 )
    {
        // TODO(P3): Unfortunately, need to read stream twice, since
        // we must write byte count to stdout before
#if POPPLER_CHECK_VERSION(22, 6, 0)
        std::optional<std::vector<unsigned char>> pBuf = gfxFont->readEmbFontFile( m_pDoc->getXRef() );
        if ( pBuf )
        {
            aNewFont.isEmbedded = true;
            nSize = pBuf->size();
        }
#else
        char* pBuf = gfxFont->readEmbFontFile( m_pDoc->getXRef(), &nSize );
        if( pBuf )
        {
            aNewFont.isEmbedded = true;
            gfree(pBuf);
        }
#endif
    }

    m_aFontMap[ nNewId ] = aNewFont;
    return nSize;
}

void PDFOutDev::writeFontFile( GfxFont* gfxFont ) const
{
    if( gfxFont->getType() != fontTrueType && gfxFont->getType() != fontType1 )
        return;

    int nSize = 0;
#if POPPLER_CHECK_VERSION(22, 6, 0)
    std::optional<std::vector<unsigned char>> pBuf = gfxFont->readEmbFontFile( m_pDoc->getXRef() );
    if ( pBuf )
        nSize = pBuf->size();
    if ( nSize == 0 )
        return;
#else
    char* pBuf = gfxFont->readEmbFontFile( m_pDoc->getXRef(), &nSize );
    if( !pBuf )
        return;
#endif

    // ---sync point--- see SYNC STREAMS above
    fflush(stdout);

#if POPPLER_CHECK_VERSION(22, 6, 0)
    if( fwrite(pBuf->data(), sizeof(*pBuf->data()), nSize, g_binary_out) != static_cast<size_t>(nSize) )
    {
        exit(1); // error
    }
    // ---sync point--- see SYNC STREAMS above
    fflush(g_binary_out);
#else
    if( fwrite(pBuf, sizeof(char), nSize, g_binary_out) != static_cast<size_t>(nSize) )
    {
        gfree(pBuf);
        exit(1); // error
    }
    // ---sync point--- see SYNC STREAMS above
    fflush(g_binary_out);
    gfree(pBuf);
#endif
}

#if POPPLER_CHECK_VERSION(0, 83, 0)
void PDFOutDev::printPath( const GfxPath* pPath )
#else
void PDFOutDev::printPath( GfxPath* pPath )
#endif
{
    int nSubPaths = pPath ? pPath->getNumSubpaths() : 0;
    for( int i=0; i<nSubPaths; i++ )
    {
#if POPPLER_CHECK_VERSION(0, 83, 0)
        const
#endif
        GfxSubpath* pSub  = pPath->getSubpath( i );
        const int nPoints = pSub->getNumPoints();

        printf( " subpath %d", pSub->isClosed() );

        for( int n=0; n<nPoints; ++n )
        {
            printf( " %f %f %d",
                    normalize(pSub->getX(n)),
                    normalize(pSub->getY(n)),
                    pSub->getCurve(n) );
        }
    }
}

PDFOutDev::PDFOutDev( PDFDoc* pDoc ) :
    m_pDoc( pDoc ),
    m_pUtf8Map( new UnicodeMap("UTF-8", true, &mapUTF8) ),
    m_bSkipImages(false)
{
}
PDFOutDev::~PDFOutDev()
{
}

void PDFOutDev::startPage(int /*pageNum*/, GfxState* state
#if POPPLER_CHECK_VERSION(0, 23, 0)
                          , XRef* /*xref*/
#endif
)
{
    assert(state);
    printf("startPage %f %f\n",
           normalize(state->getPageWidth()),
           normalize(state->getPageHeight()));
}

void PDFOutDev::endPage()
{
    printf("endPage\n");
}

#if POPPLER_CHECK_VERSION(0, 19, 0)
void PDFOutDev::processLink(AnnotLink *link)
#elif POPPLER_CHECK_VERSION(0, 17, 0)
void PDFOutDev::processLink(AnnotLink *link, Catalog *)
#else
void PDFOutDev::processLink(Link* link, Catalog*)
#endif
{
    assert(link);

    double x1,x2,y1,y2;
    link->getRect( &x1, &y1, &x2, &y2 );

    LinkAction* pAction = link->getAction();
    if (!(pAction && pAction->getKind() == actionURI))
        return;

#if POPPLER_CHECK_VERSION(0, 86, 0)
    const char* pURI = static_cast<LinkURI*>(pAction)->getURI().c_str();
#elif POPPLER_CHECK_VERSION(0, 72, 0)
    const char* pURI = static_cast<LinkURI*>(pAction)->getURI()->c_str();
#else
    const char* pURI = static_cast<LinkURI*>(pAction)->getURI()->getCString();
#endif

    std::vector<char> aEsc( lcl_escapeLineFeeds(pURI) );

    printf( "drawLink %f %f %f %f %s\n",
            normalize(x1),
            normalize(y1),
            normalize(x2),
            normalize(y2),
            aEsc.data() );
}

void PDFOutDev::saveState(GfxState*)
{
    printf( "saveState\n" );
}

void PDFOutDev::restoreState(GfxState*)
{
    printf( "restoreState\n" );
}

#if POPPLER_CHECK_VERSION(0, 71, 0)
void PDFOutDev::setDefaultCTM(const double *pMat)
#else
void PDFOutDev::setDefaultCTM(double *pMat)
#endif
{
    assert(pMat);

    OutputDev::setDefaultCTM(pMat);

    printf( "updateCtm %f %f %f %f %f %f\n",
            normalize(pMat[0]),
            normalize(pMat[1]),
            normalize(pMat[2]),
            normalize(pMat[3]),
            normalize(pMat[4]),
            normalize(pMat[5]) );
}

void PDFOutDev::updateCTM(GfxState* state,
                          double, double,
                          double, double,
                          double, double)
{
    assert(state);

    const double* const pMat = state->getCTM();
    assert(pMat);

    printf( "updateCtm %f %f %f %f %f %f\n",
            normalize(pMat[0]),
            normalize(pMat[1]),
            normalize(pMat[2]),
            normalize(pMat[3]),
            normalize(pMat[4]),
            normalize(pMat[5]) );
}

void PDFOutDev::updateLineDash(GfxState *state)
{
    if (m_bSkipImages)
        return;
    assert(state);

    int arrayLen; double startOffset;
#if POPPLER_CHECK_VERSION(22, 9, 0)
    const std::vector<double> &dash = state->getLineDash(&startOffset);
    const double* dashArray = dash.data();
    arrayLen = dash.size();
#else
    double* dashArray;
    state->getLineDash(&dashArray, &arrayLen, &startOffset);
#endif

    printf( "updateLineDash" );
    if( arrayLen && dashArray )
    {
        printf( " %f %d", normalize(startOffset), arrayLen );
        for( int i=0; i<arrayLen; ++i )
            printf( " %f", normalize(*dashArray++) );
    }
    printf( "\n" );
}

void PDFOutDev::updateFlatness(GfxState *state)
{
    if (m_bSkipImages)
        return;
    assert(state);
    printf( "updateFlatness %d\n", state->getFlatness() );
}

void PDFOutDev::updateLineJoin(GfxState *state)
{
    if (m_bSkipImages)
        return;
    assert(state);
    printf( "updateLineJoin %d\n", state->getLineJoin() );
}

void PDFOutDev::updateLineCap(GfxState *state)
{
    if (m_bSkipImages)
        return;
    assert(state);
    printf( "updateLineCap %d\n", state->getLineCap() );
}

void PDFOutDev::updateMiterLimit(GfxState *state)
{
    if (m_bSkipImages)
        return;
    assert(state);
    printf( "updateMiterLimit %f\n", normalize(state->getMiterLimit()) );
}

void PDFOutDev::updateLineWidth(GfxState *state)
{
    if (m_bSkipImages)
        return;
    assert(state);
    printf( "updateLineWidth %f\n", normalize(state->getLineWidth()) );
}

void PDFOutDev::updateFillColor(GfxState *state)
{
    if (m_bSkipImages)
        return;
    assert(state);

    GfxRGB aRGB;
    state->getFillRGB( &aRGB );

    printf( "updateFillColor %f %f %f %f\n",
            normalize(colToDbl(aRGB.r)),
            normalize(colToDbl(aRGB.g)),
            normalize(colToDbl(aRGB.b)),
            normalize(state->getFillOpacity()) );
}

void PDFOutDev::updateStrokeColor(GfxState *state)
{
    if (m_bSkipImages)
        return;
    assert(state);

    GfxRGB aRGB;
    state->getStrokeRGB( &aRGB );

    printf( "updateStrokeColor %f %f %f %f\n",
            normalize(colToDbl(aRGB.r)),
            normalize(colToDbl(aRGB.g)),
            normalize(colToDbl(aRGB.b)),
            normalize(state->getStrokeOpacity()) );
}

void PDFOutDev::updateFillOpacity(GfxState *state)
{
    if (m_bSkipImages)
        return;
    updateFillColor(state);
}

void PDFOutDev::updateStrokeOpacity(GfxState *state)
{
    if (m_bSkipImages)
        return;
    updateStrokeColor(state);
}

void PDFOutDev::updateBlendMode(GfxState*)
{
}

void PDFOutDev::updateFont(GfxState *state)
{
    assert(state);

#if POPPLER_CHECK_VERSION(22, 6, 0)
    GfxFont *gfxFont = state->getFont().get();
#else
    GfxFont *gfxFont = state->getFont();
#endif
    if( !gfxFont )
        return;

    FontAttributes aFont;
    int nEmbedSize=0;

#if POPPLER_CHECK_VERSION(0, 64, 0)
    const
#endif
    Ref* pID = gfxFont->getID();
    // TODO(Q3): Portability problem
    long long fontID = static_cast<long long>(pID->gen) << 32 | static_cast<long long>(pID->num);
    std::unordered_map< long long, FontAttributes >::const_iterator it =
        m_aFontMap.find( fontID );
    if( it == m_aFontMap.end() )
    {
        nEmbedSize = parseFont( fontID, gfxFont, state );
        it = m_aFontMap.find( fontID );
    }

    printf( "updateFont" );
    if( it != m_aFontMap.end() )
    {
        // conflating this with printf below crashes under Windoze
        printf( " %lld", fontID );

        aFont = it->second;

#if POPPLER_CHECK_VERSION(0, 72, 0)
        std::vector<char> aEsc( lcl_escapeLineFeeds(aFont.familyName.c_str()) );
#else
        std::vector<char> aEsc( lcl_escapeLineFeeds(aFont.familyName.getCString()) );
#endif
        printf( " %d %d %d %d %f %d %s",
                aFont.isEmbedded,
                aFont.maFontWeight,
                aFont.isItalic,
                aFont.isUnderline,
                normalize(state->getTransformedFontSize()),
                nEmbedSize,
                aEsc.data() );
    }
    printf( "\n" );

    if (nEmbedSize)
    {
        writeFontFile(gfxFont);
    }
}

void PDFOutDev::updateRender(GfxState *state)
{
    assert(state);

    printf( "setTextRenderMode %d\n", state->getRender() );
}

void PDFOutDev::stroke(GfxState *state)
{
    if (m_bSkipImages)
        return;
    assert(state);

    printf( "strokePath" );
    printPath( state->getPath() );
    printf( "\n" );
}

void PDFOutDev::fill(GfxState *state)
{
    if (m_bSkipImages)
        return;
    assert(state);

    printf( "fillPath" );
    printPath( state->getPath() );
    printf( "\n" );
}

void PDFOutDev::eoFill(GfxState *state)
{
    if (m_bSkipImages)
        return;
    assert(state);

    printf( "eoFillPath" );
    printPath( state->getPath() );
    printf( "\n" );
}

void PDFOutDev::clip(GfxState *state)
{
    if (m_bSkipImages)
        return;
    assert(state);

    printf( "clipPath" );
    printPath( state->getPath() );
    printf( "\n" );
}

void PDFOutDev::eoClip(GfxState *state)
{
    if (m_bSkipImages)
        return;
    assert(state);

    printf( "eoClipPath" );
    printPath( state->getPath() );
    printf( "\n" );
}

void PDFOutDev::clipToStrokePath(GfxState *state)
{
    if (m_bSkipImages)
        return;
    assert(state);

    printf( "clipToStrokePath" );
    printPath( state->getPath() );
    printf( "\n" );
}

/** Output one glyph


    @param dx
    horizontal skip for character (already scaled with font size) +
    inter-char space: cursor is shifted by this amount for next char

    @param dy
    vertical skip for character (zero for horizontal writing mode):
    cursor is shifted by this amount for next char

    @param originX
    local offset of character (zero for horizontal writing mode). not
    taken into account for output pos updates. Used for vertical writing.

    @param originY
    local offset of character (zero for horizontal writing mode). not
    taken into account for output pos updates. Used for vertical writing.
 */

#if POPPLER_CHECK_VERSION(0, 82, 0)
void PDFOutDev::drawChar(GfxState *state, double x, double y,
                         double dx, double dy,
                         double originX, double originY,
                         CharCode, int /*nBytes*/, const Unicode *u, int uLen)
{
#else
void PDFOutDev::drawChar(GfxState *state, double x, double y,
                         double dx, double dy,
                         double originX, double originY,
                         CharCode, int /*nBytes*/, Unicode *u, int uLen)
{
#endif
    assert(state);

    if( u == nullptr )
        return;

    // Fix for tdf#96080
    if (uLen == 4 && u[0] == '\t' && u[1] == '\r' && u[2] == ' ' && u[3] == 0xA0)
    {
        u += 2;
        uLen = 1;
    }

    double csdx = 0.0;
    double csdy = 0.0;
    if (!state->getFont() || !state->getFont()->getWMode())
    {
        csdx = state->getCharSpace();
        if (*u == ' ')
            csdx += state->getWordSpace();
        csdx *= state->getHorizScaling();
    }
    else
    {
        csdy = state->getCharSpace();
        if (*u == ' ')
            csdy += state->getWordSpace();
    }

    double cstdx = 0.0;
    double cstdy = 0.0;
    state->textTransformDelta(csdx, csdy, &cstdx, &cstdy);

    const double fontSize = state->getFontSize();

    const double aPositionX(x-originX);
    const double aPositionY(y-originY);

    const double* pTextMat=state->getTextMat();
    printf( "drawChar %f %f %f %f %f %f %f %f %f ",
            normalize(aPositionX),
            normalize(aPositionY),
            normalize(aPositionX + dx - cstdx),
            normalize(aPositionY + dy - cstdy),
            normalize(pTextMat[0]),
            normalize(pTextMat[2]),
            normalize(pTextMat[1]),
            normalize(pTextMat[3]),
            normalize(fontSize)
            );

    // silence spurious warning
#if POPPLER_CHECK_VERSION(0, 62, 0)
    (void)&mapUTF16;
#else
    (void)&mapUCS2;
#endif

    char buf[9];
    for( int i=0; i<uLen; ++i )
    {
        buf[ m_pUtf8Map->mapUnicode(u[i], buf, sizeof(buf)-1) ] = 0;
        std::vector<char> aEsc( lcl_escapeLineFeeds(buf) );
        printf( "%s", aEsc.data() );
    }

    printf( "\n" );
}

#if POPPLER_CHECK_VERSION(0, 64, 0)
void PDFOutDev::drawString(GfxState*, const GooString* /*s*/)
#else
void PDFOutDev::drawString(GfxState*, GooString* /*s*/)
#endif
{
    // TODO(F3): NYI
}

void PDFOutDev::endTextObject(GfxState*)
{
    printf( "endTextObject\n" );
}

void PDFOutDev::drawImageMask(GfxState* pState, Object*, Stream* str,
                              int width, int height, poppler_bool invert,
                              poppler_bool /*interpolate*/,
                              poppler_bool /*inlineImg*/ )
{
    if (m_bSkipImages)
        return;
    OutputBuffer aBuf; initBuf(aBuf);

    printf( "drawMask %d %d %d", width, height, invert );

    int bitsPerComponent = 1;
    StreamColorSpaceMode csMode = streamCSNone;
#if POPPLER_CHECK_VERSION(24, 12, 0)
    bool hasAlpha;
    str->getImageParams( &bitsPerComponent, &csMode, &hasAlpha );
#else
    str->getImageParams( &bitsPerComponent, &csMode );
#endif
    if( bitsPerComponent == 1 && (csMode == streamCSNone || csMode == streamCSDeviceGray) )
    {
        GfxRGB oneColor = { dblToCol( 1.0 ), dblToCol( 1.0 ), dblToCol( 1.0 ) };
        GfxRGB zeroColor = { dblToCol( 0.0 ), dblToCol( 0.0 ), dblToCol( 0.0 ) };
        pState->getFillColorSpace()->getRGB( pState->getFillColor(), &zeroColor );
        if( invert )
            writePng_( aBuf, str, width, height, oneColor, zeroColor, true );
        else
            writePng_( aBuf, str, width, height, zeroColor, oneColor, true );
    }
    else
        writeMaskLF(aBuf, str, width, height, invert);
    writeBinaryBuffer(aBuf);
}

#if POPPLER_CHECK_VERSION(0, 70, 0)
#if POPPLER_CHECK_VERSION(25, 9, 0)
void PDFOutDev::beginTransparencyGroup(GfxState * /*state*/, const std::array<double, 4> & /*bbox*/,
#else
void PDFOutDev::beginTransparencyGroup(GfxState * /*state*/, const double * /*bbox*/,
#endif
#else
void PDFOutDev::beginTransparencyGroup(GfxState * /*state*/, double * /*bbox*/,
#endif
                                    GfxColorSpace * /*blendingColorSpace*/,
                                    poppler_bool /*isolated*/,
                                    poppler_bool /*knockout*/,
                                    poppler_bool forSoftMask)
{
    // We're not doing full transparency group yet, just trying to hide the
    // soft mask temporaries.
    printf( "beginTransparencyGroup %d", !!forSoftMask);
    printf("\n");
}

void PDFOutDev::endTransparencyGroup(GfxState * /* state */)
{
    printf( "endTransparencyGroup");
    printf("\n");
}

#if POPPLER_CHECK_VERSION(0, 82, 0)
void PDFOutDev::drawImage(GfxState*, Object*, Stream* str,
                          int width, int height, GfxImageColorMap* colorMap,
                          poppler_bool /*interpolate*/,
                          const int* maskColors, poppler_bool /*inlineImg*/ )
{
#else
void PDFOutDev::drawImage(GfxState*, Object*, Stream* str,
                          int width, int height, GfxImageColorMap* colorMap,
                          poppler_bool /*interpolate*/,
                          int* maskColors, poppler_bool /*inlineImg*/ )
{
#endif
    if (m_bSkipImages)
        return;
    OutputBuffer aBuf; initBuf(aBuf);
    OutputBuffer aMaskBuf;

    printf( "drawImage %d %d", width, height );

    if( maskColors )
    {
        // write mask colors. nBytes must be even - first half is
        // lower bound values, second half upper bound values
        if( colorMap->getColorSpace()->getMode() == csIndexed )
        {
            aMaskBuf.push_back( static_cast<char>(maskColors[0]) );
            aMaskBuf.push_back( static_cast<char>(maskColors[gfxColorMaxComps]) );
        }
        else
        {
            GfxRGB aMinRGB;
            colorMap->getColorSpace()->getRGB(
#if POPPLER_CHECK_VERSION(0, 82, 0)
                reinterpret_cast<const GfxColor*>(maskColors),
#else
                reinterpret_cast<GfxColor*>(maskColors),
#endif
                &aMinRGB );

            GfxRGB aMaxRGB;
            colorMap->getColorSpace()->getRGB(
#if POPPLER_CHECK_VERSION(0, 82, 0)
                reinterpret_cast<const GfxColor*>(maskColors)+gfxColorMaxComps,
#else
                reinterpret_cast<GfxColor*>(maskColors)+gfxColorMaxComps,
#endif
                &aMaxRGB );

            aMaskBuf.push_back( colToByte(aMinRGB.r) );
            aMaskBuf.push_back( colToByte(aMinRGB.g) );
            aMaskBuf.push_back( colToByte(aMinRGB.b) );
            aMaskBuf.push_back( colToByte(aMaxRGB.r) );
            aMaskBuf.push_back( colToByte(aMaxRGB.g) );
            aMaskBuf.push_back( colToByte(aMaxRGB.b) );
        }
    }

    printf( " %d", static_cast<int>(aMaskBuf.size()) );
    writeImageLF( aBuf, str, width, height, colorMap );
    writeBinaryBuffer(aBuf);
    writeBinaryBuffer(aMaskBuf);
}

void PDFOutDev::drawMaskedImage(GfxState*, Object*, Stream* str,
                                int width, int height,
                                GfxImageColorMap* colorMap,
                                poppler_bool /*interpolate*/,
                                Stream* maskStr,
                                int maskWidth, int maskHeight,
                                poppler_bool maskInvert, poppler_bool /*maskInterpolate*/
                               )
{
    if (m_bSkipImages)
        return;
    OutputBuffer aBuf;     initBuf(aBuf);
    printf( "drawImage %d %d 0", width, height );
    writePng_( aBuf, str, width, height, colorMap, maskStr, maskWidth, maskHeight, maskInvert );
    writeBinaryBuffer( aBuf );
}

void PDFOutDev::drawSoftMaskedImage(GfxState*, Object*, Stream* str,
                                    int width, int height,
                                    GfxImageColorMap* colorMap,
                                    poppler_bool /*interpolate*/,
                                    Stream* maskStr,
                                    int maskWidth, int maskHeight,
                                    GfxImageColorMap* maskColorMap
                                    , poppler_bool /*maskInterpolate*/
                                   )
{
    if (m_bSkipImages)
        return;
    OutputBuffer aBuf;     initBuf(aBuf);
    printf( "drawImage %d %d 0", width, height );
    writePng_( aBuf, str, width, height, colorMap, maskStr, maskWidth, maskHeight, maskColorMap );
    writeBinaryBuffer( aBuf );
}

void PDFOutDev::setPageNum( int nNumPages )
{
    // TODO(F3): printf might format int locale-dependent!
    printf("setPageNum %d\n", nNumPages);
}

void PDFOutDev::setSkipImages( bool bSkipImages )
{
    m_bSkipImages = bSkipImages;
}

// This is a fudge to fixup the opacity during shaded fills,
// there are situations where the stroke opacity is used rather than the
// fill opacity is used, so flip it here.
// Note that Poppler does a state save around the fill, so it restores this hack
// See https://gitlab.freedesktop.org/poppler/poppler/-/issues/178#note_472334
poppler_bool PDFOutDev::axialShadedFill(GfxState *state, GfxAxialShading *, double, double)
{
    if (state->getStrokePattern()!=nullptr)
    {
        state->setFillOpacity(state->getStrokeOpacity());
    }
    return false;
}

#if POPPLER_CHECK_VERSION(21, 3, 0)
poppler_bool PDFOutDev::tilingPatternFill(GfxState *state, Gfx *, Catalog *,
#if POPPLER_CHECK_VERSION(25, 9, 0)
                                          GfxTilingPattern *tPat, const std::array<double, 6> &mat,
#else
                                          GfxTilingPattern *tPat, const double *mat,
#endif
                                          int x0, int y0, int x1, int y1,
                                          double xStep, double yStep)
{
#if POPPLER_CHECK_VERSION(25, 9, 0)
    const std::array<double, 4> pBbox = tPat->getBBox();
#else
    const double *pBbox = tPat->getBBox();
#endif
    const int nPaintType = tPat->getPaintType();
    Dict *pResDict = tPat->getResDict();
    Object *aStr = tPat->getContentStream();
    double nWidth = pBbox[2] - pBbox[0];
    double nHeight = pBbox[3] - pBbox[1];

    // If our wrapper is skipping images then we don't need to do anything
    // but return 'true' so that Poppler doesn't do the slow method
    if (m_bSkipImages)
        return true;

    // Copied from the Cairo output dev; I think this is patterns
    // with gaps, let poppler do the slow method for now.
    if (xStep != nWidth || yStep != nHeight)
        return false;

    printf( "tilingPatternFill %d %d %d %d %f %f "
            "%d "
            "%f %f %f %f %f %f", // No ending space!

            x0, y0, x1, y1, normalize(xStep), normalize(yStep),

            nPaintType,

            normalize(mat[0]), normalize(mat[1]),
            normalize(mat[2]), normalize(mat[3]),
            normalize(mat[4]), normalize(mat[5])
            );

// error: no matching function for call to 'GfxImageColorMap::GfxImageColorMap(int, Object*, GfxDeviceRGBColorSpace*)' 
#ifndef __sun__
    PDFRectangle aBox;
    aBox.x1 = pBbox[0];
    aBox.y1 = pBbox[1];
    aBox.x2 = pBbox[2];
    aBox.y2 = pBbox[3];

    const int nDPI = 72; // GfxState seems to have 72.0 as magic for some reason
    auto pSplashGfxState = new GfxState(nDPI, nDPI, &aBox, 0, false);
    auto pSplashOut = new SplashOutputDev(splashModeRGB8, 1, false, nullptr);
    pSplashOut->setEnableFreeType(false);
    pSplashOut->startDoc(m_pDoc);
    pSplashOut->startPage(0 /* pageNum */, pSplashGfxState, nullptr /* xref */);

    auto pSplashGfx = new Gfx(m_pDoc, pSplashOut, pResDict, &aBox, nullptr);
    pSplashGfx->display(aStr);
    std::unique_ptr<SplashBitmap> pSplashBitmap(pSplashOut->takeBitmap());
    // Poppler tells us to free the splash device immediately after taking the
    // bitmap
    delete pSplashGfxState;
    delete pSplashGfx;
    delete pSplashOut;

    // Add a vertical flip, we can't do this in LO for an image filled poly
    flipSplashBitmap(pSplashBitmap.get());

    auto nBitmapWidth = static_cast<size_t>(pSplashBitmap->getWidth());
    auto nBitmapHeight = static_cast<size_t>(pSplashBitmap->getHeight());

    char *pBitmapData = reinterpret_cast<char *>(pSplashBitmap->getDataPtr());
    if (nPaintType == 2)
    {
        // My understanding is Type 2 fills are just bitmaps of *what* to fill
        // in the current fill colour.
        // sending it to LO as a flat colour image with the alpha map is easiest
        GfxRGB aCurFill;
        unsigned char r,g,b;
        state->getFillColorSpace()->getRGB(state->getFillColor(), &aCurFill);
        r = colToByte(aCurFill.r);
        g = colToByte(aCurFill.g);
        b = colToByte(aCurFill.b);

        for(size_t i=0; i < (nBitmapWidth * nBitmapHeight * 3); i+=3)
        {
            pBitmapData[i  ] = r;
            pBitmapData[i+1] = g;
            pBitmapData[i+2] = b;
        }
    }

#if POPPLER_CHECK_VERSION(25, 5, 0)
    std::unique_ptr<MemStream> pRgbStr(new MemStream(pBitmapData, 0,
        nBitmapWidth * nBitmapHeight * 3, Object::null()));
    std::unique_ptr<MemStream> pAlphaStr(new MemStream(reinterpret_cast<char *>(pSplashBitmap->getAlphaPtr()),
        0, nBitmapWidth * nBitmapHeight, Object::null()));
    auto aDecode = Object::null();
#else
    std::unique_ptr<MemStream> pRgbStr(new MemStream(pBitmapData, 0,
        nBitmapWidth * nBitmapHeight * 3, Object(objNull)));
    std::unique_ptr<MemStream> pAlphaStr(new MemStream(reinterpret_cast<char *>(pSplashBitmap->getAlphaPtr()),
        0, nBitmapWidth * nBitmapHeight, Object(objNull)));
    auto aDecode = Object(objNull);
#endif
#if POPPLER_CHECK_VERSION(24, 10, 0)
    std::unique_ptr<GfxImageColorMap> pRgbIdentityColorMap(new GfxImageColorMap(8, &aDecode,
        std::make_unique<GfxDeviceRGBColorSpace>()));
    std::unique_ptr<GfxImageColorMap> pGrayIdentityColorMap(new GfxImageColorMap(8, &aDecode,
        std::make_unique<GfxDeviceGrayColorSpace>()));
#else
    std::unique_ptr<GfxImageColorMap> pRgbIdentityColorMap(new GfxImageColorMap(8, &aDecode,
        new GfxDeviceRGBColorSpace()));
    std::unique_ptr<GfxImageColorMap> pGrayIdentityColorMap(new GfxImageColorMap(8, &aDecode,
        new GfxDeviceGrayColorSpace()));
#endif

    OutputBuffer aBuf; initBuf(aBuf);
    writePng_(aBuf, pRgbStr.get(), nBitmapWidth, nBitmapHeight, pRgbIdentityColorMap.get(),
        pAlphaStr.get(), nBitmapWidth, nBitmapHeight, pGrayIdentityColorMap.get());
    writeBinaryBuffer(aBuf);
#endif

    // If we return false here we can fall back to the slow path
    return true;
}

// This could be implemented for earlier versions, but the interface keeps
// changing a little; not having it is only a problem for inputs with
// large patterns.
#endif
}

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