mirror of
https://github.com/RetroDECK/ES-DE.git
synced 2025-04-10 19:15:13 +00:00
354 lines
11 KiB
Plaintext
354 lines
11 KiB
Plaintext
//========================================================================
|
|
//
|
|
// 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;
|
|
}
|
|
|
|
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;
|
|
}
|