//========================================================================
//
// Stream.h
//
// Copyright 1996-2003 Glyph & Cog, LLC
//
//========================================================================

//========================================================================
//
// Modified under the Poppler project - http://poppler.freedesktop.org
//
// All changes made under the Poppler project to this file are licensed
// under GPL version 2 or later
//
// Copyright (C) 2005 Jeff Muizelaar <jeff@infidigm.net>
// Copyright (C) 2008 Julien Rebetez <julien@fhtagn.net>
// Copyright (C) 2008, 2010, 2011, 2016-2021 Albert Astals Cid <aacid@kde.org>
// Copyright (C) 2009 Carlos Garcia Campos <carlosgc@gnome.org>
// Copyright (C) 2009 Stefan Thomas <thomas@eload24.com>
// Copyright (C) 2010 Hib Eris <hib@hiberis.nl>
// Copyright (C) 2011, 2012, 2016, 2020 William Bader <williambader@hotmail.com>
// Copyright (C) 2012, 2013 Thomas Freitag <Thomas.Freitag@alfa.de>
// Copyright (C) 2012, 2013 Fabio D'Urso <fabiodurso@hotmail.it>
// Copyright (C) 2013, 2017 Adrian Johnson <ajohnson@redneon.com>
// Copyright (C) 2013 Peter Breitenlohner <peb@mppmu.mpg.de>
// Copyright (C) 2013, 2018 Adam Reichold <adamreichold@myopera.com>
// Copyright (C) 2013 Pino Toscano <pino@kde.org>
// Copyright (C) 2019 Volker Krause <vkrause@kde.org>
// Copyright (C) 2019 Alexander Volkov <a.volkov@rusbitech.ru>
// Copyright (C) 2020, 2021 Oliver Sander <oliver.sander@tu-dresden.de>
// Copyright (C) 2020 Philipp Knechtges <philipp-dev@knechtges.com>
// Copyright (C) 2021 Hubert Figuiere <hub@figuiere.net>
// Copyright (C) 2021 Christian Persch <chpe@src.gnome.org>
// Copyright (C) 2021 Georgiy Sgibnev <georgiy@sgibnev.com>. Work sponsored by lab50.net.
//
// To see a description of the changes please see the Changelog file that
// came with your tarball or type make ChangeLog if you are building from git
//
//========================================================================

#ifndef STREAM_H
#define STREAM_H

#include <atomic>
#include <cstdio>

#include "poppler-config.h"
#include "poppler_private_export.h"
#include "Object.h"

class GooFile;
class BaseStream;
class CachedFile;
class SplashBitmap;

//------------------------------------------------------------------------

enum StreamKind
{
    strFile,
    strCachedFile,
    strASCIIHex,
    strASCII85,
    strLZW,
    strRunLength,
    strCCITTFax,
    strDCT,
    strFlate,
    strJBIG2,
    strJPX,
    strWeird, // internal-use stream types
    strCrypt // internal-use to detect decode streams
};

enum StreamColorSpaceMode
{
    streamCSNone,
    streamCSDeviceGray,
    streamCSDeviceRGB,
    streamCSDeviceCMYK
};

//------------------------------------------------------------------------

// This is in Stream.h instead of Decrypt.h to avoid really annoying
// include file dependency loops.
enum CryptAlgorithm
{
    cryptRC4,
    cryptAES,
    cryptAES256,
    cryptNone
};

//------------------------------------------------------------------------

typedef struct _ByteRange
{
    unsigned int offset;
    unsigned int length;
} ByteRange;

//------------------------------------------------------------------------
// Stream (base class)
//------------------------------------------------------------------------

class POPPLER_PRIVATE_EXPORT Stream
{
public:
    // Constructor.
    Stream();

    // Destructor.
    virtual ~Stream();

    Stream(const Stream &) = delete;
    Stream &operator=(const Stream &other) = delete;

    // Get kind of stream.
    virtual StreamKind getKind() const = 0;

    // Reset stream to beginning.
    virtual void reset() = 0;

    // Close down the stream.
    virtual void close();

    inline int doGetChars(int nChars, unsigned char *buffer)
    {
        if (hasGetChars()) {
            return getChars(nChars, buffer);
        } else {
            for (int i = 0; i < nChars; ++i) {
                const int c = getChar();
                if (likely(c != EOF))
                    buffer[i] = c;
                else
                    return i;
            }
            return nChars;
        }
    }

    inline void fillString(std::string &s)
    {
        unsigned char readBuf[4096];
        int readChars;
        reset();
        while ((readChars = doGetChars(4096, readBuf)) != 0) {
            s.append((const char *)readBuf, readChars);
        }
    }

    inline void fillGooString(GooString *s) { fillString(s->toNonConstStr()); }

    inline unsigned char *toUnsignedChars(int *length, int initialSize = 4096, int sizeIncrement = 4096)
    {
        int readChars;
        unsigned char *buf = (unsigned char *)gmalloc(initialSize);
        int size = initialSize;
        *length = 0;
        int charsToRead = initialSize;
        bool continueReading = true;
        reset();
        while (continueReading && (readChars = doGetChars(charsToRead, &buf[*length])) != 0) {
            *length += readChars;
            if (readChars == charsToRead) {
                if (lookChar() != EOF) {
                    size += sizeIncrement;
                    charsToRead = sizeIncrement;
                    buf = (unsigned char *)grealloc(buf, size);
                } else {
                    continueReading = false;
                }
            } else {
                continueReading = false;
            }
        }
        return buf;
    }

    // Get next char from stream.
    virtual int getChar() = 0;

    // Peek at next char in stream.
    virtual int lookChar() = 0;

    // Get next char from stream without using the predictor.
    // This is only used by StreamPredictor.
    virtual int getRawChar();
    virtual void getRawChars(int nChars, int *buffer);

    // Get next char directly from stream source, without filtering it
    virtual int getUnfilteredChar() = 0;

    // Resets the stream without reading anything (even not the headers)
    // WARNING: Reading the stream with something else than getUnfilteredChar
    // may lead to unexcepted behaviour until you call reset ()
    virtual void unfilteredReset() = 0;

    // Get next line from stream.
    virtual char *getLine(char *buf, int size);

    // Discard the next <n> bytes from stream.  Returns the number of
    // bytes discarded, which will be less than <n> only if EOF is
    // reached.
    virtual unsigned int discardChars(unsigned int n);

    // Get current position in file.
    virtual Goffset getPos() = 0;

    // Go to a position in the stream.  If <dir> is negative, the
    // position is from the end of the file; otherwise the position is
    // from the start of the file.
    virtual void setPos(Goffset pos, int dir = 0) = 0;

    // Get PostScript command for the filter(s).
    virtual GooString *getPSFilter(int psLevel, const char *indent);

    // Does this stream type potentially contain non-printable chars?
    virtual bool isBinary(bool last = true) const = 0;

    // Get the BaseStream of this stream.
    virtual BaseStream *getBaseStream() = 0;

    // Get the stream after the last decoder (this may be a BaseStream
    // or a DecryptStream).
    virtual Stream *getUndecodedStream() = 0;

    // Get the dictionary associated with this stream.
    virtual Dict *getDict() = 0;
    virtual Object *getDictObject() = 0;

    // Is this an encoding filter?
    virtual bool isEncoder() const { return false; }

    // Get image parameters which are defined by the stream contents.
    virtual void getImageParams(int * /*bitsPerComponent*/, StreamColorSpaceMode * /*csMode*/) { }

    // Return the next stream in the "stack".
    virtual Stream *getNextStream() const { return nullptr; }

    // Add filters to this stream according to the parameters in <dict>.
    // Returns the new stream.
    Stream *addFilters(Dict *dict, int recursion = 0);

    // Returns true if this stream includes a crypt filter.
    bool isEncrypted() const;

private:
    friend class Object; // for incRef/decRef

    // Reference counting.
    int incRef() { return ++ref; }
    int decRef() { return --ref; }

    virtual bool hasGetChars() { return false; }
    virtual int getChars(int nChars, unsigned char *buffer);

    Stream *makeFilter(const char *name, Stream *str, Object *params, int recursion = 0, Dict *dict = nullptr);

    std::atomic_int ref; // reference count
};

//------------------------------------------------------------------------
// OutStream
//
// This is the base class for all streams that output to a file
//------------------------------------------------------------------------
class POPPLER_PRIVATE_EXPORT OutStream
{
public:
    // Constructor.
    OutStream();

    // Desctructor.
    virtual ~OutStream();

    OutStream(const OutStream &) = delete;
    OutStream &operator=(const OutStream &other) = delete;

    // Close the stream
    virtual void close() = 0;

    // Return position in stream
    virtual Goffset getPos() = 0;

    // Put a char in the stream
    virtual void put(char c) = 0;

    virtual void printf(const char *format, ...) GCC_PRINTF_FORMAT(2, 3) = 0;
};

//------------------------------------------------------------------------
// FileOutStream
//------------------------------------------------------------------------
class POPPLER_PRIVATE_EXPORT FileOutStream : public OutStream
{
public:
    FileOutStream(FILE *fa, Goffset startA);

    ~FileOutStream() override;

    void close() override;

    Goffset getPos() override;

    void put(char c) override;

    void printf(const char *format, ...) override GCC_PRINTF_FORMAT(2, 3);

private:
    FILE *f;
    Goffset start;
};

//------------------------------------------------------------------------
// BaseStream
//
// This is the base class for all streams that read directly from a file.
//------------------------------------------------------------------------

class POPPLER_PRIVATE_EXPORT BaseStream : public Stream
{
public:
    // TODO Mirar si puedo hacer que dictA sea un puntero
    BaseStream(Object &&dictA, Goffset lengthA);
    ~BaseStream() override;
    virtual BaseStream *copy() = 0;
    virtual Stream *makeSubStream(Goffset start, bool limited, Goffset length, Object &&dict) = 0;
    void setPos(Goffset pos, int dir = 0) override = 0;
    bool isBinary(bool last = true) const override { return last; }
    BaseStream *getBaseStream() override { return this; }
    Stream *getUndecodedStream() override { return this; }
    Dict *getDict() override { return dict.getDict(); }
    Object *getDictObject() override { return &dict; }
    virtual GooString *getFileName() { return nullptr; }
    virtual Goffset getLength() { return length; }

    // Get/set position of first byte of stream within the file.
    virtual Goffset getStart() = 0;
    virtual void moveStart(Goffset delta) = 0;

protected:
    Goffset length;
    Object dict;
};

//------------------------------------------------------------------------
// BaseInputStream
//------------------------------------------------------------------------

class POPPLER_PRIVATE_EXPORT BaseSeekInputStream : public BaseStream
{
public:
    // This enum is used to tell the seek() method how it must reposition
    // the stream offset.
    enum SeekType
    {
        SeekSet, // the offset is set to offset bytes
        SeekCur, // the offset is set to its current location plus offset bytes
        SeekEnd // the offset is set to the size of the stream plus offset bytes
    };

    BaseSeekInputStream(Goffset startA, bool limitedA, Goffset lengthA, Object &&dictA);
    ~BaseSeekInputStream() override;
    StreamKind getKind() const override { return strWeird; }
    void reset() override;
    void close() override;
    int getChar() override { return (bufPtr >= bufEnd && !fillBuf()) ? EOF : (*bufPtr++ & 0xff); }
    int lookChar() override { return (bufPtr >= bufEnd && !fillBuf()) ? EOF : (*bufPtr & 0xff); }
    Goffset getPos() override { return bufPos + (bufPtr - buf); }
    void setPos(Goffset pos, int dir = 0) override;
    Goffset getStart() override { return start; }
    void moveStart(Goffset delta) override;

    int getUnfilteredChar() override { return getChar(); }
    void unfilteredReset() override { reset(); }

protected:
    Goffset start;
    bool limited;

private:
    bool fillBuf();

    bool hasGetChars() override { return true; }
    int getChars(int nChars, unsigned char *buffer) override;

    virtual Goffset currentPos() const = 0;
    virtual void setCurrentPos(Goffset offset) = 0;
    virtual Goffset read(char *buf, Goffset size) = 0;

    static constexpr int seekInputStreamBufSize = 1024;
    char buf[seekInputStreamBufSize];
    char *bufPtr;
    char *bufEnd;
    Goffset bufPos;
    Goffset savePos;
    bool saved;
};

//------------------------------------------------------------------------
// FilterStream
//
// This is the base class for all streams that filter another stream.
//------------------------------------------------------------------------

class FilterStream : public Stream
{
public:
    explicit FilterStream(Stream *strA);
    ~FilterStream() override;
    void close() override;
    Goffset getPos() override { return str->getPos(); }
    void setPos(Goffset pos, int dir = 0) override;
    BaseStream *getBaseStream() override { return str->getBaseStream(); }
    Stream *getUndecodedStream() override { return str->getUndecodedStream(); }
    Dict *getDict() override { return str->getDict(); }
    Object *getDictObject() override { return str->getDictObject(); }
    Stream *getNextStream() const override { return str; }

    int getUnfilteredChar() override { return str->getUnfilteredChar(); }
    void unfilteredReset() override { str->unfilteredReset(); }

protected:
    Stream *str;
};

//------------------------------------------------------------------------
// ImageStream
//------------------------------------------------------------------------

class POPPLER_PRIVATE_EXPORT ImageStream
{
public:
    // Create an image stream object for an image with the specified
    // parameters.  Note that these are the actual image parameters,
    // which may be different from the predictor parameters.
    ImageStream(Stream *strA, int widthA, int nCompsA, int nBitsA);

    ~ImageStream();

    ImageStream(const ImageStream &) = delete;
    ImageStream &operator=(const ImageStream &other) = delete;

    // Reset the stream.
    void reset();

    // Close the stream previously reset
    void close();

    // Gets the next pixel from the stream.  <pix> should be able to hold
    // at least nComps elements.  Returns false at end of file.
    bool getPixel(unsigned char *pix);

    // Returns a pointer to the next line of pixels.  Returns NULL at
    // end of file.
    unsigned char *getLine();

    // Skip an entire line from the image.
    void skipLine();

private:
    Stream *str; // base stream
    int width; // pixels per line
    int nComps; // components per pixel
    int nBits; // bits per component
    int nVals; // components per line
    int inputLineSize; // input line buffer size
    unsigned char *inputLine; // input line buffer
    unsigned char *imgLine; // line buffer
    int imgIdx; // current index in imgLine
};

//------------------------------------------------------------------------
// StreamPredictor
//------------------------------------------------------------------------

class StreamPredictor
{
public:
    // Create a predictor object.  Note that the parameters are for the
    // predictor, and may not match the actual image parameters.
    StreamPredictor(Stream *strA, int predictorA, int widthA, int nCompsA, int nBitsA);

    ~StreamPredictor();

    StreamPredictor(const StreamPredictor &) = delete;
    StreamPredictor &operator=(const StreamPredictor &) = delete;

    bool isOk() { return ok; }

    int lookChar();
    int getChar();
    int getChars(int nChars, unsigned char *buffer);

private:
    bool getNextLine();

    Stream *str; // base stream
    int predictor; // predictor
    int width; // pixels per line
    int nComps; // components per pixel
    int nBits; // bits per component
    int nVals; // components per line
    int pixBytes; // bytes per pixel
    int rowBytes; // bytes per line
    unsigned char *predLine; // line buffer
    int predIdx; // current index in predLine
    bool ok;
};

//------------------------------------------------------------------------
// FileStream
//------------------------------------------------------------------------

#define fileStreamBufSize 256

class POPPLER_PRIVATE_EXPORT FileStream : public BaseStream
{
public:
    FileStream(GooFile *fileA, Goffset startA, bool limitedA, Goffset lengthA, Object &&dictA);
    ~FileStream() override;
    BaseStream *copy() override;
    Stream *makeSubStream(Goffset startA, bool limitedA, Goffset lengthA, Object &&dictA) override;
    StreamKind getKind() const override { return strFile; }
    void reset() override;
    void close() override;
    int getChar() override { return (bufPtr >= bufEnd && !fillBuf()) ? EOF : (*bufPtr++ & 0xff); }
    int lookChar() override { return (bufPtr >= bufEnd && !fillBuf()) ? EOF : (*bufPtr & 0xff); }
    Goffset getPos() override { return bufPos + (bufPtr - buf); }
    void setPos(Goffset pos, int dir = 0) override;
    Goffset getStart() override { return start; }
    void moveStart(Goffset delta) override;

    int getUnfilteredChar() override { return getChar(); }
    void unfilteredReset() override { reset(); }

    bool getNeedsEncryptionOnSave() const { return needsEncryptionOnSave; }
    void setNeedsEncryptionOnSave(bool needsEncryptionOnSaveA) { needsEncryptionOnSave = needsEncryptionOnSaveA; }

private:
    bool fillBuf();

    bool hasGetChars() override { return true; }
    int getChars(int nChars, unsigned char *buffer) override
    {
        int n, m;

        n = 0;
        while (n < nChars) {
            if (bufPtr >= bufEnd) {
                if (!fillBuf()) {
                    break;
                }
            }
            m = (int)(bufEnd - bufPtr);
            if (m > nChars - n) {
                m = nChars - n;
            }
            memcpy(buffer + n, bufPtr, m);
            bufPtr += m;
            n += m;
        }
        return n;
    }

private:
    GooFile *file;
    Goffset offset;
    Goffset start;
    bool limited;
    char buf[fileStreamBufSize];
    char *bufPtr;
    char *bufEnd;
    Goffset bufPos;
    Goffset savePos;
    bool saved;
    bool needsEncryptionOnSave; // Needed for FileStreams that point to "external" files
                                // and thus when saving we can't do a raw copy
};

//------------------------------------------------------------------------
// CachedFileStream
//------------------------------------------------------------------------

#define cachedStreamBufSize 1024

class POPPLER_PRIVATE_EXPORT CachedFileStream : public BaseStream
{
public:
    CachedFileStream(CachedFile *ccA, Goffset startA, bool limitedA, Goffset lengthA, Object &&dictA);
    ~CachedFileStream() override;
    BaseStream *copy() override;
    Stream *makeSubStream(Goffset startA, bool limitedA, Goffset lengthA, Object &&dictA) override;
    StreamKind getKind() const override { return strCachedFile; }
    void reset() override;
    void close() override;
    int getChar() override { return (bufPtr >= bufEnd && !fillBuf()) ? EOF : (*bufPtr++ & 0xff); }
    int lookChar() override { return (bufPtr >= bufEnd && !fillBuf()) ? EOF : (*bufPtr & 0xff); }
    Goffset getPos() override { return bufPos + (bufPtr - buf); }
    void setPos(Goffset pos, int dir = 0) override;
    Goffset getStart() override { return start; }
    void moveStart(Goffset delta) override;

    int getUnfilteredChar() override { return getChar(); }
    void unfilteredReset() override { reset(); }

private:
    bool fillBuf();

    CachedFile *cc;
    Goffset start;
    bool limited;
    char buf[cachedStreamBufSize];
    char *bufPtr;
    char *bufEnd;
    unsigned int bufPos;
    int savePos;
    bool saved;
};

//------------------------------------------------------------------------
// MemStream
//------------------------------------------------------------------------

template<typename T>
class BaseMemStream : public BaseStream
{
public:
    BaseMemStream(T *bufA, Goffset startA, Goffset lengthA, Object &&dictA) : BaseStream(std::move(dictA), lengthA)
    {
        buf = bufA;
        start = startA;
        length = lengthA;
        bufEnd = buf + start + length;
        bufPtr = buf + start;
    }

    BaseStream *copy() override { return new BaseMemStream(buf, start, length, dict.copy()); }

    Stream *makeSubStream(Goffset startA, bool limited, Goffset lengthA, Object &&dictA) override
    {
        Goffset newLength;

        if (!limited || startA + lengthA > start + length) {
            newLength = start + length - startA;
        } else {
            newLength = lengthA;
        }
        return new BaseMemStream(buf, startA, newLength, std::move(dictA));
    }

    StreamKind getKind() const override { return strWeird; }

    void reset() override { bufPtr = buf + start; }

    void close() override { }

    int getChar() override { return (bufPtr < bufEnd) ? (*bufPtr++ & 0xff) : EOF; }

    int lookChar() override { return (bufPtr < bufEnd) ? (*bufPtr & 0xff) : EOF; }

    Goffset getPos() override { return (int)(bufPtr - buf); }

    void setPos(Goffset pos, int dir = 0) override
    {
        unsigned int i;

        if (dir >= 0) {
            i = pos;
        } else {
            i = start + length - pos;
        }
        if (i < start) {
            i = start;
        } else if (i > start + length) {
            i = start + length;
        }
        bufPtr = buf + i;
    }

    Goffset getStart() override { return start; }

    void moveStart(Goffset delta) override
    {
        start += delta;
        length -= delta;
        bufPtr = buf + start;
    }

    int getUnfilteredChar() override { return getChar(); }

    void unfilteredReset() override { reset(); }

protected:
    T *buf;

private:
    bool hasGetChars() override { return true; }

    int getChars(int nChars, unsigned char *buffer) override
    {
        int n;

        if (unlikely(nChars <= 0)) {
            return 0;
        }
        if (unlikely(bufPtr >= bufEnd)) {
            return 0;
        }
        if (bufEnd - bufPtr < nChars) {
            n = (int)(bufEnd - bufPtr);
        } else {
            n = nChars;
        }
        memcpy(buffer, bufPtr, n);
        bufPtr += n;
        return n;
    }

    Goffset start;
    T *bufEnd;
    T *bufPtr;
};

class POPPLER_PRIVATE_EXPORT MemStream : public BaseMemStream<const char>
{
public:
    MemStream(const char *bufA, Goffset startA, Goffset lengthA, Object &&dictA) : BaseMemStream(bufA, startA, lengthA, std::move(dictA)) { }
    ~MemStream() override;
};

class AutoFreeMemStream : public BaseMemStream<char>
{
    bool filterRemovalForbidden = false;

public:
    // AutoFreeMemStream takes ownership over the buffer.
    // The buffer should be created using gmalloc().
    AutoFreeMemStream(char *bufA, Goffset startA, Goffset lengthA, Object &&dictA) : BaseMemStream(bufA, startA, lengthA, std::move(dictA)) { }
    ~AutoFreeMemStream() override;

    // A hack to deal with the strange behaviour of PDFDoc::writeObject().
    bool isFilterRemovalForbidden() const;
    void setFilterRemovalForbidden(bool forbidden);
};

//------------------------------------------------------------------------
// EmbedStream
//
// This is a special stream type used for embedded streams (inline
// images).  It reads directly from the base stream -- after the
// EmbedStream is deleted, reads from the base stream will proceed where
// the BaseStream left off.  Note that this is very different behavior
// that creating a new FileStream (using makeSubStream).
//------------------------------------------------------------------------

class POPPLER_PRIVATE_EXPORT EmbedStream : public BaseStream
{
public:
    EmbedStream(Stream *strA, Object &&dictA, bool limitedA, Goffset lengthA, bool reusableA = false);
    ~EmbedStream() override;
    BaseStream *copy() override;
    Stream *makeSubStream(Goffset start, bool limitedA, Goffset lengthA, Object &&dictA) override;
    StreamKind getKind() const override { return str->getKind(); }
    void reset() override;
    int getChar() override;
    int lookChar() override;
    Goffset getPos() override;
    void setPos(Goffset pos, int dir = 0) override;
    Goffset getStart() override;
    void moveStart(Goffset delta) override;

    int getUnfilteredChar() override { return str->getUnfilteredChar(); }
    void unfilteredReset() override { str->unfilteredReset(); }

    void rewind();
    void restore();

private:
    bool hasGetChars() override { return true; }
    int getChars(int nChars, unsigned char *buffer) override;

    Stream *str;
    bool limited;
    bool reusable;
    bool record;
    bool replay;
    unsigned char *bufData;
    long bufMax;
    long bufLen;
    long bufPos;
    Goffset start;
};

//------------------------------------------------------------------------
// ASCIIHexStream
//------------------------------------------------------------------------

class ASCIIHexStream : public FilterStream
{
public:
    explicit ASCIIHexStream(Stream *strA);
    ~ASCIIHexStream() override;
    StreamKind getKind() const override { return strASCIIHex; }
    void reset() override;
    int getChar() override
    {
        int c = lookChar();
        buf = EOF;
        return c;
    }
    int lookChar() override;
    GooString *getPSFilter(int psLevel, const char *indent) override;
    bool isBinary(bool last = true) const override;

private:
    int buf;
    bool eof;
};

//------------------------------------------------------------------------
// ASCII85Stream
//------------------------------------------------------------------------

class ASCII85Stream : public FilterStream
{
public:
    explicit ASCII85Stream(Stream *strA);
    ~ASCII85Stream() override;
    StreamKind getKind() const override { return strASCII85; }
    void reset() override;
    int getChar() override
    {
        int ch = lookChar();
        ++index;
        return ch;
    }
    int lookChar() override;
    GooString *getPSFilter(int psLevel, const char *indent) override;
    bool isBinary(bool last = true) const override;

private:
    int c[5];
    int b[4];
    int index, n;
    bool eof;
};

//------------------------------------------------------------------------
// LZWStream
//------------------------------------------------------------------------

class LZWStream : public FilterStream
{
public:
    LZWStream(Stream *strA, int predictor, int columns, int colors, int bits, int earlyA);
    ~LZWStream() override;
    StreamKind getKind() const override { return strLZW; }
    void reset() override;
    int getChar() override;
    int lookChar() override;
    int getRawChar() override;
    void getRawChars(int nChars, int *buffer) override;
    GooString *getPSFilter(int psLevel, const char *indent) override;
    bool isBinary(bool last = true) const override;

private:
    bool hasGetChars() override { return true; }
    int getChars(int nChars, unsigned char *buffer) override;

    inline int doGetRawChar()
    {
        if (eof) {
            return EOF;
        }
        if (seqIndex >= seqLength) {
            if (!processNextCode()) {
                return EOF;
            }
        }
        return seqBuf[seqIndex++];
    }

    StreamPredictor *pred; // predictor
    int early; // early parameter
    bool eof; // true if at eof
    unsigned int inputBuf; // input buffer
    int inputBits; // number of bits in input buffer
    struct
    { // decoding table
        int length;
        int head;
        unsigned char tail;
    } table[4097];
    int nextCode; // next code to be used
    int nextBits; // number of bits in next code word
    int prevCode; // previous code used in stream
    int newChar; // next char to be added to table
    unsigned char seqBuf[4097]; // buffer for current sequence
    int seqLength; // length of current sequence
    int seqIndex; // index into current sequence
    bool first; // first code after a table clear

    bool processNextCode();
    void clearTable();
    int getCode();
};

//------------------------------------------------------------------------
// RunLengthStream
//------------------------------------------------------------------------

class RunLengthStream : public FilterStream
{
public:
    explicit RunLengthStream(Stream *strA);
    ~RunLengthStream() override;
    StreamKind getKind() const override { return strRunLength; }
    void reset() override;
    int getChar() override { return (bufPtr >= bufEnd && !fillBuf()) ? EOF : (*bufPtr++ & 0xff); }
    int lookChar() override { return (bufPtr >= bufEnd && !fillBuf()) ? EOF : (*bufPtr & 0xff); }
    GooString *getPSFilter(int psLevel, const char *indent) override;
    bool isBinary(bool last = true) const override;

private:
    bool hasGetChars() override { return true; }
    int getChars(int nChars, unsigned char *buffer) override;

    char buf[128]; // buffer
    char *bufPtr; // next char to read
    char *bufEnd; // end of buffer
    bool eof;

    bool fillBuf();
};

//------------------------------------------------------------------------
// CCITTFaxStream
//------------------------------------------------------------------------

struct CCITTCodeTable;

class CCITTFaxStream : public FilterStream
{
public:
    CCITTFaxStream(Stream *strA, int encodingA, bool endOfLineA, bool byteAlignA, int columnsA, int rowsA, bool endOfBlockA, bool blackA, int damagedRowsBeforeErrorA);
    ~CCITTFaxStream() override;
    StreamKind getKind() const override { return strCCITTFax; }
    void reset() override;
    int getChar() override
    {
        int c = lookChar();
        buf = EOF;
        return c;
    }
    int lookChar() override;
    GooString *getPSFilter(int psLevel, const char *indent) override;
    bool isBinary(bool last = true) const override;

    void unfilteredReset() override;

    int getEncoding() { return encoding; }
    bool getEndOfLine() { return endOfLine; }
    bool getEncodedByteAlign() { return byteAlign; }
    bool getEndOfBlock() { return endOfBlock; }
    int getColumns() { return columns; }
    bool getBlackIs1() { return black; }
    int getDamagedRowsBeforeError() { return damagedRowsBeforeError; }

private:
    void ccittReset(bool unfiltered);
    int encoding; // 'K' parameter
    bool endOfLine; // 'EndOfLine' parameter
    bool byteAlign; // 'EncodedByteAlign' parameter
    int columns; // 'Columns' parameter
    int rows; // 'Rows' parameter
    bool endOfBlock; // 'EndOfBlock' parameter
    bool black; // 'BlackIs1' parameter
    int damagedRowsBeforeError; // 'DamagedRowsBeforeError' parameter
    bool eof; // true if at eof
    bool nextLine2D; // true if next line uses 2D encoding
    int row; // current row
    unsigned int inputBuf; // input buffer
    int inputBits; // number of bits in input buffer
    int *codingLine; // coding line changing elements
    int *refLine; // reference line changing elements
    int a0i; // index into codingLine
    bool err; // error on current line
    int outputBits; // remaining ouput bits
    int buf; // character buffer

    void addPixels(int a1, int blackPixels);
    void addPixelsNeg(int a1, int blackPixels);
    short getTwoDimCode();
    short getWhiteCode();
    short getBlackCode();
    short lookBits(int n);
    void eatBits(int n)
    {
        if ((inputBits -= n) < 0)
            inputBits = 0;
    }
};

#ifndef ENABLE_LIBJPEG
//------------------------------------------------------------------------
// DCTStream
//------------------------------------------------------------------------

// DCT component info
struct DCTCompInfo
{
    int id; // component ID
    int hSample, vSample; // horiz/vert sampling resolutions
    int quantTable; // quantization table number
    int prevDC; // DC coefficient accumulator
};

struct DCTScanInfo
{
    bool comp[4]; // comp[i] is set if component i is
                  //   included in this scan
    int numComps; // number of components in the scan
    int dcHuffTable[4]; // DC Huffman table numbers
    int acHuffTable[4]; // AC Huffman table numbers
    int firstCoeff, lastCoeff; // first and last DCT coefficient
    int ah, al; // successive approximation parameters
};

// DCT Huffman decoding table
struct DCTHuffTable
{
    unsigned char firstSym[17]; // first symbol for this bit length
    unsigned short firstCode[17]; // first code for this bit length
    unsigned short numCodes[17]; // number of codes of this bit length
    unsigned char sym[256]; // symbols
};

class DCTStream : public FilterStream
{
public:
    DCTStream(Stream *strA, int colorXformA, Dict *dict, int recursion);
    ~DCTStream() override;
    StreamKind getKind() const override { return strDCT; }
    void reset() override;
    void close() override;
    int getChar() override;
    int lookChar() override;
    GooString *getPSFilter(int psLevel, const char *indent) override;
    bool isBinary(bool last = true) const override;

    void unfilteredReset() override;

private:
    void dctReset(bool unfiltered);
    bool progressive; // set if in progressive mode
    bool interleaved; // set if in interleaved mode
    int width, height; // image size
    int mcuWidth, mcuHeight; // size of min coding unit, in data units
    int bufWidth, bufHeight; // frameBuf size
    DCTCompInfo compInfo[4]; // info for each component
    DCTScanInfo scanInfo; // info for the current scan
    int numComps; // number of components in image
    int colorXform; // color transform: -1 = unspecified
                    //                   0 = none
                    //                   1 = YUV/YUVK -> RGB/CMYK
    bool gotJFIFMarker; // set if APP0 JFIF marker was present
    bool gotAdobeMarker; // set if APP14 Adobe marker was present
    int restartInterval; // restart interval, in MCUs
    unsigned short quantTables[4][64]; // quantization tables
    int numQuantTables; // number of quantization tables
    DCTHuffTable dcHuffTables[4]; // DC Huffman tables
    DCTHuffTable acHuffTables[4]; // AC Huffman tables
    int numDCHuffTables; // number of DC Huffman tables
    int numACHuffTables; // number of AC Huffman tables
    unsigned char *rowBuf[4][32]; // buffer for one MCU (non-progressive mode)
    int *frameBuf[4]; // buffer for frame (progressive mode)
    int comp, x, y, dy; // current position within image/MCU
    int restartCtr; // MCUs left until restart
    int restartMarker; // next restart marker
    int eobRun; // number of EOBs left in the current run
    int inputBuf; // input buffer for variable length codes
    int inputBits; // number of valid bits in input buffer

    void restart();
    bool readMCURow();
    void readScan();
    bool readDataUnit(DCTHuffTable *dcHuffTable, DCTHuffTable *acHuffTable, int *prevDC, int data[64]);
    bool readProgressiveDataUnit(DCTHuffTable *dcHuffTable, DCTHuffTable *acHuffTable, int *prevDC, int data[64]);
    void decodeImage();
    void transformDataUnit(unsigned short *quantTable, int dataIn[64], unsigned char dataOut[64]);
    int readHuffSym(DCTHuffTable *table);
    int readAmp(int size);
    int readBit();
    bool readHeader();
    bool readBaselineSOF();
    bool readProgressiveSOF();
    bool readScanInfo();
    bool readQuantTables();
    bool readHuffmanTables();
    bool readRestartInterval();
    bool readJFIFMarker();
    bool readAdobeMarker();
    bool readTrailer();
    int readMarker();
    int read16();
};

#endif

#ifndef ENABLE_ZLIB_UNCOMPRESS
//------------------------------------------------------------------------
// FlateStream
//------------------------------------------------------------------------

#    define flateWindow 32768 // buffer size
#    define flateMask (flateWindow - 1)
#    define flateMaxHuffman 15 // max Huffman code length
#    define flateMaxCodeLenCodes 19 // max # code length codes
#    define flateMaxLitCodes 288 // max # literal codes
#    define flateMaxDistCodes 30 // max # distance codes

// Huffman code table entry
struct FlateCode
{
    unsigned short len; // code length, in bits
    unsigned short val; // value represented by this code
};

struct FlateHuffmanTab
{
    const FlateCode *codes;
    int maxLen;
};

// Decoding info for length and distance code words
struct FlateDecode
{
    int bits; // # extra bits
    int first; // first length/distance
};

class FlateStream : public FilterStream
{
public:
    FlateStream(Stream *strA, int predictor, int columns, int colors, int bits);
    ~FlateStream() override;
    StreamKind getKind() const override { return strFlate; }
    void reset() override;
    int getChar() override;
    int lookChar() override;
    int getRawChar() override;
    void getRawChars(int nChars, int *buffer) override;
    GooString *getPSFilter(int psLevel, const char *indent) override;
    bool isBinary(bool last = true) const override;
    void unfilteredReset() override;

private:
    void flateReset(bool unfiltered);
    inline int doGetRawChar()
    {
        int c;

        while (remain == 0) {
            if (endOfBlock && eof)
                return EOF;
            readSome();
        }
        c = buf[index];
        index = (index + 1) & flateMask;
        --remain;
        return c;
    }

    bool hasGetChars() override { return true; }
    int getChars(int nChars, unsigned char *buffer) override;

    StreamPredictor *pred; // predictor
    unsigned char buf[flateWindow]; // output data buffer
    int index; // current index into output buffer
    int remain; // number valid bytes in output buffer
    int codeBuf; // input buffer
    int codeSize; // number of bits in input buffer
    int // literal and distance code lengths
            codeLengths[flateMaxLitCodes + flateMaxDistCodes];
    FlateHuffmanTab litCodeTab; // literal code table
    FlateHuffmanTab distCodeTab; // distance code table
    bool compressedBlock; // set if reading a compressed block
    int blockLen; // remaining length of uncompressed block
    bool endOfBlock; // set when end of block is reached
    bool eof; // set when end of stream is reached

    static const int // code length code reordering
            codeLenCodeMap[flateMaxCodeLenCodes];
    static const FlateDecode // length decoding info
            lengthDecode[flateMaxLitCodes - 257];
    static const FlateDecode // distance decoding info
            distDecode[flateMaxDistCodes];
    static FlateHuffmanTab // fixed literal code table
            fixedLitCodeTab;
    static FlateHuffmanTab // fixed distance code table
            fixedDistCodeTab;

    void readSome();
    bool startBlock();
    void loadFixedCodes();
    bool readDynamicCodes();
    FlateCode *compHuffmanCodes(const int *lengths, int n, int *maxLen);
    int getHuffmanCodeWord(FlateHuffmanTab *tab);
    int getCodeWord(int bits);
};
#endif

//------------------------------------------------------------------------
// EOFStream
//------------------------------------------------------------------------

class EOFStream : public FilterStream
{
public:
    explicit EOFStream(Stream *strA);
    ~EOFStream() override;
    StreamKind getKind() const override { return strWeird; }
    void reset() override { }
    int getChar() override { return EOF; }
    int lookChar() override { return EOF; }
    GooString *getPSFilter(int /*psLevel*/, const char * /*indent*/) override { return nullptr; }
    bool isBinary(bool /*last = true*/) const override { return false; }
};

//------------------------------------------------------------------------
// BufStream
//------------------------------------------------------------------------

class BufStream : public FilterStream
{
public:
    BufStream(Stream *strA, int bufSizeA);
    ~BufStream() override;
    StreamKind getKind() const override { return strWeird; }
    void reset() override;
    int getChar() override;
    int lookChar() override;
    GooString *getPSFilter(int psLevel, const char *indent) override { return nullptr; }
    bool isBinary(bool last = true) const override;

    int lookChar(int idx);

private:
    int *buf;
    int bufSize;
};

//------------------------------------------------------------------------
// FixedLengthEncoder
//------------------------------------------------------------------------

class FixedLengthEncoder : public FilterStream
{
public:
    FixedLengthEncoder(Stream *strA, int lengthA);
    ~FixedLengthEncoder() override;
    StreamKind getKind() const override { return strWeird; }
    void reset() override;
    int getChar() override;
    int lookChar() override;
    GooString *getPSFilter(int /*psLevel*/, const char * /*indent*/) override { return nullptr; }
    bool isBinary(bool /*last = true*/) const override;
    bool isEncoder() const override { return true; }

private:
    int length;
    int count;
};

//------------------------------------------------------------------------
// ASCIIHexEncoder
//------------------------------------------------------------------------

class ASCIIHexEncoder : public FilterStream
{
public:
    explicit ASCIIHexEncoder(Stream *strA);
    ~ASCIIHexEncoder() override;
    StreamKind getKind() const override { return strWeird; }
    void reset() override;
    int getChar() override { return (bufPtr >= bufEnd && !fillBuf()) ? EOF : (*bufPtr++ & 0xff); }
    int lookChar() override { return (bufPtr >= bufEnd && !fillBuf()) ? EOF : (*bufPtr & 0xff); }
    GooString *getPSFilter(int /*psLevel*/, const char * /*indent*/) override { return nullptr; }
    bool isBinary(bool /*last = true*/) const override { return false; }
    bool isEncoder() const override { return true; }

private:
    char buf[4];
    char *bufPtr;
    char *bufEnd;
    int lineLen;
    bool eof;

    bool fillBuf();
};

//------------------------------------------------------------------------
// ASCII85Encoder
//------------------------------------------------------------------------

class ASCII85Encoder : public FilterStream
{
public:
    explicit ASCII85Encoder(Stream *strA);
    ~ASCII85Encoder() override;
    StreamKind getKind() const override { return strWeird; }
    void reset() override;
    int getChar() override { return (bufPtr >= bufEnd && !fillBuf()) ? EOF : (*bufPtr++ & 0xff); }
    int lookChar() override { return (bufPtr >= bufEnd && !fillBuf()) ? EOF : (*bufPtr & 0xff); }
    GooString *getPSFilter(int /*psLevel*/, const char * /*indent*/) override { return nullptr; }
    bool isBinary(bool /*last = true*/) const override { return false; }
    bool isEncoder() const override { return true; }

private:
    char buf[8];
    char *bufPtr;
    char *bufEnd;
    int lineLen;
    bool eof;

    bool fillBuf();
};

//------------------------------------------------------------------------
// RunLengthEncoder
//------------------------------------------------------------------------

class RunLengthEncoder : public FilterStream
{
public:
    explicit RunLengthEncoder(Stream *strA);
    ~RunLengthEncoder() override;
    StreamKind getKind() const override { return strWeird; }
    void reset() override;
    int getChar() override { return (bufPtr >= bufEnd && !fillBuf()) ? EOF : (*bufPtr++ & 0xff); }
    int lookChar() override { return (bufPtr >= bufEnd && !fillBuf()) ? EOF : (*bufPtr & 0xff); }
    GooString *getPSFilter(int /*psLevel*/, const char * /*indent*/) override { return nullptr; }
    bool isBinary(bool /*last = true*/) const override { return true; }
    bool isEncoder() const override { return true; }

private:
    char buf[131];
    char *bufPtr;
    char *bufEnd;
    char *nextEnd;
    bool eof;

    bool fillBuf();
};

//------------------------------------------------------------------------
// LZWEncoder
//------------------------------------------------------------------------

struct LZWEncoderNode
{
    int byte;
    LZWEncoderNode *next; // next sibling
    LZWEncoderNode *children; // first child
};

class LZWEncoder : public FilterStream
{
public:
    explicit LZWEncoder(Stream *strA);
    ~LZWEncoder() override;
    StreamKind getKind() const override { return strWeird; }
    void reset() override;
    int getChar() override;
    int lookChar() override;
    GooString *getPSFilter(int psLevel, const char *indent) override { return nullptr; }
    bool isBinary(bool last = true) const override { return true; }
    bool isEncoder() const override { return true; }

private:
    LZWEncoderNode table[4096];
    int nextSeq;
    int codeLen;
    unsigned char inBuf[4096];
    int inBufLen;
    int outBuf;
    int outBufLen;
    bool needEOD;

    void fillBuf();
};

//------------------------------------------------------------------------
// CMYKGrayEncoder
//------------------------------------------------------------------------

class CMYKGrayEncoder : public FilterStream
{
public:
    explicit CMYKGrayEncoder(Stream *strA);
    ~CMYKGrayEncoder() override;
    StreamKind getKind() const override { return strWeird; }
    void reset() override;
    int getChar() override { return (bufPtr >= bufEnd && !fillBuf()) ? EOF : (*bufPtr++ & 0xff); }
    int lookChar() override { return (bufPtr >= bufEnd && !fillBuf()) ? EOF : (*bufPtr & 0xff); }
    GooString *getPSFilter(int /*psLevel*/, const char * /*indent*/) override { return nullptr; }
    bool isBinary(bool /*last = true*/) const override { return false; }
    bool isEncoder() const override { return true; }

private:
    char buf[2];
    char *bufPtr;
    char *bufEnd;
    bool eof;

    bool fillBuf();
};

//------------------------------------------------------------------------
// RGBGrayEncoder
//------------------------------------------------------------------------

class RGBGrayEncoder : public FilterStream
{
public:
    explicit RGBGrayEncoder(Stream *strA);
    ~RGBGrayEncoder() override;
    StreamKind getKind() const override { return strWeird; }
    void reset() override;
    int getChar() override { return (bufPtr >= bufEnd && !fillBuf()) ? EOF : (*bufPtr++ & 0xff); }
    int lookChar() override { return (bufPtr >= bufEnd && !fillBuf()) ? EOF : (*bufPtr & 0xff); }
    GooString *getPSFilter(int /*psLevel*/, const char * /*indent*/) override { return nullptr; }
    bool isBinary(bool /*last = true*/) const override { return false; }
    bool isEncoder() const override { return true; }

private:
    char buf[2];
    char *bufPtr;
    char *bufEnd;
    bool eof;

    bool fillBuf();
};

//------------------------------------------------------------------------
// SplashBitmapCMYKEncoder
//
// This stream helps to condense SplashBitmaps (mostly of DeviceN8 type) into
// pure CMYK colors. In particular for a DeviceN8 bitmap it redacts the spot colorants.
//------------------------------------------------------------------------

class SplashBitmapCMYKEncoder : public Stream
{
public:
    explicit SplashBitmapCMYKEncoder(SplashBitmap *bitmapA);
    ~SplashBitmapCMYKEncoder() override;
    StreamKind getKind() const override { return strWeird; }
    void reset() override;
    int getChar() override;
    int lookChar() override;
    GooString *getPSFilter(int /*psLevel*/, const char * /*indent*/) override { return nullptr; }
    bool isBinary(bool /*last = true*/) const override { return true; }

    // Although we are an encoder, we return false here, since we do not want do be auto-deleted by
    // successive streams.
    bool isEncoder() const override { return false; }

    int getUnfilteredChar() override { return getChar(); }
    void unfilteredReset() override { reset(); }

    BaseStream *getBaseStream() override { return nullptr; }
    Stream *getUndecodedStream() override { return this; }

    Dict *getDict() override { return nullptr; }
    Object *getDictObject() override { return nullptr; }

    Goffset getPos() override;
    void setPos(Goffset pos, int dir = 0) override;

private:
    SplashBitmap *bitmap;
    size_t width;
    int height;

    std::vector<unsigned char> buf;
    size_t bufPtr;
    int curLine;

    bool fillBuf();
};

#endif
