diff --git a/bower.json b/bower.json index c4d625396..8fd4cae5e 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "pdfjs-dist", - "version": "1.0.579", + "version": "1.0.582", "keywords": [ "Mozilla", "pdf", diff --git a/build/pdf.combined.js b/build/pdf.combined.js index f376077d8..8889f322b 100644 --- a/build/pdf.combined.js +++ b/build/pdf.combined.js @@ -22,8 +22,8 @@ if (typeof PDFJS === 'undefined') { (typeof window !== 'undefined' ? window : this).PDFJS = {}; } -PDFJS.version = '1.0.579'; -PDFJS.build = '12b5048'; +PDFJS.version = '1.0.582'; +PDFJS.build = 'cb4a847'; (function pdfjsWrapper() { // Use strict in our context only - users might not want it @@ -10155,6 +10155,24 @@ var PDFFunction = (function PDFFunctionClosure() { return this.fromIR(IR); }, + parseArray: function PDFFunction_parseArray(xref, fnObj) { + if (!isArray(fnObj)) { + // not an array -- parsing as regular function + return this.parse(xref, fnObj); + } + + var fnArray = []; + for (var j = 0, jj = fnObj.length; j < jj; j++) { + var obj = xref.fetchIfRef(fnObj[j]); + fnArray.push(PDFFunction.parse(xref, obj)); + } + return function (src, srcOffset, dest, destOffset) { + for (var i = 0, ii = fnArray.length; i < ii; i++) { + fnArray[i](src, srcOffset, dest, destOffset + i); + } + }; + }, + constructSampled: function PDFFunction_constructSampled(str, dict) { function toMultiArray(arr) { var inputLength = arr.length; @@ -10219,7 +10237,8 @@ var PDFFunction = (function PDFFunctionClosure() { return ymin + ((x - xmin) * ((ymax - ymin) / (xmax - xmin))); } - return function constructSampledFromIRResult(args) { + return function constructSampledFromIRResult(src, srcOffset, + dest, destOffset) { // See chapter 3, page 110 of the PDF reference. var m = IR[1]; var domain = IR[2]; @@ -10231,13 +10250,6 @@ var PDFFunction = (function PDFFunctionClosure() { //var mask = IR[8]; var range = IR[9]; - if (m !== args.length) { - error('Incorrect number of arguments: ' + m + ' != ' + - args.length); - } - - var x = args; - // Building the cube vertices: its part and sample index // http://rjwagner49.com/Mathematics/Interpolation.pdf var cubeVertices = 1 << m; @@ -10254,7 +10266,8 @@ var PDFFunction = (function PDFFunctionClosure() { // x_i' = min(max(x_i, Domain_2i), Domain_2i+1) var domain_2i = domain[i][0]; var domain_2i_1 = domain[i][1]; - var xi = Math.min(Math.max(x[i], domain_2i), domain_2i_1); + var xi = Math.min(Math.max(src[srcOffset +i], domain_2i), + domain_2i_1); // e_i = Interpolate(x_i', Domain_2i, Domain_2i+1, // Encode_2i, Encode_2i+1) @@ -10285,7 +10298,6 @@ var PDFFunction = (function PDFFunctionClosure() { pos <<= 1; } - var y = new Float64Array(n); for (j = 0; j < n; ++j) { // Sum all cube vertices' samples portions var rj = 0; @@ -10298,10 +10310,9 @@ var PDFFunction = (function PDFFunctionClosure() { rj = interpolate(rj, 0, 1, decode[j][0], decode[j][1]); // y_j = min(max(r_j, range_2j), range_2j+1) - y[j] = Math.min(Math.max(rj, range[j][0]), range[j][1]); + dest[destOffset + j] = Math.min(Math.max(rj, range[j][0]), + range[j][1]); } - - return y; }; }, @@ -10332,16 +10343,13 @@ var PDFFunction = (function PDFFunctionClosure() { var length = diff.length; - return function constructInterpolatedFromIRResult(args) { - var x = (n === 1 ? args[0] : Math.pow(args[0], n)); + return function constructInterpolatedFromIRResult(src, srcOffset, + dest, destOffset) { + var x = n === 1 ? src[srcOffset] : Math.pow(src[srcOffset], n); - var out = []; for (var j = 0; j < length; ++j) { - out.push(c0[j] + (x * diff[j])); + dest[destOffset + j] = c0[j] + (x * diff[j]); } - - return out; - }; }, @@ -10375,12 +10383,14 @@ var PDFFunction = (function PDFFunctionClosure() { var encode = IR[3]; var fnsIR = IR[4]; var fns = []; + var tmpBuf = new Float32Array(1); for (var i = 0, ii = fnsIR.length; i < ii; i++) { fns.push(PDFFunction.fromIR(fnsIR[i])); } - return function constructStichedFromIRResult(args) { + return function constructStichedFromIRResult(src, srcOffset, + dest, destOffset) { var clip = function constructStichedFromIRClip(v, min, max) { if (v > max) { v = max; @@ -10391,7 +10401,7 @@ var PDFFunction = (function PDFFunctionClosure() { }; // clip to domain - var v = clip(args[0], domain[0], domain[1]); + var v = clip(src[srcOffset], domain[0], domain[1]); // calulate which bound the value is in for (var i = 0, ii = bounds.length; i < ii; ++i) { if (v < bounds[i]) { @@ -10412,10 +10422,10 @@ var PDFFunction = (function PDFFunctionClosure() { var rmin = encode[2 * i]; var rmax = encode[2 * i + 1]; - var v2 = rmin + (v - dmin) * (rmax - rmin) / (dmax - dmin); + tmpBuf[0] = rmin + (v - dmin) * (rmax - rmin) / (dmax - dmin); // call the appropriate function - return fns[i]([v2]); + fns[i](tmpBuf, 0, dest, destOffset); }; }, @@ -10444,6 +10454,18 @@ var PDFFunction = (function PDFFunctionClosure() { var domain = IR[1]; var range = IR[2]; var code = IR[3]; + + var compiled = (new PostScriptCompiler()).compile(code, domain, range); + if (compiled) { + // Compiled function consists of simple expressions such as addition, + // subtraction, Math.max, and also contains 'var' and 'return' + // statements. See the generation in the PostScriptCompiler below. + /*jshint -W054 */ + return new Function('src', 'srcOffset', 'dest', 'destOffset', compiled); + } + + info('Unable to compile PS function'); + var numOutputs = range.length >> 1; var numInputs = domain.length >> 1; var evaluator = new PostScriptEvaluator(code); @@ -10454,22 +10476,26 @@ var PDFFunction = (function PDFFunctionClosure() { // seen in our tests. var MAX_CACHE_SIZE = 2048 * 4; var cache_available = MAX_CACHE_SIZE; - return function constructPostScriptFromIRResult(args) { + var tmpBuf = new Float32Array(numInputs); + + return function constructPostScriptFromIRResult(src, srcOffset, + dest, destOffset) { var i, value; var key = ''; - var input = new Array(numInputs); + var input = tmpBuf; for (i = 0; i < numInputs; i++) { - value = args[i]; + value = src[srcOffset + i]; input[i] = value; key += value + '_'; } var cachedValue = cache[key]; if (cachedValue !== undefined) { - return cachedValue; + cachedValue.set(dest, destOffset); + return; } - var output = new Array(numOutputs); + var output = new Float32Array(numOutputs); var stack = evaluator.execute(input); var stackIndex = stack.length - numOutputs; for (i = 0; i < numOutputs; i++) { @@ -10489,7 +10515,7 @@ var PDFFunction = (function PDFFunctionClosure() { cache_available--; cache[key] = output; } - return output; + output.set(dest, destOffset); }; } }; @@ -10512,7 +10538,8 @@ function isPDFFunction(v) { var PostScriptStack = (function PostScriptStackClosure() { var MAX_STACK_SIZE = 100; function PostScriptStack(initialStack) { - this.stack = initialStack || []; + this.stack = !initialStack ? [] : + Array.prototype.slice.call(initialStack, 0); } PostScriptStack.prototype = { @@ -10795,6 +10822,373 @@ var PostScriptEvaluator = (function PostScriptEvaluatorClosure() { return PostScriptEvaluator; })(); +// Most of the PDFs functions consist of simple operations such as: +// roll, exch, sub, cvr, pop, index, dup, mul, if, gt, add. +// +// We can compile most of such programs, and at the same moment, we can +// optimize some expressions using basic math properties. Keeping track of +// min/max values will allow us to avoid extra Math.min/Math.max calls. +var PostScriptCompiler = (function PostScriptCompilerClosure() { + function AstNode(type) { + this.type = type; + } + AstNode.prototype.visit = function (visitor) { + throw new Error('abstract method'); + }; + + function AstArgument(index, min, max) { + AstNode.call(this, 'args'); + this.index = index; + this.min = min; + this.max = max; + } + AstArgument.prototype = Object.create(AstNode.prototype); + AstArgument.prototype.visit = function (visitor) { + visitor.visitArgument(this); + }; + + function AstLiteral(number) { + AstNode.call(this, 'literal'); + this.number = number; + this.min = number; + this.max = number; + } + AstLiteral.prototype = Object.create(AstNode.prototype); + AstLiteral.prototype.visit = function (visitor) { + visitor.visitLiteral(this); + }; + + function AstBinaryOperation(op, arg1, arg2, min, max) { + AstNode.call(this, 'binary'); + this.op = op; + this.arg1 = arg1; + this.arg2 = arg2; + this.min = min; + this.max = max; + } + AstBinaryOperation.prototype = Object.create(AstNode.prototype); + AstBinaryOperation.prototype.visit = function (visitor) { + visitor.visitBinaryOperation(this); + }; + + function AstMin(arg, max) { + AstNode.call(this, 'max'); + this.arg = arg; + this.min = arg.min; + this.max = max; + } + AstMin.prototype = Object.create(AstNode.prototype); + AstMin.prototype.visit = function (visitor) { + visitor.visitMin(this); + }; + + function AstVariable(index, min, max) { + AstNode.call(this, 'var'); + this.index = index; + this.min = min; + this.max = max; + } + AstVariable.prototype = Object.create(AstNode.prototype); + AstVariable.prototype.visit = function (visitor) { + visitor.visitVariable(this); + }; + + function AstVariableDefinition(variable, arg) { + AstNode.call(this, 'definition'); + this.variable = variable; + this.arg = arg; + } + AstVariableDefinition.prototype = Object.create(AstNode.prototype); + AstVariableDefinition.prototype.visit = function (visitor) { + visitor.visitVariableDefinition(this); + }; + + function ExpressionBuilderVisitor() { + this.parts = []; + } + ExpressionBuilderVisitor.prototype = { + visitArgument: function (arg) { + this.parts.push('Math.max(', arg.min, ', Math.min(', + arg.max, ', src[srcOffset + ', arg.index, ']))'); + }, + visitVariable: function (variable) { + this.parts.push('v', variable.index); + }, + visitLiteral: function (literal) { + this.parts.push(literal.number); + }, + visitBinaryOperation: function (operation) { + this.parts.push('('); + operation.arg1.visit(this); + this.parts.push(' ', operation.op, ' '); + operation.arg2.visit(this); + this.parts.push(')'); + }, + visitVariableDefinition: function (definition) { + this.parts.push('var '); + definition.variable.visit(this); + this.parts.push(' = '); + definition.arg.visit(this); + this.parts.push(';'); + }, + visitMin: function (max) { + this.parts.push('Math.min('); + max.arg.visit(this); + this.parts.push(', ', max.max, ')'); + }, + toString: function () { + return this.parts.join(''); + } + }; + + function buildAddOperation(num1, num2) { + if (num2.type === 'literal' && num2.number === 0) { + // optimization: second operand is 0 + return num1; + } + if (num1.type === 'literal' && num1.number === 0) { + // optimization: first operand is 0 + return num2; + } + if (num2.type === 'literal' && num1.type === 'literal') { + // optimization: operands operand are literals + return new AstLiteral(num1.number + num2.number); + } + return new AstBinaryOperation('+', num1, num2, + num1.min + num2.min, num1.max + num2.max); + } + + function buildMulOperation(num1, num2) { + if (num2.type === 'literal') { + // optimization: second operands is a literal... + if (num2.number === 0) { + return new AstLiteral(0); // and it's 0 + } else if (num2.number === 1) { + return num1; // and it's 1 + } else if (num1.type === 'literal') { + // ... and first operands is a literal too + return new AstLiteral(num1.number * num2.number); + } + } + if (num1.type === 'literal') { + // optimization: first operands is a literal... + if (num1.number === 0) { + return new AstLiteral(0); // and it's 0 + } else if (num1.number === 1) { + return num2; // and it's 1 + } + } + var min = Math.min(num1.min * num2.min, num1.min * num2.max, + num1.max * num2.min, num1.max * num2.max); + var max = Math.max(num1.min * num2.min, num1.min * num2.max, + num1.max * num2.min, num1.max * num2.max); + return new AstBinaryOperation('*', num1, num2, min, max); + } + + function buildSubOperation(num1, num2) { + if (num2.type === 'literal') { + // optimization: second operands is a literal... + if (num2.number === 0) { + return num1; // ... and it's 0 + } else if (num1.type === 'literal') { + // ... and first operands is a literal too + return new AstLiteral(num1.number - num2.number); + } + } + if (num2.type === 'binary' && num2.op === '-' && + num1.type === 'literal' && num1.number === 1 && + num2.arg1.type === 'literal' && num2.arg1.number === 1) { + // optimization for case: 1 - (1 - x) + return num2.arg2; + } + return new AstBinaryOperation('-', num1, num2, + num1.min - num2.max, num1.max - num2.min); + } + + function buildMinOperation(num1, max) { + if (num1.min >= max) { + // optimization: num1 min value is not less than required max + return new AstLiteral(max); // just returning max + } else if (num1.max <= max) { + // optimization: num1 max value is not greater than required max + return num1; // just returning an argument + } + return new AstMin(num1, max); + } + + function PostScriptCompiler() {} + PostScriptCompiler.prototype = { + compile: function PostScriptCompiler_compile(code, domain, range) { + var stack = []; + var i, ii; + var instructions = []; + var inputSize = domain.length >> 1, outputSize = range.length >> 1; + var lastRegister = 0; + var n, j, min, max; + var num1, num2, ast1, ast2, tmpVar, item; + for (i = 0; i < inputSize; i++) { + stack.push(new AstArgument(i, domain[i * 2], domain[i * 2 + 1])); + } + + for (i = 0, ii = code.length; i < ii; i++) { + item = code[i]; + if (typeof item === 'number') { + stack.push(new AstLiteral(item)); + continue; + } + + switch (item) { + case 'add': + if (stack.length < 2) { + return null; + } + num2 = stack.pop(); + num1 = stack.pop(); + stack.push(buildAddOperation(num1, num2)); + break; + case 'cvr': + if (stack.length < 1) { + return null; + } + break; + case 'mul': + if (stack.length < 2) { + return null; + } + num2 = stack.pop(); + num1 = stack.pop(); + stack.push(buildMulOperation(num1, num2)); + break; + case 'sub': + if (stack.length < 2) { + return null; + } + num2 = stack.pop(); + num1 = stack.pop(); + stack.push(buildSubOperation(num1, num2)); + break; + case 'exch': + if (stack.length < 2) { + return null; + } + ast1 = stack.pop(); ast2 = stack.pop(); + stack.push(ast1, ast2); + break; + case 'pop': + if (stack.length < 1) { + return null; + } + stack.pop(); + break; + case 'index': + if (stack.length < 1) { + return null; + } + num1 = stack.pop(); + if (num1.type !== 'literal') { + return null; + } + n = num1.number; + if (n < 0 || (n|0) !== n || stack.length < n) { + return null; + } + ast1 = stack[stack.length - n - 1]; + if (ast1.type === 'literal' || ast1.type === 'var') { + stack.push(ast1); + break; + } + tmpVar = new AstVariable(lastRegister++, ast1.min, ast1.max); + stack[stack.length - n - 1] = tmpVar; + stack.push(tmpVar); + instructions.push(new AstVariableDefinition(tmpVar, ast1)); + break; + case 'dup': + if (stack.length < 1) { + return null; + } + if (typeof code[i + 1] === 'number' && code[i + 2] === 'gt' && + code[i + 3] === i + 7 && code[i + 4] === 'jz' && + code[i + 5] === 'pop' && code[i + 6] === code[i + 1]) { + // special case of the commands sequence for the min operation + num1 = stack.pop(); + stack.push(buildMinOperation(num1, code[i + 1])); + i += 6; + break; + } + ast1 = stack[stack.length - 1]; + if (ast1.type === 'literal' || ast1.type === 'var') { + // we don't have to save into intermediate variable a literal or + // variable. + stack.push(ast1); + break; + } + tmpVar = new AstVariable(lastRegister++, ast1.min, ast1.max); + stack[stack.length - 1] = tmpVar; + stack.push(tmpVar); + instructions.push(new AstVariableDefinition(tmpVar, ast1)); + break; + case 'roll': + if (stack.length < 2) { + return null; + } + num2 = stack.pop(); + num1 = stack.pop(); + if (num2.type !== 'literal' || num1.type !== 'literal') { + // both roll operands must be numbers + return null; + } + j = num2.number; + n = num1.number; + if (n <= 0 || (n|0) !== n || (j|0) !== j || stack.length < n) { + // ... and integers + return null; + } + j = ((j % n) + n) % n; + if (j === 0) { + break; // just skipping -- there are nothing to rotate + } + Array.prototype.push.apply(stack, + stack.splice(stack.length - n, n - j)); + break; + default: + return null; // unsupported operator + } + } + + if (stack.length !== outputSize) { + return null; + } + + var result = []; + instructions.forEach(function (instruction) { + var statementBuilder = new ExpressionBuilderVisitor(); + instruction.visit(statementBuilder); + result.push(statementBuilder.toString()); + }); + stack.forEach(function (expr, i) { + var statementBuilder = new ExpressionBuilderVisitor(); + expr.visit(statementBuilder); + var min = range[i * 2], max = range[i * 2 + 1]; + var out = [statementBuilder.toString()]; + if (min > expr.min) { + out.unshift('Math.max(', min, ', '); + out.push(')'); + } + if (max < expr.max) { + out.unshift('Math.min(', max, ', '); + out.push(')'); + } + out.unshift('dest[destOffset + ', i, '] = '); + out.push(';'); + result.push(out.join('')); + }); + return result.join('\n'); + } + }; + + return PostScriptCompiler; +})(); + var ColorSpace = (function ColorSpaceClosure() { // Constructor should define this.numComps, this.defaultColor, this.name @@ -11157,23 +11551,20 @@ var AlternateCS = (function AlternateCSClosure() { } this.base = base; this.tintFn = tintFn; + this.tmpBuf = new Float32Array(base.numComps); } AlternateCS.prototype = { getRgb: ColorSpace.prototype.getRgb, getRgbItem: function AlternateCS_getRgbItem(src, srcOffset, dest, destOffset) { - var baseNumComps = this.base.numComps; - var input = 'subarray' in src ? - src.subarray(srcOffset, srcOffset + this.numComps) : - Array.prototype.slice.call(src, srcOffset, srcOffset + this.numComps); - var tinted = this.tintFn(input); - this.base.getRgbItem(tinted, 0, dest, destOffset); + var tmpBuf = this.tmpBuf; + this.tintFn(src, srcOffset, tmpBuf, 0); + this.base.getRgbItem(tmpBuf, 0, dest, destOffset); }, getRgbBuffer: function AlternateCS_getRgbBuffer(src, srcOffset, count, dest, destOffset, bits, alpha01) { - var tinted; var tintFn = this.tintFn; var base = this.base; var scale = 1 / ((1 << bits) - 1); @@ -11186,13 +11577,14 @@ var AlternateCS = (function AlternateCSClosure() { var numComps = this.numComps; var scaled = new Float32Array(numComps); + var tinted = new Float32Array(baseNumComps); var i, j; if (usesZeroToOneRange) { for (i = 0; i < count; i++) { for (j = 0; j < numComps; j++) { scaled[j] = src[srcOffset++] * scale; } - tinted = tintFn(scaled); + tintFn(scaled, 0, tinted, 0); for (j = 0; j < baseNumComps; j++) { baseBuf[pos++] = tinted[j] * 255; } @@ -11202,7 +11594,7 @@ var AlternateCS = (function AlternateCSClosure() { for (j = 0; j < numComps; j++) { scaled[j] = src[srcOffset++] * scale; } - tinted = tintFn(scaled); + tintFn(scaled, 0, tinted, 0); base.getRgbItem(tinted, 0, baseBuf, pos); pos += baseNumComps; } @@ -13822,29 +14214,7 @@ Shadings.RadialAxial = (function RadialAxialClosure() { this.extendEnd = extendEnd; var fnObj = dict.get('Function'); - var fn; - if (isArray(fnObj)) { - var fnArray = []; - for (var j = 0, jj = fnObj.length; j < jj; j++) { - var obj = xref.fetchIfRef(fnObj[j]); - if (!isPDFFunction(obj)) { - error('Invalid function'); - } - fnArray.push(PDFFunction.parse(xref, obj)); - } - fn = function radialAxialColorFunction(arg) { - var out = []; - for (var i = 0, ii = fnArray.length; i < ii; i++) { - out.push(fnArray[i](arg)[0]); - } - return out; - }; - } else { - if (!isPDFFunction(fnObj)) { - error('Invalid function'); - } - fn = PDFFunction.parse(xref, fnObj); - } + var fn = PDFFunction.parseArray(xref, fnObj); // 10 samples seems good enough for now, but probably won't work // if there are sharp color changes. Ideally, we would implement @@ -13862,9 +14232,12 @@ Shadings.RadialAxial = (function RadialAxialClosure() { return; } + var color = new Float32Array(cs.numComps), ratio = new Float32Array(1); var rgbColor; for (var i = t0; i <= t1; i += step) { - rgbColor = cs.getRgb(fn([i]), 0); + ratio[0] = i; + fn(ratio, 0, color, 0); + rgbColor = cs.getRgb(color, 0); var cssColor = Util.makeCssRgb(rgbColor); colorStops.push([(i - t0) / diff, cssColor]); } @@ -13932,6 +14305,12 @@ Shadings.Mesh = (function MeshClosure() { this.context = context; this.buffer = 0; this.bufferLength = 0; + + var numComps = context.numComps; + this.tmpCompsBuf = new Float32Array(numComps); + var csNumComps = context.colorSpace; + this.tmpCsCompsBuf = context.colorFn ? new Float32Array(csNumComps) : + this.tmpCompsBuf; } MeshStreamReader.prototype = { get hasData() { @@ -14002,15 +14381,16 @@ Shadings.Mesh = (function MeshClosure() { var scale = bitsPerComponent < 32 ? 1 / ((1 << bitsPerComponent) - 1) : 2.3283064365386963e-10; // 2 ^ -32 var decode = this.context.decode; - var components = []; + var components = this.tmpCompsBuf; for (var i = 0, j = 4; i < numComps; i++, j += 2) { var ci = this.readBits(bitsPerComponent); - components.push(ci * scale * (decode[j + 1] - decode[j]) + decode[j]); + components[i] = ci * scale * (decode[j + 1] - decode[j]) + decode[j]; } + var color = this.tmpCsCompsBuf; if (this.context.colorFn) { - components = this.context.colorFn(components); + this.context.colorFn(components, 0, color, 0); } - return this.context.colorSpace.getRgb(components, 0); + return this.context.colorSpace.getRgb(color, 0); } }; @@ -14416,31 +14796,7 @@ Shadings.Mesh = (function MeshClosure() { cs.getRgb(dict.get('Background'), 0) : null; var fnObj = dict.get('Function'); - var fn; - if (!fnObj) { - fn = null; - } else if (isArray(fnObj)) { - var fnArray = []; - for (var j = 0, jj = fnObj.length; j < jj; j++) { - var obj = xref.fetchIfRef(fnObj[j]); - if (!isPDFFunction(obj)) { - error('Invalid function'); - } - fnArray.push(PDFFunction.parse(xref, obj)); - } - fn = function radialAxialColorFunction(arg) { - var out = []; - for (var i = 0, ii = fnArray.length; i < ii; i++) { - out.push(fnArray[i](arg)[0]); - } - return out; - }; - } else { - if (!isPDFFunction(fnObj)) { - error('Invalid function'); - } - fn = PDFFunction.parse(xref, fnObj); - } + var fn = fnObj ? PDFFunction.parseArray(xref, fnObj) : null; this.coords = []; this.colors = []; diff --git a/build/pdf.js b/build/pdf.js index 0edca4c56..602615186 100644 --- a/build/pdf.js +++ b/build/pdf.js @@ -22,8 +22,8 @@ if (typeof PDFJS === 'undefined') { (typeof window !== 'undefined' ? window : this).PDFJS = {}; } -PDFJS.version = '1.0.579'; -PDFJS.build = '12b5048'; +PDFJS.version = '1.0.582'; +PDFJS.build = 'cb4a847'; (function pdfjsWrapper() { // Use strict in our context only - users might not want it diff --git a/build/pdf.worker.js b/build/pdf.worker.js index 90f0622e9..0744308e3 100644 --- a/build/pdf.worker.js +++ b/build/pdf.worker.js @@ -22,8 +22,8 @@ if (typeof PDFJS === 'undefined') { (typeof window !== 'undefined' ? window : this).PDFJS = {}; } -PDFJS.version = '1.0.579'; -PDFJS.build = '12b5048'; +PDFJS.version = '1.0.582'; +PDFJS.build = 'cb4a847'; (function pdfjsWrapper() { // Use strict in our context only - users might not want it @@ -5298,6 +5298,24 @@ var PDFFunction = (function PDFFunctionClosure() { return this.fromIR(IR); }, + parseArray: function PDFFunction_parseArray(xref, fnObj) { + if (!isArray(fnObj)) { + // not an array -- parsing as regular function + return this.parse(xref, fnObj); + } + + var fnArray = []; + for (var j = 0, jj = fnObj.length; j < jj; j++) { + var obj = xref.fetchIfRef(fnObj[j]); + fnArray.push(PDFFunction.parse(xref, obj)); + } + return function (src, srcOffset, dest, destOffset) { + for (var i = 0, ii = fnArray.length; i < ii; i++) { + fnArray[i](src, srcOffset, dest, destOffset + i); + } + }; + }, + constructSampled: function PDFFunction_constructSampled(str, dict) { function toMultiArray(arr) { var inputLength = arr.length; @@ -5362,7 +5380,8 @@ var PDFFunction = (function PDFFunctionClosure() { return ymin + ((x - xmin) * ((ymax - ymin) / (xmax - xmin))); } - return function constructSampledFromIRResult(args) { + return function constructSampledFromIRResult(src, srcOffset, + dest, destOffset) { // See chapter 3, page 110 of the PDF reference. var m = IR[1]; var domain = IR[2]; @@ -5374,13 +5393,6 @@ var PDFFunction = (function PDFFunctionClosure() { //var mask = IR[8]; var range = IR[9]; - if (m !== args.length) { - error('Incorrect number of arguments: ' + m + ' != ' + - args.length); - } - - var x = args; - // Building the cube vertices: its part and sample index // http://rjwagner49.com/Mathematics/Interpolation.pdf var cubeVertices = 1 << m; @@ -5397,7 +5409,8 @@ var PDFFunction = (function PDFFunctionClosure() { // x_i' = min(max(x_i, Domain_2i), Domain_2i+1) var domain_2i = domain[i][0]; var domain_2i_1 = domain[i][1]; - var xi = Math.min(Math.max(x[i], domain_2i), domain_2i_1); + var xi = Math.min(Math.max(src[srcOffset +i], domain_2i), + domain_2i_1); // e_i = Interpolate(x_i', Domain_2i, Domain_2i+1, // Encode_2i, Encode_2i+1) @@ -5428,7 +5441,6 @@ var PDFFunction = (function PDFFunctionClosure() { pos <<= 1; } - var y = new Float64Array(n); for (j = 0; j < n; ++j) { // Sum all cube vertices' samples portions var rj = 0; @@ -5441,10 +5453,9 @@ var PDFFunction = (function PDFFunctionClosure() { rj = interpolate(rj, 0, 1, decode[j][0], decode[j][1]); // y_j = min(max(r_j, range_2j), range_2j+1) - y[j] = Math.min(Math.max(rj, range[j][0]), range[j][1]); + dest[destOffset + j] = Math.min(Math.max(rj, range[j][0]), + range[j][1]); } - - return y; }; }, @@ -5475,16 +5486,13 @@ var PDFFunction = (function PDFFunctionClosure() { var length = diff.length; - return function constructInterpolatedFromIRResult(args) { - var x = (n === 1 ? args[0] : Math.pow(args[0], n)); + return function constructInterpolatedFromIRResult(src, srcOffset, + dest, destOffset) { + var x = n === 1 ? src[srcOffset] : Math.pow(src[srcOffset], n); - var out = []; for (var j = 0; j < length; ++j) { - out.push(c0[j] + (x * diff[j])); + dest[destOffset + j] = c0[j] + (x * diff[j]); } - - return out; - }; }, @@ -5518,12 +5526,14 @@ var PDFFunction = (function PDFFunctionClosure() { var encode = IR[3]; var fnsIR = IR[4]; var fns = []; + var tmpBuf = new Float32Array(1); for (var i = 0, ii = fnsIR.length; i < ii; i++) { fns.push(PDFFunction.fromIR(fnsIR[i])); } - return function constructStichedFromIRResult(args) { + return function constructStichedFromIRResult(src, srcOffset, + dest, destOffset) { var clip = function constructStichedFromIRClip(v, min, max) { if (v > max) { v = max; @@ -5534,7 +5544,7 @@ var PDFFunction = (function PDFFunctionClosure() { }; // clip to domain - var v = clip(args[0], domain[0], domain[1]); + var v = clip(src[srcOffset], domain[0], domain[1]); // calulate which bound the value is in for (var i = 0, ii = bounds.length; i < ii; ++i) { if (v < bounds[i]) { @@ -5555,10 +5565,10 @@ var PDFFunction = (function PDFFunctionClosure() { var rmin = encode[2 * i]; var rmax = encode[2 * i + 1]; - var v2 = rmin + (v - dmin) * (rmax - rmin) / (dmax - dmin); + tmpBuf[0] = rmin + (v - dmin) * (rmax - rmin) / (dmax - dmin); // call the appropriate function - return fns[i]([v2]); + fns[i](tmpBuf, 0, dest, destOffset); }; }, @@ -5587,6 +5597,18 @@ var PDFFunction = (function PDFFunctionClosure() { var domain = IR[1]; var range = IR[2]; var code = IR[3]; + + var compiled = (new PostScriptCompiler()).compile(code, domain, range); + if (compiled) { + // Compiled function consists of simple expressions such as addition, + // subtraction, Math.max, and also contains 'var' and 'return' + // statements. See the generation in the PostScriptCompiler below. + /*jshint -W054 */ + return new Function('src', 'srcOffset', 'dest', 'destOffset', compiled); + } + + info('Unable to compile PS function'); + var numOutputs = range.length >> 1; var numInputs = domain.length >> 1; var evaluator = new PostScriptEvaluator(code); @@ -5597,22 +5619,26 @@ var PDFFunction = (function PDFFunctionClosure() { // seen in our tests. var MAX_CACHE_SIZE = 2048 * 4; var cache_available = MAX_CACHE_SIZE; - return function constructPostScriptFromIRResult(args) { + var tmpBuf = new Float32Array(numInputs); + + return function constructPostScriptFromIRResult(src, srcOffset, + dest, destOffset) { var i, value; var key = ''; - var input = new Array(numInputs); + var input = tmpBuf; for (i = 0; i < numInputs; i++) { - value = args[i]; + value = src[srcOffset + i]; input[i] = value; key += value + '_'; } var cachedValue = cache[key]; if (cachedValue !== undefined) { - return cachedValue; + cachedValue.set(dest, destOffset); + return; } - var output = new Array(numOutputs); + var output = new Float32Array(numOutputs); var stack = evaluator.execute(input); var stackIndex = stack.length - numOutputs; for (i = 0; i < numOutputs; i++) { @@ -5632,7 +5658,7 @@ var PDFFunction = (function PDFFunctionClosure() { cache_available--; cache[key] = output; } - return output; + output.set(dest, destOffset); }; } }; @@ -5655,7 +5681,8 @@ function isPDFFunction(v) { var PostScriptStack = (function PostScriptStackClosure() { var MAX_STACK_SIZE = 100; function PostScriptStack(initialStack) { - this.stack = initialStack || []; + this.stack = !initialStack ? [] : + Array.prototype.slice.call(initialStack, 0); } PostScriptStack.prototype = { @@ -5938,6 +5965,373 @@ var PostScriptEvaluator = (function PostScriptEvaluatorClosure() { return PostScriptEvaluator; })(); +// Most of the PDFs functions consist of simple operations such as: +// roll, exch, sub, cvr, pop, index, dup, mul, if, gt, add. +// +// We can compile most of such programs, and at the same moment, we can +// optimize some expressions using basic math properties. Keeping track of +// min/max values will allow us to avoid extra Math.min/Math.max calls. +var PostScriptCompiler = (function PostScriptCompilerClosure() { + function AstNode(type) { + this.type = type; + } + AstNode.prototype.visit = function (visitor) { + throw new Error('abstract method'); + }; + + function AstArgument(index, min, max) { + AstNode.call(this, 'args'); + this.index = index; + this.min = min; + this.max = max; + } + AstArgument.prototype = Object.create(AstNode.prototype); + AstArgument.prototype.visit = function (visitor) { + visitor.visitArgument(this); + }; + + function AstLiteral(number) { + AstNode.call(this, 'literal'); + this.number = number; + this.min = number; + this.max = number; + } + AstLiteral.prototype = Object.create(AstNode.prototype); + AstLiteral.prototype.visit = function (visitor) { + visitor.visitLiteral(this); + }; + + function AstBinaryOperation(op, arg1, arg2, min, max) { + AstNode.call(this, 'binary'); + this.op = op; + this.arg1 = arg1; + this.arg2 = arg2; + this.min = min; + this.max = max; + } + AstBinaryOperation.prototype = Object.create(AstNode.prototype); + AstBinaryOperation.prototype.visit = function (visitor) { + visitor.visitBinaryOperation(this); + }; + + function AstMin(arg, max) { + AstNode.call(this, 'max'); + this.arg = arg; + this.min = arg.min; + this.max = max; + } + AstMin.prototype = Object.create(AstNode.prototype); + AstMin.prototype.visit = function (visitor) { + visitor.visitMin(this); + }; + + function AstVariable(index, min, max) { + AstNode.call(this, 'var'); + this.index = index; + this.min = min; + this.max = max; + } + AstVariable.prototype = Object.create(AstNode.prototype); + AstVariable.prototype.visit = function (visitor) { + visitor.visitVariable(this); + }; + + function AstVariableDefinition(variable, arg) { + AstNode.call(this, 'definition'); + this.variable = variable; + this.arg = arg; + } + AstVariableDefinition.prototype = Object.create(AstNode.prototype); + AstVariableDefinition.prototype.visit = function (visitor) { + visitor.visitVariableDefinition(this); + }; + + function ExpressionBuilderVisitor() { + this.parts = []; + } + ExpressionBuilderVisitor.prototype = { + visitArgument: function (arg) { + this.parts.push('Math.max(', arg.min, ', Math.min(', + arg.max, ', src[srcOffset + ', arg.index, ']))'); + }, + visitVariable: function (variable) { + this.parts.push('v', variable.index); + }, + visitLiteral: function (literal) { + this.parts.push(literal.number); + }, + visitBinaryOperation: function (operation) { + this.parts.push('('); + operation.arg1.visit(this); + this.parts.push(' ', operation.op, ' '); + operation.arg2.visit(this); + this.parts.push(')'); + }, + visitVariableDefinition: function (definition) { + this.parts.push('var '); + definition.variable.visit(this); + this.parts.push(' = '); + definition.arg.visit(this); + this.parts.push(';'); + }, + visitMin: function (max) { + this.parts.push('Math.min('); + max.arg.visit(this); + this.parts.push(', ', max.max, ')'); + }, + toString: function () { + return this.parts.join(''); + } + }; + + function buildAddOperation(num1, num2) { + if (num2.type === 'literal' && num2.number === 0) { + // optimization: second operand is 0 + return num1; + } + if (num1.type === 'literal' && num1.number === 0) { + // optimization: first operand is 0 + return num2; + } + if (num2.type === 'literal' && num1.type === 'literal') { + // optimization: operands operand are literals + return new AstLiteral(num1.number + num2.number); + } + return new AstBinaryOperation('+', num1, num2, + num1.min + num2.min, num1.max + num2.max); + } + + function buildMulOperation(num1, num2) { + if (num2.type === 'literal') { + // optimization: second operands is a literal... + if (num2.number === 0) { + return new AstLiteral(0); // and it's 0 + } else if (num2.number === 1) { + return num1; // and it's 1 + } else if (num1.type === 'literal') { + // ... and first operands is a literal too + return new AstLiteral(num1.number * num2.number); + } + } + if (num1.type === 'literal') { + // optimization: first operands is a literal... + if (num1.number === 0) { + return new AstLiteral(0); // and it's 0 + } else if (num1.number === 1) { + return num2; // and it's 1 + } + } + var min = Math.min(num1.min * num2.min, num1.min * num2.max, + num1.max * num2.min, num1.max * num2.max); + var max = Math.max(num1.min * num2.min, num1.min * num2.max, + num1.max * num2.min, num1.max * num2.max); + return new AstBinaryOperation('*', num1, num2, min, max); + } + + function buildSubOperation(num1, num2) { + if (num2.type === 'literal') { + // optimization: second operands is a literal... + if (num2.number === 0) { + return num1; // ... and it's 0 + } else if (num1.type === 'literal') { + // ... and first operands is a literal too + return new AstLiteral(num1.number - num2.number); + } + } + if (num2.type === 'binary' && num2.op === '-' && + num1.type === 'literal' && num1.number === 1 && + num2.arg1.type === 'literal' && num2.arg1.number === 1) { + // optimization for case: 1 - (1 - x) + return num2.arg2; + } + return new AstBinaryOperation('-', num1, num2, + num1.min - num2.max, num1.max - num2.min); + } + + function buildMinOperation(num1, max) { + if (num1.min >= max) { + // optimization: num1 min value is not less than required max + return new AstLiteral(max); // just returning max + } else if (num1.max <= max) { + // optimization: num1 max value is not greater than required max + return num1; // just returning an argument + } + return new AstMin(num1, max); + } + + function PostScriptCompiler() {} + PostScriptCompiler.prototype = { + compile: function PostScriptCompiler_compile(code, domain, range) { + var stack = []; + var i, ii; + var instructions = []; + var inputSize = domain.length >> 1, outputSize = range.length >> 1; + var lastRegister = 0; + var n, j, min, max; + var num1, num2, ast1, ast2, tmpVar, item; + for (i = 0; i < inputSize; i++) { + stack.push(new AstArgument(i, domain[i * 2], domain[i * 2 + 1])); + } + + for (i = 0, ii = code.length; i < ii; i++) { + item = code[i]; + if (typeof item === 'number') { + stack.push(new AstLiteral(item)); + continue; + } + + switch (item) { + case 'add': + if (stack.length < 2) { + return null; + } + num2 = stack.pop(); + num1 = stack.pop(); + stack.push(buildAddOperation(num1, num2)); + break; + case 'cvr': + if (stack.length < 1) { + return null; + } + break; + case 'mul': + if (stack.length < 2) { + return null; + } + num2 = stack.pop(); + num1 = stack.pop(); + stack.push(buildMulOperation(num1, num2)); + break; + case 'sub': + if (stack.length < 2) { + return null; + } + num2 = stack.pop(); + num1 = stack.pop(); + stack.push(buildSubOperation(num1, num2)); + break; + case 'exch': + if (stack.length < 2) { + return null; + } + ast1 = stack.pop(); ast2 = stack.pop(); + stack.push(ast1, ast2); + break; + case 'pop': + if (stack.length < 1) { + return null; + } + stack.pop(); + break; + case 'index': + if (stack.length < 1) { + return null; + } + num1 = stack.pop(); + if (num1.type !== 'literal') { + return null; + } + n = num1.number; + if (n < 0 || (n|0) !== n || stack.length < n) { + return null; + } + ast1 = stack[stack.length - n - 1]; + if (ast1.type === 'literal' || ast1.type === 'var') { + stack.push(ast1); + break; + } + tmpVar = new AstVariable(lastRegister++, ast1.min, ast1.max); + stack[stack.length - n - 1] = tmpVar; + stack.push(tmpVar); + instructions.push(new AstVariableDefinition(tmpVar, ast1)); + break; + case 'dup': + if (stack.length < 1) { + return null; + } + if (typeof code[i + 1] === 'number' && code[i + 2] === 'gt' && + code[i + 3] === i + 7 && code[i + 4] === 'jz' && + code[i + 5] === 'pop' && code[i + 6] === code[i + 1]) { + // special case of the commands sequence for the min operation + num1 = stack.pop(); + stack.push(buildMinOperation(num1, code[i + 1])); + i += 6; + break; + } + ast1 = stack[stack.length - 1]; + if (ast1.type === 'literal' || ast1.type === 'var') { + // we don't have to save into intermediate variable a literal or + // variable. + stack.push(ast1); + break; + } + tmpVar = new AstVariable(lastRegister++, ast1.min, ast1.max); + stack[stack.length - 1] = tmpVar; + stack.push(tmpVar); + instructions.push(new AstVariableDefinition(tmpVar, ast1)); + break; + case 'roll': + if (stack.length < 2) { + return null; + } + num2 = stack.pop(); + num1 = stack.pop(); + if (num2.type !== 'literal' || num1.type !== 'literal') { + // both roll operands must be numbers + return null; + } + j = num2.number; + n = num1.number; + if (n <= 0 || (n|0) !== n || (j|0) !== j || stack.length < n) { + // ... and integers + return null; + } + j = ((j % n) + n) % n; + if (j === 0) { + break; // just skipping -- there are nothing to rotate + } + Array.prototype.push.apply(stack, + stack.splice(stack.length - n, n - j)); + break; + default: + return null; // unsupported operator + } + } + + if (stack.length !== outputSize) { + return null; + } + + var result = []; + instructions.forEach(function (instruction) { + var statementBuilder = new ExpressionBuilderVisitor(); + instruction.visit(statementBuilder); + result.push(statementBuilder.toString()); + }); + stack.forEach(function (expr, i) { + var statementBuilder = new ExpressionBuilderVisitor(); + expr.visit(statementBuilder); + var min = range[i * 2], max = range[i * 2 + 1]; + var out = [statementBuilder.toString()]; + if (min > expr.min) { + out.unshift('Math.max(', min, ', '); + out.push(')'); + } + if (max < expr.max) { + out.unshift('Math.min(', max, ', '); + out.push(')'); + } + out.unshift('dest[destOffset + ', i, '] = '); + out.push(';'); + result.push(out.join('')); + }); + return result.join('\n'); + } + }; + + return PostScriptCompiler; +})(); + var ColorSpace = (function ColorSpaceClosure() { // Constructor should define this.numComps, this.defaultColor, this.name @@ -6300,23 +6694,20 @@ var AlternateCS = (function AlternateCSClosure() { } this.base = base; this.tintFn = tintFn; + this.tmpBuf = new Float32Array(base.numComps); } AlternateCS.prototype = { getRgb: ColorSpace.prototype.getRgb, getRgbItem: function AlternateCS_getRgbItem(src, srcOffset, dest, destOffset) { - var baseNumComps = this.base.numComps; - var input = 'subarray' in src ? - src.subarray(srcOffset, srcOffset + this.numComps) : - Array.prototype.slice.call(src, srcOffset, srcOffset + this.numComps); - var tinted = this.tintFn(input); - this.base.getRgbItem(tinted, 0, dest, destOffset); + var tmpBuf = this.tmpBuf; + this.tintFn(src, srcOffset, tmpBuf, 0); + this.base.getRgbItem(tmpBuf, 0, dest, destOffset); }, getRgbBuffer: function AlternateCS_getRgbBuffer(src, srcOffset, count, dest, destOffset, bits, alpha01) { - var tinted; var tintFn = this.tintFn; var base = this.base; var scale = 1 / ((1 << bits) - 1); @@ -6329,13 +6720,14 @@ var AlternateCS = (function AlternateCSClosure() { var numComps = this.numComps; var scaled = new Float32Array(numComps); + var tinted = new Float32Array(baseNumComps); var i, j; if (usesZeroToOneRange) { for (i = 0; i < count; i++) { for (j = 0; j < numComps; j++) { scaled[j] = src[srcOffset++] * scale; } - tinted = tintFn(scaled); + tintFn(scaled, 0, tinted, 0); for (j = 0; j < baseNumComps; j++) { baseBuf[pos++] = tinted[j] * 255; } @@ -6345,7 +6737,7 @@ var AlternateCS = (function AlternateCSClosure() { for (j = 0; j < numComps; j++) { scaled[j] = src[srcOffset++] * scale; } - tinted = tintFn(scaled); + tintFn(scaled, 0, tinted, 0); base.getRgbItem(tinted, 0, baseBuf, pos); pos += baseNumComps; } @@ -8965,29 +9357,7 @@ Shadings.RadialAxial = (function RadialAxialClosure() { this.extendEnd = extendEnd; var fnObj = dict.get('Function'); - var fn; - if (isArray(fnObj)) { - var fnArray = []; - for (var j = 0, jj = fnObj.length; j < jj; j++) { - var obj = xref.fetchIfRef(fnObj[j]); - if (!isPDFFunction(obj)) { - error('Invalid function'); - } - fnArray.push(PDFFunction.parse(xref, obj)); - } - fn = function radialAxialColorFunction(arg) { - var out = []; - for (var i = 0, ii = fnArray.length; i < ii; i++) { - out.push(fnArray[i](arg)[0]); - } - return out; - }; - } else { - if (!isPDFFunction(fnObj)) { - error('Invalid function'); - } - fn = PDFFunction.parse(xref, fnObj); - } + var fn = PDFFunction.parseArray(xref, fnObj); // 10 samples seems good enough for now, but probably won't work // if there are sharp color changes. Ideally, we would implement @@ -9005,9 +9375,12 @@ Shadings.RadialAxial = (function RadialAxialClosure() { return; } + var color = new Float32Array(cs.numComps), ratio = new Float32Array(1); var rgbColor; for (var i = t0; i <= t1; i += step) { - rgbColor = cs.getRgb(fn([i]), 0); + ratio[0] = i; + fn(ratio, 0, color, 0); + rgbColor = cs.getRgb(color, 0); var cssColor = Util.makeCssRgb(rgbColor); colorStops.push([(i - t0) / diff, cssColor]); } @@ -9075,6 +9448,12 @@ Shadings.Mesh = (function MeshClosure() { this.context = context; this.buffer = 0; this.bufferLength = 0; + + var numComps = context.numComps; + this.tmpCompsBuf = new Float32Array(numComps); + var csNumComps = context.colorSpace; + this.tmpCsCompsBuf = context.colorFn ? new Float32Array(csNumComps) : + this.tmpCompsBuf; } MeshStreamReader.prototype = { get hasData() { @@ -9145,15 +9524,16 @@ Shadings.Mesh = (function MeshClosure() { var scale = bitsPerComponent < 32 ? 1 / ((1 << bitsPerComponent) - 1) : 2.3283064365386963e-10; // 2 ^ -32 var decode = this.context.decode; - var components = []; + var components = this.tmpCompsBuf; for (var i = 0, j = 4; i < numComps; i++, j += 2) { var ci = this.readBits(bitsPerComponent); - components.push(ci * scale * (decode[j + 1] - decode[j]) + decode[j]); + components[i] = ci * scale * (decode[j + 1] - decode[j]) + decode[j]; } + var color = this.tmpCsCompsBuf; if (this.context.colorFn) { - components = this.context.colorFn(components); + this.context.colorFn(components, 0, color, 0); } - return this.context.colorSpace.getRgb(components, 0); + return this.context.colorSpace.getRgb(color, 0); } }; @@ -9559,31 +9939,7 @@ Shadings.Mesh = (function MeshClosure() { cs.getRgb(dict.get('Background'), 0) : null; var fnObj = dict.get('Function'); - var fn; - if (!fnObj) { - fn = null; - } else if (isArray(fnObj)) { - var fnArray = []; - for (var j = 0, jj = fnObj.length; j < jj; j++) { - var obj = xref.fetchIfRef(fnObj[j]); - if (!isPDFFunction(obj)) { - error('Invalid function'); - } - fnArray.push(PDFFunction.parse(xref, obj)); - } - fn = function radialAxialColorFunction(arg) { - var out = []; - for (var i = 0, ii = fnArray.length; i < ii; i++) { - out.push(fnArray[i](arg)[0]); - } - return out; - }; - } else { - if (!isPDFFunction(fnObj)) { - error('Invalid function'); - } - fn = PDFFunction.parse(xref, fnObj); - } + var fn = fnObj ? PDFFunction.parseArray(xref, fnObj) : null; this.coords = []; this.colors = []; diff --git a/package.json b/package.json index f5b117ebc..19fe34130 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pdfjs-dist", - "version": "1.0.579", + "version": "1.0.582", "description": "Generic build of Mozilla's PDF.js library.", "keywords": [ "Mozilla",