// 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 */