I've been slowing working on the Optional Content support (PDF spec 4.10 for 
the morbidly curious) for poppler (based on bug 12661).

The attached patches show where I'm going in terms of GUI support. Feedback 
appreciated. Patch 2 can be ignored.

The next stage is to actually make the rendering process take notice of the 
OptionalContentGroup settings.

After that, I need to fold in Optional Content Member Dictionaries, and then 
Visibility Expressions (maybe).

Brad
From 73ac62ed0f2171d30a7cc2cab24217ebd70a99da Mon Sep 17 00:00:00 2001
From: Brad Hards <[EMAIL PROTECTED]>
Date: Thu, 8 Nov 2007 17:23:59 +1100
Subject: [PATCH] Start of Optional Content Group

This part just extracts the OCG object (if present) from
the Catalog.
---
 poppler/Catalog.cc |    4 ++++
 poppler/Catalog.h  |    3 +++
 2 files changed, 7 insertions(+), 0 deletions(-)

diff --git a/poppler/Catalog.cc b/poppler/Catalog.cc
index 2e12aed..20a2b0a 100644
--- a/poppler/Catalog.cc
+++ b/poppler/Catalog.cc
@@ -171,6 +171,9 @@ Catalog::Catalog(XRef *xrefA) {
   // get the outline dictionary
   catDict.dictLookup("Outlines", &outline);
 
+  // get the Optional Content dictionary
+  catDict.dictLookup("OCProperties", &optContentProps);
+
   // perform form-related loading after all widgets have been loaded
   if (form) 
     form->postWidgetsLoad();
@@ -208,6 +211,7 @@ Catalog::~Catalog() {
   }
   delete pageLabelInfo;
   delete form;
+  optContentProps.free();
   metadata.free();
   structTreeRoot.free();
   outline.free();
diff --git a/poppler/Catalog.h b/poppler/Catalog.h
index bd49189..f51260b 100644
--- a/poppler/Catalog.h
+++ b/poppler/Catalog.h
@@ -162,6 +162,8 @@ public:
 
   Object *getAcroForm() { return &acroForm; }
 
+  Object *getOptContentProps() { return &optContentProps; }
+
   Form* getForm() { return form; }
 
   enum PageMode {
@@ -202,6 +204,7 @@ private:
   Object structTreeRoot;	// structure tree root dictionary
   Object outline;		// outline dictionary
   Object acroForm;		// AcroForm dictionary
+  Object optContentProps;	// Optional Content dictionary
   GBool ok;			// true if catalog is valid
   PageLabelInfo *pageLabelInfo; // info about page labels
   PageMode pageMode;		// page mode
-- 
1.5.3.3

From 537a4e941ae3dffaef93b1a301d55caec5400715 Mon Sep 17 00:00:00 2001
From: Brad Hards <[EMAIL PROTECTED]>
Date: Sat, 17 Nov 2007 09:26:48 +1100
Subject: [PATCH] Suppress editor backup files

---
 poppler/.gitignore |    1 +
 1 files changed, 1 insertions(+), 0 deletions(-)

diff --git a/poppler/.gitignore b/poppler/.gitignore
index 73e9b19..d8c6d52 100644
--- a/poppler/.gitignore
+++ b/poppler/.gitignore
@@ -8,3 +8,4 @@ Makefile.in
 *.loT
 poppler-config.h
 stamp-h2
+*~
-- 
1.5.3.3

From d56ffa6d432824d98057a46a9f0bca6307c982b5 Mon Sep 17 00:00:00 2001
From: Brad Hards <[EMAIL PROTECTED]>
Date: Fri, 23 Nov 2007 18:25:46 +1100
Subject: [PATCH] Big update for the Optional Content work.

modified:   poppler/PDFDoc.cc, poppler/PDFDoc.h
- This is to enable us to access the OCProperties from the
  Catalog

new file: poppler/OptionalContent.cc, poppler/OptionalContent.h
- New classes to hold optional content information, and also to do
  the parsing.

modified:   poppler/Makefile.am
- Update to include new files (OptionalContent.[h,cc])

modified:   qt4/src/poppler-document.cc, qt4/src/poppler-private.h,
qt4/src/poppler-qt4.h
- Changes to expose the optional content tree. This is tree model
  (in the Qt4 Interviews sense - it derives from QAbstractItemModel)

new file:   qt4/src/poppler-optcontent.cc, qt4/src/poppler-optcontent.h
- This is where the raw tree gets converted.

modified:   qt4/src/Makefile.am
- Update to include new files (poppler-optcontent.[h,cc])

new file:   qt4/tests/check_optcontent.cpp
- unit tests for Qt4 bindings.

new file:   qt4/tests/poppler-optcontent.cpp
- trivial test application to show the tree model as a GUI tree

modified:   qt4/tests/Makefile.am
- Updates to include new unit test and test application

modified:   qt4/src/.gitignore, modified:   qt4/tests/.gitignore
- Minor changes to keep the Git noise down.
---
 poppler/Makefile.am              |    2 +
 poppler/OptionalContent.cc       |  272 ++++++++++++++++++++++++++++++++++++++
 poppler/OptionalContent.h        |   81 +++++++++++
 poppler/PDFDoc.cc                |   11 ++
 poppler/PDFDoc.h                 |    5 +
 qt4/src/.gitignore               |    1 +
 qt4/src/Makefile.am              |   10 ++-
 qt4/src/poppler-document.cc      |   13 ++
 qt4/src/poppler-optcontent.cc    |  187 ++++++++++++++++++++++++++
 qt4/src/poppler-optcontent.h     |   85 ++++++++++++
 qt4/src/poppler-private.h        |    4 +
 qt4/src/poppler-qt4.h            |   25 ++++-
 qt4/tests/.gitignore             |    2 +
 qt4/tests/Makefile.am            |   12 ++-
 qt4/tests/check_optcontent.cpp   |   90 +++++++++++++
 qt4/tests/poppler-optcontent.cpp |   32 +++++
 16 files changed, 827 insertions(+), 5 deletions(-)
 create mode 100644 poppler/OptionalContent.cc
 create mode 100644 poppler/OptionalContent.h
 create mode 100644 qt4/src/poppler-optcontent.cc
 create mode 100644 qt4/src/poppler-optcontent.h
 create mode 100644 qt4/tests/check_optcontent.cpp
 create mode 100644 qt4/tests/poppler-optcontent.cpp

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..88b4646
--- /dev/null
+++ b/poppler/OptionalContent.cc
@@ -0,0 +1,272 @@
+//========================================================================
+//
+// OptionalContent.h
+//
+// Copyright 2007 Brad Hards <[EMAIL PROTECTED]>
+//
+// 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)
+{
+  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();
+  orderedOptionalContentGroups = 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) ) {
+    OptionalContentGroup *lastGroup = NULL;
+    for (int i = 0; i < order.arrayGetLength(); ++i) {
+      Object orderItem;
+      order.arrayGet(i, &orderItem);
+      if ( orderItem.isDict() ) {
+	Object item;
+	order.arrayGetNF(i, &item);
+	if (item.isRef() ) {
+	  OptionalContentGroup *group = findOcgByRef(item.getRef());
+	  if (group) {
+	    orderedOptionalContentGroups->append( group );
+	    lastGroup = group;
+	  } else {
+	    printf("couldn't find group for object %i\n", item.getRefNum());
+	  }
+	}	  
+      } else if ( (orderItem.isArray()) && (orderItem.arrayGetLength() > 0) ) {
+	parseNestedOrderArray(orderItem.getArray(), lastGroup);
+      } 
+    }
+  }
+  order.free();
+
+  ocgList.free();
+  defaultOcgConfig.free();
+}
+
+void OCGs::parseNestedOrderArray(Array *array, OptionalContentGroup *parent)
+{
+  OptionalContentGroup *lastGroup = parent;
+  for (int i = 0; i < array->getLength(); ++i) {
+    Object item;
+    array->getNF(i, &item);
+    if (item.isRef() ) {
+      OptionalContentGroup *group = findOcgByRef(item.getRef());
+      if (group) {
+	if (parent) {
+	  parent->appendChild( group );
+	} else {
+	  orderedOptionalContentGroups->append( group );
+	}
+	lastGroup = group;
+      }
+    } else if ( (item.isArray()) && (item.arrayGetLength() > 0)) {
+      // should add some sanity check to prevent linking back up...
+      parseNestedOrderArray(item.getArray(), lastGroup);
+    } else if ( item.isString()) {
+      OptionalContentGroup *header = new OptionalContentGroup( item.getString() );
+      if (parent) {
+	parent->appendChild( header );
+      } else {
+	orderedOptionalContentGroups->append( header );
+      }
+      lastGroup = header;
+    }
+    item.free();
+  }  
+}
+
+OCGs::~OCGs()
+{
+  if (optionalContentGroups) {
+    deleteGooList(optionalContentGroups, OptionalContentGroup);
+    delete orderedOptionalContentGroups;
+  }
+}
+
+
+bool OCGs::hasOCGs()
+{
+  if (!optionalContentGroups) {
+    return false;
+  }
+  return ( optionalContentGroups->getLength() > 0 );
+}
+
+OptionalContentGroup* OCGs::findOcgByRef( const Ref ref)
+{
+  //TODO: improve the efficiency of this
+  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;
+}
+//------------------------------------------------------------------------
+
+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();
+
+  m_children = new GooList();
+  // Todo: Intent, Usage
+}
+
+OptionalContentGroup::OptionalContentGroup(GooString *label)
+{
+  m_name = label;
+  m_state = HeaderOnly;
+
+  m_children = new GooList();
+}
+
+GooString* OptionalContentGroup::name() const
+{
+  return m_name;
+}
+
+void OptionalContentGroup::setRef(const Ref ref)
+{
+  m_ref = ref;
+}
+
+Ref OptionalContentGroup::ref() const
+{
+  return m_ref;
+}
+
+void OptionalContentGroup::appendChild(OptionalContentGroup *child)
+{
+  m_children->append( child );
+}
+
+GooList* OptionalContentGroup::getChildren()
+{
+  return m_children;
+}
+
+bool OptionalContentGroup::hasChildren()
+{
+  return (m_children->getLength() > 0);
+};
+
+OptionalContentGroup::~OptionalContentGroup()
+{
+  delete m_name;
+  delete m_children;
+}
+
diff --git a/poppler/OptionalContent.h b/poppler/OptionalContent.h
new file mode 100644
index 0000000..2ab951f
--- /dev/null
+++ b/poppler/OptionalContent.h
@@ -0,0 +1,81 @@
+//========================================================================
+//
+// OptionalContent.h
+//
+// Copyright 2007 Brad Hards <[EMAIL PROTECTED]>
+//
+// 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 orderedOptionalContentGroups; }
+
+  OptionalContentGroup* findOcgByRef( const Ref ref);
+
+  void parseNestedOrderArray(Array *array, OptionalContentGroup *parent);
+
+private:
+  GooList *optionalContentGroups;
+  GooList *orderedOptionalContentGroups;
+};
+
+//------------------------------------------------------------------------
+
+class OptionalContentGroup {
+public:
+
+  enum State { On, Off, HeaderOnly };
+
+  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; };
+
+  GooList *getChildren();
+  bool hasChildren();
+  void appendChild(OptionalContentGroup *child);
+
+private:
+  XRef *xref;
+  GooString *m_name;
+  Ref m_ref;
+  State m_state; 
+  GooList *m_children;
+  // Todo: Intent, Usage
+};
+
+#endif
diff --git a/poppler/PDFDoc.cc b/poppler/PDFDoc.cc
index 78fbea2..897a177 100644
--- a/poppler/PDFDoc.cc
+++ b/poppler/PDFDoc.cc
@@ -62,6 +62,7 @@ PDFDoc::PDFDoc(GooString *fileNameA, GooString *ownerPassword,
   str = NULL;
   xref = NULL;
   catalog = NULL;
+  optContentConfig = NULL;
 #ifndef DISABLE_OUTLINE
   outline = NULL;
 #endif
@@ -119,6 +120,7 @@ PDFDoc::PDFDoc(wchar_t *fileNameA, int fileNameLen, GooString *ownerPassword,
   str = NULL;
   xref = NULL;
   catalog = NULL;
+  optContentConfig = NULL;
 #ifndef DISABLE_OUTLINE
   outline = NULL;
 #endif
@@ -172,9 +174,11 @@ PDFDoc::PDFDoc(BaseStream *strA, GooString *ownerPassword,
   str = strA;
   xref = NULL;
   catalog = NULL;
+  optContentConfig = NULL;
 #ifndef DISABLE_OUTLINE
   outline = NULL;
 #endif
+
   ok = setup(ownerPassword, userPassword);
 }
 
@@ -215,11 +219,18 @@ GBool PDFDoc::setup(GooString *ownerPassword, GooString *userPassword) {
   outline = new Outline(catalog->getOutline(), xref);
 #endif
 
+  optContentConfig = new OCGs(catalog->getOptContentProps(), xref);
+
   // done
   return gTrue;
 }
 
 PDFDoc::~PDFDoc() {
+  // the check is redundant, but I'll be consistent
+  if (optContentConfig) {
+    delete optContentConfig;
+  }
+
 #ifndef DISABLE_OUTLINE
   if (outline) {
     delete outline;
diff --git a/poppler/PDFDoc.h b/poppler/PDFDoc.h
index e6992ba..8a9916a 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;
@@ -61,6 +62,9 @@ public:
   // Get catalog.
   Catalog *getCatalog() { return catalog; }
 
+  // Get optional content configuration
+  OCGs *getOptContentConfig() { return optContentConfig; }
+
   // Get base stream.
   BaseStream *getBaseStream() { return str; }
 
@@ -191,6 +195,7 @@ private:
 #ifndef DISABLE_OUTLINE
   Outline *outline;
 #endif
+  OCGs *optContentConfig;
 
   GBool ok;
   int errCode;
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/Makefile.am b/qt4/src/Makefile.am
index 0ac6ef4..d98e201 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			\
 	../../qt/poppler-page-transition.h
 
 lib_LTLIBRARIES = libpoppler-qt4.la
@@ -25,6 +26,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				\
@@ -37,7 +40,7 @@ libpoppler_qt4_la_SOURCES =			\
 
 libpoppler_qt4_la_LIBADD = 			\
 	$(top_builddir)/poppler/libpoppler.la	\
-	$(FONTCONFIG_LIBS)				\
+	$(FONTCONFIG_LIBS)			\
 	$(POPPLER_QT4_LIBS)
 
 if BUILD_SPLASH_OUTPUT
@@ -47,3 +50,8 @@ 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 ef11fdd..4f3c096 100644
--- a/qt4/src/poppler-document.cc
+++ b/qt4/src/poppler-document.cc
@@ -497,6 +497,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()->getOCGs(), 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..4aed7d1
--- /dev/null
+++ b/qt4/src/poppler-optcontent.cc
@@ -0,0 +1,187 @@
+/* poppler-optcontent.cc: qt interface to poppler
+ *
+ * Copyright (C) 2007, Brad Hards <[EMAIL PROTECTED]>
+ *
+ * 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 <QDebug>
+
+namespace Poppler
+{
+  OptContentItem::OptContentItem( OptionalContentGroup *group )
+  {
+    m_parent = 0;
+    m_name = group->name()->getCString();
+    if ( group->state() == OptionalContentGroup::On ) {
+      m_state = true;
+    } else if (group->state() == OptionalContentGroup::Off ) {
+      m_state = false;
+    } else {
+      qDebug() << "need to handle header";
+    }
+  }
+
+  OptContentItem::OptContentItem()
+  {
+    m_parent = 0;
+  }
+
+  OptContentItem::~OptContentItem()
+  {
+    qDeleteAll( m_children );
+  }
+
+  void OptContentItem::addChild( OptContentItem *child )
+  {
+    m_children += child;
+    child->setParent( this );
+  }
+
+  OptContentModel::OptContentModel( GooList *ocgs, QObject *parent)
+    : QAbstractItemModel(parent)
+  {
+    m_rootNode = new OptContentItem();
+
+    for (int i = 0; i < ocgs->getLength(); ++i) {
+      OptionalContentGroup *ocg = static_cast<OptionalContentGroup*>(ocgs->get(i));
+      if (ocg->state() != OptionalContentGroup::HeaderOnly) {
+	OptContentItem *node = new OptContentItem( ocg );
+	addChild( m_rootNode, node );
+	if ( ocg->hasChildren() ) {
+	  parseChildItems( ocg->getChildren(), node );
+	}
+      }
+    }
+  }
+
+  OptContentModel::~OptContentModel()
+  {
+    delete m_rootNode;
+  }
+
+  void OptContentModel::setRootNode( OptContentItem *node )
+  {
+    delete m_rootNode;
+    m_rootNode = node;
+    reset();
+  }
+
+  void OptContentModel::parseChildItems( GooList *children, OptContentItem *parentNode )
+  {
+    for (int i = 0; i < children->getLength(); ++i) {
+      OptionalContentGroup *child = static_cast<OptionalContentGroup*>(children->get(i));
+      OptContentItem *childNode = new OptContentItem( child );
+      addChild( parentNode, childNode );
+      if ( child->hasChildren() ) {
+	parseChildItems( child->getChildren(), childNode );
+      }
+    }
+  }    
+
+  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) {
+      return QVariant();
+    }
+
+    OptContentItem *node = nodeFromIndex( index );
+    if (!node) {
+      return QVariant();
+    }
+
+    if (index.column() == 0) {
+      return node->state();
+    } else if (index.column() == 1) {
+      return node->name();
+    }
+
+    return QVariant();
+  }
+
+#if 0
+  Qt::ItemFlags OptContentModel::flags ( const QModelIndex & index ) const
+  {
+    return QAbstractItemModel::flags(index) | Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsUserCheckable | Qt::ItemIsEnabled;
+  }
+#endif
+
+  void OptContentModel::addChild( OptContentItem *parent, OptContentItem *child )
+  {
+    parent->addChild( child );
+  }
+
+  OptContentItem* OptContentModel::nodeFromIndex( const QModelIndex &index ) const
+  {
+    if (index.isValid()) {
+      return static_cast<OptContentItem *>(index.internalPointer());
+    } else {
+      return m_rootNode;
+    }
+  }
+}
+
+
diff --git a/qt4/src/poppler-optcontent.h b/qt4/src/poppler-optcontent.h
new file mode 100644
index 0000000..1b26660
--- /dev/null
+++ b/qt4/src/poppler-optcontent.h
@@ -0,0 +1,85 @@
+/* poppler-optcontent.h: qt interface to poppler
+ *
+ * Copyright (C) 2007, Brad Hards <[EMAIL PROTECTED]>
+ *
+ * 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 <QAbstractListModel>
+#include <QStringList>
+
+#include "goo/GooList.h"
+class OptionalContentGroup;
+
+namespace Poppler
+{
+  class OptContentItem
+  {
+    public:
+    OptContentItem( OptionalContentGroup *group );
+    OptContentItem();
+    ~OptContentItem();
+
+    QString name() const { return m_name; };
+    bool state() const { return m_state; };
+
+    QList<OptContentItem*> childList() { return m_children; };
+
+    void setParent( OptContentItem* parent) { m_parent = parent; };
+    OptContentItem* parent() { return m_parent; };
+
+    void addChild( OptContentItem *child );
+
+    private:
+    QString m_name;
+    bool m_state; // true for ON, false for OFF
+    QList<OptContentItem*> m_children;
+    OptContentItem *m_parent;
+  };
+
+  class OptContentModel : public QAbstractItemModel
+  {
+    Q_OBJECT
+
+    public:
+    OptContentModel( GooList *ocgs, 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;
+
+    // Qt::ItemFlags flags ( const QModelIndex & index ) const;
+
+    void addChild( OptContentItem *parent, OptContentItem *child);
+
+    private:
+    OptContentItem *nodeFromIndex( const QModelIndex &index ) const;
+    void parseChildItems( GooList *children, OptContentItem *parentNode );
+
+    OptContentItem *m_rootNode;
+  };
+}
+
+#endif
diff --git a/qt4/src/poppler-private.h b/qt4/src/poppler-private.h
index 3c46e58..99fa50e 100644
--- a/qt4/src/poppler-private.h
+++ b/qt4/src/poppler-private.h
@@ -124,6 +124,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;
@@ -135,6 +136,8 @@ namespace Poppler {
 	~DocumentData()
 	{
 		qDeleteAll(m_embeddedFiles);
+		delete m_optContentModel;
+		m_optContentModel = 0;
 		delete doc;
 		delete m_outputDev;
 		delete m_fontInfoScanner;
@@ -279,6 +282,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 cb3a340..f2f6c0c 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>
@@ -757,7 +758,6 @@ QString subject = m_doc->info("Subject");
 	/**
 	   \overload
 
-
 	   \param numPages the number of pages to scan
 	   \param fontList pointer to the list where the font information
 	   should be placed
@@ -804,6 +804,7 @@ QString subject = m_doc->info("Subject");
 	  \param color the new paper color
 	 */
 	void setPaperColor(const QColor &color);
+
 	/**
 	  The paper color
 
@@ -820,6 +821,7 @@ QString subject = m_doc->info("Subject");
 	 \param backend the new rendering backend
 	 */
 	void setRenderBackend( RenderBackend backend );
+
 	/**
 	  The currently set render backend
 
@@ -840,6 +842,7 @@ QString subject = m_doc->info("Subject");
 	 \param on whether the flag should be added or removed.
 	 */
 	void setRenderHint( RenderHint hint, bool on = true );
+
 	/**
 	  The currently set render hints.
 	 */
@@ -858,7 +861,25 @@ QString subject = m_doc->info("Subject");
 	QString metadata() const;
 
 	/**
-	   Destructor.
+	   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..9938e18 100644
--- a/qt4/tests/.gitignore
+++ b/qt4/tests/.gitignore
@@ -11,10 +11,12 @@ test-poppler-qt4
 test-password-qt4
 poppler-attachments
 poppler-fonts
+poppler-optcontent
 check_attachments
 check_dateConversion
 check_fonts
 check_metadata
+check_optcontent
 check_permissions
 check_pagelayout
 check_pagemode
diff --git a/qt4/tests/Makefile.am b/qt4/tests/Makefile.am
index 7698320..0d9ce4d 100644
--- a/qt4/tests/Makefile.am
+++ b/qt4/tests/Makefile.am
@@ -19,7 +19,7 @@ SUFFIXES: .moc
 
 noinst_PROGRAMS = test-poppler-qt4 stress-poppler-qt4 \
 	poppler-fonts test-password-qt4 stress-poppler-dir \
-	poppler-attachments
+	poppler-attachments poppler-optcontent
 
 
 test_poppler_qt4_SOURCES =			\
@@ -33,7 +33,6 @@ test_password_qt4_SOURCES =			\
 
 test_password_qt4_LDADD = $(LDADDS)
 
-
 poppler_fonts_SOURCES =			\
        poppler-fonts.cpp
 
@@ -44,6 +43,10 @@ poppler_attachments_SOURCES =			\
 
 poppler_attachments_LDADD = $(LDADDS)
 
+poppler_optcontent_SOURCES =			\
+       poppler-optcontent.cpp
+
+poppler_optcontent_LDADD = $(LDADDS)
 
 stress_poppler_qt4_SOURCES =			\
        stress-poppler-qt4.cpp
@@ -61,6 +64,7 @@ TESTS = \
 	check_dateConversion 	\
 	check_fonts		\
 	check_metadata         	\
+	check_optcontent	\
 	check_permissions      	\
 	check_pagemode    	\
 	check_password    	\
@@ -85,6 +89,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..bb95a23
--- /dev/null
+++ b/qt4/tests/check_optcontent.cpp
@@ -0,0 +1,90 @@
+#include <QtTest/QtTest>
+
+#include <poppler-qt4.h>
+
+class TestOptionalContent: public QObject
+{
+    Q_OBJECT
+private slots:
+    void checkVisPolicy();
+    void checkNestedLayers();
+    void checkNoOptionalContent();
+};
+
+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 ).toBool(), true );
+    index = optContent->index( 1, 0, QModelIndex() );
+    QCOMPARE( optContent->data( index, Qt::DisplayRole ).toBool(), true );
+    index = optContent->index( 0, 1, QModelIndex() );
+    QCOMPARE( optContent->data( index, Qt::DisplayRole ).toString(), QString( "A" ) );
+    index = optContent->index( 1, 1, QModelIndex() );
+    QCOMPARE( optContent->data( index, Qt::DisplayRole ).toString(), QString( "B" ) );
+
+    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 ).toBool(), false );
+    index = optContent->index( 0, 1, QModelIndex() );
+    QCOMPARE( optContent->data( index, Qt::DisplayRole ).toString(), QString( "Black Text and Green Snow" ) );
+
+    index = optContent->index( 1, 0, QModelIndex() );
+    QCOMPARE( optContent->data( index, Qt::DisplayRole ).toBool(), true );
+    index = optContent->index( 1, 1, QModelIndex() );
+    QCOMPARE( optContent->data( index, Qt::DisplayRole ).toString(), QString( "Mountains and Image" ) );
+
+    // This is a sub-item of "Mountains and Image"
+    QModelIndex subindex = optContent->index( 0, 0, index );
+    QCOMPARE( optContent->data( subindex, Qt::DisplayRole ).toBool(), true );
+    subindex = optContent->index( 0, 1, index );
+    QCOMPARE( optContent->data( subindex, Qt::DisplayRole ).toString(), QString( "Image" ) );
+
+    index = optContent->index( 2, 0, QModelIndex() );
+    QCOMPARE( optContent->data( index, Qt::DisplayRole ).toBool(), true );
+    index = optContent->index( 2, 1, QModelIndex() );
+    QCOMPARE( optContent->data( index, Qt::DisplayRole ).toString(), QString( "Starburst" ) );
+
+    index = optContent->index( 3, 0, QModelIndex() );
+    QCOMPARE( optContent->data( index, Qt::DisplayRole ).toBool(), false );
+    index = optContent->index( 3, 1, QModelIndex() );
+    QCOMPARE( optContent->data( index, Qt::DisplayRole ).toString(), QString( "Watermark" ) );
+
+    delete doc;
+}
+
+void TestOptionalContent::checkNoOptionalContent()
+{
+    Poppler::Document *doc;
+    doc = Poppler::Document::load("../../../test/unittestcases/orientation.pdf");
+    QVERIFY( doc );
+
+    QCOMPARE( doc->hasOptionalContent(), false );
+
+    delete doc;
+}
+
+QTEST_MAIN(TestOptionalContent)
+
+#include "check_optcontent.moc"
+
diff --git a/qt4/tests/poppler-optcontent.cpp b/qt4/tests/poppler-optcontent.cpp
new file mode 100644
index 0000000..bc3e412
--- /dev/null
+++ b/qt4/tests/poppler-optcontent.cpp
@@ -0,0 +1,32 @@
+#include <QtGui>
+#include <iostream>
+
+#include <poppler-qt4.h>
+
+int main( int argc, char **argv )
+{
+    QApplication a( argc, argv );
+
+    if (!( argc == 2 ))
+    {
+	qWarning() << "usage: poppler-fonts filename";
+	exit(1);
+    }
+  
+    Poppler::Document *doc = Poppler::Document::load(argv[1]);
+    if (!doc)
+    {
+	qWarning() << "doc not loaded";
+	exit(1);
+    }
+
+    QTreeView tree;
+    tree.setModel(doc->optionalContentModel(&tree));
+
+    tree.setWindowTitle("Optional Content Tree Test");
+    tree.show();
+
+    a.exec();
+    // in a real application, we'd:
+    // delete doc;
+}
-- 
1.5.3.3

_______________________________________________
poppler mailing list
[email protected]
http://lists.freedesktop.org/mailman/listinfo/poppler

Reply via email to