mirror of
https://github.com/JakubMelka/PDF4QT.git
synced 2025-06-05 21:59:17 +02:00
Implementation of optional content in content processor (without XObjects)
This commit is contained in:
@ -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())
|
||||||
|
@ -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
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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; }
|
||||||
|
@ -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
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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:
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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();
|
||||||
|
Reference in New Issue
Block a user