| //======================================================================== |
| // |
| // OptionalContent.cc |
| // |
| // Copyright 2007 Brad Hards <bradh@kde.org> |
| // Copyright 2008 Pino Toscano <pino@kde.org> |
| // Copyright 2008, 2010 Carlos Garcia Campos <carlosgc@gnome.org> |
| // Copyright 2008, 2010, 2011, 2017-2019 Albert Astals Cid <aacid@kde.org> |
| // Copyright 2008 Mark Kaplan <mkaplan@finjan.com> |
| // Copyright 2018 Adam Reichold <adam.reichold@t-online.de> |
| // Copyright 2019 Oliver Sander <oliver.sander@tu-dresden.de> |
| // |
| // Released under the GPL (version 2, or later, at your option) |
| // |
| //======================================================================== |
| |
| #include <config.h> |
| |
| #include "goo/gmem.h" |
| #include "goo/GooString.h" |
| #include "Error.h" |
| #include "OptionalContent.h" |
| |
| // Max depth of nested visibility expressions. This is used to catch |
| // infinite loops in the visibility expression object structure. |
| #define visibilityExprRecursionLimit 50 |
| |
| // Max depth of nested display nodes. This is used to catch infinite |
| // loops in the "Order" object structure. |
| #define displayNodeRecursionLimit 50 |
| |
| //------------------------------------------------------------------------ |
| |
| OCGs::OCGs(Object *ocgObject, XRef *xref) : |
| m_xref(xref) |
| { |
| // we need to parse the dictionary here, and build optionalContentGroups |
| ok = true; |
| |
| Object ocgList = ocgObject->dictLookup("OCGs"); |
| if (!ocgList.isArray()) { |
| error(errSyntaxError, -1, "Expected the optional content group list, but wasn't able to find it, or it isn't an Array"); |
| ok = false; |
| return; |
| } |
| |
| // we now enumerate over the ocgList, and build up the optionalContentGroups list. |
| for(int i = 0; i < ocgList.arrayGetLength(); ++i) { |
| Object ocgDict = ocgList.arrayGet(i); |
| if (!ocgDict.isDict()) { |
| break; |
| } |
| auto thisOptionalContentGroup = std::make_unique<OptionalContentGroup>(ocgDict.getDict()); |
| const Object &ocgRef = ocgList.arrayGetNF(i); |
| if (!ocgRef.isRef()) { |
| break; |
| } |
| thisOptionalContentGroup->setRef( ocgRef.getRef() ); |
| // the default is ON - we change state later, depending on BaseState, ON and OFF |
| thisOptionalContentGroup->setState(OptionalContentGroup::On); |
| optionalContentGroups.emplace(ocgRef.getRef(), std::move(thisOptionalContentGroup)); |
| } |
| |
| Object defaultOcgConfig = ocgObject->dictLookup("D"); |
| if (!defaultOcgConfig.isDict()) { |
| error(errSyntaxError, -1, "Expected the default config, but wasn't able to find it, or it isn't a Dictionary"); |
| ok = false; |
| return; |
| } |
| |
| Object baseState = defaultOcgConfig.dictLookup("BaseState"); |
| if (baseState.isName("OFF")) { |
| for (auto &group : optionalContentGroups) { |
| group.second->setState(OptionalContentGroup::Off); |
| } |
| } |
| |
| Object on = defaultOcgConfig.dictLookup("ON"); |
| if (on.isArray()) { |
| // ON is an optional element |
| for (int i = 0; i < on.arrayGetLength(); ++i) { |
| const Object &reference = on.arrayGetNF(i); |
| if (!reference.isRef()) { |
| // there can be null entries |
| break; |
| } |
| OptionalContentGroup *group = findOcgByRef( reference.getRef() ); |
| if (!group) { |
| error(errSyntaxWarning, -1, "Couldn't find group for reference"); |
| break; |
| } |
| group->setState(OptionalContentGroup::On); |
| } |
| } |
| |
| Object off = defaultOcgConfig.dictLookup("OFF"); |
| if (off.isArray()) { |
| // OFF is an optional element |
| for (int i = 0; i < off.arrayGetLength(); ++i) { |
| const Object &reference = off.arrayGetNF(i); |
| if (!reference.isRef()) { |
| // there can be null entries |
| break; |
| } |
| OptionalContentGroup *group = findOcgByRef( reference.getRef() ); |
| if (!group) { |
| error(errSyntaxWarning, -1, "Couldn't find group for reference to set OFF"); |
| break; |
| } |
| group->setState(OptionalContentGroup::Off); |
| } |
| } |
| |
| order = defaultOcgConfig.dictLookup("Order"); |
| rbgroups = defaultOcgConfig.dictLookup("RBGroups"); |
| } |
| |
| bool OCGs::hasOCGs() const |
| { |
| return !( optionalContentGroups.empty() ); |
| } |
| |
| OptionalContentGroup* OCGs::findOcgByRef( const Ref ref ) |
| { |
| const auto ocg = optionalContentGroups.find( ref ); |
| return ocg != optionalContentGroups.end() ? ocg->second.get() : nullptr; |
| } |
| |
| OCDisplayNode *OCGs::getDisplayRoot() |
| { |
| if (display) |
| return display.get(); |
| |
| if (order.isArray()) |
| display.reset(OCDisplayNode::parse(&order, this, m_xref)); |
| |
| return display.get(); |
| } |
| |
| bool OCGs::optContentIsVisible( const Object *dictRef ) |
| { |
| Dict *dict; |
| bool result = true; |
| |
| if (dictRef->isNull()) |
| return result; |
| |
| if (dictRef->isRef()) { |
| OptionalContentGroup *oc = findOcgByRef(dictRef->getRef()); |
| if (oc) |
| return oc->getState() == OptionalContentGroup::On; |
| } |
| |
| Object dictObj = dictRef->fetch( m_xref); |
| if ( ! dictObj.isDict() ) { |
| error(errSyntaxWarning, -1, "Unexpected oc reference target: {0:d}", dictObj.getType() ); |
| return result; |
| } |
| dict = dictObj.getDict(); |
| Object dictType = dict->lookup("Type"); |
| if (dictType.isName("OCMD")) { |
| Object ve = dict->lookup("VE"); |
| if (ve.isArray()) { |
| result = evalOCVisibilityExpr(&ve, 0); |
| } else { |
| const Object &ocg = dict->lookupNF("OCGs"); |
| if (ocg.isArray()) { |
| Object policy = dict->lookup("P"); |
| if (policy.isName("AllOn")) { |
| result = allOn( ocg.getArray() ); |
| } else if (policy.isName("AllOff")) { |
| result = allOff( ocg.getArray() ); |
| } else if (policy.isName("AnyOff")) { |
| result = anyOff( ocg.getArray() ); |
| } else if ( (!policy.isName()) || (policy.isName("AnyOn") ) ) { |
| // this is the default |
| result = anyOn( ocg.getArray() ); |
| } |
| } else if (ocg.isRef()) { |
| OptionalContentGroup *oc = findOcgByRef( ocg.getRef() ); |
| if ( oc && oc->getState() == OptionalContentGroup::Off ) { |
| result = false; |
| } else { |
| result = true ; |
| } |
| } |
| } |
| } else if ( dictType.isName("OCG") && dictRef->isRef() ) { |
| OptionalContentGroup* oc = findOcgByRef( dictRef->getRef() ); |
| if ( oc && oc->getState() == OptionalContentGroup::Off ) { |
| result=false; |
| } |
| } |
| return result; |
| } |
| |
| bool OCGs::evalOCVisibilityExpr(const Object *expr, int recursion) { |
| OptionalContentGroup *ocg; |
| bool ret; |
| |
| if (recursion > visibilityExprRecursionLimit) { |
| error(errSyntaxError, -1, |
| "Loop detected in optional content visibility expression"); |
| return true; |
| } |
| if (expr->isRef()) { |
| if ((ocg = findOcgByRef(expr->getRef()))) { |
| return ocg->getState() == OptionalContentGroup::On; |
| } |
| } |
| Object expr2 = expr->fetch(m_xref); |
| if (!expr2.isArray() || expr2.arrayGetLength() < 1) { |
| error(errSyntaxError, -1, |
| "Invalid optional content visibility expression"); |
| return true; |
| } |
| Object op = expr2.arrayGet(0); |
| if (op.isName("Not")) { |
| if (expr2.arrayGetLength() == 2) { |
| const Object &obj = expr2.arrayGetNF(1); |
| ret = !evalOCVisibilityExpr(&obj, recursion + 1); |
| } else { |
| error(errSyntaxError, -1, |
| "Invalid optional content visibility expression"); |
| ret = true; |
| } |
| } else if (op.isName("And")) { |
| ret = true; |
| for (int i = 1; i < expr2.arrayGetLength() && ret; ++i) { |
| const Object &obj = expr2.arrayGetNF(i); |
| ret = evalOCVisibilityExpr(&obj, recursion + 1); |
| } |
| } else if (op.isName("Or")) { |
| ret = false; |
| for (int i = 1; i < expr2.arrayGetLength() && !ret; ++i) { |
| const Object &obj = expr2.arrayGetNF(i); |
| ret = evalOCVisibilityExpr(&obj, recursion + 1); |
| } |
| } else { |
| error(errSyntaxError, -1, |
| "Invalid optional content visibility expression"); |
| ret = true; |
| } |
| return ret; |
| } |
| |
| bool OCGs::allOn( Array *ocgArray ) |
| { |
| for (int i = 0; i < ocgArray->getLength(); ++i) { |
| const Object &ocgItem = ocgArray->getNF(i); |
| if (ocgItem.isRef()) { |
| OptionalContentGroup* oc = findOcgByRef( ocgItem.getRef() ); |
| if ( oc && oc->getState() == OptionalContentGroup::Off ) { |
| return false; |
| } |
| } |
| } |
| return true; |
| } |
| |
| bool OCGs::allOff( Array *ocgArray ) |
| { |
| for (int i = 0; i < ocgArray->getLength(); ++i) { |
| const Object &ocgItem = ocgArray->getNF(i); |
| if (ocgItem.isRef()) { |
| OptionalContentGroup* oc = findOcgByRef( ocgItem.getRef() ); |
| if ( oc && oc->getState() == OptionalContentGroup::On ) { |
| return false; |
| } |
| } |
| } |
| return true; |
| } |
| |
| bool OCGs::anyOn( Array *ocgArray ) |
| { |
| for (int i = 0; i < ocgArray->getLength(); ++i) { |
| const Object &ocgItem = ocgArray->getNF(i); |
| if (ocgItem.isRef()) { |
| OptionalContentGroup* oc = findOcgByRef( ocgItem.getRef() ); |
| if ( oc && oc->getState() == OptionalContentGroup::On ) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| bool OCGs::anyOff( Array *ocgArray ) |
| { |
| for (int i = 0; i < ocgArray->getLength(); ++i) { |
| const Object &ocgItem = ocgArray->getNF(i); |
| if (ocgItem.isRef()) { |
| OptionalContentGroup* oc = findOcgByRef( ocgItem.getRef() ); |
| if ( oc && oc->getState() == OptionalContentGroup::Off ) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| //------------------------------------------------------------------------ |
| |
| OptionalContentGroup::OptionalContentGroup(Dict *ocgDict) : m_name(nullptr) |
| { |
| Object ocgName = ocgDict->lookup("Name"); |
| if (!ocgName.isString()) { |
| error(errSyntaxWarning, -1, "Expected the name of the OCG, but wasn't able to find it, or it isn't a String"); |
| } else { |
| m_name = new GooString( ocgName.getString() ); |
| } |
| |
| viewState = printState = ocUsageUnset; |
| Object obj1 = ocgDict->lookup("Usage"); |
| if (obj1.isDict()) { |
| Object obj2 = obj1.dictLookup("View"); |
| if (obj2.isDict()) { |
| Object obj3 = obj2.dictLookup("ViewState"); |
| if (obj3.isName()) { |
| if (obj3.isName("ON")) { |
| viewState = ocUsageOn; |
| } else { |
| viewState = ocUsageOff; |
| } |
| } |
| } |
| obj2 = obj1.dictLookup("Print"); |
| if (obj2.isDict()) { |
| Object obj3 = obj2.dictLookup("PrintState"); |
| if (obj3.isName()) { |
| if (obj3.isName("ON")) { |
| printState = ocUsageOn; |
| } else { |
| printState = ocUsageOff; |
| } |
| } |
| } |
| } |
| } |
| |
| OptionalContentGroup::OptionalContentGroup(GooString *label) |
| { |
| m_name = label; |
| m_state = On; |
| } |
| |
| const GooString* OptionalContentGroup::getName() const |
| { |
| return m_name; |
| } |
| |
| void OptionalContentGroup::setRef(const Ref ref) |
| { |
| m_ref = ref; |
| } |
| |
| Ref OptionalContentGroup::getRef() const |
| { |
| return m_ref; |
| } |
| |
| OptionalContentGroup::~OptionalContentGroup() |
| { |
| delete m_name; |
| } |
| |
| //------------------------------------------------------------------------ |
| |
| OCDisplayNode *OCDisplayNode::parse(const Object *obj, OCGs *oc, |
| XRef *xref, int recursion) { |
| OptionalContentGroup *ocgA; |
| OCDisplayNode *node, *child; |
| int i; |
| |
| if (recursion > displayNodeRecursionLimit) { |
| error(errSyntaxError, -1, "Loop detected in optional content order"); |
| return nullptr; |
| } |
| if (obj->isRef()) { |
| if ((ocgA = oc->findOcgByRef(obj->getRef()))) { |
| return new OCDisplayNode(ocgA); |
| } |
| } |
| Object obj2 = obj->fetch(xref); |
| if (!obj2.isArray()) { |
| return nullptr; |
| } |
| i = 0; |
| if (obj2.arrayGetLength() >= 1) { |
| Object obj3 = obj2.arrayGet(0); |
| if (obj3.isString()) { |
| node = new OCDisplayNode(obj3.getString()); |
| i = 1; |
| } else { |
| node = new OCDisplayNode(); |
| } |
| } else { |
| node = new OCDisplayNode(); |
| } |
| for (; i < obj2.arrayGetLength(); ++i) { |
| const Object &obj3 = obj2.arrayGetNF(i); |
| if ((child = OCDisplayNode::parse(&obj3, oc, xref, recursion + 1))) { |
| if (!child->ocg && !child->name && node->getNumChildren() > 0) { |
| node->getChild(node->getNumChildren() - 1)->addChildren(child->takeChildren()); |
| delete child; |
| } else { |
| node->addChild(child); |
| } |
| } |
| } |
| return node; |
| } |
| |
| OCDisplayNode::OCDisplayNode() { |
| name = nullptr; |
| ocg = nullptr; |
| children = nullptr; |
| } |
| |
| OCDisplayNode::OCDisplayNode(const GooString *nameA) { |
| name = new GooString(nameA); |
| ocg = nullptr; |
| children = nullptr; |
| } |
| |
| OCDisplayNode::OCDisplayNode(OptionalContentGroup *ocgA) { |
| name = (ocgA->getName()) ? ocgA->getName()->copy() : nullptr; |
| ocg = ocgA; |
| children = nullptr; |
| } |
| |
| void OCDisplayNode::addChild(OCDisplayNode *child) { |
| if (!children) { |
| children = new std::vector<OCDisplayNode*>(); |
| } |
| children->push_back(child); |
| } |
| |
| void OCDisplayNode::addChildren(std::vector<OCDisplayNode*> *childrenA) { |
| if (!children) { |
| children = new std::vector<OCDisplayNode*>(); |
| } |
| children->reserve(children->size() + childrenA->size()); |
| children->insert(children->end(), childrenA->begin(), childrenA->end()); |
| delete childrenA; |
| } |
| |
| std::vector<OCDisplayNode*> *OCDisplayNode::takeChildren() { |
| std::vector<OCDisplayNode*> *childrenA = children; |
| |
| children = nullptr; |
| return childrenA; |
| } |
| |
| OCDisplayNode::~OCDisplayNode() { |
| delete name; |
| if (children) { |
| for (auto entry : *children) { |
| delete entry; |
| } |
| delete children; |
| } |
| } |
| |
| int OCDisplayNode::getNumChildren() const { |
| if (!children) { |
| return 0; |
| } |
| return children->size(); |
| } |
| |
| OCDisplayNode *OCDisplayNode::getChild(int idx) const { |
| return (*children)[idx]; |
| } |