//========================================================================
//
// Stream.cc
//
// 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) 2006-2010, 2012-2014, 2016-2021, 2023-2026 Albert Astals Cid <aacid@kde.org>
// Copyright (C) 2007 Krzysztof Kowalczyk <kkowalczyk@gmail.com>
// Copyright (C) 2008 Julien Rebetez <julien@fhtagn.net>
// Copyright (C) 2009 Carlos Garcia Campos <carlosgc@gnome.org>
// Copyright (C) 2009 Glenn Ganz <glenn.ganz@uptime.ch>
// Copyright (C) 2009 Stefan Thomas <thomas@eload24.com>
// Copyright (C) 2010 Hib Eris <hib@hiberis.nl>
// Copyright (C) 2010 Tomas Hoger <thoger@redhat.com>
// Copyright (C) 2011, 2012, 2016, 2020 William Bader <williambader@hotmail.com>
// Copyright (C) 2012, 2013, 2020 Thomas Freitag <Thomas.Freitag@alfa.de>
// Copyright (C) 2012, 2021 Oliver Sander <oliver.sander@tu-dresden.de>
// Copyright (C) 2012 Fabio D'Urso <fabiodurso@hotmail.it>
// Copyright (C) 2012, 2024 Even Rouault <even.rouault@spatialys.com>
// Copyright (C) 2013, 2017, 2018 Adrian Johnson <ajohnson@redneon.com>
// Copyright (C) 2013, 2018 Adam Reichold <adamreichold@myopera.com>
// Copyright (C) 2013 Pino Toscano <pino@kde.org>
// Copyright (C) 2015 Suzuki Toshiya <mpsuzuki@hiroshima-u.ac.jp>
// Copyright (C) 2015 Jason Crain <jason@aquaticape.us>
// Copyright (C) 2017 Jose Aliste <jaliste@src.gnome.org>
// Copyright (C) 2017 Kay Dohmann <k.dohmann@gmx.net>
// Copyright (C) 2019 Christian Persch <chpe@src.gnome.org>
// Copyright (C) 2019 LE GARREC Vincent <legarrec.vincent@gmail.com>
// Copyright (C) 2019 Volker Krause <vkrause@kde.org>
// Copyright (C) 2019 Alexander Volkov <a.volkov@rusbitech.ru>
// Copyright (C) 2020 Philipp Knechtges <philipp-dev@knechtges.com>
// Copyright (C) 2021 Hubert Figuiere <hub@figuiere.net>
// Copyright (C) 2021 Georgiy Sgibnev <georgiy@sgibnev.com>. Work sponsored by lab50.net.
// Copyright (C) 2024-2026 g10 Code GmbH, Author: Sune Stolborg Vuorela <sune@vuorela.dk>
// Copyright (C) 2025 Nelson Benítez León <nbenitezl@gmail.com>
// Copyright (C) 2025 Martin Emrich <dev@martinemrich.me>
// Copyright (C) 2025 Arnav V <arnav0872@gmail.com>
// Copyright (C) 2026 Stefan Brüns <stefan.bruens@rwth-aachen.de>
//
// 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
//
//========================================================================

#include <config.h>

#include <cstdio>
#include <cstdlib>
#include <cstddef>
#include <climits>
#include <cstring>
#include <cctype>
#include "goo/gmem.h"
#include "goo/gfile.h"
#include "poppler-config.h"
#include "Error.h"
#include "Object.h"
#include "Lexer.h"
#include "GfxState.h"
#include "Stream.h"
#include "XRef.h"
#include "JBIG2Stream.h"
#include "Stream-CCITT.h"
#include "CachedFile.h"

#include "splash/SplashBitmap.h"

#if ENABLE_LIBJPEG
#    include "DCTStream.h"
#endif

#if ENABLE_ZLIB_UNCOMPRESS
#    include "FlateStream.h"
#endif

#if ENABLE_LIBOPENJPEG
#    include "JPEG2000Stream.h"
#endif

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

Stream::Stream() = default;

Stream::~Stream() = default;

void Stream::close() { }

int Stream::getRawChar()
{
    error(errInternal, -1, "Internal: called getRawChar() on non-predictor stream");
    return EOF;
}

int Stream::getChars(int /*nChars*/, unsigned char * /*buffer*/)
{
    error(errInternal, -1, "Internal: called getChars() on non-predictor stream");
    return 0;
}

void Stream::getRawChars(int /*nChars*/, int * /*buffer*/)
{
    error(errInternal, -1, "Internal: called getRawChars() on non-predictor stream");
}

char *Stream::getLine(char *buf, int size)
{
    int i;
    int c;

    if (lookChar() == EOF || size < 0) {
        return nullptr;
    }
    for (i = 0; i < size - 1; ++i) {
        c = getChar();
        if (c == EOF || c == '\n') {
            break;
        }
        if (c == '\r') {
            if ((c = lookChar()) == '\n') {
                getChar();
            }
            break;
        }
        buf[i] = c;
    }
    buf[i] = '\0';
    return buf;
}

unsigned int Stream::discardChars(unsigned int n)
{
    unsigned char buf[4096];
    unsigned int count, i, j;

    count = 0;
    while (count < n) {
        if ((i = n - count) > sizeof(buf)) {
            i = static_cast<unsigned int>(sizeof(buf));
        }
        j = static_cast<unsigned int>(doGetChars(static_cast<int>(i), buf));
        count += j;
        if (j != i) {
            break;
        }
    }
    return count;
}

std::optional<std::string> Stream::getPSFilter(int /*psLevel*/, const char * /*indent*/)
{
    return std::string {};
}

static std::unique_ptr<Stream> wrapEOFStream(std::unique_ptr<Stream> str)
{
    if (dynamic_cast<EOFStream *>(str.get())) {
        // str is already a EOFStream, no need to wrap it in another EOFStream
        return str;
    }
    return std::make_unique<EOFStream>(std::move(str));
}

std::unique_ptr<Stream> Stream::addFilters(std::unique_ptr<Stream> str, Dict *dict, int recursion)
{
    Object obj, obj2;
    Object params, params2;
    int i;

    obj = dict->lookup("Filter", recursion);
    if (obj.isNull()) {
        obj = dict->lookup("F", recursion);
    }
    params = dict->lookup("DecodeParms", recursion);
    if (params.isNull()) {
        params = dict->lookup("DP", recursion);
    }
    if (obj.isName()) {
        str = makeFilter(obj.getNameString(), std::move(str), &params, recursion, dict);
    } else if (obj.isArray()) {
        for (i = 0; i < obj.arrayGetLength(); ++i) {
            obj2 = obj.arrayGet(i, recursion);
            if (params.isArray()) {
                params2 = params.arrayGet(i, recursion);
            } else {
                params2.setToNull();
            }
            if (obj2.isName()) {
                str = makeFilter(obj2.getNameString(), std::move(str), &params2, recursion);
            } else {
                error(errSyntaxError, -1, "Bad filter name");
                str = wrapEOFStream(std::move(str));
            }
        }
    } else if (!obj.isNull()) {
        error(errSyntaxError, -1, "Bad 'Filter' attribute in stream");
    }

    return str;
}

bool Stream::isEncrypted() const
{
    for (const Stream *str = this; str != nullptr; str = str->getNextStream()) {
        if (str->getKind() == strCrypt) {
            return true;
        }
    }
    return false;
}

class BaseStreamStream : public Stream
{
public:
    explicit BaseStreamStream(std::unique_ptr<Stream> strA) : str(std::move(strA)) { }
    ~BaseStreamStream() override;

    StreamKind getKind() const override { return str->getBaseStream()->getKind(); }
    [[nodiscard]] bool rewind() override { return str->getBaseStream()->rewind(); }
    int getChar() override { return str->getBaseStream()->getChar(); }
    int lookChar() override { return str->getBaseStream()->lookChar(); }
    bool isBinary(bool /*last*/ = true) const override { return str->getBaseStream()->isBinary(); }
    int getUnfilteredChar() override { return str->getBaseStream()->getUnfilteredChar(); }
    [[nodiscard]] bool unfilteredRewind() override { return str->getBaseStream()->unfilteredRewind(); }
    Goffset getPos() override { return str->getBaseStream()->getPos(); }
    void setPos(Goffset pos, int dir) override { str->getBaseStream()->setPos(pos, dir); }
    BaseStream *getBaseStream() override { return str->getBaseStream()->getBaseStream(); }
    Stream *getUndecodedStream() override { return str->getBaseStream()->getUndecodedStream(); }
    Dict *getDict() override { return str->getBaseStream()->getDict(); }
    Object *getDictObject() override { return str->getBaseStream()->getDictObject(); }

private:
    std::unique_ptr<Stream> str;
};

BaseStreamStream::~BaseStreamStream() = default;

std::unique_ptr<Stream> Stream::makeFilter(const std::string &name, std::unique_ptr<Stream> str, Object *params, int recursion, Dict *dict)
{
    int pred; // parameters
    int colors;
    int bits;
    int early;
    int encoding;
    bool endOfLine, byteAlign, endOfBlock, black, damagedRowsBeforeError;
    int columns, rows;
    Object obj;

    if (name == "ASCIIHexDecode" || name == "AHx") {
        str = std::make_unique<ASCIIHexStream>(std::move(str));
    } else if (name == "ASCII85Decode" || name == "A85") {
        str = std::make_unique<ASCII85Stream>(std::move(str));
    } else if (name == "LZWDecode" || name == "LZW") {
        pred = 1;
        columns = 1;
        colors = 1;
        bits = 8;
        early = 1;
        if (params->isDict()) {
            obj = params->dictLookup("Predictor", recursion);
            if (obj.isInt()) {
                pred = obj.getInt();
            }
            obj = params->dictLookup("Columns", recursion);
            if (obj.isInt()) {
                columns = obj.getInt();
            }
            obj = params->dictLookup("Colors", recursion);
            if (obj.isInt()) {
                colors = obj.getInt();
            }
            obj = params->dictLookup("BitsPerComponent", recursion);
            if (obj.isInt()) {
                bits = obj.getInt();
            }
            obj = params->dictLookup("EarlyChange", recursion);
            if (obj.isInt()) {
                early = obj.getInt();
            }
        }
        str = std::make_unique<LZWStream>(std::move(str), pred, columns, colors, bits, early);
    } else if (name == "RunLengthDecode" || name == "RL") {
        str = std::make_unique<RunLengthStream>(std::move(str));
    } else if (name == "CCITTFaxDecode" || name == "CCF") {
        encoding = 0;
        endOfLine = false;
        byteAlign = false;
        columns = 1728;
        rows = 0;
        endOfBlock = true;
        black = false;
        damagedRowsBeforeError = false;
        if (params->isDict()) {
            obj = params->dictLookup("K", recursion);
            if (obj.isInt()) {
                encoding = obj.getInt();
            }
            obj = params->dictLookup("EndOfLine", recursion);
            if (obj.isBool()) {
                endOfLine = obj.getBool();
            }
            obj = params->dictLookup("EncodedByteAlign", recursion);
            if (obj.isBool()) {
                byteAlign = obj.getBool();
            }
            obj = params->dictLookup("Columns", recursion);
            if (obj.isInt()) {
                columns = obj.getInt();
            }
            obj = params->dictLookup("Rows", recursion);
            if (obj.isInt()) {
                rows = obj.getInt();
            }
            obj = params->dictLookup("EndOfBlock", recursion);
            if (obj.isBool()) {
                endOfBlock = obj.getBool();
            }
            obj = params->dictLookup("BlackIs1", recursion);
            if (obj.isBool()) {
                black = obj.getBool();
            }
            obj = params->dictLookup("DamagedRowsBeforeError", recursion);
            if (obj.isInt()) {
                damagedRowsBeforeError = obj.getInt();
            }
        }
        str = std::make_unique<CCITTFaxStream>(std::move(str), encoding, endOfLine, byteAlign, columns, rows, endOfBlock, black, damagedRowsBeforeError);
    } else if (name == "DCTDecode" || name == "DCT") {
#if ENABLE_LIBJPEG
        int colorXform = -1;
        if (params->isDict()) {
            obj = params->dictLookup("ColorTransform", recursion);
            if (obj.isInt()) {
                colorXform = obj.getInt();
            }
        }
        str = std::make_unique<DCTStream>(std::move(str), colorXform, dict, recursion);
#else
        (void)dict;
        error(errSyntaxError, str->getPos(), "Unknown filter '{0:r}'", &name);
        str = wrapEOFStream(std::move(str));
#endif
    } else if (name == "FlateDecode" || name == "Fl") {
        pred = 1;
        columns = 1;
        colors = 1;
        bits = 8;
        if (params->isDict()) {
            obj = params->dictLookup("Predictor", recursion);
            if (obj.isInt()) {
                pred = obj.getInt();
            }
            obj = params->dictLookup("Columns", recursion);
            if (obj.isInt()) {
                columns = obj.getInt();
            }
            obj = params->dictLookup("Colors", recursion);
            if (obj.isInt()) {
                colors = obj.getInt();
            }
            obj = params->dictLookup("BitsPerComponent", recursion);
            if (obj.isInt()) {
                bits = obj.getInt();
            }
        }
        str = std::make_unique<FlateStream>(std::move(str), pred, columns, colors, bits);
    } else if (name == "JBIG2Decode") {
        Object globals;
        if (params->isDict()) {
            XRef *xref = params->getDict()->getXRef();
            obj = params->dictLookupNF("JBIG2Globals").copy();
            globals = obj.fetch(xref, recursion);
        }
        str = std::make_unique<JBIG2Stream>(std::move(str), std::move(globals), &obj);
    } else if (name == "JPXDecode") {
#if ENABLE_LIBOPENJPEG
        str = std::make_unique<JPXStream>(std::move(str));
#else
        error(errSyntaxError, str->getPos(), "Unknown filter '{0:r}'", &name);
        str = wrapEOFStream(std::move(str));
#endif
    } else if (name == "Crypt") {
        if (str->getKind() == strCrypt) {
            str = std::make_unique<BaseStreamStream>(std::move(str));
        } else {
            error(errSyntaxError, str->getPos(), "Can't revert non decrypt streams");
        }
    } else {
        error(errSyntaxError, str->getPos(), "Unknown filter '{0:r}'", &name);
        str = wrapEOFStream(std::move(str));
    }
    return str;
}

//------------------------------------------------------------------------
// OutStream
//------------------------------------------------------------------------
OutStream::OutStream() = default;

OutStream::~OutStream() = default;

//------------------------------------------------------------------------
// FileOutStream
//------------------------------------------------------------------------
FileOutStream::FileOutStream(FILE *fa, Goffset startA)
{
    f = fa;
    start = startA;
}

FileOutStream::~FileOutStream()
{
    close();
}

void FileOutStream::close() { }

Goffset FileOutStream::getPos()
{
    return Gftell(f);
}

void FileOutStream::put(char c)
{
    fputc(c, f);
}

size_t FileOutStream::write(std::span<const unsigned char> data)
{
    return fwrite(data.data(), sizeof(decltype(data)::element_type), data.size(), f);
}

void FileOutStream::printf(const char *format, ...)
{
    va_list argptr;
    va_start(argptr, format);
    vfprintf(f, format, argptr);
    va_end(argptr);
}

//------------------------------------------------------------------------
// BaseStream
//------------------------------------------------------------------------

BaseStream::BaseStream(Object &&dictA, Goffset lengthA)
{
    dict = std::move(dictA);
    length = lengthA;
}

BaseStream::~BaseStream() = default;

//------------------------------------------------------------------------
// BaseStream
//------------------------------------------------------------------------

BaseSeekInputStream::BaseSeekInputStream(Goffset startA, bool limitedA, Goffset lengthA, Object &&dictA) : BaseStream(std::move(dictA), lengthA), start(startA), limited(limitedA), bufPtr(buf), bufEnd(buf), bufPos(start) { }

BaseSeekInputStream::~BaseSeekInputStream() = default;

bool BaseSeekInputStream::rewind()
{
    savePos = currentPos();
    setCurrentPos(start);
    saved = true;
    bufPtr = bufEnd = buf;
    bufPos = start;

    return true;
}

void BaseSeekInputStream::close()
{
    if (!saved) {
        return;
    }
    setCurrentPos(savePos);
    saved = false;
}

void BaseSeekInputStream::setPos(Goffset pos, int dir)
{
    if (dir >= 0) {
        setCurrentPos(pos);
        bufPos = pos;
    } else {
        if (pos > length) {
            pos = length;
        }

        bufPos = length - pos;
        setCurrentPos(bufPos);
    }
    bufPtr = bufEnd = buf;
}

void BaseSeekInputStream::moveStart(Goffset delta)
{
    start += delta;
    bufPtr = bufEnd = buf;
    bufPos = start;
}

bool BaseSeekInputStream::fillBuf()
{
    Goffset n;

    bufPos += bufEnd - buf;
    bufPtr = bufEnd = buf;
    if (limited && bufPos >= start + length) {
        return false;
    }

    if (limited && bufPos + seekInputStreamBufSize > start + length) {
        n = start + length - bufPos;
    } else {
        n = seekInputStreamBufSize - (bufPos % seekInputStreamBufSize);
    }

    n = read(buf, n);
    bufEnd = buf + n;
    return bufPtr < bufEnd;
}

int BaseSeekInputStream::getChars(int nChars, unsigned char *buffer)
{
    int n, m;

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

OwnedFilterStream::OwnedFilterStream(std::unique_ptr<Stream> strA) : FilterStream(strA.get()), ownedStream(std::move(strA)) { }

OwnedFilterStream::~OwnedFilterStream() = default;

//------------------------------------------------------------------------
// FilterStream
//------------------------------------------------------------------------

FilterStream::FilterStream(Stream *strA)
{
    str = strA;
}

FilterStream::~FilterStream() = default;

void FilterStream::close()
{
    str->close();
}

void FilterStream::setPos(Goffset /*pos*/, int /*dir*/)
{
    error(errInternal, -1, "Internal: called setPos() on FilterStream");
}

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

ImageStream::ImageStream(Stream *strA, int widthA, int nCompsA, int nBitsA)
{
    int imgLineSize;

    str = strA;
    width = widthA;
    nComps = nCompsA;
    nBits = nBitsA;

    nVals = width * nComps;
    inputLineSize = (nVals * nBits + 7) >> 3;
    if (nComps <= 0 || nBits <= 0 || nVals > INT_MAX / nBits - 7 || width > INT_MAX / nComps) {
        inputLineSize = -1;
    }
    inputLine = static_cast<unsigned char *>(gmallocn_checkoverflow(inputLineSize, sizeof(char)));
    if (nBits == 8) {
        imgLine = inputLine;
    } else {
        if (nBits == 1) {
            imgLineSize = (nVals + 7) & ~7;
        } else {
            imgLineSize = nVals;
        }
        if (nComps <= 0 || width > INT_MAX / nComps) {
            imgLineSize = -1;
        }
        imgLine = static_cast<unsigned char *>(gmallocn_checkoverflow(imgLineSize, sizeof(unsigned char)));
    }
    imgIdx = nVals;
}

ImageStream::~ImageStream()
{
    if (imgLine != inputLine) {
        gfree(imgLine);
    }
    gfree(inputLine);
}

bool ImageStream::rewind()
{
    return str->rewind();
}

void ImageStream::close()
{
    str->close();
}

bool ImageStream::getPixel(unsigned char *pix)
{
    int i;

    if (imgIdx >= nVals) {
        if (!getLine()) {
            return false;
        }
        imgIdx = 0;
    }
    for (i = 0; i < nComps; ++i) {
        pix[i] = imgLine[imgIdx++];
    }
    return true;
}

unsigned char *ImageStream::getLine()
{
    if (unlikely(inputLine == nullptr || imgLine == nullptr)) {
        return nullptr;
    }

    int readChars = str->doGetChars(inputLineSize, inputLine);
    if (unlikely(readChars == -1)) {
        readChars = 0;
    }
    for (; readChars < inputLineSize; readChars++) {
        inputLine[readChars] = EOF;
    }
    if (nBits == 1) {
        unsigned char *p = inputLine;
        for (int i = 0; i < nVals; i += 8) {
            const int c = *p++;
            imgLine[i + 0] = static_cast<unsigned char>((c >> 7) & 1);
            imgLine[i + 1] = static_cast<unsigned char>((c >> 6) & 1);
            imgLine[i + 2] = static_cast<unsigned char>((c >> 5) & 1);
            imgLine[i + 3] = static_cast<unsigned char>((c >> 4) & 1);
            imgLine[i + 4] = static_cast<unsigned char>((c >> 3) & 1);
            imgLine[i + 5] = static_cast<unsigned char>((c >> 2) & 1);
            imgLine[i + 6] = static_cast<unsigned char>((c >> 1) & 1);
            imgLine[i + 7] = static_cast<unsigned char>(c & 1);
        }
    } else if (nBits == 8) {
        // special case: imgLine == inputLine
    } else if (nBits == 16) {
        // this is a hack to support 16 bits images, everywhere
        // we assume a component fits in 8 bits, with this hack
        // we treat 16 bit images as 8 bit ones until it's fixed correctly.
        // The hack has another part on GfxImageColorMap::GfxImageColorMap
        unsigned char *p = inputLine;
        for (int i = 0; i < nVals; ++i) {
            imgLine[i] = *p++;
            p++;
        }
    } else {
        const unsigned long bitMask = (1 << nBits) - 1;
        unsigned long buf = 0;
        int bits = 0;
        unsigned char *p = inputLine;
        for (int i = 0; i < nVals; ++i) {
            while (bits < nBits) {
                buf = (buf << 8) | (*p++ & 0xff);
                bits += 8;
            }
            imgLine[i] = static_cast<unsigned char>((buf >> (bits - nBits)) & bitMask);
            bits -= nBits;
        }
    }
    return imgLine;
}

void ImageStream::skipLine()
{
    str->doGetChars(inputLineSize, inputLine);
}

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

StreamPredictor::StreamPredictor(Stream *strA, int predictorA, int widthA, int nCompsA, int nBitsA)
{
    str = strA;
    predictor = predictorA;
    width = widthA;
    nComps = nCompsA;
    nBits = nBitsA;
    predLine = nullptr;
    ok = false;

    if (checkedMultiply(width, nComps, &nVals)) {
        return;
    }
    if (width <= 0 || nComps <= 0 || nBits <= 0 || nComps > gfxColorMaxComps || nBits > 16 || nVals >= (INT_MAX - 7) / nBits) { // check for overflow in rowBytes
        return;
    }
    pixBytes = (nComps * nBits + 7) >> 3;
    rowBytes = ((nVals * nBits + 7) >> 3) + pixBytes;
    predLine = static_cast<unsigned char *>(gmalloc(rowBytes));
    memset(predLine, 0, rowBytes);
    predIdx = rowBytes;

    ok = true;
}

StreamPredictor::~StreamPredictor()
{
    gfree(predLine);
}

int StreamPredictor::lookChar()
{
    if (predIdx >= rowBytes) {
        if (!getNextLine()) {
            return EOF;
        }
    }
    return predLine[predIdx];
}

int StreamPredictor::getChar()
{
    if (predIdx >= rowBytes) {
        if (!getNextLine()) {
            return EOF;
        }
    }
    return predLine[predIdx++];
}

int StreamPredictor::getChars(int nChars, unsigned char *buffer)
{
    int n, m;

    n = 0;
    while (n < nChars) {
        if (predIdx >= rowBytes) {
            if (!getNextLine()) {
                break;
            }
        }
        m = rowBytes - predIdx;
        if (m > nChars - n) {
            m = nChars - n;
        }
        memcpy(buffer + n, predLine + predIdx, m);
        predIdx += m;
        n += m;
    }
    return n;
}

bool StreamPredictor::getNextLine()
{
    int curPred;
    unsigned char upLeftBuf[gfxColorMaxComps * 2 + 1];
    int left, up, upLeft, p, pa, pb, pc;
    int c;
    unsigned long inBuf, outBuf;
    int inBits, outBits;
    int i, j, k, kk;

    // get PNG optimum predictor number
    if (predictor >= 10) {
        if ((curPred = str->getRawChar()) == EOF) {
            return false;
        }
        curPred += 10;
    } else {
        curPred = predictor;
    }

    // read the raw line, apply PNG (byte) predictor
    int *rawCharLine = new int[rowBytes - pixBytes];
    str->getRawChars(rowBytes - pixBytes, rawCharLine);
    memset(upLeftBuf, 0, pixBytes + 1);
    for (i = pixBytes; i < rowBytes; ++i) {
        for (j = pixBytes; j > 0; --j) {
            upLeftBuf[j] = upLeftBuf[j - 1];
        }
        upLeftBuf[0] = predLine[i];
        if ((c = rawCharLine[i - pixBytes]) == EOF) {
            if (i > pixBytes) {
                // this ought to return false, but some (broken) PDF files
                // contain truncated image data, and Adobe apparently reads the
                // last partial line
                break;
            }
            delete[] rawCharLine;
            return false;
        }
        switch (curPred) {
        case 11: // PNG sub
            predLine[i] = predLine[i - pixBytes] + static_cast<unsigned char>(c);
            break;
        case 12: // PNG up
            predLine[i] = predLine[i] + static_cast<unsigned char>(c);
            break;
        case 13: // PNG average
            predLine[i] = ((predLine[i - pixBytes] + predLine[i]) >> 1) + static_cast<unsigned char>(c);
            break;
        case 14: // PNG Paeth
            left = predLine[i - pixBytes];
            up = predLine[i];
            upLeft = upLeftBuf[pixBytes];
            p = left + up - upLeft;
            if ((pa = p - left) < 0) {
                pa = -pa;
            }
            if ((pb = p - up) < 0) {
                pb = -pb;
            }
            if ((pc = p - upLeft) < 0) {
                pc = -pc;
            }
            if (pa <= pb && pa <= pc) {
                predLine[i] = left + static_cast<unsigned char>(c);
            } else if (pb <= pc) {
                predLine[i] = up + static_cast<unsigned char>(c);
            } else {
                predLine[i] = upLeft + static_cast<unsigned char>(c);
            }
            break;
        case 10: // PNG none
        default: // no predictor or TIFF predictor
            predLine[i] = static_cast<unsigned char>(c);
            break;
        }
    }
    delete[] rawCharLine;

    // apply TIFF (component) predictor
    if (predictor == 2) {
        if (nBits == 1 && nComps == 1) {
            inBuf = predLine[pixBytes - 1];
            for (i = pixBytes; i < rowBytes; ++i) {
                c = predLine[i] ^ inBuf;
                c ^= c >> 1;
                c ^= c >> 2;
                c ^= c >> 4;
                inBuf = (c & 1) << 7;
                predLine[i] = c;
            }
        } else if (nBits == 8) {
            for (i = pixBytes; i < rowBytes; ++i) {
                predLine[i] += predLine[i - nComps];
            }
        } else {
            memset(upLeftBuf, 0, nComps + 1);
            const unsigned long bitMask = (1 << nBits) - 1;
            inBuf = outBuf = 0;
            inBits = outBits = 0;
            j = k = pixBytes;
            for (i = 0; i < width; ++i) {
                for (kk = 0; kk < nComps; ++kk) {
                    while (inBits < nBits) {
                        inBuf = (inBuf << 8) | (predLine[j++] & 0xff);
                        inBits += 8;
                    }
                    upLeftBuf[kk] = static_cast<unsigned char>((upLeftBuf[kk] + (inBuf >> (inBits - nBits))) & bitMask);
                    inBits -= nBits;
                    outBuf = (outBuf << nBits) | upLeftBuf[kk];
                    outBits += nBits;
                    if (outBits >= 8) {
                        predLine[k++] = static_cast<unsigned char>(outBuf >> (outBits - 8));
                        outBits -= 8;
                    }
                }
            }
            if (outBits > 0) {
                predLine[k++] = static_cast<unsigned char>((outBuf << (8 - outBits)) + (inBuf & ((1 << (8 - outBits)) - 1)));
            }
        }
    }

    // rewind to start of line
    predIdx = pixBytes;

    return true;
}

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

FileStream::FileStream(GooFile *fileA, Goffset startA, bool limitedA, Goffset lengthA, Object &&dictA) : BaseStream(std::move(dictA), lengthA)
{
    file = fileA;
    offset = start = startA;
    limited = limitedA;
    length = lengthA;
    bufPtr = bufEnd = buf;
    bufPos = start;
    savePos = 0;
    saved = false;
    needsEncryptionOnSave = false;
}

FileStream::~FileStream()
{
    close();
}

std::unique_ptr<BaseStream> FileStream::copy()
{
    return std::make_unique<FileStream>(file, start, limited, length, dict.copy());
}

std::unique_ptr<Stream> FileStream::makeSubStream(Goffset startA, bool limitedA, Goffset lengthA, Object &&dictA)
{
    return std::make_unique<FileStream>(file, startA, limitedA, lengthA, std::move(dictA));
}

bool FileStream::rewind()
{
    savePos = offset;
    offset = start;
    saved = true;
    bufPtr = bufEnd = buf;
    bufPos = start;

    return true;
}

void FileStream::close()
{
    if (saved) {
        offset = savePos;
        saved = false;
    }
}

bool FileStream::fillBuf()
{
    int n;

    bufPos += bufEnd - buf;
    bufPtr = bufEnd = buf;
    if (limited && bufPos >= start + length) {
        return false;
    }
    if (limited && bufPos + fileStreamBufSize > start + length) {
        n = start + length - bufPos;
    } else {
        n = fileStreamBufSize;
    }
    n = file->read(buf, n, offset);
    if (n == -1) {
        return false;
    }
    offset += n;
    bufEnd = buf + n;
    return bufPtr < bufEnd;
}

void FileStream::setPos(Goffset pos, int dir)
{
    Goffset size;

    if (dir >= 0) {
        offset = bufPos = pos;
    } else {
        size = file->size();
        if (pos > size) {
            pos = size;
        }
        offset = size - pos;
        bufPos = offset;
    }
    bufPtr = bufEnd = buf;
}

void FileStream::moveStart(Goffset delta)
{
    start += delta;
    bufPtr = bufEnd = buf;
    bufPos = start;
}

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

CachedFileStream::CachedFileStream(std::shared_ptr<CachedFile> ccA, Goffset startA, bool limitedA, Goffset lengthA, Object &&dictA) : BaseStream(std::move(dictA), lengthA)
{
    cc = std::move(ccA);
    start = startA;
    limited = limitedA;
    length = lengthA;
    bufPtr = bufEnd = buf;
    bufPos = start;
    savePos = 0;
    saved = false;
}

CachedFileStream::~CachedFileStream()
{
    close();
}

std::unique_ptr<BaseStream> CachedFileStream::copy()
{
    return std::make_unique<CachedFileStream>(cc, start, limited, length, dict.copy());
}

std::unique_ptr<Stream> CachedFileStream::makeSubStream(Goffset startA, bool limitedA, Goffset lengthA, Object &&dictA)
{
    return std::make_unique<CachedFileStream>(cc, startA, limitedA, lengthA, std::move(dictA));
}

bool CachedFileStream::rewind()
{
    savePos = static_cast<unsigned int>(cc->tell());
    cc->seek(start, SEEK_SET);

    saved = true;
    bufPtr = bufEnd = buf;
    bufPos = start;

    return true;
}

void CachedFileStream::close()
{
    if (saved) {
        cc->seek(savePos, SEEK_SET);
        saved = false;
    }
}

bool CachedFileStream::fillBuf()
{
    int n;

    bufPos += bufEnd - buf;
    bufPtr = bufEnd = buf;
    if (limited && bufPos >= start + length) {
        return false;
    }
    if (limited && bufPos + cachedStreamBufSize > start + length) {
        n = start + length - bufPos;
    } else {
        n = cachedStreamBufSize - (bufPos % cachedStreamBufSize);
    }
    n = cc->read(buf, 1, n);
    bufEnd = buf + n;
    return bufPtr < bufEnd;
}

void CachedFileStream::setPos(Goffset pos, int dir)
{
    unsigned int size;

    if (dir >= 0) {
        if (cc->seek(pos, SEEK_SET) == 0) {
            bufPos = pos;
        } else {
            cc->seek(0, SEEK_END);
            bufPos = pos = static_cast<unsigned int>(cc->tell());
            error(errInternal, pos, "CachedFileStream: Seek beyond end attempted, capped to file size");
        }
    } else {
        cc->seek(0, SEEK_END);
        size = static_cast<unsigned int>(cc->tell());

        if (pos > size) {
            pos = size;
        }

        cc->seek(-static_cast<int>(pos), SEEK_END);
        bufPos = static_cast<unsigned int>(cc->tell());
    }

    bufPtr = bufEnd = buf;
}

void CachedFileStream::moveStart(Goffset delta)
{
    start += delta;
    bufPtr = bufEnd = buf;
    bufPos = start;
}

MemStream::~MemStream() = default;

AutoFreeMemStream::~AutoFreeMemStream() = default;

bool AutoFreeMemStream::isFilterRemovalForbidden() const
{
    return filterRemovalForbidden;
}

void AutoFreeMemStream::setFilterRemovalForbidden(bool forbidden)
{
    filterRemovalForbidden = forbidden;
}

//------------------------------------------------------------------------
// EmbedStream
//------------------------------------------------------------------------

EmbedStream::EmbedStream(Stream *strA, Object &&dictA, bool limitedA, Goffset lengthA, bool reusableA) : BaseStream(std::move(dictA), lengthA), limited(limitedA), reusable(reusableA), initialLength(length)
{
    str = strA;
    length = lengthA;
    replay = false;
    if (reusable) {
        bufData = static_cast<unsigned char *>(gmalloc(16384));
        bufMax = 16384;
        bufLen = 0;
    }
}

EmbedStream::~EmbedStream()
{
    if (reusable) {
        gfree(bufData);
    }
}

bool EmbedStream::rewind()
{
    if (length == initialLength) {
        // In general one can't rewind EmbedStream
        // but if we have not read anything yet, that's fine
        return true;
    }

    if (reusable) {
        // If the EmbedStream is reusable, rewinding switches back to reading the recorded data
        replay = true;
        bufPos = 0;
        return true;
    }

    return false;
}

std::unique_ptr<BaseStream> EmbedStream::copy()
{
    error(errInternal, -1, "Called copy() on EmbedStream");
    return nullptr;
}

std::unique_ptr<Stream> EmbedStream::makeSubStream(Goffset /*startA*/, bool /*limitedA*/, Goffset /*lengthA*/, Object && /*dictA*/)
{
    error(errInternal, -1, "Called makeSubStream() on EmbedStream");
    return nullptr;
}

Goffset EmbedStream::getPos()
{
    if (replay) {
        return bufPos;
    }
    return str->getPos();
}

int EmbedStream::getChar()
{
    if (replay) {
        if (bufPos < bufLen) {
            return bufData[bufPos++];
        }
        replay = false;
        return EOF;
    }
    if (limited && !length) {
        return EOF;
    }
    int c = str->getChar();
    --length;
    if (reusable) {
        bufData[bufLen] = c;
        bufLen++;
        if (bufLen >= bufMax) {
            bufMax *= 2;
            bufData = static_cast<unsigned char *>(grealloc(bufData, bufMax));
        }
    }
    return c;
}

int EmbedStream::lookChar()
{
    if (replay) {
        if (bufPos < bufLen) {
            return bufData[bufPos];
        }
        return EOF;
    }
    if (limited && !length) {
        return EOF;
    }
    return str->lookChar();
}

int EmbedStream::getChars(int nChars, unsigned char *buffer)
{
    if (nChars <= 0) {
        return 0;
    }
    if (replay) {
        if (bufPos >= bufLen) {
            replay = false;
            return EOF;
        }
        const long len = bufLen - bufPos;
        if (nChars > len) {
            nChars = len;
        }
        memcpy(buffer, &bufData[bufPos], nChars);
        bufPos += nChars;
        return nChars;
    }
    if (limited && length < nChars) {
        nChars = length;
    }
    const int len = str->doGetChars(nChars, buffer);
    length -= len;
    if (reusable) {
        if (bufLen + len >= bufMax) {
            while (bufLen + len >= bufMax) {
                bufMax *= 2;
            }
            bufData = static_cast<unsigned char *>(grealloc(bufData, bufMax));
        }
        memcpy(bufData + bufLen, buffer, len);
        bufLen += len;
    }
    return len;
}

void EmbedStream::setPos(Goffset /*pos*/, int /*dir*/)
{
    error(errInternal, -1, "Internal: called setPos() on EmbedStream");
}

Goffset EmbedStream::getStart()
{
    error(errInternal, -1, "Internal: called getStart() on EmbedStream");
    return 0;
}

void EmbedStream::moveStart(Goffset /*delta*/)
{
    error(errInternal, -1, "Internal: called moveStart() on EmbedStream");
}

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

ASCIIHexStream::ASCIIHexStream(std::unique_ptr<Stream> strA) : OwnedFilterStream(std::move(strA))
{
    buf = EOF;
    eof = false;
}

ASCIIHexStream::~ASCIIHexStream() = default;

bool ASCIIHexStream::rewind()
{
    buf = EOF;
    eof = false;

    return str->rewind();
}

int ASCIIHexStream::lookChar()
{
    int c1, c2, x;

    if (buf != EOF) {
        return buf;
    }
    if (eof) {
        buf = EOF;
        return EOF;
    }
    do {
        c1 = str->getChar();
    } while (isspace(c1));
    if (c1 == '>') {
        eof = true;
        buf = EOF;
        return buf;
    }
    do {
        c2 = str->getChar();
    } while (isspace(c2));
    if (c2 == '>') {
        eof = true;
        c2 = '0';
    }
    if (c1 >= '0' && c1 <= '9') {
        x = (c1 - '0') << 4;
    } else if (c1 >= 'A' && c1 <= 'F') {
        x = (c1 - 'A' + 10) << 4;
    } else if (c1 >= 'a' && c1 <= 'f') {
        x = (c1 - 'a' + 10) << 4;
    } else if (c1 == EOF) {
        eof = true;
        x = 0;
    } else {
        error(errSyntaxError, getPos(), "Illegal character <{0:02x}> in ASCIIHex stream", c1);
        x = 0;
    }
    if (c2 >= '0' && c2 <= '9') {
        x += c2 - '0';
    } else if (c2 >= 'A' && c2 <= 'F') {
        x += c2 - 'A' + 10;
    } else if (c2 >= 'a' && c2 <= 'f') {
        x += c2 - 'a' + 10;
    } else if (c2 == EOF) {
        eof = true;
        x = 0;
    } else {
        error(errSyntaxError, getPos(), "Illegal character <{0:02x}> in ASCIIHex stream", c2);
    }
    buf = x & 0xff;
    return buf;
}

std::optional<std::string> ASCIIHexStream::getPSFilter(int psLevel, const char *indent)
{
    std::optional<std::string> s;

    if (psLevel < 2) {
        return {};
    }
    if (!(s = str->getPSFilter(psLevel, indent))) {
        return {};
    }
    s->append(indent).append("/ASCIIHexDecode filter\n");
    return s;
}

bool ASCIIHexStream::isBinary(bool /*last*/) const
{
    return str->isBinary(false);
}

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

ASCII85Stream::ASCII85Stream(std::unique_ptr<Stream> strA) : OwnedFilterStream(std::move(strA))
{
    index = n = 0;
    eof = false;
}

ASCII85Stream::~ASCII85Stream() = default;

bool ASCII85Stream::rewind()
{
    index = n = 0;
    eof = false;

    return str->rewind();
}

int ASCII85Stream::lookChar()
{
    int k;
    unsigned long t;

    if (index >= n) {
        if (eof) {
            return EOF;
        }
        index = 0;
        do {
            c[0] = str->getChar();
        } while (Lexer::isSpace(c[0]));
        if (c[0] == '~' || c[0] == EOF) {
            eof = true;
            n = 0;
            return EOF;
        }
        if (c[0] == 'z') {
            b[0] = b[1] = b[2] = b[3] = 0;
            n = 4;
        } else {
            for (k = 1; k < 5; ++k) {
                do {
                    c[k] = str->getChar();
                } while (Lexer::isSpace(c[k]));
                if (c[k] == '~' || c[k] == EOF) {
                    break;
                }
            }
            n = k - 1;
            if (k < 5 && (c[k] == '~' || c[k] == EOF)) {
                for (++k; k < 5; ++k) {
                    c[k] = 0x21 + 84;
                }
                eof = true;
            }
            t = 0;
            for (k = 0; k < 5; ++k) {
                t = t * 85 + (c[k] - 0x21);
            }
            for (k = 3; k >= 0; --k) {
                b[k] = static_cast<int>(t & 0xff);
                t >>= 8;
            }
        }
    }
    return b[index];
}

std::optional<std::string> ASCII85Stream::getPSFilter(int psLevel, const char *indent)
{
    std::optional<std::string> s;

    if (psLevel < 2) {
        return {};
    }
    if (!(s = str->getPSFilter(psLevel, indent))) {
        return {};
    }
    s->append(indent).append("/ASCII85Decode filter\n");
    return s;
}

bool ASCII85Stream::isBinary(bool /*last*/) const
{
    return str->isBinary(false);
}

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

LZWStream::LZWStream(std::unique_ptr<Stream> strA, int predictor, int columns, int colors, int bits, int earlyA) : OwnedFilterStream(std::move(strA))
{
    if (predictor != 1) {
        pred = new StreamPredictor(this, predictor, columns, colors, bits);
        if (!pred->isOk()) {
            delete pred;
            pred = nullptr;
        }
    } else {
        pred = nullptr;
    }
    early = earlyA;
    eof = false;
    inputBits = 0;
    clearTable();
}

LZWStream::~LZWStream()
{
    delete pred;
}

int LZWStream::getChar()
{
    if (pred) {
        return pred->getChar();
    }
    if (eof) {
        return EOF;
    }
    if (seqIndex >= seqLength) {
        if (!processNextCode()) {
            return EOF;
        }
    }
    return seqBuf[seqIndex++];
}

int LZWStream::lookChar()
{
    if (pred) {
        return pred->lookChar();
    }
    if (eof) {
        return EOF;
    }
    if (seqIndex >= seqLength) {
        if (!processNextCode()) {
            return EOF;
        }
    }
    return seqBuf[seqIndex];
}

void LZWStream::getRawChars(int nChars, int *buffer)
{
    for (int i = 0; i < nChars; ++i) {
        buffer[i] = doGetRawChar();
    }
}

int LZWStream::getRawChar()
{
    return doGetRawChar();
}

int LZWStream::getChars(int nChars, unsigned char *buffer)
{
    int n, m;

    if (pred) {
        return pred->getChars(nChars, buffer);
    }
    if (eof) {
        return 0;
    }
    n = 0;
    while (n < nChars) {
        if (seqIndex >= seqLength) {
            if (!processNextCode()) {
                break;
            }
        }
        m = seqLength - seqIndex;
        if (m > nChars - n) {
            m = nChars - n;
        }
        memcpy(buffer + n, seqBuf + seqIndex, m);
        seqIndex += m;
        n += m;
    }
    return n;
}

bool LZWStream::rewind()
{
    bool success = str->rewind();
    eof = false;
    inputBits = 0;
    clearTable();

    return success;
}

bool LZWStream::processNextCode()
{
    int code;
    int nextLength;
    int i, j;

    // check for EOF
    if (eof) {
        return false;
    }

    // check for eod and clear-table codes
start:
    code = getCode();
    if (code == EOF || code == 257) {
        eof = true;
        return false;
    }
    if (code == 256) {
        clearTable();
        goto start;
    }

    // process the next code
    nextLength = seqLength + 1;
    if (code < 256) {
        seqBuf[0] = code;
        seqLength = 1;
    } else if (code < nextCode) {
        seqLength = table[code].length;
        for (i = seqLength - 1, j = code; i > 0; --i) {
            seqBuf[i] = table[j].tail;
            j = table[j].head;
        }
        seqBuf[0] = j;
    } else if (code == nextCode) {
        seqBuf[seqLength] = newChar;
        ++seqLength;
    } else {
        error(errSyntaxError, getPos(), "Bad LZW stream - unexpected code");
        eof = true;
        return false;
    }
    newChar = seqBuf[0];
    if (first) {
        first = false;
    } else {
        if (nextCode < 4097) {
            table[nextCode].length = nextLength;
            table[nextCode].head = prevCode;
            table[nextCode].tail = newChar;
            ++nextCode;
        }
        if (nextCode + early == 512) {
            nextBits = 10;
        } else if (nextCode + early == 1024) {
            nextBits = 11;
        } else if (nextCode + early == 2048) {
            nextBits = 12;
        }
    }
    prevCode = code;

    // rewind buffer
    seqIndex = 0;

    return true;
}

void LZWStream::clearTable()
{
    nextCode = 258;
    nextBits = 9;
    seqIndex = seqLength = 0;
    first = true;
    newChar = 0;
}

int LZWStream::getCode()
{
    int c;
    int code;

    while (inputBits < nextBits) {
        if ((c = str->getChar()) == EOF) {
            return EOF;
        }
        inputBuf = (inputBuf << 8) | static_cast<unsigned>(c & 0xff);
        inputBits += 8;
    }
    code = static_cast<signed>((inputBuf >> (inputBits - nextBits)) & ((1 << nextBits) - 1));
    inputBits -= nextBits;
    return code;
}

std::optional<std::string> LZWStream::getPSFilter(int psLevel, const char *indent)
{
    std::optional<std::string> s;

    if (psLevel < 2 || pred) {
        return {};
    }
    if (!(s = str->getPSFilter(psLevel, indent))) {
        return {};
    }
    s->append(indent).append("<< ");
    if (!early) {
        s->append("/EarlyChange 0 ");
    }
    s->append(">> /LZWDecode filter\n");
    return s;
}

bool LZWStream::isBinary(bool /*last*/) const
{
    return str->isBinary(true);
}

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

RunLengthStream::RunLengthStream(std::unique_ptr<Stream> strA) : OwnedFilterStream(std::move(strA))
{
    bufPtr = bufEnd = buf;
    eof = false;
}

RunLengthStream::~RunLengthStream() = default;

bool RunLengthStream::rewind()
{
    bufPtr = bufEnd = buf;
    eof = false;

    return str->rewind();
}

int RunLengthStream::getChars(int nChars, unsigned char *buffer)
{
    int n, m;

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

std::optional<std::string> RunLengthStream::getPSFilter(int psLevel, const char *indent)
{
    std::optional<std::string> s;

    if (psLevel < 2) {
        return {};
    }
    if (!(s = str->getPSFilter(psLevel, indent))) {
        return {};
    }
    s->append(indent).append("/RunLengthDecode filter\n");
    return s;
}

bool RunLengthStream::isBinary(bool /*last*/) const
{
    return str->isBinary(true);
}

bool RunLengthStream::fillBuf()
{
    int c;
    int n, i;

    if (eof) {
        return false;
    }
    c = str->getChar();
    if (c == 0x80 || c == EOF) {
        eof = true;
        return false;
    }
    if (c < 0x80) {
        n = c + 1;
        for (i = 0; i < n; ++i) {
            buf[i] = static_cast<char>(str->getChar());
        }
    } else {
        n = 0x101 - c;
        c = str->getChar();
        for (i = 0; i < n; ++i) {
            buf[i] = static_cast<char>(c);
        }
    }
    bufPtr = buf;
    bufEnd = buf + n;
    return true;
}

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

CCITTFaxStream::CCITTFaxStream(std::unique_ptr<Stream> strA, int encodingA, bool endOfLineA, bool byteAlignA, int columnsA, int rowsA, bool endOfBlockA, bool blackA, int damagedRowsBeforeErrorA) : OwnedFilterStream(std::move(strA))
{
    encoding = encodingA;
    endOfLine = endOfLineA;
    byteAlign = byteAlignA;
    columns = columnsA;
    damagedRowsBeforeError = damagedRowsBeforeErrorA;
    if (columns < 1) {
        columns = 1;
    } else if (columns > INT_MAX - 2) {
        columns = INT_MAX - 2;
    }
    rows = rowsA;
    endOfBlock = endOfBlockA;
    black = blackA;
    // 0 <= codingLine[0] < codingLine[1] < ... < codingLine[n] = columns
    // ---> max codingLine size = columns + 1
    // refLine has one extra guard entry at the end
    // ---> max refLine size = columns + 2
    codingLine = static_cast<int *>(gmallocn_checkoverflow(columns + 1, sizeof(int)));
    refLine = static_cast<int *>(gmallocn_checkoverflow(columns + 2, sizeof(int)));

    if (codingLine != nullptr && refLine != nullptr) {
        eof = false;
        codingLine[0] = columns;
    } else {
        eof = true;
    }
    row = 0;
    nextLine2D = encoding < 0;
    inputBits = 0;
    a0i = 0;
    outputBits = 0;

    buf = EOF;
}

CCITTFaxStream::~CCITTFaxStream()
{
    gfree(refLine);
    gfree(codingLine);
}

bool CCITTFaxStream::ccittRewind(bool unfiltered)
{
    row = 0;
    nextLine2D = encoding < 0;
    inputBits = 0;
    a0i = 0;
    outputBits = 0;
    buf = EOF;

    if (unfiltered) {
        return str->unfilteredRewind();
    }
    return str->rewind();
}

bool CCITTFaxStream::unfilteredRewind()
{
    return ccittRewind(true);
}

bool CCITTFaxStream::rewind()
{
    int code1;

    bool rewindSuccess = ccittRewind(false);

    if (codingLine != nullptr && refLine != nullptr) {
        eof = false;
        codingLine[0] = columns;
    } else {
        eof = true;
    }

    // skip any initial zero bits and end-of-line marker, and get the 2D
    // encoding tag
    while ((code1 = lookBits(12)) == 0) {
        eatBits(1);
    }
    if (code1 == 0x001) {
        eatBits(12);
        endOfLine = true;
    }
    if (encoding > 0) {
        nextLine2D = !lookBits(1);
        eatBits(1);
    }

    return rewindSuccess;
}

inline void CCITTFaxStream::addPixels(int a1, int blackPixels)
{
    if (a1 > codingLine[a0i]) {
        if (a1 > columns) {
            error(errSyntaxError, getPos(), "CCITTFax row is wrong length ({0:d})", a1);
            err = true;
            a1 = columns;
        }
        if ((a0i & 1) ^ blackPixels) {
            ++a0i;
        }
        codingLine[a0i] = a1;
    }
}

inline void CCITTFaxStream::addPixelsNeg(int a1, int blackPixels)
{
    if (a1 > codingLine[a0i]) {
        if (a1 > columns) {
            error(errSyntaxError, getPos(), "CCITTFax row is wrong length ({0:d})", a1);
            err = true;
            a1 = columns;
        }
        if ((a0i & 1) ^ blackPixels) {
            ++a0i;
        }
        codingLine[a0i] = a1;
    } else if (a1 < codingLine[a0i]) {
        if (a1 < 0) {
            error(errSyntaxError, getPos(), "Invalid CCITTFax code");
            err = true;
            a1 = columns;
        }
        while (a0i > 0 && a1 <= codingLine[a0i - 1]) {
            --a0i;
        }
        codingLine[a0i] = a1;
    }
}

int CCITTFaxStream::lookChar()
{
    int code1, code2, code3;
    int b1i, blackPixels, i, bits;
    bool gotEOL;

    if (buf != EOF) {
        return buf;
    }

    // read the next row
    if (outputBits == 0) {

        // if at eof just return EOF
        if (eof) {
            return EOF;
        }

        err = false;

        // 2-D encoding
        if (nextLine2D) {
            for (i = 0; i < columns && codingLine[i] < columns; ++i) {
                refLine[i] = codingLine[i];
            }
            for (; i < columns + 2; ++i) {
                refLine[i] = columns;
            }
            codingLine[0] = 0;
            a0i = 0;
            b1i = 0;
            blackPixels = 0;
            // invariant:
            // refLine[b1i-1] <= codingLine[a0i] < refLine[b1i] < refLine[b1i+1]
            //                                                             <= columns
            // exception at left edge:
            //   codingLine[a0i = 0] = refLine[b1i = 0] = 0 is possible
            // exception at right edge:
            //   refLine[b1i] = refLine[b1i+1] = columns is possible
            while (codingLine[a0i] < columns && !err) {
                code1 = getTwoDimCode();
                switch (code1) {
                case twoDimPass:
                    if (likely(b1i + 1 < columns + 2)) {
                        addPixels(refLine[b1i + 1], blackPixels);
                        if (refLine[b1i + 1] < columns) {
                            b1i += 2;
                        }
                    }
                    break;
                case twoDimHoriz:
                    code1 = code2 = 0;
                    if (blackPixels) {
                        do {
                            code1 += code3 = getBlackCode();
                        } while (code3 >= 64);
                        do {
                            code2 += code3 = getWhiteCode();
                        } while (code3 >= 64);
                    } else {
                        do {
                            code1 += code3 = getWhiteCode();
                        } while (code3 >= 64);
                        do {
                            code2 += code3 = getBlackCode();
                        } while (code3 >= 64);
                    }
                    addPixels(codingLine[a0i] + code1, blackPixels);
                    if (codingLine[a0i] < columns) {
                        addPixels(codingLine[a0i] + code2, blackPixels ^ 1);
                    }
                    while (refLine[b1i] <= codingLine[a0i] && refLine[b1i] < columns) {
                        b1i += 2;
                        if (unlikely(b1i > columns + 1)) {
                            error(errSyntaxError, getPos(), "Bad 2D code {0:04x} in CCITTFax stream", code1);
                            err = true;
                            break;
                        }
                    }
                    break;
                case twoDimVertR3:
                    if (unlikely(b1i > columns + 1)) {
                        error(errSyntaxError, getPos(), "Bad 2D code {0:04x} in CCITTFax stream", code1);
                        err = true;
                        break;
                    }
                    addPixels(refLine[b1i] + 3, blackPixels);
                    blackPixels ^= 1;
                    if (codingLine[a0i] < columns) {
                        ++b1i;
                        while (refLine[b1i] <= codingLine[a0i] && refLine[b1i] < columns) {
                            b1i += 2;
                            if (unlikely(b1i > columns + 1)) {
                                error(errSyntaxError, getPos(), "Bad 2D code {0:04x} in CCITTFax stream", code1);
                                err = true;
                                break;
                            }
                        }
                    }
                    break;
                case twoDimVertR2:
                    if (unlikely(b1i > columns + 1)) {
                        error(errSyntaxError, getPos(), "Bad 2D code {0:04x} in CCITTFax stream", code1);
                        err = true;
                        break;
                    }
                    addPixels(refLine[b1i] + 2, blackPixels);
                    blackPixels ^= 1;
                    if (codingLine[a0i] < columns) {
                        ++b1i;
                        while (refLine[b1i] <= codingLine[a0i] && refLine[b1i] < columns) {
                            b1i += 2;
                            if (unlikely(b1i > columns + 1)) {
                                error(errSyntaxError, getPos(), "Bad 2D code {0:04x} in CCITTFax stream", code1);
                                err = true;
                                break;
                            }
                        }
                    }
                    break;
                case twoDimVertR1:
                    if (unlikely(b1i > columns + 1)) {
                        error(errSyntaxError, getPos(), "Bad 2D code {0:04x} in CCITTFax stream", code1);
                        err = true;
                        break;
                    }
                    addPixels(refLine[b1i] + 1, blackPixels);
                    blackPixels ^= 1;
                    if (codingLine[a0i] < columns) {
                        ++b1i;
                        while (refLine[b1i] <= codingLine[a0i] && refLine[b1i] < columns) {
                            b1i += 2;
                            if (unlikely(b1i > columns + 1)) {
                                error(errSyntaxError, getPos(), "Bad 2D code {0:04x} in CCITTFax stream", code1);
                                err = true;
                                break;
                            }
                        }
                    }
                    break;
                case twoDimVert0:
                    if (unlikely(b1i > columns + 1)) {
                        error(errSyntaxError, getPos(), "Bad 2D code {0:04x} in CCITTFax stream", code1);
                        err = true;
                        break;
                    }
                    addPixels(refLine[b1i], blackPixels);
                    blackPixels ^= 1;
                    if (codingLine[a0i] < columns) {
                        ++b1i;
                        while (refLine[b1i] <= codingLine[a0i] && refLine[b1i] < columns) {
                            b1i += 2;
                            if (unlikely(b1i > columns + 1)) {
                                error(errSyntaxError, getPos(), "Bad 2D code {0:04x} in CCITTFax stream", code1);
                                err = true;
                                break;
                            }
                        }
                    }
                    break;
                case twoDimVertL3:
                    if (unlikely(b1i > columns + 1)) {
                        error(errSyntaxError, getPos(), "Bad 2D code {0:04x} in CCITTFax stream", code1);
                        err = true;
                        break;
                    }
                    addPixelsNeg(refLine[b1i] - 3, blackPixels);
                    blackPixels ^= 1;
                    if (codingLine[a0i] < columns) {
                        if (b1i > 0) {
                            --b1i;
                        } else {
                            ++b1i;
                        }
                        while (refLine[b1i] <= codingLine[a0i] && refLine[b1i] < columns) {
                            b1i += 2;
                            if (unlikely(b1i > columns + 1)) {
                                error(errSyntaxError, getPos(), "Bad 2D code {0:04x} in CCITTFax stream", code1);
                                err = true;
                                break;
                            }
                        }
                    }
                    break;
                case twoDimVertL2:
                    if (unlikely(b1i > columns + 1)) {
                        error(errSyntaxError, getPos(), "Bad 2D code {0:04x} in CCITTFax stream", code1);
                        err = true;
                        break;
                    }
                    addPixelsNeg(refLine[b1i] - 2, blackPixels);
                    blackPixels ^= 1;
                    if (codingLine[a0i] < columns) {
                        if (b1i > 0) {
                            --b1i;
                        } else {
                            ++b1i;
                        }
                        while (refLine[b1i] <= codingLine[a0i] && refLine[b1i] < columns) {
                            b1i += 2;
                            if (unlikely(b1i > columns + 1)) {
                                error(errSyntaxError, getPos(), "Bad 2D code {0:04x} in CCITTFax stream", code1);
                                err = true;
                                break;
                            }
                        }
                    }
                    break;
                case twoDimVertL1:
                    if (unlikely(b1i > columns + 1)) {
                        error(errSyntaxError, getPos(), "Bad 2D code {0:04x} in CCITTFax stream", code1);
                        err = true;
                        break;
                    }
                    addPixelsNeg(refLine[b1i] - 1, blackPixels);
                    blackPixels ^= 1;
                    if (codingLine[a0i] < columns) {
                        if (b1i > 0) {
                            --b1i;
                        } else {
                            ++b1i;
                        }
                        while (refLine[b1i] <= codingLine[a0i] && refLine[b1i] < columns) {
                            b1i += 2;
                            if (unlikely(b1i > columns + 1)) {
                                error(errSyntaxError, getPos(), "Bad 2D code {0:04x} in CCITTFax stream", code1);
                                err = true;
                                break;
                            }
                        }
                    }
                    break;
                case EOF:
                    addPixels(columns, 0);
                    eof = true;
                    break;
                default:
                    error(errSyntaxError, getPos(), "Bad 2D code {0:04x} in CCITTFax stream", code1);
                    addPixels(columns, 0);
                    err = true;
                    break;
                }
            }

            // 1-D encoding
        } else {
            codingLine[0] = 0;
            a0i = 0;
            blackPixels = 0;
            while (codingLine[a0i] < columns) {
                code1 = 0;
                if (blackPixels) {
                    do {
                        code1 += code3 = getBlackCode();
                    } while (code3 >= 64);
                } else {
                    do {
                        code1 += code3 = getWhiteCode();
                    } while (code3 >= 64);
                }
                addPixels(codingLine[a0i] + code1, blackPixels);
                blackPixels ^= 1;
            }
        }

        // check for end-of-line marker, skipping over any extra zero bits
        // (if EncodedByteAlign is true and EndOfLine is false, there can
        // be "false" EOL markers -- i.e., if the last n unused bits in
        // row i are set to zero, and the first 11-n bits in row i+1
        // happen to be zero -- so we don't look for EOL markers in this
        // case)
        gotEOL = false;
        if (!endOfBlock && row == rows - 1) {
            eof = true;
        } else if (endOfLine || !byteAlign) {
            code1 = lookBits(12);
            if (endOfLine) {
                while (code1 != EOF && code1 != 0x001) {
                    eatBits(1);
                    code1 = lookBits(12);
                }
            } else {
                while (code1 == 0) {
                    eatBits(1);
                    code1 = lookBits(12);
                }
            }
            if (code1 == 0x001) {
                eatBits(12);
                gotEOL = true;
            }
        }

        // byte-align the row
        // (Adobe apparently doesn't do byte alignment after EOL markers
        // -- I've seen CCITT image data streams in two different formats,
        // both with the byteAlign flag set:
        //   1. xx:x0:01:yy:yy
        //   2. xx:00:1y:yy:yy
        // where xx is the previous line, yy is the next line, and colons
        // separate bytes.)
        if (byteAlign && !gotEOL) {
            inputBits &= ~7;
        }

        // check for end of stream
        if (lookBits(1) == EOF) {
            eof = true;
        }

        // get 2D encoding tag
        if (!eof && encoding > 0) {
            nextLine2D = !lookBits(1);
            eatBits(1);
        }

        // check for end-of-block marker
        if (endOfBlock && !endOfLine && byteAlign) {
            // in this case, we didn't check for an EOL code above, so we
            // need to check here
            code1 = lookBits(24);
            if (code1 == 0x001001) {
                eatBits(12);
                gotEOL = true;
            }
        }
        if (endOfBlock && gotEOL) {
            code1 = lookBits(12);
            if (code1 == 0x001) {
                eatBits(12);
                if (encoding > 0) {
                    lookBits(1);
                    eatBits(1);
                }
                if (encoding >= 0) {
                    for (i = 0; i < 4; ++i) {
                        code1 = lookBits(12);
                        if (code1 != 0x001) {
                            error(errSyntaxError, getPos(), "Bad RTC code in CCITTFax stream");
                        }
                        eatBits(12);
                        if (encoding > 0) {
                            lookBits(1);
                            eatBits(1);
                        }
                    }
                }
                eof = true;
            }

            // look for an end-of-line marker after an error -- we only do
            // this if we know the stream contains end-of-line markers because
            // the "just plow on" technique tends to work better otherwise
        } else if (err && endOfLine) {
            while (true) {
                code1 = lookBits(13);
                if (code1 == EOF) {
                    eof = true;
                    return EOF;
                }
                if ((code1 >> 1) == 0x001) {
                    break;
                }
                eatBits(1);
            }
            eatBits(12);
            if (encoding > 0) {
                eatBits(1);
                nextLine2D = !(code1 & 1);
            }
        }

        // set up for output
        if (codingLine[0] > 0) {
            outputBits = codingLine[a0i = 0];
        } else {
            outputBits = codingLine[a0i = 1];
        }

        ++row;
    }

    // get a byte
    if (outputBits >= 8) {
        buf = (a0i & 1) ? 0x00 : 0xff;
        outputBits -= 8;
        if (outputBits == 0 && codingLine[a0i] < columns) {
            ++a0i;
            outputBits = codingLine[a0i] - codingLine[a0i - 1];
        }
    } else {
        bits = 8;
        buf = 0;
        do {
            if (outputBits > bits) {
                buf <<= bits;
                if (!(a0i & 1)) {
                    buf |= 0xff >> (8 - bits);
                }
                outputBits -= bits;
                bits = 0;
            } else {
                buf <<= outputBits;
                if (!(a0i & 1)) {
                    buf |= 0xff >> (8 - outputBits);
                }
                bits -= outputBits;
                outputBits = 0;
                if (codingLine[a0i] < columns) {
                    ++a0i;
                    if (unlikely(a0i > columns)) {
                        error(errSyntaxError, getPos(), "Bad bits {0:04x} in CCITTFax stream", bits);
                        err = true;
                        break;
                    }
                    outputBits = codingLine[a0i] - codingLine[a0i - 1];
                } else if (bits > 0) {
                    buf <<= bits;
                    bits = 0;
                }
            }
        } while (bits);
    }
    if (black) {
        buf ^= 0xff;
    }
    return buf;
}

short CCITTFaxStream::getTwoDimCode()
{
    int code;
    const CCITTCode *p;
    int n;

    code = 0; // make gcc happy
    if (endOfBlock) {
        if ((code = lookBits(7)) != EOF) {
            p = &twoDimTab1[code];
            if (p->bits > 0) {
                eatBits(p->bits);
                return p->n;
            }
        }
    } else {
        for (n = 1; n <= 7; ++n) {
            if ((code = lookBits(n)) == EOF) {
                break;
            }
            if (n < 7) {
                code <<= 7 - n;
            }
            p = &twoDimTab1[code];
            if (p->bits == n) {
                eatBits(n);
                return p->n;
            }
        }
    }
    error(errSyntaxError, getPos(), "Bad two dim code ({0:04x}) in CCITTFax stream", code);
    return EOF;
}

short CCITTFaxStream::getWhiteCode()
{
    short code;
    const CCITTCode *p;
    int n;

    code = 0; // make gcc happy
    if (endOfBlock) {
        code = lookBits(12);
        if (code == EOF) {
            return 1;
        }
        if ((code >> 5) == 0) {
            p = &whiteTab1[code];
        } else {
            p = &whiteTab2[code >> 3];
        }
        if (p->bits > 0) {
            eatBits(p->bits);
            return p->n;
        }
    } else {
        for (n = 1; n <= 9; ++n) {
            code = lookBits(n);
            if (code == EOF) {
                return 1;
            }
            if (n < 9) {
                code <<= 9 - n;
            }
            p = &whiteTab2[code];
            if (p->bits == n) {
                eatBits(n);
                return p->n;
            }
        }
        for (n = 11; n <= 12; ++n) {
            code = lookBits(n);
            if (code == EOF) {
                return 1;
            }
            if (n < 12) {
                code <<= 12 - n;
            }
            p = &whiteTab1[code];
            if (p->bits == n) {
                eatBits(n);
                return p->n;
            }
        }
    }
    error(errSyntaxError, getPos(), "Bad white code ({0:04x}) in CCITTFax stream", code);
    // eat a bit and return a positive number so that the caller doesn't
    // go into an infinite loop
    eatBits(1);
    return 1;
}

short CCITTFaxStream::getBlackCode()
{
    short code;
    const CCITTCode *p;
    int n;

    code = 0; // make gcc happy
    if (endOfBlock) {
        code = lookBits(13);
        if (code == EOF) {
            return 1;
        }
        if ((code >> 7) == 0) {
            p = &blackTab1[code];
        } else if ((code >> 9) == 0 && (code >> 7) != 0) {
            p = &blackTab2[(code >> 1) - 64];
        } else {
            p = &blackTab3[code >> 7];
        }
        if (p->bits > 0) {
            eatBits(p->bits);
            return p->n;
        }
    } else {
        for (n = 2; n <= 6; ++n) {
            code = lookBits(n);
            if (code == EOF) {
                return 1;
            }
            if (n < 6) {
                code <<= 6 - n;
            }
            p = &blackTab3[code];
            if (p->bits == n) {
                eatBits(n);
                return p->n;
            }
        }
        for (n = 7; n <= 12; ++n) {
            code = lookBits(n);
            if (code == EOF) {
                return 1;
            }
            if (n < 12) {
                code <<= 12 - n;
            }
            if (code >= 64) {
                p = &blackTab2[code - 64];
                if (p->bits == n) {
                    eatBits(n);
                    return p->n;
                }
            }
        }
        for (n = 10; n <= 13; ++n) {
            code = lookBits(n);
            if (code == EOF) {
                return 1;
            }
            if (n < 13) {
                code <<= 13 - n;
            }
            p = &blackTab1[code];
            if (p->bits == n) {
                eatBits(n);
                return p->n;
            }
        }
    }
    error(errSyntaxError, getPos(), "Bad black code ({0:04x}) in CCITTFax stream", code);
    // eat a bit and return a positive number so that the caller doesn't
    // go into an infinite loop
    eatBits(1);
    return 1;
}

short CCITTFaxStream::lookBits(int n)
{
    int c;

    while (inputBits < n) {
        if ((c = str->getChar()) == EOF) {
            if (inputBits == 0) {
                return EOF;
            }
            // near the end of the stream, the caller may ask for more bits
            // than are available, but there may still be a valid code in
            // however many bits are available -- we need to return correct
            // data in this case
            return (inputBuf << (n - inputBits)) & (0xffffffff >> (32 - n));
        }
        inputBuf = (inputBuf << 8) + c;
        inputBits += 8;
    }
    return (inputBuf >> (inputBits - n)) & (0xffffffff >> (32 - n));
}

std::optional<std::string> CCITTFaxStream::getPSFilter(int psLevel, const char *indent)
{
    std::optional<std::string> s;
    char s1[50];

    if (psLevel < 2) {
        return {};
    }
    if (!(s = str->getPSFilter(psLevel, indent))) {
        return {};
    }
    s->append(indent).append("<< ");
    if (encoding != 0) {
        sprintf(s1, "/K %d ", encoding);
        s->append(s1);
    }
    if (endOfLine) {
        s->append("/EndOfLine true ");
    }
    if (byteAlign) {
        s->append("/EncodedByteAlign true ");
    }
    sprintf(s1, "/Columns %d ", columns);
    s->append(s1);
    if (rows != 0) {
        sprintf(s1, "/Rows %d ", rows);
        s->append(s1);
    }
    if (!endOfBlock) {
        s->append("/EndOfBlock false ");
    }
    if (black) {
        s->append("/BlackIs1 true ");
    }
    s->append(">> /CCITTFaxDecode filter\n");
    return s;
}

bool CCITTFaxStream::isBinary(bool /*last*/) const
{
    return str->isBinary(true);
}

#if !ENABLE_ZLIB_UNCOMPRESS
//------------------------------------------------------------------------
// FlateStream
//------------------------------------------------------------------------

const int FlateStream::codeLenCodeMap[flateMaxCodeLenCodes] = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 };

const FlateDecode FlateStream::lengthDecode[flateMaxLitCodes - 257] = {
    { .bits = 0, .first = 3 },   { .bits = 0, .first = 4 },   { .bits = 0, .first = 5 },   { .bits = 0, .first = 6 },   { .bits = 0, .first = 7 },   { .bits = 0, .first = 8 },   { .bits = 0, .first = 9 },  { .bits = 0, .first = 10 },
    { .bits = 1, .first = 11 },  { .bits = 1, .first = 13 },  { .bits = 1, .first = 15 },  { .bits = 1, .first = 17 },  { .bits = 2, .first = 19 },  { .bits = 2, .first = 23 },  { .bits = 2, .first = 27 }, { .bits = 2, .first = 31 },
    { .bits = 3, .first = 35 },  { .bits = 3, .first = 43 },  { .bits = 3, .first = 51 },  { .bits = 3, .first = 59 },  { .bits = 4, .first = 67 },  { .bits = 4, .first = 83 },  { .bits = 4, .first = 99 }, { .bits = 4, .first = 115 },
    { .bits = 5, .first = 131 }, { .bits = 5, .first = 163 }, { .bits = 5, .first = 195 }, { .bits = 5, .first = 227 }, { .bits = 0, .first = 258 }, { .bits = 0, .first = 258 }, { .bits = 0, .first = 258 }
};

const FlateDecode FlateStream::distDecode[flateMaxDistCodes] = { { .bits = 0, .first = 1 },     { .bits = 0, .first = 2 },     { .bits = 0, .first = 3 },      { .bits = 0, .first = 4 },      { .bits = 1, .first = 5 },
                                                                 { .bits = 1, .first = 7 },     { .bits = 2, .first = 9 },     { .bits = 2, .first = 13 },     { .bits = 3, .first = 17 },     { .bits = 3, .first = 25 },
                                                                 { .bits = 4, .first = 33 },    { .bits = 4, .first = 49 },    { .bits = 5, .first = 65 },     { .bits = 5, .first = 97 },     { .bits = 6, .first = 129 },
                                                                 { .bits = 6, .first = 193 },   { .bits = 7, .first = 257 },   { .bits = 7, .first = 385 },    { .bits = 8, .first = 513 },    { .bits = 8, .first = 769 },
                                                                 { .bits = 9, .first = 1025 },  { .bits = 9, .first = 1537 },  { .bits = 10, .first = 2049 },  { .bits = 10, .first = 3073 },  { .bits = 11, .first = 4097 },
                                                                 { .bits = 11, .first = 6145 }, { .bits = 12, .first = 8193 }, { .bits = 12, .first = 12289 }, { .bits = 13, .first = 16385 }, { .bits = 13, .first = 24577 } };

static const FlateCode flateFixedLitCodeTabCodes[512] = {
    { .len = 7, .val = 0x0100 }, { .len = 8, .val = 0x0050 }, { .len = 8, .val = 0x0010 }, { .len = 8, .val = 0x0118 }, { .len = 7, .val = 0x0110 }, { .len = 8, .val = 0x0070 }, { .len = 8, .val = 0x0030 }, { .len = 9, .val = 0x00c0 },
    { .len = 7, .val = 0x0108 }, { .len = 8, .val = 0x0060 }, { .len = 8, .val = 0x0020 }, { .len = 9, .val = 0x00a0 }, { .len = 8, .val = 0x0000 }, { .len = 8, .val = 0x0080 }, { .len = 8, .val = 0x0040 }, { .len = 9, .val = 0x00e0 },
    { .len = 7, .val = 0x0104 }, { .len = 8, .val = 0x0058 }, { .len = 8, .val = 0x0018 }, { .len = 9, .val = 0x0090 }, { .len = 7, .val = 0x0114 }, { .len = 8, .val = 0x0078 }, { .len = 8, .val = 0x0038 }, { .len = 9, .val = 0x00d0 },
    { .len = 7, .val = 0x010c }, { .len = 8, .val = 0x0068 }, { .len = 8, .val = 0x0028 }, { .len = 9, .val = 0x00b0 }, { .len = 8, .val = 0x0008 }, { .len = 8, .val = 0x0088 }, { .len = 8, .val = 0x0048 }, { .len = 9, .val = 0x00f0 },
    { .len = 7, .val = 0x0102 }, { .len = 8, .val = 0x0054 }, { .len = 8, .val = 0x0014 }, { .len = 8, .val = 0x011c }, { .len = 7, .val = 0x0112 }, { .len = 8, .val = 0x0074 }, { .len = 8, .val = 0x0034 }, { .len = 9, .val = 0x00c8 },
    { .len = 7, .val = 0x010a }, { .len = 8, .val = 0x0064 }, { .len = 8, .val = 0x0024 }, { .len = 9, .val = 0x00a8 }, { .len = 8, .val = 0x0004 }, { .len = 8, .val = 0x0084 }, { .len = 8, .val = 0x0044 }, { .len = 9, .val = 0x00e8 },
    { .len = 7, .val = 0x0106 }, { .len = 8, .val = 0x005c }, { .len = 8, .val = 0x001c }, { .len = 9, .val = 0x0098 }, { .len = 7, .val = 0x0116 }, { .len = 8, .val = 0x007c }, { .len = 8, .val = 0x003c }, { .len = 9, .val = 0x00d8 },
    { .len = 7, .val = 0x010e }, { .len = 8, .val = 0x006c }, { .len = 8, .val = 0x002c }, { .len = 9, .val = 0x00b8 }, { .len = 8, .val = 0x000c }, { .len = 8, .val = 0x008c }, { .len = 8, .val = 0x004c }, { .len = 9, .val = 0x00f8 },
    { .len = 7, .val = 0x0101 }, { .len = 8, .val = 0x0052 }, { .len = 8, .val = 0x0012 }, { .len = 8, .val = 0x011a }, { .len = 7, .val = 0x0111 }, { .len = 8, .val = 0x0072 }, { .len = 8, .val = 0x0032 }, { .len = 9, .val = 0x00c4 },
    { .len = 7, .val = 0x0109 }, { .len = 8, .val = 0x0062 }, { .len = 8, .val = 0x0022 }, { .len = 9, .val = 0x00a4 }, { .len = 8, .val = 0x0002 }, { .len = 8, .val = 0x0082 }, { .len = 8, .val = 0x0042 }, { .len = 9, .val = 0x00e4 },
    { .len = 7, .val = 0x0105 }, { .len = 8, .val = 0x005a }, { .len = 8, .val = 0x001a }, { .len = 9, .val = 0x0094 }, { .len = 7, .val = 0x0115 }, { .len = 8, .val = 0x007a }, { .len = 8, .val = 0x003a }, { .len = 9, .val = 0x00d4 },
    { .len = 7, .val = 0x010d }, { .len = 8, .val = 0x006a }, { .len = 8, .val = 0x002a }, { .len = 9, .val = 0x00b4 }, { .len = 8, .val = 0x000a }, { .len = 8, .val = 0x008a }, { .len = 8, .val = 0x004a }, { .len = 9, .val = 0x00f4 },
    { .len = 7, .val = 0x0103 }, { .len = 8, .val = 0x0056 }, { .len = 8, .val = 0x0016 }, { .len = 8, .val = 0x011e }, { .len = 7, .val = 0x0113 }, { .len = 8, .val = 0x0076 }, { .len = 8, .val = 0x0036 }, { .len = 9, .val = 0x00cc },
    { .len = 7, .val = 0x010b }, { .len = 8, .val = 0x0066 }, { .len = 8, .val = 0x0026 }, { .len = 9, .val = 0x00ac }, { .len = 8, .val = 0x0006 }, { .len = 8, .val = 0x0086 }, { .len = 8, .val = 0x0046 }, { .len = 9, .val = 0x00ec },
    { .len = 7, .val = 0x0107 }, { .len = 8, .val = 0x005e }, { .len = 8, .val = 0x001e }, { .len = 9, .val = 0x009c }, { .len = 7, .val = 0x0117 }, { .len = 8, .val = 0x007e }, { .len = 8, .val = 0x003e }, { .len = 9, .val = 0x00dc },
    { .len = 7, .val = 0x010f }, { .len = 8, .val = 0x006e }, { .len = 8, .val = 0x002e }, { .len = 9, .val = 0x00bc }, { .len = 8, .val = 0x000e }, { .len = 8, .val = 0x008e }, { .len = 8, .val = 0x004e }, { .len = 9, .val = 0x00fc },
    { .len = 7, .val = 0x0100 }, { .len = 8, .val = 0x0051 }, { .len = 8, .val = 0x0011 }, { .len = 8, .val = 0x0119 }, { .len = 7, .val = 0x0110 }, { .len = 8, .val = 0x0071 }, { .len = 8, .val = 0x0031 }, { .len = 9, .val = 0x00c2 },
    { .len = 7, .val = 0x0108 }, { .len = 8, .val = 0x0061 }, { .len = 8, .val = 0x0021 }, { .len = 9, .val = 0x00a2 }, { .len = 8, .val = 0x0001 }, { .len = 8, .val = 0x0081 }, { .len = 8, .val = 0x0041 }, { .len = 9, .val = 0x00e2 },
    { .len = 7, .val = 0x0104 }, { .len = 8, .val = 0x0059 }, { .len = 8, .val = 0x0019 }, { .len = 9, .val = 0x0092 }, { .len = 7, .val = 0x0114 }, { .len = 8, .val = 0x0079 }, { .len = 8, .val = 0x0039 }, { .len = 9, .val = 0x00d2 },
    { .len = 7, .val = 0x010c }, { .len = 8, .val = 0x0069 }, { .len = 8, .val = 0x0029 }, { .len = 9, .val = 0x00b2 }, { .len = 8, .val = 0x0009 }, { .len = 8, .val = 0x0089 }, { .len = 8, .val = 0x0049 }, { .len = 9, .val = 0x00f2 },
    { .len = 7, .val = 0x0102 }, { .len = 8, .val = 0x0055 }, { .len = 8, .val = 0x0015 }, { .len = 8, .val = 0x011d }, { .len = 7, .val = 0x0112 }, { .len = 8, .val = 0x0075 }, { .len = 8, .val = 0x0035 }, { .len = 9, .val = 0x00ca },
    { .len = 7, .val = 0x010a }, { .len = 8, .val = 0x0065 }, { .len = 8, .val = 0x0025 }, { .len = 9, .val = 0x00aa }, { .len = 8, .val = 0x0005 }, { .len = 8, .val = 0x0085 }, { .len = 8, .val = 0x0045 }, { .len = 9, .val = 0x00ea },
    { .len = 7, .val = 0x0106 }, { .len = 8, .val = 0x005d }, { .len = 8, .val = 0x001d }, { .len = 9, .val = 0x009a }, { .len = 7, .val = 0x0116 }, { .len = 8, .val = 0x007d }, { .len = 8, .val = 0x003d }, { .len = 9, .val = 0x00da },
    { .len = 7, .val = 0x010e }, { .len = 8, .val = 0x006d }, { .len = 8, .val = 0x002d }, { .len = 9, .val = 0x00ba }, { .len = 8, .val = 0x000d }, { .len = 8, .val = 0x008d }, { .len = 8, .val = 0x004d }, { .len = 9, .val = 0x00fa },
    { .len = 7, .val = 0x0101 }, { .len = 8, .val = 0x0053 }, { .len = 8, .val = 0x0013 }, { .len = 8, .val = 0x011b }, { .len = 7, .val = 0x0111 }, { .len = 8, .val = 0x0073 }, { .len = 8, .val = 0x0033 }, { .len = 9, .val = 0x00c6 },
    { .len = 7, .val = 0x0109 }, { .len = 8, .val = 0x0063 }, { .len = 8, .val = 0x0023 }, { .len = 9, .val = 0x00a6 }, { .len = 8, .val = 0x0003 }, { .len = 8, .val = 0x0083 }, { .len = 8, .val = 0x0043 }, { .len = 9, .val = 0x00e6 },
    { .len = 7, .val = 0x0105 }, { .len = 8, .val = 0x005b }, { .len = 8, .val = 0x001b }, { .len = 9, .val = 0x0096 }, { .len = 7, .val = 0x0115 }, { .len = 8, .val = 0x007b }, { .len = 8, .val = 0x003b }, { .len = 9, .val = 0x00d6 },
    { .len = 7, .val = 0x010d }, { .len = 8, .val = 0x006b }, { .len = 8, .val = 0x002b }, { .len = 9, .val = 0x00b6 }, { .len = 8, .val = 0x000b }, { .len = 8, .val = 0x008b }, { .len = 8, .val = 0x004b }, { .len = 9, .val = 0x00f6 },
    { .len = 7, .val = 0x0103 }, { .len = 8, .val = 0x0057 }, { .len = 8, .val = 0x0017 }, { .len = 8, .val = 0x011f }, { .len = 7, .val = 0x0113 }, { .len = 8, .val = 0x0077 }, { .len = 8, .val = 0x0037 }, { .len = 9, .val = 0x00ce },
    { .len = 7, .val = 0x010b }, { .len = 8, .val = 0x0067 }, { .len = 8, .val = 0x0027 }, { .len = 9, .val = 0x00ae }, { .len = 8, .val = 0x0007 }, { .len = 8, .val = 0x0087 }, { .len = 8, .val = 0x0047 }, { .len = 9, .val = 0x00ee },
    { .len = 7, .val = 0x0107 }, { .len = 8, .val = 0x005f }, { .len = 8, .val = 0x001f }, { .len = 9, .val = 0x009e }, { .len = 7, .val = 0x0117 }, { .len = 8, .val = 0x007f }, { .len = 8, .val = 0x003f }, { .len = 9, .val = 0x00de },
    { .len = 7, .val = 0x010f }, { .len = 8, .val = 0x006f }, { .len = 8, .val = 0x002f }, { .len = 9, .val = 0x00be }, { .len = 8, .val = 0x000f }, { .len = 8, .val = 0x008f }, { .len = 8, .val = 0x004f }, { .len = 9, .val = 0x00fe },
    { .len = 7, .val = 0x0100 }, { .len = 8, .val = 0x0050 }, { .len = 8, .val = 0x0010 }, { .len = 8, .val = 0x0118 }, { .len = 7, .val = 0x0110 }, { .len = 8, .val = 0x0070 }, { .len = 8, .val = 0x0030 }, { .len = 9, .val = 0x00c1 },
    { .len = 7, .val = 0x0108 }, { .len = 8, .val = 0x0060 }, { .len = 8, .val = 0x0020 }, { .len = 9, .val = 0x00a1 }, { .len = 8, .val = 0x0000 }, { .len = 8, .val = 0x0080 }, { .len = 8, .val = 0x0040 }, { .len = 9, .val = 0x00e1 },
    { .len = 7, .val = 0x0104 }, { .len = 8, .val = 0x0058 }, { .len = 8, .val = 0x0018 }, { .len = 9, .val = 0x0091 }, { .len = 7, .val = 0x0114 }, { .len = 8, .val = 0x0078 }, { .len = 8, .val = 0x0038 }, { .len = 9, .val = 0x00d1 },
    { .len = 7, .val = 0x010c }, { .len = 8, .val = 0x0068 }, { .len = 8, .val = 0x0028 }, { .len = 9, .val = 0x00b1 }, { .len = 8, .val = 0x0008 }, { .len = 8, .val = 0x0088 }, { .len = 8, .val = 0x0048 }, { .len = 9, .val = 0x00f1 },
    { .len = 7, .val = 0x0102 }, { .len = 8, .val = 0x0054 }, { .len = 8, .val = 0x0014 }, { .len = 8, .val = 0x011c }, { .len = 7, .val = 0x0112 }, { .len = 8, .val = 0x0074 }, { .len = 8, .val = 0x0034 }, { .len = 9, .val = 0x00c9 },
    { .len = 7, .val = 0x010a }, { .len = 8, .val = 0x0064 }, { .len = 8, .val = 0x0024 }, { .len = 9, .val = 0x00a9 }, { .len = 8, .val = 0x0004 }, { .len = 8, .val = 0x0084 }, { .len = 8, .val = 0x0044 }, { .len = 9, .val = 0x00e9 },
    { .len = 7, .val = 0x0106 }, { .len = 8, .val = 0x005c }, { .len = 8, .val = 0x001c }, { .len = 9, .val = 0x0099 }, { .len = 7, .val = 0x0116 }, { .len = 8, .val = 0x007c }, { .len = 8, .val = 0x003c }, { .len = 9, .val = 0x00d9 },
    { .len = 7, .val = 0x010e }, { .len = 8, .val = 0x006c }, { .len = 8, .val = 0x002c }, { .len = 9, .val = 0x00b9 }, { .len = 8, .val = 0x000c }, { .len = 8, .val = 0x008c }, { .len = 8, .val = 0x004c }, { .len = 9, .val = 0x00f9 },
    { .len = 7, .val = 0x0101 }, { .len = 8, .val = 0x0052 }, { .len = 8, .val = 0x0012 }, { .len = 8, .val = 0x011a }, { .len = 7, .val = 0x0111 }, { .len = 8, .val = 0x0072 }, { .len = 8, .val = 0x0032 }, { .len = 9, .val = 0x00c5 },
    { .len = 7, .val = 0x0109 }, { .len = 8, .val = 0x0062 }, { .len = 8, .val = 0x0022 }, { .len = 9, .val = 0x00a5 }, { .len = 8, .val = 0x0002 }, { .len = 8, .val = 0x0082 }, { .len = 8, .val = 0x0042 }, { .len = 9, .val = 0x00e5 },
    { .len = 7, .val = 0x0105 }, { .len = 8, .val = 0x005a }, { .len = 8, .val = 0x001a }, { .len = 9, .val = 0x0095 }, { .len = 7, .val = 0x0115 }, { .len = 8, .val = 0x007a }, { .len = 8, .val = 0x003a }, { .len = 9, .val = 0x00d5 },
    { .len = 7, .val = 0x010d }, { .len = 8, .val = 0x006a }, { .len = 8, .val = 0x002a }, { .len = 9, .val = 0x00b5 }, { .len = 8, .val = 0x000a }, { .len = 8, .val = 0x008a }, { .len = 8, .val = 0x004a }, { .len = 9, .val = 0x00f5 },
    { .len = 7, .val = 0x0103 }, { .len = 8, .val = 0x0056 }, { .len = 8, .val = 0x0016 }, { .len = 8, .val = 0x011e }, { .len = 7, .val = 0x0113 }, { .len = 8, .val = 0x0076 }, { .len = 8, .val = 0x0036 }, { .len = 9, .val = 0x00cd },
    { .len = 7, .val = 0x010b }, { .len = 8, .val = 0x0066 }, { .len = 8, .val = 0x0026 }, { .len = 9, .val = 0x00ad }, { .len = 8, .val = 0x0006 }, { .len = 8, .val = 0x0086 }, { .len = 8, .val = 0x0046 }, { .len = 9, .val = 0x00ed },
    { .len = 7, .val = 0x0107 }, { .len = 8, .val = 0x005e }, { .len = 8, .val = 0x001e }, { .len = 9, .val = 0x009d }, { .len = 7, .val = 0x0117 }, { .len = 8, .val = 0x007e }, { .len = 8, .val = 0x003e }, { .len = 9, .val = 0x00dd },
    { .len = 7, .val = 0x010f }, { .len = 8, .val = 0x006e }, { .len = 8, .val = 0x002e }, { .len = 9, .val = 0x00bd }, { .len = 8, .val = 0x000e }, { .len = 8, .val = 0x008e }, { .len = 8, .val = 0x004e }, { .len = 9, .val = 0x00fd },
    { .len = 7, .val = 0x0100 }, { .len = 8, .val = 0x0051 }, { .len = 8, .val = 0x0011 }, { .len = 8, .val = 0x0119 }, { .len = 7, .val = 0x0110 }, { .len = 8, .val = 0x0071 }, { .len = 8, .val = 0x0031 }, { .len = 9, .val = 0x00c3 },
    { .len = 7, .val = 0x0108 }, { .len = 8, .val = 0x0061 }, { .len = 8, .val = 0x0021 }, { .len = 9, .val = 0x00a3 }, { .len = 8, .val = 0x0001 }, { .len = 8, .val = 0x0081 }, { .len = 8, .val = 0x0041 }, { .len = 9, .val = 0x00e3 },
    { .len = 7, .val = 0x0104 }, { .len = 8, .val = 0x0059 }, { .len = 8, .val = 0x0019 }, { .len = 9, .val = 0x0093 }, { .len = 7, .val = 0x0114 }, { .len = 8, .val = 0x0079 }, { .len = 8, .val = 0x0039 }, { .len = 9, .val = 0x00d3 },
    { .len = 7, .val = 0x010c }, { .len = 8, .val = 0x0069 }, { .len = 8, .val = 0x0029 }, { .len = 9, .val = 0x00b3 }, { .len = 8, .val = 0x0009 }, { .len = 8, .val = 0x0089 }, { .len = 8, .val = 0x0049 }, { .len = 9, .val = 0x00f3 },
    { .len = 7, .val = 0x0102 }, { .len = 8, .val = 0x0055 }, { .len = 8, .val = 0x0015 }, { .len = 8, .val = 0x011d }, { .len = 7, .val = 0x0112 }, { .len = 8, .val = 0x0075 }, { .len = 8, .val = 0x0035 }, { .len = 9, .val = 0x00cb },
    { .len = 7, .val = 0x010a }, { .len = 8, .val = 0x0065 }, { .len = 8, .val = 0x0025 }, { .len = 9, .val = 0x00ab }, { .len = 8, .val = 0x0005 }, { .len = 8, .val = 0x0085 }, { .len = 8, .val = 0x0045 }, { .len = 9, .val = 0x00eb },
    { .len = 7, .val = 0x0106 }, { .len = 8, .val = 0x005d }, { .len = 8, .val = 0x001d }, { .len = 9, .val = 0x009b }, { .len = 7, .val = 0x0116 }, { .len = 8, .val = 0x007d }, { .len = 8, .val = 0x003d }, { .len = 9, .val = 0x00db },
    { .len = 7, .val = 0x010e }, { .len = 8, .val = 0x006d }, { .len = 8, .val = 0x002d }, { .len = 9, .val = 0x00bb }, { .len = 8, .val = 0x000d }, { .len = 8, .val = 0x008d }, { .len = 8, .val = 0x004d }, { .len = 9, .val = 0x00fb },
    { .len = 7, .val = 0x0101 }, { .len = 8, .val = 0x0053 }, { .len = 8, .val = 0x0013 }, { .len = 8, .val = 0x011b }, { .len = 7, .val = 0x0111 }, { .len = 8, .val = 0x0073 }, { .len = 8, .val = 0x0033 }, { .len = 9, .val = 0x00c7 },
    { .len = 7, .val = 0x0109 }, { .len = 8, .val = 0x0063 }, { .len = 8, .val = 0x0023 }, { .len = 9, .val = 0x00a7 }, { .len = 8, .val = 0x0003 }, { .len = 8, .val = 0x0083 }, { .len = 8, .val = 0x0043 }, { .len = 9, .val = 0x00e7 },
    { .len = 7, .val = 0x0105 }, { .len = 8, .val = 0x005b }, { .len = 8, .val = 0x001b }, { .len = 9, .val = 0x0097 }, { .len = 7, .val = 0x0115 }, { .len = 8, .val = 0x007b }, { .len = 8, .val = 0x003b }, { .len = 9, .val = 0x00d7 },
    { .len = 7, .val = 0x010d }, { .len = 8, .val = 0x006b }, { .len = 8, .val = 0x002b }, { .len = 9, .val = 0x00b7 }, { .len = 8, .val = 0x000b }, { .len = 8, .val = 0x008b }, { .len = 8, .val = 0x004b }, { .len = 9, .val = 0x00f7 },
    { .len = 7, .val = 0x0103 }, { .len = 8, .val = 0x0057 }, { .len = 8, .val = 0x0017 }, { .len = 8, .val = 0x011f }, { .len = 7, .val = 0x0113 }, { .len = 8, .val = 0x0077 }, { .len = 8, .val = 0x0037 }, { .len = 9, .val = 0x00cf },
    { .len = 7, .val = 0x010b }, { .len = 8, .val = 0x0067 }, { .len = 8, .val = 0x0027 }, { .len = 9, .val = 0x00af }, { .len = 8, .val = 0x0007 }, { .len = 8, .val = 0x0087 }, { .len = 8, .val = 0x0047 }, { .len = 9, .val = 0x00ef },
    { .len = 7, .val = 0x0107 }, { .len = 8, .val = 0x005f }, { .len = 8, .val = 0x001f }, { .len = 9, .val = 0x009f }, { .len = 7, .val = 0x0117 }, { .len = 8, .val = 0x007f }, { .len = 8, .val = 0x003f }, { .len = 9, .val = 0x00df },
    { .len = 7, .val = 0x010f }, { .len = 8, .val = 0x006f }, { .len = 8, .val = 0x002f }, { .len = 9, .val = 0x00bf }, { .len = 8, .val = 0x000f }, { .len = 8, .val = 0x008f }, { .len = 8, .val = 0x004f }, { .len = 9, .val = 0x00ff }
};

FlateHuffmanTab FlateStream::fixedLitCodeTab = { .codes = flateFixedLitCodeTabCodes, .maxLen = 9 };

static const FlateCode flateFixedDistCodeTabCodes[32] = { { .len = 5, .val = 0x0000 }, { .len = 5, .val = 0x0010 }, { .len = 5, .val = 0x0008 }, { .len = 5, .val = 0x0018 }, { .len = 5, .val = 0x0004 }, { .len = 5, .val = 0x0014 },
                                                          { .len = 5, .val = 0x000c }, { .len = 5, .val = 0x001c }, { .len = 5, .val = 0x0002 }, { .len = 5, .val = 0x0012 }, { .len = 5, .val = 0x000a }, { .len = 5, .val = 0x001a },
                                                          { .len = 5, .val = 0x0006 }, { .len = 5, .val = 0x0016 }, { .len = 5, .val = 0x000e }, { .len = 0, .val = 0x0000 }, { .len = 5, .val = 0x0001 }, { .len = 5, .val = 0x0011 },
                                                          { .len = 5, .val = 0x0009 }, { .len = 5, .val = 0x0019 }, { .len = 5, .val = 0x0005 }, { .len = 5, .val = 0x0015 }, { .len = 5, .val = 0x000d }, { .len = 5, .val = 0x001d },
                                                          { .len = 5, .val = 0x0003 }, { .len = 5, .val = 0x0013 }, { .len = 5, .val = 0x000b }, { .len = 5, .val = 0x001b }, { .len = 5, .val = 0x0007 }, { .len = 5, .val = 0x0017 },
                                                          { .len = 5, .val = 0x000f }, { .len = 0, .val = 0x0000 } };

FlateHuffmanTab FlateStream::fixedDistCodeTab = { .codes = flateFixedDistCodeTabCodes, .maxLen = 5 };

FlateStream::FlateStream(std::unique_ptr<Stream> strA, int predictor, int columns, int colors, int bits) : OwnedFilterStream(std::move(strA))
{
    if (predictor != 1) {
        pred = new StreamPredictor(this, predictor, columns, colors, bits);
        if (!pred->isOk()) {
            delete pred;
            pred = nullptr;
        }
    } else {
        pred = nullptr;
    }
    litCodeTab.codes = nullptr;
    distCodeTab.codes = nullptr;
    memset(buf, 0, flateWindow);
}

FlateStream::~FlateStream()
{
    if (litCodeTab.codes != fixedLitCodeTab.codes) {
        gfree(const_cast<FlateCode *>(litCodeTab.codes));
    }
    if (distCodeTab.codes != fixedDistCodeTab.codes) {
        gfree(const_cast<FlateCode *>(distCodeTab.codes));
    }
    delete pred;
}

bool FlateStream::flateRewind(bool unfiltered)
{
    index = 0;
    remain = 0;
    codeBuf = 0;
    codeSize = 0;
    compressedBlock = false;
    endOfBlock = true;
    eof = true;
    if (unfiltered) {
        return str->unfilteredRewind();
    }
    return str->rewind();
}

bool FlateStream::unfilteredRewind()
{
    return flateRewind(true);
}

bool FlateStream::rewind()
{
    int cmf, flg;

    bool internalResetResult = flateRewind(false);

    // read header
    //~ need to look at window size?
    endOfBlock = eof = true;
    cmf = str->getChar();
    flg = str->getChar();
    if (cmf == EOF || flg == EOF) {
        return false;
    }
    if ((cmf & 0x0f) != 0x08) {
        error(errSyntaxError, getPos(), "Unknown compression method in flate stream");
        return false;
    }
    if ((((cmf << 8) + flg) % 31) != 0) {
        error(errSyntaxError, getPos(), "Bad FCHECK in flate stream");
        return false;
    }
    if (flg & 0x20) {
        error(errSyntaxError, getPos(), "FDICT bit set in flate stream");
        return false;
    }

    eof = false;

    return internalResetResult;
}

int FlateStream::getChar()
{
    if (pred) {
        return pred->getChar();
    }
    return doGetRawChar();
}

int FlateStream::getChars(int nChars, unsigned char *buffer)
{
    if (pred) {
        return pred->getChars(nChars, buffer);
    }
    for (int i = 0; i < nChars; ++i) {
        const int c = doGetRawChar();
        if (likely(c != EOF)) {
            buffer[i] = c;
        } else {
            return i;
        }
    }
    return nChars;
}

int FlateStream::lookChar()
{
    int c;

    if (pred) {
        return pred->lookChar();
    }
    while (remain == 0) {
        if (endOfBlock && eof) {
            return EOF;
        }
        readSome();
    }
    c = buf[index];
    return c;
}

void FlateStream::getRawChars(int nChars, int *buffer)
{
    for (int i = 0; i < nChars; ++i) {
        buffer[i] = doGetRawChar();
    }
}

int FlateStream::getRawChar()
{
    return doGetRawChar();
}

std::optional<std::string> FlateStream::getPSFilter(int psLevel, const char *indent)
{
    std::optional<std::string> s;

    if (psLevel < 3 || pred) {
        return {};
    }
    if (!(s = str->getPSFilter(psLevel, indent))) {
        return {};
    }
    s->append(indent).append("<< >> /FlateDecode filter\n");
    return s;
}

bool FlateStream::isBinary(bool /*last*/) const
{
    return str->isBinary(true);
}

void FlateStream::readSome()
{
    int code1, code2;
    int len, dist;
    int i, j, k;
    int c;

    if (endOfBlock) {
        if (!startBlock()) {
            return;
        }
    }

    if (compressedBlock) {
        if ((code1 = getHuffmanCodeWord(&litCodeTab)) == EOF) {
            goto err;
        }
        if (code1 < 256) {
            buf[index] = code1;
            remain = 1;
        } else if (code1 == 256) {
            endOfBlock = true;
            remain = 0;
        } else {
            code1 -= 257;
            code2 = lengthDecode[code1].bits;
            if (code2 > 0 && (code2 = getCodeWord(code2)) == EOF) {
                goto err;
            }
            len = lengthDecode[code1].first + code2;
            if ((code1 = getHuffmanCodeWord(&distCodeTab)) == EOF) {
                goto err;
            }
            code2 = distDecode[code1].bits;
            if (code2 > 0 && (code2 = getCodeWord(code2)) == EOF) {
                goto err;
            }
            dist = distDecode[code1].first + code2;
            i = index;
            j = (index - dist) & flateMask;
            for (k = 0; k < len; ++k) {
                buf[i] = buf[j];
                i = (i + 1) & flateMask;
                j = (j + 1) & flateMask;
            }
            remain = len;
        }

    } else {
        len = (blockLen < flateWindow) ? blockLen : flateWindow;
        for (i = 0, j = index; i < len; ++i, j = (j + 1) & flateMask) {
            if ((c = str->getChar()) == EOF) {
                endOfBlock = eof = true;
                break;
            }
            buf[j] = c & 0xff;
        }
        remain = i;
        blockLen -= len;
        if (blockLen == 0) {
            endOfBlock = true;
        }
    }

    return;

err:
    error(errSyntaxError, getPos(), "Unexpected end of file in flate stream");
    endOfBlock = eof = true;
    remain = 0;
}

bool FlateStream::startBlock()
{
    int blockHdr;
    int c;
    int check;

    // free the code tables from the previous block
    if (litCodeTab.codes != fixedLitCodeTab.codes) {
        gfree(const_cast<FlateCode *>(litCodeTab.codes));
    }
    litCodeTab.codes = nullptr;
    if (distCodeTab.codes != fixedDistCodeTab.codes) {
        gfree(const_cast<FlateCode *>(distCodeTab.codes));
    }
    distCodeTab.codes = nullptr;

    // read block header
    blockHdr = getCodeWord(3);
    if (blockHdr & 1) {
        eof = true;
    }
    blockHdr >>= 1;

    // uncompressed block
    if (blockHdr == 0) {
        compressedBlock = false;
        if ((c = str->getChar()) == EOF) {
            goto err;
        }
        blockLen = c & 0xff;
        if ((c = str->getChar()) == EOF) {
            goto err;
        }
        blockLen |= (c & 0xff) << 8;
        if ((c = str->getChar()) == EOF) {
            goto err;
        }
        check = c & 0xff;
        if ((c = str->getChar()) == EOF) {
            goto err;
        }
        check |= (c & 0xff) << 8;
        if (check != (~blockLen & 0xffff)) {
            error(errSyntaxError, getPos(), "Bad uncompressed block length in flate stream");
        }
        codeBuf = 0;
        codeSize = 0;

        // compressed block with fixed codes
    } else if (blockHdr == 1) {
        compressedBlock = true;
        loadFixedCodes();

        // compressed block with dynamic codes
    } else if (blockHdr == 2) {
        compressedBlock = true;
        if (!readDynamicCodes()) {
            goto err;
        }

        // unknown block type
    } else {
        goto err;
    }

    endOfBlock = false;
    return true;

err:
    error(errSyntaxError, getPos(), "Bad block header in flate stream");
    endOfBlock = eof = true;
    return false;
}

void FlateStream::loadFixedCodes()
{
    litCodeTab.codes = fixedLitCodeTab.codes;
    litCodeTab.maxLen = fixedLitCodeTab.maxLen;
    distCodeTab.codes = fixedDistCodeTab.codes;
    distCodeTab.maxLen = fixedDistCodeTab.maxLen;
}

bool FlateStream::readDynamicCodes()
{
    int numCodeLenCodes;
    int numLitCodes;
    int numDistCodes;
    int codeLenCodeLengths[flateMaxCodeLenCodes];
    FlateHuffmanTab codeLenCodeTab;
    int len, repeat, code;
    int i;

    codeLenCodeTab.codes = nullptr;

    // read lengths
    if ((numLitCodes = getCodeWord(5)) == EOF) {
        goto err;
    }
    numLitCodes += 257;
    if ((numDistCodes = getCodeWord(5)) == EOF) {
        goto err;
    }
    numDistCodes += 1;
    if ((numCodeLenCodes = getCodeWord(4)) == EOF) {
        goto err;
    }
    numCodeLenCodes += 4;
    if (numLitCodes > flateMaxLitCodes || numDistCodes > flateMaxDistCodes || numCodeLenCodes > flateMaxCodeLenCodes) {
        goto err;
    }

    // build the code length code table
    for (i = 0; i < flateMaxCodeLenCodes; ++i) {
        codeLenCodeLengths[i] = 0;
    }
    for (i = 0; i < numCodeLenCodes; ++i) {
        if ((codeLenCodeLengths[codeLenCodeMap[i]] = getCodeWord(3)) == -1) {
            goto err;
        }
    }
    codeLenCodeTab.codes = compHuffmanCodes(codeLenCodeLengths, flateMaxCodeLenCodes, &codeLenCodeTab.maxLen);

    // build the literal and distance code tables
    len = 0;
    repeat = 0;
    i = 0;
    while (i < numLitCodes + numDistCodes) {
        if ((code = getHuffmanCodeWord(&codeLenCodeTab)) == EOF) {
            goto err;
        }
        if (code == 16) {
            if ((repeat = getCodeWord(2)) == EOF) {
                goto err;
            }
            repeat += 3;
            if (i + repeat > numLitCodes + numDistCodes) {
                goto err;
            }
            for (; repeat > 0; --repeat) {
                codeLengths[i++] = len;
            }
        } else if (code == 17) {
            if ((repeat = getCodeWord(3)) == EOF) {
                goto err;
            }
            repeat += 3;
            if (i + repeat > numLitCodes + numDistCodes) {
                goto err;
            }
            len = 0;
            for (; repeat > 0; --repeat) {
                codeLengths[i++] = 0;
            }
        } else if (code == 18) {
            if ((repeat = getCodeWord(7)) == EOF) {
                goto err;
            }
            repeat += 11;
            if (i + repeat > numLitCodes + numDistCodes) {
                goto err;
            }
            len = 0;
            for (; repeat > 0; --repeat) {
                codeLengths[i++] = 0;
            }
        } else {
            codeLengths[i++] = len = code;
        }
    }
    litCodeTab.codes = compHuffmanCodes(codeLengths, numLitCodes, &litCodeTab.maxLen);
    distCodeTab.codes = compHuffmanCodes(codeLengths + numLitCodes, numDistCodes, &distCodeTab.maxLen);

    gfree(const_cast<FlateCode *>(codeLenCodeTab.codes));
    return true;

err:
    error(errSyntaxError, getPos(), "Bad dynamic code table in flate stream");
    gfree(const_cast<FlateCode *>(codeLenCodeTab.codes));
    return false;
}

// Convert an array <lengths> of <n> lengths, in value order, into a
// Huffman code lookup table.
FlateCode *FlateStream::compHuffmanCodes(const int *lengths, int n, int *maxLen)
{
    int len, code, code2, skip, val, i, t;

    // find max code length
    *maxLen = 0;
    for (val = 0; val < n; ++val) {
        if (lengths[val] > *maxLen) {
            *maxLen = lengths[val];
        }
    }

    // allocate the table
    const int tabSize = 1 << *maxLen;
    auto *codes = static_cast<FlateCode *>(gmallocn(tabSize, sizeof(FlateCode)));

    // clear the table
    for (i = 0; i < tabSize; ++i) {
        codes[i].len = 0;
        codes[i].val = 0;
    }

    // build the table
    for (len = 1, code = 0, skip = 2; len <= *maxLen; ++len, code <<= 1, skip <<= 1) {
        for (val = 0; val < n; ++val) {
            if (lengths[val] == len) {

                // bit-reverse the code
                code2 = 0;
                t = code;
                for (i = 0; i < len; ++i) {
                    code2 = (code2 << 1) | (t & 1);
                    t >>= 1;
                }

                // fill in the table entries
                for (i = code2; i < tabSize; i += skip) {
                    codes[i].len = static_cast<unsigned short>(len);
                    codes[i].val = static_cast<unsigned short>(val);
                }

                ++code;
            }
        }
    }

    return codes;
}

int FlateStream::getHuffmanCodeWord(FlateHuffmanTab *tab)
{
    const FlateCode *code;
    int c;

    while (codeSize < tab->maxLen) {
        if ((c = str->getChar()) == EOF) {
            break;
        }
        codeBuf |= (c & 0xff) << codeSize;
        codeSize += 8;
    }
    code = &tab->codes[codeBuf & ((1 << tab->maxLen) - 1)];
    if (codeSize == 0 || codeSize < code->len || code->len == 0) {
        return EOF;
    }
    codeBuf >>= code->len;
    codeSize -= code->len;
    return static_cast<int>(code->val);
}

int FlateStream::getCodeWord(int bits)
{
    int c;

    while (codeSize < bits) {
        if ((c = str->getChar()) == EOF) {
            return EOF;
        }
        codeBuf |= (c & 0xff) << codeSize;
        codeSize += 8;
    }
    c = codeBuf & ((1 << bits) - 1);
    codeBuf >>= bits;
    codeSize -= bits;
    return c;
}
#endif

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

EOFStream::EOFStream(std::unique_ptr<Stream> strA) : OwnedFilterStream(std::move(strA)) { }

EOFStream::~EOFStream() = default;

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

BufStream::BufStream(std::unique_ptr<Stream> strA, int bufSizeA) : OwnedFilterStream(std::move(strA))
{
    bufSize = bufSizeA;
    buf = static_cast<int *>(gmallocn(bufSize, sizeof(int)));
}

BufStream::~BufStream()
{
    gfree(buf);
}

bool BufStream::rewind()
{
    int i;

    bool success = str->rewind();
    for (i = 0; i < bufSize; ++i) {
        buf[i] = str->getChar();
    }

    return success;
}

int BufStream::getChar()
{
    int c, i;

    c = buf[0];
    for (i = 1; i < bufSize; ++i) {
        buf[i - 1] = buf[i];
    }
    buf[bufSize - 1] = str->getChar();
    return c;
}

int BufStream::lookChar()
{
    return buf[0];
}

int BufStream::lookChar(int idx)
{
    return buf[idx];
}

bool BufStream::isBinary(bool /*last*/) const
{
    return str->isBinary(true);
}

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

FixedLengthEncoder::FixedLengthEncoder(Stream *strA, int lengthA) : FilterStream(strA)
{
    length = lengthA;
    count = 0;
}

FixedLengthEncoder::~FixedLengthEncoder()
{
    if (str->isEncoder()) {
        delete str;
    }
}

bool FixedLengthEncoder::rewind()
{
    count = 0;

    return str->rewind();
}

int FixedLengthEncoder::getChar()
{
    if (length >= 0 && count >= length) {
        return EOF;
    }
    ++count;
    return str->getChar();
}

int FixedLengthEncoder::lookChar()
{
    if (length >= 0 && count >= length) {
        return EOF;
    }
    return str->getChar();
}

bool FixedLengthEncoder::isBinary(bool /*last*/) const
{
    return str->isBinary(true);
}

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

ASCIIHexEncoder::ASCIIHexEncoder(Stream *strA) : FilterStream(strA)
{
    bufPtr = bufEnd = buf;
    lineLen = 0;
    eof = false;
}

ASCIIHexEncoder::~ASCIIHexEncoder()
{
    if (str->isEncoder()) {
        delete str;
    }
}

bool ASCIIHexEncoder::rewind()
{
    bufPtr = bufEnd = buf;
    lineLen = 0;
    eof = false;

    return str->rewind();
}

bool ASCIIHexEncoder::fillBuf()
{
    static const char *hex = "0123456789abcdef";
    int c;

    if (eof) {
        return false;
    }
    bufPtr = bufEnd = buf;
    if ((c = str->getChar()) == EOF) {
        *bufEnd++ = '>';
        eof = true;
    } else {
        if (lineLen >= 64) {
            *bufEnd++ = '\n';
            lineLen = 0;
        }
        *bufEnd++ = hex[(c >> 4) & 0x0f];
        *bufEnd++ = hex[c & 0x0f];
        lineLen += 2;
    }
    return true;
}

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

ASCII85Encoder::ASCII85Encoder(Stream *strA) : FilterStream(strA)
{
    bufPtr = bufEnd = buf;
    lineLen = 0;
    eof = false;
}

ASCII85Encoder::~ASCII85Encoder()
{
    if (str->isEncoder()) {
        delete str;
    }
}

bool ASCII85Encoder::rewind()
{
    bufPtr = bufEnd = buf;
    lineLen = 0;
    eof = false;

    return str->rewind();
}

bool ASCII85Encoder::fillBuf()
{
    unsigned int t;
    char buf1[5];
    int c0, c1, c2, c3;
    int n, i;

    if (eof) {
        return false;
    }
    c0 = str->getChar();
    c1 = str->getChar();
    c2 = str->getChar();
    c3 = str->getChar();
    bufPtr = bufEnd = buf;
    if (c3 == EOF) {
        if (c0 == EOF) {
            n = 0;
            t = 0;
        } else {
            if (c1 == EOF) {
                n = 1;
                t = c0 << 24;
            } else if (c2 == EOF) {
                n = 2;
                t = (c0 << 24) | (c1 << 16);
            } else {
                n = 3;
                t = (c0 << 24) | (c1 << 16) | (c2 << 8);
            }
            for (i = 4; i >= 0; --i) {
                buf1[i] = static_cast<char>(t % 85 + 0x21);
                t /= 85;
            }
            for (i = 0; i <= n; ++i) {
                *bufEnd++ = buf1[i];
                if (++lineLen == 65) {
                    *bufEnd++ = '\n';
                    lineLen = 0;
                }
            }
        }
        *bufEnd++ = '~';
        *bufEnd++ = '>';
        eof = true;
    } else {
        t = (c0 << 24) | (c1 << 16) | (c2 << 8) | c3;
        if (t == 0) {
            *bufEnd++ = 'z';
            if (++lineLen == 65) {
                *bufEnd++ = '\n';
                lineLen = 0;
            }
        } else {
            for (i = 4; i >= 0; --i) {
                buf1[i] = static_cast<char>(t % 85 + 0x21);
                t /= 85;
            }
            for (i = 0; i <= 4; ++i) {
                *bufEnd++ = buf1[i];
                if (++lineLen == 65) {
                    *bufEnd++ = '\n';
                    lineLen = 0;
                }
            }
        }
    }
    return true;
}

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

RunLengthEncoder::RunLengthEncoder(Stream *strA) : FilterStream(strA)
{
    bufPtr = bufEnd = nextEnd = buf;
    eof = false;
}

RunLengthEncoder::~RunLengthEncoder()
{
    if (str->isEncoder()) {
        delete str;
    }
}

bool RunLengthEncoder::rewind()
{
    bufPtr = bufEnd = nextEnd = buf;
    eof = false;

    return str->rewind();
}

//
// When fillBuf finishes, buf[] looks like this:
//   +-----+--------------+-----------------+--
//   + tag | ... data ... | next 0, 1, or 2 |
//   +-----+--------------+-----------------+--
//    ^                    ^                 ^
//    bufPtr               bufEnd            nextEnd
//
bool RunLengthEncoder::fillBuf()
{
    int c, c1, c2;
    int n;

    // already hit EOF?
    if (eof) {
        return false;
    }

    // grab two bytes
    if (nextEnd < bufEnd + 1) {
        if ((c1 = str->getChar()) == EOF) {
            eof = true;
            return false;
        }
    } else {
        c1 = bufEnd[0] & 0xff;
    }
    if (nextEnd < bufEnd + 2) {
        if ((c2 = str->getChar()) == EOF) {
            eof = true;
            buf[0] = 0;
            buf[1] = c1;
            bufPtr = buf;
            bufEnd = &buf[2];
            return true;
        }
    } else {
        c2 = bufEnd[1] & 0xff;
    }

    // check for repeat
    c = 0; // make gcc happy
    if (c1 == c2) {
        n = 2;
        while (n < 128 && (c = str->getChar()) == c1) {
            ++n;
        }
        buf[0] = static_cast<char>(257 - n);
        buf[1] = c1;
        bufEnd = &buf[2];
        if (c == EOF) {
            eof = true;
        } else if (n < 128) {
            buf[2] = c;
            nextEnd = &buf[3];
        } else {
            nextEnd = bufEnd;
        }

        // get up to 128 chars
    } else {
        buf[1] = c1;
        buf[2] = c2;
        n = 2;
        while (n < 128) {
            if ((c = str->getChar()) == EOF) {
                eof = true;
                break;
            }
            ++n;
            buf[n] = c;
            if (buf[n] == buf[n - 1]) {
                break;
            }
        }
        if (buf[n] == buf[n - 1]) {
            buf[0] = static_cast<char>(n - 2 - 1);
            bufEnd = &buf[n - 1];
            nextEnd = &buf[n + 1];
        } else {
            buf[0] = static_cast<char>(n - 1);
            bufEnd = nextEnd = &buf[n + 1];
        }
    }
    bufPtr = buf;
    return true;
}

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

LZWEncoder::LZWEncoder(Stream *strA) : FilterStream(strA)
{
    inBufLen = 0;
    outBufLen = 0;
}

LZWEncoder::~LZWEncoder()
{
    if (str->isEncoder()) {
        delete str;
    }
}

bool LZWEncoder::rewind()
{
    int i;

    bool success = str->rewind();

    // initialize code table
    for (i = 0; i < 256; ++i) {
        table[i].byte = i;
        table[i].next = nullptr;
        table[i].children = nullptr;
    }
    nextSeq = 258;
    codeLen = 9;

    // initialize input buffer
    inBufLen = str->doGetChars(sizeof(inBuf), inBuf);

    // initialize output buffer with a clear-table code
    outBuf = 256;
    outBufLen = 9;
    needEOD = false;

    return success;
}

int LZWEncoder::getChar()
{
    int ret;

    if (inBufLen == 0 && !needEOD && outBufLen == 0) {
        return EOF;
    }
    if (outBufLen < 8 && (inBufLen > 0 || needEOD)) {
        fillBuf();
    }
    if (outBufLen >= 8) {
        ret = (outBuf >> (outBufLen - 8)) & 0xff;
        outBufLen -= 8;
    } else {
        ret = (outBuf << (8 - outBufLen)) & 0xff;
        outBufLen = 0;
    }
    return ret;
}

int LZWEncoder::lookChar()
{
    if (inBufLen == 0 && !needEOD && outBufLen == 0) {
        return EOF;
    }
    if (outBufLen < 8 && (inBufLen > 0 || needEOD)) {
        fillBuf();
    }
    if (outBufLen >= 8) {
        return (outBuf >> (outBufLen - 8)) & 0xff;
    }
    return (outBuf << (8 - outBufLen)) & 0xff;
}

// On input, outBufLen < 8.
// This function generates, at most, 2 12-bit codes
//   --> outBufLen < 8 + 12 + 12 = 32
void LZWEncoder::fillBuf()
{
    LZWEncoderNode *p0, *p1;
    int seqLen, code, i;

    if (needEOD) {
        outBuf = (outBuf << codeLen) | 257;
        outBufLen += codeLen;
        needEOD = false;
        return;
    }

    // find longest matching sequence (if any)
    p0 = table + inBuf[0];
    seqLen = 1;
    while (inBufLen > seqLen) {
        for (p1 = p0->children; p1; p1 = p1->next) {
            if (p1->byte == inBuf[seqLen]) {
                break;
            }
        }
        if (!p1) {
            break;
        }
        p0 = p1;
        ++seqLen;
    }
    code = static_cast<int>(p0 - table);

    // generate an output code
    outBuf = (outBuf << codeLen) | code;
    outBufLen += codeLen;

    // update the table
    table[nextSeq].byte = seqLen < inBufLen ? inBuf[seqLen] : 0;
    table[nextSeq].children = nullptr;
    if (table[code].children) {
        table[nextSeq].next = table[code].children;
    } else {
        table[nextSeq].next = nullptr;
    }
    table[code].children = table + nextSeq;
    ++nextSeq;

    // update the input buffer
    memmove(inBuf, inBuf + seqLen, inBufLen - seqLen);
    inBufLen -= seqLen;
    inBufLen += str->doGetChars(sizeof(inBuf) - inBufLen, inBuf + inBufLen);

    // increment codeLen; generate clear-table code
    if (nextSeq == (1 << codeLen)) {
        ++codeLen;
        if (codeLen == 13) {
            outBuf = (outBuf << 12) | 256;
            outBufLen += 12;
            for (i = 0; i < 256; ++i) {
                table[i].next = nullptr;
                table[i].children = nullptr;
            }
            nextSeq = 258;
            codeLen = 9;
        }
    }

    // generate EOD next time
    if (inBufLen == 0) {
        needEOD = true;
    }
}

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

CMYKGrayEncoder::CMYKGrayEncoder(Stream *strA) : FilterStream(strA)
{
    bufPtr = bufEnd = buf;
    eof = false;
}

CMYKGrayEncoder::~CMYKGrayEncoder()
{
    if (str->isEncoder()) {
        delete str;
    }
}

bool CMYKGrayEncoder::rewind()
{
    bufPtr = bufEnd = buf;
    eof = false;

    return str->rewind();
}

bool CMYKGrayEncoder::fillBuf()
{
    int c0, c1, c2, c3;
    int i;

    if (eof) {
        return false;
    }
    c0 = str->getChar();
    c1 = str->getChar();
    c2 = str->getChar();
    c3 = str->getChar();
    if (c3 == EOF) {
        eof = true;
        return false;
    }
    i = (3 * c0 + 6 * c1 + c2) / 10 + c3;
    if (i > 255) {
        i = 255;
    }
    bufPtr = bufEnd = buf;
    *bufEnd++ = static_cast<char>(i);
    return true;
}

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

RGBGrayEncoder::RGBGrayEncoder(Stream *strA) : FilterStream(strA)
{
    bufPtr = bufEnd = buf;
    eof = false;
}

RGBGrayEncoder::~RGBGrayEncoder()
{
    if (str->isEncoder()) {
        delete str;
    }
}

bool RGBGrayEncoder::rewind()
{
    bufPtr = bufEnd = buf;
    eof = false;

    return str->rewind();
}

bool RGBGrayEncoder::fillBuf()
{
    int c0, c1, c2;
    int i;

    if (eof) {
        return false;
    }
    c0 = str->getChar();
    c1 = str->getChar();
    c2 = str->getChar();
    if (c2 == EOF) {
        eof = true;
        return false;
    }
    i = 255 - (3 * c0 + 6 * c1 + c2) / 10;
    if (i < 0) {
        i = 0;
    }
    bufPtr = bufEnd = buf;
    *bufEnd++ = static_cast<char>(i);
    return true;
}

//------------------------------------------------------------------------
// SplashBitmapCMYKEncoder
//------------------------------------------------------------------------

SplashBitmapCMYKEncoder::SplashBitmapCMYKEncoder(SplashBitmap *bitmapA) : bitmap(bitmapA)
{
    width = static_cast<size_t>(4) * bitmap->getWidth();
    height = bitmap->getHeight();
    buf.resize(width);
    bufPtr = width;
    curLine = height - 1;
}

SplashBitmapCMYKEncoder::~SplashBitmapCMYKEncoder() = default;

bool SplashBitmapCMYKEncoder::rewind()
{
    bufPtr = width;
    curLine = height - 1;

    return true;
}

int SplashBitmapCMYKEncoder::lookChar()
{
    if (bufPtr >= width && !fillBuf()) {
        return EOF;
    }
    return buf[bufPtr];
}

int SplashBitmapCMYKEncoder::getChar()
{
    int ret = lookChar();
    bufPtr++;
    return ret;
}

bool SplashBitmapCMYKEncoder::fillBuf()
{
    if (curLine < 0) {
        return false;
    }

    if (bufPtr < width) {
        return true;
    }

    bitmap->getCMYKLine(curLine, buf.data());
    bufPtr = 0;
    curLine--;
    return true;
}

Goffset SplashBitmapCMYKEncoder::getPos()
{
    return (height - 1 - curLine) * width + bufPtr;
}

void SplashBitmapCMYKEncoder::setPos(Goffset pos, int dir)
{
    // This code is mostly untested!
    if (dir < 0) {
        curLine = pos / width;
    } else {
        curLine = height - 1 - pos / width;
    }

    bufPtr = width;
    fillBuf();

    if (dir < 0) {
        bufPtr = width - 1 - pos % width;
    } else {
        bufPtr = pos % width;
    }
}
