diff --git a/PdfForQtLib/sources/pdfrenderer.cpp b/PdfForQtLib/sources/pdfrenderer.cpp index 2e5419b..9a82b8c 100644 --- a/PdfForQtLib/sources/pdfrenderer.cpp +++ b/PdfForQtLib/sources/pdfrenderer.cpp @@ -152,7 +152,7 @@ static constexpr const std::pair PDFRenderer::PDFRenderer(const PDFDocument* document) : m_document(document), - m_features(Antialasing | TextAntialiasing) + m_features(Antialiasing | TextAntialiasing) { Q_ASSERT(document); } @@ -246,6 +246,17 @@ QList PDFPageContentProcessor::processContents() m_errorList.append(PDFRenderError(RenderErrorType::Error, PDFTranslationContext::tr("Invalid page contents."))); } + if (!m_stack.empty()) + { + // Stack is not empty. There was more saves than restores. This is error. + m_errorList.append(PDFRenderError(RenderErrorType::Error, PDFTranslationContext::tr("Graphic state stack was saved more times, than was restored."))); + + while (!m_stack.empty()) + { + operatorRestoreGraphicState(); + } + } + return m_errorList; } @@ -268,6 +279,16 @@ void PDFPageContentProcessor::performUpdateGraphicsState(const PDFPageContentPro Q_UNUSED(state); } +void PDFPageContentProcessor::performSaveGraphicState(PDFPageContentProcessor::ProcessOrder order) +{ + Q_UNUSED(order); +} + +void PDFPageContentProcessor::performRestoreGraphicState(PDFPageContentProcessor::ProcessOrder order) +{ + Q_UNUSED(order); +} + void PDFPageContentProcessor::processContentStream(const PDFStream* stream) { QByteArray content = m_document->getDecodedStream(stream); @@ -330,6 +351,72 @@ void PDFPageContentProcessor::processCommand(const QByteArray& command) switch (op) { + case Operator::SetLineWidth: + { + invokeOperator(&PDFPageContentProcessor::operatorSetLineWidth); + break; + } + + case Operator::SetLineCap: + { + invokeOperator(&PDFPageContentProcessor::operatorSetLineCap); + break; + } + + case Operator::SetLineJoin: + { + invokeOperator(&PDFPageContentProcessor::operatorSetLineJoin); + break; + } + + case Operator::SetMitterLimit: + { + invokeOperator(&PDFPageContentProcessor::operatorSetMitterLimit); + break; + } + + case Operator::SetLineDashPattern: + { + invokeOperator(&PDFPageContentProcessor::operatorSetLineDashPattern); + break; + } + + case Operator::SetRenderingIntent: + { + invokeOperator(&PDFPageContentProcessor::operatorSetRenderingIntent); + break; + } + + case Operator::SetFlatness: + { + invokeOperator(&PDFPageContentProcessor::operatorSetFlatness); + break; + } + + case Operator::SetGraphicState: + { + invokeOperator(&PDFPageContentProcessor::operatorSetGraphicState); + break; + } + + case Operator::SaveGraphicState: + { + operatorSaveGraphicState(); + break; + } + + case Operator::RestoreGraphicState: + { + operatorRestoreGraphicState(); + break; + } + + case Operator::AdjustCurrentTransformationMatrix: + { + invokeOperator(&PDFPageContentProcessor::operatorAdjustCurrentTransformationMatrix); + break; + } + case Operator::MoveCurrentPoint: { invokeOperator(&PDFPageContentProcessor::operatorMoveCurrentPoint); @@ -562,6 +649,196 @@ void PDFPageContentProcessor::updateGraphicState() } } +void PDFPageContentProcessor::operatorSetLineWidth(PDFReal lineWidth) +{ + lineWidth = qMax(0.0, lineWidth); + m_graphicState.setLineWidth(lineWidth); + updateGraphicState(); +} + +void PDFPageContentProcessor::operatorSetLineCap(PDFInteger lineCap) +{ + lineCap = qBound(0, lineCap, 2); + + Qt::PenCapStyle penCapStyle = Qt::FlatCap; + switch (penCapStyle) + { + case 0: + { + penCapStyle = Qt::FlatCap; + break; + } + + case 1: + { + penCapStyle = Qt::RoundCap; + break; + } + + case 2: + { + penCapStyle = Qt::SquareCap; + break; + } + + default: + { + // This case can't occur, because we are correcting invalid values above. + Q_ASSERT(false); + break; + } + } + + m_graphicState.setLineCapStyle(penCapStyle); + updateGraphicState(); +} + +void PDFPageContentProcessor::operatorSetLineJoin(PDFInteger lineJoin) +{ + lineJoin = qBound(0, lineJoin, 2); + + Qt::PenJoinStyle penJoinStyle = Qt::MiterJoin; + switch (penJoinStyle) + { + case 0: + { + penJoinStyle = Qt::MiterJoin; + break; + } + + case 1: + { + penJoinStyle = Qt::RoundJoin; + break; + } + + case 2: + { + penJoinStyle = Qt::BevelJoin; + break; + } + + default: + { + // This case can't occur, because we are correcting invalid values above. + Q_ASSERT(false); + break; + } + } + + m_graphicState.setLineJoinStyle(penJoinStyle); + updateGraphicState(); +} + +void PDFPageContentProcessor::operatorSetMitterLimit(PDFReal mitterLimit) +{ + mitterLimit = qMax(0.0, mitterLimit); + m_graphicState.setMitterLimit(mitterLimit); + updateGraphicState(); +} + +void PDFPageContentProcessor::operatorSetLineDashPattern() +{ + // Operand stack must be of this form [ ... numbers ... ] offset. We check it. + // Minimal number of operands is [] 0. + + if (m_operands.size() < 3) + { + throw PDFRendererException(RenderErrorType::Error, PDFTranslationContext::tr("Invalid line dash pattern.")); + } + + // Now, we have at least 3 arguments. Check we have an array + if (m_operands[0].type != PDFLexicalAnalyzer::TokenType::ArrayStart || + m_operands[m_operands.size() - 2].type != PDFLexicalAnalyzer::TokenType::ArrayEnd) + { + throw PDFRendererException(RenderErrorType::Error, PDFTranslationContext::tr("Invalid line dash pattern.")); + } + + const size_t dashArrayStartIndex = 1; + const size_t dashArrayEndIndex = m_operands.size() - 2; + const size_t dashOffsetIndex = m_operands.size() - 1; + + std::vector dashArray; + dashArray.reserve(dashArrayEndIndex - dashArrayStartIndex); + for (size_t i = dashArrayStartIndex; i < dashArrayEndIndex; ++i) + { + dashArray.push_back(readOperand(i)); + } + + const PDFReal dashOffset = readOperand(dashOffsetIndex); + PDFLineDashPattern pattern(std::move(dashArray), dashOffset); + m_graphicState.setLineDashPattern(std::move(pattern)); + updateGraphicState(); +} + +void PDFPageContentProcessor::operatorSetRenderingIntent(PDFName intent) +{ + m_graphicState.setRenderingIntent(intent.name); + updateGraphicState(); +} + +void PDFPageContentProcessor::operatorSetFlatness(PDFReal flatness) +{ + flatness = qBound(0.0, flatness, 100.0); + m_graphicState.setFlatness(flatness); + updateGraphicState(); +} + +void PDFPageContentProcessor::operatorSaveGraphicState() +{ + performSaveGraphicState(ProcessOrder::BeforeOperation); + m_stack.push(m_graphicState); + m_stack.top().setStateFlags(PDFPageContentProcessorState::StateUnchanged); + performSaveGraphicState(ProcessOrder::AfterOperation); +} + +void PDFPageContentProcessor::operatorRestoreGraphicState() +{ + if (m_stack.empty()) + { + throw PDFRendererException(RenderErrorType::Error, PDFTranslationContext::tr("Trying to restore graphic state more times than it was saved.")); + } + + performRestoreGraphicState(ProcessOrder::BeforeOperation); + m_graphicState = m_stack.top(); + m_stack.pop(); + updateGraphicState(); + performRestoreGraphicState(ProcessOrder::AfterOperation); +} + +void PDFPageContentProcessor::operatorAdjustCurrentTransformationMatrix(PDFReal a, PDFReal b, PDFReal c, PDFReal d, PDFReal e, PDFReal f) +{ + // We will comment following equation: + // Adobe PDF Reference 1.7 says, that we have this transformation using coefficient a, b, c, d, e and f: + // [ a, b, 0 ] + // [x', y', 1] = [ x, y, 1] * [ c, d, 0 ] + // [ e, f, 1 ] + // If we transpose this equation (we want this, because Qt uses transposed matrices (QMatrix). + // So, we will get following result: + // + // [ x' ] [ a, c, e] [ x ] + // [ y' ] = [ b, d, f] * [ y ] + // [ 1 ] [ 0, 0, 1] [ 1 ] + // + // So, it is obvious, than we will have following coefficients: + // m_11 = a, m_21 = c, dx = e + // m_12 = b, m_22 = d, dy = f + // + // We must also check, that matrix is invertible. If it is not, then we will throw exception + // to avoid errors later (for some operations, we assume matrix is invertible). + + QMatrix matrix(a, b, c, d, e, f); + QMatrix transformMatrix = m_graphicState.getCurrentTransformationMatrix() * matrix; + + if (!transformMatrix.isInvertible()) + { + throw PDFRendererException(RenderErrorType::Error, PDFTranslationContext::tr("Transformation matrix is not invertible.")); + } + + m_graphicState.setCurrentTransformationMatrix(transformMatrix); + updateGraphicState(); +} + template<> PDFReal PDFPageContentProcessor::readOperand(size_t index) const { @@ -587,6 +864,29 @@ PDFReal PDFPageContentProcessor::readOperand(size_t index) const return 0.0; } +template<> +PDFInteger PDFPageContentProcessor::readOperand(size_t index) const +{ + if (index < m_operands.size()) + { + const PDFLexicalAnalyzer::Token& token = m_operands[index]; + + switch (token.type) + { + case PDFLexicalAnalyzer::TokenType::Integer: + return token.data.value(); + + default: + throw PDFRendererException(RenderErrorType::Error, PDFTranslationContext::tr("Can't read operand (integer) on index %1. Operand is of type '%2'.").arg(index + 1).arg(PDFLexicalAnalyzer::getStringFromOperandType(token.type))); + } + } + else + { + throw PDFRendererException(RenderErrorType::Error, PDFTranslationContext::tr("Can't read operand (integer) on index %1. Only %2 operands provided.").arg(index + 1).arg(m_operands.size())); + } + + return 0; +} template<> PDFPageContentProcessor::PDFName PDFPageContentProcessor::readOperand(size_t index) const @@ -924,6 +1224,7 @@ PDFPageContentProcessor::PDFPageContentProcessorState& PDFPageContentProcessor:: setLineCapStyle(other.getLineCapStyle()); setLineJoinStyle(other.getLineJoinStyle()); setMitterLimit(other.getMitterLimit()); + setLineDashPattern(other.getLineDashPattern()); setRenderingIntent(other.getRenderingIntent()); setFlatness(other.getFlatness()); setSmoothness(other.getSmoothness()); @@ -1011,6 +1312,15 @@ void PDFPageContentProcessor::PDFPageContentProcessorState::setMitterLimit(const } } +void PDFPageContentProcessor::PDFPageContentProcessorState::setLineDashPattern(PDFLineDashPattern pattern) +{ + if (m_lineDashPattern != pattern) + { + m_lineDashPattern = std::move(pattern); + m_stateFlags |= StateLineDashPattern; + } +} + void PDFPageContentProcessor::PDFPageContentProcessorState::setRenderingIntent(const QByteArray& renderingIntent) { if (m_renderingIntent != renderingIntent) diff --git a/PdfForQtLib/sources/pdfrenderer.h b/PdfForQtLib/sources/pdfrenderer.h index 3f9e319..c9cc961 100644 --- a/PdfForQtLib/sources/pdfrenderer.h +++ b/PdfForQtLib/sources/pdfrenderer.h @@ -53,7 +53,7 @@ public: enum Feature { - Antialasing, ///< Antialiasing for lines, shapes, etc. + Antialiasing, ///< Antialiasing for lines, shapes, etc. TextAntialiasing, ///< Antialiasing for drawing text SmoothImages ///< Adjust images to the device space using smooth transformation (slower, but better performance quality) }; diff --git a/PdfForQtLib/sources/pdfrenderer_impl.h b/PdfForQtLib/sources/pdfrenderer_impl.h index 85d8926..e92fabc 100644 --- a/PdfForQtLib/sources/pdfrenderer_impl.h +++ b/PdfForQtLib/sources/pdfrenderer_impl.h @@ -167,6 +167,32 @@ public: QList processContents(); protected: + + class PDFLineDashPattern + { + public: + explicit inline PDFLineDashPattern() = default; + explicit inline PDFLineDashPattern(const std::vector& dashArray, PDFReal dashOffset) : + m_dashArray(dashArray), + m_dashOffset(dashOffset) + { + + } + + inline const std::vector& getDashArray() const { return m_dashArray; } + inline void setDashArray(const std::vector& dashArray) { m_dashArray = dashArray; } + + inline PDFReal getDashOffset() const { return m_dashOffset; } + inline void setDashOffset(PDFReal dashOffset) { m_dashOffset = dashOffset; } + + inline bool operator==(const PDFLineDashPattern& other) const { return m_dashArray == other.m_dashArray && m_dashOffset == other.m_dashOffset; } + inline bool operator!=(const PDFLineDashPattern& other) const { return !(*this == other); } + + private: + std::vector m_dashArray; + PDFReal m_dashOffset = 0.0; + }; + /// Represents graphic state of the PDF (holding current graphic state parameters). /// Please see PDF Reference 1.7, Chapter 4.3 "Graphic State" class PDFPageContentProcessorState @@ -193,9 +219,10 @@ protected: StateLineCapStyle = 0x0040, StateLineJoinStyle = 0x0080, StateMitterLimit = 0x0100, - StateRenderingIntent = 0x0200, - StateFlatness = 0x0400, - StateSmoothness = 0x0800 + StateLineDashPattern = 0x0200, + StateRenderingIntent = 0x0400, + StateFlatness = 0x0800, + StateSmoothness = 0x1000 }; Q_DECLARE_FLAGS(StateFlags, StateFlag) @@ -227,6 +254,9 @@ protected: PDFReal getMitterLimit() const { return m_mitterLimit; } void setMitterLimit(const PDFReal& mitterLimit); + const PDFLineDashPattern& getLineDashPattern() const { return m_lineDashPattern; } + void setLineDashPattern(PDFLineDashPattern pattern); + const QByteArray& getRenderingIntent() const { return m_renderingIntent; } void setRenderingIntent(const QByteArray& renderingIntent); @@ -249,12 +279,19 @@ protected: Qt::PenCapStyle m_lineCapStyle; Qt::PenJoinStyle m_lineJoinStyle; PDFReal m_mitterLimit; + PDFLineDashPattern m_lineDashPattern; QByteArray m_renderingIntent; PDFReal m_flatness; PDFReal m_smoothness; StateFlags m_stateFlags; }; + enum class ProcessOrder + { + BeforeOperation, + AfterOperation + }; + /// This function has to be implemented in the client drawing implementation, it should /// draw the path according to the parameters. /// \param path Path, which should be drawn (can be emtpy - in that case nothing happens) @@ -268,10 +305,22 @@ protected: virtual void performClipping(const QPainterPath& path, Qt::FillRule fillRule); /// This function has to be implemented in the client drawing implementation, it should - /// update the device accordin to the graphic state change. The flags are set when + /// update the device according to the graphic state change. The flags are set when /// the value differs from the previous graphic state. virtual void performUpdateGraphicsState(const PDFPageContentProcessorState& state); + /// Implement to perform save of the graphic state. This function is called two times - + /// before the operation and after the operation. Parameter \p order determines when + /// this function is called. + /// \param order If this function is called before the operation, or after the operation. + virtual void performSaveGraphicState(ProcessOrder order); + + /// Implement to perform restore of the graphic state. This function is called two times - + /// before the operation and after the operation. Parameter \p order determines when + /// this function is called. + /// \param order If this function is called before the operation, or after the operation. + virtual void performRestoreGraphicState(ProcessOrder order); + private: /// Process the content stream void processContentStream(const PDFStream* stream); @@ -291,6 +340,9 @@ private: template<> PDFReal readOperand(size_t index) const; + template<> + PDFInteger readOperand(size_t index) const; + template<> PDFName readOperand(size_t index) const; @@ -339,6 +391,21 @@ private: return QColor(); } + // General graphic state w, J, j, M, d, ri, i, gs + void operatorSetLineWidth(PDFReal lineWidth); ///< w, sets the line width + void operatorSetLineCap(PDFInteger lineCap); ///< J, sets the line cap + void operatorSetLineJoin(PDFInteger lineJoin); ///< j, sets the line join + void operatorSetMitterLimit(PDFReal mitterLimit); ///< M, sets the mitter limit + void operatorSetLineDashPattern(); ///< d, sets the line dash pattern + void operatorSetRenderingIntent(PDFName intent); ///< ri, sets the rendering intent + void operatorSetFlatness(PDFReal flatness); ///< i, sets the flattness (number in range from 0 to 100) + void operatorSetGraphicState(PDFName dictionaryName); ///< gs, sets the whole graphic state (stored in resource dictionary) + + // Special graphic state: q, Q, cm + void operatorSaveGraphicState(); ///< q, saves the graphic state + void operatorRestoreGraphicState(); ///< Q, restores the graphic state + void operatorAdjustCurrentTransformationMatrix(PDFReal a, PDFReal b, PDFReal c, PDFReal d, PDFReal e, PDFReal f); ///< cm, modify the current transformation matrix by matrix multiplication + // Path construction operators void operatorMoveCurrentPoint(PDFReal x, PDFReal y); void operatorLineTo(PDFReal x, PDFReal y);