mirror of https://github.com/JakubMelka/PDF4QT.git
Finishing of PostScript functions
This commit is contained in:
parent
0f0cd575d4
commit
60dbd0c65f
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue