[poppler] 3 commits - CMakeLists.txt poppler/Catalog.cc poppler/Catalog.h poppler/Gfx.cc poppler/Gfx.h poppler/Makefile.am poppler/OptionalContent.cc poppler/OptionalContent.h poppler/Page.cc poppler/PDFDoc.h poppler/PSOutputDev.cc qt4/src qt4/tests

Brad Hards bradh at kemper.freedesktop.org
Wed Feb 27 00:24:03 PST 2008


 CMakeLists.txt                       |    1 
 poppler/Catalog.cc                   |    9 
 poppler/Catalog.h                    |    4 
 poppler/Gfx.cc                       |   65 ++++
 poppler/Gfx.h                        |   12 
 poppler/Makefile.am                  |    2 
 poppler/OptionalContent.cc           |  322 ++++++++++++++++++++++
 poppler/OptionalContent.h            |   85 +++++
 poppler/PDFDoc.h                     |    5 
 poppler/PSOutputDev.cc               |    6 
 poppler/Page.cc                      |    2 
 qt4/src/.gitignore                   |    1 
 qt4/src/CMakeLists.txt               |    3 
 qt4/src/Makefile.am                  |    6 
 qt4/src/poppler-document.cc          |   13 
 qt4/src/poppler-optcontent-private.h |   57 +++
 qt4/src/poppler-optcontent.cc        |  365 +++++++++++++++++++++++++
 qt4/src/poppler-optcontent.h         |  108 +++++++
 qt4/src/poppler-private.h            |    4 
 qt4/src/poppler-qt4.h                |   20 +
 qt4/tests/.gitignore                 |    1 
 qt4/tests/CMakeLists.txt             |    1 
 qt4/tests/Makefile.am                |    5 
 qt4/tests/check_optcontent.cpp       |  499 +++++++++++++++++++++++++++++++++++
 24 files changed, 1588 insertions(+), 8 deletions(-)

New commits:
commit 6e2bb03b5ef256c03a8da1cbf9bbc87c593942ad
Author: Brad Hards <bradh at kde.org>
Date:   Wed Feb 27 19:23:49 2008 +1100

    Partial d-pointer implementation.

diff --git a/qt4/src/poppler-optcontent-private.h b/qt4/src/poppler-optcontent-private.h
new file mode 100644
index 0000000..b805f34
--- /dev/null
+++ b/qt4/src/poppler-optcontent-private.h
@@ -0,0 +1,57 @@
+/* poppler-optcontent-private.h: qt interface to poppler
+ *
+ * Copyright (C) 2007, Brad Hards <bradh at kde.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef POPPLER_OPTCONTENT_PRIVATE_H
+#define POPPLER_OPTCONTENT_PRIVATE_H
+
+#include "poppler-optcontent.h"
+
+#include "goo/GooList.h"
+
+namespace Poppler
+{
+
+  class RadioButtonGroup
+  {
+  public:
+    RadioButtonGroup( OptContentModel *ocModel, Array *rbarray);
+    ~RadioButtonGroup();
+    void setItemOn( OptContentItem *itemToSetOn );
+
+  private:
+    QList<OptContentItem*> itemsInGroup;
+  };
+
+  class OptContentModelPrivate
+  {
+    public:
+    OptContentModelPrivate( OCGs *optContent );
+    ~OptContentModelPrivate();
+
+    QMap<QString, OptContentItem*> m_optContentItems;
+    QList<RadioButtonGroup*> m_rbgroups;
+    OptContentItem *m_rootNode;
+
+    private:
+    void addChild( OptContentItem *parent, OptContentItem *child);
+    void parseOrderArray( OptContentItem *parentNode, Array *orderArray );
+  };
+}
+
+#endif
diff --git a/qt4/src/poppler-optcontent.cc b/qt4/src/poppler-optcontent.cc
index acad392..c7b48c3 100644
--- a/qt4/src/poppler-optcontent.cc
+++ b/qt4/src/poppler-optcontent.cc
@@ -17,7 +17,7 @@
  * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
  */
 
-#include "poppler-optcontent.h"
+#include "poppler-optcontent-private.h"
 
 #include "poppler/OptionalContent.h"
 
@@ -121,8 +121,7 @@ namespace Poppler
     child->setParent( this );
   }
 
-  OptContentModel::OptContentModel( OCGs *optContent, QObject *parent)
-    : QAbstractItemModel(parent)
+  OptContentModelPrivate::OptContentModelPrivate( OCGs *optContent )
   {
     m_rootNode = new OptContentItem();
     GooList *ocgs = optContent->getOCGs();
@@ -145,24 +144,16 @@ namespace Poppler
       parseOrderArray( m_rootNode, optContent->getOrderArray() );
     }
 
-    parseRBGroupsArray( optContent->getRBGroupsArray() );
   }
 
-  OptContentModel::~OptContentModel()
+  OptContentModelPrivate::~OptContentModelPrivate()
   {
     qDeleteAll( m_optContentItems );
     qDeleteAll( m_rbgroups );
     delete m_rootNode;
   }
 
-  void OptContentModel::setRootNode( OptContentItem *node )
-  {
-    delete m_rootNode;
-    m_rootNode = node;
-    reset();
-  }
-
-  void OptContentModel::parseOrderArray( OptContentItem *parentNode, Array *orderArray )
+  void OptContentModelPrivate::parseOrderArray( OptContentItem *parentNode, Array *orderArray )
   {
     OptContentItem *lastItem = parentNode;
     for (int i = 0; i < orderArray->getLength(); ++i) {
@@ -211,14 +202,34 @@ namespace Poppler
       }
       Array *rbarray = rbObj.getArray();
       RadioButtonGroup *rbg = new RadioButtonGroup( this, rbarray );
-      m_rbgroups.append( rbg );
+      d->m_rbgroups.append( rbg );
       rbObj.free();
     }
   }
 
+  OptContentModel::OptContentModel( OCGs *optContent, QObject *parent)
+    : QAbstractItemModel(parent)
+  {
+    d = new OptContentModelPrivate( optContent );
+
+    parseRBGroupsArray( optContent->getRBGroupsArray() );
+  }
+
+  OptContentModel::~OptContentModel()
+  {
+    delete d;
+  }
+
+  void OptContentModel::setRootNode( OptContentItem *node )
+  {
+    delete d->m_rootNode;
+    d->m_rootNode = node;
+    reset();
+  }
+
   QModelIndex OptContentModel::index(int row, int column, const QModelIndex &parent) const
   {
-    if (!m_rootNode) {
+    if (! d->m_rootNode) {
       return QModelIndex();
     }
 
@@ -328,17 +339,17 @@ namespace Poppler
     }
   }
 
-  void OptContentModel::addChild( OptContentItem *parent, OptContentItem *child )
+  void OptContentModelPrivate::addChild( OptContentItem *parent, OptContentItem *child )
   {
     parent->addChild( child );
   }
 
   OptContentItem* OptContentModel::itemFromRef( const QString &ref ) const
   {
-    if ( ! m_optContentItems.contains( ref ) ) {
+    if ( ! d->m_optContentItems.contains( ref ) ) {
       return 0;
     }
-    return m_optContentItems[ ref ];
+    return d->m_optContentItems[ ref ];
   }
 
   OptContentItem* OptContentModel::nodeFromIndex( const QModelIndex &index ) const
@@ -346,7 +357,7 @@ namespace Poppler
     if (index.isValid()) {
       return static_cast<OptContentItem *>(index.internalPointer());
     } else {
-      return m_rootNode;
+      return d->m_rootNode;
     }
   }
 }
diff --git a/qt4/src/poppler-optcontent.h b/qt4/src/poppler-optcontent.h
index fce4dfc..d1c2aa1 100644
--- a/qt4/src/poppler-optcontent.h
+++ b/qt4/src/poppler-optcontent.h
@@ -23,7 +23,6 @@
 #include <QtCore/QAbstractListModel>
 #include <QtCore/QStringList>
 
-#include "goo/GooList.h"
 class OptionalContentGroup;
 class OCGs;
 class Array;
@@ -32,17 +31,8 @@ namespace Poppler
 {
   class OptContentItem;
   class OptContentModel;
-
-  class RadioButtonGroup
-  {
-  public:
-    RadioButtonGroup( OptContentModel *ocModel, Array *rbarray);
-    ~RadioButtonGroup();
-    void setItemOn( OptContentItem *itemToSetOn );
-
-  private:
-    QList<OptContentItem*> itemsInGroup;
-  };
+  class OptContentModelPrivate;
+  class RadioButtonGroup;
 
   class OptContentItem
   {
@@ -97,8 +87,6 @@ namespace Poppler
 
     Qt::ItemFlags flags ( const QModelIndex & index ) const;
 
-    void addChild( OptContentItem *parent, OptContentItem *child);
-
     /**
        Get the OptContentItem corresponding to a given reference value.
 
@@ -109,13 +97,11 @@ namespace Poppler
     OptContentItem *itemFromRef( const QString &ref ) const;
 
     private:
-    OptContentItem *nodeFromIndex( const QModelIndex &index ) const;
-    void parseOrderArray( OptContentItem *parentNode, Array *orderArray );
     void parseRBGroupsArray( Array *rBGroupArray );
 
-    QMap<QString, OptContentItem*> m_optContentItems;
-    QList<RadioButtonGroup*> m_rbgroups;
-    OptContentItem *m_rootNode;
+    OptContentItem *nodeFromIndex( const QModelIndex &index ) const;
+
+    OptContentModelPrivate *d;
   };
 }
 
commit c627b7aa10ae9cdceb78b751a7e826170f402af0
Author: Brad Hards <bradh at kde.org>
Date:   Wed Feb 27 16:12:38 2008 +1100

    A couple of cleanups suggested by Pino.

diff --git a/qt4/src/poppler-document.cc b/qt4/src/poppler-document.cc
index ff84818..6e353cb 100644
--- a/qt4/src/poppler-document.cc
+++ b/qt4/src/poppler-document.cc
@@ -465,15 +465,15 @@ namespace Poppler {
         return result;
     }
 
-    bool Document::hasOptionalContent()
+    bool Document::hasOptionalContent() const
     {
         return ( m_doc->doc->getOptContentConfig()->hasOCGs() );
     }
 
-    OptContentModel *Document::optionalContentModel(QObject *parent)
+    OptContentModel *Document::optionalContentModel()
     {
         if (!m_doc->m_optContentModel) {
-	    m_doc->m_optContentModel = new OptContentModel(m_doc->doc->getOptContentConfig(), parent);
+	    m_doc->m_optContentModel = new OptContentModel(m_doc->doc->getOptContentConfig(), 0);
 	}
 	return (m_doc->m_optContentModel);
     }
diff --git a/qt4/src/poppler-qt4.h b/qt4/src/poppler-qt4.h
index 121ff49..65bde55 100644
--- a/qt4/src/poppler-qt4.h
+++ b/qt4/src/poppler-qt4.h
@@ -875,12 +875,14 @@ QString subject = m_doc->info("Subject");
 	   such as not including some content in printing, and
 	   displaying content in the appropriate language.
 	*/
-	bool hasOptionalContent();
+	bool hasOptionalContent() const;
 
 	/**
-	   Itemviews model for optional content
+	   Itemviews model for optional content.
+
+	   The model is owned by the document.
 	*/
-	OptContentModel *optionalContentModel(QObject *parent = 0);
+	OptContentModel *optionalContentModel();
 
 	/**
 	   Destructor.
commit 81891667e18fcf164af02f5f366de07f78d67c8f
Author: Brad Hards <bradh at kde.org>
Date:   Wed Feb 27 15:47:03 2008 +1100

    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.

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 5b130bf..b97b595 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -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
diff --git a/poppler/Catalog.cc b/poppler/Catalog.cc
index 2e12aed..0a6b005 100644
--- a/poppler/Catalog.cc
+++ b/poppler/Catalog.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();
diff --git a/poppler/Catalog.h b/poppler/Catalog.h
index bd49189..5b6e166 100644
--- a/poppler/Catalog.h
+++ b/poppler/Catalog.h
@@ -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
diff --git a/poppler/Gfx.cc b/poppler/Gfx.cc
index 3ae897e..e8d0b1c 100644
--- a/poppler/Gfx.cc
+++ b/poppler/Gfx.cc
@@ -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);
 }
 
diff --git a/poppler/Gfx.h b/poppler/Gfx.h
index 2c86105..10be638 100644
--- a/poppler/Gfx.h
+++ b/poppler/Gfx.h
@@ -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
diff --git a/poppler/Makefile.am b/poppler/Makefile.am
index cde5281..77f4f27 100644
--- a/poppler/Makefile.am
+++ b/poppler/Makefile.am
@@ -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 		\
diff --git a/poppler/OptionalContent.cc b/poppler/OptionalContent.cc
new file mode 100644
index 0000000..600c52b
--- /dev/null
+++ b/poppler/OptionalContent.cc
@@ -0,0 +1,322 @@
+//========================================================================
+//
+// OptionalContent.h
+//
+// Copyright 2007 Brad Hards <bradh at 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;
+}
+
diff --git a/poppler/OptionalContent.h b/poppler/OptionalContent.h
new file mode 100644
index 0000000..6afa023
--- /dev/null
+++ b/poppler/OptionalContent.h
@@ -0,0 +1,85 @@
+//========================================================================
+//
+// OptionalContent.h
+//
+// Copyright 2007 Brad Hards <bradh at 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
diff --git a/poppler/PDFDoc.h b/poppler/PDFDoc.h
index d6e4347..07fea34 100644
--- a/poppler/PDFDoc.h
+++ b/poppler/PDFDoc.h
@@ -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(); }
+
   // Get base stream.
   BaseStream *getBaseStream() { return str; }
 
@@ -213,6 +217,7 @@ private:
 #ifndef DISABLE_OUTLINE
   Outline *outline;
 #endif
+  OCGs *optContentConfig;
 
   GBool ok;
   int errCode;
diff --git a/poppler/PSOutputDev.cc b/poppler/PSOutputDev.cc
index ec65599..7eb486d 100644
--- a/poppler/PSOutputDev.cc
+++ b/poppler/PSOutputDev.cc
@@ -2514,7 +2514,7 @@ void PSOutputDev::setupType3Font(GfxFont *font, GooString *psName,
     box.y1 = m[1];
     box.x2 = m[2];
     box.y2 = m[3];
-    gfx = new Gfx(xref, this, resDict, &box, NULL);
+    gfx = new Gfx(xref, this, resDict, m_catalog, &box, NULL);
     inType3Char = gTrue;
     for (i = 0; i < charProcs->getLength(); ++i) {
       t3Cacheable = gFalse;
@@ -2833,7 +2833,7 @@ void PSOutputDev::setupForm(Ref id, Object *strObj) {
   box.y1 = bbox[1];
   box.x2 = bbox[2];
   box.y2 = bbox[3];
-  gfx = new Gfx(xref, this, resDict, &box, &box);
+  gfx = new Gfx(xref, this, resDict, m_catalog, &box, &box);
   gfx->display(strObj);
   delete gfx;
 
@@ -3668,7 +3668,7 @@ void PSOutputDev::tilingPatternFill(GfxState *state, Object *str,
   box.y1 = bbox[1];
   box.x2 = bbox[2];
   box.y2 = bbox[3];
-  gfx = new Gfx(xref, this, resDict, &box, NULL);
+  gfx = new Gfx(xref, this, resDict, m_catalog, &box, NULL);
   writePS("/x {\n");
   if (paintType == 2) {
     writePSFmt("{0:.4g} 0 {1:.4g} {2:.4g} {3:.4g} {4:.4g} setcachedevice\n",
diff --git a/poppler/Page.cc b/poppler/Page.cc
index 906d18e..b28a3ee 100644
--- a/poppler/Page.cc
+++ b/poppler/Page.cc
@@ -375,7 +375,7 @@ Gfx *Page::createGfx(OutputDev *out, double hDPI, double vDPI,
     printf("***** Rotate = %d\n", attrs->getRotate());
   }
 
-  gfx = new Gfx(xref, out, num, attrs->getResourceDict(),
+  gfx = new Gfx(xref, out, num, attrs->getResourceDict(), catalog,
 		hDPI, vDPI, &box, crop ? cropBox : (PDFRectangle *)NULL,
 		rotate, abortCheckCbk, abortCheckCbkData);
 
diff --git a/qt4/src/.gitignore b/qt4/src/.gitignore
index e4dd82d..53c9077 100644
--- a/qt4/src/.gitignore
+++ b/qt4/src/.gitignore
@@ -6,3 +6,4 @@ Makefile
 Makefile.in
 APIDOCS-html
 APIDOCS-latex
+moc_poppler-optcontent.cpp
diff --git a/qt4/src/CMakeLists.txt b/qt4/src/CMakeLists.txt
index 44ce5b9..a57cb1c 100644
--- a/qt4/src/CMakeLists.txt
+++ b/qt4/src/CMakeLists.txt
@@ -4,6 +4,7 @@ include_directories(
   ${CMAKE_CURRENT_SOURCE_DIR}
   ${CMAKE_SOURCE_DIR}/qt # for PageTransition
   ${QT4_INCLUDE_DIR}
+  ${CMAKE_CURRENT_BINARY_DIR}
 )
 
 set(poppler_qt4_SRCS
@@ -14,6 +15,7 @@ set(poppler_qt4_SRCS
   poppler-form.cc
   poppler-link.cc
   poppler-link-extractor.cc
+  poppler-optcontent.cc
   poppler-page.cc
   poppler-base-converter.cc
   poppler-pdf-converter.cc
@@ -29,6 +31,7 @@ if (ENABLE_SPLASH)
     ${CMAKE_SOURCE_DIR}/poppler/ArthurOutputDev.cc
   )
 endif (ENABLE_SPLASH)
+qt4_automoc(${poppler_qt4_SRCS})
 add_library(poppler-qt4 SHARED ${poppler_qt4_SRCS})
 set_target_properties(poppler-qt4 PROPERTIES VERSION 2.0.0 SOVERSION 2)
 target_link_libraries(poppler-qt4 poppler ${QT4_QTCORE_LIBRARY} ${QT4_QTGUI_LIBRARY} ${QT4_QTXML_LIBRARY})
diff --git a/qt4/src/Makefile.am b/qt4/src/Makefile.am
index 66f3009..cd9af86 100644
--- a/qt4/src/Makefile.am
+++ b/qt4/src/Makefile.am
@@ -12,6 +12,7 @@ poppler_include_HEADERS =			\
 	poppler-link.h				\
 	poppler-annotation.h			\
 	poppler-form.h				\
+	poppler-optcontent.h			\
 	poppler-export.h			\
 	../../qt/poppler-page-transition.h
 
@@ -26,6 +27,8 @@ libpoppler_qt4_la_SOURCES =			\
 	poppler-link.cc				\
 	poppler-annotation.cc			\
 	poppler-link-extractor.cc		\
+	poppler-optcontent.cc			\
+	moc_poppler-optcontent.cpp		\
 	../../qt/poppler-page-transition.cc	\
 	poppler-sound.cc			\
 	poppler-form.cc				\
@@ -55,3 +58,6 @@ endif
 
 libpoppler_qt4_la_LDFLAGS = -version-info 2:0:0
 
+# This rule lets GNU make create any moc_*.cpp from the equivalent *.h
+moc_%.cpp: %.h
+	moc $< -o $@
diff --git a/qt4/src/poppler-document.cc b/qt4/src/poppler-document.cc
index 7536834..ff84818 100644
--- a/qt4/src/poppler-document.cc
+++ b/qt4/src/poppler-document.cc
@@ -465,6 +465,19 @@ namespace Poppler {
         return result;
     }
 
+    bool Document::hasOptionalContent()
+    {
+        return ( m_doc->doc->getOptContentConfig()->hasOCGs() );
+    }
+
+    OptContentModel *Document::optionalContentModel(QObject *parent)
+    {
+        if (!m_doc->m_optContentModel) {
+	    m_doc->m_optContentModel = new OptContentModel(m_doc->doc->getOptContentConfig(), parent);
+	}
+	return (m_doc->m_optContentModel);
+    }
+
     QDateTime convertDate( char *dateString )
     {
         int year;
diff --git a/qt4/src/poppler-optcontent.cc b/qt4/src/poppler-optcontent.cc
new file mode 100644
index 0000000..acad392
--- /dev/null
+++ b/qt4/src/poppler-optcontent.cc
@@ -0,0 +1,354 @@
+/* poppler-optcontent.cc: qt interface to poppler
+ *
+ * Copyright (C) 2007, Brad Hards <bradh at kde.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "poppler-optcontent.h"
+
+#include "poppler/OptionalContent.h"
+
+#include "poppler-qt4.h"
+#include "poppler-private.h"
+
+#include <QtCore/QDebug>
+
+namespace Poppler
+{
+
+  RadioButtonGroup::RadioButtonGroup( OptContentModel *ocModel, Array *rbarray )
+  {
+    for (int i = 0; i < rbarray->getLength(); ++i) {
+      Object ref;
+      rbarray->getNF( i, &ref );
+      if ( ! ref.isRef() ) {
+	qDebug() << "expected ref, but got:" << ref.getType();
+      }
+      OptContentItem *item = ocModel->itemFromRef( QString::number(ref.getRefNum() ) );
+      itemsInGroup.append( item );
+    }
+    for (int i = 0; i < itemsInGroup.size(); ++i) {
+      OptContentItem *item = itemsInGroup.at(i);
+      item->appendRBGroup( this );
+    }
+  }
+
+  RadioButtonGroup::~RadioButtonGroup()
+  {
+  }
+
+  void RadioButtonGroup::setItemOn( OptContentItem *itemToSetOn )
+  {
+    for (int i = 0; i < itemsInGroup.size(); ++i) {
+      OptContentItem *thisItem = itemsInGroup.at(i);
+      if (thisItem != itemToSetOn) {
+	thisItem->setState( OptContentItem::Off );
+      }
+    }
+  }
+
+
+
+  OptContentItem::OptContentItem( OptionalContentGroup *group )
+  {
+    m_group = group;
+    m_parent = 0;
+    m_name = UnicodeParsedString( group->name() );
+    if ( group->state() == OptionalContentGroup::On ) {
+      m_state = OptContentItem::On;
+    } else {
+      m_state = OptContentItem::Off;
+    }
+  }
+
+  OptContentItem::OptContentItem( const QString &label )
+  {
+    m_parent = 0;
+    m_name = label;
+    m_group = 0;
+    m_state = OptContentItem::HeadingOnly;
+  }
+
+  OptContentItem::OptContentItem()
+  {
+    m_parent = 0;
+  }
+
+  OptContentItem::~OptContentItem()
+  {
+  }
+
+  void OptContentItem::appendRBGroup( RadioButtonGroup *rbgroup )
+  {
+    m_rbGroups.append( rbgroup );
+  }
+
+
+  bool OptContentItem::setState( ItemState state )
+  {
+    m_state = state;
+    if (!m_group) {
+      return false;
+    }
+    if ( state == OptContentItem::On ) {
+      m_group->setState( OptionalContentGroup::On );
+      for (int i = 0; i < m_rbGroups.size(); ++i) {
+	RadioButtonGroup *rbgroup = m_rbGroups.at(i);
+	rbgroup->setItemOn( this );
+      }
+    } else if ( state == OptContentItem::Off ) {
+      m_group->setState( OptionalContentGroup::Off );
+    }
+    return true;
+  }
+
+  void OptContentItem::addChild( OptContentItem *child )
+  {
+    m_children += child;
+    child->setParent( this );
+  }
+
+  OptContentModel::OptContentModel( OCGs *optContent, QObject *parent)
+    : QAbstractItemModel(parent)
+  {
+    m_rootNode = new OptContentItem();
+    GooList *ocgs = optContent->getOCGs();
+
+    for (int i = 0; i < ocgs->getLength(); ++i) {
+      OptionalContentGroup *ocg = static_cast<OptionalContentGroup*>(ocgs->get(i));
+      OptContentItem *node = new OptContentItem( ocg );
+      m_optContentItems.insert( QString::number(ocg->ref().num), node);
+    }
+
+    if ( optContent->getOrderArray() == 0 ) {
+      // no Order array, so drop them all at the top level
+      QMapIterator<QString, OptContentItem*> i(m_optContentItems);
+      while ( i.hasNext() ) {
+	i.next();
+	qDebug() << "iterator" << i.key() << ":" << i.value();
+	addChild( i.value(), m_rootNode );
+      }
+    } else {
+      parseOrderArray( m_rootNode, optContent->getOrderArray() );
+    }
+
+    parseRBGroupsArray( optContent->getRBGroupsArray() );
+  }
+
+  OptContentModel::~OptContentModel()
+  {
+    qDeleteAll( m_optContentItems );
+    qDeleteAll( m_rbgroups );
+    delete m_rootNode;
+  }
+
+  void OptContentModel::setRootNode( OptContentItem *node )
+  {
+    delete m_rootNode;
+    m_rootNode = node;
+    reset();
+  }
+
+  void OptContentModel::parseOrderArray( OptContentItem *parentNode, Array *orderArray )
+  {
+    OptContentItem *lastItem = parentNode;
+    for (int i = 0; i < orderArray->getLength(); ++i) {
+      Object orderItem;
+      orderArray->get(i, &orderItem);
+      if ( orderItem.isDict() ) {
+	Object item;
+	orderArray->getNF(i, &item);
+	if (item.isRef() ) {
+	  OptContentItem *ocItem = m_optContentItems[ QString("%1").arg(item.getRefNum()) ];
+	  if (ocItem) {
+	    addChild( parentNode, ocItem );
+	    lastItem = ocItem;
+	  } else {
+	    printf("couldn't find group for object %i\n", item.getRefNum());
+	  }
+	}
+	item.free();
+      } else if ( (orderItem.isArray()) && (orderItem.arrayGetLength() > 0) ) {
+	parseOrderArray(lastItem, orderItem.getArray());
+      } else if ( orderItem.isString() ) {
+	GooString *label = orderItem.getString();
+	OptContentItem *header = new OptContentItem ( UnicodeParsedString ( label ) );
+	addChild( parentNode, header );
+	parentNode = header;
+	lastItem = header;
+      } else {
+	qDebug() << "something unexpected";
+      }	
+      orderItem.free();
+    }
+  }
+
+  void OptContentModel::parseRBGroupsArray( Array *rBGroupArray )
+  {
+    if (! rBGroupArray) {
+      return;
+    }
+    // This is an array of array(s)
+    for (int i = 0; i < rBGroupArray->getLength(); ++i) {
+      Object rbObj;
+      rBGroupArray->get(i, &rbObj);
+      if ( ! rbObj.isArray() ) {
+	qDebug() << "expected inner array, got:" << rbObj.getType();
+	return;
+      }
+      Array *rbarray = rbObj.getArray();
+      RadioButtonGroup *rbg = new RadioButtonGroup( this, rbarray );
+      m_rbgroups.append( rbg );
+      rbObj.free();
+    }
+  }
+
+  QModelIndex OptContentModel::index(int row, int column, const QModelIndex &parent) const
+  {
+    if (!m_rootNode) {
+      return QModelIndex();
+    }
+
+    OptContentItem *parentNode = nodeFromIndex( parent );
+    return createIndex( row, column, parentNode->childList()[row] );
+  }
+
+  QModelIndex OptContentModel::parent(const QModelIndex &child) const
+  {
+    OptContentItem *childNode = nodeFromIndex( child );
+    if (!childNode) {
+      return QModelIndex();
+    }
+    OptContentItem *parentNode = childNode->parent();
+    if (!parentNode) {
+      return QModelIndex();
+    }
+    OptContentItem *grandparentNode = parentNode->parent();
+    if (!grandparentNode) {
+      return QModelIndex();
+    }
+    int row = grandparentNode->childList().indexOf(parentNode);
+    return createIndex(row, child.column(), parentNode);
+  }
+ 
+  int OptContentModel::rowCount(const QModelIndex &parent) const
+  {
+    OptContentItem *parentNode = nodeFromIndex( parent );
+    if (!parentNode) {
+      return 0;
+    } else {
+      return parentNode->childList().count();
+    }
+  }
+
+  int OptContentModel::columnCount(const QModelIndex &parent) const
+  {
+    return 2;
+  }
+
+
+  QVariant OptContentModel::data(const QModelIndex &index, int role) const
+  {
+    if ( (role != Qt::DisplayRole) && (role != Qt::EditRole) ) {
+      return QVariant();
+    }
+
+    OptContentItem *node = nodeFromIndex( index );
+    if (!node) {
+      return QVariant();
+    }
+
+    if (index.column() == 0) {
+      return node->name();
+    } else if (index.column() == 1) {
+      if ( node->state() == OptContentItem::On ) {
+	return true;
+      } else if ( node->state() == OptContentItem::Off ) {
+	return false;
+      } else {
+	return QVariant();
+      }
+    }
+
+    return QVariant();
+  }
+
+  bool OptContentModel::setData ( const QModelIndex & index, const QVariant & value, int role )
+  {
+    OptContentItem *node = nodeFromIndex( index );
+    if (!node) {
+      return false;
+    }
+
+    if (index.column() == 0) {
+      // we don't allow setting of the label
+      return false;
+    } else if (index.column() == 1) {
+      if ( value.toBool() == true ) {
+	if ( node->state() != OptContentItem::On ) {
+	  node->setState( OptContentItem::On );
+	  emit dataChanged( index, index );
+	}
+	return true;
+      } else if ( value.toBool() == false ) {
+	if ( node->state() != OptContentItem::Off ) {
+	  node->setState( OptContentItem::Off );
+	  emit dataChanged( index, index );
+	}
+	return true;
+      } else {
+	return false;
+      }
+    }
+
+    return false;
+  }
+
+  Qt::ItemFlags OptContentModel::flags ( const QModelIndex & index ) const
+  {
+    if (index.column() == 0) {
+      return QAbstractItemModel::flags(index) | Qt::ItemIsSelectable | Qt::ItemIsEnabled;
+    } else if (index.column() == 1) {
+      return QAbstractItemModel::flags(index) | Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsUserCheckable | Qt::ItemIsEnabled;
+    } else {
+      return QAbstractItemModel::flags(index);
+    }
+  }
+
+  void OptContentModel::addChild( OptContentItem *parent, OptContentItem *child )
+  {
+    parent->addChild( child );
+  }
+
+  OptContentItem* OptContentModel::itemFromRef( const QString &ref ) const
+  {
+    if ( ! m_optContentItems.contains( ref ) ) {
+      return 0;
+    }
+    return m_optContentItems[ ref ];
+  }
+
+  OptContentItem* OptContentModel::nodeFromIndex( const QModelIndex &index ) const
+  {
+    if (index.isValid()) {
+      return static_cast<OptContentItem *>(index.internalPointer());
+    } else {
+      return m_rootNode;
+    }
+  }
+}
+
+#include "poppler-optcontent.moc"
diff --git a/qt4/src/poppler-optcontent.h b/qt4/src/poppler-optcontent.h
new file mode 100644
index 0000000..fce4dfc
--- /dev/null
+++ b/qt4/src/poppler-optcontent.h
@@ -0,0 +1,122 @@
+/* poppler-optcontent.h: qt interface to poppler
+ *
+ * Copyright (C) 2007, Brad Hards <bradh at kde.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef POPPLER_OPTCONTENT_H
+#define POPPLER_OPTCONTENT_H
+
+#include <QtCore/QAbstractListModel>
+#include <QtCore/QStringList>
+
+#include "goo/GooList.h"
+class OptionalContentGroup;
+class OCGs;
+class Array;
+
+namespace Poppler
+{
+  class OptContentItem;
+  class OptContentModel;
+
+  class RadioButtonGroup
+  {
+  public:
+    RadioButtonGroup( OptContentModel *ocModel, Array *rbarray);
+    ~RadioButtonGroup();
+    void setItemOn( OptContentItem *itemToSetOn );
+
+  private:
+    QList<OptContentItem*> itemsInGroup;
+  };
+
+  class OptContentItem
+  {
+    public:
+    enum ItemState { On, Off, HeadingOnly };
+
+    OptContentItem( OptionalContentGroup *group );
+    OptContentItem( const QString &label );
+    OptContentItem();
+    ~OptContentItem();
+
+    QString name() const { return m_name; };
+    ItemState state() const { return m_state; };
+    bool setState( ItemState state );
+
+    QList<OptContentItem*> childList() { return m_children; };
+
+    void setParent( OptContentItem* parent) { m_parent = parent; };
+    OptContentItem* parent() { return m_parent; };
+
+    void addChild( OptContentItem *child );
+
+    void appendRBGroup( RadioButtonGroup *rbgroup );
+
+    private:
+    OptionalContentGroup *m_group;
+    QString m_name;
+    ItemState m_state; // true for ON, false for OFF
+    QList<OptContentItem*> m_children;
+    OptContentItem *m_parent;
+    QList<RadioButtonGroup*> m_rbGroups;
+  };
+
+  class OptContentModel : public QAbstractItemModel
+  {
+    Q_OBJECT
+
+    public:
+    OptContentModel( OCGs *optContent, QObject *parent = 0);
+    virtual ~OptContentModel();
+
+    void setRootNode(OptContentItem *node);
+
+    QModelIndex index(int row, int column, const QModelIndex &parent) const;
+    QModelIndex parent(const QModelIndex &child) const;
+
+    int rowCount(const QModelIndex &parent = QModelIndex()) const;
+    int columnCount(const QModelIndex &parent) const;
+
+    QVariant data(const QModelIndex &index, int role) const;
+    virtual bool setData ( const QModelIndex & index, const QVariant & value, int role = Qt::EditRole );
+
+    Qt::ItemFlags flags ( const QModelIndex & index ) const;
+
+    void addChild( OptContentItem *parent, OptContentItem *child);
+
+    /**
+       Get the OptContentItem corresponding to a given reference value.
+
+       \param ref the reference number (e.g. from Object.getRefNum()) to look up
+
+       \return the matching optional content item, or null if the reference wasn't found
+    */
+    OptContentItem *itemFromRef( const QString &ref ) const;
+
+    private:
+    OptContentItem *nodeFromIndex( const QModelIndex &index ) const;
+    void parseOrderArray( OptContentItem *parentNode, Array *orderArray );
+    void parseRBGroupsArray( Array *rBGroupArray );
+
+    QMap<QString, OptContentItem*> m_optContentItems;
+    QList<RadioButtonGroup*> m_rbgroups;
+    OptContentItem *m_rootNode;
+  };
+}
+
+#endif
diff --git a/qt4/src/poppler-private.h b/qt4/src/poppler-private.h
index 77cb431..e663312 100644
--- a/qt4/src/poppler-private.h
+++ b/qt4/src/poppler-private.h
@@ -94,6 +94,7 @@ namespace Poppler {
 		m_outputDev = 0;
 		paperColor = Qt::white;
 		m_hints = 0;
+		m_optContentModel = 0;
 		// It might be more appropriate to delete these in PDFDoc
 		delete ownerPassword;
 		delete userPassword;
@@ -109,6 +110,8 @@ namespace Poppler {
 	~DocumentData()
 	{
 		qDeleteAll(m_embeddedFiles);
+		delete m_optContentModel;
+		m_optContentModel = 0;
 		delete doc;
 		delete m_outputDev;
 		delete m_fontInfoScanner;
@@ -254,6 +257,7 @@ namespace Poppler {
 	Document::RenderBackend m_backend;
 	OutputDev *m_outputDev;
 	QList<EmbeddedFile*> m_embeddedFiles;
+	OptContentModel *m_optContentModel;
 	QColor paperColor;
 	int m_hints;
 	static int count;
diff --git a/qt4/src/poppler-qt4.h b/qt4/src/poppler-qt4.h
index 60945cb..121ff49 100644
--- a/qt4/src/poppler-qt4.h
+++ b/qt4/src/poppler-qt4.h
@@ -22,6 +22,7 @@
 
 #include "poppler-annotation.h"
 #include "poppler-link.h"
+#include "poppler-optcontent.h"
 #include "poppler-page-transition.h"
 
 #include <QtCore/QByteArray>
@@ -865,6 +866,23 @@ QString subject = m_doc->info("Subject");
 	QString metadata() const;
 
 	/**
+	   Test whether this document has "optional content".
+
+	   Optional content is used to optionally turn on (display)
+	   and turn off (not display) some elements of the document.
+	   The most common use of this is for layers in design
+	   applications, but it can be used for a range of things,
+	   such as not including some content in printing, and
+	   displaying content in the appropriate language.
+	*/
+	bool hasOptionalContent();
+
+	/**
+	   Itemviews model for optional content
+	*/
+	OptContentModel *optionalContentModel(QObject *parent = 0);
+
+	/**
 	   Destructor.
 	*/
 	~Document();
diff --git a/qt4/tests/.gitignore b/qt4/tests/.gitignore
index 25a1981..dd2f84e 100644
--- a/qt4/tests/.gitignore
+++ b/qt4/tests/.gitignore
@@ -15,6 +15,7 @@ check_attachments
 check_dateConversion
 check_fonts
 check_metadata
+check_optcontent
 check_permissions
 check_pagelayout
 check_pagemode
diff --git a/qt4/tests/CMakeLists.txt b/qt4/tests/CMakeLists.txt
index 06dd988..bd8512c 100644
--- a/qt4/tests/CMakeLists.txt
+++ b/qt4/tests/CMakeLists.txt
@@ -47,6 +47,7 @@ qt4_add_qtest(check_attachments check_attachments.cpp)
 qt4_add_qtest(check_dateConversion check_dateConversion.cpp)
 qt4_add_qtest(check_fonts check_fonts.cpp)
 qt4_add_qtest(check_metadata check_metadata.cpp)
+qt4_add_qtest(check_optcontent check_optcontent.cpp)
 qt4_add_qtest(check_pagelayout check_pagelayout.cpp)
 qt4_add_qtest(check_pagemode check_pagemode.cpp)
 qt4_add_qtest(check_password check_password.cpp)
diff --git a/qt4/tests/Makefile.am b/qt4/tests/Makefile.am
index 7698320..3ad4aac 100644
--- a/qt4/tests/Makefile.am
+++ b/qt4/tests/Makefile.am
@@ -61,6 +61,7 @@ TESTS = \
 	check_dateConversion 	\
 	check_fonts		\
 	check_metadata         	\
+	check_optcontent	\
 	check_permissions      	\
 	check_pagemode    	\
 	check_password    	\
@@ -85,6 +86,10 @@ check_metadata_SOURCES = check_metadata.cpp
 check_metadata.$(OBJEXT): check_metadata.moc
 check_metadata_LDADD = $(LDADDS)
 
+check_optcontent_SOURCES = check_optcontent.cpp
+check_optcontent.$(OBJEXT): check_optcontent.moc
+check_optcontent_LDADD = $(LDADDS)
+
 check_pagemode_SOURCES = check_pagemode.cpp
 check_pagemode.$(OBJEXT): check_pagemode.moc
 check_pagemode_LDADD = $(LDADDS)
diff --git a/qt4/tests/check_optcontent.cpp b/qt4/tests/check_optcontent.cpp
new file mode 100644
index 0000000..045ec18
--- /dev/null
+++ b/qt4/tests/check_optcontent.cpp
@@ -0,0 +1,499 @@
+#include <QtTest/QtTest>
+
+#include "PDFDoc.h"
+
+#include <poppler-qt4.h>
+
+class TestOptionalContent: public QObject
+{
+    Q_OBJECT
+private slots:
+    void checkVisPolicy();
+    void checkNestedLayers();
+    void checkNoOptionalContent();
+    void checkIsVisible();
+    void checkVisibilitySetting();
+    void checkRadioButtons();
+};
+
+void TestOptionalContent::checkVisPolicy()
+{
+    Poppler::Document *doc;
+    doc = Poppler::Document::load("../../../test/unittestcases/vis_policy_test.pdf");
+    QVERIFY( doc );
+
+    QVERIFY( doc->hasOptionalContent() );
+
+    Poppler::OptContentModel *optContent = doc->optionalContentModel();
+    QModelIndex index;
+    index = optContent->index( 0, 0, QModelIndex() );
+    QCOMPARE( optContent->data( index, Qt::DisplayRole ).toString(), QString( "A" ) );
+    index = optContent->index( 0, 1, QModelIndex() );
+    QCOMPARE( optContent->data( index, Qt::DisplayRole ).toBool(), true );
+    index = optContent->index( 1, 0, QModelIndex() );
+    QCOMPARE( optContent->data( index, Qt::DisplayRole ).toString(), QString( "B" ) );
+    index = optContent->index( 1, 1, QModelIndex() );
+    QCOMPARE( optContent->data( index, Qt::DisplayRole ).toBool(), true );
+
+    delete doc;
+}
+
+void TestOptionalContent::checkNestedLayers()
+{
+    Poppler::Document *doc;
+    doc = Poppler::Document::load("../../../test/unittestcases/NestedLayers.pdf");
+    QVERIFY( doc );
+
+    QVERIFY( doc->hasOptionalContent() );
+
+    Poppler::OptContentModel *optContent = doc->optionalContentModel();
+    QModelIndex index;
+
+    index = optContent->index( 0, 0, QModelIndex() );
+    QCOMPARE( optContent->data( index, Qt::DisplayRole ).toString(), QString( "Black Text and Green Snow" ) );
+    index = optContent->index( 0, 1, QModelIndex() );
+    QCOMPARE( optContent->data( index, Qt::DisplayRole ).toBool(), false );
+
+    index = optContent->index( 1, 0, QModelIndex() );
+    QCOMPARE( optContent->data( index, Qt::DisplayRole ).toString(), QString( "Mountains and Image" ) );
+    index = optContent->index( 1, 1, QModelIndex() );
+    QCOMPARE( optContent->data( index, Qt::DisplayRole ).toBool(), true );
+
+    // This is a sub-item of "Mountains and Image"
+    QModelIndex subindex = optContent->index( 0, 0, index );
+    QCOMPARE( optContent->data( subindex, Qt::DisplayRole ).toString(), QString( "Image" ) );
+    subindex = optContent->index( 0, 1, index );
+    QCOMPARE( optContent->data( subindex, Qt::DisplayRole ).toBool(), true );
+
+    index = optContent->index( 2, 0, QModelIndex() );
+    QCOMPARE( optContent->data( index, Qt::DisplayRole ).toString(), QString( "Starburst" ) );
+    index = optContent->index( 2, 1, QModelIndex() );
+    QCOMPARE( optContent->data( index, Qt::DisplayRole ).toBool(), true );
+
+    index = optContent->index( 3, 0, QModelIndex() );
+    QCOMPARE( optContent->data( index, Qt::DisplayRole ).toString(), QString( "Watermark" ) );
+    index = optContent->index( 3, 1, QModelIndex() );
+    QCOMPARE( optContent->data( index, Qt::DisplayRole ).toBool(), false );
+
+    delete doc;
+}
+
+void TestOptionalContent::checkNoOptionalContent()
+{
+    Poppler::Document *doc;
+    doc = Poppler::Document::load("../../../test/unittestcases/orientation.pdf");
+    QVERIFY( doc );
+
+    QCOMPARE( doc->hasOptionalContent(), false );
+
+    delete doc;
+}
+
+void TestOptionalContent::checkIsVisible()
+{
+    GooString *fileName = new GooString("../../../test/unittestcases/vis_policy_test.pdf"); 
+    PDFDoc *doc = new PDFDoc( fileName );
+    QVERIFY( doc );
+
+    OCGs *ocgs = doc->getOptContentConfig();
+    QVERIFY( ocgs );
+
+    XRef *xref = doc->getXRef();
+
+    Object obj;
+
+    // In this test, both Ref(21,0) and Ref(2,0) are set to On
+
+    // AnyOn, one element array:
+    // 22 0 obj<</Type/OCMD/OCGs[21 0 R]/P/AnyOn>>endobj
+    xref->fetch( 22, 0, &obj );
+    QVERIFY( obj.isDict() );
+    QVERIFY( ocgs->optContentIsVisible( &obj ) );
+    obj.free();
+
+    // Same again, looking for any leaks or dubious free()'s
+    xref->fetch( 22, 0, &obj );
+    QVERIFY( obj.isDict() );
+    QVERIFY( ocgs->optContentIsVisible( &obj ) );
+    obj.free();
+
+    // AnyOff, one element array:
+    // 29 0 obj<</Type/OCMD/OCGs[21 0 R]/P/AnyOff>>endobj
+    xref->fetch( 29, 0, &obj );
+    QVERIFY( obj.isDict() );
+    QCOMPARE( ocgs->optContentIsVisible( &obj ), false );
+    obj.free();
+
+    // AllOn, one element array:
+    // 36 0 obj<</Type/OCMD/OCGs[28 0 R]/P/AllOn>>endobj
+    xref->fetch( 36, 0, &obj );
+    QVERIFY( obj.isDict() );
+    QCOMPARE( ocgs->optContentIsVisible( &obj ), true );
+    obj.free();
+
+
+    // AllOff, one element array:
+    // 43 0 obj<</Type/OCMD/OCGs[28 0 R]/P/AllOff>>endobj
+    xref->fetch( 43, 0, &obj );
+    QVERIFY( obj.isDict() );
+    QCOMPARE( ocgs->optContentIsVisible( &obj ), false );
+    obj.free();
+
+    // AnyOn, multi-element array:
+    // 50 0 obj<</Type/OCMD/OCGs[21 0 R 28 0 R]/P/AnyOn>>endobj
+    xref->fetch( 50, 0, &obj );
+    QVERIFY( obj.isDict() );
+    QCOMPARE( ocgs->optContentIsVisible( &obj ), true );
+    obj.free();
+
+    // AnyOff, multi-element array:
+    // 57 0 obj<</Type/OCMD/P/AnyOff/OCGs[21 0 R 28 0 R]>>endobj
+    xref->fetch( 57, 0, &obj );
+    QVERIFY( obj.isDict() );
+    QCOMPARE( ocgs->optContentIsVisible( &obj ), false );
+    obj.free();
+
+    // AllOn, multi-element array:
+    // 64 0 obj<</Type/OCMD/P/AllOn/OCGs[21 0 R 28 0 R]>>endobj
+    xref->fetch( 64, 0, &obj );
+    QVERIFY( obj.isDict() );
+    QCOMPARE( ocgs->optContentIsVisible( &obj ), true );
+    obj.free();
+
+    // AllOff, multi-element array:
+    // 71 0 obj<</Type/OCMD/P/AllOff/OCGs[21 0 R 28 0 R]>>endobj
+    xref->fetch( 71, 0, &obj );
+    QVERIFY( obj.isDict() );
+    QCOMPARE( ocgs->optContentIsVisible( &obj ), false );
+    obj.free();
+
+    delete doc;
+}
+
+void TestOptionalContent::checkVisibilitySetting()
+{
+    GooString *fileName = new GooString("../../../test/unittestcases/vis_policy_test.pdf"); 
+    PDFDoc *doc = new PDFDoc( fileName );
+    QVERIFY( doc );
+
+    OCGs *ocgs = doc->getOptContentConfig();
+    QVERIFY( ocgs );
+
+    XRef *xref = doc->getXRef();
+
+    Object obj;
+
+    // In this test, both Ref(21,0) and Ref(28,0) start On,
+    // based on the file settings
+    Object ref21obj;
+    ref21obj.initRef( 21, 0 );
+    Ref ref21 = ref21obj.getRef();
+    OptionalContentGroup *ocgA = ocgs->findOcgByRef( ref21 );
+    QVERIFY( ocgA );
+
+    QVERIFY( (ocgA->name()->cmp("A")) == 0 );
+    QCOMPARE( ocgA->state(), OptionalContentGroup::On );
+
+    Object ref28obj;
+    ref28obj.initRef( 28, 0 );
+    Ref ref28 = ref28obj.getRef();
+    OptionalContentGroup *ocgB = ocgs->findOcgByRef( ref28 );
+    QVERIFY( ocgB );
+
+    QVERIFY( (ocgB->name()->cmp("B")) == 0 );
+    QCOMPARE( ocgB->state(), OptionalContentGroup::On );
+
+    // turn one Off
+    ocgA->setState( OptionalContentGroup::Off );
+
+    // AnyOn, one element array:
+    // 22 0 obj<</Type/OCMD/OCGs[21 0 R]/P/AnyOn>>endobj
+    xref->fetch( 22, 0, &obj );
+    QVERIFY( obj.isDict() );
+    QCOMPARE( ocgs->optContentIsVisible( &obj ), false );
+    obj.free();
+
+    // Same again, looking for any leaks or dubious free()'s
+    xref->fetch( 22, 0, &obj );
+    QVERIFY( obj.isDict() );
+    QCOMPARE( ocgs->optContentIsVisible( &obj ), false );
+    obj.free();
+
+    // AnyOff, one element array:
+    // 29 0 obj<</Type/OCMD/OCGs[21 0 R]/P/AnyOff>>endobj
+    xref->fetch( 29, 0, &obj );
+    QVERIFY( obj.isDict() );
+    QCOMPARE( ocgs->optContentIsVisible( &obj ), true );
+    obj.free();
+
+    // AllOn, one element array:
+    // 36 0 obj<</Type/OCMD/OCGs[28 0 R]/P/AllOn>>endobj
+    xref->fetch( 36, 0, &obj );
+    QVERIFY( obj.isDict() );
+    QCOMPARE( ocgs->optContentIsVisible( &obj ), true );
+    obj.free();
+
+    // AllOff, one element array:
+    // 43 0 obj<</Type/OCMD/OCGs[28 0 R]/P/AllOff>>endobj
+    xref->fetch( 43, 0, &obj );
+    QVERIFY( obj.isDict() );
+    QCOMPARE( ocgs->optContentIsVisible( &obj ), false );
+    obj.free();
+
+    // AnyOn, multi-element array:
+    // 50 0 obj<</Type/OCMD/OCGs[21 0 R 28 0 R]/P/AnyOn>>endobj
+    xref->fetch( 50, 0, &obj );
+    QVERIFY( obj.isDict() );
+    QCOMPARE( ocgs->optContentIsVisible( &obj ), true );
+    obj.free();
+
+    // AnyOff, multi-element array:
+    // 57 0 obj<</Type/OCMD/P/AnyOff/OCGs[21 0 R 28 0 R]>>endobj
+    xref->fetch( 57, 0, &obj );
+    QVERIFY( obj.isDict() );
+    QCOMPARE( ocgs->optContentIsVisible( &obj ), true );
+    obj.free();
+
+    // AllOn, multi-element array:
+    // 64 0 obj<</Type/OCMD/P/AllOn/OCGs[21 0 R 28 0 R]>>endobj
+    xref->fetch( 64, 0, &obj );
+    QVERIFY( obj.isDict() );
+    QCOMPARE( ocgs->optContentIsVisible( &obj ), false );
+    obj.free();
+
+    // AllOff, multi-element array:
+    // 71 0 obj<</Type/OCMD/P/AllOff/OCGs[21 0 R 28 0 R]>>endobj
+    xref->fetch( 71, 0, &obj );
+    QVERIFY( obj.isDict() );
+    QCOMPARE( ocgs->optContentIsVisible( &obj ), false );
+    obj.free();
+
+
+    // Turn the other one off as well (i.e. both are Off)
+    ocgB->setState(OptionalContentGroup::Off);
+
+    // AnyOn, one element array:
+    // 22 0 obj<</Type/OCMD/OCGs[21 0 R]/P/AnyOn>>endobj
+    xref->fetch( 22, 0, &obj );
+    QVERIFY( obj.isDict() );
+    QCOMPARE( ocgs->optContentIsVisible( &obj ), false );
+    obj.free();
+
+    // Same again, looking for any leaks or dubious free()'s
+    xref->fetch( 22, 0, &obj );
+    QVERIFY( obj.isDict() );
+    QCOMPARE( ocgs->optContentIsVisible( &obj ), false );
+    obj.free();
+
+    // AnyOff, one element array:
+    // 29 0 obj<</Type/OCMD/OCGs[21 0 R]/P/AnyOff>>endobj
+    xref->fetch( 29, 0, &obj );
+    QVERIFY( obj.isDict() );
+    QCOMPARE( ocgs->optContentIsVisible( &obj ), true );
+    obj.free();
+
+    // AllOn, one element array:
+    // 36 0 obj<</Type/OCMD/OCGs[28 0 R]/P/AllOn>>endobj
+    xref->fetch( 36, 0, &obj );
+    QVERIFY( obj.isDict() );
+    QCOMPARE( ocgs->optContentIsVisible( &obj ), false );
+    obj.free();
+
+    // AllOff, one element array:
+    // 43 0 obj<</Type/OCMD/OCGs[28 0 R]/P/AllOff>>endobj
+    xref->fetch( 43, 0, &obj );
+    QVERIFY( obj.isDict() );
+    QCOMPARE( ocgs->optContentIsVisible( &obj ), true );
+    obj.free();
+
+    // AnyOn, multi-element array:
+    // 50 0 obj<</Type/OCMD/OCGs[21 0 R 28 0 R]/P/AnyOn>>endobj
+    xref->fetch( 50, 0, &obj );
+    QVERIFY( obj.isDict() );
+    QCOMPARE( ocgs->optContentIsVisible( &obj ), false );
+    obj.free();
+
+    // AnyOff, multi-element array:
+    // 57 0 obj<</Type/OCMD/P/AnyOff/OCGs[21 0 R 28 0 R]>>endobj
+    xref->fetch( 57, 0, &obj );
+    QVERIFY( obj.isDict() );
+    QCOMPARE( ocgs->optContentIsVisible( &obj ), true );
+    obj.free();
+
+    // AllOn, multi-element array:
+    // 64 0 obj<</Type/OCMD/P/AllOn/OCGs[21 0 R 28 0 R]>>endobj
+    xref->fetch( 64, 0, &obj );
+    QVERIFY( obj.isDict() );
+    QCOMPARE( ocgs->optContentIsVisible( &obj ), false );
+    obj.free();
+
+    // AllOff, multi-element array:
+    // 71 0 obj<</Type/OCMD/P/AllOff/OCGs[21 0 R 28 0 R]>>endobj
+    xref->fetch( 71, 0, &obj );
+    QVERIFY( obj.isDict() );
+    QCOMPARE( ocgs->optContentIsVisible( &obj ), true );
+    obj.free();
+
+
+    // Turn the first one on again (21 is On, 28 is Off)
+    ocgA->setState(OptionalContentGroup::On);
+
+    // AnyOn, one element array:
+    // 22 0 obj<</Type/OCMD/OCGs[21 0 R]/P/AnyOn>>endobj
+    xref->fetch( 22, 0, &obj );
+    QVERIFY( obj.isDict() );
+    QCOMPARE( ocgs->optContentIsVisible( &obj ), true );
+    obj.free();
+
+    // Same again, looking for any leaks or dubious free()'s
+    xref->fetch( 22, 0, &obj );
+    QVERIFY( obj.isDict() );
+    QCOMPARE( ocgs->optContentIsVisible( &obj ), true );
+    obj.free();
+
+    // AnyOff, one element array:
+    // 29 0 obj<</Type/OCMD/OCGs[21 0 R]/P/AnyOff>>endobj
+    xref->fetch( 29, 0, &obj );
+    QVERIFY( obj.isDict() );
+    QCOMPARE( ocgs->optContentIsVisible( &obj ), false );
+    obj.free();
+
+    // AllOn, one element array:
+    // 36 0 obj<</Type/OCMD/OCGs[28 0 R]/P/AllOn>>endobj
+    xref->fetch( 36, 0, &obj );
+    QVERIFY( obj.isDict() );
+    QCOMPARE( ocgs->optContentIsVisible( &obj ), false );
+    obj.free();
+
+    // AllOff, one element array:
+    // 43 0 obj<</Type/OCMD/OCGs[28 0 R]/P/AllOff>>endobj
+    xref->fetch( 43, 0, &obj );
+    QVERIFY( obj.isDict() );
+    QCOMPARE( ocgs->optContentIsVisible( &obj ), true );
+    obj.free();
+
+    // AnyOn, multi-element array:
+    // 50 0 obj<</Type/OCMD/OCGs[21 0 R 28 0 R]/P/AnyOn>>endobj
+    xref->fetch( 50, 0, &obj );
+    QVERIFY( obj.isDict() );
+    QCOMPARE( ocgs->optContentIsVisible( &obj ), true );
+    obj.free();
+
+    // AnyOff, multi-element array:
+    // 57 0 obj<</Type/OCMD/P/AnyOff/OCGs[21 0 R 28 0 R]>>endobj
+    xref->fetch( 57, 0, &obj );
+    QVERIFY( obj.isDict() );
+    QCOMPARE( ocgs->optContentIsVisible( &obj ), true );
+    obj.free();
+
+    // AllOn, multi-element array:
+    // 64 0 obj<</Type/OCMD/P/AllOn/OCGs[21 0 R 28 0 R]>>endobj
+    xref->fetch( 64, 0, &obj );
+    QVERIFY( obj.isDict() );
+    QCOMPARE( ocgs->optContentIsVisible( &obj ), false );
+    obj.free();
+
+    // AllOff, multi-element array:
+    // 71 0 obj<</Type/OCMD/P/AllOff/OCGs[21 0 R 28 0 R]>>endobj
+    xref->fetch( 71, 0, &obj );
+    QVERIFY( obj.isDict() );
+    QCOMPARE( ocgs->optContentIsVisible( &obj ), false );
+    obj.free();
+
+    delete doc;
+}
+
+void TestOptionalContent::checkRadioButtons()
+{
+    Poppler::Document *doc;
+    doc = Poppler::Document::load("../../../test/unittestcases/ClarityOCGs.pdf");
+    QVERIFY( doc );
+
+    QVERIFY( doc->hasOptionalContent() );
+
+    Poppler::OptContentModel *optContent = doc->optionalContentModel();
+    QModelIndex index;
+
+    index = optContent->index( 0, 0, QModelIndex() );
+    QCOMPARE( optContent->data( index, Qt::DisplayRole ).toString(), QString( "Languages" ) );
+    index = optContent->index( 0, 1, QModelIndex() );
+    QCOMPARE( optContent->data( index, Qt::DisplayRole ), QVariant() );
+
+    // These are sub-items of the "Languages" label
+    QModelIndex subindex = optContent->index( 0, 0, index );
+    QCOMPARE( optContent->data( subindex, Qt::DisplayRole ).toString(), QString( "English" ) );
+    subindex = optContent->index( 0, 1, index );
+    QCOMPARE( optContent->data( subindex, Qt::DisplayRole ).toBool(), true );
+
+    subindex = optContent->index( 1, 0, index );
+    QCOMPARE( optContent->data( subindex, Qt::DisplayRole ).toString(), QString( "French" ) );
+    subindex = optContent->index( 1, 1, index );
+    QCOMPARE( optContent->data( subindex, Qt::DisplayRole ).toBool(), false );
+
+    subindex = optContent->index( 2, 0, index );
+    QCOMPARE( optContent->data( subindex, Qt::DisplayRole ).toString(), QString( "Japanese" ) );
+    subindex = optContent->index( 2, 1, index );
+    QCOMPARE( optContent->data( subindex, Qt::DisplayRole ).toBool(), false );
+
+    // RBGroup of languages, so turning on Japanese should turn off English
+    bool result = optContent->setData( subindex, QVariant( true ) );
+
+    subindex = optContent->index( 0, 0, index );
+    QCOMPARE( optContent->data( subindex, Qt::DisplayRole ).toString(), QString( "English" ) );
+    subindex = optContent->index( 0, 1, index );
+    QCOMPARE( optContent->data( subindex, Qt::DisplayRole ).toBool(), false );
+
+    subindex = optContent->index( 2, 0, index );
+    QCOMPARE( optContent->data( subindex, Qt::DisplayRole ).toString(), QString( "Japanese" ) );
+    subindex = optContent->index( 2, 1, index );
+    QCOMPARE( optContent->data( subindex, Qt::DisplayRole ).toBool(), true );
+
+    subindex = optContent->index( 1, 0, index );
+    QCOMPARE( optContent->data( subindex, Qt::DisplayRole ).toString(), QString( "French" ) );
+    subindex = optContent->index( 1, 1, index );
+    QCOMPARE( optContent->data( subindex, Qt::DisplayRole ).toBool(), false );
+
+    // and turning on French should turn off Japanese
+    result = optContent->setData( subindex, QVariant( true ) );
+
+    subindex = optContent->index( 0, 0, index );
+    QCOMPARE( optContent->data( subindex, Qt::DisplayRole ).toString(), QString( "English" ) );
+    subindex = optContent->index( 0, 1, index );
+    QCOMPARE( optContent->data( subindex, Qt::DisplayRole ).toBool(), false );
+
+    subindex = optContent->index( 2, 0, index );
+    QCOMPARE( optContent->data( subindex, Qt::DisplayRole ).toString(), QString( "Japanese" ) );
+    subindex = optContent->index( 2, 1, index );
+    QCOMPARE( optContent->data( subindex, Qt::DisplayRole ).toBool(), false );
+
+    subindex = optContent->index( 1, 0, index );
+    QCOMPARE( optContent->data( subindex, Qt::DisplayRole ).toString(), QString( "French" ) );
+    subindex = optContent->index( 1, 1, index );
+    QCOMPARE( optContent->data( subindex, Qt::DisplayRole ).toBool(), true );
+
+
+    // and turning off French should leave them all off
+    result = optContent->setData( subindex, QVariant( false ) );
+
+    subindex = optContent->index( 0, 0, index );
+    QCOMPARE( optContent->data( subindex, Qt::DisplayRole ).toString(), QString( "English" ) );
+    subindex = optContent->index( 0, 1, index );
+    QCOMPARE( optContent->data( subindex, Qt::DisplayRole ).toBool(), false );
+
+    subindex = optContent->index( 2, 0, index );
+    QCOMPARE( optContent->data( subindex, Qt::DisplayRole ).toString(), QString( "Japanese" ) );
+    subindex = optContent->index( 2, 1, index );
+    QCOMPARE( optContent->data( subindex, Qt::DisplayRole ).toBool(), false );
+
+    subindex = optContent->index( 1, 0, index );
+    QCOMPARE( optContent->data( subindex, Qt::DisplayRole ).toString(), QString( "French" ) );
+    subindex = optContent->index( 1, 1, index );
+    QCOMPARE( optContent->data( subindex, Qt::DisplayRole ).toBool(), false );
+
+    delete doc;
+}
+
+QTEST_MAIN(TestOptionalContent)
+
+#include "check_optcontent.moc"
+


More information about the poppler mailing list