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