diff options
Diffstat (limited to 'kpdf/xpdf/xpdf/Gfx.cpp')
| -rw-r--r-- | kpdf/xpdf/xpdf/Gfx.cpp | 4189 | 
1 files changed, 4189 insertions, 0 deletions
| diff --git a/kpdf/xpdf/xpdf/Gfx.cpp b/kpdf/xpdf/xpdf/Gfx.cpp new file mode 100644 index 00000000..2a4fdb6e --- /dev/null +++ b/kpdf/xpdf/xpdf/Gfx.cpp @@ -0,0 +1,4189 @@ +//======================================================================== +// +// Gfx.cpp +// +// Copyright 1996-2003 Glyph & Cog, LLC +// +//======================================================================== + +#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 "Stream.h" +#include "Lexer.h" +#include "Parser.h" +#include "GfxFont.h" +#include "GfxState.h" +#include "OutputDev.h" +#include "Page.h" +#include "Annot.h" +#include "Error.h" +#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 6 + +// Max delta allowed in any color component for a Gouraud triangle +// shading fill. +#define gouraudColorDelta (dblToCol(1 / 256.0)) + +// Max recursive depth for a patch mesh shading fill. +#define patchMaxDepth 6 + +// Max delta allowed in any color component for a patch mesh shading +// fill. +#define patchColorDelta (dblToCol(1 / 256.0)) + +//------------------------------------------------------------------------ +// 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}, +  {"'",   1, {tchkString}, +          &Gfx::opMoveShowText}, +  {"B",   0, {tchkNone}, +          &Gfx::opFillStroke}, +  {"B*",  0, {tchkNone}, +          &Gfx::opEOFillStroke}, +  {"BDC", 2, {tchkName,   tchkProps}, +          &Gfx::opBeginMarkedContent}, +  {"BI",  0, {tchkNone}, +          &Gfx::opBeginImage}, +  {"BMC", 1, {tchkName}, +          &Gfx::opBeginMarkedContent}, +  {"BT",  0, {tchkNone}, +          &Gfx::opBeginText}, +  {"BX",  0, {tchkNone}, +          &Gfx::opBeginIgnoreUndef}, +  {"CS",  1, {tchkName}, +          &Gfx::opSetStrokeColorSpace}, +  {"DP",  2, {tchkName,   tchkProps}, +          &Gfx::opMarkPoint}, +  {"Do",  1, {tchkName}, +          &Gfx::opXObject}, +  {"EI",  0, {tchkNone}, +          &Gfx::opEndImage}, +  {"EMC", 0, {tchkNone}, +          &Gfx::opEndMarkedContent}, +  {"ET",  0, {tchkNone}, +          &Gfx::opEndText}, +  {"EX",  0, {tchkNone}, +          &Gfx::opEndIgnoreUndef}, +  {"F",   0, {tchkNone}, +          &Gfx::opFill}, +  {"G",   1, {tchkNum}, +          &Gfx::opSetStrokeGray}, +  {"ID",  0, {tchkNone}, +          &Gfx::opImageData}, +  {"J",   1, {tchkInt}, +          &Gfx::opSetLineCap}, +  {"K",   4, {tchkNum,    tchkNum,    tchkNum,    tchkNum}, +          &Gfx::opSetStrokeCMYKColor}, +  {"M",   1, {tchkNum}, +          &Gfx::opSetMiterLimit}, +  {"MP",  1, {tchkName}, +          &Gfx::opMarkPoint}, +  {"Q",   0, {tchkNone}, +          &Gfx::opRestore}, +  {"RG",  3, {tchkNum,    tchkNum,    tchkNum}, +          &Gfx::opSetStrokeRGBColor}, +  {"S",   0, {tchkNone}, +          &Gfx::opStroke}, +  {"SC",  -4, {tchkNum,   tchkNum,    tchkNum,    tchkNum}, +          &Gfx::opSetStrokeColor}, +  {"SCN", -33, {tchkSCN,   tchkSCN,    tchkSCN,    tchkSCN, +	        tchkSCN,   tchkSCN,    tchkSCN,    tchkSCN, +	        tchkSCN,   tchkSCN,    tchkSCN,    tchkSCN, +	        tchkSCN,   tchkSCN,    tchkSCN,    tchkSCN, +	        tchkSCN,   tchkSCN,    tchkSCN,    tchkSCN, +	        tchkSCN,   tchkSCN,    tchkSCN,    tchkSCN, +	        tchkSCN,   tchkSCN,    tchkSCN,    tchkSCN, +	        tchkSCN,   tchkSCN,    tchkSCN,    tchkSCN, +	        tchkSCN}, +          &Gfx::opSetStrokeColorN}, +  {"T*",  0, {tchkNone}, +          &Gfx::opTextNextLine}, +  {"TD",  2, {tchkNum,    tchkNum}, +          &Gfx::opTextMoveSet}, +  {"TJ",  1, {tchkArray}, +          &Gfx::opShowSpaceText}, +  {"TL",  1, {tchkNum}, +          &Gfx::opSetTextLeading}, +  {"Tc",  1, {tchkNum}, +          &Gfx::opSetCharSpacing}, +  {"Td",  2, {tchkNum,    tchkNum}, +          &Gfx::opTextMove}, +  {"Tf",  2, {tchkName,   tchkNum}, +          &Gfx::opSetFont}, +  {"Tj",  1, {tchkString}, +          &Gfx::opShowText}, +  {"Tm",  6, {tchkNum,    tchkNum,    tchkNum,    tchkNum, +	      tchkNum,    tchkNum}, +          &Gfx::opSetTextMatrix}, +  {"Tr",  1, {tchkInt}, +          &Gfx::opSetTextRender}, +  {"Ts",  1, {tchkNum}, +          &Gfx::opSetTextRise}, +  {"Tw",  1, {tchkNum}, +          &Gfx::opSetWordSpacing}, +  {"Tz",  1, {tchkNum}, +          &Gfx::opSetHorizScaling}, +  {"W",   0, {tchkNone}, +          &Gfx::opClip}, +  {"W*",  0, {tchkNone}, +          &Gfx::opEOClip}, +  {"b",   0, {tchkNone}, +          &Gfx::opCloseFillStroke}, +  {"b*",  0, {tchkNone}, +          &Gfx::opCloseEOFillStroke}, +  {"c",   6, {tchkNum,    tchkNum,    tchkNum,    tchkNum, +	      tchkNum,    tchkNum}, +          &Gfx::opCurveTo}, +  {"cm",  6, {tchkNum,    tchkNum,    tchkNum,    tchkNum, +	      tchkNum,    tchkNum}, +          &Gfx::opConcat}, +  {"cs",  1, {tchkName}, +          &Gfx::opSetFillColorSpace}, +  {"d",   2, {tchkArray,  tchkNum}, +          &Gfx::opSetDash}, +  {"d0",  2, {tchkNum,    tchkNum}, +          &Gfx::opSetCharWidth}, +  {"d1",  6, {tchkNum,    tchkNum,    tchkNum,    tchkNum, +	      tchkNum,    tchkNum}, +          &Gfx::opSetCacheDevice}, +  {"f",   0, {tchkNone}, +          &Gfx::opFill}, +  {"f*",  0, {tchkNone}, +          &Gfx::opEOFill}, +  {"g",   1, {tchkNum}, +          &Gfx::opSetFillGray}, +  {"gs",  1, {tchkName}, +          &Gfx::opSetExtGState}, +  {"h",   0, {tchkNone}, +          &Gfx::opClosePath}, +  {"i",   1, {tchkNum}, +          &Gfx::opSetFlat}, +  {"j",   1, {tchkInt}, +          &Gfx::opSetLineJoin}, +  {"k",   4, {tchkNum,    tchkNum,    tchkNum,    tchkNum}, +          &Gfx::opSetFillCMYKColor}, +  {"l",   2, {tchkNum,    tchkNum}, +          &Gfx::opLineTo}, +  {"m",   2, {tchkNum,    tchkNum}, +          &Gfx::opMoveTo}, +  {"n",   0, {tchkNone}, +          &Gfx::opEndPath}, +  {"q",   0, {tchkNone}, +          &Gfx::opSave}, +  {"re",  4, {tchkNum,    tchkNum,    tchkNum,    tchkNum}, +          &Gfx::opRectangle}, +  {"rg",  3, {tchkNum,    tchkNum,    tchkNum}, +          &Gfx::opSetFillRGBColor}, +  {"ri",  1, {tchkName}, +          &Gfx::opSetRenderingIntent}, +  {"s",   0, {tchkNone}, +          &Gfx::opCloseStroke}, +  {"sc",  -4, {tchkNum,   tchkNum,    tchkNum,    tchkNum}, +          &Gfx::opSetFillColor}, +  {"scn", -33, {tchkSCN,   tchkSCN,    tchkSCN,    tchkSCN, +	        tchkSCN,   tchkSCN,    tchkSCN,    tchkSCN, +	        tchkSCN,   tchkSCN,    tchkSCN,    tchkSCN, +	        tchkSCN,   tchkSCN,    tchkSCN,    tchkSCN, +	        tchkSCN,   tchkSCN,    tchkSCN,    tchkSCN, +	        tchkSCN,   tchkSCN,    tchkSCN,    tchkSCN, +	        tchkSCN,   tchkSCN,    tchkSCN,    tchkSCN, +	        tchkSCN,   tchkSCN,    tchkSCN,    tchkSCN, +	        tchkSCN}, +          &Gfx::opSetFillColorN}, +  {"sh",  1, {tchkName}, +          &Gfx::opShFill}, +  {"v",   4, {tchkNum,    tchkNum,    tchkNum,    tchkNum}, +          &Gfx::opCurveTo1}, +  {"w",   1, {tchkNum}, +          &Gfx::opSetLineWidth}, +  {"y",   4, {tchkNum,    tchkNum,    tchkNum,    tchkNum}, +          &Gfx::opCurveTo2}, +}; + +#ifdef WIN32 // this works around a bug in the VC7 compiler +#  pragma optimize("",on) +#endif + +#define numOps (sizeof(opTab) / sizeof(Operator)) + +//------------------------------------------------------------------------ +// GfxResources +//------------------------------------------------------------------------ + +GfxResources::GfxResources(XRef *xref, Dict *resDict, GfxResources *nextA) { +  Object obj1, obj2; +  Ref r; + +  if (resDict) { + +    // build font dictionary +    fonts = NULL; +    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(); + +    // get XObject dictionary +    resDict->lookup("XObject", &xObjDict); + +    // get color space dictionary +    resDict->lookup("ColorSpace", &colorSpaceDict); + +    // get pattern dictionary +    resDict->lookup("Pattern", &patternDict); + +    // get shading dictionary +    resDict->lookup("Shading", &shadingDict); + +    // get graphics state parameter dictionary +    resDict->lookup("ExtGState", &gStateDict); + +  } else { +    fonts = NULL; +    xObjDict.initNull(); +    colorSpaceDict.initNull(); +    patternDict.initNull(); +    shadingDict.initNull(); +    gStateDict.initNull(); +  } + +  next = nextA; +} + +GfxResources::~GfxResources() { +  if (fonts) { +    delete fonts; +  } +  xObjDict.free(); +  colorSpaceDict.free(); +  patternDict.free(); +  shadingDict.free(); +  gStateDict.free(); +} + +GfxFont *GfxResources::lookupFont(char *name) { +  GfxFont *font; +  GfxResources *resPtr; + +  for (resPtr = this; resPtr; resPtr = resPtr->next) { +    if (resPtr->fonts) { +      if ((font = resPtr->fonts->lookup(name))) +	return font; +    } +  } +  error(-1, "Unknown font tag '%s'", name); +  return NULL; +} + +GBool GfxResources::lookupXObject(char *name, Object *obj) { +  GfxResources *resPtr; + +  for (resPtr = this; resPtr; resPtr = resPtr->next) { +    if (resPtr->xObjDict.isDict()) { +      if (!resPtr->xObjDict.dictLookup(name, obj)->isNull()) +	return gTrue; +      obj->free(); +    } +  } +  error(-1, "XObject '%s' is unknown", name); +  return gFalse; +} + +GBool GfxResources::lookupXObjectNF(char *name, Object *obj) { +  GfxResources *resPtr; + +  for (resPtr = this; resPtr; resPtr = resPtr->next) { +    if (resPtr->xObjDict.isDict()) { +      if (!resPtr->xObjDict.dictLookupNF(name, obj)->isNull()) +	return gTrue; +      obj->free(); +    } +  } +  error(-1, "XObject '%s' is unknown", name); +  return gFalse; +} + +void GfxResources::lookupColorSpace(char *name, Object *obj) { +  GfxResources *resPtr; + +  for (resPtr = this; resPtr; resPtr = resPtr->next) { +    if (resPtr->colorSpaceDict.isDict()) { +      if (!resPtr->colorSpaceDict.dictLookup(name, obj)->isNull()) { +	return; +      } +      obj->free(); +    } +  } +  obj->initNull(); +} + +GfxPattern *GfxResources::lookupPattern(char *name) { +  GfxResources *resPtr; +  GfxPattern *pattern; +  Object obj; + +  for (resPtr = this; resPtr; resPtr = resPtr->next) { +    if (resPtr->patternDict.isDict()) { +      if (!resPtr->patternDict.dictLookup(name, &obj)->isNull()) { +	pattern = GfxPattern::parse(&obj); +	obj.free(); +	return pattern; +      } +      obj.free(); +    } +  } +  error(-1, "Unknown pattern '%s'", 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; + +  for (resPtr = this; resPtr; resPtr = resPtr->next) { +    if (resPtr->gStateDict.isDict()) { +      if (!resPtr->gStateDict.dictLookup(name, obj)->isNull()) { +	return gTrue; +      } +      obj->free(); +    } +  } +  error(-1, "ExtGState '%s' is unknown", name); +  return gFalse; +} + +//------------------------------------------------------------------------ +// Gfx +//------------------------------------------------------------------------ + +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(xref, resDict, NULL); + +  // initialize +  out = outA; +  state = new GfxState(hDPI, vDPI, box, rotate, out->upsideDown()); +  fontChanged = gFalse; +  clip = clipNone; +  ignoreUndef = 0; +  out->startPage(pageNum, state); +  out->setDefaultCTM(state->getCTM()); +  out->updateAll(state); +  for (i = 0; i < 6; ++i) { +    baseMatrix[i] = state->getCTM()[i]; +  } +  formDepth = 0; +  parser = NULL; +  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; +  parser = NULL; +  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() { +  while (state->hasSaves()) { +    restoreState(); +  } +  if (!subPage) { +    out->endPage(); +  } +  while (res) { +    popResources(); +  } +  if (state) { +    delete state; +  } +} + +void Gfx::display(Object *obj, GBool topLevel) { +  Object obj2; +  int i; + +  if (obj->isArray()) { +    for (i = 0; i < obj->arrayGetLength(); ++i) { +      obj->arrayGet(i, &obj2); +      if (!obj2.isStream()) { +	error(-1, "Weird page contents"); +	obj2.free(); +	return; +      } +      obj2.free(); +    } +  } else if (!obj->isStream()) { +    error(-1, "Weird page contents"); +    return; +  } +  parser = new Parser(xref, new Lexer(xref, obj), gFalse); +  go(topLevel); +  delete parser; +  parser = NULL; +} + +void Gfx::go(GBool topLevel) { +  Object obj; +  Object args[maxArgs]; +  int numArgs, i; +  int lastAbortCheck; + +  // scan a sequence of objects +  updateLevel = lastAbortCheck = 0; +  numArgs = 0; +  parser->getObj(&obj); +  while (!obj.isEOF()) { + +    // got a command - execute it +    if (obj.isCmd()) { +      if (printCommands) { +	obj.print(stdout); +	for (i = 0; i < numArgs; ++i) { +	  printf(" "); +	  args[i].print(stdout); +	} +	printf("\n"); +	fflush(stdout); +      } +      execOp(&obj, args, numArgs); +      obj.free(); +      for (i = 0; i < numArgs; ++i) +	args[i].free(); +      numArgs = 0; + +      // periodically update display +      if (++updateLevel >= 20000) { +	out->dump(); +	updateLevel = 0; +      } + +      // check for an abort +      if (abortCheckCbk) { +	if (updateLevel - lastAbortCheck > 10) { +	  if ((*abortCheckCbk)(abortCheckCbkData)) { +	    break; +	  } +	  lastAbortCheck = updateLevel; +	} +      } + +    // got an argument - save it +    } else if (numArgs < maxArgs) { +      args[numArgs++] = obj; + +    // too many arguments - something is wrong +    } else { +      error(getPos(), "Too many args in content stream"); +      if (printCommands) { +	printf("throwing away arg: "); +	obj.print(stdout); +	printf("\n"); +	fflush(stdout); +      } +      obj.free(); +    } + +    // grab the next object +    parser->getObj(&obj); +  } +  obj.free(); + +  // args at end with no command +  if (numArgs > 0) { +    error(getPos(), "Leftover args in content stream"); +    if (printCommands) { +      printf("%d leftovers:", numArgs); +      for (i = 0; i < numArgs; ++i) { +	printf(" "); +	args[i].print(stdout); +      } +      printf("\n"); +      fflush(stdout); +    } +    for (i = 0; i < numArgs; ++i) +      args[i].free(); +  } + +  // update display +  if (topLevel && updateLevel > 0) { +    out->dump(); +  } +} + +void Gfx::execOp(Object *cmd, Object args[], int numArgs) { +  Operator *op; +  char *name; +  Object *argPtr; +  int i; + +  // find operator +  name = cmd->getCmd(); +  if (!(op = findOp(name))) { +    if (ignoreUndef == 0) +      error(getPos(), "Unknown operator '%s'", name); +    return; +  } + +  // type check args +  argPtr = args; +  if (op->numArgs >= 0) { +    if (numArgs < op->numArgs) { +      error(getPos(), "Too few (%d) args to '%s' operator", numArgs, name); +      return; +    } +    if (numArgs > op->numArgs) { +#if 0 +      error(getPos(), "Too many (%d) args to '%s' operator", numArgs, name); +#endif +      argPtr += numArgs - op->numArgs; +      numArgs = op->numArgs; +    } +  } else { +    if (numArgs > -op->numArgs) { +      error(getPos(), "Too many (%d) args to '%s' operator", +	    numArgs, name); +      return; +    } +  } +  for (i = 0; i < numArgs; ++i) { +    if (!checkArg(&argPtr[i], op->tchk[i])) { +      error(getPos(), "Arg #%d to '%s' operator is wrong type (%s)", +	    i, name, argPtr[i].getTypeName()); +      return; +    } +  } + +  // do it +  (this->*op->func)(argPtr, numArgs); +} + +Operator *Gfx::findOp(char *name) { +  int a, b, m, cmp; + +  a = -1; +  b = numOps; +  // invariant: opTab[a] < name < opTab[b] +  while (b - a > 1) { +    m = (a + b) / 2; +    cmp = strcmp(opTab[m].name, name); +    if (cmp < 0) +      a = m; +    else if (cmp > 0) +      b = m; +    else +      a = b = m; +  } +  if (cmp != 0) +    return NULL; +  return &opTab[a]; +} + +GBool Gfx::checkArg(Object *arg, TchkType type) { +  switch (type) { +  case tchkBool:   return arg->isBool(); +  case tchkInt:    return arg->isInt(); +  case tchkNum:    return arg->isNum(); +  case tchkString: return arg->isString(); +  case tchkName:   return arg->isName(); +  case tchkArray:  return arg->isArray(); +  case tchkProps:  return arg->isDict() || arg->isName(); +  case tchkSCN:    return arg->isNum() || arg->isName(); +  case tchkNone:   return gFalse; +  } +  return gFalse; +} + +int Gfx::getPos() { +  return parser ? parser->getPos() : -1; +} + +//------------------------------------------------------------------------ +// graphics state operators +//------------------------------------------------------------------------ + +void Gfx::opSave(Object * /*args[]*/, int /*numArgs*/) { +  saveState(); +} + +void Gfx::opRestore(Object * /*args[]*/, int /*numArgs*/) { +  restoreState(); +} + +void Gfx::opConcat(Object args[], int /*numArgs*/) { +  state->concatCTM(args[0].getNum(), args[1].getNum(), +		   args[2].getNum(), args[3].getNum(), +		   args[4].getNum(), args[5].getNum()); +  out->updateCTM(state, args[0].getNum(), args[1].getNum(), +		 args[2].getNum(), args[3].getNum(), +		 args[4].getNum(), args[5].getNum()); +  fontChanged = gTrue; +} + +void Gfx::opSetDash(Object args[], int /*numArgs*/) { +  Array *a; +  int length; +  Object obj; +  double *dash; +  int i; + +  a = args[0].getArray(); +  length = a->getLength(); +  if (length == 0) { +    dash = NULL; +  } else { +    dash = (double *)gmallocn(length, sizeof(double)); +    for (i = 0; i < length; ++i) { +      dash[i] = a->get(i, &obj)->getNum(); +      obj.free(); +    } +  } +  state->setLineDash(dash, length, args[1].getNum()); +  out->updateLineDash(state); +} + +void Gfx::opSetFlat(Object args[], int /*numArgs*/) { +  state->setFlatness((int)args[0].getNum()); +  out->updateFlatness(state); +} + +void Gfx::opSetLineJoin(Object args[], int /*numArgs*/) { +  state->setLineJoin(args[0].getInt()); +  out->updateLineJoin(state); +} + +void Gfx::opSetLineCap(Object args[], int /*numArgs*/) { +  state->setLineCap(args[0].getInt()); +  out->updateLineCap(state); +} + +void Gfx::opSetMiterLimit(Object args[], int /*numArgs*/) { +  state->setMiterLimit(args[0].getNum()); +  out->updateMiterLimit(state); +} + +void Gfx::opSetLineWidth(Object args[], int /*numArgs*/) { +  state->setLineWidth(args[0].getNum()); +  out->updateLineWidth(state); +} + +void Gfx::opSetExtGState(Object args[], int /*numArgs*/) { +  Object obj1, obj2, obj3, obj4, obj5; +  GfxBlendMode mode; +  GBool haveFillOP; +  Function *funcs[4]; +  GfxColor backdropColor; +  GBool haveBackdropColor; +  GfxColorSpace *blendingColorSpace; +  GBool alpha, isolated, knockout; +  int i; + +  if (!res->lookupGState(args[0].getName(), &obj1)) { +    return; +  } +  if (!obj1.isDict()) { +    error(getPos(), "ExtGState '%s' is wrong type", args[0].getName()); +    obj1.free(); +    return; +  } +  if (printCommands) { +    printf("  gfx state dict: "); +    obj1.print(); +    printf("\n"); +  } + +  // transparency support: blend mode, fill/stroke opacity +  if (!obj1.dictLookup("BM", &obj2)->isNull()) { +    if (state->parseBlendMode(&obj2, &mode)) { +      state->setBlendMode(mode); +      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); +  } +  obj2.free(); +  if (obj1.dictLookup("CA", &obj2)->isNum()) { +    state->setStrokeOpacity(obj2.getNum()); +    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(); + +  // stroke adjust +  if (obj1.dictLookup("SA", &obj2)->isBool()) { +    state->setStrokeAdjust(obj2.getBool()); +    out->updateStrokeAdjust(state); +  } +  obj2.free(); + +  // transfer function +  if (obj1.dictLookup("TR2", &obj2)->isNull()) { +    obj2.free(); +    obj1.dictLookup("TR", &obj2); +  } +  if (obj2.isName("Default") || +      obj2.isName("Identity")) { +    funcs[0] = funcs[1] = funcs[2] = funcs[3] = NULL; +    state->setTransfer(funcs); +    out->updateTransfer(state); +  } else if (obj2.isArray() && obj2.arrayGetLength() == 4) { +    for (i = 0; i < 4; ++i) { +      obj2.arrayGet(i, &obj3); +      funcs[i] = Function::parse(&obj3); +      obj3.free(); +      if (!funcs[i]) { +	break; +      } +    } +    if (i == 4) { +      state->setTransfer(funcs); +      out->updateTransfer(state); +    } +  } else if (obj2.isName() || obj2.isDict() || obj2.isStream()) { +    if ((funcs[0] = Function::parse(&obj2))) { +      funcs[1] = funcs[2] = funcs[3] = NULL; +      state->setTransfer(funcs); +      out->updateTransfer(state); +    } +  } else if (!obj2.isNull()) { +    error(getPos(), "Invalid transfer function in ExtGState"); +  } +  obj2.free(); + +  // soft mask +  if (!obj1.dictLookup("SMask", &obj2)->isNull()) { +    if (obj2.isName("None")) { +      out->clearSoftMask(state); +    } else if (obj2.isDict()) { +      if (obj2.dictLookup("S", &obj3)->isName("Alpha")) { +	alpha = gTrue; +      } else { // "Luminosity" +	alpha = gFalse; +      } +      obj3.free(); +      funcs[0] = NULL; +      if (!obj2.dictLookup("TR", &obj3)->isNull()) { +	funcs[0] = Function::parse(&obj3); +	if (funcs[0]->getInputSize() != 1 || +	    funcs[0]->getOutputSize() != 1) { +	  error(getPos(), +		"Invalid transfer function in soft mask in ExtGState"); +	  delete funcs[0]; +	  funcs[0] = NULL; +	} +      } +      obj3.free(); +      if ((haveBackdropColor = obj2.dictLookup("BC", &obj3)->isArray())) { +	for (i = 0; i < gfxColorMaxComps; ++i) { +	  backdropColor.c[i] = 0; +	} +	for (i = 0; i < obj3.arrayGetLength() && i < gfxColorMaxComps; ++i) { +	  obj3.arrayGet(i, &obj4); +	  if (obj4.isNum()) { +	    backdropColor.c[i] = dblToCol(obj4.getNum()); +	  } +	  obj4.free(); +	} +      } +      obj3.free(); +      if (obj2.dictLookup("G", &obj3)->isStream()) { +	if (obj3.streamGetDict()->lookup("Group", &obj4)->isDict()) { +	  blendingColorSpace = NULL; +	  isolated = knockout = gFalse; +	  if (!obj4.dictLookup("CS", &obj5)->isNull()) { +	    blendingColorSpace = GfxColorSpace::parse(&obj5); +	  } +	  obj5.free(); +	  if (obj4.dictLookup("I", &obj5)->isBool()) { +	    isolated = obj5.getBool(); +	  } +	  obj5.free(); +	  if (obj4.dictLookup("K", &obj5)->isBool()) { +	    knockout = obj5.getBool(); +	  } +	  obj5.free(); +	  if (!haveBackdropColor) { +	    if (blendingColorSpace) { +	      blendingColorSpace->getDefaultColor(&backdropColor); +	    } else { +	      //~ need to get the parent or default color space (?) +	      for (i = 0; i < gfxColorMaxComps; ++i) { +		backdropColor.c[i] = 0; +	      } +	    } +	  } +	  doSoftMask(&obj3, alpha, blendingColorSpace, +		     isolated, knockout, funcs[0], &backdropColor); +	  if (funcs[0]) { +	    delete funcs[0]; +	  } +	} else { +	  error(getPos(), "Invalid soft mask in ExtGState - missing group"); +	} +	obj4.free(); +      } else { +	error(getPos(), "Invalid soft mask in ExtGState - missing group"); +      } +      obj3.free(); +    } else if (!obj2.isNull()) { +      error(getPos(), "Invalid soft mask in ExtGState"); +    } +  } +  obj2.free(); + +  obj1.free(); +} + +void Gfx::doSoftMask(Object *str, GBool alpha, +		     GfxColorSpace *blendingColorSpace, +		     GBool isolated, GBool knockout, +		     Function *transferFunc, GfxColor *backdropColor) { +  Dict *dict, *resDict; +  double m[6], bbox[4]; +  Object obj1, obj2; +  int i; + +  // check for excessive recursion +  if (formDepth > 20) { +    return; +  } + +  // get stream dict +  dict = str->streamGetDict(); + +  // check form type +  dict->lookup("FormType", &obj1); +  if (!(obj1.isNull() || (obj1.isInt() && obj1.getInt() == 1))) { +    error(getPos(), "Unknown form type"); +  } +  obj1.free(); + +  // get bounding box +  dict->lookup("BBox", &obj1); +  if (!obj1.isArray()) { +    obj1.free(); +    error(getPos(), "Bad form bounding box"); +    return; +  } +  for (i = 0; i < 4; ++i) { +    obj1.arrayGet(i, &obj2); +    bbox[i] = obj2.getNum(); +    obj2.free(); +  } +  obj1.free(); + +  // get matrix +  dict->lookup("Matrix", &obj1); +  if (obj1.isArray()) { +    for (i = 0; i < 6; ++i) { +      obj1.arrayGet(i, &obj2); +      m[i] = obj2.getNum(); +      obj2.free(); +    } +  } else { +    m[0] = 1; m[1] = 0; +    m[2] = 0; m[3] = 1; +    m[4] = 0; m[5] = 0; +  } +  obj1.free(); + +  // get resources +  dict->lookup("Resources", &obj1); +  resDict = obj1.isDict() ? obj1.getDict() : (Dict *)NULL; + +  // draw it +  ++formDepth; +  doForm1(str, resDict, m, bbox, gTrue, gTrue, +	  blendingColorSpace, isolated, knockout, +	  alpha, transferFunc, backdropColor); +  --formDepth; + +  if (blendingColorSpace) { +    delete blendingColorSpace; +  } +  obj1.free(); +} + +void Gfx::opSetRenderingIntent(Object * /*args[]*/, int /*numArgs*/) { +} + +//------------------------------------------------------------------------ +// color operators +//------------------------------------------------------------------------ + +void Gfx::opSetFillGray(Object args[], int /*numArgs*/) { +  GfxColor color; + +  state->setFillPattern(NULL); +  state->setFillColorSpace(new GfxDeviceGrayColorSpace()); +  out->updateFillColorSpace(state); +  color.c[0] = dblToCol(args[0].getNum()); +  state->setFillColor(&color); +  out->updateFillColor(state); +} + +void Gfx::opSetStrokeGray(Object args[], int /*numArgs*/) { +  GfxColor color; + +  state->setStrokePattern(NULL); +  state->setStrokeColorSpace(new GfxDeviceGrayColorSpace()); +  out->updateStrokeColorSpace(state); +  color.c[0] = dblToCol(args[0].getNum()); +  state->setStrokeColor(&color); +  out->updateStrokeColor(state); +} + +void Gfx::opSetFillCMYKColor(Object args[], int /*numArgs*/) { +  GfxColor color; +  int i; + +  state->setFillPattern(NULL); +  state->setFillColorSpace(new GfxDeviceCMYKColorSpace()); +  out->updateFillColorSpace(state); +  for (i = 0; i < 4; ++i) { +    color.c[i] = dblToCol(args[i].getNum()); +  } +  state->setFillColor(&color); +  out->updateFillColor(state); +} + +void Gfx::opSetStrokeCMYKColor(Object args[], int /*numArgs*/) { +  GfxColor color; +  int i; + +  state->setStrokePattern(NULL); +  state->setStrokeColorSpace(new GfxDeviceCMYKColorSpace()); +  out->updateStrokeColorSpace(state); +  for (i = 0; i < 4; ++i) { +    color.c[i] = dblToCol(args[i].getNum()); +  } +  state->setStrokeColor(&color); +  out->updateStrokeColor(state); +} + +void Gfx::opSetFillRGBColor(Object args[], int /*numArgs*/) { +  GfxColor color; +  int i; + +  state->setFillPattern(NULL); +  state->setFillColorSpace(new GfxDeviceRGBColorSpace()); +  out->updateFillColorSpace(state); +  for (i = 0; i < 3; ++i) { +    color.c[i] = dblToCol(args[i].getNum()); +  } +  state->setFillColor(&color); +  out->updateFillColor(state); +} + +void Gfx::opSetStrokeRGBColor(Object args[], int /*numArgs*/) { +  GfxColor color; +  int i; + +  state->setStrokePattern(NULL); +  state->setStrokeColorSpace(new GfxDeviceRGBColorSpace()); +  out->updateStrokeColorSpace(state); +  for (i = 0; i < 3; ++i) { +    color.c[i] = dblToCol(args[i].getNum()); +  } +  state->setStrokeColor(&color); +  out->updateStrokeColor(state); +} + +void Gfx::opSetFillColorSpace(Object args[], int /*numArgs*/) { +  Object obj; +  GfxColorSpace *colorSpace; +  GfxColor color; + +  state->setFillPattern(NULL); +  res->lookupColorSpace(args[0].getName(), &obj); +  if (obj.isNull()) { +    colorSpace = GfxColorSpace::parse(&args[0]); +  } else { +    colorSpace = GfxColorSpace::parse(&obj); +  } +  obj.free(); +  if (colorSpace) { +    state->setFillColorSpace(colorSpace); +    out->updateFillColorSpace(state); +    colorSpace->getDefaultColor(&color); +    state->setFillColor(&color); +    out->updateFillColor(state); +  } else { +    error(getPos(), "Bad color space (fill)"); +  } +} + +void Gfx::opSetStrokeColorSpace(Object args[], int /*numArgs*/) { +  Object obj; +  GfxColorSpace *colorSpace; +  GfxColor color; + +  state->setStrokePattern(NULL); +  res->lookupColorSpace(args[0].getName(), &obj); +  if (obj.isNull()) { +    colorSpace = GfxColorSpace::parse(&args[0]); +  } else { +    colorSpace = GfxColorSpace::parse(&obj); +  } +  obj.free(); +  if (colorSpace) { +    state->setStrokeColorSpace(colorSpace); +    out->updateStrokeColorSpace(state); +    colorSpace->getDefaultColor(&color); +    state->setStrokeColor(&color); +    out->updateStrokeColor(state); +  } else { +    error(getPos(), "Bad color space (stroke)"); +  } +} + +void Gfx::opSetFillColor(Object args[], int numArgs) { +  GfxColor color; +  int i; + +  if (numArgs != state->getFillColorSpace()->getNComps()) { +    error(getPos(), "Incorrect number of arguments in 'sc' command"); +    return; +  } +  state->setFillPattern(NULL); +  for (i = 0; i < numArgs; ++i) { +    color.c[i] = dblToCol(args[i].getNum()); +  } +  state->setFillColor(&color); +  out->updateFillColor(state); +} + +void Gfx::opSetStrokeColor(Object args[], int numArgs) { +  GfxColor color; +  int i; + +  if (numArgs != state->getStrokeColorSpace()->getNComps()) { +    error(getPos(), "Incorrect number of arguments in 'SC' command"); +    return; +  } +  state->setStrokePattern(NULL); +  for (i = 0; i < numArgs; ++i) { +    color.c[i] = dblToCol(args[i].getNum()); +  } +  state->setStrokeColor(&color); +  out->updateStrokeColor(state); +} + +void Gfx::opSetFillColorN(Object args[], int numArgs) { +  GfxColor color; +  GfxPattern *pattern; +  int i; + +  if (state->getFillColorSpace()->getMode() == csPattern) { +    if (numArgs > 1) { +      if (!((GfxPatternColorSpace *)state->getFillColorSpace())->getUnder() || +	  numArgs - 1 != ((GfxPatternColorSpace *)state->getFillColorSpace()) +	                     ->getUnder()->getNComps()) { +	error(getPos(), "Incorrect number of arguments in 'scn' command"); +	return; +      } +      for (i = 0; i < numArgs - 1 && i < gfxColorMaxComps; ++i) { +	if (args[i].isNum()) { +	  color.c[i] = dblToCol(args[i].getNum()); +	} +      } +      state->setFillColor(&color); +      out->updateFillColor(state); +    } +    if (args[numArgs-1].isName() && +	(pattern = res->lookupPattern(args[numArgs-1].getName()))) { +      state->setFillPattern(pattern); +    } + +  } else { +    if (numArgs != state->getFillColorSpace()->getNComps()) { +      error(getPos(), "Incorrect number of arguments in 'scn' command"); +      return; +    } +    state->setFillPattern(NULL); +    for (i = 0; i < numArgs && i < gfxColorMaxComps; ++i) { +      if (args[i].isNum()) { +	color.c[i] = dblToCol(args[i].getNum()); +      } +    } +    state->setFillColor(&color); +    out->updateFillColor(state); +  } +} + +void Gfx::opSetStrokeColorN(Object args[], int numArgs) { +  GfxColor color; +  GfxPattern *pattern; +  int i; + +  if (state->getStrokeColorSpace()->getMode() == csPattern) { +    if (numArgs > 1) { +      if (!((GfxPatternColorSpace *)state->getStrokeColorSpace()) +	       ->getUnder() || +	  numArgs - 1 != ((GfxPatternColorSpace *)state->getStrokeColorSpace()) +	                     ->getUnder()->getNComps()) { +	error(getPos(), "Incorrect number of arguments in 'SCN' command"); +	return; +      } +      for (i = 0; i < numArgs - 1 && i < gfxColorMaxComps; ++i) { +	if (args[i].isNum()) { +	  color.c[i] = dblToCol(args[i].getNum()); +	} +      } +      state->setStrokeColor(&color); +      out->updateStrokeColor(state); +    } +    if (args[numArgs-1].isName() && +	(pattern = res->lookupPattern(args[numArgs-1].getName()))) { +      state->setStrokePattern(pattern); +    } + +  } else { +    if (numArgs != state->getStrokeColorSpace()->getNComps()) { +      error(getPos(), "Incorrect number of arguments in 'SCN' command"); +      return; +    } +    state->setStrokePattern(NULL); +    for (i = 0; i < numArgs && i < gfxColorMaxComps; ++i) { +      if (args[i].isNum()) { +	color.c[i] = dblToCol(args[i].getNum()); +      } +    } +    state->setStrokeColor(&color); +    out->updateStrokeColor(state); +  } +} + +//------------------------------------------------------------------------ +// path segment operators +//------------------------------------------------------------------------ + +void Gfx::opMoveTo(Object args[], int /*numArgs*/) { +  state->moveTo(args[0].getNum(), args[1].getNum()); +} + +void Gfx::opLineTo(Object args[], int /*numArgs*/) { +  if (!state->isCurPt()) { +    error(getPos(), "No current point in lineto"); +    return; +  } +  state->lineTo(args[0].getNum(), args[1].getNum()); +} + +void Gfx::opCurveTo(Object args[], int /*numArgs*/) { +  double x1, y1, x2, y2, x3, y3; + +  if (!state->isCurPt()) { +    error(getPos(), "No current point in curveto"); +    return; +  } +  x1 = args[0].getNum(); +  y1 = args[1].getNum(); +  x2 = args[2].getNum(); +  y2 = args[3].getNum(); +  x3 = args[4].getNum(); +  y3 = args[5].getNum(); +  state->curveTo(x1, y1, x2, y2, x3, y3); +} + +void Gfx::opCurveTo1(Object args[], int /*numArgs*/) { +  double x1, y1, x2, y2, x3, y3; + +  if (!state->isCurPt()) { +    error(getPos(), "No current point in curveto1"); +    return; +  } +  x1 = state->getCurX(); +  y1 = state->getCurY(); +  x2 = args[0].getNum(); +  y2 = args[1].getNum(); +  x3 = args[2].getNum(); +  y3 = args[3].getNum(); +  state->curveTo(x1, y1, x2, y2, x3, y3); +} + +void Gfx::opCurveTo2(Object args[], int /*numArgs*/) { +  double x1, y1, x2, y2, x3, y3; + +  if (!state->isCurPt()) { +    error(getPos(), "No current point in curveto2"); +    return; +  } +  x1 = args[0].getNum(); +  y1 = args[1].getNum(); +  x2 = args[2].getNum(); +  y2 = args[3].getNum(); +  x3 = x2; +  y3 = y2; +  state->curveTo(x1, y1, x2, y2, x3, y3); +} + +void Gfx::opRectangle(Object args[], int /*numArgs*/) { +  double x, y, w, h; + +  x = args[0].getNum(); +  y = args[1].getNum(); +  w = args[2].getNum(); +  h = args[3].getNum(); +  state->moveTo(x, y); +  state->lineTo(x + w, y); +  state->lineTo(x + w, y + h); +  state->lineTo(x, y + h); +  state->closePath(); +} + +void Gfx::opClosePath(Object * /*args[]*/, int /*numArgs*/) { +  if (!state->isCurPt()) { +    error(getPos(), "No current point in closepath"); +    return; +  } +  state->closePath(); +} + +//------------------------------------------------------------------------ +// path painting operators +//------------------------------------------------------------------------ + +void Gfx::opEndPath(Object * /*args[]*/, int /*numArgs*/) { +  doEndPath(); +} + +void Gfx::opStroke(Object * /*args[]*/, int /*numArgs*/) { +  if (!state->isCurPt()) { +    //error(getPos(), "No path in stroke"); +    return; +  } +  if (state->isPath()) { +    if (state->getStrokeColorSpace()->getMode() == csPattern) { +      doPatternStroke(); +    } else { +      out->stroke(state); +    } +  } +  doEndPath(); +} + +void Gfx::opCloseStroke(Object * /*args[]*/, int /*numArgs*/) { +  if (!state->isCurPt()) { +    //error(getPos(), "No path in closepath/stroke"); +    return; +  } +  if (state->isPath()) { +    state->closePath(); +    if (state->getStrokeColorSpace()->getMode() == csPattern) { +      doPatternStroke(); +    } else { +      out->stroke(state); +    } +  } +  doEndPath(); +} + +void Gfx::opFill(Object * /*args[]*/, int /*numArgs*/) { +  if (!state->isCurPt()) { +    //error(getPos(), "No path in fill"); +    return; +  } +  if (state->isPath()) { +    if (state->getFillColorSpace()->getMode() == csPattern) { +      doPatternFill(gFalse); +    } else { +      out->fill(state); +    } +  } +  doEndPath(); +} + +void Gfx::opEOFill(Object * /*args[]*/, int /*numArgs*/) { +  if (!state->isCurPt()) { +    //error(getPos(), "No path in eofill"); +    return; +  } +  if (state->isPath()) { +    if (state->getFillColorSpace()->getMode() == csPattern) { +      doPatternFill(gTrue); +    } else { +      out->eoFill(state); +    } +  } +  doEndPath(); +} + +void Gfx::opFillStroke(Object * /*args[]*/, int /*numArgs*/) { +  if (!state->isCurPt()) { +    //error(getPos(), "No path in fill/stroke"); +    return; +  } +  if (state->isPath()) { +    if (state->getFillColorSpace()->getMode() == csPattern) { +      doPatternFill(gFalse); +    } else { +      out->fill(state); +    } +    if (state->getStrokeColorSpace()->getMode() == csPattern) { +      doPatternStroke(); +    } else { +      out->stroke(state); +    } +  } +  doEndPath(); +} + +void Gfx::opCloseFillStroke(Object * /*args[]*/, int /*numArgs*/) { +  if (!state->isCurPt()) { +    //error(getPos(), "No path in closepath/fill/stroke"); +    return; +  } +  if (state->isPath()) { +    state->closePath(); +    if (state->getFillColorSpace()->getMode() == csPattern) { +      doPatternFill(gFalse); +    } else { +      out->fill(state); +    } +    if (state->getStrokeColorSpace()->getMode() == csPattern) { +      doPatternStroke(); +    } else { +      out->stroke(state); +    } +  } +  doEndPath(); +} + +void Gfx::opEOFillStroke(Object * /*args[]*/, int /*numArgs*/) { +  if (!state->isCurPt()) { +    //error(getPos(), "No path in eofill/stroke"); +    return; +  } +  if (state->isPath()) { +    if (state->getFillColorSpace()->getMode() == csPattern) { +      doPatternFill(gTrue); +    } else { +      out->eoFill(state); +    } +    if (state->getStrokeColorSpace()->getMode() == csPattern) { +      doPatternStroke(); +    } else { +      out->stroke(state); +    } +  } +  doEndPath(); +} + +void Gfx::opCloseEOFillStroke(Object * /*args[]*/, int /*numArgs*/) { +  if (!state->isCurPt()) { +    //error(getPos(), "No path in closepath/eofill/stroke"); +    return; +  } +  if (state->isPath()) { +    state->closePath(); +    if (state->getFillColorSpace()->getMode() == csPattern) { +      doPatternFill(gTrue); +    } else { +      out->eoFill(state); +    } +    if (state->getStrokeColorSpace()->getMode() == csPattern) { +      doPatternStroke(); +    } else { +      out->stroke(state); +    } +  } +  doEndPath(); +} + +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, gFalse, eoFill); +    break; +  case 2: +    doShadingPatternFill((GfxShadingPattern *)pattern, gFalse, eoFill); +    break; +  default: +    error(getPos(), "Unimplemented pattern type (%d) in fill", +	  pattern->getType()); +    break; +  } +} + +void Gfx::doPatternStroke() { +  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->getStrokePattern())) { +    return; +  } +  switch (pattern->getType()) { +  case 1: +    doTilingPatternFill((GfxTilingPattern *)pattern, gTrue, gFalse); +    break; +  case 2: +    doShadingPatternFill((GfxShadingPattern *)pattern, gTrue, gFalse); +    break; +  default: +    error(getPos(), "Unimplemented pattern type (%d) in stroke", +	  pattern->getType()); +    break; +  } +} + +void Gfx::doTilingPatternFill(GfxTilingPattern *tPat, +			      GBool stroke, GBool eoFill) { +  GfxPatternColorSpace *patCS; +  GfxColorSpace *cs; +  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], imb[6]; +  double det; +  double xstep, ystep; +  int i; + +  // get color space +  patCS = (GfxPatternColorSpace *)(stroke ? state->getStrokeColorSpace() +				          : state->getFillColorSpace()); + +  // construct a (pattern space) -> (current space) transform matrix +  ctm = state->getCTM(); +  btm = baseMatrix; +  ptm = tPat->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]; + +  // 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 +  savedPath = state->getPath()->copy(); +  saveState(); + +  // 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); +  if (!stroke) { +    state->setLineWidth(0); +    out->updateLineWidth(state); +  } + +  // clip to current path +  if (stroke) { +    state->clipToStrokePath(); +    out->clipToStrokePath(state); +  } else { +    state->clip(); +    if (eoFill) { +      out->eoClip(state); +    } else { +      out->clip(state); +    } +  } +  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)ceil((xMin - tPat->getBBox()[2]) / xstep); +  xi1 = (int)floor((xMax - tPat->getBBox()[0]) / xstep) + 1; +  yi0 = (int)ceil((yMin - tPat->getBBox()[3]) / ystep); +  yi1 = (int)floor((yMax - tPat->getBBox()[1]) / ystep) + 1; +  for (i = 0; i < 4; ++i) { +    m1[i] = m[i]; +  } +  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 + err: +  restoreState(); +  state->setPath(savedPath); +} + +void Gfx::doShadingPatternFill(GfxShadingPattern *sPat, +			       GBool stroke, GBool eoFill) { +  GfxShading *shading; +  GfxPath *savedPath; +  double *ctm, *btm, *ptm; +  double m[6], ictm[6], m1[6]; +  double xMin, yMin, xMax, yMax; +  double det; + +  shading = sPat->getShading(); + +  // save current graphics state +  savedPath = state->getPath()->copy(); +  saveState(); + +  // clip to bbox +  if (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()); +  } + +  // clip to current path +  if (stroke) { +    state->clipToStrokePath(); +    out->clipToStrokePath(state); +  } else { +    state->clip(); +    if (eoFill) { +      out->eoClip(state); +    } else { +      out->clip(state); +    } +  } + +  // set the color space +  state->setFillColorSpace(shading->getColorSpace()->copy()); +  out->updateFillColorSpace(state); + +  // background color fill +  if (shading->getHasBackground()) { +    state->setFillColor(shading->getBackground()); +    out->updateFillColor(state); +    out->fill(state); +  } +  state->clearPath(); + +  // construct a (pattern space) -> (current space) transform matrix +  ctm = state->getCTM(); +  btm = baseMatrix; +  ptm = sPat->getMatrix(); +  // iCTM = invert CTM +  det = 1 / (ctm[0] * ctm[3] - ctm[1] * ctm[2]); +  ictm[0] = ctm[3] * det; +  ictm[1] = -ctm[1] * det; +  ictm[2] = -ctm[2] * det; +  ictm[3] = ctm[0] * det; +  ictm[4] = (ctm[2] * ctm[5] - ctm[3] * ctm[4]) * det; +  ictm[5] = (ctm[1] * ctm[4] - ctm[0] * ctm[5]) * det; +  // m1 = PTM * BTM = PTM * base transform matrix +  m1[0] = ptm[0] * btm[0] + ptm[1] * btm[2]; +  m1[1] = ptm[0] * btm[1] + ptm[1] * btm[3]; +  m1[2] = ptm[2] * btm[0] + ptm[3] * btm[2]; +  m1[3] = ptm[2] * btm[1] + ptm[3] * btm[3]; +  m1[4] = ptm[4] * btm[0] + ptm[5] * btm[2] + btm[4]; +  m1[5] = ptm[4] * btm[1] + ptm[5] * btm[3] + btm[5]; +  // m = m1 * iCTM = (PTM * BTM) * (iCTM) +  m[0] = m1[0] * ictm[0] + m1[1] * ictm[2]; +  m[1] = m1[0] * ictm[1] + m1[1] * ictm[3]; +  m[2] = m1[2] * ictm[0] + m1[3] * ictm[2]; +  m[3] = m1[2] * ictm[1] + m1[3] * ictm[3]; +  m[4] = m1[4] * ictm[0] + m1[5] * ictm[2] + ictm[4]; +  m[5] = m1[4] * ictm[1] + m1[5] * ictm[3] + ictm[5]; + +  // set the new matrix +  state->concatCTM(m[0], m[1], m[2], m[3], m[4], m[5]); +  out->updateCTM(state, m[0], m[1], m[2], m[3], m[4], m[5]); + +#if 1 //~tmp: turn off anti-aliasing temporarily +  GBool vaa = out->getVectorAntialias(); +  if (vaa) { +    out->setVectorAntialias(gFalse); +  } +#endif + +  // 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; +  } + +#if 1 //~tmp: turn off anti-aliasing temporarily +  if (vaa) { +    out->setVectorAntialias(gTrue); +  } +#endif + +  // restore graphics state +  restoreState(); +  state->setPath(savedPath); +} + +void Gfx::opShFill(Object args[], int /*numArgs*/) { +  GfxShading *shading; +  GfxPath *savedPath; +  double xMin, yMin, xMax, yMax; + +  if (!(shading = res->lookupShading(args[0].getName()))) { +    return; +  } + +  // save current graphics state +  savedPath = state->getPath()->copy(); +  saveState(); + +  // clip to bbox +  if (shading->getHasBBox()) { +    shading->getBBox(&xMin, &yMin, &xMax, &yMax); +    state->moveTo(xMin, yMin); +    state->lineTo(xMax, yMin); +    state->lineTo(xMax, yMax); +    state->lineTo(xMin, yMax); +    state->closePath(); +    state->clip(); +    out->clip(state); +    state->clearPath(); +  } + +  // set the color space +  state->setFillColorSpace(shading->getColorSpace()->copy()); +  out->updateFillColorSpace(state); + +#if 1 //~tmp: turn off anti-aliasing temporarily +  GBool vaa = out->getVectorAntialias(); +  if (vaa) { +    out->setVectorAntialias(gFalse); +  } +#endif + +  // 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; +  } + +#if 1 //~tmp: turn off anti-aliasing temporarily +  if (vaa) { +    out->setVectorAntialias(gTrue); +  } +#endif + +  // restore graphics state +  restoreState(); +  state->setPath(savedPath); + +  delete shading; +} + +void Gfx::doFunctionShFill(GfxFunctionShading *shading) { +  double x0, y0, x1, y1; +  GfxColor colors[4]; + +  if (out->useShadedFills() && +      out->functionShadedFill(state, shading)) { +    return; +  } + +  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; +    } +  } + +  // center of the rectangle +  xM = 0.5 * (x0 + x1); +  yM = 0.5 * (y0 + y1); + +  // the four corner colors are close (or we hit the recursive limit) +  // -- fill the rectangle; but require at least one subdivision +  // (depth==0) to avoid problems when the four outer corners of the +  // shaded region are the same color +  if ((i == 4 && depth > 0) || depth == functionMaxDepth) { + +    // use the center color +    shading->getColor(xM, yM, &fillColor); +    state->setFillColor(&fillColor); +    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)) { +    return; +  } + +  // 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.01; +  dyZero = fabs(dy) < 0.01; +  if (dxZero && dyZero) { +    tMin = tMax = 0; +  } else { +    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; +  } else 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; +    } else 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 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; +  double sz, xz, yz, sMin, sMax; +  GBool enclosed; +  int ia, ib, k, n; +  double *ctm; +  double theta, alpha, angle, t; + +  if (out->useShadedFills() && +      out->radialShadedFill(state, shading)) { +    return; +  } + +  // get the shading info +  shading->getCoords(&x0, &y0, &r0, &x1, &y1, &r1); +  t0 = shading->getDomain0(); +  t1 = shading->getDomain1(); +  nComps = shading->getColorSpace()->getNComps(); + +  // Compute the point at which r(s) = 0; check for the enclosed +  // circles case; and compute the angles for the tangent lines. +  if (x0 == x1 && y0 == y1) { +    enclosed = gTrue; +    theta = 0; // make gcc happy +    sz = 0; // make gcc happy +  } else if (r0 == r1) { +    enclosed = gFalse; +    theta = 0; +    sz = 0; // make gcc happy +  } else { +    sz = -r0 / (r1 - r0); +    xz = x0 + sz * (x1 - x0); +    yz = y0 + sz * (y1 - y0); +    enclosed = (xz - x0) * (xz - x0) + (yz - y0) * (yz - y0) <= r0 * r0; +    theta = asin(r0 / sqrt((x0 - xz) * (x0 - xz) + (y0 - yz) * (y0 - yz))); +    if (r0 > r1) { +      theta = -theta; +    } +  } +  if (enclosed) { +    alpha = 0; +  } else { +    alpha = atan2(y1 - y0, x1 - x0); +  } + +  // compute the (possibly extended) s range +  state->getUserClipBBox(&xMin, &yMin, &xMax, &yMax); +  if (enclosed) { +    sMin = 0; +    sMax = 1; +  } else { +    sMin = 1; +    sMax = 0; +    // solve for x(s) + r(s) = xMin +    if ((x1 + r1) - (x0 + r0) != 0) { +      sa = (xMin - (x0 + r0)) / ((x1 + r1) - (x0 + r0)); +      if (sa < sMin) { +	sMin = sa; +      } else if (sa > sMax) { +	sMax = sa; +      } +    } +    // solve for x(s) - r(s) = xMax +    if ((x1 - r1) - (x0 - r0) != 0) { +      sa = (xMax - (x0 - r0)) / ((x1 - r1) - (x0 - r0)); +      if (sa < sMin) { +	sMin = sa; +      } else if (sa > sMax) { +	sMax = sa; +      } +    } +    // solve for y(s) + r(s) = yMin +    if ((y1 + r1) - (y0 + r0) != 0) { +      sa = (yMin - (y0 + r0)) / ((y1 + r1) - (y0 + r0)); +      if (sa < sMin) { +	sMin = sa; +      } else if (sa > sMax) { +	sMax = sa; +      } +    } +    // solve for y(s) - r(s) = yMax +    if ((y1 - r1) - (y0 - r0) != 0) { +      sa = (yMax - (y0 - r0)) / ((y1 - r1) - (y0 - r0)); +      if (sa < sMin) { +	sMin = sa; +      } else if (sa > sMax) { +	sMax = sa; +      } +    } +    // check against sz +    if (r0 < r1) { +      if (sMin < sz) { +	sMin = sz; +      } +    } else if (r0 > r1) { +      if (sMax > sz) { +	sMax = sz; +      } +    } +    // check the 'extend' flags +    if (!shading->getExtend0() && sMin < 0) { +      sMin = 0; +    } +    if (!shading->getExtend1() && sMax > 1) { +      sMax = 1; +    } +  } + +  // 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; +    } +  } + +  // 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); +  } + +  // fill the circles +  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 = sMax; +    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); + +    if (enclosed) { + +      // construct path for first circle (counterclockwise) +      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 (clockwise) +      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(); + +    } else { + +      // construct the first subpath (clockwise) +      state->moveTo(xa + ra * cos(alpha + theta + 0.5 * M_PI), +		    ya + ra * sin(alpha + theta + 0.5 * M_PI)); +      for (k = 0; k < n; ++k) { +	angle = alpha + theta + 0.5 * M_PI +	  - ((double)k / (double)n) * (2 * theta + M_PI); +	state->lineTo(xb + rb * cos(angle), yb + rb * sin(angle)); +      } +      for (k = 0; k < n; ++k) { +	angle = alpha - theta - 0.5 * M_PI +	  + ((double)k / (double)n) * (2 * theta - M_PI); +	state->lineTo(xa + ra * cos(angle), ya + ra * sin(angle)); +      } +      state->closePath(); + +      // construct the second subpath (counterclockwise) +      state->moveTo(xa + ra * cos(alpha + theta + 0.5 * M_PI), +		    ya + ra * sin(alpha + theta + 0.5 * M_PI)); +      for (k = 0; k < n; ++k) { +	angle = alpha + theta + 0.5 * M_PI +	        + ((double)k / (double)n) * (-2 * theta + M_PI); +	state->lineTo(xb + rb * cos(angle), yb + rb * sin(angle)); +      } +      for (k = 0; k < n; ++k) { +	angle = alpha - theta - 0.5 * M_PI +	        + ((double)k / (double)n) * (2 * theta + M_PI); +	state->lineTo(xa + ra * cos(angle), ya + ra * sin(angle)); +      } +      state->closePath(); +    } + +    // fill the path +    out->fill(state); +    state->clearPath(); + +    // step to the next value of t +    ia = ib; +    sa = sb; +    ta = tb; +    xa = xb; +    ya = yb; +    ra = rb; +    colorA = colorB; +  } + +  if (enclosed) { +    // extend the smaller circle +    if ((shading->getExtend0() && r0 <= r1) || +	(shading->getExtend1() && r1 < r0)) { +      if (r0 <= r1) { +	ta = t0; +	ra = r0; +	xa = x0; +	ya = y0; +      } else { +	ta = t1; +	ra = r1; +	xa = x1; +	ya = y1; +      } +      shading->getColor(ta, &colorA); +      state->setFillColor(&colorA); +      out->updateFillColor(state); +      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(); +      out->fill(state); +      state->clearPath(); +    } + +    // extend the larger circle +    if ((shading->getExtend0() && r0 > r1) || +	(shading->getExtend1() && r1 >= r0)) { +      if (r0 > r1) { +	ta = t0; +	ra = r0; +	xa = x0; +	ya = y0; +      } else { +	ta = t1; +	ra = r1; +	xa = x1; +	ya = y1; +      } +      shading->getColor(ta, &colorA); +      state->setFillColor(&colorA); +      out->updateFillColor(state); +      state->moveTo(xMin, yMin); +      state->lineTo(xMin, yMax); +      state->lineTo(xMax, yMax); +      state->lineTo(xMax, yMin); +      state->closePath(); +      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(); +      out->fill(state); +      state->clearPath(); +    } +  } +} + +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); +} + +void Gfx::opTextMoveSet(Object args[], int /*numArgs*/) { +  double tx, ty; + +  tx = state->getLineX() + args[0].getNum(); +  ty = args[1].getNum(); +  state->setLeading(-ty); +  ty += state->getLineY(); +  state->textMoveTo(tx, ty); +  out->updateTextPos(state); +} + +void Gfx::opSetTextMatrix(Object args[], int /*numArgs*/) { +  state->setTextMat(args[0].getNum(), args[1].getNum(), +		    args[2].getNum(), args[3].getNum(), +		    args[4].getNum(), args[5].getNum()); +  state->textMoveTo(0, 0); +  out->updateTextMat(state); +  out->updateTextPos(state); +  fontChanged = gTrue; +} + +void Gfx::opTextNextLine(Object * /*args[]*/, int /*numArgs*/) { +  double tx, ty; + +  tx = state->getLineX(); +  ty = state->getLineY() - state->getLeading(); +  state->textMoveTo(tx, ty); +  out->updateTextPos(state); +} + +//------------------------------------------------------------------------ +// text string operators +//------------------------------------------------------------------------ + +void Gfx::opShowText(Object args[], int /*numArgs*/) { +  if (!state->getFont()) { +    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*/) { +  double tx, ty; + +  if (!state->getFont()) { +    error(getPos(), "No font in move/show"); +    return; +  } +  if (fontChanged) { +    out->updateFont(state); +    fontChanged = gFalse; +  } +  tx = state->getLineX(); +  ty = state->getLineY() - state->getLeading(); +  state->textMoveTo(tx, ty); +  out->updateTextPos(state); +  out->beginStringOp(state); +  doShowText(args[0].getString()); +  out->endStringOp(state); +} + +void Gfx::opMoveSetShowText(Object args[], int /*numArgs*/) { +  double tx, ty; + +  if (!state->getFont()) { +    error(getPos(), "No font in move/set/show"); +    return; +  } +  if (fontChanged) { +    out->updateFont(state); +    fontChanged = gFalse; +  } +  state->setWordSpace(args[0].getNum()); +  state->setCharSpace(args[1].getNum()); +  tx = state->getLineX(); +  ty = state->getLineY() - state->getLeading(); +  state->textMoveTo(tx, ty); +  out->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()) { +      // 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()); +    } else { +      error(getPos(), "Element of show/space array must be number or string"); +    } +    obj.free(); +  } +  out->endStringOp(state); +} + +void Gfx::doShowText(GString *s) { +  GfxFont *font; +  int wMode; +  double riseX, riseY; +  CharCode code; +  Unicode u[8]; +  double x, y, dx, dy, dx2, dy2, curX, curY, tdx, tdy, lineX, lineY; +  double originX, originY, tOriginX, tOriginY; +  double oldCTM[6], newCTM[6]; +  double *mat; +  Object charProc; +  Dict *resDict; +  Parser *oldParser; +  char *p; +  int len, n, uLen, nChars, nSpaces, i; + +  font = state->getFont(); +  wMode = font->getWMode(); + +  if (out->useDrawChar()) { +    out->beginString(state, s); +  } + +  // 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(); +      } +      dx *= state->getHorizScaling(); +      dy *= state->getFontSize(); +      state->textTransformDelta(dx, dy, &tdx, &tdy); +      state->transform(curX + riseX, curY + riseY, &x, &y); +      saveState(); +      state->setCTM(newCTM[0], newCTM[1], newCTM[2], newCTM[3], x, y); +      //~ the CTM concat values here are wrong (but never used) +      out->updateCTM(state, 1, 0, 0, 1, 0, 0); +      if (!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"); +	} +	out->endType3Char(state); +	if (resDict) { +	  popResources(); +	} +	charProc.free(); +      } +      restoreState(); +      // GfxState::restore() does *not* restore the current position, +      // so we deal with it here using (curX, curY) and (lineX, lineY) +      curX += tdx; +      curY += tdy; +      state->moveTo(curX, curY); +      state->textSetPos(lineX, lineY); +      p += n; +      len -= n; +    } +    parser = oldParser; + +  } else if (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(); +	} +      } else { +	dx = dx * state->getFontSize() + state->getCharSpace(); +	if (n == 1 && *p == ' ') { +	  dx += state->getWordSpace(); +	} +	dx *= state->getHorizScaling(); +	dy *= state->getFontSize(); +      } +      state->textTransformDelta(dx, dy, &tdx, &tdy); +      originX *= state->getFontSize(); +      originY *= state->getFontSize(); +      state->textTransformDelta(originX, originY, &tOriginX, &tOriginY); +      out->drawChar(state, state->getCurX() + riseX, state->getCurY() + riseY, +		    tdx, tdy, tOriginX, tOriginY, code, n, u, uLen); +      state->shift(tdx, tdy); +      p += n; +      len -= n; +    } + +  } else { +    dx = dy = 0; +    p = s->getCString(); +    len = s->getLength(); +    nChars = nSpaces = 0; +    while (len > 0) { +      n = font->getNextChar(p, len, &code, +			    u, (int)(sizeof(u) / sizeof(Unicode)), &uLen, +			    &dx2, &dy2, &originX, &originY); +      dx += dx2; +      dy += dy2; +      if (n == 1 && *p == ' ') { +	++nSpaces; +      } +      ++nChars; +      p += n; +      len -= n; +    } +    if (wMode) { +      dx *= state->getFontSize(); +      dy = dy * state->getFontSize() +	   + nChars * state->getCharSpace() +	   + nSpaces * state->getWordSpace(); +    } else { +      dx = dx * state->getFontSize() +	   + nChars * state->getCharSpace() +	   + nSpaces * state->getWordSpace(); +      dx *= state->getHorizScaling(); +      dy *= state->getFontSize(); +    } +    state->textTransformDelta(dx, dy, &tdx, &tdy); +    out->drawString(state, s); +    state->shift(tdx, tdy); +  } + +  if (out->useDrawChar()) { +    out->endString(state); +  } + +  updateLevel += 10 * s->getLength(); +} + +//------------------------------------------------------------------------ +// XObject operators +//------------------------------------------------------------------------ + +void Gfx::opXObject(Object args[], int /*numArgs*/) { +  char *name; +  Object obj1, obj2, obj3, refObj; +#if OPI_SUPPORT +  Object opiDict; +#endif + +  name = args[0].getName(); +  if (!res->lookupXObject(name, &obj1)) { +    return; +  } +  if (!obj1.isStream()) { +    error(getPos(), "XObject '%s' is wrong type", name); +    obj1.free(); +    return; +  } +#if OPI_SUPPORT +  obj1.streamGetDict()->lookup("OPI", &opiDict); +  if (opiDict.isDict()) { +    out->opiBegin(state, opiDict.getDict()); +  } +#endif +  obj1.streamGetDict()->lookup("Subtype", &obj2); +  if (obj2.isName("Image")) { +    if (out->needNonText()) { +      res->lookupXObjectNF(name, &refObj); +      doImage(&refObj, obj1.getStream(), gFalse); +      refObj.free(); +    } +  } else if (obj2.isName("Form")) { +    res->lookupXObjectNF(name, &refObj); +    if (out->useDrawForm() && refObj.isRef()) { +      out->drawForm(refObj.getRef()); +    } else { +      doForm(&obj1); +    } +    refObj.free(); +  } else if (obj2.isName("PS")) { +    obj1.streamGetDict()->lookup("Level1", &obj3); +    out->psXObject(obj1.getStream(), +		   obj3.isStream() ? obj3.getStream() : (Stream *)NULL); +  } else if (obj2.isName()) { +    error(getPos(), "Unknown XObject subtype '%s'", obj2.getName()); +  } else { +    error(getPos(), "XObject subtype is missing or wrong type"); +  } +  obj2.free(); +#if OPI_SUPPORT +  if (opiDict.isDict()) { +    out->opiEnd(state, opiDict.getDict()); +  } +  opiDict.free(); +#endif +  obj1.free(); +} + +void Gfx::doImage(Object *ref, Stream *str, GBool inlineImg) { +  Dict *dict, *maskDict; +  int width, height; +  int bits, maskBits; +  StreamColorSpaceMode csMode; +  GBool mask; +  GBool invert; +  GfxColorSpace *colorSpace, *maskColorSpace; +  GfxImageColorMap *colorMap, *maskColorMap; +  Object maskObj, smaskObj; +  GBool haveColorKeyMask, haveExplicitMask, haveSoftMask; +  int maskColors[2*gfxColorMaxComps]; +  int maskWidth, maskHeight; +  GBool maskInvert; +  Stream *maskStr; +  Object obj1, obj2; +  int i; + +  // get info from the stream +  bits = 0; +  csMode = streamCSNone; +  str->getImageParams(&bits, &csMode); + +  // get stream dict +  dict = str->getDict(); + +  // get size +  dict->lookup("Width", &obj1); +  if (obj1.isNull()) { +    obj1.free(); +    dict->lookup("W", &obj1); +  } +  if (obj1.isInt()) +    width = obj1.getInt(); +  else if (obj1.isReal()) +    width = (int)obj1.getReal(); +  else +    goto err2; +  obj1.free(); +  dict->lookup("Height", &obj1); +  if (obj1.isNull()) { +    obj1.free(); +    dict->lookup("H", &obj1); +  } +  if (obj1.isInt()) +    height = obj1.getInt(); +  else if (obj1.isReal()) +    height = (int)obj1.getReal(); +  else +    goto err2; +  obj1.free(); + +  // image or mask? +  dict->lookup("ImageMask", &obj1); +  if (obj1.isNull()) { +    obj1.free(); +    dict->lookup("IM", &obj1); +  } +  mask = gFalse; +  if (obj1.isBool()) +    mask = obj1.getBool(); +  else if (!obj1.isNull()) +    goto err2; +  obj1.free(); + +  // bit depth +  if (bits == 0) { +    dict->lookup("BitsPerComponent", &obj1); +    if (obj1.isNull()) { +      obj1.free(); +      dict->lookup("BPC", &obj1); +    } +    if (obj1.isInt()) { +      bits = obj1.getInt(); +    } else if (mask) { +      bits = 1; +    } else { +      goto err2; +    } +    obj1.free(); +  } + +  // display a mask +  if (mask) { + +    // check for inverted mask +    if (bits != 1) +      goto err1; +    invert = gFalse; +    dict->lookup("Decode", &obj1); +    if (obj1.isNull()) { +      obj1.free(); +      dict->lookup("D", &obj1); +    } +    if (obj1.isArray()) { +      obj1.arrayGet(0, &obj2); +      if (obj2.isInt() && obj2.getInt() == 1) +	invert = gTrue; +      obj2.free(); +    } else if (!obj1.isNull()) { +      goto err2; +    } +    obj1.free(); + +    // draw it +    out->drawImageMask(state, ref, str, width, height, invert, inlineImg); + +  } else { + +    // get color space and color map +    dict->lookup("ColorSpace", &obj1); +    if (obj1.isNull()) { +      obj1.free(); +      dict->lookup("CS", &obj1); +    } +    if (obj1.isName()) { +      res->lookupColorSpace(obj1.getName(), &obj2); +      if (!obj2.isNull()) { +	obj1.free(); +	obj1 = obj2; +      } else { +	obj2.free(); +      } +    } +    if (!obj1.isNull()) { +      colorSpace = GfxColorSpace::parse(&obj1); +    } else if (csMode == streamCSDeviceGray) { +      colorSpace = new GfxDeviceGrayColorSpace(); +    } else if (csMode == streamCSDeviceRGB) { +      colorSpace = new GfxDeviceRGBColorSpace(); +    } else if (csMode == streamCSDeviceCMYK) { +      colorSpace = new GfxDeviceCMYKColorSpace(); +    } else { +      colorSpace = NULL; +    } +    obj1.free(); +    if (!colorSpace) { +      goto err1; +    } +    dict->lookup("Decode", &obj1); +    if (obj1.isNull()) { +      obj1.free(); +      dict->lookup("D", &obj1); +    } +    colorMap = new GfxImageColorMap(bits, &obj1, colorSpace); +    obj1.free(); +    if (!colorMap->isOk()) { +      delete colorMap; +      goto err1; +    } + +    // get the mask +    haveColorKeyMask = haveExplicitMask = haveSoftMask = gFalse; +    maskStr = NULL; // make gcc happy +    maskWidth = maskHeight = 0; // make gcc happy +    maskInvert = gFalse; // make gcc happy +    maskColorMap = NULL; // make gcc happy +    dict->lookup("Mask", &maskObj); +    dict->lookup("SMask", &smaskObj); +    if (smaskObj.isStream()) { +      // soft mask +      if (inlineImg) { +	goto err1; +      } +      maskStr = smaskObj.getStream(); +      maskDict = smaskObj.streamGetDict(); +      maskDict->lookup("Width", &obj1); +      if (obj1.isNull()) { +	obj1.free(); +	maskDict->lookup("W", &obj1); +      } +      if (!obj1.isInt()) { +	goto err2; +      } +      maskWidth = obj1.getInt(); +      obj1.free(); +      maskDict->lookup("Height", &obj1); +      if (obj1.isNull()) { +	obj1.free(); +	maskDict->lookup("H", &obj1); +      } +      if (!obj1.isInt()) { +	goto err2; +      } +      maskHeight = obj1.getInt(); +      obj1.free(); +      maskDict->lookup("BitsPerComponent", &obj1); +      if (obj1.isNull()) { +	obj1.free(); +	maskDict->lookup("BPC", &obj1); +      } +      if (!obj1.isInt()) { +	goto err2; +      } +      maskBits = obj1.getInt(); +      obj1.free(); +      maskDict->lookup("ColorSpace", &obj1); +      if (obj1.isNull()) { +	obj1.free(); +	maskDict->lookup("CS", &obj1); +      } +      if (obj1.isName()) { +	res->lookupColorSpace(obj1.getName(), &obj2); +	if (!obj2.isNull()) { +	  obj1.free(); +	  obj1 = obj2; +	} else { +	  obj2.free(); +	} +      } +      maskColorSpace = GfxColorSpace::parse(&obj1); +      obj1.free(); +      if (!maskColorSpace || maskColorSpace->getMode() != csDeviceGray) { +	goto err1; +      } +      maskDict->lookup("Decode", &obj1); +      if (obj1.isNull()) { +	obj1.free(); +	maskDict->lookup("D", &obj1); +      } +      maskColorMap = new GfxImageColorMap(maskBits, &obj1, maskColorSpace); +      obj1.free(); +      if (!maskColorMap->isOk()) { +	delete maskColorMap; +	goto err1; +      } +      //~ handle the Matte entry +      haveSoftMask = gTrue; +    } else if (maskObj.isArray()) { +      // color key mask +      for (i = 0; +	   i < maskObj.arrayGetLength() && i < 2*gfxColorMaxComps; +	   ++i) { +	maskObj.arrayGet(i, &obj1); +	maskColors[i] = obj1.getInt(); +	obj1.free(); +      } +      haveColorKeyMask = gTrue; +    } else if (maskObj.isStream()) { +      // explicit mask +      if (inlineImg) { +	goto err1; +      } +      maskStr = maskObj.getStream(); +      maskDict = maskObj.streamGetDict(); +      maskDict->lookup("Width", &obj1); +      if (obj1.isNull()) { +	obj1.free(); +	maskDict->lookup("W", &obj1); +      } +      if (!obj1.isInt()) { +	goto err2; +      } +      maskWidth = obj1.getInt(); +      obj1.free(); +      maskDict->lookup("Height", &obj1); +      if (obj1.isNull()) { +	obj1.free(); +	maskDict->lookup("H", &obj1); +      } +      if (!obj1.isInt()) { +	goto err2; +      } +      maskHeight = obj1.getInt(); +      obj1.free(); +      maskDict->lookup("ImageMask", &obj1); +      if (obj1.isNull()) { +	obj1.free(); +	maskDict->lookup("IM", &obj1); +      } +      if (!obj1.isBool() || !obj1.getBool()) { +	goto err2; +      } +      obj1.free(); +      maskInvert = gFalse; +      maskDict->lookup("Decode", &obj1); +      if (obj1.isNull()) { +	obj1.free(); +	maskDict->lookup("D", &obj1); +      } +      if (obj1.isArray()) { +	obj1.arrayGet(0, &obj2); +	if (obj2.isInt() && obj2.getInt() == 1) { +	  maskInvert = gTrue; +	} +	obj2.free(); +      } else if (!obj1.isNull()) { +	goto err2; +      } +      obj1.free(); +      haveExplicitMask = gTrue; +    } + +    // draw it +    if (haveSoftMask) { +      out->drawSoftMaskedImage(state, ref, str, width, height, colorMap, +			       maskStr, maskWidth, maskHeight, maskColorMap); +      delete maskColorMap; +    } else if (haveExplicitMask) { +      out->drawMaskedImage(state, ref, str, width, height, colorMap, +			   maskStr, maskWidth, maskHeight, maskInvert); +    } else { +      out->drawImage(state, ref, str, width, height, colorMap, +		     haveColorKeyMask ? maskColors : (int *)NULL, inlineImg); +    } +    delete colorMap; + +    maskObj.free(); +    smaskObj.free(); +  } + +  if ((i = width * height) > 1000) { +    i = 1000; +  } +  updateLevel += i; + +  return; + + err2: +  obj1.free(); + err1: +  error(getPos(), "Bad image parameters"); +} + +void Gfx::doForm(Object *str) { +  Dict *dict; +  GBool transpGroup, isolated, knockout; +  GfxColorSpace *blendingColorSpace; +  Object matrixObj, bboxObj; +  double m[6], bbox[4]; +  Object resObj; +  Dict *resDict; +  Object obj1, obj2, obj3; +  int i; + +  // check for excessive recursion +  if (formDepth > 20) { +    return; +  } + +  // get stream dict +  dict = str->streamGetDict(); + +  // check form type +  dict->lookup("FormType", &obj1); +  if (!(obj1.isNull() || (obj1.isInt() && obj1.getInt() == 1))) { +    error(getPos(), "Unknown form type"); +  } +  obj1.free(); + +  // get bounding box +  dict->lookup("BBox", &bboxObj); +  if (!bboxObj.isArray()) { +    bboxObj.free(); +    error(getPos(), "Bad form bounding box"); +    return; +  } +  for (i = 0; i < 4; ++i) { +    bboxObj.arrayGet(i, &obj1); +    bbox[i] = obj1.getNum(); +    obj1.free(); +  } +  bboxObj.free(); + +  // get matrix +  dict->lookup("Matrix", &matrixObj); +  if (matrixObj.isArray()) { +    for (i = 0; i < 6; ++i) { +      matrixObj.arrayGet(i, &obj1); +      m[i] = obj1.getNum(); +      obj1.free(); +    } +  } else { +    m[0] = 1; m[1] = 0; +    m[2] = 0; m[3] = 1; +    m[4] = 0; m[5] = 0; +  } +  matrixObj.free(); + +  // get resources +  dict->lookup("Resources", &resObj); +  resDict = resObj.isDict() ? resObj.getDict() : (Dict *)NULL; + +  // check for a transparency group +  transpGroup = isolated = knockout = gFalse; +  blendingColorSpace = NULL; +  if (dict->lookup("Group", &obj1)->isDict()) { +    if (obj1.dictLookup("S", &obj2)->isName("Transparency")) { +      transpGroup = gTrue; +      if (!obj1.dictLookup("CS", &obj3)->isNull()) { +	blendingColorSpace = GfxColorSpace::parse(&obj3); +      } +      obj3.free(); +      if (obj1.dictLookup("I", &obj3)->isBool()) { +	isolated = obj3.getBool(); +      } +      obj3.free(); +      if (obj1.dictLookup("K", &obj3)->isBool()) { +	knockout = obj3.getBool(); +      } +      obj3.free(); +    } +    obj2.free(); +  } +  obj1.free(); + +  // draw it +  ++formDepth; +  doForm1(str, resDict, m, bbox, +	  transpGroup, gFalse, blendingColorSpace, isolated, knockout); +  --formDepth; + +  if (blendingColorSpace) { +    delete blendingColorSpace; +  } +  resObj.free(); +} + +void Gfx::doForm1(Object *str, Dict *resDict, double *matrix, double *bbox, +		  GBool transpGroup, GBool softMask, +		  GfxColorSpace *blendingColorSpace, +		  GBool isolated, GBool knockout, +		  GBool alpha, Function *transferFunc, +		  GfxColor *backdropColor) { +  Parser *oldParser; +  double oldBaseMatrix[6]; +  int i; + +  // push new resources on stack +  pushResources(resDict); + +  // save current graphics state +  saveState(); + +  // kill any pre-existing path +  state->clearPath(); + +  // save current parser +  oldParser = parser; + +  // set form transformation matrix +  state->concatCTM(matrix[0], matrix[1], matrix[2], +		   matrix[3], matrix[4], matrix[5]); +  out->updateCTM(state, matrix[0], matrix[1], matrix[2], +		 matrix[3], matrix[4], matrix[5]); + +  // set form bounding box +  state->moveTo(bbox[0], bbox[1]); +  state->lineTo(bbox[2], bbox[1]); +  state->lineTo(bbox[2], bbox[3]); +  state->lineTo(bbox[0], bbox[3]); +  state->closePath(); +  state->clip(); +  out->clip(state); +  state->clearPath(); + +  if (softMask || transpGroup) { +    if (state->getBlendMode() != gfxBlendNormal) { +      state->setBlendMode(gfxBlendNormal); +      out->updateBlendMode(state); +    } +    if (state->getFillOpacity() != 1) { +      state->setFillOpacity(1); +      out->updateFillOpacity(state); +    } +    if (state->getStrokeOpacity() != 1) { +      state->setStrokeOpacity(1); +      out->updateStrokeOpacity(state); +    } +    out->clearSoftMask(state); +    out->beginTransparencyGroup(state, bbox, blendingColorSpace, +				isolated, knockout, softMask); +  } + +  // set new base matrix +  for (i = 0; i < 6; ++i) { +    oldBaseMatrix[i] = baseMatrix[i]; +    baseMatrix[i] = state->getCTM()[i]; +  } + +  // draw the form +  display(str, gFalse); + +  if (softMask || transpGroup) { +    out->endTransparencyGroup(state); +  } + +  // restore base matrix +  for (i = 0; i < 6; ++i) { +    baseMatrix[i] = oldBaseMatrix[i]; +  } + +  // restore parser +  parser = oldParser; + +  // restore graphics state +  restoreState(); + +  // pop resource stack +  popResources(); + +  if (softMask) { +    out->setSoftMask(state, bbox, alpha, transferFunc, backdropColor); +  } else if (transpGroup) { +    out->paintTransparencyGroup(state, bbox); +  } + +  return; +} + +//------------------------------------------------------------------------ +// in-line image operators +//------------------------------------------------------------------------ + +void Gfx::opBeginImage(Object * /*args[]*/, int /*numArgs*/) { +  Stream *str; +  int c1, c2; + +  // build dict/stream +  str = buildImageStream(); + +  // display the image +  if (str) { +    doImage(NULL, str, gTrue); +   +    // skip 'EI' tag +    c1 = str->getUndecodedStream()->getChar(); +    c2 = str->getUndecodedStream()->getChar(); +    while (!(c1 == 'E' && c2 == 'I') && c2 != EOF) { +      c1 = c2; +      c2 = str->getUndecodedStream()->getChar(); +    } +    delete str; +  } +} + +Stream *Gfx::buildImageStream() { +  Object dict; +  Object obj; +  char *key; +  Stream *str; + +  // build dictionary +  dict.initDict(xref); +  parser->getObj(&obj); +  while (!obj.isCmd("ID") && !obj.isEOF()) { +    if (!obj.isName()) { +      error(getPos(), "Inline image dictionary key must be a name object"); +      obj.free(); +    } else { +      key = copyString(obj.getName()); +      obj.free(); +      parser->getObj(&obj); +      if (obj.isEOF() || obj.isError()) { +	gfree(key); +	break; +      } +      dict.dictAdd(key, &obj); +    } +    parser->getObj(&obj); +  } +  if (obj.isEOF()) { +    error(getPos(), "End of file in inline image"); +    obj.free(); +    dict.free(); +    return NULL; +  } +  obj.free(); + +  // make stream +  str = new EmbedStream(parser->getStream(), &dict, gFalse, 0); +  str = str->addFilters(&dict); + +  return str; +} + +void Gfx::opImageData(Object * /*args[]*/, int /*numArgs*/) { +  error(getPos(), "Internal: got 'ID' operator"); +} + +void Gfx::opEndImage(Object * /*args[]*/, int /*numArgs*/) { +  error(getPos(), "Internal: got 'EI' operator"); +} + +//------------------------------------------------------------------------ +// type 3 font operators +//------------------------------------------------------------------------ + +void Gfx::opSetCharWidth(Object args[], int /*numArgs*/) { +  out->type3D0(state, args[0].getNum(), args[1].getNum()); +} + +void Gfx::opSetCacheDevice(Object args[], int /*numArgs*/) { +  out->type3D1(state, args[0].getNum(), args[1].getNum(), +	       args[2].getNum(), args[3].getNum(), +	       args[4].getNum(), args[5].getNum()); +} + +//------------------------------------------------------------------------ +// compatibility operators +//------------------------------------------------------------------------ + +void Gfx::opBeginIgnoreUndef(Object * /*args[]*/, int /*numArgs*/) { +  ++ignoreUndef; +} + +void Gfx::opEndIgnoreUndef(Object * /*args[]*/, int /*numArgs*/) { +  if (ignoreUndef > 0) +    --ignoreUndef; +} + +//------------------------------------------------------------------------ +// marked content operators +//------------------------------------------------------------------------ + +void Gfx::opBeginMarkedContent(Object args[], int numArgs) { +  if (printCommands) { +    printf("  marked content: %s ", args[0].getName()); +    if (numArgs == 2) +      args[2].print(stdout); +    printf("\n"); +    fflush(stdout); +  } +} + +void Gfx::opEndMarkedContent(Object * /*args[]*/, int /*numArgs*/) { +} + +void Gfx::opMarkPoint(Object args[], int numArgs) { +  if (printCommands) { +    printf("  mark point: %s ", args[0].getName()); +    if (numArgs == 2) +      args[2].print(stdout); +    printf("\n"); +    fflush(stdout); +  } +} + +//------------------------------------------------------------------------ +// misc +//------------------------------------------------------------------------ + +void Gfx::drawAnnot(Object *str, AnnotBorderStyle *borderStyle, +		    double xMin, double yMin, double xMax, double yMax) { +  Dict *dict, *resDict; +  Object matrixObj, bboxObj, resObj; +  Object obj1; +  double m[6], bbox[4], ictm[6]; +  double *ctm; +  double formX0, formY0, formX1, formY1; +  double annotX0, annotY0, annotX1, annotY1; +  double det, x, y, sx, sy; +  double r, g, b; +  GfxColor color; +  double *dash, *dash2; +  int dashLength; +  int i; + +  //~ can we assume that we're in default user space? +  //~ (i.e., baseMatrix = ctm) + +  // 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]; +  if (annotX0 > annotX1) { +    x = annotX0; annotX0 = annotX1; annotX1 = x; +  } +  if (annotY0 > annotY1) { +    y = annotY0; annotY0 = annotY1; annotY1 = y; +  } + +  // draw the appearance stream (if there is one) +  if (str->isStream()) { + +    // get stream dict +    dict = str->streamGetDict(); + +    // get the form bounding box +    dict->lookup("BBox", &bboxObj); +    if (!bboxObj.isArray()) { +      bboxObj.free(); +      error(getPos(), "Bad form bounding box"); +      return; +    } +    for (i = 0; i < 4; ++i) { +      bboxObj.arrayGet(i, &obj1); +      bbox[i] = obj1.getNum(); +      obj1.free(); +    } +    bboxObj.free(); + +    // get the form matrix +    dict->lookup("Matrix", &matrixObj); +    if (matrixObj.isArray()) { +      for (i = 0; i < 6; ++i) { +	matrixObj.arrayGet(i, &obj1); +	m[i] = obj1.getNum(); +	obj1.free(); +      } +    } else { +      m[0] = 1; m[1] = 0; +      m[2] = 0; m[3] = 1; +      m[4] = 0; m[5] = 0; +    } +    matrixObj.free(); + +    // 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]; +    if (formX0 > formX1) { +      x = formX0; formX0 = formX1; formX1 = x; +    } +    if (formY0 > formY1) { +      y = formY0; formY0 = formY1; formY1 = y; +    } + +    // 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() : (Dict *)NULL; + +    // draw it +    doForm1(str, resDict, m, bbox); + +    resObj.free(); +  } + +  // draw the border +  if (borderStyle && borderStyle->getWidth() > 0) { +    if (state->getStrokeColorSpace()->getMode() != csDeviceRGB) { +      state->setStrokePattern(NULL); +      state->setStrokeColorSpace(new GfxDeviceRGBColorSpace()); +      out->updateStrokeColorSpace(state); +    } +    borderStyle->getColor(&r, &g, &b); +    color.c[0] = dblToCol(r); +    color.c[1] = dblToCol(g); +    color.c[2] = dblToCol(b); +    state->setStrokeColor(&color); +    out->updateStrokeColor(state); +    // compute the width scale factor when going from default user +    // space to user space +    x = (baseMatrix[0] + baseMatrix[2]) * ictm[0] + +        (baseMatrix[1] + baseMatrix[3]) * ictm[2]; +    y = (baseMatrix[0] + baseMatrix[2]) * ictm[1] + +        (baseMatrix[1] + baseMatrix[3]) * ictm[3]; +    x = sqrt(0.5 * (x * x + y * y)); +    state->setLineWidth(x * borderStyle->getWidth()); +    out->updateLineWidth(state); +    borderStyle->getDash(&dash, &dashLength); +    if (borderStyle->getType() == annotBorderDashed && dashLength > 0) { +      dash2 = (double *)gmallocn(dashLength, sizeof(double)); +      for (i = 0; i < dashLength; ++i) { +	dash2[i] = x * dash[i]; +      } +      state->setLineDash(dash2, dashLength, 0); +      out->updateLineDash(state); +    } +    //~ this doesn't currently handle the beveled and engraved styles +    state->clearPath(); +    state->moveTo(annotX0, out->upsideDown() ? annotY0 : annotY1); +    state->lineTo(annotX1, out->upsideDown() ? annotY0 : annotY1); +    if (borderStyle->getType() != annotBorderUnderlined) { +      state->lineTo(annotX1, out->upsideDown() ? annotY1 : annotY0); +      state->lineTo(annotX0, out->upsideDown() ? annotY1 : annotY0); +      state->closePath(); +    } +    out->stroke(state); +  } +} + +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; +} | 
