From cca66b9ec4e494c1d919bff0f71a820d8afab1fa Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 20:24:48 +0200 Subject: Adding upstream version 1.2.2. Signed-off-by: Daniel Baumann --- src/extension/internal/pdfinput/pdf-parser.cpp | 3368 ++++++++++++++++++++++++ 1 file changed, 3368 insertions(+) create mode 100644 src/extension/internal/pdfinput/pdf-parser.cpp (limited to 'src/extension/internal/pdfinput/pdf-parser.cpp') diff --git a/src/extension/internal/pdfinput/pdf-parser.cpp b/src/extension/internal/pdfinput/pdf-parser.cpp new file mode 100644 index 0000000..d32a7fa --- /dev/null +++ b/src/extension/internal/pdfinput/pdf-parser.cpp @@ -0,0 +1,3368 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * PDF parsing using libpoppler. + *//* + * Authors: + * Derived from poppler's Gfx.cc, which was derived from Xpdf by 1996-2003 Glyph & Cog, LLC + * Jon A. Cruz + * + * Copyright (C) 2018 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" // only include where actually required! +#endif + +#ifdef HAVE_POPPLER + +#ifdef USE_GCC_PRAGMAS +#pragma implementation +#endif + +#include +#include +#include +#include +#include + +#include "svg-builder.h" +#include "Gfx.h" +#include "pdf-parser.h" +#include "util/units.h" +#include "poppler-transition-api.h" + +#include "glib/poppler-features.h" +#include "goo/gmem.h" +#include "goo/GooString.h" +#include "GlobalParams.h" +#include "CharTypes.h" +#include "Object.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 "Error.h" + +// the MSVC math.h doesn't define this +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +//------------------------------------------------------------------------ +// constants +//------------------------------------------------------------------------ + +// Default max delta allowed in any color component for a shading fill. +#define defaultShadingColorDelta (dblToCol( 1 / 2.0 )) + +// Default max recursive depth for a shading fill. +#define defaultShadingMaxDepth 6 + +// Max number of operators kept in the history list. +#define maxOperatorHistoryDepth 16 + +//------------------------------------------------------------------------ +// Operator table +//------------------------------------------------------------------------ + +PdfOperator PdfParser::opTab[] = { + {"\"", 3, {tchkNum, tchkNum, tchkString}, + &PdfParser::opMoveSetShowText}, + {"'", 1, {tchkString}, + &PdfParser::opMoveShowText}, + {"B", 0, {tchkNone}, + &PdfParser::opFillStroke}, + {"B*", 0, {tchkNone}, + &PdfParser::opEOFillStroke}, + {"BDC", 2, {tchkName, tchkProps}, + &PdfParser::opBeginMarkedContent}, + {"BI", 0, {tchkNone}, + &PdfParser::opBeginImage}, + {"BMC", 1, {tchkName}, + &PdfParser::opBeginMarkedContent}, + {"BT", 0, {tchkNone}, + &PdfParser::opBeginText}, + {"BX", 0, {tchkNone}, + &PdfParser::opBeginIgnoreUndef}, + {"CS", 1, {tchkName}, + &PdfParser::opSetStrokeColorSpace}, + {"DP", 2, {tchkName, tchkProps}, + &PdfParser::opMarkPoint}, + {"Do", 1, {tchkName}, + &PdfParser::opXObject}, + {"EI", 0, {tchkNone}, + &PdfParser::opEndImage}, + {"EMC", 0, {tchkNone}, + &PdfParser::opEndMarkedContent}, + {"ET", 0, {tchkNone}, + &PdfParser::opEndText}, + {"EX", 0, {tchkNone}, + &PdfParser::opEndIgnoreUndef}, + {"F", 0, {tchkNone}, + &PdfParser::opFill}, + {"G", 1, {tchkNum}, + &PdfParser::opSetStrokeGray}, + {"ID", 0, {tchkNone}, + &PdfParser::opImageData}, + {"J", 1, {tchkInt}, + &PdfParser::opSetLineCap}, + {"K", 4, {tchkNum, tchkNum, tchkNum, tchkNum}, + &PdfParser::opSetStrokeCMYKColor}, + {"M", 1, {tchkNum}, + &PdfParser::opSetMiterLimit}, + {"MP", 1, {tchkName}, + &PdfParser::opMarkPoint}, + {"Q", 0, {tchkNone}, + &PdfParser::opRestore}, + {"RG", 3, {tchkNum, tchkNum, tchkNum}, + &PdfParser::opSetStrokeRGBColor}, + {"S", 0, {tchkNone}, + &PdfParser::opStroke}, + {"SC", -4, {tchkNum, tchkNum, tchkNum, tchkNum}, + &PdfParser::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,}, + &PdfParser::opSetStrokeColorN}, + {"T*", 0, {tchkNone}, + &PdfParser::opTextNextLine}, + {"TD", 2, {tchkNum, tchkNum}, + &PdfParser::opTextMoveSet}, + {"TJ", 1, {tchkArray}, + &PdfParser::opShowSpaceText}, + {"TL", 1, {tchkNum}, + &PdfParser::opSetTextLeading}, + {"Tc", 1, {tchkNum}, + &PdfParser::opSetCharSpacing}, + {"Td", 2, {tchkNum, tchkNum}, + &PdfParser::opTextMove}, + {"Tf", 2, {tchkName, tchkNum}, + &PdfParser::opSetFont}, + {"Tj", 1, {tchkString}, + &PdfParser::opShowText}, + {"Tm", 6, {tchkNum, tchkNum, tchkNum, tchkNum, + tchkNum, tchkNum}, + &PdfParser::opSetTextMatrix}, + {"Tr", 1, {tchkInt}, + &PdfParser::opSetTextRender}, + {"Ts", 1, {tchkNum}, + &PdfParser::opSetTextRise}, + {"Tw", 1, {tchkNum}, + &PdfParser::opSetWordSpacing}, + {"Tz", 1, {tchkNum}, + &PdfParser::opSetHorizScaling}, + {"W", 0, {tchkNone}, + &PdfParser::opClip}, + {"W*", 0, {tchkNone}, + &PdfParser::opEOClip}, + {"b", 0, {tchkNone}, + &PdfParser::opCloseFillStroke}, + {"b*", 0, {tchkNone}, + &PdfParser::opCloseEOFillStroke}, + {"c", 6, {tchkNum, tchkNum, tchkNum, tchkNum, + tchkNum, tchkNum}, + &PdfParser::opCurveTo}, + {"cm", 6, {tchkNum, tchkNum, tchkNum, tchkNum, + tchkNum, tchkNum}, + &PdfParser::opConcat}, + {"cs", 1, {tchkName}, + &PdfParser::opSetFillColorSpace}, + {"d", 2, {tchkArray, tchkNum}, + &PdfParser::opSetDash}, + {"d0", 2, {tchkNum, tchkNum}, + &PdfParser::opSetCharWidth}, + {"d1", 6, {tchkNum, tchkNum, tchkNum, tchkNum, + tchkNum, tchkNum}, + &PdfParser::opSetCacheDevice}, + {"f", 0, {tchkNone}, + &PdfParser::opFill}, + {"f*", 0, {tchkNone}, + &PdfParser::opEOFill}, + {"g", 1, {tchkNum}, + &PdfParser::opSetFillGray}, + {"gs", 1, {tchkName}, + &PdfParser::opSetExtGState}, + {"h", 0, {tchkNone}, + &PdfParser::opClosePath}, + {"i", 1, {tchkNum}, + &PdfParser::opSetFlat}, + {"j", 1, {tchkInt}, + &PdfParser::opSetLineJoin}, + {"k", 4, {tchkNum, tchkNum, tchkNum, tchkNum}, + &PdfParser::opSetFillCMYKColor}, + {"l", 2, {tchkNum, tchkNum}, + &PdfParser::opLineTo}, + {"m", 2, {tchkNum, tchkNum}, + &PdfParser::opMoveTo}, + {"n", 0, {tchkNone}, + &PdfParser::opEndPath}, + {"q", 0, {tchkNone}, + &PdfParser::opSave}, + {"re", 4, {tchkNum, tchkNum, tchkNum, tchkNum}, + &PdfParser::opRectangle}, + {"rg", 3, {tchkNum, tchkNum, tchkNum}, + &PdfParser::opSetFillRGBColor}, + {"ri", 1, {tchkName}, + &PdfParser::opSetRenderingIntent}, + {"s", 0, {tchkNone}, + &PdfParser::opCloseStroke}, + {"sc", -4, {tchkNum, tchkNum, tchkNum, tchkNum}, + &PdfParser::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,}, + &PdfParser::opSetFillColorN}, + {"sh", 1, {tchkName}, + &PdfParser::opShFill}, + {"v", 4, {tchkNum, tchkNum, tchkNum, tchkNum}, + &PdfParser::opCurveTo1}, + {"w", 1, {tchkNum}, + &PdfParser::opSetLineWidth}, + {"y", 4, {tchkNum, tchkNum, tchkNum, tchkNum}, + &PdfParser::opCurveTo2} +}; + +#define numOps (sizeof(opTab) / sizeof(PdfOperator)) + +namespace { + +GfxPatch blankPatch() +{ + GfxPatch patch; + memset(&patch, 0, sizeof(patch)); // quick-n-dirty + return patch; +} + +} // namespace + +//------------------------------------------------------------------------ +// ClipHistoryEntry +//------------------------------------------------------------------------ + +class ClipHistoryEntry { +public: + + ClipHistoryEntry(GfxPath *clipPath = nullptr, GfxClipType clipType = clipNormal); + virtual ~ClipHistoryEntry(); + + // Manipulate clip path stack + ClipHistoryEntry *save(); + ClipHistoryEntry *restore(); + GBool hasSaves() { return saved != nullptr; } + void setClip(_POPPLER_CONST_83 GfxPath *newClipPath, GfxClipType newClipType = clipNormal); + GfxPath *getClipPath() { return clipPath; } + GfxClipType getClipType() { return clipType; } + +private: + + ClipHistoryEntry *saved; // next clip path on stack + + GfxPath *clipPath; // used as the path to be filled for an 'sh' operator + GfxClipType clipType; + + ClipHistoryEntry(ClipHistoryEntry *other); +}; + +//------------------------------------------------------------------------ +// PdfParser +//------------------------------------------------------------------------ + +PdfParser::PdfParser(XRef *xrefA, + Inkscape::Extension::Internal::SvgBuilder *builderA, + int /*pageNum*/, + int rotate, + Dict *resDict, + _POPPLER_CONST PDFRectangle *box, + _POPPLER_CONST PDFRectangle *cropBox) : + xref(xrefA), + builder(builderA), + subPage(gFalse), + printCommands(false), + res(new GfxResources(xref, resDict, nullptr)), // start the resource stack + state(new GfxState(72.0, 72.0, box, rotate, gTrue)), + fontChanged(gFalse), + clip(clipNone), + ignoreUndef(0), + baseMatrix(), + formDepth(0), + parser(nullptr), + colorDeltas(), + maxDepths(), + clipHistory(new ClipHistoryEntry()), + operatorHistory(nullptr) +{ + setDefaultApproximationPrecision(); + builder->setDocumentSize(Inkscape::Util::Quantity::convert(state->getPageWidth(), "pt", "px"), + Inkscape::Util::Quantity::convert(state->getPageHeight(), "pt", "px")); + + const double *ctm = state->getCTM(); + double scaledCTM[6]; + for (int i = 0; i < 6; ++i) { + baseMatrix[i] = ctm[i]; + scaledCTM[i] = Inkscape::Util::Quantity::convert(ctm[i], "pt", "px"); + } + saveState(); + builder->setTransform((double*)&scaledCTM); + formDepth = 0; + + // set crop box + if (cropBox) { + if (printCommands) + printf("cropBox: %f %f %f %f\n", cropBox->x1, cropBox->y1, cropBox->x2, cropBox->y2); + // do not clip if it's not needed + if (cropBox->x1 != 0.0 || cropBox->y1 != 0.0 || + cropBox->x2 != state->getPageWidth() || cropBox->y2 != state->getPageHeight()) { + + 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(); + clipHistory->setClip(state->getPath(), clipNormal); + builder->setClipPath(state); + state->clearPath(); + } + } + pushOperator("startPage"); +} + +PdfParser::PdfParser(XRef *xrefA, + Inkscape::Extension::Internal::SvgBuilder *builderA, + Dict *resDict, + _POPPLER_CONST PDFRectangle *box) : + xref(xrefA), + builder(builderA), + subPage(gTrue), + printCommands(false), + res(new GfxResources(xref, resDict, nullptr)), // start the resource stack + state(new GfxState(72, 72, box, 0, gFalse)), + fontChanged(gFalse), + clip(clipNone), + ignoreUndef(0), + baseMatrix(), + formDepth(0), + parser(nullptr), + colorDeltas(), + maxDepths(), + clipHistory(new ClipHistoryEntry()), + operatorHistory(nullptr) +{ + setDefaultApproximationPrecision(); + + for (int i = 0; i < 6; ++i) { + baseMatrix[i] = state->getCTM()[i]; + } + formDepth = 0; +} + +PdfParser::~PdfParser() { + while(operatorHistory) { + OpHistoryEntry *tmp = operatorHistory->next; + delete operatorHistory; + operatorHistory = tmp; + } + + while (state && state->hasSaves()) { + restoreState(); + } + + if (!subPage) { + //out->endPage(); + } + + while (res) { + popResources(); + } + + if (state) { + delete state; + state = nullptr; + } + + if (clipHistory) { + delete clipHistory; + clipHistory = nullptr; + } +} + +void PdfParser::parse(Object *obj, GBool topLevel) { + Object obj2; + + if (obj->isArray()) { + for (int i = 0; i < obj->arrayGetLength(); ++i) { + _POPPLER_CALL_ARGS(obj2, obj->arrayGet, i); + if (!obj2.isStream()) { + error(errInternal, -1, "Weird page contents"); + _POPPLER_FREE(obj2); + return; + } + _POPPLER_FREE(obj2); + } + } else if (!obj->isStream()) { + error(errInternal, -1, "Weird page contents"); + return; + } + parser = new _POPPLER_NEW_PARSER(xref, obj); + go(topLevel); + delete parser; + parser = nullptr; +} + +void PdfParser::go(GBool /*topLevel*/) +{ + Object obj; + Object args[maxArgs]; + + // scan a sequence of objects + int numArgs = 0; + _POPPLER_CALL(obj, parser->getObj); + while (!obj.isEOF()) { + + // got a command - execute it + if (obj.isCmd()) { + if (printCommands) { + obj.print(stdout); + for (int i = 0; i < numArgs; ++i) { + printf(" "); + args[i].print(stdout); + } + printf("\n"); + fflush(stdout); + } + + // Run the operation + execOp(&obj, args, numArgs); + +#if !defined(POPPLER_NEW_OBJECT_API) + _POPPLER_FREE(obj); + for (int i = 0; i < numArgs; ++i) + _POPPLER_FREE(args[i]); +#endif + numArgs = 0; + + // got an argument - save it + } else if (numArgs < maxArgs) { + args[numArgs++] = std::move(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); + } + _POPPLER_FREE(obj); + } + + // grab the next object + _POPPLER_CALL(obj, parser->getObj); + } + _POPPLER_FREE(obj); + + // args at end with no command + if (numArgs > 0) { + error(errSyntaxError, getPos(), "Leftover args in content stream"); + if (printCommands) { + printf("%d leftovers:", numArgs); + for (int i = 0; i < numArgs; ++i) { + printf(" "); + args[i].print(stdout); + } + printf("\n"); + fflush(stdout); + } +#if !defined(POPPLER_NEW_OBJECT_API) + for (int i = 0; i < numArgs; ++i) + _POPPLER_FREE(args[i]); +#endif + } +} + +void PdfParser::pushOperator(const char *name) +{ + OpHistoryEntry *newEntry = new OpHistoryEntry; + newEntry->name = name; + newEntry->state = nullptr; + newEntry->depth = (operatorHistory != nullptr ? (operatorHistory->depth+1) : 0); + newEntry->next = operatorHistory; + operatorHistory = newEntry; + + // Truncate list if needed + if (operatorHistory->depth > maxOperatorHistoryDepth) { + OpHistoryEntry *curr = operatorHistory; + OpHistoryEntry *prev = nullptr; + while (curr && curr->next != nullptr) { + curr->depth--; + prev = curr; + curr = curr->next; + } + if (prev) { + if (curr->state != nullptr) + delete curr->state; + delete curr; + prev->next = nullptr; + } + } +} + +const char *PdfParser::getPreviousOperator(unsigned int look_back) { + OpHistoryEntry *prev = nullptr; + if (operatorHistory != nullptr && look_back > 0) { + prev = operatorHistory->next; + while (--look_back > 0 && prev != nullptr) { + prev = prev->next; + } + } + if (prev != nullptr) { + return prev->name; + } else { + return ""; + } +} + +void PdfParser::execOp(Object *cmd, Object args[], int numArgs) { + PdfOperator *op; + const char *name; + Object *argPtr; + int i; + + // find operator + name = cmd->getCmd(); + if (!(op = findOp(name))) { + if (ignoreUndef == 0) + error(errSyntaxError, getPos(), "Unknown operator '{0:s}'", name); + return; + } + + // type check args + argPtr = args; + if (op->numArgs >= 0) { + if (numArgs < op->numArgs) { + error(errSyntaxError, getPos(), "Too few ({0:d}) args to '{1:d}' operator", numArgs, name); + return; + } + if (numArgs > op->numArgs) { +#if 0 + error(errSyntaxError, 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(errSyntaxError, getPos(), "Too many ({0:d}) args to '{1:s}' operator", + numArgs, name); + return; + } + } + 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; + } + } + + // add to history + pushOperator((char*)&op->name); + + // do it + (this->*op->func)(argPtr, numArgs); +} + +PdfOperator* PdfParser::findOp(const char *name) { + int a = -1; + int b = numOps; + int cmp = -1; + // invariant: opTab[a] < name < opTab[b] + while (b - a > 1) { + const int 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 nullptr; + return &opTab[a]; +} + +GBool PdfParser::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; +} + +int PdfParser::getPos() { + return parser ? parser->getPos() : -1; +} + +//------------------------------------------------------------------------ +// graphics state operators +//------------------------------------------------------------------------ + +void PdfParser::opSave(Object /*args*/[], int /*numArgs*/) +{ + saveState(); +} + +void PdfParser::opRestore(Object /*args*/[], int /*numArgs*/) +{ + restoreState(); +} + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::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()); + const char *prevOp = getPreviousOperator(); + double a0 = args[0].getNum(); + double a1 = args[1].getNum(); + double a2 = args[2].getNum(); + double a3 = args[3].getNum(); + double a4 = args[4].getNum(); + double a5 = args[5].getNum(); + if (!strcmp(prevOp, "q")) { + builder->setTransform(a0, a1, a2, a3, a4, a5); + } else if (!strcmp(prevOp, "cm") || !strcmp(prevOp, "startPage")) { + // multiply it with the previous transform + double otherMatrix[6]; + if (!builder->getTransform(otherMatrix)) { // invalid transform + // construct identity matrix + otherMatrix[0] = otherMatrix[3] = 1.0; + otherMatrix[1] = otherMatrix[2] = otherMatrix[4] = otherMatrix[5] = 0.0; + } + double c0 = a0*otherMatrix[0] + a1*otherMatrix[2]; + double c1 = a0*otherMatrix[1] + a1*otherMatrix[3]; + double c2 = a2*otherMatrix[0] + a3*otherMatrix[2]; + double c3 = a2*otherMatrix[1] + a3*otherMatrix[3]; + double c4 = a4*otherMatrix[0] + a5*otherMatrix[2] + otherMatrix[4]; + double c5 = a4*otherMatrix[1] + a5*otherMatrix[3] + otherMatrix[5]; + builder->setTransform(c0, c1, c2, c3, c4, c5); + } else { + builder->pushGroup(); + builder->setTransform(a0, a1, a2, a3, a4, a5); + } + fontChanged = gTrue; +} + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::opSetDash(Object args[], int /*numArgs*/) +{ + double *dash = nullptr; + + Array *a = args[0].getArray(); + int length = a->getLength(); + if (length != 0) { + dash = (double *)gmallocn(length, sizeof(double)); + for (int i = 0; i < length; ++i) { + Object obj; + dash[i] = _POPPLER_CALL_ARGS_DEREF(obj, a->get, i).getNum(); + _POPPLER_FREE(obj); + } + } +#if POPPLER_CHECK_VERSION(22, 9, 0) + state->setLineDash(std::vector (dash, dash + length), args[1].getNum()); +#else + state->setLineDash(dash, length, args[1].getNum()); +#endif + builder->updateStyle(state); +} + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::opSetFlat(Object args[], int /*numArgs*/) +{ + state->setFlatness((int)args[0].getNum()); +} + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::opSetLineJoin(Object args[], int /*numArgs*/) +{ + state->setLineJoin(args[0].getInt()); + builder->updateStyle(state); +} + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::opSetLineCap(Object args[], int /*numArgs*/) +{ + state->setLineCap(args[0].getInt()); + builder->updateStyle(state); +} + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::opSetMiterLimit(Object args[], int /*numArgs*/) +{ + state->setMiterLimit(args[0].getNum()); + builder->updateStyle(state); +} + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::opSetLineWidth(Object args[], int /*numArgs*/) +{ + state->setLineWidth(args[0].getNum()); + builder->updateStyle(state); +} + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::opSetExtGState(Object args[], int /*numArgs*/) +{ + Object obj1, obj2, obj3, obj4, obj5; + Function *funcs[4] = {nullptr, nullptr, nullptr, nullptr}; + GfxColor backdropColor; + GBool haveBackdropColor = gFalse; + GBool alpha = gFalse; + + _POPPLER_CALL_ARGS(obj1, res->lookupGState, args[0].getName()); + if (obj1.isNull()) { + return; + } + if (!obj1.isDict()) { + error(errSyntaxError, getPos(), "ExtGState '{0:s}' is wrong type"), args[0].getName(); + _POPPLER_FREE(obj1); + return; + } + if (printCommands) { + printf(" gfx state dict: "); + obj1.print(); + printf("\n"); + } + + // transparency support: blend mode, fill/stroke opacity + if (!_POPPLER_CALL_ARGS_DEREF(obj2, obj1.dictLookup, "BM").isNull()) { + GfxBlendMode mode = gfxBlendNormal; + if (state->parseBlendMode(&obj2, &mode)) { + state->setBlendMode(mode); + } else { + error(errSyntaxError, getPos(), "Invalid blend mode in ExtGState"); + } + } + _POPPLER_FREE(obj2); + if (_POPPLER_CALL_ARGS_DEREF(obj2, obj1.dictLookup, "ca").isNum()) { + state->setFillOpacity(obj2.getNum()); + } + _POPPLER_FREE(obj2); + if (_POPPLER_CALL_ARGS_DEREF(obj2, obj1.dictLookup, "CA").isNum()) { + state->setStrokeOpacity(obj2.getNum()); + } + _POPPLER_FREE(obj2); + + // fill/stroke overprint + GBool haveFillOP = gFalse; + if ((haveFillOP = _POPPLER_CALL_ARGS_DEREF(obj2, obj1.dictLookup, "op").isBool())) { + state->setFillOverprint(obj2.getBool()); + } + _POPPLER_FREE(obj2); + if (_POPPLER_CALL_ARGS_DEREF(obj2, obj1.dictLookup, "OP").isBool()) { + state->setStrokeOverprint(obj2.getBool()); + if (!haveFillOP) { + state->setFillOverprint(obj2.getBool()); + } + } + _POPPLER_FREE(obj2); + + // stroke adjust + if (_POPPLER_CALL_ARGS_DEREF(obj2, obj1.dictLookup, "SA").isBool()) { + state->setStrokeAdjust(obj2.getBool()); + } + _POPPLER_FREE(obj2); + + // transfer function + if (_POPPLER_CALL_ARGS_DEREF(obj2, obj1.dictLookup, "TR2").isNull()) { + _POPPLER_FREE(obj2); + _POPPLER_CALL_ARGS(obj2, obj1.dictLookup, "TR"); + } + if (obj2.isName(const_cast("Default")) || + obj2.isName(const_cast("Identity"))) { + funcs[0] = funcs[1] = funcs[2] = funcs[3] = nullptr; + state->setTransfer(funcs); + } else if (obj2.isArray() && obj2.arrayGetLength() == 4) { + int pos = 4; + for (int i = 0; i < 4; ++i) { + _POPPLER_CALL_ARGS(obj3, obj2.arrayGet, i); + funcs[i] = Function::parse(&obj3); + _POPPLER_FREE(obj3); + if (!funcs[i]) { + pos = i; + break; + } + } + if (pos == 4) { + state->setTransfer(funcs); + } + } else if (obj2.isName() || obj2.isDict() || obj2.isStream()) { + if ((funcs[0] = Function::parse(&obj2))) { + funcs[1] = funcs[2] = funcs[3] = nullptr; + state->setTransfer(funcs); + } + } else if (!obj2.isNull()) { + error(errSyntaxError, getPos(), "Invalid transfer function in ExtGState"); + } + _POPPLER_FREE(obj2); + + // soft mask + if (!_POPPLER_CALL_ARGS_DEREF(obj2, obj1.dictLookup, "SMask").isNull()) { + if (obj2.isName(const_cast("None"))) { + builder->clearSoftMask(state); + } else if (obj2.isDict()) { + if (_POPPLER_CALL_ARGS_DEREF(obj3, obj2.dictLookup, "S").isName("Alpha")) { + alpha = gTrue; + } else { // "Luminosity" + alpha = gFalse; + } + _POPPLER_FREE(obj3); + funcs[0] = nullptr; + if (!_POPPLER_CALL_ARGS_DEREF(obj3, obj2.dictLookup, "TR").isNull()) { + funcs[0] = Function::parse(&obj3); + if (funcs[0]->getInputSize() != 1 || + funcs[0]->getOutputSize() != 1) { + error(errSyntaxError, getPos(), "Invalid transfer function in soft mask in ExtGState"); + delete funcs[0]; + funcs[0] = nullptr; + } + } + _POPPLER_FREE(obj3); + if ((haveBackdropColor = _POPPLER_CALL_ARGS_DEREF(obj3, obj2.dictLookup, "BC").isArray())) { + for (int & i : backdropColor.c) { + i = 0; + } + for (int i = 0; i < obj3.arrayGetLength() && i < gfxColorMaxComps; ++i) { + _POPPLER_CALL_ARGS(obj4, obj3.arrayGet, i); + if (obj4.isNum()) { + backdropColor.c[i] = dblToCol(obj4.getNum()); + } + _POPPLER_FREE(obj4); + } + } + _POPPLER_FREE(obj3); + if (_POPPLER_CALL_ARGS_DEREF(obj3, obj2.dictLookup, "G").isStream()) { + if (_POPPLER_CALL_ARGS_DEREF(obj4, obj3.streamGetDict()->lookup, "Group").isDict()) { + GfxColorSpace *blendingColorSpace = nullptr; + GBool isolated = gFalse; + GBool knockout = gFalse; + if (!_POPPLER_CALL_ARGS_DEREF(obj5, obj4.dictLookup, "CS").isNull()) { + blendingColorSpace = GfxColorSpace::parse(nullptr, &obj5, nullptr, state); + } + _POPPLER_FREE(obj5); + if (_POPPLER_CALL_ARGS_DEREF(obj5, obj4.dictLookup, "I").isBool()) { + isolated = obj5.getBool(); + } + _POPPLER_FREE(obj5); + if (_POPPLER_CALL_ARGS_DEREF(obj5, obj4.dictLookup, "K").isBool()) { + knockout = obj5.getBool(); + } + _POPPLER_FREE(obj5); + if (!haveBackdropColor) { + if (blendingColorSpace) { + blendingColorSpace->getDefaultColor(&backdropColor); + } else { + //~ need to get the parent or default color space (?) + for (int & i : backdropColor.c) { + i = 0; + } + } + } + doSoftMask(&obj3, alpha, blendingColorSpace, + isolated, knockout, funcs[0], &backdropColor); + if (funcs[0]) { + delete funcs[0]; + } + } else { + error(errSyntaxError, getPos(), "Invalid soft mask in ExtGState - missing group"); + } + _POPPLER_FREE(obj4); + } else { + error(errSyntaxError, getPos(), "Invalid soft mask in ExtGState - missing group"); + } + _POPPLER_FREE(obj3); + } else if (!obj2.isNull()) { + error(errSyntaxError, getPos(), "Invalid soft mask in ExtGState"); + } + } + _POPPLER_FREE(obj2); + + _POPPLER_FREE(obj1); +} + +void PdfParser::doSoftMask(Object *str, GBool alpha, + GfxColorSpace *blendingColorSpace, + GBool isolated, GBool knockout, + Function *transferFunc, GfxColor *backdropColor) { + 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 + _POPPLER_CALL_ARGS(obj1, dict->lookup, "FormType"); + if (!(obj1.isNull() || (obj1.isInt() && obj1.getInt() == 1))) { + error(errSyntaxError, getPos(), "Unknown form type"); + } + _POPPLER_FREE(obj1); + + // get bounding box + _POPPLER_CALL_ARGS(obj1, dict->lookup, "BBox"); + if (!obj1.isArray()) { + _POPPLER_FREE(obj1); + error(errSyntaxError, getPos(), "Bad form bounding box"); + return; + } + for (i = 0; i < 4; ++i) { + _POPPLER_CALL_ARGS(obj2, obj1.arrayGet, i); + bbox[i] = obj2.getNum(); + _POPPLER_FREE(obj2); + } + _POPPLER_FREE(obj1); + + // get matrix + _POPPLER_CALL_ARGS(obj1, dict->lookup, "Matrix"); + if (obj1.isArray()) { + for (i = 0; i < 6; ++i) { + _POPPLER_CALL_ARGS(obj2, obj1.arrayGet, i); + m[i] = obj2.getNum(); + _POPPLER_FREE(obj2); + } + } else { + m[0] = 1; m[1] = 0; + m[2] = 0; m[3] = 1; + m[4] = 0; m[5] = 0; + } + _POPPLER_FREE(obj1); + + // get resources + _POPPLER_CALL_ARGS(obj1, dict->lookup, "Resources"); + resDict = obj1.isDict() ? obj1.getDict() : (Dict *)nullptr; + + // draw it + ++formDepth; + doForm1(str, resDict, m, bbox, gTrue, gTrue, + blendingColorSpace, isolated, knockout, + alpha, transferFunc, backdropColor); + --formDepth; + + if (blendingColorSpace) { + delete blendingColorSpace; + } + _POPPLER_FREE(obj1); +} + +void PdfParser::opSetRenderingIntent(Object /*args*/[], int /*numArgs*/) +{ +} + +//------------------------------------------------------------------------ +// color operators +//------------------------------------------------------------------------ + +/** + * Get a newly allocated color space instance by CS operation argument. + * + * Maintains a cache for named color spaces to avoid expensive re-parsing. + */ +GfxColorSpace *PdfParser::lookupColorSpaceCopy(Object &arg) +{ + assert(!arg.isNull()); + + char const *name = arg.isName() ? arg.getName() : nullptr; + GfxColorSpace *colorSpace = nullptr; + + if (name && (colorSpace = colorSpacesCache[name].get())) { + return colorSpace->copy(); + } + + Object *argPtr = &arg; + Object obj; + + if (name) { + _POPPLER_CALL_ARGS(obj, res->lookupColorSpace, name); + if (!obj.isNull()) { + argPtr = &obj; + } + } + + colorSpace = GfxColorSpace::parse(res, argPtr, nullptr, state); + + _POPPLER_FREE(obj); + + if (name && colorSpace) { + colorSpacesCache[name].reset(colorSpace->copy()); + } + + return colorSpace; +} + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::opSetFillGray(Object args[], int /*numArgs*/) +{ + GfxColor color; + + state->setFillPattern(nullptr); + state->setFillColorSpace(new GfxDeviceGrayColorSpace()); + color.c[0] = dblToCol(args[0].getNum()); + state->setFillColor(&color); + builder->updateStyle(state); +} + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::opSetStrokeGray(Object args[], int /*numArgs*/) +{ + GfxColor color; + + state->setStrokePattern(nullptr); + state->setStrokeColorSpace(new GfxDeviceGrayColorSpace()); + color.c[0] = dblToCol(args[0].getNum()); + state->setStrokeColor(&color); + builder->updateStyle(state); +} + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::opSetFillCMYKColor(Object args[], int /*numArgs*/) +{ + GfxColor color; + int i; + + state->setFillPattern(nullptr); + state->setFillColorSpace(new GfxDeviceCMYKColorSpace()); + for (i = 0; i < 4; ++i) { + color.c[i] = dblToCol(args[i].getNum()); + } + state->setFillColor(&color); + builder->updateStyle(state); +} + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::opSetStrokeCMYKColor(Object args[], int /*numArgs*/) +{ + GfxColor color; + + state->setStrokePattern(nullptr); + state->setStrokeColorSpace(new GfxDeviceCMYKColorSpace()); + for (int i = 0; i < 4; ++i) { + color.c[i] = dblToCol(args[i].getNum()); + } + state->setStrokeColor(&color); + builder->updateStyle(state); +} + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::opSetFillRGBColor(Object args[], int /*numArgs*/) +{ + GfxColor color; + + state->setFillPattern(nullptr); + state->setFillColorSpace(new GfxDeviceRGBColorSpace()); + for (int i = 0; i < 3; ++i) { + color.c[i] = dblToCol(args[i].getNum()); + } + state->setFillColor(&color); + builder->updateStyle(state); +} + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::opSetStrokeRGBColor(Object args[], int /*numArgs*/) { + GfxColor color; + + state->setStrokePattern(nullptr); + state->setStrokeColorSpace(new GfxDeviceRGBColorSpace()); + for (int i = 0; i < 3; ++i) { + color.c[i] = dblToCol(args[i].getNum()); + } + state->setStrokeColor(&color); + builder->updateStyle(state); +} + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::opSetFillColorSpace(Object args[], int numArgs) +{ + assert(numArgs >= 1); + GfxColorSpace *colorSpace = lookupColorSpaceCopy(args[0]); + + state->setFillPattern(nullptr); + + if (colorSpace) { + GfxColor color; + state->setFillColorSpace(colorSpace); + colorSpace->getDefaultColor(&color); + state->setFillColor(&color); + builder->updateStyle(state); + } else { + error(errSyntaxError, getPos(), "Bad color space (fill)"); + } +} + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::opSetStrokeColorSpace(Object args[], int numArgs) +{ + assert(numArgs >= 1); + GfxColorSpace *colorSpace = lookupColorSpaceCopy(args[0]); + + state->setStrokePattern(nullptr); + + if (colorSpace) { + GfxColor color; + state->setStrokeColorSpace(colorSpace); + colorSpace->getDefaultColor(&color); + state->setStrokeColor(&color); + builder->updateStyle(state); + } else { + error(errSyntaxError, getPos(), "Bad color space (stroke)"); + } +} + +void PdfParser::opSetFillColor(Object args[], int numArgs) { + GfxColor color; + int i; + + if (numArgs != state->getFillColorSpace()->getNComps()) { + error(errSyntaxError, getPos(), "Incorrect number of arguments in 'sc' command"); + return; + } + state->setFillPattern(nullptr); + for (i = 0; i < numArgs; ++i) { + color.c[i] = dblToCol(args[i].getNum()); + } + state->setFillColor(&color); + builder->updateStyle(state); +} + +void PdfParser::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; + } + state->setStrokePattern(nullptr); + for (i = 0; i < numArgs; ++i) { + color.c[i] = dblToCol(args[i].getNum()); + } + state->setStrokeColor(&color); + builder->updateStyle(state); +} + +void PdfParser::opSetFillColorN(Object args[], int numArgs) { + GfxColor color; + int i; + + if (state->getFillColorSpace()->getMode() == csPattern) { + 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); + builder->updateStyle(state); + } + GfxPattern *pattern; + if (args[numArgs-1].isName() && + (pattern = res->lookupPattern(args[numArgs-1].getName(), nullptr, state))) { + state->setFillPattern(pattern); + builder->updateStyle(state); + } + + } else { + if (numArgs != state->getFillColorSpace()->getNComps()) { + error(errSyntaxError, getPos(), "Incorrect number of arguments in 'scn' command"); + return; + } + state->setFillPattern(nullptr); + for (i = 0; i < numArgs && i < gfxColorMaxComps; ++i) { + if (args[i].isNum()) { + color.c[i] = dblToCol(args[i].getNum()); + } + } + state->setFillColor(&color); + builder->updateStyle(state); + } +} + +void PdfParser::opSetStrokeColorN(Object args[], int numArgs) { + GfxColor color; + int i; + + if (state->getStrokeColorSpace()->getMode() == csPattern) { + 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); + builder->updateStyle(state); + } + GfxPattern *pattern; + if (args[numArgs-1].isName() && + (pattern = res->lookupPattern(args[numArgs-1].getName(), nullptr, state))) { + state->setStrokePattern(pattern); + builder->updateStyle(state); + } + + } else { + if (numArgs != state->getStrokeColorSpace()->getNComps()) { + error(errSyntaxError, getPos(), "Incorrect number of arguments in 'SCN' command"); + return; + } + state->setStrokePattern(nullptr); + for (i = 0; i < numArgs && i < gfxColorMaxComps; ++i) { + if (args[i].isNum()) { + color.c[i] = dblToCol(args[i].getNum()); + } + } + state->setStrokeColor(&color); + builder->updateStyle(state); + } +} + +//------------------------------------------------------------------------ +// path segment operators +//------------------------------------------------------------------------ + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::opMoveTo(Object args[], int /*numArgs*/) +{ + state->moveTo(args[0].getNum(), args[1].getNum()); +} + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::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()); +} + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::opCurveTo(Object args[], int /*numArgs*/) +{ + if (!state->isCurPt()) { + error(errSyntaxError, getPos(), "No current point in curveto"); + return; + } + double x1 = args[0].getNum(); + double y1 = args[1].getNum(); + double x2 = args[2].getNum(); + double y2 = args[3].getNum(); + double x3 = args[4].getNum(); + double y3 = args[5].getNum(); + state->curveTo(x1, y1, x2, y2, x3, y3); +} + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::opCurveTo1(Object args[], int /*numArgs*/) +{ + if (!state->isCurPt()) { + error(errSyntaxError, getPos(), "No current point in curveto1"); + return; + } + double x1 = state->getCurX(); + double y1 = state->getCurY(); + double x2 = args[0].getNum(); + double y2 = args[1].getNum(); + double x3 = args[2].getNum(); + double y3 = args[3].getNum(); + state->curveTo(x1, y1, x2, y2, x3, y3); +} + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::opCurveTo2(Object args[], int /*numArgs*/) +{ + if (!state->isCurPt()) { + error(errSyntaxError, getPos(), "No current point in curveto2"); + return; + } + double x1 = args[0].getNum(); + double y1 = args[1].getNum(); + double x2 = args[2].getNum(); + double y2 = args[3].getNum(); + double x3 = x2; + double y3 = y2; + state->curveTo(x1, y1, x2, y2, x3, y3); +} + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::opRectangle(Object args[], int /*numArgs*/) +{ + double x = args[0].getNum(); + double y = args[1].getNum(); + double w = args[2].getNum(); + double 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 PdfParser::opClosePath(Object /*args*/[], int /*numArgs*/) +{ + if (!state->isCurPt()) { + error(errSyntaxError, getPos(), "No current point in closepath"); + return; + } + state->closePath(); +} + +//------------------------------------------------------------------------ +// path painting operators +//------------------------------------------------------------------------ + +void PdfParser::opEndPath(Object /*args*/[], int /*numArgs*/) +{ + doEndPath(); +} + +void PdfParser::opStroke(Object /*args*/[], int /*numArgs*/) +{ + if (!state->isCurPt()) { + //error(getPos(), const_cast("No path in stroke")); + return; + } + if (state->isPath()) { + if (state->getStrokeColorSpace()->getMode() == csPattern && + !builder->isPatternTypeSupported(state->getStrokePattern())) { + doPatternStrokeFallback(); + } else { + builder->addPath(state, false, true); + } + } + doEndPath(); +} + +void PdfParser::opCloseStroke(Object * /*args[]*/, int /*numArgs*/) { + if (!state->isCurPt()) { + //error(getPos(), const_cast("No path in closepath/stroke")); + return; + } + state->closePath(); + if (state->isPath()) { + if (state->getStrokeColorSpace()->getMode() == csPattern && + !builder->isPatternTypeSupported(state->getStrokePattern())) { + doPatternStrokeFallback(); + } else { + builder->addPath(state, false, true); + } + } + doEndPath(); +} + +void PdfParser::opFill(Object /*args*/[], int /*numArgs*/) +{ + if (!state->isCurPt()) { + //error(getPos(), const_cast("No path in fill")); + return; + } + if (state->isPath()) { + if (state->getFillColorSpace()->getMode() == csPattern && + !builder->isPatternTypeSupported(state->getFillPattern())) { + doPatternFillFallback(gFalse); + } else { + builder->addPath(state, true, false); + } + } + doEndPath(); +} + +void PdfParser::opEOFill(Object /*args*/[], int /*numArgs*/) +{ + if (!state->isCurPt()) { + //error(getPos(), const_cast("No path in eofill")); + return; + } + if (state->isPath()) { + if (state->getFillColorSpace()->getMode() == csPattern && + !builder->isPatternTypeSupported(state->getFillPattern())) { + doPatternFillFallback(gTrue); + } else { + builder->addPath(state, true, false, true); + } + } + doEndPath(); +} + +void PdfParser::opFillStroke(Object /*args*/[], int /*numArgs*/) +{ + if (!state->isCurPt()) { + //error(getPos(), const_cast("No path in fill/stroke")); + return; + } + if (state->isPath()) { + doFillAndStroke(gFalse); + } else { + builder->addPath(state, true, true); + } + doEndPath(); +} + +void PdfParser::opCloseFillStroke(Object /*args*/[], int /*numArgs*/) +{ + if (!state->isCurPt()) { + //error(getPos(), const_cast("No path in closepath/fill/stroke")); + return; + } + if (state->isPath()) { + state->closePath(); + doFillAndStroke(gFalse); + } + doEndPath(); +} + +void PdfParser::opEOFillStroke(Object /*args*/[], int /*numArgs*/) +{ + if (!state->isCurPt()) { + //error(getPos(), const_cast("No path in eofill/stroke")); + return; + } + if (state->isPath()) { + doFillAndStroke(gTrue); + } + doEndPath(); +} + +void PdfParser::opCloseEOFillStroke(Object /*args*/[], int /*numArgs*/) +{ + if (!state->isCurPt()) { + //error(getPos(), const_cast("No path in closepath/eofill/stroke")); + return; + } + if (state->isPath()) { + state->closePath(); + doFillAndStroke(gTrue); + } + doEndPath(); +} + +void PdfParser::doFillAndStroke(GBool eoFill) { + GBool fillOk = gTrue, strokeOk = gTrue; + if (state->getFillColorSpace()->getMode() == csPattern && + !builder->isPatternTypeSupported(state->getFillPattern())) { + fillOk = gFalse; + } + if (state->getStrokeColorSpace()->getMode() == csPattern && + !builder->isPatternTypeSupported(state->getStrokePattern())) { + strokeOk = gFalse; + } + if (fillOk && strokeOk) { + builder->addPath(state, true, true, eoFill); + } else { + doPatternFillFallback(eoFill); + doPatternStrokeFallback(); + } +} + +void PdfParser::doPatternFillFallback(GBool eoFill) { + GfxPattern *pattern; + + if (!(pattern = state->getFillPattern())) { + return; + } + switch (pattern->getType()) { + case 1: + break; + case 2: + doShadingPatternFillFallback(static_cast(pattern), gFalse, eoFill); + break; + default: + error(errUnimplemented, getPos(), "Unimplemented pattern type (%d) in fill", + pattern->getType()); + break; + } +} + +void PdfParser::doPatternStrokeFallback() { + GfxPattern *pattern; + + if (!(pattern = state->getStrokePattern())) { + return; + } + switch (pattern->getType()) { + case 1: + break; + case 2: + doShadingPatternFillFallback(static_cast(pattern), gTrue, gFalse); + break; + default: + error(errUnimplemented, getPos(), "Unimplemented pattern type ({0:d}) in stroke", + pattern->getType()); + break; + } +} + +void PdfParser::doShadingPatternFillFallback(GfxShadingPattern *sPat, + GBool stroke, GBool eoFill) { + GfxShading *shading; + GfxPath *savedPath; + const 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 + savedPath = state->getPath()->copy(); + saveState(); + + // clip to bbox + if (false ){//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(); + //builder->clip(state); + state->setPath(savedPath->copy()); + } + + // clip to current path + if (stroke) { + state->clipToStrokePath(); + //out->clipToStrokePath(state); + } else { + state->clip(); + if (eoFill) { + builder->setClipPath(state, true); + } else { + builder->setClipPath(state); + } + } + + // set the color space + state->setFillColorSpace(shading->getColorSpace()->copy()); + + // background color fill + if (shading->getHasBackground()) { + state->setFillColor(shading->getBackground()); + builder->addPath(state, true, false); + } + state->clearPath(); + + // construct a (pattern space) -> (current space) transform matrix + ctm = state->getCTM(); + btm = baseMatrix; + ptm = sPat->getMatrix(); + // iCTM = invert CTM + det = 1 / (ctm[0] * ctm[3] - ctm[1] * ctm[2]); + 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]); + builder->setTransform(m[0], m[1], m[2], m[3], m[4], m[5]); + + // do shading type-specific operations + switch (shading->getType()) { + case 1: + doFunctionShFill(static_cast(shading)); + break; + case 2: + case 3: + // no need to implement these + break; + case 4: + case 5: + doGouraudTriangleShFill(static_cast(shading)); + break; + case 6: + case 7: + doPatchMeshShFill(static_cast(shading)); + break; + } + + // restore graphics state + restoreState(); + state->setPath(savedPath); +} + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::opShFill(Object args[], int /*numArgs*/) +{ + GfxShading *shading = nullptr; + GfxPath *savedPath = nullptr; + double xMin, yMin, xMax, yMax; + double xTemp, yTemp; + double gradientTransform[6]; + double *matrix = nullptr; + GBool savedState = gFalse; + + if (!(shading = res->lookupShading(args[0].getName(), nullptr, state))) { + return; + } + + // save current graphics state + if (shading->getType() != 2 && shading->getType() != 3) { + savedPath = state->getPath()->copy(); + saveState(); + savedState = gTrue; + } else { // get gradient transform if possible + // check proper operator sequence + // first there should be one W(*) and then one 'cm' somewhere before 'sh' + GBool seenClip, seenConcat; + seenClip = (clipHistory->getClipPath() != nullptr); + seenConcat = gFalse; + int i = 1; + while (i <= maxOperatorHistoryDepth) { + const char *opName = getPreviousOperator(i); + if (!strcmp(opName, "cm")) { + if (seenConcat) { // more than one 'cm' + break; + } else { + seenConcat = gTrue; + } + } + i++; + } + + if (seenConcat && seenClip) { + if (builder->getTransform(gradientTransform)) { + matrix = (double*)&gradientTransform; + builder->setTransform(1.0, 0.0, 0.0, 1.0, 0.0, 0.0); // remove transform + } + } + } + + // clip to bbox + if (shading->getHasBBox()) { + shading->getBBox(&xMin, &yMin, &xMax, &yMax); + if (matrix != nullptr) { + xTemp = matrix[0]*xMin + matrix[2]*yMin + matrix[4]; + yTemp = matrix[1]*xMin + matrix[3]*yMin + matrix[5]; + state->moveTo(xTemp, yTemp); + xTemp = matrix[0]*xMax + matrix[2]*yMin + matrix[4]; + yTemp = matrix[1]*xMax + matrix[3]*yMin + matrix[5]; + state->lineTo(xTemp, yTemp); + xTemp = matrix[0]*xMax + matrix[2]*yMax + matrix[4]; + yTemp = matrix[1]*xMax + matrix[3]*yMax + matrix[5]; + state->lineTo(xTemp, yTemp); + xTemp = matrix[0]*xMin + matrix[2]*yMax + matrix[4]; + yTemp = matrix[1]*xMin + matrix[3]*yMax + matrix[5]; + state->lineTo(xTemp, yTemp); + } + else { + state->moveTo(xMin, yMin); + state->lineTo(xMax, yMin); + state->lineTo(xMax, yMax); + state->lineTo(xMin, yMax); + } + state->closePath(); + state->clip(); + if (savedState) + builder->setClipPath(state); + else + builder->clip(state); + state->clearPath(); + } + + // set the color space + if (savedState) + state->setFillColorSpace(shading->getColorSpace()->copy()); + + // do shading type-specific operations + switch (shading->getType()) { + case 1: + doFunctionShFill(static_cast(shading)); + break; + case 2: + case 3: + if (clipHistory->getClipPath()) { + builder->addShadedFill(shading, matrix, clipHistory->getClipPath(), + clipHistory->getClipType() == clipEO ? true : false); + } + break; + case 4: + case 5: + doGouraudTriangleShFill(static_cast(shading)); + break; + case 6: + case 7: + doPatchMeshShFill(static_cast(shading)); + break; + } + + // restore graphics state + if (savedState) { + restoreState(); + state->setPath(savedPath); + } + + delete shading; +} + +void PdfParser::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 PdfParser::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 functionColorDelta = colorDeltas[pdfFunctionShading-1]; + const 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 == maxDepths[pdfFunctionShading-1]) { + + // use the center color + shading->getColor(xM, yM, &fillColor); + state->setFillColor(&fillColor); + + // 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(); + builder->addPath(state, true, false); + 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 PdfParser::doGouraudTriangleShFill(GfxGouraudTriangleShading *shading) { + double x0, y0, x1, y1, x2, y2; + GfxColor color0, color1, color2; + int i; + + for (i = 0; i < shading->getNTriangles(); ++i) { + shading->getTriangle(i, &x0, &y0, &color0, + &x1, &y1, &color1, + &x2, &y2, &color2); + gouraudFillTriangle(x0, y0, &color0, x1, y1, &color1, x2, y2, &color2, + shading->getColorSpace()->getNComps(), 0); + } +} + +void PdfParser::gouraudFillTriangle(double x0, double y0, GfxColor *color0, + double x1, double y1, GfxColor *color1, + double x2, double y2, GfxColor *color2, + int nComps, int depth) { + double x01, y01, x12, y12, x20, y20; + double gouraudColorDelta = colorDeltas[pdfGouraudTriangleShading-1]; + GfxColor color01, color12, color20; + int i; + + for (i = 0; i < nComps; ++i) { + if (abs(color0->c[i] - color1->c[i]) > gouraudColorDelta || + abs(color1->c[i] - color2->c[i]) > gouraudColorDelta) { + break; + } + } + if (i == nComps || depth == maxDepths[pdfGouraudTriangleShading-1]) { + state->setFillColor(color0); + state->moveTo(x0, y0); + state->lineTo(x1, y1); + state->lineTo(x2, y2); + state->closePath(); + builder->addPath(state, true, false); + 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); + //~ if the shading has a Function, this should interpolate on the + //~ function parameter, not on the color components + for (i = 0; i < nComps; ++i) { + color01.c[i] = (color0->c[i] + color1->c[i]) / 2; + color12.c[i] = (color1->c[i] + color2->c[i]) / 2; + color20.c[i] = (color2->c[i] + color0->c[i]) / 2; + } + gouraudFillTriangle(x0, y0, color0, x01, y01, &color01, + x20, y20, &color20, nComps, depth + 1); + gouraudFillTriangle(x01, y01, &color01, x1, y1, color1, + x12, y12, &color12, nComps, depth + 1); + gouraudFillTriangle(x01, y01, &color01, x12, y12, &color12, + x20, y20, &color20, nComps, depth + 1); + gouraudFillTriangle(x20, y20, &color20, x12, y12, &color12, + x2, y2, color2, nComps, depth + 1); + } +} + +void PdfParser::doPatchMeshShFill(GfxPatchMeshShading *shading) { + int start, i; + + if (shading->getNPatches() > 128) { + start = 3; + } else if (shading->getNPatches() > 64) { + start = 2; + } else if (shading->getNPatches() > 16) { + start = 1; + } else { + start = 0; + } + for (i = 0; i < shading->getNPatches(); ++i) { + fillPatch(shading->getPatch(i), shading->getColorSpace()->getNComps(), + start); + } +} + +void PdfParser::fillPatch(_POPPLER_CONST GfxPatch *patch, int nComps, int depth) { + GfxPatch patch00 = blankPatch(); + GfxPatch patch01 = blankPatch(); + GfxPatch patch10 = blankPatch(); + GfxPatch patch11 = blankPatch(); + GfxColor color = {{0}}; + double xx[4][8]; + double yy[4][8]; + double xxm; + double yym; + double patchColorDelta = colorDeltas[pdfPatchMeshShading - 1]; + + int i; + + for (i = 0; i < nComps; ++i) { + if (std::abs(patch->color[0][0].c[i] - patch->color[0][1].c[i]) + > patchColorDelta || + std::abs(patch->color[0][1].c[i] - patch->color[1][1].c[i]) + > patchColorDelta || + std::abs(patch->color[1][1].c[i] - patch->color[1][0].c[i]) + > patchColorDelta || + std::abs(patch->color[1][0].c[i] - patch->color[0][0].c[i]) + > patchColorDelta) { + break; + } + color.c[i] = GfxColorComp(patch->color[0][0].c[i]); + } + if (i == nComps || depth == maxDepths[pdfPatchMeshShading-1]) { + state->setFillColor(&color); + 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(); + builder->addPath(state, true, false); + state->clearPath(); + } 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]; + } + //~ if the shading has a Function, this should interpolate on the + //~ function parameter, not on the color components + for (i = 0; i < nComps; ++i) { + patch00.color[0][0].c[i] = patch->color[0][0].c[i]; + patch00.color[0][1].c[i] = (patch->color[0][0].c[i] + + patch->color[0][1].c[i]) / 2; + patch01.color[0][0].c[i] = patch00.color[0][1].c[i]; + patch01.color[0][1].c[i] = patch->color[0][1].c[i]; + patch01.color[1][1].c[i] = (patch->color[0][1].c[i] + + patch->color[1][1].c[i]) / 2; + patch11.color[0][1].c[i] = patch01.color[1][1].c[i]; + patch11.color[1][1].c[i] = patch->color[1][1].c[i]; + patch11.color[1][0].c[i] = (patch->color[1][1].c[i] + + patch->color[1][0].c[i]) / 2; + patch10.color[1][1].c[i] = patch11.color[1][0].c[i]; + patch10.color[1][0].c[i] = patch->color[1][0].c[i]; + patch10.color[0][0].c[i] = (patch->color[1][0].c[i] + + patch->color[0][0].c[i]) / 2; + patch00.color[1][0].c[i] = patch10.color[0][0].c[i]; + patch00.color[1][1].c[i] = (patch00.color[1][0].c[i] + + patch01.color[1][1].c[i]) / 2; + patch01.color[1][0].c[i] = patch00.color[1][1].c[i]; + patch11.color[0][0].c[i] = patch00.color[1][1].c[i]; + patch10.color[0][1].c[i] = patch00.color[1][1].c[i]; + } + fillPatch(&patch00, nComps, depth + 1); + fillPatch(&patch10, nComps, depth + 1); + fillPatch(&patch01, nComps, depth + 1); + fillPatch(&patch11, nComps, depth + 1); + } +} + +void PdfParser::doEndPath() { + if (state->isCurPt() && clip != clipNone) { + state->clip(); + if (clip == clipNormal) { + clipHistory->setClip(state->getPath(), clipNormal); + builder->clip(state); + } else { + clipHistory->setClip(state->getPath(), clipEO); + builder->clip(state, true); + } + } + clip = clipNone; + state->clearPath(); +} + +//------------------------------------------------------------------------ +// path clipping operators +//------------------------------------------------------------------------ + +void PdfParser::opClip(Object /*args*/[], int /*numArgs*/) +{ + clip = clipNormal; +} + +void PdfParser::opEOClip(Object /*args*/[], int /*numArgs*/) +{ + clip = clipEO; +} + +//------------------------------------------------------------------------ +// text object operators +//------------------------------------------------------------------------ + +void PdfParser::opBeginText(Object /*args*/[], int /*numArgs*/) +{ + state->setTextMat(1, 0, 0, 1, 0, 0); + state->textMoveTo(0, 0); + builder->updateTextPosition(0.0, 0.0); + fontChanged = gTrue; + builder->beginTextObject(state); +} + +void PdfParser::opEndText(Object /*args*/[], int /*numArgs*/) +{ + builder->endTextObject(state); +} + +//------------------------------------------------------------------------ +// text state operators +//------------------------------------------------------------------------ + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::opSetCharSpacing(Object args[], int /*numArgs*/) +{ + state->setCharSpace(args[0].getNum()); +} + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::opSetFont(Object args[], int /*numArgs*/) +{ + auto font = res->lookupFont(args[0].getName()); + + if (!font) { + // unsetting the font (drawing no text) is better than using the + // previous one and drawing random glyphs from it + state->setFont(nullptr, args[1].getNum()); + fontChanged = gTrue; + return; + } + if (printCommands) { + printf(" font: tag=%s name='%s' %g\n", +#if POPPLER_CHECK_VERSION(21,11,0) + font->getTag().c_str(), +#else + font->getTag()->getCString(), +#endif + font->getName() ? font->getName()->getCString() : "???", + args[1].getNum()); + fflush(stdout); + } + +#if !POPPLER_CHECK_VERSION(22, 4, 0) + font->incRefCnt(); +#endif + state->setFont(font, args[1].getNum()); + fontChanged = gTrue; +} + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::opSetTextLeading(Object args[], int /*numArgs*/) +{ + state->setLeading(args[0].getNum()); +} + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::opSetTextRender(Object args[], int /*numArgs*/) +{ + state->setRender(args[0].getInt()); + builder->updateStyle(state); +} + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::opSetTextRise(Object args[], int /*numArgs*/) +{ + state->setRise(args[0].getNum()); +} + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::opSetWordSpacing(Object args[], int /*numArgs*/) +{ + state->setWordSpace(args[0].getNum()); +} + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::opSetHorizScaling(Object args[], int /*numArgs*/) +{ + state->setHorizScaling(args[0].getNum()); + builder->updateTextMatrix(state); + fontChanged = gTrue; +} + +//------------------------------------------------------------------------ +// text positioning operators +//------------------------------------------------------------------------ + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::opTextMove(Object args[], int /*numArgs*/) +{ + double tx, ty; + + tx = state->getLineX() + args[0].getNum(); + ty = state->getLineY() + args[1].getNum(); + state->textMoveTo(tx, ty); + builder->updateTextPosition(tx, ty); +} + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::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); + builder->updateTextPosition(tx, ty); +} + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::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); + builder->updateTextMatrix(state); + builder->updateTextPosition(0.0, 0.0); + fontChanged = gTrue; +} + +void PdfParser::opTextNextLine(Object /*args*/[], int /*numArgs*/) +{ + double tx, ty; + + tx = state->getLineX(); + ty = state->getLineY() - state->getLeading(); + state->textMoveTo(tx, ty); + builder->updateTextPosition(tx, ty); +} + +//------------------------------------------------------------------------ +// text string operators +//------------------------------------------------------------------------ + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::opShowText(Object args[], int /*numArgs*/) +{ + if (!state->getFont()) { + error(errSyntaxError, getPos(), "No font in show"); + return; + } + if (fontChanged) { + builder->updateFont(state); + fontChanged = gFalse; + } + doShowText(args[0].getString()); +} + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::opMoveShowText(Object args[], int /*numArgs*/) +{ + double tx = 0; + double ty = 0; + + if (!state->getFont()) { + error(errSyntaxError, getPos(), "No font in move/show"); + return; + } + if (fontChanged) { + builder->updateFont(state); + fontChanged = gFalse; + } + tx = state->getLineX(); + ty = state->getLineY() - state->getLeading(); + state->textMoveTo(tx, ty); + builder->updateTextPosition(tx, ty); + doShowText(args[0].getString()); +} + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::opMoveSetShowText(Object args[], int /*numArgs*/) +{ + double tx = 0; + double ty = 0; + + if (!state->getFont()) { + error(errSyntaxError, getPos(), "No font in move/set/show"); + return; + } + if (fontChanged) { + builder->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); + builder->updateTextPosition(tx, ty); + doShowText(args[2].getString()); +} + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::opShowSpaceText(Object args[], int /*numArgs*/) +{ + Array *a = nullptr; + Object obj; + int wMode = 0; + + if (!state->getFont()) { + error(errSyntaxError, getPos(), "No font in show/space"); + return; + } + if (fontChanged) { + builder->updateFont(state); + fontChanged = gFalse; + } + wMode = state->getFont()->getWMode(); + a = args[0].getArray(); + for (int i = 0; i < a->getLength(); ++i) { + _POPPLER_CALL_ARGS(obj, a->get, i); + if (obj.isNum()) { + // this uses the absolute value of the font size to match + // Acrobat's behavior + if (wMode) { + state->textShift(0, -obj.getNum() * 0.001 * + fabs(state->getFontSize())); + } else { + state->textShift(-obj.getNum() * 0.001 * + fabs(state->getFontSize()), 0); + } + builder->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"); + } + _POPPLER_FREE(obj); + } +} + +#if POPPLER_CHECK_VERSION(0,64,0) +void PdfParser::doShowText(const GooString *s) { +#else +void PdfParser::doShowText(GooString *s) { +#endif + int wMode; + double riseX, riseY; + CharCode code; + Unicode _POPPLER_CONST_82 *u = nullptr; + double x, y, dx, dy, tdx, tdy; + double originX, originY, tOriginX, tOriginY; + double oldCTM[6], newCTM[6]; + const double *mat; + Object charProc; + Dict *resDict; + Parser *oldParser; +#if POPPLER_CHECK_VERSION(0,64,0) + const char *p; +#else + char *p; +#endif + int len, n, uLen; + + auto font = state->getFont(); + wMode = font->getWMode(); + + builder->beginString(state); + + // handle a Type 3 char + if (font->getType() == fontType3 && false) {//out->interpretType3Chars()) { + mat = state->getCTM(); + for (int 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(); + state->textTransformDelta(0, state->getRise(), &riseX, &riseY); + double curX = state->getCurX(); + double curY = state->getCurY(); + double lineX = state->getLineX(); + double lineY = state->getLineY(); + oldParser = parser; + p = s->getCString(); + len = s->getLength(); + while (len > 0) { + n = font->getNextChar(p, len, &code, + &u, &uLen, /* TODO: This looks like a memory leak for u. */ + &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); + saveState(); + 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); + if (false){ /*!out->beginType3Char(state, curX + riseX, curY + riseY, tdx, tdy, + code, u, uLen)) {*/ + _POPPLER_CALL_ARGS(charProc, _POPPLER_FONTPTR_TO_GFX8(font)->getCharProc, code); + if (resDict = _POPPLER_FONTPTR_TO_GFX8(font)->getResources()) { + pushResources(resDict); + } + if (charProc.isStream()) { + //parse(&charProc, gFalse); // TODO: parse into SVG font + } else { + error(errSyntaxError, getPos(), "Missing or bad Type3 CharProc entry"); + } + //out->endType3Char(state); + if (resDict) { + popResources(); + } + _POPPLER_FREE(charProc); + } + restoreState(); + // GfxState::restore() does *not* restore the current position, + // so we deal with it here using (curX, curY) and (lineX, lineY) + curX += tdx; + curY += tdy; + state->moveTo(curX, curY); + state->textSetPos(lineX, lineY); + p += n; + len -= n; + } + parser = oldParser; + + } else { + state->textTransformDelta(0, state->getRise(), &riseX, &riseY); + p = s->getCString(); + len = s->getLength(); + while (len > 0) { + n = font->getNextChar(p, len, &code, + &u, &uLen, /* TODO: This looks like a memory leak for u. */ + &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); + builder->addChar(state, state->getCurX() + riseX, state->getCurY() + riseY, + dx, dy, tOriginX, tOriginY, code, n, u, uLen); + state->shift(tdx, tdy); + p += n; + len -= n; + } + } + + builder->endString(state); +} + + +//------------------------------------------------------------------------ +// XObject operators +//------------------------------------------------------------------------ + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::opXObject(Object args[], int /*numArgs*/) +{ + Object obj1, obj2, obj3, refObj; + +#if POPPLER_CHECK_VERSION(0,64,0) + const char *name = args[0].getName(); +#else + char *name = args[0].getName(); +#endif + _POPPLER_CALL_ARGS(obj1, res->lookupXObject, name); + if (obj1.isNull()) { + return; + } + if (!obj1.isStream()) { + error(errSyntaxError, getPos(), "XObject '{0:s}' is wrong type", name); + _POPPLER_FREE(obj1); + return; + } + _POPPLER_CALL_ARGS(obj2, obj1.streamGetDict()->lookup, "Subtype"); + if (obj2.isName(const_cast("Image"))) { + _POPPLER_CALL_ARGS(refObj, res->lookupXObjectNF, name); + doImage(&refObj, obj1.getStream(), gFalse); + _POPPLER_FREE(refObj); + } else if (obj2.isName(const_cast("Form"))) { + doForm(&obj1); + } else if (obj2.isName(const_cast("PS"))) { + _POPPLER_CALL_ARGS(obj3, obj1.streamGetDict()->lookup, "Level1"); +/* 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"); + } + _POPPLER_FREE(obj2); + _POPPLER_FREE(obj1); +} + +void PdfParser::doImage(Object * /*ref*/, Stream *str, GBool inlineImg) +{ + Dict *dict; + int width, height; + int bits; + GBool interpolate; + StreamColorSpaceMode csMode; + GBool mask; + GBool invert; + Object maskObj, smaskObj; + GBool haveColorKeyMask, haveExplicitMask, haveSoftMask; + GBool maskInvert; + GBool maskInterpolate; + Object obj1, obj2; + + // get info from the stream + bits = 0; + csMode = streamCSNone; + str->getImageParams(&bits, &csMode); + + // get stream dict + dict = str->getDict(); + + // get size + _POPPLER_CALL_ARGS(obj1, dict->lookup, "Width"); + if (obj1.isNull()) { + _POPPLER_FREE(obj1); + _POPPLER_CALL_ARGS(obj1, dict->lookup, "W"); + } + if (obj1.isInt()){ + width = obj1.getInt(); + } + else if (obj1.isReal()) { + width = (int)obj1.getReal(); + } + else { + goto err2; + } + _POPPLER_FREE(obj1); + _POPPLER_CALL_ARGS(obj1, dict->lookup, "Height"); + if (obj1.isNull()) { + _POPPLER_FREE(obj1); + _POPPLER_CALL_ARGS(obj1, dict->lookup, "H"); + } + if (obj1.isInt()) { + height = obj1.getInt(); + } + else if (obj1.isReal()){ + height = static_cast(obj1.getReal()); + } + else { + goto err2; + } + _POPPLER_FREE(obj1); + + // image interpolation + _POPPLER_CALL_ARGS(obj1, dict->lookup, "Interpolate"); + if (obj1.isNull()) { + _POPPLER_FREE(obj1); + _POPPLER_CALL_ARGS(obj1, dict->lookup, "I"); + } + if (obj1.isBool()) + interpolate = obj1.getBool(); + else + interpolate = gFalse; + _POPPLER_FREE(obj1); + maskInterpolate = gFalse; + + // image or mask? + _POPPLER_CALL_ARGS(obj1, dict->lookup, "ImageMask"); + if (obj1.isNull()) { + _POPPLER_FREE(obj1); + _POPPLER_CALL_ARGS(obj1, dict->lookup, "IM"); + } + mask = gFalse; + if (obj1.isBool()) { + mask = obj1.getBool(); + } + else if (!obj1.isNull()) { + goto err2; + } + _POPPLER_FREE(obj1); + + // bit depth + if (bits == 0) { + _POPPLER_CALL_ARGS(obj1, dict->lookup, "BitsPerComponent"); + if (obj1.isNull()) { + _POPPLER_FREE(obj1); + _POPPLER_CALL_ARGS(obj1, dict->lookup, "BPC"); + } + if (obj1.isInt()) { + bits = obj1.getInt(); + } else if (mask) { + bits = 1; + } else { + goto err2; + } + _POPPLER_FREE(obj1); + } + + // display a mask + if (mask) { + // check for inverted mask + if (bits != 1) { + goto err1; + } + invert = gFalse; + _POPPLER_CALL_ARGS(obj1, dict->lookup, "Decode"); + if (obj1.isNull()) { + _POPPLER_FREE(obj1); + _POPPLER_CALL_ARGS(obj1, dict->lookup, "D"); + } + if (obj1.isArray()) { + _POPPLER_CALL_ARGS(obj2, obj1.arrayGet, 0); + if (obj2.isInt() && obj2.getInt() == 1) { + invert = gTrue; + } + _POPPLER_FREE(obj2); + } else if (!obj1.isNull()) { + goto err2; + } + _POPPLER_FREE(obj1); + + // draw it + builder->addImageMask(state, str, width, height, invert, interpolate); + + } else { + // get color space and color map + GfxColorSpace *colorSpace; + _POPPLER_CALL_ARGS(obj1, dict->lookup, "ColorSpace"); + if (obj1.isNull()) { + _POPPLER_FREE(obj1); + _POPPLER_CALL_ARGS(obj1, dict->lookup, "CS"); + } + if (!obj1.isNull()) { + colorSpace = lookupColorSpaceCopy(obj1); + } else if (csMode == streamCSDeviceGray) { + colorSpace = new GfxDeviceGrayColorSpace(); + } else if (csMode == streamCSDeviceRGB) { + colorSpace = new GfxDeviceRGBColorSpace(); + } else if (csMode == streamCSDeviceCMYK) { + colorSpace = new GfxDeviceCMYKColorSpace(); + } else { + colorSpace = nullptr; + } + _POPPLER_FREE(obj1); + if (!colorSpace) { + goto err1; + } + _POPPLER_CALL_ARGS(obj1, dict->lookup, "Decode"); + if (obj1.isNull()) { + _POPPLER_FREE(obj1); + _POPPLER_CALL_ARGS(obj1, dict->lookup, "D"); + } + GfxImageColorMap *colorMap = new GfxImageColorMap(bits, &obj1, colorSpace); + _POPPLER_FREE(obj1); + if (!colorMap->isOk()) { + delete colorMap; + goto err1; + } + + // get the mask + int maskColors[2*gfxColorMaxComps]; + haveColorKeyMask = haveExplicitMask = haveSoftMask = gFalse; + Stream *maskStr = nullptr; + int maskWidth = 0; + int maskHeight = 0; + maskInvert = gFalse; + GfxImageColorMap *maskColorMap = nullptr; + _POPPLER_CALL_ARGS(maskObj, dict->lookup, "Mask"); + _POPPLER_CALL_ARGS(smaskObj, dict->lookup, "SMask"); + Dict* maskDict; + if (smaskObj.isStream()) { + // soft mask + if (inlineImg) { + goto err1; + } + maskStr = smaskObj.getStream(); + maskDict = smaskObj.streamGetDict(); + _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "Width"); + if (obj1.isNull()) { + _POPPLER_FREE(obj1); + _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "W"); + } + if (!obj1.isInt()) { + goto err2; + } + maskWidth = obj1.getInt(); + _POPPLER_FREE(obj1); + _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "Height"); + if (obj1.isNull()) { + _POPPLER_FREE(obj1); + _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "H"); + } + if (!obj1.isInt()) { + goto err2; + } + maskHeight = obj1.getInt(); + _POPPLER_FREE(obj1); + _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "BitsPerComponent"); + if (obj1.isNull()) { + _POPPLER_FREE(obj1); + _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "BPC"); + } + if (!obj1.isInt()) { + goto err2; + } + int maskBits = obj1.getInt(); + _POPPLER_FREE(obj1); + _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "Interpolate"); + if (obj1.isNull()) { + _POPPLER_FREE(obj1); + _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "I"); + } + if (obj1.isBool()) + maskInterpolate = obj1.getBool(); + else + maskInterpolate = gFalse; + _POPPLER_FREE(obj1); + _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "ColorSpace"); + if (obj1.isNull()) { + _POPPLER_FREE(obj1); + _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "CS"); + } + GfxColorSpace *maskColorSpace = lookupColorSpaceCopy(obj1); + _POPPLER_FREE(obj1); + if (!maskColorSpace || maskColorSpace->getMode() != csDeviceGray) { + goto err1; + } + _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "Decode"); + if (obj1.isNull()) { + _POPPLER_FREE(obj1); + _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "D"); + } + maskColorMap = new GfxImageColorMap(maskBits, &obj1, maskColorSpace); + _POPPLER_FREE(obj1); + if (!maskColorMap->isOk()) { + delete maskColorMap; + goto err1; + } + //~ handle the Matte entry + haveSoftMask = gTrue; + } else if (maskObj.isArray()) { + // color key mask + int i; + for (i = 0; i < maskObj.arrayGetLength() && i < 2*gfxColorMaxComps; ++i) { + _POPPLER_CALL_ARGS(obj1, maskObj.arrayGet, i); + maskColors[i] = obj1.getInt(); + _POPPLER_FREE(obj1); + } + haveColorKeyMask = gTrue; + } else if (maskObj.isStream()) { + // explicit mask + if (inlineImg) { + goto err1; + } + maskStr = maskObj.getStream(); + maskDict = maskObj.streamGetDict(); + _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "Width"); + if (obj1.isNull()) { + _POPPLER_FREE(obj1); + _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "W"); + } + if (!obj1.isInt()) { + goto err2; + } + maskWidth = obj1.getInt(); + _POPPLER_FREE(obj1); + _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "Height"); + if (obj1.isNull()) { + _POPPLER_FREE(obj1); + _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "H"); + } + if (!obj1.isInt()) { + goto err2; + } + maskHeight = obj1.getInt(); + _POPPLER_FREE(obj1); + _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "ImageMask"); + if (obj1.isNull()) { + _POPPLER_FREE(obj1); + _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "IM"); + } + if (!obj1.isBool() || !obj1.getBool()) { + goto err2; + } + _POPPLER_FREE(obj1); + _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "Interpolate"); + if (obj1.isNull()) { + _POPPLER_FREE(obj1); + _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "I"); + } + if (obj1.isBool()) + maskInterpolate = obj1.getBool(); + else + maskInterpolate = gFalse; + _POPPLER_FREE(obj1); + maskInvert = gFalse; + _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "Decode"); + if (obj1.isNull()) { + _POPPLER_FREE(obj1); + _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "D"); + } + if (obj1.isArray()) { + _POPPLER_CALL_ARGS(obj2, obj1.arrayGet, 0); + if (obj2.isInt() && obj2.getInt() == 1) { + maskInvert = gTrue; + } + _POPPLER_FREE(obj2); + } else if (!obj1.isNull()) { + goto err2; + } + _POPPLER_FREE(obj1); + haveExplicitMask = gTrue; + } + + // draw it + if (haveSoftMask) { + builder->addSoftMaskedImage(state, str, width, height, colorMap, interpolate, + maskStr, maskWidth, maskHeight, maskColorMap, maskInterpolate); + delete maskColorMap; + } else if (haveExplicitMask) { + builder->addMaskedImage(state, str, width, height, colorMap, interpolate, + maskStr, maskWidth, maskHeight, maskInvert, maskInterpolate); + } else { + builder->addImage(state, str, width, height, colorMap, interpolate, + haveColorKeyMask ? maskColors : static_cast(nullptr)); + } + delete colorMap; + + _POPPLER_FREE(maskObj); + _POPPLER_FREE(smaskObj); + } + + return; + + err2: + _POPPLER_FREE(obj1); + err1: + error(errSyntaxError, getPos(), "Bad image parameters"); +} + +void PdfParser::doForm(Object *str) { + Dict *dict; + GBool transpGroup, isolated, knockout; + GfxColorSpace *blendingColorSpace; + Object matrixObj, bboxObj; + double m[6], bbox[4]; + Object resObj; + Dict *resDict; + Object obj1, obj2, obj3; + int i; + + // check for excessive recursion + if (formDepth > 20) { + return; + } + + // get stream dict + dict = str->streamGetDict(); + + // check form type + _POPPLER_CALL_ARGS(obj1, dict->lookup, "FormType"); + if (!(obj1.isNull() || (obj1.isInt() && obj1.getInt() == 1))) { + error(errSyntaxError, getPos(), "Unknown form type"); + } + _POPPLER_FREE(obj1); + + // get bounding box + _POPPLER_CALL_ARGS(bboxObj, dict->lookup, "BBox"); + if (!bboxObj.isArray()) { + _POPPLER_FREE(bboxObj); + error(errSyntaxError, getPos(), "Bad form bounding box"); + return; + } + for (i = 0; i < 4; ++i) { + _POPPLER_CALL_ARGS(obj1, bboxObj.arrayGet, i); + bbox[i] = obj1.getNum(); + _POPPLER_FREE(obj1); + } + _POPPLER_FREE(bboxObj); + + // get matrix + _POPPLER_CALL_ARGS(matrixObj, dict->lookup, "Matrix"); + if (matrixObj.isArray()) { + for (i = 0; i < 6; ++i) { + _POPPLER_CALL_ARGS(obj1, matrixObj.arrayGet, i); + m[i] = obj1.getNum(); + _POPPLER_FREE(obj1); + } + } else { + m[0] = 1; m[1] = 0; + m[2] = 0; m[3] = 1; + m[4] = 0; m[5] = 0; + } + _POPPLER_FREE(matrixObj); + + // get resources + _POPPLER_CALL_ARGS(resObj, dict->lookup, "Resources"); + resDict = resObj.isDict() ? resObj.getDict() : (Dict *)nullptr; + + // check for a transparency group + transpGroup = isolated = knockout = gFalse; + blendingColorSpace = nullptr; + if (_POPPLER_CALL_ARGS_DEREF(obj1, dict->lookup, "Group").isDict()) { + if (_POPPLER_CALL_ARGS_DEREF(obj2, obj1.dictLookup, "S").isName("Transparency")) { + transpGroup = gTrue; + if (!_POPPLER_CALL_ARGS_DEREF(obj3, obj1.dictLookup, "CS").isNull()) { + blendingColorSpace = GfxColorSpace::parse(nullptr, &obj3, nullptr, state); + } + _POPPLER_FREE(obj3); + if (_POPPLER_CALL_ARGS_DEREF(obj3, obj1.dictLookup, "I").isBool()) { + isolated = obj3.getBool(); + } + _POPPLER_FREE(obj3); + if (_POPPLER_CALL_ARGS_DEREF(obj3, obj1.dictLookup, "K").isBool()) { + knockout = obj3.getBool(); + } + _POPPLER_FREE(obj3); + } + _POPPLER_FREE(obj2); + } + _POPPLER_FREE(obj1); + + // draw it + ++formDepth; + doForm1(str, resDict, m, bbox, + transpGroup, gFalse, blendingColorSpace, isolated, knockout); + --formDepth; + + if (blendingColorSpace) { + delete blendingColorSpace; + } + _POPPLER_FREE(resObj); +} + +void PdfParser::doForm1(Object *str, Dict *resDict, double *matrix, double *bbox, + GBool transpGroup, GBool softMask, + GfxColorSpace *blendingColorSpace, + GBool isolated, GBool knockout, + GBool alpha, Function *transferFunc, + GfxColor *backdropColor) { + Parser *oldParser; + double oldBaseMatrix[6]; + int i; + + // push new resources on stack + pushResources(resDict); + + // save current graphics state + saveState(); + + // kill any pre-existing path + state->clearPath(); + + if (softMask || transpGroup) { + builder->clearSoftMask(state); + builder->pushTransparencyGroup(state, bbox, blendingColorSpace, + isolated, knockout, softMask); + } + + // save current parser + oldParser = parser; + + // set form transformation matrix + state->concatCTM(matrix[0], matrix[1], matrix[2], + matrix[3], matrix[4], matrix[5]); + builder->setTransform(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(); + clipHistory->setClip(state->getPath()); + builder->clip(state); + state->clearPath(); + + if (softMask || transpGroup) { + if (state->getBlendMode() != gfxBlendNormal) { + state->setBlendMode(gfxBlendNormal); + } + if (state->getFillOpacity() != 1) { + builder->setGroupOpacity(state->getFillOpacity()); + state->setFillOpacity(1); + } + if (state->getStrokeOpacity() != 1) { + state->setStrokeOpacity(1); + } + } + + // set new base matrix + for (i = 0; i < 6; ++i) { + oldBaseMatrix[i] = baseMatrix[i]; + baseMatrix[i] = state->getCTM()[i]; + } + + // draw the form + parse(str, gFalse); + + // restore base matrix + for (i = 0; i < 6; ++i) { + baseMatrix[i] = oldBaseMatrix[i]; + } + + // restore parser + parser = oldParser; + + if (softMask || transpGroup) { + builder->popTransparencyGroup(state); + } + + // restore graphics state + restoreState(); + + // pop resource stack + popResources(); + + if (softMask) { + builder->setSoftMask(state, bbox, alpha, transferFunc, backdropColor); + } else if (transpGroup) { + builder->paintTransparencyGroup(state, bbox); + } + + return; +} + +//------------------------------------------------------------------------ +// in-line image operators +//------------------------------------------------------------------------ + +void PdfParser::opBeginImage(Object /*args*/[], int /*numArgs*/) +{ + // build dict/stream + Stream *str = buildImageStream(); + + // display the image + if (str) { + doImage(nullptr, str, gTrue); + + // skip 'EI' tag + int c1 = str->getUndecodedStream()->getChar(); + int c2 = str->getUndecodedStream()->getChar(); + while (!(c1 == 'E' && c2 == 'I') && c2 != EOF) { + c1 = c2; + c2 = str->getUndecodedStream()->getChar(); + } + delete str; + } +} + +Stream *PdfParser::buildImageStream() { + Object dict; + Object obj; + Stream *str; + + // build dictionary +#if defined(POPPLER_NEW_OBJECT_API) + dict = Object(new Dict(xref)); +#else + dict.initDict(xref); +#endif + _POPPLER_CALL(obj, parser->getObj); + while (!obj.isCmd(const_cast("ID")) && !obj.isEOF()) { + if (!obj.isName()) { + error(errSyntaxError, getPos(), "Inline image dictionary key must be a name object"); + _POPPLER_FREE(obj); + } else { + Object obj2; + _POPPLER_CALL(obj2, parser->getObj); + if (obj2.isEOF() || obj2.isError()) { + _POPPLER_FREE(obj); + break; + } + _POPPLER_DICTADD(dict, obj.getName(), obj2); + _POPPLER_FREE(obj); + _POPPLER_FREE(obj2); + } + _POPPLER_CALL(obj, parser->getObj); + } + if (obj.isEOF()) { + error(errSyntaxError, getPos(), "End of file in inline image"); + _POPPLER_FREE(obj); + _POPPLER_FREE(dict); + return nullptr; + } + _POPPLER_FREE(obj); + + // make stream +#if defined(POPPLER_NEW_OBJECT_API) + str = new EmbedStream(parser->getStream(), dict.copy(), gFalse, 0); + str = str->addFilters(dict.getDict()); +#else + str = new EmbedStream(parser->getStream(), &dict, gFalse, 0); + str = str->addFilters(&dict); +#endif + + return str; +} + +void PdfParser::opImageData(Object /*args*/[], int /*numArgs*/) +{ + error(errInternal, getPos(), "Internal: got 'ID' operator"); +} + +void PdfParser::opEndImage(Object /*args*/[], int /*numArgs*/) +{ + error(errInternal, getPos(), "Internal: got 'EI' operator"); +} + +//------------------------------------------------------------------------ +// type 3 font operators +//------------------------------------------------------------------------ + +void PdfParser::opSetCharWidth(Object /*args*/[], int /*numArgs*/) +{ +} + +void PdfParser::opSetCacheDevice(Object /*args*/[], int /*numArgs*/) +{ +} + +//------------------------------------------------------------------------ +// compatibility operators +//------------------------------------------------------------------------ + +void PdfParser::opBeginIgnoreUndef(Object /*args*/[], int /*numArgs*/) +{ + ++ignoreUndef; +} + +void PdfParser::opEndIgnoreUndef(Object /*args*/[], int /*numArgs*/) +{ + if (ignoreUndef > 0) + --ignoreUndef; +} + +//------------------------------------------------------------------------ +// marked content operators +//------------------------------------------------------------------------ + +void PdfParser::opBeginMarkedContent(Object args[], int numArgs) { + if (printCommands) { + printf(" marked content: %s ", args[0].getName()); + if (numArgs == 2) + args[2].print(stdout); + printf("\n"); + fflush(stdout); + } + + if(numArgs == 2) { + //out->beginMarkedContent(args[0].getName(),args[1].getDict()); + } else { + //out->beginMarkedContent(args[0].getName()); + } +} + +void PdfParser::opEndMarkedContent(Object /*args*/[], int /*numArgs*/) +{ + //out->endMarkedContent(); +} + +void PdfParser::opMarkPoint(Object args[], int numArgs) { + if (printCommands) { + printf(" mark point: %s ", args[0].getName()); + if (numArgs == 2) + args[2].print(stdout); + printf("\n"); + fflush(stdout); + } + + if(numArgs == 2) { + //out->markPoint(args[0].getName(),args[1].getDict()); + } else { + //out->markPoint(args[0].getName()); + } + +} + +//------------------------------------------------------------------------ +// misc +//------------------------------------------------------------------------ + +void PdfParser::saveState() { + bool is_radial = false; + + GfxPattern *pattern = state->getFillPattern(); + if (pattern != nullptr) + if (pattern->getType() == 2 ) { + GfxShadingPattern *shading_pattern = static_cast(pattern); + GfxShading *shading = shading_pattern->getShading(); + if (shading->getType() == 3) + is_radial = true; + } + + builder->saveState(); + if (is_radial) + state->save(); // nasty hack to prevent GfxRadialShading from getting corrupted during copy operation + else + state = state->save(); // see LP Bug 919176 comment 8 + clipHistory = clipHistory->save(); +} + +void PdfParser::restoreState() { + clipHistory = clipHistory->restore(); + state = state->restore(); + builder->restoreState(); +} + +void PdfParser::pushResources(Dict *resDict) { + res = new GfxResources(xref, resDict, res); +} + +void PdfParser::popResources() { + GfxResources *resPtr; + + resPtr = res->getNext(); + delete res; + res = resPtr; +} + +void PdfParser::setDefaultApproximationPrecision() { + for (int i = 1; i <= pdfNumShadingTypes; ++i) { + setApproximationPrecision(i, defaultShadingColorDelta, defaultShadingMaxDepth); + } +} + +void PdfParser::setApproximationPrecision(int shadingType, double colorDelta, + int maxDepth) { + + if (shadingType > pdfNumShadingTypes || shadingType < 1) { + return; + } + colorDeltas[shadingType-1] = dblToCol(colorDelta); + maxDepths[shadingType-1] = maxDepth; +} + +//------------------------------------------------------------------------ +// ClipHistoryEntry +//------------------------------------------------------------------------ + +ClipHistoryEntry::ClipHistoryEntry(GfxPath *clipPathA, GfxClipType clipTypeA) : + saved(nullptr), + clipPath((clipPathA) ? clipPathA->copy() : nullptr), + clipType(clipTypeA) +{ +} + +ClipHistoryEntry::~ClipHistoryEntry() +{ + if (clipPath) { + delete clipPath; + clipPath = nullptr; + } +} + +void ClipHistoryEntry::setClip(_POPPLER_CONST_83 GfxPath *clipPathA, GfxClipType clipTypeA) { + // Free previous clip path + if (clipPath) { + delete clipPath; + } + if (clipPathA) { + clipPath = clipPathA->copy(); + clipType = clipTypeA; + } else { + clipPath = nullptr; + clipType = clipNormal; + } +} + +ClipHistoryEntry *ClipHistoryEntry::save() { + ClipHistoryEntry *newEntry = new ClipHistoryEntry(this); + newEntry->saved = this; + + return newEntry; +} + +ClipHistoryEntry *ClipHistoryEntry::restore() { + ClipHistoryEntry *oldEntry; + + if (saved) { + oldEntry = saved; + saved = nullptr; + delete this; // TODO really should avoid deleting from inside. + } else { + oldEntry = this; + } + + return oldEntry; +} + +ClipHistoryEntry::ClipHistoryEntry(ClipHistoryEntry *other) { + if (other->clipPath) { + this->clipPath = other->clipPath->copy(); + this->clipType = other->clipType; + } else { + this->clipPath = nullptr; + this->clipType = clipNormal; + } + saved = nullptr; +} + +#endif /* HAVE_POPPLER */ -- cgit v1.2.3