From 9bb2b881b0bd0984b28cab66ef4fcdc084c633c3 Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Tue, 8 Nov 2011 08:48:10 -0800 Subject: [PATCH 01/35] Adds cmyk jpg support. Treats the cmyk jpg as a regular stream. --- Makefile | 1 + examples/helloworld/index.html | 1 + src/core.js | 2 +- src/evaluator.js | 2 +- src/image.js | 12 +++++----- src/stream.js | 44 +++++++++++++++++++++++++++------- test/test_slave.html | 1 + web/viewer.html | 2 +- 8 files changed, 47 insertions(+), 18 deletions(-) diff --git a/Makefile b/Makefile index 3e385b175..2b566cbbf 100644 --- a/Makefile +++ b/Makefile @@ -33,6 +33,7 @@ PDF_JS_FILES = \ pattern.js \ stream.js \ worker.js \ + ../external/jpgjs/jpg.js \ $(NULL) # make server diff --git a/examples/helloworld/index.html b/examples/helloworld/index.html index a48e3705b..a2485d918 100644 --- a/examples/helloworld/index.html +++ b/examples/helloworld/index.html @@ -22,6 +22,7 @@ + + - + From 1869888838666b73c6956b046b60bfac7c9968ec Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Tue, 8 Nov 2011 08:50:01 -0800 Subject: [PATCH 02/35] Comment on the new flag. --- src/stream.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/stream.js b/src/stream.js index de07e1387..d8a681a88 100644 --- a/src/stream.js +++ b/src/stream.js @@ -793,6 +793,8 @@ var JpegStream = (function jpegStream() { // TODO: per poppler, some images may have 'junk' before that // need to be removed this.dict = dict; + + // Flag indicating wether the image can be natively loaded. this.isNative = true; if (isAdobeImage(bytes)) { From 8d52a1e92a450f5b43049b3f1527bad8aaf79bfe Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Tue, 8 Nov 2011 13:53:56 -0800 Subject: [PATCH 03/35] Add another way to detect cmyk images. --- src/parser.js | 2 +- src/stream.js | 28 ++++++++++++++++++++++++---- src/worker_loader.js | 3 ++- 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/src/parser.js b/src/parser.js index a740615ed..93a3f21b5 100644 --- a/src/parser.js +++ b/src/parser.js @@ -236,7 +236,7 @@ var Parser = (function parserParser() { return new LZWStream(stream, earlyChange); } else if (name == 'DCTDecode' || name == 'DCT') { var bytes = stream.getBytes(length); - return new JpegStream(bytes, stream.dict); + return new JpegStream(bytes, stream.dict, this.xref); } else if (name == 'ASCII85Decode' || name == 'A85') { return new Ascii85Stream(stream); } else if (name == 'ASCIIHexDecode' || name == 'AHx') { diff --git a/src/stream.js b/src/stream.js index d8a681a88..7634e50bb 100644 --- a/src/stream.js +++ b/src/stream.js @@ -776,6 +776,25 @@ var JpegStream = (function jpegStream() { return false; } + function isCmykAdobe(bytes) { + var maxBytesScanned = Math.max(bytes.length - 16, 1024); + // Looking for APP14, 'Adobe' + for (var i = 0; i < maxBytesScanned; ++i) { + if (bytes[i] == 0xFF && bytes[i + 1] == 0xEE && + bytes[i + 2] == 0x00 && bytes[i + 3] == 0x0E && + bytes[i + 4] == 0x41 && bytes[i + 5] == 0x64 && + bytes[i + 6] == 0x6F && bytes[i + 7] == 0x62 && + bytes[i + 8] == 0x65 && bytes[i + 9] == 0 && + bytes[i + 15] == 2 ) { + return true; + } + // scanning until frame tag + if (bytes[i] == 0xFF && bytes[i + 1] == 0xC0) + break; + } + return false; + } + function fixAdobeImage(bytes) { // Inserting 'EMBED' marker after JPEG signature var embedMarker = new Uint8Array([0xFF, 0xEC, 0, 8, 0x45, 0x4D, 0x42, 0x45, @@ -789,19 +808,20 @@ var JpegStream = (function jpegStream() { return newBytes; } - function constructor(bytes, dict) { + function constructor(bytes, dict, xref) { // TODO: per poppler, some images may have 'junk' before that // need to be removed this.dict = dict; - + // Flag indicating wether the image can be natively loaded. this.isNative = true; if (isAdobeImage(bytes)) { // when bug 674619 land, let's check if browser can do // normal cmyk and then we won't have to the following - var cs = dict.get('ColorSpace'); + var cs = xref.fetchIfRef(dict.get('ColorSpace')); if (isName(cs) && cs.name === 'DeviceCMYK') { + //if (isCmykAdobe(bytes)) { this.isNative = false; this.bytes = bytes; } else { @@ -826,7 +846,7 @@ var JpegStream = (function jpegStream() { var height = jpegImage.height; var dataLength = width * height * 4; var data = new Uint8Array(dataLength); - jpegImage.getData(data, width, height); + jpegImage.getData(data, width, height, true); this.buffer = data; this.bufferLength = data.length; }; diff --git a/src/worker_loader.js b/src/worker_loader.js index cb0a91071..7141fa3e3 100644 --- a/src/worker_loader.js +++ b/src/worker_loader.js @@ -40,7 +40,8 @@ function onMessageLoader(evt) { 'parser.js', 'pattern.js', 'stream.js', - 'worker.js' + 'worker.js', + '../external/jpgjs/jpg.js' ]; // Load all the files. From 493805d9521ad064d099bbbb9ba5128611674c71 Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Tue, 8 Nov 2011 14:00:39 -0800 Subject: [PATCH 04/35] Remove the alternative way to do CMYK check. Checking the colorspace seems sufficient. --- src/stream.js | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/src/stream.js b/src/stream.js index 7634e50bb..ce69e96aa 100644 --- a/src/stream.js +++ b/src/stream.js @@ -776,25 +776,6 @@ var JpegStream = (function jpegStream() { return false; } - function isCmykAdobe(bytes) { - var maxBytesScanned = Math.max(bytes.length - 16, 1024); - // Looking for APP14, 'Adobe' - for (var i = 0; i < maxBytesScanned; ++i) { - if (bytes[i] == 0xFF && bytes[i + 1] == 0xEE && - bytes[i + 2] == 0x00 && bytes[i + 3] == 0x0E && - bytes[i + 4] == 0x41 && bytes[i + 5] == 0x64 && - bytes[i + 6] == 0x6F && bytes[i + 7] == 0x62 && - bytes[i + 8] == 0x65 && bytes[i + 9] == 0 && - bytes[i + 15] == 2 ) { - return true; - } - // scanning until frame tag - if (bytes[i] == 0xFF && bytes[i + 1] == 0xC0) - break; - } - return false; - } - function fixAdobeImage(bytes) { // Inserting 'EMBED' marker after JPEG signature var embedMarker = new Uint8Array([0xFF, 0xEC, 0, 8, 0x45, 0x4D, 0x42, 0x45, @@ -821,7 +802,6 @@ var JpegStream = (function jpegStream() { // normal cmyk and then we won't have to the following var cs = xref.fetchIfRef(dict.get('ColorSpace')); if (isName(cs) && cs.name === 'DeviceCMYK') { - //if (isCmykAdobe(bytes)) { this.isNative = false; this.bytes = bytes; } else { From 073af783070e15863ab688954055d098e4cd8e9c Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Wed, 9 Nov 2011 09:29:33 -0800 Subject: [PATCH 05/35] Handle device gray and pass in the color transform option. --- src/stream.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/stream.js b/src/stream.js index ce69e96aa..65e122ad0 100644 --- a/src/stream.js +++ b/src/stream.js @@ -797,16 +797,21 @@ var JpegStream = (function jpegStream() { // Flag indicating wether the image can be natively loaded. this.isNative = true; + this.colorTransform = -1; + if (isAdobeImage(bytes)) { // when bug 674619 land, let's check if browser can do // normal cmyk and then we won't have to the following var cs = xref.fetchIfRef(dict.get('ColorSpace')); - if (isName(cs) && cs.name === 'DeviceCMYK') { - this.isNative = false; - this.bytes = bytes; - } else { + + // DeviceRGB and DeviceGray are the only Adobe images that work natively + if (isName(cs) && (cs.name === 'DeviceRGB' || cs.name === 'DeviceGray')) { bytes = fixAdobeImage(bytes); this.src = bytesToString(bytes); + } else { + this.colorTransform = dict.get('ColorTransform'); + this.isNative = false; + this.bytes = bytes; } } else { this.src = bytesToString(bytes); @@ -820,7 +825,7 @@ var JpegStream = (function jpegStream() { constructor.prototype.ensureBuffer = function jpegStreamEnsureBuffer(req) { if (this.bufferLength) return; - var jpegImage = new JpegImage(); + var jpegImage = new JpegImage(this.colorTransform); jpegImage.parse(this.bytes); var width = jpegImage.width; var height = jpegImage.height; From 22e531b1f2c48dbdc4f78ab28df181c4d5e8e0e5 Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Wed, 9 Nov 2011 09:36:57 -0800 Subject: [PATCH 06/35] Adding the jpgjs as submodule. --- .gitmodules | 3 +++ external/jpgjs | 1 + 2 files changed, 4 insertions(+) create mode 100644 .gitmodules create mode 160000 external/jpgjs diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..5a852cd3f --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "external/jpgjs"] + path = external/jpgjs + url = git@github.com:brendandahl/jpgjs.git diff --git a/external/jpgjs b/external/jpgjs new file mode 160000 index 000000000..592bec146 --- /dev/null +++ b/external/jpgjs @@ -0,0 +1 @@ +Subproject commit 592bec14658593fe90df1309926a38177889042e From 3d662cf08a559c8a074bbe8440fe3074d413f1a2 Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Wed, 9 Nov 2011 09:39:55 -0800 Subject: [PATCH 07/35] Add the cmyk jpeg test file. --- test/pdfs/.gitignore | 2 +- test/pdfs/cmykjpeg.pdf | Bin 0 -> 141727 bytes test/test_manifest.json | 7 +++++++ 3 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 test/pdfs/cmykjpeg.pdf diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore index 443cb155a..854612f83 100644 --- a/test/pdfs/.gitignore +++ b/test/pdfs/.gitignore @@ -14,4 +14,4 @@ !sizes.pdf !close-path-bug.pdf !alphatrans.pdf - +!cmykjpeg.pdf diff --git a/test/pdfs/cmykjpeg.pdf b/test/pdfs/cmykjpeg.pdf new file mode 100644 index 0000000000000000000000000000000000000000..2f35ef763824b72d78054d53974eecabd87d5981 GIT binary patch literal 141727 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 D8?>TF{ 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 z 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%0Ds{6}h0| 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 Date: Wed, 9 Nov 2011 09:50:40 -0800 Subject: [PATCH 08/35] Data is now created by jpg.js since we don't know the size. --- external/jpgjs | 2 +- src/stream.js | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/external/jpgjs b/external/jpgjs index 592bec146..0d69fa811 160000 --- a/external/jpgjs +++ b/external/jpgjs @@ -1 +1 @@ -Subproject commit 592bec14658593fe90df1309926a38177889042e +Subproject commit 0d69fa8117fd72c58d85f8b0536d7eebd7b96c47 diff --git a/src/stream.js b/src/stream.js index 65e122ad0..69f792b58 100644 --- a/src/stream.js +++ b/src/stream.js @@ -829,9 +829,7 @@ var JpegStream = (function jpegStream() { jpegImage.parse(this.bytes); var width = jpegImage.width; var height = jpegImage.height; - var dataLength = width * height * 4; - var data = new Uint8Array(dataLength); - jpegImage.getData(data, width, height, true); + var data = jpegImage.getData(width, height); this.buffer = data; this.bufferLength = data.length; }; From ceda6aada8b90d58a17c5d132ddd4885aecc6e62 Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Wed, 9 Nov 2011 16:38:55 -0800 Subject: [PATCH 09/35] Update readme to init the submodule. --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index deb925601..be342060d 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,8 @@ To get a local copy of the current code, clone it using git: $ git clone git://github.com/mozilla/pdf.js.git pdfjs $ cd pdfjs + $ git submodule init + $ git submodule update Next, you need to start a local web server as some browsers don't allow opening PDF files for a file:// url: From a12419c08469f71e910db864a55a904ea9fff473 Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Thu, 10 Nov 2011 08:41:36 -0800 Subject: [PATCH 10/35] Initial devicen colorspace support. --- src/colorspace.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/colorspace.js b/src/colorspace.js index 946a1bdf4..a75b66f0c 100644 --- a/src/colorspace.js +++ b/src/colorspace.js @@ -137,8 +137,14 @@ var ColorSpace = (function colorSpaceColorSpace() { var alt = ColorSpace.parseToIR(cs[2], xref, res); var tintFnIR = PDFFunction.getIR(xref, xref.fetchIfRef(cs[3])); return ['SeparationCS', alt, tintFnIR]; - case 'Lab': case 'DeviceN': + if (cs.length > 4) + TODO('devicen color with n channels'); + debugger; + var alt = ColorSpace.parseToIR(cs[2], xref, res); + var tintFnIR = PDFFunction.getIR(xref, xref.fetchIfRef(cs[3])); + return ['SeparationCS', alt, tintFnIR]; + case 'Lab': default: error('unimplemented color space object "' + mode + '"'); } From c198ec432335956ef564fae1a5e9e9c51bfbe53a Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Thu, 10 Nov 2011 14:06:42 -0800 Subject: [PATCH 11/35] Makes new alternate colorspace which handles separation and device. --- src/colorspace.js | 53 ++++++++++++++++++++++++++--------------------- 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/src/colorspace.js b/src/colorspace.js index a75b66f0c..953bd1c2e 100644 --- a/src/colorspace.js +++ b/src/colorspace.js @@ -24,7 +24,7 @@ var ColorSpace = (function colorSpaceColorSpace() { constructor.parse = function colorSpaceParse(cs, xref, res) { var IR = constructor.parseToIR(cs, xref, res); - if (IR instanceof SeparationCS) + if (IR instanceof AlternateCS) return IR; return constructor.fromIR(IR); @@ -50,11 +50,12 @@ var ColorSpace = (function colorSpaceColorSpace() { var hiVal = IR[2]; var lookup = IR[3]; return new IndexedCS(ColorSpace.fromIR(baseIndexedCS), hiVal, lookup); - case 'SeparationCS': - var alt = IR[1]; - var tintFnIR = IR[2]; + case 'AlternateCS': + var numComps = IR[1]; + var alt = IR[2]; + var tintFnIR = IR[3]; - return new SeparationCS(ColorSpace.fromIR(alt), + return new AlternateCS(numComps, ColorSpace.fromIR(alt), PDFFunction.fromIR(tintFnIR)); default: error('Unkown name ' + name); @@ -134,16 +135,16 @@ var ColorSpace = (function colorSpaceColorSpace() { var lookup = xref.fetchIfRef(cs[3]); return ['IndexedCS', baseIndexedCS, hiVal, lookup]; case 'Separation': - var alt = ColorSpace.parseToIR(cs[2], xref, res); - var tintFnIR = PDFFunction.getIR(xref, xref.fetchIfRef(cs[3])); - return ['SeparationCS', alt, tintFnIR]; case 'DeviceN': - if (cs.length > 4) - TODO('devicen color with n channels'); - debugger; + var name = cs[1]; + var numComps = 1; + if (isName(name)) + numComps = 1; + else if (isArray(name)) + numComps = name.length; var alt = ColorSpace.parseToIR(cs[2], xref, res); var tintFnIR = PDFFunction.getIR(xref, xref.fetchIfRef(cs[3])); - return ['SeparationCS', alt, tintFnIR]; + return ['AlternateCS', numComps, alt, tintFnIR]; case 'Lab': default: error('unimplemented color space object "' + mode + '"'); @@ -157,33 +158,37 @@ var ColorSpace = (function colorSpaceColorSpace() { return constructor; })(); -var SeparationCS = (function separationCS() { - function constructor(base, tintFn) { - this.name = 'Separation'; - this.numComps = 1; +var AlternateCS = (function alternateCS() { + function constructor(numComps, base, tintFn) { + this.name = 'Alternate'; + this.numComps = numComps; this.defaultColor = [1]; this.base = base; this.tintFn = tintFn; } constructor.prototype = { - getRgb: function sepcs_getRgb(color) { + getRgb: function altcs_getRgb(color) { var tinted = this.tintFn(color); return this.base.getRgb(tinted); }, - getRgbBuffer: function sepcs_getRgbBuffer(input, bits) { + getRgbBuffer: function altcs_getRgbBuffer(input, bits) { var tintFn = this.tintFn; var base = this.base; var scale = 1 / ((1 << bits) - 1); var length = input.length; var pos = 0; - var numComps = base.numComps; - var baseBuf = new Uint8Array(numComps * length); + var baseNumComps = base.numComps; + var baseBuf = new Uint8Array(baseNumComps * length); + var numComps = this.numComps; + var scaled = new Array(numComps); - for (var i = 0; i < length; ++i) { - var scaled = input[i] * scale; - var tinted = tintFn([scaled]); - for (var j = 0; j < numComps; ++j) + for (var i = 0; i < length; i += numComps) { + for (var z = 0; z < numComps; ++z) + scaled[z] = input[i + z] * scale; + + var tinted = tintFn(scaled); + for (var j = 0; j < baseNumComps; ++j) baseBuf[pos++] = 255 * tinted[j]; } return base.getRgbBuffer(baseBuf, 8); From 74004b23bba4daa4955476559abcf3a44acb54e3 Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Thu, 10 Nov 2011 14:23:58 -0800 Subject: [PATCH 12/35] Add comments, fix default color. --- src/colorspace.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/colorspace.js b/src/colorspace.js index 953bd1c2e..b369d0f88 100644 --- a/src/colorspace.js +++ b/src/colorspace.js @@ -158,11 +158,19 @@ var ColorSpace = (function colorSpaceColorSpace() { return constructor; })(); +/** + * Alternate color space handles both Separation and DeviceN color spaces. A + * Separation color space is actually just a DeviceN with one color component. + * Both color spaces use a tinting function to convert colors to a base color + * space. + */ var AlternateCS = (function alternateCS() { function constructor(numComps, base, tintFn) { this.name = 'Alternate'; this.numComps = numComps; - this.defaultColor = [1]; + this.defaultColor = []; + for (var i = 0; i < numComps; ++i) + this.defaultColor.push(1); this.base = base; this.tintFn = tintFn; } From 543e3377de27e9281fd7bef52f9fd9945ef74b08 Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Fri, 11 Nov 2011 14:44:47 -0800 Subject: [PATCH 13/35] Adding multi dimensional interpolation and test file. --- src/function.js | 142 ++++++++++++++++++++++++++-------------- test/pdfs/.gitignore | 1 + test/pdfs/devicen.pdf | Bin 0 -> 9550 bytes test/test_manifest.json | 7 ++ 4 files changed, 100 insertions(+), 50 deletions(-) create mode 100644 test/pdfs/devicen.pdf diff --git a/src/function.js b/src/function.js index 80c5a5460..2f9b050dd 100644 --- a/src/function.js +++ b/src/function.js @@ -20,6 +20,7 @@ var PDFFunction = (function pdfFunction() { var array = []; var codeSize = 0; var codeBuf = 0; + var sampleMul = 1.0 / (Math.pow(2.0, bps) - 1); var strBytes = str.getBytes((length * bps + 7) / 8); var strIdx = 0; @@ -30,7 +31,7 @@ var PDFFunction = (function pdfFunction() { codeSize += 8; } codeSize -= bps; - array.push(codeBuf >> codeSize); + array.push((codeBuf >> codeSize) * sampleMul); codeBuf &= (1 << codeSize) - 1; } return array; @@ -76,6 +77,17 @@ var PDFFunction = (function pdfFunction() { }, constructSampled: function pdfFunctionConstructSampled(str, dict) { + function toMultiArray(arr) { + var inputLength = arr.length; + var outputLength = arr.length / 2; + var out = new Array(outputLength); + var index = 0; + for (var i = 0; i < inputLength; i += 2) { + out[index] = [arr[i], arr[i + 1]]; + ++index; + } + return out; + } var domain = dict.get('Domain'); var range = dict.get('Range'); @@ -85,9 +97,8 @@ var PDFFunction = (function pdfFunction() { var inputSize = domain.length / 2; var outputSize = range.length / 2; - if (inputSize != 1) - error('No support for multi-variable inputs to functions: ' + - inputSize); + domain = toMultiArray(domain); + range = toMultiArray(range); var size = dict.get('Size'); var bps = dict.get('BitsPerSample'); @@ -105,15 +116,36 @@ var PDFFunction = (function pdfFunction() { encode.push(size[i] - 1); } } + encode = toMultiArray(encode); + var decode = dict.get('Decode'); if (!decode) decode = range; + else + decode = toMultiArray(decode); + + // Precalc the multipliers + var inputMul = new Float64Array(inputSize); + for (var i = 0; i < inputSize; ++i) { + inputMul[i] = (encode[i][1] - encode[i][0]) / + (domain[i][1] - domain[i][0]); + } + + var idxMul = new Int32Array(inputSize); + idxMul[0] = outputSize; + for (i = 1; i < inputSize; ++i) { + idxMul[i] = idxMul[i - 1] * size[i - 1]; + } + + var nSamples = outputSize; + for (i = 0; i < inputSize; ++i) + nSamples *= size[i]; var samples = this.getSampleArray(size, outputSize, bps, str); return [ CONSTRUCT_SAMPLED, inputSize, domain, encode, decode, samples, size, - outputSize, bps, range + outputSize, bps, range, inputMul, idxMul, nSamples ]; }, @@ -127,64 +159,74 @@ var PDFFunction = (function pdfFunction() { var outputSize = IR[7]; var bps = IR[8]; var range = IR[9]; + var inputMul = IR[10]; + var idxMul = IR[11]; + var nSamples = IR[12]; return function constructSampledFromIRResult(args) { - var clip = function constructSampledFromIRClip(v, min, max) { - if (v > max) - v = max; - else if (v < min) - v = min; - return v; - }; - if (inputSize != args.length) error('Incorrect number of arguments: ' + inputSize + ' != ' + args.length); + // Most of the below is a port of Poppler's implementation. + // TODO: There's a few other ways to do multilinear interpolation such + // as piecewise, which is much faster but an approximation. + var out = new Float64Array(outputSize); + var x; + var e = new Array(inputSize); + var efrac0 = new Float64Array(inputSize); + var efrac1 = new Float64Array(inputSize); + var sBuf = new Float64Array(1 << inputSize); + var i, j, k, idx, t; - for (var i = 0; i < inputSize; i++) { - var i2 = i * 2; - - // clip to the domain - var v = clip(args[i], domain[i2], domain[i2 + 1]); - - // encode - v = encode[i2] + ((v - domain[i2]) * - (encode[i2 + 1] - encode[i2]) / - (domain[i2 + 1] - domain[i2])); - - // clip to the size - args[i] = clip(v, 0, size[i] - 1); + // map input values into sample array + for (i = 0; i < inputSize; ++i) { + x = (args[i] - domain[i][0]) * inputMul[i] + encode[i][0]; + if (x < 0) { + x = 0; + } else if (x > size[i] - 1) { + x = size[i] - 1; + } + e[i] = [Math.floor(x), 0]; + if ((e[i][1] = e[i][0] + 1) >= size[i]) { + // this happens if in[i] = domain[i][1] + e[i][1] = e[i][0]; + } + efrac1[i] = x - e[i][0]; + efrac0[i] = 1 - efrac1[i]; } - // interpolate to table - TODO('Multi-dimensional interpolation'); - var floor = Math.floor(args[0]); - var ceil = Math.ceil(args[0]); - var scale = args[0] - floor; + // for each output, do m-linear interpolation + for (i = 0; i < outputSize; ++i) { - floor *= outputSize; - ceil *= outputSize; - - var output = [], v = 0; - for (var i = 0; i < outputSize; ++i) { - if (ceil == floor) { - v = samples[ceil + i]; - } else { - var low = samples[floor + i]; - var high = samples[ceil + i]; - v = low * scale + high * (1 - scale); + // pull 2^m values out of the sample array + for (j = 0; j < (1 << inputSize); ++j) { + idx = i; + for (k = 0, t = j; k < inputSize; ++k, t >>= 1) { + idx += idxMul[k] * (e[k][t & 1]); + } + if (idx >= 0 && idx < nSamples) { + sBuf[j] = samples[idx]; + } else { + sBuf[j] = 0; // TODO Investigate if this is what Adobe does + } } - var i2 = i * 2; - // decode - v = decode[i2] + (v * (decode[i2 + 1] - decode[i2]) / - ((1 << bps) - 1)); + // do m sets of interpolations + for (j = 0, t = (1 << inputSize); j < inputSize; ++j, t >>= 1) { + for (k = 0; k < t; k += 2) { + sBuf[k >> 1] = efrac0[j] * sBuf[k] + efrac1[j] * sBuf[k + 1]; + } + } - // clip to the domain - output.push(clip(v, range[i2], range[i2 + 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]; + } } - - return output; + return out; } }, diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore index 443cb155a..ac3dabb70 100644 --- a/test/pdfs/.gitignore +++ b/test/pdfs/.gitignore @@ -14,4 +14,5 @@ !sizes.pdf !close-path-bug.pdf !alphatrans.pdf +!devicen.pdf diff --git a/test/pdfs/devicen.pdf b/test/pdfs/devicen.pdf new file mode 100644 index 0000000000000000000000000000000000000000..53ef9e5d5174b4d333c81ef8f79d22d17a5fc001 GIT binary patch literal 9550 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 literal 0 HcmV?d00001 diff --git a/test/test_manifest.json b/test/test_manifest.json index 8085506a2..4d2c859b5 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -268,5 +268,12 @@ "link": false, "rounds": 1, "type": "eq" + }, + { "id": "devicen", + "file": "pdfs/devicen.pdf", + "md5": "b9dbf00ec6bf02fcbefbb40332713aad", + "link": false, + "rounds": 1, + "type": "eq" } ] From c2dd4523421c620da8fa8872bfc6719948710091 Mon Sep 17 00:00:00 2001 From: Kalervo Kujala Date: Sun, 13 Nov 2011 21:39:56 +0200 Subject: [PATCH 14/35] Create first unit test in Jasmine unit test framework. To run the unit test open pdf.js/test/unit/unit_test.html in your browser. This requires that https://github.com/pivotal/jasmine is cloned to the same directory level as pdf.js. --- test/unit/spec/obj_spec.js | 16 ++++++++++++ test/unit/unit_test.html | 51 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 test/unit/spec/obj_spec.js create mode 100644 test/unit/unit_test.html diff --git a/test/unit/spec/obj_spec.js b/test/unit/spec/obj_spec.js new file mode 100644 index 000000000..b79a21ed5 --- /dev/null +++ b/test/unit/spec/obj_spec.js @@ -0,0 +1,16 @@ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ + +'use strict'; + +describe("obj", function() { + + describe("Name", function() { + it("should retain the given name", function() { + var givenName = "My Name"; + var name = new Name(givenName); + expect(name.name).toEqual(givenName); + }); + }); +}); + diff --git a/test/unit/unit_test.html b/test/unit/unit_test.html new file mode 100644 index 000000000..cedd65c7a --- /dev/null +++ b/test/unit/unit_test.html @@ -0,0 +1,51 @@ + + + + pdf.js unit test + + + + + + + + + + + + + + + + + + + From 87d13cf803a1366f4838d7f6ae05d3713aefb806 Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Tue, 15 Nov 2011 15:05:31 -0800 Subject: [PATCH 15/35] Remove submodule. --- .gitmodules | 3 --- external/jpgjs | 1 - 2 files changed, 4 deletions(-) delete mode 100644 .gitmodules delete mode 160000 external/jpgjs diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 5a852cd3f..000000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "external/jpgjs"] - path = external/jpgjs - url = git@github.com:brendandahl/jpgjs.git diff --git a/external/jpgjs b/external/jpgjs deleted file mode 160000 index 0d69fa811..000000000 --- a/external/jpgjs +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 0d69fa8117fd72c58d85f8b0536d7eebd7b96c47 From 9e8d0795793b9c4edd85f95eec2fd5ef27fdcb2a Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Tue, 15 Nov 2011 15:08:13 -0800 Subject: [PATCH 16/35] Adding jpg.js back as a regular file. --- external/jpgjs/jpg.js | 722 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 722 insertions(+) create mode 100644 external/jpgjs/jpg.js diff --git a/external/jpgjs/jpg.js b/external/jpgjs/jpg.js new file mode 100644 index 000000000..6ae443c41 --- /dev/null +++ b/external/jpgjs/jpg.js @@ -0,0 +1,722 @@ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- / +/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ + +// - The JPEG specification can be found in the ITU CCITT Recommendation T.81 +// (www.w3.org/Graphics/JPEG/itu-t81.pdf) +// - The JFIF specification can be found in the JPEG File Interchange Format +// (www.w3.org/Graphics/JPEG/jfif3.pdf) +// - The Adobe Application-Specific JPEG markers in the Supporting the DCT Filters +// in PostScript Level 2, Technical Note #5116 +// (partners.adobe.com/public/developer/en/ps/sdk/5116.DCT_Filter.pdf) + +var JpegImage = (function jpegImage() { + "use strict"; + + function constructor(colorTransform) { + this.colorTransform = typeof colorTransform !== 'undefined' ? colorTransform : -1; + } + + var iDCTTables = (function initDCTTables() { + var cosTables = [], i, j; + for (i = 0; i < 8; i++) { + cosTables.push(new Float32Array(8)); + for (j = 0; j < 8; j++) + cosTables[i][j] = Math.cos((2 * i + 1) * j * Math.PI / 16) * + (j > 0 ? 1 : 1/Math.sqrt(2)); + } + + var zzTransform = new Int32Array([ + 0, 1, 5, 6, 14, 15, 27, 28, 2, 4, 7, 13, 16, 26, 29, 42, 3, 8, 12, + 17, 25, 30, 41, 43, 9, 11, 18, 24, 31, 40, 44, 53, 10, 19, 23, 32, + 39, 45, 52, 54, 20, 22, 33, 38, 46, 51, 55, 60, 21, 34, 37, 47, + 50, 56, 59, 61, 35, 36, 48, 49, 57, 58, 62, 63]); + + var x, y, u, v; + var tables = []; + for (y = 0; y < 8; y++) { + var cosTable_y = cosTables[y]; + for (x = 0; x < 8; x++) { + var cosTable_x = cosTables[x]; + var table = new Float32Array(64); + i = 0; + for (v = 0; v < 8; v++) { + for (u = 0; u < 8; u++) + table[zzTransform[i++]] = cosTable_x[u] * cosTable_y[v]; + } + tables.push(table); + } + } + return tables; + })(); + + function buildHuffmanTable(codeLengths, values) { + var k = 0, code = [], i, j, length = 16; + while (length > 0 && !codeLengths[length - 1]) + length--; + code.push({children: [], index: 0}); + var p = code[0], q; + for (i = 0; i < length; i++) { + for (j = 0; j < codeLengths[i]; j++) { + p = code.pop(); + p.children[p.index] = values[k]; + while (p.index > 0) { + p = code.pop(); + } + p.index++; + code.push(p); + while (code.length <= i) { + code.push(q = {children: [], index: 0}); + p.children[p.index] = q.children; + p = q; + } + k++; + } + if (i + 1 < length) { + // p here points to last code + code.push(q = {children: [], index: 0}); + p.children[p.index] = q.children; + p = q; + } + } + return code[0].children; + } + + function decodeScan(data, offset, + frame, components, resetInterval, + spectralStart, spectralEnd, + successivePrev, successive) { + var precision = frame.precision; + var samplesPerLine = frame.samplesPerLine; + var scanLines = frame.scanLines; + var mcusPerLine = frame.mcusPerLine; + var progressive = frame.progressive; + var maxH = frame.maxH, maxV = frame.maxV; + + var startOffset = offset, bitsData = 0, bitsCount = 0; + function readBit() { + if (bitsCount > 0) { + bitsCount--; + return (bitsData >> bitsCount) & 1; + } + bitsData = data[offset++]; + if (bitsData == 0xFF) { + var nextByte = data[offset++]; + if (nextByte) { + throw "unexpected marker: " + ((bitsData << 8) | nextByte).toString(16); + } + // unstuff 0 + } + bitsCount = 7; + return bitsData >>> 7; + } + function decodeHuffman(tree) { + var node = tree, bit; + while ((bit = readBit()) !== null) { + node = node[bit]; + if (typeof node === 'number') + return node; + if (typeof node !== 'object') + throw "invalid huffman sequence"; + } + return null; + } + function receive(length) { + var n = 0; + while (length > 0) { + var bit = readBit(); + if (bit === null) return; + n = (n << 1) | bit; + length--; + } + return n; + } + function receiveAndExtend(length) { + var n = receive(length); + if (n >= 1 << (length - 1)) + return n; + return n + (-1 << length) + 1; + } + function decodeBaseline(component, zz) { + var t = decodeHuffman(component.huffmanTableDC); + var diff = t === 0 ? 0 : receiveAndExtend(t); + zz[0]= (component.pred += diff); + var k = 1; + while (k < 64) { + var rs = decodeHuffman(component.huffmanTableAC); + var s = rs & 15, r = rs >> 4; + if (s === 0) { + if (r < 15) + break; + k += 16; + continue; + } + k += r; + zz[k] = receiveAndExtend(s); + k++; + } + } + function decodeDCFirst(component, zz) { + var t = decodeHuffman(component.huffmanTableDC); + var diff = t === 0 ? 0 : (receiveAndExtend(t) << successive); + zz[0] = (component.pred += diff); + } + function decodeDCSuccessive(component, zz) { + zz[0] |= readBit() << successive; + } + var eobrun = 0; + function decodeACFirst(component, zz) { + if (eobrun > 0) { + eobrun--; + return; + } + var k = spectralStart, e = spectralEnd; + while (k <= e) { + var rs = decodeHuffman(component.huffmanTableAC); + var s = rs & 15, r = rs >> 4; + if (s === 0) { + if (r < 15) { + eobrun = receive(r) + (1 << r) - 1; + break; + } + k += 16; + continue; + } + k += r; + zz[k] = receiveAndExtend(s) * (1 << successive); + k++; + } + } + var successiveACState = 0, successiveACNextValue; + function decodeACSuccessive(component, zz) { + var k = spectralStart, e = spectralEnd, r = 0; + while (k <= e) { + switch (successiveACState) { + case 0: // initial state + var rs = decodeHuffman(component.huffmanTableAC); + var s = rs & 15, r = rs >> 4; + if (s === 0) { + if (r < 15) { + eobrun = receive(r) + (1 << r); + successiveACState = 4; + } else { + r = 16; + successiveACState = 1; + } + } else { + if (s !== 1) + throw "invalid ACn encoding"; + successiveACNextValue = receiveAndExtend(s); + successiveACState = r ? 2 : 3; + } + continue; + case 1: // skipping r zero items + case 2: + if (zz[k]) + zz[k] += (readBit() << successive); + else { + r--; + if (r === 0) + successiveACState = successiveACState == 2 ? 3 : 0; + } + break; + case 3: // set value for a zero item + if (zz[k]) + zz[k] += (readBit() << successive); + else { + zz[k] = successiveACNextValue << successive; + successiveACState = 0; + } + break; + case 4: // eob + if (zz[k]) + zz[k] += (readBit() << successive); + break; + } + k++; + } + if (successiveACState === 4) { + eobrun--; + if (eobrun === 0) + successiveACState = 0; + } + } + function decodeMcu(component, decode, mcu, row, col) { + var mcuRow = (mcu / mcusPerLine) | 0; + var mcuCol = mcu % mcusPerLine; + var blockRow = mcuRow * component.v + row; + var blockCol = mcuCol * component.h + col; + decode(component, component.blocks[blockRow][blockCol]); + } + function decodeBlock(component, decode, mcu) { + var blockRow = (mcu / component.blocksPerLine) | 0; + var blockCol = mcu % component.blocksPerLine; + decode(component, component.blocks[blockRow][blockCol]); + } + + var componentsLength = components.length; + var component, i, j, k, n; + var decodeFn; + if (progressive) { + if (spectralStart === 0) + decodeFn = successivePrev === 0 ? decodeDCFirst : decodeDCSuccessive; + else + decodeFn = successivePrev === 0 ? decodeACFirst : decodeACSuccessive; + } else { + decodeFn = decodeBaseline; + } + + var mcu = 0, marker; + var mcuExpected; + if (componentsLength == 1) { + mcuExpected = components[0].blocksPerLine * components[0].blocksPerColumn; + } else { + mcuExpected = mcusPerLine * frame.mcusPerColumn; + } + if (!resetInterval) resetInterval = mcuExpected; + + var h, v; + while (mcu < mcuExpected) { + // reset interval stuff + for (i = 0; i < componentsLength; i++) + components[i].pred = 0; + eobrun = 0; + + if (componentsLength == 1) { + component = components[0]; + for (n = 0; n < resetInterval; n++) { + decodeBlock(component, decodeFn, mcu); + mcu++; + } + } else { + for (n = 0; n < resetInterval; n++) { + for (i = 0; i < componentsLength; i++) { + component = components[i]; + h = component.h; + v = component.v; + for (j = 0; j < v; j++) { + for (k = 0; k < h; k++) { + decodeMcu(component, decodeFn, mcu, j, k); + } + } + } + mcu++; + } + } + + // find marker + bitsCount = 0; + marker = (data[offset] << 8) | data[offset + 1]; + if (marker <= 0xFF00) { + throw "marker was not found"; + } + + if (marker >= 0xFFD0 && marker <= 0xFFD7) { // RSTx + offset += 2; + } + else + break; + } + + return offset - startOffset; + } + + function buildComponentData(frame, component) { + var lines = []; + var blocksPerLine = component.blocksPerLine; + var blocksPerColumn = component.blocksPerColumn; + var samplesPerLine = blocksPerLine << 3; + + function quantizeAndInverse(zz) { + var qt = component.quantizationTable; + var precisionShift = frame.precision - 8; + + var R = new Int32Array(64); + for (i = 0; i < 64; i++) + R[i] = zz[i] * qt[i]; + + var r = new Uint8Array(64), i, j; + for (i = 0; i < 64; i++) { + var sum = 0; + var table = iDCTTables[i]; + for (j = 0; j < 64; j++) + sum += table[j] * R[j]; + // TODO loosing precision? + var sample = 128 + ((sum / 4) >> precisionShift); + // clamping + r[i] = sample < 0 ? 0 : sample > 0xFF ? 0xFF : sample; + } + return r; + } + + var i, j; + for (var blockRow = 0; blockRow < blocksPerColumn; blockRow++) { + var scanLine = blockRow << 3; + for (i = 0; i < 8; i++) + lines.push(new Uint8Array(samplesPerLine)); + for (var blockCol = 0; blockCol < blocksPerLine; blockCol++) { + var r = quantizeAndInverse(component.blocks[blockRow][blockCol]); + + var offset = 0, sample = blockCol << 3; + for (j = 0; j < 8; j++) { + var line = lines[scanLine + j]; + for (i = 0; i < 8; i++) + line[sample + i] = r[offset++]; + } + } + } + return lines; + } + + constructor.prototype = { + load: function load(path) { + var xhr = new XMLHttpRequest(); + xhr.open("GET", path, true); + xhr.responseType = "arraybuffer"; + xhr.onload = (function() { + // TODO catch parse error + var data = new Uint8Array(xhr.response || xhr.mozResponseArrayBuffer); + this.parse(data); + if (this.onload) + this.onload(); + }).bind(this); + xhr.send(null); + }, + parse: function parse(data) { + var offset = 0, length = data.length; + function readUint16() { + var value = (data[offset] << 8) | data[offset + 1]; + offset += 2; + return value; + } + function readDataBlock() { + var length = readUint16(); + var array = data.subarray(offset, offset + length - 2); + offset += array.length; + return array; + } + function prepareComponents(frame) { + var maxH = 0, maxV = 0; + var component, componentId; + for (componentId in frame.components) { + if (frame.components.hasOwnProperty(componentId)) { + component = frame.components[componentId]; + if (maxH < component.h) maxH = component.h; + if (maxV < component.v) maxV = component.v; + } + } + var mcusPerLine = Math.ceil(frame.samplesPerLine / 8 / maxH); + var mcusPerColumn = Math.ceil(frame.scanLines / 8 / maxV); + for (componentId in frame.components) { + if (frame.components.hasOwnProperty(componentId)) { + component = frame.components[componentId]; + var blocksPerLine = Math.ceil(Math.ceil(frame.samplesPerLine / 8) * component.h / maxH); + var blocksPerColumn = Math.ceil(Math.ceil(frame.scanLines / 8) * component.v / maxV); + var blocksPerLineForMcu = mcusPerLine * component.h; + var blocksPerColumnForMcu = mcusPerColumn * component.v; + var blocks = []; + for (var i = 0; i < blocksPerColumnForMcu; i++) { + var row = []; + for (var j = 0; j < blocksPerLineForMcu; j++) + row.push(new Int32Array(64)); + blocks.push(row); + } + component.blocksPerLine = blocksPerLine; + component.blocksPerColumn = blocksPerColumn; + component.blocks = blocks; + } + } + frame.maxH = maxH; + frame.maxV = maxV; + frame.mcusPerLine = mcusPerLine; + frame.mcusPerColumn = mcusPerColumn; + } + var jfif = null; + var adobe = null; + var pixels = null; + var frame, resetInterval; + var quantizationTables = [], frames = []; + var huffmanTablesAC = [], huffmanTablesDC = []; + var fileMarker = readUint16(); + if (fileMarker != 0xFFD8) { // SOI (Start of Image) + throw "SOI not found"; + } + + fileMarker = readUint16(); + while (fileMarker != 0xFFD9) { // EOI (End of image) + var i, j, l; + switch(fileMarker) { + case 0xFFE0: // APP0 (Application Specific) + case 0xFFE1: // APP1 + case 0xFFE2: // APP2 + case 0xFFE3: // APP3 + case 0xFFE4: // APP4 + case 0xFFE5: // APP5 + case 0xFFE6: // APP6 + case 0xFFE7: // APP7 + case 0xFFE8: // APP8 + case 0xFFE9: // APP9 + case 0xFFEA: // APP10 + case 0xFFEB: // APP11 + case 0xFFEC: // APP12 + case 0xFFED: // APP13 + case 0xFFEE: // APP14 + case 0xFFEF: // APP15 + case 0xFFFE: // COM (Comment) + var appData = readDataBlock(); + + if (fileMarker === 0xFFE0) { + if (appData[0] === 0x4A && appData[1] === 0x46 && appData[2] === 0x49 && + appData[3] === 0x46 && appData[4] === 0) { // 'JFIF\x00' + jfif = { + version: { major: appData[5], minor: appData[6] }, + densityUnits: appData[7], + xDensity: (appData[8] << 8) | appData[9], + yDensity: (appData[10] << 8) | appData[11], + thumbWidth: appData[12], + thumbHeight: appData[13], + thumbData: appData.subarray(14, 14 + 3 * appData[12] * appData[13]) + }; + } + } + // TODO APP1 - Exif + if (fileMarker === 0xFFEE) { + if (appData[0] === 0x41 && appData[1] === 0x64 && appData[2] === 0x6F && + appData[3] === 0x62 && appData[4] === 0x65 && appData[5] === 0) { // 'Adobe\x00' + adobe = { + version: appData[6], + flags0: (appData[7] << 8) | appData[8], + flags1: (appData[9] << 8) | appData[10], + transformCode: appData[11] + }; + } + } + break; + + case 0xFFDB: // DQT (Define Quantization Tables) + var quantizationTableCount = Math.floor((readUint16() - 2) / 65); + for (i = 0; i < quantizationTableCount; i++) { + var quantizationTableSpec = data[offset++]; + var tableData = new Int32Array(64); + if ((quantizationTableSpec >> 4) === 0) { // 8 bit values + for (j = 0; j < 64; j++) + tableData[j] = data[offset++]; + } else if ((quantizationTableSpec >> 4) === 1) { //16 bit + tableData[j] = readUint16(); + } else + throw "DQT: invalid table spec"; + quantizationTables[quantizationTableSpec & 15] = tableData; + } + break; + + case 0xFFC0: // SOF0 (Start of Frame, Baseline DCT) + case 0xFFC2: // SOF2 (Start of Frame, Progressive DCT) + readUint16(); // skip data length + frame = {}; + frame.progressive = (fileMarker === 0xFFC2); + frame.precision = data[offset++]; + frame.scanLines = readUint16(); + frame.samplesPerLine = readUint16(); + frame.components = []; + var componentsCount = data[offset++], componentId; + var maxH = 0, maxV = 0; + for (i = 0; i < componentsCount; i++) { + componentId = data[offset]; + var h = data[offset + 1] >> 4; + var v = data[offset + 1] & 15; + var qId = data[offset + 2]; + frame.components[componentId] = { + h: h, + v: v, + quantizationTable: quantizationTables[qId] + }; + offset += 3; + } + prepareComponents(frame); + frames.push(frame); + break; + + case 0xFFC4: // DHT (Define Huffman Tables) + var huffmanLength = readUint16(); + for (i = 2; i < huffmanLength;) { + var huffmanTableSpec = data[offset++]; + var codeLengths = new Uint8Array(16); + var codeLengthSum = 0; + for (j = 0; j < 16; j++, offset++) + codeLengthSum += (codeLengths[j] = data[offset]); + var huffmanValues = new Uint8Array(codeLengthSum); + for (j = 0; j < codeLengthSum; j++, offset++) + huffmanValues[j] = data[offset]; + i += 17 + codeLengthSum; + + ((huffmanTableSpec >> 4) === 0 ? + huffmanTablesDC : huffmanTablesAC)[huffmanTableSpec & 15] = + buildHuffmanTable(codeLengths, huffmanValues); + } + break; + + case 0xFFDD: // DRI (Define Restart Interval) + readUint16(); // skip data length + resetInterval = readUint16(); + break; + + case 0xFFDA: // SOS (Start of Scan) + var scanLength = readUint16(); + var selectorsCount = data[offset++]; + var components = [], component; + for (i = 0; i < selectorsCount; i++) { + component = frame.components[data[offset++]]; + var tableSpec = data[offset++]; + component.huffmanTableDC = huffmanTablesDC[tableSpec >> 4]; + component.huffmanTableAC = huffmanTablesAC[tableSpec & 15]; + components.push(component); + } + var spectralStart = data[offset++]; + var spectralEnd = data[offset++]; + var successiveApproximation = data[offset++]; + var processed = decodeScan(data, offset, + frame, components, resetInterval, + spectralStart, spectralEnd, + successiveApproximation >> 4, successiveApproximation & 15); + offset += processed; + break; + default: + throw "unknown JPEG marker " + fileMarker.toString(16); + } + fileMarker = readUint16(); + } + if (frames.length != 1) + throw "only single frame JPEGs supported"; + + this.width = frame.samplesPerLine; + this.height = frame.scanLines; + this.jfif = jfif; + this.adobe = adobe; + this.components = []; + for (var id in frame.components) { + if (frame.components.hasOwnProperty(id)) { + this.components.push({ + lines: buildComponentData(frame, frame.components[id]), + scaleX: frame.components[id].h / frame.maxH, + scaleY: frame.components[id].v / frame.maxV + }); + } + } + }, + getData: function getData(width, height) { + function clampTo8bit(a) { + return a < 0 ? 0 : a > 255 ? 255 : a; + } + var scaleX = this.width / width, scaleY = this.height / height; + + var component1, component2, component3, component4; + var component1Line, component2Line, component3Line, component4Line; + var x, y; + var offset = 0; + var Y, Cb, Cr, K, C, M, Ye, R, G, B; + var colorTransform; + var dataLength = width * height * this.components.length; + var data = new Uint8Array(dataLength); + switch (this.components.length) { + case 1: + component1 = this.components[0]; + for (y = 0; y < height; y++) { + component1Line = component1.lines[0 | (y * component1.scaleY * scaleY)]; + for (x = 0; x < width; x++) { + Y = component1Line[0 | (x * component1.scaleX * scaleX)]; + + data[offset++] = Y; + } + } + break; + case 3: + // The default transform for three components is true + colorTransform = true; + // The adobe transform marker overrides any previous setting + if (this.adobe && this.adobe.transformCode) + colorTransform = true; + else if (typeof this.colorTransform != -1) + colorTransform = this.colorTransform == true; + + component1 = this.components[0]; + component2 = this.components[1]; + component3 = this.components[2]; + for (y = 0; y < height; y++) { + component1Line = component1.lines[0 | (y * component1.scaleY * scaleY)]; + component2Line = component2.lines[0 | (y * component2.scaleY * scaleY)]; + component3Line = component3.lines[0 | (y * component3.scaleY * scaleY)]; + for (x = 0; x < width; x++) { + if (!colorTransform) { + R = component1Line[0 | (x * component1.scaleX * scaleX)]; + G = component2Line[0 | (x * component2.scaleX * scaleX)]; + B = component3Line[0 | (x * component3.scaleX * scaleX)]; + } else { + Y = component1Line[0 | (x * component1.scaleX * scaleX)]; + Cb = component2Line[0 | (x * component2.scaleX * scaleX)]; + Cr = component3Line[0 | (x * component3.scaleX * scaleX)]; + + R = clampTo8bit(Y + 1.402 * (Cr - 128)); + G = clampTo8bit(Y - 0.3441363 * (Cb - 128) - 0.71413636 * (Cr - 128)); + B = clampTo8bit(Y + 1.772 * (Cb - 128)); + } + + data[offset++] = R; + data[offset++] = G; + data[offset++] = B; + } + } + break; + case 4: + if (!this.adobe) + throw 'Unsupported color mode (4 components)'; + // The default transform for four components is false + colorTransform = false; + // The adobe transform marker overrides any previous setting + if (this.adobe && this.adobe.transformCode) + colorTransform = true; + else if (typeof this.colorTransform != -1) + colorTransform = this.colorTransform == true; + + component1 = this.components[0]; + component2 = this.components[1]; + component3 = this.components[2]; + component4 = this.components[3]; + for (y = 0; y < height; y++) { + component1Line = component1.lines[0 | (y * component1.scaleY * scaleY)]; + component2Line = component2.lines[0 | (y * component2.scaleY * scaleY)]; + component3Line = component3.lines[0 | (y * component3.scaleY * scaleY)]; + component4Line = component4.lines[0 | (y * component4.scaleY * scaleY)]; + for (x = 0; x < width; x++) { + if (!colorTransform) { + C = component1Line[0 | (x * component1.scaleX * scaleX)]; + M = component2Line[0 | (x * component2.scaleX * scaleX)]; + Ye = component3Line[0 | (x * component3.scaleX * scaleX)]; + K = component4Line[0 | (x * component4.scaleX * scaleX)]; + } else { + Y = component1Line[0 | (x * component1.scaleX * scaleX)]; + Cb = component2Line[0 | (x * component2.scaleX * scaleX)]; + Cr = component3Line[0 | (x * component3.scaleX * scaleX)]; + K = component4Line[0 | (x * component4.scaleX * scaleX)]; + + C = 255 - clampTo8bit(Y + 1.402 * (Cr - 128)); + M = 255 - clampTo8bit(Y - 0.3441363 * (Cb - 128) - 0.71413636 * (Cr - 128)); + Ye = 255 - clampTo8bit(Y + 1.772 * (Cb - 128)); + } + data[offset++] = C; + data[offset++] = M; + data[offset++] = Ye; + data[offset++] = K; + } + } + break; + default: + throw 'Unsupported color mode'; + } + return data; + }, + copyToImageData: function copyToImageData(imageData) { + this.getData(imageData.data, imageData.width, imageData.height); + } + }; + + return constructor; +})(); \ No newline at end of file From edf6f51c27d9a721ed6640ef926ae8878fd95048 Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Tue, 15 Nov 2011 15:51:32 -0800 Subject: [PATCH 17/35] Remove submodule documentation. --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index be342060d..deb925601 100644 --- a/README.md +++ b/README.md @@ -45,8 +45,6 @@ To get a local copy of the current code, clone it using git: $ git clone git://github.com/mozilla/pdf.js.git pdfjs $ cd pdfjs - $ git submodule init - $ git submodule update Next, you need to start a local web server as some browsers don't allow opening PDF files for a file:// url: From d2964f3e94b92629150cb97525753dc845d6eade Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Wed, 16 Nov 2011 15:12:42 -0800 Subject: [PATCH 18/35] Update documentation. --- src/stream.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/stream.js b/src/stream.js index 69f792b58..c7b5c8121 100644 --- a/src/stream.js +++ b/src/stream.js @@ -756,8 +756,13 @@ var PredictorStream = (function predictorStream() { return constructor; })(); -// A JpegStream can't be read directly. We use the platform to render -// the underlying JPEG data for us. +/** + * Depending on the type of JPEG a JpegStream is handled in different ways. For + * JPEG's that are supported natively such as DeviceGray and DeviceRGB the image + * data is stored and then loaded by the browser. For unsupported JPEG's we use + * a library to decode these images and the stream behaves like all the other + * DecodeStreams. + */ var JpegStream = (function jpegStream() { function isAdobeImage(bytes) { var maxBytesScanned = Math.max(bytes.length - 16, 1024); From 564fe0d90783db6b34071270f54af36a8edd54d7 Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Wed, 16 Nov 2011 15:16:04 -0800 Subject: [PATCH 19/35] Remove open web pdf until we can resolve the performance issues. --- test/test_manifest.json | 7 ------- 1 file changed, 7 deletions(-) diff --git a/test/test_manifest.json b/test/test_manifest.json index a6f2dc72a..583e1cbaa 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -44,13 +44,6 @@ "rounds": 1, "type": "eq" }, - { "id": "openweb-cover", - "file": "pdfs/openweb_tm-PRINT.pdf", - "md5": "53f611dfc19ddfb50554c21c4af465c0", - "link": true, - "rounds": 1, - "type": "eq" - }, { "id": "plusminus", "file": "pdfs/Test-plusminus.pdf", "md5": "1ec7ade5b95ac9aaba3a618af28d34c7", From b150f8be0dd163c542c35dbca1fdbaeaf223f392 Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Wed, 16 Nov 2011 15:18:31 -0800 Subject: [PATCH 20/35] Remove the open web link file for now. --- test/pdfs/openweb_tm-PRINT.pdf.link | 1 - 1 file changed, 1 deletion(-) delete mode 100644 test/pdfs/openweb_tm-PRINT.pdf.link diff --git a/test/pdfs/openweb_tm-PRINT.pdf.link b/test/pdfs/openweb_tm-PRINT.pdf.link deleted file mode 100644 index 08c897140..000000000 --- a/test/pdfs/openweb_tm-PRINT.pdf.link +++ /dev/null @@ -1 +0,0 @@ -http://openweb.flossmanuals.net/materials/openweb_tm-PRINT.pdf \ No newline at end of file From 624a7a74ea9e27c46100a7064235cfb6e0b6ec5e Mon Sep 17 00:00:00 2001 From: Kalervo Kujala Date: Sun, 20 Nov 2011 21:16:47 +0200 Subject: [PATCH 21/35] Add jasmine files to external-directory. --- external/jasmine/jasmine-html.js | 676 +++++++ external/jasmine/jasmine.css | 81 + external/jasmine/jasmine.js | 2476 ++++++++++++++++++++++++++ external/jasmine/jasmine_favicon.png | Bin 0 -> 905 bytes test/unit/{spec => }/obj_spec.js | 2 +- test/unit/unit_test.html | 10 +- 6 files changed, 3239 insertions(+), 6 deletions(-) create mode 100644 external/jasmine/jasmine-html.js create mode 100644 external/jasmine/jasmine.css create mode 100644 external/jasmine/jasmine.js create mode 100644 external/jasmine/jasmine_favicon.png rename test/unit/{spec => }/obj_spec.js (92%) diff --git a/external/jasmine/jasmine-html.js b/external/jasmine/jasmine-html.js new file mode 100644 index 000000000..3de4e8a5f --- /dev/null +++ b/external/jasmine/jasmine-html.js @@ -0,0 +1,676 @@ +jasmine.HtmlReporterHelpers = {}; + +jasmine.HtmlReporterHelpers.createDom = function(type, attrs, childrenVarArgs) { + var el = document.createElement(type); + + for (var i = 2; i < arguments.length; i++) { + var child = arguments[i]; + + if (typeof child === 'string') { + el.appendChild(document.createTextNode(child)); + } else { + if (child) { + el.appendChild(child); + } + } + } + + for (var attr in attrs) { + if (attr == "className") { + el[attr] = attrs[attr]; + } else { + el.setAttribute(attr, attrs[attr]); + } + } + + return el; +}; + +jasmine.HtmlReporterHelpers.getSpecStatus = function(child) { + var results = child.results(); + var status = results.passed() ? 'passed' : 'failed'; + if (results.skipped) { + status = 'skipped'; + } + + return status; +}; + +jasmine.HtmlReporterHelpers.appendToSummary = function(child, childElement) { + var parentDiv = this.dom.summary; + var parentSuite = (typeof child.parentSuite == 'undefined') ? 'suite' : 'parentSuite'; + var parent = child[parentSuite]; + + if (parent) { + if (typeof this.views.suites[parent.id] == 'undefined') { + this.views.suites[parent.id] = new jasmine.HtmlReporter.SuiteView(parent, this.dom, this.views); + } + parentDiv = this.views.suites[parent.id].element; + } + + parentDiv.appendChild(childElement); +}; + + +jasmine.HtmlReporterHelpers.addHelpers = function(ctor) { + for(var fn in jasmine.HtmlReporterHelpers) { + ctor.prototype[fn] = jasmine.HtmlReporterHelpers[fn]; + } +}; + +jasmine.HtmlReporter = function(_doc) { + var self = this; + var doc = _doc || window.document; + + var reporterView; + + var dom = {}; + + // Jasmine Reporter Public Interface + self.logRunningSpecs = false; + + self.reportRunnerStarting = function(runner) { + var specs = runner.specs() || []; + + if (specs.length == 0) { + return; + } + + createReporterDom(runner.env.versionString()); + doc.body.appendChild(dom.reporter); + + reporterView = new jasmine.HtmlReporter.ReporterView(dom); + reporterView.addSpecs(specs, self.specFilter); + }; + + self.reportRunnerResults = function(runner) { + reporterView.complete(); + }; + + self.reportSuiteResults = function(suite) { + reporterView.suiteComplete(suite); + }; + + self.reportSpecStarting = function(spec) { + if (self.logRunningSpecs) { + self.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...'); + } + }; + + self.reportSpecResults = function(spec) { + reporterView.specComplete(spec); + }; + + self.log = function() { + var console = jasmine.getGlobal().console; + if (console && console.log) { + if (console.log.apply) { + console.log.apply(console, arguments); + } else { + console.log(arguments); // ie fix: console.log.apply doesn't exist on ie + } + } + }; + + self.specFilter = function(spec) { + if (!focusedSpecName()) { + return true; + } + + return spec.getFullName().indexOf(focusedSpecName()) === 0; + }; + + return self; + + function focusedSpecName() { + var specName; + + (function memoizeFocusedSpec() { + if (specName) { + return; + } + + var paramMap = []; + var params = doc.location.search.substring(1).split('&'); + + for (var i = 0; i < params.length; i++) { + var p = params[i].split('='); + paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]); + } + + specName = paramMap.spec; + })(); + + return specName; + } + + function createReporterDom(version) { + dom.reporter = self.createDom('div', { id: 'HTMLReporter', className: 'jasmine_reporter' }, + dom.banner = self.createDom('div', { className: 'banner' }, + self.createDom('span', { className: 'title' }, "Jasmine "), + self.createDom('span', { className: 'version' }, version)), + + dom.symbolSummary = self.createDom('ul', {className: 'symbolSummary'}), + dom.alert = self.createDom('div', {className: 'alert'}), + dom.results = self.createDom('div', {className: 'results'}, + dom.summary = self.createDom('div', { className: 'summary' }), + dom.details = self.createDom('div', { id: 'details' })) + ); + } +}; +jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter);jasmine.HtmlReporterHelpers = {}; + +jasmine.HtmlReporterHelpers.createDom = function(type, attrs, childrenVarArgs) { + var el = document.createElement(type); + + for (var i = 2; i < arguments.length; i++) { + var child = arguments[i]; + + if (typeof child === 'string') { + el.appendChild(document.createTextNode(child)); + } else { + if (child) { + el.appendChild(child); + } + } + } + + for (var attr in attrs) { + if (attr == "className") { + el[attr] = attrs[attr]; + } else { + el.setAttribute(attr, attrs[attr]); + } + } + + return el; +}; + +jasmine.HtmlReporterHelpers.getSpecStatus = function(child) { + var results = child.results(); + var status = results.passed() ? 'passed' : 'failed'; + if (results.skipped) { + status = 'skipped'; + } + + return status; +}; + +jasmine.HtmlReporterHelpers.appendToSummary = function(child, childElement) { + var parentDiv = this.dom.summary; + var parentSuite = (typeof child.parentSuite == 'undefined') ? 'suite' : 'parentSuite'; + var parent = child[parentSuite]; + + if (parent) { + if (typeof this.views.suites[parent.id] == 'undefined') { + this.views.suites[parent.id] = new jasmine.HtmlReporter.SuiteView(parent, this.dom, this.views); + } + parentDiv = this.views.suites[parent.id].element; + } + + parentDiv.appendChild(childElement); +}; + + +jasmine.HtmlReporterHelpers.addHelpers = function(ctor) { + for(var fn in jasmine.HtmlReporterHelpers) { + ctor.prototype[fn] = jasmine.HtmlReporterHelpers[fn]; + } +}; + +jasmine.HtmlReporter.ReporterView = function(dom) { + this.startedAt = new Date(); + this.runningSpecCount = 0; + this.completeSpecCount = 0; + this.passedCount = 0; + this.failedCount = 0; + this.skippedCount = 0; + + this.createResultsMenu = function() { + this.resultsMenu = this.createDom('span', {className: 'resultsMenu bar'}, + this.summaryMenuItem = this.createDom('a', {className: 'summaryMenuItem', href: "#"}, '0 specs'), + ' | ', + this.detailsMenuItem = this.createDom('a', {className: 'detailsMenuItem', href: "#"}, '0 failing')); + + this.summaryMenuItem.onclick = function() { + dom.reporter.className = dom.reporter.className.replace(/ showDetails/g, ''); + }; + + this.detailsMenuItem.onclick = function() { + showDetails(); + }; + }; + + this.addSpecs = function(specs, specFilter) { + this.totalSpecCount = specs.length; + + this.views = { + specs: {}, + suites: {} + }; + + for (var i = 0; i < specs.length; i++) { + var spec = specs[i]; + this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom, this.views); + if (specFilter(spec)) { + this.runningSpecCount++; + } + } + }; + + this.specComplete = function(spec) { + this.completeSpecCount++; + + if (isUndefined(this.views.specs[spec.id])) { + this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom); + } + + var specView = this.views.specs[spec.id]; + + switch (specView.status()) { + case 'passed': + this.passedCount++; + break; + + case 'failed': + this.failedCount++; + break; + + case 'skipped': + this.skippedCount++; + break; + } + + specView.refresh(); + this.refresh(); + }; + + this.suiteComplete = function(suite) { + var suiteView = this.views.suites[suite.id]; + if (isUndefined(suiteView)) { + return; + } + suiteView.refresh(); + }; + + this.refresh = function() { + + if (isUndefined(this.resultsMenu)) { + this.createResultsMenu(); + } + + // currently running UI + if (isUndefined(this.runningAlert)) { + this.runningAlert = this.createDom('a', {href: "?", className: "runningAlert bar"}); + dom.alert.appendChild(this.runningAlert); + } + this.runningAlert.innerHTML = "Running " + this.completeSpecCount + " of " + specPluralizedFor(this.totalSpecCount); + + // skipped specs UI + if (isUndefined(this.skippedAlert)) { + this.skippedAlert = this.createDom('a', {href: "?", className: "skippedAlert bar"}); + } + + this.skippedAlert.innerHTML = "Skipping " + this.skippedCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all"; + + if (this.skippedCount === 1 && isDefined(dom.alert)) { + dom.alert.appendChild(this.skippedAlert); + } + + // passing specs UI + if (isUndefined(this.passedAlert)) { + this.passedAlert = this.createDom('span', {href: "?", className: "passingAlert bar"}); + } + this.passedAlert.innerHTML = "Passing " + specPluralizedFor(this.passedCount); + + // failing specs UI + if (isUndefined(this.failedAlert)) { + this.failedAlert = this.createDom('span', {href: "?", className: "failingAlert bar"}); + } + this.failedAlert.innerHTML = "Failing " + specPluralizedFor(this.failedCount); + + if (this.failedCount === 1 && isDefined(dom.alert)) { + dom.alert.appendChild(this.failedAlert); + dom.alert.appendChild(this.resultsMenu); + } + + // summary info + this.summaryMenuItem.innerHTML = "" + specPluralizedFor(this.runningSpecCount); + this.detailsMenuItem.innerHTML = "" + this.failedCount + " failing"; + }; + + this.complete = function() { + dom.alert.removeChild(this.runningAlert); + + this.skippedAlert.innerHTML = "Ran " + this.runningSpecCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all"; + + if (this.failedCount === 0) { + dom.alert.appendChild(this.createDom('span', {className: 'passingAlert bar'}, "Passing " + specPluralizedFor(this.passedCount))); + } else { + showDetails(); + } + + dom.banner.appendChild(this.createDom('span', {className: 'duration'}, "finished in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s")); + }; + + return this; + + function showDetails() { + if (dom.reporter.className.search(/showDetails/) === -1) { + dom.reporter.className += " showDetails"; + } + } + + function isUndefined(obj) { + return typeof obj === 'undefined'; + } + + function isDefined(obj) { + return !isUndefined(obj); + } + + function specPluralizedFor(count) { + var str = count + " spec"; + if (count > 1) { + str += "s" + } + return str; + } + +}; + +jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.ReporterView); + + +jasmine.HtmlReporter.SpecView = function(spec, dom, views) { + this.spec = spec; + this.dom = dom; + this.views = views; + + this.symbol = this.createDom('li', { className: 'pending' }); + this.dom.symbolSummary.appendChild(this.symbol); + + this.summary = this.createDom('div', { className: 'specSummary' }, + this.createDom('a', { + className: 'description', + href: '?spec=' + encodeURIComponent(this.spec.getFullName()), + title: this.spec.getFullName() + }, this.spec.description) + ); + + this.detail = this.createDom('div', { className: 'specDetail' }, + this.createDom('a', { + className: 'description', + href: '?spec=' + encodeURIComponent(this.spec.getFullName()), + title: this.spec.getFullName() + }, this.spec.getFullName()) + ); +}; + +jasmine.HtmlReporter.SpecView.prototype.status = function() { + return this.getSpecStatus(this.spec); +}; + +jasmine.HtmlReporter.SpecView.prototype.refresh = function() { + this.symbol.className = this.status(); + + switch (this.status()) { + case 'skipped': + break; + + case 'passed': + this.appendSummaryToSuiteDiv(); + break; + + case 'failed': + this.appendSummaryToSuiteDiv(); + this.appendFailureDetail(); + break; + } +}; + +jasmine.HtmlReporter.SpecView.prototype.appendSummaryToSuiteDiv = function() { + this.summary.className += ' ' + this.status(); + this.appendToSummary(this.spec, this.summary); +}; + +jasmine.HtmlReporter.SpecView.prototype.appendFailureDetail = function() { + this.detail.className += ' ' + this.status(); + + var resultItems = this.spec.results().getItems(); + var messagesDiv = this.createDom('div', { className: 'messages' }); + + for (var i = 0; i < resultItems.length; i++) { + var result = resultItems[i]; + + if (result.type == 'log') { + messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString())); + } else if (result.type == 'expect' && result.passed && !result.passed()) { + messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message)); + + if (result.trace.stack) { + messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack)); + } + } + } + + if (messagesDiv.childNodes.length > 0) { + this.detail.appendChild(messagesDiv); + this.dom.details.appendChild(this.detail); + } +}; + +jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SpecView);jasmine.HtmlReporter.SuiteView = function(suite, dom, views) { + this.suite = suite; + this.dom = dom; + this.views = views; + + this.element = this.createDom('div', { className: 'suite' }, + this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(this.suite.getFullName()) }, this.suite.description) + ); + + this.appendToSummary(this.suite, this.element); +}; + +jasmine.HtmlReporter.SuiteView.prototype.status = function() { + return this.getSpecStatus(this.suite); +}; + +jasmine.HtmlReporter.SuiteView.prototype.refresh = function() { + this.element.className += " " + this.status(); +}; + +jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SuiteView); + +/* @deprecated Use jasmine.HtmlReporter instead + */ +jasmine.TrivialReporter = function(doc) { + this.document = doc || document; + this.suiteDivs = {}; + this.logRunningSpecs = false; +}; + +jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) { + var el = document.createElement(type); + + for (var i = 2; i < arguments.length; i++) { + var child = arguments[i]; + + if (typeof child === 'string') { + el.appendChild(document.createTextNode(child)); + } else { + if (child) { el.appendChild(child); } + } + } + + for (var attr in attrs) { + if (attr == "className") { + el[attr] = attrs[attr]; + } else { + el.setAttribute(attr, attrs[attr]); + } + } + + return el; +}; + +jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) { + var showPassed, showSkipped; + + this.outerDiv = this.createDom('div', { id: 'TrivialReporter', className: 'jasmine_reporter' }, + this.createDom('div', { className: 'banner' }, + this.createDom('div', { className: 'logo' }, + this.createDom('span', { className: 'title' }, "Jasmine"), + this.createDom('span', { className: 'version' }, runner.env.versionString())), + this.createDom('div', { className: 'options' }, + "Show ", + showPassed = this.createDom('input', { id: "__jasmine_TrivialReporter_showPassed__", type: 'checkbox' }), + this.createDom('label', { "for": "__jasmine_TrivialReporter_showPassed__" }, " passed "), + showSkipped = this.createDom('input', { id: "__jasmine_TrivialReporter_showSkipped__", type: 'checkbox' }), + this.createDom('label', { "for": "__jasmine_TrivialReporter_showSkipped__" }, " skipped") + ) + ), + + this.runnerDiv = this.createDom('div', { className: 'runner running' }, + this.createDom('a', { className: 'run_spec', href: '?' }, "run all"), + this.runnerMessageSpan = this.createDom('span', {}, "Running..."), + this.finishedAtSpan = this.createDom('span', { className: 'finished-at' }, "")) + ); + + this.document.body.appendChild(this.outerDiv); + + var suites = runner.suites(); + for (var i = 0; i < suites.length; i++) { + var suite = suites[i]; + var suiteDiv = this.createDom('div', { className: 'suite' }, + this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"), + this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, suite.description)); + this.suiteDivs[suite.id] = suiteDiv; + var parentDiv = this.outerDiv; + if (suite.parentSuite) { + parentDiv = this.suiteDivs[suite.parentSuite.id]; + } + parentDiv.appendChild(suiteDiv); + } + + this.startedAt = new Date(); + + var self = this; + showPassed.onclick = function(evt) { + if (showPassed.checked) { + self.outerDiv.className += ' show-passed'; + } else { + self.outerDiv.className = self.outerDiv.className.replace(/ show-passed/, ''); + } + }; + + showSkipped.onclick = function(evt) { + if (showSkipped.checked) { + self.outerDiv.className += ' show-skipped'; + } else { + self.outerDiv.className = self.outerDiv.className.replace(/ show-skipped/, ''); + } + }; +}; + +jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) { + var results = runner.results(); + var className = (results.failedCount > 0) ? "runner failed" : "runner passed"; + this.runnerDiv.setAttribute("class", className); + //do it twice for IE + this.runnerDiv.setAttribute("className", className); + var specs = runner.specs(); + var specCount = 0; + for (var i = 0; i < specs.length; i++) { + if (this.specFilter(specs[i])) { + specCount++; + } + } + var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s"); + message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s"; + this.runnerMessageSpan.replaceChild(this.createDom('a', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild); + + this.finishedAtSpan.appendChild(document.createTextNode("Finished at " + new Date().toString())); +}; + +jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) { + var results = suite.results(); + var status = results.passed() ? 'passed' : 'failed'; + if (results.totalCount === 0) { // todo: change this to check results.skipped + status = 'skipped'; + } + this.suiteDivs[suite.id].className += " " + status; +}; + +jasmine.TrivialReporter.prototype.reportSpecStarting = function(spec) { + if (this.logRunningSpecs) { + this.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...'); + } +}; + +jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) { + var results = spec.results(); + var status = results.passed() ? 'passed' : 'failed'; + if (results.skipped) { + status = 'skipped'; + } + var specDiv = this.createDom('div', { className: 'spec ' + status }, + this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"), + this.createDom('a', { + className: 'description', + href: '?spec=' + encodeURIComponent(spec.getFullName()), + title: spec.getFullName() + }, spec.description)); + + + var resultItems = results.getItems(); + var messagesDiv = this.createDom('div', { className: 'messages' }); + for (var i = 0; i < resultItems.length; i++) { + var result = resultItems[i]; + + if (result.type == 'log') { + messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString())); + } else if (result.type == 'expect' && result.passed && !result.passed()) { + messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message)); + + if (result.trace.stack) { + messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack)); + } + } + } + + if (messagesDiv.childNodes.length > 0) { + specDiv.appendChild(messagesDiv); + } + + this.suiteDivs[spec.suite.id].appendChild(specDiv); +}; + +jasmine.TrivialReporter.prototype.log = function() { + var console = jasmine.getGlobal().console; + if (console && console.log) { + if (console.log.apply) { + console.log.apply(console, arguments); + } else { + console.log(arguments); // ie fix: console.log.apply doesn't exist on ie + } + } +}; + +jasmine.TrivialReporter.prototype.getLocation = function() { + return this.document.location; +}; + +jasmine.TrivialReporter.prototype.specFilter = function(spec) { + var paramMap = {}; + var params = this.getLocation().search.substring(1).split('&'); + for (var i = 0; i < params.length; i++) { + var p = params[i].split('='); + paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]); + } + + if (!paramMap.spec) { + return true; + } + return spec.getFullName().indexOf(paramMap.spec) === 0; +}; diff --git a/external/jasmine/jasmine.css b/external/jasmine/jasmine.css new file mode 100644 index 000000000..826e57531 --- /dev/null +++ b/external/jasmine/jasmine.css @@ -0,0 +1,81 @@ +body { background-color: #eeeeee; padding: 0; margin: 5px; overflow-y: scroll; } + +#HTMLReporter { font-size: 11px; font-family: Monaco, "Lucida Console", monospace; line-height: 14px; color: #333333; } +#HTMLReporter a { text-decoration: none; } +#HTMLReporter a:hover { text-decoration: underline; } +#HTMLReporter p, #HTMLReporter h1, #HTMLReporter h2, #HTMLReporter h3, #HTMLReporter h4, #HTMLReporter h5, #HTMLReporter h6 { margin: 0; line-height: 14px; } +#HTMLReporter .banner, #HTMLReporter .symbolSummary, #HTMLReporter .summary, #HTMLReporter .resultMessage, #HTMLReporter .specDetail .description, #HTMLReporter .alert .bar, #HTMLReporter .stackTrace { padding-left: 9px; padding-right: 9px; } +#HTMLReporter #jasmine_content { position: fixed; right: 100%; } +#HTMLReporter .version { color: #aaaaaa; } +#HTMLReporter .banner { margin-top: 14px; } +#HTMLReporter .duration { color: #aaaaaa; float: right; } +#HTMLReporter .symbolSummary { overflow: hidden; *zoom: 1; margin: 14px 0; } +#HTMLReporter .symbolSummary li { display: block; float: left; height: 7px; width: 14px; margin-bottom: 7px; font-size: 16px; } +#HTMLReporter .symbolSummary li.passed { font-size: 14px; } +#HTMLReporter .symbolSummary li.passed:before { color: #5e7d00; content: "\02022"; } +#HTMLReporter .symbolSummary li.failed { line-height: 9px; } +#HTMLReporter .symbolSummary li.failed:before { color: #b03911; content: "x"; font-weight: bold; margin-left: -1px; } +#HTMLReporter .symbolSummary li.skipped { font-size: 14px; } +#HTMLReporter .symbolSummary li.skipped:before { color: #bababa; content: "\02022"; } +#HTMLReporter .symbolSummary li.pending { line-height: 11px; } +#HTMLReporter .symbolSummary li.pending:before { color: #aaaaaa; content: "-"; } +#HTMLReporter .bar { line-height: 28px; font-size: 14px; display: block; color: #eee; } +#HTMLReporter .runningAlert { background-color: #666666; } +#HTMLReporter .skippedAlert { background-color: #aaaaaa; } +#HTMLReporter .skippedAlert:first-child { background-color: #333333; } +#HTMLReporter .skippedAlert:hover { text-decoration: none; color: white; text-decoration: underline; } +#HTMLReporter .passingAlert { background-color: #a6b779; } +#HTMLReporter .passingAlert:first-child { background-color: #5e7d00; } +#HTMLReporter .failingAlert { background-color: #cf867e; } +#HTMLReporter .failingAlert:first-child { background-color: #b03911; } +#HTMLReporter .results { margin-top: 14px; } +#HTMLReporter #details { display: none; } +#HTMLReporter .resultsMenu, #HTMLReporter .resultsMenu a { background-color: #fff; color: #333333; } +#HTMLReporter.showDetails .summaryMenuItem { font-weight: normal; text-decoration: inherit; } +#HTMLReporter.showDetails .summaryMenuItem:hover { text-decoration: underline; } +#HTMLReporter.showDetails .detailsMenuItem { font-weight: bold; text-decoration: underline; } +#HTMLReporter.showDetails .summary { display: none; } +#HTMLReporter.showDetails #details { display: block; } +#HTMLReporter .summaryMenuItem { font-weight: bold; text-decoration: underline; } +#HTMLReporter .summary { margin-top: 14px; } +#HTMLReporter .summary .suite .suite, #HTMLReporter .summary .specSummary { margin-left: 14px; } +#HTMLReporter .summary .specSummary.passed a { color: #5e7d00; } +#HTMLReporter .summary .specSummary.failed a { color: #b03911; } +#HTMLReporter .description + .suite { margin-top: 0; } +#HTMLReporter .suite { margin-top: 14px; } +#HTMLReporter .suite a { color: #333333; } +#HTMLReporter #details .specDetail { margin-bottom: 28px; } +#HTMLReporter #details .specDetail .description { display: block; color: white; background-color: #b03911; } +#HTMLReporter .resultMessage { padding-top: 14px; color: #333333; } +#HTMLReporter .resultMessage span.result { display: block; } +#HTMLReporter .stackTrace { margin: 5px 0 0 0; max-height: 224px; overflow: auto; line-height: 18px; color: #666666; border: 1px solid #ddd; background: white; white-space: pre; } + +#TrivialReporter { padding: 8px 13px; position: absolute; top: 0; bottom: 0; left: 0; right: 0; overflow-y: scroll; background-color: white; font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif; /*.resultMessage {*/ /*white-space: pre;*/ /*}*/ } +#TrivialReporter a:visited, #TrivialReporter a { color: #303; } +#TrivialReporter a:hover, #TrivialReporter a:active { color: blue; } +#TrivialReporter .run_spec { float: right; padding-right: 5px; font-size: .8em; text-decoration: none; } +#TrivialReporter .banner { color: #303; background-color: #fef; padding: 5px; } +#TrivialReporter .logo { float: left; font-size: 1.1em; padding-left: 5px; } +#TrivialReporter .logo .version { font-size: .6em; padding-left: 1em; } +#TrivialReporter .runner.running { background-color: yellow; } +#TrivialReporter .options { text-align: right; font-size: .8em; } +#TrivialReporter .suite { border: 1px outset gray; margin: 5px 0; padding-left: 1em; } +#TrivialReporter .suite .suite { margin: 5px; } +#TrivialReporter .suite.passed { background-color: #dfd; } +#TrivialReporter .suite.failed { background-color: #fdd; } +#TrivialReporter .spec { margin: 5px; padding-left: 1em; clear: both; } +#TrivialReporter .spec.failed, #TrivialReporter .spec.passed, #TrivialReporter .spec.skipped { padding-bottom: 5px; border: 1px solid gray; } +#TrivialReporter .spec.failed { background-color: #fbb; border-color: red; } +#TrivialReporter .spec.passed { background-color: #bfb; border-color: green; } +#TrivialReporter .spec.skipped { background-color: #bbb; } +#TrivialReporter .messages { border-left: 1px dashed gray; padding-left: 1em; padding-right: 1em; } +#TrivialReporter .passed { background-color: #cfc; display: none; } +#TrivialReporter .failed { background-color: #fbb; } +#TrivialReporter .skipped { color: #777; background-color: #eee; display: none; } +#TrivialReporter .resultMessage span.result { display: block; line-height: 2em; color: black; } +#TrivialReporter .resultMessage .mismatch { color: black; } +#TrivialReporter .stackTrace { white-space: pre; font-size: .8em; margin-left: 10px; max-height: 5em; overflow: auto; border: 1px inset red; padding: 1em; background: #eef; } +#TrivialReporter .finished-at { padding-left: 1em; font-size: .6em; } +#TrivialReporter.show-passed .passed, #TrivialReporter.show-skipped .skipped { display: block; } +#TrivialReporter #jasmine_content { position: fixed; right: 100%; } +#TrivialReporter .runner { border: 1px solid gray; display: block; margin: 5px 0; padding: 2px 0 2px 10px; } diff --git a/external/jasmine/jasmine.js b/external/jasmine/jasmine.js new file mode 100644 index 000000000..8bba9262d --- /dev/null +++ b/external/jasmine/jasmine.js @@ -0,0 +1,2476 @@ +var isCommonJS = typeof window == "undefined"; + +/** + * Top level namespace for Jasmine, a lightweight JavaScript BDD/spec/testing framework. + * + * @namespace + */ +var jasmine = {}; +if (isCommonJS) exports.jasmine = jasmine; +/** + * @private + */ +jasmine.unimplementedMethod_ = function() { + throw new Error("unimplemented method"); +}; + +/** + * Use jasmine.undefined instead of undefined, since undefined is just + * a plain old variable and may be redefined by somebody else. + * + * @private + */ +jasmine.undefined = jasmine.___undefined___; + +/** + * Show diagnostic messages in the console if set to true + * + */ +jasmine.VERBOSE = false; + +/** + * Default interval in milliseconds for event loop yields (e.g. to allow network activity or to refresh the screen with the HTML-based runner). Small values here may result in slow test running. Zero means no updates until all tests have completed. + * + */ +jasmine.DEFAULT_UPDATE_INTERVAL = 250; + +/** + * Default timeout interval in milliseconds for waitsFor() blocks. + */ +jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000; + +jasmine.getGlobal = function() { + function getGlobal() { + return this; + } + + return getGlobal(); +}; + +/** + * Allows for bound functions to be compared. Internal use only. + * + * @ignore + * @private + * @param base {Object} bound 'this' for the function + * @param name {Function} function to find + */ +jasmine.bindOriginal_ = function(base, name) { + var original = base[name]; + if (original.apply) { + return function() { + return original.apply(base, arguments); + }; + } else { + // IE support + return jasmine.getGlobal()[name]; + } +}; + +jasmine.setTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'setTimeout'); +jasmine.clearTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearTimeout'); +jasmine.setInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'setInterval'); +jasmine.clearInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearInterval'); + +jasmine.MessageResult = function(values) { + this.type = 'log'; + this.values = values; + this.trace = new Error(); // todo: test better +}; + +jasmine.MessageResult.prototype.toString = function() { + var text = ""; + for (var i = 0; i < this.values.length; i++) { + if (i > 0) text += " "; + if (jasmine.isString_(this.values[i])) { + text += this.values[i]; + } else { + text += jasmine.pp(this.values[i]); + } + } + return text; +}; + +jasmine.ExpectationResult = function(params) { + this.type = 'expect'; + this.matcherName = params.matcherName; + this.passed_ = params.passed; + this.expected = params.expected; + this.actual = params.actual; + this.message = this.passed_ ? 'Passed.' : params.message; + + var trace = (params.trace || new Error(this.message)); + this.trace = this.passed_ ? '' : trace; +}; + +jasmine.ExpectationResult.prototype.toString = function () { + return this.message; +}; + +jasmine.ExpectationResult.prototype.passed = function () { + return this.passed_; +}; + +/** + * Getter for the Jasmine environment. Ensures one gets created + */ +jasmine.getEnv = function() { + var env = jasmine.currentEnv_ = jasmine.currentEnv_ || new jasmine.Env(); + return env; +}; + +/** + * @ignore + * @private + * @param value + * @returns {Boolean} + */ +jasmine.isArray_ = function(value) { + return jasmine.isA_("Array", value); +}; + +/** + * @ignore + * @private + * @param value + * @returns {Boolean} + */ +jasmine.isString_ = function(value) { + return jasmine.isA_("String", value); +}; + +/** + * @ignore + * @private + * @param value + * @returns {Boolean} + */ +jasmine.isNumber_ = function(value) { + return jasmine.isA_("Number", value); +}; + +/** + * @ignore + * @private + * @param {String} typeName + * @param value + * @returns {Boolean} + */ +jasmine.isA_ = function(typeName, value) { + return Object.prototype.toString.apply(value) === '[object ' + typeName + ']'; +}; + +/** + * Pretty printer for expecations. Takes any object and turns it into a human-readable string. + * + * @param value {Object} an object to be outputted + * @returns {String} + */ +jasmine.pp = function(value) { + var stringPrettyPrinter = new jasmine.StringPrettyPrinter(); + stringPrettyPrinter.format(value); + return stringPrettyPrinter.string; +}; + +/** + * Returns true if the object is a DOM Node. + * + * @param {Object} obj object to check + * @returns {Boolean} + */ +jasmine.isDomNode = function(obj) { + return obj.nodeType > 0; +}; + +/** + * Returns a matchable 'generic' object of the class type. For use in expecations of type when values don't matter. + * + * @example + * // don't care about which function is passed in, as long as it's a function + * expect(mySpy).toHaveBeenCalledWith(jasmine.any(Function)); + * + * @param {Class} clazz + * @returns matchable object of the type clazz + */ +jasmine.any = function(clazz) { + return new jasmine.Matchers.Any(clazz); +}; + +/** + * Jasmine Spies are test doubles that can act as stubs, spies, fakes or when used in an expecation, mocks. + * + * Spies should be created in test setup, before expectations. They can then be checked, using the standard Jasmine + * expectation syntax. Spies can be checked if they were called or not and what the calling params were. + * + * A Spy has the following fields: wasCalled, callCount, mostRecentCall, and argsForCall (see docs). + * + * Spies are torn down at the end of every spec. + * + * Note: Do not call new jasmine.Spy() directly - a spy must be created using spyOn, jasmine.createSpy or jasmine.createSpyObj. + * + * @example + * // a stub + * var myStub = jasmine.createSpy('myStub'); // can be used anywhere + * + * // spy example + * var foo = { + * not: function(bool) { return !bool; } + * } + * + * // actual foo.not will not be called, execution stops + * spyOn(foo, 'not'); + + // foo.not spied upon, execution will continue to implementation + * spyOn(foo, 'not').andCallThrough(); + * + * // fake example + * var foo = { + * not: function(bool) { return !bool; } + * } + * + * // foo.not(val) will return val + * spyOn(foo, 'not').andCallFake(function(value) {return value;}); + * + * // mock example + * foo.not(7 == 7); + * expect(foo.not).toHaveBeenCalled(); + * expect(foo.not).toHaveBeenCalledWith(true); + * + * @constructor + * @see spyOn, jasmine.createSpy, jasmine.createSpyObj + * @param {String} name + */ +jasmine.Spy = function(name) { + /** + * The name of the spy, if provided. + */ + this.identity = name || 'unknown'; + /** + * Is this Object a spy? + */ + this.isSpy = true; + /** + * The actual function this spy stubs. + */ + this.plan = function() { + }; + /** + * Tracking of the most recent call to the spy. + * @example + * var mySpy = jasmine.createSpy('foo'); + * mySpy(1, 2); + * mySpy.mostRecentCall.args = [1, 2]; + */ + this.mostRecentCall = {}; + + /** + * Holds arguments for each call to the spy, indexed by call count + * @example + * var mySpy = jasmine.createSpy('foo'); + * mySpy(1, 2); + * mySpy(7, 8); + * mySpy.mostRecentCall.args = [7, 8]; + * mySpy.argsForCall[0] = [1, 2]; + * mySpy.argsForCall[1] = [7, 8]; + */ + this.argsForCall = []; + this.calls = []; +}; + +/** + * Tells a spy to call through to the actual implemenatation. + * + * @example + * var foo = { + * bar: function() { // do some stuff } + * } + * + * // defining a spy on an existing property: foo.bar + * spyOn(foo, 'bar').andCallThrough(); + */ +jasmine.Spy.prototype.andCallThrough = function() { + this.plan = this.originalValue; + return this; +}; + +/** + * For setting the return value of a spy. + * + * @example + * // defining a spy from scratch: foo() returns 'baz' + * var foo = jasmine.createSpy('spy on foo').andReturn('baz'); + * + * // defining a spy on an existing property: foo.bar() returns 'baz' + * spyOn(foo, 'bar').andReturn('baz'); + * + * @param {Object} value + */ +jasmine.Spy.prototype.andReturn = function(value) { + this.plan = function() { + return value; + }; + return this; +}; + +/** + * For throwing an exception when a spy is called. + * + * @example + * // defining a spy from scratch: foo() throws an exception w/ message 'ouch' + * var foo = jasmine.createSpy('spy on foo').andThrow('baz'); + * + * // defining a spy on an existing property: foo.bar() throws an exception w/ message 'ouch' + * spyOn(foo, 'bar').andThrow('baz'); + * + * @param {String} exceptionMsg + */ +jasmine.Spy.prototype.andThrow = function(exceptionMsg) { + this.plan = function() { + throw exceptionMsg; + }; + return this; +}; + +/** + * Calls an alternate implementation when a spy is called. + * + * @example + * var baz = function() { + * // do some stuff, return something + * } + * // defining a spy from scratch: foo() calls the function baz + * var foo = jasmine.createSpy('spy on foo').andCall(baz); + * + * // defining a spy on an existing property: foo.bar() calls an anonymnous function + * spyOn(foo, 'bar').andCall(function() { return 'baz';} ); + * + * @param {Function} fakeFunc + */ +jasmine.Spy.prototype.andCallFake = function(fakeFunc) { + this.plan = fakeFunc; + return this; +}; + +/** + * Resets all of a spy's the tracking variables so that it can be used again. + * + * @example + * spyOn(foo, 'bar'); + * + * foo.bar(); + * + * expect(foo.bar.callCount).toEqual(1); + * + * foo.bar.reset(); + * + * expect(foo.bar.callCount).toEqual(0); + */ +jasmine.Spy.prototype.reset = function() { + this.wasCalled = false; + this.callCount = 0; + this.argsForCall = []; + this.calls = []; + this.mostRecentCall = {}; +}; + +jasmine.createSpy = function(name) { + + var spyObj = function() { + spyObj.wasCalled = true; + spyObj.callCount++; + var args = jasmine.util.argsToArray(arguments); + spyObj.mostRecentCall.object = this; + spyObj.mostRecentCall.args = args; + spyObj.argsForCall.push(args); + spyObj.calls.push({object: this, args: args}); + return spyObj.plan.apply(this, arguments); + }; + + var spy = new jasmine.Spy(name); + + for (var prop in spy) { + spyObj[prop] = spy[prop]; + } + + spyObj.reset(); + + return spyObj; +}; + +/** + * Determines whether an object is a spy. + * + * @param {jasmine.Spy|Object} putativeSpy + * @returns {Boolean} + */ +jasmine.isSpy = function(putativeSpy) { + return putativeSpy && putativeSpy.isSpy; +}; + +/** + * Creates a more complicated spy: an Object that has every property a function that is a spy. Used for stubbing something + * large in one call. + * + * @param {String} baseName name of spy class + * @param {Array} methodNames array of names of methods to make spies + */ +jasmine.createSpyObj = function(baseName, methodNames) { + if (!jasmine.isArray_(methodNames) || methodNames.length === 0) { + throw new Error('createSpyObj requires a non-empty array of method names to create spies for'); + } + var obj = {}; + for (var i = 0; i < methodNames.length; i++) { + obj[methodNames[i]] = jasmine.createSpy(baseName + '.' + methodNames[i]); + } + return obj; +}; + +/** + * All parameters are pretty-printed and concatenated together, then written to the current spec's output. + * + * Be careful not to leave calls to jasmine.log in production code. + */ +jasmine.log = function() { + var spec = jasmine.getEnv().currentSpec; + spec.log.apply(spec, arguments); +}; + +/** + * Function that installs a spy on an existing object's method name. Used within a Spec to create a spy. + * + * @example + * // spy example + * var foo = { + * not: function(bool) { return !bool; } + * } + * spyOn(foo, 'not'); // actual foo.not will not be called, execution stops + * + * @see jasmine.createSpy + * @param obj + * @param methodName + * @returns a Jasmine spy that can be chained with all spy methods + */ +var spyOn = function(obj, methodName) { + return jasmine.getEnv().currentSpec.spyOn(obj, methodName); +}; +if (isCommonJS) exports.spyOn = spyOn; + +/** + * Creates a Jasmine spec that will be added to the current suite. + * + * // TODO: pending tests + * + * @example + * it('should be true', function() { + * expect(true).toEqual(true); + * }); + * + * @param {String} desc description of this specification + * @param {Function} func defines the preconditions and expectations of the spec + */ +var it = function(desc, func) { + return jasmine.getEnv().it(desc, func); +}; +if (isCommonJS) exports.it = it; + +/** + * Creates a disabled Jasmine spec. + * + * A convenience method that allows existing specs to be disabled temporarily during development. + * + * @param {String} desc description of this specification + * @param {Function} func defines the preconditions and expectations of the spec + */ +var xit = function(desc, func) { + return jasmine.getEnv().xit(desc, func); +}; +if (isCommonJS) exports.xit = xit; + +/** + * Starts a chain for a Jasmine expectation. + * + * It is passed an Object that is the actual value and should chain to one of the many + * jasmine.Matchers functions. + * + * @param {Object} actual Actual value to test against and expected value + */ +var expect = function(actual) { + return jasmine.getEnv().currentSpec.expect(actual); +}; +if (isCommonJS) exports.expect = expect; + +/** + * Defines part of a jasmine spec. Used in cominbination with waits or waitsFor in asynchrnous specs. + * + * @param {Function} func Function that defines part of a jasmine spec. + */ +var runs = function(func) { + jasmine.getEnv().currentSpec.runs(func); +}; +if (isCommonJS) exports.runs = runs; + +/** + * Waits a fixed time period before moving to the next block. + * + * @deprecated Use waitsFor() instead + * @param {Number} timeout milliseconds to wait + */ +var waits = function(timeout) { + jasmine.getEnv().currentSpec.waits(timeout); +}; +if (isCommonJS) exports.waits = waits; + +/** + * Waits for the latchFunction to return true before proceeding to the next block. + * + * @param {Function} latchFunction + * @param {String} optional_timeoutMessage + * @param {Number} optional_timeout + */ +var waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) { + jasmine.getEnv().currentSpec.waitsFor.apply(jasmine.getEnv().currentSpec, arguments); +}; +if (isCommonJS) exports.waitsFor = waitsFor; + +/** + * A function that is called before each spec in a suite. + * + * Used for spec setup, including validating assumptions. + * + * @param {Function} beforeEachFunction + */ +var beforeEach = function(beforeEachFunction) { + jasmine.getEnv().beforeEach(beforeEachFunction); +}; +if (isCommonJS) exports.beforeEach = beforeEach; + +/** + * A function that is called after each spec in a suite. + * + * Used for restoring any state that is hijacked during spec execution. + * + * @param {Function} afterEachFunction + */ +var afterEach = function(afterEachFunction) { + jasmine.getEnv().afterEach(afterEachFunction); +}; +if (isCommonJS) exports.afterEach = afterEach; + +/** + * Defines a suite of specifications. + * + * Stores the description and all defined specs in the Jasmine environment as one suite of specs. Variables declared + * are accessible by calls to beforeEach, it, and afterEach. Describe blocks can be nested, allowing for specialization + * of setup in some tests. + * + * @example + * // TODO: a simple suite + * + * // TODO: a simple suite with a nested describe block + * + * @param {String} description A string, usually the class under test. + * @param {Function} specDefinitions function that defines several specs. + */ +var describe = function(description, specDefinitions) { + return jasmine.getEnv().describe(description, specDefinitions); +}; +if (isCommonJS) exports.describe = describe; + +/** + * Disables a suite of specifications. Used to disable some suites in a file, or files, temporarily during development. + * + * @param {String} description A string, usually the class under test. + * @param {Function} specDefinitions function that defines several specs. + */ +var xdescribe = function(description, specDefinitions) { + return jasmine.getEnv().xdescribe(description, specDefinitions); +}; +if (isCommonJS) exports.xdescribe = xdescribe; + + +// Provide the XMLHttpRequest class for IE 5.x-6.x: +jasmine.XmlHttpRequest = (typeof XMLHttpRequest == "undefined") ? function() { + function tryIt(f) { + try { + return f(); + } catch(e) { + } + return null; + } + + var xhr = tryIt(function() { + return new ActiveXObject("Msxml2.XMLHTTP.6.0"); + }) || + tryIt(function() { + return new ActiveXObject("Msxml2.XMLHTTP.3.0"); + }) || + tryIt(function() { + return new ActiveXObject("Msxml2.XMLHTTP"); + }) || + tryIt(function() { + return new ActiveXObject("Microsoft.XMLHTTP"); + }); + + if (!xhr) throw new Error("This browser does not support XMLHttpRequest."); + + return xhr; +} : XMLHttpRequest; +/** + * @namespace + */ +jasmine.util = {}; + +/** + * Declare that a child class inherit it's prototype from the parent class. + * + * @private + * @param {Function} childClass + * @param {Function} parentClass + */ +jasmine.util.inherit = function(childClass, parentClass) { + /** + * @private + */ + var subclass = function() { + }; + subclass.prototype = parentClass.prototype; + childClass.prototype = new subclass(); +}; + +jasmine.util.formatException = function(e) { + var lineNumber; + if (e.line) { + lineNumber = e.line; + } + else if (e.lineNumber) { + lineNumber = e.lineNumber; + } + + var file; + + if (e.sourceURL) { + file = e.sourceURL; + } + else if (e.fileName) { + file = e.fileName; + } + + var message = (e.name && e.message) ? (e.name + ': ' + e.message) : e.toString(); + + if (file && lineNumber) { + message += ' in ' + file + ' (line ' + lineNumber + ')'; + } + + return message; +}; + +jasmine.util.htmlEscape = function(str) { + if (!str) return str; + return str.replace(/&/g, '&') + .replace(//g, '>'); +}; + +jasmine.util.argsToArray = function(args) { + var arrayOfArgs = []; + for (var i = 0; i < args.length; i++) arrayOfArgs.push(args[i]); + return arrayOfArgs; +}; + +jasmine.util.extend = function(destination, source) { + for (var property in source) destination[property] = source[property]; + return destination; +}; + +/** + * Environment for Jasmine + * + * @constructor + */ +jasmine.Env = function() { + this.currentSpec = null; + this.currentSuite = null; + this.currentRunner_ = new jasmine.Runner(this); + + this.reporter = new jasmine.MultiReporter(); + + this.updateInterval = jasmine.DEFAULT_UPDATE_INTERVAL; + this.defaultTimeoutInterval = jasmine.DEFAULT_TIMEOUT_INTERVAL; + this.lastUpdate = 0; + this.specFilter = function() { + return true; + }; + + this.nextSpecId_ = 0; + this.nextSuiteId_ = 0; + this.equalityTesters_ = []; + + // wrap matchers + this.matchersClass = function() { + jasmine.Matchers.apply(this, arguments); + }; + jasmine.util.inherit(this.matchersClass, jasmine.Matchers); + + jasmine.Matchers.wrapInto_(jasmine.Matchers.prototype, this.matchersClass); +}; + + +jasmine.Env.prototype.setTimeout = jasmine.setTimeout; +jasmine.Env.prototype.clearTimeout = jasmine.clearTimeout; +jasmine.Env.prototype.setInterval = jasmine.setInterval; +jasmine.Env.prototype.clearInterval = jasmine.clearInterval; + +/** + * @returns an object containing jasmine version build info, if set. + */ +jasmine.Env.prototype.version = function () { + if (jasmine.version_) { + return jasmine.version_; + } else { + throw new Error('Version not set'); + } +}; + +/** + * @returns string containing jasmine version build info, if set. + */ +jasmine.Env.prototype.versionString = function() { + if (!jasmine.version_) { + return "version unknown"; + } + + var version = this.version(); + var versionString = version.major + "." + version.minor + "." + version.build; + if (version.release_candidate) { + versionString += ".rc" + version.release_candidate; + } + versionString += " revision " + version.revision; + return versionString; +}; + +/** + * @returns a sequential integer starting at 0 + */ +jasmine.Env.prototype.nextSpecId = function () { + return this.nextSpecId_++; +}; + +/** + * @returns a sequential integer starting at 0 + */ +jasmine.Env.prototype.nextSuiteId = function () { + return this.nextSuiteId_++; +}; + +/** + * Register a reporter to receive status updates from Jasmine. + * @param {jasmine.Reporter} reporter An object which will receive status updates. + */ +jasmine.Env.prototype.addReporter = function(reporter) { + this.reporter.addReporter(reporter); +}; + +jasmine.Env.prototype.execute = function() { + this.currentRunner_.execute(); +}; + +jasmine.Env.prototype.describe = function(description, specDefinitions) { + var suite = new jasmine.Suite(this, description, specDefinitions, this.currentSuite); + + var parentSuite = this.currentSuite; + if (parentSuite) { + parentSuite.add(suite); + } else { + this.currentRunner_.add(suite); + } + + this.currentSuite = suite; + + var declarationError = null; + try { + specDefinitions.call(suite); + } catch(e) { + declarationError = e; + } + + if (declarationError) { + this.it("encountered a declaration exception", function() { + throw declarationError; + }); + } + + this.currentSuite = parentSuite; + + return suite; +}; + +jasmine.Env.prototype.beforeEach = function(beforeEachFunction) { + if (this.currentSuite) { + this.currentSuite.beforeEach(beforeEachFunction); + } else { + this.currentRunner_.beforeEach(beforeEachFunction); + } +}; + +jasmine.Env.prototype.currentRunner = function () { + return this.currentRunner_; +}; + +jasmine.Env.prototype.afterEach = function(afterEachFunction) { + if (this.currentSuite) { + this.currentSuite.afterEach(afterEachFunction); + } else { + this.currentRunner_.afterEach(afterEachFunction); + } + +}; + +jasmine.Env.prototype.xdescribe = function(desc, specDefinitions) { + return { + execute: function() { + } + }; +}; + +jasmine.Env.prototype.it = function(description, func) { + var spec = new jasmine.Spec(this, this.currentSuite, description); + this.currentSuite.add(spec); + this.currentSpec = spec; + + if (func) { + spec.runs(func); + } + + return spec; +}; + +jasmine.Env.prototype.xit = function(desc, func) { + return { + id: this.nextSpecId(), + runs: function() { + } + }; +}; + +jasmine.Env.prototype.compareObjects_ = function(a, b, mismatchKeys, mismatchValues) { + if (a.__Jasmine_been_here_before__ === b && b.__Jasmine_been_here_before__ === a) { + return true; + } + + a.__Jasmine_been_here_before__ = b; + b.__Jasmine_been_here_before__ = a; + + var hasKey = function(obj, keyName) { + return obj !== null && obj[keyName] !== jasmine.undefined; + }; + + for (var property in b) { + if (!hasKey(a, property) && hasKey(b, property)) { + mismatchKeys.push("expected has key '" + property + "', but missing from actual."); + } + } + for (property in a) { + if (!hasKey(b, property) && hasKey(a, property)) { + mismatchKeys.push("expected missing key '" + property + "', but present in actual."); + } + } + for (property in b) { + if (property == '__Jasmine_been_here_before__') continue; + if (!this.equals_(a[property], b[property], mismatchKeys, mismatchValues)) { + mismatchValues.push("'" + property + "' was '" + (b[property] ? jasmine.util.htmlEscape(b[property].toString()) : b[property]) + "' in expected, but was '" + (a[property] ? jasmine.util.htmlEscape(a[property].toString()) : a[property]) + "' in actual."); + } + } + + if (jasmine.isArray_(a) && jasmine.isArray_(b) && a.length != b.length) { + mismatchValues.push("arrays were not the same length"); + } + + delete a.__Jasmine_been_here_before__; + delete b.__Jasmine_been_here_before__; + return (mismatchKeys.length === 0 && mismatchValues.length === 0); +}; + +jasmine.Env.prototype.equals_ = function(a, b, mismatchKeys, mismatchValues) { + mismatchKeys = mismatchKeys || []; + mismatchValues = mismatchValues || []; + + for (var i = 0; i < this.equalityTesters_.length; i++) { + var equalityTester = this.equalityTesters_[i]; + var result = equalityTester(a, b, this, mismatchKeys, mismatchValues); + if (result !== jasmine.undefined) return result; + } + + if (a === b) return true; + + if (a === jasmine.undefined || a === null || b === jasmine.undefined || b === null) { + return (a == jasmine.undefined && b == jasmine.undefined); + } + + if (jasmine.isDomNode(a) && jasmine.isDomNode(b)) { + return a === b; + } + + if (a instanceof Date && b instanceof Date) { + return a.getTime() == b.getTime(); + } + + if (a instanceof jasmine.Matchers.Any) { + return a.matches(b); + } + + if (b instanceof jasmine.Matchers.Any) { + return b.matches(a); + } + + if (jasmine.isString_(a) && jasmine.isString_(b)) { + return (a == b); + } + + if (jasmine.isNumber_(a) && jasmine.isNumber_(b)) { + return (a == b); + } + + if (typeof a === "object" && typeof b === "object") { + return this.compareObjects_(a, b, mismatchKeys, mismatchValues); + } + + //Straight check + return (a === b); +}; + +jasmine.Env.prototype.contains_ = function(haystack, needle) { + if (jasmine.isArray_(haystack)) { + for (var i = 0; i < haystack.length; i++) { + if (this.equals_(haystack[i], needle)) return true; + } + return false; + } + return haystack.indexOf(needle) >= 0; +}; + +jasmine.Env.prototype.addEqualityTester = function(equalityTester) { + this.equalityTesters_.push(equalityTester); +}; +/** No-op base class for Jasmine reporters. + * + * @constructor + */ +jasmine.Reporter = function() { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.Reporter.prototype.reportRunnerStarting = function(runner) { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.Reporter.prototype.reportRunnerResults = function(runner) { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.Reporter.prototype.reportSuiteResults = function(suite) { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.Reporter.prototype.reportSpecStarting = function(spec) { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.Reporter.prototype.reportSpecResults = function(spec) { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.Reporter.prototype.log = function(str) { +}; + +/** + * Blocks are functions with executable code that make up a spec. + * + * @constructor + * @param {jasmine.Env} env + * @param {Function} func + * @param {jasmine.Spec} spec + */ +jasmine.Block = function(env, func, spec) { + this.env = env; + this.func = func; + this.spec = spec; +}; + +jasmine.Block.prototype.execute = function(onComplete) { + try { + this.func.apply(this.spec); + } catch (e) { + this.spec.fail(e); + } + onComplete(); +}; +/** JavaScript API reporter. + * + * @constructor + */ +jasmine.JsApiReporter = function() { + this.started = false; + this.finished = false; + this.suites_ = []; + this.results_ = {}; +}; + +jasmine.JsApiReporter.prototype.reportRunnerStarting = function(runner) { + this.started = true; + var suites = runner.topLevelSuites(); + for (var i = 0; i < suites.length; i++) { + var suite = suites[i]; + this.suites_.push(this.summarize_(suite)); + } +}; + +jasmine.JsApiReporter.prototype.suites = function() { + return this.suites_; +}; + +jasmine.JsApiReporter.prototype.summarize_ = function(suiteOrSpec) { + var isSuite = suiteOrSpec instanceof jasmine.Suite; + var summary = { + id: suiteOrSpec.id, + name: suiteOrSpec.description, + type: isSuite ? 'suite' : 'spec', + children: [] + }; + + if (isSuite) { + var children = suiteOrSpec.children(); + for (var i = 0; i < children.length; i++) { + summary.children.push(this.summarize_(children[i])); + } + } + return summary; +}; + +jasmine.JsApiReporter.prototype.results = function() { + return this.results_; +}; + +jasmine.JsApiReporter.prototype.resultsForSpec = function(specId) { + return this.results_[specId]; +}; + +//noinspection JSUnusedLocalSymbols +jasmine.JsApiReporter.prototype.reportRunnerResults = function(runner) { + this.finished = true; +}; + +//noinspection JSUnusedLocalSymbols +jasmine.JsApiReporter.prototype.reportSuiteResults = function(suite) { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.JsApiReporter.prototype.reportSpecResults = function(spec) { + this.results_[spec.id] = { + messages: spec.results().getItems(), + result: spec.results().failedCount > 0 ? "failed" : "passed" + }; +}; + +//noinspection JSUnusedLocalSymbols +jasmine.JsApiReporter.prototype.log = function(str) { +}; + +jasmine.JsApiReporter.prototype.resultsForSpecs = function(specIds){ + var results = {}; + for (var i = 0; i < specIds.length; i++) { + var specId = specIds[i]; + results[specId] = this.summarizeResult_(this.results_[specId]); + } + return results; +}; + +jasmine.JsApiReporter.prototype.summarizeResult_ = function(result){ + var summaryMessages = []; + var messagesLength = result.messages.length; + for (var messageIndex = 0; messageIndex < messagesLength; messageIndex++) { + var resultMessage = result.messages[messageIndex]; + summaryMessages.push({ + text: resultMessage.type == 'log' ? resultMessage.toString() : jasmine.undefined, + passed: resultMessage.passed ? resultMessage.passed() : true, + type: resultMessage.type, + message: resultMessage.message, + trace: { + stack: resultMessage.passed && !resultMessage.passed() ? resultMessage.trace.stack : jasmine.undefined + } + }); + } + + return { + result : result.result, + messages : summaryMessages + }; +}; + +/** + * @constructor + * @param {jasmine.Env} env + * @param actual + * @param {jasmine.Spec} spec + */ +jasmine.Matchers = function(env, actual, spec, opt_isNot) { + this.env = env; + this.actual = actual; + this.spec = spec; + this.isNot = opt_isNot || false; + this.reportWasCalled_ = false; +}; + +// todo: @deprecated as of Jasmine 0.11, remove soon [xw] +jasmine.Matchers.pp = function(str) { + throw new Error("jasmine.Matchers.pp() is no longer supported, please use jasmine.pp() instead!"); +}; + +// todo: @deprecated Deprecated as of Jasmine 0.10. Rewrite your custom matchers to return true or false. [xw] +jasmine.Matchers.prototype.report = function(result, failing_message, details) { + throw new Error("As of jasmine 0.11, custom matchers must be implemented differently -- please see jasmine docs"); +}; + +jasmine.Matchers.wrapInto_ = function(prototype, matchersClass) { + for (var methodName in prototype) { + if (methodName == 'report') continue; + var orig = prototype[methodName]; + matchersClass.prototype[methodName] = jasmine.Matchers.matcherFn_(methodName, orig); + } +}; + +jasmine.Matchers.matcherFn_ = function(matcherName, matcherFunction) { + return function() { + var matcherArgs = jasmine.util.argsToArray(arguments); + var result = matcherFunction.apply(this, arguments); + + if (this.isNot) { + result = !result; + } + + if (this.reportWasCalled_) return result; + + var message; + if (!result) { + if (this.message) { + message = this.message.apply(this, arguments); + if (jasmine.isArray_(message)) { + message = message[this.isNot ? 1 : 0]; + } + } else { + var englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); }); + message = "Expected " + jasmine.pp(this.actual) + (this.isNot ? " not " : " ") + englishyPredicate; + if (matcherArgs.length > 0) { + for (var i = 0; i < matcherArgs.length; i++) { + if (i > 0) message += ","; + message += " " + jasmine.pp(matcherArgs[i]); + } + } + message += "."; + } + } + var expectationResult = new jasmine.ExpectationResult({ + matcherName: matcherName, + passed: result, + expected: matcherArgs.length > 1 ? matcherArgs : matcherArgs[0], + actual: this.actual, + message: message + }); + this.spec.addMatcherResult(expectationResult); + return jasmine.undefined; + }; +}; + + + + +/** + * toBe: compares the actual to the expected using === + * @param expected + */ +jasmine.Matchers.prototype.toBe = function(expected) { + return this.actual === expected; +}; + +/** + * toNotBe: compares the actual to the expected using !== + * @param expected + * @deprecated as of 1.0. Use not.toBe() instead. + */ +jasmine.Matchers.prototype.toNotBe = function(expected) { + return this.actual !== expected; +}; + +/** + * toEqual: compares the actual to the expected using common sense equality. Handles Objects, Arrays, etc. + * + * @param expected + */ +jasmine.Matchers.prototype.toEqual = function(expected) { + return this.env.equals_(this.actual, expected); +}; + +/** + * toNotEqual: compares the actual to the expected using the ! of jasmine.Matchers.toEqual + * @param expected + * @deprecated as of 1.0. Use not.toNotEqual() instead. + */ +jasmine.Matchers.prototype.toNotEqual = function(expected) { + return !this.env.equals_(this.actual, expected); +}; + +/** + * Matcher that compares the actual to the expected using a regular expression. Constructs a RegExp, so takes + * a pattern or a String. + * + * @param expected + */ +jasmine.Matchers.prototype.toMatch = function(expected) { + return new RegExp(expected).test(this.actual); +}; + +/** + * Matcher that compares the actual to the expected using the boolean inverse of jasmine.Matchers.toMatch + * @param expected + * @deprecated as of 1.0. Use not.toMatch() instead. + */ +jasmine.Matchers.prototype.toNotMatch = function(expected) { + return !(new RegExp(expected).test(this.actual)); +}; + +/** + * Matcher that compares the actual to jasmine.undefined. + */ +jasmine.Matchers.prototype.toBeDefined = function() { + return (this.actual !== jasmine.undefined); +}; + +/** + * Matcher that compares the actual to jasmine.undefined. + */ +jasmine.Matchers.prototype.toBeUndefined = function() { + return (this.actual === jasmine.undefined); +}; + +/** + * Matcher that compares the actual to null. + */ +jasmine.Matchers.prototype.toBeNull = function() { + return (this.actual === null); +}; + +/** + * Matcher that boolean not-nots the actual. + */ +jasmine.Matchers.prototype.toBeTruthy = function() { + return !!this.actual; +}; + + +/** + * Matcher that boolean nots the actual. + */ +jasmine.Matchers.prototype.toBeFalsy = function() { + return !this.actual; +}; + + +/** + * Matcher that checks to see if the actual, a Jasmine spy, was called. + */ +jasmine.Matchers.prototype.toHaveBeenCalled = function() { + if (arguments.length > 0) { + throw new Error('toHaveBeenCalled does not take arguments, use toHaveBeenCalledWith'); + } + + if (!jasmine.isSpy(this.actual)) { + throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); + } + + this.message = function() { + return [ + "Expected spy " + this.actual.identity + " to have been called.", + "Expected spy " + this.actual.identity + " not to have been called." + ]; + }; + + return this.actual.wasCalled; +}; + +/** @deprecated Use expect(xxx).toHaveBeenCalled() instead */ +jasmine.Matchers.prototype.wasCalled = jasmine.Matchers.prototype.toHaveBeenCalled; + +/** + * Matcher that checks to see if the actual, a Jasmine spy, was not called. + * + * @deprecated Use expect(xxx).not.toHaveBeenCalled() instead + */ +jasmine.Matchers.prototype.wasNotCalled = function() { + if (arguments.length > 0) { + throw new Error('wasNotCalled does not take arguments'); + } + + if (!jasmine.isSpy(this.actual)) { + throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); + } + + this.message = function() { + return [ + "Expected spy " + this.actual.identity + " to not have been called.", + "Expected spy " + this.actual.identity + " to have been called." + ]; + }; + + return !this.actual.wasCalled; +}; + +/** + * Matcher that checks to see if the actual, a Jasmine spy, was called with a set of parameters. + * + * @example + * + */ +jasmine.Matchers.prototype.toHaveBeenCalledWith = function() { + var expectedArgs = jasmine.util.argsToArray(arguments); + if (!jasmine.isSpy(this.actual)) { + throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); + } + this.message = function() { + if (this.actual.callCount === 0) { + // todo: what should the failure message for .not.toHaveBeenCalledWith() be? is this right? test better. [xw] + return [ + "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but it was never called.", + "Expected spy " + this.actual.identity + " not to have been called with " + jasmine.pp(expectedArgs) + " but it was." + ]; + } else { + return [ + "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but was called with " + jasmine.pp(this.actual.argsForCall), + "Expected spy " + this.actual.identity + " not to have been called with " + jasmine.pp(expectedArgs) + " but was called with " + jasmine.pp(this.actual.argsForCall) + ]; + } + }; + + return this.env.contains_(this.actual.argsForCall, expectedArgs); +}; + +/** @deprecated Use expect(xxx).toHaveBeenCalledWith() instead */ +jasmine.Matchers.prototype.wasCalledWith = jasmine.Matchers.prototype.toHaveBeenCalledWith; + +/** @deprecated Use expect(xxx).not.toHaveBeenCalledWith() instead */ +jasmine.Matchers.prototype.wasNotCalledWith = function() { + var expectedArgs = jasmine.util.argsToArray(arguments); + if (!jasmine.isSpy(this.actual)) { + throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); + } + + this.message = function() { + return [ + "Expected spy not to have been called with " + jasmine.pp(expectedArgs) + " but it was", + "Expected spy to have been called with " + jasmine.pp(expectedArgs) + " but it was" + ]; + }; + + return !this.env.contains_(this.actual.argsForCall, expectedArgs); +}; + +/** + * Matcher that checks that the expected item is an element in the actual Array. + * + * @param {Object} expected + */ +jasmine.Matchers.prototype.toContain = function(expected) { + return this.env.contains_(this.actual, expected); +}; + +/** + * Matcher that checks that the expected item is NOT an element in the actual Array. + * + * @param {Object} expected + * @deprecated as of 1.0. Use not.toNotContain() instead. + */ +jasmine.Matchers.prototype.toNotContain = function(expected) { + return !this.env.contains_(this.actual, expected); +}; + +jasmine.Matchers.prototype.toBeLessThan = function(expected) { + return this.actual < expected; +}; + +jasmine.Matchers.prototype.toBeGreaterThan = function(expected) { + return this.actual > expected; +}; + +/** + * Matcher that checks that the expected item is equal to the actual item + * up to a given level of decimal precision (default 2). + * + * @param {Number} expected + * @param {Number} precision + */ +jasmine.Matchers.prototype.toBeCloseTo = function(expected, precision) { + if (!(precision === 0)) { + precision = precision || 2; + } + var multiplier = Math.pow(10, precision); + var actual = Math.round(this.actual * multiplier); + expected = Math.round(expected * multiplier); + return expected == actual; +}; + +/** + * Matcher that checks that the expected exception was thrown by the actual. + * + * @param {String} expected + */ +jasmine.Matchers.prototype.toThrow = function(expected) { + var result = false; + var exception; + if (typeof this.actual != 'function') { + throw new Error('Actual is not a function'); + } + try { + this.actual(); + } catch (e) { + exception = e; + } + if (exception) { + result = (expected === jasmine.undefined || this.env.equals_(exception.message || exception, expected.message || expected)); + } + + var not = this.isNot ? "not " : ""; + + this.message = function() { + if (exception && (expected === jasmine.undefined || !this.env.equals_(exception.message || exception, expected.message || expected))) { + return ["Expected function " + not + "to throw", expected ? expected.message || expected : "an exception", ", but it threw", exception.message || exception].join(' '); + } else { + return "Expected function to throw an exception."; + } + }; + + return result; +}; + +jasmine.Matchers.Any = function(expectedClass) { + this.expectedClass = expectedClass; +}; + +jasmine.Matchers.Any.prototype.matches = function(other) { + if (this.expectedClass == String) { + return typeof other == 'string' || other instanceof String; + } + + if (this.expectedClass == Number) { + return typeof other == 'number' || other instanceof Number; + } + + if (this.expectedClass == Function) { + return typeof other == 'function' || other instanceof Function; + } + + if (this.expectedClass == Object) { + return typeof other == 'object'; + } + + return other instanceof this.expectedClass; +}; + +jasmine.Matchers.Any.prototype.toString = function() { + return ''; +}; + +/** + * @constructor + */ +jasmine.MultiReporter = function() { + this.subReporters_ = []; +}; +jasmine.util.inherit(jasmine.MultiReporter, jasmine.Reporter); + +jasmine.MultiReporter.prototype.addReporter = function(reporter) { + this.subReporters_.push(reporter); +}; + +(function() { + var functionNames = [ + "reportRunnerStarting", + "reportRunnerResults", + "reportSuiteResults", + "reportSpecStarting", + "reportSpecResults", + "log" + ]; + for (var i = 0; i < functionNames.length; i++) { + var functionName = functionNames[i]; + jasmine.MultiReporter.prototype[functionName] = (function(functionName) { + return function() { + for (var j = 0; j < this.subReporters_.length; j++) { + var subReporter = this.subReporters_[j]; + if (subReporter[functionName]) { + subReporter[functionName].apply(subReporter, arguments); + } + } + }; + })(functionName); + } +})(); +/** + * Holds results for a set of Jasmine spec. Allows for the results array to hold another jasmine.NestedResults + * + * @constructor + */ +jasmine.NestedResults = function() { + /** + * The total count of results + */ + this.totalCount = 0; + /** + * Number of passed results + */ + this.passedCount = 0; + /** + * Number of failed results + */ + this.failedCount = 0; + /** + * Was this suite/spec skipped? + */ + this.skipped = false; + /** + * @ignore + */ + this.items_ = []; +}; + +/** + * Roll up the result counts. + * + * @param result + */ +jasmine.NestedResults.prototype.rollupCounts = function(result) { + this.totalCount += result.totalCount; + this.passedCount += result.passedCount; + this.failedCount += result.failedCount; +}; + +/** + * Adds a log message. + * @param values Array of message parts which will be concatenated later. + */ +jasmine.NestedResults.prototype.log = function(values) { + this.items_.push(new jasmine.MessageResult(values)); +}; + +/** + * Getter for the results: message & results. + */ +jasmine.NestedResults.prototype.getItems = function() { + return this.items_; +}; + +/** + * Adds a result, tracking counts (total, passed, & failed) + * @param {jasmine.ExpectationResult|jasmine.NestedResults} result + */ +jasmine.NestedResults.prototype.addResult = function(result) { + if (result.type != 'log') { + if (result.items_) { + this.rollupCounts(result); + } else { + this.totalCount++; + if (result.passed()) { + this.passedCount++; + } else { + this.failedCount++; + } + } + } + this.items_.push(result); +}; + +/** + * @returns {Boolean} True if everything below passed + */ +jasmine.NestedResults.prototype.passed = function() { + return this.passedCount === this.totalCount; +}; +/** + * Base class for pretty printing for expectation results. + */ +jasmine.PrettyPrinter = function() { + this.ppNestLevel_ = 0; +}; + +/** + * Formats a value in a nice, human-readable string. + * + * @param value + */ +jasmine.PrettyPrinter.prototype.format = function(value) { + if (this.ppNestLevel_ > 40) { + throw new Error('jasmine.PrettyPrinter: format() nested too deeply!'); + } + + this.ppNestLevel_++; + try { + if (value === jasmine.undefined) { + this.emitScalar('undefined'); + } else if (value === null) { + this.emitScalar('null'); + } else if (value === jasmine.getGlobal()) { + this.emitScalar(''); + } else if (value instanceof jasmine.Matchers.Any) { + this.emitScalar(value.toString()); + } else if (typeof value === 'string') { + this.emitString(value); + } else if (jasmine.isSpy(value)) { + this.emitScalar("spy on " + value.identity); + } else if (value instanceof RegExp) { + this.emitScalar(value.toString()); + } else if (typeof value === 'function') { + this.emitScalar('Function'); + } else if (typeof value.nodeType === 'number') { + this.emitScalar('HTMLNode'); + } else if (value instanceof Date) { + this.emitScalar('Date(' + value + ')'); + } else if (value.__Jasmine_been_here_before__) { + this.emitScalar(''); + } else if (jasmine.isArray_(value) || typeof value == 'object') { + value.__Jasmine_been_here_before__ = true; + if (jasmine.isArray_(value)) { + this.emitArray(value); + } else { + this.emitObject(value); + } + delete value.__Jasmine_been_here_before__; + } else { + this.emitScalar(value.toString()); + } + } finally { + this.ppNestLevel_--; + } +}; + +jasmine.PrettyPrinter.prototype.iterateObject = function(obj, fn) { + for (var property in obj) { + if (property == '__Jasmine_been_here_before__') continue; + fn(property, obj.__lookupGetter__ ? (obj.__lookupGetter__(property) !== jasmine.undefined && + obj.__lookupGetter__(property) !== null) : false); + } +}; + +jasmine.PrettyPrinter.prototype.emitArray = jasmine.unimplementedMethod_; +jasmine.PrettyPrinter.prototype.emitObject = jasmine.unimplementedMethod_; +jasmine.PrettyPrinter.prototype.emitScalar = jasmine.unimplementedMethod_; +jasmine.PrettyPrinter.prototype.emitString = jasmine.unimplementedMethod_; + +jasmine.StringPrettyPrinter = function() { + jasmine.PrettyPrinter.call(this); + + this.string = ''; +}; +jasmine.util.inherit(jasmine.StringPrettyPrinter, jasmine.PrettyPrinter); + +jasmine.StringPrettyPrinter.prototype.emitScalar = function(value) { + this.append(value); +}; + +jasmine.StringPrettyPrinter.prototype.emitString = function(value) { + this.append("'" + value + "'"); +}; + +jasmine.StringPrettyPrinter.prototype.emitArray = function(array) { + this.append('[ '); + for (var i = 0; i < array.length; i++) { + if (i > 0) { + this.append(', '); + } + this.format(array[i]); + } + this.append(' ]'); +}; + +jasmine.StringPrettyPrinter.prototype.emitObject = function(obj) { + var self = this; + this.append('{ '); + var first = true; + + this.iterateObject(obj, function(property, isGetter) { + if (first) { + first = false; + } else { + self.append(', '); + } + + self.append(property); + self.append(' : '); + if (isGetter) { + self.append(''); + } else { + self.format(obj[property]); + } + }); + + this.append(' }'); +}; + +jasmine.StringPrettyPrinter.prototype.append = function(value) { + this.string += value; +}; +jasmine.Queue = function(env) { + this.env = env; + this.blocks = []; + this.running = false; + this.index = 0; + this.offset = 0; + this.abort = false; +}; + +jasmine.Queue.prototype.addBefore = function(block) { + this.blocks.unshift(block); +}; + +jasmine.Queue.prototype.add = function(block) { + this.blocks.push(block); +}; + +jasmine.Queue.prototype.insertNext = function(block) { + this.blocks.splice((this.index + this.offset + 1), 0, block); + this.offset++; +}; + +jasmine.Queue.prototype.start = function(onComplete) { + this.running = true; + this.onComplete = onComplete; + this.next_(); +}; + +jasmine.Queue.prototype.isRunning = function() { + return this.running; +}; + +jasmine.Queue.LOOP_DONT_RECURSE = true; + +jasmine.Queue.prototype.next_ = function() { + var self = this; + var goAgain = true; + + while (goAgain) { + goAgain = false; + + if (self.index < self.blocks.length && !this.abort) { + var calledSynchronously = true; + var completedSynchronously = false; + + var onComplete = function () { + if (jasmine.Queue.LOOP_DONT_RECURSE && calledSynchronously) { + completedSynchronously = true; + return; + } + + if (self.blocks[self.index].abort) { + self.abort = true; + } + + self.offset = 0; + self.index++; + + var now = new Date().getTime(); + if (self.env.updateInterval && now - self.env.lastUpdate > self.env.updateInterval) { + self.env.lastUpdate = now; + self.env.setTimeout(function() { + self.next_(); + }, 0); + } else { + if (jasmine.Queue.LOOP_DONT_RECURSE && completedSynchronously) { + goAgain = true; + } else { + self.next_(); + } + } + }; + self.blocks[self.index].execute(onComplete); + + calledSynchronously = false; + if (completedSynchronously) { + onComplete(); + } + + } else { + self.running = false; + if (self.onComplete) { + self.onComplete(); + } + } + } +}; + +jasmine.Queue.prototype.results = function() { + var results = new jasmine.NestedResults(); + for (var i = 0; i < this.blocks.length; i++) { + if (this.blocks[i].results) { + results.addResult(this.blocks[i].results()); + } + } + return results; +}; + + +/** + * Runner + * + * @constructor + * @param {jasmine.Env} env + */ +jasmine.Runner = function(env) { + var self = this; + self.env = env; + self.queue = new jasmine.Queue(env); + self.before_ = []; + self.after_ = []; + self.suites_ = []; +}; + +jasmine.Runner.prototype.execute = function() { + var self = this; + if (self.env.reporter.reportRunnerStarting) { + self.env.reporter.reportRunnerStarting(this); + } + self.queue.start(function () { + self.finishCallback(); + }); +}; + +jasmine.Runner.prototype.beforeEach = function(beforeEachFunction) { + beforeEachFunction.typeName = 'beforeEach'; + this.before_.splice(0,0,beforeEachFunction); +}; + +jasmine.Runner.prototype.afterEach = function(afterEachFunction) { + afterEachFunction.typeName = 'afterEach'; + this.after_.splice(0,0,afterEachFunction); +}; + + +jasmine.Runner.prototype.finishCallback = function() { + this.env.reporter.reportRunnerResults(this); +}; + +jasmine.Runner.prototype.addSuite = function(suite) { + this.suites_.push(suite); +}; + +jasmine.Runner.prototype.add = function(block) { + if (block instanceof jasmine.Suite) { + this.addSuite(block); + } + this.queue.add(block); +}; + +jasmine.Runner.prototype.specs = function () { + var suites = this.suites(); + var specs = []; + for (var i = 0; i < suites.length; i++) { + specs = specs.concat(suites[i].specs()); + } + return specs; +}; + +jasmine.Runner.prototype.suites = function() { + return this.suites_; +}; + +jasmine.Runner.prototype.topLevelSuites = function() { + var topLevelSuites = []; + for (var i = 0; i < this.suites_.length; i++) { + if (!this.suites_[i].parentSuite) { + topLevelSuites.push(this.suites_[i]); + } + } + return topLevelSuites; +}; + +jasmine.Runner.prototype.results = function() { + return this.queue.results(); +}; +/** + * Internal representation of a Jasmine specification, or test. + * + * @constructor + * @param {jasmine.Env} env + * @param {jasmine.Suite} suite + * @param {String} description + */ +jasmine.Spec = function(env, suite, description) { + if (!env) { + throw new Error('jasmine.Env() required'); + } + if (!suite) { + throw new Error('jasmine.Suite() required'); + } + var spec = this; + spec.id = env.nextSpecId ? env.nextSpecId() : null; + spec.env = env; + spec.suite = suite; + spec.description = description; + spec.queue = new jasmine.Queue(env); + + spec.afterCallbacks = []; + spec.spies_ = []; + + spec.results_ = new jasmine.NestedResults(); + spec.results_.description = description; + spec.matchersClass = null; +}; + +jasmine.Spec.prototype.getFullName = function() { + return this.suite.getFullName() + ' ' + this.description + '.'; +}; + + +jasmine.Spec.prototype.results = function() { + return this.results_; +}; + +/** + * All parameters are pretty-printed and concatenated together, then written to the spec's output. + * + * Be careful not to leave calls to jasmine.log in production code. + */ +jasmine.Spec.prototype.log = function() { + return this.results_.log(arguments); +}; + +jasmine.Spec.prototype.runs = function (func) { + var block = new jasmine.Block(this.env, func, this); + this.addToQueue(block); + return this; +}; + +jasmine.Spec.prototype.addToQueue = function (block) { + if (this.queue.isRunning()) { + this.queue.insertNext(block); + } else { + this.queue.add(block); + } +}; + +/** + * @param {jasmine.ExpectationResult} result + */ +jasmine.Spec.prototype.addMatcherResult = function(result) { + this.results_.addResult(result); +}; + +jasmine.Spec.prototype.expect = function(actual) { + var positive = new (this.getMatchersClass_())(this.env, actual, this); + positive.not = new (this.getMatchersClass_())(this.env, actual, this, true); + return positive; +}; + +/** + * Waits a fixed time period before moving to the next block. + * + * @deprecated Use waitsFor() instead + * @param {Number} timeout milliseconds to wait + */ +jasmine.Spec.prototype.waits = function(timeout) { + var waitsFunc = new jasmine.WaitsBlock(this.env, timeout, this); + this.addToQueue(waitsFunc); + return this; +}; + +/** + * Waits for the latchFunction to return true before proceeding to the next block. + * + * @param {Function} latchFunction + * @param {String} optional_timeoutMessage + * @param {Number} optional_timeout + */ +jasmine.Spec.prototype.waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) { + var latchFunction_ = null; + var optional_timeoutMessage_ = null; + var optional_timeout_ = null; + + for (var i = 0; i < arguments.length; i++) { + var arg = arguments[i]; + switch (typeof arg) { + case 'function': + latchFunction_ = arg; + break; + case 'string': + optional_timeoutMessage_ = arg; + break; + case 'number': + optional_timeout_ = arg; + break; + } + } + + var waitsForFunc = new jasmine.WaitsForBlock(this.env, optional_timeout_, latchFunction_, optional_timeoutMessage_, this); + this.addToQueue(waitsForFunc); + return this; +}; + +jasmine.Spec.prototype.fail = function (e) { + var expectationResult = new jasmine.ExpectationResult({ + passed: false, + message: e ? jasmine.util.formatException(e) : 'Exception', + trace: { stack: e.stack } + }); + this.results_.addResult(expectationResult); +}; + +jasmine.Spec.prototype.getMatchersClass_ = function() { + return this.matchersClass || this.env.matchersClass; +}; + +jasmine.Spec.prototype.addMatchers = function(matchersPrototype) { + var parent = this.getMatchersClass_(); + var newMatchersClass = function() { + parent.apply(this, arguments); + }; + jasmine.util.inherit(newMatchersClass, parent); + jasmine.Matchers.wrapInto_(matchersPrototype, newMatchersClass); + this.matchersClass = newMatchersClass; +}; + +jasmine.Spec.prototype.finishCallback = function() { + this.env.reporter.reportSpecResults(this); +}; + +jasmine.Spec.prototype.finish = function(onComplete) { + this.removeAllSpies(); + this.finishCallback(); + if (onComplete) { + onComplete(); + } +}; + +jasmine.Spec.prototype.after = function(doAfter) { + if (this.queue.isRunning()) { + this.queue.add(new jasmine.Block(this.env, doAfter, this)); + } else { + this.afterCallbacks.unshift(doAfter); + } +}; + +jasmine.Spec.prototype.execute = function(onComplete) { + var spec = this; + if (!spec.env.specFilter(spec)) { + spec.results_.skipped = true; + spec.finish(onComplete); + return; + } + + this.env.reporter.reportSpecStarting(this); + + spec.env.currentSpec = spec; + + spec.addBeforesAndAftersToQueue(); + + spec.queue.start(function () { + spec.finish(onComplete); + }); +}; + +jasmine.Spec.prototype.addBeforesAndAftersToQueue = function() { + var runner = this.env.currentRunner(); + var i; + + for (var suite = this.suite; suite; suite = suite.parentSuite) { + for (i = 0; i < suite.before_.length; i++) { + this.queue.addBefore(new jasmine.Block(this.env, suite.before_[i], this)); + } + } + for (i = 0; i < runner.before_.length; i++) { + this.queue.addBefore(new jasmine.Block(this.env, runner.before_[i], this)); + } + for (i = 0; i < this.afterCallbacks.length; i++) { + this.queue.add(new jasmine.Block(this.env, this.afterCallbacks[i], this)); + } + for (suite = this.suite; suite; suite = suite.parentSuite) { + for (i = 0; i < suite.after_.length; i++) { + this.queue.add(new jasmine.Block(this.env, suite.after_[i], this)); + } + } + for (i = 0; i < runner.after_.length; i++) { + this.queue.add(new jasmine.Block(this.env, runner.after_[i], this)); + } +}; + +jasmine.Spec.prototype.explodes = function() { + throw 'explodes function should not have been called'; +}; + +jasmine.Spec.prototype.spyOn = function(obj, methodName, ignoreMethodDoesntExist) { + if (obj == jasmine.undefined) { + throw "spyOn could not find an object to spy upon for " + methodName + "()"; + } + + if (!ignoreMethodDoesntExist && obj[methodName] === jasmine.undefined) { + throw methodName + '() method does not exist'; + } + + if (!ignoreMethodDoesntExist && obj[methodName] && obj[methodName].isSpy) { + throw new Error(methodName + ' has already been spied upon'); + } + + var spyObj = jasmine.createSpy(methodName); + + this.spies_.push(spyObj); + spyObj.baseObj = obj; + spyObj.methodName = methodName; + spyObj.originalValue = obj[methodName]; + + obj[methodName] = spyObj; + + return spyObj; +}; + +jasmine.Spec.prototype.removeAllSpies = function() { + for (var i = 0; i < this.spies_.length; i++) { + var spy = this.spies_[i]; + spy.baseObj[spy.methodName] = spy.originalValue; + } + this.spies_ = []; +}; + +/** + * Internal representation of a Jasmine suite. + * + * @constructor + * @param {jasmine.Env} env + * @param {String} description + * @param {Function} specDefinitions + * @param {jasmine.Suite} parentSuite + */ +jasmine.Suite = function(env, description, specDefinitions, parentSuite) { + var self = this; + self.id = env.nextSuiteId ? env.nextSuiteId() : null; + self.description = description; + self.queue = new jasmine.Queue(env); + self.parentSuite = parentSuite; + self.env = env; + self.before_ = []; + self.after_ = []; + self.children_ = []; + self.suites_ = []; + self.specs_ = []; +}; + +jasmine.Suite.prototype.getFullName = function() { + var fullName = this.description; + for (var parentSuite = this.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) { + fullName = parentSuite.description + ' ' + fullName; + } + return fullName; +}; + +jasmine.Suite.prototype.finish = function(onComplete) { + this.env.reporter.reportSuiteResults(this); + this.finished = true; + if (typeof(onComplete) == 'function') { + onComplete(); + } +}; + +jasmine.Suite.prototype.beforeEach = function(beforeEachFunction) { + beforeEachFunction.typeName = 'beforeEach'; + this.before_.unshift(beforeEachFunction); +}; + +jasmine.Suite.prototype.afterEach = function(afterEachFunction) { + afterEachFunction.typeName = 'afterEach'; + this.after_.unshift(afterEachFunction); +}; + +jasmine.Suite.prototype.results = function() { + return this.queue.results(); +}; + +jasmine.Suite.prototype.add = function(suiteOrSpec) { + this.children_.push(suiteOrSpec); + if (suiteOrSpec instanceof jasmine.Suite) { + this.suites_.push(suiteOrSpec); + this.env.currentRunner().addSuite(suiteOrSpec); + } else { + this.specs_.push(suiteOrSpec); + } + this.queue.add(suiteOrSpec); +}; + +jasmine.Suite.prototype.specs = function() { + return this.specs_; +}; + +jasmine.Suite.prototype.suites = function() { + return this.suites_; +}; + +jasmine.Suite.prototype.children = function() { + return this.children_; +}; + +jasmine.Suite.prototype.execute = function(onComplete) { + var self = this; + this.queue.start(function () { + self.finish(onComplete); + }); +}; +jasmine.WaitsBlock = function(env, timeout, spec) { + this.timeout = timeout; + jasmine.Block.call(this, env, null, spec); +}; + +jasmine.util.inherit(jasmine.WaitsBlock, jasmine.Block); + +jasmine.WaitsBlock.prototype.execute = function (onComplete) { + if (jasmine.VERBOSE) { + this.env.reporter.log('>> Jasmine waiting for ' + this.timeout + ' ms...'); + } + this.env.setTimeout(function () { + onComplete(); + }, this.timeout); +}; +/** + * A block which waits for some condition to become true, with timeout. + * + * @constructor + * @extends jasmine.Block + * @param {jasmine.Env} env The Jasmine environment. + * @param {Number} timeout The maximum time in milliseconds to wait for the condition to become true. + * @param {Function} latchFunction A function which returns true when the desired condition has been met. + * @param {String} message The message to display if the desired condition hasn't been met within the given time period. + * @param {jasmine.Spec} spec The Jasmine spec. + */ +jasmine.WaitsForBlock = function(env, timeout, latchFunction, message, spec) { + this.timeout = timeout || env.defaultTimeoutInterval; + this.latchFunction = latchFunction; + this.message = message; + this.totalTimeSpentWaitingForLatch = 0; + jasmine.Block.call(this, env, null, spec); +}; +jasmine.util.inherit(jasmine.WaitsForBlock, jasmine.Block); + +jasmine.WaitsForBlock.TIMEOUT_INCREMENT = 10; + +jasmine.WaitsForBlock.prototype.execute = function(onComplete) { + if (jasmine.VERBOSE) { + this.env.reporter.log('>> Jasmine waiting for ' + (this.message || 'something to happen')); + } + var latchFunctionResult; + try { + latchFunctionResult = this.latchFunction.apply(this.spec); + } catch (e) { + this.spec.fail(e); + onComplete(); + return; + } + + if (latchFunctionResult) { + onComplete(); + } else if (this.totalTimeSpentWaitingForLatch >= this.timeout) { + var message = 'timed out after ' + this.timeout + ' msec waiting for ' + (this.message || 'something to happen'); + this.spec.fail({ + name: 'timeout', + message: message + }); + + this.abort = true; + onComplete(); + } else { + this.totalTimeSpentWaitingForLatch += jasmine.WaitsForBlock.TIMEOUT_INCREMENT; + var self = this; + this.env.setTimeout(function() { + self.execute(onComplete); + }, jasmine.WaitsForBlock.TIMEOUT_INCREMENT); + } +}; +// Mock setTimeout, clearTimeout +// Contributed by Pivotal Computer Systems, www.pivotalsf.com + +jasmine.FakeTimer = function() { + this.reset(); + + var self = this; + self.setTimeout = function(funcToCall, millis) { + self.timeoutsMade++; + self.scheduleFunction(self.timeoutsMade, funcToCall, millis, false); + return self.timeoutsMade; + }; + + self.setInterval = function(funcToCall, millis) { + self.timeoutsMade++; + self.scheduleFunction(self.timeoutsMade, funcToCall, millis, true); + return self.timeoutsMade; + }; + + self.clearTimeout = function(timeoutKey) { + self.scheduledFunctions[timeoutKey] = jasmine.undefined; + }; + + self.clearInterval = function(timeoutKey) { + self.scheduledFunctions[timeoutKey] = jasmine.undefined; + }; + +}; + +jasmine.FakeTimer.prototype.reset = function() { + this.timeoutsMade = 0; + this.scheduledFunctions = {}; + this.nowMillis = 0; +}; + +jasmine.FakeTimer.prototype.tick = function(millis) { + var oldMillis = this.nowMillis; + var newMillis = oldMillis + millis; + this.runFunctionsWithinRange(oldMillis, newMillis); + this.nowMillis = newMillis; +}; + +jasmine.FakeTimer.prototype.runFunctionsWithinRange = function(oldMillis, nowMillis) { + var scheduledFunc; + var funcsToRun = []; + for (var timeoutKey in this.scheduledFunctions) { + scheduledFunc = this.scheduledFunctions[timeoutKey]; + if (scheduledFunc != jasmine.undefined && + scheduledFunc.runAtMillis >= oldMillis && + scheduledFunc.runAtMillis <= nowMillis) { + funcsToRun.push(scheduledFunc); + this.scheduledFunctions[timeoutKey] = jasmine.undefined; + } + } + + if (funcsToRun.length > 0) { + funcsToRun.sort(function(a, b) { + return a.runAtMillis - b.runAtMillis; + }); + for (var i = 0; i < funcsToRun.length; ++i) { + try { + var funcToRun = funcsToRun[i]; + this.nowMillis = funcToRun.runAtMillis; + funcToRun.funcToCall(); + if (funcToRun.recurring) { + this.scheduleFunction(funcToRun.timeoutKey, + funcToRun.funcToCall, + funcToRun.millis, + true); + } + } catch(e) { + } + } + this.runFunctionsWithinRange(oldMillis, nowMillis); + } +}; + +jasmine.FakeTimer.prototype.scheduleFunction = function(timeoutKey, funcToCall, millis, recurring) { + this.scheduledFunctions[timeoutKey] = { + runAtMillis: this.nowMillis + millis, + funcToCall: funcToCall, + recurring: recurring, + timeoutKey: timeoutKey, + millis: millis + }; +}; + +/** + * @namespace + */ +jasmine.Clock = { + defaultFakeTimer: new jasmine.FakeTimer(), + + reset: function() { + jasmine.Clock.assertInstalled(); + jasmine.Clock.defaultFakeTimer.reset(); + }, + + tick: function(millis) { + jasmine.Clock.assertInstalled(); + jasmine.Clock.defaultFakeTimer.tick(millis); + }, + + runFunctionsWithinRange: function(oldMillis, nowMillis) { + jasmine.Clock.defaultFakeTimer.runFunctionsWithinRange(oldMillis, nowMillis); + }, + + scheduleFunction: function(timeoutKey, funcToCall, millis, recurring) { + jasmine.Clock.defaultFakeTimer.scheduleFunction(timeoutKey, funcToCall, millis, recurring); + }, + + useMock: function() { + if (!jasmine.Clock.isInstalled()) { + var spec = jasmine.getEnv().currentSpec; + spec.after(jasmine.Clock.uninstallMock); + + jasmine.Clock.installMock(); + } + }, + + installMock: function() { + jasmine.Clock.installed = jasmine.Clock.defaultFakeTimer; + }, + + uninstallMock: function() { + jasmine.Clock.assertInstalled(); + jasmine.Clock.installed = jasmine.Clock.real; + }, + + real: { + setTimeout: jasmine.getGlobal().setTimeout, + clearTimeout: jasmine.getGlobal().clearTimeout, + setInterval: jasmine.getGlobal().setInterval, + clearInterval: jasmine.getGlobal().clearInterval + }, + + assertInstalled: function() { + if (!jasmine.Clock.isInstalled()) { + throw new Error("Mock clock is not installed, use jasmine.Clock.useMock()"); + } + }, + + isInstalled: function() { + return jasmine.Clock.installed == jasmine.Clock.defaultFakeTimer; + }, + + installed: null +}; +jasmine.Clock.installed = jasmine.Clock.real; + +//else for IE support +jasmine.getGlobal().setTimeout = function(funcToCall, millis) { + if (jasmine.Clock.installed.setTimeout.apply) { + return jasmine.Clock.installed.setTimeout.apply(this, arguments); + } else { + return jasmine.Clock.installed.setTimeout(funcToCall, millis); + } +}; + +jasmine.getGlobal().setInterval = function(funcToCall, millis) { + if (jasmine.Clock.installed.setInterval.apply) { + return jasmine.Clock.installed.setInterval.apply(this, arguments); + } else { + return jasmine.Clock.installed.setInterval(funcToCall, millis); + } +}; + +jasmine.getGlobal().clearTimeout = function(timeoutKey) { + if (jasmine.Clock.installed.clearTimeout.apply) { + return jasmine.Clock.installed.clearTimeout.apply(this, arguments); + } else { + return jasmine.Clock.installed.clearTimeout(timeoutKey); + } +}; + +jasmine.getGlobal().clearInterval = function(timeoutKey) { + if (jasmine.Clock.installed.clearTimeout.apply) { + return jasmine.Clock.installed.clearInterval.apply(this, arguments); + } else { + return jasmine.Clock.installed.clearInterval(timeoutKey); + } +}; + +jasmine.version_= { + "major": 1, + "minor": 1, + "build": 0, + "revision": 1320442951 +}; diff --git a/external/jasmine/jasmine_favicon.png b/external/jasmine/jasmine_favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..218f3b43713598fa5a3e78b57aceb909c33f46df GIT binary patch literal 905 zcmV;419tq0P)Px#AY({UO#lFTCIA3{ga82g0001h=l}q9FaQARU;qF* zm;eA5aGbhPJOBUy24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_0008u zNkl3{fod28|PjmA)7fYg4w8-(2my9xtBGOs}K`n&t1VzxMO^X)M zrW+Ln1udc?q6TP)z5gAjt)P&D!M$+HJK#x<`xnD030zwD?KrxxY!2tlA zGc-58?0D7SsT)7Km=v+tNVNUk`?s@;^OxCF)y6P}_mL;~7;S<@b|MzmKq)m8l@yky zT1~ECpxZw@64!nkI34QLiUsA%i%N>-$&zGYR7WJyi9ERMyS(%kf z7A_r)X>!90&m(FwDQZ>q;+nOa*KR2+E6Fz)QwU=W1Oyo*4>_qlm|~joa|{4_A_3W8 z#FFZzRp-xMIx5a7D_Fj3&#r^TbIY@cND1d0f*^qDIs{!pw!IWGQ_%l4#ASm_D5Vet z0%ek7^)@xPihX_G0&hIc9*14ca=D!8oG}vW?H%~w^F?f_s>zU|fKrNJXJ_d6{v!t( zpEoqMws_yQws>3o?VW8Txq~#->dJG^ELW5irR!s`(_JvD^6;r+ho~eIK@ia8_lH(h zt*-p?CFC1_h2MV=?jP){uW!7WjLjCaO&c1D+tf582!XEaoB#xWAYcN5f$sLtf$koW zQs{{>)ZTq?FC6|J_%n}AWbiFK(Bo-%^-{H`*)E(ucjo-r%SYm)W5f6tN=xz=S646E fNXW#U{x?4WXWJ pdf.js unit test - - + + - - + + - + From 1b0ccad5b0f6ba813600cdf90639f86179cd917a Mon Sep 17 00:00:00 2001 From: Kalervo Kujala Date: Sun, 20 Nov 2011 21:29:54 +0200 Subject: [PATCH 22/35] Add jasmine license file. --- external/jasmine/MIT.LICENSE | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 external/jasmine/MIT.LICENSE diff --git a/external/jasmine/MIT.LICENSE b/external/jasmine/MIT.LICENSE new file mode 100644 index 000000000..7c435baae --- /dev/null +++ b/external/jasmine/MIT.LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2008-2011 Pivotal Labs + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. From 4c15c985244aadad2ef794894d8052abf7ebf4af Mon Sep 17 00:00:00 2001 From: Artur Adib Date: Mon, 21 Nov 2011 09:17:44 -0500 Subject: [PATCH 23/35] test --- test/test_manifest.json | 258 ---------------------------------------- 1 file changed, 258 deletions(-) diff --git a/test/test_manifest.json b/test/test_manifest.json index 0bac41d34..8b93334e5 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -10,263 +10,5 @@ "md5": "9a192d8b1a7dc652a19835f6f08098bd", "rounds": 2, "type": "fbf" - }, - { "id": "html5-canvas-cheat-sheet-load", - "file": "pdfs/canvas.pdf", - "md5": "59510028561daf62e00bf9f6f066b033", - "rounds": 1, - "type": "load" - }, - { "id": "intelisa-load", - "file": "pdfs/intelisa.pdf", - "md5": "f5712097d29287a97f1278839814f682", - "link": true, - "rounds": 1, - "type": "load" - }, - { "id": "pdfspec-load", - "file": "pdfs/pdf.pdf", - "md5": "dbdb23c939d2be09b43126c3c56060c7", - "link": true, - "rounds": 1, - "type": "load" - }, - { "id": "shavian-load", - "file": "pdfs/shavian.pdf", - "md5": "4fabf0a03e82693007435020bc446f9b", - "link": true, - "rounds": 1, - "type": "load" - }, - { "id": "sizes", - "file": "pdfs/sizes.pdf", - "md5": "c101ba7b44aee36048e1ac7b98f302ea", - "rounds": 1, - "type": "eq" - }, - { "id": "openweb-cover", - "file": "pdfs/openweb_tm-PRINT.pdf", - "md5": "53f611dfc19ddfb50554c21c4af465c0", - "link": true, - "rounds": 1, - "type": "eq" - }, - { "id": "plusminus", - "file": "pdfs/Test-plusminus.pdf", - "md5": "1ec7ade5b95ac9aaba3a618af28d34c7", - "rounds": 1, - "type": "eq" - }, - { "id": "openoffice-pdf", - "file": "pdfs/DiwanProfile.pdf", - "md5": "55d0c6a1a6d26c9ec9dcecaa7a471e0e", - "link": true, - "rounds": 1, - "type": "load" - }, - { "id": "openofficecidtruetype-pdf", - "file": "pdfs/arial_unicode_en_cidfont.pdf", - "md5": "03591cdf20214fb0b2dd5e5c3dd32d8c", - "rounds": 1, - "type": "load" - }, - { "id": "openofficearabiccidtruetype-pdf", - "file": "pdfs/arial_unicode_ab_cidfont.pdf", - "md5": "35090fa7d29e7196ae3421812e554988", - "rounds": 1, - "type": "load" - }, - { "id": "arabiccidtruetype-pdf", - "file": "pdfs/ArabicCIDTrueType.pdf", - "md5": "d66dbd18bdb572d3ac8b88b32de2ece6", - "rounds": 1, - "type": "load" - }, - { "id": "complexttffont-pdf", - "file": "pdfs/complex_ttf_font.pdf", - "md5": "76de93f9116b01b693bf8583b3e76d91", - "rounds": 1, - "type": "load" - }, - { "id": "thuluthfont-pdf", - "file": "pdfs/ThuluthFeatures.pdf", - "md5": "b7e18bf7a3d6a9c82aefa12d721072fc", - "rounds": 1, - "type": "eq" - }, - { "id": "wnv_chinese-pdf", - "file": "pdfs/wnv_chinese.pdf", - "md5": "db682638e68391125e8982d3c984841e", - "link": true, - "rounds": 1, - "type": "eq" - }, - { "id": "i9-pdf", - "file": "pdfs/i9.pdf", - "md5": "ba7cd54fdff083bb389295bc0415f6c5", - "link": true, - "rounds": 1, - "type": "eq" - }, - { "id": "hmm-pdf", - "file": "pdfs/hmm.pdf", - "md5": "e08467e60101ee5f4a59716e86db6dc9", - "link": true, - "rounds": 1, - "type": "load" - }, - { "id": "rotation", - "file": "pdfs/rotation.pdf", - "md5": "4fb25ada00ce7528569d9791c14decf5", - "rounds": 1, - "type": "eq" - }, - { "id": "ecma262-pdf", - "file": "pdfs/ecma262.pdf", - "md5": "763ead98f535578842891e5574e0af0f", - "link": true, - "rounds": 1, - "type": "load" - }, - { "id": "jai-pdf", - "file": "pdfs/jai.pdf", - "md5": "1f5dd128c3757420a881a155f2f8ace3", - "link": true, - "rounds": 1, - "type": "load" - }, - { "id": "cable", - "file": "pdfs/cable.pdf", - "md5": "09a41b9a759d60c698228224ab85b46d", - "link": true, - "rounds": 1, - "type": "eq" - }, - { "id": "pdkids", - "file": "pdfs/pdkids.pdf", - "md5": "278982bf016dbe46d2066f9245d9b3e6", - "link": true, - "rounds": 1, - "type": "eq" - }, - { "id": "artofwar", - "file": "pdfs/artofwar.pdf", - "md5": "7bdd51c327b74f1f7abdd90eedb2f912", - "link": true, - "rounds": 1, - "type": "eq" - }, - { "id": "wdsg_fitc", - "file": "pdfs/wdsg_fitc.pdf", - "md5": "5bb1c2b83705d4cdfc43197ee74f07f9", - "link": true, - "rounds": 1, - "type": "eq" - }, - { "id": "unix01", - "file": "pdfs/unix01.pdf", - "md5": "2742999f0bf9b9c035dbb0736096e220", - "link": true, - "rounds": 1, - "type": "eq" - }, - { "id": "fit11-talk", - "file": "pdfs/fit11-talk.pdf", - "md5": "eb7b224107205db4fea9f7df0185f77d", - "link": true, - "rounds": 1, - "skipPages": [12,31], - "type": "eq" - }, - { "id": "fips197", - "file": "pdfs/fips197.pdf", - "md5": "374800cf78ce4b4abd02cd10a856b57f", - "link": true, - "rounds": 1, - "type": "eq" - }, - { "id": "txt2pdf", - "file": "pdfs/txt2pdf.pdf", - "md5": "02cefa0f5e8d96313bb05163b2f88c8c", - "link": true, - "rounds": 1, - "type": "load" - }, - { "id": "f1040", - "file": "pdfs/f1040.pdf", - "md5": "b59272ce19b4a0c5808c8861441b0741", - "link": true, - "rounds": 1, - "type": "load" - }, - { "id": "hudsonsurvey", - "file": "pdfs/hudsonsurvey.pdf", - "md5": "bf0e6576a7b6c2fe7485bce1b78e006f", - "link": true, - "rounds": 1, - "type": "load" - }, - { "id": "extgstate", - "file": "pdfs/extgstate.pdf", - "md5": "001bb4ec04463a01d93aad748361f049", - "link": false, - "rounds": 1, - "type": "eq" - }, - { "id": "usmanm-bad", - "file": "pdfs/usmanm-bad.pdf", - "md5": "38afb822433aaf07fc8f54807cd4f61a", - "link": true, - "rounds": 1, - "type": "eq" - }, - { "id": "vesta-bad", - "file": "pdfs/vesta.pdf", - "md5": "0afebc109b7c17b95619ea3fab5eafe6", - "link": true, - "rounds": 1, - "type": "load" - }, - { "id": "ibwa-bad", - "file": "pdfs/ibwa-bad.pdf", - "md5": "6ca059d32b74ac2688ae06f727fee755", - "link": true, - "rounds": 1, - "skipPages": [ 16 ], - "type": "load" - }, - { "id": "tcpdf_033", - "file": "pdfs/tcpdf_033.pdf", - "md5": "861294df58d185aae80919173f2732ff", - "link": true, - "rounds": 1, - "type": "eq" - }, - { "id": "pal-o47", - "file": "pdfs/pal-o47.pdf", - "md5": "81ae15e539e89f0f0b41169d923b611b", - "link": true, - "rounds": 1, - "type": "eq" - }, - { "id": "simpletype3font", - "file": "pdfs/simpletype3font.pdf", - "md5": "b374c7543920840c61999e9e86939f99", - "link": false, - "rounds": 1, - "type": "eq" - }, - { "id": "close-path-bug", - "file": "pdfs/close-path-bug.pdf", - "md5": "48dd17ef58393857d2d038d33699cac5", - "rounds": 1, - "type": "eq" - }, - { "id": "alphatrans", - "file": "pdfs/alphatrans.pdf", - "md5": "5ca2d3da0c5f20b3a5a14e895ad24b65", - "link": false, - "rounds": 1, - "type": "eq" } ] From f00e3ff154641cb23a9ac8f5a8c684bda5933d82 Mon Sep 17 00:00:00 2001 From: Artur Adib Date: Mon, 21 Nov 2011 09:26:19 -0500 Subject: [PATCH 24/35] blah --- README.md | 2 +- test/test_manifest.json | 258 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 259 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 97db68d36..443410d66 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # pdf.js - + ## Overview diff --git a/test/test_manifest.json b/test/test_manifest.json index 8b93334e5..0bac41d34 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -10,5 +10,263 @@ "md5": "9a192d8b1a7dc652a19835f6f08098bd", "rounds": 2, "type": "fbf" + }, + { "id": "html5-canvas-cheat-sheet-load", + "file": "pdfs/canvas.pdf", + "md5": "59510028561daf62e00bf9f6f066b033", + "rounds": 1, + "type": "load" + }, + { "id": "intelisa-load", + "file": "pdfs/intelisa.pdf", + "md5": "f5712097d29287a97f1278839814f682", + "link": true, + "rounds": 1, + "type": "load" + }, + { "id": "pdfspec-load", + "file": "pdfs/pdf.pdf", + "md5": "dbdb23c939d2be09b43126c3c56060c7", + "link": true, + "rounds": 1, + "type": "load" + }, + { "id": "shavian-load", + "file": "pdfs/shavian.pdf", + "md5": "4fabf0a03e82693007435020bc446f9b", + "link": true, + "rounds": 1, + "type": "load" + }, + { "id": "sizes", + "file": "pdfs/sizes.pdf", + "md5": "c101ba7b44aee36048e1ac7b98f302ea", + "rounds": 1, + "type": "eq" + }, + { "id": "openweb-cover", + "file": "pdfs/openweb_tm-PRINT.pdf", + "md5": "53f611dfc19ddfb50554c21c4af465c0", + "link": true, + "rounds": 1, + "type": "eq" + }, + { "id": "plusminus", + "file": "pdfs/Test-plusminus.pdf", + "md5": "1ec7ade5b95ac9aaba3a618af28d34c7", + "rounds": 1, + "type": "eq" + }, + { "id": "openoffice-pdf", + "file": "pdfs/DiwanProfile.pdf", + "md5": "55d0c6a1a6d26c9ec9dcecaa7a471e0e", + "link": true, + "rounds": 1, + "type": "load" + }, + { "id": "openofficecidtruetype-pdf", + "file": "pdfs/arial_unicode_en_cidfont.pdf", + "md5": "03591cdf20214fb0b2dd5e5c3dd32d8c", + "rounds": 1, + "type": "load" + }, + { "id": "openofficearabiccidtruetype-pdf", + "file": "pdfs/arial_unicode_ab_cidfont.pdf", + "md5": "35090fa7d29e7196ae3421812e554988", + "rounds": 1, + "type": "load" + }, + { "id": "arabiccidtruetype-pdf", + "file": "pdfs/ArabicCIDTrueType.pdf", + "md5": "d66dbd18bdb572d3ac8b88b32de2ece6", + "rounds": 1, + "type": "load" + }, + { "id": "complexttffont-pdf", + "file": "pdfs/complex_ttf_font.pdf", + "md5": "76de93f9116b01b693bf8583b3e76d91", + "rounds": 1, + "type": "load" + }, + { "id": "thuluthfont-pdf", + "file": "pdfs/ThuluthFeatures.pdf", + "md5": "b7e18bf7a3d6a9c82aefa12d721072fc", + "rounds": 1, + "type": "eq" + }, + { "id": "wnv_chinese-pdf", + "file": "pdfs/wnv_chinese.pdf", + "md5": "db682638e68391125e8982d3c984841e", + "link": true, + "rounds": 1, + "type": "eq" + }, + { "id": "i9-pdf", + "file": "pdfs/i9.pdf", + "md5": "ba7cd54fdff083bb389295bc0415f6c5", + "link": true, + "rounds": 1, + "type": "eq" + }, + { "id": "hmm-pdf", + "file": "pdfs/hmm.pdf", + "md5": "e08467e60101ee5f4a59716e86db6dc9", + "link": true, + "rounds": 1, + "type": "load" + }, + { "id": "rotation", + "file": "pdfs/rotation.pdf", + "md5": "4fb25ada00ce7528569d9791c14decf5", + "rounds": 1, + "type": "eq" + }, + { "id": "ecma262-pdf", + "file": "pdfs/ecma262.pdf", + "md5": "763ead98f535578842891e5574e0af0f", + "link": true, + "rounds": 1, + "type": "load" + }, + { "id": "jai-pdf", + "file": "pdfs/jai.pdf", + "md5": "1f5dd128c3757420a881a155f2f8ace3", + "link": true, + "rounds": 1, + "type": "load" + }, + { "id": "cable", + "file": "pdfs/cable.pdf", + "md5": "09a41b9a759d60c698228224ab85b46d", + "link": true, + "rounds": 1, + "type": "eq" + }, + { "id": "pdkids", + "file": "pdfs/pdkids.pdf", + "md5": "278982bf016dbe46d2066f9245d9b3e6", + "link": true, + "rounds": 1, + "type": "eq" + }, + { "id": "artofwar", + "file": "pdfs/artofwar.pdf", + "md5": "7bdd51c327b74f1f7abdd90eedb2f912", + "link": true, + "rounds": 1, + "type": "eq" + }, + { "id": "wdsg_fitc", + "file": "pdfs/wdsg_fitc.pdf", + "md5": "5bb1c2b83705d4cdfc43197ee74f07f9", + "link": true, + "rounds": 1, + "type": "eq" + }, + { "id": "unix01", + "file": "pdfs/unix01.pdf", + "md5": "2742999f0bf9b9c035dbb0736096e220", + "link": true, + "rounds": 1, + "type": "eq" + }, + { "id": "fit11-talk", + "file": "pdfs/fit11-talk.pdf", + "md5": "eb7b224107205db4fea9f7df0185f77d", + "link": true, + "rounds": 1, + "skipPages": [12,31], + "type": "eq" + }, + { "id": "fips197", + "file": "pdfs/fips197.pdf", + "md5": "374800cf78ce4b4abd02cd10a856b57f", + "link": true, + "rounds": 1, + "type": "eq" + }, + { "id": "txt2pdf", + "file": "pdfs/txt2pdf.pdf", + "md5": "02cefa0f5e8d96313bb05163b2f88c8c", + "link": true, + "rounds": 1, + "type": "load" + }, + { "id": "f1040", + "file": "pdfs/f1040.pdf", + "md5": "b59272ce19b4a0c5808c8861441b0741", + "link": true, + "rounds": 1, + "type": "load" + }, + { "id": "hudsonsurvey", + "file": "pdfs/hudsonsurvey.pdf", + "md5": "bf0e6576a7b6c2fe7485bce1b78e006f", + "link": true, + "rounds": 1, + "type": "load" + }, + { "id": "extgstate", + "file": "pdfs/extgstate.pdf", + "md5": "001bb4ec04463a01d93aad748361f049", + "link": false, + "rounds": 1, + "type": "eq" + }, + { "id": "usmanm-bad", + "file": "pdfs/usmanm-bad.pdf", + "md5": "38afb822433aaf07fc8f54807cd4f61a", + "link": true, + "rounds": 1, + "type": "eq" + }, + { "id": "vesta-bad", + "file": "pdfs/vesta.pdf", + "md5": "0afebc109b7c17b95619ea3fab5eafe6", + "link": true, + "rounds": 1, + "type": "load" + }, + { "id": "ibwa-bad", + "file": "pdfs/ibwa-bad.pdf", + "md5": "6ca059d32b74ac2688ae06f727fee755", + "link": true, + "rounds": 1, + "skipPages": [ 16 ], + "type": "load" + }, + { "id": "tcpdf_033", + "file": "pdfs/tcpdf_033.pdf", + "md5": "861294df58d185aae80919173f2732ff", + "link": true, + "rounds": 1, + "type": "eq" + }, + { "id": "pal-o47", + "file": "pdfs/pal-o47.pdf", + "md5": "81ae15e539e89f0f0b41169d923b611b", + "link": true, + "rounds": 1, + "type": "eq" + }, + { "id": "simpletype3font", + "file": "pdfs/simpletype3font.pdf", + "md5": "b374c7543920840c61999e9e86939f99", + "link": false, + "rounds": 1, + "type": "eq" + }, + { "id": "close-path-bug", + "file": "pdfs/close-path-bug.pdf", + "md5": "48dd17ef58393857d2d038d33699cac5", + "rounds": 1, + "type": "eq" + }, + { "id": "alphatrans", + "file": "pdfs/alphatrans.pdf", + "md5": "5ca2d3da0c5f20b3a5a14e895ad24b65", + "link": false, + "rounds": 1, + "type": "eq" } ] From 7a5556ca91ff2a5ebdb4038564d5f1cd96436f56 Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Mon, 21 Nov 2011 09:35:36 -0800 Subject: [PATCH 25/35] Add license. --- external/jpgjs/LICENSE | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 external/jpgjs/LICENSE diff --git a/external/jpgjs/LICENSE b/external/jpgjs/LICENSE new file mode 100644 index 000000000..d1c08028c --- /dev/null +++ b/external/jpgjs/LICENSE @@ -0,0 +1,21 @@ + +Copyright (C) 2011 by notmasteryet + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. From 1e642231134635a46a8b57ba91253cb62d1cc635 Mon Sep 17 00:00:00 2001 From: Artur Adib Date: Mon, 21 Nov 2011 13:44:30 -0500 Subject: [PATCH 26/35] Removed fbf from test_manifest --- README.md | 2 +- test/test_manifest.json | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/README.md b/README.md index 443410d66..97db68d36 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # pdf.js - + ## Overview diff --git a/test/test_manifest.json b/test/test_manifest.json index 0bac41d34..ab5821218 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -5,12 +5,6 @@ "rounds": 1, "type": "eq" }, - { "id": "tracemonkey-fbf", - "file": "pdfs/tracemonkey.pdf", - "md5": "9a192d8b1a7dc652a19835f6f08098bd", - "rounds": 2, - "type": "fbf" - }, { "id": "html5-canvas-cheat-sheet-load", "file": "pdfs/canvas.pdf", "md5": "59510028561daf62e00bf9f6f066b033", From 408dd5d403502e1977c1f16f329abb42a284fce3 Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Mon, 21 Nov 2011 17:20:23 -0800 Subject: [PATCH 27/35] Replace devicen file with an example we can include. --- test/pdfs/devicen.pdf | Bin 9550 -> 15751 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/test/pdfs/devicen.pdf b/test/pdfs/devicen.pdf index 53ef9e5d5174b4d333c81ef8f79d22d17a5fc001..d20c884eafe16dd18b095c3546845c25fdc3f529 100644 GIT binary patch delta 4102 zcmd5y1=%=9e29bugb;!h!XcKNL`Xq^H~@;sCFGC;AS80&C4nOTuIlNT zT}J^V4zyO&RsXyG|EvG+{l%xA`22Gx4r@A6kl%Z3u3pEjfYHeHy*7>5e7mM9imsTX zOf;ez#@2%5`@#IxgT>N=3wYJHb4db^N{C4mrCL%dM-=7A0zMh|c5E?Fc=NtHPd_^U z8P~J@^Wmcl3wYCwIGeZGj%zWGERMp3Mq`cv8zM2L5glh z$D8S5l||I15p6WCog8 zuo|pWGA<$^5^XOPn<)c5ym}I~{D2{2Q7u`DLQq*-BwAJ1st+PnAqrNiST##Tt7&FU zEu-uyHVUw+2ewmNZ+FqSApF4=!uc8cL__i?B5f90fjdf*jZzib^W$i#SWIo?j|_Pv zOgvS*{puI*J8*Yy2Bhl1s!ej-fE<6!uH!wl%v>F(K4yH1SW^r8=>IMjNG2TTsp3bI zJ#hfJCa}X&JTy|=loUu&c@wfk)Ea=Nj`s;oPg+kofF;-2&D04g!JkStiCH5`O(3eK zDAIfVg%Z;&v7V)rbfVd%s_l>xvrW@Bbi1c9 zsL@Tz?};0kDCjfL;&9&M8igz8>Q^q#zwiI}{gI?lKRO;!Ynw#~`u$SzJT&UL0xZJ_ z#?Npv1kC9WXj*~a1#BZy0pfbz_fa}1*6~T&XUA!e@ervwAw-&3i$T$XA;JB=m@tVL zF{z=+(DS_ra1bI=6&kI;A1(Xi(+cd$L`7y*Jp(Rnm)=A@NdwT#Jgu|Pj{}Q^=^*vF zA8f!WS-RO!5Fx4UMgw<}arDqe1Z@&H52YM&hz2}(-B0OAk<%?b)^sL{qh}GfoAdY> z<2zt-(}ABpTEOjfu-EY{Xro>qmM7a?K=_1!RFN6*(M{mEz!&;zdd79UMcX~3W)Y?` zF5`B1`pk^?G&Py`uFE49;4X()+YB5soK0y`DK{Ef6${5MJ$ZTQ;48{)kN@o(uYCC1 z+piwI)W6mF@uB2_mzS?R{l?j1>F5K`U3}xs>qlOF=dFc9TRYqD-}-ZKZQ;V@@Y$EX zwD|bzw|$lT<;DA7RzC3kqo2F?+3)=JN589o>-UG$1Jz@H|K*JjT0i~a3l~=I`R?*- zpJdyAee&Sy#`Yf$7|+~(=r`Bze!`-MzgGC{9j{++|K!fEy!O2tU;e@^&%E%DTOX@* zmp`5Gd!GNn*YCJ=?VA@n-CsTZ$A2Dtp1<>vpTpT0+WYQ*22B zq>QsLbTOBrKEt8pTo5lr$_nnpfe#BETXB$9VHY-Kob&>Jn|VDNz_x;O%a%Xdl`;D; zKp7m_^&qvTilK7>#JJ~2coBYJ=;DC&-4Jdev+&m5wKS`)Z>QxK<}7G*oKp<7&($HIAq}mVi-;$tx;J{7ktvLZ)0*Gr0JmF@3_@+Qvvx8d`{~f8_k=mV!HYj&;traP= zB4t))WL9#SPBg$Lq`NgCp5QepzcwSkmPzDl!}LpU#CP$UEbuAm>XcMHHKTee*Lg%T zHwS*;VYmfD0BVnim`YtL*$JIdhL1G3SeWKEs7M557%rqN1k@Go7-mwN%_xIFhDuk; zxKhTQk#Tbw28r;%T>}1mDep^pe@5Py0l^f>Oxx~`n`+23#8NSqit&tMoNMe#CR|lW z!|sH9Mh>-(m&g${ZGYo{IdcUVgz%F;sB9RTfq zY@H}-OI_9teYw>tcXYE!I^~L1B~5+VG?iAnaR&bRW;@V7G{|%TK1qW}Ci);TG!s&K L_;6>fJ6HG@(Uc+1 delta 7 OcmZp#KIgT;PZa Date: Mon, 21 Nov 2011 17:23:54 -0800 Subject: [PATCH 28/35] Address Yury's comment. --- src/function.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/function.js b/src/function.js index 2f9b050dd..ef24736c1 100644 --- a/src/function.js +++ b/src/function.js @@ -20,6 +20,7 @@ var PDFFunction = (function pdfFunction() { var array = []; var codeSize = 0; var codeBuf = 0; + // 32 is a valid bps so shifting won't work var sampleMul = 1.0 / (Math.pow(2.0, bps) - 1); var strBytes = str.getBytes((length * bps + 7) / 8); From a191511c18048b71d3e785fa4ba30d53d47d25d4 Mon Sep 17 00:00:00 2001 From: notmasteryet Date: Mon, 21 Nov 2011 23:00:45 -0600 Subject: [PATCH 29/35] test fbf --- test/test_manifest.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/test_manifest.json b/test/test_manifest.json index ab5821218..0bac41d34 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -5,6 +5,12 @@ "rounds": 1, "type": "eq" }, + { "id": "tracemonkey-fbf", + "file": "pdfs/tracemonkey.pdf", + "md5": "9a192d8b1a7dc652a19835f6f08098bd", + "rounds": 2, + "type": "fbf" + }, { "id": "html5-canvas-cheat-sheet-load", "file": "pdfs/canvas.pdf", "md5": "59510028561daf62e00bf9f6f066b033", From f90cdcf0cdb9bf3fbfa29d01fb77b52d8640db3c Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Tue, 22 Nov 2011 09:06:53 -0800 Subject: [PATCH 30/35] Update md5. --- test/test_manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_manifest.json b/test/test_manifest.json index 4d2c859b5..23d28bf0c 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -271,7 +271,7 @@ }, { "id": "devicen", "file": "pdfs/devicen.pdf", - "md5": "b9dbf00ec6bf02fcbefbb40332713aad", + "md5": "aac6a91725435d1376c6ff492dc5cb75", "link": false, "rounds": 1, "type": "eq" From b64b7d55b1e805fbcf0d41f843a06f162a801537 Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Tue, 22 Nov 2011 09:32:20 -0800 Subject: [PATCH 31/35] Use the updated jpgjs with faster transform. --- external/jpgjs/LICENSE | 3 + external/jpgjs/jpg.js | 323 ++++++++++++++++++++++++++++++++--------- src/stream.js | 3 +- 3 files changed, 261 insertions(+), 68 deletions(-) diff --git a/external/jpgjs/LICENSE b/external/jpgjs/LICENSE index d1c08028c..f28619c6a 100644 --- a/external/jpgjs/LICENSE +++ b/external/jpgjs/LICENSE @@ -1,6 +1,9 @@ Copyright (C) 2011 by notmasteryet +Contributors: Yury Delendik + Brendan Dahl + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including diff --git a/external/jpgjs/jpg.js b/external/jpgjs/jpg.js index 6ae443c41..dc8956ac0 100644 --- a/external/jpgjs/jpg.js +++ b/external/jpgjs/jpg.js @@ -11,44 +11,36 @@ var JpegImage = (function jpegImage() { "use strict"; + var dctZigZag = new Int32Array([ + 0, + 1, 8, + 16, 9, 2, + 3, 10, 17, 24, + 32, 25, 18, 11, 4, + 5, 12, 19, 26, 33, 40, + 48, 41, 34, 27, 20, 13, 6, + 7, 14, 21, 28, 35, 42, 49, 56, + 57, 50, 43, 36, 29, 22, 15, + 23, 30, 37, 44, 51, 58, + 59, 52, 45, 38, 31, + 39, 46, 53, 60, + 61, 54, 47, + 55, 62, + 63 + ]); - function constructor(colorTransform) { - this.colorTransform = typeof colorTransform !== 'undefined' ? colorTransform : -1; + var dctCos1 = 4017 // cos(pi/16) + var dctSin1 = 799 // sin(pi/16) + var dctCos3 = 3406 // cos(3*pi/16) + var dctSin3 = 2276 // sin(3*pi/16) + var dctCos6 = 1567 // cos(6*pi/16) + var dctSin6 = 3784 // sin(6*pi/16) + var dctSqrt2 = 5793 // sqrt(2) + var dctSqrt1d2 = 2896 // sqrt(2) / 2 + + function constructor() { } - var iDCTTables = (function initDCTTables() { - var cosTables = [], i, j; - for (i = 0; i < 8; i++) { - cosTables.push(new Float32Array(8)); - for (j = 0; j < 8; j++) - cosTables[i][j] = Math.cos((2 * i + 1) * j * Math.PI / 16) * - (j > 0 ? 1 : 1/Math.sqrt(2)); - } - - var zzTransform = new Int32Array([ - 0, 1, 5, 6, 14, 15, 27, 28, 2, 4, 7, 13, 16, 26, 29, 42, 3, 8, 12, - 17, 25, 30, 41, 43, 9, 11, 18, 24, 31, 40, 44, 53, 10, 19, 23, 32, - 39, 45, 52, 54, 20, 22, 33, 38, 46, 51, 55, 60, 21, 34, 37, 47, - 50, 56, 59, 61, 35, 36, 48, 49, 57, 58, 62, 63]); - - var x, y, u, v; - var tables = []; - for (y = 0; y < 8; y++) { - var cosTable_y = cosTables[y]; - for (x = 0; x < 8; x++) { - var cosTable_x = cosTables[x]; - var table = new Float32Array(64); - i = 0; - for (v = 0; v < 8; v++) { - for (u = 0; u < 8; u++) - table[zzTransform[i++]] = cosTable_x[u] * cosTable_y[v]; - } - tables.push(table); - } - } - return tables; - })(); - function buildHuffmanTable(codeLengths, values) { var k = 0, code = [], i, j, length = 16; while (length > 0 && !codeLengths[length - 1]) @@ -151,7 +143,8 @@ var JpegImage = (function jpegImage() { continue; } k += r; - zz[k] = receiveAndExtend(s); + var z = dctZigZag[k]; + zz[z] = receiveAndExtend(s); k++; } } @@ -182,7 +175,8 @@ var JpegImage = (function jpegImage() { continue; } k += r; - zz[k] = receiveAndExtend(s) * (1 << successive); + var z = dctZigZag[k]; + zz[z] = receiveAndExtend(s) * (1 << successive); k++; } } @@ -190,6 +184,7 @@ var JpegImage = (function jpegImage() { function decodeACSuccessive(component, zz) { var k = spectralStart, e = spectralEnd, r = 0; while (k <= e) { + var z = dctZigZag[k]; switch (successiveACState) { case 0: // initial state var rs = decodeHuffman(component.huffmanTableAC); @@ -211,8 +206,8 @@ var JpegImage = (function jpegImage() { continue; case 1: // skipping r zero items case 2: - if (zz[k]) - zz[k] += (readBit() << successive); + if (zz[z]) + zz[z] += (readBit() << successive); else { r--; if (r === 0) @@ -220,16 +215,16 @@ var JpegImage = (function jpegImage() { } break; case 3: // set value for a zero item - if (zz[k]) - zz[k] += (readBit() << successive); + if (zz[z]) + zz[z] += (readBit() << successive); else { - zz[k] = successiveACNextValue << successive; + zz[z] = successiveACNextValue << successive; successiveACState = 0; } break; case 4: // eob - if (zz[k]) - zz[k] += (readBit() << successive); + if (zz[z]) + zz[z] += (readBit() << successive); break; } k++; @@ -325,27 +320,166 @@ var JpegImage = (function jpegImage() { var blocksPerLine = component.blocksPerLine; var blocksPerColumn = component.blocksPerColumn; var samplesPerLine = blocksPerLine << 3; + var R = new Int32Array(64), r = new Uint8Array(64); - function quantizeAndInverse(zz) { + // A port of poppler's IDCT method which in turn is taken from: + // Christoph Loeffler, Adriaan Ligtenberg, George S. Moschytz, + // "Practical Fast 1-D DCT Algorithms with 11 Multiplications", + // IEEE Intl. Conf. on Acoustics, Speech & Signal Processing, 1989, + // 988-991. + function quantizeAndInverse(zz, dataOut, dataIn) { var qt = component.quantizationTable; - var precisionShift = frame.precision - 8; + var v0, v1, v2, v3, v4, v5, v6, v7, t; + var p = dataIn; + var i; - var R = new Int32Array(64); + // dequant for (i = 0; i < 64; i++) - R[i] = zz[i] * qt[i]; + p[i] = zz[i] * qt[i]; - var r = new Uint8Array(64), i, j; - for (i = 0; i < 64; i++) { - var sum = 0; - var table = iDCTTables[i]; - for (j = 0; j < 64; j++) - sum += table[j] * R[j]; - // TODO loosing precision? - var sample = 128 + ((sum / 4) >> precisionShift); - // clamping - r[i] = sample < 0 ? 0 : sample > 0xFF ? 0xFF : sample; + // inverse DCT on rows + for (i = 0; i < 8; ++i) { + var row = 8 * i; + + // check for all-zero AC coefficients + if (p[1 + row] == 0 && p[2 + row] == 0 && p[3 + row] == 0 && + p[4 + row] == 0 && p[5 + row] == 0 && p[6 + row] == 0 && + p[7 + row] == 0) { + t = (dctSqrt2 * p[0 + row] + 512) >> 10; + p[0 + row] = t; + p[1 + row] = t; + p[2 + row] = t; + p[3 + row] = t; + p[4 + row] = t; + p[5 + row] = t; + p[6 + row] = t; + p[7 + row] = t; + continue; + } + + // stage 4 + v0 = (dctSqrt2 * p[0 + row] + 128) >> 8; + v1 = (dctSqrt2 * p[4 + row] + 128) >> 8; + v2 = p[2 + row]; + v3 = p[6 + row]; + v4 = (dctSqrt1d2 * (p[1 + row] - p[7 + row]) + 128) >> 8; + v7 = (dctSqrt1d2 * (p[1 + row] + p[7 + row]) + 128) >> 8; + v5 = p[3 + row] << 4; + v6 = p[5 + row] << 4; + + // stage 3 + t = (v0 - v1+ 1) >> 1; + v0 = (v0 + v1 + 1) >> 1; + v1 = t; + t = (v2 * dctSin6 + v3 * dctCos6 + 128) >> 8; + v2 = (v2 * dctCos6 - v3 * dctSin6 + 128) >> 8; + v3 = t; + t = (v4 - v6 + 1) >> 1; + v4 = (v4 + v6 + 1) >> 1; + v6 = t; + t = (v7 + v5 + 1) >> 1; + v5 = (v7 - v5 + 1) >> 1; + v7 = t; + + // stage 2 + t = (v0 - v3 + 1) >> 1; + v0 = (v0 + v3 + 1) >> 1; + v3 = t; + t = (v1 - v2 + 1) >> 1; + v1 = (v1 + v2 + 1) >> 1; + v2 = t; + t = (v4 * dctSin3 + v7 * dctCos3 + 2048) >> 12; + v4 = (v4 * dctCos3 - v7 * dctSin3 + 2048) >> 12; + v7 = t; + t = (v5 * dctSin1 + v6 * dctCos1 + 2048) >> 12; + v5 = (v5 * dctCos1 - v6 * dctSin1 + 2048) >> 12; + v6 = t; + + // stage 1 + p[0 + row] = v0 + v7; + p[7 + row] = v0 - v7; + p[1 + row] = v1 + v6; + p[6 + row] = v1 - v6; + p[2 + row] = v2 + v5; + p[5 + row] = v2 - v5; + p[3 + row] = v3 + v4; + p[4 + row] = v3 - v4; + } + + // inverse DCT on columns + for (i = 0; i < 8; ++i) { + var col = i; + + // check for all-zero AC coefficients + if (p[1*8 + col] == 0 && p[2*8 + col] == 0 && p[3*8 + col] == 0 && + p[4*8 + col] == 0 && p[5*8 + col] == 0 && p[6*8 + col] == 0 && + p[7*8 + col] == 0) { + t = (dctSqrt2 * dataIn[i+0] + 8192) >> 14; + p[0*8 + col] = t; + p[1*8 + col] = t; + p[2*8 + col] = t; + p[3*8 + col] = t; + p[4*8 + col] = t; + p[5*8 + col] = t; + p[6*8 + col] = t; + p[7*8 + col] = t; + continue; + } + + // stage 4 + v0 = (dctSqrt2 * p[0*8 + col] + 2048) >> 12; + v1 = (dctSqrt2 * p[4*8 + col] + 2048) >> 12; + v2 = p[2*8 + col]; + v3 = p[6*8 + col]; + v4 = (dctSqrt1d2 * (p[1*8 + col] - p[7*8 + col]) + 2048) >> 12; + v7 = (dctSqrt1d2 * (p[1*8 + col] + p[7*8 + col]) + 2048) >> 12; + v5 = p[3*8 + col]; + v6 = p[5*8 + col]; + + // stage 3 + t = (v0 - v1 + 1) >> 1; + v0 = (v0 + v1 + 1) >> 1; + v1 = t; + t = (v2 * dctSin6 + v3 * dctCos6 + 2048) >> 12; + v2 = (v2 * dctCos6 - v3 * dctSin6 + 2048) >> 12; + v3 = t; + t = (v4 - v6 + 1) >> 1; + v4 = (v4 + v6 + 1) >> 1; + v6 = t; + t = (v7 + v5 + 1) >> 1; + v5 = (v7 - v5 + 1) >> 1; + v7 = t; + + // stage 2 + t = (v0 - v3 + 1) >> 1; + v0 = (v0 + v3 + 1) >> 1; + v3 = t; + t = (v1 - v2 + 1) >> 1; + v1 = (v1 + v2 + 1) >> 1; + v2 = t; + t = (v4 * dctSin3 + v7 * dctCos3 + 2048) >> 12; + v4 = (v4 * dctCos3 - v7 * dctSin3 + 2048) >> 12; + v7 = t; + t = (v5 * dctSin1 + v6 * dctCos1 + 2048) >> 12; + v5 = (v5 * dctCos1 - v6 * dctSin1 + 2048) >> 12; + v6 = t; + + // stage 1 + p[0*8 + col] = v0 + v7; + p[7*8 + col] = v0 - v7; + p[1*8 + col] = v1 + v6; + p[6*8 + col] = v1 - v6; + p[2*8 + col] = v2 + v5; + p[5*8 + col] = v2 - v5; + p[3*8 + col] = v3 + v4; + p[4*8 + col] = v3 - v4; + } + + // convert to 8-bit integers + for (i = 0; i < 64; ++i) { + var sample = 128 + ((p[i] + 8) >> 4); + dataOut[i] = sample < 0 ? 0 : sample > 0xFF ? 0xFF : sample; } - return r; } var i, j; @@ -354,7 +488,7 @@ var JpegImage = (function jpegImage() { for (i = 0; i < 8; i++) lines.push(new Uint8Array(samplesPerLine)); for (var blockCol = 0; blockCol < blocksPerLine; blockCol++) { - var r = quantizeAndInverse(component.blocks[blockRow][blockCol]); + quantizeAndInverse(component.blocks[blockRow][blockCol], r, R); var offset = 0, sample = blockCol << 3; for (j = 0; j < 8; j++) { @@ -498,8 +632,10 @@ var JpegImage = (function jpegImage() { var quantizationTableSpec = data[offset++]; var tableData = new Int32Array(64); if ((quantizationTableSpec >> 4) === 0) { // 8 bit values - for (j = 0; j < 64; j++) - tableData[j] = data[offset++]; + for (j = 0; j < 64; j++) { + var z = dctZigZag[j]; + tableData[z] = data[offset++]; + } } else if ((quantizationTableSpec >> 4) === 1) { //16 bit tableData[j] = readUint16(); } else @@ -634,8 +770,8 @@ var JpegImage = (function jpegImage() { // The adobe transform marker overrides any previous setting if (this.adobe && this.adobe.transformCode) colorTransform = true; - else if (typeof this.colorTransform != -1) - colorTransform = this.colorTransform == true; + else if (typeof this.colorTransform !== 'undefined') + colorTransform = !!this.colorTransform; component1 = this.components[0]; component2 = this.components[1]; @@ -673,8 +809,8 @@ var JpegImage = (function jpegImage() { // The adobe transform marker overrides any previous setting if (this.adobe && this.adobe.transformCode) colorTransform = true; - else if (typeof this.colorTransform != -1) - colorTransform = this.colorTransform == true; + else if (typeof this.colorTransform !== 'undefined') + colorTransform = !!this.colorTransform; component1 = this.components[0]; component2 = this.components[1]; @@ -714,7 +850,60 @@ var JpegImage = (function jpegImage() { return data; }, copyToImageData: function copyToImageData(imageData) { - this.getData(imageData.data, imageData.width, imageData.height); + var width = imageData.width, height = imageData.height; + var imageDataArray = imageData.data; + var data = this.getData(width, height); + var i = 0, j = 0, x, y; + var Y, K, C, M, R, G, B; + switch (this.components.length) { + case 1: + for (y = 0; y < height; y++) { + for (x = 0; x < width; x++) { + Y = data[i++]; + + imageDataArray[j++] = Y; + imageDataArray[j++] = Y; + imageDataArray[j++] = Y; + imageDataArray[j++] = 255; + } + } + break; + case 3: + for (y = 0; y < height; y++) { + for (x = 0; x < width; x++) { + R = data[i++]; + G = data[i++]; + B = data[i++]; + + imageDataArray[j++] = R; + imageDataArray[j++] = G; + imageDataArray[j++] = B; + imageDataArray[j++] = 255; + } + } + break; + case 4: + for (y = 0; y < height; y++) { + for (x = 0; x < width; x++) { + C = data[i++]; + M = data[i++]; + Y = data[i++]; + K = data[i++]; + + R = 255 - clampTo8bit(C * (1 - K / 255) + K); + G = 255 - clampTo8bit(M * (1 - K / 255) + K); + B = 255 - clampTo8bit(Y * (1 - K / 255) + K); + + imageDataArray[j++] = R; + imageDataArray[j++] = G; + imageDataArray[j++] = B; + imageDataArray[j++] = 255; + } + } + break; + default: + throw 'Unsupported color mode'; + } } }; diff --git a/src/stream.js b/src/stream.js index c7b5c8121..559fb2ca2 100644 --- a/src/stream.js +++ b/src/stream.js @@ -830,7 +830,8 @@ var JpegStream = (function jpegStream() { constructor.prototype.ensureBuffer = function jpegStreamEnsureBuffer(req) { if (this.bufferLength) return; - var jpegImage = new JpegImage(this.colorTransform); + var jpegImage = new JpegImage(); + jpegImage.colorTransform = this.colorTransform; jpegImage.parse(this.bytes); var width = jpegImage.width; var height = jpegImage.height; From 868591554285c4577372b4a1478cf987aed76d15 Mon Sep 17 00:00:00 2001 From: notmasteryet Date: Thu, 24 Nov 2011 12:53:11 -0600 Subject: [PATCH 32/35] Re-using the pages as thumbnails --- web/viewer.js | 58 ++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 50 insertions(+), 8 deletions(-) diff --git a/web/viewer.js b/web/viewer.js index bfb3a4303..c8b830431 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -193,6 +193,13 @@ var PDFView = { }, load: function pdfViewLoad(data, scale) { + function bindOnAfterDraw(pageView, thumbnailView) { + // when page is painted, using the image as thumbnail base + pageView.onAfterDraw = function pdfViewLoadOnAfterDraw() { + thumbnailView.setImage(pageView.canvas); + }; + } + var loadingIndicator = document.getElementById('loading'); loadingIndicator.setAttribute('hidden', 'true'); @@ -219,10 +226,14 @@ var PDFView = { var thumbnails = this.thumbnails = []; for (var i = 1; i <= pagesCount; i++) { var page = pdf.getPage(i); - pages.push(new PageView(container, page, i, page.width, page.height, - page.stats, this.navigateTo.bind(this))); - thumbnails.push(new ThumbnailView(sidebar, page, i, - page.width / page.height)); + var pageView = new PageView(container, page, i, page.width, page.height, + page.stats, this.navigateTo.bind(this)); + var thumbnailView = new ThumbnailView(sidebar, page, i, + page.width / page.height); + bindOnAfterDraw(pageView, thumbnailView); + + pages.push(pageView); + thumbnails.push(thumbnailView); var pageRef = page.ref; pagesRefMap[pageRef.num + ' ' + pageRef.gen + ' R'] = i; } @@ -360,6 +371,8 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight, while (div.hasChildNodes()) div.removeChild(div.lastChild); div.removeAttribute('data-loaded'); + + delete this.canvas; }; function setupLinks(content, scale) { @@ -474,6 +487,7 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight, canvas.id = 'page' + this.id; canvas.mozOpaque = true; div.appendChild(canvas); + this.canvas = canvas; var scale = this.scale; canvas.width = pageWidth * scale; @@ -487,7 +501,11 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight, ctx.translate(-this.x * scale, -this.y * scale); stats.begin = Date.now(); - this.content.startRendering(ctx, this.updateStats); + this.content.startRendering(ctx, (function pageViewDrawCallback() { + this.updateStats(); + if (this.onAfterDraw) + this.onAfterDraw(); + }).bind(this)); setupLinks(this.content, this.scale); div.setAttribute('data-loaded', true); @@ -518,10 +536,9 @@ var ThumbnailView = function thumbnailView(container, page, id, pageRatio) { anchor.appendChild(div); container.appendChild(anchor); - this.draw = function thumbnailViewDraw() { - if (div.hasChildNodes()) - return; + this.hasImage = false; + function getPageDrawContext() { var canvas = document.createElement('canvas'); canvas.id = 'thumbnail' + id; canvas.mozOpaque = true; @@ -549,7 +566,28 @@ var ThumbnailView = function thumbnailView(container, page, id, pageRatio) { div.style.height = (view.height * scaleY) + 'px'; div.style.lineHeight = (view.height * scaleY) + 'px'; + return ctx; + } + + this.draw = function thumbnailViewDraw() { + if (this.hasImage) + return; + + var ctx = getPageDrawContext(); page.startRendering(ctx, function thumbnailViewDrawStartRendering() {}); + + this.hasImage = true; + }; + + this.setImage = function thumbnailViewSetImage(img) { + if (this.hasImage) + return; + + var ctx = getPageDrawContext(); + ctx.drawImage(img, 0, 0, img.width, img.height, + 0, 0, ctx.canvas.width, ctx.canvas.height); + + this.hasImage = true; }; }; @@ -687,6 +725,10 @@ window.addEventListener('transitionend', function webViewerTransitionend(evt) { var container = document.getElementById('sidebarView'); container._interval = window.setInterval(function interval() { + // skipping the thumbnails with set images + while (pageIndex < pagesCount && PDFView.thumbnails[pageIndex].hasImage) + pageIndex++; + if (pageIndex >= pagesCount) { window.clearInterval(container._interval); return; From a30f0ff848beb8d205c9e4c061973f5955181f79 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Tue, 22 Nov 2011 21:00:04 +0100 Subject: [PATCH 33/35] Implement ctx.mozCurrentTransform and ctx.mozCurrentTransformInverse shim --- src/canvas.js | 109 +++++++++++++++++++++++++++++++++++++++++++++++++ src/pattern.js | 2 + 2 files changed, 111 insertions(+) diff --git a/src/canvas.js b/src/canvas.js index 9b3ed0ba9..d9c60f416 100644 --- a/src/canvas.js +++ b/src/canvas.js @@ -59,6 +59,111 @@ function ScratchCanvas(width, height) { return canvas; } +function addCtxCurrentTransform(ctx) { + // If the context doesn't expose a `mozCurrentTransform`, add a JS based on. + if (!ctx.mozCurrentTransform) { + // Store the original context + ctx._originalSave = ctx.save; + ctx._originalRestore = ctx.restore; + ctx._originalRotate = ctx.rotate; + ctx._originalScale = ctx.scale; + ctx._originalTranslate = ctx.translate; + ctx._originalTransform = ctx.transform; + + ctx._transformMatrix = [1, 0, 0, 1, 0, 0]; + ctx._transformStack = []; + + ctx.__defineGetter__('mozCurrentTransform', function getCurrentTransform() { + return this._transformMatrix; + }); + + ctx.__defineGetter__('mozCurrentTransformInverse', + function getCurrentTransformInverse() { + // Calculation done using WolframAlpha: + // http://www.wolframalpha.com/input/? + // i=Inverse+{{a%2C+c%2C+e}%2C+{b%2C+d%2C+f}%2C+{0%2C+0%2C+1}} + + var m = this._transformMatrix; + var a = m[0], b = m[1], c = m[2], d = m[3], e = m[4], f = m[5]; + + return [ + d / (a * d - b * c), + b / (b * c - a * d), + c / (b * c - a * d), + a / (a * d - b * c), + (d * e - c * f) / (b * c - a * d), + (b * e - a * f) / (a * d - b * c) + ]; + } + ); + + ctx.save = function ctxSave() { + var old = this._transformMatrix; + this._transformStack.push(old); + this._transformMatrix = old.slice(0, 6); + + this._originalSave(); + }; + + ctx.restore = function ctxRestore() { + var prev = this._transformStack.pop(); + if (prev) { + this._transformMatrix = prev; + this._originalRestore(); + } + } + + ctx.translate = function ctxTranslate(x, y) { + var m = this._transformMatrix; + m[4] = m[0] * x + m[2] * y + m[4]; + m[5] = m[1] * x + m[3] * y + m[5]; + + this._originalTranslate(x, y); + } + + ctx.scale = function ctxScale(x, y) { + var m = this._transformMatrix; + m[0] = m[0] * x; + m[1] = m[1] * x; + m[2] = m[2] * y; + m[3] = m[3] * y; + + this._originalScale(x, y); + } + + ctx.transform = function ctxTransform(a, b, c, d, e, f) { + var m = this._transformMatrix; + this._transformMatrix = [ + m[0] * a + m[2] * b, + m[1] * a + m[3] * b, + m[0] * c + m[2] * d, + m[1] * c + m[3] * d, + m[0] * e + m[2] * f + m[4], + m[1] * e + m[3] * f + m[5] + ]; + + ctx._originalTransform(a, b, c, d, e, f); + } + + ctx.rotate = function ctxRotate(angle) { + var cosValue = Math.cos(angle); + var sinValue = Math.sin(angle); + + var m = this._transformMatrix; + this._transformMatrix = [ + m[0] * cosValue + m[2] * sinValue, + m[1] * cosValue + m[3] * sinValue, + m[0] * (-sinValue) + m[2] * cosValue, + m[1] * (-sinValue) + m[3] * cosValue, + m[4], + m[5] + ]; + + this._originalRotate(angle); + } + } +} + var CanvasGraphics = (function canvasGraphics() { // Defines the time the executeIRQueue is going to be executing // before it stops and shedules a continue of execution. @@ -77,6 +182,10 @@ var CanvasGraphics = (function canvasGraphics() { this.xobjs = null; this.ScratchCanvas = ScratchCanvas; this.objs = objs; + + if (canvasCtx) { + addCtxCurrentTransform(canvasCtx); + } } var LINE_CAP_STYLES = ['butt', 'round', 'square']; diff --git a/src/pattern.js b/src/pattern.js index 72d13d896..c565e8b7f 100644 --- a/src/pattern.js +++ b/src/pattern.js @@ -217,6 +217,8 @@ var TilingPattern = (function tilingPattern() { // set the new canvas element context as the graphics context var tmpCtx = tmpCanvas.getContext('2d'); + addCtxCurrentTransform(tmpCtx); + var graphics = new CanvasGraphics(tmpCtx, objs); switch (paintType) { From 36e618c373f718320faa0540536263a7c37068f9 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Sun, 27 Nov 2011 09:22:08 +0100 Subject: [PATCH 34/35] Address Yury's review comments + remove call to addCtxCurrentTransform in pattern.js as the contet is added during the CanvasGraphics constructor already --- src/canvas.js | 41 +++++++++++++++++++++++------------------ src/pattern.js | 2 -- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/src/canvas.js b/src/canvas.js index d9c60f416..035651b0f 100644 --- a/src/canvas.js +++ b/src/canvas.js @@ -59,7 +59,7 @@ function ScratchCanvas(width, height) { return canvas; } -function addCtxCurrentTransform(ctx) { +function addContextCurrentTransform(ctx) { // If the context doesn't expose a `mozCurrentTransform`, add a JS based on. if (!ctx.mozCurrentTransform) { // Store the original context @@ -73,12 +73,14 @@ function addCtxCurrentTransform(ctx) { ctx._transformMatrix = [1, 0, 0, 1, 0, 0]; ctx._transformStack = []; - ctx.__defineGetter__('mozCurrentTransform', function getCurrentTransform() { - return this._transformMatrix; + Object.defineProperty(ctx, 'mozCurrentTransform', { + get: function getCurrentTransform() { + return this._transformMatrix; + } }); - ctx.__defineGetter__('mozCurrentTransformInverse', - function getCurrentTransformInverse() { + Object.defineProperty(ctx, 'mozCurrentTransformInverse', { + get: function getCurrentTransformInverse() { // Calculation done using WolframAlpha: // http://www.wolframalpha.com/input/? // i=Inverse+{{a%2C+c%2C+e}%2C+{b%2C+d%2C+f}%2C+{0%2C+0%2C+1}} @@ -86,16 +88,19 @@ function addCtxCurrentTransform(ctx) { var m = this._transformMatrix; var a = m[0], b = m[1], c = m[2], d = m[3], e = m[4], f = m[5]; + var ad_bc = a * d - b * c; + var bc_ad = b * c - a * d; + return [ - d / (a * d - b * c), - b / (b * c - a * d), - c / (b * c - a * d), - a / (a * d - b * c), - (d * e - c * f) / (b * c - a * d), - (b * e - a * f) / (a * d - b * c) + d / ad_bc, + b / bc_ad, + c / bc_ad, + a / ad_bc, + (d * e - c * f) / bc_ad, + (b * e - a * f) / ad_bc ]; } - ); + }); ctx.save = function ctxSave() { var old = this._transformMatrix; @@ -111,7 +116,7 @@ function addCtxCurrentTransform(ctx) { this._transformMatrix = prev; this._originalRestore(); } - } + }; ctx.translate = function ctxTranslate(x, y) { var m = this._transformMatrix; @@ -119,7 +124,7 @@ function addCtxCurrentTransform(ctx) { m[5] = m[1] * x + m[3] * y + m[5]; this._originalTranslate(x, y); - } + }; ctx.scale = function ctxScale(x, y) { var m = this._transformMatrix; @@ -129,7 +134,7 @@ function addCtxCurrentTransform(ctx) { m[3] = m[3] * y; this._originalScale(x, y); - } + }; ctx.transform = function ctxTransform(a, b, c, d, e, f) { var m = this._transformMatrix; @@ -143,7 +148,7 @@ function addCtxCurrentTransform(ctx) { ]; ctx._originalTransform(a, b, c, d, e, f); - } + }; ctx.rotate = function ctxRotate(angle) { var cosValue = Math.cos(angle); @@ -160,7 +165,7 @@ function addCtxCurrentTransform(ctx) { ]; this._originalRotate(angle); - } + }; } } @@ -184,7 +189,7 @@ var CanvasGraphics = (function canvasGraphics() { this.objs = objs; if (canvasCtx) { - addCtxCurrentTransform(canvasCtx); + addContextCurrentTransform(canvasCtx); } } diff --git a/src/pattern.js b/src/pattern.js index c565e8b7f..72d13d896 100644 --- a/src/pattern.js +++ b/src/pattern.js @@ -217,8 +217,6 @@ var TilingPattern = (function tilingPattern() { // set the new canvas element context as the graphics context var tmpCtx = tmpCanvas.getContext('2d'); - addCtxCurrentTransform(tmpCtx); - var graphics = new CanvasGraphics(tmpCtx, objs); switch (paintType) { From 8991cfad83768f3d73152ccf8ad1c59c595afa53 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Sun, 27 Nov 2011 20:54:25 +0100 Subject: [PATCH 35/35] Check after each executed command if execution took too long already --- src/canvas.js | 40 +++++++++++++++++++--------------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/src/canvas.js b/src/canvas.js index 9b3ed0ba9..780a96c9b 100644 --- a/src/canvas.js +++ b/src/canvas.js @@ -64,10 +64,6 @@ var CanvasGraphics = (function canvasGraphics() { // before it stops and shedules a continue of execution. var kExecutionTime = 50; - // Number of IR commands to execute before checking - // if we execute longer then `kExecutionTime`. - var kExecutionTimeCheck = 500; - function constructor(canvasCtx, objs) { this.ctx = canvasCtx; this.current = new CanvasExtraState(); @@ -112,31 +108,33 @@ var CanvasGraphics = (function canvasGraphics() { var i = executionStartIdx || 0; var argsArrayLen = argsArray.length; + // Sometimes the IRQueue to execute is empty. + if (argsArrayLen == i) { + return i; + } + var executionEndIdx; var startTime = Date.now(); var objs = this.objs; - do { - executionEndIdx = Math.min(argsArrayLen, i + kExecutionTimeCheck); + while (true) { + if (fnArray[i] !== 'dependency') { + this[fnArray[i]].apply(this, argsArray[i]); + } else { + var deps = argsArray[i]; + for (var n = 0, nn = deps.length; n < nn; n++) { + var depObjId = deps[n]; - for (i; i < executionEndIdx; i++) { - if (fnArray[i] !== 'dependency') { - this[fnArray[i]].apply(this, argsArray[i]); - } else { - var deps = argsArray[i]; - for (var n = 0, nn = deps.length; n < nn; n++) { - var depObjId = deps[n]; - - // If the promise isn't resolved yet, add the continueCallback - // to the promise and bail out. - if (!objs.isResolved(depObjId)) { - objs.get(depObjId, continueCallback); - return i; - } + // If the promise isn't resolved yet, add the continueCallback + // to the promise and bail out. + if (!objs.isResolved(depObjId)) { + objs.get(depObjId, continueCallback); + return i; } } } + i++; // If the entire IRQueue was executed, stop as were done. if (i == argsArrayLen) { @@ -153,7 +151,7 @@ var CanvasGraphics = (function canvasGraphics() { // If the IRQueue isn't executed completly yet OR the execution time // was short enough, do another execution round. - } while (true); + } }, endDrawing: function canvasGraphicsEndDrawing() {