Advanced functions

This commit is contained in:
Jakub Melka 2019-03-07 19:57:03 +01:00
parent 2023e17c4c
commit 234b7c77f7
6 changed files with 901 additions and 32 deletions

View File

@ -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

View File

@ -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;
};

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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)