Finishing of PostScript functions

This commit is contained in:
Jakub Melka 2019-03-16 19:09:10 +01:00
parent 0f0cd575d4
commit 60dbd0c65f
3 changed files with 189 additions and 16 deletions

View File

@ -21,6 +21,7 @@
#include "pdfdocument.h"
#include <stack>
#include <iterator>
#include <type_traits>
namespace pdf
@ -203,7 +204,7 @@ PDFFunctionPtr PDFFunction::createFunctionImpl(const PDFDocument* document, cons
throw PDFParserException(PDFParsingContext::tr("Invalid domain of exponential function."));
}
const uint32_t m = 1;
constexpr 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() }));
@ -227,7 +228,7 @@ PDFFunctionPtr PDFFunction::createFunctionImpl(const PDFDocument* document, cons
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);
return std::make_shared<PDFExponentialFunction>(m, n, std::move(domain), std::move(range), std::move(c0), std::move(c1), exponent);
}
case 3:
{
@ -304,6 +305,17 @@ PDFFunctionPtr PDFFunction::createFunctionImpl(const PDFDocument* document, cons
case 4:
{
// Postscript function
PDFPostScriptFunction::Program program = PDFPostScriptFunction::parseProgram(streamData);
const uint32_t m = static_cast<uint32_t>(domain.size()) / 2;
const uint32_t n = static_cast<uint32_t>(range.size()) / 2;
if (program.empty())
{
throw PDFParserException(PDFParsingContext::tr("Empty program in PostScript function."));
}
return std::make_shared<PDFPostScriptFunction>(m, n, std::move(domain), std::move(range), std::move(program));
}
default:
@ -718,6 +730,12 @@ public:
/// Pushes the operand onto the stack
void push(const OperandObject& operand) { m_stack.push_back(operand); checkOverflow(); }
/// Returns true, if stack is empty
bool empty() const { return m_stack.empty(); }
/// Returns size of the stack
std::size_t size() const { return m_stack.size(); }
private:
/// Check operand stack overflow (maximum limit is 100, according to the PDF 1.7 specification)
void checkOverflow() const;
@ -762,8 +780,8 @@ private:
}
else
{
const PDFReal b = m_stack.popReal();
const PDFReal a = m_stack.popReal();
const PDFReal b = m_stack.popNumber();
const PDFReal a = m_stack.popNumber();
m_stack.pushBoolean(Comparator<PDFReal>()(a, b));
}
}
@ -799,8 +817,8 @@ void PDFPostScriptFunctionExecutor::execute()
}
else
{
const PDFReal b = m_stack.popReal();
const PDFReal a = m_stack.popReal();
const PDFReal b = m_stack.popNumber();
const PDFReal a = m_stack.popNumber();
m_stack.pushReal(a + b);
}
break;
@ -816,8 +834,8 @@ void PDFPostScriptFunctionExecutor::execute()
}
else
{
const PDFReal b = m_stack.popReal();
const PDFReal a = m_stack.popReal();
const PDFReal b = m_stack.popNumber();
const PDFReal a = m_stack.popNumber();
m_stack.pushReal(a - b);
}
break;
@ -833,8 +851,8 @@ void PDFPostScriptFunctionExecutor::execute()
}
else
{
const PDFReal b = m_stack.popReal();
const PDFReal a = m_stack.popReal();
const PDFReal b = m_stack.popNumber();
const PDFReal a = m_stack.popNumber();
m_stack.pushReal(a * b);
}
break;
@ -1072,8 +1090,8 @@ void PDFPostScriptFunctionExecutor::execute()
else
{
// Real values
const PDFReal b = m_stack.popReal();
const PDFReal a = m_stack.popReal();
const PDFReal b = m_stack.popNumber();
const PDFReal a = m_stack.popNumber();
m_stack.pushBoolean(a == b);
}
@ -1097,8 +1115,8 @@ void PDFPostScriptFunctionExecutor::execute()
else
{
// Real values
const PDFReal b = m_stack.popReal();
const PDFReal a = m_stack.popReal();
const PDFReal b = m_stack.popNumber();
const PDFReal a = m_stack.popNumber();
m_stack.pushBoolean(a != b);
}
@ -1622,6 +1640,18 @@ PDFPostScriptFunction::Code PDFPostScriptFunction::getCode(const QByteArray& byt
throw PDFParserException(PDFTranslationContext::tr("Invalid operator (PostScript function) '%1'.").arg(QString::fromLatin1(byteArray)));
}
PDFPostScriptFunction::PDFPostScriptFunction(uint32_t m, uint32_t n, std::vector<PDFReal>&& domain, std::vector<PDFReal>&& range, PDFPostScriptFunction::Program&& program) :
PDFFunction(m, n, std::move(domain), std::move(range)),
m_program(std::move(program))
{
Q_ASSERT(!m_program.empty());
}
PDFPostScriptFunction::~PDFPostScriptFunction()
{
}
PDFPostScriptFunction::Program PDFPostScriptFunction::parseProgram(const QByteArray& byteArray)
{
Program result;
@ -1653,7 +1683,7 @@ PDFPostScriptFunction::Program PDFPostScriptFunction::parseProgram(const QByteAr
case PDFLexicalAnalyzer::TokenType::Real:
{
result.emplace_back(OperandObject::createInteger(token.data.toDouble()), result.size() + 1);
result.emplace_back(OperandObject::createReal(token.data.toDouble()), result.size() + 1);
break;
}
@ -1706,4 +1736,56 @@ PDFPostScriptFunction::Program PDFPostScriptFunction::parseProgram(const QByteAr
return result;
}
PDFFunction::FunctionResult PDFPostScriptFunction::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 != m_m)
{
return PDFTranslationContext::tr("Invalid number of operands for function. Expected %1, provided %2.").arg(m_m).arg(m);
}
if (n != m_n)
{
return PDFTranslationContext::tr("Invalid number of output variables for function. Expected %1, provided %2.").arg(m_n).arg(n);
}
try
{
PDFPostScriptFunctionStack stack;
// Insert input values
for (uint32_t i = 0; i < m; ++i)
{
const PDFReal x = *std::next(x_1, i);
const PDFReal xClamped = clampInput(i, x);
stack.pushReal(xClamped);
}
PDFPostScriptFunctionExecutor executor(m_program, stack);
executor.execute();
uint32_t i = static_cast<uint32_t>(n);
auto it = std::make_reverse_iterator(y_n);
auto itEnd = std::make_reverse_iterator(y_1);
for (; it != itEnd; ++it)
{
const PDFReal y = stack.popReal();
const PDFReal yClamped = clampOutput(--i, y);
*it = yClamped;
}
if (!stack.empty())
{
return PDFTranslationContext::tr("Stack contains more values, than output size (%1 remains) (PostScript function).").arg(stack.size());
}
}
catch (PDFPostScriptFunction::PDFPostScriptFunctionException exception)
{
return exception.getMessage();
}
return true;
}
} // namespace pdf

View File

@ -451,8 +451,24 @@ public:
using Program = std::vector<CodeObject>;
/// Construct new postscript function.
/// \param m Number of input variables
/// \param n Number of output variables
/// \param domain Array of 2 x m variables of input range - [x1 min, x1 max, x2 min, x2 max, ... ]
/// \param range Array of 2 x n variables of output range - [y1 min, y1 max, y2 min, y2 max, ... ]
explicit PDFPostScriptFunction(uint32_t m, uint32_t n, std::vector<PDFReal>&& domain, std::vector<PDFReal>&& range, Program&& program);
virtual ~PDFPostScriptFunction() override;
/// Create a PostScript program from the byte array
static Program parseProgram(const QByteArray& byteArray);
/// 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;
private:
Program m_program;

View File

@ -51,6 +51,7 @@ private slots:
void test_sampled_function();
void test_exponential_function();
void test_stitching_function();
void test_postscript_function();
private:
void scanWholeStream(const char* stream);
@ -954,7 +955,81 @@ void LexicalAnalyzerTest::test_stitching_function()
pdf::PDFFunctionPtr function = pdf::PDFFunction::createFunction(&document, parser.getObject());
QVERIFY(!function);
}, pdf::PDFParserException);
}, pdf::PDFParserException);
}
void LexicalAnalyzerTest::test_postscript_function()
{
auto makeStream = [](pdf::PDFReal xMin, pdf::PDFReal xMax, pdf::PDFReal yMin, pdf::PDFReal yMax, const char* stream) -> QByteArray
{
QByteArray result;
QDataStream dataStream(&result, QIODevice::WriteOnly);
QByteArray dictionaryData = (QString(" << /FunctionType 4 ") +
QString(" /Domain [ %1 %2 ] ").arg(xMin).arg(xMax) +
QString(" /Range [ %1 %2 ] ").arg(yMin).arg(yMax) +
QString(" /Length %1 ").arg(std::strlen(stream)) +
QString(">> stream\n")).toLocal8Bit();
QByteArray remainder = " endstream";
dataStream.writeRawData(dictionaryData.constBegin(), dictionaryData.size());
dataStream.writeRawData(stream, int(std::strlen(stream)));
dataStream.writeRawData(remainder, remainder.size());
return result;
};
auto test01 = [&](const char* program, auto verifyFunction)
{
QByteArray data = makeStream(0, 1, 0, 1, program);
pdf::PDFDocument document;
pdf::PDFParser parser(data, nullptr, pdf::PDFParser::AllowStreams);
pdf::PDFFunctionPtr function = pdf::PDFFunction::createFunction(&document, parser.getObject());
QVERIFY(function);
for (double value = -1.0; value <= 3.0; value += 0.01)
{
const double clampedValue = qBound(0.0, value, 1.0);
const double expected = verifyFunction(clampedValue);
double actual = 0.0;
pdf::PDFFunction::FunctionResult functionResult = function->apply(&value, &value + 1, &actual, &actual + 1);
if (!functionResult)
{
qInfo() << qPrintable(QString("Program: %1").arg(QString::fromLatin1(program)));
qInfo() << qPrintable(QString("Program execution failed: %1").arg(functionResult.errorMessage));
QVERIFY(false);
}
else
{
bool isSame = std::abs(expected - actual) < 1e-10;
if (!isSame)
{
qInfo() << qPrintable(QString("Program: %1").arg(QString::fromLatin1(program)));
qInfo() << qPrintable(QString(" Expected: %1, Actual: %2, input value was: %3").arg(expected).arg(actual).arg(value));
}
QVERIFY(isSame);
}
}
};
test01("dup mul", [](double x) { return x * x; });
test01("1.0 exch sub", [](double x) { return 1.0 - x; });
test01("dup add", [](double x) { return qBound(0.0, x + x, 1.0); });
test01("dup 1.0 add div", [](double x) { return x / (1.0 + x); });
test01("100.0 mul cvi 10 idiv cvr 10.0 div", [](double x) { return static_cast<double>(static_cast<int>(x * 100.0) / 10) / 10.0; });
test01("100.0 mul cvi 2 mod cvr 0.5 mul", [](double x) { return (static_cast<int>(x * 100.0) % 2) * 0.5; });
test01("neg 1.0 exch add", [](double x) { return 1.0 - x; });
test01("neg 0.5 add abs", [](double x) { return std::abs(0.5 - x); });
test01("10.0 mul ceiling 10.0 div", [](double x) { return std::ceil(10.0 * x) / 10.0; });
test01("10.0 mul floor 10.0 div", [](double x) { return std::floor(10.0 * x) / 10.0; });
test01("10.0 mul round 10.0 div", [](double x) { return std::round(10.0 * x) / 10.0; });
test01("10.0 mul truncate 10.0 div", [](double x) { return std::trunc(10.0 * x) / 10.0; });
test01("sqrt", [](double x) { return std::sqrt(x); });
test01("360.0 mul sin 2 div 0.5 add", [](double x) { return std::sin(qDegreesToRadians(360.0 * x)) / 2.0 + 0.5; });
test01("360.0 mul cos 2 div 0.5 add", [](double x) { return std::cos(qDegreesToRadians(360.0 * x)) / 2.0 + 0.5; });
}
void LexicalAnalyzerTest::scanWholeStream(const char* stream)