mirror of
https://github.com/qpdf/qpdf.git
synced 2024-12-22 10:58:58 +00:00
Implement user-provided stream filters
Refactor QPDF_Stream to use stream filter classes to handle supported stream filters as well.
This commit is contained in:
parent
1fb26f08ad
commit
39bfa01307
10
ChangeLog
10
ChangeLog
@ -1,3 +1,13 @@
|
||||
2020-12-23 Jay Berkenbilt <ejb@ql.org>
|
||||
|
||||
* Allow library users to provide their own decoders for stream
|
||||
filters by deriving classes from QPDFStreamFilter and registering
|
||||
them using QPDF::registerStreamFilter. Registered stream filters
|
||||
provide code to validate and interpret /DecodeParms for a specific
|
||||
/Filter and also to provide a pipeline that will decode. Note that
|
||||
it is possible to encode to a filter type that is not supported
|
||||
even without this feature.
|
||||
|
||||
2020-12-22 Jay Berkenbilt <ejb@ql.org>
|
||||
|
||||
* Add QPDFObjectHandle::makeDirect(bool allow_streams) -- if
|
||||
|
7
TODO
7
TODO
@ -317,13 +317,6 @@ I find it useful to make reference to them in this list
|
||||
is exercised elsewhere in qpdf's test suite, so this is not that
|
||||
pressing.
|
||||
|
||||
* Support user-pluggable stream filters. This would enable external
|
||||
code to provide interpretation for filters that are missing from
|
||||
qpdf. Make it possible for user-provided filters to override
|
||||
built-in filters. Make sure that the pluggable filters can be
|
||||
prioritized so that we can poll all registered filters to see
|
||||
whether they are capable of filtering a particular stream.
|
||||
|
||||
* If possible, consider adding CCITT3, CCITT4, or any other easy
|
||||
filters. For some reference code that we probably can't use but may
|
||||
be handy anyway, see
|
||||
|
@ -31,6 +31,8 @@
|
||||
#include <list>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
#include <qpdf/QIntC.hh>
|
||||
#include <qpdf/QPDFExc.hh>
|
||||
@ -39,6 +41,7 @@
|
||||
#include <qpdf/QPDFXRefEntry.hh>
|
||||
#include <qpdf/QPDFObjectHandle.hh>
|
||||
#include <qpdf/QPDFTokenizer.hh>
|
||||
#include <qpdf/QPDFStreamFilter.hh>
|
||||
#include <qpdf/Buffer.hh>
|
||||
#include <qpdf/InputSource.hh>
|
||||
|
||||
@ -132,6 +135,20 @@ class QPDF
|
||||
QPDF_DLL
|
||||
void emptyPDF();
|
||||
|
||||
// From 10.1: register a new filter implementation for a specific
|
||||
// stream filter. You can add your own implementations for new
|
||||
// filter types or override existing ones provided by the library.
|
||||
// Registered stream filters are used for decoding only as you can
|
||||
// override encoding with stream data providers. For example, you
|
||||
// could use this method to support for one of the other filter
|
||||
// types by using additional third-party libraries that qpdf does
|
||||
// not presently use. The standard filters are implemented using
|
||||
// QPDFStreamFilter classes.
|
||||
QPDF_DLL
|
||||
static void registerStreamFilter(
|
||||
std::string const& filter_name,
|
||||
std::function<std::shared_ptr<QPDFStreamFilter> ()> factory);
|
||||
|
||||
// Parameter settings
|
||||
|
||||
// By default, warning messages are issued to std::cerr and output
|
||||
|
78
include/qpdf/QPDFStreamFilter.hh
Normal file
78
include/qpdf/QPDFStreamFilter.hh
Normal file
@ -0,0 +1,78 @@
|
||||
// Copyright (c) 2005-2020 Jay Berkenbilt
|
||||
//
|
||||
// This file is part of qpdf.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// Versions of qpdf prior to version 7 were released under the terms
|
||||
// of version 2.0 of the Artistic License. At your option, you may
|
||||
// continue to consider qpdf to be licensed under those terms. Please
|
||||
// see the manual for additional information.
|
||||
|
||||
#ifndef QPDFSTREAMFILTER_HH
|
||||
#define QPDFSTREAMFILTER_HH
|
||||
|
||||
#include <qpdf/DLL.h>
|
||||
#include <qpdf/QPDFObjectHandle.hh>
|
||||
#include <qpdf/Pipeline.hh>
|
||||
|
||||
class QPDF_DLL_CLASS QPDFStreamFilter
|
||||
{
|
||||
public:
|
||||
QPDF_DLL
|
||||
QPDFStreamFilter() = default;
|
||||
|
||||
QPDF_DLL
|
||||
virtual ~QPDFStreamFilter() = default;
|
||||
|
||||
// A QPDFStreamFilter class must implement, at a minimum,
|
||||
// setDecodeParms() and getDecodePipeline(). QPDF will always call
|
||||
// setDecodeParms() before calling getDecodePipeline(). It is
|
||||
// expected that you will store any needed information from
|
||||
// decode_parms (or the decode_parms object iself) in your
|
||||
// instance so that it can be used to construct the decode
|
||||
// pipeline.
|
||||
|
||||
// Return a boolean indicating whether your filter can proceed
|
||||
// with the given /DecodeParms. The default implementation accepts
|
||||
// a null object and rejects everything else.
|
||||
QPDF_DLL
|
||||
virtual bool setDecodeParms(QPDFObjectHandle decode_parms);
|
||||
|
||||
// Return a pipeline that will decode data encoded with your
|
||||
// filter. Your implementation must ensure that the pipeline is
|
||||
// deleted when the instance of your class is destroyed.
|
||||
QPDF_DLL
|
||||
virtual Pipeline* getDecodePipeline(Pipeline* next) = 0;
|
||||
|
||||
// If your filter implements "specialized" compression or lossy
|
||||
// compression, override one or both of these methods. The default
|
||||
// implementations return false. See comments in QPDFWriter for
|
||||
// details. QPDF defines specialized compression as non-lossy
|
||||
// compression not intended for general-purpose data. qpdf, by
|
||||
// default, doesn't mess with streams that are compressed with
|
||||
// specialized compression, the idea being that the decision to
|
||||
// use that compression scheme would fall outside of what
|
||||
// QPDFWriter would know anything about, so any attempt to decode
|
||||
// and re-encode would probably be undesirable.
|
||||
QPDF_DLL
|
||||
virtual bool isSpecializedCompression();
|
||||
QPDF_DLL
|
||||
virtual bool isLossyCompression();
|
||||
|
||||
private:
|
||||
QPDFStreamFilter(QPDFStreamFilter const&) = delete;
|
||||
QPDFStreamFilter& operator=(QPDFStreamFilter const&) = delete;
|
||||
};
|
||||
|
||||
#endif // QPDFSTREAMFILTER_HH
|
@ -269,6 +269,14 @@ QPDF::emptyPDF()
|
||||
processMemoryFile("empty PDF", EMPTY_PDF, strlen(EMPTY_PDF));
|
||||
}
|
||||
|
||||
void
|
||||
QPDF::registerStreamFilter(
|
||||
std::string const& filter_name,
|
||||
std::function<std::shared_ptr<QPDFStreamFilter> ()> factory)
|
||||
{
|
||||
QPDF_Stream::registerStreamFilter(filter_name, factory);
|
||||
}
|
||||
|
||||
void
|
||||
QPDF::setIgnoreXRefStreams(bool val)
|
||||
{
|
||||
|
19
libqpdf/QPDFStreamFilter.cc
Normal file
19
libqpdf/QPDFStreamFilter.cc
Normal file
@ -0,0 +1,19 @@
|
||||
#include <qpdf/QPDFStreamFilter.hh>
|
||||
|
||||
bool
|
||||
QPDFStreamFilter::setDecodeParms(QPDFObjectHandle decode_parms)
|
||||
{
|
||||
return decode_parms.isNull();
|
||||
}
|
||||
|
||||
bool
|
||||
QPDFStreamFilter::isSpecializedCompression()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
QPDFStreamFilter::isLossyCompression()
|
||||
{
|
||||
return false;
|
||||
}
|
@ -3,15 +3,7 @@
|
||||
#include <qpdf/QUtil.hh>
|
||||
#include <qpdf/Pipeline.hh>
|
||||
#include <qpdf/Pl_Flate.hh>
|
||||
#include <qpdf/Pl_PNGFilter.hh>
|
||||
#include <qpdf/Pl_TIFFPredictor.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/Pl_RunLength.hh>
|
||||
#include <qpdf/Pl_DCT.hh>
|
||||
#include <qpdf/Pl_Count.hh>
|
||||
#include <qpdf/ContentNormalizer.hh>
|
||||
#include <qpdf/QTC.hh>
|
||||
@ -19,10 +11,78 @@
|
||||
#include <qpdf/QPDFExc.hh>
|
||||
#include <qpdf/Pl_QPDFTokenizer.hh>
|
||||
#include <qpdf/QIntC.hh>
|
||||
#include <qpdf/SF_FlateLzwDecode.hh>
|
||||
#include <qpdf/SF_DCTDecode.hh>
|
||||
#include <qpdf/SF_RunLengthDecode.hh>
|
||||
#include <qpdf/SF_ASCII85Decode.hh>
|
||||
#include <qpdf/SF_ASCIIHexDecode.hh>
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
std::map<std::string, std::string> QPDF_Stream::filter_abbreviations;
|
||||
class SF_Crypt: public QPDFStreamFilter
|
||||
{
|
||||
public:
|
||||
SF_Crypt() = default;
|
||||
virtual ~SF_Crypt() = default;
|
||||
|
||||
virtual bool setDecodeParms(QPDFObjectHandle decode_parms)
|
||||
{
|
||||
if (decode_parms.isNull())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
bool filterable = true;
|
||||
for (auto const& key: decode_parms.getKeys())
|
||||
{
|
||||
if (((key == "/Type") || (key == "/Name")) &&
|
||||
(decode_parms.getKey("/Type").isNull() ||
|
||||
(decode_parms.getKey("/Type").isName() &&
|
||||
(decode_parms.getKey("/Type").getName() ==
|
||||
"/CryptFilterDecodeParms"))))
|
||||
{
|
||||
// we handle this in decryptStream
|
||||
}
|
||||
else
|
||||
{
|
||||
filterable = false;
|
||||
}
|
||||
}
|
||||
return filterable;
|
||||
}
|
||||
|
||||
virtual Pipeline* getDecodePipeline(Pipeline*)
|
||||
{
|
||||
// Not used -- handled by pipeStreamData
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
std::map<std::string, std::string> QPDF_Stream::filter_abbreviations = {
|
||||
// The PDF specification provides these filter abbreviations for
|
||||
// use in inline images, but according to table H.1 in the pre-ISO
|
||||
// versions of the PDF specification, Adobe Reader also accepts
|
||||
// them for stream filters.
|
||||
{"/AHx", "/ASCIIHexDecode"},
|
||||
{"/A85", "/ASCII85Decode"},
|
||||
{"/LZW", "/LZWDecode"},
|
||||
{"/Fl", "/FlateDecode"},
|
||||
{"/RL", "/RunLengthDecode"},
|
||||
{"/CCF", "/CCITTFaxDecode"},
|
||||
{"/DCT", "/DCTDecode"},
|
||||
};
|
||||
|
||||
std::map<
|
||||
std::string,
|
||||
std::function<std::shared_ptr<QPDFStreamFilter>()>>
|
||||
QPDF_Stream::filter_factories = {
|
||||
{"/Crypt", []() { return std::make_shared<SF_Crypt>(); }},
|
||||
{"/FlateDecode", SF_FlateLzwDecode::flate_factory},
|
||||
{"/LZWDecode", SF_FlateLzwDecode::lzw_factory},
|
||||
{"/RunLengthDecode", SF_RunLengthDecode::factory},
|
||||
{"/DCTDecode", SF_DCTDecode::factory},
|
||||
{"/ASCII85Decode", SF_ASCII85Decode::factory},
|
||||
{"/ASCIIHexDecode", SF_ASCIIHexDecode::factory},
|
||||
};
|
||||
|
||||
QPDF_Stream::QPDF_Stream(QPDF* qpdf, int objid, int generation,
|
||||
QPDFObjectHandle stream_dict,
|
||||
@ -47,6 +107,14 @@ QPDF_Stream::~QPDF_Stream()
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
QPDF_Stream::registerStreamFilter(
|
||||
std::string const& filter_name,
|
||||
std::function<std::shared_ptr<QPDFStreamFilter>()> factory)
|
||||
{
|
||||
filter_factories[filter_name] = factory;
|
||||
}
|
||||
|
||||
void
|
||||
QPDF_Stream::releaseResolved()
|
||||
{
|
||||
@ -190,125 +258,18 @@ QPDF_Stream::getRawStreamData()
|
||||
}
|
||||
|
||||
bool
|
||||
QPDF_Stream::understandDecodeParams(
|
||||
std::string const& filter, QPDFObjectHandle decode_obj,
|
||||
int& predictor, int& columns,
|
||||
int& colors, int& bits_per_component,
|
||||
bool& early_code_change)
|
||||
{
|
||||
bool filterable = true;
|
||||
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 (((filter == "/FlateDecode") || (filter == "/LZWDecode")) &&
|
||||
(key == "/Predictor"))
|
||||
{
|
||||
QPDFObjectHandle predictor_obj = decode_obj.getKey(key);
|
||||
if (predictor_obj.isInteger())
|
||||
{
|
||||
predictor = predictor_obj.getIntValueAsInt();
|
||||
if (! ((predictor == 1) || (predictor == 2) ||
|
||||
((predictor >= 10) && (predictor <= 15))))
|
||||
{
|
||||
filterable = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
filterable = false;
|
||||
}
|
||||
}
|
||||
else if ((filter == "/LZWDecode") && (key == "/EarlyChange"))
|
||||
{
|
||||
QPDFObjectHandle earlychange_obj = decode_obj.getKey(key);
|
||||
if (earlychange_obj.isInteger())
|
||||
{
|
||||
int earlychange = earlychange_obj.getIntValueAsInt();
|
||||
early_code_change = (earlychange == 1);
|
||||
if (! ((earlychange == 0) || (earlychange == 1)))
|
||||
{
|
||||
filterable = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
filterable = false;
|
||||
}
|
||||
}
|
||||
else if ((key == "/Columns") ||
|
||||
(key == "/Colors") ||
|
||||
(key == "/BitsPerComponent"))
|
||||
{
|
||||
QPDFObjectHandle param_obj = decode_obj.getKey(key);
|
||||
if (param_obj.isInteger())
|
||||
{
|
||||
int val = param_obj.getIntValueAsInt();
|
||||
if (key == "/Columns")
|
||||
{
|
||||
columns = val;
|
||||
}
|
||||
else if (key == "/Colors")
|
||||
{
|
||||
colors = val;
|
||||
}
|
||||
else if (key == "/BitsPerComponent")
|
||||
{
|
||||
bits_per_component = val;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
filterable = false;
|
||||
}
|
||||
}
|
||||
else if ((filter == "/Crypt") &&
|
||||
(((key == "/Type") || (key == "/Name")) &&
|
||||
(decode_obj.getKey("/Type").isNull() ||
|
||||
(decode_obj.getKey("/Type").isName() &&
|
||||
(decode_obj.getKey("/Type").getName() ==
|
||||
"/CryptFilterDecodeParms")))))
|
||||
{
|
||||
// we handle this in decryptStream
|
||||
}
|
||||
else
|
||||
{
|
||||
filterable = false;
|
||||
}
|
||||
}
|
||||
|
||||
return filterable;
|
||||
}
|
||||
|
||||
bool
|
||||
QPDF_Stream::filterable(std::vector<std::string>& filters,
|
||||
QPDF_Stream::filterable(
|
||||
std::vector<std::shared_ptr<QPDFStreamFilter>>& filters,
|
||||
bool& specialized_compression,
|
||||
bool& lossy_compression,
|
||||
int& predictor, int& columns,
|
||||
int& colors, int& bits_per_component,
|
||||
bool& early_code_change)
|
||||
bool& lossy_compression)
|
||||
{
|
||||
if (filter_abbreviations.empty())
|
||||
{
|
||||
// The PDF specification provides these filter abbreviations
|
||||
// for use in inline images, but according to table H.1 in the
|
||||
// pre-ISO versions of the PDF specification, Adobe Reader
|
||||
// also accepts them for stream filters.
|
||||
filter_abbreviations["/AHx"] = "/ASCIIHexDecode";
|
||||
filter_abbreviations["/A85"] = "/ASCII85Decode";
|
||||
filter_abbreviations["/LZW"] = "/LZWDecode";
|
||||
filter_abbreviations["/Fl"] = "/FlateDecode";
|
||||
filter_abbreviations["/RL"] = "/RunLengthDecode";
|
||||
filter_abbreviations["/CCF"] = "/CCITTFaxDecode";
|
||||
filter_abbreviations["/DCT"] = "/DCTDecode";
|
||||
}
|
||||
|
||||
// Check filters
|
||||
|
||||
QPDFObjectHandle filter_obj = this->stream_dict.getKey("/Filter");
|
||||
bool filters_okay = true;
|
||||
|
||||
std::vector<std::string> filter_names;
|
||||
|
||||
if (filter_obj.isNull())
|
||||
{
|
||||
// No filters
|
||||
@ -316,7 +277,7 @@ QPDF_Stream::filterable(std::vector<std::string>& filters,
|
||||
else if (filter_obj.isName())
|
||||
{
|
||||
// One filter
|
||||
filters.push_back(filter_obj.getName());
|
||||
filter_names.push_back(filter_obj.getName());
|
||||
}
|
||||
else if (filter_obj.isArray())
|
||||
{
|
||||
@ -327,7 +288,7 @@ QPDF_Stream::filterable(std::vector<std::string>& filters,
|
||||
QPDFObjectHandle item = filter_obj.getArrayItem(i);
|
||||
if (item.isName())
|
||||
{
|
||||
filters.push_back(item.getName());
|
||||
filter_names.push_back(item.getName());
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -351,34 +312,23 @@ QPDF_Stream::filterable(std::vector<std::string>& filters,
|
||||
|
||||
bool filterable = true;
|
||||
|
||||
for (std::vector<std::string>::iterator iter = filters.begin();
|
||||
iter != filters.end(); ++iter)
|
||||
for (auto& filter_name: filter_names)
|
||||
{
|
||||
std::string& filter = *iter;
|
||||
|
||||
if (filter_abbreviations.count(filter))
|
||||
if (filter_abbreviations.count(filter_name))
|
||||
{
|
||||
QTC::TC("qpdf", "QPDF_Stream expand filter abbreviation");
|
||||
filter = filter_abbreviations[filter];
|
||||
filter_name = filter_abbreviations[filter_name];
|
||||
}
|
||||
|
||||
if (filter == "/RunLengthDecode")
|
||||
{
|
||||
specialized_compression = true;
|
||||
}
|
||||
else if (filter == "/DCTDecode")
|
||||
{
|
||||
specialized_compression = true;
|
||||
lossy_compression = true;
|
||||
}
|
||||
else if (! ((filter == "/Crypt") ||
|
||||
(filter == "/FlateDecode") ||
|
||||
(filter == "/LZWDecode") ||
|
||||
(filter == "/ASCII85Decode") ||
|
||||
(filter == "/ASCIIHexDecode")))
|
||||
auto ff = filter_factories.find(filter_name);
|
||||
if (ff == filter_factories.end())
|
||||
{
|
||||
filterable = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
filters.push_back((ff->second)());
|
||||
}
|
||||
}
|
||||
|
||||
if (! filterable)
|
||||
@ -386,15 +336,8 @@ QPDF_Stream::filterable(std::vector<std::string>& filters,
|
||||
return false;
|
||||
}
|
||||
|
||||
// `filters' now contains a list of filters to be applied in
|
||||
// order. See which ones we can support.
|
||||
|
||||
// Initialize values to their defaults as per the PDF spec
|
||||
predictor = 1;
|
||||
columns = 0;
|
||||
colors = 1;
|
||||
bits_per_component = 8;
|
||||
early_code_change = true;
|
||||
// filters now contains a list of filters to be applied in order.
|
||||
// See which ones we can support.
|
||||
|
||||
// See if we can support any decode parameters that are specified.
|
||||
|
||||
@ -413,7 +356,7 @@ QPDF_Stream::filterable(std::vector<std::string>& filters,
|
||||
}
|
||||
else
|
||||
{
|
||||
for (unsigned int i = 0; i < filters.size(); ++i)
|
||||
for (unsigned int i = 0; i < filter_names.size(); ++i)
|
||||
{
|
||||
decode_parms.push_back(decode_obj);
|
||||
}
|
||||
@ -436,21 +379,21 @@ QPDF_Stream::filterable(std::vector<std::string>& filters,
|
||||
return false;
|
||||
}
|
||||
|
||||
for (unsigned int i = 0; i < filters.size(); ++i)
|
||||
for (size_t i = 0; i < filters.size(); ++i)
|
||||
{
|
||||
QPDFObjectHandle decode_item = decode_parms.at(i);
|
||||
if (decode_item.isNull())
|
||||
auto filter = filters.at(i);
|
||||
auto decode_item = decode_parms.at(i);
|
||||
|
||||
if (filter->setDecodeParms(decode_item))
|
||||
{
|
||||
// okay
|
||||
if (filter->isSpecializedCompression())
|
||||
{
|
||||
specialized_compression = true;
|
||||
}
|
||||
else if (decode_item.isDictionary())
|
||||
if (filter->isLossyCompression())
|
||||
{
|
||||
if (! understandDecodeParams(
|
||||
filters.at(i), decode_item,
|
||||
predictor, columns, colors, bits_per_component,
|
||||
early_code_change))
|
||||
{
|
||||
filterable = false;
|
||||
specialized_compression = true;
|
||||
lossy_compression = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -459,17 +402,6 @@ QPDF_Stream::filterable(std::vector<std::string>& filters,
|
||||
}
|
||||
}
|
||||
|
||||
if ((predictor > 1) && (columns == 0))
|
||||
{
|
||||
// invalid
|
||||
filterable = false;
|
||||
}
|
||||
|
||||
if (! filterable)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return filterable;
|
||||
}
|
||||
|
||||
@ -479,12 +411,7 @@ QPDF_Stream::pipeStreamData(Pipeline* pipeline, bool* filterp,
|
||||
qpdf_stream_decode_level_e decode_level,
|
||||
bool suppress_warnings, bool will_retry)
|
||||
{
|
||||
std::vector<std::string> filters;
|
||||
int predictor = 1;
|
||||
int columns = 0;
|
||||
int colors = 1;
|
||||
int bits_per_component = 8;
|
||||
bool early_code_change = true;
|
||||
std::vector<std::shared_ptr<QPDFStreamFilter>> filters;
|
||||
bool specialized_compression = false;
|
||||
bool lossy_compression = false;
|
||||
bool ignored;
|
||||
@ -497,10 +424,8 @@ QPDF_Stream::pipeStreamData(Pipeline* pipeline, bool* filterp,
|
||||
bool success = true;
|
||||
if (filter)
|
||||
{
|
||||
filter = filterable(filters, specialized_compression, lossy_compression,
|
||||
predictor, columns,
|
||||
colors, bits_per_component,
|
||||
early_code_change);
|
||||
filter = filterable(
|
||||
filters, specialized_compression, lossy_compression);
|
||||
if ((decode_level < qpdf_dl_all) && lossy_compression)
|
||||
{
|
||||
filter = false;
|
||||
@ -524,7 +449,9 @@ QPDF_Stream::pipeStreamData(Pipeline* pipeline, bool* filterp,
|
||||
}
|
||||
|
||||
// Construct the pipeline in reverse order. Force pipelines we
|
||||
// create to be deleted when this function finishes.
|
||||
// create to be deleted when this function finishes. Pipelines
|
||||
// created by QPDFStreamFilter objects will be deleted by those
|
||||
// objects.
|
||||
std::vector<PointerHolder<Pipeline>> to_delete;
|
||||
|
||||
PointerHolder<ContentNormalizer> normalizer;
|
||||
@ -555,79 +482,13 @@ QPDF_Stream::pipeStreamData(Pipeline* pipeline, bool* filterp,
|
||||
to_delete.push_back(pipeline);
|
||||
}
|
||||
|
||||
for (std::vector<std::string>::reverse_iterator f_iter =
|
||||
filters.rbegin();
|
||||
for (auto f_iter = filters.rbegin();
|
||||
f_iter != filters.rend(); ++f_iter)
|
||||
{
|
||||
std::string const& filter_name = *f_iter;
|
||||
|
||||
if ((filter_name == "/FlateDecode") ||
|
||||
(filter_name == "/LZWDecode"))
|
||||
auto decode_pipeline = (*f_iter)->getDecodePipeline(pipeline);
|
||||
if (decode_pipeline)
|
||||
{
|
||||
if ((predictor >= 10) && (predictor <= 15))
|
||||
{
|
||||
QTC::TC("qpdf", "QPDF_Stream PNG filter");
|
||||
pipeline = new Pl_PNGFilter(
|
||||
"png decode", pipeline, Pl_PNGFilter::a_decode,
|
||||
QIntC::to_uint(columns),
|
||||
QIntC::to_uint(colors),
|
||||
QIntC::to_uint(bits_per_component));
|
||||
to_delete.push_back(pipeline);
|
||||
}
|
||||
else if (predictor == 2)
|
||||
{
|
||||
QTC::TC("qpdf", "QPDF_Stream TIFF predictor");
|
||||
pipeline = new Pl_TIFFPredictor(
|
||||
"tiff decode", pipeline, Pl_TIFFPredictor::a_decode,
|
||||
QIntC::to_uint(columns),
|
||||
QIntC::to_uint(colors),
|
||||
QIntC::to_uint(bits_per_component));
|
||||
to_delete.push_back(pipeline);
|
||||
}
|
||||
}
|
||||
|
||||
if (filter_name == "/Crypt")
|
||||
{
|
||||
// Ignore -- handled by pipeStreamData
|
||||
}
|
||||
else if (filter_name == "/FlateDecode")
|
||||
{
|
||||
pipeline = new Pl_Flate("stream inflate",
|
||||
pipeline, Pl_Flate::a_inflate);
|
||||
to_delete.push_back(pipeline);
|
||||
}
|
||||
else if (filter_name == "/ASCII85Decode")
|
||||
{
|
||||
pipeline = new Pl_ASCII85Decoder("ascii85 decode", pipeline);
|
||||
to_delete.push_back(pipeline);
|
||||
}
|
||||
else if (filter_name == "/ASCIIHexDecode")
|
||||
{
|
||||
pipeline = new Pl_ASCIIHexDecoder("asciiHex decode", pipeline);
|
||||
to_delete.push_back(pipeline);
|
||||
}
|
||||
else if (filter_name == "/LZWDecode")
|
||||
{
|
||||
pipeline = new Pl_LZWDecoder("lzw decode", pipeline,
|
||||
early_code_change);
|
||||
to_delete.push_back(pipeline);
|
||||
}
|
||||
else if (filter_name == "/RunLengthDecode")
|
||||
{
|
||||
pipeline = new Pl_RunLength("runlength decode", pipeline,
|
||||
Pl_RunLength::a_decode);
|
||||
to_delete.push_back(pipeline);
|
||||
}
|
||||
else if (filter_name == "/DCTDecode")
|
||||
{
|
||||
pipeline = new Pl_DCT("DCT decode", pipeline);
|
||||
to_delete.push_back(pipeline);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::logic_error(
|
||||
"INTERNAL ERROR: QPDFStream: unknown filter "
|
||||
"encountered after check");
|
||||
pipeline = decode_pipeline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
153
libqpdf/SF_FlateLzwDecode.cc
Normal file
153
libqpdf/SF_FlateLzwDecode.cc
Normal file
@ -0,0 +1,153 @@
|
||||
#include <qpdf/SF_FlateLzwDecode.hh>
|
||||
#include <qpdf/Pl_PNGFilter.hh>
|
||||
#include <qpdf/Pl_TIFFPredictor.hh>
|
||||
#include <qpdf/Pl_Flate.hh>
|
||||
#include <qpdf/Pl_LZWDecoder.hh>
|
||||
#include <qpdf/QTC.hh>
|
||||
#include <qpdf/QIntC.hh>
|
||||
|
||||
SF_FlateLzwDecode::SF_FlateLzwDecode(bool lzw) :
|
||||
lzw(lzw),
|
||||
// Initialize values to their defaults as per the PDF spec
|
||||
predictor(1),
|
||||
columns(0),
|
||||
colors(1),
|
||||
bits_per_component(8),
|
||||
early_code_change(true)
|
||||
{
|
||||
}
|
||||
|
||||
bool
|
||||
SF_FlateLzwDecode::setDecodeParms(QPDFObjectHandle decode_parms)
|
||||
{
|
||||
if (decode_parms.isNull())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool filterable = true;
|
||||
std::set<std::string> keys = decode_parms.getKeys();
|
||||
for (auto const& key: keys)
|
||||
{
|
||||
QPDFObjectHandle value = decode_parms.getKey(key);
|
||||
if (key == "/Predictor")
|
||||
{
|
||||
if (value.isInteger())
|
||||
{
|
||||
this->predictor = value.getIntValueAsInt();
|
||||
if (! ((this->predictor == 1) || (this->predictor == 2) ||
|
||||
((this->predictor >= 10) && (this->predictor <= 15))))
|
||||
{
|
||||
filterable = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
filterable = false;
|
||||
}
|
||||
}
|
||||
else if ((key == "/Columns") ||
|
||||
(key == "/Colors") ||
|
||||
(key == "/BitsPerComponent"))
|
||||
{
|
||||
if (value.isInteger())
|
||||
{
|
||||
int val = value.getIntValueAsInt();
|
||||
if (key == "/Columns")
|
||||
{
|
||||
this->columns = val;
|
||||
}
|
||||
else if (key == "/Colors")
|
||||
{
|
||||
this->colors = val;
|
||||
}
|
||||
else if (key == "/BitsPerComponent")
|
||||
{
|
||||
this->bits_per_component = val;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
filterable = false;
|
||||
}
|
||||
}
|
||||
else if (lzw && (key == "/EarlyChange"))
|
||||
{
|
||||
if (value.isInteger())
|
||||
{
|
||||
int earlychange = value.getIntValueAsInt();
|
||||
this->early_code_change = (earlychange == 1);
|
||||
if (! ((earlychange == 0) || (earlychange == 1)))
|
||||
{
|
||||
filterable = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
filterable = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((this->predictor > 1) && (this->columns == 0))
|
||||
{
|
||||
filterable = false;
|
||||
}
|
||||
|
||||
return filterable;
|
||||
}
|
||||
|
||||
|
||||
|
||||
Pipeline*
|
||||
SF_FlateLzwDecode::getDecodePipeline(Pipeline* next)
|
||||
{
|
||||
std::shared_ptr<Pipeline> pipeline;
|
||||
if ((this->predictor >= 10) && (this->predictor <= 15))
|
||||
{
|
||||
QTC::TC("qpdf", "SF_FlateLzwDecode PNG filter");
|
||||
pipeline = std::make_shared<Pl_PNGFilter>(
|
||||
"png decode", next, Pl_PNGFilter::a_decode,
|
||||
QIntC::to_uint(this->columns),
|
||||
QIntC::to_uint(this->colors),
|
||||
QIntC::to_uint(this->bits_per_component));
|
||||
this->pipelines.push_back(pipeline);
|
||||
next = pipeline.get();
|
||||
}
|
||||
else if (this->predictor == 2)
|
||||
{
|
||||
QTC::TC("qpdf", "SF_FlateLzwDecode TIFF predictor");
|
||||
pipeline = std::make_shared<Pl_TIFFPredictor>(
|
||||
"tiff decode", next, Pl_TIFFPredictor::a_decode,
|
||||
QIntC::to_uint(this->columns),
|
||||
QIntC::to_uint(this->colors),
|
||||
QIntC::to_uint(this->bits_per_component));
|
||||
this->pipelines.push_back(pipeline);
|
||||
next = pipeline.get();
|
||||
}
|
||||
|
||||
if (lzw)
|
||||
{
|
||||
pipeline = std::make_shared<Pl_LZWDecoder>(
|
||||
"lzw decode", next, early_code_change);
|
||||
}
|
||||
else
|
||||
{
|
||||
pipeline = std::make_shared<Pl_Flate>(
|
||||
"stream inflate", next, Pl_Flate::a_inflate);
|
||||
}
|
||||
this->pipelines.push_back(pipeline);
|
||||
return pipeline.get();
|
||||
}
|
||||
|
||||
std::shared_ptr<QPDFStreamFilter>
|
||||
SF_FlateLzwDecode::flate_factory()
|
||||
{
|
||||
return std::make_shared<SF_FlateLzwDecode>(false);
|
||||
}
|
||||
|
||||
std::shared_ptr<QPDFStreamFilter>
|
||||
SF_FlateLzwDecode::lzw_factory()
|
||||
{
|
||||
return std::make_shared<SF_FlateLzwDecode>(true);
|
||||
}
|
@ -70,6 +70,7 @@ SRCS_libqpdf = \
|
||||
libqpdf/QPDFPageDocumentHelper.cc \
|
||||
libqpdf/QPDFPageLabelDocumentHelper.cc \
|
||||
libqpdf/QPDFPageObjectHelper.cc \
|
||||
libqpdf/QPDFStreamFilter.cc \
|
||||
libqpdf/QPDFSystemError.cc \
|
||||
libqpdf/QPDFTokenizer.cc \
|
||||
libqpdf/QPDFWriter.cc \
|
||||
@ -94,6 +95,7 @@ SRCS_libqpdf = \
|
||||
libqpdf/QUtil.cc \
|
||||
libqpdf/RC4.cc \
|
||||
libqpdf/SecureRandomDataProvider.cc \
|
||||
libqpdf/SF_FlateLzwDecode.cc \
|
||||
libqpdf/SparseOHArray.cc \
|
||||
libqpdf/qpdf-c.cc
|
||||
|
||||
|
@ -5,6 +5,10 @@
|
||||
|
||||
#include <qpdf/QPDFObject.hh>
|
||||
#include <qpdf/QPDFObjectHandle.hh>
|
||||
#include <qpdf/QPDFStreamFilter.hh>
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
class Pipeline;
|
||||
class QPDF;
|
||||
@ -49,6 +53,10 @@ class QPDF_Stream: public QPDFObject
|
||||
|
||||
void replaceDict(QPDFObjectHandle new_dict);
|
||||
|
||||
static void registerStreamFilter(
|
||||
std::string const& filter_name,
|
||||
std::function<std::shared_ptr<QPDFStreamFilter>()> factory);
|
||||
|
||||
// Replace object ID and generation. This may only be called if
|
||||
// object ID and generation are 0. It is used by QPDFObjectHandle
|
||||
// when adding streams to files.
|
||||
@ -59,20 +67,15 @@ class QPDF_Stream: public QPDFObject
|
||||
|
||||
private:
|
||||
static std::map<std::string, std::string> filter_abbreviations;
|
||||
static std::map<
|
||||
std::string,
|
||||
std::function<std::shared_ptr<QPDFStreamFilter>()>> filter_factories;
|
||||
|
||||
void replaceFilterData(QPDFObjectHandle const& filter,
|
||||
QPDFObjectHandle const& decode_parms,
|
||||
size_t length);
|
||||
bool understandDecodeParams(
|
||||
std::string const& filter, QPDFObjectHandle decode_params,
|
||||
int& predictor, int& columns,
|
||||
int& colors, int& bits_per_component,
|
||||
bool& early_code_change);
|
||||
bool filterable(std::vector<std::string>& filters,
|
||||
bool& specialized_compression, bool& lossy_compression,
|
||||
int& predictor, int& columns,
|
||||
int& colors, int& bits_per_component,
|
||||
bool& early_code_change);
|
||||
bool filterable(std::vector<std::shared_ptr<QPDFStreamFilter>>& filters,
|
||||
bool& specialized_compression, bool& lossy_compression);
|
||||
void warn(QPDFExc const& e);
|
||||
void setDictDescription();
|
||||
void setStreamDescription();
|
||||
|
30
libqpdf/qpdf/SF_ASCII85Decode.hh
Normal file
30
libqpdf/qpdf/SF_ASCII85Decode.hh
Normal file
@ -0,0 +1,30 @@
|
||||
#include <qpdf/QPDFStreamFilter.hh>
|
||||
#include <qpdf/Pl_ASCII85Decoder.hh>
|
||||
#include <memory>
|
||||
|
||||
#ifndef SF_ASCII85DECODE_HH
|
||||
#define SF_ASCII85DECODE_HH
|
||||
|
||||
class SF_ASCII85Decode: public QPDFStreamFilter
|
||||
{
|
||||
public:
|
||||
SF_ASCII85Decode() = default;
|
||||
virtual ~SF_ASCII85Decode() = default;
|
||||
|
||||
virtual Pipeline* getDecodePipeline(Pipeline* next) override
|
||||
{
|
||||
this->pipeline = std::make_shared<Pl_ASCII85Decoder>(
|
||||
"ascii85 decode", next);
|
||||
return this->pipeline.get();
|
||||
}
|
||||
|
||||
static std::shared_ptr<QPDFStreamFilter> factory()
|
||||
{
|
||||
return std::make_shared<SF_ASCII85Decode>();
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<Pipeline> pipeline;
|
||||
};
|
||||
|
||||
#endif // SF_ASCII85DECODE_HH
|
30
libqpdf/qpdf/SF_ASCIIHexDecode.hh
Normal file
30
libqpdf/qpdf/SF_ASCIIHexDecode.hh
Normal file
@ -0,0 +1,30 @@
|
||||
#include <qpdf/QPDFStreamFilter.hh>
|
||||
#include <qpdf/Pl_ASCIIHexDecoder.hh>
|
||||
#include <memory>
|
||||
|
||||
#ifndef SF_ASCIIHEXDECODE_HH
|
||||
#define SF_ASCIIHEXDECODE_HH
|
||||
|
||||
class SF_ASCIIHexDecode: public QPDFStreamFilter
|
||||
{
|
||||
public:
|
||||
SF_ASCIIHexDecode() = default;
|
||||
virtual ~SF_ASCIIHexDecode() = default;
|
||||
|
||||
virtual Pipeline* getDecodePipeline(Pipeline* next) override
|
||||
{
|
||||
this->pipeline = std::make_shared<Pl_ASCIIHexDecoder>(
|
||||
"asciiHex decode", next);
|
||||
return this->pipeline.get();
|
||||
}
|
||||
|
||||
static std::shared_ptr<QPDFStreamFilter> factory()
|
||||
{
|
||||
return std::make_shared<SF_ASCIIHexDecode>();
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<Pipeline> pipeline;
|
||||
};
|
||||
|
||||
#endif // SF_ASCIIHEXDECODE_HH
|
39
libqpdf/qpdf/SF_DCTDecode.hh
Normal file
39
libqpdf/qpdf/SF_DCTDecode.hh
Normal file
@ -0,0 +1,39 @@
|
||||
#include <qpdf/QPDFStreamFilter.hh>
|
||||
#include <qpdf/Pl_DCT.hh>
|
||||
#include <memory>
|
||||
|
||||
#ifndef SF_DCTDECODE_HH
|
||||
#define SF_DCTDECODE_HH
|
||||
|
||||
class SF_DCTDecode: public QPDFStreamFilter
|
||||
{
|
||||
public:
|
||||
SF_DCTDecode() = default;
|
||||
virtual ~SF_DCTDecode() = default;
|
||||
|
||||
virtual Pipeline* getDecodePipeline(Pipeline* next) override
|
||||
{
|
||||
this->pipeline = std::make_shared<Pl_DCT>("DCT decode", next);
|
||||
return this->pipeline.get();
|
||||
}
|
||||
|
||||
static std::shared_ptr<QPDFStreamFilter> factory()
|
||||
{
|
||||
return std::make_shared<SF_DCTDecode>();
|
||||
}
|
||||
|
||||
virtual bool isSpecializedCompression() override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual bool isLossyCompression() override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<Pipeline> pipeline;
|
||||
};
|
||||
|
||||
#endif // SF_DCTDECODE_HH
|
30
libqpdf/qpdf/SF_FlateLzwDecode.hh
Normal file
30
libqpdf/qpdf/SF_FlateLzwDecode.hh
Normal file
@ -0,0 +1,30 @@
|
||||
#include <qpdf/QPDFStreamFilter.hh>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#ifndef SF_FLATELZWDECODE_HH
|
||||
#define SF_FLATELZWDECODE_HH
|
||||
|
||||
class SF_FlateLzwDecode: public QPDFStreamFilter
|
||||
{
|
||||
public:
|
||||
SF_FlateLzwDecode(bool lzw);
|
||||
virtual ~SF_FlateLzwDecode() = default;
|
||||
|
||||
virtual bool setDecodeParms(QPDFObjectHandle decode_parms);
|
||||
virtual Pipeline* getDecodePipeline(Pipeline* next);
|
||||
|
||||
static std::shared_ptr<QPDFStreamFilter> flate_factory();
|
||||
static std::shared_ptr<QPDFStreamFilter> lzw_factory();
|
||||
|
||||
private:
|
||||
bool lzw;
|
||||
int predictor;
|
||||
int columns;
|
||||
int colors;
|
||||
int bits_per_component;
|
||||
bool early_code_change;
|
||||
std::vector<std::shared_ptr<Pipeline>> pipelines;
|
||||
};
|
||||
|
||||
#endif // SF_FLATELZWDECODE_HH
|
35
libqpdf/qpdf/SF_RunLengthDecode.hh
Normal file
35
libqpdf/qpdf/SF_RunLengthDecode.hh
Normal file
@ -0,0 +1,35 @@
|
||||
#include <qpdf/QPDFStreamFilter.hh>
|
||||
#include <qpdf/Pl_RunLength.hh>
|
||||
#include <memory>
|
||||
|
||||
#ifndef SF_RUNLENGTHDECODE_HH
|
||||
#define SF_RUNLENGTHDECODE_HH
|
||||
|
||||
class SF_RunLengthDecode: public QPDFStreamFilter
|
||||
{
|
||||
public:
|
||||
SF_RunLengthDecode() = default;
|
||||
virtual ~SF_RunLengthDecode() = default;
|
||||
|
||||
virtual Pipeline* getDecodePipeline(Pipeline* next) override
|
||||
{
|
||||
this->pipeline = std::make_shared<Pl_RunLength>(
|
||||
"runlength decode", next, Pl_RunLength::a_decode);
|
||||
return this->pipeline.get();
|
||||
}
|
||||
|
||||
static std::shared_ptr<QPDFStreamFilter> factory()
|
||||
{
|
||||
return std::make_shared<SF_RunLengthDecode>();
|
||||
}
|
||||
|
||||
virtual bool isSpecializedCompression() override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<Pipeline> pipeline;
|
||||
};
|
||||
|
||||
#endif // SF_RUNLENGTHDECODE_HH
|
@ -86,7 +86,7 @@ QPDF prev key in trailer dictionary 0
|
||||
QPDF found xref stream 0
|
||||
QPDF ignoring XRefStm in trailer 0
|
||||
QPDF xref deleted object 0
|
||||
QPDF_Stream PNG filter 0
|
||||
SF_FlateLzwDecode PNG filter 0
|
||||
QPDF xref /Index is null 0
|
||||
QPDF xref /Index is array 1
|
||||
QPDFWriter copy Extends 0
|
||||
@ -294,7 +294,7 @@ qpdf-c called qpdf_set_decode_level 0
|
||||
qpdf-c called qpdf_set_compress_streams 0
|
||||
qpdf-c called qpdf_set_preserve_unreferenced_objects 0
|
||||
qpdf-c called qpdf_set_newline_before_endstream 0
|
||||
QPDF_Stream TIFF predictor 0
|
||||
SF_FlateLzwDecode TIFF predictor 0
|
||||
QPDFTokenizer inline image at EOF 0
|
||||
Pl_QPDFTokenizer found ID 0
|
||||
QPDFObjectHandle non-stream in stream array 0
|
||||
|
Loading…
Reference in New Issue
Block a user