applied diff between xpdf-3.00-orig and xpdf-3.00-swftools
[swftools.git] / pdf2swf / xpdf / Gfx.cc
index 0096d4b..49e9407 100644 (file)
@@ -2,19 +2,24 @@
 //
 // Gfx.cc
 //
-// Copyright 1996 Derek B. Noonburg
+// Copyright 1996-2003 Glyph & Cog, LLC
 //
 //========================================================================
 
-#ifdef __GNUC__
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
 #pragma implementation
 #endif
 
+#include <stdlib.h>
 #include <stdio.h>
 #include <stddef.h>
 #include <string.h>
 #include <math.h>
 #include "gmem.h"
+#include "GlobalParams.h"
+#include "CharTypes.h"
 #include "Object.h"
 #include "Array.h"
 #include "Dict.h"
 #include "GfxFont.h"
 #include "GfxState.h"
 #include "OutputDev.h"
-#include "Params.h"
+#include "Page.h"
 #include "Error.h"
 #include "Gfx.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 4
+
+// 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))
+
 //------------------------------------------------------------------------
 // Operator table
 //------------------------------------------------------------------------
 
+#ifdef WIN32 // this works around a bug in the VC7 compiler
+#  pragma optimize("",off)
+#endif
+
 Operator Gfx::opTab[] = {
   {"\"",  3, {tchkNum,    tchkNum,    tchkString},
           &Gfx::opMoveSetShowText},
@@ -187,26 +237,34 @@ Operator Gfx::opTab[] = {
           &Gfx::opCurveTo2},
 };
 
-#define numOps (sizeof(opTab) / sizeof(Operator))
-
-//------------------------------------------------------------------------
+#ifdef WIN32 // this works around a bug in the VC7 compiler
+#  pragma optimize("",on)
+#endif
 
-GBool printCommands = gFalse;
+#define numOps (sizeof(opTab) / sizeof(Operator))
 
 //------------------------------------------------------------------------
 // GfxResources
 //------------------------------------------------------------------------
 
-GfxResources::GfxResources(Dict *resDict, GfxResources *next) {
-  Object obj1;
+GfxResources::GfxResources(XRef *xref, Dict *resDict, GfxResources *nextA) {
+  Object obj1, obj2;
+  Ref r;
 
   if (resDict) {
 
     // build font dictionary
     fonts = NULL;
-    resDict->lookup("Font", &obj1);
-    if (obj1.isDict()) {
-      fonts = new GfxFontDict(obj1.getDict());
+    resDict->lookupNF("Font", &obj1);
+    if (obj1.isRef()) {
+      obj1.fetch(xref, &obj2);
+      if (obj2.isDict()) {
+       r = obj1.getRef();
+       fonts = new GfxFontDict(xref, &r, obj2.getDict());
+      }
+      obj2.free();
+    } else if (obj1.isDict()) {
+      fonts = new GfxFontDict(xref, NULL, obj1.getDict());
     }
     obj1.free();
 
@@ -219,6 +277,9 @@ GfxResources::GfxResources(Dict *resDict, GfxResources *next) {
     // get pattern dictionary
     resDict->lookup("Pattern", &patternDict);
 
+    // get shading dictionary
+    resDict->lookup("Shading", &shadingDict);
+
     // get graphics state parameter dictionary
     resDict->lookup("ExtGState", &gStateDict);
 
@@ -227,10 +288,11 @@ GfxResources::GfxResources(Dict *resDict, GfxResources *next) {
     xObjDict.initNull();
     colorSpaceDict.initNull();
     patternDict.initNull();
+    shadingDict.initNull();
     gStateDict.initNull();
   }
 
-  this->next = next;
+  next = nextA;
 }
 
 GfxResources::~GfxResources() {
@@ -240,6 +302,7 @@ GfxResources::~GfxResources() {
   xObjDict.free();
   colorSpaceDict.free();
   patternDict.free();
+  shadingDict.free();
   gStateDict.free();
 }
 
@@ -318,6 +381,25 @@ GfxPattern *GfxResources::lookupPattern(char *name) {
   return NULL;
 }
 
+GfxShading *GfxResources::lookupShading(char *name) {
+  GfxResources *resPtr;
+  GfxShading *shading;
+  Object obj;
+
+  for (resPtr = this; resPtr; resPtr = resPtr->next) {
+    if (resPtr->shadingDict.isDict()) {
+      if (!resPtr->shadingDict.dictLookup(name, &obj)->isNull()) {
+       shading = GfxShading::parse(&obj);
+       obj.free();
+       return shading;
+      }
+      obj.free();
+    }
+  }
+  error(-1, "Unknown shading '%s'", name);
+  return NULL;
+}
+
 GBool GfxResources::lookupGState(char *name, Object *obj) {
   GfxResources *resPtr;
 
@@ -337,55 +419,101 @@ GBool GfxResources::lookupGState(char *name, Object *obj) {
 // Gfx
 //------------------------------------------------------------------------
 
-Gfx::Gfx(OutputDev *out1, int pageNum, Dict *resDict,
-        double dpi, double x1, double y1, double x2, double y2, GBool crop,
-        double cropX1, double cropY1, double cropX2, double cropY2,
-        int rotate) {
+Gfx::Gfx(XRef *xrefA, OutputDev *outA, int pageNum, Dict *resDict,
+        double hDPI, double vDPI, PDFRectangle *box,
+        PDFRectangle *cropBox, int rotate,
+        GBool (*abortCheckCbkA)(void *data),
+        void *abortCheckCbkDataA) {
   int i;
 
+  xref = xrefA;
+  subPage = gFalse;
+  printCommands = globalParams->getPrintCommands();
+
   // start the resource stack
-  res = new GfxResources(resDict, NULL);
+  res = new GfxResources(xref, resDict, NULL);
 
   // initialize
-  out = out1;
-  state = new GfxState(dpi, x1, y1, x2, y2, rotate, out->upsideDown());
+  out = outA;
+  state = new GfxState(hDPI, vDPI, box, rotate, out->upsideDown());
   fontChanged = gFalse;
   clip = clipNone;
   ignoreUndef = 0;
-  out->startPage(pageNum, state);
+  out->startPage(pageNum, state, cropBox->x1,cropBox->y1,cropBox->x2,cropBox->y2);
   out->setDefaultCTM(state->getCTM());
   out->updateAll(state);
   for (i = 0; i < 6; ++i) {
     baseMatrix[i] = state->getCTM()[i];
   }
+  formDepth = 0;
+  abortCheckCbk = abortCheckCbkA;
+  abortCheckCbkData = abortCheckCbkDataA;
+
+  // set crop box
+  /*if (cropBox) {
+    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();
+    out->clip(state);
+    state->clearPath();
+  }*/
+}
+
+Gfx::Gfx(XRef *xrefA, OutputDev *outA, Dict *resDict,
+        PDFRectangle *box, PDFRectangle *cropBox,
+        GBool (*abortCheckCbkA)(void *data),
+        void *abortCheckCbkDataA) {
+  int i;
+
+  xref = xrefA;
+  subPage = gTrue;
+  printCommands = globalParams->getPrintCommands();
+
+  // start the resource stack
+  res = new GfxResources(xref, resDict, NULL);
+
+  // initialize
+  out = outA;
+  state = new GfxState(72, 72, box, 0, gFalse);
+  fontChanged = gFalse;
+  clip = clipNone;
+  ignoreUndef = 0;
+  for (i = 0; i < 6; ++i) {
+    baseMatrix[i] = state->getCTM()[i];
+  }
+  formDepth = 0;
+  abortCheckCbk = abortCheckCbkA;
+  abortCheckCbkData = abortCheckCbkDataA;
 
   // set crop box
-  if (crop) {
-    state->moveTo(cropX1, cropY1);
-    state->lineTo(cropX2, cropY1);
-    state->lineTo(cropX2, cropY2);
-    state->lineTo(cropX1, cropY2);
+  if (cropBox) {
+    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();
     out->clip(state);
     state->clearPath();
   }
 }
 
 Gfx::~Gfx() {
-  GfxResources *resPtr;
-
   while (state->hasSaves()) {
-    state = state->restore();
-    out->restoreState(state);
+    restoreState();
+  }
+  if (!subPage) {
+    out->endPage();
   }
-  out->endPage();
   while (res) {
-    resPtr = res->getNext();
-    delete res;
-    res = resPtr;
+    popResources();
   }
-  if (state)
+  if (state) {
     delete state;
+  }
 }
 
 void Gfx::display(Object *obj, GBool topLevel) {
@@ -406,7 +534,7 @@ void Gfx::display(Object *obj, GBool topLevel) {
     error(-1, "Weird page contents");
     return;
   }
-  parser = new Parser(new Lexer(obj));
+  parser = new Parser(xref, new Lexer(xref, obj));
   go(topLevel);
   delete parser;
   parser = NULL;
@@ -415,11 +543,11 @@ void Gfx::display(Object *obj, GBool topLevel) {
 void Gfx::go(GBool topLevel) {
   Object obj;
   Object args[maxArgs];
-  int numCmds, numArgs;
-  int i;
+  int numArgs, i;
+  int lastAbortCheck;
 
   // scan a sequence of objects
-  numCmds = 0;
+  updateLevel = lastAbortCheck = 0;
   numArgs = 0;
   parser->getObj(&obj);
   while (!obj.isEOF()) {
@@ -433,6 +561,7 @@ void Gfx::go(GBool topLevel) {
          args[i].print(stdout);
        }
        printf("\n");
+       fflush(stdout);
       }
       execOp(&obj, args, numArgs);
       obj.free();
@@ -441,9 +570,19 @@ void Gfx::go(GBool topLevel) {
       numArgs = 0;
 
       // periodically update display
-      if (++numCmds == 200) {
+      if (++updateLevel >= 20000) {
        out->dump();
-       numCmds = 0;
+       updateLevel = 0;
+      }
+
+      // check for an abort
+      if (abortCheckCbk) {
+       if (updateLevel - lastAbortCheck > 10) {
+         if ((*abortCheckCbk)(abortCheckCbkData)) {
+           break;
+         }
+         lastAbortCheck = updateLevel;
+       }
       }
 
     // got an argument - save it
@@ -457,6 +596,7 @@ void Gfx::go(GBool topLevel) {
        printf("throwing away arg: ");
        obj.print(stdout);
        printf("\n");
+       fflush(stdout);
       }
       obj.free();
     }
@@ -476,29 +616,26 @@ void Gfx::go(GBool topLevel) {
        args[i].print(stdout);
       }
       printf("\n");
+      fflush(stdout);
     }
     for (i = 0; i < numArgs; ++i)
       args[i].free();
   }
 
   // update display
-  if (topLevel && numCmds > 0) {
+  if (topLevel && updateLevel > 0) {
     out->dump();
   }
-
-  // clean up
-  if (printCommands) {
-    fflush(stdout);
-  }
 }
 
 void Gfx::execOp(Object *cmd, Object args[], int numArgs) {
   Operator *op;
   char *name;
+  Object *argPtr;
   int i;
 
   // find operator
-  name = cmd->getName();
+  name = cmd->getCmd();
   if (!(op = findOp(name))) {
     if (ignoreUndef == 0)
       error(getPos(), "Unknown operator '%s'", name);
@@ -506,12 +643,19 @@ void Gfx::execOp(Object *cmd, Object args[], int numArgs) {
   }
 
   // type check args
+  argPtr = args;
   if (op->numArgs >= 0) {
-    if (numArgs != op->numArgs) {
-      error(getPos(), "Wrong number (%d) of args to '%s' operator",
-           numArgs, name);
+    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",
@@ -520,15 +664,15 @@ void Gfx::execOp(Object *cmd, Object args[], int numArgs) {
     }
   }
   for (i = 0; i < numArgs; ++i) {
-    if (!checkArg(&args[i], op->tchk[i])) {
+    if (!checkArg(&argPtr[i], op->tchk[i])) {
       error(getPos(), "Arg #%d to '%s' operator is wrong type (%s)",
-           i, name, args[i].getTypeName());
+           i, name, argPtr[i].getTypeName());
       return;
     }
   }
 
   // do it
-  (this->*op->func)(args, numArgs);
+  (this->*op->func)(argPtr, numArgs);
 }
 
 Operator *Gfx::findOp(char *name) {
@@ -576,19 +720,11 @@ int Gfx::getPos() {
 //------------------------------------------------------------------------
 
 void Gfx::opSave(Object args[], int numArgs) {
-  out->saveState(state);
-  state = state->save();
+  saveState();
 }
 
 void Gfx::opRestore(Object args[], int numArgs) {
-  state = state->restore();
-  out->restoreState(state);
-
-  // Some PDF producers (Macromedia FreeHand) generate a save (q) and
-  // restore (Q) inside a path sequence.  The PDF spec seems to imply
-  // that this is illegal.  Calling clearPath() here implements the
-  // behavior apparently expected by this software.
-  state->clearPath();
+  restoreState();
 }
 
 void Gfx::opConcat(Object args[], int numArgs) {
@@ -613,7 +749,7 @@ void Gfx::opSetDash(Object args[], int numArgs) {
   if (length == 0) {
     dash = NULL;
   } else {
-    dash = (double *)gmalloc(length * sizeof(double));
+    dash = (double *)gmallocn(length, sizeof(double));
     for (i = 0; i < length; ++i) {
       dash[i] = a->get(i, &obj)->getNum();
       obj.free();
@@ -650,6 +786,8 @@ void Gfx::opSetLineWidth(Object args[], int numArgs) {
 
 void Gfx::opSetExtGState(Object args[], int numArgs) {
   Object obj1, obj2;
+  GfxBlendMode mode;
+  GBool haveFillOP;
 
   if (!res->lookupGState(args[0].getName(), &obj1)) {
     return;
@@ -659,6 +797,17 @@ void Gfx::opSetExtGState(Object args[], int numArgs) {
     obj1.free();
     return;
   }
+
+  // transparency support: blend mode, fill/stroke opacity
+  if (!obj1.dictLookup("BM", &obj2)->isNull()) {
+    if (state->parseBlendMode(&obj2, &mode)) {
+      state->setBlendMode(mode);
+      out->updateBlendMode(state);
+    } else {
+      error(getPos(), "Invalid blend mode in ExtGState");
+    }
+  }
+  obj2.free();
   if (obj1.dictLookup("ca", &obj2)->isNum()) {
     state->setFillOpacity(obj2.getNum());
     out->updateFillOpacity(state);
@@ -669,6 +818,23 @@ void Gfx::opSetExtGState(Object args[], int numArgs) {
     out->updateStrokeOpacity(state);
   }
   obj2.free();
+
+  // fill/stroke overprint
+  if ((haveFillOP = (obj1.dictLookup("op", &obj2)->isBool()))) {
+    state->setFillOverprint(obj2.getBool());
+    out->updateFillOverprint(state);
+  }
+  obj2.free();
+  if (obj1.dictLookup("OP", &obj2)->isBool()) {
+    state->setStrokeOverprint(obj2.getBool());
+    out->updateStrokeOverprint(state);
+    if (!haveFillOP) {
+      state->setFillOverprint(obj2.getBool());
+      out->updateFillOverprint(state);
+    }
+  }
+  obj2.free();
+
   obj1.free();
 }
 
@@ -684,7 +850,8 @@ void Gfx::opSetFillGray(Object args[], int numArgs) {
 
   state->setFillPattern(NULL);
   state->setFillColorSpace(new GfxDeviceGrayColorSpace());
-  color.c[0] = args[0].getNum();
+  out->updateFillColorSpace(state);
+  color.c[0] = dblToCol(args[0].getNum());
   state->setFillColor(&color);
   out->updateFillColor(state);
 }
@@ -694,7 +861,8 @@ void Gfx::opSetStrokeGray(Object args[], int numArgs) {
 
   state->setStrokePattern(NULL);
   state->setStrokeColorSpace(new GfxDeviceGrayColorSpace());
-  color.c[0] = args[0].getNum();
+  out->updateStrokeColorSpace(state);
+  color.c[0] = dblToCol(args[0].getNum());
   state->setStrokeColor(&color);
   out->updateStrokeColor(state);
 }
@@ -705,8 +873,9 @@ void Gfx::opSetFillCMYKColor(Object args[], int numArgs) {
 
   state->setFillPattern(NULL);
   state->setFillColorSpace(new GfxDeviceCMYKColorSpace());
+  out->updateFillColorSpace(state);
   for (i = 0; i < 4; ++i) {
-    color.c[i] = args[i].getNum();
+    color.c[i] = dblToCol(args[i].getNum());
   }
   state->setFillColor(&color);
   out->updateFillColor(state);
@@ -718,8 +887,9 @@ void Gfx::opSetStrokeCMYKColor(Object args[], int numArgs) {
 
   state->setStrokePattern(NULL);
   state->setStrokeColorSpace(new GfxDeviceCMYKColorSpace());
+  out->updateStrokeColorSpace(state);
   for (i = 0; i < 4; ++i) {
-    color.c[i] = args[i].getNum();
+    color.c[i] = dblToCol(args[i].getNum());
   }
   state->setStrokeColor(&color);
   out->updateStrokeColor(state);
@@ -731,8 +901,9 @@ void Gfx::opSetFillRGBColor(Object args[], int numArgs) {
 
   state->setFillPattern(NULL);
   state->setFillColorSpace(new GfxDeviceRGBColorSpace());
+  out->updateFillColorSpace(state);
   for (i = 0; i < 3; ++i) {
-    color.c[i] = args[i].getNum();
+    color.c[i] = dblToCol(args[i].getNum());
   }
   state->setFillColor(&color);
   out->updateFillColor(state);
@@ -744,8 +915,9 @@ void Gfx::opSetStrokeRGBColor(Object args[], int numArgs) {
 
   state->setStrokePattern(NULL);
   state->setStrokeColorSpace(new GfxDeviceRGBColorSpace());
+  out->updateStrokeColorSpace(state);
   for (i = 0; i < 3; ++i) {
-    color.c[i] = args[i].getNum();
+    color.c[i] = dblToCol(args[i].getNum());
   }
   state->setStrokeColor(&color);
   out->updateStrokeColor(state);
@@ -767,8 +939,9 @@ void Gfx::opSetFillColorSpace(Object args[], int numArgs) {
   obj.free();
   if (colorSpace) {
     state->setFillColorSpace(colorSpace);
+    out->updateFillColorSpace(state);
   } else {
-    error(getPos(), "Bad color space");
+    error(getPos(), "Bad color space (fill)");
   }
   for (i = 0; i < gfxColorMaxComps; ++i) {
     color.c[i] = 0;
@@ -793,8 +966,9 @@ void Gfx::opSetStrokeColorSpace(Object args[], int numArgs) {
   obj.free();
   if (colorSpace) {
     state->setStrokeColorSpace(colorSpace);
+    out->updateStrokeColorSpace(state);
   } else {
-    error(getPos(), "Bad color space");
+    error(getPos(), "Bad color space (stroke)");
   }
   for (i = 0; i < gfxColorMaxComps; ++i) {
     color.c[i] = 0;
@@ -809,7 +983,7 @@ void Gfx::opSetFillColor(Object args[], int numArgs) {
 
   state->setFillPattern(NULL);
   for (i = 0; i < numArgs; ++i) {
-    color.c[i] = args[i].getNum();
+    color.c[i] = dblToCol(args[i].getNum());
   }
   state->setFillColor(&color);
   out->updateFillColor(state);
@@ -821,7 +995,7 @@ void Gfx::opSetStrokeColor(Object args[], int numArgs) {
 
   state->setStrokePattern(NULL);
   for (i = 0; i < numArgs; ++i) {
-    color.c[i] = args[i].getNum();
+    color.c[i] = dblToCol(args[i].getNum());
   }
   state->setStrokeColor(&color);
   out->updateStrokeColor(state);
@@ -836,7 +1010,7 @@ void Gfx::opSetFillColorN(Object args[], int numArgs) {
     if (numArgs > 1) {
       for (i = 0; i < numArgs && i < 4; ++i) {
        if (args[i].isNum()) {
-         color.c[i] = args[i].getNum();
+         color.c[i] = dblToCol(args[i].getNum());
        }
       }
       state->setFillColor(&color);
@@ -851,7 +1025,7 @@ void Gfx::opSetFillColorN(Object args[], int numArgs) {
     state->setFillPattern(NULL);
     for (i = 0; i < numArgs && i < 4; ++i) {
       if (args[i].isNum()) {
-       color.c[i] = args[i].getNum();
+       color.c[i] = dblToCol(args[i].getNum());
       }
     }
     state->setFillColor(&color);
@@ -868,7 +1042,7 @@ void Gfx::opSetStrokeColorN(Object args[], int numArgs) {
     if (numArgs > 1) {
       for (i = 0; i < numArgs && i < 4; ++i) {
        if (args[i].isNum()) {
-         color.c[i] = args[i].getNum();
+         color.c[i] = dblToCol(args[i].getNum());
        }
       }
       state->setStrokeColor(&color);
@@ -883,7 +1057,7 @@ void Gfx::opSetStrokeColorN(Object args[], int numArgs) {
     state->setStrokePattern(NULL);
     for (i = 0; i < numArgs && i < 4; ++i) {
       if (args[i].isNum()) {
-       color.c[i] = args[i].getNum();
+       color.c[i] = dblToCol(args[i].getNum());
       }
     }
     state->setStrokeColor(&color);
@@ -970,7 +1144,7 @@ void Gfx::opRectangle(Object args[], int numArgs) {
 }
 
 void Gfx::opClosePath(Object args[], int numArgs) {
-  if (!state->isPath()) {
+  if (!state->isCurPt()) {
     error(getPos(), "No current point in closepath");
     return;
   }
@@ -1103,36 +1277,49 @@ void Gfx::opCloseEOFillStroke(Object args[], int numArgs) {
   doEndPath();
 }
 
-void Gfx::opShFill(Object args[], int numArgs) {
+void Gfx::doPatternFill(GBool eoFill) {
+  GfxPattern *pattern;
+
+  // this is a bit of a kludge -- patterns can be really slow, so we
+  // skip them if we're only doing text extraction, since they almost
+  // certainly don't contain any text
+  if (!out->needNonText()) {
+    return;
+  }
+
+  if (!(pattern = state->getFillPattern())) {
+    return;
+  }
+  switch (pattern->getType()) {
+  case 1:
+    doTilingPatternFill((GfxTilingPattern *)pattern, eoFill);
+    break;
+  case 2:
+    doShadingPatternFill((GfxShadingPattern *)pattern, eoFill);
+    break;
+  default:
+    error(getPos(), "Unimplemented pattern type (%d) in fill",
+         pattern->getType());
+    break;
+  }
 }
 
-void Gfx::doPatternFill(GBool eoFill) {
+void Gfx::doTilingPatternFill(GfxTilingPattern *tPat, GBool eoFill) {
   GfxPatternColorSpace *patCS;
-  GfxPattern *pattern;
-  GfxTilingPattern *tPat;
   GfxColorSpace *cs;
-  GfxPath *path;
-  GfxSubpath *subpath;
+  GfxPath *savedPath;
   double xMin, yMin, xMax, yMax, x, y, x1, y1;
+  double cxMin, cyMin, cxMax, cyMax;
   int xi0, yi0, xi1, yi1, xi, yi;
   double *ctm, *btm, *ptm;
-  double m[6], ictm[6], m1[6], im[6];
+  double m[6], ictm[6], m1[6], imb[6];
   double det;
   double xstep, ystep;
-  int i, j;
+  int i;
 
   // get color space
   patCS = (GfxPatternColorSpace *)state->getFillColorSpace();
 
-  // get pattern
-  if (!(pattern = state->getFillPattern())) {
-    return;
-  }
-  if (pattern->getType() != 1) {
-    return;
-  }
-  tPat = (GfxTilingPattern *)pattern;
-
   // construct a (pattern space) -> (current space) transform matrix
   ctm = state->getCTM();
   btm = baseMatrix;
@@ -1160,57 +1347,43 @@ void Gfx::doPatternFill(GBool eoFill) {
   m[4] = m1[4] * ictm[0] + m1[5] * ictm[2] + ictm[4];
   m[5] = m1[4] * ictm[1] + m1[5] * ictm[3] + ictm[5];
 
-  // construct a (current space) -> (pattern space) transform matrix
-  det = 1 / (m[0] * m[3] - m[1] * m[2]);
-  im[0] = m[3] * det;
-  im[1] = -m[1] * det;
-  im[2] = -m[2] * det;
-  im[3] = m[0] * det;
-  im[4] = (m[2] * m[5] - m[3] * m[4]) * det;
-  im[5] = (m[1] * m[4] - m[0] * m[5]) * det;
-
-  // compute bounding box of current path, in pattern space
-  xMin = xMax = yMin = yMax = 0; // make gcc happy
-  path = state->getPath();
-  for (i = 0; i < path->getNumSubpaths(); ++i) {
-    subpath = path->getSubpath(i);
-    for (j = 0; j < subpath->getNumPoints(); ++j) {
-      x = subpath->getX(j);
-      y = subpath->getY(j);
-      x1 = x * im[0] + y * im[2] + im[4];
-      y1 = x * im[1] + y * im[3] + im[5];
-      if (i == 0 && j == 0) {
-       xMin = xMax = x1;
-       yMin = yMax = y1;
-      } else {
-       if (x1 < xMin) {
-         xMin = x1;
-       } else if (x1 > xMax) {
-         xMax = x1;
-       }
-       if (y1 < yMin) {
-         yMin = y1;
-       } else if (y1 > yMax) {
-         yMax = y1;
-       }
-      }
-    }
-  }
+  // construct a (device space) -> (pattern space) transform matrix
+  det = 1 / (m1[0] * m1[3] - m1[1] * m1[2]);
+  imb[0] = m1[3] * det;
+  imb[1] = -m1[1] * det;
+  imb[2] = -m1[2] * det;
+  imb[3] = m1[0] * det;
+  imb[4] = (m1[2] * m1[5] - m1[3] * m1[4]) * det;
+  imb[5] = (m1[1] * m1[4] - m1[0] * m1[5]) * det;
 
   // save current graphics state
-  out->saveState(state);
-  state = state->save();
+  savedPath = state->getPath()->copy();
+  saveState();
 
-  // set underlying color space (for uncolored tiling patterns)
+  // set underlying color space (for uncolored tiling patterns); set
+  // various other parameters (stroke color, line width) to match
+  // Adobe's behavior
   if (tPat->getPaintType() == 2 && (cs = patCS->getUnder())) {
     state->setFillColorSpace(cs->copy());
+    out->updateFillColorSpace(state);
+    state->setStrokeColorSpace(cs->copy());
+    out->updateStrokeColorSpace(state);
+    state->setStrokeColor(state->getFillColor());
   } else {
     state->setFillColorSpace(new GfxDeviceGrayColorSpace());
+    out->updateFillColorSpace(state);
+    state->setStrokeColorSpace(new GfxDeviceGrayColorSpace());
+    out->updateStrokeColorSpace(state);
   }
   state->setFillPattern(NULL);
   out->updateFillColor(state);
+  state->setStrokePattern(NULL);
+  out->updateStrokeColor(state);
+  state->setLineWidth(0);
+  out->updateLineWidth(state);
 
   // clip to current path
+  state->clip();
   if (eoFill) {
     out->eoClip(state);
   } else {
@@ -1218,128 +1391,1138 @@ void Gfx::doPatternFill(GBool eoFill) {
   }
   state->clearPath();
 
+  // get the clip region, check for empty
+  state->getClipBBox(&cxMin, &cyMin, &cxMax, &cyMax);
+  if (cxMin > cxMax || cyMin > cyMax) {
+    goto err;
+  }
+
+  // transform clip region bbox to pattern space
+  xMin = xMax = cxMin * imb[0] + cyMin * imb[2] + imb[4];
+  yMin = yMax = cxMin * imb[1] + cyMin * imb[3] + imb[5];
+  x1 = cxMin * imb[0] + cyMax * imb[2] + imb[4];
+  y1 = cxMin * imb[1] + cyMax * imb[3] + imb[5];
+  if (x1 < xMin) {
+    xMin = x1;
+  } else if (x1 > xMax) {
+    xMax = x1;
+  }
+  if (y1 < yMin) {
+    yMin = y1;
+  } else if (y1 > yMax) {
+    yMax = y1;
+  }
+  x1 = cxMax * imb[0] + cyMin * imb[2] + imb[4];
+  y1 = cxMax * imb[1] + cyMin * imb[3] + imb[5];
+  if (x1 < xMin) {
+    xMin = x1;
+  } else if (x1 > xMax) {
+    xMax = x1;
+  }
+  if (y1 < yMin) {
+    yMin = y1;
+  } else if (y1 > yMax) {
+    yMax = y1;
+  }
+  x1 = cxMax * imb[0] + cyMax * imb[2] + imb[4];
+  y1 = cxMax * imb[1] + cyMax * imb[3] + imb[5];
+  if (x1 < xMin) {
+    xMin = x1;
+  } else if (x1 > xMax) {
+    xMax = x1;
+  }
+  if (y1 < yMin) {
+    yMin = y1;
+  } else if (y1 > yMax) {
+    yMax = y1;
+  }
+
   // draw the pattern
   //~ this should treat negative steps differently -- start at right/top
   //~ edge instead of left/bottom (?)
   xstep = fabs(tPat->getXStep());
   ystep = fabs(tPat->getYStep());
-  xi0 = (int)floor(xMin / xstep);
-  xi1 = (int)ceil(xMax / xstep);
-  yi0 = (int)floor(yMin / ystep);
-  yi1 = (int)ceil(yMax / ystep);
+  xi0 = (int)floor((xMin - tPat->getBBox()[0]) / xstep);
+  xi1 = (int)ceil((xMax - tPat->getBBox()[0]) / xstep);
+  yi0 = (int)floor((yMin - tPat->getBBox()[1]) / ystep);
+  yi1 = (int)ceil((yMax - tPat->getBBox()[1]) / ystep);
   for (i = 0; i < 4; ++i) {
     m1[i] = m[i];
   }
-  for (yi = yi0; yi < yi1; ++yi) {
-    for (xi = xi0; xi < xi1; ++xi) {
-      x = xi * xstep;
-      y = yi * ystep;
-      m1[4] = x * m[0] + y * m[2] + m[4];
-      m1[5] = x * m[1] + y * m[3] + m[5];
-      doForm1(tPat->getContentStream(), tPat->getResDict(),
-             m1, tPat->getBBox());
+  if (out->useTilingPatternFill()) {
+    m1[4] = m[4];
+    m1[5] = m[5];
+    out->tilingPatternFill(state, tPat->getContentStream(),
+                          tPat->getPaintType(), tPat->getResDict(),
+                          m1, tPat->getBBox(),
+                          xi0, yi0, xi1, yi1, xstep, ystep);
+  } else {
+    for (yi = yi0; yi < yi1; ++yi) {
+      for (xi = xi0; xi < xi1; ++xi) {
+       x = xi * xstep;
+       y = yi * ystep;
+       m1[4] = x * m[0] + y * m[2] + m[4];
+       m1[5] = x * m[1] + y * m[3] + m[5];
+       doForm1(tPat->getContentStream(), tPat->getResDict(),
+               m1, tPat->getBBox());
+      }
     }
   }
 
   // restore graphics state
-  state = state->restore();
-  out->restoreState(state);
+ err:
+  restoreState();
+  state->setPath(savedPath);
 }
 
-void Gfx::doEndPath() {
-  if (state->isPath()) {
-    if (clip == clipNormal)
-      out->clip(state);
-    else if (clip == clipEO)
-      out->eoClip(state);
-  }
-  clip = clipNone;
-  state->clearPath();
-}
+void Gfx::doShadingPatternFill(GfxShadingPattern *sPat, GBool eoFill) {
+  GfxShading *shading;
+  GfxPath *savedPath;
+  double *ctm, *btm, *ptm;
+  double m[6], ictm[6], m1[6];
+  double xMin, yMin, xMax, yMax;
+  double det;
 
-//------------------------------------------------------------------------
-// path clipping operators
-//------------------------------------------------------------------------
+  shading = sPat->getShading();
 
-void Gfx::opClip(Object args[], int numArgs) {
-  clip = clipNormal;
-}
+  // 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();
+    out->clip(state);
+    state->setPath(savedPath->copy());
+  }
 
-void Gfx::opEOClip(Object args[], int numArgs) {
-  clip = clipEO;
-}
+  // clip to current path
+  state->clip();
+  if (eoFill) {
+    out->eoClip(state);
+  } else {
+    out->clip(state);
+  }
 
-//------------------------------------------------------------------------
-// text object operators
-//------------------------------------------------------------------------
+  // set the color space
+  state->setFillColorSpace(shading->getColorSpace()->copy());
+  out->updateFillColorSpace(state);
 
-void Gfx::opBeginText(Object args[], int numArgs) {
-  state->setTextMat(1, 0, 0, 1, 0, 0);
-  state->textMoveTo(0, 0);
-  out->updateTextMat(state);
-  out->updateTextPos(state);
-  fontChanged = gTrue;
-}
+  // background color fill
+  if (shading->getHasBackground()) {
+    state->setFillColor(shading->getBackground());
+    out->updateFillColor(state);
+    out->fill(state);
+  }
+  state->clearPath();
 
-void Gfx::opEndText(Object args[], int numArgs) {
-}
+  // 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];
 
-//------------------------------------------------------------------------
-// text state operators
-//------------------------------------------------------------------------
+  // set the new matrix
+  state->concatCTM(m[0], m[1], m[2], m[3], m[4], m[5]);
+  out->updateCTM(state, 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:
+    doAxialShFill((GfxAxialShading *)shading);
+    break;
+  case 3:
+    doRadialShFill((GfxRadialShading *)shading);
+    break;
+  case 4:
+  case 5:
+    doGouraudTriangleShFill((GfxGouraudTriangleShading *)shading);
+    break;
+  case 6:
+  case 7:
+    doPatchMeshShFill((GfxPatchMeshShading *)shading);
+    break;
+  }
 
-void Gfx::opSetCharSpacing(Object args[], int numArgs) {
-  state->setCharSpace(args[0].getNum());
-  out->updateCharSpace(state);
+  // restore graphics state
+  restoreState();
+  state->setPath(savedPath);
 }
 
-void Gfx::opSetFont(Object args[], int numArgs) {
-  GfxFont *font;
+void Gfx::opShFill(Object args[], int numArgs) {
+  GfxShading *shading;
+  GfxPath *savedPath;
+  double xMin, yMin, xMax, yMax;
 
-  if (!(font = res->lookupFont(args[0].getName()))) {
+  if (!(shading = res->lookupShading(args[0].getName()))) {
     return;
   }
-  if (printCommands) {
-    printf("  font: '%s' %g\n",
-          font->getName() ? font->getName()->getCString() : "???",
-          args[1].getNum());
+
+  // 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();
+    out->clip(state);
+    state->clearPath();
   }
-  state->setFont(font, args[1].getNum());
-  fontChanged = gTrue;
-}
 
-void Gfx::opSetTextLeading(Object args[], int numArgs) {
-  state->setLeading(args[0].getNum());
-}
+  // set the color space
+  state->setFillColorSpace(shading->getColorSpace()->copy());
+  out->updateFillColorSpace(state);
+
+  // do shading type-specific operations
+  switch (shading->getType()) {
+  case 1:
+    doFunctionShFill((GfxFunctionShading *)shading);
+    break;
+  case 2:
+    doAxialShFill((GfxAxialShading *)shading);
+    break;
+  case 3:
+    doRadialShFill((GfxRadialShading *)shading);
+    break;
+  case 4:
+  case 5:
+    doGouraudTriangleShFill((GfxGouraudTriangleShading *)shading);
+    break;
+  case 6:
+  case 7:
+    doPatchMeshShFill((GfxPatchMeshShading *)shading);
+    break;
+  }
 
-void Gfx::opSetTextRender(Object args[], int numArgs) {
-  state->setRender(args[0].getInt());
-  out->updateRender(state);
-}
+  // restore graphics state
+  restoreState();
+  state->setPath(savedPath);
 
-void Gfx::opSetTextRise(Object args[], int numArgs) {
-  state->setRise(args[0].getNum());
-  out->updateRise(state);
+  delete shading;
 }
 
-void Gfx::opSetWordSpacing(Object args[], int numArgs) {
-  state->setWordSpace(args[0].getNum());
-  out->updateWordSpace(state);
-}
+void Gfx::doFunctionShFill(GfxFunctionShading *shading) {
+  double x0, y0, x1, y1;
+  GfxColor colors[4];
 
-void Gfx::opSetHorizScaling(Object args[], int numArgs) {
-  state->setHorizScaling(args[0].getNum());
-  out->updateHorizScaling(state);
-}
+  if (out->useShadedFills()) {
+    out->functionShadedFill(state, shading);
+  } else {
+    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 Gfx::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;
+    }
+  }
 
-//------------------------------------------------------------------------
-// text positioning operators
-//------------------------------------------------------------------------
+  // center of the rectangle
+  xM = 0.5 * (x0 + x1);
+  yM = 0.5 * (y0 + y1);
 
-void Gfx::opTextMove(Object args[], int numArgs) {
-  double tx, ty;
+  // 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) {
 
-  tx = state->getLineX() + args[0].getNum();
+    // use the center color
+    shading->getColor(xM, yM, &fillColor);
+    state->setFillColor(&fillColor);
+    out->updateFillColor(state);
+
+    // 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();
+    out->fill(state);
+    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 Gfx::doAxialShFill(GfxAxialShading *shading) {
+  double xMin, yMin, xMax, yMax;
+  double x0, y0, x1, y1;
+  double dx, dy, mul;
+  GBool dxZero, dyZero;
+  double tMin, tMax, t, tx, ty;
+  double s[4], sMin, sMax, tmp;
+  double ux0, uy0, ux1, uy1, vx0, vy0, vx1, vy1;
+  double t0, t1, tt;
+  double ta[axialMaxSplits + 1];
+  int next[axialMaxSplits + 1];
+  GfxColor color0, color1;
+  int nComps;
+  int i, j, k, kk;
+
+  if (out->useShadedFills()) {
+
+    out->axialShadedFill(state, shading);
+
+  } else {
+
+    // get the clip region bbox
+    state->getUserClipBBox(&xMin, &yMin, &xMax, &yMax);
+
+    // compute min and max t values, based on the four corners of the
+    // clip region bbox
+    shading->getCoords(&x0, &y0, &x1, &y1);
+    dx = x1 - x0;
+    dy = y1 - y0;
+    dxZero = fabs(dx) < 0.001;
+    dyZero = fabs(dy) < 0.001;
+    mul = 1 / (dx * dx + dy * dy);
+    tMin = tMax = ((xMin - x0) * dx + (yMin - y0) * dy) * mul;
+    t = ((xMin - x0) * dx + (yMax - y0) * dy) * mul;
+    if (t < tMin) {
+      tMin = t;
+    } else if (t > tMax) {
+      tMax = t;
+    }
+    t = ((xMax - x0) * dx + (yMin - y0) * dy) * mul;
+    if (t < tMin) {
+      tMin = t;
+    } else if (t > tMax) {
+      tMax = t;
+    }
+    t = ((xMax - x0) * dx + (yMax - y0) * dy) * mul;
+    if (t < tMin) {
+      tMin = t;
+    } else if (t > tMax) {
+      tMax = t;
+    }
+    if (tMin < 0 && !shading->getExtend0()) {
+      tMin = 0;
+    }
+    if (tMax > 1 && !shading->getExtend1()) {
+      tMax = 1;
+    }
+
+    // get the function domain
+    t0 = shading->getDomain0();
+    t1 = shading->getDomain1();
+
+    // Traverse the t axis and do the shading.
+    //
+    // For each point (tx, ty) on the t axis, consider a line through
+    // that point perpendicular to the t axis:
+    //
+    //     x(s) = tx + s * -dy   -->   s = (x - tx) / -dy
+    //     y(s) = ty + s * dx    -->   s = (y - ty) / dx
+    //
+    // Then look at the intersection of this line with the bounding box
+    // (xMin, yMin, xMax, yMax).  In the general case, there are four
+    // intersection points:
+    //
+    //     s0 = (xMin - tx) / -dy
+    //     s1 = (xMax - tx) / -dy
+    //     s2 = (yMin - ty) / dx
+    //     s3 = (yMax - ty) / dx
+    //
+    // and we want the middle two s values.
+    //
+    // In the case where dx = 0, take s0 and s1; in the case where dy =
+    // 0, take s2 and s3.
+    //
+    // Each filled polygon is bounded by two of these line segments
+    // perpdendicular to the t axis.
+    //
+    // The t axis is bisected into smaller regions until the color
+    // difference across a region is small enough, and then the region
+    // is painted with a single color.
+
+    // set up: require at least one split to avoid problems when the two
+    // ends of the t axis have the same color
+    nComps = shading->getColorSpace()->getNComps();
+    ta[0] = tMin;
+    next[0] = axialMaxSplits / 2;
+    ta[axialMaxSplits / 2] = 0.5 * (tMin + tMax);
+    next[axialMaxSplits / 2] = axialMaxSplits;
+    ta[axialMaxSplits] = tMax;
+
+    // compute the color at t = tMin
+    if (tMin < 0) {
+      tt = t0;
+    } else if (tMin > 1) {
+      tt = t1;
+    } else {
+      tt = t0 + (t1 - t0) * tMin;
+    }
+    shading->getColor(tt, &color0);
+
+    // compute the coordinates of the point on the t axis at t = tMin;
+    // then compute the intersection of the perpendicular line with the
+    // bounding box
+    tx = x0 + tMin * dx;
+    ty = y0 + tMin * dy;
+    if (dxZero && dyZero) {
+      sMin = sMax = 0;
+    } if (dxZero) {
+      sMin = (xMin - tx) / -dy;
+      sMax = (xMax - tx) / -dy;
+      if (sMin > sMax) { tmp = sMin; sMin = sMax; sMax = tmp; }
+    } else if (dyZero) {
+      sMin = (yMin - ty) / dx;
+      sMax = (yMax - ty) / dx;
+      if (sMin > sMax) { tmp = sMin; sMin = sMax; sMax = tmp; }
+    } else {
+      s[0] = (yMin - ty) / dx;
+      s[1] = (yMax - ty) / dx;
+      s[2] = (xMin - tx) / -dy;
+      s[3] = (xMax - tx) / -dy;
+      for (j = 0; j < 3; ++j) {
+       kk = j;
+       for (k = j + 1; k < 4; ++k) {
+         if (s[k] < s[kk]) {
+           kk = k;
+         }
+       }
+       tmp = s[j]; s[j] = s[kk]; s[kk] = tmp;
+      }
+      sMin = s[1];
+      sMax = s[2];
+    }
+    ux0 = tx - sMin * dy;
+    uy0 = ty + sMin * dx;
+    vx0 = tx - sMax * dy;
+    vy0 = ty + sMax * dx;
+
+    i = 0;
+    while (i < axialMaxSplits) {
+
+      // bisect until color difference is small enough or we hit the
+      // bisection limit
+      j = next[i];
+      while (j > i + 1) {
+       if (ta[j] < 0) {
+         tt = t0;
+       } else if (ta[j] > 1) {
+         tt = t1;
+       } else {
+         tt = t0 + (t1 - t0) * ta[j];
+       }
+       shading->getColor(tt, &color1);
+       for (k = 0; k < nComps; ++k) {
+         if (abs(color1.c[k] - color0.c[k]) > axialColorDelta) {
+           break;
+         }
+       }
+       if (k == nComps) {
+         break;
+       }
+       k = (i + j) / 2;
+       ta[k] = 0.5 * (ta[i] + ta[j]);
+       next[i] = k;
+       next[k] = j;
+       j = k;
+      }
+
+      // use the average of the colors of the two sides of the region
+      for (k = 0; k < nComps; ++k) {
+       color0.c[k] = (color0.c[k] + color1.c[k]) / 2;
+      }
+
+      // compute the coordinates of the point on the t axis; then
+      // compute the intersection of the perpendicular line with the
+      // bounding box
+      tx = x0 + ta[j] * dx;
+      ty = y0 + ta[j] * dy;
+      if (dxZero && dyZero) {
+       sMin = sMax = 0;
+      } if (dxZero) {
+       sMin = (xMin - tx) / -dy;
+       sMax = (xMax - tx) / -dy;
+       if (sMin > sMax) { tmp = sMin; sMin = sMax; sMax = tmp; }
+      } else if (dyZero) {
+       sMin = (yMin - ty) / dx;
+       sMax = (yMax - ty) / dx;
+       if (sMin > sMax) { tmp = sMin; sMin = sMax; sMax = tmp; }
+      } else {
+       s[0] = (yMin - ty) / dx;
+       s[1] = (yMax - ty) / dx;
+       s[2] = (xMin - tx) / -dy;
+       s[3] = (xMax - tx) / -dy;
+       for (j = 0; j < 3; ++j) {
+         kk = j;
+         for (k = j + 1; k < 4; ++k) {
+           if (s[k] < s[kk]) {
+             kk = k;
+           }
+         }
+         tmp = s[j]; s[j] = s[kk]; s[kk] = tmp;
+       }
+       sMin = s[1];
+       sMax = s[2];
+      }
+      ux1 = tx - sMin * dy;
+      uy1 = ty + sMin * dx;
+      vx1 = tx - sMax * dy;
+      vy1 = ty + sMax * dx;
+
+      // set the color
+      state->setFillColor(&color0);
+      out->updateFillColor(state);
+
+      // fill the region
+      state->moveTo(ux0, uy0);
+      state->lineTo(vx0, vy0);
+      state->lineTo(vx1, vy1);
+      state->lineTo(ux1, uy1);
+      state->closePath();
+      out->fill(state);
+      state->clearPath();
+
+      // set up for next region
+      ux0 = ux1;
+      uy0 = uy1;
+      vx0 = vx1;
+      vy0 = vy1;
+      color0 = color1;
+      i = next[i];
+    }
+  }
+}
+
+void Gfx::doRadialShFill(GfxRadialShading *shading) {
+  double sMin, sMax, xMin, yMin, xMax, yMax;
+  double x0, y0, r0, x1, y1, r1, t0, t1;
+  int nComps;
+  GfxColor colorA, colorB;
+  double xa, ya, xb, yb, ra, rb;
+  double ta, tb, sa, sb;
+  int ia, ib, k, n;
+  double *ctm;
+  double angle, t, d0, d1;
+
+  if (out->useShadedFills()) {
+
+    out->radialShadedFill(state, shading);
+
+  } else {
+
+    // get the shading info
+    shading->getCoords(&x0, &y0, &r0, &x1, &y1, &r1);
+    t0 = shading->getDomain0();
+    t1 = shading->getDomain1();
+    nComps = shading->getColorSpace()->getNComps();
+
+    // compute the (possibly extended) s range
+    sMin = 0;
+    sMax = 1;
+    if (shading->getExtend0()) {
+      if (r0 < r1) {
+       // extend the smaller end
+       sMin = -r0 / (r1 - r0);
+      } else {
+       // extend the larger end
+       state->getUserClipBBox(&xMin, &yMin, &xMax, &yMax);
+       d0 = (x0 - xMin) * (x0 - xMin);
+       d1 = (x0 - xMax) * (x0 - xMax);
+       sMin = d0 > d1 ? d0 : d1;
+       d0 = (y0 - yMin) * (y0 - yMin);
+       d1 = (y0 - yMax) * (y0 - yMax);
+       sMin += d0 > d1 ? d0 : d1;
+       sMin = (sqrt(sMin) - r0) / (r1 - r0);
+       if (sMin > 0) {
+         sMin = 0;
+       } else if (sMin < -20) {
+         // sanity check
+         sMin = -20;
+       }
+      }
+    }
+    if (shading->getExtend1()) {
+      if (r1 < r0) {
+       // extend the smaller end
+       sMax = -r0 / (r1 - r0);
+      } else if (r1 > r0) {
+       // extend the larger end
+       state->getUserClipBBox(&xMin, &yMin, &xMax, &yMax);
+       d0 = (x1 - xMin) * (x1 - xMin);
+       d1 = (x1 - xMax) * (x1 - xMax);
+       sMax = d0 > d1 ? d0 : d1;
+       d0 = (y1 - yMin) * (y1 - yMin);
+       d1 = (y1 - yMax) * (y1 - yMax);
+       sMax += d0 > d1 ? d0 : d1;
+       sMax = (sqrt(sMax) - r0) / (r1 - r0);
+       if (sMax < 1) {
+         sMax = 1;
+       } else if (sMax > 20) {
+         // sanity check
+         sMax = 20;
+       }
+      }
+    }
+
+    // compute the number of steps into which circles must be divided to
+    // achieve a curve flatness of 0.1 pixel in device space for the
+    // largest circle (note that "device space" is 72 dpi when generating
+    // PostScript, hence the relatively small 0.1 pixel accuracy)
+    ctm = state->getCTM();
+    t = fabs(ctm[0]);
+    if (fabs(ctm[1]) > t) {
+      t = fabs(ctm[1]);
+    }
+    if (fabs(ctm[2]) > t) {
+      t = fabs(ctm[2]);
+    }
+    if (fabs(ctm[3]) > t) {
+      t = fabs(ctm[3]);
+    }
+    if (r0 > r1) {
+      t *= r0;
+    } else {
+      t *= r1;
+    }
+    if (t < 1) {
+      n = 3;
+    } else {
+      n = (int)(M_PI / acos(1 - 0.1 / t));
+      if (n < 3) {
+       n = 3;
+      } else if (n > 200) {
+       n = 200;
+      }
+    }
+
+    // Traverse the t axis and do the shading.
+    //
+    // This generates and fills a series of rings.  Each ring is defined
+    // by two circles:
+    //   sa, ta, xa, ya, ra, colorA
+    //   sb, tb, xb, yb, rb, colorB
+    //
+    // The s/t axis is divided into radialMaxSplits parts; these parts
+    // are combined as much as possible while respecting the
+    // radialColorDelta parameter.
+
+    // setup for the start circle
+    ia = 0;
+    sa = sMin;
+    ta = t0 + sa * (t1 - t0);
+    xa = x0 + sa * (x1 - x0);
+    ya = y0 + sa * (y1 - y0);
+    ra = r0 + sa * (r1 - r0);
+    if (ta < t0) {
+      shading->getColor(t0, &colorA);
+    } else if (ta > t1) {
+      shading->getColor(t1, &colorA);
+    } else {
+      shading->getColor(ta, &colorA);
+    }
+
+    while (ia < radialMaxSplits) {
+
+      // go as far along the t axis (toward t1) as we can, such that the
+      // color difference is within the tolerance (radialColorDelta) --
+      // this uses bisection (between the current value, t, and t1),
+      // limited to radialMaxSplits points along the t axis; require at
+      // least one split to avoid problems when the innermost and
+      // outermost colors are the same
+      ib = radialMaxSplits;
+      sb = sMin + ((double)ib / (double)radialMaxSplits) * (sMax - sMin);
+      tb = t0 + sb * (t1 - t0);
+      if (tb < t0) {
+       shading->getColor(t0, &colorB);
+      } else if (tb > t1) {
+       shading->getColor(t1, &colorB);
+      } else {
+       shading->getColor(tb, &colorB);
+      }
+      while (ib - ia > 1) {
+       for (k = 0; k < nComps; ++k) {
+         if (abs(colorB.c[k] - colorA.c[k]) > radialColorDelta) {
+           break;
+         }
+       }
+       if (k == nComps && ib < radialMaxSplits) {
+         break;
+       }
+       ib = (ia + ib) / 2;
+       sb = sMin + ((double)ib / (double)radialMaxSplits) * (sMax - sMin);
+       tb = t0 + sb * (t1 - t0);
+       if (tb < t0) {
+         shading->getColor(t0, &colorB);
+       } else if (tb > t1) {
+         shading->getColor(t1, &colorB);
+       } else {
+         shading->getColor(tb, &colorB);
+       }
+      }
+
+      // compute center and radius of the circle
+      xb = x0 + sb * (x1 - x0);
+      yb = y0 + sb * (y1 - y0);
+      rb = r0 + sb * (r1 - r0);
+
+      // use the average of the colors at the two circles
+      for (k = 0; k < nComps; ++k) {
+       colorA.c[k] = (colorA.c[k] + colorB.c[k]) / 2;
+      }
+      state->setFillColor(&colorA);
+      out->updateFillColor(state);
+
+      // construct path for first circle
+      state->moveTo(xa + ra, ya);
+      for (k = 1; k < n; ++k) {
+       angle = ((double)k / (double)n) * 2 * M_PI;
+       state->lineTo(xa + ra * cos(angle), ya + ra * sin(angle));
+      }
+      state->closePath();
+
+      // construct and append path for second circle
+      state->moveTo(xb + rb, yb);
+      for (k = 1; k < n; ++k) {
+       angle = ((double)k / (double)n) * 2 * M_PI;
+       state->lineTo(xb + rb * cos(angle), yb + rb * sin(angle));
+      }
+      state->closePath();
+
+      // fill the ring
+      out->eoFill(state);
+      state->clearPath();
+
+      // step to the next value of t
+      ia = ib;
+      sa = sb;
+      ta = tb;
+      xa = xb;
+      ya = yb;
+      ra = rb;
+      colorA = colorB;
+    }
+  }
+}
+
+void Gfx::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 Gfx::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);
+    out->updateFillColor(state);
+    state->moveTo(x0, y0);
+    state->lineTo(x1, y1);
+    state->lineTo(x2, y2);
+    state->closePath();
+    out->fill(state);
+    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 Gfx::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 Gfx::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]);
+    out->updateFillColor(state);
+    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();
+    out->fill(state);
+    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 Gfx::doEndPath() {
+  if (state->isCurPt() && clip != clipNone) {
+    state->clip();
+    if (clip == clipNormal) {
+      out->clip(state);
+    } else {
+      out->eoClip(state);
+    }
+  }
+  clip = clipNone;
+  state->clearPath();
+}
+
+//------------------------------------------------------------------------
+// path clipping operators
+//------------------------------------------------------------------------
+
+void Gfx::opClip(Object args[], int numArgs) {
+  clip = clipNormal;
+}
+
+void Gfx::opEOClip(Object args[], int numArgs) {
+  clip = clipEO;
+}
+
+//------------------------------------------------------------------------
+// text object operators
+//------------------------------------------------------------------------
+
+void Gfx::opBeginText(Object args[], int numArgs) {
+  state->setTextMat(1, 0, 0, 1, 0, 0);
+  state->textMoveTo(0, 0);
+  out->updateTextMat(state);
+  out->updateTextPos(state);
+  fontChanged = gTrue;
+}
+
+void Gfx::opEndText(Object args[], int numArgs) {
+  out->endTextObject(state);
+}
+
+//------------------------------------------------------------------------
+// text state operators
+//------------------------------------------------------------------------
+
+void Gfx::opSetCharSpacing(Object args[], int numArgs) {
+  state->setCharSpace(args[0].getNum());
+  out->updateCharSpace(state);
+}
+
+void Gfx::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);
+  }
+  state->setFont(font, args[1].getNum());
+  fontChanged = gTrue;
+}
+
+void Gfx::opSetTextLeading(Object args[], int numArgs) {
+  state->setLeading(args[0].getNum());
+}
+
+void Gfx::opSetTextRender(Object args[], int numArgs) {
+  state->setRender(args[0].getInt());
+  out->updateRender(state);
+}
+
+void Gfx::opSetTextRise(Object args[], int numArgs) {
+  state->setRise(args[0].getNum());
+  out->updateRise(state);
+}
+
+void Gfx::opSetWordSpacing(Object args[], int numArgs) {
+  state->setWordSpace(args[0].getNum());
+  out->updateWordSpace(state);
+}
+
+void Gfx::opSetHorizScaling(Object args[], int numArgs) {
+  state->setHorizScaling(args[0].getNum());
+  out->updateHorizScaling(state);
+  fontChanged = gTrue;
+}
+
+//------------------------------------------------------------------------
+// text positioning operators
+//------------------------------------------------------------------------
+
+void Gfx::opTextMove(Object args[], int numArgs) {
+  double tx, ty;
+
+  tx = state->getLineX() + args[0].getNum();
   ty = state->getLineY() + args[1].getNum();
   state->textMoveTo(tx, ty);
   out->updateTextPos(state);
@@ -1384,7 +2567,13 @@ void Gfx::opShowText(Object args[], int numArgs) {
     error(getPos(), "No font in show");
     return;
   }
+  if (fontChanged) {
+    out->updateFont(state);
+    fontChanged = gFalse;
+  }
+  out->beginStringOp(state);
   doShowText(args[0].getString());
+  out->endStringOp(state);
 }
 
 void Gfx::opMoveShowText(Object args[], int numArgs) {
@@ -1394,11 +2583,17 @@ void Gfx::opMoveShowText(Object args[], int numArgs) {
     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->updateTextPos(state);
+  out->beginStringOp(state);
   doShowText(args[0].getString());
+  out->endStringOp(state);
 }
 
 void Gfx::opMoveSetShowText(Object args[], int numArgs) {
@@ -1408,6 +2603,10 @@ void Gfx::opMoveSetShowText(Object args[], int numArgs) {
     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();
@@ -1416,23 +2615,40 @@ void Gfx::opMoveSetShowText(Object args[], int numArgs) {
   out->updateWordSpace(state);
   out->updateCharSpace(state);
   out->updateTextPos(state);
+  out->beginStringOp(state);
   doShowText(args[2].getString());
+  out->endStringOp(state);
 }
 
 void Gfx::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()) {
-      state->textShift(-obj.getNum() * 0.001 * state->getFontSize());
+      // 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);
+      }
       out->updateTextShift(state, obj.getNum());
     } else if (obj.isString()) {
       doShowText(obj.getString());
@@ -1441,192 +2657,179 @@ void Gfx::opShowSpaceText(Object args[], int numArgs) {
     }
     obj.free();
   }
+  out->endStringOp(state);
 }
 
 void Gfx::doShowText(GString *s) {
   GfxFont *font;
-  GfxFontEncoding16 *enc;
-  Guchar *p;
-  Guchar c8;
-  int c16;
-  GString *s16;
-  char s16a[2];
-  int m, n;
-#if 0 //~type3
-  double dx, dy, width, height, w, h, x, y;
+  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;
-  int i;
-#else
-  double dx, dy, width, height, w, h, sWidth, sHeight;
-#endif
+  char *p;
+  int len, n, uLen, nChars, nSpaces, i;
 
-  if (fontChanged) {
-    out->updateFont(state);
-    fontChanged = gFalse;
-  }
   font = state->getFont();
+  wMode = font->getWMode();
 
-  //----- 16-bit font
-  if (font->is16Bit()) {
-    enc = font->getEncoding16();
-    if (out->useDrawChar()) {
-      out->beginString(state, s);
-      s16 = NULL;
-    } else {
-      s16 = new GString();
-    }
-    sWidth = sHeight = 0;
-    state->textTransformDelta(0, state->getRise(), &dx, &dy);
-    p = (Guchar *)s->getCString();
-    n = s->getLength();
-    while (n > 0) {
-      m = getNextChar16(enc, p, &c16);
-      if (enc->wMode == 0) {
-       width = state->getFontSize() * state->getHorizScaling() *
-               font->getWidth16(c16) +
-               state->getCharSpace();
-       if (c16 == ' ') {
-         width += state->getWordSpace();
-       }
-       height = 0;
-      } else {
-       width = 0;
-       height = state->getFontSize() * font->getHeight16(c16);
-      }
-      state->textTransformDelta(width, height, &w, &h);
-      if (out->useDrawChar()) {
-       out->drawChar16(state, state->getCurX() + dx, state->getCurY() + dy,
-                       w, h, c16);
-       state->textShift(width, height);
-      } else {
-       s16a[0] = (char)(c16 >> 8);
-       s16a[1] = (char)c16;
-       s16->append(s16a, 2);
-       sWidth += w;
-       sHeight += h;
-      }
-      n -= m;
-      p += m;
-    }
-    if (out->useDrawChar()) {
-      out->endString(state);
-    } else {
-      out->drawString16(state, s16);
-      delete s16;
-      state->textShift(sWidth, sHeight);
-    }
+  if (out->useDrawChar()) {
+    out->beginString(state, s);
+  }
 
-  //----- 8-bit font
-  } else {
-#if 0 //~type3
-    //~ also check out->renderType3()
-    if (font->getType() == fontType3) {
-      out->beginString(state, s);
-      mat = state->getCTM();
-      for (i = 0; i < 6; ++i) {
-       oldCTM[i] = mat[i];
+  // handle a Type 3 char
+  if (font->getType() == fontType3 && 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();
       }
-      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[3] *= state->getFontSize();
-      newCTM[0] *= state->getHorizScaling();
-      newCTM[2] *= state->getHorizScaling();
-      state->textTransformDelta(0, state->getRise(), &dx, &dy);
-      oldParser = parser;
-      for (p = (Guchar *)s->getCString(), n = s->getLength(); n; ++p, --n) {
-       c8 = *p;
-       font->getCharProc(c8, &charProc);
-       state->transform(state->getCurX() + dx, state->getCurY() + dy, &x, &y);
-       state->setCTM(newCTM[0], newCTM[1], newCTM[2], newCTM[3], x, y);
-       //~ out->updateCTM(???)
+      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);
+      //~ out->updateCTM(???)
+      if (!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()) {
          display(&charProc, gFalse);
        } else {
          error(getPos(), "Missing or bad Type3 CharProc entry");
        }
-       state->setCTM(oldCTM[0], oldCTM[1], oldCTM[2],
-                     oldCTM[3], oldCTM[4], oldCTM[5]);
-       //~ out->updateCTM(???) - use gsave/grestore instead?
+       out->endType3Char(state);
+       if (resDict) {
+         popResources();
+       }
        charProc.free();
-       width = state->getFontSize() * state->getHorizScaling() *
-               font->getWidth(c8) +
-               state->getCharSpace();
-       if (c8 == ' ') {
-         width += state->getWordSpace();
+      }
+      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 (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 (wMode) {
+       dx *= state->getFontSize();
+       dy = dy * state->getFontSize() + state->getCharSpace();
+       if (n == 1 && *p == ' ') {
+         dy += state->getWordSpace();
        }
-       state->textShift(width);
+      } else {
+       dx = dx * state->getFontSize() + state->getCharSpace();
+       if (n == 1 && *p == ' ') {
+         dx += state->getWordSpace();
+       }
+       dx *= state->getHorizScaling();
+       dy *= state->getFontSize();
       }
-      parser = oldParser;
-      out->endString(state);
-    } else
-#endif
-    if (out->useDrawChar()) {
-      out->beginString(state, s);
-      state->textTransformDelta(0, state->getRise(), &dx, &dy);
-      for (p = (Guchar *)s->getCString(), n = s->getLength(); n; ++p, --n) {
-       c8 = *p;
-       width = state->getFontSize() * state->getHorizScaling() *
-               font->getWidth(c8) +
-               state->getCharSpace();
-       if (c8 == ' ')
-         width += state->getWordSpace();
-       state->textTransformDelta(width, 0, &w, &h);
-       out->drawChar(state, state->getCurX() + dx, state->getCurY() + dy,
-                     w, h, c8);
-       state->textShift(width);
+      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;
       }
-      out->endString(state);
+      ++nChars;
+      p += n;
+      len -= n;
+    }
+    if (wMode) {
+      dx *= state->getFontSize();
+      dy = dy * state->getFontSize()
+          + nChars * state->getCharSpace()
+          + nSpaces * state->getWordSpace();
     } else {
-      out->drawString(state, s);
-      width = state->getFontSize() * state->getHorizScaling() *
-             font->getWidth(s) +
-             s->getLength() * state->getCharSpace();
-      for (p = (Guchar *)s->getCString(), n = s->getLength(); n; ++p, --n) {
-       if (*p == ' ')
-         width += state->getWordSpace();
-      }
-      state->textShift(width);
+      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);
   }
-}
 
-int Gfx::getNextChar16(GfxFontEncoding16 *enc, Guchar *p, int *c16) {
-  int n;
-  int code;
-  int a, b, m;
-
-  n = enc->codeLen[*p];
-  if (n == 1) {
-    *c16 = enc->map1[*p];
-  } else {
-    code = (p[0] << 8) + p[1];
-    a = 0;
-    b = enc->map2Len;
-    // invariant: map2[2*a] <= code < map2[2*b]
-    while (b - a > 1) {
-      m = (a + b) / 2;
-      if (enc->map2[2*m] <= code)
-       a = m;
-      else if (enc->map2[2*m] > code)
-       b = m;
-      else
-       break;
-    }
-    *c16 = enc->map2[2*a+1] + (code - enc->map2[2*a]);
+  if (out->useDrawChar()) {
+    out->endString(state);
   }
-  return n;
+
+  updateLevel += 10 * s->getLength();
 }
 
 //------------------------------------------------------------------------
@@ -1634,7 +2837,7 @@ int Gfx::getNextChar16(GfxFontEncoding16 *enc, Guchar *p, int *c16) {
 //------------------------------------------------------------------------
 
 void Gfx::opXObject(Object args[], int numArgs) {
-  Object obj1, obj2, refObj;
+  Object obj1, obj2, obj3, refObj;
 #if OPI_SUPPORT
   Object opiDict;
 #endif
@@ -1655,11 +2858,17 @@ void Gfx::opXObject(Object args[], int numArgs) {
 #endif
   obj1.streamGetDict()->lookup("Subtype", &obj2);
   if (obj2.isName("Image")) {
-    res->lookupXObjectNF(args[0].getName(), &refObj);
-    doImage(&refObj, obj1.getStream(), gFalse);
-    refObj.free();
+    if (out->needNonText()) {
+      res->lookupXObjectNF(args[0].getName(), &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 {
@@ -1676,14 +2885,27 @@ void Gfx::opXObject(Object args[], int numArgs) {
 }
 
 void Gfx::doImage(Object *ref, Stream *str, GBool inlineImg) {
-  Dict *dict;
-  Object obj1, obj2;
+  Dict *dict, *maskDict;
   int width, height;
-  int bits;
+  int bits, maskBits;
+  StreamColorSpaceMode csMode;
   GBool mask;
-  GfxColorSpace *colorSpace;
-  GfxImageColorMap *colorMap;
   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();
@@ -1722,15 +2944,21 @@ void Gfx::doImage(Object *ref, Stream *str, GBool inlineImg) {
   obj1.free();
 
   // bit depth
-  dict->lookup("BitsPerComponent", &obj1);
-  if (obj1.isNull()) {
+  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();
-    dict->lookup("BPC", &obj1);
   }
-  if (!obj1.isInt())
-    goto err2;
-  bits = obj1.getInt();
-  obj1.free();
 
   // display a mask
   if (mask) {
@@ -1774,7 +3002,17 @@ void Gfx::doImage(Object *ref, Stream *str, GBool inlineImg) {
        obj2.free();
       }
     }
-    colorSpace = GfxColorSpace::parse(&obj1);
+    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;
@@ -1791,12 +3029,171 @@ void Gfx::doImage(Object *ref, Stream *str, GBool inlineImg) {
       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
-    out->drawImage(state, ref, str, width, height, colorMap, inlineImg);
+    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;
-    str->close();
+
+    maskObj.free();
+    smaskObj.free();
   }
 
+  if ((i = width * height) > 1000) {
+    i = 1000;
+  }
+  updateLevel += i;
+
   return;
 
  err2:
@@ -1814,12 +3211,17 @@ void Gfx::doForm(Object *str) {
   Object obj1;
   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.isInt() && obj1.getInt() == 1)) {
+  if (!(obj1.isNull() || (obj1.isInt() && obj1.getInt() == 1))) {
     error(getPos(), "Unknown form type");
   }
   obj1.free();
@@ -1856,27 +3258,32 @@ void Gfx::doForm(Object *str) {
 
   // get resources
   dict->lookup("Resources", &resObj);
-  resDict = resObj.isDict() ? resObj.getDict() : NULL;
+  resDict = resObj.isDict() ? resObj.getDict() : (Dict *)NULL;
 
   // draw it
+  ++formDepth;
   doForm1(str, resDict, m, bbox);
+  --formDepth;
 
   resObj.free();
 }
 
-void Gfx::doWidgetForm(Object *str, double xMin, double yMin,
-                      double xMax, double yMax) {
+void Gfx::doAnnot(Object *str, double xMin, double yMin,
+                 double xMax, double yMax) {
   Dict *dict, *resDict;
   Object matrixObj, bboxObj, resObj;
   Object obj1;
-  double m[6], bbox[6];
-  double sx, sy;
+  double m[6], bbox[6], ictm[6];
+  double *ctm;
+  double formX0, formY0, formX1, formY1;
+  double annotX0, annotY0, annotX1, annotY1;
+  double det, x, y, sx, sy;
   int i;
 
   // get stream dict
   dict = str->streamGetDict();
 
-  // get bounding box
+  // get the form bounding box
   dict->lookup("BBox", &bboxObj);
   if (!bboxObj.isArray()) {
     bboxObj.free();
@@ -1890,7 +3297,7 @@ void Gfx::doWidgetForm(Object *str, double xMin, double yMin,
   }
   bboxObj.free();
 
-  // get matrix
+  // get the form matrix
   dict->lookup("Matrix", &matrixObj);
   if (matrixObj.isArray()) {
     for (i = 0; i < 6; ++i) {
@@ -1905,20 +3312,68 @@ void Gfx::doWidgetForm(Object *str, double xMin, double yMin,
   }
   matrixObj.free();
 
-  // scale form bbox to widget rectangle
-  sx = fabs((xMax - xMin) / (bbox[2] - bbox[0]));
-  sy = fabs((yMax - yMin) / (bbox[3] - bbox[1]));
-  m[0] *= sx;  m[1] *= sy;
-  m[2] *= sx;  m[3] *= sy;
-  m[4] *= sx;  m[5] *= sy;
+  // transform the form bbox from form space to user space
+  formX0 = bbox[0] * m[0] + bbox[1] * m[2] + m[4];
+  formY0 = bbox[0] * m[1] + bbox[1] * m[3] + m[5];
+  formX1 = bbox[2] * m[0] + bbox[3] * m[2] + m[4];
+  formY1 = bbox[2] * m[1] + bbox[3] * m[3] + m[5];
+
+  // transform the annotation bbox from default user space to user
+  // space: (bbox * baseMatrix) * iCTM
+  ctm = state->getCTM();
+  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;
+  x = baseMatrix[0] * xMin + baseMatrix[2] * yMin + baseMatrix[4];
+  y = baseMatrix[1] * xMin + baseMatrix[3] * yMin + baseMatrix[5];
+  annotX0 = ictm[0] * x + ictm[2] * y + ictm[4];
+  annotY0 = ictm[1] * x + ictm[3] * y + ictm[5];
+  x = baseMatrix[0] * xMax + baseMatrix[2] * yMax + baseMatrix[4];
+  y = baseMatrix[1] * xMax + baseMatrix[3] * yMax + baseMatrix[5];
+  annotX1 = ictm[0] * x + ictm[2] * y + ictm[4];
+  annotY1 = ictm[1] * x + ictm[3] * y + ictm[5];
+
+  // swap min/max coords
+  if (formX0 > formX1) {
+    x = formX0; formX0 = formX1; formX1 = x;
+  }
+  if (formY0 > formY1) {
+    y = formY0; formY0 = formY1; formY1 = y;
+  }
+  if (annotX0 > annotX1) {
+    x = annotX0; annotX0 = annotX1; annotX1 = x;
+  }
+  if (annotY0 > annotY1) {
+    y = annotY0; annotY0 = annotY1; annotY1 = y;
+  }
 
-  // translate to widget rectangle
-  m[4] += xMin;
-  m[5] += yMin;
+  // scale the form to fit the annotation bbox
+  if (formX1 == formX0) {
+    // this shouldn't happen
+    sx = 1;
+  } else {
+    sx = (annotX1 - annotX0) / (formX1 - formX0);
+  }
+  if (formY1 == formY0) {
+    // this shouldn't happen
+    sy = 1;
+  } else {
+    sy = (annotY1 - annotY0) / (formY1 - formY0);
+  }
+  m[0] *= sx;
+  m[2] *= sx;
+  m[4] = (m[4] - formX0) * sx + annotX0;
+  m[1] *= sy;
+  m[3] *= sy;
+  m[5] = (m[5] - formY0) * sy + annotY0;
 
   // get resources
   dict->lookup("Resources", &resObj);
-  resDict = resObj.isDict() ? resObj.getDict() : NULL;
+  resDict = resObj.isDict() ? resObj.getDict() : (Dict *)NULL;
 
   // draw it
   doForm1(str, resDict, m, bbox);
@@ -1930,15 +3385,16 @@ void Gfx::doWidgetForm(Object *str, double xMin, double yMin,
 void Gfx::doForm1(Object *str, Dict *resDict, double *matrix, double *bbox) {
   Parser *oldParser;
   double oldBaseMatrix[6];
-  GfxResources *resPtr;
   int i;
 
   // push new resources on stack
-  res = new GfxResources(resDict, res);
+  pushResources(resDict);
 
   // save current graphics state
-  out->saveState(state);
-  state = state->save();
+  saveState();
+
+  // kill any pre-existing path
+  state->clearPath();
 
   // save current parser
   oldParser = parser;
@@ -1961,6 +3417,7 @@ void Gfx::doForm1(Object *str, Dict *resDict, double *matrix, double *bbox) {
   state->lineTo(bbox[2], bbox[3]);
   state->lineTo(bbox[0], bbox[3]);
   state->closePath();
+  state->clip();
   out->clip(state);
   state->clearPath();
 
@@ -1976,13 +3433,10 @@ void Gfx::doForm1(Object *str, Dict *resDict, double *matrix, double *bbox) {
   parser = oldParser;
 
   // restore graphics state
-  state = state->restore();
-  out->restoreState(state);
+  restoreState();
 
   // pop resource stack
-  resPtr = res->getNext();
-  delete res;
-  res = resPtr;
+  popResources();
 
   return;
 }
@@ -2020,29 +3474,34 @@ Stream *Gfx::buildImageStream() {
   Stream *str;
 
   // build dictionary
-  dict.initDict();
+  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();
-      parser->getObj(&obj);
     } else {
       key = copyString(obj.getName());
       obj.free();
       parser->getObj(&obj);
-      if (obj.isEOF() || obj.isError())
+      if (obj.isEOF() || obj.isError()) {
+       gfree(key);
        break;
+      }
       dict.dictAdd(key, &obj);
     }
     parser->getObj(&obj);
   }
-  if (obj.isEOF())
+  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);
+  str = new EmbedStream(parser->getStream(), &dict, gFalse, 0);
   str = str->addFilters(&dict);
 
   return str;
@@ -2061,11 +3520,13 @@ void Gfx::opEndImage(Object args[], int numArgs) {
 //------------------------------------------------------------------------
 
 void Gfx::opSetCharWidth(Object args[], int numArgs) {
-  error(getPos(), "Encountered 'd0' operator in content stream");
+  out->type3D0(state, args[0].getNum(), args[1].getNum());
 }
 
 void Gfx::opSetCacheDevice(Object args[], int numArgs) {
-  error(getPos(), "Encountered 'd1' operator in content stream");
+  out->type3D1(state, args[0].getNum(), args[1].getNum(),
+              args[2].getNum(), args[3].getNum(),
+              args[4].getNum(), args[5].getNum());
 }
 
 //------------------------------------------------------------------------
@@ -2091,6 +3552,7 @@ void Gfx::opBeginMarkedContent(Object args[], int numArgs) {
     if (numArgs == 2)
       args[2].print(stdout);
     printf("\n");
+    fflush(stdout);
   }
 }
 
@@ -2103,5 +3565,32 @@ void Gfx::opMarkPoint(Object args[], int numArgs) {
     if (numArgs == 2)
       args[2].print(stdout);
     printf("\n");
+    fflush(stdout);
   }
 }
+
+//------------------------------------------------------------------------
+// misc
+//------------------------------------------------------------------------
+
+void Gfx::saveState() {
+  out->saveState(state);
+  state = state->save();
+}
+
+void Gfx::restoreState() {
+  state = state->restore();
+  out->restoreState(state);
+}
+
+void Gfx::pushResources(Dict *resDict) {
+  res = new GfxResources(xref, resDict, res);
+}
+
+void Gfx::popResources() {
+  GfxResources *resPtr;
+
+  resPtr = res->getNext();
+  delete res;
+  res = resPtr;
+}