Code

Initial commit of native poppler-based PDF import.
authormiklosh <miklosh@users.sourceforge.net>
Thu, 12 Jul 2007 17:05:34 +0000 (17:05 +0000)
committermiklosh <miklosh@users.sourceforge.net>
Thu, 12 Jul 2007 17:05:34 +0000 (17:05 +0000)
src/Makefile.am
src/extension/init.cpp
src/extension/internal/Makefile_insert
src/extension/internal/pdfinput/pdf-input.cpp [new file with mode: 0644]
src/extension/internal/pdfinput/pdf-input.h [new file with mode: 0644]
src/extension/internal/pdfinput/pdf-parser.cpp [new file with mode: 0644]
src/extension/internal/pdfinput/pdf-parser.h [new file with mode: 0644]
src/extension/internal/pdfinput/svg-builder.cpp [new file with mode: 0644]
src/extension/internal/pdfinput/svg-builder.h [new file with mode: 0644]

index 0f8cd9e4b796807f63916cd85ab690f973dd86f1..9f33ae5f4e6ce6ca8353d37e90a7f61b5bb44d89 100644 (file)
@@ -21,6 +21,8 @@ INCLUDES =    \
        $(INKBOARD_CFLAGS) \
        $(XFT_CFLAGS)   \
        $(GTK_UNIX_PRINT_CFLAGS)        \
+       $(POPPLER_CFLAGS)       \
+       $(POPPLER_GLIB_CFLAGS)  \
        -DPOTRACE=\"potrace\"   \
        $(INKSCAPE_CFLAGS) \
        -I$(top_srcdir)/cxxtest
index a6471be257f9d8348769cefcfcd65257b254cf91..9a11097827bec8a0b6c8119b5c3323cb4b1da6c3 100644 (file)
@@ -43,6 +43,9 @@
 # include "internal/cairo-png-out.h"
 # include "internal/cairo-ps-out.h"
 #endif
+#ifdef HAVE_POPPLER
+# include "internal/pdfinput/pdf-input.h"
+#endif
 #ifdef HAVE_POPPLER_GLIB
 # include "internal/pdf-input-cairo.h"
 #endif
@@ -127,6 +130,9 @@ init()
     }
     Internal::CairoPsOutput::init();
 #endif
+#ifdef HAVE_POPPLER
+//    Internal::PdfInput::init();
+#endif
 #ifdef HAVE_POPPLER_GLIB
     Internal::PdfInputCairo::init();
 #endif
index 8886254dad8973d0f6248e51891022b014317e43..f615e02fd3a82fcccf47eda22fcbcab0b9b1c07b 100644 (file)
@@ -47,6 +47,12 @@ extension_internal_libinternal_a_SOURCES =   \
        extension/internal/eps-out.cpp  \
        extension/internal/gdkpixbuf-input.h    \
        extension/internal/gdkpixbuf-input.cpp  \
+       extension/internal/pdfinput/svg-builder.h \
+       extension/internal/pdfinput/svg-builder.cpp \
+       extension/internal/pdfinput/pdf-parser.h \
+       extension/internal/pdfinput/pdf-parser.cpp \
+       extension/internal/pdfinput/pdf-input.h \
+       extension/internal/pdfinput/pdf-input.cpp       \
        extension/internal/pov-out.cpp  \
        extension/internal/pov-out.h    \
        extension/internal/odf.cpp      \
diff --git a/src/extension/internal/pdfinput/pdf-input.cpp b/src/extension/internal/pdfinput/pdf-input.cpp
new file mode 100644 (file)
index 0000000..b9e6160
--- /dev/null
@@ -0,0 +1,139 @@
+ /** \file
+ * Native PDF import using libpoppler.
+ * 
+ * Authors:
+ *   miklos erdelyi
+ *
+ * Copyright (C) 2007 Authors
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#ifdef HAVE_POPPLER
+
+#include "goo/GooString.h"
+#include "ErrorCodes.h"
+#include "GlobalParams.h"
+#include "PDFDoc.h"
+#include "Page.h"
+#include "Catalog.h"
+
+#include "pdf-input.h"
+#include "extension/system.h"
+#include "extension/input.h"
+#include "svg-builder.h"
+#include "pdf-parser.h"
+
+#include "document-private.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+
+/**
+ * Parses the first page of the given PDF document using PdfParser.
+ */
+SPDocument *
+PdfInput::open(::Inkscape::Extension::Input * mod, const gchar * uri) {
+
+    // Initialize the globalParams variable for poppler
+    if (!globalParams) {
+        globalParams = new GlobalParams();
+        g_message("Created globalParams");
+    }
+    GooString *filename_goo = new GooString(uri);
+    PDFDoc *pdf_doc = new PDFDoc(filename_goo, NULL, NULL, NULL);   // TODO: Could ask for password
+    if (!pdf_doc->isOk()) {
+        int error = pdf_doc->getErrorCode();
+        delete pdf_doc;
+        if (error == errEncrypted) {
+            g_message("Document is encrypted.");
+        } else {
+            g_message("Failed to load document from data (error %d)", error);
+        }
+        return NULL;
+    }
+
+    // Get needed page
+    int page_num = 1;
+    Catalog *catalog = pdf_doc->getCatalog();
+    Page *page = catalog->getPage(page_num);
+
+    double width, height;
+    int rotate = page->getRotate();
+    if (rotate == 90 || rotate == 270)  {
+        width = page->getCropHeight();
+        height = page->getCropWidth();
+    } else {
+        width = page->getCropWidth();
+        height = page->getCropHeight();
+    }
+    
+    SPDocument *doc = sp_document_new(NULL, TRUE, TRUE);
+    bool saved = sp_document_get_undo_sensitive(doc);
+    sp_document_set_undo_sensitive(doc, false); // No need to undo in this temporary document
+
+    // Create builder and parser
+    SvgBuilder *builder = new SvgBuilder(doc);
+    PdfParser *pdf_parser = new PdfParser(pdf_doc->getXRef(), builder, page_num-1,
+                                          page->getResourceDict(), page->getCropBox());
+
+    // Parse the document structure
+    Object obj;
+    page->getContents(&obj);
+    if (!obj.isNull()) {
+        pdf_parser->saveState();
+        pdf_parser->parse(&obj);
+        pdf_parser->restoreState();
+    }
+    
+    // Cleanup
+    obj.free();
+    delete pdf_parser;
+    delete builder;
+
+    // Restore undo
+    sp_document_set_undo_sensitive(doc, saved);
+
+    return doc;
+}
+
+#include "../clear-n_.h"
+
+void
+PdfInput::init(void) {
+    Inkscape::Extension::Extension * ext;
+
+    ext = Inkscape::Extension::build_from_mem(
+        "<inkscape-extension>\n"
+            "<name>PDF Input</name>\n"
+            "<id>org.inkscape.input.pdf</id>\n"
+            "<input>\n"
+                "<extension>.pdf</extension>\n"
+                "<mimetype>application/pdf</mimetype>\n"
+                "<filetypename>Adobe PDF (*.pdf) [native]</filetypename>\n"
+                "<filetypetooltip>PDF Document</filetypetooltip>\n"
+            "</input>\n"
+        "</inkscape-extension>", new PdfInput());
+} // init
+
+} } }  /* namespace Inkscape, Extension, Implementation */
+
+#endif /* HAVE_POPPLER */
+
+/*
+  Local Variables:
+  mode:c++
+  c-file-style:"stroustrup"
+  c-file-offsets:((innamespace . 0)(inline-open . 0))
+  indent-tabs-mode:nil
+  fill-column:99
+  End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/extension/internal/pdfinput/pdf-input.h b/src/extension/internal/pdfinput/pdf-input.h
new file mode 100644 (file)
index 0000000..abbfc83
--- /dev/null
@@ -0,0 +1,51 @@
+#ifndef __EXTENSION_INTERNAL_PDFINPUT_H__
+#define __EXTENSION_INTERNAL_PDFINPUT_H__
+
+ /** \file
+ * PDF import using libpoppler.
+ *
+ * Authors:
+ *   miklos erdelyi
+ *
+ * Copyright (C) 2007 Authors
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#ifdef HAVE_POPPLER
+
+#include "../../implementation/implementation.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+
+class PdfInput: public Inkscape::Extension::Implementation::Implementation {
+    PdfInput () { };
+public:
+    SPDocument *open( Inkscape::Extension::Input *mod,
+                                const gchar *uri );
+    static void         init( void );
+
+};
+
+} } }  /* namespace Inkscape, Extension, Implementation */
+
+#endif /* HAVE_POPPLER */
+
+#endif /* __EXTENSION_INTERNAL_PDFINPUT_H__ */
+
+/*
+  Local Variables:
+  mode:c++
+  c-file-style:"stroustrup"
+  c-file-offsets:((innamespace . 0)(inline-open . 0))
+  indent-tabs-mode:nil
+  fill-column:99
+  End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/extension/internal/pdfinput/pdf-parser.cpp b/src/extension/internal/pdfinput/pdf-parser.cpp
new file mode 100644 (file)
index 0000000..b8804ac
--- /dev/null
@@ -0,0 +1,3031 @@
+
+ /** \file
+ * PDF parsing using libpoppler
+ *
+ * Derived from poppler's Gfx.cc
+ *
+ * Copyright 1996-2003 Glyph & Cog, LLC
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#ifdef HAVE_POPPLER
+
+#ifdef USE_GCC_PRAGMAS
+#pragma implementation
+#endif
+
+extern "C" {
+        
+#include <stdlib.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <string.h>
+#include <math.h>
+
+}
+
+#include "svg-builder.h"
+#include "Gfx.h"
+#include "pdf-parser.h"
+#include "unit-constants.h"
+
+#include "goo/gmem.h"
+#include "goo/GooTimer.h"
+#include "goo/GooHash.h"
+#include "GlobalParams.h"
+#include "CharTypes.h"
+#include "Object.h"
+#include "Array.h"
+#include "Dict.h"
+#include "Stream.h"
+#include "Lexer.h"
+#include "Parser.h"
+#include "GfxFont.h"
+#include "GfxState.h"
+#include "OutputDev.h"
+#include "Page.h"
+#include "Annot.h"
+#include "Error.h"
+
+// the MSVC math.h doesn't define this
+#ifndef M_PI
+#define M_PI 3.14159265358979323846
+#endif
+
+//------------------------------------------------------------------------
+// constants
+//------------------------------------------------------------------------
+
+// Max recursive depth for a function shading fill.
+#define functionMaxDepth 6
+
+// Max delta allowed in any color component for a function shading fill.
+#define functionColorDelta (dblToCol(1 / 256.0))
+
+// Max number of splits along the t axis for an axial shading fill.
+#define axialMaxSplits 256
+
+// Max delta allowed in any color component for an axial shading fill.
+#define axialColorDelta (dblToCol(1 / 256.0))
+
+// Max number of splits along the t axis for a radial shading fill.
+#define radialMaxSplits 256
+
+// Max delta allowed in any color component for a radial shading fill.
+#define radialColorDelta (dblToCol(1 / 256.0))
+
+// Max recursive depth for a Gouraud triangle shading fill.
+#define gouraudMaxDepth 6
+
+// Max delta allowed in any color component for a Gouraud triangle
+// shading fill.
+#define gouraudColorDelta (dblToCol(1 / 256.0))
+
+// Max recursive depth for a patch mesh shading fill.
+#define patchMaxDepth 6
+
+// Max delta allowed in any color component for a patch mesh shading
+// fill.
+#define patchColorDelta (dblToCol(1 / 256.0))
+
+// Max number of operators kept in the history list.
+#define maxOperatorHistoryDepth 5
+
+//------------------------------------------------------------------------
+// Operator table
+//------------------------------------------------------------------------
+
+#ifdef WIN32 // this works around a bug in the VC7 compiler
+#  pragma optimize("",off)
+#endif
+
+PdfOperator PdfParser::opTab[] = {
+  {"\"",  3, {tchkNum,    tchkNum,    tchkString},
+          &PdfParser::opMoveSetShowText},
+  {"'",   1, {tchkString},
+          &PdfParser::opMoveShowText},
+  {"B",   0, {tchkNone},
+          &PdfParser::opFillStroke},
+  {"B*",  0, {tchkNone},
+          &PdfParser::opEOFillStroke},
+  {"BDC", 2, {tchkName,   tchkProps},
+          &PdfParser::opBeginMarkedContent},
+  {"BI",  0, {tchkNone},
+          &PdfParser::opBeginImage},
+  {"BMC", 1, {tchkName},
+          &PdfParser::opBeginMarkedContent},
+  {"BT",  0, {tchkNone},
+          &PdfParser::opBeginText},
+  {"BX",  0, {tchkNone},
+          &PdfParser::opBeginIgnoreUndef},
+  {"CS",  1, {tchkName},
+          &PdfParser::opSetStrokeColorSpace},
+  {"DP",  2, {tchkName,   tchkProps},
+          &PdfParser::opMarkPoint},
+  {"Do",  1, {tchkName},
+          &PdfParser::opXObject},
+  {"EI",  0, {tchkNone},
+          &PdfParser::opEndImage},
+  {"EMC", 0, {tchkNone},
+          &PdfParser::opEndMarkedContent},
+  {"ET",  0, {tchkNone},
+          &PdfParser::opEndText},
+  {"EX",  0, {tchkNone},
+          &PdfParser::opEndIgnoreUndef},
+  {"F",   0, {tchkNone},
+          &PdfParser::opFill},
+  {"G",   1, {tchkNum},
+          &PdfParser::opSetStrokeGray},
+  {"ID",  0, {tchkNone},
+          &PdfParser::opImageData},
+  {"J",   1, {tchkInt},
+          &PdfParser::opSetLineCap},
+  {"K",   4, {tchkNum,    tchkNum,    tchkNum,    tchkNum},
+          &PdfParser::opSetStrokeCMYKColor},
+  {"M",   1, {tchkNum},
+          &PdfParser::opSetMiterLimit},
+  {"MP",  1, {tchkName},
+          &PdfParser::opMarkPoint},
+  {"Q",   0, {tchkNone},
+          &PdfParser::opRestore},
+  {"RG",  3, {tchkNum,    tchkNum,    tchkNum},
+          &PdfParser::opSetStrokeRGBColor},
+  {"S",   0, {tchkNone},
+          &PdfParser::opStroke},
+  {"SC",  -4, {tchkNum,   tchkNum,    tchkNum,    tchkNum},
+          &PdfParser::opSetStrokeColor},
+  {"SCN", -33, {tchkSCN,   tchkSCN,    tchkSCN,    tchkSCN,
+               tchkSCN,   tchkSCN,    tchkSCN,    tchkSCN,
+               tchkSCN,   tchkSCN,    tchkSCN,    tchkSCN,
+               tchkSCN,   tchkSCN,    tchkSCN,    tchkSCN,
+               tchkSCN,   tchkSCN,    tchkSCN,    tchkSCN,
+               tchkSCN,   tchkSCN,    tchkSCN,    tchkSCN,
+               tchkSCN,   tchkSCN,    tchkSCN,    tchkSCN,
+               tchkSCN,   tchkSCN,    tchkSCN,    tchkSCN,
+               tchkSCN},
+          &PdfParser::opSetStrokeColorN},
+  {"T*",  0, {tchkNone},
+          &PdfParser::opTextNextLine},
+  {"TD",  2, {tchkNum,    tchkNum},
+          &PdfParser::opTextMoveSet},
+  {"TJ",  1, {tchkArray},
+          &PdfParser::opShowSpaceText},
+  {"TL",  1, {tchkNum},
+          &PdfParser::opSetTextLeading},
+  {"Tc",  1, {tchkNum},
+          &PdfParser::opSetCharSpacing},
+  {"Td",  2, {tchkNum,    tchkNum},
+          &PdfParser::opTextMove},
+  {"Tf",  2, {tchkName,   tchkNum},
+          &PdfParser::opSetFont},
+  {"Tj",  1, {tchkString},
+          &PdfParser::opShowText},
+  {"Tm",  6, {tchkNum,    tchkNum,    tchkNum,    tchkNum,
+             tchkNum,    tchkNum},
+          &PdfParser::opSetTextMatrix},
+  {"Tr",  1, {tchkInt},
+          &PdfParser::opSetTextRender},
+  {"Ts",  1, {tchkNum},
+          &PdfParser::opSetTextRise},
+  {"Tw",  1, {tchkNum},
+          &PdfParser::opSetWordSpacing},
+  {"Tz",  1, {tchkNum},
+          &PdfParser::opSetHorizScaling},
+  {"W",   0, {tchkNone},
+          &PdfParser::opClip},
+  {"W*",  0, {tchkNone},
+          &PdfParser::opEOClip},
+  {"b",   0, {tchkNone},
+          &PdfParser::opCloseFillStroke},
+  {"b*",  0, {tchkNone},
+          &PdfParser::opCloseEOFillStroke},
+  {"c",   6, {tchkNum,    tchkNum,    tchkNum,    tchkNum,
+             tchkNum,    tchkNum},
+          &PdfParser::opCurveTo},
+  {"cm",  6, {tchkNum,    tchkNum,    tchkNum,    tchkNum,
+             tchkNum,    tchkNum},
+          &PdfParser::opConcat},
+  {"cs",  1, {tchkName},
+          &PdfParser::opSetFillColorSpace},
+  {"d",   2, {tchkArray,  tchkNum},
+          &PdfParser::opSetDash},
+  {"d0",  2, {tchkNum,    tchkNum},
+          &PdfParser::opSetCharWidth},
+  {"d1",  6, {tchkNum,    tchkNum,    tchkNum,    tchkNum,
+             tchkNum,    tchkNum},
+          &PdfParser::opSetCacheDevice},
+  {"f",   0, {tchkNone},
+          &PdfParser::opFill},
+  {"f*",  0, {tchkNone},
+          &PdfParser::opEOFill},
+  {"g",   1, {tchkNum},
+          &PdfParser::opSetFillGray},
+  {"gs",  1, {tchkName},
+          &PdfParser::opSetExtGState},
+  {"h",   0, {tchkNone},
+          &PdfParser::opClosePath},
+  {"i",   1, {tchkNum},
+          &PdfParser::opSetFlat},
+  {"j",   1, {tchkInt},
+          &PdfParser::opSetLineJoin},
+  {"k",   4, {tchkNum,    tchkNum,    tchkNum,    tchkNum},
+          &PdfParser::opSetFillCMYKColor},
+  {"l",   2, {tchkNum,    tchkNum},
+          &PdfParser::opLineTo},
+  {"m",   2, {tchkNum,    tchkNum},
+          &PdfParser::opMoveTo},
+  {"n",   0, {tchkNone},
+          &PdfParser::opEndPath},
+  {"q",   0, {tchkNone},
+          &PdfParser::opSave},
+  {"re",  4, {tchkNum,    tchkNum,    tchkNum,    tchkNum},
+          &PdfParser::opRectangle},
+  {"rg",  3, {tchkNum,    tchkNum,    tchkNum},
+          &PdfParser::opSetFillRGBColor},
+  {"ri",  1, {tchkName},
+          &PdfParser::opSetRenderingIntent},
+  {"s",   0, {tchkNone},
+          &PdfParser::opCloseStroke},
+  {"sc",  -4, {tchkNum,   tchkNum,    tchkNum,    tchkNum},
+          &PdfParser::opSetFillColor},
+  {"scn", -33, {tchkSCN,   tchkSCN,    tchkSCN,    tchkSCN,
+               tchkSCN,   tchkSCN,    tchkSCN,    tchkSCN,
+               tchkSCN,   tchkSCN,    tchkSCN,    tchkSCN,
+               tchkSCN,   tchkSCN,    tchkSCN,    tchkSCN,
+               tchkSCN,   tchkSCN,    tchkSCN,    tchkSCN,
+               tchkSCN,   tchkSCN,    tchkSCN,    tchkSCN,
+               tchkSCN,   tchkSCN,    tchkSCN,    tchkSCN,
+               tchkSCN,   tchkSCN,    tchkSCN,    tchkSCN,
+               tchkSCN},
+          &PdfParser::opSetFillColorN},
+  {"sh",  1, {tchkName},
+          &PdfParser::opShFill},
+  {"v",   4, {tchkNum,    tchkNum,    tchkNum,    tchkNum},
+          &PdfParser::opCurveTo1},
+  {"w",   1, {tchkNum},
+          &PdfParser::opSetLineWidth},
+  {"y",   4, {tchkNum,    tchkNum,    tchkNum,    tchkNum},
+          &PdfParser::opCurveTo2}
+};
+
+#ifdef WIN32 // this works around a bug in the VC7 compiler
+#  pragma optimize("",on)
+#endif
+
+#define numOps (sizeof(opTab) / sizeof(PdfOperator))
+
+//------------------------------------------------------------------------
+// PdfParser
+//------------------------------------------------------------------------
+
+PdfParser::PdfParser(XRef *xrefA, Inkscape::Extension::Internal::SvgBuilder *builderA,
+                     int pageNum, Dict *resDict, PDFRectangle *cropBox,
+                     GBool (*abortCheckCbkA)(void *data),
+                     void *abortCheckCbkDataA) {
+  int i;
+
+  xref = xrefA;
+  subPage = gFalse;
+  printCommands = false;
+
+  // start the resource stack
+  res = new GfxResources(xref, resDict, NULL);
+
+  // initialize
+  state = new GfxState(72.0, 72.0, cropBox, 0, gFalse);
+  fontChanged = gFalse;
+  clip = clipNone;
+  ignoreUndef = 0;
+  operatorHistory = NULL;
+  builder = builderA;
+  builder->setDocumentSize(state->getPageWidth()*PX_PER_PT,
+                           state->getPageHeight()*PX_PER_PT);
+  
+  for (i = 0; i < 6; ++i) {
+    baseMatrix[i] = state->getCTM()[i];
+  }
+  // scale by PX_PER_PT and flip by the y axis
+  builder->setTransform(PX_PER_PT, 0.0, 0.0, -PX_PER_PT, 0.0,
+                        state->getPageHeight()*PX_PER_PT);
+  formDepth = 0;
+  abortCheckCbk = abortCheckCbkA;
+  abortCheckCbkData = abortCheckCbkDataA;
+
+  // set crop box
+  if (cropBox) {
+    if (printCommands)
+        printf("cropBox: %f %f %f %f\n", cropBox->x1, cropBox->y1, cropBox->x2, cropBox->y2);
+    // do not clip if it's not needed
+    if (cropBox->x1 != 0.0 || cropBox->y1 != 0.0 ||
+        cropBox->x2 != state->getPageWidth() || cropBox->y2 != state->getPageHeight()) {
+        
+        state->moveTo(cropBox->x1, cropBox->y1);
+        state->lineTo(cropBox->x2, cropBox->y1);
+        state->lineTo(cropBox->x2, cropBox->y2);
+        state->lineTo(cropBox->x1, cropBox->y2);
+        state->closePath();
+        state->clip();
+        builder->setClipPath(state);
+        state->clearPath();
+    }
+  }
+  pushOperator("startPage");
+}
+
+PdfParser::~PdfParser() {
+  while (state->hasSaves()) {
+    restoreState();
+  }
+  if (!subPage) {
+    //out->endPage();
+  }
+  while (res) {
+    popResources();
+  }
+  if (state) {
+    delete state;
+  }
+}
+
+void PdfParser::parse(Object *obj, GBool topLevel) {
+  Object obj2;
+  int i;
+
+  if (obj->isArray()) {
+    for (i = 0; i < obj->arrayGetLength(); ++i) {
+      obj->arrayGet(i, &obj2);
+      if (!obj2.isStream()) {
+       error(-1, "Weird page contents");
+       obj2.free();
+       return;
+      }
+      obj2.free();
+    }
+  } else if (!obj->isStream()) {
+    error(-1, "Weird page contents");
+    return;
+  }
+  parser = new Parser(xref, new Lexer(xref, obj), gFalse);
+  go(topLevel);
+  delete parser;
+  parser = NULL;
+}
+
+void PdfParser::go(GBool topLevel) {
+  Object obj;
+  Object args[maxArgs];
+  int numArgs, i;
+  int lastAbortCheck;
+
+  // scan a sequence of objects
+  updateLevel = lastAbortCheck = 0;
+  numArgs = 0;
+  parser->getObj(&obj);
+  while (!obj.isEOF()) {
+
+    // got a command - execute it
+    if (obj.isCmd()) {
+      if (printCommands) {
+       obj.print(stdout);
+       for (i = 0; i < numArgs; ++i) {
+         printf(" ");
+         args[i].print(stdout);
+       }
+       printf("\n");
+       fflush(stdout);
+      }
+
+      // Run the operation
+      execOp(&obj, args, numArgs);
+
+      obj.free();
+      for (i = 0; i < numArgs; ++i)
+       args[i].free();
+      numArgs = 0;
+
+      // check for an abort
+      if (abortCheckCbk) {
+       if (updateLevel - lastAbortCheck > 10) {
+         if ((*abortCheckCbk)(abortCheckCbkData)) {
+           break;
+         }
+         lastAbortCheck = updateLevel;
+       }
+      }
+
+    // got an argument - save it
+    } else if (numArgs < maxArgs) {
+      args[numArgs++] = obj;
+
+    // too many arguments - something is wrong
+    } else {
+      error(getPos(), "Too many args in content stream");
+      if (printCommands) {
+       printf("throwing away arg: ");
+       obj.print(stdout);
+       printf("\n");
+       fflush(stdout);
+      }
+      obj.free();
+    }
+
+    // grab the next object
+    parser->getObj(&obj);
+  }
+  obj.free();
+
+  // args at end with no command
+  if (numArgs > 0) {
+    error(getPos(), "Leftover args in content stream");
+    if (printCommands) {
+      printf("%d leftovers:", numArgs);
+      for (i = 0; i < numArgs; ++i) {
+       printf(" ");
+       args[i].print(stdout);
+      }
+      printf("\n");
+      fflush(stdout);
+    }
+    for (i = 0; i < numArgs; ++i)
+      args[i].free();
+  }
+}
+
+void PdfParser::pushOperator(const char *name) {
+    OpHistoryEntry *newEntry = new OpHistoryEntry;
+    newEntry->name = name;
+    newEntry->state = NULL;
+    newEntry->depth = (operatorHistory != NULL ? (operatorHistory->depth+1) : 0);
+    newEntry->next = operatorHistory;
+    operatorHistory = newEntry;
+
+    // Truncate list if needed
+    if (operatorHistory->depth > maxOperatorHistoryDepth) {
+        OpHistoryEntry *curr = operatorHistory;
+        OpHistoryEntry *prev = NULL;
+        while (curr && curr->next != NULL) {
+            curr->depth--;
+            prev = curr;
+            curr = curr->next;
+        }
+        if (prev) {
+            if (curr->state != NULL)
+                delete curr->state;
+            delete curr;
+            prev->next = NULL;
+        }
+    }
+}
+
+const char *PdfParser::getPreviousOperator() {
+    OpHistoryEntry *prev = NULL;
+    if (operatorHistory != NULL) {
+        prev = operatorHistory->next;
+    }
+    if (prev != NULL) {
+        return prev->name;
+    } else {
+        return "";
+    }
+}
+
+void PdfParser::execOp(Object *cmd, Object args[], int numArgs) {
+  PdfOperator *op;
+  char *name;
+  Object *argPtr;
+  int i;
+
+  // find operator
+  name = cmd->getCmd();
+  if (!(op = findOp(name))) {
+    if (ignoreUndef == 0)
+      error(getPos(), "Unknown operator '%s'", name);
+    return;
+  }
+
+  // type check args
+  argPtr = args;
+  if (op->numArgs >= 0) {
+    if (numArgs < op->numArgs) {
+      error(getPos(), "Too few (%d) args to '%s' operator", numArgs, name);
+      return;
+    }
+    if (numArgs > op->numArgs) {
+#if 0
+      error(getPos(), "Too many (%d) args to '%s' operator", numArgs, name);
+#endif
+      argPtr += numArgs - op->numArgs;
+      numArgs = op->numArgs;
+    }
+  } else {
+    if (numArgs > -op->numArgs) {
+      error(getPos(), "Too many (%d) args to '%s' operator",
+           numArgs, name);
+      return;
+    }
+  }
+  for (i = 0; i < numArgs; ++i) {
+    if (!checkArg(&argPtr[i], op->tchk[i])) {
+      error(getPos(), "Arg #%d to '%s' operator is wrong type (%s)",
+           i, name, argPtr[i].getTypeName());
+      return;
+    }
+  }
+
+  // add to history
+  pushOperator((char*)&op->name);
+
+  // do it
+  (this->*op->func)(argPtr, numArgs);
+}
+
+PdfOperator *PdfParser::findOp(char *name) {
+  int a, b, m, cmp;
+
+  a = -1;
+  b = numOps;
+  // invariant: opTab[a] < name < opTab[b]
+  while (b - a > 1) {
+    m = (a + b) / 2;
+    cmp = strcmp(opTab[m].name, name);
+    if (cmp < 0)
+      a = m;
+    else if (cmp > 0)
+      b = m;
+    else
+      a = b = m;
+  }
+  if (cmp != 0)
+    return NULL;
+  return &opTab[a];
+}
+
+GBool PdfParser::checkArg(Object *arg, TchkType type) {
+  switch (type) {
+  case tchkBool:   return arg->isBool();
+  case tchkInt:    return arg->isInt();
+  case tchkNum:    return arg->isNum();
+  case tchkString: return arg->isString();
+  case tchkName:   return arg->isName();
+  case tchkArray:  return arg->isArray();
+  case tchkProps:  return arg->isDict() || arg->isName();
+  case tchkSCN:    return arg->isNum() || arg->isName();
+  case tchkNone:   return gFalse;
+  }
+  return gFalse;
+}
+
+int PdfParser::getPos() {
+  return parser ? parser->getPos() : -1;
+}
+
+//------------------------------------------------------------------------
+// graphics state operators
+//------------------------------------------------------------------------
+
+void PdfParser::opSave(Object args[], int numArgs) {
+  saveState();
+}
+
+void PdfParser::opRestore(Object args[], int numArgs) {
+  restoreState();
+}
+
+void PdfParser::opConcat(Object args[], int numArgs) {
+  state->concatCTM(args[0].getNum(), args[1].getNum(),
+                  args[2].getNum(), args[3].getNum(),
+                  args[4].getNum(), args[5].getNum());
+  const char *prevOp = getPreviousOperator();
+  double a0 = args[0].getNum();
+  double a1 = args[1].getNum();
+  double a2 = args[2].getNum();
+  double a3 = args[3].getNum();
+  double a4 = args[4].getNum();
+  double a5 = args[5].getNum();
+  if (!strcmp(prevOp, "q")) {
+      builder->setTransform(a0, a1, a2, a3, a4, a5);
+  } else if (!strcmp(prevOp, "startPage")) {
+      // multiply it with the current baseMatrix
+      double c0 = baseMatrix[0]*a0 + baseMatrix[1]*a2;
+      double c1 = baseMatrix[0]*a1 + baseMatrix[1]*a3;
+      double c2 = baseMatrix[2]*a0 + baseMatrix[3]*a2;
+      double c3 = baseMatrix[2]*a1 + baseMatrix[3]*a3;
+      double c4 = baseMatrix[4]*a0 + baseMatrix[5]*a2 + a4;
+      double c5 = baseMatrix[4]*a1 + baseMatrix[5]*a3 + a5;
+      builder->setTransform(c0, c1, c2, c3, c4, c5);
+  } else {
+      builder->pushGroup();
+      builder->setTransform(a0, a1, a2, a3, a4, a5);
+  }
+  fontChanged = gTrue;
+}
+
+void PdfParser::opSetDash(Object args[], int numArgs) {
+  Array *a;
+  int length;
+  Object obj;
+  double *dash;
+  int i;
+
+  a = args[0].getArray();
+  length = a->getLength();
+  if (length == 0) {
+    dash = NULL;
+  } else {
+    dash = (double *)gmallocn(length, sizeof(double));
+    for (i = 0; i < length; ++i) {
+      dash[i] = a->get(i, &obj)->getNum();
+      obj.free();
+    }
+  }
+  state->setLineDash(dash, length, args[1].getNum());
+}
+
+void PdfParser::opSetFlat(Object args[], int numArgs) {
+  state->setFlatness((int)args[0].getNum());
+}
+
+void PdfParser::opSetLineJoin(Object args[], int numArgs) {
+  state->setLineJoin(args[0].getInt());
+}
+
+void PdfParser::opSetLineCap(Object args[], int numArgs) {
+  state->setLineCap(args[0].getInt());
+}
+
+void PdfParser::opSetMiterLimit(Object args[], int numArgs) {
+  state->setMiterLimit(args[0].getNum());
+}
+
+void PdfParser::opSetLineWidth(Object args[], int numArgs) {
+  state->setLineWidth(args[0].getNum());
+}
+
+void PdfParser::opSetExtGState(Object args[], int numArgs) {
+  Object obj1, obj2, obj3, obj4, obj5;
+  GfxBlendMode mode;
+  GBool haveFillOP;
+  Function *funcs[4];
+  GfxColor backdropColor;
+  GBool haveBackdropColor;
+  GfxColorSpace *blendingColorSpace;
+  GBool alpha, isolated, knockout;
+  int i;
+
+  if (!res->lookupGState(args[0].getName(), &obj1)) {
+    return;
+  }
+  if (!obj1.isDict()) {
+    error(getPos(), "ExtGState '%s' is wrong type", args[0].getName());
+    obj1.free();
+    return;
+  }
+  if (printCommands) {
+    printf("  gfx state dict: ");
+    obj1.print();
+    printf("\n");
+  }
+
+  // transparency support: blend mode, fill/stroke opacity
+  if (!obj1.dictLookup("BM", &obj2)->isNull()) {
+    if (state->parseBlendMode(&obj2, &mode)) {
+      state->setBlendMode(mode);
+    } else {
+      error(getPos(), "Invalid blend mode in ExtGState");
+    }
+  }
+  obj2.free();
+  if (obj1.dictLookup("ca", &obj2)->isNum()) {
+    state->setFillOpacity(obj2.getNum());
+  }
+  obj2.free();
+  if (obj1.dictLookup("CA", &obj2)->isNum()) {
+    state->setStrokeOpacity(obj2.getNum());
+  }
+  obj2.free();
+
+  // fill/stroke overprint
+  if ((haveFillOP = (obj1.dictLookup("op", &obj2)->isBool()))) {
+    state->setFillOverprint(obj2.getBool());
+  }
+  obj2.free();
+  if (obj1.dictLookup("OP", &obj2)->isBool()) {
+    state->setStrokeOverprint(obj2.getBool());
+    if (!haveFillOP) {
+      state->setFillOverprint(obj2.getBool());
+    }
+  }
+  obj2.free();
+
+  // stroke adjust
+  if (obj1.dictLookup("SA", &obj2)->isBool()) {
+    state->setStrokeAdjust(obj2.getBool());
+  }
+  obj2.free();
+
+  // transfer function
+  if (obj1.dictLookup("TR2", &obj2)->isNull()) {
+    obj2.free();
+    obj1.dictLookup("TR", &obj2);
+  }
+  if (obj2.isName("Default") ||
+      obj2.isName("Identity")) {
+    funcs[0] = funcs[1] = funcs[2] = funcs[3] = NULL;
+    state->setTransfer(funcs);
+  } else if (obj2.isArray() && obj2.arrayGetLength() == 4) {
+    for (i = 0; i < 4; ++i) {
+      obj2.arrayGet(i, &obj3);
+      funcs[i] = Function::parse(&obj3);
+      obj3.free();
+      if (!funcs[i]) {
+       break;
+      }
+    }
+    if (i == 4) {
+      state->setTransfer(funcs);
+    }
+  } else if (obj2.isName() || obj2.isDict() || obj2.isStream()) {
+    if ((funcs[0] = Function::parse(&obj2))) {
+      funcs[1] = funcs[2] = funcs[3] = NULL;
+      state->setTransfer(funcs);
+    }
+  } else if (!obj2.isNull()) {
+    error(getPos(), "Invalid transfer function in ExtGState");
+  }
+  obj2.free();
+
+  // soft mask
+  if (!obj1.dictLookup("SMask", &obj2)->isNull()) {
+    if (obj2.isName("None")) {
+      //out->clearSoftMask(state);
+    } else if (obj2.isDict()) {
+      if (obj2.dictLookup("S", &obj3)->isName("Alpha")) {
+       alpha = gTrue;
+      } else { // "Luminosity"
+       alpha = gFalse;
+      }
+      obj3.free();
+      funcs[0] = NULL;
+      if (!obj2.dictLookup("TR", &obj3)->isNull()) {
+       funcs[0] = Function::parse(&obj3);
+       if (funcs[0]->getInputSize() != 1 ||
+           funcs[0]->getOutputSize() != 1) {
+         error(getPos(),
+               "Invalid transfer function in soft mask in ExtGState");
+         delete funcs[0];
+         funcs[0] = NULL;
+       }
+      }
+      obj3.free();
+      if ((haveBackdropColor = obj2.dictLookup("BC", &obj3)->isArray())) {
+       for (i = 0; i < gfxColorMaxComps; ++i) {
+         backdropColor.c[i] = 0;
+       }
+       for (i = 0; i < obj3.arrayGetLength() && i < gfxColorMaxComps; ++i) {
+         obj3.arrayGet(i, &obj4);
+         if (obj4.isNum()) {
+           backdropColor.c[i] = dblToCol(obj4.getNum());
+         }
+         obj4.free();
+       }
+      }
+      obj3.free();
+      if (obj2.dictLookup("G", &obj3)->isStream()) {
+       if (obj3.streamGetDict()->lookup("Group", &obj4)->isDict()) {
+         blendingColorSpace = NULL;
+         isolated = knockout = gFalse;
+         if (!obj4.dictLookup("CS", &obj5)->isNull()) {
+           blendingColorSpace = GfxColorSpace::parse(&obj5);
+         }
+         obj5.free();
+         if (obj4.dictLookup("I", &obj5)->isBool()) {
+           isolated = obj5.getBool();
+         }
+         obj5.free();
+         if (obj4.dictLookup("K", &obj5)->isBool()) {
+           knockout = obj5.getBool();
+         }
+         obj5.free();
+         if (!haveBackdropColor) {
+           if (blendingColorSpace) {
+             blendingColorSpace->getDefaultColor(&backdropColor);
+           } else {
+             //~ need to get the parent or default color space (?)
+             for (i = 0; i < gfxColorMaxComps; ++i) {
+               backdropColor.c[i] = 0;
+             }
+           }
+         }
+         doSoftMask(&obj3, alpha, blendingColorSpace,
+                    isolated, knockout, funcs[0], &backdropColor);
+         if (funcs[0]) {
+           delete funcs[0];
+         }
+       } else {
+         error(getPos(), "Invalid soft mask in ExtGState - missing group");
+       }
+       obj4.free();
+      } else {
+       error(getPos(), "Invalid soft mask in ExtGState - missing group");
+      }
+      obj3.free();
+    } else if (!obj2.isNull()) {
+      error(getPos(), "Invalid soft mask in ExtGState");
+    }
+  }
+  obj2.free();
+
+  obj1.free();
+}
+
+void PdfParser::doSoftMask(Object *str, GBool alpha,
+                    GfxColorSpace *blendingColorSpace,
+                    GBool isolated, GBool knockout,
+                    Function *transferFunc, GfxColor *backdropColor) {
+  Dict *dict, *resDict;
+  double m[6], bbox[4];
+  Object obj1, obj2;
+  int i;
+
+  // check for excessive recursion
+  if (formDepth > 20) {
+    return;
+  }
+
+  // get stream dict
+  dict = str->streamGetDict();
+
+  // check form type
+  dict->lookup("FormType", &obj1);
+  if (!(obj1.isNull() || (obj1.isInt() && obj1.getInt() == 1))) {
+    error(getPos(), "Unknown form type");
+  }
+  obj1.free();
+
+  // get bounding box
+  dict->lookup("BBox", &obj1);
+  if (!obj1.isArray()) {
+    obj1.free();
+    error(getPos(), "Bad form bounding box");
+    return;
+  }
+  for (i = 0; i < 4; ++i) {
+    obj1.arrayGet(i, &obj2);
+    bbox[i] = obj2.getNum();
+    obj2.free();
+  }
+  obj1.free();
+
+  // get matrix
+  dict->lookup("Matrix", &obj1);
+  if (obj1.isArray()) {
+    for (i = 0; i < 6; ++i) {
+      obj1.arrayGet(i, &obj2);
+      m[i] = obj2.getNum();
+      obj2.free();
+    }
+  } else {
+    m[0] = 1; m[1] = 0;
+    m[2] = 0; m[3] = 1;
+    m[4] = 0; m[5] = 0;
+  }
+  obj1.free();
+
+  // get resources
+  dict->lookup("Resources", &obj1);
+  resDict = obj1.isDict() ? obj1.getDict() : (Dict *)NULL;
+
+  // draw it
+  ++formDepth;
+  doForm1(str, resDict, m, bbox, gTrue, gTrue,
+         blendingColorSpace, isolated, knockout,
+         alpha, transferFunc, backdropColor);
+  --formDepth;
+
+  if (blendingColorSpace) {
+    delete blendingColorSpace;
+  }
+  obj1.free();
+}
+
+void PdfParser::opSetRenderingIntent(Object args[], int numArgs) {
+}
+
+//------------------------------------------------------------------------
+// color operators
+//------------------------------------------------------------------------
+
+void PdfParser::opSetFillGray(Object args[], int numArgs) {
+  GfxColor color;
+
+  state->setFillPattern(NULL);
+  state->setFillColorSpace(new GfxDeviceGrayColorSpace());
+  color.c[0] = dblToCol(args[0].getNum());
+  state->setFillColor(&color);
+}
+
+void PdfParser::opSetStrokeGray(Object args[], int numArgs) {
+  GfxColor color;
+
+  state->setStrokePattern(NULL);
+  state->setStrokeColorSpace(new GfxDeviceGrayColorSpace());
+  color.c[0] = dblToCol(args[0].getNum());
+  state->setStrokeColor(&color);
+}
+
+void PdfParser::opSetFillCMYKColor(Object args[], int numArgs) {
+  GfxColor color;
+  int i;
+
+  state->setFillPattern(NULL);
+  state->setFillColorSpace(new GfxDeviceCMYKColorSpace());
+  for (i = 0; i < 4; ++i) {
+    color.c[i] = dblToCol(args[i].getNum());
+  }
+  state->setFillColor(&color);
+}
+
+void PdfParser::opSetStrokeCMYKColor(Object args[], int numArgs) {
+  GfxColor color;
+  int i;
+
+  state->setStrokePattern(NULL);
+  state->setStrokeColorSpace(new GfxDeviceCMYKColorSpace());
+  for (i = 0; i < 4; ++i) {
+    color.c[i] = dblToCol(args[i].getNum());
+  }
+  state->setStrokeColor(&color);
+}
+
+void PdfParser::opSetFillRGBColor(Object args[], int numArgs) {
+  GfxColor color;
+  int i;
+
+  state->setFillPattern(NULL);
+  state->setFillColorSpace(new GfxDeviceRGBColorSpace());
+  for (i = 0; i < 3; ++i) {
+    color.c[i] = dblToCol(args[i].getNum());
+  }
+  state->setFillColor(&color);
+}
+
+void PdfParser::opSetStrokeRGBColor(Object args[], int numArgs) {
+  GfxColor color;
+  int i;
+
+  state->setStrokePattern(NULL);
+  state->setStrokeColorSpace(new GfxDeviceRGBColorSpace());
+  for (i = 0; i < 3; ++i) {
+    color.c[i] = dblToCol(args[i].getNum());
+  }
+  state->setStrokeColor(&color);
+}
+
+void PdfParser::opSetFillColorSpace(Object args[], int numArgs) {
+  Object obj;
+  GfxColorSpace *colorSpace;
+  GfxColor color;
+
+  state->setFillPattern(NULL);
+  res->lookupColorSpace(args[0].getName(), &obj);
+  if (obj.isNull()) {
+    colorSpace = GfxColorSpace::parse(&args[0]);
+  } else {
+    colorSpace = GfxColorSpace::parse(&obj);
+  }
+  obj.free();
+  if (colorSpace) {
+    state->setFillColorSpace(colorSpace);
+    colorSpace->getDefaultColor(&color);
+    state->setFillColor(&color);
+  } else {
+    error(getPos(), "Bad color space (fill)");
+  }
+}
+
+void PdfParser::opSetStrokeColorSpace(Object args[], int numArgs) {
+  Object obj;
+  GfxColorSpace *colorSpace;
+  GfxColor color;
+
+  state->setStrokePattern(NULL);
+  res->lookupColorSpace(args[0].getName(), &obj);
+  if (obj.isNull()) {
+    colorSpace = GfxColorSpace::parse(&args[0]);
+  } else {
+    colorSpace = GfxColorSpace::parse(&obj);
+  }
+  obj.free();
+  if (colorSpace) {
+    state->setStrokeColorSpace(colorSpace);
+    colorSpace->getDefaultColor(&color);
+    state->setStrokeColor(&color);
+  } else {
+    error(getPos(), "Bad color space (stroke)");
+  }
+}
+
+void PdfParser::opSetFillColor(Object args[], int numArgs) {
+  GfxColor color;
+  int i;
+
+  if (numArgs != state->getFillColorSpace()->getNComps()) {
+    error(getPos(), "Incorrect number of arguments in 'sc' command");
+    return;
+  }
+  state->setFillPattern(NULL);
+  for (i = 0; i < numArgs; ++i) {
+    color.c[i] = dblToCol(args[i].getNum());
+  }
+  state->setFillColor(&color);
+}
+
+void PdfParser::opSetStrokeColor(Object args[], int numArgs) {
+  GfxColor color;
+  int i;
+
+  if (numArgs != state->getStrokeColorSpace()->getNComps()) {
+    error(getPos(), "Incorrect number of arguments in 'SC' command");
+    return;
+  }
+  state->setStrokePattern(NULL);
+  for (i = 0; i < numArgs; ++i) {
+    color.c[i] = dblToCol(args[i].getNum());
+  }
+  state->setStrokeColor(&color);
+}
+
+void PdfParser::opSetFillColorN(Object args[], int numArgs) {
+  GfxColor color;
+  GfxPattern *pattern;
+  int i;
+
+  if (state->getFillColorSpace()->getMode() == csPattern) {
+    if (numArgs > 1) {
+      if (!((GfxPatternColorSpace *)state->getFillColorSpace())->getUnder() ||
+         numArgs - 1 != ((GfxPatternColorSpace *)state->getFillColorSpace())
+                            ->getUnder()->getNComps()) {
+       error(getPos(), "Incorrect number of arguments in 'scn' command");
+       return;
+      }
+      for (i = 0; i < numArgs - 1 && i < gfxColorMaxComps; ++i) {
+       if (args[i].isNum()) {
+         color.c[i] = dblToCol(args[i].getNum());
+       }
+      }
+      state->setFillColor(&color);
+    }
+    if (args[numArgs-1].isName() &&
+       (pattern = res->lookupPattern(args[numArgs-1].getName()))) {
+      state->setFillPattern(pattern);
+    }
+
+  } else {
+    if (numArgs != state->getFillColorSpace()->getNComps()) {
+      error(getPos(), "Incorrect number of arguments in 'scn' command");
+      return;
+    }
+    state->setFillPattern(NULL);
+    for (i = 0; i < numArgs && i < gfxColorMaxComps; ++i) {
+      if (args[i].isNum()) {
+       color.c[i] = dblToCol(args[i].getNum());
+      }
+    }
+    state->setFillColor(&color);
+  }
+}
+
+void PdfParser::opSetStrokeColorN(Object args[], int numArgs) {
+  GfxColor color;
+  GfxPattern *pattern;
+  int i;
+
+  if (state->getStrokeColorSpace()->getMode() == csPattern) {
+    if (numArgs > 1) {
+      if (!((GfxPatternColorSpace *)state->getStrokeColorSpace())
+              ->getUnder() ||
+         numArgs - 1 != ((GfxPatternColorSpace *)state->getStrokeColorSpace())
+                            ->getUnder()->getNComps()) {
+       error(getPos(), "Incorrect number of arguments in 'SCN' command");
+       return;
+      }
+      for (i = 0; i < numArgs - 1 && i < gfxColorMaxComps; ++i) {
+       if (args[i].isNum()) {
+         color.c[i] = dblToCol(args[i].getNum());
+       }
+      }
+      state->setStrokeColor(&color);
+    }
+    if (args[numArgs-1].isName() &&
+       (pattern = res->lookupPattern(args[numArgs-1].getName()))) {
+      state->setStrokePattern(pattern);
+    }
+
+  } else {
+    if (numArgs != state->getStrokeColorSpace()->getNComps()) {
+      error(getPos(), "Incorrect number of arguments in 'SCN' command");
+      return;
+    }
+    state->setStrokePattern(NULL);
+    for (i = 0; i < numArgs && i < gfxColorMaxComps; ++i) {
+      if (args[i].isNum()) {
+       color.c[i] = dblToCol(args[i].getNum());
+      }
+    }
+    state->setStrokeColor(&color);
+  }
+}
+
+//------------------------------------------------------------------------
+// path segment operators
+//------------------------------------------------------------------------
+
+void PdfParser::opMoveTo(Object args[], int numArgs) {
+  state->moveTo(args[0].getNum(), args[1].getNum());
+}
+
+void PdfParser::opLineTo(Object args[], int numArgs) {
+  if (!state->isCurPt()) {
+    error(getPos(), "No current point in lineto");
+    return;
+  }
+  state->lineTo(args[0].getNum(), args[1].getNum());
+}
+
+void PdfParser::opCurveTo(Object args[], int numArgs) {
+  double x1, y1, x2, y2, x3, y3;
+
+  if (!state->isCurPt()) {
+    error(getPos(), "No current point in curveto");
+    return;
+  }
+  x1 = args[0].getNum();
+  y1 = args[1].getNum();
+  x2 = args[2].getNum();
+  y2 = args[3].getNum();
+  x3 = args[4].getNum();
+  y3 = args[5].getNum();
+  state->curveTo(x1, y1, x2, y2, x3, y3);
+}
+
+void PdfParser::opCurveTo1(Object args[], int numArgs) {
+  double x1, y1, x2, y2, x3, y3;
+
+  if (!state->isCurPt()) {
+    error(getPos(), "No current point in curveto1");
+    return;
+  }
+  x1 = state->getCurX();
+  y1 = state->getCurY();
+  x2 = args[0].getNum();
+  y2 = args[1].getNum();
+  x3 = args[2].getNum();
+  y3 = args[3].getNum();
+  state->curveTo(x1, y1, x2, y2, x3, y3);
+}
+
+void PdfParser::opCurveTo2(Object args[], int numArgs) {
+  double x1, y1, x2, y2, x3, y3;
+
+  if (!state->isCurPt()) {
+    error(getPos(), "No current point in curveto2");
+    return;
+  }
+  x1 = args[0].getNum();
+  y1 = args[1].getNum();
+  x2 = args[2].getNum();
+  y2 = args[3].getNum();
+  x3 = x2;
+  y3 = y2;
+  state->curveTo(x1, y1, x2, y2, x3, y3);
+}
+
+void PdfParser::opRectangle(Object args[], int numArgs) {
+  double x, y, w, h;
+
+  x = args[0].getNum();
+  y = args[1].getNum();
+  w = args[2].getNum();
+  h = args[3].getNum();
+  state->moveTo(x, y);
+  state->lineTo(x + w, y);
+  state->lineTo(x + w, y + h);
+  state->lineTo(x, y + h);
+  state->closePath();
+}
+
+void PdfParser::opClosePath(Object args[], int numArgs) {
+  if (!state->isCurPt()) {
+    error(getPos(), "No current point in closepath");
+    return;
+  }
+  state->closePath();
+}
+
+//------------------------------------------------------------------------
+// path painting operators
+//------------------------------------------------------------------------
+
+void PdfParser::opEndPath(Object args[], int numArgs) {
+  doEndPath();
+}
+
+void PdfParser::opStroke(Object args[], int numArgs) {
+  if (!state->isCurPt()) {
+    //error(getPos(), "No path in stroke");
+    return;
+  }
+  if (state->isPath()) {
+    if (state->getStrokeColorSpace()->getMode() == csPattern &&
+        !builder->isPatternTypeSupported(state->getStrokePattern())) {
+          doPatternStrokeFallback();
+    } else {
+      builder->addPath(state, false, true);
+    }
+  }
+  doEndPath();
+}
+
+void PdfParser::opCloseStroke(Object * /*args[]*/, int /*numArgs*/) {
+  if (!state->isCurPt()) {
+    //error(getPos(), "No path in closepath/stroke");
+    return;
+  }
+  state->closePath();
+  if (state->isPath()) {
+    if (state->getStrokeColorSpace()->getMode() == csPattern &&
+        !builder->isPatternTypeSupported(state->getStrokePattern())) {
+      doPatternStrokeFallback();
+    } else {
+      builder->addPath(state, false, true);
+    }
+  }
+  doEndPath();
+}
+
+void PdfParser::opFill(Object args[], int numArgs) {
+  if (!state->isCurPt()) {
+    //error(getPos(), "No path in fill");
+    return;
+  }
+  if (state->isPath()) {
+    if (state->getFillColorSpace()->getMode() == csPattern &&
+        !builder->isPatternTypeSupported(state->getFillPattern())) {
+      doPatternFillFallback(gFalse);
+    } else {
+      builder->addPath(state, true, false);
+    }
+  }
+  doEndPath();
+}
+
+void PdfParser::opEOFill(Object args[], int numArgs) {
+  if (!state->isCurPt()) {
+    //error(getPos(), "No path in eofill");
+    return;
+  }
+  if (state->isPath()) {
+    if (state->getFillColorSpace()->getMode() == csPattern &&
+        !builder->isPatternTypeSupported(state->getFillPattern())) {
+      doPatternFillFallback(gTrue);
+    } else {
+      builder->addPath(state, true, false, true);
+    }
+  }
+  doEndPath();
+}
+
+void PdfParser::opFillStroke(Object args[], int numArgs) {
+  if (!state->isCurPt()) {
+    //error(getPos(), "No path in fill/stroke");
+    return;
+  }
+  if (state->isPath()) {
+    doFillAndStroke(gFalse);
+  } else {
+    builder->addPath(state, true, true);
+  }
+  doEndPath();
+}
+
+void PdfParser::opCloseFillStroke(Object args[], int numArgs) {
+  if (!state->isCurPt()) {
+    //error(getPos(), "No path in closepath/fill/stroke");
+    return;
+  }
+  if (state->isPath()) {
+    state->closePath();
+    doFillAndStroke(gFalse);
+  }
+  doEndPath();
+}
+
+void PdfParser::opEOFillStroke(Object args[], int numArgs) {
+  if (!state->isCurPt()) {
+    //error(getPos(), "No path in eofill/stroke");
+    return;
+  }
+  if (state->isPath()) {
+    doFillAndStroke(gTrue);
+  }
+  doEndPath();
+}
+
+void PdfParser::opCloseEOFillStroke(Object args[], int numArgs) {
+  if (!state->isCurPt()) {
+    //error(getPos(), "No path in closepath/eofill/stroke");
+    return;
+  }
+  if (state->isPath()) {
+    state->closePath();
+    doFillAndStroke(gTrue);
+  }
+  doEndPath();
+}
+
+void PdfParser::doFillAndStroke(GBool eoFill) {
+    GBool fillOk = gTrue, strokeOk = gTrue;
+    if (state->getFillColorSpace()->getMode() == csPattern &&
+        !builder->isPatternTypeSupported(state->getFillPattern())) {
+        fillOk = gFalse;
+    }
+    if (state->getStrokeColorSpace()->getMode() == csPattern &&
+        !builder->isPatternTypeSupported(state->getStrokePattern())) {
+        strokeOk = gFalse;
+    }
+    if (fillOk && strokeOk) {
+        builder->addPath(state, true, true, eoFill);
+    } else {
+        doPatternFillFallback(eoFill);
+        doPatternStrokeFallback();
+    }
+}
+
+void PdfParser::doPatternFillFallback(GBool eoFill) {
+  GfxPattern *pattern;
+
+  if (!(pattern = state->getFillPattern())) {
+    return;
+  }
+  switch (pattern->getType()) {
+  case 1:
+    break;
+  case 2:
+    doShadingPatternFillFallback((GfxShadingPattern *)pattern, gFalse, eoFill);
+    break;
+  default:
+    error(getPos(), "Unimplemented pattern type (%d) in fill",
+         pattern->getType());
+    break;
+  }
+}
+
+void PdfParser::doPatternStrokeFallback() {
+  GfxPattern *pattern;
+
+  if (!(pattern = state->getStrokePattern())) {
+    return;
+  }
+  switch (pattern->getType()) {
+  case 1:
+    break;
+  case 2:
+    doShadingPatternFillFallback((GfxShadingPattern *)pattern, gTrue, gFalse);
+    break;
+  default:
+    error(getPos(), "Unimplemented pattern type (%d) in stroke",
+         pattern->getType());
+    break;
+  }
+}
+
+void PdfParser::doShadingPatternFillFallback(GfxShadingPattern *sPat,
+                                             GBool stroke, GBool eoFill) {
+  GfxShading *shading;
+  GfxPath *savedPath;
+  double *ctm, *btm, *ptm;
+  double m[6], ictm[6], m1[6];
+  double xMin, yMin, xMax, yMax;
+  double det;
+
+  shading = sPat->getShading();
+
+  // save current graphics state
+  savedPath = state->getPath()->copy();
+  saveState();
+
+  // clip to bbox
+  if (0 ){//shading->getHasBBox()) {
+    shading->getBBox(&xMin, &yMin, &xMax, &yMax);
+    state->moveTo(xMin, yMin);
+    state->lineTo(xMax, yMin);
+    state->lineTo(xMax, yMax);
+    state->lineTo(xMin, yMax);
+    state->closePath();
+    state->clip();
+    //builder->clip(state);
+    state->setPath(savedPath->copy());
+  }
+
+  // clip to current path
+  if (stroke) {
+    state->clipToStrokePath();
+    //out->clipToStrokePath(state);
+  } else {
+    state->clip();
+    if (eoFill) {
+      builder->clip(state, true);
+    } else {
+      builder->clip(state);
+    }
+  }
+
+  // set the color space
+  state->setFillColorSpace(shading->getColorSpace()->copy());
+
+  // background color fill
+  if (shading->getHasBackground()) {
+    state->setFillColor(shading->getBackground());
+    builder->addPath(state, true, false);
+  }
+  state->clearPath();
+
+  // construct a (pattern space) -> (current space) transform matrix
+  ctm = state->getCTM();
+  btm = baseMatrix;
+  ptm = sPat->getMatrix();
+  // iCTM = invert CTM
+  det = 1 / (ctm[0] * ctm[3] - ctm[1] * ctm[2]);
+  ictm[0] = ctm[3] * det;
+  ictm[1] = -ctm[1] * det;
+  ictm[2] = -ctm[2] * det;
+  ictm[3] = ctm[0] * det;
+  ictm[4] = (ctm[2] * ctm[5] - ctm[3] * ctm[4]) * det;
+  ictm[5] = (ctm[1] * ctm[4] - ctm[0] * ctm[5]) * det;
+  // m1 = PTM * BTM = PTM * base transform matrix
+  m1[0] = ptm[0] * btm[0] + ptm[1] * btm[2];
+  m1[1] = ptm[0] * btm[1] + ptm[1] * btm[3];
+  m1[2] = ptm[2] * btm[0] + ptm[3] * btm[2];
+  m1[3] = ptm[2] * btm[1] + ptm[3] * btm[3];
+  m1[4] = ptm[4] * btm[0] + ptm[5] * btm[2] + btm[4];
+  m1[5] = ptm[4] * btm[1] + ptm[5] * btm[3] + btm[5];
+  // m = m1 * iCTM = (PTM * BTM) * (iCTM)
+  m[0] = m1[0] * ictm[0] + m1[1] * ictm[2];
+  m[1] = m1[0] * ictm[1] + m1[1] * ictm[3];
+  m[2] = m1[2] * ictm[0] + m1[3] * ictm[2];
+  m[3] = m1[2] * ictm[1] + m1[3] * ictm[3];
+  m[4] = m1[4] * ictm[0] + m1[5] * ictm[2] + ictm[4];
+  m[5] = m1[4] * ictm[1] + m1[5] * ictm[3] + ictm[5];
+
+  // set the new matrix
+  state->concatCTM(m[0], m[1], m[2], m[3], m[4], m[5]);
+  builder->setTransform(m[0], m[1], m[2], m[3], m[4], m[5]);
+
+  // do shading type-specific operations
+  switch (shading->getType()) {
+  case 1:
+    doFunctionShFill((GfxFunctionShading *)shading);
+    break;
+  case 2:
+  case 3:
+    // no need to implement these
+    break;
+  case 4:
+  case 5:
+    doGouraudTriangleShFill((GfxGouraudTriangleShading *)shading);
+    break;
+  case 6:
+  case 7:
+    doPatchMeshShFill((GfxPatchMeshShading *)shading);
+    break;
+  }
+
+  // restore graphics state
+  restoreState();
+  state->setPath(savedPath);
+}
+
+void PdfParser::opShFill(Object args[], int numArgs) {
+  GfxShading *shading;
+  GfxPath *savedPath;
+  double xMin, yMin, xMax, yMax;
+
+  if (!(shading = res->lookupShading(args[0].getName()))) {
+    return;
+  }
+
+  // save current graphics state
+  savedPath = state->getPath()->copy();
+  saveState();
+
+  // clip to bbox
+  if (shading->getHasBBox()) {
+    shading->getBBox(&xMin, &yMin, &xMax, &yMax);
+    state->moveTo(xMin, yMin);
+    state->lineTo(xMax, yMin);
+    state->lineTo(xMax, yMax);
+    state->lineTo(xMin, yMax);
+    state->closePath();
+    state->clip();
+    builder->clip(state);
+    state->clearPath();
+  }
+
+  // set the color space
+  state->setFillColorSpace(shading->getColorSpace()->copy());
+
+  // do shading type-specific operations
+  switch (shading->getType()) {
+  case 1:
+    doFunctionShFill((GfxFunctionShading *)shading);
+    break;
+  case 2:
+  case 3:
+    // no need to implement these
+    break;
+  case 4:
+  case 5:
+    doGouraudTriangleShFill((GfxGouraudTriangleShading *)shading);
+    break;
+  case 6:
+  case 7:
+    doPatchMeshShFill((GfxPatchMeshShading *)shading);
+    break;
+  }
+
+  // restore graphics state
+  restoreState();
+  state->setPath(savedPath);
+
+  delete shading;
+}
+
+void PdfParser::doFunctionShFill(GfxFunctionShading *shading) {
+  double x0, y0, x1, y1;
+  GfxColor colors[4];
+
+  shading->getDomain(&x0, &y0, &x1, &y1);
+  shading->getColor(x0, y0, &colors[0]);
+  shading->getColor(x0, y1, &colors[1]);
+  shading->getColor(x1, y0, &colors[2]);
+  shading->getColor(x1, y1, &colors[3]);
+  doFunctionShFill1(shading, x0, y0, x1, y1, colors, 0);
+}
+
+void PdfParser::doFunctionShFill1(GfxFunctionShading *shading,
+                           double x0, double y0,
+                           double x1, double y1,
+                           GfxColor *colors, int depth) {
+  GfxColor fillColor;
+  GfxColor color0M, color1M, colorM0, colorM1, colorMM;
+  GfxColor colors2[4];
+  double *matrix;
+  double xM, yM;
+  int nComps, i, j;
+
+  nComps = shading->getColorSpace()->getNComps();
+  matrix = shading->getMatrix();
+
+  // compare the four corner colors
+  for (i = 0; i < 4; ++i) {
+    for (j = 0; j < nComps; ++j) {
+      if (abs(colors[i].c[j] - colors[(i+1)&3].c[j]) > functionColorDelta) {
+       break;
+      }
+    }
+    if (j < nComps) {
+      break;
+    }
+  }
+
+  // center of the rectangle
+  xM = 0.5 * (x0 + x1);
+  yM = 0.5 * (y0 + y1);
+
+  // the four corner colors are close (or we hit the recursive limit)
+  // -- fill the rectangle; but require at least one subdivision
+  // (depth==0) to avoid problems when the four outer corners of the
+  // shaded region are the same color
+  if ((i == 4 && depth > 0) || depth == functionMaxDepth) {
+
+    // use the center color
+    shading->getColor(xM, yM, &fillColor);
+    state->setFillColor(&fillColor);
+
+    // fill the rectangle
+    state->moveTo(x0 * matrix[0] + y0 * matrix[2] + matrix[4],
+                 x0 * matrix[1] + y0 * matrix[3] + matrix[5]);
+    state->lineTo(x1 * matrix[0] + y0 * matrix[2] + matrix[4],
+                 x1 * matrix[1] + y0 * matrix[3] + matrix[5]);
+    state->lineTo(x1 * matrix[0] + y1 * matrix[2] + matrix[4],
+                 x1 * matrix[1] + y1 * matrix[3] + matrix[5]);
+    state->lineTo(x0 * matrix[0] + y1 * matrix[2] + matrix[4],
+                 x0 * matrix[1] + y1 * matrix[3] + matrix[5]);
+    state->closePath();
+    builder->addPath(state, true, false);
+    state->clearPath();
+
+  // the four corner colors are not close enough -- subdivide the
+  // rectangle
+  } else {
+
+    // colors[0]       colorM0       colors[2]
+    //   (x0,y0)       (xM,y0)       (x1,y0)
+    //         +----------+----------+
+    //         |          |          |
+    //         |    UL    |    UR    |
+    // color0M |       colorMM       | color1M
+    // (x0,yM) +----------+----------+ (x1,yM)
+    //         |       (xM,yM)       |
+    //         |    LL    |    LR    |
+    //         |          |          |
+    //         +----------+----------+
+    // colors[1]       colorM1       colors[3]
+    //   (x0,y1)       (xM,y1)       (x1,y1)
+
+    shading->getColor(x0, yM, &color0M);
+    shading->getColor(x1, yM, &color1M);
+    shading->getColor(xM, y0, &colorM0);
+    shading->getColor(xM, y1, &colorM1);
+    shading->getColor(xM, yM, &colorMM);
+
+    // upper-left sub-rectangle
+    colors2[0] = colors[0];
+    colors2[1] = color0M;
+    colors2[2] = colorM0;
+    colors2[3] = colorMM;
+    doFunctionShFill1(shading, x0, y0, xM, yM, colors2, depth + 1);
+    
+    // lower-left sub-rectangle
+    colors2[0] = color0M;
+    colors2[1] = colors[1];
+    colors2[2] = colorMM;
+    colors2[3] = colorM1;
+    doFunctionShFill1(shading, x0, yM, xM, y1, colors2, depth + 1);
+    
+    // upper-right sub-rectangle
+    colors2[0] = colorM0;
+    colors2[1] = colorMM;
+    colors2[2] = colors[2];
+    colors2[3] = color1M;
+    doFunctionShFill1(shading, xM, y0, x1, yM, colors2, depth + 1);
+
+    // lower-right sub-rectangle
+    colors2[0] = colorMM;
+    colors2[1] = colorM1;
+    colors2[2] = color1M;
+    colors2[3] = colors[3];
+    doFunctionShFill1(shading, xM, yM, x1, y1, colors2, depth + 1);
+  }
+}
+
+void PdfParser::doGouraudTriangleShFill(GfxGouraudTriangleShading *shading) {
+  double x0, y0, x1, y1, x2, y2;
+  GfxColor color0, color1, color2;
+  int i;
+
+  for (i = 0; i < shading->getNTriangles(); ++i) {
+    shading->getTriangle(i, &x0, &y0, &color0,
+                        &x1, &y1, &color1,
+                        &x2, &y2, &color2);
+    gouraudFillTriangle(x0, y0, &color0, x1, y1, &color1, x2, y2, &color2,
+                       shading->getColorSpace()->getNComps(), 0);
+  }
+}
+
+void PdfParser::gouraudFillTriangle(double x0, double y0, GfxColor *color0,
+                             double x1, double y1, GfxColor *color1,
+                             double x2, double y2, GfxColor *color2,
+                             int nComps, int depth) {
+  double x01, y01, x12, y12, x20, y20;
+  GfxColor color01, color12, color20;
+  int i;
+
+  for (i = 0; i < nComps; ++i) {
+    if (abs(color0->c[i] - color1->c[i]) > gouraudColorDelta ||
+       abs(color1->c[i] - color2->c[i]) > gouraudColorDelta) {
+      break;
+    }
+  }
+  if (i == nComps || depth == gouraudMaxDepth) {
+    state->setFillColor(color0);
+    state->moveTo(x0, y0);
+    state->lineTo(x1, y1);
+    state->lineTo(x2, y2);
+    state->closePath();
+    builder->addPath(state, true, false);
+    state->clearPath();
+  } else {
+    x01 = 0.5 * (x0 + x1);
+    y01 = 0.5 * (y0 + y1);
+    x12 = 0.5 * (x1 + x2);
+    y12 = 0.5 * (y1 + y2);
+    x20 = 0.5 * (x2 + x0);
+    y20 = 0.5 * (y2 + y0);
+    //~ if the shading has a Function, this should interpolate on the
+    //~ function parameter, not on the color components
+    for (i = 0; i < nComps; ++i) {
+      color01.c[i] = (color0->c[i] + color1->c[i]) / 2;
+      color12.c[i] = (color1->c[i] + color2->c[i]) / 2;
+      color20.c[i] = (color2->c[i] + color0->c[i]) / 2;
+    }
+    gouraudFillTriangle(x0, y0, color0, x01, y01, &color01,
+                       x20, y20, &color20, nComps, depth + 1);
+    gouraudFillTriangle(x01, y01, &color01, x1, y1, color1,
+                       x12, y12, &color12, nComps, depth + 1);
+    gouraudFillTriangle(x01, y01, &color01, x12, y12, &color12,
+                       x20, y20, &color20, nComps, depth + 1);
+    gouraudFillTriangle(x20, y20, &color20, x12, y12, &color12,
+                       x2, y2, color2, nComps, depth + 1);
+  }
+}
+
+void PdfParser::doPatchMeshShFill(GfxPatchMeshShading *shading) {
+  int start, i;
+
+  if (shading->getNPatches() > 128) {
+    start = 3;
+  } else if (shading->getNPatches() > 64) {
+    start = 2;
+  } else if (shading->getNPatches() > 16) {
+    start = 1;
+  } else {
+    start = 0;
+  }
+  for (i = 0; i < shading->getNPatches(); ++i) {
+    fillPatch(shading->getPatch(i), shading->getColorSpace()->getNComps(),
+             start);
+  }
+}
+
+void PdfParser::fillPatch(GfxPatch *patch, int nComps, int depth) {
+  GfxPatch patch00, patch01, patch10, patch11;
+  double xx[4][8], yy[4][8];
+  double xxm, yym;
+  int i;
+
+  for (i = 0; i < nComps; ++i) {
+    if (abs(patch->color[0][0].c[i] - patch->color[0][1].c[i])
+         > patchColorDelta ||
+       abs(patch->color[0][1].c[i] - patch->color[1][1].c[i])
+         > patchColorDelta ||
+       abs(patch->color[1][1].c[i] - patch->color[1][0].c[i])
+         > patchColorDelta ||
+       abs(patch->color[1][0].c[i] - patch->color[0][0].c[i])
+         > patchColorDelta) {
+      break;
+    }
+  }
+  if (i == nComps || depth == patchMaxDepth) {
+    state->setFillColor(&patch->color[0][0]);
+    state->moveTo(patch->x[0][0], patch->y[0][0]);
+    state->curveTo(patch->x[0][1], patch->y[0][1],
+                  patch->x[0][2], patch->y[0][2],
+                  patch->x[0][3], patch->y[0][3]);
+    state->curveTo(patch->x[1][3], patch->y[1][3],
+                  patch->x[2][3], patch->y[2][3],
+                  patch->x[3][3], patch->y[3][3]);
+    state->curveTo(patch->x[3][2], patch->y[3][2],
+                  patch->x[3][1], patch->y[3][1],
+                  patch->x[3][0], patch->y[3][0]);
+    state->curveTo(patch->x[2][0], patch->y[2][0],
+                  patch->x[1][0], patch->y[1][0],
+                  patch->x[0][0], patch->y[0][0]);
+    state->closePath();
+    builder->addPath(state, true, false);
+    state->clearPath();
+  } else {
+    for (i = 0; i < 4; ++i) {
+      xx[i][0] = patch->x[i][0];
+      yy[i][0] = patch->y[i][0];
+      xx[i][1] = 0.5 * (patch->x[i][0] + patch->x[i][1]);
+      yy[i][1] = 0.5 * (patch->y[i][0] + patch->y[i][1]);
+      xxm = 0.5 * (patch->x[i][1] + patch->x[i][2]);
+      yym = 0.5 * (patch->y[i][1] + patch->y[i][2]);
+      xx[i][6] = 0.5 * (patch->x[i][2] + patch->x[i][3]);
+      yy[i][6] = 0.5 * (patch->y[i][2] + patch->y[i][3]);
+      xx[i][2] = 0.5 * (xx[i][1] + xxm);
+      yy[i][2] = 0.5 * (yy[i][1] + yym);
+      xx[i][5] = 0.5 * (xxm + xx[i][6]);
+      yy[i][5] = 0.5 * (yym + yy[i][6]);
+      xx[i][3] = xx[i][4] = 0.5 * (xx[i][2] + xx[i][5]);
+      yy[i][3] = yy[i][4] = 0.5 * (yy[i][2] + yy[i][5]);
+      xx[i][7] = patch->x[i][3];
+      yy[i][7] = patch->y[i][3];
+    }
+    for (i = 0; i < 4; ++i) {
+      patch00.x[0][i] = xx[0][i];
+      patch00.y[0][i] = yy[0][i];
+      patch00.x[1][i] = 0.5 * (xx[0][i] + xx[1][i]);
+      patch00.y[1][i] = 0.5 * (yy[0][i] + yy[1][i]);
+      xxm = 0.5 * (xx[1][i] + xx[2][i]);
+      yym = 0.5 * (yy[1][i] + yy[2][i]);
+      patch10.x[2][i] = 0.5 * (xx[2][i] + xx[3][i]);
+      patch10.y[2][i] = 0.5 * (yy[2][i] + yy[3][i]);
+      patch00.x[2][i] = 0.5 * (patch00.x[1][i] + xxm);
+      patch00.y[2][i] = 0.5 * (patch00.y[1][i] + yym);
+      patch10.x[1][i] = 0.5 * (xxm + patch10.x[2][i]);
+      patch10.y[1][i] = 0.5 * (yym + patch10.y[2][i]);
+      patch00.x[3][i] = 0.5 * (patch00.x[2][i] + patch10.x[1][i]);
+      patch00.y[3][i] = 0.5 * (patch00.y[2][i] + patch10.y[1][i]);
+      patch10.x[0][i] = patch00.x[3][i];
+      patch10.y[0][i] = patch00.y[3][i];
+      patch10.x[3][i] = xx[3][i];
+      patch10.y[3][i] = yy[3][i];
+    }
+    for (i = 4; i < 8; ++i) {
+      patch01.x[0][i-4] = xx[0][i];
+      patch01.y[0][i-4] = yy[0][i];
+      patch01.x[1][i-4] = 0.5 * (xx[0][i] + xx[1][i]);
+      patch01.y[1][i-4] = 0.5 * (yy[0][i] + yy[1][i]);
+      xxm = 0.5 * (xx[1][i] + xx[2][i]);
+      yym = 0.5 * (yy[1][i] + yy[2][i]);
+      patch11.x[2][i-4] = 0.5 * (xx[2][i] + xx[3][i]);
+      patch11.y[2][i-4] = 0.5 * (yy[2][i] + yy[3][i]);
+      patch01.x[2][i-4] = 0.5 * (patch01.x[1][i-4] + xxm);
+      patch01.y[2][i-4] = 0.5 * (patch01.y[1][i-4] + yym);
+      patch11.x[1][i-4] = 0.5 * (xxm + patch11.x[2][i-4]);
+      patch11.y[1][i-4] = 0.5 * (yym + patch11.y[2][i-4]);
+      patch01.x[3][i-4] = 0.5 * (patch01.x[2][i-4] + patch11.x[1][i-4]);
+      patch01.y[3][i-4] = 0.5 * (patch01.y[2][i-4] + patch11.y[1][i-4]);
+      patch11.x[0][i-4] = patch01.x[3][i-4];
+      patch11.y[0][i-4] = patch01.y[3][i-4];
+      patch11.x[3][i-4] = xx[3][i];
+      patch11.y[3][i-4] = yy[3][i];
+    }
+    //~ if the shading has a Function, this should interpolate on the
+    //~ function parameter, not on the color components
+    for (i = 0; i < nComps; ++i) {
+      patch00.color[0][0].c[i] = patch->color[0][0].c[i];
+      patch00.color[0][1].c[i] = (patch->color[0][0].c[i] +
+                                 patch->color[0][1].c[i]) / 2;
+      patch01.color[0][0].c[i] = patch00.color[0][1].c[i];
+      patch01.color[0][1].c[i] = patch->color[0][1].c[i];
+      patch01.color[1][1].c[i] = (patch->color[0][1].c[i] +
+                                 patch->color[1][1].c[i]) / 2;
+      patch11.color[0][1].c[i] = patch01.color[1][1].c[i];
+      patch11.color[1][1].c[i] = patch->color[1][1].c[i];
+      patch11.color[1][0].c[i] = (patch->color[1][1].c[i] +
+                                 patch->color[1][0].c[i]) / 2;
+      patch10.color[1][1].c[i] = patch11.color[1][0].c[i];
+      patch10.color[1][0].c[i] = patch->color[1][0].c[i];
+      patch10.color[0][0].c[i] = (patch->color[1][0].c[i] +
+                                 patch->color[0][0].c[i]) / 2;
+      patch00.color[1][0].c[i] = patch10.color[0][0].c[i];
+      patch00.color[1][1].c[i] = (patch00.color[1][0].c[i] +
+                                 patch01.color[1][1].c[i]) / 2;
+      patch01.color[1][0].c[i] = patch00.color[1][1].c[i];
+      patch11.color[0][0].c[i] = patch00.color[1][1].c[i];
+      patch10.color[0][1].c[i] = patch00.color[1][1].c[i];
+    }
+    fillPatch(&patch00, nComps, depth + 1);
+    fillPatch(&patch10, nComps, depth + 1);
+    fillPatch(&patch01, nComps, depth + 1);
+    fillPatch(&patch11, nComps, depth + 1);
+  }
+}
+
+void PdfParser::doEndPath() {
+  if (state->isCurPt() && clip != clipNone) {
+    state->clip();
+    if (clip == clipNormal) {
+      builder->clip(state);
+    } else {
+      builder->clip(state, true);
+    }
+  }
+  clip = clipNone;
+  state->clearPath();
+}
+
+//------------------------------------------------------------------------
+// path clipping operators
+//------------------------------------------------------------------------
+
+void PdfParser::opClip(Object args[], int numArgs) {
+  clip = clipNormal;
+}
+
+void PdfParser::opEOClip(Object args[], int numArgs) {
+  clip = clipEO;
+}
+
+//------------------------------------------------------------------------
+// text object operators
+//------------------------------------------------------------------------
+
+void PdfParser::opBeginText(Object args[], int numArgs) {
+  state->setTextMat(1, 0, 0, 1, 0, 0);
+  state->textMoveTo(0, 0);
+  fontChanged = gTrue;
+}
+
+void PdfParser::opEndText(Object args[], int numArgs) {
+  //NEEDED out->endTextObject(state);
+}
+
+//------------------------------------------------------------------------
+// text state operators
+//------------------------------------------------------------------------
+
+void PdfParser::opSetCharSpacing(Object args[], int numArgs) {
+  state->setCharSpace(args[0].getNum());
+}
+
+void PdfParser::opSetFont(Object args[], int numArgs) {
+  GfxFont *font;
+
+  if (!(font = res->lookupFont(args[0].getName()))) {
+    return;
+  }
+  if (printCommands) {
+    printf("  font: tag=%s name='%s' %g\n",
+          font->getTag()->getCString(),
+          font->getName() ? font->getName()->getCString() : "???",
+          args[1].getNum());
+    fflush(stdout);
+  }
+
+  font->incRefCnt();
+  state->setFont(font, args[1].getNum());
+  fontChanged = gTrue;
+}
+
+void PdfParser::opSetTextLeading(Object args[], int numArgs) {
+  state->setLeading(args[0].getNum());
+}
+
+void PdfParser::opSetTextRender(Object args[], int numArgs) {
+  state->setRender(args[0].getInt());
+}
+
+void PdfParser::opSetTextRise(Object args[], int numArgs) {
+  state->setRise(args[0].getNum());
+}
+
+void PdfParser::opSetWordSpacing(Object args[], int numArgs) {
+  state->setWordSpace(args[0].getNum());
+  //out->updateWordSpace(state);
+}
+
+void PdfParser::opSetHorizScaling(Object args[], int numArgs) {
+  state->setHorizScaling(args[0].getNum());
+  //out->updateHorizScaling(state);
+  fontChanged = gTrue;
+}
+
+//------------------------------------------------------------------------
+// text positioning operators
+//------------------------------------------------------------------------
+
+void PdfParser::opTextMove(Object args[], int numArgs) {
+  double tx, ty;
+
+  tx = state->getLineX() + args[0].getNum();
+  ty = state->getLineY() + args[1].getNum();
+  state->textMoveTo(tx, ty);
+}
+
+void PdfParser::opTextMoveSet(Object args[], int numArgs) {
+  double tx, ty;
+
+  tx = state->getLineX() + args[0].getNum();
+  ty = args[1].getNum();
+  state->setLeading(-ty);
+  ty += state->getLineY();
+  state->textMoveTo(tx, ty);
+}
+
+void PdfParser::opSetTextMatrix(Object args[], int numArgs) {
+  state->setTextMat(args[0].getNum(), args[1].getNum(),
+                   args[2].getNum(), args[3].getNum(),
+                   args[4].getNum(), args[5].getNum());
+  state->textMoveTo(0, 0);
+  fontChanged = gTrue;
+}
+
+void PdfParser::opTextNextLine(Object args[], int numArgs) {
+  double tx, ty;
+
+  tx = state->getLineX();
+  ty = state->getLineY() - state->getLeading();
+  state->textMoveTo(tx, ty);
+}
+
+//------------------------------------------------------------------------
+// text string operators
+//------------------------------------------------------------------------
+
+void PdfParser::opShowText(Object args[], int numArgs) {
+  if (!state->getFont()) {
+    error(getPos(), "No font in show");
+    return;
+  }
+  if (fontChanged) {
+    //NEEDED out->updateFont(state);
+    fontChanged = gFalse;
+  }
+  //out->beginStringOp(state);
+  doShowText(args[0].getString());
+  //out->endStringOp(state);
+}
+
+void PdfParser::opMoveShowText(Object args[], int numArgs) {
+  double tx, ty;
+
+  if (!state->getFont()) {
+    error(getPos(), "No font in move/show");
+    return;
+  }
+  if (fontChanged) {
+    //out->updateFont(state);
+    fontChanged = gFalse;
+  }
+  tx = state->getLineX();
+  ty = state->getLineY() - state->getLeading();
+  state->textMoveTo(tx, ty);
+  //out->beginStringOp(state);
+  doShowText(args[0].getString());
+  //out->endStringOp(state);
+}
+
+void PdfParser::opMoveSetShowText(Object args[], int numArgs) {
+  double tx, ty;
+
+  if (!state->getFont()) {
+    error(getPos(), "No font in move/set/show");
+    return;
+  }
+  if (fontChanged) {
+    //out->updateFont(state);
+    fontChanged = gFalse;
+  }
+  state->setWordSpace(args[0].getNum());
+  state->setCharSpace(args[1].getNum());
+  tx = state->getLineX();
+  ty = state->getLineY() - state->getLeading();
+  state->textMoveTo(tx, ty);
+  //out->beginStringOp(state);
+  doShowText(args[2].getString());
+  //out->endStringOp(state);
+}
+
+void PdfParser::opShowSpaceText(Object args[], int numArgs) {
+  Array *a;
+  Object obj;
+  int wMode;
+  int i;
+
+  if (!state->getFont()) {
+    error(getPos(), "No font in show/space");
+    return;
+  }
+  if (fontChanged) {
+    //out->updateFont(state);
+    fontChanged = gFalse;
+  }
+  //out->beginStringOp(state);
+  wMode = state->getFont()->getWMode();
+  a = args[0].getArray();
+  for (i = 0; i < a->getLength(); ++i) {
+    a->get(i, &obj);
+    if (obj.isNum()) {
+      // this uses the absolute value of the font size to match
+      // Acrobat's behavior
+      if (wMode) {
+       state->textShift(0, -obj.getNum() * 0.001 *
+                           fabs(state->getFontSize()));
+      } else {
+       state->textShift(-obj.getNum() * 0.001 *
+                        fabs(state->getFontSize()), 0);
+      }
+    } else if (obj.isString()) {
+      doShowText(obj.getString());
+    } else {
+      error(getPos(), "Element of show/space array must be number or string");
+    }
+    obj.free();
+  }
+  //out->endStringOp(state);
+}
+
+#include <glib-object.h>
+#include <UnicodeMap.h>
+                 
+static gchar *
+unicode_to_char (Unicode *unicode,
+                 int      len)
+{
+        static UnicodeMap *uMap = NULL;
+        if (uMap == NULL) {
+                GooString *enc = new GooString("UTF-8");
+                uMap = globalParams->getUnicodeMap(enc);
+                uMap->incRefCnt ();
+                delete enc;
+        }
+
+        GooString gstr;
+        gchar buf[8]; /* 8 is enough for mapping an unicode char to a string */
+        int i, n;
+
+        for (i = 0; i < len; ++i) {
+                n = uMap->mapUnicode(unicode[i], buf, sizeof(buf));
+                gstr.append(buf, n);
+        }
+
+        return g_strdup (gstr.getCString ());
+}
+
+
+void PdfParser::doShowText(GooString *s) {
+  GfxFont *font;
+  int wMode;
+  double riseX, riseY;
+  CharCode code;
+  Unicode u[8];
+  double x, y, dx, dy, dx2, dy2, curX, curY, tdx, tdy, lineX, lineY;
+  double originX, originY, tOriginX, tOriginY;
+  double oldCTM[6], newCTM[6];
+  double *mat;
+  Object charProc;
+  Dict *resDict;
+  Parser *oldParser;
+  char *p;
+  int len, n, uLen, nChars, nSpaces, i;
+
+  font = state->getFont();
+  wMode = font->getWMode();
+printf("doShowText() called\n");
+//   if (out->useDrawChar()) {
+//       out->beginString(state, s);
+//   }
+
+  // handle a Type 3 char
+  if (font->getType() == fontType3 && 0) {//out->interpretType3Chars()) {
+    mat = state->getCTM();
+    for (i = 0; i < 6; ++i) {
+      oldCTM[i] = mat[i];
+    }
+    mat = state->getTextMat();
+    newCTM[0] = mat[0] * oldCTM[0] + mat[1] * oldCTM[2];
+    newCTM[1] = mat[0] * oldCTM[1] + mat[1] * oldCTM[3];
+    newCTM[2] = mat[2] * oldCTM[0] + mat[3] * oldCTM[2];
+    newCTM[3] = mat[2] * oldCTM[1] + mat[3] * oldCTM[3];
+    mat = font->getFontMatrix();
+    newCTM[0] = mat[0] * newCTM[0] + mat[1] * newCTM[2];
+    newCTM[1] = mat[0] * newCTM[1] + mat[1] * newCTM[3];
+    newCTM[2] = mat[2] * newCTM[0] + mat[3] * newCTM[2];
+    newCTM[3] = mat[2] * newCTM[1] + mat[3] * newCTM[3];
+    newCTM[0] *= state->getFontSize();
+    newCTM[1] *= state->getFontSize();
+    newCTM[2] *= state->getFontSize();
+    newCTM[3] *= state->getFontSize();
+    newCTM[0] *= state->getHorizScaling();
+    newCTM[2] *= state->getHorizScaling();
+    state->textTransformDelta(0, state->getRise(), &riseX, &riseY);
+    curX = state->getCurX();
+    curY = state->getCurY();
+    lineX = state->getLineX();
+    lineY = state->getLineY();
+    oldParser = parser;
+    p = s->getCString();
+    len = s->getLength();
+    while (len > 0) {
+      n = font->getNextChar(p, len, &code,
+                           u, (int)(sizeof(u) / sizeof(Unicode)), &uLen,
+                           &dx, &dy, &originX, &originY);
+      dx = dx * state->getFontSize() + state->getCharSpace();
+      if (n == 1 && *p == ' ') {
+       dx += state->getWordSpace();
+      }
+      dx *= state->getHorizScaling();
+      dy *= state->getFontSize();
+      state->textTransformDelta(dx, dy, &tdx, &tdy);
+      state->transform(curX + riseX, curY + riseY, &x, &y);
+      saveState();
+      state->setCTM(newCTM[0], newCTM[1], newCTM[2], newCTM[3], x, y);
+      //~ the CTM concat values here are wrong (but never used)
+      //out->updateCTM(state, 1, 0, 0, 1, 0, 0);
+      if (0){ /*!out->beginType3Char(state, curX + riseX, curY + riseY, tdx, tdy,
+                              code, u, uLen)) {*/
+       ((Gfx8BitFont *)font)->getCharProc(code, &charProc);
+       if ((resDict = ((Gfx8BitFont *)font)->getResources())) {
+         pushResources(resDict);
+       }
+       if (charProc.isStream()) {
+         //parse(&charProc, gFalse); // TODO: parse into SVG font
+       } else {
+         error(getPos(), "Missing or bad Type3 CharProc entry");
+       }
+       //out->endType3Char(state);
+       if (resDict) {
+         popResources();
+       }
+       charProc.free();
+      }
+      restoreState();
+      // GfxState::restore() does *not* restore the current position,
+      // so we deal with it here using (curX, curY) and (lineX, lineY)
+      curX += tdx;
+      curY += tdy;
+      state->moveTo(curX, curY);
+      state->textSetPos(lineX, lineY);
+      p += n;
+      len -= n;
+    }
+    parser = oldParser;
+
+  } else if (1){//out->useDrawChar()) {
+    state->textTransformDelta(0, state->getRise(), &riseX, &riseY);
+    p = s->getCString();
+    len = s->getLength();
+    while (len > 0) {
+      n = font->getNextChar(p, len, &code,
+                           u, (int)(sizeof(u) / sizeof(Unicode)), &uLen,
+                           &dx, &dy, &originX, &originY);
+      
+      if (uLen > 0) {
+          gchar *sc = unicode_to_char((Unicode*)&u, uLen);
+          printf("%s", sc);
+          g_free(sc);
+      }
+      if (wMode) {
+       dx *= state->getFontSize();
+       dy = dy * state->getFontSize() + state->getCharSpace();
+       if (n == 1 && *p == ' ') {
+         dy += state->getWordSpace();
+       }
+      } else {
+       dx = dx * state->getFontSize() + state->getCharSpace();
+       if (n == 1 && *p == ' ') {
+         dx += state->getWordSpace();
+       }
+       dx *= state->getHorizScaling();
+       dy *= state->getFontSize();
+      }
+      state->textTransformDelta(dx, dy, &tdx, &tdy);
+      originX *= state->getFontSize();
+      originY *= state->getFontSize();
+      state->textTransformDelta(originX, originY, &tOriginX, &tOriginY);
+//       out->drawChar(state, state->getCurX() + riseX, state->getCurY() + riseY,
+//                 tdx, tdy, tOriginX, tOriginY, code, n, u, uLen);
+      state->shift(tdx, tdy);
+      p += n;
+      len -= n;
+    }
+
+  } else {
+    dx = dy = 0;
+    p = s->getCString();
+    len = s->getLength();
+    nChars = nSpaces = 0;
+    while (len > 0) {
+      n = font->getNextChar(p, len, &code,
+                           u, (int)(sizeof(u) / sizeof(Unicode)), &uLen,
+                           &dx2, &dy2, &originX, &originY);
+      dx += dx2;
+      dy += dy2;
+      if (n == 1 && *p == ' ') {
+       ++nSpaces;
+      }
+      ++nChars;
+      p += n;
+      len -= n;
+    }
+    if (wMode) {
+      dx *= state->getFontSize();
+      dy = dy * state->getFontSize()
+          + nChars * state->getCharSpace()
+          + nSpaces * state->getWordSpace();
+    } else {
+      dx = dx * state->getFontSize()
+          + nChars * state->getCharSpace()
+          + nSpaces * state->getWordSpace();
+      dx *= state->getHorizScaling();
+      dy *= state->getFontSize();
+    }
+    state->textTransformDelta(dx, dy, &tdx, &tdy);
+//    out->drawString(state, s);
+    state->shift(tdx, tdy);
+  }
+
+//   if (out->useDrawChar()) {
+//     out->endString(state);
+//   }
+
+  updateLevel += 10 * s->getLength();
+}
+
+//------------------------------------------------------------------------
+// XObject operators
+//------------------------------------------------------------------------
+
+void PdfParser::opXObject(Object args[], int numArgs) {
+  char *name;
+  Object obj1, obj2, obj3, refObj;
+
+  name = args[0].getName();
+  if (!res->lookupXObject(name, &obj1)) {
+    return;
+  }
+  if (!obj1.isStream()) {
+    error(getPos(), "XObject '%s' is wrong type", name);
+    obj1.free();
+    return;
+  }
+  obj1.streamGetDict()->lookup("Subtype", &obj2);
+  if (obj2.isName("Image")) {
+    res->lookupXObjectNF(name, &refObj);
+    doImage(&refObj, obj1.getStream(), gFalse);
+    refObj.free();
+  } else if (obj2.isName("Form")) {
+    doForm(&obj1);
+  } else if (obj2.isName("PS")) {
+    obj1.streamGetDict()->lookup("Level1", &obj3);
+/*    out->psXObject(obj1.getStream(),
+                  obj3.isStream() ? obj3.getStream() : (Stream *)NULL);*/
+  } else if (obj2.isName()) {
+    error(getPos(), "Unknown XObject subtype '%s'", obj2.getName());
+  } else {
+    error(getPos(), "XObject subtype is missing or wrong type");
+  }
+  obj2.free();
+  obj1.free();
+}
+
+void PdfParser::doImage(Object *ref, Stream *str, GBool inlineImg) {
+  Dict *dict, *maskDict;
+  int width, height;
+  int bits, maskBits;
+  StreamColorSpaceMode csMode;
+  GBool mask;
+  GBool invert;
+  GfxColorSpace *colorSpace, *maskColorSpace;
+  GfxImageColorMap *colorMap, *maskColorMap;
+  Object maskObj, smaskObj;
+  GBool haveColorKeyMask, haveExplicitMask, haveSoftMask;
+  int maskColors[2*gfxColorMaxComps];
+  int maskWidth, maskHeight;
+  GBool maskInvert;
+  Stream *maskStr;
+  Object obj1, obj2;
+  int i;
+
+  // get info from the stream
+  bits = 0;
+  csMode = streamCSNone;
+  str->getImageParams(&bits, &csMode);
+
+  // get stream dict
+  dict = str->getDict();
+
+  // get size
+  dict->lookup("Width", &obj1);
+  if (obj1.isNull()) {
+    obj1.free();
+    dict->lookup("W", &obj1);
+  }
+  if (obj1.isInt())
+    width = obj1.getInt();
+  else if (obj1.isReal())
+    width = (int)obj1.getReal();
+  else
+    goto err2;
+  obj1.free();
+  dict->lookup("Height", &obj1);
+  if (obj1.isNull()) {
+    obj1.free();
+    dict->lookup("H", &obj1);
+  }
+  if (obj1.isInt())
+    height = obj1.getInt();
+  else if (obj1.isReal())
+    height = (int)obj1.getReal();
+  else
+    goto err2;
+  obj1.free();
+
+  // image or mask?
+  dict->lookup("ImageMask", &obj1);
+  if (obj1.isNull()) {
+    obj1.free();
+    dict->lookup("IM", &obj1);
+  }
+  mask = gFalse;
+  if (obj1.isBool())
+    mask = obj1.getBool();
+  else if (!obj1.isNull())
+    goto err2;
+  obj1.free();
+
+  // bit depth
+  if (bits == 0) {
+    dict->lookup("BitsPerComponent", &obj1);
+    if (obj1.isNull()) {
+      obj1.free();
+      dict->lookup("BPC", &obj1);
+    }
+    if (obj1.isInt()) {
+      bits = obj1.getInt();
+    } else if (mask) {
+      bits = 1;
+    } else {
+      goto err2;
+    }
+    obj1.free();
+  }
+
+  // display a mask
+  if (mask) {
+
+    // check for inverted mask
+    if (bits != 1)
+      goto err1;
+    invert = gFalse;
+    dict->lookup("Decode", &obj1);
+    if (obj1.isNull()) {
+      obj1.free();
+      dict->lookup("D", &obj1);
+    }
+    if (obj1.isArray()) {
+      obj1.arrayGet(0, &obj2);
+      if (obj2.isInt() && obj2.getInt() == 1)
+       invert = gTrue;
+      obj2.free();
+    } else if (!obj1.isNull()) {
+      goto err2;
+    }
+    obj1.free();
+
+    // draw it
+    //out->drawImageMask(state, ref, str, width, height, invert, inlineImg);
+
+  } else {
+
+    // get color space and color map
+    dict->lookup("ColorSpace", &obj1);
+    if (obj1.isNull()) {
+      obj1.free();
+      dict->lookup("CS", &obj1);
+    }
+    if (obj1.isName()) {
+      res->lookupColorSpace(obj1.getName(), &obj2);
+      if (!obj2.isNull()) {
+       obj1.free();
+       obj1 = obj2;
+      } else {
+       obj2.free();
+      }
+    }
+    if (!obj1.isNull()) {
+      colorSpace = GfxColorSpace::parse(&obj1);
+    } else if (csMode == streamCSDeviceGray) {
+      colorSpace = new GfxDeviceGrayColorSpace();
+    } else if (csMode == streamCSDeviceRGB) {
+      colorSpace = new GfxDeviceRGBColorSpace();
+    } else if (csMode == streamCSDeviceCMYK) {
+      colorSpace = new GfxDeviceCMYKColorSpace();
+    } else {
+      colorSpace = NULL;
+    }
+    obj1.free();
+    if (!colorSpace) {
+      goto err1;
+    }
+    dict->lookup("Decode", &obj1);
+    if (obj1.isNull()) {
+      obj1.free();
+      dict->lookup("D", &obj1);
+    }
+    colorMap = new GfxImageColorMap(bits, &obj1, colorSpace);
+    obj1.free();
+    if (!colorMap->isOk()) {
+      delete colorMap;
+      goto err1;
+    }
+
+    // get the mask
+    haveColorKeyMask = haveExplicitMask = haveSoftMask = gFalse;
+    maskStr = NULL; // make gcc happy
+    maskWidth = maskHeight = 0; // make gcc happy
+    maskInvert = gFalse; // make gcc happy
+    maskColorMap = NULL; // make gcc happy
+    dict->lookup("Mask", &maskObj);
+    dict->lookup("SMask", &smaskObj);
+    if (smaskObj.isStream()) {
+      // soft mask
+      if (inlineImg) {
+       goto err1;
+      }
+      maskStr = smaskObj.getStream();
+      maskDict = smaskObj.streamGetDict();
+      maskDict->lookup("Width", &obj1);
+      if (obj1.isNull()) {
+       obj1.free();
+       maskDict->lookup("W", &obj1);
+      }
+      if (!obj1.isInt()) {
+       goto err2;
+      }
+      maskWidth = obj1.getInt();
+      obj1.free();
+      maskDict->lookup("Height", &obj1);
+      if (obj1.isNull()) {
+       obj1.free();
+       maskDict->lookup("H", &obj1);
+      }
+      if (!obj1.isInt()) {
+       goto err2;
+      }
+      maskHeight = obj1.getInt();
+      obj1.free();
+      maskDict->lookup("BitsPerComponent", &obj1);
+      if (obj1.isNull()) {
+       obj1.free();
+       maskDict->lookup("BPC", &obj1);
+      }
+      if (!obj1.isInt()) {
+       goto err2;
+      }
+      maskBits = obj1.getInt();
+      obj1.free();
+      maskDict->lookup("ColorSpace", &obj1);
+      if (obj1.isNull()) {
+       obj1.free();
+       maskDict->lookup("CS", &obj1);
+      }
+      if (obj1.isName()) {
+       res->lookupColorSpace(obj1.getName(), &obj2);
+       if (!obj2.isNull()) {
+         obj1.free();
+         obj1 = obj2;
+       } else {
+         obj2.free();
+       }
+      }
+      maskColorSpace = GfxColorSpace::parse(&obj1);
+      obj1.free();
+      if (!maskColorSpace || maskColorSpace->getMode() != csDeviceGray) {
+       goto err1;
+      }
+      maskDict->lookup("Decode", &obj1);
+      if (obj1.isNull()) {
+       obj1.free();
+       maskDict->lookup("D", &obj1);
+      }
+      maskColorMap = new GfxImageColorMap(maskBits, &obj1, maskColorSpace);
+      obj1.free();
+      if (!maskColorMap->isOk()) {
+       delete maskColorMap;
+       goto err1;
+      }
+      //~ handle the Matte entry
+      haveSoftMask = gTrue;
+    } else if (maskObj.isArray()) {
+      // color key mask
+      for (i = 0;
+          i < maskObj.arrayGetLength() && i < 2*gfxColorMaxComps;
+          ++i) {
+       maskObj.arrayGet(i, &obj1);
+       maskColors[i] = obj1.getInt();
+       obj1.free();
+      }
+      haveColorKeyMask = gTrue;
+    } else if (maskObj.isStream()) {
+      // explicit mask
+      if (inlineImg) {
+       goto err1;
+      }
+      maskStr = maskObj.getStream();
+      maskDict = maskObj.streamGetDict();
+      maskDict->lookup("Width", &obj1);
+      if (obj1.isNull()) {
+       obj1.free();
+       maskDict->lookup("W", &obj1);
+      }
+      if (!obj1.isInt()) {
+       goto err2;
+      }
+      maskWidth = obj1.getInt();
+      obj1.free();
+      maskDict->lookup("Height", &obj1);
+      if (obj1.isNull()) {
+       obj1.free();
+       maskDict->lookup("H", &obj1);
+      }
+      if (!obj1.isInt()) {
+       goto err2;
+      }
+      maskHeight = obj1.getInt();
+      obj1.free();
+      maskDict->lookup("ImageMask", &obj1);
+      if (obj1.isNull()) {
+       obj1.free();
+       maskDict->lookup("IM", &obj1);
+      }
+      if (!obj1.isBool() || !obj1.getBool()) {
+       goto err2;
+      }
+      obj1.free();
+      maskInvert = gFalse;
+      maskDict->lookup("Decode", &obj1);
+      if (obj1.isNull()) {
+       obj1.free();
+       maskDict->lookup("D", &obj1);
+      }
+      if (obj1.isArray()) {
+       obj1.arrayGet(0, &obj2);
+       if (obj2.isInt() && obj2.getInt() == 1) {
+         maskInvert = gTrue;
+       }
+       obj2.free();
+      } else if (!obj1.isNull()) {
+       goto err2;
+      }
+      obj1.free();
+      haveExplicitMask = gTrue;
+    }
+
+    // draw it
+    if (haveSoftMask) {
+/*      out->drawSoftMaskedImage(state, ref, str, width, height, colorMap,
+                              maskStr, maskWidth, maskHeight, maskColorMap);*/
+      delete maskColorMap;
+    } else if (haveExplicitMask) {
+/*      out->drawMaskedImage(state, ref, str, width, height, colorMap,
+                          maskStr, maskWidth, maskHeight, maskInvert);*/
+    } else {
+/*      out->drawImage(state, ref, str, width, height, colorMap,
+                    haveColorKeyMask ? maskColors : (int *)NULL, inlineImg);*/
+    }
+    delete colorMap;
+
+    maskObj.free();
+    smaskObj.free();
+  }
+
+  if ((i = width * height) > 1000) {
+    i = 1000;
+  }
+  updateLevel += i;
+
+  return;
+
+ err2:
+  obj1.free();
+ err1:
+  error(getPos(), "Bad image parameters");
+}
+
+void PdfParser::doForm(Object *str) {
+  Dict *dict;
+  GBool transpGroup, isolated, knockout;
+  GfxColorSpace *blendingColorSpace;
+  Object matrixObj, bboxObj;
+  double m[6], bbox[4];
+  Object resObj;
+  Dict *resDict;
+  Object obj1, obj2, obj3;
+  int i;
+
+  // check for excessive recursion
+  if (formDepth > 20) {
+    return;
+  }
+
+  // get stream dict
+  dict = str->streamGetDict();
+
+  // check form type
+  dict->lookup("FormType", &obj1);
+  if (!(obj1.isNull() || (obj1.isInt() && obj1.getInt() == 1))) {
+    error(getPos(), "Unknown form type");
+  }
+  obj1.free();
+
+  // get bounding box
+  dict->lookup("BBox", &bboxObj);
+  if (!bboxObj.isArray()) {
+    bboxObj.free();
+    error(getPos(), "Bad form bounding box");
+    return;
+  }
+  for (i = 0; i < 4; ++i) {
+    bboxObj.arrayGet(i, &obj1);
+    bbox[i] = obj1.getNum();
+    obj1.free();
+  }
+  bboxObj.free();
+
+  // get matrix
+  dict->lookup("Matrix", &matrixObj);
+  if (matrixObj.isArray()) {
+    for (i = 0; i < 6; ++i) {
+      matrixObj.arrayGet(i, &obj1);
+      m[i] = obj1.getNum();
+      obj1.free();
+    }
+  } else {
+    m[0] = 1; m[1] = 0;
+    m[2] = 0; m[3] = 1;
+    m[4] = 0; m[5] = 0;
+  }
+  matrixObj.free();
+
+  // get resources
+  dict->lookup("Resources", &resObj);
+  resDict = resObj.isDict() ? resObj.getDict() : (Dict *)NULL;
+
+  // check for a transparency group
+  transpGroup = isolated = knockout = gFalse;
+  blendingColorSpace = NULL;
+  if (dict->lookup("Group", &obj1)->isDict()) {
+    if (obj1.dictLookup("S", &obj2)->isName("Transparency")) {
+      transpGroup = gTrue;
+      if (!obj1.dictLookup("CS", &obj3)->isNull()) {
+       blendingColorSpace = GfxColorSpace::parse(&obj3);
+      }
+      obj3.free();
+      if (obj1.dictLookup("I", &obj3)->isBool()) {
+       isolated = obj3.getBool();
+      }
+      obj3.free();
+      if (obj1.dictLookup("K", &obj3)->isBool()) {
+       knockout = obj3.getBool();
+      }
+      obj3.free();
+    }
+    obj2.free();
+  }
+  obj1.free();
+
+  // draw it
+  ++formDepth;
+  doForm1(str, resDict, m, bbox,
+         transpGroup, gFalse, blendingColorSpace, isolated, knockout);
+  --formDepth;
+
+  if (blendingColorSpace) {
+    delete blendingColorSpace;
+  }
+  resObj.free();
+}
+
+void PdfParser::doForm1(Object *str, Dict *resDict, double *matrix, double *bbox,
+                 GBool transpGroup, GBool softMask,
+                 GfxColorSpace *blendingColorSpace,
+                 GBool isolated, GBool knockout,
+                 GBool alpha, Function *transferFunc,
+                 GfxColor *backdropColor) {
+  Parser *oldParser;
+  double oldBaseMatrix[6];
+  int i;
+
+  // push new resources on stack
+  pushResources(resDict);
+
+  // save current graphics state
+  saveState();
+
+  // kill any pre-existing path
+  state->clearPath();
+
+  // save current parser
+  oldParser = parser;
+
+  // set form transformation matrix
+  state->concatCTM(matrix[0], matrix[1], matrix[2],
+                  matrix[3], matrix[4], matrix[5]);
+  builder->setTransform(matrix[0], matrix[1], matrix[2],
+                        matrix[3], matrix[4], matrix[5]);
+
+  // set form bounding box
+  state->moveTo(bbox[0], bbox[1]);
+  state->lineTo(bbox[2], bbox[1]);
+  state->lineTo(bbox[2], bbox[3]);
+  state->lineTo(bbox[0], bbox[3]);
+  state->closePath();
+  state->clip();
+  builder->clip(state);
+  state->clearPath();
+
+  if (softMask || transpGroup) {
+    if (state->getBlendMode() != gfxBlendNormal) {
+      state->setBlendMode(gfxBlendNormal);
+    }
+    if (state->getFillOpacity() != 1) {
+      state->setFillOpacity(1);
+    }
+    if (state->getStrokeOpacity() != 1) {
+      state->setStrokeOpacity(1);
+    }
+    //out->clearSoftMask(state);
+    //out->beginTransparencyGroup(state, bbox, blendingColorSpace,
+    //                         isolated, knockout, softMask);
+  }
+
+  // set new base matrix
+  for (i = 0; i < 6; ++i) {
+    oldBaseMatrix[i] = baseMatrix[i];
+    baseMatrix[i] = state->getCTM()[i];
+  }
+
+  // draw the form
+  parse(str, gFalse);
+
+  if (softMask || transpGroup) {
+    //out->endTransparencyGroup(state);
+  }
+
+  // restore base matrix
+  for (i = 0; i < 6; ++i) {
+    baseMatrix[i] = oldBaseMatrix[i];
+  }
+
+  // restore parser
+  parser = oldParser;
+
+  // restore graphics state
+  restoreState();
+
+  // pop resource stack
+  popResources();
+
+  if (softMask) {
+    //out->setSoftMask(state, bbox, alpha, transferFunc, backdropColor);
+  } else if (transpGroup) {
+    //out->paintTransparencyGroup(state, bbox);
+  }
+
+  return;
+}
+
+//------------------------------------------------------------------------
+// in-line image operators
+//------------------------------------------------------------------------
+
+void PdfParser::opBeginImage(Object args[], int numArgs) {
+  Stream *str;
+  int c1, c2;
+
+  // build dict/stream
+  str = buildImageStream();
+
+  // display the image
+  if (str) {
+    doImage(NULL, str, gTrue);
+  
+    // skip 'EI' tag
+    c1 = str->getUndecodedStream()->getChar();
+    c2 = str->getUndecodedStream()->getChar();
+    while (!(c1 == 'E' && c2 == 'I') && c2 != EOF) {
+      c1 = c2;
+      c2 = str->getUndecodedStream()->getChar();
+    }
+    delete str;
+  }
+}
+
+Stream *PdfParser::buildImageStream() {
+  Object dict;
+  Object obj;
+  char *key;
+  Stream *str;
+
+  // build dictionary
+  dict.initDict(xref);
+  parser->getObj(&obj);
+  while (!obj.isCmd("ID") && !obj.isEOF()) {
+    if (!obj.isName()) {
+      error(getPos(), "Inline image dictionary key must be a name object");
+      obj.free();
+    } else {
+      key = copyString(obj.getName());
+      obj.free();
+      parser->getObj(&obj);
+      if (obj.isEOF() || obj.isError()) {
+       gfree(key);
+       break;
+      }
+      dict.dictAdd(key, &obj);
+    }
+    parser->getObj(&obj);
+  }
+  if (obj.isEOF()) {
+    error(getPos(), "End of file in inline image");
+    obj.free();
+    dict.free();
+    return NULL;
+  }
+  obj.free();
+
+  // make stream
+  str = new EmbedStream(parser->getStream(), &dict, gFalse, 0);
+  str = str->addFilters(&dict);
+
+  return str;
+}
+
+void PdfParser::opImageData(Object args[], int numArgs) {
+  error(getPos(), "Internal: got 'ID' operator");
+}
+
+void PdfParser::opEndImage(Object args[], int numArgs) {
+  error(getPos(), "Internal: got 'EI' operator");
+}
+
+//------------------------------------------------------------------------
+// type 3 font operators
+//------------------------------------------------------------------------
+
+void PdfParser::opSetCharWidth(Object args[], int numArgs) {
+}
+
+void PdfParser::opSetCacheDevice(Object args[], int numArgs) {
+}
+
+//------------------------------------------------------------------------
+// compatibility operators
+//------------------------------------------------------------------------
+
+void PdfParser::opBeginIgnoreUndef(Object args[], int numArgs) {
+  ++ignoreUndef;
+}
+
+void PdfParser::opEndIgnoreUndef(Object args[], int numArgs) {
+  if (ignoreUndef > 0)
+    --ignoreUndef;
+}
+
+//------------------------------------------------------------------------
+// marked content operators
+//------------------------------------------------------------------------
+
+void PdfParser::opBeginMarkedContent(Object args[], int numArgs) {
+  if (printCommands) {
+    printf("  marked content: %s ", args[0].getName());
+    if (numArgs == 2)
+      args[2].print(stdout);
+    printf("\n");
+    fflush(stdout);
+  }
+
+  if(numArgs == 2) {
+    //out->beginMarkedContent(args[0].getName(),args[1].getDict());
+  } else {
+    //out->beginMarkedContent(args[0].getName());
+  }
+}
+
+void PdfParser::opEndMarkedContent(Object args[], int numArgs) {
+  //out->endMarkedContent();
+}
+
+void PdfParser::opMarkPoint(Object args[], int numArgs) {
+  if (printCommands) {
+    printf("  mark point: %s ", args[0].getName());
+    if (numArgs == 2)
+      args[2].print(stdout);
+    printf("\n");
+    fflush(stdout);
+  }
+
+  if(numArgs == 2) {
+    //out->markPoint(args[0].getName(),args[1].getDict());
+  } else {
+    //out->markPoint(args[0].getName());
+  }
+
+}
+
+//------------------------------------------------------------------------
+// misc
+//------------------------------------------------------------------------
+
+void PdfParser::saveState() {
+  builder->saveState();
+  state = state->save();
+}
+
+void PdfParser::restoreState() {
+  state = state->restore();
+  builder->restoreState();
+}
+
+void PdfParser::pushResources(Dict *resDict) {
+  res = new GfxResources(xref, resDict, res);
+}
+
+void PdfParser::popResources() {
+  GfxResources *resPtr;
+
+  resPtr = res->getNext();
+  delete res;
+  res = resPtr;
+}
+
+#endif /* HAVE_POPPLER */
diff --git a/src/extension/internal/pdfinput/pdf-parser.h b/src/extension/internal/pdfinput/pdf-parser.h
new file mode 100644 (file)
index 0000000..4c66769
--- /dev/null
@@ -0,0 +1,303 @@
+
+ /** \file
+ * PDF parsing module using libpoppler's facilities
+ *
+ * Derived from Gfx.h
+ *
+ * Copyright 1996-2003 Glyph & Cog, LLC
+ *
+ */
+
+#ifndef PDF_PARSER_H
+#define PDF_PARSER_H
+
+#ifdef HAVE_POPPLER
+
+#ifdef USE_GCC_PRAGMAS
+#pragma interface
+#endif
+
+namespace Inkscape {
+    namespace Extension {
+        namespace Internal {
+                class SvgBuilder;
+        }
+    }
+}
+using Inkscape::Extension::Internal::SvgBuilder;
+
+#include "goo/gtypes.h"
+#include "Object.h"
+
+class GooString;
+class XRef;
+class Array;
+class Stream;
+class Parser;
+class Dict;
+class Function;
+class OutputDev;
+class GfxFontDict;
+class GfxFont;
+class GfxPattern;
+class GfxTilingPattern;
+class GfxShadingPattern;
+class GfxShading;
+class GfxFunctionShading;
+class GfxAxialShading;
+class GfxRadialShading;
+class GfxGouraudTriangleShading;
+class GfxPatchMeshShading;
+struct GfxPatch;
+class GfxState;
+struct GfxColor;
+class GfxColorSpace;
+class Gfx;
+class GfxResources;
+class PDFRectangle;
+class AnnotBorderStyle;
+
+class PdfParser;
+
+//------------------------------------------------------------------------
+
+#ifndef GFX_H
+enum GfxClipType {
+    clipNone,
+    clipNormal,
+    clipEO
+};
+
+enum TchkType {
+    tchkBool,                     // boolean
+    tchkInt,                      // integer
+    tchkNum,                      // number (integer or real)
+    tchkString,                   // string
+    tchkName,                     // name
+    tchkArray,                    // array
+    tchkProps,                    // properties (dictionary or name)
+    tchkSCN,                      // scn/SCN args (number of name)
+    tchkNone                      // used to avoid empty initializer lists
+};
+#endif /* GFX_H */
+
+#define maxOperatorArgs 33
+
+struct PdfOperator {
+    char name[4];
+    int numArgs;
+    TchkType tchk[maxOperatorArgs];
+    void (PdfParser::*func)(Object args[], int numArgs);
+};
+
+#undef maxOperatorArgs
+
+struct OpHistoryEntry {
+    const char *name;       // operator's name
+    GfxState *state;        // saved state, NULL if none
+    GBool executed;         // whether the operator has been executed
+
+    OpHistoryEntry *next;   // next entry on stack
+    unsigned depth;         // total number of entries descending from this
+};
+
+//------------------------------------------------------------------------
+// PdfParser
+//------------------------------------------------------------------------
+
+class PdfParser {
+public:
+
+  // Constructor for regular output.
+  PdfParser(XRef *xrefA, SvgBuilder *builderA,
+            int pageNum, Dict *resDict, PDFRectangle *cropBox,
+            GBool (*abortCheckCbkA)(void *data) = NULL,
+            void *abortCheckCbkDataA = NULL);
+
+  ~PdfParser();
+
+  // Interpret a stream or array of streams.
+  void parse(Object *obj, GBool topLevel = gTrue);
+
+  // Save graphics state.
+  void saveState();
+
+  // Restore graphics state.
+  void restoreState();
+
+  // Get the current graphics state object.
+  GfxState *getState() { return state; }
+
+private:
+
+  XRef *xref;                  // the xref table for this PDF file
+  SvgBuilder *builder;          // SVG generator
+  GBool subPage;               // is this a sub-page object?
+  GBool printCommands;         // print the drawing commands (for debugging)
+  GfxResources *res;           // resource stack
+  int updateLevel;
+
+  GfxState *state;             // current graphics state
+  GBool fontChanged;           // set if font or text matrix has changed
+  GfxClipType clip;            // do a clip?
+  int ignoreUndef;             // current BX/EX nesting level
+  double baseMatrix[6];                // default matrix for most recent
+                               //   page/form/pattern
+  int formDepth;
+
+  Parser *parser;              // parser for page content stream(s)
+
+  GBool                                // callback to check for an abort
+    (*abortCheckCbk)(void *data);
+  void *abortCheckCbkData;
+
+  static PdfOperator opTab[];  // table of operators
+
+  OpHistoryEntry *operatorHistory;  // list containing the last N operators
+  void pushOperator(const char *name);
+  OpHistoryEntry *popOperator();
+  const char *getPreviousOperator();    // returns the previous operator's name
+
+  void go(GBool topLevel);
+  void execOp(Object *cmd, Object args[], int numArgs);
+  PdfOperator *findOp(char *name);
+  GBool checkArg(Object *arg, TchkType type);
+  int getPos();
+
+  // graphics state operators
+  void opSave(Object args[], int numArgs);
+  void opRestore(Object args[], int numArgs);
+  void opConcat(Object args[], int numArgs);
+  void opSetDash(Object args[], int numArgs);
+  void opSetFlat(Object args[], int numArgs);
+  void opSetLineJoin(Object args[], int numArgs);
+  void opSetLineCap(Object args[], int numArgs);
+  void opSetMiterLimit(Object args[], int numArgs);
+  void opSetLineWidth(Object args[], int numArgs);
+  void opSetExtGState(Object args[], int numArgs);
+  void doSoftMask(Object *str, GBool alpha,
+                 GfxColorSpace *blendingColorSpace,
+                 GBool isolated, GBool knockout,
+                 Function *transferFunc, GfxColor *backdropColor);
+  void opSetRenderingIntent(Object args[], int numArgs);
+
+  // color operators
+  void opSetFillGray(Object args[], int numArgs);
+  void opSetStrokeGray(Object args[], int numArgs);
+  void opSetFillCMYKColor(Object args[], int numArgs);
+  void opSetStrokeCMYKColor(Object args[], int numArgs);
+  void opSetFillRGBColor(Object args[], int numArgs);
+  void opSetStrokeRGBColor(Object args[], int numArgs);
+  void opSetFillColorSpace(Object args[], int numArgs);
+  void opSetStrokeColorSpace(Object args[], int numArgs);
+  void opSetFillColor(Object args[], int numArgs);
+  void opSetStrokeColor(Object args[], int numArgs);
+  void opSetFillColorN(Object args[], int numArgs);
+  void opSetStrokeColorN(Object args[], int numArgs);
+
+  // path segment operators
+  void opMoveTo(Object args[], int numArgs);
+  void opLineTo(Object args[], int numArgs);
+  void opCurveTo(Object args[], int numArgs);
+  void opCurveTo1(Object args[], int numArgs);
+  void opCurveTo2(Object args[], int numArgs);
+  void opRectangle(Object args[], int numArgs);
+  void opClosePath(Object args[], int numArgs);
+
+  // path painting operators
+  void opEndPath(Object args[], int numArgs);
+  void opStroke(Object args[], int numArgs);
+  void opCloseStroke(Object args[], int numArgs);
+  void opFill(Object args[], int numArgs);
+  void opEOFill(Object args[], int numArgs);
+  void opFillStroke(Object args[], int numArgs);
+  void opCloseFillStroke(Object args[], int numArgs);
+  void opEOFillStroke(Object args[], int numArgs);
+  void opCloseEOFillStroke(Object args[], int numArgs);
+  void doFillAndStroke(GBool eoFill);
+  void doPatternFillFallback(GBool eoFill);
+  void doPatternStrokeFallback();
+  void doShadingPatternFillFallback(GfxShadingPattern *sPat,
+                                    GBool stroke, GBool eoFill);
+  void opShFill(Object args[], int numArgs);
+  void doFunctionShFill(GfxFunctionShading *shading);
+  void doFunctionShFill1(GfxFunctionShading *shading,
+                        double x0, double y0,
+                        double x1, double y1,
+                        GfxColor *colors, int depth);
+  void doGouraudTriangleShFill(GfxGouraudTriangleShading *shading);
+  void gouraudFillTriangle(double x0, double y0, GfxColor *color0,
+                          double x1, double y1, GfxColor *color1,
+                          double x2, double y2, GfxColor *color2,
+                          int nComps, int depth);
+  void doPatchMeshShFill(GfxPatchMeshShading *shading);
+  void fillPatch(GfxPatch *patch, int nComps, int depth);
+  void doEndPath();
+
+  // path clipping operators
+  void opClip(Object args[], int numArgs);
+  void opEOClip(Object args[], int numArgs);
+
+  // text object operators
+  void opBeginText(Object args[], int numArgs);
+  void opEndText(Object args[], int numArgs);
+
+  // text state operators
+  void opSetCharSpacing(Object args[], int numArgs);
+  void opSetFont(Object args[], int numArgs);
+  void opSetTextLeading(Object args[], int numArgs);
+  void opSetTextRender(Object args[], int numArgs);
+  void opSetTextRise(Object args[], int numArgs);
+  void opSetWordSpacing(Object args[], int numArgs);
+  void opSetHorizScaling(Object args[], int numArgs);
+
+  // text positioning operators
+  void opTextMove(Object args[], int numArgs);
+  void opTextMoveSet(Object args[], int numArgs);
+  void opSetTextMatrix(Object args[], int numArgs);
+  void opTextNextLine(Object args[], int numArgs);
+
+  // text string operators
+  void opShowText(Object args[], int numArgs);
+  void opMoveShowText(Object args[], int numArgs);
+  void opMoveSetShowText(Object args[], int numArgs);
+  void opShowSpaceText(Object args[], int numArgs);
+  void doShowText(GooString *s);
+
+  // XObject operators
+  void opXObject(Object args[], int numArgs);
+  void doImage(Object *ref, Stream *str, GBool inlineImg);
+  void doForm(Object *str);
+  void doForm1(Object *str, Dict *resDict, double *matrix, double *bbox,
+              GBool transpGroup = gFalse, GBool softMask = gFalse,
+              GfxColorSpace *blendingColorSpace = NULL,
+              GBool isolated = gFalse, GBool knockout = gFalse,
+              GBool alpha = gFalse, Function *transferFunc = NULL,
+              GfxColor *backdropColor = NULL);
+
+  // in-line image operators
+  void opBeginImage(Object args[], int numArgs);
+  Stream *buildImageStream();
+  void opImageData(Object args[], int numArgs);
+  void opEndImage(Object args[], int numArgs);
+
+  // type 3 font operators
+  void opSetCharWidth(Object args[], int numArgs);
+  void opSetCacheDevice(Object args[], int numArgs);
+
+  // compatibility operators
+  void opBeginIgnoreUndef(Object args[], int numArgs);
+  void opEndIgnoreUndef(Object args[], int numArgs);
+
+  // marked content operators
+  void opBeginMarkedContent(Object args[], int numArgs);
+  void opEndMarkedContent(Object args[], int numArgs);
+  void opMarkPoint(Object args[], int numArgs);
+
+  void pushResources(Dict *resDict);
+  void popResources();
+};
+
+#endif /* HAVE_POPPLER */
+
+#endif /* PDF_PARSER_H */
diff --git a/src/extension/internal/pdfinput/svg-builder.cpp b/src/extension/internal/pdfinput/svg-builder.cpp
new file mode 100644 (file)
index 0000000..9457d22
--- /dev/null
@@ -0,0 +1,608 @@
+ /** \file
+ * Native PDF import using libpoppler.
+ * 
+ * Authors:
+ *   miklos erdelyi
+ *
+ * Copyright (C) 2007 Authors
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#ifdef HAVE_POPPLER
+
+#include "svg-builder.h"
+#include "pdf-parser.h"
+
+#include "document-private.h"
+#include "xml/document.h"
+#include "xml/node.h"
+#include "xml/repr.h"
+#include "svg/svg.h"
+#include "svg/path-string.h"
+#include "svg/css-ostringstream.h"
+#include "svg/svg-color.h"
+#include "color.h"
+#include "unit-constants.h"
+
+#include "Function.h"
+#include "GfxState.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+
+#define LOG(expr) ( expr );
+
+/**
+ * \class SvgBuilder
+ * 
+ */
+
+SvgBuilder::SvgBuilder(SPDocument *document) {
+    doc = document;
+    xml_doc = sp_document_repr_doc(doc);
+    container = root = doc->rroot;
+}
+
+SvgBuilder::SvgBuilder(SvgBuilder *parent, Inkscape::XML::Node *root) {
+    doc = parent->doc;
+    xml_doc = parent->xml_doc;
+    container = this->root = root;
+}
+
+SvgBuilder::~SvgBuilder() {
+}
+
+void SvgBuilder::setDocumentSize(double width, double height) {
+    sp_repr_set_svg_double(root, "width", width);
+    sp_repr_set_svg_double(root, "height", height);
+    this->width = width;
+    this->height = height;
+}
+
+void SvgBuilder::saveState() {
+    groupDepth.push_back(0);
+    pushGroup();
+}
+
+void SvgBuilder::restoreState() {
+    int depth = groupDepth.back();
+    while (groupDepth.back() > 0) {
+        popGroup();
+    }
+    groupDepth.pop_back();
+}
+
+Inkscape::XML::Node *SvgBuilder::pushGroup() {
+    Inkscape::XML::Node *node = xml_doc->createElement("svg:g");
+    container->appendChild(node);
+    container = node;
+    Inkscape::GC::release(node);
+    groupDepth.back()++;
+
+    return container;
+}
+
+Inkscape::XML::Node *SvgBuilder::popGroup() {
+    if (container != root) {  // Pop if the current container isn't root
+        container = container->parent();
+        groupDepth[groupDepth.size()-1] = --groupDepth.back();
+    }
+
+    return container;
+}
+
+Inkscape::XML::Node *SvgBuilder::getContainer() {
+    return container;
+}
+
+static gchar *svgConvertRGBToText(double r, double g, double b) {
+    static gchar tmp[1023] = {0};
+    snprintf(tmp, 1023,
+             "rgb(%i, %i, %i)",
+             SP_COLOR_F_TO_U(r),
+             SP_COLOR_F_TO_U(g),
+             SP_COLOR_F_TO_U(b));
+    return (gchar*)&tmp;
+}
+
+static gchar *svgConvertGfxRGB(GfxRGB *color) {
+    double r = color->r / 65535.0;
+    double g = color->g / 65535.0;
+    double b = color->b / 65535.0;
+    return svgConvertRGBToText(r, g, b);
+}
+
+static gchar *svgInterpretTransform(double c0, double c1, double c2, double c3,
+                                    double c4, double c5) {
+    NR::Matrix matrix(c0, c1, c2, c3, c4, c5);
+    return sp_svg_transform_write(matrix);
+}
+
+static gchar *svgInterpretTransform(double *transform) {
+    return svgInterpretTransform(transform[0], transform[1], transform[2],
+                                 transform[3], transform[4], transform[5]);
+}
+
+static gchar *svgInterpretPath(GfxPath *path) {
+    GfxSubpath *subpath;
+    Inkscape::SVG::PathString pathString;
+    int i, j;
+    for (i = 0; i < path->getNumSubpaths(); ++i) {
+        subpath = path->getSubpath(i);
+        if (subpath->getNumPoints() > 0) {
+            pathString.moveTo(subpath->getX(0), subpath->getY(0));
+            j = 1;
+            while (j < subpath->getNumPoints()) {
+                if (subpath->getCurve(j)) {
+                    pathString.curveTo(subpath->getX(j), subpath->getY(j),
+                                       subpath->getX(j+1), subpath->getY(j+1),
+                                       subpath->getX(j+2), subpath->getY(j+2));
+
+                    j += 3;
+                } else {
+                    pathString.lineTo(subpath->getX(j), subpath->getY(j));
+                    ++j;
+                }
+            }
+            if (subpath->isClosed()) {
+                pathString.closePath();
+            }
+        }
+    }
+
+    return g_strdup(pathString.c_str());
+}
+
+/**
+ * \brief Sets stroke style from poppler's GfxState data structure
+ * Uses the given SPCSSAttr for storing the style properties
+ */
+void SvgBuilder::setStrokeStyle(SPCSSAttr *css, GfxState *state) {
+
+    // Check line width
+    if (state->getLineWidth() <= 0.0) {
+        // Ignore stroke
+        return;
+    }
+
+    // Stroke color/pattern
+    if (state->getStrokeColorSpace()->getMode() == csPattern) {
+        gchar *urltext = createPattern(state->getStrokePattern());
+        sp_repr_css_set_property(css, "stroke", urltext);
+        if (urltext) {
+            g_free(urltext);
+        }
+    } else {
+        GfxRGB stroke_color;
+        state->getStrokeRGB(&stroke_color);
+        sp_repr_css_set_property(css, "stroke", svgConvertGfxRGB(&stroke_color));
+    }
+
+    // Opacity
+    Inkscape::CSSOStringStream os_opacity;
+    os_opacity << state->getStrokeOpacity();
+    sp_repr_css_set_property(css, "stroke-opacity", os_opacity.str().c_str());
+
+    // Line width
+    Inkscape::CSSOStringStream os_width;
+    os_width << state->getLineWidth();
+    sp_repr_css_set_property(css, "stroke-width", os_width.str().c_str());
+
+    // Line cap
+    switch (state->getLineCap()) {
+        case 0:
+            sp_repr_css_set_property(css, "stroke-linecap", "butt");
+            break;
+        case 1:
+            sp_repr_css_set_property(css, "stroke-linecap", "round");
+            break;
+        case 2:
+            sp_repr_css_set_property(css, "stroke-linecap", "square");
+            break;
+    }
+
+    // Line join
+    switch (state->getLineJoin()) {
+        case 0:
+            sp_repr_css_set_property(css, "stroke-linejoin", "miter");
+            break;
+        case 1:
+            sp_repr_css_set_property(css, "stroke-linejoin", "round");
+            break;
+        case 2:
+            sp_repr_css_set_property(css, "stroke-linejoin", "bevel");
+            break;
+    }
+
+    // Miterlimit
+    Inkscape::CSSOStringStream os_ml;
+    os_ml << state->getMiterLimit();
+    sp_repr_css_set_property(css, "stroke-miterlimit", os_ml.str().c_str());
+
+    // Line dash
+    double *dash_pattern;
+    int dash_length;
+    double dash_start;
+    state->getLineDash(&dash_pattern, &dash_length, &dash_start);
+    if (dash_length > 0) {
+        Inkscape::CSSOStringStream os_array;
+        for (int i = 0; i < dash_length; i++) {
+            os_array << dash_pattern[i];
+            if (i < (dash_length - 1)) {
+                os_array << ",";
+            }
+        }
+        printf("aaaa %s\n", os_array.str().c_str());
+        sp_repr_css_set_property(css, "stroke-dasharray", os_array.str().c_str());
+
+        Inkscape::CSSOStringStream os_offset;
+        os_offset << dash_start;
+        sp_repr_css_set_property(css, "stroke-dashoffset", os_offset.str().c_str());
+    } else {
+        sp_repr_css_set_property(css, "stroke-dasharray", "none");
+        sp_repr_css_set_property(css, "stroke-dashoffset", NULL);
+    }
+}
+
+/**
+ * \brief Sets fill style from poppler's GfxState data structure
+ * Uses the given SPCSSAttr for storing the style properties.
+ */
+void SvgBuilder::setFillStyle(SPCSSAttr *css, GfxState *state, bool even_odd) {
+
+    // Fill color/pattern
+    if (state->getFillColorSpace()->getMode() == csPattern) {
+        gchar *urltext = createPattern(state->getFillPattern());
+        sp_repr_css_set_property(css, "fill", urltext);
+        if (urltext) {
+            g_free(urltext);
+        }
+    } else {
+        GfxRGB fill_color;
+        state->getFillRGB(&fill_color);
+        sp_repr_css_set_property(css, "fill", svgConvertGfxRGB(&fill_color));
+    }
+
+    // Opacity
+    Inkscape::CSSOStringStream os_opacity;
+    os_opacity << state->getFillOpacity();
+    sp_repr_css_set_property(css, "fill-opacity", os_opacity.str().c_str());
+    
+    // Fill rule
+    sp_repr_css_set_property(css, "fill-rule", even_odd ? "evenodd" : "nonzero");
+}
+
+/**
+ * \brief Sets style properties from poppler's GfxState data structure
+ * \return SPCSSAttr with all the relevant properties set
+ */
+SPCSSAttr *SvgBuilder::setStyle(GfxState *state, bool fill, bool stroke, bool even_odd) {
+    SPCSSAttr *css = sp_repr_css_attr_new();
+    if (fill) {
+        setFillStyle(css, state, even_odd);
+    } else {
+        sp_repr_css_set_property(css, "fill", "none");
+    }
+    
+    if (stroke) {
+        setStrokeStyle(css, state);
+    } else {
+        sp_repr_css_set_property(css, "stroke", "none");
+    }
+
+    return css;
+}
+
+/**
+ * \brief Emits the current path in poppler's GfxState data structure
+ * Can be used to do filling and stroking at once.
+ *
+ * \param fill whether the path should be filled
+ * \param stroke whether the path should be stroked
+ * \param even_odd whether the even-odd rule should be used when filling the path
+ */
+void SvgBuilder::addPath(GfxState *state, bool fill, bool stroke, bool even_odd) {
+    Inkscape::XML::Node *path = xml_doc->createElement("svg:path");
+    gchar *pathtext = svgInterpretPath(state->getPath());
+    path->setAttribute("d", pathtext);
+    g_free(pathtext);
+
+    // Set style
+    SPCSSAttr *css = setStyle(state, fill, stroke, even_odd);
+    sp_repr_css_change(path, css, "style");
+    sp_repr_css_attr_unref(css);
+
+    container->appendChild(path);
+    Inkscape::GC::release(path);
+}
+
+void SvgBuilder::clip(GfxState *state, bool even_odd) {
+    pushGroup();
+    setClipPath(state, even_odd);
+}
+
+void SvgBuilder::setClipPath(GfxState *state, bool even_odd) {
+    // Create the clipPath repr
+    Inkscape::XML::Node *clip_path = xml_doc->createElement("svg:clipPath");
+    clip_path->setAttribute("clipPathUnits", "userSpaceOnUse");
+    // Create the path
+    Inkscape::XML::Node *path = xml_doc->createElement("svg:path");
+    gchar *pathtext = svgInterpretPath(state->getPath());
+    path->setAttribute("d", pathtext);
+    g_free(pathtext);
+    clip_path->appendChild(path);
+    Inkscape::GC::release(path);
+    // Append clipPath to defs and get id
+    SP_OBJECT_REPR (SP_DOCUMENT_DEFS (doc))->appendChild(clip_path);
+    gchar *urltext = g_strdup_printf ("url(#%s)", clip_path->attribute("id"));
+    Inkscape::GC::release(clip_path);
+    container->setAttribute("clip-path", urltext);
+    g_free(urltext);
+}
+
+bool SvgBuilder::getTransform(double *transform) {
+    NR::Matrix svd;
+    gchar const *tr = container->attribute("transform");
+    bool valid = sp_svg_transform_read(tr, &svd);
+    if (valid) {
+        for (unsigned i = 0; i < 6; i++) {
+            transform[i] = svd[i];
+        }
+        return true;
+    } else {
+        return false;
+    }
+}
+
+void SvgBuilder::setTransform(double c0, double c1, double c2, double c3,
+                              double c4, double c5) {
+
+    gchar *transform_text = svgInterpretTransform(c0, c1, c2, c3, c4, c5);
+    LOG(g_message("setTransform: %f %f %f %f %f %f", c0, c1, c2, c3, c4, c5));
+    container->setAttribute("transform", transform_text);
+    g_free(transform_text);
+}
+
+void SvgBuilder::setTransform(double *transform) {
+    setTransform(transform[0], transform[1], transform[2], transform[3],
+                 transform[4], transform[5]);
+}
+
+/**
+ * \brief Checks whether the given pattern type can be represented in SVG
+ * Used by PdfParser to decide when to do fallback operations.
+ */
+bool SvgBuilder::isPatternTypeSupported(GfxPattern *pattern) {
+    if (pattern != NULL) {
+        if (pattern->getType() == 2) {    // shading pattern
+            GfxShading *shading = ((GfxShadingPattern *)pattern)->getShading();
+            int shadingType = shading->getType();
+            if (shadingType == 2 || // axial shading
+                shadingType == 3) {   // radial shading
+                return true;
+            }
+            return false;
+        } else if (pattern->getType() == 1) {   // tiling pattern
+            return true;
+        }
+    }
+
+    return false;
+}
+
+/**
+ * \brief Creates a pattern from poppler's data structure
+ * Handles linear and radial gradients. Creates a new PdfParser and uses it to
+ * build a tiling pattern.
+ * \return an url pointing to the created pattern
+ */
+gchar *SvgBuilder::createPattern(GfxPattern *pattern) {
+    gchar *id = NULL;
+    if (pattern != NULL) {
+        if (pattern->getType() == 2) {  // Shading pattern
+            id = createGradient((GfxShadingPattern*)pattern);
+        } else if (pattern->getType() == 1) {   // Tiling pattern
+            id = createTilingPattern((GfxTilingPattern*)pattern);
+        }
+    } else {
+        return NULL;
+    }
+    gchar *urltext = g_strdup_printf ("url(#%s)", id);
+    g_free(id);
+    return urltext;
+}
+
+gchar *SvgBuilder::createTilingPattern(GfxTilingPattern *tiling_pattern) {
+    return NULL;
+}
+
+/**
+ * \brief Creates a linear or radial gradient from poppler's data structure
+ * \return id of the created object
+ */
+gchar *SvgBuilder::createGradient(GfxShadingPattern *shading_pattern) {
+    GfxShading *shading = shading_pattern->getShading();
+    Inkscape::XML::Node *gradient;
+    Function *func;
+    int num_funcs;
+    bool extend0, extend1;
+    if (shading->getType() == 2) {  // Axial shading
+        gradient = xml_doc->createElement("svg:linearGradient");
+        GfxAxialShading *axial_shading = (GfxAxialShading*)shading;
+        double x1, y1, x2, y2;
+        axial_shading->getCoords(&x1, &y1, &x2, &y2);
+        sp_repr_set_svg_double(gradient, "x1", x1);
+        sp_repr_set_svg_double(gradient, "y1", y1);
+        sp_repr_set_svg_double(gradient, "x2", x2);
+        sp_repr_set_svg_double(gradient, "y2", y2);
+        extend0 = axial_shading->getExtend0();
+        extend1 = axial_shading->getExtend1();
+        num_funcs = axial_shading->getNFuncs();
+        func = axial_shading->getFunc(0);
+    } else if (shading->getType() == 3) {   // Radial shading
+        gradient = xml_doc->createElement("svg:radialGradient");
+        GfxRadialShading *radial_shading = (GfxRadialShading*)shading;
+        double x1, y1, r1, x2, y2, r2;
+        radial_shading->getCoords(&x1, &y1, &r1, &x2, &y2, &r2);
+        // FIXME: the inner circle's radius is ignored here
+        sp_repr_set_svg_double(gradient, "fx", x1);
+        sp_repr_set_svg_double(gradient, "fy", y1);
+        sp_repr_set_svg_double(gradient, "cx", x2);
+        sp_repr_set_svg_double(gradient, "cy", y2);
+        sp_repr_set_svg_double(gradient, "r", r2);
+        extend0 = radial_shading->getExtend0();
+        extend1 = radial_shading->getExtend1();
+        num_funcs = radial_shading->getNFuncs();
+        func = radial_shading->getFunc(0);
+    } else {    // Unsupported shading type
+        return NULL;
+    }
+    gradient->setAttribute("gradientUnits", "userSpaceOnUse");
+    // Flip the gradient transform around the y axis
+    double *p2u = shading_pattern->getMatrix();
+    NR::Matrix pat_matrix(p2u[0], p2u[1], p2u[2], p2u[3], p2u[4], p2u[5]);
+    NR::Matrix flip(1.0, 0.0, 0.0, -1.0, 0.0, height*PT_PER_PX);
+    pat_matrix *= flip;
+    gradient->setAttribute("gradientTransform", sp_svg_transform_write(pat_matrix));
+
+    if (extend0 && extend1) {
+        gradient->setAttribute("spreadMethod", "pad");
+    }
+    if (num_funcs > 1 || !addStopsToGradient(gradient, func, 1.0)) {
+        Inkscape::GC::release(gradient);
+        return NULL;
+    }
+
+    Inkscape::XML::Node *defs = SP_OBJECT_REPR (SP_DOCUMENT_DEFS (doc));
+    defs->appendChild(gradient);
+    gchar *id = g_strdup(gradient->attribute("id"));
+    Inkscape::GC::release(gradient);
+
+    return id;
+}
+
+#define EPSILON 0.0001
+bool SvgBuilder::addSamplesToGradient(Inkscape::XML::Node *gradient,
+                                      SampledFunction *func, double offset0,
+                                      double offset1, double opacity) {
+
+    // Check whether this sampled function can be converted to color stops
+    int sample_size = func->getSampleSize(0);
+    if (sample_size != 2)
+        return false;
+    int num_comps = func->getOutputSize();
+    if (num_comps != 3)
+        return false;
+
+    double *samples = func->getSamples();
+    // See if this is the continuation of the previous sampled function
+    bool is_continuation = false;
+    unsigned stop_count = gradient->childCount();
+    if (stop_count > 0) {
+        // Get previous stop
+        Inkscape::XML::Node *prev_stop = gradient->nthChild(stop_count-1);
+
+        // Read its properties
+        double offset;
+        sp_repr_get_double(prev_stop, "offset", &offset);
+        SPCSSAttr *css = sp_repr_css_attr(prev_stop, "style");
+        guint32 prev_color = sp_svg_read_color(sp_repr_css_property(css, "stop-color", NULL), 0);
+        double alpha = sp_repr_css_double_property(css, "stop-opacity", 1.0);
+        sp_repr_css_attr_unref(css);
+        // Convert colors
+        double r = SP_RGBA32_R_F(prev_color);
+        double g = SP_RGBA32_G_F(prev_color);
+        double b = SP_RGBA32_B_F(prev_color);
+
+        if (fabs(offset - offset0) < EPSILON &&
+            fabs(samples[0] - r) < EPSILON &&
+            fabs(samples[1] - g) < EPSILON &&
+            fabs(samples[2] - b) < EPSILON &&
+            fabs(alpha - opacity) < EPSILON) {  // Identical
+
+            is_continuation = true;
+            LOG(g_message("gradient offsets merged"));
+        }
+    }
+
+    int i = 0;
+    if (is_continuation)
+        i = num_comps;
+    while (i < sample_size*num_comps) {
+        Inkscape::XML::Node *stop = xml_doc->createElement("svg:stop");
+        SPCSSAttr *css = sp_repr_css_attr_new();
+        Inkscape::CSSOStringStream os_opacity;
+        os_opacity << opacity;
+        sp_repr_css_set_property(css, "stop-opacity", os_opacity.str().c_str());
+        sp_repr_css_set_property(css, "stop-color",
+                                 svgConvertRGBToText(samples[i], samples[i+1],
+                                         samples[i+2]));
+        sp_repr_css_change(stop, css, "style");
+        sp_repr_css_attr_unref(css);
+        sp_repr_set_css_double(stop, "offset", (i < num_comps) ? offset0 : offset1);
+
+        gradient->appendChild(stop);
+        Inkscape::GC::release(stop);
+        i += num_comps;
+    }
+  
+    return true;
+}
+
+bool SvgBuilder::addStopsToGradient(Inkscape::XML::Node *gradient, Function *func,
+                                    double opacity) {
+    
+    int type = func->getType();
+    if (type == 0) {  // Sampled
+        SampledFunction *sampledFunc = (SampledFunction*)func;
+        addSamplesToGradient(gradient, sampledFunc, 0.0, 1.0, opacity);
+    } else if (type == 3) { // Stitching
+        StitchingFunction *stitchingFunc = (StitchingFunction*)func;
+        double *bounds = stitchingFunc->getBounds();
+        // bounds might be outside of [0..1]!!!!
+        double *encode = stitchingFunc->getEncode();
+        // should check whether all of them are [0..1]
+        int num_funcs = stitchingFunc->getNumFuncs();
+        // Add samples from all the stitched functions
+        for (int i=0; i<num_funcs; i++) {
+            Function *func = stitchingFunc->getFunc(i);
+            if (func->getType() != 0) // Only sampled functions are supported
+                continue;
+            
+            LOG(g_message("t%i i%i o%i m%f m%f",
+                   func->getType(),
+                   func->getInputSize(), func->getOutputSize(),
+                   func->getDomainMin(0), func->getDomainMax(0)));
+            
+            SampledFunction *sampledFunc = (SampledFunction*)func;
+            addSamplesToGradient(gradient, sampledFunc, bounds[i],
+                                 bounds[i+1], opacity);
+        }
+    } else { // Unsupported function type
+        return false;
+    }
+
+    return true;
+}
+
+} } } /* namespace Inkscape, Extension, Internal, PdfInput */
+
+#endif /* HAVE_POPPLER */
+
+/*
+  Local Variables:
+  mode:c++
+  c-file-style:"stroustrup"
+  c-file-offsets:((innamespace . 0)(inline-open . 0))
+  indent-tabs-mode:nil
+  fill-column:99
+  End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/extension/internal/pdfinput/svg-builder.h b/src/extension/internal/pdfinput/svg-builder.h
new file mode 100644 (file)
index 0000000..0422184
--- /dev/null
@@ -0,0 +1,122 @@
+#ifndef __EXTENSION_INTERNAL_PDFINPUT_SVGBUILDER_H__
+#define __EXTENSION_INTERNAL_PDFINPUT_SVGBUILDER_H__
+
+ /** \file
+ * SVG representation creator using libpoppler.
+ *
+ * Authors:
+ *   miklos erdelyi
+ *
+ * Copyright (C) 2007 Authors
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#ifdef HAVE_POPPLER
+
+class SPDocument;
+namespace Inkscape {
+    namespace XML {
+        class Document;
+        class Node;
+    }
+}
+
+class Function;
+class SampledFunction;
+struct GfxState;
+class GfxPattern;
+class GfxShadingPattern;
+class GfxTilingPattern;
+
+class SPCSSAttr;
+
+#include <vector>
+#include <glib/gtypes.h>
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+
+/**
+ * \class SvgBuilder
+ *
+ * Builds the inner SVG representation from the calls of PdfParser
+ *
+ */
+class SvgBuilder {
+public:
+    SvgBuilder(SPDocument *document);
+    SvgBuilder(SvgBuilder *parent, Inkscape::XML::Node *root);
+    ~SvgBuilder();
+
+    // Property setting
+    void setDocumentSize(double width, double height);  // Document size in px
+
+    // Handling the node stack
+    Inkscape::XML::Node *pushGroup();
+    Inkscape::XML::Node *popGroup();
+    Inkscape::XML::Node *getContainer();    // Returns current group node
+
+    // Path adding
+    void addPath(GfxState *state, bool fill, bool stroke, bool even_odd=false);
+
+    bool isPatternTypeSupported(GfxPattern *pattern);
+
+    // State manipulation
+    void saveState();
+    void restoreState();
+
+    // Clipping
+    void clip(GfxState *state, bool even_odd=false);
+    void setClipPath(GfxState *state, bool even_odd=false);
+
+    // Transforming
+    void setTransform(double c0, double c1, double c2, double c3, double c4,
+                      double c5);
+    void setTransform(double *transform);
+    bool getTransform(double *transform);
+
+private:
+    // Pattern creation
+    gchar *createPattern(GfxPattern *pattern);
+    gchar *createGradient(GfxShadingPattern *shading_pattern);
+    bool addStopsToGradient(Inkscape::XML::Node *gradient, Function *func, double opacity);
+    bool addSamplesToGradient(Inkscape::XML::Node *gradient, SampledFunction *func,
+                              double offset0, double offset1, double opacity);
+    gchar *createTilingPattern(GfxTilingPattern *tiling_pattern);
+    // Style setting
+    SPCSSAttr *setStyle(GfxState *state, bool fill, bool stroke, bool even_odd);
+    void setStrokeStyle(SPCSSAttr *css, GfxState *state);
+    void setFillStyle(SPCSSAttr *css, GfxState *state, bool even_odd);
+
+    std::vector<int> groupDepth;    // Depth of nesting groups
+
+    SPDocument *doc;
+    Inkscape::XML::Document *xml_doc;
+    Inkscape::XML::Node *root;  // Root node from the point of view of this SvgBuilder
+    Inkscape::XML::Node *container; // Current container (group/pattern/mask)
+    double width, height;       // Document size in px
+};
+
+
+} } } /* namespace Inkscape, Extension, Internal, PdfInput */
+
+#endif /* HAVE_POPPLER */
+
+#endif /* __EXTENSION_INTERNAL_PDFINPUT_SVGBUILDER_H__ */
+
+/*
+  Local Variables:
+  mode:c++
+  c-file-style:"stroustrup"
+  c-file-offsets:((innamespace . 0)(inline-open . 0))
+  indent-tabs-mode:nil
+  fill-column:99
+  End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :