beta-0.89.2
[luatex.git] / source / libs / poppler / poppler-src / poppler / Gfx.cc
blob577c482ec1888ce6b33673495918027f1dee50b1
1 //========================================================================
2 //
3 // Gfx.cc
4 //
5 // Copyright 1996-2003 Glyph & Cog, LLC
6 //
7 //========================================================================
9 //========================================================================
11 // Modified under the Poppler project - http://poppler.freedesktop.org
13 // All changes made under the Poppler project to this file are licensed
14 // under GPL version 2 or later
16 // Copyright (C) 2005 Jonathan Blandford <jrb@redhat.com>
17 // Copyright (C) 2005-2013, 2015 Albert Astals Cid <aacid@kde.org>
18 // Copyright (C) 2006 Thorkild Stray <thorkild@ifi.uio.no>
19 // Copyright (C) 2006 Kristian Høgsberg <krh@redhat.com>
20 // Copyright (C) 2006-2011 Carlos Garcia Campos <carlosgc@gnome.org>
21 // Copyright (C) 2006, 2007 Jeff Muizelaar <jeff@infidigm.net>
22 // Copyright (C) 2007, 2008 Brad Hards <bradh@kde.org>
23 // Copyright (C) 2007, 2011 Adrian Johnson <ajohnson@redneon.com>
24 // Copyright (C) 2007, 2008 Iñigo Martínez <inigomartinez@gmail.com>
25 // Copyright (C) 2007 Koji Otani <sho@bbr.jp>
26 // Copyright (C) 2007 Krzysztof Kowalczyk <kkowalczyk@gmail.com>
27 // Copyright (C) 2008 Pino Toscano <pino@kde.org>
28 // Copyright (C) 2008 Michael Vrable <mvrable@cs.ucsd.edu>
29 // Copyright (C) 2008 Hib Eris <hib@hiberis.nl>
30 // Copyright (C) 2009 M Joonas Pihlaja <jpihlaja@cc.helsinki.fi>
31 // Copyright (C) 2009-2015 Thomas Freitag <Thomas.Freitag@alfa.de>
32 // Copyright (C) 2009 William Bader <williambader@hotmail.com>
33 // Copyright (C) 2009, 2010 David Benjamin <davidben@mit.edu>
34 // Copyright (C) 2010 Nils Höglund <nils.hoglund@gmail.com>
35 // Copyright (C) 2010 Christian Feuersänger <cfeuersaenger@googlemail.com>
36 // Copyright (C) 2011 Axel Strübing <axel.struebing@freenet.de>
37 // Copyright (C) 2012 Even Rouault <even.rouault@mines-paris.org>
38 // Copyright (C) 2012, 2013 Fabio D'Urso <fabiodurso@hotmail.it>
39 // Copyright (C) 2012 Lu Wang <coolwanglu@gmail.com>
40 // Copyright (C) 2014 Jason Crain <jason@aquaticape.us>
42 // To see a description of the changes please see the Changelog file that
43 // came with your tarball or type make ChangeLog if you are building from git
45 //========================================================================
47 #include <config.h>
49 #ifdef USE_GCC_PRAGMAS
50 #pragma implementation
51 #endif
53 #include <stdlib.h>
54 #include <stdio.h>
55 #include <stddef.h>
56 #include <string.h>
57 #include <math.h>
58 #include "goo/gmem.h"
59 #include "goo/GooTimer.h"
60 #include "goo/GooHash.h"
61 #include "GlobalParams.h"
62 #include "CharTypes.h"
63 #include "Object.h"
64 #include "PDFDoc.h"
65 #include "Array.h"
66 #include "Annot.h"
67 #include "Dict.h"
68 #include "Stream.h"
69 #include "Lexer.h"
70 #include "Parser.h"
71 #include "GfxFont.h"
72 #include "GfxState.h"
73 #include "OutputDev.h"
74 #include "Page.h"
75 #include "Annot.h"
76 #include "Error.h"
77 #include "Gfx.h"
78 #include "ProfileData.h"
79 #include "Catalog.h"
80 #include "OptionalContent.h"
82 // the MSVC math.h doesn't define this
83 #ifndef M_PI
84 #define M_PI 3.14159265358979323846
85 #endif
87 //------------------------------------------------------------------------
88 // constants
89 //------------------------------------------------------------------------
91 // Max recursive depth for a function shading fill.
92 #define functionMaxDepth 6
94 // Max delta allowed in any color component for a function shading fill.
95 #define functionColorDelta (dblToCol(1 / 256.0))
97 // Max number of splits along the t axis for an axial shading fill.
98 #define axialMaxSplits 256
100 // Max delta allowed in any color component for an axial shading fill.
101 #define axialColorDelta (dblToCol(1 / 256.0))
103 // Max number of splits along the t axis for a radial shading fill.
104 #define radialMaxSplits 256
106 // Max delta allowed in any color component for a radial shading fill.
107 #define radialColorDelta (dblToCol(1 / 256.0))
109 // Max recursive depth for a Gouraud triangle shading fill.
111 // Triangles will be split at most gouraudMaxDepth times (each time into 4
112 // smaller ones). That makes pow(4,gouraudMaxDepth) many triangles for
113 // every triangle.
114 #define gouraudMaxDepth 6
116 // Max delta allowed in any color component for a Gouraud triangle
117 // shading fill.
118 #define gouraudColorDelta (dblToCol(3. / 256.0))
120 // Gouraud triangle: if the three color parameters differ by at more than this percend of
121 // the total color parameter range, the triangle will be refined
122 #define gouraudParameterizedColorDelta 5e-3
124 // Max recursive depth for a patch mesh shading fill.
125 #define patchMaxDepth 6
127 // Max delta allowed in any color component for a patch mesh shading
128 // fill.
129 #define patchColorDelta (dblToCol((3. / 256.0)))
131 //------------------------------------------------------------------------
132 // Operator table
133 //------------------------------------------------------------------------
135 #ifdef _MSC_VER // this works around a bug in the VC7 compiler
136 # pragma optimize("",off)
137 #endif
139 Operator Gfx::opTab[] = {
140 {"\"", 3, {tchkNum, tchkNum, tchkString},
141 &Gfx::opMoveSetShowText},
142 {"'", 1, {tchkString},
143 &Gfx::opMoveShowText},
144 {"B", 0, {tchkNone},
145 &Gfx::opFillStroke},
146 {"B*", 0, {tchkNone},
147 &Gfx::opEOFillStroke},
148 {"BDC", 2, {tchkName, tchkProps},
149 &Gfx::opBeginMarkedContent},
150 {"BI", 0, {tchkNone},
151 &Gfx::opBeginImage},
152 {"BMC", 1, {tchkName},
153 &Gfx::opBeginMarkedContent},
154 {"BT", 0, {tchkNone},
155 &Gfx::opBeginText},
156 {"BX", 0, {tchkNone},
157 &Gfx::opBeginIgnoreUndef},
158 {"CS", 1, {tchkName},
159 &Gfx::opSetStrokeColorSpace},
160 {"DP", 2, {tchkName, tchkProps},
161 &Gfx::opMarkPoint},
162 {"Do", 1, {tchkName},
163 &Gfx::opXObject},
164 {"EI", 0, {tchkNone},
165 &Gfx::opEndImage},
166 {"EMC", 0, {tchkNone},
167 &Gfx::opEndMarkedContent},
168 {"ET", 0, {tchkNone},
169 &Gfx::opEndText},
170 {"EX", 0, {tchkNone},
171 &Gfx::opEndIgnoreUndef},
172 {"F", 0, {tchkNone},
173 &Gfx::opFill},
174 {"G", 1, {tchkNum},
175 &Gfx::opSetStrokeGray},
176 {"ID", 0, {tchkNone},
177 &Gfx::opImageData},
178 {"J", 1, {tchkInt},
179 &Gfx::opSetLineCap},
180 {"K", 4, {tchkNum, tchkNum, tchkNum, tchkNum},
181 &Gfx::opSetStrokeCMYKColor},
182 {"M", 1, {tchkNum},
183 &Gfx::opSetMiterLimit},
184 {"MP", 1, {tchkName},
185 &Gfx::opMarkPoint},
186 {"Q", 0, {tchkNone},
187 &Gfx::opRestore},
188 {"RG", 3, {tchkNum, tchkNum, tchkNum},
189 &Gfx::opSetStrokeRGBColor},
190 {"S", 0, {tchkNone},
191 &Gfx::opStroke},
192 {"SC", -4, {tchkNum, tchkNum, tchkNum, tchkNum},
193 &Gfx::opSetStrokeColor},
194 {"SCN", -33, {tchkSCN, tchkSCN, tchkSCN, tchkSCN,
195 tchkSCN, tchkSCN, tchkSCN, tchkSCN,
196 tchkSCN, tchkSCN, tchkSCN, tchkSCN,
197 tchkSCN, tchkSCN, tchkSCN, tchkSCN,
198 tchkSCN, tchkSCN, tchkSCN, tchkSCN,
199 tchkSCN, tchkSCN, tchkSCN, tchkSCN,
200 tchkSCN, tchkSCN, tchkSCN, tchkSCN,
201 tchkSCN, tchkSCN, tchkSCN, tchkSCN,
202 tchkSCN},
203 &Gfx::opSetStrokeColorN},
204 {"T*", 0, {tchkNone},
205 &Gfx::opTextNextLine},
206 {"TD", 2, {tchkNum, tchkNum},
207 &Gfx::opTextMoveSet},
208 {"TJ", 1, {tchkArray},
209 &Gfx::opShowSpaceText},
210 {"TL", 1, {tchkNum},
211 &Gfx::opSetTextLeading},
212 {"Tc", 1, {tchkNum},
213 &Gfx::opSetCharSpacing},
214 {"Td", 2, {tchkNum, tchkNum},
215 &Gfx::opTextMove},
216 {"Tf", 2, {tchkName, tchkNum},
217 &Gfx::opSetFont},
218 {"Tj", 1, {tchkString},
219 &Gfx::opShowText},
220 {"Tm", 6, {tchkNum, tchkNum, tchkNum, tchkNum,
221 tchkNum, tchkNum},
222 &Gfx::opSetTextMatrix},
223 {"Tr", 1, {tchkInt},
224 &Gfx::opSetTextRender},
225 {"Ts", 1, {tchkNum},
226 &Gfx::opSetTextRise},
227 {"Tw", 1, {tchkNum},
228 &Gfx::opSetWordSpacing},
229 {"Tz", 1, {tchkNum},
230 &Gfx::opSetHorizScaling},
231 {"W", 0, {tchkNone},
232 &Gfx::opClip},
233 {"W*", 0, {tchkNone},
234 &Gfx::opEOClip},
235 {"b", 0, {tchkNone},
236 &Gfx::opCloseFillStroke},
237 {"b*", 0, {tchkNone},
238 &Gfx::opCloseEOFillStroke},
239 {"c", 6, {tchkNum, tchkNum, tchkNum, tchkNum,
240 tchkNum, tchkNum},
241 &Gfx::opCurveTo},
242 {"cm", 6, {tchkNum, tchkNum, tchkNum, tchkNum,
243 tchkNum, tchkNum},
244 &Gfx::opConcat},
245 {"cs", 1, {tchkName},
246 &Gfx::opSetFillColorSpace},
247 {"d", 2, {tchkArray, tchkNum},
248 &Gfx::opSetDash},
249 {"d0", 2, {tchkNum, tchkNum},
250 &Gfx::opSetCharWidth},
251 {"d1", 6, {tchkNum, tchkNum, tchkNum, tchkNum,
252 tchkNum, tchkNum},
253 &Gfx::opSetCacheDevice},
254 {"f", 0, {tchkNone},
255 &Gfx::opFill},
256 {"f*", 0, {tchkNone},
257 &Gfx::opEOFill},
258 {"g", 1, {tchkNum},
259 &Gfx::opSetFillGray},
260 {"gs", 1, {tchkName},
261 &Gfx::opSetExtGState},
262 {"h", 0, {tchkNone},
263 &Gfx::opClosePath},
264 {"i", 1, {tchkNum},
265 &Gfx::opSetFlat},
266 {"j", 1, {tchkInt},
267 &Gfx::opSetLineJoin},
268 {"k", 4, {tchkNum, tchkNum, tchkNum, tchkNum},
269 &Gfx::opSetFillCMYKColor},
270 {"l", 2, {tchkNum, tchkNum},
271 &Gfx::opLineTo},
272 {"m", 2, {tchkNum, tchkNum},
273 &Gfx::opMoveTo},
274 {"n", 0, {tchkNone},
275 &Gfx::opEndPath},
276 {"q", 0, {tchkNone},
277 &Gfx::opSave},
278 {"re", 4, {tchkNum, tchkNum, tchkNum, tchkNum},
279 &Gfx::opRectangle},
280 {"rg", 3, {tchkNum, tchkNum, tchkNum},
281 &Gfx::opSetFillRGBColor},
282 {"ri", 1, {tchkName},
283 &Gfx::opSetRenderingIntent},
284 {"s", 0, {tchkNone},
285 &Gfx::opCloseStroke},
286 {"sc", -4, {tchkNum, tchkNum, tchkNum, tchkNum},
287 &Gfx::opSetFillColor},
288 {"scn", -33, {tchkSCN, tchkSCN, tchkSCN, tchkSCN,
289 tchkSCN, tchkSCN, tchkSCN, tchkSCN,
290 tchkSCN, tchkSCN, tchkSCN, tchkSCN,
291 tchkSCN, tchkSCN, tchkSCN, tchkSCN,
292 tchkSCN, tchkSCN, tchkSCN, tchkSCN,
293 tchkSCN, tchkSCN, tchkSCN, tchkSCN,
294 tchkSCN, tchkSCN, tchkSCN, tchkSCN,
295 tchkSCN, tchkSCN, tchkSCN, tchkSCN,
296 tchkSCN},
297 &Gfx::opSetFillColorN},
298 {"sh", 1, {tchkName},
299 &Gfx::opShFill},
300 {"v", 4, {tchkNum, tchkNum, tchkNum, tchkNum},
301 &Gfx::opCurveTo1},
302 {"w", 1, {tchkNum},
303 &Gfx::opSetLineWidth},
304 {"y", 4, {tchkNum, tchkNum, tchkNum, tchkNum},
305 &Gfx::opCurveTo2},
308 #ifdef _MSC_VER // this works around a bug in the VC7 compiler
309 # pragma optimize("",on)
310 #endif
312 #define numOps (sizeof(opTab) / sizeof(Operator))
314 static inline GBool isSameGfxColor(const GfxColor &colorA, const GfxColor &colorB, Guint nComps, double delta) {
315 for (Guint k = 0; k < nComps; ++k) {
316 if (abs(colorA.c[k] - colorB.c[k]) > delta) {
317 return false;
320 return true;
323 //------------------------------------------------------------------------
324 // GfxResources
325 //------------------------------------------------------------------------
327 GfxResources::GfxResources(XRef *xref, Dict *resDictA, GfxResources *nextA) :
328 gStateCache(2, xref) {
329 Object obj1, obj2;
330 Ref r;
332 if (resDictA) {
334 // build font dictionary
335 Dict *resDict = resDictA->copy(xref);
336 fonts = NULL;
337 resDict->lookupNF("Font", &obj1);
338 if (obj1.isRef()) {
339 obj1.fetch(xref, &obj2);
340 if (obj2.isDict()) {
341 r = obj1.getRef();
342 fonts = new GfxFontDict(xref, &r, obj2.getDict());
344 obj2.free();
345 } else if (obj1.isDict()) {
346 fonts = new GfxFontDict(xref, NULL, obj1.getDict());
348 obj1.free();
350 // get XObject dictionary
351 resDict->lookup("XObject", &xObjDict);
353 // get color space dictionary
354 resDict->lookup("ColorSpace", &colorSpaceDict);
356 // get pattern dictionary
357 resDict->lookup("Pattern", &patternDict);
359 // get shading dictionary
360 resDict->lookup("Shading", &shadingDict);
362 // get graphics state parameter dictionary
363 resDict->lookup("ExtGState", &gStateDict);
365 // get properties dictionary
366 resDict->lookup("Properties", &propertiesDict);
368 delete resDict;
369 } else {
370 fonts = NULL;
371 xObjDict.initNull();
372 colorSpaceDict.initNull();
373 patternDict.initNull();
374 shadingDict.initNull();
375 gStateDict.initNull();
376 propertiesDict.initNull();
379 next = nextA;
382 GfxResources::~GfxResources() {
383 if (fonts) {
384 delete fonts;
386 xObjDict.free();
387 colorSpaceDict.free();
388 patternDict.free();
389 shadingDict.free();
390 gStateDict.free();
391 propertiesDict.free();
394 GfxFont *GfxResources::lookupFont(char *name) {
395 GfxFont *font;
396 GfxResources *resPtr;
398 for (resPtr = this; resPtr; resPtr = resPtr->next) {
399 if (resPtr->fonts) {
400 if ((font = resPtr->fonts->lookup(name)))
401 return font;
404 error(errSyntaxError, -1, "Unknown font tag '{0:s}'", name);
405 return NULL;
408 GBool GfxResources::lookupXObject(char *name, Object *obj) {
409 GfxResources *resPtr;
411 for (resPtr = this; resPtr; resPtr = resPtr->next) {
412 if (resPtr->xObjDict.isDict()) {
413 if (!resPtr->xObjDict.dictLookup(name, obj)->isNull())
414 return gTrue;
415 obj->free();
418 error(errSyntaxError, -1, "XObject '{0:s}' is unknown", name);
419 return gFalse;
422 GBool GfxResources::lookupXObjectNF(char *name, Object *obj) {
423 GfxResources *resPtr;
425 for (resPtr = this; resPtr; resPtr = resPtr->next) {
426 if (resPtr->xObjDict.isDict()) {
427 if (!resPtr->xObjDict.dictLookupNF(name, obj)->isNull())
428 return gTrue;
429 obj->free();
432 error(errSyntaxError, -1, "XObject '{0:s}' is unknown", name);
433 return gFalse;
436 GBool GfxResources::lookupMarkedContentNF(char *name, Object *obj) {
437 GfxResources *resPtr;
439 for (resPtr = this; resPtr; resPtr = resPtr->next) {
440 if (resPtr->propertiesDict.isDict()) {
441 if (!resPtr->propertiesDict.dictLookupNF(name, obj)->isNull())
442 return gTrue;
443 obj->free();
446 error(errSyntaxError, -1, "Marked Content '{0:s}' is unknown", name);
447 return gFalse;
450 void GfxResources::lookupColorSpace(const char *name, Object *obj) {
451 GfxResources *resPtr;
453 for (resPtr = this; resPtr; resPtr = resPtr->next) {
454 if (resPtr->colorSpaceDict.isDict()) {
455 if (!resPtr->colorSpaceDict.dictLookup(name, obj)->isNull()) {
456 return;
458 obj->free();
461 obj->initNull();
464 GfxPattern *GfxResources::lookupPattern(char *name, OutputDev *out, GfxState *state) {
465 GfxResources *resPtr;
466 GfxPattern *pattern;
467 Object obj;
469 for (resPtr = this; resPtr; resPtr = resPtr->next) {
470 if (resPtr->patternDict.isDict()) {
471 if (!resPtr->patternDict.dictLookup(name, &obj)->isNull()) {
472 pattern = GfxPattern::parse(resPtr, &obj, out, state);
473 obj.free();
474 return pattern;
476 obj.free();
479 error(errSyntaxError, -1, "Unknown pattern '{0:s}'", name);
480 return NULL;
483 GfxShading *GfxResources::lookupShading(char *name, OutputDev *out, GfxState *state) {
484 GfxResources *resPtr;
485 GfxShading *shading;
486 Object obj;
488 for (resPtr = this; resPtr; resPtr = resPtr->next) {
489 if (resPtr->shadingDict.isDict()) {
490 if (!resPtr->shadingDict.dictLookup(name, &obj)->isNull()) {
491 shading = GfxShading::parse(resPtr, &obj, out, state);
492 obj.free();
493 return shading;
495 obj.free();
498 error(errSyntaxError, -1, "ExtGState '{0:s}' is unknown", name);
499 return NULL;
502 GBool GfxResources::lookupGState(char *name, Object *obj) {
503 if (!lookupGStateNF(name, obj))
504 return gFalse;
506 if (!obj->isRef())
507 return gTrue;
509 const Ref ref = obj->getRef();
510 if (!gStateCache.lookup(ref, obj)->isNull())
511 return gTrue;
512 obj->free();
514 gStateCache.put(ref)->copy(obj);
515 return gTrue;
518 GBool GfxResources::lookupGStateNF(char *name, Object *obj) {
519 GfxResources *resPtr;
521 for (resPtr = this; resPtr; resPtr = resPtr->next) {
522 if (resPtr->gStateDict.isDict()) {
523 if (!resPtr->gStateDict.dictLookupNF(name, obj)->isNull()) {
524 return gTrue;
526 obj->free();
529 error(errSyntaxError, -1, "ExtGState '{0:s}' is unknown", name);
530 return gFalse;
533 //------------------------------------------------------------------------
534 // Gfx
535 //------------------------------------------------------------------------
537 Gfx::Gfx(PDFDoc *docA, OutputDev *outA, int pageNum, Dict *resDict,
538 double hDPI, double vDPI, PDFRectangle *box,
539 PDFRectangle *cropBox, int rotate,
540 GBool (*abortCheckCbkA)(void *data),
541 void *abortCheckCbkDataA, XRef *xrefA)
543 int i;
545 doc = docA;
546 xref = (xrefA == NULL) ? doc->getXRef() : xrefA;
547 catalog = doc->getCatalog();
548 subPage = gFalse;
549 printCommands = globalParams->getPrintCommands();
550 profileCommands = globalParams->getProfileCommands();
551 mcStack = NULL;
552 parser = NULL;
554 // start the resource stack
555 res = new GfxResources(xref, resDict, NULL);
557 // initialize
558 out = outA;
559 state = new GfxState(hDPI, vDPI, box, rotate, out->upsideDown());
560 stackHeight = 1;
561 pushStateGuard();
562 fontChanged = gFalse;
563 clip = clipNone;
564 ignoreUndef = 0;
565 out->startPage(pageNum, state, xref);
566 out->setDefaultCTM(state->getCTM());
567 out->updateAll(state);
568 for (i = 0; i < 6; ++i) {
569 baseMatrix[i] = state->getCTM()[i];
571 formDepth = 0;
572 ocState = gTrue;
573 parser = NULL;
574 abortCheckCbk = abortCheckCbkA;
575 abortCheckCbkData = abortCheckCbkDataA;
577 // set crop box
578 if (cropBox) {
579 state->moveTo(cropBox->x1, cropBox->y1);
580 state->lineTo(cropBox->x2, cropBox->y1);
581 state->lineTo(cropBox->x2, cropBox->y2);
582 state->lineTo(cropBox->x1, cropBox->y2);
583 state->closePath();
584 state->clip();
585 out->clip(state);
586 state->clearPath();
588 #ifdef USE_CMS
589 initDisplayProfile();
590 #endif
593 Gfx::Gfx(PDFDoc *docA, OutputDev *outA, Dict *resDict,
594 PDFRectangle *box, PDFRectangle *cropBox,
595 GBool (*abortCheckCbkA)(void *data),
596 void *abortCheckCbkDataA, XRef *xrefA)
598 int i;
600 doc = docA;
601 xref = (xrefA == NULL) ? doc->getXRef() : xrefA;
602 catalog = doc->getCatalog();
603 subPage = gTrue;
604 printCommands = globalParams->getPrintCommands();
605 profileCommands = globalParams->getProfileCommands();
606 mcStack = NULL;
607 parser = NULL;
609 // start the resource stack
610 res = new GfxResources(xref, resDict, NULL);
612 // initialize
613 out = outA;
614 state = new GfxState(72, 72, box, 0, gFalse);
615 stackHeight = 1;
616 pushStateGuard();
617 fontChanged = gFalse;
618 clip = clipNone;
619 ignoreUndef = 0;
620 for (i = 0; i < 6; ++i) {
621 baseMatrix[i] = state->getCTM()[i];
623 formDepth = 0;
624 ocState = gTrue;
625 parser = NULL;
626 abortCheckCbk = abortCheckCbkA;
627 abortCheckCbkData = abortCheckCbkDataA;
629 // set crop box
630 if (cropBox) {
631 state->moveTo(cropBox->x1, cropBox->y1);
632 state->lineTo(cropBox->x2, cropBox->y1);
633 state->lineTo(cropBox->x2, cropBox->y2);
634 state->lineTo(cropBox->x1, cropBox->y2);
635 state->closePath();
636 state->clip();
637 out->clip(state);
638 state->clearPath();
640 #ifdef USE_CMS
641 initDisplayProfile();
642 #endif
645 #ifdef USE_CMS
647 #ifdef USE_LCMS1
648 #include <lcms.h>
649 #else
650 #include <lcms2.h>
651 #define LCMS_FLAGS cmsFLAGS_NOOPTIMIZE
652 #endif
654 void Gfx::initDisplayProfile() {
655 Object catDict;
656 xref->getCatalog(&catDict);
657 if (catDict.isDict()) {
658 Object outputIntents;
659 catDict.dictLookup("OutputIntents", &outputIntents);
660 if (outputIntents.isArray() && outputIntents.arrayGetLength() == 1) {
661 Object firstElement;
662 outputIntents.arrayGet(0, &firstElement);
663 if (firstElement.isDict()) {
664 Object profile;
665 firstElement.dictLookup("DestOutputProfile", &profile);
666 if (profile.isStream()) {
667 Stream *iccStream = profile.getStream();
668 int length = 0;
669 Guchar *profBuf = iccStream->toUnsignedChars(&length, 65536, 65536);
670 cmsHPROFILE hp = cmsOpenProfileFromMem(profBuf,length);
671 if (hp == 0) {
672 error(errSyntaxWarning, -1, "read ICCBased color space profile error");
673 } else {
674 state->setDisplayProfile(hp);
676 gfree(profBuf);
678 profile.free();
680 firstElement.free();
682 outputIntents.free();
684 catDict.free();
687 #endif
689 Gfx::~Gfx() {
690 while (stateGuards.size()) {
691 popStateGuard();
693 if (!subPage) {
694 out->endPage();
696 // There shouldn't be more saves, but pop them if there were any
697 while (state->hasSaves()) {
698 error(errSyntaxError, -1, "Found state under last state guard. Popping.");
699 restoreState();
701 delete state;
702 while (res) {
703 popResources();
705 while (mcStack) {
706 popMarkedContent();
710 void Gfx::display(Object *obj, GBool topLevel) {
711 Object obj2;
712 int i;
714 if (obj->isArray()) {
715 for (i = 0; i < obj->arrayGetLength(); ++i) {
716 obj->arrayGet(i, &obj2);
717 if (!obj2.isStream()) {
718 error(errSyntaxError, -1, "Weird page contents");
719 obj2.free();
720 return;
722 obj2.free();
724 } else if (!obj->isStream()) {
725 error(errSyntaxError, -1, "Weird page contents");
726 return;
728 parser = new Parser(xref, new Lexer(xref, obj), gFalse);
729 go(topLevel);
730 delete parser;
731 parser = NULL;
734 void Gfx::go(GBool topLevel) {
735 Object obj;
736 Object args[maxArgs];
737 int numArgs, i;
738 int lastAbortCheck;
740 // scan a sequence of objects
741 pushStateGuard();
742 updateLevel = 1; // make sure even empty pages trigger a call to dump()
743 lastAbortCheck = 0;
744 numArgs = 0;
745 parser->getObj(&obj);
746 while (!obj.isEOF()) {
747 commandAborted = gFalse;
749 // got a command - execute it
750 if (obj.isCmd()) {
751 if (printCommands) {
752 obj.print(stdout);
753 for (i = 0; i < numArgs; ++i) {
754 printf(" ");
755 args[i].print(stdout);
757 printf("\n");
758 fflush(stdout);
760 GooTimer timer;
762 // Run the operation
763 execOp(&obj, args, numArgs);
765 // Update the profile information
766 if (profileCommands) {
767 GooHash *hash;
769 hash = out->getProfileHash ();
770 if (hash) {
771 GooString *cmd_g;
772 ProfileData *data_p;
774 cmd_g = new GooString (obj.getCmd());
775 data_p = (ProfileData *)hash->lookup (cmd_g);
776 if (data_p == NULL) {
777 data_p = new ProfileData();
778 hash->add (cmd_g, data_p);
781 data_p->addElement(timer.getElapsed ());
784 obj.free();
785 for (i = 0; i < numArgs; ++i)
786 args[i].free();
787 numArgs = 0;
789 // periodically update display
790 if (++updateLevel >= 20000) {
791 out->dump();
792 updateLevel = 0;
795 // did the command throw an exception
796 if (commandAborted) {
797 // don't propogate; recursive drawing comes from Form XObjects which
798 // should probably be drawn in a separate context anyway for caching
799 commandAborted = gFalse;
800 break;
803 // check for an abort
804 if (abortCheckCbk) {
805 if (updateLevel - lastAbortCheck > 10) {
806 if ((*abortCheckCbk)(abortCheckCbkData)) {
807 break;
809 lastAbortCheck = updateLevel;
813 // got an argument - save it
814 } else if (numArgs < maxArgs) {
815 args[numArgs++] = obj;
817 // too many arguments - something is wrong
818 } else {
819 error(errSyntaxError, getPos(), "Too many args in content stream");
820 if (printCommands) {
821 printf("throwing away arg: ");
822 obj.print(stdout);
823 printf("\n");
824 fflush(stdout);
826 obj.free();
829 // grab the next object
830 parser->getObj(&obj);
832 obj.free();
834 // args at end with no command
835 if (numArgs > 0) {
836 error(errSyntaxError, getPos(), "Leftover args in content stream");
837 if (printCommands) {
838 printf("%d leftovers:", numArgs);
839 for (i = 0; i < numArgs; ++i) {
840 printf(" ");
841 args[i].print(stdout);
843 printf("\n");
844 fflush(stdout);
846 for (i = 0; i < numArgs; ++i)
847 args[i].free();
850 popStateGuard();
852 // update display
853 if (topLevel && updateLevel > 0) {
854 out->dump();
858 void Gfx::execOp(Object *cmd, Object args[], int numArgs) {
859 Operator *op;
860 char *name;
861 Object *argPtr;
862 int i;
864 // find operator
865 name = cmd->getCmd();
866 if (!(op = findOp(name))) {
867 if (ignoreUndef == 0)
868 error(errSyntaxError, getPos(), "Unknown operator '{0:s}'", name);
869 return;
872 // type check args
873 argPtr = args;
874 if (op->numArgs >= 0) {
875 if (numArgs < op->numArgs) {
876 error(errSyntaxError, getPos(), "Too few ({0:d}) args to '{1:s}' operator", numArgs, name);
877 commandAborted = gTrue;
878 return;
880 if (numArgs > op->numArgs) {
881 #if 0
882 error(errSyntaxWarning, getPos(),
883 "Too many ({0:d}) args to '{1:s}' operator", numArgs, name);
884 #endif
885 argPtr += numArgs - op->numArgs;
886 numArgs = op->numArgs;
888 } else {
889 if (numArgs > -op->numArgs) {
890 error(errSyntaxError, getPos(), "Too many ({0:d}) args to '{1:s}' operator",
891 numArgs, name);
892 return;
895 for (i = 0; i < numArgs; ++i) {
896 if (!checkArg(&argPtr[i], op->tchk[i])) {
897 error(errSyntaxError, getPos(), "Arg #{0:d} to '{1:s}' operator is wrong type ({2:s})",
898 i, name, argPtr[i].getTypeName());
899 return;
903 // do it
904 (this->*op->func)(argPtr, numArgs);
907 Operator *Gfx::findOp(char *name) {
908 int a, b, m, cmp;
910 a = -1;
911 b = numOps;
912 cmp = 0; // make gcc happy
913 // invariant: opTab[a] < name < opTab[b]
914 while (b - a > 1) {
915 m = (a + b) / 2;
916 cmp = strcmp(opTab[m].name, name);
917 if (cmp < 0)
918 a = m;
919 else if (cmp > 0)
920 b = m;
921 else
922 a = b = m;
924 if (cmp != 0)
925 return NULL;
926 return &opTab[a];
929 GBool Gfx::checkArg(Object *arg, TchkType type) {
930 switch (type) {
931 case tchkBool: return arg->isBool();
932 case tchkInt: return arg->isInt();
933 case tchkNum: return arg->isNum();
934 case tchkString: return arg->isString();
935 case tchkName: return arg->isName();
936 case tchkArray: return arg->isArray();
937 case tchkProps: return arg->isDict() || arg->isName();
938 case tchkSCN: return arg->isNum() || arg->isName();
939 case tchkNone: return gFalse;
941 return gFalse;
944 Goffset Gfx::getPos() {
945 return parser ? parser->getPos() : -1;
948 //------------------------------------------------------------------------
949 // graphics state operators
950 //------------------------------------------------------------------------
952 void Gfx::opSave(Object args[], int numArgs) {
953 saveState();
956 void Gfx::opRestore(Object args[], int numArgs) {
957 restoreState();
960 void Gfx::opConcat(Object args[], int numArgs) {
961 state->concatCTM(args[0].getNum(), args[1].getNum(),
962 args[2].getNum(), args[3].getNum(),
963 args[4].getNum(), args[5].getNum());
964 out->updateCTM(state, args[0].getNum(), args[1].getNum(),
965 args[2].getNum(), args[3].getNum(),
966 args[4].getNum(), args[5].getNum());
967 fontChanged = gTrue;
970 void Gfx::opSetDash(Object args[], int numArgs) {
971 Array *a;
972 int length;
973 Object obj;
974 double *dash;
975 int i;
977 a = args[0].getArray();
978 length = a->getLength();
979 if (length == 0) {
980 dash = NULL;
981 } else {
982 dash = (double *)gmallocn(length, sizeof(double));
983 for (i = 0; i < length; ++i) {
984 a->get(i, &obj);
985 if (obj.isNum()) {
986 dash[i] = obj.getNum();
988 obj.free();
991 state->setLineDash(dash, length, args[1].getNum());
992 out->updateLineDash(state);
995 void Gfx::opSetFlat(Object args[], int numArgs) {
996 state->setFlatness((int)args[0].getNum());
997 out->updateFlatness(state);
1000 void Gfx::opSetLineJoin(Object args[], int numArgs) {
1001 state->setLineJoin(args[0].getInt());
1002 out->updateLineJoin(state);
1005 void Gfx::opSetLineCap(Object args[], int numArgs) {
1006 state->setLineCap(args[0].getInt());
1007 out->updateLineCap(state);
1010 void Gfx::opSetMiterLimit(Object args[], int numArgs) {
1011 state->setMiterLimit(args[0].getNum());
1012 out->updateMiterLimit(state);
1015 void Gfx::opSetLineWidth(Object args[], int numArgs) {
1016 state->setLineWidth(args[0].getNum());
1017 out->updateLineWidth(state);
1020 void Gfx::opSetExtGState(Object args[], int numArgs) {
1021 Object obj1, obj2, obj3, obj4, obj5;
1022 Object args2[2];
1023 GfxBlendMode mode;
1024 GBool haveFillOP;
1025 Function *funcs[4];
1026 GfxColor backdropColor;
1027 GBool haveBackdropColor;
1028 GfxColorSpace *blendingColorSpace;
1029 GBool alpha, isolated, knockout;
1030 double opac;
1031 int i;
1033 if (!res->lookupGState(args[0].getName(), &obj1)) {
1034 return;
1036 if (!obj1.isDict()) {
1037 error(errSyntaxError, getPos(), "ExtGState '{0:s}' is wrong type", args[0].getName());
1038 obj1.free();
1039 return;
1041 if (printCommands) {
1042 printf(" gfx state dict: ");
1043 obj1.print();
1044 printf("\n");
1047 // parameters that are also set by individual PDF operators
1048 if (obj1.dictLookup("LW", &obj2)->isNum()) {
1049 opSetLineWidth(&obj2, 1);
1051 obj2.free();
1052 if (obj1.dictLookup("LC", &obj2)->isInt()) {
1053 opSetLineCap(&obj2, 1);
1055 obj2.free();
1056 if (obj1.dictLookup("LJ", &obj2)->isInt()) {
1057 opSetLineJoin(&obj2, 1);
1059 obj2.free();
1060 if (obj1.dictLookup("ML", &obj2)->isNum()) {
1061 opSetMiterLimit(&obj2, 1);
1063 obj2.free();
1064 if (obj1.dictLookup("D", &obj2)->isArray() &&
1065 obj2.arrayGetLength() == 2) {
1066 obj2.arrayGet(0, &args2[0]);
1067 obj2.arrayGet(1, &args2[1]);
1068 if (args2[0].isArray() && args2[1].isNum()) {
1069 opSetDash(args2, 2);
1071 args2[0].free();
1072 args2[1].free();
1074 obj2.free();
1075 #if 0 //~ need to add a new version of GfxResources::lookupFont() that
1076 //~ takes an indirect ref instead of a name
1077 if (obj1.dictLookup("Font", &obj2)->isArray() &&
1078 obj2.arrayGetLength() == 2) {
1079 obj2.arrayGet(0, &args2[0]);
1080 obj2.arrayGet(1, &args2[1]);
1081 if (args2[0].isDict() && args2[1].isNum()) {
1082 opSetFont(args2, 2);
1084 args2[0].free();
1085 args2[1].free();
1087 obj2.free();
1088 #endif
1089 if (obj1.dictLookup("FL", &obj2)->isNum()) {
1090 opSetFlat(&obj2, 1);
1092 obj2.free();
1094 // transparency support: blend mode, fill/stroke opacity
1095 if (!obj1.dictLookup("BM", &obj2)->isNull()) {
1096 if (state->parseBlendMode(&obj2, &mode)) {
1097 state->setBlendMode(mode);
1098 out->updateBlendMode(state);
1099 } else {
1100 error(errSyntaxError, getPos(), "Invalid blend mode in ExtGState");
1103 obj2.free();
1104 if (obj1.dictLookup("ca", &obj2)->isNum()) {
1105 opac = obj2.getNum();
1106 state->setFillOpacity(opac < 0 ? 0 : opac > 1 ? 1 : opac);
1107 out->updateFillOpacity(state);
1109 obj2.free();
1110 if (obj1.dictLookup("CA", &obj2)->isNum()) {
1111 opac = obj2.getNum();
1112 state->setStrokeOpacity(opac < 0 ? 0 : opac > 1 ? 1 : opac);
1113 out->updateStrokeOpacity(state);
1115 obj2.free();
1117 // fill/stroke overprint, overprint mode
1118 if ((haveFillOP = (obj1.dictLookup("op", &obj2)->isBool()))) {
1119 state->setFillOverprint(obj2.getBool());
1120 out->updateFillOverprint(state);
1122 obj2.free();
1123 if (obj1.dictLookup("OP", &obj2)->isBool()) {
1124 state->setStrokeOverprint(obj2.getBool());
1125 out->updateStrokeOverprint(state);
1126 if (!haveFillOP) {
1127 state->setFillOverprint(obj2.getBool());
1128 out->updateFillOverprint(state);
1131 obj2.free();
1132 if (obj1.dictLookup("OPM", &obj2)->isInt()) {
1133 state->setOverprintMode(obj2.getInt());
1134 out->updateOverprintMode(state);
1136 obj2.free();
1138 // stroke adjust
1139 if (obj1.dictLookup("SA", &obj2)->isBool()) {
1140 state->setStrokeAdjust(obj2.getBool());
1141 out->updateStrokeAdjust(state);
1143 obj2.free();
1145 // transfer function
1146 if (obj1.dictLookup("TR2", &obj2)->isNull()) {
1147 obj2.free();
1148 obj1.dictLookup("TR", &obj2);
1150 if (obj2.isName("Default") ||
1151 obj2.isName("Identity")) {
1152 funcs[0] = funcs[1] = funcs[2] = funcs[3] = NULL;
1153 state->setTransfer(funcs);
1154 out->updateTransfer(state);
1155 } else if (obj2.isArray() && obj2.arrayGetLength() == 4) {
1156 for (i = 0; i < 4; ++i) {
1157 obj2.arrayGet(i, &obj3);
1158 funcs[i] = Function::parse(&obj3);
1159 obj3.free();
1160 if (!funcs[i]) {
1161 break;
1164 if (i == 4) {
1165 state->setTransfer(funcs);
1166 out->updateTransfer(state);
1168 } else if (obj2.isName() || obj2.isDict() || obj2.isStream()) {
1169 if ((funcs[0] = Function::parse(&obj2))) {
1170 funcs[1] = funcs[2] = funcs[3] = NULL;
1171 state->setTransfer(funcs);
1172 out->updateTransfer(state);
1174 } else if (!obj2.isNull()) {
1175 error(errSyntaxError, getPos(), "Invalid transfer function in ExtGState");
1177 obj2.free();
1179 // alpha is shape
1180 if (obj1.dictLookup("AIS", &obj2)->isBool()) {
1181 state->setAlphaIsShape(obj2.getBool());
1182 out->updateAlphaIsShape(state);
1184 obj2.free();
1186 // text knockout
1187 if (obj1.dictLookup("TK", &obj2)->isBool()) {
1188 state->setTextKnockout(obj2.getBool());
1189 out->updateTextKnockout(state);
1191 obj2.free();
1193 // soft mask
1194 if (!obj1.dictLookup("SMask", &obj2)->isNull()) {
1195 if (obj2.isName("None")) {
1196 out->clearSoftMask(state);
1197 } else if (obj2.isDict()) {
1198 if (obj2.dictLookup("S", &obj3)->isName("Alpha")) {
1199 alpha = gTrue;
1200 } else { // "Luminosity"
1201 alpha = gFalse;
1203 obj3.free();
1204 funcs[0] = NULL;
1205 if (!obj2.dictLookup("TR", &obj3)->isNull()) {
1206 if (obj3.isName("Default") ||
1207 obj3.isName("Identity")) {
1208 funcs[0] = NULL;
1209 } else {
1210 funcs[0] = Function::parse(&obj3);
1211 if (funcs[0] == NULL ||
1212 funcs[0]->getInputSize() != 1 ||
1213 funcs[0]->getOutputSize() != 1) {
1214 error(errSyntaxError, getPos(),
1215 "Invalid transfer function in soft mask in ExtGState");
1216 delete funcs[0];
1217 funcs[0] = NULL;
1221 obj3.free();
1222 if ((haveBackdropColor = obj2.dictLookup("BC", &obj3)->isArray())) {
1223 for (i = 0; i < gfxColorMaxComps; ++i) {
1224 backdropColor.c[i] = 0;
1226 for (i = 0; i < obj3.arrayGetLength() && i < gfxColorMaxComps; ++i) {
1227 obj3.arrayGet(i, &obj4);
1228 if (obj4.isNum()) {
1229 backdropColor.c[i] = dblToCol(obj4.getNum());
1231 obj4.free();
1234 obj3.free();
1235 if (obj2.dictLookup("G", &obj3)->isStream()) {
1236 if (obj3.streamGetDict()->lookup("Group", &obj4)->isDict()) {
1237 blendingColorSpace = NULL;
1238 isolated = knockout = gFalse;
1239 if (!obj4.dictLookup("CS", &obj5)->isNull()) {
1240 blendingColorSpace = GfxColorSpace::parse(res, &obj5, out, state);
1242 obj5.free();
1243 if (obj4.dictLookup("I", &obj5)->isBool()) {
1244 isolated = obj5.getBool();
1246 obj5.free();
1247 if (obj4.dictLookup("K", &obj5)->isBool()) {
1248 knockout = obj5.getBool();
1250 obj5.free();
1251 if (!haveBackdropColor) {
1252 if (blendingColorSpace) {
1253 blendingColorSpace->getDefaultColor(&backdropColor);
1254 } else {
1255 //~ need to get the parent or default color space (?)
1256 for (i = 0; i < gfxColorMaxComps; ++i) {
1257 backdropColor.c[i] = 0;
1261 doSoftMask(&obj3, alpha, blendingColorSpace,
1262 isolated, knockout, funcs[0], &backdropColor);
1263 if (funcs[0]) {
1264 delete funcs[0];
1266 } else {
1267 error(errSyntaxError, getPos(), "Invalid soft mask in ExtGState - missing group");
1269 obj4.free();
1270 } else {
1271 error(errSyntaxError, getPos(), "Invalid soft mask in ExtGState - missing group");
1273 obj3.free();
1274 } else if (!obj2.isNull()) {
1275 error(errSyntaxError, getPos(), "Invalid soft mask in ExtGState");
1278 obj2.free();
1279 if (obj1.dictLookup("Font", &obj2)->isArray()) {
1280 GfxFont *font;
1281 if (obj2.arrayGetLength() == 2) {
1282 Object fargs0, fargs1;
1284 obj2.arrayGetNF(0,&fargs0);
1285 obj2.arrayGet(1,&fargs1);
1286 if (fargs0.isRef() && fargs1.isNum()) {
1287 Object fobj;
1288 Ref r;
1290 fargs0.fetch(xref, &fobj);
1291 if (fobj.isDict()) {
1292 r = fargs0.getRef();
1293 font = GfxFont::makeFont(xref,args[0].getName(),r,fobj.getDict());
1294 state->setFont(font,fargs1.getNum());
1295 fontChanged = gTrue;
1297 fobj.free();
1299 fargs0.free();
1300 fargs1.free();
1301 } else {
1302 error(errSyntaxError, getPos(), "Number of args mismatch for /Font in ExtGState");
1305 obj2.free();
1306 if (obj1.dictLookup("LW", &obj2)->isNum()) {
1307 opSetLineWidth(&obj2,1);
1309 obj2.free();
1310 if (obj1.dictLookup("LC", &obj2)->isInt()) {
1311 opSetLineCap(&obj2,1);
1313 obj2.free();
1314 if (obj1.dictLookup("LJ", &obj2)->isInt()) {
1315 opSetLineJoin(&obj2,1);
1317 obj2.free();
1318 if (obj1.dictLookup("ML", &obj2)->isNum()) {
1319 opSetMiterLimit(&obj2,1);
1321 obj2.free();
1322 if (obj1.dictLookup("D", &obj2)->isArray()) {
1323 if (obj2.arrayGetLength() == 2) {
1324 Object dargs[2];
1326 obj2.arrayGetNF(0,&dargs[0]);
1327 obj2.arrayGet(1,&dargs[1]);
1328 if (dargs[0].isArray() && dargs[1].isInt()) {
1329 opSetDash(dargs,2);
1331 dargs[0].free();
1332 dargs[1].free();
1333 } else {
1334 error(errSyntaxError, getPos(), "Number of args mismatch for /D in ExtGState");
1337 obj2.free();
1338 if (obj1.dictLookup("RI", &obj2)->isName()) {
1339 opSetRenderingIntent(&obj2,1);
1341 obj2.free();
1342 if (obj1.dictLookup("FL", &obj2)->isNum()) {
1343 opSetFlat(&obj2,1);
1345 obj2.free();
1347 obj1.free();
1350 void Gfx::doSoftMask(Object *str, GBool alpha,
1351 GfxColorSpace *blendingColorSpace,
1352 GBool isolated, GBool knockout,
1353 Function *transferFunc, GfxColor *backdropColor) {
1354 Dict *dict, *resDict;
1355 double m[6], bbox[4];
1356 Object obj1, obj2;
1357 int i;
1359 // check for excessive recursion
1360 if (formDepth > 20) {
1361 return;
1364 // get stream dict
1365 dict = str->streamGetDict();
1367 // check form type
1368 dict->lookup("FormType", &obj1);
1369 if (!(obj1.isNull() || (obj1.isInt() && obj1.getInt() == 1))) {
1370 error(errSyntaxError, getPos(), "Unknown form type");
1372 obj1.free();
1374 // get bounding box
1375 dict->lookup("BBox", &obj1);
1376 if (!obj1.isArray()) {
1377 obj1.free();
1378 error(errSyntaxError, getPos(), "Bad form bounding box");
1379 return;
1381 for (i = 0; i < 4; ++i) {
1382 obj1.arrayGet(i, &obj2);
1383 if (likely(obj2.isNum())) bbox[i] = obj2.getNum();
1384 else {
1385 obj2.free();
1386 obj1.free();
1387 error(errSyntaxError, getPos(), "Bad form bounding box (non number)");
1388 return;
1390 obj2.free();
1392 obj1.free();
1394 // get matrix
1395 dict->lookup("Matrix", &obj1);
1396 if (obj1.isArray()) {
1397 for (i = 0; i < 6; ++i) {
1398 obj1.arrayGet(i, &obj2);
1399 if (likely(obj2.isNum())) m[i] = obj2.getNum();
1400 else m[i] = 0;
1401 obj2.free();
1403 } else {
1404 m[0] = 1; m[1] = 0;
1405 m[2] = 0; m[3] = 1;
1406 m[4] = 0; m[5] = 0;
1408 obj1.free();
1410 // get resources
1411 dict->lookup("Resources", &obj1);
1412 resDict = obj1.isDict() ? obj1.getDict() : (Dict *)NULL;
1414 // draw it
1415 ++formDepth;
1416 drawForm(str, resDict, m, bbox, gTrue, gTrue,
1417 blendingColorSpace, isolated, knockout,
1418 alpha, transferFunc, backdropColor);
1419 --formDepth;
1421 if (blendingColorSpace) {
1422 delete blendingColorSpace;
1424 obj1.free();
1427 void Gfx::opSetRenderingIntent(Object args[], int numArgs) {
1428 state->setRenderingIntent(args[0].getName());
1431 //------------------------------------------------------------------------
1432 // color operators
1433 //------------------------------------------------------------------------
1435 void Gfx::opSetFillGray(Object args[], int numArgs) {
1436 GfxColor color;
1437 GfxColorSpace *colorSpace = NULL;
1438 Object obj;
1440 state->setFillPattern(NULL);
1441 res->lookupColorSpace("DefaultGray", &obj);
1442 if (!obj.isNull()) {
1443 colorSpace = GfxColorSpace::parse(res, &obj, out, state);
1445 if (colorSpace == NULL) {
1446 colorSpace = new GfxDeviceGrayColorSpace();
1448 obj.free();
1449 state->setFillColorSpace(colorSpace);
1450 out->updateFillColorSpace(state);
1451 color.c[0] = dblToCol(args[0].getNum());
1452 state->setFillColor(&color);
1453 out->updateFillColor(state);
1456 void Gfx::opSetStrokeGray(Object args[], int numArgs) {
1457 GfxColor color;
1458 GfxColorSpace *colorSpace = NULL;
1459 Object obj;
1461 state->setStrokePattern(NULL);
1462 res->lookupColorSpace("DefaultGray", &obj);
1463 if (!obj.isNull()) {
1464 colorSpace = GfxColorSpace::parse(res, &obj, out, state);
1466 if (colorSpace == NULL) {
1467 colorSpace = new GfxDeviceGrayColorSpace();
1469 obj.free();
1470 state->setStrokeColorSpace(colorSpace);
1471 out->updateStrokeColorSpace(state);
1472 color.c[0] = dblToCol(args[0].getNum());
1473 state->setStrokeColor(&color);
1474 out->updateStrokeColor(state);
1477 void Gfx::opSetFillCMYKColor(Object args[], int numArgs) {
1478 GfxColor color;
1479 GfxColorSpace *colorSpace = NULL;
1480 Object obj;
1481 int i;
1483 res->lookupColorSpace("DefaultCMYK", &obj);
1484 if (!obj.isNull()) {
1485 colorSpace = GfxColorSpace::parse(res, &obj, out, state);
1487 if (colorSpace == NULL) {
1488 colorSpace = new GfxDeviceCMYKColorSpace();
1490 obj.free();
1491 state->setFillPattern(NULL);
1492 state->setFillColorSpace(colorSpace);
1493 out->updateFillColorSpace(state);
1494 for (i = 0; i < 4; ++i) {
1495 color.c[i] = dblToCol(args[i].getNum());
1497 state->setFillColor(&color);
1498 out->updateFillColor(state);
1501 void Gfx::opSetStrokeCMYKColor(Object args[], int numArgs) {
1502 GfxColor color;
1503 GfxColorSpace *colorSpace = NULL;
1504 Object obj;
1505 int i;
1507 state->setStrokePattern(NULL);
1508 res->lookupColorSpace("DefaultCMYK", &obj);
1509 if (!obj.isNull()) {
1510 colorSpace = GfxColorSpace::parse(res, &obj, out, state);
1512 if (colorSpace == NULL) {
1513 colorSpace = new GfxDeviceCMYKColorSpace();
1515 obj.free();
1516 state->setStrokeColorSpace(colorSpace);
1517 out->updateStrokeColorSpace(state);
1518 for (i = 0; i < 4; ++i) {
1519 color.c[i] = dblToCol(args[i].getNum());
1521 state->setStrokeColor(&color);
1522 out->updateStrokeColor(state);
1525 void Gfx::opSetFillRGBColor(Object args[], int numArgs) {
1526 Object obj;
1527 GfxColorSpace *colorSpace = NULL;
1528 GfxColor color;
1529 int i;
1531 state->setFillPattern(NULL);
1532 res->lookupColorSpace("DefaultRGB", &obj);
1533 if (!obj.isNull()) {
1534 colorSpace = GfxColorSpace::parse(res, &obj, out, state);
1536 if (colorSpace == NULL) {
1537 colorSpace = new GfxDeviceRGBColorSpace();
1539 obj.free();
1540 state->setFillColorSpace(colorSpace);
1541 out->updateFillColorSpace(state);
1542 for (i = 0; i < 3; ++i) {
1543 color.c[i] = dblToCol(args[i].getNum());
1545 state->setFillColor(&color);
1546 out->updateFillColor(state);
1549 void Gfx::opSetStrokeRGBColor(Object args[], int numArgs) {
1550 Object obj;
1551 GfxColorSpace *colorSpace = NULL;
1552 GfxColor color;
1553 int i;
1555 state->setStrokePattern(NULL);
1556 res->lookupColorSpace("DefaultRGB", &obj);
1557 if (!obj.isNull()) {
1558 colorSpace = GfxColorSpace::parse(res, &obj, out, state);
1560 if (colorSpace == NULL) {
1561 colorSpace = new GfxDeviceRGBColorSpace();
1563 obj.free();
1564 state->setStrokeColorSpace(colorSpace);
1565 out->updateStrokeColorSpace(state);
1566 for (i = 0; i < 3; ++i) {
1567 color.c[i] = dblToCol(args[i].getNum());
1569 state->setStrokeColor(&color);
1570 out->updateStrokeColor(state);
1573 void Gfx::opSetFillColorSpace(Object args[], int numArgs) {
1574 Object obj;
1575 GfxColorSpace *colorSpace;
1576 GfxColor color;
1578 res->lookupColorSpace(args[0].getName(), &obj);
1579 if (obj.isNull()) {
1580 colorSpace = GfxColorSpace::parse(res, &args[0], out, state);
1581 } else {
1582 colorSpace = GfxColorSpace::parse(res, &obj, out, state);
1584 obj.free();
1585 if (colorSpace) {
1586 state->setFillPattern(NULL);
1587 state->setFillColorSpace(colorSpace);
1588 out->updateFillColorSpace(state);
1589 colorSpace->getDefaultColor(&color);
1590 state->setFillColor(&color);
1591 out->updateFillColor(state);
1592 } else {
1593 error(errSyntaxError, getPos(), "Bad color space (fill)");
1597 void Gfx::opSetStrokeColorSpace(Object args[], int numArgs) {
1598 Object obj;
1599 GfxColorSpace *colorSpace;
1600 GfxColor color;
1602 state->setStrokePattern(NULL);
1603 res->lookupColorSpace(args[0].getName(), &obj);
1604 if (obj.isNull()) {
1605 colorSpace = GfxColorSpace::parse(res, &args[0], out, state);
1606 } else {
1607 colorSpace = GfxColorSpace::parse(res, &obj, out, state);
1609 obj.free();
1610 if (colorSpace) {
1611 state->setStrokeColorSpace(colorSpace);
1612 out->updateStrokeColorSpace(state);
1613 colorSpace->getDefaultColor(&color);
1614 state->setStrokeColor(&color);
1615 out->updateStrokeColor(state);
1616 } else {
1617 error(errSyntaxError, getPos(), "Bad color space (stroke)");
1621 void Gfx::opSetFillColor(Object args[], int numArgs) {
1622 GfxColor color;
1623 int i;
1625 if (numArgs != state->getFillColorSpace()->getNComps()) {
1626 error(errSyntaxError, getPos(), "Incorrect number of arguments in 'sc' command");
1627 return;
1629 state->setFillPattern(NULL);
1630 for (i = 0; i < numArgs; ++i) {
1631 color.c[i] = dblToCol(args[i].getNum());
1633 state->setFillColor(&color);
1634 out->updateFillColor(state);
1637 void Gfx::opSetStrokeColor(Object args[], int numArgs) {
1638 GfxColor color;
1639 int i;
1641 if (numArgs != state->getStrokeColorSpace()->getNComps()) {
1642 error(errSyntaxError, getPos(), "Incorrect number of arguments in 'SC' command");
1643 return;
1645 state->setStrokePattern(NULL);
1646 for (i = 0; i < numArgs; ++i) {
1647 color.c[i] = dblToCol(args[i].getNum());
1649 state->setStrokeColor(&color);
1650 out->updateStrokeColor(state);
1653 void Gfx::opSetFillColorN(Object args[], int numArgs) {
1654 GfxColor color;
1655 GfxPattern *pattern;
1656 int i;
1658 if (state->getFillColorSpace()->getMode() == csPattern) {
1659 if (numArgs > 1) {
1660 if (!((GfxPatternColorSpace *)state->getFillColorSpace())->getUnder() ||
1661 numArgs - 1 != ((GfxPatternColorSpace *)state->getFillColorSpace())
1662 ->getUnder()->getNComps()) {
1663 error(errSyntaxError, getPos(), "Incorrect number of arguments in 'scn' command");
1664 return;
1666 for (i = 0; i < numArgs - 1 && i < gfxColorMaxComps; ++i) {
1667 if (args[i].isNum()) {
1668 color.c[i] = dblToCol(args[i].getNum());
1669 } else {
1670 color.c[i] = 0; // TODO Investigate if this is what Adobe does
1673 state->setFillColor(&color);
1674 out->updateFillColor(state);
1676 if (numArgs > 0) {
1677 if (args[numArgs-1].isName() &&
1678 (pattern = res->lookupPattern(args[numArgs-1].getName(), out, state))) {
1679 state->setFillPattern(pattern);
1683 } else {
1684 if (numArgs != state->getFillColorSpace()->getNComps()) {
1685 error(errSyntaxError, getPos(), "Incorrect number of arguments in 'scn' command");
1686 return;
1688 state->setFillPattern(NULL);
1689 for (i = 0; i < numArgs && i < gfxColorMaxComps; ++i) {
1690 if (args[i].isNum()) {
1691 color.c[i] = dblToCol(args[i].getNum());
1692 } else {
1693 color.c[i] = 0; // TODO Investigate if this is what Adobe does
1696 state->setFillColor(&color);
1697 out->updateFillColor(state);
1701 void Gfx::opSetStrokeColorN(Object args[], int numArgs) {
1702 GfxColor color;
1703 GfxPattern *pattern;
1704 int i;
1706 if (state->getStrokeColorSpace()->getMode() == csPattern) {
1707 if (numArgs > 1) {
1708 if (!((GfxPatternColorSpace *)state->getStrokeColorSpace())
1709 ->getUnder() ||
1710 numArgs - 1 != ((GfxPatternColorSpace *)state->getStrokeColorSpace())
1711 ->getUnder()->getNComps()) {
1712 error(errSyntaxError, getPos(), "Incorrect number of arguments in 'SCN' command");
1713 return;
1715 for (i = 0; i < numArgs - 1 && i < gfxColorMaxComps; ++i) {
1716 if (args[i].isNum()) {
1717 color.c[i] = dblToCol(args[i].getNum());
1718 } else {
1719 color.c[i] = 0; // TODO Investigate if this is what Adobe does
1722 state->setStrokeColor(&color);
1723 out->updateStrokeColor(state);
1725 if (unlikely(numArgs <= 0)) {
1726 error(errSyntaxError, getPos(), "Incorrect number of arguments in 'SCN' command");
1727 return;
1729 if (args[numArgs-1].isName() &&
1730 (pattern = res->lookupPattern(args[numArgs-1].getName(), out, state))) {
1731 state->setStrokePattern(pattern);
1734 } else {
1735 if (numArgs != state->getStrokeColorSpace()->getNComps()) {
1736 error(errSyntaxError, getPos(), "Incorrect number of arguments in 'SCN' command");
1737 return;
1739 state->setStrokePattern(NULL);
1740 for (i = 0; i < numArgs && i < gfxColorMaxComps; ++i) {
1741 if (args[i].isNum()) {
1742 color.c[i] = dblToCol(args[i].getNum());
1743 } else {
1744 color.c[i] = 0; // TODO Investigate if this is what Adobe does
1747 state->setStrokeColor(&color);
1748 out->updateStrokeColor(state);
1752 //------------------------------------------------------------------------
1753 // path segment operators
1754 //------------------------------------------------------------------------
1756 void Gfx::opMoveTo(Object args[], int numArgs) {
1757 state->moveTo(args[0].getNum(), args[1].getNum());
1760 void Gfx::opLineTo(Object args[], int numArgs) {
1761 if (!state->isCurPt()) {
1762 error(errSyntaxError, getPos(), "No current point in lineto");
1763 return;
1765 state->lineTo(args[0].getNum(), args[1].getNum());
1768 void Gfx::opCurveTo(Object args[], int numArgs) {
1769 double x1, y1, x2, y2, x3, y3;
1771 if (!state->isCurPt()) {
1772 error(errSyntaxError, getPos(), "No current point in curveto");
1773 return;
1775 x1 = args[0].getNum();
1776 y1 = args[1].getNum();
1777 x2 = args[2].getNum();
1778 y2 = args[3].getNum();
1779 x3 = args[4].getNum();
1780 y3 = args[5].getNum();
1781 state->curveTo(x1, y1, x2, y2, x3, y3);
1784 void Gfx::opCurveTo1(Object args[], int numArgs) {
1785 double x1, y1, x2, y2, x3, y3;
1787 if (!state->isCurPt()) {
1788 error(errSyntaxError, getPos(), "No current point in curveto1");
1789 return;
1791 x1 = state->getCurX();
1792 y1 = state->getCurY();
1793 x2 = args[0].getNum();
1794 y2 = args[1].getNum();
1795 x3 = args[2].getNum();
1796 y3 = args[3].getNum();
1797 state->curveTo(x1, y1, x2, y2, x3, y3);
1800 void Gfx::opCurveTo2(Object args[], int numArgs) {
1801 double x1, y1, x2, y2, x3, y3;
1803 if (!state->isCurPt()) {
1804 error(errSyntaxError, getPos(), "No current point in curveto2");
1805 return;
1807 x1 = args[0].getNum();
1808 y1 = args[1].getNum();
1809 x2 = args[2].getNum();
1810 y2 = args[3].getNum();
1811 x3 = x2;
1812 y3 = y2;
1813 state->curveTo(x1, y1, x2, y2, x3, y3);
1816 void Gfx::opRectangle(Object args[], int numArgs) {
1817 double x, y, w, h;
1819 x = args[0].getNum();
1820 y = args[1].getNum();
1821 w = args[2].getNum();
1822 h = args[3].getNum();
1823 state->moveTo(x, y);
1824 state->lineTo(x + w, y);
1825 state->lineTo(x + w, y + h);
1826 state->lineTo(x, y + h);
1827 state->closePath();
1830 void Gfx::opClosePath(Object args[], int numArgs) {
1831 if (!state->isCurPt()) {
1832 error(errSyntaxError, getPos(), "No current point in closepath");
1833 return;
1835 state->closePath();
1838 //------------------------------------------------------------------------
1839 // path painting operators
1840 //------------------------------------------------------------------------
1842 void Gfx::opEndPath(Object args[], int numArgs) {
1843 doEndPath();
1846 void Gfx::opStroke(Object args[], int numArgs) {
1847 if (!state->isCurPt()) {
1848 //error(errSyntaxError, getPos(), "No path in stroke");
1849 return;
1851 if (state->isPath()) {
1852 if (ocState) {
1853 if (state->getStrokeColorSpace()->getMode() == csPattern) {
1854 doPatternStroke();
1855 } else {
1856 out->stroke(state);
1860 doEndPath();
1863 void Gfx::opCloseStroke(Object * /*args[]*/, int /*numArgs*/) {
1864 if (!state->isCurPt()) {
1865 //error(errSyntaxError, getPos(), "No path in closepath/stroke");
1866 return;
1868 if (state->isPath()) {
1869 state->closePath();
1870 if (ocState) {
1871 if (state->getStrokeColorSpace()->getMode() == csPattern) {
1872 doPatternStroke();
1873 } else {
1874 out->stroke(state);
1878 doEndPath();
1881 void Gfx::opFill(Object args[], int numArgs) {
1882 if (!state->isCurPt()) {
1883 //error(errSyntaxError, getPos(), "No path in fill");
1884 return;
1886 if (state->isPath()) {
1887 if (ocState) {
1888 if (state->getFillColorSpace()->getMode() == csPattern) {
1889 doPatternFill(gFalse);
1890 } else {
1891 out->fill(state);
1895 doEndPath();
1898 void Gfx::opEOFill(Object args[], int numArgs) {
1899 if (!state->isCurPt()) {
1900 //error(errSyntaxError, getPos(), "No path in eofill");
1901 return;
1903 if (state->isPath()) {
1904 if (ocState) {
1905 if (state->getFillColorSpace()->getMode() == csPattern) {
1906 doPatternFill(gTrue);
1907 } else {
1908 out->eoFill(state);
1912 doEndPath();
1915 void Gfx::opFillStroke(Object args[], int numArgs) {
1916 if (!state->isCurPt()) {
1917 //error(errSyntaxError, getPos(), "No path in fill/stroke");
1918 return;
1920 if (state->isPath()) {
1921 if (ocState) {
1922 if (state->getFillColorSpace()->getMode() == csPattern) {
1923 doPatternFill(gFalse);
1924 } else {
1925 out->fill(state);
1927 if (state->getStrokeColorSpace()->getMode() == csPattern) {
1928 doPatternStroke();
1929 } else {
1930 out->stroke(state);
1934 doEndPath();
1937 void Gfx::opCloseFillStroke(Object args[], int numArgs) {
1938 if (!state->isCurPt()) {
1939 //error(errSyntaxError, getPos(), "No path in closepath/fill/stroke");
1940 return;
1942 if (state->isPath()) {
1943 state->closePath();
1944 if (ocState) {
1945 if (state->getFillColorSpace()->getMode() == csPattern) {
1946 doPatternFill(gFalse);
1947 } else {
1948 out->fill(state);
1950 if (state->getStrokeColorSpace()->getMode() == csPattern) {
1951 doPatternStroke();
1952 } else {
1953 out->stroke(state);
1957 doEndPath();
1960 void Gfx::opEOFillStroke(Object args[], int numArgs) {
1961 if (!state->isCurPt()) {
1962 //error(errSyntaxError, getPos(), "No path in eofill/stroke");
1963 return;
1965 if (state->isPath()) {
1966 if (ocState) {
1967 if (state->getFillColorSpace()->getMode() == csPattern) {
1968 doPatternFill(gTrue);
1969 } else {
1970 out->eoFill(state);
1972 if (state->getStrokeColorSpace()->getMode() == csPattern) {
1973 doPatternStroke();
1974 } else {
1975 out->stroke(state);
1979 doEndPath();
1982 void Gfx::opCloseEOFillStroke(Object args[], int numArgs) {
1983 if (!state->isCurPt()) {
1984 //error(errSyntaxError, getPos(), "No path in closepath/eofill/stroke");
1985 return;
1987 if (state->isPath()) {
1988 state->closePath();
1989 if (ocState) {
1990 if (state->getFillColorSpace()->getMode() == csPattern) {
1991 doPatternFill(gTrue);
1992 } else {
1993 out->eoFill(state);
1995 if (state->getStrokeColorSpace()->getMode() == csPattern) {
1996 doPatternStroke();
1997 } else {
1998 out->stroke(state);
2002 doEndPath();
2005 void Gfx::doPatternFill(GBool eoFill) {
2006 GfxPattern *pattern;
2008 // this is a bit of a kludge -- patterns can be really slow, so we
2009 // skip them if we're only doing text extraction, since they almost
2010 // certainly don't contain any text
2011 if (!out->needNonText()) {
2012 return;
2015 if (!(pattern = state->getFillPattern())) {
2016 return;
2018 switch (pattern->getType()) {
2019 case 1:
2020 doTilingPatternFill((GfxTilingPattern *)pattern, gFalse, eoFill, gFalse);
2021 break;
2022 case 2:
2023 doShadingPatternFill((GfxShadingPattern *)pattern, gFalse, eoFill, gFalse);
2024 break;
2025 default:
2026 error(errSyntaxError, getPos(), "Unknown pattern type ({0:d}) in fill",
2027 pattern->getType());
2028 break;
2032 void Gfx::doPatternStroke() {
2033 GfxPattern *pattern;
2035 // this is a bit of a kludge -- patterns can be really slow, so we
2036 // skip them if we're only doing text extraction, since they almost
2037 // certainly don't contain any text
2038 if (!out->needNonText()) {
2039 return;
2042 if (!(pattern = state->getStrokePattern())) {
2043 return;
2045 switch (pattern->getType()) {
2046 case 1:
2047 doTilingPatternFill((GfxTilingPattern *)pattern, gTrue, gFalse, gFalse);
2048 break;
2049 case 2:
2050 doShadingPatternFill((GfxShadingPattern *)pattern, gTrue, gFalse, gFalse);
2051 break;
2052 default:
2053 error(errSyntaxError, getPos(), "Unknown pattern type ({0:d}) in stroke",
2054 pattern->getType());
2055 break;
2059 void Gfx::doPatternText() {
2060 GfxPattern *pattern;
2062 // this is a bit of a kludge -- patterns can be really slow, so we
2063 // skip them if we're only doing text extraction, since they almost
2064 // certainly don't contain any text
2065 if (!out->needNonText()) {
2066 return;
2069 if (!(pattern = state->getFillPattern())) {
2070 return;
2072 switch (pattern->getType()) {
2073 case 1:
2074 doTilingPatternFill((GfxTilingPattern *)pattern, gFalse, gFalse, gTrue);
2075 break;
2076 case 2:
2077 doShadingPatternFill((GfxShadingPattern *)pattern, gFalse, gFalse, gTrue);
2078 break;
2079 default:
2080 error(errSyntaxError, getPos(), "Unknown pattern type ({0:d}) in fill",
2081 pattern->getType());
2082 break;
2086 void Gfx::doPatternImageMask(Object *ref, Stream *str, int width, int height,
2087 GBool invert, GBool inlineImg) {
2088 saveState();
2090 out->setSoftMaskFromImageMask(state, ref, str,
2091 width, height, invert, inlineImg, baseMatrix);
2093 state->clearPath();
2094 state->moveTo(0, 0);
2095 state->lineTo(1, 0);
2096 state->lineTo(1, 1);
2097 state->lineTo(0, 1);
2098 state->closePath();
2099 doPatternText();
2101 out->unsetSoftMaskFromImageMask(state, baseMatrix);
2102 restoreState();
2105 void Gfx::doTilingPatternFill(GfxTilingPattern *tPat,
2106 GBool stroke, GBool eoFill, GBool text) {
2107 GfxPatternColorSpace *patCS;
2108 GfxColorSpace *cs;
2109 GfxColor color;
2110 GfxState *savedState;
2111 double xMin, yMin, xMax, yMax, x, y, x1, y1;
2112 double cxMin, cyMin, cxMax, cyMax;
2113 int xi0, yi0, xi1, yi1, xi, yi;
2114 double *ctm, *btm, *ptm;
2115 double m[6], ictm[6], m1[6], imb[6];
2116 double det;
2117 double xstep, ystep;
2118 int i;
2120 // get color space
2121 patCS = (GfxPatternColorSpace *)(stroke ? state->getStrokeColorSpace()
2122 : state->getFillColorSpace());
2124 // construct a (pattern space) -> (current space) transform matrix
2125 ctm = state->getCTM();
2126 btm = baseMatrix;
2127 ptm = tPat->getMatrix();
2128 // iCTM = invert CTM
2129 det = 1 / (ctm[0] * ctm[3] - ctm[1] * ctm[2]);
2130 ictm[0] = ctm[3] * det;
2131 ictm[1] = -ctm[1] * det;
2132 ictm[2] = -ctm[2] * det;
2133 ictm[3] = ctm[0] * det;
2134 ictm[4] = (ctm[2] * ctm[5] - ctm[3] * ctm[4]) * det;
2135 ictm[5] = (ctm[1] * ctm[4] - ctm[0] * ctm[5]) * det;
2136 // m1 = PTM * BTM = PTM * base transform matrix
2137 m1[0] = ptm[0] * btm[0] + ptm[1] * btm[2];
2138 m1[1] = ptm[0] * btm[1] + ptm[1] * btm[3];
2139 m1[2] = ptm[2] * btm[0] + ptm[3] * btm[2];
2140 m1[3] = ptm[2] * btm[1] + ptm[3] * btm[3];
2141 m1[4] = ptm[4] * btm[0] + ptm[5] * btm[2] + btm[4];
2142 m1[5] = ptm[4] * btm[1] + ptm[5] * btm[3] + btm[5];
2143 // m = m1 * iCTM = (PTM * BTM) * (iCTM)
2144 m[0] = m1[0] * ictm[0] + m1[1] * ictm[2];
2145 m[1] = m1[0] * ictm[1] + m1[1] * ictm[3];
2146 m[2] = m1[2] * ictm[0] + m1[3] * ictm[2];
2147 m[3] = m1[2] * ictm[1] + m1[3] * ictm[3];
2148 m[4] = m1[4] * ictm[0] + m1[5] * ictm[2] + ictm[4];
2149 m[5] = m1[4] * ictm[1] + m1[5] * ictm[3] + ictm[5];
2151 // construct a (device space) -> (pattern space) transform matrix
2152 det = 1 / (m1[0] * m1[3] - m1[1] * m1[2]);
2153 imb[0] = m1[3] * det;
2154 imb[1] = -m1[1] * det;
2155 imb[2] = -m1[2] * det;
2156 imb[3] = m1[0] * det;
2157 imb[4] = (m1[2] * m1[5] - m1[3] * m1[4]) * det;
2158 imb[5] = (m1[1] * m1[4] - m1[0] * m1[5]) * det;
2160 // save current graphics state
2161 savedState = saveStateStack();
2163 // set underlying color space (for uncolored tiling patterns); set
2164 // various other parameters (stroke color, line width) to match
2165 // Adobe's behavior
2166 state->setFillPattern(NULL);
2167 state->setStrokePattern(NULL);
2168 if (tPat->getPaintType() == 2 && (cs = patCS->getUnder())) {
2169 state->setFillColorSpace(cs->copy());
2170 out->updateFillColorSpace(state);
2171 state->setStrokeColorSpace(cs->copy());
2172 out->updateStrokeColorSpace(state);
2173 if (stroke) {
2174 state->setFillColor(state->getStrokeColor());
2175 } else {
2176 state->setStrokeColor(state->getFillColor());
2178 out->updateFillColor(state);
2179 out->updateStrokeColor(state);
2180 } else {
2181 cs = new GfxDeviceGrayColorSpace();
2182 state->setFillColorSpace(cs);
2183 cs->getDefaultColor(&color);
2184 state->setFillColor(&color);
2185 out->updateFillColorSpace(state);
2186 state->setStrokeColorSpace(new GfxDeviceGrayColorSpace());
2187 state->setStrokeColor(&color);
2188 out->updateStrokeColorSpace(state);
2190 if (!stroke) {
2191 state->setLineWidth(0);
2192 out->updateLineWidth(state);
2195 // clip to current path
2196 if (stroke) {
2197 state->clipToStrokePath();
2198 out->clipToStrokePath(state);
2199 } else if (!text) {
2200 state->clip();
2201 if (eoFill) {
2202 out->eoClip(state);
2203 } else {
2204 out->clip(state);
2207 state->clearPath();
2209 // get the clip region, check for empty
2210 state->getClipBBox(&cxMin, &cyMin, &cxMax, &cyMax);
2211 if (cxMin > cxMax || cyMin > cyMax) {
2212 goto restore;
2215 // transform clip region bbox to pattern space
2216 xMin = xMax = cxMin * imb[0] + cyMin * imb[2] + imb[4];
2217 yMin = yMax = cxMin * imb[1] + cyMin * imb[3] + imb[5];
2218 x1 = cxMin * imb[0] + cyMax * imb[2] + imb[4];
2219 y1 = cxMin * imb[1] + cyMax * imb[3] + imb[5];
2220 if (x1 < xMin) {
2221 xMin = x1;
2222 } else if (x1 > xMax) {
2223 xMax = x1;
2225 if (y1 < yMin) {
2226 yMin = y1;
2227 } else if (y1 > yMax) {
2228 yMax = y1;
2230 x1 = cxMax * imb[0] + cyMin * imb[2] + imb[4];
2231 y1 = cxMax * imb[1] + cyMin * imb[3] + imb[5];
2232 if (x1 < xMin) {
2233 xMin = x1;
2234 } else if (x1 > xMax) {
2235 xMax = x1;
2237 if (y1 < yMin) {
2238 yMin = y1;
2239 } else if (y1 > yMax) {
2240 yMax = y1;
2242 x1 = cxMax * imb[0] + cyMax * imb[2] + imb[4];
2243 y1 = cxMax * imb[1] + cyMax * imb[3] + imb[5];
2244 if (x1 < xMin) {
2245 xMin = x1;
2246 } else if (x1 > xMax) {
2247 xMax = x1;
2249 if (y1 < yMin) {
2250 yMin = y1;
2251 } else if (y1 > yMax) {
2252 yMax = y1;
2255 // draw the pattern
2256 //~ this should treat negative steps differently -- start at right/top
2257 //~ edge instead of left/bottom (?)
2258 xstep = fabs(tPat->getXStep());
2259 ystep = fabs(tPat->getYStep());
2260 if (tPat->getBBox()[0] < tPat->getBBox()[2]) {
2261 xi0 = (int)ceil((xMin - tPat->getBBox()[2]) / xstep);
2262 xi1 = (int)floor((xMax - tPat->getBBox()[0]) / xstep) + 1;
2263 } else {
2264 xi0 = (int)ceil((xMin - tPat->getBBox()[0]) / xstep);
2265 xi1 = (int)floor((xMax - tPat->getBBox()[2]) / xstep) + 1;
2267 if (tPat->getBBox()[1] < tPat->getBBox()[3]) {
2268 yi0 = (int)ceil((yMin - tPat->getBBox()[3]) / ystep);
2269 yi1 = (int)floor((yMax - tPat->getBBox()[1]) / ystep) + 1;
2270 } else {
2271 yi0 = (int)ceil((yMin - tPat->getBBox()[1]) / ystep);
2272 yi1 = (int)floor((yMax - tPat->getBBox()[3]) / ystep) + 1;
2274 for (i = 0; i < 4; ++i) {
2275 m1[i] = m[i];
2277 m1[4] = m[4];
2278 m1[5] = m[5];
2279 if (out->useTilingPatternFill() &&
2280 out->tilingPatternFill(state, this, catalog, tPat->getContentStream(),
2281 tPat->getMatrix(), tPat->getPaintType(), tPat->getTilingType(),
2282 tPat->getResDict(), m1, tPat->getBBox(),
2283 xi0, yi0, xi1, yi1, xstep, ystep)) {
2284 goto restore;
2285 } else {
2286 out->updatePatternOpacity(state);
2287 for (yi = yi0; yi < yi1; ++yi) {
2288 for (xi = xi0; xi < xi1; ++xi) {
2289 x = xi * xstep;
2290 y = yi * ystep;
2291 m1[4] = x * m[0] + y * m[2] + m[4];
2292 m1[5] = x * m[1] + y * m[3] + m[5];
2293 drawForm(tPat->getContentStream(), tPat->getResDict(),
2294 m1, tPat->getBBox());
2297 out->clearPatternOpacity(state);
2300 // restore graphics state
2301 restore:
2302 restoreStateStack(savedState);
2305 void Gfx::doShadingPatternFill(GfxShadingPattern *sPat,
2306 GBool stroke, GBool eoFill, GBool text) {
2307 GfxShading *shading;
2308 GfxState *savedState;
2309 double *ctm, *btm, *ptm;
2310 double m[6], ictm[6], m1[6];
2311 double xMin, yMin, xMax, yMax;
2312 double det;
2314 shading = sPat->getShading();
2316 // save current graphics state
2317 savedState = saveStateStack();
2319 // clip to current path
2320 if (stroke) {
2321 state->clipToStrokePath();
2322 out->clipToStrokePath(state);
2323 } else if (!text) {
2324 state->clip();
2325 if (eoFill) {
2326 out->eoClip(state);
2327 } else {
2328 out->clip(state);
2331 state->clearPath();
2333 // construct a (pattern space) -> (current space) transform matrix
2334 ctm = state->getCTM();
2335 btm = baseMatrix;
2336 ptm = sPat->getMatrix();
2337 // iCTM = invert CTM
2338 det = 1 / (ctm[0] * ctm[3] - ctm[1] * ctm[2]);
2339 ictm[0] = ctm[3] * det;
2340 ictm[1] = -ctm[1] * det;
2341 ictm[2] = -ctm[2] * det;
2342 ictm[3] = ctm[0] * det;
2343 ictm[4] = (ctm[2] * ctm[5] - ctm[3] * ctm[4]) * det;
2344 ictm[5] = (ctm[1] * ctm[4] - ctm[0] * ctm[5]) * det;
2345 // m1 = PTM * BTM = PTM * base transform matrix
2346 m1[0] = ptm[0] * btm[0] + ptm[1] * btm[2];
2347 m1[1] = ptm[0] * btm[1] + ptm[1] * btm[3];
2348 m1[2] = ptm[2] * btm[0] + ptm[3] * btm[2];
2349 m1[3] = ptm[2] * btm[1] + ptm[3] * btm[3];
2350 m1[4] = ptm[4] * btm[0] + ptm[5] * btm[2] + btm[4];
2351 m1[5] = ptm[4] * btm[1] + ptm[5] * btm[3] + btm[5];
2352 // m = m1 * iCTM = (PTM * BTM) * (iCTM)
2353 m[0] = m1[0] * ictm[0] + m1[1] * ictm[2];
2354 m[1] = m1[0] * ictm[1] + m1[1] * ictm[3];
2355 m[2] = m1[2] * ictm[0] + m1[3] * ictm[2];
2356 m[3] = m1[2] * ictm[1] + m1[3] * ictm[3];
2357 m[4] = m1[4] * ictm[0] + m1[5] * ictm[2] + ictm[4];
2358 m[5] = m1[4] * ictm[1] + m1[5] * ictm[3] + ictm[5];
2360 // set the new matrix
2361 state->concatCTM(m[0], m[1], m[2], m[3], m[4], m[5]);
2362 out->updateCTM(state, m[0], m[1], m[2], m[3], m[4], m[5]);
2364 // clip to bbox
2365 if (shading->getHasBBox()) {
2366 shading->getBBox(&xMin, &yMin, &xMax, &yMax);
2367 state->moveTo(xMin, yMin);
2368 state->lineTo(xMax, yMin);
2369 state->lineTo(xMax, yMax);
2370 state->lineTo(xMin, yMax);
2371 state->closePath();
2372 state->clip();
2373 out->clip(state);
2374 state->clearPath();
2377 // set the color space
2378 state->setFillColorSpace(shading->getColorSpace()->copy());
2379 out->updateFillColorSpace(state);
2381 // background color fill
2382 if (shading->getHasBackground()) {
2383 state->setFillColor(shading->getBackground());
2384 out->updateFillColor(state);
2385 state->getUserClipBBox(&xMin, &yMin, &xMax, &yMax);
2386 state->moveTo(xMin, yMin);
2387 state->lineTo(xMax, yMin);
2388 state->lineTo(xMax, yMax);
2389 state->lineTo(xMin, yMax);
2390 state->closePath();
2391 out->fill(state);
2392 state->clearPath();
2395 #if 1 //~tmp: turn off anti-aliasing temporarily
2396 GBool vaa = out->getVectorAntialias();
2397 if (vaa) {
2398 out->setVectorAntialias(gFalse);
2400 #endif
2402 // do shading type-specific operations
2403 switch (shading->getType()) {
2404 case 1:
2405 doFunctionShFill((GfxFunctionShading *)shading);
2406 break;
2407 case 2:
2408 doAxialShFill((GfxAxialShading *)shading);
2409 break;
2410 case 3:
2411 doRadialShFill((GfxRadialShading *)shading);
2412 break;
2413 case 4:
2414 case 5:
2415 doGouraudTriangleShFill((GfxGouraudTriangleShading *)shading);
2416 break;
2417 case 6:
2418 case 7:
2419 doPatchMeshShFill((GfxPatchMeshShading *)shading);
2420 break;
2423 #if 1 //~tmp: turn off anti-aliasing temporarily
2424 if (vaa) {
2425 out->setVectorAntialias(gTrue);
2427 #endif
2429 // restore graphics state
2430 restoreStateStack(savedState);
2433 void Gfx::opShFill(Object args[], int numArgs) {
2434 GfxShading *shading;
2435 GfxState *savedState;
2436 double xMin, yMin, xMax, yMax;
2438 if (!ocState) {
2439 return;
2442 if (!(shading = res->lookupShading(args[0].getName(), out, state))) {
2443 return;
2446 // save current graphics state
2447 savedState = saveStateStack();
2449 // clip to bbox
2450 if (shading->getHasBBox()) {
2451 shading->getBBox(&xMin, &yMin, &xMax, &yMax);
2452 state->moveTo(xMin, yMin);
2453 state->lineTo(xMax, yMin);
2454 state->lineTo(xMax, yMax);
2455 state->lineTo(xMin, yMax);
2456 state->closePath();
2457 state->clip();
2458 out->clip(state);
2459 state->clearPath();
2462 // set the color space
2463 state->setFillColorSpace(shading->getColorSpace()->copy());
2464 out->updateFillColorSpace(state);
2466 #if 1 //~tmp: turn off anti-aliasing temporarily
2467 GBool vaa = out->getVectorAntialias();
2468 if (vaa) {
2469 out->setVectorAntialias(gFalse);
2471 #endif
2473 // do shading type-specific operations
2474 switch (shading->getType()) {
2475 case 1:
2476 doFunctionShFill((GfxFunctionShading *)shading);
2477 break;
2478 case 2:
2479 doAxialShFill((GfxAxialShading *)shading);
2480 break;
2481 case 3:
2482 doRadialShFill((GfxRadialShading *)shading);
2483 break;
2484 case 4:
2485 case 5:
2486 doGouraudTriangleShFill((GfxGouraudTriangleShading *)shading);
2487 break;
2488 case 6:
2489 case 7:
2490 doPatchMeshShFill((GfxPatchMeshShading *)shading);
2491 break;
2494 #if 1 //~tmp: turn off anti-aliasing temporarily
2495 if (vaa) {
2496 out->setVectorAntialias(gTrue);
2498 #endif
2500 // restore graphics state
2501 restoreStateStack(savedState);
2503 delete shading;
2506 void Gfx::doFunctionShFill(GfxFunctionShading *shading) {
2507 double x0, y0, x1, y1;
2508 GfxColor colors[4];
2510 if (out->useShadedFills( shading->getType() ) &&
2511 out->functionShadedFill(state, shading)) {
2512 return;
2515 shading->getDomain(&x0, &y0, &x1, &y1);
2516 shading->getColor(x0, y0, &colors[0]);
2517 shading->getColor(x0, y1, &colors[1]);
2518 shading->getColor(x1, y0, &colors[2]);
2519 shading->getColor(x1, y1, &colors[3]);
2520 doFunctionShFill1(shading, x0, y0, x1, y1, colors, 0);
2523 void Gfx::doFunctionShFill1(GfxFunctionShading *shading,
2524 double x0, double y0,
2525 double x1, double y1,
2526 GfxColor *colors, int depth) {
2527 GfxColor fillColor;
2528 GfxColor color0M, color1M, colorM0, colorM1, colorMM;
2529 GfxColor colors2[4];
2530 double *matrix;
2531 double xM, yM;
2532 int nComps, i, j;
2534 nComps = shading->getColorSpace()->getNComps();
2535 matrix = shading->getMatrix();
2537 // compare the four corner colors
2538 for (i = 0; i < 4; ++i) {
2539 for (j = 0; j < nComps; ++j) {
2540 if (abs(colors[i].c[j] - colors[(i+1)&3].c[j]) > functionColorDelta) {
2541 break;
2544 if (j < nComps) {
2545 break;
2549 // center of the rectangle
2550 xM = 0.5 * (x0 + x1);
2551 yM = 0.5 * (y0 + y1);
2553 // the four corner colors are close (or we hit the recursive limit)
2554 // -- fill the rectangle; but require at least one subdivision
2555 // (depth==0) to avoid problems when the four outer corners of the
2556 // shaded region are the same color
2557 if ((i == 4 && depth > 0) || depth == functionMaxDepth) {
2559 // use the center color
2560 shading->getColor(xM, yM, &fillColor);
2561 state->setFillColor(&fillColor);
2562 out->updateFillColor(state);
2564 // fill the rectangle
2565 state->moveTo(x0 * matrix[0] + y0 * matrix[2] + matrix[4],
2566 x0 * matrix[1] + y0 * matrix[3] + matrix[5]);
2567 state->lineTo(x1 * matrix[0] + y0 * matrix[2] + matrix[4],
2568 x1 * matrix[1] + y0 * matrix[3] + matrix[5]);
2569 state->lineTo(x1 * matrix[0] + y1 * matrix[2] + matrix[4],
2570 x1 * matrix[1] + y1 * matrix[3] + matrix[5]);
2571 state->lineTo(x0 * matrix[0] + y1 * matrix[2] + matrix[4],
2572 x0 * matrix[1] + y1 * matrix[3] + matrix[5]);
2573 state->closePath();
2574 out->fill(state);
2575 state->clearPath();
2577 // the four corner colors are not close enough -- subdivide the
2578 // rectangle
2579 } else {
2581 // colors[0] colorM0 colors[2]
2582 // (x0,y0) (xM,y0) (x1,y0)
2583 // +----------+----------+
2584 // | | |
2585 // | UL | UR |
2586 // color0M | colorMM | color1M
2587 // (x0,yM) +----------+----------+ (x1,yM)
2588 // | (xM,yM) |
2589 // | LL | LR |
2590 // | | |
2591 // +----------+----------+
2592 // colors[1] colorM1 colors[3]
2593 // (x0,y1) (xM,y1) (x1,y1)
2595 shading->getColor(x0, yM, &color0M);
2596 shading->getColor(x1, yM, &color1M);
2597 shading->getColor(xM, y0, &colorM0);
2598 shading->getColor(xM, y1, &colorM1);
2599 shading->getColor(xM, yM, &colorMM);
2601 // upper-left sub-rectangle
2602 colors2[0] = colors[0];
2603 colors2[1] = color0M;
2604 colors2[2] = colorM0;
2605 colors2[3] = colorMM;
2606 doFunctionShFill1(shading, x0, y0, xM, yM, colors2, depth + 1);
2608 // lower-left sub-rectangle
2609 colors2[0] = color0M;
2610 colors2[1] = colors[1];
2611 colors2[2] = colorMM;
2612 colors2[3] = colorM1;
2613 doFunctionShFill1(shading, x0, yM, xM, y1, colors2, depth + 1);
2615 // upper-right sub-rectangle
2616 colors2[0] = colorM0;
2617 colors2[1] = colorMM;
2618 colors2[2] = colors[2];
2619 colors2[3] = color1M;
2620 doFunctionShFill1(shading, xM, y0, x1, yM, colors2, depth + 1);
2622 // lower-right sub-rectangle
2623 colors2[0] = colorMM;
2624 colors2[1] = colorM1;
2625 colors2[2] = color1M;
2626 colors2[3] = colors[3];
2627 doFunctionShFill1(shading, xM, yM, x1, y1, colors2, depth + 1);
2631 static void bubbleSort(double array[])
2633 for (int j = 0; j < 3; ++j) {
2634 int kk = j;
2635 for (int k = j + 1; k < 4; ++k) {
2636 if (array[k] < array[kk]) {
2637 kk = k;
2640 double tmp = array[j];
2641 array[j] = array[kk];
2642 array[kk] = tmp;
2646 void Gfx::doAxialShFill(GfxAxialShading *shading) {
2647 double xMin, yMin, xMax, yMax;
2648 double x0, y0, x1, y1;
2649 double dx, dy, mul;
2650 GBool dxZero, dyZero;
2651 double bboxIntersections[4];
2652 double tMin, tMax, tx, ty;
2653 double s[4], sMin, sMax, tmp;
2654 double ux0, uy0, ux1, uy1, vx0, vy0, vx1, vy1;
2655 double t0, t1, tt;
2656 double ta[axialMaxSplits + 1];
2657 int next[axialMaxSplits + 1];
2658 GfxColor color0, color1;
2659 int nComps;
2660 int i, j, k;
2661 GBool needExtend = gTrue;
2663 // get the clip region bbox
2664 state->getUserClipBBox(&xMin, &yMin, &xMax, &yMax);
2666 // compute min and max t values, based on the four corners of the
2667 // clip region bbox
2668 shading->getCoords(&x0, &y0, &x1, &y1);
2669 dx = x1 - x0;
2670 dy = y1 - y0;
2671 dxZero = fabs(dx) < 0.01;
2672 dyZero = fabs(dy) < 0.01;
2673 if (dxZero && dyZero) {
2674 tMin = tMax = 0;
2675 } else {
2676 mul = 1 / (dx * dx + dy * dy);
2677 bboxIntersections[0] = ((xMin - x0) * dx + (yMin - y0) * dy) * mul;
2678 bboxIntersections[1] = ((xMin - x0) * dx + (yMax - y0) * dy) * mul;
2679 bboxIntersections[2] = ((xMax - x0) * dx + (yMin - y0) * dy) * mul;
2680 bboxIntersections[3] = ((xMax - x0) * dx + (yMax - y0) * dy) * mul;
2681 bubbleSort(bboxIntersections);
2682 tMin = bboxIntersections[0];
2683 tMax = bboxIntersections[3];
2684 if (tMin < 0 && !shading->getExtend0()) {
2685 tMin = 0;
2687 if (tMax > 1 && !shading->getExtend1()) {
2688 tMax = 1;
2692 if (out->useShadedFills( shading->getType() ) &&
2693 out->axialShadedFill(state, shading, tMin, tMax)) {
2694 return;
2697 // get the function domain
2698 t0 = shading->getDomain0();
2699 t1 = shading->getDomain1();
2701 // Traverse the t axis and do the shading.
2703 // For each point (tx, ty) on the t axis, consider a line through
2704 // that point perpendicular to the t axis:
2706 // x(s) = tx + s * -dy --> s = (x - tx) / -dy
2707 // y(s) = ty + s * dx --> s = (y - ty) / dx
2709 // Then look at the intersection of this line with the bounding box
2710 // (xMin, yMin, xMax, yMax). In the general case, there are four
2711 // intersection points:
2713 // s0 = (xMin - tx) / -dy
2714 // s1 = (xMax - tx) / -dy
2715 // s2 = (yMin - ty) / dx
2716 // s3 = (yMax - ty) / dx
2718 // and we want the middle two s values.
2720 // In the case where dx = 0, take s0 and s1; in the case where dy =
2721 // 0, take s2 and s3.
2723 // Each filled polygon is bounded by two of these line segments
2724 // perpdendicular to the t axis.
2726 // The t axis is bisected into smaller regions until the color
2727 // difference across a region is small enough, and then the region
2728 // is painted with a single color.
2730 // set up: require at least one split to avoid problems when the two
2731 // ends of the t axis have the same color
2732 nComps = shading->getColorSpace()->getNComps();
2733 ta[0] = tMin;
2734 next[0] = axialMaxSplits / 2;
2735 ta[axialMaxSplits / 2] = 0.5 * (tMin + tMax);
2736 next[axialMaxSplits / 2] = axialMaxSplits;
2737 ta[axialMaxSplits] = tMax;
2739 // compute the color at t = tMin
2740 if (tMin < 0) {
2741 tt = t0;
2742 } else if (tMin > 1) {
2743 tt = t1;
2744 } else {
2745 tt = t0 + (t1 - t0) * tMin;
2747 shading->getColor(tt, &color0);
2749 if (out->useFillColorStop()) {
2750 // make sure we add stop color when t = tMin
2751 state->setFillColor(&color0);
2752 out->updateFillColorStop(state, 0);
2755 // compute the coordinates of the point on the t axis at t = tMin;
2756 // then compute the intersection of the perpendicular line with the
2757 // bounding box
2758 tx = x0 + tMin * dx;
2759 ty = y0 + tMin * dy;
2760 if (dxZero && dyZero) {
2761 sMin = sMax = 0;
2762 } else if (dxZero) {
2763 sMin = (xMin - tx) / -dy;
2764 sMax = (xMax - tx) / -dy;
2765 if (sMin > sMax) { tmp = sMin; sMin = sMax; sMax = tmp; }
2766 } else if (dyZero) {
2767 sMin = (yMin - ty) / dx;
2768 sMax = (yMax - ty) / dx;
2769 if (sMin > sMax) { tmp = sMin; sMin = sMax; sMax = tmp; }
2770 } else {
2771 s[0] = (yMin - ty) / dx;
2772 s[1] = (yMax - ty) / dx;
2773 s[2] = (xMin - tx) / -dy;
2774 s[3] = (xMax - tx) / -dy;
2775 bubbleSort(s);
2776 sMin = s[1];
2777 sMax = s[2];
2779 ux0 = tx - sMin * dy;
2780 uy0 = ty + sMin * dx;
2781 vx0 = tx - sMax * dy;
2782 vy0 = ty + sMax * dx;
2784 i = 0;
2785 bool doneBBox1, doneBBox2;
2786 if (dxZero && dyZero) {
2787 doneBBox1 = doneBBox2 = true;
2788 } else {
2789 doneBBox1 = bboxIntersections[1] < tMin;
2790 doneBBox2 = bboxIntersections[2] > tMax;
2793 // If output device doesn't support the extended mode required
2794 // we have to do it here
2795 needExtend = !out->axialShadedSupportExtend(state, shading);
2797 while (i < axialMaxSplits) {
2799 // bisect until color difference is small enough or we hit the
2800 // bisection limit
2801 j = next[i];
2802 while (j > i + 1) {
2803 if (ta[j] < 0) {
2804 tt = t0;
2805 } else if (ta[j] > 1) {
2806 tt = t1;
2807 } else {
2808 tt = t0 + (t1 - t0) * ta[j];
2810 shading->getColor(tt, &color1);
2811 if (isSameGfxColor(color1, color0, nComps, axialColorDelta)) {
2812 // in these two if what we guarantee is that if we are skipping lots of
2813 // positions because the colors are the same, we still create a region
2814 // with vertexs passing by bboxIntersections[1] and bboxIntersections[2]
2815 // otherwise we can have empty regions that should really be painted
2816 // like happened in bug 19896
2817 // What we do to ensure that we pass a line through this points
2818 // is making sure use the exact bboxIntersections[] value as one of the used ta[] values
2819 if (!doneBBox1 && ta[i] < bboxIntersections[1] && ta[j] > bboxIntersections[1]) {
2820 int teoricalj = (int) ((bboxIntersections[1] - tMin) * axialMaxSplits / (tMax - tMin));
2821 if (teoricalj <= i) teoricalj = i + 1;
2822 if (teoricalj < j) {
2823 next[i] = teoricalj;
2824 next[teoricalj] = j;
2826 else {
2827 teoricalj = j;
2829 ta[teoricalj] = bboxIntersections[1];
2830 j = teoricalj;
2831 doneBBox1 = true;
2833 if (!doneBBox2 && ta[i] < bboxIntersections[2] && ta[j] > bboxIntersections[2]) {
2834 int teoricalj = (int) ((bboxIntersections[2] - tMin) * axialMaxSplits / (tMax - tMin));
2835 if (teoricalj <= i) teoricalj = i + 1;
2836 if (teoricalj < j) {
2837 next[i] = teoricalj;
2838 next[teoricalj] = j;
2840 else {
2841 teoricalj = j;
2843 ta[teoricalj] = bboxIntersections[2];
2844 j = teoricalj;
2845 doneBBox2 = true;
2847 break;
2849 k = (i + j) / 2;
2850 ta[k] = 0.5 * (ta[i] + ta[j]);
2851 next[i] = k;
2852 next[k] = j;
2853 j = k;
2856 // use the average of the colors of the two sides of the region
2857 for (k = 0; k < nComps; ++k) {
2858 color0.c[k] = (color0.c[k] + color1.c[k]) / 2;
2861 // compute the coordinates of the point on the t axis; then
2862 // compute the intersection of the perpendicular line with the
2863 // bounding box
2864 tx = x0 + ta[j] * dx;
2865 ty = y0 + ta[j] * dy;
2866 if (dxZero && dyZero) {
2867 sMin = sMax = 0;
2868 } else if (dxZero) {
2869 sMin = (xMin - tx) / -dy;
2870 sMax = (xMax - tx) / -dy;
2871 if (sMin > sMax) { tmp = sMin; sMin = sMax; sMax = tmp; }
2872 } else if (dyZero) {
2873 sMin = (yMin - ty) / dx;
2874 sMax = (yMax - ty) / dx;
2875 if (sMin > sMax) { tmp = sMin; sMin = sMax; sMax = tmp; }
2876 } else {
2877 s[0] = (yMin - ty) / dx;
2878 s[1] = (yMax - ty) / dx;
2879 s[2] = (xMin - tx) / -dy;
2880 s[3] = (xMax - tx) / -dy;
2881 bubbleSort(s);
2882 sMin = s[1];
2883 sMax = s[2];
2885 ux1 = tx - sMin * dy;
2886 uy1 = ty + sMin * dx;
2887 vx1 = tx - sMax * dy;
2888 vy1 = ty + sMax * dx;
2890 // set the color
2891 state->setFillColor(&color0);
2892 if (out->useFillColorStop())
2893 out->updateFillColorStop(state, (ta[j] - tMin)/(tMax - tMin));
2894 else
2895 out->updateFillColor(state);
2897 if (needExtend) {
2898 // fill the region
2899 state->moveTo(ux0, uy0);
2900 state->lineTo(vx0, vy0);
2901 state->lineTo(vx1, vy1);
2902 state->lineTo(ux1, uy1);
2903 state->closePath();
2906 if (!out->useFillColorStop()) {
2907 out->fill(state);
2908 state->clearPath();
2911 // set up for next region
2912 ux0 = ux1;
2913 uy0 = uy1;
2914 vx0 = vx1;
2915 vy0 = vy1;
2916 color0 = color1;
2917 i = next[i];
2920 if (out->useFillColorStop()) {
2921 if (!needExtend) {
2922 state->moveTo(xMin, yMin);
2923 state->lineTo(xMin, yMax);
2924 state->lineTo(xMax, yMax);
2925 state->lineTo(xMax, yMin);
2926 state->closePath();
2928 out->fill(state);
2929 state->clearPath();
2933 static inline void getShadingColorRadialHelper(double t0, double t1, double t, GfxRadialShading *shading, GfxColor *color)
2935 if (t0 < t1) {
2936 if (t < t0) {
2937 shading->getColor(t0, color);
2938 } else if (t > t1) {
2939 shading->getColor(t1, color);
2940 } else {
2941 shading->getColor(t, color);
2943 } else {
2944 if (t > t0) {
2945 shading->getColor(t0, color);
2946 } else if (t < t1) {
2947 shading->getColor(t1, color);
2948 } else {
2949 shading->getColor(t, color);
2954 void Gfx::doRadialShFill(GfxRadialShading *shading) {
2955 double xMin, yMin, xMax, yMax;
2956 double x0, y0, r0, x1, y1, r1, t0, t1;
2957 int nComps;
2958 GfxColor colorA, colorB;
2959 double xa, ya, xb, yb, ra, rb;
2960 double ta, tb, sa, sb;
2961 double sz, xz, yz, sMin, sMax;
2962 GBool enclosed;
2963 int ia, ib, k, n;
2964 double *ctm;
2965 double theta, alpha, angle, t;
2966 GBool needExtend = gTrue;
2968 // get the shading info
2969 shading->getCoords(&x0, &y0, &r0, &x1, &y1, &r1);
2970 t0 = shading->getDomain0();
2971 t1 = shading->getDomain1();
2972 nComps = shading->getColorSpace()->getNComps();
2974 // Compute the point at which r(s) = 0; check for the enclosed
2975 // circles case; and compute the angles for the tangent lines.
2976 if (x0 == x1 && y0 == y1) {
2977 enclosed = gTrue;
2978 theta = 0; // make gcc happy
2979 sz = 0; // make gcc happy
2980 } else if (r0 == r1) {
2981 enclosed = gFalse;
2982 theta = 0;
2983 sz = 0; // make gcc happy
2984 } else {
2985 sz = (r1 > r0) ? -r0 / (r1 - r0) : -r1 / (r0 - r1);
2986 xz = x0 + sz * (x1 - x0);
2987 yz = y0 + sz * (y1 - y0);
2988 enclosed = (xz - x0) * (xz - x0) + (yz - y0) * (yz - y0) <= r0 * r0;
2989 theta = asin(r0 / sqrt((x0 - xz) * (x0 - xz) + (y0 - yz) * (y0 - yz)));
2990 if (r0 > r1) {
2991 theta = -theta;
2994 if (enclosed) {
2995 alpha = 0;
2996 } else {
2997 alpha = atan2(y1 - y0, x1 - x0);
3000 // compute the (possibly extended) s range
3001 state->getUserClipBBox(&xMin, &yMin, &xMax, &yMax);
3002 if (enclosed) {
3003 sMin = 0;
3004 sMax = 1;
3005 } else {
3006 sMin = 1;
3007 sMax = 0;
3008 // solve for x(s) + r(s) = xMin
3009 if ((x1 + r1) - (x0 + r0) != 0) {
3010 sa = (xMin - (x0 + r0)) / ((x1 + r1) - (x0 + r0));
3011 if (sa < sMin) {
3012 sMin = sa;
3013 } else if (sa > sMax) {
3014 sMax = sa;
3017 // solve for x(s) - r(s) = xMax
3018 if ((x1 - r1) - (x0 - r0) != 0) {
3019 sa = (xMax - (x0 - r0)) / ((x1 - r1) - (x0 - r0));
3020 if (sa < sMin) {
3021 sMin = sa;
3022 } else if (sa > sMax) {
3023 sMax = sa;
3026 // solve for y(s) + r(s) = yMin
3027 if ((y1 + r1) - (y0 + r0) != 0) {
3028 sa = (yMin - (y0 + r0)) / ((y1 + r1) - (y0 + r0));
3029 if (sa < sMin) {
3030 sMin = sa;
3031 } else if (sa > sMax) {
3032 sMax = sa;
3035 // solve for y(s) - r(s) = yMax
3036 if ((y1 - r1) - (y0 - r0) != 0) {
3037 sa = (yMax - (y0 - r0)) / ((y1 - r1) - (y0 - r0));
3038 if (sa < sMin) {
3039 sMin = sa;
3040 } else if (sa > sMax) {
3041 sMax = sa;
3044 // check against sz
3045 if (r0 < r1) {
3046 if (sMin < sz) {
3047 sMin = sz;
3049 } else if (r0 > r1) {
3050 if (sMax > sz) {
3051 sMax = sz;
3054 // check the 'extend' flags
3055 if (!shading->getExtend0() && sMin < 0) {
3056 sMin = 0;
3058 if (!shading->getExtend1() && sMax > 1) {
3059 sMax = 1;
3063 if (out->useShadedFills( shading->getType() ) &&
3064 out->radialShadedFill(state, shading, sMin, sMax)) {
3065 return;
3068 // compute the number of steps into which circles must be divided to
3069 // achieve a curve flatness of 0.1 pixel in device space for the
3070 // largest circle (note that "device space" is 72 dpi when generating
3071 // PostScript, hence the relatively small 0.1 pixel accuracy)
3072 ctm = state->getCTM();
3073 t = fabs(ctm[0]);
3074 if (fabs(ctm[1]) > t) {
3075 t = fabs(ctm[1]);
3077 if (fabs(ctm[2]) > t) {
3078 t = fabs(ctm[2]);
3080 if (fabs(ctm[3]) > t) {
3081 t = fabs(ctm[3]);
3083 if (r0 > r1) {
3084 t *= r0;
3085 } else {
3086 t *= r1;
3088 if (t < 1) {
3089 n = 3;
3090 } else {
3091 n = (int)(M_PI / acos(1 - 0.1 / t));
3092 if (n < 3) {
3093 n = 3;
3094 } else if (n > 200) {
3095 n = 200;
3099 // setup for the start circle
3100 ia = 0;
3101 sa = sMin;
3102 ta = t0 + sa * (t1 - t0);
3103 xa = x0 + sa * (x1 - x0);
3104 ya = y0 + sa * (y1 - y0);
3105 ra = r0 + sa * (r1 - r0);
3106 getShadingColorRadialHelper(t0, t1, ta, shading, &colorA);
3108 needExtend = !out->radialShadedSupportExtend(state, shading);
3110 // fill the circles
3111 while (ia < radialMaxSplits) {
3113 // go as far along the t axis (toward t1) as we can, such that the
3114 // color difference is within the tolerance (radialColorDelta) --
3115 // this uses bisection (between the current value, t, and t1),
3116 // limited to radialMaxSplits points along the t axis; require at
3117 // least one split to avoid problems when the innermost and
3118 // outermost colors are the same
3119 ib = radialMaxSplits;
3120 sb = sMax;
3121 tb = t0 + sb * (t1 - t0);
3122 getShadingColorRadialHelper(t0, t1, tb, shading, &colorB);
3123 while (ib - ia > 1) {
3124 if (isSameGfxColor(colorB, colorA, nComps, radialColorDelta)) {
3125 // The shading is not necessarily lineal so having two points with the
3126 // same color does not mean all the areas in between have the same color too
3127 int ic = ia + 1;
3128 for (; ic <= ib; ic++) {
3129 GfxColor colorC;
3130 const double sc = sMin + ((double)ic / (double)radialMaxSplits) * (sMax - sMin);
3131 const double tc = t0 + sc * (t1 - t0);
3132 getShadingColorRadialHelper(t0, t1, tc, shading, &colorC);
3133 if (!isSameGfxColor(colorC, colorA, nComps, radialColorDelta)) {
3134 break;
3137 ib = (ic > ia + 1) ? ic - 1 : ia + 1;
3138 sb = sMin + ((double)ib / (double)radialMaxSplits) * (sMax - sMin);
3139 tb = t0 + sb * (t1 - t0);
3140 getShadingColorRadialHelper(t0, t1, tb, shading, &colorB);
3141 break;
3143 ib = (ia + ib) / 2;
3144 sb = sMin + ((double)ib / (double)radialMaxSplits) * (sMax - sMin);
3145 tb = t0 + sb * (t1 - t0);
3146 getShadingColorRadialHelper(t0, t1, tb, shading, &colorB);
3149 // compute center and radius of the circle
3150 xb = x0 + sb * (x1 - x0);
3151 yb = y0 + sb * (y1 - y0);
3152 rb = r0 + sb * (r1 - r0);
3154 // use the average of the colors at the two circles
3155 for (k = 0; k < nComps; ++k) {
3156 colorA.c[k] = (colorA.c[k] + colorB.c[k]) / 2;
3158 state->setFillColor(&colorA);
3159 if (out->useFillColorStop())
3160 out->updateFillColorStop(state, (sa - sMin)/(sMax - sMin));
3161 else
3162 out->updateFillColor(state);
3164 if (needExtend) {
3165 if (enclosed) {
3166 // construct path for first circle (counterclockwise)
3167 state->moveTo(xa + ra, ya);
3168 for (k = 1; k < n; ++k) {
3169 angle = ((double)k / (double)n) * 2 * M_PI;
3170 state->lineTo(xa + ra * cos(angle), ya + ra * sin(angle));
3172 state->closePath();
3174 // construct and append path for second circle (clockwise)
3175 state->moveTo(xb + rb, yb);
3176 for (k = 1; k < n; ++k) {
3177 angle = -((double)k / (double)n) * 2 * M_PI;
3178 state->lineTo(xb + rb * cos(angle), yb + rb * sin(angle));
3180 state->closePath();
3181 } else {
3182 // construct the first subpath (clockwise)
3183 state->moveTo(xa + ra * cos(alpha + theta + 0.5 * M_PI),
3184 ya + ra * sin(alpha + theta + 0.5 * M_PI));
3185 for (k = 0; k < n; ++k) {
3186 angle = alpha + theta + 0.5 * M_PI
3187 - ((double)k / (double)n) * (2 * theta + M_PI);
3188 state->lineTo(xb + rb * cos(angle), yb + rb * sin(angle));
3190 for (k = 0; k < n; ++k) {
3191 angle = alpha - theta - 0.5 * M_PI
3192 + ((double)k / (double)n) * (2 * theta - M_PI);
3193 state->lineTo(xa + ra * cos(angle), ya + ra * sin(angle));
3195 state->closePath();
3197 // construct the second subpath (counterclockwise)
3198 state->moveTo(xa + ra * cos(alpha + theta + 0.5 * M_PI),
3199 ya + ra * sin(alpha + theta + 0.5 * M_PI));
3200 for (k = 0; k < n; ++k) {
3201 angle = alpha + theta + 0.5 * M_PI
3202 + ((double)k / (double)n) * (-2 * theta + M_PI);
3203 state->lineTo(xb + rb * cos(angle), yb + rb * sin(angle));
3205 for (k = 0; k < n; ++k) {
3206 angle = alpha - theta - 0.5 * M_PI
3207 + ((double)k / (double)n) * (2 * theta + M_PI);
3208 state->lineTo(xa + ra * cos(angle), ya + ra * sin(angle));
3210 state->closePath();
3214 if (!out->useFillColorStop()) {
3215 // fill the path
3216 out->fill(state);
3217 state->clearPath();
3220 // step to the next value of t
3221 ia = ib;
3222 sa = sb;
3223 ta = tb;
3224 xa = xb;
3225 ya = yb;
3226 ra = rb;
3227 colorA = colorB;
3230 if (out->useFillColorStop()) {
3231 // make sure we add stop color when sb = sMax
3232 state->setFillColor(&colorA);
3233 out->updateFillColorStop(state, (sb - sMin)/(sMax - sMin));
3235 // fill the path
3236 state->moveTo(xMin, yMin);
3237 state->lineTo(xMin, yMax);
3238 state->lineTo(xMax, yMax);
3239 state->lineTo(xMax, yMin);
3240 state->closePath();
3242 out->fill(state);
3243 state->clearPath();
3246 if (!needExtend)
3247 return;
3249 if (enclosed) {
3250 // extend the smaller circle
3251 if ((shading->getExtend0() && r0 <= r1) ||
3252 (shading->getExtend1() && r1 < r0)) {
3253 if (r0 <= r1) {
3254 ta = t0;
3255 ra = r0;
3256 xa = x0;
3257 ya = y0;
3258 } else {
3259 ta = t1;
3260 ra = r1;
3261 xa = x1;
3262 ya = y1;
3264 shading->getColor(ta, &colorA);
3265 state->setFillColor(&colorA);
3266 out->updateFillColor(state);
3267 state->moveTo(xa + ra, ya);
3268 for (k = 1; k < n; ++k) {
3269 angle = ((double)k / (double)n) * 2 * M_PI;
3270 state->lineTo(xa + ra * cos(angle), ya + ra * sin(angle));
3272 state->closePath();
3273 out->fill(state);
3274 state->clearPath();
3277 // extend the larger circle
3278 if ((shading->getExtend0() && r0 > r1) ||
3279 (shading->getExtend1() && r1 >= r0)) {
3280 if (r0 > r1) {
3281 ta = t0;
3282 ra = r0;
3283 xa = x0;
3284 ya = y0;
3285 } else {
3286 ta = t1;
3287 ra = r1;
3288 xa = x1;
3289 ya = y1;
3291 shading->getColor(ta, &colorA);
3292 state->setFillColor(&colorA);
3293 out->updateFillColor(state);
3294 state->moveTo(xMin, yMin);
3295 state->lineTo(xMin, yMax);
3296 state->lineTo(xMax, yMax);
3297 state->lineTo(xMax, yMin);
3298 state->closePath();
3299 state->moveTo(xa + ra, ya);
3300 for (k = 1; k < n; ++k) {
3301 angle = ((double)k / (double)n) * 2 * M_PI;
3302 state->lineTo(xa + ra * cos(angle), ya + ra * sin(angle));
3304 state->closePath();
3305 out->fill(state);
3306 state->clearPath();
3311 void Gfx::doGouraudTriangleShFill(GfxGouraudTriangleShading *shading) {
3312 double x0, y0, x1, y1, x2, y2;
3313 int i;
3315 if (out->useShadedFills( shading->getType())) {
3316 if (out->gouraudTriangleShadedFill( state, shading))
3317 return;
3320 // preallocate a path (speed improvements)
3321 state->moveTo(0., 0.);
3322 state->lineTo(1., 0.);
3323 state->lineTo(0., 1.);
3324 state->closePath();
3326 GfxState::ReusablePathIterator *reusablePath = state->getReusablePath();
3328 if (shading->isParameterized()) {
3329 // work with parameterized values:
3330 double color0, color1, color2;
3331 // a relative threshold:
3332 const double refineColorThreshold = gouraudParameterizedColorDelta *
3333 (shading->getParameterDomainMax() - shading->getParameterDomainMin());
3334 for (i = 0; i < shading->getNTriangles(); ++i) {
3335 shading->getTriangle(i, &x0, &y0, &color0,
3336 &x1, &y1, &color1,
3337 &x2, &y2, &color2);
3338 gouraudFillTriangle(x0, y0, color0, x1, y1, color1, x2, y2, color2, refineColorThreshold, 0, shading, reusablePath);
3341 } else {
3342 // this always produces output -- even for parameterized ranges.
3343 // But it ignores the parameterized color map (the function).
3345 // Note that using this code in for parameterized shadings might be
3346 // correct in circumstances (namely if the function is linear in the actual
3347 // triangle), but in general, it will simply be wrong.
3348 GfxColor color0, color1, color2;
3349 for (i = 0; i < shading->getNTriangles(); ++i) {
3350 shading->getTriangle(i, &x0, &y0, &color0,
3351 &x1, &y1, &color1,
3352 &x2, &y2, &color2);
3353 gouraudFillTriangle(x0, y0, &color0, x1, y1, &color1, x2, y2, &color2, shading->getColorSpace()->getNComps(), 0, reusablePath);
3357 delete reusablePath;
3360 static inline void checkTrue(bool b, const char *message) {
3361 if (unlikely(!b)) {
3362 error(errSyntaxError, -1, message);
3366 void Gfx::gouraudFillTriangle(double x0, double y0, GfxColor *color0,
3367 double x1, double y1, GfxColor *color1,
3368 double x2, double y2, GfxColor *color2,
3369 int nComps, int depth, GfxState::ReusablePathIterator *path) {
3370 double x01, y01, x12, y12, x20, y20;
3371 GfxColor color01, color12, color20;
3372 int i;
3374 for (i = 0; i < nComps; ++i) {
3375 if (abs(color0->c[i] - color1->c[i]) > gouraudColorDelta ||
3376 abs(color1->c[i] - color2->c[i]) > gouraudColorDelta) {
3377 break;
3380 if (i == nComps || depth == gouraudMaxDepth) {
3381 state->setFillColor(color0);
3382 out->updateFillColor(state);
3384 path->reset(); checkTrue(!path->isEnd(), "Path should not be at end");
3385 path->setCoord(x0,y0); path->next(); checkTrue(!path->isEnd(), "Path should not be at end");
3386 path->setCoord(x1,y1); path->next(); checkTrue(!path->isEnd(), "Path should not be at end");
3387 path->setCoord(x2,y2); path->next(); checkTrue(!path->isEnd(), "Path should not be at end");
3388 path->setCoord(x0,y0); path->next(); checkTrue( path->isEnd(), "Path should be at end");
3389 out->fill(state);
3391 } else {
3392 x01 = 0.5 * (x0 + x1);
3393 y01 = 0.5 * (y0 + y1);
3394 x12 = 0.5 * (x1 + x2);
3395 y12 = 0.5 * (y1 + y2);
3396 x20 = 0.5 * (x2 + x0);
3397 y20 = 0.5 * (y2 + y0);
3398 for (i = 0; i < nComps; ++i) {
3399 color01.c[i] = (color0->c[i] + color1->c[i]) / 2;
3400 color12.c[i] = (color1->c[i] + color2->c[i]) / 2;
3401 color20.c[i] = (color2->c[i] + color0->c[i]) / 2;
3403 gouraudFillTriangle(x0, y0, color0, x01, y01, &color01,
3404 x20, y20, &color20, nComps, depth + 1, path);
3405 gouraudFillTriangle(x01, y01, &color01, x1, y1, color1,
3406 x12, y12, &color12, nComps, depth + 1, path);
3407 gouraudFillTriangle(x01, y01, &color01, x12, y12, &color12,
3408 x20, y20, &color20, nComps, depth + 1, path);
3409 gouraudFillTriangle(x20, y20, &color20, x12, y12, &color12,
3410 x2, y2, color2, nComps, depth + 1, path);
3413 void Gfx::gouraudFillTriangle(double x0, double y0, double color0,
3414 double x1, double y1, double color1,
3415 double x2, double y2, double color2,
3416 double refineColorThreshold, int depth, GfxGouraudTriangleShading *shading, GfxState::ReusablePathIterator *path) {
3417 const double meanColor = (color0 + color1 + color2) / 3;
3419 const bool isFineEnough =
3420 fabs(color0 - meanColor) < refineColorThreshold &&
3421 fabs(color1 - meanColor) < refineColorThreshold &&
3422 fabs(color2 - meanColor) < refineColorThreshold;
3424 if (isFineEnough || depth == gouraudMaxDepth) {
3425 GfxColor color;
3427 shading->getParameterizedColor(meanColor, &color);
3428 state->setFillColor(&color);
3429 out->updateFillColor(state);
3431 path->reset(); checkTrue(!path->isEnd(), "Path should not be at end");
3432 path->setCoord(x0,y0); path->next(); checkTrue(!path->isEnd(), "Path should not be at end");
3433 path->setCoord(x1,y1); path->next(); checkTrue(!path->isEnd(), "Path should not be at end");
3434 path->setCoord(x2,y2); path->next(); checkTrue(!path->isEnd(), "Path should not be at end");
3435 path->setCoord(x0,y0); path->next(); checkTrue( path->isEnd(), "Path should be at end");
3436 out->fill(state);
3438 } else {
3439 const double x01 = 0.5 * (x0 + x1);
3440 const double y01 = 0.5 * (y0 + y1);
3441 const double x12 = 0.5 * (x1 + x2);
3442 const double y12 = 0.5 * (y1 + y2);
3443 const double x20 = 0.5 * (x2 + x0);
3444 const double y20 = 0.5 * (y2 + y0);
3445 const double color01 = (color0 + color1) / 2.;
3446 const double color12 = (color1 + color2) / 2.;
3447 const double color20 = (color2 + color0) / 2.;
3448 ++depth;
3449 gouraudFillTriangle(x0, y0, color0,
3450 x01, y01, color01,
3451 x20, y20, color20,
3452 refineColorThreshold, depth, shading, path);
3453 gouraudFillTriangle(x01, y01, color01,
3454 x1, y1, color1,
3455 x12, y12, color12,
3456 refineColorThreshold, depth, shading, path);
3457 gouraudFillTriangle(x01, y01, color01,
3458 x12, y12, color12,
3459 x20, y20, color20,
3460 refineColorThreshold, depth, shading, path);
3461 gouraudFillTriangle(x20, y20, color20,
3462 x12, y12, color12,
3463 x2, y2, color2,
3464 refineColorThreshold, depth, shading, path);
3468 void Gfx::doPatchMeshShFill(GfxPatchMeshShading *shading) {
3469 int start, i;
3471 if (out->useShadedFills( shading->getType())) {
3472 if (out->patchMeshShadedFill( state, shading))
3473 return;
3476 if (shading->getNPatches() > 128) {
3477 start = 3;
3478 } else if (shading->getNPatches() > 64) {
3479 start = 2;
3480 } else if (shading->getNPatches() > 16) {
3481 start = 1;
3482 } else {
3483 start = 0;
3486 * Parameterized shadings take one parameter [t_0,t_e]
3487 * and map it into the color space.
3489 * Consequently, all color values are stored as doubles.
3491 * These color values are interpreted as parameters for parameterized
3492 * shadings and as colorspace entities otherwise.
3494 * The only difference is that color space entities are stored into
3495 * DOUBLE arrays, not into arrays of type GfxColorComp.
3497 const int colorComps = shading->getColorSpace()->getNComps();
3498 double refineColorThreshold;
3499 if( shading->isParameterized() ) {
3500 refineColorThreshold = gouraudParameterizedColorDelta *
3501 (shading->getParameterDomainMax() - shading->getParameterDomainMin());
3503 } else {
3504 refineColorThreshold = patchColorDelta;
3507 for (i = 0; i < shading->getNPatches(); ++i) {
3508 fillPatch(shading->getPatch(i),
3509 colorComps,
3510 shading->isParameterized() ? 1 : colorComps,
3511 refineColorThreshold,
3512 start,
3513 shading);
3518 void Gfx::fillPatch(GfxPatch *patch, int colorComps, int patchColorComps, double refineColorThreshold, int depth, GfxPatchMeshShading *shading) {
3519 GfxPatch patch00, patch01, patch10, patch11;
3520 double xx[4][8], yy[4][8];
3521 double xxm, yym;
3522 int i;
3524 for (i = 0; i < patchColorComps; ++i) {
3525 // these comparisons are done in double arithmetics.
3527 // For non-parameterized shadings, they are done in color space
3528 // components.
3529 if (fabs(patch->color[0][0].c[i] - patch->color[0][1].c[i]) > refineColorThreshold ||
3530 fabs(patch->color[0][1].c[i] - patch->color[1][1].c[i]) > refineColorThreshold ||
3531 fabs(patch->color[1][1].c[i] - patch->color[1][0].c[i]) > refineColorThreshold ||
3532 fabs(patch->color[1][0].c[i] - patch->color[0][0].c[i]) > refineColorThreshold) {
3533 break;
3536 if (i == patchColorComps || depth == patchMaxDepth) {
3537 GfxColor flatColor;
3538 if( shading->isParameterized() ) {
3539 shading->getParameterizedColor( patch->color[0][0].c[0], &flatColor );
3540 } else {
3541 for( i = 0; i<colorComps; ++i ) {
3542 // simply cast to the desired type; that's all what is needed.
3543 flatColor.c[i] = GfxColorComp(patch->color[0][0].c[i]);
3546 state->setFillColor(&flatColor);
3547 out->updateFillColor(state);
3548 state->moveTo(patch->x[0][0], patch->y[0][0]);
3549 state->curveTo(patch->x[0][1], patch->y[0][1],
3550 patch->x[0][2], patch->y[0][2],
3551 patch->x[0][3], patch->y[0][3]);
3552 state->curveTo(patch->x[1][3], patch->y[1][3],
3553 patch->x[2][3], patch->y[2][3],
3554 patch->x[3][3], patch->y[3][3]);
3555 state->curveTo(patch->x[3][2], patch->y[3][2],
3556 patch->x[3][1], patch->y[3][1],
3557 patch->x[3][0], patch->y[3][0]);
3558 state->curveTo(patch->x[2][0], patch->y[2][0],
3559 patch->x[1][0], patch->y[1][0],
3560 patch->x[0][0], patch->y[0][0]);
3561 state->closePath();
3562 out->fill(state);
3563 state->clearPath();
3564 } else {
3565 for (i = 0; i < 4; ++i) {
3566 xx[i][0] = patch->x[i][0];
3567 yy[i][0] = patch->y[i][0];
3568 xx[i][1] = 0.5 * (patch->x[i][0] + patch->x[i][1]);
3569 yy[i][1] = 0.5 * (patch->y[i][0] + patch->y[i][1]);
3570 xxm = 0.5 * (patch->x[i][1] + patch->x[i][2]);
3571 yym = 0.5 * (patch->y[i][1] + patch->y[i][2]);
3572 xx[i][6] = 0.5 * (patch->x[i][2] + patch->x[i][3]);
3573 yy[i][6] = 0.5 * (patch->y[i][2] + patch->y[i][3]);
3574 xx[i][2] = 0.5 * (xx[i][1] + xxm);
3575 yy[i][2] = 0.5 * (yy[i][1] + yym);
3576 xx[i][5] = 0.5 * (xxm + xx[i][6]);
3577 yy[i][5] = 0.5 * (yym + yy[i][6]);
3578 xx[i][3] = xx[i][4] = 0.5 * (xx[i][2] + xx[i][5]);
3579 yy[i][3] = yy[i][4] = 0.5 * (yy[i][2] + yy[i][5]);
3580 xx[i][7] = patch->x[i][3];
3581 yy[i][7] = patch->y[i][3];
3583 for (i = 0; i < 4; ++i) {
3584 patch00.x[0][i] = xx[0][i];
3585 patch00.y[0][i] = yy[0][i];
3586 patch00.x[1][i] = 0.5 * (xx[0][i] + xx[1][i]);
3587 patch00.y[1][i] = 0.5 * (yy[0][i] + yy[1][i]);
3588 xxm = 0.5 * (xx[1][i] + xx[2][i]);
3589 yym = 0.5 * (yy[1][i] + yy[2][i]);
3590 patch10.x[2][i] = 0.5 * (xx[2][i] + xx[3][i]);
3591 patch10.y[2][i] = 0.5 * (yy[2][i] + yy[3][i]);
3592 patch00.x[2][i] = 0.5 * (patch00.x[1][i] + xxm);
3593 patch00.y[2][i] = 0.5 * (patch00.y[1][i] + yym);
3594 patch10.x[1][i] = 0.5 * (xxm + patch10.x[2][i]);
3595 patch10.y[1][i] = 0.5 * (yym + patch10.y[2][i]);
3596 patch00.x[3][i] = 0.5 * (patch00.x[2][i] + patch10.x[1][i]);
3597 patch00.y[3][i] = 0.5 * (patch00.y[2][i] + patch10.y[1][i]);
3598 patch10.x[0][i] = patch00.x[3][i];
3599 patch10.y[0][i] = patch00.y[3][i];
3600 patch10.x[3][i] = xx[3][i];
3601 patch10.y[3][i] = yy[3][i];
3603 for (i = 4; i < 8; ++i) {
3604 patch01.x[0][i-4] = xx[0][i];
3605 patch01.y[0][i-4] = yy[0][i];
3606 patch01.x[1][i-4] = 0.5 * (xx[0][i] + xx[1][i]);
3607 patch01.y[1][i-4] = 0.5 * (yy[0][i] + yy[1][i]);
3608 xxm = 0.5 * (xx[1][i] + xx[2][i]);
3609 yym = 0.5 * (yy[1][i] + yy[2][i]);
3610 patch11.x[2][i-4] = 0.5 * (xx[2][i] + xx[3][i]);
3611 patch11.y[2][i-4] = 0.5 * (yy[2][i] + yy[3][i]);
3612 patch01.x[2][i-4] = 0.5 * (patch01.x[1][i-4] + xxm);
3613 patch01.y[2][i-4] = 0.5 * (patch01.y[1][i-4] + yym);
3614 patch11.x[1][i-4] = 0.5 * (xxm + patch11.x[2][i-4]);
3615 patch11.y[1][i-4] = 0.5 * (yym + patch11.y[2][i-4]);
3616 patch01.x[3][i-4] = 0.5 * (patch01.x[2][i-4] + patch11.x[1][i-4]);
3617 patch01.y[3][i-4] = 0.5 * (patch01.y[2][i-4] + patch11.y[1][i-4]);
3618 patch11.x[0][i-4] = patch01.x[3][i-4];
3619 patch11.y[0][i-4] = patch01.y[3][i-4];
3620 patch11.x[3][i-4] = xx[3][i];
3621 patch11.y[3][i-4] = yy[3][i];
3623 for (i = 0; i < patchColorComps; ++i) {
3624 patch00.color[0][0].c[i] = patch->color[0][0].c[i];
3625 patch00.color[0][1].c[i] = (patch->color[0][0].c[i] +
3626 patch->color[0][1].c[i]) / 2;
3627 patch01.color[0][0].c[i] = patch00.color[0][1].c[i];
3628 patch01.color[0][1].c[i] = patch->color[0][1].c[i];
3629 patch01.color[1][1].c[i] = (patch->color[0][1].c[i] +
3630 patch->color[1][1].c[i]) / 2;
3631 patch11.color[0][1].c[i] = patch01.color[1][1].c[i];
3632 patch11.color[1][1].c[i] = patch->color[1][1].c[i];
3633 patch11.color[1][0].c[i] = (patch->color[1][1].c[i] +
3634 patch->color[1][0].c[i]) / 2;
3635 patch10.color[1][1].c[i] = patch11.color[1][0].c[i];
3636 patch10.color[1][0].c[i] = patch->color[1][0].c[i];
3637 patch10.color[0][0].c[i] = (patch->color[1][0].c[i] +
3638 patch->color[0][0].c[i]) / 2;
3639 patch00.color[1][0].c[i] = patch10.color[0][0].c[i];
3640 patch00.color[1][1].c[i] = (patch00.color[1][0].c[i] +
3641 patch01.color[1][1].c[i]) / 2;
3642 patch01.color[1][0].c[i] = patch00.color[1][1].c[i];
3643 patch11.color[0][0].c[i] = patch00.color[1][1].c[i];
3644 patch10.color[0][1].c[i] = patch00.color[1][1].c[i];
3646 fillPatch(&patch00, colorComps, patchColorComps, refineColorThreshold, depth + 1, shading);
3647 fillPatch(&patch10, colorComps, patchColorComps, refineColorThreshold, depth + 1, shading);
3648 fillPatch(&patch01, colorComps, patchColorComps, refineColorThreshold, depth + 1, shading);
3649 fillPatch(&patch11, colorComps, patchColorComps, refineColorThreshold, depth + 1, shading);
3653 void Gfx::doEndPath() {
3654 if (state->isCurPt() && clip != clipNone) {
3655 state->clip();
3656 if (clip == clipNormal) {
3657 out->clip(state);
3658 } else {
3659 out->eoClip(state);
3662 clip = clipNone;
3663 state->clearPath();
3666 //------------------------------------------------------------------------
3667 // path clipping operators
3668 //------------------------------------------------------------------------
3670 void Gfx::opClip(Object args[], int numArgs) {
3671 clip = clipNormal;
3674 void Gfx::opEOClip(Object args[], int numArgs) {
3675 clip = clipEO;
3678 //------------------------------------------------------------------------
3679 // text object operators
3680 //------------------------------------------------------------------------
3682 void Gfx::opBeginText(Object args[], int numArgs) {
3683 out->beginTextObject(state);
3684 state->setTextMat(1, 0, 0, 1, 0, 0);
3685 state->textMoveTo(0, 0);
3686 out->updateTextMat(state);
3687 out->updateTextPos(state);
3688 fontChanged = gTrue;
3691 void Gfx::opEndText(Object args[], int numArgs) {
3692 out->endTextObject(state);
3695 //------------------------------------------------------------------------
3696 // text state operators
3697 //------------------------------------------------------------------------
3699 void Gfx::opSetCharSpacing(Object args[], int numArgs) {
3700 state->setCharSpace(args[0].getNum());
3701 out->updateCharSpace(state);
3704 void Gfx::opSetFont(Object args[], int numArgs) {
3705 GfxFont *font;
3707 if (!(font = res->lookupFont(args[0].getName()))) {
3708 // unsetting the font (drawing no text) is better than using the
3709 // previous one and drawing random glyphs from it
3710 state->setFont(NULL, args[1].getNum());
3711 fontChanged = gTrue;
3712 return;
3714 if (printCommands) {
3715 printf(" font: tag=%s name='%s' %g\n",
3716 font->getTag()->getCString(),
3717 font->getName() ? font->getName()->getCString() : "???",
3718 args[1].getNum());
3719 fflush(stdout);
3722 font->incRefCnt();
3723 state->setFont(font, args[1].getNum());
3724 fontChanged = gTrue;
3727 void Gfx::opSetTextLeading(Object args[], int numArgs) {
3728 state->setLeading(args[0].getNum());
3731 void Gfx::opSetTextRender(Object args[], int numArgs) {
3732 state->setRender(args[0].getInt());
3733 out->updateRender(state);
3736 void Gfx::opSetTextRise(Object args[], int numArgs) {
3737 state->setRise(args[0].getNum());
3738 out->updateRise(state);
3741 void Gfx::opSetWordSpacing(Object args[], int numArgs) {
3742 state->setWordSpace(args[0].getNum());
3743 out->updateWordSpace(state);
3746 void Gfx::opSetHorizScaling(Object args[], int numArgs) {
3747 state->setHorizScaling(args[0].getNum());
3748 out->updateHorizScaling(state);
3749 fontChanged = gTrue;
3752 //------------------------------------------------------------------------
3753 // text positioning operators
3754 //------------------------------------------------------------------------
3756 void Gfx::opTextMove(Object args[], int numArgs) {
3757 double tx, ty;
3759 tx = state->getLineX() + args[0].getNum();
3760 ty = state->getLineY() + args[1].getNum();
3761 state->textMoveTo(tx, ty);
3762 out->updateTextPos(state);
3765 void Gfx::opTextMoveSet(Object args[], int numArgs) {
3766 double tx, ty;
3768 tx = state->getLineX() + args[0].getNum();
3769 ty = args[1].getNum();
3770 state->setLeading(-ty);
3771 ty += state->getLineY();
3772 state->textMoveTo(tx, ty);
3773 out->updateTextPos(state);
3776 void Gfx::opSetTextMatrix(Object args[], int numArgs) {
3777 state->setTextMat(args[0].getNum(), args[1].getNum(),
3778 args[2].getNum(), args[3].getNum(),
3779 args[4].getNum(), args[5].getNum());
3780 state->textMoveTo(0, 0);
3781 out->updateTextMat(state);
3782 out->updateTextPos(state);
3783 fontChanged = gTrue;
3786 void Gfx::opTextNextLine(Object args[], int numArgs) {
3787 double tx, ty;
3789 tx = state->getLineX();
3790 ty = state->getLineY() - state->getLeading();
3791 state->textMoveTo(tx, ty);
3792 out->updateTextPos(state);
3795 //------------------------------------------------------------------------
3796 // text string operators
3797 //------------------------------------------------------------------------
3799 void Gfx::opShowText(Object args[], int numArgs) {
3800 if (!state->getFont()) {
3801 error(errSyntaxError, getPos(), "No font in show");
3802 return;
3804 if (fontChanged) {
3805 out->updateFont(state);
3806 fontChanged = gFalse;
3808 out->beginStringOp(state);
3809 doShowText(args[0].getString());
3810 out->endStringOp(state);
3811 if (!ocState) {
3812 doIncCharCount(args[0].getString());
3816 void Gfx::opMoveShowText(Object args[], int numArgs) {
3817 double tx, ty;
3819 if (!state->getFont()) {
3820 error(errSyntaxError, getPos(), "No font in move/show");
3821 return;
3823 if (fontChanged) {
3824 out->updateFont(state);
3825 fontChanged = gFalse;
3827 tx = state->getLineX();
3828 ty = state->getLineY() - state->getLeading();
3829 state->textMoveTo(tx, ty);
3830 out->updateTextPos(state);
3831 out->beginStringOp(state);
3832 doShowText(args[0].getString());
3833 out->endStringOp(state);
3834 if (!ocState) {
3835 doIncCharCount(args[0].getString());
3839 void Gfx::opMoveSetShowText(Object args[], int numArgs) {
3840 double tx, ty;
3842 if (!state->getFont()) {
3843 error(errSyntaxError, getPos(), "No font in move/set/show");
3844 return;
3846 if (fontChanged) {
3847 out->updateFont(state);
3848 fontChanged = gFalse;
3850 state->setWordSpace(args[0].getNum());
3851 state->setCharSpace(args[1].getNum());
3852 tx = state->getLineX();
3853 ty = state->getLineY() - state->getLeading();
3854 state->textMoveTo(tx, ty);
3855 out->updateWordSpace(state);
3856 out->updateCharSpace(state);
3857 out->updateTextPos(state);
3858 out->beginStringOp(state);
3859 doShowText(args[2].getString());
3860 out->endStringOp(state);
3861 if (ocState) {
3862 doIncCharCount(args[2].getString());
3866 void Gfx::opShowSpaceText(Object args[], int numArgs) {
3867 Array *a;
3868 Object obj;
3869 int wMode;
3870 int i;
3872 if (!state->getFont()) {
3873 error(errSyntaxError, getPos(), "No font in show/space");
3874 return;
3876 if (fontChanged) {
3877 out->updateFont(state);
3878 fontChanged = gFalse;
3880 out->beginStringOp(state);
3881 wMode = state->getFont()->getWMode();
3882 a = args[0].getArray();
3883 for (i = 0; i < a->getLength(); ++i) {
3884 a->get(i, &obj);
3885 if (obj.isNum()) {
3886 // this uses the absolute value of the font size to match
3887 // Acrobat's behavior
3888 if (wMode) {
3889 state->textShift(0, -obj.getNum() * 0.001 *
3890 state->getFontSize());
3891 } else {
3892 state->textShift(-obj.getNum() * 0.001 *
3893 state->getFontSize() *
3894 state->getHorizScaling(), 0);
3896 out->updateTextShift(state, obj.getNum());
3897 } else if (obj.isString()) {
3898 doShowText(obj.getString());
3899 } else {
3900 error(errSyntaxError, getPos(),
3901 "Element of show/space array must be number or string");
3903 obj.free();
3905 out->endStringOp(state);
3906 if (!ocState) {
3907 a = args[0].getArray();
3908 for (i = 0; i < a->getLength(); ++i) {
3909 a->get(i, &obj);
3910 if (obj.isString()) {
3911 doIncCharCount(obj.getString());
3913 obj.free();
3918 void Gfx::doShowText(GooString *s) {
3919 GfxFont *font;
3920 int wMode;
3921 double riseX, riseY;
3922 CharCode code;
3923 Unicode *u = NULL;
3924 double x, y, dx, dy, dx2, dy2, curX, curY, tdx, tdy, ddx, ddy;
3925 double originX, originY, tOriginX, tOriginY;
3926 double x0, y0, x1, y1;
3927 double oldCTM[6], newCTM[6];
3928 double *mat;
3929 Object charProc;
3930 Dict *resDict;
3931 Parser *oldParser;
3932 GfxState *savedState;
3933 char *p;
3934 int render;
3935 GBool patternFill;
3936 int len, n, uLen, nChars, nSpaces, i;
3938 font = state->getFont();
3939 wMode = font->getWMode();
3941 if (out->useDrawChar()) {
3942 out->beginString(state, s);
3945 // if we're doing a pattern fill, set up clipping
3946 render = state->getRender();
3947 if (!(render & 1) &&
3948 state->getFillColorSpace()->getMode() == csPattern) {
3949 patternFill = gTrue;
3950 saveState();
3951 // disable fill, enable clipping, leave stroke unchanged
3952 if ((render ^ (render >> 1)) & 1) {
3953 render = 5;
3954 } else {
3955 render = 7;
3957 state->setRender(render);
3958 out->updateRender(state);
3959 } else {
3960 patternFill = gFalse;
3963 state->textTransformDelta(0, state->getRise(), &riseX, &riseY);
3964 x0 = state->getCurX() + riseX;
3965 y0 = state->getCurY() + riseY;
3967 // handle a Type 3 char
3968 if (font->getType() == fontType3 && out->interpretType3Chars()) {
3969 mat = state->getCTM();
3970 for (i = 0; i < 6; ++i) {
3971 oldCTM[i] = mat[i];
3973 mat = state->getTextMat();
3974 newCTM[0] = mat[0] * oldCTM[0] + mat[1] * oldCTM[2];
3975 newCTM[1] = mat[0] * oldCTM[1] + mat[1] * oldCTM[3];
3976 newCTM[2] = mat[2] * oldCTM[0] + mat[3] * oldCTM[2];
3977 newCTM[3] = mat[2] * oldCTM[1] + mat[3] * oldCTM[3];
3978 mat = font->getFontMatrix();
3979 newCTM[0] = mat[0] * newCTM[0] + mat[1] * newCTM[2];
3980 newCTM[1] = mat[0] * newCTM[1] + mat[1] * newCTM[3];
3981 newCTM[2] = mat[2] * newCTM[0] + mat[3] * newCTM[2];
3982 newCTM[3] = mat[2] * newCTM[1] + mat[3] * newCTM[3];
3983 newCTM[0] *= state->getFontSize();
3984 newCTM[1] *= state->getFontSize();
3985 newCTM[2] *= state->getFontSize();
3986 newCTM[3] *= state->getFontSize();
3987 newCTM[0] *= state->getHorizScaling();
3988 newCTM[2] *= state->getHorizScaling();
3989 curX = state->getCurX();
3990 curY = state->getCurY();
3991 oldParser = parser;
3992 p = s->getCString();
3993 len = s->getLength();
3994 while (len > 0) {
3995 n = font->getNextChar(p, len, &code,
3996 &u, &uLen,
3997 &dx, &dy, &originX, &originY);
3998 dx = dx * state->getFontSize() + state->getCharSpace();
3999 if (n == 1 && *p == ' ') {
4000 dx += state->getWordSpace();
4002 dx *= state->getHorizScaling();
4003 dy *= state->getFontSize();
4004 state->textTransformDelta(dx, dy, &tdx, &tdy);
4005 state->transform(curX + riseX, curY + riseY, &x, &y);
4006 savedState = saveStateStack();
4007 state->setCTM(newCTM[0], newCTM[1], newCTM[2], newCTM[3], x, y);
4008 //~ the CTM concat values here are wrong (but never used)
4009 out->updateCTM(state, 1, 0, 0, 1, 0, 0);
4010 state->transformDelta(dx, dy, &ddx, &ddy);
4011 if (!out->beginType3Char(state, curX + riseX, curY + riseY, ddx, ddy,
4012 code, u, uLen)) {
4013 ((Gfx8BitFont *)font)->getCharProc(code, &charProc);
4014 if ((resDict = ((Gfx8BitFont *)font)->getResources())) {
4015 pushResources(resDict);
4017 if (charProc.isStream()) {
4018 display(&charProc, gFalse);
4019 } else {
4020 error(errSyntaxError, getPos(), "Missing or bad Type3 CharProc entry");
4022 out->endType3Char(state);
4023 if (resDict) {
4024 popResources();
4026 charProc.free();
4028 restoreStateStack(savedState);
4029 // GfxState::restore() does *not* restore the current position,
4030 // so we deal with it here using (curX, curY) and (lineX, lineY)
4031 curX += tdx;
4032 curY += tdy;
4033 state->moveTo(curX, curY);
4034 p += n;
4035 len -= n;
4037 parser = oldParser;
4039 } else if (out->useDrawChar()) {
4040 p = s->getCString();
4041 len = s->getLength();
4042 while (len > 0) {
4043 n = font->getNextChar(p, len, &code,
4044 &u, &uLen,
4045 &dx, &dy, &originX, &originY);
4046 if (wMode) {
4047 dx *= state->getFontSize();
4048 dy = dy * state->getFontSize() + state->getCharSpace();
4049 if (n == 1 && *p == ' ') {
4050 dy += state->getWordSpace();
4052 } else {
4053 dx = dx * state->getFontSize() + state->getCharSpace();
4054 if (n == 1 && *p == ' ') {
4055 dx += state->getWordSpace();
4057 dx *= state->getHorizScaling();
4058 dy *= state->getFontSize();
4060 state->textTransformDelta(dx, dy, &tdx, &tdy);
4061 originX *= state->getFontSize();
4062 originY *= state->getFontSize();
4063 state->textTransformDelta(originX, originY, &tOriginX, &tOriginY);
4064 if (ocState)
4065 out->drawChar(state, state->getCurX() + riseX, state->getCurY() + riseY,
4066 tdx, tdy, tOriginX, tOriginY, code, n, u, uLen);
4067 state->shift(tdx, tdy);
4068 p += n;
4069 len -= n;
4071 } else {
4072 dx = dy = 0;
4073 p = s->getCString();
4074 len = s->getLength();
4075 nChars = nSpaces = 0;
4076 while (len > 0) {
4077 n = font->getNextChar(p, len, &code,
4078 &u, &uLen,
4079 &dx2, &dy2, &originX, &originY);
4080 dx += dx2;
4081 dy += dy2;
4082 if (n == 1 && *p == ' ') {
4083 ++nSpaces;
4085 ++nChars;
4086 p += n;
4087 len -= n;
4089 if (wMode) {
4090 dx *= state->getFontSize();
4091 dy = dy * state->getFontSize()
4092 + nChars * state->getCharSpace()
4093 + nSpaces * state->getWordSpace();
4094 } else {
4095 dx = dx * state->getFontSize()
4096 + nChars * state->getCharSpace()
4097 + nSpaces * state->getWordSpace();
4098 dx *= state->getHorizScaling();
4099 dy *= state->getFontSize();
4101 state->textTransformDelta(dx, dy, &tdx, &tdy);
4102 if (ocState)
4103 out->drawString(state, s);
4104 state->shift(tdx, tdy);
4107 if (out->useDrawChar()) {
4108 out->endString(state);
4111 if (patternFill && ocState) {
4112 out->saveTextPos(state);
4113 // tell the OutputDev to do the clipping
4114 out->endTextObject(state);
4115 // set up a clipping bbox so doPatternText will work -- assume
4116 // that the text bounding box does not extend past the baseline in
4117 // any direction by more than twice the font size
4118 x1 = state->getCurX() + riseX;
4119 y1 = state->getCurY() + riseY;
4120 if (x0 > x1) {
4121 x = x0; x0 = x1; x1 = x;
4123 if (y0 > y1) {
4124 y = y0; y0 = y1; y1 = y;
4126 state->textTransformDelta(0, state->getFontSize(), &dx, &dy);
4127 state->textTransformDelta(state->getFontSize(), 0, &dx2, &dy2);
4128 dx = fabs(dx);
4129 dx2 = fabs(dx2);
4130 if (dx2 > dx) {
4131 dx = dx2;
4133 dy = fabs(dy);
4134 dy2 = fabs(dy2);
4135 if (dy2 > dy) {
4136 dy = dy2;
4138 state->clipToRect(x0 - 2 * dx, y0 - 2 * dy, x1 + 2 * dx, y1 + 2 * dy);
4139 // set render mode to fill-only
4140 state->setRender(0);
4141 out->updateRender(state);
4142 doPatternText();
4143 restoreState();
4144 out->restoreTextPos(state);
4147 updateLevel += 10 * s->getLength();
4150 // NB: this is only called when ocState is false.
4151 void Gfx::doIncCharCount(GooString *s) {
4152 if (out->needCharCount()) {
4153 out->incCharCount(s->getLength());
4157 //------------------------------------------------------------------------
4158 // XObject operators
4159 //------------------------------------------------------------------------
4161 void Gfx::opXObject(Object args[], int numArgs) {
4162 char *name;
4163 Object obj1, obj2, obj3, refObj;
4164 #if OPI_SUPPORT
4165 Object opiDict;
4166 #endif
4168 if (!ocState && !out->needCharCount()) {
4169 return;
4171 name = args[0].getName();
4172 if (!res->lookupXObject(name, &obj1)) {
4173 return;
4175 if (!obj1.isStream()) {
4176 error(errSyntaxError, getPos(), "XObject '{0:s}' is wrong type", name);
4177 obj1.free();
4178 return;
4181 #if OPI_SUPPORT
4182 obj1.streamGetDict()->lookup("OPI", &opiDict);
4183 if (opiDict.isDict()) {
4184 out->opiBegin(state, opiDict.getDict());
4186 #endif
4187 obj1.streamGetDict()->lookup("Subtype", &obj2);
4188 if (obj2.isName("Image")) {
4189 if (out->needNonText()) {
4190 res->lookupXObjectNF(name, &refObj);
4191 doImage(&refObj, obj1.getStream(), gFalse);
4192 refObj.free();
4194 } else if (obj2.isName("Form")) {
4195 res->lookupXObjectNF(name, &refObj);
4196 GBool shouldDoForm = gTrue;
4197 std::set<int>::iterator drawingFormIt;
4198 if (refObj.isRef()) {
4199 const int num = refObj.getRef().num;
4200 if (formsDrawing.find(num) == formsDrawing.end()) {
4201 drawingFormIt = formsDrawing.insert(num).first;
4202 } else {
4203 shouldDoForm = gFalse;
4206 if (shouldDoForm) {
4207 if (out->useDrawForm() && refObj.isRef()) {
4208 out->drawForm(refObj.getRef());
4209 } else {
4210 doForm(&obj1);
4213 if (refObj.isRef() && shouldDoForm) {
4214 formsDrawing.erase(drawingFormIt);
4216 refObj.free();
4217 } else if (obj2.isName("PS")) {
4218 obj1.streamGetDict()->lookup("Level1", &obj3);
4219 out->psXObject(obj1.getStream(),
4220 obj3.isStream() ? obj3.getStream() : (Stream *)NULL);
4221 } else if (obj2.isName()) {
4222 error(errSyntaxError, getPos(), "Unknown XObject subtype '{0:s}'", obj2.getName());
4223 } else {
4224 error(errSyntaxError, getPos(), "XObject subtype is missing or wrong type");
4226 obj2.free();
4227 #if OPI_SUPPORT
4228 if (opiDict.isDict()) {
4229 out->opiEnd(state, opiDict.getDict());
4231 opiDict.free();
4232 #endif
4233 obj1.free();
4236 void Gfx::doImage(Object *ref, Stream *str, GBool inlineImg) {
4237 Dict *dict, *maskDict;
4238 int width, height;
4239 int bits, maskBits;
4240 GBool interpolate;
4241 StreamColorSpaceMode csMode;
4242 GBool mask;
4243 GBool invert;
4244 GfxColorSpace *colorSpace, *maskColorSpace;
4245 GfxImageColorMap *colorMap, *maskColorMap;
4246 Object maskObj, smaskObj;
4247 GBool haveColorKeyMask, haveExplicitMask, haveSoftMask;
4248 int maskColors[2*gfxColorMaxComps];
4249 int maskWidth, maskHeight;
4250 GBool maskInvert;
4251 GBool maskInterpolate;
4252 Stream *maskStr;
4253 Object obj1, obj2;
4254 int i, n;
4256 // get info from the stream
4257 bits = 0;
4258 csMode = streamCSNone;
4259 str->getImageParams(&bits, &csMode);
4261 // get stream dict
4262 dict = str->getDict();
4264 // check for optional content key
4265 if (ref) {
4266 dict->lookupNF("OC", &obj1);
4267 if (catalog->getOptContentConfig() && !catalog->getOptContentConfig()->optContentIsVisible(&obj1)) {
4268 obj1.free();
4269 return;
4271 obj1.free();
4274 // get size
4275 dict->lookup("Width", &obj1);
4276 if (obj1.isNull()) {
4277 obj1.free();
4278 dict->lookup("W", &obj1);
4280 if (obj1.isInt())
4281 width = obj1.getInt();
4282 else if (obj1.isReal())
4283 width = (int)obj1.getReal();
4284 else
4285 goto err2;
4286 obj1.free();
4287 dict->lookup("Height", &obj1);
4288 if (obj1.isNull()) {
4289 obj1.free();
4290 dict->lookup("H", &obj1);
4292 if (obj1.isInt())
4293 height = obj1.getInt();
4294 else if (obj1.isReal())
4295 height = (int)obj1.getReal();
4296 else
4297 goto err2;
4298 obj1.free();
4300 if (width < 1 || height < 1)
4301 goto err1;
4303 // image interpolation
4304 dict->lookup("Interpolate", &obj1);
4305 if (obj1.isNull()) {
4306 obj1.free();
4307 dict->lookup("I", &obj1);
4309 if (obj1.isBool())
4310 interpolate = obj1.getBool();
4311 else
4312 interpolate = gFalse;
4313 obj1.free();
4314 maskInterpolate = gFalse;
4316 // image or mask?
4317 dict->lookup("ImageMask", &obj1);
4318 if (obj1.isNull()) {
4319 obj1.free();
4320 dict->lookup("IM", &obj1);
4322 mask = gFalse;
4323 if (obj1.isBool())
4324 mask = obj1.getBool();
4325 else if (!obj1.isNull())
4326 goto err2;
4327 obj1.free();
4329 // bit depth
4330 if (bits == 0) {
4331 dict->lookup("BitsPerComponent", &obj1);
4332 if (obj1.isNull()) {
4333 obj1.free();
4334 dict->lookup("BPC", &obj1);
4336 if (obj1.isInt()) {
4337 bits = obj1.getInt();
4338 } else if (mask) {
4339 bits = 1;
4340 } else {
4341 goto err2;
4343 obj1.free();
4346 // display a mask
4347 if (mask) {
4349 // check for inverted mask
4350 if (bits != 1)
4351 goto err1;
4352 invert = gFalse;
4353 dict->lookup("Decode", &obj1);
4354 if (obj1.isNull()) {
4355 obj1.free();
4356 dict->lookup("D", &obj1);
4358 if (obj1.isArray()) {
4359 obj1.arrayGet(0, &obj2);
4360 // Table 4.39 says /Decode must be [1 0] or [0 1]. Adobe
4361 // accepts [1.0 0.0] as well.
4362 if (obj2.isNum() && obj2.getNum() >= 0.9)
4363 invert = gTrue;
4364 obj2.free();
4365 } else if (!obj1.isNull()) {
4366 goto err2;
4368 obj1.free();
4370 // if drawing is disabled, skip over inline image data
4371 if (!ocState || !out->needNonText()) {
4372 str->reset();
4373 n = height * ((width + 7) / 8);
4374 for (i = 0; i < n; ++i) {
4375 str->getChar();
4377 str->close();
4379 // draw it
4380 } else {
4381 if (state->getFillColorSpace()->getMode() == csPattern) {
4382 doPatternImageMask(ref, str, width, height, invert, inlineImg);
4383 } else {
4384 out->drawImageMask(state, ref, str, width, height, invert, interpolate, inlineImg);
4387 } else {
4389 // get color space and color map
4390 dict->lookup("ColorSpace", &obj1);
4391 if (obj1.isNull()) {
4392 obj1.free();
4393 dict->lookup("CS", &obj1);
4395 if (obj1.isName() && inlineImg) {
4396 res->lookupColorSpace(obj1.getName(), &obj2);
4397 if (!obj2.isNull()) {
4398 obj1.free();
4399 obj1 = obj2;
4400 } else {
4401 obj2.free();
4404 if (!obj1.isNull()) {
4405 Object objIntent;
4406 char *tempIntent = NULL;
4407 dict->lookup("Intent", &objIntent);
4408 if (objIntent.isName()) {
4409 tempIntent = state->getRenderingIntent();
4410 if (tempIntent != NULL) {
4411 tempIntent = strdup(tempIntent);
4413 state->setRenderingIntent(objIntent.getName());
4415 colorSpace = GfxColorSpace::parse(res, &obj1, out, state);
4416 if (objIntent.isName()) {
4417 state->setRenderingIntent(tempIntent);
4418 free(tempIntent);
4420 objIntent.free();
4421 } else if (csMode == streamCSDeviceGray) {
4422 Object objCS;
4423 res->lookupColorSpace("DefaultGray", &objCS);
4424 if (objCS.isNull()) {
4425 colorSpace = new GfxDeviceGrayColorSpace();
4426 } else {
4427 colorSpace = GfxColorSpace::parse(res, &objCS, out, state);
4429 objCS.free();
4430 } else if (csMode == streamCSDeviceRGB) {
4431 Object objCS;
4432 res->lookupColorSpace("DefaultRGB", &objCS);
4433 if (objCS.isNull()) {
4434 colorSpace = new GfxDeviceRGBColorSpace();
4435 } else {
4436 colorSpace = GfxColorSpace::parse(res, &objCS, out, state);
4438 objCS.free();
4439 } else if (csMode == streamCSDeviceCMYK) {
4440 Object objCS;
4441 res->lookupColorSpace("DefaultCMYK", &objCS);
4442 if (objCS.isNull()) {
4443 colorSpace = new GfxDeviceCMYKColorSpace();
4444 } else {
4445 colorSpace = GfxColorSpace::parse(res, &objCS, out, state);
4447 objCS.free();
4448 } else {
4449 colorSpace = NULL;
4451 obj1.free();
4452 if (!colorSpace) {
4453 goto err1;
4455 dict->lookup("Decode", &obj1);
4456 if (obj1.isNull()) {
4457 obj1.free();
4458 dict->lookup("D", &obj1);
4460 if (bits == 0) {
4461 delete colorSpace;
4462 goto err2;
4464 colorMap = new GfxImageColorMap(bits, &obj1, colorSpace);
4465 obj1.free();
4466 if (!colorMap->isOk()) {
4467 delete colorMap;
4468 goto err1;
4471 // get the mask
4472 haveColorKeyMask = haveExplicitMask = haveSoftMask = gFalse;
4473 maskStr = NULL; // make gcc happy
4474 maskWidth = maskHeight = 0; // make gcc happy
4475 maskInvert = gFalse; // make gcc happy
4476 maskColorMap = NULL; // make gcc happy
4477 dict->lookup("Mask", &maskObj);
4478 dict->lookup("SMask", &smaskObj);
4479 if (smaskObj.isStream()) {
4480 // soft mask
4481 if (inlineImg) {
4482 goto err1;
4484 maskStr = smaskObj.getStream();
4485 maskDict = smaskObj.streamGetDict();
4486 maskDict->lookup("Width", &obj1);
4487 if (obj1.isNull()) {
4488 obj1.free();
4489 maskDict->lookup("W", &obj1);
4491 if (!obj1.isInt()) {
4492 goto err2;
4494 maskWidth = obj1.getInt();
4495 obj1.free();
4496 maskDict->lookup("Height", &obj1);
4497 if (obj1.isNull()) {
4498 obj1.free();
4499 maskDict->lookup("H", &obj1);
4501 if (!obj1.isInt()) {
4502 goto err2;
4504 maskHeight = obj1.getInt();
4505 obj1.free();
4506 maskDict->lookup("Interpolate", &obj1);
4507 if (obj1.isNull()) {
4508 obj1.free();
4509 maskDict->lookup("I", &obj1);
4511 if (obj1.isBool())
4512 maskInterpolate = obj1.getBool();
4513 else
4514 maskInterpolate = gFalse;
4515 obj1.free();
4516 maskDict->lookup("BitsPerComponent", &obj1);
4517 if (obj1.isNull()) {
4518 obj1.free();
4519 maskDict->lookup("BPC", &obj1);
4521 if (!obj1.isInt()) {
4522 goto err2;
4524 maskBits = obj1.getInt();
4525 obj1.free();
4526 maskDict->lookup("ColorSpace", &obj1);
4527 if (obj1.isNull()) {
4528 obj1.free();
4529 maskDict->lookup("CS", &obj1);
4531 if (obj1.isName()) {
4532 res->lookupColorSpace(obj1.getName(), &obj2);
4533 if (!obj2.isNull()) {
4534 obj1.free();
4535 obj1 = obj2;
4536 } else {
4537 obj2.free();
4540 maskColorSpace = GfxColorSpace::parse(NULL, &obj1, out, state);
4541 obj1.free();
4542 if (!maskColorSpace || maskColorSpace->getMode() != csDeviceGray) {
4543 goto err1;
4545 maskDict->lookup("Decode", &obj1);
4546 if (obj1.isNull()) {
4547 obj1.free();
4548 maskDict->lookup("D", &obj1);
4550 maskColorMap = new GfxImageColorMap(maskBits, &obj1, maskColorSpace);
4551 obj1.free();
4552 if (!maskColorMap->isOk()) {
4553 delete maskColorMap;
4554 goto err1;
4556 //~ handle the Matte entry
4557 haveSoftMask = gTrue;
4558 } else if (maskObj.isArray()) {
4559 // color key mask
4560 for (i = 0;
4561 i < maskObj.arrayGetLength() && i < 2*gfxColorMaxComps;
4562 ++i) {
4563 maskObj.arrayGet(i, &obj1);
4564 if (obj1.isInt()) {
4565 maskColors[i] = obj1.getInt();
4566 } else if (obj1.isReal()) {
4567 error(errSyntaxError, -1, "Mask entry should be an integer but it's a real, trying to use it");
4568 maskColors[i] = (int) obj1.getReal();
4569 } else {
4570 error(errSyntaxError, -1, "Mask entry should be an integer but it's of type {0:d}", obj1.getType());
4571 obj1.free();
4572 goto err1;
4574 obj1.free();
4576 haveColorKeyMask = gTrue;
4577 } else if (maskObj.isStream()) {
4578 // explicit mask
4579 if (inlineImg) {
4580 goto err1;
4582 maskStr = maskObj.getStream();
4583 maskDict = maskObj.streamGetDict();
4584 maskDict->lookup("Width", &obj1);
4585 if (obj1.isNull()) {
4586 obj1.free();
4587 maskDict->lookup("W", &obj1);
4589 if (!obj1.isInt()) {
4590 goto err2;
4592 maskWidth = obj1.getInt();
4593 obj1.free();
4594 maskDict->lookup("Height", &obj1);
4595 if (obj1.isNull()) {
4596 obj1.free();
4597 maskDict->lookup("H", &obj1);
4599 if (!obj1.isInt()) {
4600 goto err2;
4602 maskHeight = obj1.getInt();
4603 obj1.free();
4604 maskDict->lookup("Interpolate", &obj1);
4605 if (obj1.isNull()) {
4606 obj1.free();
4607 maskDict->lookup("I", &obj1);
4609 if (obj1.isBool())
4610 maskInterpolate = obj1.getBool();
4611 else
4612 maskInterpolate = gFalse;
4613 obj1.free();
4614 maskDict->lookup("ImageMask", &obj1);
4615 if (obj1.isNull()) {
4616 obj1.free();
4617 maskDict->lookup("IM", &obj1);
4619 if (!obj1.isBool() || !obj1.getBool()) {
4620 goto err2;
4622 obj1.free();
4623 maskInvert = gFalse;
4624 maskDict->lookup("Decode", &obj1);
4625 if (obj1.isNull()) {
4626 obj1.free();
4627 maskDict->lookup("D", &obj1);
4629 if (obj1.isArray()) {
4630 obj1.arrayGet(0, &obj2);
4631 // Table 4.39 says /Decode must be [1 0] or [0 1]. Adobe
4632 // accepts [1.0 0.0] as well.
4633 if (obj2.isNum() && obj2.getNum() >= 0.9) {
4634 maskInvert = gTrue;
4636 obj2.free();
4637 } else if (!obj1.isNull()) {
4638 goto err2;
4640 obj1.free();
4641 haveExplicitMask = gTrue;
4644 // if drawing is disabled, skip over inline image data
4645 if (!ocState || !out->needNonText()) {
4646 str->reset();
4647 n = height * ((width * colorMap->getNumPixelComps() *
4648 colorMap->getBits() + 7) / 8);
4649 for (i = 0; i < n; ++i) {
4650 str->getChar();
4652 str->close();
4654 // draw it
4655 } else {
4656 if (haveSoftMask) {
4657 out->drawSoftMaskedImage(state, ref, str, width, height, colorMap, interpolate,
4658 maskStr, maskWidth, maskHeight, maskColorMap, maskInterpolate);
4659 delete maskColorMap;
4660 } else if (haveExplicitMask) {
4661 out->drawMaskedImage(state, ref, str, width, height, colorMap, interpolate,
4662 maskStr, maskWidth, maskHeight, maskInvert, maskInterpolate);
4663 } else {
4664 out->drawImage(state, ref, str, width, height, colorMap, interpolate,
4665 haveColorKeyMask ? maskColors : (int *)NULL, inlineImg);
4668 delete colorMap;
4670 maskObj.free();
4671 smaskObj.free();
4674 if ((i = width * height) > 1000) {
4675 i = 1000;
4677 updateLevel += i;
4679 return;
4681 err2:
4682 obj1.free();
4683 err1:
4684 error(errSyntaxError, getPos(), "Bad image parameters");
4687 GBool Gfx::checkTransparencyGroup(Dict *resDict) {
4688 // check the effect of compositing objects as a group:
4689 // look for ExtGState entries with ca != 1 or CA != 1 or BM != normal
4690 Object extGStates;
4691 GBool transpGroup = gFalse;
4692 double opac;
4694 if (resDict == NULL)
4695 return gFalse;
4696 pushResources(resDict);
4697 resDict->lookup("ExtGState", &extGStates);
4698 if (extGStates.isDict()) {
4699 Dict *dict = extGStates.getDict();
4700 for (int i = 0; i < dict->getLength() && !transpGroup; i++) {
4701 Object obj1, obj2;
4702 GfxBlendMode mode;
4704 if (res->lookupGState(dict->getKey(i), &obj1) && obj1.isDict()) {
4705 if (!obj1.dictLookup("BM", &obj2)->isNull()) {
4706 if (state->parseBlendMode(&obj2, &mode)) {
4707 if (mode != gfxBlendNormal)
4708 transpGroup = gTrue;
4709 } else {
4710 error(errSyntaxError, getPos(), "Invalid blend mode in ExtGState");
4713 obj2.free();
4714 if (obj1.dictLookup("ca", &obj2)->isNum()) {
4715 opac = obj2.getNum();
4716 opac = opac < 0 ? 0 : opac > 1 ? 1 : opac;
4717 if (opac != 1)
4718 transpGroup = gTrue;
4720 obj2.free();
4721 if (obj1.dictLookup("CA", &obj2)->isNum()) {
4722 opac = obj2.getNum();
4723 opac = opac < 0 ? 0 : opac > 1 ? 1 : opac;
4724 if (opac != 1)
4725 transpGroup = gTrue;
4727 obj2.free();
4728 // alpha is shape
4729 if (!transpGroup && obj1.dictLookup("AIS", &obj2)->isBool()) {
4730 transpGroup = obj2.getBool();
4732 obj2.free();
4733 // soft mask
4734 if (!transpGroup && !obj1.dictLookup("SMask", &obj2)->isNull()) {
4735 if (!obj2.isName("None")) {
4736 transpGroup = gTrue;
4739 obj2.free();
4741 obj1.free();
4744 extGStates.free();
4745 popResources();
4746 return transpGroup;
4749 void Gfx::doForm(Object *str) {
4750 Dict *dict;
4751 GBool transpGroup, isolated, knockout;
4752 GfxColorSpace *blendingColorSpace;
4753 Object matrixObj, bboxObj;
4754 double m[6], bbox[4];
4755 Object resObj;
4756 Dict *resDict;
4757 GBool ocSaved;
4758 Object obj1, obj2, obj3;
4759 int i;
4761 // check for excessive recursion
4762 if (formDepth > 100) {
4763 return;
4766 // get stream dict
4767 dict = str->streamGetDict();
4769 // check form type
4770 dict->lookup("FormType", &obj1);
4771 if (!(obj1.isNull() || (obj1.isInt() && obj1.getInt() == 1))) {
4772 error(errSyntaxError, getPos(), "Unknown form type");
4774 obj1.free();
4776 // check for optional content key
4777 ocSaved = ocState;
4778 dict->lookupNF("OC", &obj1);
4779 if (catalog->getOptContentConfig() && !catalog->getOptContentConfig()->optContentIsVisible(&obj1)) {
4780 obj1.free();
4781 if (out->needCharCount()) {
4782 ocState = gFalse;
4783 } else {
4784 return;
4787 obj1.free();
4789 // get bounding box
4790 dict->lookup("BBox", &bboxObj);
4791 if (!bboxObj.isArray()) {
4792 bboxObj.free();
4793 error(errSyntaxError, getPos(), "Bad form bounding box");
4794 ocState = ocSaved;
4795 return;
4797 for (i = 0; i < 4; ++i) {
4798 bboxObj.arrayGet(i, &obj1);
4799 if (likely(obj1.isNum())) {
4800 bbox[i] = obj1.getNum();
4801 obj1.free();
4802 } else {
4803 obj1.free();
4804 error(errSyntaxError, getPos(), "Bad form bounding box value");
4805 return;
4808 bboxObj.free();
4810 // get matrix
4811 dict->lookup("Matrix", &matrixObj);
4812 if (matrixObj.isArray()) {
4813 for (i = 0; i < 6; ++i) {
4814 matrixObj.arrayGet(i, &obj1);
4815 if (likely(obj1.isNum())) m[i] = obj1.getNum();
4816 else m[i] = 0;
4817 obj1.free();
4819 } else {
4820 m[0] = 1; m[1] = 0;
4821 m[2] = 0; m[3] = 1;
4822 m[4] = 0; m[5] = 0;
4824 matrixObj.free();
4826 // get resources
4827 dict->lookup("Resources", &resObj);
4828 resDict = resObj.isDict() ? resObj.getDict() : (Dict *)NULL;
4830 // check for a transparency group
4831 transpGroup = isolated = knockout = gFalse;
4832 blendingColorSpace = NULL;
4833 if (dict->lookup("Group", &obj1)->isDict()) {
4834 if (obj1.dictLookup("S", &obj2)->isName("Transparency")) {
4835 if (!obj1.dictLookup("CS", &obj3)->isNull()) {
4836 blendingColorSpace = GfxColorSpace::parse(res, &obj3, out, state);
4838 obj3.free();
4839 if (obj1.dictLookup("I", &obj3)->isBool()) {
4840 isolated = obj3.getBool();
4842 obj3.free();
4843 if (obj1.dictLookup("K", &obj3)->isBool()) {
4844 knockout = obj3.getBool();
4846 obj3.free();
4847 transpGroup = isolated || out->checkTransparencyGroup(state, knockout) || checkTransparencyGroup(resDict);
4849 obj2.free();
4851 obj1.free();
4853 // draw it
4854 ++formDepth;
4855 drawForm(str, resDict, m, bbox,
4856 transpGroup, gFalse, blendingColorSpace, isolated, knockout);
4857 --formDepth;
4859 if (blendingColorSpace) {
4860 delete blendingColorSpace;
4862 resObj.free();
4864 ocState = ocSaved;
4867 void Gfx::drawForm(Object *str, Dict *resDict, double *matrix, double *bbox,
4868 GBool transpGroup, GBool softMask,
4869 GfxColorSpace *blendingColorSpace,
4870 GBool isolated, GBool knockout,
4871 GBool alpha, Function *transferFunc,
4872 GfxColor *backdropColor) {
4873 Parser *oldParser;
4874 GfxState *savedState;
4875 double oldBaseMatrix[6];
4876 int i;
4878 // push new resources on stack
4879 pushResources(resDict);
4881 // save current graphics state
4882 savedState = saveStateStack();
4884 // kill any pre-existing path
4885 state->clearPath();
4887 // save current parser
4888 oldParser = parser;
4890 // set form transformation matrix
4891 state->concatCTM(matrix[0], matrix[1], matrix[2],
4892 matrix[3], matrix[4], matrix[5]);
4893 out->updateCTM(state, matrix[0], matrix[1], matrix[2],
4894 matrix[3], matrix[4], matrix[5]);
4896 // set form bounding box
4897 state->moveTo(bbox[0], bbox[1]);
4898 state->lineTo(bbox[2], bbox[1]);
4899 state->lineTo(bbox[2], bbox[3]);
4900 state->lineTo(bbox[0], bbox[3]);
4901 state->closePath();
4902 state->clip();
4903 out->clip(state);
4904 state->clearPath();
4906 if (softMask || transpGroup) {
4907 if (state->getBlendMode() != gfxBlendNormal) {
4908 state->setBlendMode(gfxBlendNormal);
4909 out->updateBlendMode(state);
4911 if (state->getFillOpacity() != 1) {
4912 state->setFillOpacity(1);
4913 out->updateFillOpacity(state);
4915 if (state->getStrokeOpacity() != 1) {
4916 state->setStrokeOpacity(1);
4917 out->updateStrokeOpacity(state);
4919 out->clearSoftMask(state);
4920 out->beginTransparencyGroup(state, bbox, blendingColorSpace,
4921 isolated, knockout, softMask);
4924 // set new base matrix
4925 for (i = 0; i < 6; ++i) {
4926 oldBaseMatrix[i] = baseMatrix[i];
4927 baseMatrix[i] = state->getCTM()[i];
4930 GfxState *stateBefore = state;
4932 // draw the form
4933 display(str, gFalse);
4935 if (stateBefore != state) {
4936 if (state->isParentState(stateBefore)) {
4937 error(errSyntaxError, -1, "There's a form with more q than Q, trying to fix");
4938 while (stateBefore != state) {
4939 restoreState();
4941 } else {
4942 error(errSyntaxError, -1, "There's a form with more Q than q");
4946 if (softMask || transpGroup) {
4947 out->endTransparencyGroup(state);
4950 // restore base matrix
4951 for (i = 0; i < 6; ++i) {
4952 baseMatrix[i] = oldBaseMatrix[i];
4955 // restore parser
4956 parser = oldParser;
4958 // restore graphics state
4959 restoreStateStack(savedState);
4961 // pop resource stack
4962 popResources();
4964 if (softMask) {
4965 out->setSoftMask(state, bbox, alpha, transferFunc, backdropColor);
4966 } else if (transpGroup) {
4967 out->paintTransparencyGroup(state, bbox);
4970 return;
4973 //------------------------------------------------------------------------
4974 // in-line image operators
4975 //------------------------------------------------------------------------
4977 void Gfx::opBeginImage(Object args[], int numArgs) {
4978 Stream *str;
4979 int c1, c2;
4981 // NB: this function is run even if ocState is false -- doImage() is
4982 // responsible for skipping over the inline image data
4984 // build dict/stream
4985 str = buildImageStream();
4987 // display the image
4988 if (str) {
4989 doImage(NULL, str, gTrue);
4991 // skip 'EI' tag
4992 c1 = str->getUndecodedStream()->getChar();
4993 c2 = str->getUndecodedStream()->getChar();
4994 while (!(c1 == 'E' && c2 == 'I') && c2 != EOF) {
4995 c1 = c2;
4996 c2 = str->getUndecodedStream()->getChar();
4998 delete str;
5002 Stream *Gfx::buildImageStream() {
5003 Object dict;
5004 Object obj;
5005 char *key;
5006 Stream *str;
5008 // build dictionary
5009 dict.initDict(xref);
5010 parser->getObj(&obj);
5011 while (!obj.isCmd("ID") && !obj.isEOF()) {
5012 if (!obj.isName()) {
5013 error(errSyntaxError, getPos(), "Inline image dictionary key must be a name object");
5014 obj.free();
5015 } else {
5016 key = copyString(obj.getName());
5017 obj.free();
5018 parser->getObj(&obj);
5019 if (obj.isEOF() || obj.isError()) {
5020 gfree(key);
5021 break;
5023 dict.dictAdd(key, &obj);
5025 parser->getObj(&obj);
5027 if (obj.isEOF()) {
5028 error(errSyntaxError, getPos(), "End of file in inline image");
5029 obj.free();
5030 dict.free();
5031 return NULL;
5033 obj.free();
5035 // make stream
5036 if (parser->getStream()) {
5037 str = new EmbedStream(parser->getStream(), &dict, gFalse, 0);
5038 str = str->addFilters(&dict);
5039 } else {
5040 str = NULL;
5041 dict.free();
5044 return str;
5047 void Gfx::opImageData(Object args[], int numArgs) {
5048 error(errInternal, getPos(), "Got 'ID' operator");
5051 void Gfx::opEndImage(Object args[], int numArgs) {
5052 error(errInternal, getPos(), "Got 'EI' operator");
5055 //------------------------------------------------------------------------
5056 // type 3 font operators
5057 //------------------------------------------------------------------------
5059 void Gfx::opSetCharWidth(Object args[], int numArgs) {
5060 out->type3D0(state, args[0].getNum(), args[1].getNum());
5063 void Gfx::opSetCacheDevice(Object args[], int numArgs) {
5064 out->type3D1(state, args[0].getNum(), args[1].getNum(),
5065 args[2].getNum(), args[3].getNum(),
5066 args[4].getNum(), args[5].getNum());
5069 //------------------------------------------------------------------------
5070 // compatibility operators
5071 //------------------------------------------------------------------------
5073 void Gfx::opBeginIgnoreUndef(Object args[], int numArgs) {
5074 ++ignoreUndef;
5077 void Gfx::opEndIgnoreUndef(Object args[], int numArgs) {
5078 if (ignoreUndef > 0)
5079 --ignoreUndef;
5082 //------------------------------------------------------------------------
5083 // marked content operators
5084 //------------------------------------------------------------------------
5086 enum GfxMarkedContentKind {
5087 gfxMCOptionalContent,
5088 gfxMCActualText,
5089 gfxMCOther
5092 struct MarkedContentStack {
5093 GfxMarkedContentKind kind;
5094 GBool ocSuppressed; // are we ignoring content based on OptionalContent?
5095 MarkedContentStack *next; // next object on stack
5098 void Gfx::popMarkedContent() {
5099 MarkedContentStack *mc = mcStack;
5100 mcStack = mc->next;
5101 delete mc;
5104 void Gfx::pushMarkedContent() {
5105 MarkedContentStack *mc = new MarkedContentStack();
5106 mc->ocSuppressed = gFalse;
5107 mc->kind = gfxMCOther;
5108 mc->next = mcStack;
5109 mcStack = mc;
5112 GBool Gfx::contentIsHidden() {
5113 MarkedContentStack *mc = mcStack;
5114 bool hidden = mc && mc->ocSuppressed;
5115 while (!hidden && mc && mc->next) {
5116 mc = mc->next;
5117 hidden = mc->ocSuppressed;
5119 return hidden;
5122 void Gfx::opBeginMarkedContent(Object args[], int numArgs) {
5123 // push a new stack entry
5124 pushMarkedContent();
5126 OCGs *contentConfig = catalog->getOptContentConfig();
5127 char* name0 = args[0].getName();
5128 if ( strncmp( name0, "OC", 2) == 0 && contentConfig) {
5129 if ( numArgs >= 2 ) {
5130 if (!args[1].isName()) {
5131 error(errSyntaxError, getPos(), "Unexpected MC Type: {0:d}", args[1].getType());
5133 char* name1 = args[1].getName();
5134 Object markedContent;
5135 MarkedContentStack *mc = mcStack;
5136 mc->kind = gfxMCOptionalContent;
5137 if ( res->lookupMarkedContentNF( name1, &markedContent ) ) {
5138 bool visible = contentConfig->optContentIsVisible(&markedContent);
5139 mc->ocSuppressed = !(visible);
5140 } else {
5141 error(errSyntaxError, getPos(), "DID NOT find {0:s}", name1);
5143 markedContent.free();
5144 } else {
5145 error(errSyntaxError, getPos(), "insufficient arguments for Marked Content");
5147 } else if (args[0].isName("Span") && numArgs == 2 && args[1].isDict()) {
5148 Object obj;
5149 if (args[1].dictLookup("ActualText", &obj)->isString()) {
5150 out->beginActualText(state, obj.getString());
5151 MarkedContentStack *mc = mcStack;
5152 mc->kind = gfxMCActualText;
5154 obj.free();
5157 if (printCommands) {
5158 printf(" marked content: %s ", args[0].getName());
5159 if (numArgs == 2)
5160 args[1].print(stdout);
5161 printf("\n");
5162 fflush(stdout);
5164 ocState = !contentIsHidden();
5166 if (numArgs == 2 && args[1].isDict()) {
5167 out->beginMarkedContent(args[0].getName(), args[1].getDict());
5168 } else if(numArgs == 1) {
5169 out->beginMarkedContent(args[0].getName(), NULL);
5173 void Gfx::opEndMarkedContent(Object args[], int numArgs) {
5174 if (!mcStack) {
5175 error(errSyntaxWarning, getPos(), "Mismatched EMC operator");
5176 return;
5179 MarkedContentStack *mc = mcStack;
5180 GfxMarkedContentKind mcKind = mc->kind;
5182 // pop the stack
5183 popMarkedContent();
5185 if (mcKind == gfxMCActualText)
5186 out->endActualText(state);
5187 ocState = !contentIsHidden();
5189 out->endMarkedContent(state);
5192 void Gfx::opMarkPoint(Object args[], int numArgs) {
5193 if (printCommands) {
5194 printf(" mark point: %s ", args[0].getName());
5195 if (numArgs == 2)
5196 args[1].print(stdout);
5197 printf("\n");
5198 fflush(stdout);
5201 if(numArgs == 2 && args[1].isDict()) {
5202 out->markPoint(args[0].getName(),args[1].getDict());
5203 } else {
5204 out->markPoint(args[0].getName());
5209 //------------------------------------------------------------------------
5210 // misc
5211 //------------------------------------------------------------------------
5213 struct GfxStackStateSaver {
5214 GfxStackStateSaver(Gfx *gfx) : gfx(gfx) {
5215 gfx->saveState();
5218 ~GfxStackStateSaver() {
5219 gfx->restoreState();
5222 Gfx * const gfx;
5225 void Gfx::drawAnnot(Object *str, AnnotBorder *border, AnnotColor *aColor,
5226 double xMin, double yMin, double xMax, double yMax, int rotate) {
5227 Dict *dict, *resDict;
5228 Object matrixObj, bboxObj, resObj, obj1;
5229 double formXMin, formYMin, formXMax, formYMax;
5230 double x, y, sx, sy, tx, ty;
5231 double m[6], bbox[4];
5232 double r, g, b;
5233 GfxColor color;
5234 double *dash, *dash2;
5235 int dashLength;
5236 int i;
5238 // this function assumes that we are in the default user space,
5239 // i.e., baseMatrix = ctm
5241 // if the bounding box has zero width or height, don't draw anything
5242 // at all
5243 if (xMin == xMax || yMin == yMax) {
5244 return;
5247 // saves gfx state and automatically restores it on return
5248 GfxStackStateSaver stackStateSaver(this);
5250 // Rotation around the topleft corner (for the NoRotate flag)
5251 if (rotate != 0) {
5252 const double angle_rad = rotate * M_PI / 180;
5253 const double c = cos(angle_rad);
5254 const double s = sin(angle_rad);
5256 // (xMin, yMax) is the pivot
5257 const double unrotateMTX[6] = {
5258 +c, -s,
5259 +s, +c,
5260 -c*xMin - s*yMax + xMin, -c*yMax + s*xMin + yMax
5263 state->concatCTM(unrotateMTX[0], unrotateMTX[1], unrotateMTX[2],
5264 unrotateMTX[3], unrotateMTX[4], unrotateMTX[5]);
5265 out->updateCTM(state, unrotateMTX[0], unrotateMTX[1], unrotateMTX[2],
5266 unrotateMTX[3], unrotateMTX[4], unrotateMTX[5]);
5269 // draw the appearance stream (if there is one)
5270 if (str->isStream()) {
5272 // get stream dict
5273 dict = str->streamGetDict();
5275 // get the form bounding box
5276 dict->lookup("BBox", &bboxObj);
5277 if (!bboxObj.isArray()) {
5278 bboxObj.free();
5279 error(errSyntaxError, getPos(), "Bad form bounding box");
5280 return;
5282 for (i = 0; i < 4; ++i) {
5283 bboxObj.arrayGet(i, &obj1);
5284 if (likely(obj1.isNum())) {
5285 bbox[i] = obj1.getNum();
5286 obj1.free();
5287 } else {
5288 obj1.free();
5289 bboxObj.free();
5290 error(errSyntaxError, getPos(), "Bad form bounding box value");
5291 return;
5294 bboxObj.free();
5296 // get the form matrix
5297 dict->lookup("Matrix", &matrixObj);
5298 if (matrixObj.isArray() && matrixObj.arrayGetLength() >= 6) {
5299 for (i = 0; i < 6; ++i) {
5300 matrixObj.arrayGet(i, &obj1);
5301 if (likely(obj1.isNum())) {
5302 m[i] = obj1.getNum();
5303 obj1.free();
5304 } else {
5305 obj1.free();
5306 matrixObj.free();
5307 error(errSyntaxError, getPos(), "Bad form matrix");
5308 return;
5311 } else {
5312 m[0] = 1; m[1] = 0;
5313 m[2] = 0; m[3] = 1;
5314 m[4] = 0; m[5] = 0;
5316 matrixObj.free();
5318 // transform the four corners of the form bbox to default user
5319 // space, and construct the transformed bbox
5320 x = bbox[0] * m[0] + bbox[1] * m[2] + m[4];
5321 y = bbox[0] * m[1] + bbox[1] * m[3] + m[5];
5322 formXMin = formXMax = x;
5323 formYMin = formYMax = y;
5324 x = bbox[0] * m[0] + bbox[3] * m[2] + m[4];
5325 y = bbox[0] * m[1] + bbox[3] * m[3] + m[5];
5326 if (x < formXMin) {
5327 formXMin = x;
5328 } else if (x > formXMax) {
5329 formXMax = x;
5331 if (y < formYMin) {
5332 formYMin = y;
5333 } else if (y > formYMax) {
5334 formYMax = y;
5336 x = bbox[2] * m[0] + bbox[1] * m[2] + m[4];
5337 y = bbox[2] * m[1] + bbox[1] * m[3] + m[5];
5338 if (x < formXMin) {
5339 formXMin = x;
5340 } else if (x > formXMax) {
5341 formXMax = x;
5343 if (y < formYMin) {
5344 formYMin = y;
5345 } else if (y > formYMax) {
5346 formYMax = y;
5348 x = bbox[2] * m[0] + bbox[3] * m[2] + m[4];
5349 y = bbox[2] * m[1] + bbox[3] * m[3] + m[5];
5350 if (x < formXMin) {
5351 formXMin = x;
5352 } else if (x > formXMax) {
5353 formXMax = x;
5355 if (y < formYMin) {
5356 formYMin = y;
5357 } else if (y > formYMax) {
5358 formYMax = y;
5361 // construct a mapping matrix, [sx 0 0], which maps the transformed
5362 // [0 sy 0]
5363 // [tx ty 1]
5364 // bbox to the annotation rectangle
5365 if (formXMin == formXMax) {
5366 // this shouldn't happen
5367 sx = 1;
5368 } else {
5369 sx = (xMax - xMin) / (formXMax - formXMin);
5371 if (formYMin == formYMax) {
5372 // this shouldn't happen
5373 sy = 1;
5374 } else {
5375 sy = (yMax - yMin) / (formYMax - formYMin);
5377 tx = -formXMin * sx + xMin;
5378 ty = -formYMin * sy + yMin;
5380 // the final transform matrix is (form matrix) * (mapping matrix)
5381 m[0] *= sx;
5382 m[1] *= sy;
5383 m[2] *= sx;
5384 m[3] *= sy;
5385 m[4] = m[4] * sx + tx;
5386 m[5] = m[5] * sy + ty;
5388 // get the resources
5389 dict->lookup("Resources", &resObj);
5390 resDict = resObj.isDict() ? resObj.getDict() : (Dict *)NULL;
5392 // draw it
5393 drawForm(str, resDict, m, bbox);
5395 resObj.free();
5398 // draw the border
5399 if (border && border->getWidth() > 0) {
5400 if (state->getStrokeColorSpace()->getMode() != csDeviceRGB) {
5401 state->setStrokePattern(NULL);
5402 state->setStrokeColorSpace(new GfxDeviceRGBColorSpace());
5403 out->updateStrokeColorSpace(state);
5405 if (aColor && (aColor->getSpace() == AnnotColor::colorRGB)) {
5406 const double *values = aColor->getValues();
5407 r = values[0];
5408 g = values[1];
5409 b = values[2];
5410 } else {
5411 r = g = b = 0;
5413 color.c[0] = dblToCol(r);
5414 color.c[1] = dblToCol(g);
5415 color.c[2] = dblToCol(b);
5416 state->setStrokeColor(&color);
5417 out->updateStrokeColor(state);
5418 state->setLineWidth(border->getWidth());
5419 out->updateLineWidth(state);
5420 dashLength = border->getDashLength();
5421 dash = border->getDash();
5422 if (border->getStyle() == AnnotBorder::borderDashed && dashLength > 0) {
5423 dash2 = (double *)gmallocn(dashLength, sizeof(double));
5424 memcpy(dash2, dash, dashLength * sizeof(double));
5425 state->setLineDash(dash2, dashLength, 0);
5426 out->updateLineDash(state);
5428 //~ this doesn't currently handle the beveled and engraved styles
5429 state->clearPath();
5430 state->moveTo(xMin, yMin);
5431 state->lineTo(xMax, yMin);
5432 if (border->getStyle() != AnnotBorder::borderUnderlined) {
5433 state->lineTo(xMax, yMax);
5434 state->lineTo(xMin, yMax);
5435 state->closePath();
5437 out->stroke(state);
5441 int Gfx::bottomGuard() {
5442 return stateGuards[stateGuards.size()-1];
5445 void Gfx::pushStateGuard() {
5446 stateGuards.push_back(stackHeight);
5449 void Gfx::popStateGuard() {
5450 while (stackHeight > bottomGuard() && state->hasSaves())
5451 restoreState();
5452 stateGuards.pop_back();
5455 void Gfx::saveState() {
5456 out->saveState(state);
5457 state = state->save();
5458 stackHeight++;
5461 void Gfx::restoreState() {
5462 if (stackHeight <= bottomGuard() || !state->hasSaves()) {
5463 error(errSyntaxError, -1, "Restoring state when no valid states to pop");
5464 commandAborted = gTrue;
5465 return;
5467 state = state->restore();
5468 out->restoreState(state);
5469 stackHeight--;
5472 // Create a new state stack, and initialize it with a copy of the
5473 // current state.
5474 GfxState *Gfx::saveStateStack() {
5475 GfxState *oldState;
5477 out->saveState(state);
5478 oldState = state;
5479 state = state->copy(gTrue);
5480 return oldState;
5483 // Switch back to the previous state stack.
5484 void Gfx::restoreStateStack(GfxState *oldState) {
5485 while (state->hasSaves()) {
5486 restoreState();
5488 delete state;
5489 state = oldState;
5490 out->restoreState(state);
5493 void Gfx::pushResources(Dict *resDict) {
5494 res = new GfxResources(xref, resDict, res);
5497 void Gfx::popResources() {
5498 GfxResources *resPtr;
5500 resPtr = res->getNext();
5501 delete res;
5502 res = resPtr;