| //======================================================================== |
| // |
| // Function.cc |
| // |
| // Copyright 2001-2003 Glyph & Cog, LLC |
| // |
| //======================================================================== |
| |
| //======================================================================== |
| // |
| // Modified under the Poppler project - http://poppler.freedesktop.org |
| // |
| // All changes made under the Poppler project to this file are licensed |
| // under GPL version 2 or later |
| // |
| // Copyright (C) 2006, 2008-2010, 2013-2015, 2017-2020, 2022-2024 Albert Astals Cid <aacid@kde.org> |
| // Copyright (C) 2006 Jeff Muizelaar <jeff@infidigm.net> |
| // Copyright (C) 2010 Christian Feuersänger <cfeuersaenger@googlemail.com> |
| // Copyright (C) 2011 Andrea Canciani <ranma42@gmail.com> |
| // Copyright (C) 2012 Thomas Freitag <Thomas.Freitag@alfa.de> |
| // Copyright (C) 2012 Adam Reichold <adamreichold@myopera.com> |
| // Copyright (C) 2013 Fabio D'Urso <fabiodurso@hotmail.it> |
| // |
| // To see a description of the changes please see the Changelog file that |
| // came with your tarball or type make ChangeLog if you are building from git |
| // |
| //======================================================================== |
| |
| #include <config.h> |
| |
| #include <cstdlib> |
| #include <cstring> |
| #include <cctype> |
| #include <cmath> |
| #include "goo/gmem.h" |
| #include "goo/gstrtod.h" |
| #include "Object.h" |
| #include "Dict.h" |
| #include "Stream.h" |
| #include "Error.h" |
| #include "Function.h" |
| |
| #ifndef M_PI |
| # define M_PI 3.14159265358979323846 |
| #endif |
| |
| //------------------------------------------------------------------------ |
| // Function |
| //------------------------------------------------------------------------ |
| |
| Function::Function() : domain {} { } |
| |
| Function::~Function() { } |
| |
| Function *Function::parse(Object *funcObj) |
| { |
| std::set<int> usedParents; |
| return parse(funcObj, &usedParents); |
| } |
| |
| Function *Function::parse(Object *funcObj, std::set<int> *usedParents) |
| { |
| Function *func; |
| Dict *dict; |
| int funcType; |
| |
| if (funcObj->isStream()) { |
| dict = funcObj->streamGetDict(); |
| } else if (funcObj->isDict()) { |
| dict = funcObj->getDict(); |
| } else if (funcObj->isName("Identity")) { |
| return new IdentityFunction(); |
| } else { |
| error(errSyntaxError, -1, "Expected function dictionary or stream"); |
| return nullptr; |
| } |
| |
| Object obj1 = dict->lookup("FunctionType"); |
| if (!obj1.isInt()) { |
| error(errSyntaxError, -1, "Function type is missing or wrong type"); |
| return nullptr; |
| } |
| funcType = obj1.getInt(); |
| |
| if (funcType == 0) { |
| func = new SampledFunction(funcObj, dict); |
| } else if (funcType == 2) { |
| func = new ExponentialFunction(funcObj, dict); |
| } else if (funcType == 3) { |
| func = new StitchingFunction(funcObj, dict, usedParents); |
| } else if (funcType == 4) { |
| func = new PostScriptFunction(funcObj, dict); |
| } else { |
| error(errSyntaxError, -1, "Unimplemented function type ({0:d})", funcType); |
| return nullptr; |
| } |
| if (!func->isOk()) { |
| delete func; |
| return nullptr; |
| } |
| |
| return func; |
| } |
| |
| Function::Function(const Function *func) |
| { |
| m = func->m; |
| n = func->n; |
| |
| memcpy(domain, func->domain, funcMaxInputs * 2 * sizeof(double)); |
| memcpy(range, func->range, funcMaxOutputs * 2 * sizeof(double)); |
| |
| hasRange = func->hasRange; |
| } |
| |
| bool Function::init(Dict *dict) |
| { |
| Object obj1; |
| int i; |
| |
| //----- Domain |
| obj1 = dict->lookup("Domain"); |
| if (!obj1.isArray()) { |
| error(errSyntaxError, -1, "Function is missing domain"); |
| return false; |
| } |
| m = obj1.arrayGetLength() / 2; |
| if (m > funcMaxInputs) { |
| error(errSyntaxError, -1, "Functions with more than {0:d} inputs are unsupported", funcMaxInputs); |
| return false; |
| } |
| for (i = 0; i < m; ++i) { |
| Object obj2 = obj1.arrayGet(2 * i); |
| if (!obj2.isNum()) { |
| error(errSyntaxError, -1, "Illegal value in function domain array"); |
| return false; |
| } |
| domain[i][0] = obj2.getNum(); |
| obj2 = obj1.arrayGet(2 * i + 1); |
| if (!obj2.isNum()) { |
| error(errSyntaxError, -1, "Illegal value in function domain array"); |
| return false; |
| } |
| domain[i][1] = obj2.getNum(); |
| } |
| |
| //----- Range |
| hasRange = false; |
| n = 0; |
| obj1 = dict->lookup("Range"); |
| if (obj1.isArray()) { |
| hasRange = true; |
| n = obj1.arrayGetLength() / 2; |
| if (n > funcMaxOutputs) { |
| error(errSyntaxError, -1, "Functions with more than {0:d} outputs are unsupported", funcMaxOutputs); |
| return false; |
| } |
| for (i = 0; i < n; ++i) { |
| Object obj2 = obj1.arrayGet(2 * i); |
| if (!obj2.isNum()) { |
| error(errSyntaxError, -1, "Illegal value in function range array"); |
| return false; |
| } |
| range[i][0] = obj2.getNum(); |
| obj2 = obj1.arrayGet(2 * i + 1); |
| if (!obj2.isNum()) { |
| error(errSyntaxError, -1, "Illegal value in function range array"); |
| return false; |
| } |
| range[i][1] = obj2.getNum(); |
| } |
| } |
| |
| return true; |
| } |
| |
| //------------------------------------------------------------------------ |
| // IdentityFunction |
| //------------------------------------------------------------------------ |
| |
| IdentityFunction::IdentityFunction() |
| { |
| int i; |
| |
| // fill these in with arbitrary values just in case they get used |
| // somewhere |
| m = funcMaxInputs; |
| n = funcMaxOutputs; |
| for (i = 0; i < funcMaxInputs; ++i) { |
| domain[i][0] = 0; |
| domain[i][1] = 1; |
| } |
| hasRange = false; |
| } |
| |
| IdentityFunction::~IdentityFunction() { } |
| |
| void IdentityFunction::transform(const double *in, double *out) const |
| { |
| int i; |
| |
| for (i = 0; i < funcMaxOutputs; ++i) { |
| out[i] = in[i]; |
| } |
| } |
| |
| //------------------------------------------------------------------------ |
| // SampledFunction |
| //------------------------------------------------------------------------ |
| |
| SampledFunction::SampledFunction(Object *funcObj, Dict *dict) : cacheOut {} |
| { |
| Stream *str; |
| int sampleBits; |
| double sampleMul; |
| Object obj1; |
| unsigned int buf, bitMask; |
| int bits; |
| unsigned int s; |
| double in[funcMaxInputs]; |
| int i, j, t, bit, idx; |
| |
| idxOffset = nullptr; |
| samples = nullptr; |
| sBuf = nullptr; |
| ok = false; |
| |
| //----- initialize the generic stuff |
| if (!init(dict)) { |
| return; |
| } |
| if (!hasRange) { |
| error(errSyntaxError, -1, "Type 0 function is missing range"); |
| return; |
| } |
| if (m > sampledFuncMaxInputs) { |
| error(errSyntaxError, -1, "Sampled functions with more than {0:d} inputs are unsupported", sampledFuncMaxInputs); |
| return; |
| } |
| |
| //----- buffer |
| sBuf = (double *)gmallocn(1 << m, sizeof(double)); |
| |
| //----- get the stream |
| if (!funcObj->isStream()) { |
| error(errSyntaxError, -1, "Type 0 function isn't a stream"); |
| return; |
| } |
| str = funcObj->getStream(); |
| |
| //----- Size |
| obj1 = dict->lookup("Size"); |
| if (!obj1.isArray() || obj1.arrayGetLength() != m) { |
| error(errSyntaxError, -1, "Function has missing or invalid size array"); |
| return; |
| } |
| for (i = 0; i < m; ++i) { |
| Object obj2 = obj1.arrayGet(i); |
| if (!obj2.isInt()) { |
| error(errSyntaxError, -1, "Illegal value in function size array"); |
| return; |
| } |
| sampleSize[i] = obj2.getInt(); |
| if (sampleSize[i] <= 0) { |
| error(errSyntaxError, -1, "Illegal non-positive value in function size array"); |
| return; |
| } |
| } |
| idxOffset = (int *)gmallocn(1 << m, sizeof(int)); |
| for (i = 0; i < (1 << m); ++i) { |
| idx = 0; |
| for (j = m - 1, t = i; j >= 1; --j, t <<= 1) { |
| if (sampleSize[j] == 1) { |
| bit = 0; |
| } else { |
| bit = (t >> (m - 1)) & 1; |
| } |
| idx = (idx + bit) * sampleSize[j - 1]; |
| } |
| if (m > 0 && sampleSize[0] == 1) { |
| bit = 0; |
| } else { |
| bit = (t >> (m - 1)) & 1; |
| } |
| idxOffset[i] = (idx + bit) * n; |
| } |
| |
| //----- BitsPerSample |
| obj1 = dict->lookup("BitsPerSample"); |
| if (!obj1.isInt()) { |
| error(errSyntaxError, -1, "Function has missing or invalid BitsPerSample"); |
| return; |
| } |
| sampleBits = obj1.getInt(); |
| if (unlikely(sampleBits < 1 || sampleBits > 32)) { |
| error(errSyntaxError, -1, "Function invalid BitsPerSample"); |
| return; |
| } |
| sampleMul = 1.0 / (pow(2.0, (double)sampleBits) - 1); |
| |
| //----- Encode |
| obj1 = dict->lookup("Encode"); |
| if (obj1.isArray() && obj1.arrayGetLength() == 2 * m) { |
| for (i = 0; i < m; ++i) { |
| Object obj2 = obj1.arrayGet(2 * i); |
| if (!obj2.isNum()) { |
| error(errSyntaxError, -1, "Illegal value in function encode array"); |
| return; |
| } |
| encode[i][0] = obj2.getNum(); |
| obj2 = obj1.arrayGet(2 * i + 1); |
| if (!obj2.isNum()) { |
| error(errSyntaxError, -1, "Illegal value in function encode array"); |
| return; |
| } |
| encode[i][1] = obj2.getNum(); |
| } |
| } else { |
| for (i = 0; i < m; ++i) { |
| encode[i][0] = 0; |
| encode[i][1] = sampleSize[i] - 1; |
| } |
| } |
| for (i = 0; i < m; ++i) { |
| if (unlikely((domain[i][1] - domain[i][0]) == 0)) { |
| error(errSyntaxError, -1, "Illegal value in function domain array"); |
| return; |
| } |
| inputMul[i] = (encode[i][1] - encode[i][0]) / (domain[i][1] - domain[i][0]); |
| } |
| |
| //----- Decode |
| obj1 = dict->lookup("Decode"); |
| if (obj1.isArray() && obj1.arrayGetLength() == 2 * n) { |
| for (i = 0; i < n; ++i) { |
| Object obj2 = obj1.arrayGet(2 * i); |
| if (!obj2.isNum()) { |
| error(errSyntaxError, -1, "Illegal value in function decode array"); |
| return; |
| } |
| decode[i][0] = obj2.getNum(); |
| obj2 = obj1.arrayGet(2 * i + 1); |
| if (!obj2.isNum()) { |
| error(errSyntaxError, -1, "Illegal value in function decode array"); |
| return; |
| } |
| decode[i][1] = obj2.getNum(); |
| } |
| } else { |
| for (i = 0; i < n; ++i) { |
| decode[i][0] = range[i][0]; |
| decode[i][1] = range[i][1]; |
| } |
| } |
| |
| //----- samples |
| nSamples = n; |
| for (i = 0; i < m; ++i) { |
| nSamples *= sampleSize[i]; |
| } |
| samples = (double *)gmallocn_checkoverflow(nSamples, sizeof(double)); |
| if (!samples) { |
| error(errSyntaxError, -1, "Function has invalid number of samples"); |
| return; |
| } |
| buf = 0; |
| bits = 0; |
| bitMask = (1 << sampleBits) - 1; |
| str->reset(); |
| for (i = 0; i < nSamples; ++i) { |
| if (sampleBits == 8) { |
| s = str->getChar(); |
| } else if (sampleBits == 16) { |
| s = str->getChar(); |
| s = (s << 8) + str->getChar(); |
| } else if (sampleBits == 32) { |
| s = str->getChar(); |
| s = (s << 8) + str->getChar(); |
| s = (s << 8) + str->getChar(); |
| s = (s << 8) + str->getChar(); |
| } else { |
| while (bits < sampleBits) { |
| buf = (buf << 8) | (str->getChar() & 0xff); |
| bits += 8; |
| } |
| s = (buf >> (bits - sampleBits)) & bitMask; |
| bits -= sampleBits; |
| } |
| samples[i] = (double)s * sampleMul; |
| } |
| str->close(); |
| |
| // set up the cache |
| for (i = 0; i < m; ++i) { |
| in[i] = domain[i][0]; |
| cacheIn[i] = in[i] - 1; |
| } |
| transform(in, cacheOut); |
| |
| ok = true; |
| } |
| |
| SampledFunction::~SampledFunction() |
| { |
| if (idxOffset) { |
| gfree(idxOffset); |
| } |
| if (samples) { |
| gfree(samples); |
| } |
| if (sBuf) { |
| gfree(sBuf); |
| } |
| } |
| |
| SampledFunction::SampledFunction(const SampledFunction *func) : Function(func) |
| { |
| memcpy(sampleSize, func->sampleSize, funcMaxInputs * sizeof(int)); |
| |
| memcpy(encode, func->encode, funcMaxInputs * 2 * sizeof(double)); |
| memcpy(decode, func->decode, funcMaxOutputs * 2 * sizeof(double)); |
| |
| memcpy(inputMul, func->inputMul, funcMaxInputs * sizeof(double)); |
| |
| nSamples = func->nSamples; |
| |
| idxOffset = (int *)gmallocn(1 << m, sizeof(int)); |
| memcpy(idxOffset, func->idxOffset, (1 << m) * (int)sizeof(int)); |
| |
| samples = (double *)gmallocn(nSamples, sizeof(double)); |
| memcpy(samples, func->samples, nSamples * sizeof(double)); |
| |
| sBuf = (double *)gmallocn(1 << m, sizeof(double)); |
| |
| memcpy(cacheIn, func->cacheIn, funcMaxInputs * sizeof(double)); |
| memcpy(cacheOut, func->cacheOut, funcMaxOutputs * sizeof(double)); |
| |
| ok = func->ok; |
| } |
| |
| void SampledFunction::transform(const double *in, double *out) const |
| { |
| double x; |
| int e[funcMaxInputs]; |
| double efrac0[funcMaxInputs]; |
| double efrac1[funcMaxInputs]; |
| |
| // check the cache |
| bool inCache = true; |
| for (int i = 0; i < m; ++i) { |
| if (in[i] != cacheIn[i]) { |
| inCache = false; |
| break; |
| } |
| } |
| if (inCache) { |
| for (int i = 0; i < n; ++i) { |
| out[i] = cacheOut[i]; |
| } |
| return; |
| } |
| |
| // map input values into sample array |
| for (int i = 0; i < m; ++i) { |
| x = (in[i] - domain[i][0]) * inputMul[i] + encode[i][0]; |
| if (x < 0 || std::isnan(x)) { |
| x = 0; |
| } else if (x > sampleSize[i] - 1) { |
| x = sampleSize[i] - 1; |
| } |
| e[i] = (int)x; |
| if (e[i] == sampleSize[i] - 1 && sampleSize[i] > 1) { |
| // this happens if in[i] = domain[i][1] |
| e[i] = sampleSize[i] - 2; |
| } |
| efrac1[i] = x - e[i]; |
| efrac0[i] = 1 - efrac1[i]; |
| } |
| |
| // compute index for the first sample to be used |
| int idx0 = 0; |
| for (int k = m - 1; k >= 1; --k) { |
| idx0 = (idx0 + e[k]) * sampleSize[k - 1]; |
| } |
| idx0 = (idx0 + e[0]) * n; |
| |
| // for each output, do m-linear interpolation |
| for (int i = 0; i < n; ++i) { |
| |
| // pull 2^m values out of the sample array |
| for (int j = 0; j < (1 << m); ++j) { |
| int idx = idx0 + idxOffset[j] + i; |
| if (likely(idx >= 0 && idx < nSamples)) { |
| sBuf[j] = samples[idx]; |
| } else { |
| sBuf[j] = 0; // TODO Investigate if this is what Adobe does |
| } |
| } |
| |
| // do m sets of interpolations |
| for (int j = 0, t = (1 << m); j < m; ++j, t >>= 1) { |
| for (int k = 0; k < t; k += 2) { |
| sBuf[k >> 1] = efrac0[j] * sBuf[k] + efrac1[j] * sBuf[k + 1]; |
| } |
| } |
| |
| // map output value to range |
| out[i] = sBuf[0] * (decode[i][1] - decode[i][0]) + decode[i][0]; |
| if (out[i] < range[i][0]) { |
| out[i] = range[i][0]; |
| } else if (out[i] > range[i][1]) { |
| out[i] = range[i][1]; |
| } |
| } |
| |
| // save current result in the cache |
| for (int i = 0; i < m; ++i) { |
| cacheIn[i] = in[i]; |
| } |
| for (int i = 0; i < n; ++i) { |
| cacheOut[i] = out[i]; |
| } |
| } |
| |
| bool SampledFunction::hasDifferentResultSet(const Function *func) const |
| { |
| if (func->getType() == Type::Sampled) { |
| SampledFunction *compTo = (SampledFunction *)func; |
| if (compTo->getSampleNumber() != nSamples) { |
| return true; |
| } |
| const double *compSamples = compTo->getSamples(); |
| for (int i = 0; i < nSamples; i++) { |
| if (samples[i] != compSamples[i]) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| //------------------------------------------------------------------------ |
| // ExponentialFunction |
| //------------------------------------------------------------------------ |
| |
| ExponentialFunction::ExponentialFunction(Object *funcObj, Dict *dict) |
| { |
| Object obj1; |
| |
| ok = false; |
| |
| //----- initialize the generic stuff |
| if (!init(dict)) { |
| return; |
| } |
| if (m != 1) { |
| error(errSyntaxError, -1, "Exponential function with more than one input"); |
| return; |
| } |
| |
| //----- C0 |
| obj1 = dict->lookup("C0"); |
| if (obj1.isArray()) { |
| if (hasRange && obj1.arrayGetLength() != n) { |
| error(errSyntaxError, -1, "Function's C0 array is wrong length"); |
| return; |
| } |
| n = obj1.arrayGetLength(); |
| if (unlikely(n > funcMaxOutputs)) { |
| error(errSyntaxError, -1, "Function's C0 array is wrong length"); |
| n = funcMaxOutputs; |
| } |
| for (int i = 0; i < n; ++i) { |
| Object obj2 = obj1.arrayGet(i); |
| if (!obj2.isNum()) { |
| error(errSyntaxError, -1, "Illegal value in function C0 array"); |
| return; |
| } |
| c0[i] = obj2.getNum(); |
| } |
| } else { |
| if (hasRange && n != 1) { |
| error(errSyntaxError, -1, "Function's C0 array is wrong length"); |
| return; |
| } |
| n = 1; |
| c0[0] = 0; |
| } |
| |
| //----- C1 |
| obj1 = dict->lookup("C1"); |
| if (obj1.isArray()) { |
| if (obj1.arrayGetLength() != n) { |
| error(errSyntaxError, -1, "Function's C1 array is wrong length"); |
| return; |
| } |
| for (int i = 0; i < n; ++i) { |
| Object obj2 = obj1.arrayGet(i); |
| if (!obj2.isNum()) { |
| error(errSyntaxError, -1, "Illegal value in function C1 array"); |
| return; |
| } |
| c1[i] = obj2.getNum(); |
| } |
| } else { |
| if (n != 1) { |
| error(errSyntaxError, -1, "Function's C1 array is wrong length"); |
| return; |
| } |
| c1[0] = 1; |
| } |
| |
| //----- N (exponent) |
| obj1 = dict->lookup("N"); |
| if (!obj1.isNum()) { |
| error(errSyntaxError, -1, "Function has missing or invalid N"); |
| return; |
| } |
| e = obj1.getNum(); |
| |
| isLinear = fabs(e - 1.) < 1e-10; |
| ok = true; |
| } |
| |
| ExponentialFunction::~ExponentialFunction() { } |
| |
| ExponentialFunction::ExponentialFunction(const ExponentialFunction *func) : Function(func) |
| { |
| memcpy(c0, func->c0, funcMaxOutputs * sizeof(double)); |
| memcpy(c1, func->c1, funcMaxOutputs * sizeof(double)); |
| |
| e = func->e; |
| isLinear = func->isLinear; |
| ok = func->ok; |
| } |
| |
| void ExponentialFunction::transform(const double *in, double *out) const |
| { |
| double x; |
| int i; |
| |
| if (in[0] < domain[0][0]) { |
| x = domain[0][0]; |
| } else if (in[0] > domain[0][1]) { |
| x = domain[0][1]; |
| } else { |
| x = in[0]; |
| } |
| for (i = 0; i < n; ++i) { |
| out[i] = c0[i] + (isLinear ? x : pow(x, e)) * (c1[i] - c0[i]); |
| if (hasRange) { |
| if (out[i] < range[i][0]) { |
| out[i] = range[i][0]; |
| } else if (out[i] > range[i][1]) { |
| out[i] = range[i][1]; |
| } |
| } |
| } |
| return; |
| } |
| |
| //------------------------------------------------------------------------ |
| // StitchingFunction |
| //------------------------------------------------------------------------ |
| |
| StitchingFunction::StitchingFunction(Object *funcObj, Dict *dict, std::set<int> *usedParents) |
| { |
| Object obj1; |
| int i; |
| |
| ok = false; |
| funcs = nullptr; |
| bounds = nullptr; |
| encode = nullptr; |
| scale = nullptr; |
| |
| //----- initialize the generic stuff |
| if (!init(dict)) { |
| return; |
| } |
| if (m != 1) { |
| error(errSyntaxError, -1, "Stitching function with more than one input"); |
| return; |
| } |
| |
| //----- Functions |
| obj1 = dict->lookup("Functions"); |
| if (!obj1.isArray()) { |
| error(errSyntaxError, -1, "Missing 'Functions' entry in stitching function"); |
| return; |
| } |
| k = obj1.arrayGetLength(); |
| funcs = (Function **)gmallocn(k, sizeof(Function *)); |
| bounds = (double *)gmallocn(k + 1, sizeof(double)); |
| encode = (double *)gmallocn(2 * k, sizeof(double)); |
| scale = (double *)gmallocn(k, sizeof(double)); |
| for (i = 0; i < k; ++i) { |
| funcs[i] = nullptr; |
| } |
| for (i = 0; i < k; ++i) { |
| std::set<int> usedParentsAux = *usedParents; |
| Ref ref; |
| Object obj2 = obj1.getArray()->get(i, &ref); |
| if (ref != Ref::INVALID()) { |
| if (usedParentsAux.find(ref.num) == usedParentsAux.end()) { |
| usedParentsAux.insert(ref.num); |
| } else { |
| return; |
| } |
| } |
| if (!(funcs[i] = Function::parse(&obj2, &usedParentsAux))) { |
| return; |
| } |
| if (funcs[i]->getInputSize() != 1 || (i > 0 && funcs[i]->getOutputSize() != funcs[0]->getOutputSize())) { |
| error(errSyntaxError, -1, "Incompatible subfunctions in stitching function"); |
| return; |
| } |
| } |
| |
| //----- Bounds |
| obj1 = dict->lookup("Bounds"); |
| if (!obj1.isArray() || obj1.arrayGetLength() != k - 1) { |
| error(errSyntaxError, -1, "Missing or invalid 'Bounds' entry in stitching function"); |
| return; |
| } |
| bounds[0] = domain[0][0]; |
| for (i = 1; i < k; ++i) { |
| Object obj2 = obj1.arrayGet(i - 1); |
| if (!obj2.isNum()) { |
| error(errSyntaxError, -1, "Invalid type in 'Bounds' array in stitching function"); |
| return; |
| } |
| bounds[i] = obj2.getNum(); |
| } |
| bounds[k] = domain[0][1]; |
| |
| //----- Encode |
| obj1 = dict->lookup("Encode"); |
| if (!obj1.isArray() || obj1.arrayGetLength() != 2 * k) { |
| error(errSyntaxError, -1, "Missing or invalid 'Encode' entry in stitching function"); |
| return; |
| } |
| for (i = 0; i < 2 * k; ++i) { |
| Object obj2 = obj1.arrayGet(i); |
| if (!obj2.isNum()) { |
| error(errSyntaxError, -1, "Invalid type in 'Encode' array in stitching function"); |
| return; |
| } |
| encode[i] = obj2.getNum(); |
| } |
| |
| //----- pre-compute the scale factors |
| for (i = 0; i < k; ++i) { |
| if (bounds[i] == bounds[i + 1]) { |
| // avoid a divide-by-zero -- in this situation, function i will |
| // never be used anyway |
| scale[i] = 0; |
| } else { |
| scale[i] = (encode[2 * i + 1] - encode[2 * i]) / (bounds[i + 1] - bounds[i]); |
| } |
| } |
| |
| n = funcs[0]->getOutputSize(); |
| ok = true; |
| return; |
| } |
| |
| StitchingFunction::StitchingFunction(const StitchingFunction *func) : Function(func) |
| { |
| k = func->k; |
| |
| funcs = (Function **)gmallocn(k, sizeof(Function *)); |
| for (int i = 0; i < k; ++i) { |
| funcs[i] = func->funcs[i]->copy(); |
| } |
| |
| bounds = (double *)gmallocn(k + 1, sizeof(double)); |
| memcpy(bounds, func->bounds, (k + 1) * sizeof(double)); |
| |
| encode = (double *)gmallocn(2 * k, sizeof(double)); |
| memcpy(encode, func->encode, 2 * k * sizeof(double)); |
| |
| scale = (double *)gmallocn(k, sizeof(double)); |
| memcpy(scale, func->scale, k * sizeof(double)); |
| |
| ok = func->ok; |
| } |
| |
| StitchingFunction::~StitchingFunction() |
| { |
| int i; |
| |
| if (funcs) { |
| for (i = 0; i < k; ++i) { |
| if (funcs[i]) { |
| delete funcs[i]; |
| } |
| } |
| } |
| gfree(funcs); |
| gfree(bounds); |
| gfree(encode); |
| gfree(scale); |
| } |
| |
| void StitchingFunction::transform(const double *in, double *out) const |
| { |
| double x; |
| int i; |
| |
| if (in[0] < domain[0][0]) { |
| x = domain[0][0]; |
| } else if (in[0] > domain[0][1]) { |
| x = domain[0][1]; |
| } else { |
| x = in[0]; |
| } |
| for (i = 0; i < k - 1; ++i) { |
| if (x < bounds[i + 1]) { |
| break; |
| } |
| } |
| x = encode[2 * i] + (x - bounds[i]) * scale[i]; |
| funcs[i]->transform(&x, out); |
| } |
| |
| //------------------------------------------------------------------------ |
| // PostScriptFunction |
| //------------------------------------------------------------------------ |
| |
| enum PSOp |
| { |
| psOpAbs, |
| psOpAdd, |
| psOpAnd, |
| psOpAtan, |
| psOpBitshift, |
| psOpCeiling, |
| psOpCopy, |
| psOpCos, |
| psOpCvi, |
| psOpCvr, |
| psOpDiv, |
| psOpDup, |
| psOpEq, |
| psOpExch, |
| psOpExp, |
| psOpFalse, |
| psOpFloor, |
| psOpGe, |
| psOpGt, |
| psOpIdiv, |
| psOpIndex, |
| psOpLe, |
| psOpLn, |
| psOpLog, |
| psOpLt, |
| psOpMod, |
| psOpMul, |
| psOpNe, |
| psOpNeg, |
| psOpNot, |
| psOpOr, |
| psOpPop, |
| psOpRoll, |
| psOpRound, |
| psOpSin, |
| psOpSqrt, |
| psOpSub, |
| psOpTrue, |
| psOpTruncate, |
| psOpXor, |
| psOpIf, |
| psOpIfelse, |
| psOpReturn |
| }; |
| |
| // Note: 'if' and 'ifelse' are parsed separately. |
| // The rest are listed here in alphabetical order. |
| // The index in this table is equivalent to the entry in PSOp. |
| static const char *psOpNames[] = { "abs", "add", "and", "atan", "bitshift", "ceiling", "copy", "cos", "cvi", "cvr", "div", "dup", "eq", "exch", "exp", "false", "floor", "ge", "gt", "idiv", |
| "index", "le", "ln", "log", "lt", "mod", "mul", "ne", "neg", "not", "or", "pop", "roll", "round", "sin", "sqrt", "sub", "true", "truncate", "xor" }; |
| |
| #define nPSOps (sizeof(psOpNames) / sizeof(char *)) |
| |
| enum PSObjectType |
| { |
| psBool, |
| psInt, |
| psReal, |
| psOperator, |
| psBlock |
| }; |
| |
| // In the code array, 'if'/'ifelse' operators take up three slots |
| // plus space for the code in the subclause(s). |
| // |
| // +---------------------------------+ |
| // | psOperator: psOpIf / psOpIfelse | |
| // +---------------------------------+ |
| // | psBlock: ptr=<A> | |
| // +---------------------------------+ |
| // | psBlock: ptr=<B> | |
| // +---------------------------------+ |
| // | if clause | |
| // | ... | |
| // | psOperator: psOpReturn | |
| // +---------------------------------+ |
| // <A> | else clause | |
| // | ... | |
| // | psOperator: psOpReturn | |
| // +---------------------------------+ |
| // <B> | ... | |
| // |
| // For 'if', pointer <A> is present in the code stream but unused. |
| |
| struct PSObject |
| { |
| PSObjectType type; |
| union { |
| bool booln; // boolean (stack only) |
| int intg; // integer (stack and code) |
| double real; // real (stack and code) |
| PSOp op; // operator (code only) |
| int blk; // if/ifelse block pointer (code only) |
| }; |
| }; |
| |
| #define psStackSize 100 |
| |
| class PSStack |
| { |
| public: |
| PSStack() { sp = psStackSize; } |
| void clear() { sp = psStackSize; } |
| void pushBool(bool booln) |
| { |
| if (checkOverflow()) { |
| stack[--sp].type = psBool; |
| stack[sp].booln = booln; |
| } |
| } |
| void pushInt(int intg) |
| { |
| if (checkOverflow()) { |
| stack[--sp].type = psInt; |
| stack[sp].intg = intg; |
| } |
| } |
| void pushReal(double real) |
| { |
| if (checkOverflow()) { |
| stack[--sp].type = psReal; |
| stack[sp].real = real; |
| } |
| } |
| bool popBool() |
| { |
| if (checkUnderflow() && checkType(psBool, psBool)) { |
| return stack[sp++].booln; |
| } |
| return false; |
| } |
| int popInt() |
| { |
| if (checkUnderflow() && checkType(psInt, psInt)) { |
| return stack[sp++].intg; |
| } |
| return 0; |
| } |
| double popNum() |
| { |
| double ret; |
| |
| if (checkUnderflow() && checkType(psInt, psReal)) { |
| ret = (stack[sp].type == psInt) ? (double)stack[sp].intg : stack[sp].real; |
| ++sp; |
| return ret; |
| } |
| return 0; |
| } |
| bool empty() { return sp == psStackSize; } |
| bool topIsInt() { return sp < psStackSize && stack[sp].type == psInt; } |
| bool topTwoAreInts() { return sp < psStackSize - 1 && stack[sp].type == psInt && stack[sp + 1].type == psInt; } |
| bool topIsReal() { return sp < psStackSize && stack[sp].type == psReal; } |
| bool topTwoAreNums() { return sp < psStackSize - 1 && (stack[sp].type == psInt || stack[sp].type == psReal) && (stack[sp + 1].type == psInt || stack[sp + 1].type == psReal); } |
| void copy(int n); |
| void roll(int n, int j); |
| void index(int i) |
| { |
| if (!checkOverflow()) { |
| return; |
| } |
| --sp; |
| if (unlikely(sp + i + 1 >= psStackSize)) { |
| error(errSyntaxError, -1, "Stack underflow in PostScript function"); |
| return; |
| } |
| if (unlikely(sp + i + 1 < 0)) { |
| error(errSyntaxError, -1, "Stack overflow in PostScript function"); |
| return; |
| } |
| stack[sp] = stack[sp + 1 + i]; |
| } |
| void pop() |
| { |
| if (!checkUnderflow()) { |
| return; |
| } |
| ++sp; |
| } |
| |
| private: |
| bool checkOverflow(int n = 1) |
| { |
| if (sp - n < 0) { |
| error(errSyntaxError, -1, "Stack overflow in PostScript function"); |
| return false; |
| } |
| return true; |
| } |
| bool checkUnderflow() |
| { |
| if (sp == psStackSize) { |
| error(errSyntaxError, -1, "Stack underflow in PostScript function"); |
| return false; |
| } |
| return true; |
| } |
| bool checkType(PSObjectType t1, PSObjectType t2) |
| { |
| if (stack[sp].type != t1 && stack[sp].type != t2) { |
| error(errSyntaxError, -1, "Type mismatch in PostScript function"); |
| return false; |
| } |
| return true; |
| } |
| PSObject stack[psStackSize]; |
| int sp; |
| }; |
| |
| void PSStack::copy(int n) |
| { |
| int i; |
| |
| int aux; |
| if (unlikely(checkedAdd(sp, n, &aux) || aux > psStackSize)) { |
| error(errSyntaxError, -1, "Stack underflow in PostScript function"); |
| return; |
| } |
| if (unlikely(checkedSubtraction(sp, n, &aux) || aux > psStackSize)) { |
| error(errSyntaxError, -1, "Stack underflow in PostScript function"); |
| return; |
| } |
| if (!checkOverflow(n)) { |
| return; |
| } |
| for (i = sp + n - 1; i >= sp; --i) { |
| stack[i - n] = stack[i]; |
| } |
| sp -= n; |
| } |
| |
| void PSStack::roll(int n, int j) |
| { |
| PSObject obj; |
| int i, k; |
| |
| if (unlikely(n == 0)) { |
| return; |
| } |
| if (j >= 0) { |
| j %= n; |
| } else { |
| j = -j % n; |
| if (j != 0) { |
| j = n - j; |
| } |
| } |
| if (n <= 0 || j == 0 || n > psStackSize || sp + n > psStackSize) { |
| return; |
| } |
| if (j <= n / 2) { |
| for (i = 0; i < j; ++i) { |
| obj = stack[sp]; |
| for (k = sp; k < sp + n - 1; ++k) { |
| stack[k] = stack[k + 1]; |
| } |
| stack[sp + n - 1] = obj; |
| } |
| } else { |
| j = n - j; |
| for (i = 0; i < j; ++i) { |
| obj = stack[sp + n - 1]; |
| for (k = sp + n - 1; k > sp; --k) { |
| stack[k] = stack[k - 1]; |
| } |
| stack[sp] = obj; |
| } |
| } |
| } |
| |
| PostScriptFunction::PostScriptFunction(Object *funcObj, Dict *dict) |
| { |
| Stream *str; |
| int codePtr; |
| double in[funcMaxInputs]; |
| int i; |
| |
| code = nullptr; |
| codeString = nullptr; |
| codeSize = 0; |
| ok = false; |
| |
| //----- initialize the generic stuff |
| if (!init(dict)) { |
| goto err1; |
| } |
| if (!hasRange) { |
| error(errSyntaxError, -1, "Type 4 function is missing range"); |
| goto err1; |
| } |
| |
| //----- get the stream |
| if (!funcObj->isStream()) { |
| error(errSyntaxError, -1, "Type 4 function isn't a stream"); |
| goto err1; |
| } |
| str = funcObj->getStream(); |
| |
| //----- parse the function |
| codeString = new GooString(); |
| str->reset(); |
| if (getToken(str)->cmp("{") != 0) { |
| error(errSyntaxError, -1, "Expected '{{' at start of PostScript function"); |
| goto err1; |
| } |
| codePtr = 0; |
| if (!parseCode(str, &codePtr)) { |
| goto err2; |
| } |
| str->close(); |
| |
| //----- set up the cache |
| for (i = 0; i < m; ++i) { |
| in[i] = domain[i][0]; |
| cacheIn[i] = in[i] - 1; |
| } |
| transform(in, cacheOut); |
| |
| ok = true; |
| |
| err2: |
| str->close(); |
| err1: |
| return; |
| } |
| |
| PostScriptFunction::PostScriptFunction(const PostScriptFunction *func) : Function(func) |
| { |
| codeSize = func->codeSize; |
| |
| code = (PSObject *)gmallocn(codeSize, sizeof(PSObject)); |
| memcpy(code, func->code, codeSize * sizeof(PSObject)); |
| |
| codeString = func->codeString->copy(); |
| |
| memcpy(cacheIn, func->cacheIn, funcMaxInputs * sizeof(double)); |
| memcpy(cacheOut, func->cacheOut, funcMaxOutputs * sizeof(double)); |
| |
| ok = func->ok; |
| } |
| |
| PostScriptFunction::~PostScriptFunction() |
| { |
| gfree(code); |
| delete codeString; |
| } |
| |
| void PostScriptFunction::transform(const double *in, double *out) const |
| { |
| PSStack stack; |
| int i; |
| |
| // check the cache |
| for (i = 0; i < m; ++i) { |
| if (in[i] != cacheIn[i]) { |
| break; |
| } |
| } |
| if (i == m) { |
| for (i = 0; i < n; ++i) { |
| out[i] = cacheOut[i]; |
| } |
| return; |
| } |
| |
| for (i = 0; i < m; ++i) { |
| //~ may need to check for integers here |
| stack.pushReal(in[i]); |
| } |
| exec(&stack, 0); |
| for (i = n - 1; i >= 0; --i) { |
| out[i] = stack.popNum(); |
| if (out[i] < range[i][0]) { |
| out[i] = range[i][0]; |
| } else if (out[i] > range[i][1]) { |
| out[i] = range[i][1]; |
| } |
| } |
| stack.clear(); |
| |
| // if (!stack->empty()) { |
| // error(errSyntaxWarning, -1, |
| // "Extra values on stack at end of PostScript function"); |
| // } |
| |
| // save current result in the cache |
| for (i = 0; i < m; ++i) { |
| cacheIn[i] = in[i]; |
| } |
| for (i = 0; i < n; ++i) { |
| cacheOut[i] = out[i]; |
| } |
| } |
| |
| bool PostScriptFunction::parseCode(Stream *str, int *codePtr) |
| { |
| bool isReal; |
| int opPtr, elsePtr; |
| int a, b, mid, cmp; |
| |
| while (true) { |
| // This needs to be on the heap to help make parseCode |
| // able to call itself more times recursively |
| std::unique_ptr<GooString> tok = getToken(str); |
| const char *p = tok->c_str(); |
| if (isdigit(*p) || *p == '.' || *p == '-') { |
| isReal = false; |
| for (; *p; ++p) { |
| if (*p == '.') { |
| isReal = true; |
| break; |
| } |
| } |
| resizeCode(*codePtr); |
| if (isReal) { |
| code[*codePtr].type = psReal; |
| code[*codePtr].real = gatof(tok->c_str()); |
| } else { |
| code[*codePtr].type = psInt; |
| code[*codePtr].intg = atoi(tok->c_str()); |
| } |
| ++*codePtr; |
| } else if (!tok->cmp("{")) { |
| opPtr = *codePtr; |
| *codePtr += 3; |
| resizeCode(opPtr + 2); |
| if (!parseCode(str, codePtr)) { |
| return false; |
| } |
| tok = getToken(str); |
| if (!tok->cmp("{")) { |
| elsePtr = *codePtr; |
| if (!parseCode(str, codePtr)) { |
| return false; |
| } |
| tok = getToken(str); |
| } else { |
| elsePtr = -1; |
| } |
| if (!tok->cmp("if")) { |
| if (elsePtr >= 0) { |
| error(errSyntaxError, -1, "Got 'if' operator with two blocks in PostScript function"); |
| return false; |
| } |
| code[opPtr].type = psOperator; |
| code[opPtr].op = psOpIf; |
| code[opPtr + 2].type = psBlock; |
| code[opPtr + 2].blk = *codePtr; |
| } else if (!tok->cmp("ifelse")) { |
| if (elsePtr < 0) { |
| error(errSyntaxError, -1, "Got 'ifelse' operator with one block in PostScript function"); |
| return false; |
| } |
| code[opPtr].type = psOperator; |
| code[opPtr].op = psOpIfelse; |
| code[opPtr + 1].type = psBlock; |
| code[opPtr + 1].blk = elsePtr; |
| code[opPtr + 2].type = psBlock; |
| code[opPtr + 2].blk = *codePtr; |
| } else { |
| error(errSyntaxError, -1, "Expected if/ifelse operator in PostScript function"); |
| return false; |
| } |
| } else if (!tok->cmp("}")) { |
| resizeCode(*codePtr); |
| code[*codePtr].type = psOperator; |
| code[*codePtr].op = psOpReturn; |
| ++*codePtr; |
| break; |
| } else { |
| a = -1; |
| b = nPSOps; |
| cmp = 0; // make gcc happy |
| // invariant: psOpNames[a] < tok < psOpNames[b] |
| while (b - a > 1) { |
| mid = (a + b) / 2; |
| cmp = tok->cmp(psOpNames[mid]); |
| if (cmp > 0) { |
| a = mid; |
| } else if (cmp < 0) { |
| b = mid; |
| } else { |
| a = b = mid; |
| } |
| } |
| if (cmp != 0) { |
| error(errSyntaxError, -1, "Unknown operator '{0:t}' in PostScript function", tok.get()); |
| return false; |
| } |
| resizeCode(*codePtr); |
| code[*codePtr].type = psOperator; |
| code[*codePtr].op = (PSOp)a; |
| ++*codePtr; |
| } |
| } |
| return true; |
| } |
| |
| std::unique_ptr<GooString> PostScriptFunction::getToken(Stream *str) |
| { |
| int c; |
| bool comment; |
| |
| std::string s; |
| comment = false; |
| while (true) { |
| if ((c = str->getChar()) == EOF) { |
| break; |
| } |
| codeString->append(c); |
| if (comment) { |
| if (c == '\x0a' || c == '\x0d') { |
| comment = false; |
| } |
| } else if (c == '%') { |
| comment = true; |
| } else if (!isspace(c)) { |
| break; |
| } |
| } |
| if (c == '{' || c == '}') { |
| s.push_back((char)c); |
| } else if (isdigit(c) || c == '.' || c == '-') { |
| while (true) { |
| s.push_back((char)c); |
| c = str->lookChar(); |
| if (c == EOF || !(isdigit(c) || c == '.' || c == '-')) { |
| break; |
| } |
| str->getChar(); |
| codeString->append(c); |
| } |
| } else { |
| while (true) { |
| s.push_back((char)c); |
| c = str->lookChar(); |
| if (c == EOF || !isalnum(c)) { |
| break; |
| } |
| str->getChar(); |
| codeString->append(c); |
| } |
| } |
| return std::make_unique<GooString>(s); |
| } |
| |
| void PostScriptFunction::resizeCode(int newSize) |
| { |
| if (newSize >= codeSize) { |
| codeSize += 64; |
| code = (PSObject *)greallocn(code, codeSize, sizeof(PSObject)); |
| } |
| } |
| |
| void PostScriptFunction::exec(PSStack *stack, int codePtr) const |
| { |
| int i1, i2; |
| double r1, r2, result; |
| bool b1, b2; |
| |
| while (true) { |
| switch (code[codePtr].type) { |
| case psInt: |
| stack->pushInt(code[codePtr++].intg); |
| break; |
| case psReal: |
| stack->pushReal(code[codePtr++].real); |
| break; |
| case psOperator: |
| switch (code[codePtr++].op) { |
| case psOpAbs: |
| if (stack->topIsInt()) { |
| stack->pushInt(abs(stack->popInt())); |
| } else { |
| stack->pushReal(fabs(stack->popNum())); |
| } |
| break; |
| case psOpAdd: |
| if (stack->topTwoAreInts()) { |
| i2 = stack->popInt(); |
| i1 = stack->popInt(); |
| stack->pushInt(i1 + i2); |
| } else { |
| r2 = stack->popNum(); |
| r1 = stack->popNum(); |
| stack->pushReal(r1 + r2); |
| } |
| break; |
| case psOpAnd: |
| if (stack->topTwoAreInts()) { |
| i2 = stack->popInt(); |
| i1 = stack->popInt(); |
| stack->pushInt(i1 & i2); |
| } else { |
| b2 = stack->popBool(); |
| b1 = stack->popBool(); |
| stack->pushBool(b1 && b2); |
| } |
| break; |
| case psOpAtan: |
| r2 = stack->popNum(); |
| r1 = stack->popNum(); |
| result = atan2(r1, r2) * 180.0 / M_PI; |
| if (result < 0) { |
| result += 360.0; |
| } |
| stack->pushReal(result); |
| break; |
| case psOpBitshift: |
| i2 = stack->popInt(); |
| i1 = stack->popInt(); |
| if (i2 > 0) { |
| stack->pushInt(i1 << i2); |
| } else if (i2 < 0) { |
| stack->pushInt((int)((unsigned int)i1 >> -i2)); |
| } else { |
| stack->pushInt(i1); |
| } |
| break; |
| case psOpCeiling: |
| if (!stack->topIsInt()) { |
| stack->pushReal(ceil(stack->popNum())); |
| } |
| break; |
| case psOpCopy: |
| stack->copy(stack->popInt()); |
| break; |
| case psOpCos: |
| stack->pushReal(cos(stack->popNum() * M_PI / 180.0)); |
| break; |
| case psOpCvi: |
| if (!stack->topIsInt()) { |
| stack->pushInt((int)stack->popNum()); |
| } |
| break; |
| case psOpCvr: |
| if (!stack->topIsReal()) { |
| stack->pushReal(stack->popNum()); |
| } |
| break; |
| case psOpDiv: |
| r2 = stack->popNum(); |
| r1 = stack->popNum(); |
| stack->pushReal(r1 / r2); |
| break; |
| case psOpDup: |
| stack->copy(1); |
| break; |
| case psOpEq: |
| if (stack->topTwoAreInts()) { |
| i2 = stack->popInt(); |
| i1 = stack->popInt(); |
| stack->pushBool(i1 == i2); |
| } else if (stack->topTwoAreNums()) { |
| r2 = stack->popNum(); |
| r1 = stack->popNum(); |
| stack->pushBool(r1 == r2); |
| } else { |
| b2 = stack->popBool(); |
| b1 = stack->popBool(); |
| stack->pushBool(b1 == b2); |
| } |
| break; |
| case psOpExch: |
| stack->roll(2, 1); |
| break; |
| case psOpExp: |
| r2 = stack->popNum(); |
| r1 = stack->popNum(); |
| stack->pushReal(pow(r1, r2)); |
| break; |
| case psOpFalse: |
| stack->pushBool(false); |
| break; |
| case psOpFloor: |
| if (!stack->topIsInt()) { |
| stack->pushReal(floor(stack->popNum())); |
| } |
| break; |
| case psOpGe: |
| if (stack->topTwoAreInts()) { |
| i2 = stack->popInt(); |
| i1 = stack->popInt(); |
| stack->pushBool(i1 >= i2); |
| } else { |
| r2 = stack->popNum(); |
| r1 = stack->popNum(); |
| stack->pushBool(r1 >= r2); |
| } |
| break; |
| case psOpGt: |
| if (stack->topTwoAreInts()) { |
| i2 = stack->popInt(); |
| i1 = stack->popInt(); |
| stack->pushBool(i1 > i2); |
| } else { |
| r2 = stack->popNum(); |
| r1 = stack->popNum(); |
| stack->pushBool(r1 > r2); |
| } |
| break; |
| case psOpIdiv: |
| i2 = stack->popInt(); |
| i1 = stack->popInt(); |
| if (likely((i2 != 0) && !(i2 == -1 && i1 == INT_MIN))) { |
| stack->pushInt(i1 / i2); |
| } |
| break; |
| case psOpIndex: |
| stack->index(stack->popInt()); |
| break; |
| case psOpLe: |
| if (stack->topTwoAreInts()) { |
| i2 = stack->popInt(); |
| i1 = stack->popInt(); |
| stack->pushBool(i1 <= i2); |
| } else { |
| r2 = stack->popNum(); |
| r1 = stack->popNum(); |
| stack->pushBool(r1 <= r2); |
| } |
| break; |
| case psOpLn: |
| stack->pushReal(log(stack->popNum())); |
| break; |
| case psOpLog: |
| stack->pushReal(log10(stack->popNum())); |
| break; |
| case psOpLt: |
| if (stack->topTwoAreInts()) { |
| i2 = stack->popInt(); |
| i1 = stack->popInt(); |
| stack->pushBool(i1 < i2); |
| } else { |
| r2 = stack->popNum(); |
| r1 = stack->popNum(); |
| stack->pushBool(r1 < r2); |
| } |
| break; |
| case psOpMod: |
| i2 = stack->popInt(); |
| i1 = stack->popInt(); |
| if (likely(i2 != 0)) { |
| stack->pushInt(i1 % i2); |
| } |
| break; |
| case psOpMul: |
| if (stack->topTwoAreInts()) { |
| i2 = stack->popInt(); |
| i1 = stack->popInt(); |
| //~ should check for out-of-range, and push a real instead |
| stack->pushInt(i1 * i2); |
| } else { |
| r2 = stack->popNum(); |
| r1 = stack->popNum(); |
| stack->pushReal(r1 * r2); |
| } |
| break; |
| case psOpNe: |
| if (stack->topTwoAreInts()) { |
| i2 = stack->popInt(); |
| i1 = stack->popInt(); |
| stack->pushBool(i1 != i2); |
| } else if (stack->topTwoAreNums()) { |
| r2 = stack->popNum(); |
| r1 = stack->popNum(); |
| stack->pushBool(r1 != r2); |
| } else { |
| b2 = stack->popBool(); |
| b1 = stack->popBool(); |
| stack->pushBool(b1 != b2); |
| } |
| break; |
| case psOpNeg: |
| if (stack->topIsInt()) { |
| stack->pushInt(-stack->popInt()); |
| } else { |
| stack->pushReal(-stack->popNum()); |
| } |
| break; |
| case psOpNot: |
| if (stack->topIsInt()) { |
| stack->pushInt(~stack->popInt()); |
| } else { |
| stack->pushBool(!stack->popBool()); |
| } |
| break; |
| case psOpOr: |
| if (stack->topTwoAreInts()) { |
| i2 = stack->popInt(); |
| i1 = stack->popInt(); |
| stack->pushInt(i1 | i2); |
| } else { |
| b2 = stack->popBool(); |
| b1 = stack->popBool(); |
| stack->pushBool(b1 || b2); |
| } |
| break; |
| case psOpPop: |
| stack->pop(); |
| break; |
| case psOpRoll: |
| i2 = stack->popInt(); |
| i1 = stack->popInt(); |
| stack->roll(i1, i2); |
| break; |
| case psOpRound: |
| if (!stack->topIsInt()) { |
| r1 = stack->popNum(); |
| stack->pushReal((r1 >= 0) ? floor(r1 + 0.5) : ceil(r1 - 0.5)); |
| } |
| break; |
| case psOpSin: |
| stack->pushReal(sin(stack->popNum() * M_PI / 180.0)); |
| break; |
| case psOpSqrt: |
| stack->pushReal(sqrt(stack->popNum())); |
| break; |
| case psOpSub: |
| if (stack->topTwoAreInts()) { |
| i2 = stack->popInt(); |
| i1 = stack->popInt(); |
| stack->pushInt(i1 - i2); |
| } else { |
| r2 = stack->popNum(); |
| r1 = stack->popNum(); |
| stack->pushReal(r1 - r2); |
| } |
| break; |
| case psOpTrue: |
| stack->pushBool(true); |
| break; |
| case psOpTruncate: |
| if (!stack->topIsInt()) { |
| r1 = stack->popNum(); |
| stack->pushReal((r1 >= 0) ? floor(r1) : ceil(r1)); |
| } |
| break; |
| case psOpXor: |
| if (stack->topTwoAreInts()) { |
| i2 = stack->popInt(); |
| i1 = stack->popInt(); |
| stack->pushInt(i1 ^ i2); |
| } else { |
| b2 = stack->popBool(); |
| b1 = stack->popBool(); |
| stack->pushBool(b1 ^ b2); |
| } |
| break; |
| case psOpIf: |
| b1 = stack->popBool(); |
| if (b1) { |
| exec(stack, codePtr + 2); |
| } |
| codePtr = code[codePtr + 1].blk; |
| break; |
| case psOpIfelse: |
| b1 = stack->popBool(); |
| if (b1) { |
| exec(stack, codePtr + 2); |
| } else { |
| exec(stack, code[codePtr].blk); |
| } |
| codePtr = code[codePtr + 1].blk; |
| break; |
| case psOpReturn: |
| return; |
| } |
| break; |
| default: |
| error(errSyntaxError, -1, "Internal: bad object in PostScript function code"); |
| break; |
| } |
| } |
| } |