Commit 81891667 authored by Brad Hards's avatar Brad Hards

Add in the initial part of the optional content support.

To see this work, compare ClarityOCGs.pdf with and
without this change.

Right now we only handle optional content using
XObjects. Optional content using Marked Content has
infrastructure, but is not implemented. That will be
quite invasive in Gfx, and I'm not confident enough
to do it this late in the process.
parent 11b70bcd
......@@ -148,6 +148,7 @@ set(poppler_SRCS
poppler/Link.cc
poppler/NameToCharCode.cc
poppler/Object.cc
poppler/OptionalContent.cc
poppler/Outline.cc
poppler/OutputDev.cc
poppler/Page.cc
......
......@@ -25,6 +25,7 @@
#include "PageLabelInfo.h"
#include "Catalog.h"
#include "Form.h"
#include "OptionalContent.h"
//------------------------------------------------------------------------
// Catalog
......@@ -33,6 +34,7 @@
Catalog::Catalog(XRef *xrefA) {
Object catDict, pagesDict, pagesDictRef;
Object obj, obj2;
Object optContentProps;
char *alreadyRead;
int numPages0;
int i;
......@@ -45,6 +47,7 @@ Catalog::Catalog(XRef *xrefA) {
baseURI = NULL;
pageLabelInfo = NULL;
form = NULL;
optContent = NULL;
xref->getCatalog(&catDict);
if (!catDict.isDict()) {
......@@ -171,6 +174,11 @@ Catalog::Catalog(XRef *xrefA) {
// get the outline dictionary
catDict.dictLookup("Outlines", &outline);
// get the Optional Content dictionary
catDict.dictLookup("OCProperties", &optContentProps);
optContent = new OCGs(&optContentProps, xref);
optContentProps.free();
// perform form-related loading after all widgets have been loaded
if (form)
form->postWidgetsLoad();
......@@ -208,6 +216,7 @@ Catalog::~Catalog() {
}
delete pageLabelInfo;
delete form;
delete optContent;
metadata.free();
structTreeRoot.free();
outline.free();
......
......@@ -21,6 +21,7 @@ struct Ref;
class LinkDest;
class PageLabelInfo;
class Form;
class OCGs;
//------------------------------------------------------------------------
// NameTree
......@@ -162,6 +163,8 @@ public:
Object *getAcroForm() { return &acroForm; }
OCGs *getOptContentConfig() { return optContent; }
Form* getForm() { return form; }
enum PageMode {
......@@ -202,6 +205,7 @@ private:
Object structTreeRoot; // structure tree root dictionary
Object outline; // outline dictionary
Object acroForm; // AcroForm dictionary
OCGs *optContent; // Optional Content groups
GBool ok; // true if catalog is valid
PageLabelInfo *pageLabelInfo; // info about page labels
PageMode pageMode; // page mode
......
......@@ -36,6 +36,8 @@
#include "Error.h"
#include "Gfx.h"
#include "ProfileData.h"
#include "Catalog.h"
#include "OptionalContent.h"
// the MSVC math.h doesn't define this
#ifndef M_PI
......@@ -301,6 +303,9 @@ GfxResources::GfxResources(XRef *xref, Dict *resDict, GfxResources *nextA) {
// get graphics state parameter dictionary
resDict->lookup("ExtGState", &gStateDict);
// get properties dictionary
resDict->lookup("Properties", &propertiesDict);
} else {
fonts = NULL;
xObjDict.initNull();
......@@ -308,6 +313,7 @@ GfxResources::GfxResources(XRef *xref, Dict *resDict, GfxResources *nextA) {
patternDict.initNull();
shadingDict.initNull();
gStateDict.initNull();
propertiesDict.initNull();
}
next = nextA;
......@@ -322,6 +328,7 @@ GfxResources::~GfxResources() {
patternDict.free();
shadingDict.free();
gStateDict.free();
propertiesDict.free();
}
GfxFont *GfxResources::lookupFont(char *name) {
......@@ -366,6 +373,20 @@ GBool GfxResources::lookupXObjectNF(char *name, Object *obj) {
return gFalse;
}
GBool GfxResources::lookupMarkedContentNF(char *name, Object *obj) {
GfxResources *resPtr;
for (resPtr = this; resPtr; resPtr = resPtr->next) {
if (resPtr->propertiesDict.isDict()) {
if (!resPtr->propertiesDict.dictLookupNF(name, obj)->isNull())
return gTrue;
obj->free();
}
}
error(-1, "Marked Content '%s' is unknown", name);
return gFalse;
}
void GfxResources::lookupColorSpace(char *name, Object *obj) {
GfxResources *resPtr;
......@@ -437,7 +458,7 @@ GBool GfxResources::lookupGState(char *name, Object *obj) {
// Gfx
//------------------------------------------------------------------------
Gfx::Gfx(XRef *xrefA, OutputDev *outA, int pageNum, Dict *resDict,
Gfx::Gfx(XRef *xrefA, OutputDev *outA, int pageNum, Dict *resDict, Catalog *catalogA,
double hDPI, double vDPI, PDFRectangle *box,
PDFRectangle *cropBox, int rotate,
GBool (*abortCheckCbkA)(void *data),
......@@ -445,6 +466,7 @@ Gfx::Gfx(XRef *xrefA, OutputDev *outA, int pageNum, Dict *resDict,
int i;
xref = xrefA;
catalog = catalogA;
subPage = gFalse;
printCommands = globalParams->getPrintCommands();
profileCommands = globalParams->getProfileCommands();
......@@ -481,13 +503,14 @@ Gfx::Gfx(XRef *xrefA, OutputDev *outA, int pageNum, Dict *resDict,
}
}
Gfx::Gfx(XRef *xrefA, OutputDev *outA, Dict *resDict,
Gfx::Gfx(XRef *xrefA, OutputDev *outA, Dict *resDict, Catalog *catalogA,
PDFRectangle *box, PDFRectangle *cropBox,
GBool (*abortCheckCbkA)(void *data),
void *abortCheckCbkDataA) {
int i;
xref = xrefA;
catalog = catalogA;
subPage = gTrue;
printCommands = globalParams->getPrintCommands();
......@@ -3431,6 +3454,19 @@ void Gfx::opXObject(Object args[], int numArgs) {
obj1.free();
return;
}
obj1.streamGetDict()->lookupNF("OC", &obj2);
if (obj2.isNull()) {
// No OC entry - so we proceed as normal
} else if (obj2.isRef()) {
if ( ! catalog->getOptContentConfig()->optContentIsVisible( &obj2 ) ) {
return;
}
} else {
error(getPos(), "XObject OC value not null or dict: %i", obj2.getType());
}
obj2.free();
#if OPI_SUPPORT
obj1.streamGetDict()->lookup("OPI", &opiDict);
if (opiDict.isDict()) {
......@@ -4090,6 +4126,29 @@ void Gfx::opEndIgnoreUndef(Object args[], int numArgs) {
//------------------------------------------------------------------------
void Gfx::opBeginMarkedContent(Object args[], int numArgs) {
// TODO: we really need to be adding this to the markedContentStack
char* name0 = args[0].getName();
if ( strncmp( name0, "OC", 2) == 0 ) {
if ( numArgs >= 2 ) {
if (!args[1].isName()) {
error(getPos(), "Unexpected MC Type: %i", args[1].getType());
}
char* name1 = args[1].getName();
Object markedContent;
if ( res->lookupMarkedContentNF( name1, &markedContent ) ) {
if ( markedContent.isRef() ) {
bool visible = catalog->getOptContentConfig()->optContentIsVisible( &markedContent );
ocSuppressed = !(visible);
}
} else {
error(getPos(), "DID NOT find %s", name1);
}
} else {
error(getPos(), "insufficient arguments for Marked Content");
}
}
if (printCommands) {
printf(" marked content: %s ", args[0].getName());
if (numArgs == 2)
......@@ -4106,6 +4165,8 @@ void Gfx::opBeginMarkedContent(Object args[], int numArgs) {
}
void Gfx::opEndMarkedContent(Object args[], int numArgs) {
// TODO: we should turn this off based on the markedContentStack
ocSuppressed = false;
out->endMarkedContent(state);
}
......
......@@ -14,6 +14,7 @@
#endif
#include "goo/gtypes.h"
#include "goo/GooList.h"
#include "Object.h"
class GooString;
......@@ -43,6 +44,7 @@ class Gfx;
class PDFRectangle;
class AnnotBorder;
class AnnotColor;
class Catalog;
//------------------------------------------------------------------------
......@@ -84,6 +86,7 @@ public:
GfxFont *lookupFont(char *name);
GBool lookupXObject(char *name, Object *obj);
GBool lookupXObjectNF(char *name, Object *obj);
GBool lookupMarkedContentNF(char *name, Object *obj);
void lookupColorSpace(char *name, Object *obj);
GfxPattern *lookupPattern(char *name);
GfxShading *lookupShading(char *name);
......@@ -99,6 +102,7 @@ private:
Object patternDict;
Object shadingDict;
Object gStateDict;
Object propertiesDict;
GfxResources *next;
};
......@@ -110,14 +114,14 @@ class Gfx {
public:
// Constructor for regular output.
Gfx(XRef *xrefA, OutputDev *outA, int pageNum, Dict *resDict,
Gfx(XRef *xrefA, OutputDev *outA, int pageNum, Dict *resDict, Catalog *catalog,
double hDPI, double vDPI, PDFRectangle *box,
PDFRectangle *cropBox, int rotate,
GBool (*abortCheckCbkA)(void *data) = NULL,
void *abortCheckCbkDataA = NULL);
// Constructor for a sub-page object.
Gfx(XRef *xrefA, OutputDev *outA, Dict *resDict,
Gfx(XRef *xrefA, OutputDev *outA, Dict *resDict, Catalog *catalog,
PDFRectangle *box, PDFRectangle *cropBox,
GBool (*abortCheckCbkA)(void *data) = NULL,
void *abortCheckCbkDataA = NULL);
......@@ -144,12 +148,14 @@ public:
private:
XRef *xref; // the xref table for this PDF file
Catalog *catalog; // the Catalog for this PDF file
OutputDev *out; // output device
GBool subPage; // is this a sub-page object?
GBool printCommands; // print the drawing commands (for debugging)
GBool profileCommands; // profile the drawing commands (for debugging)
GfxResources *res; // resource stack
int updateLevel;
GBool ocSuppressed; // are we ignoring content based on OptionalContent?
GfxState *state; // current graphics state
GBool fontChanged; // set if font or text matrix has changed
......@@ -159,6 +165,8 @@ private:
// page/form/pattern
int formDepth;
GooList *markedContentStack; // current BMC/EMC stack
Parser *parser; // parser for page content stream(s)
GBool // callback to check for an abort
......
......@@ -147,6 +147,7 @@ poppler_include_HEADERS = \
Link.h \
NameToCharCode.h \
Object.h \
OptionalContent.h \
Outline.h \
OutputDev.h \
Page.h \
......@@ -212,6 +213,7 @@ libpoppler_la_SOURCES = \
Link.cc \
NameToCharCode.cc \
Object.cc \
OptionalContent.cc \
Outline.cc \
OutputDev.cc \
Page.cc \
......
//========================================================================
//
// OptionalContent.h
//
// Copyright 2007 Brad Hards <bradh@kde.org>
//
// 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 "PDFDocEncoding.h"
#include "OptionalContent.h"
//------------------------------------------------------------------------
OCGs::OCGs(Object *ocgObject, XRef *xref) :
m_orderArray(0), m_rBGroupsArray(), m_xref(xref)
{
optionalContentGroups = NULL;
if (!ocgObject->isDict()) {
// This isn't an error - OCProperties is optional.
return;
}
// we need to parse the dictionary here, and build optionalContentGroups
optionalContentGroups = new GooList();
Object ocgList;
ocgObject->dictLookup("OCGs", &ocgList);
if (!ocgList.isArray()) {
printf("PROBLEM: expected the optional content group list, but wasn't able to find it, or it isn't an Array\n");
}
// 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()) {
break;
}
OptionalContentGroup *thisOptionalContentGroup = new OptionalContentGroup(ocg.getDict(), xref);
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()) {
printf("PROBLEM: expected the default config, but wasn't able to find it, or it isn't a Dictionary\n");
}
#if 0
// this is untested - we need an example showing BaseState
Object baseState;
defaultOcgConfig.dictLookup("BaseState", &baseState);
if (baseState.isString()) {
// read the value, and set each OptionalContentGroup entry appropriately
}
baseState.free();
#endif
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
break;
}
OptionalContentGroup *group = findOcgByRef( reference.getRef() );
reference.free();
if (!group) {
printf("Couldn't find group for reference\n");
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
break;
}
OptionalContentGroup *group = findOcgByRef( reference.getRef() );
reference.free();
if (!group) {
printf("Couldn't find group for reference to set OFF\n");
break;
}
group->setState(OptionalContentGroup::Off);
}
}
off.free();
Object order;
defaultOcgConfig.dictLookup("Order", &order);
if ( (order.isArray()) && (order.arrayGetLength() > 0) ) {
m_orderArray = order.getArray();
}
Object rbgroups;
defaultOcgConfig.dictLookup("RBGroups", &rbgroups);
if ( (rbgroups.isArray()) && (rbgroups.arrayGetLength() > 0) ) {
m_rBGroupsArray = rbgroups.getArray();
}
ocgList.free();
defaultOcgConfig.free();
}
OCGs::~OCGs()
{
if (optionalContentGroups) {
deleteGooList(optionalContentGroups, OptionalContentGroup);
}
}
bool OCGs::hasOCGs()
{
if (!optionalContentGroups) {
return false;
}
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->ref().num == ref.num) && (ocg->ref().gen == ref.gen) ) {
return ocg;
}
}
// not found
return NULL;
}
bool OCGs::optContentIsVisible( Object *dictRef )
{
Object dictObj;
Dict *dict;
Object dictType;
Object ocg;
Object policy;
bool result = true;
dictRef->fetch( m_xref, &dictObj );
if ( ! dictObj.isDict() ) {
printf( "Unexpected oc reference target: %i\n", dictObj.getType() );
return result;
}
dict = dictObj.getDict();
// printf("checking if optContent is visible\n");
dict->lookup("Type", &dictType);
if (dictType.isName("OCMD")) {
// If we supported Visibility Expressions, we'd check
// for a VE entry, and then call out to the parser here...
// printf("found OCMD dict\n");
dict->lookup("P", &policy);
dict->lookupNF("OCGs", &ocg);
if (ocg.isArray()) {
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->state() == OptionalContentGroup::Off ) {
result = false;
} else {
result = true ;
}
}
ocg.free();
policy.free();
} else if ( dictType.isName("OCG") ) {
OptionalContentGroup* oc = findOcgByRef( dictRef->getRef() );
if ( oc ) {
printf("Found valid group object\n");
if ( oc->state() == OptionalContentGroup::Off ) {
result=false;
}
}
}
dictType.free();
// printf("visibility: %s\n", result? "on" : "off");
return result;
}
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->state() == 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->state() == 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->state() == 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->state() == OptionalContentGroup::Off ) {
return true;
}
}
}
return false;
}
//------------------------------------------------------------------------
OptionalContentGroup::OptionalContentGroup(Dict *ocgDict, XRef *xrefA)
{
Object ocgName;
ocgDict->lookupNF("Name", &ocgName);
if (!ocgName.isString()) {
printf("PROBLEM: expected the name of the OCG, but wasn't able to find it, or it isn't a String\n");
} else {
m_name = new GooString( ocgName.getString() );
}
ocgName.free();
}
OptionalContentGroup::OptionalContentGroup(GooString *label)
{
m_name = label;
m_state = On;
}
GooString* OptionalContentGroup::name() const
{
return m_name;
}
void OptionalContentGroup::setRef(const Ref ref)
{
m_ref = ref;
}
Ref OptionalContentGroup::ref() const
{
return m_ref;
}
OptionalContentGroup::~OptionalContentGroup()
{
delete m_name;
}
//========================================================================
//
// OptionalContent.h
//
// Copyright 2007 Brad Hards <bradh@kde.org>
//
// Released under the GPL (version 2, or later, at your option)
//
//========================================================================
#ifndef OPTIONALCONTENT_H
#define OPTIONALCONTENT_H
#ifdef USE_GCC_PRAGMAS
#pragma interface
#endif
#include "Object.h"
#include "CharTypes.h"
class GooString;
class GooList;
class XRef;
class OptionalContentGroup;
//------------------------------------------------------------------------
class OCGs {
public:
OCGs(Object *ocgObject, XRef *xref);
~OCGs();
bool hasOCGs();
GooList *getOCGs() const { return optionalContentGroups; }
OptionalContentGroup* findOcgByRef( const Ref &ref);
Array* getOrderArray() const { return m_orderArray; }
Array* getRBGroupsArray() const { return m_rBGroupsArray; }
bool optContentIsVisible( Object *dictRef );
private:
bool allOn( Array *ocgArray );
bool allOff( Array *ocgArray );
bool anyOn( Array *ocgArray );
bool anyOff( Array *ocgArray );
GooList *optionalContentGroups;
Array *m_orderArray;
Array *m_rBGroupsArray;
XRef *m_xref;
};
//------------------------------------------------------------------------
class OptionalContentGroup {
public:
enum State { On, Off };
OptionalContentGroup(Dict *dict, XRef *xrefA);
OptionalContentGroup(GooString *label);
~OptionalContentGroup();
GooString* name() const;
Ref ref() const;
void setRef(const Ref ref);
State state() { return m_state; };
void setState(State state) { m_state = state; };
private:
XRef *xref;
GooString *m_name;
Ref m_ref;
State m_state;
};
#endif
......@@ -18,6 +18,7 @@
#include "Catalog.h"
#include "Page.h"
#include "Annot.h"
#include "OptionalContent.h"
class GooString;
class BaseStream;
......@@ -67,6 +68,9 @@ public:
// Get catalog.
Catalog *getCatalog() { return catalog; }
// Get optional content configuration
OCGs *getOptContentConfig() { return catalog->getOptContentConfig();