1 //========================================================================
5 // Copyright 1996-2003 Glyph & Cog, LLC
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 //========================================================================
49 #ifdef USE_GCC_PRAGMAS
50 #pragma implementation
59 #include "goo/GooTimer.h"
60 #include "goo/GooHash.h"
61 #include "GlobalParams.h"
62 #include "CharTypes.h"
73 #include "OutputDev.h"
78 #include "ProfileData.h"
80 #include "OptionalContent.h"
82 // the MSVC math.h doesn't define this
84 #define M_PI 3.14159265358979323846
87 //------------------------------------------------------------------------
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
114 #define gouraudMaxDepth 6
116 // Max delta allowed in any color component for a Gouraud triangle
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
129 #define patchColorDelta (dblToCol((3. / 256.0)))
131 //------------------------------------------------------------------------
133 //------------------------------------------------------------------------
135 #ifdef _MSC_VER // this works around a bug in the VC7 compiler
136 # pragma optimize("",off)
139 Operator
Gfx::opTab
[] = {
140 {"\"", 3, {tchkNum
, tchkNum
, tchkString
},
141 &Gfx::opMoveSetShowText
},
142 {"'", 1, {tchkString
},
143 &Gfx::opMoveShowText
},
146 {"B*", 0, {tchkNone
},
147 &Gfx::opEOFillStroke
},
148 {"BDC", 2, {tchkName
, tchkProps
},
149 &Gfx::opBeginMarkedContent
},
150 {"BI", 0, {tchkNone
},
152 {"BMC", 1, {tchkName
},
153 &Gfx::opBeginMarkedContent
},
154 {"BT", 0, {tchkNone
},
156 {"BX", 0, {tchkNone
},
157 &Gfx::opBeginIgnoreUndef
},
158 {"CS", 1, {tchkName
},
159 &Gfx::opSetStrokeColorSpace
},
160 {"DP", 2, {tchkName
, tchkProps
},
162 {"Do", 1, {tchkName
},
164 {"EI", 0, {tchkNone
},
166 {"EMC", 0, {tchkNone
},
167 &Gfx::opEndMarkedContent
},
168 {"ET", 0, {tchkNone
},
170 {"EX", 0, {tchkNone
},
171 &Gfx::opEndIgnoreUndef
},
175 &Gfx::opSetStrokeGray
},
176 {"ID", 0, {tchkNone
},
180 {"K", 4, {tchkNum
, tchkNum
, tchkNum
, tchkNum
},
181 &Gfx::opSetStrokeCMYKColor
},
183 &Gfx::opSetMiterLimit
},
184 {"MP", 1, {tchkName
},
188 {"RG", 3, {tchkNum
, tchkNum
, tchkNum
},
189 &Gfx::opSetStrokeRGBColor
},
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
,
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
},
211 &Gfx::opSetTextLeading
},
213 &Gfx::opSetCharSpacing
},
214 {"Td", 2, {tchkNum
, tchkNum
},
216 {"Tf", 2, {tchkName
, tchkNum
},
218 {"Tj", 1, {tchkString
},
220 {"Tm", 6, {tchkNum
, tchkNum
, tchkNum
, tchkNum
,
222 &Gfx::opSetTextMatrix
},
224 &Gfx::opSetTextRender
},
226 &Gfx::opSetTextRise
},
228 &Gfx::opSetWordSpacing
},
230 &Gfx::opSetHorizScaling
},
233 {"W*", 0, {tchkNone
},
236 &Gfx::opCloseFillStroke
},
237 {"b*", 0, {tchkNone
},
238 &Gfx::opCloseEOFillStroke
},
239 {"c", 6, {tchkNum
, tchkNum
, tchkNum
, tchkNum
,
242 {"cm", 6, {tchkNum
, tchkNum
, tchkNum
, tchkNum
,
245 {"cs", 1, {tchkName
},
246 &Gfx::opSetFillColorSpace
},
247 {"d", 2, {tchkArray
, tchkNum
},
249 {"d0", 2, {tchkNum
, tchkNum
},
250 &Gfx::opSetCharWidth
},
251 {"d1", 6, {tchkNum
, tchkNum
, tchkNum
, tchkNum
,
253 &Gfx::opSetCacheDevice
},
256 {"f*", 0, {tchkNone
},
259 &Gfx::opSetFillGray
},
260 {"gs", 1, {tchkName
},
261 &Gfx::opSetExtGState
},
267 &Gfx::opSetLineJoin
},
268 {"k", 4, {tchkNum
, tchkNum
, tchkNum
, tchkNum
},
269 &Gfx::opSetFillCMYKColor
},
270 {"l", 2, {tchkNum
, tchkNum
},
272 {"m", 2, {tchkNum
, tchkNum
},
278 {"re", 4, {tchkNum
, tchkNum
, tchkNum
, tchkNum
},
280 {"rg", 3, {tchkNum
, tchkNum
, tchkNum
},
281 &Gfx::opSetFillRGBColor
},
282 {"ri", 1, {tchkName
},
283 &Gfx::opSetRenderingIntent
},
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
,
297 &Gfx::opSetFillColorN
},
298 {"sh", 1, {tchkName
},
300 {"v", 4, {tchkNum
, tchkNum
, tchkNum
, tchkNum
},
303 &Gfx::opSetLineWidth
},
304 {"y", 4, {tchkNum
, tchkNum
, tchkNum
, tchkNum
},
308 #ifdef _MSC_VER // this works around a bug in the VC7 compiler
309 # pragma optimize("",on)
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
) {
323 //------------------------------------------------------------------------
325 //------------------------------------------------------------------------
327 GfxResources::GfxResources(XRef
*xref
, Dict
*resDictA
, GfxResources
*nextA
) :
328 gStateCache(2, xref
) {
334 // build font dictionary
335 Dict
*resDict
= resDictA
->copy(xref
);
337 resDict
->lookupNF("Font", &obj1
);
339 obj1
.fetch(xref
, &obj2
);
342 fonts
= new GfxFontDict(xref
, &r
, obj2
.getDict());
345 } else if (obj1
.isDict()) {
346 fonts
= new GfxFontDict(xref
, NULL
, obj1
.getDict());
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
);
372 colorSpaceDict
.initNull();
373 patternDict
.initNull();
374 shadingDict
.initNull();
375 gStateDict
.initNull();
376 propertiesDict
.initNull();
382 GfxResources::~GfxResources() {
387 colorSpaceDict
.free();
391 propertiesDict
.free();
394 GfxFont
*GfxResources::lookupFont(char *name
) {
396 GfxResources
*resPtr
;
398 for (resPtr
= this; resPtr
; resPtr
= resPtr
->next
) {
400 if ((font
= resPtr
->fonts
->lookup(name
)))
404 error(errSyntaxError
, -1, "Unknown font tag '{0:s}'", name
);
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())
418 error(errSyntaxError
, -1, "XObject '{0:s}' is unknown", name
);
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())
432 error(errSyntaxError
, -1, "XObject '{0:s}' is unknown", name
);
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())
446 error(errSyntaxError
, -1, "Marked Content '{0:s}' is unknown", name
);
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()) {
464 GfxPattern
*GfxResources::lookupPattern(char *name
, OutputDev
*out
, GfxState
*state
) {
465 GfxResources
*resPtr
;
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
);
479 error(errSyntaxError
, -1, "Unknown pattern '{0:s}'", name
);
483 GfxShading
*GfxResources::lookupShading(char *name
, OutputDev
*out
, GfxState
*state
) {
484 GfxResources
*resPtr
;
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
);
498 error(errSyntaxError
, -1, "ExtGState '{0:s}' is unknown", name
);
502 GBool
GfxResources::lookupGState(char *name
, Object
*obj
) {
503 if (!lookupGStateNF(name
, obj
))
509 const Ref ref
= obj
->getRef();
510 if (!gStateCache
.lookup(ref
, obj
)->isNull())
514 gStateCache
.put(ref
)->copy(obj
);
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()) {
529 error(errSyntaxError
, -1, "ExtGState '{0:s}' is unknown", name
);
533 //------------------------------------------------------------------------
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
)
546 xref
= (xrefA
== NULL
) ? doc
->getXRef() : xrefA
;
547 catalog
= doc
->getCatalog();
549 printCommands
= globalParams
->getPrintCommands();
550 profileCommands
= globalParams
->getProfileCommands();
554 // start the resource stack
555 res
= new GfxResources(xref
, resDict
, NULL
);
559 state
= new GfxState(hDPI
, vDPI
, box
, rotate
, out
->upsideDown());
562 fontChanged
= gFalse
;
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
];
574 abortCheckCbk
= abortCheckCbkA
;
575 abortCheckCbkData
= abortCheckCbkDataA
;
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
);
589 initDisplayProfile();
593 Gfx::Gfx(PDFDoc
*docA
, OutputDev
*outA
, Dict
*resDict
,
594 PDFRectangle
*box
, PDFRectangle
*cropBox
,
595 GBool (*abortCheckCbkA
)(void *data
),
596 void *abortCheckCbkDataA
, XRef
*xrefA
)
601 xref
= (xrefA
== NULL
) ? doc
->getXRef() : xrefA
;
602 catalog
= doc
->getCatalog();
604 printCommands
= globalParams
->getPrintCommands();
605 profileCommands
= globalParams
->getProfileCommands();
609 // start the resource stack
610 res
= new GfxResources(xref
, resDict
, NULL
);
614 state
= new GfxState(72, 72, box
, 0, gFalse
);
617 fontChanged
= gFalse
;
620 for (i
= 0; i
< 6; ++i
) {
621 baseMatrix
[i
] = state
->getCTM()[i
];
626 abortCheckCbk
= abortCheckCbkA
;
627 abortCheckCbkData
= abortCheckCbkDataA
;
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
);
641 initDisplayProfile();
651 #define LCMS_FLAGS cmsFLAGS_NOOPTIMIZE
654 void Gfx::initDisplayProfile() {
656 xref
->getCatalog(&catDict
);
657 if (catDict
.isDict()) {
658 Object outputIntents
;
659 catDict
.dictLookup("OutputIntents", &outputIntents
);
660 if (outputIntents
.isArray() && outputIntents
.arrayGetLength() == 1) {
662 outputIntents
.arrayGet(0, &firstElement
);
663 if (firstElement
.isDict()) {
665 firstElement
.dictLookup("DestOutputProfile", &profile
);
666 if (profile
.isStream()) {
667 Stream
*iccStream
= profile
.getStream();
669 Guchar
*profBuf
= iccStream
->toUnsignedChars(&length
, 65536, 65536);
670 cmsHPROFILE hp
= cmsOpenProfileFromMem(profBuf
,length
);
672 error(errSyntaxWarning
, -1, "read ICCBased color space profile error");
674 state
->setDisplayProfile(hp
);
682 outputIntents
.free();
690 while (stateGuards
.size()) {
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.");
710 void Gfx::display(Object
*obj
, GBool topLevel
) {
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");
724 } else if (!obj
->isStream()) {
725 error(errSyntaxError
, -1, "Weird page contents");
728 parser
= new Parser(xref
, new Lexer(xref
, obj
), gFalse
);
734 void Gfx::go(GBool topLevel
) {
736 Object args
[maxArgs
];
740 // scan a sequence of objects
742 updateLevel
= 1; // make sure even empty pages trigger a call to dump()
745 parser
->getObj(&obj
);
746 while (!obj
.isEOF()) {
747 commandAborted
= gFalse
;
749 // got a command - execute it
753 for (i
= 0; i
< numArgs
; ++i
) {
755 args
[i
].print(stdout
);
763 execOp(&obj
, args
, numArgs
);
765 // Update the profile information
766 if (profileCommands
) {
769 hash
= out
->getProfileHash ();
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 ());
785 for (i
= 0; i
< numArgs
; ++i
)
789 // periodically update display
790 if (++updateLevel
>= 20000) {
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
;
803 // check for an abort
805 if (updateLevel
- lastAbortCheck
> 10) {
806 if ((*abortCheckCbk
)(abortCheckCbkData
)) {
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
819 error(errSyntaxError
, getPos(), "Too many args in content stream");
821 printf("throwing away arg: ");
829 // grab the next object
830 parser
->getObj(&obj
);
834 // args at end with no command
836 error(errSyntaxError
, getPos(), "Leftover args in content stream");
838 printf("%d leftovers:", numArgs
);
839 for (i
= 0; i
< numArgs
; ++i
) {
841 args
[i
].print(stdout
);
846 for (i
= 0; i
< numArgs
; ++i
)
853 if (topLevel
&& updateLevel
> 0) {
858 void Gfx::execOp(Object
*cmd
, Object args
[], int numArgs
) {
865 name
= cmd
->getCmd();
866 if (!(op
= findOp(name
))) {
867 if (ignoreUndef
== 0)
868 error(errSyntaxError
, getPos(), "Unknown operator '{0:s}'", name
);
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
;
880 if (numArgs
> op
->numArgs
) {
882 error(errSyntaxWarning
, getPos(),
883 "Too many ({0:d}) args to '{1:s}' operator", numArgs
, name
);
885 argPtr
+= numArgs
- op
->numArgs
;
886 numArgs
= op
->numArgs
;
889 if (numArgs
> -op
->numArgs
) {
890 error(errSyntaxError
, getPos(), "Too many ({0:d}) args to '{1:s}' operator",
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());
904 (this->*op
->func
)(argPtr
, numArgs
);
907 Operator
*Gfx::findOp(char *name
) {
912 cmp
= 0; // make gcc happy
913 // invariant: opTab[a] < name < opTab[b]
916 cmp
= strcmp(opTab
[m
].name
, name
);
929 GBool
Gfx::checkArg(Object
*arg
, TchkType 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
;
944 Goffset
Gfx::getPos() {
945 return parser
? parser
->getPos() : -1;
948 //------------------------------------------------------------------------
949 // graphics state operators
950 //------------------------------------------------------------------------
952 void Gfx::opSave(Object args
[], int numArgs
) {
956 void Gfx::opRestore(Object args
[], int numArgs
) {
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());
970 void Gfx::opSetDash(Object args
[], int numArgs
) {
977 a
= args
[0].getArray();
978 length
= a
->getLength();
982 dash
= (double *)gmallocn(length
, sizeof(double));
983 for (i
= 0; i
< length
; ++i
) {
986 dash
[i
] = obj
.getNum();
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
;
1026 GfxColor backdropColor
;
1027 GBool haveBackdropColor
;
1028 GfxColorSpace
*blendingColorSpace
;
1029 GBool alpha
, isolated
, knockout
;
1033 if (!res
->lookupGState(args
[0].getName(), &obj1
)) {
1036 if (!obj1
.isDict()) {
1037 error(errSyntaxError
, getPos(), "ExtGState '{0:s}' is wrong type", args
[0].getName());
1041 if (printCommands
) {
1042 printf(" gfx state dict: ");
1047 // parameters that are also set by individual PDF operators
1048 if (obj1
.dictLookup("LW", &obj2
)->isNum()) {
1049 opSetLineWidth(&obj2
, 1);
1052 if (obj1
.dictLookup("LC", &obj2
)->isInt()) {
1053 opSetLineCap(&obj2
, 1);
1056 if (obj1
.dictLookup("LJ", &obj2
)->isInt()) {
1057 opSetLineJoin(&obj2
, 1);
1060 if (obj1
.dictLookup("ML", &obj2
)->isNum()) {
1061 opSetMiterLimit(&obj2
, 1);
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);
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);
1089 if (obj1
.dictLookup("FL", &obj2
)->isNum()) {
1090 opSetFlat(&obj2
, 1);
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
);
1100 error(errSyntaxError
, getPos(), "Invalid blend mode in ExtGState");
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
);
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
);
1117 // fill/stroke overprint, overprint mode
1118 if ((haveFillOP
= (obj1
.dictLookup("op", &obj2
)->isBool()))) {
1119 state
->setFillOverprint(obj2
.getBool());
1120 out
->updateFillOverprint(state
);
1123 if (obj1
.dictLookup("OP", &obj2
)->isBool()) {
1124 state
->setStrokeOverprint(obj2
.getBool());
1125 out
->updateStrokeOverprint(state
);
1127 state
->setFillOverprint(obj2
.getBool());
1128 out
->updateFillOverprint(state
);
1132 if (obj1
.dictLookup("OPM", &obj2
)->isInt()) {
1133 state
->setOverprintMode(obj2
.getInt());
1134 out
->updateOverprintMode(state
);
1139 if (obj1
.dictLookup("SA", &obj2
)->isBool()) {
1140 state
->setStrokeAdjust(obj2
.getBool());
1141 out
->updateStrokeAdjust(state
);
1145 // transfer function
1146 if (obj1
.dictLookup("TR2", &obj2
)->isNull()) {
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
);
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");
1180 if (obj1
.dictLookup("AIS", &obj2
)->isBool()) {
1181 state
->setAlphaIsShape(obj2
.getBool());
1182 out
->updateAlphaIsShape(state
);
1187 if (obj1
.dictLookup("TK", &obj2
)->isBool()) {
1188 state
->setTextKnockout(obj2
.getBool());
1189 out
->updateTextKnockout(state
);
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")) {
1200 } else { // "Luminosity"
1205 if (!obj2
.dictLookup("TR", &obj3
)->isNull()) {
1206 if (obj3
.isName("Default") ||
1207 obj3
.isName("Identity")) {
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");
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
);
1229 backdropColor
.c
[i
] = dblToCol(obj4
.getNum());
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
);
1243 if (obj4
.dictLookup("I", &obj5
)->isBool()) {
1244 isolated
= obj5
.getBool();
1247 if (obj4
.dictLookup("K", &obj5
)->isBool()) {
1248 knockout
= obj5
.getBool();
1251 if (!haveBackdropColor
) {
1252 if (blendingColorSpace
) {
1253 blendingColorSpace
->getDefaultColor(&backdropColor
);
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
);
1267 error(errSyntaxError
, getPos(), "Invalid soft mask in ExtGState - missing group");
1271 error(errSyntaxError
, getPos(), "Invalid soft mask in ExtGState - missing group");
1274 } else if (!obj2
.isNull()) {
1275 error(errSyntaxError
, getPos(), "Invalid soft mask in ExtGState");
1279 if (obj1
.dictLookup("Font", &obj2
)->isArray()) {
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()) {
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
;
1302 error(errSyntaxError
, getPos(), "Number of args mismatch for /Font in ExtGState");
1306 if (obj1
.dictLookup("LW", &obj2
)->isNum()) {
1307 opSetLineWidth(&obj2
,1);
1310 if (obj1
.dictLookup("LC", &obj2
)->isInt()) {
1311 opSetLineCap(&obj2
,1);
1314 if (obj1
.dictLookup("LJ", &obj2
)->isInt()) {
1315 opSetLineJoin(&obj2
,1);
1318 if (obj1
.dictLookup("ML", &obj2
)->isNum()) {
1319 opSetMiterLimit(&obj2
,1);
1322 if (obj1
.dictLookup("D", &obj2
)->isArray()) {
1323 if (obj2
.arrayGetLength() == 2) {
1326 obj2
.arrayGetNF(0,&dargs
[0]);
1327 obj2
.arrayGet(1,&dargs
[1]);
1328 if (dargs
[0].isArray() && dargs
[1].isInt()) {
1334 error(errSyntaxError
, getPos(), "Number of args mismatch for /D in ExtGState");
1338 if (obj1
.dictLookup("RI", &obj2
)->isName()) {
1339 opSetRenderingIntent(&obj2
,1);
1342 if (obj1
.dictLookup("FL", &obj2
)->isNum()) {
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];
1359 // check for excessive recursion
1360 if (formDepth
> 20) {
1365 dict
= str
->streamGetDict();
1368 dict
->lookup("FormType", &obj1
);
1369 if (!(obj1
.isNull() || (obj1
.isInt() && obj1
.getInt() == 1))) {
1370 error(errSyntaxError
, getPos(), "Unknown form type");
1375 dict
->lookup("BBox", &obj1
);
1376 if (!obj1
.isArray()) {
1378 error(errSyntaxError
, getPos(), "Bad form bounding box");
1381 for (i
= 0; i
< 4; ++i
) {
1382 obj1
.arrayGet(i
, &obj2
);
1383 if (likely(obj2
.isNum())) bbox
[i
] = obj2
.getNum();
1387 error(errSyntaxError
, getPos(), "Bad form bounding box (non number)");
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();
1411 dict
->lookup("Resources", &obj1
);
1412 resDict
= obj1
.isDict() ? obj1
.getDict() : (Dict
*)NULL
;
1416 drawForm(str
, resDict
, m
, bbox
, gTrue
, gTrue
,
1417 blendingColorSpace
, isolated
, knockout
,
1418 alpha
, transferFunc
, backdropColor
);
1421 if (blendingColorSpace
) {
1422 delete blendingColorSpace
;
1427 void Gfx::opSetRenderingIntent(Object args
[], int numArgs
) {
1428 state
->setRenderingIntent(args
[0].getName());
1431 //------------------------------------------------------------------------
1433 //------------------------------------------------------------------------
1435 void Gfx::opSetFillGray(Object args
[], int numArgs
) {
1437 GfxColorSpace
*colorSpace
= NULL
;
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();
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
) {
1458 GfxColorSpace
*colorSpace
= NULL
;
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();
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
) {
1479 GfxColorSpace
*colorSpace
= NULL
;
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();
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
) {
1503 GfxColorSpace
*colorSpace
= NULL
;
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();
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
) {
1527 GfxColorSpace
*colorSpace
= NULL
;
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();
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
) {
1551 GfxColorSpace
*colorSpace
= NULL
;
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();
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
) {
1575 GfxColorSpace
*colorSpace
;
1578 res
->lookupColorSpace(args
[0].getName(), &obj
);
1580 colorSpace
= GfxColorSpace::parse(res
, &args
[0], out
, state
);
1582 colorSpace
= GfxColorSpace::parse(res
, &obj
, out
, state
);
1586 state
->setFillPattern(NULL
);
1587 state
->setFillColorSpace(colorSpace
);
1588 out
->updateFillColorSpace(state
);
1589 colorSpace
->getDefaultColor(&color
);
1590 state
->setFillColor(&color
);
1591 out
->updateFillColor(state
);
1593 error(errSyntaxError
, getPos(), "Bad color space (fill)");
1597 void Gfx::opSetStrokeColorSpace(Object args
[], int numArgs
) {
1599 GfxColorSpace
*colorSpace
;
1602 state
->setStrokePattern(NULL
);
1603 res
->lookupColorSpace(args
[0].getName(), &obj
);
1605 colorSpace
= GfxColorSpace::parse(res
, &args
[0], out
, state
);
1607 colorSpace
= GfxColorSpace::parse(res
, &obj
, out
, state
);
1611 state
->setStrokeColorSpace(colorSpace
);
1612 out
->updateStrokeColorSpace(state
);
1613 colorSpace
->getDefaultColor(&color
);
1614 state
->setStrokeColor(&color
);
1615 out
->updateStrokeColor(state
);
1617 error(errSyntaxError
, getPos(), "Bad color space (stroke)");
1621 void Gfx::opSetFillColor(Object args
[], int numArgs
) {
1625 if (numArgs
!= state
->getFillColorSpace()->getNComps()) {
1626 error(errSyntaxError
, getPos(), "Incorrect number of arguments in 'sc' command");
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
) {
1641 if (numArgs
!= state
->getStrokeColorSpace()->getNComps()) {
1642 error(errSyntaxError
, getPos(), "Incorrect number of arguments in 'SC' command");
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
) {
1655 GfxPattern
*pattern
;
1658 if (state
->getFillColorSpace()->getMode() == csPattern
) {
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");
1666 for (i
= 0; i
< numArgs
- 1 && i
< gfxColorMaxComps
; ++i
) {
1667 if (args
[i
].isNum()) {
1668 color
.c
[i
] = dblToCol(args
[i
].getNum());
1670 color
.c
[i
] = 0; // TODO Investigate if this is what Adobe does
1673 state
->setFillColor(&color
);
1674 out
->updateFillColor(state
);
1677 if (args
[numArgs
-1].isName() &&
1678 (pattern
= res
->lookupPattern(args
[numArgs
-1].getName(), out
, state
))) {
1679 state
->setFillPattern(pattern
);
1684 if (numArgs
!= state
->getFillColorSpace()->getNComps()) {
1685 error(errSyntaxError
, getPos(), "Incorrect number of arguments in 'scn' command");
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());
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
) {
1703 GfxPattern
*pattern
;
1706 if (state
->getStrokeColorSpace()->getMode() == csPattern
) {
1708 if (!((GfxPatternColorSpace
*)state
->getStrokeColorSpace())
1710 numArgs
- 1 != ((GfxPatternColorSpace
*)state
->getStrokeColorSpace())
1711 ->getUnder()->getNComps()) {
1712 error(errSyntaxError
, getPos(), "Incorrect number of arguments in 'SCN' command");
1715 for (i
= 0; i
< numArgs
- 1 && i
< gfxColorMaxComps
; ++i
) {
1716 if (args
[i
].isNum()) {
1717 color
.c
[i
] = dblToCol(args
[i
].getNum());
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");
1729 if (args
[numArgs
-1].isName() &&
1730 (pattern
= res
->lookupPattern(args
[numArgs
-1].getName(), out
, state
))) {
1731 state
->setStrokePattern(pattern
);
1735 if (numArgs
!= state
->getStrokeColorSpace()->getNComps()) {
1736 error(errSyntaxError
, getPos(), "Incorrect number of arguments in 'SCN' command");
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());
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");
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");
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");
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");
1807 x1
= args
[0].getNum();
1808 y1
= args
[1].getNum();
1809 x2
= args
[2].getNum();
1810 y2
= args
[3].getNum();
1813 state
->curveTo(x1
, y1
, x2
, y2
, x3
, y3
);
1816 void Gfx::opRectangle(Object args
[], int numArgs
) {
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
);
1830 void Gfx::opClosePath(Object args
[], int numArgs
) {
1831 if (!state
->isCurPt()) {
1832 error(errSyntaxError
, getPos(), "No current point in closepath");
1838 //------------------------------------------------------------------------
1839 // path painting operators
1840 //------------------------------------------------------------------------
1842 void Gfx::opEndPath(Object args
[], int numArgs
) {
1846 void Gfx::opStroke(Object args
[], int numArgs
) {
1847 if (!state
->isCurPt()) {
1848 //error(errSyntaxError, getPos(), "No path in stroke");
1851 if (state
->isPath()) {
1853 if (state
->getStrokeColorSpace()->getMode() == csPattern
) {
1863 void Gfx::opCloseStroke(Object
* /*args[]*/, int /*numArgs*/) {
1864 if (!state
->isCurPt()) {
1865 //error(errSyntaxError, getPos(), "No path in closepath/stroke");
1868 if (state
->isPath()) {
1871 if (state
->getStrokeColorSpace()->getMode() == csPattern
) {
1881 void Gfx::opFill(Object args
[], int numArgs
) {
1882 if (!state
->isCurPt()) {
1883 //error(errSyntaxError, getPos(), "No path in fill");
1886 if (state
->isPath()) {
1888 if (state
->getFillColorSpace()->getMode() == csPattern
) {
1889 doPatternFill(gFalse
);
1898 void Gfx::opEOFill(Object args
[], int numArgs
) {
1899 if (!state
->isCurPt()) {
1900 //error(errSyntaxError, getPos(), "No path in eofill");
1903 if (state
->isPath()) {
1905 if (state
->getFillColorSpace()->getMode() == csPattern
) {
1906 doPatternFill(gTrue
);
1915 void Gfx::opFillStroke(Object args
[], int numArgs
) {
1916 if (!state
->isCurPt()) {
1917 //error(errSyntaxError, getPos(), "No path in fill/stroke");
1920 if (state
->isPath()) {
1922 if (state
->getFillColorSpace()->getMode() == csPattern
) {
1923 doPatternFill(gFalse
);
1927 if (state
->getStrokeColorSpace()->getMode() == csPattern
) {
1937 void Gfx::opCloseFillStroke(Object args
[], int numArgs
) {
1938 if (!state
->isCurPt()) {
1939 //error(errSyntaxError, getPos(), "No path in closepath/fill/stroke");
1942 if (state
->isPath()) {
1945 if (state
->getFillColorSpace()->getMode() == csPattern
) {
1946 doPatternFill(gFalse
);
1950 if (state
->getStrokeColorSpace()->getMode() == csPattern
) {
1960 void Gfx::opEOFillStroke(Object args
[], int numArgs
) {
1961 if (!state
->isCurPt()) {
1962 //error(errSyntaxError, getPos(), "No path in eofill/stroke");
1965 if (state
->isPath()) {
1967 if (state
->getFillColorSpace()->getMode() == csPattern
) {
1968 doPatternFill(gTrue
);
1972 if (state
->getStrokeColorSpace()->getMode() == csPattern
) {
1982 void Gfx::opCloseEOFillStroke(Object args
[], int numArgs
) {
1983 if (!state
->isCurPt()) {
1984 //error(errSyntaxError, getPos(), "No path in closepath/eofill/stroke");
1987 if (state
->isPath()) {
1990 if (state
->getFillColorSpace()->getMode() == csPattern
) {
1991 doPatternFill(gTrue
);
1995 if (state
->getStrokeColorSpace()->getMode() == csPattern
) {
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()) {
2015 if (!(pattern
= state
->getFillPattern())) {
2018 switch (pattern
->getType()) {
2020 doTilingPatternFill((GfxTilingPattern
*)pattern
, gFalse
, eoFill
, gFalse
);
2023 doShadingPatternFill((GfxShadingPattern
*)pattern
, gFalse
, eoFill
, gFalse
);
2026 error(errSyntaxError
, getPos(), "Unknown pattern type ({0:d}) in fill",
2027 pattern
->getType());
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()) {
2042 if (!(pattern
= state
->getStrokePattern())) {
2045 switch (pattern
->getType()) {
2047 doTilingPatternFill((GfxTilingPattern
*)pattern
, gTrue
, gFalse
, gFalse
);
2050 doShadingPatternFill((GfxShadingPattern
*)pattern
, gTrue
, gFalse
, gFalse
);
2053 error(errSyntaxError
, getPos(), "Unknown pattern type ({0:d}) in stroke",
2054 pattern
->getType());
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()) {
2069 if (!(pattern
= state
->getFillPattern())) {
2072 switch (pattern
->getType()) {
2074 doTilingPatternFill((GfxTilingPattern
*)pattern
, gFalse
, gFalse
, gTrue
);
2077 doShadingPatternFill((GfxShadingPattern
*)pattern
, gFalse
, gFalse
, gTrue
);
2080 error(errSyntaxError
, getPos(), "Unknown pattern type ({0:d}) in fill",
2081 pattern
->getType());
2086 void Gfx::doPatternImageMask(Object
*ref
, Stream
*str
, int width
, int height
,
2087 GBool invert
, GBool inlineImg
) {
2090 out
->setSoftMaskFromImageMask(state
, ref
, str
,
2091 width
, height
, invert
, inlineImg
, baseMatrix
);
2094 state
->moveTo(0, 0);
2095 state
->lineTo(1, 0);
2096 state
->lineTo(1, 1);
2097 state
->lineTo(0, 1);
2101 out
->unsetSoftMaskFromImageMask(state
, baseMatrix
);
2105 void Gfx::doTilingPatternFill(GfxTilingPattern
*tPat
,
2106 GBool stroke
, GBool eoFill
, GBool text
) {
2107 GfxPatternColorSpace
*patCS
;
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];
2117 double xstep
, ystep
;
2121 patCS
= (GfxPatternColorSpace
*)(stroke
? state
->getStrokeColorSpace()
2122 : state
->getFillColorSpace());
2124 // construct a (pattern space) -> (current space) transform matrix
2125 ctm
= state
->getCTM();
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
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
);
2174 state
->setFillColor(state
->getStrokeColor());
2176 state
->setStrokeColor(state
->getFillColor());
2178 out
->updateFillColor(state
);
2179 out
->updateStrokeColor(state
);
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
);
2191 state
->setLineWidth(0);
2192 out
->updateLineWidth(state
);
2195 // clip to current path
2197 state
->clipToStrokePath();
2198 out
->clipToStrokePath(state
);
2209 // get the clip region, check for empty
2210 state
->getClipBBox(&cxMin
, &cyMin
, &cxMax
, &cyMax
);
2211 if (cxMin
> cxMax
|| cyMin
> cyMax
) {
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];
2222 } else if (x1
> xMax
) {
2227 } else if (y1
> yMax
) {
2230 x1
= cxMax
* imb
[0] + cyMin
* imb
[2] + imb
[4];
2231 y1
= cxMax
* imb
[1] + cyMin
* imb
[3] + imb
[5];
2234 } else if (x1
> xMax
) {
2239 } else if (y1
> yMax
) {
2242 x1
= cxMax
* imb
[0] + cyMax
* imb
[2] + imb
[4];
2243 y1
= cxMax
* imb
[1] + cyMax
* imb
[3] + imb
[5];
2246 } else if (x1
> xMax
) {
2251 } else if (y1
> yMax
) {
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;
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;
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
) {
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
)) {
2286 for (yi
= yi0
; yi
< yi1
; ++yi
) {
2287 for (xi
= xi0
; xi
< xi1
; ++xi
) {
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
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
;
2312 shading
= sPat
->getShading();
2314 // save current graphics state
2315 savedState
= saveStateStack();
2317 // clip to current path
2319 state
->clipToStrokePath();
2320 out
->clipToStrokePath(state
);
2331 // construct a (pattern space) -> (current space) transform matrix
2332 ctm
= state
->getCTM();
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]);
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
);
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
);
2393 #if 1 //~tmp: turn off anti-aliasing temporarily
2394 GBool vaa
= out
->getVectorAntialias();
2396 out
->setVectorAntialias(gFalse
);
2400 // do shading type-specific operations
2401 switch (shading
->getType()) {
2403 doFunctionShFill((GfxFunctionShading
*)shading
);
2406 doAxialShFill((GfxAxialShading
*)shading
);
2409 doRadialShFill((GfxRadialShading
*)shading
);
2413 doGouraudTriangleShFill((GfxGouraudTriangleShading
*)shading
);
2417 doPatchMeshShFill((GfxPatchMeshShading
*)shading
);
2421 #if 1 //~tmp: turn off anti-aliasing temporarily
2423 out
->setVectorAntialias(gTrue
);
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
;
2440 if (!(shading
= res
->lookupShading(args
[0].getName(), out
, state
))) {
2444 // save current graphics state
2445 savedState
= saveStateStack();
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
);
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();
2467 out
->setVectorAntialias(gFalse
);
2471 // do shading type-specific operations
2472 switch (shading
->getType()) {
2474 doFunctionShFill((GfxFunctionShading
*)shading
);
2477 doAxialShFill((GfxAxialShading
*)shading
);
2480 doRadialShFill((GfxRadialShading
*)shading
);
2484 doGouraudTriangleShFill((GfxGouraudTriangleShading
*)shading
);
2488 doPatchMeshShFill((GfxPatchMeshShading
*)shading
);
2492 #if 1 //~tmp: turn off anti-aliasing temporarily
2494 out
->setVectorAntialias(gTrue
);
2498 // restore graphics state
2499 restoreStateStack(savedState
);
2504 void Gfx::doFunctionShFill(GfxFunctionShading
*shading
) {
2505 double x0
, y0
, x1
, y1
;
2508 if (out
->useShadedFills( shading
->getType() ) &&
2509 out
->functionShadedFill(state
, shading
)) {
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
) {
2526 GfxColor color0M
, color1M
, colorM0
, colorM1
, colorMM
;
2527 GfxColor colors2
[4];
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
) {
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]);
2575 // the four corner colors are not close enough -- subdivide the
2579 // colors[0] colorM0 colors[2]
2580 // (x0,y0) (xM,y0) (x1,y0)
2581 // +----------+----------+
2584 // color0M | colorMM | color1M
2585 // (x0,yM) +----------+----------+ (x1,yM)
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
) {
2633 for (int k
= j
+ 1; k
< 4; ++k
) {
2634 if (array
[k
] < array
[kk
]) {
2638 double tmp
= array
[j
];
2639 array
[j
] = array
[kk
];
2644 void Gfx::doAxialShFill(GfxAxialShading
*shading
) {
2645 double xMin
, yMin
, xMax
, yMax
;
2646 double x0
, y0
, x1
, y1
;
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
;
2654 double ta
[axialMaxSplits
+ 1];
2655 int next
[axialMaxSplits
+ 1];
2656 GfxColor color0
, color1
;
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
2666 shading
->getCoords(&x0
, &y0
, &x1
, &y1
);
2669 dxZero
= fabs(dx
) < 0.01;
2670 dyZero
= fabs(dy
) < 0.01;
2671 if (dxZero
&& dyZero
) {
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()) {
2685 if (tMax
> 1 && !shading
->getExtend1()) {
2690 if (out
->useShadedFills( shading
->getType() ) &&
2691 out
->axialShadedFill(state
, shading
, tMin
, tMax
)) {
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();
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
2740 } else if (tMin
> 1) {
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
2756 tx
= x0
+ tMin
* dx
;
2757 ty
= y0
+ tMin
* dy
;
2758 if (dxZero
&& dyZero
) {
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
; }
2769 s
[0] = (yMin
- ty
) / dx
;
2770 s
[1] = (yMax
- ty
) / dx
;
2771 s
[2] = (xMin
- tx
) / -dy
;
2772 s
[3] = (xMax
- tx
) / -dy
;
2777 ux0
= tx
- sMin
* dy
;
2778 uy0
= ty
+ sMin
* dx
;
2779 vx0
= tx
- sMax
* dy
;
2780 vy0
= ty
+ sMax
* dx
;
2783 bool doneBBox1
, doneBBox2
;
2784 if (dxZero
&& dyZero
) {
2785 doneBBox1
= doneBBox2
= true;
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
2803 } else if (ta
[j
] > 1) {
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
;
2827 ta
[teoricalj
] = bboxIntersections
[1];
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
;
2841 ta
[teoricalj
] = bboxIntersections
[2];
2848 ta
[k
] = 0.5 * (ta
[i
] + ta
[j
]);
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
2862 tx
= x0
+ ta
[j
] * dx
;
2863 ty
= y0
+ ta
[j
] * dy
;
2864 if (dxZero
&& dyZero
) {
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
; }
2875 s
[0] = (yMin
- ty
) / dx
;
2876 s
[1] = (yMax
- ty
) / dx
;
2877 s
[2] = (xMin
- tx
) / -dy
;
2878 s
[3] = (xMax
- tx
) / -dy
;
2883 ux1
= tx
- sMin
* dy
;
2884 uy1
= ty
+ sMin
* dx
;
2885 vx1
= tx
- sMax
* dy
;
2886 vy1
= ty
+ sMax
* dx
;
2889 state
->setFillColor(&color0
);
2890 if (out
->useFillColorStop())
2891 out
->updateFillColorStop(state
, (ta
[j
] - tMin
)/(tMax
- tMin
));
2893 out
->updateFillColor(state
);
2897 state
->moveTo(ux0
, uy0
);
2898 state
->lineTo(vx0
, vy0
);
2899 state
->lineTo(vx1
, vy1
);
2900 state
->lineTo(ux1
, uy1
);
2904 if (!out
->useFillColorStop()) {
2909 // set up for next region
2918 if (out
->useFillColorStop()) {
2920 state
->moveTo(xMin
, yMin
);
2921 state
->lineTo(xMin
, yMax
);
2922 state
->lineTo(xMax
, yMax
);
2923 state
->lineTo(xMax
, yMin
);
2931 static inline void getShadingColorRadialHelper(double t0
, double t1
, double t
, GfxRadialShading
*shading
, GfxColor
*color
)
2935 shading
->getColor(t0
, color
);
2936 } else if (t
> t1
) {
2937 shading
->getColor(t1
, color
);
2939 shading
->getColor(t
, color
);
2943 shading
->getColor(t0
, color
);
2944 } else if (t
< t1
) {
2945 shading
->getColor(t1
, color
);
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
;
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
;
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
) {
2976 theta
= 0; // make gcc happy
2977 sz
= 0; // make gcc happy
2978 } else if (r0
== r1
) {
2981 sz
= 0; // make gcc happy
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
)));
2995 alpha
= atan2(y1
- y0
, x1
- x0
);
2998 // compute the (possibly extended) s range
2999 state
->getUserClipBBox(&xMin
, &yMin
, &xMax
, &yMax
);
3006 // solve for x(s) + r(s) = xMin
3007 if ((x1
+ r1
) - (x0
+ r0
) != 0) {
3008 sa
= (xMin
- (x0
+ r0
)) / ((x1
+ r1
) - (x0
+ r0
));
3011 } else if (sa
> sMax
) {
3015 // solve for x(s) - r(s) = xMax
3016 if ((x1
- r1
) - (x0
- r0
) != 0) {
3017 sa
= (xMax
- (x0
- r0
)) / ((x1
- r1
) - (x0
- r0
));
3020 } else if (sa
> sMax
) {
3024 // solve for y(s) + r(s) = yMin
3025 if ((y1
+ r1
) - (y0
+ r0
) != 0) {
3026 sa
= (yMin
- (y0
+ r0
)) / ((y1
+ r1
) - (y0
+ r0
));
3029 } else if (sa
> sMax
) {
3033 // solve for y(s) - r(s) = yMax
3034 if ((y1
- r1
) - (y0
- r0
) != 0) {
3035 sa
= (yMax
- (y0
- r0
)) / ((y1
- r1
) - (y0
- r0
));
3038 } else if (sa
> sMax
) {
3047 } else if (r0
> r1
) {
3052 // check the 'extend' flags
3053 if (!shading
->getExtend0() && sMin
< 0) {
3056 if (!shading
->getExtend1() && sMax
> 1) {
3061 if (out
->useShadedFills( shading
->getType() ) &&
3062 out
->radialShadedFill(state
, shading
, sMin
, sMax
)) {
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();
3072 if (fabs(ctm
[1]) > t
) {
3075 if (fabs(ctm
[2]) > t
) {
3078 if (fabs(ctm
[3]) > t
) {
3089 n
= (int)(M_PI
/ acos(1 - 0.1 / t
));
3092 } else if (n
> 200) {
3097 // setup for the start circle
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
);
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
;
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
3126 for (; ic
<= ib
; ic
++) {
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
)) {
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
);
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
));
3160 out
->updateFillColor(state
);
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
));
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
));
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
));
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
));
3212 if (!out
->useFillColorStop()) {
3218 // step to the next value of t
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
));
3234 state
->moveTo(xMin
, yMin
);
3235 state
->lineTo(xMin
, yMax
);
3236 state
->lineTo(xMax
, yMax
);
3237 state
->lineTo(xMax
, yMin
);
3248 // extend the smaller circle
3249 if ((shading
->getExtend0() && r0
<= r1
) ||
3250 (shading
->getExtend1() && r1
< r0
)) {
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
));
3275 // extend the larger circle
3276 if ((shading
->getExtend0() && r0
> r1
) ||
3277 (shading
->getExtend1() && r1
>= r0
)) {
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
);
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
));
3309 void Gfx::doGouraudTriangleShFill(GfxGouraudTriangleShading
*shading
) {
3310 double x0
, y0
, x1
, y1
, x2
, y2
;
3313 if (out
->useShadedFills( shading
->getType())) {
3314 if (out
->gouraudTriangleShadedFill( state
, shading
))
3318 // preallocate a path (speed improvements)
3319 state
->moveTo(0., 0.);
3320 state
->lineTo(1., 0.);
3321 state
->lineTo(0., 1.);
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
,
3336 gouraudFillTriangle(x0
, y0
, color0
, x1
, y1
, color1
, x2
, y2
, color2
, refineColorThreshold
, 0, shading
, reusablePath
);
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
,
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
) {
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
;
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
) {
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");
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
) {
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");
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.;
3447 gouraudFillTriangle(x0
, y0
, color0
,
3450 refineColorThreshold
, depth
, shading
, path
);
3451 gouraudFillTriangle(x01
, y01
, color01
,
3454 refineColorThreshold
, depth
, shading
, path
);
3455 gouraudFillTriangle(x01
, y01
, color01
,
3458 refineColorThreshold
, depth
, shading
, path
);
3459 gouraudFillTriangle(x20
, y20
, color20
,
3462 refineColorThreshold
, depth
, shading
, path
);
3466 void Gfx::doPatchMeshShFill(GfxPatchMeshShading
*shading
) {
3469 if (out
->useShadedFills( shading
->getType())) {
3470 if (out
->patchMeshShadedFill( state
, shading
))
3474 if (shading
->getNPatches() > 128) {
3476 } else if (shading
->getNPatches() > 64) {
3478 } else if (shading
->getNPatches() > 16) {
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());
3502 refineColorThreshold
= patchColorDelta
;
3505 for (i
= 0; i
< shading
->getNPatches(); ++i
) {
3506 fillPatch(shading
->getPatch(i
),
3508 shading
->isParameterized() ? 1 : colorComps
,
3509 refineColorThreshold
,
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];
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
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
) {
3534 if (i
== patchColorComps
|| depth
== patchMaxDepth
) {
3536 if( shading
->isParameterized() ) {
3537 shading
->getParameterizedColor( patch
->color
[0][0].c
[0], &flatColor
);
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]);
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
) {
3654 if (clip
== clipNormal
) {
3664 //------------------------------------------------------------------------
3665 // path clipping operators
3666 //------------------------------------------------------------------------
3668 void Gfx::opClip(Object args
[], int numArgs
) {
3672 void Gfx::opEOClip(Object args
[], int numArgs
) {
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
) {
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
;
3712 if (printCommands
) {
3713 printf(" font: tag=%s name='%s' %g\n",
3714 font
->getTag()->getCString(),
3715 font
->getName() ? font
->getName()->getCString() : "???",
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
) {
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
) {
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
) {
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");
3803 out
->updateFont(state
);
3804 fontChanged
= gFalse
;
3806 out
->beginStringOp(state
);
3807 doShowText(args
[0].getString());
3808 out
->endStringOp(state
);
3810 doIncCharCount(args
[0].getString());
3814 void Gfx::opMoveShowText(Object args
[], int numArgs
) {
3817 if (!state
->getFont()) {
3818 error(errSyntaxError
, getPos(), "No font in move/show");
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
);
3833 doIncCharCount(args
[0].getString());
3837 void Gfx::opMoveSetShowText(Object args
[], int numArgs
) {
3840 if (!state
->getFont()) {
3841 error(errSyntaxError
, getPos(), "No font in move/set/show");
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
);
3860 doIncCharCount(args
[2].getString());
3864 void Gfx::opShowSpaceText(Object args
[], int numArgs
) {
3870 if (!state
->getFont()) {
3871 error(errSyntaxError
, getPos(), "No font in show/space");
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
) {
3884 // this uses the absolute value of the font size to match
3885 // Acrobat's behavior
3887 state
->textShift(0, -obj
.getNum() * 0.001 *
3888 state
->getFontSize());
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());
3898 error(errSyntaxError
, getPos(),
3899 "Element of show/space array must be number or string");
3903 out
->endStringOp(state
);
3905 a
= args
[0].getArray();
3906 for (i
= 0; i
< a
->getLength(); ++i
) {
3908 if (obj
.isString()) {
3909 doIncCharCount(obj
.getString());
3916 void Gfx::doShowText(GooString
*s
) {
3919 double riseX
, riseY
;
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];
3930 GfxState
*savedState
;
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
;
3949 // disable fill, enable clipping, leave stroke unchanged
3950 if ((render
^ (render
>> 1)) & 1) {
3955 state
->setRender(render
);
3956 out
->updateRender(state
);
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
) {
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();
3990 p
= s
->getCString();
3991 len
= s
->getLength();
3993 n
= font
->getNextChar(p
, len
, &code
,
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
,
4011 ((Gfx8BitFont
*)font
)->getCharProc(code
, &charProc
);
4012 if ((resDict
= ((Gfx8BitFont
*)font
)->getResources())) {
4013 pushResources(resDict
);
4015 if (charProc
.isStream()) {
4016 display(&charProc
, gFalse
);
4018 error(errSyntaxError
, getPos(), "Missing or bad Type3 CharProc entry");
4020 out
->endType3Char(state
);
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)
4031 state
->moveTo(curX
, curY
);
4037 } else if (out
->useDrawChar()) {
4038 p
= s
->getCString();
4039 len
= s
->getLength();
4041 n
= font
->getNextChar(p
, len
, &code
,
4043 &dx
, &dy
, &originX
, &originY
);
4045 dx
*= state
->getFontSize();
4046 dy
= dy
* state
->getFontSize() + state
->getCharSpace();
4047 if (n
== 1 && *p
== ' ') {
4048 dy
+= state
->getWordSpace();
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
);
4063 out
->drawChar(state
, state
->getCurX() + riseX
, state
->getCurY() + riseY
,
4064 tdx
, tdy
, tOriginX
, tOriginY
, code
, n
, u
, uLen
);
4065 state
->shift(tdx
, tdy
);
4071 p
= s
->getCString();
4072 len
= s
->getLength();
4073 nChars
= nSpaces
= 0;
4075 n
= font
->getNextChar(p
, len
, &code
,
4077 &dx2
, &dy2
, &originX
, &originY
);
4080 if (n
== 1 && *p
== ' ') {
4088 dx
*= state
->getFontSize();
4089 dy
= dy
* state
->getFontSize()
4090 + nChars
* state
->getCharSpace()
4091 + nSpaces
* state
->getWordSpace();
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
);
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
;
4119 x
= x0
; x0
= x1
; x1
= x
;
4122 y
= y0
; y0
= y1
; y1
= y
;
4124 state
->textTransformDelta(0, state
->getFontSize(), &dx
, &dy
);
4125 state
->textTransformDelta(state
->getFontSize(), 0, &dx2
, &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
);
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
) {
4161 Object obj1
, obj2
, obj3
, refObj
;
4166 if (!ocState
&& !out
->needCharCount()) {
4169 name
= args
[0].getName();
4170 if (!res
->lookupXObject(name
, &obj1
)) {
4173 if (!obj1
.isStream()) {
4174 error(errSyntaxError
, getPos(), "XObject '{0:s}' is wrong type", name
);
4180 obj1
.streamGetDict()->lookup("OPI", &opiDict
);
4181 if (opiDict
.isDict()) {
4182 out
->opiBegin(state
, opiDict
.getDict());
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
);
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
;
4201 shouldDoForm
= gFalse
;
4205 if (out
->useDrawForm() && refObj
.isRef()) {
4206 out
->drawForm(refObj
.getRef());
4211 if (refObj
.isRef() && shouldDoForm
) {
4212 formsDrawing
.erase(drawingFormIt
);
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());
4222 error(errSyntaxError
, getPos(), "XObject subtype is missing or wrong type");
4226 if (opiDict
.isDict()) {
4227 out
->opiEnd(state
, opiDict
.getDict());
4234 void Gfx::doImage(Object
*ref
, Stream
*str
, GBool inlineImg
) {
4235 Dict
*dict
, *maskDict
;
4239 StreamColorSpaceMode csMode
;
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
;
4249 GBool maskInterpolate
;
4254 // get info from the stream
4256 csMode
= streamCSNone
;
4257 str
->getImageParams(&bits
, &csMode
);
4260 dict
= str
->getDict();
4262 // check for optional content key
4264 dict
->lookupNF("OC", &obj1
);
4265 if (catalog
->getOptContentConfig() && !catalog
->getOptContentConfig()->optContentIsVisible(&obj1
)) {
4273 dict
->lookup("Width", &obj1
);
4274 if (obj1
.isNull()) {
4276 dict
->lookup("W", &obj1
);
4279 width
= obj1
.getInt();
4280 else if (obj1
.isReal())
4281 width
= (int)obj1
.getReal();
4285 dict
->lookup("Height", &obj1
);
4286 if (obj1
.isNull()) {
4288 dict
->lookup("H", &obj1
);
4291 height
= obj1
.getInt();
4292 else if (obj1
.isReal())
4293 height
= (int)obj1
.getReal();
4298 if (width
< 1 || height
< 1)
4301 // image interpolation
4302 dict
->lookup("Interpolate", &obj1
);
4303 if (obj1
.isNull()) {
4305 dict
->lookup("I", &obj1
);
4308 interpolate
= obj1
.getBool();
4310 interpolate
= gFalse
;
4312 maskInterpolate
= gFalse
;
4315 dict
->lookup("ImageMask", &obj1
);
4316 if (obj1
.isNull()) {
4318 dict
->lookup("IM", &obj1
);
4322 mask
= obj1
.getBool();
4323 else if (!obj1
.isNull())
4329 dict
->lookup("BitsPerComponent", &obj1
);
4330 if (obj1
.isNull()) {
4332 dict
->lookup("BPC", &obj1
);
4335 bits
= obj1
.getInt();
4347 // check for inverted mask
4351 dict
->lookup("Decode", &obj1
);
4352 if (obj1
.isNull()) {
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)
4363 } else if (!obj1
.isNull()) {
4368 // if drawing is disabled, skip over inline image data
4369 if (!ocState
|| !out
->needNonText()) {
4371 n
= height
* ((width
+ 7) / 8);
4372 for (i
= 0; i
< n
; ++i
) {
4379 if (state
->getFillColorSpace()->getMode() == csPattern
) {
4380 doPatternImageMask(ref
, str
, width
, height
, invert
, inlineImg
);
4382 out
->drawImageMask(state
, ref
, str
, width
, height
, invert
, interpolate
, inlineImg
);
4387 // get color space and color map
4388 dict
->lookup("ColorSpace", &obj1
);
4389 if (obj1
.isNull()) {
4391 dict
->lookup("CS", &obj1
);
4393 if (obj1
.isName() && inlineImg
) {
4394 res
->lookupColorSpace(obj1
.getName(), &obj2
);
4395 if (!obj2
.isNull()) {
4402 if (!obj1
.isNull()) {
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
);
4419 } else if (csMode
== streamCSDeviceGray
) {
4421 res
->lookupColorSpace("DefaultGray", &objCS
);
4422 if (objCS
.isNull()) {
4423 colorSpace
= new GfxDeviceGrayColorSpace();
4425 colorSpace
= GfxColorSpace::parse(res
, &objCS
, out
, state
);
4428 } else if (csMode
== streamCSDeviceRGB
) {
4430 res
->lookupColorSpace("DefaultRGB", &objCS
);
4431 if (objCS
.isNull()) {
4432 colorSpace
= new GfxDeviceRGBColorSpace();
4434 colorSpace
= GfxColorSpace::parse(res
, &objCS
, out
, state
);
4437 } else if (csMode
== streamCSDeviceCMYK
) {
4439 res
->lookupColorSpace("DefaultCMYK", &objCS
);
4440 if (objCS
.isNull()) {
4441 colorSpace
= new GfxDeviceCMYKColorSpace();
4443 colorSpace
= GfxColorSpace::parse(res
, &objCS
, out
, state
);
4453 dict
->lookup("Decode", &obj1
);
4454 if (obj1
.isNull()) {
4456 dict
->lookup("D", &obj1
);
4462 colorMap
= new GfxImageColorMap(bits
, &obj1
, colorSpace
);
4464 if (!colorMap
->isOk()) {
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()) {
4482 maskStr
= smaskObj
.getStream();
4483 maskDict
= smaskObj
.streamGetDict();
4484 maskDict
->lookup("Width", &obj1
);
4485 if (obj1
.isNull()) {
4487 maskDict
->lookup("W", &obj1
);
4489 if (!obj1
.isInt()) {
4492 maskWidth
= obj1
.getInt();
4494 maskDict
->lookup("Height", &obj1
);
4495 if (obj1
.isNull()) {
4497 maskDict
->lookup("H", &obj1
);
4499 if (!obj1
.isInt()) {
4502 maskHeight
= obj1
.getInt();
4504 maskDict
->lookup("Interpolate", &obj1
);
4505 if (obj1
.isNull()) {
4507 maskDict
->lookup("I", &obj1
);
4510 maskInterpolate
= obj1
.getBool();
4512 maskInterpolate
= gFalse
;
4514 maskDict
->lookup("BitsPerComponent", &obj1
);
4515 if (obj1
.isNull()) {
4517 maskDict
->lookup("BPC", &obj1
);
4519 if (!obj1
.isInt()) {
4522 maskBits
= obj1
.getInt();
4524 maskDict
->lookup("ColorSpace", &obj1
);
4525 if (obj1
.isNull()) {
4527 maskDict
->lookup("CS", &obj1
);
4529 if (obj1
.isName()) {
4530 res
->lookupColorSpace(obj1
.getName(), &obj2
);
4531 if (!obj2
.isNull()) {
4538 maskColorSpace
= GfxColorSpace::parse(NULL
, &obj1
, out
, state
);
4540 if (!maskColorSpace
|| maskColorSpace
->getMode() != csDeviceGray
) {
4543 maskDict
->lookup("Decode", &obj1
);
4544 if (obj1
.isNull()) {
4546 maskDict
->lookup("D", &obj1
);
4548 maskColorMap
= new GfxImageColorMap(maskBits
, &obj1
, maskColorSpace
);
4550 if (!maskColorMap
->isOk()) {
4551 delete maskColorMap
;
4554 //~ handle the Matte entry
4555 haveSoftMask
= gTrue
;
4556 } else if (maskObj
.isArray()) {
4559 i
< maskObj
.arrayGetLength() && i
< 2*gfxColorMaxComps
;
4561 maskObj
.arrayGet(i
, &obj1
);
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();
4568 error(errSyntaxError
, -1, "Mask entry should be an integer but it's of type {0:d}", obj1
.getType());
4574 haveColorKeyMask
= gTrue
;
4575 } else if (maskObj
.isStream()) {
4580 maskStr
= maskObj
.getStream();
4581 maskDict
= maskObj
.streamGetDict();
4582 maskDict
->lookup("Width", &obj1
);
4583 if (obj1
.isNull()) {
4585 maskDict
->lookup("W", &obj1
);
4587 if (!obj1
.isInt()) {
4590 maskWidth
= obj1
.getInt();
4592 maskDict
->lookup("Height", &obj1
);
4593 if (obj1
.isNull()) {
4595 maskDict
->lookup("H", &obj1
);
4597 if (!obj1
.isInt()) {
4600 maskHeight
= obj1
.getInt();
4602 maskDict
->lookup("Interpolate", &obj1
);
4603 if (obj1
.isNull()) {
4605 maskDict
->lookup("I", &obj1
);
4608 maskInterpolate
= obj1
.getBool();
4610 maskInterpolate
= gFalse
;
4612 maskDict
->lookup("ImageMask", &obj1
);
4613 if (obj1
.isNull()) {
4615 maskDict
->lookup("IM", &obj1
);
4617 if (!obj1
.isBool() || !obj1
.getBool()) {
4621 maskInvert
= gFalse
;
4622 maskDict
->lookup("Decode", &obj1
);
4623 if (obj1
.isNull()) {
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) {
4635 } else if (!obj1
.isNull()) {
4639 haveExplicitMask
= gTrue
;
4642 // if drawing is disabled, skip over inline image data
4643 if (!ocState
|| !out
->needNonText()) {
4645 n
= height
* ((width
* colorMap
->getNumPixelComps() *
4646 colorMap
->getBits() + 7) / 8);
4647 for (i
= 0; i
< n
; ++i
) {
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
);
4662 out
->drawImage(state
, ref
, str
, width
, height
, colorMap
, interpolate
,
4663 haveColorKeyMask
? maskColors
: (int *)NULL
, inlineImg
);
4672 if ((i
= width
* height
) > 1000) {
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
4689 GBool transpGroup
= gFalse
;
4692 if (resDict
== NULL
)
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
++) {
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
;
4708 error(errSyntaxError
, getPos(), "Invalid blend mode in ExtGState");
4712 if (obj1
.dictLookup("ca", &obj2
)->isNum()) {
4713 opac
= obj2
.getNum();
4714 opac
= opac
< 0 ? 0 : opac
> 1 ? 1 : opac
;
4716 transpGroup
= gTrue
;
4719 if (obj1
.dictLookup("CA", &obj2
)->isNum()) {
4720 opac
= obj2
.getNum();
4721 opac
= opac
< 0 ? 0 : opac
> 1 ? 1 : opac
;
4723 transpGroup
= gTrue
;
4727 if (!transpGroup
&& obj1
.dictLookup("AIS", &obj2
)->isBool()) {
4728 transpGroup
= obj2
.getBool();
4732 if (!transpGroup
&& !obj1
.dictLookup("SMask", &obj2
)->isNull()) {
4733 if (!obj2
.isName("None")) {
4734 transpGroup
= gTrue
;
4747 void Gfx::doForm(Object
*str
) {
4749 GBool transpGroup
, isolated
, knockout
;
4750 GfxColorSpace
*blendingColorSpace
;
4751 Object matrixObj
, bboxObj
;
4752 double m
[6], bbox
[4];
4756 Object obj1
, obj2
, obj3
;
4759 // check for excessive recursion
4760 if (formDepth
> 100) {
4765 dict
= str
->streamGetDict();
4768 dict
->lookup("FormType", &obj1
);
4769 if (!(obj1
.isNull() || (obj1
.isInt() && obj1
.getInt() == 1))) {
4770 error(errSyntaxError
, getPos(), "Unknown form type");
4774 // check for optional content key
4776 dict
->lookupNF("OC", &obj1
);
4777 if (catalog
->getOptContentConfig() && !catalog
->getOptContentConfig()->optContentIsVisible(&obj1
)) {
4779 if (out
->needCharCount()) {
4788 dict
->lookup("BBox", &bboxObj
);
4789 if (!bboxObj
.isArray()) {
4791 error(errSyntaxError
, getPos(), "Bad form bounding box");
4795 for (i
= 0; i
< 4; ++i
) {
4796 bboxObj
.arrayGet(i
, &obj1
);
4797 if (likely(obj1
.isNum())) {
4798 bbox
[i
] = obj1
.getNum();
4802 error(errSyntaxError
, getPos(), "Bad form bounding box value");
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();
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
);
4837 if (obj1
.dictLookup("I", &obj3
)->isBool()) {
4838 isolated
= obj3
.getBool();
4841 if (obj1
.dictLookup("K", &obj3
)->isBool()) {
4842 knockout
= obj3
.getBool();
4845 transpGroup
= isolated
|| out
->checkTransparencyGroup(state
, knockout
) || checkTransparencyGroup(resDict
);
4853 drawForm(str
, resDict
, m
, bbox
,
4854 transpGroup
, gFalse
, blendingColorSpace
, isolated
, knockout
);
4857 if (blendingColorSpace
) {
4858 delete blendingColorSpace
;
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
) {
4872 GfxState
*savedState
;
4873 double oldBaseMatrix
[6];
4876 // push new resources on stack
4877 pushResources(resDict
);
4879 // save current graphics state
4880 savedState
= saveStateStack();
4882 // kill any pre-existing path
4885 // save current 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]);
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
;
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
) {
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
];
4956 // restore graphics state
4957 restoreStateStack(savedState
);
4959 // pop resource stack
4963 out
->setSoftMask(state
, bbox
, alpha
, transferFunc
, backdropColor
);
4964 } else if (transpGroup
) {
4965 out
->paintTransparencyGroup(state
, bbox
);
4971 //------------------------------------------------------------------------
4972 // in-line image operators
4973 //------------------------------------------------------------------------
4975 void Gfx::opBeginImage(Object args
[], int numArgs
) {
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
4987 doImage(NULL
, str
, gTrue
);
4990 c1
= str
->getUndecodedStream()->getChar();
4991 c2
= str
->getUndecodedStream()->getChar();
4992 while (!(c1
== 'E' && c2
== 'I') && c2
!= EOF
) {
4994 c2
= str
->getUndecodedStream()->getChar();
5000 Stream
*Gfx::buildImageStream() {
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");
5014 key
= copyString(obj
.getName());
5016 parser
->getObj(&obj
);
5017 if (obj
.isEOF() || obj
.isError()) {
5021 dict
.dictAdd(key
, &obj
);
5023 parser
->getObj(&obj
);
5026 error(errSyntaxError
, getPos(), "End of file in inline image");
5034 if (parser
->getStream()) {
5035 str
= new EmbedStream(parser
->getStream(), &dict
, gFalse
, 0);
5036 str
= str
->addFilters(&dict
);
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
) {
5075 void Gfx::opEndIgnoreUndef(Object args
[], int numArgs
) {
5076 if (ignoreUndef
> 0)
5080 //------------------------------------------------------------------------
5081 // marked content operators
5082 //------------------------------------------------------------------------
5084 enum GfxMarkedContentKind
{
5085 gfxMCOptionalContent
,
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
;
5102 void Gfx::pushMarkedContent() {
5103 MarkedContentStack
*mc
= new MarkedContentStack();
5104 mc
->ocSuppressed
= gFalse
;
5105 mc
->kind
= gfxMCOther
;
5110 GBool
Gfx::contentIsHidden() {
5111 MarkedContentStack
*mc
= mcStack
;
5112 bool hidden
= mc
&& mc
->ocSuppressed
;
5113 while (!hidden
&& mc
&& mc
->next
) {
5115 hidden
= mc
->ocSuppressed
;
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
);
5139 error(errSyntaxError
, getPos(), "DID NOT find {0:s}", name1
);
5141 markedContent
.free();
5143 error(errSyntaxError
, getPos(), "insufficient arguments for Marked Content");
5145 } else if (args
[0].isName("Span") && numArgs
== 2 && args
[1].isDict()) {
5147 if (args
[1].dictLookup("ActualText", &obj
)->isString()) {
5148 out
->beginActualText(state
, obj
.getString());
5149 MarkedContentStack
*mc
= mcStack
;
5150 mc
->kind
= gfxMCActualText
;
5155 if (printCommands
) {
5156 printf(" marked content: %s ", args
[0].getName());
5158 args
[1].print(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
) {
5173 error(errSyntaxWarning
, getPos(), "Mismatched EMC operator");
5177 MarkedContentStack
*mc
= mcStack
;
5178 GfxMarkedContentKind mcKind
= mc
->kind
;
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());
5194 args
[1].print(stdout
);
5199 if(numArgs
== 2 && args
[1].isDict()) {
5200 out
->markPoint(args
[0].getName(),args
[1].getDict());
5202 out
->markPoint(args
[0].getName());
5207 //------------------------------------------------------------------------
5209 //------------------------------------------------------------------------
5211 struct GfxStackStateSaver
{
5212 GfxStackStateSaver(Gfx
*gfx
) : gfx(gfx
) {
5216 ~GfxStackStateSaver() {
5217 gfx
->restoreState();
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];
5232 double *dash
, *dash2
;
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
5241 if (xMin
== xMax
|| yMin
== yMax
) {
5245 // saves gfx state and automatically restores it on return
5246 GfxStackStateSaver
stackStateSaver(this);
5248 // Rotation around the topleft corner (for the NoRotate flag)
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] = {
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()) {
5271 dict
= str
->streamGetDict();
5273 // get the form bounding box
5274 dict
->lookup("BBox", &bboxObj
);
5275 if (!bboxObj
.isArray()) {
5277 error(errSyntaxError
, getPos(), "Bad form bounding box");
5280 for (i
= 0; i
< 4; ++i
) {
5281 bboxObj
.arrayGet(i
, &obj1
);
5282 if (likely(obj1
.isNum())) {
5283 bbox
[i
] = obj1
.getNum();
5288 error(errSyntaxError
, getPos(), "Bad form bounding box value");
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();
5305 error(errSyntaxError
, getPos(), "Bad form matrix");
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];
5326 } else if (x
> formXMax
) {
5331 } else if (y
> formYMax
) {
5334 x
= bbox
[2] * m
[0] + bbox
[1] * m
[2] + m
[4];
5335 y
= bbox
[2] * m
[1] + bbox
[1] * m
[3] + m
[5];
5338 } else if (x
> formXMax
) {
5343 } else if (y
> formYMax
) {
5346 x
= bbox
[2] * m
[0] + bbox
[3] * m
[2] + m
[4];
5347 y
= bbox
[2] * m
[1] + bbox
[3] * m
[3] + m
[5];
5350 } else if (x
> formXMax
) {
5355 } else if (y
> formYMax
) {
5359 // construct a mapping matrix, [sx 0 0], which maps the transformed
5362 // bbox to the annotation rectangle
5363 if (formXMin
== formXMax
) {
5364 // this shouldn't happen
5367 sx
= (xMax
- xMin
) / (formXMax
- formXMin
);
5369 if (formYMin
== formYMax
) {
5370 // this shouldn't happen
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)
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
;
5391 drawForm(str
, resDict
, m
, bbox
);
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();
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
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
);
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())
5450 stateGuards
.pop_back();
5453 void Gfx::saveState() {
5454 out
->saveState(state
);
5455 state
= state
->save();
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
;
5465 state
= state
->restore();
5466 out
->restoreState(state
);
5470 // Create a new state stack, and initialize it with a copy of the
5472 GfxState
*Gfx::saveStateStack() {
5475 out
->saveState(state
);
5477 state
= state
->copy(gTrue
);
5481 // Switch back to the previous state stack.
5482 void Gfx::restoreStateStack(GfxState
*oldState
) {
5483 while (state
->hasSaves()) {
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();