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) :
|
||||
QObject(parent),
|
||||
m_document(nullptr),
|
||||
m_optionalContentActivity(nullptr),
|
||||
m_pageLayoutMode(PageLayout::OneColumn),
|
||||
m_verticalSpacingMM(5.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)
|
||||
{
|
||||
m_document = document;
|
||||
m_fontCache.setDocument(document);
|
||||
m_optionalContentActivity = optionalContentActivity;
|
||||
connect(m_optionalContentActivity, &PDFOptionalContentActivity::optionalContentGroupStateChanged, this, &PDFDrawSpaceController::repaintNeeded);
|
||||
recalculate();
|
||||
}
|
||||
}
|
||||
@ -348,6 +351,7 @@ PDFDrawWidgetProxy::PDFDrawWidgetProxy(QObject* parent) :
|
||||
{
|
||||
m_controller = new PDFDrawSpaceController(this);
|
||||
connect(m_controller, &PDFDrawSpaceController::drawSpaceChanged, this, &PDFDrawWidgetProxy::update);
|
||||
connect(m_controller, &PDFDrawSpaceController::repaintNeeded, this, &PDFDrawWidgetProxy::repaintNeeded);
|
||||
}
|
||||
|
||||
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)
|
||||
@ -545,7 +549,7 @@ void PDFDrawWidgetProxy::draw(QPainter* painter, QRect rect)
|
||||
// Clear the page space by white color
|
||||
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);
|
||||
|
||||
if (!errors.empty())
|
||||
|
@ -47,9 +47,11 @@ public:
|
||||
virtual ~PDFDrawSpaceController() override;
|
||||
|
||||
/// 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
|
||||
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.
|
||||
/// \param pageLayout Page layout
|
||||
@ -93,8 +95,12 @@ public:
|
||||
/// Returns the font cache
|
||||
const PDFFontCache* getFontCache() const { return &m_fontCache; }
|
||||
|
||||
/// Returns optional content activity
|
||||
const PDFOptionalContentActivity* getOptionalContentActivity() const { return m_optionalContentActivity; }
|
||||
|
||||
signals:
|
||||
void drawSpaceChanged();
|
||||
void repaintNeeded();
|
||||
|
||||
private:
|
||||
/// Recalculates the draw space. Preserves setted page rotation.
|
||||
@ -118,6 +124,7 @@ private:
|
||||
static constexpr size_t REALIZED_FONT_CACHE_LIMIT = 128;
|
||||
|
||||
const PDFDocument* m_document;
|
||||
const PDFOptionalContentActivity* m_optionalContentActivity;
|
||||
|
||||
PageLayout m_pageLayoutMode;
|
||||
LayoutItems m_layoutItems;
|
||||
@ -140,9 +147,11 @@ public:
|
||||
virtual ~PDFDrawWidgetProxy() override;
|
||||
|
||||
/// 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
|
||||
void setDocument(const PDFDocument* document);
|
||||
/// \param optionalContentActivity Optional content activity
|
||||
void setDocument(const PDFDocument* document, const PDFOptionalContentActivity* optionalContentActivity);
|
||||
|
||||
void init(PDFWidget* widget);
|
||||
|
||||
@ -208,6 +217,7 @@ signals:
|
||||
void drawSpaceChanged();
|
||||
void pageLayoutChanged();
|
||||
void renderingError(PDFInteger pageIndex, const QList<PDFRenderError>& errors);
|
||||
void repaintNeeded();
|
||||
|
||||
private:
|
||||
struct LayoutItem
|
||||
|
@ -50,6 +50,7 @@ PDFWidget::PDFWidget(QWidget* parent) :
|
||||
m_proxy = new PDFDrawWidgetProxy(this);
|
||||
m_proxy->init(this);
|
||||
connect(m_proxy, &PDFDrawWidgetProxy::renderingError, this, &PDFWidget::onRenderingError);
|
||||
connect(m_proxy, &PDFDrawWidgetProxy::repaintNeeded, m_drawWidget, QOverload<>::of(&PDFDrawWidget::update));
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
|
@ -42,9 +42,11 @@ public:
|
||||
using PageRenderingErrors = std::map<PDFInteger, QList<PDFRenderError>>;
|
||||
|
||||
/// 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
|
||||
void setDocument(const PDFDocument* document);
|
||||
/// \param optionalContentActivity Optional content activity
|
||||
void setDocument(const PDFDocument* document, const PDFOptionalContentActivity* optionalContentActivity);
|
||||
|
||||
PDFDrawWidget* getDrawWidget() const { return m_drawWidget; }
|
||||
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
|
||||
|
@ -25,6 +25,7 @@ namespace pdf
|
||||
{
|
||||
|
||||
class PDFDocument;
|
||||
class PDFOptionalContentActivity;
|
||||
class PDFOptionalContentProperties;
|
||||
class PDFOptionalContentConfiguration;
|
||||
|
||||
@ -73,6 +74,86 @@ constexpr OCState operator |(OCState left, OCState right)
|
||||
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
|
||||
class PDFFORQTLIBSHARED_EXPORT PDFOptionalContentActivity : public QObject
|
||||
{
|
||||
@ -93,12 +174,15 @@ public:
|
||||
/// optional content groups in radio button group can be switched off).
|
||||
/// \param ocg 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);
|
||||
|
||||
/// Applies configuration to the current state of optional content groups
|
||||
void applyConfiguration(const PDFOptionalContentConfiguration& configuration);
|
||||
|
||||
/// Returns the properties of optional content
|
||||
const PDFOptionalContentProperties* getProperties() const { return m_properties; }
|
||||
|
||||
signals:
|
||||
void optionalContentGroupStateChanged(PDFObjectReference ocg, OCState state);
|
||||
|
||||
|
@ -172,16 +172,22 @@ void PDFPageContentProcessor::initDictionaries(const PDFObject& resourcesObject)
|
||||
m_fontDictionary = getDictionary("Font");
|
||||
m_xobjectDictionary = getDictionary("XObject");
|
||||
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_document(document),
|
||||
m_fontCache(fontCache),
|
||||
m_optionalContentActivity(optionalContentActivity),
|
||||
m_colorSpaceDictionary(nullptr),
|
||||
m_fontDictionary(nullptr),
|
||||
m_xobjectDictionary(nullptr),
|
||||
m_extendedGraphicStateDictionary(nullptr),
|
||||
m_propertiesDictionary(nullptr),
|
||||
m_textBeginEndState(0),
|
||||
m_compatibilityBeginEndState(0)
|
||||
{
|
||||
@ -312,6 +318,28 @@ void PDFPageContentProcessor::performRestoreGraphicState(ProcessOrder 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)
|
||||
{
|
||||
PDFLexicalAnalyzer parser(content.constBegin(), content.constEnd());
|
||||
@ -791,6 +819,41 @@ void PDFPageContentProcessor::processCommand(const QByteArray& command)
|
||||
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:
|
||||
{
|
||||
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()
|
||||
{
|
||||
++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() :
|
||||
m_currentTransformationMatrix(),
|
||||
m_fillColorSpace(),
|
||||
@ -2306,7 +2469,8 @@ PDFPageContentProcessor::PDFPageContentProcessorStateGuard::PDFPageContentProces
|
||||
m_colorSpaceDictionary(processor->m_colorSpaceDictionary),
|
||||
m_fontDictionary(processor->m_fontDictionary),
|
||||
m_xobjectDictionary(processor->m_xobjectDictionary),
|
||||
m_extendedGraphicStateDictionary(processor->m_extendedGraphicStateDictionary)
|
||||
m_extendedGraphicStateDictionary(processor->m_extendedGraphicStateDictionary),
|
||||
m_propertiesDictionary(processor->m_propertiesDictionary)
|
||||
{
|
||||
m_processor->operatorSaveGraphicState();
|
||||
}
|
||||
@ -2318,6 +2482,7 @@ PDFPageContentProcessor::PDFPageContentProcessorStateGuard::~PDFPageContentProce
|
||||
m_processor->m_fontDictionary = m_fontDictionary;
|
||||
m_processor->m_xobjectDictionary = m_xobjectDictionary;
|
||||
m_processor->m_extendedGraphicStateDictionary = m_extendedGraphicStateDictionary;
|
||||
m_processor->m_propertiesDictionary = m_propertiesDictionary;
|
||||
|
||||
m_processor->operatorRestoreGraphicState();
|
||||
}
|
||||
|
@ -34,15 +34,18 @@
|
||||
|
||||
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.
|
||||
class PDFPageContentProcessor : public PDFRenderErrorReporter
|
||||
{
|
||||
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();
|
||||
|
||||
enum class Operator
|
||||
@ -379,6 +382,20 @@ protected:
|
||||
/// \param order If this function is called before the operation, or after the operation.
|
||||
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
|
||||
const PDFPageContentProcessorState* getGraphicState() const { return &m_graphicState; }
|
||||
|
||||
@ -386,6 +403,9 @@ protected:
|
||||
/// \param error Error message
|
||||
void addError(const QString& error) { m_errorList.append(PDFRenderError(RenderErrorType::Error, error)); }
|
||||
|
||||
/// Returns true, if graphic content is suppressed
|
||||
bool isContentSuppressed() const;
|
||||
|
||||
private:
|
||||
/// Initializes the resources dictionaries
|
||||
void initDictionaries(const PDFObject& resourcesObject);
|
||||
@ -406,6 +426,28 @@ private:
|
||||
/// \param content Content stream of the form
|
||||
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
|
||||
{
|
||||
public:
|
||||
@ -420,6 +462,7 @@ private:
|
||||
const PDFDictionary* m_fontDictionary;
|
||||
const PDFDictionary* m_xobjectDictionary;
|
||||
const PDFDictionary* m_extendedGraphicStateDictionary;
|
||||
const PDFDictionary* m_propertiesDictionary;
|
||||
};
|
||||
|
||||
/// Wrapper for PDF Name
|
||||
@ -593,6 +636,13 @@ private:
|
||||
// XObject: Do
|
||||
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
|
||||
void operatorCompatibilityBegin(); ///< BX, Compatibility mode begin (unrecognized operators are ignored)
|
||||
void operatorCompatibilityEnd(); ///< EX, Compatibility mode end
|
||||
@ -612,13 +662,22 @@ private:
|
||||
/// Checks, if filling color is valid
|
||||
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 PDFDocument* m_document;
|
||||
const PDFFontCache* m_fontCache;
|
||||
const PDFOptionalContentActivity* m_optionalContentActivity;
|
||||
const PDFDictionary* m_colorSpaceDictionary;
|
||||
const PDFDictionary* m_fontDictionary;
|
||||
const PDFDictionary* m_xobjectDictionary;
|
||||
const PDFDictionary* m_extendedGraphicStateDictionary;
|
||||
const PDFDictionary* m_propertiesDictionary;
|
||||
|
||||
// Default color spaces
|
||||
PDFColorSpacePointer m_deviceGrayColorSpace;
|
||||
@ -631,6 +690,9 @@ private:
|
||||
/// Stack with saved graphic states
|
||||
std::stack<PDFPageContentProcessorState> m_stack;
|
||||
|
||||
/// Stack with marked content
|
||||
std::vector<MarkedContentState> m_markedContentStack;
|
||||
|
||||
/// Current graphic state
|
||||
PDFPageContentProcessorState m_graphicState;
|
||||
|
||||
|
@ -22,9 +22,14 @@
|
||||
namespace pdf
|
||||
{
|
||||
|
||||
|
||||
PDFPainter::PDFPainter(QPainter* painter, PDFRenderer::Features features, QMatrix pagePointToDevicePointMatrix, const PDFPage* page, const PDFDocument* document, const PDFFontCache* fontCache) :
|
||||
PDFPageContentProcessor(page, document, fontCache),
|
||||
PDFPainter::PDFPainter(QPainter* painter,
|
||||
PDFRenderer::Features features,
|
||||
QMatrix pagePointToDevicePointMatrix,
|
||||
const PDFPage* page,
|
||||
const PDFDocument* document,
|
||||
const PDFFontCache* fontCache,
|
||||
const PDFOptionalContentActivity* optionalContentActivity) :
|
||||
PDFPageContentProcessor(page, document, fontCache, optionalContentActivity),
|
||||
m_painter(painter),
|
||||
m_features(features),
|
||||
m_pagePointToDevicePointMatrix(pagePointToDevicePointMatrix)
|
||||
@ -42,6 +47,12 @@ PDFPainter::~PDFPainter()
|
||||
|
||||
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())
|
||||
{
|
||||
// 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)
|
||||
{
|
||||
if (isContentSuppressed())
|
||||
{
|
||||
// Content is suppressed, do not paint anything
|
||||
return;
|
||||
}
|
||||
|
||||
m_painter->save();
|
||||
|
||||
// TODO: Draw smooth images
|
||||
|
@ -40,12 +40,14 @@ public:
|
||||
/// \param page Page, which will be drawn
|
||||
/// \param document Document owning the page
|
||||
/// \param fontCache Font cache
|
||||
/// \param optionalContentActivity Activity of optional content
|
||||
explicit PDFPainter(QPainter* painter,
|
||||
PDFRenderer::Features features,
|
||||
QMatrix pagePointToDevicePointMatrix,
|
||||
const PDFPage* page,
|
||||
const PDFDocument* document,
|
||||
const PDFFontCache* fontCache);
|
||||
const PDFFontCache* fontCache,
|
||||
const PDFOptionalContentActivity* optionalContentActivity);
|
||||
virtual ~PDFPainter() override;
|
||||
|
||||
protected:
|
||||
|
@ -641,8 +641,8 @@ PDFParser::PDFParser(const QByteArray& data, PDFParsingContext* context, Feature
|
||||
m_lexicalAnalyzer(data.constData(), data.constData() + data.size()),
|
||||
m_features(features)
|
||||
{
|
||||
m_lookAhead1 = m_lexicalAnalyzer.fetch();
|
||||
m_lookAhead2 = m_lexicalAnalyzer.fetch();
|
||||
m_lookAhead1 = fetch();
|
||||
m_lookAhead2 = fetch();
|
||||
}
|
||||
|
||||
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_features(features)
|
||||
{
|
||||
m_lookAhead1 = m_lexicalAnalyzer.fetch();
|
||||
m_lookAhead2 = m_lexicalAnalyzer.fetch();
|
||||
m_lookAhead1 = 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()
|
||||
@ -837,8 +847,8 @@ PDFObject PDFParser::getObject()
|
||||
}
|
||||
|
||||
// Refill lookahead tokens
|
||||
m_lookAhead1 = m_lexicalAnalyzer.fetch();
|
||||
m_lookAhead2 = m_lexicalAnalyzer.fetch();
|
||||
m_lookAhead1 = fetch();
|
||||
m_lookAhead2 = fetch();
|
||||
|
||||
if (m_lookAhead1.type == PDFLexicalAnalyzer::TokenType::Command &&
|
||||
m_lookAhead1.data.toByteArray() == PDF_STREAM_END_COMMAND)
|
||||
@ -902,8 +912,8 @@ void PDFParser::seek(PDFInteger offset)
|
||||
m_lexicalAnalyzer.seek(offset);
|
||||
|
||||
// We must read lookahead symbols, because we invalidated them
|
||||
m_lookAhead1 = m_lexicalAnalyzer.fetch();
|
||||
m_lookAhead2 = m_lexicalAnalyzer.fetch();
|
||||
m_lookAhead1 = fetch();
|
||||
m_lookAhead2 = fetch();
|
||||
}
|
||||
|
||||
bool PDFParser::fetchCommand(const char* command)
|
||||
@ -921,7 +931,12 @@ bool PDFParser::fetchCommand(const char* command)
|
||||
void PDFParser::shift()
|
||||
{
|
||||
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
|
||||
|
@ -276,13 +276,14 @@ public:
|
||||
enum Feature
|
||||
{
|
||||
None = 0x0000,
|
||||
AllowStreams = 0x0001,
|
||||
AllowStreams = 0x0001
|
||||
};
|
||||
|
||||
Q_DECLARE_FLAGS(Features, Feature)
|
||||
|
||||
explicit PDFParser(const QByteArray& data, 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
|
||||
/// cyclical references. If object cannot be fetched, then
|
||||
@ -312,6 +313,11 @@ public:
|
||||
private:
|
||||
void shift();
|
||||
|
||||
PDFLexicalAnalyzer::Token fetch();
|
||||
|
||||
/// Functor for fetching tokens
|
||||
std::function<PDFLexicalAnalyzer::Token(void)> m_tokenFetcher;
|
||||
|
||||
/// Parsing context (multiple parsers can share it)
|
||||
PDFParsingContext* m_context;
|
||||
|
||||
|
@ -22,9 +22,10 @@
|
||||
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_fontCache(fontCache),
|
||||
m_optionalContentActivity(optionalContentActivity),
|
||||
m_features(Antialiasing | TextAntialiasing)
|
||||
{
|
||||
Q_ASSERT(document);
|
||||
@ -32,6 +33,7 @@ PDFRenderer::PDFRenderer(const PDFDocument* document, const PDFFontCache* fontCa
|
||||
|
||||
// TODO: Dodelat rotovani stranek
|
||||
// TODO: Dodelat obrazky - SMOOTH
|
||||
// TODO: Clipovani na stranku
|
||||
|
||||
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.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();
|
||||
}
|
||||
|
||||
@ -72,7 +74,7 @@ QList<PDFRenderError> PDFRenderer::render(QPainter* painter, const QMatrix& matr
|
||||
const PDFPage* page = catalog->getPage(pageIndex);
|
||||
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();
|
||||
}
|
||||
|
||||
|
@ -26,12 +26,13 @@ class QPainter;
|
||||
namespace pdf
|
||||
{
|
||||
class PDFFontCache;
|
||||
class PDFOptionalContentActivity;
|
||||
|
||||
/// Renders the PDF page on the painter, or onto an image.
|
||||
class PDFRenderer
|
||||
{
|
||||
public:
|
||||
explicit PDFRenderer(const PDFDocument* document, const PDFFontCache* fontCache);
|
||||
explicit PDFRenderer(const PDFDocument* document, const PDFFontCache* fontCache, const PDFOptionalContentActivity* optionalContentActivity);
|
||||
|
||||
enum Feature
|
||||
{
|
||||
@ -58,6 +59,7 @@ public:
|
||||
private:
|
||||
const PDFDocument* m_document;
|
||||
const PDFFontCache* m_fontCache;
|
||||
const PDFOptionalContentActivity* m_optionalContentActivity;
|
||||
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_pdfWidget->setDocument(document);
|
||||
m_pdfWidget->setDocument(document, m_optionalContentActivity);
|
||||
m_optionalContentTreeModel->setDocument(document);
|
||||
m_optionalContentTreeModel->setActivity(m_optionalContentActivity);
|
||||
m_optionalContentTreeView->expandAll();
|
||||
|
Reference in New Issue
Block a user