mirror of
https://github.com/qpdf/qpdf.git
synced 2024-11-18 02:25:08 +00:00
9a0b88bf77
git-svn-id: svn+q:///qpdf/trunk@599 71b93d88-0707-0410-a8cf-f5a4172ac649
310 lines
6.8 KiB
C++
310 lines
6.8 KiB
C++
|
|
#include <qpdf/QPDF_Stream.hh>
|
|
|
|
#include <qpdf/QEXC.hh>
|
|
#include <qpdf/QUtil.hh>
|
|
#include <qpdf/Pipeline.hh>
|
|
#include <qpdf/Pl_Flate.hh>
|
|
#include <qpdf/Pl_PNGFilter.hh>
|
|
#include <qpdf/Pl_RC4.hh>
|
|
#include <qpdf/Pl_Buffer.hh>
|
|
#include <qpdf/Pl_ASCII85Decoder.hh>
|
|
#include <qpdf/Pl_ASCIIHexDecoder.hh>
|
|
#include <qpdf/Pl_LZWDecoder.hh>
|
|
|
|
#include <qpdf/QTC.hh>
|
|
#include <qpdf/QPDF.hh>
|
|
#include <qpdf/QPDFExc.hh>
|
|
#include <qpdf/Pl_QPDFTokenizer.hh>
|
|
|
|
QPDF_Stream::QPDF_Stream(QPDF* qpdf, int objid, int generation,
|
|
QPDFObjectHandle stream_dict,
|
|
off_t offset, int length) :
|
|
qpdf(qpdf),
|
|
objid(objid),
|
|
generation(generation),
|
|
stream_dict(stream_dict),
|
|
offset(offset),
|
|
length(length)
|
|
{
|
|
if (! stream_dict.isDictionary())
|
|
{
|
|
throw QEXC::Internal("stream object instantiated with non-dictionary "
|
|
"object for dictionary");
|
|
}
|
|
}
|
|
|
|
QPDF_Stream::~QPDF_Stream()
|
|
{
|
|
}
|
|
|
|
std::string
|
|
QPDF_Stream::unparse()
|
|
{
|
|
// Unparse stream objects as indirect references
|
|
return QUtil::int_to_string(this->objid) + " " +
|
|
QUtil::int_to_string(this->generation) + " R";
|
|
}
|
|
|
|
QPDFObjectHandle
|
|
QPDF_Stream::getDict() const
|
|
{
|
|
return this->stream_dict;
|
|
}
|
|
|
|
PointerHolder<Buffer>
|
|
QPDF_Stream::getStreamData()
|
|
{
|
|
Pl_Buffer buf("stream data buffer");
|
|
if (! pipeStreamData(&buf, true, false, false))
|
|
{
|
|
throw QPDFExc("getStreamData called on unfilterable stream");
|
|
}
|
|
return buf.getBuffer();
|
|
}
|
|
|
|
bool
|
|
QPDF_Stream::filterable(std::vector<std::string>& filters,
|
|
int& predictor, int& columns,
|
|
bool& early_code_change)
|
|
{
|
|
// Initialize values to their defaults as per the PDF spec
|
|
predictor = 1;
|
|
columns = 0;
|
|
early_code_change = true;
|
|
|
|
bool filterable = true;
|
|
|
|
// See if we can support any decode parameters that are specified.
|
|
|
|
QPDFObjectHandle decode_obj =
|
|
this->stream_dict.getKey("/DecodeParms");
|
|
if (decode_obj.isNull())
|
|
{
|
|
// no problem
|
|
}
|
|
else if (decode_obj.isDictionary())
|
|
{
|
|
std::set<std::string> keys = decode_obj.getKeys();
|
|
for (std::set<std::string>::iterator iter = keys.begin();
|
|
iter != keys.end(); ++iter)
|
|
{
|
|
std::string const& key = *iter;
|
|
if (key == "/Predictor")
|
|
{
|
|
QPDFObjectHandle predictor_obj = decode_obj.getKey(key);
|
|
if (predictor_obj.isInteger())
|
|
{
|
|
predictor = predictor_obj.getIntValue();
|
|
if (! ((predictor == 1) || (predictor == 12)))
|
|
{
|
|
filterable = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
filterable = false;
|
|
}
|
|
}
|
|
else if (key == "/EarlyChange")
|
|
{
|
|
QPDFObjectHandle earlychange_obj = decode_obj.getKey(key);
|
|
if (earlychange_obj.isInteger())
|
|
{
|
|
int earlychange = earlychange_obj.getIntValue();
|
|
early_code_change = (earlychange == 1);
|
|
if (! ((earlychange == 0) || (earlychange == 1)))
|
|
{
|
|
filterable = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
filterable = false;
|
|
}
|
|
}
|
|
else if (key == "/Columns")
|
|
{
|
|
QPDFObjectHandle columns_obj = decode_obj.getKey(key);
|
|
if (columns_obj.isInteger())
|
|
{
|
|
columns = columns_obj.getIntValue();
|
|
}
|
|
else
|
|
{
|
|
filterable = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
filterable = false;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
throw QPDFExc(qpdf->getFilename(), this->offset,
|
|
"invalid decode parameters object type for this stream");
|
|
}
|
|
|
|
if ((predictor > 1) && (columns == 0))
|
|
{
|
|
// invalid
|
|
filterable = false;
|
|
}
|
|
|
|
if (! filterable)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Check filters
|
|
|
|
QPDFObjectHandle filter_obj = this->stream_dict.getKey("/Filter");
|
|
bool filters_okay = true;
|
|
|
|
if (filter_obj.isNull())
|
|
{
|
|
// No filters
|
|
}
|
|
else if (filter_obj.isName())
|
|
{
|
|
// One filter
|
|
filters.push_back(filter_obj.getName());
|
|
}
|
|
else if (filter_obj.isArray())
|
|
{
|
|
// Potentially multiple filters
|
|
int n = filter_obj.getArrayNItems();
|
|
for (int i = 0; i < n; ++i)
|
|
{
|
|
QPDFObjectHandle item = filter_obj.getArrayItem(i);
|
|
if (item.isName())
|
|
{
|
|
filters.push_back(item.getName());
|
|
}
|
|
else
|
|
{
|
|
filters_okay = false;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
filters_okay = false;
|
|
}
|
|
|
|
if (! filters_okay)
|
|
{
|
|
QTC::TC("qpdf", "QPDF_Stream invalid filter");
|
|
throw QPDFExc(qpdf->getFilename(), this->offset,
|
|
"invalid filter object type for this stream");
|
|
}
|
|
|
|
// `filters' now contains a list of filters to be applied in
|
|
// order. See which ones we can support.
|
|
|
|
for (std::vector<std::string>::iterator iter = filters.begin();
|
|
iter != filters.end(); ++iter)
|
|
{
|
|
std::string const& filter = *iter;
|
|
if (! ((filter == "/FlateDecode") ||
|
|
(filter == "/LZWDecode") ||
|
|
(filter == "/ASCII85Decode") ||
|
|
(filter == "/ASCIIHexDecode")))
|
|
{
|
|
filterable = false;
|
|
}
|
|
}
|
|
|
|
return filterable;
|
|
}
|
|
|
|
bool
|
|
QPDF_Stream::pipeStreamData(Pipeline* pipeline, bool filter,
|
|
bool normalize, bool compress)
|
|
{
|
|
std::vector<std::string> filters;
|
|
int predictor = 1;
|
|
int columns = 0;
|
|
bool early_code_change = true;
|
|
if (filter)
|
|
{
|
|
filter = filterable(filters, predictor, columns, early_code_change);
|
|
}
|
|
|
|
if (pipeline == 0)
|
|
{
|
|
QTC::TC("qpdf", "QPDF_Stream pipeStreamData with null pipeline");
|
|
return filter;
|
|
}
|
|
|
|
// Construct the pipeline in reverse order. Force pipelines we
|
|
// create to be deleted when this function finishes.
|
|
std::vector<PointerHolder<Pipeline> > to_delete;
|
|
|
|
if (filter)
|
|
{
|
|
if (compress)
|
|
{
|
|
pipeline = new Pl_Flate("compress object stream", pipeline,
|
|
Pl_Flate::a_deflate);
|
|
to_delete.push_back(pipeline);
|
|
}
|
|
|
|
if (normalize)
|
|
{
|
|
pipeline = new Pl_QPDFTokenizer("normalizer", pipeline);
|
|
to_delete.push_back(pipeline);
|
|
}
|
|
|
|
for (std::vector<std::string>::reverse_iterator iter = filters.rbegin();
|
|
iter != filters.rend(); ++iter)
|
|
{
|
|
std::string const& filter = *iter;
|
|
if (filter == "/FlateDecode")
|
|
{
|
|
if (predictor == 12)
|
|
{
|
|
QTC::TC("qpdf", "QPDF_Stream PNG filter");
|
|
pipeline = new Pl_PNGFilter(
|
|
"png decode", pipeline, Pl_PNGFilter::a_decode,
|
|
columns, 0 /* not used */);
|
|
to_delete.push_back(pipeline);
|
|
}
|
|
|
|
pipeline = new Pl_Flate("stream inflate",
|
|
pipeline, Pl_Flate::a_inflate);
|
|
to_delete.push_back(pipeline);
|
|
}
|
|
else if (filter == "/ASCII85Decode")
|
|
{
|
|
pipeline = new Pl_ASCII85Decoder("ascii85 decode", pipeline);
|
|
to_delete.push_back(pipeline);
|
|
}
|
|
else if (filter == "/ASCIIHexDecode")
|
|
{
|
|
pipeline = new Pl_ASCIIHexDecoder("asciiHex decode", pipeline);
|
|
to_delete.push_back(pipeline);
|
|
}
|
|
else if (filter == "/LZWDecode")
|
|
{
|
|
pipeline = new Pl_LZWDecoder("lzw decode", pipeline,
|
|
early_code_change);
|
|
to_delete.push_back(pipeline);
|
|
}
|
|
else
|
|
{
|
|
throw QEXC::Internal("QPDFStream: unknown filter "
|
|
"encountered after check");
|
|
}
|
|
}
|
|
}
|
|
|
|
QPDF::Pipe::pipeStreamData(this->qpdf, this->objid, this->generation,
|
|
this->offset, this->length,
|
|
this->stream_dict, pipeline);
|
|
|
|
return filter;
|
|
}
|