blob: 35ed575a5fe57480d8bdce28d94f22d28d8dedde [file] [log] [blame]
//========================================================================
//
// 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 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 g10 Code GmbH, Author: Sune Stolborg Vuorela <sune@vuorela.dk>
//
// 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>
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif
#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"
#ifdef ENABLE_LIBJPEG
# include "DCTStream.h"
#endif
#ifdef ENABLE_ZLIB_UNCOMPRESS
# include "FlateStream.h"
#endif
#ifdef ENABLE_LIBOPENJPEG
# include "JPEG2000Stream.h"
#else
# include "JPXStream.h"
#endif
#ifdef __DJGPP__
static bool setDJSYSFLAGS = false;
#endif
//------------------------------------------------------------------------
// Stream (base class)
//------------------------------------------------------------------------
Stream::Stream()
{
ref = 1;
}
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 = (unsigned int)sizeof(buf);
}
j = (unsigned int)doGetChars((int)i, buf);
count += j;
if (j != i) {
break;
}
}
return count;
}
GooString *Stream::getPSFilter(int psLevel, const char *indent)
{
return new GooString();
}
static Stream *wrapEOFStream(Stream *str)
{
if (dynamic_cast<EOFStream *>(str)) {
// str is already a EOFStream, no need to wrap it in another EOFStream
return str;
} else {
return new EOFStream(str);
}
}
Stream *Stream::addFilters(Dict *dict, int recursion)
{
Object obj, obj2;
Object params, params2;
Stream *str;
int i;
str = this;
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.getName(), 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.getName(), str, &params2, recursion);
} else {
error(errSyntaxError, getPos(), "Bad filter name");
str = wrapEOFStream(str);
}
}
} else if (!obj.isNull()) {
error(errSyntaxError, getPos(), "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(Stream *strA) : str(strA) { }
~BaseStreamStream() override;
StreamKind getKind() const override { return str->getBaseStream()->getKind(); }
void reset() override { str->getBaseStream()->reset(); }
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(); }
void unfilteredReset() override { str->getBaseStream()->unfilteredReset(); }
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;
Stream *Stream::makeFilter(const char *name, 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 (!strcmp(name, "ASCIIHexDecode") || !strcmp(name, "AHx")) {
str = new ASCIIHexStream(str);
} else if (!strcmp(name, "ASCII85Decode") || !strcmp(name, "A85")) {
str = new ASCII85Stream(str);
} else if (!strcmp(name, "LZWDecode") || !strcmp(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 = new LZWStream(str, pred, columns, colors, bits, early);
} else if (!strcmp(name, "RunLengthDecode") || !strcmp(name, "RL")) {
str = new RunLengthStream(str);
} else if (!strcmp(name, "CCITTFaxDecode") || !strcmp(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 = new CCITTFaxStream(str, encoding, endOfLine, byteAlign, columns, rows, endOfBlock, black, damagedRowsBeforeError);
} else if (!strcmp(name, "DCTDecode") || !strcmp(name, "DCT")) {
#ifdef HAVE_DCT_DECODER
int colorXform = -1;
if (params->isDict()) {
obj = params->dictLookup("ColorTransform", recursion);
if (obj.isInt()) {
colorXform = obj.getInt();
}
}
str = new DCTStream(str, colorXform, dict, recursion);
#else
error(errSyntaxError, getPos(), "Unknown filter '{0:s}'", name);
str = wrapEOFStream(str);
#endif
} else if (!strcmp(name, "FlateDecode") || !strcmp(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 = new FlateStream(str, pred, columns, colors, bits);
} else if (!strcmp(name, "JBIG2Decode")) {
Object globals;
if (params->isDict()) {
XRef *xref = params->getDict()->getXRef();
obj = params->dictLookupNF("JBIG2Globals").copy();
globals = obj.fetch(xref, recursion);
}
str = new JBIG2Stream(str, std::move(globals), &obj);
} else if (!strcmp(name, "JPXDecode")) {
#ifdef HAVE_JPX_DECODER
str = new JPXStream(str);
#else
error(errSyntaxError, getPos(), "Unknown filter '{0:s}'", name);
str = wrapEOFStream(str);
#endif
} else if (!strcmp(name, "Crypt")) {
if (str->getKind() == strCrypt) {
str = new BaseStreamStream(str);
} else {
error(errSyntaxError, getPos(), "Can't revert non decrypt streams");
}
} else {
error(errSyntaxError, getPos(), "Unknown filter '{0:s}'", name);
str = wrapEOFStream(str);
}
return str;
}
//------------------------------------------------------------------------
// OutStream
//------------------------------------------------------------------------
OutStream::OutStream() { }
OutStream::~OutStream() { }
//------------------------------------------------------------------------
// 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<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() { }
//------------------------------------------------------------------------
// 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), savePos(0), saved(false)
{
}
BaseSeekInputStream::~BaseSeekInputStream() { }
void BaseSeekInputStream::reset()
{
savePos = currentPos();
setCurrentPos(start);
saved = true;
bufPtr = bufEnd = buf;
bufPos = start;
}
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;
if (bufPtr >= bufEnd) {
return false;
}
return true;
}
int BaseSeekInputStream::getChars(int nChars, unsigned char *buffer)
{
int n, m;
n = 0;
while (n < nChars) {
if (bufPtr >= bufEnd) {
if (!fillBuf()) {
break;
}
}
m = (int)(bufEnd - bufPtr);
if (m > nChars - n) {
m = nChars - n;
}
memcpy(buffer + n, bufPtr, m);
bufPtr += m;
n += m;
}
return n;
}
//------------------------------------------------------------------------
// FilterStream
//------------------------------------------------------------------------
FilterStream::FilterStream(Stream *strA)
{
str = strA;
}
FilterStream::~FilterStream() { }
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 = (unsigned char *)gmallocn_checkoverflow(inputLineSize, sizeof(char));
if (nBits == 8) {
imgLine = (unsigned char *)inputLine;
} else {
if (nBits == 1) {
imgLineSize = (nVals + 7) & ~7;
} else {
imgLineSize = nVals;
}
if (nComps <= 0 || width > INT_MAX / nComps) {
imgLineSize = -1;
}
imgLine = (unsigned char *)gmallocn_checkoverflow(imgLineSize, sizeof(unsigned char));
}
imgIdx = nVals;
}
ImageStream::~ImageStream()
{
if (imgLine != (unsigned char *)inputLine) {
gfree(imgLine);
}
gfree(inputLine);
}
void ImageStream::reset()
{
str->reset();
}
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] = (unsigned char)((c >> 7) & 1);
imgLine[i + 1] = (unsigned char)((c >> 6) & 1);
imgLine[i + 2] = (unsigned char)((c >> 5) & 1);
imgLine[i + 3] = (unsigned char)((c >> 4) & 1);
imgLine[i + 4] = (unsigned char)((c >> 3) & 1);
imgLine[i + 5] = (unsigned char)((c >> 2) & 1);
imgLine[i + 6] = (unsigned char)((c >> 1) & 1);
imgLine[i + 7] = (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] = (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 = (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] + (unsigned char)c;
break;
case 12: // PNG up
predLine[i] = predLine[i] + (unsigned char)c;
break;
case 13: // PNG average
predLine[i] = ((predLine[i - pixBytes] + predLine[i]) >> 1) + (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 + (unsigned char)c;
} else if (pb <= pc) {
predLine[i] = up + (unsigned char)c;
} else {
predLine[i] = upLeft + (unsigned char)c;
}
break;
case 10: // PNG none
default: // no predictor or TIFF predictor
predLine[i] = (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] = (unsigned char)((upLeftBuf[kk] + (inBuf >> (inBits - nBits))) & bitMask);
inBits -= nBits;
outBuf = (outBuf << nBits) | upLeftBuf[kk];
outBits += nBits;
if (outBits >= 8) {
predLine[k++] = (unsigned char)(outBuf >> (outBits - 8));
outBits -= 8;
}
}
}
if (outBits > 0) {
predLine[k++] = (unsigned char)((outBuf << (8 - outBits)) + (inBuf & ((1 << (8 - outBits)) - 1)));
}
}
}
// reset 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();
}
BaseStream *FileStream::copy()
{
return new FileStream(file, start, limited, length, dict.copy());
}
Stream *FileStream::makeSubStream(Goffset startA, bool limitedA, Goffset lengthA, Object &&dictA)
{
return new FileStream(file, startA, limitedA, lengthA, std::move(dictA));
}
void FileStream::reset()
{
savePos = offset;
offset = start;
saved = true;
bufPtr = bufEnd = buf;
bufPos = start;
}
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;
if (bufPtr >= bufEnd) {
return false;
}
return true;
}
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(CachedFile *ccA, Goffset startA, bool limitedA, Goffset lengthA, Object &&dictA) : BaseStream(std::move(dictA), lengthA)
{
cc = ccA;
start = startA;
limited = limitedA;
length = lengthA;
bufPtr = bufEnd = buf;
bufPos = start;
savePos = 0;
saved = false;
}
CachedFileStream::~CachedFileStream()
{
close();
cc->decRefCnt();
}
BaseStream *CachedFileStream::copy()
{
cc->incRefCnt();
return new CachedFileStream(cc, start, limited, length, dict.copy());
}
Stream *CachedFileStream::makeSubStream(Goffset startA, bool limitedA, Goffset lengthA, Object &&dictA)
{
cc->incRefCnt();
return new CachedFileStream(cc, startA, limitedA, lengthA, std::move(dictA));
}
void CachedFileStream::reset()
{
savePos = (unsigned int)cc->tell();
cc->seek(start, SEEK_SET);
saved = true;
bufPtr = bufEnd = buf;
bufPos = start;
}
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;
if (bufPtr >= bufEnd) {
return false;
}
return true;
}
void CachedFileStream::setPos(Goffset pos, int dir)
{
unsigned int size;
if (dir >= 0) {
cc->seek(pos, SEEK_SET);
bufPos = pos;
} else {
cc->seek(0, SEEK_END);
size = (unsigned int)cc->tell();
if (pos > size) {
pos = (unsigned int)size;
}
cc->seek(-(int)pos, SEEK_END);
bufPos = (unsigned int)cc->tell();
}
bufPtr = bufEnd = buf;
}
void CachedFileStream::moveStart(Goffset delta)
{
start += delta;
bufPtr = bufEnd = buf;
bufPos = start;
}
MemStream::~MemStream() = default;
AutoFreeMemStream::~AutoFreeMemStream()
{
gfree(buf);
}
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)
{
str = strA;
limited = limitedA;
length = lengthA;
reusable = reusableA;
record = false;
replay = false;
start = str->getPos();
if (reusable) {
bufData = (unsigned char *)gmalloc(16384);
bufMax = 16384;
bufLen = 0;
record = true;
}
}
EmbedStream::~EmbedStream()
{
if (reusable) {
gfree(bufData);
}
}
void EmbedStream::reset()
{
if (str->getPos() != start) {
str->reset();
// Might be a FilterStream that does not support str->setPos(start)
while (str->getPos() < start) {
if (str->getChar() == EOF) {
break;
}
}
if (str->getPos() != start) {
error(errInternal, -1, "Failed to reset EmbedStream");
}
}
record = false;
replay = false;
bufPos = 0;
}
BaseStream *EmbedStream::copy()
{
error(errInternal, -1, "Called copy() on EmbedStream");
return nullptr;
}
Stream *EmbedStream::makeSubStream(Goffset startA, bool limitedA, Goffset lengthA, Object &&dictA)
{
error(errInternal, -1, "Called makeSubStream() on EmbedStream");
return nullptr;
}
void EmbedStream::rewind()
{
record = false;
replay = true;
bufPos = 0;
}
void EmbedStream::restore()
{
replay = false;
}
Goffset EmbedStream::getPos()
{
if (replay) {
return bufPos;
} else {
return str->getPos();
}
}
int EmbedStream::getChar()
{
if (replay) {
if (bufPos < bufLen) {
return bufData[bufPos++];
} else {
return EOF;
}
} else {
if (limited && !length) {
return EOF;
}
int c = str->getChar();
--length;
if (record) {
bufData[bufLen] = c;
bufLen++;
if (bufLen >= bufMax) {
bufMax *= 2;
bufData = (unsigned char *)grealloc(bufData, bufMax);
}
}
return c;
}
}
int EmbedStream::lookChar()
{
if (replay) {
if (bufPos < bufLen) {
return bufData[bufPos];
} else {
return EOF;
}
} else {
if (limited && !length) {
return EOF;
}
return str->lookChar();
}
}
int EmbedStream::getChars(int nChars, unsigned char *buffer)
{
int len;
if (nChars <= 0) {
return 0;
}
if (replay) {
if (bufPos >= bufLen) {
return EOF;
}
len = bufLen - bufPos;
if (nChars > len) {
nChars = len;
}
memcpy(buffer, bufData, nChars);
return len;
} else {
if (limited && length < nChars) {
nChars = length;
}
len = str->doGetChars(nChars, buffer);
if (record) {
if (bufLen + len >= bufMax) {
while (bufLen + len >= bufMax) {
bufMax *= 2;
}
bufData = (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(Stream *strA) : FilterStream(strA)
{
buf = EOF;
eof = false;
}
ASCIIHexStream::~ASCIIHexStream()
{
delete str;
}
void ASCIIHexStream::reset()
{
str->reset();
buf = EOF;
eof = false;
}
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;
}
GooString *ASCIIHexStream::getPSFilter(int psLevel, const char *indent)
{
GooString *s;
if (psLevel < 2) {
return nullptr;
}
if (!(s = str->getPSFilter(psLevel, indent))) {
return nullptr;
}
s->append(indent)->append("/ASCIIHexDecode filter\n");
return s;
}
bool ASCIIHexStream::isBinary(bool last) const
{
return str->isBinary(false);
}
//------------------------------------------------------------------------
// ASCII85Stream
//------------------------------------------------------------------------
ASCII85Stream::ASCII85Stream(Stream *strA) : FilterStream(strA)
{
index = n = 0;
eof = false;
}
ASCII85Stream::~ASCII85Stream()
{
delete str;
}
void ASCII85Stream::reset()
{
str->reset();
index = n = 0;
eof = false;
}
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;
} else 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] = (int)(t & 0xff);
t >>= 8;
}
}
}
return b[index];
}
GooString *ASCII85Stream::getPSFilter(int psLevel, const char *indent)
{
GooString *s;
if (psLevel < 2) {
return nullptr;
}
if (!(s = str->getPSFilter(psLevel, indent))) {
return nullptr;
}
s->append(indent)->append("/ASCII85Decode filter\n");
return s;
}
bool ASCII85Stream::isBinary(bool last) const
{
return str->isBinary(false);
}
//------------------------------------------------------------------------
// LZWStream
//------------------------------------------------------------------------
LZWStream::LZWStream(Stream *strA, int predictor, int columns, int colors, int bits, int earlyA) : FilterStream(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()
{
if (pred) {
delete pred;
}
delete str;
}
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;
}
void LZWStream::reset()
{
str->reset();
eof = false;
inputBits = 0;
clearTable();
}
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;
// reset 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;
}
GooString *LZWStream::getPSFilter(int psLevel, const char *indent)
{
GooString *s;
if (psLevel < 2 || pred) {
return nullptr;
}
if (!(s = str->getPSFilter(psLevel, indent))) {
return nullptr;
}
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(Stream *strA) : FilterStream(strA)
{
bufPtr = bufEnd = buf;
eof = false;
}
RunLengthStream::~RunLengthStream()
{
delete str;
}
void RunLengthStream::reset()
{
str->reset();
bufPtr = bufEnd = buf;
eof = false;
}
int RunLengthStream::getChars(int nChars, unsigned char *buffer)
{
int n, m;
n = 0;
while (n < nChars) {
if (bufPtr >= bufEnd) {
if (!fillBuf()) {
break;
}
}
m = (int)(bufEnd - bufPtr);
if (m > nChars - n) {
m = nChars - n;
}
memcpy(buffer + n, bufPtr, m);
bufPtr += m;
n += m;
}
return n;
}
GooString *RunLengthStream::getPSFilter(int psLevel, const char *indent)
{
GooString *s;
if (psLevel < 2) {
return nullptr;
}
if (!(s = str->getPSFilter(psLevel, indent))) {
return nullptr;
}
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] = (char)str->getChar();
}
} else {
n = 0x101 - c;
c = str->getChar();
for (i = 0; i < n; ++i) {
buf[i] = (char)c;
}
}
bufPtr = buf;
bufEnd = buf + n;
return true;
}
//------------------------------------------------------------------------
// CCITTFaxStream
//------------------------------------------------------------------------
CCITTFaxStream::CCITTFaxStream(Stream *strA, int encodingA, bool endOfLineA, bool byteAlignA, int columnsA, int rowsA, bool endOfBlockA, bool blackA, int damagedRowsBeforeErrorA) : FilterStream(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 = (int *)gmallocn_checkoverflow(columns + 1, sizeof(int));
refLine = (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()
{
delete str;
gfree(refLine);
gfree(codingLine);
}
void CCITTFaxStream::ccittReset(bool unfiltered)
{
if (unfiltered) {
str->unfilteredReset();
} else {
str->reset();
}
row = 0;
nextLine2D = encoding < 0;
inputBits = 0;
a0i = 0;
outputBits = 0;
buf = EOF;
}
void CCITTFaxStream::unfilteredReset()
{
ccittReset(true);
}
void CCITTFaxStream::reset()
{
int code1;
ccittReset(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);
}
}
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));
}
GooString *CCITTFaxStream::getPSFilter(int psLevel, const char *indent)
{
GooString *s;
char s1[50];
if (psLevel < 2) {
return nullptr;
}
if (!(s = str->getPSFilter(psLevel, indent))) {
return nullptr;
}
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);
}
#ifndef ENABLE_LIBJPEG
//------------------------------------------------------------------------
// DCTStream
//------------------------------------------------------------------------
// IDCT constants (20.12 fixed point format)
# define dctCos1 4017 // cos(pi/16)
# define dctSin1 799 // sin(pi/16)
# define dctCos3 3406 // cos(3*pi/16)
# define dctSin3 2276 // sin(3*pi/16)
# define dctCos6 1567 // cos(6*pi/16)
# define dctSin6 3784 // sin(6*pi/16)
# define dctSqrt2 5793 // sqrt(2)
# define dctSqrt1d2 2896 // sqrt(2) / 2
// color conversion parameters (16.16 fixed point format)
# define dctCrToR 91881 // 1.4020
# define dctCbToG -22553 // -0.3441363
# define dctCrToG -46802 // -0.71413636
# define dctCbToB 116130 // 1.772
// clip [-256,511] --> [0,255]
# define dctClipOffset 256
# define dctClipLength 768
static unsigned char dctClip[dctClipLength];
static int dctClipInit = 0;
// zig zag decode map
static const int dctZigZag[64] = { 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28,
35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63 };
DCTStream::DCTStream(Stream *strA, int colorXformA, Dict *dict, int recursion) : FilterStream(strA)
{
int i, j;
colorXform = colorXformA;
progressive = interleaved = false;
width = height = 0;
mcuWidth = mcuHeight = 0;
numComps = 0;
comp = 0;
x = y = dy = 0;
for (i = 0; i < 4; ++i) {
for (j = 0; j < 32; ++j) {
rowBuf[i][j] = nullptr;
}
frameBuf[i] = nullptr;
}
if (!dctClipInit) {
for (i = -256; i < 0; ++i)
dctClip[dctClipOffset + i] = 0;
for (i = 0; i < 256; ++i)
dctClip[dctClipOffset + i] = i;
for (i = 256; i < 512; ++i)
dctClip[dctClipOffset + i] = 255;
dctClipInit = 1;
}
}
DCTStream::~DCTStream()
{
close();
delete str;
}
void DCTStream::dctReset(bool unfiltered)
{
if (unfiltered)
str->unfilteredReset();
else
str->reset();
progressive = interleaved = false;
width = height = 0;
numComps = 0;
numQuantTables = 0;
numDCHuffTables = 0;
numACHuffTables = 0;
gotJFIFMarker = false;
gotAdobeMarker = false;
restartInterval = 0;
}
void DCTStream::unfilteredReset()
{
dctReset(true);
}
void DCTStream::reset()
{
int i, j;
dctReset(false);
if (!readHeader()) {
y = height;
return;
}
// compute MCU size
if (numComps == 1) {
compInfo[0].hSample = compInfo[0].vSample = 1;
}
mcuWidth = compInfo[0].hSample;
mcuHeight = compInfo[0].vSample;
for (i = 1; i < numComps; ++i) {
if (compInfo[i].hSample > mcuWidth) {
mcuWidth = compInfo[i].hSample;
}
if (compInfo[i].vSample > mcuHeight) {
mcuHeight = compInfo[i].vSample;
}
}
mcuWidth *= 8;
mcuHeight *= 8;
// figure out color transform
if (colorXform == -1) {
if (numComps == 3) {
if (gotJFIFMarker) {
colorXform = 1;
} else if (compInfo[0].id == 82 && compInfo[1].id == 71 && compInfo[2].id == 66) { // ASCII "RGB"
colorXform = 0;
} else {
colorXform = 1;
}
} else {
colorXform = 0;
}
}
if (progressive || !interleaved) {
// allocate a buffer for the whole image
bufWidth = ((width + mcuWidth - 1) / mcuWidth) * mcuWidth;
bufHeight = ((height + mcuHeight - 1) / mcuHeight) * mcuHeight;
if (bufWidth <= 0 || bufHeight <= 0 || bufWidth > INT_MAX / bufWidth / (int)sizeof(int)) {
error(errSyntaxError, getPos(), "Invalid image size in DCT stream");
y = height;
return;
}
for (i = 0; i < numComps; ++i) {
frameBuf[i] = (int *)gmallocn(bufWidth * bufHeight, sizeof(int));
memset(frameBuf[i], 0, bufWidth * bufHeight * sizeof(int));
}
// read the image data
do {
restartMarker = 0xd0;
restart();
readScan();
} while (readHeader());
// decode
decodeImage();
// initialize counters
comp = 0;
x = 0;
y = 0;
} else {
// allocate a buffer for one row of MCUs
bufWidth = ((width + mcuWidth - 1) / mcuWidth) * mcuWidth;
for (i = 0; i < numComps; ++i) {
for (j = 0; j < mcuHeight; ++j) {
rowBuf[i][j] = (unsigned char *)gmallocn(bufWidth, sizeof(unsigned char));
}
}
// initialize counters
comp = 0;
x = 0;
y = 0;
dy = mcuHeight;
restartMarker = 0xd0;
restart();
}
}
void DCTStream::close()
{
int i, j;
for (i = 0; i < 4; ++i) {
for (j = 0; j < 32; ++j) {
gfree(rowBuf[i][j]);
rowBuf[i][j] = nullptr;
}
gfree(frameBuf[i]);
frameBuf[i] = nullptr;
}
FilterStream::close();
}
int DCTStream::getChar()
{
int c;
if (y >= height) {
return EOF;
}
if (progressive || !interleaved) {
c = frameBuf[comp][y * bufWidth + x];
if (++comp == numComps) {
comp = 0;
if (++x == width) {
x = 0;
++y;
}
}
} else {
if (dy >= mcuHeight) {
if (!readMCURow()) {
y = height;
return EOF;
}
comp = 0;
x = 0;
dy = 0;
}
c = rowBuf[comp][dy][x];
if (++comp == numComps) {
comp = 0;
if (++x == width) {
x = 0;
++y;
++dy;
if (y == height) {
readTrailer();
}
}
}
}
return c;
}
int DCTStream::lookChar()
{
if (y >= height) {
return EOF;
}
if (progressive || !interleaved) {
return frameBuf[comp][y * bufWidth + x];
} else {
if (dy >= mcuHeight) {
if (!readMCURow()) {