a patch for ttc fonts with bad index
[luatex.git] / source / libs / poppler / poppler-0.36.0 / poppler / Gfx.cc
blob07d95b355a894caa514bc04f21f797f2f3438b9f
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-2014 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 for (yi = yi0; yi < yi1; ++yi) {
2287 for (xi = xi0; xi < xi1; ++xi) {
2288 x = xi * xstep;
2289 y = yi * ystep;
2290 m1[4] = x * m[0] + y * m[2] + m[4];
2291 m1[5] = x * m[1] + y * m[3] + m[5];
2292 drawForm(tPat->getContentStream(), tPat->getResDict(),
2293 m1, tPat->getBBox());
2298 // restore graphics state
2299 restore:
2300 restoreStateStack(savedState);
2303 void Gfx::doShadingPatternFill(GfxShadingPattern *sPat,
2304 GBool stroke, GBool eoFill, GBool text) {
2305 GfxShading *shading;
2306 GfxState *savedState;
2307 double *ctm, *btm, *ptm;
2308 double m[6], ictm[6], m1[6];
2309 double xMin, yMin, xMax, yMax;
2310 double det;
2312 shading = sPat->getShading();
2314 // save current graphics state
2315 savedState = saveStateStack();
2317 // clip to current path
2318 if (stroke) {
2319 state->clipToStrokePath();
2320 out->clipToStrokePath(state);
2321 } else if (!text) {
2322 state->clip();
2323 if (eoFill) {
2324 out->eoClip(state);
2325 } else {
2326 out->clip(state);
2329 state->clearPath();
2331 // construct a (pattern space) -> (current space) transform matrix
2332 ctm = state->getCTM();
2333 btm = baseMatrix;
2334 ptm = sPat->getMatrix();
2335 // iCTM = invert CTM
2336 det = 1 / (ctm[0] * ctm[3] - ctm[1] * ctm[2]);
2337 ictm[0] = ctm[3] * det;
2338 ictm[1] = -ctm[1] * det;
2339 ictm[2] = -ctm[2] * det;
2340 ictm[3] = ctm[0] * det;
2341 ictm[4] = (ctm[2] * ctm[5] - ctm[3] * ctm[4]) * det;
2342 ictm[5] = (ctm[1] * ctm[4] - ctm[0] * ctm[5]) * det;
2343 // m1 = PTM * BTM = PTM * base transform matrix
2344 m1[0] = ptm[0] * btm[0] + ptm[1] * btm[2];
2345 m1[1] = ptm[0] * btm[1] + ptm[1] * btm[3];
2346 m1[2] = ptm[2] * btm[0] + ptm[3] * btm[2];
2347 m1[3] = ptm[2] * btm[1] + ptm[3] * btm[3];
2348 m1[4] = ptm[4] * btm[0] + ptm[5] * btm[2] + btm[4];
2349 m1[5] = ptm[4] * btm[1] + ptm[5] * btm[3] + btm[5];
2350 // m = m1 * iCTM = (PTM * BTM) * (iCTM)
2351 m[0] = m1[0] * ictm[0] + m1[1] * ictm[2];
2352 m[1] = m1[0] * ictm[1] + m1[1] * ictm[3];
2353 m[2] = m1[2] * ictm[0] + m1[3] * ictm[2];
2354 m[3] = m1[2] * ictm[1] + m1[3] * ictm[3];
2355 m[4] = m1[4] * ictm[0] + m1[5] * ictm[2] + ictm[4];
2356 m[5] = m1[4] * ictm[1] + m1[5] * ictm[3] + ictm[5];
2358 // set the new matrix
2359 state->concatCTM(m[0], m[1], m[2], m[3], m[4], m[5]);
2360 out->updateCTM(state, m[0], m[1], m[2], m[3], m[4], m[5]);
2362 // clip to bbox
2363 if (shading->getHasBBox()) {
2364 shading->getBBox(&xMin, &yMin, &xMax, &yMax);
2365 state->moveTo(xMin, yMin);
2366 state->lineTo(xMax, yMin);
2367 state->lineTo(xMax, yMax);
2368 state->lineTo(xMin, yMax);
2369 state->closePath();
2370 state->clip();
2371 out->clip(state);
2372 state->clearPath();
2375 // set the color space
2376 state->setFillColorSpace(shading->getColorSpace()->copy());
2377 out->updateFillColorSpace(state);
2379 // background color fill
2380 if (shading->getHasBackground()) {
2381 state->setFillColor(shading->getBackground());
2382 out->updateFillColor(state);
2383 state->getUserClipBBox(&xMin, &yMin, &xMax, &yMax);
2384 state->moveTo(xMin, yMin);
2385 state->lineTo(xMax, yMin);
2386 state->lineTo(xMax, yMax);
2387 state->lineTo(xMin, yMax);
2388 state->closePath();
2389 out->fill(state);
2390 state->clearPath();
2393 #if 1 //~tmp: turn off anti-aliasing temporarily
2394 GBool vaa = out->getVectorAntialias();
2395 if (vaa) {
2396 out->setVectorAntialias(gFalse);
2398 #endif
2400 // do shading type-specific operations
2401 switch (shading->getType()) {
2402 case 1:
2403 doFunctionShFill((GfxFunctionShading *)shading);
2404 break;
2405 case 2:
2406 doAxialShFill((GfxAxialShading *)shading);
2407 break;
2408 case 3:
2409 doRadialShFill((GfxRadialShading *)shading);
2410 break;
2411 case 4:
2412 case 5:
2413 doGouraudTriangleShFill((GfxGouraudTriangleShading *)shading);
2414 break;
2415 case 6:
2416 case 7:
2417 doPatchMeshShFill((GfxPatchMeshShading *)shading);
2418 break;
2421 #if 1 //~tmp: turn off anti-aliasing temporarily
2422 if (vaa) {
2423 out->setVectorAntialias(gTrue);
2425 #endif
2427 // restore graphics state
2428 restoreStateStack(savedState);
2431 void Gfx::opShFill(Object args[], int numArgs) {
2432 GfxShading *shading;
2433 GfxState *savedState;
2434 double xMin, yMin, xMax, yMax;
2436 if (!ocState) {
2437 return;
2440 if (!(shading = res->lookupShading(args[0].getName(), out, state))) {
2441 return;
2444 // save current graphics state
2445 savedState = saveStateStack();
2447 // clip to bbox
2448 if (shading->getHasBBox()) {
2449 shading->getBBox(&xMin, &yMin, &xMax, &yMax);
2450 state->moveTo(xMin, yMin);
2451 state->lineTo(xMax, yMin);
2452 state->lineTo(xMax, yMax);
2453 state->lineTo(xMin, yMax);
2454 state->closePath();
2455 state->clip();
2456 out->clip(state);
2457 state->clearPath();
2460 // set the color space
2461 state->setFillColorSpace(shading->getColorSpace()->copy());
2462 out->updateFillColorSpace(state);
2464 #if 1 //~tmp: turn off anti-aliasing temporarily
2465 GBool vaa = out->getVectorAntialias();
2466 if (vaa) {
2467 out->setVectorAntialias(gFalse);
2469 #endif
2471 // do shading type-specific operations
2472 switch (shading->getType()) {
2473 case 1:
2474 doFunctionShFill((GfxFunctionShading *)shading);
2475 break;
2476 case 2:
2477 doAxialShFill((GfxAxialShading *)shading);
2478 break;
2479 case 3:
2480 doRadialShFill((GfxRadialShading *)shading);
2481 break;
2482 case 4:
2483 case 5:
2484 doGouraudTriangleShFill((GfxGouraudTriangleShading *)shading);
2485 break;
2486 case 6:
2487 case 7:
2488 doPatchMeshShFill((GfxPatchMeshShading *)shading);
2489 break;
2492 #if 1 //~tmp: turn off anti-aliasing temporarily
2493 if (vaa) {
2494 out->setVectorAntialias(gTrue);
2496 #endif
2498 // restore graphics state
2499 restoreStateStack(savedState);
2501 delete shading;
2504 void Gfx::doFunctionShFill(GfxFunctionShading *shading) {
2505 double x0, y0, x1, y1;
2506 GfxColor colors[4];
2508 if (out->useShadedFills( shading->getType() ) &&
2509 out->functionShadedFill(state, shading)) {
2510 return;
2513 shading->getDomain(&x0, &y0, &x1, &y1);
2514 shading->getColor(x0, y0, &colors[0]);
2515 shading->getColor(x0, y1, &colors[1]);
2516 shading->getColor(x1, y0, &colors[2]);
2517 shading->getColor(x1, y1, &colors[3]);
2518 doFunctionShFill1(shading, x0, y0, x1, y1, colors, 0);
2521 void Gfx::doFunctionShFill1(GfxFunctionShading *shading,
2522 double x0, double y0,
2523 double x1, double y1,
2524 GfxColor *colors, int depth) {
2525 GfxColor fillColor;
2526 GfxColor color0M, color1M, colorM0, colorM1, colorMM;
2527 GfxColor colors2[4];
2528 double *matrix;
2529 double xM, yM;
2530 int nComps, i, j;
2532 nComps = shading->getColorSpace()->getNComps();
2533 matrix = shading->getMatrix();
2535 // compare the four corner colors
2536 for (i = 0; i < 4; ++i) {
2537 for (j = 0; j < nComps; ++j) {
2538 if (abs(colors[i].c[j] - colors[(i+1)&3].c[j]) > functionColorDelta) {
2539 break;
2542 if (j < nComps) {
2543 break;
2547 // center of the rectangle
2548 xM = 0.5 * (x0 + x1);
2549 yM = 0.5 * (y0 + y1);
2551 // the four corner colors are close (or we hit the recursive limit)
2552 // -- fill the rectangle; but require at least one subdivision
2553 // (depth==0) to avoid problems when the four outer corners of the
2554 // shaded region are the same color
2555 if ((i == 4 && depth > 0) || depth == functionMaxDepth) {
2557 // use the center color
2558 shading->getColor(xM, yM, &fillColor);
2559 state->setFillColor(&fillColor);
2560 out->updateFillColor(state);
2562 // fill the rectangle
2563 state->moveTo(x0 * matrix[0] + y0 * matrix[2] + matrix[4],
2564 x0 * matrix[1] + y0 * matrix[3] + matrix[5]);
2565 state->lineTo(x1 * matrix[0] + y0 * matrix[2] + matrix[4],
2566 x1 * matrix[1] + y0 * matrix[3] + matrix[5]);
2567 state->lineTo(x1 * matrix[0] + y1 * matrix[2] + matrix[4],
2568 x1 * matrix[1] + y1 * matrix[3] + matrix[5]);
2569 state->lineTo(x0 * matrix[0] + y1 * matrix[2] + matrix[4],
2570 x0 * matrix[1] + y1 * matrix[3] + matrix[5]);
2571 state->closePath();
2572 out->fill(state);
2573 state->clearPath();
2575 // the four corner colors are not close enough -- subdivide the
2576 // rectangle
2577 } else {
2579 // colors[0] colorM0 colors[2]
2580 // (x0,y0) (xM,y0) (x1,y0)
2581 // +----------+----------+
2582 // | | |
2583 // | UL | UR |
2584 // color0M | colorMM | color1M
2585 // (x0,yM) +----------+----------+ (x1,yM)
2586 // | (xM,yM) |
2587 // | LL | LR |
2588 // | | |
2589 // +----------+----------+
2590 // colors[1] colorM1 colors[3]
2591 // (x0,y1) (xM,y1) (x1,y1)
2593 shading->getColor(x0, yM, &color0M);
2594 shading->getColor(x1, yM, &color1M);
2595 shading->getColor(xM, y0, &colorM0);
2596 shading->getColor(xM, y1, &colorM1);
2597 shading->getColor(xM, yM, &colorMM);
2599 // upper-left sub-rectangle
2600 colors2[0] = colors[0];
2601 colors2[1] = color0M;
2602 colors2[2] = colorM0;
2603 colors2[3] = colorMM;
2604 doFunctionShFill1(shading, x0, y0, xM, yM, colors2, depth + 1);
2606 // lower-left sub-rectangle
2607 colors2[0] = color0M;
2608 colors2[1] = colors[1];
2609 colors2[2] = colorMM;
2610 colors2[3] = colorM1;
2611 doFunctionShFill1(shading, x0, yM, xM, y1, colors2, depth + 1);
2613 // upper-right sub-rectangle
2614 colors2[0] = colorM0;
2615 colors2[1] = colorMM;
2616 colors2[2] = colors[2];
2617 colors2[3] = color1M;
2618 doFunctionShFill1(shading, xM, y0, x1, yM, colors2, depth + 1);
2620 // lower-right sub-rectangle
2621 colors2[0] = colorMM;
2622 colors2[1] = colorM1;
2623 colors2[2] = color1M;
2624 colors2[3] = colors[3];
2625 doFunctionShFill1(shading, xM, yM, x1, y1, colors2, depth + 1);
2629 static void bubbleSort(double array[])
2631 for (int j = 0; j < 3; ++j) {
2632 int kk = j;
2633 for (int k = j + 1; k < 4; ++k) {
2634 if (array[k] < array[kk]) {
2635 kk = k;
2638 double tmp = array[j];
2639 array[j] = array[kk];
2640 array[kk] = tmp;
2644 void Gfx::doAxialShFill(GfxAxialShading *shading) {
2645 double xMin, yMin, xMax, yMax;
2646 double x0, y0, x1, y1;
2647 double dx, dy, mul;
2648 GBool dxZero, dyZero;
2649 double bboxIntersections[4];
2650 double tMin, tMax, tx, ty;
2651 double s[4], sMin, sMax, tmp;
2652 double ux0, uy0, ux1, uy1, vx0, vy0, vx1, vy1;
2653 double t0, t1, tt;
2654 double ta[axialMaxSplits + 1];
2655 int next[axialMaxSplits + 1];
2656 GfxColor color0, color1;
2657 int nComps;
2658 int i, j, k;
2659 GBool needExtend = gTrue;
2661 // get the clip region bbox
2662 state->getUserClipBBox(&xMin, &yMin, &xMax, &yMax);
2664 // compute min and max t values, based on the four corners of the
2665 // clip region bbox
2666 shading->getCoords(&x0, &y0, &x1, &y1);
2667 dx = x1 - x0;
2668 dy = y1 - y0;
2669 dxZero = fabs(dx) < 0.01;
2670 dyZero = fabs(dy) < 0.01;
2671 if (dxZero && dyZero) {
2672 tMin = tMax = 0;
2673 } else {
2674 mul = 1 / (dx * dx + dy * dy);
2675 bboxIntersections[0] = ((xMin - x0) * dx + (yMin - y0) * dy) * mul;
2676 bboxIntersections[1] = ((xMin - x0) * dx + (yMax - y0) * dy) * mul;
2677 bboxIntersections[2] = ((xMax - x0) * dx + (yMin - y0) * dy) * mul;
2678 bboxIntersections[3] = ((xMax - x0) * dx + (yMax - y0) * dy) * mul;
2679 bubbleSort(bboxIntersections);
2680 tMin = bboxIntersections[0];
2681 tMax = bboxIntersections[3];
2682 if (tMin < 0 && !shading->getExtend0()) {
2683 tMin = 0;
2685 if (tMax > 1 && !shading->getExtend1()) {
2686 tMax = 1;
2690 if (out->useShadedFills( shading->getType() ) &&
2691 out->axialShadedFill(state, shading, tMin, tMax)) {
2692 return;
2695 // get the function domain
2696 t0 = shading->getDomain0();
2697 t1 = shading->getDomain1();
2699 // Traverse the t axis and do the shading.
2701 // For each point (tx, ty) on the t axis, consider a line through
2702 // that point perpendicular to the t axis:
2704 // x(s) = tx + s * -dy --> s = (x - tx) / -dy
2705 // y(s) = ty + s * dx --> s = (y - ty) / dx
2707 // Then look at the intersection of this line with the bounding box
2708 // (xMin, yMin, xMax, yMax). In the general case, there are four
2709 // intersection points:
2711 // s0 = (xMin - tx) / -dy
2712 // s1 = (xMax - tx) / -dy
2713 // s2 = (yMin - ty) / dx
2714 // s3 = (yMax - ty) / dx
2716 // and we want the middle two s values.
2718 // In the case where dx = 0, take s0 and s1; in the case where dy =
2719 // 0, take s2 and s3.
2721 // Each filled polygon is bounded by two of these line segments
2722 // perpdendicular to the t axis.
2724 // The t axis is bisected into smaller regions until the color
2725 // difference across a region is small enough, and then the region
2726 // is painted with a single color.
2728 // set up: require at least one split to avoid problems when the two
2729 // ends of the t axis have the same color
2730 nComps = shading->getColorSpace()->getNComps();
2731 ta[0] = tMin;
2732 next[0] = axialMaxSplits / 2;
2733 ta[axialMaxSplits / 2] = 0.5 * (tMin + tMax);
2734 next[axialMaxSplits / 2] = axialMaxSplits;
2735 ta[axialMaxSplits] = tMax;
2737 // compute the color at t = tMin
2738 if (tMin < 0) {
2739 tt = t0;
2740 } else if (tMin > 1) {
2741 tt = t1;
2742 } else {
2743 tt = t0 + (t1 - t0) * tMin;
2745 shading->getColor(tt, &color0);
2747 if (out->useFillColorStop()) {
2748 // make sure we add stop color when t = tMin
2749 state->setFillColor(&color0);
2750 out->updateFillColorStop(state, 0);
2753 // compute the coordinates of the point on the t axis at t = tMin;
2754 // then compute the intersection of the perpendicular line with the
2755 // bounding box
2756 tx = x0 + tMin * dx;
2757 ty = y0 + tMin * dy;
2758 if (dxZero && dyZero) {
2759 sMin = sMax = 0;
2760 } else if (dxZero) {
2761 sMin = (xMin - tx) / -dy;
2762 sMax = (xMax - tx) / -dy;
2763 if (sMin > sMax) { tmp = sMin; sMin = sMax; sMax = tmp; }
2764 } else if (dyZero) {
2765 sMin = (yMin - ty) / dx;
2766 sMax = (yMax - ty) / dx;
2767 if (sMin > sMax) { tmp = sMin; sMin = sMax; sMax = tmp; }
2768 } else {
2769 s[0] = (yMin - ty) / dx;
2770 s[1] = (yMax - ty) / dx;
2771 s[2] = (xMin - tx) / -dy;
2772 s[3] = (xMax - tx) / -dy;
2773 bubbleSort(s);
2774 sMin = s[1];
2775 sMax = s[2];
2777 ux0 = tx - sMin * dy;
2778 uy0 = ty + sMin * dx;
2779 vx0 = tx - sMax * dy;
2780 vy0 = ty + sMax * dx;
2782 i = 0;
2783 bool doneBBox1, doneBBox2;
2784 if (dxZero && dyZero) {
2785 doneBBox1 = doneBBox2 = true;
2786 } else {
2787 doneBBox1 = bboxIntersections[1] < tMin;
2788 doneBBox2 = bboxIntersections[2] > tMax;
2791 // If output device doesn't support the extended mode required
2792 // we have to do it here
2793 needExtend = !out->axialShadedSupportExtend(state, shading);
2795 while (i < axialMaxSplits) {
2797 // bisect until color difference is small enough or we hit the
2798 // bisection limit
2799 j = next[i];
2800 while (j > i + 1) {
2801 if (ta[j] < 0) {
2802 tt = t0;
2803 } else if (ta[j] > 1) {
2804 tt = t1;
2805 } else {
2806 tt = t0 + (t1 - t0) * ta[j];
2808 shading->getColor(tt, &color1);
2809 if (isSameGfxColor(color1, color0, nComps, axialColorDelta)) {
2810 // in these two if what we guarantee is that if we are skipping lots of
2811 // positions because the colors are the same, we still create a region
2812 // with vertexs passing by bboxIntersections[1] and bboxIntersections[2]
2813 // otherwise we can have empty regions that should really be painted
2814 // like happened in bug 19896
2815 // What we do to ensure that we pass a line through this points
2816 // is making sure use the exact bboxIntersections[] value as one of the used ta[] values
2817 if (!doneBBox1 && ta[i] < bboxIntersections[1] && ta[j] > bboxIntersections[1]) {
2818 int teoricalj = (int) ((bboxIntersections[1] - tMin) * axialMaxSplits / (tMax - tMin));
2819 if (teoricalj <= i) teoricalj = i + 1;
2820 if (teoricalj < j) {
2821 next[i] = teoricalj;
2822 next[teoricalj] = j;
2824 else {
2825 teoricalj = j;
2827 ta[teoricalj] = bboxIntersections[1];
2828 j = teoricalj;
2829 doneBBox1 = true;
2831 if (!doneBBox2 && ta[i] < bboxIntersections[2] && ta[j] > bboxIntersections[2]) {
2832 int teoricalj = (int) ((bboxIntersections[2] - tMin) * axialMaxSplits / (tMax - tMin));
2833 if (teoricalj <= i) teoricalj = i + 1;
2834 if (teoricalj < j) {
2835 next[i] = teoricalj;
2836 next[teoricalj] = j;
2838 else {
2839 teoricalj = j;
2841 ta[teoricalj] = bboxIntersections[2];
2842 j = teoricalj;
2843 doneBBox2 = true;
2845 break;
2847 k = (i + j) / 2;
2848 ta[k] = 0.5 * (ta[i] + ta[j]);
2849 next[i] = k;
2850 next[k] = j;
2851 j = k;
2854 // use the average of the colors of the two sides of the region
2855 for (k = 0; k < nComps; ++k) {
2856 color0.c[k] = (color0.c[k] + color1.c[k]) / 2;
2859 // compute the coordinates of the point on the t axis; then
2860 // compute the intersection of the perpendicular line with the
2861 // bounding box
2862 tx = x0 + ta[j] * dx;
2863 ty = y0 + ta[j] * dy;
2864 if (dxZero && dyZero) {
2865 sMin = sMax = 0;
2866 } else if (dxZero) {
2867 sMin = (xMin - tx) / -dy;
2868 sMax = (xMax - tx) / -dy;
2869 if (sMin > sMax) { tmp = sMin; sMin = sMax; sMax = tmp; }
2870 } else if (dyZero) {
2871 sMin = (yMin - ty) / dx;
2872 sMax = (yMax - ty) / dx;
2873 if (sMin > sMax) { tmp = sMin; sMin = sMax; sMax = tmp; }
2874 } else {
2875 s[0] = (yMin - ty) / dx;
2876 s[1] = (yMax - ty) / dx;
2877 s[2] = (xMin - tx) / -dy;
2878 s[3] = (xMax - tx) / -dy;
2879 bubbleSort(s);
2880 sMin = s[1];
2881 sMax = s[2];
2883 ux1 = tx - sMin * dy;
2884 uy1 = ty + sMin * dx;
2885 vx1 = tx - sMax * dy;
2886 vy1 = ty + sMax * dx;
2888 // set the color
2889 state->setFillColor(&color0);
2890 if (out->useFillColorStop())
2891 out->updateFillColorStop(state, (ta[j] - tMin)/(tMax - tMin));
2892 else
2893 out->updateFillColor(state);
2895 if (needExtend) {
2896 // fill the region
2897 state->moveTo(ux0, uy0);
2898 state->lineTo(vx0, vy0);
2899 state->lineTo(vx1, vy1);
2900 state->lineTo(ux1, uy1);
2901 state->closePath();
2904 if (!out->useFillColorStop()) {
2905 out->fill(state);
2906 state->clearPath();
2909 // set up for next region
2910 ux0 = ux1;
2911 uy0 = uy1;
2912 vx0 = vx1;
2913 vy0 = vy1;
2914 color0 = color1;
2915 i = next[i];
2918 if (out->useFillColorStop()) {
2919 if (!needExtend) {
2920 state->moveTo(xMin, yMin);
2921 state->lineTo(xMin, yMax);
2922 state->lineTo(xMax, yMax);
2923 state->lineTo(xMax, yMin);
2924 state->closePath();
2926 out->fill(state);
2927 state->clearPath();
2931 static inline void getShadingColorRadialHelper(double t0, double t1, double t, GfxRadialShading *shading, GfxColor *color)
2933 if (t0 < t1) {
2934 if (t < t0) {
2935 shading->getColor(t0, color);
2936 } else if (t > t1) {
2937 shading->getColor(t1, color);
2938 } else {
2939 shading->getColor(t, color);
2941 } else {
2942 if (t > t0) {
2943 shading->getColor(t0, color);
2944 } else if (t < t1) {
2945 shading->getColor(t1, color);
2946 } else {
2947 shading->getColor(t, color);
2952 void Gfx::doRadialShFill(GfxRadialShading *shading) {
2953 double xMin, yMin, xMax, yMax;
2954 double x0, y0, r0, x1, y1, r1, t0, t1;
2955 int nComps;
2956 GfxColor colorA, colorB;
2957 double xa, ya, xb, yb, ra, rb;
2958 double ta, tb, sa, sb;
2959 double sz, xz, yz, sMin, sMax;
2960 GBool enclosed;
2961 int ia, ib, k, n;
2962 double *ctm;
2963 double theta, alpha, angle, t;
2964 GBool needExtend = gTrue;
2966 // get the shading info
2967 shading->getCoords(&x0, &y0, &r0, &x1, &y1, &r1);
2968 t0 = shading->getDomain0();
2969 t1 = shading->getDomain1();
2970 nComps = shading->getColorSpace()->getNComps();
2972 // Compute the point at which r(s) = 0; check for the enclosed
2973 // circles case; and compute the angles for the tangent lines.
2974 if (x0 == x1 && y0 == y1) {
2975 enclosed = gTrue;
2976 theta = 0; // make gcc happy
2977 sz = 0; // make gcc happy
2978 } else if (r0 == r1) {
2979 enclosed = gFalse;
2980 theta = 0;
2981 sz = 0; // make gcc happy
2982 } else {
2983 sz = (r1 > r0) ? -r0 / (r1 - r0) : -r1 / (r0 - r1);
2984 xz = x0 + sz * (x1 - x0);
2985 yz = y0 + sz * (y1 - y0);
2986 enclosed = (xz - x0) * (xz - x0) + (yz - y0) * (yz - y0) <= r0 * r0;
2987 theta = asin(r0 / sqrt((x0 - xz) * (x0 - xz) + (y0 - yz) * (y0 - yz)));
2988 if (r0 > r1) {
2989 theta = -theta;
2992 if (enclosed) {
2993 alpha = 0;
2994 } else {
2995 alpha = atan2(y1 - y0, x1 - x0);
2998 // compute the (possibly extended) s range
2999 state->getUserClipBBox(&xMin, &yMin, &xMax, &yMax);
3000 if (enclosed) {
3001 sMin = 0;
3002 sMax = 1;
3003 } else {
3004 sMin = 1;
3005 sMax = 0;
3006 // solve for x(s) + r(s) = xMin
3007 if ((x1 + r1) - (x0 + r0) != 0) {
3008 sa = (xMin - (x0 + r0)) / ((x1 + r1) - (x0 + r0));
3009 if (sa < sMin) {
3010 sMin = sa;
3011 } else if (sa > sMax) {
3012 sMax = sa;
3015 // solve for x(s) - r(s) = xMax
3016 if ((x1 - r1) - (x0 - r0) != 0) {
3017 sa = (xMax - (x0 - r0)) / ((x1 - r1) - (x0 - r0));
3018 if (sa < sMin) {
3019 sMin = sa;
3020 } else if (sa > sMax) {
3021 sMax = sa;
3024 // solve for y(s) + r(s) = yMin
3025 if ((y1 + r1) - (y0 + r0) != 0) {
3026 sa = (yMin - (y0 + r0)) / ((y1 + r1) - (y0 + r0));
3027 if (sa < sMin) {
3028 sMin = sa;
3029 } else if (sa > sMax) {
3030 sMax = sa;
3033 // solve for y(s) - r(s) = yMax
3034 if ((y1 - r1) - (y0 - r0) != 0) {
3035 sa = (yMax - (y0 - r0)) / ((y1 - r1) - (y0 - r0));
3036 if (sa < sMin) {
3037 sMin = sa;
3038 } else if (sa > sMax) {
3039 sMax = sa;
3042 // check against sz
3043 if (r0 < r1) {
3044 if (sMin < sz) {
3045 sMin = sz;
3047 } else if (r0 > r1) {
3048 if (sMax > sz) {
3049 sMax = sz;
3052 // check the 'extend' flags
3053 if (!shading->getExtend0() && sMin < 0) {
3054 sMin = 0;
3056 if (!shading->getExtend1() && sMax > 1) {
3057 sMax = 1;
3061 if (out->useShadedFills( shading->getType() ) &&
3062 out->radialShadedFill(state, shading, sMin, sMax)) {
3063 return;
3066 // compute the number of steps into which circles must be divided to
3067 // achieve a curve flatness of 0.1 pixel in device space for the
3068 // largest circle (note that "device space" is 72 dpi when generating
3069 // PostScript, hence the relatively small 0.1 pixel accuracy)
3070 ctm = state->getCTM();
3071 t = fabs(ctm[0]);
3072 if (fabs(ctm[1]) > t) {
3073 t = fabs(ctm[1]);
3075 if (fabs(ctm[2]) > t) {
3076 t = fabs(ctm[2]);
3078 if (fabs(ctm[3]) > t) {
3079 t = fabs(ctm[3]);
3081 if (r0 > r1) {
3082 t *= r0;
3083 } else {
3084 t *= r1;
3086 if (t < 1) {
3087 n = 3;
3088 } else {
3089 n = (int)(M_PI / acos(1 - 0.1 / t));
3090 if (n < 3) {
3091 n = 3;
3092 } else if (n > 200) {
3093 n = 200;
3097 // setup for the start circle
3098 ia = 0;
3099 sa = sMin;
3100 ta = t0 + sa * (t1 - t0);
3101 xa = x0 + sa * (x1 - x0);
3102 ya = y0 + sa * (y1 - y0);
3103 ra = r0 + sa * (r1 - r0);
3104 getShadingColorRadialHelper(t0, t1, ta, shading, &colorA);
3106 needExtend = !out->radialShadedSupportExtend(state, shading);
3108 // fill the circles
3109 while (ia < radialMaxSplits) {
3111 // go as far along the t axis (toward t1) as we can, such that the
3112 // color difference is within the tolerance (radialColorDelta) --
3113 // this uses bisection (between the current value, t, and t1),
3114 // limited to radialMaxSplits points along the t axis; require at
3115 // least one split to avoid problems when the innermost and
3116 // outermost colors are the same
3117 ib = radialMaxSplits;
3118 sb = sMax;
3119 tb = t0 + sb * (t1 - t0);
3120 getShadingColorRadialHelper(t0, t1, tb, shading, &colorB);
3121 while (ib - ia > 1) {
3122 if (isSameGfxColor(colorB, colorA, nComps, radialColorDelta)) {
3123 // The shading is not necessarily lineal so having two points with the
3124 // same color does not mean all the areas in between have the same color too
3125 int ic = ia + 1;
3126 for (; ic <= ib; ic++) {
3127 GfxColor colorC;
3128 const double sc = sMin + ((double)ic / (double)radialMaxSplits) * (sMax - sMin);
3129 const double tc = t0 + sc * (t1 - t0);
3130 getShadingColorRadialHelper(t0, t1, tc, shading, &colorC);
3131 if (!isSameGfxColor(colorC, colorA, nComps, radialColorDelta)) {
3132 break;
3135 ib = (ic > ia + 1) ? ic - 1 : ia + 1;
3136 sb = sMin + ((double)ib / (double)radialMaxSplits) * (sMax - sMin);
3137 tb = t0 + sb * (t1 - t0);
3138 getShadingColorRadialHelper(t0, t1, tb, shading, &colorB);
3139 break;
3141 ib = (ia + ib) / 2;
3142 sb = sMin + ((double)ib / (double)radialMaxSplits) * (sMax - sMin);
3143 tb = t0 + sb * (t1 - t0);
3144 getShadingColorRadialHelper(t0, t1, tb, shading, &colorB);
3147 // compute center and radius of the circle
3148 xb = x0 + sb * (x1 - x0);
3149 yb = y0 + sb * (y1 - y0);
3150 rb = r0 + sb * (r1 - r0);
3152 // use the average of the colors at the two circles
3153 for (k = 0; k < nComps; ++k) {
3154 colorA.c[k] = (colorA.c[k] + colorB.c[k]) / 2;
3156 state->setFillColor(&colorA);
3157 if (out->useFillColorStop())
3158 out->updateFillColorStop(state, (sa - sMin)/(sMax - sMin));
3159 else
3160 out->updateFillColor(state);
3162 if (needExtend) {
3163 if (enclosed) {
3164 // construct path for first circle (counterclockwise)
3165 state->moveTo(xa + ra, ya);
3166 for (k = 1; k < n; ++k) {
3167 angle = ((double)k / (double)n) * 2 * M_PI;
3168 state->lineTo(xa + ra * cos(angle), ya + ra * sin(angle));
3170 state->closePath();
3172 // construct and append path for second circle (clockwise)
3173 state->moveTo(xb + rb, yb);
3174 for (k = 1; k < n; ++k) {
3175 angle = -((double)k / (double)n) * 2 * M_PI;
3176 state->lineTo(xb + rb * cos(angle), yb + rb * sin(angle));
3178 state->closePath();
3179 } else {
3180 // construct the first subpath (clockwise)
3181 state->moveTo(xa + ra * cos(alpha + theta + 0.5 * M_PI),
3182 ya + ra * sin(alpha + theta + 0.5 * M_PI));
3183 for (k = 0; k < n; ++k) {
3184 angle = alpha + theta + 0.5 * M_PI
3185 - ((double)k / (double)n) * (2 * theta + M_PI);
3186 state->lineTo(xb + rb * cos(angle), yb + rb * sin(angle));
3188 for (k = 0; k < n; ++k) {
3189 angle = alpha - theta - 0.5 * M_PI
3190 + ((double)k / (double)n) * (2 * theta - M_PI);
3191 state->lineTo(xa + ra * cos(angle), ya + ra * sin(angle));
3193 state->closePath();
3195 // construct the second subpath (counterclockwise)
3196 state->moveTo(xa + ra * cos(alpha + theta + 0.5 * M_PI),
3197 ya + ra * sin(alpha + theta + 0.5 * M_PI));
3198 for (k = 0; k < n; ++k) {
3199 angle = alpha + theta + 0.5 * M_PI
3200 + ((double)k / (double)n) * (-2 * theta + M_PI);
3201 state->lineTo(xb + rb * cos(angle), yb + rb * sin(angle));
3203 for (k = 0; k < n; ++k) {
3204 angle = alpha - theta - 0.5 * M_PI
3205 + ((double)k / (double)n) * (2 * theta + M_PI);
3206 state->lineTo(xa + ra * cos(angle), ya + ra * sin(angle));
3208 state->closePath();
3212 if (!out->useFillColorStop()) {
3213 // fill the path
3214 out->fill(state);
3215 state->clearPath();
3218 // step to the next value of t
3219 ia = ib;
3220 sa = sb;
3221 ta = tb;
3222 xa = xb;
3223 ya = yb;
3224 ra = rb;
3225 colorA = colorB;
3228 if (out->useFillColorStop()) {
3229 // make sure we add stop color when sb = sMax
3230 state->setFillColor(&colorA);
3231 out->updateFillColorStop(state, (sb - sMin)/(sMax - sMin));
3233 // fill the path
3234 state->moveTo(xMin, yMin);
3235 state->lineTo(xMin, yMax);
3236 state->lineTo(xMax, yMax);
3237 state->lineTo(xMax, yMin);
3238 state->closePath();
3240 out->fill(state);
3241 state->clearPath();
3244 if (!needExtend)
3245 return;
3247 if (enclosed) {
3248 // extend the smaller circle
3249 if ((shading->getExtend0() && r0 <= r1) ||
3250 (shading->getExtend1() && r1 < r0)) {
3251 if (r0 <= r1) {
3252 ta = t0;
3253 ra = r0;
3254 xa = x0;
3255 ya = y0;
3256 } else {
3257 ta = t1;
3258 ra = r1;
3259 xa = x1;
3260 ya = y1;
3262 shading->getColor(ta, &colorA);
3263 state->setFillColor(&colorA);
3264 out->updateFillColor(state);
3265 state->moveTo(xa + ra, ya);
3266 for (k = 1; k < n; ++k) {
3267 angle = ((double)k / (double)n) * 2 * M_PI;
3268 state->lineTo(xa + ra * cos(angle), ya + ra * sin(angle));
3270 state->closePath();
3271 out->fill(state);
3272 state->clearPath();
3275 // extend the larger circle
3276 if ((shading->getExtend0() && r0 > r1) ||
3277 (shading->getExtend1() && r1 >= r0)) {
3278 if (r0 > r1) {
3279 ta = t0;
3280 ra = r0;
3281 xa = x0;
3282 ya = y0;
3283 } else {
3284 ta = t1;
3285 ra = r1;
3286 xa = x1;
3287 ya = y1;
3289 shading->getColor(ta, &colorA);
3290 state->setFillColor(&colorA);
3291 out->updateFillColor(state);
3292 state->moveTo(xMin, yMin);
3293 state->lineTo(xMin, yMax);
3294 state->lineTo(xMax, yMax);
3295 state->lineTo(xMax, yMin);
3296 state->closePath();
3297 state->moveTo(xa + ra, ya);
3298 for (k = 1; k < n; ++k) {
3299 angle = ((double)k / (double)n) * 2 * M_PI;
3300 state->lineTo(xa + ra * cos(angle), ya + ra * sin(angle));
3302 state->closePath();
3303 out->fill(state);
3304 state->clearPath();
3309 void Gfx::doGouraudTriangleShFill(GfxGouraudTriangleShading *shading) {
3310 double x0, y0, x1, y1, x2, y2;
3311 int i;
3313 if (out->useShadedFills( shading->getType())) {
3314 if (out->gouraudTriangleShadedFill( state, shading))
3315 return;
3318 // preallocate a path (speed improvements)
3319 state->moveTo(0., 0.);
3320 state->lineTo(1., 0.);
3321 state->lineTo(0., 1.);
3322 state->closePath();
3324 GfxState::ReusablePathIterator *reusablePath = state->getReusablePath();
3326 if (shading->isParameterized()) {
3327 // work with parameterized values:
3328 double color0, color1, color2;
3329 // a relative threshold:
3330 const double refineColorThreshold = gouraudParameterizedColorDelta *
3331 (shading->getParameterDomainMax() - shading->getParameterDomainMin());
3332 for (i = 0; i < shading->getNTriangles(); ++i) {
3333 shading->getTriangle(i, &x0, &y0, &color0,
3334 &x1, &y1, &color1,
3335 &x2, &y2, &color2);
3336 gouraudFillTriangle(x0, y0, color0, x1, y1, color1, x2, y2, color2, refineColorThreshold, 0, shading, reusablePath);
3339 } else {
3340 // this always produces output -- even for parameterized ranges.
3341 // But it ignores the parameterized color map (the function).
3343 // Note that using this code in for parameterized shadings might be
3344 // correct in circumstances (namely if the function is linear in the actual
3345 // triangle), but in general, it will simply be wrong.
3346 GfxColor color0, color1, color2;
3347 for (i = 0; i < shading->getNTriangles(); ++i) {
3348 shading->getTriangle(i, &x0, &y0, &color0,
3349 &x1, &y1, &color1,
3350 &x2, &y2, &color2);
3351 gouraudFillTriangle(x0, y0, &color0, x1, y1, &color1, x2, y2, &color2, shading->getColorSpace()->getNComps(), 0, reusablePath);
3355 delete reusablePath;
3358 static inline void checkTrue(bool b, const char *message) {
3359 if (unlikely(!b)) {
3360 error(errSyntaxError, -1, message);
3364 void Gfx::gouraudFillTriangle(double x0, double y0, GfxColor *color0,
3365 double x1, double y1, GfxColor *color1,
3366 double x2, double y2, GfxColor *color2,
3367 int nComps, int depth, GfxState::ReusablePathIterator *path) {
3368 double x01, y01, x12, y12, x20, y20;
3369 GfxColor color01, color12, color20;
3370 int i;
3372 for (i = 0; i < nComps; ++i) {
3373 if (abs(color0->c[i] - color1->c[i]) > gouraudColorDelta ||
3374 abs(color1->c[i] - color2->c[i]) > gouraudColorDelta) {
3375 break;
3378 if (i == nComps || depth == gouraudMaxDepth) {
3379 state->setFillColor(color0);
3380 out->updateFillColor(state);
3382 path->reset(); checkTrue(!path->isEnd(), "Path should not be at end");
3383 path->setCoord(x0,y0); path->next(); checkTrue(!path->isEnd(), "Path should not be at end");
3384 path->setCoord(x1,y1); path->next(); checkTrue(!path->isEnd(), "Path should not be at end");
3385 path->setCoord(x2,y2); path->next(); checkTrue(!path->isEnd(), "Path should not be at end");
3386 path->setCoord(x0,y0); path->next(); checkTrue( path->isEnd(), "Path should be at end");
3387 out->fill(state);
3389 } else {
3390 x01 = 0.5 * (x0 + x1);
3391 y01 = 0.5 * (y0 + y1);
3392 x12 = 0.5 * (x1 + x2);
3393 y12 = 0.5 * (y1 + y2);
3394 x20 = 0.5 * (x2 + x0);
3395 y20 = 0.5 * (y2 + y0);
3396 for (i = 0; i < nComps; ++i) {
3397 color01.c[i] = (color0->c[i] + color1->c[i]) / 2;
3398 color12.c[i] = (color1->c[i] + color2->c[i]) / 2;
3399 color20.c[i] = (color2->c[i] + color0->c[i]) / 2;
3401 gouraudFillTriangle(x0, y0, color0, x01, y01, &color01,
3402 x20, y20, &color20, nComps, depth + 1, path);
3403 gouraudFillTriangle(x01, y01, &color01, x1, y1, color1,
3404 x12, y12, &color12, nComps, depth + 1, path);
3405 gouraudFillTriangle(x01, y01, &color01, x12, y12, &color12,
3406 x20, y20, &color20, nComps, depth + 1, path);
3407 gouraudFillTriangle(x20, y20, &color20, x12, y12, &color12,
3408 x2, y2, color2, nComps, depth + 1, path);
3411 void Gfx::gouraudFillTriangle(double x0, double y0, double color0,
3412 double x1, double y1, double color1,
3413 double x2, double y2, double color2,
3414 double refineColorThreshold, int depth, GfxGouraudTriangleShading *shading, GfxState::ReusablePathIterator *path) {
3415 const double meanColor = (color0 + color1 + color2) / 3;
3417 const bool isFineEnough =
3418 fabs(color0 - meanColor) < refineColorThreshold &&
3419 fabs(color1 - meanColor) < refineColorThreshold &&
3420 fabs(color2 - meanColor) < refineColorThreshold;
3422 if (isFineEnough || depth == gouraudMaxDepth) {
3423 GfxColor color;
3425 shading->getParameterizedColor(meanColor, &color);
3426 state->setFillColor(&color);
3427 out->updateFillColor(state);
3429 path->reset(); checkTrue(!path->isEnd(), "Path should not be at end");
3430 path->setCoord(x0,y0); path->next(); checkTrue(!path->isEnd(), "Path should not be at end");
3431 path->setCoord(x1,y1); path->next(); checkTrue(!path->isEnd(), "Path should not be at end");
3432 path->setCoord(x2,y2); path->next(); checkTrue(!path->isEnd(), "Path should not be at end");
3433 path->setCoord(x0,y0); path->next(); checkTrue( path->isEnd(), "Path should be at end");
3434 out->fill(state);
3436 } else {
3437 const double x01 = 0.5 * (x0 + x1);
3438 const double y01 = 0.5 * (y0 + y1);
3439 const double x12 = 0.5 * (x1 + x2);
3440 const double y12 = 0.5 * (y1 + y2);
3441 const double x20 = 0.5 * (x2 + x0);
3442 const double y20 = 0.5 * (y2 + y0);
3443 const double color01 = (color0 + color1) / 2.;
3444 const double color12 = (color1 + color2) / 2.;
3445 const double color20 = (color2 + color0) / 2.;
3446 ++depth;
3447 gouraudFillTriangle(x0, y0, color0,
3448 x01, y01, color01,
3449 x20, y20, color20,
3450 refineColorThreshold, depth, shading, path);
3451 gouraudFillTriangle(x01, y01, color01,
3452 x1, y1, color1,
3453 x12, y12, color12,
3454 refineColorThreshold, depth, shading, path);
3455 gouraudFillTriangle(x01, y01, color01,
3456 x12, y12, color12,
3457 x20, y20, color20,
3458 refineColorThreshold, depth, shading, path);
3459 gouraudFillTriangle(x20, y20, color20,
3460 x12, y12, color12,
3461 x2, y2, color2,
3462 refineColorThreshold, depth, shading, path);
3466 void Gfx::doPatchMeshShFill(GfxPatchMeshShading *shading) {
3467 int start, i;
3469 if (out->useShadedFills( shading->getType())) {
3470 if (out->patchMeshShadedFill( state, shading))
3471 return;
3474 if (shading->getNPatches() > 128) {
3475 start = 3;
3476 } else if (shading->getNPatches() > 64) {
3477 start = 2;
3478 } else if (shading->getNPatches() > 16) {
3479 start = 1;
3480 } else {
3481 start = 0;
3484 * Parameterized shadings take one parameter [t_0,t_e]
3485 * and map it into the color space.
3487 * Consequently, all color values are stored as doubles.
3489 * These color values are interpreted as parameters for parameterized
3490 * shadings and as colorspace entities otherwise.
3492 * The only difference is that color space entities are stored into
3493 * DOUBLE arrays, not into arrays of type GfxColorComp.
3495 const int colorComps = shading->getColorSpace()->getNComps();
3496 double refineColorThreshold;
3497 if( shading->isParameterized() ) {
3498 refineColorThreshold = gouraudParameterizedColorDelta *
3499 (shading->getParameterDomainMax() - shading->getParameterDomainMin());
3501 } else {
3502 refineColorThreshold = patchColorDelta;
3505 for (i = 0; i < shading->getNPatches(); ++i) {
3506 fillPatch(shading->getPatch(i),
3507 colorComps,
3508 shading->isParameterized() ? 1 : colorComps,
3509 refineColorThreshold,
3510 start,
3511 shading);
3516 void Gfx::fillPatch(GfxPatch *patch, int colorComps, int patchColorComps, double refineColorThreshold, int depth, GfxPatchMeshShading *shading) {
3517 GfxPatch patch00, patch01, patch10, patch11;
3518 double xx[4][8], yy[4][8];
3519 double xxm, yym;
3520 int i;
3522 for (i = 0; i < patchColorComps; ++i) {
3523 // these comparisons are done in double arithmetics.
3525 // For non-parameterized shadings, they are done in color space
3526 // components.
3527 if (fabs(patch->color[0][0].c[i] - patch->color[0][1].c[i]) > refineColorThreshold ||
3528 fabs(patch->color[0][1].c[i] - patch->color[1][1].c[i]) > refineColorThreshold ||
3529 fabs(patch->color[1][1].c[i] - patch->color[1][0].c[i]) > refineColorThreshold ||
3530 fabs(patch->color[1][0].c[i] - patch->color[0][0].c[i]) > refineColorThreshold) {
3531 break;
3534 if (i == patchColorComps || depth == patchMaxDepth) {
3535 GfxColor flatColor;
3536 if( shading->isParameterized() ) {
3537 shading->getParameterizedColor( patch->color[0][0].c[0], &flatColor );
3538 } else {
3539 for( i = 0; i<colorComps; ++i ) {
3540 // simply cast to the desired type; that's all what is needed.
3541 flatColor.c[i] = GfxColorComp(patch->color[0][0].c[i]);
3544 state->setFillColor(&flatColor);
3545 out->updateFillColor(state);
3546 state->moveTo(patch->x[0][0], patch->y[0][0]);
3547 state->curveTo(patch->x[0][1], patch->y[0][1],
3548 patch->x[0][2], patch->y[0][2],
3549 patch->x[0][3], patch->y[0][3]);
3550 state->curveTo(patch->x[1][3], patch->y[1][3],
3551 patch->x[2][3], patch->y[2][3],
3552 patch->x[3][3], patch->y[3][3]);
3553 state->curveTo(patch->x[3][2], patch->y[3][2],
3554 patch->x[3][1], patch->y[3][1],
3555 patch->x[3][0], patch->y[3][0]);
3556 state->curveTo(patch->x[2][0], patch->y[2][0],
3557 patch->x[1][0], patch->y[1][0],
3558 patch->x[0][0], patch->y[0][0]);
3559 state->closePath();
3560 out->fill(state);
3561 state->clearPath();
3562 } else {
3563 for (i = 0; i < 4; ++i) {
3564 xx[i][0] = patch->x[i][0];
3565 yy[i][0] = patch->y[i][0];
3566 xx[i][1] = 0.5 * (patch->x[i][0] + patch->x[i][1]);
3567 yy[i][1] = 0.5 * (patch->y[i][0] + patch->y[i][1]);
3568 xxm = 0.5 * (patch->x[i][1] + patch->x[i][2]);
3569 yym = 0.5 * (patch->y[i][1] + patch->y[i][2]);
3570 xx[i][6] = 0.5 * (patch->x[i][2] + patch->x[i][3]);
3571 yy[i][6] = 0.5 * (patch->y[i][2] + patch->y[i][3]);
3572 xx[i][2] = 0.5 * (xx[i][1] + xxm);
3573 yy[i][2] = 0.5 * (yy[i][1] + yym);
3574 xx[i][5] = 0.5 * (xxm + xx[i][6]);
3575 yy[i][5] = 0.5 * (yym + yy[i][6]);
3576 xx[i][3] = xx[i][4] = 0.5 * (xx[i][2] + xx[i][5]);
3577 yy[i][3] = yy[i][4] = 0.5 * (yy[i][2] + yy[i][5]);
3578 xx[i][7] = patch->x[i][3];
3579 yy[i][7] = patch->y[i][3];
3581 for (i = 0; i < 4; ++i) {
3582 patch00.x[0][i] = xx[0][i];
3583 patch00.y[0][i] = yy[0][i];
3584 patch00.x[1][i] = 0.5 * (xx[0][i] + xx[1][i]);
3585 patch00.y[1][i] = 0.5 * (yy[0][i] + yy[1][i]);
3586 xxm = 0.5 * (xx[1][i] + xx[2][i]);
3587 yym = 0.5 * (yy[1][i] + yy[2][i]);
3588 patch10.x[2][i] = 0.5 * (xx[2][i] + xx[3][i]);
3589 patch10.y[2][i] = 0.5 * (yy[2][i] + yy[3][i]);
3590 patch00.x[2][i] = 0.5 * (patch00.x[1][i] + xxm);
3591 patch00.y[2][i] = 0.5 * (patch00.y[1][i] + yym);
3592 patch10.x[1][i] = 0.5 * (xxm + patch10.x[2][i]);
3593 patch10.y[1][i] = 0.5 * (yym + patch10.y[2][i]);
3594 patch00.x[3][i] = 0.5 * (patch00.x[2][i] + patch10.x[1][i]);
3595 patch00.y[3][i] = 0.5 * (patch00.y[2][i] + patch10.y[1][i]);
3596 patch10.x[0][i] = patch00.x[3][i];
3597 patch10.y[0][i] = patch00.y[3][i];
3598 patch10.x[3][i] = xx[3][i];
3599 patch10.y[3][i] = yy[3][i];
3601 for (i = 4; i < 8; ++i) {
3602 patch01.x[0][i-4] = xx[0][i];
3603 patch01.y[0][i-4] = yy[0][i];
3604 patch01.x[1][i-4] = 0.5 * (xx[0][i] + xx[1][i]);
3605 patch01.y[1][i-4] = 0.5 * (yy[0][i] + yy[1][i]);
3606 xxm = 0.5 * (xx[1][i] + xx[2][i]);
3607 yym = 0.5 * (yy[1][i] + yy[2][i]);
3608 patch11.x[2][i-4] = 0.5 * (xx[2][i] + xx[3][i]);
3609 patch11.y[2][i-4] = 0.5 * (yy[2][i] + yy[3][i]);
3610 patch01.x[2][i-4] = 0.5 * (patch01.x[1][i-4] + xxm);
3611 patch01.y[2][i-4] = 0.5 * (patch01.y[1][i-4] + yym);
3612 patch11.x[1][i-4] = 0.5 * (xxm + patch11.x[2][i-4]);
3613 patch11.y[1][i-4] = 0.5 * (yym + patch11.y[2][i-4]);
3614 patch01.x[3][i-4] = 0.5 * (patch01.x[2][i-4] + patch11.x[1][i-4]);
3615 patch01.y[3][i-4] = 0.5 * (patch01.y[2][i-4] + patch11.y[1][i-4]);
3616 patch11.x[0][i-4] = patch01.x[3][i-4];
3617 patch11.y[0][i-4] = patch01.y[3][i-4];
3618 patch11.x[3][i-4] = xx[3][i];
3619 patch11.y[3][i-4] = yy[3][i];
3621 for (i = 0; i < patchColorComps; ++i) {
3622 patch00.color[0][0].c[i] = patch->color[0][0].c[i];
3623 patch00.color[0][1].c[i] = (patch->color[0][0].c[i] +
3624 patch->color[0][1].c[i]) / 2;
3625 patch01.color[0][0].c[i] = patch00.color[0][1].c[i];
3626 patch01.color[0][1].c[i] = patch->color[0][1].c[i];
3627 patch01.color[1][1].c[i] = (patch->color[0][1].c[i] +
3628 patch->color[1][1].c[i]) / 2;
3629 patch11.color[0][1].c[i] = patch01.color[1][1].c[i];
3630 patch11.color[1][1].c[i] = patch->color[1][1].c[i];
3631 patch11.color[1][0].c[i] = (patch->color[1][1].c[i] +
3632 patch->color[1][0].c[i]) / 2;
3633 patch10.color[1][1].c[i] = patch11.color[1][0].c[i];
3634 patch10.color[1][0].c[i] = patch->color[1][0].c[i];
3635 patch10.color[0][0].c[i] = (patch->color[1][0].c[i] +
3636 patch->color[0][0].c[i]) / 2;
3637 patch00.color[1][0].c[i] = patch10.color[0][0].c[i];
3638 patch00.color[1][1].c[i] = (patch00.color[1][0].c[i] +
3639 patch01.color[1][1].c[i]) / 2;
3640 patch01.color[1][0].c[i] = patch00.color[1][1].c[i];
3641 patch11.color[0][0].c[i] = patch00.color[1][1].c[i];
3642 patch10.color[0][1].c[i] = patch00.color[1][1].c[i];
3644 fillPatch(&patch00, colorComps, patchColorComps, refineColorThreshold, depth + 1, shading);
3645 fillPatch(&patch10, colorComps, patchColorComps, refineColorThreshold, depth + 1, shading);
3646 fillPatch(&patch01, colorComps, patchColorComps, refineColorThreshold, depth + 1, shading);
3647 fillPatch(&patch11, colorComps, patchColorComps, refineColorThreshold, depth + 1, shading);
3651 void Gfx::doEndPath() {
3652 if (state->isCurPt() && clip != clipNone) {
3653 state->clip();
3654 if (clip == clipNormal) {
3655 out->clip(state);
3656 } else {
3657 out->eoClip(state);
3660 clip = clipNone;
3661 state->clearPath();
3664 //------------------------------------------------------------------------
3665 // path clipping operators
3666 //------------------------------------------------------------------------
3668 void Gfx::opClip(Object args[], int numArgs) {
3669 clip = clipNormal;
3672 void Gfx::opEOClip(Object args[], int numArgs) {
3673 clip = clipEO;
3676 //------------------------------------------------------------------------
3677 // text object operators
3678 //------------------------------------------------------------------------
3680 void Gfx::opBeginText(Object args[], int numArgs) {
3681 out->beginTextObject(state);
3682 state->setTextMat(1, 0, 0, 1, 0, 0);
3683 state->textMoveTo(0, 0);
3684 out->updateTextMat(state);
3685 out->updateTextPos(state);
3686 fontChanged = gTrue;
3689 void Gfx::opEndText(Object args[], int numArgs) {
3690 out->endTextObject(state);
3693 //------------------------------------------------------------------------
3694 // text state operators
3695 //------------------------------------------------------------------------
3697 void Gfx::opSetCharSpacing(Object args[], int numArgs) {
3698 state->setCharSpace(args[0].getNum());
3699 out->updateCharSpace(state);
3702 void Gfx::opSetFont(Object args[], int numArgs) {
3703 GfxFont *font;
3705 if (!(font = res->lookupFont(args[0].getName()))) {
3706 // unsetting the font (drawing no text) is better than using the
3707 // previous one and drawing random glyphs from it
3708 state->setFont(NULL, args[1].getNum());
3709 fontChanged = gTrue;
3710 return;
3712 if (printCommands) {
3713 printf(" font: tag=%s name='%s' %g\n",
3714 font->getTag()->getCString(),
3715 font->getName() ? font->getName()->getCString() : "???",
3716 args[1].getNum());
3717 fflush(stdout);
3720 font->incRefCnt();
3721 state->setFont(font, args[1].getNum());
3722 fontChanged = gTrue;
3725 void Gfx::opSetTextLeading(Object args[], int numArgs) {
3726 state->setLeading(args[0].getNum());
3729 void Gfx::opSetTextRender(Object args[], int numArgs) {
3730 state->setRender(args[0].getInt());
3731 out->updateRender(state);
3734 void Gfx::opSetTextRise(Object args[], int numArgs) {
3735 state->setRise(args[0].getNum());
3736 out->updateRise(state);
3739 void Gfx::opSetWordSpacing(Object args[], int numArgs) {
3740 state->setWordSpace(args[0].getNum());
3741 out->updateWordSpace(state);
3744 void Gfx::opSetHorizScaling(Object args[], int numArgs) {
3745 state->setHorizScaling(args[0].getNum());
3746 out->updateHorizScaling(state);
3747 fontChanged = gTrue;
3750 //------------------------------------------------------------------------
3751 // text positioning operators
3752 //------------------------------------------------------------------------
3754 void Gfx::opTextMove(Object args[], int numArgs) {
3755 double tx, ty;
3757 tx = state->getLineX() + args[0].getNum();
3758 ty = state->getLineY() + args[1].getNum();
3759 state->textMoveTo(tx, ty);
3760 out->updateTextPos(state);
3763 void Gfx::opTextMoveSet(Object args[], int numArgs) {
3764 double tx, ty;
3766 tx = state->getLineX() + args[0].getNum();
3767 ty = args[1].getNum();
3768 state->setLeading(-ty);
3769 ty += state->getLineY();
3770 state->textMoveTo(tx, ty);
3771 out->updateTextPos(state);
3774 void Gfx::opSetTextMatrix(Object args[], int numArgs) {
3775 state->setTextMat(args[0].getNum(), args[1].getNum(),
3776 args[2].getNum(), args[3].getNum(),
3777 args[4].getNum(), args[5].getNum());
3778 state->textMoveTo(0, 0);
3779 out->updateTextMat(state);
3780 out->updateTextPos(state);
3781 fontChanged = gTrue;
3784 void Gfx::opTextNextLine(Object args[], int numArgs) {
3785 double tx, ty;
3787 tx = state->getLineX();
3788 ty = state->getLineY() - state->getLeading();
3789 state->textMoveTo(tx, ty);
3790 out->updateTextPos(state);
3793 //------------------------------------------------------------------------
3794 // text string operators
3795 //------------------------------------------------------------------------
3797 void Gfx::opShowText(Object args[], int numArgs) {
3798 if (!state->getFont()) {
3799 error(errSyntaxError, getPos(), "No font in show");
3800 return;
3802 if (fontChanged) {
3803 out->updateFont(state);
3804 fontChanged = gFalse;
3806 out->beginStringOp(state);
3807 doShowText(args[0].getString());
3808 out->endStringOp(state);
3809 if (!ocState) {
3810 doIncCharCount(args[0].getString());
3814 void Gfx::opMoveShowText(Object args[], int numArgs) {
3815 double tx, ty;
3817 if (!state->getFont()) {
3818 error(errSyntaxError, getPos(), "No font in move/show");
3819 return;
3821 if (fontChanged) {
3822 out->updateFont(state);
3823 fontChanged = gFalse;
3825 tx = state->getLineX();
3826 ty = state->getLineY() - state->getLeading();
3827 state->textMoveTo(tx, ty);
3828 out->updateTextPos(state);
3829 out->beginStringOp(state);
3830 doShowText(args[0].getString());
3831 out->endStringOp(state);
3832 if (!ocState) {
3833 doIncCharCount(args[0].getString());
3837 void Gfx::opMoveSetShowText(Object args[], int numArgs) {
3838 double tx, ty;
3840 if (!state->getFont()) {
3841 error(errSyntaxError, getPos(), "No font in move/set/show");
3842 return;
3844 if (fontChanged) {
3845 out->updateFont(state);
3846 fontChanged = gFalse;
3848 state->setWordSpace(args[0].getNum());
3849 state->setCharSpace(args[1].getNum());
3850 tx = state->getLineX();
3851 ty = state->getLineY() - state->getLeading();
3852 state->textMoveTo(tx, ty);
3853 out->updateWordSpace(state);
3854 out->updateCharSpace(state);
3855 out->updateTextPos(state);
3856 out->beginStringOp(state);
3857 doShowText(args[2].getString());
3858 out->endStringOp(state);
3859 if (ocState) {
3860 doIncCharCount(args[2].getString());
3864 void Gfx::opShowSpaceText(Object args[], int numArgs) {
3865 Array *a;
3866 Object obj;
3867 int wMode;
3868 int i;
3870 if (!state->getFont()) {
3871 error(errSyntaxError, getPos(), "No font in show/space");
3872 return;
3874 if (fontChanged) {
3875 out->updateFont(state);
3876 fontChanged = gFalse;
3878 out->beginStringOp(state);
3879 wMode = state->getFont()->getWMode();
3880 a = args[0].getArray();
3881 for (i = 0; i < a->getLength(); ++i) {
3882 a->get(i, &obj);
3883 if (obj.isNum()) {
3884 // this uses the absolute value of the font size to match
3885 // Acrobat's behavior
3886 if (wMode) {
3887 state->textShift(0, -obj.getNum() * 0.001 *
3888 state->getFontSize());
3889 } else {
3890 state->textShift(-obj.getNum() * 0.001 *
3891 state->getFontSize() *
3892 state->getHorizScaling(), 0);
3894 out->updateTextShift(state, obj.getNum());
3895 } else if (obj.isString()) {
3896 doShowText(obj.getString());
3897 } else {
3898 error(errSyntaxError, getPos(),
3899 "Element of show/space array must be number or string");
3901 obj.free();
3903 out->endStringOp(state);
3904 if (!ocState) {
3905 a = args[0].getArray();
3906 for (i = 0; i < a->getLength(); ++i) {
3907 a->get(i, &obj);
3908 if (obj.isString()) {
3909 doIncCharCount(obj.getString());
3911 obj.free();
3916 void Gfx::doShowText(GooString *s) {
3917 GfxFont *font;
3918 int wMode;
3919 double riseX, riseY;
3920 CharCode code;
3921 Unicode *u = NULL;
3922 double x, y, dx, dy, dx2, dy2, curX, curY, tdx, tdy, ddx, ddy;
3923 double originX, originY, tOriginX, tOriginY;
3924 double x0, y0, x1, y1;
3925 double oldCTM[6], newCTM[6];
3926 double *mat;
3927 Object charProc;
3928 Dict *resDict;
3929 Parser *oldParser;
3930 GfxState *savedState;
3931 char *p;
3932 int render;
3933 GBool patternFill;
3934 int len, n, uLen, nChars, nSpaces, i;
3936 font = state->getFont();
3937 wMode = font->getWMode();
3939 if (out->useDrawChar()) {
3940 out->beginString(state, s);
3943 // if we're doing a pattern fill, set up clipping
3944 render = state->getRender();
3945 if (!(render & 1) &&
3946 state->getFillColorSpace()->getMode() == csPattern) {
3947 patternFill = gTrue;
3948 saveState();
3949 // disable fill, enable clipping, leave stroke unchanged
3950 if ((render ^ (render >> 1)) & 1) {
3951 render = 5;
3952 } else {
3953 render = 7;
3955 state->setRender(render);
3956 out->updateRender(state);
3957 } else {
3958 patternFill = gFalse;
3961 state->textTransformDelta(0, state->getRise(), &riseX, &riseY);
3962 x0 = state->getCurX() + riseX;
3963 y0 = state->getCurY() + riseY;
3965 // handle a Type 3 char
3966 if (font->getType() == fontType3 && out->interpretType3Chars()) {
3967 mat = state->getCTM();
3968 for (i = 0; i < 6; ++i) {
3969 oldCTM[i] = mat[i];
3971 mat = state->getTextMat();
3972 newCTM[0] = mat[0] * oldCTM[0] + mat[1] * oldCTM[2];
3973 newCTM[1] = mat[0] * oldCTM[1] + mat[1] * oldCTM[3];
3974 newCTM[2] = mat[2] * oldCTM[0] + mat[3] * oldCTM[2];
3975 newCTM[3] = mat[2] * oldCTM[1] + mat[3] * oldCTM[3];
3976 mat = font->getFontMatrix();
3977 newCTM[0] = mat[0] * newCTM[0] + mat[1] * newCTM[2];
3978 newCTM[1] = mat[0] * newCTM[1] + mat[1] * newCTM[3];
3979 newCTM[2] = mat[2] * newCTM[0] + mat[3] * newCTM[2];
3980 newCTM[3] = mat[2] * newCTM[1] + mat[3] * newCTM[3];
3981 newCTM[0] *= state->getFontSize();
3982 newCTM[1] *= state->getFontSize();
3983 newCTM[2] *= state->getFontSize();
3984 newCTM[3] *= state->getFontSize();
3985 newCTM[0] *= state->getHorizScaling();
3986 newCTM[2] *= state->getHorizScaling();
3987 curX = state->getCurX();
3988 curY = state->getCurY();
3989 oldParser = parser;
3990 p = s->getCString();
3991 len = s->getLength();
3992 while (len > 0) {
3993 n = font->getNextChar(p, len, &code,
3994 &u, &uLen,
3995 &dx, &dy, &originX, &originY);
3996 dx = dx * state->getFontSize() + state->getCharSpace();
3997 if (n == 1 && *p == ' ') {
3998 dx += state->getWordSpace();
4000 dx *= state->getHorizScaling();
4001 dy *= state->getFontSize();
4002 state->textTransformDelta(dx, dy, &tdx, &tdy);
4003 state->transform(curX + riseX, curY + riseY, &x, &y);
4004 savedState = saveStateStack();
4005 state->setCTM(newCTM[0], newCTM[1], newCTM[2], newCTM[3], x, y);
4006 //~ the CTM concat values here are wrong (but never used)
4007 out->updateCTM(state, 1, 0, 0, 1, 0, 0);
4008 state->transformDelta(dx, dy, &ddx, &ddy);
4009 if (!out->beginType3Char(state, curX + riseX, curY + riseY, ddx, ddy,
4010 code, u, uLen)) {
4011 ((Gfx8BitFont *)font)->getCharProc(code, &charProc);
4012 if ((resDict = ((Gfx8BitFont *)font)->getResources())) {
4013 pushResources(resDict);
4015 if (charProc.isStream()) {
4016 display(&charProc, gFalse);
4017 } else {
4018 error(errSyntaxError, getPos(), "Missing or bad Type3 CharProc entry");
4020 out->endType3Char(state);
4021 if (resDict) {
4022 popResources();
4024 charProc.free();
4026 restoreStateStack(savedState);
4027 // GfxState::restore() does *not* restore the current position,
4028 // so we deal with it here using (curX, curY) and (lineX, lineY)
4029 curX += tdx;
4030 curY += tdy;
4031 state->moveTo(curX, curY);
4032 p += n;
4033 len -= n;
4035 parser = oldParser;
4037 } else if (out->useDrawChar()) {
4038 p = s->getCString();
4039 len = s->getLength();
4040 while (len > 0) {
4041 n = font->getNextChar(p, len, &code,
4042 &u, &uLen,
4043 &dx, &dy, &originX, &originY);
4044 if (wMode) {
4045 dx *= state->getFontSize();
4046 dy = dy * state->getFontSize() + state->getCharSpace();
4047 if (n == 1 && *p == ' ') {
4048 dy += state->getWordSpace();
4050 } else {
4051 dx = dx * state->getFontSize() + state->getCharSpace();
4052 if (n == 1 && *p == ' ') {
4053 dx += state->getWordSpace();
4055 dx *= state->getHorizScaling();
4056 dy *= state->getFontSize();
4058 state->textTransformDelta(dx, dy, &tdx, &tdy);
4059 originX *= state->getFontSize();
4060 originY *= state->getFontSize();
4061 state->textTransformDelta(originX, originY, &tOriginX, &tOriginY);
4062 if (ocState)
4063 out->drawChar(state, state->getCurX() + riseX, state->getCurY() + riseY,
4064 tdx, tdy, tOriginX, tOriginY, code, n, u, uLen);
4065 state->shift(tdx, tdy);
4066 p += n;
4067 len -= n;
4069 } else {
4070 dx = dy = 0;
4071 p = s->getCString();
4072 len = s->getLength();
4073 nChars = nSpaces = 0;
4074 while (len > 0) {
4075 n = font->getNextChar(p, len, &code,
4076 &u, &uLen,
4077 &dx2, &dy2, &originX, &originY);
4078 dx += dx2;
4079 dy += dy2;
4080 if (n == 1 && *p == ' ') {
4081 ++nSpaces;
4083 ++nChars;
4084 p += n;
4085 len -= n;
4087 if (wMode) {
4088 dx *= state->getFontSize();
4089 dy = dy * state->getFontSize()
4090 + nChars * state->getCharSpace()
4091 + nSpaces * state->getWordSpace();
4092 } else {
4093 dx = dx * state->getFontSize()
4094 + nChars * state->getCharSpace()
4095 + nSpaces * state->getWordSpace();
4096 dx *= state->getHorizScaling();
4097 dy *= state->getFontSize();
4099 state->textTransformDelta(dx, dy, &tdx, &tdy);
4100 if (ocState)
4101 out->drawString(state, s);
4102 state->shift(tdx, tdy);
4105 if (out->useDrawChar()) {
4106 out->endString(state);
4109 if (patternFill && ocState) {
4110 out->saveTextPos(state);
4111 // tell the OutputDev to do the clipping
4112 out->endTextObject(state);
4113 // set up a clipping bbox so doPatternText will work -- assume
4114 // that the text bounding box does not extend past the baseline in
4115 // any direction by more than twice the font size
4116 x1 = state->getCurX() + riseX;
4117 y1 = state->getCurY() + riseY;
4118 if (x0 > x1) {
4119 x = x0; x0 = x1; x1 = x;
4121 if (y0 > y1) {
4122 y = y0; y0 = y1; y1 = y;
4124 state->textTransformDelta(0, state->getFontSize(), &dx, &dy);
4125 state->textTransformDelta(state->getFontSize(), 0, &dx2, &dy2);
4126 dx = fabs(dx);
4127 dx2 = fabs(dx2);
4128 if (dx2 > dx) {
4129 dx = dx2;
4131 dy = fabs(dy);
4132 dy2 = fabs(dy2);
4133 if (dy2 > dy) {
4134 dy = dy2;
4136 state->clipToRect(x0 - 2 * dx, y0 - 2 * dy, x1 + 2 * dx, y1 + 2 * dy);
4137 // set render mode to fill-only
4138 state->setRender(0);
4139 out->updateRender(state);
4140 doPatternText();
4141 restoreState();
4142 out->restoreTextPos(state);
4145 updateLevel += 10 * s->getLength();
4148 // NB: this is only called when ocState is false.
4149 void Gfx::doIncCharCount(GooString *s) {
4150 if (out->needCharCount()) {
4151 out->incCharCount(s->getLength());
4155 //------------------------------------------------------------------------
4156 // XObject operators
4157 //------------------------------------------------------------------------
4159 void Gfx::opXObject(Object args[], int numArgs) {
4160 char *name;
4161 Object obj1, obj2, obj3, refObj;
4162 #if OPI_SUPPORT
4163 Object opiDict;
4164 #endif
4166 if (!ocState && !out->needCharCount()) {
4167 return;
4169 name = args[0].getName();
4170 if (!res->lookupXObject(name, &obj1)) {
4171 return;
4173 if (!obj1.isStream()) {
4174 error(errSyntaxError, getPos(), "XObject '{0:s}' is wrong type", name);
4175 obj1.free();
4176 return;
4179 #if OPI_SUPPORT
4180 obj1.streamGetDict()->lookup("OPI", &opiDict);
4181 if (opiDict.isDict()) {
4182 out->opiBegin(state, opiDict.getDict());
4184 #endif
4185 obj1.streamGetDict()->lookup("Subtype", &obj2);
4186 if (obj2.isName("Image")) {
4187 if (out->needNonText()) {
4188 res->lookupXObjectNF(name, &refObj);
4189 doImage(&refObj, obj1.getStream(), gFalse);
4190 refObj.free();
4192 } else if (obj2.isName("Form")) {
4193 res->lookupXObjectNF(name, &refObj);
4194 GBool shouldDoForm = gTrue;
4195 std::set<int>::iterator drawingFormIt;
4196 if (refObj.isRef()) {
4197 const int num = refObj.getRef().num;
4198 if (formsDrawing.find(num) == formsDrawing.end()) {
4199 drawingFormIt = formsDrawing.insert(num).first;
4200 } else {
4201 shouldDoForm = gFalse;
4204 if (shouldDoForm) {
4205 if (out->useDrawForm() && refObj.isRef()) {
4206 out->drawForm(refObj.getRef());
4207 } else {
4208 doForm(&obj1);
4211 if (refObj.isRef() && shouldDoForm) {
4212 formsDrawing.erase(drawingFormIt);
4214 refObj.free();
4215 } else if (obj2.isName("PS")) {
4216 obj1.streamGetDict()->lookup("Level1", &obj3);
4217 out->psXObject(obj1.getStream(),
4218 obj3.isStream() ? obj3.getStream() : (Stream *)NULL);
4219 } else if (obj2.isName()) {
4220 error(errSyntaxError, getPos(), "Unknown XObject subtype '{0:s}'", obj2.getName());
4221 } else {
4222 error(errSyntaxError, getPos(), "XObject subtype is missing or wrong type");
4224 obj2.free();
4225 #if OPI_SUPPORT
4226 if (opiDict.isDict()) {
4227 out->opiEnd(state, opiDict.getDict());
4229 opiDict.free();
4230 #endif
4231 obj1.free();
4234 void Gfx::doImage(Object *ref, Stream *str, GBool inlineImg) {
4235 Dict *dict, *maskDict;
4236 int width, height;
4237 int bits, maskBits;
4238 GBool interpolate;
4239 StreamColorSpaceMode csMode;
4240 GBool mask;
4241 GBool invert;
4242 GfxColorSpace *colorSpace, *maskColorSpace;
4243 GfxImageColorMap *colorMap, *maskColorMap;
4244 Object maskObj, smaskObj;
4245 GBool haveColorKeyMask, haveExplicitMask, haveSoftMask;
4246 int maskColors[2*gfxColorMaxComps];
4247 int maskWidth, maskHeight;
4248 GBool maskInvert;
4249 GBool maskInterpolate;
4250 Stream *maskStr;
4251 Object obj1, obj2;
4252 int i, n;
4254 // get info from the stream
4255 bits = 0;
4256 csMode = streamCSNone;
4257 str->getImageParams(&bits, &csMode);
4259 // get stream dict
4260 dict = str->getDict();
4262 // check for optional content key
4263 if (ref) {
4264 dict->lookupNF("OC", &obj1);
4265 if (catalog->getOptContentConfig() && !catalog->getOptContentConfig()->optContentIsVisible(&obj1)) {
4266 obj1.free();
4267 return;
4269 obj1.free();
4272 // get size
4273 dict->lookup("Width", &obj1);
4274 if (obj1.isNull()) {
4275 obj1.free();
4276 dict->lookup("W", &obj1);
4278 if (obj1.isInt())
4279 width = obj1.getInt();
4280 else if (obj1.isReal())
4281 width = (int)obj1.getReal();
4282 else
4283 goto err2;
4284 obj1.free();
4285 dict->lookup("Height", &obj1);
4286 if (obj1.isNull()) {
4287 obj1.free();
4288 dict->lookup("H", &obj1);
4290 if (obj1.isInt())
4291 height = obj1.getInt();
4292 else if (obj1.isReal())
4293 height = (int)obj1.getReal();
4294 else
4295 goto err2;
4296 obj1.free();
4298 if (width < 1 || height < 1)
4299 goto err1;
4301 // image interpolation
4302 dict->lookup("Interpolate", &obj1);
4303 if (obj1.isNull()) {
4304 obj1.free();
4305 dict->lookup("I", &obj1);
4307 if (obj1.isBool())
4308 interpolate = obj1.getBool();
4309 else
4310 interpolate = gFalse;
4311 obj1.free();
4312 maskInterpolate = gFalse;
4314 // image or mask?
4315 dict->lookup("ImageMask", &obj1);
4316 if (obj1.isNull()) {
4317 obj1.free();
4318 dict->lookup("IM", &obj1);
4320 mask = gFalse;
4321 if (obj1.isBool())
4322 mask = obj1.getBool();
4323 else if (!obj1.isNull())
4324 goto err2;
4325 obj1.free();
4327 // bit depth
4328 if (bits == 0) {
4329 dict->lookup("BitsPerComponent", &obj1);
4330 if (obj1.isNull()) {
4331 obj1.free();
4332 dict->lookup("BPC", &obj1);
4334 if (obj1.isInt()) {
4335 bits = obj1.getInt();
4336 } else if (mask) {
4337 bits = 1;
4338 } else {
4339 goto err2;
4341 obj1.free();
4344 // display a mask
4345 if (mask) {
4347 // check for inverted mask
4348 if (bits != 1)
4349 goto err1;
4350 invert = gFalse;
4351 dict->lookup("Decode", &obj1);
4352 if (obj1.isNull()) {
4353 obj1.free();
4354 dict->lookup("D", &obj1);
4356 if (obj1.isArray()) {
4357 obj1.arrayGet(0, &obj2);
4358 // Table 4.39 says /Decode must be [1 0] or [0 1]. Adobe
4359 // accepts [1.0 0.0] as well.
4360 if (obj2.isNum() && obj2.getNum() >= 0.9)
4361 invert = gTrue;
4362 obj2.free();
4363 } else if (!obj1.isNull()) {
4364 goto err2;
4366 obj1.free();
4368 // if drawing is disabled, skip over inline image data
4369 if (!ocState || !out->needNonText()) {
4370 str->reset();
4371 n = height * ((width + 7) / 8);
4372 for (i = 0; i < n; ++i) {
4373 str->getChar();
4375 str->close();
4377 // draw it
4378 } else {
4379 if (state->getFillColorSpace()->getMode() == csPattern) {
4380 doPatternImageMask(ref, str, width, height, invert, inlineImg);
4381 } else {
4382 out->drawImageMask(state, ref, str, width, height, invert, interpolate, inlineImg);
4385 } else {
4387 // get color space and color map
4388 dict->lookup("ColorSpace", &obj1);
4389 if (obj1.isNull()) {
4390 obj1.free();
4391 dict->lookup("CS", &obj1);
4393 if (obj1.isName() && inlineImg) {
4394 res->lookupColorSpace(obj1.getName(), &obj2);
4395 if (!obj2.isNull()) {
4396 obj1.free();
4397 obj1 = obj2;
4398 } else {
4399 obj2.free();
4402 if (!obj1.isNull()) {
4403 Object objIntent;
4404 char *tempIntent = NULL;
4405 dict->lookup("Intent", &objIntent);
4406 if (objIntent.isName()) {
4407 tempIntent = state->getRenderingIntent();
4408 if (tempIntent != NULL) {
4409 tempIntent = strdup(tempIntent);
4411 state->setRenderingIntent(objIntent.getName());
4413 colorSpace = GfxColorSpace::parse(res, &obj1, out, state);
4414 if (objIntent.isName()) {
4415 state->setRenderingIntent(tempIntent);
4416 free(tempIntent);
4418 objIntent.free();
4419 } else if (csMode == streamCSDeviceGray) {
4420 Object objCS;
4421 res->lookupColorSpace("DefaultGray", &objCS);
4422 if (objCS.isNull()) {
4423 colorSpace = new GfxDeviceGrayColorSpace();
4424 } else {
4425 colorSpace = GfxColorSpace::parse(res, &objCS, out, state);
4427 objCS.free();
4428 } else if (csMode == streamCSDeviceRGB) {
4429 Object objCS;
4430 res->lookupColorSpace("DefaultRGB", &objCS);
4431 if (objCS.isNull()) {
4432 colorSpace = new GfxDeviceRGBColorSpace();
4433 } else {
4434 colorSpace = GfxColorSpace::parse(res, &objCS, out, state);
4436 objCS.free();
4437 } else if (csMode == streamCSDeviceCMYK) {
4438 Object objCS;
4439 res->lookupColorSpace("DefaultCMYK", &objCS);
4440 if (objCS.isNull()) {
4441 colorSpace = new GfxDeviceCMYKColorSpace();
4442 } else {
4443 colorSpace = GfxColorSpace::parse(res, &objCS, out, state);
4445 objCS.free();
4446 } else {
4447 colorSpace = NULL;
4449 obj1.free();
4450 if (!colorSpace) {
4451 goto err1;
4453 dict->lookup("Decode", &obj1);
4454 if (obj1.isNull()) {
4455 obj1.free();
4456 dict->lookup("D", &obj1);
4458 if (bits == 0) {
4459 delete colorSpace;
4460 goto err2;
4462 colorMap = new GfxImageColorMap(bits, &obj1, colorSpace);
4463 obj1.free();
4464 if (!colorMap->isOk()) {
4465 delete colorMap;
4466 goto err1;
4469 // get the mask
4470 haveColorKeyMask = haveExplicitMask = haveSoftMask = gFalse;
4471 maskStr = NULL; // make gcc happy
4472 maskWidth = maskHeight = 0; // make gcc happy
4473 maskInvert = gFalse; // make gcc happy
4474 maskColorMap = NULL; // make gcc happy
4475 dict->lookup("Mask", &maskObj);
4476 dict->lookup("SMask", &smaskObj);
4477 if (smaskObj.isStream()) {
4478 // soft mask
4479 if (inlineImg) {
4480 goto err1;
4482 maskStr = smaskObj.getStream();
4483 maskDict = smaskObj.streamGetDict();
4484 maskDict->lookup("Width", &obj1);
4485 if (obj1.isNull()) {
4486 obj1.free();
4487 maskDict->lookup("W", &obj1);
4489 if (!obj1.isInt()) {
4490 goto err2;
4492 maskWidth = obj1.getInt();
4493 obj1.free();
4494 maskDict->lookup("Height", &obj1);
4495 if (obj1.isNull()) {
4496 obj1.free();
4497 maskDict->lookup("H", &obj1);
4499 if (!obj1.isInt()) {
4500 goto err2;
4502 maskHeight = obj1.getInt();
4503 obj1.free();
4504 maskDict->lookup("Interpolate", &obj1);
4505 if (obj1.isNull()) {
4506 obj1.free();
4507 maskDict->lookup("I", &obj1);
4509 if (obj1.isBool())
4510 maskInterpolate = obj1.getBool();
4511 else
4512 maskInterpolate = gFalse;
4513 obj1.free();
4514 maskDict->lookup("BitsPerComponent", &obj1);
4515 if (obj1.isNull()) {
4516 obj1.free();
4517 maskDict->lookup("BPC", &obj1);
4519 if (!obj1.isInt()) {
4520 goto err2;
4522 maskBits = obj1.getInt();
4523 obj1.free();
4524 maskDict->lookup("ColorSpace", &obj1);
4525 if (obj1.isNull()) {
4526 obj1.free();
4527 maskDict->lookup("CS", &obj1);
4529 if (obj1.isName()) {
4530 res->lookupColorSpace(obj1.getName(), &obj2);
4531 if (!obj2.isNull()) {
4532 obj1.free();
4533 obj1 = obj2;
4534 } else {
4535 obj2.free();
4538 maskColorSpace = GfxColorSpace::parse(NULL, &obj1, out, state);
4539 obj1.free();
4540 if (!maskColorSpace || maskColorSpace->getMode() != csDeviceGray) {
4541 goto err1;
4543 maskDict->lookup("Decode", &obj1);
4544 if (obj1.isNull()) {
4545 obj1.free();
4546 maskDict->lookup("D", &obj1);
4548 maskColorMap = new GfxImageColorMap(maskBits, &obj1, maskColorSpace);
4549 obj1.free();
4550 if (!maskColorMap->isOk()) {
4551 delete maskColorMap;
4552 goto err1;
4554 //~ handle the Matte entry
4555 haveSoftMask = gTrue;
4556 } else if (maskObj.isArray()) {
4557 // color key mask
4558 for (i = 0;
4559 i < maskObj.arrayGetLength() && i < 2*gfxColorMaxComps;
4560 ++i) {
4561 maskObj.arrayGet(i, &obj1);
4562 if (obj1.isInt()) {
4563 maskColors[i] = obj1.getInt();
4564 } else if (obj1.isReal()) {
4565 error(errSyntaxError, -1, "Mask entry should be an integer but it's a real, trying to use it");
4566 maskColors[i] = (int) obj1.getReal();
4567 } else {
4568 error(errSyntaxError, -1, "Mask entry should be an integer but it's of type {0:d}", obj1.getType());
4569 obj1.free();
4570 goto err1;
4572 obj1.free();
4574 haveColorKeyMask = gTrue;
4575 } else if (maskObj.isStream()) {
4576 // explicit mask
4577 if (inlineImg) {
4578 goto err1;
4580 maskStr = maskObj.getStream();
4581 maskDict = maskObj.streamGetDict();
4582 maskDict->lookup("Width", &obj1);
4583 if (obj1.isNull()) {
4584 obj1.free();
4585 maskDict->lookup("W", &obj1);
4587 if (!obj1.isInt()) {
4588 goto err2;
4590 maskWidth = obj1.getInt();
4591 obj1.free();
4592 maskDict->lookup("Height", &obj1);
4593 if (obj1.isNull()) {
4594 obj1.free();
4595 maskDict->lookup("H", &obj1);
4597 if (!obj1.isInt()) {
4598 goto err2;
4600 maskHeight = obj1.getInt();
4601 obj1.free();
4602 maskDict->lookup("Interpolate", &obj1);
4603 if (obj1.isNull()) {
4604 obj1.free();
4605 maskDict->lookup("I", &obj1);
4607 if (obj1.isBool())
4608 maskInterpolate = obj1.getBool();
4609 else
4610 maskInterpolate = gFalse;
4611 obj1.free();
4612 maskDict->lookup("ImageMask", &obj1);
4613 if (obj1.isNull()) {
4614 obj1.free();
4615 maskDict->lookup("IM", &obj1);
4617 if (!obj1.isBool() || !obj1.getBool()) {
4618 goto err2;
4620 obj1.free();
4621 maskInvert = gFalse;
4622 maskDict->lookup("Decode", &obj1);
4623 if (obj1.isNull()) {
4624 obj1.free();
4625 maskDict->lookup("D", &obj1);
4627 if (obj1.isArray()) {
4628 obj1.arrayGet(0, &obj2);
4629 // Table 4.39 says /Decode must be [1 0] or [0 1]. Adobe
4630 // accepts [1.0 0.0] as well.
4631 if (obj2.isNum() && obj2.getNum() >= 0.9) {
4632 maskInvert = gTrue;
4634 obj2.free();
4635 } else if (!obj1.isNull()) {
4636 goto err2;
4638 obj1.free();
4639 haveExplicitMask = gTrue;
4642 // if drawing is disabled, skip over inline image data
4643 if (!ocState || !out->needNonText()) {
4644 str->reset();
4645 n = height * ((width * colorMap->getNumPixelComps() *
4646 colorMap->getBits() + 7) / 8);
4647 for (i = 0; i < n; ++i) {
4648 str->getChar();
4650 str->close();
4652 // draw it
4653 } else {
4654 if (haveSoftMask) {
4655 out->drawSoftMaskedImage(state, ref, str, width, height, colorMap, interpolate,
4656 maskStr, maskWidth, maskHeight, maskColorMap, maskInterpolate);
4657 delete maskColorMap;
4658 } else if (haveExplicitMask) {
4659 out->drawMaskedImage(state, ref, str, width, height, colorMap, interpolate,
4660 maskStr, maskWidth, maskHeight, maskInvert, maskInterpolate);
4661 } else {
4662 out->drawImage(state, ref, str, width, height, colorMap, interpolate,
4663 haveColorKeyMask ? maskColors : (int *)NULL, inlineImg);
4666 delete colorMap;
4668 maskObj.free();
4669 smaskObj.free();
4672 if ((i = width * height) > 1000) {
4673 i = 1000;
4675 updateLevel += i;
4677 return;
4679 err2:
4680 obj1.free();
4681 err1:
4682 error(errSyntaxError, getPos(), "Bad image parameters");
4685 GBool Gfx::checkTransparencyGroup(Dict *resDict) {
4686 // check the effect of compositing objects as a group:
4687 // look for ExtGState entries with ca != 1 or CA != 1 or BM != normal
4688 Object extGStates;
4689 GBool transpGroup = gFalse;
4690 double opac;
4692 if (resDict == NULL)
4693 return gFalse;
4694 pushResources(resDict);
4695 resDict->lookup("ExtGState", &extGStates);
4696 if (extGStates.isDict()) {
4697 Dict *dict = extGStates.getDict();
4698 for (int i = 0; i < dict->getLength() && !transpGroup; i++) {
4699 Object obj1, obj2;
4700 GfxBlendMode mode;
4702 if (res->lookupGState(dict->getKey(i), &obj1) && obj1.isDict()) {
4703 if (!obj1.dictLookup("BM", &obj2)->isNull()) {
4704 if (state->parseBlendMode(&obj2, &mode)) {
4705 if (mode != gfxBlendNormal)
4706 transpGroup = gTrue;
4707 } else {
4708 error(errSyntaxError, getPos(), "Invalid blend mode in ExtGState");
4711 obj2.free();
4712 if (obj1.dictLookup("ca", &obj2)->isNum()) {
4713 opac = obj2.getNum();
4714 opac = opac < 0 ? 0 : opac > 1 ? 1 : opac;
4715 if (opac != 1)
4716 transpGroup = gTrue;
4718 obj2.free();
4719 if (obj1.dictLookup("CA", &obj2)->isNum()) {
4720 opac = obj2.getNum();
4721 opac = opac < 0 ? 0 : opac > 1 ? 1 : opac;
4722 if (opac != 1)
4723 transpGroup = gTrue;
4725 obj2.free();
4726 // alpha is shape
4727 if (!transpGroup && obj1.dictLookup("AIS", &obj2)->isBool()) {
4728 transpGroup = obj2.getBool();
4730 obj2.free();
4731 // soft mask
4732 if (!transpGroup && !obj1.dictLookup("SMask", &obj2)->isNull()) {
4733 if (!obj2.isName("None")) {
4734 transpGroup = gTrue;
4737 obj2.free();
4739 obj1.free();
4742 extGStates.free();
4743 popResources();
4744 return transpGroup;
4747 void Gfx::doForm(Object *str) {
4748 Dict *dict;
4749 GBool transpGroup, isolated, knockout;
4750 GfxColorSpace *blendingColorSpace;
4751 Object matrixObj, bboxObj;
4752 double m[6], bbox[4];
4753 Object resObj;
4754 Dict *resDict;
4755 GBool ocSaved;
4756 Object obj1, obj2, obj3;
4757 int i;
4759 // check for excessive recursion
4760 if (formDepth > 100) {
4761 return;
4764 // get stream dict
4765 dict = str->streamGetDict();
4767 // check form type
4768 dict->lookup("FormType", &obj1);
4769 if (!(obj1.isNull() || (obj1.isInt() && obj1.getInt() == 1))) {
4770 error(errSyntaxError, getPos(), "Unknown form type");
4772 obj1.free();
4774 // check for optional content key
4775 ocSaved = ocState;
4776 dict->lookupNF("OC", &obj1);
4777 if (catalog->getOptContentConfig() && !catalog->getOptContentConfig()->optContentIsVisible(&obj1)) {
4778 obj1.free();
4779 if (out->needCharCount()) {
4780 ocState = gFalse;
4781 } else {
4782 return;
4785 obj1.free();
4787 // get bounding box
4788 dict->lookup("BBox", &bboxObj);
4789 if (!bboxObj.isArray()) {
4790 bboxObj.free();
4791 error(errSyntaxError, getPos(), "Bad form bounding box");
4792 ocState = ocSaved;
4793 return;
4795 for (i = 0; i < 4; ++i) {
4796 bboxObj.arrayGet(i, &obj1);
4797 if (likely(obj1.isNum())) {
4798 bbox[i] = obj1.getNum();
4799 obj1.free();
4800 } else {
4801 obj1.free();
4802 error(errSyntaxError, getPos(), "Bad form bounding box value");
4803 return;
4806 bboxObj.free();
4808 // get matrix
4809 dict->lookup("Matrix", &matrixObj);
4810 if (matrixObj.isArray()) {
4811 for (i = 0; i < 6; ++i) {
4812 matrixObj.arrayGet(i, &obj1);
4813 if (likely(obj1.isNum())) m[i] = obj1.getNum();
4814 else m[i] = 0;
4815 obj1.free();
4817 } else {
4818 m[0] = 1; m[1] = 0;
4819 m[2] = 0; m[3] = 1;
4820 m[4] = 0; m[5] = 0;
4822 matrixObj.free();
4824 // get resources
4825 dict->lookup("Resources", &resObj);
4826 resDict = resObj.isDict() ? resObj.getDict() : (Dict *)NULL;
4828 // check for a transparency group
4829 transpGroup = isolated = knockout = gFalse;
4830 blendingColorSpace = NULL;
4831 if (dict->lookup("Group", &obj1)->isDict()) {
4832 if (obj1.dictLookup("S", &obj2)->isName("Transparency")) {
4833 if (!obj1.dictLookup("CS", &obj3)->isNull()) {
4834 blendingColorSpace = GfxColorSpace::parse(res, &obj3, out, state);
4836 obj3.free();
4837 if (obj1.dictLookup("I", &obj3)->isBool()) {
4838 isolated = obj3.getBool();
4840 obj3.free();
4841 if (obj1.dictLookup("K", &obj3)->isBool()) {
4842 knockout = obj3.getBool();
4844 obj3.free();
4845 transpGroup = isolated || out->checkTransparencyGroup(state, knockout) || checkTransparencyGroup(resDict);
4847 obj2.free();
4849 obj1.free();
4851 // draw it
4852 ++formDepth;
4853 drawForm(str, resDict, m, bbox,
4854 transpGroup, gFalse, blendingColorSpace, isolated, knockout);
4855 --formDepth;
4857 if (blendingColorSpace) {
4858 delete blendingColorSpace;
4860 resObj.free();
4862 ocState = ocSaved;
4865 void Gfx::drawForm(Object *str, Dict *resDict, double *matrix, double *bbox,
4866 GBool transpGroup, GBool softMask,
4867 GfxColorSpace *blendingColorSpace,
4868 GBool isolated, GBool knockout,
4869 GBool alpha, Function *transferFunc,
4870 GfxColor *backdropColor) {
4871 Parser *oldParser;
4872 GfxState *savedState;
4873 double oldBaseMatrix[6];
4874 int i;
4876 // push new resources on stack
4877 pushResources(resDict);
4879 // save current graphics state
4880 savedState = saveStateStack();
4882 // kill any pre-existing path
4883 state->clearPath();
4885 // save current parser
4886 oldParser = parser;
4888 // set form transformation matrix
4889 state->concatCTM(matrix[0], matrix[1], matrix[2],
4890 matrix[3], matrix[4], matrix[5]);
4891 out->updateCTM(state, matrix[0], matrix[1], matrix[2],
4892 matrix[3], matrix[4], matrix[5]);
4894 // set form bounding box
4895 state->moveTo(bbox[0], bbox[1]);
4896 state->lineTo(bbox[2], bbox[1]);
4897 state->lineTo(bbox[2], bbox[3]);
4898 state->lineTo(bbox[0], bbox[3]);
4899 state->closePath();
4900 state->clip();
4901 out->clip(state);
4902 state->clearPath();
4904 if (softMask || transpGroup) {
4905 if (state->getBlendMode() != gfxBlendNormal) {
4906 state->setBlendMode(gfxBlendNormal);
4907 out->updateBlendMode(state);
4909 if (state->getFillOpacity() != 1) {
4910 state->setFillOpacity(1);
4911 out->updateFillOpacity(state);
4913 if (state->getStrokeOpacity() != 1) {
4914 state->setStrokeOpacity(1);
4915 out->updateStrokeOpacity(state);
4917 out->clearSoftMask(state);
4918 out->beginTransparencyGroup(state, bbox, blendingColorSpace,
4919 isolated, knockout, softMask);
4922 // set new base matrix
4923 for (i = 0; i < 6; ++i) {
4924 oldBaseMatrix[i] = baseMatrix[i];
4925 baseMatrix[i] = state->getCTM()[i];
4928 GfxState *stateBefore = state;
4930 // draw the form
4931 display(str, gFalse);
4933 if (stateBefore != state) {
4934 if (state->isParentState(stateBefore)) {
4935 error(errSyntaxError, -1, "There's a form with more q than Q, trying to fix");
4936 while (stateBefore != state) {
4937 restoreState();
4939 } else {
4940 error(errSyntaxError, -1, "There's a form with more Q than q");
4944 if (softMask || transpGroup) {
4945 out->endTransparencyGroup(state);
4948 // restore base matrix
4949 for (i = 0; i < 6; ++i) {
4950 baseMatrix[i] = oldBaseMatrix[i];
4953 // restore parser
4954 parser = oldParser;
4956 // restore graphics state
4957 restoreStateStack(savedState);
4959 // pop resource stack
4960 popResources();
4962 if (softMask) {
4963 out->setSoftMask(state, bbox, alpha, transferFunc, backdropColor);
4964 } else if (transpGroup) {
4965 out->paintTransparencyGroup(state, bbox);
4968 return;
4971 //------------------------------------------------------------------------
4972 // in-line image operators
4973 //------------------------------------------------------------------------
4975 void Gfx::opBeginImage(Object args[], int numArgs) {
4976 Stream *str;
4977 int c1, c2;
4979 // NB: this function is run even if ocState is false -- doImage() is
4980 // responsible for skipping over the inline image data
4982 // build dict/stream
4983 str = buildImageStream();
4985 // display the image
4986 if (str) {
4987 doImage(NULL, str, gTrue);
4989 // skip 'EI' tag
4990 c1 = str->getUndecodedStream()->getChar();
4991 c2 = str->getUndecodedStream()->getChar();
4992 while (!(c1 == 'E' && c2 == 'I') && c2 != EOF) {
4993 c1 = c2;
4994 c2 = str->getUndecodedStream()->getChar();
4996 delete str;
5000 Stream *Gfx::buildImageStream() {
5001 Object dict;
5002 Object obj;
5003 char *key;
5004 Stream *str;
5006 // build dictionary
5007 dict.initDict(xref);
5008 parser->getObj(&obj);
5009 while (!obj.isCmd("ID") && !obj.isEOF()) {
5010 if (!obj.isName()) {
5011 error(errSyntaxError, getPos(), "Inline image dictionary key must be a name object");
5012 obj.free();
5013 } else {
5014 key = copyString(obj.getName());
5015 obj.free();
5016 parser->getObj(&obj);
5017 if (obj.isEOF() || obj.isError()) {
5018 gfree(key);
5019 break;
5021 dict.dictAdd(key, &obj);
5023 parser->getObj(&obj);
5025 if (obj.isEOF()) {
5026 error(errSyntaxError, getPos(), "End of file in inline image");
5027 obj.free();
5028 dict.free();
5029 return NULL;
5031 obj.free();
5033 // make stream
5034 if (parser->getStream()) {
5035 str = new EmbedStream(parser->getStream(), &dict, gFalse, 0);
5036 str = str->addFilters(&dict);
5037 } else {
5038 str = NULL;
5039 dict.free();
5042 return str;
5045 void Gfx::opImageData(Object args[], int numArgs) {
5046 error(errInternal, getPos(), "Got 'ID' operator");
5049 void Gfx::opEndImage(Object args[], int numArgs) {
5050 error(errInternal, getPos(), "Got 'EI' operator");
5053 //------------------------------------------------------------------------
5054 // type 3 font operators
5055 //------------------------------------------------------------------------
5057 void Gfx::opSetCharWidth(Object args[], int numArgs) {
5058 out->type3D0(state, args[0].getNum(), args[1].getNum());
5061 void Gfx::opSetCacheDevice(Object args[], int numArgs) {
5062 out->type3D1(state, args[0].getNum(), args[1].getNum(),
5063 args[2].getNum(), args[3].getNum(),
5064 args[4].getNum(), args[5].getNum());
5067 //------------------------------------------------------------------------
5068 // compatibility operators
5069 //------------------------------------------------------------------------
5071 void Gfx::opBeginIgnoreUndef(Object args[], int numArgs) {
5072 ++ignoreUndef;
5075 void Gfx::opEndIgnoreUndef(Object args[], int numArgs) {
5076 if (ignoreUndef > 0)
5077 --ignoreUndef;
5080 //------------------------------------------------------------------------
5081 // marked content operators
5082 //------------------------------------------------------------------------
5084 enum GfxMarkedContentKind {
5085 gfxMCOptionalContent,
5086 gfxMCActualText,
5087 gfxMCOther
5090 struct MarkedContentStack {
5091 GfxMarkedContentKind kind;
5092 GBool ocSuppressed; // are we ignoring content based on OptionalContent?
5093 MarkedContentStack *next; // next object on stack
5096 void Gfx::popMarkedContent() {
5097 MarkedContentStack *mc = mcStack;
5098 mcStack = mc->next;
5099 delete mc;
5102 void Gfx::pushMarkedContent() {
5103 MarkedContentStack *mc = new MarkedContentStack();
5104 mc->ocSuppressed = gFalse;
5105 mc->kind = gfxMCOther;
5106 mc->next = mcStack;
5107 mcStack = mc;
5110 GBool Gfx::contentIsHidden() {
5111 MarkedContentStack *mc = mcStack;
5112 bool hidden = mc && mc->ocSuppressed;
5113 while (!hidden && mc && mc->next) {
5114 mc = mc->next;
5115 hidden = mc->ocSuppressed;
5117 return hidden;
5120 void Gfx::opBeginMarkedContent(Object args[], int numArgs) {
5121 // push a new stack entry
5122 pushMarkedContent();
5124 OCGs *contentConfig = catalog->getOptContentConfig();
5125 char* name0 = args[0].getName();
5126 if ( strncmp( name0, "OC", 2) == 0 && contentConfig) {
5127 if ( numArgs >= 2 ) {
5128 if (!args[1].isName()) {
5129 error(errSyntaxError, getPos(), "Unexpected MC Type: {0:d}", args[1].getType());
5131 char* name1 = args[1].getName();
5132 Object markedContent;
5133 MarkedContentStack *mc = mcStack;
5134 mc->kind = gfxMCOptionalContent;
5135 if ( res->lookupMarkedContentNF( name1, &markedContent ) ) {
5136 bool visible = contentConfig->optContentIsVisible(&markedContent);
5137 mc->ocSuppressed = !(visible);
5138 } else {
5139 error(errSyntaxError, getPos(), "DID NOT find {0:s}", name1);
5141 markedContent.free();
5142 } else {
5143 error(errSyntaxError, getPos(), "insufficient arguments for Marked Content");
5145 } else if (args[0].isName("Span") && numArgs == 2 && args[1].isDict()) {
5146 Object obj;
5147 if (args[1].dictLookup("ActualText", &obj)->isString()) {
5148 out->beginActualText(state, obj.getString());
5149 MarkedContentStack *mc = mcStack;
5150 mc->kind = gfxMCActualText;
5152 obj.free();
5155 if (printCommands) {
5156 printf(" marked content: %s ", args[0].getName());
5157 if (numArgs == 2)
5158 args[1].print(stdout);
5159 printf("\n");
5160 fflush(stdout);
5162 ocState = !contentIsHidden();
5164 if (numArgs == 2 && args[1].isDict()) {
5165 out->beginMarkedContent(args[0].getName(), args[1].getDict());
5166 } else if(numArgs == 1) {
5167 out->beginMarkedContent(args[0].getName(), NULL);
5171 void Gfx::opEndMarkedContent(Object args[], int numArgs) {
5172 if (!mcStack) {
5173 error(errSyntaxWarning, getPos(), "Mismatched EMC operator");
5174 return;
5177 MarkedContentStack *mc = mcStack;
5178 GfxMarkedContentKind mcKind = mc->kind;
5180 // pop the stack
5181 popMarkedContent();
5183 if (mcKind == gfxMCActualText)
5184 out->endActualText(state);
5185 ocState = !contentIsHidden();
5187 out->endMarkedContent(state);
5190 void Gfx::opMarkPoint(Object args[], int numArgs) {
5191 if (printCommands) {
5192 printf(" mark point: %s ", args[0].getName());
5193 if (numArgs == 2)
5194 args[1].print(stdout);
5195 printf("\n");
5196 fflush(stdout);
5199 if(numArgs == 2 && args[1].isDict()) {
5200 out->markPoint(args[0].getName(),args[1].getDict());
5201 } else {
5202 out->markPoint(args[0].getName());
5207 //------------------------------------------------------------------------
5208 // misc
5209 //------------------------------------------------------------------------
5211 struct GfxStackStateSaver {
5212 GfxStackStateSaver(Gfx *gfx) : gfx(gfx) {
5213 gfx->saveState();
5216 ~GfxStackStateSaver() {
5217 gfx->restoreState();
5220 Gfx * const gfx;
5223 void Gfx::drawAnnot(Object *str, AnnotBorder *border, AnnotColor *aColor,
5224 double xMin, double yMin, double xMax, double yMax, int rotate) {
5225 Dict *dict, *resDict;
5226 Object matrixObj, bboxObj, resObj, obj1;
5227 double formXMin, formYMin, formXMax, formYMax;
5228 double x, y, sx, sy, tx, ty;
5229 double m[6], bbox[4];
5230 double r, g, b;
5231 GfxColor color;
5232 double *dash, *dash2;
5233 int dashLength;
5234 int i;
5236 // this function assumes that we are in the default user space,
5237 // i.e., baseMatrix = ctm
5239 // if the bounding box has zero width or height, don't draw anything
5240 // at all
5241 if (xMin == xMax || yMin == yMax) {
5242 return;
5245 // saves gfx state and automatically restores it on return
5246 GfxStackStateSaver stackStateSaver(this);
5248 // Rotation around the topleft corner (for the NoRotate flag)
5249 if (rotate != 0) {
5250 const double angle_rad = rotate * M_PI / 180;
5251 const double c = cos(angle_rad);
5252 const double s = sin(angle_rad);
5254 // (xMin, yMax) is the pivot
5255 const double unrotateMTX[6] = {
5256 +c, -s,
5257 +s, +c,
5258 -c*xMin - s*yMax + xMin, -c*yMax + s*xMin + yMax
5261 state->concatCTM(unrotateMTX[0], unrotateMTX[1], unrotateMTX[2],
5262 unrotateMTX[3], unrotateMTX[4], unrotateMTX[5]);
5263 out->updateCTM(state, unrotateMTX[0], unrotateMTX[1], unrotateMTX[2],
5264 unrotateMTX[3], unrotateMTX[4], unrotateMTX[5]);
5267 // draw the appearance stream (if there is one)
5268 if (str->isStream()) {
5270 // get stream dict
5271 dict = str->streamGetDict();
5273 // get the form bounding box
5274 dict->lookup("BBox", &bboxObj);
5275 if (!bboxObj.isArray()) {
5276 bboxObj.free();
5277 error(errSyntaxError, getPos(), "Bad form bounding box");
5278 return;
5280 for (i = 0; i < 4; ++i) {
5281 bboxObj.arrayGet(i, &obj1);
5282 if (likely(obj1.isNum())) {
5283 bbox[i] = obj1.getNum();
5284 obj1.free();
5285 } else {
5286 obj1.free();
5287 bboxObj.free();
5288 error(errSyntaxError, getPos(), "Bad form bounding box value");
5289 return;
5292 bboxObj.free();
5294 // get the form matrix
5295 dict->lookup("Matrix", &matrixObj);
5296 if (matrixObj.isArray() && matrixObj.arrayGetLength() >= 6) {
5297 for (i = 0; i < 6; ++i) {
5298 matrixObj.arrayGet(i, &obj1);
5299 if (likely(obj1.isNum())) {
5300 m[i] = obj1.getNum();
5301 obj1.free();
5302 } else {
5303 obj1.free();
5304 matrixObj.free();
5305 error(errSyntaxError, getPos(), "Bad form matrix");
5306 return;
5309 } else {
5310 m[0] = 1; m[1] = 0;
5311 m[2] = 0; m[3] = 1;
5312 m[4] = 0; m[5] = 0;
5314 matrixObj.free();
5316 // transform the four corners of the form bbox to default user
5317 // space, and construct the transformed bbox
5318 x = bbox[0] * m[0] + bbox[1] * m[2] + m[4];
5319 y = bbox[0] * m[1] + bbox[1] * m[3] + m[5];
5320 formXMin = formXMax = x;
5321 formYMin = formYMax = y;
5322 x = bbox[0] * m[0] + bbox[3] * m[2] + m[4];
5323 y = bbox[0] * m[1] + bbox[3] * m[3] + m[5];
5324 if (x < formXMin) {
5325 formXMin = x;
5326 } else if (x > formXMax) {
5327 formXMax = x;
5329 if (y < formYMin) {
5330 formYMin = y;
5331 } else if (y > formYMax) {
5332 formYMax = y;
5334 x = bbox[2] * m[0] + bbox[1] * m[2] + m[4];
5335 y = bbox[2] * m[1] + bbox[1] * m[3] + m[5];
5336 if (x < formXMin) {
5337 formXMin = x;
5338 } else if (x > formXMax) {
5339 formXMax = x;
5341 if (y < formYMin) {
5342 formYMin = y;
5343 } else if (y > formYMax) {
5344 formYMax = y;
5346 x = bbox[2] * m[0] + bbox[3] * m[2] + m[4];
5347 y = bbox[2] * m[1] + bbox[3] * m[3] + m[5];
5348 if (x < formXMin) {
5349 formXMin = x;
5350 } else if (x > formXMax) {
5351 formXMax = x;
5353 if (y < formYMin) {
5354 formYMin = y;
5355 } else if (y > formYMax) {
5356 formYMax = y;
5359 // construct a mapping matrix, [sx 0 0], which maps the transformed
5360 // [0 sy 0]
5361 // [tx ty 1]
5362 // bbox to the annotation rectangle
5363 if (formXMin == formXMax) {
5364 // this shouldn't happen
5365 sx = 1;
5366 } else {
5367 sx = (xMax - xMin) / (formXMax - formXMin);
5369 if (formYMin == formYMax) {
5370 // this shouldn't happen
5371 sy = 1;
5372 } else {
5373 sy = (yMax - yMin) / (formYMax - formYMin);
5375 tx = -formXMin * sx + xMin;
5376 ty = -formYMin * sy + yMin;
5378 // the final transform matrix is (form matrix) * (mapping matrix)
5379 m[0] *= sx;
5380 m[1] *= sy;
5381 m[2] *= sx;
5382 m[3] *= sy;
5383 m[4] = m[4] * sx + tx;
5384 m[5] = m[5] * sy + ty;
5386 // get the resources
5387 dict->lookup("Resources", &resObj);
5388 resDict = resObj.isDict() ? resObj.getDict() : (Dict *)NULL;
5390 // draw it
5391 drawForm(str, resDict, m, bbox);
5393 resObj.free();
5396 // draw the border
5397 if (border && border->getWidth() > 0) {
5398 if (state->getStrokeColorSpace()->getMode() != csDeviceRGB) {
5399 state->setStrokePattern(NULL);
5400 state->setStrokeColorSpace(new GfxDeviceRGBColorSpace());
5401 out->updateStrokeColorSpace(state);
5403 if (aColor && (aColor->getSpace() == AnnotColor::colorRGB)) {
5404 const double *values = aColor->getValues();
5405 r = values[0];
5406 g = values[1];
5407 b = values[2];
5408 } else {
5409 r = g = b = 0;
5411 color.c[0] = dblToCol(r);
5412 color.c[1] = dblToCol(g);
5413 color.c[2] = dblToCol(b);
5414 state->setStrokeColor(&color);
5415 out->updateStrokeColor(state);
5416 state->setLineWidth(border->getWidth());
5417 out->updateLineWidth(state);
5418 dashLength = border->getDashLength();
5419 dash = border->getDash();
5420 if (border->getStyle() == AnnotBorder::borderDashed && dashLength > 0) {
5421 dash2 = (double *)gmallocn(dashLength, sizeof(double));
5422 memcpy(dash2, dash, dashLength * sizeof(double));
5423 state->setLineDash(dash2, dashLength, 0);
5424 out->updateLineDash(state);
5426 //~ this doesn't currently handle the beveled and engraved styles
5427 state->clearPath();
5428 state->moveTo(xMin, yMin);
5429 state->lineTo(xMax, yMin);
5430 if (border->getStyle() != AnnotBorder::borderUnderlined) {
5431 state->lineTo(xMax, yMax);
5432 state->lineTo(xMin, yMax);
5433 state->closePath();
5435 out->stroke(state);
5439 int Gfx::bottomGuard() {
5440 return stateGuards[stateGuards.size()-1];
5443 void Gfx::pushStateGuard() {
5444 stateGuards.push_back(stackHeight);
5447 void Gfx::popStateGuard() {
5448 while (stackHeight > bottomGuard() && state->hasSaves())
5449 restoreState();
5450 stateGuards.pop_back();
5453 void Gfx::saveState() {
5454 out->saveState(state);
5455 state = state->save();
5456 stackHeight++;
5459 void Gfx::restoreState() {
5460 if (stackHeight <= bottomGuard() || !state->hasSaves()) {
5461 error(errSyntaxError, -1, "Restoring state when no valid states to pop");
5462 commandAborted = gTrue;
5463 return;
5465 state = state->restore();
5466 out->restoreState(state);
5467 stackHeight--;
5470 // Create a new state stack, and initialize it with a copy of the
5471 // current state.
5472 GfxState *Gfx::saveStateStack() {
5473 GfxState *oldState;
5475 out->saveState(state);
5476 oldState = state;
5477 state = state->copy(gTrue);
5478 return oldState;
5481 // Switch back to the previous state stack.
5482 void Gfx::restoreStateStack(GfxState *oldState) {
5483 while (state->hasSaves()) {
5484 restoreState();
5486 delete state;
5487 state = oldState;
5488 out->restoreState(state);
5491 void Gfx::pushResources(Dict *resDict) {
5492 res = new GfxResources(xref, resDict, res);
5495 void Gfx::popResources() {
5496 GfxResources *resPtr;
5498 resPtr = res->getNext();
5499 delete res;
5500 res = resPtr;