Merge remote-tracking branch 'origin/poppler-0.24'

Conflicts:
	CMakeLists.txt
	NEWS
	configure.ac
	cpp/Doxyfile
	qt4/src/Doxyfile
	qt5/src/Doxyfile
diff --git a/CMakeLists.txt b/CMakeLists.txt
index dfd5be6..f2eb0aa 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -17,8 +17,8 @@
 CHECK_FILE_OFFSET_BITS()
 
 set(POPPLER_MAJOR_VERSION "0")
-set(POPPLER_MINOR_VERSION "24")
-set(POPPLER_MICRO_VERSION "5")
+set(POPPLER_MINOR_VERSION "25")
+set(POPPLER_MICRO_VERSION "0")
 set(POPPLER_VERSION "${POPPLER_MAJOR_VERSION}.${POPPLER_MINOR_VERSION}.${POPPLER_MICRO_VERSION}")
 
 # command line switches
@@ -264,6 +264,7 @@
   goo/GooString.cc
   goo/gmem.cc
   goo/FixedPoint.cc
+  goo/NetPBMWriter.cc
   goo/PNGWriter.cc
   goo/TiffWriter.cc
   goo/JpegWriter.cc
@@ -304,6 +305,7 @@
   poppler/Link.cc
   poppler/Linearization.cc
   poppler/LocalPDFDocBuilder.cc
+  poppler/MarkedContentOutputDev.cc
   poppler/NameToCharCode.cc
   poppler/Object.cc
   poppler/OptionalContent.cc
@@ -320,6 +322,8 @@
   poppler/PreScanOutputDev.cc
   poppler/PSTokenizer.cc
   poppler/Stream.cc
+  poppler/StructTreeRoot.cc
+  poppler/StructElement.cc
   poppler/strtok_r.cpp
   poppler/UnicodeMap.cc
   poppler/UnicodeTypeTable.cc
@@ -423,7 +427,7 @@
 else(MSVC)
 add_library(poppler SHARED ${poppler_SRCS})
 endif(MSVC)
-set_target_properties(poppler PROPERTIES VERSION 44.0.0 SOVERSION 44)
+set_target_properties(poppler PROPERTIES VERSION 45.0.0 SOVERSION 45)
 target_link_libraries(poppler ${poppler_LIBS})
 target_link_libraries(poppler LINK_INTERFACE_LIBRARIES "")
 install(TARGETS poppler RUNTIME DESTINATION bin LIBRARY DESTINATION lib${LIB_SUFFIX} ARCHIVE DESTINATION lib${LIB_SUFFIX})
@@ -460,6 +464,7 @@
     poppler/Link.h
     poppler/Linearization.h
     poppler/LocalPDFDocBuilder.h
+    poppler/MarkedContentOutputDev.h
     poppler/Movie.h
     poppler/NameToCharCode.h
     poppler/Object.h
@@ -480,6 +485,8 @@
     poppler/Rendition.h
     poppler/Stream-CCITT.h
     poppler/Stream.h
+    poppler/StructElement.h
+    poppler/StructTreeRoot.h
     poppler/UnicodeMap.h
     poppler/UnicodeMapTables.h
     poppler/UnicodeTypeTable.h
diff --git a/NEWS b/NEWS
index 38ef36d..6d5140e 100644
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,26 @@
+Release 0.25.0
+        core:
+         * Annotation improvements
+         * Tagged PDF work
+         * Improve speed on some files using ICC color space
+         * Use ICC profile in OutputIntents. Bug #34053
+         * Limit use of ZapfDingbats character names. Bug #60243
+         * Splash: correction for knockout transparency groups
+         * regression test improvements
+
+        utils:
+         * pdftoppm: Added thinlinemode option setting
+         * pdfinfo: Indicate if pdf contains javascript
+         * pdfinfo: Add option to print out javascript
+         * pdfimages: Print size, ratio, and ppi
+         * pdfimages: More image output format support
+         * pdfseparate: allow zero-padded pagespecs
+
+        glib:
+         * Annotation improvements
+         * Add API to get text, text layout and text attributes for a given area
+         * demo improvements
+
 Release 0.24.5
         core:
          * Fix crash due to wrong formatting of error message. KDE Bug #328511
diff --git a/configure.ac b/configure.ac
index 7476021..b248149 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,6 +1,6 @@
 m4_define([poppler_version_major],[0])
-m4_define([poppler_version_minor],[24])
-m4_define([poppler_version_micro],[5])
+m4_define([poppler_version_minor],[25])
+m4_define([poppler_version_micro],[0])
 m4_define([poppler_version],[poppler_version_major.poppler_version_minor.poppler_version_micro])
 
 AC_PREREQ(2.59)
@@ -661,20 +661,20 @@
   AC_CHECK_TOOL(MOCQT5, moc)
   AC_MSG_CHECKING([for Qt5 moc])
   mocversion=`$MOCQT5 -v 2>&1`
-  mocversiongrep=`echo $mocversion | grep "Qt 5"`
+  mocversiongrep=`echo $mocversion | grep "Qt\|moc 5"`
   if test x"$mocversiongrep" != x"$mocversion"; then
     AC_MSG_RESULT([no])
     # moc was not the qt5 one, try with moc-qt5
     AC_CHECK_TOOL(MOCQT52, moc-qt5)
     AC_MSG_CHECKING([for Qt5 moc-qt5])
     mocversion=`$MOCQT52 -v 2>&1`
-    mocversiongrep=`echo $mocversion | grep "Qt 5"`
+    mocversiongrep=`echo $mocversion | grep "Qt\|moc 5"`
     if test x"$mocversiongrep" != x"$mocversion"; then
       AC_CHECK_TOOL(QTCHOOSER, qtchooser)
       AC_MSG_CHECKING([for qtchooser])
       qt5tooldir=`QT_SELECT=qt5 qtchooser -print-env | grep QTTOOLDIR | cut -d '=' -f 2 | cut -d \" -f 2`
       mocversion=`$qt5tooldir/moc -v 2>&1`
-      mocversiongrep=`echo $mocversion | grep "Qt 5"`
+      mocversiongrep=`echo $mocversion | grep "Qt\|moc 5"`
       if test x"$mocversiongrep" != x"$mocversion"; then
         # no valid moc found
         enable_poppler_qt5=no;
diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt
index af61606..e606988 100644
--- a/cpp/CMakeLists.txt
+++ b/cpp/CMakeLists.txt
@@ -8,7 +8,6 @@
 add_subdirectory(tests)
 
 set(poppler_cpp_SRCS
-  PNMWriter.cc
   poppler-document.cpp
   poppler-embedded-file.cpp
   poppler-font.cpp
diff --git a/cpp/Doxyfile b/cpp/Doxyfile
index 40c6cc4..3d51934 100644
--- a/cpp/Doxyfile
+++ b/cpp/Doxyfile
@@ -31,7 +31,7 @@
 # This could be handy for archiving the generated documentation or
 # if some version control system is used.
 
-PROJECT_NUMBER         = 0.24.5
+PROJECT_NUMBER         = 0.25.0
 
 # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute)
 # base path where the generated documentation will be put.
diff --git a/cpp/Makefile.am b/cpp/Makefile.am
index f381e58..50856e2 100644
--- a/cpp/Makefile.am
+++ b/cpp/Makefile.am
@@ -41,8 +41,6 @@
 
 lib_LTLIBRARIES = libpoppler-cpp.la
 libpoppler_cpp_la_SOURCES =			\
-	PNMWriter.cc				\
-	PNMWriter.h				\
 	poppler-document.cpp			\
 	poppler-document-private.h		\
 	poppler-embedded-file.cpp		\
diff --git a/cpp/PNMWriter.cc b/cpp/PNMWriter.cc
deleted file mode 100644
index a2b9a77..0000000
--- a/cpp/PNMWriter.cc
+++ /dev/null
@@ -1,119 +0,0 @@
-//========================================================================
-//
-// PNMWriter.cc
-//
-// This file is licensed under the GPLv2 or later
-//
-// Copyright (C) 2011 Pino Toscano <pino@kde.org>
-//
-//========================================================================
-
-#include "PNMWriter.h"
-
-#include <vector>
-
-using namespace poppler;
-
-PNMWriter::PNMWriter(OutFormat formatArg)
-  : format(formatArg)
-  , file(0)
-  , imgWidth(0)
-  , rowSize(0)
-{
-}
-
-PNMWriter::~PNMWriter()
-{
-}
-
-bool PNMWriter::init(FILE *f, int width, int height, int /*hDPI*/, int /*vDPI*/)
-{
-  file = f;
-  imgWidth = width;
-
-  switch (format)
-  {
-    case PNMWriter::PBM:
-      fprintf(file, "P4\n%d %d\n", width, height);
-      rowSize = (width + 7) >> 3;
-      break;
-    case PNMWriter::PGM:
-      fprintf(file, "P5\n%d %d\n255\n", width, height);
-      rowSize = width;
-      break;
-    case PNMWriter::PPM:
-      fprintf(file, "P6\n%d %d\n255\n", width, height);
-      rowSize = width * 3;
-      break;
-  }
-
-  return true;
-}
-
-bool PNMWriter::writePointers(unsigned char **rowPointers, int rowCount)
-{
-  bool ret = true;
-  for (int i = 0; ret && (i < rowCount); ++i) {
-    ret = writeRow(&(rowPointers[i]));
-  }
-
-  return ret;
-}
-
-bool PNMWriter::writeRow(unsigned char **row)
-{
-  std::vector<unsigned char> newRow;
-  unsigned char *rowPtr = *row;
-  unsigned char *p = *row;
-
-  switch (format)
-  {
-    case PNMWriter::PBM:
-      newRow.resize(rowSize, 0);
-      rowPtr = &newRow[0];
-      for (int i = 0; i < imgWidth; ++i) {
-        unsigned char pixel = p[0];
-        if (p[0] == p[1] && p[1] == p[2]) {
-          // gray, stored already
-        } else {
-          pixel = static_cast<unsigned char>((p[0] * 11 + p[1] * 16 + p[2] * 5) / 32);
-        }
-        if (pixel < 0x7F) {
-          *(rowPtr + (i >> 3)) |= (1 << (i & 7));
-        }
-        p += 3;
-      }
-      break;
-    case PNMWriter::PGM:
-      newRow.resize(rowSize, 0);
-      rowPtr = &newRow[0];
-      for (int i = 0; i < imgWidth; ++i) {
-        if (p[0] == p[1] && p[1] == p[2]) {
-          // gray, store directly
-          newRow[i] = p[0];
-        } else {
-          // calculate the gray value
-          newRow[i] = static_cast<unsigned char>((p[0] * 11 + p[1] * 16 + p[2] * 5) / 32);
-        }
-        p += 3;
-      }
-      break;
-    case PNMWriter::PPM:
-      break;
-  }
-
-  if (int(fwrite(rowPtr, 1, rowSize, file)) < rowSize) {
-    return false;
-  }
-
-  return true;
-}
-
-bool PNMWriter::close()
-{
-  file = 0;
-  imgWidth = 0;
-  rowSize = 0;
-
-  return true;
-}
diff --git a/cpp/PNMWriter.h b/cpp/PNMWriter.h
deleted file mode 100644
index 8d8da2d..0000000
--- a/cpp/PNMWriter.h
+++ /dev/null
@@ -1,43 +0,0 @@
-//========================================================================
-//
-// PNMWriter.h
-//
-// This file is licensed under the GPLv2 or later
-//
-// Copyright (C) 2011 Pino Toscano <pino@kde.org>
-//
-//========================================================================
-
-#ifndef PNMWRITER_H
-#define PNMWRITER_H
-
-#include "ImgWriter.h"
-
-namespace poppler
-{
-
-class PNMWriter : public ImgWriter
-{
-  public:
-    enum OutFormat { PBM, PGM, PPM };
-
-    PNMWriter(OutFormat formatArg);
-    ~PNMWriter();
-
-    bool init(FILE *f, int width, int height, int hDPI, int vDPI);
-
-    bool writePointers(unsigned char **rowPointers, int rowCount);
-    bool writeRow(unsigned char **row);
-
-    bool close();
-
-  private:
-    const OutFormat format;
-    FILE *file;
-    int imgWidth;
-    int rowSize;
-};
-
-}
-
-#endif
diff --git a/cpp/poppler-image.cpp b/cpp/poppler-image.cpp
index 8e9ac63..23306df 100644
--- a/cpp/poppler-image.cpp
+++ b/cpp/poppler-image.cpp
@@ -1,5 +1,6 @@
 /*
  * Copyright (C) 2010-2011, Pino Toscano <pino@kde.org>
+ * Copyright (C) 2013 Adrian Johnson <ajohnson@redneon.com>
  *
  * 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
@@ -31,7 +32,7 @@
 #if defined(ENABLE_LIBTIFF)
 #include "TiffWriter.h"
 #endif
-#include "PNMWriter.h"
+#include "NetPBMWriter.h"
 
 #include <cstdlib>
 #include <cstring>
@@ -39,8 +40,6 @@
 #include <memory>
 #include <vector>
 
-using poppler::PNMWriter;
-
 namespace {
 
 struct FileCloser {
@@ -69,17 +68,17 @@
     return 0;
 }
 
-PNMWriter::OutFormat pnm_format(poppler::image::format_enum format)
+NetPBMWriter::Format pnm_format(poppler::image::format_enum format)
 {
     switch (format) {
     case poppler::image::format_invalid: // unused, anyway
     case poppler::image::format_mono:
-        return PNMWriter::PBM;
+        return NetPBMWriter::MONOCHROME;
     case poppler::image::format_rgb24:
     case poppler::image::format_argb32:
-        return PNMWriter::PPM;
+        return NetPBMWriter::RGB;
     }
-    return PNMWriter::PPM;
+    return NetPBMWriter::RGB;
 }
 
 }
@@ -366,7 +365,7 @@
     }
 #endif
     else if (fmt == "pnm") {
-        w.reset(new PNMWriter(pnm_format(d->format)));
+        w.reset(new NetPBMWriter(pnm_format(d->format)));
     }
     if (!w.get()) {
         return false;
diff --git a/glib/demo/annots.c b/glib/demo/annots.c
index 3ff7f12..6402501 100644
--- a/glib/demo/annots.c
+++ b/glib/demo/annots.c
@@ -23,10 +23,6 @@
 #include "utils.h"
 
 enum {
-    ANNOTS_X1_COLUMN,
-    ANNOTS_Y1_COLUMN,
-    ANNOTS_X2_COLUMN,
-    ANNOTS_Y2_COLUMN,
     ANNOTS_TYPE_COLUMN,
     ANNOTS_COLOR_COLUMN,
     ANNOTS_FLAG_INVISIBLE_COLUMN,
@@ -36,24 +32,59 @@
     N_COLUMNS
 };
 
+enum {
+    SELECTED_TYPE_COLUMN,
+    SELECTED_LABEL_COLUMN,
+    SELECTED_N_COLUMNS
+};
+
+typedef enum {
+    MODE_NORMAL,  /* Regular use as pointer in the page */
+    MODE_ADD,     /* To add simple annotations */
+    MODE_EDIT,    /* To move/edit an annotation */
+    MODE_DRAWING  /* To add annotations that require mouse interactions */
+} ModeType;
+
 typedef struct {
     PopplerDocument *doc;
     PopplerPage     *page;
+    PopplerAnnot    *active_annot;
 
     GtkWidget       *tree_view;
     GtkListStore    *model;
+    GtkWidget       *darea;
     GtkWidget       *annot_view;
     GtkWidget       *timer_label;
+    GtkWidget       *remove_button;
+    GtkWidget       *type_selector;
+    GtkWidget       *main_box;
 
     gint             num_page;
+    gint             annot_type;
+    ModeType         mode;
+
+    cairo_surface_t *surface;
+    GdkRGBA          annot_color;
+
+    GdkPoint         start;
+    GdkPoint         stop;
+    GdkCursorType    cursor;
+    guint            annotations_idle;
 } PgdAnnotsDemo;
 
+static void pgd_annots_viewer_queue_redraw (PgdAnnotsDemo *demo);
+
 static void
 pgd_annots_free (PgdAnnotsDemo *demo)
 {
     if (!demo)
         return;
 
+    if (demo->annotations_idle > 0) {
+        g_source_remove (demo->annotations_idle);
+        demo->annotations_idle = 0;
+    }
+
     if (demo->doc) {
         g_object_unref (demo->doc);
         demo->doc = NULL;
@@ -80,7 +111,7 @@
     frame = gtk_frame_new (NULL);
     gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_NONE);
     label = gtk_label_new (NULL);
-    gtk_label_set_markup (GTK_LABEL (label), "<b>Annot Properties</b>");
+    gtk_label_set_markup (GTK_LABEL (label), "<b>Annotation Properties</b>");
     gtk_frame_set_label_widget (GTK_FRAME (frame), label);
     gtk_widget_show (label);
 
@@ -155,7 +186,12 @@
     PopplerColor *poppler_color;
 
     if ((poppler_color = poppler_annot_get_color (poppler_annot))) {
-        GdkPixbuf *pixbuf = pgd_pixbuf_new_for_color (poppler_color);
+        GdkPixbuf *pixbuf_tmp, *pixbuf;
+
+        pixbuf_tmp = pgd_pixbuf_new_for_color (poppler_color);
+        pixbuf = gdk_pixbuf_scale_simple(pixbuf_tmp, 16, 16, GDK_INTERP_BILINEAR);
+        g_object_unref (pixbuf_tmp);
+
         g_free (poppler_color);
 
         return pixbuf;
@@ -281,6 +317,45 @@
 }
 
 static void
+pgd_annots_update_cursor (PgdAnnotsDemo *demo,
+                          GdkCursorType  cursor_type)
+{
+    GdkCursor *cursor = NULL;
+
+    if (cursor_type == demo->cursor)
+        return;
+
+    if (cursor_type != GDK_LAST_CURSOR) {
+        cursor = gdk_cursor_new_for_display (gtk_widget_get_display (demo->main_box),
+                                             cursor_type);
+     }
+
+     demo->cursor = cursor_type;
+
+     gdk_window_set_cursor (gtk_widget_get_window (demo->main_box), cursor);
+     gdk_flush ();
+     if (cursor)
+         g_object_unref (cursor);
+}
+
+static void
+pgd_annots_start_add_annot (GtkWidget     *button,
+                            PgdAnnotsDemo *demo)
+{
+    GtkTreeModel *model;
+    GtkTreeIter   iter;
+
+    gtk_combo_box_get_active_iter (GTK_COMBO_BOX (demo->type_selector), &iter);
+    model = gtk_combo_box_get_model (GTK_COMBO_BOX (demo->type_selector));
+    gtk_tree_model_get (model, &iter,
+                        SELECTED_TYPE_COLUMN, &(demo->annot_type),
+                        -1);
+
+    demo->mode = MODE_ADD;
+    pgd_annots_update_cursor (demo, GDK_TCROSS);
+}
+
+static void
 pgd_annots_remove_annot (GtkWidget     *button,
                          PgdAnnotsDemo *demo)
 {
@@ -299,6 +374,8 @@
         poppler_page_remove_annot (demo->page, annot);
         g_object_unref (annot);
         gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
+
+        pgd_annots_viewer_queue_redraw (demo);
     }
 }
 
@@ -360,6 +437,18 @@
 }
 
 static void
+pgd_annot_color_changed (GtkButton     *button,
+                         GParamSpec    *pspec,
+                         PgdAnnotsDemo *demo)
+{
+#if GTK_CHECK_VERSION(3,4,0)
+    gtk_color_chooser_get_rgba (GTK_COLOR_CHOOSER (button), &demo->annot_color);
+#else
+    gtk_color_button_get_rgba (GTK_COLOR_BUTTON (button), &demo->annot_color);
+#endif
+}
+
+static void
 pgd_annot_view_set_annot_free_text (GtkWidget            *table,
                                     PopplerAnnotFreeText *annot,
                                     gint                 *row)
@@ -479,10 +568,10 @@
 {
     GtkWidget  *alignment;
     GtkWidget  *table;
-    GtkWidget  *button;
     gint        row = 0;
     gchar      *text;
     time_t      timet;
+    PopplerRectangle rect;
 
     alignment = gtk_bin_get_child (GTK_BIN (demo->annot_view));
     if (alignment) {
@@ -490,7 +579,7 @@
     }
 
     alignment = gtk_alignment_new (0.5, 0.5, 1, 1);
-    gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), 5, 5, 12, 5);
+    gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), 5, 5, 8, 5);
     gtk_container_add (GTK_CONTAINER (demo->annot_view), alignment);
     gtk_widget_show (alignment);
 
@@ -517,12 +606,9 @@
     pgd_table_add_property (GTK_GRID (table), "<b>Modified:</b>", text, &row);
     g_free (text);
 
-    text = g_strdup_printf ("%d", poppler_annot_get_flags (annot));
-    pgd_table_add_property (GTK_GRID (table), "<b>Flags:</b>", text, &row);
-    g_free (text);
-
-    text = g_strdup_printf ("%d", poppler_annot_get_page_index (annot));
-    pgd_table_add_property (GTK_GRID (table), "<b>Page:</b>", text, &row);
+    poppler_annot_get_rectangle (annot, &rect);
+    text = g_strdup_printf ("(%.2f;%.2f) (%.2f;%.2f)", rect.x1, rect.y1, rect.x2, rect.y2);
+    pgd_table_add_property (GTK_GRID (table), "<b>Coords:</b>", text, &row);
     g_free (text);
 
     if (POPPLER_IS_ANNOT_MARKUP (annot))
@@ -549,20 +635,47 @@
           break;
     }
 
-    button = gtk_button_new_from_stock (GTK_STOCK_REMOVE);
-    g_signal_connect (G_OBJECT (button), "clicked",
-                      G_CALLBACK (pgd_annots_remove_annot),
-                      (gpointer) demo);
-    gtk_grid_attach (GTK_GRID (table), button, 0, row, 2, 1);
-    gtk_widget_show (button);
-
     gtk_container_add (GTK_CONTAINER (alignment), table);
     gtk_widget_show (table);
 }
 
 static void
-pgd_annots_get_annots (GtkWidget     *button,
-                       PgdAnnotsDemo *demo)
+pgd_annots_add_annot_to_model (PgdAnnotsDemo *demo,
+                               PopplerAnnot *annot,
+                               PopplerRectangle area,
+                               gboolean selected)
+{
+    GtkTreeIter      iter;
+    GdkPixbuf        *pixbuf;
+    PopplerAnnotFlag  flags;
+
+    pixbuf = get_annot_color (annot);
+    flags = poppler_annot_get_flags (annot);
+
+    gtk_list_store_append (demo->model, &iter);
+    gtk_list_store_set (demo->model, &iter,
+                        ANNOTS_TYPE_COLUMN, get_annot_type (annot),
+                        ANNOTS_COLOR_COLUMN, pixbuf,
+                        ANNOTS_FLAG_INVISIBLE_COLUMN, (flags & POPPLER_ANNOT_FLAG_INVISIBLE),
+                        ANNOTS_FLAG_HIDDEN_COLUMN, (flags & POPPLER_ANNOT_FLAG_HIDDEN),
+                        ANNOTS_FLAG_PRINT_COLUMN, (flags & POPPLER_ANNOT_FLAG_PRINT),
+                        ANNOTS_COLUMN, annot,
+                        -1);
+
+    if (selected) {
+        GtkTreePath *path;
+
+        path = gtk_tree_model_get_path (GTK_TREE_MODEL (demo->model), &iter);
+        gtk_tree_view_set_cursor (GTK_TREE_VIEW (demo->tree_view), path, NULL, FALSE);
+        gtk_tree_path_free (path);
+    }
+
+    if (pixbuf)
+        g_object_unref (pixbuf);
+}
+
+static void
+pgd_annots_get_annots (PgdAnnotsDemo *demo)
 {
     GList       *mapping, *l;
     gint         n_fields;
@@ -588,54 +701,22 @@
     if (n_fields > 0) {
         gchar *str;
 
-        str = g_strdup_printf ("<i>%d annots found in %.4f seconds</i>",
+        str = g_strdup_printf ("<i>%d annotations found in %.4f seconds</i>",
                                n_fields, g_timer_elapsed (timer, NULL));
         gtk_label_set_markup (GTK_LABEL (demo->timer_label), str);
         g_free (str);
     } else {
-        gtk_label_set_markup (GTK_LABEL (demo->timer_label), "<i>No annots found</i>");
+        gtk_label_set_markup (GTK_LABEL (demo->timer_label), "<i>No annotations found</i>");
     }
 
     g_timer_destroy (timer);
 
     for (l = mapping; l; l = g_list_next (l)) {
         PopplerAnnotMapping *amapping;
-        GtkTreeIter          iter;
-        gchar               *x1, *y1, *x2, *y2;
-        GdkPixbuf           *pixbuf;
-	PopplerAnnotFlag     flags;
 
         amapping = (PopplerAnnotMapping *) l->data;
-
-        x1 = g_strdup_printf ("%.2f", amapping->area.x1);
-        y1 = g_strdup_printf ("%.2f", amapping->area.y1);
-        x2 = g_strdup_printf ("%.2f", amapping->area.x2);
-        y2 = g_strdup_printf ("%.2f", amapping->area.y2);
-
-        pixbuf = get_annot_color (amapping->annot);
-	flags = poppler_annot_get_flags (amapping->annot);
-
-        gtk_list_store_append (demo->model, &iter);
-        gtk_list_store_set (demo->model, &iter,
-                            ANNOTS_X1_COLUMN, x1,
-                            ANNOTS_Y1_COLUMN, y1,
-                            ANNOTS_X2_COLUMN, x2,
-                            ANNOTS_Y2_COLUMN, y2,
-                            ANNOTS_TYPE_COLUMN, get_annot_type (amapping->annot),
-                            ANNOTS_COLOR_COLUMN, pixbuf,
-			    ANNOTS_FLAG_INVISIBLE_COLUMN, (flags & POPPLER_ANNOT_FLAG_INVISIBLE),
-			    ANNOTS_FLAG_HIDDEN_COLUMN, (flags & POPPLER_ANNOT_FLAG_HIDDEN),
-			    ANNOTS_FLAG_PRINT_COLUMN, (flags & POPPLER_ANNOT_FLAG_PRINT),
-                            ANNOTS_COLUMN, amapping->annot,
-                           -1);
-
-        if (pixbuf)
-            g_object_unref (pixbuf);
-
-        g_free (x1);
-        g_free (y1);
-        g_free (x2);
-        g_free (y2);
+        pgd_annots_add_annot_to_model (demo, amapping->annot,
+                                       amapping->area, FALSE);
     }
 
     poppler_page_free_annot_mapping (mapping);
@@ -646,6 +727,8 @@
                                         PgdAnnotsDemo *demo)
 {
     demo->num_page = (gint) gtk_spin_button_get_value (spinbutton) - 1;
+    pgd_annots_viewer_queue_redraw (demo);
+    pgd_annots_get_annots (demo);
 }
 
 static void
@@ -663,8 +746,11 @@
                            -1);
         pgd_annot_view_set_annot (demo, annot);
         g_object_unref (annot);
+
+        gtk_widget_set_sensitive (demo->remove_button, TRUE);
     } else {
         pgd_annot_view_set_annot (demo, NULL);
+        gtk_widget_set_sensitive (demo->remove_button, FALSE);
     }
 }
 
@@ -698,6 +784,8 @@
 
     pgd_annot_view_set_annot (demo, annot);
     gtk_tree_path_free (path);
+
+    pgd_annots_viewer_queue_redraw (demo);
 }
 
 static void
@@ -725,134 +813,262 @@
 }
 
 static void
-pgd_annots_add_annot (GtkWidget     *button,
-		      PgdAnnotsDemo *demo)
+pgd_annots_add_annot (PgdAnnotsDemo *demo)
 {
-    GtkWidget   *hbox, *vbox;
-    GtkWidget   *type_selector;
-    GtkWidget   *label;
-    GtkWidget   *rect_hbox;
-    GtkWidget   *rect_x1, *rect_y1, *rect_x2, *rect_y2;
-    GtkWidget   *dialog;
+    PopplerRectangle  rect;
+    PopplerColor      color;
+    PopplerAnnot     *annot;
+    gdouble           height;
+
+    g_assert (demo->mode == MODE_ADD);
+
+    poppler_page_get_size (demo->page, NULL, &height);
+
+    rect.x1 = demo->start.x;
+    rect.y1 = height - demo->start.y;
+    rect.x2 = demo->stop.x;
+    rect.y2 = height - demo->stop.y;
+
+    color.red = CLAMP ((guint) (demo->annot_color.red * 65535), 0, 65535);
+    color.green = CLAMP ((guint) (demo->annot_color.green * 65535), 0, 65535);
+    color.blue = CLAMP ((guint) (demo->annot_color.blue * 65535), 0, 65535);
+
+    switch (demo->annot_type) {
+        case POPPLER_ANNOT_TEXT:
+            annot = poppler_annot_text_new (demo->doc, &rect);
+
+            break;
+        case POPPLER_ANNOT_LINE: {
+            PopplerPoint start, end;
+
+            start.x = rect.x1;
+            start.y = rect.y1;
+            end.x = rect.x2;
+            end.y = rect.y2;
+
+            annot = poppler_annot_line_new (demo->doc, &rect);
+            poppler_annot_line_set_vertices (POPPLER_ANNOT_LINE (annot),
+                                             &start, &end);
+        }
+            break;
+        case POPPLER_ANNOT_SQUARE:
+            annot = poppler_annot_square_new (demo->doc, &rect);
+            break;
+        case POPPLER_ANNOT_CIRCLE:
+            annot = poppler_annot_circle_new (demo->doc, &rect);
+            break;
+        default:
+            g_assert_not_reached ();
+    }
+
+    demo->active_annot = annot;
+
+    poppler_annot_set_color (annot, &color);
+    poppler_page_add_annot (demo->page, annot);
+    pgd_annots_add_annot_to_model (demo, annot, rect, TRUE);
+    g_object_unref (annot);
+}
+
+static void
+pgd_annots_finish_add_annot (PgdAnnotsDemo *demo)
+{
+    g_assert (demo->mode == MODE_ADD || demo->mode == MODE_DRAWING);
+
+    demo->mode = MODE_NORMAL;
+    demo->start.x = -1;
+    pgd_annots_update_cursor (demo, GDK_LAST_CURSOR);
+    pgd_annots_viewer_queue_redraw (demo);
+
+    gtk_label_set_text (GTK_LABEL (demo->timer_label), NULL);
+}
+
+/* Render area */
+static cairo_surface_t *
+pgd_annots_render_page (PgdAnnotsDemo *demo)
+{
+    cairo_t *cr;
     PopplerPage *page;
-    gdouble      width, height;
-    PopplerAnnot *annot;
-    PopplerRectangle rect;
+    gdouble width, height;
+    cairo_surface_t *surface = NULL;
 
     page = poppler_document_get_page (demo->doc, demo->num_page);
     if (!page)
-	    return;
+        return NULL;
+
     poppler_page_get_size (page, &width, &height);
+    gtk_widget_set_size_request (demo->darea, width, height);
 
-    dialog = gtk_dialog_new_with_buttons ("Add new annotation",
-					  GTK_WINDOW (gtk_widget_get_toplevel (button)),
-					  GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
-					  GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
-					  "Add annotation", GTK_RESPONSE_ACCEPT,
-					  NULL);
+    surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24,
+                                          width, height);
+    cr = cairo_create (surface);
 
-    vbox = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
+    cairo_save (cr);
+    cairo_set_source_rgb (cr, 1, 1, 1);
+    cairo_rectangle (cr, 0, 0, width, height);
+    cairo_fill (cr);
+    cairo_restore (cr);
 
-    type_selector = gtk_combo_box_text_new ();
-    gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (type_selector), "POPPLER_ANNOT_UNKNOWN");
-    gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (type_selector), "POPPLER_ANNOT_TEXT");
-    gtk_combo_box_set_active (GTK_COMBO_BOX (type_selector), 1);
-    gtk_box_pack_start (GTK_BOX (vbox), type_selector, TRUE, TRUE, 0);
-    gtk_widget_show (type_selector);
+    cairo_save (cr);
+    poppler_page_render (page, cr);
+    cairo_restore (cr);
 
-    hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
-
-    rect_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
-
-    label = gtk_label_new ("x1:");
-    gtk_box_pack_start (GTK_BOX (rect_hbox), label, TRUE, TRUE, 0);
-    gtk_widget_show (label);
-
-    rect_x1 = gtk_spin_button_new_with_range (0, width, 1.0);
-    gtk_box_pack_start (GTK_BOX (rect_hbox), rect_x1, TRUE, TRUE, 0);
-    gtk_widget_show (rect_x1);
-
-    gtk_box_pack_start (GTK_BOX (hbox), rect_hbox, FALSE, TRUE, 0);
-    gtk_widget_show (rect_hbox);
-
-    rect_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
-
-    label = gtk_label_new ("x2:");
-    gtk_box_pack_start (GTK_BOX (rect_hbox), label, TRUE, TRUE, 0);
-    gtk_widget_show (label);
-
-    rect_x2 = gtk_spin_button_new_with_range (0, width, 1.0);
-    gtk_box_pack_start (GTK_BOX (rect_hbox), rect_x2, TRUE, TRUE, 0);
-    gtk_widget_show (rect_x2);
-
-    gtk_box_pack_start (GTK_BOX (hbox), rect_hbox, FALSE, TRUE, 0);
-    gtk_widget_show (rect_hbox);
-
-    rect_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
-
-    label = gtk_label_new ("y1:");
-    gtk_box_pack_start (GTK_BOX (rect_hbox), label, TRUE, TRUE, 0);
-    gtk_widget_show (label);
-
-    rect_y1 = gtk_spin_button_new_with_range (0, height, 1.0);
-    gtk_box_pack_start (GTK_BOX (rect_hbox), rect_y1, TRUE, TRUE, 0);
-    gtk_widget_show (rect_y1);
-
-    gtk_box_pack_start (GTK_BOX (hbox), rect_hbox, FALSE, TRUE, 0);
-    gtk_widget_show (rect_hbox);
-
-    rect_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
-
-    label = gtk_label_new ("y2:");
-    gtk_box_pack_start (GTK_BOX (rect_hbox), label, TRUE, TRUE, 0);
-    gtk_widget_show (label);
-
-    rect_y2 = gtk_spin_button_new_with_range (0, height, 1.0);
-    gtk_box_pack_start (GTK_BOX (rect_hbox), rect_y2, TRUE, TRUE, 0);
-    gtk_widget_show (rect_y2);
-
-    gtk_box_pack_start (GTK_BOX (hbox), rect_hbox, FALSE, TRUE, 0);
-    gtk_widget_show (rect_hbox);
-
-    gtk_box_pack_start (GTK_BOX (vbox), hbox, TRUE, TRUE, 0);
-    gtk_widget_show (hbox);
-
-    gtk_dialog_run (GTK_DIALOG (dialog));
-
-    rect.x1 = gtk_spin_button_get_value (GTK_SPIN_BUTTON (rect_x1));
-    rect.x2 = gtk_spin_button_get_value (GTK_SPIN_BUTTON (rect_x2));
-    rect.y1 = height - gtk_spin_button_get_value (GTK_SPIN_BUTTON (rect_y2));
-    rect.y2 = height - gtk_spin_button_get_value (GTK_SPIN_BUTTON (rect_y1));
-    annot = poppler_annot_text_new (demo->doc, &rect);
-    poppler_page_add_annot (page, annot);
-
+    cairo_destroy (cr);
     g_object_unref (page);
 
-    gtk_widget_destroy (dialog);
+    return surface;
 }
 
+static gboolean
+pgd_annots_view_drawing_area_draw (GtkWidget   *area,
+                                   cairo_t     *cr,
+                                   PgdAnnotsDemo *demo)
+{
+    if (demo->num_page == -1)
+        return FALSE;
+
+    if (!demo->surface) {
+        demo->surface = pgd_annots_render_page (demo);
+        if (!demo->surface)
+            return FALSE;
+    }
+
+    cairo_set_source_surface (cr, demo->surface, 0, 0);
+    cairo_paint (cr);
+
+    return TRUE;
+}
+
+static gboolean
+pgd_annots_viewer_redraw (PgdAnnotsDemo *demo)
+{
+    cairo_surface_destroy (demo->surface);
+    demo->surface = NULL;
+
+    gtk_widget_queue_draw (demo->darea);
+
+    demo->annotations_idle = 0;
+
+    return FALSE;
+}
+
+static void
+pgd_annots_viewer_queue_redraw (PgdAnnotsDemo *demo)
+{
+    if (demo->annotations_idle == 0)
+        demo->annotations_idle = g_idle_add ((GSourceFunc)pgd_annots_viewer_redraw,
+                                             demo);
+}
+
+static void
+pgd_annots_drawing_area_realize (GtkWidget     *area,
+                                 PgdAnnotsDemo *demo)
+{
+    gtk_widget_add_events (area,
+                           GDK_POINTER_MOTION_HINT_MASK |
+                           GDK_BUTTON1_MOTION_MASK |
+                           GDK_BUTTON_PRESS_MASK |
+                           GDK_BUTTON_RELEASE_MASK);
+}
+
+static gboolean
+pgd_annots_drawing_area_button_press (GtkWidget      *area,
+                                      GdkEventButton *event,
+                                      PgdAnnotsDemo  *demo)
+{
+    if (!demo->page || demo->mode != MODE_ADD || event->button != 1)
+        return FALSE;
+
+    demo->start.x = event->x;
+    demo->start.y = event->y;
+    demo->stop = demo->start;
+
+    pgd_annots_add_annot (demo);
+    pgd_annots_viewer_queue_redraw (demo);
+    demo->mode = MODE_DRAWING;
+
+    return TRUE;
+}
+
+static gboolean
+pgd_annots_drawing_area_motion_notify (GtkWidget      *area,
+                                       GdkEventMotion *event,
+                                       PgdAnnotsDemo  *demo)
+{
+    PopplerRectangle rect;
+    PopplerPoint start, end;
+    gdouble width, height;
+
+    if (!demo->page || demo->mode != MODE_DRAWING || demo->start.x == -1)
+        return FALSE;
+
+    demo->stop.x = event->x;
+    demo->stop.y = event->y;
+
+    poppler_page_get_size (demo->page, &width, &height);
+
+    /* Keep the drawing within the page */
+    demo->stop.x = CLAMP (demo->stop.x, 0, width);
+    demo->stop.y = CLAMP (demo->stop.y, 0, height);
+
+    rect.x1 = start.x = demo->start.x;
+    rect.y1 = start.y = height - demo->start.y;
+    rect.x2 = end.x = demo->stop.x;
+    rect.y2 = end.y = height - demo->stop.y;
+
+    poppler_annot_set_rectangle (demo->active_annot, &rect);
+
+    if (demo->annot_type == POPPLER_ANNOT_LINE)
+        poppler_annot_line_set_vertices (POPPLER_ANNOT_LINE (demo->active_annot),
+                                         &start, &end);
+
+    pgd_annot_view_set_annot (demo, demo->active_annot);
+    pgd_annots_viewer_queue_redraw (demo);
+
+    return TRUE;
+}
+
+static gboolean
+pgd_annots_drawing_area_button_release (GtkWidget      *area,
+                                        GdkEventButton *event,
+                                        PgdAnnotsDemo  *demo)
+{
+    if (!demo->page || demo->mode != MODE_DRAWING || event->button != 1)
+        return FALSE;
+
+    pgd_annots_finish_add_annot (demo);
+
+    return TRUE;
+}
+
+/* Main UI */
 GtkWidget *
 pgd_annots_create_widget (PopplerDocument *document)
 {
     PgdAnnotsDemo    *demo;
     GtkWidget        *label;
-    GtkWidget        *vbox;
-    GtkWidget        *hbox, *page_selector;
+    GtkWidget        *vbox, *vbox2;
     GtkWidget        *button;
+    GtkWidget        *hbox, *page_selector;
     GtkWidget        *hpaned;
     GtkWidget        *swindow, *treeview;
     GtkTreeSelection *selection;
     GtkCellRenderer  *renderer;
+    GtkTreeViewColumn *column;
+    GtkListStore     *model;
+    GtkTreeIter       iter;
     gchar            *str;
     gint              n_pages;
 
     demo = g_new0 (PgdAnnotsDemo, 1);
 
     demo->doc = g_object_ref (document);
+    demo->cursor = GDK_LAST_CURSOR;
+    demo->mode = MODE_NORMAL;
 
     n_pages = poppler_document_get_n_pages (document);
 
     vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
+    vbox2 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
 
     hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
 
@@ -873,25 +1089,79 @@
     gtk_widget_show (label);
     g_free (str);
 
-    button = gtk_button_new_with_label ("Get Annots");
+    demo->remove_button = gtk_button_new_from_stock (GTK_STOCK_REMOVE);
+    gtk_widget_set_sensitive (demo->remove_button, FALSE);
+    g_signal_connect (G_OBJECT (demo->remove_button), "clicked",
+                      G_CALLBACK (pgd_annots_remove_annot),
+                      (gpointer) demo);
+    gtk_box_pack_end (GTK_BOX (hbox), demo->remove_button, FALSE, FALSE, 6);
+    gtk_widget_show (demo->remove_button);
+
+    gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, TRUE, 0);
+
+    button = gtk_button_new_from_stock (GTK_STOCK_ADD);
     g_signal_connect (G_OBJECT (button), "clicked",
-                      G_CALLBACK (pgd_annots_get_annots),
+                      G_CALLBACK (pgd_annots_start_add_annot),
                       (gpointer) demo);
     gtk_box_pack_end (GTK_BOX (hbox), button, FALSE, FALSE, 0);
     gtk_widget_show (button);
 
-    button = gtk_button_new_with_label ("Add Annot");
-    g_signal_connect (G_OBJECT (button), "clicked",
-		      G_CALLBACK (pgd_annots_add_annot),
-		      (gpointer) demo);
-    gtk_box_pack_end (GTK_BOX (hbox), button, FALSE, FALSE, 0);
+    model = gtk_list_store_new(SELECTED_N_COLUMNS,
+                               G_TYPE_INT, G_TYPE_STRING);
+    gtk_list_store_append (model, &iter);
+    gtk_list_store_set (model, &iter,
+                        SELECTED_TYPE_COLUMN, POPPLER_ANNOT_TEXT,
+                        SELECTED_LABEL_COLUMN, "Text",
+                        -1);
+
+    gtk_list_store_append (model, &iter);
+    gtk_list_store_set (model, &iter,
+                        SELECTED_TYPE_COLUMN, POPPLER_ANNOT_LINE,
+                        SELECTED_LABEL_COLUMN, "Line",
+                        -1);
+
+    gtk_list_store_append (model, &iter);
+    gtk_list_store_set (model, &iter,
+                        SELECTED_TYPE_COLUMN, POPPLER_ANNOT_SQUARE,
+                        SELECTED_LABEL_COLUMN, "Square",
+                        -1);
+
+    gtk_list_store_append (model, &iter);
+    gtk_list_store_set (model, &iter,
+                        SELECTED_TYPE_COLUMN, POPPLER_ANNOT_CIRCLE,
+                        SELECTED_LABEL_COLUMN, "Circle",
+                        -1);
+
+    demo->type_selector = gtk_combo_box_new_with_model (GTK_TREE_MODEL (model));
+    g_object_unref (model);
+
+    renderer = gtk_cell_renderer_text_new ();
+    gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (demo->type_selector), renderer, TRUE);
+    gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (demo->type_selector), renderer,
+                                    "text", SELECTED_LABEL_COLUMN,
+                                    NULL);
+    gtk_combo_box_set_active (GTK_COMBO_BOX (demo->type_selector), 0);
+    gtk_box_pack_end (GTK_BOX (hbox), demo->type_selector, FALSE, FALSE, 0);
+    gtk_widget_show (demo->type_selector);
+
+    button = gtk_color_button_new ();
+    demo->annot_color.red = 65535;
+    demo->annot_color.alpha = 1.0;
+#if GTK_CHECK_VERSION(3,4,0)
+    gtk_color_chooser_set_rgba (GTK_COLOR_CHOOSER (button), &demo->annot_color);
+#else
+    gtk_color_button_set_rgba (GTK_COLOR_BUTTON (button), &demo->annot_color);
+#endif
+    g_signal_connect (button, "notify::color",
+                      G_CALLBACK (pgd_annot_color_changed),
+                      (gpointer)demo);
+    gtk_box_pack_end (GTK_BOX (hbox), button, FALSE, TRUE, 0);
     gtk_widget_show (button);
 
-    gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, TRUE, 0);
     gtk_widget_show (hbox);
 
     demo->timer_label = gtk_label_new (NULL);
-    gtk_label_set_markup (GTK_LABEL (demo->timer_label), "<i>No annots found</i>");
+    gtk_label_set_markup (GTK_LABEL (demo->timer_label), "<i>No annotations found</i>");
     g_object_set (G_OBJECT (demo->timer_label), "xalign", 1.0, NULL);
     gtk_box_pack_start (GTK_BOX (vbox), demo->timer_label, FALSE, TRUE, 0);
     gtk_widget_show (demo->timer_label);
@@ -906,52 +1176,23 @@
                                     GTK_POLICY_AUTOMATIC);
 
     demo->model = gtk_list_store_new (N_COLUMNS, G_TYPE_STRING,
-                                      G_TYPE_STRING, G_TYPE_STRING,
-                                      G_TYPE_STRING, G_TYPE_STRING,
                                       GDK_TYPE_PIXBUF, G_TYPE_BOOLEAN,
 				      G_TYPE_BOOLEAN, G_TYPE_BOOLEAN,
 				      G_TYPE_OBJECT);
     treeview = gtk_tree_view_new_with_model (GTK_TREE_MODEL (demo->model));
     demo->tree_view = treeview;
 
-    renderer = gtk_cell_renderer_text_new ();
-    gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (treeview),
-                                                 ANNOTS_X1_COLUMN, "X1",
-                                                 renderer,
-                                                 "text", ANNOTS_X1_COLUMN,
-                                                 NULL);
-    renderer = gtk_cell_renderer_text_new ();
-    gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (treeview),
-                                                 ANNOTS_Y1_COLUMN, "Y1",
-                                                 renderer,
-                                                 "text", ANNOTS_Y1_COLUMN,
-                                                 NULL);
-    renderer = gtk_cell_renderer_text_new ();
-    gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (treeview),
-                                                 ANNOTS_X2_COLUMN, "X2",
-                                                 renderer,
-                                                 "text", ANNOTS_X2_COLUMN,
-                                                 NULL);
-    renderer = gtk_cell_renderer_text_new ();
-    gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (treeview),
-                                                 ANNOTS_Y2_COLUMN, "Y2",
-                                                 renderer,
-                                                 "text", ANNOTS_Y2_COLUMN,
-                                                 NULL);
-
-    renderer = gtk_cell_renderer_text_new ();
-    gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (treeview),
-                                                 ANNOTS_TYPE_COLUMN, "Type",
-                                                 renderer,
-                                                 "text", ANNOTS_TYPE_COLUMN,
-                                                 NULL);
+    column = gtk_tree_view_column_new ();
+    gtk_tree_view_column_set_title (column, "Type");
+    gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);
 
     renderer = gtk_cell_renderer_pixbuf_new ();
-    gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (treeview),
-                                                 ANNOTS_COLOR_COLUMN, "Color",
-                                                 renderer,
-                                                 "pixbuf", ANNOTS_COLOR_COLUMN,
-                                                 NULL);
+    gtk_tree_view_column_pack_start (column, renderer, TRUE);
+    gtk_tree_view_column_add_attribute (column, renderer, "pixbuf", ANNOTS_COLOR_COLUMN);
+
+    renderer = gtk_cell_renderer_text_new ();
+    gtk_tree_view_column_pack_start (column, renderer, TRUE);
+    gtk_tree_view_column_add_attribute (column, renderer, "text", ANNOTS_TYPE_COLUMN);
 
     renderer = gtk_cell_renderer_toggle_new ();
     g_signal_connect (renderer, "toggled",
@@ -988,14 +1229,56 @@
                       G_CALLBACK (pgd_annots_selection_changed),
                       (gpointer) demo);
 
+    /* Annotation's list */
     gtk_container_add (GTK_CONTAINER (swindow), treeview);
     gtk_widget_show (treeview);
 
-    gtk_paned_add1 (GTK_PANED (hpaned), swindow);
+    gtk_box_pack_start (GTK_BOX (vbox2), swindow, TRUE, TRUE, 0);
     gtk_widget_show (swindow);
 
-    gtk_paned_add2 (GTK_PANED (hpaned), demo->annot_view);
+    /* Annotation Properties */
+    swindow = gtk_scrolled_window_new (NULL, NULL);
+    gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (swindow),
+                                    GTK_POLICY_AUTOMATIC,
+                                    GTK_POLICY_AUTOMATIC);
+    gtk_container_add (GTK_CONTAINER (swindow), demo->annot_view);
     gtk_widget_show (demo->annot_view);
+    gtk_widget_show (swindow);
+
+    gtk_box_pack_start (GTK_BOX (vbox2), swindow, TRUE, TRUE, 6);
+    gtk_widget_show (swindow);
+
+    gtk_paned_add1 (GTK_PANED (hpaned), vbox2);
+    gtk_widget_show (vbox2);
+
+    /* Demo Area (Render) */
+    demo->darea = gtk_drawing_area_new ();
+    g_signal_connect (demo->darea, "draw",
+                      G_CALLBACK (pgd_annots_view_drawing_area_draw),
+                      demo);
+    g_signal_connect (demo->darea, "realize",
+                      G_CALLBACK (pgd_annots_drawing_area_realize),
+                      (gpointer)demo);
+    g_signal_connect (demo->darea, "button_press_event",
+                      G_CALLBACK (pgd_annots_drawing_area_button_press),
+                      (gpointer)demo);
+    g_signal_connect (demo->darea, "motion_notify_event",
+                      G_CALLBACK (pgd_annots_drawing_area_motion_notify),
+                      (gpointer)demo);
+    g_signal_connect (demo->darea, "button_release_event",
+                      G_CALLBACK (pgd_annots_drawing_area_button_release),
+                      (gpointer)demo);
+
+    swindow = gtk_scrolled_window_new (NULL, NULL);
+#if GTK_CHECK_VERSION(3, 7, 8)
+    gtk_container_add(GTK_CONTAINER(swindow), demo->darea);
+#else
+    gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (swindow), demo->darea);
+#endif
+    gtk_widget_show (demo->darea);
+
+    gtk_paned_add2 (GTK_PANED (hpaned), swindow);
+    gtk_widget_show (swindow);
 
     gtk_paned_set_position (GTK_PANED (hpaned), 300);
 
@@ -1006,5 +1289,10 @@
                        (GWeakNotify)pgd_annots_free,
                        demo);
 
+    pgd_annots_viewer_queue_redraw (demo);
+    pgd_annots_get_annots (demo);
+
+    demo->main_box = vbox;
+
     return vbox;
 }
diff --git a/glib/demo/main.c b/glib/demo/main.c
index 2523800..3ba9b59 100644
--- a/glib/demo/main.c
+++ b/glib/demo/main.c
@@ -1,4 +1,4 @@
-/* 
+/*
  * Copyright (C) 2007 Carlos Garcia Campos  <carlosgc@gnome.org>
  *
  * This program is free software; you can redistribute it and/or modify
@@ -61,7 +61,7 @@
 	{ "Forms",            pgd_forms_create_widget },
 	{ "Page Transitions", pgd_transitions_create_widget },
 	{ "Images",           pgd_images_create_widget },
-	{ "Annots",           pgd_annots_create_widget },
+	{ "Annotations",      pgd_annots_create_widget },
 	{ "Attachments",      pgd_attachments_create_widget },
 	{ "Layers",           pgd_layers_create_widget },
 	{ "Text",             pgd_text_create_widget },
@@ -78,7 +78,7 @@
 
 	if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
 		gint n_page;
-		
+
 		gtk_tree_model_get (model, &iter,
 				    PGD_NPAGE_COLUMN, &n_page,
 				    -1);
@@ -106,7 +106,7 @@
 						     renderer,
 						     "text", PGD_TITLE_COLUMN,
 						     NULL);
-	
+
 	for (i = 0; i < G_N_ELEMENTS (demo_list); i++) {
 		GtkTreeIter iter;
 
@@ -118,12 +118,12 @@
 	}
 
 	g_object_unref (model);
-	
+
 	return treeview;
 }
 
 static GtkWidget *
-pdg_demo_notebook_create (PopplerDocument *document)
+pgd_demo_notebook_create (PopplerDocument *document)
 {
 	GtkWidget *notebook;
 	gint       i;
@@ -131,7 +131,7 @@
 	notebook = gtk_notebook_new ();
 	gtk_notebook_set_show_tabs (GTK_NOTEBOOK (notebook), FALSE);
 	gtk_notebook_set_show_border (GTK_NOTEBOOK (notebook), FALSE);
-	
+
 	for (i = 0; i < G_N_ELEMENTS (demo_list); i++) {
 		GtkWidget *demo_widget;
 
@@ -345,7 +345,7 @@
 
 	/* Main window */
 	win = gtk_window_new (GTK_WINDOW_TOPLEVEL);
-	gtk_window_set_default_size (GTK_WINDOW (win), 600, 600);
+	gtk_window_set_default_size (GTK_WINDOW (win), 1024, 768);
 	gtk_window_set_title (GTK_WINDOW (win), "Poppler GLib Demo");
 	g_signal_connect (G_OBJECT (win), "delete-event",
 			  G_CALLBACK (gtk_main_quit), NULL);
@@ -355,15 +355,15 @@
 	gtk_accel_group_connect (gtk_accel, gdk_keyval_from_name ("q"),
 				 GDK_CONTROL_MASK, 0, closure);
 	g_closure_unref (closure);
-	gtk_window_add_accel_group (GTK_WINDOW(win), gtk_accel);
+	gtk_window_add_accel_group (GTK_WINDOW (win), gtk_accel);
 
 	hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
 
 	treeview = pgd_demo_list_create ();
 	gtk_box_pack_start (GTK_BOX (hbox), treeview, FALSE, TRUE, 0);
 	gtk_widget_show (treeview);
-	
-	notebook = pdg_demo_notebook_create (document);
+
+	notebook = pgd_demo_notebook_create (document);
 	gtk_box_pack_start (GTK_BOX (hbox), notebook, TRUE, TRUE, 0);
 	gtk_widget_show (notebook);
 
@@ -374,12 +374,12 @@
 
 	gtk_container_add (GTK_CONTAINER (win), hbox);
 	gtk_widget_show (hbox);
-	
+
 	gtk_widget_show (win);
 
 	gtk_main ();
 
 	g_object_unref (document);
-	
+
 	return 0;
 }
diff --git a/glib/demo/text.c b/glib/demo/text.c
index af8428e..8af3ce3 100644
--- a/glib/demo/text.c
+++ b/glib/demo/text.c
@@ -38,6 +38,10 @@
 	GtkTextBuffer   *buffer;
         GtkWidget       *treeview;
 	GtkListStore    *model;
+        GtkWidget       *area_x1;
+        GtkWidget       *area_y1;
+        GtkWidget       *area_x2;
+        GtkWidget       *area_y2;
 
         /* Text attributes */
         GList           *text_attrs;
@@ -47,6 +51,7 @@
         GtkWidget       *text_color;
 
 	gint             page;
+        PopplerRectangle area;
 } PgdTextDemo;
 
 static void
@@ -99,7 +104,7 @@
         demo->text_attrs = NULL;
 
 	timer = g_timer_new ();
-	text = poppler_page_get_text (page);
+	text = poppler_page_get_text_for_area (page, &demo->area);
 	g_timer_stop (timer);
 
 	if (text) {
@@ -109,17 +114,17 @@
 		text_elapsed = g_timer_elapsed (timer, NULL);
 
 		g_timer_start (timer);
-		poppler_page_get_text_layout (page, &recs, &n_recs);
+		poppler_page_get_text_layout_for_area (page, &demo->area, &recs, &n_recs);
 		g_timer_stop (timer);
 
                 layout_elapsed = g_timer_elapsed (timer, NULL);
 
                 g_timer_start (timer);
-                demo->text_attrs = poppler_page_get_text_attributes (page);
+                demo->text_attrs = poppler_page_get_text_attributes_for_area (page, &demo->area);
                 g_timer_stop (timer);
 
-		str = g_strdup_printf ("<i>got text in %.4f seconds, text layout in %.4f seconds, text attrs in %.4f seconds</i>",
-				       text_elapsed, layout_elapsed, g_timer_elapsed (timer, NULL));
+		str = g_strdup_printf ("<i>got %ld chars in %.4f seconds, %u layout units in %.4f seconds, text attrs in %.4f seconds</i>",
+				       g_utf8_strlen(text, -1), text_elapsed, n_recs, layout_elapsed, g_timer_elapsed (timer, NULL));
 		gtk_label_set_markup (GTK_LABEL (demo->timer_label), str);
 		g_free (str);
 	} else {
@@ -273,6 +278,40 @@
         }
 }
 
+static void
+pgd_text_area_selector_setup (PgdTextDemo *demo)
+{
+        PopplerPage *page;
+        gdouble      width, height;
+
+        page = poppler_document_get_page (demo->doc, demo->page);
+        if (!page)
+                return;
+
+        poppler_page_get_size (page, &width, &height);
+
+        gtk_spin_button_set_range (GTK_SPIN_BUTTON (demo->area_x1), -10, width - 10);
+        gtk_spin_button_set_range (GTK_SPIN_BUTTON (demo->area_y1), -10, height - 10);
+        gtk_spin_button_set_range (GTK_SPIN_BUTTON (demo->area_x2), 0, width + 10);
+        gtk_spin_button_set_range (GTK_SPIN_BUTTON (demo->area_y2), 0, height + 10);
+
+        gtk_spin_button_set_value (GTK_SPIN_BUTTON (demo->area_x1), 0);
+        gtk_spin_button_set_value (GTK_SPIN_BUTTON (demo->area_y1), 0);
+        gtk_spin_button_set_value (GTK_SPIN_BUTTON (demo->area_x2), width);
+        gtk_spin_button_set_value (GTK_SPIN_BUTTON (demo->area_y2), height);
+
+        g_object_unref (page);
+}
+
+static void
+pgd_text_area_selector_value_changed (GtkSpinButton *spinbutton,
+                                      PgdTextDemo   *demo)
+{
+        demo->area.x1 = gtk_spin_button_get_value (GTK_SPIN_BUTTON (demo->area_x1));
+        demo->area.y1 = gtk_spin_button_get_value (GTK_SPIN_BUTTON (demo->area_y1));
+        demo->area.x2 = gtk_spin_button_get_value (GTK_SPIN_BUTTON (demo->area_x2));
+        demo->area.y2 = gtk_spin_button_get_value (GTK_SPIN_BUTTON (demo->area_y2));
+}
 
 static void
 pgd_text_page_selector_value_changed (GtkSpinButton *spinbutton,
@@ -287,7 +326,7 @@
 	PgdTextDemo      *demo;
 	GtkWidget        *label;
 	GtkWidget        *vbox, *vbox2;
-	GtkWidget        *hbox, *page_selector;
+	GtkWidget        *hbox, *page_selector, *area_hbox;
 	GtkWidget        *button;
 	GtkWidget        *swindow, *textview, *treeview;
 	GtkTreeSelection *selection;
@@ -326,6 +365,73 @@
 	gtk_widget_show (label);
 	g_free (str);
 
+        gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, TRUE, 0);
+	gtk_widget_show (hbox);
+
+        hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12);
+
+        area_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
+	label = gtk_label_new ("X1:");
+	gtk_box_pack_start (GTK_BOX (area_hbox), label, TRUE, TRUE, 0);
+	gtk_widget_show (label);
+
+	demo->area_x1 = gtk_spin_button_new_with_range (0, 0, 0.01);
+	g_signal_connect (demo->area_x1, "value-changed",
+			  G_CALLBACK (pgd_text_area_selector_value_changed),
+			  demo);
+	gtk_box_pack_start (GTK_BOX (area_hbox), demo->area_x1, TRUE, TRUE, 0);
+	gtk_widget_show (demo->area_x1);
+
+	gtk_box_pack_start (GTK_BOX (hbox), area_hbox, FALSE, TRUE, 0);
+	gtk_widget_show (area_hbox);
+
+        area_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
+	label = gtk_label_new ("Y1:");
+	gtk_box_pack_start (GTK_BOX (area_hbox), label, TRUE, TRUE, 0);
+	gtk_widget_show (label);
+
+	demo->area_y1 = gtk_spin_button_new_with_range (0, 0, 0.01);
+        g_signal_connect (demo->area_y1, "value-changed",
+			  G_CALLBACK (pgd_text_area_selector_value_changed),
+			  demo);
+	gtk_box_pack_start (GTK_BOX (area_hbox), demo->area_y1, TRUE, TRUE, 0);
+	gtk_widget_show (demo->area_y1);
+
+	gtk_box_pack_start (GTK_BOX (hbox), area_hbox, FALSE, TRUE, 0);
+	gtk_widget_show (area_hbox);
+
+        area_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
+	label = gtk_label_new ("X2:");
+	gtk_box_pack_start (GTK_BOX (area_hbox), label, TRUE, TRUE, 0);
+	gtk_widget_show (label);
+
+	demo->area_x2 = gtk_spin_button_new_with_range (0, 0, 0.01);
+        g_signal_connect (demo->area_x2, "value-changed",
+                          G_CALLBACK (pgd_text_area_selector_value_changed),
+                          demo);
+	gtk_box_pack_start (GTK_BOX (area_hbox), demo->area_x2, TRUE, TRUE, 0);
+	gtk_widget_show (demo->area_x2);
+
+	gtk_box_pack_start (GTK_BOX (hbox), area_hbox, FALSE, TRUE, 0);
+	gtk_widget_show (area_hbox);
+
+        area_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
+	label = gtk_label_new ("Y2:");
+	gtk_box_pack_start (GTK_BOX (area_hbox), label, TRUE, TRUE, 0);
+	gtk_widget_show (label);
+
+	demo->area_y2 = gtk_spin_button_new_with_range (0, 0, 0.01);
+        g_signal_connect (demo->area_y2, "value-changed",
+                          G_CALLBACK (pgd_text_area_selector_value_changed),
+                          demo);
+	gtk_box_pack_start (GTK_BOX (area_hbox), demo->area_y2, TRUE, TRUE, 0);
+	gtk_widget_show (demo->area_y2);
+
+	gtk_box_pack_start (GTK_BOX (hbox), area_hbox, FALSE, TRUE, 0);
+	gtk_widget_show (area_hbox);
+
+        pgd_text_area_selector_setup (demo);
+
 	button = gtk_button_new_with_label ("Get Text");
 	g_signal_connect (G_OBJECT (button), "clicked",
 			  G_CALLBACK (pgd_text_get_text),
diff --git a/glib/poppler-annot.cc b/glib/poppler-annot.cc
index 31cc081..6a1c05e 100644
--- a/glib/poppler-annot.cc
+++ b/glib/poppler-annot.cc
@@ -2,6 +2,7 @@
  *
  * Copyright (C) 2007 Inigo Martinez <inigomartinez@gmail.com>
  * Copyright (C) 2009 Carlos Garcia Campos <carlosgc@gnome.org>
+ * Copyright (C) 2013 German Poo-Caamano <gpoo@gnome.org>
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -35,6 +36,9 @@
 typedef struct _PopplerAnnotFileAttachmentClass PopplerAnnotFileAttachmentClass;
 typedef struct _PopplerAnnotMovieClass          PopplerAnnotMovieClass;
 typedef struct _PopplerAnnotScreenClass         PopplerAnnotScreenClass;
+typedef struct _PopplerAnnotLineClass           PopplerAnnotLineClass;
+typedef struct _PopplerAnnotCircleClass         PopplerAnnotCircleClass;
+typedef struct _PopplerAnnotSquareClass         PopplerAnnotSquareClass;
 
 struct _PopplerAnnotClass
 {
@@ -105,6 +109,35 @@
   PopplerAnnotClass parent_class;
 };
 
+struct _PopplerAnnotLine
+{
+  PopplerAnnotMarkup parent_instance;
+};
+
+struct _PopplerAnnotLineClass
+{
+  PopplerAnnotMarkupClass parent_class;
+};
+
+struct _PopplerAnnotCircle
+{
+  PopplerAnnotMarkup parent_instance;
+};
+
+struct _PopplerAnnotCircleClass
+{
+  PopplerAnnotMarkupClass parent_class;
+};
+
+struct _PopplerAnnotSquare
+{
+  PopplerAnnotMarkup parent_instance;
+};
+
+struct _PopplerAnnotSquareClass
+{
+  PopplerAnnotMarkupClass parent_class;
+};
 
 G_DEFINE_TYPE (PopplerAnnot, poppler_annot, G_TYPE_OBJECT)
 G_DEFINE_TYPE (PopplerAnnotMarkup, poppler_annot_markup, POPPLER_TYPE_ANNOT)
@@ -113,6 +146,9 @@
 G_DEFINE_TYPE (PopplerAnnotFileAttachment, poppler_annot_file_attachment, POPPLER_TYPE_ANNOT_MARKUP)
 G_DEFINE_TYPE (PopplerAnnotMovie, poppler_annot_movie, POPPLER_TYPE_ANNOT)
 G_DEFINE_TYPE (PopplerAnnotScreen, poppler_annot_screen, POPPLER_TYPE_ANNOT)
+G_DEFINE_TYPE (PopplerAnnotLine, poppler_annot_line, POPPLER_TYPE_ANNOT_MARKUP)
+G_DEFINE_TYPE (PopplerAnnotCircle, poppler_annot_circle, POPPLER_TYPE_ANNOT_MARKUP)
+G_DEFINE_TYPE (PopplerAnnotSquare, poppler_annot_square, POPPLER_TYPE_ANNOT_MARKUP)
 
 static PopplerAnnot *
 _poppler_create_annot (GType annot_type, Annot *annot)
@@ -324,6 +360,131 @@
   return poppler_annot;
 }
 
+PopplerAnnot *
+_poppler_annot_line_new (Annot *annot)
+{
+  return _poppler_create_annot (POPPLER_TYPE_ANNOT_LINE, annot);
+}
+
+static void
+poppler_annot_line_init (PopplerAnnotLine *poppler_annot)
+{
+}
+
+static void
+poppler_annot_line_class_init (PopplerAnnotLineClass *klass)
+{
+}
+
+/**
+ * poppler_annot_line_new:
+ * @doc: a #PopplerDocument
+ * @rect: a #PopplerRectangle
+ *
+ * Creates a new Line annotation that will be
+ * located on @rect when added to a page. See
+ * poppler_page_add_annot()
+ *
+ * Return value: A newly created #PopplerAnnotLine annotation
+ *
+ * Since: 0.26
+ */
+PopplerAnnot *
+poppler_annot_line_new (PopplerDocument  *doc,
+			PopplerRectangle *rect)
+{
+  Annot *annot;
+  PDFRectangle pdf_rect(rect->x1, rect->y1,
+			rect->x2, rect->y2);
+
+  annot = new AnnotLine (doc->doc, &pdf_rect);
+
+  return _poppler_annot_line_new (annot);
+}
+
+PopplerAnnot *
+_poppler_annot_circle_new (Annot *annot)
+{
+  return _poppler_create_annot (POPPLER_TYPE_ANNOT_CIRCLE, annot);
+}
+
+static void
+poppler_annot_circle_init (PopplerAnnotCircle *poppler_annot)
+{
+}
+
+static void
+poppler_annot_circle_class_init (PopplerAnnotCircleClass *klass)
+{
+}
+
+/**
+ * poppler_annot_circle_new:
+ * @doc: a #PopplerDocument
+ * @rect: a #PopplerRectangle
+ *
+ * Creates a new Circle annotation that will be
+ * located on @rect when added to a page. See
+ * poppler_page_add_annot()
+ *
+ * Return value: a newly created #PopplerAnnotCircle annotation
+ *
+ * Since: 0.26
+ **/
+PopplerAnnot *
+poppler_annot_circle_new (PopplerDocument  *doc,
+			  PopplerRectangle *rect)
+{
+  Annot *annot;
+  PDFRectangle pdf_rect(rect->x1, rect->y1,
+			rect->x2, rect->y2);
+
+  annot = new AnnotGeometry (doc->doc, &pdf_rect, Annot::typeCircle);
+
+  return _poppler_annot_circle_new (annot);
+}
+
+PopplerAnnot *
+_poppler_annot_square_new (Annot *annot)
+{
+  return _poppler_create_annot (POPPLER_TYPE_ANNOT_SQUARE, annot);
+}
+
+static void
+poppler_annot_square_init (PopplerAnnotSquare *poppler_annot)
+{
+}
+
+static void
+poppler_annot_square_class_init (PopplerAnnotSquareClass *klass)
+{
+}
+
+/**
+ * poppler_annot_square_new:
+ * @doc: a #PopplerDocument
+ * @rect: a #PopplerRectangle
+ *
+ * Creates a new Square annotation that will be
+ * located on @rect when added to a page. See
+ * poppler_page_add_annot()
+ *
+ * Return value: a newly created #PopplerAnnotSquare annotation
+ *
+ * Since: 0.26
+**/
+PopplerAnnot *
+poppler_annot_square_new (PopplerDocument  *doc,
+			  PopplerRectangle *rect)
+{
+  Annot *annot;
+  PDFRectangle pdf_rect(rect->x1, rect->y1,
+			rect->x2, rect->y2);
+
+  annot = new AnnotGeometry (doc->doc, &pdf_rect, Annot::typeSquare);
+
+  return _poppler_annot_square_new (annot);
+}
 
 /* Public methods */
 /**
@@ -528,26 +689,11 @@
   poppler_annot->annot->setFlags ((guint) flags);
 }
 
-
-/**
- * poppler_annot_get_color:
- * @poppler_annot: a #PopplerAnnot
- *
- * Retrieves the color of @poppler_annot.
- *
- * Return value: a new allocated #PopplerColor with the color values of
- *               @poppler_annot, or %NULL. It must be freed with g_free() when done.
- **/
-PopplerColor *
-poppler_annot_get_color (PopplerAnnot *poppler_annot)
+static PopplerColor *
+create_poppler_color_from_annot_color (AnnotColor *color)
 {
-  AnnotColor *color;
   PopplerColor *poppler_color = NULL;
 
-  g_return_val_if_fail (POPPLER_IS_ANNOT (poppler_annot), NULL);
-
-  color = poppler_annot->annot->getColor ();
-
   if (color) {
     const double *values = color->getValues ();
 
@@ -579,6 +725,34 @@
   return poppler_color;
 }
 
+static AnnotColor *
+create_annot_color_from_poppler_color (PopplerColor *poppler_color)
+{
+  if (!poppler_color)
+    return NULL;
+
+  return new AnnotColor ((double)poppler_color->red / 65535,
+                         (double)poppler_color->green / 65535,
+                         (double)poppler_color->blue / 65535);
+}
+
+/**
+ * poppler_annot_get_color:
+ * @poppler_annot: a #PopplerAnnot
+ *
+ * Retrieves the color of @poppler_annot.
+ *
+ * Return value: a new allocated #PopplerColor with the color values of
+ *               @poppler_annot, or %NULL. It must be freed with g_free() when done.
+ **/
+PopplerColor *
+poppler_annot_get_color (PopplerAnnot *poppler_annot)
+{
+  g_return_val_if_fail (POPPLER_IS_ANNOT (poppler_annot), NULL);
+
+  return create_poppler_color_from_annot_color (poppler_annot->annot->getColor ());
+}
+
 /**
  * poppler_annot_set_color:
  * @poppler_annot: a #PopplerAnnot
@@ -592,16 +766,8 @@
 poppler_annot_set_color (PopplerAnnot *poppler_annot,
 			 PopplerColor *poppler_color)
 {
-  AnnotColor *color = NULL;
-
-  if (poppler_color) {
-    color = new AnnotColor ((double)poppler_color->red / 65535,
-			    (double)poppler_color->green / 65535,
-			    (double)poppler_color->blue / 65535);
-  }
-
   /* Annot takes ownership of the color */
-  poppler_annot->annot->setColor (color);
+  poppler_annot->annot->setColor (create_annot_color_from_poppler_color (poppler_color));
 }
 
 /**
@@ -625,6 +791,53 @@
   return page_num <= 0 ? -1 : page_num - 1;
 }
 
+/**
+ * poppler_annot_get_rectangle:
+ * @poppler_annot: a #PopplerAnnot
+ * @poppler_rect: (out): a #PopplerRectangle to store the annotation's coordinates
+ *
+ * Retrieves the rectangle representing the page coordinates where the
+ * annotation @poppler_annot is placed.
+ *
+ * Since: 0.26
+ */
+void
+poppler_annot_get_rectangle (PopplerAnnot     *poppler_annot,
+                             PopplerRectangle *poppler_rect)
+{
+  PDFRectangle *annot_rect;
+
+  g_return_if_fail (POPPLER_IS_ANNOT (poppler_annot));
+  g_return_if_fail (poppler_rect != NULL);
+
+  annot_rect = poppler_annot->annot->getRect ();
+  poppler_rect->x1 = annot_rect->x1;
+  poppler_rect->x2 = annot_rect->x2;
+  poppler_rect->y1 = annot_rect->y1;
+  poppler_rect->y2 = annot_rect->y2;
+}
+
+/**
+ * poppler_annot_set_rectangle:
+ * @poppler_annot: a #PopplerAnnot
+ * @poppler_rect: a #PopplerRectangle with the new annotation's coordinates
+ *
+ * Move the annotation to the rectangle representing the page coordinates
+ * where the annotation @poppler_annot should be placed.
+ *
+ * Since: 0.26
+ */
+void
+poppler_annot_set_rectangle (PopplerAnnot     *poppler_annot,
+                             PopplerRectangle *poppler_rect)
+{
+  g_return_if_fail (POPPLER_IS_ANNOT (poppler_annot));
+  g_return_if_fail (poppler_rect != NULL);
+
+  poppler_annot->annot->setRect (poppler_rect->x1, poppler_rect->y1,
+                                 poppler_rect->x2, poppler_rect->y2);
+}
+
 /* PopplerAnnotMarkup */
 /**
  * poppler_annot_markup_get_label:
@@ -1384,3 +1597,128 @@
 {
   return poppler_annot->action;
 }
+
+/* PopplerAnnotLine */
+/**
+ * poppler_annot_line_set_vertices:
+ * @poppler_annot: a #PopplerAnnotLine
+ * @start: a #PopplerPoint of the starting vertice
+ * @end: a #PopplerPoint of the ending vertice
+ *
+ * Set the coordinate points where the @poppler_annot starts and ends.
+ *
+ * Since: 0.26
+ */
+void
+poppler_annot_line_set_vertices (PopplerAnnotLine *poppler_annot,
+				 PopplerPoint     *start,
+				 PopplerPoint     *end)
+{
+  AnnotLine *annot;
+
+  g_return_if_fail (POPPLER_IS_ANNOT_LINE (poppler_annot));
+  g_return_if_fail (start != NULL);
+  g_return_if_fail (end != NULL);
+
+  annot = static_cast<AnnotLine *>(POPPLER_ANNOT (poppler_annot)->annot);
+  annot->setVertices (start->x, start->y, end->x, end->y);
+}
+
+/* PopplerAnnotCircle and PopplerAnnotSquare helpers */
+static PopplerColor *
+poppler_annot_geometry_get_interior_color (PopplerAnnot *poppler_annot)
+{
+  AnnotGeometry *annot;
+
+  annot = static_cast<AnnotGeometry *>(POPPLER_ANNOT (poppler_annot)->annot);
+
+  return create_poppler_color_from_annot_color (annot->getInteriorColor ());
+}
+
+static void
+poppler_annot_geometry_set_interior_color (PopplerAnnot *poppler_annot,
+					    PopplerColor *poppler_color)
+{
+  AnnotGeometry *annot;
+
+  annot = static_cast<AnnotGeometry *>(POPPLER_ANNOT (poppler_annot)->annot);
+
+  /* Annot takes ownership of the color */
+  annot->setInteriorColor (create_annot_color_from_poppler_color (poppler_color));
+}
+
+/* PopplerAnnotCircle */
+/**
+ * poppler_annot_circle_get_interior_color:
+ * @poppler_annot: a #PopplerAnnotCircle
+ *
+ * Retrieves the interior color of @poppler_annot.
+ *
+ * Return value: a new allocated #PopplerColor with the color values of
+ *               @poppler_annot, or %NULL. It must be freed with g_free() when done.
+ *
+ * Since: 0.26
+ */
+PopplerColor *
+poppler_annot_circle_get_interior_color (PopplerAnnotCircle *poppler_annot)
+{
+  g_return_val_if_fail (POPPLER_IS_ANNOT_CIRCLE (poppler_annot), NULL);
+
+  return poppler_annot_geometry_get_interior_color (POPPLER_ANNOT (poppler_annot));
+}
+
+/**
+ * poppler_annot_circle_set_interior_color:
+ * @poppler_annot: a #PopplerAnnotCircle
+ * @poppler_color: (allow-none): a #PopplerColor, or %NULL
+ *
+ * Sets the interior color of @poppler_annot.
+ *
+ * Since: 0.26
+ */
+void
+poppler_annot_circle_set_interior_color (PopplerAnnotCircle *poppler_annot,
+					 PopplerColor       *poppler_color)
+{
+  g_return_if_fail (POPPLER_IS_ANNOT_CIRCLE (poppler_annot));
+
+  poppler_annot_geometry_set_interior_color (POPPLER_ANNOT (poppler_annot), poppler_color);
+}
+
+/* PopplerAnnotSquare */
+/**
+ * poppler_annot_square_get_interior_color:
+ * @poppler_annot: a #PopplerAnnotSquare
+ *
+ * Retrieves the interior color of @poppler_annot.
+ *
+ * Return value: a new allocated #PopplerColor with the color values of
+ *               @poppler_annot, or %NULL. It must be freed with g_free() when done.
+ *
+ * Since: 0.26
+ */
+PopplerColor *
+poppler_annot_square_get_interior_color (PopplerAnnotSquare *poppler_annot)
+{
+  g_return_val_if_fail (POPPLER_IS_ANNOT_SQUARE (poppler_annot), NULL);
+
+  return poppler_annot_geometry_get_interior_color (POPPLER_ANNOT (poppler_annot));
+}
+
+/**
+ * poppler_annot_square_set_interior_color:
+ * @poppler_annot: a #PopplerAnnotSquare
+ * @poppler_color: (allow-none): a #PopplerColor, or %NULL
+ *
+ * Sets the interior color of @poppler_annot.
+ *
+ * Since: 0.26
+ */
+void
+poppler_annot_square_set_interior_color (PopplerAnnotSquare *poppler_annot,
+					 PopplerColor       *poppler_color)
+{
+  g_return_if_fail (POPPLER_IS_ANNOT_SQUARE (poppler_annot));
+
+  poppler_annot_geometry_set_interior_color (POPPLER_ANNOT (poppler_annot), poppler_color);
+}
diff --git a/glib/poppler-annot.h b/glib/poppler-annot.h
index 88f4e46..300e815 100644
--- a/glib/poppler-annot.h
+++ b/glib/poppler-annot.h
@@ -54,8 +54,19 @@
 #define POPPLER_ANNOT_SCREEN(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), POPPLER_TYPE_ANNOT_SCREEN, PopplerAnnotScreen))
 #define POPPLER_IS_ANNOT_SCREEN(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), POPPLER_TYPE_ANNOT_SCREEN))
 
+#define POPPLER_TYPE_ANNOT_LINE              (poppler_annot_line_get_type ())
+#define POPPLER_ANNOT_LINE(obj)              (G_TYPE_CHECK_INSTANCE_CAST ((obj), POPPLER_TYPE_ANNOT_LINE, PopplerAnnotLine))
+#define POPPLER_IS_ANNOT_LINE(obj)           (G_TYPE_CHECK_INSTANCE_TYPE ((obj), POPPLER_TYPE_ANNOT_LINE))
+
 #define POPPLER_TYPE_ANNOT_CALLOUT_LINE      (poppler_annot_callout_line_get_type ())
 
+#define POPPLER_TYPE_ANNOT_CIRCLE            (poppler_annot_circle_get_type ())
+#define POPPLER_ANNOT_CIRCLE(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), POPPLER_TYPE_ANNOT_CIRCLE, PopplerAnnotCircle))
+#define POPPLER_IS_ANNOT_CIRCLE(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), POPPLER_TYPE_ANNOT_CIRCLE))
+
+#define POPPLER_TYPE_ANNOT_SQUARE            (poppler_annot_square_get_type ())
+#define POPPLER_ANNOT_SQUARE(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), POPPLER_TYPE_ANNOT_SQUARE, PopplerAnnotSquare))
+#define POPPLER_IS_ANNOT_SQUARE(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), POPPLER_TYPE_ANNOT_SQUARE))
 
 typedef enum
 {
@@ -168,6 +179,10 @@
 void                          poppler_annot_set_color                          (PopplerAnnot *poppler_annot,
 										PopplerColor *poppler_color);
 gint                          poppler_annot_get_page_index                     (PopplerAnnot *poppler_annot);
+void                          poppler_annot_get_rectangle                      (PopplerAnnot     *poppler_annot,
+										PopplerRectangle *poppler_rect);
+void                          poppler_annot_set_rectangle                      (PopplerAnnot     *poppler_annot,
+										PopplerRectangle *poppler_rect);
 
 /* PopplerAnnotMarkup */
 GType                         poppler_annot_markup_get_type                    (void) G_GNUC_CONST;
@@ -221,12 +236,36 @@
 GType                         poppler_annot_screen_get_type                    (void) G_GNUC_CONST;
 PopplerAction                *poppler_annot_screen_get_action                  (PopplerAnnotScreen *poppler_annot);
 
+/* PopplerAnnotLine */
+GType                         poppler_annot_line_get_type                      (void) G_GNUC_CONST;
+PopplerAnnot                 *poppler_annot_line_new                           (PopplerDocument  *doc,
+                                                                                PopplerRectangle *rect);
+void                          poppler_annot_line_set_vertices                  (PopplerAnnotLine *poppler_annot,
+										PopplerPoint     *start,
+										PopplerPoint     *end);
+
 /* PopplerAnnotCalloutLine */
 GType                         poppler_annot_callout_line_get_type              (void) G_GNUC_CONST;
 PopplerAnnotCalloutLine      *poppler_annot_callout_line_new                   (void);
 PopplerAnnotCalloutLine      *poppler_annot_callout_line_copy                  (PopplerAnnotCalloutLine *callout);
 void                          poppler_annot_callout_line_free                  (PopplerAnnotCalloutLine *callout);
 
+/* PopplerAnnotCircle */
+GType                         poppler_annot_circle_get_type                    (void) G_GNUC_CONST;
+PopplerAnnot                 *poppler_annot_circle_new                         (PopplerDocument    *doc,
+                                                                                PopplerRectangle   *rect);
+void                          poppler_annot_circle_set_interior_color          (PopplerAnnotCircle *poppler_annot,
+										PopplerColor       *poppler_color);
+PopplerColor                 *poppler_annot_circle_get_interior_color          (PopplerAnnotCircle *poppler_annot);
+
+/* PopplerAnnotGeometry */
+GType                         poppler_annot_square_get_type                    (void) G_GNUC_CONST;
+PopplerAnnot                 *poppler_annot_square_new                         (PopplerDocument    *doc,
+                                                                                PopplerRectangle   *rect);
+void                          poppler_annot_square_set_interior_color          (PopplerAnnotSquare *poppler_annot,
+										PopplerColor       *poppler_color);
+PopplerColor                 *poppler_annot_square_get_interior_color          (PopplerAnnotSquare *poppler_annot);
+
 G_END_DECLS
 
 #endif /* __POPPLER_ANNOT_H__ */
diff --git a/glib/poppler-page.cc b/glib/poppler-page.cc
index 9115b78..a21be0f 100644
--- a/glib/poppler-page.cc
+++ b/glib/poppler-page.cc
@@ -851,6 +851,28 @@
 }
 
 /**
+ * poppler_page_get_text_for_area:
+ * @page: a #PopplerPage
+ * @area: a #PopplerRectangle
+ *
+ * Retrieves the text of @page contained in @area.
+ *
+ * Return value: a pointer to the text as a string
+ *
+ * Since: 0.26
+ **/
+char *
+poppler_page_get_text_for_area (PopplerPage      *page,
+                                PopplerRectangle *area)
+{
+  g_return_val_if_fail (POPPLER_IS_PAGE (page), NULL);
+  g_return_val_if_fail (area != NULL, NULL);
+
+  return poppler_page_get_selected_text (page, POPPLER_SELECTION_GLYPH, area);
+}
+
+
+/**
  * poppler_page_find_text_with_options:
  * @page: a #PopplerPage
  * @text: the text to search for (UTF-8 encoded)
@@ -1375,6 +1397,15 @@
       case Annot::typeScreen:
         mapping->annot = _poppler_annot_screen_new (annot);
 	break;
+      case Annot::typeLine:
+        mapping->annot = _poppler_annot_line_new (annot);
+	break;
+      case Annot::typeSquare:
+        mapping->annot = _poppler_annot_square_new (annot);
+	break;
+      case Annot::typeCircle:
+        mapping->annot = _poppler_annot_circle_new (annot);
+	break;
       default:
         mapping->annot = _poppler_annot_new (annot);
 	break;
@@ -1527,6 +1558,60 @@
   g_slice_free (PopplerRectangle, rectangle);
 }
 
+/* PopplerPoint type */
+
+POPPLER_DEFINE_BOXED_TYPE (PopplerPoint, poppler_point,
+                           poppler_point_copy,
+                           poppler_point_free)
+
+/**
+ * poppler_point_new:
+ *
+ * Creates a new #PopplerPoint. It must be freed with poppler_point_free() after use.
+ *
+ * Returns: a new #PopplerPoint
+ *
+ * Since: 0.26
+ **/
+PopplerPoint *
+poppler_point_new (void)
+{
+  return g_slice_new0 (PopplerPoint);
+}
+
+/**
+ * poppler_point_copy:
+ * @point: a #PopplerPoint to copy
+ *
+ * Creates a copy of @point. The copy must be freed with poppler_point_free()
+ * after use.
+ *
+ * Returns: a new allocated copy of @point
+ *
+ * Since: 0.26
+ **/
+PopplerPoint *
+poppler_point_copy (PopplerPoint *point)
+{
+  g_return_val_if_fail (point != NULL, NULL);
+
+  return g_slice_dup (PopplerPoint, point);
+}
+
+/**
+ * poppler_point_free:
+ * @point: a #PopplerPoint
+ *
+ * Frees the memory used by @point
+ *
+ * Since: 0.26
+ **/
+void
+poppler_point_free (PopplerPoint *point)
+{
+  g_slice_free (PopplerPoint, point);
+}
+
 /* PopplerTextAttributes type */
 
 POPPLER_DEFINE_BOXED_TYPE (PopplerTextAttributes, poppler_text_attributes,
@@ -1961,14 +2046,16 @@
  * poppler_page_get_text_layout:
  * @page: A #PopplerPage
  * @rectangles: (out) (array length=n_rectangles) (transfer container): return location for an array of #PopplerRectangle
- * @n_rectangles: (out) length of returned array
+ * @n_rectangles: (out): length of returned array
  *
  * Obtains the layout of the text as a list of #PopplerRectangle
- * This array must be freed with g_free () when done.
+ * This array must be freed with g_free() when done.
  *
  * The position in the array represents an offset in the text returned by
  * poppler_page_get_text()
  *
+ * See also poppler_page_get_text_layout_for_area().
+ *
  * Return value: %TRUE if the page contains text, %FALSE otherwise
  *
  * Since: 0.16
@@ -1978,6 +2065,38 @@
                               PopplerRectangle **rectangles,
                               guint             *n_rectangles)
 {
+  PopplerRectangle selection = {0, 0, 0, 0};
+
+  g_return_val_if_fail (POPPLER_IS_PAGE (page), FALSE);
+
+  poppler_page_get_size (page, &selection.x2, &selection.y2);
+
+  return poppler_page_get_text_layout_for_area (page, &selection, rectangles, n_rectangles);
+}
+
+/**
+ * poppler_page_get_text_layout_for_area:
+ * @page: A #PopplerPage
+ * @area: a #PopplerRectangle
+ * @rectangles: (out) (array length=n_rectangles) (transfer container): return location for an array of #PopplerRectangle
+ * @n_rectangles: (out): length of returned array
+ *
+ * Obtains the layout of the text contained in @area as a list of #PopplerRectangle
+ * This array must be freed with g_free() when done.
+ *
+ * The position in the array represents an offset in the text returned by
+ * poppler_page_get_text_for_area()
+ *
+ * Return value: %TRUE if the page contains text, %FALSE otherwise
+ *
+ * Since: 0.26
+ **/
+gboolean
+poppler_page_get_text_layout_for_area (PopplerPage       *page,
+                                       PopplerRectangle  *area,
+                                       PopplerRectangle **rectangles,
+                                       guint             *n_rectangles)
+{
   TextPage *text;
   PopplerRectangle *rect;
   PDFRectangle selection;
@@ -1990,10 +2109,15 @@
   int n_lines;
 
   g_return_val_if_fail (POPPLER_IS_PAGE (page), FALSE);
+  g_return_val_if_fail (area != NULL, FALSE);
 
   *n_rectangles = 0;
 
-  poppler_page_get_size (page, &selection.x2, &selection.y2);
+  selection.x1 = area->x1;
+  selection.y1 = area->y1;
+  selection.x2 = area->x2;
+  selection.y2 = area->y2;
+
   text = poppler_page_get_text_page (page);
   word_list = text->getSelectionWords (&selection, selectionStyleGlyph, &n_lines);
   if (!word_list)
@@ -2006,8 +2130,8 @@
       n_rects += line_words->getLength() - 1;
       for (j = 0; j < line_words->getLength(); j++)
         {
-          TextWord *word = (TextWord *)line_words->get(j);
-          n_rects += word->getLength();
+          TextWordSelection *word_sel = (TextWordSelection *)line_words->get(j);
+          n_rects += word_sel->getEnd() - word_sel->getBegin();
         }
     }
 
@@ -2019,8 +2143,11 @@
       GooList *line_words = word_list[i];
       for (j = 0; j < line_words->getLength(); j++)
         {
-          TextWord *word = (TextWord *)line_words->get(j);
-          for (k = 0; k < word->getLength(); k++)
+          TextWordSelection *word_sel = (TextWordSelection *)line_words->get(j);
+          TextWord *word = word_sel->getWord();
+          int end = word_sel->getEnd();
+
+          for (k = word_sel->getBegin(); k < end; k++)
             {
               rect = *rectangles + offset;
               word->getCharBBox (k,
@@ -2036,9 +2163,9 @@
 
           if (j < line_words->getLength() - 1)
             {
-              TextWord *next_word = (TextWord *)line_words->get(j + 1);
+              TextWordSelection *word_sel = (TextWordSelection *)line_words->get(j + 1);
 
-              next_word->getBBox(&x3, &y3, &x4, &y4);
+              word_sel->getWord()->getBBox(&x3, &y3, &x4, &y4);
 	      // space is from one word to other and with the same height as
 	      // first word.
 	      rect->x1 = x2;
@@ -2110,13 +2237,15 @@
  * poppler_page_get_text_attributes:
  * @page: A #PopplerPage
  *
- * Obtains the attributes of the text as a GList of #PopplerTextAttributes.
+ * Obtains the attributes of the text as a #GList of #PopplerTextAttributes.
  * This list must be freed with poppler_page_free_text_attributes() when done.
  *
  * Each list element is a #PopplerTextAttributes struct where start_index and
  * end_index indicates the range of text (as returned by poppler_page_get_text())
  * to which text attributes apply.
  *
+ * See also poppler_page_get_text_attributes_for_area()
+ *
  * Return value: (element-type PopplerTextAttributes) (transfer full): A #GList of #PopplerTextAttributes
  *
  * Since: 0.18
@@ -2124,6 +2253,35 @@
 GList *
 poppler_page_get_text_attributes (PopplerPage *page)
 {
+  PopplerRectangle selection = {0, 0, 0, 0};
+
+  g_return_val_if_fail (POPPLER_IS_PAGE (page), NULL);
+
+  poppler_page_get_size (page, &selection.x2, &selection.y2);
+
+  return poppler_page_get_text_attributes_for_area (page, &selection);
+}
+
+/**
+ * poppler_page_get_text_attributes_for_area:
+ * @page: A #PopplerPage
+ * @area: a #PopplerRectangle
+ *
+ * Obtains the attributes of the text in @area as a #GList of #PopplerTextAttributes.
+ * This list must be freed with poppler_page_free_text_attributes() when done.
+ *
+ * Each list element is a #PopplerTextAttributes struct where start_index and
+ * end_index indicates the range of text (as returned by poppler_page_get_text_for_area())
+ * to which text attributes apply.
+ *
+ * Return value: (element-type PopplerTextAttributes) (transfer full): A #GList of #PopplerTextAttributes
+ *
+ * Since: 0.26
+ **/
+GList *
+poppler_page_get_text_attributes_for_area (PopplerPage      *page,
+                                           PopplerRectangle *area)
+{
   TextPage *text;
   PDFRectangle selection;
   GooList **word_list;
@@ -2136,8 +2294,13 @@
   GList *attributes = NULL;
 
   g_return_val_if_fail (POPPLER_IS_PAGE (page), NULL);
+  g_return_val_if_fail (area != NULL, FALSE);
 
-  poppler_page_get_size (page, &selection.x2, &selection.y2);
+  selection.x1 = area->x1;
+  selection.y1 = area->y1;
+  selection.x2 = area->x2;
+  selection.y2 = area->y2;
+
   text = poppler_page_get_text_page (page);
   word_list = text->getSelectionWords (&selection, selectionStyleGlyph, &n_lines);
   if (!word_list)
@@ -2148,9 +2311,12 @@
       GooList *line_words = word_list[i];
       for (j = 0; j < line_words->getLength(); j++)
         {
-          word = (TextWord *)line_words->get(j);
+          TextWordSelection *word_sel = (TextWordSelection *)line_words->get(j);
+          int end = word_sel->getEnd();
 
-          for (word_i = 0; word_i < word->getLength (); word_i++)
+          word = word_sel->getWord();
+
+          for (word_i = word_sel->getBegin(); word_i < end; word_i++)
             {
               if (!prev_word || !word_text_attributes_equal (word, word_i, prev_word, prev_word_i))
                 {
diff --git a/glib/poppler-page.h b/glib/poppler-page.h
index c081b8c..68e2a1d 100644
--- a/glib/poppler-page.h
+++ b/glib/poppler-page.h
@@ -68,6 +68,8 @@
 void                   poppler_page_render_to_ps         (PopplerPage        *page,
 							  PopplerPSFile      *ps_file);
 char                  *poppler_page_get_text             (PopplerPage        *page);
+char                  *poppler_page_get_text_for_area    (PopplerPage        *page,
+                                                          PopplerRectangle   *area);
 char                  *poppler_page_get_selected_text    (PopplerPage        *page,
 							  PopplerSelectionStyle style,
 							  PopplerRectangle   *selection);
@@ -99,8 +101,14 @@
 gboolean               poppler_page_get_text_layout      (PopplerPage        *page,
                                                           PopplerRectangle  **rectangles,
                                                           guint              *n_rectangles);
+gboolean           poppler_page_get_text_layout_for_area (PopplerPage        *page,
+                                                          PopplerRectangle   *area,
+                                                          PopplerRectangle  **rectangles,
+                                                          guint              *n_rectangles);
 GList                 *poppler_page_get_text_attributes  (PopplerPage        *page);
 void                   poppler_page_free_text_attributes (GList              *list);
+GList *        poppler_page_get_text_attributes_for_area (PopplerPage        *page,
+                                                          PopplerRectangle   *area);
 
 /* A rectangle on a page, with coordinates in PDF points. */
 #define POPPLER_TYPE_RECTANGLE             (poppler_rectangle_get_type ())
@@ -127,6 +135,26 @@
 PopplerRectangle *poppler_rectangle_copy     (PopplerRectangle *rectangle);
 void              poppler_rectangle_free     (PopplerRectangle *rectangle);
 
+/* A point on a page, with coordinates in PDF points. */
+#define POPPLER_TYPE_POINT             (poppler_point_get_type ())
+/**
+ * PopplerPoint:
+ * @x: x coordinate
+ * @y: y coordinate
+ *
+ * A #PopplerPoint is used to describe a location point on a page
+ */
+struct _PopplerPoint
+{
+  gdouble x;
+  gdouble y;
+};
+
+GType             poppler_point_get_type (void) G_GNUC_CONST;
+PopplerPoint     *poppler_point_new      (void);
+PopplerPoint     *poppler_point_copy     (PopplerPoint *point);
+void              poppler_point_free     (PopplerPoint *point);
+
 /* A color in RGB */
 #define POPPLER_TYPE_COLOR                 (poppler_color_get_type ())
 
diff --git a/glib/poppler-private.h b/glib/poppler-private.h
index ab39b49..1a1dab9 100644
--- a/glib/poppler-private.h
+++ b/glib/poppler-private.h
@@ -120,6 +120,9 @@
 PopplerAnnot      *_poppler_annot_file_attachment_new (Annot *annot);
 PopplerAnnot      *_poppler_annot_movie_new (Annot *annot);
 PopplerAnnot      *_poppler_annot_screen_new (Annot *annot);
+PopplerAnnot      *_poppler_annot_line_new (Annot *annot);
+PopplerAnnot      *_poppler_annot_circle_new (Annot *annot);
+PopplerAnnot      *_poppler_annot_square_new (Annot *annot);
 
 char *_poppler_goo_string_to_utf8(GooString *s);
 gboolean _poppler_convert_pdf_date_to_gtime (GooString *date,
diff --git a/glib/poppler.h b/glib/poppler.h
index 2d190f3..92121f6 100644
--- a/glib/poppler.h
+++ b/glib/poppler.h
@@ -175,6 +175,7 @@
 typedef struct _PopplerIndexIter           PopplerIndexIter;
 typedef struct _PopplerFontsIter           PopplerFontsIter;
 typedef struct _PopplerLayersIter          PopplerLayersIter;
+typedef struct _PopplerPoint               PopplerPoint;
 typedef struct _PopplerRectangle           PopplerRectangle;
 typedef struct _PopplerTextAttributes      PopplerTextAttributes;
 typedef struct _PopplerColor               PopplerColor;
@@ -202,6 +203,9 @@
 typedef struct _PopplerAnnotMovie          PopplerAnnotMovie;
 typedef struct _PopplerAnnotScreen         PopplerAnnotScreen;
 typedef struct _PopplerAnnotCalloutLine    PopplerAnnotCalloutLine;
+typedef struct _PopplerAnnotLine           PopplerAnnotLine;
+typedef struct _PopplerAnnotCircle         PopplerAnnotCircle;
+typedef struct _PopplerAnnotSquare         PopplerAnnotSquare;
 
 typedef enum
 {
diff --git a/glib/reference/poppler-sections.txt b/glib/reference/poppler-sections.txt
index 6fb14bc..9bf2a05 100644
--- a/glib/reference/poppler-sections.txt
+++ b/glib/reference/poppler-sections.txt
@@ -35,8 +35,11 @@
 poppler_page_find_text
 poppler_page_find_text_with_options
 poppler_page_get_text
+poppler_page_get_text_for_area
 poppler_page_get_text_layout
+poppler_page_get_text_layout_for_area
 poppler_page_get_text_attributes
+poppler_page_get_text_attributes_for_area
 poppler_page_free_text_attributes
 poppler_page_get_link_mapping
 poppler_page_free_link_mapping
@@ -369,9 +372,11 @@
 <TITLE>PopplerAnnot</TITLE>
 PopplerAnnot
 PopplerAnnotMarkup
+PopplerAnnotCircle
 PopplerAnnotText
 PopplerAnnotFreeText
 PopplerAnnotFileAttachment
+PopplerAnnotLine
 PopplerAnnotMovie
 PopplerAnnotScreen
 PopplerAnnotType
@@ -381,6 +386,8 @@
 PopplerAnnotTextState
 PopplerAnnotCalloutLine
 PopplerAnnotFreeTextQuadding
+PopplerAnnotSquare
+PopplerPoint
 poppler_annot_get_annot_type
 poppler_annot_get_flags
 poppler_annot_get_name
@@ -390,6 +397,8 @@
 poppler_annot_get_contents
 poppler_annot_set_contents
 poppler_annot_get_modified
+poppler_annot_get_rectangle
+poppler_annot_set_rectangle
 poppler_annot_markup_get_label
 poppler_annot_markup_set_label
 poppler_annot_markup_get_subject
@@ -428,17 +437,35 @@
 poppler_annot_callout_line_new
 poppler_annot_callout_line_copy
 poppler_annot_callout_line_free
+poppler_annot_circle_new
+poppler_annot_circle_get_interior_color
+poppler_annot_circle_set_interior_color
+poppler_annot_line_new
+poppler_annot_line_set_vertices
+poppler_annot_square_new
+poppler_annot_square_get_interior_color
+poppler_annot_square_set_interior_color
+poppler_point_copy
+poppler_point_free
+poppler_point_get_type
+poppler_point_new
 
 <SUBSECTION Standard>
 POPPLER_ANNOT
 POPPLER_IS_ANNOT
 POPPLER_TYPE_ANNOT
+POPPLER_ANNOT_CIRCLE
+POPPLER_IS_ANNOT_CIRCLE
+POPPLER_TYPE_ANNOT_CIRCLE
 POPPLER_ANNOT_FILE_ATTACHMENT
 POPPLER_IS_ANNOT_FILE_ATTACHMENT
 POPPLER_TYPE_ANNOT_FILE_ATTACHMENT
 POPPLER_ANNOT_FREE_TEXT
 POPPLER_IS_ANNOT_FREE_TEXT
 POPPLER_TYPE_ANNOT_FREE_TEXT
+POPPLER_ANNOT_LINE
+POPPLER_IS_ANNOT_LINE
+POPPLER_TYPE_ANNOT_LINE
 POPPLER_ANNOT_MARKUP
 POPPLER_IS_ANNOT_MARKUP
 POPPLER_TYPE_ANNOT_MARKUP
@@ -448,6 +475,9 @@
 POPPLER_ANNOT_SCREEN
 POPPLER_IS_ANNOT_SCREEN
 POPPLER_TYPE_ANNOT_SCREEN
+POPPLER_ANNOT_SQUARE
+POPPLER_IS_ANNOT_SQUARE
+POPPLER_TYPE_ANNOT_SQUARE
 POPPLER_ANNOT_TEXT
 POPPLER_IS_ANNOT_TEXT
 POPPLER_TYPE_ANNOT_TEXT
@@ -474,6 +504,9 @@
 poppler_annot_callout_line_get_type
 poppler_annot_text_state_get_type
 poppler_annot_free_text_quadding_get_type
+poppler_annot_line_get_type
+poppler_annot_circle_get_type
+poppler_annot_square_get_type
 </SECTION>
 
 <SECTION>
diff --git a/goo/GooString.cc b/goo/GooString.cc
index d35161d..8591d95 100644
--- a/goo/GooString.cc
+++ b/goo/GooString.cc
@@ -20,9 +20,10 @@
 // Copyright (C) 2007 Jeff Muizelaar <jeff@infidigm.net>
 // Copyright (C) 2008-2011 Albert Astals Cid <aacid@kde.org>
 // Copyright (C) 2011 Kenji Uno <ku@digitaldolphins.jp>
-// Copyright (C) 2012 Fabio D'Urso <fabiodurso@hotmail.it>
+// Copyright (C) 2012, 2013 Fabio D'Urso <fabiodurso@hotmail.it>
 // Copyright (C) 2012 Adrian Johnson <ajohnson@redneon.com>
 // Copyright (C) 2012 Pino Toscano <pino@kde.org>
+// Copyright (C) 2013 Jason Crain <jason@aquaticape.us>
 //
 // To see a description of the changes please see the Changelog file that
 // came with your tarball or type make ChangeLog if you are building from git
@@ -650,18 +651,25 @@
   const char *vals = upperCase ? upperCaseDigits : lowerCaseDigits;
   GBool neg;
   int start, i, j;
+#ifdef LLONG_MAX
+  unsigned long long abs_x;
+#else
+  unsigned long abs_x;
+#endif
 
   i = bufSize;
   if ((neg = x < 0)) {
-    x = -x;
+    abs_x = -x;
+  } else {
+    abs_x = x;
   }
   start = neg ? 1 : 0;
-  if (x == 0) {
+  if (abs_x == 0) {
     buf[--i] = '0';
   } else {
-    while (i > start && x) {
-      buf[--i] = vals[x % base];
-      x /= base;
+    while (i > start && abs_x) {
+      buf[--i] = vals[abs_x % base];
+      abs_x /= base;
     }
   }
   if (zeroFill) {
@@ -893,6 +901,15 @@
   return 0;
 }
 
+GBool GooString::endsWith(const char *suffix) const {
+  int suffixLen = strlen(suffix);
+
+  if (length < suffixLen)
+    return gFalse;
+
+  return strcmp(s + length - suffixLen, suffix) == 0;
+}
+
 GBool GooString::hasUnicodeMarker(void)
 {
   return length > 1 && (s[0] & 0xff) == 0xfe && (s[1] & 0xff) == 0xff;
diff --git a/goo/GooString.h b/goo/GooString.h
index b24051b..6bdcf06 100644
--- a/goo/GooString.h
+++ b/goo/GooString.h
@@ -18,7 +18,8 @@
 // Copyright (C) 2006 Kristian Høgsberg <krh@redhat.com>
 // Copyright (C) 2006 Krzysztof Kowalczyk <kkowalczyk@gmail.com>
 // Copyright (C) 2008-2010, 2012 Albert Astals Cid <aacid@kde.org>
-// Copyright (C) 2012 Fabio D'Urso <fabiodurso@hotmail.it>
+// Copyright (C) 2012, 2013 Fabio D'Urso <fabiodurso@hotmail.it>
+// Copyright (C) 2013 Jason Crain <jason@aquaticape.us>
 //
 // To see a description of the changes please see the Changelog file that
 // came with your tarball or type make ChangeLog if you are building from git
@@ -86,8 +87,12 @@
   //     ld, lx, lX, lo, lb, uld, ulx, ulX, ulo, ulb -- long, unsigned long
   //     lld, llx, llX, llo, llb, ulld, ullx, ullX, ullo, ullb
   //         -- long long, unsigned long long
-  //     f, g -- double
-  //     c -- char
+  //     f, g, gs -- floating point (float or double)
+  //         f  -- always prints trailing zeros (eg 1.0 with .2f will print 1.00)
+  //         g  -- omits trailing zeros and, if possible, the dot (eg 1.0 shows up as 1)
+  //         gs -- is like g, but treats <precision> as number of significant
+  //               digits to show (eg 0.0123 with .2gs will print 0.012)
+  //     c -- character (char, short or int)
   //     s -- string (char *)
   //     t -- GooString *
   //     w -- blank space; arg determines width
@@ -140,6 +145,9 @@
   int cmp(const char *sA) const;
   int cmpN(const char *sA, int n) const;
 
+  // Return true if string ends with suffix
+  GBool endsWith(const char *suffix) const;
+
   GBool hasUnicodeMarker(void);
 
   // Sanitizes the string so that it does
diff --git a/goo/Makefile.am b/goo/Makefile.am
index 0764e79..a48b20e 100644
--- a/goo/Makefile.am
+++ b/goo/Makefile.am
@@ -13,6 +13,7 @@
 	gmem.h					\
 	gfile.h					\
 	FixedPoint.h				\
+	NetPBMWriter.h				\
 	PNGWriter.h				\
 	JpegWriter.h				\
 	TiffWriter.h				\
@@ -55,6 +56,7 @@
 	GooString.cc				\
 	gmem.cc					\
 	FixedPoint.cc				\
+	NetPBMWriter.cc				\
 	PNGWriter.cc				\
 	JpegWriter.cc				\
 	TiffWriter.cc				\
diff --git a/goo/NetPBMWriter.cc b/goo/NetPBMWriter.cc
new file mode 100644
index 0000000..fca00b2
--- /dev/null
+++ b/goo/NetPBMWriter.cc
@@ -0,0 +1,84 @@
+//========================================================================
+//
+// NetPBMWriter.h
+//
+// Copyright 1998-2003 Glyph & Cog, LLC
+//
+//========================================================================
+//
+//========================================================================
+//
+// Modified under the Poppler project - http://poppler.freedesktop.org
+//
+// All changes made under the Poppler project to this file are licensed
+// under GPL version 2 or later
+//
+// Copyright (C) 2005, 2007, 2011 Albert Astals Cid <aacid@kde.org>
+// Copyright (C) 2006 Rainer Keller <class321@gmx.de>
+// Copyright (C) 2008 Timothy Lee <timothy.lee@siriushk.com>
+// Copyright (C) 2008 Vasile Gaburici <gaburici@cs.umd.edu>
+// Copyright (C) 2009 Carlos Garcia Campos <carlosgc@gnome.org>
+// Copyright (C) 2009 William Bader <williambader@hotmail.com>
+// Copyright (C) 2010 Jakob Voss <jakob.voss@gbv.de>
+// Copyright (C) 2012, 2013 Adrian Johnson <ajohnson@redneon.com>
+// Copyright (C) 2013 Thomas Fischer <fischer@unix-ag.uni-kl.de>
+//
+// To see a description of the changes please see the Changelog file that
+// came with your tarball or type make ChangeLog if you are building from git
+//
+//========================================================================
+
+#include "poppler-config.h"
+
+#include "NetPBMWriter.h"
+
+// Writer for the NetPBM formats (PBM and PPM)
+// This format is documented at:
+//   http://netpbm.sourceforge.net/doc/pbm.html
+//   http://netpbm.sourceforge.net/doc/ppm.html
+
+NetPBMWriter::NetPBMWriter(Format formatA) : format(formatA)
+{
+}
+
+bool NetPBMWriter::init(FILE *f, int widthA, int heightA, int hDPI, int vDPI)
+{
+  file = f;
+  width = widthA;
+  if (format == MONOCHROME) {
+    fprintf(file, "P4\n");
+    fprintf(file, "%d %d\n", widthA, heightA);
+  } else {
+    fprintf(file, "P6\n");
+    fprintf(file, "%d %d\n", widthA, heightA);
+    fprintf(file, "255\n");
+  }
+  return true;
+}
+
+bool NetPBMWriter::writePointers(unsigned char **rowPointers, int rowCount)
+{
+  for (int i = 0; i < rowCount; i++)
+    writeRow(&rowPointers[i]);
+  return true;
+}
+
+bool NetPBMWriter::writeRow(unsigned char **row)
+{
+  if (format == MONOCHROME) {
+    // PBM uses 0 = white, 1 = black so we need to invert the colors
+    int size = (width + 7)/8;
+    for (int i = 0; i < size; i++)
+      fputc((*row)[i] ^ 0xff, file);
+  } else {
+    fwrite(*row, 1, width*3, file);
+  }
+  return true;
+}
+
+
+bool NetPBMWriter::close()
+{
+  return true;
+}
+
diff --git a/goo/NetPBMWriter.h b/goo/NetPBMWriter.h
new file mode 100644
index 0000000..21a19ee
--- /dev/null
+++ b/goo/NetPBMWriter.h
@@ -0,0 +1,52 @@
+//========================================================================
+//
+// NetPBMWriter.h
+//
+// This file is licensed under the GPLv2 or later
+//
+// Copyright (C) 2009 Stefan Thomas <thomas@eload24.com>
+// Copyright (C) 2009, 2011 Albert Astals Cid <aacid@kde.org>
+// Copyright (C) 2010, 2013 Adrian Johnson <ajohnson@redneon.com>
+// Copyright (C) 2010 Brian Cameron <brian.cameron@oracle.com>
+// Copyright (C) 2011 Thomas Freitag <Thomas.Freitag@alfa.de>
+//
+//========================================================================
+
+#ifndef NETPBMWRITER_H
+#define NETPBMWRITER_H
+
+#include "poppler-config.h"
+
+#include "ImgWriter.h"
+
+// Writer for the NetPBM formats (PBM and PPM)
+// This format is documented at:
+//   http://netpbm.sourceforge.net/doc/pbm.html
+//   http://netpbm.sourceforge.net/doc/ppm.html
+
+class NetPBMWriter : public ImgWriter
+{
+public:
+
+  /* RGB        - 3 bytes/pixel
+   * MONOCHROME - 8 pixels/byte
+   */
+  enum Format { RGB, MONOCHROME };
+
+  NetPBMWriter(Format formatA = RGB);
+  ~NetPBMWriter() {};
+
+  bool init(FILE *f, int width, int height, int hDPI, int vDPI);
+
+  bool writePointers(unsigned char **rowPointers, int rowCount);
+  bool writeRow(unsigned char **row);
+
+  bool close();
+
+private:
+  FILE *file;
+  Format format;
+  int width;
+};
+
+#endif
diff --git a/goo/PNGWriter.cc b/goo/PNGWriter.cc
index b775600..c9d5199 100644
--- a/goo/PNGWriter.cc
+++ b/goo/PNGWriter.cc
@@ -8,7 +8,7 @@
 // Copyright (C) 2009 Shen Liang <shenzhuxi@gmail.com>
 // Copyright (C) 2009, 2011 Albert Astals Cid <aacid@kde.org>
 // Copyright (C) 2009 Stefan Thomas <thomas@eload24.com>
-// Copyright (C) 2010, 2011 Adrian Johnson <ajohnson@redneon.com>
+// Copyright (C) 2010, 2011, 2013 Adrian Johnson <ajohnson@redneon.com>
 // Copyright (C) 2011 Thomas Klausner <wiz@danbala.tuwien.ac.at>
 // Copyright (C) 2012 Pino Toscano <pino@kde.org>
 //
@@ -147,10 +147,6 @@
     return false;
   }
 
-  // pack 1 pixel/byte rows into 8 pixels/byte
-  if (priv->format == MONOCHROME)
-    png_set_packing(priv->png_ptr);
-
   return true;
 }
 
diff --git a/goo/PNGWriter.h b/goo/PNGWriter.h
index c73c964..90a298e 100644
--- a/goo/PNGWriter.h
+++ b/goo/PNGWriter.h
@@ -6,9 +6,9 @@
 //
 // Copyright (C) 2009 Warren Toomey <wkt@tuhs.org>
 // Copyright (C) 2009 Shen Liang <shenzhuxi@gmail.com>
-// Copyright (C) 2009, 2011, 2012 Albert Astals Cid <aacid@kde.org>
+// Copyright (C) 2009, 2011-2013 Albert Astals Cid <aacid@kde.org>
 // Copyright (C) 2009 Stefan Thomas <thomas@eload24.com>
-// Copyright (C) 2010, 2011 Adrian Johnson <ajohnson@redneon.com>
+// Copyright (C) 2010, 2011, 2013 Adrian Johnson <ajohnson@redneon.com>
 // Copyright (C) 2012 Pino Toscano <pino@kde.org>
 //
 //========================================================================
@@ -22,7 +22,7 @@
 
 #include "ImgWriter.h"
 
-class PNGWriterPrivate;
+struct PNGWriterPrivate;
 
 class PNGWriter : public ImgWriter
 {
@@ -31,7 +31,7 @@
   /* RGB        - 3 bytes/pixel
    * RGBA       - 4 bytes/pixel
    * GRAY       - 1 byte/pixel
-   * MONOCHROME - 1 byte/pixel. PNGWriter will bitpack to 8 pixels/byte
+   * MONOCHROME - 8 pixels/byte
    */
   enum Format { RGB, RGBA, GRAY, MONOCHROME };
 
diff --git a/poppler/Annot.cc b/poppler/Annot.cc
index 00291f8..90a6e5d 100644
--- a/poppler/Annot.cc
+++ b/poppler/Annot.cc
@@ -28,6 +28,7 @@
 // Copyright (C) 2012, 2013 Thomas Freitag <Thomas.Freitag@alfa.de>
 // Copyright (C) 2012 Tobias Koenig <tokoe@kdab.com>
 // Copyright (C) 2013 Peter Breitenlohner <peb@mppmu.mpg.de>
+// Copyright (C) 2013 Adrian Johnson <ajohnson@redneon.com>
 //
 // To see a description of the changes please see the Changelog file that
 // came with your tarball or type make ChangeLog if you are building from git
@@ -234,6 +235,28 @@
   return linkAction;
 }
 
+static LinkAction* getFormAdditionalAction(Annot::FormAdditionalActionsType type, Object *additionalActions, PDFDoc *doc) {
+  Object additionalActionsObject;
+  LinkAction *linkAction = NULL;
+
+  if (additionalActions->fetch(doc->getXRef(), &additionalActionsObject)->isDict()) {
+    const char *key = (type == Annot::actionFieldModified ?  "K" :
+                       type == Annot::actionFormatField ?    "F" :
+                       type == Annot::actionValidateField ?  "V" :
+                       type == Annot::actionCalculateField ? "C" : NULL);
+
+    Object actionObject;
+
+    if (additionalActionsObject.dictLookup(key, &actionObject)->isDict())
+      linkAction = LinkAction::parseAction(&actionObject, doc->getCatalog()->getBaseURI());
+    actionObject.free();
+  }
+
+  additionalActionsObject.free();
+
+  return linkAction;
+}
+
 //------------------------------------------------------------------------
 // AnnotBorderEffect
 //------------------------------------------------------------------------
@@ -497,7 +520,6 @@
 // AnnotBorder
 //------------------------------------------------------------------------
 AnnotBorder::AnnotBorder() {
-  type = typeUnknown;
   width = 1;
   dashLength = 0;
   dash = NULL;
@@ -542,7 +564,6 @@
 //------------------------------------------------------------------------
 
 AnnotBorderArray::AnnotBorderArray() {
-  type = typeArray;
   horizontalCorner = 0;
   verticalCorner = 0;
 }
@@ -593,10 +614,17 @@
   Object obj2;
 
   obj1->initArray(xref);
-  obj1->arrayAdd(obj2.initReal( horizontalCorner ));
-  obj1->arrayAdd(obj2.initReal( verticalCorner ));
-  obj1->arrayAdd(obj2.initReal( width ));
-  // TODO: Dash array
+  obj1->arrayAdd(obj2.initReal(horizontalCorner));
+  obj1->arrayAdd(obj2.initReal(verticalCorner));
+  obj1->arrayAdd(obj2.initReal(width));
+
+  if (dashLength > 0) {
+    Object obj3;
+
+    obj1->arrayAdd(obj3.initArray(xref));
+    for (int i = 0; i < dashLength; i++)
+      obj3.arrayAdd(obj2.initReal(dash[i]));
+  }
 }
 
 //------------------------------------------------------------------------
@@ -604,7 +632,6 @@
 //------------------------------------------------------------------------
 
 AnnotBorderBS::AnnotBorderBS() {
-  type = typeBS;
 }
 
 AnnotBorderBS::AnnotBorderBS(Dict *dict) {
@@ -655,6 +682,38 @@
   }
 }
 
+const char *AnnotBorderBS::getStyleName() const {
+  switch (style) {
+  case borderSolid:
+    return "S";
+  case borderDashed:
+    return "D";
+  case borderBeveled:
+    return "B";
+  case borderInset:
+    return "I";
+  case borderUnderlined:
+    return "U";
+  }
+
+  return "S";
+}
+
+void AnnotBorderBS::writeToObject(XRef *xref, Object *obj1) const {
+  Object obj2;
+
+  obj1->initDict(xref);
+  obj1->dictSet("W", obj2.initReal(width));
+  obj1->dictSet("S", obj2.initName(getStyleName()));
+  if (style == borderDashed && dashLength > 0) {
+    Object obj3;
+
+    obj1->dictSet("D", obj3.initArray(xref));
+    for (int i = 0; i < dashLength; i++)
+      obj3.arrayAdd(obj2.initReal(dash[i]));
+  }
+}
+
 //------------------------------------------------------------------------
 // AnnotColor
 //------------------------------------------------------------------------
@@ -1272,18 +1331,14 @@
   }
 
   //----- parse the border style
-  if (dict->lookup("BS", &obj1)->isDict()) {
-    border = new AnnotBorderBS(obj1.getDict());
-  } else {
-    obj1.free();
-
-    if (dict->lookup("Border", &obj1)->isArray())
-      border = new AnnotBorderArray(obj1.getArray());
-    else
-      // Adobe draws no border at all if the last element is of
-      // the wrong type.
-      border = NULL;
-  }
+  // According to the spec if neither the Border nor the BS entry is present,
+  // the border shall be drawn as a solid line with a width of 1 point. But acroread
+  // seems to ignore the Border entry for annots that can't have a BS entry. So, we only
+  // follow this rule for annots tha can have a BS entry.
+  if (dict->lookup("Border", &obj1)->isArray())
+    border = new AnnotBorderArray(obj1.getArray());
+  else
+    border = NULL;
   obj1.free();
 
   if (dict->lookup("C", &obj1)->isArray()) {
@@ -1425,14 +1480,14 @@
   update ("F", &obj1);
 }
 
-void Annot::setBorder(AnnotBorderArray *new_border) {
+void Annot::setBorder(AnnotBorder *new_border) {
   annotLocker();
   delete border;
 
   if (new_border) {
     Object obj1;
     new_border->writeToObject(xref, &obj1);
-    update ("Border", &obj1);
+    update(new_border->getType() == AnnotBorder::typeArray ? "Border" : "BS", &obj1);
     border = new_border;
   } else {
     border = NULL;
@@ -1640,6 +1695,26 @@
   }
 }
 
+void Annot::setLineStyleForBorder(AnnotBorder *border) {
+  int i, dashLength;
+  double *dash;
+
+  switch (border->getStyle()) {
+  case AnnotBorder::borderDashed:
+    appearBuf->append("[");
+    dashLength = border->getDashLength();
+    dash = border->getDash();
+    for (i = 0; i < dashLength; ++i)
+      appearBuf->appendf(" {0:.2f}", dash[i]);
+    appearBuf->append(" ] 0 d\n");
+    break;
+  default:
+    appearBuf->append("[] 0 d\n");
+    break;
+  }
+  appearBuf->appendf("{0:.2f} w\n", border->getWidth());
+}
+
 // Draw an (approximate) circle of radius <r> centered at (<cx>, <cy>).
 // If <fill> is true, the circle is filled; otherwise it is stroked.
 void Annot::drawCircle(double cx, double cy, double r, GBool fill) {
@@ -2580,6 +2655,14 @@
     quadrilaterals = NULL;
   }
   obj1.free();
+
+  if (dict->lookup("BS", &obj1)->isDict()) {
+    delete border;
+    border = new AnnotBorderBS(obj1.getDict());
+  } else if (!border) {
+    border = new AnnotBorderBS();
+  }
+  obj1.free();
 }
 
 void AnnotLink::draw(Gfx *gfx, GBool printing) {
@@ -2707,6 +2790,14 @@
   }
   obj1.free();
 
+  if (dict->lookup("BS", &obj1)->isDict()) {
+    delete border;
+    border = new AnnotBorderBS(obj1.getDict());
+  } else if (!border) {
+    border = new AnnotBorderBS();
+  }
+  obj1.free();
+
   if (dict->lookup("BE", &obj1)->isDict()) {
     borderEffect = new AnnotBorderEffect(obj1.getDict());
   } else {
@@ -2905,29 +2996,10 @@
 
   appearBuf = new GooString ();
   appearBuf->append ("q\n");
-  
-  if (border) {
-    int i, dashLength;
-    double *dash;
-    borderWidth = border->getWidth();
 
-    switch (border->getStyle()) {
-    case AnnotBorder::borderDashed:
-      appearBuf->append("[");
-      dashLength = border->getDashLength();
-      dash = border->getDash();
-      for (i = 0; i < dashLength; ++i)
-        appearBuf->appendf(" {0:.2f}", dash[i]);
-      appearBuf->append(" ] 0 d\n");
-      break;
-    default:
-      appearBuf->append("[] 0 d\n");
-      break;
-    }
-    appearBuf->appendf("{0:.2f} w\n", borderWidth);
-  } else {
-    borderWidth = 0; // No border
-  }
+  borderWidth = border->getWidth();
+  if (borderWidth > 0)
+    setLineStyleForBorder(border);
 
   // Box size
   const double width = rect->x2 - rect->x1;
@@ -3213,6 +3285,14 @@
     captionTextHorizontal = captionTextVertical = 0;
   }
   obj1.free();
+
+  if (dict->lookup("BS", &obj1)->isDict()) {
+    delete border;
+    border = new AnnotBorderBS(obj1.getDict());
+  } else if (!border) {
+    border = new AnnotBorderBS();
+  }
+  obj1.free();
 }
 
 void AnnotLine::setContents(GooString *new_content) {
@@ -3320,29 +3400,9 @@
     setColor(color, gFalse);
   }
 
-  if (border) {
-    int i, dashLength;
-    double *dash;
-
-    switch (border->getStyle()) {
-    case AnnotBorder::borderDashed:
-      appearBuf->append("[");
-      dashLength = border->getDashLength();
-      dash = border->getDash();
-      for (i = 0; i < dashLength; ++i)
-        appearBuf->appendf(" {0:.2f}", dash[i]);
-      appearBuf->append(" ] 0 d\n");
-      break;
-    default:
-      appearBuf->append("[] 0 d\n");
-      break;
-    }
-    borderWidth = border->getWidth();
-    appearBuf->appendf("{0:.2f} w\n", borderWidth);
-    appearBBox->setBorderWidth(borderWidth);
-  } else {
-    borderWidth = 0;
-  }
+  setLineStyleForBorder(border);
+  borderWidth = border->getWidth();
+  appearBBox->setBorderWidth(std::max(1., borderWidth));
 
   const double x1 = coord1->getX();
   const double y1 = coord1->getY();
@@ -3903,6 +3963,12 @@
   }
   obj1.free();
 
+  if (dict->lookup("BS", &obj1)->isDict()) {
+    delete border;
+    border = new AnnotBorderBS(obj1.getDict());
+  }
+  obj1.free();
+
   updatedAppearanceStream.num = updatedAppearanceStream.gen = -1;
 }
 
@@ -3911,6 +3977,11 @@
   return ::getAdditionalAction(type, &additionalActions, doc);
 }
 
+LinkAction* AnnotWidget::getFormAdditionalAction(FormAdditionalActionsType type)
+{
+  return ::getFormAdditionalAction(type, &additionalActions, doc);
+}
+
 // Grand unified handler for preparing text strings to be drawn into form
 // fields.  Takes as input a text string (in PDFDocEncoding or UTF-16).
 // Converts some or all of this string to the appropriate encoding for the
@@ -5418,6 +5489,14 @@
   }
   obj1.free();
 
+  if (dict->lookup("BS", &obj1)->isDict()) {
+    delete border;
+    border = new AnnotBorderBS(obj1.getDict());
+  } else if (!border) {
+    border = new AnnotBorderBS();
+  }
+  obj1.free();
+
   if (dict->lookup("BE", &obj1)->isDict()) {
     borderEffect = new AnnotBorderEffect(obj1.getDict());
   } else {
@@ -5482,88 +5561,70 @@
     if (color)
       setColor(color, gFalse);
 
-    if (border) {
-      int i, dashLength;
-      double *dash;
-      double borderWidth = border->getWidth();
+    double borderWidth = border->getWidth();
+    setLineStyleForBorder(border);
 
-      switch (border->getStyle()) {
-      case AnnotBorder::borderDashed:
-        appearBuf->append("[");
-	dashLength = border->getDashLength();
-	dash = border->getDash();
-	for (i = 0; i < dashLength; ++i)
-	  appearBuf->appendf(" {0:.2f}", dash[i]);
-	appearBuf->append(" ] 0 d\n");
-	break;
-      default:
-        appearBuf->append("[] 0 d\n");
-        break;
-      }
-      appearBuf->appendf("{0:.2f} w\n", borderWidth);
+    if (interiorColor)
+      setColor(interiorColor, gTrue);
 
-      if (interiorColor)
-        setColor(interiorColor, gTrue);
+    if (type == typeSquare) {
+      appearBuf->appendf ("{0:.2f} {1:.2f} {2:.2f} {3:.2f} re\n",
+                          borderWidth / 2.0, borderWidth / 2.0,
+                          (rect->x2 - rect->x1) - borderWidth,
+                          (rect->y2 - rect->y1) - borderWidth);
+    } else {
+      double width, height;
+      double b;
+      double x1, y1, x2, y2, x3, y3;
 
-      if (type == typeSquare) {
-        appearBuf->appendf ("{0:.2f} {1:.2f} {2:.2f} {3:.2f} re\n",
-			    borderWidth / 2.0, borderWidth / 2.0,
-			    (rect->x2 - rect->x1) - borderWidth,
-			    (rect->y2 - rect->y1) - borderWidth);
-      } else {
-        double width, height;
-	double b;
-	double x1, y1, x2, y2, x3, y3;
+      width = rect->x2 - rect->x1;
+      height = rect->y2 - rect->y1;
+      b = borderWidth / 2.0;
 
-	width = rect->x2 - rect->x1;
-	height = rect->y2 - rect->y1;
-	b = borderWidth / 2.0;
+      x1 = b;
+      y1 = height / 2.0;
+      appearBuf->appendf ("{0:.2f} {1:.2f} m\n", x1, y1);
 
-	x1 = b;
-	y1 = height / 2.0;
-	appearBuf->appendf ("{0:.2f} {1:.2f} m\n", x1, y1);
+      y1 += height / 4.0;
+      x2 = width / 4.0;
+      y2 = height - b;
+      x3 = width / 2.0;
+      y3 = y2;
+      appearBuf->appendf ("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n",
+                          x1, y1, x2, y2, x3, y3);
+      x2 = width - b;
+      y2 = y1;
+      x1 = x3 + (width / 4.0);
+      y1 = y3;
+      x3 = x2;
+      y3 = height / 2.0;
+      appearBuf->appendf ("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n",
+                          x1, y1, x2, y2, x3, y3);
 
-	y1 += height / 4.0;
-	x2 = width / 4.0;
-	y2 = height - b;
-	x3 = width / 2.0;
-	y3 = y2;
-	appearBuf->appendf ("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n",
-			    x1, y1, x2, y2, x3, y3);
-	x2 = width - b;
-	y2 = y1;
-	x1 = x3 + (width / 4.0);
-	y1 = y3;
-	x3 = x2;
-	y3 = height / 2.0;
-	appearBuf->appendf ("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n",
-			    x1, y1, x2, y2, x3, y3);
+      x2 = x1;
+      y2 = b;
+      x1 = x3;
+      y1 = height / 4.0;
+      x3 = width / 2.0;
+      y3 = b;
+      appearBuf->appendf ("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n",
+                          x1, y1, x2, y2, x3, y3);
 
-	x2 = x1;
-	y2 = b;
-	x1 = x3;
-	y1 = height / 4.0;
-	x3 = width / 2.0;
-	y3 = b;
-	appearBuf->appendf ("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n",
-			    x1, y1, x2, y2, x3, y3);
-
-	x2 = b;
-	y2 = y1;
-	x1 = width / 4.0;
-	y1 = b;
-	x3 = b;
-	y3 = height / 2.0;
-	appearBuf->appendf ("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n",
-			    x1, y1, x2, y2, x3, y3);
-
-      }
-
-      if (interiorColor && interiorColor->getSpace() != AnnotColor::colorTransparent)
-        appearBuf->append ("b\n");
-      else
-        appearBuf->append ("S\n");
+      x2 = b;
+      y2 = y1;
+      x1 = width / 4.0;
+      y1 = b;
+      x3 = b;
+      y3 = height / 2.0;
+      appearBuf->appendf ("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n",
+                          x1, y1, x2, y2, x3, y3);
     }
+
+    if (interiorColor && interiorColor->getSpace() != AnnotColor::colorTransparent)
+      appearBuf->append ("b\n");
+    else
+      appearBuf->append ("S\n");
+
     appearBuf->append ("Q\n");
 
     double bbox[4];
@@ -5687,6 +5748,14 @@
   }
   obj1.free();
 
+  if (dict->lookup("BS", &obj1)->isDict()) {
+    delete border;
+    border = new AnnotBorderBS(obj1.getDict());
+  } else if (!border) {
+    border = new AnnotBorderBS();
+  }
+  obj1.free();
+
   if (dict->lookup("BE", &obj1)->isDict()) {
     borderEffect = new AnnotBorderEffect(obj1.getDict());
   } else {
@@ -5806,26 +5875,8 @@
       setColor(color, gFalse);
     }
 
-    if (border) {
-      int i, dashLength;
-      double *dash;
-
-      switch (border->getStyle()) {
-      case AnnotBorder::borderDashed:
-        appearBuf->append("[");
-        dashLength = border->getDashLength();
-        dash = border->getDash();
-        for (i = 0; i < dashLength; ++i)
-          appearBuf->appendf(" {0:.2f}", dash[i]);
-        appearBuf->append(" ] 0 d\n");
-        break;
-      default:
-        appearBuf->append("[] 0 d\n");
-        break;
-      }
-      appearBuf->appendf("{0:.2f} w\n", border->getWidth());
-      appearBBox->setBorderWidth(border->getWidth());
-    }
+    setLineStyleForBorder(border);
+    appearBBox->setBorderWidth(std::max(1., border->getWidth()));
 
     if (interiorColor) {
       setColor(interiorColor, gTrue);
@@ -5982,6 +6033,14 @@
     ok = gFalse;
   }
   obj1.free();
+
+  if (dict->lookup("BS", &obj1)->isDict()) {
+    delete border;
+    border = new AnnotBorderBS(obj1.getDict());
+  } else if (!border) {
+    border = new AnnotBorderBS();
+  }
+  obj1.free();
 }
 
 void AnnotInk::writeInkList(AnnotPath **paths, int n_paths, Array *dest_array) {
@@ -6049,10 +6108,8 @@
       setColor(color, gFalse);
     }
 
-    if (border) {
-      appearBuf->appendf("{0:.2f} w\n", border->getWidth());
-      appearBBox->setBorderWidth(border->getWidth());
-    }
+    setLineStyleForBorder(border);
+    appearBBox->setBorderWidth(std::max(1., border->getWidth()));
 
     for (int i = 0; i < inkListLength; ++i) {
       const AnnotPath * path = inkList[i];
@@ -6313,7 +6370,7 @@
 
   // draw the appearance stream
   appearance.fetch(gfx->getXRef(), &obj);
-  gfx->drawAnnot(&obj, border, color,
+  gfx->drawAnnot(&obj, (AnnotBorder *)NULL, color,
 		 rect->x1, rect->y1, rect->x2, rect->y2, getRotation());
   obj.free();
 }
@@ -6475,7 +6532,7 @@
 
   // draw the appearance stream
   appearance.fetch(gfx->getXRef(), &obj);
-  gfx->drawAnnot(&obj, border, color,
+  gfx->drawAnnot(&obj, (AnnotBorder *)NULL, color,
 		 rect->x1, rect->y1, rect->x2, rect->y2, getRotation());
   obj.free();
 }
diff --git a/poppler/Annot.h b/poppler/Annot.h
index 2865d23..8fde6a6 100644
--- a/poppler/Annot.h
+++ b/poppler/Annot.h
@@ -25,6 +25,7 @@
 // Copyright (C) 2012, 2013 Fabio D'Urso <fabiodurso@hotmail.it>
 // Copyright (C) 2012 Tobias Koenig <tokoe@kdab.com>
 // Copyright (C) 2013 Thomas Freitag <Thomas.Freitag@alfa.de>
+// Copyright (C) 2013 Adrian Johnson <ajohnson@redneon.com>
 //
 // To see a description of the changes please see the Changelog file that
 // came with your tarball or type make ChangeLog if you are building from git
@@ -215,7 +216,6 @@
 class AnnotBorder {
 public:
   enum AnnotBorderType {
-    typeUnknown,
     typeArray,
     typeBS
   };
@@ -228,18 +228,21 @@
     borderUnderlined  // Underlined
   };
 
-  AnnotBorder();
   virtual ~AnnotBorder();
 
   virtual void setWidth(double new_width) { width = new_width; }
 
-  virtual AnnotBorderType getType() const { return type; }
+  virtual AnnotBorderType getType() const = 0;
   virtual double getWidth() const { return width; }
   virtual int getDashLength() const { return dashLength; }
   virtual double *getDash() const { return dash; }
   virtual AnnotBorderStyle getStyle() const { return style; }
 
+  virtual void writeToObject(XRef *xref, Object *obj1) const = 0;
+
 protected:
+  AnnotBorder();
+
   GBool parseDashArray(Object *dashObj);
 
   AnnotBorderType type;
@@ -259,15 +262,16 @@
   AnnotBorderArray();
   AnnotBorderArray(Array *array);
 
-  void writeToObject(XRef *xref, Object *dest) const;
-
   void setHorizontalCorner(double hc) { horizontalCorner = hc; }
   void setVerticalCorner(double vc) { verticalCorner = vc; }
 
   double getHorizontalCorner() const { return horizontalCorner; }
   double getVerticalCorner() const { return verticalCorner; }
 
-protected:
+private:
+  virtual AnnotBorderType getType() const { return typeArray; }
+  virtual void writeToObject(XRef *xref, Object *obj1) const;
+
   double horizontalCorner;          // (Default 0)
   double verticalCorner;            // (Default 0)
   // double width;                  // (Default 1)  (inherited from AnnotBorder)
@@ -284,6 +288,11 @@
   AnnotBorderBS(Dict *dict);
 
 private:
+  virtual AnnotBorderType getType() const { return typeBS; }
+  virtual void writeToObject(XRef *xref, Object *obj1) const;
+
+  const char *getStyleName() const;
+
   // double width;           // W  (Default 1)   (inherited from AnnotBorder)
   // AnnotBorderStyle style; // S  (Default S)   (inherited from AnnotBorder)
   // double *dash;           // D  (Default [3]) (inherited from AnnotBorder)
@@ -536,6 +545,13 @@
     actionPageInvisible   ///< Performed when the page containing the annotation becomes invisible
   };
 
+  enum FormAdditionalActionsType {
+    actionFieldModified,   ///< Performed when the when the user modifies the field
+    actionFormatField,     ///< Performed before the field is formatted to display its value
+    actionValidateField,   ///< Performed when the field value changes
+    actionCalculateField,  ///< Performed when the field needs to be recalculated
+  };
+
   Annot(PDFDoc *docA, PDFRectangle *rectA);
   Annot(PDFDoc *docA, Dict *dict);
   Annot(PDFDoc *docA, Dict *dict, Object *obj);
@@ -568,7 +584,7 @@
   void setModified(GooString *new_date);
   void setFlags(Guint new_flags);
 
-  void setBorder(AnnotBorderArray *new_border); // Takes ownership
+  void setBorder(AnnotBorder *new_border); // Takes ownership
 
   // The annotation takes the ownership of
   // new_color. 
@@ -612,6 +628,7 @@
   virtual ~Annot();
   virtual void removeReferencedObjects(); // Called by Page::removeAnnot
   void setColor(AnnotColor *color, GBool fill);
+  void setLineStyleForBorder(AnnotBorder *border);
   void drawCircle(double cx, double cy, double r, GBool fill);
   void drawCircleTopLeft(double cx, double cy, double r);
   void drawCircleBottomRight(double cx, double cy, double r);
@@ -1305,6 +1322,7 @@
   AnnotAppearanceCharacs *getAppearCharacs() { return appearCharacs; }
   LinkAction *getAction() { return action; }
   LinkAction *getAdditionalAction(AdditionalActionsType type);
+  LinkAction *getFormAdditionalAction(FormAdditionalActionsType type);
   Dict *getParent() { return parent; }
 
 private:
diff --git a/poppler/CMap.cc b/poppler/CMap.cc
index 13f293a..6731ab5 100644
--- a/poppler/CMap.cc
+++ b/poppler/CMap.cc
@@ -15,6 +15,7 @@
 //
 // Copyright (C) 2008 Koji Otani <sho@bbr.jp>
 // Copyright (C) 2008, 2009 Albert Astals Cid <aacid@kde.org>
+// Copyright (C) 2013 Fabio D'Urso <fabiodurso@hotmail.it>
 //
 // To see a description of the changes please see the Changelog file that
 // came with your tarball or type make ChangeLog if you are building from git
@@ -420,7 +421,7 @@
   for (byte = (int)(start & 0xff); byte <= (int)(end & 0xff); ++byte) {
     if (vec[byte].isVector) {
       error(errSyntaxError, -1,
-	    "Invalid CID ({0:x} - {1:x} [{2:d} bytes]) in CMap",
+	    "Invalid CID ({0:ux} - {1:ux} [{2:ud} bytes]) in CMap",
 	    start, end, nBytes);
     } else {
       vec[byte].cid = cid;
diff --git a/poppler/Catalog.cc b/poppler/Catalog.cc
index f17f2c0..4464346 100644
--- a/poppler/Catalog.cc
+++ b/poppler/Catalog.cc
@@ -27,6 +27,8 @@
 // Copyright (C) 2012 Fabio D'Urso <fabiodurso@hotmail.it>
 // Copyright (C) 2013 Thomas Freitag <Thomas.Freitag@alfa.de>
 // Copyright (C) 2013 Julien Nabet <serval2412@yahoo.fr>
+// Copyright (C) 2013 Adrian Perez de Castro <aperez@igalia.com>
+// Copyright (C) 2013 Adrian Johnson <ajohnson@redneon.com>
 // Copyright (C) 2013 José Aliste <jaliste@src.gnome.org>
 //
 // To see a description of the changes please see the Changelog file that
@@ -57,6 +59,7 @@
 #include "OptionalContent.h"
 #include "ViewerPreferences.h"
 #include "FileSpec.h"
+#include "StructTreeRoot.h"
 
 #if MULTITHREADED
 #  define catalogLocker()   MutexLocker locker(&mutex)
@@ -92,12 +95,14 @@
   embeddedFileNameTree = NULL;
   jsNameTree = NULL;
   viewerPrefs = NULL;
+  structTreeRoot = NULL;
 
   pagesList = NULL;
   pagesRefList = NULL;
   attrsList = NULL;
   kidsIdxList = NULL;
   lastCachedPage = 0;
+  markInfo = markInfoNull;
 
   xref->getCatalog(&catDict);
   if (!catDict.isDict()) {
@@ -126,6 +131,9 @@
   }
   optContentProps.free();
 
+  // actions
+  catDict.dictLookupNF("AA", &additionalActions);
+
   // get the ViewerPreferences dictionary
   catDict.dictLookup("ViewerPreferences", &viewerPreferences);
   catDict.free();
@@ -176,11 +184,12 @@
   delete form;
   delete optContent;
   delete viewerPrefs;
+  delete structTreeRoot;
   metadata.free();
-  structTreeRoot.free();
   outline.free();
   acroForm.free();
   viewerPreferences.free();
+  additionalActions.free();
 #if MULTITHREADED
   gDestroyMutex(&mutex);
 #endif
@@ -849,24 +858,72 @@
   return pageLabelInfo;
 }
 
-Object *Catalog::getStructTreeRoot()
+StructTreeRoot *Catalog::getStructTreeRoot()
 {
   catalogLocker();
-  if (structTreeRoot.isNone())
-  {
-     Object catDict;
+  if (!structTreeRoot) {
+    Object catalog;
+    Object root;
 
-     xref->getCatalog(&catDict);
-     if (catDict.isDict()) {
-       catDict.dictLookup("StructTreeRoot", &structTreeRoot);
-     } else {
-       error(errSyntaxError, -1, "Catalog object is wrong type ({0:s})", catDict.getTypeName());
-       structTreeRoot.initNull();
-     }
-     catDict.free();
+    xref->getCatalog(&catalog);
+    if (!catalog.isDict()) {
+      error(errSyntaxError, -1, "Catalog object is wrong type ({0:s})", catalog.getTypeName());
+      catalog.free();
+      return NULL;
+    }
+
+    if (catalog.dictLookup("StructTreeRoot", &root)->isDict("StructTreeRoot")) {
+      structTreeRoot = new StructTreeRoot(doc, root.getDict());
+    }
+
+    root.free();
+    catalog.free();
   }
+  return structTreeRoot;
+}
 
-  return &structTreeRoot;
+Guint Catalog::getMarkInfo()
+{
+  if (markInfo == markInfoNull) {
+    markInfo = 0;
+
+    Object catDict;
+    catalogLocker();
+    xref->getCatalog(&catDict);
+
+    if (catDict.isDict()) {
+      Object markInfoDict;
+      catDict.dictLookup("MarkInfo", &markInfoDict);
+      if (markInfoDict.isDict()) {
+        Object value;
+
+        if (markInfoDict.dictLookup("Marked", &value)->isBool() && value.getBool())
+          markInfo |= markInfoMarked;
+        else if (!value.isNull())
+          error(errSyntaxError, -1, "Marked object is wrong type ({0:s})", value.getTypeName());
+        value.free();
+
+        if (markInfoDict.dictLookup("Suspects", &value)->isBool() && value.getBool())
+          markInfo |= markInfoSuspects;
+        else if (!value.isNull())
+          error(errSyntaxError, -1, "Suspects object is wrong type ({0:s})", value.getTypeName());
+        value.free();
+
+        if (markInfoDict.dictLookup("UserProperties", &value)->isBool() && value.getBool())
+          markInfo |= markInfoUserProperties;
+        else if (!value.isNull())
+          error(errSyntaxError, -1, "UserProperties object is wrong type ({0:s})", value.getTypeName());
+        value.free();
+      } else if (!markInfoDict.isNull()) {
+        error(errSyntaxError, -1, "MarkInfo object is wrong type ({0:s})", markInfoDict.getTypeName());
+      }
+      markInfoDict.free();
+    } else {
+      error(errSyntaxError, -1, "Catalog object is wrong type ({0:s})", catDict.getTypeName());
+    }
+    catDict.free();
+  }
+  return markInfo;
 }
 
 Object *Catalog::getOutline()
@@ -1029,3 +1086,25 @@
   return jsNameTree;
 }
 
+LinkAction* Catalog::getAdditionalAction(DocumentAdditionalActionsType type) {
+  Object additionalActionsObject;
+  LinkAction *linkAction = NULL;
+
+  if (additionalActions.fetch(doc->getXRef(), &additionalActionsObject)->isDict()) {
+    const char *key = (type == actionCloseDocument ?       "WC" :
+                       type == actionSaveDocumentStart ?   "WS" :
+                       type == actionSaveDocumentFinish ?  "DS" :
+                       type == actionPrintDocumentStart ?  "WP" :
+                       type == actionPrintDocumentFinish ? "DP" : NULL);
+
+    Object actionObject;
+
+    if (additionalActionsObject.dictLookup(key, &actionObject)->isDict())
+      linkAction = LinkAction::parseAction(&actionObject, doc->getCatalog()->getBaseURI());
+    actionObject.free();
+  }
+
+  additionalActionsObject.free();
+
+  return linkAction;
+}
diff --git a/poppler/Catalog.h b/poppler/Catalog.h
index 384f634..bc9ce20 100644
--- a/poppler/Catalog.h
+++ b/poppler/Catalog.h
@@ -22,6 +22,8 @@
 // Copyright (C) 2010 Hib Eris <hib@hiberis.nl>
 // Copyright (C) 2012 Fabio D'Urso <fabiodurso@hotmail.it>
 // Copyright (C) 2013 Thomas Freitag <Thomas.Freitag@alfa.de>
+// Copyright (C) 2013 Adrian Perez de Castro <aperez@igalia.com>
+// Copyright (C) 2013 Adrian Johnson <ajohnson@redneon.com>
 // Copyright (C) 2013 José Aliste <jaliste@src.gnome.org>
 //
 // To see a description of the changes please see the Changelog file that
@@ -49,11 +51,13 @@
 class PageAttrs;
 struct Ref;
 class LinkDest;
+class LinkAction;
 class PageLabelInfo;
 class Form;
 class OCGs;
 class ViewerPreferences;
 class FileSpec;
+class StructTreeRoot;
 
 //------------------------------------------------------------------------
 // NameTree
@@ -125,7 +129,16 @@
   GooString *readMetadata();
 
   // Return the structure tree root object.
-  Object *getStructTreeRoot();
+  StructTreeRoot *getStructTreeRoot();
+
+  // Return values from the MarkInfo dictionary as flags in a bitfield.
+  enum MarkInfoFlags {
+    markInfoNull           = 1 << 0,
+    markInfoMarked         = 1 << 1,
+    markInfoUserProperties = 1 << 2,
+    markInfoSuspects       = 1 << 3,
+  };
+  Guint getMarkInfo();
 
   // Find a page, given its object ID.  Returns page number, or 0 if
   // not found.
@@ -145,6 +158,7 @@
 
   // Get the number of javascript scripts
   int numJS() { return getJSNameTree()->numEntries(); }
+  GooString *getJSName(int i) { return getJSNameTree()->getName(i); }
 
   // Get the i'th JavaScript script (at the Document level) in the document
   GooString *getJS(int i);
@@ -195,6 +209,16 @@
   PageMode getPageMode();
   PageLayout getPageLayout();
 
+  enum DocumentAdditionalActionsType {
+    actionCloseDocument,        ///< Performed before closing the document
+    actionSaveDocumentStart,    ///< Performed before saving the document
+    actionSaveDocumentFinish,   ///< Performed after saving the document
+    actionPrintDocumentStart,   ///< Performed before printing the document
+    actionPrintDocumentFinish,  ///< Performed after printing the document
+  };
+
+  LinkAction *getAdditionalAction(DocumentAdditionalActionsType type);
+
 private:
 
   // Get page label info.
@@ -220,7 +244,8 @@
   NameTree *jsNameTree;		// Java Script name-tree
   GooString *baseURI;		// base URI for URI-type links
   Object metadata;		// metadata stream
-  Object structTreeRoot;	// structure tree root dictionary
+  StructTreeRoot *structTreeRoot;	// structure tree root
+  Guint markInfo;               // Flags from MarkInfo dictionary
   Object outline;		// outline dictionary
   Object acroForm;		// AcroForm dictionary
   Object viewerPreferences;     // ViewerPreference dictionary
@@ -229,6 +254,7 @@
   PageLabelInfo *pageLabelInfo; // info about page labels
   PageMode pageMode;		// page mode
   PageLayout pageLayout;	// page layout
+  Object additionalActions;     // page additional actions
 
   GBool cachePageTree(int page); // Cache first <page> pages.
   Object *findDestInTree(Object *tree, GooString *name, Object *obj);
diff --git a/poppler/Form.cc b/poppler/Form.cc
index 78c25e3..661ed89 100644
--- a/poppler/Form.cc
+++ b/poppler/Form.cc
@@ -7,7 +7,7 @@
 // Copyright 2006-2008 Julien Rebetez <julienr@svn.gnome.org>
 // Copyright 2007-2012 Albert Astals Cid <aacid@kde.org>
 // Copyright 2007-2008, 2011 Carlos Garcia Campos <carlosgc@gnome.org>
-// Copyright 2007 Adrian Johnson <ajohnson@redneon.com>
+// Copyright 2007, 2013 Adrian Johnson <ajohnson@redneon.com>
 // Copyright 2007 Iñigo Martínez <inigomartinez@gmail.com>
 // Copyright 2008, 2011 Pino Toscano <pino@kde.org>
 // Copyright 2008 Michael Vrable <mvrable@cs.ucsd.edu>
@@ -156,6 +156,10 @@
   return widget ? widget->getAction() : NULL;
 }
 
+LinkAction *FormWidget::getAdditionalAction(Annot::FormAdditionalActionsType type) {
+  return widget ? widget->getFormAdditionalAction(type) : NULL;
+}
+
 FormWidgetButton::FormWidgetButton (PDFDoc *docA, Object *aobj, unsigned num, Ref ref, FormField *p) :
 	FormWidget(docA, aobj, num, ref, p)
 {
diff --git a/poppler/Form.h b/poppler/Form.h
index ef67748..3778ff6 100644
--- a/poppler/Form.h
+++ b/poppler/Form.h
@@ -10,6 +10,7 @@
 // Copyright 2010 Mark Riedesel <mark@klowner.com>
 // Copyright 2011 Pino Toscano <pino@kde.org>
 // Copyright 2012 Fabio D'Urso <fabiodurso@hotmail.it>
+// Copyright 2013 Adrian Johnson <ajohnson@redneon.com>
 //
 //========================================================================
 
@@ -21,6 +22,7 @@
 #endif
 
 #include "Object.h"
+#include "Annot.h"
 
 #include <set>
 
@@ -101,6 +103,7 @@
   bool isReadOnly() const;
 
   LinkAction *getActivationAction();
+  LinkAction *getAdditionalAction(Annot::FormAdditionalActionsType type);
 
   // return the unique ID corresponding to pageNum/fieldNum
   static int encodeID (unsigned pageNum, unsigned fieldNum);
diff --git a/poppler/Function.cc b/poppler/Function.cc
index 2f94a54..81829ec 100644
--- a/poppler/Function.cc
+++ b/poppler/Function.cc
@@ -19,6 +19,7 @@
 // Copyright (C) 2011 Andrea Canciani <ranma42@gmail.com>
 // Copyright (C) 2012 Thomas Freitag <Thomas.Freitag@alfa.de>
 // Copyright (C) 2012 Adam Reichold <adamreichold@myopera.com>
+// Copyright (C) 2013 Fabio D'Urso <fabiodurso@hotmail.it>
 //
 // To see a description of the changes please see the Changelog file that
 // came with your tarball or type make ChangeLog if you are building from git
@@ -1194,7 +1195,7 @@
   codeString = new GooString();
   str->reset();
   if (!(tok = getToken(str)) || tok->cmp("{")) {
-    error(errSyntaxError, -1, "Expected '{' at start of PostScript function");
+    error(errSyntaxError, -1, "Expected '{{' at start of PostScript function");
     if (tok) {
       delete tok;
     }
diff --git a/poppler/Gfx.cc b/poppler/Gfx.cc
index f2971d6..90bf41e 100644
--- a/poppler/Gfx.cc
+++ b/poppler/Gfx.cc
@@ -460,7 +460,7 @@
   obj->initNull();
 }
 
-GfxPattern *GfxResources::lookupPattern(char *name, OutputDev *out) {
+GfxPattern *GfxResources::lookupPattern(char *name, OutputDev *out, GfxState *state) {
   GfxResources *resPtr;
   GfxPattern *pattern;
   Object obj;
@@ -468,7 +468,7 @@
   for (resPtr = this; resPtr; resPtr = resPtr->next) {
     if (resPtr->patternDict.isDict()) {
       if (!resPtr->patternDict.dictLookup(name, &obj)->isNull()) {
-	pattern = GfxPattern::parse(&obj, out);
+	pattern = GfxPattern::parse(&obj, out, state);
 	obj.free();
 	return pattern;
       }
@@ -479,7 +479,7 @@
   return NULL;
 }
 
-GfxShading *GfxResources::lookupShading(char *name, OutputDev *out) {
+GfxShading *GfxResources::lookupShading(char *name, OutputDev *out, GfxState *state) {
   GfxResources *resPtr;
   GfxShading *shading;
   Object obj;
@@ -487,7 +487,7 @@
   for (resPtr = this; resPtr; resPtr = resPtr->next) {
     if (resPtr->shadingDict.isDict()) {
       if (!resPtr->shadingDict.dictLookup(name, &obj)->isNull()) {
-	shading = GfxShading::parse(&obj, out);
+	shading = GfxShading::parse(&obj, out, state);
 	obj.free();
 	return shading;
       }
@@ -584,6 +584,9 @@
     out->clip(state);
     state->clearPath();
   }
+#ifdef USE_CMS
+  initDisplayProfile();
+#endif
 }
 
 Gfx::Gfx(PDFDoc *docA, OutputDev *outA, Dict *resDict,
@@ -633,8 +636,55 @@
     out->clip(state);
     state->clearPath();
   }
+#ifdef USE_CMS
+  initDisplayProfile();
+#endif
 }
 
+#ifdef USE_CMS
+
+#ifdef USE_LCMS1
+#include <lcms.h>
+#else
+#include <lcms2.h>
+#define LCMS_FLAGS cmsFLAGS_NOOPTIMIZE
+#endif
+
+void Gfx::initDisplayProfile() {
+   Object catDict;
+   xref->getCatalog(&catDict);
+   if (catDict.isDict()) {
+     Object outputIntents;
+     catDict.dictLookup("OutputIntents", &outputIntents);
+     if (outputIntents.isArray() && outputIntents.arrayGetLength() == 1) {
+          Object firstElement;
+          outputIntents.arrayGet(0, &firstElement);
+          if (firstElement.isDict()) {
+              Object profile;
+              firstElement.dictLookup("DestOutputProfile", &profile);
+              if (profile.isStream()) {
+                Stream *iccStream = profile.getStream();
+                int length = 0;
+                Guchar *profBuf = iccStream->toUnsignedChars(&length, 65536, 65536);
+                cmsHPROFILE hp = cmsOpenProfileFromMem(profBuf,length);
+                if (hp == 0) {
+                  error(errSyntaxWarning, -1, "read ICCBased color space profile error");
+                } else {
+                  state->setDisplayProfile(hp);
+                }
+                gfree(profBuf);
+              }
+              profile.free();
+          }
+          firstElement.free();
+     }
+     outputIntents.free();
+   }
+   catDict.free();
+}
+
+#endif
+
 Gfx::~Gfx() {
   while (stateGuards.size()) {
     popStateGuard();
@@ -1183,7 +1233,7 @@
 	  blendingColorSpace = NULL;
 	  isolated = knockout = gFalse;
 	  if (!obj4.dictLookup("CS", &obj5)->isNull()) {
-	    blendingColorSpace = GfxColorSpace::parse(&obj5, out);
+	    blendingColorSpace = GfxColorSpace::parse(&obj5, out, state);
 	  }
 	  obj5.free();
 	  if (obj4.dictLookup("I", &obj5)->isBool()) {
@@ -1371,6 +1421,7 @@
 }
 
 void Gfx::opSetRenderingIntent(Object args[], int numArgs) {
+  state->setRenderingIntent(args[0].getName());
 }
 
 //------------------------------------------------------------------------
@@ -1385,7 +1436,7 @@
   state->setFillPattern(NULL);
   res->lookupColorSpace("DefaultGray", &obj);
   if (!obj.isNull()) {
-    colorSpace = GfxColorSpace::parse(&obj, out);
+    colorSpace = GfxColorSpace::parse(&obj, out, state);
   }
   if (colorSpace == NULL) {
     colorSpace = new GfxDeviceGrayColorSpace();
@@ -1406,7 +1457,7 @@
   state->setStrokePattern(NULL);
   res->lookupColorSpace("DefaultGray", &obj);
   if (!obj.isNull()) {
-    colorSpace = GfxColorSpace::parse(&obj, out);
+    colorSpace = GfxColorSpace::parse(&obj, out, state);
   }
   if (colorSpace == NULL) {
     colorSpace = new GfxDeviceGrayColorSpace();
@@ -1427,7 +1478,7 @@
 
   res->lookupColorSpace("DefaultCMYK", &obj);
   if (!obj.isNull()) {
-    colorSpace = GfxColorSpace::parse(&obj, out);
+    colorSpace = GfxColorSpace::parse(&obj, out, state);
   }
   if (colorSpace == NULL) {
     colorSpace = new GfxDeviceCMYKColorSpace();
@@ -1452,7 +1503,7 @@
   state->setStrokePattern(NULL);
   res->lookupColorSpace("DefaultCMYK", &obj);
   if (!obj.isNull()) {
-    colorSpace = GfxColorSpace::parse(&obj, out);
+    colorSpace = GfxColorSpace::parse(&obj, out, state);
   }
   if (colorSpace == NULL) {
     colorSpace = new GfxDeviceCMYKColorSpace();
@@ -1476,7 +1527,7 @@
   state->setFillPattern(NULL);
   res->lookupColorSpace("DefaultRGB", &obj);
   if (!obj.isNull()) {
-    colorSpace = GfxColorSpace::parse(&obj, out);
+    colorSpace = GfxColorSpace::parse(&obj, out, state);
   }
   if (colorSpace == NULL) {
     colorSpace = new GfxDeviceRGBColorSpace();
@@ -1500,7 +1551,7 @@
   state->setStrokePattern(NULL);
   res->lookupColorSpace("DefaultRGB", &obj);
   if (!obj.isNull()) {
-    colorSpace = GfxColorSpace::parse(&obj, out);
+    colorSpace = GfxColorSpace::parse(&obj, out, state);
   }
   if (colorSpace == NULL) {
     colorSpace = new GfxDeviceRGBColorSpace();
@@ -1522,9 +1573,9 @@
 
   res->lookupColorSpace(args[0].getName(), &obj);
   if (obj.isNull()) {
-    colorSpace = GfxColorSpace::parse(&args[0], out);
+    colorSpace = GfxColorSpace::parse(&args[0], out, state);
   } else {
-    colorSpace = GfxColorSpace::parse(&obj, out);
+    colorSpace = GfxColorSpace::parse(&obj, out, state);
   }
   obj.free();
   if (colorSpace) {
@@ -1547,9 +1598,9 @@
   state->setStrokePattern(NULL);
   res->lookupColorSpace(args[0].getName(), &obj);
   if (obj.isNull()) {
-    colorSpace = GfxColorSpace::parse(&args[0], out);
+    colorSpace = GfxColorSpace::parse(&args[0], out, state);
   } else {
-    colorSpace = GfxColorSpace::parse(&obj, out);
+    colorSpace = GfxColorSpace::parse(&obj, out, state);
   }
   obj.free();
   if (colorSpace) {
@@ -1620,7 +1671,7 @@
     }
     if (numArgs > 0) {
       if (args[numArgs-1].isName() &&
-	  (pattern = res->lookupPattern(args[numArgs-1].getName(), out))) {
+	  (pattern = res->lookupPattern(args[numArgs-1].getName(), out, state))) {
         state->setFillPattern(pattern);
       }
     }
@@ -1672,7 +1723,7 @@
       return;
     }
     if (args[numArgs-1].isName() &&
-	(pattern = res->lookupPattern(args[numArgs-1].getName(), out))) {
+	(pattern = res->lookupPattern(args[numArgs-1].getName(), out, state))) {
       state->setStrokePattern(pattern);
     }
 
@@ -2382,7 +2433,7 @@
     return;
   }
 
-  if (!(shading = res->lookupShading(args[0].getName(), out))) {
+  if (!(shading = res->lookupShading(args[0].getName(), out, state))) {
     return;
   }
 
@@ -4339,14 +4390,29 @@
       }
     }
     if (!obj1.isNull()) {
-      colorSpace = GfxColorSpace::parse(&obj1, out);
+      Object objIntent;
+      char *tempIntent = NULL;
+      dict->lookup("Intent", &objIntent);
+      if (objIntent.isName()) {
+        tempIntent = state->getRenderingIntent();
+        if (tempIntent != NULL) {
+          tempIntent = strdup(tempIntent);
+        }
+        state->setRenderingIntent(objIntent.getName());
+      }
+      colorSpace = GfxColorSpace::parse(&obj1, out, state);
+      if (objIntent.isName()) {
+        state->setRenderingIntent(tempIntent);
+        free(tempIntent);
+      }
+      objIntent.free();
     } else if (csMode == streamCSDeviceGray) {
       Object objCS;
       res->lookupColorSpace("DefaultGray", &objCS);
       if (objCS.isNull()) {
         colorSpace = new GfxDeviceGrayColorSpace();
       } else {
-        colorSpace = GfxColorSpace::parse(&objCS, out);
+        colorSpace = GfxColorSpace::parse(&objCS, out, state);
       }
       objCS.free();
     } else if (csMode == streamCSDeviceRGB) {
@@ -4355,7 +4421,7 @@
       if (objCS.isNull()) {
         colorSpace = new GfxDeviceRGBColorSpace();
       } else {
-        colorSpace = GfxColorSpace::parse(&objCS, out);
+        colorSpace = GfxColorSpace::parse(&objCS, out, state);
       }
       objCS.free();
     } else if (csMode == streamCSDeviceCMYK) {
@@ -4364,7 +4430,7 @@
       if (objCS.isNull()) {
         colorSpace = new GfxDeviceCMYKColorSpace();
       } else {
-        colorSpace = GfxColorSpace::parse(&objCS, out);
+        colorSpace = GfxColorSpace::parse(&objCS, out, state);
       }
       objCS.free();
     } else {
@@ -4459,7 +4525,7 @@
 	  obj2.free();
 	}
       }
-      maskColorSpace = GfxColorSpace::parse(&obj1, out);
+      maskColorSpace = GfxColorSpace::parse(&obj1, out, state);
       obj1.free();
       if (!maskColorSpace || maskColorSpace->getMode() != csDeviceGray) {
 	goto err1;
@@ -4755,7 +4821,7 @@
   if (dict->lookup("Group", &obj1)->isDict()) {
     if (obj1.dictLookup("S", &obj2)->isName("Transparency")) {
       if (!obj1.dictLookup("CS", &obj3)->isNull()) {
-	blendingColorSpace = GfxColorSpace::parse(&obj3, out);
+	blendingColorSpace = GfxColorSpace::parse(&obj3, out, state);
       }
       obj3.free();
       if (obj1.dictLookup("I", &obj3)->isBool()) {
diff --git a/poppler/Gfx.h b/poppler/Gfx.h
index aba3b7e..a82f9f4 100644
--- a/poppler/Gfx.h
+++ b/poppler/Gfx.h
@@ -117,8 +117,8 @@
   GBool lookupXObjectNF(char *name, Object *obj);
   GBool lookupMarkedContentNF(char *name, Object *obj);
   void lookupColorSpace(const char *name, Object *obj);
-  GfxPattern *lookupPattern(char *name, OutputDev *out);
-  GfxShading *lookupShading(char *name, OutputDev *out);
+  GfxPattern *lookupPattern(char *name, OutputDev *out, GfxState *state);
+  GfxShading *lookupShading(char *name, OutputDev *out, GfxState *state);
   GBool lookupGState(char *name, Object *obj);
   GBool lookupGStateNF(char *name, Object *obj);
 
@@ -156,7 +156,9 @@
       PDFRectangle *box, PDFRectangle *cropBox,
       GBool (*abortCheckCbkA)(void *data) = NULL,
       void *abortCheckCbkDataA = NULL, XRef *xrefA = NULL);
-
+#ifdef USE_CMS
+  void initDisplayProfile();
+#endif
   ~Gfx();
 
   XRef *getXRef() { return xref; }
diff --git a/poppler/GfxFont.cc b/poppler/GfxFont.cc
index ea22af8..c54181b 100644
--- a/poppler/GfxFont.cc
+++ b/poppler/GfxFont.cc
@@ -30,6 +30,7 @@
 // Copyright (C) 2012 Yi Yang <ahyangyi@gmail.com>
 // Copyright (C) 2012 Suzuki Toshiya <mpsuzuki@hiroshima-u.ac.jp>
 // Copyright (C) 2012 Thomas Freitag <Thomas.Freitag@alfa.de>
+// Copyright (C) 2013 Jason Crain <jason@aquaticape.us>
 //
 // To see a description of the changes please see the Changelog file that
 // came with your tarball or type make ChangeLog if you are building from git
@@ -1221,10 +1222,16 @@
 
   // pass 1: use the name-to-Unicode mapping table
   missing = hex = gFalse;
+  GBool isZapfDingbats = name && name->endsWith("ZapfDingbats");
   for (code = 0; code < 256; ++code) {
     if ((charName = enc[code])) {
-      if (!(toUnicode[code] = globalParams->mapNameToUnicode(charName)) &&
-	  strcmp(charName, ".notdef")) {
+      if (isZapfDingbats) {
+	// include ZapfDingbats names
+	toUnicode[code] = globalParams->mapNameToUnicodeAll(charName);
+      } else {
+	toUnicode[code] = globalParams->mapNameToUnicodeText(charName);
+      }
+      if (!toUnicode[code] && strcmp(charName, ".notdef")) {
 	// if it wasn't in the name-to-Unicode table, check for a
 	// name that looks like 'Axx' or 'xx', where 'A' is any letter
 	// and 'xx' is two hex digits
@@ -1485,7 +1492,7 @@
   // corresponding character in that list.
   // 3.2. otherwise, if the component is in the Adobe Glyph List, then map it
   // to the corresponding character in that list.
-  if (names && (uBuf[0] = globalParams->mapNameToUnicode(charName))) {
+  if (names && (uBuf[0] = globalParams->mapNameToUnicodeText(charName))) {
     return 1;
   }
   if (numeric) {
@@ -1674,7 +1681,7 @@
   } else if (useUnicode) {
     Unicode *uAux;
     for (i = 0; i < 256; ++i) {
-      if (((charName = enc[i]) && (u = globalParams->mapNameToUnicode(charName))))
+      if (((charName = enc[i]) && (u = globalParams->mapNameToUnicodeAll(charName))))
 	map[i] = ff->mapCodeToGID(cmap, u);
       else
       {
diff --git a/poppler/GfxState.cc b/poppler/GfxState.cc
index 2b0a29b..c6f855b 100644
--- a/poppler/GfxState.cc
+++ b/poppler/GfxState.cc
@@ -18,13 +18,14 @@
 // Copyright (C) 2006, 2010 Carlos Garcia Campos <carlosgc@gnome.org>
 // Copyright (C) 2006-2013 Albert Astals Cid <aacid@kde.org>
 // Copyright (C) 2009, 2012 Koji Otani <sho@bbr.jp>
-// Copyright (C) 2009, 2011, 2012 Thomas Freitag <Thomas.Freitag@alfa.de>
+// Copyright (C) 2009, 2011-2013 Thomas Freitag <Thomas.Freitag@alfa.de>
 // Copyright (C) 2009 Christian Persch <chpe@gnome.org>
 // Copyright (C) 2010 Paweł Wiejacha <pawel.wiejacha@gmail.com>
 // Copyright (C) 2010 Christian Feuersänger <cfeuersaenger@googlemail.com>
 // Copyright (C) 2011 Andrea Canciani <ranma42@gmail.com>
 // Copyright (C) 2012 William Bader <williambader@hotmail.com>
 // Copyright (C) 2013 Lu Wang <coolwanglu@gmail.com>
+// Copyright (C) 2013 Hib Eris <hib@hiberis.nl>
 // Copyright (C) 2013 Fabio D'Urso <fabiodurso@hotmail.it>
 //
 // To see a description of the changes please see the Changelog file that
@@ -54,6 +55,7 @@
 #include "GlobalParams.h"
 #include "PopplerCache.h"
 #include "OutputDev.h"
+#include "splash/SplashTypes.h"
 
 //------------------------------------------------------------------------
 
@@ -156,6 +158,8 @@
 
 #ifdef USE_CMS
 
+static const std::map<unsigned int, unsigned int>::size_type CMSCACHE_LIMIT = 2048;
+
 #ifdef USE_LCMS1
 #include <lcms.h>
 #define cmsColorSpaceSignature icColorSpaceSignature
@@ -199,9 +203,11 @@
 }
 
 // transformA should be a cmsHTRANSFORM
-GfxColorTransform::GfxColorTransform(void *transformA) {
+GfxColorTransform::GfxColorTransform(void *transformA, int cmsIntentA, unsigned int transformPixelTypeA) {
   transform = transformA;
   refCount = 1;
+  cmsIntent = cmsIntentA;
+  transformPixelType = transformPixelTypeA;
 }
 
 GfxColorTransform::~GfxColorTransform() {
@@ -229,6 +235,25 @@
 
 void GfxColorSpace::setDisplayProfile(void *displayProfileA) {
   displayProfile = displayProfileA;
+  if (displayProfile != NULL) {
+    cmsHTRANSFORM transform;
+    unsigned int nChannels;
+
+    displayPixelType = getCMSColorSpaceType(cmsGetColorSpace(displayProfile));
+    nChannels = getCMSNChannels(cmsGetColorSpace(displayProfile));
+    // create transform from XYZ
+    cmsHPROFILE XYZProfile = cmsCreateXYZProfile();
+    if ((transform = cmsCreateTransform(XYZProfile, TYPE_XYZ_DBL,
+	   displayProfile,
+	   COLORSPACE_SH(displayPixelType) |
+	     CHANNELS_SH(nChannels) | BYTES_SH(1),
+	  INTENT_RELATIVE_COLORIMETRIC,LCMS_FLAGS)) == 0) {
+      error(errSyntaxWarning, -1, "Can't create Lab transform");
+    } else {
+      XYZ2DisplayTransform = new GfxColorTransform(transform, INTENT_RELATIVE_COLORIMETRIC, displayPixelType);
+    }
+    cmsCloseProfile(XYZProfile);
+  }
 }
 
 void GfxColorSpace::setDisplayProfileName(GooString *name) {
@@ -257,7 +282,7 @@
 GfxColorSpace::~GfxColorSpace() {
 }
 
-GfxColorSpace *GfxColorSpace::parse(Object *csObj, OutputDev *out, int recursion) {
+GfxColorSpace *GfxColorSpace::parse(Object *csObj, OutputDev *out, GfxState *state, int recursion) {
   GfxColorSpace *cs;
   Object obj1;
 
@@ -288,21 +313,21 @@
     } else if (obj1.isName("DeviceCMYK") || obj1.isName("CMYK")) {
       cs = new GfxDeviceCMYKColorSpace();
     } else if (obj1.isName("CalGray")) {
-      cs = GfxCalGrayColorSpace::parse(csObj->getArray());
+      cs = GfxCalGrayColorSpace::parse(csObj->getArray(), state);
     } else if (obj1.isName("CalRGB")) {
-      cs = GfxCalRGBColorSpace::parse(csObj->getArray());
+      cs = GfxCalRGBColorSpace::parse(csObj->getArray(), state);
     } else if (obj1.isName("Lab")) {
-      cs = GfxLabColorSpace::parse(csObj->getArray());
+      cs = GfxLabColorSpace::parse(csObj->getArray(), state);
     } else if (obj1.isName("ICCBased")) {
-      cs = GfxICCBasedColorSpace::parse(csObj->getArray(), out, recursion);
+      cs = GfxICCBasedColorSpace::parse(csObj->getArray(), out, state, recursion);
     } else if (obj1.isName("Indexed") || obj1.isName("I")) {
-      cs = GfxIndexedColorSpace::parse(csObj->getArray(), out, recursion);
+      cs = GfxIndexedColorSpace::parse(csObj->getArray(), out, state, recursion);
     } else if (obj1.isName("Separation")) {
-      cs = GfxSeparationColorSpace::parse(csObj->getArray(), out, recursion);
+      cs = GfxSeparationColorSpace::parse(csObj->getArray(), out, state, recursion);
     } else if (obj1.isName("DeviceN")) {
-      cs = GfxDeviceNColorSpace::parse(csObj->getArray(), out, recursion);
+      cs = GfxDeviceNColorSpace::parse(csObj->getArray(), out, state, recursion);
     } else if (obj1.isName("Pattern")) {
-      cs = GfxPatternColorSpace::parse(csObj->getArray(), out, recursion);
+      cs = GfxPatternColorSpace::parse(csObj->getArray(), out, state, recursion);
     } else {
       error(errSyntaxWarning, -1, "Bad color space");
     }
@@ -421,13 +446,13 @@
     // create transform from XYZ
     cmsHPROFILE XYZProfile = cmsCreateXYZProfile();
     if ((transform = cmsCreateTransform(XYZProfile, TYPE_XYZ_DBL,
-	   displayProfile, 
+	   displayProfile,
 	   COLORSPACE_SH(displayPixelType) |
 	     CHANNELS_SH(nChannels) | BYTES_SH(1),
 	  INTENT_RELATIVE_COLORIMETRIC,LCMS_FLAGS)) == 0) {
       error(errSyntaxWarning, -1, "Can't create Lab transform");
     } else {
-      XYZ2DisplayTransform = new GfxColorTransform(transform);
+      XYZ2DisplayTransform = new GfxColorTransform(transform, INTENT_RELATIVE_COLORIMETRIC, displayPixelType);
     }
     cmsCloseProfile(XYZProfile);
   }
@@ -605,6 +630,24 @@
   }
 }
 
+void GfxDeviceGrayColorSpace::getCMYKLine(Guchar *in, Guchar *out, int length) {
+  for (int i = 0; i < length; i++) {
+    *out++ = 0;
+    *out++ = 0;
+    *out++ = 0;
+    *out++ = in[i];
+  }
+}
+
+void GfxDeviceGrayColorSpace::getDeviceNLine(Guchar *in, Guchar *out, int length) {
+  for (int i = 0; i < length; i++) {
+    for (int j = 0; j < SPOT_NCOMPS+4; j++)
+      out[j] = 0;
+    out[4] = in[i];
+    out += (SPOT_NCOMPS+4);
+  }
+}
+
 void GfxDeviceGrayColorSpace::getCMYK(GfxColor *color, GfxCMYK *cmyk) {
   cmyk->c = cmyk->m = cmyk->y = 0;
   cmyk->k = clip01(gfxColorComp1 - color->c[0]);
@@ -631,6 +674,11 @@
 }
 
 GfxCalGrayColorSpace::~GfxCalGrayColorSpace() {
+#ifdef USE_CMS
+  if (transform != NULL) {
+    if (transform->unref() == 0) delete transform;
+  }
+#endif
 }
 
 GfxColorSpace *GfxCalGrayColorSpace::copy() {
@@ -644,6 +692,10 @@
   cs->blackY = blackY;
   cs->blackZ = blackZ;
   cs->gamma = gamma;
+#ifdef USE_CMS
+  cs->transform = transform;
+  if (transform != NULL) transform->ref();
+#endif
   return cs;
 }
 
@@ -655,7 +707,7 @@
   {  0.055643, -0.204026,  1.057229 }
 };
 
-GfxColorSpace *GfxCalGrayColorSpace::parse(Array *arr) {
+GfxColorSpace *GfxCalGrayColorSpace::parse(Array *arr, GfxState *state) {
   GfxCalGrayColorSpace *cs;
   Object obj1, obj2, obj3;
 
@@ -713,7 +765,10 @@
   cs->kb = 1 / (xyzrgb[2][0] * cs->whiteX +
 		xyzrgb[2][1] * cs->whiteY +
 		xyzrgb[2][2] * cs->whiteZ);
-
+#ifdef USE_CMS
+  cs->transform = (state != NULL) ? state->getXYZ2DisplayTransform() : XYZ2DisplayTransform;
+  if (cs->transform != NULL) cs->transform->ref();
+#endif
   return cs;
 }
 
@@ -732,7 +787,7 @@
   GfxRGB rgb;
 
 #ifdef USE_CMS
-  if (XYZ2DisplayTransform != NULL && displayPixelType == PT_GRAY) {
+  if (transform != NULL && transform->getTransformPixelType() == PT_GRAY) {
     Guchar out[gfxColorMaxComps];
     double in[gfxColorMaxComps];
     double X, Y, Z;
@@ -741,7 +796,7 @@
     in[0] = clip01(X);
     in[1] = clip01(Y);
     in[2] = clip01(Z);
-    XYZ2DisplayTransform->doTransform(in,out,1);
+    transform->doTransform(in,out,1);
     *gray = byteToCol(out[0]);
     return;
   }
@@ -758,14 +813,14 @@
 
   getXYZ(color,&X,&Y,&Z);
 #ifdef USE_CMS
-  if (XYZ2DisplayTransform != NULL && displayPixelType == PT_RGB) {
+  if (transform != NULL && transform->getTransformPixelType() == PT_RGB) {
     Guchar out[gfxColorMaxComps];
     double in[gfxColorMaxComps];
     
     in[0] = clip01(X);
     in[1] = clip01(Y);
     in[2] = clip01(Z);
-    XYZ2DisplayTransform->doTransform(in,out,1);
+    transform->doTransform(in,out,1);
     rgb->r = byteToCol(out[0]);
     rgb->g = byteToCol(out[1]);
     rgb->b = byteToCol(out[2]);
@@ -789,7 +844,7 @@
   GfxColorComp c, m, y, k;
 
 #ifdef USE_CMS
-  if (XYZ2DisplayTransform != NULL && displayPixelType == PT_CMYK) {
+  if (transform != NULL && transform->getTransformPixelType() == PT_CMYK) {
     double in[gfxColorMaxComps];
     Guchar out[gfxColorMaxComps];
     double X, Y, Z;
@@ -799,7 +854,7 @@
     in[1] = clip01(Y);
     in[2] = clip01(Z);
     
-    XYZ2DisplayTransform->doTransform(in,out,1);
+    transform->doTransform(in,out,1);
     cmyk->c = byteToCol(out[0]);
     cmyk->m = byteToCol(out[1]);
     cmyk->y = byteToCol(out[2]);
@@ -901,6 +956,52 @@
     *out++ = 255;
   }
 }
+
+void GfxDeviceRGBColorSpace::getCMYKLine(Guchar *in, Guchar *out, int length) {
+  GfxColorComp c, m, y, k;
+
+  for (int i = 0; i < length; i++) {
+    c = byteToCol(255 - *in++);
+    m = byteToCol(255 - *in++);
+    y = byteToCol(255 - *in++);
+    k = c;
+    if (m < k) {
+      k = m;
+    }
+    if (y < k) {
+      k = y;
+    }
+    *out++ = colToByte(c - k);
+    *out++ = colToByte(m - k);
+    *out++ = colToByte(y - k);
+    *out++ = colToByte(k);
+  }
+}
+
+void GfxDeviceRGBColorSpace::getDeviceNLine(Guchar *in, Guchar *out, int length) {
+  GfxColorComp c, m, y, k;
+
+  for (int i = 0; i < length; i++) {
+    for (int j = 0; j < SPOT_NCOMPS+4; j++) 
+      out[j] = 0;
+    c = byteToCol(255 - *in++);
+    m = byteToCol(255 - *in++);
+    y = byteToCol(255 - *in++);
+    k = c;
+    if (m < k) {
+      k = m;
+    }
+    if (y < k) {
+      k = y;
+    }
+    out[0] = colToByte(c - k);
+    out[1] = colToByte(m - k);
+    out[2] = colToByte(y - k);
+    out[3] = colToByte(k);
+    out += (SPOT_NCOMPS+4);
+  }
+}
+
 void GfxDeviceRGBColorSpace::getCMYK(GfxColor *color, GfxCMYK *cmyk) {
   GfxColorComp c, m, y, k;
 
@@ -951,6 +1052,11 @@
 }
 
 GfxCalRGBColorSpace::~GfxCalRGBColorSpace() {
+#ifdef USE_CMS
+  if (transform != NULL) {
+    if (transform->unref() == 0) delete transform;
+  }
+#endif
 }
 
 GfxColorSpace *GfxCalRGBColorSpace::copy() {
@@ -970,10 +1076,14 @@
   for (i = 0; i < 9; ++i) {
     cs->mat[i] = mat[i];
   }
+#ifdef USE_CMS
+  cs->transform = transform;
+  if (transform != NULL) transform->ref();
+#endif
   return cs;
 }
 
-GfxColorSpace *GfxCalRGBColorSpace::parse(Array *arr) {
+GfxColorSpace *GfxCalRGBColorSpace::parse(Array *arr, GfxState *state) {
   GfxCalRGBColorSpace *cs;
   Object obj1, obj2, obj3;
   int i;
@@ -1055,6 +1165,10 @@
 		xyzrgb[2][1] * cs->whiteY +
 		xyzrgb[2][2] * cs->whiteZ);
 
+#ifdef USE_CMS
+  cs->transform = (state != NULL) ? state->getXYZ2DisplayTransform() : XYZ2DisplayTransform;
+  if (cs->transform != NULL) cs->transform->ref();
+#endif
   return cs;
 }
 
@@ -1075,7 +1189,7 @@
   GfxRGB rgb;
 
 #ifdef USE_CMS
-  if (XYZ2DisplayTransform != NULL && displayPixelType == PT_GRAY) {
+  if (transform != NULL && transform->getTransformPixelType() == PT_GRAY) {
     Guchar out[gfxColorMaxComps];
     double in[gfxColorMaxComps];
     double X, Y, Z;
@@ -1084,7 +1198,7 @@
     in[0] = clip01(X);
     in[1] = clip01(Y);
     in[2] = clip01(Z);
-    XYZ2DisplayTransform->doTransform(in,out,1);
+    transform->doTransform(in,out,1);
     *gray = byteToCol(out[0]);
     return;
   }
@@ -1101,14 +1215,14 @@
 
   getXYZ(color,&X,&Y,&Z);
 #ifdef USE_CMS
-  if (XYZ2DisplayTransform != NULL && displayPixelType == PT_RGB) {
+  if (transform != NULL && transform->getTransformPixelType() == PT_RGB) {
     Guchar out[gfxColorMaxComps];
     double in[gfxColorMaxComps];
     
     in[0] = clip01(X/whiteX);
     in[1] = clip01(Y/whiteY);
     in[2] = clip01(Z/whiteZ);
-    XYZ2DisplayTransform->doTransform(in,out,1);
+    transform->doTransform(in,out,1);
     rgb->r = byteToCol(out[0]);
     rgb->g = byteToCol(out[1]);
     rgb->b = byteToCol(out[2]);
@@ -1129,7 +1243,7 @@
   GfxColorComp c, m, y, k;
 
 #ifdef USE_CMS
-  if (XYZ2DisplayTransform != NULL && displayPixelType == PT_CMYK) {
+  if (transform != NULL && transform->getTransformPixelType() == PT_CMYK) {
     double in[gfxColorMaxComps];
     Guchar out[gfxColorMaxComps];
     double X, Y, Z;
@@ -1138,7 +1252,7 @@
     in[0] = clip01(X);
     in[1] = clip01(Y);
     in[2] = clip01(Z);
-    XYZ2DisplayTransform->doTransform(in,out,1);
+    transform->doTransform(in,out,1);
     cmyk->c = byteToCol(out[0]);
     cmyk->m = byteToCol(out[1]);
     cmyk->y = byteToCol(out[2]);
@@ -1267,6 +1381,29 @@
   }
 }
 
+void GfxDeviceCMYKColorSpace::getCMYKLine(Guchar *in, Guchar *out, int length)
+{
+  for (int i = 0; i < length; i++) {
+    *out++ = *in++;
+    *out++ = *in++;
+    *out++ = *in++;
+    *out++ = *in++;
+  }
+}
+
+void GfxDeviceCMYKColorSpace::getDeviceNLine(Guchar *in, Guchar *out, int length)
+{
+  for (int i = 0; i < length; i++) {
+    for (int j = 0; j < SPOT_NCOMPS+4; j++)
+      out[j] = 0;
+    out[0] = *in++;
+    out[1] = *in++;
+    out[2] = *in++;
+    out[3] = *in++;
+    out += (SPOT_NCOMPS+4);
+  }
+}
+
 void GfxDeviceCMYKColorSpace::getCMYK(GfxColor *color, GfxCMYK *cmyk) {
   cmyk->c = clip01(color->c[0]);
   cmyk->m = clip01(color->c[1]);
@@ -1302,6 +1439,11 @@
 }
 
 GfxLabColorSpace::~GfxLabColorSpace() {
+#ifdef USE_CMS
+  if (transform != NULL) {
+    if (transform->unref() == 0) delete transform;
+  }
+#endif
 }
 
 GfxColorSpace *GfxLabColorSpace::copy() {
@@ -1321,10 +1463,14 @@
   cs->kr = kr;
   cs->kg = kg;
   cs->kb = kb;
+#ifdef USE_CMS
+  cs->transform = transform;
+  if (transform != NULL) transform->ref();
+#endif
   return cs;
 }
 
-GfxColorSpace *GfxLabColorSpace::parse(Array *arr) {
+GfxColorSpace *GfxLabColorSpace::parse(Array *arr, GfxState *state) {
   GfxLabColorSpace *cs;
   Object obj1, obj2, obj3;
 
@@ -1389,6 +1535,10 @@
 		xyzrgb[2][1] * cs->whiteY +
 		xyzrgb[2][2] * cs->whiteZ);
 
+#ifdef USE_CMS
+  cs->transform = (state != NULL) ? state->getXYZ2DisplayTransform() : XYZ2DisplayTransform;
+  if (cs->transform != NULL) cs->transform->ref();
+#endif
   return cs;
 }
 
@@ -1396,12 +1546,12 @@
   GfxRGB rgb;
 
 #ifdef USE_CMS
-  if (XYZ2DisplayTransform != NULL && displayPixelType == PT_GRAY) {
+  if (transform != NULL && transform->getTransformPixelType() == PT_GRAY) {
     Guchar out[gfxColorMaxComps];
     double in[gfxColorMaxComps];
     
     getXYZ(color, &in[0], &in[1], &in[2]);
-    XYZ2DisplayTransform->doTransform(in,out,1);
+    transform->doTransform(in,out,1);
     *gray = byteToCol(out[0]);
     return;
   }
@@ -1448,18 +1598,40 @@
 
   getXYZ(color, &X, &Y, &Z);
 #ifdef USE_CMS
-  if (XYZ2DisplayTransform != NULL && displayPixelType == PT_RGB) {
+  if (transform != NULL && transform->getTransformPixelType() == PT_RGB) {
     Guchar out[gfxColorMaxComps];
     double in[gfxColorMaxComps];
     
     in[0] = clip01(X);
     in[1] = clip01(Y);
     in[2] = clip01(Z);
-    XYZ2DisplayTransform->doTransform(in,out,1);
+    transform->doTransform(in,out,1);
     rgb->r = byteToCol(out[0]);
     rgb->g = byteToCol(out[1]);
     rgb->b = byteToCol(out[2]);
     return;
+  } else if (transform != NULL && transform->getTransformPixelType() == PT_CMYK) {
+    Guchar out[gfxColorMaxComps];
+    double in[gfxColorMaxComps];
+    double c, m, y, k, c1, m1, y1, k1, r, g, b;
+
+    in[0] = clip01(X);
+    in[1] = clip01(Y);
+    in[2] = clip01(Z);
+    transform->doTransform(in,out,1);
+    c = byteToDbl(out[0]);
+    m = byteToDbl(out[1]);
+    y = byteToDbl(out[2]);
+    k = byteToDbl(out[3]);
+    c1 = 1 - c;
+    m1 = 1 - m;
+    y1 = 1 - y;
+    k1 = 1 - k;
+    cmykToRGBMatrixMultiplication(c, m, y, k, c1, m1, y1, k1, r, g, b);
+    rgb->r = clip01(dblToCol(r));
+    rgb->g = clip01(dblToCol(g));
+    rgb->b = clip01(dblToCol(b));
+    return;
   }
 #endif
   X *= whiteX;
@@ -1479,12 +1651,12 @@
   GfxColorComp c, m, y, k;
 
 #ifdef USE_CMS
-  if (XYZ2DisplayTransform != NULL && displayPixelType == PT_CMYK) {
+  if (transform != NULL && transform->getTransformPixelType() == PT_CMYK) {
     double in[gfxColorMaxComps];
     Guchar out[gfxColorMaxComps];
     
     getXYZ(color, &in[0], &in[1], &in[2]);
-    XYZ2DisplayTransform->doTransform(in,out,1);
+    transform->doTransform(in,out,1);
     cmyk->c = byteToCol(out[0]);
     cmyk->m = byteToCol(out[1]);
     cmyk->y = byteToCol(out[2]);
@@ -1627,7 +1799,7 @@
   return cs;
 }
 
-GfxColorSpace *GfxICCBasedColorSpace::parse(Array *arr, OutputDev *out, int recursion) {
+GfxColorSpace *GfxICCBasedColorSpace::parse(Array *arr, OutputDev *out, GfxState *state, int recursion) {
   GfxICCBasedColorSpace *cs;
   Ref iccProfileStreamA;
   int nCompsA;
@@ -1656,7 +1828,24 @@
     if (item != NULL)
     {
       cs = static_cast<GfxICCBasedColorSpace*>(item->cs->copy());
-      return cs;
+      int transformIntent = cs->getIntent();
+      int cmsIntent = INTENT_RELATIVE_COLORIMETRIC;
+      if (state != NULL) {
+        const char *intent = state->getRenderingIntent();
+        if (intent != NULL) {
+          if (strcmp(intent, "AbsoluteColorimetric") == 0) {
+            cmsIntent = INTENT_ABSOLUTE_COLORIMETRIC;
+          } else if (strcmp(intent, "Saturation") == 0) {
+            cmsIntent = INTENT_SATURATION;
+          } else if (strcmp(intent, "Perceptual") == 0) {
+            cmsIntent = INTENT_PERCEPTUAL;
+          }
+        }
+      }
+      if (transformIntent == cmsIntent) {
+        return cs;
+      }
+      delete cs;
     }
   }
 #endif
@@ -1682,7 +1871,7 @@
     nCompsA = 4;
   }
   if (dict->lookup("Alternate", &obj2)->isNull() ||
-      !(altA = GfxColorSpace::parse(&obj2, out, recursion + 1))) {
+      !(altA = GfxColorSpace::parse(&obj2, out, state, recursion + 1))) {
     switch (nCompsA) {
     case 1:
       altA = new GfxDeviceGrayColorSpace();
@@ -1732,32 +1921,46 @@
   if (hp == 0) {
     error(errSyntaxWarning, -1, "read ICCBased color space profile error");
   } else {
-    cmsHPROFILE dhp = displayProfile;
+    cmsHPROFILE dhp = (state != NULL && state->getDisplayProfile() != NULL) ? state->getDisplayProfile() : displayProfile;
     if (dhp == NULL) dhp = RGBProfile;
     unsigned int cst = getCMSColorSpaceType(cmsGetColorSpace(hp));
     unsigned int dNChannels = getCMSNChannels(cmsGetColorSpace(dhp));
     unsigned int dcst = getCMSColorSpaceType(cmsGetColorSpace(dhp));
     cmsHTRANSFORM transform;
+
+    int cmsIntent = INTENT_RELATIVE_COLORIMETRIC;
+    if (state != NULL) {
+      const char *intent = state->getRenderingIntent();
+      if (intent != NULL) {
+        if (strcmp(intent, "AbsoluteColorimetric") == 0) {
+          cmsIntent = INTENT_ABSOLUTE_COLORIMETRIC;
+        } else if (strcmp(intent, "Saturation") == 0) {
+          cmsIntent = INTENT_SATURATION;
+        } else if (strcmp(intent, "Perceptual") == 0) {
+          cmsIntent = INTENT_PERCEPTUAL;
+        }
+      }
+    }
     if ((transform = cmsCreateTransform(hp,
 	   COLORSPACE_SH(cst) |CHANNELS_SH(nCompsA) | BYTES_SH(1),
 	   dhp,
 	   COLORSPACE_SH(dcst) |
 	     CHANNELS_SH(dNChannels) | BYTES_SH(1),
-	  INTENT_RELATIVE_COLORIMETRIC,LCMS_FLAGS)) == 0) {
+	   cmsIntent, LCMS_FLAGS)) == 0) {
       error(errSyntaxWarning, -1, "Can't create transform");
       cs->transform = NULL;
     } else {
-      cs->transform = new GfxColorTransform(transform);
+      cs->transform = new GfxColorTransform(transform, cmsIntent, dcst);
     }
-    if (dcst == PT_RGB) {
+    if (dcst == PT_RGB || dcst == PT_CMYK) {
        // create line transform only when the display is RGB type color space 
       if ((transform = cmsCreateTransform(hp,
 	    CHANNELS_SH(nCompsA) | BYTES_SH(1),dhp,
-	    TYPE_RGB_8,INTENT_RELATIVE_COLORIMETRIC,LCMS_FLAGS)) == 0) {
+	    (dcst == PT_RGB) ? TYPE_RGB_8 : TYPE_CMYK_8, cmsIntent, LCMS_FLAGS)) == 0) {
 	error(errSyntaxWarning, -1, "Can't create transform");
 	cs->lineTransform = NULL;
       } else {
-	cs->lineTransform = new GfxColorTransform(transform);
+	cs->lineTransform = new GfxColorTransform(transform, cmsIntent, dcst);
       }
     }
     cmsCloseProfile(hp);
@@ -1775,15 +1978,35 @@
 
 void GfxICCBasedColorSpace::getGray(GfxColor *color, GfxGray *gray) {
 #ifdef USE_CMS
-  if (transform != 0 && displayPixelType == PT_GRAY) {
+  if (transform != 0 && transform->getTransformPixelType() == PT_GRAY) {
     Guchar in[gfxColorMaxComps];
     Guchar out[gfxColorMaxComps];
     
     for (int i = 0;i < nComps;i++) {
 	in[i] = colToByte(color->c[i]);
     }
+    if (nComps <= 4) {
+      unsigned int key = 0;
+      for (int j = 0; j < nComps; j++) {
+        key = (key << 8) + in[j];
+      }
+      std::map<unsigned int, unsigned int>::iterator it = cmsCache.find(key);
+      if (it != cmsCache.end()) {
+        unsigned int value = it->second;
+        *gray = byteToCol(value & 0xff);
+        return;
+      }
+    }    
     transform->doTransform(in,out,1);
     *gray = byteToCol(out[0]);
+    if (nComps <= 4 && cmsCache.size() <= CMSCACHE_LIMIT) {
+      unsigned int key = 0;
+      for (int j = 0; j < nComps; j++) {
+        key = (key << 8) + in[j];
+      }
+      unsigned int value = out[0];
+      cmsCache.insert(std::pair<unsigned int, unsigned int>(key, value));
+    }
   } else {
     GfxRGB rgb;
     getRGB(color,&rgb);
@@ -1798,18 +2021,82 @@
 
 void GfxICCBasedColorSpace::getRGB(GfxColor *color, GfxRGB *rgb) {
 #ifdef USE_CMS
-  if (transform != 0
-       && (displayProfile == NULL || displayPixelType == PT_RGB)) {
+  if (transform != 0 && transform->getTransformPixelType() == PT_RGB) {
     Guchar in[gfxColorMaxComps];
     Guchar out[gfxColorMaxComps];
     
     for (int i = 0;i < nComps;i++) {
 	in[i] = colToByte(color->c[i]);
     }
+    if (nComps <= 4) {
+      unsigned int key = 0;
+      for (int j = 0; j < nComps; j++) {
+        key = (key << 8) + in[j];
+      }
+      std::map<unsigned int, unsigned int>::iterator it = cmsCache.find(key);
+      if (it != cmsCache.end()) {
+        unsigned int value = it->second;
+        rgb->r = byteToCol(value >> 16);
+        rgb->g = byteToCol((value >> 8) & 0xff);
+        rgb->b = byteToCol(value & 0xff);
+        return;
+      }
+    }    
     transform->doTransform(in,out,1);
     rgb->r = byteToCol(out[0]);
     rgb->g = byteToCol(out[1]);
     rgb->b = byteToCol(out[2]);
+    if (nComps <= 4 && cmsCache.size() <= CMSCACHE_LIMIT) {
+      unsigned int key = 0;
+      for (int j = 0; j < nComps; j++) {
+        key = (key << 8) + in[j];
+      }
+      unsigned int value = (out[0] << 16) + (out[1] << 8) + out[2];
+      cmsCache.insert(std::pair<unsigned int, unsigned int>(key, value));
+    }
+  } else if (transform != NULL && transform->getTransformPixelType() == PT_CMYK) {
+    Guchar in[gfxColorMaxComps];
+    Guchar out[gfxColorMaxComps];
+    double c, m, y, k, c1, m1, y1, k1, r, g, b;
+
+    for (int i = 0;i < nComps;i++) {
+	in[i] = colToByte(color->c[i]);
+    }
+    if (nComps <= 4) {
+      unsigned int key = 0;
+      for (int j = 0; j < nComps; j++) {
+        key = (key << 8) + in[j];
+      }
+      std::map<unsigned int, unsigned int>::iterator it = cmsCache.find(key);
+      if (it != cmsCache.end()) {
+        unsigned int value = it->second;
+        rgb->r = byteToCol(value >> 16);
+        rgb->g = byteToCol((value >> 8) & 0xff);
+        rgb->b = byteToCol(value & 0xff);
+        return;
+      }
+    }    
+    transform->doTransform(in,out,1);
+    c = byteToDbl(out[0]);
+    m = byteToDbl(out[1]);
+    y = byteToDbl(out[2]);
+    k = byteToDbl(out[3]);
+    c1 = 1 - c;
+    m1 = 1 - m;
+    y1 = 1 - y;
+    k1 = 1 - k;
+    cmykToRGBMatrixMultiplication(c, m, y, k, c1, m1, y1, k1, r, g, b);
+    rgb->r = clip01(dblToCol(r));
+    rgb->g = clip01(dblToCol(g));
+    rgb->b = clip01(dblToCol(b));
+    if (nComps <= 4 && cmsCache.size() <= CMSCACHE_LIMIT) {
+      unsigned int key = 0;
+      for (int j = 0; j < nComps; j++) {
+        key = (key << 8) + in[j];
+      }
+      unsigned int value = (dblToByte(r) << 16) + (dblToByte(g) << 8) + dblToByte(b);
+      cmsCache.insert(std::pair<unsigned int, unsigned int>(key, value));
+    }
   } else {
     alt->getRGB(color, rgb);
   }
@@ -1821,7 +2108,7 @@
 void GfxICCBasedColorSpace::getRGBLine(Guchar *in, unsigned int *out,
 				       int length) {
 #ifdef USE_CMS
-  if (lineTransform != 0) {
+  if (lineTransform != 0 && lineTransform->getTransformPixelType() == PT_RGB) {
     Guchar* tmp = (Guchar *)gmallocn(3 * length, sizeof(Guchar));
     lineTransform->doTransform(in, tmp, length);
     for (int i = 0; i < length; ++i) {
@@ -1839,7 +2126,7 @@
 
 void GfxICCBasedColorSpace::getRGBLine(Guchar *in, Guchar *out, int length) {
 #ifdef USE_CMS
-  if (lineTransform != 0) {
+  if (lineTransform != 0 && lineTransform->getTransformPixelType() == PT_RGB) {
     Guchar* tmp = (Guchar *)gmallocn(3 * length, sizeof(Guchar));
     lineTransform->doTransform(in, tmp, length);
     Guchar *current = tmp;
@@ -1849,6 +2136,26 @@
         *out++ = *current++;
     }
     gfree(tmp);
+  } else if (lineTransform != NULL && lineTransform->getTransformPixelType() == PT_CMYK) {
+    Guchar* tmp = (Guchar *)gmallocn(4 * length, sizeof(Guchar));
+    lineTransform->doTransform(in, tmp, length);
+    Guchar *current = tmp;
+    double c, m, y, k, c1, m1, y1, k1, r, g, b;
+    for (int i = 0; i < length; ++i) {
+      c = byteToDbl(*current++);
+      m = byteToDbl(*current++);
+      y = byteToDbl(*current++);
+      k = byteToDbl(*current++);
+      c1 = 1 - c;
+      m1 = 1 - m;
+      y1 = 1 - y;
+      k1 = 1 - k;
+      cmykToRGBMatrixMultiplication(c, m, y, k, c1, m1, y1, k1, r, g, b);
+      *out++ = dblToByte(r);
+      *out++ = dblToByte(g);
+      *out++ = dblToByte(b);
+    }
+    gfree(tmp);
   } else {
     alt->getRGBLine(in, out, length);
   }
@@ -1859,7 +2166,7 @@
 
 void GfxICCBasedColorSpace::getRGBXLine(Guchar *in, Guchar *out, int length) {
 #ifdef USE_CMS
-  if (lineTransform != 0) {
+  if (lineTransform != 0 && lineTransform->getTransformPixelType() == PT_RGB) {
     Guchar* tmp = (Guchar *)gmallocn(3 * length, sizeof(Guchar));
     lineTransform->doTransform(in, tmp, length);
     Guchar *current = tmp;
@@ -1878,20 +2185,123 @@
 #endif
 }
 
+void GfxICCBasedColorSpace::getCMYKLine(Guchar *in, Guchar *out, int length) {
+#ifdef USE_CMS
+  if (lineTransform != NULL && lineTransform->getTransformPixelType() == PT_CMYK) {
+    transform->doTransform(in,out,length);
+  } else if (lineTransform != NULL) {
+    GfxColorComp c, m, y, k;
+    Guchar* tmp = (Guchar *)gmallocn(3 * length, sizeof(Guchar));
+    getRGBLine(in, tmp, length);
+    Guchar *p = tmp;
+    for (int i = 0; i < length; i++) {
+      c = byteToCol(255 - *p++);
+      m = byteToCol(255 - *p++);
+      y = byteToCol(255 - *p++);
+      k = c;
+      if (m < k) {
+        k = m;
+      }
+      if (y < k) {
+        k = y;
+      }
+      *out++ = colToByte(c - k);
+      *out++ = colToByte(m - k);
+      *out++ = colToByte(y - k);
+      *out++ = colToByte(k);
+    }
+    gfree(tmp);
+  } else {
+    alt->getCMYKLine(in, out, length);
+  }
+#else
+  alt->getCMYKLine(in, out, length);
+#endif
+}
+
+void GfxICCBasedColorSpace::getDeviceNLine(Guchar *in, Guchar *out, int length) {
+#ifdef USE_CMS
+  if (lineTransform != NULL && lineTransform->getTransformPixelType() == PT_CMYK) {
+    Guchar* tmp = (Guchar *)gmallocn(4 * length, sizeof(Guchar));
+    transform->doTransform(in,tmp,length);
+    Guchar *p = tmp;
+    for (int i = 0; i < length; i++) {
+      for (int j = 0; j < 4; j++)
+        *out++ = *p++;
+      for (int j = 4; j < SPOT_NCOMPS+4; j++)
+        *out++ = 0;
+    }
+    gfree(tmp);
+  } else if (lineTransform != NULL) {
+    GfxColorComp c, m, y, k;
+    Guchar* tmp = (Guchar *)gmallocn(3 * length, sizeof(Guchar));
+    getRGBLine(in, tmp, length);
+    Guchar *p = tmp;
+    for (int i = 0; i < length; i++) {
+      for (int j = 0; j < SPOT_NCOMPS+4; j++) 
+        out[j] = 0;
+      c = byteToCol(255 - *p++);
+      m = byteToCol(255 - *p++);
+      y = byteToCol(255 - *p++);
+      k = c;
+      if (m < k) {
+        k = m;
+      }
+      if (y < k) {
+        k = y;
+      }
+      out[0] = colToByte(c - k);
+      out[1] = colToByte(m - k);
+      out[2] = colToByte(y - k);
+      out[3] = colToByte(k);
+      out += (SPOT_NCOMPS+4);
+    }
+    gfree(tmp);
+  } else {
+    alt->getDeviceNLine(in, out, length);
+  }
+#else
+  alt->getDeviceNLine(in, out, length);
+#endif
+}
+
 void GfxICCBasedColorSpace::getCMYK(GfxColor *color, GfxCMYK *cmyk) {
 #ifdef USE_CMS
-  if (transform != NULL && displayPixelType == PT_CMYK) {
+  if (transform != NULL && transform->getTransformPixelType() == PT_CMYK) {
     Guchar in[gfxColorMaxComps];
     Guchar out[gfxColorMaxComps];
     
     for (int i = 0;i < nComps;i++) {
 	in[i] = colToByte(color->c[i]);
     }
+    if (nComps <= 4) {
+      unsigned int key = 0;
+      for (int j = 0; j < nComps; j++) {
+        key = (key << 8) + in[j];
+      }
+      std::map<unsigned int, unsigned int>::iterator it = cmsCache.find(key);
+      if (it != cmsCache.end()) {
+        unsigned int value = it->second;
+        cmyk->c = byteToCol(value >> 24);
+        cmyk->m = byteToCol((value >> 16) & 0xff);
+        cmyk->y = byteToCol((value >> 8) & 0xff);
+        cmyk->k = byteToCol(value & 0xff);
+        return;
+      }
+    }    
     transform->doTransform(in,out,1);
     cmyk->c = byteToCol(out[0]);
     cmyk->m = byteToCol(out[1]);
     cmyk->y = byteToCol(out[2]);
     cmyk->k = byteToCol(out[3]);
+    if (nComps <= 4 && cmsCache.size() <= CMSCACHE_LIMIT) {
+      unsigned int key = 0;
+      for (int j = 0; j < nComps; j++) {
+        key = (key << 8) + in[j];
+      }
+      unsigned int value = (out[0] << 24) + (out[1] << 16) + (out[2] << 8) + out[3];
+      cmsCache.insert(std::pair<unsigned int, unsigned int>(key, value));
+    }
   } else {
     GfxRGB rgb;
     GfxColorComp c, m, y, k;
@@ -1925,6 +2335,22 @@
 #endif
 }
 
+GBool GfxICCBasedColorSpace::useGetCMYKLine() {
+#ifdef USE_CMS
+  return lineTransform != NULL || alt->useGetCMYKLine();
+#else
+  return alt->useGetCMYKLine();
+#endif
+}
+
+GBool GfxICCBasedColorSpace::useGetDeviceNLine() {
+#ifdef USE_CMS
+  return lineTransform != NULL || alt->useGetDeviceNLine();
+#else
+  return alt->useGetDeviceNLine();
+#endif
+}
+
 void GfxICCBasedColorSpace::getDeviceN(GfxColor *color, GfxColor *deviceN) {
   GfxCMYK cmyk;
   for (int i = 0; i < gfxColorMaxComps; i++)
@@ -1994,7 +2420,7 @@
   return cs;
 }
 
-GfxColorSpace *GfxIndexedColorSpace::parse(Array *arr, OutputDev *out, int recursion) {
+GfxColorSpace *GfxIndexedColorSpace::parse(Array *arr, OutputDev *out, GfxState *state, int recursion) {
   GfxIndexedColorSpace *cs;
   GfxColorSpace *baseA;
   int indexHighA;
@@ -2007,7 +2433,7 @@
     goto err1;
   }
   arr->get(1, &obj1);
-  if (!(baseA = GfxColorSpace::parse(&obj1, out, recursion + 1))) {
+  if (!(baseA = GfxColorSpace::parse(&obj1, out, state, recursion + 1))) {
     error(errSyntaxWarning, -1, "Bad Indexed color space (base color space)");
     goto err2;
   }
@@ -2149,6 +2575,36 @@
   gfree (line);
 }
 
+void GfxIndexedColorSpace::getCMYKLine(Guchar *in, Guchar *out, int length) {
+  Guchar *line;
+  int i, j, n;
+
+  n = base->getNComps();
+  line = (Guchar *) gmallocn (length, n);
+  for (i = 0; i < length; i++)
+    for (j = 0; j < n; j++)
+      line[i * n + j] = lookup[in[i] * n + j];
+
+  base->getCMYKLine(line, out, length);
+
+  gfree (line);
+}
+
+void GfxIndexedColorSpace::getDeviceNLine(Guchar *in, Guchar *out, int length) {
+  Guchar *line;
+  int i, j, n;
+
+  n = base->getNComps();
+  line = (Guchar *) gmallocn (length, n);
+  for (i = 0; i < length; i++)
+    for (j = 0; j < n; j++)
+      line[i * n + j] = lookup[in[i] * n + j];
+
+  base->getDeviceNLine(line, out, length);
+
+  gfree (line);
+}
+
 void GfxIndexedColorSpace::getCMYK(GfxColor *color, GfxCMYK *cmyk) {
   GfxColor color2;
 
@@ -2229,7 +2685,7 @@
 }
 
 //~ handle the 'All' and 'None' colorants
-GfxColorSpace *GfxSeparationColorSpace::parse(Array *arr, OutputDev *out, int recursion) {
+GfxColorSpace *GfxSeparationColorSpace::parse(Array *arr, OutputDev *out, GfxState *state, int recursion) {
   GfxSeparationColorSpace *cs;
   GooString *nameA;
   GfxColorSpace *altA;
@@ -2247,7 +2703,7 @@
   nameA = new GooString(obj1.getName());
   obj1.free();
   arr->get(2, &obj1);
-  if (!(altA = GfxColorSpace::parse(&obj1, out, recursion + 1))) {
+  if (!(altA = GfxColorSpace::parse(&obj1, out, state, recursion + 1))) {
     error(errSyntaxWarning, -1, "Bad Separation color space (alternate color space)");
     goto err3;
   }
@@ -2478,7 +2934,7 @@
 }
 
 //~ handle the 'None' colorant
-GfxColorSpace *GfxDeviceNColorSpace::parse(Array *arr, OutputDev *out, int recursion) {
+GfxColorSpace *GfxDeviceNColorSpace::parse(Array *arr, OutputDev *out, GfxState *state, int recursion) {
   GfxDeviceNColorSpace *cs;
   int nCompsA;
   GooString *namesA[gfxColorMaxComps];
@@ -2513,7 +2969,7 @@
   }
   obj1.free();
   arr->get(2, &obj1);
-  if (!(altA = GfxColorSpace::parse(&obj1, out, recursion + 1))) {
+  if (!(altA = GfxColorSpace::parse(&obj1, out, state, recursion + 1))) {
     error(errSyntaxWarning, -1, "Bad DeviceN color space (alternate color space)");
     goto err3;
   }
@@ -2536,7 +2992,7 @@
         Object obj3;
         colorants->getVal(i, &obj3);
         if (obj3.isArray()) {
-          separationList->append(GfxSeparationColorSpace::parse(obj3.getArray(), out, recursion));
+          separationList->append(GfxSeparationColorSpace::parse(obj3.getArray(), out, state, recursion));
         } else {
           obj3.free();
           obj2.free();
@@ -2744,7 +3200,7 @@
 				          (GfxColorSpace *)NULL);
 }
 
-GfxColorSpace *GfxPatternColorSpace::parse(Array *arr, OutputDev *out, int recursion) {
+GfxColorSpace *GfxPatternColorSpace::parse(Array *arr, OutputDev *out, GfxState *state, int recursion) {
   GfxPatternColorSpace *cs;
   GfxColorSpace *underA;
   Object obj1;
@@ -2756,7 +3212,7 @@
   underA = NULL;
   if (arr->getLength() == 2) {
     arr->get(1, &obj1);
-    if (!(underA = GfxColorSpace::parse(&obj1, out, recursion + 1))) {
+    if (!(underA = GfxColorSpace::parse(&obj1, out, state, recursion + 1))) {
       error(errSyntaxWarning, -1, "Bad Pattern color space (underlying color space)");
       obj1.free();
       return NULL;
@@ -2801,7 +3257,7 @@
 GfxPattern::~GfxPattern() {
 }
 
-GfxPattern *GfxPattern::parse(Object *obj, OutputDev *out) {
+GfxPattern *GfxPattern::parse(Object *obj, OutputDev *out, GfxState *state) {
   GfxPattern *pattern;
   Object obj1;
 
@@ -2816,7 +3272,7 @@
   if (obj1.isInt() && obj1.getInt() == 1) {
     pattern = GfxTilingPattern::parse(obj);
   } else if (obj1.isInt() && obj1.getInt() == 2) {
-    pattern = GfxShadingPattern::parse(obj, out);
+    pattern = GfxShadingPattern::parse(obj, out, state);
   }
   obj1.free();
   return pattern;
@@ -2944,7 +3400,7 @@
 // GfxShadingPattern
 //------------------------------------------------------------------------
 
-GfxShadingPattern *GfxShadingPattern::parse(Object *patObj, OutputDev *out) {
+GfxShadingPattern *GfxShadingPattern::parse(Object *patObj, OutputDev *out, GfxState *state) {
   Dict *dict;
   GfxShading *shadingA;
   double matrixA[6];
@@ -2957,7 +3413,7 @@
   dict = patObj->getDict();
 
   dict->lookup("Shading", &obj1);
-  shadingA = GfxShading::parse(&obj1, out);
+  shadingA = GfxShading::parse(&obj1, out, state);
   obj1.free();
   if (!shadingA) {
     return NULL;
@@ -3030,7 +3486,7 @@
   }
 }
 
-GfxShading *GfxShading::parse(Object *obj, OutputDev *out) {
+GfxShading *GfxShading::parse(Object *obj, OutputDev *out, GfxState *state) {
   GfxShading *shading;
   Dict *dict;
   int typeA;
@@ -3054,17 +3510,17 @@
 
   switch (typeA) {
   case 1:
-    shading = GfxFunctionShading::parse(dict, out);
+    shading = GfxFunctionShading::parse(dict, out, state);
     break;
   case 2:
-    shading = GfxAxialShading::parse(dict, out);
+    shading = GfxAxialShading::parse(dict, out, state);
     break;
   case 3:
-    shading = GfxRadialShading::parse(dict, out);
+    shading = GfxRadialShading::parse(dict, out, state);
     break;
   case 4:
     if (obj->isStream()) {
-      shading = GfxGouraudTriangleShading::parse(4, dict, obj->getStream(), out);
+      shading = GfxGouraudTriangleShading::parse(4, dict, obj->getStream(), out, state);
     } else {
       error(errSyntaxWarning, -1, "Invalid Type 4 shading object");
       goto err1;
@@ -3072,7 +3528,7 @@
     break;
   case 5:
     if (obj->isStream()) {
-      shading = GfxGouraudTriangleShading::parse(5, dict, obj->getStream(), out);
+      shading = GfxGouraudTriangleShading::parse(5, dict, obj->getStream(), out, state);
     } else {
       error(errSyntaxWarning, -1, "Invalid Type 5 shading object");
       goto err1;
@@ -3080,7 +3536,7 @@
     break;
   case 6:
     if (obj->isStream()) {
-      shading = GfxPatchMeshShading::parse(6, dict, obj->getStream(), out);
+      shading = GfxPatchMeshShading::parse(6, dict, obj->getStream(), out, state);
     } else {
       error(errSyntaxWarning, -1, "Invalid Type 6 shading object");
       goto err1;
@@ -3088,7 +3544,7 @@
     break;
   case 7:
     if (obj->isStream()) {
-      shading = GfxPatchMeshShading::parse(7, dict, obj->getStream(), out);
+      shading = GfxPatchMeshShading::parse(7, dict, obj->getStream(), out, state);
     } else {
       error(errSyntaxWarning, -1, "Invalid Type 7 shading object");
       goto err1;
@@ -3105,12 +3561,12 @@
   return NULL;
 }
 
-GBool GfxShading::init(Dict *dict, OutputDev *out) {
+GBool GfxShading::init(Dict *dict, OutputDev *out, GfxState *state) {
   Object obj1, obj2;
   int i;
 
   dict->lookup("ColorSpace", &obj1);
-  if (!(colorSpace = GfxColorSpace::parse(&obj1, out))) {
+  if (!(colorSpace = GfxColorSpace::parse(&obj1, out, state))) {
     error(errSyntaxWarning, -1, "Bad color space in shading dictionary");
     obj1.free();
     return gFalse;
@@ -3217,7 +3673,7 @@
   }
 }
 
-GfxFunctionShading *GfxFunctionShading::parse(Dict *dict, OutputDev *out) {
+GfxFunctionShading *GfxFunctionShading::parse(Dict *dict, OutputDev *out, GfxState *state) {
   GfxFunctionShading *shading;
   double x0A, y0A, x1A, y1A;
   double matrixA[6];
@@ -3285,7 +3741,7 @@
 
   shading = new GfxFunctionShading(x0A, y0A, x1A, y1A, matrixA,
 				   funcsA, nFuncsA);
-  if (!shading->init(dict, out)) {
+  if (!shading->init(dict, out, state)) {
     delete shading;
     return NULL;
   }
@@ -3536,7 +3992,7 @@
 GfxAxialShading::~GfxAxialShading() {
 }
 
-GfxAxialShading *GfxAxialShading::parse(Dict *dict, OutputDev *out) {
+GfxAxialShading *GfxAxialShading::parse(Dict *dict, OutputDev *out, GfxState *state) {
   GfxAxialShading *shading;
   double x0A, y0A, x1A, y1A;
   double t0A, t1A;
@@ -3633,7 +4089,7 @@
 
   shading = new GfxAxialShading(x0A, y0A, x1A, y1A, t0A, t1A,
 				funcsA, nFuncsA, extend0A, extend1A);
-  if (!shading->init(dict, out)) {
+  if (!shading->init(dict, out, state)) {
     delete shading;
     return NULL;
   }
@@ -3744,7 +4200,7 @@
 GfxRadialShading::~GfxRadialShading() {
 }
 
-GfxRadialShading *GfxRadialShading::parse(Dict *dict, OutputDev *out) {
+GfxRadialShading *GfxRadialShading::parse(Dict *dict, OutputDev *out, GfxState *state) {
   GfxRadialShading *shading;
   double x0A, y0A, r0A, x1A, y1A, r1A;
   double t0A, t1A;
@@ -3823,7 +4279,7 @@
 
   shading = new GfxRadialShading(x0A, y0A, r0A, x1A, y1A, r1A, t0A, t1A,
 				 funcsA, nFuncsA, extend0A, extend1A);
-  if (!shading->init(dict, out)) {
+  if (!shading->init(dict, out, state)) {
     delete shading;
     return NULL;
   }
@@ -4267,7 +4723,7 @@
 GfxGouraudTriangleShading *GfxGouraudTriangleShading::parse(int typeA,
 							    Dict *dict,
 							    Stream *str,
-							    OutputDev *out) {
+							    OutputDev *out, GfxState *gfxState) {
   GfxGouraudTriangleShading *shading;
   Function *funcsA[gfxColorMaxComps];
   int nFuncsA;
@@ -4463,7 +4919,7 @@
   shading = new GfxGouraudTriangleShading(typeA, verticesA, nVerticesA,
 					  trianglesA, nTrianglesA,
 					  funcsA, nFuncsA);
-  if (!shading->init(dict, out)) {
+  if (!shading->init(dict, out, gfxState)) {
     delete shading;
     return NULL;
   }
@@ -4610,7 +5066,7 @@
 }
 
 GfxPatchMeshShading *GfxPatchMeshShading::parse(int typeA, Dict *dict,
-						Stream *str, OutputDev *out) {
+						Stream *str, OutputDev *out, GfxState *state) {
   GfxPatchMeshShading *shading;
   Function *funcsA[gfxColorMaxComps];
   int nFuncsA;
@@ -5134,7 +5590,7 @@
 
   shading = new GfxPatchMeshShading(typeA, patchesA, nPatchesA,
 				    funcsA, nFuncsA);
-  if (!shading->init(dict, out)) {
+  if (!shading->init(dict, out, state)) {
     delete shading;
     return NULL;
   }
@@ -5259,7 +5715,7 @@
     nComps2 = colorSpace2->getNComps();
     indexedLookup = indexedCS->getLookup();
     colorSpace2->getDefaultRanges(x, y, indexHigh);
-    if (colorSpace2->useGetGrayLine() || colorSpace2->useGetRGBLine()) {
+    if (colorSpace2->useGetGrayLine() || colorSpace2->useGetRGBLine() || colorSpace2->useGetCMYKLine() || colorSpace2->useGetDeviceNLine()) {
       byte_lookup = (Guchar *)gmallocn ((maxPixel + 1), nComps2);
       useByteLookup = gTrue;
     }
@@ -5286,7 +5742,7 @@
     colorSpace2 = sepCS->getAlt();
     nComps2 = colorSpace2->getNComps();
     sepFunc = sepCS->getFunc();
-    if (colorSpace2->useGetGrayLine() || colorSpace2->useGetRGBLine()) {
+    if (colorSpace2->useGetGrayLine() || colorSpace2->useGetRGBLine() || colorSpace2->useGetCMYKLine() || colorSpace2->useGetDeviceNLine()) {
       byte_lookup = (Guchar *)gmallocn ((maxPixel + 1), nComps2);
       useByteLookup = gTrue;
     }
@@ -5303,7 +5759,7 @@
     }
     break;
   default:
-    if (colorSpace->useGetGrayLine() || colorSpace->useGetRGBLine()) {
+    if (colorSpace->useGetGrayLine() || colorSpace->useGetRGBLine() || colorSpace->useGetCMYKLine() || colorSpace->useGetDeviceNLine()) {
       byte_lookup = (Guchar *)gmallocn ((maxPixel + 1), nComps);
       useByteLookup = gTrue;
     }
@@ -5600,6 +6056,94 @@
 
 }
 
+void GfxImageColorMap::getCMYKLine(Guchar *in, Guchar *out, int length) {
+  int i, j;
+  Guchar *inp, *tmp_line;
+
+  if (!useCMYKLine()) {
+    GfxCMYK cmyk;
+
+    inp = in;
+    for (i = 0; i < length; i++) {
+      getCMYK (inp, &cmyk);
+      *out++ = colToByte(cmyk.c);
+      *out++ = colToByte(cmyk.m);
+      *out++ = colToByte(cmyk.y);
+      *out++ = colToByte(cmyk.k);
+      inp += nComps;
+    }
+    return;
+  }
+
+  switch (colorSpace->getMode()) {
+  case csIndexed:
+  case csSeparation:
+    tmp_line = (Guchar *) gmallocn (length, nComps2);
+    for (i = 0; i < length; i++) {
+      for (j = 0; j < nComps2; j++) {
+	tmp_line[i * nComps2 + j] = byte_lookup[in[i] * nComps2 + j];
+      }
+    }
+    colorSpace2->getCMYKLine(tmp_line, out, length);
+    gfree (tmp_line);
+    break;
+
+  default:
+    inp = in;
+    for (j = 0; j < length; j++)
+      for (i = 0; i < nComps; i++) {
+	*inp = byte_lookup[*inp * nComps + i];
+	inp++;
+      }
+    colorSpace->getCMYKLine(in, out, length);
+    break;
+  }
+
+}
+
+void GfxImageColorMap::getDeviceNLine(Guchar *in, Guchar *out, int length) {
+  int i, j;
+  Guchar *inp, *tmp_line;
+
+  if (!useDeviceNLine()) {
+    GfxColor deviceN;
+
+    inp = in;
+    for (i = 0; i < length; i++) {
+      getDeviceN (inp, &deviceN);
+      for (int j = 0; j < SPOT_NCOMPS+4; j++)
+        *out++ = deviceN.c[j];
+      inp += nComps;
+    }
+    return;
+  }
+
+  switch (colorSpace->getMode()) {
+  case csIndexed:
+  case csSeparation:
+    tmp_line = (Guchar *) gmallocn (length, nComps2);
+    for (i = 0; i < length; i++) {
+      for (j = 0; j < nComps2; j++) {
+	tmp_line[i * nComps2 + j] = byte_lookup[in[i] * nComps2 + j];
+      }
+    }
+    colorSpace2->getDeviceNLine(tmp_line, out, length);
+    gfree (tmp_line);
+    break;
+
+  default:
+    inp = in;
+    for (j = 0; j < length; j++)
+      for (i = 0; i < nComps; i++) {
+	*inp = byte_lookup[*inp * nComps + i];
+	inp++;
+      }
+    colorSpace->getDeviceNLine(in, out, length);
+    break;
+  }
+
+}
+
 void GfxImageColorMap::getCMYK(Guchar *x, GfxCMYK *cmyk) {
   GfxColor color;
   int i;
@@ -5982,9 +6526,17 @@
   clipXMax = pageWidth;
   clipYMax = pageHeight;
 
+  renderingIntent[0] = 0;
+
   saved = NULL;
 #ifdef USE_CMS
   GfxColorSpace::setupColorProfiles();
+  XYZ2DisplayTransformRelCol = NULL;
+  XYZ2DisplayTransformAbsCol = NULL;
+  XYZ2DisplayTransformSat = NULL;
+  XYZ2DisplayTransformPerc = NULL;
+  localDisplayProfile = NULL;
+  displayProfileRef = 0;
 #endif
 }
 
@@ -6016,6 +6568,27 @@
   if (font) {
     font->decRefCnt();
   }
+#ifdef USE_CMS
+  if (XYZ2DisplayTransformRelCol) {
+    if (XYZ2DisplayTransformRelCol->unref() == 0)
+      delete XYZ2DisplayTransformRelCol;
+  }
+  if (XYZ2DisplayTransformAbsCol) {
+    if (XYZ2DisplayTransformAbsCol->unref() == 0)
+      delete XYZ2DisplayTransformAbsCol;
+  }
+  if (XYZ2DisplayTransformSat) {
+    if (XYZ2DisplayTransformSat->unref() == 0)
+      delete XYZ2DisplayTransformSat;
+  }
+  if (XYZ2DisplayTransformPerc) {
+    if (XYZ2DisplayTransformPerc->unref() == 0)
+      delete XYZ2DisplayTransformPerc;
+  }
+  if (--displayProfileRef == 0 && localDisplayProfile != NULL) {
+    cmsCloseProfile(localDisplayProfile);
+  }
+#endif
 }
 
 // Used for copy();
@@ -6051,8 +6624,102 @@
     path = state->path->copy();
   }
   saved = NULL;
+#ifdef USE_CMS
+  if (XYZ2DisplayTransformRelCol) {
+    XYZ2DisplayTransformRelCol->ref();
+  }
+  if (XYZ2DisplayTransformAbsCol) {
+    XYZ2DisplayTransformAbsCol->ref();
+  }
+  if (XYZ2DisplayTransformSat) {
+    XYZ2DisplayTransformSat->ref();
+  }
+  if (XYZ2DisplayTransformPerc) {
+    XYZ2DisplayTransformPerc->ref();
+  }
+  if (localDisplayProfile) {
+    displayProfileRef++;
+  }
+#endif
 }
 
+#ifdef USE_CMS
+void GfxState::setDisplayProfile(cmsHPROFILE localDisplayProfileA) {
+  if (localDisplayProfile != NULL) {
+    cmsCloseProfile(localDisplayProfile);
+  }
+  localDisplayProfile = localDisplayProfileA;
+  if (localDisplayProfileA != NULL) {
+    cmsHTRANSFORM transform;
+    unsigned int nChannels;
+    unsigned int localDisplayPixelType;
+
+    localDisplayPixelType = getCMSColorSpaceType(cmsGetColorSpace(localDisplayProfile));
+    nChannels = getCMSNChannels(cmsGetColorSpace(localDisplayProfile));
+    displayProfileRef = 1;
+    // create transform from XYZ
+    cmsHPROFILE XYZProfile = cmsCreateXYZProfile();
+    if ((transform = cmsCreateTransform(XYZProfile, TYPE_XYZ_DBL,
+	   localDisplayProfile,
+	   COLORSPACE_SH(localDisplayPixelType) |
+	     CHANNELS_SH(nChannels) | BYTES_SH(1),
+	  INTENT_RELATIVE_COLORIMETRIC,LCMS_FLAGS)) == 0) {
+      error(errSyntaxWarning, -1, "Can't create Lab transform");
+    } else {
+      XYZ2DisplayTransformRelCol = new GfxColorTransform(transform, INTENT_RELATIVE_COLORIMETRIC, localDisplayPixelType);
+    }
+    if ((transform = cmsCreateTransform(XYZProfile, TYPE_XYZ_DBL,
+	   localDisplayProfile,
+	   COLORSPACE_SH(localDisplayPixelType) |
+	     CHANNELS_SH(nChannels) | BYTES_SH(1),
+	  INTENT_ABSOLUTE_COLORIMETRIC,LCMS_FLAGS)) == 0) {
+      error(errSyntaxWarning, -1, "Can't create Lab transform");
+    } else {
+      XYZ2DisplayTransformAbsCol = new GfxColorTransform(transform, INTENT_ABSOLUTE_COLORIMETRIC, localDisplayPixelType);
+    }
+    if ((transform = cmsCreateTransform(XYZProfile, TYPE_XYZ_DBL,
+	   localDisplayProfile,
+	   COLORSPACE_SH(localDisplayPixelType) |
+	     CHANNELS_SH(nChannels) | BYTES_SH(1),
+	  INTENT_SATURATION,LCMS_FLAGS)) == 0) {
+      error(errSyntaxWarning, -1, "Can't create Lab transform");
+    } else {
+      XYZ2DisplayTransformSat = new GfxColorTransform(transform, INTENT_SATURATION, localDisplayPixelType);
+    }
+    if ((transform = cmsCreateTransform(XYZProfile, TYPE_XYZ_DBL,
+	   localDisplayProfile,
+	   COLORSPACE_SH(localDisplayPixelType) |
+	     CHANNELS_SH(nChannels) | BYTES_SH(1),
+	  INTENT_PERCEPTUAL,LCMS_FLAGS)) == 0) {
+      error(errSyntaxWarning, -1, "Can't create Lab transform");
+    } else {
+      XYZ2DisplayTransformPerc = new GfxColorTransform(transform, INTENT_PERCEPTUAL, localDisplayPixelType);
+    }
+    cmsCloseProfile(XYZProfile);
+  }
+}
+
+GfxColorTransform *GfxState::getXYZ2DisplayTransform() {
+  GfxColorTransform *transform;
+
+  transform = XYZ2DisplayTransformRelCol;
+  if (renderingIntent != NULL) {
+    if (strcmp(renderingIntent, "AbsoluteColorimetric") == 0) {
+      transform = XYZ2DisplayTransformAbsCol;
+    } else if (strcmp(renderingIntent, "Saturation") == 0) {
+      transform = XYZ2DisplayTransformSat;
+    } else if (strcmp(renderingIntent, "Perceptual") == 0) {
+      transform = XYZ2DisplayTransformPerc;
+    }
+  }
+  if (transform == NULL) {
+    transform = XYZ2DisplayTransform;
+  }
+  return transform;
+}
+
+#endif
+
 void GfxState::setPath(GfxPath *pathA) {
   delete path;
   path = pathA;
diff --git a/poppler/GfxState.h b/poppler/GfxState.h
index 2f3efcf..106b2c0 100644
--- a/poppler/GfxState.h
+++ b/poppler/GfxState.h
@@ -20,7 +20,7 @@
 // Copyright (C) 2009-2011, 2013 Albert Astals Cid <aacid@kde.org>
 // Copyright (C) 2010 Christian Feuersänger <cfeuersaenger@googlemail.com>
 // Copyright (C) 2011 Andrea Canciani <ranma42@gmail.com>
-// Copyright (C) 2011, 2012 Thomas Freitag <Thomas.Freitag@alfa.de>
+// Copyright (C) 2011-2013 Thomas Freitag <Thomas.Freitag@alfa.de>
 // Copyright (C) 2013 Lu Wang <coolwanglu@gmail.com>
 //
 // To see a description of the changes please see the Changelog file that
@@ -42,6 +42,7 @@
 #include "Function.h"
 
 #include <assert.h>
+#include <map>
 
 class Array;
 class Gfx;
@@ -51,6 +52,7 @@
 class PopplerCache;
 class GooList;
 class OutputDev;
+class GfxState;
 
 class Matrix {
 public:
@@ -180,14 +182,18 @@
 public:
   void doTransform(void *in, void *out, unsigned int size);
   // transformA should be a cmsHTRANSFORM
-  GfxColorTransform(void *transformA);
+  GfxColorTransform(void *transformA, int cmsIntent, unsigned int transformPixelType);
   ~GfxColorTransform();
+  int getIntent() { return cmsIntent; }
+  int getTransformPixelType() { return transformPixelType; }
   void ref();
   unsigned int unref();
 private:
   GfxColorTransform() {}
   void *transform;
   unsigned int refCount;
+  int cmsIntent;
+  unsigned int transformPixelType;
 };
 
 class GfxColorSpace {
@@ -199,7 +205,7 @@
   virtual GfxColorSpaceMode getMode() = 0;
 
   // Construct a color space.  Returns NULL if unsuccessful.
-  static GfxColorSpace *parse(Object *csObj, OutputDev *out, int recursion = 0);
+  static GfxColorSpace *parse(Object *csObj, OutputDev *out, GfxState *state, int recursion = 0);
 
   // Convert to gray, RGB, or CMYK.
   virtual void getGray(GfxColor *color, GfxGray *gray) = 0;
@@ -210,6 +216,8 @@
   virtual void getRGBLine(Guchar * /*in*/, unsigned int * /*out*/, int /*length*/) { error(errInternal, -1, "GfxColorSpace::getRGBLine (first variant) this should not happen"); }
   virtual void getRGBLine(Guchar * /*in*/, Guchar * /*out*/, int /*length*/) {  error(errInternal, -1, "GfxColorSpace::getRGBLine (second variant) this should not happen"); }
   virtual void getRGBXLine(Guchar * /*in*/, Guchar * /*out*/, int /*length*/) {  error(errInternal, -1, "GfxColorSpace::getRGBXLine this should not happen"); }
+  virtual void getCMYKLine(Guchar * /*in*/, Guchar * /*out*/, int /*length*/) {  error(errInternal, -1, "GfxColorSpace::getCMYKLine this should not happen"); }
+  virtual void getDeviceNLine(Guchar * /*in*/, Guchar * /*out*/, int /*length*/) {  error(errInternal, -1, "GfxColorSpace::getDeviceNLine this should not happen"); }
 
   // create mapping for spot colorants
   virtual void createMapping(GooList *separationList, int maxSepComps);
@@ -218,6 +226,10 @@
   virtual GBool useGetRGBLine() { return gFalse; }
   // Does this ColorSpace support getGrayLine?
   virtual GBool useGetGrayLine() { return gFalse; }
+  // Does this ColorSpace support getCMYKLine?
+  virtual GBool useGetCMYKLine() { return gFalse; }
+  // Does this ColorSpace support getDeviceNLine?
+  virtual GBool useGetDeviceNLine() { return gFalse; }
 
   // Return the number of color components.
   virtual int getNComps() = 0;
@@ -279,9 +291,13 @@
   virtual void getRGBLine(Guchar *in, unsigned int *out, int length);
   virtual void getRGBLine(Guchar *in, Guchar *out, int length);
   virtual void getRGBXLine(Guchar *in, Guchar *out, int length);
+  virtual void getCMYKLine(Guchar *in, Guchar *out, int length);
+  virtual void getDeviceNLine(Guchar *in, Guchar *out, int length);
 
   virtual GBool useGetRGBLine() { return gTrue; }
   virtual GBool useGetGrayLine() { return gTrue; }
+  virtual GBool useGetCMYKLine() { return gTrue; }
+  virtual GBool useGetDeviceNLine() { return gTrue; }
 
   virtual int getNComps() { return 1; }
   virtual void getDefaultColor(GfxColor *color);
@@ -302,7 +318,7 @@
   virtual GfxColorSpaceMode getMode() { return csCalGray; }
 
   // Construct a CalGray color space.  Returns NULL if unsuccessful.
-  static GfxColorSpace *parse(Array *arr);
+  static GfxColorSpace *parse(Array *arr, GfxState *state);
 
   virtual void getGray(GfxColor *color, GfxGray *gray);
   virtual void getRGB(GfxColor *color, GfxRGB *rgb);
@@ -328,6 +344,9 @@
   double gamma;			    // gamma value
   double kr, kg, kb;		    // gamut mapping mulitpliers
   void getXYZ(GfxColor *color, double *pX, double *pY, double *pZ);
+#ifdef USE_CMS
+  GfxColorTransform *transform;
+#endif
 };
 
 //------------------------------------------------------------------------
@@ -350,9 +369,13 @@
   virtual void getRGBLine(Guchar *in, unsigned int *out, int length);
   virtual void getRGBLine(Guchar *in, Guchar *out, int length);
   virtual void getRGBXLine(Guchar *in, Guchar *out, int length);
+  virtual void getCMYKLine(Guchar *in, Guchar *out, int length);
+  virtual void getDeviceNLine(Guchar *in, Guchar *out, int length);
 
   virtual GBool useGetRGBLine() { return gTrue; }
   virtual GBool useGetGrayLine() { return gTrue; }
+  virtual GBool useGetCMYKLine() { return gTrue; }
+  virtual GBool useGetDeviceNLine() { return gTrue; }
 
   virtual int getNComps() { return 3; }
   virtual void getDefaultColor(GfxColor *color);
@@ -373,7 +396,7 @@
   virtual GfxColorSpaceMode getMode() { return csCalRGB; }
 
   // Construct a CalRGB color space.  Returns NULL if unsuccessful.
-  static GfxColorSpace *parse(Array *arr);
+  static GfxColorSpace *parse(Array *arr, GfxState *state);
 
   virtual void getGray(GfxColor *color, GfxGray *gray);
   virtual void getRGB(GfxColor *color, GfxRGB *rgb);
@@ -403,6 +426,9 @@
   double mat[9];		    // ABC -> XYZ transform matrix
   double kr, kg, kb;		    // gamut mapping mulitpliers
   void getXYZ(GfxColor *color, double *pX, double *pY, double *pZ);
+#ifdef USE_CMS
+  GfxColorTransform *transform;
+#endif
 };
 
 //------------------------------------------------------------------------
@@ -424,7 +450,11 @@
   virtual void getRGBLine(Guchar *in, unsigned int *out, int length);
   virtual void getRGBLine(Guchar *, Guchar *out, int length);
   virtual void getRGBXLine(Guchar *in, Guchar *out, int length);
+  virtual void getCMYKLine(Guchar *in, Guchar *out, int length);
+  virtual void getDeviceNLine(Guchar *in, Guchar *out, int length);
   virtual GBool useGetRGBLine() { return gTrue; }
+  virtual GBool useGetCMYKLine() { return gTrue; }
+  virtual GBool useGetDeviceNLine() { return gTrue; }
 
   virtual int getNComps() { return 4; }
   virtual void getDefaultColor(GfxColor *color);
@@ -445,7 +475,7 @@
   virtual GfxColorSpaceMode getMode() { return csLab; }
 
   // Construct a Lab color space.  Returns NULL if unsuccessful.
-  static GfxColorSpace *parse(Array *arr);
+  static GfxColorSpace *parse(Array *arr, GfxState *state);
 
   virtual void getGray(GfxColor *color, GfxGray *gray);
   virtual void getRGB(GfxColor *color, GfxRGB *rgb);
@@ -477,6 +507,9 @@
   double aMin, aMax, bMin, bMax;    // range for the a and b components
   double kr, kg, kb;		    // gamut mapping mulitpliers
   void getXYZ(GfxColor *color, double *pX, double *pY, double *pZ);
+#ifdef USE_CMS
+  GfxColorTransform *transform;
+#endif
 };
 
 //------------------------------------------------------------------------
@@ -493,7 +526,7 @@
   virtual GfxColorSpaceMode getMode() { return csICCBased; }
 
   // Construct an ICCBased color space.  Returns NULL if unsuccessful.
-  static GfxColorSpace *parse(Array *arr, OutputDev *out, int recursion);
+  static GfxColorSpace *parse(Array *arr, OutputDev *out, GfxState *state, int recursion);
 
   virtual void getGray(GfxColor *color, GfxGray *gray);
   virtual void getRGB(GfxColor *color, GfxRGB *rgb);
@@ -502,8 +535,12 @@
   virtual void getRGBLine(Guchar *in, unsigned int *out, int length);
   virtual void getRGBLine(Guchar *in, Guchar *out, int length);
   virtual void getRGBXLine(Guchar *in, Guchar *out, int length);
+  virtual void getCMYKLine(Guchar *in, Guchar *out, int length);
+  virtual void getDeviceNLine(Guchar *in, Guchar *out, int length);
 
   virtual GBool useGetRGBLine();
+  virtual GBool useGetCMYKLine();
+  virtual GBool useGetDeviceNLine();
 
   virtual int getNComps() { return nComps; }
   virtual void getDefaultColor(GfxColor *color);
@@ -522,8 +559,10 @@
   double rangeMax[4];		// max values for each component
   Ref iccProfileStream;		// the ICC profile
 #ifdef USE_CMS
+  int getIntent() { return (transform != NULL) ? transform->getIntent() : 0; }
   GfxColorTransform *transform;
   GfxColorTransform *lineTransform; // color transform for line
+  std::map<unsigned int, unsigned int> cmsCache;
 #endif
 };
 //------------------------------------------------------------------------
@@ -539,7 +578,7 @@
   virtual GfxColorSpaceMode getMode() { return csIndexed; }
 
   // Construct an Indexed color space.  Returns NULL if unsuccessful.
-  static GfxColorSpace *parse(Array *arr, OutputDev *out, int recursion);
+  static GfxColorSpace *parse(Array *arr, OutputDev *out, GfxState *state, int recursion);
 
   virtual void getGray(GfxColor *color, GfxGray *gray);
   virtual void getRGB(GfxColor *color, GfxRGB *rgb);
@@ -548,8 +587,12 @@
   virtual void getRGBLine(Guchar *in, unsigned int *out, int length);
   virtual void getRGBLine(Guchar *in, Guchar *out, int length);
   virtual void getRGBXLine(Guchar *in, Guchar *out, int length);
+  virtual void getCMYKLine(Guchar *in, Guchar *out, int length);
+  virtual void getDeviceNLine(Guchar *in, Guchar *out, int length);
 
   virtual GBool useGetRGBLine() { return gTrue; }
+  virtual GBool useGetCMYKLine() { return gTrue; }
+  virtual GBool useGetDeviceNLine() { return gTrue; }
 
   virtual int getNComps() { return 1; }
   virtual void getDefaultColor(GfxColor *color);
@@ -588,7 +631,7 @@
   virtual GfxColorSpaceMode getMode() { return csSeparation; }
 
   // Construct a Separation color space.  Returns NULL if unsuccessful.
-  static GfxColorSpace *parse(Array *arr, OutputDev *out, int recursion);
+  static GfxColorSpace *parse(Array *arr, OutputDev *out, GfxState *state, int recursion);
 
   virtual void getGray(GfxColor *color, GfxGray *gray);
   virtual void getRGB(GfxColor *color, GfxRGB *rgb);
@@ -633,7 +676,7 @@
   virtual GfxColorSpaceMode getMode() { return csDeviceN; }
 
   // Construct a DeviceN color space.  Returns NULL if unsuccessful.
-  static GfxColorSpace *parse(Array *arr, OutputDev *out, int recursion);
+  static GfxColorSpace *parse(Array *arr, OutputDev *out, GfxState *state, int recursion);
 
   virtual void getGray(GfxColor *color, GfxGray *gray);
   virtual void getRGB(GfxColor *color, GfxRGB *rgb);
@@ -680,7 +723,7 @@
   virtual GfxColorSpaceMode getMode() { return csPattern; }
 
   // Construct a Pattern color space.  Returns NULL if unsuccessful.
-  static GfxColorSpace *parse(Array *arr, OutputDev *out, int recursion);
+  static GfxColorSpace *parse(Array *arr, OutputDev *out, GfxState *state, int recursion);
 
   virtual void getGray(GfxColor *color, GfxGray *gray);
   virtual void getRGB(GfxColor *color, GfxRGB *rgb);
@@ -709,7 +752,7 @@
   GfxPattern(int typeA);
   virtual ~GfxPattern();
 
-  static GfxPattern *parse(Object *obj, OutputDev *out);
+  static GfxPattern *parse(Object *obj, OutputDev *out, GfxState *state);
 
   virtual GfxPattern *copy() = 0;
 
@@ -765,7 +808,7 @@
 class GfxShadingPattern: public GfxPattern {
 public:
 
-  static GfxShadingPattern *parse(Object *patObj, OutputDev *out);
+  static GfxShadingPattern *parse(Object *patObj, OutputDev *out, GfxState *state);
   virtual ~GfxShadingPattern();
 
   virtual GfxPattern *copy();
@@ -792,7 +835,7 @@
   GfxShading(GfxShading *shading);
   virtual ~GfxShading();
 
-  static GfxShading *parse(Object *obj, OutputDev *out);
+  static GfxShading *parse(Object *obj, OutputDev *out, GfxState *state);
 
   virtual GfxShading *copy() = 0;
 
@@ -806,7 +849,7 @@
 
 protected:
 
-  GBool init(Dict *dict, OutputDev *out);
+  GBool init(Dict *dict, OutputDev *out, GfxState *state);
 
   int type;
   GfxColorSpace *colorSpace;
@@ -875,7 +918,7 @@
   GfxFunctionShading(GfxFunctionShading *shading);
   virtual ~GfxFunctionShading();
 
-  static GfxFunctionShading *parse(Dict *dict, OutputDev *out);
+  static GfxFunctionShading *parse(Dict *dict, OutputDev *out, GfxState *state);
 
   virtual GfxShading *copy();
 
@@ -909,7 +952,7 @@
   GfxAxialShading(GfxAxialShading *shading);
   virtual ~GfxAxialShading();
 
-  static GfxAxialShading *parse(Dict *dict, OutputDev *out);
+  static GfxAxialShading *parse(Dict *dict, OutputDev *out, GfxState *state);
 
   virtual GfxShading *copy();
 
@@ -942,7 +985,7 @@
   GfxRadialShading(GfxRadialShading *shading);
   virtual ~GfxRadialShading();
 
-  static GfxRadialShading *parse(Dict *dict, OutputDev *out);
+  static GfxRadialShading *parse(Dict *dict, OutputDev *out, GfxState *state);
 
   virtual GfxShading *copy();
 
@@ -980,7 +1023,7 @@
   GfxGouraudTriangleShading(GfxGouraudTriangleShading *shading);
   virtual ~GfxGouraudTriangleShading();
 
-  static GfxGouraudTriangleShading *parse(int typeA, Dict *dict, Stream *str, OutputDev *out);
+  static GfxGouraudTriangleShading *parse(int typeA, Dict *dict, Stream *str, OutputDev *out, GfxState *state);
 
   virtual GfxShading *copy();
 
@@ -1066,7 +1109,7 @@
   GfxPatchMeshShading(GfxPatchMeshShading *shading);
   virtual ~GfxPatchMeshShading();
 
-  static GfxPatchMeshShading *parse(int typeA, Dict *dict, Stream *str, OutputDev *out);
+  static GfxPatchMeshShading *parse(int typeA, Dict *dict, Stream *str, OutputDev *out, GfxState *state);
 
   virtual GfxShading *copy();
 
@@ -1126,6 +1169,8 @@
   double getDecodeHigh(int i) { return decodeLow[i] + decodeRange[i]; }
   
   bool useRGBLine() { return (colorSpace2 && colorSpace2->useGetRGBLine ()) || (!colorSpace2 && colorSpace->useGetRGBLine ()); }
+  bool useCMYKLine() { return (colorSpace2 && colorSpace2->useGetCMYKLine ()) || (!colorSpace2 && colorSpace->useGetCMYKLine ()); }
+  bool useDeviceNLine() { return (colorSpace2 && colorSpace2->useGetDeviceNLine ()) || (!colorSpace2 && colorSpace->useGetDeviceNLine ()); }
 
   // Convert an image pixel to a color.
   void getGray(Guchar *x, GfxGray *gray);
@@ -1134,6 +1179,8 @@
   void getRGBLine(Guchar *in, Guchar *out, int length);
   void getRGBXLine(Guchar *in, Guchar *out, int length);
   void getGrayLine(Guchar *in, Guchar *out, int length);
+  void getCMYKLine(Guchar *in, Guchar *out, int length);
+  void getDeviceNLine(Guchar *in, Guchar *out, int length);
   void getCMYK(Guchar *x, GfxCMYK *cmyk);
   void getDeviceN(Guchar *x, GfxColor *deviceN);
   void getColor(Guchar *x, GfxColor *color);
@@ -1407,6 +1454,7 @@
   double getLeading() { return leading; }
   double getRise() { return rise; }
   int getRender() { return render; }
+  char *getRenderingIntent() { return renderingIntent; }
   GfxPath *getPath() { return path; }
   void setPath(GfxPath *pathA);
   double getCurX() { return curX; }
@@ -1485,6 +1533,14 @@
     { rise = riseA; }
   void setRender(int renderA)
     { render = renderA; }
+  void setRenderingIntent(const char *intent)
+    { strncpy(renderingIntent, intent, 31); }
+
+#ifdef USE_CMS
+  void setDisplayProfile(void *localDisplayProfileA);
+  void *getDisplayProfile() { return localDisplayProfile; }
+  GfxColorTransform *getXYZ2DisplayTransform();
+#endif
 
   // Add to path.
   void moveTo(double x, double y)
@@ -1573,10 +1629,20 @@
 
   double clipXMin, clipYMin,	// bounding box for clip region
          clipXMax, clipYMax;
+  char renderingIntent[32];
 
   GfxState *saved;		// next GfxState on stack
 
   GfxState(GfxState *state, GBool copyPath);
+
+#ifdef USE_CMS
+  void *localDisplayProfile;
+  int displayProfileRef;
+  GfxColorTransform *XYZ2DisplayTransformRelCol;
+  GfxColorTransform *XYZ2DisplayTransformAbsCol;
+  GfxColorTransform *XYZ2DisplayTransformSat;
+  GfxColorTransform *XYZ2DisplayTransformPerc;
+#endif
 };
 
 #endif
diff --git a/poppler/GlobalParams.cc b/poppler/GlobalParams.cc
index 6efdd0c..01a3157 100644
--- a/poppler/GlobalParams.cc
+++ b/poppler/GlobalParams.cc
@@ -34,6 +34,7 @@
 // Copyright (C) 2012 Adrian Johnson <ajohnson@redneon.com>
 // Copyright (C) 2012 Thomas Freitag <Thomas.Freitag@alfa.de>
 // Copyright (C) 2012 Peter Breitenlohner <peb@mppmu.mpg.de>
+// Copyright (C) 2013 Jason Crain <jason@aquaticape.us>
 //
 // To see a description of the changes please see the Changelog file that
 // came with your tarball or type make ChangeLog if you are building from git
@@ -576,7 +577,8 @@
 #ifdef _WIN32
   substFiles = new GooHash(gTrue);
 #endif
-  nameToUnicode = new NameToCharCode();
+  nameToUnicodeZapfDingbats = new NameToCharCode();
+  nameToUnicodeText = new NameToCharCode();
   cidToUnicodes = new GooHash(gTrue);
   unicodeToUnicodes = new GooHash(gTrue);
   residentUnicodeMaps = new GooHash();
@@ -648,9 +650,13 @@
   securityHandlers = new GooList();
 #endif
 
-  // set up the initial nameToUnicode table
-  for (i = 0; nameToUnicodeTab[i].name; ++i) {
-    nameToUnicode->add(nameToUnicodeTab[i].name, nameToUnicodeTab[i].u);
+  // set up the initial nameToUnicode tables
+  for (i = 0; nameToUnicodeZapfDingbatsTab[i].name; ++i) {
+    nameToUnicodeZapfDingbats->add(nameToUnicodeZapfDingbatsTab[i].name, nameToUnicodeZapfDingbatsTab[i].u);
+  }
+
+  for (i = 0; nameToUnicodeTextTab[i].name; ++i) {
+    nameToUnicodeText->add(nameToUnicodeTextTab[i].name, nameToUnicodeTextTab[i].u);
   }
 
   // set up the residentUnicodeMaps table
@@ -740,7 +746,7 @@
     tok2 = strtok_r(NULL, " \t\r\n", &tokptr);
     if (tok1 && tok2) {
       sscanf(tok1, "%x", &u);
-      nameToUnicode->add(tok2, u);
+      nameToUnicodeText->add(tok2, u);
     } else {
       error(errConfig, -1, "Bad line in 'nameToUnicode' file ({0:t}:{1:d})",
 	    name, line);
@@ -796,7 +802,8 @@
 
   delete macRomanReverseMap;
 
-  delete nameToUnicode;
+  delete nameToUnicodeZapfDingbats;
+  delete nameToUnicodeText;
   deleteGooHash(cidToUnicodes, GooString);
   deleteGooHash(unicodeToUnicodes, GooString);
   deleteGooHash(residentUnicodeMaps, UnicodeMap);
@@ -853,9 +860,17 @@
   return macRomanReverseMap->lookup(charName);
 }
 
-Unicode GlobalParams::mapNameToUnicode(const char *charName) {
-  // no need to lock - nameToUnicode is constant
-  return nameToUnicode->lookup(charName);
+Unicode GlobalParams::mapNameToUnicodeAll(const char *charName) {
+  // no need to lock - nameToUnicodeZapfDingbats and nameToUnicodeText are constant
+  Unicode u = nameToUnicodeZapfDingbats->lookup(charName);
+  if (!u)
+    u = nameToUnicodeText->lookup(charName);
+  return u;
+}
+
+Unicode GlobalParams::mapNameToUnicodeText(const char *charName) {
+  // no need to lock - nameToUnicodeText is constant
+  return nameToUnicodeText->lookup(charName);
 }
 
 UnicodeMap *GlobalParams::getResidentUnicodeMap(GooString *encodingName) {
diff --git a/poppler/GlobalParams.h b/poppler/GlobalParams.h
index bc11684..c33a802 100644
--- a/poppler/GlobalParams.h
+++ b/poppler/GlobalParams.h
@@ -25,6 +25,7 @@
 // Copyright (C) 2011 Pino Toscano <pino@kde.org>
 // Copyright (C) 2012 Adrian Johnson <ajohnson@redneon.com>
 // Copyright (C) 2012 Thomas Freitag <Thomas.Freitag@alfa.de>
+// Copyright (C) 2013 Jason Crain <jason@aquaticape.us>
 //
 // To see a description of the changes please see the Changelog file that
 // came with your tarball or type make ChangeLog if you are building from git
@@ -139,7 +140,14 @@
 
   CharCode getMacRomanCharCode(char *charName);
 
-  Unicode mapNameToUnicode(const char *charName);
+  // Return Unicode values for character names.  Used for general text
+  // extraction.
+  Unicode mapNameToUnicodeText(const char *charName);
+
+  // Return Unicode values for character names.  Used for glyph
+  // lookups or text extraction with ZapfDingbats fonts.
+  Unicode mapNameToUnicodeAll(const char *charName);
+
   UnicodeMap *getResidentUnicodeMap(GooString *encodingName);
   FILE *getUnicodeMapFile(GooString *encodingName);
   FILE *findCMapFile(GooString *collection, GooString *cMapName);
@@ -271,8 +279,10 @@
 
   //----- user-modifiable settings
 
-  NameToCharCode *		// mapping from char name to Unicode
-    nameToUnicode;
+  NameToCharCode *		// mapping from char name to Unicode for ZapfDingbats
+    nameToUnicodeZapfDingbats;
+  NameToCharCode *		// mapping from char name to Unicode for text
+    nameToUnicodeText;		// extraction
   GooHash *cidToUnicodes;		// files for mappings from char collections
 				//   to Unicode, indexed by collection name
 				//   [GooString]
diff --git a/poppler/Hints.h b/poppler/Hints.h
index f5225f9..f46c07f 100644
--- a/poppler/Hints.h
+++ b/poppler/Hints.h
@@ -5,7 +5,7 @@
 // This file is licensed under the GPLv2 or later
 //
 // Copyright 2010 Hib Eris <hib@hiberis.nl>
-// Copyright 2010 Albert Astals Cid <aacid@kde.org>
+// Copyright 2010, 2013 Albert Astals Cid <aacid@kde.org>
 // Copyright 2013 Adrian Johnson <ajohnson@redneon.com>
 //
 //========================================================================
@@ -57,7 +57,6 @@
   int pageObjectFirst;
   Goffset pageOffsetFirst;
   Guint pageEndFirst;
-  int objectNumberFirst;
 
   Guint nObjectLeast;
   Guint objectOffsetFirst;
diff --git a/poppler/JBIG2Stream.h b/poppler/JBIG2Stream.h
index be1b3bd..0ee2518 100644
--- a/poppler/JBIG2Stream.h
+++ b/poppler/JBIG2Stream.h
@@ -56,6 +56,7 @@
   virtual int lookChar();
   virtual GooString *getPSFilter(int psLevel, const char *indent);
   virtual GBool isBinary(GBool last = gTrue);
+  virtual Object *getGlobalsStream() { return &globalsStream; }
 
 private:
   virtual GBool hasGetChars() { return true; }
diff --git a/poppler/Makefile.am b/poppler/Makefile.am
index 6e21c17..07cfb3b 100644
--- a/poppler/Makefile.am
+++ b/poppler/Makefile.am
@@ -157,7 +157,7 @@
 	$(PTHREAD_LIBS)				\
 	$(win32_libs)
 
-libpoppler_la_LDFLAGS = -version-info 44:0:0 @create_shared_lib@ @auto_import_flags@
+libpoppler_la_LDFLAGS = -version-info 45:0:0 @create_shared_lib@ @auto_import_flags@
 
 if ENABLE_XPDF_HEADERS
 
@@ -216,6 +216,8 @@
 	StdinPDFDocBuilder.h	\
 	Stream-CCITT.h		\
 	Stream.h		\
+	StructElement.h		\
+	StructTreeRoot.h	\
 	UnicodeMap.h		\
 	UnicodeMapTables.h	\
 	UnicodeTypeTable.h	\
@@ -230,6 +232,7 @@
 	NameToUnicodeTable.h	\
 	PSOutputDev.h		\
 	TextOutputDev.h		\
+	MarkedContentOutputDev.h \
 	SecurityHandler.h	\
 	UTF.h			\
 	UTF8.h			\
@@ -294,6 +297,8 @@
 	StdinCachedFile.cc	\
 	StdinPDFDocBuilder.cc	\
 	Stream.cc 		\
+	StructTreeRoot.cc	\
+	StructElement.cc	\
 	strtok_r.cpp		\
 	UnicodeMap.cc		\
 	UnicodeTypeTable.cc	\
@@ -302,6 +307,7 @@
 	XRef.cc			\
 	PSOutputDev.cc		\
 	TextOutputDev.cc	\
+	MarkedContentOutputDev.cc \
 	PageLabelInfo.h		\
 	PageLabelInfo.cc	\
 	SecurityHandler.cc	\
diff --git a/poppler/MarkedContentOutputDev.cc b/poppler/MarkedContentOutputDev.cc
new file mode 100644
index 0000000..7fdd8f5
--- /dev/null
+++ b/poppler/MarkedContentOutputDev.cc
@@ -0,0 +1,212 @@
+//========================================================================
+//
+// MarkedContentOutputDev.cc
+//
+// This file is licensed under the GPLv2 or later
+//
+// Copyright 2013 Igalia S.L.
+//
+//========================================================================
+
+#include "MarkedContentOutputDev.h"
+#include "GlobalParams.h"
+#include "UnicodeMap.h"
+#include "GfxState.h"
+#include "GfxFont.h"
+#include "Annot.h"
+#include <vector>
+
+
+MarkedContentOutputDev::MarkedContentOutputDev(int mcidA):
+  currentFont(NULL),
+  currentText(NULL),
+  mcid(mcidA),
+  pageWidth(0.0),
+  pageHeight(0.0),
+  unicodeMap(NULL)
+{
+  currentColor.r = currentColor.g = currentColor.b = 0;
+}
+
+
+MarkedContentOutputDev::~MarkedContentOutputDev()
+{
+  if (unicodeMap)
+    unicodeMap->decRefCnt();
+  if (currentFont)
+    currentFont->decRefCnt();
+  delete currentText;
+}
+
+
+void MarkedContentOutputDev::endSpan()
+{
+  if (currentText && currentText->getLength()) {
+    // The TextSpan takes ownership of currentText and
+    // increases the reference count for currentFont.
+    textSpans.push_back(TextSpan(currentText,
+                                 currentFont,
+                                 currentColor));
+  }
+  currentText = NULL;
+}
+
+
+void MarkedContentOutputDev::startPage(int pageNum, GfxState *state, XRef *xref)
+{
+  if (state) {
+    pageWidth  = state->getPageWidth();
+    pageHeight = state->getPageHeight();
+  } else {
+    pageWidth = pageHeight = 0.0;
+  }
+}
+
+
+void MarkedContentOutputDev::endPage()
+{
+  pageWidth = pageHeight = 0.0;
+}
+
+
+void MarkedContentOutputDev::beginMarkedContent(char *name, Dict *properties)
+{
+  int id = -1;
+  if (properties)
+    properties->lookupInt("MCID", NULL, &id);
+
+  if (id == -1)
+    return;
+
+  // The stack keep track of MCIDs of nested marked content.
+  if (inMarkedContent() || id == mcid)
+    mcidStack.push_back(id);
+}
+
+
+void MarkedContentOutputDev::endMarkedContent(GfxState *state)
+{
+  if (inMarkedContent()) {
+      mcidStack.pop_back();
+      // The outer marked content sequence MCID was popped, ensure
+      // that the last piece of text collected ends up in a TextSpan.
+      if (!inMarkedContent())
+        endSpan();
+  }
+}
+
+
+bool MarkedContentOutputDev::needFontChange(GfxFont* font) const
+{
+  if (currentFont == font)
+    return gFalse;
+
+  if (!currentFont)
+    return font != NULL && font->isOk();
+
+  if (font == NULL)
+    return gTrue;
+
+  // Two non-null valid fonts are the same if they point to the same Ref
+  if (currentFont->getID()->num == font->getID()->num &&
+      currentFont->getID()->gen == font->getID()->gen)
+    return gFalse;
+
+  return gTrue;
+}
+
+
+void MarkedContentOutputDev::drawChar(GfxState *state,
+                                      double xx, double yy,
+                                      double dx, double dy,
+                                      double ox, double oy,
+                                      CharCode c, int nBytes,
+                                      Unicode *u, int uLen)
+{
+  if (!inMarkedContent() || !uLen)
+    return;
+
+
+  // Color changes are tracked here so the color can be chosen depending on
+  // the render mode (for mode 1 stroke color is used), so there is no need
+  // to implement both updateFillColor() and updateStrokeColor().
+  GBool colorChange = gFalse;
+  GfxRGB color;
+  if ((state->getRender() & 3) == 1)
+    state->getStrokeRGB(&color);
+  else
+    state->getFillRGB(&color);
+
+  colorChange = (color.r != currentColor.r ||
+                 color.g != currentColor.g ||
+                 color.b != currentColor.b);
+
+  // Check also for font changes.
+  GBool fontChange = needFontChange(state->getFont());
+
+  // Save a span with the current changes.
+  if (colorChange || fontChange) {
+    endSpan();
+  }
+
+  // Perform the color/font changes.
+  if (colorChange)
+    currentColor = color;
+
+  if (fontChange) {
+    if (currentFont != NULL) {
+      currentFont->decRefCnt();
+      currentFont = NULL;
+    }
+    if (state->getFont() != NULL) {
+      currentFont = state->getFont();
+      currentFont->incRefCnt();
+    }
+  }
+
+
+  double sp, dx2, dy2, w1, h1, x1, y1;
+
+  // Subtract char and word spacing from the (dx,dy) values
+  sp = state->getCharSpace();
+  if (c == (CharCode) 0x20)
+    sp += state->getWordSpace();
+  state->textTransformDelta(sp * state->getHorizScaling(), 0, &dx2, &dy2);
+  dx -= dx2;
+  dy -= dy2;
+  state->transformDelta(dx, dy, &w1, &h1);
+  state->transform(xx, yy, &x1, &y1);
+
+  // Throw away characters that are not inside the page boundaries.
+  if (x1 + w1 < 0 || x1 > pageWidth || y1 + h1 < 0 || y1 > pageHeight)
+    return;
+
+  // Make a sanity check on character size. Note: (x != x) <-> isnan(x)
+  if (x1 != x1 || y1 != y1 || w1 != w1 || h1 != h1)
+    return;
+
+  for (int i = 0; i < uLen; i++) {
+    // Soft hyphen markers are skipped, as they are invisible unless
+    // rendering is done to an actual device and the hyphenation hint
+    // used. MarkedContentOutputDev extracts the *visible* text content.
+    if (u[i] != 0x00AD) {
+      // Add the UTF-8 sequence to the current text span.
+      if (!unicodeMap)
+        unicodeMap = globalParams->getTextEncoding();
+
+      char buf[8];
+      int n = unicodeMap->mapUnicode(u[i], buf, sizeof(buf));
+      if (n > 0) {
+        if (currentText == NULL)
+          currentText = new GooString();
+        currentText->append(buf, n);
+      }
+    }
+  }
+}
+
+
+const TextSpanArray& MarkedContentOutputDev::getTextSpans() const
+{
+  return textSpans;
+}
diff --git a/poppler/MarkedContentOutputDev.h b/poppler/MarkedContentOutputDev.h
new file mode 100644
index 0000000..4ea60c2
--- /dev/null
+++ b/poppler/MarkedContentOutputDev.h
@@ -0,0 +1,130 @@
+//========================================================================
+//
+// MarkedContentOutputDev.h
+//
+// This file is licensed under the GPLv2 or later
+//
+// Copyright 2013 Igalia S.L.
+//
+//========================================================================
+
+#ifndef MARKEDCONTENTOUTPUTDEV_H
+#define MARKEDCONTENTOUTPUTDEV_H
+
+#include "goo/gtypes.h"
+#include "goo/gmem.h"
+#include "OutputDev.h"
+#include "GfxState.h"
+#include "GfxFont.h"
+#include <vector>
+
+class Dict;
+class UnicodeMap;
+
+
+class TextSpan {
+public:
+  TextSpan(const TextSpan& other): data(other.data) {
+    data->refcount++;
+  }
+
+  TextSpan& operator=(const TextSpan& other) {
+    if (this != &other) {
+      data = other.data;
+      data->refcount++;
+    }
+    return *this;
+  }
+
+  ~TextSpan() {
+    if (data && --data->refcount == 0)
+      delete data;
+  }
+
+  GfxFont* getFont() const { return data->font; }
+  GooString* getText() const { return data->text; }
+  GfxRGB& getColor() const { return data->color; }
+
+private:
+  // Note: Takes ownership of strings, increases refcount for font.
+  TextSpan(GooString *text,
+           GfxFont *font,
+           const GfxRGB& color)
+      : data(new Data) {
+    data->text = text;
+    data->font = font;
+    data->color = color;
+    if (data->font)
+      data->font->incRefCnt();
+  }
+
+  struct Data {
+    GfxFont   *font;
+    GooString *text;
+    GfxRGB     color;
+    unsigned refcount;
+
+    Data(): refcount(1) {}
+
+    ~Data() {
+      assert(refcount == 0);
+      if (font)
+        font->decRefCnt();
+      delete text;
+    }
+  };
+
+  Data *data;
+
+  friend class MarkedContentOutputDev;
+};
+
+
+typedef std::vector<TextSpan> TextSpanArray;
+
+
+class MarkedContentOutputDev: public OutputDev {
+public:
+  MarkedContentOutputDev(int mcidA);
+  virtual ~MarkedContentOutputDev();
+
+  virtual GBool isOk() { return gTrue; }
+  virtual GBool upsideDown() { return gTrue; }
+  virtual GBool useDrawChar() { return gTrue; }
+  virtual GBool interpretType3Chars() { return gFalse; }
+  virtual GBool needNonText() { return gFalse; }
+  virtual GBool needCharCount() { return gFalse; }
+
+  virtual void startPage(int pageNum, GfxState *state, XRef *xref);
+  virtual void endPage();
+
+  virtual void drawChar(GfxState *state,
+                        double xx, double yy,
+                        double dx, double dy,
+                        double ox, double oy,
+                        CharCode c, int nBytes,
+                        Unicode *u, int uLen);
+
+  virtual void beginMarkedContent(char *name, Dict *properties);
+  virtual void endMarkedContent(GfxState *state);
+
+  const TextSpanArray& getTextSpans() const;
+
+private:
+
+  void endSpan();
+  bool inMarkedContent() const { return mcidStack.size() > 0; }
+  bool needFontChange(GfxFont* font) const;
+
+  GfxFont         *currentFont;
+  GooString       *currentText;
+  GfxRGB           currentColor;
+  TextSpanArray    textSpans;
+  int              mcid;
+  std::vector<int> mcidStack;
+  double           pageWidth;
+  double           pageHeight;
+  UnicodeMap      *unicodeMap;
+};
+
+#endif /* !MARKEDCONTENTOUTPUTDEV_H */
diff --git a/poppler/NameToUnicodeTable.h b/poppler/NameToUnicodeTable.h
index 74645ab..827edba 100644
--- a/poppler/NameToUnicodeTable.h
+++ b/poppler/NameToUnicodeTable.h
@@ -14,6 +14,7 @@
 // under GPL version 2 or later
 //
 // Copyright (C) 2011, 2012 Albert Astals Cid <aacid@kde.org>
+// Copyright (C) 2013 Jason Crain <jason@aquaticape.us>
 //
 // To see a description of the changes please see the Changelog file that
 // came with your tarball or type make ChangeLog if you are building from git
@@ -24,10 +25,13 @@
 
 #include <stddef.h>
 
-static struct {
+struct NameToUnicodeTab {
   Unicode u;
   const char *name;
-} nameToUnicodeTab[] = {
+};
+
+// map character names to Unicode
+static struct NameToUnicodeTab nameToUnicodeTextTab[] = {
   {0x0021, "!"},
   {0x0023, "#"},
   {0x0024, "$"},
@@ -800,207 +804,6 @@
   {0x005f, "_"},
   {0x0060, "`"},
   {0x0061, "a"},
-  {0x275e, "a100"},
-  {0x2761, "a101"},
-  {0x2762, "a102"},
-  {0x2763, "a103"},
-  {0x2764, "a104"},
-  {0x2710, "a105"},
-  {0x2765, "a106"},
-  {0x2766, "a107"},
-  {0x2767, "a108"},
-  {0x2660, "a109"},
-  {0x2721, "a10"},
-  {0x2665, "a110"},
-  {0x2666, "a111"},
-  {0x2663, "a112"},
-  {0x2709, "a117"},
-  {0x2708, "a118"},
-  {0x2707, "a119"},
-  {0x261b, "a11"},
-  {0x2460, "a120"},
-  {0x2461, "a121"},
-  {0x2462, "a122"},
-  {0x2463, "a123"},
-  {0x2464, "a124"},
-  {0x2465, "a125"},
-  {0x2466, "a126"},
-  {0x2467, "a127"},
-  {0x2468, "a128"},
-  {0x2469, "a129"},
-  {0x261e, "a12"},
-  {0x2776, "a130"},
-  {0x2777, "a131"},
-  {0x2778, "a132"},
-  {0x2779, "a133"},
-  {0x277a, "a134"},
-  {0x277b, "a135"},
-  {0x277c, "a136"},
-  {0x277d, "a137"},
-  {0x277e, "a138"},
-  {0x277f, "a139"},
-  {0x270c, "a13"},
-  {0x2780, "a140"},
-  {0x2781, "a141"},
-  {0x2782, "a142"},
-  {0x2783, "a143"},
-  {0x2784, "a144"},
-  {0x2785, "a145"},
-  {0x2786, "a146"},
-  {0x2787, "a147"},
-  {0x2788, "a148"},
-  {0x2789, "a149"},
-  {0x270d, "a14"},
-  {0x278a, "a150"},
-  {0x278b, "a151"},
-  {0x278c, "a152"},
-  {0x278d, "a153"},
-  {0x278e, "a154"},
-  {0x278f, "a155"},
-  {0x2790, "a156"},
-  {0x2791, "a157"},
-  {0x2792, "a158"},
-  {0x2793, "a159"},
-  {0x270e, "a15"},
-  {0x2794, "a160"},
-  {0x2192, "a161"},
-  {0x27a3, "a162"},
-  {0x2194, "a163"},
-  {0x2195, "a164"},
-  {0x2799, "a165"},
-  {0x279b, "a166"},
-  {0x279c, "a167"},
-  {0x279d, "a168"},
-  {0x279e, "a169"},
-  {0x270f, "a16"},
-  {0x279f, "a170"},
-  {0x27a0, "a171"},
-  {0x27a1, "a172"},
-  {0x27a2, "a173"},
-  {0x27a4, "a174"},
-  {0x27a5, "a175"},
-  {0x27a6, "a176"},
-  {0x27a7, "a177"},
-  {0x27a8, "a178"},
-  {0x27a9, "a179"},
-  {0x2711, "a17"},
-  {0x27ab, "a180"},
-  {0x27ad, "a181"},
-  {0x27af, "a182"},
-  {0x27b2, "a183"},
-  {0x27b3, "a184"},
-  {0x27b5, "a185"},
-  {0x27b8, "a186"},
-  {0x27ba, "a187"},
-  {0x27bb, "a188"},
-  {0x27bc, "a189"},
-  {0x2712, "a18"},
-  {0x27bd, "a190"},
-  {0x27be, "a191"},
-  {0x279a, "a192"},
-  {0x27aa, "a193"},
-  {0x27b6, "a194"},
-  {0x27b9, "a195"},
-  {0x2798, "a196"},
-  {0x27b4, "a197"},
-  {0x27b7, "a198"},
-  {0x27ac, "a199"},
-  {0x2713, "a19"},
-  {0x2701, "a1"},
-  {0x27ae, "a200"},
-  {0x27b1, "a201"},
-  {0x2703, "a202"},
-  {0x2750, "a203"},
-  {0x2752, "a204"},
-  {0x276e, "a205"},
-  {0x2770, "a206"},
-  {0x2714, "a20"},
-  {0x2715, "a21"},
-  {0x2716, "a22"},
-  {0x2717, "a23"},
-  {0x2718, "a24"},
-  {0x2719, "a25"},
-  {0x271a, "a26"},
-  {0x271b, "a27"},
-  {0x271c, "a28"},
-  {0x2722, "a29"},
-  {0x2702, "a2"},
-  {0x2723, "a30"},
-  {0x2724, "a31"},
-  {0x2725, "a32"},
-  {0x2726, "a33"},
-  {0x2727, "a34"},
-  {0x2605, "a35"},
-  {0x2729, "a36"},
-  {0x272a, "a37"},
-  {0x272b, "a38"},
-  {0x272c, "a39"},
-  {0x2704, "a3"},
-  {0x272d, "a40"},
-  {0x272e, "a41"},
-  {0x272f, "a42"},
-  {0x2730, "a43"},
-  {0x2731, "a44"},
-  {0x2732, "a45"},
-  {0x2733, "a46"},
-  {0x2734, "a47"},
-  {0x2735, "a48"},
-  {0x2736, "a49"},
-  {0x260e, "a4"},
-  {0x2737, "a50"},
-  {0x2738, "a51"},
-  {0x2739, "a52"},
-  {0x273a, "a53"},
-  {0x273b, "a54"},
-  {0x273c, "a55"},
-  {0x273d, "a56"},
-  {0x273e, "a57"},
-  {0x273f, "a58"},
-  {0x2740, "a59"},
-  {0x2706, "a5"},
-  {0x2741, "a60"},
-  {0x2742, "a61"},
-  {0x2743, "a62"},
-  {0x2744, "a63"},
-  {0x2745, "a64"},
-  {0x2746, "a65"},
-  {0x2747, "a66"},
-  {0x2748, "a67"},
-  {0x2749, "a68"},
-  {0x274a, "a69"},
-  {0x271d, "a6"},
-  {0x274b, "a70"},
-  {0x25cf, "a71"},
-  {0x274d, "a72"},
-  {0x25a0, "a73"},
-  {0x274f, "a74"},
-  {0x2751, "a75"},
-  {0x25b2, "a76"},
-  {0x25bc, "a77"},
-  {0x25c6, "a78"},
-  {0x2756, "a79"},
-  {0x271e, "a7"},
-  {0x25d7, "a81"},
-  {0x2758, "a82"},
-  {0x2759, "a83"},
-  {0x275a, "a84"},
-  {0x276f, "a85"},
-  {0x2771, "a86"},
-  {0x2772, "a87"},
-  {0x2773, "a88"},
-  {0x2768, "a89"},
-  {0x271f, "a8"},
-  {0x2769, "a90"},
-  {0x276c, "a91"},
-  {0x276d, "a92"},
-  {0x276a, "a93"},
-  {0x276b, "a94"},
-  {0x2774, "a95"},
-  {0x2775, "a96"},
-  {0x275b, "a97"},
-  {0x275c, "a98"},
-  {0x275d, "a99"},
-  {0x2720, "a9"},
   {0x0986, "aabengali"},
   {0x00e1, "aacute"},
   {0x0906, "aadeva"},
@@ -4473,3 +4276,209 @@
   {0x007e, "~"},
   { 0, NULL }
 };
+
+// map ZapfDingbats names to Unicode
+static struct NameToUnicodeTab nameToUnicodeZapfDingbatsTab[] = {
+  {0x275e, "a100"},
+  {0x2761, "a101"},
+  {0x2762, "a102"},
+  {0x2763, "a103"},
+  {0x2764, "a104"},
+  {0x2710, "a105"},
+  {0x2765, "a106"},
+  {0x2766, "a107"},
+  {0x2767, "a108"},
+  {0x2660, "a109"},
+  {0x2721, "a10"},
+  {0x2665, "a110"},
+  {0x2666, "a111"},
+  {0x2663, "a112"},
+  {0x2709, "a117"},
+  {0x2708, "a118"},
+  {0x2707, "a119"},
+  {0x261b, "a11"},
+  {0x2460, "a120"},
+  {0x2461, "a121"},
+  {0x2462, "a122"},
+  {0x2463, "a123"},
+  {0x2464, "a124"},
+  {0x2465, "a125"},
+  {0x2466, "a126"},
+  {0x2467, "a127"},
+  {0x2468, "a128"},
+  {0x2469, "a129"},
+  {0x261e, "a12"},
+  {0x2776, "a130"},
+  {0x2777, "a131"},
+  {0x2778, "a132"},
+  {0x2779, "a133"},
+  {0x277a, "a134"},
+  {0x277b, "a135"},
+  {0x277c, "a136"},
+  {0x277d, "a137"},
+  {0x277e, "a138"},
+  {0x277f, "a139"},
+  {0x270c, "a13"},
+  {0x2780, "a140"},
+  {0x2781, "a141"},
+  {0x2782, "a142"},
+  {0x2783, "a143"},
+  {0x2784, "a144"},
+  {0x2785, "a145"},
+  {0x2786, "a146"},
+  {0x2787, "a147"},
+  {0x2788, "a148"},
+  {0x2789, "a149"},
+  {0x270d, "a14"},
+  {0x278a, "a150"},
+  {0x278b, "a151"},
+  {0x278c, "a152"},
+  {0x278d, "a153"},
+  {0x278e, "a154"},
+  {0x278f, "a155"},
+  {0x2790, "a156"},
+  {0x2791, "a157"},
+  {0x2792, "a158"},
+  {0x2793, "a159"},
+  {0x270e, "a15"},
+  {0x2794, "a160"},
+  {0x2192, "a161"},
+  {0x27a3, "a162"},
+  {0x2194, "a163"},
+  {0x2195, "a164"},
+  {0x2799, "a165"},
+  {0x279b, "a166"},
+  {0x279c, "a167"},
+  {0x279d, "a168"},
+  {0x279e, "a169"},
+  {0x270f, "a16"},
+  {0x279f, "a170"},
+  {0x27a0, "a171"},
+  {0x27a1, "a172"},
+  {0x27a2, "a173"},
+  {0x27a4, "a174"},
+  {0x27a5, "a175"},
+  {0x27a6, "a176"},
+  {0x27a7, "a177"},
+  {0x27a8, "a178"},
+  {0x27a9, "a179"},
+  {0x2711, "a17"},
+  {0x27ab, "a180"},
+  {0x27ad, "a181"},
+  {0x27af, "a182"},
+  {0x27b2, "a183"},
+  {0x27b3, "a184"},
+  {0x27b5, "a185"},
+  {0x27b8, "a186"},
+  {0x27ba, "a187"},
+  {0x27bb, "a188"},
+  {0x27bc, "a189"},
+  {0x2712, "a18"},
+  {0x27bd, "a190"},
+  {0x27be, "a191"},
+  {0x279a, "a192"},
+  {0x27aa, "a193"},
+  {0x27b6, "a194"},
+  {0x27b9, "a195"},
+  {0x2798, "a196"},
+  {0x27b4, "a197"},
+  {0x27b7, "a198"},
+  {0x27ac, "a199"},
+  {0x2713, "a19"},
+  {0x2701, "a1"},
+  {0x27ae, "a200"},
+  {0x27b1, "a201"},
+  {0x2703, "a202"},
+  {0x2750, "a203"},
+  {0x2752, "a204"},
+  {0x276e, "a205"},
+  {0x2770, "a206"},
+  {0x2714, "a20"},
+  {0x2715, "a21"},
+  {0x2716, "a22"},
+  {0x2717, "a23"},
+  {0x2718, "a24"},
+  {0x2719, "a25"},
+  {0x271a, "a26"},
+  {0x271b, "a27"},
+  {0x271c, "a28"},
+  {0x2722, "a29"},
+  {0x2702, "a2"},
+  {0x2723, "a30"},
+  {0x2724, "a31"},
+  {0x2725, "a32"},
+  {0x2726, "a33"},
+  {0x2727, "a34"},
+  {0x2605, "a35"},
+  {0x2729, "a36"},
+  {0x272a, "a37"},
+  {0x272b, "a38"},
+  {0x272c, "a39"},
+  {0x2704, "a3"},
+  {0x272d, "a40"},
+  {0x272e, "a41"},
+  {0x272f, "a42"},
+  {0x2730, "a43"},
+  {0x2731, "a44"},
+  {0x2732, "a45"},
+  {0x2733, "a46"},
+  {0x2734, "a47"},
+  {0x2735, "a48"},
+  {0x2736, "a49"},
+  {0x260e, "a4"},
+  {0x2737, "a50"},
+  {0x2738, "a51"},
+  {0x2739, "a52"},
+  {0x273a, "a53"},
+  {0x273b, "a54"},
+  {0x273c, "a55"},
+  {0x273d, "a56"},
+  {0x273e, "a57"},
+  {0x273f, "a58"},
+  {0x2740, "a59"},
+  {0x2706, "a5"},
+  {0x2741, "a60"},
+  {0x2742, "a61"},
+  {0x2743, "a62"},
+  {0x2744, "a63"},
+  {0x2745, "a64"},
+  {0x2746, "a65"},
+  {0x2747, "a66"},
+  {0x2748, "a67"},
+  {0x2749, "a68"},
+  {0x274a, "a69"},
+  {0x271d, "a6"},
+  {0x274b, "a70"},
+  {0x25cf, "a71"},
+  {0x274d, "a72"},
+  {0x25a0, "a73"},
+  {0x274f, "a74"},
+  {0x2751, "a75"},
+  {0x25b2, "a76"},
+  {0x25bc, "a77"},
+  {0x25c6, "a78"},
+  {0x2756, "a79"},
+  {0x271e, "a7"},
+  {0x25d7, "a81"},
+  {0x2758, "a82"},
+  {0x2759, "a83"},
+  {0x275a, "a84"},
+  {0x276f, "a85"},
+  {0x2771, "a86"},
+  {0x2772, "a87"},
+  {0x2773, "a88"},
+  {0x2768, "a89"},
+  {0x271f, "a8"},
+  {0x2769, "a90"},
+  {0x276c, "a91"},
+  {0x276d, "a92"},
+  {0x276a, "a93"},
+  {0x276b, "a94"},
+  {0x2774, "a95"},
+  {0x2775, "a96"},
+  {0x275b, "a97"},
+  {0x275c, "a98"},
+  {0x275d, "a99"},
+  {0x2720, "a9"},
+  { 0, NULL }
+};
diff --git a/poppler/Object.h b/poppler/Object.h
index 1b12354..15bea15 100644
--- a/poppler/Object.h
+++ b/poppler/Object.h
@@ -20,6 +20,7 @@
 // Copyright (C) 2012 Fabio D'Urso <fabiodurso@hotmail.it>
 // Copyright (C) 2013 Thomas Freitag <Thomas.Freitag@alfa.de>
 // Copyright (C) 2013 Adrian Johnson <ajohnson@redneon.com>
+// Copyright (C) 2013 Adrian Perez de Castro <aperez@igalia.com>
 //
 // To see a description of the changes please see the Changelog file that
 // came with your tarball or type make ChangeLog if you are building from git
@@ -44,14 +45,14 @@
 
 #define OBJECT_TYPE_CHECK(wanted_type) \
     if (unlikely(type != wanted_type)) { \
-        error(errInternal, 0, (char *) "Call to Object where the object was type {0:d}, " \
+        error(errInternal, 0, "Call to Object where the object was type {0:d}, " \
                  "not the expected type {1:d}", type, wanted_type); \
         abort(); \
     }
 
 #define OBJECT_3TYPES_CHECK(wanted_type1, wanted_type2, wanted_type3) \
     if (unlikely(type != wanted_type1) && unlikely(type != wanted_type2) && unlikely(type != wanted_type3)) { \
-        error(errInternal, 0, (char *) "Call to Object where the object was type {0:d}, " \
+        error(errInternal, 0, "Call to Object where the object was type {0:d}, " \
 	      "not the expected type {1:d}, {2:d} or {3:d}", type, wanted_type1, wanted_type2, wanted_type3); \
         abort(); \
     }
@@ -199,6 +200,10 @@
   double getNum() { OBJECT_3TYPES_CHECK(objInt, objInt64, objReal);
     return type == objInt ? (double)intg : type == objInt64 ? (double)int64g : real; }
   GooString *getString() { OBJECT_TYPE_CHECK(objString); return string; }
+  // After takeString() the only method that should be called for the object is free()
+  // because the object it's not expected to have a NULL string.
+  GooString *takeString() {
+    OBJECT_TYPE_CHECK(objString); GooString *s = string; string = NULL; return s; }
   char *getName() { OBJECT_TYPE_CHECK(objName); return name; }
   Array *getArray() { OBJECT_TYPE_CHECK(objArray); return array; }
   Dict *getDict() { OBJECT_TYPE_CHECK(objDict); return dict; }
diff --git a/poppler/OptionalContent.h b/poppler/OptionalContent.h
index 02c9a47..2e41646 100644
--- a/poppler/OptionalContent.h
+++ b/poppler/OptionalContent.h
@@ -4,6 +4,7 @@
 //
 // Copyright 2007 Brad Hards <bradh@kde.org>
 // Copyright 2008 Carlos Garcia Campos <carlosgc@gnome.org>
+// Copyright 2013 Albert Astals Cid <aacid@kde.org>
 //
 // Released under the GPL (version 2, or later, at your option)
 //
@@ -101,7 +102,6 @@
   UsageState getPrintState() { return printState; }
 
 private:
-  XRef *xref;
   GooString *m_name;
   Ref m_ref;
   State m_state;
diff --git a/poppler/PDFDoc.h b/poppler/PDFDoc.h
index da9bf5b..42c7cba 100644
--- a/poppler/PDFDoc.h
+++ b/poppler/PDFDoc.h
@@ -26,6 +26,7 @@
 // Copyright (C) 2012 Fabio D'Urso <fabiodurso@hotmail.it>
 // Copyright (C) 2013 Adrian Johnson <ajohnson@redneon.com>
 // Copyright (C) 2013 Adam Reichold <adamreichold@myopera.com>
+// Copyright (C) 2013 Adrian Perez de Castro <aperez@igalia.com>
 //
 // To see a description of the changes please see the Changelog file that
 // came with your tarball or type make ChangeLog if you are building from git
@@ -60,6 +61,7 @@
 class Linearization;
 class SecurityHandler;
 class Hints;
+class StructTreeRoot;
 
 enum PDFWriteMode {
   writeStandard,
@@ -139,7 +141,7 @@
   GooString *readMetadata() { return catalog->readMetadata(); }
 
   // Return the structure tree root object.
-  Object *getStructTreeRoot() { return catalog->getStructTreeRoot(); }
+  StructTreeRoot *getStructTreeRoot() { return catalog->getStructTreeRoot(); }
 
   // Get page.
   Page *getPage(int page);
diff --git a/poppler/Page.cc b/poppler/Page.cc
index e0a3b29..ecdff32 100644
--- a/poppler/Page.cc
+++ b/poppler/Page.cc
@@ -27,6 +27,7 @@
 // Copyright (C) 2012, 2013 Fabio D'Urso <fabiodurso@hotmail.it>
 // Copyright (C) 2013 Thomas Freitag <Thomas.Freitag@alfa.de>
 // Copyright (C) 2013 Jason Crain <jason@aquaticape.us>
+// Copyright (C) 2013 Adrian Johnson <ajohnson@redneon.com>
 //
 // To see a description of the changes please see the Changelog file that
 // came with your tarball or type make ChangeLog if you are building from git
@@ -671,7 +672,7 @@
     obj1.free ();
     dict->lookup ("CS", &obj1);
   }
-  colorSpace = GfxColorSpace::parse(&obj1, NULL);
+  colorSpace = GfxColorSpace::parse(&obj1, NULL, NULL);
   obj1.free();
   if (!colorSpace) {
     fprintf (stderr, "Error: Cannot parse color space\n");
@@ -823,3 +824,23 @@
   }
  delete state;
 }
+
+LinkAction* Page::getAdditionalAction(PageAdditionalActionsType type) {
+  Object additionalActionsObject;
+  LinkAction *linkAction = NULL;
+
+  if (actions.fetch(doc->getXRef(), &additionalActionsObject)->isDict()) {
+    const char *key = (type == actionOpenPage ?  "O" :
+                       type == actionClosePage ? "C" : NULL);
+
+    Object actionObject;
+
+    if (additionalActionsObject.dictLookup(key, &actionObject)->isDict())
+      linkAction = LinkAction::parseAction(&actionObject, doc->getCatalog()->getBaseURI());
+    actionObject.free();
+  }
+
+  additionalActionsObject.free();
+
+  return linkAction;
+}
diff --git a/poppler/Page.h b/poppler/Page.h
index 1c9d0a9..2aaabae 100644
--- a/poppler/Page.h
+++ b/poppler/Page.h
@@ -22,6 +22,7 @@
 // Copyright (C) 2012 Fabio D'Urso <fabiodurso@hotmail.it>
 // Copyright (C) 2012 Albert Astals Cid <aacid@kde.org>
 // Copyright (C) 2013 Thomas Freitag <Thomas.Freitag@alfa.de>
+// Copyright (C) 2013 Adrian Johnson <ajohnson@redneon.com>
 //
 // To see a description of the changes please see the Changelog file that
 // came with your tarball or type make ChangeLog if you are building from git
@@ -44,6 +45,7 @@
 class XRef;
 class OutputDev;
 class Links;
+class LinkAction;
 class Annots;
 class Annot;
 class Gfx;
@@ -211,6 +213,13 @@
   // Get actions
   Object *getActions(Object *obj) { return actions.fetch(xref, obj); }
 
+  enum PageAdditionalActionsType {
+    actionOpenPage,     ///< Performed when opening the page
+    actionClosePage,    ///< Performed when closing the page
+  };
+
+  LinkAction *getAdditionalAction(PageAdditionalActionsType type);
+
   Gfx *createGfx(OutputDev *out, double hDPI, double vDPI,
 		 int rotate, GBool useMediaBox, GBool crop,
 		 int sliceX, int sliceY, int sliceW, int sliceH,
@@ -267,7 +276,7 @@
   Object contents;		// page contents
   Object thumb;			// page thumbnail
   Object trans;			// page transition
-  Object actions;		// page addiction actions
+  Object actions;		// page additional actions
   double duration;              // page duration
   GBool ok;			// true if page is valid
 #if MULTITHREADED
diff --git a/poppler/SplashOutputDev.cc b/poppler/SplashOutputDev.cc
index 8c69502..d0a9454 100644
--- a/poppler/SplashOutputDev.cc
+++ b/poppler/SplashOutputDev.cc
@@ -1195,6 +1195,7 @@
   SplashBitmap *shape;
   GBool knockout;
   SplashCoord knockoutOpacity;
+  GBool fontAA;
 
   //----- saved state
   SplashBitmap *origBitmap;
@@ -2888,19 +2889,27 @@
       break;
 #if SPLASH_CMYK
     case splashModeCMYK8:
-      for (x = 0, q = colorLine; x < imgData->width; ++x, p += nComps) {
-	imgData->colorMap->getCMYK(p, &cmyk);
-	*q++ = colToByte(cmyk.c);
-	*q++ = colToByte(cmyk.m);
-	*q++ = colToByte(cmyk.y);
-	*q++ = colToByte(cmyk.k);
+      if (imgData->colorMap->useCMYKLine()) {
+	imgData->colorMap->getCMYKLine(p, (Guchar *) colorLine, imgData->width);
+      } else {
+	for (x = 0, q = colorLine; x < imgData->width; ++x, p += nComps) {
+	  imgData->colorMap->getCMYK(p, &cmyk);
+	  *q++ = colToByte(cmyk.c);
+	  *q++ = colToByte(cmyk.m);
+	  *q++ = colToByte(cmyk.y);
+	  *q++ = colToByte(cmyk.k);
+	}
       }
       break;
     case splashModeDeviceN8:
-      for (x = 0, q = colorLine; x < imgData->width; ++x, p += nComps) {
-	imgData->colorMap->getDeviceN(p, &deviceN);
-  for (int cp = 0; cp < SPOT_NCOMPS+4; cp++)
-    *q++ = colToByte(deviceN.c[cp]);
+      if (imgData->colorMap->useDeviceNLine()) {
+	imgData->colorMap->getDeviceNLine(p, (Guchar *) colorLine, imgData->width);
+      } else {
+        for (x = 0, q = colorLine; x < imgData->width; ++x, p += nComps) {
+	  imgData->colorMap->getDeviceN(p, &deviceN);
+          for (int cp = 0; cp < SPOT_NCOMPS+4; cp++)
+            *q++ = colToByte(deviceN.c[cp]);
+	}
       }
       break;
 #endif
@@ -3803,7 +3812,7 @@
   transpGroup->blendingColorSpace = blendingColorSpace;
   transpGroup->isolated = isolated;
   transpGroup->shape = (knockout && !isolated) ? SplashBitmap::copy(bitmap) : NULL;
-  transpGroup->knockout = gFalse; 
+  transpGroup->knockout = (knockout && isolated);
   transpGroup->knockoutOpacity = 1.0;
   transpGroup->next = transpGroupStack;
   transpGroupStack = transpGroup;
@@ -3811,6 +3820,7 @@
   // save state
   transpGroup->origBitmap = bitmap;
   transpGroup->origSplash = splash;
+  transpGroup->fontAA = fontEngine->getAA();
 
   //~ this handles the blendingColorSpace arg for soft masks, but
   //~   not yet for transparency groups
@@ -3842,6 +3852,9 @@
 			    bitmapTopDown, bitmap->getSeparationList());
   splash = new Splash(bitmap, vectorAntialias,
 		      transpGroup->origSplash->getScreen());
+  if (transpGroup->next != NULL && transpGroup->next->knockout) {
+    fontEngine->setAA(gFalse);
+  }
   splash->setThinLineMode(transpGroup->origSplash->getThinLineMode());
   splash->setMinLineWidth(globalParams->getMinLineWidth());
   //~ Acrobat apparently copies at least the fill and stroke colors, and
@@ -3860,8 +3873,12 @@
   } else {
     SplashBitmap *shape = (knockout) ? transpGroup->shape :
                                        (transpGroup->next != NULL && transpGroup->next->shape != NULL) ? transpGroup->next->shape : transpGroup->origBitmap;
+    int shapeTx = (knockout) ? tx :
+      (transpGroup->next != NULL && transpGroup->next->shape != NULL) ? transpGroup->next->tx + tx : tx;
+    int shapeTy = (knockout) ? ty :
+      (transpGroup->next != NULL && transpGroup->next->shape != NULL) ? transpGroup->next->ty + ty : ty;
     splash->blitTransparent(transpGroup->origBitmap, tx, ty, 0, 0, w, h);
-    splash->setInNonIsolatedGroup(shape, tx, ty);
+    splash->setInNonIsolatedGroup(shape, shapeTx, shapeTy);
   }
   transpGroup->tBitmap = bitmap;
   state->shiftCTMAndClip(-tx, -ty);
@@ -3898,8 +3915,9 @@
                                                                    : transpGroupStack->knockoutOpacity;
     splash->setOverprintMask(0xffffffff, gFalse);
     splash->composite(tBitmap, 0, 0, tx, ty,
-	      tBitmap->getWidth(), tBitmap->getHeight(),
-	      gFalse, !isolated, transpGroupStack->next != NULL && transpGroupStack->next->knockout, knockoutOpacity);
+      tBitmap->getWidth(), tBitmap->getHeight(),
+      gFalse, !isolated, transpGroupStack->next != NULL && transpGroupStack->next->knockout, knockoutOpacity);
+    fontEngine->setAA(transpGroupStack->fontAA);
     if (transpGroupStack->next != NULL && transpGroupStack->next->shape != NULL) {
       transpGroupStack->next->knockout = gTrue;
     }
diff --git a/poppler/Stream.h b/poppler/Stream.h
index 9b40fd1..00b2925 100644
--- a/poppler/Stream.h
+++ b/poppler/Stream.h
@@ -797,6 +797,11 @@
 
   virtual void unfilteredReset ();
 
+  int getEncoding() { return encoding; }
+  GBool getEndOfLine() { return endOfLine; }
+  int getColumns() { return columns; }
+  GBool getBlackIs1() { return black; }
+
 private:
 
   void ccittReset(GBool unfiltered);
diff --git a/poppler/StructElement.cc b/poppler/StructElement.cc
new file mode 100644
index 0000000..6392433
--- /dev/null
+++ b/poppler/StructElement.cc
@@ -0,0 +1,1382 @@
+//========================================================================
+//
+// StructElement.cc
+//
+// This file is licensed under the GPLv2 or later
+//
+// Copyright 2013 Igalia S.L.
+//
+//========================================================================
+
+#ifdef USE_GCC_PRAGMAS
+#pragma interface
+#endif
+
+#include "StructElement.h"
+#include "StructTreeRoot.h"
+#include "GlobalParams.h"
+#include "UnicodeMap.h"
+#include "PDFDoc.h"
+#include "Dict.h"
+
+#include <assert.h>
+
+class GfxState;
+
+
+static GBool isPlacementName(Object *value)
+{
+  return value->isName("Block")
+      || value->isName("Inline")
+      || value->isName("Before")
+      || value->isName("Start")
+      || value->isName("End");
+}
+
+static GBool isWritingModeName(Object *value)
+{
+  return value->isName("LrTb")
+      || value->isName("RlTb")
+      || value->isName("TbRl");
+}
+
+static GBool isBorderStyleName(Object *value)
+{
+  return value->isName("None")
+      || value->isName("Hidden")
+      || value->isName("Dotted")
+      || value->isName("Dashed")
+      || value->isName("Solid")
+      || value->isName("Double")
+      || value->isName("Groove")
+      || value->isName("Ridge")
+      || value->isName("Inset")
+      || value->isName("Outset");
+}
+
+static GBool isTextAlignName(Object *value)
+{
+  return value->isName("Start")
+      || value->isName("End")
+      || value->isName("Center")
+      || value->isName("Justify");
+}
+
+static GBool isBlockAlignName(Object *value)
+{
+  return value->isName("Before")
+      || value->isName("Middle")
+      || value->isName("After")
+      || value->isName("Justify");
+}
+
+static GBool isInlineAlignName(Object *value)
+{
+  return value->isName("Start")
+      || value->isName("End")
+      || value->isName("Center");
+}
+
+static GBool isNumber(Object *value)
+{
+  return value->isNum();
+}
+
+static GBool isLineHeight(Object *value)
+{
+  return value->isName("Normal")
+      || value->isName("Auto")
+      || isNumber(value);
+}
+
+static GBool isTextDecorationName(Object *value)
+{
+  return value->isName("None")
+      || value->isName("Underline")
+      || value->isName("Overline")
+      || value->isName("LineThrough");
+}
+
+static GBool isRubyAlignName(Object *value)
+{
+  return value->isName("Start")
+      || value->isName("End")
+      || value->isName("Center")
+      || value->isName("Justify")
+      || value->isName("Distribute");
+}
+
+static GBool isRubyPositionName(Object *value)
+{
+  return value->isName("Before")
+      || value->isName("After")
+      || value->isName("Warichu")
+      || value->isName("Inline");
+}
+
+static GBool isGlyphOrientationName(Object *value)
+{
+  return value->isName("Auto")
+      || value->isName("90")
+      || value->isName("180")
+      || value->isName("270")
+      || value->isName("360")
+      || value->isName("-90")
+      || value->isName("-180");
+}
+
+static GBool isListNumberingName(Object *value)
+{
+  return value->isName("None")
+      || value->isName("Disc")
+      || value->isName("Circle")
+      || value->isName("Square")
+      || value->isName("Decimal")
+      || value->isName("UpperRoman")
+      || value->isName("LowerRoman")
+      || value->isName("UpperAlpha")
+      || value->isName("LowerAlpha");
+}
+
+static GBool isFieldRoleName(Object *value)
+{
+  return value->isName("rb")
+      || value->isName("cb")
+      || value->isName("pb")
+      || value->isName("tv");
+}
+
+static GBool isFieldCheckedName(Object *value)
+{
+  return value->isName("on")
+      || value->isName("off")
+      || value->isName("neutral");
+}
+
+static GBool isTableScopeName(Object *value)
+{
+  return value->isName("Row")
+      || value->isName("Column")
+      || value->isName("Both");
+}
+
+static GBool isRGBColor(Object *value)
+{
+  if (!(value->isArray() && value->arrayGetLength() == 3))
+    return gFalse;
+
+  GBool okay = gTrue;
+  for (int i = 0; i < 3; i++) {
+    Object obj;
+    if (!value->arrayGet(i, &obj)->isNum()) {
+      okay = gFalse;
+      obj.free();
+      break;
+    }
+    if (obj.getNum() < 0.0 || obj.getNum() > 1.0) {
+      okay = gFalse;
+      obj.free();
+      break;
+    }
+    obj.free();
+  }
+
+  return okay;
+}
+
+static GBool isNatural(Object *value)
+{
+  return (value->isInt()   && value->getInt()   > 0)
+      || (value->isInt64() && value->getInt64() > 0);
+}
+
+static GBool isPositive(Object *value)
+{
+  return value->isNum() && value->getNum() >= 0.0;
+}
+
+static GBool isNumberOrAuto(Object *value)
+{
+  return isNumber(value) || value->isName("Auto");
+}
+
+static GBool isTextString(Object *value)
+{
+  // XXX: Shall isName() also be checked?
+  return value->isString();
+}
+
+
+#define ARRAY_CHECKER(name, checkItem, length, allowSingle, allowNulls) \
+    static GBool name(Object *value) {                                  \
+      if (!value->isArray())                                            \
+        return allowSingle ? checkItem(value) : gFalse;                 \
+                                                                        \
+      if (length && value->arrayGetLength() != length)                  \
+        return gFalse;                                                  \
+                                                                        \
+      GBool okay = gTrue;                                               \
+      for (int i = 0; i < value->arrayGetLength(); i++) {               \
+        Object obj;                                                     \
+        value->arrayGet(i, &obj);                                       \
+        if ((!allowNulls && obj.isNull()) || !checkItem(&obj)) {        \
+          okay = gFalse;                                                \
+          obj.free();                                                   \
+          break;                                                        \
+        }                                                               \
+        obj.free();                                                     \
+      }                                                                 \
+      return okay;                                                      \
+    }
+
+ARRAY_CHECKER(isRGBColorOrOptionalArray4, isRGBColor,        4, gTrue,  gTrue );
+ARRAY_CHECKER(isPositiveOrOptionalArray4, isPositive,        4, gTrue,  gTrue );
+ARRAY_CHECKER(isPositiveOrArray4,         isPositive,        4, gTrue,  gFalse);
+ARRAY_CHECKER(isBorderStyle,              isBorderStyleName, 4, gTrue,  gTrue );
+ARRAY_CHECKER(isNumberArray4,             isNumber,          4, gFalse, gFalse);
+ARRAY_CHECKER(isNumberOrArrayN,           isNumber,          0, gTrue,  gFalse);
+ARRAY_CHECKER(isTableHeaders,             isTextString,      0, gFalse, gFalse);
+
+
+// Type of functions used to do type-checking on attribute values
+typedef GBool (*AttributeCheckFunc)(Object*);
+
+// Maps attributes to their names and whether the attribute can be inherited.
+struct AttributeMapEntry {
+  Attribute::Type    type;
+  const char        *name;
+  const Object      *defval;
+  GBool              inherit;
+  AttributeCheckFunc check;
+};
+
+struct AttributeDefaults {
+  Object Inline;
+  Object LrTb;
+  Object Normal;
+  Object Distribute;
+  Object off;
+  Object Zero;
+  Object Auto;
+  Object Start;
+  Object None;
+  Object Before;
+  Object Nat1;
+
+  AttributeDefaults() {
+    Inline.initName("Inline");
+    LrTb.initName("LrTb");
+    Normal.initName("Normal");
+    Distribute.initName("Distribute");
+    off.initName("off");
+
+    Zero.initReal(0.0);
+    Auto.initName("Auto");
+    Start.initName("Start");
+    None.initName("None");
+    Before.initName("Before");
+    Nat1.initInt(1);
+  }
+};
+
+static const AttributeDefaults attributeDefaults;
+
+
+#define ATTR_LIST_END \
+  { Attribute::Unknown, NULL, NULL, gFalse, NULL }
+
+#define ATTR_WITH_DEFAULT(name, inherit, check, defval) \
+  { Attribute::name,           \
+    #name,                     \
+    &attributeDefaults.defval, \
+    inherit,                   \
+    check }
+
+#define ATTR(name, inherit, check) \
+  { Attribute::name,           \
+    #name,                     \
+    NULL,                      \
+    inherit,                   \
+    check }
+
+static const AttributeMapEntry attributeMapCommonShared[] =
+{
+  ATTR_WITH_DEFAULT(Placement,       gFalse, isPlacementName, Inline),
+  ATTR_WITH_DEFAULT(WritingMode,     gTrue,  isWritingModeName, LrTb),
+  ATTR             (BackgroundColor, gFalse, isRGBColor),
+  ATTR             (BorderColor,     gTrue,  isRGBColorOrOptionalArray4),
+  ATTR_WITH_DEFAULT(BorderStyle,     gFalse, isBorderStyle, None),
+  ATTR             (BorderThickness, gTrue,  isPositiveOrOptionalArray4),
+  ATTR_WITH_DEFAULT(Padding,         gFalse, isPositiveOrArray4, Zero),
+  ATTR             (Color,           gTrue,  isRGBColor),
+  ATTR_LIST_END
+};
+
+static const AttributeMapEntry attributeMapCommonBlock[] =
+{
+  ATTR_WITH_DEFAULT(SpaceBefore, gFalse, isPositive, Zero),
+  ATTR_WITH_DEFAULT(SpaceAfter,  gFalse, isPositive, Zero),
+  ATTR_WITH_DEFAULT(StartIndent, gTrue,  isNumber,   Zero),
+  ATTR_WITH_DEFAULT(EndIndent,   gTrue,  isNumber,   Zero),
+  ATTR_WITH_DEFAULT(TextIndent,  gTrue,  isNumber,   Zero),
+  ATTR_WITH_DEFAULT(TextAlign,   gTrue,  isTextAlignName, Start),
+  ATTR             (BBox,        gFalse, isNumberArray4),
+  ATTR_WITH_DEFAULT(Width,       gFalse, isNumberOrAuto, Auto),
+  ATTR_WITH_DEFAULT(Height,      gFalse, isNumberOrAuto, Auto),
+  ATTR_WITH_DEFAULT(BlockAlign,  gTrue,  isBlockAlignName, Before),
+  ATTR_WITH_DEFAULT(InlineAlign, gTrue,  isInlineAlignName, Start),
+  ATTR_LIST_END
+};
+
+static const AttributeMapEntry attributeMapCommonInline[] =
+{
+  ATTR_WITH_DEFAULT(BaselineShift,            gFalse, isNumber, Zero),
+  ATTR_WITH_DEFAULT(LineHeight,               gTrue,  isLineHeight, Normal),
+  ATTR             (TextDecorationColor,      gTrue,  isRGBColor),
+  ATTR             (TextDecorationThickness,  gTrue,  isPositive),
+  ATTR_WITH_DEFAULT(TextDecorationType,       gFalse, isTextDecorationName, None),
+  ATTR_WITH_DEFAULT(GlyphOrientationVertical, gTrue,  isGlyphOrientationName, Auto),
+  ATTR_LIST_END
+};
+
+static const AttributeMapEntry attributeMapCommonRubyText[] =
+{
+  ATTR_WITH_DEFAULT(RubyPosition, gTrue, isRubyPositionName, Before),
+  ATTR_WITH_DEFAULT(RubyAlign,    gTrue, isRubyAlignName, Distribute),
+  ATTR_LIST_END
+};
+
+static const AttributeMapEntry attributeMapCommonColumns[] =
+{
+  ATTR_WITH_DEFAULT(ColumnCount,  gFalse, isNatural, Nat1),
+  ATTR             (ColumnGap,    gFalse, isNumberOrArrayN),
+  ATTR             (ColumnWidths, gFalse, isNumberOrArrayN),
+  ATTR_LIST_END
+};
+
+static const AttributeMapEntry attributeMapCommonList[] = {
+  ATTR_WITH_DEFAULT(ListNumbering, gTrue, isListNumberingName, None),
+  ATTR_LIST_END
+};
+
+static const AttributeMapEntry attributeMapCommonPrintField[] =
+{
+  ATTR             (Role,    gFalse, isFieldRoleName),
+  ATTR_WITH_DEFAULT(checked, gFalse, isFieldCheckedName, off),
+  ATTR             (Desc,    gFalse, isTextString),
+  ATTR_LIST_END
+};
+
+static const AttributeMapEntry attributeMapCommonTable[] =
+{
+  ATTR(Headers, gFalse, isTableHeaders),
+  ATTR(Scope,   gFalse, isTableScopeName),
+  ATTR(Summary, gFalse, isTextString),
+  ATTR_LIST_END
+};
+
+static const AttributeMapEntry attributeMapCommonTableCell[] =
+{
+  ATTR_WITH_DEFAULT(RowSpan,      gFalse, isNatural, Nat1),
+  ATTR_WITH_DEFAULT(ColSpan,      gFalse, isNatural, Nat1),
+  ATTR_WITH_DEFAULT(TBorderStyle, gTrue,  isBorderStyle, None),
+  ATTR_WITH_DEFAULT(TPadding,     gTrue,  isPositiveOrArray4, Zero),
+  ATTR_LIST_END
+};
+
+#undef ATTR_WITH_DEFAULT
+#undef ATTR
+
+
+static const AttributeMapEntry *attributeMapAll[] = {
+  attributeMapCommonShared,
+  attributeMapCommonBlock,
+  attributeMapCommonInline,
+  attributeMapCommonRubyText,
+  attributeMapCommonColumns,
+  attributeMapCommonList,
+  attributeMapCommonPrintField,
+  attributeMapCommonTable,
+  attributeMapCommonTableCell,
+  NULL,
+};
+
+static const AttributeMapEntry *attributeMapShared[] = {
+  attributeMapCommonShared,
+  NULL,
+};
+
+static const AttributeMapEntry *attributeMapBlock[] = {
+  attributeMapCommonShared,
+  attributeMapCommonBlock,
+  NULL,
+};
+
+static const AttributeMapEntry *attributeMapInline[] = {
+  attributeMapCommonShared,
+  attributeMapCommonInline,
+  NULL,
+};
+
+static const AttributeMapEntry *attributeMapTableCell[] = {
+  attributeMapCommonShared,
+  attributeMapCommonBlock,
+  attributeMapCommonTable,
+  attributeMapCommonTableCell,
+  NULL,
+};
+
+static const AttributeMapEntry *attributeMapRubyText[] = {
+  attributeMapCommonShared,
+  attributeMapCommonInline,
+  attributeMapCommonRubyText,
+  NULL,
+};
+
+static const AttributeMapEntry *attributeMapColumns[] = {
+  attributeMapCommonShared,
+  attributeMapCommonInline,
+  attributeMapCommonColumns,
+  NULL,
+};
+
+static const AttributeMapEntry *attributeMapList[] = {
+  attributeMapCommonShared,
+  attributeMapCommonList,
+  NULL,
+};
+
+static const AttributeMapEntry *attributeMapTable[] = {
+  attributeMapCommonShared,
+  attributeMapCommonBlock,
+  attributeMapCommonTable,
+  NULL,
+};
+
+static const AttributeMapEntry *attributeMapIllustration[] = {
+  // XXX: Illustrations may have some attributes from the "shared", "inline",
+  //      the "block" sets. This is a loose specification; making it better
+  //      means duplicating entries from the sets. This seems good enough...
+  attributeMapCommonShared,
+  attributeMapCommonBlock,
+  attributeMapCommonInline,
+  NULL,
+};
+
+// Table mapping owners of attributes to their names.
+static const struct OwnerMapEntry {
+  Attribute::Owner owner;
+  const char      *name;
+} ownerMap[] = {
+  // XXX: Those are sorted in the owner priority resolution order. If the
+  //      same attribute is defined with two owners, the order in the table
+  //      can be used to know which one has more priority.
+  { Attribute::XML_1_00,       "XML-1.00"       },
+  { Attribute::HTML_3_20,      "HTML-3.20"      },
+  { Attribute::HTML_4_01,      "HTML-4.01"      },
+  { Attribute::OEB_1_00,       "OEB-1.00"       },
+  { Attribute::RTF_1_05,       "RTF-1.05"       },
+  { Attribute::CSS_1_00,       "CSS-1.00"       },
+  { Attribute::CSS_2_00,       "CSS-2.00"       },
+  { Attribute::Layout,         "Layout"         },
+  { Attribute::PrintField,     "PrintField"     },
+  { Attribute::Table,          "Table"          },
+  { Attribute::List,           "List"           },
+  { Attribute::UserProperties, "UserProperties" },
+};
+
+
+static GBool ownerHasMorePriority(Attribute::Owner a, Attribute::Owner b)
+{
+  unsigned aIndex, bIndex;
+
+  for (unsigned i = aIndex = bIndex = 0; i < sizeof(ownerMap) / sizeof(ownerMap[0]); i++) {
+    if (ownerMap[i].owner == a)
+      aIndex = i;
+    if (ownerMap[i].owner == b)
+      bIndex = i;
+  }
+
+  return aIndex < bIndex;
+}
+
+
+// Maps element types to their names and also serves as lookup table
+// for additional element type attributes.
+
+enum ElementType {
+  elementTypeUndefined,
+  elementTypeInline,
+  elementTypeBlock,
+};
+
+static const struct TypeMapEntry {
+  StructElement::Type       type;
+  const char               *name;
+  ElementType               elementType;
+  const AttributeMapEntry **attributes;
+} typeMap[] = {
+  { StructElement::Document,   "Document",   elementTypeInline,    attributeMapShared       },
+  { StructElement::Part,       "Part",       elementTypeInline,    attributeMapShared       },
+  { StructElement::Art,        "Art",        elementTypeInline,    attributeMapColumns      },
+  { StructElement::Sect,       "Sect",       elementTypeInline,    attributeMapColumns      },
+  { StructElement::Div,        "Div",        elementTypeInline,    attributeMapColumns      },
+  { StructElement::BlockQuote, "BlockQuote", elementTypeInline,    attributeMapInline       },
+  { StructElement::Caption,    "Caption",    elementTypeInline,    attributeMapInline       },
+  { StructElement::NonStruct,  "NonStruct",  elementTypeInline,    attributeMapInline       },
+  { StructElement::Index,      "Index",      elementTypeInline,    attributeMapInline       },
+  { StructElement::Private,    "Private",    elementTypeInline,    attributeMapInline       },
+  { StructElement::Span,       "Span",       elementTypeInline,    attributeMapInline       },
+  { StructElement::Quote,      "Quote",      elementTypeInline,    attributeMapInline       },
+  { StructElement::Note,       "Note",       elementTypeInline,    attributeMapInline       },
+  { StructElement::Reference,  "Reference",  elementTypeInline,    attributeMapInline       },
+  { StructElement::BibEntry,   "BibEntry",   elementTypeInline,    attributeMapInline       },
+  { StructElement::Code,       "Code",       elementTypeInline,    attributeMapInline       },
+  { StructElement::Link,       "Link",       elementTypeInline,    attributeMapInline       },
+  { StructElement::Annot,      "Annot",      elementTypeInline,    attributeMapInline       },
+  { StructElement::Ruby,       "Ruby",       elementTypeInline,    attributeMapRubyText     },
+  { StructElement::RB,         "RB",         elementTypeUndefined, attributeMapRubyText     },
+  { StructElement::RT,         "RT",         elementTypeUndefined, attributeMapRubyText     },
+  { StructElement::RP,         "RP",         elementTypeUndefined, attributeMapShared       },
+  { StructElement::Warichu,    "Warichu",    elementTypeInline,    attributeMapRubyText     },
+  { StructElement::WT,         "WT",         elementTypeUndefined, attributeMapShared       },
+  { StructElement::WP,         "WP",         elementTypeUndefined, attributeMapShared       },
+  { StructElement::P,          "P",          elementTypeBlock,     attributeMapBlock        },
+  { StructElement::H,          "H",          elementTypeBlock,     attributeMapBlock        },
+  { StructElement::H1,         "H1",         elementTypeBlock,     attributeMapBlock        },
+  { StructElement::H2,         "H2",         elementTypeBlock,     attributeMapBlock        },
+  { StructElement::H3,         "H3",         elementTypeBlock,     attributeMapBlock        },
+  { StructElement::H4,         "H4",         elementTypeBlock,     attributeMapBlock        },
+  { StructElement::H5,         "H5",         elementTypeBlock,     attributeMapBlock        },
+  { StructElement::H6,         "H6",         elementTypeBlock,     attributeMapBlock        },
+  { StructElement::L,          "L",          elementTypeBlock,     attributeMapList         },
+  { StructElement::LI,         "LI",         elementTypeBlock,     attributeMapBlock        },
+  { StructElement::Lbl,        "Lbl",        elementTypeBlock,     attributeMapBlock        },
+  { StructElement::LBody,      "LBody",      elementTypeUndefined, attributeMapBlock        },
+  { StructElement::Table,      "Table",      elementTypeBlock,     attributeMapTable        },
+  { StructElement::TR,         "TR",         elementTypeUndefined, attributeMapShared       },
+  { StructElement::TH,         "TH",         elementTypeUndefined, attributeMapTableCell    },
+  { StructElement::TD,         "TD",         elementTypeUndefined, attributeMapTableCell    },
+  { StructElement::THead,      "THead",      elementTypeUndefined, attributeMapShared       },
+  { StructElement::TFoot,      "TFoot",      elementTypeUndefined, attributeMapShared       },
+  { StructElement::TBody,      "TBody",      elementTypeUndefined, attributeMapShared       },
+  { StructElement::Figure,     "Figure",     elementTypeUndefined, attributeMapIllustration },
+  { StructElement::Formula,    "Formula",    elementTypeUndefined, attributeMapIllustration },
+  { StructElement::Form,       "Form",       elementTypeUndefined, attributeMapIllustration },
+  { StructElement::TOC,        "TOC",        elementTypeUndefined, attributeMapShared       },
+  { StructElement::TOCI,       "TOCI",       elementTypeUndefined, attributeMapShared       },
+};
+
+
+//------------------------------------------------------------------------
+// Helpers for the attribute and structure type tables
+//------------------------------------------------------------------------
+
+static inline const AttributeMapEntry *
+getAttributeMapEntry(const AttributeMapEntry **entryList, Attribute::Type type)
+{
+  assert(entryList);
+  while (*entryList) {
+    const AttributeMapEntry *entry = *entryList;
+    while (entry->type != Attribute::Unknown) {
+      assert(entry->name);
+      if (type == entry->type)
+        return entry;
+      entry++;
+    }
+    entryList++;
+  }
+  return NULL;
+}
+
+static inline const AttributeMapEntry *
+getAttributeMapEntry(const AttributeMapEntry **entryList, const char *name)
+{
+  assert(entryList);
+  while (*entryList) {
+    const AttributeMapEntry *entry = *entryList;
+    while (entry->type != Attribute::Unknown) {
+      assert(entry->name);
+      if (strcmp(name, entry->name) == 0)
+        return entry;
+      entry++;
+    }
+    entryList++;
+  }
+  return NULL;
+}
+
+static inline const OwnerMapEntry *getOwnerMapEntry(Attribute::Owner owner)
+{
+  for (unsigned i = 0; i < sizeof(ownerMap) / sizeof(ownerMap[0]); i++) {
+    if (owner == ownerMap[i].owner)
+      return &ownerMap[i];
+  }
+  return NULL;
+}
+
+static inline const OwnerMapEntry *getOwnerMapEntry(const char *name)
+{
+  for (unsigned i = 0; i < sizeof(ownerMap) / sizeof(ownerMap[0]); i++) {
+    if (strcmp(name, ownerMap[i].name) == 0)
+      return &ownerMap[i];
+  }
+  return NULL;
+}
+
+static const char *ownerToName(Attribute::Owner owner)
+{
+  const OwnerMapEntry *entry = getOwnerMapEntry(owner);
+  return entry ? entry->name : "UnknownOwner";
+}
+
+static Attribute::Owner nameToOwner(const char *name)
+{
+  const OwnerMapEntry *entry = getOwnerMapEntry(name);
+  return entry ? entry->owner : Attribute::UnknownOwner;
+}
+
+static inline const TypeMapEntry *getTypeMapEntry(StructElement::Type type)
+{
+  for (unsigned i = 0; i < sizeof(typeMap) / sizeof(typeMap[0]); i++) {
+    if (type == typeMap[i].type)
+      return &typeMap[i];
+  }
+  return NULL;
+}
+
+static inline const TypeMapEntry *getTypeMapEntry(const char *name)
+{
+  for (unsigned i = 0; i < sizeof(typeMap) / sizeof(typeMap[0]); i++) {
+    if (strcmp(name, typeMap[i].name) == 0)
+      return &typeMap[i];
+  }
+  return NULL;
+}
+
+static const char *typeToName(StructElement::Type type)
+{
+  if (type == StructElement::MCID)
+    return "MarkedContent";
+  if (type == StructElement::OBJR)
+    return "ObjectReference";
+
+  const TypeMapEntry *entry = getTypeMapEntry(type);
+  return entry ? entry->name : "Unknown";
+}
+
+static StructElement::Type nameToType(const char *name)
+{
+  const TypeMapEntry *entry = getTypeMapEntry(name);
+  return entry ? entry->type : StructElement::Unknown;
+}
+
+
+//------------------------------------------------------------------------
+// Attribute
+//------------------------------------------------------------------------
+
+Attribute::Attribute(const char *nameA, Object *valueA):
+  type(UserProperty),
+  owner(UserProperties),
+  revision(0),
+  name(nameA),
+  value(),
+  hidden(gFalse),
+  formatted(NULL)
+{
+  assert(valueA);
+  valueA->copy(&value);
+}
+
+Attribute::Attribute(Type type, Object *valueA):
+  type(type),
+  owner(UserProperties), // TODO: Determine corresponding owner from Type
+  revision(0),
+  name(),
+  value(),
+  hidden(gFalse),
+  formatted(NULL)
+{
+  assert(valueA);
+
+  valueA->copy(&value);
+
+  if (!checkType())
+    type = Unknown;
+}
+
+Attribute::~Attribute()
+{
+  delete formatted;
+  value.free();
+}
+
+const char *Attribute::getTypeName() const
+{
+  if (type == UserProperty)
+    return name.getCString();
+
+  const AttributeMapEntry *entry = getAttributeMapEntry(attributeMapAll, type);
+  if (entry)
+    return entry->name;
+
+  return "Unknown";
+}
+
+const char *Attribute::getOwnerName() const
+{
+  return ownerToName(owner);
+}
+
+Object *Attribute::getDefaultValue(Attribute::Type type)
+{
+  const AttributeMapEntry *entry = getAttributeMapEntry(attributeMapAll, type);
+  return entry ? const_cast<Object*>(entry->defval) : NULL;
+}
+
+void Attribute::setFormattedValue(const char *formattedA)
+{
+  if (formattedA) {
+    if (formatted)
+      formatted->Set(formattedA);
+    else
+      formatted = new GooString(formattedA);
+  } else {
+    delete formatted;
+    formatted = NULL;
+  }
+}
+
+GBool Attribute::checkType(StructElement *element)
+{
+  // If an element is passed, tighther type-checking can be done.
+  if (!element)
+    return gTrue;
+
+  const TypeMapEntry *elementTypeEntry = getTypeMapEntry(element->getType());
+  if (elementTypeEntry && elementTypeEntry->attributes) {
+    const AttributeMapEntry *entry = getAttributeMapEntry(elementTypeEntry->attributes, type);
+    if (entry) {
+      if (entry->check && !((*entry->check)(&value))) {
+        return gFalse;
+      }
+    } else {
+      // No entry: the attribute is not valid for the containing element.
+      return gFalse;
+    }
+  }
+
+  return gTrue;
+}
+
+Attribute::Type Attribute::getTypeForName(const char *name, StructElement *element)
+{
+  const AttributeMapEntry **attributes = attributeMapAll;
+  if (element) {
+    const TypeMapEntry *elementTypeEntry = getTypeMapEntry(element->getType());
+    if (elementTypeEntry && elementTypeEntry->attributes) {
+      attributes = elementTypeEntry->attributes;
+    }
+  }
+
+  const AttributeMapEntry *entry = getAttributeMapEntry(attributes, name);
+  return entry ? entry->type : Unknown;
+}
+
+Attribute *Attribute::parseUserProperty(Dict *property)
+{
+  Object obj, value;
+  const char *name = NULL;
+
+  if (property->lookup("N", &obj)->isString())
+    name = obj.getString()->getCString();
+  else if (obj.isName())
+    name = obj.getName();
+  else {
+    error(errSyntaxError, -1, "N object is wrong type ({0:s})", obj.getTypeName());
+    obj.free();
+    return NULL;
+  }
+
+  if (property->lookup("V", &value)->isNull()) {
+    error(errSyntaxError, -1, "V object is wrong type ({0:s})", value.getTypeName());
+    value.free();
+    obj.free();
+    return NULL;
+  }
+
+  Attribute *attribute = new Attribute(name, &value);
+  value.free();
+  obj.free();
+
+  if (property->lookup("F", &obj)->isString()) {
+    attribute->setFormattedValue(obj.getString()->getCString());
+  } else if (!obj.isNull()) {
+    error(errSyntaxWarning, -1, "F object is wrong type ({0:s})", obj.getTypeName());
+  }
+  obj.free();
+
+  if (property->lookup("H", &obj)->isBool()) {
+    attribute->setHidden(obj.getBool());
+  } else if (!obj.isNull()) {
+    error(errSyntaxWarning, -1, "H object is wrong type ({0:s})", obj.getTypeName());
+  }
+  obj.free();
+
+  return attribute;
+}
+
+
+//------------------------------------------------------------------------
+// StructElement
+//------------------------------------------------------------------------
+
+StructElement::StructData::StructData():
+  altText(0),
+  actualText(0),
+  id(0),
+  title(0),
+  expandedAbbr(0),
+  language(0),
+  revision(0)
+{
+}
+
+StructElement::StructData::~StructData()
+{
+  delete altText;
+  delete actualText;
+  delete id;
+  delete title;
+  delete language;
+  parentRef.free();
+  for (ElemPtrArray::iterator i = elements.begin(); i != elements.end(); ++i) delete *i;
+  for (AttrPtrArray::iterator i = attributes.begin(); i != attributes.end(); ++i) delete *i;
+}
+
+
+StructElement::StructElement(Dict *element,
+                             StructTreeRoot *treeRootA,
+                             StructElement *parentA,
+                             std::set<int> &seen):
+  type(Unknown),
+  treeRoot(treeRootA),
+  parent(parentA),
+  s(new StructData())
+{
+  assert(treeRoot);
+  assert(element);
+
+  parse(element);
+  parseChildren(element, seen);
+}
+
+StructElement::StructElement(int mcid, StructTreeRoot *treeRootA, StructElement *parentA):
+  type(MCID),
+  treeRoot(treeRootA),
+  parent(parentA),
+  c(new ContentData(mcid))
+{
+  assert(treeRoot);
+  assert(parent);
+}
+
+StructElement::StructElement(const Ref& ref, StructTreeRoot *treeRootA, StructElement *parentA):
+  type(OBJR),
+  treeRoot(treeRootA),
+  parent(parentA),
+  c(new ContentData(ref))
+{
+  assert(treeRoot);
+  assert(parent);
+}
+
+StructElement::~StructElement()
+{
+  if (isContent())
+    delete c;
+  else
+    delete s;
+  pageRef.free();
+}
+
+GBool StructElement::isBlock() const
+{
+  const TypeMapEntry *entry = getTypeMapEntry(type);
+  return entry ? (entry->elementType == elementTypeBlock) : gFalse;
+}
+
+GBool StructElement::isInline() const
+{
+  const TypeMapEntry *entry = getTypeMapEntry(type);
+  return entry ? (entry->elementType == elementTypeInline) : gFalse;
+}
+
+GBool StructElement::hasPageRef() const
+{
+  return pageRef.isRef() || (parent && parent->hasPageRef());
+}
+
+bool StructElement::getPageRef(Ref& ref) const
+{
+  if (pageRef.isRef()) {
+    ref = pageRef.getRef();
+    return gTrue;
+  }
+
+  if (parent)
+    return parent->getPageRef(ref);
+
+  return gFalse;
+}
+
+const char *StructElement::getTypeName() const
+{
+  return typeToName(type);
+}
+
+const Attribute *StructElement::findAttribute(Attribute::Type attributeType, GBool inherit,
+                                              Attribute::Owner attributeOwner) const
+{
+  if (isContent())
+    return parent->findAttribute(attributeType, inherit, attributeOwner);
+
+  if (attributeType == Attribute::Unknown || attributeType == Attribute::UserProperty)
+    return NULL;
+
+  const Attribute *result = NULL;
+
+  if (attributeOwner == Attribute::UnknownOwner) {
+    // Search for the attribute, no matter who the owner is
+    for (unsigned i = 0; i < getNumAttributes(); i++) {
+      const Attribute *attr = getAttribute(i);
+      if (attributeType == attr->getType()) {
+        if (!result || ownerHasMorePriority(attr->getOwner(), result->getOwner()))
+          result = attr;
+      }
+    }
+  } else {
+    // Search for the attribute, with a specific owner
+    for (unsigned i = 0; i < getNumAttributes(); i++) {
+      const Attribute *attr = getAttribute(i);
+      if (attributeType == attr->getType() && attributeOwner == attr->getOwner()) {
+        result = attr;
+        break;
+      }
+    }
+  }
+
+  if (result)
+    return result;
+
+  if (inherit && parent) {
+    const AttributeMapEntry *entry = getAttributeMapEntry(attributeMapAll, attributeType);
+    assert(entry);
+    // TODO: Take into account special inheritance cases, for example:
+    //       inline elements which have been changed to be block using
+    //       "/Placement/Block" have slightly different rules.
+    if (entry->inherit)
+      return parent->findAttribute(attributeType, inherit, attributeOwner);
+  }
+
+  return NULL;
+}
+
+GooString* StructElement::appendSubTreeText(GooString *string, GBool recursive) const
+{
+  if (isContent() && !isObjectRef()) {
+    MarkedContentOutputDev mcdev(getMCID());
+    const TextSpanArray& spans(getTextSpansInternal(mcdev));
+
+    if (!string)
+      string = new GooString();
+
+    for (TextSpanArray::const_iterator i = spans.begin(); i != spans.end(); ++i)
+      string->append(i->getText());
+
+    return string;
+  }
+
+  if (!recursive)
+    return NULL;
+
+  // Do a depth-first traversal, to get elements in logical order
+  if (!string)
+    string = new GooString();
+
+  for (unsigned i = 0; i < getNumElements(); i++)
+    getElement(i)->appendSubTreeText(string, recursive);
+
+  return string;
+}
+
+const TextSpanArray& StructElement::getTextSpansInternal(MarkedContentOutputDev& mcdev) const
+{
+  assert(isContent());
+
+  int startPage = 0, endPage = 0;
+
+  Ref ref;
+  if (getPageRef(ref)) {
+    startPage = endPage = treeRoot->getDoc()->findPage(ref.num, ref.gen);
+  }
+
+  if (!(startPage && endPage)) {
+    startPage = 1;
+    endPage = treeRoot->getDoc()->getNumPages();
+  }
+
+  treeRoot->getDoc()->displayPages(&mcdev, startPage, endPage, 72.0, 72.0, 0, gTrue, gFalse, gFalse);
+  return mcdev.getTextSpans();
+}
+
+static StructElement::Type roleMapResolve(Dict *roleMap, const char *name, const char *curName, Object *resolved)
+{
+  // Circular reference
+  if (curName && !strcmp(name, curName))
+    return StructElement::Unknown;
+
+  if (roleMap->lookup(curName ? curName : name, resolved)->isName()) {
+    StructElement::Type type = nameToType(resolved->getName());
+    return type == StructElement::Unknown
+      ? roleMapResolve(roleMap, name, resolved->getName(), resolved)
+      : type;
+  }
+
+  if (!resolved->isNull())
+    error(errSyntaxWarning, -1, "RoleMap entry is wrong type ({0:s})", resolved->getTypeName());
+  return StructElement::Unknown;
+}
+
+void StructElement::parse(Dict *element)
+{
+  Object obj;
+
+  // Type is optional, but if present must be StructElem
+  if (!element->lookup("Type", &obj)->isNull() && !obj.isName("StructElem")) {
+    error(errSyntaxError, -1, "Type of StructElem object is wrong");
+    obj.free();
+    return;
+  }
+  obj.free();
+
+  // Parent object reference (required).
+  if (!element->lookupNF("P", &s->parentRef)->isRef()) {
+    error(errSyntaxError, -1, "P object is wrong type ({0:s})", obj.getTypeName());
+    return;
+  }
+
+  // Check whether the S-type is valid for the top level
+  // element and create a node of the appropriate type.
+  if (!element->lookup("S", &obj)->isName()) {
+    error(errSyntaxError, -1, "S object is wrong type ({0:s})", obj.getTypeName());
+    obj.free();
+    return;
+  }
+
+  // Type name may not be standard, resolve through RoleMap first.
+  if (treeRoot->getRoleMap()) {
+    Object resolvedName;
+    type = roleMapResolve(treeRoot->getRoleMap(), obj.getName(), NULL, &resolvedName);
+  }
+
+  // Resolving through RoleMap may leave type as Unknown, e.g. for types
+  // which are not present in it, yet they are standard element types.
+  if (type == Unknown)
+    type = nameToType(obj.getName());
+
+  // At this point either the type name must have been resolved.
+  if (type == Unknown) {
+    error(errSyntaxError, -1, "StructElem object is wrong type ({0:s})", obj.getName());
+    obj.free();
+    return;
+  }
+  obj.free();
+
+  // Object ID (optional), to be looked at the IDTree in the tree root.
+  if (element->lookup("ID", &obj)->isString()) {
+    s->id = obj.takeString();
+  }
+  obj.free();
+
+  // Page reference (optional) in which at least one of the child items
+  // is to be rendered in. Note: each element stores only the /Pg value
+  // contained by it, and StructElement::getPageRef() may look in parent
+  // elements to find the page where an element belongs.
+  element->lookupNF("Pg", &pageRef);
+
+  // Revision number (optional).
+  if (element->lookup("R", &obj)->isInt()) {
+    s->revision = obj.getInt();
+  }
+  obj.free();
+
+  // Element title (optional).
+  if (element->lookup("T", &obj)->isString()) {
+    s->title = obj.takeString();
+  }
+  obj.free();
+
+  // Language (optional).
+  if (element->lookup("Lang", &obj)->isString()) {
+    s->language = obj.takeString();
+  }
+  obj.free();
+
+  // Alternative text (optional).
+  if (element->lookup("Alt", &obj)->isString()) {
+    s->altText = obj.takeString();
+  }
+  obj.free();
+
+  // Expanded form of an abbreviation (optional).
+  if (element->lookup("E", &obj)->isString()) {
+    s->expandedAbbr = obj.takeString();
+  }
+  obj.free();
+
+  // Actual text (optional).
+  if (element->lookup("ActualText", &obj)->isString()) {
+    s->actualText = obj.takeString();
+  }
+  obj.free();
+
+  // Attributes directly attached to the element (optional).
+  if (element->lookup("A", &obj)->isDict()) {
+    parseAttributes(obj.getDict());
+  } else if (obj.isArray()) {
+    Object iobj;
+    unsigned attrIndex = getNumAttributes();
+    for (int i = 0; i < obj.arrayGetLength(); i++) {
+      if (obj.arrayGet(i, &iobj)->isDict()) {
+        attrIndex = getNumAttributes();
+        parseAttributes(obj.getDict());
+      } else if (iobj.isInt()) {
+        const int revision = iobj.getInt();
+        // Set revision numbers for the elements previously created.
+        for (unsigned j = attrIndex; j < getNumAttributes(); j++)
+          getAttribute(j)->setRevision(revision);
+      } else {
+        error(errSyntaxWarning, -1, "A item is wrong type ({0:s})", iobj.getTypeName());
+      }
+      iobj.free();
+    }
+  } else if (!obj.isNull()) {
+    error(errSyntaxWarning, -1, "A is wrong type ({0:s})", obj.getTypeName());
+  }
+  obj.free();
+
+  // Attributes referenced indirectly through the ClassMap (optional).
+  if (treeRoot->getClassMap()) {
+    Object classes;
+    if (element->lookup("C", &classes)->isName()) {
+      Object attr;
+      if (treeRoot->getClassMap()->lookup(classes.getName(), &attr)->isDict()) {
+        parseAttributes(attr.getDict(), gTrue);
+      } else if (attr.isArray()) {
+        for (int i = 0; i < attr.arrayGetLength(); i++) {
+          Object iobj;
+          unsigned attrIndex = getNumAttributes();
+          if (attr.arrayGet(i, &iobj)->isDict()) {
+            attrIndex = getNumAttributes();
+            parseAttributes(iobj.getDict(), gTrue);
+          } else if (iobj.isInt()) {
+            // Set revision numbers for the elements previously created.
+            const int revision = iobj.getInt();
+            for (unsigned j = attrIndex; j < getNumAttributes(); j++)
+              getAttribute(j)->setRevision(revision);
+          } else {
+            error(errSyntaxWarning, -1, "C item is wrong type ({0:s})", iobj.getTypeName());
+          }
+          iobj.free();
+        }
+      } else if (!attr.isNull()) {
+        error(errSyntaxWarning, -1, "C object is wrong type ({0:s})", classes.getTypeName());
+      }
+      classes.free();
+      attr.free();
+    }
+  }
+}
+
+StructElement *StructElement::parseChild(Object *ref,
+                                         Object *childObj,
+                                         std::set<int> &seen)
+{
+  assert(childObj);
+  assert(ref);
+
+  StructElement *child = NULL;
+
+  if (childObj->isInt()) {
+    child = new StructElement(childObj->getInt(), treeRoot, this);
+  } else if (childObj->isDict("MCR")) {
+    /*
+     * TODO: The optional Stm/StwOwn attributes are not handled, so all the
+     *      page will be always scanned when calling StructElement::getText().
+     */
+    Object mcidObj;
+    Object pageRefObj;
+
+    if (!childObj->dictLookup("MCID", &mcidObj)->isInt()) {
+      error(errSyntaxError, -1, "MCID object is wrong type ({0:s})", mcidObj.getTypeName());
+      mcidObj.free();
+      return NULL;
+    }
+
+    child = new StructElement(mcidObj.getInt(), treeRoot, this);
+    mcidObj.free();
+
+    if (childObj->dictLookupNF("Pg", &pageRefObj)->isRef()) {
+      child->pageRef = pageRefObj;
+    } else {
+      pageRefObj.free();
+    }
+  } else if (childObj->isDict("OBJR")) {
+    Object refObj;
+
+    if (childObj->dictLookupNF("Obj", &refObj)->isRef()) {
+      Object pageRefObj;
+
+      child = new StructElement(refObj.getRef(), treeRoot, this);
+
+      if (childObj->dictLookupNF("Pg", &pageRefObj)->isRef()) {
+        child->pageRef = pageRefObj;
+      } else {
+        pageRefObj.free();
+      }
+    } else {
+      error(errSyntaxError, -1, "Obj object is wrong type ({0:s})", refObj.getTypeName());
+    }
+    refObj.free();
+  } else if (childObj->isDict()) {
+    if (!ref->isRef()) {
+      error(errSyntaxError, -1,
+            "Structure element dictionary is not an indirect reference ({0:s})",
+            ref->getTypeName());
+    } else if (seen.find(ref->getRefNum()) == seen.end()) {
+      seen.insert(ref->getRefNum());
+      child = new StructElement(childObj->getDict(), treeRoot, this, seen);
+    } else {
+      error(errSyntaxWarning, -1,
+            "Loop detected in structure tree, skipping subtree at object {0:d}:{1:d}",
+            ref->getRefNum(), ref->getRefGen());
+    }
+  } else {
+    error(errSyntaxWarning, -1, "K has a child of wrong type ({0:s})", childObj->getTypeName());
+  }
+
+  if (child) {
+    if (child->isOk()) {
+      appendElement(child);
+      if (ref->isRef())
+        treeRoot->parentTreeAdd(ref->getRef(), child);
+    } else {
+      delete child;
+      child = NULL;
+    }
+  }
+
+  return child;
+}
+
+void StructElement::parseChildren(Dict *element, std::set<int> &seen)
+{
+  Object kids;
+
+  if (element->lookup("K", &kids)->isArray()) {
+    for (int i = 0; i < kids.arrayGetLength(); i++) {
+      Object obj, ref;
+      parseChild(kids.arrayGetNF(i, &ref), kids.arrayGet(i, &obj), seen);
+      obj.free();
+      ref.free();
+    }
+  } else if (kids.isDict() || kids.isInt()) {
+    Object ref;
+    parseChild(element->lookupNF("K", &ref), &kids, seen);
+    ref.free();
+  }
+
+  kids.free();
+}
+
+void StructElement::parseAttributes(Dict *attributes, GBool keepExisting)
+{
+  Object owner;
+  if (attributes->lookup("O", &owner)->isName("UserProperties")) {
+    // In this case /P is an array of UserProperty dictionaries
+    Object userProperties;
+    if (attributes->lookup("P", &userProperties)->isArray()) {
+      for (int i = 0; i < userProperties.arrayGetLength(); i++) {
+        Object property;
+        if (userProperties.arrayGet(i, &property)->isDict()) {
+          Attribute *attribute = Attribute::parseUserProperty(property.getDict());
+          if (attribute && attribute->isOk()) {
+            appendAttribute(attribute);
+          } else {
+            error(errSyntaxWarning, -1, "Item in P is invalid");
+            delete attribute;
+          }
+        } else {
+          error(errSyntaxWarning, -1, "Item in P is wrong type ({0:s})", property.getTypeName());
+        }
+        property.free();
+      }
+    }
+    userProperties.free();
+  } else if (owner.isName()) {
+    // In this case /P contains standard attributes.
+    // Check first if the owner is a valid standard one.
+    Attribute::Owner ownerValue = nameToOwner(owner.getName());
+    if (ownerValue != Attribute::UnknownOwner) {
+      // Iterate over the entries of the "attributes" dictionary.
+      // The /O entry (owner) is skipped.
+      for (int i = 0; i < attributes->getLength(); i++) {
+        const char *key = attributes->getKey(i);
+        if (strcmp(key, "O") != 0) {
+          Attribute::Type type = Attribute::getTypeForName(key, this);
+
+          // Check if the attribute is already defined.
+          if (keepExisting) {
+            GBool exists = gFalse;
+            for (unsigned j = 0; j < getNumAttributes(); j++) {
+              if (getAttribute(j)->getType() == type) {
+                exists = gTrue;
+                break;
+              }
+            }
+            if (exists)
+              continue;
+          }
+
+          if (type != Attribute::Unknown) {
+            Object value;
+            GBool typeCheckOk = gTrue;
+            Attribute *attribute = new Attribute(type, attributes->getVal(i, &value));
+            value.free();
+
+            if (attribute->isOk() && (typeCheckOk = attribute->checkType(this))) {
+              appendAttribute(attribute);
+            } else {
+              // It is not needed to free "value", the Attribute instance
+              // owns the contents, so deleting "attribute" is enough.
+              if (!typeCheckOk) {
+                error(errSyntaxWarning, -1, "Attribute {0:s} value is of wrong type ({1:s})",
+                      attribute->getTypeName(), attribute->getValue()->getTypeName());
+              }
+              delete attribute;
+            }
+          } else {
+            error(errSyntaxWarning, -1, "Wrong Attribute '{0:s}' in element {1:s}", key, getTypeName());
+          }
+        }
+      }
+    } else {
+      error(errSyntaxWarning, -1, "O object is invalid value ({0:s})", owner.getName());
+    }
+  } else if (!owner.isNull()) {
+    error(errSyntaxWarning, -1, "O is wrong type ({0:s})", owner.getTypeName());
+  }
+  owner.free();
+}
diff --git a/poppler/StructElement.h b/poppler/StructElement.h
new file mode 100644
index 0000000..b9eef8a
--- /dev/null
+++ b/poppler/StructElement.h
@@ -0,0 +1,306 @@
+//========================================================================
+//
+// StructElement.h
+//
+// This file is licensed under the GPLv2 or later
+//
+// Copyright 2013 Igalia S.L.
+//
+//========================================================================
+
+#ifndef STRUCTELEMENT_H
+#define STRUCTELEMENT_H
+
+#ifdef USE_GCC_PRAGMAS
+#pragma interface
+#endif
+
+#include "goo/gtypes.h"
+#include "goo/GooString.h"
+#include "MarkedContentOutputDev.h"
+#include "Object.h"
+#include <vector>
+#include <set>
+
+class GooString;
+class Dict;
+class StructElement;
+class StructTreeRoot;
+
+
+class Attribute {
+public:
+  enum Type {
+    Unknown = 0,        // Uninitialized, parsing error, etc.
+    UserProperty,       // User defined attribute (i.e. non-standard)
+
+    // Common standard attributes
+    Placement, WritingMode, BackgroundColor, BorderColor, BorderStyle,
+    BorderThickness, Color, Padding,
+
+    // Block element standard attributes
+    SpaceBefore, SpaceAfter, StartIndent, EndIndent, TextIndent, TextAlign,
+    BBox, Width, Height, BlockAlign, InlineAlign, TBorderStyle, TPadding,
+
+    // Inline element standard attributes
+    BaselineShift, LineHeight, TextDecorationColor, TextDecorationThickness,
+    TextDecorationType, RubyAlign, RubyPosition, GlyphOrientationVertical,
+
+    // Column-only standard attributes
+    ColumnCount, ColumnGap, ColumnWidths,
+
+    // List-only standard attributes
+    ListNumbering,
+
+    // PrintField-only standard attributes
+    Role, checked, Desc,
+
+    // Table-only standard attributes
+    RowSpan, ColSpan, Headers, Scope, Summary,
+  };
+
+  enum Owner {
+    UnknownOwner = 0,
+    // User-defined attributes
+    UserProperties,
+    // Standard attributes
+    Layout, List, PrintField, Table,
+    // Translation to other formats
+    XML_1_00, HTML_3_20, HTML_4_01, OEB_1_00, RTF_1_05, CSS_1_00, CSS_2_00,
+  };
+
+  // Creates a standard attribute. The name is predefined, and the
+  // value is type-checked to conform to the PDF specification.
+  Attribute(Type type, Object *value);
+
+  // Creates an UserProperty attribute, with an arbitrary name and value.
+  Attribute(const char *name, Object *value);
+
+  GBool isOk() const { return type != Unknown; }
+
+  // Name, type and value can be set only on construction.
+  Type getType() const { return type; }
+  Owner getOwner() const { return owner; }
+  const char *getTypeName() const;
+  const char *getOwnerName() const;
+  Object *getValue() const { return &value; }
+  static Object *getDefaultValue(Type type);
+
+  const char *getName() const { return type == UserProperty ? name.getCString() : getTypeName(); }
+
+  // The revision is optional, and defaults to zero.
+  Guint getRevision() const { return revision; }
+  void setRevision(Guint revisionA) { revision = revisionA; }
+
+  // Hidden elements should not be displayed by the user agent
+  GBool isHidden() const { return hidden; }
+  void setHidden(GBool hiddenA) { hidden = hiddenA; }
+
+  // The formatted value may be in the PDF, or be left undefined (NULL).
+  // In the later case the user agent should provide a default representation.
+  const char *getFormattedValue() const { return formatted ? formatted->getCString() : NULL; }
+  void setFormattedValue(const char *formattedA);
+
+  ~Attribute();
+
+private:
+  Type type;
+  Owner owner;
+  Guint revision;
+  mutable GooString name;
+  mutable Object value;
+  GBool hidden;
+  GooString *formatted;
+
+  GBool checkType(StructElement *element = NULL);
+  static Type getTypeForName(const char *name, StructElement *element = NULL);
+  static Attribute *parseUserProperty(Dict *property);
+
+  friend class StructElement;
+};
+
+
+class StructElement {
+public:
+  enum Type {
+    Unknown = 0,
+    MCID,                                   // MCID reference, used internally
+    OBJR,                                   // Object reference, used internally
+
+    Document, Part, Art, Sect, Div,         // Structural elements
+
+    Span, Quote, Note, Reference, BibEntry, // Inline elements
+    Code, Link, Annot,
+    BlockQuote, Caption, NonStruct,
+    TOC, TOCI, Index, Private,
+
+    P, H, H1, H2, H3, H4, H5, H6,           // Paragraph-like
+
+    L, LI, Lbl, LBody,                      // List elements
+
+    Table, TR, TH, TD, THead, TFoot, TBody, // Table elements
+
+    Ruby, RB, RT, RP,                       // Ruby text elements
+    Warichu, WT, WP,
+
+    Figure, Formula, Form,                  // Illustration-like elements
+  };
+
+  static const Ref InvalidRef;
+
+  const char *getTypeName() const;
+  Type getType() const { return type; }
+  GBool isOk() const { return type != Unknown; }
+  GBool isBlock() const;
+  GBool isInline() const;
+
+  inline GBool isContent() const { return (type == MCID) || isObjectRef(); }
+  inline GBool isObjectRef() const { return (type == OBJR && c->ref.num != -1 && c->ref.gen != -1); }
+
+  int getMCID() const { return c->mcid; }
+  Ref getObjectRef() const { return c->ref; }
+  Ref getParentRef() { return isContent() ? parent->getParentRef() : s->parentRef.getRef(); }
+  GBool hasPageRef() const;
+  GBool getPageRef(Ref& ref) const;
+  StructTreeRoot *getStructTreeRoot() { return treeRoot; }
+
+  // Optional element identifier.
+  const GooString *getID() const { return isContent() ? NULL : s->id; }
+  GooString *getID() { return isContent() ? NULL : s->id; }
+
+  // Optional ISO language name, e.g. en_US
+  GooString *getLanguage() {
+    if (!isContent() && s->language) return s->language;
+    return parent ? parent->getLanguage() : NULL;
+  }
+  const GooString *getLanguage() const {
+    if (!isContent() && s->language) return s->language;
+    return parent ? parent->getLanguage() : NULL;
+  }
+
+  // Optional revision number, defaults to zero.
+  Guint getRevision() const { return isContent() ? 0 : s->revision; }
+  void setRevision(Guint revision) { if (isContent()) s->revision = revision; }
+
+  // Optional element title, in human-readable form.
+  const GooString *getTitle() const { return isContent() ? NULL : s->title; }
+  GooString *getTitle() { return isContent() ? NULL : s->title; }
+
+  // Optional element expanded abbreviation text.
+  const GooString *getExpandedAbbr() const { return isContent() ? NULL : s->expandedAbbr; }
+  GooString *getExpandedAbbr() { return isContent() ? NULL : s->expandedAbbr; }
+
+  unsigned getNumElements() const { return isContent() ? 0 : s->elements.size(); }
+  const StructElement *getElement(int i) const { return isContent() ? NULL : s->elements.at(i); }
+  StructElement *getElement(int i) { return isContent() ? NULL : s->elements.at(i); }
+
+  void appendElement(StructElement *element) {
+    if (!isContent() && element && element->isOk()) {
+      s->elements.push_back(element);
+    }
+  }
+
+  unsigned getNumAttributes() const { return isContent() ? 0 : s->attributes.size(); }
+  const Attribute *getAttribute(int i) const { return isContent() ? NULL : s->attributes.at(i); }
+  Attribute *getAttribute(int i) { return isContent() ? NULL : s->attributes.at(i); }
+
+  void appendAttribute(Attribute *attribute) {
+    if (!isContent() && attribute) {
+      s->attributes.push_back(attribute);
+    }
+  }
+
+  const Attribute* findAttribute(Attribute::Type attributeType, GBool inherit = gFalse,
+                                 Attribute::Owner owner = Attribute::UnknownOwner) const;
+
+  const GooString *getAltText() const { return isContent() ? NULL : s->altText; }
+  GooString *getAltText() { return isContent() ? NULL : s->altText; }
+
+  const GooString *getActualText() const { return isContent() ? NULL : s->actualText; }
+  GooString *getActualText() { return isContent() ? NULL : s->actualText; }
+
+  // Content text referenced by the element:
+  //
+  // - For MCID reference elements, this is just the text of the
+  //   corresponding marked content object in the page stream, regardless
+  //   of the setting of the "recursive" flag.
+  // - For other elements, if the "recursive" flag is set, the text
+  //   enclosed by *all* the child MCID reference elements of the subtree
+  //   is returned. The text is assembled by traversing the leaf MCID
+  //   reference elements in logical order.
+  // - In any other case, the function returns NULL.
+  //
+  // A new string is returned, and the ownership passed to the caller.
+  //
+  GooString *getText(GBool recursive = gTrue) const {
+    return appendSubTreeText(NULL, recursive);
+  }
+
+  const TextSpanArray getTextSpans() const {
+    if (!isContent())
+      return TextSpanArray();
+    MarkedContentOutputDev mcdev(getMCID());
+    return getTextSpansInternal(mcdev);
+  }
+
+  ~StructElement();
+
+private:
+  GooString* appendSubTreeText(GooString *string, GBool recursive) const;
+  const TextSpanArray& getTextSpansInternal(MarkedContentOutputDev& mcdev) const;
+
+  typedef std::vector<Attribute*>     AttrPtrArray;
+  typedef std::vector<StructElement*> ElemPtrArray;
+
+  struct StructData {
+    Object       parentRef;
+    GooString   *altText;
+    GooString   *actualText;
+    GooString   *id;
+    GooString   *title;
+    GooString   *expandedAbbr;
+    GooString   *language;
+    Guint        revision;
+    ElemPtrArray elements;
+    AttrPtrArray attributes;
+
+    StructData();
+    ~StructData();
+  };
+
+  // Data in content elements (MCID, MCR)
+  struct ContentData {
+    union {
+      int mcid;
+      Ref ref;
+    };
+
+    ContentData(int mcidA): mcid(mcidA) {}
+    ContentData(const Ref& r) { ref.num = r.num; ref.gen = r.gen; }
+  };
+
+  // Common data
+  Type type;
+  StructTreeRoot *treeRoot;
+  StructElement *parent;
+  mutable Object pageRef;
+
+  union {
+    StructData  *s;
+    ContentData *c;
+  };
+
+  StructElement(Dict *elementDict, StructTreeRoot *treeRootA, StructElement *parentA, std::set<int> &seen);
+  StructElement(int mcid, StructTreeRoot *treeRootA, StructElement *parentA);
+  StructElement(const Ref &ref, StructTreeRoot *treeRootA, StructElement *parentA);
+
+  void parse(Dict* elementDict);
+  StructElement* parseChild(Object *ref, Object* childObj, std::set<int> &seen);
+  void parseChildren(Dict* element, std::set<int> &seen);
+  void parseAttributes(Dict *element, GBool keepExisting = gFalse);
+
+  friend class StructTreeRoot;
+};
+
+#endif
+
diff --git a/poppler/StructTreeRoot.cc b/poppler/StructTreeRoot.cc
new file mode 100644
index 0000000..59f017e
--- /dev/null
+++ b/poppler/StructTreeRoot.cc
@@ -0,0 +1,174 @@
+//========================================================================
+//
+// StructTreeRoot.cc
+//
+// This file is licensed under the GPLv2 or later
+//
+// Copyright 2013 Igalia S.L.
+//
+//========================================================================
+
+#ifdef USE_GCC_PRAGMAS
+#pragma interface
+#endif
+
+#include "goo/GooString.h"
+#include "StructTreeRoot.h"
+#include "StructElement.h"
+#include "PDFDoc.h"
+#include "Object.h"
+#include "Dict.h"
+#include <set>
+#include <assert.h>
+
+
+StructTreeRoot::StructTreeRoot(PDFDoc *docA, Dict *structTreeRootDict):
+  doc(docA)
+{
+  assert(doc);
+  assert(structTreeRootDict);
+  parse(structTreeRootDict);
+}
+
+StructTreeRoot::~StructTreeRoot()
+{
+  for (ElemPtrArray::iterator i = elements.begin(); i != elements.end(); ++i)
+    delete *i;
+  classMap.free();
+  roleMap.free();
+}
+
+void StructTreeRoot::parse(Dict *root)
+{
+  // The RoleMap/ClassMap dictionaries are needed by all the parsing
+  // functions, which will resolve the custom names to canonical
+  // standard names.
+  root->lookup("RoleMap", &roleMap);
+  root->lookup("ClassMap", &classMap);
+
+  // ParentTree (optional). If present, it must be a number tree,
+  // otherwise it is not possible to map stream objects to their
+  // corresponsing structure element. Here only the references are
+  // loaded into the array, the pointers to the StructElements will
+  // be filled-in later when parsing them.
+  Object obj;
+  if (root->lookup("ParentTree", &obj)->isDict()) {
+    Object nums;
+    if (obj.dictLookup("Nums", &nums)->isArray()) {
+      if (nums.arrayGetLength() % 2 == 0) {
+        parentTree.resize(nums.arrayGetLength() / 2);
+        // Index numbers in even positions, references in odd ones
+        for (int i = 0; i < nums.arrayGetLength(); i += 2) {
+          Object index, value;
+
+          if (!nums.arrayGet(i, &index)->isInt()) {
+            error(errSyntaxError, -1, "Nums item at position {0:d} is wrong type ({1:s})", i, index.getTypeName());
+            index.free();
+            continue;
+          }
+          if (index.getInt() < 0) {
+            error(errSyntaxError, -1, "Nums item at position {0:d} is invalid value ({1:d})", i, index.getInt());
+            index.free();
+            continue;
+          }
+
+          const unsigned idx = index.getInt();
+          if (nums.arrayGetNF(i + 1, &value)->isRef()) {
+            parentTree[idx].resize(1);
+            parentTree[idx][0].ref = value.getRef();
+          } else if (nums.arrayGet(i + 1, &value)->isArray()) {
+            parentTree[idx].resize(value.arrayGetLength());
+            for (int j = 0; j < value.arrayGetLength(); j++) {
+              Object itemvalue;
+              if (value.arrayGetNF(j, &itemvalue)->isRef())
+                parentTree[idx][j].ref = itemvalue.getRef();
+              else
+                error(errSyntaxError, -1, "Nums array item at position {0:d}/{1:d} is invalid type ({2:s})", i, j, itemvalue.getTypeName());
+              itemvalue.free();
+            }
+          } else {
+            error(errSyntaxError, -1, "Nums item at position {0:d} is wrong type ({1:s})", i + 1, value.getTypeName());
+          }
+
+          value.free();
+          index.free();
+        }
+      } else {
+        error(errSyntaxError, -1, "Nums array length is not a even ({0:i})", nums.arrayGetLength());
+      }
+    } else {
+      error(errSyntaxError, -1, "Nums object is wrong type ({0:s})", nums.getTypeName());
+    }
+    nums.free();
+  }
+  obj.free();
+
+  std::set<int> seenElements;
+
+  // Parse the children StructElements
+  const GBool marked = doc->getCatalog()->getMarkInfo() & Catalog::markInfoMarked;
+  Object kids;
+  if (root->lookup("K", &kids)->isArray()) {
+    if (marked && kids.arrayGetLength() > 1) {
+      error(errSyntaxWarning, -1, "K in StructTreeRoot has more than one children in a tagged PDF");
+    }
+    for (int i = 0; i < kids.arrayGetLength(); i++) {
+      Object obj, ref;
+      kids.arrayGetNF(i, &ref);
+      if (ref.isRef()) {
+        seenElements.insert(ref.getRefNum());
+      }
+      if (kids.arrayGet(i, &obj)->isDict()) {
+        StructElement *child = new StructElement(obj.getDict(), this, NULL, seenElements);
+        if (child->isOk()) {
+          if (marked && !(child->getType() == StructElement::Document ||
+                          child->getType() == StructElement::Part ||
+                          child->getType() == StructElement::Art ||
+                          child->getType() == StructElement::Div)) {
+            error(errSyntaxWarning, -1, "StructTreeRoot element of tagged PDF is wrong type ({0:s})", child->getTypeName());
+          }
+          appendElement(child);
+          if (ref.isRef()) {
+            parentTreeAdd(ref.getRef(), child);
+          }
+        } else {
+          error(errSyntaxWarning, -1, "StructTreeRoot element could not be parsed");
+          delete child;
+        }
+      } else {
+        error(errSyntaxWarning, -1, "K has a child of wrong type ({0:s})", obj.getTypeName());
+      }
+      obj.free();
+      ref.free();
+    }
+  } else if (kids.isDict()) {
+    if (marked) {
+      error(errSyntaxWarning, -1, "K has a child of wrong type for a tagged PDF ({0:s})", kids.getTypeName());
+    }
+    StructElement *child = new StructElement(kids.getDict(), this, NULL, seenElements);
+    if (child->isOk()) {
+      appendElement(child);
+      Object ref;
+      if (root->lookupNF("K", &ref)->isRef())
+        parentTreeAdd(ref.getRef(), child);
+      ref.free();
+    } else {
+      error(errSyntaxWarning, -1, "StructTreeRoot element could not be parsed");
+      delete child;
+    }
+  } else if (!kids.isNull()) {
+    error(errSyntaxWarning, -1, "K in StructTreeRoot is wrong type ({0:s})", kids.getTypeName());
+  }
+
+  kids.free();
+}
+
+void StructTreeRoot::parentTreeAdd(const Ref &objectRef, StructElement *element)
+{
+  for (std::vector< std::vector<Parent> >::iterator i = parentTree.begin(); i != parentTree.end(); ++i) {
+    for (std::vector<Parent>::iterator j = i->begin(); j != i->end(); ++j) {
+      if (j->ref.num == objectRef.num && j->ref.gen == objectRef.gen)
+        j->element = element;
+    }
+  }
+}
diff --git a/poppler/StructTreeRoot.h b/poppler/StructTreeRoot.h
new file mode 100644
index 0000000..9928e2f
--- /dev/null
+++ b/poppler/StructTreeRoot.h
@@ -0,0 +1,83 @@
+//========================================================================
+//
+// StructTreeRoot.h
+//
+// This file is licensed under the GPLv2 or later
+//
+// Copyright 2013 Igalia S.L.
+//
+//========================================================================
+
+#ifndef STRUCTTREEROOT_H
+#define STRUCTTREEROOT_H
+
+#ifdef USE_GCC_PRAGMAS
+#pragma interface
+#endif
+
+#include "goo/gtypes.h"
+#include "Object.h"
+#include "StructElement.h"
+#include <vector>
+
+class Dict;
+class PDFDoc;
+
+
+class StructTreeRoot
+{
+public:
+  StructTreeRoot(PDFDoc *docA, Dict *rootDict);
+  ~StructTreeRoot();
+
+  PDFDoc *getDoc() { return doc; }
+  Dict *getRoleMap() { return roleMap.isDict() ? roleMap.getDict() : NULL; }
+  Dict *getClassMap() { return classMap.isDict() ? classMap.getDict() : NULL; }
+  unsigned getNumElements() const { return elements.size(); }
+  const StructElement *getElement(int i) const { return elements.at(i); }
+  StructElement *getElement(int i) { return elements.at(i); }
+
+  void appendElement(StructElement *element) {
+    if (element && element->isOk()) {
+      elements.push_back(element);
+    }
+  }
+
+  const StructElement *findParentElement(unsigned index) const {
+    if (index < parentTree.size() && parentTree[index].size() == 1) {
+      return parentTree[index][0].element;
+    }
+    return NULL;
+  }
+
+private:
+  typedef std::vector<StructElement*> ElemPtrArray;
+
+  // Structure for items in /ParentTree, it keeps a mapping of
+  // object references and pointers to StructElement objects.
+  struct Parent {
+    Ref            ref;
+    StructElement *element;
+
+    Parent(): element(NULL) { ref.num = ref.gen = -1; }
+    Parent(const Parent &p): element(p.element) {
+      ref.num = p.ref.num;
+      ref.gen = p.ref.gen;
+    }
+    ~Parent() {}
+  };
+
+  PDFDoc *doc;
+  Object roleMap;
+  Object classMap;
+  ElemPtrArray elements;
+  std::vector< std::vector<Parent> > parentTree;
+
+  void parse(Dict *rootDict);
+  void parentTreeAdd(const Ref &objectRef, StructElement *element);
+
+  friend class StructElement;
+};
+
+#endif
+
diff --git a/poppler/TextOutputDev.cc b/poppler/TextOutputDev.cc
index 4adb3c2..7c2ca78 100644
--- a/poppler/TextOutputDev.cc
+++ b/poppler/TextOutputDev.cc
@@ -4000,21 +4000,6 @@
 			  PDFRectangle *selection) = 0;
 
 protected:
-
-  class TextWordSelection {
-  public:
-    TextWordSelection(TextWord *word, int begin, int end)
-      : word(word),
-        begin(begin),
-        end(end)
-    {
-    }
-
-    TextWord *word;
-    int begin;
-    int end;
-  };
-
   TextPage *page;
 };
 
@@ -4044,7 +4029,7 @@
   void endPage();
 
   GooString *getText(void);
-  GooList **getWordList(int *nLines);
+  GooList **takeWordList(int *nLines);
 
 private:
 
@@ -4179,27 +4164,18 @@
   return text;
 }
 
-GooList **TextSelectionDumper::getWordList(int *nLinesOut)
+GooList **TextSelectionDumper::takeWordList(int *nLinesOut)
 {
-  int i, j;
+  GooList **returnValue = lines;
 
+  *nLinesOut = nLines;
   if (nLines == 0)
     return NULL;
 
-  GooList **wordList = (GooList **)gmallocn(nLines, sizeof(GooList *));
+  nLines = 0;
+  lines = NULL;
 
-  for (i = 0; i < nLines; i++) {
-    GooList *lineWords = lines[i];
-    wordList[i] = new GooList();
-    for (j = 0; j < lineWords->getLength(); j++) {
-      TextWordSelection *sel = (TextWordSelection *)lineWords->get(j);
-      wordList[i]->append(sel->word);
-    }
-  }
-
-  *nLinesOut = nLines;
-
-  return wordList;
+  return returnValue;
 }
 
 class TextSelectionSizer : public TextSelectionVisitor {
@@ -4793,7 +4769,7 @@
   visitSelection(&dumper, selection, style);
   dumper.endPage();
 
-  return dumper.getWordList(nLines);
+  return dumper.takeWordList(nLines);
 }
 
 GBool TextPage::findCharRange(int pos, int length,
diff --git a/poppler/TextOutputDev.h b/poppler/TextOutputDev.h
index 56736b3..23fb3b7 100644
--- a/poppler/TextOutputDev.h
+++ b/poppler/TextOutputDev.h
@@ -478,6 +478,26 @@
 
 #endif // TEXTOUT_WORD_LIST
 
+class TextWordSelection {
+public:
+  TextWordSelection(TextWord *word, int begin, int end)
+    : word(word), begin(begin), end(end)
+  {
+  }
+
+  TextWord * getWord() const { return word; }
+  int getBegin() const { return begin; }
+  int getEnd() const { return end; }
+
+private:
+  TextWord *word;
+  int begin;
+  int end;
+
+  friend class TextSelectionPainter;
+  friend class TextSelectionDumper;
+};
+
 //------------------------------------------------------------------------
 // TextPage
 //------------------------------------------------------------------------
diff --git a/qt4/src/ArthurOutputDev.cc b/qt4/src/ArthurOutputDev.cc
index 5d57e93..914edb9 100644
--- a/qt4/src/ArthurOutputDev.cc
+++ b/qt4/src/ArthurOutputDev.cc
@@ -21,6 +21,7 @@
 // Copyright (C) 2010 Matthias Fauconneau <matthias.fauconneau@gmail.com>
 // Copyright (C) 2011 Andreas Hartmetz <ahartmetz@gmail.com>
 // Copyright (C) 2013 Thomas Freitag <Thomas.Freitag@alfa.de>
+// Copyright (C) 2013 Dominik Haumann <dhaumann@kde.org>
 //
 // To see a description of the changes please see the Changelog file that
 // came with your tarball or type make ChangeLog if you are building from git
@@ -593,10 +594,15 @@
       QPainterPath qPath;
       qPath.setFillRule(Qt::WindingFill);
       for (int i = 0; i < fontPath->length; ++i) {
+        // SplashPath.flags: bitwise or allowed
+        if (fontPath->flags[i] & splashPathLast || fontPath->flags[i] & splashPathClosed) {
+            qPath.closeSubpath();
+        }
         if (fontPath->flags[i] & splashPathFirst) {
             state->transform(fontPath->pts[i].x+x, -fontPath->pts[i].y+y, &x1, &y1);
             qPath.moveTo(x1,y1);
-        } else if (fontPath->flags[i] & splashPathCurve) {
+        }
+        if (fontPath->flags[i] & splashPathCurve) {
             state->transform(fontPath->pts[i].x+x, -fontPath->pts[i].y+y, &x1, &y1);
             state->transform(fontPath->pts[i+1].x+x, -fontPath->pts[i+1].y+y, &x2, &y2);
             qPath.quadTo(x1,y1,x2,y2);
@@ -610,20 +616,14 @@
             state->transform(fontPath->pts[i].x+x, -fontPath->pts[i].y+y, &x1, &y1);
             qPath.lineTo(x1,y1);
         }
-        if (fontPath->flags[i] & splashPathLast) {
-            qPath.closeSubpath();
-        }
       }
       GfxRGB rgb;
       QColor brushColour = m_currentBrush.color();
       state->getFillRGB(&rgb);
       brushColour.setRgbF(colToDbl(rgb.r), colToDbl(rgb.g), colToDbl(rgb.b), state->getFillOpacity());
       m_painter->setBrush(brushColour);
-      QColor penColour = m_currentPen.color();
-      state->getStrokeRGB(&rgb);
-      penColour.setRgbF(colToDbl(rgb.r), colToDbl(rgb.g), colToDbl(rgb.b), state->getStrokeOpacity());
-      m_painter->setPen(penColour);
-      m_painter->drawPath( qPath );
+      m_painter->setPen(Qt::NoPen);
+      m_painter->drawPath(qPath);
       delete fontPath;
     }
   }
diff --git a/qt4/src/Doxyfile b/qt4/src/Doxyfile
index bd1417c..a1acf72 100644
--- a/qt4/src/Doxyfile
+++ b/qt4/src/Doxyfile
@@ -31,7 +31,7 @@
 # This could be handy for archiving the generated documentation or
 # if some version control system is used.
 
-PROJECT_NUMBER         = 0.24.5
+PROJECT_NUMBER         = 0.25.0
 
 # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute)
 # base path where the generated documentation will be put.
diff --git a/qt4/tests/CMakeLists.txt b/qt4/tests/CMakeLists.txt
index 69e9c49..4f16f24 100644
--- a/qt4/tests/CMakeLists.txt
+++ b/qt4/tests/CMakeLists.txt
@@ -38,6 +38,7 @@
 
 qt4_add_simpletest(test-poppler-qt4 test-poppler-qt4.cpp)
 qt4_add_simpletest(test-password-qt4 test-password-qt4.cpp)
+qt4_add_simpletest(test-render-to-file test-render-to-file.cpp)
 qt4_add_simpletest(poppler-forms poppler-forms.cpp)
 qt4_add_simpletest(poppler-fonts poppler-fonts.cpp)
 qt4_add_simpletest(poppler_attachments poppler-attachments.cpp)
diff --git a/qt4/tests/Makefile.am b/qt4/tests/Makefile.am
index 8efe124..4e4b210 100644
--- a/qt4/tests/Makefile.am
+++ b/qt4/tests/Makefile.am
@@ -20,7 +20,7 @@
 noinst_PROGRAMS = test-poppler-qt4 stress-poppler-qt4 \
 	poppler-fonts test-password-qt4 stress-poppler-dir \
 	poppler-attachments poppler-texts poppler-forms \
-	stress-threads-qt4
+	stress-threads-qt4 test-render-to-file
 
 
 test_poppler_qt4_SOURCES =			\
@@ -29,6 +29,12 @@
 test_poppler_qt4_LDADD = $(LDADDS)
 
 
+test_render_to_file_SOURCES =			\
+       test-render-to-file.cpp
+
+test_render_to_file_LDADD = $(LDADDS)
+
+
 test_password_qt4_SOURCES =			\
        test-password-qt4.cpp
 
diff --git a/qt4/tests/check_goostring.cpp b/qt4/tests/check_goostring.cpp
index 07999b5..69f7cdc 100644
--- a/qt4/tests/check_goostring.cpp
+++ b/qt4/tests/check_goostring.cpp
@@ -1,3 +1,4 @@
+#include <QtCore/QScopedPointer>
 #include <QtTest/QtTest>
 
 #include "goo/GooString.h"
@@ -9,6 +10,7 @@
     void testInsertData_data();
     void testInsertData();
     void testInsert();
+    void testFormat();
 };
 
 void TestGooString::testInsertData_data()
@@ -56,6 +58,70 @@
     }
 }
 
+void TestGooString::testFormat()
+{
+    {
+        const QScopedPointer<GooString> goo(GooString::format("{0:d},{1:x}", 1, 0xF));
+        QCOMPARE(goo->getCString(), "1,f");
+    }
+    {
+        const QScopedPointer<GooString> goo(GooString::format("{0:d},{0:x},{0:X},{0:o},{0:b},{0:w}", 0xA));
+        QCOMPARE(goo->getCString(), "10,a,A,12,1010,          ");
+    }
+    {
+        const QScopedPointer<GooString> goo(GooString::format("{0:d},{0:x},{0:X},{0:o},{0:b}", -0xA));
+        QCOMPARE(goo->getCString(), "-10,-a,-A,-12,-1010");
+    }
+    {
+        const QScopedPointer<GooString> goo(GooString::format("{0:c}{1:c}{2:c}{3:c}",
+            'T', (char)'E', (short)'S', (int)'T'));
+        QCOMPARE(goo->getCString(), "TEST");
+
+        const QScopedPointer<GooString> goo2(GooString::format("{0:s} {1:t}", "TEST", goo.data()));
+        QCOMPARE(goo2->getCString(), "TEST TEST");
+    }
+    {
+        const QScopedPointer<GooString> goo(GooString::format("{0:ud} {1:d} {2:d}",
+            UINT_MAX, INT_MAX, INT_MIN));
+        const QByteArray expected = QString("%1 %2 %3").arg(UINT_MAX).arg(INT_MAX).arg(INT_MIN).toLatin1();
+        QCOMPARE(goo->getCString(), expected.constData());
+    }
+    {
+        const QScopedPointer<GooString> goo(GooString::format("{0:uld} {1:ld} {2:ld}",
+            ULONG_MAX, LONG_MAX, LONG_MIN));
+        const QByteArray expected = QString("%1 %2 %3").arg(ULONG_MAX).arg(LONG_MAX).arg(LONG_MIN).toLatin1();
+        QCOMPARE(goo->getCString(), expected.constData());
+    }
+    {
+        const QScopedPointer<GooString> goo(GooString::format("{0:ulld} {1:lld} {2:lld}",
+            ULLONG_MAX, LLONG_MAX, LLONG_MIN));
+        const QByteArray expected = QString("%1 %2 %3").arg(ULLONG_MAX).arg(LLONG_MAX).arg(LLONG_MIN).toLatin1();
+        QCOMPARE(goo->getCString(), expected.constData());
+    }
+    {
+        const QScopedPointer<GooString> gooD(GooString::format("{0:.1f} {0:.1g} {0:.1gs} | {1:.1f} {1:.1g} {1:.1gs}", 1., .012));
+        const QScopedPointer<GooString> gooF(GooString::format("{0:.1f} {0:.1g} {0:.1gs} | {1:.1f} {1:.1g} {1:.1gs}", 1.f, .012f));
+        QCOMPARE(gooD->getCString(), "1.0 1 1 | 0.0 0 0.01");
+        QCOMPARE(gooF->getCString(), "1.0 1 1 | 0.0 0 0.01");
+    }
+    {
+        const QScopedPointer<GooString> goo(GooString::format("{0:.4f} {0:.4g} {0:.4gs}", .012));
+        QCOMPARE(goo->getCString(), "0.0120 0.012 0.012");
+    }
+    {
+        const QScopedPointer<GooString> goo(GooString::format("{{ SomeText {0:d} }}", 1));
+        QCOMPARE(goo->getCString(), "{ SomeText 1 }");
+    }
+    {
+        const QScopedPointer<GooString> goo(GooString::format("{{{{ {{ SomeText {0:d}", 2));
+        QCOMPARE(goo->getCString(), "{{ { SomeText 2");
+    }
+    {
+        const QScopedPointer<GooString> goo(GooString::format("SomeText {0:d} }} }}}}", 3));
+        QCOMPARE(goo->getCString(), "SomeText 3 } }}");
+    }
+}
+
 QTEST_MAIN(TestGooString)
 #include "check_goostring.moc"
 
diff --git a/qt4/tests/test-render-to-file.cpp b/qt4/tests/test-render-to-file.cpp
new file mode 100644
index 0000000..b01aa03
--- /dev/null
+++ b/qt4/tests/test-render-to-file.cpp
@@ -0,0 +1,69 @@
+#include <QtCore/QDebug>
+#include <QtCore/QFile>
+#include <QtGui/QApplication>
+#include <QtGui/QImage>
+
+#include <poppler-qt4.h>
+
+int main( int argc, char **argv )
+{
+    QApplication a( argc, argv );               // QApplication required!
+
+    if ( argc < 2 ||
+        (argc == 3 && strcmp(argv[2], "-arthur") != 0) ||
+        argc > 3)
+    {
+        // use argument as file name
+        qWarning() << "usage: test-poppler-qt4 filename [-arthur]";
+        exit(1);
+    }
+  
+    Poppler::Document *doc = Poppler::Document::load(QFile::decodeName(argv[1]));
+    if (!doc)
+    {
+        qWarning() << "doc not loaded";
+        exit(1);
+    }
+
+    if (doc->isLocked())
+    {
+        qWarning() << "document locked (needs password)";
+        exit(0);
+    }
+  
+    if (doc->numPages() <= 0)
+    {
+        delete doc;
+        qDebug() << "Doc has no pages";
+        return 0;
+    }
+
+    QString backendString;
+    if (argc == 3 && strcmp(argv[2], "-arthur") == 0)
+    {
+        backendString = "Arthur";
+        doc->setRenderBackend(Poppler::Document::ArthurBackend);
+    }
+    else
+    {
+        backendString = "Splash";
+        doc->setRenderBackend(Poppler::Document::SplashBackend);
+    }
+    doc->setRenderHint(Poppler::Document::Antialiasing, true);
+    doc->setRenderHint(Poppler::Document::TextAntialiasing, true);
+    
+    for (int i = 0; i < doc->numPages(); ++i)
+    {
+        Poppler::Page *page = doc->page(i);
+        if (page) {
+            qDebug() << "Rendering page using" << backendString << "backend: " << i;
+            QTime t = QTime::currentTime();
+            QImage image = page->renderToImage();
+            qDebug() << "Rendering took" << t.msecsTo(QTime::currentTime()) << "msecs";
+            image.save(QString("test-rennder-to-file%1.ppm").arg(i));
+            delete page;
+        }
+    }
+    
+    return 0;
+}
diff --git a/qt5/src/ArthurOutputDev.cc b/qt5/src/ArthurOutputDev.cc
index 5d57e93..914edb9 100644
--- a/qt5/src/ArthurOutputDev.cc
+++ b/qt5/src/ArthurOutputDev.cc
@@ -21,6 +21,7 @@
 // Copyright (C) 2010 Matthias Fauconneau <matthias.fauconneau@gmail.com>
 // Copyright (C) 2011 Andreas Hartmetz <ahartmetz@gmail.com>
 // Copyright (C) 2013 Thomas Freitag <Thomas.Freitag@alfa.de>
+// Copyright (C) 2013 Dominik Haumann <dhaumann@kde.org>
 //
 // To see a description of the changes please see the Changelog file that
 // came with your tarball or type make ChangeLog if you are building from git
@@ -593,10 +594,15 @@
       QPainterPath qPath;
       qPath.setFillRule(Qt::WindingFill);
       for (int i = 0; i < fontPath->length; ++i) {
+        // SplashPath.flags: bitwise or allowed
+        if (fontPath->flags[i] & splashPathLast || fontPath->flags[i] & splashPathClosed) {
+            qPath.closeSubpath();
+        }
         if (fontPath->flags[i] & splashPathFirst) {
             state->transform(fontPath->pts[i].x+x, -fontPath->pts[i].y+y, &x1, &y1);
             qPath.moveTo(x1,y1);
-        } else if (fontPath->flags[i] & splashPathCurve) {
+        }
+        if (fontPath->flags[i] & splashPathCurve) {
             state->transform(fontPath->pts[i].x+x, -fontPath->pts[i].y+y, &x1, &y1);
             state->transform(fontPath->pts[i+1].x+x, -fontPath->pts[i+1].y+y, &x2, &y2);
             qPath.quadTo(x1,y1,x2,y2);
@@ -610,20 +616,14 @@
             state->transform(fontPath->pts[i].x+x, -fontPath->pts[i].y+y, &x1, &y1);
             qPath.lineTo(x1,y1);
         }
-        if (fontPath->flags[i] & splashPathLast) {
-            qPath.closeSubpath();
-        }
       }
       GfxRGB rgb;
       QColor brushColour = m_currentBrush.color();
       state->getFillRGB(&rgb);
       brushColour.setRgbF(colToDbl(rgb.r), colToDbl(rgb.g), colToDbl(rgb.b), state->getFillOpacity());
       m_painter->setBrush(brushColour);
-      QColor penColour = m_currentPen.color();
-      state->getStrokeRGB(&rgb);
-      penColour.setRgbF(colToDbl(rgb.r), colToDbl(rgb.g), colToDbl(rgb.b), state->getStrokeOpacity());
-      m_painter->setPen(penColour);
-      m_painter->drawPath( qPath );
+      m_painter->setPen(Qt::NoPen);
+      m_painter->drawPath(qPath);
       delete fontPath;
     }
   }
diff --git a/qt5/src/Doxyfile b/qt5/src/Doxyfile
index 4e2f9cd..ced525a 100644
--- a/qt5/src/Doxyfile
+++ b/qt5/src/Doxyfile
@@ -31,7 +31,7 @@
 # This could be handy for archiving the generated documentation or
 # if some version control system is used.
 
-PROJECT_NUMBER         = 0.24.5
+PROJECT_NUMBER         = 0.25.0
 
 # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute)
 # base path where the generated documentation will be put.
diff --git a/qt5/tests/CMakeLists.txt b/qt5/tests/CMakeLists.txt
index dfeb1fb..ae0bd6c 100644
--- a/qt5/tests/CMakeLists.txt
+++ b/qt5/tests/CMakeLists.txt
@@ -47,6 +47,7 @@
 
 qt5_add_simpletest(test-poppler-qt5 test-poppler-qt5.cpp)
 qt5_add_simpletest(test-password-qt5 test-password-qt5.cpp)
+qt5_add_simpletest(test-render-to-file test-render-to-file.cpp)
 qt5_add_simpletest(poppler-forms poppler-forms.cpp)
 qt5_add_simpletest(poppler-fonts poppler-fonts.cpp)
 qt5_add_simpletest(poppler_attachments poppler-attachments.cpp)
diff --git a/qt5/tests/Makefile.am b/qt5/tests/Makefile.am
index be9fa44..b3eecef 100644
--- a/qt5/tests/Makefile.am
+++ b/qt5/tests/Makefile.am
@@ -20,7 +20,7 @@
 noinst_PROGRAMS = test-poppler-qt5 stress-poppler-qt5 \
 	poppler-fonts test-password-qt5 stress-poppler-dir \
 	poppler-attachments poppler-texts poppler-forms \
-	stress-threads-qt5
+	stress-threads-qt5 test-render-to-file
 
 
 test_poppler_qt5_SOURCES =			\
@@ -29,6 +29,12 @@
 test_poppler_qt5_LDADD = $(LDADDS)
 
 
+test_render_to_file_SOURCES =			\
+       test-render-to-file.cpp
+
+test_render_to_file_LDADD = $(LDADDS)
+
+
 test_password_qt5_SOURCES =			\
        test-password-qt5.cpp
 
diff --git a/qt5/tests/check_goostring.cpp b/qt5/tests/check_goostring.cpp
index 07999b5..69f7cdc 100644
--- a/qt5/tests/check_goostring.cpp
+++ b/qt5/tests/check_goostring.cpp
@@ -1,3 +1,4 @@
+#include <QtCore/QScopedPointer>
 #include <QtTest/QtTest>
 
 #include "goo/GooString.h"
@@ -9,6 +10,7 @@
     void testInsertData_data();
     void testInsertData();
     void testInsert();
+    void testFormat();
 };
 
 void TestGooString::testInsertData_data()
@@ -56,6 +58,70 @@
     }
 }
 
+void TestGooString::testFormat()
+{
+    {
+        const QScopedPointer<GooString> goo(GooString::format("{0:d},{1:x}", 1, 0xF));
+        QCOMPARE(goo->getCString(), "1,f");
+    }
+    {
+        const QScopedPointer<GooString> goo(GooString::format("{0:d},{0:x},{0:X},{0:o},{0:b},{0:w}", 0xA));
+        QCOMPARE(goo->getCString(), "10,a,A,12,1010,          ");
+    }
+    {
+        const QScopedPointer<GooString> goo(GooString::format("{0:d},{0:x},{0:X},{0:o},{0:b}", -0xA));
+        QCOMPARE(goo->getCString(), "-10,-a,-A,-12,-1010");
+    }
+    {
+        const QScopedPointer<GooString> goo(GooString::format("{0:c}{1:c}{2:c}{3:c}",
+            'T', (char)'E', (short)'S', (int)'T'));
+        QCOMPARE(goo->getCString(), "TEST");
+
+        const QScopedPointer<GooString> goo2(GooString::format("{0:s} {1:t}", "TEST", goo.data()));
+        QCOMPARE(goo2->getCString(), "TEST TEST");
+    }
+    {
+        const QScopedPointer<GooString> goo(GooString::format("{0:ud} {1:d} {2:d}",
+            UINT_MAX, INT_MAX, INT_MIN));
+        const QByteArray expected = QString("%1 %2 %3").arg(UINT_MAX).arg(INT_MAX).arg(INT_MIN).toLatin1();
+        QCOMPARE(goo->getCString(), expected.constData());
+    }
+    {
+        const QScopedPointer<GooString> goo(GooString::format("{0:uld} {1:ld} {2:ld}",
+            ULONG_MAX, LONG_MAX, LONG_MIN));
+        const QByteArray expected = QString("%1 %2 %3").arg(ULONG_MAX).arg(LONG_MAX).arg(LONG_MIN).toLatin1();
+        QCOMPARE(goo->getCString(), expected.constData());
+    }
+    {
+        const QScopedPointer<GooString> goo(GooString::format("{0:ulld} {1:lld} {2:lld}",
+            ULLONG_MAX, LLONG_MAX, LLONG_MIN));
+        const QByteArray expected = QString("%1 %2 %3").arg(ULLONG_MAX).arg(LLONG_MAX).arg(LLONG_MIN).toLatin1();
+        QCOMPARE(goo->getCString(), expected.constData());
+    }
+    {
+        const QScopedPointer<GooString> gooD(GooString::format("{0:.1f} {0:.1g} {0:.1gs} | {1:.1f} {1:.1g} {1:.1gs}", 1., .012));
+        const QScopedPointer<GooString> gooF(GooString::format("{0:.1f} {0:.1g} {0:.1gs} | {1:.1f} {1:.1g} {1:.1gs}", 1.f, .012f));
+        QCOMPARE(gooD->getCString(), "1.0 1 1 | 0.0 0 0.01");
+        QCOMPARE(gooF->getCString(), "1.0 1 1 | 0.0 0 0.01");
+    }
+    {
+        const QScopedPointer<GooString> goo(GooString::format("{0:.4f} {0:.4g} {0:.4gs}", .012));
+        QCOMPARE(goo->getCString(), "0.0120 0.012 0.012");
+    }
+    {
+        const QScopedPointer<GooString> goo(GooString::format("{{ SomeText {0:d} }}", 1));
+        QCOMPARE(goo->getCString(), "{ SomeText 1 }");
+    }
+    {
+        const QScopedPointer<GooString> goo(GooString::format("{{{{ {{ SomeText {0:d}", 2));
+        QCOMPARE(goo->getCString(), "{{ { SomeText 2");
+    }
+    {
+        const QScopedPointer<GooString> goo(GooString::format("SomeText {0:d} }} }}}}", 3));
+        QCOMPARE(goo->getCString(), "SomeText 3 } }}");
+    }
+}
+
 QTEST_MAIN(TestGooString)
 #include "check_goostring.moc"
 
diff --git a/qt5/tests/test-render-to-file.cpp b/qt5/tests/test-render-to-file.cpp
new file mode 100644
index 0000000..5f86da4
--- /dev/null
+++ b/qt5/tests/test-render-to-file.cpp
@@ -0,0 +1,69 @@
+#include <QtCore/QDebug>
+#include <QtCore/QFile>
+#include <QGuiApplication>
+#include <QImage>
+
+#include <poppler-qt5.h>
+
+int main( int argc, char **argv )
+{
+    QGuiApplication a( argc, argv );               // QApplication required!
+
+    if ( argc < 2 ||
+        (argc == 3 && strcmp(argv[2], "-arthur") != 0) ||
+        argc > 3)
+    {
+        // use argument as file name
+        qWarning() << "usage: test-poppler-qt4 filename [-arthur]";
+        exit(1);
+    }
+  
+    Poppler::Document *doc = Poppler::Document::load(QFile::decodeName(argv[1]));
+    if (!doc)
+    {
+        qWarning() << "doc not loaded";
+        exit(1);
+    }
+
+    if (doc->isLocked())
+    {
+        qWarning() << "document locked (needs password)";
+        exit(0);
+    }
+  
+    if (doc->numPages() <= 0)
+    {
+        delete doc;
+        qDebug() << "Doc has no pages";
+        return 0;
+    }
+
+    QString backendString;
+    if (argc == 3 && strcmp(argv[2], "-arthur") == 0)
+    {
+        backendString = "Arthur";
+        doc->setRenderBackend(Poppler::Document::ArthurBackend);
+    }
+    else
+    {
+        backendString = "Splash";
+        doc->setRenderBackend(Poppler::Document::SplashBackend);
+    }
+    doc->setRenderHint(Poppler::Document::Antialiasing, true);
+    doc->setRenderHint(Poppler::Document::TextAntialiasing, true);
+    
+    for (int i = 0; i < doc->numPages(); ++i)
+    {
+        Poppler::Page *page = doc->page(i);
+        if (page) {
+            qDebug() << "Rendering page using" << backendString << "backend: " << i;
+            QTime t = QTime::currentTime();
+            QImage image = page->renderToImage();
+            qDebug() << "Rendering took" << t.msecsTo(QTime::currentTime()) << "msecs";
+            image.save(QString("test-rennder-to-file%1.ppm").arg(i));
+            delete page;
+        }
+    }
+    
+    return 0;
+}
diff --git a/regtest/HTMLReport.py b/regtest/HTMLReport.py
index 85abd34..0655e99 100644
--- a/regtest/HTMLReport.py
+++ b/regtest/HTMLReport.py
@@ -263,7 +263,7 @@
         except:
             raise
 
-    def create(self):
+    def create(self, launch_browser):
         html = "<html><body><a name='top'></a>"
         if self.config.backends:
             backends = [get_backend(name) for name in self.config.backends]
@@ -323,4 +323,5 @@
         f.write(html)
         f.close()
 
-        subprocess.Popen(['xdg-open', report_index])
+        if launch_browser:
+            subprocess.Popen(['xdg-open', report_index])
diff --git a/regtest/Printer.py b/regtest/Printer.py
index 0b0b34a..1de693d 100644
--- a/regtest/Printer.py
+++ b/regtest/Printer.py
@@ -32,21 +32,19 @@
         self._verbose = Config().verbose
         self._stream = sys.stdout
         self._rewrite = self._stream.isatty() and not self._verbose
-        self._current_line = None
-
-        self._tests = {}
+        self._current_line_len = 0
 
         self._lock = RLock()
 
         Printer.__single = self
 
     def _erase_current_line(self):
-        if not self._rewrite or self._current_line is None:
+        if not self._current_line_len:
             return
 
-        line_len = len(self._current_line)
+        line_len = self._current_line_len
         self._stream.write('\b' * line_len + ' ' * line_len + '\b' * line_len)
-        self._current_line = None
+        self._current_line_len = 0
 
     def _ensure_new_line(self, msg):
         if not msg.endswith('\n'):
@@ -58,25 +56,17 @@
         self._stream.flush()
 
     def printout(self, msg):
+        if not self._rewrite:
+            self.printout_ln(msg)
+
         with self._lock:
             self._erase_current_line()
             self._print(msg)
-            self._current_line = msg[msg.rfind('\n') + 1:]
+            self._current_line_len = len(msg[msg.rfind('\n') + 1:])
 
-    def printout_update(self, msg):
+    def printout_ln(self, msg=''):
         with self._lock:
-            if self._rewrite and self._current_line is not None:
-                msg = self._current_line + msg
-            elif not self._rewrite:
-                msg = self._ensure_new_line(msg)
-            self.printout(msg)
-
-    def printout_ln(self, msg):
-        with self._lock:
-            if self._current_line is not None:
-                self._current_line = None
-                msg = '\n' + msg
-
+            self._erase_current_line()
             self._print(self._ensure_new_line(msg))
 
     def printerr(self, msg):
@@ -84,25 +74,11 @@
             self.stderr.write(self._ensure_new_line(msg))
             self.stderr.flush()
 
-    def print_test_start(self, doc_path, backend_name, n_doc, total_docs):
-        with self._lock:
-            self._tests[(doc_path, backend_name)] = n_doc, total_docs
+    def print_test_result(self, doc_path, backend_name, n_test, total_tests, msg):
+        self.printout("[%d/%d] %s (%s): %s" % (n_test, total_tests, doc_path, backend_name, msg))
 
-    def print_test_result(self, doc_path, backend_name, msg):
-        if not self._rewrite:
-            self.print_test_result_ln(doc_path, backend_name, msg)
-            return
-
-        with self._lock:
-            n_doc, total_docs = self._tests.pop((doc_path, backend_name))
-            msg = "Tested '%s' using %s backend (%d/%d): %s" % (doc_path, backend_name, n_doc, total_docs, msg)
-        self.printout(msg)
-
-    def print_test_result_ln(self, doc_path, backend_name, msg):
-        with self._lock:
-            n_doc, total_docs = self._tests.pop((doc_path, backend_name))
-            msg = "Tested '%s' using %s backend (%d/%d): %s" % (doc_path, backend_name, n_doc, total_docs, msg)
-        self.printout_ln(msg)
+    def print_test_result_ln(self, doc_path, backend_name, n_test, total_tests, msg):
+        self.printout_ln("[%d/%d] %s (%s): %s" % (n_test, total_tests, doc_path, backend_name, msg))
 
     def print_default(self, msg):
         if self._verbose:
diff --git a/regtest/TestReferences.py b/regtest/TestReferences.py
index 6cedb4b..4572ef6 100644
--- a/regtest/TestReferences.py
+++ b/regtest/TestReferences.py
@@ -24,7 +24,7 @@
 from Utils import get_document_paths_from_dir, get_skipped_tests
 
 from Queue import Queue
-from threading import Thread
+from threading import Thread, RLock
 
 class TestReferences:
 
@@ -34,8 +34,11 @@
         self._skipped = get_skipped_tests(docsdir)
         self.config = Config()
         self.printer = get_printer()
+        self._total_tests = 1
+        self._n_tests = 0
 
         self._queue = Queue()
+        self._lock = RLock()
 
         try:
             os.makedirs(self._refsdir)
@@ -45,9 +48,19 @@
         except:
             raise
 
-    def create_refs_for_file(self, filename, n_doc = 1, total_docs = 1):
+    def _get_backends(self):
+        if self.config.backends:
+            return [get_backend(name) for name in self.config.backends]
+
+        return get_all_backends()
+
+    def create_refs_for_file(self, filename):
+        backends = self._get_backends()
+
         if filename in self._skipped:
-            self.printer.print_default("Skipping test '%s' (%d/%d)" % (os.path.join(self._docsdir, filename), n_doc, total_docs))
+            with self._lock:
+                self._n_tests += len(backends)
+            self.printer.print_default("Skipping test '%s'" % (os.path.join(self._docsdir, filename)))
             return
 
         refs_path = os.path.join(self._refsdir, filename)
@@ -60,38 +73,43 @@
             raise
         doc_path = os.path.join(self._docsdir, filename)
 
-        if self.config.backends:
-            backends = [get_backend(name) for name in self.config.backends]
-        else:
-            backends = get_all_backends()
-
         for backend in backends:
             if not self.config.force and backend.has_results(refs_path):
-                self.printer.print_default("Results found, skipping '%s' for %s backend (%d/%d)" % (doc_path, backend.get_name(), n_doc, total_docs))
+                with self._lock:
+                    self._n_tests += 1
+                self.printer.print_default("Results found, skipping '%s' for %s backend" % (doc_path, backend.get_name()))
                 continue
-            self.printer.printout_ln("Creating refs for '%s' using %s backend (%d/%d)" % (doc_path, backend.get_name(), n_doc, total_docs))
+
             if backend.create_refs(doc_path, refs_path):
                 backend.create_checksums(refs_path, self.config.checksums_only)
+            with self._lock:
+                self._n_tests += 1
+                self.printer.printout_ln("[%d/%d] %s (%s): done" % (self._n_tests, self._total_tests, doc_path, backend.get_name()))
 
     def _worker_thread(self):
         while True:
-            doc, n_doc, total_docs = self._queue.get()
-            self.create_refs_for_file(doc, n_doc, total_docs)
+            doc = self._queue.get()
+            self.create_refs_for_file(doc)
             self._queue.task_done()
 
     def create_refs(self):
         docs, total_docs = get_document_paths_from_dir(self._docsdir)
+        backends = self._get_backends()
+        self._total_tests = total_docs * len(backends)
 
-        self.printer.printout_ln('Process %d is spawning %d worker threads...' % (os.getpid(), self.config.threads))
+        self.printer.printout_ln('Found %d documents' % (total_docs))
+        self.printer.printout_ln('Backends: %s' % ', '.join([backend.get_name() for backend in backends]))
+        self.printer.printout_ln('Process %d using %d worker threads' % (os.getpid(), self.config.threads))
+        self.printer.printout_ln()
+
+        self.printer.printout('Spawning %d workers...' % (self.config.threads))
 
         for n_thread in range(self.config.threads):
             thread = Thread(target=self._worker_thread)
             thread.daemon = True
             thread.start()
 
-        n_doc = 0
         for doc in docs:
-            n_doc += 1
-            self._queue.put( (doc, n_doc, total_docs) )
+            self._queue.put(doc)
 
         self._queue.join()
diff --git a/regtest/TestRun.py b/regtest/TestRun.py
index 8f8513c..6aba62f 100644
--- a/regtest/TestRun.py
+++ b/regtest/TestRun.py
@@ -36,15 +36,20 @@
         self._skip = get_skipped_tests(docsdir)
         self.config = Config()
         self.printer = get_printer()
+        self._total_tests = 1
 
         # Results
         self._n_tests = 0
+        self._n_run = 0
         self._n_passed = 0
-        self._failed = []
-        self._crashed = []
-        self._failed_status_error = []
-        self._stderr = []
+        self._failed = {}
+        self._crashed = {}
+        self._failed_status_error = {}
+        self._did_not_crash = {}
+        self._did_not_fail_status_error = {}
+        self._stderr = {}
         self._skipped = []
+        self._new = []
 
         self._queue = Queue()
         self._lock = RLock()
@@ -57,78 +62,87 @@
         except:
             raise
 
-    def test(self, refs_path, doc_path, test_path, backend, n_doc, total_docs):
+    def _get_backends(self):
+        if self.config.backends:
+            return [get_backend(name) for name in self.config.backends]
+
+        return get_all_backends()
+
+    def test(self, refs_path, doc_path, test_path, backend):
         # First check whether there are test results for the backend
         ref_has_md5 = backend.has_md5(refs_path)
         ref_is_crashed = backend.is_crashed(refs_path)
         ref_is_failed = backend.is_failed(refs_path)
         if not ref_has_md5 and not ref_is_crashed and not ref_is_failed:
             with self._lock:
-                self._skipped.append("%s (%s)" % (doc_path, backend.get_name()))
+                self._new.append("%s (%s)" % (doc_path, backend.get_name()))
+                self._n_tests += 1
             self.printer.print_default("Reference files not found, skipping '%s' for %s backend" % (doc_path, backend.get_name()))
             return
 
+        test_has_md5 = backend.create_refs(doc_path, test_path)
+        test_passed = False
+        if ref_has_md5 and test_has_md5:
+            test_passed = backend.compare_checksums(refs_path, test_path, not self.config.keep_results, self.config.create_diffs, self.config.update_refs)
+
         with self._lock:
             self._n_tests += 1
+            self._n_run += 1
 
-        self.printer.print_test_start(doc_path, backend.get_name(), n_doc, total_docs)
-        test_has_md5 = backend.create_refs(doc_path, test_path)
+            if backend.has_stderr(test_path):
+                self._stderr.setdefault(backend.get_name(), []).append(doc_path)
 
-        if backend.has_stderr(test_path):
-            with self._lock:
-                self._stderr.append("%s (%s)" % (doc_path, backend.get_name()))
-
-        if ref_has_md5 and test_has_md5:
-            if backend.compare_checksums(refs_path, test_path, not self.config.keep_results, self.config.create_diffs, self.config.update_refs):
-                # FIXME: remove dir if it's empty?
-                self.printer.print_test_result(doc_path, backend.get_name(), "PASS")
-                with self._lock:
+            if ref_has_md5 and test_has_md5:
+                if test_passed:
+                    # FIXME: remove dir if it's empty?
+                    self.printer.print_test_result(doc_path, backend.get_name(), self._n_tests, self._total_tests, "PASS")
                     self._n_passed += 1
-            else:
-                self.printer.print_test_result_ln(doc_path, backend.get_name(), "FAIL")
-                with self._lock:
-                    self._failed.append("%s (%s)" % (doc_path, backend.get_name()))
-            return
-        elif test_has_md5:
-            if ref_is_crashed:
-                self.printer.print_test_result_ln(doc_path, backend.get_name(), "DOES NOT CRASH")
-            elif ref_is_failed:
-                self.printer.print_test_result_ln(doc_path, backend.get_name(), "DOES NOT FAIL")
-            return
+                else:
+                    self.printer.print_test_result_ln(doc_path, backend.get_name(), self._n_tests, self._total_tests, "FAIL")
+                    self._failed.setdefault(backend.get_name(), []).append(doc_path)
+                return
 
-        test_is_crashed = backend.is_crashed(test_path)
-        if ref_is_crashed and test_is_crashed:
-            self.printer.print_test_result(doc_path, backend.get_name(), "PASS (Expected crash)")
-            with self._lock:
+            if test_has_md5:
+                if ref_is_crashed:
+                    self.printer.print_test_result_ln(doc_path, backend.get_name(), self._n_tests, self._total_tests, "DOES NOT CRASH")
+                    self._did_not_crash.setdefault(backend.get_name(), []).append(doc_path)
+                elif ref_is_failed:
+                    self.printer.print_test_result_ln(doc_path, backend.get_name(), self._n_tests, self._total_tests, "DOES NOT FAIL")
+                    self._did_not_fail_status_error.setdefault(backend.get_name(), []).append(doc_path)
+                return
+
+            test_is_crashed = backend.is_crashed(test_path)
+            if ref_is_crashed and test_is_crashed:
+                self.printer.print_test_result(doc_path, backend.get_name(), self._n_tests, self._total_tests, "PASS (Expected crash)")
                 self._n_passed += 1
-            return
+                return
 
-        test_is_failed = backend.is_failed(test_path)
-        if ref_is_failed and test_is_failed:
-            # FIXME: compare status errors
-            self.printer.print_test_result(doc_path, backend.get_name(), "PASS (Expected fail with status error %d)" % (test_is_failed))
-            with self._lock:
+            test_is_failed = backend.is_failed(test_path)
+            if ref_is_failed and test_is_failed:
+                # FIXME: compare status errors
+                self.printer.print_test_result(doc_path, backend.get_name(), self._n_tests, self._total_tests, "PASS (Expected fail with status error %d)" % (test_is_failed))
                 self._n_passed += 1
-            return
+                return
 
-        if test_is_crashed:
-            self.printer.print_test_result_ln(doc_path, backend.get_name(), "CRASH")
-            with self._lock:
-                self._crashed.append("%s (%s)" % (doc_path, backend.get_name()))
-            return
+            if test_is_crashed:
+                self.printer.print_test_result_ln(doc_path, backend.get_name(), self._n_tests, self._total_tests, "CRASH")
+                self._crashed.setdefault(backend.get_name(), []).append(doc_path)
+                return
 
-        if test_is_failed:
-            self.printer.print_test_result_ln(doc_path, backend.get_name(), "FAIL (status error %d)" % (test_is_failed))
-            with self._lock:
-                self._failed_status_error("%s (%s)" % (doc_path, backend.get_name()))
-            return
+            if test_is_failed:
+                self.printer.print_test_result_ln(doc_path, backend.get_name(), self._n_tests, self._total_tests, "FAIL (status error %d)" % (test_is_failed))
+                self._failed_status_error.setdefault(backend.get_name(), []).append(doc_path)
+                return
 
-    def run_test(self, filename, n_doc = 1, total_docs = 1):
+    def run_test(self, filename):
+        backends = self._get_backends()
+
         if filename in self._skip:
             doc_path = os.path.join(self._docsdir, filename)
             with self._lock:
                 self._skipped.append("%s" % (doc_path))
-            self.printer.print_default("Skipping test '%s' (%d/%d)" % (doc_path, n_doc, total_docs))
+                self._n_tests += len(backends)
+            self.printer.print_default("Skipping test '%s'" % (doc_path))
             return
 
         out_path = os.path.join(self._outdir, filename)
@@ -144,58 +158,97 @@
 
         if not os.path.isdir(refs_path):
             with self._lock:
-                self._skipped.append("%s" % (doc_path))
-            self.printer.print_default("Reference dir not found for %s, skipping (%d/%d)" % (doc_path, n_doc, total_docs))
+                self._new.append("%s" % (doc_path))
+                self._n_tests += len(backends)
+            self.printer.print_default("Reference dir not found for %s, skipping" % (doc_path))
             return
 
-        if self.config.backends:
-            backends = [get_backend(name) for name in self.config.backends]
-        else:
-            backends = get_all_backends()
-
         for backend in backends:
-            self.test(refs_path, doc_path, out_path, backend, n_doc, total_docs)
+            self.test(refs_path, doc_path, out_path, backend)
 
     def _worker_thread(self):
         while True:
-            doc, n_doc, total_docs = self._queue.get()
-            self.run_test(doc, n_doc, total_docs)
+            doc = self._queue.get()
+            self.run_test(doc)
             self._queue.task_done()
 
     def run_tests(self):
         docs, total_docs = get_document_paths_from_dir(self._docsdir)
+        backends = self._get_backends()
+        self._total_tests = total_docs * len(backends)
 
-        self.printer.printout_ln('Process %d is spawning %d worker threads...' % (os.getpid(), self.config.threads))
+        self.printer.printout_ln('Found %d documents' % (total_docs))
+        self.printer.printout_ln('Backends: %s' % ', '.join([backend.get_name() for backend in backends]))
+        self.printer.printout_ln('Process %d using %d worker threads' % (os.getpid(), self.config.threads))
+        self.printer.printout_ln()
+
+        self.printer.printout('Spawning %d workers...' % (self.config.threads))
 
         for n_thread in range(self.config.threads):
             thread = Thread(target=self._worker_thread)
             thread.daemon = True
             thread.start()
 
-        n_doc = 0
         for doc in docs:
-            n_doc += 1
-            self._queue.put( (doc, n_doc, total_docs) )
+            self._queue.put(doc)
 
         self._queue.join()
 
+        return int(self._n_passed != self._n_run)
+
     def summary(self):
-        if not self._n_tests:
+        self.printer.printout_ln()
+
+        if self._n_run:
+            self.printer.printout_ln("%d tests passed (%.2f%%)" % (self._n_passed, (self._n_passed * 100.) / self._n_run))
+            self.printer.printout_ln()
+
+            def result_tests(test_dict):
+                if not test_dict:
+                    return 0, None
+
+                n_tests = 0
+                tests = ""
+                for backend in test_dict:
+                    backend_docs = test_dict[backend]
+                    n_tests += len(backend_docs)
+                    tests += "\n".join(["  %s (%s)" % (doc_path, backend) for doc_path in backend_docs])
+                    tests += "\n"
+
+                return n_tests, tests
+
+            def backends_summary(test_dict, n_tests):
+                percs = []
+                for backend in test_dict:
+                    n_docs = len(test_dict[backend])
+                    percs.append("%d %s (%.2f%%)" % (n_docs, backend, (n_docs * 100.) / n_tests))
+                return ", ".join(percs)
+
+            test_results = [(self._failed, "unexpected failures"),
+                            (self._crashed, "unexpected crashes"),
+                            (self._failed_status_error, "unexpected failures (test program returned with an exit error status)"),
+                            (self._stderr, "tests have stderr output"),
+                            (self._did_not_crash, "expected to crash, but didn't crash"),
+                            (self._did_not_fail_status_error, "expected to fail to run, but didn't fail")]
+
+            for test_dict, test_msg in test_results:
+                n_tests, tests = result_tests(test_dict)
+                if n_tests == 0:
+                    continue
+
+                self.printer.printout_ln("%d %s (%.2f%%) [%s]" % (n_tests, test_msg, (n_tests * 100.) / self._n_run, backends_summary(test_dict, n_tests)))
+                self.printer.printout_ln(tests)
+                self.printer.printout_ln()
+        else:
             self.printer.printout_ln("No tests run")
-            return
 
-        self.printer.printout_ln("Total %d tests" % (self._n_tests))
-        self.printer.printout_ln("%d tests passed (%.2f%%)" % (self._n_passed, (self._n_passed * 100.) / self._n_tests))
-        def report_tests(test_list, test_type):
-            n_tests = len(test_list)
-            if not n_tests:
-                return
-            self.printer.printout_ln("%d tests %s (%.2f%%): %s" % (n_tests, test_type, (n_tests * 100.) / self._n_tests, ", ".join(test_list)))
+        if self._skipped:
+            self.printer.printout_ln("%d tests skipped" % len(self._skipped))
+            self.printer.printout_ln("\n".join(["  %s" % skipped for skipped in self._skipped]))
+            self.printer.printout_ln()
 
-        report_tests(self._failed, "failed")
-        report_tests(self._crashed, "crashed")
-        report_tests(self._failed_status_error, "failed to run")
-        report_tests(self._stderr, "have stderr output")
-        report_tests(self._skipped, "skipped")
-
-
+        if self._new:
+            self.printer.printout_ln("%d new documents" % len(self._new))
+            self.printer.printout_ln("\n".join(["  %s" % new for new in self._new]))
+            self.printer.printout_ln("Use create-refs command to add reference results for them")
+            self.printer.printout_ln()
diff --git a/regtest/backends/__init__.py b/regtest/backends/__init__.py
index ff6ef84..36391d1 100644
--- a/regtest/backends/__init__.py
+++ b/regtest/backends/__init__.py
@@ -16,8 +16,9 @@
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
-from hashlib import md5
+import hashlib
 import os
+import select
 import shutil
 import errno
 from Config import Config
@@ -47,6 +48,14 @@
     def get_diff_ext(self):
         return self._diff_ext
 
+    def __md5sum(self, ref_path):
+        md5 = hashlib.md5()
+        with open(ref_path,'rb') as f:
+            for chunk in iter(lambda: f.read(128 * md5.block_size), b''):
+                md5.update(chunk)
+
+        return md5.hexdigest()
+
     def __should_have_checksum(self, entry):
         if not entry.startswith(self._name):
             return False
@@ -58,13 +67,11 @@
         path = os.path.join(refs_path, self._name)
         md5_file = open(path + '.md5', 'w')
 
-        for entry in os.listdir(refs_path):
+        for entry in sorted(os.listdir(refs_path)):
             if not self.__should_have_checksum(entry):
                 continue
             ref_path = os.path.join(refs_path, entry)
-            f = open(ref_path, 'rb')
-            md5_file.write("%s %s\n" % (md5(f.read()).hexdigest(), ref_path))
-            f.close()
+            md5_file.write("%s %s\n" % (self.__md5sum(ref_path), ref_path))
             if delete_refs:
                 os.remove(ref_path)
 
@@ -90,10 +97,9 @@
                 continue
 
             result_path = os.path.join(out_path, basename)
-            f = open(result_path, 'rb')
-            result_md5sum = md5(f.read()).hexdigest()
+
+            result_md5sum = self.__md5sum(result_path);
             matched = md5sum == result_md5sum
-            f.close()
 
             if update_refs:
                 result_md5.append("%s %s\n" % (result_md5sum, ref_path))
@@ -188,13 +194,6 @@
             return False
         return os.path.exists(test_result + self._diff_ext)
 
-    def __create_stderr_file(self, stderr, out_path):
-        if not stderr:
-            return
-        stderr_file = open(out_path + '.stderr', 'wb')
-        stderr_file.write(stderr)
-        stderr_file.close()
-
     def __create_failed_file_if_needed(self, status, out_path):
         if os.WIFEXITED(status) or os.WEXITSTATUS(status) == 0:
             return False
@@ -205,10 +204,40 @@
 
         return True
 
-    def _check_exit_status(self, p, out_path):
-        stderr = p.stderr.read()
-        self.__create_stderr_file(stderr, out_path)
+    def __redirect_stderr_to_file(self, fd, out_path):
+        stderr_file = None
+        max_size = 1024 * 1024
+        read_set = [fd]
 
+        while read_set:
+            try:
+                rlist, wlist, xlist = select.select(read_set, [], [])
+            except select.error as e:
+                continue
+
+            if fd in rlist:
+                try:
+                    chunk = os.read(fd, 1024)
+                except OSError as e:
+                    if e.errno == errno.EIO:
+                        # Child process finished.
+                        chunk = ''
+                    else:
+                        raise e
+                if chunk:
+                    if stderr_file is None:
+                        stderr_file = open(out_path + '.stderr', 'wb')
+                    if max_size > 0:
+                        stderr_file.write(chunk)
+                        max_size -= len(chunk)
+                else:
+                    read_set.remove(fd)
+
+        if stderr_file is not None:
+            stderr_file.close()
+
+    def _check_exit_status(self, p, out_path):
+        self.__redirect_stderr_to_file(p.stderr.fileno(), out_path)
         status = p.wait()
 
         if not os.WIFEXITED(status):
@@ -220,26 +249,6 @@
 
         return True
 
-    def _check_exit_status2(self, p1, p2, out_path):
-        p1_stderr = p1.stderr.read()
-        status1 = p1.wait()
-        p2_stderr = p2.stderr.read()
-        status2 = p2.wait()
-
-        if p1_stderr or p2_stderr:
-            self.__create_stderr_file(p1_stderr + p2_stderr, out_path)
-
-        if not os.WIFEXITED(status1) or not os.WIFEXITED(status2):
-            open(out_path + '.crashed', 'w').close()
-            return False
-
-        if self.__create_failed_file_if_needed(status1, out_path):
-            return False
-        if self.__create_failed_file_if_needed(status2, out_path):
-            return False
-
-        return True
-
     def _diff_png(self, ref_path, result_path):
         try:
             import Image, ImageChops
diff --git a/regtest/commands/__init__.py b/regtest/commands/__init__.py
index 86f58fd..78e1f6f 100644
--- a/regtest/commands/__init__.py
+++ b/regtest/commands/__init__.py
@@ -44,7 +44,7 @@
 
     def execute(self, args):
         ns = self._parser.parse_args(args)
-        self.run(vars(ns))
+        return self.run(vars(ns))
 
     def run(self, options):
         raise NotImplementedError
@@ -68,7 +68,7 @@
 def run(args):
     command_class = _get_command(args[0])
     command = command_class()
-    command.execute(args[1:])
+    return command.execute(args[1:])
 
 def print_help():
     import os
diff --git a/regtest/commands/create-refs.py b/regtest/commands/create-refs.py
index d559fb3..b8073d3 100644
--- a/regtest/commands/create-refs.py
+++ b/regtest/commands/create-refs.py
@@ -63,4 +63,6 @@
             refs.create_refs_for_file(os.path.basename(doc))
         get_printer().printout_ln("Refs created in %s" % (t.elapsed_str()))
 
+        return 0
+
 register_command('create-refs', CreateRefs)
diff --git a/regtest/commands/create-report.py b/regtest/commands/create-report.py
index f17aabe..e356cf9 100644
--- a/regtest/commands/create-report.py
+++ b/regtest/commands/create-report.py
@@ -40,6 +40,9 @@
         parser.add_argument('-p', '--pretty-diff',
                             action = 'store_true', dest = 'pretty_diff', default = False,
                             help = 'Include pretty diff output')
+        parser.add_argument('-n', '--no-browser',
+                            action = 'store_false', dest = 'launch_browser', default = True,
+                            help = 'Do not launch a web browser with the results')
         parser.add_argument('tests')
 
     def run(self, options):
@@ -53,6 +56,8 @@
             docs_dir = os.path.dirname(doc)
 
         report = HTMLReport(docs_dir, options['refs_dir'], options['out_dir'])
-        report.create()
+        report.create(options['launch_browser'])
+
+        return 0
 
 register_command('create-report', CreateReport)
diff --git a/regtest/commands/find-regression.py b/regtest/commands/find-regression.py
index 8f5f811..734d12a 100644
--- a/regtest/commands/find-regression.py
+++ b/regtest/commands/find-regression.py
@@ -68,11 +68,13 @@
         doc = options['test']
         if not os.path.isfile(doc):
             get_printer().printerr("Invalid test %s: not a regulat file" % (doc))
-            return
+            return 1
 
         t = Timer()
         bisect = Bisect(options['test'], options['refs_dir'], options['out_dir'])
         bisect.run()
         get_printer().printout_ln("Tests run in %s" % (t.elapsed_str()))
 
+        return 0
+
 register_command('find-regression', FindRegression)
diff --git a/regtest/commands/run-tests.py b/regtest/commands/run-tests.py
index c5d87f9..29e7dfa 100644
--- a/regtest/commands/run-tests.py
+++ b/regtest/commands/run-tests.py
@@ -65,10 +65,12 @@
 
         tests = TestRun(docs_dir, options['refs_dir'], options['out_dir'])
         if doc == docs_dir:
-            tests.run_tests()
+            status = tests.run_tests()
         else:
-            tests.run_test(os.path.basename(doc))
+            status = tests.run_test(os.path.basename(doc))
         tests.summary()
         get_printer().printout_ln("Tests run in %s" % (t.elapsed_str()))
 
+        return status
+
 register_command('run-tests', RunTests)
diff --git a/regtest/main.py b/regtest/main.py
index 41ce454..59b178b 100644
--- a/regtest/main.py
+++ b/regtest/main.py
@@ -80,16 +80,16 @@
         c.threads = n_cpus - c.threads
 
     try:
-        commands.run(args)
+        return commands.run(args)
     except commands.UnknownCommandError:
         sys.stderr.write("Unknown command: %s\n" % (args[0]))
         commands.print_help()
-        sys.exit(1)
+        return 1
     except backends.UnknownBackendError as e:
         sys.stderr.write(str(e) + "\n")
         sys.stdout.write("Backends are: %s\n" % (", ".join([backend.get_name() for backend in backends.get_all_backends()])))
-        sys.exit(1)
+        return 1
 
 if __name__ == '__main__':
     import sys
-    main(sys.argv[1:])
+    sys.exit(main(sys.argv[1:]))
diff --git a/regtest/poppler-regtest b/regtest/poppler-regtest
index fb1e126..fd69bcf 100755
--- a/regtest/poppler-regtest
+++ b/regtest/poppler-regtest
@@ -3,4 +3,4 @@
 import sys
 import main
 
-main.main(sys.argv[1:])
+sys.exit(main.main(sys.argv[1:]))
diff --git a/splash/Splash.cc b/splash/Splash.cc
index 6bb0dfe..4a7d814 100644
--- a/splash/Splash.cc
+++ b/splash/Splash.cc
@@ -446,39 +446,68 @@
 
     //----- read destination pixel
 
+    Guchar *destColorPtr;
+    if (pipe->shape && state->blendFunc && pipe->knockout && alpha0Bitmap != NULL) {
+      destColorPtr = alpha0Bitmap->data + (alpha0Y+pipe->y)*alpha0Bitmap->rowSize;
+      switch (bitmap->mode) {
+        case splashModeMono1:
+          destColorPtr += (alpha0X+pipe->x) / 8;
+          break;
+        case splashModeMono8:
+          destColorPtr += (alpha0X+pipe->x);
+          break;
+        case splashModeRGB8:
+        case splashModeBGR8:
+          destColorPtr += (alpha0X+pipe->x) * 3;
+          break;
+        case splashModeXBGR8:
+#if SPLASH_CMYK
+        case splashModeCMYK8:
+#endif
+          destColorPtr += (alpha0X+pipe->x) * 4;
+          break;
+#if SPLASH_CMYK
+        case splashModeDeviceN8:
+          destColorPtr += (alpha0X+pipe->x) * (SPOT_NCOMPS + 4);
+          break;
+#endif
+      }
+    } else {
+      destColorPtr = pipe->destColorPtr;
+    }
     switch (bitmap->mode) {
     case splashModeMono1:
-      cDest[0] = (*pipe->destColorPtr & pipe->destColorMask) ? 0xff : 0x00;
+      cDest[0] = (*destColorPtr & pipe->destColorMask) ? 0xff : 0x00;
       break;
     case splashModeMono8:
-      cDest[0] = *pipe->destColorPtr;
+      cDest[0] = *destColorPtr;
       break;
     case splashModeRGB8:
-      cDest[0] = pipe->destColorPtr[0];
-      cDest[1] = pipe->destColorPtr[1];
-      cDest[2] = pipe->destColorPtr[2];
+      cDest[0] = destColorPtr[0];
+      cDest[1] = destColorPtr[1];
+      cDest[2] = destColorPtr[2];
       break;
     case splashModeXBGR8:
-      cDest[0] = pipe->destColorPtr[2];
-      cDest[1] = pipe->destColorPtr[1];
-      cDest[2] = pipe->destColorPtr[0];
+      cDest[0] = destColorPtr[2];
+      cDest[1] = destColorPtr[1];
+      cDest[2] = destColorPtr[0];
       cDest[3] = 255;
       break;
     case splashModeBGR8:
-      cDest[0] = pipe->destColorPtr[2];
-      cDest[1] = pipe->destColorPtr[1];
-      cDest[2] = pipe->destColorPtr[0];
+      cDest[0] = destColorPtr[2];
+      cDest[1] = destColorPtr[1];
+      cDest[2] = destColorPtr[0];
       break;
 #if SPLASH_CMYK
     case splashModeCMYK8:
-      cDest[0] = pipe->destColorPtr[0];
-      cDest[1] = pipe->destColorPtr[1];
-      cDest[2] = pipe->destColorPtr[2];
-      cDest[3] = pipe->destColorPtr[3];
+      cDest[0] = destColorPtr[0];
+      cDest[1] = destColorPtr[1];
+      cDest[2] = destColorPtr[2];
+      cDest[3] = destColorPtr[3];
       break;
     case splashModeDeviceN8:
       for (cp = 0; cp < SPOT_NCOMPS + 4; cp++)
-        cDest[cp] = pipe->destColorPtr[cp];
+        cDest[cp] = destColorPtr[cp];
       break;
 #endif
     }
@@ -1549,6 +1578,7 @@
   thinLineMode = splashThinLineDefault;
   clearModRegion();
   debugMode = gFalse;
+  alpha0Bitmap = NULL;
 }
 
 Splash::Splash(SplashBitmap *bitmapA, GBool vectorAntialiasA,
@@ -1576,6 +1606,7 @@
   thinLineMode = splashThinLineDefault;
   clearModRegion();
   debugMode = gFalse;
+  alpha0Bitmap = NULL;
 }
 
 Splash::~Splash() {
diff --git a/splash/SplashFTFontEngine.h b/splash/SplashFTFontEngine.h
index aa1ad5f..11bbea7 100644
--- a/splash/SplashFTFontEngine.h
+++ b/splash/SplashFTFontEngine.h
@@ -15,6 +15,7 @@
 // Copyright (C) 2009 Petr Gajdos <pgajdos@novell.com>
 // Copyright (C) 2009 Albert Astals Cid <aacid@kde.org>
 // Copyright (C) 2011 Andreas Hartmetz <ahartmetz@gmail.com>
+// Copyright (C) 2013 Thomas Freitag <Thomas.Freitag@alfa.de>
 //
 // To see a description of the changes please see the Changelog file that
 // came with your tarball or type make ChangeLog if you are building from git
@@ -58,6 +59,8 @@
                                       int *codeToGID, int codeToGIDLen);
   SplashFontFile *loadTrueTypeFont(SplashFontFileID *idA, SplashFontSrc *src,
 				   int *codeToGID, int codeToGIDLen, int faceIndex = 0);
+  GBool getAA() { return aa; }
+  void setAA(GBool aaA) { aa = aaA; }
 
 private:
 
diff --git a/splash/SplashFontEngine.cc b/splash/SplashFontEngine.cc
index 2e74f5a..968e820 100644
--- a/splash/SplashFontEngine.cc
+++ b/splash/SplashFontEngine.cc
@@ -16,6 +16,7 @@
 // Copyright (C) 2009 Kovid Goyal <kovid@kovidgoyal.net>
 // Copyright (C) 2009 Albert Astals Cid <aacid@kde.org>
 // Copyright (C) 2011 Andreas Hartmetz <ahartmetz@gmail.com>
+// Copyright (C) 2013 Thomas Freitag <Thomas.Freitag@alfa.de>
 //
 // To see a description of the changes please see the Changelog file that
 // came with your tarball or type make ChangeLog if you are building from git
@@ -288,6 +289,18 @@
   return fontFile;
 }
 
+#if HAVE_FREETYPE_FREETYPE_H || HAVE_FREETYPE_H
+GBool SplashFontEngine::getAA() {
+  return (ftEngine == NULL) ? gFalse : ftEngine->getAA();
+}
+
+void SplashFontEngine::setAA(GBool aa) {
+  if (ftEngine != NULL) {
+    ftEngine->setAA(aa);
+  }
+}
+#endif
+
 SplashFont *SplashFontEngine::getFont(SplashFontFile *fontFile,
 				      SplashCoord *textMat,
 				      SplashCoord *ctm) {
diff --git a/splash/SplashFontEngine.h b/splash/SplashFontEngine.h
index 54926b4..6502ea5 100644
--- a/splash/SplashFontEngine.h
+++ b/splash/SplashFontEngine.h
@@ -15,6 +15,7 @@
 // Copyright (C) 2009 Petr Gajdos <pgajdos@novell.com>
 // Copyright (C) 2009, 2011 Albert Astals Cid <aacid@kde.org>
 // Copyright (C) 2011 Andreas Hartmetz <ahartmetz@gmail.com>
+// Copyright (C) 2013 Thomas Freitag <Thomas.Freitag@alfa.de>
 //
 // To see a description of the changes please see the Changelog file that
 // came with your tarball or type make ChangeLog if you are building from git
@@ -89,6 +90,10 @@
   // Note that the Splash y axis points downward.
   SplashFont *getFont(SplashFontFile *fontFile,
 		      SplashCoord *textMat, SplashCoord *ctm);
+#if HAVE_FREETYPE_FREETYPE_H || HAVE_FREETYPE_H
+  GBool getAA();
+  void setAA(GBool aa);
+#endif
 
 private:
 
diff --git a/splash/SplashTypes.h b/splash/SplashTypes.h
index fa4a379..5ea8160 100644
--- a/splash/SplashTypes.h
+++ b/splash/SplashTypes.h
@@ -46,11 +46,9 @@
 
 #define splashAASize 4
 
-#ifdef SPLASH_CMYK
 #ifndef SPOT_NCOMPS
 #define SPOT_NCOMPS 4
 #endif
-#endif
 
 //------------------------------------------------------------------------
 // colors
diff --git a/utils/CMakeLists.txt b/utils/CMakeLists.txt
index f82cfa4..2f04b39 100644
--- a/utils/CMakeLists.txt
+++ b/utils/CMakeLists.txt
@@ -69,6 +69,8 @@
   pdfimages.cc
   ImageOutputDev.cc
   ImageOutputDev.h
+  JSInfo.cc
+  JSInfo.h
 )
 add_executable(pdfimages ${pdfimages_SOURCES})
 target_link_libraries(pdfimages ${common_libs})
@@ -78,6 +80,8 @@
 # pdfinfo
 set(pdfinfo_SOURCES ${common_srcs}
   pdfinfo.cc printencodings.cc
+  JSInfo.cc
+  JSInfo.h
 )
 add_executable(pdfinfo ${pdfinfo_SOURCES})
 target_link_libraries(pdfinfo ${common_libs})
diff --git a/utils/HtmlOutputDev.cc b/utils/HtmlOutputDev.cc
index 7926674..a3ae239 100644
--- a/utils/HtmlOutputDev.cc
+++ b/utils/HtmlOutputDev.cc
@@ -25,7 +25,7 @@
 // Copyright (C) 2009 Warren Toomey <wkt@tuhs.org>
 // Copyright (C) 2009, 2011 Carlos Garcia Campos <carlosgc@gnome.org>
 // Copyright (C) 2009 Reece Dunn <msclrhd@gmail.com>
-// Copyright (C) 2010, 2012 Adrian Johnson <ajohnson@redneon.com>
+// Copyright (C) 2010, 2012, 2013 Adrian Johnson <ajohnson@redneon.com>
 // Copyright (C) 2010 Hib Eris <hib@hiberis.nl>
 // Copyright (C) 2010 OSSD CDAC Mumbai by Leena Chourey (leenac@cdacmumbai.in) and Onkar Potdar (onkar@cdacmumbai.in)
 // Copyright (C) 2011 Joshua Richardson <jric@chegg.com>
@@ -1413,32 +1413,39 @@
     delete imgStr;
   }
   else { // isMask == true
-    ImageStream *imgStr = new ImageStream(str, width, 1, 1);
-    imgStr->reset();
+    int size = (width + 7)/8;
 
-    Guchar *png_row = (Guchar *)gmalloc( width );
+    // PDF masks use 0 = draw current color, 1 = leave unchanged.
+    // We invert this to provide the standard interpretation of alpha
+    // (0 = transparent, 1 = opaque). If the colorMap already inverts
+    // the mask we leave the data unchanged.
+    int invert_bits = 0xff;
+    if (colorMap) {
+      GfxGray gray;
+      Guchar zero = 0;
+      colorMap->getGray(&zero, &gray);
+      if (colToByte(gray) == 0)
+        invert_bits = 0x00;
+    }
+
+    str->reset();
+    Guchar *png_row = (Guchar *)gmalloc(size);
 
     for (int ri = 0; ri < height; ++ri)
     {
-      // read the row of the mask
-      Guchar *bit_row = imgStr->getLine();
-
-      // invert for PNG
-      for(int i = 0; i < width; i++)
-        png_row[i] = bit_row[i] ? 0xff : 0x00;
+      for(int i = 0; i < size; i++)
+        png_row[i] = str->getChar() ^ invert_bits;
 
       if (!writer->writeRow( &png_row ))
       {
         error(errIO, -1, "Failed to write into PNG '%s'", fName->getCString());
         delete writer;
         fclose(f1);
-        delete imgStr;
         gfree(png_row);
         return;
       }
     }
-    imgStr->close();
-    delete imgStr;
+    str->close();
     gfree(png_row);
   }
 
diff --git a/utils/ImageOutputDev.cc b/utils/ImageOutputDev.cc
index 8b18d2b..5de51ad 100644
--- a/utils/ImageOutputDev.cc
+++ b/utils/ImageOutputDev.cc
@@ -20,8 +20,9 @@
 // Copyright (C) 2009 Carlos Garcia Campos <carlosgc@gnome.org>
 // Copyright (C) 2009 William Bader <williambader@hotmail.com>
 // Copyright (C) 2010 Jakob Voss <jakob.voss@gbv.de>
-// Copyright (C) 2012 Adrian Johnson <ajohnson@redneon.com>
+// Copyright (C) 2012, 2013 Adrian Johnson <ajohnson@redneon.com>
 // Copyright (C) 2013 Thomas Fischer <fischer@unix-ag.uni-kl.de>
+// Copyright (C) 2013 Hib Eris <hib@hiberis.nl>
 //
 // To see a description of the changes please see the Changelog file that
 // came with your tarball or type make ChangeLog if you are building from git
@@ -39,27 +40,37 @@
 #include <stdlib.h>
 #include <stddef.h>
 #include <ctype.h>
+#include <math.h>
 #include "goo/gmem.h"
+#include "goo/NetPBMWriter.h"
+#include "goo/PNGWriter.h"
+#include "goo/TiffWriter.h"
 #include "Error.h"
 #include "GfxState.h"
 #include "Object.h"
 #include "Stream.h"
+#include "JBIG2Stream.h"
 #include "ImageOutputDev.h"
 
-ImageOutputDev::ImageOutputDev(char *fileRootA, GBool pageNamesA, GBool dumpJPEGA, GBool listImagesA) {
+ImageOutputDev::ImageOutputDev(char *fileRootA, GBool pageNamesA, GBool listImagesA) {
   listImages = listImagesA;
   if (!listImages) {
     fileRoot = copyString(fileRootA);
     fileName = (char *)gmalloc(strlen(fileRoot) + 45);
   }
-  dumpJPEG = dumpJPEGA;
+  outputPNG = gFalse;
+  outputTiff = gFalse;
+  dumpJPEG =  gFalse;
+  dumpJP2 = gFalse;
+  dumpJBIG2 = gFalse;
+  dumpCCITT = gFalse;
   pageNames = pageNamesA;
   imgNum = 0;
   pageNum = 0;
   ok = gTrue;
   if (listImages) {
-    printf("page   num  type   width height color comp bpc  enc interp  object ID\n");
-    printf("---------------------------------------------------------------------\n");
+    printf("page   num  type   width height color comp bpc  enc interp  object ID x-ppi y-ppi size ratio\n");
+    printf("--------------------------------------------------------------------------------------------\n");
   }
 }
 
@@ -79,6 +90,34 @@
   }
 }
 
+
+// Print a floating point number between 0 - 9999 using 4 characters
+// eg '1.23', '12.3', ' 123', '1234'
+//
+// We need to be careful to handle the cases where rounding adds an
+// extra digit before the decimal. eg printf("%4.2f", 9.99999)
+// outputs "10.00" instead of "9.99".
+static void printNumber(double d)
+{
+  char buf[10];
+
+  if (d < 10.0) {
+    sprintf(buf, "%4.2f", d);
+    buf[4] = 0;
+    printf("%s", buf);
+  } else if (d < 100.0) {
+    sprintf(buf, "%4.1f", d);
+    if (!isdigit(buf[3])) {
+      buf[3] = 0;
+      printf(" %s", buf);
+    } else {
+      printf("%s", buf);
+    }
+  } else {
+    printf("%4.0f", d);
+  }
+}
+
 void ImageOutputDev::listImage(GfxState *state, Object *ref, Stream *str,
 			       int width, int height,
 			       GfxImageColorMap *colorMap,
@@ -179,192 +218,370 @@
   printf("%-3s  ", interpolate ? "yes" : "no");
 
   if (inlineImg) {
-    printf("[inline]\n");
+    printf("[inline]   ");
   } else if (ref->isRef()) {
     const Ref imageRef = ref->getRef();
     if (imageRef.gen >= 100000) {
-      printf("[none]\n");
+      printf("[none]     ");
     } else {
-      printf(" %6d %2d\n", imageRef.num, imageRef.gen);
+      printf(" %6d %2d ", imageRef.num, imageRef.gen);
     }
   } else {
-    printf("[none]\n");
+    printf("[none]     ");
   }
 
+  double *mat = state->getCTM();
+  double width2 = mat[0] + mat[2];
+  double height2 = mat[1] + mat[3];
+  double xppi = fabs(width*72.0/width2) + 0.5;
+  double yppi = fabs(height*72.0/height2) + 0.5;
+  if (xppi < 1.0)
+    printf("%5.3f ", xppi);
+  else
+    printf("%5.0f ", xppi);
+  if (yppi < 1.0)
+    printf("%5.3f ", yppi);
+  else
+    printf("%5.0f ", yppi);
+
+  Goffset embedSize = -1;
+  if (!inlineImg)
+    embedSize = str->getBaseStream()->getLength();
+
+  long long imageSize = 0;
+  if (colorMap && colorMap->isOk())
+    imageSize = ((long long)width * height * colorMap->getNumPixelComps() * colorMap->getBits())/8;
+  else
+    imageSize = (long long)width*height/8; // mask
+
+  double ratio = -1.0;
+  if (imageSize > 0)
+    ratio = 100.0*embedSize/imageSize;
+
+  if (embedSize < 0) {
+    printf("   - ");
+  } else if (embedSize <= 9999) {
+    printf("%4lldB", embedSize);
+  } else {
+    double d = embedSize/1024.0;
+    if (d <= 9999.0) {
+      printNumber(d);
+      putchar('K');
+    } else {
+      d /= 1024.0;
+      if (d <= 9999.0) {
+        printNumber(d);
+        putchar('M');
+      } else {
+        d /= 1024.0;
+        printNumber(d);
+        putchar('G');
+      }
+    }
+  }
+
+  if (ratio > 9.9)
+    printf(" %3.0f%%\n", ratio);
+  else if (ratio >= 0.0)
+    printf(" %3.1f%%\n", ratio);
+  else
+    printf("   - \n");
+
   ++imgNum;
 }
 
-void ImageOutputDev::writeMask(GfxState *state, Object *ref, Stream *str,
-			       int width, int height, GBool invert,
-			       GBool interpolate, GBool inlineImg) {
+void ImageOutputDev::writeRawImage(Stream *str, const char *ext) {
   FILE *f;
   int c;
-  int size, i;
 
-  // dump JPEG file
-  if (dumpJPEG && str->getKind() == strDCT && !inlineImg) {
-
-    // open the image file
-    setFilename("jpg");
-    ++imgNum;
-    if (!(f = fopen(fileName, "wb"))) {
-      error(errIO, -1, "Couldn't open image file '{0:s}'", fileName);
-      return;
-    }
-
-    // initialize stream
-    str = str->getNextStream();
-    str->reset();
-
-    // copy the stream
-    while ((c = str->getChar()) != EOF)
-      fputc(c, f);
-
-    str->close();
-    fclose(f);
-
-  // dump PBM file
-  } else {
-
-    // open the image file and write the PBM header
-    setFilename("pbm");
-    ++imgNum;
-    if (!(f = fopen(fileName, "wb"))) {
-      error(errIO, -1, "Couldn't open image file '{0:s}'", fileName);
-      return;
-    }
-    fprintf(f, "P4\n");
-    fprintf(f, "%d %d\n", width, height);
-
-    // initialize stream
-    str->reset();
-
-    // copy the stream
-    size = height * ((width + 7) / 8);
-    for (i = 0; i < size; ++i) {
-      fputc(str->getChar(), f);
-    }
-
-    str->close();
-    fclose(f);
+  // open the image file
+  setFilename(ext);
+  ++imgNum;
+  if (!(f = fopen(fileName, "wb"))) {
+    error(errIO, -1, "Couldn't open image file '{0:s}'", fileName);
+    return;
   }
+
+  // initialize stream
+  str = str->getNextStream();
+  str->reset();
+
+  // copy the stream
+  while ((c = str->getChar()) != EOF)
+    fputc(c, f);
+
+  str->close();
+  fclose(f);
+}
+
+void ImageOutputDev::writeImageFile(ImgWriter *writer, ImageFormat format, const char *ext,
+                                    Stream *str, int width, int height, GfxImageColorMap *colorMap) {
+  FILE *f;
+  ImageStream *imgStr = NULL;
+  unsigned char *row;
+  unsigned char *rowp;
+  Guchar *p;
+  GfxRGB rgb;
+  GfxCMYK cmyk;
+  GfxGray gray;
+  Guchar zero = 0;
+  int invert_bits;
+
+  setFilename(ext);
+  ++imgNum;
+  if (!(f = fopen(fileName, "wb"))) {
+    error(errIO, -1, "Couldn't open image file '{0:s}'", fileName);
+    return;
+  }
+
+  if (!writer->init(f, width, height, 72, 72)) {
+    error(errIO, -1, "Error writing '{0:s}'", fileName);
+    return;
+  }
+
+  if (format != imgMonochrome) {
+    // initialize stream
+    imgStr = new ImageStream(str, width, colorMap->getNumPixelComps(),
+                             colorMap->getBits());
+    imgStr->reset();
+  } else {
+    // initialize stream
+    str->reset();
+  }
+
+  row = (unsigned char *) gmallocn(width, sizeof(unsigned int));
+
+  // PDF masks use 0 = draw current color, 1 = leave unchanged.
+  // We invert this to provide the standard interpretation of alpha
+  // (0 = transparent, 1 = opaque). If the colorMap already inverts
+  // the mask we leave the data unchanged.
+  invert_bits = 0xff;
+  if (colorMap) {
+    colorMap->getGray(&zero, &gray);
+    if (colToByte(gray) == 0)
+      invert_bits = 0x00;
+  }
+
+  // for each line...
+  for (int y = 0; y < height; y++) {
+    switch (format) {
+    case imgRGB:
+      p = imgStr->getLine();
+      rowp = row;
+      for (int x = 0; x < width; ++x) {
+        if (p) {
+          colorMap->getRGB(p, &rgb);
+          *rowp++ = colToByte(rgb.r);
+          *rowp++ = colToByte(rgb.g);
+          *rowp++ = colToByte(rgb.b);
+          p += colorMap->getNumPixelComps();
+        } else {
+          *rowp++ = 0;
+          *rowp++ = 0;
+          *rowp++ = 0;
+        }
+      }
+      writer->writeRow(&row);
+      break;
+
+    case imgCMYK:
+      p = imgStr->getLine();
+      rowp = row;
+      for (int x = 0; x < width; ++x) {
+        if (p) {
+          colorMap->getCMYK(p, &cmyk);
+          *rowp++ = colToByte(cmyk.c);
+          *rowp++ = colToByte(cmyk.m);
+          *rowp++ = colToByte(cmyk.y);
+          *rowp++ = colToByte(cmyk.k);
+          p += colorMap->getNumPixelComps();
+        } else {
+          *rowp++ = 0;
+          *rowp++ = 0;
+          *rowp++ = 0;
+          *rowp++ = 0;
+        }
+      }
+      writer->writeRow(&row);
+      break;
+
+    case imgGray:
+      p = imgStr->getLine();
+      rowp = row;
+      for (int x = 0; x < width; ++x) {
+        if (p) {
+          colorMap->getGray(p, &gray);
+          *rowp++ = colToByte(gray);
+          p += colorMap->getNumPixelComps();
+        } else {
+          *rowp++ = 0;
+        }
+      }
+      writer->writeRow(&row);
+      break;
+
+    case imgMonochrome:
+      int size = (width + 7)/8;
+      for (int x = 0; x < size; x++)
+        row[x] = str->getChar() ^ invert_bits;
+      writer->writeRow(&row);
+      break;
+    }
+  }
+
+  gfree(row);
+  if (format != imgMonochrome) {
+    imgStr->close();
+    delete imgStr;
+  }
+  str->close();
+  writer->close();
+  fclose(f);
 }
 
 void ImageOutputDev::writeImage(GfxState *state, Object *ref, Stream *str,
 				int width, int height,
-				GfxImageColorMap *colorMap,
-				GBool interpolate, int *maskColors, GBool inlineImg) {
-  FILE *f;
-  ImageStream *imgStr;
-  Guchar *p;
-  Guchar zero = 0;
-  GfxGray gray;
-  GfxRGB rgb;
-  int x, y;
-  int c;
-  int size, i;
-  int pbm_mask = 0xff;
+				GfxImageColorMap *colorMap, GBool inlineImg) {
+  ImageFormat format;
 
-  // dump JPEG file
   if (dumpJPEG && str->getKind() == strDCT &&
       (colorMap->getNumPixelComps() == 1 ||
        colorMap->getNumPixelComps() == 3) &&
       !inlineImg) {
 
-    // open the image file
-    setFilename("jpg");
-    ++imgNum;
-    if (!(f = fopen(fileName, "wb"))) {
-      error(errIO, -1, "Couldn't open image file '{0:s}'", fileName);
-      return;
-    }
+    // dump JPEG file
+    writeRawImage(str, "jpg");
 
-    // initialize stream
-    str = str->getNextStream();
-    str->reset();
+  } else if (dumpJP2 && str->getKind() == strJPX && !inlineImg) {
+    // dump JPEG2000 file
+    writeRawImage(str, "jp2");
 
-    // copy the stream
-    while ((c = str->getChar()) != EOF)
-      fputc(c, f);
+  } else if (dumpJBIG2 && str->getKind() == strJBIG2 && !inlineImg) {
+    // dump JBIG2 globals stream if available
+    JBIG2Stream *jb2Str = static_cast<JBIG2Stream *>(str);
+    Object *globals = jb2Str->getGlobalsStream();
+    if (globals->isStream()) {
+      FILE *f;
+      int c;
+      Stream *str = globals->getStream();
 
-    str->close();
-    fclose(f);
-
-  // dump PBM file
-  } else if (colorMap->getNumPixelComps() == 1 &&
-	     colorMap->getBits() == 1) {
-
-    // open the image file and write the PBM header
-    setFilename("pbm");
-    ++imgNum;
-    if (!(f = fopen(fileName, "wb"))) {
-      error(errIO, -1, "Couldn't open image file '{0:s}'", fileName);
-      return;
-    }
-    fprintf(f, "P4\n");
-    fprintf(f, "%d %d\n", width, height);
-
-    // initialize stream
-    str->reset();
-
-    // if 0 comes out as 0 in the color map, the we _flip_ stream bits
-    // otherwise we pass through stream bits unmolested
-    colorMap->getGray(&zero, &gray);
-    if(colToByte(gray))
-      pbm_mask = 0;
-
-    // copy the stream
-    size = height * ((width + 7) / 8);
-    for (i = 0; i < size; ++i) {
-      fputc(str->getChar() ^ pbm_mask, f);
-    }
-
-    str->close();
-    fclose(f);
-
-  // dump PPM file
-  } else {
-
-    // open the image file and write the PPM header
-    setFilename("ppm");
-    ++imgNum;
-    if (!(f = fopen(fileName, "wb"))) {
-      error(errIO, -1, "Couldn't open image file '{0:s}'", fileName);
-      return;
-    }
-    fprintf(f, "P6\n");
-    fprintf(f, "%d %d\n", width, height);
-    fprintf(f, "255\n");
-
-    // initialize stream
-    imgStr = new ImageStream(str, width, colorMap->getNumPixelComps(),
-			     colorMap->getBits());
-    imgStr->reset();
-
-    // for each line...
-    for (y = 0; y < height; ++y) {
-
-      // write the line
-      if ((p = imgStr->getLine())) {
-	for (x = 0; x < width; ++x) {
-	  colorMap->getRGB(p, &rgb);
-	  fputc(colToByte(rgb.r), f);
-	  fputc(colToByte(rgb.g), f);
-	  fputc(colToByte(rgb.b), f);
-	  p += colorMap->getNumPixelComps();
-	}
-      } else {
-	for (x = 0; x < width; ++x) {
-	  fputc(0, f);
-	  fputc(0, f);
-	  fputc(0, f);
-	}
+      setFilename("jb2g");
+      if (!(f = fopen(fileName, "wb"))) {
+        error(errIO, -1, "Couldn't open image file '{0:s}'", fileName);
+        return;
       }
+      str->reset();
+      while ((c = str->getChar()) != EOF)
+        fputc(c, f);
+      str->close();
+      fclose(f);
     }
-    imgStr->close();
-    delete imgStr;
+
+    // dump JBIG2 embedded file
+    writeRawImage(str, "jb2e");
+
+  } else if (dumpCCITT && str->getKind() == strCCITTFax && !inlineImg) {
+    // write CCITT parameters
+    CCITTFaxStream *ccittStr = static_cast<CCITTFaxStream *>(str);
+    FILE *f;
+    setFilename("params");
+    if (!(f = fopen(fileName, "wb"))) {
+      error(errIO, -1, "Couldn't open image file '{0:s}'", fileName);
+      return;
+    }
+    if (ccittStr->getEncoding() < 0)
+      fprintf(f, "-4 ");
+    else if (ccittStr->getEncoding() == 0)
+      fprintf(f, "-1 ");
+    else
+      fprintf(f, "-2 ");
+
+    if (ccittStr->getEndOfLine())
+      fprintf(f, "-A ");
+    else
+      fprintf(f, "-P ");
+
+    fprintf(f, "-X %d ", ccittStr->getColumns());
+
+    if (ccittStr->getBlackIs1())
+      fprintf(f, "-W ");
+    else
+      fprintf(f, "-B ");
+
+    fprintf(f, "-M\n"); // PDF uses MSB first
 
     fclose(f);
+
+    // dump CCITT file
+    writeRawImage(str, "ccitt");
+
+  } else if (outputPNG && !(outputTiff && colorMap &&
+                            (colorMap->getColorSpace()->getMode() == csDeviceCMYK ||
+                             (colorMap->getColorSpace()->getMode() == csICCBased &&
+                              colorMap->getNumPixelComps() == 4)))) {
+
+    // output in PNG format
+
+#if ENABLE_LIBPNG
+    ImgWriter *writer;
+
+    if (!colorMap || (colorMap->getNumPixelComps() == 1 && colorMap->getBits() == 1)) {
+      writer = new PNGWriter(PNGWriter::MONOCHROME);
+      format = imgMonochrome;
+    } else if (colorMap->getColorSpace()->getMode() == csDeviceGray ||
+               colorMap->getColorSpace()->getMode() == csCalGray) {
+      writer = new PNGWriter(PNGWriter::GRAY);
+      format = imgGray;
+    } else {
+      writer = new PNGWriter(PNGWriter::RGB);
+      format = imgRGB;
+    }
+
+    writeImageFile(writer, format, "png", str, width, height, colorMap);
+#endif
+
+  } else if (outputTiff) {
+    // output in TIFF format
+
+#if ENABLE_LIBTIFF
+    ImgWriter *writer;
+
+    if (!colorMap || (colorMap->getNumPixelComps() == 1 && colorMap->getBits() == 1)) {
+      writer = new TiffWriter(TiffWriter::MONOCHROME);
+      format = imgMonochrome;
+    } else if (colorMap->getColorSpace()->getMode() == csDeviceGray ||
+               colorMap->getColorSpace()->getMode() == csCalGray) {
+      writer = new TiffWriter(TiffWriter::GRAY);
+      format = imgGray;
+    } else if (colorMap->getColorSpace()->getMode() == csDeviceCMYK ||
+               (colorMap->getColorSpace()->getMode() == csICCBased && colorMap->getNumPixelComps() == 4)) {
+      writer = new TiffWriter(TiffWriter::CMYK);
+      format = imgCMYK;
+    } else {
+      writer = new TiffWriter(TiffWriter::RGB);
+      format = imgRGB;
+    }
+
+    writeImageFile(writer, format, "tif", str, width, height, colorMap);
+#endif
+
+  } else {
+    // output in PPM/PBM format
+    ImgWriter *writer;
+
+    if (!colorMap || (colorMap->getNumPixelComps() == 1 && colorMap->getBits() == 1)) {
+      writer = new NetPBMWriter(NetPBMWriter::MONOCHROME);
+      format = imgMonochrome;
+    } else {
+      writer = new NetPBMWriter(NetPBMWriter::RGB);
+      format = imgRGB;
+    }
+
+    writeImageFile(writer, format,
+                   format == imgRGB ? "ppm" : "pbm",
+                   str, width, height, colorMap);
+
+    delete writer;
   }
 }
 
@@ -381,9 +598,9 @@
 				   int width, int height, GBool invert,
 				   GBool interpolate, GBool inlineImg) {
   if (listImages)
-    listImage(state, ref, str, width, height, NULL, interpolate, inlineImg, imgMask);
+    listImage(state, ref, str, width, height, NULL, interpolate, inlineImg, imgStencil);
   else
-    writeMask(state, ref, str, width, height, invert, interpolate, inlineImg);
+    writeImage(state, ref, str, width, height, NULL, inlineImg);
 }
 
 void ImageOutputDev::drawImage(GfxState *state, Object *ref, Stream *str,
@@ -393,7 +610,7 @@
   if (listImages)
     listImage(state, ref, str, width, height, colorMap, interpolate, inlineImg, imgImage);
   else
-    writeImage(state, ref, str, width, height, colorMap, interpolate, maskColors, inlineImg);
+    writeImage(state, ref, str, width, height, colorMap, inlineImg);
 }
 
 void ImageOutputDev::drawMaskedImage(
@@ -404,9 +621,8 @@
     listImage(state, ref, str, width, height, colorMap, interpolate, gFalse, imgImage);
     listImage(state, ref, str, maskWidth, maskHeight, NULL, maskInterpolate, gFalse, imgMask);
   } else {
-    drawImage(state, ref, str, width, height, colorMap, interpolate, NULL, gFalse);
-    drawImageMask(state, ref, maskStr, maskWidth, maskHeight, maskInvert,
-		  maskInterpolate, gFalse);
+    writeImage(state, ref, str, width, height, colorMap, gFalse);
+    writeImage(state, ref, maskStr, maskWidth, maskHeight, NULL, gFalse);
   }
 }
 
@@ -419,8 +635,7 @@
     listImage(state, ref, str, width, height, colorMap, interpolate, gFalse, imgImage);
     listImage(state, ref, maskStr, maskWidth, maskHeight, maskColorMap, maskInterpolate, gFalse, imgSmask);
   } else {
-    drawImage(state, ref, str, width, height, colorMap, interpolate, NULL, gFalse);
-    drawImage(state, ref, maskStr, maskWidth, maskHeight,
-	      maskColorMap, maskInterpolate, NULL, gFalse);
+    writeImage(state, ref, str, width, height, colorMap, gFalse);
+    writeImage(state, ref, maskStr, maskWidth, maskHeight, maskColorMap, gFalse);
   }
 }
diff --git a/utils/ImageOutputDev.h b/utils/ImageOutputDev.h
index 13911ed..a694bbc 100644
--- a/utils/ImageOutputDev.h
+++ b/utils/ImageOutputDev.h
@@ -17,7 +17,7 @@
 // Copyright (C) 2008 Timothy Lee <timothy.lee@siriushk.com>
 // Copyright (C) 2009 Carlos Garcia Campos <carlosgc@gnome.org>
 // Copyright (C) 2010 Jakob Voss <jakob.voss@gbv.de>
-// Copyright (C) 2012 Adrian Johnson <ajohnson@redneon.com>
+// Copyright (C) 2012, 2013 Adrian Johnson <ajohnson@redneon.com>
 // Copyright (C) 2013 Thomas Freitag <Thomas.Freitag@alfa.de>
 //
 // To see a description of the changes please see the Changelog file that
@@ -36,6 +36,7 @@
 
 #include <stdio.h>
 #include "goo/gtypes.h"
+#include "goo/ImgWriter.h"
 #include "OutputDev.h"
 
 class GfxState;
@@ -52,17 +53,42 @@
     imgMask,
     imgSmask
   };
+  enum ImageFormat {
+    imgRGB,
+    imgGray,
+    imgMonochrome,
+    imgCMYK
+  };
 
   // Create an OutputDev which will write images to files named
   // <fileRoot>-NNN.<type> or <fileRoot>-PPP-NNN.<type>, if 
   // <pageNames> is set. Normally, all images are written as PBM
-  // (.pbm) or PPM (.ppm) files.  If <dumpJPEG> is set, JPEG images 
+  // (.pbm) or PPM (.ppm) files unless PNG or Tiff output is enabled
+  // (PNG is used if both are enabled).  If Jpeg is enabled, JPEG images
   // are written as JPEG (.jpg) files.
-  ImageOutputDev(char *fileRootA, GBool pageNamesA, GBool dumpJPEGA, GBool listImagesA);
+  ImageOutputDev(char *fileRootA, GBool pageNamesA, GBool listImagesA);
 
   // Destructor.
   virtual ~ImageOutputDev();
 
+  // Use PNG format for output
+  void enablePNG(GBool png) { outputPNG = png; }
+
+  // Use TIFF format for output
+  void enableTiff(GBool tiff) { outputTiff = tiff; }
+
+  // Use Jpeg format for Jpeg files
+  void enableJpeg(GBool jpeg) { dumpJPEG = jpeg; }
+
+  // Use Jpeg2000 format for Jpeg2000 files
+  void enableJpeg2000(GBool jp2) { dumpJP2 = jp2; }
+
+  // Use JBIG2 format for JBIG2 files
+  void enableJBig2(GBool jbig2) { dumpJBIG2 = jbig2; }
+
+  // Use CCITT format for CCITT files
+  void enableCCITT(GBool ccitt) { dumpCCITT = ccitt; }
+
   // Check if file was successfully created.
   virtual GBool isOk() { return ok; }
 
@@ -128,18 +154,21 @@
 		 GfxImageColorMap *colorMap,
 		 GBool interpolate, GBool inlineImg,
 		 ImageType imageType);
-  void writeMask(GfxState *state, Object *ref, Stream *str,
-		 int width, int height, GBool invert,
-		 GBool interpolate, GBool inlineImg);
   void writeImage(GfxState *state, Object *ref, Stream *str,
-                  int width, int height, GfxImageColorMap *colorMap,
-                  GBool interpolate, int *maskColors, GBool inlineImg);
-
+                  int width, int height, GfxImageColorMap *colorMap, GBool inlineImg);
+  void writeRawImage(Stream *str, const char *ext);
+  void writeImageFile(ImgWriter *writer, ImageFormat format, const char *ext,
+                      Stream *str, int width, int height, GfxImageColorMap *colorMap);
 
   char *fileRoot;		// root of output file names
   char *fileName;		// buffer for output file names
   GBool listImages;		// list images instead of dumping
   GBool dumpJPEG;		// set to dump native JPEG files
+  GBool dumpJP2;		// set to dump native JPEG2000 files
+  GBool dumpJBIG2;		// set to dump native JBIG2 files
+  GBool dumpCCITT;		// set to dump native CCITT files
+  GBool outputPNG;		// set to output in PNG format
+  GBool outputTiff;		// set to output in TIFF format
   GBool pageNames;		// set to include page number in file names
   int pageNum;			// current page number
   int imgNum;			// current image number
diff --git a/utils/JSInfo.cc b/utils/JSInfo.cc
new file mode 100644
index 0000000..e3205c4
--- /dev/null
+++ b/utils/JSInfo.cc
@@ -0,0 +1,233 @@
+//========================================================================
+//
+// JSInfo.cc
+//
+// This file is licensed under the GPLv2 or later
+//
+// Copyright (C) 2013 Adrian Johnson <ajohnson@redneon.com>
+//
+// To see a description of the changes please see the Changelog file that
+// came with your tarball or type make ChangeLog if you are building from git
+//
+//========================================================================
+
+
+#include "config.h"
+#include <stdio.h>
+#include "Object.h"
+#include "Dict.h"
+#include "Annot.h"
+#include "PDFDoc.h"
+#include "JSInfo.h"
+#include "Link.h"
+#include "Form.h"
+#include "UnicodeMap.h"
+#include "UTF.h"
+
+JSInfo::JSInfo(PDFDoc *docA, int firstPage) {
+  doc = docA;
+  currentPage = firstPage + 1;
+}
+
+JSInfo::~JSInfo() {
+}
+
+void JSInfo::printJS(GooString *js) {
+  Unicode *u;
+  char buf[8];
+  int i, n, len;
+
+  if (!js || !js->getCString())
+    return;
+
+  len = TextStringToUCS4(js, &u);
+  for (i = 0; i < len; i++) {
+    n = uniMap->mapUnicode(u[i], buf, sizeof(buf));
+    fwrite(buf, 1, n, file);
+  }
+}
+
+void JSInfo::scanLinkAction(LinkAction *link, const char *action) {
+  if (!link)
+    return;
+
+  if (link->getKind() == actionJavaScript) {
+    hasJS = gTrue;
+    if (print) {
+      LinkJavaScript *linkjs = static_cast<LinkJavaScript *>(link);
+      GooString *s = linkjs->getScript();
+      if (s && s->getCString()) {
+	fprintf(file, "%s:\n", action);
+	printJS(s);
+	fputs("\n\n", file);
+      }
+    }
+  }
+
+  if (link->getKind() == actionRendition) {
+    LinkRendition *linkr = static_cast<LinkRendition *>(link);
+    if (linkr->getScript()) {
+      hasJS = gTrue;
+      if (print) {
+        GooString *s = linkr->getScript();
+        if (s && s->getCString()) {
+          fprintf(file, "%s (Rendition):\n", action);
+          printJS(s);
+          fputs("\n\n", file);
+        }
+      }
+    }
+  }
+}
+
+void JSInfo::scanJS(int nPages) {
+  print = gFalse;
+  file = NULL;
+  scan(nPages);
+}
+
+void JSInfo::scanJS(int nPages, FILE *fout, UnicodeMap *uMap) {
+  print = gTrue;
+  file = fout;
+  uniMap = uMap;
+  scan(nPages);
+}
+
+void JSInfo::scan(int nPages) {
+  Page *page;
+  Annots *annots;
+  Object obj1, obj2;
+  int lastPage;
+
+  hasJS = gFalse;
+
+  // Names
+  int numNames = doc->getCatalog()->numJS();
+  if (numNames > 0) {
+    hasJS = gTrue;
+    if (print) {
+      for (int i = 0; i < numNames; i++) {
+	fprintf(file, "Name Dictionary \"%s\":\n", doc->getCatalog()->getJSName(i)->getCString());
+	printJS(doc->getCatalog()->getJS(i));
+	fputs("\n\n", file);
+      }
+    }
+  }
+
+  // document actions
+  scanLinkAction(doc->getCatalog()->getAdditionalAction(Catalog::actionCloseDocument),
+                 "Before Close Document");
+  scanLinkAction(doc->getCatalog()->getAdditionalAction(Catalog::actionSaveDocumentStart),
+                 "Before Save Document");
+  scanLinkAction(doc->getCatalog()->getAdditionalAction(Catalog::actionSaveDocumentFinish),
+                 "After Save Document");
+  scanLinkAction(doc->getCatalog()->getAdditionalAction(Catalog::actionPrintDocumentStart),
+                 "Before Print Document");
+  scanLinkAction(doc->getCatalog()->getAdditionalAction(Catalog::actionPrintDocumentFinish),
+                 "After Print Document");
+
+  // form field actions
+  if (doc->getCatalog()->getFormType() == Catalog::AcroForm) {
+    Form *form = doc->getCatalog()->getForm();
+    for (int i = 0; i < form->getNumFields(); i++) {
+      FormField *field = form->getRootField(i);
+      for (int j = 0; j < field->getNumWidgets(); j++) {
+	FormWidget *widget = field->getWidget(j);
+	scanLinkAction(widget->getActivationAction(),
+                       "Field Activated");
+	scanLinkAction(widget->getAdditionalAction(Annot::actionFieldModified),
+                       "Field Modified");
+	scanLinkAction(widget->getAdditionalAction(Annot::actionFormatField),
+                       "Format Field");
+	scanLinkAction(widget->getAdditionalAction(Annot::actionValidateField),
+                       "Validate Field");
+	scanLinkAction(widget->getAdditionalAction(Annot::actionCalculateField),
+                       "Calculate Field");
+      }
+    }
+  }
+
+  // scan pages
+
+  if (currentPage > doc->getNumPages()) {
+    return;
+  }
+
+  lastPage = currentPage + nPages;
+  if (lastPage > doc->getNumPages() + 1) {
+    lastPage = doc->getNumPages() + 1;
+  }
+
+  for (int pg = currentPage; pg < lastPage; ++pg) {
+    page = doc->getPage(pg);
+    if (!page) continue;
+
+    // page actions (open, close)
+    scanLinkAction(page->getAdditionalAction(Page::actionOpenPage), "Page Open");
+    scanLinkAction(page->getAdditionalAction(Page::actionClosePage), "Page Close");
+
+    // annotation actions (links, screen, widget)
+    annots = page->getAnnots();
+    for (int i = 0; i < annots->getNumAnnots(); ++i) {
+      if (annots->getAnnot(i)->getType() == Annot::typeLink) {
+	AnnotLink *annot = static_cast<AnnotLink *>(annots->getAnnot(i));
+	scanLinkAction(annot->getAction(), "Link Annotation Activated");
+      } else if (annots->getAnnot(i)->getType() == Annot::typeScreen) {
+	AnnotScreen *annot = static_cast<AnnotScreen *>(annots->getAnnot(i));
+	scanLinkAction(annot->getAction(),
+                       "Screen Annotation Activated");
+	scanLinkAction(annot->getAdditionalAction(Annot::actionCursorEntering),
+                       "Screen Annotation Cursor Enter");
+	scanLinkAction(annot->getAdditionalAction(Annot::actionCursorLeaving),
+                       "Screen Annotation Cursor Leave");
+	scanLinkAction(annot->getAdditionalAction(Annot::actionMousePressed),
+                       "Screen Annotation Mouse Pressed");
+	scanLinkAction(annot->getAdditionalAction(Annot::actionMouseReleased),
+                       "Screen Annotation Mouse Released");
+	scanLinkAction(annot->getAdditionalAction(Annot::actionFocusIn),
+                       "Screen Annotation Focus In");
+	scanLinkAction(annot->getAdditionalAction(Annot::actionFocusOut),
+                       "Screen Annotation Focus Out");
+	scanLinkAction(annot->getAdditionalAction(Annot::actionPageOpening),
+                       "Screen Annotation Page Open");
+	scanLinkAction(annot->getAdditionalAction(Annot::actionPageClosing),
+                       "Screen Annotation Page Close");
+	scanLinkAction(annot->getAdditionalAction(Annot::actionPageVisible),
+                       "Screen Annotation Page Visible");
+	scanLinkAction(annot->getAdditionalAction(Annot::actionPageInvisible),
+                       "Screen Annotation Page Invisible");
+
+      } else if (annots->getAnnot(i)->getType() == Annot::typeWidget) {
+	AnnotWidget *annot = static_cast<AnnotWidget *>(annots->getAnnot(i));
+	scanLinkAction(annot->getAction(),
+                       "Widget Annotation Activated");
+	scanLinkAction(annot->getAdditionalAction(Annot::actionCursorEntering),
+                       "Widget Annotation Cursor Enter");
+	scanLinkAction(annot->getAdditionalAction(Annot::actionCursorLeaving),
+                       "Widget Annotation Cursor Leave");
+	scanLinkAction(annot->getAdditionalAction(Annot::actionMousePressed),
+                       "Widget Annotation Mouse Pressed");
+	scanLinkAction(annot->getAdditionalAction(Annot::actionMouseReleased),
+                       "Widget Annotation Mouse Released");
+	scanLinkAction(annot->getAdditionalAction(Annot::actionFocusIn),
+                       "Widget Annotation Focus In");
+	scanLinkAction(annot->getAdditionalAction(Annot::actionFocusOut),
+                       "Widget Annotation Focus Out");
+	scanLinkAction(annot->getAdditionalAction(Annot::actionPageOpening),
+                       "Widget Annotation Page Open");
+	scanLinkAction(annot->getAdditionalAction(Annot::actionPageClosing),
+                       "Widget Annotation Page Close");
+	scanLinkAction(annot->getAdditionalAction(Annot::actionPageVisible),
+                       "Widget Annotation Page Visible");
+	scanLinkAction(annot->getAdditionalAction(Annot::actionPageInvisible),
+                       "Widget Annotation Page Invisible");
+      }
+    }
+  }
+
+  currentPage = lastPage;
+}
+
+GBool JSInfo::containsJS() {
+  return hasJS;
+};
diff --git a/utils/JSInfo.h b/utils/JSInfo.h
new file mode 100644
index 0000000..19b786f
--- /dev/null
+++ b/utils/JSInfo.h
@@ -0,0 +1,60 @@
+//========================================================================
+//
+// JSInfo.h
+//
+// This file is licensed under the GPLv2 or later
+//
+// Copyright (C) 2013 Adrian Johnson <ajohnson@redneon.com>
+//
+// To see a description of the changes please see the Changelog file that
+// came with your tarball or type make ChangeLog if you are building from git
+//
+//========================================================================
+
+#ifndef JS_INFO_H
+#define JS_INFO_H
+
+#include <stdio.h>
+#include "Object.h"
+#include "PDFDoc.h"
+#include "goo/gtypes.h"
+
+#include "Link.h"
+#include "UnicodeMap.h"
+
+class PDFDoc;
+
+class JSInfo {
+public:
+
+  // Constructor.
+  JSInfo(PDFDoc *doc, int firstPage = 0);
+
+  // Destructor.
+  ~JSInfo();
+
+  // scan for JS in the PDF
+  void scanJS(int nPages);
+
+  // scan and print JS in the PDF
+  void scanJS(int nPages, FILE *fout, UnicodeMap *uMap);
+
+  // return true if PDF contains JavaScript
+  GBool containsJS();
+
+private:
+
+  PDFDoc *doc;
+  int currentPage;
+  GBool hasJS;
+  GBool print;
+  FILE *file;
+  UnicodeMap *uniMap;
+
+  void scan(int nPages);
+  void scanLinkAction(LinkAction *link, const char *action);
+  void printJS(GooString *js);
+
+};
+
+#endif
diff --git a/utils/Makefile.am b/utils/Makefile.am
index 0c95441..1dd9a12 100644
--- a/utils/Makefile.am
+++ b/utils/Makefile.am
@@ -88,12 +88,16 @@
 	pdfimages.cc				\
 	ImageOutputDev.cc			\
 	ImageOutputDev.h			\
+	JSInfo.cc				\
+	JSInfo.h				\
 	$(common)
 
 pdfinfo_SOURCES =				\
 	pdfinfo.cc				\
 	printencodings.cc			\
 	printencodings.h			\
+	JSInfo.cc				\
+	JSInfo.h				\
 	$(common)
 
 pdftops_SOURCES =				\
diff --git a/utils/pdfimages.1 b/utils/pdfimages.1
index 2929eca..8485f3d 100644
--- a/utils/pdfimages.1
+++ b/utils/pdfimages.1
@@ -10,17 +10,26 @@
 .SH DESCRIPTION
 .B Pdfimages
 saves images from a Portable Document Format (PDF) file as Portable
-Pixmap (PPM), Portable Bitmap (PBM), or JPEG files.
+Pixmap (PPM), Portable Bitmap (PBM), Portable Network Graphics (PNG),
+Tagged Image File Format (TIFF), JPEG, JPEG2000, or JBIG2 files.
 .PP
 Pdfimages reads the PDF file
 .IR PDF-file ,
-scans one or more pages, and writes one PPM, PBM, or JPEG file for each image,
+scans one or more pages, and writes one file for each image,
 .IR image-root - nnn . xxx ,
 where
 .I nnn
 is the image number and
 .I xxx
-is the image type (.ppm, .pbm, .jpg).
+is the image type (.ppm, .pbm, .png, .tif, .jpg, jp2, jb2e, or jb2g).
+.PP
+The default output format is PBM (for monochrome images) or PPM for
+non-monochrome. The \-png or \-tiff options change to default output
+to PNG or TIFF respectively. If both \-png and \-tiff are specified,
+CMYK images will be written as TIFF and all other images will be
+written as PNG. In addition the \-j, \-jp2, and \-jbig2 options will
+cause JPEG, JPEG2000, and JBIG2, respectively, images in the PDF file
+to be written in their native format.
 .SH OPTIONS
 .TP
 .BI \-f " number"
@@ -29,11 +38,61 @@
 .BI \-l " number"
 Specifies the last page to scan.
 .TP
+.B \-png
+Change the default output format to PNG.
+.TP
+.B \-tiff
+Change the default output format to TIFF.
+.TP
 .B \-j
-Normally, all images are written as PBM (for monochrome images) or PPM
-(for non-monochrome images) files.  With this option, images in DCT
-format are saved as JPEG files.  All non-DCT images are saved in
-PBM/PPM format as usual.
+Write images in JPEG format as JPEG files instead of the default format. The JPEG file is identical to the JPEG data stored in the PDF.
+.TP
+.B \-jp2
+Write images in JPEG2000 format as JP2 files instead of the default format. The JP2 file is identical to the JPEG2000 data stored in the PDF.
+.TP
+.B \-jbig2
+Write images in JBIG2 format as JBIG2 files instead of the default format. JBIG2 data in PDF is of the embedded type. The embedded type of JBIG2 has an optional separate file containing global data. The embedded data is written with the extension .jb2e and the global data (if available) will be written to the same image number with the extension .jb2g. The content of both these files is indentical to the JBIG2 data in the PDF.
+.TP
+.B \-ccitt
+Write images in CCITT format as CCITT files instead of the default
+format. The CCITT file is identical to the CCITT data stored in the
+PDF. PDF files contain additional parameters specifying
+how to decode the CCITT data. These parameters are translated to
+fax2tiff input options and written to a .params file with the same image
+number. The parameters are:
+.RS
+.TP
+.B \-1
+1D Group 3 encoding
+.TP
+.B \-2
+2D Group 3 encoding
+.TP
+.B \-4
+Group 4 encoding
+.TP
+.B \-A
+Beginning of line is aligned on a byte boundary
+.TP
+.B \-P
+Beginning of line is not aligned on a byte boundary
+.TP
+.B \-X n
+The image width in pixels
+.TP
+.B \-W
+Encoding uses 1 for black and 0 for white
+.TP
+.B \-B
+Encoding uses 0 for black and 1 for white
+.TP
+.B \-M
+Input data fills from most significant bit to least significant bit.
+.RE
+.TP
+.B \-all
+Write JPEG, JPEG2000, JBIG2, and CCITT images in their native format. CMYK files are written as TIFF files. All other images are written as PNG files.
+This is equivalent to specifying the options \-png \-tiff \-j \-jp2 \-jbig2 \-ccitt.
 .TP
 .B \-list
 Instead of writing the images, list the images along with various information for each image. Do not specify an
@@ -134,6 +193,18 @@
 .TP
 .B object ID
 the image dictionary object ID (number and generation)
+.TP
+.B x\-ppi
+The horizontal resolution of the image (in pixels per inch) when rendered on the pdf page.
+.TP
+.B y\-ppi
+The vertical resolution of the image (in pixels per inch) when rendered on the pdf page.
+.TP
+.B size
+The size of the embedded image in the pdf file. The following suffixes are used: 'B' bytes, 'K' kilobytes, 'M' megabytes, and 'G' gigabytes.
+.TP
+.B ratio
+The compression ratio of the embedded image.
 .RE
 .TP
 .BI \-opw " password"
diff --git a/utils/pdfimages.cc b/utils/pdfimages.cc
index e898985..563839e 100644
--- a/utils/pdfimages.cc
+++ b/utils/pdfimages.cc
@@ -18,7 +18,7 @@
 // Copyright (C) 2007-2008, 2010 Albert Astals Cid <aacid@kde.org>
 // Copyright (C) 2010 Hib Eris <hib@hiberis.nl>
 // Copyright (C) 2010 Jakob Voss <jakob.voss@gbv.de>
-// Copyright (C) 2012 Adrian Johnson <ajohnson@redneon.com>
+// Copyright (C) 2012, 2013 Adrian Johnson <ajohnson@redneon.com>
 // Copyright (C) 2013 Suzuki Toshiya <mpsuzuki@hiroshima-u.ac.jp>
 //
 // To see a description of the changes please see the Changelog file that
@@ -51,7 +51,13 @@
 static int firstPage = 1;
 static int lastPage = 0;
 static GBool listImages = gFalse;
+static GBool enablePNG = gFalse;
+static GBool enableTiff = gFalse;
 static GBool dumpJPEG = gFalse;
+static GBool dumpJP2 = gFalse;
+static GBool dumpJBIG2 = gFalse;
+static GBool dumpCCITT = gFalse;
+static GBool allFormats = gFalse;
 static GBool pageNames = gFalse;
 static char ownerPassword[33] = "\001";
 static char userPassword[33] = "\001";
@@ -64,8 +70,24 @@
    "first page to convert"},
   {"-l",      argInt,      &lastPage,      0,
    "last page to convert"},
+#if ENABLE_LIBPNG
+  {"-png",      argFlag,     &enablePNG,      0,
+   "change the default output format to PNG"},
+#endif
+#if ENABLE_LIBTIFF
+  {"-tiff",      argFlag,     &enableTiff,      0,
+   "change the default output format to TIFF"},
+#endif
   {"-j",      argFlag,     &dumpJPEG,      0,
    "write JPEG images as JPEG files"},
+  {"-jp2",      argFlag,     &dumpJP2,      0,
+   "write JPEG2000 images as JP2 files"},
+  {"-jbig2",      argFlag,     &dumpJBIG2,      0,
+   "write JBIG2 images as JBIG2 files"},
+  {"-ccitt",      argFlag,     &dumpCCITT,      0,
+   "write CCITT images as CCITT files"},
+  {"-all",      argFlag,     &allFormats,    0,
+   "equivalent to -png -tiff -j -jp2 -jbig2 -ccitt"},
   {"-list",   argFlag,     &listImages,      0,
    "print list of images instead of saving"},
   {"-opw",    argString,   ownerPassword,  sizeof(ownerPassword),
@@ -175,10 +197,25 @@
   }
 
   // write image files
-  imgOut = new ImageOutputDev(imgRoot, pageNames, dumpJPEG, listImages);
+  imgOut = new ImageOutputDev(imgRoot, pageNames, listImages);
   if (imgOut->isOk()) {
-      doc->displayPages(imgOut, firstPage, lastPage, 72, 72, 0,
-			gTrue, gFalse, gFalse);
+    if (allFormats) {
+      imgOut->enablePNG(gTrue);
+      imgOut->enableTiff(gTrue);
+      imgOut->enableJpeg(gTrue);
+      imgOut->enableJpeg2000(gTrue);
+      imgOut->enableJBig2(gTrue);
+      imgOut->enableCCITT(gTrue);
+    } else {
+      imgOut->enablePNG(enablePNG);
+      imgOut->enableTiff(enableTiff);
+      imgOut->enableJpeg(dumpJPEG);
+      imgOut->enableJpeg2000(dumpJP2);
+      imgOut->enableJBig2(dumpJBIG2);
+      imgOut->enableCCITT(dumpCCITT);
+    }
+    doc->displayPages(imgOut, firstPage, lastPage, 72, 72, 0,
+                      gTrue, gFalse, gFalse);
   }
   delete imgOut;
 
diff --git a/utils/pdfinfo.1 b/utils/pdfinfo.1
index a3ad1c3..1dd7466 100644
--- a/utils/pdfinfo.1
+++ b/utils/pdfinfo.1
@@ -48,6 +48,9 @@
 form (AcroForm / XFA / none)
 .RE
 .RS
+javascript (yes/no)
+.RE
+.RS
 page count
 .RE
 .RS
@@ -90,6 +93,9 @@
 Prints document-level metadata.  (This is the "Metadata" stream from
 the PDF file's Catalog object.)
 .TP
++.B \-js
++Prints all JavaScript in the PDF.
++.TP
 .B \-rawdates
 Prints the raw (undecoded) date strings, directly from the PDF file.
 .TP
diff --git a/utils/pdfinfo.cc b/utils/pdfinfo.cc
index 68fbd20..5a9745f 100644
--- a/utils/pdfinfo.cc
+++ b/utils/pdfinfo.cc
@@ -19,6 +19,7 @@
 // Copyright (C) 2011 Vittal Aithal <vittal.aithal@cognidox.com>
 // Copyright (C) 2012, 2013 Adrian Johnson <ajohnson@redneon.com>
 // Copyright (C) 2012 Fabio D'Urso <fabiodurso@hotmail.it>
+// Copyright (C) 2013 Adrian Perez de Castro <aperez@igalia.com>
 // Copyright (C) 2013 Suzuki Toshiya <mpsuzuki@hiroshima-u.ac.jp>
 //
 // To see a description of the changes please see the Changelog file that
@@ -54,6 +55,7 @@
 #include "UTF.h"
 #include "Error.h"
 #include "DateInfo.h"
+#include "JSInfo.h"
 
 static void printInfoString(Dict *infoDict, const char *key, const char *text,
 			    UnicodeMap *uMap);
@@ -64,6 +66,7 @@
 static int lastPage = 0;
 static GBool printBoxes = gFalse;
 static GBool printMetadata = gFalse;
+static GBool printJS = gFalse;
 static GBool rawDates = gFalse;
 static char textEncName[128] = "";
 static char ownerPassword[33] = "\001";
@@ -81,6 +84,8 @@
    "print the page bounding boxes"},
   {"-meta",   argFlag,     &printMetadata,    0,
    "print the document metadata (XML)"},
+  {"-js",     argFlag,     &printJS,          0,
+   "print all JavaScript in the PDF"},
   {"-rawdates", argFlag,   &rawDates,         0,
    "print the undecoded date strings directly from the PDF file"},
   {"-enc",    argString,   textEncName,    sizeof(textEncName),
@@ -232,8 +237,12 @@
   info.free();
 
   // print tagging info
-  printf("Tagged:         %s\n",
-	 doc->getStructTreeRoot()->isDict() ? "yes" : "no");
+   printf("Tagged:         %s\n",
+	  (doc->getCatalog()->getMarkInfo() & Catalog::markInfoMarked) ? "yes" : "no");
+   printf("UserProperties: %s\n",
+	  (doc->getCatalog()->getMarkInfo() & Catalog::markInfoUserProperties) ? "yes" : "no");
+   printf("Suspects:       %s\n",
+	  (doc->getCatalog()->getMarkInfo() & Catalog::markInfoSuspects) ? "yes" : "no");
 
   // print form info
   switch (doc->getCatalog()->getFormType())
@@ -249,6 +258,13 @@
       break;
   }
 
+  // print javascript info
+  {
+    JSInfo jsInfo(doc, firstPage - 1);
+    jsInfo.scanJS(lastPage - firstPage + 1);
+    printf("JavaScript:     %s\n", jsInfo.containsJS() ? "yes" : "no");
+  }
+
   // print page count
   printf("Pages:          %d\n", doc->getNumPages());
 
@@ -378,6 +394,13 @@
     delete metadata;
   }
 
+  // print javascript
+  if (printJS) {
+    JSInfo jsInfo(doc, firstPage - 1);
+    fputs("\n", stdout);
+    jsInfo.scanJS(lastPage - firstPage + 1, stdout, uMap);
+  }
+
   exitCode = 0;
 
   // clean up
diff --git a/utils/pdfseparate.cc b/utils/pdfseparate.cc
index d93ca3e..78dbf12 100644
--- a/utils/pdfseparate.cc
+++ b/utils/pdfseparate.cc
@@ -7,6 +7,7 @@
 // Copyright (C) 2011, 2012 Thomas Freitag <Thomas.Freitag@alfa.de>
 // Copyright (C) 2012, 2013 Albert Astals Cid <aacid@kde.org>
 // Copyright (C) 2013 Pino Toscano <pino@kde.org>
+// Copyright (C) 2013 Daniel Kahn Gillmor <dkg@fifthhorseman.net>
 // Copyright (C) 2013 Suzuki Toshiya <mpsuzuki@hiroshima-u.ac.jp>
 //
 //========================================================================
@@ -56,6 +57,12 @@
     return false;
   }
 
+  // destFileName can have multiple %% and one %d
+  // We use auxDestFileName to replace all the valid % appearances
+  // by 'A' (random char that is not %), if at the end of replacing
+  // any of the valid appearances there is still any % around, the
+  // pattern is wrong
+  char *auxDestFileName = strdup(destFileName);
   if (firstPage == 0 && lastPage == 0) {
     firstPage = 1;
     lastPage = doc->getNumPages();
@@ -70,29 +77,37 @@
           firstPage, lastPage);
     return false;
   }
-  if (firstPage != lastPage && strstr(destFileName, "%d") == NULL) {
-    error(errSyntaxError, -1, "'{0:s}' must contain '%d' if more than one page should be extracted", destFileName);
+  bool foundmatch = false;
+  char *p = strstr(auxDestFileName, "%d");
+  if (p != NULL) {
+    foundmatch = true;
+    *p = 'A';
+  } else {
+    char pattern[5];
+    for (int i = 2; i < 10; i++) {
+      sprintf(pattern, "%%0%dd", i);
+      p = strstr(auxDestFileName, pattern);
+      if (p != NULL) {
+       foundmatch = true;
+       *p = 'A';
+       break;
+      }
+    }
+  }
+  if (!foundmatch && firstPage != lastPage) {
+    error(errSyntaxError, -1, "'{0:s}' must contain '%%d' if more than one page should be extracted", destFileName);
+    free(auxDestFileName);
     return false;
   }
-  
-  // destFileName can have multiple %% and one %d
-  // We use auxDestFileName to replace all the valid % appearances
-  // by 'A' (random char that is not %), if at the end of replacing
-  // any of the valid appearances there is still any % around, the
-  // pattern is wrong
-  char *auxDestFileName = strdup(destFileName);
-  // %% can appear as many times as you want
-  char *p = strstr(auxDestFileName, "%%");
+
+  // at this point auxDestFileName can only contain %%
+  p = strstr(auxDestFileName, "%%");
   while (p != NULL) {
     *p = 'A';
     *(p + 1) = 'A';
     p = strstr(p, "%%"); 
   }
-  // %d can appear only one time
-  p = strstr(auxDestFileName, "%d");
-  if (p != NULL) {
-    *p = 'A';
-  }
+
   // at this point any other % is wrong
   p = strstr(auxDestFileName, "%");
   if (p != NULL) {
diff --git a/utils/pdftocairo.cc b/utils/pdftocairo.cc
index 86e413c..f67007f 100644
--- a/utils/pdftocairo.cc
+++ b/utils/pdftocairo.cc
@@ -19,7 +19,7 @@
 // Copyright (C) 2009 Shen Liang <shenzhuxi@gmail.com>
 // Copyright (C) 2009 Stefan Thomas <thomas@eload24.com>
 // Copyright (C) 2009, 2010 Albert Astals Cid <aacid@kde.org>
-// Copyright (C) 2010, 2011, 2012 Adrian Johnson <ajohnson@redneon.com>
+// Copyright (C) 2010, 2011-2013 Adrian Johnson <ajohnson@redneon.com>
 // Copyright (C) 2010 Hib Eris <hib@hiberis.nl>
 // Copyright (C) 2010 Jonathan Liu <net147@gmail.com>
 // Copyright (C) 2010 William Bader <williambader@hotmail.com>
@@ -372,7 +372,7 @@
 	int b = (*pixel & 0x000000ff) >>  0;
 	// an arbitrary integer approximation of .3*r + .59*g + .11*b
 	int y = (r*19661+g*38666+b*7209 + 32829)>>16;
-        if (tiff && mono) {
+        if (mono) {
           if (bit == 7)
             *rowp = 0;
           if (y > 127)
@@ -674,7 +674,7 @@
   GooString *imageName = new GooString(outputFileName);
   if (!singleFile) {
     snprintf(buf, sizeof(buf), "-%0*d", numDigits, page);
-    imageName->appendf(buf);
+    imageName->append(buf);
   }
   if (png)
     imageName->append(".png");
diff --git a/utils/pdftohtml.cc b/utils/pdftohtml.cc
index 5f39e95..8ce0430 100644
--- a/utils/pdftohtml.cc
+++ b/utils/pdftohtml.cc
@@ -104,8 +104,12 @@
    "don't print any messages or errors"},
   {"-h",      argFlag,     &printHelp,     0,
    "print usage information"},
+  {"-?",      argFlag,     &printHelp,     0,
+   "print usage information"},
   {"-help",   argFlag,     &printHelp,     0,
    "print usage information"},
+  {"--help",  argFlag,     &printHelp,     0,
+   "print usage information"},
   {"-p",      argFlag,     &printHtml,     0,
    "exchange .pdf links by .html"}, 
   {"-c",      argFlag,     &complexMode,          0,
diff --git a/utils/pdftoppm.1 b/utils/pdftoppm.1
index 2321d6d..7f88b6d 100644
--- a/utils/pdftoppm.1
+++ b/utils/pdftoppm.1
@@ -102,6 +102,18 @@
 Enable or disable FreeType (a TrueType / Type 1 font rasterizer).
 This defaults to "yes".
 .TP
+.BI \-thinlinemode " none | solid | shape"
+Specifies the thin line mode. This defaults to "none".
+.TP
+"solid": 
+adjust lines with a width less than one pixel to pixel boundary 
+and paint it with a width of one pixel.
+.TP
+"shape": 
+adjust lines with a width less than one pixel to pixel boundary 
+and paint it with a width of one pixel but with a shape in proportion
+to its width.
+.TP
 .BI \-aa " yes | no"
 Enable or disable font anti-aliasing.  This defaults to "yes".
 .TP
diff --git a/utils/pdftoppm.cc b/utils/pdftoppm.cc
index e5138d1..962860b 100644
--- a/utils/pdftoppm.cc
+++ b/utils/pdftoppm.cc
@@ -95,6 +95,8 @@
 static char ownerPassword[33] = "";
 static char userPassword[33] = "";
 static char TiffCompressionStr[16] = "";
+static char thinLineModeStr[8] = "";
+static SplashThinLineMode thinLineMode = splashThinLineDefault;
 #ifdef UTILS_USE_PTHREADS
 static int numberOfJobs = 1;
 #endif // UTILS_USE_PTHREADS
@@ -170,6 +172,8 @@
   {"-freetype",   argString,      enableFreeTypeStr, sizeof(enableFreeTypeStr),
    "enable FreeType font rasterizer: yes, no"},
 #endif
+  {"-thinlinemode", argString, thinLineModeStr, sizeof(thinLineModeStr),
+   "set thin line mode: none, solid, shape. Default: none"},
   
   {"-aa",         argString,      antialiasStr,   sizeof(antialiasStr),
    "enable font anti-aliasing: yes, no"},
@@ -284,7 +288,7 @@
 #if SPLASH_CMYK
         			    (jpegcmyk || overprint) ? splashModeDeviceN8 :
 #endif
-		              splashModeRGB8, 4, gFalse, *pageJob.paperColor);
+		              splashModeRGB8, 4, gFalse, *pageJob.paperColor, gTrue, gTrue, thinLineMode);
     splashOut->startDoc(pageJob.doc);
     
     savePageSlice(pageJob.doc, splashOut, pageJob.pg, x, y, w, h, pageJob.pg_w, pageJob.pg_h, pageJob.ppmFile);
@@ -369,6 +373,15 @@
       fprintf(stderr, "Bad '-aaVector' value on command line\n");
     }
   }
+  if (thinLineModeStr[0]) {
+    if (strcmp(thinLineModeStr, "solid") == 0) {
+      thinLineMode = splashThinLineSolid;
+    } else if (strcmp(thinLineModeStr, "shape") == 0) {
+      thinLineMode = splashThinLineShape;
+    } else if (strcmp(thinLineModeStr, "none") != 0) {
+      fprintf(stderr, "Bad '-thinlinemode' value on command line\n");
+    }
+  }
   if (quiet) {
     globalParams->setErrQuiet(quiet);
   }
@@ -451,7 +464,7 @@
 				    (jpegcmyk || overprint) ? splashModeDeviceN8 :
 #endif
 				             splashModeRGB8, 4,
-				  gFalse, paperColor);
+				  gFalse, paperColor, gTrue, gTrue, thinLineMode);
   splashOut->startDoc(doc);
   
 #endif // UTILS_USE_PTHREADS
diff --git a/utils/pdftotext.cc b/utils/pdftotext.cc
index 7ab6cdd..15c741d 100644
--- a/utils/pdftotext.cc
+++ b/utils/pdftotext.cc
@@ -394,9 +394,9 @@
       }
 
     } else {
-    delete textOut;
-    exitCode = 2;
-    goto err3;
+      delete textOut;
+      exitCode = 2;
+      goto err3;
     }
   }
   delete textOut;
diff --git a/utils/pdfunite.cc b/utils/pdfunite.cc
index 59b6a8f..5f67c0e 100644
--- a/utils/pdfunite.cc
+++ b/utils/pdfunite.cc
@@ -4,7 +4,7 @@
 //
 // This file is licensed under the GPLv2 or later
 //
-// Copyright (C) 2011, 2012 Thomas Freitag <Thomas.Freitag@alfa.de>
+// Copyright (C) 2011-2013 Thomas Freitag <Thomas.Freitag@alfa.de>
 // Copyright (C) 2012 Arseny Solokha <asolokha@gmx.com>
 // Copyright (C) 2012 Fabio D'Urso <fabiodurso@hotmail.it>
 // Copyright (C) 2012 Albert Astals Cid <aacid@kde.org>
@@ -60,7 +60,7 @@
 
   exitCode = 99;
   const GBool ok = parseArgs (argDesc, &argc, argv);
-  if (!ok || argc <= 3 || printVersion || printHelp) {
+  if (!ok || argc < 3 || printVersion || printHelp) {
     fprintf(stderr, "pdfunite version %s\n", PACKAGE_VERSION);
     fprintf(stderr, "%s\n", popplerCopyright);
     fprintf(stderr, "%s\n", xpdfCopyright);
@@ -119,9 +119,15 @@
       Ref *refPage = docs[i]->getCatalog()->getPageRef(j);
       Object page;
       docs[i]->getXRef()->fetch(refPage->num, refPage->gen, &page);
+      Dict *pageDict = page.getDict();
+      Dict *resDict = docs[i]->getCatalog()->getPage(j)->getResourceDict();
+      if (resDict) {
+        Object *newResource = new Object();
+        newResource->initDict(resDict);
+        pageDict->set("Resources", newResource);
+      }
       pages.push_back(page);
       offsets.push_back(numOffset);
-      Dict *pageDict = page.getDict();
       docs[i]->markPageObjects(pageDict, yRef, countRef, numOffset);
     }
     objectsCount += docs[i]->writePageObjects(outStr, yRef, numOffset, gTrue);