/* poppler-document.cc: glib wrapper for poppler
 * Copyright (C) 2005, Red Hat, Inc.
 *
 * Copyright (C) 2016 Jakub Alba <jakubalba@gmail.com>
 * Copyright (C) 2018-2019 Marek Kasik <mkasik@redhat.com>
 * Copyright (C) 2019 Masamichi Hosoda <trueroad@trueroad.jp>
 * Copyright (C) 2019, Oliver Sander <oliver.sander@tu-dresden.de>
 *
 * 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 <cstring>

#ifndef __GI_SCANNER__
#include <memory>

#include <splash/SplashBitmap.h>
#include <DateInfo.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>
#include <ViewerPreferences.h>
#endif

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

#ifdef G_OS_WIN32
  #include <stringapiset.h>
#endif

/**
 * 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_SUBTYPE,
	PROP_SUBTYPE_STRING,
	PROP_SUBTYPE_PART,
	PROP_SUBTYPE_CONF,
	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,
	PROP_PRINT_SCALING,
	PROP_PRINT_DUPLEX,
	PROP_PRINT_N_COPIES
};

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 (std::unique_ptr<GlobalParamsIniter> &&initer,
                                   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 nullptr;
  }

  document = (PopplerDocument *) g_object_new (POPPLER_TYPE_DOCUMENT, nullptr);
  document->initer = std::move(initer);
  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 nullptr;

  password_latin = g_convert(password, -1, "ISO-8859-1", "UTF-8",
                             nullptr, nullptr, nullptr);
  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;

  auto initer = std::make_unique<GlobalParamsIniter>(_poppler_error_cb);

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

  password_g = poppler_password_to_latin1(password);

#ifdef G_OS_WIN32
  wchar_t *filenameW;
  int length;

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

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

  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 (std::move(initer), newDoc, error);
}

/**
 * poppler_document_new_from_data:
 * @data: (array length=length) (element-type guint8): the pdf data
 * @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.
 *
 * Note that @data must remain valid for as long as the returned document exists.
 * Prefer using poppler_document_new_from_bytes().
 * 
 * Return value: A newly created #PopplerDocument, or %NULL
 **/
PopplerDocument *
poppler_document_new_from_data (char        *data,
                                int          length,
                                const char  *password,
                                GError     **error)
{
  PDFDoc *newDoc;
  MemStream *str;
  GooString *password_g;

  auto initer = std::make_unique<GlobalParamsIniter>(_poppler_error_cb);
  
  // create stream
  str = new MemStream(data, 0, length, Object(objNull));

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

  return _poppler_document_new_from_pdfdoc (std::move(initer), newDoc, error);
}

class BytesStream : public MemStream
{
  std::unique_ptr<GBytes, decltype(&g_bytes_unref)> m_bytes;

public:
  BytesStream(GBytes *bytes, Object &&dictA)
    : MemStream(static_cast<const char*>(g_bytes_get_data(bytes, nullptr)),
		0, g_bytes_get_size(bytes), std::move(dictA)),
      m_bytes{g_bytes_ref(bytes), &g_bytes_unref}
  { }
};

/**
 * poppler_document_new_from_bytes:
 * @bytes: a #GBytes
 * @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 from @bytes. The returned document
 * will hold a reference to @bytes.
 *
 * On error,  %NULL is returned, with @error set. Possible errors include
 * those in the #POPPLER_ERROR and #G_FILE_ERROR domains.
 *
 * Return value: (transfer full): a newly created #PopplerDocument, or %NULL
 *
 * Since: 0.82
 **/
PopplerDocument *
poppler_document_new_from_bytes (GBytes      *bytes,
				 const char  *password,
				 GError     **error)
{
  PDFDoc *newDoc;
  BytesStream *str;
  GooString *password_g;

  g_return_val_if_fail(bytes != nullptr, nullptr);
  g_return_val_if_fail(error == nullptr || *error == nullptr, nullptr);

  auto initer = std::make_unique<GlobalParamsIniter>(_poppler_error_cb);

  // create stream
  str = new BytesStream(bytes, Object(objNull));

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

  return _poppler_document_new_from_pdfdoc (std::move(initer), 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, #G_FILE_ERROR
 * and #G_IO_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)
{
  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);

  auto initer = std::make_unique<GlobalParamsIniter>(_poppler_error_cb);

  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 nullptr;
  }

  if (stream_is_memory_buffer_or_local_file(stream)) {
    if (length == (goffset)-1) {
      if (!g_seekable_seek(G_SEEKABLE(stream), 0, G_SEEK_END, cancellable, error)) {
        g_prefix_error(error, "Unable to determine length of stream: ");
        return nullptr;
      }
      length = g_seekable_tell(G_SEEKABLE(stream));
    }
    str = new PopplerInputStream(stream, cancellable, 0, false, length, Object(objNull));
  } else {
    CachedFile *cachedFile = new CachedFile(new PopplerCachedFileLoader(stream, cancellable, length), new GooString());
    str = new CachedFileStream(cachedFile, 0, false, cachedFile->getLength(), Object(objNull));
  }

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

  return _poppler_document_new_from_pdfdoc (std::move(initer), 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 nullptr;

  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, nullptr, error);
  if (filename != nullptr) {
    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, nullptr, error);
  if (filename != nullptr) {
    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;
  delete document->initer.release();

  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 = nullptr;
  if (update_id)
    *update_id = nullptr;

  if (document->doc->getID (permanent_id ? &permanent : nullptr, update_id ? &update : nullptr)) {
    if (permanent_id)
      *permanent_id = (gchar *)g_memdup (permanent.c_str(), 32);
    if (update_id)
      *update_id = (gchar *)g_memdup (update.c_str(), 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 nullptr;

  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 nullptr;

  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 = nullptr;

  g_return_val_if_fail (POPPLER_IS_DOCUMENT (document), NULL);

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

  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;

      if (attachment != nullptr)
        retval = g_list_prepend (retval, attachment);
    }
  return g_list_reverse (retval);
}

/**
 * poppler_named_dest_from_bytestring:
 * @data: (array length=length): the bytestring data
 * @length: the bytestring length
 *
 * Converts a bytestring into a zero-terminated string suitable to
 * pass to poppler_document_find_dest().
 *
 * Note that the returned string has no defined encoding and is not
 * suitable for display to the user.
 *
 * The returned data must be freed using g_free().
 *
 * Returns: (transfer full): the named dest
 *
 * Since: 0.73
 */
char *
poppler_named_dest_from_bytestring (const guint8 *data,
				    gsize         length)
{
  const guint8 *p, *pend;
  char *dest, *q;

  g_return_val_if_fail (length != 0 || data != nullptr, nullptr);
  /* Each source byte needs maximally 2 destination chars (\\ or \0) */
  q = dest = (gchar *)g_malloc (length * 2 + 1);

  pend = data + length;
  for (p = data; p < pend; ++p) {
    switch (*p) {
    case '\0':
      *q++ = '\\';
      *q++ = '0';
      break;
    case '\\':
      *q++ = '\\';
      *q++ = '\\';
      break;
    default:
      *q++ = *p;
      break;
    }
  }

  *q = 0; /* zero terminate */
  return dest;
}

/**
 * poppler_named_dest_to_bytestring:
 * @name: the named dest string
 * @length: (out): a location to store the length of the returned bytestring
 *
 * Converts a named dest string (e.g. from #PopplerDest.named_dest) into a
 * bytestring, inverting the transformation of
 * poppler_named_dest_from_bytestring().
 *
 * Note that the returned data is not zero terminated and may also
 * contains embedded NUL bytes.
 *
 * If @name is not a valid named dest string, returns %NULL.
 *
 * The returned data must be freed using g_free().
 *
 * Returns: (array length=length) (transfer full) (nullable): a new bytestring,
 *   or %NULL
 *
 * Since: 0.73
 */
guint8 *
poppler_named_dest_to_bytestring (const char *name,
				  gsize      *length)
{
  const char *p;
  guint8 *data, *q;
  gsize len;

  g_return_val_if_fail (name != nullptr, nullptr);
  g_return_val_if_fail (length != nullptr, nullptr);

  len = strlen (name);
  q = data = (guint8*) g_malloc (len);
  for (p = name; *p; ++p) {
    if (*p == '\\') {
      p++;
      len--;
      if (*p == '0')
	*q++ = '\0';
      else if (*p == '\\')
	*q++ = '\\';
      else
	goto invalid;
    } else {
      *q++ = *p;
    }
  }

  *length = len;
  return data;

invalid:
  g_free(data);
  *length = 0;
  return nullptr;
}

/**
 * poppler_document_find_dest:
 * @document: A #PopplerDocument
 * @link_name: a named destination
 *
 * Creates a #PopplerDest for the named destination @link_name in @document.
 *
 * Note that named destinations are bytestrings, not string. That means that
 * unless @link_name was returned by a poppler function (e.g. is
 * #PopplerDest.named_dest), it needs to be converted to string
 * using poppler_named_dest_from_bytestring() before being passed to this
 * function.
 *
 * The returned value must be freed with poppler_dest_free().
 *
 * Return value: (transfer full): a new #PopplerDest destination, or %NULL if
 *   @link_name is not a destination.
 **/
PopplerDest *
poppler_document_find_dest (PopplerDocument *document,
			    const gchar     *link_name)
{
  g_return_val_if_fail (POPPLER_IS_DOCUMENT (document), nullptr);
  g_return_val_if_fail (link_name != nullptr, nullptr);

  gsize len;
  guint8* data = poppler_named_dest_to_bytestring (link_name, &len);
  if (data == nullptr)
    return nullptr;

  GooString g_link_name ((const char*)data, (int)len);
  g_free (data);

  std::unique_ptr<LinkDest> link_dest = document->doc->findDest (&g_link_name);
  if (link_dest == nullptr)
    return nullptr;

  PopplerDest *dest = _poppler_dest_new_goto (document, link_dest.get());

  return dest;
}

static gint
_poppler_dest_compare_keys (gconstpointer a,
			    gconstpointer b,
			    gpointer user_data)
{
	return g_strcmp0 (static_cast<const gchar*>(a),
			  static_cast<const gchar*>(b));
}

static void
_poppler_dest_destroy_value (gpointer value)
{
	poppler_dest_free (static_cast<PopplerDest*>(value));
}

/**
 * poppler_document_create_dests_tree:
 * @document: A #PopplerDocument
 *
 * Creates named destinations balanced binary tree in @document
 *
 * The tree key is strings in the form returned by
 * poppler_named_dest_bytestring() which constains a destination name.
 * The tree value is the #PopplerDest* which contains a named destination.
 * The return value must be freed with #g_tree_destroy.
 *
 * Returns: (transfer full) (nullable): the #GTree, or %NULL
 * Since: 0.78
 **/
GTree *
poppler_document_create_dests_tree (PopplerDocument *document)
{
	GTree *tree;
	Catalog *catalog;
	PopplerDest *dest;
	int i;
	gchar *key;

	g_return_val_if_fail (POPPLER_IS_DOCUMENT (document), nullptr);

	catalog = document->doc->getCatalog ();
	if (catalog == nullptr)
		return nullptr;

	tree = g_tree_new_full (_poppler_dest_compare_keys, nullptr,
				g_free,
				_poppler_dest_destroy_value);

	// Iterate from name-dict
	const int nDests = catalog->numDests ();
	for (i = 0; i < nDests; ++i) {
		// The names of name-dict cannot contain \0,
		// so we can use strlen().
		auto name = catalog->getDestsName (i);
		key = poppler_named_dest_from_bytestring
			(reinterpret_cast<const guint8*> (name),
			 strlen (name));
		std::unique_ptr<LinkDest> link_dest = catalog->getDestsDest (i);
		if (link_dest) {
			dest = _poppler_dest_new_goto (document, link_dest.get());
			g_tree_insert (tree, key, dest);
		}
	}

	// Iterate form name-tree
	const int nDestsNameTree = catalog->numDestNameTree ();
	for (i = 0; i < nDestsNameTree; ++i) {
		auto name = catalog->getDestNameTreeName (i);
		key = poppler_named_dest_from_bytestring
			(reinterpret_cast<const guint8*> (name->c_str ()),
			 name->getLength ());
		std::unique_ptr<LinkDest> link_dest = catalog->getDestNameTreeDest (i);
		if (link_dest) {
			dest = _poppler_dest_new_goto (document, link_dest.get());
			g_tree_insert (tree, key, dest);
		}
	}

	return tree;
}

char *_poppler_goo_string_to_utf8(const GooString *s)
{
  if (s == nullptr) {
    return nullptr;
  }

  char *result;

  if (s->hasUnicodeMarker()) {
    result = g_convert (s->c_str () + 2,
			s->getLength () - 2,
			"UTF-8", "UTF-16BE", nullptr, nullptr, nullptr);
  } else if (s->hasUnicodeMarkerLE()) {
    result = g_convert (s->c_str () + 2,
			s->getLength () - 2,
			"UTF-8", "UTF-16LE", nullptr, nullptr, nullptr);
  } 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, nullptr, nullptr, nullptr);

    g_free (ucs4_temp);
  }

  return result;
}

static GooString *
_poppler_goo_string_from_utf8(const gchar *src)
{
  if (src == nullptr) {
    return nullptr;
  }

  gsize outlen;

  gchar *utf16 = g_convert (src, -1, "UTF-16BE", "UTF-8", nullptr, &outlen, nullptr);
  if (utf16 == nullptr) {
    return nullptr;
  }

  GooString *result = new GooString (utf16, outlen);
  g_free (utf16);

  if (!result->hasUnicodeMarker()) {
    result->prependUnicodeMarker();
  }

  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;
    }
}

static PopplerPDFSubtype
convert_pdf_subtype (PDFSubtype pdfSubtype)
{
  switch (pdfSubtype)
    {
    case subtypePDFA:
      return POPPLER_PDF_SUBTYPE_PDF_A;
    case subtypePDFE:
      return POPPLER_PDF_SUBTYPE_PDF_E;
    case subtypePDFUA:
      return POPPLER_PDF_SUBTYPE_PDF_UA;
    case subtypePDFVT:
      return POPPLER_PDF_SUBTYPE_PDF_VT;
    case subtypePDFX:
      return POPPLER_PDF_SUBTYPE_PDF_X;
    case subtypeNone:
      return POPPLER_PDF_SUBTYPE_NONE;
    case subtypeNull:
    default:
      return POPPLER_PDF_SUBTYPE_UNSET;
    }
}

static PopplerPDFPart
convert_pdf_subtype_part (PDFSubtypePart pdfSubtypePart)
{
  switch (pdfSubtypePart)
    {
    case subtypePart1:
      return POPPLER_PDF_SUBTYPE_PART_1;
    case subtypePart2:
      return POPPLER_PDF_SUBTYPE_PART_2;
    case subtypePart3:
      return POPPLER_PDF_SUBTYPE_PART_3;
    case subtypePart4:
      return POPPLER_PDF_SUBTYPE_PART_4;
    case subtypePart5:
      return POPPLER_PDF_SUBTYPE_PART_5;
    case subtypePart6:
      return POPPLER_PDF_SUBTYPE_PART_6;
    case subtypePart7:
      return POPPLER_PDF_SUBTYPE_PART_7;
    case subtypePart8:
      return POPPLER_PDF_SUBTYPE_PART_8;
    case subtypePartNone:
      return POPPLER_PDF_SUBTYPE_PART_NONE;
    case subtypePartNull:
    default:
      return POPPLER_PDF_SUBTYPE_PART_UNSET;
    }
}

static PopplerPDFConformance
convert_pdf_subtype_conformance (PDFSubtypeConformance pdfSubtypeConf)
{
  switch (pdfSubtypeConf)
    {
    case subtypeConfA:
      return POPPLER_PDF_SUBTYPE_CONF_A;
    case subtypeConfB:
      return POPPLER_PDF_SUBTYPE_CONF_B;
    case subtypeConfG:
      return POPPLER_PDF_SUBTYPE_CONF_G;
    case subtypeConfN:
      return POPPLER_PDF_SUBTYPE_CONF_N;
    case subtypeConfP:
      return POPPLER_PDF_SUBTYPE_CONF_P;
    case subtypeConfPG:
      return POPPLER_PDF_SUBTYPE_CONF_PG;
    case subtypeConfU:
      return POPPLER_PDF_SUBTYPE_CONF_U;
    case subtypeConfNone:
      return POPPLER_PDF_SUBTYPE_CONF_NONE;
    case subtypeConfNull:
    default:
      return POPPLER_PDF_SUBTYPE_CONF_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)
{
  g_return_val_if_fail (POPPLER_IS_DOCUMENT (document), NULL);

  GooString *goo_title = document->doc->getDocInfoTitle();
  gchar *utf8 = _poppler_goo_string_to_utf8(goo_title);
  delete goo_title;

  return utf8;
}

/**
 * poppler_document_set_title:
 * @document: A #PopplerDocument
 * @title: A new title
 *
 * Sets the document's title. If @title is %NULL, Title entry
 * is removed from the document's Info dictionary.
 *
 * Since: 0.46
 **/
void
poppler_document_set_title (PopplerDocument *document, const gchar *title)
{
  g_return_if_fail (POPPLER_IS_DOCUMENT (document));

  GooString *goo_title;
  if (!title) {
    goo_title = nullptr;
  } else {
    goo_title = _poppler_goo_string_from_utf8(title);
    if (!goo_title)
      return;
  }
  document->doc->setDocInfoTitle(goo_title);
}

/**
 * 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)
{
  g_return_val_if_fail (POPPLER_IS_DOCUMENT (document), NULL);

  GooString *goo_author = document->doc->getDocInfoAuthor();
  gchar *utf8 = _poppler_goo_string_to_utf8(goo_author);
  delete goo_author;

  return utf8;
}

/**
 * poppler_document_set_author:
 * @document: A #PopplerDocument
 * @author: A new author
 *
 * Sets the document's author. If @author is %NULL, Author
 * entry is removed from the document's Info dictionary.
 *
 * Since: 0.46
 **/
void
poppler_document_set_author (PopplerDocument *document, const gchar *author)
{
  g_return_if_fail (POPPLER_IS_DOCUMENT (document));

  GooString *goo_author;
  if (!author) {
    goo_author = nullptr;
  } else {
    goo_author = _poppler_goo_string_from_utf8(author);
    if (!goo_author)
      return;
  }
  document->doc->setDocInfoAuthor(goo_author);
}

/**
 * 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)
{
  g_return_val_if_fail (POPPLER_IS_DOCUMENT (document), NULL);

  GooString *goo_subject = document->doc->getDocInfoSubject();
  gchar *utf8 = _poppler_goo_string_to_utf8(goo_subject);
  delete goo_subject;

  return utf8;
}

/**
 * poppler_document_set_subject:
 * @document: A #PopplerDocument
 * @subject: A new subject
 *
 * Sets the document's subject. If @subject is %NULL, Subject
 * entry is removed from the document's Info dictionary.
 *
 * Since: 0.46
 **/
void
poppler_document_set_subject (PopplerDocument *document, const gchar *subject)
{
  g_return_if_fail (POPPLER_IS_DOCUMENT (document));

  GooString *goo_subject;
  if (!subject) {
    goo_subject = nullptr;
  } else {
    goo_subject = _poppler_goo_string_from_utf8(subject);
    if (!goo_subject)
      return;
  }
  document->doc->setDocInfoSubject(goo_subject);
}

/**
 * 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)
{
  g_return_val_if_fail (POPPLER_IS_DOCUMENT (document), NULL);

  GooString *goo_keywords = document->doc->getDocInfoKeywords();
  gchar *utf8 = _poppler_goo_string_to_utf8(goo_keywords);
  delete goo_keywords;

  return utf8;
}

/**
 * poppler_document_set_keywords:
 * @document: A #PopplerDocument
 * @keywords: New keywords
 *
 * Sets the document's keywords. If @keywords is %NULL,
 * Keywords entry is removed from the document's Info dictionary.
 *
 * Since: 0.46
 **/
void
poppler_document_set_keywords (PopplerDocument *document, const gchar *keywords)
{
  g_return_if_fail (POPPLER_IS_DOCUMENT (document));

  GooString *goo_keywords;
  if (!keywords) {
    goo_keywords = nullptr;
  } else {
    goo_keywords = _poppler_goo_string_from_utf8(keywords);
    if (!goo_keywords)
      return;
  }
  document->doc->setDocInfoKeywords(goo_keywords);
}

/**
 * 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)
{
  g_return_val_if_fail (POPPLER_IS_DOCUMENT (document), NULL);

  GooString *goo_creator = document->doc->getDocInfoCreator();
  gchar *utf8 = _poppler_goo_string_to_utf8(goo_creator);
  delete goo_creator;

  return utf8;
}

/**
 * poppler_document_set_creator:
 * @document: A #PopplerDocument
 * @creator: A new creator
 *
 * Sets the document's creator. If @creator is %NULL, Creator
 * entry is removed from the document's Info dictionary.
 *
 * Since: 0.46
 **/
void
poppler_document_set_creator (PopplerDocument *document, const gchar *creator)
{
  g_return_if_fail (POPPLER_IS_DOCUMENT (document));

  GooString *goo_creator;
  if (!creator) {
    goo_creator = nullptr;
  } else {
    goo_creator = _poppler_goo_string_from_utf8(creator);
    if (!goo_creator)
      return;
  }
  document->doc->setDocInfoCreator(goo_creator);
}

/**
 * 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)
{
  g_return_val_if_fail (POPPLER_IS_DOCUMENT (document), NULL);

  GooString *goo_producer = document->doc->getDocInfoProducer();
  gchar *utf8 = _poppler_goo_string_to_utf8(goo_producer);
  delete goo_producer;

  return utf8;
}

/**
 * poppler_document_set_producer:
 * @document: A #PopplerDocument
 * @producer: A new producer
 *
 * Sets the document's producer. If @producer is %NULL,
 * Producer entry is removed from the document's Info dictionary.
 *
 * Since: 0.46
 **/
void
poppler_document_set_producer (PopplerDocument *document, const gchar *producer)
{
  g_return_if_fail (POPPLER_IS_DOCUMENT (document));

  GooString *goo_producer;
  if (!producer) {
    goo_producer = nullptr;
  } else {
    goo_producer = _poppler_goo_string_from_utf8(producer);
    if (!goo_producer)
      return;
  }
  document->doc->setDocInfoProducer(goo_producer);
}

/**
 * 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)
{
  g_return_val_if_fail (POPPLER_IS_DOCUMENT (document), (time_t)-1);

  GooString *str = document->doc->getDocInfoCreatDate();
  if (str == nullptr) {
    return (time_t)-1;
  }

  time_t date;
  gboolean success = _poppler_convert_pdf_date_to_gtime (str, &date);
  delete str;

  return (success) ? date : (time_t)-1;
}

/**
 * poppler_document_set_creation_date:
 * @document: A #PopplerDocument
 * @creation_date: A new creation date
 *
 * Sets the document's creation date. If @creation_date is -1, CreationDate
 * entry is removed from the document's Info dictionary.
 *
 * Since: 0.46
 **/
void
poppler_document_set_creation_date (PopplerDocument *document,
                                    time_t creation_date)
{
  g_return_if_fail (POPPLER_IS_DOCUMENT (document));

  GooString *str = creation_date == (time_t)-1 ? nullptr : timeToDateString (&creation_date);
  document->doc->setDocInfoCreatDate (str);
}

/**
 * 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)
{
  g_return_val_if_fail (POPPLER_IS_DOCUMENT (document), (time_t)-1);

  GooString *str = document->doc->getDocInfoModDate();
  if (str == nullptr) {
    return (time_t)-1;
  }

  time_t date;
  gboolean success = _poppler_convert_pdf_date_to_gtime (str, &date);
  delete str;

  return (success) ? date : (time_t)-1;
}

/**
 * poppler_document_set_modification_date:
 * @document: A #PopplerDocument
 * @modification_date: A new modification date
 *
 * Sets the document's modification date. If @modification_date is -1, ModDate
 * entry is removed from the document's Info dictionary.
 *
 * Since: 0.46
 **/
void
poppler_document_set_modification_date (PopplerDocument *document,
                                        time_t modification_date)
{
  g_return_if_fail (POPPLER_IS_DOCUMENT (document));

  GooString *str = modification_date == (time_t)-1 ? nullptr : timeToDateString (&modification_date);
  document->doc->setDocInfoModDate (str);
}

/**
 * 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 otherwise
 *
 * 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_print_scaling:
 * @document: A #PopplerDocument
 *
 * Returns the print scaling value suggested by author of the document.
 *
 * Return value: a #PopplerPrintScaling that should be used when document is printed
 *
 * Since: 0.73
 **/
PopplerPrintScaling
poppler_document_get_print_scaling (PopplerDocument *document)
{
  Catalog *catalog;
  ViewerPreferences *preferences;
  PopplerPrintScaling print_scaling = POPPLER_PRINT_SCALING_APP_DEFAULT;

  g_return_val_if_fail (POPPLER_IS_DOCUMENT (document), POPPLER_PRINT_SCALING_APP_DEFAULT);

  catalog = document->doc->getCatalog ();
  if (catalog && catalog->isOk ()) {
    preferences = catalog->getViewerPreferences();
    if (preferences) {
      switch (preferences->getPrintScaling()) {
        default:
        case ViewerPreferences::PrintScaling::printScalingAppDefault:
          print_scaling = POPPLER_PRINT_SCALING_APP_DEFAULT;
          break;
        case ViewerPreferences::PrintScaling::printScalingNone:
          print_scaling = POPPLER_PRINT_SCALING_NONE;
          break;
      }
    }
  }

  return print_scaling;
}

/**
 * poppler_document_get_print_duplex:
 * @document: A #PopplerDocument
 *
 * Returns the duplex mode value suggested for printing by author of the document.
 * Value POPPLER_PRINT_DUPLEX_NONE means that the document does not specify this
 * preference.
 *
 * Returns: a #PopplerPrintDuplex that should be used when document is printed
 *
 * Since: 0.80
 **/
PopplerPrintDuplex
poppler_document_get_print_duplex (PopplerDocument *document)
{
  Catalog *catalog;
  ViewerPreferences *preferences;
  PopplerPrintDuplex duplex = POPPLER_PRINT_DUPLEX_NONE;

  g_return_val_if_fail (POPPLER_IS_DOCUMENT (document), POPPLER_PRINT_DUPLEX_NONE);

  catalog = document->doc->getCatalog ();
  if (catalog && catalog->isOk ()) {
    preferences = catalog->getViewerPreferences();
    if (preferences) {
      switch (preferences->getDuplex()) {
        default:
        case ViewerPreferences::Duplex::duplexNone:
          duplex = POPPLER_PRINT_DUPLEX_NONE;
          break;
        case ViewerPreferences::Duplex::duplexSimplex:
          duplex = POPPLER_PRINT_DUPLEX_SIMPLEX;
          break;
        case ViewerPreferences::Duplex::duplexDuplexFlipShortEdge:
          duplex = POPPLER_PRINT_DUPLEX_DUPLEX_FLIP_SHORT_EDGE;
          break;
        case ViewerPreferences::Duplex::duplexDuplexFlipLongEdge:
          duplex = POPPLER_PRINT_DUPLEX_DUPLEX_FLIP_LONG_EDGE;
          break;
      }
    }
  }

  return duplex;
}

/**
 * poppler_document_get_print_n_copies:
 * @document: A #PopplerDocument
 *
 * Returns the suggested number of copies to be printed.
 * This preference should be applied only if returned value
 * is greater than 1 since value 1 usually means that
 * the document does not specify it.
 *
 * Returns: Number of copies
 *
 * Since: 0.80
 **/
gint
poppler_document_get_print_n_copies (PopplerDocument *document)
{
  Catalog *catalog;
  ViewerPreferences *preferences;
  gint retval = 1;

  g_return_val_if_fail (POPPLER_IS_DOCUMENT (document), 1);

  catalog = document->doc->getCatalog ();
  if (catalog && catalog->isOk ()) {
    preferences = catalog->getViewerPreferences();
    if (preferences) {
      retval = preferences->getNumCopies();
    }
  }

  return retval;
}

/**
 * poppler_document_get_print_page_ranges:
 * @document: A #PopplerDocument
 * @n_ranges: (out): return location for number of ranges
 *
 * Returns the suggested page ranges to print in the form of array
 * of #PopplerPageRanges and number of ranges.
 * NULL pointer means that the document does not specify page ranges
 * for printing.
 *
 * Returns: (array length=n_ranges) (transfer full): an array
 *          of #PopplerPageRanges or NULL. Free the array when
 *          it is no longer needed.
 *
 * Since: 0.80
 **/
PopplerPageRange *
poppler_document_get_print_page_ranges (PopplerDocument *document,
                                        int             *n_ranges)
{
  Catalog *catalog;
  ViewerPreferences *preferences;
  std::vector<std::pair<int, int>> ranges;
  PopplerPageRange *result = nullptr;

  g_return_val_if_fail (n_ranges != nullptr, nullptr);
  *n_ranges = 0;
  g_return_val_if_fail (POPPLER_IS_DOCUMENT (document), nullptr);

  catalog = document->doc->getCatalog ();
  if (catalog && catalog->isOk ()) {
    preferences = catalog->getViewerPreferences ();
    if (preferences) {
      ranges = preferences->getPrintPageRange ();

      *n_ranges = ranges.size ();
      result = g_new (PopplerPageRange, ranges.size ());
      for (size_t i = 0; i < ranges.size (); ++i) {
        result[i].start_page = ranges[i].first;
        result[i].end_page = ranges[i].second;
      }
    }
  }

  return result;
}

/**
 * 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 flags 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_pdf_subtype_string:
 * @document: A #PopplerDocument
 *
 * Returns the PDF subtype version of @document as a string.
 *
 * Returns: (transfer full) (nullable): a newly allocated string containing
 * the PDF subtype version of @document, or %NULL
 *
 * Since: 0.70
 **/
gchar *
poppler_document_get_pdf_subtype_string (PopplerDocument *document)
{
  g_return_val_if_fail (POPPLER_IS_DOCUMENT (document), NULL);

  GooString *infostring;

  switch (document->doc->getPDFSubtype ())
    {
    case subtypePDFA:
      infostring = document->doc->getDocInfoStringEntry ("GTS_PDFA1Version");
      break;
    case subtypePDFE:
      infostring = document->doc->getDocInfoStringEntry ("GTS_PDFEVersion");
      break;
    case subtypePDFUA:
      infostring = document->doc->getDocInfoStringEntry ("GTS_PDFUAVersion");
      break;
    case subtypePDFVT:
      infostring = document->doc->getDocInfoStringEntry ("GTS_PDFVTVersion");
      break;
    case subtypePDFX:
      infostring = document->doc->getDocInfoStringEntry ("GTS_PDFXVersion");
      break;
    case subtypeNone:
    case subtypeNull:
    default:
      infostring = nullptr;
    }

  gchar *utf8 = _poppler_goo_string_to_utf8 (infostring);
  delete infostring;

  return utf8;
}

/**
 * poppler_document_get_pdf_subtype:
 * @document: A #PopplerDocument
 *
 * Returns the subtype of @document as a #PopplerPDFSubtype.
 *
 * Returns: the document's subtype
 *
 * Since: 0.70
 **/
PopplerPDFSubtype
poppler_document_get_pdf_subtype (PopplerDocument *document)
{
  g_return_val_if_fail (POPPLER_IS_DOCUMENT (document), POPPLER_PDF_SUBTYPE_NONE);

  return convert_pdf_subtype (document->doc->getPDFSubtype ());
}

/**
 * poppler_document_get_pdf_part:
 * @document: A #PopplerDocument
 *
 * Returns the part of the conforming standard that the @document adheres to
 * as a #PopplerPDFSubtype.
 *
 * Returns: the document's subtype part
 *
 * Since: 0.70
 **/
PopplerPDFPart
poppler_document_get_pdf_part (PopplerDocument *document)
{
  g_return_val_if_fail (POPPLER_IS_DOCUMENT (document), POPPLER_PDF_SUBTYPE_PART_NONE);

  return convert_pdf_subtype_part (document->doc->getPDFSubtypePart ());
}

/**
 * poppler_document_get_pdf_conformance:
 * @document: A #PopplerDocument
 *
 * Returns the conformance level of the @document as #PopplerPDFConformance.
 *
 * Returns: the document's subtype conformance level
 *
 * Since: 0.70
 **/
PopplerPDFConformance
poppler_document_get_pdf_conformance (PopplerDocument *document)
{
  g_return_val_if_fail (POPPLER_IS_DOCUMENT (document), POPPLER_PDF_SUBTYPE_CONF_NONE);

  return convert_pdf_subtype_conformance (document->doc->getPDFSubtypeConformance ());
}

/**
 * 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 = nullptr;

  g_return_val_if_fail (POPPLER_IS_DOCUMENT (document), NULL);

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

    if (s != nullptr) {
      retval = g_strdup (s->c_str());
      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, nullptr);
      g_value_set_uint (value, version);
      break;
    case PROP_FORMAT_MINOR:
      poppler_document_get_pdf_version (document, nullptr, &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_PRINT_SCALING:
      g_value_set_enum (value, poppler_document_get_print_scaling (document));
      break;
    case PROP_PRINT_DUPLEX:
      g_value_set_enum (value, poppler_document_get_print_duplex (document));
      break;
    case PROP_PRINT_N_COPIES:
      g_value_set_int (value, poppler_document_get_print_n_copies (document));
      break;
    case PROP_PERMISSIONS:
      g_value_set_flags (value, poppler_document_get_permissions (document));
      break;
    case PROP_SUBTYPE:
      g_value_set_enum (value, poppler_document_get_pdf_subtype (document));
      break;
    case PROP_SUBTYPE_STRING:
      g_value_take_string (value, poppler_document_get_pdf_subtype_string (document));
      break;
    case PROP_SUBTYPE_PART:
      g_value_set_enum (value, poppler_document_get_pdf_part (document));
      break;
    case PROP_SUBTYPE_CONF:
      g_value_set_enum (value, poppler_document_get_pdf_conformance (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_set_property (GObject      *object,
			       guint         prop_id,
			       const GValue *value,
			       GParamSpec   *pspec)
{
  PopplerDocument *document = POPPLER_DOCUMENT (object);

  switch (prop_id)
    {
    case PROP_TITLE:
      poppler_document_set_title (document, g_value_get_string (value));
      break;
    case PROP_AUTHOR:
      poppler_document_set_author (document, g_value_get_string (value));
      break;
    case PROP_SUBJECT:
      poppler_document_set_subject (document, g_value_get_string (value));
      break;
    case PROP_KEYWORDS:
      poppler_document_set_keywords (document, g_value_get_string (value));
      break;
    case PROP_CREATOR:
      poppler_document_set_creator (document, g_value_get_string (value));
      break;
    case PROP_PRODUCER:
      poppler_document_set_producer (document, g_value_get_string (value));
      break;
    case PROP_CREATION_DATE:
      poppler_document_set_creation_date (document, g_value_get_int (value));
      break;
    case PROP_MOD_DATE:
      poppler_document_set_modification_date (document, g_value_get_int (value));
      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;
  gobject_class->set_property = poppler_document_set_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",
							nullptr,
							G_PARAM_READWRITE));

  /**
   * 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",
							nullptr,
							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",
							nullptr,
							G_PARAM_READWRITE));

  /**
   * 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",
							nullptr,
							G_PARAM_READWRITE));

  /**
   * 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",
							nullptr,
							G_PARAM_READWRITE));

  /**
   * 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",
							nullptr,
							G_PARAM_READWRITE));

  /**
   * 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",
							nullptr,
							G_PARAM_READWRITE));

  /**
   * 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_READWRITE));

  /**
   * 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_READWRITE));

  /**
   * 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:print-scaling:
   *
   * Since: 0.73
   */
  g_object_class_install_property (G_OBJECT_CLASS (klass),
				   PROP_PRINT_SCALING,
				   g_param_spec_enum ("print-scaling",
						      "Print Scaling",
						      "Print Scaling Viewer Preference",
						      POPPLER_TYPE_PRINT_SCALING,
						      POPPLER_PRINT_SCALING_APP_DEFAULT,
						      (GParamFlags) (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)));

  /**
   * PopplerDocument:print-duplex:
   *
   * Since: 0.80
   */
  g_object_class_install_property (G_OBJECT_CLASS (klass),
				   PROP_PRINT_DUPLEX,
				   g_param_spec_enum ("print-duplex",
						      "Print Duplex",
						      "Duplex Viewer Preference",
						      POPPLER_TYPE_PRINT_DUPLEX,
						      POPPLER_PRINT_DUPLEX_NONE,
						      (GParamFlags) (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)));

  /**
   * PopplerDocument:print-n-copies:
   *
   * Suggested number of copies to be printed for this document
   *
   * Since: 0.80
   */
  g_object_class_install_property (G_OBJECT_CLASS (klass),
				   PROP_PRINT_N_COPIES,
				   g_param_spec_int ("print-n-copies",
						     "Number of Copies to Print",
						     "Number of Copies Viewer Preference",
						     1, G_MAXINT, 1,
						     (GParamFlags) (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)));

  /**
   * 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:subtype:
   *
   *  Document PDF subtype type
   */
  g_object_class_install_property (G_OBJECT_CLASS (klass),
				   PROP_SUBTYPE,
				   g_param_spec_enum ("subtype",
						       "PDF Format Subtype Type",
						       "The PDF subtype of the document",
						       POPPLER_TYPE_PDF_SUBTYPE,
						       POPPLER_PDF_SUBTYPE_UNSET,
						       G_PARAM_READABLE));

  /**
   * PopplerDocument:subtype-string:
   *
   *  Document PDF subtype. See also poppler_document_get_pdf_subtype_string()
   */
  g_object_class_install_property (G_OBJECT_CLASS (klass),
				   PROP_SUBTYPE_STRING,
				   g_param_spec_string ("subtype-string",
						       "PDF Format Subtype",
						       "The PDF subtype of the document",
						       nullptr,
						       G_PARAM_READABLE));

  /**
   * PopplerDocument:subtype-part:
   *
   *  Document PDF subtype part
   */
  g_object_class_install_property (G_OBJECT_CLASS (klass),
				   PROP_SUBTYPE_PART,
						       g_param_spec_enum ("subtype-part",
						       "PDF Format Subtype Part",
						       "The part of PDF conformance",
						       POPPLER_TYPE_PDF_PART,
						       POPPLER_PDF_SUBTYPE_PART_UNSET,
						       G_PARAM_READABLE));

  /**
   * PopplerDocument:subtype-conformance:
   *
   *  Document PDF subtype conformance
   */
  g_object_class_install_property (G_OBJECT_CLASS (klass),
				   PROP_SUBTYPE_CONF,
				   g_param_spec_enum ("subtype-conformance",
						       "PDF Format Subtype Conformance",
						       "The conformance level of PDF subtype",
						       POPPLER_TYPE_PDF_CONFORMANCE,
						       POPPLER_PDF_SUBTYPE_CONF_UNSET,
						       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",
							nullptr,
							G_PARAM_READABLE));
}

static void
poppler_document_init (PopplerDocument *document)
{
}

/* PopplerIndexIter: For determining the index of a tree */
struct _PopplerIndexIter
{
	PopplerDocument *document;
	const std::vector<OutlineItem*> *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 != nullptr, 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 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;
	const std::vector<OutlineItem*> *items;

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

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

	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 != nullptr, NULL);
	
	item = (*parent->items)[parent->index];
	item->open ();
	if (! (item->hasKids() && item->getKids()) )
		return nullptr;

	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 (const Unicode *unicode,
		 int      len)
{
	const UnicodeMap *uMap = globalParams->getUtf8Map();
		
	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.c_str ());
}

/**
 * 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 = (*iter->items)[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;
	const LinkAction *link_action;
	PopplerAction *action;
	gchar *title;

	g_return_val_if_fail (iter != nullptr, NULL);

	item = (*iter->items)[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 != nullptr, FALSE);

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

	return TRUE;
}

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

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

struct _PopplerFontsIter
{
	std::vector<FontInfo*> 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)
{
	const GooString *name;
	FontInfo *info;

	info = iter->items[iter->index];

	name = info->getName();
	if (name != nullptr) {
		return info->getName()->c_str();
	} else {
		return nullptr;
	}
}

/**
 * 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 = iter->items[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)
{
	const GooString *name;
	FontInfo *info;

	info = iter->items[iter->index];

	name = info->getSubstituteName();
	if (name != nullptr) {
		return name->c_str();
	} else {
		return nullptr;
	}
}

/**
 * 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)
{
	const GooString *file;
	FontInfo *info;

	info = iter->items[iter->index];

	file = info->getFile();
	if (file != nullptr) {
		return file->c_str();
	} else {
		return nullptr;
	}
}

/**
 * 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 != nullptr, POPPLER_FONT_TYPE_UNKNOWN);

	info = iter->items[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)
{
	const GooString *encoding;
	FontInfo *info;

	info = iter->items[iter->index];

	encoding = info->getEncoding();
	if (encoding != nullptr) {
		return encoding->c_str();
	} else {
		return nullptr;
	}
}

/**
 * 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 embedded, %FALSE otherwise
 */
gboolean
poppler_fonts_iter_is_embedded (PopplerFontsIter *iter)
{
	FontInfo *info;

	info = iter->items[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 = iter->items[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 != nullptr, FALSE);

	iter->index++;
	if (iter->index >= (int)iter->items.size())
		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 != nullptr, NULL);

	new_iter = g_slice_dup (PopplerFontsIter, iter);

	new_iter->items.resize(iter->items.size());
	for (std::size_t i = 0; i < iter->items.size(); i++) {
		FontInfo *info = iter->items[i];
		new_iter->items[i] = 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 == nullptr))
		return;

        for (auto entry : iter->items) {
          delete entry;
        }
        iter->items.~vector<FontInfo*>();

	g_slice_free (PopplerFontsIter, iter);
}

static PopplerFontsIter *
poppler_fonts_iter_new (std::vector<FontInfo*> &&items)
{
	PopplerFontsIter *iter;

	iter = g_slice_new (PopplerFontsIter);
	new ((void*)&iter->items) std::vector<FontInfo*>(std::move(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 = nullptr;
        font_info->scanner = nullptr;
}

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,
						      nullptr);
	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)
{
	g_return_val_if_fail (iter != nullptr, FALSE);

	std::vector<FontInfo*> items = font_info->scanner->scan(n_pages);

	if (items.empty()) {
		*iter = nullptr;
		return FALSE;
	} else {
		*iter = poppler_fonts_iter_new(std::move(items));
	}
	
	return TRUE;
}

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

	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_free_full (layer->kids, (GDestroyNotify)layer_free);
  }

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

  g_slice_free (Layer, layer);
}

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

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

      Object obj = rb->get (i);
      if (!obj.isArray ()) {
	continue;
      }

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

        const Object &ref = rb_array->getNF (j);
	if (!ref.isRef ()) {
	  continue;
	}

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

      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 nullptr;
}

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

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

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

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

  if (order) {
    items = get_optional_content_items_sorted (ocg, nullptr, order);
  } else {
    const auto &ocgs = ocg->getOCGs ();

    for (const auto &oc : ocgs) {
      Layer *layer = layer_new (oc.second.get());

      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 nullptr;
    
    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_free_full (document->layers, (GDestroyNotify)layer_free);
  g_list_free_full (document->layers_rbgroups, (GDestroyNotify)g_list_free);

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

/* 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 != nullptr, 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 == nullptr))
    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 nullptr;

  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 != nullptr, NULL);
	
  layer = (Layer *) g_list_nth_data (parent->items, parent->index);
  if (!layer || !layer->kids)
    return nullptr;

  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 != nullptr, NULL);
  
  layer = (Layer *)g_list_nth_data (iter->items, iter->index);

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

/**
 * 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 = nullptr;
  
  g_return_val_if_fail (iter != nullptr, NULL);
  
  layer = (Layer *)g_list_nth_data (iter->items, iter->index);
  if (layer->oc) {
    GList *rb_group = nullptr;

    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 != nullptr, 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 = nullptr;
        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 != nullptr, NULL);
	g_return_val_if_fail (n_pages > 0, NULL);

        ps_file = (PopplerPSFile *) g_object_new (POPPLER_TYPE_PS_FILE, nullptr);
	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 == nullptr);
        
        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 == nullptr);

        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 != nullptr);
        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 nullptr;

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

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

  return nullptr;
}

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

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

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

  return retval;
}
