source: trunk/poppler/mypoppler/poppler/Gfx.cc @ 2

Last change on this file since 2 was 2, checked in by Eugene Romanenko, 16 years ago

First import

File size: 93.9 KB
Line 
1//========================================================================
2//
3// Gfx.cc
4//
5// Copyright 1996-2003 Glyph & Cog, LLC
6//
7//========================================================================
8
9#include <config.h>
10
11#ifdef USE_GCC_PRAGMAS
12#pragma implementation
13#endif
14
15#include <stdlib.h>
16#include <stdio.h>
17#include <stddef.h>
18#include <string.h>
19#include <math.h>
20#include "goo/gmem.h"
21#include "goo/GooTimer.h"
22#include "goo/GooHash.h"
23#include "GlobalParams.h"
24#include "CharTypes.h"
25#include "Object.h"
26#include "Array.h"
27#include "Dict.h"
28#include "Stream.h"
29#include "Lexer.h"
30#include "Parser.h"
31#include "GfxFont.h"
32#include "GfxState.h"
33#include "OutputDev.h"
34#include "Page.h"
35#include "Error.h"
36#include "Gfx.h"
37#include "ProfileData.h"
38#include "UGooString.h"
39
40// the MSVC math.h doesn't define this
41#ifndef M_PI
42#define M_PI 3.14159265358979323846
43#endif
44
45//------------------------------------------------------------------------
46// constants
47//------------------------------------------------------------------------
48
49// Max recursive depth for a function shading fill.
50#define functionMaxDepth 6
51
52// Max delta allowed in any color component for a function shading fill.
53#define functionColorDelta (dblToCol(1 / 256.0))
54
55// Max number of splits along the t axis for an axial shading fill.
56#define axialMaxSplits 256
57
58// Max delta allowed in any color component for an axial shading fill.
59#define axialColorDelta (dblToCol(1 / 256.0))
60
61// Max number of splits along the t axis for a radial shading fill.
62#define radialMaxSplits 256
63
64// Max delta allowed in any color component for a radial shading fill.
65#define radialColorDelta (dblToCol(1 / 256.0))
66
67// Max recursive depth for a Gouraud triangle shading fill.
68#define gouraudMaxDepth 4
69
70// Max delta allowed in any color component for a Gouraud triangle
71// shading fill.
72#define gouraudColorDelta (dblToCol(1 / 256.0))
73
74// Max recursive depth for a patch mesh shading fill.
75#define patchMaxDepth 6
76
77// Max delta allowed in any color component for a patch mesh shading
78// fill.
79#define patchColorDelta (dblToCol(1 / 256.0))
80
81//------------------------------------------------------------------------
82// Operator table
83//------------------------------------------------------------------------
84
85#ifdef WIN32 // this works around a bug in the VC7 compiler
86#  pragma optimize("",off)
87#endif
88
89Operator Gfx::opTab[] = {
90  {"\"",  3, {tchkNum,    tchkNum,    tchkString},
91          &Gfx::opMoveSetShowText},
92  {"'",   1, {tchkString},
93          &Gfx::opMoveShowText},
94  {"B",   0, {tchkNone},
95          &Gfx::opFillStroke},
96  {"B*",  0, {tchkNone},
97          &Gfx::opEOFillStroke},
98  {"BDC", 2, {tchkName,   tchkProps},
99          &Gfx::opBeginMarkedContent},
100  {"BI",  0, {tchkNone},
101          &Gfx::opBeginImage},
102  {"BMC", 1, {tchkName},
103          &Gfx::opBeginMarkedContent},
104  {"BT",  0, {tchkNone},
105          &Gfx::opBeginText},
106  {"BX",  0, {tchkNone},
107          &Gfx::opBeginIgnoreUndef},
108  {"CS",  1, {tchkName},
109          &Gfx::opSetStrokeColorSpace},
110  {"DP",  2, {tchkName,   tchkProps},
111          &Gfx::opMarkPoint},
112  {"Do",  1, {tchkName},
113          &Gfx::opXObject},
114  {"EI",  0, {tchkNone},
115          &Gfx::opEndImage},
116  {"EMC", 0, {tchkNone},
117          &Gfx::opEndMarkedContent},
118  {"ET",  0, {tchkNone},
119          &Gfx::opEndText},
120  {"EX",  0, {tchkNone},
121          &Gfx::opEndIgnoreUndef},
122  {"F",   0, {tchkNone},
123          &Gfx::opFill},
124  {"G",   1, {tchkNum},
125          &Gfx::opSetStrokeGray},
126  {"ID",  0, {tchkNone},
127          &Gfx::opImageData},
128  {"J",   1, {tchkInt},
129          &Gfx::opSetLineCap},
130  {"K",   4, {tchkNum,    tchkNum,    tchkNum,    tchkNum},
131          &Gfx::opSetStrokeCMYKColor},
132  {"M",   1, {tchkNum},
133          &Gfx::opSetMiterLimit},
134  {"MP",  1, {tchkName},
135          &Gfx::opMarkPoint},
136  {"Q",   0, {tchkNone},
137          &Gfx::opRestore},
138  {"RG",  3, {tchkNum,    tchkNum,    tchkNum},
139          &Gfx::opSetStrokeRGBColor},
140  {"S",   0, {tchkNone},
141          &Gfx::opStroke},
142  {"SC",  -4, {tchkNum,   tchkNum,    tchkNum,    tchkNum},
143          &Gfx::opSetStrokeColor},
144  {"SCN", -5, {tchkSCN,   tchkSCN,    tchkSCN,    tchkSCN,
145               tchkSCN},
146          &Gfx::opSetStrokeColorN},
147  {"T*",  0, {tchkNone},
148          &Gfx::opTextNextLine},
149  {"TD",  2, {tchkNum,    tchkNum},
150          &Gfx::opTextMoveSet},
151  {"TJ",  1, {tchkArray},
152          &Gfx::opShowSpaceText},
153  {"TL",  1, {tchkNum},
154          &Gfx::opSetTextLeading},
155  {"Tc",  1, {tchkNum},
156          &Gfx::opSetCharSpacing},
157  {"Td",  2, {tchkNum,    tchkNum},
158          &Gfx::opTextMove},
159  {"Tf",  2, {tchkName,   tchkNum},
160          &Gfx::opSetFont},
161  {"Tj",  1, {tchkString},
162          &Gfx::opShowText},
163  {"Tm",  6, {tchkNum,    tchkNum,    tchkNum,    tchkNum,
164              tchkNum,    tchkNum},
165          &Gfx::opSetTextMatrix},
166  {"Tr",  1, {tchkInt},
167          &Gfx::opSetTextRender},
168  {"Ts",  1, {tchkNum},
169          &Gfx::opSetTextRise},
170  {"Tw",  1, {tchkNum},
171          &Gfx::opSetWordSpacing},
172  {"Tz",  1, {tchkNum},
173          &Gfx::opSetHorizScaling},
174  {"W",   0, {tchkNone},
175          &Gfx::opClip},
176  {"W*",  0, {tchkNone},
177          &Gfx::opEOClip},
178  {"b",   0, {tchkNone},
179          &Gfx::opCloseFillStroke},
180  {"b*",  0, {tchkNone},
181          &Gfx::opCloseEOFillStroke},
182  {"c",   6, {tchkNum,    tchkNum,    tchkNum,    tchkNum,
183              tchkNum,    tchkNum},
184          &Gfx::opCurveTo},
185  {"cm",  6, {tchkNum,    tchkNum,    tchkNum,    tchkNum,
186              tchkNum,    tchkNum},
187          &Gfx::opConcat},
188  {"cs",  1, {tchkName},
189          &Gfx::opSetFillColorSpace},
190  {"d",   2, {tchkArray,  tchkNum},
191          &Gfx::opSetDash},
192  {"d0",  2, {tchkNum,    tchkNum},
193          &Gfx::opSetCharWidth},
194  {"d1",  6, {tchkNum,    tchkNum,    tchkNum,    tchkNum,
195              tchkNum,    tchkNum},
196          &Gfx::opSetCacheDevice},
197  {"f",   0, {tchkNone},
198          &Gfx::opFill},
199  {"f*",  0, {tchkNone},
200          &Gfx::opEOFill},
201  {"g",   1, {tchkNum},
202          &Gfx::opSetFillGray},
203  {"gs",  1, {tchkName},
204          &Gfx::opSetExtGState},
205  {"h",   0, {tchkNone},
206          &Gfx::opClosePath},
207  {"i",   1, {tchkNum},
208          &Gfx::opSetFlat},
209  {"j",   1, {tchkInt},
210          &Gfx::opSetLineJoin},
211  {"k",   4, {tchkNum,    tchkNum,    tchkNum,    tchkNum},
212          &Gfx::opSetFillCMYKColor},
213  {"l",   2, {tchkNum,    tchkNum},
214          &Gfx::opLineTo},
215  {"m",   2, {tchkNum,    tchkNum},
216          &Gfx::opMoveTo},
217  {"n",   0, {tchkNone},
218          &Gfx::opEndPath},
219  {"q",   0, {tchkNone},
220          &Gfx::opSave},
221  {"re",  4, {tchkNum,    tchkNum,    tchkNum,    tchkNum},
222          &Gfx::opRectangle},
223  {"rg",  3, {tchkNum,    tchkNum,    tchkNum},
224          &Gfx::opSetFillRGBColor},
225  {"ri",  1, {tchkName},
226          &Gfx::opSetRenderingIntent},
227  {"s",   0, {tchkNone},
228          &Gfx::opCloseStroke},
229  {"sc",  -4, {tchkNum,   tchkNum,    tchkNum,    tchkNum},
230          &Gfx::opSetFillColor},
231  {"scn", -5, {tchkSCN,   tchkSCN,    tchkSCN,    tchkSCN,
232               tchkSCN},
233          &Gfx::opSetFillColorN},
234  {"sh",  1, {tchkName},
235          &Gfx::opShFill},
236  {"v",   4, {tchkNum,    tchkNum,    tchkNum,    tchkNum},
237          &Gfx::opCurveTo1},
238  {"w",   1, {tchkNum},
239          &Gfx::opSetLineWidth},
240  {"y",   4, {tchkNum,    tchkNum,    tchkNum,    tchkNum},
241          &Gfx::opCurveTo2},
242};
243
244#ifdef WIN32 // this works around a bug in the VC7 compiler
245#  pragma optimize("",on)
246#endif
247
248#define numOps (sizeof(opTab) / sizeof(Operator))
249
250//------------------------------------------------------------------------
251// GfxResources
252//------------------------------------------------------------------------
253
254GfxResources::GfxResources(XRef *xref, Dict *resDict, GfxResources *nextA) {
255  Object obj1, obj2;
256  Ref r;
257
258  if (resDict) {
259
260    // build font dictionary
261    fonts = NULL;
262    resDict->lookupNF("Font", &obj1);
263    if (obj1.isRef()) {
264      obj1.fetch(xref, &obj2);
265      if (obj2.isDict()) {
266        r = obj1.getRef();
267        fonts = new GfxFontDict(xref, &r, obj2.getDict());
268      }
269      obj2.free();
270    } else if (obj1.isDict()) {
271      fonts = new GfxFontDict(xref, NULL, obj1.getDict());
272    }
273    obj1.free();
274
275    // get XObject dictionary
276    resDict->lookup("XObject", &xObjDict);
277
278    // get color space dictionary
279    resDict->lookup("ColorSpace", &colorSpaceDict);
280
281    // get pattern dictionary
282    resDict->lookup("Pattern", &patternDict);
283
284    // get shading dictionary
285    resDict->lookup("Shading", &shadingDict);
286
287    // get graphics state parameter dictionary
288    resDict->lookup("ExtGState", &gStateDict);
289
290  } else {
291    fonts = NULL;
292    xObjDict.initNull();
293    colorSpaceDict.initNull();
294    patternDict.initNull();
295    shadingDict.initNull();
296    gStateDict.initNull();
297  }
298
299  next = nextA;
300}
301
302GfxResources::~GfxResources() {
303  if (fonts) {
304    delete fonts;
305  }
306  xObjDict.free();
307  colorSpaceDict.free();
308  patternDict.free();
309  shadingDict.free();
310  gStateDict.free();
311}
312
313GfxFont *GfxResources::lookupFont(char *name) {
314  GfxFont *font;
315  GfxResources *resPtr;
316
317  for (resPtr = this; resPtr; resPtr = resPtr->next) {
318    if (resPtr->fonts) {
319      if ((font = resPtr->fonts->lookup(name)))
320        return font;
321    }
322  }
323  error(-1, "Unknown font tag '%s'", name);
324  return NULL;
325}
326
327GBool GfxResources::lookupXObject(char *name, Object *obj) {
328  GfxResources *resPtr;
329
330  for (resPtr = this; resPtr; resPtr = resPtr->next) {
331    if (resPtr->xObjDict.isDict()) {
332      if (!resPtr->xObjDict.dictLookup(name, obj)->isNull())
333        return gTrue;
334      obj->free();
335    }
336  }
337  error(-1, "XObject '%s' is unknown", name);
338  return gFalse;
339}
340
341GBool GfxResources::lookupXObjectNF(char *name, Object *obj) {
342  GfxResources *resPtr;
343
344  for (resPtr = this; resPtr; resPtr = resPtr->next) {
345    if (resPtr->xObjDict.isDict()) {
346      if (!resPtr->xObjDict.dictLookupNF(name, obj)->isNull())
347        return gTrue;
348      obj->free();
349    }
350  }
351  error(-1, "XObject '%s' is unknown", name);
352  return gFalse;
353}
354
355void GfxResources::lookupColorSpace(char *name, Object *obj) {
356  GfxResources *resPtr;
357
358  for (resPtr = this; resPtr; resPtr = resPtr->next) {
359    if (resPtr->colorSpaceDict.isDict()) {
360      if (!resPtr->colorSpaceDict.dictLookup(name, obj)->isNull()) {
361        return;
362      }
363      obj->free();
364    }
365  }
366  obj->initNull();
367}
368
369GfxPattern *GfxResources::lookupPattern(char *name) {
370  GfxResources *resPtr;
371  GfxPattern *pattern;
372  Object obj;
373
374  for (resPtr = this; resPtr; resPtr = resPtr->next) {
375    if (resPtr->patternDict.isDict()) {
376      if (!resPtr->patternDict.dictLookup(name, &obj)->isNull()) {
377        pattern = GfxPattern::parse(&obj);
378        obj.free();
379        return pattern;
380      }
381      obj.free();
382    }
383  }
384  error(-1, "Unknown pattern '%s'", name);
385  return NULL;
386}
387
388GfxShading *GfxResources::lookupShading(char *name) {
389  GfxResources *resPtr;
390  GfxShading *shading;
391  Object obj;
392
393  for (resPtr = this; resPtr; resPtr = resPtr->next) {
394    if (resPtr->shadingDict.isDict()) {
395      if (!resPtr->shadingDict.dictLookup(name, &obj)->isNull()) {
396        shading = GfxShading::parse(&obj);
397        obj.free();
398        return shading;
399      }
400      obj.free();
401    }
402  }
403  error(-1, "Unknown shading '%s'", name);
404  return NULL;
405}
406
407GBool GfxResources::lookupGState(char *name, Object *obj) {
408  GfxResources *resPtr;
409
410  for (resPtr = this; resPtr; resPtr = resPtr->next) {
411    if (resPtr->gStateDict.isDict()) {
412      if (!resPtr->gStateDict.dictLookup(name, obj)->isNull()) {
413        return gTrue;
414      }
415      obj->free();
416    }
417  }
418  error(-1, "ExtGState '%s' is unknown", name);
419  return gFalse;
420}
421
422//------------------------------------------------------------------------
423// Gfx
424//------------------------------------------------------------------------
425
426Gfx::Gfx(XRef *xrefA, OutputDev *outA, int pageNum, Dict *resDict,
427         double hDPI, double vDPI, PDFRectangle *box,
428         PDFRectangle *cropBox, int rotate,
429         GBool (*abortCheckCbkA)(void *data),
430         void *abortCheckCbkDataA) {
431  int i;
432
433  xref = xrefA;
434  subPage = gFalse;
435  printCommands = globalParams->getPrintCommands();
436  profileCommands = globalParams->getProfileCommands();
437
438  // start the resource stack
439  res = new GfxResources(xref, resDict, NULL);
440
441  // initialize
442  out = outA;
443  state = new GfxState(hDPI, vDPI, box, rotate, out->upsideDown());
444  fontChanged = gFalse;
445  clip = clipNone;
446  ignoreUndef = 0;
447  out->startPage(pageNum, state);
448  out->setDefaultCTM(state->getCTM());
449  out->updateAll(state);
450  for (i = 0; i < 6; ++i) {
451    baseMatrix[i] = state->getCTM()[i];
452  }
453  formDepth = 0;
454  abortCheckCbk = abortCheckCbkA;
455  abortCheckCbkData = abortCheckCbkDataA;
456
457  // set crop box
458  if (cropBox) {
459    state->moveTo(cropBox->x1, cropBox->y1);
460    state->lineTo(cropBox->x2, cropBox->y1);
461    state->lineTo(cropBox->x2, cropBox->y2);
462    state->lineTo(cropBox->x1, cropBox->y2);
463    state->closePath();
464    state->clip();
465    out->clip(state);
466    state->clearPath();
467  }
468}
469
470Gfx::Gfx(XRef *xrefA, OutputDev *outA, Dict *resDict,
471         PDFRectangle *box, PDFRectangle *cropBox,
472         GBool (*abortCheckCbkA)(void *data),
473         void *abortCheckCbkDataA) {
474  int i;
475
476  xref = xrefA;
477  subPage = gTrue;
478  printCommands = globalParams->getPrintCommands();
479
480  // start the resource stack
481  res = new GfxResources(xref, resDict, NULL);
482
483  // initialize
484  out = outA;
485  state = new GfxState(72, 72, box, 0, gFalse);
486  fontChanged = gFalse;
487  clip = clipNone;
488  ignoreUndef = 0;
489  for (i = 0; i < 6; ++i) {
490    baseMatrix[i] = state->getCTM()[i];
491  }
492  formDepth = 0;
493  abortCheckCbk = abortCheckCbkA;
494  abortCheckCbkData = abortCheckCbkDataA;
495
496  // set crop box
497  if (cropBox) {
498    state->moveTo(cropBox->x1, cropBox->y1);
499    state->lineTo(cropBox->x2, cropBox->y1);
500    state->lineTo(cropBox->x2, cropBox->y2);
501    state->lineTo(cropBox->x1, cropBox->y2);
502    state->closePath();
503    state->clip();
504    out->clip(state);
505    state->clearPath();
506  }
507}
508
509Gfx::~Gfx() {
510  while (state->hasSaves()) {
511    restoreState();
512  }
513  if (!subPage) {
514    out->endPage();
515  }
516  while (res) {
517    popResources();
518  }
519  if (state) {
520    delete state;
521  }
522}
523
524void Gfx::display(Object *obj, GBool topLevel) {
525  Object obj2;
526  int i;
527
528  if (obj->isArray()) {
529    for (i = 0; i < obj->arrayGetLength(); ++i) {
530      obj->arrayGet(i, &obj2);
531      if (!obj2.isStream()) {
532        error(-1, "Weird page contents");
533        obj2.free();
534        return;
535      }
536      obj2.free();
537    }
538  } else if (!obj->isStream()) {
539    error(-1, "Weird page contents");
540    return;
541  }
542  parser = new Parser(xref, new Lexer(xref, obj));
543  go(topLevel);
544  delete parser;
545  parser = NULL;
546}
547
548void Gfx::go(GBool topLevel) {
549  Object obj;
550  Object args[maxArgs];
551  int numArgs, i;
552  int lastAbortCheck;
553#ifdef HAVE_GETTIMEOFDAY
554  GooTimer *timer;
555#endif
556
557  // scan a sequence of objects
558  updateLevel = lastAbortCheck = 0;
559  numArgs = 0;
560  parser->getObj(&obj);
561  while (!obj.isEOF()) {
562
563    // got a command - execute it
564    if (obj.isCmd()) {
565      if (printCommands) {
566        obj.print(stdout);
567        for (i = 0; i < numArgs; ++i) {
568          printf(" ");
569          args[i].print(stdout);
570        }
571        printf("\n");
572        fflush(stdout);
573      }
574#ifdef HAVE_GETTIMEOFDAY
575      if (profileCommands) 
576        timer = new GooTimer ();
577#endif
578
579      // Run the operation
580      execOp(&obj, args, numArgs);
581
582#ifdef HAVE_GETTIMEOFDAY
583      // Update the profile information
584      if (profileCommands) {
585        GooHash *hash;
586
587        hash = out->getProfileHash ();
588        if (hash) {
589          GooString *cmd_g;
590          ProfileData *data_p;
591
592          cmd_g = new GooString (obj.getCmd());
593          data_p = (ProfileData *)hash->lookup (cmd_g);
594          if (data_p == NULL) {
595            data_p = new ProfileData();
596            hash->add (cmd_g, data_p);
597          }
598         
599          data_p->addElement (timer->getElapsed ());
600        }
601        delete (timer);
602      }
603#endif
604      obj.free();
605      for (i = 0; i < numArgs; ++i)
606        args[i].free();
607      numArgs = 0;
608
609      // periodically update display
610      if (++updateLevel >= 20000) {
611        out->dump();
612        updateLevel = 0;
613      }
614
615      // check for an abort
616      if (abortCheckCbk) {
617        if (updateLevel - lastAbortCheck > 10) {
618          if ((*abortCheckCbk)(abortCheckCbkData)) {
619            break;
620          }
621          lastAbortCheck = updateLevel;
622        }
623      }
624
625    // got an argument - save it
626    } else if (numArgs < maxArgs) {
627      args[numArgs++] = obj;
628
629    // too many arguments - something is wrong
630    } else {
631      error(getPos(), "Too many args in content stream");
632      if (printCommands) {
633        printf("throwing away arg: ");
634        obj.print(stdout);
635        printf("\n");
636        fflush(stdout);
637      }
638      obj.free();
639    }
640
641    // grab the next object
642    parser->getObj(&obj);
643  }
644  obj.free();
645
646  // args at end with no command
647  if (numArgs > 0) {
648    error(getPos(), "Leftover args in content stream");
649    if (printCommands) {
650      printf("%d leftovers:", numArgs);
651      for (i = 0; i < numArgs; ++i) {
652        printf(" ");
653        args[i].print(stdout);
654      }
655      printf("\n");
656      fflush(stdout);
657    }
658    for (i = 0; i < numArgs; ++i)
659      args[i].free();
660  }
661
662  // update display
663  if (topLevel && updateLevel > 0) {
664    out->dump();
665  }
666}
667
668void Gfx::execOp(Object *cmd, Object args[], int numArgs) {
669  Operator *op;
670  char *name;
671  Object *argPtr;
672  int i;
673
674  // find operator
675  name = cmd->getCmd();
676  if (!(op = findOp(name))) {
677    if (ignoreUndef == 0)
678      error(getPos(), "Unknown operator '%s'", name);
679    return;
680  }
681
682  // type check args
683  argPtr = args;
684  if (op->numArgs >= 0) {
685    if (numArgs < op->numArgs) {
686      error(getPos(), "Too few (%d) args to '%s' operator", numArgs, name);
687      return;
688    }
689    if (numArgs > op->numArgs) {
690#if 0
691      error(getPos(), "Too many (%d) args to '%s' operator", numArgs, name);
692#endif
693      argPtr += numArgs - op->numArgs;
694      numArgs = op->numArgs;
695    }
696  } else {
697    if (numArgs > -op->numArgs) {
698      error(getPos(), "Too many (%d) args to '%s' operator",
699            numArgs, name);
700      return;
701    }
702  }
703  for (i = 0; i < numArgs; ++i) {
704    if (!checkArg(&argPtr[i], op->tchk[i])) {
705      error(getPos(), "Arg #%d to '%s' operator is wrong type (%s)",
706            i, name, argPtr[i].getTypeName());
707      return;
708    }
709  }
710
711  // do it
712  (this->*op->func)(argPtr, numArgs);
713}
714
715Operator *Gfx::findOp(char *name) {
716  int a, b, m, cmp;
717
718  a = -1;
719  b = numOps;
720  // invariant: opTab[a] < name < opTab[b]
721  while (b - a > 1) {
722    m = (a + b) / 2;
723    cmp = strcmp(opTab[m].name, name);
724    if (cmp < 0)
725      a = m;
726    else if (cmp > 0)
727      b = m;
728    else
729      a = b = m;
730  }
731  if (cmp != 0)
732    return NULL;
733  return &opTab[a];
734}
735
736GBool Gfx::checkArg(Object *arg, TchkType type) {
737  switch (type) {
738  case tchkBool:   return arg->isBool();
739  case tchkInt:    return arg->isInt();
740  case tchkNum:    return arg->isNum();
741  case tchkString: return arg->isString();
742  case tchkName:   return arg->isName();
743  case tchkArray:  return arg->isArray();
744  case tchkProps:  return arg->isDict() || arg->isName();
745  case tchkSCN:    return arg->isNum() || arg->isName();
746  case tchkNone:   return gFalse;
747  }
748  return gFalse;
749}
750
751int Gfx::getPos() {
752  return parser ? parser->getPos() : -1;
753}
754
755//------------------------------------------------------------------------
756// graphics state operators
757//------------------------------------------------------------------------
758
759void Gfx::opSave(Object args[], int numArgs) {
760  saveState();
761}
762
763void Gfx::opRestore(Object args[], int numArgs) {
764  restoreState();
765}
766
767void Gfx::opConcat(Object args[], int numArgs) {
768  state->concatCTM(args[0].getNum(), args[1].getNum(),
769                   args[2].getNum(), args[3].getNum(),
770                   args[4].getNum(), args[5].getNum());
771  out->updateCTM(state, args[0].getNum(), args[1].getNum(),
772                 args[2].getNum(), args[3].getNum(),
773                 args[4].getNum(), args[5].getNum());
774  fontChanged = gTrue;
775}
776
777void Gfx::opSetDash(Object args[], int numArgs) {
778  Array *a;
779  int length;
780  Object obj;
781  double *dash;
782  int i;
783
784  a = args[0].getArray();
785  length = a->getLength();
786  if (length == 0) {
787    dash = NULL;
788  } else {
789    dash = (double *)gmallocn(length, sizeof(double));
790    for (i = 0; i < length; ++i) {
791      dash[i] = a->get(i, &obj)->getNum();
792      obj.free();
793    }
794  }
795  state->setLineDash(dash, length, args[1].getNum());
796  out->updateLineDash(state);
797}
798
799void Gfx::opSetFlat(Object args[], int numArgs) {
800  state->setFlatness((int)args[0].getNum());
801  out->updateFlatness(state);
802}
803
804void Gfx::opSetLineJoin(Object args[], int numArgs) {
805  state->setLineJoin(args[0].getInt());
806  out->updateLineJoin(state);
807}
808
809void Gfx::opSetLineCap(Object args[], int numArgs) {
810  state->setLineCap(args[0].getInt());
811  out->updateLineCap(state);
812}
813
814void Gfx::opSetMiterLimit(Object args[], int numArgs) {
815  state->setMiterLimit(args[0].getNum());
816  out->updateMiterLimit(state);
817}
818
819void Gfx::opSetLineWidth(Object args[], int numArgs) {
820  state->setLineWidth(args[0].getNum());
821  out->updateLineWidth(state);
822}
823
824void Gfx::opSetExtGState(Object args[], int numArgs) {
825  Object obj1, obj2;
826  GfxBlendMode mode;
827  GBool haveFillOP;
828
829  if (!res->lookupGState(args[0].getName(), &obj1)) {
830    return;
831  }
832  if (!obj1.isDict()) {
833    error(getPos(), "ExtGState '%s' is wrong type", args[0].getName());
834    obj1.free();
835    return;
836  }
837
838  // transparency support: blend mode, fill/stroke opacity
839  if (!obj1.dictLookup("BM", &obj2)->isNull()) {
840    if (state->parseBlendMode(&obj2, &mode)) {
841      state->setBlendMode(mode);
842      out->updateBlendMode(state);
843    } else {
844      error(getPos(), "Invalid blend mode in ExtGState");
845    }
846  }
847  obj2.free();
848  if (obj1.dictLookup("ca", &obj2)->isNum()) {
849    state->setFillOpacity(obj2.getNum());
850    out->updateFillOpacity(state);
851  }
852  obj2.free();
853  if (obj1.dictLookup("CA", &obj2)->isNum()) {
854    state->setStrokeOpacity(obj2.getNum());
855    out->updateStrokeOpacity(state);
856  }
857  obj2.free();
858
859  // fill/stroke overprint
860  if ((haveFillOP = (obj1.dictLookup("op", &obj2)->isBool()))) {
861    state->setFillOverprint(obj2.getBool());
862    out->updateFillOverprint(state);
863  }
864  obj2.free();
865  if (obj1.dictLookup("OP", &obj2)->isBool()) {
866    state->setStrokeOverprint(obj2.getBool());
867    out->updateStrokeOverprint(state);
868    if (!haveFillOP) {
869      state->setFillOverprint(obj2.getBool());
870      out->updateFillOverprint(state);
871    }
872  }
873  obj2.free();
874
875  obj1.free();
876}
877
878void Gfx::opSetRenderingIntent(Object args[], int numArgs) {
879}
880
881//------------------------------------------------------------------------
882// color operators
883//------------------------------------------------------------------------
884
885void Gfx::opSetFillGray(Object args[], int numArgs) {
886  GfxColor color;
887
888  state->setFillPattern(NULL);
889  state->setFillColorSpace(new GfxDeviceGrayColorSpace());
890  out->updateFillColorSpace(state);
891  color.c[0] = dblToCol(args[0].getNum());
892  state->setFillColor(&color);
893  out->updateFillColor(state);
894}
895
896void Gfx::opSetStrokeGray(Object args[], int numArgs) {
897  GfxColor color;
898
899  state->setStrokePattern(NULL);
900  state->setStrokeColorSpace(new GfxDeviceGrayColorSpace());
901  out->updateStrokeColorSpace(state);
902  color.c[0] = dblToCol(args[0].getNum());
903  state->setStrokeColor(&color);
904  out->updateStrokeColor(state);
905}
906
907void Gfx::opSetFillCMYKColor(Object args[], int numArgs) {
908  GfxColor color;
909  int i;
910
911  state->setFillPattern(NULL);
912  state->setFillColorSpace(new GfxDeviceCMYKColorSpace());
913  out->updateFillColorSpace(state);
914  for (i = 0; i < 4; ++i) {
915    color.c[i] = dblToCol(args[i].getNum());
916  }
917  state->setFillColor(&color);
918  out->updateFillColor(state);
919}
920
921void Gfx::opSetStrokeCMYKColor(Object args[], int numArgs) {
922  GfxColor color;
923  int i;
924
925  state->setStrokePattern(NULL);
926  state->setStrokeColorSpace(new GfxDeviceCMYKColorSpace());
927  out->updateStrokeColorSpace(state);
928  for (i = 0; i < 4; ++i) {
929    color.c[i] = dblToCol(args[i].getNum());
930  }
931  state->setStrokeColor(&color);
932  out->updateStrokeColor(state);
933}
934
935void Gfx::opSetFillRGBColor(Object args[], int numArgs) {
936  GfxColor color;
937  int i;
938
939  state->setFillPattern(NULL);
940  state->setFillColorSpace(new GfxDeviceRGBColorSpace());
941  out->updateFillColorSpace(state);
942  for (i = 0; i < 3; ++i) {
943    color.c[i] = dblToCol(args[i].getNum());
944  }
945  state->setFillColor(&color);
946  out->updateFillColor(state);
947}
948
949void Gfx::opSetStrokeRGBColor(Object args[], int numArgs) {
950  GfxColor color;
951  int i;
952
953  state->setStrokePattern(NULL);
954  state->setStrokeColorSpace(new GfxDeviceRGBColorSpace());
955  out->updateStrokeColorSpace(state);
956  for (i = 0; i < 3; ++i) {
957    color.c[i] = dblToCol(args[i].getNum());
958  }
959  state->setStrokeColor(&color);
960  out->updateStrokeColor(state);
961}
962
963void Gfx::opSetFillColorSpace(Object args[], int numArgs) {
964  Object obj;
965  GfxColorSpace *colorSpace;
966  GfxColor color;
967  int i;
968
969  state->setFillPattern(NULL);
970  res->lookupColorSpace(args[0].getName(), &obj);
971  if (obj.isNull()) {
972    colorSpace = GfxColorSpace::parse(&args[0]);
973  } else {
974    colorSpace = GfxColorSpace::parse(&obj);
975  }
976  obj.free();
977  if (colorSpace) {
978    state->setFillColorSpace(colorSpace);
979    out->updateFillColorSpace(state);
980  } else {
981    error(getPos(), "Bad color space (fill)");
982  }
983  for (i = 0; i < gfxColorMaxComps; ++i) {
984    color.c[i] = 0;
985  }
986  state->setFillColor(&color);
987  out->updateFillColor(state);
988}
989
990void Gfx::opSetStrokeColorSpace(Object args[], int numArgs) {
991  Object obj;
992  GfxColorSpace *colorSpace;
993  GfxColor color;
994  int i;
995
996  state->setStrokePattern(NULL);
997  res->lookupColorSpace(args[0].getName(), &obj);
998  if (obj.isNull()) {
999    colorSpace = GfxColorSpace::parse(&args[0]);
1000  } else {
1001    colorSpace = GfxColorSpace::parse(&obj);
1002  }
1003  obj.free();
1004  if (colorSpace) {
1005    state->setStrokeColorSpace(colorSpace);
1006    out->updateStrokeColorSpace(state);
1007  } else {
1008    error(getPos(), "Bad color space (stroke)");
1009  }
1010  for (i = 0; i < gfxColorMaxComps; ++i) {
1011    color.c[i] = 0;
1012  }
1013  state->setStrokeColor(&color);
1014  out->updateStrokeColor(state);
1015}
1016
1017void Gfx::opSetFillColor(Object args[], int numArgs) {
1018  GfxColor color;
1019  int i;
1020
1021  state->setFillPattern(NULL);
1022  for (i = 0; i < numArgs; ++i) {
1023    color.c[i] = dblToCol(args[i].getNum());
1024  }
1025  state->setFillColor(&color);
1026  out->updateFillColor(state);
1027}
1028
1029void Gfx::opSetStrokeColor(Object args[], int numArgs) {
1030  GfxColor color;
1031  int i;
1032
1033  state->setStrokePattern(NULL);
1034  for (i = 0; i < numArgs; ++i) {
1035    color.c[i] = dblToCol(args[i].getNum());
1036  }
1037  state->setStrokeColor(&color);
1038  out->updateStrokeColor(state);
1039}
1040
1041void Gfx::opSetFillColorN(Object args[], int numArgs) {
1042  GfxColor color;
1043  GfxPattern *pattern;
1044  int i;
1045
1046  if (state->getFillColorSpace()->getMode() == csPattern) {
1047    if (numArgs > 1) {
1048      for (i = 0; i < numArgs && i < 4; ++i) {
1049        if (args[i].isNum()) {
1050          color.c[i] = dblToCol(args[i].getNum());
1051        }
1052      }
1053      state->setFillColor(&color);
1054      out->updateFillColor(state);
1055    }
1056    if (args[numArgs-1].isName() &&
1057        (pattern = res->lookupPattern(args[numArgs-1].getName()))) {
1058      state->setFillPattern(pattern);
1059    }
1060
1061  } else {
1062    state->setFillPattern(NULL);
1063    for (i = 0; i < numArgs && i < 4; ++i) {
1064      if (args[i].isNum()) {
1065        color.c[i] = dblToCol(args[i].getNum());
1066      }
1067    }
1068    state->setFillColor(&color);
1069    out->updateFillColor(state);
1070  }
1071}
1072
1073void Gfx::opSetStrokeColorN(Object args[], int numArgs) {
1074  GfxColor color;
1075  GfxPattern *pattern;
1076  int i;
1077
1078  if (state->getStrokeColorSpace()->getMode() == csPattern) {
1079    if (numArgs > 1) {
1080      for (i = 0; i < numArgs && i < 4; ++i) {
1081        if (args[i].isNum()) {
1082          color.c[i] = dblToCol(args[i].getNum());
1083        }
1084      }
1085      state->setStrokeColor(&color);
1086      out->updateStrokeColor(state);
1087    }
1088    if (args[numArgs-1].isName() &&
1089        (pattern = res->lookupPattern(args[numArgs-1].getName()))) {
1090      state->setStrokePattern(pattern);
1091    }
1092
1093  } else {
1094    state->setStrokePattern(NULL);
1095    for (i = 0; i < numArgs && i < 4; ++i) {
1096      if (args[i].isNum()) {
1097        color.c[i] = dblToCol(args[i].getNum());
1098      }
1099    }
1100    state->setStrokeColor(&color);
1101    out->updateStrokeColor(state);
1102  }
1103}
1104
1105//------------------------------------------------------------------------
1106// path segment operators
1107//------------------------------------------------------------------------
1108
1109void Gfx::opMoveTo(Object args[], int numArgs) {
1110  state->moveTo(args[0].getNum(), args[1].getNum());
1111}
1112
1113void Gfx::opLineTo(Object args[], int numArgs) {
1114  if (!state->isCurPt()) {
1115    error(getPos(), "No current point in lineto");
1116    return;
1117  }
1118  state->lineTo(args[0].getNum(), args[1].getNum());
1119}
1120
1121void Gfx::opCurveTo(Object args[], int numArgs) {
1122  double x1, y1, x2, y2, x3, y3;
1123
1124  if (!state->isCurPt()) {
1125    error(getPos(), "No current point in curveto");
1126    return;
1127  }
1128  x1 = args[0].getNum();
1129  y1 = args[1].getNum();
1130  x2 = args[2].getNum();
1131  y2 = args[3].getNum();
1132  x3 = args[4].getNum();
1133  y3 = args[5].getNum();
1134  state->curveTo(x1, y1, x2, y2, x3, y3);
1135}
1136
1137void Gfx::opCurveTo1(Object args[], int numArgs) {
1138  double x1, y1, x2, y2, x3, y3;
1139
1140  if (!state->isCurPt()) {
1141    error(getPos(), "No current point in curveto1");
1142    return;
1143  }
1144  x1 = state->getCurX();
1145  y1 = state->getCurY();
1146  x2 = args[0].getNum();
1147  y2 = args[1].getNum();
1148  x3 = args[2].getNum();
1149  y3 = args[3].getNum();
1150  state->curveTo(x1, y1, x2, y2, x3, y3);
1151}
1152
1153void Gfx::opCurveTo2(Object args[], int numArgs) {
1154  double x1, y1, x2, y2, x3, y3;
1155
1156  if (!state->isCurPt()) {
1157    error(getPos(), "No current point in curveto2");
1158    return;
1159  }
1160  x1 = args[0].getNum();
1161  y1 = args[1].getNum();
1162  x2 = args[2].getNum();
1163  y2 = args[3].getNum();
1164  x3 = x2;
1165  y3 = y2;
1166  state->curveTo(x1, y1, x2, y2, x3, y3);
1167}
1168
1169void Gfx::opRectangle(Object args[], int numArgs) {
1170  double x, y, w, h;
1171
1172  x = args[0].getNum();
1173  y = args[1].getNum();
1174  w = args[2].getNum();
1175  h = args[3].getNum();
1176  state->moveTo(x, y);
1177  state->lineTo(x + w, y);
1178  state->lineTo(x + w, y + h);
1179  state->lineTo(x, y + h);
1180  state->closePath();
1181}
1182
1183void Gfx::opClosePath(Object args[], int numArgs) {
1184  if (!state->isCurPt()) {
1185    error(getPos(), "No current point in closepath");
1186    return;
1187  }
1188  state->closePath();
1189}
1190
1191//------------------------------------------------------------------------
1192// path painting operators
1193//------------------------------------------------------------------------
1194
1195void Gfx::opEndPath(Object args[], int numArgs) {
1196  doEndPath();
1197}
1198
1199void Gfx::opStroke(Object args[], int numArgs) {
1200  if (!state->isCurPt()) {
1201    //error(getPos(), "No path in stroke");
1202    return;
1203  }
1204  if (state->isPath())
1205    out->stroke(state);
1206  doEndPath();
1207}
1208
1209void Gfx::opCloseStroke(Object args[], int numArgs) {
1210  if (!state->isCurPt()) {
1211    //error(getPos(), "No path in closepath/stroke");
1212    return;
1213  }
1214  if (state->isPath()) {
1215    state->closePath();
1216    out->stroke(state);
1217  }
1218  doEndPath();
1219}
1220
1221void Gfx::opFill(Object args[], int numArgs) {
1222  if (!state->isCurPt()) {
1223    //error(getPos(), "No path in fill");
1224    return;
1225  }
1226  if (state->isPath()) {
1227    if (state->getFillColorSpace()->getMode() == csPattern) {
1228      doPatternFill(gFalse);
1229    } else {
1230      out->fill(state);
1231    }
1232  }
1233  doEndPath();
1234}
1235
1236void Gfx::opEOFill(Object args[], int numArgs) {
1237  if (!state->isCurPt()) {
1238    //error(getPos(), "No path in eofill");
1239    return;
1240  }
1241  if (state->isPath()) {
1242    if (state->getFillColorSpace()->getMode() == csPattern) {
1243      doPatternFill(gTrue);
1244    } else {
1245      out->eoFill(state);
1246    }
1247  }
1248  doEndPath();
1249}
1250
1251void Gfx::opFillStroke(Object args[], int numArgs) {
1252  if (!state->isCurPt()) {
1253    //error(getPos(), "No path in fill/stroke");
1254    return;
1255  }
1256  if (state->isPath()) {
1257    if (state->getFillColorSpace()->getMode() == csPattern) {
1258      doPatternFill(gFalse);
1259    } else {
1260      out->fill(state);
1261    }
1262    out->stroke(state);
1263  }
1264  doEndPath();
1265}
1266
1267void Gfx::opCloseFillStroke(Object args[], int numArgs) {
1268  if (!state->isCurPt()) {
1269    //error(getPos(), "No path in closepath/fill/stroke");
1270    return;
1271  }
1272  if (state->isPath()) {
1273    state->closePath();
1274    if (state->getFillColorSpace()->getMode() == csPattern) {
1275      doPatternFill(gFalse);
1276    } else {
1277      out->fill(state);
1278    }
1279    out->stroke(state);
1280  }
1281  doEndPath();
1282}
1283
1284void Gfx::opEOFillStroke(Object args[], int numArgs) {
1285  if (!state->isCurPt()) {
1286    //error(getPos(), "No path in eofill/stroke");
1287    return;
1288  }
1289  if (state->isPath()) {
1290    if (state->getFillColorSpace()->getMode() == csPattern) {
1291      doPatternFill(gTrue);
1292    } else {
1293      out->eoFill(state);
1294    }
1295    out->stroke(state);
1296  }
1297  doEndPath();
1298}
1299
1300void Gfx::opCloseEOFillStroke(Object args[], int numArgs) {
1301  if (!state->isCurPt()) {
1302    //error(getPos(), "No path in closepath/eofill/stroke");
1303    return;
1304  }
1305  if (state->isPath()) {
1306    state->closePath();
1307    if (state->getFillColorSpace()->getMode() == csPattern) {
1308      doPatternFill(gTrue);
1309    } else {
1310      out->eoFill(state);
1311    }
1312    out->stroke(state);
1313  }
1314  doEndPath();
1315}
1316
1317void Gfx::doPatternFill(GBool eoFill) {
1318  GfxPattern *pattern;
1319
1320  // this is a bit of a kludge -- patterns can be really slow, so we
1321  // skip them if we're only doing text extraction, since they almost
1322  // certainly don't contain any text
1323  if (!out->needNonText()) {
1324    return;
1325  }
1326
1327  if (!(pattern = state->getFillPattern())) {
1328    return;
1329  }
1330  switch (pattern->getType()) {
1331  case 1:
1332    doTilingPatternFill((GfxTilingPattern *)pattern, eoFill);
1333    break;
1334  case 2:
1335    doShadingPatternFill((GfxShadingPattern *)pattern, eoFill);
1336    break;
1337  default:
1338    error(getPos(), "Unimplemented pattern type (%d) in fill",
1339          pattern->getType());
1340    break;
1341  }
1342}
1343
1344void Gfx::doTilingPatternFill(GfxTilingPattern *tPat, GBool eoFill) {
1345  GfxPatternColorSpace *patCS;
1346  GfxColorSpace *cs;
1347  GfxPath *savedPath;
1348  double xMin, yMin, xMax, yMax, x, y, x1, y1;
1349  double cxMin, cyMin, cxMax, cyMax;
1350  int xi0, yi0, xi1, yi1, xi, yi;
1351  double *ctm, *btm, *ptm;
1352  double m[6], ictm[6], m1[6], imb[6];
1353  double det;
1354  double xstep, ystep;
1355  int i;
1356
1357  // get color space
1358  patCS = (GfxPatternColorSpace *)state->getFillColorSpace();
1359
1360  // construct a (pattern space) -> (current space) transform matrix
1361  ctm = state->getCTM();
1362  btm = baseMatrix;
1363  ptm = tPat->getMatrix();
1364  // iCTM = invert CTM
1365  det = 1 / (ctm[0] * ctm[3] - ctm[1] * ctm[2]);
1366  ictm[0] = ctm[3] * det;
1367  ictm[1] = -ctm[1] * det;
1368  ictm[2] = -ctm[2] * det;
1369  ictm[3] = ctm[0] * det;
1370  ictm[4] = (ctm[2] * ctm[5] - ctm[3] * ctm[4]) * det;
1371  ictm[5] = (ctm[1] * ctm[4] - ctm[0] * ctm[5]) * det;
1372  // m1 = PTM * BTM = PTM * base transform matrix
1373  m1[0] = ptm[0] * btm[0] + ptm[1] * btm[2];
1374  m1[1] = ptm[0] * btm[1] + ptm[1] * btm[3];
1375  m1[2] = ptm[2] * btm[0] + ptm[3] * btm[2];
1376  m1[3] = ptm[2] * btm[1] + ptm[3] * btm[3];
1377  m1[4] = ptm[4] * btm[0] + ptm[5] * btm[2] + btm[4];
1378  m1[5] = ptm[4] * btm[1] + ptm[5] * btm[3] + btm[5];
1379  // m = m1 * iCTM = (PTM * BTM) * (iCTM)
1380  m[0] = m1[0] * ictm[0] + m1[1] * ictm[2];
1381  m[1] = m1[0] * ictm[1] + m1[1] * ictm[3];
1382  m[2] = m1[2] * ictm[0] + m1[3] * ictm[2];
1383  m[3] = m1[2] * ictm[1] + m1[3] * ictm[3];
1384  m[4] = m1[4] * ictm[0] + m1[5] * ictm[2] + ictm[4];
1385  m[5] = m1[4] * ictm[1] + m1[5] * ictm[3] + ictm[5];
1386
1387  // construct a (device space) -> (pattern space) transform matrix
1388  det = 1 / (m1[0] * m1[3] - m1[1] * m1[2]);
1389  imb[0] = m1[3] * det;
1390  imb[1] = -m1[1] * det;
1391  imb[2] = -m1[2] * det;
1392  imb[3] = m1[0] * det;
1393  imb[4] = (m1[2] * m1[5] - m1[3] * m1[4]) * det;
1394  imb[5] = (m1[1] * m1[4] - m1[0] * m1[5]) * det;
1395
1396  // save current graphics state
1397  savedPath = state->getPath()->copy();
1398  saveState();
1399
1400  // set underlying color space (for uncolored tiling patterns); set
1401  // various other parameters (stroke color, line width) to match
1402  // Adobe's behavior
1403  if (tPat->getPaintType() == 2 && (cs = patCS->getUnder())) {
1404    state->setFillColorSpace(cs->copy());
1405    out->updateFillColorSpace(state);
1406    state->setStrokeColorSpace(cs->copy());
1407    out->updateStrokeColorSpace(state);
1408    state->setStrokeColor(state->getFillColor());
1409  } else {
1410    state->setFillColorSpace(new GfxDeviceGrayColorSpace());
1411    out->updateFillColorSpace(state);
1412    state->setStrokeColorSpace(new GfxDeviceGrayColorSpace());
1413    out->updateStrokeColorSpace(state);
1414  }
1415  state->setFillPattern(NULL);
1416  out->updateFillColor(state);
1417  state->setStrokePattern(NULL);
1418  out->updateStrokeColor(state);
1419  state->setLineWidth(0);
1420  out->updateLineWidth(state);
1421
1422  // clip to current path
1423  state->clip();
1424  if (eoFill) {
1425    out->eoClip(state);
1426  } else {
1427    out->clip(state);
1428  }
1429  state->clearPath();
1430
1431  // get the clip region, check for empty
1432  state->getClipBBox(&cxMin, &cyMin, &cxMax, &cyMax);
1433  if (cxMin > cxMax || cyMin > cyMax) {
1434    goto err;
1435  }
1436
1437  // transform clip region bbox to pattern space
1438  xMin = xMax = cxMin * imb[0] + cyMin * imb[2] + imb[4];
1439  yMin = yMax = cxMin * imb[1] + cyMin * imb[3] + imb[5];
1440  x1 = cxMin * imb[0] + cyMax * imb[2] + imb[4];
1441  y1 = cxMin * imb[1] + cyMax * imb[3] + imb[5];
1442  if (x1 < xMin) {
1443    xMin = x1;
1444  } else if (x1 > xMax) {
1445    xMax = x1;
1446  }
1447  if (y1 < yMin) {
1448    yMin = y1;
1449  } else if (y1 > yMax) {
1450    yMax = y1;
1451  }
1452  x1 = cxMax * imb[0] + cyMin * imb[2] + imb[4];
1453  y1 = cxMax * imb[1] + cyMin * imb[3] + imb[5];
1454  if (x1 < xMin) {
1455    xMin = x1;
1456  } else if (x1 > xMax) {
1457    xMax = x1;
1458  }
1459  if (y1 < yMin) {
1460    yMin = y1;
1461  } else if (y1 > yMax) {
1462    yMax = y1;
1463  }
1464  x1 = cxMax * imb[0] + cyMax * imb[2] + imb[4];
1465  y1 = cxMax * imb[1] + cyMax * imb[3] + imb[5];
1466  if (x1 < xMin) {
1467    xMin = x1;
1468  } else if (x1 > xMax) {
1469    xMax = x1;
1470  }
1471  if (y1 < yMin) {
1472    yMin = y1;
1473  } else if (y1 > yMax) {
1474    yMax = y1;
1475  }
1476
1477  // draw the pattern
1478  //~ this should treat negative steps differently -- start at right/top
1479  //~ edge instead of left/bottom (?)
1480  xstep = fabs(tPat->getXStep());
1481  ystep = fabs(tPat->getYStep());
1482  xi0 = (int)floor((xMin - tPat->getBBox()[0]) / xstep);
1483  xi1 = (int)ceil((xMax - tPat->getBBox()[0]) / xstep);
1484  yi0 = (int)floor((yMin - tPat->getBBox()[1]) / ystep);
1485  yi1 = (int)ceil((yMax - tPat->getBBox()[1]) / ystep);
1486  for (i = 0; i < 4; ++i) {
1487    m1[i] = m[i];
1488  }
1489  if (out->useTilingPatternFill()) {
1490    m1[4] = m[4];
1491    m1[5] = m[5];
1492    out->tilingPatternFill(state, tPat->getContentStream(),
1493                           tPat->getPaintType(), tPat->getResDict(),
1494                           m1, tPat->getBBox(),
1495                           xi0, yi0, xi1, yi1, xstep, ystep);
1496  } else {
1497  for (yi = yi0; yi < yi1; ++yi) {
1498    for (xi = xi0; xi < xi1; ++xi) {
1499      x = xi * xstep;
1500      y = yi * ystep;
1501      m1[4] = x * m[0] + y * m[2] + m[4];
1502      m1[5] = x * m[1] + y * m[3] + m[5];
1503      doForm1(tPat->getContentStream(), tPat->getResDict(),
1504              m1, tPat->getBBox());
1505    }
1506  }
1507  }
1508
1509  // restore graphics state
1510 err:
1511  restoreState();
1512  state->setPath(savedPath);
1513}
1514
1515void Gfx::doShadingPatternFill(GfxShadingPattern *sPat, GBool eoFill) {
1516  GfxShading *shading;
1517  GfxPath *savedPath;
1518  double *ctm, *btm, *ptm;
1519  double m[6], ictm[6], m1[6];
1520  double xMin, yMin, xMax, yMax;
1521  double det;
1522
1523  shading = sPat->getShading();
1524
1525  // save current graphics state
1526  savedPath = state->getPath()->copy();
1527  saveState();
1528
1529  // clip to bbox
1530  if (shading->getHasBBox()) {
1531    shading->getBBox(&xMin, &yMin, &xMax, &yMax);
1532    state->moveTo(xMin, yMin);
1533    state->lineTo(xMax, yMin);
1534    state->lineTo(xMax, yMax);
1535    state->lineTo(xMin, yMax);
1536    state->closePath();
1537    state->clip();
1538    out->clip(state);
1539    state->setPath(savedPath->copy());
1540  }
1541
1542  // clip to current path
1543  state->clip();
1544  if (eoFill) {
1545    out->eoClip(state);
1546  } else {
1547    out->clip(state);
1548  }
1549
1550  // set the color space
1551  state->setFillColorSpace(shading->getColorSpace()->copy());
1552  out->updateFillColorSpace(state);
1553
1554  // background color fill
1555  if (shading->getHasBackground()) {
1556    state->setFillColor(shading->getBackground());
1557    out->updateFillColor(state);
1558    out->fill(state);
1559  }
1560  state->clearPath();
1561
1562  // construct a (pattern space) -> (current space) transform matrix
1563  ctm = state->getCTM();
1564  btm = baseMatrix;
1565  ptm = sPat->getMatrix();
1566  // iCTM = invert CTM
1567  det = 1 / (ctm[0] * ctm[3] - ctm[1] * ctm[2]);
1568  ictm[0] = ctm[3] * det;
1569  ictm[1] = -ctm[1] * det;
1570  ictm[2] = -ctm[2] * det;
1571  ictm[3] = ctm[0] * det;
1572  ictm[4] = (ctm[2] * ctm[5] - ctm[3] * ctm[4]) * det;
1573  ictm[5] = (ctm[1] * ctm[4] - ctm[0] * ctm[5]) * det;
1574  // m1 = PTM * BTM = PTM * base transform matrix
1575  m1[0] = ptm[0] * btm[0] + ptm[1] * btm[2];
1576  m1[1] = ptm[0] * btm[1] + ptm[1] * btm[3];
1577  m1[2] = ptm[2] * btm[0] + ptm[3] * btm[2];
1578  m1[3] = ptm[2] * btm[1] + ptm[3] * btm[3];
1579  m1[4] = ptm[4] * btm[0] + ptm[5] * btm[2] + btm[4];
1580  m1[5] = ptm[4] * btm[1] + ptm[5] * btm[3] + btm[5];
1581  // m = m1 * iCTM = (PTM * BTM) * (iCTM)
1582  m[0] = m1[0] * ictm[0] + m1[1] * ictm[2];
1583  m[1] = m1[0] * ictm[1] + m1[1] * ictm[3];
1584  m[2] = m1[2] * ictm[0] + m1[3] * ictm[2];
1585  m[3] = m1[2] * ictm[1] + m1[3] * ictm[3];
1586  m[4] = m1[4] * ictm[0] + m1[5] * ictm[2] + ictm[4];
1587  m[5] = m1[4] * ictm[1] + m1[5] * ictm[3] + ictm[5];
1588
1589  // set the new matrix
1590  state->concatCTM(m[0], m[1], m[2], m[3], m[4], m[5]);
1591  out->updateCTM(state, m[0], m[1], m[2], m[3], m[4], m[5]);
1592
1593  // do shading type-specific operations
1594  switch (shading->getType()) {
1595  case 1:
1596    doFunctionShFill((GfxFunctionShading *)shading);
1597    break;
1598  case 2:
1599    doAxialShFill((GfxAxialShading *)shading);
1600    break;
1601  case 3:
1602    doRadialShFill((GfxRadialShading *)shading);
1603    break;
1604  case 4:
1605  case 5:
1606    doGouraudTriangleShFill((GfxGouraudTriangleShading *)shading);
1607    break;
1608  case 6:
1609  case 7:
1610    doPatchMeshShFill((GfxPatchMeshShading *)shading);
1611    break;
1612  }
1613
1614  // restore graphics state
1615  restoreState();
1616  state->setPath(savedPath);
1617}
1618
1619void Gfx::opShFill(Object args[], int numArgs) {
1620  GfxShading *shading;
1621  GfxPath *savedPath;
1622  double xMin, yMin, xMax, yMax;
1623
1624  if (!(shading = res->lookupShading(args[0].getName()))) {
1625    return;
1626  }
1627
1628  // save current graphics state
1629  savedPath = state->getPath()->copy();
1630  saveState();
1631
1632  // clip to bbox
1633  if (shading->getHasBBox()) {
1634    shading->getBBox(&xMin, &yMin, &xMax, &yMax);
1635    state->moveTo(xMin, yMin);
1636    state->lineTo(xMax, yMin);
1637    state->lineTo(xMax, yMax);
1638    state->lineTo(xMin, yMax);
1639    state->closePath();
1640    state->clip();
1641    out->clip(state);
1642    state->clearPath();
1643  }
1644
1645  // set the color space
1646  state->setFillColorSpace(shading->getColorSpace()->copy());
1647  out->updateFillColorSpace(state);
1648
1649  // do shading type-specific operations
1650  switch (shading->getType()) {
1651  case 1:
1652    doFunctionShFill((GfxFunctionShading *)shading);
1653    break;
1654  case 2:
1655    doAxialShFill((GfxAxialShading *)shading);
1656    break;
1657  case 3:
1658    doRadialShFill((GfxRadialShading *)shading);
1659    break;
1660  case 4:
1661  case 5:
1662    doGouraudTriangleShFill((GfxGouraudTriangleShading *)shading);
1663    break;
1664  case 6:
1665  case 7:
1666    doPatchMeshShFill((GfxPatchMeshShading *)shading);
1667    break;
1668  }
1669
1670  // restore graphics state
1671  restoreState();
1672  state->setPath(savedPath);
1673
1674  delete shading;
1675}
1676
1677void Gfx::doFunctionShFill(GfxFunctionShading *shading) {
1678  double x0, y0, x1, y1;
1679  GfxColor colors[4];
1680
1681  if (out->useShadedFills()) {
1682    out->functionShadedFill(state, shading);
1683  } else {
1684  shading->getDomain(&x0, &y0, &x1, &y1);
1685  shading->getColor(x0, y0, &colors[0]);
1686  shading->getColor(x0, y1, &colors[1]);
1687  shading->getColor(x1, y0, &colors[2]);
1688  shading->getColor(x1, y1, &colors[3]);
1689  doFunctionShFill1(shading, x0, y0, x1, y1, colors, 0);
1690  }
1691}
1692
1693void Gfx::doFunctionShFill1(GfxFunctionShading *shading,
1694                            double x0, double y0,
1695                            double x1, double y1,
1696                            GfxColor *colors, int depth) {
1697  GfxColor fillColor;
1698  GfxColor color0M, color1M, colorM0, colorM1, colorMM;
1699  GfxColor colors2[4];
1700  double *matrix;
1701  double xM, yM;
1702  int nComps, i, j;
1703
1704  nComps = shading->getColorSpace()->getNComps();
1705  matrix = shading->getMatrix();
1706
1707  // compare the four corner colors
1708  for (i = 0; i < 4; ++i) {
1709    for (j = 0; j < nComps; ++j) {
1710      if (abs(colors[i].c[j] - colors[(i+1)&3].c[j]) > functionColorDelta) {
1711        break;
1712      }
1713    }
1714    if (j < nComps) {
1715      break;
1716    }
1717  }
1718
1719  // center of the rectangle
1720  xM = 0.5 * (x0 + x1);
1721  yM = 0.5 * (y0 + y1);
1722
1723  // the four corner colors are close (or we hit the recursive limit)
1724  // -- fill the rectangle; but require at least one subdivision
1725  // (depth==0) to avoid problems when the four outer corners of the
1726  // shaded region are the same color
1727  if ((i == 4 && depth > 0) || depth == functionMaxDepth) {
1728
1729    // use the center color
1730    shading->getColor(xM, yM, &fillColor);
1731    state->setFillColor(&fillColor);
1732    out->updateFillColor(state);
1733
1734    // fill the rectangle
1735    state->moveTo(x0 * matrix[0] + y0 * matrix[2] + matrix[4],
1736                  x0 * matrix[1] + y0 * matrix[3] + matrix[5]);
1737    state->lineTo(x1 * matrix[0] + y0 * matrix[2] + matrix[4],
1738                  x1 * matrix[1] + y0 * matrix[3] + matrix[5]);
1739    state->lineTo(x1 * matrix[0] + y1 * matrix[2] + matrix[4],
1740                  x1 * matrix[1] + y1 * matrix[3] + matrix[5]);
1741    state->lineTo(x0 * matrix[0] + y1 * matrix[2] + matrix[4],
1742                  x0 * matrix[1] + y1 * matrix[3] + matrix[5]);
1743    state->closePath();
1744    out->fill(state);
1745    state->clearPath();
1746
1747  // the four corner colors are not close enough -- subdivide the
1748  // rectangle
1749  } else {
1750
1751    // colors[0]       colorM0       colors[2]
1752    //   (x0,y0)       (xM,y0)       (x1,y0)
1753    //         +----------+----------+
1754    //         |          |          |
1755    //         |    UL    |    UR    |
1756    // color0M |       colorMM       | color1M
1757    // (x0,yM) +----------+----------+ (x1,yM)
1758    //         |       (xM,yM)       |
1759    //         |    LL    |    LR    |
1760    //         |          |          |
1761    //         +----------+----------+
1762    // colors[1]       colorM1       colors[3]
1763    //   (x0,y1)       (xM,y1)       (x1,y1)
1764
1765    shading->getColor(x0, yM, &color0M);
1766    shading->getColor(x1, yM, &color1M);
1767    shading->getColor(xM, y0, &colorM0);
1768    shading->getColor(xM, y1, &colorM1);
1769    shading->getColor(xM, yM, &colorMM);
1770
1771    // upper-left sub-rectangle
1772    colors2[0] = colors[0];
1773    colors2[1] = color0M;
1774    colors2[2] = colorM0;
1775    colors2[3] = colorMM;
1776    doFunctionShFill1(shading, x0, y0, xM, yM, colors2, depth + 1);
1777   
1778    // lower-left sub-rectangle
1779    colors2[0] = color0M;
1780    colors2[1] = colors[1];
1781    colors2[2] = colorMM;
1782    colors2[3] = colorM1;
1783    doFunctionShFill1(shading, x0, yM, xM, y1, colors2, depth + 1);
1784   
1785    // upper-right sub-rectangle
1786    colors2[0] = colorM0;
1787    colors2[1] = colorMM;
1788    colors2[2] = colors[2];
1789    colors2[3] = color1M;
1790    doFunctionShFill1(shading, xM, y0, x1, yM, colors2, depth + 1);
1791
1792    // lower-right sub-rectangle
1793    colors2[0] = colorMM;
1794    colors2[1] = colorM1;
1795    colors2[2] = color1M;
1796    colors2[3] = colors[3];
1797    doFunctionShFill1(shading, xM, yM, x1, y1, colors2, depth + 1);
1798  }
1799}
1800
1801void Gfx::doAxialShFill(GfxAxialShading *shading) {
1802  double xMin, yMin, xMax, yMax;
1803  double x0, y0, x1, y1;
1804  double dx, dy, mul;
1805  GBool dxZero, dyZero;
1806  double tMin, tMax, t, tx, ty;
1807  double s[4], sMin, sMax, tmp;
1808  double ux0, uy0, ux1, uy1, vx0, vy0, vx1, vy1;
1809  double t0, t1, tt;
1810  double ta[axialMaxSplits + 1];
1811  int next[axialMaxSplits + 1];
1812  GfxColor color0, color1;
1813  int nComps;
1814  int i, j, k, kk;
1815
1816  if (out->useShadedFills()) {
1817
1818    out->axialShadedFill(state, shading);
1819
1820  } else {
1821
1822  // get the clip region bbox
1823  state->getUserClipBBox(&xMin, &yMin, &xMax, &yMax);
1824
1825  // compute min and max t values, based on the four corners of the
1826  // clip region bbox
1827  shading->getCoords(&x0, &y0, &x1, &y1);
1828  dx = x1 - x0;
1829  dy = y1 - y0;
1830    dxZero = fabs(dx) < 0.001;
1831    dyZero = fabs(dy) < 0.001;
1832  mul = 1 / (dx * dx + dy * dy);
1833  tMin = tMax = ((xMin - x0) * dx + (yMin - y0) * dy) * mul;
1834  t = ((xMin - x0) * dx + (yMax - y0) * dy) * mul;
1835  if (t < tMin) {
1836    tMin = t;
1837  } else if (t > tMax) {
1838    tMax = t;
1839  }
1840  t = ((xMax - x0) * dx + (yMin - y0) * dy) * mul;
1841  if (t < tMin) {
1842    tMin = t;
1843  } else if (t > tMax) {
1844    tMax = t;
1845  }
1846  t = ((xMax - x0) * dx + (yMax - y0) * dy) * mul;
1847  if (t < tMin) {
1848    tMin = t;
1849  } else if (t > tMax) {
1850    tMax = t;
1851  }
1852  if (tMin < 0 && !shading->getExtend0()) {
1853    tMin = 0;
1854  }
1855  if (tMax > 1 && !shading->getExtend1()) {
1856    tMax = 1;
1857  }
1858
1859  // get the function domain
1860  t0 = shading->getDomain0();
1861  t1 = shading->getDomain1();
1862
1863  // Traverse the t axis and do the shading.
1864  //
1865  // For each point (tx, ty) on the t axis, consider a line through
1866  // that point perpendicular to the t axis:
1867  //
1868  //     x(s) = tx + s * -dy   -->   s = (x - tx) / -dy
1869  //     y(s) = ty + s * dx    -->   s = (y - ty) / dx
1870  //
1871  // Then look at the intersection of this line with the bounding box
1872  // (xMin, yMin, xMax, yMax).  In the general case, there are four
1873  // intersection points:
1874  //
1875  //     s0 = (xMin - tx) / -dy
1876  //     s1 = (xMax - tx) / -dy
1877  //     s2 = (yMin - ty) / dx
1878  //     s3 = (yMax - ty) / dx
1879  //
1880  // and we want the middle two s values.
1881  //
1882  // In the case where dx = 0, take s0 and s1; in the case where dy =
1883  // 0, take s2 and s3.
1884  //
1885  // Each filled polygon is bounded by two of these line segments
1886  // perpdendicular to the t axis.
1887  //
1888  // The t axis is bisected into smaller regions until the color
1889  // difference across a region is small enough, and then the region
1890  // is painted with a single color.
1891
1892  // set up: require at least one split to avoid problems when the two
1893  // ends of the t axis have the same color
1894  nComps = shading->getColorSpace()->getNComps();
1895  ta[0] = tMin;
1896  next[0] = axialMaxSplits / 2;
1897  ta[axialMaxSplits / 2] = 0.5 * (tMin + tMax);
1898  next[axialMaxSplits / 2] = axialMaxSplits;
1899  ta[axialMaxSplits] = tMax;
1900
1901  // compute the color at t = tMin
1902  if (tMin < 0) {
1903    tt = t0;
1904  } else if (tMin > 1) {
1905    tt = t1;
1906  } else {
1907    tt = t0 + (t1 - t0) * tMin;
1908  }
1909  shading->getColor(tt, &color0);
1910
1911  // compute the coordinates of the point on the t axis at t = tMin;
1912  // then compute the intersection of the perpendicular line with the
1913  // bounding box
1914  tx = x0 + tMin * dx;
1915  ty = y0 + tMin * dy;
1916    if (dxZero && dyZero) {
1917    sMin = sMax = 0;
1918    } if (dxZero) {
1919    sMin = (xMin - tx) / -dy;
1920    sMax = (xMax - tx) / -dy;
1921    if (sMin > sMax) { tmp = sMin; sMin = sMax; sMax = tmp; }
1922    } else if (dyZero) {
1923    sMin = (yMin - ty) / dx;
1924    sMax = (yMax - ty) / dx;
1925    if (sMin > sMax) { tmp = sMin; sMin = sMax; sMax = tmp; }
1926  } else {
1927    s[0] = (yMin - ty) / dx;
1928    s[1] = (yMax - ty) / dx;
1929    s[2] = (xMin - tx) / -dy;
1930    s[3] = (xMax - tx) / -dy;
1931    for (j = 0; j < 3; ++j) {
1932      kk = j;
1933      for (k = j + 1; k < 4; ++k) {
1934        if (s[k] < s[kk]) {
1935          kk = k;
1936        }
1937      }
1938      tmp = s[j]; s[j] = s[kk]; s[kk] = tmp;
1939    }
1940    sMin = s[1];
1941    sMax = s[2];
1942  }
1943  ux0 = tx - sMin * dy;
1944  uy0 = ty + sMin * dx;
1945  vx0 = tx - sMax * dy;
1946  vy0 = ty + sMax * dx;
1947
1948  i = 0;
1949  while (i < axialMaxSplits) {
1950
1951    // bisect until color difference is small enough or we hit the
1952    // bisection limit
1953    j = next[i];
1954    while (j > i + 1) {
1955      if (ta[j] < 0) {
1956        tt = t0;
1957      } else if (ta[j] > 1) {
1958        tt = t1;
1959      } else {
1960        tt = t0 + (t1 - t0) * ta[j];
1961      }
1962      shading->getColor(tt, &color1);
1963      for (k = 0; k < nComps; ++k) {
1964          if (abs(color1.c[k] - color0.c[k]) > axialColorDelta) {
1965          break;
1966        }
1967      }
1968      if (k == nComps) {
1969        break;
1970      }
1971      k = (i + j) / 2;
1972      ta[k] = 0.5 * (ta[i] + ta[j]);
1973      next[i] = k;
1974      next[k] = j;
1975      j = k;
1976    }
1977
1978    // use the average of the colors of the two sides of the region
1979    for (k = 0; k < nComps; ++k) {
1980        color0.c[k] = (color0.c[k] + color1.c[k]) / 2;
1981    }
1982
1983    // compute the coordinates of the point on the t axis; then
1984    // compute the intersection of the perpendicular line with the
1985    // bounding box
1986    tx = x0 + ta[j] * dx;
1987    ty = y0 + ta[j] * dy;
1988      if (dxZero && dyZero) {
1989      sMin = sMax = 0;
1990      } if (dxZero) {
1991      sMin = (xMin - tx) / -dy;
1992      sMax = (xMax - tx) / -dy;
1993      if (sMin > sMax) { tmp = sMin; sMin = sMax; sMax = tmp; }
1994      } else if (dyZero) {
1995      sMin = (yMin - ty) / dx;
1996      sMax = (yMax - ty) / dx;
1997      if (sMin > sMax) { tmp = sMin; sMin = sMax; sMax = tmp; }
1998    } else {
1999      s[0] = (yMin - ty) / dx;
2000      s[1] = (yMax - ty) / dx;
2001      s[2] = (xMin - tx) / -dy;
2002      s[3] = (xMax - tx) / -dy;
2003      for (j = 0; j < 3; ++j) {
2004        kk = j;
2005        for (k = j + 1; k < 4; ++k) {
2006          if (s[k] < s[kk]) {
2007            kk = k;
2008          }
2009        }
2010        tmp = s[j]; s[j] = s[kk]; s[kk] = tmp;
2011      }
2012      sMin = s[1];
2013      sMax = s[2];
2014    }
2015    ux1 = tx - sMin * dy;
2016    uy1 = ty + sMin * dx;
2017    vx1 = tx - sMax * dy;
2018    vy1 = ty + sMax * dx;
2019
2020    // set the color
2021    state->setFillColor(&color0);
2022    out->updateFillColor(state);
2023
2024    // fill the region
2025    state->moveTo(ux0, uy0);
2026    state->lineTo(vx0, vy0);
2027    state->lineTo(vx1, vy1);
2028    state->lineTo(ux1, uy1);
2029    state->closePath();
2030    out->fill(state);
2031    state->clearPath();
2032
2033    // set up for next region
2034    ux0 = ux1;
2035    uy0 = uy1;
2036    vx0 = vx1;
2037    vy0 = vy1;
2038    color0 = color1;
2039    i = next[i];
2040  }
2041  }
2042}
2043
2044void Gfx::doRadialShFill(GfxRadialShading *shading) {
2045  double sMin, sMax, xMin, yMin, xMax, yMax;
2046  double x0, y0, r0, x1, y1, r1, t0, t1;
2047  int nComps;
2048  GfxColor colorA, colorB;
2049  double xa, ya, xb, yb, ra, rb;
2050  double ta, tb, sa, sb;
2051  int ia, ib, k, n;
2052  double *ctm;
2053  double angle, t, d0, d1;
2054
2055  if (out->useShadedFills()) {
2056
2057    out->radialShadedFill(state, shading);
2058
2059  } else {
2060
2061  // get the shading info
2062  shading->getCoords(&x0, &y0, &r0, &x1, &y1, &r1);
2063  t0 = shading->getDomain0();
2064  t1 = shading->getDomain1();
2065  nComps = shading->getColorSpace()->getNComps();
2066
2067  // compute the (possibly extended) s range
2068  sMin = 0;
2069  sMax = 1;
2070  if (shading->getExtend0()) {
2071    if (r0 < r1) {
2072      // extend the smaller end
2073      sMin = -r0 / (r1 - r0);
2074    } else {
2075      // extend the larger end
2076      state->getUserClipBBox(&xMin, &yMin, &xMax, &yMax);
2077        d0 = (x0 - xMin) * (x0 - xMin);
2078        d1 = (x0 - xMax) * (x0 - xMax);
2079        sMin = d0 > d1 ? d0 : d1;
2080        d0 = (y0 - yMin) * (y0 - yMin);
2081        d1 = (y0 - yMax) * (y0 - yMax);
2082        sMin += d0 > d1 ? d0 : d1;
2083        sMin = (sqrt(sMin) - r0) / (r1 - r0);
2084      if (sMin > 0) {
2085        sMin = 0;
2086      } else if (sMin < -20) {
2087        // sanity check
2088        sMin = -20;
2089      }
2090    }
2091  }
2092  if (shading->getExtend1()) {
2093    if (r1 < r0) {
2094      // extend the smaller end
2095      sMax = -r0 / (r1 - r0);
2096    } else if (r1 > r0) {
2097      // extend the larger end
2098      state->getUserClipBBox(&xMin, &yMin, &xMax, &yMax);
2099        d0 = (x1 - xMin) * (x1 - xMin);
2100        d1 = (x1 - xMax) * (x1 - xMax);
2101        sMax = d0 > d1 ? d0 : d1;
2102        d0 = (y1 - yMin) * (y1 - yMin);
2103        d1 = (y1 - yMax) * (y1 - yMax);
2104        sMax += d0 > d1 ? d0 : d1;
2105        sMax = (sqrt(sMax) - r0) / (r1 - r0);
2106      if (sMax < 1) {
2107          sMax = 1;
2108      } else if (sMax > 20) {
2109        // sanity check
2110        sMax = 20;
2111      }
2112    }
2113  }
2114
2115  // compute the number of steps into which circles must be divided to
2116  // achieve a curve flatness of 0.1 pixel in device space for the
2117  // largest circle (note that "device space" is 72 dpi when generating
2118  // PostScript, hence the relatively small 0.1 pixel accuracy)
2119  ctm = state->getCTM();
2120  t = fabs(ctm[0]);
2121  if (fabs(ctm[1]) > t) {
2122    t = fabs(ctm[1]);
2123  }
2124  if (fabs(ctm[2]) > t) {
2125    t = fabs(ctm[2]);
2126  }
2127  if (fabs(ctm[3]) > t) {
2128    t = fabs(ctm[3]);
2129  }
2130  if (r0 > r1) {
2131    t *= r0;
2132  } else {
2133    t *= r1;
2134  }
2135  if (t < 1) {
2136    n = 3;
2137  } else {
2138    n = (int)(M_PI / acos(1 - 0.1 / t));
2139    if (n < 3) {
2140      n = 3;
2141    } else if (n > 200) {
2142      n = 200;
2143    }
2144  }
2145
2146  // Traverse the t axis and do the shading.
2147  //
2148  // This generates and fills a series of rings.  Each ring is defined
2149  // by two circles:
2150  //   sa, ta, xa, ya, ra, colorA
2151  //   sb, tb, xb, yb, rb, colorB
2152  //
2153  // The s/t axis is divided into radialMaxSplits parts; these parts
2154  // are combined as much as possible while respecting the
2155  // radialColorDelta parameter.
2156
2157  // setup for the start circle
2158  ia = 0;
2159  sa = sMin;
2160  ta = t0 + sa * (t1 - t0);
2161  xa = x0 + sa * (x1 - x0);
2162  ya = y0 + sa * (y1 - y0);
2163  ra = r0 + sa * (r1 - r0);
2164  if (ta < t0) {
2165    shading->getColor(t0, &colorA);
2166  } else if (ta > t1) {
2167    shading->getColor(t1, &colorA);
2168  } else {
2169    shading->getColor(ta, &colorA);
2170  }
2171
2172  while (ia < radialMaxSplits) {
2173
2174    // go as far along the t axis (toward t1) as we can, such that the
2175    // color difference is within the tolerance (radialColorDelta) --
2176    // this uses bisection (between the current value, t, and t1),
2177    // limited to radialMaxSplits points along the t axis; require at
2178    // least one split to avoid problems when the innermost and
2179    // outermost colors are the same
2180    ib = radialMaxSplits;
2181    sb = sMin + ((double)ib / (double)radialMaxSplits) * (sMax - sMin);
2182    tb = t0 + sb * (t1 - t0);
2183    if (tb < t0) {
2184      shading->getColor(t0, &colorB);
2185    } else if (tb > t1) {
2186      shading->getColor(t1, &colorB);
2187    } else {
2188      shading->getColor(tb, &colorB);
2189    }
2190    while (ib - ia > 1) {
2191      for (k = 0; k < nComps; ++k) {
2192          if (abs(colorB.c[k] - colorA.c[k]) > radialColorDelta) {
2193          break;
2194        }
2195      }
2196      if (k == nComps && ib < radialMaxSplits) {
2197        break;
2198      }
2199      ib = (ia + ib) / 2;
2200      sb = sMin + ((double)ib / (double)radialMaxSplits) * (sMax - sMin);
2201      tb = t0 + sb * (t1 - t0);
2202      if (tb < t0) {
2203        shading->getColor(t0, &colorB);
2204      } else if (tb > t1) {
2205        shading->getColor(t1, &colorB);
2206      } else {
2207        shading->getColor(tb, &colorB);
2208      }
2209    }
2210
2211    // compute center and radius of the circle
2212    xb = x0 + sb * (x1 - x0);
2213    yb = y0 + sb * (y1 - y0);
2214    rb = r0 + sb * (r1 - r0);
2215
2216    // use the average of the colors at the two circles
2217    for (k = 0; k < nComps; ++k) {
2218        colorA.c[k] = (colorA.c[k] + colorB.c[k]) / 2;
2219    }
2220    state->setFillColor(&colorA);
2221    out->updateFillColor(state);
2222
2223    // construct path for first circle
2224    state->moveTo(xa + ra, ya);
2225    for (k = 1; k < n; ++k) {
2226      angle = ((double)k / (double)n) * 2 * M_PI;
2227      state->lineTo(xa + ra * cos(angle), ya + ra * sin(angle));
2228    }
2229    state->closePath();
2230
2231    // construct and append path for second circle
2232    state->moveTo(xb + rb, yb);
2233    for (k = 1; k < n; ++k) {
2234      angle = ((double)k / (double)n) * 2 * M_PI;
2235      state->lineTo(xb + rb * cos(angle), yb + rb * sin(angle));
2236    }
2237    state->closePath();
2238
2239    // fill the ring
2240    out->eoFill(state);
2241    state->clearPath();
2242
2243    // step to the next value of t
2244    ia = ib;
2245    sa = sb;
2246    ta = tb;
2247    xa = xb;
2248    ya = yb;
2249    ra = rb;
2250    colorA = colorB;
2251  }
2252  }
2253}
2254
2255void Gfx::doGouraudTriangleShFill(GfxGouraudTriangleShading *shading) {
2256  double x0, y0, x1, y1, x2, y2;
2257  GfxColor color0, color1, color2;
2258  int i;
2259
2260  for (i = 0; i < shading->getNTriangles(); ++i) {
2261    shading->getTriangle(i, &x0, &y0, &color0,
2262                         &x1, &y1, &color1,
2263                         &x2, &y2, &color2);
2264    gouraudFillTriangle(x0, y0, &color0, x1, y1, &color1, x2, y2, &color2,
2265                        shading->getColorSpace()->getNComps(), 0);
2266  }
2267}
2268
2269void Gfx::gouraudFillTriangle(double x0, double y0, GfxColor *color0,
2270                              double x1, double y1, GfxColor *color1,
2271                              double x2, double y2, GfxColor *color2,
2272                              int nComps, int depth) {
2273  double x01, y01, x12, y12, x20, y20;
2274  GfxColor color01, color12, color20;
2275  int i;
2276
2277  for (i = 0; i < nComps; ++i) {
2278    if (abs(color0->c[i] - color1->c[i]) > gouraudColorDelta ||
2279        abs(color1->c[i] - color2->c[i]) > gouraudColorDelta) {
2280      break;
2281    }
2282  }
2283  if (i == nComps || depth == gouraudMaxDepth) {
2284    state->setFillColor(color0);
2285    out->updateFillColor(state);
2286    state->moveTo(x0, y0);
2287    state->lineTo(x1, y1);
2288    state->lineTo(x2, y2);
2289    state->closePath();
2290    out->fill(state);
2291    state->clearPath();
2292  } else {
2293    x01 = 0.5 * (x0 + x1);
2294    y01 = 0.5 * (y0 + y1);
2295    x12 = 0.5 * (x1 + x2);
2296    y12 = 0.5 * (y1 + y2);
2297    x20 = 0.5 * (x2 + x0);
2298    y20 = 0.5 * (y2 + y0);
2299    //~ if the shading has a Function, this should interpolate on the
2300    //~ function parameter, not on the color components
2301    for (i = 0; i < nComps; ++i) {
2302      color01.c[i] = (color0->c[i] + color1->c[i]) / 2;
2303      color12.c[i] = (color1->c[i] + color2->c[i]) / 2;
2304      color20.c[i] = (color2->c[i] + color0->c[i]) / 2;
2305    }
2306    gouraudFillTriangle(x0, y0, color0, x01, y01, &color01,
2307                        x20, y20, &color20, nComps, depth + 1);
2308    gouraudFillTriangle(x01, y01, &color01, x1, y1, color1,
2309                        x12, y12, &color12, nComps, depth + 1);
2310    gouraudFillTriangle(x01, y01, &color01, x12, y12, &color12,
2311                        x20, y20, &color20, nComps, depth + 1);
2312    gouraudFillTriangle(x20, y20, &color20, x12, y12, &color12,
2313                        x2, y2, color2, nComps, depth + 1);
2314  }
2315}
2316
2317void Gfx::doPatchMeshShFill(GfxPatchMeshShading *shading) {
2318  int start, i;
2319
2320  if (shading->getNPatches() > 128) {
2321    start = 3;
2322  } else if (shading->getNPatches() > 64) {
2323    start = 2;
2324  } else if (shading->getNPatches() > 16) {
2325    start = 1;
2326  } else {
2327    start = 0;
2328  }
2329  for (i = 0; i < shading->getNPatches(); ++i) {
2330    fillPatch(shading->getPatch(i), shading->getColorSpace()->getNComps(),
2331              start);
2332  }
2333}
2334
2335void Gfx::fillPatch(GfxPatch *patch, int nComps, int depth) {
2336  GfxPatch patch00, patch01, patch10, patch11;
2337  double xx[4][8], yy[4][8];
2338  double xxm, yym;
2339  int i;
2340
2341  for (i = 0; i < nComps; ++i) {
2342    if (abs(patch->color[0][0].c[i] - patch->color[0][1].c[i])
2343          > patchColorDelta ||
2344        abs(patch->color[0][1].c[i] - patch->color[1][1].c[i])
2345          > patchColorDelta ||
2346        abs(patch->color[1][1].c[i] - patch->color[1][0].c[i])
2347          > patchColorDelta ||
2348        abs(patch->color[1][0].c[i] - patch->color[0][0].c[i])
2349          > patchColorDelta) {
2350      break;
2351    }
2352  }
2353  if (i == nComps || depth == patchMaxDepth) {
2354    state->setFillColor(&patch->color[0][0]);
2355    out->updateFillColor(state);
2356    state->moveTo(patch->x[0][0], patch->y[0][0]);
2357    state->curveTo(patch->x[0][1], patch->y[0][1],
2358                   patch->x[0][2], patch->y[0][2],
2359                   patch->x[0][3], patch->y[0][3]);
2360    state->curveTo(patch->x[1][3], patch->y[1][3],
2361                   patch->x[2][3], patch->y[2][3],
2362                   patch->x[3][3], patch->y[3][3]);
2363    state->curveTo(patch->x[3][2], patch->y[3][2],
2364                   patch->x[3][1], patch->y[3][1],
2365                   patch->x[3][0], patch->y[3][0]);
2366    state->curveTo(patch->x[2][0], patch->y[2][0],
2367                   patch->x[1][0], patch->y[1][0],
2368                   patch->x[0][0], patch->y[0][0]);
2369    state->closePath();
2370    out->fill(state);
2371    state->clearPath();
2372  } else {
2373    for (i = 0; i < 4; ++i) {
2374      xx[i][0] = patch->x[i][0];
2375      yy[i][0] = patch->y[i][0];
2376      xx[i][1] = 0.5 * (patch->x[i][0] + patch->x[i][1]);
2377      yy[i][1] = 0.5 * (patch->y[i][0] + patch->y[i][1]);
2378      xxm = 0.5 * (patch->x[i][1] + patch->x[i][2]);
2379      yym = 0.5 * (patch->y[i][1] + patch->y[i][2]);
2380      xx[i][6] = 0.5 * (patch->x[i][2] + patch->x[i][3]);
2381      yy[i][6] = 0.5 * (patch->y[i][2] + patch->y[i][3]);
2382      xx[i][2] = 0.5 * (xx[i][1] + xxm);
2383      yy[i][2] = 0.5 * (yy[i][1] + yym);
2384      xx[i][5] = 0.5 * (xxm + xx[i][6]);
2385      yy[i][5] = 0.5 * (yym + yy[i][6]);
2386      xx[i][3] = xx[i][4] = 0.5 * (xx[i][2] + xx[i][5]);
2387      yy[i][3] = yy[i][4] = 0.5 * (yy[i][2] + yy[i][5]);
2388      xx[i][7] = patch->x[i][3];
2389      yy[i][7] = patch->y[i][3];
2390    }
2391    for (i = 0; i < 4; ++i) {
2392      patch00.x[0][i] = xx[0][i];
2393      patch00.y[0][i] = yy[0][i];
2394      patch00.x[1][i] = 0.5 * (xx[0][i] + xx[1][i]);
2395      patch00.y[1][i] = 0.5 * (yy[0][i] + yy[1][i]);
2396      xxm = 0.5 * (xx[1][i] + xx[2][i]);
2397      yym = 0.5 * (yy[1][i] + yy[2][i]);
2398      patch10.x[2][i] = 0.5 * (xx[2][i] + xx[3][i]);
2399      patch10.y[2][i] = 0.5 * (yy[2][i] + yy[3][i]);
2400      patch00.x[2][i] = 0.5 * (patch00.x[1][i] + xxm);
2401      patch00.y[2][i] = 0.5 * (patch00.y[1][i] + yym);
2402      patch10.x[1][i] = 0.5 * (xxm + patch10.x[2][i]);
2403      patch10.y[1][i] = 0.5 * (yym + patch10.y[2][i]);
2404      patch00.x[3][i] = 0.5 * (patch00.x[2][i] + patch10.x[1][i]);
2405      patch00.y[3][i] = 0.5 * (patch00.y[2][i] + patch10.y[1][i]);
2406      patch10.x[0][i] = patch00.x[3][i];
2407      patch10.y[0][i] = patch00.y[3][i];
2408      patch10.x[3][i] = xx[3][i];
2409      patch10.y[3][i] = yy[3][i];
2410    }
2411    for (i = 4; i < 8; ++i) {
2412      patch01.x[0][i-4] = xx[0][i];
2413      patch01.y[0][i-4] = yy[0][i];
2414      patch01.x[1][i-4] = 0.5 * (xx[0][i] + xx[1][i]);
2415      patch01.y[1][i-4] = 0.5 * (yy[0][i] + yy[1][i]);
2416      xxm = 0.5 * (xx[1][i] + xx[2][i]);
2417      yym = 0.5 * (yy[1][i] + yy[2][i]);
2418      patch11.x[2][i-4] = 0.5 * (xx[2][i] + xx[3][i]);
2419      patch11.y[2][i-4] = 0.5 * (yy[2][i] + yy[3][i]);
2420      patch01.x[2][i-4] = 0.5 * (patch01.x[1][i-4] + xxm);
2421      patch01.y[2][i-4] = 0.5 * (patch01.y[1][i-4] + yym);
2422      patch11.x[1][i-4] = 0.5 * (xxm + patch11.x[2][i-4]);
2423      patch11.y[1][i-4] = 0.5 * (yym + patch11.y[2][i-4]);
2424      patch01.x[3][i-4] = 0.5 * (patch01.x[2][i-4] + patch11.x[1][i-4]);
2425      patch01.y[3][i-4] = 0.5 * (patch01.y[2][i-4] + patch11.y[1][i-4]);
2426      patch11.x[0][i-4] = patch01.x[3][i-4];
2427      patch11.y[0][i-4] = patch01.y[3][i-4];
2428      patch11.x[3][i-4] = xx[3][i];
2429      patch11.y[3][i-4] = yy[3][i];
2430    }
2431    //~ if the shading has a Function, this should interpolate on the
2432    //~ function parameter, not on the color components
2433    for (i = 0; i < nComps; ++i) {
2434      patch00.color[0][0].c[i] = patch->color[0][0].c[i];
2435      patch00.color[0][1].c[i] = (patch->color[0][0].c[i] +
2436                                  patch->color[0][1].c[i]) / 2;
2437      patch01.color[0][0].c[i] = patch00.color[0][1].c[i];
2438      patch01.color[0][1].c[i] = patch->color[0][1].c[i];
2439      patch01.color[1][1].c[i] = (patch->color[0][1].c[i] +
2440                                  patch->color[1][1].c[i]) / 2;
2441      patch11.color[0][1].c[i] = patch01.color[1][1].c[i];
2442      patch11.color[1][1].c[i] = patch->color[1][1].c[i];
2443      patch11.color[1][0].c[i] = (patch->color[1][1].c[i] +
2444                                  patch->color[1][0].c[i]) / 2;
2445      patch10.color[1][1].c[i] = patch11.color[1][0].c[i];
2446      patch10.color[1][0].c[i] = patch->color[1][0].c[i];
2447      patch10.color[0][0].c[i] = (patch->color[1][0].c[i] +
2448                                  patch->color[0][0].c[i]) / 2;
2449      patch00.color[1][0].c[i] = patch10.color[0][0].c[i];
2450      patch00.color[1][1].c[i] = (patch00.color[1][0].c[i] +
2451                                  patch01.color[1][1].c[i]) / 2;
2452      patch01.color[1][0].c[i] = patch00.color[1][1].c[i];
2453      patch11.color[0][0].c[i] = patch00.color[1][1].c[i];
2454      patch10.color[0][1].c[i] = patch00.color[1][1].c[i];
2455    }
2456    fillPatch(&patch00, nComps, depth + 1);
2457    fillPatch(&patch10, nComps, depth + 1);
2458    fillPatch(&patch01, nComps, depth + 1);
2459    fillPatch(&patch11, nComps, depth + 1);
2460  }
2461}
2462
2463void Gfx::doEndPath() {
2464  if (state->isCurPt() && clip != clipNone) {
2465    state->clip();
2466    if (clip == clipNormal) {
2467      out->clip(state);
2468    } else {
2469      out->eoClip(state);
2470    }
2471  }
2472  clip = clipNone;
2473  state->clearPath();
2474}
2475
2476//------------------------------------------------------------------------
2477// path clipping operators
2478//------------------------------------------------------------------------
2479
2480void Gfx::opClip(Object args[], int numArgs) {
2481  clip = clipNormal;
2482}
2483
2484void Gfx::opEOClip(Object args[], int numArgs) {
2485  clip = clipEO;
2486}
2487
2488//------------------------------------------------------------------------
2489// text object operators
2490//------------------------------------------------------------------------
2491
2492void Gfx::opBeginText(Object args[], int numArgs) {
2493  state->setTextMat(1, 0, 0, 1, 0, 0);
2494  state->textMoveTo(0, 0);
2495  out->updateTextMat(state);
2496  out->updateTextPos(state);
2497  fontChanged = gTrue;
2498}
2499
2500void Gfx::opEndText(Object args[], int numArgs) {
2501  out->endTextObject(state);
2502}
2503
2504//------------------------------------------------------------------------
2505// text state operators
2506//------------------------------------------------------------------------
2507
2508void Gfx::opSetCharSpacing(Object args[], int numArgs) {
2509  state->setCharSpace(args[0].getNum());
2510  out->updateCharSpace(state);
2511}
2512
2513void Gfx::opSetFont(Object args[], int numArgs) {
2514  GfxFont *font;
2515
2516  if (!(font = res->lookupFont(args[0].getName()))) {
2517    return;
2518  }
2519  if (printCommands) {
2520    printf("  font: tag=%s name='%s' %g\n",
2521           font->getTag()->getCString(),
2522           font->getName() ? font->getName()->getCString() : "???",
2523           args[1].getNum());
2524    fflush(stdout);
2525  }
2526  state->setFont(font, args[1].getNum());
2527  fontChanged = gTrue;
2528}
2529
2530void Gfx::opSetTextLeading(Object args[], int numArgs) {
2531  state->setLeading(args[0].getNum());
2532}
2533
2534void Gfx::opSetTextRender(Object args[], int numArgs) {
2535  state->setRender(args[0].getInt());
2536  out->updateRender(state);
2537}
2538
2539void Gfx::opSetTextRise(Object args[], int numArgs) {
2540  state->setRise(args[0].getNum());
2541  out->updateRise(state);
2542}
2543
2544void Gfx::opSetWordSpacing(Object args[], int numArgs) {
2545  state->setWordSpace(args[0].getNum());
2546  out->updateWordSpace(state);
2547}
2548
2549void Gfx::opSetHorizScaling(Object args[], int numArgs) {
2550  state->setHorizScaling(args[0].getNum());
2551  out->updateHorizScaling(state);
2552  fontChanged = gTrue;
2553}
2554
2555//------------------------------------------------------------------------
2556// text positioning operators
2557//------------------------------------------------------------------------
2558
2559void Gfx::opTextMove(Object args[], int numArgs) {
2560  double tx, ty;
2561
2562  tx = state->getLineX() + args[0].getNum();
2563  ty = state->getLineY() + args[1].getNum();
2564  state->textMoveTo(tx, ty);
2565  out->updateTextPos(state);
2566}
2567
2568void Gfx::opTextMoveSet(Object args[], int numArgs) {
2569  double tx, ty;
2570
2571  tx = state->getLineX() + args[0].getNum();
2572  ty = args[1].getNum();
2573  state->setLeading(-ty);
2574  ty += state->getLineY();
2575  state->textMoveTo(tx, ty);
2576  out->updateTextPos(state);
2577}
2578
2579void Gfx::opSetTextMatrix(Object args[], int numArgs) {
2580  state->setTextMat(args[0].getNum(), args[1].getNum(),
2581                    args[2].getNum(), args[3].getNum(),
2582                    args[4].getNum(), args[5].getNum());
2583  state->textMoveTo(0, 0);
2584  out->updateTextMat(state);
2585  out->updateTextPos(state);
2586  fontChanged = gTrue;
2587}
2588
2589void Gfx::opTextNextLine(Object args[], int numArgs) {
2590  double tx, ty;
2591
2592  tx = state->getLineX();
2593  ty = state->getLineY() - state->getLeading();
2594  state->textMoveTo(tx, ty);
2595  out->updateTextPos(state);
2596}
2597
2598//------------------------------------------------------------------------
2599// text string operators
2600//------------------------------------------------------------------------
2601
2602void Gfx::opShowText(Object args[], int numArgs) {
2603  if (!state->getFont()) {
2604    error(getPos(), "No font in show");
2605    return;
2606  }
2607  if (fontChanged) {
2608    out->updateFont(state);
2609    fontChanged = gFalse;
2610  }
2611  out->beginStringOp(state);
2612  doShowText(args[0].getString());
2613  out->endStringOp(state);
2614}
2615
2616void Gfx::opMoveShowText(Object args[], int numArgs) {
2617  double tx, ty;
2618
2619  if (!state->getFont()) {
2620    error(getPos(), "No font in move/show");
2621    return;
2622  }
2623  if (fontChanged) {
2624    out->updateFont(state);
2625    fontChanged = gFalse;
2626  }
2627  tx = state->getLineX();
2628  ty = state->getLineY() - state->getLeading();
2629  state->textMoveTo(tx, ty);
2630  out->updateTextPos(state);
2631  out->beginStringOp(state);
2632  doShowText(args[0].getString());
2633  out->endStringOp(state);
2634}
2635
2636void Gfx::opMoveSetShowText(Object args[], int numArgs) {
2637  double tx, ty;
2638
2639  if (!state->getFont()) {
2640    error(getPos(), "No font in move/set/show");
2641    return;
2642  }
2643  if (fontChanged) {
2644    out->updateFont(state);
2645    fontChanged = gFalse;
2646  }
2647  state->setWordSpace(args[0].getNum());
2648  state->setCharSpace(args[1].getNum());
2649  tx = state->getLineX();
2650  ty = state->getLineY() - state->getLeading();
2651  state->textMoveTo(tx, ty);
2652  out->updateWordSpace(state);
2653  out->updateCharSpace(state);
2654  out->updateTextPos(state);
2655  out->beginStringOp(state);
2656  doShowText(args[2].getString());
2657  out->endStringOp(state);
2658}
2659
2660void Gfx::opShowSpaceText(Object args[], int numArgs) {
2661  Array *a;
2662  Object obj;
2663  int wMode;
2664  int i;
2665
2666  if (!state->getFont()) {
2667    error(getPos(), "No font in show/space");
2668    return;
2669  }
2670  if (fontChanged) {
2671    out->updateFont(state);
2672    fontChanged = gFalse;
2673  }
2674  out->beginStringOp(state);
2675  wMode = state->getFont()->getWMode();
2676  a = args[0].getArray();
2677  for (i = 0; i < a->getLength(); ++i) {
2678    a->get(i, &obj);
2679    if (obj.isNum()) {
2680      // this uses the absolute value of the font size to match
2681      // Acrobat's behavior
2682      if (wMode) {
2683        state->textShift(0, -obj.getNum() * 0.001 *
2684                            fabs(state->getFontSize()));
2685      } else {
2686        state->textShift(-obj.getNum() * 0.001 *
2687                         fabs(state->getFontSize()), 0);
2688      }
2689      out->updateTextShift(state, obj.getNum());
2690    } else if (obj.isString()) {
2691      doShowText(obj.getString());
2692    } else {
2693      error(getPos(), "Element of show/space array must be number or string");
2694    }
2695    obj.free();
2696  }
2697  out->endStringOp(state);
2698}
2699
2700void Gfx::doShowText(GooString *s) {
2701  GfxFont *font;
2702  int wMode;
2703  double riseX, riseY;
2704  CharCode code;
2705  Unicode u[8];
2706  double x, y, dx, dy, dx2, dy2, curX, curY, tdx, tdy, lineX, lineY;
2707  double originX, originY, tOriginX, tOriginY;
2708  double oldCTM[6], newCTM[6];
2709  double *mat;
2710  Object charProc;
2711  Dict *resDict;
2712  Parser *oldParser;
2713  char *p;
2714  int len, n, uLen, nChars, nSpaces, i;
2715
2716  font = state->getFont();
2717  wMode = font->getWMode();
2718
2719  if (out->useDrawChar()) {
2720    out->beginString(state, s);
2721  }
2722
2723  // handle a Type 3 char
2724  if (font->getType() == fontType3 && out->interpretType3Chars()) {
2725    mat = state->getCTM();
2726    for (i = 0; i < 6; ++i) {
2727      oldCTM[i] = mat[i];
2728    }
2729    mat = state->getTextMat();
2730    newCTM[0] = mat[0] * oldCTM[0] + mat[1] * oldCTM[2];
2731    newCTM[1] = mat[0] * oldCTM[1] + mat[1] * oldCTM[3];
2732    newCTM[2] = mat[2] * oldCTM[0] + mat[3] * oldCTM[2];
2733    newCTM[3] = mat[2] * oldCTM[1] + mat[3] * oldCTM[3];
2734    mat = font->getFontMatrix();
2735    newCTM[0] = mat[0] * newCTM[0] + mat[1] * newCTM[2];
2736    newCTM[1] = mat[0] * newCTM[1] + mat[1] * newCTM[3];
2737    newCTM[2] = mat[2] * newCTM[0] + mat[3] * newCTM[2];
2738    newCTM[3] = mat[2] * newCTM[1] + mat[3] * newCTM[3];
2739    newCTM[0] *= state->getFontSize();
2740    newCTM[1] *= state->getFontSize();
2741    newCTM[2] *= state->getFontSize();
2742    newCTM[3] *= state->getFontSize();
2743    newCTM[0] *= state->getHorizScaling();
2744    newCTM[2] *= state->getHorizScaling();
2745    state->textTransformDelta(0, state->getRise(), &riseX, &riseY);
2746    curX = state->getCurX();
2747    curY = state->getCurY();
2748    lineX = state->getLineX();
2749    lineY = state->getLineY();
2750    oldParser = parser;
2751    p = s->getCString();
2752    len = s->getLength();
2753    while (len > 0) {
2754      n = font->getNextChar(p, len, &code,
2755                            u, (int)(sizeof(u) / sizeof(Unicode)), &uLen,
2756                            &dx, &dy, &originX, &originY);
2757      dx = dx * state->getFontSize() + state->getCharSpace();
2758      if (n == 1 && *p == ' ') {
2759        dx += state->getWordSpace();
2760      }
2761      dx *= state->getHorizScaling();
2762      dy *= state->getFontSize();
2763      state->textTransformDelta(dx, dy, &tdx, &tdy);
2764      state->transform(curX + riseX, curY + riseY, &x, &y);
2765      saveState();
2766      state->setCTM(newCTM[0], newCTM[1], newCTM[2], newCTM[3], x, y);
2767      //~ out->updateCTM(???)
2768      if (!out->beginType3Char(state, curX + riseX, curY + riseY, tdx, tdy,
2769                               code, u, uLen)) {
2770        ((Gfx8BitFont *)font)->getCharProc(code, &charProc);
2771        if ((resDict = ((Gfx8BitFont *)font)->getResources())) {
2772          pushResources(resDict);
2773        }
2774        if (charProc.isStream()) {
2775          display(&charProc, gFalse);
2776        } else {
2777          error(getPos(), "Missing or bad Type3 CharProc entry");
2778        }
2779        out->endType3Char(state);
2780        if (resDict) {
2781          popResources();
2782        }
2783        charProc.free();
2784      }
2785      restoreState();
2786      // GfxState::restore() does *not* restore the current position,
2787      // so we deal with it here using (curX, curY) and (lineX, lineY)
2788      curX += tdx;
2789      curY += tdy;
2790      state->moveTo(curX, curY);
2791      state->textSetPos(lineX, lineY);
2792      p += n;
2793      len -= n;
2794    }
2795    parser = oldParser;
2796
2797  } else if (out->useDrawChar()) {
2798    state->textTransformDelta(0, state->getRise(), &riseX, &riseY);
2799    p = s->getCString();
2800    len = s->getLength();
2801    while (len > 0) {
2802      n = font->getNextChar(p, len, &code,
2803                            u, (int)(sizeof(u) / sizeof(Unicode)), &uLen,
2804                            &dx, &dy, &originX, &originY);
2805      if (wMode) {
2806        dx *= state->getFontSize();
2807        dy = dy * state->getFontSize() + state->getCharSpace();
2808        if (n == 1 && *p == ' ') {
2809          dy += state->getWordSpace();
2810        }
2811      } else {
2812        dx = dx * state->getFontSize() + state->getCharSpace();
2813        if (n == 1 && *p == ' ') {
2814          dx += state->getWordSpace();
2815        }
2816        dx *= state->getHorizScaling();
2817        dy *= state->getFontSize();
2818      }
2819      state->textTransformDelta(dx, dy, &tdx, &tdy);
2820      originX *= state->getFontSize();
2821      originY *= state->getFontSize();
2822      state->textTransformDelta(originX, originY, &tOriginX, &tOriginY);
2823      out->drawChar(state, state->getCurX() + riseX, state->getCurY() + riseY,
2824                    tdx, tdy, tOriginX, tOriginY, code, n, u, uLen);
2825      state->shift(tdx, tdy);
2826      p += n;
2827      len -= n;
2828    }
2829
2830  } else {
2831    dx = dy = 0;
2832    p = s->getCString();
2833    len = s->getLength();
2834    nChars = nSpaces = 0;
2835    while (len > 0) {
2836      n = font->getNextChar(p, len, &code,
2837                            u, (int)(sizeof(u) / sizeof(Unicode)), &uLen,
2838                            &dx2, &dy2, &originX, &originY);
2839      dx += dx2;
2840      dy += dy2;
2841      if (n == 1 && *p == ' ') {
2842        ++nSpaces;
2843      }
2844      ++nChars;
2845      p += n;
2846      len -= n;
2847    }
2848    if (wMode) {
2849      dx *= state->getFontSize();
2850      dy = dy * state->getFontSize()
2851           + nChars * state->getCharSpace()
2852           + nSpaces * state->getWordSpace();
2853    } else {
2854      dx = dx * state->getFontSize()
2855           + nChars * state->getCharSpace()
2856           + nSpaces * state->getWordSpace();
2857      dx *= state->getHorizScaling();
2858      dy *= state->getFontSize();
2859    }
2860    state->textTransformDelta(dx, dy, &tdx, &tdy);
2861    out->drawString(state, s);
2862    state->shift(tdx, tdy);
2863  }
2864
2865  if (out->useDrawChar()) {
2866    out->endString(state);
2867  }
2868
2869  updateLevel += 10 * s->getLength();
2870}
2871
2872//------------------------------------------------------------------------
2873// XObject operators
2874//------------------------------------------------------------------------
2875
2876void Gfx::opXObject(Object args[], int numArgs) {
2877  Object obj1, obj2, obj3, refObj;
2878#if OPI_SUPPORT
2879  Object opiDict;
2880#endif
2881
2882  if (!res->lookupXObject(args[0].getName(), &obj1)) {
2883    return;
2884  }
2885  if (!obj1.isStream()) {
2886    error(getPos(), "XObject '%s' is wrong type", args[0].getName());
2887    obj1.free();
2888    return;
2889  }
2890#if OPI_SUPPORT
2891  obj1.streamGetDict()->lookup("OPI", &opiDict);
2892  if (opiDict.isDict()) {
2893    out->opiBegin(state, opiDict.getDict());
2894  }
2895#endif
2896  obj1.streamGetDict()->lookup("Subtype", &obj2);
2897  if (obj2.isName("Image")) {
2898    if (out->needNonText()) {
2899    res->lookupXObjectNF(args[0].getName(), &refObj);
2900    doImage(&refObj, obj1.getStream(), gFalse);
2901    refObj.free();
2902    }
2903  } else if (obj2.isName("Form")) {
2904    doForm(&obj1);
2905  } else if (obj2.isName("PS")) {
2906    obj1.streamGetDict()->lookup("Level1", &obj3);
2907    out->psXObject(obj1.getStream(),
2908                   obj3.isStream() ? obj3.getStream() : (Stream *)NULL);
2909  } else if (obj2.isName()) {
2910    error(getPos(), "Unknown XObject subtype '%s'", obj2.getName());
2911  } else {
2912    error(getPos(), "XObject subtype is missing or wrong type");
2913  }
2914  obj2.free();
2915#if OPI_SUPPORT
2916  if (opiDict.isDict()) {
2917    out->opiEnd(state, opiDict.getDict());
2918  }
2919  opiDict.free();
2920#endif
2921  obj1.free();
2922}
2923
2924void Gfx::doImage(Object *ref, Stream *str, GBool inlineImg) {
2925  Dict *dict, *maskDict;
2926  int width, height;
2927  int bits, maskBits;
2928  StreamColorSpaceMode csMode;
2929  GBool mask;
2930  GBool invert;
2931  GfxColorSpace *colorSpace, *maskColorSpace;
2932  GfxImageColorMap *colorMap, *maskColorMap;
2933  Object maskObj, smaskObj;
2934  GBool haveColorKeyMask, haveExplicitMask, haveSoftMask;
2935  int maskColors[2*gfxColorMaxComps];
2936  int maskWidth, maskHeight;
2937  GBool maskInvert;
2938  Stream *maskStr;
2939  Object obj1, obj2;
2940  int i;
2941
2942  // get info from the stream
2943  bits = 0;
2944  csMode = streamCSNone;
2945  str->getImageParams(&bits, &csMode);
2946
2947  // get stream dict
2948  dict = str->getDict();
2949
2950  // get size
2951  dict->lookup("Width", &obj1);
2952  if (obj1.isNull()) {
2953    obj1.free();
2954    dict->lookup("W", &obj1);
2955  }
2956  if (!obj1.isInt())
2957    goto err2;
2958  width = obj1.getInt();
2959  obj1.free();
2960  dict->lookup("Height", &obj1);
2961  if (obj1.isNull()) {
2962    obj1.free();
2963    dict->lookup("H", &obj1);
2964  }
2965  if (!obj1.isInt())
2966    goto err2;
2967  height = obj1.getInt();
2968  obj1.free();
2969
2970  // image or mask?
2971  dict->lookup("ImageMask", &obj1);
2972  if (obj1.isNull()) {
2973    obj1.free();
2974    dict->lookup("IM", &obj1);
2975  }
2976  mask = gFalse;
2977  if (obj1.isBool())
2978    mask = obj1.getBool();
2979  else if (!obj1.isNull())
2980    goto err2;
2981  obj1.free();
2982
2983  // bit depth
2984  if (bits == 0) {
2985  dict->lookup("BitsPerComponent", &obj1);
2986  if (obj1.isNull()) {
2987    obj1.free();
2988    dict->lookup("BPC", &obj1);
2989  }
2990  if (obj1.isInt()) {
2991    bits = obj1.getInt();
2992  } else if (mask) {
2993    bits = 1;
2994  } else {
2995    goto err2;
2996  }
2997  obj1.free();
2998  }
2999
3000  // display a mask
3001  if (mask) {
3002
3003    // check for inverted mask
3004    if (bits != 1)
3005      goto err1;
3006    invert = gFalse;
3007    dict->lookup("Decode", &obj1);
3008    if (obj1.isNull()) {
3009      obj1.free();
3010      dict->lookup("D", &obj1);
3011    }
3012    if (obj1.isArray()) {
3013      obj1.arrayGet(0, &obj2);
3014      if (obj2.isInt() && obj2.getInt() == 1)
3015        invert = gTrue;
3016      obj2.free();
3017    } else if (!obj1.isNull()) {
3018      goto err2;
3019    }
3020    obj1.free();
3021
3022    // draw it
3023    out->drawImageMask(state, ref, str, width, height, invert, inlineImg);
3024
3025  } else {
3026
3027    // get color space and color map
3028    dict->lookup("ColorSpace", &obj1);
3029    if (obj1.isNull()) {
3030      obj1.free();
3031      dict->lookup("CS", &obj1);
3032    }
3033    if (obj1.isName()) {
3034      res->lookupColorSpace(obj1.getName(), &obj2);
3035      if (!obj2.isNull()) {
3036        obj1.free();
3037        obj1 = obj2;
3038      } else {
3039        obj2.free();
3040      }
3041    }
3042    if (!obj1.isNull()) {
3043    colorSpace = GfxColorSpace::parse(&obj1);
3044    } else if (csMode == streamCSDeviceGray) {
3045      colorSpace = new GfxDeviceGrayColorSpace();
3046    } else if (csMode == streamCSDeviceRGB) {
3047      colorSpace = new GfxDeviceRGBColorSpace();
3048    } else if (csMode == streamCSDeviceCMYK) {
3049      colorSpace = new GfxDeviceCMYKColorSpace();
3050    } else {
3051      colorSpace = NULL;
3052    }
3053    obj1.free();
3054    if (!colorSpace) {
3055      goto err1;
3056    }
3057    dict->lookup("Decode", &obj1);
3058    if (obj1.isNull()) {
3059      obj1.free();
3060      dict->lookup("D", &obj1);
3061    }
3062    colorMap = new GfxImageColorMap(bits, &obj1, colorSpace);
3063    obj1.free();
3064    if (!colorMap->isOk()) {
3065      delete colorMap;
3066      goto err1;
3067    }
3068
3069    // get the mask
3070    haveColorKeyMask = haveExplicitMask = haveSoftMask = gFalse;
3071    maskStr = NULL; // make gcc happy
3072    maskWidth = maskHeight = 0; // make gcc happy
3073    maskInvert = gFalse; // make gcc happy
3074    maskColorMap = NULL; // make gcc happy
3075    dict->lookup("Mask", &maskObj);
3076    dict->lookup("SMask", &smaskObj);
3077    if (smaskObj.isStream()) {
3078      // soft mask
3079      if (inlineImg) {
3080        goto err1;
3081      }
3082      maskStr = smaskObj.getStream();
3083      maskDict = smaskObj.streamGetDict();
3084      maskDict->lookup("Width", &obj1);
3085      if (obj1.isNull()) {
3086        obj1.free();
3087        maskDict->lookup("W", &obj1);
3088      }
3089      if (!obj1.isInt()) {
3090        goto err2;
3091      }
3092      maskWidth = obj1.getInt();
3093      obj1.free();
3094      maskDict->lookup("Height", &obj1);
3095      if (obj1.isNull()) {
3096        obj1.free();
3097        maskDict->lookup("H", &obj1);
3098      }
3099      if (!obj1.isInt()) {
3100        goto err2;
3101      }
3102      maskHeight = obj1.getInt();
3103      obj1.free();
3104      maskDict->lookup("BitsPerComponent", &obj1);
3105      if (obj1.isNull()) {
3106        obj1.free();
3107        maskDict->lookup("BPC", &obj1);
3108      }
3109      if (!obj1.isInt()) {
3110        goto err2;
3111      }
3112      maskBits = obj1.getInt();
3113      obj1.free();
3114      maskDict->lookup("ColorSpace", &obj1);
3115      if (obj1.isNull()) {
3116        obj1.free();
3117        maskDict->lookup("CS", &obj1);
3118      }
3119      if (obj1.isName()) {
3120        res->lookupColorSpace(obj1.getName(), &obj2);
3121        if (!obj2.isNull()) {
3122          obj1.free();
3123          obj1 = obj2;
3124        } else {
3125          obj2.free();
3126        }
3127      }
3128      maskColorSpace = GfxColorSpace::parse(&obj1);
3129      obj1.free();
3130      if (!maskColorSpace || maskColorSpace->getMode() != csDeviceGray) {
3131        goto err1;
3132      }
3133      maskDict->lookup("Decode", &obj1);
3134      if (obj1.isNull()) {
3135        obj1.free();
3136        maskDict->lookup("D", &obj1);
3137      }
3138      maskColorMap = new GfxImageColorMap(maskBits, &obj1, maskColorSpace);
3139      obj1.free();
3140      if (!maskColorMap->isOk()) {
3141        delete maskColorMap;
3142        goto err1;
3143      }
3144      //~ handle the Matte entry
3145      haveSoftMask = gTrue;
3146    } else if (maskObj.isArray()) {
3147      // color key mask
3148      for (i = 0;
3149           i < maskObj.arrayGetLength() && i < 2*gfxColorMaxComps;
3150           ++i) {
3151        maskObj.arrayGet(i, &obj1);
3152        maskColors[i] = obj1.getInt();
3153        obj1.free();
3154      }
3155      haveColorKeyMask = gTrue;
3156    } else if (maskObj.isStream()) {
3157      // explicit mask
3158      if (inlineImg) {
3159        goto err1;
3160      }
3161      maskStr = maskObj.getStream();
3162      maskDict = maskObj.streamGetDict();
3163      maskDict->lookup("Width", &obj1);
3164      if (obj1.isNull()) {
3165        obj1.free();
3166        maskDict->lookup("W", &obj1);
3167      }
3168      if (!obj1.isInt()) {
3169        goto err2;
3170      }
3171      maskWidth = obj1.getInt();
3172      obj1.free();
3173      maskDict->lookup("Height", &obj1);
3174      if (obj1.isNull()) {
3175        obj1.free();
3176        maskDict->lookup("H", &obj1);
3177      }
3178      if (!obj1.isInt()) {
3179        goto err2;
3180      }
3181      maskHeight = obj1.getInt();
3182      obj1.free();
3183      maskDict->lookup("ImageMask", &obj1);
3184      if (obj1.isNull()) {
3185        obj1.free();
3186        maskDict->lookup("IM", &obj1);
3187      }
3188      if (!obj1.isBool() || !obj1.getBool()) {
3189        goto err2;
3190      }
3191      obj1.free();
3192      maskInvert = gFalse;
3193      maskDict->lookup("Decode", &obj1);
3194      if (obj1.isNull()) {
3195        obj1.free();
3196        maskDict->lookup("D", &obj1);
3197      }
3198      if (obj1.isArray()) {
3199        obj1.arrayGet(0, &obj2);
3200        if (obj2.isInt() && obj2.getInt() == 1) {
3201          maskInvert = gTrue;
3202        }
3203        obj2.free();
3204      } else if (!obj1.isNull()) {
3205        goto err2;
3206      }
3207      obj1.free();
3208      haveExplicitMask = gTrue;
3209    }
3210
3211    // draw it
3212    if (haveSoftMask) {
3213      out->drawSoftMaskedImage(state, ref, str, width, height, colorMap,
3214                               maskStr, maskWidth, maskHeight, maskColorMap);
3215      delete maskColorMap;
3216    } else if (haveExplicitMask) {
3217      out->drawMaskedImage(state, ref, str, width, height, colorMap,
3218                           maskStr, maskWidth, maskHeight, maskInvert);
3219    } else {
3220    out->drawImage(state, ref, str, width, height, colorMap,
3221                     haveColorKeyMask ? maskColors : (int *)NULL, inlineImg);
3222    }
3223    delete colorMap;
3224
3225    maskObj.free();
3226    smaskObj.free();
3227  }
3228
3229  if ((i = width * height) > 1000) {
3230    i = 1000;
3231  }
3232  updateLevel += i;
3233
3234  return;
3235
3236 err2:
3237  obj1.free();
3238 err1:
3239  error(getPos(), "Bad image parameters");
3240}
3241
3242void Gfx::doForm(Object *str) {
3243  Dict *dict;
3244  Object matrixObj, bboxObj;
3245  double m[6], bbox[6];
3246  Object resObj;
3247  Dict *resDict;
3248  Object obj1;
3249  int i;
3250
3251  // check for excessive recursion
3252  if (formDepth > 20) {
3253    return;
3254  }
3255
3256  // get stream dict
3257  dict = str->streamGetDict();
3258
3259  // check form type
3260  dict->lookup("FormType", &obj1);
3261  if (!(obj1.isNull() || (obj1.isInt() && obj1.getInt() == 1))) {
3262    error(getPos(), "Unknown form type");
3263  }
3264  obj1.free();
3265
3266  // get bounding box
3267  dict->lookup("BBox", &bboxObj);
3268  if (!bboxObj.isArray()) {
3269    matrixObj.free();
3270    bboxObj.free();
3271    error(getPos(), "Bad form bounding box");
3272    return;
3273  }
3274  for (i = 0; i < 4; ++i) {
3275    bboxObj.arrayGet(i, &obj1);
3276    bbox[i] = obj1.getNum();
3277    obj1.free();
3278  }
3279  bboxObj.free();
3280
3281  // get matrix
3282  dict->lookup("Matrix", &matrixObj);
3283  if (matrixObj.isArray()) {
3284    for (i = 0; i < 6; ++i) {
3285      matrixObj.arrayGet(i, &obj1);
3286      m[i] = obj1.getNum();
3287      obj1.free();
3288    }
3289  } else {
3290    m[0] = 1; m[1] = 0;
3291    m[2] = 0; m[3] = 1;
3292    m[4] = 0; m[5] = 0;
3293  }
3294  matrixObj.free();
3295
3296  // get resources
3297  dict->lookup("Resources", &resObj);
3298  resDict = resObj.isDict() ? resObj.getDict() : (Dict *)NULL;
3299
3300  // draw it
3301  ++formDepth;
3302  doForm1(str, resDict, m, bbox);
3303  --formDepth;
3304
3305  resObj.free();
3306}
3307
3308void Gfx::doAnnot(Object *str, double xMin, double yMin,
3309                  double xMax, double yMax) {
3310  Dict *dict, *resDict;
3311  Object matrixObj, bboxObj, resObj;
3312  Object obj1;
3313  double m[6], bbox[6], ictm[6];
3314  double *ctm;
3315  double formX0, formY0, formX1, formY1;
3316  double annotX0, annotY0, annotX1, annotY1;
3317  double det, x, y, sx, sy;
3318  int i;
3319
3320  // get stream dict
3321  dict = str->streamGetDict();
3322
3323  // get the form bounding box
3324  dict->lookup("BBox", &bboxObj);
3325  if (!bboxObj.isArray()) {
3326    bboxObj.free();
3327    error(getPos(), "Bad form bounding box");
3328    return;
3329  }
3330  for (i = 0; i < 4; ++i) {
3331    bboxObj.arrayGet(i, &obj1);
3332    bbox[i] = obj1.getNum();
3333    obj1.free();
3334  }
3335  bboxObj.free();
3336
3337  // get the form matrix
3338  dict->lookup("Matrix", &matrixObj);
3339  if (matrixObj.isArray()) {
3340    for (i = 0; i < 6; ++i) {
3341      matrixObj.arrayGet(i, &obj1);
3342      m[i] = obj1.getNum();
3343      obj1.free();
3344    }
3345  } else {
3346    m[0] = 1; m[1] = 0;
3347    m[2] = 0; m[3] = 1;
3348    m[4] = 0; m[5] = 0;
3349  }
3350  matrixObj.free();
3351
3352  // transform the form bbox from form space to user space
3353  formX0 = bbox[0] * m[0] + bbox[1] * m[2] + m[4];
3354  formY0 = bbox[0] * m[1] + bbox[1] * m[3] + m[5];
3355  formX1 = bbox[2] * m[0] + bbox[3] * m[2] + m[4];
3356  formY1 = bbox[2] * m[1] + bbox[3] * m[3] + m[5];
3357
3358  // transform the annotation bbox from default user space to user
3359  // space: (bbox * baseMatrix) * iCTM
3360  ctm = state->getCTM();
3361  det = 1 / (ctm[0] * ctm[3] - ctm[1] * ctm[2]);
3362  ictm[0] = ctm[3] * det;
3363  ictm[1] = -ctm[1] * det;
3364  ictm[2] = -ctm[2] * det;
3365  ictm[3] = ctm[0] * det;
3366  ictm[4] = (ctm[2] * ctm[5] - ctm[3] * ctm[4]) * det;
3367  ictm[5] = (ctm[1] * ctm[4] - ctm[0] * ctm[5]) * det;
3368  x = baseMatrix[0] * xMin + baseMatrix[2] * yMin + baseMatrix[4];
3369  y = baseMatrix[1] * xMin + baseMatrix[3] * yMin + baseMatrix[5];
3370  annotX0 = ictm[0] * x + ictm[2] * y + ictm[4];
3371  annotY0 = ictm[1] * x + ictm[3] * y + ictm[5];
3372  x = baseMatrix[0] * xMax + baseMatrix[2] * yMax + baseMatrix[4];
3373  y = baseMatrix[1] * xMax + baseMatrix[3] * yMax + baseMatrix[5];
3374  annotX1 = ictm[0] * x + ictm[2] * y + ictm[4];
3375  annotY1 = ictm[1] * x + ictm[3] * y + ictm[5];
3376
3377  // swap min/max coords
3378  if (formX0 > formX1) {
3379    x = formX0; formX0 = formX1; formX1 = x;
3380  }
3381  if (formY0 > formY1) {
3382    y = formY0; formY0 = formY1; formY1 = y;
3383  }
3384  if (annotX0 > annotX1) {
3385    x = annotX0; annotX0 = annotX1; annotX1 = x;
3386  }
3387  if (annotY0 > annotY1) {
3388    y = annotY0; annotY0 = annotY1; annotY1 = y;
3389  }
3390
3391  // scale the form to fit the annotation bbox
3392  if (formX1 == formX0) {
3393    // this shouldn't happen
3394    sx = 1;
3395  } else {
3396    sx = (annotX1 - annotX0) / (formX1 - formX0);
3397  }
3398  if (formY1 == formY0) {
3399    // this shouldn't happen
3400    sy = 1;
3401  } else {
3402    sy = (annotY1 - annotY0) / (formY1 - formY0);
3403  }
3404  m[0] *= sx;
3405  m[2] *= sx;
3406  m[4] = (m[4] - formX0) * sx + annotX0;
3407  m[1] *= sy;
3408  m[3] *= sy;
3409  m[5] = (m[5] - formY0) * sy + annotY0;
3410
3411  // get resources
3412  dict->lookup("Resources", &resObj);
3413  resDict = resObj.isDict() ? resObj.getDict() : (Dict *)NULL;
3414
3415  // draw it
3416  doForm1(str, resDict, m, bbox);
3417
3418  resObj.free();
3419  bboxObj.free();
3420}
3421
3422void Gfx::doForm1(Object *str, Dict *resDict, double *matrix, double *bbox) {
3423  Parser *oldParser;
3424  double oldBaseMatrix[6];
3425  int i;
3426
3427  // push new resources on stack
3428  pushResources(resDict);
3429
3430  // save current graphics state
3431  saveState();
3432
3433  // kill any pre-existing path
3434  state->clearPath();
3435
3436  // save current parser
3437  oldParser = parser;
3438
3439  // set form transformation matrix
3440  state->concatCTM(matrix[0], matrix[1], matrix[2],
3441                   matrix[3], matrix[4], matrix[5]);
3442  out->updateCTM(state, matrix[0], matrix[1], matrix[2],
3443                 matrix[3], matrix[4], matrix[5]);
3444
3445  // set new base matrix
3446  for (i = 0; i < 6; ++i) {
3447    oldBaseMatrix[i] = baseMatrix[i];
3448    baseMatrix[i] = state->getCTM()[i];
3449  }
3450
3451  // set form bounding box
3452  state->moveTo(bbox[0], bbox[1]);
3453  state->lineTo(bbox[2], bbox[1]);
3454  state->lineTo(bbox[2], bbox[3]);
3455  state->lineTo(bbox[0], bbox[3]);
3456  state->closePath();
3457  state->clip();
3458  out->clip(state);
3459  state->clearPath();
3460
3461  // draw the form
3462  display(str, gFalse);
3463
3464  // restore base matrix
3465  for (i = 0; i < 6; ++i) {
3466    baseMatrix[i] = oldBaseMatrix[i];
3467  }
3468
3469  // restore parser
3470  parser = oldParser;
3471
3472  // restore graphics state
3473  restoreState();
3474
3475  // pop resource stack
3476  popResources();
3477
3478  return;
3479}
3480
3481//------------------------------------------------------------------------
3482// in-line image operators
3483//------------------------------------------------------------------------
3484
3485void Gfx::opBeginImage(Object args[], int numArgs) {
3486  Stream *str;
3487  int c1, c2;
3488
3489  // build dict/stream
3490  str = buildImageStream();
3491
3492  // display the image
3493  if (str) {
3494    doImage(NULL, str, gTrue);
3495 
3496    // skip 'EI' tag
3497    c1 = str->getBaseStream()->getChar();
3498    c2 = str->getBaseStream()->getChar();
3499    while (!(c1 == 'E' && c2 == 'I') && c2 != EOF) {
3500      c1 = c2;
3501      c2 = str->getBaseStream()->getChar();
3502    }
3503    delete str;
3504  }
3505}
3506
3507Stream *Gfx::buildImageStream() {
3508  Object dict;
3509  Object obj;
3510  char *key;
3511  Stream *str;
3512
3513  // build dictionary
3514  dict.initDict(xref);
3515  parser->getObj(&obj);
3516  while (!obj.isCmd("ID") && !obj.isEOF()) {
3517    if (!obj.isName()) {
3518      error(getPos(), "Inline image dictionary key must be a name object");
3519      obj.free();
3520    } else {
3521      key = copyString(obj.getName());
3522      obj.free();
3523      parser->getObj(&obj);
3524      if (obj.isEOF() || obj.isError()) {
3525        gfree(key);
3526        break;
3527      }
3528      dict.dictAdd(key, &obj);
3529      gfree(key);
3530    }
3531    parser->getObj(&obj);
3532  }
3533  if (obj.isEOF()) {
3534    error(getPos(), "End of file in inline image");
3535    obj.free();
3536    dict.free();
3537    return NULL;
3538  }
3539  obj.free();
3540
3541  // make stream
3542  str = new EmbedStream(parser->getStream(), &dict, gFalse, 0);
3543  str = str->addFilters(&dict);
3544
3545  return str;
3546}
3547
3548void Gfx::opImageData(Object args[], int numArgs) {
3549  error(getPos(), "Internal: got 'ID' operator");
3550}
3551
3552void Gfx::opEndImage(Object args[], int numArgs) {
3553  error(getPos(), "Internal: got 'EI' operator");
3554}
3555
3556//------------------------------------------------------------------------
3557// type 3 font operators
3558//------------------------------------------------------------------------
3559
3560void Gfx::opSetCharWidth(Object args[], int numArgs) {
3561  out->type3D0(state, args[0].getNum(), args[1].getNum());
3562}
3563
3564void Gfx::opSetCacheDevice(Object args[], int numArgs) {
3565  out->type3D1(state, args[0].getNum(), args[1].getNum(),
3566               args[2].getNum(), args[3].getNum(),
3567               args[4].getNum(), args[5].getNum());
3568}
3569
3570//------------------------------------------------------------------------
3571// compatibility operators
3572//------------------------------------------------------------------------
3573
3574void Gfx::opBeginIgnoreUndef(Object args[], int numArgs) {
3575  ++ignoreUndef;
3576}
3577
3578void Gfx::opEndIgnoreUndef(Object args[], int numArgs) {
3579  if (ignoreUndef > 0)
3580    --ignoreUndef;
3581}
3582
3583//------------------------------------------------------------------------
3584// marked content operators
3585//------------------------------------------------------------------------
3586
3587void Gfx::opBeginMarkedContent(Object args[], int numArgs) {
3588  if (printCommands) {
3589    printf("  marked content: %s ", args[0].getName());
3590    if (numArgs == 2)
3591      args[2].print(stdout);
3592    printf("\n");
3593    fflush(stdout);
3594  }
3595
3596  if(numArgs == 2) {
3597    out->beginMarkedContent(args[0].getName(),args[1].getDict());
3598  } else {
3599    out->beginMarkedContent(args[0].getName());
3600  }
3601}
3602
3603void Gfx::opEndMarkedContent(Object args[], int numArgs) {
3604  out->endMarkedContent();
3605}
3606
3607void Gfx::opMarkPoint(Object args[], int numArgs) {
3608  if (printCommands) {
3609    printf("  mark point: %s ", args[0].getName());
3610    if (numArgs == 2)
3611      args[2].print(stdout);
3612    printf("\n");
3613    fflush(stdout);
3614  }
3615
3616  if(numArgs == 2) {
3617    out->markPoint(args[0].getName(),args[1].getDict());
3618  } else {
3619    out->markPoint(args[0].getName());
3620  }
3621
3622}
3623
3624//------------------------------------------------------------------------
3625// misc
3626//------------------------------------------------------------------------
3627
3628void Gfx::saveState() {
3629  out->saveState(state);
3630  state = state->save();
3631}
3632
3633void Gfx::restoreState() {
3634  state = state->restore();
3635  out->restoreState(state);
3636}
3637
3638void Gfx::pushResources(Dict *resDict) {
3639  res = new GfxResources(xref, resDict, res);
3640}
3641
3642void Gfx::popResources() {
3643  GfxResources *resPtr;
3644
3645  resPtr = res->getNext();
3646  delete res;
3647  res = resPtr;
3648}
Note: See TracBrowser for help on using the repository browser.