Main Page | Namespace List | Class Hierarchy | Alphabetical List | Class List | Directories | File List | Namespace Members | Class Members | File Members | Related Pages

Gfx.cc

Go to the documentation of this file.
00001 //========================================================================
00002 //
00003 // Gfx.cc
00004 //
00005 // Copyright 1996-2002 Glyph & Cog, LLC
00006 //
00007 //========================================================================
00008 
00009 #ifdef __GNUC__
00010 #pragma implementation
00011 #endif
00012 
00013 #include <aconf.h>
00014 #include <stdio.h>
00015 #include <stddef.h>
00016 #include <string.h>
00017 #include <math.h>
00018 #include "gmem.h"
00019 #include "CharTypes.h"
00020 #include "Object.h"
00021 #include "Array.h"
00022 #include "Dict.h"
00023 #include "Stream.h"
00024 #include "Lexer.h"
00025 #include "Parser.h"
00026 #include "GfxFont.h"
00027 #include "GfxState.h"
00028 #include "OutputDev.h"
00029 #include "Page.h"
00030 #include "Error.h"
00031 #include "Gfx.h"
00032 
00033 // the MSVC math.h doesn't define this
00034 #ifndef M_PI
00035 #define M_PI 3.14159265358979323846
00036 #endif
00037 
00038 //------------------------------------------------------------------------
00039 // constants
00040 //------------------------------------------------------------------------
00041 
00042 // Max number of splits along the t axis for an axial shading fill.
00043 #define axialMaxSplits 256
00044 
00045 // Max delta allowed in any color component for an axial shading fill.
00046 #define axialColorDelta (1 / 256.0)
00047 
00048 // Max number of splits along the t axis for a radial shading fill.
00049 #define radialMaxSplits 256
00050 
00051 // Max delta allowed in any color component for a radial shading fill.
00052 #define radialColorDelta (1 / 256.0)
00053 
00054 //------------------------------------------------------------------------
00055 // Operator table
00056 //------------------------------------------------------------------------
00057 
00058 Operator Gfx::opTab[] = {
00059   {"\"",  3, {tchkNum,    tchkNum,    tchkString},
00060           &Gfx::opMoveSetShowText},
00061   {"'",   1, {tchkString},
00062           &Gfx::opMoveShowText},
00063   {"B",   0, {tchkNone},
00064           &Gfx::opFillStroke},
00065   {"B*",  0, {tchkNone},
00066           &Gfx::opEOFillStroke},
00067   {"BDC", 2, {tchkName,   tchkProps},
00068           &Gfx::opBeginMarkedContent},
00069   {"BI",  0, {tchkNone},
00070           &Gfx::opBeginImage},
00071   {"BMC", 1, {tchkName},
00072           &Gfx::opBeginMarkedContent},
00073   {"BT",  0, {tchkNone},
00074           &Gfx::opBeginText},
00075   {"BX",  0, {tchkNone},
00076           &Gfx::opBeginIgnoreUndef},
00077   {"CS",  1, {tchkName},
00078           &Gfx::opSetStrokeColorSpace},
00079   {"DP",  2, {tchkName,   tchkProps},
00080           &Gfx::opMarkPoint},
00081   {"Do",  1, {tchkName},
00082           &Gfx::opXObject},
00083   {"EI",  0, {tchkNone},
00084           &Gfx::opEndImage},
00085   {"EMC", 0, {tchkNone},
00086           &Gfx::opEndMarkedContent},
00087   {"ET",  0, {tchkNone},
00088           &Gfx::opEndText},
00089   {"EX",  0, {tchkNone},
00090           &Gfx::opEndIgnoreUndef},
00091   {"F",   0, {tchkNone},
00092           &Gfx::opFill},
00093   {"G",   1, {tchkNum},
00094           &Gfx::opSetStrokeGray},
00095   {"ID",  0, {tchkNone},
00096           &Gfx::opImageData},
00097   {"J",   1, {tchkInt},
00098           &Gfx::opSetLineCap},
00099   {"K",   4, {tchkNum,    tchkNum,    tchkNum,    tchkNum},
00100           &Gfx::opSetStrokeCMYKColor},
00101   {"M",   1, {tchkNum},
00102           &Gfx::opSetMiterLimit},
00103   {"MP",  1, {tchkName},
00104           &Gfx::opMarkPoint},
00105   {"Q",   0, {tchkNone},
00106           &Gfx::opRestore},
00107   {"RG",  3, {tchkNum,    tchkNum,    tchkNum},
00108           &Gfx::opSetStrokeRGBColor},
00109   {"S",   0, {tchkNone},
00110           &Gfx::opStroke},
00111   {"SC",  -4, {tchkNum,   tchkNum,    tchkNum,    tchkNum},
00112           &Gfx::opSetStrokeColor},
00113   {"SCN", -5, {tchkSCN,   tchkSCN,    tchkSCN,    tchkSCN,
00114                tchkSCN},
00115           &Gfx::opSetStrokeColorN},
00116   {"T*",  0, {tchkNone},
00117           &Gfx::opTextNextLine},
00118   {"TD",  2, {tchkNum,    tchkNum},
00119           &Gfx::opTextMoveSet},
00120   {"TJ",  1, {tchkArray},
00121           &Gfx::opShowSpaceText},
00122   {"TL",  1, {tchkNum},
00123           &Gfx::opSetTextLeading},
00124   {"Tc",  1, {tchkNum},
00125           &Gfx::opSetCharSpacing},
00126   {"Td",  2, {tchkNum,    tchkNum},
00127           &Gfx::opTextMove},
00128   {"Tf",  2, {tchkName,   tchkNum},
00129           &Gfx::opSetFont},
00130   {"Tj",  1, {tchkString},
00131           &Gfx::opShowText},
00132   {"Tm",  6, {tchkNum,    tchkNum,    tchkNum,    tchkNum,
00133               tchkNum,    tchkNum},
00134           &Gfx::opSetTextMatrix},
00135   {"Tr",  1, {tchkInt},
00136           &Gfx::opSetTextRender},
00137   {"Ts",  1, {tchkNum},
00138           &Gfx::opSetTextRise},
00139   {"Tw",  1, {tchkNum},
00140           &Gfx::opSetWordSpacing},
00141   {"Tz",  1, {tchkNum},
00142           &Gfx::opSetHorizScaling},
00143   {"W",   0, {tchkNone},
00144           &Gfx::opClip},
00145   {"W*",  0, {tchkNone},
00146           &Gfx::opEOClip},
00147   {"b",   0, {tchkNone},
00148           &Gfx::opCloseFillStroke},
00149   {"b*",  0, {tchkNone},
00150           &Gfx::opCloseEOFillStroke},
00151   {"c",   6, {tchkNum,    tchkNum,    tchkNum,    tchkNum,
00152               tchkNum,    tchkNum},
00153           &Gfx::opCurveTo},
00154   {"cm",  6, {tchkNum,    tchkNum,    tchkNum,    tchkNum,
00155               tchkNum,    tchkNum},
00156           &Gfx::opConcat},
00157   {"cs",  1, {tchkName},
00158           &Gfx::opSetFillColorSpace},
00159   {"d",   2, {tchkArray,  tchkNum},
00160           &Gfx::opSetDash},
00161   {"d0",  2, {tchkNum,    tchkNum},
00162           &Gfx::opSetCharWidth},
00163   {"d1",  6, {tchkNum,    tchkNum,    tchkNum,    tchkNum,
00164               tchkNum,    tchkNum},
00165           &Gfx::opSetCacheDevice},
00166   {"f",   0, {tchkNone},
00167           &Gfx::opFill},
00168   {"f*",  0, {tchkNone},
00169           &Gfx::opEOFill},
00170   {"g",   1, {tchkNum},
00171           &Gfx::opSetFillGray},
00172   {"gs",  1, {tchkName},
00173           &Gfx::opSetExtGState},
00174   {"h",   0, {tchkNone},
00175           &Gfx::opClosePath},
00176   {"i",   1, {tchkNum},
00177           &Gfx::opSetFlat},
00178   {"j",   1, {tchkInt},
00179           &Gfx::opSetLineJoin},
00180   {"k",   4, {tchkNum,    tchkNum,    tchkNum,    tchkNum},
00181           &Gfx::opSetFillCMYKColor},
00182   {"l",   2, {tchkNum,    tchkNum},
00183           &Gfx::opLineTo},
00184   {"m",   2, {tchkNum,    tchkNum},
00185           &Gfx::opMoveTo},
00186   {"n",   0, {tchkNone},
00187           &Gfx::opEndPath},
00188   {"q",   0, {tchkNone},
00189           &Gfx::opSave},
00190   {"re",  4, {tchkNum,    tchkNum,    tchkNum,    tchkNum},
00191           &Gfx::opRectangle},
00192   {"rg",  3, {tchkNum,    tchkNum,    tchkNum},
00193           &Gfx::opSetFillRGBColor},
00194   {"ri",  1, {tchkName},
00195           &Gfx::opSetRenderingIntent},
00196   {"s",   0, {tchkNone},
00197           &Gfx::opCloseStroke},
00198   {"sc",  -4, {tchkNum,   tchkNum,    tchkNum,    tchkNum},
00199           &Gfx::opSetFillColor},
00200   {"scn", -5, {tchkSCN,   tchkSCN,    tchkSCN,    tchkSCN,
00201                tchkSCN},
00202           &Gfx::opSetFillColorN},
00203   {"sh",  1, {tchkName},
00204           &Gfx::opShFill},
00205   {"v",   4, {tchkNum,    tchkNum,    tchkNum,    tchkNum},
00206           &Gfx::opCurveTo1},
00207   {"w",   1, {tchkNum},
00208           &Gfx::opSetLineWidth},
00209   {"y",   4, {tchkNum,    tchkNum,    tchkNum,    tchkNum},
00210           &Gfx::opCurveTo2},
00211 };
00212 
00213 #define numOps (sizeof(opTab) / sizeof(Operator))
00214 
00215 //------------------------------------------------------------------------
00216 // GfxResources
00217 //------------------------------------------------------------------------
00218 
00219 GfxResources::GfxResources(XRef *xref, Dict *resDict, GfxResources *nextA) {
00220   Object obj1;
00221 
00222   if (resDict) {
00223 
00224     // build font dictionary
00225     fonts = NULL;
00226     resDict->lookup("Font", &obj1);
00227     if (obj1.isDict()) {
00228       fonts = new GfxFontDict(xref, obj1.getDict());
00229     }
00230     obj1.free();
00231 
00232     // get XObject dictionary
00233     resDict->lookup("XObject", &xObjDict);
00234 
00235     // get color space dictionary
00236     resDict->lookup("ColorSpace", &colorSpaceDict);
00237 
00238     // get pattern dictionary
00239     resDict->lookup("Pattern", &patternDict);
00240 
00241     // get shading dictionary
00242     resDict->lookup("Shading", &shadingDict);
00243 
00244     // get graphics state parameter dictionary
00245     resDict->lookup("ExtGState", &gStateDict);
00246 
00247   } else {
00248     fonts = NULL;
00249     xObjDict.initNull();
00250     colorSpaceDict.initNull();
00251     patternDict.initNull();
00252     gStateDict.initNull();
00253   }
00254 
00255   next = nextA;
00256 }
00257 
00258 GfxResources::~GfxResources() {
00259   if (fonts) {
00260     delete fonts;
00261   }
00262   xObjDict.free();
00263   colorSpaceDict.free();
00264   patternDict.free();
00265   shadingDict.free();
00266   gStateDict.free();
00267 }
00268 
00269 GfxFont *GfxResources::lookupFont(char *name) {
00270   GfxFont *font;
00271   GfxResources *resPtr;
00272 
00273   for (resPtr = this; resPtr; resPtr = resPtr->next) {
00274     if (resPtr->fonts) {
00275       if ((font = resPtr->fonts->lookup(name)))
00276         return font;
00277     }
00278   }
00279   error(-1, "Unknown font tag '%s'", name);
00280   return NULL;
00281 }
00282 
00283 GBool GfxResources::lookupXObject(char *name, Object *obj) {
00284   GfxResources *resPtr;
00285 
00286   for (resPtr = this; resPtr; resPtr = resPtr->next) {
00287     if (resPtr->xObjDict.isDict()) {
00288       if (!resPtr->xObjDict.dictLookup(name, obj)->isNull())
00289         return gTrue;
00290       obj->free();
00291     }
00292   }
00293   error(-1, "XObject '%s' is unknown", name);
00294   return gFalse;
00295 }
00296 
00297 GBool GfxResources::lookupXObjectNF(char *name, Object *obj) {
00298   GfxResources *resPtr;
00299 
00300   for (resPtr = this; resPtr; resPtr = resPtr->next) {
00301     if (resPtr->xObjDict.isDict()) {
00302       if (!resPtr->xObjDict.dictLookupNF(name, obj)->isNull())
00303         return gTrue;
00304       obj->free();
00305     }
00306   }
00307   error(-1, "XObject '%s' is unknown", name);
00308   return gFalse;
00309 }
00310 
00311 void GfxResources::lookupColorSpace(char *name, Object *obj) {
00312   GfxResources *resPtr;
00313 
00314   for (resPtr = this; resPtr; resPtr = resPtr->next) {
00315     if (resPtr->colorSpaceDict.isDict()) {
00316       if (!resPtr->colorSpaceDict.dictLookup(name, obj)->isNull()) {
00317         return;
00318       }
00319       obj->free();
00320     }
00321   }
00322   obj->initNull();
00323 }
00324 
00325 GfxPattern *GfxResources::lookupPattern(char *name) {
00326   GfxResources *resPtr;
00327   GfxPattern *pattern;
00328   Object obj;
00329 
00330   for (resPtr = this; resPtr; resPtr = resPtr->next) {
00331     if (resPtr->patternDict.isDict()) {
00332       if (!resPtr->patternDict.dictLookup(name, &obj)->isNull()) {
00333         pattern = GfxPattern::parse(&obj);
00334         obj.free();
00335         return pattern;
00336       }
00337       obj.free();
00338     }
00339   }
00340   error(-1, "Unknown pattern '%s'", name);
00341   return NULL;
00342 }
00343 
00344 GfxShading *GfxResources::lookupShading(char *name) {
00345   GfxResources *resPtr;
00346   GfxShading *shading;
00347   Object obj;
00348 
00349   for (resPtr = this; resPtr; resPtr = resPtr->next) {
00350     if (resPtr->shadingDict.isDict()) {
00351       if (!resPtr->shadingDict.dictLookup(name, &obj)->isNull()) {
00352         shading = GfxShading::parse(&obj);
00353         obj.free();
00354         return shading;
00355       }
00356       obj.free();
00357     }
00358   }
00359   error(-1, "Unknown shading '%s'", name);
00360   return NULL;
00361 }
00362 
00363 GBool GfxResources::lookupGState(char *name, Object *obj) {
00364   GfxResources *resPtr;
00365 
00366   for (resPtr = this; resPtr; resPtr = resPtr->next) {
00367     if (resPtr->gStateDict.isDict()) {
00368       if (!resPtr->gStateDict.dictLookup(name, obj)->isNull()) {
00369         return gTrue;
00370       }
00371       obj->free();
00372     }
00373   }
00374   error(-1, "ExtGState '%s' is unknown", name);
00375   return gFalse;
00376 }
00377 
00378 //------------------------------------------------------------------------
00379 // Gfx
00380 //------------------------------------------------------------------------
00381 
00382 Gfx::Gfx(XRef *xrefA, OutputDev *outA, int pageNum, Dict *resDict, fouble dpi,
00383          PDFRectangle *box, GBool crop, PDFRectangle *cropBox, int rotate,
00384          GBool printCommandsA) {
00385   int i;
00386 
00387   xref = xrefA;
00388   subPage = gFalse;
00389   printCommands = printCommandsA;
00390 
00391   // start the resource stack
00392   res = new GfxResources(xref, resDict, NULL);
00393 
00394   // initialize
00395   out = outA;
00396   state = new GfxState(dpi, box, rotate, out->upsideDown());
00397   fontChanged = gFalse;
00398   clip = clipNone;
00399   ignoreUndef = 0;
00400   out->startPage(pageNum, state);
00401   out->setDefaultCTM(state->getCTM());
00402   out->updateAll(state);
00403   for (i = 0; i < 6; ++i) {
00404     baseMatrix[i] = state->getCTM()[i];
00405   }
00406 
00407   // set crop box
00408   if (crop) {
00409     state->moveTo(cropBox->x1, cropBox->y1);
00410     state->lineTo(cropBox->x2, cropBox->y1);
00411     state->lineTo(cropBox->x2, cropBox->y2);
00412     state->lineTo(cropBox->x1, cropBox->y2);
00413     state->closePath();
00414     state->clip();
00415     out->clip(state);
00416     state->clearPath();
00417   }
00418 }
00419 
00420 Gfx::Gfx(XRef *xrefA, OutputDev *outA, Dict *resDict,
00421          PDFRectangle *box, GBool crop, PDFRectangle *cropBox) {
00422   int i;
00423 
00424   xref = xrefA;
00425   subPage = gTrue;
00426   printCommands = gFalse;
00427 
00428   // start the resource stack
00429   res = new GfxResources(xref, resDict, NULL);
00430 
00431   // initialize
00432   out = outA;
00433   state = new GfxState(72, box, 0, gFalse);
00434   fontChanged = gFalse;
00435   clip = clipNone;
00436   ignoreUndef = 0;
00437   for (i = 0; i < 6; ++i) {
00438     baseMatrix[i] = state->getCTM()[i];
00439   }
00440 
00441   // set crop box
00442   if (crop) {
00443     state->moveTo(cropBox->x1, cropBox->y1);
00444     state->lineTo(cropBox->x2, cropBox->y1);
00445     state->lineTo(cropBox->x2, cropBox->y2);
00446     state->lineTo(cropBox->x1, cropBox->y2);
00447     state->closePath();
00448     state->clip();
00449     out->clip(state);
00450     state->clearPath();
00451   }
00452 }
00453 
00454 Gfx::~Gfx() {
00455   while (state->hasSaves()) {
00456     state = state->restore();
00457     out->restoreState(state);
00458   }
00459   if (!subPage) {
00460     out->endPage();
00461   }
00462   while (res) {
00463     popResources();
00464   }
00465   if (state) {
00466     delete state;
00467   }
00468 }
00469 
00470 void Gfx::display(Object *obj, GBool topLevel) {
00471   Object obj2;
00472   int i;
00473 
00474   if (obj->isArray()) {
00475     for (i = 0; i < obj->arrayGetLength(); ++i) {
00476       obj->arrayGet(i, &obj2);
00477       if (!obj2.isStream()) {
00478         error(-1, "Weird page contents");
00479         obj2.free();
00480         return;
00481       }
00482       obj2.free();
00483     }
00484   } else if (!obj->isStream()) {
00485     error(-1, "Weird page contents");
00486     return;
00487   }
00488   parser = new Parser(xref, new Lexer(xref, obj));
00489   go(topLevel);
00490   delete parser;
00491   parser = NULL;
00492 }
00493 
00494 void Gfx::go(GBool topLevel) {
00495   Object obj;
00496   Object args[maxArgs];
00497   int numArgs;
00498   int i;
00499 
00500   // scan a sequence of objects
00501   updateLevel = 0;
00502   numArgs = 0;
00503   parser->getObj(&obj);
00504   while (!obj.isEOF()) {
00505 
00506     // got a command - execute it
00507     if (obj.isCmd()) {
00508       if (printCommands) {
00509         obj.print(stdout);
00510         for (i = 0; i < numArgs; ++i) {
00511           printf(" ");
00512           args[i].print(stdout);
00513         }
00514         printf("\n");
00515         fflush(stdout);
00516       }
00517       execOp(&obj, args, numArgs);
00518       obj.free();
00519       for (i = 0; i < numArgs; ++i)
00520         args[i].free();
00521       numArgs = 0;
00522 
00523       // periodically update display
00524       if (++updateLevel >= 20000) {
00525         out->dump();
00526         updateLevel = 0;
00527       }
00528 
00529     // got an argument - save it
00530     } else if (numArgs < maxArgs) {
00531       args[numArgs++] = obj;
00532 
00533     // too many arguments - something is wrong
00534     } else {
00535       error(getPos(), "Too many args in content stream");
00536       if (printCommands) {
00537         printf("throwing away arg: ");
00538         obj.print(stdout);
00539         printf("\n");
00540         fflush(stdout);
00541       }
00542       obj.free();
00543     }
00544 
00545     // grab the next object
00546     parser->getObj(&obj);
00547   }
00548   obj.free();
00549 
00550   // args at end with no command
00551   if (numArgs > 0) {
00552     error(getPos(), "Leftover args in content stream");
00553     if (printCommands) {
00554       printf("%d leftovers:", numArgs);
00555       for (i = 0; i < numArgs; ++i) {
00556         printf(" ");
00557         args[i].print(stdout);
00558       }
00559       printf("\n");
00560       fflush(stdout);
00561     }
00562     for (i = 0; i < numArgs; ++i)
00563       args[i].free();
00564   }
00565 
00566   // update display
00567   if (topLevel && updateLevel > 0) {
00568     out->dump();
00569   }
00570 }
00571 
00572 void Gfx::execOp(Object *cmd, Object args[], int numArgs) {
00573   Operator *op;
00574   char *name;
00575   int i;
00576 
00577   // find operator
00578   name = cmd->getName();
00579   if (!(op = findOp(name))) {
00580     if (ignoreUndef == 0)
00581       error(getPos(), "Unknown operator '%s'", name);
00582     return;
00583   }
00584 
00585   // type check args
00586   if (op->numArgs >= 0) {
00587     if (numArgs != op->numArgs) {
00588       error(getPos(), "Wrong number (%d) of args to '%s' operator",
00589             numArgs, name);
00590       return;
00591     }
00592   } else {
00593     if (numArgs > -op->numArgs) {
00594       error(getPos(), "Too many (%d) args to '%s' operator",
00595             numArgs, name);
00596       return;
00597     }
00598   }
00599   for (i = 0; i < numArgs; ++i) {
00600     if (!checkArg(&args[i], op->tchk[i])) {
00601       error(getPos(), "Arg #%d to '%s' operator is wrong type (%s)",
00602             i, name, args[i].getTypeName());
00603       return;
00604     }
00605   }
00606 
00607   // do it
00608   (this->*op->func)(args, numArgs);
00609 }
00610 
00611 Operator *Gfx::findOp(char *name) {
00612   int a, b, m, cmp;
00613 
00614   a = -1;
00615   b = numOps;
00616   // invariant: opTab[a] < name < opTab[b]
00617   while (b - a > 1) {
00618     m = (a + b) / 2;
00619     cmp = strcmp(opTab[m].name, name);
00620     if (cmp < 0)
00621       a = m;
00622     else if (cmp > 0)
00623       b = m;
00624     else
00625       a = b = m;
00626   }
00627   if (cmp != 0)
00628     return NULL;
00629   return &opTab[a];
00630 }
00631 
00632 GBool Gfx::checkArg(Object *arg, TchkType type) {
00633   switch (type) {
00634   case tchkBool:   return arg->isBool();
00635   case tchkInt:    return arg->isInt();
00636   case tchkNum:    return arg->isNum();
00637   case tchkString: return arg->isString();
00638   case tchkName:   return arg->isName();
00639   case tchkArray:  return arg->isArray();
00640   case tchkProps:  return arg->isDict() || arg->isName();
00641   case tchkSCN:    return arg->isNum() || arg->isName();
00642   case tchkNone:   return gFalse;
00643   }
00644   return gFalse;
00645 }
00646 
00647 int Gfx::getPos() {
00648   return parser ? parser->getPos() : -1;
00649 }
00650 
00651 //------------------------------------------------------------------------
00652 // graphics state operators
00653 //------------------------------------------------------------------------
00654 
00655 void Gfx::opSave(Object args[], int numArgs) {
00656   out->saveState(state);
00657   state = state->save();
00658 }
00659 
00660 void Gfx::opRestore(Object args[], int numArgs) {
00661   state = state->restore();
00662   out->restoreState(state);
00663 }
00664 
00665 void Gfx::opConcat(Object args[], int numArgs) {
00666   state->concatCTM(args[0].getNum(), args[1].getNum(),
00667                    args[2].getNum(), args[3].getNum(),
00668                    args[4].getNum(), args[5].getNum());
00669   out->updateCTM(state, args[0].getNum(), args[1].getNum(),
00670                  args[2].getNum(), args[3].getNum(),
00671                  args[4].getNum(), args[5].getNum());
00672   fontChanged = gTrue;
00673 }
00674 
00675 void Gfx::opSetDash(Object args[], int numArgs) {
00676   Array *a;
00677   int length;
00678   Object obj;
00679   fouble *dash;
00680   int i;
00681 
00682   a = args[0].getArray();
00683   length = a->getLength();
00684   if (length == 0) {
00685     dash = NULL;
00686   } else {
00687     dash = (fouble *)gmalloc(length * sizeof(fouble));
00688     for (i = 0; i < length; ++i) {
00689       dash[i] = a->get(i, &obj)->getNum();
00690       obj.free();
00691     }
00692   }
00693   state->setLineDash(dash, length, args[1].getNum());
00694   out->updateLineDash(state);
00695 }
00696 
00697 void Gfx::opSetFlat(Object args[], int numArgs) {
00698   state->setFlatness((int)args[0].getNum());
00699   out->updateFlatness(state);
00700 }
00701 
00702 void Gfx::opSetLineJoin(Object args[], int numArgs) {
00703   state->setLineJoin(args[0].getInt());
00704   out->updateLineJoin(state);
00705 }
00706 
00707 void Gfx::opSetLineCap(Object args[], int numArgs) {
00708   state->setLineCap(args[0].getInt());
00709   out->updateLineCap(state);
00710 }
00711 
00712 void Gfx::opSetMiterLimit(Object args[], int numArgs) {
00713   state->setMiterLimit(args[0].getNum());
00714   out->updateMiterLimit(state);
00715 }
00716 
00717 void Gfx::opSetLineWidth(Object args[], int numArgs) {
00718   state->setLineWidth(args[0].getNum());
00719   out->updateLineWidth(state);
00720 }
00721 
00722 void Gfx::opSetExtGState(Object args[], int numArgs) {
00723   Object obj1, obj2;
00724 
00725   if (!res->lookupGState(args[0].getName(), &obj1)) {
00726     return;
00727   }
00728   if (!obj1.isDict()) {
00729     error(getPos(), "ExtGState '%s' is wrong type", args[0].getName());
00730     obj1.free();
00731     return;
00732   }
00733   if (obj1.dictLookup("ca", &obj2)->isNum()) {
00734     state->setFillOpacity(obj2.getNum());
00735     out->updateFillOpacity(state);
00736   }
00737   obj2.free();
00738   if (obj1.dictLookup("CA", &obj2)->isNum()) {
00739     state->setStrokeOpacity(obj2.getNum());
00740     out->updateStrokeOpacity(state);
00741   }
00742   obj2.free();
00743   obj1.free();
00744 }
00745 
00746 void Gfx::opSetRenderingIntent(Object args[], int numArgs) {
00747 }
00748 
00749 //------------------------------------------------------------------------
00750 // color operators
00751 //------------------------------------------------------------------------
00752 
00753 void Gfx::opSetFillGray(Object args[], int numArgs) {
00754   GfxColor color;
00755 
00756   state->setFillPattern(NULL);
00757   state->setFillColorSpace(new GfxDeviceGrayColorSpace());
00758   color.c[0] = args[0].getNum();
00759   state->setFillColor(&color);
00760   out->updateFillColor(state);
00761 }
00762 
00763 void Gfx::opSetStrokeGray(Object args[], int numArgs) {
00764   GfxColor color;
00765 
00766   state->setStrokePattern(NULL);
00767   state->setStrokeColorSpace(new GfxDeviceGrayColorSpace());
00768   color.c[0] = args[0].getNum();
00769   state->setStrokeColor(&color);
00770   out->updateStrokeColor(state);
00771 }
00772 
00773 void Gfx::opSetFillCMYKColor(Object args[], int numArgs) {
00774   GfxColor color;
00775   int i;
00776 
00777   state->setFillPattern(NULL);
00778   state->setFillColorSpace(new GfxDeviceCMYKColorSpace());
00779   for (i = 0; i < 4; ++i) {
00780     color.c[i] = args[i].getNum();
00781   }
00782   state->setFillColor(&color);
00783   out->updateFillColor(state);
00784 }
00785 
00786 void Gfx::opSetStrokeCMYKColor(Object args[], int numArgs) {
00787   GfxColor color;
00788   int i;
00789 
00790   state->setStrokePattern(NULL);
00791   state->setStrokeColorSpace(new GfxDeviceCMYKColorSpace());
00792   for (i = 0; i < 4; ++i) {
00793     color.c[i] = args[i].getNum();
00794   }
00795   state->setStrokeColor(&color);
00796   out->updateStrokeColor(state);
00797 }
00798 
00799 void Gfx::opSetFillRGBColor(Object args[], int numArgs) {
00800   GfxColor color;
00801   int i;
00802 
00803   state->setFillPattern(NULL);
00804   state->setFillColorSpace(new GfxDeviceRGBColorSpace());
00805   for (i = 0; i < 3; ++i) {
00806     color.c[i] = args[i].getNum();
00807   }
00808   state->setFillColor(&color);
00809   out->updateFillColor(state);
00810 }
00811 
00812 void Gfx::opSetStrokeRGBColor(Object args[], int numArgs) {
00813   GfxColor color;
00814   int i;
00815 
00816   state->setStrokePattern(NULL);
00817   state->setStrokeColorSpace(new GfxDeviceRGBColorSpace());
00818   for (i = 0; i < 3; ++i) {
00819     color.c[i] = args[i].getNum();
00820   }
00821   state->setStrokeColor(&color);
00822   out->updateStrokeColor(state);
00823 }
00824 
00825 void Gfx::opSetFillColorSpace(Object args[], int numArgs) {
00826   Object obj;
00827   GfxColorSpace *colorSpace;
00828   GfxColor color;
00829   int i;
00830 
00831   state->setFillPattern(NULL);
00832   res->lookupColorSpace(args[0].getName(), &obj);
00833   if (obj.isNull()) {
00834     colorSpace = GfxColorSpace::parse(&args[0]);
00835   } else {
00836     colorSpace = GfxColorSpace::parse(&obj);
00837   }
00838   obj.free();
00839   if (colorSpace) {
00840     state->setFillColorSpace(colorSpace);
00841   } else {
00842     error(getPos(), "Bad color space (fill)");
00843   }
00844   for (i = 0; i < gfxColorMaxComps; ++i) {
00845     color.c[i] = 0;
00846   }
00847   state->setFillColor(&color);
00848   out->updateFillColor(state);
00849 }
00850 
00851 void Gfx::opSetStrokeColorSpace(Object args[], int numArgs) {
00852   Object obj;
00853   GfxColorSpace *colorSpace;
00854   GfxColor color;
00855   int i;
00856 
00857   state->setStrokePattern(NULL);
00858   res->lookupColorSpace(args[0].getName(), &obj);
00859   if (obj.isNull()) {
00860     colorSpace = GfxColorSpace::parse(&args[0]);
00861   } else {
00862     colorSpace = GfxColorSpace::parse(&obj);
00863   }
00864   obj.free();
00865   if (colorSpace) {
00866     state->setStrokeColorSpace(colorSpace);
00867   } else {
00868     error(getPos(), "Bad color space (stroke)");
00869   }
00870   for (i = 0; i < gfxColorMaxComps; ++i) {
00871     color.c[i] = 0;
00872   }
00873   state->setStrokeColor(&color);
00874   out->updateStrokeColor(state);
00875 }
00876 
00877 void Gfx::opSetFillColor(Object args[], int numArgs) {
00878   GfxColor color;
00879   int i;
00880 
00881   state->setFillPattern(NULL);
00882   for (i = 0; i < numArgs; ++i) {
00883     color.c[i] = args[i].getNum();
00884   }
00885   state->setFillColor(&color);
00886   out->updateFillColor(state);
00887 }
00888 
00889 void Gfx::opSetStrokeColor(Object args[], int numArgs) {
00890   GfxColor color;
00891   int i;
00892 
00893   state->setStrokePattern(NULL);
00894   for (i = 0; i < numArgs; ++i) {
00895     color.c[i] = args[i].getNum();
00896   }
00897   state->setStrokeColor(&color);
00898   out->updateStrokeColor(state);
00899 }
00900 
00901 void Gfx::opSetFillColorN(Object args[], int numArgs) {
00902   GfxColor color;
00903   GfxPattern *pattern;
00904   int i;
00905 
00906   if (state->getFillColorSpace()->getMode() == csPattern) {
00907     if (numArgs > 1) {
00908       for (i = 0; i < numArgs && i < 4; ++i) {
00909         if (args[i].isNum()) {
00910           color.c[i] = args[i].getNum();
00911         }
00912       }
00913       state->setFillColor(&color);
00914       out->updateFillColor(state);
00915     }
00916     if (args[numArgs-1].isName() &&
00917         (pattern = res->lookupPattern(args[numArgs-1].getName()))) {
00918       state->setFillPattern(pattern);
00919     }
00920 
00921   } else {
00922     state->setFillPattern(NULL);
00923     for (i = 0; i < numArgs && i < 4; ++i) {
00924       if (args[i].isNum()) {
00925         color.c[i] = args[i].getNum();
00926       }
00927     }
00928     state->setFillColor(&color);
00929     out->updateFillColor(state);
00930   }
00931 }
00932 
00933 void Gfx::opSetStrokeColorN(Object args[], int numArgs) {
00934   GfxColor color;
00935   GfxPattern *pattern;
00936   int i;
00937 
00938   if (state->getStrokeColorSpace()->getMode() == csPattern) {
00939     if (numArgs > 1) {
00940       for (i = 0; i < numArgs && i < 4; ++i) {
00941         if (args[i].isNum()) {
00942           color.c[i] = args[i].getNum();
00943         }
00944       }
00945       state->setStrokeColor(&color);
00946       out->updateStrokeColor(state);
00947     }
00948     if (args[numArgs-1].isName() &&
00949         (pattern = res->lookupPattern(args[numArgs-1].getName()))) {
00950       state->setStrokePattern(pattern);
00951     }
00952 
00953   } else {
00954     state->setStrokePattern(NULL);
00955     for (i = 0; i < numArgs && i < 4; ++i) {
00956       if (args[i].isNum()) {
00957         color.c[i] = args[i].getNum();
00958       }
00959     }
00960     state->setStrokeColor(&color);
00961     out->updateStrokeColor(state);
00962   }
00963 }
00964 
00965 //------------------------------------------------------------------------
00966 // path segment operators
00967 //------------------------------------------------------------------------
00968 
00969 void Gfx::opMoveTo(Object args[], int numArgs) {
00970   state->moveTo(args[0].getNum(), args[1].getNum());
00971 }
00972 
00973 void Gfx::opLineTo(Object args[], int numArgs) {
00974   if (!state->isCurPt()) {
00975     error(getPos(), "No current point in lineto");
00976     return;
00977   }
00978   state->lineTo(args[0].getNum(), args[1].getNum());
00979 }
00980 
00981 void Gfx::opCurveTo(Object args[], int numArgs) {
00982   fouble x1, y1, x2, y2, x3, y3;
00983 
00984   if (!state->isCurPt()) {
00985     error(getPos(), "No current point in curveto");
00986     return;
00987   }
00988   x1 = args[0].getNum();
00989   y1 = args[1].getNum();
00990   x2 = args[2].getNum();
00991   y2 = args[3].getNum();
00992   x3 = args[4].getNum();
00993   y3 = args[5].getNum();
00994   state->curveTo(x1, y1, x2, y2, x3, y3);
00995 }
00996 
00997 void Gfx::opCurveTo1(Object args[], int numArgs) {
00998   fouble x1, y1, x2, y2, x3, y3;
00999 
01000   if (!state->isCurPt()) {
01001     error(getPos(), "No current point in curveto1");
01002     return;
01003   }
01004   x1 = state->getCurX();
01005   y1 = state->getCurY();
01006   x2 = args[0].getNum();
01007   y2 = args[1].getNum();
01008   x3 = args[2].getNum();
01009   y3 = args[3].getNum();
01010   state->curveTo(x1, y1, x2, y2, x3, y3);
01011 }
01012 
01013 void Gfx::opCurveTo2(Object args[], int numArgs) {
01014   fouble x1, y1, x2, y2, x3, y3;
01015 
01016   if (!state->isCurPt()) {
01017     error(getPos(), "No current point in curveto2");
01018     return;
01019   }
01020   x1 = args[0].getNum();
01021   y1 = args[1].getNum();
01022   x2 = args[2].getNum();
01023   y2 = args[3].getNum();
01024   x3 = x2;
01025   y3 = y2;
01026   state->curveTo(x1, y1, x2, y2, x3, y3);
01027 }
01028 
01029 void Gfx::opRectangle(Object args[], int numArgs) {
01030   fouble x, y, w, h;
01031 
01032   x = args[0].getNum();
01033   y = args[1].getNum();
01034   w = args[2].getNum();
01035   h = args[3].getNum();
01036   state->moveTo(x, y);
01037   state->lineTo(x + w, y);
01038   state->lineTo(x + w, y + h);
01039   state->lineTo(x, y + h);
01040   state->closePath();
01041 }
01042 
01043 void Gfx::opClosePath(Object args[], int numArgs) {
01044   if (!state->isCurPt()) {
01045     error(getPos(), "No current point in closepath");
01046     return;
01047   }
01048   state->closePath();
01049 }
01050 
01051 //------------------------------------------------------------------------
01052 // path painting operators
01053 //------------------------------------------------------------------------
01054 
01055 void Gfx::opEndPath(Object args[], int numArgs) {
01056   doEndPath();
01057 }
01058 
01059 void Gfx::opStroke(Object args[], int numArgs) {
01060   if (!state->isCurPt()) {
01061     //error(getPos(), "No path in stroke");
01062     return;
01063   }
01064   if (state->isPath())
01065     out->stroke(state);
01066   doEndPath();
01067 }
01068 
01069 void Gfx::opCloseStroke(Object args[], int numArgs) {
01070   if (!state->isCurPt()) {
01071     //error(getPos(), "No path in closepath/stroke");
01072     return;
01073   }
01074   if (state->isPath()) {
01075     state->closePath();
01076     out->stroke(state);
01077   }
01078   doEndPath();
01079 }
01080 
01081 void Gfx::opFill(Object args[], int numArgs) {
01082   if (!state->isCurPt()) {
01083     //error(getPos(), "No path in fill");
01084     return;
01085   }
01086   if (state->isPath()) {
01087     if (state->getFillColorSpace()->getMode() == csPattern) {
01088       doPatternFill(gFalse);
01089     } else {
01090       out->fill(state);
01091     }
01092   }
01093   doEndPath();
01094 }
01095 
01096 void Gfx::opEOFill(Object args[], int numArgs) {
01097   if (!state->isCurPt()) {
01098     //error(getPos(), "No path in eofill");
01099     return;
01100   }
01101   if (state->isPath()) {
01102     if (state->getFillColorSpace()->getMode() == csPattern) {
01103       doPatternFill(gTrue);
01104     } else {
01105       out->eoFill(state);
01106     }
01107   }
01108   doEndPath();
01109 }
01110 
01111 void Gfx::opFillStroke(Object args[], int numArgs) {
01112   if (!state->isCurPt()) {
01113     //error(getPos(), "No path in fill/stroke");
01114     return;
01115   }
01116   if (state->isPath()) {
01117     if (state->getFillColorSpace()->getMode() == csPattern) {
01118       doPatternFill(gFalse);
01119     } else {
01120       out->fill(state);
01121     }
01122     out->stroke(state);
01123   }
01124   doEndPath();
01125 }
01126 
01127 void Gfx::opCloseFillStroke(Object args[], int numArgs) {
01128   if (!state->isCurPt()) {
01129     //error(getPos(), "No path in closepath/fill/stroke");
01130     return;
01131   }
01132   if (state->isPath()) {
01133     state->closePath();
01134     if (state->getFillColorSpace()->getMode() == csPattern) {
01135       doPatternFill(gFalse);
01136     } else {
01137       out->fill(state);
01138     }
01139     out->stroke(state);
01140   }
01141   doEndPath();
01142 }
01143 
01144 void Gfx::opEOFillStroke(Object args[], int numArgs) {
01145   if (!state->isCurPt()) {
01146     //error(getPos(), "No path in eofill/stroke");
01147     return;
01148   }
01149   if (state->isPath()) {
01150     if (state->getFillColorSpace()->getMode() == csPattern) {
01151       doPatternFill(gTrue);
01152     } else {
01153       out->eoFill(state);
01154     }
01155     out->stroke(state);
01156   }
01157   doEndPath();
01158 }
01159 
01160 void Gfx::opCloseEOFillStroke(Object args[], int numArgs) {
01161   if (!state->isCurPt()) {
01162     //error(getPos(), "No path in closepath/eofill/stroke");
01163     return;
01164   }
01165   if (state->isPath()) {
01166     state->closePath();
01167     if (state->getFillColorSpace()->getMode() == csPattern) {
01168       doPatternFill(gTrue);
01169     } else {
01170       out->eoFill(state);
01171     }
01172     out->stroke(state);
01173   }
01174   doEndPath();
01175 }
01176 
01177 void Gfx::doPatternFill(GBool eoFill) {
01178   GfxPatternColorSpace *patCS;
01179   GfxPattern *pattern;
01180   GfxTilingPattern *tPat;
01181   GfxColorSpace *cs;
01182   fouble xMin, yMin, xMax, yMax, x, y, x1, y1;
01183   fouble cxMin, cyMin, cxMax, cyMax;
01184   int xi0, yi0, xi1, yi1, xi, yi;
01185   fouble *ctm, *btm, *ptm;
01186   fouble m[6], ictm[6], m1[6], imb[6];
01187   fouble det;
01188   fouble xstep, ystep;
01189   int i;
01190 
01191   // this is a bit of a kludge -- patterns can be really slow, so we
01192   // skip them if we're only doing text extraction, since they almost
01193   // certainly don't contain any text
01194   if (!out->needNonText()) {
01195     return;
01196   }
01197 
01198   // get color space
01199   patCS = (GfxPatternColorSpace *)state->getFillColorSpace();
01200 
01201   // get pattern
01202   if (!(pattern = state->getFillPattern())) {
01203     return;
01204   }
01205   if (pattern->getType() != 1) {
01206     return;
01207   }
01208   tPat = (GfxTilingPattern *)pattern;
01209 
01210   // construct a (pattern space) -> (current space) transform matrix
01211   ctm = state->getCTM();
01212   btm = baseMatrix;
01213   ptm = tPat->getMatrix();
01214   // iCTM = invert CTM
01215   det = 1 / (ctm[0] * ctm[3] - ctm[1] * ctm[2]);
01216   ictm[0] = ctm[3] * det;
01217   ictm[1] = -ctm[1] * det;
01218   ictm[2] = -ctm[2] * det;
01219   ictm[3] = ctm[0] * det;
01220   ictm[4] = (ctm[2] * ctm[5] - ctm[3] * ctm[4]) * det;
01221   ictm[5] = (ctm[1] * ctm[4] - ctm[0] * ctm[5]) * det;
01222   // m1 = PTM * BTM = PTM * base transform matrix
01223   m1[0] = ptm[0] * btm[0] + ptm[1] * btm[2];
01224   m1[1] = ptm[0] * btm[1] + ptm[1] * btm[3];
01225   m1[2] = ptm[2] * btm[0] + ptm[3] * btm[2];
01226   m1[3] = ptm[2] * btm[1] + ptm[3] * btm[3];
01227   m1[4] = ptm[4] * btm[0] + ptm[5] * btm[2] + btm[4];
01228   m1[5] = ptm[4] * btm[1] + ptm[5] * btm[3] + btm[5];
01229   // m = m1 * iCTM = (PTM * BTM) * (iCTM)
01230   m[0] = m1[0] * ictm[0] + m1[1] * ictm[2];
01231   m[1] = m1[0] * ictm[1] + m1[1] * ictm[3];
01232   m[2] = m1[2] * ictm[0] + m1[3] * ictm[2];
01233   m[3] = m1[2] * ictm[1] + m1[3] * ictm[3];
01234   m[4] = m1[4] * ictm[0] + m1[5] * ictm[2] + ictm[4];
01235   m[5] = m1[4] * ictm[1] + m1[5] * ictm[3] + ictm[5];
01236 
01237   // construct a (base space) -> (pattern space) transform matrix
01238   det = 1 / (m1[0] * m1[3] - m1[1] * m1[2]);
01239   imb[0] = m1[3] * det;
01240   imb[1] = -m1[1] * det;
01241   imb[2] = -m1[2] * det;
01242   imb[3] = m1[0] * det;
01243   imb[4] = (m1[2] * m1[5] - m1[3] * m1[4]) * det;
01244   imb[5] = (m1[1] * m1[4] - m1[0] * m1[5]) * det;
01245 
01246   // save current graphics state
01247   out->saveState(state);
01248   state = state->save();
01249 
01250   // set underlying color space (for uncolored tiling patterns)
01251   if (tPat->getPaintType() == 2 && (cs = patCS->getUnder())) {
01252     state->setFillColorSpace(cs->copy());
01253   } else {
01254     state->setFillColorSpace(new GfxDeviceGrayColorSpace());
01255   }
01256   state->setFillPattern(NULL);
01257   out->updateFillColor(state);
01258 
01259   // clip to current path
01260   state->clip();
01261   if (eoFill) {
01262     out->eoClip(state);
01263   } else {
01264     out->clip(state);
01265   }
01266   state->clearPath();
01267 
01268   // transform clip region bbox to pattern space
01269   state->getClipBBox(&cxMin, &cyMin, &cxMax, &cyMax);
01270   xMin = xMax = cxMin * imb[0] + cyMin * imb[2] + imb[4];
01271   yMin = yMax = cxMin * imb[1] + cyMin * imb[3] + imb[5];
01272   x1 = cxMin * imb[0] + cyMax * imb[2] + imb[4];
01273   y1 = cxMin * imb[1] + cyMax * imb[3] + imb[5];
01274   if (x1 < xMin) {
01275     xMin = x1;
01276   } else if (x1 > xMax) {
01277     xMax = x1;
01278   }
01279   if (y1 < yMin) {
01280     yMin = y1;
01281   } else if (y1 > yMax) {
01282     yMax = y1;
01283   }
01284   x1 = cxMax * imb[0] + cyMin * imb[2] + imb[4];
01285   y1 = cxMax * imb[1] + cyMin * imb[3] + imb[5];
01286   if (x1 < xMin) {
01287     xMin = x1;
01288   } else if (x1 > xMax) {
01289     xMax = x1;
01290   }
01291   if (y1 < yMin) {
01292     yMin = y1;
01293   } else if (y1 > yMax) {
01294     yMax = y1;
01295   }
01296   x1 = cxMax * imb[0] + cyMax * imb[2] + imb[4];
01297   y1 = cxMax * imb[1] + cyMax * imb[3] + imb[5];
01298   if (x1 < xMin) {
01299     xMin = x1;
01300   } else if (x1 > xMax) {
01301     xMax = x1;
01302   }
01303   if (y1 < yMin) {
01304     yMin = y1;
01305   } else if (y1 > yMax) {
01306     yMax = y1;
01307   }
01308 
01309   // draw the pattern
01310   //~ this should treat negative steps differently -- start at right/top
01311   //~ edge instead of left/bottom (?)
01312   xstep = fabs(tPat->getXStep());
01313   ystep = fabs(tPat->getYStep());
01314   xi0 = (int)floor(xMin / xstep);
01315   xi1 = (int)ceil(xMax / xstep);
01316   yi0 = (int)floor(yMin / ystep);
01317   yi1 = (int)ceil(yMax / ystep);
01318   for (i = 0; i < 4; ++i) {
01319     m1[i] = m[i];
01320   }
01321   for (yi = yi0; yi < yi1; ++yi) {
01322     for (xi = xi0; xi < xi1; ++xi) {
01323       x = xi * xstep;
01324       y = yi * ystep;
01325       m1[4] = x * m[0] + y * m[2] + m[4];
01326       m1[5] = x * m[1] + y * m[3] + m[5];
01327       doForm1(tPat->getContentStream(), tPat->getResDict(),
01328               m1, tPat->getBBox());
01329     }
01330   }
01331 
01332   // restore graphics state
01333   state = state->restore();
01334   out->restoreState(state);
01335 }
01336 
01337 void Gfx::opShFill(Object args[], int numArgs) {
01338   GfxShading *shading;
01339   fouble xMin, yMin, xMax, yMax;
01340 
01341   if (!(shading = res->lookupShading(args[0].getName()))) {
01342     return;
01343   }
01344 
01345   // save current graphics state
01346   out->saveState(state);
01347   state = state->save();
01348 
01349   // clip to bbox
01350   if (shading->getHasBBox()) {
01351     shading->getBBox(&xMin, &yMin, &xMax, &yMax);
01352     state->moveTo(xMin, yMin);
01353     state->lineTo(xMax, yMin);
01354     state->lineTo(xMax, yMax);
01355     state->lineTo(xMin, yMax);
01356     state->closePath();
01357     state->clip();
01358     out->clip(state);
01359     state->clearPath();
01360   }
01361 
01362   // set the color space
01363   state->setFillColorSpace(shading->getColorSpace()->copy());
01364 
01365   // do shading type-specific operations
01366   switch (shading->getType()) {
01367   case 2:
01368     doAxialShFill((GfxAxialShading *)shading);
01369     break;
01370   case 3:
01371     doRadialShFill((GfxRadialShading *)shading);
01372     break;
01373   }
01374 
01375   // restore graphics state
01376   state = state->restore();
01377   out->restoreState(state);
01378 
01379   delete shading;
01380 }
01381 
01382 void Gfx::doAxialShFill(GfxAxialShading *shading) {
01383   fouble xMin, yMin, xMax, yMax;
01384   fouble x0, y0, x1, y1;
01385   fouble dx, dy, mul;
01386   fouble tMin, tMax, t, tx, ty;
01387   fouble s[4], sMin, sMax, tmp;
01388   fouble ux0, uy0, ux1, uy1, vx0, vy0, vx1, vy1;
01389   fouble t0, t1, tt;
01390   fouble ta[axialMaxSplits + 1];
01391   int next[axialMaxSplits + 1];
01392   GfxColor color0, color1;
01393   int nComps;
01394   int i, j, k, kk;
01395 
01396   // get the clip region bbox
01397   state->getUserClipBBox(&xMin, &yMin, &xMax, &yMax);
01398 
01399   // compute min and max t values, based on the four corners of the
01400   // clip region bbox
01401   shading->getCoords(&x0, &y0, &x1, &y1);
01402   dx = x1 - x0;
01403   dy = y1 - y0;
01404   mul = 1 / (dx * dx + dy * dy);
01405   tMin = tMax = ((xMin - x0) * dx + (yMin - y0) * dy) * mul;
01406   t = ((xMin - x0) * dx + (yMax - y0) * dy) * mul;
01407   if (t < tMin) {
01408     tMin = t;
01409   } else if (t > tMax) {
01410     tMax = t;
01411   }
01412   t = ((xMax - x0) * dx + (yMin - y0) * dy) * mul;
01413   if (t < tMin) {
01414     tMin = t;
01415   } else if (t > tMax) {
01416     tMax = t;
01417   }
01418   t = ((xMax - x0) * dx + (yMax - y0) * dy) * mul;
01419   if (t < tMin) {
01420     tMin = t;
01421   } else if (t > tMax) {
01422     tMax = t;
01423   }
01424   if (tMin < 0 && !shading->getExtend0()) {
01425     tMin = 0;
01426   }
01427   if (tMax > 1 && !shading->getExtend1()) {
01428     tMax = 1;
01429   }
01430 
01431   // get the function domain
01432   t0 = shading->getDomain0();
01433   t1 = shading->getDomain1();
01434 
01435   // Traverse the t axis and do the shading.
01436   //
01437   // For each point (tx, ty) on the t axis, consider a line through
01438   // that point perpendicular to the t axis:
01439   //
01440   //     x(s) = tx + s * -dy   -->   s = (x - tx) / -dy
01441   //     y(s) = ty + s * dx    -->   s = (y - ty) / dx
01442   //
01443   // Then look at the intersection of this line with the bounding box
01444   // (xMin, yMin, xMax, yMax).  In the general case, there are four
01445   // intersection points:
01446   //
01447   //     s0 = (xMin - tx) / -dy
01448   //     s1 = (xMax - tx) / -dy
01449   //     s2 = (yMin - ty) / dx
01450   //     s3 = (yMax - ty) / dx
01451   //
01452   // and we want the middle two s values.
01453   //
01454   // In the case where dx = 0, take s0 and s1; in the case where dy =
01455   // 0, take s2 and s3.
01456   //
01457   // Each filled polygon is bounded by two of these line segments
01458   // perpdendicular to the t axis.
01459   //
01460   // The t axis is bisected into smaller regions until the color
01461   // difference across a region is small enough, and then the region
01462   // is painted with a single color.
01463 
01464   // set up
01465   nComps = shading->getColorSpace()->getNComps();
01466   ta[0] = tMin;
01467   ta[axialMaxSplits] = tMax;
01468   next[0] = axialMaxSplits;
01469 
01470   // compute the color at t = tMin
01471   if (tMin < 0) {
01472     tt = t0;
01473   } else if (tMin > 1) {
01474     tt = t1;
01475   } else {
01476     tt = t0 + (t1 - t0) * tMin;
01477   }
01478   shading->getColor(tt, &color0);
01479 
01480   // compute the coordinates of the point on the t axis at t = tMin;
01481   // then compute the intersection of the perpendicular line with the
01482   // bounding box
01483   tx = x0 + tMin * dx;
01484   ty = y0 + tMin * dy;
01485   if (dx == 0 && dy == 0) {
01486     sMin = sMax = 0;
01487   } if (dx == 0) {
01488     sMin = (xMin - tx) / -dy;
01489     sMax = (xMax - tx) / -dy;
01490     if (sMin > sMax) { tmp = sMin; sMin = sMax; sMax = tmp; }
01491   } else if (dy == 0) {
01492     sMin = (yMin - ty) / dx;
01493     sMax = (yMax - ty) / dx;
01494     if (sMin > sMax) { tmp = sMin; sMin = sMax; sMax = tmp; }
01495   } else {
01496     s[0] = (yMin - ty) / dx;
01497     s[1] = (yMax - ty) / dx;
01498     s[2] = (xMin - tx) / -dy;
01499     s[3] = (xMax - tx) / -dy;
01500     for (j = 0; j < 3; ++j) {
01501       kk = j;
01502       for (k = j + 1; k < 4; ++k) {
01503         if (s[k] < s[kk]) {
01504           kk = k;
01505         }
01506       }
01507       tmp = s[j]; s[j] = s[kk]; s[kk] = tmp;
01508     }
01509     sMin = s[1];
01510     sMax = s[2];
01511   }
01512   ux0 = tx - sMin * dy;
01513   uy0 = ty + sMin * dx;
01514   vx0 = tx - sMax * dy;
01515   vy0 = ty + sMax * dx;
01516 
01517   i = 0;
01518   while (i < axialMaxSplits) {
01519 
01520     // bisect until color difference is small enough or we hit the
01521     // bisection limit
01522     j = next[i];
01523     while (j > i + 1) {
01524       if (ta[j] < 0) {
01525         tt = t0;
01526       } else if (ta[j] > 1) {
01527         tt = t1;
01528       } else {
01529         tt = t0 + (t1 - t0) * ta[j];
01530       }
01531       shading->getColor(tt, &color1);
01532       for (k = 0; k < nComps; ++k) {
01533         if (fabs(color1.c[k] - color0.c[k]) > axialColorDelta) {
01534           break;
01535         }
01536       }
01537       if (k == nComps) {
01538         break;
01539       }
01540       k = (i + j) / 2;
01541       ta[k] = 0.5 * (ta[i] + ta[j]);
01542       next[i] = k;
01543       next[k] = j;
01544       j = k;
01545     }
01546 
01547     // use the average of the colors of the two sides of the region
01548     for (k = 0; k < nComps; ++k) {
01549       color0.c[k] = 0.5 * (color0.c[k] + color1.c[k]);
01550     }
01551 
01552     // compute the coordinates of the point on the t axis; then
01553     // compute the intersection of the perpendicular line with the
01554     // bounding box
01555     tx = x0 + ta[j] * dx;
01556     ty = y0 + ta[j] * dy;
01557     if (dx == 0 && dy == 0) {
01558       sMin = sMax = 0;
01559     } if (dx == 0) {
01560       sMin = (xMin - tx) / -dy;
01561       sMax = (xMax - tx) / -dy;
01562       if (sMin > sMax) { tmp = sMin; sMin = sMax; sMax = tmp; }
01563     } else if (dy == 0) {
01564       sMin = (yMin - ty) / dx;
01565       sMax = (yMax - ty) / dx;
01566       if (sMin > sMax) { tmp = sMin; sMin = sMax; sMax = tmp; }
01567     } else {
01568       s[0] = (yMin - ty) / dx;
01569       s[1] = (yMax - ty) / dx;
01570       s[2] = (xMin - tx) / -dy;
01571       s[3] = (xMax - tx) / -dy;
01572       for (j = 0; j < 3; ++j) {
01573         kk = j;
01574         for (k = j + 1; k < 4; ++k) {
01575           if (s[k] < s[kk]) {
01576             kk = k;
01577           }
01578         }
01579         tmp = s[j]; s[j] = s[kk]; s[kk] = tmp;
01580       }
01581       sMin = s[1];
01582       sMax = s[2];
01583     }
01584     ux1 = tx - sMin * dy;
01585     uy1 = ty + sMin * dx;
01586     vx1 = tx - sMax * dy;
01587     vy1 = ty + sMax * dx;
01588 
01589     // set the color
01590     state->setFillColor(&color0);
01591     out->updateFillColor(state);
01592 
01593     // fill the region
01594     state->moveTo(ux0, uy0);
01595     state->lineTo(vx0, vy0);
01596     state->lineTo(vx1, vy1);
01597     state->lineTo(ux1, uy1);
01598     state->closePath();
01599     out->fill(state);
01600     state->clearPath();
01601 
01602     // set up for next region
01603     ux0 = ux1;
01604     uy0 = uy1;
01605     vx0 = vx1;
01606     vy0 = vy1;
01607     color0 = color1;
01608     i = next[i];
01609   }
01610 }
01611 
01612 void Gfx::doRadialShFill(GfxRadialShading *shading) {
01613   fouble sMin, sMax, xMin, yMin, xMax, yMax;
01614   fouble x0, y0, r0, x1, y1, r1, t0, t1;
01615   int nComps;
01616   GfxColor colorA, colorB;
01617   fouble xa, ya, xb, yb, ra, rb;
01618   fouble ta, tb, sa, sb;
01619   int ia, ib, k, n;
01620   fouble *ctm;
01621   fouble angle, t;
01622 
01623   // get the shading info
01624   shading->getCoords(&x0, &y0, &r0, &x1, &y1, &r1);
01625   t0 = shading->getDomain0();
01626   t1 = shading->getDomain1();
01627   nComps = shading->getColorSpace()->getNComps();
01628 
01629   // compute the (possibly extended) s range
01630   sMin = 0;
01631   sMax = 1;
01632   if (shading->getExtend0()) {
01633     if (r0 < r1) {
01634       // extend the smaller end
01635       sMin = -r0 / (r1 - r0);
01636     } else {
01637       // extend the larger end
01638       //~ this computes the diagonal of the bounding box -- we should
01639       //~ really compute the intersection of the moving/expanding
01640       //~ circles with each of the four corners and look for the max
01641       //~ radius
01642       state->getUserClipBBox(&xMin, &yMin, &xMax, &yMax);
01643       sMin = (sqrt((xMax - xMin) * (xMax - xMin) +
01644                    (yMax - yMin) * (yMax - yMin)) - r0) / (r1 - r0);
01645       if (sMin > 0) {
01646         sMin = 0;
01647       } else if (sMin < -20) {
01648         // sanity check
01649         sMin = -20;
01650       }
01651     }
01652   }
01653   if (shading->getExtend1()) {
01654     if (r1 < r0) {
01655       // extend the smaller end
01656       sMax = -r0 / (r1 - r0);
01657     } else if (r1 > r0) {
01658       // extend the larger end
01659       state->getUserClipBBox(&xMin, &yMin, &xMax, &yMax);
01660       sMax = (sqrt((xMax - xMin) * (xMax - xMin) +
01661                    (yMax - yMin) * (yMax - yMin)) - r0) / (r1 - r0);
01662       if (sMax < 1) {
01663         sMin = 1;
01664       } else if (sMax > 20) {
01665         // sanity check
01666         sMax = 20;
01667       }
01668     }
01669   }
01670 
01671   // compute the number of steps into which circles must be divided to
01672   // achieve a curve flatness of 0.1 pixel in device space for the
01673   // largest circle (note that "device space" is 72 dpi when generating
01674   // PostScript, hence the relatively small 0.1 pixel accuracy)
01675   ctm = state->getCTM();
01676   t = fabs(ctm[0]);
01677   if (fabs(ctm[1]) > t) {
01678     t = fabs(ctm[1]);
01679   }
01680   if (fabs(ctm[2]) > t) {
01681     t = fabs(ctm[2]);
01682   }
01683   if (fabs(ctm[3]) > t) {
01684     t = fabs(ctm[3]);
01685   }
01686   if (r0 > r1) {
01687     t *= r0;
01688   } else {
01689     t *= r1;
01690   }
01691   if (t < 1) {
01692     n = 3;
01693   } else {
01694     n = (int)(M_PI / acos(1 - 0.1 / t));
01695     if (n < 3) {
01696       n = 3;
01697     } else if (n > 200) {
01698       n = 200;
01699     }
01700   }
01701 
01702   // Traverse the t axis and do the shading.
01703   //
01704   // This generates and fills a series of rings.  Each ring is defined
01705   // by two circles:
01706   //   sa, ta, xa, ya, ra, colorA
01707   //   sb, tb, xb, yb, rb, colorB
01708   //
01709   // The s/t axis is divided into radialMaxSplits parts; these parts
01710   // are combined as much as possible while respecting the
01711   // radialColorDelta parameter.
01712 
01713   // setup for the start circle
01714   ia = 0;
01715   sa = sMin;
01716   ta = t0 + sa * (t1 - t0);
01717   xa = x0 + sa * (x1 - x0);
01718   ya = y0 + sa * (y1 - y0);
01719   ra = r0 + sa * (r1 - r0);
01720   if (ta < t0) {
01721     shading->getColor(t0, &colorA);
01722   } else if (ta > t1) {
01723     shading->getColor(t1, &colorA);
01724   } else {
01725     shading->getColor(ta, &colorA);
01726   }
01727 
01728   while (ia < radialMaxSplits) {
01729 
01730     // go as far along the t axis (toward t1) as we can, such that the
01731     // color difference is within the tolerance (radialColorDelta) --
01732     // this uses bisection (between the current value, t, and t1),
01733     // limited to radialMaxSplits points along the t axis
01734     ib = radialMaxSplits;
01735     sb = sMin + ((fouble)ib / (fouble)radialMaxSplits) * (sMax - sMin);
01736     tb = t0 + sb * (t1 - t0);
01737     if (tb < t0) {
01738       shading->getColor(t0, &colorB);
01739     } else if (tb > t1) {
01740       shading->getColor(t1, &colorB);
01741     } else {
01742       shading->getColor(tb, &colorB);
01743     }
01744     while (ib - ia > 1) {
01745       for (k = 0; k < nComps; ++k) {
01746         if (fabs(colorB.c[k] - colorA.c[k]) > radialColorDelta) {
01747           break;
01748         }
01749       }
01750       if (k == nComps) {
01751         break;
01752       }
01753       ib = (ia + ib) / 2;
01754       sb = sMin + ((fouble)ib / (fouble)radialMaxSplits) * (sMax - sMin);
01755       tb = t0 + sb * (t1 - t0);
01756       if (tb < t0) {
01757         shading->getColor(t0, &colorB);
01758       } else if (tb > t1) {
01759         shading->getColor(t1, &colorB);
01760       } else {
01761         shading->getColor(tb, &colorB);
01762       }
01763     }
01764 
01765     // compute center and radius of the circle
01766     xb = x0 + sb * (x1 - x0);
01767     yb = y0 + sb * (y1 - y0);
01768     rb = r0 + sb * (r1 - r0);
01769 
01770     // use the average of the colors at the two circles
01771     for (k = 0; k < nComps; ++k) {
01772       colorA.c[k] = 0.5 * (colorA.c[k] + colorB.c[k]);
01773     }
01774     state->setFillColor(&colorA);
01775     out->updateFillColor(state);
01776 
01777     // construct path for first circle
01778     state->moveTo(xa + ra, ya);
01779     for (k = 1; k < n; ++k) {
01780       angle = ((fouble)k / (fouble)n) * 2 * M_PI;
01781       state->lineTo(xa + ra * cos(angle), ya + ra * sin(angle));
01782     }
01783     state->closePath();
01784 
01785     // construct and append path for second circle
01786     state->moveTo(xb + rb, yb);
01787     for (k = 1; k < n; ++k) {
01788       angle = ((fouble)k / (fouble)n) * 2 * M_PI;
01789       state->lineTo(xb + rb * cos(angle), yb + rb * sin(angle));
01790     }
01791     state->closePath();
01792 
01793     // fill the ring
01794     out->eoFill(state);
01795     state->clearPath();
01796 
01797     // step to the next value of t
01798     ia = ib;
01799     sa = sb;
01800     ta = tb;
01801     xa = xb;
01802     ya = yb;
01803     ra = rb;
01804     colorA = colorB;
01805   }
01806 }
01807 
01808 void Gfx::doEndPath() {
01809   if (state->isPath() && clip != clipNone) {
01810     state->clip();
01811     if (clip == clipNormal) {
01812       out->clip(state);
01813     } else {
01814       out->eoClip(state);
01815     }
01816   }
01817   clip = clipNone;
01818   state->clearPath();
01819 }
01820 
01821 //------------------------------------------------------------------------
01822 // path clipping operators
01823 //------------------------------------------------------------------------
01824 
01825 void Gfx::opClip(Object args[], int numArgs) {
01826   clip = clipNormal;
01827 }
01828 
01829 void Gfx::opEOClip(Object args[], int numArgs) {
01830   clip = clipEO;
01831 }
01832 
01833 //------------------------------------------------------------------------
01834 // text object operators
01835 //------------------------------------------------------------------------
01836 
01837 void Gfx::opBeginText(Object args[], int numArgs) {
01838   state->setTextMat(1, 0, 0, 1, 0, 0);
01839   state->textMoveTo(0, 0);
01840   out->updateTextMat(state);
01841   out->updateTextPos(state);
01842   fontChanged = gTrue;
01843 }
01844 
01845 void Gfx::opEndText(Object args[], int numArgs) {
01846 }
01847 
01848 //------------------------------------------------------------------------
01849 // text state operators
01850 //------------------------------------------------------------------------
01851 
01852 void Gfx::opSetCharSpacing(Object args[], int numArgs) {
01853   state->setCharSpace(args[0].getNum());
01854   out->updateCharSpace(state);
01855 }
01856 
01857 void Gfx::opSetFont(Object args[], int numArgs) {
01858   GfxFont *font;
01859 
01860   if (!(font = res->lookupFont(args[0].getName()))) {
01861     return;
01862   }
01863   if (printCommands) {
01864     printf("  font: tag=%s name='%s' %g\n",
01865            font->getTag()->getCString(),
01866            font->getName() ? font->getName()->getCString() : "???",
01867            static_cast<double>(args[1].getNum()));
01868     fflush(stdout);
01869   }
01870   state->setFont(font, args[1].getNum());
01871   fontChanged = gTrue;
01872 }
01873 
01874 void Gfx::opSetTextLeading(Object args[], int numArgs) {
01875   state->setLeading(args[0].getNum());
01876 }
01877 
01878 void Gfx::opSetTextRender(Object args[], int numArgs) {
01879   state->setRender(args[0].getInt());
01880   out->updateRender(state);
01881 }
01882 
01883 void Gfx::opSetTextRise(Object args[], int numArgs) {
01884   state->setRise(args[0].getNum());
01885   out->updateRise(state);
01886 }
01887 
01888 void Gfx::opSetWordSpacing(Object args[], int numArgs) {
01889   state->setWordSpace(args[0].getNum());
01890   out->updateWordSpace(state);
01891 }
01892 
01893 void Gfx::opSetHorizScaling(Object args[], int numArgs) {
01894   state->setHorizScaling(args[0].getNum());
01895   out->updateHorizScaling(state);
01896   fontChanged = gTrue;
01897 }
01898 
01899 //------------------------------------------------------------------------
01900 // text positioning operators
01901 //------------------------------------------------------------------------
01902 
01903 void Gfx::opTextMove(Object args[], int numArgs) {
01904   fouble tx, ty;
01905 
01906   tx = state->getLineX() + args[0].getNum();
01907   ty = state->getLineY() + args[1].getNum();
01908   state->textMoveTo(tx, ty);
01909   out->updateTextPos(state);
01910 }
01911 
01912 void Gfx::opTextMoveSet(Object args[], int numArgs) {
01913   fouble tx, ty;
01914 
01915   tx = state->getLineX() + args[0].getNum();
01916   ty = args[1].getNum();
01917   state->setLeading(-ty);
01918   ty += state->getLineY();
01919   state->textMoveTo(tx, ty);
01920   out->updateTextPos(state);
01921 }
01922 
01923 void Gfx::opSetTextMatrix(Object args[], int numArgs) {
01924   state->setTextMat(args[0].getNum(), args[1].getNum(),
01925                     args[2].getNum(), args[3].getNum(),
01926                     args[4].getNum(), args[5].getNum());
01927   state->textMoveTo(0, 0);
01928   out->updateTextMat(state);
01929   out->updateTextPos(state);
01930   fontChanged = gTrue;
01931 }
01932 
01933 void Gfx::opTextNextLine(Object args[], int numArgs) {
01934   fouble tx, ty;
01935 
01936   tx = state->getLineX();
01937   ty = state->getLineY() - state->getLeading();
01938   state->textMoveTo(tx, ty);
01939   out->updateTextPos(state);
01940 }
01941 
01942 //------------------------------------------------------------------------
01943 // text string operators
01944 //------------------------------------------------------------------------
01945 
01946 void Gfx::opShowText(Object args[], int numArgs) {
01947   if (!state->getFont()) {
01948     error(getPos(), "No font in show");
01949     return;
01950   }
01951   doShowText(args[0].getString());
01952 }
01953 
01954 void Gfx::opMoveShowText(Object args[], int numArgs) {
01955   fouble tx, ty;
01956 
01957   if (!state->getFont()) {
01958     error(getPos(), "No font in move/show");
01959     return;
01960   }
01961   tx = state->getLineX();
01962   ty = state->getLineY() - state->getLeading();
01963   state->textMoveTo(tx, ty);
01964   out->updateTextPos(state);
01965   doShowText(args[0].getString());
01966 }
01967 
01968 void Gfx::opMoveSetShowText(Object args[], int numArgs) {
01969   fouble tx, ty;
01970 
01971   if (!state->getFont()) {
01972     error(getPos(), "No font in move/set/show");
01973     return;
01974   }
01975   state->setWordSpace(args[0].getNum());
01976   state->setCharSpace(args[1].getNum());
01977   tx = state->getLineX();
01978   ty = state->getLineY() - state->getLeading();
01979   state->textMoveTo(tx, ty);
01980   out->updateWordSpace(state);
01981   out->updateCharSpace(state);
01982   out->updateTextPos(state);
01983   doShowText(args[2].getString());
01984 }
01985 
01986 void Gfx::opShowSpaceText(Object args[], int numArgs) {
01987   Array *a;
01988   Object obj;
01989   int wMode;
01990   int i;
01991 
01992   if (!state->getFont()) {
01993     error(getPos(), "No font in show/space");
01994     return;
01995   }
01996   wMode = state->getFont()->getWMode();
01997   a = args[0].getArray();
01998   for (i = 0; i < a->getLength(); ++i) {
01999     a->get(i, &obj);
02000     if (obj.isNum()) {
02001       if (wMode) {
02002         state->textShift(0, -obj.getNum() * 0.001 * state->getFontSize());
02003       } else {
02004         state->textShift(-obj.getNum() * 0.001 * state->getFontSize(), 0);
02005       }
02006       out->updateTextShift(state, obj.getNum());
02007     } else if (obj.isString()) {
02008       doShowText(obj.getString());
02009     } else {
02010       error(getPos(), "Element of show/space array must be number or string");
02011     }
02012     obj.free();
02013   }
02014 }
02015 
02016 void Gfx::doShowText(GString *s) {
02017   GfxFont *font;
02018   int wMode;
02019   fouble riseX, riseY;
02020   CharCode code;
02021   Unicode u[8];
02022   fouble x, y, dx, dy, dx2, dy2, curX, curY, tdx, tdy;
02023   fouble originX, originY, tOriginX, tOriginY;
02024   fouble oldCTM[6], newCTM[6];
02025   fouble *mat;
02026   Object charProc;
02027   Dict *resDict;
02028   Parser *oldParser;
02029   char *p;
02030   int len, n, uLen, nChars, nSpaces, i;
02031 
02032   if (fontChanged) {
02033     out->updateFont(state);
02034     fontChanged = gFalse;
02035   }
02036   font = state->getFont();
02037   wMode = font->getWMode();
02038 
02039   if (out->useDrawChar()) {
02040     out->beginString(state, s);
02041   }
02042 
02043   // handle a Type 3 char
02044   if (font->getType() == fontType3 && out->interpretType3Chars()) {
02045     mat = state->getCTM();
02046     for (i = 0; i < 6; ++i) {
02047       oldCTM[i] = mat[i];
02048     }
02049     mat = state->getTextMat();
02050     newCTM[0] = mat[0] * oldCTM[0] + mat[1] * oldCTM[2];
02051     newCTM[1] = mat[0] * oldCTM[1] + mat[1] * oldCTM[3];
02052     newCTM[2] = mat[2] * oldCTM[0] + mat[3] * oldCTM[2];
02053     newCTM[3] = mat[2] * oldCTM[1] + mat[3] * oldCTM[3];
02054     mat = font->getFontMatrix();
02055     newCTM[0] = mat[0] * newCTM[0] + mat[1] * newCTM[2];
02056     newCTM[1] = mat[0] * newCTM[1] + mat[1] * newCTM[3];
02057     newCTM[2] = mat[2] * newCTM[0] + mat[3] * newCTM[2];
02058     newCTM[3] = mat[2] * newCTM[1] + mat[3] * newCTM[3];
02059     newCTM[0] *= state->getFontSize();
02060     newCTM[3] *= state->getFontSize();
02061     newCTM[0] *= state->getHorizScaling();
02062     newCTM[2] *= state->getHorizScaling();
02063     state->textTransformDelta(0, state->getRise(), &riseX, &riseY);
02064     curX = state->getCurX();
02065     curY = state->getCurY();
02066     oldParser = parser;
02067     p = s->getCString();
02068     len = s->getLength();
02069     while (len > 0) {
02070       n = font->getNextChar(p, len, &code,
02071                             u, (int)(sizeof(u) / sizeof(Unicode)), &uLen,
02072                             &dx, &dy, &originX, &originY);
02073       dx = dx * state->getFontSize() + state->getCharSpace();
02074       if (n == 1 && *p == ' ') {
02075         dx += state->getWordSpace();
02076       }
02077       dx *= state->getHorizScaling();
02078       dy *= state->getFontSize();
02079       state->textTransformDelta(dx, dy, &tdx, &tdy);
02080       state->transform(curX + riseX, curY + riseY, &x, &y);
02081       out->saveState(state);
02082       state = state->save();
02083       state->setCTM(newCTM[0], newCTM[1], newCTM[2], newCTM[3], x, y);
02084       //~ out->updateCTM(???)
02085       if (!out->beginType3Char(state, code, u, uLen)) {
02086         ((Gfx8BitFont *)font)->getCharProc(code, &charProc);
02087         if ((resDict = ((Gfx8BitFont *)font)->getResources())) {
02088           pushResources(resDict);
02089         }
02090         if (charProc.isStream()) {
02091           display(&charProc, gFalse);
02092         } else {
02093           error(getPos(), "Missing or bad Type3 CharProc entry");
02094         }
02095         out->endType3Char(state);
02096         if (resDict) {
02097           popResources();
02098         }
02099         charProc.free();
02100       }
02101       state = state->restore();
02102       out->restoreState(state);
02103       // GfxState::restore() does *not* restore the current position,
02104       // so we track it here with (curX, curY)
02105       curX += tdx;
02106       curY += tdy;
02107       state->moveTo(curX, curY);
02108       p += n;
02109       len -= n;
02110     }
02111     parser = oldParser;
02112 
02113   } else if (out->useDrawChar()) {
02114     state->textTransformDelta(0, state->getRise(), &riseX, &riseY);
02115     p = s->getCString();
02116     len = s->getLength();
02117     while (len > 0) {
02118       n = font->getNextChar(p, len, &code,
02119                             u, (int)(sizeof(u) / sizeof(Unicode)), &uLen,
02120                             &dx, &dy, &originX, &originY);
02121       if (wMode) {
02122         dx *= state->getFontSize();
02123         dy = dy * state->getFontSize() + state->getCharSpace();
02124         if (n == 1 && *p == ' ') {
02125           dy += state->getWordSpace();
02126         }
02127       } else {
02128         dx = dx * state->getFontSize() + state->getCharSpace();
02129         if (n == 1 && *p == ' ') {
02130           dx += state->getWordSpace();
02131         }
02132         dx *= state->getHorizScaling();
02133         dy *= state->getFontSize();
02134       }
02135       state->textTransformDelta(dx, dy, &tdx, &tdy);
02136       originX *= state->getFontSize();
02137       originY *= state->getFontSize();
02138       state->textTransformDelta(originX, originY, &tOriginX, &tOriginY);
02139       out->drawChar(state, state->getCurX() + riseX, state->getCurY() + riseY,
02140                     tdx, tdy, tOriginX, tOriginY, code, u, uLen);
02141       state->shift(tdx, tdy);
02142       p += n;
02143       len -= n;
02144     }
02145 
02146   } else {
02147     dx = dy = 0;
02148     p = s->getCString();
02149     len = s->getLength();
02150     nChars = nSpaces = 0;
02151     while (len > 0) {
02152       n = font->getNextChar(p, len, &code,
02153                             u, (int)(sizeof(u) / sizeof(Unicode)), &uLen,
02154                             &dx2, &dy2, &originX, &originY);
02155       dx += dx2;
02156       dy += dy2;
02157       if (n == 1 && *p == ' ') {
02158         ++nSpaces;
02159       }
02160       ++nChars;
02161       p += n;
02162       len -= n;
02163     }
02164     if (wMode) {
02165       dx *= state->getFontSize();
02166       dy = dy * state->getFontSize()
02167            + nChars * state->getCharSpace()
02168            + nSpaces * state->getWordSpace();
02169     } else {
02170       dx = dx * state->getFontSize()
02171            + nChars * state->getCharSpace()
02172            + nSpaces * state->getWordSpace();
02173       dx *= state->getHorizScaling();
02174       dy *= state->getFontSize();
02175     }
02176     state->textTransformDelta(dx, dy, &tdx, &tdy);
02177     out->drawString(state, s);
02178     state->shift(tdx, tdy);
02179   }
02180 
02181   if (out->useDrawChar()) {
02182     out->endString(state);
02183   }
02184 
02185   updateLevel += 10 * s->getLength();
02186 }
02187 
02188 //------------------------------------------------------------------------
02189 // XObject operators
02190 //------------------------------------------------------------------------
02191 
02192 void Gfx::opXObject(Object args[], int numArgs) {
02193   Object obj1, obj2, obj3, refObj;
02194 #if OPI_SUPPORT
02195   Object opiDict;
02196 #endif
02197 
02198   if (!res->lookupXObject(args[0].getName(), &obj1)) {
02199     return;
02200   }
02201   if (!obj1.isStream()) {
02202     error(getPos(), "XObject '%s' is wrong type", args[0].getName());
02203     obj1.free();
02204     return;
02205   }
02206 #if OPI_SUPPORT
02207   obj1.streamGetDict()->lookup("OPI", &opiDict);
02208   if (opiDict.isDict()) {
02209     out->opiBegin(state, opiDict.getDict());
02210   }
02211 #endif
02212   obj1.streamGetDict()->lookup("Subtype", &obj2);
02213   if (obj2.isName("Image")) {
02214     res->lookupXObjectNF(args[0].getName(), &refObj);
02215     doImage(&refObj, obj1.getStream(), gFalse);
02216     refObj.free();
02217   } else if (obj2.isName("Form")) {
02218     doForm(&obj1);
02219   } else if (obj2.isName("PS")) {
02220     obj1.streamGetDict()->lookup("Level1", &obj3);
02221     out->psXObject(obj1.getStream(),
02222                    obj3.isStream() ? obj3.getStream() : (Stream *)NULL);
02223   } else if (obj2.isName()) {
02224     error(getPos(), "Unknown XObject subtype '%s'", obj2.getName());
02225   } else {
02226     error(getPos(), "XObject subtype is missing or wrong type");
02227   }
02228   obj2.free();
02229 #if OPI_SUPPORT
02230   if (opiDict.isDict()) {
02231     out->opiEnd(state, opiDict.getDict());
02232   }
02233   opiDict.free();
02234 #endif
02235   obj1.free();
02236 }
02237 
02238 void Gfx::doImage(Object *ref, Stream *str, GBool inlineImg) {
02239   Dict *dict;
02240   int width, height;
02241   int bits;
02242   GBool mask;
02243   GBool invert;
02244   GfxColorSpace *colorSpace;
02245   GfxImageColorMap *colorMap;
02246   Object maskObj;
02247   GBool haveMask;
02248   int maskColors[2*gfxColorMaxComps];
02249   Object obj1, obj2;
02250   int i;
02251 
02252   // get stream dict
02253   dict = str->getDict();
02254 
02255   // get size
02256   dict->lookup("Width", &obj1);
02257   if (obj1.isNull()) {
02258     obj1.free();
02259     dict->lookup("W", &obj1);
02260   }
02261   if (!obj1.isInt())
02262     goto err2;
02263   width = obj1.getInt();
02264   obj1.free();
02265   dict->lookup("Height", &obj1);
02266   if (obj1.isNull()) {
02267     obj1.free();
02268     dict->lookup("H", &obj1);
02269   }
02270   if (!obj1.isInt())
02271     goto err2;
02272   height = obj1.getInt();
02273   obj1.free();
02274 
02275   // image or mask?
02276   dict->lookup("ImageMask", &obj1);
02277   if (obj1.isNull()) {
02278     obj1.free();
02279     dict->lookup("IM", &obj1);
02280   }
02281   mask = gFalse;
02282   if (obj1.isBool())
02283     mask = obj1.getBool();
02284   else if (!obj1.isNull())
02285     goto err2;
02286   obj1.free();
02287 
02288   // bit depth
02289   dict->lookup("BitsPerComponent", &obj1);
02290   if (obj1.isNull()) {
02291     obj1.free();
02292     dict->lookup("BPC", &obj1);
02293   }
02294   if (!obj1.isInt())
02295     goto err2;
02296   bits = obj1.getInt();
02297   obj1.free();
02298 
02299   // display a mask
02300   if (mask) {
02301 
02302     // check for inverted mask
02303     if (bits != 1)
02304       goto err1;
02305     invert = gFalse;
02306     dict->lookup("Decode", &obj1);
02307     if (obj1.isNull()) {
02308       obj1.free();
02309       dict->lookup("D", &obj1);
02310     }
02311     if (obj1.isArray()) {
02312       obj1.arrayGet(0, &obj2);
02313       if (obj2.isInt() && obj2.getInt() == 1)
02314         invert = gTrue;
02315       obj2.free();
02316     } else if (!obj1.isNull()) {
02317       goto err2;
02318     }
02319     obj1.free();
02320 
02321     // draw it
02322     out->drawImageMask(state, ref, str, width, height, invert, inlineImg);
02323 
02324   } else {
02325 
02326     // get color space and color map
02327     dict->lookup("ColorSpace", &obj1);
02328     if (obj1.isNull()) {
02329       obj1.free();
02330       dict->lookup("CS", &obj1);
02331     }
02332     if (obj1.isName()) {
02333       res->lookupColorSpace(obj1.getName(), &obj2);
02334       if (!obj2.isNull()) {
02335         obj1.free();
02336         obj1 = obj2;
02337       } else {
02338         obj2.free();
02339       }
02340     }
02341     colorSpace = GfxColorSpace::parse(&obj1);
02342     obj1.free();
02343     if (!colorSpace) {
02344       goto err1;
02345     }
02346     dict->lookup("Decode", &obj1);
02347     if (obj1.isNull()) {
02348       obj1.free();
02349       dict->lookup("D", &obj1);
02350     }
02351     colorMap = new GfxImageColorMap(bits, &obj1, colorSpace);
02352     obj1.free();
02353     if (!colorMap->isOk()) {
02354       delete colorMap;
02355       goto err1;
02356     }
02357 
02358     // get the mask
02359     haveMask = gFalse;
02360     dict->lookup("Mask", &maskObj);
02361     if (maskObj.isArray()) {
02362       for (i = 0; i < maskObj.arrayGetLength(); ++i) {
02363         maskObj.arrayGet(i, &obj1);
02364         maskColors[i] = obj1.getInt();
02365         obj1.free();
02366       }
02367       haveMask = gTrue;
02368     }
02369 
02370     // draw it
02371     out->drawImage(state, ref, str, width, height, colorMap,
02372                    haveMask ? maskColors : (int *)NULL,  inlineImg);
02373     delete colorMap;
02374 
02375     maskObj.free();
02376   }
02377 
02378   if ((i = width * height) > 1000) {
02379     i = 1000;
02380   }
02381   updateLevel += i;
02382 
02383   return;
02384 
02385  err2:
02386   obj1.free();
02387  err1:
02388   error(getPos(), "Bad image parameters");
02389 }
02390 
02391 void Gfx::doForm(Object *str) {
02392   Dict *dict;
02393   Object matrixObj, bboxObj;
02394   fouble m[6], bbox[6];
02395   Object resObj;
02396   Dict *resDict;
02397   Object obj1;
02398   int i;
02399 
02400   // get stream dict
02401   dict = str->streamGetDict();
02402 
02403   // check form type
02404   dict->lookup("FormType", &obj1);
02405   if (!(obj1.isInt() && obj1.getInt() == 1)) {
02406     error(getPos(), "Unknown form type");
02407   }
02408   obj1.free();
02409 
02410   // get bounding box
02411   dict->lookup("BBox", &bboxObj);
02412   if (!bboxObj.isArray()) {
02413     matrixObj.free();
02414     bboxObj.free();
02415     error(getPos(), "Bad form bounding box");
02416     return;
02417   }
02418   for (i = 0; i < 4; ++i) {
02419     bboxObj.arrayGet(i, &obj1);
02420     bbox[i] = obj1.getNum();
02421     obj1.free();
02422   }
02423   bboxObj.free();
02424 
02425   // get matrix
02426   dict->lookup("Matrix", &matrixObj);
02427   if (matrixObj.isArray()) {
02428     for (i = 0; i < 6; ++i) {
02429       matrixObj.arrayGet(i, &obj1);
02430       m[i] = obj1.getNum();
02431       obj1.free();
02432     }
02433   } else {
02434     m[0] = 1; m[1] = 0;
02435     m[2] = 0; m[3] = 1;
02436     m[4] = 0; m[5] = 0;
02437   }
02438   matrixObj.free();
02439 
02440   // get resources
02441   dict->lookup("Resources", &resObj);
02442   resDict = resObj.isDict() ? resObj.getDict() : (Dict *)NULL;
02443 
02444   // draw it
02445   doForm1(str, resDict, m, bbox);
02446 
02447   resObj.free();
02448 }
02449 
02450 void Gfx::doAnnot(Object *str, fouble xMin, fouble yMin,
02451                   fouble xMax, fouble yMax) {
02452   Dict *dict, *resDict;
02453   Object matrixObj, bboxObj, resObj;
02454   Object obj1;
02455   fouble m[6], bbox[6], ictm[6];
02456   fouble *ctm;
02457   fouble formX0, formY0, formX1, formY1;
02458   fouble annotX0, annotY0, annotX1, annotY1;
02459   fouble det, x, y, sx, sy;
02460   int i;
02461 
02462   // get stream dict
02463   dict = str->streamGetDict();
02464 
02465   // get the form bounding box
02466   dict->lookup("BBox", &bboxObj);
02467   if (!bboxObj.isArray()) {
02468     bboxObj.free();
02469     error(getPos(), "Bad form bounding box");
02470     return;
02471   }
02472   for (i = 0; i < 4; ++i) {
02473     bboxObj.arrayGet(i, &obj1);
02474     bbox[i] = obj1.getNum();
02475     obj1.free();
02476   }
02477   bboxObj.free();
02478 
02479   // get the form matrix
02480   dict->lookup("Matrix", &matrixObj);
02481   if (matrixObj.isArray()) {
02482     for (i = 0; i < 6; ++i) {
02483       matrixObj.arrayGet(i, &obj1);
02484       m[i] = obj1.getNum();
02485       obj1.free();
02486     }
02487   } else {
02488     m[0] = 1; m[1] = 0;
02489     m[2] = 0; m[3] = 1;
02490     m[4] = 0; m[5] = 0;
02491   }
02492   matrixObj.free();
02493 
02494   // transform the form bbox from form space to user space
02495   formX0 = bbox[0] * m[0] + bbox[1] * m[2] + m[4];
02496   formY0 = bbox[0] * m[1] + bbox[1] * m[3] + m[5];
02497   formX1 = bbox[2] * m[0] + bbox[3] * m[2] + m[4];
02498   formY1 = bbox[2] * m[1] + bbox[3] * m[3] + m[5];
02499 
02500   // transform the annotation bbox from default user space to user
02501   // space: (bbox * baseMatrix) * iCTM
02502   ctm = state->getCTM();
02503   det = 1 / (ctm[0] * ctm[3] - ctm[1] * ctm[2]);
02504   ictm[0] = ctm[3] * det;
02505   ictm[1] = -ctm[1] * det;
02506   ictm[2] = -ctm[2] * det;
02507   ictm[3] = ctm[0] * det;
02508   ictm[4] = (ctm[2] * ctm[5] - ctm[3] * ctm[4]) * det;
02509   ictm[5] = (ctm[1] * ctm[4] - ctm[0] * ctm[5]) * det;
02510   x = baseMatrix[0] * xMin + baseMatrix[2] * yMin + baseMatrix[4];
02511   y = baseMatrix[1] * xMin + baseMatrix[3] * yMin + baseMatrix[5];
02512   annotX0 = ictm[0] * x + ictm[2] * y + ictm[4];
02513   annotY0 = ictm[1] * x + ictm[3] * y + ictm[5];
02514   x = baseMatrix[0] * xMax + baseMatrix[2] * yMax + baseMatrix[4];
02515   y = baseMatrix[1] * xMax + baseMatrix[3] * yMax + baseMatrix[5];
02516   annotX1 = ictm[0] * x + ictm[2] * y + ictm[4];
02517   annotY1 = ictm[1] * x + ictm[3] * y + ictm[5];
02518 
02519   // swap min/max coords
02520   if (formX0 > formX1) {
02521     x = formX0; formX0 = formX1; formX1 = x;
02522   }
02523   if (formY0 > formY1) {
02524     y = formY0; formY0 = formY1; formY1 = y;
02525   }
02526   if (annotX0 > annotX1) {
02527     x = annotX0; annotX0 = annotX1; annotX1 = x;
02528   }
02529   if (annotY0 > annotY1) {
02530     y = annotY0; annotY0 = annotY1; annotY1 = y;
02531   }
02532 
02533   // scale the form to fit the annotation bbox
02534   if (formX1 == formX0) {
02535     // this shouldn't happen
02536     sx = 1;
02537   } else {
02538     sx = (annotX1 - annotX0) / (formX1 - formX0);
02539   }
02540   if (formY1 == formY0) {
02541     // this shouldn't happen
02542     sy = 1;
02543   } else {
02544     sy = (annotY1 - annotY0) / (formY1 - formY0);
02545   }
02546   m[0] *= sx;
02547   m[2] *= sx;
02548   m[4] = (m[4] - formX0) * sx + annotX0;
02549   m[1] *= sy;
02550   m[3] *= sy;
02551   m[5] = (m[5] - formY0) * sy + annotY0;
02552 
02553   // get resources
02554   dict->lookup("Resources", &resObj);
02555   resDict = resObj.isDict() ? resObj.getDict() : (Dict *)NULL;
02556 
02557   // draw it
02558   doForm1(str, resDict, m, bbox);
02559 
02560   resObj.free();
02561   bboxObj.free();
02562 }
02563 
02564 void Gfx::doForm1(Object *str, Dict *resDict, fouble *matrix, fouble *bbox) {
02565   Parser *oldParser;
02566   fouble oldBaseMatrix[6];
02567   int i;
02568 
02569   // push new resources on stack
02570   pushResources(resDict);
02571 
02572   // save current graphics state
02573   out->saveState(state);
02574   state = state->save();
02575 
02576   // save current parser
02577   oldParser = parser;
02578 
02579   // set form transformation matrix
02580   state->concatCTM(matrix[0], matrix[1], matrix[2],
02581                    matrix[3], matrix[4], matrix[5]);
02582   out->updateCTM(state, matrix[0], matrix[1], matrix[2],
02583                  matrix[3], matrix[4], matrix[5]);
02584 
02585   // set new base matrix
02586   for (i = 0; i < 6; ++i) {
02587     oldBaseMatrix[i] = baseMatrix[i];
02588     baseMatrix[i] = state->getCTM()[i];
02589   }
02590 
02591   // set form bounding box
02592   state->moveTo(bbox[0], bbox[1]);
02593   state->lineTo(bbox[2], bbox[1]);
02594   state->lineTo(bbox[2], bbox[3]);
02595   state->lineTo(bbox[0], bbox[3]);
02596   state->closePath();
02597   state->clip();
02598   out->clip(state);
02599   state->clearPath();
02600 
02601   // draw the form
02602   display(str, gFalse);
02603 
02604   // restore base matrix
02605   for (i = 0; i < 6; ++i) {
02606     baseMatrix[i] = oldBaseMatrix[i];
02607   }
02608 
02609   // restore parser
02610   parser = oldParser;
02611 
02612   // restore graphics state
02613   state = state->restore();
02614   out->restoreState(state);
02615 
02616   // pop resource stack
02617   popResources();
02618 
02619   return;
02620 }
02621 
02622 void Gfx::pushResources(Dict *resDict) {
02623   res = new GfxResources(xref, resDict, res);
02624 }
02625 
02626 void Gfx::popResources() {
02627   GfxResources *resPtr;
02628 
02629   resPtr = res->getNext();
02630   delete res;
02631   res = resPtr;
02632 }
02633 
02634 //------------------------------------------------------------------------
02635 // in-line image operators
02636 //------------------------------------------------------------------------
02637 
02638 void Gfx::opBeginImage(Object args[], int numArgs) {
02639   Stream *str;
02640   int c1, c2;
02641 
02642   // build dict/stream
02643   str = buildImageStream();
02644 
02645   // display the image
02646   if (str) {
02647     doImage(NULL, str, gTrue);
02648   
02649     // skip 'EI' tag
02650     c1 = str->getBaseStream()->getChar();
02651     c2 = str->getBaseStream()->getChar();
02652     while (!(c1 == 'E' && c2 == 'I') && c2 != EOF) {
02653       c1 = c2;
02654       c2 = str->getBaseStream()->getChar();
02655     }
02656     delete str;
02657   }
02658 }
02659 
02660 Stream *Gfx::buildImageStream() {
02661   Object dict;
02662   Object obj;
02663   char *key;
02664   Stream *str;
02665 
02666   // build dictionary
02667   dict.initDict(xref);
02668   parser->getObj(&obj);
02669   while (!obj.isCmd("ID") && !obj.isEOF()) {
02670     if (!obj.isName()) {
02671       error(getPos(), "Inline image dictionary key must be a name object");
02672       obj.free();
02673     } else {
02674       key = copyString(obj.getName());
02675       obj.free();
02676       parser->getObj(&obj);
02677       if (obj.isEOF() || obj.isError()) {
02678         gfree(key);
02679         break;
02680       }
02681       dict.dictAdd(key, &obj);
02682     }
02683     parser->getObj(&obj);
02684   }
02685   if (obj.isEOF()) {
02686     error(getPos(), "End of file in inline image");
02687     obj.free();
02688     dict.free();
02689     return NULL;
02690   }
02691   obj.free();
02692 
02693   // make stream
02694   str = new EmbedStream(parser->getStream(), &dict);
02695   str = str->addFilters(&dict);
02696 
02697   return str;
02698 }
02699 
02700 void Gfx::opImageData(Object args[], int numArgs) {
02701   error(getPos(), "Internal: got 'ID' operator");
02702 }
02703 
02704 void Gfx::opEndImage(Object args[], int numArgs) {
02705   error(getPos(), "Internal: got 'EI' operator");
02706 }
02707 
02708 //------------------------------------------------------------------------
02709 // type 3 font operators
02710 //------------------------------------------------------------------------
02711 
02712 void Gfx::opSetCharWidth(Object args[], int numArgs) {
02713   out->type3D0(state, args[0].getNum(), args[1].getNum());
02714 }
02715 
02716 void Gfx::opSetCacheDevice(Object args[], int numArgs) {
02717   out->type3D1(state, args[0].getNum(), args[1].getNum(),
02718                args[2].getNum(), args[3].getNum(),
02719                args[4].getNum(), args[5].getNum());
02720 }
02721 
02722 //------------------------------------------------------------------------
02723 // compatibility operators
02724 //------------------------------------------------------------------------
02725 
02726 void Gfx::opBeginIgnoreUndef(Object args[], int numArgs) {
02727   ++ignoreUndef;
02728 }
02729 
02730 void Gfx::opEndIgnoreUndef(Object args[], int numArgs) {
02731   if (ignoreUndef > 0)
02732     --ignoreUndef;
02733 }
02734 
02735 //------------------------------------------------------------------------
02736 // marked content operators
02737 //------------------------------------------------------------------------
02738 
02739 void Gfx::opBeginMarkedContent(Object args[], int numArgs) {
02740   if (printCommands) {
02741     printf("  marked content: %s ", args[0].getName());
02742     if (numArgs == 2)
02743       args[2].print(stdout);
02744     printf("\n");
02745     fflush(stdout);
02746   }
02747 }
02748 
02749 void Gfx::opEndMarkedContent(Object args[], int numArgs) {
02750 }
02751 
02752 void Gfx::opMarkPoint(Object args[], int numArgs) {
02753   if (printCommands) {
02754     printf("  mark point: %s ", args[0].getName());
02755     if (numArgs == 2)
02756       args[2].print(stdout);
02757     printf("\n");
02758     fflush(stdout);
02759   }
02760 }

Generated on Sat Nov 5 16:18:14 2005 for OPIE by  doxygen 1.4.2