| //======================================================================== |
| // |
| // 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 Albert Astals Cid <aacid@kde.org> |
| // Copyright 2008 Mark Kaplan <mkaplan@finjan.com> |
| // |
| // Released under the GPL (version 2, or later, at your option) |
| // |
| //======================================================================== |
| |
| #include <config.h> |
| |
| #ifdef USE_GCC_PRAGMAS |
| #pragma implementation |
| #endif |
| |
| #include "goo/gmem.h" |
| #include "goo/GooString.h" |
| #include "goo/GooList.h" |
| #include "Error.h" |
| // #include "PDFDocEncoding.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 = gTrue; |
| optionalContentGroups = new GooList(); |
| display = NULL; |
| |
| Object ocgList; |
| ocgObject->dictLookup("OCGs", &ocgList); |
| 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"); |
| ocgList.free(); |
| ok = gFalse; |
| return; |
| } |
| |
| // we now enumerate over the ocgList, and build up the optionalContentGroups list. |
| for(int i = 0; i < ocgList.arrayGetLength(); ++i) { |
| Object ocg; |
| ocgList.arrayGet(i, &ocg); |
| if (!ocg.isDict()) { |
| ocg.free(); |
| break; |
| } |
| OptionalContentGroup *thisOptionalContentGroup = new OptionalContentGroup(ocg.getDict()); |
| ocg.free(); |
| ocgList.arrayGetNF(i, &ocg); |
| // TODO: we should create a lookup map from Ref to the OptionalContentGroup |
| thisOptionalContentGroup->setRef( ocg.getRef() ); |
| ocg.free(); |
| // the default is ON - we change state later, depending on BaseState, ON and OFF |
| thisOptionalContentGroup->setState(OptionalContentGroup::On); |
| optionalContentGroups->append(thisOptionalContentGroup); |
| } |
| |
| Object defaultOcgConfig; |
| ocgObject->dictLookup("D", &defaultOcgConfig); |
| if (!defaultOcgConfig.isDict()) { |
| error(errSyntaxError, -1, "Expected the default config, but wasn't able to find it, or it isn't a Dictionary"); |
| defaultOcgConfig.free(); |
| ocgList.free(); |
| ok = gFalse; |
| return; |
| } |
| |
| Object baseState; |
| defaultOcgConfig.dictLookup("BaseState", &baseState); |
| if (baseState.isName("OFF")) { |
| for (int i = 0; i < optionalContentGroups->getLength(); ++i) { |
| OptionalContentGroup *group; |
| |
| group = (OptionalContentGroup *)optionalContentGroups->get(i); |
| group->setState(OptionalContentGroup::Off); |
| } |
| } |
| baseState.free(); |
| |
| Object on; |
| defaultOcgConfig.dictLookup("ON", &on); |
| if (on.isArray()) { |
| // ON is an optional element |
| for (int i = 0; i < on.arrayGetLength(); ++i) { |
| Object reference; |
| on.arrayGetNF(i, &reference); |
| if (!reference.isRef()) { |
| // there can be null entries |
| reference.free(); |
| break; |
| } |
| OptionalContentGroup *group = findOcgByRef( reference.getRef() ); |
| reference.free(); |
| if (!group) { |
| error(errSyntaxWarning, -1, "Couldn't find group for reference"); |
| break; |
| } |
| group->setState(OptionalContentGroup::On); |
| } |
| } |
| on.free(); |
| |
| Object off; |
| defaultOcgConfig.dictLookup("OFF", &off); |
| if (off.isArray()) { |
| // OFF is an optional element |
| for (int i = 0; i < off.arrayGetLength(); ++i) { |
| Object reference; |
| off.arrayGetNF(i, &reference); |
| if (!reference.isRef()) { |
| // there can be null entries |
| reference.free(); |
| break; |
| } |
| OptionalContentGroup *group = findOcgByRef( reference.getRef() ); |
| reference.free(); |
| if (!group) { |
| error(errSyntaxWarning, -1, "Couldn't find group for reference to set OFF"); |
| break; |
| } |
| group->setState(OptionalContentGroup::Off); |
| } |
| } |
| off.free(); |
| |
| defaultOcgConfig.dictLookup("Order", &order); |
| defaultOcgConfig.dictLookup("RBGroups", &rbgroups); |
| |
| ocgList.free(); |
| defaultOcgConfig.free(); |
| } |
| |
| OCGs::~OCGs() |
| { |
| deleteGooList(optionalContentGroups, OptionalContentGroup); |
| order.free(); |
| if (display) |
| delete display; |
| rbgroups.free(); |
| } |
| |
| |
| bool OCGs::hasOCGs() |
| { |
| return ( optionalContentGroups->getLength() > 0 ); |
| } |
| |
| OptionalContentGroup* OCGs::findOcgByRef( const Ref &ref) |
| { |
| //TODO: make this more efficient |
| OptionalContentGroup *ocg = NULL; |
| for (int i=0; i < optionalContentGroups->getLength(); ++i) { |
| ocg = (OptionalContentGroup*)optionalContentGroups->get(i); |
| if ( (ocg->getRef().num == ref.num) && (ocg->getRef().gen == ref.gen) ) { |
| return ocg; |
| } |
| } |
| |
| // not found |
| return NULL; |
| } |
| |
| OCDisplayNode *OCGs::getDisplayRoot() |
| { |
| if (display) |
| return display; |
| |
| if (order.isArray()) |
| display = OCDisplayNode::parse(&order, this, m_xref); |
| |
| return display; |
| } |
| |
| bool OCGs::optContentIsVisible( Object *dictRef ) |
| { |
| Object dictObj; |
| Dict *dict; |
| Object dictType; |
| Object ocg; |
| Object policy; |
| Object ve; |
| bool result = true; |
| |
| if (dictRef->isNull()) |
| return result; |
| |
| if (dictRef->isRef()) { |
| OptionalContentGroup *oc = findOcgByRef(dictRef->getRef()); |
| if (oc) |
| return oc->getState() == OptionalContentGroup::On; |
| } |
| |
| dictRef->fetch( m_xref, &dictObj ); |
| if ( ! dictObj.isDict() ) { |
| error(errSyntaxWarning, -1, "Unexpected oc reference target: {0:d}", dictObj.getType() ); |
| dictObj.free(); |
| return result; |
| } |
| dict = dictObj.getDict(); |
| // printf("checking if optContent is visible\n"); |
| dict->lookup("Type", &dictType); |
| if (dictType.isName("OCMD")) { |
| if (dict->lookup("VE", &ve)->isArray()) { |
| result = evalOCVisibilityExpr(&ve, 0); |
| } else { |
| dict->lookupNF("OCGs", &ocg); |
| if (ocg.isArray()) { |
| dict->lookup("P", &policy); |
| 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() ); |
| } |
| policy.free(); |
| } else if (ocg.isRef()) { |
| OptionalContentGroup *oc = findOcgByRef( ocg.getRef() ); |
| if ( oc && oc->getState() == OptionalContentGroup::Off ) { |
| result = false; |
| } else { |
| result = true ; |
| } |
| } |
| ocg.free(); |
| } |
| ve.free(); |
| } else if ( dictType.isName("OCG") ) { |
| OptionalContentGroup* oc = findOcgByRef( dictRef->getRef() ); |
| if ( oc && oc->getState() == OptionalContentGroup::Off ) { |
| result=false; |
| } |
| } |
| dictType.free(); |
| dictObj.free(); |
| // printf("visibility: %s\n", result? "on" : "off"); |
| return result; |
| } |
| |
| GBool OCGs::evalOCVisibilityExpr(Object *expr, int recursion) { |
| OptionalContentGroup *ocg; |
| Object expr2, op, obj; |
| GBool ret; |
| int i; |
| |
| if (recursion > visibilityExprRecursionLimit) { |
| error(errSyntaxError, -1, |
| "Loop detected in optional content visibility expression"); |
| return gTrue; |
| } |
| if (expr->isRef()) { |
| if ((ocg = findOcgByRef(expr->getRef()))) { |
| return ocg->getState() == OptionalContentGroup::On; |
| } |
| } |
| expr->fetch(m_xref, &expr2); |
| if (!expr2.isArray() || expr2.arrayGetLength() < 1) { |
| error(errSyntaxError, -1, |
| "Invalid optional content visibility expression"); |
| expr2.free(); |
| return gTrue; |
| } |
| expr2.arrayGet(0, &op); |
| if (op.isName("Not")) { |
| if (expr2.arrayGetLength() == 2) { |
| expr2.arrayGetNF(1, &obj); |
| ret = !evalOCVisibilityExpr(&obj, recursion + 1); |
| obj.free(); |
| } else { |
| error(errSyntaxError, -1, |
| "Invalid optional content visibility expression"); |
| ret = gTrue; |
| } |
| } else if (op.isName("And")) { |
| ret = gTrue; |
| for (i = 1; i < expr2.arrayGetLength() && ret; ++i) { |
| expr2.arrayGetNF(i, &obj); |
| ret = evalOCVisibilityExpr(&obj, recursion + 1); |
| obj.free(); |
| } |
| } else if (op.isName("Or")) { |
| ret = gFalse; |
| for (i = 1; i < expr2.arrayGetLength() && !ret; ++i) { |
| expr2.arrayGetNF(i, &obj); |
| ret = evalOCVisibilityExpr(&obj, recursion + 1); |
| obj.free(); |
| } |
| } else { |
| error(errSyntaxError, -1, |
| "Invalid optional content visibility expression"); |
| ret = gTrue; |
| } |
| op.free(); |
| expr2.free(); |
| return ret; |
| } |
| |
| bool OCGs::allOn( Array *ocgArray ) |
| { |
| for (int i = 0; i < ocgArray->getLength(); ++i) { |
| Object ocgItem; |
| ocgArray->getNF(i, &ocgItem); |
| 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) { |
| Object ocgItem; |
| ocgArray->getNF(i, &ocgItem); |
| 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) { |
| Object ocgItem; |
| ocgArray->getNF(i, &ocgItem); |
| 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) { |
| Object ocgItem; |
| ocgArray->getNF(i, &ocgItem); |
| if (ocgItem.isRef()) { |
| OptionalContentGroup* oc = findOcgByRef( ocgItem.getRef() ); |
| if ( oc && oc->getState() == OptionalContentGroup::Off ) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| //------------------------------------------------------------------------ |
| |
| OptionalContentGroup::OptionalContentGroup(Dict *ocgDict) : m_name(NULL) |
| { |
| Object obj1, obj2, obj3; |
| Object ocgName; |
| ocgDict->lookup("Name", &ocgName); |
| 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() ); |
| } |
| ocgName.free(); |
| |
| viewState = printState = ocUsageUnset; |
| if (ocgDict->lookup("Usage", &obj1)->isDict()) { |
| if (obj1.dictLookup("View", &obj2)->isDict()) { |
| if (obj2.dictLookup("ViewState", &obj3)->isName()) { |
| if (obj3.isName("ON")) { |
| viewState = ocUsageOn; |
| } else { |
| viewState = ocUsageOff; |
| } |
| } |
| obj3.free(); |
| } |
| obj2.free(); |
| if (obj1.dictLookup("Print", &obj2)->isDict()) { |
| if (obj2.dictLookup("PrintState", &obj3)->isName()) { |
| if (obj3.isName("ON")) { |
| printState = ocUsageOn; |
| } else { |
| printState = ocUsageOff; |
| } |
| } |
| obj3.free(); |
| } |
| obj2.free(); |
| } |
| obj1.free(); |
| } |
| |
| OptionalContentGroup::OptionalContentGroup(GooString *label) |
| { |
| m_name = label; |
| m_state = On; |
| } |
| |
| 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(Object *obj, OCGs *oc, |
| XRef *xref, int recursion) { |
| Object obj2, obj3; |
| OptionalContentGroup *ocgA; |
| OCDisplayNode *node, *child; |
| int i; |
| |
| if (recursion > displayNodeRecursionLimit) { |
| error(errSyntaxError, -1, "Loop detected in optional content order"); |
| return NULL; |
| } |
| if (obj->isRef()) { |
| if ((ocgA = oc->findOcgByRef(obj->getRef()))) { |
| return new OCDisplayNode(ocgA); |
| } |
| } |
| obj->fetch(xref, &obj2); |
| if (!obj2.isArray()) { |
| obj2.free(); |
| return NULL; |
| } |
| i = 0; |
| if (obj2.arrayGetLength() >= 1) { |
| if (obj2.arrayGet(0, &obj3)->isString()) { |
| node = new OCDisplayNode(obj3.getString()); |
| i = 1; |
| } else { |
| node = new OCDisplayNode(); |
| } |
| obj3.free(); |
| } else { |
| node = new OCDisplayNode(); |
| } |
| for (; i < obj2.arrayGetLength(); ++i) { |
| obj2.arrayGetNF(i, &obj3); |
| 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); |
| } |
| } |
| obj3.free(); |
| } |
| obj2.free(); |
| return node; |
| } |
| |
| OCDisplayNode::OCDisplayNode() { |
| name = NULL; |
| ocg = NULL; |
| children = NULL; |
| } |
| |
| OCDisplayNode::OCDisplayNode(GooString *nameA) { |
| name = new GooString(nameA); |
| ocg = NULL; |
| children = NULL; |
| } |
| |
| OCDisplayNode::OCDisplayNode(OptionalContentGroup *ocgA) { |
| name = (ocgA->getName()) ? ocgA->getName()->copy() : NULL; |
| ocg = ocgA; |
| children = NULL; |
| } |
| |
| void OCDisplayNode::addChild(OCDisplayNode *child) { |
| if (!children) { |
| children = new GooList(); |
| } |
| children->append(child); |
| } |
| |
| void OCDisplayNode::addChildren(GooList *childrenA) { |
| if (!children) { |
| children = new GooList(); |
| } |
| children->append(childrenA); |
| delete childrenA; |
| } |
| |
| GooList *OCDisplayNode::takeChildren() { |
| GooList *childrenA; |
| |
| childrenA = children; |
| children = NULL; |
| return childrenA; |
| } |
| |
| OCDisplayNode::~OCDisplayNode() { |
| gfree(name); |
| if (children) { |
| deleteGooList(children, OCDisplayNode); |
| } |
| } |
| |
| int OCDisplayNode::getNumChildren() { |
| if (!children) { |
| return 0; |
| } |
| return children->getLength(); |
| } |
| |
| OCDisplayNode *OCDisplayNode::getChild(int idx) { |
| return (OCDisplayNode *)children->get(idx); |
| } |