Implementation of optional content in content processor (without XObjects)

This commit is contained in:
Jakub Melka
2019-07-04 17:52:38 +02:00
parent e04222fb0b
commit 584211bf36
15 changed files with 623 additions and 37 deletions

View File

@ -28,6 +28,7 @@ namespace pdf
PDFDrawSpaceController::PDFDrawSpaceController(QObject* parent) : PDFDrawSpaceController::PDFDrawSpaceController(QObject* parent) :
QObject(parent), QObject(parent),
m_document(nullptr), m_document(nullptr),
m_optionalContentActivity(nullptr),
m_pageLayoutMode(PageLayout::OneColumn), m_pageLayoutMode(PageLayout::OneColumn),
m_verticalSpacingMM(5.0), m_verticalSpacingMM(5.0),
m_horizontalSpacingMM(1.0), m_horizontalSpacingMM(1.0),
@ -41,12 +42,14 @@ PDFDrawSpaceController::~PDFDrawSpaceController()
} }
void PDFDrawSpaceController::setDocument(const PDFDocument* document) void PDFDrawSpaceController::setDocument(const PDFDocument* document, const PDFOptionalContentActivity* optionalContentActivity)
{ {
if (document != m_document) if (document != m_document)
{ {
m_document = document; m_document = document;
m_fontCache.setDocument(document); m_fontCache.setDocument(document);
m_optionalContentActivity = optionalContentActivity;
connect(m_optionalContentActivity, &PDFOptionalContentActivity::optionalContentGroupStateChanged, this, &PDFDrawSpaceController::repaintNeeded);
recalculate(); recalculate();
} }
} }
@ -348,6 +351,7 @@ PDFDrawWidgetProxy::PDFDrawWidgetProxy(QObject* parent) :
{ {
m_controller = new PDFDrawSpaceController(this); m_controller = new PDFDrawSpaceController(this);
connect(m_controller, &PDFDrawSpaceController::drawSpaceChanged, this, &PDFDrawWidgetProxy::update); connect(m_controller, &PDFDrawSpaceController::drawSpaceChanged, this, &PDFDrawWidgetProxy::update);
connect(m_controller, &PDFDrawSpaceController::repaintNeeded, this, &PDFDrawWidgetProxy::repaintNeeded);
} }
PDFDrawWidgetProxy::~PDFDrawWidgetProxy() PDFDrawWidgetProxy::~PDFDrawWidgetProxy()
@ -355,9 +359,9 @@ PDFDrawWidgetProxy::~PDFDrawWidgetProxy()
} }
void PDFDrawWidgetProxy::setDocument(const PDFDocument* document) void PDFDrawWidgetProxy::setDocument(const PDFDocument* document, const PDFOptionalContentActivity* optionalContentActivity)
{ {
m_controller->setDocument(document); m_controller->setDocument(document, optionalContentActivity);
} }
void PDFDrawWidgetProxy::init(PDFWidget* widget) void PDFDrawWidgetProxy::init(PDFWidget* widget)
@ -545,7 +549,7 @@ void PDFDrawWidgetProxy::draw(QPainter* painter, QRect rect)
// Clear the page space by white color // Clear the page space by white color
painter->fillRect(placedRect, Qt::white); painter->fillRect(placedRect, Qt::white);
PDFRenderer renderer(m_controller->getDocument(), m_controller->getFontCache()); PDFRenderer renderer(m_controller->getDocument(), m_controller->getFontCache(), m_controller->getOptionalContentActivity());
QList<PDFRenderError> errors = renderer.render(painter, placedRect, item.pageIndex); QList<PDFRenderError> errors = renderer.render(painter, placedRect, item.pageIndex);
if (!errors.empty()) if (!errors.empty())

View File

@ -47,9 +47,11 @@ public:
virtual ~PDFDrawSpaceController() override; virtual ~PDFDrawSpaceController() override;
/// Sets the document and recalculates the draw space. Document can be nullptr, /// Sets the document and recalculates the draw space. Document can be nullptr,
/// in that case, draw space is cleared. /// in that case, draw space is cleared. Optional content activity can be nullptr,
/// in that case, no content is suppressed.
/// \param document Document /// \param document Document
void setDocument(const PDFDocument* document); /// \param optionalContentActivity Optional content activity
void setDocument(const PDFDocument* document, const PDFOptionalContentActivity* optionalContentActivity);
/// Sets the page layout. Page layout can be one of the PDF's page layouts. /// Sets the page layout. Page layout can be one of the PDF's page layouts.
/// \param pageLayout Page layout /// \param pageLayout Page layout
@ -93,8 +95,12 @@ public:
/// Returns the font cache /// Returns the font cache
const PDFFontCache* getFontCache() const { return &m_fontCache; } const PDFFontCache* getFontCache() const { return &m_fontCache; }
/// Returns optional content activity
const PDFOptionalContentActivity* getOptionalContentActivity() const { return m_optionalContentActivity; }
signals: signals:
void drawSpaceChanged(); void drawSpaceChanged();
void repaintNeeded();
private: private:
/// Recalculates the draw space. Preserves setted page rotation. /// Recalculates the draw space. Preserves setted page rotation.
@ -118,6 +124,7 @@ private:
static constexpr size_t REALIZED_FONT_CACHE_LIMIT = 128; static constexpr size_t REALIZED_FONT_CACHE_LIMIT = 128;
const PDFDocument* m_document; const PDFDocument* m_document;
const PDFOptionalContentActivity* m_optionalContentActivity;
PageLayout m_pageLayoutMode; PageLayout m_pageLayoutMode;
LayoutItems m_layoutItems; LayoutItems m_layoutItems;
@ -140,9 +147,11 @@ public:
virtual ~PDFDrawWidgetProxy() override; virtual ~PDFDrawWidgetProxy() override;
/// Sets the document and updates the draw space. Document can be nullptr, /// Sets the document and updates the draw space. Document can be nullptr,
/// in that case, draw space is cleared. /// in that case, draw space is cleared. Optional content activity can be nullptr,
/// in that case, no content is suppressed.
/// \param document Document /// \param document Document
void setDocument(const PDFDocument* document); /// \param optionalContentActivity Optional content activity
void setDocument(const PDFDocument* document, const PDFOptionalContentActivity* optionalContentActivity);
void init(PDFWidget* widget); void init(PDFWidget* widget);
@ -208,6 +217,7 @@ signals:
void drawSpaceChanged(); void drawSpaceChanged();
void pageLayoutChanged(); void pageLayoutChanged();
void renderingError(PDFInteger pageIndex, const QList<PDFRenderError>& errors); void renderingError(PDFInteger pageIndex, const QList<PDFRenderError>& errors);
void repaintNeeded();
private: private:
struct LayoutItem struct LayoutItem

View File

@ -50,6 +50,7 @@ PDFWidget::PDFWidget(QWidget* parent) :
m_proxy = new PDFDrawWidgetProxy(this); m_proxy = new PDFDrawWidgetProxy(this);
m_proxy->init(this); m_proxy->init(this);
connect(m_proxy, &PDFDrawWidgetProxy::renderingError, this, &PDFWidget::onRenderingError); connect(m_proxy, &PDFDrawWidgetProxy::renderingError, this, &PDFWidget::onRenderingError);
connect(m_proxy, &PDFDrawWidgetProxy::repaintNeeded, m_drawWidget, QOverload<>::of(&PDFDrawWidget::update));
} }
PDFWidget::~PDFWidget() PDFWidget::~PDFWidget()
@ -57,9 +58,9 @@ PDFWidget::~PDFWidget()
} }
void PDFWidget::setDocument(const PDFDocument* document) void PDFWidget::setDocument(const PDFDocument* document, const PDFOptionalContentActivity* optionalContentActivity)
{ {
m_proxy->setDocument(document); m_proxy->setDocument(document, optionalContentActivity);
m_pageRenderingErrors.clear(); m_pageRenderingErrors.clear();
} }

View File

@ -42,9 +42,11 @@ public:
using PageRenderingErrors = std::map<PDFInteger, QList<PDFRenderError>>; using PageRenderingErrors = std::map<PDFInteger, QList<PDFRenderError>>;
/// Sets the document to be viewed in this widget. Document can be nullptr, /// Sets the document to be viewed in this widget. Document can be nullptr,
/// in that case, widget contents are cleared. /// in that case, widget contents are cleared. Optional content activity can be nullptr,
/// if this occurs, no content is suppressed.
/// \param document Document /// \param document Document
void setDocument(const PDFDocument* document); /// \param optionalContentActivity Optional content activity
void setDocument(const PDFDocument* document, const PDFOptionalContentActivity* optionalContentActivity);
PDFDrawWidget* getDrawWidget() const { return m_drawWidget; } PDFDrawWidget* getDrawWidget() const { return m_drawWidget; }
QScrollBar* getHorizontalScrollbar() const { return m_horizontalScrollBar; } QScrollBar* getHorizontalScrollbar() const { return m_horizontalScrollBar; }

View File

@ -425,4 +425,218 @@ void PDFOptionalContentActivity::applyConfiguration(const PDFOptionalContentConf
} }
} }
PDFOptionalContentMembershipObject PDFOptionalContentMembershipObject::create(const PDFDocument* document, const PDFObject& object)
{
PDFOptionalContentMembershipObject result;
const PDFObject& dereferencedObject = document->getObject(object);
if (dereferencedObject.isDictionary())
{
const PDFDictionary* dictionary = dereferencedObject.getDictionary();
if (dictionary->hasKey("VE"))
{
// Parse visibility expression
std::set<PDFObjectReference> usedReferences;
std::function<std::unique_ptr<Node>(const PDFObject&)> parseNode = [document, &parseNode, &usedReferences](const PDFObject& nodeObject) -> std::unique_ptr<Node>
{
const PDFObject& dereferencedNodeObject = document->getObject(nodeObject);
if (dereferencedNodeObject.isArray())
{
// It is probably array. We must check, if we doesn't have cyclic reference.
if (nodeObject.isReference())
{
if (usedReferences.count(nodeObject.getReference()))
{
throw PDFParserException(PDFTranslationContext::tr("Cyclic reference error in optional content visibility expression."));
}
else
{
usedReferences.insert(nodeObject.getReference());
}
}
// Check the array
const PDFArray* array = dereferencedNodeObject.getArray();
if (array->getCount() < 2)
{
throw PDFParserException(PDFTranslationContext::tr("Invalid optional content visibility expression."));
}
// Read the operator
const PDFObject& dereferencedNameObject = document->getObject(array->getItem(0));
QByteArray operatorName;
if (dereferencedNameObject.isName())
{
operatorName = dereferencedNameObject.getString();
}
Operator operatorType = Operator::And;
if (operatorName == "And")
{
operatorType = Operator::And;
} else if (operatorName == "Or")
{
operatorType = Operator::Or;
}
else if (operatorName == "Not")
{
operatorType = Operator::Not;
}
else
{
throw PDFParserException(PDFTranslationContext::tr("Invalid optional content visibility expression."));
}
// Read the operands
std::vector<std::unique_ptr<Node>> operands;
operands.reserve(array->getCount());
for (size_t i = 1, count = array->getCount(); i < count; ++i)
{
operands.push_back(parseNode(array->getItem(i)));
}
return std::unique_ptr<Node>(new OperatorNode(operatorType, qMove(operands)));
}
else if (nodeObject.isReference())
{
// Treat is as an optional content group
return std::unique_ptr<Node>(new OptionalContentGroupNode(nodeObject.getReference()));
}
else
{
// Something strange occured - either we should have an array, or we should have a reference to the OCG
throw PDFParserException(PDFTranslationContext::tr("Invalid optional content visibility expression."));
return std::unique_ptr<Node>(nullptr);
}
};
result.m_expression = parseNode(dictionary->get("VE"));
}
else
{
// First, scan all optional content groups
PDFDocumentDataLoaderDecorator loader(document);
std::vector<PDFObjectReference> ocgs = loader.readReferenceArrayFromDictionary(dictionary, "OCGs");
if (!ocgs.empty())
{
auto createOperatorOnOcgs = [&ocgs](Operator operator_)
{
std::vector<std::unique_ptr<Node>> operands;
operands.reserve(ocgs.size());
for (PDFObjectReference reference : ocgs)
{
operands.push_back(std::unique_ptr<Node>(new OptionalContentGroupNode(reference)));
}
return std::unique_ptr<Node>(new OperatorNode(operator_, qMove(operands)));
};
// Parse 'P' mode
QByteArray type = loader.readNameFromDictionary(dictionary, "P");
if (type == "AllOn")
{
// All of entries in OCGS are turned on
result.m_expression = createOperatorOnOcgs(Operator::And);
}
else if (type == "AnyOn")
{
// Any of entries in OCGS is turned on
result.m_expression = createOperatorOnOcgs(Operator::Or);
}
else if (type == "AnyOff")
{
// Any of entries are turned off. It is negation of 'AllOn'.
std::vector<std::unique_ptr<Node>> subexpression;
subexpression.push_back(createOperatorOnOcgs(Operator::And));
result.m_expression = std::unique_ptr<Node>(new OperatorNode(Operator::Not, qMove(subexpression)));
}
else if (type == "AllOff")
{
// All of entries are turned off. It is negation of 'AnyOn'
std::vector<std::unique_ptr<Node>> subexpression;
subexpression.push_back(createOperatorOnOcgs(Operator::Or));
result.m_expression = std::unique_ptr<Node>(new OperatorNode(Operator::Not, qMove(subexpression)));
}
else
{
// Default value is AnyOn according to the PDF reference
result.m_expression = createOperatorOnOcgs(Operator::Or);
}
}
}
}
return result;
}
OCState PDFOptionalContentMembershipObject::evaluate(const PDFOptionalContentActivity* activity) const
{
return m_expression ? m_expression->evaluate(activity) : OCState::Unknown;
}
OCState PDFOptionalContentMembershipObject::OptionalContentGroupNode::evaluate(const PDFOptionalContentActivity* activity) const
{
return activity->getState(m_optionalContentGroup);
}
OCState PDFOptionalContentMembershipObject::OperatorNode::evaluate(const PDFOptionalContentActivity* activity) const
{
OCState result = OCState::Unknown;
switch (m_operator)
{
case Operator::And:
{
for (const auto& child : m_children)
{
result = result & child->evaluate(activity);
}
break;
}
case Operator::Or:
{
for (const auto& child : m_children)
{
result = result | child->evaluate(activity);
}
break;
}
case Operator::Not:
{
// We must handle case, when we have zero or more expressions (Not operator requires exactly one).
// If this case occurs, then we return Unknown state.
if (m_children.size() == 1)
{
OCState childState = m_children.front()->evaluate(activity);
switch (childState)
{
case OCState::ON:
result = OCState::OFF;
break;
case OCState::OFF:
result = OCState::ON;
break;
default:
Q_ASSERT(result == OCState::Unknown);
break;
}
}
break;
}
default:
{
Q_ASSERT(false);
break;
}
}
return result;
}
} // namespace pdf } // namespace pdf

View File

@ -25,6 +25,7 @@ namespace pdf
{ {
class PDFDocument; class PDFDocument;
class PDFOptionalContentActivity;
class PDFOptionalContentProperties; class PDFOptionalContentProperties;
class PDFOptionalContentConfiguration; class PDFOptionalContentConfiguration;
@ -73,6 +74,86 @@ constexpr OCState operator |(OCState left, OCState right)
return (left == OCState::ON || right == OCState::ON) ? OCState::ON : OCState::OFF; return (left == OCState::ON || right == OCState::ON) ? OCState::ON : OCState::OFF;
} }
/// Object describing optional content membership dictionary
class PDFOptionalContentMembershipObject
{
public:
explicit PDFOptionalContentMembershipObject() = default;
constexpr inline PDFOptionalContentMembershipObject(const PDFOptionalContentMembershipObject&) = delete;
constexpr inline PDFOptionalContentMembershipObject(PDFOptionalContentMembershipObject&&) = default;
constexpr inline PDFOptionalContentMembershipObject& operator=(const PDFOptionalContentMembershipObject&) = delete;
constexpr inline PDFOptionalContentMembershipObject& operator=(PDFOptionalContentMembershipObject&&) = default;
/// Creates optional content membership dictionary. If creation fails, then
/// exception is thrown.
/// \param document Document owning the membership dictionary
/// \param object Object to be parsed
static PDFOptionalContentMembershipObject create(const PDFDocument* document, const PDFObject& object);
/// Returns true, if this object is valid
bool isValid() const { return static_cast<bool>(m_expression); }
/// Evaluate objects. If error occurs, then Uknown state is returned.
/// \param activity Activity
OCState evaluate(const PDFOptionalContentActivity* activity) const;
private:
enum class Operator
{
And,
Or,
Not
};
/// Node in the expression tree
class Node
{
public:
inline explicit Node() = default;
virtual ~Node() = default;
virtual OCState evaluate(const PDFOptionalContentActivity* activity) const = 0;
};
/// Node reprsenting optional content group
class OptionalContentGroupNode : public Node
{
public:
inline explicit OptionalContentGroupNode(PDFObjectReference optionalContentGroup) :
m_optionalContentGroup(optionalContentGroup)
{
}
virtual OCState evaluate(const PDFOptionalContentActivity* activity) const override;
private:
PDFObjectReference m_optionalContentGroup;
};
/// Node representing operator
class OperatorNode : public Node
{
public:
inline explicit OperatorNode(Operator operator_, std::vector<std::unique_ptr<Node>>&& nodes) :
m_operator(operator_),
m_children(qMove(nodes))
{
}
virtual OCState evaluate(const PDFOptionalContentActivity* activity) const override;
private:
Operator m_operator;
std::vector<std::unique_ptr<Node>> m_children;
};
std::unique_ptr<Node> m_expression;
};
/// Activeness of the optional content /// Activeness of the optional content
class PDFFORQTLIBSHARED_EXPORT PDFOptionalContentActivity : public QObject class PDFFORQTLIBSHARED_EXPORT PDFOptionalContentActivity : public QObject
{ {
@ -93,12 +174,15 @@ public:
/// optional content groups in radio button group can be switched off). /// optional content groups in radio button group can be switched off).
/// \param ocg Optional content group /// \param ocg Optional content group
/// \param state New state of the optional content group /// \param state New state of the optional content group
/// \note If something changed, then signalp \p optionalContentGroupStateChanged is emitted. /// \note If something changed, then signal \p optionalContentGroupStateChanged is emitted.
void setState(PDFObjectReference ocg, OCState state); void setState(PDFObjectReference ocg, OCState state);
/// Applies configuration to the current state of optional content groups /// Applies configuration to the current state of optional content groups
void applyConfiguration(const PDFOptionalContentConfiguration& configuration); void applyConfiguration(const PDFOptionalContentConfiguration& configuration);
/// Returns the properties of optional content
const PDFOptionalContentProperties* getProperties() const { return m_properties; }
signals: signals:
void optionalContentGroupStateChanged(PDFObjectReference ocg, OCState state); void optionalContentGroupStateChanged(PDFObjectReference ocg, OCState state);

View File

@ -172,16 +172,22 @@ void PDFPageContentProcessor::initDictionaries(const PDFObject& resourcesObject)
m_fontDictionary = getDictionary("Font"); m_fontDictionary = getDictionary("Font");
m_xobjectDictionary = getDictionary("XObject"); m_xobjectDictionary = getDictionary("XObject");
m_extendedGraphicStateDictionary = getDictionary(PDF_RESOURCE_EXTGSTATE); m_extendedGraphicStateDictionary = getDictionary(PDF_RESOURCE_EXTGSTATE);
m_propertiesDictionary = getDictionary("Properties");
} }
PDFPageContentProcessor::PDFPageContentProcessor(const PDFPage* page, const PDFDocument* document, const PDFFontCache* fontCache) : PDFPageContentProcessor::PDFPageContentProcessor(const PDFPage* page,
const PDFDocument* document,
const PDFFontCache* fontCache,
const PDFOptionalContentActivity* optionalContentActivity) :
m_page(page), m_page(page),
m_document(document), m_document(document),
m_fontCache(fontCache), m_fontCache(fontCache),
m_optionalContentActivity(optionalContentActivity),
m_colorSpaceDictionary(nullptr), m_colorSpaceDictionary(nullptr),
m_fontDictionary(nullptr), m_fontDictionary(nullptr),
m_xobjectDictionary(nullptr), m_xobjectDictionary(nullptr),
m_extendedGraphicStateDictionary(nullptr), m_extendedGraphicStateDictionary(nullptr),
m_propertiesDictionary(nullptr),
m_textBeginEndState(0), m_textBeginEndState(0),
m_compatibilityBeginEndState(0) m_compatibilityBeginEndState(0)
{ {
@ -312,6 +318,28 @@ void PDFPageContentProcessor::performRestoreGraphicState(ProcessOrder order)
Q_UNUSED(order); Q_UNUSED(order);
} }
void PDFPageContentProcessor::performMarkedContentPoint(const QByteArray& tag, const PDFObject& properties)
{
Q_UNUSED(tag);
Q_UNUSED(properties);
}
void PDFPageContentProcessor::performMarkedContentBegin(const QByteArray& tag, const PDFObject& properties)
{
Q_UNUSED(tag);
Q_UNUSED(properties);
}
void PDFPageContentProcessor::performMarkedContentEnd()
{
}
bool PDFPageContentProcessor::isContentSuppressed() const
{
return std::any_of(m_markedContentStack.cbegin(), m_markedContentStack.cend(), [](const MarkedContentState& state) { return state.contentSuppressed; });
}
void PDFPageContentProcessor::processContent(const QByteArray& content) void PDFPageContentProcessor::processContent(const QByteArray& content)
{ {
PDFLexicalAnalyzer parser(content.constBegin(), content.constEnd()); PDFLexicalAnalyzer parser(content.constBegin(), content.constEnd());
@ -791,6 +819,41 @@ void PDFPageContentProcessor::processCommand(const QByteArray& command)
break; break;
} }
case Operator::MarkedContentPoint:
{
// MP, marked content point
invokeOperator(&PDFPageContentProcessor::operatorMarkedContentPoint);
break;
}
case Operator::MarkedContentPointWithProperties:
{
// DP, marked content point with properties
operatorMarkedContentPointWithProperties(readOperand<PDFOperandName>(0), readObjectFromOperandStack(1));
break;
}
case Operator::MarkedContentBegin:
{
// BMC, begin of sequence of marked content
invokeOperator(&PDFPageContentProcessor::operatorMarkedContentBegin);
break;
}
case Operator::MarkedContentBeginWithProperties:
{
// BDC, begin of sequence of marked content with properties
operatorMarkedContentBeginWithProperties(readOperand<PDFOperandName>(0), readObjectFromOperandStack(1));
break;
}
case Operator::MarkedContentEnd:
{
// EMC, end of marked content sequence
operatorMarkedContentEnd();
break;
}
case Operator::CompatibilityBegin: case Operator::CompatibilityBegin:
{ {
operatorCompatibilityBegin(); operatorCompatibilityBegin();
@ -1889,6 +1952,59 @@ void PDFPageContentProcessor::operatorPaintXObject(PDFPageContentProcessor::PDFO
} }
} }
void PDFPageContentProcessor::operatorMarkedContentPoint(PDFOperandName name)
{
performMarkedContentPoint(name.name, PDFObject());
}
void PDFPageContentProcessor::operatorMarkedContentPointWithProperties(PDFOperandName name, PDFObject properties)
{
performMarkedContentPoint(name.name, properties);
}
void PDFPageContentProcessor::operatorMarkedContentBegin(PDFOperandName name)
{
operatorMarkedContentBeginWithProperties(qMove(name), PDFObject());
}
void PDFPageContentProcessor::operatorMarkedContentBeginWithProperties(PDFOperandName name, PDFObject properties)
{
// TODO: Handle optional content of images/forms
// Handle the optional content
if (name.name == "OC")
{
PDFObjectReference ocg;
if (m_propertiesDictionary && properties.isName())
{
const PDFObject& ocgObject = m_propertiesDictionary->get(properties.getString());
if (ocgObject.isReference())
{
ocg = ocgObject.getReference();
}
}
m_markedContentStack.emplace_back(name.name, MarkedContentKind::OptionalContent, isContentSuppressedByOC(ocg));
}
else
{
m_markedContentStack.emplace_back(name.name, MarkedContentKind::Other, false);
}
performMarkedContentBegin(name.name, properties);
}
void PDFPageContentProcessor::operatorMarkedContentEnd()
{
if (m_markedContentStack.empty())
{
throw PDFRendererException(RenderErrorType::Error, PDFTranslationContext::tr("Mismatched begin/end of marked content."));
}
m_markedContentStack.pop_back();
performMarkedContentEnd();
}
void PDFPageContentProcessor::operatorCompatibilityBegin() void PDFPageContentProcessor::operatorCompatibilityBegin()
{ {
++m_compatibilityBeginEndState; ++m_compatibilityBeginEndState;
@ -2024,6 +2140,53 @@ void PDFPageContentProcessor::checkFillingColor()
} }
} }
PDFObject PDFPageContentProcessor::readObjectFromOperandStack(size_t startPosition) const
{
auto tokenFetcher = [this, &startPosition]()
{
if (startPosition < m_operands.size())
{
return m_operands[startPosition++];
}
return PDFLexicalAnalyzer::Token();
};
PDFParser parser(tokenFetcher);
return parser.getObject();
}
bool PDFPageContentProcessor::isContentSuppressedByOC(PDFObjectReference ocgOrOcmd)
{
if (!m_optionalContentActivity)
{
// Optional content activity control is suppressed, treat all content as not suppressed
return false;
}
if (m_optionalContentActivity->getProperties()->hasOptionalContentGroup(ocgOrOcmd))
{
// Simplest case - we have single optional content group
return m_optionalContentActivity->getState(ocgOrOcmd) == OCState::OFF;
}
PDFOptionalContentMembershipObject ocmd;
try
{
ocmd = PDFOptionalContentMembershipObject::create(m_document, PDFObject::createReference(ocgOrOcmd));
}
catch (PDFParserException e)
{
m_errorList.push_back(PDFRenderError(RenderErrorType::Error, e.getMessage()));
}
if (ocmd.isValid())
{
return ocmd.evaluate(m_optionalContentActivity) == OCState::OFF;
}
return false;
}
PDFPageContentProcessor::PDFPageContentProcessorState::PDFPageContentProcessorState() : PDFPageContentProcessor::PDFPageContentProcessorState::PDFPageContentProcessorState() :
m_currentTransformationMatrix(), m_currentTransformationMatrix(),
m_fillColorSpace(), m_fillColorSpace(),
@ -2306,7 +2469,8 @@ PDFPageContentProcessor::PDFPageContentProcessorStateGuard::PDFPageContentProces
m_colorSpaceDictionary(processor->m_colorSpaceDictionary), m_colorSpaceDictionary(processor->m_colorSpaceDictionary),
m_fontDictionary(processor->m_fontDictionary), m_fontDictionary(processor->m_fontDictionary),
m_xobjectDictionary(processor->m_xobjectDictionary), m_xobjectDictionary(processor->m_xobjectDictionary),
m_extendedGraphicStateDictionary(processor->m_extendedGraphicStateDictionary) m_extendedGraphicStateDictionary(processor->m_extendedGraphicStateDictionary),
m_propertiesDictionary(processor->m_propertiesDictionary)
{ {
m_processor->operatorSaveGraphicState(); m_processor->operatorSaveGraphicState();
} }
@ -2318,6 +2482,7 @@ PDFPageContentProcessor::PDFPageContentProcessorStateGuard::~PDFPageContentProce
m_processor->m_fontDictionary = m_fontDictionary; m_processor->m_fontDictionary = m_fontDictionary;
m_processor->m_xobjectDictionary = m_xobjectDictionary; m_processor->m_xobjectDictionary = m_xobjectDictionary;
m_processor->m_extendedGraphicStateDictionary = m_extendedGraphicStateDictionary; m_processor->m_extendedGraphicStateDictionary = m_extendedGraphicStateDictionary;
m_processor->m_propertiesDictionary = m_propertiesDictionary;
m_processor->operatorRestoreGraphicState(); m_processor->operatorRestoreGraphicState();
} }

View File

@ -34,15 +34,18 @@
namespace pdf namespace pdf
{ {
static constexpr const char* PDF_RESOURCE_EXTGSTATE = "ExtGState"; class PDFOptionalContentActivity;
// TODO: Implement optional content groups static constexpr const char* PDF_RESOURCE_EXTGSTATE = "ExtGState";
/// Process the contents of the page. /// Process the contents of the page.
class PDFPageContentProcessor : public PDFRenderErrorReporter class PDFPageContentProcessor : public PDFRenderErrorReporter
{ {
public: public:
explicit PDFPageContentProcessor(const PDFPage* page, const PDFDocument* document, const PDFFontCache* fontCache); explicit PDFPageContentProcessor(const PDFPage* page,
const PDFDocument* document,
const PDFFontCache* fontCache,
const PDFOptionalContentActivity* optionalContentActivity);
virtual ~PDFPageContentProcessor(); virtual ~PDFPageContentProcessor();
enum class Operator enum class Operator
@ -379,6 +382,20 @@ protected:
/// \param order If this function is called before the operation, or after the operation. /// \param order If this function is called before the operation, or after the operation.
virtual void performRestoreGraphicState(ProcessOrder order); virtual void performRestoreGraphicState(ProcessOrder order);
/// Implement to react on marked content point. Properties object can be null, in case
/// that no properties are provided.
/// \param tag Tag of the marked content point
/// \param properties Properties of the marked content point.
virtual void performMarkedContentPoint(const QByteArray& tag, const PDFObject& properties);
/// Implement to react on marked content begin.
/// \param tag Tag of the marked content point
/// \param properties Properties of the marked content point.
virtual void performMarkedContentBegin(const QByteArray& tag, const PDFObject& properties);
/// Implement to react on marked content end
virtual void performMarkedContentEnd();
/// Returns current graphic state /// Returns current graphic state
const PDFPageContentProcessorState* getGraphicState() const { return &m_graphicState; } const PDFPageContentProcessorState* getGraphicState() const { return &m_graphicState; }
@ -386,6 +403,9 @@ protected:
/// \param error Error message /// \param error Error message
void addError(const QString& error) { m_errorList.append(PDFRenderError(RenderErrorType::Error, error)); } void addError(const QString& error) { m_errorList.append(PDFRenderError(RenderErrorType::Error, error)); }
/// Returns true, if graphic content is suppressed
bool isContentSuppressed() const;
private: private:
/// Initializes the resources dictionaries /// Initializes the resources dictionaries
void initDictionaries(const PDFObject& resourcesObject); void initDictionaries(const PDFObject& resourcesObject);
@ -406,6 +426,28 @@ private:
/// \param content Content stream of the form /// \param content Content stream of the form
void processForm(const QMatrix& matrix, const QRectF& boundingBox, const PDFObject& resources, const QByteArray& content); void processForm(const QMatrix& matrix, const QRectF& boundingBox, const PDFObject& resources, const QByteArray& content);
enum class MarkedContentKind
{
OptionalContent,
Other
};
struct MarkedContentState
{
inline explicit MarkedContentState() = default;
inline explicit MarkedContentState(const QByteArray& tag, MarkedContentKind kind, bool contentSuppressed) :
tag(tag),
kind(kind),
contentSuppressed(contentSuppressed)
{
}
QByteArray tag;
MarkedContentKind kind = MarkedContentKind::Other;
bool contentSuppressed = false;
};
struct PDFPageContentProcessorStateGuard struct PDFPageContentProcessorStateGuard
{ {
public: public:
@ -420,6 +462,7 @@ private:
const PDFDictionary* m_fontDictionary; const PDFDictionary* m_fontDictionary;
const PDFDictionary* m_xobjectDictionary; const PDFDictionary* m_xobjectDictionary;
const PDFDictionary* m_extendedGraphicStateDictionary; const PDFDictionary* m_extendedGraphicStateDictionary;
const PDFDictionary* m_propertiesDictionary;
}; };
/// Wrapper for PDF Name /// Wrapper for PDF Name
@ -593,6 +636,13 @@ private:
// XObject: Do // XObject: Do
void operatorPaintXObject(PDFOperandName name); ///< Do, paint the X Object (image, form, ...) void operatorPaintXObject(PDFOperandName name); ///< Do, paint the X Object (image, form, ...)
// Marked content: MP, DP, BMC, BDC, EMC
void operatorMarkedContentPoint(PDFOperandName name); ///< MP, marked content point
void operatorMarkedContentPointWithProperties(PDFOperandName name, PDFObject properties); ///< DP, marked content point with properties
void operatorMarkedContentBegin(PDFOperandName name); ///< BMC, begin of sequence of marked content
void operatorMarkedContentBeginWithProperties(PDFOperandName name, PDFObject properties); ///< BDC, begin of sequence of marked content with properties
void operatorMarkedContentEnd(); ///< EMC, end of marked content sequence
// Compatibility: BX, EX // Compatibility: BX, EX
void operatorCompatibilityBegin(); ///< BX, Compatibility mode begin (unrecognized operators are ignored) void operatorCompatibilityBegin(); ///< BX, Compatibility mode begin (unrecognized operators are ignored)
void operatorCompatibilityEnd(); ///< EX, Compatibility mode end void operatorCompatibilityEnd(); ///< EX, Compatibility mode end
@ -612,13 +662,22 @@ private:
/// Checks, if filling color is valid /// Checks, if filling color is valid
void checkFillingColor(); void checkFillingColor();
/// Read object from operand stack
PDFObject readObjectFromOperandStack(size_t startPosition) const;
/// Computes visibility of OCG/OCMD - returns false, if it is not suppressed,
/// or true, if it is suppressed.
bool isContentSuppressedByOC(PDFObjectReference ocgOrOcmd);
const PDFPage* m_page; const PDFPage* m_page;
const PDFDocument* m_document; const PDFDocument* m_document;
const PDFFontCache* m_fontCache; const PDFFontCache* m_fontCache;
const PDFOptionalContentActivity* m_optionalContentActivity;
const PDFDictionary* m_colorSpaceDictionary; const PDFDictionary* m_colorSpaceDictionary;
const PDFDictionary* m_fontDictionary; const PDFDictionary* m_fontDictionary;
const PDFDictionary* m_xobjectDictionary; const PDFDictionary* m_xobjectDictionary;
const PDFDictionary* m_extendedGraphicStateDictionary; const PDFDictionary* m_extendedGraphicStateDictionary;
const PDFDictionary* m_propertiesDictionary;
// Default color spaces // Default color spaces
PDFColorSpacePointer m_deviceGrayColorSpace; PDFColorSpacePointer m_deviceGrayColorSpace;
@ -631,6 +690,9 @@ private:
/// Stack with saved graphic states /// Stack with saved graphic states
std::stack<PDFPageContentProcessorState> m_stack; std::stack<PDFPageContentProcessorState> m_stack;
/// Stack with marked content
std::vector<MarkedContentState> m_markedContentStack;
/// Current graphic state /// Current graphic state
PDFPageContentProcessorState m_graphicState; PDFPageContentProcessorState m_graphicState;

View File

@ -22,9 +22,14 @@
namespace pdf namespace pdf
{ {
PDFPainter::PDFPainter(QPainter* painter,
PDFPainter::PDFPainter(QPainter* painter, PDFRenderer::Features features, QMatrix pagePointToDevicePointMatrix, const PDFPage* page, const PDFDocument* document, const PDFFontCache* fontCache) : PDFRenderer::Features features,
PDFPageContentProcessor(page, document, fontCache), QMatrix pagePointToDevicePointMatrix,
const PDFPage* page,
const PDFDocument* document,
const PDFFontCache* fontCache,
const PDFOptionalContentActivity* optionalContentActivity) :
PDFPageContentProcessor(page, document, fontCache, optionalContentActivity),
m_painter(painter), m_painter(painter),
m_features(features), m_features(features),
m_pagePointToDevicePointMatrix(pagePointToDevicePointMatrix) m_pagePointToDevicePointMatrix(pagePointToDevicePointMatrix)
@ -42,6 +47,12 @@ PDFPainter::~PDFPainter()
void PDFPainter::performPathPainting(const QPainterPath& path, bool stroke, bool fill, bool text, Qt::FillRule fillRule) void PDFPainter::performPathPainting(const QPainterPath& path, bool stroke, bool fill, bool text, Qt::FillRule fillRule)
{ {
if (isContentSuppressed())
{
// Content is suppressed, do not paint anything
return;
}
if ((!stroke && !fill) || path.isEmpty()) if ((!stroke && !fill) || path.isEmpty())
{ {
// No operation requested - either path is empty, or neither stroking or filling // No operation requested - either path is empty, or neither stroking or filling
@ -84,6 +95,12 @@ void PDFPainter::performClipping(const QPainterPath& path, Qt::FillRule fillRule
void PDFPainter::performImagePainting(const QImage& image) void PDFPainter::performImagePainting(const QImage& image)
{ {
if (isContentSuppressed())
{
// Content is suppressed, do not paint anything
return;
}
m_painter->save(); m_painter->save();
// TODO: Draw smooth images // TODO: Draw smooth images

View File

@ -40,12 +40,14 @@ public:
/// \param page Page, which will be drawn /// \param page Page, which will be drawn
/// \param document Document owning the page /// \param document Document owning the page
/// \param fontCache Font cache /// \param fontCache Font cache
/// \param optionalContentActivity Activity of optional content
explicit PDFPainter(QPainter* painter, explicit PDFPainter(QPainter* painter,
PDFRenderer::Features features, PDFRenderer::Features features,
QMatrix pagePointToDevicePointMatrix, QMatrix pagePointToDevicePointMatrix,
const PDFPage* page, const PDFPage* page,
const PDFDocument* document, const PDFDocument* document,
const PDFFontCache* fontCache); const PDFFontCache* fontCache,
const PDFOptionalContentActivity* optionalContentActivity);
virtual ~PDFPainter() override; virtual ~PDFPainter() override;
protected: protected:

View File

@ -641,8 +641,8 @@ PDFParser::PDFParser(const QByteArray& data, PDFParsingContext* context, Feature
m_lexicalAnalyzer(data.constData(), data.constData() + data.size()), m_lexicalAnalyzer(data.constData(), data.constData() + data.size()),
m_features(features) m_features(features)
{ {
m_lookAhead1 = m_lexicalAnalyzer.fetch(); m_lookAhead1 = fetch();
m_lookAhead2 = m_lexicalAnalyzer.fetch(); m_lookAhead2 = fetch();
} }
PDFParser::PDFParser(const char* begin, const char* end, PDFParsingContext* context, Features features) : PDFParser::PDFParser(const char* begin, const char* end, PDFParsingContext* context, Features features) :
@ -650,8 +650,18 @@ PDFParser::PDFParser(const char* begin, const char* end, PDFParsingContext* cont
m_lexicalAnalyzer(begin, end), m_lexicalAnalyzer(begin, end),
m_features(features) m_features(features)
{ {
m_lookAhead1 = m_lexicalAnalyzer.fetch(); m_lookAhead1 = fetch();
m_lookAhead2 = m_lexicalAnalyzer.fetch(); m_lookAhead2 = fetch();
}
PDFParser::PDFParser(std::function<PDFLexicalAnalyzer::Token ()> tokenFetcher) :
m_tokenFetcher(qMove(tokenFetcher)),
m_context(nullptr),
m_lexicalAnalyzer(nullptr, nullptr),
m_features(None)
{
m_lookAhead1 = fetch();
m_lookAhead2 = fetch();
} }
PDFObject PDFParser::getObject() PDFObject PDFParser::getObject()
@ -837,8 +847,8 @@ PDFObject PDFParser::getObject()
} }
// Refill lookahead tokens // Refill lookahead tokens
m_lookAhead1 = m_lexicalAnalyzer.fetch(); m_lookAhead1 = fetch();
m_lookAhead2 = m_lexicalAnalyzer.fetch(); m_lookAhead2 = fetch();
if (m_lookAhead1.type == PDFLexicalAnalyzer::TokenType::Command && if (m_lookAhead1.type == PDFLexicalAnalyzer::TokenType::Command &&
m_lookAhead1.data.toByteArray() == PDF_STREAM_END_COMMAND) m_lookAhead1.data.toByteArray() == PDF_STREAM_END_COMMAND)
@ -902,8 +912,8 @@ void PDFParser::seek(PDFInteger offset)
m_lexicalAnalyzer.seek(offset); m_lexicalAnalyzer.seek(offset);
// We must read lookahead symbols, because we invalidated them // We must read lookahead symbols, because we invalidated them
m_lookAhead1 = m_lexicalAnalyzer.fetch(); m_lookAhead1 = fetch();
m_lookAhead2 = m_lexicalAnalyzer.fetch(); m_lookAhead2 = fetch();
} }
bool PDFParser::fetchCommand(const char* command) bool PDFParser::fetchCommand(const char* command)
@ -921,7 +931,12 @@ bool PDFParser::fetchCommand(const char* command)
void PDFParser::shift() void PDFParser::shift()
{ {
m_lookAhead1 = std::move(m_lookAhead2); m_lookAhead1 = std::move(m_lookAhead2);
m_lookAhead2 = m_lexicalAnalyzer.fetch(); m_lookAhead2 = fetch();
}
PDFLexicalAnalyzer::Token PDFParser::fetch()
{
return m_tokenFetcher ? m_tokenFetcher() : m_lexicalAnalyzer.fetch();
} }
} // namespace pdf } // namespace pdf

View File

@ -276,13 +276,14 @@ public:
enum Feature enum Feature
{ {
None = 0x0000, None = 0x0000,
AllowStreams = 0x0001, AllowStreams = 0x0001
}; };
Q_DECLARE_FLAGS(Features, Feature) Q_DECLARE_FLAGS(Features, Feature)
explicit PDFParser(const QByteArray& data, PDFParsingContext* context, Features features); explicit PDFParser(const QByteArray& data, PDFParsingContext* context, Features features);
explicit PDFParser(const char* begin, const char* end, PDFParsingContext* context, Features features); explicit PDFParser(const char* begin, const char* end, PDFParsingContext* context, Features features);
explicit PDFParser(std::function<PDFLexicalAnalyzer::Token(void)> tokenFetcher);
/// Fetches single object from the stream. Does not check /// Fetches single object from the stream. Does not check
/// cyclical references. If object cannot be fetched, then /// cyclical references. If object cannot be fetched, then
@ -312,6 +313,11 @@ public:
private: private:
void shift(); void shift();
PDFLexicalAnalyzer::Token fetch();
/// Functor for fetching tokens
std::function<PDFLexicalAnalyzer::Token(void)> m_tokenFetcher;
/// Parsing context (multiple parsers can share it) /// Parsing context (multiple parsers can share it)
PDFParsingContext* m_context; PDFParsingContext* m_context;

View File

@ -22,9 +22,10 @@
namespace pdf namespace pdf
{ {
PDFRenderer::PDFRenderer(const PDFDocument* document, const PDFFontCache* fontCache) : PDFRenderer::PDFRenderer(const PDFDocument* document, const PDFFontCache* fontCache, const PDFOptionalContentActivity* optionalContentActivity) :
m_document(document), m_document(document),
m_fontCache(fontCache), m_fontCache(fontCache),
m_optionalContentActivity(optionalContentActivity),
m_features(Antialiasing | TextAntialiasing) m_features(Antialiasing | TextAntialiasing)
{ {
Q_ASSERT(document); Q_ASSERT(document);
@ -32,6 +33,7 @@ PDFRenderer::PDFRenderer(const PDFDocument* document, const PDFFontCache* fontCa
// TODO: Dodelat rotovani stranek // TODO: Dodelat rotovani stranek
// TODO: Dodelat obrazky - SMOOTH // TODO: Dodelat obrazky - SMOOTH
// TODO: Clipovani na stranku
QList<PDFRenderError> PDFRenderer::render(QPainter* painter, const QRectF& rectangle, size_t pageIndex) const QList<PDFRenderError> PDFRenderer::render(QPainter* painter, const QRectF& rectangle, size_t pageIndex) const
{ {
@ -54,7 +56,7 @@ QList<PDFRenderError> PDFRenderer::render(QPainter* painter, const QRectF& recta
matrix.translate(rectangle.left(), rectangle.bottom()); matrix.translate(rectangle.left(), rectangle.bottom());
matrix.scale(rectangle.width() / mediaBox.width(), -rectangle.height() / mediaBox.height()); matrix.scale(rectangle.width() / mediaBox.width(), -rectangle.height() / mediaBox.height());
PDFPainter processor(painter, m_features, matrix, page, m_document, m_fontCache); PDFPainter processor(painter, m_features, matrix, page, m_document, m_fontCache, m_optionalContentActivity);
return processor.processContents(); return processor.processContents();
} }
@ -72,7 +74,7 @@ QList<PDFRenderError> PDFRenderer::render(QPainter* painter, const QMatrix& matr
const PDFPage* page = catalog->getPage(pageIndex); const PDFPage* page = catalog->getPage(pageIndex);
Q_ASSERT(page); Q_ASSERT(page);
PDFPainter processor(painter, m_features, matrix, page, m_document, m_fontCache); PDFPainter processor(painter, m_features, matrix, page, m_document, m_fontCache, m_optionalContentActivity);
return processor.processContents(); return processor.processContents();
} }

View File

@ -26,12 +26,13 @@ class QPainter;
namespace pdf namespace pdf
{ {
class PDFFontCache; class PDFFontCache;
class PDFOptionalContentActivity;
/// Renders the PDF page on the painter, or onto an image. /// Renders the PDF page on the painter, or onto an image.
class PDFRenderer class PDFRenderer
{ {
public: public:
explicit PDFRenderer(const PDFDocument* document, const PDFFontCache* fontCache); explicit PDFRenderer(const PDFDocument* document, const PDFFontCache* fontCache, const PDFOptionalContentActivity* optionalContentActivity);
enum Feature enum Feature
{ {
@ -58,6 +59,7 @@ public:
private: private:
const PDFDocument* m_document; const PDFDocument* m_document;
const PDFFontCache* m_fontCache; const PDFFontCache* m_fontCache;
const PDFOptionalContentActivity* m_optionalContentActivity;
Features m_features; Features m_features;
}; };

View File

@ -269,7 +269,7 @@ void PDFViewerMainWindow::setDocument(const pdf::PDFDocument* document)
m_optionalContentActivity = new pdf::PDFOptionalContentActivity(document, pdf::OCUsage::View, this); m_optionalContentActivity = new pdf::PDFOptionalContentActivity(document, pdf::OCUsage::View, this);
} }
m_pdfWidget->setDocument(document); m_pdfWidget->setDocument(document, m_optionalContentActivity);
m_optionalContentTreeModel->setDocument(document); m_optionalContentTreeModel->setDocument(document);
m_optionalContentTreeModel->setActivity(m_optionalContentActivity); m_optionalContentTreeModel->setActivity(m_optionalContentActivity);
m_optionalContentTreeView->expandAll(); m_optionalContentTreeView->expandAll();