mirror of https://github.com/JakubMelka/PDF4QT.git
Advanced functions
This commit is contained in:
parent
2023e17c4c
commit
234b7c77f7
|
@ -337,6 +337,26 @@ QRectF PDFDocumentDataLoaderDecorator::readRectangle(const PDFObject& object, co
|
|||
return defaultValue;
|
||||
}
|
||||
|
||||
std::vector<PDFReal> PDFDocumentDataLoaderDecorator::readNumberArrayFromDictionary(const PDFDictionary* dictionary, const char* key)
|
||||
{
|
||||
if (dictionary->hasKey(key))
|
||||
{
|
||||
return readNumberArray(dictionary->get(key));
|
||||
}
|
||||
|
||||
return std::vector<PDFReal>();
|
||||
}
|
||||
|
||||
std::vector<PDFInteger> PDFDocumentDataLoaderDecorator::readIntegerArrayFromDictionary(const PDFDictionary* dictionary, const char* key)
|
||||
{
|
||||
if (dictionary->hasKey(key))
|
||||
{
|
||||
return readIntegerArray(dictionary->get(key));
|
||||
}
|
||||
|
||||
return std::vector<PDFInteger>();
|
||||
}
|
||||
|
||||
PDFReal PDFDocumentDataLoaderDecorator::readNumberFromDictionary(const PDFDictionary* dictionary, const char* key, PDFReal defaultValue) const
|
||||
{
|
||||
if (dictionary->hasKey(key))
|
||||
|
@ -385,4 +405,35 @@ std::vector<PDFReal> PDFDocumentDataLoaderDecorator::readNumberArray(const PDFOb
|
|||
return std::vector<PDFReal>();
|
||||
}
|
||||
|
||||
std::vector<PDFInteger> PDFDocumentDataLoaderDecorator::readIntegerArray(const PDFObject& object) const
|
||||
{
|
||||
const PDFObject& dereferencedObject = m_document->getObject(object);
|
||||
if (dereferencedObject.isArray())
|
||||
{
|
||||
const PDFArray* array = dereferencedObject.getArray();
|
||||
std::vector<PDFInteger> result;
|
||||
const size_t count = array->getCount();
|
||||
result.reserve(count);
|
||||
|
||||
for (size_t i = 0; i < count; ++i)
|
||||
{
|
||||
// This value is not representable in the current PDF parser. So we assume we
|
||||
// can't get this value.
|
||||
constexpr const PDFInteger INVALID_VALUE = std::numeric_limits<PDFInteger>::max();
|
||||
const PDFReal number = readInteger(array->getItem(i), INVALID_VALUE);
|
||||
if (number == INVALID_VALUE)
|
||||
{
|
||||
return std::vector<PDFInteger>();
|
||||
}
|
||||
result.push_back(number);
|
||||
}
|
||||
|
||||
// We assume, that RVO (return value optimization) will not work for this function
|
||||
// (multiple return points).
|
||||
return std::move(result);
|
||||
}
|
||||
|
||||
return std::vector<PDFInteger>();
|
||||
}
|
||||
|
||||
} // namespace pdf
|
||||
|
|
|
@ -174,6 +174,14 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
/// Tries to read array of real values from dictionary. If entry dictionary doesn't exist,
|
||||
/// or error occurs, empty record is returned.
|
||||
std::vector<PDFReal> readNumberArrayFromDictionary(const PDFDictionary *dictionary, const char *key);
|
||||
|
||||
/// Tries to read array of integer values from dictionary. If entry dictionary doesn't exist,
|
||||
/// or error occurs, empty record is returned.
|
||||
std::vector<PDFInteger> readIntegerArrayFromDictionary(const PDFDictionary *dictionary, const char *key);
|
||||
|
||||
/// Reads number from dictionary. If dictionary entry doesn't exist, or error occurs, default value is returned.
|
||||
/// \param dictionary Dictionary containing desired data
|
||||
/// \param key Entry key
|
||||
|
@ -192,6 +200,12 @@ public:
|
|||
/// \param object Object containing array of numbers
|
||||
std::vector<PDFReal> readNumberArray(const PDFObject& object) const;
|
||||
|
||||
/// Reads integer array from dictionary. Reads all values. If some value is not
|
||||
/// integer number, empty array is returned. Empty array is also returned,
|
||||
/// if \p object is invalid.
|
||||
/// \param object Object containing array of numbers
|
||||
std::vector<PDFInteger> readIntegerArray(const PDFObject& object) const;
|
||||
|
||||
private:
|
||||
const PDFDocument* m_document;
|
||||
};
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
|
||||
#include "pdffunction.h"
|
||||
#include "pdfflatarray.h"
|
||||
#include "pdfparser.h"
|
||||
#include "pdfdocument.h"
|
||||
|
||||
namespace pdf
|
||||
{
|
||||
|
@ -30,6 +32,218 @@ PDFFunction::PDFFunction(uint32_t m, uint32_t n, std::vector<PDFReal>&& domain,
|
|||
|
||||
}
|
||||
|
||||
PDFFunctionPtr PDFFunction::createFunction(const PDFDocument* document, const PDFObject& object)
|
||||
{
|
||||
PDFParsingContext context(nullptr);
|
||||
return createFunctionImpl(document, object, &context);
|
||||
}
|
||||
|
||||
PDFFunctionPtr PDFFunction::createFunctionImpl(const PDFDocument* document, const PDFObject& object, PDFParsingContext* context)
|
||||
{
|
||||
PDFParsingContext::PDFParsingContextObjectGuard guard(context, &object);
|
||||
|
||||
const PDFDictionary* dictionary = nullptr;
|
||||
QByteArray streamData;
|
||||
|
||||
const PDFObject& dereferencedObject = document->getObject(object);
|
||||
if (dereferencedObject.isName() && dereferencedObject.getString() == "Identity")
|
||||
{
|
||||
return std::make_shared<PDFIdentityFunction>();
|
||||
}
|
||||
else if (dereferencedObject.isDictionary())
|
||||
{
|
||||
dictionary = dereferencedObject.getDictionary();
|
||||
}
|
||||
else if (dereferencedObject.isStream())
|
||||
{
|
||||
const PDFStream* stream = dereferencedObject.getStream();
|
||||
dictionary = stream->getDictionary();
|
||||
streamData = document->getDecodedStream(stream);
|
||||
}
|
||||
|
||||
if (!dictionary)
|
||||
{
|
||||
throw PDFParserException(PDFParsingContext::tr("Function dictionary expected."));
|
||||
}
|
||||
|
||||
PDFDocumentDataLoaderDecorator loader(document);
|
||||
const PDFInteger functionType = loader.readIntegerFromDictionary(dictionary, "FunctionType", -1);
|
||||
std::vector<PDFReal> domain = loader.readNumberArrayFromDictionary(dictionary, "Domain");
|
||||
std::vector<PDFReal> range = loader.readNumberArrayFromDictionary(dictionary, "Range");
|
||||
|
||||
// Domain is required for all function
|
||||
if (domain.empty())
|
||||
{
|
||||
throw PDFParserException(PDFParsingContext::tr("Fuction has invalid domain."));
|
||||
}
|
||||
|
||||
if ((functionType == 0 || functionType == 4) && range.empty())
|
||||
{
|
||||
throw PDFParserException(PDFParsingContext::tr("Fuction has invalid range."));
|
||||
}
|
||||
|
||||
switch (functionType)
|
||||
{
|
||||
case 0:
|
||||
{
|
||||
// Sampled function
|
||||
std::vector<PDFInteger> size = loader.readIntegerArrayFromDictionary(dictionary, "Size");
|
||||
const size_t bitsPerSample = loader.readIntegerFromDictionary(dictionary, "BitsPerSample", 0);
|
||||
std::vector<PDFReal> encode = loader.readNumberArrayFromDictionary(dictionary, "Encode");
|
||||
std::vector<PDFReal> decode = loader.readNumberArrayFromDictionary(dictionary, "Decode");
|
||||
|
||||
if (size.empty() || !std::all_of(size.cbegin(), size.cend(), [](PDFInteger size) { return size >= 1; }))
|
||||
{
|
||||
throw PDFParserException(PDFParsingContext::tr("Sampled function has invalid sample size."));
|
||||
}
|
||||
|
||||
if (bitsPerSample < 1 || bitsPerSample > 32)
|
||||
{
|
||||
throw PDFParserException(PDFParsingContext::tr("Sampled function has invalid count of bits per sample."));
|
||||
}
|
||||
|
||||
if (encode.empty())
|
||||
{
|
||||
// Construct default array according to the PDF 1.7 specification
|
||||
encode.resize(2 * size.size(), 0);
|
||||
for (size_t i = 0, count = size.size(); i < count; ++i)
|
||||
{
|
||||
encode[2 * i + 1] = size[i] - 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (decode.empty())
|
||||
{
|
||||
// Default decode array is same as range, see PDF 1.7 specification
|
||||
decode = range;
|
||||
}
|
||||
|
||||
const size_t m = size.size();
|
||||
const size_t n = range.size() / 2;
|
||||
|
||||
if (n == 0)
|
||||
{
|
||||
throw PDFParserException(PDFParsingContext::tr("Sampled function hasn't any output."));
|
||||
}
|
||||
|
||||
if (domain.size() != encode.size())
|
||||
{
|
||||
throw PDFParserException(PDFParsingContext::tr("Sampled function has invalid encode array."));
|
||||
}
|
||||
|
||||
if (range.size() != decode.size())
|
||||
{
|
||||
throw PDFParserException(PDFParsingContext::tr("Sampled function has invalid decode array."));
|
||||
}
|
||||
|
||||
const uint64_t sampleMaxValueInteger = (static_cast<uint64_t>(1) << static_cast<uint64_t>(bitsPerSample)) - 1;
|
||||
const PDFReal sampleMaxValue = sampleMaxValueInteger;
|
||||
|
||||
// Load samples - first see, how much of them will be needed.
|
||||
const PDFInteger sampleCount = std::accumulate(size.cbegin(), size.cend(), 1, [](PDFInteger a, PDFInteger b) { return a * b; } ) * n;
|
||||
std::vector<PDFReal> samples;
|
||||
samples.resize(sampleCount, 0.0);
|
||||
|
||||
// We must use 64 bit, because we can have 32 bit values
|
||||
uint64_t buffer = 0;
|
||||
uint64_t bitsWritten = 0;
|
||||
uint64_t bitMask = sampleMaxValueInteger;
|
||||
|
||||
QDataStream reader(&streamData, QIODevice::ReadOnly);
|
||||
for (PDFReal& sample : samples)
|
||||
{
|
||||
while (bitsWritten < bitsPerSample)
|
||||
{
|
||||
if (!reader.atEnd())
|
||||
{
|
||||
uint8_t currentByte = 0;
|
||||
reader >> currentByte;
|
||||
buffer = (buffer << 8) | currentByte;
|
||||
bitsWritten += 8;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw PDFParserException(PDFParsingContext::tr("Not enough samples for sampled function."));
|
||||
}
|
||||
}
|
||||
|
||||
// Now we have enough bits to read the data
|
||||
uint64_t sampleUint = (buffer >> (bitsWritten - bitsPerSample)) & bitMask;
|
||||
bitsWritten -= bitsPerSample;
|
||||
sample = sampleUint;
|
||||
}
|
||||
|
||||
std::vector<uint32_t> sizeAsUint;
|
||||
std::transform(size.cbegin(), size.cend(), std::back_inserter(sizeAsUint), [](PDFInteger integer) { return static_cast<uint32_t>(integer); });
|
||||
|
||||
return std::make_shared<PDFSampledFunction>(static_cast<uint32_t>(m), static_cast<uint32_t>(n), std::move(domain), std::move(range), std::move(sizeAsUint), std::move(samples), std::move(encode), std::move(decode), sampleMaxValue);
|
||||
}
|
||||
case 2:
|
||||
{
|
||||
// Exponential function
|
||||
std::vector<PDFReal> c0 = loader.readNumberArrayFromDictionary(dictionary, "C0");
|
||||
std::vector<PDFReal> c1 = loader.readNumberArrayFromDictionary(dictionary, "C1");
|
||||
const PDFReal exponent = loader.readNumberFromDictionary(dictionary, "N", 1.0);
|
||||
|
||||
if (domain.size() != 2)
|
||||
{
|
||||
throw PDFParserException(PDFParsingContext::tr("Exponential function can have only one input value."));
|
||||
}
|
||||
|
||||
if (exponent < 0.0 && domain[0] <= 0.0)
|
||||
{
|
||||
throw PDFParserException(PDFParsingContext::tr("Invalid domain of exponential function."));
|
||||
}
|
||||
|
||||
if (!qFuzzyIsNull(std::fmod(exponent, 1.0)) && domain[0] < 0.0)
|
||||
{
|
||||
throw PDFParserException(PDFParsingContext::tr("Invalid domain of exponential function."));
|
||||
}
|
||||
|
||||
const uint32_t m = 1;
|
||||
|
||||
// Determine n.
|
||||
uint32_t n = static_cast<uint32_t>(std::max({ static_cast<size_t>(1), range.size() / 2, c0.size(), c1.size() }));
|
||||
|
||||
// Resolve default values
|
||||
if (c0.empty())
|
||||
{
|
||||
c0.resize(n, 0.0);
|
||||
}
|
||||
if (c1.empty())
|
||||
{
|
||||
c1.resize(n, 1.0);
|
||||
}
|
||||
|
||||
if (c0.size() != n)
|
||||
{
|
||||
throw PDFParserException(PDFParsingContext::tr("Invalid parameter of exponential function (at x = 0.0)."));
|
||||
}
|
||||
if (c1.size() != n)
|
||||
{
|
||||
throw PDFParserException(PDFParsingContext::tr("Invalid parameter of exponential function (at x = 1.0)."));
|
||||
}
|
||||
|
||||
return std::make_shared<PDFExponentialFunction>(1, n, std::move(domain), std::move(range), std::move(c0), std::move(c1), exponent);
|
||||
}
|
||||
case 3:
|
||||
{
|
||||
// Stitching function
|
||||
}
|
||||
case 4:
|
||||
{
|
||||
// Postscript function
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
throw PDFParserException(PDFParsingContext::tr("Invalid function type: %1.").arg(functionType));
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
PDFSampledFunction::PDFSampledFunction(uint32_t m, uint32_t n,
|
||||
std::vector<PDFReal>&& domain,
|
||||
std::vector<PDFReal>&& range,
|
||||
|
@ -229,7 +443,7 @@ PDFFunction::FunctionResult PDFExponentialFunction::apply(PDFFunction::const_ite
|
|||
}
|
||||
|
||||
Q_ASSERT(m == 1);
|
||||
const PDFReal x = clampInput(0, *x1);
|
||||
const PDFReal x = clampInput(0, *x_1);
|
||||
|
||||
if (!m_isLinear)
|
||||
{
|
||||
|
@ -280,7 +494,7 @@ PDFFunction::FunctionResult PDFStitchingFunction::apply(const_iterator x_1,
|
|||
}
|
||||
|
||||
Q_ASSERT(m == 1);
|
||||
const PDFReal x = clampInput(0, *x1);
|
||||
const PDFReal x = clampInput(0, *x_1);
|
||||
|
||||
// First search for partial function, which defines our range. Use algorithm
|
||||
// similar to the std::lower_bound.
|
||||
|
@ -323,4 +537,27 @@ PDFFunction::FunctionResult PDFStitchingFunction::apply(const_iterator x_1,
|
|||
return result;
|
||||
}
|
||||
|
||||
PDFIdentityFunction::PDFIdentityFunction() :
|
||||
PDFFunction(0, 0, std::vector<PDFReal>(), std::vector<PDFReal>())
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
PDFFunction::FunctionResult PDFIdentityFunction::apply(const_iterator x_1,
|
||||
const_iterator x_m,
|
||||
iterator y_1,
|
||||
iterator y_n) const
|
||||
{
|
||||
const size_t m = std::distance(x_1, x_m);
|
||||
const size_t n = std::distance(y_1, y_n);
|
||||
|
||||
if (m != n)
|
||||
{
|
||||
return PDFTranslationContext::tr("Invalid number of operands for identity function. Expected %1, provided %2.").arg(n).arg(m);
|
||||
}
|
||||
|
||||
std::copy(x_1, x_m, y_1);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace pdf
|
||||
|
|
|
@ -21,17 +21,26 @@
|
|||
|
||||
#include "pdfglobal.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace pdf
|
||||
{
|
||||
class PDFObject;
|
||||
class PDFFunction;
|
||||
class PDFDocument;
|
||||
class PDFParsingContext;
|
||||
|
||||
enum class FunctionType
|
||||
{
|
||||
Identity = -1,
|
||||
Sampled = 0,
|
||||
Exponential = 2,
|
||||
Stitching = 3,
|
||||
PostScript = 4
|
||||
};
|
||||
|
||||
using PDFFunctionPtr = std::shared_ptr<PDFFunction>;
|
||||
|
||||
/// Represents PDF function, as defined in Adobe PDF Reference 1.7, chapter 3.9.
|
||||
/// Generally, function is m to n relation, f(x_1, ... , x_m) = (y_1, ..., y_n).
|
||||
/// Function has domain and range, values outside of domain and range are clamped
|
||||
|
@ -71,9 +80,20 @@ public:
|
|||
/// \param y_n Iterator to the end of the output values (one item after last value)
|
||||
virtual FunctionResult apply(const_iterator x_1, const_iterator x_m, iterator y_1, iterator y_n) const = 0;
|
||||
|
||||
/// Creates function from the object. If error occurs, exception is thrown.
|
||||
/// \param document Document, owning the pdf object
|
||||
/// \param object Object defining the function
|
||||
static PDFFunctionPtr createFunction(const PDFDocument* document, const PDFObject& object);
|
||||
|
||||
protected:
|
||||
static constexpr const size_t DEFAULT_OPERAND_COUNT = 32;
|
||||
|
||||
/// Creates function from the object. If error occurs, exception is thrown.
|
||||
/// \param document Document, owning the pdf object
|
||||
/// \param object Object defining the function
|
||||
/// \param context Parsing context (to avoid circural references)
|
||||
static PDFFunctionPtr createFunctionImpl(const PDFDocument* document, const PDFObject& object, PDFParsingContext* context);
|
||||
|
||||
/// Clamps input value to the domain range.
|
||||
/// \param index Index of the input variable, in range [0, m - 1]
|
||||
/// \param value Value to be clamped
|
||||
|
@ -115,9 +135,24 @@ protected:
|
|||
std::vector<PDFReal> m_range;
|
||||
};
|
||||
|
||||
using PDFFunctionPtr = QSharedPointer<PDFFunction>;
|
||||
/// Identity function
|
||||
class PDFFORQTLIBSHARED_EXPORT PDFIdentityFunction : public PDFFunction
|
||||
{
|
||||
public:
|
||||
explicit PDFIdentityFunction();
|
||||
virtual ~PDFIdentityFunction() = default;
|
||||
|
||||
/// Sampled function (Type 0 function)
|
||||
/// Transforms input values to the output values.
|
||||
/// \param x_1 Iterator to the first input value
|
||||
/// \param x_n Iterator to the end of the input values (one item after last value)
|
||||
/// \param y_1 Iterator to the first output value
|
||||
/// \param y_n Iterator to the end of the output values (one item after last value)
|
||||
virtual FunctionResult apply(const_iterator x_1, const_iterator x_m, iterator y_1, iterator y_n) const override;
|
||||
};
|
||||
|
||||
/// Sampled function (Type 0 function).
|
||||
/// \note Order is ignored, linear interpolation is always performed. No cubic spline
|
||||
/// interpolation occurs.
|
||||
class PDFFORQTLIBSHARED_EXPORT PDFSampledFunction : public PDFFunction
|
||||
{
|
||||
public:
|
||||
|
@ -182,6 +217,7 @@ private:
|
|||
/// then linear interpolation is used instead.
|
||||
class PDFExponentialFunction : public PDFFunction
|
||||
{
|
||||
public:
|
||||
/// Construct new exponential function.
|
||||
/// \param m Number of input variables (must be always 1!)
|
||||
/// \param n Number of output variables
|
||||
|
|
|
@ -217,7 +217,7 @@ public:
|
|||
|
||||
}
|
||||
|
||||
/// Guard guarding the cyclical references.
|
||||
/// Guard guarding the cyclical references (by reference).
|
||||
class PDFParsingContextGuard
|
||||
{
|
||||
public:
|
||||
|
@ -238,6 +238,33 @@ public:
|
|||
PDFObjectReference m_reference;
|
||||
};
|
||||
|
||||
/// Guard guarding the cyclical references (by object).
|
||||
class PDFParsingContextObjectGuard
|
||||
{
|
||||
public:
|
||||
explicit inline PDFParsingContextObjectGuard(PDFParsingContext* context, const PDFObject* object) :
|
||||
m_context(context),
|
||||
m_object(object)
|
||||
{
|
||||
if (object->isReference())
|
||||
{
|
||||
m_context->beginParsingObject(object->getReference());
|
||||
}
|
||||
}
|
||||
|
||||
inline ~PDFParsingContextObjectGuard()
|
||||
{
|
||||
if (m_object->isReference())
|
||||
{
|
||||
m_context->endParsingObject(m_object->getReference());
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
PDFParsingContext* m_context;
|
||||
const PDFObject* m_object;
|
||||
};
|
||||
|
||||
/// Returns dereferenced object, if object is a reference. If it is not a reference,
|
||||
/// then same object is returned.
|
||||
PDFObject getObject(const PDFObject& object);
|
||||
|
@ -257,7 +284,7 @@ private:
|
|||
|
||||
/// Class for parsing objects. Checks cyclical references. If
|
||||
/// the object cannot be obtained from the stream, exception is thrown.
|
||||
class PDFParser
|
||||
class PDFFORQTLIBSHARED_EXPORT PDFParser
|
||||
{
|
||||
Q_DECLARE_TR_FUNCTIONS(pdf::PDFParser)
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
#include "pdfflatmap.h"
|
||||
#include "pdfstreamfilters.h"
|
||||
#include "pdffunction.h"
|
||||
#include "pdfdocument.h"
|
||||
|
||||
#include <regex>
|
||||
|
||||
|
@ -48,6 +49,7 @@ private slots:
|
|||
void test_flat_map();
|
||||
void test_lzw_filter();
|
||||
void test_sampled_function();
|
||||
void test_exponential_function();
|
||||
|
||||
private:
|
||||
void scanWholeStream(const char* stream);
|
||||
|
@ -312,33 +314,535 @@ void LexicalAnalyzerTest::test_lzw_filter()
|
|||
|
||||
void LexicalAnalyzerTest::test_sampled_function()
|
||||
{
|
||||
// Calculate hypercube offsets. Offsets are indexed in bits, from the lowest
|
||||
// bit to the highest. We assume, that we do not have more, than 32 input
|
||||
// variables (we probably run out of memory in that time). Example:
|
||||
//
|
||||
// We have m = 3, f(x_0, x_1, x_2) is sampled function of 3 variables, n = 1.
|
||||
// We have 2, 4, 6 samples for x_0, x_1 and x_2 (so sample count differs).
|
||||
// Then the i-th bit corresponds to variable x_i. We will have m_hypercubeNodeCount == 8,
|
||||
// hypercube offset indices are from 0 to 7.
|
||||
{
|
||||
// Positions in stream: f(0, 0) = 0 = 0 = 0.00
|
||||
// f(1, 0) = \377 = 255 = 1.00
|
||||
// f(0, 1) = \200 = 128 = 0.50
|
||||
// f(1, 1) = \300 = 192 = 0.75
|
||||
|
||||
/* explicit PDFSampledFunction(uint32_t m,
|
||||
uint32_t n,
|
||||
std::vector<PDFReal>&& domain,
|
||||
std::vector<PDFReal>&& range,
|
||||
std::vector<uint32_t>&& size,
|
||||
std::vector<PDFReal>&& samples,
|
||||
std::vector<PDFReal>&& encoder,
|
||||
std::vector<PDFReal>&& decoder);*/
|
||||
std::vector<pdf::PDFReal> samples;
|
||||
samples.resize(2 * 4 * 6, 0);
|
||||
pdf::PDFSampledFunction function(3, 1,
|
||||
{ 0.0, 1.0, 0.0, 1.0, 0.0, 1.0 },
|
||||
{ 0.0, 1.0, 0.0, 1.0, 0.0, 1.0 },
|
||||
{ 2, 4, 6 },
|
||||
std::move(samples),
|
||||
{ 0.0, 1.0, 0.0, 1.0, 0.0, 1.0 },
|
||||
{ 0.0, 1.0, 0.0, 1.0, 0.0, 1.0 },
|
||||
1.0);
|
||||
const char data[] = " << "
|
||||
" /FunctionType 0 "
|
||||
" /Domain [ 0 1 0 1 ] "
|
||||
" /Range [ 0 1 ] "
|
||||
" /Size [ 2 2 ] "
|
||||
" /BitsPerSample 8 "
|
||||
" /Order 1 "
|
||||
" /Length 4 "
|
||||
" >> "
|
||||
" stream\n\000\377\200\300 endstream ";
|
||||
|
||||
pdf::PDFDocument document;
|
||||
pdf::PDFParser parser(data, data + std::size(data), nullptr, pdf::PDFParser::AllowStreams);
|
||||
pdf::PDFFunctionPtr function = pdf::PDFFunction::createFunction(&document, parser.getObject());
|
||||
|
||||
QVERIFY(function);
|
||||
|
||||
auto apply = [&function](pdf::PDFReal x, pdf::PDFReal y) -> pdf::PDFReal
|
||||
{
|
||||
pdf::PDFReal values[2] = {x, y};
|
||||
pdf::PDFReal output = -1.0;
|
||||
function->apply(values, values + std::size(values), &output, &output + 1);
|
||||
return output;
|
||||
};
|
||||
|
||||
auto bilinear = [](pdf::PDFReal x, pdf::PDFReal y)
|
||||
{
|
||||
// See https://en.wikipedia.org/wiki/Bilinear_interpolation - formulas are taken from here.
|
||||
// We are interpolating on unit square.
|
||||
const pdf::PDFReal f00 = 0.00;
|
||||
const pdf::PDFReal f10 = 1.00;
|
||||
const pdf::PDFReal f01 = 0.50;
|
||||
const pdf::PDFReal f11 = 0.75;
|
||||
|
||||
const pdf::PDFReal a00 = f00;
|
||||
const pdf::PDFReal a10 = f10 - f00;
|
||||
const pdf::PDFReal a01 = f01 - f00;
|
||||
const pdf::PDFReal a11 = f11 + f00 - f10 - f01;
|
||||
|
||||
return a00 + a10 * x + a01 * y + a11 * x * y;
|
||||
};
|
||||
|
||||
auto compare = [](pdf::PDFReal x, pdf::PDFReal y)
|
||||
{
|
||||
// We are using 8 bits, so we need 2-digit accuracy
|
||||
return std::abs(x - y) < 0.01;
|
||||
};
|
||||
|
||||
QVERIFY(compare(apply(0.0, 0.0), 0.00));
|
||||
QVERIFY(compare(apply(1.0, 0.0), 1.00));
|
||||
QVERIFY(compare(apply(0.0, 1.0), 0.50));
|
||||
QVERIFY(compare(apply(1.0, 1.0), 0.75));
|
||||
|
||||
for (pdf::PDFReal x = 0.0; x <= 1.0; x += 0.01)
|
||||
{
|
||||
for (pdf::PDFReal y = 0.0; y <= 1.0; y += 0.01)
|
||||
{
|
||||
const pdf::PDFReal actual = apply(x, y);
|
||||
const pdf::PDFReal expected = bilinear(x, y);
|
||||
QVERIFY(compare(actual, expected));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
const char data[] = " << "
|
||||
" /FunctionType 0 "
|
||||
" /Domain [ 0 1 ] "
|
||||
" /Range [ 0 1 ] "
|
||||
" /Size [ 2 ] "
|
||||
" /BitsPerSample 8 "
|
||||
" /Order 1 "
|
||||
" /Length 2 "
|
||||
" >> "
|
||||
" stream\n\377\000 endstream ";
|
||||
|
||||
pdf::PDFDocument document;
|
||||
pdf::PDFParser parser(data, data + std::size(data), nullptr, pdf::PDFParser::AllowStreams);
|
||||
pdf::PDFFunctionPtr function = pdf::PDFFunction::createFunction(&document, parser.getObject());
|
||||
|
||||
auto apply = [&function](pdf::PDFReal x) -> pdf::PDFReal
|
||||
{
|
||||
pdf::PDFReal output = -1.0;
|
||||
function->apply(&x, &x + 1, &output, &output + 1);
|
||||
return output;
|
||||
};
|
||||
|
||||
auto compare = [&apply](pdf::PDFReal x)
|
||||
{
|
||||
const pdf::PDFReal actual = apply(x);
|
||||
const pdf::PDFReal expected = 1.0 - x;
|
||||
return qFuzzyCompare(actual, expected);
|
||||
};
|
||||
|
||||
for (pdf::PDFReal x = 0.0; x <= 1.0; x += 0.01)
|
||||
{
|
||||
QVERIFY(compare(x));
|
||||
}
|
||||
|
||||
QVERIFY(qFuzzyCompare(apply(-1.0), 1.0));
|
||||
QVERIFY(qFuzzyCompare(apply(2.0), 0.0));
|
||||
}
|
||||
|
||||
// Test invalid inputs
|
||||
QVERIFY_EXCEPTION_THROWN(
|
||||
{
|
||||
const char data[] = " << "
|
||||
" /FunctionType 0 "
|
||||
" /Domain [ 0 1 0 1 ] "
|
||||
" /Range [ 0 1 ] "
|
||||
" /Size [ 2 2 ] "
|
||||
" /BitsPerSample 8 "
|
||||
" /Order 1 "
|
||||
" /Length 2 "
|
||||
" >> "
|
||||
" stream\n\000\377 endstream ";
|
||||
|
||||
pdf::PDFDocument document;
|
||||
pdf::PDFParser parser(data, data + std::size(data), nullptr, pdf::PDFParser::AllowStreams);
|
||||
pdf::PDFFunctionPtr function = pdf::PDFFunction::createFunction(&document, parser.getObject());
|
||||
|
||||
QVERIFY(!function);
|
||||
}, pdf::PDFParserException);
|
||||
|
||||
// Test invalid inputs
|
||||
QVERIFY_EXCEPTION_THROWN(
|
||||
{
|
||||
const char data[] = " << "
|
||||
" /FunctionType 0 "
|
||||
" /Domain [ 0 1 0 1 ] "
|
||||
" /Range [ 0 1 ] "
|
||||
" /Size [ 2 2 ] "
|
||||
" /BitsPerSample -5 "
|
||||
" /Order 1 "
|
||||
" /Length 4 "
|
||||
" >> "
|
||||
" stream\n\000\377\200\300 endstream ";
|
||||
|
||||
pdf::PDFDocument document;
|
||||
pdf::PDFParser parser(data, data + std::size(data), nullptr, pdf::PDFParser::AllowStreams);
|
||||
pdf::PDFFunctionPtr function = pdf::PDFFunction::createFunction(&document, parser.getObject());
|
||||
|
||||
QVERIFY(!function);
|
||||
}, pdf::PDFParserException);
|
||||
|
||||
// Test invalid inputs
|
||||
QVERIFY_EXCEPTION_THROWN(
|
||||
{
|
||||
const char data[] = " << "
|
||||
" /FunctionType 0 "
|
||||
" /Domain [ 0 1 0 ] "
|
||||
" /Range [ 0 1 ] "
|
||||
" /Size [ 2 2 ] "
|
||||
" /BitsPerSample 8 "
|
||||
" /Order 1 "
|
||||
" /Length 4 "
|
||||
" >> "
|
||||
" stream\n\000\377\200\300 endstream ";
|
||||
|
||||
pdf::PDFDocument document;
|
||||
pdf::PDFParser parser(data, data + std::size(data), nullptr, pdf::PDFParser::AllowStreams);
|
||||
pdf::PDFFunctionPtr function = pdf::PDFFunction::createFunction(&document, parser.getObject());
|
||||
|
||||
QVERIFY(!function);
|
||||
}, pdf::PDFParserException);
|
||||
|
||||
// Test invalid inputs
|
||||
QVERIFY_EXCEPTION_THROWN(
|
||||
{
|
||||
const char data[] = " << "
|
||||
" /FunctionType 0 "
|
||||
" /Domain [ 0 1 0 1 ] "
|
||||
" /Range [ 0 ] "
|
||||
" /Size [ 2 2 ] "
|
||||
" /BitsPerSample 8 "
|
||||
" /Order 1 "
|
||||
" /Length 4 "
|
||||
" >> "
|
||||
" stream\n\000\377\200\300 endstream ";
|
||||
|
||||
pdf::PDFDocument document;
|
||||
pdf::PDFParser parser(data, data + std::size(data), nullptr, pdf::PDFParser::AllowStreams);
|
||||
pdf::PDFFunctionPtr function = pdf::PDFFunction::createFunction(&document, parser.getObject());
|
||||
|
||||
QVERIFY(!function);
|
||||
}, pdf::PDFParserException);
|
||||
|
||||
// Test invalid inputs
|
||||
QVERIFY_EXCEPTION_THROWN(
|
||||
{
|
||||
const char data[] = " << "
|
||||
" /FunctionType 0 "
|
||||
" /Domain [ 0 1 0 1 ] "
|
||||
" /Range [ 0 1 ] "
|
||||
" /Size [ 2 ] "
|
||||
" /BitsPerSample 8 "
|
||||
" /Order 1 "
|
||||
" /Length 4 "
|
||||
" >> "
|
||||
" stream\n\000\377\200\300 endstream ";
|
||||
|
||||
pdf::PDFDocument document;
|
||||
pdf::PDFParser parser(data, data + std::size(data), nullptr, pdf::PDFParser::AllowStreams);
|
||||
pdf::PDFFunctionPtr function = pdf::PDFFunction::createFunction(&document, parser.getObject());
|
||||
|
||||
QVERIFY(!function);
|
||||
}, pdf::PDFParserException);
|
||||
|
||||
// Test invalid inputs
|
||||
QVERIFY_EXCEPTION_THROWN(
|
||||
{
|
||||
const char data[] = " << "
|
||||
" /FunctionType 0 "
|
||||
" /Domain [ 0 1 0 1 ] "
|
||||
" /Range [ 0 1 ] "
|
||||
" /Size [ 2 2 ] "
|
||||
" /Encode [ 1 ] "
|
||||
" /BitsPerSample 8 "
|
||||
" /Order 1 "
|
||||
" /Length 4 "
|
||||
" >> "
|
||||
" stream\n\000\377\200\300 endstream ";
|
||||
|
||||
pdf::PDFDocument document;
|
||||
pdf::PDFParser parser(data, data + std::size(data), nullptr, pdf::PDFParser::AllowStreams);
|
||||
pdf::PDFFunctionPtr function = pdf::PDFFunction::createFunction(&document, parser.getObject());
|
||||
|
||||
QVERIFY(!function);
|
||||
}, pdf::PDFParserException);
|
||||
|
||||
// Test invalid inputs
|
||||
QVERIFY_EXCEPTION_THROWN(
|
||||
{
|
||||
const char data[] = " << "
|
||||
" /FunctionType 0 "
|
||||
" /Domain [ 0 1 0 1 ] "
|
||||
" /Range [ 0 1 ] "
|
||||
" /Decode [ 1 ] "
|
||||
" /Size [ 2 2 ] "
|
||||
" /BitsPerSample 8 "
|
||||
" /Order 1 "
|
||||
" /Length 4 "
|
||||
" >> "
|
||||
" stream\n\000\377\200\300 endstream ";
|
||||
|
||||
pdf::PDFDocument document;
|
||||
pdf::PDFParser parser(data, data + std::size(data), nullptr, pdf::PDFParser::AllowStreams);
|
||||
pdf::PDFFunctionPtr function = pdf::PDFFunction::createFunction(&document, parser.getObject());
|
||||
|
||||
QVERIFY(!function);
|
||||
}, pdf::PDFParserException);
|
||||
|
||||
// Test invalid inputs
|
||||
QVERIFY_EXCEPTION_THROWN(
|
||||
{
|
||||
const char data[] = " << "
|
||||
" /FunctionType 0 "
|
||||
" /Domain [ 0 1 0 1 ] "
|
||||
" /Size [ 2 2 ] "
|
||||
" /BitsPerSample 8 "
|
||||
" /Order 1 "
|
||||
" /Length 4 "
|
||||
" >> "
|
||||
" stream\n\000\377\200\300 endstream ";
|
||||
|
||||
pdf::PDFDocument document;
|
||||
pdf::PDFParser parser(data, data + std::size(data), nullptr, pdf::PDFParser::AllowStreams);
|
||||
pdf::PDFFunctionPtr function = pdf::PDFFunction::createFunction(&document, parser.getObject());
|
||||
|
||||
QVERIFY(!function);
|
||||
}, pdf::PDFParserException);
|
||||
|
||||
// Test invalid inputs
|
||||
QVERIFY_EXCEPTION_THROWN(
|
||||
{
|
||||
const char data[] = " << "
|
||||
" /FunctionType 0 "
|
||||
" /Range [ 0 1 ] "
|
||||
" /Size [ 2 2 ] "
|
||||
" /BitsPerSample 8 "
|
||||
" /Order 1 "
|
||||
" /Length 4 "
|
||||
" >> "
|
||||
" stream\n\000\377\200\300 endstream ";
|
||||
|
||||
pdf::PDFDocument document;
|
||||
pdf::PDFParser parser(data, data + std::size(data), nullptr, pdf::PDFParser::AllowStreams);
|
||||
pdf::PDFFunctionPtr function = pdf::PDFFunction::createFunction(&document, parser.getObject());
|
||||
|
||||
QVERIFY(!function);
|
||||
}, pdf::PDFParserException);
|
||||
}
|
||||
|
||||
void LexicalAnalyzerTest::test_exponential_function()
|
||||
{
|
||||
{
|
||||
QByteArray data = " << "
|
||||
" /FunctionType 2 "
|
||||
" /Domain [ 0 2 ] "
|
||||
" /Range [ 0 2 ] "
|
||||
" /N 1.0 "
|
||||
" >> ";
|
||||
|
||||
pdf::PDFDocument document;
|
||||
pdf::PDFParser parser(data, nullptr, pdf::PDFParser::None);
|
||||
pdf::PDFFunctionPtr function = pdf::PDFFunction::createFunction(&document, parser.getObject());
|
||||
|
||||
QVERIFY(function);
|
||||
for (double value = -1.0; value <= 3.0; value += 0.01)
|
||||
{
|
||||
const double expected = qBound(0.0, value, 2.0);
|
||||
|
||||
double actual = 0.0;
|
||||
QVERIFY(function->apply(&value, &value + 1, &actual, &actual + 1));
|
||||
QVERIFY(qFuzzyCompare(expected, actual));
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
QByteArray data = " << "
|
||||
" /FunctionType 2 "
|
||||
" /Domain [ 0 2 ] "
|
||||
" /Range [ 0 4 ] "
|
||||
" /N 2.0 "
|
||||
" >> ";
|
||||
|
||||
pdf::PDFDocument document;
|
||||
pdf::PDFParser parser(data, nullptr, pdf::PDFParser::None);
|
||||
pdf::PDFFunctionPtr function = pdf::PDFFunction::createFunction(&document, parser.getObject());
|
||||
|
||||
QVERIFY(function);
|
||||
for (double value = -1.0; value <= 3.0; value += 0.01)
|
||||
{
|
||||
const double expected = std::pow(qBound(0.0, value, 2.0), 2.0);
|
||||
|
||||
double actual = 0.0;
|
||||
QVERIFY(function->apply(&value, &value + 1, &actual, &actual + 1));
|
||||
QVERIFY(qFuzzyCompare(expected, actual));
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
QByteArray data = " << "
|
||||
" /FunctionType 2 "
|
||||
" /Domain [ 0 2 ] "
|
||||
" /Range [ -4 4 ] "
|
||||
" /C0 [ 1.0 ] "
|
||||
" /C1 [ 0.0 ] "
|
||||
" /N 2.0 "
|
||||
" >> ";
|
||||
|
||||
pdf::PDFDocument document;
|
||||
pdf::PDFParser parser(data, nullptr, pdf::PDFParser::None);
|
||||
pdf::PDFFunctionPtr function = pdf::PDFFunction::createFunction(&document, parser.getObject());
|
||||
|
||||
QVERIFY(function);
|
||||
for (double value = -1.0; value <= 3.0; value += 0.01)
|
||||
{
|
||||
const double expected = qBound(-4.0, 1.0 - std::pow(qBound(0.0, value, 2.0), 2.0), 4.0);
|
||||
|
||||
double actual = 0.0;
|
||||
QVERIFY(function->apply(&value, &value + 1, &actual, &actual + 1));
|
||||
QVERIFY(qFuzzyCompare(expected, actual));
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
QByteArray data = " << "
|
||||
" /FunctionType 2 "
|
||||
" /Domain [ 0 2 ] "
|
||||
" /Range [ 0 4 -4 4 ] "
|
||||
" /C0 [ 0.0 1.0 ] "
|
||||
" /C1 [ 1.0 0.0 ] "
|
||||
" /N 2.0 "
|
||||
" >> ";
|
||||
|
||||
pdf::PDFDocument document;
|
||||
pdf::PDFParser parser(data, nullptr, pdf::PDFParser::None);
|
||||
pdf::PDFFunctionPtr function = pdf::PDFFunction::createFunction(&document, parser.getObject());
|
||||
|
||||
QVERIFY(function);
|
||||
for (double value = -1.0; value <= 3.0; value += 0.01)
|
||||
{
|
||||
const double expected1 = std::pow(qBound(0.0, value, 2.0), 2.0);
|
||||
const double expected2 = qBound(-4.0, 1.0 - std::pow(qBound(0.0, value, 2.0), 2.0), 4.0);
|
||||
|
||||
double actual[2] = { };
|
||||
QVERIFY(function->apply(&value, &value + 1, actual, actual + std::size(actual)));
|
||||
QVERIFY(qFuzzyCompare(expected1, actual[0]));
|
||||
QVERIFY(qFuzzyCompare(expected2, actual[1]));
|
||||
}
|
||||
}
|
||||
|
||||
// Test invalid inputs
|
||||
QVERIFY_EXCEPTION_THROWN(
|
||||
{
|
||||
QByteArray data = " << "
|
||||
" /FunctionType 2 "
|
||||
" /Domain [ 0 ] "
|
||||
" /Range [ 0 2 ] "
|
||||
" /N 1.0 "
|
||||
" >> ";
|
||||
|
||||
pdf::PDFDocument document;
|
||||
pdf::PDFParser parser(data, nullptr, pdf::PDFParser::None);
|
||||
pdf::PDFFunctionPtr function = pdf::PDFFunction::createFunction(&document, parser.getObject());
|
||||
|
||||
QVERIFY(!function);
|
||||
}, pdf::PDFParserException);
|
||||
|
||||
QVERIFY_EXCEPTION_THROWN(
|
||||
{
|
||||
QByteArray data = " << "
|
||||
" /FunctionType 2 "
|
||||
" /Domain [ -1 2 ] "
|
||||
" /N -1.0 "
|
||||
" >> ";
|
||||
|
||||
pdf::PDFDocument document;
|
||||
pdf::PDFParser parser(data, nullptr, pdf::PDFParser::None);
|
||||
pdf::PDFFunctionPtr function = pdf::PDFFunction::createFunction(&document, parser.getObject());
|
||||
|
||||
QVERIFY(!function);
|
||||
}, pdf::PDFParserException);
|
||||
|
||||
QVERIFY_EXCEPTION_THROWN(
|
||||
{
|
||||
QByteArray data = " << "
|
||||
" /FunctionType 2 "
|
||||
" /Domain [ 0 2 ] "
|
||||
" /N -1.0 "
|
||||
" >> ";
|
||||
|
||||
pdf::PDFDocument document;
|
||||
pdf::PDFParser parser(data, nullptr, pdf::PDFParser::None);
|
||||
pdf::PDFFunctionPtr function = pdf::PDFFunction::createFunction(&document, parser.getObject());
|
||||
|
||||
QVERIFY(!function);
|
||||
}, pdf::PDFParserException);
|
||||
|
||||
QVERIFY_EXCEPTION_THROWN(
|
||||
{
|
||||
QByteArray data = " << "
|
||||
" /FunctionType 2 "
|
||||
" /Domain [ -1 2 ] "
|
||||
" /N 3.4 "
|
||||
" >> ";
|
||||
|
||||
pdf::PDFDocument document;
|
||||
pdf::PDFParser parser(data, nullptr, pdf::PDFParser::None);
|
||||
pdf::PDFFunctionPtr function = pdf::PDFFunction::createFunction(&document, parser.getObject());
|
||||
|
||||
QVERIFY(!function);
|
||||
}, pdf::PDFParserException);
|
||||
|
||||
QVERIFY_EXCEPTION_THROWN(
|
||||
{
|
||||
QByteArray data = " << "
|
||||
" /FunctionType 2 "
|
||||
" /Domain [ 0 2 2 0] "
|
||||
" /Range [ 0 4 -4 4 ] "
|
||||
" /C0 [ 0.0 1.0 ] "
|
||||
" /C1 [ 1.0 0.0 ] "
|
||||
" /N 2.0 "
|
||||
" >> ";
|
||||
|
||||
pdf::PDFDocument document;
|
||||
pdf::PDFParser parser(data, nullptr, pdf::PDFParser::None);
|
||||
pdf::PDFFunctionPtr function = pdf::PDFFunction::createFunction(&document, parser.getObject());
|
||||
|
||||
QVERIFY(!function);
|
||||
}, pdf::PDFParserException);
|
||||
|
||||
QVERIFY_EXCEPTION_THROWN(
|
||||
{
|
||||
QByteArray data = " << "
|
||||
" /FunctionType 2 "
|
||||
" /Domain [ 0 2 ] "
|
||||
" /C0 [ 0.0 1.0 3.0 ] "
|
||||
" /C1 [ 1.0 0.0 ] "
|
||||
" /N 2.0 "
|
||||
" >> ";
|
||||
|
||||
pdf::PDFDocument document;
|
||||
pdf::PDFParser parser(data, nullptr, pdf::PDFParser::None);
|
||||
pdf::PDFFunctionPtr function = pdf::PDFFunction::createFunction(&document, parser.getObject());
|
||||
|
||||
QVERIFY(!function);
|
||||
}, pdf::PDFParserException);
|
||||
|
||||
QVERIFY_EXCEPTION_THROWN(
|
||||
{
|
||||
QByteArray data = " << "
|
||||
" /FunctionType 2 "
|
||||
" /Domain [ 0 2 ] "
|
||||
" /C0 [ 0.0 ] "
|
||||
" /C1 [ 1.0 0.0 ] "
|
||||
" /N 2.0 "
|
||||
" >> ";
|
||||
|
||||
pdf::PDFDocument document;
|
||||
pdf::PDFParser parser(data, nullptr, pdf::PDFParser::None);
|
||||
pdf::PDFFunctionPtr function = pdf::PDFFunction::createFunction(&document, parser.getObject());
|
||||
|
||||
QVERIFY(!function);
|
||||
}, pdf::PDFParserException);
|
||||
|
||||
QVERIFY_EXCEPTION_THROWN(
|
||||
{
|
||||
QByteArray data = " << "
|
||||
" /FunctionType 2 "
|
||||
" /Domain /Something "
|
||||
" /C0 [ 0.0 ] "
|
||||
" /C1 [ 1.0 0.0 ] "
|
||||
" /N 2.0 "
|
||||
" >> ";
|
||||
|
||||
pdf::PDFDocument document;
|
||||
pdf::PDFParser parser(data, nullptr, pdf::PDFParser::None);
|
||||
pdf::PDFFunctionPtr function = pdf::PDFFunction::createFunction(&document, parser.getObject());
|
||||
|
||||
QVERIFY(!function);
|
||||
}, pdf::PDFParserException);
|
||||
}
|
||||
|
||||
void LexicalAnalyzerTest::scanWholeStream(const char* stream)
|
||||
|
|
Loading…
Reference in New Issue