From d76f5f681531937276a4a379a56a2efafb123166 Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Tue, 13 Dec 2011 08:48:27 -0800 Subject: [PATCH 01/18] Playing with initial decode map impl. --- src/evaluator.js | 2 +- src/image.js | 21 ++++++++++++++++----- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/evaluator.js b/src/evaluator.js index edef57f91..5c20c8660 100644 --- a/src/evaluator.js +++ b/src/evaluator.js @@ -227,7 +227,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { data: new Uint8Array(w * h * 4) }; var pixels = imgData.data; - imageObj.fillRgbaBuffer(pixels, imageObj.decode); + imageObj.fillRgbaBuffer(pixels); handler.send('obj', [objId, 'Image', imgData]); }, handler, xref, resources, image, inline); } diff --git a/src/image.js b/src/image.js index 987542c58..8adae5030 100644 --- a/src/image.js +++ b/src/image.js @@ -104,10 +104,13 @@ var PDFImage = (function PDFImageClosure() { }; PDFImage.prototype = { - getComponents: function getComponents(buffer, decodeMap) { + getComponents: function getComponents(buffer) { var bpc = this.bpc; - if (bpc == 8) - return buffer; + var decodeMap = this.decode; + //if (decodeMap) + // debugger; + //if (bpc == 8) + // return buffer; var width = this.width; var height = this.height; @@ -160,6 +163,14 @@ var PDFImage = (function PDFImageClosure() { var remainingBits = bits - bpc; output[i] = buf >> remainingBits; + if (decodeMap) { + var x = output[i]; + var dmin = decodeMap[0]; + var dmax = decodeMap[1]; + var max = Math.pow(2, bpc) - 1; + var val = max * (dmin + x * ((dmax - dmin)/(max))); + output[i] = val; + } buf = buf & ((1 << remainingBits) - 1); bits = remainingBits; } @@ -210,7 +221,7 @@ var PDFImage = (function PDFImageClosure() { } } }, - fillRgbaBuffer: function fillRgbaBuffer(buffer, decodeMap) { + fillRgbaBuffer: function fillRgbaBuffer(buffer) { var numComps = this.numComps; var width = this.width; var height = this.height; @@ -221,7 +232,7 @@ var PDFImage = (function PDFImageClosure() { var imgArray = this.getImageBytes(height * rowBytes); var comps = this.colorSpace.getRgbBuffer( - this.getComponents(imgArray, decodeMap), bpc); + this.getComponents(imgArray), bpc); var compsPos = 0; var opacity = this.getOpacity(); var opacityPos = 0; From 55bbcbc6649504779c1469ecd8a4620466b5f5c0 Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Tue, 13 Dec 2011 13:53:22 -0800 Subject: [PATCH 02/18] Working improved version. Still need to refactor default decode stuff. --- src/image.js | 87 ++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 70 insertions(+), 17 deletions(-) diff --git a/src/image.js b/src/image.js index 8adae5030..f848b12ae 100644 --- a/src/image.js +++ b/src/image.js @@ -24,6 +24,16 @@ var PDFImage = (function PDFImageClosure() { promise.resolve(image); } } + /** + * Decode and clamp a value. + */ + function decode(value, addend, coefficient, max) { + // This formula is different from the spec because we don't decode to + // float range [0,1], we decode it in the [0,max] range. + value = addend + value * coefficient; + // Clamp the value to the range + return value < 0 ? 0 : value > max ? max : value; + } function PDFImage(xref, res, image, inline, smask) { this.image = image; if (image.getParams) { @@ -69,6 +79,18 @@ var PDFImage = (function PDFImageClosure() { } this.decode = dict.get('Decode', 'D'); + if (this.decode && !this.hasDefaultDecode()) { + // Do some preprocessing to avoid more math. + var max = (1 << bitsPerComponent) - 1; + this.decodeCoefficients = []; + this.decodeAddends = []; + for (var i = 0, j = 0; i < this.decode.length; i += 2, ++j) { + var dmin = this.decode[i]; + var dmax = this.decode[i + 1]; + this.decodeCoefficients[j] = dmax - dmin; + this.decodeAddends[j] = max * dmin; + } + } var mask = xref.fetchIfRef(dict.get('Mask')); @@ -106,23 +128,40 @@ var PDFImage = (function PDFImageClosure() { PDFImage.prototype = { getComponents: function getComponents(buffer) { var bpc = this.bpc; + var defaultDecode = this.hasDefaultDecode(); var decodeMap = this.decode; - //if (decodeMap) - // debugger; - //if (bpc == 8) - // return buffer; - + if (decodeMap) console.time('getComps'); + // This image doesn't require extra work + if (bpc == 8 && defaultDecode) + return buffer; + var bufferLength = buffer.length; var width = this.width; var height = this.height; var numComps = this.numComps; - var length = width * height; + var length = width * height * numComps; var bufferPos = 0; var output = bpc <= 8 ? new Uint8Array(length) : bpc <= 16 ? new Uint16Array(length) : new Uint32Array(length); var rowComps = width * numComps; + var decodeAddends, decodeCoefficients; + if (!defaultDecode) { + decodeAddends = this.decodeAddends; + decodeCoefficients = this.decodeCoefficients; + } + var max = (1 << bpc) - 1; - if (bpc == 1) { + if (bpc == 8) { + // Optimization for reading 8 bpc images that have a decode. + for (var i = 0, ii = length; i < ii; ++i) { + var compIndex = i % numComps; + var value = buffer[i]; + value = decode(value, decodeAddends[compIndex], + decodeCoefficients[compIndex], max); + output[i] = value; + } + } else if (bpc == 1) { + // Optimization for reading 1 bpc images. var valueZero = 0, valueOne = 1; if (decodeMap) { valueZero = decodeMap[0] ? 1 : 0; @@ -147,8 +186,7 @@ var PDFImage = (function PDFImageClosure() { output[i] = !(buf & mask) ? valueZero : valueOne; } } else { - if (decodeMap != null) - TODO('interpolate component values'); + // The general case that handles all other bpc values. var bits = 0, buf = 0; for (var i = 0, ii = length; i < ii; ++i) { if (i % rowComps == 0) { @@ -162,19 +200,18 @@ var PDFImage = (function PDFImageClosure() { } var remainingBits = bits - bpc; - output[i] = buf >> remainingBits; - if (decodeMap) { - var x = output[i]; - var dmin = decodeMap[0]; - var dmax = decodeMap[1]; - var max = Math.pow(2, bpc) - 1; - var val = max * (dmin + x * ((dmax - dmin)/(max))); - output[i] = val; + var value = buf >> remainingBits; + if (!defaultDecode) { + var compIndex = i % numComps; + value = decode(value, decodeAddends[compIndex], + decodeCoefficients[compIndex], max); } + output[i] = value; buf = buf & ((1 << remainingBits) - 1); bits = remainingBits; } } + if(decodeMap) console.timeEnd('getComps'); return output; }, getOpacity: function getOpacity() { @@ -267,6 +304,22 @@ var PDFImage = (function PDFImageClosure() { getImageBytes: function getImageBytes(length) { this.image.reset(); return this.image.getBytes(length); + }, + hasDefaultDecode: function hasDefaultDecode() { + // TODO lab color as a way different decode map + if (!this.decode) + return true; + var numComps = this.numComps; + var decode = this.decode; + if (numComps * 2 !== decode.length) { + warning('The decode map is not the correct length'); + return true; + } + for (var i = 0, ii = decode.length; i < ii; i += 2) { + if (decode[i] != 0 || decode[i + 1] != 1) + return false; + } + return true; } }; return PDFImage; From ff1d804fd706e709ab240364a862e5c2557fd30f Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Tue, 13 Dec 2011 14:35:46 -0800 Subject: [PATCH 03/18] Move the default decode to logic to the colorspace. --- src/colorspace.js | 40 ++++++++++++++++++++++++++++++++++++++++ src/image.js | 45 ++++++++++++++++----------------------------- 2 files changed, 56 insertions(+), 29 deletions(-) diff --git a/src/colorspace.js b/src/colorspace.js index 231ff6923..87d0083c0 100644 --- a/src/colorspace.js +++ b/src/colorspace.js @@ -154,6 +154,29 @@ var ColorSpace = (function ColorSpaceClosure() { } return null; }; + /** + * Checks if a decode map matches the default decode map for a color space. + * This handles the general decode maps where there are two values per + * component. e.g. [0, 1, 0, 1, 0, 1] for a RGB color. + * This does not handle Lab, Indexed, or Pattern decode maps since they are + * slightly different. + * @param {Array} decode Decode map (usually from an image). + * @param {Number} n Number of components the color space has. + */ + ColorSpace.isDefaultDecode = function colorSpaceIsDefaultDecode(decode, n) { + if (!decode) + return true; + + if (n * 2 !== decode.length) { + warning('The decode map is not the correct length'); + return true; + } + for (var i = 0, ii = decode.length; i < ii; i += 2) { + if (decode[i] != 0 || decode[i + 1] != 1) + return false; + } + return true; + }; return ColorSpace; })(); @@ -200,6 +223,9 @@ var AlternateCS = (function AlternateCSClosure() { baseBuf[pos++] = 255 * tinted[j]; } return base.getRgbBuffer(baseBuf, 8); + }, + isDefaultDecode: function altcs_isDefaultDecode(decodeMap) { + ColorSpace.isDefaultDecode(decodeMap, this.numComps); } }; @@ -267,6 +293,10 @@ var IndexedCS = (function IndexedCSClosure() { } return base.getRgbBuffer(baseBuf, 8); + }, + isDefaultDecode: function indexcs_isDefaultDecode(decodeMap) { + // indexed color maps shouldn't be changed + return true; } }; return IndexedCS; @@ -295,6 +325,9 @@ var DeviceGrayCS = (function DeviceGrayCSClosure() { rgbBuf[j++] = c; } return rgbBuf; + }, + isDefaultDecode: function graycs_isDefaultDecode(decodeMap) { + ColorSpace.isDefaultDecode(decodeMap, this.numComps); } }; return DeviceGrayCS; @@ -319,6 +352,10 @@ var DeviceRgbCS = (function DeviceRgbCSClosure() { for (i = 0; i < length; ++i) rgbBuf[i] = (scale * input[i]) | 0; return rgbBuf; + }, + isDefaultDecode: function rgbcs_isDefaultDecode(decodeMap) { + return 0 == decodeMap[0] == decodeMap[2] == decodeMap[4] && + 1 == decodeMap[1] == decodeMap[3] == decoeMap[5]; } }; return DeviceRgbCS; @@ -403,6 +440,9 @@ var DeviceCmykCS = (function DeviceCmykCSClosure() { } return rgbBuf; + }, + isDefaultDecode: function cmykcs_isDefaultDecode(decodeMap) { + ColorSpace.isDefaultDecode(decodeMap, this.numComps); } }; diff --git a/src/image.js b/src/image.js index f848b12ae..2f99dee54 100644 --- a/src/image.js +++ b/src/image.js @@ -25,11 +25,10 @@ var PDFImage = (function PDFImageClosure() { } } /** - * Decode and clamp a value. + * Decode and clamp a value. The formula is different from the spec because we + * don't decode to float range [0,1], we decode it in the [0,max] range. */ - function decode(value, addend, coefficient, max) { - // This formula is different from the spec because we don't decode to - // float range [0,1], we decode it in the [0,max] range. + function decodeAndClamp(value, addend, coefficient, max) { value = addend + value * coefficient; // Clamp the value to the range return value < 0 ? 0 : value > max ? max : value; @@ -79,7 +78,10 @@ var PDFImage = (function PDFImageClosure() { } this.decode = dict.get('Decode', 'D'); - if (this.decode && !this.hasDefaultDecode()) { + this.needsDecode = false; + if (this.decode && this.colorSpace && + !this.colorSpace.isDefaultDecode(this.decode)) { + this.needsDecode = true; // Do some preprocessing to avoid more math. var max = (1 << bitsPerComponent) - 1; this.decodeCoefficients = []; @@ -128,12 +130,13 @@ var PDFImage = (function PDFImageClosure() { PDFImage.prototype = { getComponents: function getComponents(buffer) { var bpc = this.bpc; - var defaultDecode = this.hasDefaultDecode(); + var needsDecode = this.needsDecode; var decodeMap = this.decode; - if (decodeMap) console.time('getComps'); - // This image doesn't require extra work - if (bpc == 8 && defaultDecode) + + // This image doesn't require any extra work. + if (bpc == 8 && !needsDecode) return buffer; + var bufferLength = buffer.length; var width = this.width; var height = this.height; @@ -145,7 +148,7 @@ var PDFImage = (function PDFImageClosure() { bpc <= 16 ? new Uint16Array(length) : new Uint32Array(length); var rowComps = width * numComps; var decodeAddends, decodeCoefficients; - if (!defaultDecode) { + if (needsDecode) { decodeAddends = this.decodeAddends; decodeCoefficients = this.decodeCoefficients; } @@ -156,7 +159,7 @@ var PDFImage = (function PDFImageClosure() { for (var i = 0, ii = length; i < ii; ++i) { var compIndex = i % numComps; var value = buffer[i]; - value = decode(value, decodeAddends[compIndex], + value = decodeAndClamp(value, decodeAddends[compIndex], decodeCoefficients[compIndex], max); output[i] = value; } @@ -201,9 +204,9 @@ var PDFImage = (function PDFImageClosure() { var remainingBits = bits - bpc; var value = buf >> remainingBits; - if (!defaultDecode) { + if (needsDecode) { var compIndex = i % numComps; - value = decode(value, decodeAddends[compIndex], + value = decodeAndClamp(value, decodeAddends[compIndex], decodeCoefficients[compIndex], max); } output[i] = value; @@ -304,22 +307,6 @@ var PDFImage = (function PDFImageClosure() { getImageBytes: function getImageBytes(length) { this.image.reset(); return this.image.getBytes(length); - }, - hasDefaultDecode: function hasDefaultDecode() { - // TODO lab color as a way different decode map - if (!this.decode) - return true; - var numComps = this.numComps; - var decode = this.decode; - if (numComps * 2 !== decode.length) { - warning('The decode map is not the correct length'); - return true; - } - for (var i = 0, ii = decode.length; i < ii; i += 2) { - if (decode[i] != 0 || decode[i + 1] != 1) - return false; - } - return true; } }; return PDFImage; From 9d042ce16661b3453581786f701e172594ba5a13 Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Wed, 14 Dec 2011 08:47:35 -0800 Subject: [PATCH 04/18] Use the general function for is default decode. --- src/colorspace.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/colorspace.js b/src/colorspace.js index 87d0083c0..8088ab7df 100644 --- a/src/colorspace.js +++ b/src/colorspace.js @@ -354,8 +354,7 @@ var DeviceRgbCS = (function DeviceRgbCSClosure() { return rgbBuf; }, isDefaultDecode: function rgbcs_isDefaultDecode(decodeMap) { - return 0 == decodeMap[0] == decodeMap[2] == decodeMap[4] && - 1 == decodeMap[1] == decodeMap[3] == decoeMap[5]; + ColorSpace.isDefaultDecode(decodeMap, this.numComps); } }; return DeviceRgbCS; From ce57bac447cbe7cf9e264d8aaa3608a5e61c24be Mon Sep 17 00:00:00 2001 From: Adil Allawi Date: Wed, 14 Dec 2011 22:28:34 +0000 Subject: [PATCH 05/18] Build Text Layer one div at a a time as an Interval instead of a all in a TimeOut to keep the browser responsive --- src/canvas.js | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/canvas.js b/src/canvas.js index 4aaf75508..1efddbefa 100644 --- a/src/canvas.js +++ b/src/canvas.js @@ -259,7 +259,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { this.textLayerQueue = []; // Prevent textLayerQueue from being rendered while rendering a new page if (this.textLayerTimer) - clearTimeout(this.textLayerTimer); + clearInterval(this.textLayerTimer); }, executeIRQueue: function canvasGraphicsExecuteIRQueue(codeIR, @@ -329,17 +329,21 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { var self = this; var renderTextLayer = function canvasRenderTextLayer() { + var finished = true; var textDivs = self.textDivs; - for (var i = 0, length = textDivs.length; i < length; ++i) { - if (textDivs[i].dataset.textLength > 1) { // avoid div by zero - textLayer.appendChild(textDivs[i]); + if (textDivs.length > 0) { + var textDiv = textDivs.shift(); + if (textDiv.dataset.textLength > 1) { // avoid div by zero + textLayer.appendChild(textDiv); // Adjust div width (via letterSpacing) to match canvas text // Due to the .offsetWidth calls, this is slow - textDivs[i].style.letterSpacing = - ((textDivs[i].dataset.canvasWidth - textDivs[i].offsetWidth) / - (textDivs[i].dataset.textLength - 1)) + 'px'; + textDiv.style.letterSpacing = + ((textDiv.dataset.canvasWidth - textDiv.offsetWidth) / + (textDiv.dataset.textLength - 1)) + 'px'; } + finished = false; } + return finished; } var textLayerQueue = this.textLayerQueue; textLayerQueue.push(renderTextLayer); @@ -347,12 +351,16 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { // Lazy textLayer rendering (to prevent UI hangs) // Only render queue if activity has stopped, where "no activity" == // "no beginDrawing() calls in the last N ms" - this.textLayerTimer = setTimeout(function renderTextLayerQueue() { + this.textLayerTimer = setInterval(function renderTextLayerQueue() { // Render most recent (==most relevant) layers first for (var i = textLayerQueue.length - 1; i >= 0; i--) { - textLayerQueue.pop().call(); + var finished = textLayerQueue[i].call(); + if (finished) + textLayerQueue.splice(i,1); } - }, 500); + if (textLayerQueue.length == 0) + clearInterval(this.textLayerTimer); + }, 1); }, // Graphics state From cc007b539a73d0d96d7d34d48851a4aad7f95f75 Mon Sep 17 00:00:00 2001 From: Adil Allawi Date: Thu, 15 Dec 2011 09:20:55 +0000 Subject: [PATCH 06/18] fix lint nitpick --- src/canvas.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/canvas.js b/src/canvas.js index 1efddbefa..fbebc0aa2 100644 --- a/src/canvas.js +++ b/src/canvas.js @@ -356,7 +356,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { for (var i = textLayerQueue.length - 1; i >= 0; i--) { var finished = textLayerQueue[i].call(); if (finished) - textLayerQueue.splice(i,1); + textLayerQueue.splice(i, 1); } if (textLayerQueue.length == 0) clearInterval(this.textLayerTimer); From 4a14a798845e42e946f41da89a4bdc6ceab4a08d Mon Sep 17 00:00:00 2001 From: Adil Allawi Date: Thu, 15 Dec 2011 10:08:50 +0000 Subject: [PATCH 07/18] use array index instead of array.shift() --- src/canvas.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/canvas.js b/src/canvas.js index fbebc0aa2..38b33d19d 100644 --- a/src/canvas.js +++ b/src/canvas.js @@ -328,11 +328,12 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { return; var self = this; + var textDivIndex = 0; var renderTextLayer = function canvasRenderTextLayer() { - var finished = true; + var finished = false; var textDivs = self.textDivs; - if (textDivs.length > 0) { - var textDiv = textDivs.shift(); + if (textDivIndex < textDivs.length) { + var textDiv = textDivs[textDivIndex++]; if (textDiv.dataset.textLength > 1) { // avoid div by zero textLayer.appendChild(textDiv); // Adjust div width (via letterSpacing) to match canvas text @@ -341,8 +342,9 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { ((textDiv.dataset.canvasWidth - textDiv.offsetWidth) / (textDiv.dataset.textLength - 1)) + 'px'; } - finished = false; } + else + finished = true; return finished; } var textLayerQueue = this.textLayerQueue; From 5bd080fd0508197e34e03e2d73447b7cfe9a3c26 Mon Sep 17 00:00:00 2001 From: Adil Allawi Date: Thu, 15 Dec 2011 11:32:58 +0000 Subject: [PATCH 08/18] oops, interval was not clearing because 'this' is not the same 'this' inside an interval. Should use local variable 'self' instead. Reviewers you should have spotted this! :) --- src/canvas.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/canvas.js b/src/canvas.js index 2d1130b4f..50c762bca 100644 --- a/src/canvas.js +++ b/src/canvas.js @@ -361,7 +361,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { textLayerQueue.splice(i, 1); } if (textLayerQueue.length == 0) - clearInterval(this.textLayerTimer); + clearInterval(self.textLayerTimer); }, 1); }, From 8096d747c0f0ff85f9a39b43e56d7c2153cc9913 Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Thu, 15 Dec 2011 15:13:48 -0800 Subject: [PATCH 09/18] Fix nits. --- src/canvas.js | 4 ++-- src/image.js | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/canvas.js b/src/canvas.js index e056fe0f2..e42f53cf1 100644 --- a/src/canvas.js +++ b/src/canvas.js @@ -1106,9 +1106,9 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { paintImageXObject: function canvasGraphicsPaintImageXObject(objId) { var imgData = this.objs.get(objId); - if (!imgData) { + if (!imgData) error('Dependent image isn\'t ready yet'); - } + this.save(); var ctx = this.ctx; var w = imgData.width; diff --git a/src/image.js b/src/image.js index 2f99dee54..22a9f9eb4 100644 --- a/src/image.js +++ b/src/image.js @@ -214,7 +214,6 @@ var PDFImage = (function PDFImageClosure() { bits = remainingBits; } } - if(decodeMap) console.timeEnd('getComps'); return output; }, getOpacity: function getOpacity() { From d6e4607fd039c0f2ccb7c863d048cd4d9235ec9d Mon Sep 17 00:00:00 2001 From: notmasteryet Date: Thu, 15 Dec 2011 20:44:55 -0600 Subject: [PATCH 10/18] Add compatibility for HTMLElement's dataset (#945) --- web/compatibility.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/web/compatibility.js b/web/compatibility.js index 7d1d72553..e4e2f2440 100644 --- a/web/compatibility.js +++ b/web/compatibility.js @@ -205,3 +205,15 @@ }); })(); +// HTMLElement dataset property +(function checkDatasetProperty() { + var div = document.createElement('div'); + if ('dataset' in div) + return; // dataset property exists + Object.defineProperty(HTMLElement.prototype, 'dataset', { + get: function htmlElementDatasetGetter() { + // adding dataset field to the actual object + return (this.dataset = {}); + } + }); +})(); From b921486bce08ef6cc2683989848b44f28861e2f4 Mon Sep 17 00:00:00 2001 From: Adil Allawi Date: Fri, 16 Dec 2011 10:38:30 +0000 Subject: [PATCH 11/18] simplify adding of textdivs to DOM as queue is no longer needed --- src/canvas.js | 52 +++++++++++++++------------------------------------ 1 file changed, 15 insertions(+), 37 deletions(-) diff --git a/src/canvas.js b/src/canvas.js index 50c762bca..0e351c1b8 100644 --- a/src/canvas.js +++ b/src/canvas.js @@ -257,9 +257,6 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { this.ctx.scale(cw / mediaBox.width, ch / mediaBox.height); this.textDivs = []; this.textLayerQueue = []; - // Prevent textLayerQueue from being rendered while rendering a new page - if (this.textLayerTimer) - clearInterval(this.textLayerTimer); }, executeIRQueue: function canvasGraphicsExecuteIRQueue(codeIR, @@ -328,41 +325,22 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { return; var self = this; - var textDivIndex = 0; - var renderTextLayer = function canvasRenderTextLayer() { - var finished = false; - var textDivs = self.textDivs; - if (textDivIndex < textDivs.length) { - var textDiv = textDivs[textDivIndex++]; - if (textDiv.dataset.textLength > 1) { // avoid div by zero - textLayer.appendChild(textDiv); - // Adjust div width (via letterSpacing) to match canvas text - // Due to the .offsetWidth calls, this is slow - textDiv.style.letterSpacing = - ((textDiv.dataset.canvasWidth - textDiv.offsetWidth) / - (textDiv.dataset.textLength - 1)) + 'px'; - } - } - else - finished = true; - return finished; - } - var textLayerQueue = this.textLayerQueue; - textLayerQueue.push(renderTextLayer); - - // Lazy textLayer rendering (to prevent UI hangs) - // Only render queue if activity has stopped, where "no activity" == - // "no beginDrawing() calls in the last N ms" - this.textLayerTimer = setInterval(function renderTextLayerQueue() { - // Render most recent (==most relevant) layers first - for (var i = textLayerQueue.length - 1; i >= 0; i--) { - var finished = textLayerQueue[i].call(); - if (finished) - textLayerQueue.splice(i, 1); - } - if (textLayerQueue.length == 0) + var textDivs = this.textDivs; + this.textLayerTimer = setInterval(function renderTextLayer() { + if (textDivs.length === 0) { clearInterval(self.textLayerTimer); - }, 1); + return; + } + var textDiv = textDivs.shift(); + if (textDiv.dataset.textLength > 1) { // avoid div by zero + textLayer.appendChild(textDiv); + // Adjust div width (via letterSpacing) to match canvas text + // Due to the .offsetWidth calls, this is slow + textDiv.style.letterSpacing = + ((textDiv.dataset.canvasWidth - textDiv.offsetWidth) / + (textDiv.dataset.textLength - 1)) + 'px'; + } + }, 0); }, // Graphics state From 156e20ca7398b34d5eeb10c98c9da879b61d3c3a Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Sun, 18 Dec 2011 20:39:10 +0100 Subject: [PATCH 12/18] Cache Cmd object to reduce number of created objects --- src/obj.js | 11 +++++++++++ src/parser.js | 12 ++++++------ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/obj.js b/src/obj.js index 2f7488a76..cb29f4c3c 100644 --- a/src/obj.js +++ b/src/obj.js @@ -22,6 +22,17 @@ var Cmd = (function CmdClosure() { Cmd.prototype = { }; + + var cmdCache = {}; + + Cmd.get = function(cmd) { + if (cmdCache[cmd]) { + return cmdCache[cmd]; + } else { + return cmdCache[cmd] = new Cmd(cmd); + } + } + return Cmd; })(); diff --git a/src/parser.js b/src/parser.js index 695438379..e50b12b9b 100644 --- a/src/parser.js +++ b/src/parser.js @@ -157,7 +157,7 @@ var Parser = (function ParserClosure() { imageStream = this.filter(imageStream, dict, length); imageStream.parameters = dict; - this.buf2 = new Cmd('EI'); + this.buf2 = Cmd.get('EI'); this.shift(); return imageStream; @@ -496,14 +496,14 @@ var Lexer = (function LexerClosure() { // array punctuation case '[': case ']': - return new Cmd(ch); + return Cmd.get(ch); // hex string or dict punctuation case '<': ch = stream.lookChar(); if (ch == '<') { // dict punctuation stream.skip(); - return new Cmd('<<'); + return Cmd.get('<<'); } return this.getHexString(ch); // dict punctuation @@ -511,11 +511,11 @@ var Lexer = (function LexerClosure() { ch = stream.lookChar(); if (ch == '>') { stream.skip(); - return new Cmd('>>'); + return Cmd.get('>>'); } case '{': case '}': - return new Cmd(ch); + return Cmd.get(ch); // fall through case ')': error('Illegal character: ' + ch); @@ -538,7 +538,7 @@ var Lexer = (function LexerClosure() { return false; if (str == 'null') return null; - return new Cmd(str); + return Cmd.get(str); }, skipToNextLine: function lexerSkipToNextLine() { var stream = this.stream; From 3a86e047e05e12ab5544d9f0609a3b56153c44fd Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Sun, 18 Dec 2011 20:42:06 +0100 Subject: [PATCH 13/18] Prevent default actions when zooming. Otherwise the browser performs a zoom of the entire page --- web/viewer.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/web/viewer.js b/web/viewer.js index daf0174ab..d9732c2a3 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -895,18 +895,26 @@ window.addEventListener('keydown', function keydown(evt) { return; // ignoring if the 'controls' element is focused curElement = curElement.parentNode; } + var handled = false; switch (evt.keyCode) { case 61: // FF/Mac '=' case 107: // FF '+' and '=' case 187: // Chrome '+' PDFView.zoomIn(); + handled = true; break; case 109: // FF '-' case 189: // Chrome '-' PDFView.zoomOut(); + handled = true; break; case 48: // '0' PDFView.setScale(kDefaultScale, true); + handled = true; break; } + + if (handled) { + evt.preventDefault(); + } }); From aebb9946f8a653c83c5bdad1a79ab062d7d75321 Mon Sep 17 00:00:00 2001 From: Chris Peterson Date: Sun, 18 Dec 2011 20:39:41 -0800 Subject: [PATCH 14/18] Add keyboard shortcuts to navigate pages with left and right arrow keys This is the behavior of Adobe Reader.app and Apple's Preview.app. --- web/viewer.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/web/viewer.js b/web/viewer.js index d9732c2a3..e8b94af46 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -912,6 +912,14 @@ window.addEventListener('keydown', function keydown(evt) { PDFView.setScale(kDefaultScale, true); handled = true; break; + case 37: // left arrow + PDFView.page--; + handle = true; + break; + case 39: // right arrow + PDFView.page++; + handle = true; + break; } if (handled) { From 4008ec69b832dd06f004dc2dcc59386c17e521a7 Mon Sep 17 00:00:00 2001 From: Chris Peterson Date: Sun, 18 Dec 2011 20:40:44 -0800 Subject: [PATCH 15/18] Add keyboard shortcuts to navigate pages with 'j' and 'k' keys like vi --- web/viewer.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/web/viewer.js b/web/viewer.js index e8b94af46..3404c6e42 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -913,10 +913,12 @@ window.addEventListener('keydown', function keydown(evt) { handled = true; break; case 37: // left arrow + case 75: // 'k' PDFView.page--; handle = true; break; case 39: // right arrow + case 74: // 'j' PDFView.page++; handle = true; break; From 396daddefebc90edfb553e8d9f75fc373c438423 Mon Sep 17 00:00:00 2001 From: Chris Peterson Date: Sun, 18 Dec 2011 20:42:18 -0800 Subject: [PATCH 16/18] Add keyboard shortcuts to navigate pages with 'n' and 'p' keys (Next/Previous) --- web/viewer.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/web/viewer.js b/web/viewer.js index 3404c6e42..24c3458d7 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -914,11 +914,13 @@ window.addEventListener('keydown', function keydown(evt) { break; case 37: // left arrow case 75: // 'k' + case 80: // 'p' PDFView.page--; handle = true; break; case 39: // right arrow case 74: // 'j' + case 78: // 'n' PDFView.page++; handle = true; break; From 8adb92a7092818462abe087c47f53a9d95e9d075 Mon Sep 17 00:00:00 2001 From: Chris Peterson Date: Sun, 18 Dec 2011 20:58:14 -0800 Subject: [PATCH 17/18] Fix typo: s/handle/handled/ --- web/viewer.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/viewer.js b/web/viewer.js index 24c3458d7..153de99aa 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -916,13 +916,13 @@ window.addEventListener('keydown', function keydown(evt) { case 75: // 'k' case 80: // 'p' PDFView.page--; - handle = true; + handled = true; break; case 39: // right arrow case 74: // 'j' case 78: // 'n' PDFView.page++; - handle = true; + handled = true; break; } From e9c762a97fa9c7520f5b88a8a3b108f2372ddf99 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Mon, 19 Dec 2011 16:37:36 +0100 Subject: [PATCH 18/18] Address review comments by notmasteryet --- src/obj.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/obj.js b/src/obj.js index cb29f4c3c..453014a91 100644 --- a/src/obj.js +++ b/src/obj.js @@ -25,13 +25,13 @@ var Cmd = (function CmdClosure() { var cmdCache = {}; - Cmd.get = function(cmd) { - if (cmdCache[cmd]) { - return cmdCache[cmd]; - } else { - return cmdCache[cmd] = new Cmd(cmd); - } - } + Cmd.get = function cmdGet(cmd) { + var cmdValue = cmdCache[cmd]; + if (cmdValue) + return cmdValue; + + return cmdCache[cmd] = new Cmd(cmd); + }; return Cmd; })();