/* -*- 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 <config_folders.h>

#include <contentsink.hxx>
#include <pdfparse.hxx>
#include <pdfihelper.hxx>
#include <wrapper.hxx>

#include <osl/file.h>
#include <osl/file.hxx>
#include <osl/thread.h>
#include <osl/process.h>
#include <osl/diagnose.h>
#include <rtl/bootstrap.hxx>
#include <rtl/ustring.hxx>
#include <rtl/ustrbuf.hxx>
#include <rtl/strbuf.hxx>
#include <sal/log.hxx>

#include <comphelper/propertysequence.hxx>
#include <com/sun/star/io/XInputStream.hpp>
#include <com/sun/star/uno/XComponentContext.hpp>
#include <com/sun/star/awt/FontDescriptor.hpp>
#include <com/sun/star/beans/XMaterialHolder.hpp>
#include <com/sun/star/rendering/PathCapType.hpp>
#include <com/sun/star/rendering/PathJoinType.hpp>
#include <com/sun/star/rendering/XPolyPolygon2D.hpp>
#include <com/sun/star/geometry/Matrix2D.hpp>
#include <com/sun/star/geometry/AffineMatrix2D.hpp>
#include <com/sun/star/geometry/RealRectangle2D.hpp>
#include <com/sun/star/geometry/RealSize2D.hpp>
#include <com/sun/star/task/XInteractionHandler.hpp>

#include <basegfx/point/b2dpoint.hxx>
#include <basegfx/polygon/b2dpolypolygon.hxx>
#include <basegfx/polygon/b2dpolygon.hxx>
#include <basegfx/utils/unopolypolygon.hxx>

#include <vcl/metric.hxx>
#include <vcl/font.hxx>
#include <vcl/virdev.hxx>

#include <memory>
#include <unordered_map>
#include <string.h>
#include <stdlib.h>

#include <rtl/character.hxx>

using namespace com::sun::star;

namespace pdfi
{

namespace
{

// identifier of the strings coming from the out-of-process xpdf
// converter
enum parseKey {
    CLIPPATH,
    DRAWCHAR,
    DRAWIMAGE,
    DRAWLINK,
    DRAWMASK,
    DRAWMASKEDIMAGE,
    DRAWSOFTMASKEDIMAGE,
    ENDPAGE,
    ENDTEXTOBJECT,
    EOCLIPPATH,
    EOFILLPATH,
    FILLPATH,
    HYPERLINK,
    INTERSECTCLIP,
    INTERSECTEOCLIP,
    POPSTATE,
    PUSHSTATE,
    RESTORESTATE,
    SAVESTATE,
    SETBLENDMODE,
    SETFILLCOLOR,
    SETFONT,
    SETLINECAP,
    SETLINEDASH,
    SETLINEJOIN,
    SETLINEWIDTH,
    SETMITERLIMIT,
    SETPAGENUM,
    SETSTROKECOLOR,
    SETTEXTRENDERMODE,
    SETTRANSFORMATION,
    STARTPAGE,
    STROKEPATH,
    UPDATEBLENDMODE,
    UPDATECTM,
    UPDATEFILLCOLOR,
    UPDATEFILLOPACITY,
    UPDATEFLATNESS,
    UPDATEFONT,
    UPDATELINECAP,
    UPDATELINEDASH,
    UPDATELINEJOIN,
    UPDATELINEWIDTH,
    UPDATEMITERLIMIT,
    UPDATESTROKECOLOR,
    UPDATESTROKEOPACITY,
    NONE
};

#if defined _MSC_VER && defined __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-register"
#pragma clang diagnostic ignored "-Wextra-tokens"
#endif
#include <hash.cxx>
#if defined _MSC_VER && defined __clang__
#pragma clang diagnostic pop
#endif

class Parser
{
    typedef std::unordered_map< sal_Int64,
                           FontAttributes > FontMapType;

    ScopedVclPtr<VirtualDevice> m_xDev;
    const uno::Reference<uno::XComponentContext> m_xContext;
    const ContentSinkSharedPtr                   m_pSink;
    const oslFileHandle                          m_pErr;
    OString                               m_aLine;
    FontMapType                                  m_aFontMap;
    sal_Int32                                    m_nNextToken;
    sal_Int32                                    m_nCharIndex;


    OString readNextToken();
    void           readInt32( sal_Int32& o_Value );
    sal_Int32      readInt32();
    void           readInt64( sal_Int64& o_Value );
    void           readDouble( double& o_Value );
    double         readDouble();
    void           readBinaryData( uno::Sequence<sal_Int8>& rBuf );

    uno::Reference<rendering::XPolyPolygon2D> readPath();

    void                 readChar();
    void                 readLineCap();
    void                 readLineDash();
    void                 readLineJoin();
    void                 readTransformation();
    rendering::ARGBColor readColor();
    static void          parseFontFamilyName( FontAttributes& aResult );
    void                 readFont();
    uno::Sequence<beans::PropertyValue> readImageImpl();

    void                 readImage();
    void                 readMask();
    void                 readLink();
    void                 readMaskedImage();
    void                 readSoftMaskedImage();
    static sal_Int32 parseFontCheckForString(const sal_Unicode* pCopy, sal_Int32 nCopyLen,
                                      const char* pAttrib, sal_Int32 nAttribLen,
                                      FontAttributes& rResult, bool bItalic, bool bBold);
    static sal_Int32 parseFontRemoveSuffix(const sal_Unicode* pCopy, sal_Int32 nCopyLen,
                              const char* pAttrib, sal_Int32 nAttribLen);

public:
    Parser( const ContentSinkSharedPtr&                   rSink,
            oslFileHandle                                 pErr,
            const uno::Reference<uno::XComponentContext>& xContext ) :
        m_xContext(xContext),
        m_pSink(rSink),
        m_pErr(pErr),
        m_aLine(),
        m_aFontMap(101),
        m_nNextToken(-1),
        m_nCharIndex(-1)
    {}

    void parseLine( const OString& rLine );
};

/** Unescapes line-ending characters in input string. These
    characters are encoded as pairs of characters: '\\' 'n', resp.
    '\\' 'r'. This function converts them back to '\n', resp. '\r'.
  */
OString lcl_unescapeLineFeeds(const OString& i_rStr)
{
    const size_t nOrigLen(sal::static_int_cast<size_t>(i_rStr.getLength()));
    const sal_Char* const pOrig(i_rStr.getStr());
    std::unique_ptr<sal_Char[]> pBuffer(new sal_Char[nOrigLen + 1]);

    const sal_Char* pRead(pOrig);
    sal_Char* pWrite(pBuffer.get());
    const sal_Char* pCur(pOrig);
    while ((pCur = strchr(pCur, '\\')) != nullptr)
    {
        const sal_Char cNext(pCur[1]);
        if (cNext == 'n' || cNext == 'r' || cNext == '\\')
        {
            const size_t nLen(pCur - pRead);
            strncpy(pWrite, pRead, nLen);
            pWrite += nLen;
            *pWrite = cNext == 'n' ? '\n' : (cNext == 'r' ? '\r' : '\\');
            ++pWrite;
            pCur = pRead = pCur + 2;
        }
        else
        {
            // Just continue on the next character. The current
            // block will be copied the next time it goes through the
            // 'if' branch.
            ++pCur;
        }
    }
    // maybe there are some data to copy yet
    if (sal::static_int_cast<size_t>(pRead - pOrig) < nOrigLen)
    {
        const size_t nLen(nOrigLen - (pRead - pOrig));
        strncpy(pWrite, pRead, nLen);
        pWrite += nLen;
    }
    *pWrite = '\0';

    OString aResult(pBuffer.get());
    return aResult;
}

OString Parser::readNextToken()
{
    OSL_PRECOND(m_nCharIndex!=-1,"insufficient input");
    return m_aLine.getToken(m_nNextToken,' ',m_nCharIndex);
}

void Parser::readInt32( sal_Int32& o_Value )
{
    o_Value = readNextToken().toInt32();
}

sal_Int32 Parser::readInt32()
{
    return readNextToken().toInt32();
}

void Parser::readInt64( sal_Int64& o_Value )
{
    o_Value = readNextToken().toInt64();
}

void Parser::readDouble( double& o_Value )
{
    o_Value = readNextToken().toDouble();
}

double Parser::readDouble()
{
    return readNextToken().toDouble();
}

void Parser::readBinaryData( uno::Sequence<sal_Int8>& rBuf )
{
    sal_Int32 nFileLen( rBuf.getLength() );
    sal_Int8*           pBuf( rBuf.getArray() );
    sal_uInt64          nBytesRead(0);
    oslFileError        nRes=osl_File_E_None;
    while( nFileLen &&
           osl_File_E_None == (nRes=osl_readFile( m_pErr, pBuf, nFileLen, &nBytesRead )) )
    {
        pBuf += nBytesRead;
        nFileLen -= sal::static_int_cast<sal_Int32>(nBytesRead);
    }

    OSL_PRECOND(nRes==osl_File_E_None, "inconsistent data");
}

uno::Reference<rendering::XPolyPolygon2D> Parser::readPath()
{
    const OString aSubPathMarker( "subpath" );

    if( readNextToken() != aSubPathMarker )
        OSL_PRECOND(false, "broken path");

    basegfx::B2DPolyPolygon aResult;
    while( m_nCharIndex != -1 )
    {
        basegfx::B2DPolygon aSubPath;

        sal_Int32 nClosedFlag;
        readInt32( nClosedFlag );
        aSubPath.setClosed( nClosedFlag != 0 );

        sal_Int32 nContiguousControlPoints(0);
        sal_Int32 nDummy=m_nCharIndex;
        OString aCurrToken( m_aLine.getToken(m_nNextToken,' ',nDummy) );

        while( m_nCharIndex != -1 && aCurrToken != aSubPathMarker )
        {
            sal_Int32 nCurveFlag;
            double    nX, nY;
            readDouble( nX );
            readDouble( nY );
            readInt32(  nCurveFlag );

            aSubPath.append(basegfx::B2DPoint(nX,nY));
            if( nCurveFlag )
            {
                ++nContiguousControlPoints;
            }
            else if( nContiguousControlPoints )
            {
                OSL_PRECOND(nContiguousControlPoints==2,"broken bezier path");

                // have two control points before us. the current one
                // is a normal point - thus, convert previous points
                // into bezier segment
                const sal_uInt32 nPoints( aSubPath.count() );
                const basegfx::B2DPoint aCtrlA( aSubPath.getB2DPoint(nPoints-3) );
                const basegfx::B2DPoint aCtrlB( aSubPath.getB2DPoint(nPoints-2) );
                const basegfx::B2DPoint aEnd( aSubPath.getB2DPoint(nPoints-1) );
                aSubPath.remove(nPoints-3, 3);
                aSubPath.appendBezierSegment(aCtrlA, aCtrlB, aEnd);

                nContiguousControlPoints=0;
            }

            // one token look-ahead (new subpath or more points?
            nDummy=m_nCharIndex;
            aCurrToken = m_aLine.getToken(m_nNextToken,' ',nDummy);
        }

        aResult.append( aSubPath );
        if( m_nCharIndex != -1 )
            readNextToken();
    }

    return static_cast<rendering::XLinePolyPolygon2D*>(
        new basegfx::unotools::UnoPolyPolygon(aResult));
}

void Parser::readChar()
{
    double fontSize;
    geometry::Matrix2D aUnoMatrix;
    geometry::RealRectangle2D aRect;

    readDouble(aRect.X1);
    readDouble(aRect.Y1);
    readDouble(aRect.X2);
    readDouble(aRect.Y2);
    readDouble(aUnoMatrix.m00);
    readDouble(aUnoMatrix.m01);
    readDouble(aUnoMatrix.m10);
    readDouble(aUnoMatrix.m11);
    readDouble(fontSize);

    OString aChars;

    if (m_nCharIndex != -1)
        aChars = lcl_unescapeLineFeeds( m_aLine.copy( m_nCharIndex ) );

    // chars gobble up rest of line
    m_nCharIndex = -1;

    m_pSink->drawGlyphs(OStringToOUString(aChars, RTL_TEXTENCODING_UTF8),
        aRect, aUnoMatrix, fontSize);
}

void Parser::readLineCap()
{
    sal_Int8 nCap(rendering::PathCapType::BUTT);
    switch( readInt32() )
    {
        default:
        case 0: nCap = rendering::PathCapType::BUTT; break;
        case 1: nCap = rendering::PathCapType::ROUND; break;
        case 2: nCap = rendering::PathCapType::SQUARE; break;
    }
    m_pSink->setLineCap(nCap);
}

void Parser::readLineDash()
{
    if( m_nCharIndex == -1 )
    {
        m_pSink->setLineDash( uno::Sequence<double>(), 0.0 );
        return;
    }

    const double nOffset(readDouble());
    const sal_Int32 nLen(readInt32());

    uno::Sequence<double> aDashArray(nLen);
    double* pArray=aDashArray.getArray();
    for( sal_Int32 i=0; i<nLen; ++i )
        *pArray++ = readDouble();

    m_pSink->setLineDash( aDashArray, nOffset );
}

void Parser::readLineJoin()
{
    sal_Int8 nJoin(rendering::PathJoinType::MITER);
    switch( readInt32() )
    {
        default:
        case 0: nJoin = rendering::PathJoinType::MITER; break;
        case 1: nJoin = rendering::PathJoinType::ROUND; break;
        case 2: nJoin = rendering::PathJoinType::BEVEL; break;
    }
    m_pSink->setLineJoin(nJoin);
}

void Parser::readTransformation()
{
    geometry::AffineMatrix2D aMat;
    readDouble(aMat.m00);
    readDouble(aMat.m10);
    readDouble(aMat.m01);
    readDouble(aMat.m11);
    readDouble(aMat.m02);
    readDouble(aMat.m12);
    m_pSink->setTransformation( aMat );
}

rendering::ARGBColor Parser::readColor()
{
    rendering::ARGBColor aRes;
    readDouble(aRes.Red);
    readDouble(aRes.Green);
    readDouble(aRes.Blue);
    readDouble(aRes.Alpha);
    return aRes;
}

sal_Int32 Parser::parseFontCheckForString(
    const sal_Unicode* pCopy, sal_Int32 nCopyLen,
    const char* pAttrib, sal_Int32 nAttribLen,
    FontAttributes& rResult, bool bItalic, bool bBold)
{
    if (nCopyLen < nAttribLen)
        return 0;
    for (sal_Int32 i = 0; i < nAttribLen; ++i)
    {
        sal_uInt32 nCode = pAttrib[i];
        if (rtl::toAsciiLowerCase(pCopy[i]) != nCode
            && rtl::toAsciiUpperCase(pCopy[i]) != nCode)
            return 0;
    }
    rResult.isItalic |= bItalic;
    rResult.isBold |= bBold;
    return nAttribLen;
}

sal_Int32 Parser::parseFontRemoveSuffix(
    const sal_Unicode* pCopy, sal_Int32 nCopyLen,
    const char* pAttrib, sal_Int32 nAttribLen)
{
    if (nCopyLen < nAttribLen)
        return 0;
    for (sal_Int32 i = 0; i < nAttribLen; ++i)
        if ( pCopy[nCopyLen - nAttribLen + i] != pAttrib[i] )
            return 0;
    return nAttribLen;
}

void Parser::parseFontFamilyName( FontAttributes& rResult )
{
    OUStringBuffer aNewFamilyName( rResult.familyName.getLength() );

    const sal_Unicode* pCopy = rResult.familyName.getStr();
    sal_Int32 nLen = rResult.familyName.getLength();
    // parse out truetype subsets (e.g. BAAAAA+Thorndale)
    if( nLen > 8 && pCopy[6] == '+' )
    {
        pCopy += 7;
        nLen -= 7;
    }

    // TODO: Looks like this block needs to be refactored
    while( nLen )
    {
        if (parseFontRemoveSuffix(pCopy, nLen, RTL_CONSTASCII_STRINGPARAM("PSMT")))
        {
            nLen -= RTL_CONSTASCII_LENGTH("PSMT");
        }
        else if (parseFontRemoveSuffix(pCopy, nLen, RTL_CONSTASCII_STRINGPARAM("MT")))
        {
            nLen -= RTL_CONSTASCII_LENGTH("MT");
        }

        if (parseFontCheckForString(pCopy, nLen, RTL_CONSTASCII_STRINGPARAM("Italic"), rResult, true, false))
        {
            sal_Int32 nAttribLen = RTL_CONSTASCII_LENGTH("Italic");
            nLen -= nAttribLen;
            pCopy += nAttribLen;
        }
        else if (parseFontCheckForString(pCopy, nLen, RTL_CONSTASCII_STRINGPARAM("-LightOblique"), rResult, true, false))
        {
            sal_Int32 nAttribLen = RTL_CONSTASCII_LENGTH("-LightOblique");
            nLen -= nAttribLen;
            pCopy += nAttribLen;
        }
        else if (parseFontCheckForString(pCopy, nLen, RTL_CONSTASCII_STRINGPARAM("-Light"), rResult, false, false))
        {
            sal_Int32 nAttribLen = RTL_CONSTASCII_LENGTH("-Light");
            nLen -= nAttribLen;
            pCopy += nAttribLen;
        }
        else if (parseFontCheckForString(pCopy, nLen, RTL_CONSTASCII_STRINGPARAM("-BoldOblique"), rResult, true, true))
        {
            sal_Int32 nAttribLen = RTL_CONSTASCII_LENGTH("-BoldOblique");
            nLen -= nAttribLen;
            pCopy += nAttribLen;
        }
        else if (parseFontCheckForString(pCopy, nLen, RTL_CONSTASCII_STRINGPARAM("-Bold"), rResult, false, true))
        {
            sal_Int32 nAttribLen = RTL_CONSTASCII_LENGTH("-Bold");
            nLen -= nAttribLen;
            pCopy += nAttribLen;
        }
        else if (parseFontCheckForString(pCopy, nLen, RTL_CONSTASCII_STRINGPARAM("Bold"), rResult, false, true))
        {
            sal_Int32 nAttribLen = RTL_CONSTASCII_LENGTH("Bold");
            nLen -= nAttribLen;
            pCopy += nAttribLen;
        }
        else if (parseFontCheckForString(pCopy, nLen, RTL_CONSTASCII_STRINGPARAM("-Roman"), rResult, false, false))
        {
            sal_Int32 nAttribLen = RTL_CONSTASCII_LENGTH("-Roman");
            nLen -= nAttribLen;
            pCopy += nAttribLen;
        }
        else if (parseFontCheckForString(pCopy, nLen, RTL_CONSTASCII_STRINGPARAM("-Oblique"), rResult, true, false))
        {
            sal_Int32 nAttribLen = RTL_CONSTASCII_LENGTH("-Oblique");
            nLen -= nAttribLen;
            pCopy += nAttribLen;
        }
        else if (parseFontCheckForString(pCopy, nLen, RTL_CONSTASCII_STRINGPARAM("-Reg"), rResult, false, false))
        {
            sal_Int32 nAttribLen = RTL_CONSTASCII_LENGTH("-Reg");
            nLen -= nAttribLen;
            pCopy += nAttribLen;
        }
        else if(nLen > 0)
        {
            if( *pCopy != '-' )
                aNewFamilyName.append( *pCopy );
            pCopy++;
            nLen--;
        }
    }
    rResult.familyName = aNewFamilyName.makeStringAndClear();
}

void Parser::readFont()
{
    OString aFontName;
    sal_Int64      nFontID;
    sal_Int32      nIsEmbedded, nIsBold, nIsItalic, nIsUnderline, nFileLen;
    double         nSize;

    readInt64(nFontID);
    readInt32(nIsEmbedded);
    readInt32(nIsBold);
    readInt32(nIsItalic);
    readInt32(nIsUnderline);
    readDouble(nSize);
    readInt32(nFileLen);

    nSize = nSize < 0.0 ? -nSize : nSize;
    aFontName = lcl_unescapeLineFeeds( m_aLine.copy( m_nCharIndex ) );

    // name gobbles up rest of line
    m_nCharIndex = -1;

    FontMapType::const_iterator pFont( m_aFontMap.find(nFontID) );
    if( pFont != m_aFontMap.end() )
    {
        OSL_PRECOND(nFileLen==0,"font data for known font");
        FontAttributes aRes(pFont->second);
        aRes.size = nSize;
        m_pSink->setFont( aRes );

        return;
    }

    // yet unknown font - get info and add to map
    FontAttributes aResult( OStringToOUString( aFontName,
                                                    RTL_TEXTENCODING_UTF8 ),
                            nIsBold != 0,
                            nIsItalic != 0,
                            nIsUnderline != 0,
                            nSize,
                            1.0);

    // extract textual attributes (bold, italic in the name, etc.)
    parseFontFamilyName(aResult);
    // need to read font file?
    if( nFileLen )
    {
        uno::Sequence<sal_Int8> aFontFile(nFileLen);
        readBinaryData( aFontFile );

        awt::FontDescriptor aFD;
        uno::Sequence< uno::Any > aArgs(1);
        aArgs[0] <<= aFontFile;

        try
        {
            uno::Reference< beans::XMaterialHolder > xMat(
                m_xContext->getServiceManager()->createInstanceWithArgumentsAndContext(
                    "com.sun.star.awt.FontIdentificator", aArgs, m_xContext ),
                uno::UNO_QUERY );
            if( xMat.is() )
            {
                uno::Any aRes( xMat->getMaterial() );
                if( aRes >>= aFD )
                {
                    if (!aFD.Name.isEmpty())
                    {
                        aResult.familyName = aFD.Name;
                        parseFontFamilyName(aResult);
                    }
                    aResult.isBold      = (aFD.Weight > 100.0);
                    aResult.isItalic    = (aFD.Slant == awt::FontSlant_OBLIQUE ||
                                           aFD.Slant == awt::FontSlant_ITALIC );
                    aResult.isUnderline = false;
                    aResult.size        = 0;
                }
            }
        }
        catch( uno::Exception& )
        {
        }

        if( aResult.familyName.isEmpty() )
        {
            // last fallback
            aResult.familyName  = "Arial";
            aResult.isUnderline = false;
        }

    }

    if (!m_xDev)
        m_xDev.disposeAndReset(VclPtr<VirtualDevice>::Create());

    vcl::Font font(aResult.familyName, Size(0, 1000));
    m_xDev->SetFont(font);
    FontMetric metric(m_xDev->GetFontMetric());
    aResult.ascent = metric.GetAscent() / 1000.0;

    m_aFontMap[nFontID] = aResult;

    aResult.size = nSize;
    m_pSink->setFont(aResult);
}

uno::Sequence<beans::PropertyValue> Parser::readImageImpl()
{
    OString aToken = readNextToken();
    const sal_Int32 nImageSize( readInt32() );

    OUString           aFileName;
    if( aToken == "PNG" )
        aFileName = "DUMMY.PNG";
    else if( aToken == "JPEG" )
        aFileName = "DUMMY.JPEG";
    else if( aToken == "PBM" )
        aFileName = "DUMMY.PBM";
    else
    {
        SAL_WARN_IF(aToken != "PPM","sdext.pdfimport","Invalid bitmap format");
        aFileName = "DUMMY.PPM";
    }

    uno::Sequence<sal_Int8> aDataSequence(nImageSize);
    readBinaryData( aDataSequence );

    uno::Sequence< uno::Any > aStreamCreationArgs(1);
    aStreamCreationArgs[0] <<= aDataSequence;

    uno::Reference< uno::XComponentContext > xContext( m_xContext, uno::UNO_SET_THROW );
    uno::Reference< lang::XMultiComponentFactory > xFactory( xContext->getServiceManager(), uno::UNO_SET_THROW );
    uno::Reference< io::XInputStream > xDataStream(
        xFactory->createInstanceWithArgumentsAndContext( "com.sun.star.io.SequenceInputStream", aStreamCreationArgs, m_xContext ),
        uno::UNO_QUERY_THROW );

    uno::Sequence<beans::PropertyValue> aSequence( comphelper::InitPropertySequence({
            { "URL", uno::makeAny(aFileName) },
            { "InputStream", uno::makeAny( xDataStream ) },
            { "InputSequence", uno::makeAny(aDataSequence) }
        }));

    return aSequence;
}

void Parser::readImage()
{
    sal_Int32 nWidth, nHeight,nMaskColors;
    readInt32(nWidth);
    readInt32(nHeight);
    readInt32(nMaskColors);

    uno::Sequence<beans::PropertyValue> aImg( readImageImpl() );

    if( nMaskColors )
    {
        uno::Sequence<sal_Int8> aDataSequence(nMaskColors);
        readBinaryData( aDataSequence );

        uno::Sequence<uno::Any> aMaskRanges(2);

        uno::Sequence<double> aMinRange(nMaskColors/2);
        uno::Sequence<double> aMaxRange(nMaskColors/2);
        for( sal_Int32 i=0; i<nMaskColors/2; ++i )
        {
            aMinRange[i] = aDataSequence[i] / 255.0;
            aMaxRange[i] = aDataSequence[i+nMaskColors/2] / 255.0;
        }

        aMaskRanges[0] <<= aMinRange;
        aMaskRanges[1] <<= aMaxRange;

        m_pSink->drawColorMaskedImage( aImg, aMaskRanges );
    }
    else
        m_pSink->drawImage( aImg );
}

void Parser::readMask()
{
    sal_Int32 nWidth, nHeight, nInvert;
    readInt32(nWidth);
    readInt32(nHeight);
    readInt32(nInvert);

    m_pSink->drawMask( readImageImpl(), nInvert != 0);
}

void Parser::readLink()
{
    geometry::RealRectangle2D aBounds;
    readDouble(aBounds.X1);
    readDouble(aBounds.Y1);
    readDouble(aBounds.X2);
    readDouble(aBounds.Y2);

    m_pSink->hyperLink( aBounds,
                        OStringToOUString( lcl_unescapeLineFeeds(
                                m_aLine.copy(m_nCharIndex) ),
                                RTL_TEXTENCODING_UTF8 ) );
    // name gobbles up rest of line
    m_nCharIndex = -1;
}

void Parser::readMaskedImage()
{
    sal_Int32 nWidth, nHeight, nMaskWidth, nMaskHeight, nMaskInvert;
    readInt32(nWidth);
    readInt32(nHeight);
    readInt32(nMaskWidth);
    readInt32(nMaskHeight);
    readInt32(nMaskInvert);

    const uno::Sequence<beans::PropertyValue> aImage( readImageImpl() );
    const uno::Sequence<beans::PropertyValue> aMask ( readImageImpl() );
    m_pSink->drawMaskedImage( aImage, aMask, nMaskInvert != 0 );
}

void Parser::readSoftMaskedImage()
{
    sal_Int32 nWidth, nHeight, nMaskWidth, nMaskHeight;
    readInt32(nWidth);
    readInt32(nHeight);
    readInt32(nMaskWidth);
    readInt32(nMaskHeight);

    const uno::Sequence<beans::PropertyValue> aImage( readImageImpl() );
    const uno::Sequence<beans::PropertyValue> aMask ( readImageImpl() );
    m_pSink->drawAlphaMaskedImage( aImage, aMask );
}

void Parser::parseLine( const OString& rLine )
{
    OSL_PRECOND( m_pSink,         "Invalid sink" );
    OSL_PRECOND( m_pErr,          "Invalid filehandle" );
    OSL_PRECOND( m_xContext.is(), "Invalid service factory" );

    m_nNextToken = 0; m_nCharIndex = 0; m_aLine = rLine;
    const OString& rCmd = readNextToken();
    const hash_entry* pEntry = PdfKeywordHash::in_word_set( rCmd.getStr(),
                                                            rCmd.getLength() );
    OSL_ASSERT(pEntry);
    switch( pEntry->eKey )
    {
        case CLIPPATH:
            m_pSink->intersectClip(readPath()); break;
        case DRAWCHAR:
            readChar(); break;
        case DRAWIMAGE:
            readImage(); break;
        case DRAWLINK:
            readLink(); break;
        case DRAWMASK:
            readMask(); break;
        case DRAWMASKEDIMAGE:
            readMaskedImage(); break;
        case DRAWSOFTMASKEDIMAGE:
            readSoftMaskedImage(); break;
        case ENDPAGE:
            m_pSink->endPage(); break;
        case ENDTEXTOBJECT:
            m_pSink->endText(); break;
        case EOCLIPPATH:
            m_pSink->intersectEoClip(readPath()); break;
        case EOFILLPATH:
            m_pSink->eoFillPath(readPath()); break;
        case FILLPATH:
            m_pSink->fillPath(readPath()); break;
        case RESTORESTATE:
            m_pSink->popState(); break;
        case SAVESTATE:
            m_pSink->pushState(); break;
        case SETPAGENUM:
            m_pSink->setPageNum( readInt32() ); break;
        case STARTPAGE:
        {
            const double nWidth ( readDouble() );
            const double nHeight( readDouble() );
            m_pSink->startPage( geometry::RealSize2D( nWidth, nHeight ) );
            break;
        }
        case STROKEPATH:
            m_pSink->strokePath(readPath()); break;
        case UPDATECTM:
            readTransformation(); break;
        case UPDATEFILLCOLOR:
            m_pSink->setFillColor( readColor() ); break;
        case UPDATEFLATNESS:
            m_pSink->setFlatness( readDouble( ) ); break;
        case UPDATEFONT:
            readFont(); break;
        case UPDATELINECAP:
            readLineCap(); break;
        case UPDATELINEDASH:
            readLineDash(); break;
        case UPDATELINEJOIN:
            readLineJoin(); break;
        case UPDATELINEWIDTH:
            m_pSink->setLineWidth( readDouble() );break;
        case UPDATEMITERLIMIT:
            m_pSink->setMiterLimit( readDouble() ); break;
        case UPDATESTROKECOLOR:
            m_pSink->setStrokeColor( readColor() ); break;
        case UPDATESTROKEOPACITY:
            break;
        case SETTEXTRENDERMODE:
            m_pSink->setTextRenderMode( readInt32() ); break;

        case NONE:
        default:
            OSL_PRECOND(false,"Unknown input");
            break;
    }

    // all consumed?
    SAL_WARN_IF(m_nCharIndex!=-1, "sdext.pdfimport", "leftover scanner input");
}

} // namespace

static bool checkEncryption( const OUString&                               i_rPath,
                             const uno::Reference< task::XInteractionHandler >& i_xIHdl,
                             OUString&                                     io_rPwd,
                             bool&                                              o_rIsEncrypted,
                             const OUString&                               i_rDocName
                             )
{
    bool bSuccess = false;
    OString aPDFFile = OUStringToOString( i_rPath, osl_getThreadTextEncoding() );

    std::unique_ptr<pdfparse::PDFEntry> pEntry( pdfparse::PDFReader::read( aPDFFile.getStr() ));
    if( pEntry )
    {
        pdfparse::PDFFile* pPDFFile = dynamic_cast<pdfparse::PDFFile*>(pEntry.get());
        if( pPDFFile )
        {
            o_rIsEncrypted = pPDFFile->isEncrypted();
            if( o_rIsEncrypted )
            {
                if( pPDFFile->usesSupportedEncryptionFormat() )
                {
                    bool bAuthenticated = false;
                    if( !io_rPwd.isEmpty() )
                    {
                        OString aIsoPwd = OUStringToOString( io_rPwd,
                                                                       RTL_TEXTENCODING_ISO_8859_1 );
                        bAuthenticated = pPDFFile->setupDecryptionData( aIsoPwd.getStr() );
                    }
                    if( bAuthenticated )
                        bSuccess = true;
                    else
                    {
                        if( i_xIHdl.is() )
                        {
                            bool bEntered = false;
                            do
                            {
                                bEntered = getPassword( i_xIHdl, io_rPwd, ! bEntered, i_rDocName );
                                OString aIsoPwd = OUStringToOString( io_rPwd,
                                                                               RTL_TEXTENCODING_ISO_8859_1 );
                                bAuthenticated = pPDFFile->setupDecryptionData( aIsoPwd.getStr() );
                            } while( bEntered && ! bAuthenticated );
                        }

                        bSuccess = bAuthenticated;
                    }
                }
                else if( i_xIHdl.is() )
                {
                    reportUnsupportedEncryptionFormat( i_xIHdl );
                        //TODO: this should either be handled further down the
                        // call stack, or else information that this has already
                        // been handled should be passed down the call stack, so
                        // that SfxBaseModel::load does not show an additional
                        // "General Error" message box
                }
            }
            else
                bSuccess = true;
        }
    }
    return bSuccess;
}

class Buffering
{
    static const int SIZE = 64*1024;
    std::unique_ptr<char[]> aBuffer;
    oslFileHandle& pOut;
    size_t pos;
    sal_uInt64 left;

public:
    explicit Buffering(oslFileHandle& out) : aBuffer(new char[SIZE]), pOut(out), pos(0), left(0) {}

    oslFileError read(char *pChar, short count, sal_uInt64* pBytesRead)
    {
        oslFileError nRes = osl_File_E_None;
        sal_uInt64 nBytesRead = 0;
        while (count > 0)
        {
            if (left == 0)
            {
                nRes = osl_readFile(pOut, aBuffer.get(), SIZE, &left);
                if (nRes != osl_File_E_None || left == 0)
                {
                    *pBytesRead = nBytesRead;
                    return nRes;
                }
                pos = 0;
            }
            *pChar = aBuffer.get()[pos];
            --count;
            ++pos;
            --left;
            ++pChar;
            ++nBytesRead;
        }
        *pBytesRead = nBytesRead;
        return osl_File_E_None;
    }
};

bool xpdf_ImportFromFile(const OUString& rURL,
                         const ContentSinkSharedPtr& rSink,
                         const uno::Reference<task::XInteractionHandler>& xIHdl,
                         const OUString& rPwd,
                         const uno::Reference<uno::XComponentContext>& xContext,
                         const OUString& rFilterOptions)
{
    OSL_ASSERT(rSink);

    OUString aSysUPath;
    if( osl_getSystemPathFromFileURL( rURL.pData, &aSysUPath.pData ) != osl_File_E_None )
    {
        SAL_WARN(
            "sdext.pdfimport",
            "getSystemPathFromFileURL(" << rURL << ") failed");
        return false;
    }
    OUString aDocName( rURL.copy( rURL.lastIndexOf( '/' )+1 ) );

    // check for encryption, if necessary get password
    OUString aPwd( rPwd );
    bool bIsEncrypted = false;
    if( !checkEncryption( aSysUPath, xIHdl, aPwd, bIsEncrypted, aDocName ) )
    {
        SAL_INFO(
            "sdext.pdfimport",
            "checkEncryption(" << aSysUPath << ") failed");
        return false;
    }

    // Determine xpdfimport executable URL:
    OUString converterURL("$BRAND_BASE_DIR/" LIBO_BIN_FOLDER "/xpdfimport");
    rtl::Bootstrap::expandMacros(converterURL); //TODO: detect failure

    // Determine pathname of xpdfimport_err.pdf:
    OUString errPathname("$BRAND_BASE_DIR/" LIBO_SHARE_FOLDER "/xpdfimport/xpdfimport_err.pdf");
    rtl::Bootstrap::expandMacros(errPathname); //TODO: detect failure
    if (osl::FileBase::getSystemPathFromFileURL(errPathname, errPathname)
        != osl::FileBase::E_None)
    {
        SAL_WARN(
            "sdext.pdfimport",
            "getSystemPathFromFileURL(" << errPathname << ") failed");
        return false;
    }

    // spawn separate process to keep LGPL/GPL code apart.

    OUString aOptFlag("-o");
    rtl_uString*  args[] = { aSysUPath.pData, errPathname.pData,
                             aOptFlag.pData, rFilterOptions.pData };
    sal_Int32 nArgs = rFilterOptions.isEmpty() ? 2 : 4;

    oslProcess    aProcess;
    oslFileHandle pIn  = nullptr;
    oslFileHandle pOut = nullptr;
    oslFileHandle pErr = nullptr;
    oslSecurity pSecurity = osl_getCurrentSecurity ();
    oslProcessError eErr =
        osl_executeProcess_WithRedirectedIO(converterURL.pData,
                                            args,
                                            nArgs,
                                            osl_Process_SEARCHPATH|osl_Process_HIDDEN,
                                            pSecurity,
                                            nullptr, nullptr, 0,
                                            &aProcess, &pIn, &pOut, &pErr);
    osl_freeSecurityHandle(pSecurity);

    bool bRet=true;
    try
    {
        if( eErr!=osl_Process_E_None )
        {
            SAL_WARN(
                "sdext.pdfimport",
                "executeProcess of " << converterURL << " failed with "
                    << +eErr);
            return false;
        }

        if( pIn )
        {
            OStringBuffer aBuf(256);
            if( bIsEncrypted )
                aBuf.append( OUStringToOString( aPwd, RTL_TEXTENCODING_ISO_8859_1 ) );
            aBuf.append( '\n' );

            sal_uInt64 nWritten = 0;
            osl_writeFile( pIn, aBuf.getStr(), sal_uInt64(aBuf.getLength()), &nWritten );
        }

        if( pOut && pErr )
        {
            // read results of PDF parser. One line - one call to
            // OutputDev. stderr is used for alternate streams, like
            // embedded fonts and bitmaps
            Parser aParser(rSink,pErr,xContext);
            Buffering aBuffering(pOut);
            OStringBuffer line;
            for( ;; )
            {
                char aChar('\n');
                sal_uInt64 nBytesRead;
                oslFileError nRes;

                // skip garbage \r \n at start of line
                while( osl_File_E_None == (nRes = aBuffering.read(&aChar, 1, &nBytesRead)) &&
                       nBytesRead == 1 &&
                       (aChar == '\n' || aChar == '\r') ) ;
                if ( osl_File_E_None != nRes )
                    break;

                if( aChar != '\n' && aChar != '\r' )
                    line.append( aChar );

                while( osl_File_E_None == (nRes = aBuffering.read(&aChar, 1, &nBytesRead)) &&
                       nBytesRead == 1 && aChar != '\n' && aChar != '\r' )
                {
                    line.append( aChar );
                }
                if ( osl_File_E_None != nRes )
                    break;
                if ( line.isEmpty() )
                    break;

                aParser.parseLine(line.makeStringAndClear());
            }
        }
    }
    catch( uno::Exception& )
    {
        // crappy C file interface. need manual resource dealloc
        bRet = false;
    }

    if( pIn )
        osl_closeFile(pIn);
    if( pOut )
        osl_closeFile(pOut);
    if( pErr )
        osl_closeFile(pErr);
    eErr = osl_joinProcess(aProcess);
    if (eErr == osl_Process_E_None)
    {
        oslProcessInfo info;
        info.Size = sizeof info;
        eErr = osl_getProcessInfo(aProcess, osl_Process_EXITCODE, &info);
        if (eErr == osl_Process_E_None)
        {
            if (info.Code != 0)
            {
                SAL_WARN(
                    "sdext.pdfimport",
                    "getProcessInfo of " << converterURL
                        << " failed with exit code " << info.Code);
                bRet = false;
            }
        }
        else
        {
            SAL_WARN(
                "sdext.pdfimport",
                "getProcessInfo of " << converterURL << " failed with "
                    << +eErr);
            bRet = false;
        }
    }
    else
    {
        SAL_WARN(
            "sdext.pdfimport",
            "joinProcess of " << converterURL << " failed with " << +eErr);
        bRet = false;
    }
    osl_freeProcessHandle(aProcess);
    return bRet;
}


bool xpdf_ImportFromStream( const uno::Reference< io::XInputStream >&         xInput,
                            const ContentSinkSharedPtr&                       rSink,
                            const uno::Reference<task::XInteractionHandler >& xIHdl,
                            const OUString&                              rPwd,
                            const uno::Reference< uno::XComponentContext >&   xContext,
                            const OUString&                                   rFilterOptions )
{
    OSL_ASSERT(xInput.is());
    OSL_ASSERT(rSink);

    // convert XInputStream to local temp file
    oslFileHandle aFile = nullptr;
    OUString aURL;
    if( osl_createTempFile( nullptr, &aFile, &aURL.pData ) != osl_File_E_None )
        return false;

    // copy content, buffered...
    const sal_uInt32 nBufSize = 4096;
    uno::Sequence<sal_Int8> aBuf( nBufSize );
    sal_uInt64 nBytes = 0;
    sal_uInt64 nWritten = 0;
    bool bSuccess = true;
    do
    {
        try
        {
            nBytes = xInput->readBytes( aBuf, nBufSize );
        }
        catch( css::uno::Exception& )
        {
            osl_closeFile( aFile );
            throw;
        }
        if( nBytes > 0 )
        {
            osl_writeFile( aFile, aBuf.getConstArray(), nBytes, &nWritten );
            if( nWritten != nBytes )
            {
                bSuccess = false;
                break;
            }
        }
    }
    while( nBytes == nBufSize );

    osl_closeFile( aFile );

    if ( bSuccess )
        bSuccess = xpdf_ImportFromFile( aURL, rSink, xIHdl, rPwd, xContext, rFilterOptions );
    osl_removeFile( aURL.pData );

    return bSuccess;
}

}

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