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-2015 Thomas Freitag <Thomas.Freitag@alfa.de>
32 // Copyright (C) 2009 William Bader <williambader@hotmail.com>
33 // Copyright (C) 2009, 2010 David Benjamin <davidben@mit.edu>
34 // Copyright (C) 2010 Nils Höglund <nils.hoglund@gmail.com>
35 // Copyright (C) 2010 Christian Feuersänger <cfeuersaenger@googlemail.com>
36 // Copyright (C) 2011 Axel Strübing <axel.struebing@freenet.de>
37 // Copyright (C) 2012 Even Rouault <even.rouault@mines-paris.org>
38 // Copyright (C) 2012, 2013 Fabio D'Urso <fabiodurso@hotmail.it>
39 // Copyright (C) 2012 Lu Wang <coolwanglu@gmail.com>
40 // Copyright (C) 2014 Jason Crain <jason@aquaticape.us>
42 // To see a description of the changes please see the Changelog file that
43 // came with your tarball or type make ChangeLog if you are building from git
45 //========================================================================
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 out
->updatePatternOpacity(state
);
2287 for (yi
= yi0
; yi
< yi1
; ++yi
) {
2288 for (xi
= xi0
; xi
< xi1
; ++xi
) {
2291 m1
[4] = x
* m
[0] + y
* m
[2] + m
[4];
2292 m1
[5] = x
* m
[1] + y
* m
[3] + m
[5];
2293 drawForm(tPat
->getContentStream(), tPat
->getResDict(),
2294 m1
, tPat
->getBBox());
2297 out
->clearPatternOpacity(state
);
2300 // restore graphics state
2302 restoreStateStack(savedState
);
2305 void Gfx::doShadingPatternFill(GfxShadingPattern
*sPat
,
2306 GBool stroke
, GBool eoFill
, GBool text
) {
2307 GfxShading
*shading
;
2308 GfxState
*savedState
;
2309 double *ctm
, *btm
, *ptm
;
2310 double m
[6], ictm
[6], m1
[6];
2311 double xMin
, yMin
, xMax
, yMax
;
2314 shading
= sPat
->getShading();
2316 // save current graphics state
2317 savedState
= saveStateStack();
2319 // clip to current path
2321 state
->clipToStrokePath();
2322 out
->clipToStrokePath(state
);
2333 // construct a (pattern space) -> (current space) transform matrix
2334 ctm
= state
->getCTM();
2336 ptm
= sPat
->getMatrix();
2337 // iCTM = invert CTM
2338 det
= 1 / (ctm
[0] * ctm
[3] - ctm
[1] * ctm
[2]);
2339 ictm
[0] = ctm
[3] * det
;
2340 ictm
[1] = -ctm
[1] * det
;
2341 ictm
[2] = -ctm
[2] * det
;
2342 ictm
[3] = ctm
[0] * det
;
2343 ictm
[4] = (ctm
[2] * ctm
[5] - ctm
[3] * ctm
[4]) * det
;
2344 ictm
[5] = (ctm
[1] * ctm
[4] - ctm
[0] * ctm
[5]) * det
;
2345 // m1 = PTM * BTM = PTM * base transform matrix
2346 m1
[0] = ptm
[0] * btm
[0] + ptm
[1] * btm
[2];
2347 m1
[1] = ptm
[0] * btm
[1] + ptm
[1] * btm
[3];
2348 m1
[2] = ptm
[2] * btm
[0] + ptm
[3] * btm
[2];
2349 m1
[3] = ptm
[2] * btm
[1] + ptm
[3] * btm
[3];
2350 m1
[4] = ptm
[4] * btm
[0] + ptm
[5] * btm
[2] + btm
[4];
2351 m1
[5] = ptm
[4] * btm
[1] + ptm
[5] * btm
[3] + btm
[5];
2352 // m = m1 * iCTM = (PTM * BTM) * (iCTM)
2353 m
[0] = m1
[0] * ictm
[0] + m1
[1] * ictm
[2];
2354 m
[1] = m1
[0] * ictm
[1] + m1
[1] * ictm
[3];
2355 m
[2] = m1
[2] * ictm
[0] + m1
[3] * ictm
[2];
2356 m
[3] = m1
[2] * ictm
[1] + m1
[3] * ictm
[3];
2357 m
[4] = m1
[4] * ictm
[0] + m1
[5] * ictm
[2] + ictm
[4];
2358 m
[5] = m1
[4] * ictm
[1] + m1
[5] * ictm
[3] + ictm
[5];
2360 // set the new matrix
2361 state
->concatCTM(m
[0], m
[1], m
[2], m
[3], m
[4], m
[5]);
2362 out
->updateCTM(state
, m
[0], m
[1], m
[2], m
[3], m
[4], m
[5]);
2365 if (shading
->getHasBBox()) {
2366 shading
->getBBox(&xMin
, &yMin
, &xMax
, &yMax
);
2367 state
->moveTo(xMin
, yMin
);
2368 state
->lineTo(xMax
, yMin
);
2369 state
->lineTo(xMax
, yMax
);
2370 state
->lineTo(xMin
, yMax
);
2377 // set the color space
2378 state
->setFillColorSpace(shading
->getColorSpace()->copy());
2379 out
->updateFillColorSpace(state
);
2381 // background color fill
2382 if (shading
->getHasBackground()) {
2383 state
->setFillColor(shading
->getBackground());
2384 out
->updateFillColor(state
);
2385 state
->getUserClipBBox(&xMin
, &yMin
, &xMax
, &yMax
);
2386 state
->moveTo(xMin
, yMin
);
2387 state
->lineTo(xMax
, yMin
);
2388 state
->lineTo(xMax
, yMax
);
2389 state
->lineTo(xMin
, yMax
);
2395 #if 1 //~tmp: turn off anti-aliasing temporarily
2396 GBool vaa
= out
->getVectorAntialias();
2398 out
->setVectorAntialias(gFalse
);
2402 // do shading type-specific operations
2403 switch (shading
->getType()) {
2405 doFunctionShFill((GfxFunctionShading
*)shading
);
2408 doAxialShFill((GfxAxialShading
*)shading
);
2411 doRadialShFill((GfxRadialShading
*)shading
);
2415 doGouraudTriangleShFill((GfxGouraudTriangleShading
*)shading
);
2419 doPatchMeshShFill((GfxPatchMeshShading
*)shading
);
2423 #if 1 //~tmp: turn off anti-aliasing temporarily
2425 out
->setVectorAntialias(gTrue
);
2429 // restore graphics state
2430 restoreStateStack(savedState
);
2433 void Gfx::opShFill(Object args
[], int numArgs
) {
2434 GfxShading
*shading
;
2435 GfxState
*savedState
;
2436 double xMin
, yMin
, xMax
, yMax
;
2442 if (!(shading
= res
->lookupShading(args
[0].getName(), out
, state
))) {
2446 // save current graphics state
2447 savedState
= saveStateStack();
2450 if (shading
->getHasBBox()) {
2451 shading
->getBBox(&xMin
, &yMin
, &xMax
, &yMax
);
2452 state
->moveTo(xMin
, yMin
);
2453 state
->lineTo(xMax
, yMin
);
2454 state
->lineTo(xMax
, yMax
);
2455 state
->lineTo(xMin
, yMax
);
2462 // set the color space
2463 state
->setFillColorSpace(shading
->getColorSpace()->copy());
2464 out
->updateFillColorSpace(state
);
2466 #if 1 //~tmp: turn off anti-aliasing temporarily
2467 GBool vaa
= out
->getVectorAntialias();
2469 out
->setVectorAntialias(gFalse
);
2473 // do shading type-specific operations
2474 switch (shading
->getType()) {
2476 doFunctionShFill((GfxFunctionShading
*)shading
);
2479 doAxialShFill((GfxAxialShading
*)shading
);
2482 doRadialShFill((GfxRadialShading
*)shading
);
2486 doGouraudTriangleShFill((GfxGouraudTriangleShading
*)shading
);
2490 doPatchMeshShFill((GfxPatchMeshShading
*)shading
);
2494 #if 1 //~tmp: turn off anti-aliasing temporarily
2496 out
->setVectorAntialias(gTrue
);
2500 // restore graphics state
2501 restoreStateStack(savedState
);
2506 void Gfx::doFunctionShFill(GfxFunctionShading
*shading
) {
2507 double x0
, y0
, x1
, y1
;
2510 if (out
->useShadedFills( shading
->getType() ) &&
2511 out
->functionShadedFill(state
, shading
)) {
2515 shading
->getDomain(&x0
, &y0
, &x1
, &y1
);
2516 shading
->getColor(x0
, y0
, &colors
[0]);
2517 shading
->getColor(x0
, y1
, &colors
[1]);
2518 shading
->getColor(x1
, y0
, &colors
[2]);
2519 shading
->getColor(x1
, y1
, &colors
[3]);
2520 doFunctionShFill1(shading
, x0
, y0
, x1
, y1
, colors
, 0);
2523 void Gfx::doFunctionShFill1(GfxFunctionShading
*shading
,
2524 double x0
, double y0
,
2525 double x1
, double y1
,
2526 GfxColor
*colors
, int depth
) {
2528 GfxColor color0M
, color1M
, colorM0
, colorM1
, colorMM
;
2529 GfxColor colors2
[4];
2534 nComps
= shading
->getColorSpace()->getNComps();
2535 matrix
= shading
->getMatrix();
2537 // compare the four corner colors
2538 for (i
= 0; i
< 4; ++i
) {
2539 for (j
= 0; j
< nComps
; ++j
) {
2540 if (abs(colors
[i
].c
[j
] - colors
[(i
+1)&3].c
[j
]) > functionColorDelta
) {
2549 // center of the rectangle
2550 xM
= 0.5 * (x0
+ x1
);
2551 yM
= 0.5 * (y0
+ y1
);
2553 // the four corner colors are close (or we hit the recursive limit)
2554 // -- fill the rectangle; but require at least one subdivision
2555 // (depth==0) to avoid problems when the four outer corners of the
2556 // shaded region are the same color
2557 if ((i
== 4 && depth
> 0) || depth
== functionMaxDepth
) {
2559 // use the center color
2560 shading
->getColor(xM
, yM
, &fillColor
);
2561 state
->setFillColor(&fillColor
);
2562 out
->updateFillColor(state
);
2564 // fill the rectangle
2565 state
->moveTo(x0
* matrix
[0] + y0
* matrix
[2] + matrix
[4],
2566 x0
* matrix
[1] + y0
* matrix
[3] + matrix
[5]);
2567 state
->lineTo(x1
* matrix
[0] + y0
* matrix
[2] + matrix
[4],
2568 x1
* matrix
[1] + y0
* matrix
[3] + matrix
[5]);
2569 state
->lineTo(x1
* matrix
[0] + y1
* matrix
[2] + matrix
[4],
2570 x1
* matrix
[1] + y1
* matrix
[3] + matrix
[5]);
2571 state
->lineTo(x0
* matrix
[0] + y1
* matrix
[2] + matrix
[4],
2572 x0
* matrix
[1] + y1
* matrix
[3] + matrix
[5]);
2577 // the four corner colors are not close enough -- subdivide the
2581 // colors[0] colorM0 colors[2]
2582 // (x0,y0) (xM,y0) (x1,y0)
2583 // +----------+----------+
2586 // color0M | colorMM | color1M
2587 // (x0,yM) +----------+----------+ (x1,yM)
2591 // +----------+----------+
2592 // colors[1] colorM1 colors[3]
2593 // (x0,y1) (xM,y1) (x1,y1)
2595 shading
->getColor(x0
, yM
, &color0M
);
2596 shading
->getColor(x1
, yM
, &color1M
);
2597 shading
->getColor(xM
, y0
, &colorM0
);
2598 shading
->getColor(xM
, y1
, &colorM1
);
2599 shading
->getColor(xM
, yM
, &colorMM
);
2601 // upper-left sub-rectangle
2602 colors2
[0] = colors
[0];
2603 colors2
[1] = color0M
;
2604 colors2
[2] = colorM0
;
2605 colors2
[3] = colorMM
;
2606 doFunctionShFill1(shading
, x0
, y0
, xM
, yM
, colors2
, depth
+ 1);
2608 // lower-left sub-rectangle
2609 colors2
[0] = color0M
;
2610 colors2
[1] = colors
[1];
2611 colors2
[2] = colorMM
;
2612 colors2
[3] = colorM1
;
2613 doFunctionShFill1(shading
, x0
, yM
, xM
, y1
, colors2
, depth
+ 1);
2615 // upper-right sub-rectangle
2616 colors2
[0] = colorM0
;
2617 colors2
[1] = colorMM
;
2618 colors2
[2] = colors
[2];
2619 colors2
[3] = color1M
;
2620 doFunctionShFill1(shading
, xM
, y0
, x1
, yM
, colors2
, depth
+ 1);
2622 // lower-right sub-rectangle
2623 colors2
[0] = colorMM
;
2624 colors2
[1] = colorM1
;
2625 colors2
[2] = color1M
;
2626 colors2
[3] = colors
[3];
2627 doFunctionShFill1(shading
, xM
, yM
, x1
, y1
, colors2
, depth
+ 1);
2631 static void bubbleSort(double array
[])
2633 for (int j
= 0; j
< 3; ++j
) {
2635 for (int k
= j
+ 1; k
< 4; ++k
) {
2636 if (array
[k
] < array
[kk
]) {
2640 double tmp
= array
[j
];
2641 array
[j
] = array
[kk
];
2646 void Gfx::doAxialShFill(GfxAxialShading
*shading
) {
2647 double xMin
, yMin
, xMax
, yMax
;
2648 double x0
, y0
, x1
, y1
;
2650 GBool dxZero
, dyZero
;
2651 double bboxIntersections
[4];
2652 double tMin
, tMax
, tx
, ty
;
2653 double s
[4], sMin
, sMax
, tmp
;
2654 double ux0
, uy0
, ux1
, uy1
, vx0
, vy0
, vx1
, vy1
;
2656 double ta
[axialMaxSplits
+ 1];
2657 int next
[axialMaxSplits
+ 1];
2658 GfxColor color0
, color1
;
2661 GBool needExtend
= gTrue
;
2663 // get the clip region bbox
2664 state
->getUserClipBBox(&xMin
, &yMin
, &xMax
, &yMax
);
2666 // compute min and max t values, based on the four corners of the
2668 shading
->getCoords(&x0
, &y0
, &x1
, &y1
);
2671 dxZero
= fabs(dx
) < 0.01;
2672 dyZero
= fabs(dy
) < 0.01;
2673 if (dxZero
&& dyZero
) {
2676 mul
= 1 / (dx
* dx
+ dy
* dy
);
2677 bboxIntersections
[0] = ((xMin
- x0
) * dx
+ (yMin
- y0
) * dy
) * mul
;
2678 bboxIntersections
[1] = ((xMin
- x0
) * dx
+ (yMax
- y0
) * dy
) * mul
;
2679 bboxIntersections
[2] = ((xMax
- x0
) * dx
+ (yMin
- y0
) * dy
) * mul
;
2680 bboxIntersections
[3] = ((xMax
- x0
) * dx
+ (yMax
- y0
) * dy
) * mul
;
2681 bubbleSort(bboxIntersections
);
2682 tMin
= bboxIntersections
[0];
2683 tMax
= bboxIntersections
[3];
2684 if (tMin
< 0 && !shading
->getExtend0()) {
2687 if (tMax
> 1 && !shading
->getExtend1()) {
2692 if (out
->useShadedFills( shading
->getType() ) &&
2693 out
->axialShadedFill(state
, shading
, tMin
, tMax
)) {
2697 // get the function domain
2698 t0
= shading
->getDomain0();
2699 t1
= shading
->getDomain1();
2701 // Traverse the t axis and do the shading.
2703 // For each point (tx, ty) on the t axis, consider a line through
2704 // that point perpendicular to the t axis:
2706 // x(s) = tx + s * -dy --> s = (x - tx) / -dy
2707 // y(s) = ty + s * dx --> s = (y - ty) / dx
2709 // Then look at the intersection of this line with the bounding box
2710 // (xMin, yMin, xMax, yMax). In the general case, there are four
2711 // intersection points:
2713 // s0 = (xMin - tx) / -dy
2714 // s1 = (xMax - tx) / -dy
2715 // s2 = (yMin - ty) / dx
2716 // s3 = (yMax - ty) / dx
2718 // and we want the middle two s values.
2720 // In the case where dx = 0, take s0 and s1; in the case where dy =
2721 // 0, take s2 and s3.
2723 // Each filled polygon is bounded by two of these line segments
2724 // perpdendicular to the t axis.
2726 // The t axis is bisected into smaller regions until the color
2727 // difference across a region is small enough, and then the region
2728 // is painted with a single color.
2730 // set up: require at least one split to avoid problems when the two
2731 // ends of the t axis have the same color
2732 nComps
= shading
->getColorSpace()->getNComps();
2734 next
[0] = axialMaxSplits
/ 2;
2735 ta
[axialMaxSplits
/ 2] = 0.5 * (tMin
+ tMax
);
2736 next
[axialMaxSplits
/ 2] = axialMaxSplits
;
2737 ta
[axialMaxSplits
] = tMax
;
2739 // compute the color at t = tMin
2742 } else if (tMin
> 1) {
2745 tt
= t0
+ (t1
- t0
) * tMin
;
2747 shading
->getColor(tt
, &color0
);
2749 if (out
->useFillColorStop()) {
2750 // make sure we add stop color when t = tMin
2751 state
->setFillColor(&color0
);
2752 out
->updateFillColorStop(state
, 0);
2755 // compute the coordinates of the point on the t axis at t = tMin;
2756 // then compute the intersection of the perpendicular line with the
2758 tx
= x0
+ tMin
* dx
;
2759 ty
= y0
+ tMin
* dy
;
2760 if (dxZero
&& dyZero
) {
2762 } else if (dxZero
) {
2763 sMin
= (xMin
- tx
) / -dy
;
2764 sMax
= (xMax
- tx
) / -dy
;
2765 if (sMin
> sMax
) { tmp
= sMin
; sMin
= sMax
; sMax
= tmp
; }
2766 } else if (dyZero
) {
2767 sMin
= (yMin
- ty
) / dx
;
2768 sMax
= (yMax
- ty
) / dx
;
2769 if (sMin
> sMax
) { tmp
= sMin
; sMin
= sMax
; sMax
= tmp
; }
2771 s
[0] = (yMin
- ty
) / dx
;
2772 s
[1] = (yMax
- ty
) / dx
;
2773 s
[2] = (xMin
- tx
) / -dy
;
2774 s
[3] = (xMax
- tx
) / -dy
;
2779 ux0
= tx
- sMin
* dy
;
2780 uy0
= ty
+ sMin
* dx
;
2781 vx0
= tx
- sMax
* dy
;
2782 vy0
= ty
+ sMax
* dx
;
2785 bool doneBBox1
, doneBBox2
;
2786 if (dxZero
&& dyZero
) {
2787 doneBBox1
= doneBBox2
= true;
2789 doneBBox1
= bboxIntersections
[1] < tMin
;
2790 doneBBox2
= bboxIntersections
[2] > tMax
;
2793 // If output device doesn't support the extended mode required
2794 // we have to do it here
2795 needExtend
= !out
->axialShadedSupportExtend(state
, shading
);
2797 while (i
< axialMaxSplits
) {
2799 // bisect until color difference is small enough or we hit the
2805 } else if (ta
[j
] > 1) {
2808 tt
= t0
+ (t1
- t0
) * ta
[j
];
2810 shading
->getColor(tt
, &color1
);
2811 if (isSameGfxColor(color1
, color0
, nComps
, axialColorDelta
)) {
2812 // in these two if what we guarantee is that if we are skipping lots of
2813 // positions because the colors are the same, we still create a region
2814 // with vertexs passing by bboxIntersections[1] and bboxIntersections[2]
2815 // otherwise we can have empty regions that should really be painted
2816 // like happened in bug 19896
2817 // What we do to ensure that we pass a line through this points
2818 // is making sure use the exact bboxIntersections[] value as one of the used ta[] values
2819 if (!doneBBox1
&& ta
[i
] < bboxIntersections
[1] && ta
[j
] > bboxIntersections
[1]) {
2820 int teoricalj
= (int) ((bboxIntersections
[1] - tMin
) * axialMaxSplits
/ (tMax
- tMin
));
2821 if (teoricalj
<= i
) teoricalj
= i
+ 1;
2822 if (teoricalj
< j
) {
2823 next
[i
] = teoricalj
;
2824 next
[teoricalj
] = j
;
2829 ta
[teoricalj
] = bboxIntersections
[1];
2833 if (!doneBBox2
&& ta
[i
] < bboxIntersections
[2] && ta
[j
] > bboxIntersections
[2]) {
2834 int teoricalj
= (int) ((bboxIntersections
[2] - tMin
) * axialMaxSplits
/ (tMax
- tMin
));
2835 if (teoricalj
<= i
) teoricalj
= i
+ 1;
2836 if (teoricalj
< j
) {
2837 next
[i
] = teoricalj
;
2838 next
[teoricalj
] = j
;
2843 ta
[teoricalj
] = bboxIntersections
[2];
2850 ta
[k
] = 0.5 * (ta
[i
] + ta
[j
]);
2856 // use the average of the colors of the two sides of the region
2857 for (k
= 0; k
< nComps
; ++k
) {
2858 color0
.c
[k
] = (color0
.c
[k
] + color1
.c
[k
]) / 2;
2861 // compute the coordinates of the point on the t axis; then
2862 // compute the intersection of the perpendicular line with the
2864 tx
= x0
+ ta
[j
] * dx
;
2865 ty
= y0
+ ta
[j
] * dy
;
2866 if (dxZero
&& dyZero
) {
2868 } else if (dxZero
) {
2869 sMin
= (xMin
- tx
) / -dy
;
2870 sMax
= (xMax
- tx
) / -dy
;
2871 if (sMin
> sMax
) { tmp
= sMin
; sMin
= sMax
; sMax
= tmp
; }
2872 } else if (dyZero
) {
2873 sMin
= (yMin
- ty
) / dx
;
2874 sMax
= (yMax
- ty
) / dx
;
2875 if (sMin
> sMax
) { tmp
= sMin
; sMin
= sMax
; sMax
= tmp
; }
2877 s
[0] = (yMin
- ty
) / dx
;
2878 s
[1] = (yMax
- ty
) / dx
;
2879 s
[2] = (xMin
- tx
) / -dy
;
2880 s
[3] = (xMax
- tx
) / -dy
;
2885 ux1
= tx
- sMin
* dy
;
2886 uy1
= ty
+ sMin
* dx
;
2887 vx1
= tx
- sMax
* dy
;
2888 vy1
= ty
+ sMax
* dx
;
2891 state
->setFillColor(&color0
);
2892 if (out
->useFillColorStop())
2893 out
->updateFillColorStop(state
, (ta
[j
] - tMin
)/(tMax
- tMin
));
2895 out
->updateFillColor(state
);
2899 state
->moveTo(ux0
, uy0
);
2900 state
->lineTo(vx0
, vy0
);
2901 state
->lineTo(vx1
, vy1
);
2902 state
->lineTo(ux1
, uy1
);
2906 if (!out
->useFillColorStop()) {
2911 // set up for next region
2920 if (out
->useFillColorStop()) {
2922 state
->moveTo(xMin
, yMin
);
2923 state
->lineTo(xMin
, yMax
);
2924 state
->lineTo(xMax
, yMax
);
2925 state
->lineTo(xMax
, yMin
);
2933 static inline void getShadingColorRadialHelper(double t0
, double t1
, double t
, GfxRadialShading
*shading
, GfxColor
*color
)
2937 shading
->getColor(t0
, color
);
2938 } else if (t
> t1
) {
2939 shading
->getColor(t1
, color
);
2941 shading
->getColor(t
, color
);
2945 shading
->getColor(t0
, color
);
2946 } else if (t
< t1
) {
2947 shading
->getColor(t1
, color
);
2949 shading
->getColor(t
, color
);
2954 void Gfx::doRadialShFill(GfxRadialShading
*shading
) {
2955 double xMin
, yMin
, xMax
, yMax
;
2956 double x0
, y0
, r0
, x1
, y1
, r1
, t0
, t1
;
2958 GfxColor colorA
, colorB
;
2959 double xa
, ya
, xb
, yb
, ra
, rb
;
2960 double ta
, tb
, sa
, sb
;
2961 double sz
, xz
, yz
, sMin
, sMax
;
2965 double theta
, alpha
, angle
, t
;
2966 GBool needExtend
= gTrue
;
2968 // get the shading info
2969 shading
->getCoords(&x0
, &y0
, &r0
, &x1
, &y1
, &r1
);
2970 t0
= shading
->getDomain0();
2971 t1
= shading
->getDomain1();
2972 nComps
= shading
->getColorSpace()->getNComps();
2974 // Compute the point at which r(s) = 0; check for the enclosed
2975 // circles case; and compute the angles for the tangent lines.
2976 if (x0
== x1
&& y0
== y1
) {
2978 theta
= 0; // make gcc happy
2979 sz
= 0; // make gcc happy
2980 } else if (r0
== r1
) {
2983 sz
= 0; // make gcc happy
2985 sz
= (r1
> r0
) ? -r0
/ (r1
- r0
) : -r1
/ (r0
- r1
);
2986 xz
= x0
+ sz
* (x1
- x0
);
2987 yz
= y0
+ sz
* (y1
- y0
);
2988 enclosed
= (xz
- x0
) * (xz
- x0
) + (yz
- y0
) * (yz
- y0
) <= r0
* r0
;
2989 theta
= asin(r0
/ sqrt((x0
- xz
) * (x0
- xz
) + (y0
- yz
) * (y0
- yz
)));
2997 alpha
= atan2(y1
- y0
, x1
- x0
);
3000 // compute the (possibly extended) s range
3001 state
->getUserClipBBox(&xMin
, &yMin
, &xMax
, &yMax
);
3008 // solve for x(s) + r(s) = xMin
3009 if ((x1
+ r1
) - (x0
+ r0
) != 0) {
3010 sa
= (xMin
- (x0
+ r0
)) / ((x1
+ r1
) - (x0
+ r0
));
3013 } else if (sa
> sMax
) {
3017 // solve for x(s) - r(s) = xMax
3018 if ((x1
- r1
) - (x0
- r0
) != 0) {
3019 sa
= (xMax
- (x0
- r0
)) / ((x1
- r1
) - (x0
- r0
));
3022 } else if (sa
> sMax
) {
3026 // solve for y(s) + r(s) = yMin
3027 if ((y1
+ r1
) - (y0
+ r0
) != 0) {
3028 sa
= (yMin
- (y0
+ r0
)) / ((y1
+ r1
) - (y0
+ r0
));
3031 } else if (sa
> sMax
) {
3035 // solve for y(s) - r(s) = yMax
3036 if ((y1
- r1
) - (y0
- r0
) != 0) {
3037 sa
= (yMax
- (y0
- r0
)) / ((y1
- r1
) - (y0
- r0
));
3040 } else if (sa
> sMax
) {
3049 } else if (r0
> r1
) {
3054 // check the 'extend' flags
3055 if (!shading
->getExtend0() && sMin
< 0) {
3058 if (!shading
->getExtend1() && sMax
> 1) {
3063 if (out
->useShadedFills( shading
->getType() ) &&
3064 out
->radialShadedFill(state
, shading
, sMin
, sMax
)) {
3068 // compute the number of steps into which circles must be divided to
3069 // achieve a curve flatness of 0.1 pixel in device space for the
3070 // largest circle (note that "device space" is 72 dpi when generating
3071 // PostScript, hence the relatively small 0.1 pixel accuracy)
3072 ctm
= state
->getCTM();
3074 if (fabs(ctm
[1]) > t
) {
3077 if (fabs(ctm
[2]) > t
) {
3080 if (fabs(ctm
[3]) > t
) {
3091 n
= (int)(M_PI
/ acos(1 - 0.1 / t
));
3094 } else if (n
> 200) {
3099 // setup for the start circle
3102 ta
= t0
+ sa
* (t1
- t0
);
3103 xa
= x0
+ sa
* (x1
- x0
);
3104 ya
= y0
+ sa
* (y1
- y0
);
3105 ra
= r0
+ sa
* (r1
- r0
);
3106 getShadingColorRadialHelper(t0
, t1
, ta
, shading
, &colorA
);
3108 needExtend
= !out
->radialShadedSupportExtend(state
, shading
);
3111 while (ia
< radialMaxSplits
) {
3113 // go as far along the t axis (toward t1) as we can, such that the
3114 // color difference is within the tolerance (radialColorDelta) --
3115 // this uses bisection (between the current value, t, and t1),
3116 // limited to radialMaxSplits points along the t axis; require at
3117 // least one split to avoid problems when the innermost and
3118 // outermost colors are the same
3119 ib
= radialMaxSplits
;
3121 tb
= t0
+ sb
* (t1
- t0
);
3122 getShadingColorRadialHelper(t0
, t1
, tb
, shading
, &colorB
);
3123 while (ib
- ia
> 1) {
3124 if (isSameGfxColor(colorB
, colorA
, nComps
, radialColorDelta
)) {
3125 // The shading is not necessarily lineal so having two points with the
3126 // same color does not mean all the areas in between have the same color too
3128 for (; ic
<= ib
; ic
++) {
3130 const double sc
= sMin
+ ((double)ic
/ (double)radialMaxSplits
) * (sMax
- sMin
);
3131 const double tc
= t0
+ sc
* (t1
- t0
);
3132 getShadingColorRadialHelper(t0
, t1
, tc
, shading
, &colorC
);
3133 if (!isSameGfxColor(colorC
, colorA
, nComps
, radialColorDelta
)) {
3137 ib
= (ic
> ia
+ 1) ? ic
- 1 : ia
+ 1;
3138 sb
= sMin
+ ((double)ib
/ (double)radialMaxSplits
) * (sMax
- sMin
);
3139 tb
= t0
+ sb
* (t1
- t0
);
3140 getShadingColorRadialHelper(t0
, t1
, tb
, shading
, &colorB
);
3144 sb
= sMin
+ ((double)ib
/ (double)radialMaxSplits
) * (sMax
- sMin
);
3145 tb
= t0
+ sb
* (t1
- t0
);
3146 getShadingColorRadialHelper(t0
, t1
, tb
, shading
, &colorB
);
3149 // compute center and radius of the circle
3150 xb
= x0
+ sb
* (x1
- x0
);
3151 yb
= y0
+ sb
* (y1
- y0
);
3152 rb
= r0
+ sb
* (r1
- r0
);
3154 // use the average of the colors at the two circles
3155 for (k
= 0; k
< nComps
; ++k
) {
3156 colorA
.c
[k
] = (colorA
.c
[k
] + colorB
.c
[k
]) / 2;
3158 state
->setFillColor(&colorA
);
3159 if (out
->useFillColorStop())
3160 out
->updateFillColorStop(state
, (sa
- sMin
)/(sMax
- sMin
));
3162 out
->updateFillColor(state
);
3166 // construct path for first circle (counterclockwise)
3167 state
->moveTo(xa
+ ra
, ya
);
3168 for (k
= 1; k
< n
; ++k
) {
3169 angle
= ((double)k
/ (double)n
) * 2 * M_PI
;
3170 state
->lineTo(xa
+ ra
* cos(angle
), ya
+ ra
* sin(angle
));
3174 // construct and append path for second circle (clockwise)
3175 state
->moveTo(xb
+ rb
, yb
);
3176 for (k
= 1; k
< n
; ++k
) {
3177 angle
= -((double)k
/ (double)n
) * 2 * M_PI
;
3178 state
->lineTo(xb
+ rb
* cos(angle
), yb
+ rb
* sin(angle
));
3182 // construct the first subpath (clockwise)
3183 state
->moveTo(xa
+ ra
* cos(alpha
+ theta
+ 0.5 * M_PI
),
3184 ya
+ ra
* sin(alpha
+ theta
+ 0.5 * M_PI
));
3185 for (k
= 0; k
< n
; ++k
) {
3186 angle
= alpha
+ theta
+ 0.5 * M_PI
3187 - ((double)k
/ (double)n
) * (2 * theta
+ M_PI
);
3188 state
->lineTo(xb
+ rb
* cos(angle
), yb
+ rb
* sin(angle
));
3190 for (k
= 0; k
< n
; ++k
) {
3191 angle
= alpha
- theta
- 0.5 * M_PI
3192 + ((double)k
/ (double)n
) * (2 * theta
- M_PI
);
3193 state
->lineTo(xa
+ ra
* cos(angle
), ya
+ ra
* sin(angle
));
3197 // construct the second subpath (counterclockwise)
3198 state
->moveTo(xa
+ ra
* cos(alpha
+ theta
+ 0.5 * M_PI
),
3199 ya
+ ra
* sin(alpha
+ theta
+ 0.5 * M_PI
));
3200 for (k
= 0; k
< n
; ++k
) {
3201 angle
= alpha
+ theta
+ 0.5 * M_PI
3202 + ((double)k
/ (double)n
) * (-2 * theta
+ M_PI
);
3203 state
->lineTo(xb
+ rb
* cos(angle
), yb
+ rb
* sin(angle
));
3205 for (k
= 0; k
< n
; ++k
) {
3206 angle
= alpha
- theta
- 0.5 * M_PI
3207 + ((double)k
/ (double)n
) * (2 * theta
+ M_PI
);
3208 state
->lineTo(xa
+ ra
* cos(angle
), ya
+ ra
* sin(angle
));
3214 if (!out
->useFillColorStop()) {
3220 // step to the next value of t
3230 if (out
->useFillColorStop()) {
3231 // make sure we add stop color when sb = sMax
3232 state
->setFillColor(&colorA
);
3233 out
->updateFillColorStop(state
, (sb
- sMin
)/(sMax
- sMin
));
3236 state
->moveTo(xMin
, yMin
);
3237 state
->lineTo(xMin
, yMax
);
3238 state
->lineTo(xMax
, yMax
);
3239 state
->lineTo(xMax
, yMin
);
3250 // extend the smaller circle
3251 if ((shading
->getExtend0() && r0
<= r1
) ||
3252 (shading
->getExtend1() && r1
< r0
)) {
3264 shading
->getColor(ta
, &colorA
);
3265 state
->setFillColor(&colorA
);
3266 out
->updateFillColor(state
);
3267 state
->moveTo(xa
+ ra
, ya
);
3268 for (k
= 1; k
< n
; ++k
) {
3269 angle
= ((double)k
/ (double)n
) * 2 * M_PI
;
3270 state
->lineTo(xa
+ ra
* cos(angle
), ya
+ ra
* sin(angle
));
3277 // extend the larger circle
3278 if ((shading
->getExtend0() && r0
> r1
) ||
3279 (shading
->getExtend1() && r1
>= r0
)) {
3291 shading
->getColor(ta
, &colorA
);
3292 state
->setFillColor(&colorA
);
3293 out
->updateFillColor(state
);
3294 state
->moveTo(xMin
, yMin
);
3295 state
->lineTo(xMin
, yMax
);
3296 state
->lineTo(xMax
, yMax
);
3297 state
->lineTo(xMax
, yMin
);
3299 state
->moveTo(xa
+ ra
, ya
);
3300 for (k
= 1; k
< n
; ++k
) {
3301 angle
= ((double)k
/ (double)n
) * 2 * M_PI
;
3302 state
->lineTo(xa
+ ra
* cos(angle
), ya
+ ra
* sin(angle
));
3311 void Gfx::doGouraudTriangleShFill(GfxGouraudTriangleShading
*shading
) {
3312 double x0
, y0
, x1
, y1
, x2
, y2
;
3315 if (out
->useShadedFills( shading
->getType())) {
3316 if (out
->gouraudTriangleShadedFill( state
, shading
))
3320 // preallocate a path (speed improvements)
3321 state
->moveTo(0., 0.);
3322 state
->lineTo(1., 0.);
3323 state
->lineTo(0., 1.);
3326 GfxState::ReusablePathIterator
*reusablePath
= state
->getReusablePath();
3328 if (shading
->isParameterized()) {
3329 // work with parameterized values:
3330 double color0
, color1
, color2
;
3331 // a relative threshold:
3332 const double refineColorThreshold
= gouraudParameterizedColorDelta
*
3333 (shading
->getParameterDomainMax() - shading
->getParameterDomainMin());
3334 for (i
= 0; i
< shading
->getNTriangles(); ++i
) {
3335 shading
->getTriangle(i
, &x0
, &y0
, &color0
,
3338 gouraudFillTriangle(x0
, y0
, color0
, x1
, y1
, color1
, x2
, y2
, color2
, refineColorThreshold
, 0, shading
, reusablePath
);
3342 // this always produces output -- even for parameterized ranges.
3343 // But it ignores the parameterized color map (the function).
3345 // Note that using this code in for parameterized shadings might be
3346 // correct in circumstances (namely if the function is linear in the actual
3347 // triangle), but in general, it will simply be wrong.
3348 GfxColor color0
, color1
, color2
;
3349 for (i
= 0; i
< shading
->getNTriangles(); ++i
) {
3350 shading
->getTriangle(i
, &x0
, &y0
, &color0
,
3353 gouraudFillTriangle(x0
, y0
, &color0
, x1
, y1
, &color1
, x2
, y2
, &color2
, shading
->getColorSpace()->getNComps(), 0, reusablePath
);
3357 delete reusablePath
;
3360 static inline void checkTrue(bool b
, const char *message
) {
3362 error(errSyntaxError
, -1, message
);
3366 void Gfx::gouraudFillTriangle(double x0
, double y0
, GfxColor
*color0
,
3367 double x1
, double y1
, GfxColor
*color1
,
3368 double x2
, double y2
, GfxColor
*color2
,
3369 int nComps
, int depth
, GfxState::ReusablePathIterator
*path
) {
3370 double x01
, y01
, x12
, y12
, x20
, y20
;
3371 GfxColor color01
, color12
, color20
;
3374 for (i
= 0; i
< nComps
; ++i
) {
3375 if (abs(color0
->c
[i
] - color1
->c
[i
]) > gouraudColorDelta
||
3376 abs(color1
->c
[i
] - color2
->c
[i
]) > gouraudColorDelta
) {
3380 if (i
== nComps
|| depth
== gouraudMaxDepth
) {
3381 state
->setFillColor(color0
);
3382 out
->updateFillColor(state
);
3384 path
->reset(); checkTrue(!path
->isEnd(), "Path should not be at end");
3385 path
->setCoord(x0
,y0
); path
->next(); checkTrue(!path
->isEnd(), "Path should not be at end");
3386 path
->setCoord(x1
,y1
); path
->next(); checkTrue(!path
->isEnd(), "Path should not be at end");
3387 path
->setCoord(x2
,y2
); path
->next(); checkTrue(!path
->isEnd(), "Path should not be at end");
3388 path
->setCoord(x0
,y0
); path
->next(); checkTrue( path
->isEnd(), "Path should be at end");
3392 x01
= 0.5 * (x0
+ x1
);
3393 y01
= 0.5 * (y0
+ y1
);
3394 x12
= 0.5 * (x1
+ x2
);
3395 y12
= 0.5 * (y1
+ y2
);
3396 x20
= 0.5 * (x2
+ x0
);
3397 y20
= 0.5 * (y2
+ y0
);
3398 for (i
= 0; i
< nComps
; ++i
) {
3399 color01
.c
[i
] = (color0
->c
[i
] + color1
->c
[i
]) / 2;
3400 color12
.c
[i
] = (color1
->c
[i
] + color2
->c
[i
]) / 2;
3401 color20
.c
[i
] = (color2
->c
[i
] + color0
->c
[i
]) / 2;
3403 gouraudFillTriangle(x0
, y0
, color0
, x01
, y01
, &color01
,
3404 x20
, y20
, &color20
, nComps
, depth
+ 1, path
);
3405 gouraudFillTriangle(x01
, y01
, &color01
, x1
, y1
, color1
,
3406 x12
, y12
, &color12
, nComps
, depth
+ 1, path
);
3407 gouraudFillTriangle(x01
, y01
, &color01
, x12
, y12
, &color12
,
3408 x20
, y20
, &color20
, nComps
, depth
+ 1, path
);
3409 gouraudFillTriangle(x20
, y20
, &color20
, x12
, y12
, &color12
,
3410 x2
, y2
, color2
, nComps
, depth
+ 1, path
);
3413 void Gfx::gouraudFillTriangle(double x0
, double y0
, double color0
,
3414 double x1
, double y1
, double color1
,
3415 double x2
, double y2
, double color2
,
3416 double refineColorThreshold
, int depth
, GfxGouraudTriangleShading
*shading
, GfxState::ReusablePathIterator
*path
) {
3417 const double meanColor
= (color0
+ color1
+ color2
) / 3;
3419 const bool isFineEnough
=
3420 fabs(color0
- meanColor
) < refineColorThreshold
&&
3421 fabs(color1
- meanColor
) < refineColorThreshold
&&
3422 fabs(color2
- meanColor
) < refineColorThreshold
;
3424 if (isFineEnough
|| depth
== gouraudMaxDepth
) {
3427 shading
->getParameterizedColor(meanColor
, &color
);
3428 state
->setFillColor(&color
);
3429 out
->updateFillColor(state
);
3431 path
->reset(); checkTrue(!path
->isEnd(), "Path should not be at end");
3432 path
->setCoord(x0
,y0
); path
->next(); checkTrue(!path
->isEnd(), "Path should not be at end");
3433 path
->setCoord(x1
,y1
); path
->next(); checkTrue(!path
->isEnd(), "Path should not be at end");
3434 path
->setCoord(x2
,y2
); path
->next(); checkTrue(!path
->isEnd(), "Path should not be at end");
3435 path
->setCoord(x0
,y0
); path
->next(); checkTrue( path
->isEnd(), "Path should be at end");
3439 const double x01
= 0.5 * (x0
+ x1
);
3440 const double y01
= 0.5 * (y0
+ y1
);
3441 const double x12
= 0.5 * (x1
+ x2
);
3442 const double y12
= 0.5 * (y1
+ y2
);
3443 const double x20
= 0.5 * (x2
+ x0
);
3444 const double y20
= 0.5 * (y2
+ y0
);
3445 const double color01
= (color0
+ color1
) / 2.;
3446 const double color12
= (color1
+ color2
) / 2.;
3447 const double color20
= (color2
+ color0
) / 2.;
3449 gouraudFillTriangle(x0
, y0
, color0
,
3452 refineColorThreshold
, depth
, shading
, path
);
3453 gouraudFillTriangle(x01
, y01
, color01
,
3456 refineColorThreshold
, depth
, shading
, path
);
3457 gouraudFillTriangle(x01
, y01
, color01
,
3460 refineColorThreshold
, depth
, shading
, path
);
3461 gouraudFillTriangle(x20
, y20
, color20
,
3464 refineColorThreshold
, depth
, shading
, path
);
3468 void Gfx::doPatchMeshShFill(GfxPatchMeshShading
*shading
) {
3471 if (out
->useShadedFills( shading
->getType())) {
3472 if (out
->patchMeshShadedFill( state
, shading
))
3476 if (shading
->getNPatches() > 128) {
3478 } else if (shading
->getNPatches() > 64) {
3480 } else if (shading
->getNPatches() > 16) {
3486 * Parameterized shadings take one parameter [t_0,t_e]
3487 * and map it into the color space.
3489 * Consequently, all color values are stored as doubles.
3491 * These color values are interpreted as parameters for parameterized
3492 * shadings and as colorspace entities otherwise.
3494 * The only difference is that color space entities are stored into
3495 * DOUBLE arrays, not into arrays of type GfxColorComp.
3497 const int colorComps
= shading
->getColorSpace()->getNComps();
3498 double refineColorThreshold
;
3499 if( shading
->isParameterized() ) {
3500 refineColorThreshold
= gouraudParameterizedColorDelta
*
3501 (shading
->getParameterDomainMax() - shading
->getParameterDomainMin());
3504 refineColorThreshold
= patchColorDelta
;
3507 for (i
= 0; i
< shading
->getNPatches(); ++i
) {
3508 fillPatch(shading
->getPatch(i
),
3510 shading
->isParameterized() ? 1 : colorComps
,
3511 refineColorThreshold
,
3518 void Gfx::fillPatch(GfxPatch
*patch
, int colorComps
, int patchColorComps
, double refineColorThreshold
, int depth
, GfxPatchMeshShading
*shading
) {
3519 GfxPatch patch00
, patch01
, patch10
, patch11
;
3520 double xx
[4][8], yy
[4][8];
3524 for (i
= 0; i
< patchColorComps
; ++i
) {
3525 // these comparisons are done in double arithmetics.
3527 // For non-parameterized shadings, they are done in color space
3529 if (fabs(patch
->color
[0][0].c
[i
] - patch
->color
[0][1].c
[i
]) > refineColorThreshold
||
3530 fabs(patch
->color
[0][1].c
[i
] - patch
->color
[1][1].c
[i
]) > refineColorThreshold
||
3531 fabs(patch
->color
[1][1].c
[i
] - patch
->color
[1][0].c
[i
]) > refineColorThreshold
||
3532 fabs(patch
->color
[1][0].c
[i
] - patch
->color
[0][0].c
[i
]) > refineColorThreshold
) {
3536 if (i
== patchColorComps
|| depth
== patchMaxDepth
) {
3538 if( shading
->isParameterized() ) {
3539 shading
->getParameterizedColor( patch
->color
[0][0].c
[0], &flatColor
);
3541 for( i
= 0; i
<colorComps
; ++i
) {
3542 // simply cast to the desired type; that's all what is needed.
3543 flatColor
.c
[i
] = GfxColorComp(patch
->color
[0][0].c
[i
]);
3546 state
->setFillColor(&flatColor
);
3547 out
->updateFillColor(state
);
3548 state
->moveTo(patch
->x
[0][0], patch
->y
[0][0]);
3549 state
->curveTo(patch
->x
[0][1], patch
->y
[0][1],
3550 patch
->x
[0][2], patch
->y
[0][2],
3551 patch
->x
[0][3], patch
->y
[0][3]);
3552 state
->curveTo(patch
->x
[1][3], patch
->y
[1][3],
3553 patch
->x
[2][3], patch
->y
[2][3],
3554 patch
->x
[3][3], patch
->y
[3][3]);
3555 state
->curveTo(patch
->x
[3][2], patch
->y
[3][2],
3556 patch
->x
[3][1], patch
->y
[3][1],
3557 patch
->x
[3][0], patch
->y
[3][0]);
3558 state
->curveTo(patch
->x
[2][0], patch
->y
[2][0],
3559 patch
->x
[1][0], patch
->y
[1][0],
3560 patch
->x
[0][0], patch
->y
[0][0]);
3565 for (i
= 0; i
< 4; ++i
) {
3566 xx
[i
][0] = patch
->x
[i
][0];
3567 yy
[i
][0] = patch
->y
[i
][0];
3568 xx
[i
][1] = 0.5 * (patch
->x
[i
][0] + patch
->x
[i
][1]);
3569 yy
[i
][1] = 0.5 * (patch
->y
[i
][0] + patch
->y
[i
][1]);
3570 xxm
= 0.5 * (patch
->x
[i
][1] + patch
->x
[i
][2]);
3571 yym
= 0.5 * (patch
->y
[i
][1] + patch
->y
[i
][2]);
3572 xx
[i
][6] = 0.5 * (patch
->x
[i
][2] + patch
->x
[i
][3]);
3573 yy
[i
][6] = 0.5 * (patch
->y
[i
][2] + patch
->y
[i
][3]);
3574 xx
[i
][2] = 0.5 * (xx
[i
][1] + xxm
);
3575 yy
[i
][2] = 0.5 * (yy
[i
][1] + yym
);
3576 xx
[i
][5] = 0.5 * (xxm
+ xx
[i
][6]);
3577 yy
[i
][5] = 0.5 * (yym
+ yy
[i
][6]);
3578 xx
[i
][3] = xx
[i
][4] = 0.5 * (xx
[i
][2] + xx
[i
][5]);
3579 yy
[i
][3] = yy
[i
][4] = 0.5 * (yy
[i
][2] + yy
[i
][5]);
3580 xx
[i
][7] = patch
->x
[i
][3];
3581 yy
[i
][7] = patch
->y
[i
][3];
3583 for (i
= 0; i
< 4; ++i
) {
3584 patch00
.x
[0][i
] = xx
[0][i
];
3585 patch00
.y
[0][i
] = yy
[0][i
];
3586 patch00
.x
[1][i
] = 0.5 * (xx
[0][i
] + xx
[1][i
]);
3587 patch00
.y
[1][i
] = 0.5 * (yy
[0][i
] + yy
[1][i
]);
3588 xxm
= 0.5 * (xx
[1][i
] + xx
[2][i
]);
3589 yym
= 0.5 * (yy
[1][i
] + yy
[2][i
]);
3590 patch10
.x
[2][i
] = 0.5 * (xx
[2][i
] + xx
[3][i
]);
3591 patch10
.y
[2][i
] = 0.5 * (yy
[2][i
] + yy
[3][i
]);
3592 patch00
.x
[2][i
] = 0.5 * (patch00
.x
[1][i
] + xxm
);
3593 patch00
.y
[2][i
] = 0.5 * (patch00
.y
[1][i
] + yym
);
3594 patch10
.x
[1][i
] = 0.5 * (xxm
+ patch10
.x
[2][i
]);
3595 patch10
.y
[1][i
] = 0.5 * (yym
+ patch10
.y
[2][i
]);
3596 patch00
.x
[3][i
] = 0.5 * (patch00
.x
[2][i
] + patch10
.x
[1][i
]);
3597 patch00
.y
[3][i
] = 0.5 * (patch00
.y
[2][i
] + patch10
.y
[1][i
]);
3598 patch10
.x
[0][i
] = patch00
.x
[3][i
];
3599 patch10
.y
[0][i
] = patch00
.y
[3][i
];
3600 patch10
.x
[3][i
] = xx
[3][i
];
3601 patch10
.y
[3][i
] = yy
[3][i
];
3603 for (i
= 4; i
< 8; ++i
) {
3604 patch01
.x
[0][i
-4] = xx
[0][i
];
3605 patch01
.y
[0][i
-4] = yy
[0][i
];
3606 patch01
.x
[1][i
-4] = 0.5 * (xx
[0][i
] + xx
[1][i
]);
3607 patch01
.y
[1][i
-4] = 0.5 * (yy
[0][i
] + yy
[1][i
]);
3608 xxm
= 0.5 * (xx
[1][i
] + xx
[2][i
]);
3609 yym
= 0.5 * (yy
[1][i
] + yy
[2][i
]);
3610 patch11
.x
[2][i
-4] = 0.5 * (xx
[2][i
] + xx
[3][i
]);
3611 patch11
.y
[2][i
-4] = 0.5 * (yy
[2][i
] + yy
[3][i
]);
3612 patch01
.x
[2][i
-4] = 0.5 * (patch01
.x
[1][i
-4] + xxm
);
3613 patch01
.y
[2][i
-4] = 0.5 * (patch01
.y
[1][i
-4] + yym
);
3614 patch11
.x
[1][i
-4] = 0.5 * (xxm
+ patch11
.x
[2][i
-4]);
3615 patch11
.y
[1][i
-4] = 0.5 * (yym
+ patch11
.y
[2][i
-4]);
3616 patch01
.x
[3][i
-4] = 0.5 * (patch01
.x
[2][i
-4] + patch11
.x
[1][i
-4]);
3617 patch01
.y
[3][i
-4] = 0.5 * (patch01
.y
[2][i
-4] + patch11
.y
[1][i
-4]);
3618 patch11
.x
[0][i
-4] = patch01
.x
[3][i
-4];
3619 patch11
.y
[0][i
-4] = patch01
.y
[3][i
-4];
3620 patch11
.x
[3][i
-4] = xx
[3][i
];
3621 patch11
.y
[3][i
-4] = yy
[3][i
];
3623 for (i
= 0; i
< patchColorComps
; ++i
) {
3624 patch00
.color
[0][0].c
[i
] = patch
->color
[0][0].c
[i
];
3625 patch00
.color
[0][1].c
[i
] = (patch
->color
[0][0].c
[i
] +
3626 patch
->color
[0][1].c
[i
]) / 2;
3627 patch01
.color
[0][0].c
[i
] = patch00
.color
[0][1].c
[i
];
3628 patch01
.color
[0][1].c
[i
] = patch
->color
[0][1].c
[i
];
3629 patch01
.color
[1][1].c
[i
] = (patch
->color
[0][1].c
[i
] +
3630 patch
->color
[1][1].c
[i
]) / 2;
3631 patch11
.color
[0][1].c
[i
] = patch01
.color
[1][1].c
[i
];
3632 patch11
.color
[1][1].c
[i
] = patch
->color
[1][1].c
[i
];
3633 patch11
.color
[1][0].c
[i
] = (patch
->color
[1][1].c
[i
] +
3634 patch
->color
[1][0].c
[i
]) / 2;
3635 patch10
.color
[1][1].c
[i
] = patch11
.color
[1][0].c
[i
];
3636 patch10
.color
[1][0].c
[i
] = patch
->color
[1][0].c
[i
];
3637 patch10
.color
[0][0].c
[i
] = (patch
->color
[1][0].c
[i
] +
3638 patch
->color
[0][0].c
[i
]) / 2;
3639 patch00
.color
[1][0].c
[i
] = patch10
.color
[0][0].c
[i
];
3640 patch00
.color
[1][1].c
[i
] = (patch00
.color
[1][0].c
[i
] +
3641 patch01
.color
[1][1].c
[i
]) / 2;
3642 patch01
.color
[1][0].c
[i
] = patch00
.color
[1][1].c
[i
];
3643 patch11
.color
[0][0].c
[i
] = patch00
.color
[1][1].c
[i
];
3644 patch10
.color
[0][1].c
[i
] = patch00
.color
[1][1].c
[i
];
3646 fillPatch(&patch00
, colorComps
, patchColorComps
, refineColorThreshold
, depth
+ 1, shading
);
3647 fillPatch(&patch10
, colorComps
, patchColorComps
, refineColorThreshold
, depth
+ 1, shading
);
3648 fillPatch(&patch01
, colorComps
, patchColorComps
, refineColorThreshold
, depth
+ 1, shading
);
3649 fillPatch(&patch11
, colorComps
, patchColorComps
, refineColorThreshold
, depth
+ 1, shading
);
3653 void Gfx::doEndPath() {
3654 if (state
->isCurPt() && clip
!= clipNone
) {
3656 if (clip
== clipNormal
) {
3666 //------------------------------------------------------------------------
3667 // path clipping operators
3668 //------------------------------------------------------------------------
3670 void Gfx::opClip(Object args
[], int numArgs
) {
3674 void Gfx::opEOClip(Object args
[], int numArgs
) {
3678 //------------------------------------------------------------------------
3679 // text object operators
3680 //------------------------------------------------------------------------
3682 void Gfx::opBeginText(Object args
[], int numArgs
) {
3683 out
->beginTextObject(state
);
3684 state
->setTextMat(1, 0, 0, 1, 0, 0);
3685 state
->textMoveTo(0, 0);
3686 out
->updateTextMat(state
);
3687 out
->updateTextPos(state
);
3688 fontChanged
= gTrue
;
3691 void Gfx::opEndText(Object args
[], int numArgs
) {
3692 out
->endTextObject(state
);
3695 //------------------------------------------------------------------------
3696 // text state operators
3697 //------------------------------------------------------------------------
3699 void Gfx::opSetCharSpacing(Object args
[], int numArgs
) {
3700 state
->setCharSpace(args
[0].getNum());
3701 out
->updateCharSpace(state
);
3704 void Gfx::opSetFont(Object args
[], int numArgs
) {
3707 if (!(font
= res
->lookupFont(args
[0].getName()))) {
3708 // unsetting the font (drawing no text) is better than using the
3709 // previous one and drawing random glyphs from it
3710 state
->setFont(NULL
, args
[1].getNum());
3711 fontChanged
= gTrue
;
3714 if (printCommands
) {
3715 printf(" font: tag=%s name='%s' %g\n",
3716 font
->getTag()->getCString(),
3717 font
->getName() ? font
->getName()->getCString() : "???",
3723 state
->setFont(font
, args
[1].getNum());
3724 fontChanged
= gTrue
;
3727 void Gfx::opSetTextLeading(Object args
[], int numArgs
) {
3728 state
->setLeading(args
[0].getNum());
3731 void Gfx::opSetTextRender(Object args
[], int numArgs
) {
3732 state
->setRender(args
[0].getInt());
3733 out
->updateRender(state
);
3736 void Gfx::opSetTextRise(Object args
[], int numArgs
) {
3737 state
->setRise(args
[0].getNum());
3738 out
->updateRise(state
);
3741 void Gfx::opSetWordSpacing(Object args
[], int numArgs
) {
3742 state
->setWordSpace(args
[0].getNum());
3743 out
->updateWordSpace(state
);
3746 void Gfx::opSetHorizScaling(Object args
[], int numArgs
) {
3747 state
->setHorizScaling(args
[0].getNum());
3748 out
->updateHorizScaling(state
);
3749 fontChanged
= gTrue
;
3752 //------------------------------------------------------------------------
3753 // text positioning operators
3754 //------------------------------------------------------------------------
3756 void Gfx::opTextMove(Object args
[], int numArgs
) {
3759 tx
= state
->getLineX() + args
[0].getNum();
3760 ty
= state
->getLineY() + args
[1].getNum();
3761 state
->textMoveTo(tx
, ty
);
3762 out
->updateTextPos(state
);
3765 void Gfx::opTextMoveSet(Object args
[], int numArgs
) {
3768 tx
= state
->getLineX() + args
[0].getNum();
3769 ty
= args
[1].getNum();
3770 state
->setLeading(-ty
);
3771 ty
+= state
->getLineY();
3772 state
->textMoveTo(tx
, ty
);
3773 out
->updateTextPos(state
);
3776 void Gfx::opSetTextMatrix(Object args
[], int numArgs
) {
3777 state
->setTextMat(args
[0].getNum(), args
[1].getNum(),
3778 args
[2].getNum(), args
[3].getNum(),
3779 args
[4].getNum(), args
[5].getNum());
3780 state
->textMoveTo(0, 0);
3781 out
->updateTextMat(state
);
3782 out
->updateTextPos(state
);
3783 fontChanged
= gTrue
;
3786 void Gfx::opTextNextLine(Object args
[], int numArgs
) {
3789 tx
= state
->getLineX();
3790 ty
= state
->getLineY() - state
->getLeading();
3791 state
->textMoveTo(tx
, ty
);
3792 out
->updateTextPos(state
);
3795 //------------------------------------------------------------------------
3796 // text string operators
3797 //------------------------------------------------------------------------
3799 void Gfx::opShowText(Object args
[], int numArgs
) {
3800 if (!state
->getFont()) {
3801 error(errSyntaxError
, getPos(), "No font in show");
3805 out
->updateFont(state
);
3806 fontChanged
= gFalse
;
3808 out
->beginStringOp(state
);
3809 doShowText(args
[0].getString());
3810 out
->endStringOp(state
);
3812 doIncCharCount(args
[0].getString());
3816 void Gfx::opMoveShowText(Object args
[], int numArgs
) {
3819 if (!state
->getFont()) {
3820 error(errSyntaxError
, getPos(), "No font in move/show");
3824 out
->updateFont(state
);
3825 fontChanged
= gFalse
;
3827 tx
= state
->getLineX();
3828 ty
= state
->getLineY() - state
->getLeading();
3829 state
->textMoveTo(tx
, ty
);
3830 out
->updateTextPos(state
);
3831 out
->beginStringOp(state
);
3832 doShowText(args
[0].getString());
3833 out
->endStringOp(state
);
3835 doIncCharCount(args
[0].getString());
3839 void Gfx::opMoveSetShowText(Object args
[], int numArgs
) {
3842 if (!state
->getFont()) {
3843 error(errSyntaxError
, getPos(), "No font in move/set/show");
3847 out
->updateFont(state
);
3848 fontChanged
= gFalse
;
3850 state
->setWordSpace(args
[0].getNum());
3851 state
->setCharSpace(args
[1].getNum());
3852 tx
= state
->getLineX();
3853 ty
= state
->getLineY() - state
->getLeading();
3854 state
->textMoveTo(tx
, ty
);
3855 out
->updateWordSpace(state
);
3856 out
->updateCharSpace(state
);
3857 out
->updateTextPos(state
);
3858 out
->beginStringOp(state
);
3859 doShowText(args
[2].getString());
3860 out
->endStringOp(state
);
3862 doIncCharCount(args
[2].getString());
3866 void Gfx::opShowSpaceText(Object args
[], int numArgs
) {
3872 if (!state
->getFont()) {
3873 error(errSyntaxError
, getPos(), "No font in show/space");
3877 out
->updateFont(state
);
3878 fontChanged
= gFalse
;
3880 out
->beginStringOp(state
);
3881 wMode
= state
->getFont()->getWMode();
3882 a
= args
[0].getArray();
3883 for (i
= 0; i
< a
->getLength(); ++i
) {
3886 // this uses the absolute value of the font size to match
3887 // Acrobat's behavior
3889 state
->textShift(0, -obj
.getNum() * 0.001 *
3890 state
->getFontSize());
3892 state
->textShift(-obj
.getNum() * 0.001 *
3893 state
->getFontSize() *
3894 state
->getHorizScaling(), 0);
3896 out
->updateTextShift(state
, obj
.getNum());
3897 } else if (obj
.isString()) {
3898 doShowText(obj
.getString());
3900 error(errSyntaxError
, getPos(),
3901 "Element of show/space array must be number or string");
3905 out
->endStringOp(state
);
3907 a
= args
[0].getArray();
3908 for (i
= 0; i
< a
->getLength(); ++i
) {
3910 if (obj
.isString()) {
3911 doIncCharCount(obj
.getString());
3918 void Gfx::doShowText(GooString
*s
) {
3921 double riseX
, riseY
;
3924 double x
, y
, dx
, dy
, dx2
, dy2
, curX
, curY
, tdx
, tdy
, ddx
, ddy
;
3925 double originX
, originY
, tOriginX
, tOriginY
;
3926 double x0
, y0
, x1
, y1
;
3927 double oldCTM
[6], newCTM
[6];
3932 GfxState
*savedState
;
3936 int len
, n
, uLen
, nChars
, nSpaces
, i
;
3938 font
= state
->getFont();
3939 wMode
= font
->getWMode();
3941 if (out
->useDrawChar()) {
3942 out
->beginString(state
, s
);
3945 // if we're doing a pattern fill, set up clipping
3946 render
= state
->getRender();
3947 if (!(render
& 1) &&
3948 state
->getFillColorSpace()->getMode() == csPattern
) {
3949 patternFill
= gTrue
;
3951 // disable fill, enable clipping, leave stroke unchanged
3952 if ((render
^ (render
>> 1)) & 1) {
3957 state
->setRender(render
);
3958 out
->updateRender(state
);
3960 patternFill
= gFalse
;
3963 state
->textTransformDelta(0, state
->getRise(), &riseX
, &riseY
);
3964 x0
= state
->getCurX() + riseX
;
3965 y0
= state
->getCurY() + riseY
;
3967 // handle a Type 3 char
3968 if (font
->getType() == fontType3
&& out
->interpretType3Chars()) {
3969 mat
= state
->getCTM();
3970 for (i
= 0; i
< 6; ++i
) {
3973 mat
= state
->getTextMat();
3974 newCTM
[0] = mat
[0] * oldCTM
[0] + mat
[1] * oldCTM
[2];
3975 newCTM
[1] = mat
[0] * oldCTM
[1] + mat
[1] * oldCTM
[3];
3976 newCTM
[2] = mat
[2] * oldCTM
[0] + mat
[3] * oldCTM
[2];
3977 newCTM
[3] = mat
[2] * oldCTM
[1] + mat
[3] * oldCTM
[3];
3978 mat
= font
->getFontMatrix();
3979 newCTM
[0] = mat
[0] * newCTM
[0] + mat
[1] * newCTM
[2];
3980 newCTM
[1] = mat
[0] * newCTM
[1] + mat
[1] * newCTM
[3];
3981 newCTM
[2] = mat
[2] * newCTM
[0] + mat
[3] * newCTM
[2];
3982 newCTM
[3] = mat
[2] * newCTM
[1] + mat
[3] * newCTM
[3];
3983 newCTM
[0] *= state
->getFontSize();
3984 newCTM
[1] *= state
->getFontSize();
3985 newCTM
[2] *= state
->getFontSize();
3986 newCTM
[3] *= state
->getFontSize();
3987 newCTM
[0] *= state
->getHorizScaling();
3988 newCTM
[2] *= state
->getHorizScaling();
3989 curX
= state
->getCurX();
3990 curY
= state
->getCurY();
3992 p
= s
->getCString();
3993 len
= s
->getLength();
3995 n
= font
->getNextChar(p
, len
, &code
,
3997 &dx
, &dy
, &originX
, &originY
);
3998 dx
= dx
* state
->getFontSize() + state
->getCharSpace();
3999 if (n
== 1 && *p
== ' ') {
4000 dx
+= state
->getWordSpace();
4002 dx
*= state
->getHorizScaling();
4003 dy
*= state
->getFontSize();
4004 state
->textTransformDelta(dx
, dy
, &tdx
, &tdy
);
4005 state
->transform(curX
+ riseX
, curY
+ riseY
, &x
, &y
);
4006 savedState
= saveStateStack();
4007 state
->setCTM(newCTM
[0], newCTM
[1], newCTM
[2], newCTM
[3], x
, y
);
4008 //~ the CTM concat values here are wrong (but never used)
4009 out
->updateCTM(state
, 1, 0, 0, 1, 0, 0);
4010 state
->transformDelta(dx
, dy
, &ddx
, &ddy
);
4011 if (!out
->beginType3Char(state
, curX
+ riseX
, curY
+ riseY
, ddx
, ddy
,
4013 ((Gfx8BitFont
*)font
)->getCharProc(code
, &charProc
);
4014 if ((resDict
= ((Gfx8BitFont
*)font
)->getResources())) {
4015 pushResources(resDict
);
4017 if (charProc
.isStream()) {
4018 display(&charProc
, gFalse
);
4020 error(errSyntaxError
, getPos(), "Missing or bad Type3 CharProc entry");
4022 out
->endType3Char(state
);
4028 restoreStateStack(savedState
);
4029 // GfxState::restore() does *not* restore the current position,
4030 // so we deal with it here using (curX, curY) and (lineX, lineY)
4033 state
->moveTo(curX
, curY
);
4039 } else if (out
->useDrawChar()) {
4040 p
= s
->getCString();
4041 len
= s
->getLength();
4043 n
= font
->getNextChar(p
, len
, &code
,
4045 &dx
, &dy
, &originX
, &originY
);
4047 dx
*= state
->getFontSize();
4048 dy
= dy
* state
->getFontSize() + state
->getCharSpace();
4049 if (n
== 1 && *p
== ' ') {
4050 dy
+= state
->getWordSpace();
4053 dx
= dx
* state
->getFontSize() + state
->getCharSpace();
4054 if (n
== 1 && *p
== ' ') {
4055 dx
+= state
->getWordSpace();
4057 dx
*= state
->getHorizScaling();
4058 dy
*= state
->getFontSize();
4060 state
->textTransformDelta(dx
, dy
, &tdx
, &tdy
);
4061 originX
*= state
->getFontSize();
4062 originY
*= state
->getFontSize();
4063 state
->textTransformDelta(originX
, originY
, &tOriginX
, &tOriginY
);
4065 out
->drawChar(state
, state
->getCurX() + riseX
, state
->getCurY() + riseY
,
4066 tdx
, tdy
, tOriginX
, tOriginY
, code
, n
, u
, uLen
);
4067 state
->shift(tdx
, tdy
);
4073 p
= s
->getCString();
4074 len
= s
->getLength();
4075 nChars
= nSpaces
= 0;
4077 n
= font
->getNextChar(p
, len
, &code
,
4079 &dx2
, &dy2
, &originX
, &originY
);
4082 if (n
== 1 && *p
== ' ') {
4090 dx
*= state
->getFontSize();
4091 dy
= dy
* state
->getFontSize()
4092 + nChars
* state
->getCharSpace()
4093 + nSpaces
* state
->getWordSpace();
4095 dx
= dx
* state
->getFontSize()
4096 + nChars
* state
->getCharSpace()
4097 + nSpaces
* state
->getWordSpace();
4098 dx
*= state
->getHorizScaling();
4099 dy
*= state
->getFontSize();
4101 state
->textTransformDelta(dx
, dy
, &tdx
, &tdy
);
4103 out
->drawString(state
, s
);
4104 state
->shift(tdx
, tdy
);
4107 if (out
->useDrawChar()) {
4108 out
->endString(state
);
4111 if (patternFill
&& ocState
) {
4112 out
->saveTextPos(state
);
4113 // tell the OutputDev to do the clipping
4114 out
->endTextObject(state
);
4115 // set up a clipping bbox so doPatternText will work -- assume
4116 // that the text bounding box does not extend past the baseline in
4117 // any direction by more than twice the font size
4118 x1
= state
->getCurX() + riseX
;
4119 y1
= state
->getCurY() + riseY
;
4121 x
= x0
; x0
= x1
; x1
= x
;
4124 y
= y0
; y0
= y1
; y1
= y
;
4126 state
->textTransformDelta(0, state
->getFontSize(), &dx
, &dy
);
4127 state
->textTransformDelta(state
->getFontSize(), 0, &dx2
, &dy2
);
4138 state
->clipToRect(x0
- 2 * dx
, y0
- 2 * dy
, x1
+ 2 * dx
, y1
+ 2 * dy
);
4139 // set render mode to fill-only
4140 state
->setRender(0);
4141 out
->updateRender(state
);
4144 out
->restoreTextPos(state
);
4147 updateLevel
+= 10 * s
->getLength();
4150 // NB: this is only called when ocState is false.
4151 void Gfx::doIncCharCount(GooString
*s
) {
4152 if (out
->needCharCount()) {
4153 out
->incCharCount(s
->getLength());
4157 //------------------------------------------------------------------------
4158 // XObject operators
4159 //------------------------------------------------------------------------
4161 void Gfx::opXObject(Object args
[], int numArgs
) {
4163 Object obj1
, obj2
, obj3
, refObj
;
4168 if (!ocState
&& !out
->needCharCount()) {
4171 name
= args
[0].getName();
4172 if (!res
->lookupXObject(name
, &obj1
)) {
4175 if (!obj1
.isStream()) {
4176 error(errSyntaxError
, getPos(), "XObject '{0:s}' is wrong type", name
);
4182 obj1
.streamGetDict()->lookup("OPI", &opiDict
);
4183 if (opiDict
.isDict()) {
4184 out
->opiBegin(state
, opiDict
.getDict());
4187 obj1
.streamGetDict()->lookup("Subtype", &obj2
);
4188 if (obj2
.isName("Image")) {
4189 if (out
->needNonText()) {
4190 res
->lookupXObjectNF(name
, &refObj
);
4191 doImage(&refObj
, obj1
.getStream(), gFalse
);
4194 } else if (obj2
.isName("Form")) {
4195 res
->lookupXObjectNF(name
, &refObj
);
4196 GBool shouldDoForm
= gTrue
;
4197 std::set
<int>::iterator drawingFormIt
;
4198 if (refObj
.isRef()) {
4199 const int num
= refObj
.getRef().num
;
4200 if (formsDrawing
.find(num
) == formsDrawing
.end()) {
4201 drawingFormIt
= formsDrawing
.insert(num
).first
;
4203 shouldDoForm
= gFalse
;
4207 if (out
->useDrawForm() && refObj
.isRef()) {
4208 out
->drawForm(refObj
.getRef());
4213 if (refObj
.isRef() && shouldDoForm
) {
4214 formsDrawing
.erase(drawingFormIt
);
4217 } else if (obj2
.isName("PS")) {
4218 obj1
.streamGetDict()->lookup("Level1", &obj3
);
4219 out
->psXObject(obj1
.getStream(),
4220 obj3
.isStream() ? obj3
.getStream() : (Stream
*)NULL
);
4221 } else if (obj2
.isName()) {
4222 error(errSyntaxError
, getPos(), "Unknown XObject subtype '{0:s}'", obj2
.getName());
4224 error(errSyntaxError
, getPos(), "XObject subtype is missing or wrong type");
4228 if (opiDict
.isDict()) {
4229 out
->opiEnd(state
, opiDict
.getDict());
4236 void Gfx::doImage(Object
*ref
, Stream
*str
, GBool inlineImg
) {
4237 Dict
*dict
, *maskDict
;
4241 StreamColorSpaceMode csMode
;
4244 GfxColorSpace
*colorSpace
, *maskColorSpace
;
4245 GfxImageColorMap
*colorMap
, *maskColorMap
;
4246 Object maskObj
, smaskObj
;
4247 GBool haveColorKeyMask
, haveExplicitMask
, haveSoftMask
;
4248 int maskColors
[2*gfxColorMaxComps
];
4249 int maskWidth
, maskHeight
;
4251 GBool maskInterpolate
;
4256 // get info from the stream
4258 csMode
= streamCSNone
;
4259 str
->getImageParams(&bits
, &csMode
);
4262 dict
= str
->getDict();
4264 // check for optional content key
4266 dict
->lookupNF("OC", &obj1
);
4267 if (catalog
->getOptContentConfig() && !catalog
->getOptContentConfig()->optContentIsVisible(&obj1
)) {
4275 dict
->lookup("Width", &obj1
);
4276 if (obj1
.isNull()) {
4278 dict
->lookup("W", &obj1
);
4281 width
= obj1
.getInt();
4282 else if (obj1
.isReal())
4283 width
= (int)obj1
.getReal();
4287 dict
->lookup("Height", &obj1
);
4288 if (obj1
.isNull()) {
4290 dict
->lookup("H", &obj1
);
4293 height
= obj1
.getInt();
4294 else if (obj1
.isReal())
4295 height
= (int)obj1
.getReal();
4300 if (width
< 1 || height
< 1)
4303 // image interpolation
4304 dict
->lookup("Interpolate", &obj1
);
4305 if (obj1
.isNull()) {
4307 dict
->lookup("I", &obj1
);
4310 interpolate
= obj1
.getBool();
4312 interpolate
= gFalse
;
4314 maskInterpolate
= gFalse
;
4317 dict
->lookup("ImageMask", &obj1
);
4318 if (obj1
.isNull()) {
4320 dict
->lookup("IM", &obj1
);
4324 mask
= obj1
.getBool();
4325 else if (!obj1
.isNull())
4331 dict
->lookup("BitsPerComponent", &obj1
);
4332 if (obj1
.isNull()) {
4334 dict
->lookup("BPC", &obj1
);
4337 bits
= obj1
.getInt();
4349 // check for inverted mask
4353 dict
->lookup("Decode", &obj1
);
4354 if (obj1
.isNull()) {
4356 dict
->lookup("D", &obj1
);
4358 if (obj1
.isArray()) {
4359 obj1
.arrayGet(0, &obj2
);
4360 // Table 4.39 says /Decode must be [1 0] or [0 1]. Adobe
4361 // accepts [1.0 0.0] as well.
4362 if (obj2
.isNum() && obj2
.getNum() >= 0.9)
4365 } else if (!obj1
.isNull()) {
4370 // if drawing is disabled, skip over inline image data
4371 if (!ocState
|| !out
->needNonText()) {
4373 n
= height
* ((width
+ 7) / 8);
4374 for (i
= 0; i
< n
; ++i
) {
4381 if (state
->getFillColorSpace()->getMode() == csPattern
) {
4382 doPatternImageMask(ref
, str
, width
, height
, invert
, inlineImg
);
4384 out
->drawImageMask(state
, ref
, str
, width
, height
, invert
, interpolate
, inlineImg
);
4389 // get color space and color map
4390 dict
->lookup("ColorSpace", &obj1
);
4391 if (obj1
.isNull()) {
4393 dict
->lookup("CS", &obj1
);
4395 if (obj1
.isName() && inlineImg
) {
4396 res
->lookupColorSpace(obj1
.getName(), &obj2
);
4397 if (!obj2
.isNull()) {
4404 if (!obj1
.isNull()) {
4406 char *tempIntent
= NULL
;
4407 dict
->lookup("Intent", &objIntent
);
4408 if (objIntent
.isName()) {
4409 tempIntent
= state
->getRenderingIntent();
4410 if (tempIntent
!= NULL
) {
4411 tempIntent
= strdup(tempIntent
);
4413 state
->setRenderingIntent(objIntent
.getName());
4415 colorSpace
= GfxColorSpace::parse(res
, &obj1
, out
, state
);
4416 if (objIntent
.isName()) {
4417 state
->setRenderingIntent(tempIntent
);
4421 } else if (csMode
== streamCSDeviceGray
) {
4423 res
->lookupColorSpace("DefaultGray", &objCS
);
4424 if (objCS
.isNull()) {
4425 colorSpace
= new GfxDeviceGrayColorSpace();
4427 colorSpace
= GfxColorSpace::parse(res
, &objCS
, out
, state
);
4430 } else if (csMode
== streamCSDeviceRGB
) {
4432 res
->lookupColorSpace("DefaultRGB", &objCS
);
4433 if (objCS
.isNull()) {
4434 colorSpace
= new GfxDeviceRGBColorSpace();
4436 colorSpace
= GfxColorSpace::parse(res
, &objCS
, out
, state
);
4439 } else if (csMode
== streamCSDeviceCMYK
) {
4441 res
->lookupColorSpace("DefaultCMYK", &objCS
);
4442 if (objCS
.isNull()) {
4443 colorSpace
= new GfxDeviceCMYKColorSpace();
4445 colorSpace
= GfxColorSpace::parse(res
, &objCS
, out
, state
);
4455 dict
->lookup("Decode", &obj1
);
4456 if (obj1
.isNull()) {
4458 dict
->lookup("D", &obj1
);
4464 colorMap
= new GfxImageColorMap(bits
, &obj1
, colorSpace
);
4466 if (!colorMap
->isOk()) {
4472 haveColorKeyMask
= haveExplicitMask
= haveSoftMask
= gFalse
;
4473 maskStr
= NULL
; // make gcc happy
4474 maskWidth
= maskHeight
= 0; // make gcc happy
4475 maskInvert
= gFalse
; // make gcc happy
4476 maskColorMap
= NULL
; // make gcc happy
4477 dict
->lookup("Mask", &maskObj
);
4478 dict
->lookup("SMask", &smaskObj
);
4479 if (smaskObj
.isStream()) {
4484 maskStr
= smaskObj
.getStream();
4485 maskDict
= smaskObj
.streamGetDict();
4486 maskDict
->lookup("Width", &obj1
);
4487 if (obj1
.isNull()) {
4489 maskDict
->lookup("W", &obj1
);
4491 if (!obj1
.isInt()) {
4494 maskWidth
= obj1
.getInt();
4496 maskDict
->lookup("Height", &obj1
);
4497 if (obj1
.isNull()) {
4499 maskDict
->lookup("H", &obj1
);
4501 if (!obj1
.isInt()) {
4504 maskHeight
= obj1
.getInt();
4506 maskDict
->lookup("Interpolate", &obj1
);
4507 if (obj1
.isNull()) {
4509 maskDict
->lookup("I", &obj1
);
4512 maskInterpolate
= obj1
.getBool();
4514 maskInterpolate
= gFalse
;
4516 maskDict
->lookup("BitsPerComponent", &obj1
);
4517 if (obj1
.isNull()) {
4519 maskDict
->lookup("BPC", &obj1
);
4521 if (!obj1
.isInt()) {
4524 maskBits
= obj1
.getInt();
4526 maskDict
->lookup("ColorSpace", &obj1
);
4527 if (obj1
.isNull()) {
4529 maskDict
->lookup("CS", &obj1
);
4531 if (obj1
.isName()) {
4532 res
->lookupColorSpace(obj1
.getName(), &obj2
);
4533 if (!obj2
.isNull()) {
4540 maskColorSpace
= GfxColorSpace::parse(NULL
, &obj1
, out
, state
);
4542 if (!maskColorSpace
|| maskColorSpace
->getMode() != csDeviceGray
) {
4545 maskDict
->lookup("Decode", &obj1
);
4546 if (obj1
.isNull()) {
4548 maskDict
->lookup("D", &obj1
);
4550 maskColorMap
= new GfxImageColorMap(maskBits
, &obj1
, maskColorSpace
);
4552 if (!maskColorMap
->isOk()) {
4553 delete maskColorMap
;
4556 //~ handle the Matte entry
4557 haveSoftMask
= gTrue
;
4558 } else if (maskObj
.isArray()) {
4561 i
< maskObj
.arrayGetLength() && i
< 2*gfxColorMaxComps
;
4563 maskObj
.arrayGet(i
, &obj1
);
4565 maskColors
[i
] = obj1
.getInt();
4566 } else if (obj1
.isReal()) {
4567 error(errSyntaxError
, -1, "Mask entry should be an integer but it's a real, trying to use it");
4568 maskColors
[i
] = (int) obj1
.getReal();
4570 error(errSyntaxError
, -1, "Mask entry should be an integer but it's of type {0:d}", obj1
.getType());
4576 haveColorKeyMask
= gTrue
;
4577 } else if (maskObj
.isStream()) {
4582 maskStr
= maskObj
.getStream();
4583 maskDict
= maskObj
.streamGetDict();
4584 maskDict
->lookup("Width", &obj1
);
4585 if (obj1
.isNull()) {
4587 maskDict
->lookup("W", &obj1
);
4589 if (!obj1
.isInt()) {
4592 maskWidth
= obj1
.getInt();
4594 maskDict
->lookup("Height", &obj1
);
4595 if (obj1
.isNull()) {
4597 maskDict
->lookup("H", &obj1
);
4599 if (!obj1
.isInt()) {
4602 maskHeight
= obj1
.getInt();
4604 maskDict
->lookup("Interpolate", &obj1
);
4605 if (obj1
.isNull()) {
4607 maskDict
->lookup("I", &obj1
);
4610 maskInterpolate
= obj1
.getBool();
4612 maskInterpolate
= gFalse
;
4614 maskDict
->lookup("ImageMask", &obj1
);
4615 if (obj1
.isNull()) {
4617 maskDict
->lookup("IM", &obj1
);
4619 if (!obj1
.isBool() || !obj1
.getBool()) {
4623 maskInvert
= gFalse
;
4624 maskDict
->lookup("Decode", &obj1
);
4625 if (obj1
.isNull()) {
4627 maskDict
->lookup("D", &obj1
);
4629 if (obj1
.isArray()) {
4630 obj1
.arrayGet(0, &obj2
);
4631 // Table 4.39 says /Decode must be [1 0] or [0 1]. Adobe
4632 // accepts [1.0 0.0] as well.
4633 if (obj2
.isNum() && obj2
.getNum() >= 0.9) {
4637 } else if (!obj1
.isNull()) {
4641 haveExplicitMask
= gTrue
;
4644 // if drawing is disabled, skip over inline image data
4645 if (!ocState
|| !out
->needNonText()) {
4647 n
= height
* ((width
* colorMap
->getNumPixelComps() *
4648 colorMap
->getBits() + 7) / 8);
4649 for (i
= 0; i
< n
; ++i
) {
4657 out
->drawSoftMaskedImage(state
, ref
, str
, width
, height
, colorMap
, interpolate
,
4658 maskStr
, maskWidth
, maskHeight
, maskColorMap
, maskInterpolate
);
4659 delete maskColorMap
;
4660 } else if (haveExplicitMask
) {
4661 out
->drawMaskedImage(state
, ref
, str
, width
, height
, colorMap
, interpolate
,
4662 maskStr
, maskWidth
, maskHeight
, maskInvert
, maskInterpolate
);
4664 out
->drawImage(state
, ref
, str
, width
, height
, colorMap
, interpolate
,
4665 haveColorKeyMask
? maskColors
: (int *)NULL
, inlineImg
);
4674 if ((i
= width
* height
) > 1000) {
4684 error(errSyntaxError
, getPos(), "Bad image parameters");
4687 GBool
Gfx::checkTransparencyGroup(Dict
*resDict
) {
4688 // check the effect of compositing objects as a group:
4689 // look for ExtGState entries with ca != 1 or CA != 1 or BM != normal
4691 GBool transpGroup
= gFalse
;
4694 if (resDict
== NULL
)
4696 pushResources(resDict
);
4697 resDict
->lookup("ExtGState", &extGStates
);
4698 if (extGStates
.isDict()) {
4699 Dict
*dict
= extGStates
.getDict();
4700 for (int i
= 0; i
< dict
->getLength() && !transpGroup
; i
++) {
4704 if (res
->lookupGState(dict
->getKey(i
), &obj1
) && obj1
.isDict()) {
4705 if (!obj1
.dictLookup("BM", &obj2
)->isNull()) {
4706 if (state
->parseBlendMode(&obj2
, &mode
)) {
4707 if (mode
!= gfxBlendNormal
)
4708 transpGroup
= gTrue
;
4710 error(errSyntaxError
, getPos(), "Invalid blend mode in ExtGState");
4714 if (obj1
.dictLookup("ca", &obj2
)->isNum()) {
4715 opac
= obj2
.getNum();
4716 opac
= opac
< 0 ? 0 : opac
> 1 ? 1 : opac
;
4718 transpGroup
= gTrue
;
4721 if (obj1
.dictLookup("CA", &obj2
)->isNum()) {
4722 opac
= obj2
.getNum();
4723 opac
= opac
< 0 ? 0 : opac
> 1 ? 1 : opac
;
4725 transpGroup
= gTrue
;
4729 if (!transpGroup
&& obj1
.dictLookup("AIS", &obj2
)->isBool()) {
4730 transpGroup
= obj2
.getBool();
4734 if (!transpGroup
&& !obj1
.dictLookup("SMask", &obj2
)->isNull()) {
4735 if (!obj2
.isName("None")) {
4736 transpGroup
= gTrue
;
4749 void Gfx::doForm(Object
*str
) {
4751 GBool transpGroup
, isolated
, knockout
;
4752 GfxColorSpace
*blendingColorSpace
;
4753 Object matrixObj
, bboxObj
;
4754 double m
[6], bbox
[4];
4758 Object obj1
, obj2
, obj3
;
4761 // check for excessive recursion
4762 if (formDepth
> 100) {
4767 dict
= str
->streamGetDict();
4770 dict
->lookup("FormType", &obj1
);
4771 if (!(obj1
.isNull() || (obj1
.isInt() && obj1
.getInt() == 1))) {
4772 error(errSyntaxError
, getPos(), "Unknown form type");
4776 // check for optional content key
4778 dict
->lookupNF("OC", &obj1
);
4779 if (catalog
->getOptContentConfig() && !catalog
->getOptContentConfig()->optContentIsVisible(&obj1
)) {
4781 if (out
->needCharCount()) {
4790 dict
->lookup("BBox", &bboxObj
);
4791 if (!bboxObj
.isArray()) {
4793 error(errSyntaxError
, getPos(), "Bad form bounding box");
4797 for (i
= 0; i
< 4; ++i
) {
4798 bboxObj
.arrayGet(i
, &obj1
);
4799 if (likely(obj1
.isNum())) {
4800 bbox
[i
] = obj1
.getNum();
4804 error(errSyntaxError
, getPos(), "Bad form bounding box value");
4811 dict
->lookup("Matrix", &matrixObj
);
4812 if (matrixObj
.isArray()) {
4813 for (i
= 0; i
< 6; ++i
) {
4814 matrixObj
.arrayGet(i
, &obj1
);
4815 if (likely(obj1
.isNum())) m
[i
] = obj1
.getNum();
4827 dict
->lookup("Resources", &resObj
);
4828 resDict
= resObj
.isDict() ? resObj
.getDict() : (Dict
*)NULL
;
4830 // check for a transparency group
4831 transpGroup
= isolated
= knockout
= gFalse
;
4832 blendingColorSpace
= NULL
;
4833 if (dict
->lookup("Group", &obj1
)->isDict()) {
4834 if (obj1
.dictLookup("S", &obj2
)->isName("Transparency")) {
4835 if (!obj1
.dictLookup("CS", &obj3
)->isNull()) {
4836 blendingColorSpace
= GfxColorSpace::parse(res
, &obj3
, out
, state
);
4839 if (obj1
.dictLookup("I", &obj3
)->isBool()) {
4840 isolated
= obj3
.getBool();
4843 if (obj1
.dictLookup("K", &obj3
)->isBool()) {
4844 knockout
= obj3
.getBool();
4847 transpGroup
= isolated
|| out
->checkTransparencyGroup(state
, knockout
) || checkTransparencyGroup(resDict
);
4855 drawForm(str
, resDict
, m
, bbox
,
4856 transpGroup
, gFalse
, blendingColorSpace
, isolated
, knockout
);
4859 if (blendingColorSpace
) {
4860 delete blendingColorSpace
;
4867 void Gfx::drawForm(Object
*str
, Dict
*resDict
, double *matrix
, double *bbox
,
4868 GBool transpGroup
, GBool softMask
,
4869 GfxColorSpace
*blendingColorSpace
,
4870 GBool isolated
, GBool knockout
,
4871 GBool alpha
, Function
*transferFunc
,
4872 GfxColor
*backdropColor
) {
4874 GfxState
*savedState
;
4875 double oldBaseMatrix
[6];
4878 // push new resources on stack
4879 pushResources(resDict
);
4881 // save current graphics state
4882 savedState
= saveStateStack();
4884 // kill any pre-existing path
4887 // save current parser
4890 // set form transformation matrix
4891 state
->concatCTM(matrix
[0], matrix
[1], matrix
[2],
4892 matrix
[3], matrix
[4], matrix
[5]);
4893 out
->updateCTM(state
, matrix
[0], matrix
[1], matrix
[2],
4894 matrix
[3], matrix
[4], matrix
[5]);
4896 // set form bounding box
4897 state
->moveTo(bbox
[0], bbox
[1]);
4898 state
->lineTo(bbox
[2], bbox
[1]);
4899 state
->lineTo(bbox
[2], bbox
[3]);
4900 state
->lineTo(bbox
[0], bbox
[3]);
4906 if (softMask
|| transpGroup
) {
4907 if (state
->getBlendMode() != gfxBlendNormal
) {
4908 state
->setBlendMode(gfxBlendNormal
);
4909 out
->updateBlendMode(state
);
4911 if (state
->getFillOpacity() != 1) {
4912 state
->setFillOpacity(1);
4913 out
->updateFillOpacity(state
);
4915 if (state
->getStrokeOpacity() != 1) {
4916 state
->setStrokeOpacity(1);
4917 out
->updateStrokeOpacity(state
);
4919 out
->clearSoftMask(state
);
4920 out
->beginTransparencyGroup(state
, bbox
, blendingColorSpace
,
4921 isolated
, knockout
, softMask
);
4924 // set new base matrix
4925 for (i
= 0; i
< 6; ++i
) {
4926 oldBaseMatrix
[i
] = baseMatrix
[i
];
4927 baseMatrix
[i
] = state
->getCTM()[i
];
4930 GfxState
*stateBefore
= state
;
4933 display(str
, gFalse
);
4935 if (stateBefore
!= state
) {
4936 if (state
->isParentState(stateBefore
)) {
4937 error(errSyntaxError
, -1, "There's a form with more q than Q, trying to fix");
4938 while (stateBefore
!= state
) {
4942 error(errSyntaxError
, -1, "There's a form with more Q than q");
4946 if (softMask
|| transpGroup
) {
4947 out
->endTransparencyGroup(state
);
4950 // restore base matrix
4951 for (i
= 0; i
< 6; ++i
) {
4952 baseMatrix
[i
] = oldBaseMatrix
[i
];
4958 // restore graphics state
4959 restoreStateStack(savedState
);
4961 // pop resource stack
4965 out
->setSoftMask(state
, bbox
, alpha
, transferFunc
, backdropColor
);
4966 } else if (transpGroup
) {
4967 out
->paintTransparencyGroup(state
, bbox
);
4973 //------------------------------------------------------------------------
4974 // in-line image operators
4975 //------------------------------------------------------------------------
4977 void Gfx::opBeginImage(Object args
[], int numArgs
) {
4981 // NB: this function is run even if ocState is false -- doImage() is
4982 // responsible for skipping over the inline image data
4984 // build dict/stream
4985 str
= buildImageStream();
4987 // display the image
4989 doImage(NULL
, str
, gTrue
);
4992 c1
= str
->getUndecodedStream()->getChar();
4993 c2
= str
->getUndecodedStream()->getChar();
4994 while (!(c1
== 'E' && c2
== 'I') && c2
!= EOF
) {
4996 c2
= str
->getUndecodedStream()->getChar();
5002 Stream
*Gfx::buildImageStream() {
5009 dict
.initDict(xref
);
5010 parser
->getObj(&obj
);
5011 while (!obj
.isCmd("ID") && !obj
.isEOF()) {
5012 if (!obj
.isName()) {
5013 error(errSyntaxError
, getPos(), "Inline image dictionary key must be a name object");
5016 key
= copyString(obj
.getName());
5018 parser
->getObj(&obj
);
5019 if (obj
.isEOF() || obj
.isError()) {
5023 dict
.dictAdd(key
, &obj
);
5025 parser
->getObj(&obj
);
5028 error(errSyntaxError
, getPos(), "End of file in inline image");
5036 if (parser
->getStream()) {
5037 str
= new EmbedStream(parser
->getStream(), &dict
, gFalse
, 0);
5038 str
= str
->addFilters(&dict
);
5047 void Gfx::opImageData(Object args
[], int numArgs
) {
5048 error(errInternal
, getPos(), "Got 'ID' operator");
5051 void Gfx::opEndImage(Object args
[], int numArgs
) {
5052 error(errInternal
, getPos(), "Got 'EI' operator");
5055 //------------------------------------------------------------------------
5056 // type 3 font operators
5057 //------------------------------------------------------------------------
5059 void Gfx::opSetCharWidth(Object args
[], int numArgs
) {
5060 out
->type3D0(state
, args
[0].getNum(), args
[1].getNum());
5063 void Gfx::opSetCacheDevice(Object args
[], int numArgs
) {
5064 out
->type3D1(state
, args
[0].getNum(), args
[1].getNum(),
5065 args
[2].getNum(), args
[3].getNum(),
5066 args
[4].getNum(), args
[5].getNum());
5069 //------------------------------------------------------------------------
5070 // compatibility operators
5071 //------------------------------------------------------------------------
5073 void Gfx::opBeginIgnoreUndef(Object args
[], int numArgs
) {
5077 void Gfx::opEndIgnoreUndef(Object args
[], int numArgs
) {
5078 if (ignoreUndef
> 0)
5082 //------------------------------------------------------------------------
5083 // marked content operators
5084 //------------------------------------------------------------------------
5086 enum GfxMarkedContentKind
{
5087 gfxMCOptionalContent
,
5092 struct MarkedContentStack
{
5093 GfxMarkedContentKind kind
;
5094 GBool ocSuppressed
; // are we ignoring content based on OptionalContent?
5095 MarkedContentStack
*next
; // next object on stack
5098 void Gfx::popMarkedContent() {
5099 MarkedContentStack
*mc
= mcStack
;
5104 void Gfx::pushMarkedContent() {
5105 MarkedContentStack
*mc
= new MarkedContentStack();
5106 mc
->ocSuppressed
= gFalse
;
5107 mc
->kind
= gfxMCOther
;
5112 GBool
Gfx::contentIsHidden() {
5113 MarkedContentStack
*mc
= mcStack
;
5114 bool hidden
= mc
&& mc
->ocSuppressed
;
5115 while (!hidden
&& mc
&& mc
->next
) {
5117 hidden
= mc
->ocSuppressed
;
5122 void Gfx::opBeginMarkedContent(Object args
[], int numArgs
) {
5123 // push a new stack entry
5124 pushMarkedContent();
5126 OCGs
*contentConfig
= catalog
->getOptContentConfig();
5127 char* name0
= args
[0].getName();
5128 if ( strncmp( name0
, "OC", 2) == 0 && contentConfig
) {
5129 if ( numArgs
>= 2 ) {
5130 if (!args
[1].isName()) {
5131 error(errSyntaxError
, getPos(), "Unexpected MC Type: {0:d}", args
[1].getType());
5133 char* name1
= args
[1].getName();
5134 Object markedContent
;
5135 MarkedContentStack
*mc
= mcStack
;
5136 mc
->kind
= gfxMCOptionalContent
;
5137 if ( res
->lookupMarkedContentNF( name1
, &markedContent
) ) {
5138 bool visible
= contentConfig
->optContentIsVisible(&markedContent
);
5139 mc
->ocSuppressed
= !(visible
);
5141 error(errSyntaxError
, getPos(), "DID NOT find {0:s}", name1
);
5143 markedContent
.free();
5145 error(errSyntaxError
, getPos(), "insufficient arguments for Marked Content");
5147 } else if (args
[0].isName("Span") && numArgs
== 2 && args
[1].isDict()) {
5149 if (args
[1].dictLookup("ActualText", &obj
)->isString()) {
5150 out
->beginActualText(state
, obj
.getString());
5151 MarkedContentStack
*mc
= mcStack
;
5152 mc
->kind
= gfxMCActualText
;
5157 if (printCommands
) {
5158 printf(" marked content: %s ", args
[0].getName());
5160 args
[1].print(stdout
);
5164 ocState
= !contentIsHidden();
5166 if (numArgs
== 2 && args
[1].isDict()) {
5167 out
->beginMarkedContent(args
[0].getName(), args
[1].getDict());
5168 } else if(numArgs
== 1) {
5169 out
->beginMarkedContent(args
[0].getName(), NULL
);
5173 void Gfx::opEndMarkedContent(Object args
[], int numArgs
) {
5175 error(errSyntaxWarning
, getPos(), "Mismatched EMC operator");
5179 MarkedContentStack
*mc
= mcStack
;
5180 GfxMarkedContentKind mcKind
= mc
->kind
;
5185 if (mcKind
== gfxMCActualText
)
5186 out
->endActualText(state
);
5187 ocState
= !contentIsHidden();
5189 out
->endMarkedContent(state
);
5192 void Gfx::opMarkPoint(Object args
[], int numArgs
) {
5193 if (printCommands
) {
5194 printf(" mark point: %s ", args
[0].getName());
5196 args
[1].print(stdout
);
5201 if(numArgs
== 2 && args
[1].isDict()) {
5202 out
->markPoint(args
[0].getName(),args
[1].getDict());
5204 out
->markPoint(args
[0].getName());
5209 //------------------------------------------------------------------------
5211 //------------------------------------------------------------------------
5213 struct GfxStackStateSaver
{
5214 GfxStackStateSaver(Gfx
*gfx
) : gfx(gfx
) {
5218 ~GfxStackStateSaver() {
5219 gfx
->restoreState();
5225 void Gfx::drawAnnot(Object
*str
, AnnotBorder
*border
, AnnotColor
*aColor
,
5226 double xMin
, double yMin
, double xMax
, double yMax
, int rotate
) {
5227 Dict
*dict
, *resDict
;
5228 Object matrixObj
, bboxObj
, resObj
, obj1
;
5229 double formXMin
, formYMin
, formXMax
, formYMax
;
5230 double x
, y
, sx
, sy
, tx
, ty
;
5231 double m
[6], bbox
[4];
5234 double *dash
, *dash2
;
5238 // this function assumes that we are in the default user space,
5239 // i.e., baseMatrix = ctm
5241 // if the bounding box has zero width or height, don't draw anything
5243 if (xMin
== xMax
|| yMin
== yMax
) {
5247 // saves gfx state and automatically restores it on return
5248 GfxStackStateSaver
stackStateSaver(this);
5250 // Rotation around the topleft corner (for the NoRotate flag)
5252 const double angle_rad
= rotate
* M_PI
/ 180;
5253 const double c
= cos(angle_rad
);
5254 const double s
= sin(angle_rad
);
5256 // (xMin, yMax) is the pivot
5257 const double unrotateMTX
[6] = {
5260 -c
*xMin
- s
*yMax
+ xMin
, -c
*yMax
+ s
*xMin
+ yMax
5263 state
->concatCTM(unrotateMTX
[0], unrotateMTX
[1], unrotateMTX
[2],
5264 unrotateMTX
[3], unrotateMTX
[4], unrotateMTX
[5]);
5265 out
->updateCTM(state
, unrotateMTX
[0], unrotateMTX
[1], unrotateMTX
[2],
5266 unrotateMTX
[3], unrotateMTX
[4], unrotateMTX
[5]);
5269 // draw the appearance stream (if there is one)
5270 if (str
->isStream()) {
5273 dict
= str
->streamGetDict();
5275 // get the form bounding box
5276 dict
->lookup("BBox", &bboxObj
);
5277 if (!bboxObj
.isArray()) {
5279 error(errSyntaxError
, getPos(), "Bad form bounding box");
5282 for (i
= 0; i
< 4; ++i
) {
5283 bboxObj
.arrayGet(i
, &obj1
);
5284 if (likely(obj1
.isNum())) {
5285 bbox
[i
] = obj1
.getNum();
5290 error(errSyntaxError
, getPos(), "Bad form bounding box value");
5296 // get the form matrix
5297 dict
->lookup("Matrix", &matrixObj
);
5298 if (matrixObj
.isArray() && matrixObj
.arrayGetLength() >= 6) {
5299 for (i
= 0; i
< 6; ++i
) {
5300 matrixObj
.arrayGet(i
, &obj1
);
5301 if (likely(obj1
.isNum())) {
5302 m
[i
] = obj1
.getNum();
5307 error(errSyntaxError
, getPos(), "Bad form matrix");
5318 // transform the four corners of the form bbox to default user
5319 // space, and construct the transformed bbox
5320 x
= bbox
[0] * m
[0] + bbox
[1] * m
[2] + m
[4];
5321 y
= bbox
[0] * m
[1] + bbox
[1] * m
[3] + m
[5];
5322 formXMin
= formXMax
= x
;
5323 formYMin
= formYMax
= y
;
5324 x
= bbox
[0] * m
[0] + bbox
[3] * m
[2] + m
[4];
5325 y
= bbox
[0] * m
[1] + bbox
[3] * m
[3] + m
[5];
5328 } else if (x
> formXMax
) {
5333 } else if (y
> formYMax
) {
5336 x
= bbox
[2] * m
[0] + bbox
[1] * m
[2] + m
[4];
5337 y
= bbox
[2] * m
[1] + bbox
[1] * m
[3] + m
[5];
5340 } else if (x
> formXMax
) {
5345 } else if (y
> formYMax
) {
5348 x
= bbox
[2] * m
[0] + bbox
[3] * m
[2] + m
[4];
5349 y
= bbox
[2] * m
[1] + bbox
[3] * m
[3] + m
[5];
5352 } else if (x
> formXMax
) {
5357 } else if (y
> formYMax
) {
5361 // construct a mapping matrix, [sx 0 0], which maps the transformed
5364 // bbox to the annotation rectangle
5365 if (formXMin
== formXMax
) {
5366 // this shouldn't happen
5369 sx
= (xMax
- xMin
) / (formXMax
- formXMin
);
5371 if (formYMin
== formYMax
) {
5372 // this shouldn't happen
5375 sy
= (yMax
- yMin
) / (formYMax
- formYMin
);
5377 tx
= -formXMin
* sx
+ xMin
;
5378 ty
= -formYMin
* sy
+ yMin
;
5380 // the final transform matrix is (form matrix) * (mapping matrix)
5385 m
[4] = m
[4] * sx
+ tx
;
5386 m
[5] = m
[5] * sy
+ ty
;
5388 // get the resources
5389 dict
->lookup("Resources", &resObj
);
5390 resDict
= resObj
.isDict() ? resObj
.getDict() : (Dict
*)NULL
;
5393 drawForm(str
, resDict
, m
, bbox
);
5399 if (border
&& border
->getWidth() > 0) {
5400 if (state
->getStrokeColorSpace()->getMode() != csDeviceRGB
) {
5401 state
->setStrokePattern(NULL
);
5402 state
->setStrokeColorSpace(new GfxDeviceRGBColorSpace());
5403 out
->updateStrokeColorSpace(state
);
5405 if (aColor
&& (aColor
->getSpace() == AnnotColor::colorRGB
)) {
5406 const double *values
= aColor
->getValues();
5413 color
.c
[0] = dblToCol(r
);
5414 color
.c
[1] = dblToCol(g
);
5415 color
.c
[2] = dblToCol(b
);
5416 state
->setStrokeColor(&color
);
5417 out
->updateStrokeColor(state
);
5418 state
->setLineWidth(border
->getWidth());
5419 out
->updateLineWidth(state
);
5420 dashLength
= border
->getDashLength();
5421 dash
= border
->getDash();
5422 if (border
->getStyle() == AnnotBorder::borderDashed
&& dashLength
> 0) {
5423 dash2
= (double *)gmallocn(dashLength
, sizeof(double));
5424 memcpy(dash2
, dash
, dashLength
* sizeof(double));
5425 state
->setLineDash(dash2
, dashLength
, 0);
5426 out
->updateLineDash(state
);
5428 //~ this doesn't currently handle the beveled and engraved styles
5430 state
->moveTo(xMin
, yMin
);
5431 state
->lineTo(xMax
, yMin
);
5432 if (border
->getStyle() != AnnotBorder::borderUnderlined
) {
5433 state
->lineTo(xMax
, yMax
);
5434 state
->lineTo(xMin
, yMax
);
5441 int Gfx::bottomGuard() {
5442 return stateGuards
[stateGuards
.size()-1];
5445 void Gfx::pushStateGuard() {
5446 stateGuards
.push_back(stackHeight
);
5449 void Gfx::popStateGuard() {
5450 while (stackHeight
> bottomGuard() && state
->hasSaves())
5452 stateGuards
.pop_back();
5455 void Gfx::saveState() {
5456 out
->saveState(state
);
5457 state
= state
->save();
5461 void Gfx::restoreState() {
5462 if (stackHeight
<= bottomGuard() || !state
->hasSaves()) {
5463 error(errSyntaxError
, -1, "Restoring state when no valid states to pop");
5464 commandAborted
= gTrue
;
5467 state
= state
->restore();
5468 out
->restoreState(state
);
5472 // Create a new state stack, and initialize it with a copy of the
5474 GfxState
*Gfx::saveStateStack() {
5477 out
->saveState(state
);
5479 state
= state
->copy(gTrue
);
5483 // Switch back to the previous state stack.
5484 void Gfx::restoreStateStack(GfxState
*oldState
) {
5485 while (state
->hasSaves()) {
5490 out
->restoreState(state
);
5493 void Gfx::pushResources(Dict
*resDict
) {
5494 res
= new GfxResources(xref
, resDict
, res
);
5497 void Gfx::popResources() {
5498 GfxResources
*resPtr
;
5500 resPtr
= res
->getNext();