/* poppler-document.cc: glib wrapper for poppler
 * Copyright (C) 2005, Red Hat, Inc.
 *
 * 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 "config.h"
#include <string.h>

#ifndef __GI_SCANNER__
#include <goo/GooList.h>
#include <splash/SplashBitmap.h>
#include <GlobalParams.h>
#include <PDFDoc.h>
#include <Outline.h>
#include <ErrorCodes.h>
#include <UnicodeMap.h>
#include <GfxState.h>
#include <SplashOutputDev.h>
#include <Stream.h>
#include <FontInfo.h>
#include <PDFDocEncoding.h>
#include <OptionalContent.h>
#endif

#include "poppler.h"
#include "poppler-private.h"
#include "poppler-enums.h"
#include "poppler-input-stream.h"
#include "poppler-cached-file-loader.h"

/**
 * SECTION:poppler-document
 * @short_description: Information about a document
 * @title: PopplerDocument
 *
 * The #PopplerDocument is an object used to refer to a main document.
 */

enum {
	PROP_0,
	PROP_TITLE,
	PROP_FORMAT,
	PROP_FORMAT_MAJOR,
	PROP_FORMAT_MINOR,
	PROP_AUTHOR,
	PROP_SUBJECT,
	PROP_KEYWORDS,
	PROP_CREATOR,
	PROP_PRODUCER,
	PROP_CREATION_DATE,
	PROP_MOD_DATE,
	PROP_LINEARIZED,
	PROP_PAGE_LAYOUT,
	PROP_PAGE_MODE,
	PROP_VIEWER_PREFERENCES,
	PROP_PERMISSIONS,
	PROP_METADATA
};

static void poppler_document_layers_free (PopplerDocument *document);

typedef struct _PopplerDocumentClass PopplerDocumentClass;
struct _PopplerDocumentClass
{
  GObjectClass parent_class;
};

G_DEFINE_TYPE (PopplerDocument, poppler_document, G_TYPE_OBJECT)

static PopplerDocument *
_poppler_document_new_from_pdfdoc (PDFDoc  *newDoc,
                                   GError **error)
{
  PopplerDocument *document;

  if (!newDoc->isOk()) {
    int fopen_errno;
    switch (newDoc->getErrorCode())
      {
      case errOpenFile:
        // If there was an error opening the file, count it as a G_FILE_ERROR 
        // and set the GError parameters accordingly. (this assumes that the 
        // only way to get an errOpenFile error is if newDoc was created using 
        // a filename and thus fopen was called, which right now is true.
        fopen_errno = newDoc->getFopenErrno();
        g_set_error (error, G_FILE_ERROR,
		     g_file_error_from_errno (fopen_errno),
		     "%s", g_strerror (fopen_errno));
	break;
      case errBadCatalog:
        g_set_error (error, POPPLER_ERROR,
		     POPPLER_ERROR_BAD_CATALOG,
		     "Failed to read the document catalog");
	break;
      case errDamaged:
        g_set_error (error, POPPLER_ERROR,
		     POPPLER_ERROR_DAMAGED,
		     "PDF document is damaged");
	break;
      case errEncrypted:
        g_set_error (error, POPPLER_ERROR,
		     POPPLER_ERROR_ENCRYPTED,
		     "Document is encrypted");
	break;
      default:
        g_set_error (error, POPPLER_ERROR,
		     POPPLER_ERROR_INVALID,
		     "Failed to load document");
    }
    
    delete newDoc;
    return NULL;
  }

  document = (PopplerDocument *) g_object_new (POPPLER_TYPE_DOCUMENT, NULL);
  document->doc = newDoc;

  document->output_dev = new CairoOutputDev ();
  document->output_dev->startDoc(document->doc);

  return document;
}

static GooString *
poppler_password_to_latin1 (const gchar *password)
{
  gchar *password_latin;
  GooString *password_g;

  if (!password)
    return NULL;

  password_latin = g_convert(password, -1, "ISO-8859-1", "UTF-8",
                             NULL, NULL, NULL);
  password_g = new GooString (password_latin);
  g_free (password_latin);

  return password_g;
}

/**
 * poppler_document_new_from_file:
 * @uri: uri of the file to load
 * @password: (allow-none): password to unlock the file with, or %NULL
 * @error: (allow-none): Return location for an error, or %NULL
 * 
 * Creates a new #PopplerDocument.  If %NULL is returned, then @error will be
 * set. Possible errors include those in the #POPPLER_ERROR and #G_FILE_ERROR
 * domains.
 * 
 * Return value: A newly created #PopplerDocument, or %NULL
 **/
PopplerDocument *
poppler_document_new_from_file (const char  *uri,
				const char  *password,
				GError     **error)
{
  PDFDoc *newDoc;
  GooString *password_g;
  char *filename;

  if (!globalParams) {
    globalParams = new GlobalParams();
  }

  filename = g_filename_from_uri (uri, NULL, error);
  if (!filename)
    return NULL;

  password_g = poppler_password_to_latin1(password);

#ifdef G_OS_WIN32
  wchar_t *filenameW;
  int length;

  length = MultiByteToWideChar(CP_UTF8, 0, filename, -1, NULL, 0);

  filenameW = new WCHAR[length];
  if (!filenameW)
      return NULL;

  length = MultiByteToWideChar(CP_UTF8, 0, filename, -1, filenameW, length);

  newDoc = new PDFDoc(filenameW, length, password_g, password_g);
  delete [] filenameW;
#else
  GooString *filename_g;
  filename_g = new GooString (filename);
  newDoc = new PDFDoc(filename_g, password_g, password_g);
#endif
  g_free (filename);

  delete password_g;

  return _poppler_document_new_from_pdfdoc (newDoc, error);
}

/**
 * poppler_document_new_from_data:
 * @data: the pdf data contained in a char array
 * @length: the length of #data
 * @password: (allow-none): password to unlock the file with, or %NULL
 * @error: (allow-none): Return location for an error, or %NULL
 * 
 * Creates a new #PopplerDocument.  If %NULL is returned, then @error will be
 * set. Possible errors include those in the #POPPLER_ERROR and #G_FILE_ERROR
 * domains.
 * 
 * Return value: A newly created #PopplerDocument, or %NULL
 **/
PopplerDocument *
poppler_document_new_from_data (char        *data,
                                int          length,
                                const char  *password,
                                GError     **error)
{
  Object obj;
  PDFDoc *newDoc;
  MemStream *str;
  GooString *password_g;

  if (!globalParams) {
    globalParams = new GlobalParams();
  }
  
  // create stream
  obj.initNull();
  str = new MemStream(data, 0, length, &obj);

  password_g = poppler_password_to_latin1(password);
  newDoc = new PDFDoc(str, password_g, password_g);
  delete password_g;

  return _poppler_document_new_from_pdfdoc (newDoc, error);
}

static inline gboolean
stream_is_memory_buffer_or_local_file (GInputStream *stream)
{
  return G_IS_MEMORY_INPUT_STREAM(stream) ||
    (G_IS_FILE_INPUT_STREAM(stream) && strcmp(g_type_name_from_instance((GTypeInstance*)stream), "GLocalFileInputStream") == 0);
}

/**
 * poppler_document_new_from_stream:
 * @stream: a #GInputStream to read from
 * @length: the stream length, or -1 if not known
 * @password: (allow-none): password to unlock the file with, or %NULL
 * @cancellable: (allow-none): a #GCancellable, or %NULL
 * @error: (allow-none): Return location for an error, or %NULL
 *
 * Creates a new #PopplerDocument reading the PDF contents from @stream.
 * Note that the given #GInputStream must be seekable or %G_IO_ERROR_NOT_SUPPORTED
 * will be returned.
 * Possible errors include those in the #POPPLER_ERROR and #G_FILE_ERROR
 * domains.
 *
 * Returns: (transfer full): a new #PopplerDocument, or %NULL
 *
 * Since: 0.22
 */
PopplerDocument *
poppler_document_new_from_stream (GInputStream *stream,
                                  goffset       length,
                                  const char   *password,
                                  GCancellable *cancellable,
                                  GError      **error)
{
  Object obj;
  PDFDoc *newDoc;
  BaseStream *str;
  GooString *password_g;

  g_return_val_if_fail(G_IS_INPUT_STREAM(stream), NULL);
  g_return_val_if_fail(length == (goffset)-1 || length > 0, NULL);

  if (!globalParams) {
    globalParams = new GlobalParams();
  }

  if (!G_IS_SEEKABLE(stream) || !g_seekable_can_seek(G_SEEKABLE(stream))) {
    g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
                        "Stream is not seekable");
    return NULL;
  }

  obj.initNull();
  if (stream_is_memory_buffer_or_local_file(stream)) {
    str = new PopplerInputStream(stream, cancellable, 0, gFalse, 0, &obj);
  } else {
    CachedFile *cachedFile = new CachedFile(new PopplerCachedFileLoader(stream, cancellable, length), new GooString());
    str = new CachedFileStream(cachedFile, 0, gFalse, cachedFile->getLength(), &obj);
  }

  password_g = poppler_password_to_latin1(password);
  newDoc = new PDFDoc(str, password_g, password_g);
  delete password_g;

  return _poppler_document_new_from_pdfdoc (newDoc, error);
}

/**
 * poppler_document_new_from_gfile:
 * @file: a #GFile to load
 * @password: (allow-none): password to unlock the file with, or %NULL
 * @cancellable: (allow-none): a #GCancellable, or %NULL
 * @error: (allow-none): Return location for an error, or %NULL
 *
 * Creates a new #PopplerDocument reading the PDF contents from @file.
 * Possible errors include those in the #POPPLER_ERROR and #G_FILE_ERROR
 * domains.
 *
 * Returns: (transfer full): a new #PopplerDocument, or %NULL
 *
 * Since: 0.22
 */
PopplerDocument *
poppler_document_new_from_gfile (GFile        *file,
                                 const char   *password,
                                 GCancellable *cancellable,
                                 GError      **error)
{
  PopplerDocument *document;
  GFileInputStream *stream;

  g_return_val_if_fail(G_IS_FILE(file), NULL);

  if (g_file_is_native(file)) {
    gchar *uri;

    uri = g_file_get_uri(file);
    document = poppler_document_new_from_file(uri, password, error);
    g_free(uri);

    return document;
  }

  stream = g_file_read(file, cancellable, error);
  if (!stream)
    return NULL;

  document = poppler_document_new_from_stream(G_INPUT_STREAM(stream), -1, password, cancellable, error);
  g_object_unref(stream);

  return document;
}

static gboolean
handle_save_error (int      err_code,
		   GError **error)
{
  switch (err_code)
    {
    case errNone:
      break;
    case errOpenFile:
      g_set_error (error, POPPLER_ERROR,
		   POPPLER_ERROR_OPEN_FILE,
		   "Failed to open file for writing");
      break;
    case errEncrypted:
      g_set_error (error, POPPLER_ERROR,
		   POPPLER_ERROR_ENCRYPTED,
		   "Document is encrypted");
      break;
    default:
      g_set_error (error, POPPLER_ERROR,
		   POPPLER_ERROR_INVALID,
		   "Failed to save document");
    }

  return err_code == errNone;
}

/**
 * poppler_document_save:
 * @document: a #PopplerDocument
 * @uri: uri of file to save
 * @error: (allow-none): return location for an error, or %NULL
 *
 * Saves @document. Any change made in the document such as
 * form fields filled, annotations added or modified
 * will be saved.
 * If @error is set, %FALSE will be returned. Possible errors
 * include those in the #G_FILE_ERROR domain.
 * 
 * Return value: %TRUE, if the document was successfully saved
 **/
gboolean
poppler_document_save (PopplerDocument  *document,
		       const char       *uri,
		       GError          **error)
{
  char *filename;
  gboolean retval = FALSE;

  g_return_val_if_fail (POPPLER_IS_DOCUMENT (document), FALSE);

  filename = g_filename_from_uri (uri, NULL, error);
  if (filename != NULL) {
    GooString *fname = new GooString (filename);
    int err_code;
    g_free (filename);

    err_code = document->doc->saveAs (fname);
    retval = handle_save_error (err_code, error);
    delete fname;
  }

  return retval;
}

/**
 * poppler_document_save_a_copy:
 * @document: a #PopplerDocument
 * @uri: uri of file to save
 * @error: (allow-none): return location for an error, or %NULL
 * 
 * Saves a copy of the original @document.
 * Any change made in the document such as 
 * form fields filled by the user will not be saved. 
 * If @error is set, %FALSE will be returned. Possible errors
 * include those in the #G_FILE_ERROR domain.
 * 
 * Return value: %TRUE, if the document was successfully saved
 **/
gboolean
poppler_document_save_a_copy (PopplerDocument  *document,
			      const char       *uri,
			      GError          **error)
{
  char *filename;
  gboolean retval = FALSE;

  g_return_val_if_fail (POPPLER_IS_DOCUMENT (document), FALSE);

  filename = g_filename_from_uri (uri, NULL, error);
  if (filename != NULL) {
    GooString *fname = new GooString (filename);
    int err_code;
    g_free (filename);

    err_code = document->doc->saveWithoutChangesAs (fname);
    retval = handle_save_error (err_code, error);
    delete fname;
  }

  return retval;
}

static void
poppler_document_finalize (GObject *object)
{
  PopplerDocument *document = POPPLER_DOCUMENT (object);

  poppler_document_layers_free (document);
  delete document->output_dev;
  delete document->doc;

  G_OBJECT_CLASS (poppler_document_parent_class)->finalize (object);
}

/**
 * poppler_document_get_id:
 * @document: A #PopplerDocument
 * @permanent_id: (out) (allow-none): location to store an allocated string, use g_free() to free the returned string
 * @update_id: (out) (allow-none): location to store an allocated string, use g_free() to free the returned string
 *
 * Returns the PDF file identifier represented as two byte string arrays of size 32.
 * @permanent_id is the permanent identifier that is built based on the file
 * contents at the time it was originally created, so that this identifer
 * never changes. @update_id is the update identifier that is built based on
 * the file contents at the time it was last updated.
 *
 * Note that returned strings are not null-terminated, they have a fixed
 * size of 32 bytes.
 *
 * Returns: %TRUE if the @document contains an id, %FALSE otherwise
 *
 * Since: 0.16
 */
gboolean
poppler_document_get_id (PopplerDocument *document,
			 gchar          **permanent_id,
			 gchar          **update_id)
{
  GooString permanent;
  GooString update;
  gboolean  retval = FALSE;

  g_return_val_if_fail (POPPLER_IS_DOCUMENT (document), FALSE);

  if (permanent_id)
    *permanent_id = NULL;
  if (update_id)
    *update_id = NULL;

  if (document->doc->getID (permanent_id ? &permanent : NULL, update_id ? &update : NULL)) {
    if (permanent_id)
      *permanent_id = (gchar *)g_memdup (permanent.getCString(), 32);
    if (update_id)
      *update_id = (gchar *)g_memdup (update.getCString(), 32);

    retval = TRUE;
  }

  return retval;
}

/**
 * poppler_document_get_n_pages:
 * @document: A #PopplerDocument
 * 
 * Returns the number of pages in a loaded document.
 * 
 * Return value: Number of pages
 **/
int
poppler_document_get_n_pages (PopplerDocument *document)
{
  g_return_val_if_fail (POPPLER_IS_DOCUMENT (document), 0);

  return document->doc->getNumPages();
}

/**
 * poppler_document_get_page:
 * @document: A #PopplerDocument
 * @index: a page index 
 * 
 * Returns the #PopplerPage indexed at @index.  This object is owned by the
 * caller.
 *
 * Return value: (transfer full) : The #PopplerPage at @index
 **/
PopplerPage *
poppler_document_get_page (PopplerDocument  *document,
			   int               index)
{
  Page *page;

  g_return_val_if_fail (0 <= index &&
			index < poppler_document_get_n_pages (document),
			NULL);

  page = document->doc->getPage (index + 1);
  if (!page) return NULL;

  return _poppler_page_new (document, page, index);
}

/**
 * poppler_document_get_page_by_label:
 * @document: A #PopplerDocument
 * @label: a page label
 * 
 * Returns the #PopplerPage reference by @label.  This object is owned by the
 * caller.  @label is a human-readable string representation of the page number,
 * and can be document specific.  Typically, it is a value such as "iii" or "3".
 *
 * By default, "1" refers to the first page.
 * 
 * Return value: (transfer full) :The #PopplerPage referenced by @label
 **/
PopplerPage *
poppler_document_get_page_by_label (PopplerDocument  *document,
				    const char       *label)
{
  Catalog *catalog;
  GooString label_g(label);
  int index;

  catalog = document->doc->getCatalog();
  if (!catalog->labelToIndex (&label_g, &index))
    return NULL;

  return poppler_document_get_page (document, index);
}

/**
 * poppler_document_get_n_attachments:
 * @document: A #PopplerDocument
 *
 * Returns the number of attachments in a loaded document.
 * See also poppler_document_get_attachments()
 *
 * Return value: Number of attachments
 *
 * Since: 0.18
 */
guint
poppler_document_get_n_attachments (PopplerDocument *document)
{
  Catalog *catalog;

  g_return_val_if_fail (POPPLER_IS_DOCUMENT (document), 0);

  catalog = document->doc->getCatalog ();

  return catalog && catalog->isOk () ? catalog->numEmbeddedFiles () : 0;
}

/**
 * poppler_document_has_attachments:
 * @document: A #PopplerDocument
 * 
 * Returns %TRUE of @document has any attachments.
 * 
 * Return value: %TRUE, if @document has attachments.
 **/
gboolean
poppler_document_has_attachments (PopplerDocument *document)
{
  g_return_val_if_fail (POPPLER_IS_DOCUMENT (document), FALSE);

  return (poppler_document_get_n_attachments (document) != 0);
}

/**
 * poppler_document_get_attachments:
 * @document: A #PopplerDocument
 *
 * Returns a #GList containing #PopplerAttachment<!-- -->s.  These attachments
 * are unowned, and must be unreffed, and the list must be freed with
 * g_list_free().
 *
 * Return value: (element-type PopplerAttachment) (transfer full): a list of available attachments.
 **/
GList *
poppler_document_get_attachments (PopplerDocument *document)
{
  Catalog *catalog;
  int n_files, i;
  GList *retval = NULL;

  g_return_val_if_fail (POPPLER_IS_DOCUMENT (document), NULL);

  catalog = document->doc->getCatalog ();
  if (catalog == NULL || ! catalog->isOk ())
    return NULL;

  n_files = catalog->numEmbeddedFiles ();
  for (i = 0; i < n_files; i++)
    {
      PopplerAttachment *attachment;
      FileSpec *emb_file;

      emb_file = catalog->embeddedFile (i);
      if (!emb_file->isOk () || !emb_file->getEmbeddedFile()->isOk()) {
        delete emb_file;
	continue;
      }

      attachment = _poppler_attachment_new (emb_file);
      delete emb_file;

      retval = g_list_prepend (retval, attachment);
    }
  return g_list_reverse (retval);
}

/**
 * poppler_document_find_dest:
 * @document: A #PopplerDocument
 * @link_name: a named destination
 *
 * Finds named destination @link_name in @document
 *
 * Return value: The #PopplerDest destination or %NULL if
 * @link_name is not a destination. Returned value must
 * be freed with #poppler_dest_free
 **/
PopplerDest *
poppler_document_find_dest (PopplerDocument *document,
			    const gchar     *link_name)
{
	PopplerDest *dest = NULL;
	LinkDest *link_dest = NULL;
	GooString *g_link_name;

	g_return_val_if_fail (POPPLER_IS_DOCUMENT (document), NULL);
	g_return_val_if_fail (link_name != NULL, NULL);

	g_link_name = new GooString (link_name);

	if (g_link_name) {
		link_dest = document->doc->findDest (g_link_name);
		delete g_link_name;
	}

	if (link_dest) {
		dest = _poppler_dest_new_goto (document, link_dest);
		delete link_dest;
	}

	return dest;
}

char *_poppler_goo_string_to_utf8(GooString *s)
{
  char *result;

  if (s->hasUnicodeMarker()) {
    result = g_convert (s->getCString () + 2,
			s->getLength () - 2,
			"UTF-8", "UTF-16BE", NULL, NULL, NULL);
  } else {
    int len;
    gunichar *ucs4_temp;
    int i;
    
    len = s->getLength ();
    ucs4_temp = g_new (gunichar, len + 1);
    for (i = 0; i < len; ++i) {
      ucs4_temp[i] = pdfDocEncoding[(unsigned char)s->getChar(i)];
    }
    ucs4_temp[i] = 0;

    result = g_ucs4_to_utf8 (ucs4_temp, -1, NULL, NULL, NULL);

    g_free (ucs4_temp);
  }

  return result;
}

static gchar *
info_dict_get_string (Dict *info_dict, const gchar *key)
{
  Object obj;
  GooString *goo_value;
  gchar *result;

  if (!info_dict->lookup ((gchar *)key, &obj)->isString ()) {
    obj.free ();
    return NULL;
  }

  goo_value = obj.getString ();
  result = _poppler_goo_string_to_utf8(goo_value);
  obj.free ();

  return result;
}

static time_t
info_dict_get_date (Dict *info_dict, const gchar *key)
{
  Object obj;
  time_t result;

  if (!info_dict->lookup ((gchar *)key, &obj)->isString ()) {
    obj.free ();
    return (time_t)-1;
  }

  if (!_poppler_convert_pdf_date_to_gtime (obj.getString (), &result))
    result = (time_t)-1;
  obj.free ();

  return result;
}

static PopplerPageLayout
convert_page_layout (Catalog::PageLayout pageLayout)
{
  switch (pageLayout)
    {
    case Catalog::pageLayoutSinglePage:
      return POPPLER_PAGE_LAYOUT_SINGLE_PAGE;
    case Catalog::pageLayoutOneColumn:
      return POPPLER_PAGE_LAYOUT_ONE_COLUMN;
    case Catalog::pageLayoutTwoColumnLeft:
      return POPPLER_PAGE_LAYOUT_TWO_COLUMN_LEFT;
    case Catalog::pageLayoutTwoColumnRight:
      return POPPLER_PAGE_LAYOUT_TWO_COLUMN_RIGHT;
    case Catalog::pageLayoutTwoPageLeft:
      return POPPLER_PAGE_LAYOUT_TWO_PAGE_LEFT;
    case Catalog::pageLayoutTwoPageRight:
      return POPPLER_PAGE_LAYOUT_TWO_PAGE_RIGHT;
    case Catalog::pageLayoutNone:
    default:
      return POPPLER_PAGE_LAYOUT_UNSET;
    }
}

static PopplerPageMode
convert_page_mode (Catalog::PageMode pageMode)
{
  switch (pageMode)
    {
    case Catalog::pageModeOutlines:
      return POPPLER_PAGE_MODE_USE_OUTLINES;
    case Catalog::pageModeThumbs:
      return POPPLER_PAGE_MODE_USE_THUMBS;
    case Catalog::pageModeFullScreen:
      return POPPLER_PAGE_MODE_FULL_SCREEN;
    case Catalog::pageModeOC:
      return POPPLER_PAGE_MODE_USE_OC;
    case Catalog::pageModeAttach:
      return POPPLER_PAGE_MODE_USE_ATTACHMENTS;
    case Catalog::pageModeNone:
    default:
      return POPPLER_PAGE_MODE_UNSET;
    }
}

/**
 * poppler_document_get_pdf_version_string:
 * @document: A #PopplerDocument
 *
 * Returns the PDF version of @document as a string (e.g. PDF-1.6)
 *
 * Return value: a new allocated string containing the PDF version
 *               of @document, or %NULL
 *
 * Since: 0.16
 **/
gchar *
poppler_document_get_pdf_version_string (PopplerDocument *document)
{
  gchar *retval;

  g_return_val_if_fail (POPPLER_IS_DOCUMENT (document), NULL);

  retval = g_strndup ("PDF-", 15); /* allocates 16 chars, pads with \0s */
  g_ascii_formatd (retval + 4, 15 + 1 - 4, "%.2g",
		   document->doc->getPDFMajorVersion () + document->doc->getPDFMinorVersion() / 10.0);
  return retval;
}

/**
 * poppler_document_get_pdf_version:
 * @document: A #PopplerDocument
 * @major_version: (out) (allow-none): return location for the PDF major version number
 * @minor_version: (out) (allow-none): return location for the PDF minor version number
 *
 * Returns: the major and minor PDF version numbers
 *
 * Since: 0.16
 **/
void
poppler_document_get_pdf_version (PopplerDocument *document,
				  guint           *major_version,
				  guint           *minor_version)
{
  g_return_if_fail (POPPLER_IS_DOCUMENT (document));

  if (major_version)
    *major_version = document->doc->getPDFMajorVersion ();
  if (minor_version)
    *minor_version = document->doc->getPDFMinorVersion();
}

/**
 * poppler_document_get_title:
 * @document: A #PopplerDocument
 *
 * Returns the document's title
 *
 * Return value: a new allocated string containing the title
 *               of @document, or %NULL
 *
 * Since: 0.16
 **/
gchar *
poppler_document_get_title (PopplerDocument *document)
{
  Object obj;
  gchar *retval = NULL;

  g_return_val_if_fail (POPPLER_IS_DOCUMENT (document), NULL);

  document->doc->getDocInfo (&obj);
  if (obj.isDict ())
    retval = info_dict_get_string (obj.getDict(), "Title");
  obj.free ();

  return retval;
}

/**
 * poppler_document_get_author:
 * @document: A #PopplerDocument
 *
 * Returns the author of the document
 *
 * Return value: a new allocated string containing the author
 *               of @document, or %NULL
 *
 * Since: 0.16
 **/
gchar *
poppler_document_get_author (PopplerDocument *document)
{
  Object obj;
  gchar *retval = NULL;

  g_return_val_if_fail (POPPLER_IS_DOCUMENT (document), NULL);

  document->doc->getDocInfo (&obj);
  if (obj.isDict ())
    retval = info_dict_get_string (obj.getDict(), "Author");
  obj.free ();

  return retval;
}

/**
 * poppler_document_get_subject:
 * @document: A #PopplerDocument
 *
 * Returns the subject of the document
 *
 * Return value: a new allocated string containing the subject
 *               of @document, or %NULL
 *
 * Since: 0.16
 **/
gchar *
poppler_document_get_subject (PopplerDocument *document)
{
  Object obj;
  gchar *retval = NULL;

  g_return_val_if_fail (POPPLER_IS_DOCUMENT (document), NULL);

  document->doc->getDocInfo (&obj);
  if (obj.isDict ())
    retval = info_dict_get_string (obj.getDict(), "Subject");
  obj.free ();

  return retval;
}

/**
 * poppler_document_get_keywords:
 * @document: A #PopplerDocument
 *
 * Returns the keywords associated to the document
 *
 * Return value: a new allocated string containing keywords associated
 *               to @document, or %NULL
 *
 * Since: 0.16
 **/
gchar *
poppler_document_get_keywords (PopplerDocument *document)
{
  Object obj;
  gchar *retval = NULL;

  g_return_val_if_fail (POPPLER_IS_DOCUMENT (document), NULL);

  document->doc->getDocInfo (&obj);
  if (obj.isDict ())
    retval = info_dict_get_string (obj.getDict(), "Keywords");
  obj.free ();

  return retval;
}

/**
 * poppler_document_get_creator:
 * @document: A #PopplerDocument
 *
 * Returns the creator of the document. If the document was converted
 * from another format, the creator is the name of the product
 * that created the original document from which it was converted.
 *
 * Return value: a new allocated string containing the creator
 *               of @document, or %NULL
 *
 * Since: 0.16
 **/
gchar *
poppler_document_get_creator (PopplerDocument *document)
{
  Object obj;
  gchar *retval = NULL;

  g_return_val_if_fail (POPPLER_IS_DOCUMENT (document), NULL);

  document->doc->getDocInfo (&obj);
  if (obj.isDict ())
    retval = info_dict_get_string (obj.getDict(), "Creator");
  obj.free ();

  return retval;
}

/**
 * poppler_document_get_producer:
 * @document: A #PopplerDocument
 *
 * Returns the producer of the document. If the document was converted
 * from another format, the producer is the name of the product
 * that converted it to PDF
 *
 * Return value: a new allocated string containing the producer
 *               of @document, or %NULL
 *
 * Since: 0.16
 **/
gchar *
poppler_document_get_producer (PopplerDocument *document)
{
  Object obj;
  gchar *retval = NULL;

  g_return_val_if_fail (POPPLER_IS_DOCUMENT (document), NULL);

  document->doc->getDocInfo (&obj);
  if (obj.isDict ())
    retval = info_dict_get_string (obj.getDict(), "Producer");
  obj.free ();

  return retval;
}

/**
 * poppler_document_get_creation_date:
 * @document: A #PopplerDocument
 *
 * Returns the date the document was created as seconds since the Epoch
 *
 * Return value: the date the document was created, or -1
 *
 * Since: 0.16
 **/
time_t
poppler_document_get_creation_date (PopplerDocument *document)
{
  Object obj;
  time_t retval = (time_t)-1;

  g_return_val_if_fail (POPPLER_IS_DOCUMENT (document), (time_t)-1);

  document->doc->getDocInfo (&obj);
  if (obj.isDict ())
    retval = info_dict_get_date (obj.getDict(), "CreationDate");
  obj.free ();

  return retval;
}

/**
 * poppler_document_get_modification_date:
 * @document: A #PopplerDocument
 *
 * Returns the date the document was most recently modified as seconds since the Epoch
 *
 * Return value: the date the document was most recently modified, or -1
 *
 * Since: 0.16
 **/
time_t
poppler_document_get_modification_date (PopplerDocument *document)
{
  Object obj;
  time_t retval = (time_t)-1;

  g_return_val_if_fail (POPPLER_IS_DOCUMENT (document), (time_t)-1);

  document->doc->getDocInfo (&obj);
  if (obj.isDict ())
    retval = info_dict_get_date (obj.getDict(), "ModDate");
  obj.free ();

  return retval;
}

/**
 * poppler_document_is_linearized:
 * @document: A #PopplerDocument
 *
 * Returns whether @document is linearized or not. Linearization of PDF
 * enables efficient incremental access of the PDF file in a network environment.
 *
 * Return value: %TRUE if @document is linearized, %FALSE otherwhise
 *
 * Since: 0.16
 **/
gboolean
poppler_document_is_linearized (PopplerDocument *document)
{
  g_return_val_if_fail (POPPLER_IS_DOCUMENT (document), FALSE);

  return document->doc->isLinearized ();
}

/**
 * poppler_document_get_page_layout:
 * @document: A #PopplerDocument
 *
 * Returns the page layout that should be used when the document is opened
 *
 * Return value: a #PopplerPageLayout that should be used when the document is opened
 *
 * Since: 0.16
 **/
PopplerPageLayout
poppler_document_get_page_layout (PopplerDocument *document)
{
  Catalog *catalog;

  g_return_val_if_fail (POPPLER_IS_DOCUMENT (document), POPPLER_PAGE_LAYOUT_UNSET);

  catalog = document->doc->getCatalog ();
  if (catalog && catalog->isOk ())
    return convert_page_layout (catalog->getPageLayout ());

  return POPPLER_PAGE_LAYOUT_UNSET;
}

/**
 * poppler_document_get_page_mode:
 * @document: A #PopplerDocument
 *
 * Returns a #PopplerPageMode representing how the document should
 * be initially displayed when opened.
 *
 * Return value: a #PopplerPageMode that should be used when document is opened
 *
 * Since: 0.16
 **/
PopplerPageMode
poppler_document_get_page_mode (PopplerDocument *document)
{
  Catalog *catalog;

  g_return_val_if_fail (POPPLER_IS_DOCUMENT (document), POPPLER_PAGE_MODE_UNSET);

  catalog = document->doc->getCatalog ();
  if (catalog && catalog->isOk ())
    return convert_page_mode (catalog->getPageMode ());

  return POPPLER_PAGE_MODE_UNSET;
}

/**
 * poppler_document_get_permissions:
 * @document: A #PopplerDocument
 *
 * Returns the flags specifying which operations are permitted when the document is opened.
 *
 * Return value: a set of falgs from  #PopplerPermissions enumeration
 *
 * Since: 0.16
 **/
PopplerPermissions
poppler_document_get_permissions (PopplerDocument *document)
{
  guint flag = 0;

  g_return_val_if_fail (POPPLER_IS_DOCUMENT (document), POPPLER_PERMISSIONS_FULL);

  if (document->doc->okToPrint ())
    flag |= POPPLER_PERMISSIONS_OK_TO_PRINT;
  if (document->doc->okToChange ())
    flag |= POPPLER_PERMISSIONS_OK_TO_MODIFY;
  if (document->doc->okToCopy ())
    flag |= POPPLER_PERMISSIONS_OK_TO_COPY;
  if (document->doc->okToAddNotes ())
    flag |= POPPLER_PERMISSIONS_OK_TO_ADD_NOTES;
  if (document->doc->okToFillForm ())
    flag |= POPPLER_PERMISSIONS_OK_TO_FILL_FORM;
  if (document->doc->okToAccessibility())
    flag |= POPPLER_PERMISSIONS_OK_TO_EXTRACT_CONTENTS;
  if (document->doc->okToAssemble())
    flag |= POPPLER_PERMISSIONS_OK_TO_ASSEMBLE;
  if (document->doc->okToPrintHighRes())
    flag |= POPPLER_PERMISSIONS_OK_TO_PRINT_HIGH_RESOLUTION;

  return (PopplerPermissions)flag;
}

/**
 * poppler_document_get_metadata:
 * @document: A #PopplerDocument
 *
 * Returns the XML metadata string of the document
 *
 * Return value: a new allocated string containing the XML
 *               metadata, or %NULL
 *
 * Since: 0.16
 **/
gchar *
poppler_document_get_metadata (PopplerDocument *document)
{
  Catalog *catalog;
  gchar *retval = NULL;

  g_return_val_if_fail (POPPLER_IS_DOCUMENT (document), NULL);

  catalog = document->doc->getCatalog ();
  if (catalog && catalog->isOk ()) {
    GooString *s = catalog->readMetadata ();

    if (s != NULL) {
      retval = g_strdup (s->getCString());
      delete s;
    }
  }

  return retval;
}

static void
poppler_document_get_property (GObject    *object,
			       guint       prop_id,
			       GValue     *value,
			       GParamSpec *pspec)
{
  PopplerDocument *document = POPPLER_DOCUMENT (object);
  guint version;

  switch (prop_id)
    {
    case PROP_TITLE:
      g_value_take_string (value, poppler_document_get_title (document));
      break;
    case PROP_FORMAT:
      g_value_take_string (value, poppler_document_get_pdf_version_string (document));
      break;
    case PROP_FORMAT_MAJOR:
      poppler_document_get_pdf_version (document, &version, NULL);
      g_value_set_uint (value, version);
      break;
    case PROP_FORMAT_MINOR:
      poppler_document_get_pdf_version (document, NULL, &version);
      g_value_set_uint (value, version);
      break;
    case PROP_AUTHOR:
      g_value_take_string (value, poppler_document_get_author (document));
      break;
    case PROP_SUBJECT:
      g_value_take_string (value, poppler_document_get_subject (document));
      break;
    case PROP_KEYWORDS:
      g_value_take_string (value, poppler_document_get_keywords (document));
      break;
    case PROP_CREATOR:
      g_value_take_string (value, poppler_document_get_creator (document));
      break;
    case PROP_PRODUCER:
      g_value_take_string (value, poppler_document_get_producer (document));
      break;
    case PROP_CREATION_DATE:
      g_value_set_int (value, poppler_document_get_creation_date (document));
      break;
    case PROP_MOD_DATE:
      g_value_set_int (value, poppler_document_get_modification_date (document));
      break;
    case PROP_LINEARIZED:
      g_value_set_boolean (value, poppler_document_is_linearized (document));
      break;
    case PROP_PAGE_LAYOUT:
      g_value_set_enum (value, poppler_document_get_page_layout (document));
      break;
    case PROP_PAGE_MODE:
      g_value_set_enum (value, poppler_document_get_page_mode (document));
      break;
    case PROP_VIEWER_PREFERENCES:
      /* FIXME: write... */
      g_value_set_flags (value, POPPLER_VIEWER_PREFERENCES_UNSET);
      break;
    case PROP_PERMISSIONS:
      g_value_set_flags (value, poppler_document_get_permissions (document));
      break;
    case PROP_METADATA:
      g_value_take_string (value, poppler_document_get_metadata (document));
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
    }
}

static void
poppler_document_class_init (PopplerDocumentClass *klass)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);

  gobject_class->finalize = poppler_document_finalize;
  gobject_class->get_property = poppler_document_get_property;

  /**
   * PopplerDocument:title:
   *
   * The document's title or %NULL
   */
  g_object_class_install_property (G_OBJECT_CLASS (klass),
				   PROP_TITLE,
				   g_param_spec_string ("title",
							"Document Title",
							"The title of the document",
							NULL,
							G_PARAM_READABLE));

  /**
   * PopplerDocument:format:
   *
   * The PDF version as string. See also poppler_document_get_pdf_version_string()
   */
  g_object_class_install_property (G_OBJECT_CLASS (klass),
				   PROP_FORMAT,
				   g_param_spec_string ("format",
							"PDF Format",
							"The PDF version of the document",
							NULL,
							G_PARAM_READABLE));

  /**
   * PopplerDocument:format-major:
   *
   * The PDF major version number. See also poppler_document_get_pdf_version()
   */
  g_object_class_install_property (G_OBJECT_CLASS (klass),
				   PROP_FORMAT_MAJOR,
				   g_param_spec_uint ("format-major",
						      "PDF Format Major",
						      "The PDF major version number of the document",
						      0, G_MAXUINT, 1,
						      G_PARAM_READABLE));

  /**
   * PopplerDocument:format-minor:
   *
   * The PDF minor version number. See also poppler_document_get_pdf_version()
   */
  g_object_class_install_property (G_OBJECT_CLASS (klass),
				   PROP_FORMAT_MINOR,
				   g_param_spec_uint ("format-minor",
						      "PDF Format Minor",
						      "The PDF minor version number of the document",
						      0, G_MAXUINT, 0,
						      G_PARAM_READABLE));

  /**
   * PopplerDocument:author:
   *
   * The author of the document
   */
  g_object_class_install_property (G_OBJECT_CLASS (klass),
				   PROP_AUTHOR,
				   g_param_spec_string ("author",
							"Author",
							"The author of the document",
							NULL,
							G_PARAM_READABLE));

  /**
   * PopplerDocument:subject:
   *
   * The subject of the document
   */
  g_object_class_install_property (G_OBJECT_CLASS (klass),
				   PROP_SUBJECT,
				   g_param_spec_string ("subject",
							"Subject",
							"Subjects the document touches",
							NULL,
							G_PARAM_READABLE));

  /**
   * PopplerDocument:keywords:
   *
   * The keywords associated to the document
   */
  g_object_class_install_property (G_OBJECT_CLASS (klass),
				   PROP_KEYWORDS,
				   g_param_spec_string ("keywords",
							"Keywords",
							"Keywords",
							NULL,
							G_PARAM_READABLE));

  /**
   * PopplerDocument:creator:
   *
   * The creator of the document. See also poppler_document_get_creator()
   */
  g_object_class_install_property (G_OBJECT_CLASS (klass),
				   PROP_CREATOR,
				   g_param_spec_string ("creator",
							"Creator",
							"The software that created the document",
							NULL,
							G_PARAM_READABLE));

  /**
   * PopplerDocument:producer:
   *
   * The producer of the document. See also poppler_document_get_producer()
   */
  g_object_class_install_property (G_OBJECT_CLASS (klass),
				   PROP_PRODUCER,
				   g_param_spec_string ("producer",
							"Producer",
							"The software that converted the document",
							NULL,
							G_PARAM_READABLE));

  /**
   * PopplerDocument:creation-date:
   *
   * The date the document was created as seconds since the Epoch, or -1
   */
  g_object_class_install_property (G_OBJECT_CLASS (klass),
				   PROP_CREATION_DATE,
				   g_param_spec_int ("creation-date",
						     "Creation Date",
						     "The date and time the document was created",
						     -1, G_MAXINT, -1,
						     G_PARAM_READABLE));

  /**
   * PopplerDocument:mod-date:
   *
   * The date the document was most recently modified as seconds since the Epoch, or -1
   */
  g_object_class_install_property (G_OBJECT_CLASS (klass),
				   PROP_MOD_DATE,
				   g_param_spec_int ("mod-date",
						     "Modification Date",
						     "The date and time the document was modified",
						     -1, G_MAXINT, -1,
						     G_PARAM_READABLE));

  /**
   * PopplerDocument:linearized:
   *
   * Whether document is linearized. See also poppler_document_is_linearized()
   */
  g_object_class_install_property (G_OBJECT_CLASS (klass),
				   PROP_LINEARIZED,
				   g_param_spec_boolean ("linearized",
							 "Fast Web View Enabled",
							 "Is the document optimized for web viewing?",
							 FALSE,
							 G_PARAM_READABLE));

  /**
   * PopplerDocument:page-layout:
   *
   * The page layout that should be used when the document is opened
   */
  g_object_class_install_property (G_OBJECT_CLASS (klass),
				   PROP_PAGE_LAYOUT,
				   g_param_spec_enum ("page-layout",
						      "Page Layout",
						      "Initial Page Layout",
						      POPPLER_TYPE_PAGE_LAYOUT,
						      POPPLER_PAGE_LAYOUT_UNSET,
						      G_PARAM_READABLE));

  /**
   * PopplerDocument:page-mode:
   *
   * The mode that should be used when the document is opened
   */
  g_object_class_install_property (G_OBJECT_CLASS (klass),
				   PROP_PAGE_MODE,
				   g_param_spec_enum ("page-mode",
						      "Page Mode",
						      "Page Mode",
						      POPPLER_TYPE_PAGE_MODE,
						      POPPLER_PAGE_MODE_UNSET,
						      G_PARAM_READABLE));

  /**
   * PopplerDocument:viewer-preferences:
   */
  g_object_class_install_property (G_OBJECT_CLASS (klass),
				   PROP_VIEWER_PREFERENCES,
				   g_param_spec_flags ("viewer-preferences",
						       "Viewer Preferences",
						       "Viewer Preferences",
						       POPPLER_TYPE_VIEWER_PREFERENCES,
						       POPPLER_VIEWER_PREFERENCES_UNSET,
						       G_PARAM_READABLE));

  /**
   * PopplerDocument:permissions:
   *
   * Flags specifying which operations are permitted when the document is opened
   */
  g_object_class_install_property (G_OBJECT_CLASS (klass),
				   PROP_PERMISSIONS,
				   g_param_spec_flags ("permissions",
						       "Permissions",
						       "Permissions",
						       POPPLER_TYPE_PERMISSIONS,
						       POPPLER_PERMISSIONS_FULL,
						       G_PARAM_READABLE));

  /**
   * PopplerDocument:metadata:
   *
   * Document metadata in XML format, or %NULL
   */
  g_object_class_install_property (G_OBJECT_CLASS (klass),
				   PROP_METADATA,
				   g_param_spec_string ("metadata",
							"XML Metadata",
							"Embedded XML metadata",
							NULL,
							G_PARAM_READABLE));
}

static void
poppler_document_init (PopplerDocument *document)
{
}

/* PopplerIndexIter: For determining the index of a tree */
struct _PopplerIndexIter
{
	PopplerDocument *document;
	GooList *items;
	int index;
};


POPPLER_DEFINE_BOXED_TYPE (PopplerIndexIter, poppler_index_iter,
			   poppler_index_iter_copy,
			   poppler_index_iter_free)

/**
 * poppler_index_iter_copy:
 * @iter: a #PopplerIndexIter
 * 
 * Creates a new #PopplerIndexIter as a copy of @iter.  This must be freed with
 * poppler_index_iter_free().
 * 
 * Return value: a new #PopplerIndexIter
 **/
PopplerIndexIter *
poppler_index_iter_copy (PopplerIndexIter *iter)
{
	PopplerIndexIter *new_iter;

	g_return_val_if_fail (iter != NULL, NULL);

	new_iter = g_slice_dup (PopplerIndexIter, iter);
	new_iter->document = (PopplerDocument *) g_object_ref (new_iter->document);

	return new_iter;
}

/**
 * poppler_index_iter_new:
 * @document: a #PopplerDocument
 * 
 * Returns the root #PopplerIndexIter for @document, or %NULL.  This must be
 * freed with poppler_index_iter_free().
 *
 * Certain documents have an index associated with them.  This index can be used
 * to help the user navigate the document, and is similar to a table of
 * contents.  Each node in the index will contain a #PopplerAction that can be
 * displayed to the user &mdash; typically a #POPPLER_ACTION_GOTO_DEST or a
 * #POPPLER_ACTION_URI<!-- -->.
 *
 * Here is a simple example of some code that walks the full index:
 *
 * <informalexample><programlisting>
 * static void
 * walk_index (PopplerIndexIter *iter)
 * {
 *   do
 *     {
 *       /<!-- -->* Get the the action and do something with it *<!-- -->/
 *       PopplerIndexIter *child = poppler_index_iter_get_child (iter);
 *       if (child)
 *         walk_index (child);
 *       poppler_index_iter_free (child);
 *     }
 *   while (poppler_index_iter_next (iter));
 * }
 * ...
 * {
 *   iter = poppler_index_iter_new (document);
 *   walk_index (iter);
 *   poppler_index_iter_free (iter);
 * }
 *</programlisting></informalexample>
 *
 * Return value: a new #PopplerIndexIter
 **/
PopplerIndexIter *
poppler_index_iter_new (PopplerDocument *document)
{
	PopplerIndexIter *iter;
	Outline *outline;
	GooList *items;

	outline = document->doc->getOutline();
	if (outline == NULL)
		return NULL;

	items = outline->getItems();
	if (items == NULL)
		return NULL;

	iter = g_slice_new (PopplerIndexIter);
	iter->document = (PopplerDocument *) g_object_ref (document);
	iter->items = items;
	iter->index = 0;

	return iter;
}

/**
 * poppler_index_iter_get_child:
 * @parent: a #PopplerIndexIter
 * 
 * Returns a newly created child of @parent, or %NULL if the iter has no child.
 * See poppler_index_iter_new() for more information on this function.
 * 
 * Return value: a new #PopplerIndexIter
 **/
PopplerIndexIter *
poppler_index_iter_get_child (PopplerIndexIter *parent)
{
	PopplerIndexIter *child;
	OutlineItem *item;

	g_return_val_if_fail (parent != NULL, NULL);
	
	item = (OutlineItem *)parent->items->get (parent->index);
	item->open ();
	if (! (item->hasKids() && item->getKids()) )
		return NULL;

	child = g_slice_new0 (PopplerIndexIter);
	child->document = (PopplerDocument *)g_object_ref (parent->document);
	child->items = item->getKids ();

	g_assert (child->items);

	return child;
}

static gchar *
unicode_to_char (Unicode *unicode,
		 int      len)
{
	static UnicodeMap *uMap = NULL;
	if (uMap == NULL) {
		GooString *enc = new GooString("UTF-8");
		uMap = globalParams->getUnicodeMap(enc);
		uMap->incRefCnt ();
		delete enc;
	}
		
	GooString gstr;
	gchar buf[8]; /* 8 is enough for mapping an unicode char to a string */
	int i, n;

	for (i = 0; i < len; ++i) {
		n = uMap->mapUnicode(unicode[i], buf, sizeof(buf));
		gstr.append(buf, n);
	}

	return g_strdup (gstr.getCString ());
}

/**
 * poppler_index_iter_is_open:
 * @iter: a #PopplerIndexIter
 * 
 * Returns whether this node should be expanded by default to the user.  The
 * document can provide a hint as to how the document's index should be expanded
 * initially.
 * 
 * Return value: %TRUE, if the document wants @iter to be expanded
 **/
gboolean
poppler_index_iter_is_open (PopplerIndexIter *iter)
{
	OutlineItem *item;

	item = (OutlineItem *)iter->items->get (iter->index);

	return item->isOpen();
}

/**
 * poppler_index_iter_get_action:
 * @iter: a #PopplerIndexIter
 * 
 * Returns the #PopplerAction associated with @iter.  It must be freed with
 * poppler_action_free().
 * 
 * Return value: a new #PopplerAction
 **/
PopplerAction *
poppler_index_iter_get_action (PopplerIndexIter  *iter)
{
	OutlineItem *item;
	LinkAction *link_action;
	PopplerAction *action;
	gchar *title;

	g_return_val_if_fail (iter != NULL, NULL);

	item = (OutlineItem *)iter->items->get (iter->index);
	link_action = item->getAction ();

	title = unicode_to_char (item->getTitle(),
				 item->getTitleLength ());

	action = _poppler_action_new (iter->document, link_action, title);
	g_free (title);

	return action;
}

/**
 * poppler_index_iter_next:
 * @iter: a #PopplerIndexIter
 * 
 * Sets @iter to point to the next action at the current level, if valid.  See
 * poppler_index_iter_new() for more information.
 * 
 * Return value: %TRUE, if @iter was set to the next action
 **/
gboolean
poppler_index_iter_next (PopplerIndexIter *iter)
{
	g_return_val_if_fail (iter != NULL, FALSE);

	iter->index++;
	if (iter->index >= iter->items->getLength())
		return FALSE;

	return TRUE;
}

/**
 * poppler_index_iter_free:
 * @iter: a #PopplerIndexIter
 * 
 * Frees @iter.
 **/
void
poppler_index_iter_free (PopplerIndexIter *iter)
{
	if (G_UNLIKELY (iter == NULL))
		return;

	g_object_unref (iter->document);
	g_slice_free (PopplerIndexIter, iter);
}

struct _PopplerFontsIter
{
	GooList *items;
	int index;
};

POPPLER_DEFINE_BOXED_TYPE (PopplerFontsIter, poppler_fonts_iter,
			   poppler_fonts_iter_copy,
			   poppler_fonts_iter_free)

/**
 * poppler_fonts_iter_get_full_name:
 * @iter: a #PopplerFontsIter
 *
 * Returns the full name of the font associated with @iter
 *
 * Returns: the font full name
 */
const char *
poppler_fonts_iter_get_full_name (PopplerFontsIter *iter)
{
	GooString *name;
	FontInfo *info;

	info = (FontInfo *)iter->items->get (iter->index);

	name = info->getName();
	if (name != NULL) {
		return info->getName()->getCString();
	} else {
		return NULL;
	}
}

/**
 * poppler_fonts_iter_get_name:
 * @iter: a #PopplerFontsIter
 *
 * Returns the name of the font associated with @iter
 *
 * Returns: the font name
 */
const char *
poppler_fonts_iter_get_name (PopplerFontsIter *iter)
{
	FontInfo *info;
	const char *name;

	name = poppler_fonts_iter_get_full_name (iter);
	info = (FontInfo *)iter->items->get (iter->index);

	if (info->getSubset() && name) {
		while (*name && *name != '+')
			name++;

		if (*name)
			name++;
	}

	return name;
}

/**
 * poppler_fonts_iter_get_substitute_name:
 * @iter: a #PopplerFontsIter
 *
 * The name of the substitute font of the font associated with @iter or %NULL if
 * the font is embedded
 *
 * Returns: the name of the substitute font or %NULL if font is embedded
 *
 * Since: 0.20
 */
const char *
poppler_fonts_iter_get_substitute_name (PopplerFontsIter *iter)
{
	GooString *name;
	FontInfo *info;

	info = (FontInfo *)iter->items->get (iter->index);

	name = info->getSubstituteName();
	if (name != NULL) {
		return name->getCString();
	} else {
		return NULL;
	}
}

/**
 * poppler_fonts_iter_get_file_name:
 * @iter: a #PopplerFontsIter
 *
 * The filename of the font associated with @iter or %NULL if
 * the font is embedded
 *
 * Returns: the filename of the font or %NULL if font is embedded
 */
const char *
poppler_fonts_iter_get_file_name (PopplerFontsIter *iter)
{
	GooString *file;
	FontInfo *info;

	info = (FontInfo *)iter->items->get (iter->index);

	file = info->getFile();
	if (file != NULL) {
		return file->getCString();
	} else {
		return NULL;
	}
}

/**
 * poppler_fonts_iter_get_font_type:
 * @iter: a #PopplerFontsIter
 *
 * Returns the type of the font associated with @iter
 *
 * Returns: the font type
 */
PopplerFontType
poppler_fonts_iter_get_font_type (PopplerFontsIter *iter)
{
	FontInfo *info;

	g_return_val_if_fail (iter != NULL, POPPLER_FONT_TYPE_UNKNOWN);

	info = (FontInfo *)iter->items->get (iter->index);

	return (PopplerFontType)info->getType ();
}

/**
 * poppler_fonts_iter_get_encoding:
 * @iter: a #PopplerFontsIter
 *
 * Returns the encoding of the font associated with @iter
 *
 * Returns: the font encoding
 *
 * Since: 0.20
 */
const char *
poppler_fonts_iter_get_encoding (PopplerFontsIter *iter)
{
	GooString *encoding;
	FontInfo *info;

	info = (FontInfo *)iter->items->get (iter->index);

	encoding = info->getEncoding();
	if (encoding != NULL) {
		return encoding->getCString();
	} else {
		return NULL;
	}
}

/**
 * poppler_fonts_iter_is_embedded:
 * @iter: a #PopplerFontsIter
 *
 * Returns whether the font associated with @iter is embedded in the document
 *
 * Returns: %TRUE if font is emebdded, %FALSE otherwise
 */
gboolean
poppler_fonts_iter_is_embedded (PopplerFontsIter *iter)
{
	FontInfo *info;

	info = (FontInfo *)iter->items->get (iter->index);

	return info->getEmbedded();
}

/**
 * poppler_fonts_iter_is_subset:
 * @iter: a #PopplerFontsIter
 *
 * Returns whether the font associated with @iter is a subset of another font
 *
 * Returns: %TRUE if font is a subset, %FALSE otherwise
 */
gboolean
poppler_fonts_iter_is_subset (PopplerFontsIter *iter)
{
	FontInfo *info;

	info = (FontInfo *)iter->items->get (iter->index);

	return info->getSubset();
}

/**
 * poppler_fonts_iter_next:
 * @iter: a #PopplerFontsIter
 *
 * Sets @iter to point to the next font
 *
 * Returns: %TRUE, if @iter was set to the next font
 **/
gboolean
poppler_fonts_iter_next (PopplerFontsIter *iter)
{
	g_return_val_if_fail (iter != NULL, FALSE);

	iter->index++;
	if (iter->index >= iter->items->getLength())
		return FALSE;

	return TRUE;
}

/**
 * poppler_fonts_iter_copy:
 * @iter: a #PopplerFontsIter to copy
 *
 * Creates a copy of @iter
 *
 * Returns: a new allocated copy of @iter
 */
PopplerFontsIter *
poppler_fonts_iter_copy (PopplerFontsIter *iter)
{
	PopplerFontsIter *new_iter;

	g_return_val_if_fail (iter != NULL, NULL);

	new_iter = g_slice_dup (PopplerFontsIter, iter);

	new_iter->items = new GooList ();
	for (int i = 0; i < iter->items->getLength(); i++) {
		FontInfo *info = (FontInfo *)iter->items->get(i);
		new_iter->items->append (new FontInfo (*info));
	}

	return new_iter;
}

/**
 * poppler_fonts_iter_free:
 * @iter: a #PopplerFontsIter
 *
 * Frees the given #PopplerFontsIter
 */
void
poppler_fonts_iter_free (PopplerFontsIter *iter)
{
	if (G_UNLIKELY (iter == NULL))
		return;

	deleteGooList (iter->items, FontInfo);

	g_slice_free (PopplerFontsIter, iter);
}

static PopplerFontsIter *
poppler_fonts_iter_new (GooList *items)
{
	PopplerFontsIter *iter;

	iter = g_slice_new (PopplerFontsIter);
	iter->items = items;
	iter->index = 0;

	return iter;
}


typedef struct _PopplerFontInfoClass PopplerFontInfoClass;
struct _PopplerFontInfoClass
{
        GObjectClass parent_class;
};

G_DEFINE_TYPE (PopplerFontInfo, poppler_font_info, G_TYPE_OBJECT)

static void poppler_font_info_finalize (GObject *object);


static void
poppler_font_info_class_init (PopplerFontInfoClass *klass)
{
        GObjectClass *gobject_class = G_OBJECT_CLASS (klass);

        gobject_class->finalize = poppler_font_info_finalize;
}

static void
poppler_font_info_init (PopplerFontInfo *font_info)
{
        font_info->document = NULL;
        font_info->scanner = NULL;
}

static void
poppler_font_info_finalize (GObject *object)
{
        PopplerFontInfo *font_info = POPPLER_FONT_INFO (object);

        delete font_info->scanner;
        g_object_unref (font_info->document);

        G_OBJECT_CLASS (poppler_font_info_parent_class)->finalize (object);
}

/**
 * poppler_font_info_new:
 * @document: a #PopplerDocument
 *
 * Creates a new #PopplerFontInfo object
 *
 * Returns: a new #PopplerFontInfo instance
 */
PopplerFontInfo *
poppler_font_info_new (PopplerDocument *document)
{
	PopplerFontInfo *font_info;

	g_return_val_if_fail (POPPLER_IS_DOCUMENT (document), NULL);

	font_info = (PopplerFontInfo *) g_object_new (POPPLER_TYPE_FONT_INFO,
						      NULL);
	font_info->document = (PopplerDocument *) g_object_ref (document);
	font_info->scanner = new FontInfoScanner(document->doc);

	return font_info;
}

/**
 * poppler_font_info_scan:
 * @font_info: a #PopplerFontInfo
 * @n_pages: number of pages to scan
 * @iter: (out): return location for a #PopplerFontsIter
 *
 * Scans the document associated with @font_info for fonts. At most
 * @n_pages will be scanned starting from the current iterator. @iter will
 * point to the first font scanned.
 *
 * Here is a simple example of code to scan fonts in a document
 *
 * <informalexample><programlisting>
 * font_info = poppler_font_info_new (document);
 * while (poppler_font_info_scan (font_info, 20, &fonts_iter)) {
 *         if (!fonts_iter)
 *                 continue; /<!-- -->* No fonts found in these 20 pages *<!-- -->/
 *         do {
 *                 /<!-- -->* Do something with font iter *<!-- -->/
 *                 g_print ("Font Name: %s\n", poppler_fonts_iter_get_name (fonts_iter));
 *         } while (poppler_fonts_iter_next (fonts_iter));
 *         poppler_fonts_iter_free (fonts_iter);
 * }
 * </programlisting></informalexample>
 *
 * Returns: %TRUE, if there are more fonts left to scan
 */
gboolean
poppler_font_info_scan (PopplerFontInfo   *font_info,
			int                n_pages,
			PopplerFontsIter **iter)
{
	GooList *items;

	g_return_val_if_fail (iter != NULL, FALSE);

	items = font_info->scanner->scan(n_pages);

	if (items == NULL) {
		*iter = NULL;
	} else if (items->getLength() == 0) {
		*iter = NULL;
		delete items;
	} else {
		*iter = poppler_fonts_iter_new(items);
	}
	
	return (items != NULL);
}

/* For backward compatibility */
void
poppler_font_info_free (PopplerFontInfo *font_info)
{
	g_return_if_fail (font_info != NULL);

	g_object_unref (font_info);
}


/* Optional content (layers) */
static Layer *
layer_new (OptionalContentGroup *oc)
{
  Layer *layer;

  layer = g_slice_new0 (Layer);
  layer->oc = oc;

  return layer;
}

static void
layer_free (Layer *layer)
{
  if (G_UNLIKELY (!layer))
    return;

  if (layer->kids) {
	  g_list_foreach (layer->kids, (GFunc)layer_free, NULL);
	  g_list_free (layer->kids);
  }

  if (layer->label) {
	  g_free (layer->label);
  }

  g_slice_free (Layer, layer);
}

static GList *
get_optional_content_rbgroups (OCGs *ocg)
{
  Array *rb;
  GList *groups = NULL;
  
  rb = ocg->getRBGroupsArray ();
  
  if (rb) {
    int i, j;

    for (i = 0; i < rb->getLength (); ++i) {
      Object obj;
      Array *rb_array;
      GList *group = NULL;

      rb->get (i, &obj);
      if (!obj.isArray ()) {
        obj.free ();
	continue;
      }

      rb_array = obj.getArray ();
      for (j = 0; j < rb_array->getLength (); ++j) {
        Object ref;
	OptionalContentGroup *oc;

	rb_array->getNF (j, &ref);
	if (!ref.isRef ()) {
	  ref.free ();
	  continue;
	}

	oc = ocg->findOcgByRef (ref.getRef ());
	group = g_list_prepend (group, oc);
	ref.free ();
      }
      obj.free ();

      groups = g_list_prepend (groups, group);
    }
  }

  return groups;
}

GList *
_poppler_document_get_layer_rbgroup (PopplerDocument *document,
				    Layer           *layer)
{
  GList *l;

  for (l = document->layers_rbgroups; l && l->data; l = g_list_next (l)) {
    GList *group = (GList *)l->data;

    if (g_list_find (group, layer->oc))
      return group;
  }

  return NULL;
}

static GList *
get_optional_content_items_sorted (OCGs *ocg, Layer *parent, Array *order)
{
  GList *items = NULL;
  Layer *last_item = parent;
  int i;

  for (i = 0; i < order->getLength (); ++i) {
    Object orderItem;
      
    order->get (i, &orderItem);

    if (orderItem.isDict ()) {
      Object ref;
      
      order->getNF (i, &ref);
      if (ref.isRef ()) {
        OptionalContentGroup *oc = ocg->findOcgByRef (ref.getRef ());
	Layer *layer = layer_new (oc);

	items = g_list_prepend (items, layer);
	last_item = layer;
      }
      ref.free ();
    } else if (orderItem.isArray () && orderItem.arrayGetLength () > 0) {
      if (!last_item) {
        last_item = layer_new (NULL);
	items = g_list_prepend (items, last_item);
      }
      last_item->kids = get_optional_content_items_sorted (ocg, last_item, orderItem.getArray ());
      last_item = NULL;
    } else if (orderItem.isString ()) {
      last_item->label = _poppler_goo_string_to_utf8 (orderItem.getString ());
    }
    orderItem.free ();
  }
  
  return g_list_reverse (items);
}
		
static GList *
get_optional_content_items (OCGs *ocg)
{
  Array *order;
  GList *items = NULL;
  
  order = ocg->getOrderArray ();

  if (order) {
    items = get_optional_content_items_sorted (ocg, NULL, order);
  } else {
    GooList *ocgs;
    int i;

    ocgs = ocg->getOCGs ();
    
    for (i = 0; i < ocgs->getLength (); ++i) {
      OptionalContentGroup *oc = (OptionalContentGroup *) ocgs->get (i);
      Layer *layer = layer_new (oc);
      
      items = g_list_prepend (items, layer);
    }
    
    items = g_list_reverse (items);
  }

  return items;
}

GList *
_poppler_document_get_layers (PopplerDocument *document)
{
  if (!document->layers) {
    Catalog *catalog = document->doc->getCatalog ();
    OCGs *ocg = catalog->getOptContentConfig ();

    if (!ocg)
      return NULL;
    
    document->layers = get_optional_content_items (ocg);
    document->layers_rbgroups = get_optional_content_rbgroups (ocg);
  }

  return document->layers;
}

static void
poppler_document_layers_free (PopplerDocument *document)
{
  if (G_UNLIKELY (!document->layers))
    return;

  g_list_foreach (document->layers, (GFunc)layer_free, NULL);
  g_list_free (document->layers);

  g_list_foreach (document->layers_rbgroups, (GFunc)g_list_free, NULL);
  g_list_free (document->layers_rbgroups);

  document->layers = NULL;
  document->layers_rbgroups = NULL;
}

/* PopplerLayersIter */
struct _PopplerLayersIter {
  PopplerDocument *document;
  GList *items;
  int index;
};

POPPLER_DEFINE_BOXED_TYPE (PopplerLayersIter, poppler_layers_iter,
			   poppler_layers_iter_copy,
			   poppler_layers_iter_free)

/**
 * poppler_layers_iter_copy:
 * @iter: a #PopplerLayersIter
 * 
 * Creates a new #PopplerLayersIter as a copy of @iter.  This must be freed with
 * poppler_layers_iter_free().
 * 
 * Return value: a new #PopplerLayersIter
 *
 * Since 0.12
 **/
PopplerLayersIter *
poppler_layers_iter_copy (PopplerLayersIter *iter)
{
  PopplerLayersIter *new_iter;

  g_return_val_if_fail (iter != NULL, NULL);
  
  new_iter = g_slice_dup (PopplerLayersIter, iter);
  new_iter->document = (PopplerDocument *) g_object_ref (new_iter->document);
  
  return new_iter;
}

/**
 * poppler_layers_iter_free:
 * @iter: a #PopplerLayersIter
 * 
 * Frees @iter.
 *
 * Since: 0.12
 **/
void
poppler_layers_iter_free (PopplerLayersIter *iter)
{
  if (G_UNLIKELY (iter == NULL))
    return;

  g_object_unref (iter->document);
  g_slice_free (PopplerLayersIter, iter);
}

/**
 * poppler_layers_iter_new:
 * @document: a #PopplerDocument
 *
 * Since: 0.12
 **/
PopplerLayersIter *
poppler_layers_iter_new (PopplerDocument *document)
{
  PopplerLayersIter *iter;
  GList *items;

  items = _poppler_document_get_layers (document);

  if (!items)
    return NULL;

  iter = g_slice_new0 (PopplerLayersIter);
  iter->document = (PopplerDocument *)g_object_ref (document);
  iter->items = items;

  return iter;
}

/**
 * poppler_layers_iter_get_child:
 * @parent: a #PopplerLayersIter
 * 
 * Returns a newly created child of @parent, or %NULL if the iter has no child.
 * See poppler_layers_iter_new() for more information on this function.
 * 
 * Return value: a new #PopplerLayersIter, or %NULL
 *
 * Since: 0.12
 **/
PopplerLayersIter *
poppler_layers_iter_get_child (PopplerLayersIter *parent)
{
  PopplerLayersIter *child;
  Layer *layer;

  g_return_val_if_fail (parent != NULL, NULL);
	
  layer = (Layer *) g_list_nth_data (parent->items, parent->index);
  if (!layer || !layer->kids)
    return NULL;

  child = g_slice_new0 (PopplerLayersIter);
  child->document = (PopplerDocument *)g_object_ref (parent->document);
  child->items = layer->kids;

  g_assert (child->items);

  return child;
}

/**
 * poppler_layers_iter_get_title:
 * @iter: a #PopplerLayersIter
 * 
 * Returns the title associated with @iter.  It must be freed with
 * g_free().
 * 
 * Return value: a new string containing the @iter's title or %NULL if @iter doesn't have a title.
 * The returned string should be freed with g_free() when no longer needed.
 *
 * Since: 0.12
 **/
gchar *
poppler_layers_iter_get_title (PopplerLayersIter *iter)
{
  Layer *layer;
  
  g_return_val_if_fail (iter != NULL, NULL);
  
  layer = (Layer *)g_list_nth_data (iter->items, iter->index);

  return layer->label ? g_strdup (layer->label) : NULL;
}

/**
 * poppler_layers_iter_get_layer:
 * @iter: a #PopplerLayersIter
 *
 * Returns the #PopplerLayer associated with @iter.
 *
 * Return value: (transfer full): a new #PopplerLayer, or %NULL if
 * there isn't any layer associated with @iter
 *
 * Since: 0.12
 **/
PopplerLayer *
poppler_layers_iter_get_layer (PopplerLayersIter *iter)
{
  Layer *layer;
  PopplerLayer *poppler_layer = NULL;
  
  g_return_val_if_fail (iter != NULL, NULL);
  
  layer = (Layer *)g_list_nth_data (iter->items, iter->index);
  if (layer->oc) {
    GList *rb_group = NULL;

    rb_group = _poppler_document_get_layer_rbgroup (iter->document, layer);
    poppler_layer = _poppler_layer_new (iter->document, layer, rb_group);
  }
  
  return poppler_layer;
}

/**
 * poppler_layers_iter_next:
 * @iter: a #PopplerLayersIter
 * 
 * Sets @iter to point to the next action at the current level, if valid.  See
 * poppler_layers_iter_new() for more information.
 * 
 * Return value: %TRUE, if @iter was set to the next action
 *
 * Since: 0.12
 **/
gboolean
poppler_layers_iter_next (PopplerLayersIter *iter)
{
  g_return_val_if_fail (iter != NULL, FALSE);

  iter->index++;
  if (iter->index >= (gint)g_list_length (iter->items))
    return FALSE;
  
  return TRUE;
}

typedef struct _PopplerPSFileClass PopplerPSFileClass;
struct _PopplerPSFileClass
{
        GObjectClass parent_class;
};

G_DEFINE_TYPE (PopplerPSFile, poppler_ps_file, G_TYPE_OBJECT)

static void poppler_ps_file_finalize (GObject *object);


static void
poppler_ps_file_class_init (PopplerPSFileClass *klass)
{
        GObjectClass *gobject_class = G_OBJECT_CLASS (klass);

        gobject_class->finalize = poppler_ps_file_finalize;
}

static void
poppler_ps_file_init (PopplerPSFile *ps_file)
{
        ps_file->out = NULL;
        ps_file->paper_width = -1;
        ps_file->paper_height = -1;
        ps_file->duplex = FALSE;
}

static void
poppler_ps_file_finalize (GObject *object)
{
        PopplerPSFile *ps_file = POPPLER_PS_FILE (object);

        delete ps_file->out;
        g_object_unref (ps_file->document);
        g_free (ps_file->filename);

        G_OBJECT_CLASS (poppler_ps_file_parent_class)->finalize (object);
}

/**
 * poppler_ps_file_new:
 * @document: a #PopplerDocument
 * @filename: the path of the output filename
 * @first_page: the first page to print
 * @n_pages: the number of pages to print
 * 
 * Create a new postscript file to render to
 * 
 * Return value: a PopplerPSFile 
 **/
PopplerPSFile *
poppler_ps_file_new (PopplerDocument *document, const char *filename,
		     int first_page, int n_pages)
{
	PopplerPSFile *ps_file;

	g_return_val_if_fail (POPPLER_IS_DOCUMENT (document), NULL);
	g_return_val_if_fail (filename != NULL, NULL);
	g_return_val_if_fail (n_pages > 0, NULL);

        ps_file = (PopplerPSFile *) g_object_new (POPPLER_TYPE_PS_FILE, NULL);
	ps_file->document = (PopplerDocument *) g_object_ref (document);
        ps_file->filename = g_strdup (filename);
        ps_file->first_page = first_page + 1;
        ps_file->last_page = first_page + 1 + n_pages - 1;

	return ps_file;
}

/**
 * poppler_ps_file_set_paper_size:
 * @ps_file: a PopplerPSFile which was not yet printed to.
 * @width: the paper width in 1/72 inch
 * @height: the paper height in 1/72 inch
 *
 * Set the output paper size. These values will end up in the
 * DocumentMedia, the BoundingBox DSC comments and other places in the
 * generated PostScript.
 *
 **/
void
poppler_ps_file_set_paper_size (PopplerPSFile *ps_file,
                                double width, double height)
{
        g_return_if_fail (ps_file->out == NULL);
        
        ps_file->paper_width = width;
        ps_file->paper_height = height;
}

/**
 * poppler_ps_file_set_duplex:
 * @ps_file: a PopplerPSFile which was not yet printed to
 * @duplex: whether to force duplex printing (on printers which support this)
 *
 * Enable or disable Duplex printing. 
 *
 **/
void
poppler_ps_file_set_duplex (PopplerPSFile *ps_file, gboolean duplex)
{
        g_return_if_fail (ps_file->out == NULL);

        ps_file->duplex = duplex;
}

/**
 * poppler_ps_file_free:
 * @ps_file: a PopplerPSFile
 * 
 * Frees @ps_file
 * 
 **/
void
poppler_ps_file_free (PopplerPSFile *ps_file)
{
	g_return_if_fail (ps_file != NULL);
        g_object_unref (ps_file);
}

/**
 * poppler_document_get_form_field:
 * @document: a #PopplerDocument
 * @id: an id of a #PopplerFormField
 *
 * Returns the #PopplerFormField for the given @id. It must be freed with
 * g_object_unref()
 *
 * Return value: (transfer full): a new #PopplerFormField or %NULL if
 * not found
 **/
PopplerFormField *
poppler_document_get_form_field (PopplerDocument *document,
				 gint             id)
{
  Page *page;
  unsigned pageNum;
  unsigned fieldNum;
  FormPageWidgets *widgets;
  FormWidget *field;

  FormWidget::decodeID (id, &pageNum, &fieldNum);

  page = document->doc->getPage (pageNum);
  if (!page)
    return NULL;

  widgets = page->getFormWidgets ();
  if (!widgets)
    return NULL;

  field = widgets->getWidget (fieldNum);
  if (field)
    return _poppler_form_field_new (document, field);

  return NULL;
}

gboolean
_poppler_convert_pdf_date_to_gtime (GooString *date,
				    time_t    *gdate) 
{
  gchar *date_string;
  gboolean retval;

  if (date->hasUnicodeMarker()) {
    date_string = g_convert (date->getCString () + 2,
			     date->getLength () - 2,
			     "UTF-8", "UTF-16BE", NULL, NULL, NULL);		
  } else {
    date_string = g_strndup (date->getCString (), date->getLength ());
  }

  retval = poppler_date_parse (date_string, gdate);
  g_free (date_string);

  return retval;
}
