diff --git a/PdfForQtLib/sources/pdfflatarray.h b/PdfForQtLib/sources/pdfflatarray.h index 298d039..2580e17 100644 --- a/PdfForQtLib/sources/pdfflatarray.h +++ b/PdfForQtLib/sources/pdfflatarray.h @@ -150,6 +150,12 @@ public: } } + /// Returns the last element of the array + inline const T& back() const { return m_variableBlock.empty() ? m_flatBlock[m_flatBlockItemCount - 1] : m_variableBlock.back(); } + + /// Erases the last element from the array + inline void pop_back() { resize(size() - 1); } + private: size_t getFlatBlockSize() const { return m_flatBlockItemCount; } diff --git a/PdfForQtLib/sources/pdffunction.cpp b/PdfForQtLib/sources/pdffunction.cpp index 219a899..81a35a5 100644 --- a/PdfForQtLib/sources/pdffunction.cpp +++ b/PdfForQtLib/sources/pdffunction.cpp @@ -20,6 +20,9 @@ #include "pdfparser.h" #include "pdfdocument.h" +#include +#include + namespace pdf { @@ -643,4 +646,914 @@ PDFFunction::FunctionResult PDFIdentityFunction::apply(const_iterator x_1, return true; } +class PDFPostScriptFunctionStack +{ +public: + inline explicit PDFPostScriptFunctionStack() = default; + + using OperandObject = PDFPostScriptFunction::OperandObject; + using InstructionPointer = PDFPostScriptFunction::InstructionPointer; + + inline void pushReal(PDFReal value) { m_stack.push_back(OperandObject::createReal(value)); checkOverflow(); } + inline void pushInteger(PDFInteger value) { m_stack.push_back(OperandObject::createInteger(value)); checkOverflow(); } + inline void pushBoolean(bool value) { m_stack.push_back(OperandObject::createBoolean(value)); checkOverflow(); } + inline void pushInstructionPointer(InstructionPointer value) { m_stack.push_back(OperandObject::createInstructionPointer(value)); checkOverflow(); } + + /// Returns true, if integer operation should be performed instead of operation with real values. + /// (two top elements are integer). + bool isBinaryOperationInteger() const; + + /// Returns true, if boolean operation should be performed instead of operation with integer values. + /// (two top elements are boolean). + bool isBinaryOperationBoolean() const; + + /// Pops the real value from the stack (throw exception, if stack underflow occurs, + /// or value is not of type real). + PDFReal popReal(); + + /// Pops the integer value from the stack (throw exception, if stack underflow occurs, + /// or value is not of type integer). + PDFInteger popInteger(); + + /// Pops the boolean value from the stack (throw exception, if stack underflow occurs, + /// or value is not of type boolean). + bool popBoolean(); + + /// Pops the instruction pointer from the stack (throw exception, if stack underflow occurs, + /// or value is not of type instruction pointer). + InstructionPointer popInstructionPointer(); + + /// Pops number (integer is converted to the real value) form the stack (throw exception, if stack underflow occurs, + /// or value is not of type real or integer). + PDFReal popNumber(); + + /// Returns true, if current value is real + bool isReal() const { checkUnderflow(); return m_stack.back().type == PDFPostScriptFunction::OperandType::Real; } + + /// Returns true, if current value is integer + bool isInteger() const { checkUnderflow(); return m_stack.back().type == PDFPostScriptFunction::OperandType::Integer; } + + /// Pops the current value + inline void pop() { checkUnderflow(); m_stack.pop_back(); } + + /// Exchange the two top elements + void exch(); + + /// Duplicate the top element + void dup(); + + /// Copy the n elements + /// \param n Number of elements to be copied + void copy(PDFInteger n); + + /// Copy the n-th element on the stack + /// \param n Index of the element (indexed is from the top - top has index 0, bottom has index size() - 1) + void index(PDFInteger n); + + /// Roll n elements on the stack j-times left + /// \param n Number of elements to be rolled + /// \param j Roll j-times + void roll(PDFInteger n, PDFInteger j); + + /// Pushes the operand onto the stack + void push(const OperandObject& operand) { m_stack.push_back(operand); checkOverflow(); } + +private: + /// Check operand stack overflow (maximum limit is 100, according to the PDF 1.7 specification) + void checkOverflow() const; + + /// Check operand stack underflow (if stack has at least \p n values) + /// \param n Number of values to check + void checkUnderflow(size_t n = 1) const; + + PDFFlatArray m_stack; +}; + +/// Executes the postscript program. Can throw PDFPostScriptFunctionException. +class PDFPostScriptFunctionExecutor +{ +public: + using Program = PDFPostScriptFunction::Program; + using Stack = PDFPostScriptFunctionStack; + using InstructionPointer = PDFPostScriptFunction::InstructionPointer; + using CodeObject = PDFPostScriptFunction::CodeObject; + using PDFIntegerUnsigned = std::make_unsigned::type; + + /// Creates new postscript program + explicit inline PDFPostScriptFunctionExecutor(const Program& program, Stack& stack) : + m_program(program), + m_stack(stack) + { + + } + + /// Executes the postscript program + void execute(); + +private: + template typename Comparator> + void executeRelationOperator() + { + if (m_stack.isBinaryOperationInteger()) + { + const PDFInteger b = m_stack.popInteger(); + const PDFInteger a = m_stack.popInteger(); + m_stack.pushBoolean(Comparator()(a, b)); + } + else + { + const PDFReal b = m_stack.popReal(); + const PDFReal a = m_stack.popReal(); + m_stack.pushBoolean(Comparator()(a, b)); + } + } + + const Program& m_program; + Stack& m_stack; +}; + +void PDFPostScriptFunctionExecutor::execute() +{ + Q_ASSERT(!m_program.empty()); + + std::stack callStack; + + InstructionPointer ip = 0; // First instruction is at zero + while (ip != PDFPostScriptFunction::INVALID_INSTRUCTION_POINTER) + { + if (ip >= m_program.size()) + { + throw PDFPostScriptFunction::PDFPostScriptFunctionException(PDFTranslationContext::tr("Invalid instruction pointer.")); + } + + const CodeObject& instruction = m_program[ip]; + switch (instruction.code) + { + case PDFPostScriptFunction::Code::Add: + { + if (m_stack.isBinaryOperationInteger()) + { + const PDFInteger b = m_stack.popInteger(); + const PDFInteger a = m_stack.popInteger(); + m_stack.pushInteger(a + b); + } + else + { + const PDFReal b = m_stack.popReal(); + const PDFReal a = m_stack.popReal(); + m_stack.pushReal(a + b); + } + break; + } + + case PDFPostScriptFunction::Code::Sub: + { + if (m_stack.isBinaryOperationInteger()) + { + const PDFInteger b = m_stack.popInteger(); + const PDFInteger a = m_stack.popInteger(); + m_stack.pushInteger(a - b); + } + else + { + const PDFReal b = m_stack.popReal(); + const PDFReal a = m_stack.popReal(); + m_stack.pushReal(a - b); + } + break; + } + + case PDFPostScriptFunction::Code::Mul: + { + if (m_stack.isBinaryOperationInteger()) + { + const PDFInteger b = m_stack.popInteger(); + const PDFInteger a = m_stack.popInteger(); + m_stack.pushInteger(a * b); + } + else + { + const PDFReal b = m_stack.popReal(); + const PDFReal a = m_stack.popReal(); + m_stack.pushReal(a * b); + } + break; + } + + case PDFPostScriptFunction::Code::Div: + { + const PDFReal b = m_stack.popNumber(); + const PDFReal a = m_stack.popNumber(); + + if (qFuzzyIsNull(b)) + { + throw PDFPostScriptFunction::PDFPostScriptFunctionException(PDFTranslationContext::tr("Division by zero (PostScript engine).")); + } + + m_stack.pushReal(a / b); + break; + } + + case PDFPostScriptFunction::Code::Idiv: + { + const PDFInteger b = m_stack.popInteger(); + const PDFInteger a = m_stack.popInteger(); + + if (b == 0) + { + throw PDFPostScriptFunction::PDFPostScriptFunctionException(PDFTranslationContext::tr("Division by zero (PostScript engine).")); + } + + m_stack.pushInteger(a / b); + break; + } + + case PDFPostScriptFunction::Code::Mod: + { + const PDFInteger b = m_stack.popInteger(); + const PDFInteger a = m_stack.popInteger(); + + if (b == 0) + { + throw PDFPostScriptFunction::PDFPostScriptFunctionException(PDFTranslationContext::tr("Division by zero (PostScript engine).")); + } + + m_stack.pushInteger(a % b); + break; + } + + case PDFPostScriptFunction::Code::Neg: + { + if (m_stack.isInteger()) + { + m_stack.pushInteger(-m_stack.popInteger()); + } + else + { + m_stack.pushReal(-m_stack.popReal()); + } + break; + } + + case PDFPostScriptFunction::Code::Abs: + { + if (m_stack.isInteger()) + { + m_stack.pushInteger(qAbs(m_stack.popInteger())); + } + else + { + m_stack.pushReal(qAbs(m_stack.popReal())); + } + break; + } + + case PDFPostScriptFunction::Code::Ceiling: + { + if (m_stack.isReal()) + { + m_stack.pushReal(std::ceil(m_stack.popReal())); + } + else if (!m_stack.isInteger()) + { + throw PDFPostScriptFunction::PDFPostScriptFunctionException(PDFTranslationContext::tr("Number expected for ceil function (PostScript engine).")); + } + break; + } + + case PDFPostScriptFunction::Code::Floor: + { + if (m_stack.isReal()) + { + m_stack.pushReal(std::floor(m_stack.popReal())); + } + else if (!m_stack.isInteger()) + { + throw PDFPostScriptFunction::PDFPostScriptFunctionException(PDFTranslationContext::tr("Number expected for floor function (PostScript engine).")); + } + break; + } + + case PDFPostScriptFunction::Code::Round: + { + if (m_stack.isReal()) + { + m_stack.pushReal(qRound(m_stack.popReal())); + } + else if (!m_stack.isInteger()) + { + throw PDFPostScriptFunction::PDFPostScriptFunctionException(PDFTranslationContext::tr("Number expected for round function (PostScript engine).")); + } + break; + } + + case PDFPostScriptFunction::Code::Truncate: + { + if (m_stack.isReal()) + { + m_stack.pushReal(std::trunc(m_stack.popReal())); + } + else if (!m_stack.isInteger()) + { + throw PDFPostScriptFunction::PDFPostScriptFunctionException(PDFTranslationContext::tr("Number expected for truncate function (PostScript engine).")); + } + break; + } + + case PDFPostScriptFunction::Code::Sqrt: + { + const PDFReal value = m_stack.popNumber(); + + if (value < 0.0) + { + throw PDFPostScriptFunction::PDFPostScriptFunctionException(PDFTranslationContext::tr("Square root of negative value can't be computed (PostScript engine).")); + } + + m_stack.pushReal(std::sqrt(value)); + break; + } + + case PDFPostScriptFunction::Code::Sin: + { + m_stack.pushReal(qSin(qDegreesToRadians(m_stack.popNumber()))); + break; + } + + case PDFPostScriptFunction::Code::Cos: + { + m_stack.pushReal(qCos(qDegreesToRadians(m_stack.popNumber()))); + break; + } + + case PDFPostScriptFunction::Code::Atan: + { + const PDFReal b = m_stack.popNumber(); + const PDFReal a = m_stack.popNumber(); + + const PDFReal angles = qRadiansToDegrees(qAtan2(a, b)); + m_stack.pushReal(angles < 0.0 ? (angles + 360.0) : angles); + break; + } + + case PDFPostScriptFunction::Code::Exp: + { + const PDFReal exponent = m_stack.popNumber(); + const PDFReal base = m_stack.popNumber(); + m_stack.pushReal(qPow(base, exponent)); + break; + } + + case PDFPostScriptFunction::Code::Ln: + { + const PDFReal value = m_stack.popNumber(); + + if (value < 0.0 || qFuzzyIsNull(value)) + { + throw PDFPostScriptFunction::PDFPostScriptFunctionException(PDFTranslationContext::tr("Logarithm's input should be positive value (PostScript engine).")); + } + + m_stack.pushReal(qLn(value)); + break; + } + + case PDFPostScriptFunction::Code::Log: + { + const PDFReal value = m_stack.popNumber(); + + if (value < 0.0 || qFuzzyIsNull(value)) + { + throw PDFPostScriptFunction::PDFPostScriptFunctionException(PDFTranslationContext::tr("Logarithm's input should be positive value (PostScript engine).")); + } + + m_stack.pushReal(std::log10(value)); + break; + } + + case PDFPostScriptFunction::Code::Cvi: + { + if (m_stack.isReal()) + { + m_stack.pushInteger(static_cast(m_stack.popReal())); + } + else if (!m_stack.isInteger()) + { + throw PDFPostScriptFunction::PDFPostScriptFunctionException(PDFTranslationContext::tr("Real value expected for conversion to integer (PostScript engine).")); + } + break; + } + + case PDFPostScriptFunction::Code::Cvr: + { + if (m_stack.isInteger()) + { + m_stack.pushReal(m_stack.popInteger()); + } + else if (!m_stack.isReal()) + { + throw PDFPostScriptFunction::PDFPostScriptFunctionException(PDFTranslationContext::tr("Integer value expected for conversion to real (PostScript engine).")); + } + break; + } + + case PDFPostScriptFunction::Code::Eq: + { + if (m_stack.isBinaryOperationInteger()) + { + const PDFInteger b = m_stack.popInteger(); + const PDFInteger a = m_stack.popInteger(); + m_stack.pushBoolean(a == b); + } + else if (m_stack.isBinaryOperationBoolean()) + { + const bool b = m_stack.popBoolean(); + const bool a = m_stack.popBoolean(); + m_stack.pushBoolean(a == b); + } + else + { + // Real values + const PDFReal b = m_stack.popReal(); + const PDFReal a = m_stack.popReal(); + m_stack.pushBoolean(a == b); + } + + break; + } + + case PDFPostScriptFunction::Code::Ne: + { + if (m_stack.isBinaryOperationInteger()) + { + const PDFInteger b = m_stack.popInteger(); + const PDFInteger a = m_stack.popInteger(); + m_stack.pushBoolean(a != b); + } + else if (m_stack.isBinaryOperationBoolean()) + { + const bool b = m_stack.popBoolean(); + const bool a = m_stack.popBoolean(); + m_stack.pushBoolean(a != b); + } + else + { + // Real values + const PDFReal b = m_stack.popReal(); + const PDFReal a = m_stack.popReal(); + m_stack.pushBoolean(a != b); + } + + break; + } + + case PDFPostScriptFunction::Code::Gt: + { + executeRelationOperator(); + break; + } + + case PDFPostScriptFunction::Code::Ge: + { + executeRelationOperator(); + break; + } + + case PDFPostScriptFunction::Code::Lt: + { + executeRelationOperator(); + break; + } + + case PDFPostScriptFunction::Code::Le: + { + executeRelationOperator(); + break; + } + + case PDFPostScriptFunction::Code::And: + { + if (m_stack.isBinaryOperationBoolean()) + { + const bool a = m_stack.popBoolean(); + const bool b = m_stack.popBoolean(); + m_stack.pushBoolean(a && b); + } + else + { + const PDFIntegerUnsigned a = static_cast(m_stack.popInteger()); + const PDFIntegerUnsigned b = static_cast(m_stack.popInteger()); + m_stack.pushInteger(a & b); + } + break; + } + + case PDFPostScriptFunction::Code::Or: + { + if (m_stack.isBinaryOperationBoolean()) + { + const bool a = m_stack.popBoolean(); + const bool b = m_stack.popBoolean(); + m_stack.pushBoolean(a || b); + } + else + { + const PDFIntegerUnsigned a = static_cast(m_stack.popInteger()); + const PDFIntegerUnsigned b = static_cast(m_stack.popInteger()); + m_stack.pushInteger(a | b); + } + break; + } + + case PDFPostScriptFunction::Code::Xor: + { + if (m_stack.isBinaryOperationBoolean()) + { + const bool a = m_stack.popBoolean(); + const bool b = m_stack.popBoolean(); + m_stack.pushBoolean(a != b); + } + else + { + const PDFIntegerUnsigned a = static_cast(m_stack.popInteger()); + const PDFIntegerUnsigned b = static_cast(m_stack.popInteger()); + m_stack.pushInteger(a ^ b); + } + break; + } + + case PDFPostScriptFunction::Code::Not: + { + if (m_stack.isInteger()) + { + const PDFIntegerUnsigned value = static_cast(m_stack.popInteger()); + m_stack.pushInteger(~value); + } + else + { + const bool value = m_stack.popBoolean(); + m_stack.pushBoolean(!value); + } + break; + } + + case PDFPostScriptFunction::Code::Bitshift: + { + const PDFInteger shift = m_stack.popInteger(); + const PDFIntegerUnsigned value = static_cast(m_stack.popInteger()); + PDFIntegerUnsigned shiftedValue = value; + + if (shift > 0) + { + // Positive is left + shiftedValue = value << shift; + } + else if (shift < 0) + { + // Negative is right + shiftedValue = value >> -shift; + } + + m_stack.pushInteger(shiftedValue); + break; + } + + case PDFPostScriptFunction::Code::True: + { + m_stack.pushBoolean(true); + break; + } + + case PDFPostScriptFunction::Code::False: + { + m_stack.pushBoolean(false); + break; + } + + case PDFPostScriptFunction::Code::If: + { + const PDFPostScriptFunctionStack::InstructionPointer callIp = m_stack.popInstructionPointer(); + const bool condition = m_stack.popBoolean(); + + if (condition) + { + // Call the if block + callStack.push(instruction.next); + ip = callIp; + continue; + } + + break; + } + + case PDFPostScriptFunction::Code::IfElse: + { + const PDFPostScriptFunctionStack::InstructionPointer falsePartIp = m_stack.popInstructionPointer(); + const PDFPostScriptFunctionStack::InstructionPointer truePartIp = m_stack.popInstructionPointer(); + const bool condition = m_stack.popBoolean(); + + callStack.push(instruction.next); + if (condition) + { + // Call the if part + ip = truePartIp; + } + else + { + // Call the else part + ip = falsePartIp; + } + + continue; + } + case PDFPostScriptFunction::Code::Pop: + { + m_stack.pop(); + break; + } + + case PDFPostScriptFunction::Code::Exch: + { + m_stack.exch(); + break; + } + + case PDFPostScriptFunction::Code::Dup: + { + m_stack.dup(); + break; + } + + case PDFPostScriptFunction::Code::Copy: + { + const PDFInteger n = m_stack.popInteger(); + + if (n < 0) + { + throw PDFPostScriptFunction::PDFPostScriptFunctionException(PDFTranslationContext::tr("Can't copy negative number of arguments (PostScript engine).")); + } + + if (n > 0) + { + m_stack.copy(n); + } + + break; + } + + case PDFPostScriptFunction::Code::Index: + { + const PDFInteger n = m_stack.popInteger(); + + if (n < 0) + { + throw PDFPostScriptFunction::PDFPostScriptFunctionException(PDFTranslationContext::tr("Negative index of operand (PostScript engine).")); + } + + m_stack.index(n); + break; + } + + case PDFPostScriptFunction::Code::Roll: + { + const PDFInteger j = m_stack.popInteger(); + const PDFInteger n = m_stack.popInteger(); + + if (n < 0) + { + throw PDFPostScriptFunction::PDFPostScriptFunctionException(PDFTranslationContext::tr("Negative number of operands (PostScript engine).")); + } + + m_stack.roll(n, j); + break; + } + + case PDFPostScriptFunction::Code::Call: + { + Q_ASSERT(instruction.operand.type == PDFPostScriptFunction::OperandType::InstructionPointer); + m_stack.pushInstructionPointer(instruction.operand.instructionPointer); + break; + } + + case PDFPostScriptFunction::Code::Return: + { + if (callStack.empty()) + { + throw PDFPostScriptFunction::PDFPostScriptFunctionException(PDFTranslationContext::tr("Call stack underflow (PostScript engine).")); + } + + ip = callStack.top(); + callStack.pop(); + continue; + } + + case PDFPostScriptFunction::Code::Push: + { + m_stack.push(instruction.operand); + break; + } + } + + // Move to the next instruction + ip = instruction.next; + } +} + +bool PDFPostScriptFunctionStack::isBinaryOperationInteger() const +{ + checkUnderflow(2); + + const size_t size = m_stack.size(); + return m_stack[size - 1].type == PDFPostScriptFunction::OperandType::Integer && + m_stack[size - 2].type == PDFPostScriptFunction::OperandType::Integer; +} + +bool PDFPostScriptFunctionStack::isBinaryOperationBoolean() const +{ + checkUnderflow(2); + + const size_t size = m_stack.size(); + return m_stack[size - 1].type == PDFPostScriptFunction::OperandType::Boolean && + m_stack[size - 2].type == PDFPostScriptFunction::OperandType::Boolean; +} + +PDFReal PDFPostScriptFunctionStack::popReal() +{ + checkUnderflow(); + + const PDFPostScriptFunction::OperandObject& topElement = m_stack.back(); + if (topElement.type == PDFPostScriptFunction::OperandType::Real) + { + const PDFReal value = topElement.realNumber; + m_stack.pop_back(); + return value; + } + else + { + throw PDFPostScriptFunction::PDFPostScriptFunctionException(PDFTranslationContext::tr("Real value expected (PostScript engine).")); + } +} + +PDFInteger PDFPostScriptFunctionStack::popInteger() +{ + checkUnderflow(); + + const PDFPostScriptFunction::OperandObject& topElement = m_stack.back(); + if (topElement.type == PDFPostScriptFunction::OperandType::Integer) + { + const PDFInteger value = topElement.integerNumber; + m_stack.pop_back(); + return value; + } + else + { + throw PDFPostScriptFunction::PDFPostScriptFunctionException(PDFTranslationContext::tr("Integer value expected (PostScript engine).")); + } +} + +bool PDFPostScriptFunctionStack::popBoolean() +{ + checkUnderflow(); + + const PDFPostScriptFunction::OperandObject& topElement = m_stack.back(); + if (topElement.type == PDFPostScriptFunction::OperandType::Boolean) + { + const bool value = topElement.boolean; + m_stack.pop_back(); + return value; + } + else + { + throw PDFPostScriptFunction::PDFPostScriptFunctionException(PDFTranslationContext::tr("Boolean value expected (PostScript engine).")); + } +} + +PDFPostScriptFunctionStack::InstructionPointer PDFPostScriptFunctionStack::popInstructionPointer() +{ + checkUnderflow(); + + const PDFPostScriptFunction::OperandObject& topElement = m_stack.back(); + if (topElement.type == PDFPostScriptFunction::OperandType::InstructionPointer) + { + const InstructionPointer value = topElement.instructionPointer; + m_stack.pop_back(); + return value; + } + else + { + throw PDFPostScriptFunction::PDFPostScriptFunctionException(PDFTranslationContext::tr("Instruction pointer expected (PostScript engine).")); + } +} + +PDFReal PDFPostScriptFunctionStack::popNumber() +{ + checkUnderflow(); + + const PDFPostScriptFunction::OperandObject& topElement = m_stack.back(); + if (topElement.type == PDFPostScriptFunction::OperandType::Real) + { + const PDFReal value = topElement.realNumber; + m_stack.pop_back(); + return value; + } + else if (topElement.type == PDFPostScriptFunction::OperandType::Integer) + { + const PDFInteger value = topElement.integerNumber; + m_stack.pop_back(); + return value; + } + else + { + throw PDFPostScriptFunction::PDFPostScriptFunctionException(PDFTranslationContext::tr("Instruction pointer expected (PostScript engine).")); + } +} + +void PDFPostScriptFunctionStack::exch() +{ + checkUnderflow(2); + + const size_t size = m_stack.size(); + std::swap(m_stack[size - 2], m_stack[size - 1]); +} + +void PDFPostScriptFunctionStack::dup() +{ + checkUnderflow(); + m_stack.push_back(m_stack.back()); + checkOverflow(); +} + +void PDFPostScriptFunctionStack::copy(PDFInteger n) +{ + Q_ASSERT(n > 0); + + checkUnderflow(static_cast(n)); + + size_t startIndex = m_stack.size() - n; + for (size_t i = 0; i < static_cast(n); ++i) + { + m_stack.push_back(m_stack[startIndex + i]); + checkOverflow(); + } +} + +void PDFPostScriptFunctionStack::index(PDFInteger n) +{ + Q_ASSERT(n >= 0); + + checkUnderflow(static_cast(n) + 1); + m_stack.push_back(m_stack[m_stack.size() - 1 - n]); +} + +void PDFPostScriptFunctionStack::roll(PDFInteger n, PDFInteger j) +{ + if (n == 0 || j == 0) + { + // If n is zero, then we are rolling zero arguments - do nothing + // If j is zero, then we don't roll anything at all - do nothing + return; + } + + checkUnderflow(n); + + // Load operands into temporary array + const size_t firstIndexOnStack = m_stack.size() - n; + std::vector operands(n); + for (size_t i = 0; i < static_cast(n); ++i) + { + operands[i] = m_stack[firstIndexOnStack + i]; + } + + if (j > 0) + { + // Rotate left j times + std::rotate(operands.begin(), operands.begin() + j, operands.end()); + } + else + { + // Rotate right j times + std::rotate(operands.rbegin(), operands.rbegin() - j, operands.rend()); + } + + // Load data back from temporary array + for (size_t i = 0; i < static_cast(n); ++i) + { + m_stack[firstIndexOnStack + i] = operands[i]; + } +} + +void PDFPostScriptFunctionStack::checkOverflow() const +{ + if (m_stack.size() > 100) + { + throw PDFPostScriptFunction::PDFPostScriptFunctionException(PDFTranslationContext::tr("Stack overflow occured (PostScript engine).")); + } +} + +void PDFPostScriptFunctionStack::checkUnderflow(size_t n) const +{ + if (m_stack.size() < n) + { + throw PDFPostScriptFunction::PDFPostScriptFunctionException(PDFTranslationContext::tr("Stack underflow occured (PostScript engine).")); + } +} + } // namespace pdf diff --git a/PdfForQtLib/sources/pdffunction.h b/PdfForQtLib/sources/pdffunction.h index f407753..f4543ea 100644 --- a/PdfForQtLib/sources/pdffunction.h +++ b/PdfForQtLib/sources/pdffunction.h @@ -315,6 +315,140 @@ private: std::vector m_partialFunctions; }; +/// Postscript function (Type 4 function) +/// Implements subset of postscript language +class PDFFORQTLIBSHARED_EXPORT PDFPostScriptFunction : public PDFFunction +{ +public: + +private: + class PDFPostScriptFunctionException : public std::exception + { + public: + inline explicit PDFPostScriptFunctionException(const QString& message) : + m_message(message) + { + + } + + /// Returns error message + const QString& getMessage() const { return m_message; } + + private: + QString m_message; + }; + + using InstructionPointer = size_t; + + enum class OperandType + { + Real, ///< Real number + Integer, ///< Integer number + Boolean, ///< Boolean + InstructionPointer ///< Instruction pointer + }; + + enum class Code + { + // B.1 Arithmetic operators + Add, + Sub, + Mul, + Div, + Idiv, + Mod, + Neg, + Abs, + Ceiling, + Floor, + Round, + Truncate, + Sqrt, + Sin, + Cos, + Atan, + Exp, + Ln, + Log, + Cvi, + Cvr, + + // B.2 Relational, Boolean and Bitwise operators + Eq, + Ne, + Gt, + Ge, + Lt, + Le, + And, + Or, + Xor, + Not, + Bitshift, + True, + False, + + // B.3 Conditional operators + If, + IfElse, + + // B.4 Stack operators + Pop, + Exch, + Dup, + Copy, + Index, + Roll, + + // Special codes not present in PDF reference, but needed to implement + // blocks (call and return function). + Call, + Return, + Push + }; + + struct OperandObject + { + explicit inline constexpr OperandObject() : + type(OperandType::Real), + realNumber(0.0) + { + + } + + static inline OperandObject createReal(PDFReal value) { OperandObject object; object.type = OperandType::Real; object.realNumber = value; return object; } + static inline OperandObject createInteger(PDFInteger value) { OperandObject object; object.type = OperandType::Integer; object.integerNumber = value; return object; } + static inline OperandObject createBoolean(bool value) { OperandObject object; object.type = OperandType::Boolean; object.boolean = value; return object; } + static inline OperandObject createInstructionPointer(InstructionPointer value) { OperandObject object; object.type = OperandType::InstructionPointer; object.instructionPointer = value; return object; } + + OperandType type; + + union + { + PDFReal realNumber; + PDFInteger integerNumber; + bool boolean; + InstructionPointer instructionPointer; + }; + }; + + static constexpr const InstructionPointer INVALID_INSTRUCTION_POINTER = std::numeric_limits::max(); + + struct CodeObject + { + explicit inline CodeObject() : code(Code::Return), next(INVALID_INSTRUCTION_POINTER), operand() { } + + Code code; + InstructionPointer next; + OperandObject operand; + }; + + using Program = std::vector; + + friend class PDFPostScriptFunctionStack; + friend class PDFPostScriptFunctionExecutor; +}; + } // namespace pdf #endif // PDFFUNCTION_H