Graphic state operators

This commit is contained in:
Jakub Melka
2019-02-21 19:35:07 +01:00
parent 57a9582ffd
commit 959ed6599b
3 changed files with 383 additions and 6 deletions

View File

@ -152,7 +152,7 @@ static constexpr const std::pair<const char*, PDFPageContentProcessor::Operator>
PDFRenderer::PDFRenderer(const PDFDocument* document) : PDFRenderer::PDFRenderer(const PDFDocument* document) :
m_document(document), m_document(document),
m_features(Antialasing | TextAntialiasing) m_features(Antialiasing | TextAntialiasing)
{ {
Q_ASSERT(document); Q_ASSERT(document);
} }
@ -246,6 +246,17 @@ QList<PDFRenderError> PDFPageContentProcessor::processContents()
m_errorList.append(PDFRenderError(RenderErrorType::Error, PDFTranslationContext::tr("Invalid page contents."))); 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; return m_errorList;
} }
@ -268,6 +279,16 @@ void PDFPageContentProcessor::performUpdateGraphicsState(const PDFPageContentPro
Q_UNUSED(state); 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) void PDFPageContentProcessor::processContentStream(const PDFStream* stream)
{ {
QByteArray content = m_document->getDecodedStream(stream); QByteArray content = m_document->getDecodedStream(stream);
@ -330,6 +351,72 @@ void PDFPageContentProcessor::processCommand(const QByteArray& command)
switch (op) 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: case Operator::MoveCurrentPoint:
{ {
invokeOperator(&PDFPageContentProcessor::operatorMoveCurrentPoint); 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<PDFInteger>(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<PDFInteger>(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<PDFReal> dashArray;
dashArray.reserve(dashArrayEndIndex - dashArrayStartIndex);
for (size_t i = dashArrayStartIndex; i < dashArrayEndIndex; ++i)
{
dashArray.push_back(readOperand<PDFReal>(i));
}
const PDFReal dashOffset = readOperand<PDFReal>(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<> template<>
PDFReal PDFPageContentProcessor::readOperand<PDFReal>(size_t index) const PDFReal PDFPageContentProcessor::readOperand<PDFReal>(size_t index) const
{ {
@ -587,6 +864,29 @@ PDFReal PDFPageContentProcessor::readOperand<PDFReal>(size_t index) const
return 0.0; return 0.0;
} }
template<>
PDFInteger PDFPageContentProcessor::readOperand<PDFInteger>(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<PDFInteger>();
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<> template<>
PDFPageContentProcessor::PDFName PDFPageContentProcessor::readOperand<PDFPageContentProcessor::PDFName>(size_t index) const PDFPageContentProcessor::PDFName PDFPageContentProcessor::readOperand<PDFPageContentProcessor::PDFName>(size_t index) const
@ -924,6 +1224,7 @@ PDFPageContentProcessor::PDFPageContentProcessorState& PDFPageContentProcessor::
setLineCapStyle(other.getLineCapStyle()); setLineCapStyle(other.getLineCapStyle());
setLineJoinStyle(other.getLineJoinStyle()); setLineJoinStyle(other.getLineJoinStyle());
setMitterLimit(other.getMitterLimit()); setMitterLimit(other.getMitterLimit());
setLineDashPattern(other.getLineDashPattern());
setRenderingIntent(other.getRenderingIntent()); setRenderingIntent(other.getRenderingIntent());
setFlatness(other.getFlatness()); setFlatness(other.getFlatness());
setSmoothness(other.getSmoothness()); 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) void PDFPageContentProcessor::PDFPageContentProcessorState::setRenderingIntent(const QByteArray& renderingIntent)
{ {
if (m_renderingIntent != renderingIntent) if (m_renderingIntent != renderingIntent)

View File

@ -53,7 +53,7 @@ public:
enum Feature enum Feature
{ {
Antialasing, ///< Antialiasing for lines, shapes, etc. Antialiasing, ///< Antialiasing for lines, shapes, etc.
TextAntialiasing, ///< Antialiasing for drawing text TextAntialiasing, ///< Antialiasing for drawing text
SmoothImages ///< Adjust images to the device space using smooth transformation (slower, but better performance quality) SmoothImages ///< Adjust images to the device space using smooth transformation (slower, but better performance quality)
}; };

View File

@ -167,6 +167,32 @@ public:
QList<PDFRenderError> processContents(); QList<PDFRenderError> processContents();
protected: protected:
class PDFLineDashPattern
{
public:
explicit inline PDFLineDashPattern() = default;
explicit inline PDFLineDashPattern(const std::vector<PDFReal>& dashArray, PDFReal dashOffset) :
m_dashArray(dashArray),
m_dashOffset(dashOffset)
{
}
inline const std::vector<PDFReal>& getDashArray() const { return m_dashArray; }
inline void setDashArray(const std::vector<PDFReal>& 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<PDFReal> m_dashArray;
PDFReal m_dashOffset = 0.0;
};
/// Represents graphic state of the PDF (holding current graphic state parameters). /// Represents graphic state of the PDF (holding current graphic state parameters).
/// Please see PDF Reference 1.7, Chapter 4.3 "Graphic State" /// Please see PDF Reference 1.7, Chapter 4.3 "Graphic State"
class PDFPageContentProcessorState class PDFPageContentProcessorState
@ -193,9 +219,10 @@ protected:
StateLineCapStyle = 0x0040, StateLineCapStyle = 0x0040,
StateLineJoinStyle = 0x0080, StateLineJoinStyle = 0x0080,
StateMitterLimit = 0x0100, StateMitterLimit = 0x0100,
StateRenderingIntent = 0x0200, StateLineDashPattern = 0x0200,
StateFlatness = 0x0400, StateRenderingIntent = 0x0400,
StateSmoothness = 0x0800 StateFlatness = 0x0800,
StateSmoothness = 0x1000
}; };
Q_DECLARE_FLAGS(StateFlags, StateFlag) Q_DECLARE_FLAGS(StateFlags, StateFlag)
@ -227,6 +254,9 @@ protected:
PDFReal getMitterLimit() const { return m_mitterLimit; } PDFReal getMitterLimit() const { return m_mitterLimit; }
void setMitterLimit(const PDFReal& mitterLimit); void setMitterLimit(const PDFReal& mitterLimit);
const PDFLineDashPattern& getLineDashPattern() const { return m_lineDashPattern; }
void setLineDashPattern(PDFLineDashPattern pattern);
const QByteArray& getRenderingIntent() const { return m_renderingIntent; } const QByteArray& getRenderingIntent() const { return m_renderingIntent; }
void setRenderingIntent(const QByteArray& renderingIntent); void setRenderingIntent(const QByteArray& renderingIntent);
@ -249,12 +279,19 @@ protected:
Qt::PenCapStyle m_lineCapStyle; Qt::PenCapStyle m_lineCapStyle;
Qt::PenJoinStyle m_lineJoinStyle; Qt::PenJoinStyle m_lineJoinStyle;
PDFReal m_mitterLimit; PDFReal m_mitterLimit;
PDFLineDashPattern m_lineDashPattern;
QByteArray m_renderingIntent; QByteArray m_renderingIntent;
PDFReal m_flatness; PDFReal m_flatness;
PDFReal m_smoothness; PDFReal m_smoothness;
StateFlags m_stateFlags; StateFlags m_stateFlags;
}; };
enum class ProcessOrder
{
BeforeOperation,
AfterOperation
};
/// This function has to be implemented in the client drawing implementation, it should /// This function has to be implemented in the client drawing implementation, it should
/// draw the path according to the parameters. /// draw the path according to the parameters.
/// \param path Path, which should be drawn (can be emtpy - in that case nothing happens) /// \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); virtual void performClipping(const QPainterPath& path, Qt::FillRule fillRule);
/// This function has to be implemented in the client drawing implementation, it should /// 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. /// the value differs from the previous graphic state.
virtual void performUpdateGraphicsState(const PDFPageContentProcessorState& 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: private:
/// Process the content stream /// Process the content stream
void processContentStream(const PDFStream* stream); void processContentStream(const PDFStream* stream);
@ -291,6 +340,9 @@ private:
template<> template<>
PDFReal readOperand<PDFReal>(size_t index) const; PDFReal readOperand<PDFReal>(size_t index) const;
template<>
PDFInteger readOperand<PDFInteger>(size_t index) const;
template<> template<>
PDFName readOperand<PDFName>(size_t index) const; PDFName readOperand<PDFName>(size_t index) const;
@ -339,6 +391,21 @@ private:
return QColor(); 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 // Path construction operators
void operatorMoveCurrentPoint(PDFReal x, PDFReal y); void operatorMoveCurrentPoint(PDFReal x, PDFReal y);
void operatorLineTo(PDFReal x, PDFReal y); void operatorLineTo(PDFReal x, PDFReal y);