5497 lines
144 KiB
C++
5497 lines
144 KiB
C++
//========================================================================
|
|
//
|
|
// Gfx.cc
|
|
//
|
|
// Copyright 1996-2016 Glyph & Cog, LLC
|
|
//
|
|
//========================================================================
|
|
|
|
#include <aconf.h>
|
|
|
|
#ifdef USE_GCC_PRAGMAS
|
|
#pragma implementation
|
|
#endif
|
|
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <stddef.h>
|
|
#include <string.h>
|
|
#include <math.h>
|
|
#include "gmem.h"
|
|
#include "gmempp.h"
|
|
#include "GString.h"
|
|
#include "GList.h"
|
|
#include "Trace.h"
|
|
#include "GlobalParams.h"
|
|
#include "CharTypes.h"
|
|
#include "Object.h"
|
|
#include "PDFDoc.h"
|
|
#include "Array.h"
|
|
#include "Dict.h"
|
|
#include "Stream.h"
|
|
#include "Lexer.h"
|
|
#include "Parser.h"
|
|
#include "GfxFont.h"
|
|
#include "GfxState.h"
|
|
#include "OutputDev.h"
|
|
#include "Page.h"
|
|
#include "Annot.h"
|
|
#include "OptionalContent.h"
|
|
#include "Error.h"
|
|
#include "TextString.h"
|
|
#include "Gfx.h"
|
|
|
|
// the MSVC math.h doesn't define this
|
|
#ifndef M_PI
|
|
#define M_PI 3.14159265358979323846
|
|
#endif
|
|
|
|
//------------------------------------------------------------------------
|
|
// constants
|
|
//------------------------------------------------------------------------
|
|
|
|
// Max recursive depth for a function shading fill.
|
|
#define functionMaxDepth 6
|
|
|
|
// Max delta allowed in any color component for a function shading fill.
|
|
#define functionColorDelta (dblToCol(1 / 256.0))
|
|
|
|
// Number of splits along the t axis for an axial shading fill.
|
|
#define axialSplits 256
|
|
|
|
// Max delta allowed in any color component for an axial shading fill.
|
|
#define axialColorDelta (dblToCol(1 / 256.0))
|
|
|
|
// Max number of splits along the t axis for a radial shading fill.
|
|
#define radialMaxSplits 256
|
|
|
|
// Max delta allowed in any color component for a radial shading fill.
|
|
#define radialColorDelta (dblToCol(1 / 256.0))
|
|
|
|
// Max recursive depth for a Gouraud triangle shading fill.
|
|
#define gouraudMaxDepth 6
|
|
|
|
// Max delta allowed in any color component for a Gouraud triangle
|
|
// shading fill.
|
|
#define gouraudColorDelta (dblToCol(1 / 256.0))
|
|
|
|
// Max recursive depth for a patch mesh shading fill.
|
|
#define patchMaxDepth 6
|
|
|
|
// Max delta allowed in any color component for a patch mesh shading
|
|
// fill.
|
|
#define patchColorDelta (dblToCol(1 / 256.0))
|
|
|
|
// Max errors (undefined operator, wrong number of args) allowed before
|
|
// giving up on a content stream.
|
|
#define contentStreamErrorLimit 500
|
|
|
|
//------------------------------------------------------------------------
|
|
// Operator table
|
|
//------------------------------------------------------------------------
|
|
|
|
#ifdef _WIN32 // this works around a bug in the VC7 compiler
|
|
# pragma optimize("",off)
|
|
#endif
|
|
|
|
Operator Gfx::opTab[] = {
|
|
{"\"", 3, {tchkNum, tchkNum, tchkString},
|
|
&Gfx::opMoveSetShowText},
|
|
{"'", 1, {tchkString},
|
|
&Gfx::opMoveShowText},
|
|
{"B", 0, {tchkNone},
|
|
&Gfx::opFillStroke},
|
|
{"B*", 0, {tchkNone},
|
|
&Gfx::opEOFillStroke},
|
|
{"BDC", 2, {tchkName, tchkProps},
|
|
&Gfx::opBeginMarkedContent},
|
|
{"BI", 0, {tchkNone},
|
|
&Gfx::opBeginImage},
|
|
{"BMC", 1, {tchkName},
|
|
&Gfx::opBeginMarkedContent},
|
|
{"BT", 0, {tchkNone},
|
|
&Gfx::opBeginText},
|
|
{"BX", 0, {tchkNone},
|
|
&Gfx::opBeginIgnoreUndef},
|
|
{"CS", 1, {tchkName},
|
|
&Gfx::opSetStrokeColorSpace},
|
|
{"DP", 2, {tchkName, tchkProps},
|
|
&Gfx::opMarkPoint},
|
|
{"Do", 1, {tchkName},
|
|
&Gfx::opXObject},
|
|
{"EI", 0, {tchkNone},
|
|
&Gfx::opEndImage},
|
|
{"EMC", 0, {tchkNone},
|
|
&Gfx::opEndMarkedContent},
|
|
{"ET", 0, {tchkNone},
|
|
&Gfx::opEndText},
|
|
{"EX", 0, {tchkNone},
|
|
&Gfx::opEndIgnoreUndef},
|
|
{"F", 0, {tchkNone},
|
|
&Gfx::opFill},
|
|
{"G", 1, {tchkNum},
|
|
&Gfx::opSetStrokeGray},
|
|
{"ID", 0, {tchkNone},
|
|
&Gfx::opImageData},
|
|
{"J", 1, {tchkInt},
|
|
&Gfx::opSetLineCap},
|
|
{"K", 4, {tchkNum, tchkNum, tchkNum, tchkNum},
|
|
&Gfx::opSetStrokeCMYKColor},
|
|
{"M", 1, {tchkNum},
|
|
&Gfx::opSetMiterLimit},
|
|
{"MP", 1, {tchkName},
|
|
&Gfx::opMarkPoint},
|
|
{"Q", 0, {tchkNone},
|
|
&Gfx::opRestore},
|
|
{"RG", 3, {tchkNum, tchkNum, tchkNum},
|
|
&Gfx::opSetStrokeRGBColor},
|
|
{"S", 0, {tchkNone},
|
|
&Gfx::opStroke},
|
|
{"SC", -4, {tchkNum, tchkNum, tchkNum, tchkNum,
|
|
tchkNum, tchkNum, tchkNum, tchkNum,
|
|
tchkNum, tchkNum, tchkNum, tchkNum,
|
|
tchkNum, tchkNum, tchkNum, tchkNum,
|
|
tchkNum, tchkNum, tchkNum, tchkNum,
|
|
tchkNum, tchkNum, tchkNum, tchkNum,
|
|
tchkNum, tchkNum, tchkNum, tchkNum,
|
|
tchkNum, tchkNum, tchkNum, tchkNum,
|
|
tchkNum},
|
|
&Gfx::opSetStrokeColor},
|
|
{"SCN", -33, {tchkSCN, tchkSCN, tchkSCN, tchkSCN,
|
|
tchkSCN, tchkSCN, tchkSCN, tchkSCN,
|
|
tchkSCN, tchkSCN, tchkSCN, tchkSCN,
|
|
tchkSCN, tchkSCN, tchkSCN, tchkSCN,
|
|
tchkSCN, tchkSCN, tchkSCN, tchkSCN,
|
|
tchkSCN, tchkSCN, tchkSCN, tchkSCN,
|
|
tchkSCN, tchkSCN, tchkSCN, tchkSCN,
|
|
tchkSCN, tchkSCN, tchkSCN, tchkSCN,
|
|
tchkSCN},
|
|
&Gfx::opSetStrokeColorN},
|
|
{"T*", 0, {tchkNone},
|
|
&Gfx::opTextNextLine},
|
|
{"TD", 2, {tchkNum, tchkNum},
|
|
&Gfx::opTextMoveSet},
|
|
{"TJ", 1, {tchkArray},
|
|
&Gfx::opShowSpaceText},
|
|
{"TL", 1, {tchkNum},
|
|
&Gfx::opSetTextLeading},
|
|
{"Tc", 1, {tchkNum},
|
|
&Gfx::opSetCharSpacing},
|
|
{"Td", 2, {tchkNum, tchkNum},
|
|
&Gfx::opTextMove},
|
|
{"Tf", 2, {tchkName, tchkNum},
|
|
&Gfx::opSetFont},
|
|
{"Tj", 1, {tchkString},
|
|
&Gfx::opShowText},
|
|
{"Tm", 6, {tchkNum, tchkNum, tchkNum, tchkNum,
|
|
tchkNum, tchkNum},
|
|
&Gfx::opSetTextMatrix},
|
|
{"Tr", 1, {tchkInt},
|
|
&Gfx::opSetTextRender},
|
|
{"Ts", 1, {tchkNum},
|
|
&Gfx::opSetTextRise},
|
|
{"Tw", 1, {tchkNum},
|
|
&Gfx::opSetWordSpacing},
|
|
{"Tz", 1, {tchkNum},
|
|
&Gfx::opSetHorizScaling},
|
|
{"W", 0, {tchkNone},
|
|
&Gfx::opClip},
|
|
{"W*", 0, {tchkNone},
|
|
&Gfx::opEOClip},
|
|
{"b", 0, {tchkNone},
|
|
&Gfx::opCloseFillStroke},
|
|
{"b*", 0, {tchkNone},
|
|
&Gfx::opCloseEOFillStroke},
|
|
{"c", 6, {tchkNum, tchkNum, tchkNum, tchkNum,
|
|
tchkNum, tchkNum},
|
|
&Gfx::opCurveTo},
|
|
{"cm", 6, {tchkNum, tchkNum, tchkNum, tchkNum,
|
|
tchkNum, tchkNum},
|
|
&Gfx::opConcat},
|
|
{"cs", 1, {tchkName},
|
|
&Gfx::opSetFillColorSpace},
|
|
{"d", 2, {tchkArray, tchkNum},
|
|
&Gfx::opSetDash},
|
|
{"d0", 2, {tchkNum, tchkNum},
|
|
&Gfx::opSetCharWidth},
|
|
{"d1", 6, {tchkNum, tchkNum, tchkNum, tchkNum,
|
|
tchkNum, tchkNum},
|
|
&Gfx::opSetCacheDevice},
|
|
{"f", 0, {tchkNone},
|
|
&Gfx::opFill},
|
|
{"f*", 0, {tchkNone},
|
|
&Gfx::opEOFill},
|
|
{"g", 1, {tchkNum},
|
|
&Gfx::opSetFillGray},
|
|
{"gs", 1, {tchkName},
|
|
&Gfx::opSetExtGState},
|
|
{"h", 0, {tchkNone},
|
|
&Gfx::opClosePath},
|
|
{"i", 1, {tchkNum},
|
|
&Gfx::opSetFlat},
|
|
{"j", 1, {tchkInt},
|
|
&Gfx::opSetLineJoin},
|
|
{"k", 4, {tchkNum, tchkNum, tchkNum, tchkNum},
|
|
&Gfx::opSetFillCMYKColor},
|
|
{"l", 2, {tchkNum, tchkNum},
|
|
&Gfx::opLineTo},
|
|
{"m", 2, {tchkNum, tchkNum},
|
|
&Gfx::opMoveTo},
|
|
{"n", 0, {tchkNone},
|
|
&Gfx::opEndPath},
|
|
{"q", 0, {tchkNone},
|
|
&Gfx::opSave},
|
|
{"re", 4, {tchkNum, tchkNum, tchkNum, tchkNum},
|
|
&Gfx::opRectangle},
|
|
{"rg", 3, {tchkNum, tchkNum, tchkNum},
|
|
&Gfx::opSetFillRGBColor},
|
|
{"ri", 1, {tchkName},
|
|
&Gfx::opSetRenderingIntent},
|
|
{"s", 0, {tchkNone},
|
|
&Gfx::opCloseStroke},
|
|
{"sc", -4, {tchkNum, tchkNum, tchkNum, tchkNum,
|
|
tchkNum, tchkNum, tchkNum, tchkNum,
|
|
tchkNum, tchkNum, tchkNum, tchkNum,
|
|
tchkNum, tchkNum, tchkNum, tchkNum,
|
|
tchkNum, tchkNum, tchkNum, tchkNum,
|
|
tchkNum, tchkNum, tchkNum, tchkNum,
|
|
tchkNum, tchkNum, tchkNum, tchkNum,
|
|
tchkNum, tchkNum, tchkNum, tchkNum,
|
|
tchkNum},
|
|
&Gfx::opSetFillColor},
|
|
{"scn", -33, {tchkSCN, tchkSCN, tchkSCN, tchkSCN,
|
|
tchkSCN, tchkSCN, tchkSCN, tchkSCN,
|
|
tchkSCN, tchkSCN, tchkSCN, tchkSCN,
|
|
tchkSCN, tchkSCN, tchkSCN, tchkSCN,
|
|
tchkSCN, tchkSCN, tchkSCN, tchkSCN,
|
|
tchkSCN, tchkSCN, tchkSCN, tchkSCN,
|
|
tchkSCN, tchkSCN, tchkSCN, tchkSCN,
|
|
tchkSCN, tchkSCN, tchkSCN, tchkSCN,
|
|
tchkSCN},
|
|
&Gfx::opSetFillColorN},
|
|
{"sh", 1, {tchkName},
|
|
&Gfx::opShFill},
|
|
{"v", 4, {tchkNum, tchkNum, tchkNum, tchkNum},
|
|
&Gfx::opCurveTo1},
|
|
{"w", 1, {tchkNum},
|
|
&Gfx::opSetLineWidth},
|
|
{"y", 4, {tchkNum, tchkNum, tchkNum, tchkNum},
|
|
&Gfx::opCurveTo2},
|
|
};
|
|
|
|
#ifdef _WIN32 // this works around a bug in the VC7 compiler
|
|
# pragma optimize("",on)
|
|
#endif
|
|
|
|
#define numOps (sizeof(opTab) / sizeof(Operator))
|
|
|
|
//------------------------------------------------------------------------
|
|
// GfxResources
|
|
//------------------------------------------------------------------------
|
|
|
|
GfxResources::GfxResources(XRef *xref, Dict *resDict, GfxResources *nextA) {
|
|
Object obj1, obj2;
|
|
Ref r;
|
|
|
|
if (resDict) {
|
|
valid = gTrue;
|
|
|
|
// build font dictionary
|
|
fonts = NULL;
|
|
resDict->lookupNF("Font", &obj1);
|
|
if (obj1.isRef()) {
|
|
obj1.fetch(xref, &obj2);
|
|
if (obj2.isDict()) {
|
|
r = obj1.getRef();
|
|
fonts = new GfxFontDict(xref, &r, obj2.getDict());
|
|
}
|
|
obj2.free();
|
|
} else if (obj1.isDict()) {
|
|
fonts = new GfxFontDict(xref, NULL, obj1.getDict());
|
|
}
|
|
obj1.free();
|
|
|
|
// get XObject dictionary
|
|
resDict->lookup("XObject", &xObjDict);
|
|
|
|
// get color space dictionary
|
|
resDict->lookup("ColorSpace", &colorSpaceDict);
|
|
|
|
// get pattern dictionary
|
|
resDict->lookup("Pattern", &patternDict);
|
|
|
|
// get shading dictionary
|
|
resDict->lookup("Shading", &shadingDict);
|
|
|
|
// get graphics state parameter dictionary
|
|
resDict->lookup("ExtGState", &gStateDict);
|
|
|
|
// get properties dictionary
|
|
resDict->lookup("Properties", &propsDict);
|
|
|
|
} else {
|
|
valid = gFalse;
|
|
fonts = NULL;
|
|
xObjDict.initNull();
|
|
colorSpaceDict.initNull();
|
|
patternDict.initNull();
|
|
shadingDict.initNull();
|
|
gStateDict.initNull();
|
|
propsDict.initNull();
|
|
}
|
|
|
|
next = nextA;
|
|
}
|
|
|
|
GfxResources::~GfxResources() {
|
|
if (fonts) {
|
|
delete fonts;
|
|
}
|
|
xObjDict.free();
|
|
colorSpaceDict.free();
|
|
patternDict.free();
|
|
shadingDict.free();
|
|
gStateDict.free();
|
|
propsDict.free();
|
|
}
|
|
|
|
GfxFont *GfxResources::lookupFont(char *name) {
|
|
GfxFont *font;
|
|
GfxResources *resPtr;
|
|
|
|
for (resPtr = this; resPtr; resPtr = resPtr->next) {
|
|
if (resPtr->fonts) {
|
|
if ((font = resPtr->fonts->lookup(name))) {
|
|
return font;
|
|
}
|
|
}
|
|
}
|
|
error(errSyntaxError, -1, "Unknown font tag '{0:s}'", name);
|
|
return NULL;
|
|
}
|
|
|
|
GfxFont *GfxResources::lookupFontByRef(Ref ref) {
|
|
GfxFont *font;
|
|
GfxResources *resPtr;
|
|
|
|
for (resPtr = this; resPtr; resPtr = resPtr->next) {
|
|
if (resPtr->fonts) {
|
|
if ((font = resPtr->fonts->lookupByRef(ref))) {
|
|
return font;
|
|
}
|
|
}
|
|
}
|
|
error(errSyntaxError, -1, "Unknown font ref {0:d}.{1:d}", ref.num, ref.gen);
|
|
return NULL;
|
|
}
|
|
|
|
GBool GfxResources::lookupXObject(const char *name, Object *obj) {
|
|
GfxResources *resPtr;
|
|
|
|
for (resPtr = this; resPtr; resPtr = resPtr->next) {
|
|
if (resPtr->xObjDict.isDict()) {
|
|
if (!resPtr->xObjDict.dictLookup(name, obj)->isNull())
|
|
return gTrue;
|
|
obj->free();
|
|
}
|
|
}
|
|
error(errSyntaxError, -1, "XObject '{0:s}' is unknown", name);
|
|
return gFalse;
|
|
}
|
|
|
|
GBool GfxResources::lookupXObjectNF(const char *name, Object *obj) {
|
|
GfxResources *resPtr;
|
|
|
|
for (resPtr = this; resPtr; resPtr = resPtr->next) {
|
|
if (resPtr->xObjDict.isDict()) {
|
|
if (!resPtr->xObjDict.dictLookupNF(name, obj)->isNull())
|
|
return gTrue;
|
|
obj->free();
|
|
}
|
|
}
|
|
error(errSyntaxError, -1, "XObject '{0:s}' is unknown", name);
|
|
return gFalse;
|
|
}
|
|
|
|
void GfxResources::lookupColorSpace(const char *name, Object *obj,
|
|
GBool inherit) {
|
|
GfxResources *resPtr;
|
|
|
|
//~ should also test for G, RGB, and CMYK - but only in inline images (?)
|
|
if (!strcmp(name, "DeviceGray") ||
|
|
!strcmp(name, "DeviceRGB") ||
|
|
!strcmp(name, "DeviceCMYK")) {
|
|
obj->initNull();
|
|
return;
|
|
}
|
|
for (resPtr = this; resPtr; resPtr = resPtr->next) {
|
|
if (resPtr->colorSpaceDict.isDict()) {
|
|
if (!resPtr->colorSpaceDict.dictLookup(name, obj)->isNull()) {
|
|
return;
|
|
}
|
|
obj->free();
|
|
}
|
|
if (!inherit && valid) {
|
|
break;
|
|
}
|
|
}
|
|
obj->initNull();
|
|
}
|
|
|
|
GfxPattern *GfxResources::lookupPattern(const char *name
|
|
) {
|
|
GfxResources *resPtr;
|
|
GfxPattern *pattern;
|
|
Object objRef, obj;
|
|
|
|
for (resPtr = this; resPtr; resPtr = resPtr->next) {
|
|
if (resPtr->patternDict.isDict()) {
|
|
if (!resPtr->patternDict.dictLookup(name, &obj)->isNull()) {
|
|
resPtr->patternDict.dictLookupNF(name, &objRef);
|
|
pattern = GfxPattern::parse(&objRef, &obj
|
|
);
|
|
objRef.free();
|
|
obj.free();
|
|
return pattern;
|
|
}
|
|
obj.free();
|
|
}
|
|
}
|
|
error(errSyntaxError, -1, "Unknown pattern '{0:s}'", name);
|
|
return NULL;
|
|
}
|
|
|
|
GfxShading *GfxResources::lookupShading(const char *name
|
|
) {
|
|
GfxResources *resPtr;
|
|
GfxShading *shading;
|
|
Object obj;
|
|
|
|
for (resPtr = this; resPtr; resPtr = resPtr->next) {
|
|
if (resPtr->shadingDict.isDict()) {
|
|
if (!resPtr->shadingDict.dictLookup(name, &obj)->isNull()) {
|
|
shading = GfxShading::parse(&obj
|
|
);
|
|
obj.free();
|
|
return shading;
|
|
}
|
|
obj.free();
|
|
}
|
|
}
|
|
error(errSyntaxError, -1, "Unknown shading '{0:s}'", name);
|
|
return NULL;
|
|
}
|
|
|
|
GBool GfxResources::lookupGState(const char *name, Object *obj) {
|
|
GfxResources *resPtr;
|
|
|
|
for (resPtr = this; resPtr; resPtr = resPtr->next) {
|
|
if (resPtr->gStateDict.isDict()) {
|
|
if (!resPtr->gStateDict.dictLookup(name, obj)->isNull()) {
|
|
return gTrue;
|
|
}
|
|
obj->free();
|
|
}
|
|
}
|
|
error(errSyntaxError, -1, "ExtGState '{0:s}' is unknown", name);
|
|
return gFalse;
|
|
}
|
|
|
|
GBool GfxResources::lookupPropertiesNF(const char *name, Object *obj) {
|
|
GfxResources *resPtr;
|
|
|
|
for (resPtr = this; resPtr; resPtr = resPtr->next) {
|
|
if (resPtr->propsDict.isDict()) {
|
|
if (!resPtr->propsDict.dictLookupNF(name, obj)->isNull()) {
|
|
return gTrue;
|
|
}
|
|
obj->free();
|
|
}
|
|
}
|
|
error(errSyntaxError, -1, "Properties '{0:s}' is unknown", name);
|
|
return gFalse;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
// Gfx
|
|
//------------------------------------------------------------------------
|
|
|
|
Gfx::Gfx(PDFDoc *docA, OutputDev *outA, int pageNum, Dict *resDict,
|
|
double hDPI, double vDPI, PDFRectangle *box,
|
|
PDFRectangle *cropBox, int rotate,
|
|
GBool (*abortCheckCbkA)(void *data),
|
|
void *abortCheckCbkDataA) {
|
|
int i;
|
|
|
|
doc = docA;
|
|
xref = doc->getXRef();
|
|
subPage = gFalse;
|
|
printCommands = globalParams->getPrintCommands();
|
|
|
|
// start the resource stack
|
|
res = new GfxResources(xref, resDict, NULL);
|
|
|
|
// initialize
|
|
out = outA;
|
|
state = new GfxState(hDPI, vDPI, box, rotate, out->upsideDown());
|
|
fontChanged = gFalse;
|
|
clip = clipNone;
|
|
ignoreUndef = 0;
|
|
out->startPage(pageNum, state);
|
|
out->setDefaultCTM(state->getCTM());
|
|
out->updateAll(state);
|
|
for (i = 0; i < 6; ++i) {
|
|
baseMatrix[i] = state->getCTM()[i];
|
|
}
|
|
formDepth = 0;
|
|
markedContentStack = new GList();
|
|
ocState = gTrue;
|
|
parser = NULL;
|
|
contentStreamStack = new GList();
|
|
abortCheckCbk = abortCheckCbkA;
|
|
abortCheckCbkData = abortCheckCbkDataA;
|
|
|
|
// set crop box
|
|
if (cropBox) {
|
|
state->moveTo(cropBox->x1, cropBox->y1);
|
|
state->lineTo(cropBox->x2, cropBox->y1);
|
|
state->lineTo(cropBox->x2, cropBox->y2);
|
|
state->lineTo(cropBox->x1, cropBox->y2);
|
|
state->closePath();
|
|
state->clip();
|
|
out->clip(state);
|
|
state->clearPath();
|
|
}
|
|
}
|
|
|
|
Gfx::Gfx(PDFDoc *docA, OutputDev *outA, Dict *resDict,
|
|
PDFRectangle *box, PDFRectangle *cropBox,
|
|
GBool (*abortCheckCbkA)(void *data),
|
|
void *abortCheckCbkDataA) {
|
|
int i;
|
|
|
|
doc = docA;
|
|
xref = doc->getXRef();
|
|
subPage = gTrue;
|
|
printCommands = globalParams->getPrintCommands();
|
|
|
|
// start the resource stack
|
|
res = new GfxResources(xref, resDict, NULL);
|
|
|
|
// initialize
|
|
out = outA;
|
|
state = new GfxState(72, 72, box, 0, gFalse);
|
|
fontChanged = gFalse;
|
|
clip = clipNone;
|
|
ignoreUndef = 0;
|
|
for (i = 0; i < 6; ++i) {
|
|
baseMatrix[i] = state->getCTM()[i];
|
|
}
|
|
formDepth = 0;
|
|
markedContentStack = new GList();
|
|
ocState = gTrue;
|
|
parser = NULL;
|
|
contentStreamStack = new GList();
|
|
abortCheckCbk = abortCheckCbkA;
|
|
abortCheckCbkData = abortCheckCbkDataA;
|
|
|
|
// set crop box
|
|
if (cropBox) {
|
|
state->moveTo(cropBox->x1, cropBox->y1);
|
|
state->lineTo(cropBox->x2, cropBox->y1);
|
|
state->lineTo(cropBox->x2, cropBox->y2);
|
|
state->lineTo(cropBox->x1, cropBox->y2);
|
|
state->closePath();
|
|
state->clip();
|
|
out->clip(state);
|
|
state->clearPath();
|
|
}
|
|
}
|
|
|
|
Gfx::~Gfx() {
|
|
if (!subPage) {
|
|
out->endPage();
|
|
}
|
|
while (state->hasSaves()) {
|
|
restoreState();
|
|
}
|
|
delete state;
|
|
while (res) {
|
|
popResources();
|
|
}
|
|
deleteGList(markedContentStack, GfxMarkedContent);
|
|
delete contentStreamStack;
|
|
}
|
|
|
|
void Gfx::display(Object *objRef, GBool topLevel) {
|
|
Object obj1, obj2;
|
|
int i;
|
|
|
|
objRef->fetch(xref, &obj1);
|
|
if (obj1.isArray()) {
|
|
for (i = 0; i < obj1.arrayGetLength(); ++i) {
|
|
obj1.arrayGetNF(i, &obj2);
|
|
if (checkForContentStreamLoop(&obj2)) {
|
|
obj2.free();
|
|
obj1.free();
|
|
return;
|
|
}
|
|
obj2.free();
|
|
}
|
|
for (i = 0; i < obj1.arrayGetLength(); ++i) {
|
|
obj1.arrayGet(i, &obj2);
|
|
if (!obj2.isStream()) {
|
|
error(errSyntaxError, -1, "Invalid object type for content stream");
|
|
obj2.free();
|
|
obj1.free();
|
|
return;
|
|
}
|
|
obj2.free();
|
|
}
|
|
contentStreamStack->append(&obj1);
|
|
} else if (obj1.isStream()) {
|
|
if (checkForContentStreamLoop(objRef)) {
|
|
obj1.free();
|
|
return;
|
|
}
|
|
contentStreamStack->append(objRef);
|
|
} else {
|
|
error(errSyntaxError, -1, "Invalid object type for content stream");
|
|
obj1.free();
|
|
return;
|
|
}
|
|
parser = new Parser(xref, new Lexer(xref, &obj1), gFalse);
|
|
go(topLevel);
|
|
delete parser;
|
|
parser = NULL;
|
|
contentStreamStack->del(contentStreamStack->getLength() - 1);
|
|
obj1.free();
|
|
}
|
|
|
|
// If <ref> is already on contentStreamStack, i.e., if there is a loop
|
|
// in the content streams, report an error, and return true.
|
|
GBool Gfx::checkForContentStreamLoop(Object *ref) {
|
|
Object *objPtr;
|
|
Object obj1;
|
|
int i, j;
|
|
|
|
if (ref->isRef()) {
|
|
for (i = 0; i < contentStreamStack->getLength(); ++i) {
|
|
objPtr = (Object *)contentStreamStack->get(i);
|
|
if (objPtr->isRef()) {
|
|
if (ref->getRefNum() == objPtr->getRefNum() &&
|
|
ref->getRefGen() == objPtr->getRefGen()) {
|
|
error(errSyntaxError, -1, "Loop in content streams");
|
|
return gTrue;
|
|
}
|
|
} else if (objPtr->isArray()) {
|
|
for (j = 0; j < objPtr->arrayGetLength(); ++j) {
|
|
objPtr->arrayGetNF(j, &obj1);
|
|
if (obj1.isRef()) {
|
|
if (ref->getRefNum() == obj1.getRefNum() &&
|
|
ref->getRefGen() == obj1.getRefGen()) {
|
|
error(errSyntaxError, -1, "Loop in content streams");
|
|
obj1.free();
|
|
return gTrue;
|
|
}
|
|
}
|
|
obj1.free();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return gFalse;
|
|
}
|
|
|
|
void Gfx::go(GBool topLevel) {
|
|
Object obj;
|
|
Object args[maxArgs];
|
|
GBool aborted;
|
|
int numArgs, i;
|
|
int errCount;
|
|
|
|
// scan a sequence of objects
|
|
opCounter = 0;
|
|
aborted = gFalse;
|
|
errCount = 0;
|
|
numArgs = 0;
|
|
getContentObj(&obj);
|
|
while (!obj.isEOF()) {
|
|
|
|
// check for an abort
|
|
++opCounter;
|
|
if (abortCheckCbk && opCounter > 100) {
|
|
if ((*abortCheckCbk)(abortCheckCbkData)) {
|
|
aborted = gTrue;
|
|
break;
|
|
}
|
|
opCounter = 0;
|
|
}
|
|
|
|
// got a command - execute it
|
|
if (obj.isCmd()) {
|
|
if (printCommands) {
|
|
obj.print(stdout);
|
|
for (i = 0; i < numArgs; ++i) {
|
|
printf(" ");
|
|
args[i].print(stdout);
|
|
}
|
|
printf("\n");
|
|
fflush(stdout);
|
|
}
|
|
if (!execOp(&obj, args, numArgs)) {
|
|
++errCount;
|
|
}
|
|
obj.free();
|
|
for (i = 0; i < numArgs; ++i)
|
|
args[i].free();
|
|
numArgs = 0;
|
|
|
|
// check for too many errors
|
|
if (errCount > contentStreamErrorLimit) {
|
|
error(errSyntaxError, -1,
|
|
"Too many errors - giving up on this content stream");
|
|
break;
|
|
}
|
|
|
|
// got an argument - save it
|
|
} else if (numArgs < maxArgs) {
|
|
args[numArgs++] = obj;
|
|
|
|
// too many arguments - something is wrong
|
|
} else {
|
|
error(errSyntaxError, getPos(), "Too many args in content stream");
|
|
if (printCommands) {
|
|
printf("throwing away arg: ");
|
|
obj.print(stdout);
|
|
printf("\n");
|
|
fflush(stdout);
|
|
}
|
|
obj.free();
|
|
}
|
|
|
|
// grab the next object
|
|
getContentObj(&obj);
|
|
}
|
|
obj.free();
|
|
|
|
// args at end with no command
|
|
if (numArgs > 0) {
|
|
if (!aborted) {
|
|
error(errSyntaxError, getPos(), "Leftover args in content stream");
|
|
if (printCommands) {
|
|
printf("%d leftovers:", numArgs);
|
|
for (i = 0; i < numArgs; ++i) {
|
|
printf(" ");
|
|
args[i].print(stdout);
|
|
}
|
|
printf("\n");
|
|
fflush(stdout);
|
|
}
|
|
}
|
|
for (i = 0; i < numArgs; ++i) {
|
|
args[i].free();
|
|
}
|
|
}
|
|
}
|
|
|
|
void Gfx::getContentObj(Object *obj) {
|
|
parser->getObj(obj);
|
|
if (obj->isRef()) {
|
|
error(errSyntaxError, getPos(), "Indirect reference in content stream");
|
|
obj->free();
|
|
obj->initError();
|
|
}
|
|
}
|
|
|
|
// Returns true if successful, false on error.
|
|
GBool Gfx::execOp(Object *cmd, Object args[], int numArgs) {
|
|
Operator *op;
|
|
char *name;
|
|
Object *argPtr;
|
|
int i;
|
|
|
|
// find operator
|
|
name = cmd->getCmd();
|
|
if (!(op = findOp(name))) {
|
|
if (ignoreUndef > 0) {
|
|
return gTrue;
|
|
}
|
|
error(errSyntaxError, getPos(), "Unknown operator '{0:s}'", name);
|
|
return gFalse;
|
|
}
|
|
|
|
// type check args
|
|
argPtr = args;
|
|
if (op->numArgs >= 0) {
|
|
if (numArgs < op->numArgs) {
|
|
error(errSyntaxError, getPos(),
|
|
"Too few ({0:d}) args to '{1:s}' operator", numArgs, name);
|
|
return gFalse;
|
|
}
|
|
if (numArgs > op->numArgs) {
|
|
#if 0
|
|
error(errSyntaxWarning, getPos(),
|
|
"Too many ({0:d}) args to '{1:s}' operator", numArgs, name);
|
|
#endif
|
|
argPtr += numArgs - op->numArgs;
|
|
numArgs = op->numArgs;
|
|
}
|
|
} else {
|
|
if (numArgs > -op->numArgs) {
|
|
error(errSyntaxWarning, getPos(),
|
|
"Too many ({0:d}) args to '{1:s}' operator",
|
|
numArgs, name);
|
|
}
|
|
}
|
|
for (i = 0; i < numArgs; ++i) {
|
|
if (!checkArg(&argPtr[i], op->tchk[i])) {
|
|
error(errSyntaxError, getPos(),
|
|
"Arg #{0:d} to '{1:s}' operator is wrong type ({2:s})",
|
|
i, name, argPtr[i].getTypeName());
|
|
return gFalse;
|
|
}
|
|
}
|
|
|
|
// do it
|
|
(this->*op->func)(argPtr, numArgs);
|
|
|
|
return gTrue;
|
|
}
|
|
|
|
Operator *Gfx::findOp(char *name) {
|
|
int a, b, m, cmp;
|
|
|
|
a = -1;
|
|
b = numOps;
|
|
cmp = 0; // make gcc happy
|
|
// invariant: opTab[a] < name < opTab[b]
|
|
while (b - a > 1) {
|
|
m = (a + b) / 2;
|
|
cmp = strcmp(opTab[m].name, name);
|
|
if (cmp < 0)
|
|
a = m;
|
|
else if (cmp > 0)
|
|
b = m;
|
|
else
|
|
a = b = m;
|
|
}
|
|
if (cmp != 0)
|
|
return NULL;
|
|
return &opTab[a];
|
|
}
|
|
|
|
GBool Gfx::checkArg(Object *arg, TchkType type) {
|
|
switch (type) {
|
|
case tchkBool: return arg->isBool();
|
|
case tchkInt: return arg->isInt();
|
|
case tchkNum: return arg->isNum();
|
|
case tchkString: return arg->isString();
|
|
case tchkName: return arg->isName();
|
|
case tchkArray: return arg->isArray();
|
|
case tchkProps: return arg->isDict() || arg->isName();
|
|
case tchkSCN: return arg->isNum() || arg->isName();
|
|
case tchkNone: return gFalse;
|
|
}
|
|
return gFalse;
|
|
}
|
|
|
|
GFileOffset Gfx::getPos() {
|
|
return parser ? parser->getPos() : -1;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
// graphics state operators
|
|
//------------------------------------------------------------------------
|
|
|
|
void Gfx::opSave(Object args[], int numArgs) {
|
|
saveState();
|
|
}
|
|
|
|
void Gfx::opRestore(Object args[], int numArgs) {
|
|
restoreState();
|
|
}
|
|
|
|
void Gfx::opConcat(Object args[], int numArgs) {
|
|
state->concatCTM(args[0].getNum(), args[1].getNum(),
|
|
args[2].getNum(), args[3].getNum(),
|
|
args[4].getNum(), args[5].getNum());
|
|
out->updateCTM(state, args[0].getNum(), args[1].getNum(),
|
|
args[2].getNum(), args[3].getNum(),
|
|
args[4].getNum(), args[5].getNum());
|
|
fontChanged = gTrue;
|
|
}
|
|
|
|
void Gfx::opSetDash(Object args[], int numArgs) {
|
|
Array *a;
|
|
int length;
|
|
Object obj;
|
|
double *dash;
|
|
int i;
|
|
|
|
a = args[0].getArray();
|
|
length = a->getLength();
|
|
if (length == 0) {
|
|
dash = NULL;
|
|
} else {
|
|
dash = (double *)gmallocn(length, sizeof(double));
|
|
for (i = 0; i < length; ++i) {
|
|
dash[i] = a->get(i, &obj)->getNum();
|
|
obj.free();
|
|
}
|
|
}
|
|
state->setLineDash(dash, length, args[1].getNum());
|
|
out->updateLineDash(state);
|
|
}
|
|
|
|
void Gfx::opSetFlat(Object args[], int numArgs) {
|
|
state->setFlatness((int)args[0].getNum());
|
|
out->updateFlatness(state);
|
|
}
|
|
|
|
void Gfx::opSetLineJoin(Object args[], int numArgs) {
|
|
int lineJoin;
|
|
|
|
lineJoin = args[0].getInt();
|
|
if (lineJoin < 0 || lineJoin > 2) {
|
|
lineJoin = 0;
|
|
}
|
|
state->setLineJoin(lineJoin);
|
|
out->updateLineJoin(state);
|
|
}
|
|
|
|
void Gfx::opSetLineCap(Object args[], int numArgs) {
|
|
int lineCap;
|
|
|
|
lineCap = args[0].getInt();
|
|
if (lineCap < 0 || lineCap > 2) {
|
|
lineCap = 0;
|
|
}
|
|
state->setLineCap(lineCap);
|
|
out->updateLineCap(state);
|
|
}
|
|
|
|
void Gfx::opSetMiterLimit(Object args[], int numArgs) {
|
|
state->setMiterLimit(args[0].getNum());
|
|
out->updateMiterLimit(state);
|
|
}
|
|
|
|
void Gfx::opSetLineWidth(Object args[], int numArgs) {
|
|
state->setLineWidth(args[0].getNum());
|
|
out->updateLineWidth(state);
|
|
}
|
|
|
|
void Gfx::opSetExtGState(Object args[], int numArgs) {
|
|
Object obj1, obj2, obj3, objRef3, obj4, obj5, backdropColorObj;
|
|
Object args2[2];
|
|
GfxBlendMode mode;
|
|
GBool haveFillOP;
|
|
Function *funcs[4];
|
|
GBool alpha, knockout;
|
|
double opac;
|
|
int i;
|
|
|
|
if (!res->lookupGState(args[0].getName(), &obj1)) {
|
|
return;
|
|
}
|
|
if (!obj1.isDict()) {
|
|
error(errSyntaxError, getPos(),
|
|
"ExtGState '{0:s}' is wrong type", args[0].getName());
|
|
obj1.free();
|
|
return;
|
|
}
|
|
if (printCommands) {
|
|
printf(" gfx state dict: ");
|
|
obj1.print();
|
|
printf("\n");
|
|
}
|
|
|
|
if (out->useNameOp())
|
|
{
|
|
out->setExtGState(args[0].getName());
|
|
obj1.free();
|
|
return;
|
|
}
|
|
|
|
// parameters that are also set by individual PDF operators
|
|
if (obj1.dictLookup("LW", &obj2)->isNum()) {
|
|
opSetLineWidth(&obj2, 1);
|
|
}
|
|
obj2.free();
|
|
if (obj1.dictLookup("LC", &obj2)->isInt()) {
|
|
opSetLineCap(&obj2, 1);
|
|
}
|
|
obj2.free();
|
|
if (obj1.dictLookup("LJ", &obj2)->isInt()) {
|
|
opSetLineJoin(&obj2, 1);
|
|
}
|
|
obj2.free();
|
|
if (obj1.dictLookup("ML", &obj2)->isNum()) {
|
|
opSetMiterLimit(&obj2, 1);
|
|
}
|
|
obj2.free();
|
|
if (obj1.dictLookup("D", &obj2)->isArray() &&
|
|
obj2.arrayGetLength() == 2) {
|
|
obj2.arrayGet(0, &args2[0]);
|
|
obj2.arrayGet(1, &args2[1]);
|
|
if (args2[0].isArray() && args2[1].isNum()) {
|
|
opSetDash(args2, 2);
|
|
}
|
|
args2[0].free();
|
|
args2[1].free();
|
|
}
|
|
obj2.free();
|
|
if (obj1.dictLookup("FL", &obj2)->isNum()) {
|
|
opSetFlat(&obj2, 1);
|
|
}
|
|
obj2.free();
|
|
if (obj1.dictLookup("RI", &obj2)->isName()) {
|
|
opSetRenderingIntent(&obj2, 1);
|
|
}
|
|
obj2.free();
|
|
|
|
// font
|
|
if (obj1.dictLookup("Font", &obj2)->isArray() &&
|
|
obj2.arrayGetLength() == 2) {
|
|
obj2.arrayGetNF(0, &obj3);
|
|
obj2.arrayGetNF(1, &obj4);
|
|
if (obj3.isRef() && obj4.isNum()) {
|
|
doSetFont(res->lookupFontByRef(obj3.getRef()), obj4.getNum());
|
|
}
|
|
obj3.free();
|
|
obj4.free();
|
|
}
|
|
obj2.free();
|
|
|
|
// transparency support: blend mode, fill/stroke opacity
|
|
if (!obj1.dictLookup("BM", &obj2)->isNull()) {
|
|
if (state->parseBlendMode(&obj2, &mode)) {
|
|
state->setBlendMode(mode);
|
|
out->updateBlendMode(state);
|
|
} else {
|
|
error(errSyntaxError, getPos(), "Invalid blend mode in ExtGState");
|
|
}
|
|
}
|
|
obj2.free();
|
|
if (obj1.dictLookup("ca", &obj2)->isNum()) {
|
|
opac = obj2.getNum();
|
|
state->setFillOpacity(opac < 0 ? 0 : opac > 1 ? 1 : opac);
|
|
out->updateFillOpacity(state);
|
|
}
|
|
obj2.free();
|
|
if (obj1.dictLookup("CA", &obj2)->isNum()) {
|
|
opac = obj2.getNum();
|
|
state->setStrokeOpacity(opac < 0 ? 0 : opac > 1 ? 1 : opac);
|
|
out->updateStrokeOpacity(state);
|
|
}
|
|
obj2.free();
|
|
|
|
// fill/stroke overprint, overprint mode
|
|
if ((haveFillOP = (obj1.dictLookup("op", &obj2)->isBool()))) {
|
|
if (!state->getIgnoreColorOps()) {
|
|
state->setFillOverprint(obj2.getBool());
|
|
out->updateFillOverprint(state);
|
|
} else {
|
|
error(errSyntaxWarning, getPos(), "Ignoring overprint setting"
|
|
" in uncolored Type 3 char or tiling pattern");
|
|
}
|
|
}
|
|
obj2.free();
|
|
if (obj1.dictLookup("OP", &obj2)->isBool()) {
|
|
if (!state->getIgnoreColorOps()) {
|
|
state->setStrokeOverprint(obj2.getBool());
|
|
out->updateStrokeOverprint(state);
|
|
if (!haveFillOP) {
|
|
state->setFillOverprint(obj2.getBool());
|
|
out->updateFillOverprint(state);
|
|
}
|
|
} else {
|
|
error(errSyntaxWarning, getPos(), "Ignoring overprint setting"
|
|
" in uncolored Type 3 char or tiling pattern");
|
|
}
|
|
}
|
|
obj2.free();
|
|
if (obj1.dictLookup("OPM", &obj2)->isInt()) {
|
|
if (!state->getIgnoreColorOps()) {
|
|
state->setOverprintMode(obj2.getInt());
|
|
out->updateOverprintMode(state);
|
|
} else {
|
|
error(errSyntaxWarning, getPos(), "Ignoring overprint setting"
|
|
" in uncolored Type 3 char or tiling pattern");
|
|
}
|
|
}
|
|
obj2.free();
|
|
|
|
// stroke adjust
|
|
if (obj1.dictLookup("SA", &obj2)->isBool()) {
|
|
state->setStrokeAdjust(obj2.getBool());
|
|
out->updateStrokeAdjust(state);
|
|
}
|
|
obj2.free();
|
|
|
|
// transfer function
|
|
if (obj1.dictLookup("TR2", &obj2)->isNull()) {
|
|
obj2.free();
|
|
obj1.dictLookup("TR", &obj2);
|
|
}
|
|
if (!obj2.isNull()) {
|
|
if (!state->getIgnoreColorOps()) {
|
|
if (obj2.isName("Default") ||
|
|
obj2.isName("Identity")) {
|
|
funcs[0] = funcs[1] = funcs[2] = funcs[3] = NULL;
|
|
state->setTransfer(funcs);
|
|
out->updateTransfer(state);
|
|
} else if (obj2.isArray() && obj2.arrayGetLength() == 4) {
|
|
for (i = 0; i < 4; ++i) {
|
|
obj2.arrayGet(i, &obj3);
|
|
funcs[i] = Function::parse(&obj3, 1, 1);
|
|
obj3.free();
|
|
if (!funcs[i]) {
|
|
break;
|
|
}
|
|
}
|
|
if (i == 4) {
|
|
state->setTransfer(funcs);
|
|
out->updateTransfer(state);
|
|
}
|
|
} else if (obj2.isName() || obj2.isDict() || obj2.isStream()) {
|
|
if ((funcs[0] = Function::parse(&obj2, 1, 1))) {
|
|
funcs[1] = funcs[2] = funcs[3] = NULL;
|
|
state->setTransfer(funcs);
|
|
out->updateTransfer(state);
|
|
}
|
|
} else {
|
|
error(errSyntaxError, getPos(),
|
|
"Invalid transfer function in ExtGState");
|
|
}
|
|
} else {
|
|
error(errSyntaxWarning, getPos(), "Ignoring transfer function setting"
|
|
" in uncolored Type 3 char or tiling pattern");
|
|
}
|
|
}
|
|
obj2.free();
|
|
|
|
// soft mask
|
|
if (!obj1.dictLookup("SMask", &obj2)->isNull()) {
|
|
if (obj2.isName("None")) {
|
|
out->clearSoftMask(state);
|
|
} else if (obj2.isDict()) {
|
|
obj2.dictLookup("S", &obj3);
|
|
if (obj3.isName("Alpha")) {
|
|
alpha = gTrue;
|
|
} else if (obj3.isName("Luminosity")) {
|
|
alpha = gFalse;
|
|
} else {
|
|
error(errSyntaxError, getPos(),
|
|
"Missing S (subtype) entry in soft mask");
|
|
alpha = gFalse;
|
|
}
|
|
obj3.free();
|
|
funcs[0] = NULL;
|
|
if (!obj2.dictLookup("TR", &obj3)->isNull()) {
|
|
if (obj3.isName("Default") ||
|
|
obj3.isName("Identity")) {
|
|
funcs[0] = NULL;
|
|
} else {
|
|
if (!(funcs[0] = Function::parse(&obj3, 1, 1))) {
|
|
error(errSyntaxError, getPos(),
|
|
"Invalid transfer function in soft mask in ExtGState");
|
|
delete funcs[0];
|
|
funcs[0] = NULL;
|
|
}
|
|
}
|
|
}
|
|
obj3.free();
|
|
obj2.dictLookup("BC", &backdropColorObj);
|
|
if (obj2.dictLookup("G", &obj3)->isStream()) {
|
|
if (obj3.streamGetDict()->lookup("Group", &obj4)->isDict()) {
|
|
knockout = gFalse;
|
|
if (obj4.dictLookup("K", &obj5)->isBool()) {
|
|
knockout = obj5.getBool();
|
|
}
|
|
obj5.free();
|
|
obj2.dictLookupNF("G", &objRef3);
|
|
// it doesn't make sense for softmasks to be non-isolated,
|
|
// because they're blended with a backdrop color, rather
|
|
// than the original backdrop
|
|
doSoftMask(&obj3, &objRef3, alpha, gTrue, knockout, funcs[0],
|
|
&backdropColorObj);
|
|
objRef3.free();
|
|
if (funcs[0]) {
|
|
delete funcs[0];
|
|
}
|
|
} else {
|
|
error(errSyntaxError, getPos(),
|
|
"Invalid soft mask in ExtGState - missing group");
|
|
}
|
|
obj4.free();
|
|
} else {
|
|
error(errSyntaxError, getPos(),
|
|
"Invalid soft mask in ExtGState - missing group");
|
|
}
|
|
obj3.free();
|
|
backdropColorObj.free();
|
|
} else if (!obj2.isNull()) {
|
|
error(errSyntaxError, getPos(), "Invalid soft mask in ExtGState");
|
|
}
|
|
}
|
|
obj2.free();
|
|
|
|
obj1.free();
|
|
}
|
|
|
|
void Gfx::doSoftMask(Object *str, Object *strRef, GBool alpha,
|
|
GBool isolated, GBool knockout,
|
|
Function *transferFunc, Object *backdropColorObj) {
|
|
Dict *dict, *resDict;
|
|
double m[6], bbox[4];
|
|
Object obj1, obj2;
|
|
int i;
|
|
|
|
// check for excessive recursion
|
|
if (formDepth > 20) {
|
|
return;
|
|
}
|
|
|
|
// get stream dict
|
|
dict = str->streamGetDict();
|
|
|
|
// check form type
|
|
dict->lookup("FormType", &obj1);
|
|
if (!(obj1.isNull() || (obj1.isInt() && obj1.getInt() == 1))) {
|
|
error(errSyntaxError, getPos(), "Unknown form type");
|
|
}
|
|
obj1.free();
|
|
|
|
// get bounding box
|
|
dict->lookup("BBox", &obj1);
|
|
if (!obj1.isArray()) {
|
|
obj1.free();
|
|
error(errSyntaxError, getPos(), "Bad form bounding box");
|
|
return;
|
|
}
|
|
for (i = 0; i < 4; ++i) {
|
|
obj1.arrayGet(i, &obj2);
|
|
bbox[i] = obj2.getNum();
|
|
obj2.free();
|
|
}
|
|
obj1.free();
|
|
|
|
// get matrix
|
|
dict->lookup("Matrix", &obj1);
|
|
if (obj1.isArray()) {
|
|
for (i = 0; i < 6; ++i) {
|
|
obj1.arrayGet(i, &obj2);
|
|
m[i] = obj2.getNum();
|
|
obj2.free();
|
|
}
|
|
} else {
|
|
m[0] = 1; m[1] = 0;
|
|
m[2] = 0; m[3] = 1;
|
|
m[4] = 0; m[5] = 0;
|
|
}
|
|
obj1.free();
|
|
|
|
// get resources
|
|
dict->lookup("Resources", &obj1);
|
|
resDict = obj1.isDict() ? obj1.getDict() : (Dict *)NULL;
|
|
|
|
// draw it
|
|
++formDepth;
|
|
drawForm(strRef, resDict, m, bbox, gTrue, gTrue, isolated, knockout,
|
|
alpha, transferFunc, backdropColorObj);
|
|
--formDepth;
|
|
|
|
obj1.free();
|
|
}
|
|
|
|
void Gfx::opSetRenderingIntent(Object args[], int numArgs) {
|
|
GfxRenderingIntent ri;
|
|
|
|
if (state->getIgnoreColorOps()) {
|
|
error(errSyntaxWarning, getPos(), "Ignoring rendering intent setting"
|
|
" in uncolored Type 3 char or tiling pattern");
|
|
return;
|
|
}
|
|
ri = parseRenderingIntent(args[0].getName());
|
|
state->setRenderingIntent(ri);
|
|
out->updateRenderingIntent(state);
|
|
}
|
|
|
|
GfxRenderingIntent Gfx::parseRenderingIntent(const char *name) {
|
|
if (!strcmp(name, "AbsoluteColorimetric")) {
|
|
return gfxRenderingIntentAbsoluteColorimetric;
|
|
}
|
|
if (!strcmp(name, "Saturation")) {
|
|
return gfxRenderingIntentSaturation;
|
|
}
|
|
if (!strcmp(name, "Perceptual")) {
|
|
return gfxRenderingIntentPerceptual;
|
|
}
|
|
return gfxRenderingIntentRelativeColorimetric;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
// color operators
|
|
//------------------------------------------------------------------------
|
|
|
|
void Gfx::opSetFillGray(Object args[], int numArgs) {
|
|
GfxColor color;
|
|
|
|
if (state->getIgnoreColorOps()) {
|
|
error(errSyntaxWarning, getPos(), "Ignoring color setting"
|
|
" in uncolored Type 3 char or tiling pattern");
|
|
return;
|
|
}
|
|
state->setFillPattern(NULL);
|
|
state->setFillColorSpace(GfxColorSpace::create(csDeviceGray));
|
|
out->updateFillColorSpace(state);
|
|
color.c[0] = dblToCol(args[0].getNum());
|
|
state->setFillColor(&color);
|
|
out->updateFillColor(state);
|
|
}
|
|
|
|
void Gfx::opSetStrokeGray(Object args[], int numArgs) {
|
|
GfxColor color;
|
|
|
|
if (state->getIgnoreColorOps()) {
|
|
error(errSyntaxWarning, getPos(), "Ignoring color setting"
|
|
" in uncolored Type 3 char or tiling pattern");
|
|
return;
|
|
}
|
|
state->setStrokePattern(NULL);
|
|
state->setStrokeColorSpace(GfxColorSpace::create(csDeviceGray));
|
|
out->updateStrokeColorSpace(state);
|
|
color.c[0] = dblToCol(args[0].getNum());
|
|
state->setStrokeColor(&color);
|
|
out->updateStrokeColor(state);
|
|
}
|
|
|
|
void Gfx::opSetFillCMYKColor(Object args[], int numArgs) {
|
|
GfxColor color;
|
|
int i;
|
|
|
|
if (state->getIgnoreColorOps()) {
|
|
error(errSyntaxWarning, getPos(), "Ignoring color setting"
|
|
" in uncolored Type 3 char or tiling pattern");
|
|
return;
|
|
}
|
|
state->setFillPattern(NULL);
|
|
state->setFillColorSpace(GfxColorSpace::create(csDeviceCMYK));
|
|
out->updateFillColorSpace(state);
|
|
for (i = 0; i < 4; ++i) {
|
|
color.c[i] = dblToCol(args[i].getNum());
|
|
}
|
|
state->setFillColor(&color);
|
|
out->updateFillColor(state);
|
|
}
|
|
|
|
void Gfx::opSetStrokeCMYKColor(Object args[], int numArgs) {
|
|
GfxColor color;
|
|
int i;
|
|
|
|
if (state->getIgnoreColorOps()) {
|
|
error(errSyntaxWarning, getPos(), "Ignoring color setting"
|
|
" in uncolored Type 3 char or tiling pattern");
|
|
return;
|
|
}
|
|
state->setStrokePattern(NULL);
|
|
state->setStrokeColorSpace(GfxColorSpace::create(csDeviceCMYK));
|
|
out->updateStrokeColorSpace(state);
|
|
for (i = 0; i < 4; ++i) {
|
|
color.c[i] = dblToCol(args[i].getNum());
|
|
}
|
|
state->setStrokeColor(&color);
|
|
out->updateStrokeColor(state);
|
|
}
|
|
|
|
void Gfx::opSetFillRGBColor(Object args[], int numArgs) {
|
|
GfxColor color;
|
|
int i;
|
|
|
|
if (state->getIgnoreColorOps()) {
|
|
error(errSyntaxWarning, getPos(), "Ignoring color setting"
|
|
" in uncolored Type 3 char or tiling pattern");
|
|
return;
|
|
}
|
|
state->setFillPattern(NULL);
|
|
state->setFillColorSpace(GfxColorSpace::create(csDeviceRGB));
|
|
out->updateFillColorSpace(state);
|
|
for (i = 0; i < 3; ++i) {
|
|
color.c[i] = dblToCol(args[i].getNum());
|
|
}
|
|
state->setFillColor(&color);
|
|
out->updateFillColor(state);
|
|
}
|
|
|
|
void Gfx::opSetStrokeRGBColor(Object args[], int numArgs) {
|
|
GfxColor color;
|
|
int i;
|
|
|
|
if (state->getIgnoreColorOps()) {
|
|
error(errSyntaxWarning, getPos(), "Ignoring color setting"
|
|
" in uncolored Type 3 char or tiling pattern");
|
|
return;
|
|
}
|
|
state->setStrokePattern(NULL);
|
|
state->setStrokeColorSpace(GfxColorSpace::create(csDeviceRGB));
|
|
out->updateStrokeColorSpace(state);
|
|
for (i = 0; i < 3; ++i) {
|
|
color.c[i] = dblToCol(args[i].getNum());
|
|
}
|
|
state->setStrokeColor(&color);
|
|
out->updateStrokeColor(state);
|
|
}
|
|
|
|
void Gfx::opSetFillColorSpace(Object args[], int numArgs) {
|
|
Object obj;
|
|
GfxColorSpace *colorSpace;
|
|
GfxColor color;
|
|
|
|
if (state->getIgnoreColorOps()) {
|
|
error(errSyntaxWarning, getPos(), "Ignoring color space setting"
|
|
" in uncolored Type 3 char or tiling pattern");
|
|
return;
|
|
}
|
|
if (out->useNameOp())
|
|
{
|
|
out->setFillColorSpace(args[0].getName());
|
|
return;
|
|
}
|
|
state->setFillPattern(NULL);
|
|
res->lookupColorSpace(args[0].getName(), &obj);
|
|
if (obj.isNull()) {
|
|
colorSpace = GfxColorSpace::parse(&args[0]
|
|
);
|
|
} else {
|
|
colorSpace = GfxColorSpace::parse(&obj
|
|
);
|
|
}
|
|
obj.free();
|
|
if (colorSpace) {
|
|
state->setFillColorSpace(colorSpace);
|
|
out->updateFillColorSpace(state);
|
|
colorSpace->getDefaultColor(&color);
|
|
state->setFillColor(&color);
|
|
out->updateFillColor(state);
|
|
} else {
|
|
error(errSyntaxError, getPos(), "Bad color space (fill)");
|
|
}
|
|
}
|
|
|
|
void Gfx::opSetStrokeColorSpace(Object args[], int numArgs) {
|
|
Object obj;
|
|
GfxColorSpace *colorSpace;
|
|
GfxColor color;
|
|
|
|
if (state->getIgnoreColorOps()) {
|
|
error(errSyntaxWarning, getPos(), "Ignoring color space setting"
|
|
" in uncolored Type 3 char or tiling pattern");
|
|
return;
|
|
}
|
|
if (out->useNameOp())
|
|
{
|
|
out->setStrokeColorSpace(args[0].getName());
|
|
return;
|
|
}
|
|
state->setStrokePattern(NULL);
|
|
res->lookupColorSpace(args[0].getName(), &obj);
|
|
if (obj.isNull()) {
|
|
colorSpace = GfxColorSpace::parse(&args[0]
|
|
);
|
|
} else {
|
|
colorSpace = GfxColorSpace::parse(&obj
|
|
);
|
|
}
|
|
obj.free();
|
|
if (colorSpace) {
|
|
state->setStrokeColorSpace(colorSpace);
|
|
out->updateStrokeColorSpace(state);
|
|
colorSpace->getDefaultColor(&color);
|
|
state->setStrokeColor(&color);
|
|
out->updateStrokeColor(state);
|
|
} else {
|
|
error(errSyntaxError, getPos(), "Bad color space (stroke)");
|
|
}
|
|
}
|
|
|
|
// Technically, per the PDF spec, the 'sc' operator can only be used
|
|
// with device, CIE, and Indexed color spaces. Some buggy PDF
|
|
// generators use it for DeviceN, so we also allow that (same as
|
|
// 'scn', minus the optional pattern name argument).
|
|
void Gfx::opSetFillColor(Object args[], int numArgs) {
|
|
GfxColor color;
|
|
int i;
|
|
|
|
if (state->getIgnoreColorOps()) {
|
|
error(errSyntaxWarning, getPos(), "Ignoring color setting"
|
|
" in uncolored Type 3 char or tiling pattern");
|
|
return;
|
|
}
|
|
if (numArgs != state->getFillColorSpace()->getNComps()) {
|
|
error(errSyntaxError, getPos(),
|
|
"Incorrect number of arguments in 'sc' command");
|
|
return;
|
|
}
|
|
if (out->useNameOp())
|
|
{
|
|
out->setFillColor(args, numArgs);
|
|
return;
|
|
}
|
|
state->setFillPattern(NULL);
|
|
for (i = 0; i < numArgs; ++i) {
|
|
color.c[i] = dblToCol(args[i].getNum());
|
|
}
|
|
state->setFillColor(&color);
|
|
out->updateFillColor(state);
|
|
}
|
|
|
|
// Technically, per the PDF spec, the 'SC' operator can only be used
|
|
// with device, CIE, and Indexed color spaces. Some buggy PDF
|
|
// generators use it for DeviceN, so we also allow that (same as
|
|
// 'SCN', minus the optional pattern name argument).
|
|
void Gfx::opSetStrokeColor(Object args[], int numArgs) {
|
|
GfxColor color;
|
|
int i;
|
|
|
|
if (numArgs != state->getStrokeColorSpace()->getNComps()) {
|
|
error(errSyntaxError, getPos(),
|
|
"Incorrect number of arguments in 'SC' command");
|
|
return;
|
|
}
|
|
if (out->useNameOp())
|
|
{
|
|
out->setStrokeColor(args, numArgs);
|
|
return;
|
|
}
|
|
state->setStrokePattern(NULL);
|
|
for (i = 0; i < numArgs; ++i) {
|
|
color.c[i] = dblToCol(args[i].getNum());
|
|
}
|
|
state->setStrokeColor(&color);
|
|
out->updateStrokeColor(state);
|
|
}
|
|
|
|
void Gfx::opSetFillColorN(Object args[], int numArgs) {
|
|
GfxColor color;
|
|
GfxPattern *pattern;
|
|
int i;
|
|
|
|
if (state->getIgnoreColorOps()) {
|
|
error(errSyntaxWarning, getPos(), "Ignoring color setting"
|
|
" in uncolored Type 3 char or tiling pattern");
|
|
return;
|
|
}
|
|
if (out->useNameOp())
|
|
{
|
|
out->setFillColorN(args, numArgs);
|
|
return;
|
|
}
|
|
if (state->getFillColorSpace()->getMode() == csPattern) {
|
|
if (numArgs == 0 || !args[numArgs-1].isName()) {
|
|
error(errSyntaxError, getPos(),
|
|
"Invalid arguments in 'scn' command");
|
|
return;
|
|
}
|
|
if (numArgs > 1) {
|
|
if (!((GfxPatternColorSpace *)state->getFillColorSpace())->getUnder() ||
|
|
numArgs - 1 != ((GfxPatternColorSpace *)state->getFillColorSpace())
|
|
->getUnder()->getNComps()) {
|
|
error(errSyntaxError, getPos(),
|
|
"Incorrect number of arguments in 'scn' command");
|
|
return;
|
|
}
|
|
for (i = 0; i < numArgs - 1 && i < gfxColorMaxComps; ++i) {
|
|
if (args[i].isNum()) {
|
|
color.c[i] = dblToCol(args[i].getNum());
|
|
}
|
|
}
|
|
state->setFillColor(&color);
|
|
out->updateFillColor(state);
|
|
}
|
|
if ((pattern = res->lookupPattern(args[numArgs-1].getName()
|
|
))) {
|
|
state->setFillPattern(pattern);
|
|
}
|
|
} else {
|
|
if (numArgs != state->getFillColorSpace()->getNComps()) {
|
|
error(errSyntaxError, getPos(),
|
|
"Incorrect number of arguments in 'scn' command");
|
|
return;
|
|
}
|
|
state->setFillPattern(NULL);
|
|
for (i = 0; i < numArgs && i < gfxColorMaxComps; ++i) {
|
|
if (args[i].isNum()) {
|
|
color.c[i] = dblToCol(args[i].getNum());
|
|
}
|
|
}
|
|
state->setFillColor(&color);
|
|
out->updateFillColor(state);
|
|
}
|
|
}
|
|
|
|
void Gfx::opSetStrokeColorN(Object args[], int numArgs) {
|
|
GfxColor color;
|
|
GfxPattern *pattern;
|
|
int i;
|
|
|
|
if (state->getIgnoreColorOps()) {
|
|
error(errSyntaxWarning, getPos(), "Ignoring color setting"
|
|
" in uncolored Type 3 char or tiling pattern");
|
|
return;
|
|
}
|
|
if (out->useNameOp())
|
|
{
|
|
out->setStrokeColorN(args, numArgs);
|
|
return;
|
|
}
|
|
if (state->getStrokeColorSpace()->getMode() == csPattern) {
|
|
if (numArgs == 0 || !args[numArgs-1].isName()) {
|
|
error(errSyntaxError, getPos(),
|
|
"Invalid arguments in 'SCN' command");
|
|
return;
|
|
}
|
|
if (numArgs > 1) {
|
|
if (!((GfxPatternColorSpace *)state->getStrokeColorSpace())
|
|
->getUnder() ||
|
|
numArgs - 1 != ((GfxPatternColorSpace *)state->getStrokeColorSpace())
|
|
->getUnder()->getNComps()) {
|
|
error(errSyntaxError, getPos(),
|
|
"Incorrect number of arguments in 'SCN' command");
|
|
return;
|
|
}
|
|
for (i = 0; i < numArgs - 1 && i < gfxColorMaxComps; ++i) {
|
|
if (args[i].isNum()) {
|
|
color.c[i] = dblToCol(args[i].getNum());
|
|
}
|
|
}
|
|
state->setStrokeColor(&color);
|
|
out->updateStrokeColor(state);
|
|
}
|
|
if ((pattern = res->lookupPattern(args[numArgs-1].getName()
|
|
))) {
|
|
state->setStrokePattern(pattern);
|
|
}
|
|
|
|
} else {
|
|
if (numArgs != state->getStrokeColorSpace()->getNComps()) {
|
|
error(errSyntaxError, getPos(),
|
|
"Incorrect number of arguments in 'SCN' command");
|
|
return;
|
|
}
|
|
state->setStrokePattern(NULL);
|
|
for (i = 0; i < numArgs && i < gfxColorMaxComps; ++i) {
|
|
if (args[i].isNum()) {
|
|
color.c[i] = dblToCol(args[i].getNum());
|
|
}
|
|
}
|
|
state->setStrokeColor(&color);
|
|
out->updateStrokeColor(state);
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
// path segment operators
|
|
//------------------------------------------------------------------------
|
|
|
|
void Gfx::opMoveTo(Object args[], int numArgs) {
|
|
state->moveTo(args[0].getNum(), args[1].getNum());
|
|
}
|
|
|
|
void Gfx::opLineTo(Object args[], int numArgs) {
|
|
if (!state->isCurPt()) {
|
|
error(errSyntaxError, getPos(), "No current point in lineto");
|
|
return;
|
|
}
|
|
state->lineTo(args[0].getNum(), args[1].getNum());
|
|
}
|
|
|
|
void Gfx::opCurveTo(Object args[], int numArgs) {
|
|
double x1, y1, x2, y2, x3, y3;
|
|
|
|
if (!state->isCurPt()) {
|
|
error(errSyntaxError, getPos(), "No current point in curveto");
|
|
return;
|
|
}
|
|
x1 = args[0].getNum();
|
|
y1 = args[1].getNum();
|
|
x2 = args[2].getNum();
|
|
y2 = args[3].getNum();
|
|
x3 = args[4].getNum();
|
|
y3 = args[5].getNum();
|
|
state->curveTo(x1, y1, x2, y2, x3, y3);
|
|
}
|
|
|
|
void Gfx::opCurveTo1(Object args[], int numArgs) {
|
|
double x1, y1, x2, y2, x3, y3;
|
|
|
|
if (!state->isCurPt()) {
|
|
error(errSyntaxError, getPos(), "No current point in curveto1");
|
|
return;
|
|
}
|
|
x1 = state->getCurX();
|
|
y1 = state->getCurY();
|
|
x2 = args[0].getNum();
|
|
y2 = args[1].getNum();
|
|
x3 = args[2].getNum();
|
|
y3 = args[3].getNum();
|
|
state->curveTo(x1, y1, x2, y2, x3, y3);
|
|
}
|
|
|
|
void Gfx::opCurveTo2(Object args[], int numArgs) {
|
|
double x1, y1, x2, y2, x3, y3;
|
|
|
|
if (!state->isCurPt()) {
|
|
error(errSyntaxError, getPos(), "No current point in curveto2");
|
|
return;
|
|
}
|
|
x1 = args[0].getNum();
|
|
y1 = args[1].getNum();
|
|
x2 = args[2].getNum();
|
|
y2 = args[3].getNum();
|
|
x3 = x2;
|
|
y3 = y2;
|
|
state->curveTo(x1, y1, x2, y2, x3, y3);
|
|
}
|
|
|
|
void Gfx::opRectangle(Object args[], int numArgs) {
|
|
double x, y, w, h;
|
|
|
|
x = args[0].getNum();
|
|
y = args[1].getNum();
|
|
w = args[2].getNum();
|
|
h = args[3].getNum();
|
|
state->moveTo(x, y);
|
|
state->lineTo(x + w, y);
|
|
state->lineTo(x + w, y + h);
|
|
state->lineTo(x, y + h);
|
|
state->closePath();
|
|
}
|
|
|
|
void Gfx::opClosePath(Object args[], int numArgs) {
|
|
if (!state->isCurPt()) {
|
|
error(errSyntaxError, getPos(), "No current point in closepath");
|
|
return;
|
|
}
|
|
state->closePath();
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
// path painting operators
|
|
//------------------------------------------------------------------------
|
|
|
|
void Gfx::opEndPath(Object args[], int numArgs) {
|
|
doEndPath();
|
|
}
|
|
|
|
void Gfx::opStroke(Object args[], int numArgs) {
|
|
if (!state->isCurPt()) {
|
|
//error(errSyntaxError, getPos(), "No path in stroke");
|
|
return;
|
|
}
|
|
if (state->isPath()) {
|
|
if (ocState) {
|
|
if (state->getStrokeColorSpace()->getMode() == csPattern) {
|
|
doPatternStroke();
|
|
} else {
|
|
out->stroke(state);
|
|
}
|
|
}
|
|
}
|
|
doEndPath();
|
|
}
|
|
|
|
void Gfx::opCloseStroke(Object args[], int numArgs) {
|
|
if (!state->isCurPt()) {
|
|
//error(errSyntaxError, getPos(), "No path in closepath/stroke");
|
|
return;
|
|
}
|
|
if (state->isPath()) {
|
|
state->closePath();
|
|
if (ocState) {
|
|
if (state->getStrokeColorSpace()->getMode() == csPattern) {
|
|
doPatternStroke();
|
|
} else {
|
|
out->stroke(state);
|
|
}
|
|
}
|
|
}
|
|
doEndPath();
|
|
}
|
|
|
|
void Gfx::opFill(Object args[], int numArgs) {
|
|
if (!state->isCurPt()) {
|
|
//error(errSyntaxError, getPos(), "No path in fill");
|
|
return;
|
|
}
|
|
if (state->isPath()) {
|
|
if (ocState) {
|
|
if (state->getFillColorSpace()->getMode() == csPattern) {
|
|
doPatternFill(gFalse);
|
|
} else {
|
|
out->fill(state);
|
|
}
|
|
}
|
|
}
|
|
doEndPath();
|
|
}
|
|
|
|
void Gfx::opEOFill(Object args[], int numArgs) {
|
|
if (!state->isCurPt()) {
|
|
//error(errSyntaxError, getPos(), "No path in eofill");
|
|
return;
|
|
}
|
|
if (state->isPath()) {
|
|
if (ocState) {
|
|
if (state->getFillColorSpace()->getMode() == csPattern) {
|
|
doPatternFill(gTrue);
|
|
} else {
|
|
out->eoFill(state);
|
|
}
|
|
}
|
|
}
|
|
doEndPath();
|
|
}
|
|
|
|
void Gfx::opFillStroke(Object args[], int numArgs) {
|
|
if (!state->isCurPt()) {
|
|
//error(errSyntaxError, getPos(), "No path in fill/stroke");
|
|
return;
|
|
}
|
|
if (state->isPath()) {
|
|
if (ocState) {
|
|
if (state->getFillColorSpace()->getMode() == csPattern) {
|
|
doPatternFill(gFalse);
|
|
} else {
|
|
out->fill(state);
|
|
}
|
|
if (state->getStrokeColorSpace()->getMode() == csPattern) {
|
|
doPatternStroke();
|
|
} else {
|
|
out->stroke(state);
|
|
}
|
|
}
|
|
}
|
|
doEndPath();
|
|
}
|
|
|
|
void Gfx::opCloseFillStroke(Object args[], int numArgs) {
|
|
if (!state->isCurPt()) {
|
|
//error(errSyntaxError, getPos(), "No path in closepath/fill/stroke");
|
|
return;
|
|
}
|
|
if (state->isPath()) {
|
|
state->closePath();
|
|
if (ocState) {
|
|
if (state->getFillColorSpace()->getMode() == csPattern) {
|
|
doPatternFill(gFalse);
|
|
} else {
|
|
out->fill(state);
|
|
}
|
|
if (state->getStrokeColorSpace()->getMode() == csPattern) {
|
|
doPatternStroke();
|
|
} else {
|
|
out->stroke(state);
|
|
}
|
|
}
|
|
}
|
|
doEndPath();
|
|
}
|
|
|
|
void Gfx::opEOFillStroke(Object args[], int numArgs) {
|
|
if (!state->isCurPt()) {
|
|
//error(errSyntaxError, getPos(), "No path in eofill/stroke");
|
|
return;
|
|
}
|
|
if (state->isPath()) {
|
|
if (ocState) {
|
|
if (state->getFillColorSpace()->getMode() == csPattern) {
|
|
doPatternFill(gTrue);
|
|
} else {
|
|
out->eoFill(state);
|
|
}
|
|
if (state->getStrokeColorSpace()->getMode() == csPattern) {
|
|
doPatternStroke();
|
|
} else {
|
|
out->stroke(state);
|
|
}
|
|
}
|
|
}
|
|
doEndPath();
|
|
}
|
|
|
|
void Gfx::opCloseEOFillStroke(Object args[], int numArgs) {
|
|
if (!state->isCurPt()) {
|
|
//error(errSyntaxError, getPos(), "No path in closepath/eofill/stroke");
|
|
return;
|
|
}
|
|
if (state->isPath()) {
|
|
state->closePath();
|
|
if (ocState) {
|
|
if (state->getFillColorSpace()->getMode() == csPattern) {
|
|
doPatternFill(gTrue);
|
|
} else {
|
|
out->eoFill(state);
|
|
}
|
|
if (state->getStrokeColorSpace()->getMode() == csPattern) {
|
|
doPatternStroke();
|
|
} else {
|
|
out->stroke(state);
|
|
}
|
|
}
|
|
}
|
|
doEndPath();
|
|
}
|
|
|
|
void Gfx::doPatternFill(GBool eoFill) {
|
|
GfxPattern *pattern;
|
|
|
|
// this is a bit of a kludge -- patterns can be really slow, so we
|
|
// skip them if we're only doing text extraction, since they almost
|
|
// certainly don't contain any text
|
|
if (!out->needNonText()) {
|
|
return;
|
|
}
|
|
|
|
if (!(pattern = state->getFillPattern())) {
|
|
return;
|
|
}
|
|
switch (pattern->getType()) {
|
|
case 1:
|
|
doTilingPatternFill((GfxTilingPattern *)pattern, gFalse, eoFill, gFalse);
|
|
break;
|
|
case 2:
|
|
doShadingPatternFill((GfxShadingPattern *)pattern, gFalse, eoFill, gFalse);
|
|
break;
|
|
default:
|
|
error(errSyntaxError, getPos(), "Unknown pattern type ({0:d}) in fill",
|
|
pattern->getType());
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Gfx::doPatternStroke() {
|
|
GfxPattern *pattern;
|
|
|
|
// this is a bit of a kludge -- patterns can be really slow, so we
|
|
// skip them if we're only doing text extraction, since they almost
|
|
// certainly don't contain any text
|
|
if (!out->needNonText()) {
|
|
return;
|
|
}
|
|
|
|
if (!(pattern = state->getStrokePattern())) {
|
|
return;
|
|
}
|
|
switch (pattern->getType()) {
|
|
case 1:
|
|
doTilingPatternFill((GfxTilingPattern *)pattern, gTrue, gFalse, gFalse);
|
|
break;
|
|
case 2:
|
|
doShadingPatternFill((GfxShadingPattern *)pattern, gTrue, gFalse, gFalse);
|
|
break;
|
|
default:
|
|
error(errSyntaxError, getPos(), "Unknown pattern type ({0:d}) in stroke",
|
|
pattern->getType());
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Gfx::doPatternText() {
|
|
GfxPattern *pattern;
|
|
|
|
// this is a bit of a kludge -- patterns can be really slow, so we
|
|
// skip them if we're only doing text extraction, since they almost
|
|
// certainly don't contain any text
|
|
if (!out->needNonText()) {
|
|
return;
|
|
}
|
|
|
|
if (!(pattern = state->getFillPattern())) {
|
|
return;
|
|
}
|
|
switch (pattern->getType()) {
|
|
case 1:
|
|
doTilingPatternFill((GfxTilingPattern *)pattern, gFalse, gFalse, gTrue);
|
|
break;
|
|
case 2:
|
|
doShadingPatternFill((GfxShadingPattern *)pattern, gFalse, gFalse, gTrue);
|
|
break;
|
|
default:
|
|
error(errSyntaxError, getPos(), "Unknown pattern type ({0:d}) in fill",
|
|
pattern->getType());
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Gfx::doPatternImageMask(Object *ref, Stream *str, int width, int height,
|
|
GBool invert, GBool inlineImg, GBool interpolate) {
|
|
saveState();
|
|
|
|
out->setSoftMaskFromImageMask(state, ref, str,
|
|
width, height, invert, inlineImg, interpolate);
|
|
|
|
state->clearPath();
|
|
state->moveTo(0, 0);
|
|
state->lineTo(1, 0);
|
|
state->lineTo(1, 1);
|
|
state->lineTo(0, 1);
|
|
state->closePath();
|
|
doPatternFill(gTrue);
|
|
state->clearPath();
|
|
|
|
restoreState();
|
|
}
|
|
|
|
void Gfx::doTilingPatternFill(GfxTilingPattern *tPat,
|
|
GBool stroke, GBool eoFill, GBool text) {
|
|
GfxPatternColorSpace *patCS;
|
|
GfxColorSpace *cs;
|
|
GfxColor color;
|
|
GfxState *savedState;
|
|
double xMin, yMin, xMax, yMax, x, y, x1, y1, t;
|
|
double cxMin, cyMin, cxMax, cyMax;
|
|
int xi0, yi0, xi1, yi1, xi, yi;
|
|
double *ctm, *btm, *ptm;
|
|
double bbox[4], m[6], ictm[6], m1[6], imb[6];
|
|
double det;
|
|
double xstep, ystep;
|
|
int abortCheckCounter, i;
|
|
|
|
// get color space
|
|
patCS = (GfxPatternColorSpace *)(stroke ? state->getStrokeColorSpace()
|
|
: state->getFillColorSpace());
|
|
|
|
// construct a (pattern space) -> (current space) transform matrix
|
|
ctm = state->getCTM();
|
|
btm = baseMatrix;
|
|
ptm = tPat->getMatrix();
|
|
// iCTM = invert CTM
|
|
det = ctm[0] * ctm[3] - ctm[1] * ctm[2];
|
|
if (fabs(det) <= 1e-10) {
|
|
error(errSyntaxError, getPos(), "Singular matrix in tiling pattern fill");
|
|
return;
|
|
}
|
|
det = 1 / det;
|
|
ictm[0] = ctm[3] * det;
|
|
ictm[1] = -ctm[1] * det;
|
|
ictm[2] = -ctm[2] * det;
|
|
ictm[3] = ctm[0] * det;
|
|
ictm[4] = (ctm[2] * ctm[5] - ctm[3] * ctm[4]) * det;
|
|
ictm[5] = (ctm[1] * ctm[4] - ctm[0] * ctm[5]) * det;
|
|
// m1 = PTM * BTM = PTM * base transform matrix
|
|
m1[0] = ptm[0] * btm[0] + ptm[1] * btm[2];
|
|
m1[1] = ptm[0] * btm[1] + ptm[1] * btm[3];
|
|
m1[2] = ptm[2] * btm[0] + ptm[3] * btm[2];
|
|
m1[3] = ptm[2] * btm[1] + ptm[3] * btm[3];
|
|
m1[4] = ptm[4] * btm[0] + ptm[5] * btm[2] + btm[4];
|
|
m1[5] = ptm[4] * btm[1] + ptm[5] * btm[3] + btm[5];
|
|
// m = m1 * iCTM = (PTM * BTM) * (iCTM)
|
|
m[0] = m1[0] * ictm[0] + m1[1] * ictm[2];
|
|
m[1] = m1[0] * ictm[1] + m1[1] * ictm[3];
|
|
m[2] = m1[2] * ictm[0] + m1[3] * ictm[2];
|
|
m[3] = m1[2] * ictm[1] + m1[3] * ictm[3];
|
|
m[4] = m1[4] * ictm[0] + m1[5] * ictm[2] + ictm[4];
|
|
m[5] = m1[4] * ictm[1] + m1[5] * ictm[3] + ictm[5];
|
|
|
|
// construct a (device space) -> (pattern space) transform matrix
|
|
det = m1[0] * m1[3] - m1[1] * m1[2];
|
|
if (fabs(det) <= 1e-10) {
|
|
error(errSyntaxError, getPos(), "Singular matrix in tiling pattern fill");
|
|
return;
|
|
}
|
|
det = 1 / det;
|
|
imb[0] = m1[3] * det;
|
|
imb[1] = -m1[1] * det;
|
|
imb[2] = -m1[2] * det;
|
|
imb[3] = m1[0] * det;
|
|
imb[4] = (m1[2] * m1[5] - m1[3] * m1[4]) * det;
|
|
imb[5] = (m1[1] * m1[4] - m1[0] * m1[5]) * det;
|
|
|
|
// save current graphics state
|
|
savedState = saveStateStack();
|
|
|
|
// set underlying color space (for uncolored tiling patterns); set
|
|
// various other parameters (stroke color, line width) to match
|
|
// Adobe's behavior
|
|
state->setFillPattern(NULL);
|
|
state->setStrokePattern(NULL);
|
|
if (tPat->getPaintType() == 2 && (cs = patCS->getUnder())) {
|
|
state->setFillColorSpace(cs->copy());
|
|
out->updateFillColorSpace(state);
|
|
state->setStrokeColorSpace(cs->copy());
|
|
out->updateStrokeColorSpace(state);
|
|
state->setStrokeColor(state->getFillColor());
|
|
out->updateFillColor(state);
|
|
out->updateStrokeColor(state);
|
|
state->setIgnoreColorOps(gTrue);
|
|
} else {
|
|
state->setFillColorSpace(GfxColorSpace::create(csDeviceGray));
|
|
out->updateFillColorSpace(state);
|
|
state->getFillColorSpace()->getDefaultColor(&color);
|
|
state->setFillColor(&color);
|
|
out->updateFillColor(state);
|
|
state->setStrokeColorSpace(GfxColorSpace::create(csDeviceGray));
|
|
out->updateStrokeColorSpace(state);
|
|
state->getStrokeColorSpace()->getDefaultColor(&color);
|
|
state->setStrokeColor(&color);
|
|
out->updateStrokeColor(state);
|
|
}
|
|
if (!stroke) {
|
|
state->setLineWidth(0);
|
|
out->updateLineWidth(state);
|
|
state->setLineDash(NULL, 0, 0);
|
|
out->updateLineDash(state);
|
|
}
|
|
|
|
// clip to current path
|
|
if (stroke) {
|
|
state->clipToStrokePath();
|
|
out->clipToStrokePath(state);
|
|
} else if (!text) {
|
|
state->clip();
|
|
if (eoFill) {
|
|
out->eoClip(state);
|
|
} else {
|
|
out->clip(state);
|
|
}
|
|
}
|
|
state->clearPath();
|
|
|
|
// get the clip region, check for empty
|
|
state->getClipBBox(&cxMin, &cyMin, &cxMax, &cyMax);
|
|
if (cxMin > cxMax || cyMin > cyMax) {
|
|
goto err;
|
|
}
|
|
|
|
// transform clip region bbox to pattern space
|
|
xMin = xMax = cxMin * imb[0] + cyMin * imb[2] + imb[4];
|
|
yMin = yMax = cxMin * imb[1] + cyMin * imb[3] + imb[5];
|
|
x1 = cxMin * imb[0] + cyMax * imb[2] + imb[4];
|
|
y1 = cxMin * imb[1] + cyMax * imb[3] + imb[5];
|
|
if (x1 < xMin) {
|
|
xMin = x1;
|
|
} else if (x1 > xMax) {
|
|
xMax = x1;
|
|
}
|
|
if (y1 < yMin) {
|
|
yMin = y1;
|
|
} else if (y1 > yMax) {
|
|
yMax = y1;
|
|
}
|
|
x1 = cxMax * imb[0] + cyMin * imb[2] + imb[4];
|
|
y1 = cxMax * imb[1] + cyMin * imb[3] + imb[5];
|
|
if (x1 < xMin) {
|
|
xMin = x1;
|
|
} else if (x1 > xMax) {
|
|
xMax = x1;
|
|
}
|
|
if (y1 < yMin) {
|
|
yMin = y1;
|
|
} else if (y1 > yMax) {
|
|
yMax = y1;
|
|
}
|
|
x1 = cxMax * imb[0] + cyMax * imb[2] + imb[4];
|
|
y1 = cxMax * imb[1] + cyMax * imb[3] + imb[5];
|
|
if (x1 < xMin) {
|
|
xMin = x1;
|
|
} else if (x1 > xMax) {
|
|
xMax = x1;
|
|
}
|
|
if (y1 < yMin) {
|
|
yMin = y1;
|
|
} else if (y1 > yMax) {
|
|
yMax = y1;
|
|
}
|
|
|
|
// draw the pattern
|
|
//~ this should treat negative steps differently -- start at right/top
|
|
//~ edge instead of left/bottom (?)
|
|
bbox[0] = tPat->getBBox()[0];
|
|
bbox[1] = tPat->getBBox()[1];
|
|
bbox[2] = tPat->getBBox()[2];
|
|
bbox[3] = tPat->getBBox()[3];
|
|
if (bbox[0] > bbox[2]) {
|
|
t = bbox[0]; bbox[0] = bbox[2]; bbox[2] = t;
|
|
}
|
|
if (bbox[1] > bbox[3]) {
|
|
t = bbox[1]; bbox[1] = bbox[3]; bbox[3] = t;
|
|
}
|
|
xstep = fabs(tPat->getXStep());
|
|
ystep = fabs(tPat->getYStep());
|
|
xi0 = (int)ceil((xMin - bbox[2]) / xstep);
|
|
xi1 = (int)floor((xMax - bbox[0]) / xstep) + 1;
|
|
yi0 = (int)ceil((yMin - bbox[3]) / ystep);
|
|
yi1 = (int)floor((yMax - bbox[1]) / ystep) + 1;
|
|
for (i = 0; i < 4; ++i) {
|
|
m1[i] = m[i];
|
|
}
|
|
if (out->useTilingPatternFill() && fabs(bbox[2] - bbox[0] - xstep) < 0.001 && fabs(bbox[3] - bbox[1] - ystep) < 0.001) {
|
|
m1[4] = m[4];
|
|
m1[5] = m[5];
|
|
out->tilingPatternFill(state, this, tPat->getContentStreamRef(),
|
|
tPat->getPaintType(), tPat->getTilingType(),
|
|
tPat->getResDict(),
|
|
m1, bbox,
|
|
xi0, yi0, xi1, yi1, xstep, ystep);
|
|
} else {
|
|
abortCheckCounter = 0;
|
|
for (yi = yi0; yi < yi1; ++yi) {
|
|
for (xi = xi0; xi < xi1; ++xi) {
|
|
if (abortCheckCbk) {
|
|
++abortCheckCounter;
|
|
if (abortCheckCounter > 100) {
|
|
if ((*abortCheckCbk)(abortCheckCbkData)) {
|
|
goto err;
|
|
}
|
|
abortCheckCounter = 0;
|
|
}
|
|
}
|
|
x = xi * xstep;
|
|
y = yi * ystep;
|
|
m1[4] = x * m[0] + y * m[2] + m[4];
|
|
m1[5] = x * m[1] + y * m[3] + m[5];
|
|
drawForm(tPat->getContentStreamRef(), tPat->getResDict(),
|
|
m1, bbox);
|
|
}
|
|
}
|
|
}
|
|
|
|
// restore graphics state
|
|
err:
|
|
restoreStateStack(savedState);
|
|
}
|
|
|
|
void Gfx::doShadingPatternFill(GfxShadingPattern *sPat,
|
|
GBool stroke, GBool eoFill, GBool text) {
|
|
GfxShading *shading;
|
|
GfxState *savedState;
|
|
double *ctm, *btm, *ptm;
|
|
double m[6], ictm[6], m1[6];
|
|
double xMin, yMin, xMax, yMax;
|
|
double det;
|
|
|
|
shading = sPat->getShading();
|
|
|
|
// save current graphics state
|
|
savedState = saveStateStack();
|
|
|
|
// clip to current path
|
|
if (stroke) {
|
|
state->clipToStrokePath();
|
|
out->clipToStrokePath(state);
|
|
state->setFillOverprint(state->getStrokeOverprint());
|
|
} else if (!text) {
|
|
state->clip();
|
|
if (eoFill) {
|
|
out->eoClip(state);
|
|
} else {
|
|
out->clip(state);
|
|
}
|
|
}
|
|
state->clearPath();
|
|
|
|
// construct a (pattern space) -> (current space) transform matrix
|
|
ctm = state->getCTM();
|
|
btm = baseMatrix;
|
|
ptm = sPat->getMatrix();
|
|
// iCTM = invert CTM
|
|
det = ctm[0] * ctm[3] - ctm[1] * ctm[2];
|
|
if (fabs(det) <= 1e-10) {
|
|
error(errSyntaxError, getPos(), "Singular matrix in shading pattern fill");
|
|
restoreStateStack(savedState);
|
|
return;
|
|
}
|
|
det = 1 / det;
|
|
ictm[0] = ctm[3] * det;
|
|
ictm[1] = -ctm[1] * det;
|
|
ictm[2] = -ctm[2] * det;
|
|
ictm[3] = ctm[0] * det;
|
|
ictm[4] = (ctm[2] * ctm[5] - ctm[3] * ctm[4]) * det;
|
|
ictm[5] = (ctm[1] * ctm[4] - ctm[0] * ctm[5]) * det;
|
|
// m1 = PTM * BTM = PTM * base transform matrix
|
|
m1[0] = ptm[0] * btm[0] + ptm[1] * btm[2];
|
|
m1[1] = ptm[0] * btm[1] + ptm[1] * btm[3];
|
|
m1[2] = ptm[2] * btm[0] + ptm[3] * btm[2];
|
|
m1[3] = ptm[2] * btm[1] + ptm[3] * btm[3];
|
|
m1[4] = ptm[4] * btm[0] + ptm[5] * btm[2] + btm[4];
|
|
m1[5] = ptm[4] * btm[1] + ptm[5] * btm[3] + btm[5];
|
|
// m = m1 * iCTM = (PTM * BTM) * (iCTM)
|
|
m[0] = m1[0] * ictm[0] + m1[1] * ictm[2];
|
|
m[1] = m1[0] * ictm[1] + m1[1] * ictm[3];
|
|
m[2] = m1[2] * ictm[0] + m1[3] * ictm[2];
|
|
m[3] = m1[2] * ictm[1] + m1[3] * ictm[3];
|
|
m[4] = m1[4] * ictm[0] + m1[5] * ictm[2] + ictm[4];
|
|
m[5] = m1[4] * ictm[1] + m1[5] * ictm[3] + ictm[5];
|
|
|
|
// set the new matrix
|
|
state->concatCTM(m[0], m[1], m[2], m[3], m[4], m[5]);
|
|
out->updateCTM(state, m[0], m[1], m[2], m[3], m[4], m[5]);
|
|
|
|
// clip to bbox
|
|
if (shading->getHasBBox()) {
|
|
shading->getBBox(&xMin, &yMin, &xMax, &yMax);
|
|
state->moveTo(xMin, yMin);
|
|
state->lineTo(xMax, yMin);
|
|
state->lineTo(xMax, yMax);
|
|
state->lineTo(xMin, yMax);
|
|
state->closePath();
|
|
state->clip();
|
|
out->clip(state);
|
|
state->clearPath();
|
|
}
|
|
|
|
// set the color space
|
|
state->setFillColorSpace(shading->getColorSpace()->copy());
|
|
out->updateFillColorSpace(state);
|
|
|
|
// background color fill
|
|
if (shading->getHasBackground()) {
|
|
state->setFillColor(shading->getBackground());
|
|
out->updateFillColor(state);
|
|
state->getUserClipBBox(&xMin, &yMin, &xMax, &yMax);
|
|
state->moveTo(xMin, yMin);
|
|
state->lineTo(xMax, yMin);
|
|
state->lineTo(xMax, yMax);
|
|
state->lineTo(xMin, yMax);
|
|
state->closePath();
|
|
out->fill(state);
|
|
state->clearPath();
|
|
}
|
|
|
|
// perform the fill
|
|
doShFill(shading);
|
|
|
|
// restore graphics state
|
|
restoreStateStack(savedState);
|
|
}
|
|
|
|
void Gfx::opShFill(Object args[], int numArgs) {
|
|
GfxShading *shading;
|
|
GfxState *savedState;
|
|
double xMin, yMin, xMax, yMax;
|
|
|
|
if (state->getIgnoreColorOps()) {
|
|
error(errSyntaxWarning, getPos(), "Ignoring shaded fill"
|
|
" in uncolored Type 3 char or tiling pattern");
|
|
return;
|
|
}
|
|
|
|
if (!out->needNonText()) {
|
|
return;
|
|
}
|
|
|
|
if (!ocState) {
|
|
return;
|
|
}
|
|
|
|
if (out->useNameOp())
|
|
{
|
|
out->setShading(state, args[0].getName());
|
|
return;
|
|
}
|
|
|
|
if (!(shading = res->lookupShading(args[0].getName()
|
|
))) {
|
|
return;
|
|
}
|
|
// save current graphics state
|
|
savedState = saveStateStack();
|
|
|
|
// clip to bbox
|
|
if (shading->getHasBBox()) {
|
|
shading->getBBox(&xMin, &yMin, &xMax, &yMax);
|
|
state->moveTo(xMin, yMin);
|
|
state->lineTo(xMax, yMin);
|
|
state->lineTo(xMax, yMax);
|
|
state->lineTo(xMin, yMax);
|
|
state->closePath();
|
|
state->clip();
|
|
out->clip(state);
|
|
state->clearPath();
|
|
}
|
|
|
|
// set the color space
|
|
state->setFillColorSpace(shading->getColorSpace()->copy());
|
|
out->updateFillColorSpace(state);
|
|
|
|
// perform the fill
|
|
doShFill(shading);
|
|
|
|
// restore graphics state
|
|
restoreStateStack(savedState);
|
|
|
|
delete shading;
|
|
}
|
|
|
|
void Gfx::doShFill(GfxShading *shading) {
|
|
if (out->shadedFill(state, shading)) {
|
|
return;
|
|
}
|
|
|
|
// do shading type-specific operations
|
|
switch (shading->getType()) {
|
|
case 1:
|
|
doFunctionShFill((GfxFunctionShading *)shading);
|
|
break;
|
|
case 2:
|
|
doAxialShFill((GfxAxialShading *)shading);
|
|
break;
|
|
case 3:
|
|
doRadialShFill((GfxRadialShading *)shading);
|
|
break;
|
|
case 4:
|
|
case 5:
|
|
doGouraudTriangleShFill((GfxGouraudTriangleShading *)shading);
|
|
break;
|
|
case 6:
|
|
case 7:
|
|
doPatchMeshShFill((GfxPatchMeshShading *)shading);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Gfx::doFunctionShFill(GfxFunctionShading *shading) {
|
|
double x0, y0, x1, y1;
|
|
GfxColor colors[4];
|
|
|
|
shading->getDomain(&x0, &y0, &x1, &y1);
|
|
shading->getColor(x0, y0, &colors[0]);
|
|
shading->getColor(x0, y1, &colors[1]);
|
|
shading->getColor(x1, y0, &colors[2]);
|
|
shading->getColor(x1, y1, &colors[3]);
|
|
doFunctionShFill1(shading, x0, y0, x1, y1, colors, 0);
|
|
}
|
|
|
|
void Gfx::doFunctionShFill1(GfxFunctionShading *shading,
|
|
double x0, double y0,
|
|
double x1, double y1,
|
|
GfxColor *colors, int depth) {
|
|
GfxColor fillColor;
|
|
GfxColor color0M, color1M, colorM0, colorM1, colorMM;
|
|
GfxColor colors2[4];
|
|
double *matrix;
|
|
double xM, yM;
|
|
int nComps, i, j;
|
|
|
|
nComps = shading->getColorSpace()->getNComps();
|
|
matrix = shading->getMatrix();
|
|
|
|
// compare the four corner colors
|
|
for (i = 0; i < 4; ++i) {
|
|
for (j = 0; j < nComps; ++j) {
|
|
if (abs(colors[i].c[j] - colors[(i+1)&3].c[j]) > functionColorDelta) {
|
|
break;
|
|
}
|
|
}
|
|
if (j < nComps) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// center of the rectangle
|
|
xM = 0.5 * (x0 + x1);
|
|
yM = 0.5 * (y0 + y1);
|
|
|
|
// the four corner colors are close (or we hit the recursive limit)
|
|
// -- fill the rectangle; but require at least one subdivision
|
|
// (depth==0) to avoid problems when the four outer corners of the
|
|
// shaded region are the same color
|
|
if ((i == 4 && depth > 0) || depth == functionMaxDepth) {
|
|
|
|
// use the center color
|
|
shading->getColor(xM, yM, &fillColor);
|
|
state->setFillColor(&fillColor);
|
|
out->updateFillColor(state);
|
|
|
|
// fill the rectangle
|
|
state->moveTo(x0 * matrix[0] + y0 * matrix[2] + matrix[4],
|
|
x0 * matrix[1] + y0 * matrix[3] + matrix[5]);
|
|
state->lineTo(x1 * matrix[0] + y0 * matrix[2] + matrix[4],
|
|
x1 * matrix[1] + y0 * matrix[3] + matrix[5]);
|
|
state->lineTo(x1 * matrix[0] + y1 * matrix[2] + matrix[4],
|
|
x1 * matrix[1] + y1 * matrix[3] + matrix[5]);
|
|
state->lineTo(x0 * matrix[0] + y1 * matrix[2] + matrix[4],
|
|
x0 * matrix[1] + y1 * matrix[3] + matrix[5]);
|
|
state->closePath();
|
|
out->fill(state);
|
|
state->clearPath();
|
|
|
|
// the four corner colors are not close enough -- subdivide the
|
|
// rectangle
|
|
} else {
|
|
|
|
// colors[0] colorM0 colors[2]
|
|
// (x0,y0) (xM,y0) (x1,y0)
|
|
// +----------+----------+
|
|
// | | |
|
|
// | UL | UR |
|
|
// color0M | colorMM | color1M
|
|
// (x0,yM) +----------+----------+ (x1,yM)
|
|
// | (xM,yM) |
|
|
// | LL | LR |
|
|
// | | |
|
|
// +----------+----------+
|
|
// colors[1] colorM1 colors[3]
|
|
// (x0,y1) (xM,y1) (x1,y1)
|
|
|
|
shading->getColor(x0, yM, &color0M);
|
|
shading->getColor(x1, yM, &color1M);
|
|
shading->getColor(xM, y0, &colorM0);
|
|
shading->getColor(xM, y1, &colorM1);
|
|
shading->getColor(xM, yM, &colorMM);
|
|
|
|
// upper-left sub-rectangle
|
|
colors2[0] = colors[0];
|
|
colors2[1] = color0M;
|
|
colors2[2] = colorM0;
|
|
colors2[3] = colorMM;
|
|
doFunctionShFill1(shading, x0, y0, xM, yM, colors2, depth + 1);
|
|
|
|
// lower-left sub-rectangle
|
|
colors2[0] = color0M;
|
|
colors2[1] = colors[1];
|
|
colors2[2] = colorMM;
|
|
colors2[3] = colorM1;
|
|
doFunctionShFill1(shading, x0, yM, xM, y1, colors2, depth + 1);
|
|
|
|
// upper-right sub-rectangle
|
|
colors2[0] = colorM0;
|
|
colors2[1] = colorMM;
|
|
colors2[2] = colors[2];
|
|
colors2[3] = color1M;
|
|
doFunctionShFill1(shading, xM, y0, x1, yM, colors2, depth + 1);
|
|
|
|
// lower-right sub-rectangle
|
|
colors2[0] = colorMM;
|
|
colors2[1] = colorM1;
|
|
colors2[2] = color1M;
|
|
colors2[3] = colors[3];
|
|
doFunctionShFill1(shading, xM, yM, x1, y1, colors2, depth + 1);
|
|
}
|
|
}
|
|
|
|
void Gfx::doAxialShFill(GfxAxialShading *shading) {
|
|
double xMin, yMin, xMax, yMax;
|
|
double x0, y0, x1, y1;
|
|
double dx, dy, mul;
|
|
GBool dxdyZero, horiz;
|
|
double tMin, tMax, tMinExt, tMaxExt, t, tx, ty;
|
|
double sMin, sMax, tmp;
|
|
double ux0, uy0, ux1, uy1, vx0, vy0, vx1, vy1;
|
|
double t0, t1, tt;
|
|
GfxColor colors[axialSplits];
|
|
int abortCheckCounter, nComps, i, j, k;
|
|
|
|
// get the clip region bbox
|
|
state->getUserClipBBox(&xMin, &yMin, &xMax, &yMax);
|
|
|
|
// compute min and max t values, based on the four corners of the
|
|
// clip region bbox
|
|
shading->getCoords(&x0, &y0, &x1, &y1);
|
|
dx = x1 - x0;
|
|
dy = y1 - y0;
|
|
dxdyZero = fabs(dx) < 0.0001 && fabs(dy) < 0.0001;
|
|
horiz = fabs(dy) < fabs(dx);
|
|
if (dxdyZero) {
|
|
tMinExt = tMaxExt = 0;
|
|
tMin = tMax = 0;
|
|
} else {
|
|
mul = 1 / (dx * dx + dy * dy);
|
|
tMinExt = tMaxExt = ((xMin - x0) * dx + (yMin - y0) * dy) * mul;
|
|
t = ((xMin - x0) * dx + (yMax - y0) * dy) * mul;
|
|
if (t < tMinExt) {
|
|
tMinExt = t;
|
|
} else if (t > tMaxExt) {
|
|
tMaxExt = t;
|
|
}
|
|
t = ((xMax - x0) * dx + (yMin - y0) * dy) * mul;
|
|
if (t < tMinExt) {
|
|
tMinExt = t;
|
|
} else if (t > tMaxExt) {
|
|
tMaxExt = t;
|
|
}
|
|
t = ((xMax - x0) * dx + (yMax - y0) * dy) * mul;
|
|
if (t < tMinExt) {
|
|
tMinExt = t;
|
|
} else if (t > tMaxExt) {
|
|
tMaxExt = t;
|
|
}
|
|
if ((tMin = tMinExt) < 0) {
|
|
tMin = 0;
|
|
}
|
|
if (!shading->getExtend0()) {
|
|
tMinExt = tMin;
|
|
}
|
|
if ((tMax = tMaxExt) > 1) {
|
|
tMax = 1;
|
|
}
|
|
if (!shading->getExtend1()) {
|
|
tMaxExt = tMax;
|
|
}
|
|
}
|
|
|
|
// get the function domain
|
|
t0 = shading->getDomain0();
|
|
t1 = shading->getDomain1();
|
|
|
|
// Traverse the t axis and do the shading.
|
|
//
|
|
// For each point (tx, ty) on the t axis, consider a line through
|
|
// that point perpendicular to the t axis:
|
|
//
|
|
// x(s) = tx + s * -dy --> s = (x - tx) / -dy
|
|
// y(s) = ty + s * dx --> s = (y - ty) / dx
|
|
//
|
|
// Then look at the intersection of this line with the bounding box
|
|
// (xMin, yMin, xMax, yMax). For -1 < |dy/dx| < 1, look at the
|
|
// intersection with yMin, yMax:
|
|
//
|
|
// s0 = (yMin - ty) / dx
|
|
// s1 = (yMax - ty) / dx
|
|
//
|
|
// else look at the intersection with xMin, xMax:
|
|
//
|
|
// s0 = (xMin - tx) / -dy
|
|
// s1 = (xMax - tx) / -dy
|
|
//
|
|
// Each filled polygon is bounded by two of these line segments
|
|
// perpdendicular to the t axis.
|
|
//
|
|
// The t axis is bisected into smaller regions until the color
|
|
// difference across a region is small enough, and then the region
|
|
// is painted with a single color.
|
|
|
|
// compute the coordinates of the point on the t axis at t = tMin;
|
|
// then compute the intersection of the perpendicular line with the
|
|
// bounding box
|
|
tx = x0 + tMin * dx;
|
|
ty = y0 + tMin * dy;
|
|
if (dxdyZero) {
|
|
sMin = sMax = 0;
|
|
} else {
|
|
if (horiz) {
|
|
sMin = (yMin - ty) / dx;
|
|
sMax = (yMax - ty) / dx;
|
|
} else {
|
|
sMin = (xMin - tx) / -dy;
|
|
sMax = (xMax - tx) / -dy;
|
|
}
|
|
if (sMin > sMax) {
|
|
tmp = sMin; sMin = sMax; sMax = tmp;
|
|
}
|
|
}
|
|
ux0 = tx - sMin * dy;
|
|
uy0 = ty + sMin * dx;
|
|
vx0 = tx - sMax * dy;
|
|
vy0 = ty + sMax * dx;
|
|
|
|
// fill the extension at t0
|
|
if (shading->getExtend0() && tMinExt < tMin) {
|
|
|
|
// compute the color at t0
|
|
shading->getColor(t0, &colors[0]);
|
|
|
|
// compute the coordinates of the point on the t axis at t =
|
|
// tMinExt; then compute the intersection of the perpendicular
|
|
// line with the bounding box
|
|
tx = x0 + tMinExt * dx;
|
|
ty = y0 + tMinExt * dy;
|
|
if (dxdyZero) {
|
|
sMin = sMax = 0;
|
|
} else {
|
|
if (horiz) {
|
|
sMin = (yMin - ty) / dx;
|
|
sMax = (yMax - ty) / dx;
|
|
} else {
|
|
sMin = (xMin - tx) / -dy;
|
|
sMax = (xMax - tx) / -dy;
|
|
}
|
|
if (sMin > sMax) {
|
|
tmp = sMin; sMin = sMax; sMax = tmp;
|
|
}
|
|
}
|
|
ux1 = tx - sMin * dy;
|
|
uy1 = ty + sMin * dx;
|
|
vx1 = tx - sMax * dy;
|
|
vy1 = ty + sMax * dx;
|
|
|
|
// set the color
|
|
state->setFillColor(&colors[0]);
|
|
out->updateFillColor(state);
|
|
|
|
// fill the region
|
|
state->moveTo(ux1, uy1);
|
|
state->lineTo(vx1, vy1);
|
|
state->lineTo(vx0, vy0);
|
|
state->lineTo(ux0, uy0);
|
|
state->closePath();
|
|
out->fill(state);
|
|
state->clearPath();
|
|
}
|
|
|
|
// traverse the t axis, splitting [tMin, tMax] into axialSplits regions
|
|
|
|
// compute the color in the center of each region
|
|
for (i = 0; i < axialSplits; ++i) {
|
|
t = tMin + (tMax - tMin) * (i + 0.5) / axialSplits;
|
|
tt = t0 + (t1 - t0) * t;
|
|
shading->getColor(tt, &colors[i]);
|
|
}
|
|
|
|
// each iteration draws one or more regions, starting at i --
|
|
// if the colors are similar, it will combine regions i, i+1, ...
|
|
nComps = shading->getColorSpace()->getNComps();
|
|
abortCheckCounter = 0;
|
|
i = 0;
|
|
while (i < axialSplits) {
|
|
|
|
if (abortCheckCbk) {
|
|
++abortCheckCounter;
|
|
if (abortCheckCounter > 100) {
|
|
if ((*abortCheckCbk)(abortCheckCbkData)) {
|
|
break;
|
|
}
|
|
abortCheckCounter = 0;
|
|
}
|
|
}
|
|
|
|
// check for similar colors
|
|
for (j = i + 1; j < axialSplits; ++j) {
|
|
for (k = 0; k < nComps; ++k) {
|
|
if (abs(colors[j].c[k] - colors[i].c[k]) > axialColorDelta) {
|
|
break;
|
|
}
|
|
}
|
|
if (k < nComps) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// compute the coordinates of the point on the t axis; then
|
|
// compute the intersection of the perpendicular line with the
|
|
// bounding box
|
|
t = tMin + (tMax - tMin) * (double)j / axialSplits;
|
|
tx = x0 + t * dx;
|
|
ty = y0 + t * dy;
|
|
if (dxdyZero) {
|
|
sMin = sMax = 0;
|
|
} else {
|
|
if (horiz) {
|
|
sMin = (yMin - ty) / dx;
|
|
sMax = (yMax - ty) / dx;
|
|
} else {
|
|
sMin = (xMin - tx) / -dy;
|
|
sMax = (xMax - tx) / -dy;
|
|
}
|
|
if (sMin > sMax) {
|
|
tmp = sMin; sMin = sMax; sMax = tmp;
|
|
}
|
|
}
|
|
ux1 = tx - sMin * dy;
|
|
uy1 = ty + sMin * dx;
|
|
vx1 = tx - sMax * dy;
|
|
vy1 = ty + sMax * dx;
|
|
|
|
// set the color
|
|
state->setFillColor(&colors[i]);
|
|
out->updateFillColor(state);
|
|
|
|
// fill the region
|
|
state->moveTo(ux0, uy0);
|
|
state->lineTo(vx0, vy0);
|
|
state->lineTo(vx1, vy1);
|
|
state->lineTo(ux1, uy1);
|
|
state->closePath();
|
|
out->fill(state);
|
|
state->clearPath();
|
|
|
|
// set up for next region
|
|
ux0 = ux1;
|
|
uy0 = uy1;
|
|
vx0 = vx1;
|
|
vy0 = vy1;
|
|
|
|
i = j;
|
|
}
|
|
|
|
// fill the extension at t1
|
|
if (shading->getExtend1() && tMaxExt > tMax) {
|
|
|
|
// compute the color at t1
|
|
shading->getColor(t1, &colors[0]);
|
|
|
|
// compute the coordinates of the point on the t axis at t =
|
|
// tMaxExt; then compute the intersection of the perpendicular
|
|
// line with the bounding box
|
|
tx = x0 + tMaxExt * dx;
|
|
ty = y0 + tMaxExt * dy;
|
|
if (dxdyZero) {
|
|
sMin = sMax = 0;
|
|
} else {
|
|
if (horiz) {
|
|
sMin = (yMin - ty) / dx;
|
|
sMax = (yMax - ty) / dx;
|
|
} else {
|
|
sMin = (xMin - tx) / -dy;
|
|
sMax = (xMax - tx) / -dy;
|
|
}
|
|
if (sMin > sMax) {
|
|
tmp = sMin; sMin = sMax; sMax = tmp;
|
|
}
|
|
}
|
|
ux1 = tx - sMin * dy;
|
|
uy1 = ty + sMin * dx;
|
|
vx1 = tx - sMax * dy;
|
|
vy1 = ty + sMax * dx;
|
|
|
|
// set the color
|
|
state->setFillColor(&colors[0]);
|
|
out->updateFillColor(state);
|
|
|
|
// fill the region
|
|
state->moveTo(ux0, uy0);
|
|
state->lineTo(vx0, vy0);
|
|
state->lineTo(vx1, vy1);
|
|
state->lineTo(ux1, uy1);
|
|
state->closePath();
|
|
out->fill(state);
|
|
state->clearPath();
|
|
}
|
|
}
|
|
|
|
#if defined(__GNUC__) && !defined(__clang__)
|
|
// this function makes a lot of sin()/cos() calls, which are slow
|
|
// with glibc 2.16 and newer on x86; accuracy isn't terribly
|
|
// important here, so tell gcc to use the fast version
|
|
#pragma GCC optimize ("fast-math")
|
|
#endif
|
|
|
|
void Gfx::doRadialShFill(GfxRadialShading *shading) {
|
|
double xMin, yMin, xMax, yMax;
|
|
double x0, y0, r0, x1, y1, r1, t0, t1;
|
|
int nComps;
|
|
GfxColor colorA, colorB;
|
|
double xa, ya, xb, yb, ra, rb;
|
|
double ta, tb, sa, sb;
|
|
double sMin, sMax, h;
|
|
double sLeft, sRight, sTop, sBottom, sZero, sDiag;
|
|
GBool haveSLeft, haveSRight, haveSTop, haveSBottom, haveSZero;
|
|
GBool haveSMin, haveSMax;
|
|
GBool enclosed;
|
|
double *ctm;
|
|
double theta, alpha, angle, t;
|
|
int abortCheckCounter, ia, ib, k, n;
|
|
|
|
// get the shading info
|
|
shading->getCoords(&x0, &y0, &r0, &x1, &y1, &r1);
|
|
t0 = shading->getDomain0();
|
|
t1 = shading->getDomain1();
|
|
nComps = shading->getColorSpace()->getNComps();
|
|
|
|
// Compute the point at which r(s) = 0; check for the enclosed
|
|
// circles case; and compute the angles for the tangent lines.
|
|
h = sqrt((x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0));
|
|
if (h == 0) {
|
|
enclosed = gTrue;
|
|
theta = 0; // make gcc happy
|
|
} else if (r1 - r0 == 0) {
|
|
enclosed = gFalse;
|
|
theta = 0;
|
|
} else if (fabs(r1 - r0) >= h - 0.0001) {
|
|
enclosed = gTrue;
|
|
theta = 0; // make gcc happy
|
|
} else {
|
|
enclosed = gFalse;
|
|
theta = asin((r1 - r0) / h);
|
|
}
|
|
if (enclosed) {
|
|
alpha = 0;
|
|
} else {
|
|
alpha = atan2(y1 - y0, x1 - x0);
|
|
}
|
|
|
|
// compute the (possibly extended) s range
|
|
state->getUserClipBBox(&xMin, &yMin, &xMax, &yMax);
|
|
if (enclosed) {
|
|
sMin = 0;
|
|
sMax = 1;
|
|
} else {
|
|
// solve x(sLeft) + r(sLeft) = xMin
|
|
if ((haveSLeft = fabs((x1 + r1) - (x0 + r0)) > 0.000001)) {
|
|
sLeft = (xMin - (x0 + r0)) / ((x1 + r1) - (x0 + r0));
|
|
} else {
|
|
sLeft = 0; // make gcc happy
|
|
}
|
|
// solve x(sRight) - r(sRight) = xMax
|
|
if ((haveSRight = fabs((x1 - r1) - (x0 - r0)) > 0.000001)) {
|
|
sRight = (xMax - (x0 - r0)) / ((x1 - r1) - (x0 - r0));
|
|
} else {
|
|
sRight = 0; // make gcc happy
|
|
}
|
|
// solve y(sBottom) + r(sBottom) = yMin
|
|
if ((haveSBottom = fabs((y1 + r1) - (y0 + r0)) > 0.000001)) {
|
|
sBottom = (yMin - (y0 + r0)) / ((y1 + r1) - (y0 + r0));
|
|
} else {
|
|
sBottom = 0; // make gcc happy
|
|
}
|
|
// solve y(sTop) - r(sTop) = yMax
|
|
if ((haveSTop = fabs((y1 - r1) - (y0 - r0)) > 0.000001)) {
|
|
sTop = (yMax - (y0 - r0)) / ((y1 - r1) - (y0 - r0));
|
|
} else {
|
|
sTop = 0; // make gcc happy
|
|
}
|
|
// solve r(sZero) = 0
|
|
if ((haveSZero = fabs(r1 - r0) > 0.000001)) {
|
|
sZero = -r0 / (r1 - r0);
|
|
} else {
|
|
sZero = 0; // make gcc happy
|
|
}
|
|
// solve r(sDiag) = sqrt((xMax-xMin)^2 + (yMax-yMin)^2)
|
|
if (haveSZero) {
|
|
sDiag = (sqrt((xMax - xMin) * (xMax - xMin) +
|
|
(yMax - yMin) * (yMax - yMin)) - r0) / (r1 - r0);
|
|
} else {
|
|
sDiag = 0; // make gcc happy
|
|
}
|
|
// compute sMin
|
|
if (shading->getExtend0()) {
|
|
sMin = 0;
|
|
haveSMin = gFalse;
|
|
if (x0 < x1 && haveSLeft && sLeft < 0) {
|
|
sMin = sLeft;
|
|
haveSMin = gTrue;
|
|
} else if (x0 > x1 && haveSRight && sRight < 0) {
|
|
sMin = sRight;
|
|
haveSMin = gTrue;
|
|
}
|
|
if (y0 < y1 && haveSBottom && sBottom < 0) {
|
|
if (!haveSMin || sBottom > sMin) {
|
|
sMin = sBottom;
|
|
haveSMin = gTrue;
|
|
}
|
|
} else if (y0 > y1 && haveSTop && sTop < 0) {
|
|
if (!haveSMin || sTop > sMin) {
|
|
sMin = sTop;
|
|
haveSMin = gTrue;
|
|
}
|
|
}
|
|
if (haveSZero && sZero <= 0) {
|
|
if (!haveSMin || sZero > sMin) {
|
|
sMin = sZero;
|
|
}
|
|
}
|
|
} else {
|
|
sMin = 0;
|
|
}
|
|
// compute sMax
|
|
if (shading->getExtend1()) {
|
|
sMax = 1;
|
|
haveSMax = gFalse;
|
|
if (x1 < x0 && haveSLeft && sLeft > 1) {
|
|
sMax = sLeft;
|
|
haveSMax = gTrue;
|
|
} else if (x1 > x0 && haveSRight && sRight > 1) {
|
|
sMax = sRight;
|
|
haveSMax = gTrue;
|
|
}
|
|
if (y1 < y0 && haveSBottom && sBottom > 1) {
|
|
if (!haveSMax || sBottom < sMax) {
|
|
sMax = sBottom;
|
|
haveSMax = gTrue;
|
|
}
|
|
} else if (y1 > y0 && haveSTop && sTop > 1) {
|
|
if (!haveSMax || sTop < sMax) {
|
|
sMax = sTop;
|
|
haveSMax = gTrue;
|
|
}
|
|
}
|
|
if (haveSZero && sDiag > 1) {
|
|
if (!haveSMax || sDiag < sMax) {
|
|
sMax = sDiag;
|
|
}
|
|
}
|
|
} else {
|
|
sMax = 1;
|
|
}
|
|
}
|
|
|
|
// compute the number of steps into which circles must be divided to
|
|
// achieve a curve flatness of 0.1 pixel in device space for the
|
|
// largest circle (note that "device space" is 72 dpi when generating
|
|
// PostScript, hence the relatively small 0.1 pixel accuracy)
|
|
ctm = state->getCTM();
|
|
t = fabs(ctm[0]);
|
|
if (fabs(ctm[1]) > t) {
|
|
t = fabs(ctm[1]);
|
|
}
|
|
if (fabs(ctm[2]) > t) {
|
|
t = fabs(ctm[2]);
|
|
}
|
|
if (fabs(ctm[3]) > t) {
|
|
t = fabs(ctm[3]);
|
|
}
|
|
if (r0 > r1) {
|
|
t *= r0;
|
|
} else {
|
|
t *= r1;
|
|
}
|
|
if (t < 1) {
|
|
n = 3;
|
|
} else {
|
|
n = (int)(M_PI / acos(1 - 0.1 / t));
|
|
if (n < 3) {
|
|
n = 3;
|
|
} else if (n > 200) {
|
|
n = 200;
|
|
}
|
|
}
|
|
|
|
// setup for the start circle
|
|
ia = 0;
|
|
sa = sMin;
|
|
ta = t0 + sa * (t1 - t0);
|
|
xa = x0 + sa * (x1 - x0);
|
|
ya = y0 + sa * (y1 - y0);
|
|
ra = r0 + sa * (r1 - r0);
|
|
if (ta < t0) {
|
|
shading->getColor(t0, &colorA);
|
|
} else if (ta > t1) {
|
|
shading->getColor(t1, &colorA);
|
|
} else {
|
|
shading->getColor(ta, &colorA);
|
|
}
|
|
|
|
// fill the circles
|
|
abortCheckCounter = 0;
|
|
while (ia < radialMaxSplits) {
|
|
|
|
if (abortCheckCbk) {
|
|
++abortCheckCounter;
|
|
if (abortCheckCounter > 100) {
|
|
if ((*abortCheckCbk)(abortCheckCbkData)) {
|
|
break;
|
|
}
|
|
abortCheckCounter = 0;
|
|
}
|
|
}
|
|
|
|
// go as far along the t axis (toward t1) as we can, such that the
|
|
// color difference is within the tolerance (radialColorDelta) --
|
|
// this uses bisection (between the current value, t, and t1),
|
|
// limited to radialMaxSplits points along the t axis; require at
|
|
// least one split to avoid problems when the innermost and
|
|
// outermost colors are the same
|
|
ib = radialMaxSplits;
|
|
sb = sMax;
|
|
tb = t0 + sb * (t1 - t0);
|
|
if (tb < t0) {
|
|
shading->getColor(t0, &colorB);
|
|
} else if (tb > t1) {
|
|
shading->getColor(t1, &colorB);
|
|
} else {
|
|
shading->getColor(tb, &colorB);
|
|
}
|
|
while (ib - ia > 1) {
|
|
for (k = 0; k < nComps; ++k) {
|
|
if (abs(colorB.c[k] - colorA.c[k]) > radialColorDelta) {
|
|
break;
|
|
}
|
|
}
|
|
if (k == nComps && ib < radialMaxSplits) {
|
|
break;
|
|
}
|
|
ib = (ia + ib) / 2;
|
|
sb = sMin + ((double)ib / (double)radialMaxSplits) * (sMax - sMin);
|
|
tb = t0 + sb * (t1 - t0);
|
|
if (tb < t0) {
|
|
shading->getColor(t0, &colorB);
|
|
} else if (tb > t1) {
|
|
shading->getColor(t1, &colorB);
|
|
} else {
|
|
shading->getColor(tb, &colorB);
|
|
}
|
|
}
|
|
|
|
// compute center and radius of the circle
|
|
xb = x0 + sb * (x1 - x0);
|
|
yb = y0 + sb * (y1 - y0);
|
|
rb = r0 + sb * (r1 - r0);
|
|
|
|
// use the average of the colors at the two circles
|
|
for (k = 0; k < nComps; ++k) {
|
|
colorA.c[k] = (colorA.c[k] + colorB.c[k]) / 2;
|
|
}
|
|
state->setFillColor(&colorA);
|
|
out->updateFillColor(state);
|
|
|
|
if (enclosed) {
|
|
|
|
// construct path for first circle (counterclockwise)
|
|
state->moveTo(xa + ra, ya);
|
|
for (k = 1; k < n; ++k) {
|
|
angle = ((double)k / (double)n) * 2 * M_PI;
|
|
state->lineTo(xa + ra * cos(angle), ya + ra * sin(angle));
|
|
}
|
|
state->closePath();
|
|
|
|
// construct and append path for second circle (clockwise)
|
|
state->moveTo(xb + rb, yb);
|
|
for (k = 1; k < n; ++k) {
|
|
angle = -((double)k / (double)n) * 2 * M_PI;
|
|
state->lineTo(xb + rb * cos(angle), yb + rb * sin(angle));
|
|
}
|
|
state->closePath();
|
|
|
|
} else {
|
|
|
|
// construct the first subpath (clockwise)
|
|
state->moveTo(xa + ra * cos(alpha + theta + 0.5 * M_PI),
|
|
ya + ra * sin(alpha + theta + 0.5 * M_PI));
|
|
for (k = 0; k < n; ++k) {
|
|
angle = alpha + theta + 0.5 * M_PI
|
|
- ((double)k / (double)n) * (2 * theta + M_PI);
|
|
state->lineTo(xb + rb * cos(angle), yb + rb * sin(angle));
|
|
}
|
|
for (k = 0; k < n; ++k) {
|
|
angle = alpha - theta - 0.5 * M_PI
|
|
+ ((double)k / (double)n) * (2 * theta - M_PI);
|
|
state->lineTo(xa + ra * cos(angle), ya + ra * sin(angle));
|
|
}
|
|
state->closePath();
|
|
|
|
// construct the second subpath (counterclockwise)
|
|
state->moveTo(xa + ra * cos(alpha + theta + 0.5 * M_PI),
|
|
ya + ra * sin(alpha + theta + 0.5 * M_PI));
|
|
for (k = 0; k < n; ++k) {
|
|
angle = alpha + theta + 0.5 * M_PI
|
|
+ ((double)k / (double)n) * (-2 * theta + M_PI);
|
|
state->lineTo(xb + rb * cos(angle), yb + rb * sin(angle));
|
|
}
|
|
for (k = 0; k < n; ++k) {
|
|
angle = alpha - theta - 0.5 * M_PI
|
|
+ ((double)k / (double)n) * (2 * theta + M_PI);
|
|
state->lineTo(xa + ra * cos(angle), ya + ra * sin(angle));
|
|
}
|
|
state->closePath();
|
|
}
|
|
|
|
// fill the path
|
|
out->fill(state);
|
|
state->clearPath();
|
|
|
|
// step to the next value of t
|
|
ia = ib;
|
|
sa = sb;
|
|
ta = tb;
|
|
xa = xb;
|
|
ya = yb;
|
|
ra = rb;
|
|
colorA = colorB;
|
|
}
|
|
|
|
if (enclosed) {
|
|
// extend the smaller circle
|
|
if ((shading->getExtend0() && r0 <= r1) ||
|
|
(shading->getExtend1() && r1 < r0)) {
|
|
if (r0 <= r1) {
|
|
ta = t0;
|
|
ra = r0;
|
|
xa = x0;
|
|
ya = y0;
|
|
} else {
|
|
ta = t1;
|
|
ra = r1;
|
|
xa = x1;
|
|
ya = y1;
|
|
}
|
|
shading->getColor(ta, &colorA);
|
|
state->setFillColor(&colorA);
|
|
out->updateFillColor(state);
|
|
state->moveTo(xa + ra, ya);
|
|
for (k = 1; k < n; ++k) {
|
|
angle = ((double)k / (double)n) * 2 * M_PI;
|
|
state->lineTo(xa + ra * cos(angle), ya + ra * sin(angle));
|
|
}
|
|
state->closePath();
|
|
out->fill(state);
|
|
state->clearPath();
|
|
}
|
|
|
|
// extend the larger circle
|
|
if ((shading->getExtend0() && r0 > r1) ||
|
|
(shading->getExtend1() && r1 >= r0)) {
|
|
if (r0 > r1) {
|
|
ta = t0;
|
|
ra = r0;
|
|
xa = x0;
|
|
ya = y0;
|
|
} else {
|
|
ta = t1;
|
|
ra = r1;
|
|
xa = x1;
|
|
ya = y1;
|
|
}
|
|
shading->getColor(ta, &colorA);
|
|
state->setFillColor(&colorA);
|
|
out->updateFillColor(state);
|
|
state->moveTo(xMin, yMin);
|
|
state->lineTo(xMin, yMax);
|
|
state->lineTo(xMax, yMax);
|
|
state->lineTo(xMax, yMin);
|
|
state->closePath();
|
|
state->moveTo(xa + ra, ya);
|
|
for (k = 1; k < n; ++k) {
|
|
angle = ((double)k / (double)n) * 2 * M_PI;
|
|
state->lineTo(xa + ra * cos(angle), ya + ra * sin(angle));
|
|
}
|
|
state->closePath();
|
|
out->fill(state);
|
|
state->clearPath();
|
|
}
|
|
}
|
|
}
|
|
|
|
#if defined(__GNUC__) && !defined(__clang__)
|
|
#pragma GCC reset_options
|
|
#endif
|
|
|
|
void Gfx::doGouraudTriangleShFill(GfxGouraudTriangleShading *shading) {
|
|
double x0, y0, x1, y1, x2, y2;
|
|
double color0[gfxColorMaxComps];
|
|
double color1[gfxColorMaxComps];
|
|
double color2[gfxColorMaxComps];
|
|
int abortCheckCounter, i;
|
|
|
|
abortCheckCounter = 0;
|
|
for (i = 0; i < shading->getNTriangles(); ++i) {
|
|
if (abortCheckCbk) {
|
|
++abortCheckCounter;
|
|
if (abortCheckCounter > 25) {
|
|
if ((*abortCheckCbk)(abortCheckCbkData)) {
|
|
break;
|
|
}
|
|
abortCheckCounter = 0;
|
|
}
|
|
}
|
|
shading->getTriangle(i, &x0, &y0, color0,
|
|
&x1, &y1, color1,
|
|
&x2, &y2, color2);
|
|
gouraudFillTriangle(x0, y0, color0, x1, y1, color1, x2, y2, color2,
|
|
shading, 0);
|
|
}
|
|
}
|
|
|
|
void Gfx::gouraudFillTriangle(double x0, double y0, double *color0,
|
|
double x1, double y1, double *color1,
|
|
double x2, double y2, double *color2,
|
|
GfxGouraudTriangleShading *shading, int depth) {
|
|
double dx0, dy0, dx1, dy1, dx2, dy2;
|
|
double x01, y01, x12, y12, x20, y20;
|
|
double color01[gfxColorMaxComps];
|
|
double color12[gfxColorMaxComps];
|
|
double color20[gfxColorMaxComps];
|
|
GfxColor c0, c1, c2;
|
|
int nComps, i;
|
|
|
|
// recursion ends when:
|
|
// (1) color difference is smaller than gouraudColorDelta; or
|
|
// (2) triangles are smaller than 0.5 pixel (note that "device
|
|
// space" is 72dpi when generating PostScript); or
|
|
// (3) max recursion depth (gouraudMaxDepth) is hit.
|
|
nComps = shading->getColorSpace()->getNComps();
|
|
shading->getColor(color0, &c0);
|
|
shading->getColor(color1, &c1);
|
|
shading->getColor(color2, &c2);
|
|
for (i = 0; i < nComps; ++i) {
|
|
if (abs(c0.c[i] - c1.c[i]) > gouraudColorDelta ||
|
|
abs(c1.c[i] - c2.c[i]) > gouraudColorDelta) {
|
|
break;
|
|
}
|
|
}
|
|
state->transformDelta(x1 - x0, y1 - y0, &dx0, &dy0);
|
|
state->transformDelta(x2 - x1, y2 - y1, &dx1, &dy1);
|
|
state->transformDelta(x0 - x2, y0 - y2, &dx2, &dy2);
|
|
if (i == nComps ||
|
|
depth == gouraudMaxDepth ||
|
|
(fabs(dx0) < 0.5 && fabs(dy0) < 0.5 &&
|
|
fabs(dx1) < 0.5 && fabs(dy1) < 0.5 &&
|
|
fabs(dx2) < 0.5 && fabs(dy2) < 0.5)) {
|
|
state->setFillColor(&c0);
|
|
out->updateFillColor(state);
|
|
state->moveTo(x0, y0);
|
|
state->lineTo(x1, y1);
|
|
state->lineTo(x2, y2);
|
|
state->closePath();
|
|
out->fill(state);
|
|
state->clearPath();
|
|
} else {
|
|
x01 = 0.5 * (x0 + x1);
|
|
y01 = 0.5 * (y0 + y1);
|
|
x12 = 0.5 * (x1 + x2);
|
|
y12 = 0.5 * (y1 + y2);
|
|
x20 = 0.5 * (x2 + x0);
|
|
y20 = 0.5 * (y2 + y0);
|
|
for (i = 0; i < shading->getNComps(); ++i) {
|
|
color01[i] = 0.5 * (color0[i] + color1[i]);
|
|
color12[i] = 0.5 * (color1[i] + color2[i]);
|
|
color20[i] = 0.5 * (color2[i] + color0[i]);
|
|
}
|
|
gouraudFillTriangle(x0, y0, color0, x01, y01, color01,
|
|
x20, y20, color20, shading, depth + 1);
|
|
gouraudFillTriangle(x01, y01, color01, x1, y1, color1,
|
|
x12, y12, color12, shading, depth + 1);
|
|
gouraudFillTriangle(x01, y01, color01, x12, y12, color12,
|
|
x20, y20, color20, shading, depth + 1);
|
|
gouraudFillTriangle(x20, y20, color20, x12, y12, color12,
|
|
x2, y2, color2, shading, depth + 1);
|
|
}
|
|
}
|
|
|
|
void Gfx::doPatchMeshShFill(GfxPatchMeshShading *shading) {
|
|
int start, abortCheckCounter, i;
|
|
|
|
if (shading->getNPatches() > 128) {
|
|
start = 3;
|
|
} else if (shading->getNPatches() > 64) {
|
|
start = 2;
|
|
} else if (shading->getNPatches() > 16) {
|
|
start = 1;
|
|
} else {
|
|
start = 0;
|
|
}
|
|
abortCheckCounter = 0;
|
|
for (i = 0; i < shading->getNPatches(); ++i) {
|
|
if (abortCheckCbk) {
|
|
++abortCheckCounter;
|
|
if (abortCheckCounter > 25) {
|
|
if ((*abortCheckCbk)(abortCheckCbkData)) {
|
|
break;
|
|
}
|
|
abortCheckCounter = 0;
|
|
}
|
|
}
|
|
fillPatch(shading->getPatch(i), shading, start);
|
|
}
|
|
}
|
|
|
|
void Gfx::fillPatch(GfxPatch *patch, GfxPatchMeshShading *shading, int depth) {
|
|
GfxPatch patch00, patch01, patch10, patch11;
|
|
GfxColor c00, c01, c10, c11;
|
|
double xx[4][8], yy[4][8];
|
|
double x, y, xMin, yMin, xMax, yMax, xxm, yym;
|
|
int nComps, i, j;
|
|
GBool stop;
|
|
|
|
shading->getColor(patch->color[0][0], &c00);
|
|
stop = gFalse;
|
|
|
|
// stop subdivision at max depth
|
|
if (depth == patchMaxDepth) {
|
|
stop = gTrue;
|
|
}
|
|
|
|
// stop subdivision if colors are close enough
|
|
if (!stop) {
|
|
nComps = shading->getColorSpace()->getNComps();
|
|
shading->getColor(patch->color[0][1], &c01);
|
|
shading->getColor(patch->color[1][0], &c10);
|
|
shading->getColor(patch->color[1][1], &c11);
|
|
for (i = 0; i < nComps; ++i) {
|
|
if (abs(c00.c[i] - c01.c[i]) > patchColorDelta ||
|
|
abs(c01.c[i] - c11.c[i]) > patchColorDelta ||
|
|
abs(c11.c[i] - c10.c[i]) > patchColorDelta ||
|
|
abs(c10.c[i] - c00.c[i]) > patchColorDelta) {
|
|
break;
|
|
}
|
|
}
|
|
if (i == nComps) {
|
|
stop = gTrue;
|
|
}
|
|
}
|
|
|
|
// stop subdivision if patch is small enough
|
|
if (!stop) {
|
|
xMin = yMin = xMax = yMax = 0;
|
|
for (j = 0; j < 4; ++j) {
|
|
for (i = 0; i < 4; ++i) {
|
|
state->transformDelta(patch->x[i][j], patch->y[i][j], &x, &y);
|
|
if (i == 0 && j == 0) {
|
|
xMin = xMax = x;
|
|
yMin = yMax = y;
|
|
} else {
|
|
if (x < xMin) {
|
|
xMin = x;
|
|
} else if (x > xMax) {
|
|
xMax = x;
|
|
}
|
|
if (y < yMin) {
|
|
yMin = y;
|
|
} else if (y > yMax) {
|
|
yMax = y;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (xMax - xMin < 1 && yMax - yMin < 1) {
|
|
stop = gTrue;
|
|
}
|
|
}
|
|
|
|
// draw the patch
|
|
if (stop) {
|
|
state->setFillColor(&c00);
|
|
out->updateFillColor(state);
|
|
state->moveTo(patch->x[0][0], patch->y[0][0]);
|
|
state->curveTo(patch->x[0][1], patch->y[0][1],
|
|
patch->x[0][2], patch->y[0][2],
|
|
patch->x[0][3], patch->y[0][3]);
|
|
state->curveTo(patch->x[1][3], patch->y[1][3],
|
|
patch->x[2][3], patch->y[2][3],
|
|
patch->x[3][3], patch->y[3][3]);
|
|
state->curveTo(patch->x[3][2], patch->y[3][2],
|
|
patch->x[3][1], patch->y[3][1],
|
|
patch->x[3][0], patch->y[3][0]);
|
|
state->curveTo(patch->x[2][0], patch->y[2][0],
|
|
patch->x[1][0], patch->y[1][0],
|
|
patch->x[0][0], patch->y[0][0]);
|
|
state->closePath();
|
|
out->fill(state);
|
|
state->clearPath();
|
|
|
|
// subdivide the patch
|
|
} else {
|
|
for (i = 0; i < 4; ++i) {
|
|
xx[i][0] = patch->x[i][0];
|
|
yy[i][0] = patch->y[i][0];
|
|
xx[i][1] = 0.5 * (patch->x[i][0] + patch->x[i][1]);
|
|
yy[i][1] = 0.5 * (patch->y[i][0] + patch->y[i][1]);
|
|
xxm = 0.5 * (patch->x[i][1] + patch->x[i][2]);
|
|
yym = 0.5 * (patch->y[i][1] + patch->y[i][2]);
|
|
xx[i][6] = 0.5 * (patch->x[i][2] + patch->x[i][3]);
|
|
yy[i][6] = 0.5 * (patch->y[i][2] + patch->y[i][3]);
|
|
xx[i][2] = 0.5 * (xx[i][1] + xxm);
|
|
yy[i][2] = 0.5 * (yy[i][1] + yym);
|
|
xx[i][5] = 0.5 * (xxm + xx[i][6]);
|
|
yy[i][5] = 0.5 * (yym + yy[i][6]);
|
|
xx[i][3] = xx[i][4] = 0.5 * (xx[i][2] + xx[i][5]);
|
|
yy[i][3] = yy[i][4] = 0.5 * (yy[i][2] + yy[i][5]);
|
|
xx[i][7] = patch->x[i][3];
|
|
yy[i][7] = patch->y[i][3];
|
|
}
|
|
for (i = 0; i < 4; ++i) {
|
|
patch00.x[0][i] = xx[0][i];
|
|
patch00.y[0][i] = yy[0][i];
|
|
patch00.x[1][i] = 0.5 * (xx[0][i] + xx[1][i]);
|
|
patch00.y[1][i] = 0.5 * (yy[0][i] + yy[1][i]);
|
|
xxm = 0.5 * (xx[1][i] + xx[2][i]);
|
|
yym = 0.5 * (yy[1][i] + yy[2][i]);
|
|
patch10.x[2][i] = 0.5 * (xx[2][i] + xx[3][i]);
|
|
patch10.y[2][i] = 0.5 * (yy[2][i] + yy[3][i]);
|
|
patch00.x[2][i] = 0.5 * (patch00.x[1][i] + xxm);
|
|
patch00.y[2][i] = 0.5 * (patch00.y[1][i] + yym);
|
|
patch10.x[1][i] = 0.5 * (xxm + patch10.x[2][i]);
|
|
patch10.y[1][i] = 0.5 * (yym + patch10.y[2][i]);
|
|
patch00.x[3][i] = 0.5 * (patch00.x[2][i] + patch10.x[1][i]);
|
|
patch00.y[3][i] = 0.5 * (patch00.y[2][i] + patch10.y[1][i]);
|
|
patch10.x[0][i] = patch00.x[3][i];
|
|
patch10.y[0][i] = patch00.y[3][i];
|
|
patch10.x[3][i] = xx[3][i];
|
|
patch10.y[3][i] = yy[3][i];
|
|
}
|
|
for (i = 4; i < 8; ++i) {
|
|
patch01.x[0][i-4] = xx[0][i];
|
|
patch01.y[0][i-4] = yy[0][i];
|
|
patch01.x[1][i-4] = 0.5 * (xx[0][i] + xx[1][i]);
|
|
patch01.y[1][i-4] = 0.5 * (yy[0][i] + yy[1][i]);
|
|
xxm = 0.5 * (xx[1][i] + xx[2][i]);
|
|
yym = 0.5 * (yy[1][i] + yy[2][i]);
|
|
patch11.x[2][i-4] = 0.5 * (xx[2][i] + xx[3][i]);
|
|
patch11.y[2][i-4] = 0.5 * (yy[2][i] + yy[3][i]);
|
|
patch01.x[2][i-4] = 0.5 * (patch01.x[1][i-4] + xxm);
|
|
patch01.y[2][i-4] = 0.5 * (patch01.y[1][i-4] + yym);
|
|
patch11.x[1][i-4] = 0.5 * (xxm + patch11.x[2][i-4]);
|
|
patch11.y[1][i-4] = 0.5 * (yym + patch11.y[2][i-4]);
|
|
patch01.x[3][i-4] = 0.5 * (patch01.x[2][i-4] + patch11.x[1][i-4]);
|
|
patch01.y[3][i-4] = 0.5 * (patch01.y[2][i-4] + patch11.y[1][i-4]);
|
|
patch11.x[0][i-4] = patch01.x[3][i-4];
|
|
patch11.y[0][i-4] = patch01.y[3][i-4];
|
|
patch11.x[3][i-4] = xx[3][i];
|
|
patch11.y[3][i-4] = yy[3][i];
|
|
}
|
|
for (i = 0; i < shading->getNComps(); ++i) {
|
|
patch00.color[0][0][i] = patch->color[0][0][i];
|
|
patch00.color[0][1][i] = 0.5 * (patch->color[0][0][i] +
|
|
patch->color[0][1][i]);
|
|
patch01.color[0][0][i] = patch00.color[0][1][i];
|
|
patch01.color[0][1][i] = patch->color[0][1][i];
|
|
patch01.color[1][1][i] = 0.5 * (patch->color[0][1][i] +
|
|
patch->color[1][1][i]);
|
|
patch11.color[0][1][i] = patch01.color[1][1][i];
|
|
patch11.color[1][1][i] = patch->color[1][1][i];
|
|
patch11.color[1][0][i] = 0.5 * (patch->color[1][1][i] +
|
|
patch->color[1][0][i]);
|
|
patch10.color[1][1][i] = patch11.color[1][0][i];
|
|
patch10.color[1][0][i] = patch->color[1][0][i];
|
|
patch10.color[0][0][i] = 0.5 * (patch->color[1][0][i] +
|
|
patch->color[0][0][i]);
|
|
patch00.color[1][0][i] = patch10.color[0][0][i];
|
|
patch00.color[1][1][i] = 0.5 * (patch00.color[1][0][i] +
|
|
patch01.color[1][1][i]);
|
|
patch01.color[1][0][i] = patch00.color[1][1][i];
|
|
patch11.color[0][0][i] = patch00.color[1][1][i];
|
|
patch10.color[0][1][i] = patch00.color[1][1][i];
|
|
}
|
|
fillPatch(&patch00, shading, depth + 1);
|
|
fillPatch(&patch10, shading, depth + 1);
|
|
fillPatch(&patch01, shading, depth + 1);
|
|
fillPatch(&patch11, shading, depth + 1);
|
|
}
|
|
}
|
|
|
|
void Gfx::doEndPath() {
|
|
if (state->isCurPt() && clip != clipNone) {
|
|
state->clip();
|
|
if (clip == clipNormal) {
|
|
out->clip(state);
|
|
} else {
|
|
out->eoClip(state);
|
|
}
|
|
}
|
|
clip = clipNone;
|
|
state->clearPath();
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
// path clipping operators
|
|
//------------------------------------------------------------------------
|
|
|
|
void Gfx::opClip(Object args[], int numArgs) {
|
|
clip = clipNormal;
|
|
}
|
|
|
|
void Gfx::opEOClip(Object args[], int numArgs) {
|
|
clip = clipEO;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
// text object operators
|
|
//------------------------------------------------------------------------
|
|
|
|
void Gfx::opBeginText(Object args[], int numArgs) {
|
|
state->setTextMat(1, 0, 0, 1, 0, 0);
|
|
state->textMoveTo(0, 0);
|
|
out->updateTextMat(state);
|
|
out->updateTextPos(state);
|
|
fontChanged = gTrue;
|
|
}
|
|
|
|
void Gfx::opEndText(Object args[], int numArgs) {
|
|
out->endTextObject(state);
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
// text state operators
|
|
//------------------------------------------------------------------------
|
|
|
|
void Gfx::opSetCharSpacing(Object args[], int numArgs) {
|
|
state->setCharSpace(args[0].getNum());
|
|
out->updateCharSpace(state);
|
|
}
|
|
|
|
void Gfx::opSetFont(Object args[], int numArgs) {
|
|
doSetFont(res->lookupFont(args[0].getName()), args[1].getNum());
|
|
}
|
|
|
|
void Gfx::doSetFont(GfxFont *font, double size) {
|
|
if (!font) {
|
|
state->setFont(NULL, 0);
|
|
return;
|
|
}
|
|
if (printCommands) {
|
|
printf(" font: tag=%s name='%s' %g\n",
|
|
font->getTag()->getCString(),
|
|
font->getName() ? font->getName()->getCString() : "???",
|
|
size);
|
|
fflush(stdout);
|
|
}
|
|
state->setFont(font, size);
|
|
fontChanged = gTrue;
|
|
}
|
|
|
|
void Gfx::opSetTextLeading(Object args[], int numArgs) {
|
|
state->setLeading(args[0].getNum());
|
|
}
|
|
|
|
void Gfx::opSetTextRender(Object args[], int numArgs) {
|
|
state->setRender(args[0].getInt());
|
|
out->updateRender(state);
|
|
}
|
|
|
|
void Gfx::opSetTextRise(Object args[], int numArgs) {
|
|
state->setRise(args[0].getNum());
|
|
out->updateRise(state);
|
|
}
|
|
|
|
void Gfx::opSetWordSpacing(Object args[], int numArgs) {
|
|
state->setWordSpace(args[0].getNum());
|
|
out->updateWordSpace(state);
|
|
}
|
|
|
|
void Gfx::opSetHorizScaling(Object args[], int numArgs) {
|
|
state->setHorizScaling(args[0].getNum());
|
|
out->updateHorizScaling(state);
|
|
fontChanged = gTrue;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
// text positioning operators
|
|
//------------------------------------------------------------------------
|
|
|
|
void Gfx::opTextMove(Object args[], int numArgs) {
|
|
double tx, ty;
|
|
|
|
tx = state->getLineX() + args[0].getNum();
|
|
ty = state->getLineY() + args[1].getNum();
|
|
state->textMoveTo(tx, ty);
|
|
out->updateTextPos(state);
|
|
}
|
|
|
|
void Gfx::opTextMoveSet(Object args[], int numArgs) {
|
|
double tx, ty;
|
|
|
|
tx = state->getLineX() + args[0].getNum();
|
|
ty = args[1].getNum();
|
|
state->setLeading(-ty);
|
|
ty += state->getLineY();
|
|
state->textMoveTo(tx, ty);
|
|
out->updateTextPos(state);
|
|
}
|
|
|
|
void Gfx::opSetTextMatrix(Object args[], int numArgs) {
|
|
state->setTextMat(args[0].getNum(), args[1].getNum(),
|
|
args[2].getNum(), args[3].getNum(),
|
|
args[4].getNum(), args[5].getNum());
|
|
state->textMoveTo(0, 0);
|
|
out->updateTextMat(state);
|
|
out->updateTextPos(state);
|
|
fontChanged = gTrue;
|
|
}
|
|
|
|
void Gfx::opTextNextLine(Object args[], int numArgs) {
|
|
double tx, ty;
|
|
|
|
tx = state->getLineX();
|
|
ty = state->getLineY() - state->getLeading();
|
|
state->textMoveTo(tx, ty);
|
|
out->updateTextPos(state);
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
// text string operators
|
|
//------------------------------------------------------------------------
|
|
|
|
void Gfx::opShowText(Object args[], int numArgs) {
|
|
if (!state->getFont()) {
|
|
error(errSyntaxError, getPos(), "No font in show");
|
|
return;
|
|
}
|
|
if (fontChanged) {
|
|
out->updateFont(state);
|
|
fontChanged = gFalse;
|
|
}
|
|
if (ocState) {
|
|
out->beginStringOp(state);
|
|
doShowText(args[0].getString());
|
|
out->endStringOp(state);
|
|
} else {
|
|
doIncCharCount(args[0].getString());
|
|
}
|
|
}
|
|
|
|
void Gfx::opMoveShowText(Object args[], int numArgs) {
|
|
double tx, ty;
|
|
|
|
if (!state->getFont()) {
|
|
error(errSyntaxError, getPos(), "No font in move/show");
|
|
return;
|
|
}
|
|
if (fontChanged) {
|
|
out->updateFont(state);
|
|
fontChanged = gFalse;
|
|
}
|
|
tx = state->getLineX();
|
|
ty = state->getLineY() - state->getLeading();
|
|
state->textMoveTo(tx, ty);
|
|
out->updateTextPos(state);
|
|
if (ocState) {
|
|
out->beginStringOp(state);
|
|
doShowText(args[0].getString());
|
|
out->endStringOp(state);
|
|
} else {
|
|
doIncCharCount(args[0].getString());
|
|
}
|
|
}
|
|
|
|
void Gfx::opMoveSetShowText(Object args[], int numArgs) {
|
|
double tx, ty;
|
|
|
|
if (!state->getFont()) {
|
|
error(errSyntaxError, getPos(), "No font in move/set/show");
|
|
return;
|
|
}
|
|
if (fontChanged) {
|
|
out->updateFont(state);
|
|
fontChanged = gFalse;
|
|
}
|
|
state->setWordSpace(args[0].getNum());
|
|
state->setCharSpace(args[1].getNum());
|
|
tx = state->getLineX();
|
|
ty = state->getLineY() - state->getLeading();
|
|
state->textMoveTo(tx, ty);
|
|
out->updateWordSpace(state);
|
|
out->updateCharSpace(state);
|
|
out->updateTextPos(state);
|
|
if (ocState) {
|
|
out->beginStringOp(state);
|
|
doShowText(args[2].getString());
|
|
out->endStringOp(state);
|
|
} else {
|
|
doIncCharCount(args[2].getString());
|
|
}
|
|
}
|
|
|
|
void Gfx::opShowSpaceText(Object args[], int numArgs) {
|
|
Array *a;
|
|
Object obj;
|
|
int wMode;
|
|
int i;
|
|
|
|
if (!state->getFont()) {
|
|
error(errSyntaxError, getPos(), "No font in show/space");
|
|
return;
|
|
}
|
|
if (fontChanged) {
|
|
out->updateFont(state);
|
|
fontChanged = gFalse;
|
|
}
|
|
if (ocState) {
|
|
out->beginStringOp(state);
|
|
wMode = state->getFont()->getWMode();
|
|
a = args[0].getArray();
|
|
for (i = 0; i < a->getLength(); ++i) {
|
|
a->get(i, &obj);
|
|
if (obj.isNum()) {
|
|
if (wMode) {
|
|
state->textShift(0, -obj.getNum() * 0.001 *
|
|
state->getFontSize());
|
|
} else {
|
|
state->textShift(-obj.getNum() * 0.001 *
|
|
state->getFontSize() *
|
|
state->getHorizScaling(), 0);
|
|
}
|
|
out->updateTextShift(state, obj.getNum());
|
|
} else if (obj.isString()) {
|
|
doShowText(obj.getString());
|
|
} else {
|
|
error(errSyntaxError, getPos(),
|
|
"Element of show/space array must be number or string");
|
|
}
|
|
obj.free();
|
|
}
|
|
out->endStringOp(state);
|
|
} else {
|
|
a = args[0].getArray();
|
|
for (i = 0; i < a->getLength(); ++i) {
|
|
a->get(i, &obj);
|
|
if (obj.isString()) {
|
|
doIncCharCount(obj.getString());
|
|
}
|
|
obj.free();
|
|
}
|
|
}
|
|
}
|
|
|
|
void Gfx::doShowText(GString *s) {
|
|
GfxFont *font;
|
|
int wMode;
|
|
double riseX, riseY;
|
|
CharCode code;
|
|
Unicode u[8];
|
|
double x, y, dx, dy, dx2, dy2, curX, curY, tdx, tdy, ddx, ddy;
|
|
double originX, originY, tOriginX, tOriginY;
|
|
double x0, y0, x1, y1;
|
|
double oldCTM[6], newCTM[6];
|
|
double *mat;
|
|
Object charProcRef, charProc;
|
|
Dict *resDict;
|
|
Parser *oldParser;
|
|
GfxState *savedState;
|
|
char *p;
|
|
int render;
|
|
GBool patternFill;
|
|
int len, n, uLen, nChars, nSpaces, i;
|
|
|
|
font = state->getFont();
|
|
wMode = font->getWMode();
|
|
|
|
if (globalParams->isDroppedFont(font->getName()
|
|
? font->getName()->getCString() : "")) {
|
|
doIncCharCount(s);
|
|
return;
|
|
}
|
|
|
|
if (out->useDrawChar()) {
|
|
out->beginString(state, s);
|
|
}
|
|
|
|
// if we're doing a pattern fill, set up clipping
|
|
render = state->getRender();
|
|
if (!(render & 1) &&
|
|
state->getFillColorSpace()->getMode() == csPattern) {
|
|
patternFill = gTrue;
|
|
saveState();
|
|
// disable fill, enable clipping, leave stroke unchanged
|
|
if ((render ^ (render >> 1)) & 1) {
|
|
render = 5;
|
|
} else {
|
|
render = 7;
|
|
}
|
|
state->setRender(render);
|
|
out->updateRender(state);
|
|
} else {
|
|
patternFill = gFalse;
|
|
}
|
|
|
|
state->textTransformDelta(0, state->getRise(), &riseX, &riseY);
|
|
x0 = state->getCurX() + riseX;
|
|
y0 = state->getCurY() + riseY;
|
|
|
|
// handle a Type 3 char
|
|
if (font->getType() == fontType3 && out->interpretType3Chars()) {
|
|
mat = state->getCTM();
|
|
for (i = 0; i < 6; ++i) {
|
|
oldCTM[i] = mat[i];
|
|
}
|
|
mat = state->getTextMat();
|
|
newCTM[0] = mat[0] * oldCTM[0] + mat[1] * oldCTM[2];
|
|
newCTM[1] = mat[0] * oldCTM[1] + mat[1] * oldCTM[3];
|
|
newCTM[2] = mat[2] * oldCTM[0] + mat[3] * oldCTM[2];
|
|
newCTM[3] = mat[2] * oldCTM[1] + mat[3] * oldCTM[3];
|
|
mat = font->getFontMatrix();
|
|
newCTM[0] = mat[0] * newCTM[0] + mat[1] * newCTM[2];
|
|
newCTM[1] = mat[0] * newCTM[1] + mat[1] * newCTM[3];
|
|
newCTM[2] = mat[2] * newCTM[0] + mat[3] * newCTM[2];
|
|
newCTM[3] = mat[2] * newCTM[1] + mat[3] * newCTM[3];
|
|
newCTM[0] *= state->getFontSize();
|
|
newCTM[1] *= state->getFontSize();
|
|
newCTM[2] *= state->getFontSize();
|
|
newCTM[3] *= state->getFontSize();
|
|
newCTM[0] *= state->getHorizScaling();
|
|
newCTM[2] *= state->getHorizScaling();
|
|
curX = state->getCurX();
|
|
curY = state->getCurY();
|
|
oldParser = parser;
|
|
p = s->getCString();
|
|
len = s->getLength();
|
|
while (len > 0) {
|
|
n = font->getNextChar(p, len, &code,
|
|
u, (int)(sizeof(u) / sizeof(Unicode)), &uLen,
|
|
&dx, &dy, &originX, &originY);
|
|
dx = dx * state->getFontSize() + state->getCharSpace();
|
|
if (n == 1 && *p == ' ') {
|
|
dx += state->getWordSpace();
|
|
}
|
|
dx *= state->getHorizScaling();
|
|
dy *= state->getFontSize();
|
|
state->textTransformDelta(dx, dy, &tdx, &tdy);
|
|
state->transform(curX + riseX, curY + riseY, &x, &y);
|
|
savedState = saveStateStack();
|
|
state->setCTM(newCTM[0], newCTM[1], newCTM[2], newCTM[3], x, y);
|
|
//~ the CTM concat values here are wrong (but never used)
|
|
out->updateCTM(state, 1, 0, 0, 1, 0, 0);
|
|
state->transformDelta(dx, dy, &ddx, &ddy);
|
|
#if 0
|
|
// The PDF spec says: "the graphics state shall be inherited
|
|
// from the environment of the text-showing operator that caused
|
|
// the [Type 3] glyph description to be invoked". However,
|
|
// Acrobat apparently copies the fill color to the stroke color.
|
|
// It looks like Ghostscript does the same. (This is only
|
|
// relevant for uncached Type 3 glyphs.) Uncomment this block
|
|
// to make Xpdf mimic Adobe (in violation of the PDF spec).
|
|
state->setStrokeColorSpace(state->getFillColorSpace()->copy());
|
|
out->updateStrokeColorSpace(state);
|
|
state->setStrokeColor(state->getFillColor());
|
|
out->updateStrokeColor(state);
|
|
#endif
|
|
if (!out->beginType3Char(state, curX + riseX, curY + riseY, ddx, ddy,
|
|
code, u, uLen)) {
|
|
((Gfx8BitFont *)font)->getCharProcNF(code, &charProcRef);
|
|
charProcRef.fetch(xref, &charProc);
|
|
if ((resDict = ((Gfx8BitFont *)font)->getResources())) {
|
|
pushResources(resDict);
|
|
}
|
|
if (charProc.isStream()) {
|
|
display(&charProcRef, gFalse);
|
|
} else {
|
|
error(errSyntaxError, getPos(),
|
|
"Missing or bad Type3 CharProc entry");
|
|
}
|
|
out->endType3Char(state);
|
|
if (resDict) {
|
|
popResources();
|
|
}
|
|
charProc.free();
|
|
charProcRef.free();
|
|
}
|
|
restoreStateStack(savedState);
|
|
curX += tdx;
|
|
curY += tdy;
|
|
state->moveTo(curX, curY);
|
|
p += n;
|
|
len -= n;
|
|
}
|
|
parser = oldParser;
|
|
|
|
} else if (out->useDrawChar()) {
|
|
p = s->getCString();
|
|
len = s->getLength();
|
|
while (len > 0) {
|
|
n = font->getNextChar(p, len, &code,
|
|
u, (int)(sizeof(u) / sizeof(Unicode)), &uLen,
|
|
&dx, &dy, &originX, &originY);
|
|
if (wMode) {
|
|
dx *= state->getFontSize();
|
|
dy = dy * state->getFontSize() + state->getCharSpace();
|
|
if (n == 1 && *p == ' ') {
|
|
dy += state->getWordSpace();
|
|
}
|
|
} else {
|
|
dx = dx * state->getFontSize() + state->getCharSpace();
|
|
if (n == 1 && *p == ' ') {
|
|
dx += state->getWordSpace();
|
|
}
|
|
dx *= state->getHorizScaling();
|
|
dy *= state->getFontSize();
|
|
}
|
|
state->textTransformDelta(dx, dy, &tdx, &tdy);
|
|
originX *= state->getFontSize();
|
|
originY *= state->getFontSize();
|
|
state->textTransformDelta(originX, originY, &tOriginX, &tOriginY);
|
|
out->drawChar(state, state->getCurX() + riseX, state->getCurY() + riseY,
|
|
tdx, tdy, tOriginX, tOriginY, code, n, u, uLen);
|
|
state->shift(tdx, tdy);
|
|
p += n;
|
|
len -= n;
|
|
}
|
|
|
|
} else {
|
|
dx = dy = 0;
|
|
p = s->getCString();
|
|
len = s->getLength();
|
|
nChars = nSpaces = 0;
|
|
while (len > 0) {
|
|
n = font->getNextChar(p, len, &code,
|
|
u, (int)(sizeof(u) / sizeof(Unicode)), &uLen,
|
|
&dx2, &dy2, &originX, &originY);
|
|
dx += dx2;
|
|
dy += dy2;
|
|
if (n == 1 && *p == ' ') {
|
|
++nSpaces;
|
|
}
|
|
++nChars;
|
|
p += n;
|
|
len -= n;
|
|
}
|
|
if (wMode) {
|
|
dx *= state->getFontSize();
|
|
dy = dy * state->getFontSize()
|
|
+ nChars * state->getCharSpace()
|
|
+ nSpaces * state->getWordSpace();
|
|
} else {
|
|
dx = dx * state->getFontSize()
|
|
+ nChars * state->getCharSpace()
|
|
+ nSpaces * state->getWordSpace();
|
|
dx *= state->getHorizScaling();
|
|
dy *= state->getFontSize();
|
|
}
|
|
state->textTransformDelta(dx, dy, &tdx, &tdy);
|
|
out->drawString(state, s);
|
|
state->shift(tdx, tdy);
|
|
}
|
|
|
|
if (out->useDrawChar()) {
|
|
out->endString(state);
|
|
}
|
|
|
|
if (patternFill) {
|
|
out->saveTextPos(state);
|
|
// tell the OutputDev to do the clipping
|
|
out->endTextObject(state);
|
|
// set up a clipping bbox so doPatternText will work -- assume
|
|
// that the text bounding box does not extend past the baseline in
|
|
// any direction by more than twice the font size
|
|
x1 = state->getCurX() + riseX;
|
|
y1 = state->getCurY() + riseY;
|
|
if (x0 > x1) {
|
|
x = x0; x0 = x1; x1 = x;
|
|
}
|
|
if (y0 > y1) {
|
|
y = y0; y0 = y1; y1 = y;
|
|
}
|
|
state->textTransformDelta(0, state->getFontSize(), &dx, &dy);
|
|
state->textTransformDelta(state->getFontSize(), 0, &dx2, &dy2);
|
|
dx = fabs(dx);
|
|
dx2 = fabs(dx2);
|
|
if (dx2 > dx) {
|
|
dx = dx2;
|
|
}
|
|
dy = fabs(dy);
|
|
dy2 = fabs(dy2);
|
|
if (dy2 > dy) {
|
|
dy = dy2;
|
|
}
|
|
state->clipToRect(x0 - 2 * dx, y0 - 2 * dy, x1 + 2 * dx, y1 + 2 * dy);
|
|
// set render mode to fill-only
|
|
state->setRender(0);
|
|
out->updateRender(state);
|
|
doPatternText();
|
|
restoreState();
|
|
out->restoreTextPos(state);
|
|
}
|
|
|
|
opCounter += 10 * s->getLength();
|
|
}
|
|
|
|
// NB: this is only called when ocState is false.
|
|
void Gfx::doIncCharCount(GString *s) {
|
|
if (out->needCharCount()) {
|
|
out->incCharCount(s->getLength());
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
// XObject operators
|
|
//------------------------------------------------------------------------
|
|
|
|
void Gfx::opXObject(Object args[], int numArgs) {
|
|
char *name;
|
|
Object obj1, obj2, obj3, refObj;
|
|
GBool ocSaved, oc;
|
|
#if OPI_SUPPORT
|
|
Object opiDict;
|
|
#endif
|
|
|
|
if (!ocState && !out->needCharCount()) {
|
|
return;
|
|
}
|
|
name = args[0].getName();
|
|
if (!res->lookupXObject(name, &obj1)) {
|
|
return;
|
|
}
|
|
if (!obj1.isStream()) {
|
|
error(errSyntaxError, getPos(), "XObject '{0:s}' is wrong type", name);
|
|
obj1.free();
|
|
return;
|
|
}
|
|
|
|
// check for optional content key
|
|
ocSaved = ocState;
|
|
obj1.streamGetDict()->lookupNF("OC", &obj2);
|
|
if (doc->getOptionalContent()->evalOCObject(&obj2, &oc)) {
|
|
ocState &= oc;
|
|
}
|
|
obj2.free();
|
|
|
|
#if USE_EXCEPTIONS
|
|
try {
|
|
#endif
|
|
#if OPI_SUPPORT
|
|
obj1.streamGetDict()->lookup("OPI", &opiDict);
|
|
if (opiDict.isDict()) {
|
|
out->opiBegin(state, opiDict.getDict());
|
|
}
|
|
#endif
|
|
obj1.streamGetDict()->lookup("Subtype", &obj2);
|
|
if (obj2.isName("Image")) {
|
|
if (out->needNonText()) {
|
|
res->lookupXObjectNF(name, &refObj);
|
|
if (out->useNameOp() && refObj.isRef())
|
|
out->drawImage(state, refObj.getRef(), name);
|
|
else
|
|
doImage(&refObj, obj1.getStream(), gFalse);
|
|
refObj.free();
|
|
}
|
|
} else if (obj2.isName("Form")) {
|
|
res->lookupXObjectNF(name, &refObj);
|
|
if (out->useDrawForm() && refObj.isRef()) {
|
|
if (ocState) {
|
|
out->drawForm(state, refObj.getRef(), name);
|
|
}
|
|
} else {
|
|
doForm(&refObj, &obj1);
|
|
}
|
|
refObj.free();
|
|
} else if (obj2.isName("PS")) {
|
|
if (ocState) {
|
|
obj1.streamGetDict()->lookup("Level1", &obj3);
|
|
out->psXObject(obj1.getStream(),
|
|
obj3.isStream() ? obj3.getStream() : (Stream *)NULL);
|
|
}
|
|
} else if (obj2.isName()) {
|
|
error(errSyntaxError, getPos(),
|
|
"Unknown XObject subtype '{0:s}'", obj2.getName());
|
|
} else {
|
|
error(errSyntaxError, getPos(),
|
|
"XObject subtype is missing or wrong type");
|
|
}
|
|
obj2.free();
|
|
#if OPI_SUPPORT
|
|
if (opiDict.isDict()) {
|
|
out->opiEnd(state, opiDict.getDict());
|
|
}
|
|
opiDict.free();
|
|
#endif
|
|
#if USE_EXCEPTIONS
|
|
} catch (GMemException e) {
|
|
obj1.free();
|
|
throw;
|
|
}
|
|
#endif
|
|
obj1.free();
|
|
|
|
ocState = ocSaved;
|
|
}
|
|
|
|
GBool Gfx::doImage(Object *ref, Stream *str, GBool inlineImg) {
|
|
Dict *dict, *maskDict;
|
|
int width, height;
|
|
int bits, maskBits;
|
|
StreamColorSpaceMode csMode;
|
|
GBool mask, invert;
|
|
GfxColorSpace *colorSpace, *maskColorSpace;
|
|
GfxImageColorMap *colorMap, *maskColorMap;
|
|
Object maskObj, smaskObj, maskRef;
|
|
GBool haveColorKeyMask, haveExplicitMask, haveSoftMask, haveMatte;
|
|
int maskColors[2*gfxColorMaxComps];
|
|
int maskWidth, maskHeight;
|
|
GBool maskInvert;
|
|
Stream *maskStr;
|
|
double matte[gfxColorMaxComps];
|
|
GBool interpolate;
|
|
GfxRenderingIntent riSaved;
|
|
Object obj1, obj2;
|
|
int i, n;
|
|
|
|
// check for optional content
|
|
if (!ocState && !inlineImg) {
|
|
return gTrue;
|
|
}
|
|
|
|
// get info from the stream
|
|
bits = 0;
|
|
csMode = streamCSNone;
|
|
str->getImageParams(&bits, &csMode);
|
|
|
|
// get stream dict
|
|
dict = str->getDict();
|
|
|
|
// save the current rendering intent
|
|
riSaved = state->getRenderingIntent();
|
|
|
|
// get size
|
|
dict->lookup("Width", &obj1);
|
|
if (obj1.isNull()) {
|
|
obj1.free();
|
|
dict->lookup("W", &obj1);
|
|
}
|
|
if (!obj1.isNum()) {
|
|
goto err2;
|
|
}
|
|
width = obj1.getNum();
|
|
obj1.free();
|
|
if (width <= 0) {
|
|
goto err1;
|
|
}
|
|
dict->lookup("Height", &obj1);
|
|
if (obj1.isNull()) {
|
|
obj1.free();
|
|
dict->lookup("H", &obj1);
|
|
}
|
|
if (!obj1.isNum()) {
|
|
goto err2;
|
|
}
|
|
height = obj1.getNum();
|
|
obj1.free();
|
|
if (height <= 0) {
|
|
goto err1;
|
|
}
|
|
|
|
// image or mask?
|
|
dict->lookup("ImageMask", &obj1);
|
|
if (obj1.isNull()) {
|
|
obj1.free();
|
|
dict->lookup("IM", &obj1);
|
|
}
|
|
mask = gFalse;
|
|
if (obj1.isBool())
|
|
mask = obj1.getBool();
|
|
else if (!obj1.isNull())
|
|
goto err2;
|
|
obj1.free();
|
|
|
|
// bit depth
|
|
if (bits == 0) {
|
|
dict->lookup("BitsPerComponent", &obj1);
|
|
if (obj1.isNull()) {
|
|
obj1.free();
|
|
dict->lookup("BPC", &obj1);
|
|
}
|
|
if (obj1.isInt()) {
|
|
bits = obj1.getInt();
|
|
if (bits < 1 || bits > 16) {
|
|
goto err2;
|
|
}
|
|
} else if (mask) {
|
|
bits = 1;
|
|
} else {
|
|
goto err2;
|
|
}
|
|
obj1.free();
|
|
}
|
|
|
|
// interpolate flag
|
|
dict->lookup("Interpolate", &obj1);
|
|
if (obj1.isNull()) {
|
|
obj1.free();
|
|
dict->lookup("I", &obj1);
|
|
}
|
|
interpolate = obj1.isBool() && obj1.getBool();
|
|
obj1.free();
|
|
|
|
// display a mask
|
|
if (mask) {
|
|
|
|
// check for inverted mask
|
|
if (bits != 1)
|
|
goto err1;
|
|
invert = gFalse;
|
|
dict->lookup("Decode", &obj1);
|
|
if (obj1.isNull()) {
|
|
obj1.free();
|
|
dict->lookup("D", &obj1);
|
|
}
|
|
if (obj1.isArray()) {
|
|
obj1.arrayGet(0, &obj2);
|
|
invert = obj2.isNum() && obj2.getNum() == 1;
|
|
obj2.free();
|
|
} else if (!obj1.isNull()) {
|
|
goto err2;
|
|
}
|
|
obj1.free();
|
|
|
|
// if drawing is disabled, skip over inline image data
|
|
if (!ocState) {
|
|
str->reset();
|
|
n = height * ((width + 7) / 8);
|
|
for (i = 0; i < n; ++i) {
|
|
str->getChar();
|
|
}
|
|
str->close();
|
|
|
|
// draw it
|
|
} else {
|
|
if (state->getFillColorSpace()->getMode() == csPattern) {
|
|
doPatternImageMask(ref, str, width, height, invert, inlineImg,
|
|
interpolate);
|
|
} else {
|
|
out->drawImageMask(state, ref, str, width, height, invert, inlineImg,
|
|
interpolate);
|
|
}
|
|
}
|
|
|
|
} else {
|
|
|
|
// rendering intent
|
|
if (dict->lookup("Intent", &obj1)->isName()) {
|
|
opSetRenderingIntent(&obj1, 1);
|
|
}
|
|
obj1.free();
|
|
|
|
// get color space and color map
|
|
dict->lookup("ColorSpace", &obj1);
|
|
if (obj1.isNull()) {
|
|
obj1.free();
|
|
dict->lookup("CS", &obj1);
|
|
}
|
|
if (obj1.isName()) {
|
|
res->lookupColorSpace(obj1.getName(), &obj2);
|
|
if (!obj2.isNull()) {
|
|
obj1.free();
|
|
obj1 = obj2;
|
|
} else {
|
|
obj2.free();
|
|
}
|
|
}
|
|
|
|
GBool haveRGBA = gFalse;
|
|
if (str->getKind() == strJPX && (csMode == streamCSDeviceRGB || csMode == streamCSDeviceCMYK)) {
|
|
// Case of transparent JPX image, they may contain RGBA data when SMaskInData=1
|
|
Object smaskInData;
|
|
dict->lookup("SMaskInData", &smaskInData);
|
|
haveRGBA = smaskInData.isInt() && smaskInData.getInt();
|
|
smaskInData.free();
|
|
}
|
|
|
|
if (!obj1.isNull() && !haveRGBA) {
|
|
colorSpace = GfxColorSpace::parse(&obj1);
|
|
} else if (csMode == streamCSDeviceGray) {
|
|
colorSpace = GfxColorSpace::create(csDeviceGray);
|
|
} else if (csMode == streamCSDeviceRGB) {
|
|
if (haveRGBA) {
|
|
colorSpace = GfxColorSpace::create(csDeviceRGBA);
|
|
} else {
|
|
colorSpace = GfxColorSpace::create(csDeviceRGB);
|
|
}
|
|
} else if (csMode == streamCSDeviceCMYK) {
|
|
if (haveRGBA) {
|
|
colorSpace = GfxColorSpace::create(csDeviceRGBA);
|
|
} else {
|
|
colorSpace = GfxColorSpace::create(csDeviceCMYK);
|
|
}
|
|
} else {
|
|
colorSpace = NULL;
|
|
}
|
|
obj1.free();
|
|
if (!colorSpace) {
|
|
goto err1;
|
|
}
|
|
if (colorSpace->getMode() == csPattern) {
|
|
error(errSyntaxError, getPos(), "Image with a Pattern color space");
|
|
delete colorSpace;
|
|
goto err1;
|
|
}
|
|
dict->lookup("Decode", &obj1);
|
|
if (obj1.isNull()) {
|
|
obj1.free();
|
|
dict->lookup("D", &obj1);
|
|
}
|
|
colorMap = new GfxImageColorMap(bits, &obj1, colorSpace);
|
|
obj1.free();
|
|
if (!colorMap->isOk()) {
|
|
delete colorMap;
|
|
goto err1;
|
|
}
|
|
|
|
// get the mask
|
|
haveColorKeyMask = haveExplicitMask = haveSoftMask = haveMatte = gFalse;
|
|
maskStr = NULL; // make gcc happy
|
|
maskWidth = maskHeight = 0; // make gcc happy
|
|
maskInvert = gFalse; // make gcc happy
|
|
maskColorMap = NULL; // make gcc happy
|
|
dict->lookup("Mask", &maskObj);
|
|
dict->lookup("SMask", &smaskObj);
|
|
|
|
if (maskObj.isString())
|
|
{
|
|
GString* maskStr = maskObj.getString();
|
|
Object oDict, oMaskObj;
|
|
oDict.initNull();
|
|
MemStream* stream = new MemStream(maskStr->getCString(), 0, maskStr->getLength(), &oDict);
|
|
Parser* parser = new Parser(NULL, new Lexer(NULL, stream), gTrue);
|
|
parser->getObj(&oMaskObj);
|
|
maskObj.free();
|
|
oMaskObj.copy(&maskObj);
|
|
oMaskObj.free();
|
|
delete parser;
|
|
}
|
|
|
|
if (smaskObj.isStream()) {
|
|
// soft mask
|
|
if (inlineImg) {
|
|
delete colorMap;
|
|
maskObj.free();
|
|
smaskObj.free();
|
|
goto err1;
|
|
}
|
|
maskStr = smaskObj.getStream();
|
|
maskDict = smaskObj.streamGetDict();
|
|
maskDict->lookup("Width", &obj1);
|
|
if (obj1.isNull()) {
|
|
obj1.free();
|
|
maskDict->lookup("W", &obj1);
|
|
}
|
|
if (!obj1.isNum()) {
|
|
delete colorMap;
|
|
maskObj.free();
|
|
smaskObj.free();
|
|
goto err2;
|
|
}
|
|
maskWidth = obj1.getNum();
|
|
obj1.free();
|
|
maskDict->lookup("Height", &obj1);
|
|
if (obj1.isNull()) {
|
|
obj1.free();
|
|
maskDict->lookup("H", &obj1);
|
|
}
|
|
if (!obj1.isNum()) {
|
|
delete colorMap;
|
|
maskObj.free();
|
|
smaskObj.free();
|
|
goto err2;
|
|
}
|
|
maskHeight = obj1.getNum();
|
|
obj1.free();
|
|
if (maskWidth <= 0 || maskHeight <= 0) {
|
|
delete colorMap;
|
|
maskObj.free();
|
|
smaskObj.free();
|
|
goto err1;
|
|
}
|
|
maskDict->lookup("BitsPerComponent", &obj1);
|
|
if (obj1.isNull()) {
|
|
obj1.free();
|
|
maskDict->lookup("BPC", &obj1);
|
|
}
|
|
if (!obj1.isInt()) {
|
|
delete colorMap;
|
|
maskObj.free();
|
|
smaskObj.free();
|
|
goto err2;
|
|
}
|
|
maskBits = obj1.getInt();
|
|
obj1.free();
|
|
if (maskBits < 1 || maskBits > 16) {
|
|
delete colorMap;
|
|
maskObj.free();
|
|
smaskObj.free();
|
|
goto err1;
|
|
}
|
|
maskDict->lookup("ColorSpace", &obj1);
|
|
if (obj1.isNull()) {
|
|
obj1.free();
|
|
maskDict->lookup("CS", &obj1);
|
|
}
|
|
if (obj1.isName()) {
|
|
res->lookupColorSpace(obj1.getName(), &obj2);
|
|
if (!obj2.isNull()) {
|
|
obj1.free();
|
|
obj1 = obj2;
|
|
} else {
|
|
obj2.free();
|
|
}
|
|
}
|
|
if (!obj1.isName("DeviceGray")) {
|
|
delete colorMap;
|
|
maskObj.free();
|
|
smaskObj.free();
|
|
goto err2;
|
|
}
|
|
maskColorSpace = new GfxDeviceGrayColorSpace();
|
|
obj1.free();
|
|
maskDict->lookup("Decode", &obj1);
|
|
if (obj1.isNull()) {
|
|
obj1.free();
|
|
maskDict->lookup("D", &obj1);
|
|
}
|
|
maskColorMap = new GfxImageColorMap(maskBits, &obj1, maskColorSpace);
|
|
obj1.free();
|
|
if (!maskColorMap->isOk()) {
|
|
delete maskColorMap;
|
|
delete colorMap;
|
|
maskObj.free();
|
|
smaskObj.free();
|
|
goto err1;
|
|
}
|
|
if (maskDict->lookup("Matte", &obj1)->isArray()) {
|
|
if (obj1.arrayGetLength() == colorSpace->getNComps()) {
|
|
for (i = 0; i < obj1.arrayGetLength(); ++i) {
|
|
if (obj1.arrayGet(i, &obj2)->isNum()) {
|
|
matte[i] = obj2.getNum();
|
|
} else {
|
|
error(errSyntaxError, getPos(),
|
|
"Invalid Matte entry in soft mask");
|
|
matte[i] = 0;
|
|
}
|
|
obj2.free();
|
|
}
|
|
haveMatte = gTrue;
|
|
} else {
|
|
error(errSyntaxError, getPos(), "Invalid Matte entry in soft mask");
|
|
}
|
|
}
|
|
obj1.free();
|
|
haveSoftMask = gTrue;
|
|
} else if (maskObj.isArray()) {
|
|
// color key mask
|
|
haveColorKeyMask = gTrue;
|
|
for (i = 0;
|
|
i+1 < maskObj.arrayGetLength() && i+1 < 2*gfxColorMaxComps;
|
|
i += 2) {
|
|
maskObj.arrayGet(i, &obj1);
|
|
if (!obj1.isInt()) {
|
|
obj1.free();
|
|
haveColorKeyMask = gFalse;
|
|
break;
|
|
}
|
|
maskColors[i] = obj1.getInt();
|
|
obj1.free();
|
|
if (maskColors[i] < 0 || maskColors[i] >= (1 << bits)) {
|
|
haveColorKeyMask = gFalse;
|
|
break;
|
|
}
|
|
maskObj.arrayGet(i+1, &obj1);
|
|
if (!obj1.isInt()) {
|
|
obj1.free();
|
|
haveColorKeyMask = gFalse;
|
|
break;
|
|
}
|
|
maskColors[i+1] = obj1.getInt();
|
|
obj1.free();
|
|
if (maskColors[i+1] < 0 || maskColors[i+1] >= (1 << bits) ||
|
|
maskColors[i] > maskColors[i+1]) {
|
|
haveColorKeyMask = gFalse;
|
|
break;
|
|
}
|
|
}
|
|
} else if (maskObj.isStream()) {
|
|
// explicit mask
|
|
if (inlineImg) {
|
|
delete colorMap;
|
|
maskObj.free();
|
|
smaskObj.free();
|
|
goto err1;
|
|
}
|
|
maskStr = maskObj.getStream();
|
|
maskDict = maskObj.streamGetDict();
|
|
maskDict->lookup("Width", &obj1);
|
|
if (obj1.isNull()) {
|
|
obj1.free();
|
|
maskDict->lookup("W", &obj1);
|
|
}
|
|
if (!obj1.isNum()) {
|
|
delete colorMap;
|
|
maskObj.free();
|
|
smaskObj.free();
|
|
goto err2;
|
|
}
|
|
maskWidth = obj1.getNum();
|
|
obj1.free();
|
|
maskDict->lookup("Height", &obj1);
|
|
if (obj1.isNull()) {
|
|
obj1.free();
|
|
maskDict->lookup("H", &obj1);
|
|
}
|
|
if (!obj1.isNum()) {
|
|
delete colorMap;
|
|
maskObj.free();
|
|
smaskObj.free();
|
|
goto err2;
|
|
}
|
|
maskHeight = obj1.getNum();
|
|
obj1.free();
|
|
if (maskWidth <= 0 || maskHeight <= 0) {
|
|
delete colorMap;
|
|
maskObj.free();
|
|
smaskObj.free();
|
|
goto err2;
|
|
}
|
|
maskDict->lookup("ImageMask", &obj1);
|
|
if (obj1.isNull()) {
|
|
obj1.free();
|
|
maskDict->lookup("IM", &obj1);
|
|
}
|
|
if (!obj1.isBool() || !obj1.getBool()) {
|
|
delete colorMap;
|
|
maskObj.free();
|
|
smaskObj.free();
|
|
goto err2;
|
|
}
|
|
obj1.free();
|
|
maskInvert = gFalse;
|
|
maskDict->lookup("Decode", &obj1);
|
|
if (obj1.isNull()) {
|
|
obj1.free();
|
|
maskDict->lookup("D", &obj1);
|
|
}
|
|
if (obj1.isArray()) {
|
|
obj1.arrayGet(0, &obj2);
|
|
maskInvert = obj2.isNum() && obj2.getNum() == 1;
|
|
obj2.free();
|
|
} else if (!obj1.isNull()) {
|
|
delete colorMap;
|
|
maskObj.free();
|
|
smaskObj.free();
|
|
goto err2;
|
|
}
|
|
obj1.free();
|
|
haveExplicitMask = gTrue;
|
|
}
|
|
|
|
// if drawing is disabled, skip over inline image data
|
|
if (state->getIgnoreColorOps() || !ocState) {
|
|
if (state->getIgnoreColorOps()) {
|
|
error(errSyntaxWarning, getPos(), "Ignoring image"
|
|
" in uncolored Type 3 char or tiling pattern");
|
|
}
|
|
if (inlineImg) {
|
|
str->reset();
|
|
n = height * ((width * colorMap->getNumPixelComps() *
|
|
colorMap->getBits() + 7) / 8);
|
|
str->discardChars(n);
|
|
str->close();
|
|
}
|
|
|
|
// draw it
|
|
} else {
|
|
if (haveSoftMask) {
|
|
dict->lookupNF("Mask", &maskRef);
|
|
out->drawSoftMaskedImage(state, ref, str, width, height, colorMap,
|
|
&maskRef, maskStr, maskWidth, maskHeight,
|
|
maskColorMap,
|
|
haveMatte ? matte : (double *)NULL,
|
|
interpolate);
|
|
maskRef.free();
|
|
delete maskColorMap;
|
|
} else if (haveExplicitMask) {
|
|
dict->lookupNF("Mask", &maskRef);
|
|
out->drawMaskedImage(state, ref, str, width, height, colorMap,
|
|
&maskRef, maskStr, maskWidth, maskHeight,
|
|
maskInvert, interpolate);
|
|
maskRef.free();
|
|
} else {
|
|
out->drawImage(state, ref, str, width, height, colorMap,
|
|
haveColorKeyMask ? maskColors : (int *)NULL, inlineImg,
|
|
interpolate);
|
|
}
|
|
}
|
|
|
|
delete colorMap;
|
|
maskObj.free();
|
|
smaskObj.free();
|
|
}
|
|
|
|
// restore rendering intent
|
|
if (state->getRenderingIntent() != riSaved) {
|
|
state->setRenderingIntent(riSaved);
|
|
out->updateRenderingIntent(state);
|
|
}
|
|
|
|
if ((i = width * height) > 1000) {
|
|
i = 1000;
|
|
}
|
|
opCounter += i;
|
|
|
|
return gTrue;
|
|
|
|
err2:
|
|
obj1.free();
|
|
err1:
|
|
error(errSyntaxError, getPos(), "Bad image parameters");
|
|
|
|
// restore rendering intent
|
|
if (state->getRenderingIntent() != riSaved) {
|
|
state->setRenderingIntent(riSaved);
|
|
out->updateRenderingIntent(state);
|
|
}
|
|
|
|
return gFalse;
|
|
}
|
|
|
|
void Gfx::doForm(Object *strRef, Object *str) {
|
|
Dict *dict;
|
|
GBool transpGroup, isolated, knockout;
|
|
Object matrixObj, bboxObj;
|
|
double m[6], bbox[4];
|
|
Object resObj;
|
|
Dict *resDict;
|
|
Object obj1, obj2, obj3;
|
|
int i;
|
|
|
|
// check for excessive recursion
|
|
if (formDepth > 100) {
|
|
return;
|
|
}
|
|
|
|
// check for optional content
|
|
if (!ocState && !out->needCharCount()) {
|
|
return;
|
|
}
|
|
|
|
// get stream dict
|
|
dict = str->streamGetDict();
|
|
|
|
// check form type
|
|
dict->lookup("FormType", &obj1);
|
|
if (!(obj1.isNull() || (obj1.isInt() && obj1.getInt() == 1))) {
|
|
error(errSyntaxError, getPos(), "Unknown form type");
|
|
}
|
|
obj1.free();
|
|
|
|
// get bounding box
|
|
dict->lookup("BBox", &bboxObj);
|
|
if (!bboxObj.isArray()) {
|
|
bboxObj.free();
|
|
error(errSyntaxError, getPos(), "Bad form bounding box");
|
|
return;
|
|
}
|
|
for (i = 0; i < 4; ++i) {
|
|
bboxObj.arrayGet(i, &obj1);
|
|
bbox[i] = obj1.getNum();
|
|
obj1.free();
|
|
}
|
|
bboxObj.free();
|
|
|
|
// get matrix
|
|
dict->lookup("Matrix", &matrixObj);
|
|
if (matrixObj.isArray()) {
|
|
for (i = 0; i < 6; ++i) {
|
|
matrixObj.arrayGet(i, &obj1);
|
|
m[i] = obj1.getNum();
|
|
obj1.free();
|
|
}
|
|
} else {
|
|
m[0] = 1; m[1] = 0;
|
|
m[2] = 0; m[3] = 1;
|
|
m[4] = 0; m[5] = 0;
|
|
}
|
|
matrixObj.free();
|
|
|
|
// get resources
|
|
dict->lookup("Resources", &resObj);
|
|
resDict = resObj.isDict() ? resObj.getDict() : (Dict *)NULL;
|
|
|
|
// check for a transparency group
|
|
transpGroup = isolated = knockout = gFalse;
|
|
if (dict->lookup("Group", &obj1)->isDict()) {
|
|
if (obj1.dictLookup("S", &obj2)->isName("Transparency")) {
|
|
transpGroup = gTrue;
|
|
if (obj1.dictLookup("I", &obj3)->isBool()) {
|
|
isolated = obj3.getBool();
|
|
}
|
|
obj3.free();
|
|
if (obj1.dictLookup("K", &obj3)->isBool()) {
|
|
knockout = obj3.getBool();
|
|
}
|
|
obj3.free();
|
|
}
|
|
obj2.free();
|
|
}
|
|
obj1.free();
|
|
|
|
// draw it
|
|
++formDepth;
|
|
drawForm(strRef, resDict, m, bbox, transpGroup, gFalse, isolated, knockout);
|
|
--formDepth;
|
|
|
|
resObj.free();
|
|
}
|
|
|
|
void Gfx::drawForm(Object *strRef, Dict *resDict,
|
|
double *matrix, double *bbox,
|
|
GBool transpGroup, GBool softMask,
|
|
GBool isolated, GBool knockout,
|
|
GBool alpha, Function *transferFunc,
|
|
Object *backdropColorObj) {
|
|
Parser *oldParser;
|
|
GfxState *savedState;
|
|
GfxColorSpace *blendingColorSpace;
|
|
GfxColor backdropColor;
|
|
Object strObj, groupAttrsObj, csObj, obj1;
|
|
double oldBaseMatrix[6];
|
|
int i;
|
|
|
|
// push new resources on stack
|
|
pushResources(resDict);
|
|
|
|
// save current graphics state
|
|
saveState();
|
|
|
|
// kill any pre-existing path
|
|
state->clearPath();
|
|
|
|
// save current parser
|
|
oldParser = parser;
|
|
|
|
// set form transformation matrix
|
|
state->concatCTM(matrix[0], matrix[1], matrix[2],
|
|
matrix[3], matrix[4], matrix[5]);
|
|
out->updateCTM(state, matrix[0], matrix[1], matrix[2],
|
|
matrix[3], matrix[4], matrix[5]);
|
|
|
|
// set form bounding box
|
|
state->moveTo(bbox[0], bbox[1]);
|
|
state->lineTo(bbox[2], bbox[1]);
|
|
state->lineTo(bbox[2], bbox[3]);
|
|
state->lineTo(bbox[0], bbox[3]);
|
|
state->closePath();
|
|
state->clip();
|
|
out->clip(state);
|
|
state->clearPath();
|
|
|
|
blendingColorSpace = NULL;
|
|
if (softMask || transpGroup) {
|
|
// get the blending color space
|
|
// NB: this must be done AFTER pushing the resource dictionary,
|
|
// so that any Default*** color spaces are available
|
|
strRef->fetch(xref, &strObj);
|
|
if (strObj.streamGetDict()->lookup("Group", &groupAttrsObj)->isDict()) {
|
|
if (!groupAttrsObj.dictLookup("CS", &csObj)->isNull()) {
|
|
blendingColorSpace = GfxColorSpace::parse(&csObj
|
|
);
|
|
}
|
|
csObj.free();
|
|
}
|
|
groupAttrsObj.free();
|
|
strObj.free();
|
|
|
|
traceBegin(oldBaseMatrix, softMask ? "begin soft mask" : "begin t-group");
|
|
if (state->getBlendMode() != gfxBlendNormal) {
|
|
state->setBlendMode(gfxBlendNormal);
|
|
out->updateBlendMode(state);
|
|
}
|
|
if (state->getFillOpacity() != 1) {
|
|
state->setFillOpacity(1);
|
|
out->updateFillOpacity(state);
|
|
}
|
|
if (state->getStrokeOpacity() != 1) {
|
|
state->setStrokeOpacity(1);
|
|
out->updateStrokeOpacity(state);
|
|
}
|
|
out->clearSoftMask(state);
|
|
out->beginTransparencyGroup(state, bbox, blendingColorSpace,
|
|
isolated, knockout, softMask);
|
|
}
|
|
|
|
// set new base matrix
|
|
for (i = 0; i < 6; ++i) {
|
|
oldBaseMatrix[i] = baseMatrix[i];
|
|
baseMatrix[i] = state->getCTM()[i];
|
|
}
|
|
|
|
// save the state stack -- this handles the case where the form
|
|
// contents have unbalanced q/Q operators
|
|
savedState = saveStateStack();
|
|
|
|
// draw the form
|
|
display(strRef, gFalse);
|
|
|
|
restoreStateStack(savedState);
|
|
|
|
if (softMask || transpGroup) {
|
|
out->endTransparencyGroup(state);
|
|
}
|
|
|
|
// restore base matrix
|
|
for (i = 0; i < 6; ++i) {
|
|
baseMatrix[i] = oldBaseMatrix[i];
|
|
}
|
|
|
|
// restore parser
|
|
parser = oldParser;
|
|
|
|
// restore graphics state
|
|
restoreState();
|
|
|
|
// pop resource stack
|
|
popResources();
|
|
|
|
if (softMask) {
|
|
for (i = 0; i < gfxColorMaxComps; ++i) {
|
|
backdropColor.c[i] = 0;
|
|
}
|
|
if (backdropColorObj->isArray()) {
|
|
for (i = 0;
|
|
i < backdropColorObj->arrayGetLength() && i < gfxColorMaxComps;
|
|
++i) {
|
|
backdropColorObj->arrayGet(i, &obj1);
|
|
if (obj1.isNum()) {
|
|
backdropColor.c[i] = dblToCol(obj1.getNum());
|
|
}
|
|
obj1.free();
|
|
}
|
|
} else if (blendingColorSpace) {
|
|
blendingColorSpace->getDefaultColor(&backdropColor);
|
|
}
|
|
//~ else: need to get the parent or default color space (?)
|
|
out->setSoftMask(state, bbox, alpha, transferFunc, &backdropColor);
|
|
traceEnd(oldBaseMatrix, "end soft mask");
|
|
} else if (transpGroup) {
|
|
out->paintTransparencyGroup(state, bbox);
|
|
traceEnd(oldBaseMatrix, "end t-group");
|
|
}
|
|
|
|
if (blendingColorSpace) {
|
|
delete blendingColorSpace;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
void Gfx::takeContentStreamStack(Gfx *oldGfx) {
|
|
contentStreamStack->append(oldGfx->contentStreamStack);
|
|
}
|
|
|
|
void Gfx::endOfPage() {
|
|
while (state->hasSaves()) {
|
|
restoreState();
|
|
}
|
|
while (markedContentStack->getLength() > 0) {
|
|
opEndMarkedContent(NULL, 0);
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
// in-line image operators
|
|
//------------------------------------------------------------------------
|
|
|
|
void Gfx::opBeginImage(Object args[], int numArgs) {
|
|
Stream *str;
|
|
GBool haveLength;
|
|
int c1, c2, c3;
|
|
|
|
// NB: this function is run even if ocState is false -- doImage() is
|
|
// responsible for skipping over the inline image data
|
|
|
|
// build dict/stream
|
|
str = buildImageStream(&haveLength);
|
|
|
|
// display the image
|
|
if (str) {
|
|
if (!doImage(NULL, str, gTrue)) {
|
|
delete str;
|
|
|
|
// if we have the stream length, skip to end-of-stream and then
|
|
// skip 'EI' in the original stream
|
|
} else if (haveLength) {
|
|
delete str;
|
|
str = parser->getStream();
|
|
c1 = str->getChar();
|
|
c2 = str->getChar();
|
|
c3 = str->lookChar();
|
|
while (!(c1 == 'E' && c2 == 'I' && Lexer::isSpace(c3)) && c3 != EOF) {
|
|
c1 = c2;
|
|
c2 = str->getChar();
|
|
c3 = str->lookChar();
|
|
}
|
|
|
|
// else, look for the 'EI' tag and skip it
|
|
} else {
|
|
c1 = str->getUndecodedStream()->getChar();
|
|
c2 = str->getUndecodedStream()->getChar();
|
|
c3 = str->getUndecodedStream()->lookChar();
|
|
while (!(c1 == 'E' && c2 == 'I' && Lexer::isSpace(c3)) && c3 != EOF) {
|
|
c1 = c2;
|
|
c2 = str->getUndecodedStream()->getChar();
|
|
c3 = str->getUndecodedStream()->lookChar();
|
|
}
|
|
delete str;
|
|
}
|
|
}
|
|
}
|
|
|
|
Stream *Gfx::buildImageStream(GBool *haveLength) {
|
|
Object dict;
|
|
Object obj, lengthObj;
|
|
char *key;
|
|
int length;
|
|
Stream *str;
|
|
|
|
// build dictionary
|
|
dict.initDict(xref);
|
|
getContentObj(&obj);
|
|
while (!obj.isCmd("ID") && !obj.isEOF()) {
|
|
if (!obj.isName()) {
|
|
error(errSyntaxError, getPos(),
|
|
"Inline image dictionary key must be a name object");
|
|
obj.free();
|
|
} else {
|
|
key = copyString(obj.getName());
|
|
obj.free();
|
|
getContentObj(&obj);
|
|
if (obj.isEOF()) {
|
|
gfree(key);
|
|
break;
|
|
}
|
|
if (obj.isError()) {
|
|
gfree(key);
|
|
obj.free();
|
|
} else {
|
|
dict.dictAdd(key, &obj);
|
|
}
|
|
}
|
|
getContentObj(&obj);
|
|
}
|
|
if (obj.isEOF()) {
|
|
error(errSyntaxError, getPos(), "End of file in inline image");
|
|
obj.free();
|
|
dict.free();
|
|
return NULL;
|
|
}
|
|
obj.free();
|
|
|
|
// check for length field
|
|
length = 0;
|
|
*haveLength = gFalse;
|
|
if (!dict.dictLookup("Length", &lengthObj)->isInt()) {
|
|
lengthObj.free();
|
|
dict.dictLookup("L", &lengthObj);
|
|
}
|
|
if (lengthObj.isInt()) {
|
|
length = lengthObj.getInt();
|
|
*haveLength = gTrue;
|
|
}
|
|
lengthObj.free();
|
|
|
|
// make stream
|
|
if (!(str = parser->getStream())) {
|
|
error(errSyntaxError, getPos(), "Invalid inline image data");
|
|
dict.free();
|
|
return NULL;
|
|
}
|
|
str = new EmbedStream(str, &dict, *haveLength, (GFileOffset)length);
|
|
str = str->addFilters(&dict);
|
|
|
|
return str;
|
|
}
|
|
|
|
void Gfx::opImageData(Object args[], int numArgs) {
|
|
error(errInternal, getPos(), "Got 'ID' operator");
|
|
}
|
|
|
|
void Gfx::opEndImage(Object args[], int numArgs) {
|
|
error(errInternal, getPos(), "Got 'EI' operator");
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
// type 3 font operators
|
|
//------------------------------------------------------------------------
|
|
|
|
void Gfx::opSetCharWidth(Object args[], int numArgs) {
|
|
out->type3D0(state, args[0].getNum(), args[1].getNum());
|
|
}
|
|
|
|
void Gfx::opSetCacheDevice(Object args[], int numArgs) {
|
|
state->setIgnoreColorOps(gTrue);
|
|
out->type3D1(state, args[0].getNum(), args[1].getNum(),
|
|
args[2].getNum(), args[3].getNum(),
|
|
args[4].getNum(), args[5].getNum());
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
// compatibility operators
|
|
//------------------------------------------------------------------------
|
|
|
|
void Gfx::opBeginIgnoreUndef(Object args[], int numArgs) {
|
|
++ignoreUndef;
|
|
}
|
|
|
|
void Gfx::opEndIgnoreUndef(Object args[], int numArgs) {
|
|
if (ignoreUndef > 0)
|
|
--ignoreUndef;
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
// marked content operators
|
|
//------------------------------------------------------------------------
|
|
|
|
void Gfx::SkipBDC()
|
|
{
|
|
Object obj;
|
|
getContentObj(&obj);
|
|
while (!obj.isEOF()) {
|
|
if (obj.isCmd("BMC") || obj.isCmd("BDC"))
|
|
SkipBDC();
|
|
else if (obj.isCmd("EMC"))
|
|
break;
|
|
obj.free();
|
|
getContentObj(&obj);
|
|
}
|
|
obj.free();
|
|
}
|
|
void Gfx::opBeginMarkedContent(Object args[], int numArgs) {
|
|
GfxMarkedContent *mc;
|
|
Object obj;
|
|
GBool ocStateNew;
|
|
TextString *s;
|
|
GfxMarkedContentKind mcKind;
|
|
|
|
if (printCommands) {
|
|
printf(" marked content: %s ", args[0].getName());
|
|
if (numArgs == 2) {
|
|
args[1].print(stdout);
|
|
}
|
|
printf("\n");
|
|
fflush(stdout);
|
|
}
|
|
mcKind = gfxMCOther;
|
|
if (args[0].isName("OC") && numArgs == 2 && args[1].isName() &&
|
|
res->lookupPropertiesNF(args[1].getName(), &obj)) {
|
|
if (doc->getOptionalContent()->evalOCObject(&obj, &ocStateNew)) {
|
|
ocState &= ocStateNew;
|
|
}
|
|
obj.free();
|
|
mcKind = gfxMCOptionalContent;
|
|
} else if (args[0].isName("Span") && numArgs == 2 && args[1].isDict()) {
|
|
if (args[1].dictLookup("ActualText", &obj)->isString()) {
|
|
s = new TextString(obj.getString());
|
|
out->beginActualText(state, s->getUnicode(), s->getLength());
|
|
delete s;
|
|
mcKind = gfxMCActualText;
|
|
}
|
|
obj.free();
|
|
} else if (args[0].isName("OShapes") && numArgs == 2 && args[1].isDict() && res->lookupPropertiesNF("OShapes", &obj)) {
|
|
Object oMetaOForm, oID, oTID, oID2, oIDF, oIDF2, oMCID, oMetadata, oMetadataCur;
|
|
if (obj.fetch(xref, &oMetaOForm)->isDict("OShapes") && args[1].dictLookup("IDF", &oIDF)->isString() && oMetaOForm.dictLookup("IDF", &oIDF2)->isString() &&
|
|
oIDF.getString()->cmp(oIDF2.getString()) == 0 && oMetaOForm.dictLookup("ID", &oID)->isString() && xref->getTrailerDict()->dictLookup("ID", &oTID)->isArray() &&
|
|
oTID.arrayGet(1, &oID2)->isString() && oID2.getString()->cmp(oID.getString()) == 0 && args[1].dictLookup("MCID", &oMCID)->isInt() &&
|
|
oMetaOForm.dictLookup("Metadata", &oMetadata)->isArray() && oMetadata.arrayGet(oMCID.getInt(), &oMetadataCur)->isString()) {
|
|
oID.free(); oTID.free(); oID2.free(); oIDF.free(); oIDF2.free(); oMetadata.free(); obj.free();
|
|
Object oImRef, oArrImage;
|
|
if (!oMetaOForm.dictLookup("Image", &oArrImage)->isArray() || !oArrImage.arrayGetNF(oMCID.getInt(), &oImRef)->isRef())
|
|
oImRef.free();
|
|
oMCID.free();
|
|
if (out->beginMCOShapes(state, oMetadataCur.getString(), &oImRef)) {
|
|
SkipBDC();
|
|
out->endMarkedContent(state);
|
|
oImRef.free(); oArrImage.free();
|
|
oMetaOForm.free(); oMetadataCur.free(); obj.free();
|
|
return;
|
|
}
|
|
oImRef.free(); oArrImage.free();
|
|
}
|
|
oMetaOForm.free(); oID.free(); oTID.free(); oID2.free(); oIDF.free(); oIDF2.free(); oMCID.free(), oMetadata.free(); oMetadataCur.free(); obj.free();
|
|
}
|
|
mc = new GfxMarkedContent(mcKind, ocState);
|
|
markedContentStack->append(mc);
|
|
}
|
|
|
|
void Gfx::opEndMarkedContent(Object args[], int numArgs) {
|
|
GfxMarkedContent *mc;
|
|
GfxMarkedContentKind mcKind;
|
|
|
|
if (markedContentStack->getLength() > 0) {
|
|
mc = (GfxMarkedContent *)
|
|
markedContentStack->del(markedContentStack->getLength() - 1);
|
|
mcKind = mc->kind;
|
|
delete mc;
|
|
if (mcKind == gfxMCOptionalContent) {
|
|
if (markedContentStack->getLength() > 0) {
|
|
mc = (GfxMarkedContent *)
|
|
markedContentStack->get(markedContentStack->getLength() - 1);
|
|
ocState = mc->ocState;
|
|
} else {
|
|
ocState = gTrue;
|
|
}
|
|
} else if (mcKind == gfxMCActualText) {
|
|
out->endActualText(state);
|
|
}
|
|
} else {
|
|
error(errSyntaxWarning, getPos(), "Mismatched EMC operator");
|
|
}
|
|
}
|
|
|
|
void Gfx::opMarkPoint(Object args[], int numArgs) {
|
|
if (printCommands) {
|
|
printf(" mark point: %s ", args[0].getName());
|
|
if (numArgs == 2)
|
|
args[1].print(stdout);
|
|
printf("\n");
|
|
fflush(stdout);
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
// misc
|
|
//------------------------------------------------------------------------
|
|
|
|
void Gfx::drawAnnot(Object *strRef, AnnotBorderStyle *borderStyle,
|
|
double xMin, double yMin, double xMax, double yMax) {
|
|
Dict *dict, *resDict;
|
|
Object str, matrixObj, bboxObj, resObj, obj1;
|
|
double formXMin, formYMin, formXMax, formYMax;
|
|
double x, y, sx, sy, tx, ty;
|
|
double m[6], bbox[4];
|
|
double *borderColor;
|
|
GfxColor color;
|
|
double *dash, *dash2;
|
|
int dashLength;
|
|
int i;
|
|
|
|
// this function assumes that we are in the default user space,
|
|
// i.e., baseMatrix = ctm
|
|
|
|
// if the bounding box has zero width or height, don't draw anything
|
|
// at all
|
|
if (xMin == xMax || yMin == yMax) {
|
|
return;
|
|
}
|
|
|
|
// draw the appearance stream (if there is one)
|
|
strRef->fetch(xref, &str);
|
|
if (str.isStream()) {
|
|
|
|
// get stream dict
|
|
dict = str.streamGetDict();
|
|
|
|
// get the form bounding box
|
|
dict->lookup("BBox", &bboxObj);
|
|
if (!bboxObj.isArray() || bboxObj.arrayGetLength() != 4) {
|
|
error(errSyntaxError, getPos(), "Bad form bounding box");
|
|
bboxObj.free();
|
|
str.free();
|
|
return;
|
|
}
|
|
for (i = 0; i < 4; ++i) {
|
|
bboxObj.arrayGet(i, &obj1);
|
|
if (obj1.isNum()) {
|
|
bbox[i] = obj1.getNum();
|
|
} else {
|
|
bbox[i] = 0;
|
|
}
|
|
obj1.free();
|
|
}
|
|
bboxObj.free();
|
|
|
|
// get the form matrix
|
|
dict->lookup("Matrix", &matrixObj);
|
|
if (matrixObj.isArray()) {
|
|
for (i = 0; i < 6; ++i) {
|
|
matrixObj.arrayGet(i, &obj1);
|
|
m[i] = obj1.getNum();
|
|
obj1.free();
|
|
}
|
|
} else {
|
|
m[0] = 1; m[1] = 0;
|
|
m[2] = 0; m[3] = 1;
|
|
m[4] = 0; m[5] = 0;
|
|
}
|
|
matrixObj.free();
|
|
|
|
// transform the four corners of the form bbox to default user
|
|
// space, and construct the transformed bbox
|
|
x = bbox[0] * m[0] + bbox[1] * m[2] + m[4];
|
|
y = bbox[0] * m[1] + bbox[1] * m[3] + m[5];
|
|
formXMin = formXMax = x;
|
|
formYMin = formYMax = y;
|
|
x = bbox[0] * m[0] + bbox[3] * m[2] + m[4];
|
|
y = bbox[0] * m[1] + bbox[3] * m[3] + m[5];
|
|
if (x < formXMin) {
|
|
formXMin = x;
|
|
} else if (x > formXMax) {
|
|
formXMax = x;
|
|
}
|
|
if (y < formYMin) {
|
|
formYMin = y;
|
|
} else if (y > formYMax) {
|
|
formYMax = y;
|
|
}
|
|
x = bbox[2] * m[0] + bbox[1] * m[2] + m[4];
|
|
y = bbox[2] * m[1] + bbox[1] * m[3] + m[5];
|
|
if (x < formXMin) {
|
|
formXMin = x;
|
|
} else if (x > formXMax) {
|
|
formXMax = x;
|
|
}
|
|
if (y < formYMin) {
|
|
formYMin = y;
|
|
} else if (y > formYMax) {
|
|
formYMax = y;
|
|
}
|
|
x = bbox[2] * m[0] + bbox[3] * m[2] + m[4];
|
|
y = bbox[2] * m[1] + bbox[3] * m[3] + m[5];
|
|
if (x < formXMin) {
|
|
formXMin = x;
|
|
} else if (x > formXMax) {
|
|
formXMax = x;
|
|
}
|
|
if (y < formYMin) {
|
|
formYMin = y;
|
|
} else if (y > formYMax) {
|
|
formYMax = y;
|
|
}
|
|
|
|
// construct a mapping matrix, [sx 0 0], which maps the transformed
|
|
// [0 sy 0]
|
|
// [tx ty 1]
|
|
// bbox to the annotation rectangle
|
|
if (formXMin == formXMax) {
|
|
// this shouldn't happen
|
|
sx = 1;
|
|
} else {
|
|
sx = (xMax - xMin) / (formXMax - formXMin);
|
|
}
|
|
if (formYMin == formYMax) {
|
|
// this shouldn't happen
|
|
sy = 1;
|
|
} else {
|
|
sy = (yMax - yMin) / (formYMax - formYMin);
|
|
}
|
|
tx = -formXMin * sx + xMin;
|
|
ty = -formYMin * sy + yMin;
|
|
|
|
// the final transform matrix is (form matrix) * (mapping matrix)
|
|
m[0] *= sx;
|
|
m[1] *= sy;
|
|
m[2] *= sx;
|
|
m[3] *= sy;
|
|
m[4] = m[4] * sx + tx;
|
|
m[5] = m[5] * sy + ty;
|
|
|
|
// get the resources
|
|
dict->lookup("Resources", &resObj);
|
|
resDict = resObj.isDict() ? resObj.getDict() : (Dict *)NULL;
|
|
|
|
// draw it
|
|
drawForm(strRef, resDict, m, bbox);
|
|
|
|
resObj.free();
|
|
}
|
|
str.free();
|
|
|
|
// draw the border
|
|
if (borderStyle && borderStyle->getWidth() > 0 &&
|
|
borderStyle->getNumColorComps() > 0) {
|
|
borderColor = borderStyle->getColor();
|
|
switch (borderStyle->getNumColorComps()) {
|
|
case 1:
|
|
if (state->getStrokeColorSpace()->getMode() != csDeviceGray) {
|
|
state->setStrokePattern(NULL);
|
|
state->setStrokeColorSpace(GfxColorSpace::create(csDeviceGray));
|
|
out->updateStrokeColorSpace(state);
|
|
}
|
|
break;
|
|
case 3:
|
|
if (state->getStrokeColorSpace()->getMode() != csDeviceRGB) {
|
|
state->setStrokePattern(NULL);
|
|
state->setStrokeColorSpace(GfxColorSpace::create(csDeviceRGB));
|
|
out->updateStrokeColorSpace(state);
|
|
}
|
|
break;
|
|
case 4:
|
|
if (state->getStrokeColorSpace()->getMode() != csDeviceCMYK) {
|
|
state->setStrokePattern(NULL);
|
|
state->setStrokeColorSpace(GfxColorSpace::create(csDeviceCMYK));
|
|
out->updateStrokeColorSpace(state);
|
|
}
|
|
break;
|
|
}
|
|
color.c[0] = dblToCol(borderColor[0]);
|
|
color.c[1] = dblToCol(borderColor[1]);
|
|
color.c[2] = dblToCol(borderColor[2]);
|
|
color.c[3] = dblToCol(borderColor[3]);
|
|
state->setStrokeColor(&color);
|
|
out->updateStrokeColor(state);
|
|
state->setLineWidth(borderStyle->getWidth());
|
|
out->updateLineWidth(state);
|
|
borderStyle->getDash(&dash, &dashLength);
|
|
if (borderStyle->getType() == annotBorderDashed && dashLength > 0) {
|
|
dash2 = (double *)gmallocn(dashLength, sizeof(double));
|
|
memcpy(dash2, dash, dashLength * sizeof(double));
|
|
state->setLineDash(dash2, dashLength, 0);
|
|
out->updateLineDash(state);
|
|
}
|
|
//~ this doesn't currently handle the beveled and engraved styles
|
|
state->clearPath();
|
|
state->moveTo(xMin, yMin);
|
|
state->lineTo(xMax, yMin);
|
|
if (borderStyle->getType() != annotBorderUnderlined) {
|
|
state->lineTo(xMax, yMax);
|
|
state->lineTo(xMin, yMax);
|
|
state->closePath();
|
|
}
|
|
out->stroke(state);
|
|
}
|
|
}
|
|
|
|
void Gfx::drawStamp(Object *strRef)
|
|
{
|
|
Dict *dict, *resDict;
|
|
Object str, bboxObj, resObj, obj1;
|
|
double m[6], bbox[4];
|
|
int i;
|
|
|
|
// draw the appearance stream (if there is one)
|
|
strRef->fetch(xref, &str);
|
|
if (str.isStream()) {
|
|
// get stream dict
|
|
dict = str.streamGetDict();
|
|
|
|
// get the form bounding box
|
|
dict->lookup("BBox", &bboxObj);
|
|
if (!bboxObj.isArray() || bboxObj.arrayGetLength() != 4) {
|
|
error(errSyntaxError, getPos(), "Bad form bounding box");
|
|
bboxObj.free();
|
|
str.free();
|
|
return;
|
|
}
|
|
for (i = 0; i < 4; ++i) {
|
|
bboxObj.arrayGet(i, &obj1);
|
|
if (obj1.isNum()) {
|
|
bbox[i] = obj1.getNum();
|
|
} else {
|
|
bbox[i] = 0;
|
|
}
|
|
obj1.free();
|
|
}
|
|
bboxObj.free();
|
|
|
|
m[0] = 1; m[1] = 0;
|
|
m[2] = 0; m[3] = 1;
|
|
m[4] = 0; m[5] = 0;
|
|
|
|
// get the resources
|
|
dict->lookup("Resources", &resObj);
|
|
resDict = resObj.isDict() ? resObj.getDict() : (Dict *)NULL;
|
|
|
|
// draw it
|
|
drawForm(strRef, resDict, m, bbox);
|
|
|
|
resObj.free();
|
|
}
|
|
str.free();
|
|
}
|
|
|
|
void Gfx::saveState() {
|
|
out->saveState(state);
|
|
state = state->save();
|
|
}
|
|
|
|
void Gfx::restoreState() {
|
|
state = state->restore();
|
|
out->restoreState(state);
|
|
}
|
|
|
|
// Create a new state stack, and initialize it with a copy of the
|
|
// current state.
|
|
GfxState *Gfx::saveStateStack() {
|
|
GfxState *oldState;
|
|
|
|
out->saveState(state);
|
|
oldState = state;
|
|
state = state->copy(gTrue);
|
|
return oldState;
|
|
}
|
|
|
|
// Switch back to the previous state stack.
|
|
void Gfx::restoreStateStack(GfxState *oldState) {
|
|
while (state->hasSaves()) {
|
|
restoreState();
|
|
}
|
|
delete state;
|
|
state = oldState;
|
|
out->restoreState(state);
|
|
}
|
|
|
|
void Gfx::pushResources(Dict *resDict) {
|
|
res = new GfxResources(xref, resDict, res);
|
|
}
|
|
|
|
void Gfx::popResources() {
|
|
GfxResources *resPtr;
|
|
|
|
resPtr = res->getNext();
|
|
delete res;
|
|
res = resPtr;
|
|
}
|