* removed explicit use of cropbox for cropping
[swftools.git] / pdf2swf / xpdf / Gfx.cc
1 //========================================================================
2 //
3 // Gfx.cc
4 //
5 // Copyright 1996-2002 Glyph & Cog, LLC
6 //
7 //========================================================================
8
9 #ifdef __GNUC__
10 #pragma implementation
11 #endif
12
13 #include <aconf.h>
14 #include <stdio.h>
15 #include <stddef.h>
16 #include <string.h>
17 #include <math.h>
18 #include "gmem.h"
19 #include "CharTypes.h"
20 #include "Object.h"
21 #include "Array.h"
22 #include "Dict.h"
23 #include "Stream.h"
24 #include "Lexer.h"
25 #include "Parser.h"
26 #include "GfxFont.h"
27 #include "GfxState.h"
28 #include "OutputDev.h"
29 #include "Page.h"
30 #include "Error.h"
31 #include "Gfx.h"
32
33 // the MSVC math.h doesn't define this
34 #ifndef M_PI
35 #define M_PI 3.14159265358979323846
36 #endif
37
38 //------------------------------------------------------------------------
39 // constants
40 //------------------------------------------------------------------------
41
42 // Max number of splits along the t axis for an axial shading fill.
43 #define axialMaxSplits 256
44
45 // Max delta allowed in any color component for an axial shading fill.
46 #define axialColorDelta (1 / 256.0)
47
48 // Max number of splits along the t axis for a radial shading fill.
49 #define radialMaxSplits 256
50
51 // Max delta allowed in any color component for a radial shading fill.
52 #define radialColorDelta (1 / 256.0)
53
54 //------------------------------------------------------------------------
55 // Operator table
56 //------------------------------------------------------------------------
57
58 Operator Gfx::opTab[] = {
59   {"\"",  3, {tchkNum,    tchkNum,    tchkString},
60           &Gfx::opMoveSetShowText},
61   {"'",   1, {tchkString},
62           &Gfx::opMoveShowText},
63   {"B",   0, {tchkNone},
64           &Gfx::opFillStroke},
65   {"B*",  0, {tchkNone},
66           &Gfx::opEOFillStroke},
67   {"BDC", 2, {tchkName,   tchkProps},
68           &Gfx::opBeginMarkedContent},
69   {"BI",  0, {tchkNone},
70           &Gfx::opBeginImage},
71   {"BMC", 1, {tchkName},
72           &Gfx::opBeginMarkedContent},
73   {"BT",  0, {tchkNone},
74           &Gfx::opBeginText},
75   {"BX",  0, {tchkNone},
76           &Gfx::opBeginIgnoreUndef},
77   {"CS",  1, {tchkName},
78           &Gfx::opSetStrokeColorSpace},
79   {"DP",  2, {tchkName,   tchkProps},
80           &Gfx::opMarkPoint},
81   {"Do",  1, {tchkName},
82           &Gfx::opXObject},
83   {"EI",  0, {tchkNone},
84           &Gfx::opEndImage},
85   {"EMC", 0, {tchkNone},
86           &Gfx::opEndMarkedContent},
87   {"ET",  0, {tchkNone},
88           &Gfx::opEndText},
89   {"EX",  0, {tchkNone},
90           &Gfx::opEndIgnoreUndef},
91   {"F",   0, {tchkNone},
92           &Gfx::opFill},
93   {"G",   1, {tchkNum},
94           &Gfx::opSetStrokeGray},
95   {"ID",  0, {tchkNone},
96           &Gfx::opImageData},
97   {"J",   1, {tchkInt},
98           &Gfx::opSetLineCap},
99   {"K",   4, {tchkNum,    tchkNum,    tchkNum,    tchkNum},
100           &Gfx::opSetStrokeCMYKColor},
101   {"M",   1, {tchkNum},
102           &Gfx::opSetMiterLimit},
103   {"MP",  1, {tchkName},
104           &Gfx::opMarkPoint},
105   {"Q",   0, {tchkNone},
106           &Gfx::opRestore},
107   {"RG",  3, {tchkNum,    tchkNum,    tchkNum},
108           &Gfx::opSetStrokeRGBColor},
109   {"S",   0, {tchkNone},
110           &Gfx::opStroke},
111   {"SC",  -4, {tchkNum,   tchkNum,    tchkNum,    tchkNum},
112           &Gfx::opSetStrokeColor},
113   {"SCN", -5, {tchkSCN,   tchkSCN,    tchkSCN,    tchkSCN,
114                tchkSCN},
115           &Gfx::opSetStrokeColorN},
116   {"T*",  0, {tchkNone},
117           &Gfx::opTextNextLine},
118   {"TD",  2, {tchkNum,    tchkNum},
119           &Gfx::opTextMoveSet},
120   {"TJ",  1, {tchkArray},
121           &Gfx::opShowSpaceText},
122   {"TL",  1, {tchkNum},
123           &Gfx::opSetTextLeading},
124   {"Tc",  1, {tchkNum},
125           &Gfx::opSetCharSpacing},
126   {"Td",  2, {tchkNum,    tchkNum},
127           &Gfx::opTextMove},
128   {"Tf",  2, {tchkName,   tchkNum},
129           &Gfx::opSetFont},
130   {"Tj",  1, {tchkString},
131           &Gfx::opShowText},
132   {"Tm",  6, {tchkNum,    tchkNum,    tchkNum,    tchkNum,
133               tchkNum,    tchkNum},
134           &Gfx::opSetTextMatrix},
135   {"Tr",  1, {tchkInt},
136           &Gfx::opSetTextRender},
137   {"Ts",  1, {tchkNum},
138           &Gfx::opSetTextRise},
139   {"Tw",  1, {tchkNum},
140           &Gfx::opSetWordSpacing},
141   {"Tz",  1, {tchkNum},
142           &Gfx::opSetHorizScaling},
143   {"W",   0, {tchkNone},
144           &Gfx::opClip},
145   {"W*",  0, {tchkNone},
146           &Gfx::opEOClip},
147   {"b",   0, {tchkNone},
148           &Gfx::opCloseFillStroke},
149   {"b*",  0, {tchkNone},
150           &Gfx::opCloseEOFillStroke},
151   {"c",   6, {tchkNum,    tchkNum,    tchkNum,    tchkNum,
152               tchkNum,    tchkNum},
153           &Gfx::opCurveTo},
154   {"cm",  6, {tchkNum,    tchkNum,    tchkNum,    tchkNum,
155               tchkNum,    tchkNum},
156           &Gfx::opConcat},
157   {"cs",  1, {tchkName},
158           &Gfx::opSetFillColorSpace},
159   {"d",   2, {tchkArray,  tchkNum},
160           &Gfx::opSetDash},
161   {"d0",  2, {tchkNum,    tchkNum},
162           &Gfx::opSetCharWidth},
163   {"d1",  6, {tchkNum,    tchkNum,    tchkNum,    tchkNum,
164               tchkNum,    tchkNum},
165           &Gfx::opSetCacheDevice},
166   {"f",   0, {tchkNone},
167           &Gfx::opFill},
168   {"f*",  0, {tchkNone},
169           &Gfx::opEOFill},
170   {"g",   1, {tchkNum},
171           &Gfx::opSetFillGray},
172   {"gs",  1, {tchkName},
173           &Gfx::opSetExtGState},
174   {"h",   0, {tchkNone},
175           &Gfx::opClosePath},
176   {"i",   1, {tchkNum},
177           &Gfx::opSetFlat},
178   {"j",   1, {tchkInt},
179           &Gfx::opSetLineJoin},
180   {"k",   4, {tchkNum,    tchkNum,    tchkNum,    tchkNum},
181           &Gfx::opSetFillCMYKColor},
182   {"l",   2, {tchkNum,    tchkNum},
183           &Gfx::opLineTo},
184   {"m",   2, {tchkNum,    tchkNum},
185           &Gfx::opMoveTo},
186   {"n",   0, {tchkNone},
187           &Gfx::opEndPath},
188   {"q",   0, {tchkNone},
189           &Gfx::opSave},
190   {"re",  4, {tchkNum,    tchkNum,    tchkNum,    tchkNum},
191           &Gfx::opRectangle},
192   {"rg",  3, {tchkNum,    tchkNum,    tchkNum},
193           &Gfx::opSetFillRGBColor},
194   {"ri",  1, {tchkName},
195           &Gfx::opSetRenderingIntent},
196   {"s",   0, {tchkNone},
197           &Gfx::opCloseStroke},
198   {"sc",  -4, {tchkNum,   tchkNum,    tchkNum,    tchkNum},
199           &Gfx::opSetFillColor},
200   {"scn", -5, {tchkSCN,   tchkSCN,    tchkSCN,    tchkSCN,
201                tchkSCN},
202           &Gfx::opSetFillColorN},
203   {"sh",  1, {tchkName},
204           &Gfx::opShFill},
205   {"v",   4, {tchkNum,    tchkNum,    tchkNum,    tchkNum},
206           &Gfx::opCurveTo1},
207   {"w",   1, {tchkNum},
208           &Gfx::opSetLineWidth},
209   {"y",   4, {tchkNum,    tchkNum,    tchkNum,    tchkNum},
210           &Gfx::opCurveTo2},
211 };
212
213 #define numOps (sizeof(opTab) / sizeof(Operator))
214
215 //------------------------------------------------------------------------
216 // GfxResources
217 //------------------------------------------------------------------------
218
219 GfxResources::GfxResources(XRef *xref, Dict *resDict, GfxResources *nextA) {
220   Object obj1;
221
222   if (resDict) {
223
224     // build font dictionary
225     fonts = NULL;
226     resDict->lookup("Font", &obj1);
227     if (obj1.isDict()) {
228       fonts = new GfxFontDict(xref, obj1.getDict());
229     }
230     obj1.free();
231
232     // get XObject dictionary
233     resDict->lookup("XObject", &xObjDict);
234
235     // get color space dictionary
236     resDict->lookup("ColorSpace", &colorSpaceDict);
237
238     // get pattern dictionary
239     resDict->lookup("Pattern", &patternDict);
240
241     // get shading dictionary
242     resDict->lookup("Shading", &shadingDict);
243
244     // get graphics state parameter dictionary
245     resDict->lookup("ExtGState", &gStateDict);
246
247   } else {
248     fonts = NULL;
249     xObjDict.initNull();
250     colorSpaceDict.initNull();
251     patternDict.initNull();
252     gStateDict.initNull();
253   }
254
255   next = nextA;
256 }
257
258 GfxResources::~GfxResources() {
259   if (fonts) {
260     delete fonts;
261   }
262   xObjDict.free();
263   colorSpaceDict.free();
264   patternDict.free();
265   shadingDict.free();
266   gStateDict.free();
267 }
268
269 GfxFont *GfxResources::lookupFont(char *name) {
270   GfxFont *font;
271   GfxResources *resPtr;
272
273   for (resPtr = this; resPtr; resPtr = resPtr->next) {
274     if (resPtr->fonts) {
275       if ((font = resPtr->fonts->lookup(name)))
276         return font;
277     }
278   }
279   error(-1, "Unknown font tag '%s'", name);
280   return NULL;
281 }
282
283 GBool GfxResources::lookupXObject(char *name, Object *obj) {
284   GfxResources *resPtr;
285
286   for (resPtr = this; resPtr; resPtr = resPtr->next) {
287     if (resPtr->xObjDict.isDict()) {
288       if (!resPtr->xObjDict.dictLookup(name, obj)->isNull())
289         return gTrue;
290       obj->free();
291     }
292   }
293   error(-1, "XObject '%s' is unknown", name);
294   return gFalse;
295 }
296
297 GBool GfxResources::lookupXObjectNF(char *name, Object *obj) {
298   GfxResources *resPtr;
299
300   for (resPtr = this; resPtr; resPtr = resPtr->next) {
301     if (resPtr->xObjDict.isDict()) {
302       if (!resPtr->xObjDict.dictLookupNF(name, obj)->isNull())
303         return gTrue;
304       obj->free();
305     }
306   }
307   error(-1, "XObject '%s' is unknown", name);
308   return gFalse;
309 }
310
311 void GfxResources::lookupColorSpace(char *name, Object *obj) {
312   GfxResources *resPtr;
313
314   for (resPtr = this; resPtr; resPtr = resPtr->next) {
315     if (resPtr->colorSpaceDict.isDict()) {
316       if (!resPtr->colorSpaceDict.dictLookup(name, obj)->isNull()) {
317         return;
318       }
319       obj->free();
320     }
321   }
322   obj->initNull();
323 }
324
325 GfxPattern *GfxResources::lookupPattern(char *name) {
326   GfxResources *resPtr;
327   GfxPattern *pattern;
328   Object obj;
329
330   for (resPtr = this; resPtr; resPtr = resPtr->next) {
331     if (resPtr->patternDict.isDict()) {
332       if (!resPtr->patternDict.dictLookup(name, &obj)->isNull()) {
333         pattern = GfxPattern::parse(&obj);
334         obj.free();
335         return pattern;
336       }
337       obj.free();
338     }
339   }
340   error(-1, "Unknown pattern '%s'", name);
341   return NULL;
342 }
343
344 GfxShading *GfxResources::lookupShading(char *name) {
345   GfxResources *resPtr;
346   GfxShading *shading;
347   Object obj;
348
349   for (resPtr = this; resPtr; resPtr = resPtr->next) {
350     if (resPtr->shadingDict.isDict()) {
351       if (!resPtr->shadingDict.dictLookup(name, &obj)->isNull()) {
352         shading = GfxShading::parse(&obj);
353         obj.free();
354         return shading;
355       }
356       obj.free();
357     }
358   }
359   error(-1, "Unknown shading '%s'", name);
360   return NULL;
361 }
362
363 GBool GfxResources::lookupGState(char *name, Object *obj) {
364   GfxResources *resPtr;
365
366   for (resPtr = this; resPtr; resPtr = resPtr->next) {
367     if (resPtr->gStateDict.isDict()) {
368       if (!resPtr->gStateDict.dictLookup(name, obj)->isNull()) {
369         return gTrue;
370       }
371       obj->free();
372     }
373   }
374   error(-1, "ExtGState '%s' is unknown", name);
375   return gFalse;
376 }
377
378 //------------------------------------------------------------------------
379 // Gfx
380 //------------------------------------------------------------------------
381
382 Gfx::Gfx(XRef *xrefA, OutputDev *outA, int pageNum, Dict *resDict, double dpi,
383          PDFRectangle *box, GBool crop, PDFRectangle *cropBox, int rotate,
384          GBool printCommandsA) {
385   int i;
386
387   xref = xrefA;
388   subPage = gFalse;
389   printCommands = printCommandsA;
390
391   // start the resource stack
392   res = new GfxResources(xref, resDict, NULL);
393
394   // initialize
395   out = outA;
396   state = new GfxState(dpi, box, rotate, out->upsideDown());
397   fontChanged = gFalse;
398   clip = clipNone;
399   ignoreUndef = 0;
400   out->startPage(pageNum, state, cropBox->x1,cropBox->y1,cropBox->x2,cropBox->y2);
401   out->setDefaultCTM(state->getCTM());
402   out->updateAll(state);
403   for (i = 0; i < 6; ++i) {
404     baseMatrix[i] = state->getCTM()[i];
405   }
406
407   // set crop box
408   /*if (crop) {
409     state->moveTo(cropBox->x1, cropBox->y1);
410     state->lineTo(cropBox->x2, cropBox->y1);
411     state->lineTo(cropBox->x2, cropBox->y2);
412     state->lineTo(cropBox->x1, cropBox->y2);
413     state->closePath();
414     state->clip();
415     out->clip(state);
416     state->clearPath();
417   }*/
418 }
419
420 Gfx::Gfx(XRef *xrefA, OutputDev *outA, Dict *resDict,
421          PDFRectangle *box, GBool crop, PDFRectangle *cropBox) {
422   int i;
423
424   xref = xrefA;
425   subPage = gTrue;
426   printCommands = gFalse;
427
428   // start the resource stack
429   res = new GfxResources(xref, resDict, NULL);
430
431   // initialize
432   out = outA;
433   state = new GfxState(72, box, 0, gFalse);
434   fontChanged = gFalse;
435   clip = clipNone;
436   ignoreUndef = 0;
437   for (i = 0; i < 6; ++i) {
438     baseMatrix[i] = state->getCTM()[i];
439   }
440
441   // set crop box
442   if (crop) {
443     state->moveTo(cropBox->x1, cropBox->y1);
444     state->lineTo(cropBox->x2, cropBox->y1);
445     state->lineTo(cropBox->x2, cropBox->y2);
446     state->lineTo(cropBox->x1, cropBox->y2);
447     state->closePath();
448     state->clip();
449     out->clip(state);
450     state->clearPath();
451   }
452 }
453
454 Gfx::~Gfx() {
455   while (state->hasSaves()) {
456     state = state->restore();
457     out->restoreState(state);
458   }
459   if (!subPage) {
460     out->endPage();
461   }
462   while (res) {
463     popResources();
464   }
465   if (state) {
466     delete state;
467   }
468 }
469
470 void Gfx::display(Object *obj, GBool topLevel) {
471   Object obj2;
472   int i;
473
474   if (obj->isArray()) {
475     for (i = 0; i < obj->arrayGetLength(); ++i) {
476       obj->arrayGet(i, &obj2);
477       if (!obj2.isStream()) {
478         error(-1, "Weird page contents");
479         obj2.free();
480         return;
481       }
482       obj2.free();
483     }
484   } else if (!obj->isStream()) {
485     error(-1, "Weird page contents");
486     return;
487   }
488   parser = new Parser(xref, new Lexer(xref, obj));
489   go(topLevel);
490   delete parser;
491   parser = NULL;
492 }
493
494 void Gfx::go(GBool topLevel) {
495   Object obj;
496   Object args[maxArgs];
497   int numArgs;
498   int i;
499
500   // scan a sequence of objects
501   updateLevel = 0;
502   numArgs = 0;
503   parser->getObj(&obj);
504   while (!obj.isEOF()) {
505
506     // got a command - execute it
507     if (obj.isCmd()) {
508       if (printCommands) {
509         obj.print(stdout);
510         for (i = 0; i < numArgs; ++i) {
511           printf(" ");
512           args[i].print(stdout);
513         }
514         printf("\n");
515         fflush(stdout);
516       }
517       execOp(&obj, args, numArgs);
518       obj.free();
519       for (i = 0; i < numArgs; ++i)
520         args[i].free();
521       numArgs = 0;
522
523       // periodically update display
524       if (++updateLevel >= 20000) {
525         out->dump();
526         updateLevel = 0;
527       }
528
529     // got an argument - save it
530     } else if (numArgs < maxArgs) {
531       args[numArgs++] = obj;
532
533     // too many arguments - something is wrong
534     } else {
535       error(getPos(), "Too many args in content stream");
536       if (printCommands) {
537         printf("throwing away arg: ");
538         obj.print(stdout);
539         printf("\n");
540         fflush(stdout);
541       }
542       obj.free();
543     }
544
545     // grab the next object
546     parser->getObj(&obj);
547   }
548   obj.free();
549
550   // args at end with no command
551   if (numArgs > 0) {
552     error(getPos(), "Leftover args in content stream");
553     if (printCommands) {
554       printf("%d leftovers:", numArgs);
555       for (i = 0; i < numArgs; ++i) {
556         printf(" ");
557         args[i].print(stdout);
558       }
559       printf("\n");
560       fflush(stdout);
561     }
562     for (i = 0; i < numArgs; ++i)
563       args[i].free();
564   }
565
566   // update display
567   if (topLevel && updateLevel > 0) {
568     out->dump();
569   }
570 }
571
572 void Gfx::execOp(Object *cmd, Object args[], int numArgs) {
573   Operator *op;
574   char *name;
575   int i;
576
577   // find operator
578   name = cmd->getName();
579   if (!(op = findOp(name))) {
580     if (ignoreUndef == 0)
581       error(getPos(), "Unknown operator '%s'", name);
582     return;
583   }
584
585   // type check args
586   if (op->numArgs >= 0) {
587     if (numArgs != op->numArgs) {
588       error(getPos(), "Wrong number (%d) of args to '%s' operator",
589             numArgs, name);
590       return;
591     }
592   } else {
593     if (numArgs > -op->numArgs) {
594       error(getPos(), "Too many (%d) args to '%s' operator",
595             numArgs, name);
596       return;
597     }
598   }
599   for (i = 0; i < numArgs; ++i) {
600     if (!checkArg(&args[i], op->tchk[i])) {
601       error(getPos(), "Arg #%d to '%s' operator is wrong type (%s)",
602             i, name, args[i].getTypeName());
603       return;
604     }
605   }
606
607   // do it
608   (this->*op->func)(args, numArgs);
609 }
610
611 Operator *Gfx::findOp(char *name) {
612   int a, b, m, cmp;
613
614   a = -1;
615   b = numOps;
616   // invariant: opTab[a] < name < opTab[b]
617   while (b - a > 1) {
618     m = (a + b) / 2;
619     cmp = strcmp(opTab[m].name, name);
620     if (cmp < 0)
621       a = m;
622     else if (cmp > 0)
623       b = m;
624     else
625       a = b = m;
626   }
627   if (cmp != 0)
628     return NULL;
629   return &opTab[a];
630 }
631
632 GBool Gfx::checkArg(Object *arg, TchkType type) {
633   switch (type) {
634   case tchkBool:   return arg->isBool();
635   case tchkInt:    return arg->isInt();
636   case tchkNum:    return arg->isNum();
637   case tchkString: return arg->isString();
638   case tchkName:   return arg->isName();
639   case tchkArray:  return arg->isArray();
640   case tchkProps:  return arg->isDict() || arg->isName();
641   case tchkSCN:    return arg->isNum() || arg->isName();
642   case tchkNone:   return gFalse;
643   }
644   return gFalse;
645 }
646
647 int Gfx::getPos() {
648   return parser ? parser->getPos() : -1;
649 }
650
651 //------------------------------------------------------------------------
652 // graphics state operators
653 //------------------------------------------------------------------------
654
655 void Gfx::opSave(Object args[], int numArgs) {
656   out->saveState(state);
657   state = state->save();
658 }
659
660 void Gfx::opRestore(Object args[], int numArgs) {
661   state = state->restore();
662   out->restoreState(state);
663 }
664
665 void Gfx::opConcat(Object args[], int numArgs) {
666   state->concatCTM(args[0].getNum(), args[1].getNum(),
667                    args[2].getNum(), args[3].getNum(),
668                    args[4].getNum(), args[5].getNum());
669   out->updateCTM(state, args[0].getNum(), args[1].getNum(),
670                  args[2].getNum(), args[3].getNum(),
671                  args[4].getNum(), args[5].getNum());
672   fontChanged = gTrue;
673 }
674
675 void Gfx::opSetDash(Object args[], int numArgs) {
676   Array *a;
677   int length;
678   Object obj;
679   double *dash;
680   int i;
681
682   a = args[0].getArray();
683   length = a->getLength();
684   if (length == 0) {
685     dash = NULL;
686   } else {
687     dash = (double *)gmalloc(length * sizeof(double));
688     for (i = 0; i < length; ++i) {
689       dash[i] = a->get(i, &obj)->getNum();
690       obj.free();
691     }
692   }
693   state->setLineDash(dash, length, args[1].getNum());
694   out->updateLineDash(state);
695 }
696
697 void Gfx::opSetFlat(Object args[], int numArgs) {
698   state->setFlatness((int)args[0].getNum());
699   out->updateFlatness(state);
700 }
701
702 void Gfx::opSetLineJoin(Object args[], int numArgs) {
703   state->setLineJoin(args[0].getInt());
704   out->updateLineJoin(state);
705 }
706
707 void Gfx::opSetLineCap(Object args[], int numArgs) {
708   state->setLineCap(args[0].getInt());
709   out->updateLineCap(state);
710 }
711
712 void Gfx::opSetMiterLimit(Object args[], int numArgs) {
713   state->setMiterLimit(args[0].getNum());
714   out->updateMiterLimit(state);
715 }
716
717 void Gfx::opSetLineWidth(Object args[], int numArgs) {
718   state->setLineWidth(args[0].getNum());
719   out->updateLineWidth(state);
720 }
721
722 void Gfx::opSetExtGState(Object args[], int numArgs) {
723   Object obj1, obj2;
724
725   if (!res->lookupGState(args[0].getName(), &obj1)) {
726     return;
727   }
728   if (!obj1.isDict()) {
729     error(getPos(), "ExtGState '%s' is wrong type", args[0].getName());
730     obj1.free();
731     return;
732   }
733   if (obj1.dictLookup("ca", &obj2)->isNum()) {
734     state->setFillOpacity(obj2.getNum());
735     out->updateFillOpacity(state);
736   }
737   obj2.free();
738   if (obj1.dictLookup("CA", &obj2)->isNum()) {
739     state->setStrokeOpacity(obj2.getNum());
740     out->updateStrokeOpacity(state);
741   }
742   obj2.free();
743   obj1.free();
744 }
745
746 void Gfx::opSetRenderingIntent(Object args[], int numArgs) {
747 }
748
749 //------------------------------------------------------------------------
750 // color operators
751 //------------------------------------------------------------------------
752
753 void Gfx::opSetFillGray(Object args[], int numArgs) {
754   GfxColor color;
755
756   state->setFillPattern(NULL);
757   state->setFillColorSpace(new GfxDeviceGrayColorSpace());
758   color.c[0] = args[0].getNum();
759   state->setFillColor(&color);
760   out->updateFillColor(state);
761 }
762
763 void Gfx::opSetStrokeGray(Object args[], int numArgs) {
764   GfxColor color;
765
766   state->setStrokePattern(NULL);
767   state->setStrokeColorSpace(new GfxDeviceGrayColorSpace());
768   color.c[0] = args[0].getNum();
769   state->setStrokeColor(&color);
770   out->updateStrokeColor(state);
771 }
772
773 void Gfx::opSetFillCMYKColor(Object args[], int numArgs) {
774   GfxColor color;
775   int i;
776
777   state->setFillPattern(NULL);
778   state->setFillColorSpace(new GfxDeviceCMYKColorSpace());
779   for (i = 0; i < 4; ++i) {
780     color.c[i] = args[i].getNum();
781   }
782   state->setFillColor(&color);
783   out->updateFillColor(state);
784 }
785
786 void Gfx::opSetStrokeCMYKColor(Object args[], int numArgs) {
787   GfxColor color;
788   int i;
789
790   state->setStrokePattern(NULL);
791   state->setStrokeColorSpace(new GfxDeviceCMYKColorSpace());
792   for (i = 0; i < 4; ++i) {
793     color.c[i] = args[i].getNum();
794   }
795   state->setStrokeColor(&color);
796   out->updateStrokeColor(state);
797 }
798
799 void Gfx::opSetFillRGBColor(Object args[], int numArgs) {
800   GfxColor color;
801   int i;
802
803   state->setFillPattern(NULL);
804   state->setFillColorSpace(new GfxDeviceRGBColorSpace());
805   for (i = 0; i < 3; ++i) {
806     color.c[i] = args[i].getNum();
807   }
808   state->setFillColor(&color);
809   out->updateFillColor(state);
810 }
811
812 void Gfx::opSetStrokeRGBColor(Object args[], int numArgs) {
813   GfxColor color;
814   int i;
815
816   state->setStrokePattern(NULL);
817   state->setStrokeColorSpace(new GfxDeviceRGBColorSpace());
818   for (i = 0; i < 3; ++i) {
819     color.c[i] = args[i].getNum();
820   }
821   state->setStrokeColor(&color);
822   out->updateStrokeColor(state);
823 }
824
825 void Gfx::opSetFillColorSpace(Object args[], int numArgs) {
826   Object obj;
827   GfxColorSpace *colorSpace;
828   GfxColor color;
829   int i;
830
831   state->setFillPattern(NULL);
832   res->lookupColorSpace(args[0].getName(), &obj);
833   if (obj.isNull()) {
834     colorSpace = GfxColorSpace::parse(&args[0]);
835   } else {
836     colorSpace = GfxColorSpace::parse(&obj);
837   }
838   obj.free();
839   if (colorSpace) {
840     state->setFillColorSpace(colorSpace);
841   } else {
842     error(getPos(), "Bad color space (fill)");
843   }
844   for (i = 0; i < gfxColorMaxComps; ++i) {
845     color.c[i] = 0;
846   }
847   state->setFillColor(&color);
848   out->updateFillColor(state);
849 }
850
851 void Gfx::opSetStrokeColorSpace(Object args[], int numArgs) {
852   Object obj;
853   GfxColorSpace *colorSpace;
854   GfxColor color;
855   int i;
856
857   state->setStrokePattern(NULL);
858   res->lookupColorSpace(args[0].getName(), &obj);
859   if (obj.isNull()) {
860     colorSpace = GfxColorSpace::parse(&args[0]);
861   } else {
862     colorSpace = GfxColorSpace::parse(&obj);
863   }
864   obj.free();
865   if (colorSpace) {
866     state->setStrokeColorSpace(colorSpace);
867   } else {
868     error(getPos(), "Bad color space (stroke)");
869   }
870   for (i = 0; i < gfxColorMaxComps; ++i) {
871     color.c[i] = 0;
872   }
873   state->setStrokeColor(&color);
874   out->updateStrokeColor(state);
875 }
876
877 void Gfx::opSetFillColor(Object args[], int numArgs) {
878   GfxColor color;
879   int i;
880
881   state->setFillPattern(NULL);
882   for (i = 0; i < numArgs; ++i) {
883     color.c[i] = args[i].getNum();
884   }
885   state->setFillColor(&color);
886   out->updateFillColor(state);
887 }
888
889 void Gfx::opSetStrokeColor(Object args[], int numArgs) {
890   GfxColor color;
891   int i;
892
893   state->setStrokePattern(NULL);
894   for (i = 0; i < numArgs; ++i) {
895     color.c[i] = args[i].getNum();
896   }
897   state->setStrokeColor(&color);
898   out->updateStrokeColor(state);
899 }
900
901 void Gfx::opSetFillColorN(Object args[], int numArgs) {
902   GfxColor color;
903   GfxPattern *pattern;
904   int i;
905
906   if (state->getFillColorSpace()->getMode() == csPattern) {
907     if (numArgs > 1) {
908       for (i = 0; i < numArgs && i < 4; ++i) {
909         if (args[i].isNum()) {
910           color.c[i] = args[i].getNum();
911         }
912       }
913       state->setFillColor(&color);
914       out->updateFillColor(state);
915     }
916     if (args[numArgs-1].isName() &&
917         (pattern = res->lookupPattern(args[numArgs-1].getName()))) {
918       state->setFillPattern(pattern);
919     }
920
921   } else {
922     state->setFillPattern(NULL);
923     for (i = 0; i < numArgs && i < 4; ++i) {
924       if (args[i].isNum()) {
925         color.c[i] = args[i].getNum();
926       }
927     }
928     state->setFillColor(&color);
929     out->updateFillColor(state);
930   }
931 }
932
933 void Gfx::opSetStrokeColorN(Object args[], int numArgs) {
934   GfxColor color;
935   GfxPattern *pattern;
936   int i;
937
938   if (state->getStrokeColorSpace()->getMode() == csPattern) {
939     if (numArgs > 1) {
940       for (i = 0; i < numArgs && i < 4; ++i) {
941         if (args[i].isNum()) {
942           color.c[i] = args[i].getNum();
943         }
944       }
945       state->setStrokeColor(&color);
946       out->updateStrokeColor(state);
947     }
948     if (args[numArgs-1].isName() &&
949         (pattern = res->lookupPattern(args[numArgs-1].getName()))) {
950       state->setStrokePattern(pattern);
951     }
952
953   } else {
954     state->setStrokePattern(NULL);
955     for (i = 0; i < numArgs && i < 4; ++i) {
956       if (args[i].isNum()) {
957         color.c[i] = args[i].getNum();
958       }
959     }
960     state->setStrokeColor(&color);
961     out->updateStrokeColor(state);
962   }
963 }
964
965 //------------------------------------------------------------------------
966 // path segment operators
967 //------------------------------------------------------------------------
968
969 void Gfx::opMoveTo(Object args[], int numArgs) {
970   state->moveTo(args[0].getNum(), args[1].getNum());
971 }
972
973 void Gfx::opLineTo(Object args[], int numArgs) {
974   if (!state->isCurPt()) {
975     error(getPos(), "No current point in lineto");
976     return;
977   }
978   state->lineTo(args[0].getNum(), args[1].getNum());
979 }
980
981 void Gfx::opCurveTo(Object args[], int numArgs) {
982   double x1, y1, x2, y2, x3, y3;
983
984   if (!state->isCurPt()) {
985     error(getPos(), "No current point in curveto");
986     return;
987   }
988   x1 = args[0].getNum();
989   y1 = args[1].getNum();
990   x2 = args[2].getNum();
991   y2 = args[3].getNum();
992   x3 = args[4].getNum();
993   y3 = args[5].getNum();
994   state->curveTo(x1, y1, x2, y2, x3, y3);
995 }
996
997 void Gfx::opCurveTo1(Object args[], int numArgs) {
998   double x1, y1, x2, y2, x3, y3;
999
1000   if (!state->isCurPt()) {
1001     error(getPos(), "No current point in curveto1");
1002     return;
1003   }
1004   x1 = state->getCurX();
1005   y1 = state->getCurY();
1006   x2 = args[0].getNum();
1007   y2 = args[1].getNum();
1008   x3 = args[2].getNum();
1009   y3 = args[3].getNum();
1010   state->curveTo(x1, y1, x2, y2, x3, y3);
1011 }
1012
1013 void Gfx::opCurveTo2(Object args[], int numArgs) {
1014   double x1, y1, x2, y2, x3, y3;
1015
1016   if (!state->isCurPt()) {
1017     error(getPos(), "No current point in curveto2");
1018     return;
1019   }
1020   x1 = args[0].getNum();
1021   y1 = args[1].getNum();
1022   x2 = args[2].getNum();
1023   y2 = args[3].getNum();
1024   x3 = x2;
1025   y3 = y2;
1026   state->curveTo(x1, y1, x2, y2, x3, y3);
1027 }
1028
1029 void Gfx::opRectangle(Object args[], int numArgs) {
1030   double x, y, w, h;
1031
1032   x = args[0].getNum();
1033   y = args[1].getNum();
1034   w = args[2].getNum();
1035   h = args[3].getNum();
1036   state->moveTo(x, y);
1037   state->lineTo(x + w, y);
1038   state->lineTo(x + w, y + h);
1039   state->lineTo(x, y + h);
1040   state->closePath();
1041 }
1042
1043 void Gfx::opClosePath(Object args[], int numArgs) {
1044   if (!state->isCurPt()) {
1045     error(getPos(), "No current point in closepath");
1046     return;
1047   }
1048   state->closePath();
1049 }
1050
1051 //------------------------------------------------------------------------
1052 // path painting operators
1053 //------------------------------------------------------------------------
1054
1055 void Gfx::opEndPath(Object args[], int numArgs) {
1056   doEndPath();
1057 }
1058
1059 void Gfx::opStroke(Object args[], int numArgs) {
1060   if (!state->isCurPt()) {
1061     //error(getPos(), "No path in stroke");
1062     return;
1063   }
1064   if (state->isPath())
1065     out->stroke(state);
1066   doEndPath();
1067 }
1068
1069 void Gfx::opCloseStroke(Object args[], int numArgs) {
1070   if (!state->isCurPt()) {
1071     //error(getPos(), "No path in closepath/stroke");
1072     return;
1073   }
1074   if (state->isPath()) {
1075     state->closePath();
1076     out->stroke(state);
1077   }
1078   doEndPath();
1079 }
1080
1081 void Gfx::opFill(Object args[], int numArgs) {
1082   if (!state->isCurPt()) {
1083     //error(getPos(), "No path in fill");
1084     return;
1085   }
1086   if (state->isPath()) {
1087     if (state->getFillColorSpace()->getMode() == csPattern) {
1088       doPatternFill(gFalse);
1089     } else {
1090       out->fill(state);
1091     }
1092   }
1093   doEndPath();
1094 }
1095
1096 void Gfx::opEOFill(Object args[], int numArgs) {
1097   if (!state->isCurPt()) {
1098     //error(getPos(), "No path in eofill");
1099     return;
1100   }
1101   if (state->isPath()) {
1102     if (state->getFillColorSpace()->getMode() == csPattern) {
1103       doPatternFill(gTrue);
1104     } else {
1105       out->eoFill(state);
1106     }
1107   }
1108   doEndPath();
1109 }
1110
1111 void Gfx::opFillStroke(Object args[], int numArgs) {
1112   if (!state->isCurPt()) {
1113     //error(getPos(), "No path in fill/stroke");
1114     return;
1115   }
1116   if (state->isPath()) {
1117     if (state->getFillColorSpace()->getMode() == csPattern) {
1118       doPatternFill(gFalse);
1119     } else {
1120       out->fill(state);
1121     }
1122     out->stroke(state);
1123   }
1124   doEndPath();
1125 }
1126
1127 void Gfx::opCloseFillStroke(Object args[], int numArgs) {
1128   if (!state->isCurPt()) {
1129     //error(getPos(), "No path in closepath/fill/stroke");
1130     return;
1131   }
1132   if (state->isPath()) {
1133     state->closePath();
1134     if (state->getFillColorSpace()->getMode() == csPattern) {
1135       doPatternFill(gFalse);
1136     } else {
1137       out->fill(state);
1138     }
1139     out->stroke(state);
1140   }
1141   doEndPath();
1142 }
1143
1144 void Gfx::opEOFillStroke(Object args[], int numArgs) {
1145   if (!state->isCurPt()) {
1146     //error(getPos(), "No path in eofill/stroke");
1147     return;
1148   }
1149   if (state->isPath()) {
1150     if (state->getFillColorSpace()->getMode() == csPattern) {
1151       doPatternFill(gTrue);
1152     } else {
1153       out->eoFill(state);
1154     }
1155     out->stroke(state);
1156   }
1157   doEndPath();
1158 }
1159
1160 void Gfx::opCloseEOFillStroke(Object args[], int numArgs) {
1161   if (!state->isCurPt()) {
1162     //error(getPos(), "No path in closepath/eofill/stroke");
1163     return;
1164   }
1165   if (state->isPath()) {
1166     state->closePath();
1167     if (state->getFillColorSpace()->getMode() == csPattern) {
1168       doPatternFill(gTrue);
1169     } else {
1170       out->eoFill(state);
1171     }
1172     out->stroke(state);
1173   }
1174   doEndPath();
1175 }
1176
1177 void Gfx::doPatternFill(GBool eoFill) {
1178   GfxPatternColorSpace *patCS;
1179   GfxPattern *pattern;
1180   GfxTilingPattern *tPat;
1181   GfxColorSpace *cs;
1182   double xMin, yMin, xMax, yMax, x, y, x1, y1;
1183   double cxMin, cyMin, cxMax, cyMax;
1184   int xi0, yi0, xi1, yi1, xi, yi;
1185   double *ctm, *btm, *ptm;
1186   double m[6], ictm[6], m1[6], imb[6];
1187   double det;
1188   double xstep, ystep;
1189   int i;
1190
1191   // this is a bit of a kludge -- patterns can be really slow, so we
1192   // skip them if we're only doing text extraction, since they almost
1193   // certainly don't contain any text
1194   if (!out->needNonText()) {
1195     return;
1196   }
1197
1198   // get color space
1199   patCS = (GfxPatternColorSpace *)state->getFillColorSpace();
1200
1201   // get pattern
1202   if (!(pattern = state->getFillPattern())) {
1203     return;
1204   }
1205   if (pattern->getType() != 1) {
1206     return;
1207   }
1208   tPat = (GfxTilingPattern *)pattern;
1209
1210   // construct a (pattern space) -> (current space) transform matrix
1211   ctm = state->getCTM();
1212   btm = baseMatrix;
1213   ptm = tPat->getMatrix();
1214   // iCTM = invert CTM
1215   det = 1 / (ctm[0] * ctm[3] - ctm[1] * ctm[2]);
1216   ictm[0] = ctm[3] * det;
1217   ictm[1] = -ctm[1] * det;
1218   ictm[2] = -ctm[2] * det;
1219   ictm[3] = ctm[0] * det;
1220   ictm[4] = (ctm[2] * ctm[5] - ctm[3] * ctm[4]) * det;
1221   ictm[5] = (ctm[1] * ctm[4] - ctm[0] * ctm[5]) * det;
1222   // m1 = PTM * BTM = PTM * base transform matrix
1223   m1[0] = ptm[0] * btm[0] + ptm[1] * btm[2];
1224   m1[1] = ptm[0] * btm[1] + ptm[1] * btm[3];
1225   m1[2] = ptm[2] * btm[0] + ptm[3] * btm[2];
1226   m1[3] = ptm[2] * btm[1] + ptm[3] * btm[3];
1227   m1[4] = ptm[4] * btm[0] + ptm[5] * btm[2] + btm[4];
1228   m1[5] = ptm[4] * btm[1] + ptm[5] * btm[3] + btm[5];
1229   // m = m1 * iCTM = (PTM * BTM) * (iCTM)
1230   m[0] = m1[0] * ictm[0] + m1[1] * ictm[2];
1231   m[1] = m1[0] * ictm[1] + m1[1] * ictm[3];
1232   m[2] = m1[2] * ictm[0] + m1[3] * ictm[2];
1233   m[3] = m1[2] * ictm[1] + m1[3] * ictm[3];
1234   m[4] = m1[4] * ictm[0] + m1[5] * ictm[2] + ictm[4];
1235   m[5] = m1[4] * ictm[1] + m1[5] * ictm[3] + ictm[5];
1236
1237   // construct a (base space) -> (pattern space) transform matrix
1238   det = 1 / (m1[0] * m1[3] - m1[1] * m1[2]);
1239   imb[0] = m1[3] * det;
1240   imb[1] = -m1[1] * det;
1241   imb[2] = -m1[2] * det;
1242   imb[3] = m1[0] * det;
1243   imb[4] = (m1[2] * m1[5] - m1[3] * m1[4]) * det;
1244   imb[5] = (m1[1] * m1[4] - m1[0] * m1[5]) * det;
1245
1246   // save current graphics state
1247   out->saveState(state);
1248   state = state->save();
1249
1250   // set underlying color space (for uncolored tiling patterns)
1251   if (tPat->getPaintType() == 2 && (cs = patCS->getUnder())) {
1252     state->setFillColorSpace(cs->copy());
1253   } else {
1254     state->setFillColorSpace(new GfxDeviceGrayColorSpace());
1255   }
1256   state->setFillPattern(NULL);
1257   out->updateFillColor(state);
1258
1259   // clip to current path
1260   state->clip();
1261   if (eoFill) {
1262     out->eoClip(state);
1263   } else {
1264     out->clip(state);
1265   }
1266   state->clearPath();
1267
1268   // transform clip region bbox to pattern space
1269   state->getClipBBox(&cxMin, &cyMin, &cxMax, &cyMax);
1270   xMin = xMax = cxMin * imb[0] + cyMin * imb[2] + imb[4];
1271   yMin = yMax = cxMin * imb[1] + cyMin * imb[3] + imb[5];
1272   x1 = cxMin * imb[0] + cyMax * imb[2] + imb[4];
1273   y1 = cxMin * imb[1] + cyMax * imb[3] + imb[5];
1274   if (x1 < xMin) {
1275     xMin = x1;
1276   } else if (x1 > xMax) {
1277     xMax = x1;
1278   }
1279   if (y1 < yMin) {
1280     yMin = y1;
1281   } else if (y1 > yMax) {
1282     yMax = y1;
1283   }
1284   x1 = cxMax * imb[0] + cyMin * imb[2] + imb[4];
1285   y1 = cxMax * imb[1] + cyMin * imb[3] + imb[5];
1286   if (x1 < xMin) {
1287     xMin = x1;
1288   } else if (x1 > xMax) {
1289     xMax = x1;
1290   }
1291   if (y1 < yMin) {
1292     yMin = y1;
1293   } else if (y1 > yMax) {
1294     yMax = y1;
1295   }
1296   x1 = cxMax * imb[0] + cyMax * imb[2] + imb[4];
1297   y1 = cxMax * imb[1] + cyMax * imb[3] + imb[5];
1298   if (x1 < xMin) {
1299     xMin = x1;
1300   } else if (x1 > xMax) {
1301     xMax = x1;
1302   }
1303   if (y1 < yMin) {
1304     yMin = y1;
1305   } else if (y1 > yMax) {
1306     yMax = y1;
1307   }
1308
1309   // draw the pattern
1310   //~ this should treat negative steps differently -- start at right/top
1311   //~ edge instead of left/bottom (?)
1312   xstep = fabs(tPat->getXStep());
1313   ystep = fabs(tPat->getYStep());
1314   xi0 = (int)floor(xMin / xstep);
1315   xi1 = (int)ceil(xMax / xstep);
1316   yi0 = (int)floor(yMin / ystep);
1317   yi1 = (int)ceil(yMax / ystep);
1318   for (i = 0; i < 4; ++i) {
1319     m1[i] = m[i];
1320   }
1321   for (yi = yi0; yi < yi1; ++yi) {
1322     for (xi = xi0; xi < xi1; ++xi) {
1323       x = xi * xstep;
1324       y = yi * ystep;
1325       m1[4] = x * m[0] + y * m[2] + m[4];
1326       m1[5] = x * m[1] + y * m[3] + m[5];
1327       doForm1(tPat->getContentStream(), tPat->getResDict(),
1328               m1, tPat->getBBox());
1329     }
1330   }
1331
1332   // restore graphics state
1333   state = state->restore();
1334   out->restoreState(state);
1335 }
1336
1337 void Gfx::opShFill(Object args[], int numArgs) {
1338   GfxShading *shading;
1339   double xMin, yMin, xMax, yMax;
1340
1341   if (!(shading = res->lookupShading(args[0].getName()))) {
1342     return;
1343   }
1344
1345   // save current graphics state
1346   out->saveState(state);
1347   state = state->save();
1348
1349   // clip to bbox
1350   if (shading->getHasBBox()) {
1351     shading->getBBox(&xMin, &yMin, &xMax, &yMax);
1352     state->moveTo(xMin, yMin);
1353     state->lineTo(xMax, yMin);
1354     state->lineTo(xMax, yMax);
1355     state->lineTo(xMin, yMax);
1356     state->closePath();
1357     state->clip();
1358     out->clip(state);
1359     state->clearPath();
1360   }
1361
1362   // set the color space
1363   state->setFillColorSpace(shading->getColorSpace()->copy());
1364
1365   // do shading type-specific operations
1366   switch (shading->getType()) {
1367   case 2:
1368     doAxialShFill((GfxAxialShading *)shading);
1369     break;
1370   case 3:
1371     doRadialShFill((GfxRadialShading *)shading);
1372     break;
1373   }
1374
1375   // restore graphics state
1376   state = state->restore();
1377   out->restoreState(state);
1378
1379   delete shading;
1380 }
1381
1382 void Gfx::doAxialShFill(GfxAxialShading *shading) {
1383   double xMin, yMin, xMax, yMax;
1384   double x0, y0, x1, y1;
1385   double dx, dy, mul;
1386   double tMin, tMax, t, tx, ty;
1387   double s[4], sMin, sMax, tmp;
1388   double ux0, uy0, ux1, uy1, vx0, vy0, vx1, vy1;
1389   double t0, t1, tt;
1390   double ta[axialMaxSplits + 1];
1391   int next[axialMaxSplits + 1];
1392   GfxColor color0, color1;
1393   int nComps;
1394   int i, j, k, kk;
1395
1396   // get the clip region bbox
1397   state->getUserClipBBox(&xMin, &yMin, &xMax, &yMax);
1398
1399   // compute min and max t values, based on the four corners of the
1400   // clip region bbox
1401   shading->getCoords(&x0, &y0, &x1, &y1);
1402   dx = x1 - x0;
1403   dy = y1 - y0;
1404   mul = 1 / (dx * dx + dy * dy);
1405   tMin = tMax = ((xMin - x0) * dx + (yMin - y0) * dy) * mul;
1406   t = ((xMin - x0) * dx + (yMax - y0) * dy) * mul;
1407   if (t < tMin) {
1408     tMin = t;
1409   } else if (t > tMax) {
1410     tMax = t;
1411   }
1412   t = ((xMax - x0) * dx + (yMin - y0) * dy) * mul;
1413   if (t < tMin) {
1414     tMin = t;
1415   } else if (t > tMax) {
1416     tMax = t;
1417   }
1418   t = ((xMax - x0) * dx + (yMax - y0) * dy) * mul;
1419   if (t < tMin) {
1420     tMin = t;
1421   } else if (t > tMax) {
1422     tMax = t;
1423   }
1424   if (tMin < 0 && !shading->getExtend0()) {
1425     tMin = 0;
1426   }
1427   if (tMax > 1 && !shading->getExtend1()) {
1428     tMax = 1;
1429   }
1430
1431   // get the function domain
1432   t0 = shading->getDomain0();
1433   t1 = shading->getDomain1();
1434
1435   // Traverse the t axis and do the shading.
1436   //
1437   // For each point (tx, ty) on the t axis, consider a line through
1438   // that point perpendicular to the t axis:
1439   //
1440   //     x(s) = tx + s * -dy   -->   s = (x - tx) / -dy
1441   //     y(s) = ty + s * dx    -->   s = (y - ty) / dx
1442   //
1443   // Then look at the intersection of this line with the bounding box
1444   // (xMin, yMin, xMax, yMax).  In the general case, there are four
1445   // intersection points:
1446   //
1447   //     s0 = (xMin - tx) / -dy
1448   //     s1 = (xMax - tx) / -dy
1449   //     s2 = (yMin - ty) / dx
1450   //     s3 = (yMax - ty) / dx
1451   //
1452   // and we want the middle two s values.
1453   //
1454   // In the case where dx = 0, take s0 and s1; in the case where dy =
1455   // 0, take s2 and s3.
1456   //
1457   // Each filled polygon is bounded by two of these line segments
1458   // perpdendicular to the t axis.
1459   //
1460   // The t axis is bisected into smaller regions until the color
1461   // difference across a region is small enough, and then the region
1462   // is painted with a single color.
1463
1464   // set up
1465   nComps = shading->getColorSpace()->getNComps();
1466   ta[0] = tMin;
1467   ta[axialMaxSplits] = tMax;
1468   next[0] = axialMaxSplits;
1469
1470   // compute the color at t = tMin
1471   if (tMin < 0) {
1472     tt = t0;
1473   } else if (tMin > 1) {
1474     tt = t1;
1475   } else {
1476     tt = t0 + (t1 - t0) * tMin;
1477   }
1478   shading->getColor(tt, &color0);
1479
1480   // compute the coordinates of the point on the t axis at t = tMin;
1481   // then compute the intersection of the perpendicular line with the
1482   // bounding box
1483   tx = x0 + tMin * dx;
1484   ty = y0 + tMin * dy;
1485   if (dx == 0 && dy == 0) {
1486     sMin = sMax = 0;
1487   } if (dx == 0) {
1488     sMin = (xMin - tx) / -dy;
1489     sMax = (xMax - tx) / -dy;
1490     if (sMin > sMax) { tmp = sMin; sMin = sMax; sMax = tmp; }
1491   } else if (dy == 0) {
1492     sMin = (yMin - ty) / dx;
1493     sMax = (yMax - ty) / dx;
1494     if (sMin > sMax) { tmp = sMin; sMin = sMax; sMax = tmp; }
1495   } else {
1496     s[0] = (yMin - ty) / dx;
1497     s[1] = (yMax - ty) / dx;
1498     s[2] = (xMin - tx) / -dy;
1499     s[3] = (xMax - tx) / -dy;
1500     for (j = 0; j < 3; ++j) {
1501       kk = j;
1502       for (k = j + 1; k < 4; ++k) {
1503         if (s[k] < s[kk]) {
1504           kk = k;
1505         }
1506       }
1507       tmp = s[j]; s[j] = s[kk]; s[kk] = tmp;
1508     }
1509     sMin = s[1];
1510     sMax = s[2];
1511   }
1512   ux0 = tx - sMin * dy;
1513   uy0 = ty + sMin * dx;
1514   vx0 = tx - sMax * dy;
1515   vy0 = ty + sMax * dx;
1516
1517   i = 0;
1518   while (i < axialMaxSplits) {
1519
1520     // bisect until color difference is small enough or we hit the
1521     // bisection limit
1522     j = next[i];
1523     while (j > i + 1) {
1524       if (ta[j] < 0) {
1525         tt = t0;
1526       } else if (ta[j] > 1) {
1527         tt = t1;
1528       } else {
1529         tt = t0 + (t1 - t0) * ta[j];
1530       }
1531       shading->getColor(tt, &color1);
1532       for (k = 0; k < nComps; ++k) {
1533         if (fabs(color1.c[k] - color0.c[k]) > axialColorDelta) {
1534           break;
1535         }
1536       }
1537       if (k == nComps) {
1538         break;
1539       }
1540       k = (i + j) / 2;
1541       ta[k] = 0.5 * (ta[i] + ta[j]);
1542       next[i] = k;
1543       next[k] = j;
1544       j = k;
1545     }
1546
1547     // use the average of the colors of the two sides of the region
1548     for (k = 0; k < nComps; ++k) {
1549       color0.c[k] = 0.5 * (color0.c[k] + color1.c[k]);
1550     }
1551
1552     // compute the coordinates of the point on the t axis; then
1553     // compute the intersection of the perpendicular line with the
1554     // bounding box
1555     tx = x0 + ta[j] * dx;
1556     ty = y0 + ta[j] * dy;
1557     if (dx == 0 && dy == 0) {
1558       sMin = sMax = 0;
1559     } if (dx == 0) {
1560       sMin = (xMin - tx) / -dy;
1561       sMax = (xMax - tx) / -dy;
1562       if (sMin > sMax) { tmp = sMin; sMin = sMax; sMax = tmp; }
1563     } else if (dy == 0) {
1564       sMin = (yMin - ty) / dx;
1565       sMax = (yMax - ty) / dx;
1566       if (sMin > sMax) { tmp = sMin; sMin = sMax; sMax = tmp; }
1567     } else {
1568       s[0] = (yMin - ty) / dx;
1569       s[1] = (yMax - ty) / dx;
1570       s[2] = (xMin - tx) / -dy;
1571       s[3] = (xMax - tx) / -dy;
1572       for (j = 0; j < 3; ++j) {
1573         kk = j;
1574         for (k = j + 1; k < 4; ++k) {
1575           if (s[k] < s[kk]) {
1576             kk = k;
1577           }
1578         }
1579         tmp = s[j]; s[j] = s[kk]; s[kk] = tmp;
1580       }
1581       sMin = s[1];
1582       sMax = s[2];
1583     }
1584     ux1 = tx - sMin * dy;
1585     uy1 = ty + sMin * dx;
1586     vx1 = tx - sMax * dy;
1587     vy1 = ty + sMax * dx;
1588
1589     // set the color
1590     state->setFillColor(&color0);
1591     out->updateFillColor(state);
1592
1593     // fill the region
1594     state->moveTo(ux0, uy0);
1595     state->lineTo(vx0, vy0);
1596     state->lineTo(vx1, vy1);
1597     state->lineTo(ux1, uy1);
1598     state->closePath();
1599     out->fill(state);
1600     state->clearPath();
1601
1602     // set up for next region
1603     ux0 = ux1;
1604     uy0 = uy1;
1605     vx0 = vx1;
1606     vy0 = vy1;
1607     color0 = color1;
1608     i = next[i];
1609   }
1610 }
1611
1612 void Gfx::doRadialShFill(GfxRadialShading *shading) {
1613   double sMin, sMax, xMin, yMin, xMax, yMax;
1614   double x0, y0, r0, x1, y1, r1, t0, t1;
1615   int nComps;
1616   GfxColor colorA, colorB;
1617   double xa, ya, xb, yb, ra, rb;
1618   double ta, tb, sa, sb;
1619   int ia, ib, k, n;
1620   double *ctm;
1621   double angle, t;
1622
1623   // get the shading info
1624   shading->getCoords(&x0, &y0, &r0, &x1, &y1, &r1);
1625   t0 = shading->getDomain0();
1626   t1 = shading->getDomain1();
1627   nComps = shading->getColorSpace()->getNComps();
1628
1629   // compute the (possibly extended) s range
1630   sMin = 0;
1631   sMax = 1;
1632   if (shading->getExtend0()) {
1633     if (r0 < r1) {
1634       // extend the smaller end
1635       sMin = -r0 / (r1 - r0);
1636     } else {
1637       // extend the larger end
1638       //~ this computes the diagonal of the bounding box -- we should
1639       //~ really compute the intersection of the moving/expanding
1640       //~ circles with each of the four corners and look for the max
1641       //~ radius
1642       state->getUserClipBBox(&xMin, &yMin, &xMax, &yMax);
1643       sMin = (sqrt((xMax - xMin) * (xMax - xMin) +
1644                    (yMax - yMin) * (yMax - yMin)) - r0) / (r1 - r0);
1645       if (sMin > 0) {
1646         sMin = 0;
1647       } else if (sMin < -20) {
1648         // sanity check
1649         sMin = -20;
1650       }
1651     }
1652   }
1653   if (shading->getExtend1()) {
1654     if (r1 < r0) {
1655       // extend the smaller end
1656       sMax = -r0 / (r1 - r0);
1657     } else if (r1 > r0) {
1658       // extend the larger end
1659       state->getUserClipBBox(&xMin, &yMin, &xMax, &yMax);
1660       sMax = (sqrt((xMax - xMin) * (xMax - xMin) +
1661                    (yMax - yMin) * (yMax - yMin)) - r0) / (r1 - r0);
1662       if (sMax < 1) {
1663         sMin = 1;
1664       } else if (sMax > 20) {
1665         // sanity check
1666         sMax = 20;
1667       }
1668     }
1669   }
1670
1671   // compute the number of steps into which circles must be divided to
1672   // achieve a curve flatness of 0.1 pixel in device space for the
1673   // largest circle (note that "device space" is 72 dpi when generating
1674   // PostScript, hence the relatively small 0.1 pixel accuracy)
1675   ctm = state->getCTM();
1676   t = fabs(ctm[0]);
1677   if (fabs(ctm[1]) > t) {
1678     t = fabs(ctm[1]);
1679   }
1680   if (fabs(ctm[2]) > t) {
1681     t = fabs(ctm[2]);
1682   }
1683   if (fabs(ctm[3]) > t) {
1684     t = fabs(ctm[3]);
1685   }
1686   if (r0 > r1) {
1687     t *= r0;
1688   } else {
1689     t *= r1;
1690   }
1691   if (t < 1) {
1692     n = 3;
1693   } else {
1694     n = (int)(M_PI / acos(1 - 0.1 / t));
1695     if (n < 3) {
1696       n = 3;
1697     } else if (n > 200) {
1698       n = 200;
1699     }
1700   }
1701
1702   // Traverse the t axis and do the shading.
1703   //
1704   // This generates and fills a series of rings.  Each ring is defined
1705   // by two circles:
1706   //   sa, ta, xa, ya, ra, colorA
1707   //   sb, tb, xb, yb, rb, colorB
1708   //
1709   // The s/t axis is divided into radialMaxSplits parts; these parts
1710   // are combined as much as possible while respecting the
1711   // radialColorDelta parameter.
1712
1713   // setup for the start circle
1714   ia = 0;
1715   sa = sMin;
1716   ta = t0 + sa * (t1 - t0);
1717   xa = x0 + sa * (x1 - x0);
1718   ya = y0 + sa * (y1 - y0);
1719   ra = r0 + sa * (r1 - r0);
1720   if (ta < t0) {
1721     shading->getColor(t0, &colorA);
1722   } else if (ta > t1) {
1723     shading->getColor(t1, &colorA);
1724   } else {
1725     shading->getColor(ta, &colorA);
1726   }
1727
1728   while (ia < radialMaxSplits) {
1729
1730     // go as far along the t axis (toward t1) as we can, such that the
1731     // color difference is within the tolerance (radialColorDelta) --
1732     // this uses bisection (between the current value, t, and t1),
1733     // limited to radialMaxSplits points along the t axis
1734     ib = radialMaxSplits;
1735     sb = sMin + ((double)ib / (double)radialMaxSplits) * (sMax - sMin);
1736     tb = t0 + sb * (t1 - t0);
1737     if (tb < t0) {
1738       shading->getColor(t0, &colorB);
1739     } else if (tb > t1) {
1740       shading->getColor(t1, &colorB);
1741     } else {
1742       shading->getColor(tb, &colorB);
1743     }
1744     while (ib - ia > 1) {
1745       for (k = 0; k < nComps; ++k) {
1746         if (fabs(colorB.c[k] - colorA.c[k]) > radialColorDelta) {
1747           break;
1748         }
1749       }
1750       if (k == nComps) {
1751         break;
1752       }
1753       ib = (ia + ib) / 2;
1754       sb = sMin + ((double)ib / (double)radialMaxSplits) * (sMax - sMin);
1755       tb = t0 + sb * (t1 - t0);
1756       if (tb < t0) {
1757         shading->getColor(t0, &colorB);
1758       } else if (tb > t1) {
1759         shading->getColor(t1, &colorB);
1760       } else {
1761         shading->getColor(tb, &colorB);
1762       }
1763     }
1764
1765     // compute center and radius of the circle
1766     xb = x0 + sb * (x1 - x0);
1767     yb = y0 + sb * (y1 - y0);
1768     rb = r0 + sb * (r1 - r0);
1769
1770     // use the average of the colors at the two circles
1771     for (k = 0; k < nComps; ++k) {
1772       colorA.c[k] = 0.5 * (colorA.c[k] + colorB.c[k]);
1773     }
1774     state->setFillColor(&colorA);
1775     out->updateFillColor(state);
1776
1777     // construct path for first circle
1778     state->moveTo(xa + ra, ya);
1779     for (k = 1; k < n; ++k) {
1780       angle = ((double)k / (double)n) * 2 * M_PI;
1781       state->lineTo(xa + ra * cos(angle), ya + ra * sin(angle));
1782     }
1783     state->closePath();
1784
1785     // construct and append path for second circle
1786     state->moveTo(xb + rb, yb);
1787     for (k = 1; k < n; ++k) {
1788       angle = ((double)k / (double)n) * 2 * M_PI;
1789       state->lineTo(xb + rb * cos(angle), yb + rb * sin(angle));
1790     }
1791     state->closePath();
1792
1793     // fill the ring
1794     out->eoFill(state);
1795     state->clearPath();
1796
1797     // step to the next value of t
1798     ia = ib;
1799     sa = sb;
1800     ta = tb;
1801     xa = xb;
1802     ya = yb;
1803     ra = rb;
1804     colorA = colorB;
1805   }
1806 }
1807
1808 void Gfx::doEndPath() {
1809   if (state->isPath() && clip != clipNone) {
1810     state->clip();
1811     if (clip == clipNormal) {
1812       out->clip(state);
1813     } else {
1814       out->eoClip(state);
1815     }
1816   }
1817   clip = clipNone;
1818   state->clearPath();
1819 }
1820
1821 //------------------------------------------------------------------------
1822 // path clipping operators
1823 //------------------------------------------------------------------------
1824
1825 void Gfx::opClip(Object args[], int numArgs) {
1826   clip = clipNormal;
1827 }
1828
1829 void Gfx::opEOClip(Object args[], int numArgs) {
1830   clip = clipEO;
1831 }
1832
1833 //------------------------------------------------------------------------
1834 // text object operators
1835 //------------------------------------------------------------------------
1836
1837 void Gfx::opBeginText(Object args[], int numArgs) {
1838   state->setTextMat(1, 0, 0, 1, 0, 0);
1839   state->textMoveTo(0, 0);
1840   out->updateTextMat(state);
1841   out->updateTextPos(state);
1842   fontChanged = gTrue;
1843 }
1844
1845 void Gfx::opEndText(Object args[], int numArgs) {
1846 }
1847
1848 //------------------------------------------------------------------------
1849 // text state operators
1850 //------------------------------------------------------------------------
1851
1852 void Gfx::opSetCharSpacing(Object args[], int numArgs) {
1853   state->setCharSpace(args[0].getNum());
1854   out->updateCharSpace(state);
1855 }
1856
1857 void Gfx::opSetFont(Object args[], int numArgs) {
1858   GfxFont *font;
1859
1860   if (!(font = res->lookupFont(args[0].getName()))) {
1861     return;
1862   }
1863   if (printCommands) {
1864     printf("  font: tag=%s name='%s' %g\n",
1865            font->getTag()->getCString(),
1866            font->getName() ? font->getName()->getCString() : "???",
1867            args[1].getNum());
1868     fflush(stdout);
1869   }
1870   state->setFont(font, args[1].getNum());
1871   fontChanged = gTrue;
1872 }
1873
1874 void Gfx::opSetTextLeading(Object args[], int numArgs) {
1875   state->setLeading(args[0].getNum());
1876 }
1877
1878 void Gfx::opSetTextRender(Object args[], int numArgs) {
1879   state->setRender(args[0].getInt());
1880   out->updateRender(state);
1881 }
1882
1883 void Gfx::opSetTextRise(Object args[], int numArgs) {
1884   state->setRise(args[0].getNum());
1885   out->updateRise(state);
1886 }
1887
1888 void Gfx::opSetWordSpacing(Object args[], int numArgs) {
1889   state->setWordSpace(args[0].getNum());
1890   out->updateWordSpace(state);
1891 }
1892
1893 void Gfx::opSetHorizScaling(Object args[], int numArgs) {
1894   state->setHorizScaling(args[0].getNum());
1895   out->updateHorizScaling(state);
1896   fontChanged = gTrue;
1897 }
1898
1899 //------------------------------------------------------------------------
1900 // text positioning operators
1901 //------------------------------------------------------------------------
1902
1903 void Gfx::opTextMove(Object args[], int numArgs) {
1904   double tx, ty;
1905
1906   tx = state->getLineX() + args[0].getNum();
1907   ty = state->getLineY() + args[1].getNum();
1908   state->textMoveTo(tx, ty);
1909   out->updateTextPos(state);
1910 }
1911
1912 void Gfx::opTextMoveSet(Object args[], int numArgs) {
1913   double tx, ty;
1914
1915   tx = state->getLineX() + args[0].getNum();
1916   ty = args[1].getNum();
1917   state->setLeading(-ty);
1918   ty += state->getLineY();
1919   state->textMoveTo(tx, ty);
1920   out->updateTextPos(state);
1921 }
1922
1923 void Gfx::opSetTextMatrix(Object args[], int numArgs) {
1924   state->setTextMat(args[0].getNum(), args[1].getNum(),
1925                     args[2].getNum(), args[3].getNum(),
1926                     args[4].getNum(), args[5].getNum());
1927   state->textMoveTo(0, 0);
1928   out->updateTextMat(state);
1929   out->updateTextPos(state);
1930   fontChanged = gTrue;
1931 }
1932
1933 void Gfx::opTextNextLine(Object args[], int numArgs) {
1934   double tx, ty;
1935
1936   tx = state->getLineX();
1937   ty = state->getLineY() - state->getLeading();
1938   state->textMoveTo(tx, ty);
1939   out->updateTextPos(state);
1940 }
1941
1942 //------------------------------------------------------------------------
1943 // text string operators
1944 //------------------------------------------------------------------------
1945
1946 void Gfx::opShowText(Object args[], int numArgs) {
1947   if (!state->getFont()) {
1948     error(getPos(), "No font in show");
1949     return;
1950   }
1951   doShowText(args[0].getString());
1952 }
1953
1954 void Gfx::opMoveShowText(Object args[], int numArgs) {
1955   double tx, ty;
1956
1957   if (!state->getFont()) {
1958     error(getPos(), "No font in move/show");
1959     return;
1960   }
1961   tx = state->getLineX();
1962   ty = state->getLineY() - state->getLeading();
1963   state->textMoveTo(tx, ty);
1964   out->updateTextPos(state);
1965   doShowText(args[0].getString());
1966 }
1967
1968 void Gfx::opMoveSetShowText(Object args[], int numArgs) {
1969   double tx, ty;
1970
1971   if (!state->getFont()) {
1972     error(getPos(), "No font in move/set/show");
1973     return;
1974   }
1975   state->setWordSpace(args[0].getNum());
1976   state->setCharSpace(args[1].getNum());
1977   tx = state->getLineX();
1978   ty = state->getLineY() - state->getLeading();
1979   state->textMoveTo(tx, ty);
1980   out->updateWordSpace(state);
1981   out->updateCharSpace(state);
1982   out->updateTextPos(state);
1983   doShowText(args[2].getString());
1984 }
1985
1986 void Gfx::opShowSpaceText(Object args[], int numArgs) {
1987   Array *a;
1988   Object obj;
1989   int wMode;
1990   int i;
1991
1992   if (!state->getFont()) {
1993     error(getPos(), "No font in show/space");
1994     return;
1995   }
1996   wMode = state->getFont()->getWMode();
1997   a = args[0].getArray();
1998   for (i = 0; i < a->getLength(); ++i) {
1999     a->get(i, &obj);
2000     if (obj.isNum()) {
2001       if (wMode) {
2002         state->textShift(0, -obj.getNum() * 0.001 * state->getFontSize());
2003       } else {
2004         state->textShift(-obj.getNum() * 0.001 * state->getFontSize(), 0);
2005       }
2006       out->updateTextShift(state, obj.getNum());
2007     } else if (obj.isString()) {
2008       doShowText(obj.getString());
2009     } else {
2010       error(getPos(), "Element of show/space array must be number or string");
2011     }
2012     obj.free();
2013   }
2014 }
2015
2016 void Gfx::doShowText(GString *s) {
2017   GfxFont *font;
2018   int wMode;
2019   double riseX, riseY;
2020   CharCode code;
2021   Unicode u[8];
2022   double x, y, dx, dy, dx2, dy2, curX, curY, tdx, tdy;
2023   double originX, originY, tOriginX, tOriginY;
2024   double oldCTM[6], newCTM[6];
2025   double *mat;
2026   Object charProc;
2027   Dict *resDict;
2028   Parser *oldParser;
2029   char *p;
2030   int len, n, uLen, nChars, nSpaces, i;
2031
2032   if (fontChanged) {
2033     out->updateFont(state);
2034     fontChanged = gFalse;
2035   }
2036   font = state->getFont();
2037   wMode = font->getWMode();
2038
2039   if (out->useDrawChar()) {
2040     out->beginString(state, s);
2041   }
2042
2043   // handle a Type 3 char
2044   if (font->getType() == fontType3 && out->interpretType3Chars()) {
2045     mat = state->getCTM();
2046     for (i = 0; i < 6; ++i) {
2047       oldCTM[i] = mat[i];
2048     }
2049     mat = state->getTextMat();
2050     newCTM[0] = mat[0] * oldCTM[0] + mat[1] * oldCTM[2];
2051     newCTM[1] = mat[0] * oldCTM[1] + mat[1] * oldCTM[3];
2052     newCTM[2] = mat[2] * oldCTM[0] + mat[3] * oldCTM[2];
2053     newCTM[3] = mat[2] * oldCTM[1] + mat[3] * oldCTM[3];
2054     mat = font->getFontMatrix();
2055     newCTM[0] = mat[0] * newCTM[0] + mat[1] * newCTM[2];
2056     newCTM[1] = mat[0] * newCTM[1] + mat[1] * newCTM[3];
2057     newCTM[2] = mat[2] * newCTM[0] + mat[3] * newCTM[2];
2058     newCTM[3] = mat[2] * newCTM[1] + mat[3] * newCTM[3];
2059     newCTM[0] *= state->getFontSize();
2060     newCTM[3] *= state->getFontSize();
2061     newCTM[0] *= state->getHorizScaling();
2062     newCTM[2] *= state->getHorizScaling();
2063     state->textTransformDelta(0, state->getRise(), &riseX, &riseY);
2064     curX = state->getCurX();
2065     curY = state->getCurY();
2066     oldParser = parser;
2067     p = s->getCString();
2068     len = s->getLength();
2069     while (len > 0) {
2070       n = font->getNextChar(p, len, &code,
2071                             u, (int)(sizeof(u) / sizeof(Unicode)), &uLen,
2072                             &dx, &dy, &originX, &originY);
2073       dx = dx * state->getFontSize() + state->getCharSpace();
2074       if (n == 1 && *p == ' ') {
2075         dx += state->getWordSpace();
2076       }
2077       dx *= state->getHorizScaling();
2078       dy *= state->getFontSize();
2079       state->textTransformDelta(dx, dy, &tdx, &tdy);
2080       state->transform(curX + riseX, curY + riseY, &x, &y);
2081       out->saveState(state);
2082       state = state->save();
2083       state->setCTM(newCTM[0], newCTM[1], newCTM[2], newCTM[3], x, y);
2084       //~ out->updateCTM(???)
2085       if (!out->beginType3Char(state, code, u, uLen)) {
2086         ((Gfx8BitFont *)font)->getCharProc(code, &charProc);
2087         if ((resDict = ((Gfx8BitFont *)font)->getResources())) {
2088           pushResources(resDict);
2089         }
2090         if (charProc.isStream()) {
2091           display(&charProc, gFalse);
2092         } else {
2093           error(getPos(), "Missing or bad Type3 CharProc entry");
2094         }
2095         out->endType3Char(state);
2096         if (resDict) {
2097           popResources();
2098         }
2099         charProc.free();
2100       }
2101       state = state->restore();
2102       out->restoreState(state);
2103       // GfxState::restore() does *not* restore the current position,
2104       // so we track it here with (curX, curY)
2105       curX += tdx;
2106       curY += tdy;
2107       state->moveTo(curX, curY);
2108       p += n;
2109       len -= n;
2110     }
2111     parser = oldParser;
2112
2113   } else if (out->useDrawChar()) {
2114     state->textTransformDelta(0, state->getRise(), &riseX, &riseY);
2115     p = s->getCString();
2116     len = s->getLength();
2117     while (len > 0) {
2118       n = font->getNextChar(p, len, &code,
2119                             u, (int)(sizeof(u) / sizeof(Unicode)), &uLen,
2120                             &dx, &dy, &originX, &originY);
2121       if (wMode) {
2122         dx *= state->getFontSize();
2123         dy = dy * state->getFontSize() + state->getCharSpace();
2124         if (n == 1 && *p == ' ') {
2125           dy += state->getWordSpace();
2126         }
2127       } else {
2128         dx = dx * state->getFontSize() + state->getCharSpace();
2129         if (n == 1 && *p == ' ') {
2130           dx += state->getWordSpace();
2131         }
2132         dx *= state->getHorizScaling();
2133         dy *= state->getFontSize();
2134       }
2135       state->textTransformDelta(dx, dy, &tdx, &tdy);
2136       originX *= state->getFontSize();
2137       originY *= state->getFontSize();
2138       state->textTransformDelta(originX, originY, &tOriginX, &tOriginY);
2139       out->drawChar(state, state->getCurX() + riseX, state->getCurY() + riseY,
2140                     tdx, tdy, tOriginX, tOriginY, code, u, uLen);
2141       state->shift(tdx, tdy);
2142       p += n;
2143       len -= n;
2144     }
2145
2146   } else {
2147     dx = dy = 0;
2148     p = s->getCString();
2149     len = s->getLength();
2150     nChars = nSpaces = 0;
2151     while (len > 0) {
2152       n = font->getNextChar(p, len, &code,
2153                             u, (int)(sizeof(u) / sizeof(Unicode)), &uLen,
2154                             &dx2, &dy2, &originX, &originY);
2155       dx += dx2;
2156       dy += dy2;
2157       if (n == 1 && *p == ' ') {
2158         ++nSpaces;
2159       }
2160       ++nChars;
2161       p += n;
2162       len -= n;
2163     }
2164     if (wMode) {
2165       dx *= state->getFontSize();
2166       dy = dy * state->getFontSize()
2167            + nChars * state->getCharSpace()
2168            + nSpaces * state->getWordSpace();
2169     } else {
2170       dx = dx * state->getFontSize()
2171            + nChars * state->getCharSpace()
2172            + nSpaces * state->getWordSpace();
2173       dx *= state->getHorizScaling();
2174       dy *= state->getFontSize();
2175     }
2176     state->textTransformDelta(dx, dy, &tdx, &tdy);
2177     out->drawString(state, s);
2178     state->shift(tdx, tdy);
2179   }
2180
2181   if (out->useDrawChar()) {
2182     out->endString(state);
2183   }
2184
2185   updateLevel += 10 * s->getLength();
2186 }
2187
2188 //------------------------------------------------------------------------
2189 // XObject operators
2190 //------------------------------------------------------------------------
2191
2192 void Gfx::opXObject(Object args[], int numArgs) {
2193   Object obj1, obj2, obj3, refObj;
2194 #if OPI_SUPPORT
2195   Object opiDict;
2196 #endif
2197
2198   if (!res->lookupXObject(args[0].getName(), &obj1)) {
2199     return;
2200   }
2201   if (!obj1.isStream()) {
2202     error(getPos(), "XObject '%s' is wrong type", args[0].getName());
2203     obj1.free();
2204     return;
2205   }
2206 #if OPI_SUPPORT
2207   obj1.streamGetDict()->lookup("OPI", &opiDict);
2208   if (opiDict.isDict()) {
2209     out->opiBegin(state, opiDict.getDict());
2210   }
2211 #endif
2212   obj1.streamGetDict()->lookup("Subtype", &obj2);
2213   if (obj2.isName("Image")) {
2214     res->lookupXObjectNF(args[0].getName(), &refObj);
2215     doImage(&refObj, obj1.getStream(), gFalse);
2216     refObj.free();
2217   } else if (obj2.isName("Form")) {
2218     doForm(&obj1);
2219   } else if (obj2.isName("PS")) {
2220     obj1.streamGetDict()->lookup("Level1", &obj3);
2221     out->psXObject(obj1.getStream(),
2222                    obj3.isStream() ? obj3.getStream() : (Stream *)NULL);
2223   } else if (obj2.isName()) {
2224     error(getPos(), "Unknown XObject subtype '%s'", obj2.getName());
2225   } else {
2226     error(getPos(), "XObject subtype is missing or wrong type");
2227   }
2228   obj2.free();
2229 #if OPI_SUPPORT
2230   if (opiDict.isDict()) {
2231     out->opiEnd(state, opiDict.getDict());
2232   }
2233   opiDict.free();
2234 #endif
2235   obj1.free();
2236 }
2237
2238 void Gfx::doImage(Object *ref, Stream *str, GBool inlineImg) {
2239   Dict *dict;
2240   int width, height;
2241   int bits;
2242   GBool mask;
2243   GBool invert;
2244   GfxColorSpace *colorSpace;
2245   GfxImageColorMap *colorMap;
2246   Object maskObj;
2247   GBool haveMask;
2248   int maskColors[2*gfxColorMaxComps];
2249   Object obj1, obj2;
2250   int i;
2251
2252   // get stream dict
2253   dict = str->getDict();
2254
2255   // get size
2256   dict->lookup("Width", &obj1);
2257   if (obj1.isNull()) {
2258     obj1.free();
2259     dict->lookup("W", &obj1);
2260   }
2261   if (!obj1.isInt())
2262     goto err2;
2263   width = obj1.getInt();
2264   obj1.free();
2265   dict->lookup("Height", &obj1);
2266   if (obj1.isNull()) {
2267     obj1.free();
2268     dict->lookup("H", &obj1);
2269   }
2270   if (!obj1.isInt())
2271     goto err2;
2272   height = obj1.getInt();
2273   obj1.free();
2274
2275   // image or mask?
2276   dict->lookup("ImageMask", &obj1);
2277   if (obj1.isNull()) {
2278     obj1.free();
2279     dict->lookup("IM", &obj1);
2280   }
2281   mask = gFalse;
2282   if (obj1.isBool())
2283     mask = obj1.getBool();
2284   else if (!obj1.isNull())
2285     goto err2;
2286   obj1.free();
2287
2288   // bit depth
2289   dict->lookup("BitsPerComponent", &obj1);
2290   if (obj1.isNull()) {
2291     obj1.free();
2292     dict->lookup("BPC", &obj1);
2293   }
2294   if (!obj1.isInt())
2295     goto err2;
2296   bits = obj1.getInt();
2297   obj1.free();
2298
2299   // display a mask
2300   if (mask) {
2301
2302     // check for inverted mask
2303     if (bits != 1)
2304       goto err1;
2305     invert = gFalse;
2306     dict->lookup("Decode", &obj1);
2307     if (obj1.isNull()) {
2308       obj1.free();
2309       dict->lookup("D", &obj1);
2310     }
2311     if (obj1.isArray()) {
2312       obj1.arrayGet(0, &obj2);
2313       if (obj2.isInt() && obj2.getInt() == 1)
2314         invert = gTrue;
2315       obj2.free();
2316     } else if (!obj1.isNull()) {
2317       goto err2;
2318     }
2319     obj1.free();
2320
2321     // draw it
2322     out->drawImageMask(state, ref, str, width, height, invert, inlineImg);
2323
2324   } else {
2325
2326     // get color space and color map
2327     dict->lookup("ColorSpace", &obj1);
2328     if (obj1.isNull()) {
2329       obj1.free();
2330       dict->lookup("CS", &obj1);
2331     }
2332     if (obj1.isName()) {
2333       res->lookupColorSpace(obj1.getName(), &obj2);
2334       if (!obj2.isNull()) {
2335         obj1.free();
2336         obj1 = obj2;
2337       } else {
2338         obj2.free();
2339       }
2340     }
2341     colorSpace = GfxColorSpace::parse(&obj1);
2342     obj1.free();
2343     if (!colorSpace) {
2344       goto err1;
2345     }
2346     dict->lookup("Decode", &obj1);
2347     if (obj1.isNull()) {
2348       obj1.free();
2349       dict->lookup("D", &obj1);
2350     }
2351     colorMap = new GfxImageColorMap(bits, &obj1, colorSpace);
2352     obj1.free();
2353     if (!colorMap->isOk()) {
2354       delete colorMap;
2355       goto err1;
2356     }
2357
2358     // get the mask
2359     haveMask = gFalse;
2360     dict->lookup("Mask", &maskObj);
2361     if (maskObj.isArray()) {
2362       for (i = 0; i < maskObj.arrayGetLength(); ++i) {
2363         maskObj.arrayGet(i, &obj1);
2364         maskColors[i] = obj1.getInt();
2365         obj1.free();
2366       }
2367       haveMask = gTrue;
2368     }
2369
2370     // draw it
2371     out->drawImage(state, ref, str, width, height, colorMap,
2372                    haveMask ? maskColors : (int *)NULL,  inlineImg);
2373     delete colorMap;
2374
2375     maskObj.free();
2376   }
2377
2378   if ((i = width * height) > 1000) {
2379     i = 1000;
2380   }
2381   updateLevel += i;
2382
2383   return;
2384
2385  err2:
2386   obj1.free();
2387  err1:
2388   error(getPos(), "Bad image parameters");
2389 }
2390
2391 void Gfx::doForm(Object *str) {
2392   Dict *dict;
2393   Object matrixObj, bboxObj;
2394   double m[6], bbox[6];
2395   Object resObj;
2396   Dict *resDict;
2397   Object obj1;
2398   int i;
2399
2400   // get stream dict
2401   dict = str->streamGetDict();
2402
2403   // check form type
2404   dict->lookup("FormType", &obj1);
2405   if (!(obj1.isInt() && obj1.getInt() == 1)) {
2406     error(getPos(), "Unknown form type");
2407   }
2408   obj1.free();
2409
2410   // get bounding box
2411   dict->lookup("BBox", &bboxObj);
2412   if (!bboxObj.isArray()) {
2413     matrixObj.free();
2414     bboxObj.free();
2415     error(getPos(), "Bad form bounding box");
2416     return;
2417   }
2418   for (i = 0; i < 4; ++i) {
2419     bboxObj.arrayGet(i, &obj1);
2420     bbox[i] = obj1.getNum();
2421     obj1.free();
2422   }
2423   bboxObj.free();
2424
2425   // get matrix
2426   dict->lookup("Matrix", &matrixObj);
2427   if (matrixObj.isArray()) {
2428     for (i = 0; i < 6; ++i) {
2429       matrixObj.arrayGet(i, &obj1);
2430       m[i] = obj1.getNum();
2431       obj1.free();
2432     }
2433   } else {
2434     m[0] = 1; m[1] = 0;
2435     m[2] = 0; m[3] = 1;
2436     m[4] = 0; m[5] = 0;
2437   }
2438   matrixObj.free();
2439
2440   // get resources
2441   dict->lookup("Resources", &resObj);
2442   resDict = resObj.isDict() ? resObj.getDict() : (Dict *)NULL;
2443
2444   // draw it
2445   doForm1(str, resDict, m, bbox);
2446
2447   resObj.free();
2448 }
2449
2450 void Gfx::doAnnot(Object *str, double xMin, double yMin,
2451                   double xMax, double yMax) {
2452   Dict *dict, *resDict;
2453   Object matrixObj, bboxObj, resObj;
2454   Object obj1;
2455   double m[6], bbox[6], ictm[6];
2456   double *ctm;
2457   double formX0, formY0, formX1, formY1;
2458   double annotX0, annotY0, annotX1, annotY1;
2459   double det, x, y, sx, sy;
2460   int i;
2461
2462   // get stream dict
2463   dict = str->streamGetDict();
2464
2465   // get the form bounding box
2466   dict->lookup("BBox", &bboxObj);
2467   if (!bboxObj.isArray()) {
2468     bboxObj.free();
2469     error(getPos(), "Bad form bounding box");
2470     return;
2471   }
2472   for (i = 0; i < 4; ++i) {
2473     bboxObj.arrayGet(i, &obj1);
2474     bbox[i] = obj1.getNum();
2475     obj1.free();
2476   }
2477   bboxObj.free();
2478
2479   // get the form matrix
2480   dict->lookup("Matrix", &matrixObj);
2481   if (matrixObj.isArray()) {
2482     for (i = 0; i < 6; ++i) {
2483       matrixObj.arrayGet(i, &obj1);
2484       m[i] = obj1.getNum();
2485       obj1.free();
2486     }
2487   } else {
2488     m[0] = 1; m[1] = 0;
2489     m[2] = 0; m[3] = 1;
2490     m[4] = 0; m[5] = 0;
2491   }
2492   matrixObj.free();
2493
2494   // transform the form bbox from form space to user space
2495   formX0 = bbox[0] * m[0] + bbox[1] * m[2] + m[4];
2496   formY0 = bbox[0] * m[1] + bbox[1] * m[3] + m[5];
2497   formX1 = bbox[2] * m[0] + bbox[3] * m[2] + m[4];
2498   formY1 = bbox[2] * m[1] + bbox[3] * m[3] + m[5];
2499
2500   // transform the annotation bbox from default user space to user
2501   // space: (bbox * baseMatrix) * iCTM
2502   ctm = state->getCTM();
2503   det = 1 / (ctm[0] * ctm[3] - ctm[1] * ctm[2]);
2504   ictm[0] = ctm[3] * det;
2505   ictm[1] = -ctm[1] * det;
2506   ictm[2] = -ctm[2] * det;
2507   ictm[3] = ctm[0] * det;
2508   ictm[4] = (ctm[2] * ctm[5] - ctm[3] * ctm[4]) * det;
2509   ictm[5] = (ctm[1] * ctm[4] - ctm[0] * ctm[5]) * det;
2510   x = baseMatrix[0] * xMin + baseMatrix[2] * yMin + baseMatrix[4];
2511   y = baseMatrix[1] * xMin + baseMatrix[3] * yMin + baseMatrix[5];
2512   annotX0 = ictm[0] * x + ictm[2] * y + ictm[4];
2513   annotY0 = ictm[1] * x + ictm[3] * y + ictm[5];
2514   x = baseMatrix[0] * xMax + baseMatrix[2] * yMax + baseMatrix[4];
2515   y = baseMatrix[1] * xMax + baseMatrix[3] * yMax + baseMatrix[5];
2516   annotX1 = ictm[0] * x + ictm[2] * y + ictm[4];
2517   annotY1 = ictm[1] * x + ictm[3] * y + ictm[5];
2518
2519   // swap min/max coords
2520   if (formX0 > formX1) {
2521     x = formX0; formX0 = formX1; formX1 = x;
2522   }
2523   if (formY0 > formY1) {
2524     y = formY0; formY0 = formY1; formY1 = y;
2525   }
2526   if (annotX0 > annotX1) {
2527     x = annotX0; annotX0 = annotX1; annotX1 = x;
2528   }
2529   if (annotY0 > annotY1) {
2530     y = annotY0; annotY0 = annotY1; annotY1 = y;
2531   }
2532
2533   // scale the form to fit the annotation bbox
2534   if (formX1 == formX0) {
2535     // this shouldn't happen
2536     sx = 1;
2537   } else {
2538     sx = (annotX1 - annotX0) / (formX1 - formX0);
2539   }
2540   if (formY1 == formY0) {
2541     // this shouldn't happen
2542     sy = 1;
2543   } else {
2544     sy = (annotY1 - annotY0) / (formY1 - formY0);
2545   }
2546   m[0] *= sx;
2547   m[2] *= sx;
2548   m[4] = (m[4] - formX0) * sx + annotX0;
2549   m[1] *= sy;
2550   m[3] *= sy;
2551   m[5] = (m[5] - formY0) * sy + annotY0;
2552
2553   // get resources
2554   dict->lookup("Resources", &resObj);
2555   resDict = resObj.isDict() ? resObj.getDict() : (Dict *)NULL;
2556
2557   // draw it
2558   doForm1(str, resDict, m, bbox);
2559
2560   resObj.free();
2561   bboxObj.free();
2562 }
2563
2564 void Gfx::doForm1(Object *str, Dict *resDict, double *matrix, double *bbox) {
2565   Parser *oldParser;
2566   double oldBaseMatrix[6];
2567   int i;
2568
2569   // push new resources on stack
2570   pushResources(resDict);
2571
2572   // save current graphics state
2573   out->saveState(state);
2574   state = state->save();
2575
2576   // save current parser
2577   oldParser = parser;
2578
2579   // set form transformation matrix
2580   state->concatCTM(matrix[0], matrix[1], matrix[2],
2581                    matrix[3], matrix[4], matrix[5]);
2582   out->updateCTM(state, matrix[0], matrix[1], matrix[2],
2583                  matrix[3], matrix[4], matrix[5]);
2584
2585   // set new base matrix
2586   for (i = 0; i < 6; ++i) {
2587     oldBaseMatrix[i] = baseMatrix[i];
2588     baseMatrix[i] = state->getCTM()[i];
2589   }
2590
2591   // set form bounding box
2592   state->moveTo(bbox[0], bbox[1]);
2593   state->lineTo(bbox[2], bbox[1]);
2594   state->lineTo(bbox[2], bbox[3]);
2595   state->lineTo(bbox[0], bbox[3]);
2596   state->closePath();
2597   state->clip();
2598   out->clip(state);
2599   state->clearPath();
2600
2601   // draw the form
2602   display(str, gFalse);
2603
2604   // restore base matrix
2605   for (i = 0; i < 6; ++i) {
2606     baseMatrix[i] = oldBaseMatrix[i];
2607   }
2608
2609   // restore parser
2610   parser = oldParser;
2611
2612   // restore graphics state
2613   state = state->restore();
2614   out->restoreState(state);
2615
2616   // pop resource stack
2617   popResources();
2618
2619   return;
2620 }
2621
2622 void Gfx::pushResources(Dict *resDict) {
2623   res = new GfxResources(xref, resDict, res);
2624 }
2625
2626 void Gfx::popResources() {
2627   GfxResources *resPtr;
2628
2629   resPtr = res->getNext();
2630   delete res;
2631   res = resPtr;
2632 }
2633
2634 //------------------------------------------------------------------------
2635 // in-line image operators
2636 //------------------------------------------------------------------------
2637
2638 void Gfx::opBeginImage(Object args[], int numArgs) {
2639   Stream *str;
2640   int c1, c2;
2641
2642   // build dict/stream
2643   str = buildImageStream();
2644
2645   // display the image
2646   if (str) {
2647     doImage(NULL, str, gTrue);
2648   
2649     // skip 'EI' tag
2650     c1 = str->getBaseStream()->getChar();
2651     c2 = str->getBaseStream()->getChar();
2652     while (!(c1 == 'E' && c2 == 'I') && c2 != EOF) {
2653       c1 = c2;
2654       c2 = str->getBaseStream()->getChar();
2655     }
2656     delete str;
2657   }
2658 }
2659
2660 Stream *Gfx::buildImageStream() {
2661   Object dict;
2662   Object obj;
2663   char *key;
2664   Stream *str;
2665
2666   // build dictionary
2667   dict.initDict(xref);
2668   parser->getObj(&obj);
2669   while (!obj.isCmd("ID") && !obj.isEOF()) {
2670     if (!obj.isName()) {
2671       error(getPos(), "Inline image dictionary key must be a name object");
2672       obj.free();
2673     } else {
2674       key = copyString(obj.getName());
2675       obj.free();
2676       parser->getObj(&obj);
2677       if (obj.isEOF() || obj.isError()) {
2678         gfree(key);
2679         break;
2680       }
2681       dict.dictAdd(key, &obj);
2682     }
2683     parser->getObj(&obj);
2684   }
2685   if (obj.isEOF()) {
2686     error(getPos(), "End of file in inline image");
2687     obj.free();
2688     dict.free();
2689     return NULL;
2690   }
2691   obj.free();
2692
2693   // make stream
2694   str = new EmbedStream(parser->getStream(), &dict);
2695   str = str->addFilters(&dict);
2696
2697   return str;
2698 }
2699
2700 void Gfx::opImageData(Object args[], int numArgs) {
2701   error(getPos(), "Internal: got 'ID' operator");
2702 }
2703
2704 void Gfx::opEndImage(Object args[], int numArgs) {
2705   error(getPos(), "Internal: got 'EI' operator");
2706 }
2707
2708 //------------------------------------------------------------------------
2709 // type 3 font operators
2710 //------------------------------------------------------------------------
2711
2712 void Gfx::opSetCharWidth(Object args[], int numArgs) {
2713   out->type3D0(state, args[0].getNum(), args[1].getNum());
2714 }
2715
2716 void Gfx::opSetCacheDevice(Object args[], int numArgs) {
2717   out->type3D1(state, args[0].getNum(), args[1].getNum(),
2718                args[2].getNum(), args[3].getNum(),
2719                args[4].getNum(), args[5].getNum());
2720 }
2721
2722 //------------------------------------------------------------------------
2723 // compatibility operators
2724 //------------------------------------------------------------------------
2725
2726 void Gfx::opBeginIgnoreUndef(Object args[], int numArgs) {
2727   ++ignoreUndef;
2728 }
2729
2730 void Gfx::opEndIgnoreUndef(Object args[], int numArgs) {
2731   if (ignoreUndef > 0)
2732     --ignoreUndef;
2733 }
2734
2735 //------------------------------------------------------------------------
2736 // marked content operators
2737 //------------------------------------------------------------------------
2738
2739 void Gfx::opBeginMarkedContent(Object args[], int numArgs) {
2740   if (printCommands) {
2741     printf("  marked content: %s ", args[0].getName());
2742     if (numArgs == 2)
2743       args[2].print(stdout);
2744     printf("\n");
2745     fflush(stdout);
2746   }
2747 }
2748
2749 void Gfx::opEndMarkedContent(Object args[], int numArgs) {
2750 }
2751
2752 void Gfx::opMarkPoint(Object args[], int numArgs) {
2753   if (printCommands) {
2754     printf("  mark point: %s ", args[0].getName());
2755     if (numArgs == 2)
2756       args[2].print(stdout);
2757     printf("\n");
2758     fflush(stdout);
2759   }
2760 }