diff --git a/static/js/libs/pdf/pdf.js b/static/js/libs/pdf/pdf.js index 1ba993ba..9b089ad6 100644 --- a/static/js/libs/pdf/pdf.js +++ b/static/js/libs/pdf/pdf.js @@ -22,8 +22,8 @@ if (typeof PDFJS === 'undefined') { (typeof window !== 'undefined' ? window : this).PDFJS = {}; } -PDFJS.version = '1.0.907'; -PDFJS.build = 'e9072ac'; +PDFJS.version = '1.0.1040'; +PDFJS.build = '997096f'; (function pdfjsWrapper() { // Use strict in our context only - users might not want it @@ -601,10 +601,10 @@ var Util = PDFJS.Util = (function UtilClosure() { // makeCssRgb() can be called thousands of times. Using |rgbBuf| avoids // creating many intermediate strings. - Util.makeCssRgb = function Util_makeCssRgb(rgb) { - rgbBuf[1] = rgb[0]; - rgbBuf[3] = rgb[1]; - rgbBuf[5] = rgb[2]; + Util.makeCssRgb = function Util_makeCssRgb(r, g, b) { + rgbBuf[1] = r; + rgbBuf[3] = g; + rgbBuf[5] = b; return rgbBuf.join(''); }; @@ -3111,9 +3111,17 @@ var Metadata = PDFJS.Metadata = (function MetadataClosure() { // Minimal font size that would be used during canvas fillText operations. var MIN_FONT_SIZE = 16; +// Maximum font size that would be used during canvas fillText operations. +var MAX_FONT_SIZE = 100; var MAX_GROUP_SIZE = 4096; +// Heuristic value used when enforcing minimum line widths. +var MIN_WIDTH_FACTOR = 0.65; + var COMPILE_TYPE3_GLYPHS = true; +var MAX_SIZE_TO_COMPILE = 1000; + +var FULL_CHUNK_HEIGHT = 16; function createScratchCanvas(width, height) { var canvas = document.createElement('canvas'); @@ -3247,7 +3255,7 @@ var CachedCanvases = (function CachedCanvasesClosure() { getCanvas: function CachedCanvases_getCanvas(id, width, height, trackTransform) { var canvasEntry; - if (id in cache) { + if (cache[id] !== undefined) { canvasEntry = cache[id]; canvasEntry.canvas.width = width; canvasEntry.canvas.height = height; @@ -3461,6 +3469,7 @@ var CanvasExtraState = (function CanvasExtraStateClosure() { // Default fore and background colors this.fillColor = '#000000'; this.strokeColor = '#000000'; + this.patternFill = false; // Note: fill alpha applies to all non-stroking operations this.fillAlpha = 1; this.strokeAlpha = 1; @@ -3513,6 +3522,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { if (canvasCtx) { addContextCurrentTransform(canvasCtx); } + this.cachedGetSinglePixelWidth = null; } function putBinaryImageData(ctx, imgData) { @@ -3533,13 +3543,11 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { // that's ok; any such pixels are ignored. var height = imgData.height, width = imgData.width; - var fullChunkHeight = 16; - var fracChunks = height / fullChunkHeight; - var fullChunks = Math.floor(fracChunks); - var totalChunks = Math.ceil(fracChunks); - var partialChunkHeight = height - fullChunks * fullChunkHeight; + var partialChunkHeight = height % FULL_CHUNK_HEIGHT; + var fullChunks = (height - partialChunkHeight) / FULL_CHUNK_HEIGHT; + var totalChunks = partialChunkHeight === 0 ? fullChunks : fullChunks + 1; - var chunkImgData = ctx.createImageData(width, fullChunkHeight); + var chunkImgData = ctx.createImageData(width, FULL_CHUNK_HEIGHT); var srcPos = 0, destPos; var src = imgData.data; var dest = chunkImgData.data; @@ -3559,7 +3567,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { 0xFF000000 : 0x000000FF; for (i = 0; i < totalChunks; i++) { thisChunkHeight = - (i < fullChunks) ? fullChunkHeight : partialChunkHeight; + (i < fullChunks) ? FULL_CHUNK_HEIGHT : partialChunkHeight; destPos = 0; for (j = 0; j < thisChunkHeight; j++) { var srcDiff = srcLength - srcPos; @@ -3594,19 +3602,19 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { dest32[destPos++] = 0; } - ctx.putImageData(chunkImgData, 0, i * fullChunkHeight); + ctx.putImageData(chunkImgData, 0, i * FULL_CHUNK_HEIGHT); } } else if (imgData.kind === ImageKind.RGBA_32BPP) { // RGBA, 32-bits per pixel. j = 0; - elemsInThisChunk = width * fullChunkHeight * 4; + elemsInThisChunk = width * FULL_CHUNK_HEIGHT * 4; for (i = 0; i < fullChunks; i++) { dest.set(src.subarray(srcPos, srcPos + elemsInThisChunk)); srcPos += elemsInThisChunk; ctx.putImageData(chunkImgData, 0, j); - j += fullChunkHeight; + j += FULL_CHUNK_HEIGHT; } if (i < totalChunks) { elemsInThisChunk = width * partialChunkHeight * 4; @@ -3616,11 +3624,11 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { } else if (imgData.kind === ImageKind.RGB_24BPP) { // RGB, 24-bits per pixel. - thisChunkHeight = fullChunkHeight; + thisChunkHeight = FULL_CHUNK_HEIGHT; elemsInThisChunk = width * thisChunkHeight; for (i = 0; i < totalChunks; i++) { if (i >= fullChunks) { - thisChunkHeight =partialChunkHeight; + thisChunkHeight = partialChunkHeight; elemsInThisChunk = width * thisChunkHeight; } @@ -3631,7 +3639,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { dest[destPos++] = src[srcPos++]; dest[destPos++] = 255; } - ctx.putImageData(chunkImgData, 0, i * fullChunkHeight); + ctx.putImageData(chunkImgData, 0, i * FULL_CHUNK_HEIGHT); } } else { error('bad image kind: ' + imgData.kind); @@ -3640,20 +3648,18 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { function putBinaryImageMask(ctx, imgData) { var height = imgData.height, width = imgData.width; - var fullChunkHeight = 16; - var fracChunks = height / fullChunkHeight; - var fullChunks = Math.floor(fracChunks); - var totalChunks = Math.ceil(fracChunks); - var partialChunkHeight = height - fullChunks * fullChunkHeight; + var partialChunkHeight = height % FULL_CHUNK_HEIGHT; + var fullChunks = (height - partialChunkHeight) / FULL_CHUNK_HEIGHT; + var totalChunks = partialChunkHeight === 0 ? fullChunks : fullChunks + 1; - var chunkImgData = ctx.createImageData(width, fullChunkHeight); + var chunkImgData = ctx.createImageData(width, FULL_CHUNK_HEIGHT); var srcPos = 0; var src = imgData.data; var dest = chunkImgData.data; for (var i = 0; i < totalChunks; i++) { var thisChunkHeight = - (i < fullChunks) ? fullChunkHeight : partialChunkHeight; + (i < fullChunks) ? FULL_CHUNK_HEIGHT : partialChunkHeight; // Expand the mask so it can be used by the canvas. Any required // inversion has already been handled. @@ -3670,7 +3676,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { mask >>= 1; } } - ctx.putImageData(chunkImgData, 0, i * fullChunkHeight); + ctx.putImageData(chunkImgData, 0, i * FULL_CHUNK_HEIGHT); } } @@ -3680,14 +3686,14 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { 'globalCompositeOperation', 'font']; for (var i = 0, ii = properties.length; i < ii; i++) { var property = properties[i]; - if (property in sourceCtx) { + if (sourceCtx[property] !== undefined) { destCtx[property] = sourceCtx[property]; } } - if ('setLineDash' in sourceCtx) { + if (sourceCtx.setLineDash !== undefined) { destCtx.setLineDash(sourceCtx.getLineDash()); destCtx.lineDashOffset = sourceCtx.lineDashOffset; - } else if ('mozDash' in sourceCtx) { + } else if (sourceCtx.mozDashOffset !== undefined) { destCtx.mozDash = sourceCtx.mozDash; destCtx.mozDashOffset = sourceCtx.mozDashOffset; } @@ -3722,9 +3728,9 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { function composeSMaskLuminosity(maskData, layerData) { var length = maskData.length; for (var i = 3; i < length; i += 4) { - var y = ((maskData[i - 3] * 77) + // * 0.3 / 255 * 0x10000 - (maskData[i - 2] * 152) + // * 0.59 .... - (maskData[i - 1] * 28)) | 0; // * 0.11 .... + var y = (maskData[i - 3] * 77) + // * 0.3 / 255 * 0x10000 + (maskData[i - 2] * 152) + // * 0.59 .... + (maskData[i - 1] * 28); // * 0.11 .... layerData[i] = (layerData[i] * y) >> 16; } } @@ -3744,7 +3750,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { } // processing image in chunks to save memory - var PIXELS_TO_PROCESS = 65536; + var PIXELS_TO_PROCESS = 1048576; var chunkSize = Math.min(height, Math.ceil(PIXELS_TO_PROCESS / width)); for (var row = 0; row < height; row += chunkSize) { var chunkHeight = Math.min(chunkSize, height - row); @@ -3914,7 +3920,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { }, setDash: function CanvasGraphics_setDash(dashArray, dashPhase) { var ctx = this.ctx; - if ('setLineDash' in ctx) { + if (ctx.setLineDash !== undefined) { ctx.setLineDash(dashArray); ctx.lineDashOffset = dashPhase; } else { @@ -4049,10 +4055,14 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { this.current = this.stateStack.pop(); this.ctx.restore(); + + this.cachedGetSinglePixelWidth = null; } }, transform: function CanvasGraphics_transform(a, b, c, d, e, f) { this.ctx.transform(a, b, c, d, e, f); + + this.cachedGetSinglePixelWidth = null; }, // Path @@ -4126,9 +4136,9 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { consumePath = typeof consumePath !== 'undefined' ? consumePath : true; var ctx = this.ctx; var strokeColor = this.current.strokeColor; - if (this.current.lineWidth === 0) { - ctx.lineWidth = this.getSinglePixelWidth(); - } + // Prevent drawing too thin lines by enforcing a minimum line width. + ctx.lineWidth = Math.max(this.getSinglePixelWidth() * MIN_WIDTH_FACTOR, + this.current.lineWidth); // For stroke we want to temporarily change the global alpha to the // stroking alpha. ctx.globalAlpha = this.current.strokeAlpha; @@ -4157,10 +4167,10 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { consumePath = typeof consumePath !== 'undefined' ? consumePath : true; var ctx = this.ctx; var fillColor = this.current.fillColor; + var isPatternFill = this.current.patternFill; var needRestore = false; - if (fillColor && fillColor.hasOwnProperty('type') && - fillColor.type === 'Pattern') { + if (isPatternFill) { ctx.save(); ctx.fillStyle = fillColor.getPattern(ctx, this); needRestore = true; @@ -4311,9 +4321,9 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { // Keeping the font at minimal size and using the fontSizeScale to change // the current transformation matrix before the fillText/strokeText. // See https://bugzilla.mozilla.org/show_bug.cgi?id=726227 - var browserFontSize = size >= MIN_FONT_SIZE ? size : MIN_FONT_SIZE; - this.current.fontSizeScale = browserFontSize !== MIN_FONT_SIZE ? 1.0 : - size / MIN_FONT_SIZE; + var browserFontSize = size < MIN_FONT_SIZE ? MIN_FONT_SIZE : + size > MAX_FONT_SIZE ? MAX_FONT_SIZE : size; + this.current.fontSizeScale = size / browserFontSize; var rule = italic + ' ' + bold + ' ' + browserFontSize + 'px ' + typeface; this.ctx.font = rule; @@ -4453,7 +4463,13 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { var lineWidth = current.lineWidth; var scale = current.textMatrixScale; if (scale === 0 || lineWidth === 0) { - lineWidth = this.getSinglePixelWidth(); + var fillStrokeMode = current.textRenderingMode & + TextRenderingMode.FILL_STROKE_MASK; + if (fillStrokeMode === TextRenderingMode.STROKE || + fillStrokeMode === TextRenderingMode.FILL_STROKE) { + this.cachedGetSinglePixelWidth = null; + lineWidth = this.getSinglePixelWidth() * MIN_WIDTH_FACTOR; + } } else { lineWidth /= scale; } @@ -4548,9 +4564,11 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { var textHScale = current.textHScale * fontDirection; var fontMatrix = current.fontMatrix || FONT_IDENTITY_MATRIX; var glyphsLength = glyphs.length; + var isTextInvisible = + current.textRenderingMode === TextRenderingMode.INVISIBLE; var i, glyph, width; - if (fontSize === 0) { + if (isTextInvisible || fontSize === 0) { return; } @@ -4632,16 +4650,18 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { }, setFillColorN: function CanvasGraphics_setFillColorN(/*...*/) { this.current.fillColor = this.getColorN_Pattern(arguments); + this.current.patternFill = true; }, setStrokeRGBColor: function CanvasGraphics_setStrokeRGBColor(r, g, b) { - var color = Util.makeCssRgb(arguments); + var color = Util.makeCssRgb(r, g, b); this.ctx.strokeStyle = color; this.current.strokeColor = color; }, setFillRGBColor: function CanvasGraphics_setFillRGBColor(r, g, b) { - var color = Util.makeCssRgb(arguments); + var color = Util.makeCssRgb(r, g, b); this.ctx.fillStyle = color; this.current.fillColor = color; + this.current.patternFill = false; }, shadingFill: function CanvasGraphics_shadingFill(patternIR) { @@ -4900,11 +4920,12 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { paintImageMaskXObject: function CanvasGraphics_paintImageMaskXObject(img) { var ctx = this.ctx; var width = img.width, height = img.height; + var fillColor = this.current.fillColor; + var isPatternFill = this.current.patternFill; var glyph = this.processingType3; - if (COMPILE_TYPE3_GLYPHS && glyph && !('compiled' in glyph)) { - var MAX_SIZE_TO_COMPILE = 1000; + if (COMPILE_TYPE3_GLYPHS && glyph && glyph.compiled === undefined) { if (width <= MAX_SIZE_TO_COMPILE && height <= MAX_SIZE_TO_COMPILE) { glyph.compiled = compileType3Glyph({data: img.data, width: width, height: height}); @@ -4926,9 +4947,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { maskCtx.globalCompositeOperation = 'source-in'; - var fillColor = this.current.fillColor; - maskCtx.fillStyle = (fillColor && fillColor.hasOwnProperty('type') && - fillColor.type === 'Pattern') ? + maskCtx.fillStyle = isPatternFill ? fillColor.getPattern(maskCtx, this) : fillColor; maskCtx.fillRect(0, 0, width, height); @@ -4942,7 +4961,8 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { scaleY, positions) { var width = imgData.width; var height = imgData.height; - var ctx = this.ctx; + var fillColor = this.current.fillColor; + var isPatternFill = this.current.patternFill; var maskCanvas = CachedCanvases.getCanvas('maskCanvas', width, height); var maskCtx = maskCanvas.context; @@ -4952,14 +4972,13 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { maskCtx.globalCompositeOperation = 'source-in'; - var fillColor = this.current.fillColor; - maskCtx.fillStyle = (fillColor && fillColor.hasOwnProperty('type') && - fillColor.type === 'Pattern') ? - fillColor.getPattern(maskCtx, this) : fillColor; + maskCtx.fillStyle = isPatternFill ? + fillColor.getPattern(maskCtx, this) : fillColor; maskCtx.fillRect(0, 0, width, height); maskCtx.restore(); + var ctx = this.ctx; for (var i = 0, ii = positions.length; i < ii; i += 2) { ctx.save(); ctx.transform(scaleX, 0, 0, scaleY, positions[i], positions[i + 1]); @@ -4974,6 +4993,8 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { function CanvasGraphics_paintImageMaskXObjectGroup(images) { var ctx = this.ctx; + var fillColor = this.current.fillColor; + var isPatternFill = this.current.patternFill; for (var i = 0, ii = images.length; i < ii; i++) { var image = images[i]; var width = image.width, height = image.height; @@ -4986,9 +5007,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { maskCtx.globalCompositeOperation = 'source-in'; - var fillColor = this.current.fillColor; - maskCtx.fillStyle = (fillColor && fillColor.hasOwnProperty('type') && - fillColor.type === 'Pattern') ? + maskCtx.fillStyle = isPatternFill ? fillColor.getPattern(maskCtx, this) : fillColor; maskCtx.fillRect(0, 0, width, height); @@ -5191,11 +5210,14 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { ctx.beginPath(); }, getSinglePixelWidth: function CanvasGraphics_getSinglePixelWidth(scale) { - var inverse = this.ctx.mozCurrentTransformInverse; - // max of the current horizontal and vertical scale - return Math.sqrt(Math.max( - (inverse[0] * inverse[0] + inverse[1] * inverse[1]), - (inverse[2] * inverse[2] + inverse[3] * inverse[3]))); + if (this.cachedGetSinglePixelWidth === null) { + var inverse = this.ctx.mozCurrentTransformInverse; + // max of the current horizontal and vertical scale + this.cachedGetSinglePixelWidth = Math.sqrt(Math.max( + (inverse[0] * inverse[0] + inverse[1] * inverse[1]), + (inverse[2] * inverse[2] + inverse[3] * inverse[3]))); + } + return this.cachedGetSinglePixelWidth; }, getCanvasPosition: function CanvasGraphics_getCanvasPosition(x, y) { var transform = this.ctx.mozCurrentTransform; @@ -5263,7 +5285,7 @@ var WebGLUtils = (function WebGLUtilsClosure() { } var currentGL, currentCanvas; - function generageGL() { + function generateGL() { if (currentGL) { return; } @@ -5321,7 +5343,7 @@ var WebGLUtils = (function WebGLUtilsClosure() { function initSmaskGL() { var canvas, gl; - generageGL(); + generateGL(); canvas = currentCanvas; currentCanvas = null; gl = currentGL; @@ -5453,7 +5475,7 @@ var WebGLUtils = (function WebGLUtilsClosure() { function initFiguresGL() { var canvas, gl; - generageGL(); + generateGL(); canvas = currentCanvas; currentCanvas = null; gl = currentGL; @@ -5621,7 +5643,7 @@ var WebGLUtils = (function WebGLUtilsClosure() { } var enabled = false; try { - generageGL(); + generateGL(); enabled = !!currentGL; } catch (e) { } return shadow(this, 'isEnabled', enabled); @@ -6014,7 +6036,7 @@ var TilingPattern = (function TilingPatternClosure() { context.strokeStyle = ctx.strokeStyle; break; case PaintType.UNCOLORED: - var cssColor = Util.makeCssRgb(color); + var cssColor = Util.makeCssRgb(color[0], color[1], color[2]); context.fillStyle = cssColor; context.strokeStyle = cssColor; break; @@ -6373,7 +6395,6 @@ var FontFaceObject = (function FontFaceObjectClosure() { })(); -var HIGHLIGHT_OFFSET = 4; // px var ANNOT_MIN_SIZE = 10; // px var AnnotationUtils = (function AnnotationUtilsClosure() { @@ -6400,43 +6421,36 @@ var AnnotationUtils = (function AnnotationUtilsClosure() { style.fontFamily = fontFamily + fallbackName; } - // TODO(mack): Remove this, it's not really that helpful. - function getEmptyContainer(tagName, rect, borderWidth) { - var bWidth = borderWidth || 0; - var element = document.createElement(tagName); - element.style.borderWidth = bWidth + 'px'; - var width = rect[2] - rect[0] - 2 * bWidth; - var height = rect[3] - rect[1] - 2 * bWidth; - element.style.width = width + 'px'; - element.style.height = height + 'px'; - return element; - } - - function initContainer(item) { - var container = getEmptyContainer('section', item.rect, item.borderWidth); - container.style.backgroundColor = item.color; - - var color = item.color; - var rgb = []; - for (var i = 0; i < 3; ++i) { - rgb[i] = Math.round(color[i] * 255); + function initContainer(item, drawBorder) { + var container = document.createElement('section'); + var cstyle = container.style; + var width = item.rect[2] - item.rect[0]; + var height = item.rect[3] - item.rect[1]; + + var bWidth = item.borderWidth || 0; + if (bWidth) { + width = width - 2 * bWidth; + height = height - 2 * bWidth; + cstyle.borderWidth = bWidth + 'px'; + var color = item.color; + if (drawBorder && color) { + cstyle.borderStyle = 'solid'; + cstyle.borderColor = Util.makeCssRgb(Math.round(color[0] * 255), + Math.round(color[1] * 255), + Math.round(color[2] * 255)); + } } - item.colorCssRgb = Util.makeCssRgb(rgb); - - var highlight = document.createElement('div'); - highlight.className = 'annotationHighlight'; - highlight.style.left = highlight.style.top = -HIGHLIGHT_OFFSET + 'px'; - highlight.style.right = highlight.style.bottom = -HIGHLIGHT_OFFSET + 'px'; - highlight.setAttribute('hidden', true); - - item.highlightElement = highlight; - container.appendChild(item.highlightElement); - + cstyle.width = width + 'px'; + cstyle.height = height + 'px'; return container; } function getHtmlElementForTextWidgetAnnotation(item, commonObjs) { - var element = getEmptyContainer('div', item.rect, 0); + var element = document.createElement('div'); + var width = item.rect[2] - item.rect[0]; + var height = item.rect[3] - item.rect[1]; + element.style.width = width + 'px'; + element.style.height = height + 'px'; element.style.display = 'table'; var content = document.createElement('div'); @@ -6466,7 +6480,7 @@ var AnnotationUtils = (function AnnotationUtilsClosure() { rect[2] = rect[0] + (rect[3] - rect[1]); // make it square } - var container = initContainer(item); + var container = initContainer(item, false); container.className = 'annotText'; var image = document.createElement('img'); @@ -6491,13 +6505,15 @@ var AnnotationUtils = (function AnnotationUtilsClosure() { var i, ii; if (item.hasBgColor) { var color = item.color; - var rgb = []; - for (i = 0; i < 3; ++i) { - // Enlighten the color (70%) - var c = Math.round(color[i] * 255); - rgb[i] = Math.round((255 - c) * 0.7) + c; - } - content.style.backgroundColor = Util.makeCssRgb(rgb); + + // Enlighten the color (70%) + var BACKGROUND_ENLIGHT = 0.7; + var r = BACKGROUND_ENLIGHT * (1.0 - color[0]) + color[0]; + var g = BACKGROUND_ENLIGHT * (1.0 - color[1]) + color[1]; + var b = BACKGROUND_ENLIGHT * (1.0 - color[2]) + color[2]; + content.style.backgroundColor = Util.makeCssRgb((r * 255) | 0, + (g * 255) | 0, + (b * 255) | 0); } var title = document.createElement('h1'); @@ -6573,12 +6589,9 @@ var AnnotationUtils = (function AnnotationUtilsClosure() { } function getHtmlElementForLinkAnnotation(item) { - var container = initContainer(item); + var container = initContainer(item, true); container.className = 'annotLink'; - container.style.borderColor = item.colorCssRgb; - container.style.borderStyle = 'solid'; - var link = document.createElement('a'); link.href = link.title = item.url || ''; @@ -7409,11 +7422,11 @@ var SVGGraphics = (function SVGGraphicsClosure() { this.current.miterLimit = limit; }, setStrokeRGBColor: function SVGGraphics_setStrokeRGBColor(r, g, b) { - var color = Util.makeCssRgb(arguments); + var color = Util.makeCssRgb(r, g, b); this.current.strokeColor = color; }, setFillRGBColor: function SVGGraphics_setFillRGBColor(r, g, b) { - var color = Util.makeCssRgb(arguments); + var color = Util.makeCssRgb(r, g, b); this.current.fillColor = color; this.current.tspan = document.createElementNS(NS, 'svg:tspan'); this.current.xcoords = []; diff --git a/static/js/libs/pdf/pdf.worker.js b/static/js/libs/pdf/pdf.worker.js index 9820c100..e68c9ad1 100644 --- a/static/js/libs/pdf/pdf.worker.js +++ b/static/js/libs/pdf/pdf.worker.js @@ -22,8 +22,8 @@ if (typeof PDFJS === 'undefined') { (typeof window !== 'undefined' ? window : this).PDFJS = {}; } -PDFJS.version = '1.0.907'; -PDFJS.build = 'e9072ac'; +PDFJS.version = '1.0.1040'; +PDFJS.build = '997096f'; (function pdfjsWrapper() { // Use strict in our context only - users might not want it @@ -601,10 +601,10 @@ var Util = PDFJS.Util = (function UtilClosure() { // makeCssRgb() can be called thousands of times. Using |rgbBuf| avoids // creating many intermediate strings. - Util.makeCssRgb = function Util_makeCssRgb(rgb) { - rgbBuf[1] = rgb[0]; - rgbBuf[3] = rgb[1]; - rgbBuf[5] = rgb[2]; + Util.makeCssRgb = function Util_makeCssRgb(r, g, b) { + rgbBuf[1] = r; + rgbBuf[3] = g; + rgbBuf[5] = b; return rgbBuf.join(''); }; @@ -1687,7 +1687,10 @@ var NetworkManager = (function NetworkManagerClosure() { } if (args.onProgressiveData) { - xhr.responseType = 'moz-chunked-arraybuffer'; + // Some legacy browsers might throw an exception. + try { + xhr.responseType = 'moz-chunked-arraybuffer'; + } catch(e) {} if (xhr.responseType === 'moz-chunked-arraybuffer') { pendingRequest.onProgressiveData = args.onProgressiveData; pendingRequest.mozChunked = true; @@ -2845,6 +2848,10 @@ var Page = (function PageClosure() { * `PDFDocument` objects on the main thread created. */ var PDFDocument = (function PDFDocumentClosure() { + var FINGERPRINT_FIRST_BYTES = 1024; + var EMPTY_FINGERPRINT = '\x00\x00\x00\x00\x00\x00\x00' + + '\x00\x00\x00\x00\x00\x00\x00\x00\x00'; + function PDFDocument(pdfManager, arg, password) { if (isStream(arg)) { init.call(this, pdfManager, arg, password); @@ -3056,16 +3063,25 @@ var PDFDocument = (function PDFDocumentClosure() { return shadow(this, 'documentInfo', docInfo); }, get fingerprint() { - var xref = this.xref, hash, fileID = ''; + var xref = this.xref, idArray, hash, fileID = ''; if (xref.trailer.has('ID')) { - hash = stringToBytes(xref.trailer.get('ID')[0]); + idArray = xref.trailer.get('ID'); + } + if (idArray && isArray(idArray) && idArray[0] !== EMPTY_FINGERPRINT) { + hash = stringToBytes(idArray[0]); } else { - hash = calculateMD5(this.stream.bytes.subarray(0, 100), 0, 100); + if (this.stream.ensureRange) { + this.stream.ensureRange(0, + Math.min(FINGERPRINT_FIRST_BYTES, this.stream.end)); + } + hash = calculateMD5(this.stream.bytes.subarray(0, + FINGERPRINT_FIRST_BYTES), 0, FINGERPRINT_FIRST_BYTES); } for (var i = 0, n = hash.length; i < n; i++) { - fileID += hash[i].toString(16); + var hex = hash[i].toString(16); + fileID += hex.length === 1 ? '0' + hex : hex; } return shadow(this, 'fingerprint', fileID); @@ -4921,12 +4937,26 @@ var Annotation = (function AnnotationClosure() { data.annotationFlags = dict.get('F'); var color = dict.get('C'); - if (isArray(color) && color.length === 3) { - // TODO(mack): currently only supporting rgb; need support different - // colorspaces - data.color = color; - } else { + if (!color) { + // The PDF spec does not mention how a missing color array is interpreted. + // Adobe Reader seems to default to black in this case. data.color = [0, 0, 0]; + } else if (isArray(color)) { + switch (color.length) { + case 0: + // Empty array denotes transparent border. + data.color = null; + break; + case 1: + // TODO: implement DeviceGray + break; + case 3: + data.color = color; + break; + case 4: + // TODO: implement DeviceCMYK + break; + } } // Some types of annotations have border style dict which has more @@ -4943,7 +4973,7 @@ var Annotation = (function AnnotationClosure() { if (data.borderWidth > 0 && dashArray) { if (!isArray(dashArray)) { // Ignore the border if dashArray is not actually an array, - // this is consistent with the behaviour in Adobe Reader. + // this is consistent with the behaviour in Adobe Reader. data.borderWidth = 0; } else { var dashArrayLength = dashArray.length; @@ -5189,7 +5219,7 @@ var WidgetAnnotation = (function WidgetAnnotationClosure() { var name = namedItem.get('T'); if (name) { fieldName.unshift(stringToPDFString(name)); - } else { + } else if (parent && ref) { // The field name is absent, that means more than one field // with the same name may exist. Replacing the empty name // with the '`' plus index in the parent's 'Kids' array. @@ -5366,11 +5396,7 @@ var LinkAnnotation = (function LinkAnnotationClosure() { return url; } - Util.inherit(LinkAnnotation, InteractiveAnnotation, { - hasOperatorList: function LinkAnnotation_hasOperatorList() { - return false; - } - }); + Util.inherit(LinkAnnotation, InteractiveAnnotation, { }); return LinkAnnotation; })(); @@ -5790,7 +5816,7 @@ var PDFFunction = (function PDFFunctionClosure() { var cachedValue = cache[key]; if (cachedValue !== undefined) { - cachedValue.set(dest, destOffset); + dest.set(cachedValue, destOffset); return; } @@ -5814,7 +5840,7 @@ var PDFFunction = (function PDFFunctionClosure() { cache_available--; cache[key] = output; } - output.set(dest, destOffset); + dest.set(output, destOffset); }; } }; @@ -9769,19 +9795,27 @@ var Pattern = (function PatternClosure() { var dict = isStream(shading) ? shading.dict : shading; var type = dict.get('ShadingType'); - switch (type) { - case PatternType.AXIAL: - case PatternType.RADIAL: - // Both radial and axial shadings are handled by RadialAxial shading. - return new Shadings.RadialAxial(dict, matrix, xref, res); - case PatternType.FREE_FORM_MESH: - case PatternType.LATTICE_FORM_MESH: - case PatternType.COONS_PATCH_MESH: - case PatternType.TENSOR_PATCH_MESH: - return new Shadings.Mesh(shading, matrix, xref, res); - default: - UnsupportedManager.notify(UNSUPPORTED_FEATURES.shadingPattern); - return new Shadings.Dummy(); + try { + switch (type) { + case PatternType.AXIAL: + case PatternType.RADIAL: + // Both radial and axial shadings are handled by RadialAxial shading. + return new Shadings.RadialAxial(dict, matrix, xref, res); + case PatternType.FREE_FORM_MESH: + case PatternType.LATTICE_FORM_MESH: + case PatternType.COONS_PATCH_MESH: + case PatternType.TENSOR_PATCH_MESH: + return new Shadings.Mesh(shading, matrix, xref, res); + default: + throw new Error('Unknown PatternType: ' + type); + } + } catch (ex) { + if (ex instanceof MissingDataException) { + throw ex; + } + UnsupportedManager.notify(UNSUPPORTED_FEATURES.shadingPattern); + warn(ex); + return new Shadings.Dummy(); } }; return Pattern; @@ -9866,14 +9900,14 @@ Shadings.RadialAxial = (function RadialAxialClosure() { ratio[0] = i; fn(ratio, 0, color, 0); rgbColor = cs.getRgb(color, 0); - var cssColor = Util.makeCssRgb(rgbColor); + var cssColor = Util.makeCssRgb(rgbColor[0], rgbColor[1], rgbColor[2]); colorStops.push([(i - t0) / diff, cssColor]); } var background = 'transparent'; if (dict.has('Background')) { rgbColor = cs.getRgb(dict.get('Background'), 0); - background = Util.makeCssRgb(rgbColor); + background = Util.makeCssRgb(rgbColor[0], rgbColor[1], rgbColor[2]); } if (!extendStart) { @@ -10659,7 +10693,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { buildPaintImageXObject: function PartialEvaluator_buildPaintImageXObject(resources, image, inline, operatorList, - cacheKey, cache) { + cacheKey, imageCache) { var self = this; var dict = image.dict; var w = dict.get('Width', 'W'); @@ -10697,9 +10731,10 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { args = [imgData]; operatorList.addOp(OPS.paintImageMaskXObject, args); if (cacheKey) { - cache.key = cacheKey; - cache.fn = OPS.paintImageMaskXObject; - cache.args = args; + imageCache[cacheKey] = { + fn: OPS.paintImageMaskXObject, + args: args + }; } return; } @@ -10741,16 +10776,17 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { var imgData = imageObj.createImageData(/* forceRGBA = */ false); self.handler.send('obj', [objId, self.pageIndex, 'Image', imgData], [imgData.data.buffer]); - }).then(null, function (reason) { + }).then(undefined, function (reason) { warn('Unable to decode image: ' + reason); self.handler.send('obj', [objId, self.pageIndex, 'Image', null]); }); operatorList.addOp(OPS.paintImageXObject, args); if (cacheKey) { - cache.key = cacheKey; - cache.fn = OPS.paintImageXObject; - cache.args = args; + imageCache[cacheKey] = { + fn: OPS.paintImageXObject, + args: args + }; } }, @@ -11144,8 +11180,8 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { } // eagerly compile XForm objects var name = args[0].name; - if (imageCache.key === name) { - operatorList.addOp(imageCache.fn, imageCache.args); + if (imageCache[name] !== undefined) { + operatorList.addOp(imageCache[name].fn, imageCache[name].args); args = null; continue; } @@ -11194,10 +11230,13 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { }, reject); case OPS.endInlineImage: var cacheKey = args[0].cacheKey; - if (cacheKey && imageCache.key === cacheKey) { - operatorList.addOp(imageCache.fn, imageCache.args); - args = null; - continue; + if (cacheKey) { + var cacheEntry = imageCache[cacheKey]; + if (cacheEntry !== undefined) { + operatorList.addOp(cacheEntry.fn, cacheEntry.args); + args = null; + continue; + } } self.buildPaintImageXObject(resources, args[0], true, operatorList, cacheKey, imageCache); @@ -14364,9 +14403,12 @@ var stdFontMap = { 'CourierNewPS-BoldMT': 'Courier-Bold', 'CourierNewPS-ItalicMT': 'Courier-Oblique', 'CourierNewPSMT': 'Courier', + 'Helvetica': 'Helvetica', 'Helvetica-Bold': 'Helvetica-Bold', 'Helvetica-BoldItalic': 'Helvetica-BoldOblique', + 'Helvetica-BoldOblique': 'Helvetica-BoldOblique', 'Helvetica-Italic': 'Helvetica-Oblique', + 'Helvetica-Oblique':'Helvetica-Oblique', 'Symbol-Bold': 'Symbol', 'Symbol-BoldItalic': 'Symbol', 'Symbol-Italic': 'Symbol', @@ -14392,6 +14434,10 @@ var stdFontMap = { * fonts without glyph data. */ var nonStdFontMap = { + 'CenturyGothic': 'Helvetica', + 'CenturyGothic-Bold': 'Helvetica-Bold', + 'CenturyGothic-BoldItalic': 'Helvetica-BoldOblique', + 'CenturyGothic-Italic': 'Helvetica-Oblique', 'ComicSansMS': 'Comic Sans MS', 'ComicSansMS-Bold': 'Comic Sans MS-Bold', 'ComicSansMS-BoldItalic': 'Comic Sans MS-BoldItalic', @@ -14415,7 +14461,8 @@ var nonStdFontMap = { 'MS-PMincho': 'MS PMincho', 'MS-PMincho-Bold': 'MS PMincho-Bold', 'MS-PMincho-BoldItalic': 'MS PMincho-BoldItalic', - 'MS-PMincho-Italic': 'MS PMincho-Italic' + 'MS-PMincho-Italic': 'MS PMincho-Italic', + 'Wingdings': 'ZapfDingbats' }; var serifFonts = { @@ -16498,7 +16545,8 @@ var Font = (function FontClosure() { // The file data is not specified. Trying to fix the font name // to be used with the canvas.font. var fontName = name.replace(/[,_]/g, '-'); - var isStandardFont = fontName in stdFontMap; + var isStandardFont = !!stdFontMap[fontName] || + (nonStdFontMap[fontName] && !!stdFontMap[nonStdFontMap[fontName]]); fontName = stdFontMap[fontName] || nonStdFontMap[fontName] || fontName; this.bold = (fontName.search(/bold/gi) !== -1); @@ -16544,6 +16592,10 @@ var Font = (function FontClosure() { this.toFontChar[charCode] = fontChar; } } else if (/Dingbats/i.test(fontName)) { + if (/Wingdings/i.test(name)) { + warn('Wingdings font without embedded font file, ' + + 'falling back to the ZapfDingbats encoding.'); + } var dingbats = Encodings.ZapfDingbatsEncoding; for (charCode in dingbats) { fontChar = DingbatsGlyphsUnicode[dingbats[charCode]]; @@ -16729,6 +16781,7 @@ var Font = (function FontClosure() { fontCharCode <= 0x1f || // Control chars fontCharCode === 0x7F || // Control char fontCharCode === 0xAD || // Soft hyphen + fontCharCode === 0xA0 || // Non breaking space (fontCharCode >= 0x80 && fontCharCode <= 0x9F) || // Control chars // Prevent drawing characters in the specials unicode block. (fontCharCode >= 0xFFF0 && fontCharCode <= 0xFFFF) || @@ -18747,6 +18800,8 @@ function type1FontGlyphMapping(properties, builtInEncoding, glyphNames) { glyphId = glyphNames.indexOf(baseEncoding[charCode]); if (glyphId >= 0) { charCodeToGlyphId[charCode] = glyphId; + } else { + charCodeToGlyphId[charCode] = 0; // notdef } } } else if (!!(properties.flags & FontFlags.Symbolic)) { @@ -18763,6 +18818,8 @@ function type1FontGlyphMapping(properties, builtInEncoding, glyphNames) { glyphId = glyphNames.indexOf(baseEncoding[charCode]); if (glyphId >= 0) { charCodeToGlyphId[charCode] = glyphId; + } else { + charCodeToGlyphId[charCode] = 0; // notdef } } } @@ -18775,6 +18832,8 @@ function type1FontGlyphMapping(properties, builtInEncoding, glyphNames) { glyphId = glyphNames.indexOf(glyphName); if (glyphId >= 0) { charCodeToGlyphId[charCode] = glyphId; + } else { + charCodeToGlyphId[charCode] = 0; // notdef } } } @@ -29950,16 +30009,14 @@ function isEOF(v) { return (v === EOF); } +var MAX_LENGTH_TO_CACHE = 1000; + var Parser = (function ParserClosure() { function Parser(lexer, allowStreams, xref) { this.lexer = lexer; this.allowStreams = allowStreams; this.xref = xref; - this.imageCache = { - length: 0, - adler32: 0, - stream: null - }; + this.imageCache = {}; this.refill(); } @@ -30050,31 +30107,14 @@ var Parser = (function ParserClosure() { // simple object return buf1; }, - makeInlineImage: function Parser_makeInlineImage(cipherTransform) { - var lexer = this.lexer; - var stream = lexer.stream; - - // parse dictionary - var dict = new Dict(null); - while (!isCmd(this.buf1, 'ID') && !isEOF(this.buf1)) { - if (!isName(this.buf1)) { - error('Dictionary key must be a name object'); - } - - var key = this.buf1.name; - this.shift(); - if (isEOF(this.buf1)) { - break; - } - dict.set(key, this.getObj(cipherTransform)); - } - - // parse image stream - var startPos = stream.pos; - - // searching for the /EI\s/ - var state = 0, ch, i, ii; - var E = 0x45, I = 0x49, SPACE = 0x20, NL = 0xA, CR = 0xD; + /** + * Find the end of the stream by searching for the /EI\s/. + * @returns {number} The inline stream length. + */ + findDefaultInlineStreamEnd: + function Parser_findDefaultInlineStreamEnd(stream) { + var E = 0x45, I = 0x49, SPACE = 0x20, LF = 0xA, CR = 0xD; + var startPos = stream.pos, state = 0, ch, i, n, followingBytes; while ((ch = stream.getByte()) !== -1) { if (state === 0) { state = (ch === E) ? 1 : 0; @@ -30082,13 +30122,13 @@ var Parser = (function ParserClosure() { state = (ch === I) ? 2 : 0; } else { assert(state === 2); - if (ch === SPACE || ch === NL || ch === CR) { + if (ch === SPACE || ch === LF || ch === CR) { // Let's check the next five bytes are ASCII... just be sure. - var n = 5; - var followingBytes = stream.peekBytes(n); + n = 5; + followingBytes = stream.peekBytes(n); for (i = 0; i < n; i++) { ch = followingBytes[i]; - if (ch !== NL && ch !== CR && (ch < SPACE || ch > 0x7F)) { + if (ch !== LF && ch !== CR && (ch < SPACE || ch > 0x7F)) { // Not a LF, CR, SPACE or any visible ASCII character, i.e. // it's binary stuff. Resetting the state. state = 0; @@ -30096,45 +30136,138 @@ var Parser = (function ParserClosure() { } } if (state === 2) { - break; // finished! + break; // Finished! } } else { state = 0; } } } + return ((stream.pos - 4) - startPos); + }, + /** + * Find the EOD (end-of-data) marker '~>' (i.e. TILDE + GT) of the stream. + * @returns {number} The inline stream length. + */ + findASCII85DecodeInlineStreamEnd: + function Parser_findASCII85DecodeInlineStreamEnd(stream) { + var TILDE = 0x7E, GT = 0x3E; + var startPos = stream.pos, ch, length; + while ((ch = stream.getByte()) !== -1) { + if (ch === TILDE && stream.peekByte() === GT) { + stream.skip(); + break; + } + } + length = stream.pos - startPos; + if (ch === -1) { + warn('Inline ASCII85Decode image stream: ' + + 'EOD marker not found, searching for /EI/ instead.'); + stream.skip(-length); // Reset the stream position. + return this.findDefaultInlineStreamEnd(stream); + } + this.inlineStreamSkipEI(stream); + return length; + }, + /** + * Find the EOD (end-of-data) marker '>' (i.e. GT) of the stream. + * @returns {number} The inline stream length. + */ + findASCIIHexDecodeInlineStreamEnd: + function Parser_findASCIIHexDecodeInlineStreamEnd(stream) { + var GT = 0x3E; + var startPos = stream.pos, ch, length; + while ((ch = stream.getByte()) !== -1) { + if (ch === GT) { + break; + } + } + length = stream.pos - startPos; + if (ch === -1) { + warn('Inline ASCIIHexDecode image stream: ' + + 'EOD marker not found, searching for /EI/ instead.'); + stream.skip(-length); // Reset the stream position. + return this.findDefaultInlineStreamEnd(stream); + } + this.inlineStreamSkipEI(stream); + return length; + }, + /** + * Skip over the /EI/ for streams where we search for an EOD marker. + */ + inlineStreamSkipEI: function Parser_inlineStreamSkipEI(stream) { + var E = 0x45, I = 0x49; + var state = 0, ch; + while ((ch = stream.getByte()) !== -1) { + if (state === 0) { + state = (ch === E) ? 1 : 0; + } else if (state === 1) { + state = (ch === I) ? 2 : 0; + } else if (state === 2) { + break; + } + } + }, + makeInlineImage: function Parser_makeInlineImage(cipherTransform) { + var lexer = this.lexer; + var stream = lexer.stream; - var length = (stream.pos - 4) - startPos; + // Parse dictionary. + var dict = new Dict(null); + while (!isCmd(this.buf1, 'ID') && !isEOF(this.buf1)) { + if (!isName(this.buf1)) { + error('Dictionary key must be a name object'); + } + var key = this.buf1.name; + this.shift(); + if (isEOF(this.buf1)) { + break; + } + dict.set(key, this.getObj(cipherTransform)); + } + + // Extract the name of the first (i.e. the current) image filter. + var filter = this.fetchIfRef(dict.get('Filter', 'F')), filterName; + if (isName(filter)) { + filterName = filter.name; + } else if (isArray(filter) && isName(filter[0])) { + filterName = filter[0].name; + } + + // Parse image stream. + var startPos = stream.pos, length, i, ii; + if (filterName === 'ASCII85Decide' || filterName === 'A85') { + length = this.findASCII85DecodeInlineStreamEnd(stream); + } else if (filterName === 'ASCIIHexDecode' || filterName === 'AHx') { + length = this.findASCIIHexDecodeInlineStreamEnd(stream); + } else { + length = this.findDefaultInlineStreamEnd(stream); + } var imageStream = stream.makeSubStream(startPos, length, dict); - // trying to cache repeat images, first we are trying to "warm up" caching - // using length, then comparing adler32 - var MAX_LENGTH_TO_CACHE = 1000; - var cacheImage = false, adler32; - if (length < MAX_LENGTH_TO_CACHE && this.imageCache.length === length) { + // Cache all images below the MAX_LENGTH_TO_CACHE threshold by their + // adler32 checksum. + var adler32; + if (length < MAX_LENGTH_TO_CACHE) { var imageBytes = imageStream.getBytes(); imageStream.reset(); var a = 1; var b = 0; for (i = 0, ii = imageBytes.length; i < ii; ++i) { - a = (a + (imageBytes[i] & 0xff)) % 65521; - b = (b + a) % 65521; + // No modulo required in the loop if imageBytes.length < 5552. + a += imageBytes[i] & 0xff; + b += a; } - adler32 = (b << 16) | a; + adler32 = ((b % 65521) << 16) | (a % 65521); - if (this.imageCache.stream && this.imageCache.adler32 === adler32) { + if (this.imageCache.adler32 === adler32) { this.buf2 = Cmd.get('EI'); this.shift(); - this.imageCache.stream.reset(); - return this.imageCache.stream; + this.imageCache[adler32].reset(); + return this.imageCache[adler32]; } - cacheImage = true; - } - if (!cacheImage && !this.imageCache.stream) { - this.imageCache.length = length; - this.imageCache.stream = null; } if (cipherTransform) { @@ -30143,10 +30276,9 @@ var Parser = (function ParserClosure() { imageStream = this.filter(imageStream, dict, length); imageStream.dict = dict; - if (cacheImage) { + if (adler32 !== undefined) { imageStream.cacheKey = 'inline_' + length + '_' + adler32; - this.imageCache.adler32 = adler32; - this.imageCache.stream = imageStream; + this.imageCache[adler32] = imageStream; } this.buf2 = Cmd.get('EI'); @@ -30294,22 +30426,6 @@ var Parser = (function ParserClosure() { return new LZWStream(stream, maybeLength, earlyChange); } if (name === 'DCTDecode' || name === 'DCT') { - // According to the specification: for inline images, the ID operator - // shall be followed by a single whitespace character (unless it uses - // ASCII85Decode or ASCIIHexDecode filters). - // In practice this only seems to be followed for inline JPEG images, - // and generally ignoring the first byte of the stream if it is a - // whitespace char can even *cause* issues (e.g. in the CCITTFaxDecode - // filters used in issue2984.pdf). - // Hence when the first byte of the stream of an inline JPEG image is - // a whitespace character, we thus simply skip over it. - if (isCmd(this.buf1, 'ID')) { - var firstByte = stream.peekByte(); - if (firstByte === 0x0A /* LF */ || firstByte === 0x0D /* CR */ || - firstByte === 0x20 /* SPACE */) { - stream.skip(); - } - } xrefStreamStats[StreamType.DCT] = true; return new JpegStream(stream, maybeLength, stream.dict, this.xref); } @@ -31857,8 +31973,15 @@ var PredictorStream = (function PredictorStreamClosure() { */ var JpegStream = (function JpegStreamClosure() { function JpegStream(stream, maybeLength, dict, xref) { - // TODO: per poppler, some images may have 'junk' before that - // need to be removed + // Some images may contain 'junk' before the SOI (start-of-image) marker. + // Note: this seems to mainly affect inline images. + var ch; + while ((ch = stream.getByte()) !== -1) { + if (ch === 0xFF) { // Find the first byte of the SOI marker (0xFFD8). + stream.skip(-1); // Reset the stream position to the SOI. + break; + } + } this.stream = stream; this.maybeLength = maybeLength; this.dict = dict; @@ -33033,7 +33156,7 @@ var CCITTFaxStream = (function CCITTFaxStreamClosure() { if (!this.eoblock && this.row === this.rows - 1) { this.eof = true; - } else if (this.eoline || !this.byteAlign) { + } else { code1 = this.lookBits(12); if (this.eoline) { while (code1 !== EOF && code1 !== 1) { @@ -34198,9 +34321,8 @@ var JpegImage = (function jpegImage() { function decodeHuffman(tree) { var node = tree; - var bit; - while ((bit = readBit()) !== null) { - node = node[bit]; + while (true) { + node = node[readBit()]; if (typeof node === 'number') { return node; } @@ -34208,17 +34330,12 @@ var JpegImage = (function jpegImage() { 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; + n = (n << 1) | readBit(); length--; } return n; @@ -34577,7 +34694,7 @@ var JpegImage = (function jpegImage() { // stage 3 // Shift v0 by 128.5 << 5 here, so we don't need to shift p0...p7 when - // converting to UInt8 range later. + // converting to UInt8 range later. v0 = ((v0 + v1 + 1) >> 1) + 4112; v1 = v0 - v1; t = (v2 * dctSin6 + v3 * dctCos6 + 2048) >> 12; @@ -34934,8 +35051,7 @@ var JpegImage = (function jpegImage() { } } - // decodeTransform will contains pairs of multiplier (-256..256) and - // additive + // decodeTransform contains pairs of multiplier (-256..256) and additive var transform = this.decodeTransform; if (transform) { for (i = 0; i < dataLength;) { @@ -34972,7 +35088,7 @@ var JpegImage = (function jpegImage() { }, _convertYcckToRgb: function convertYcckToRgb(data) { - var Y, Cb, Cr, k, CbCb, CbCr, CbY, Cbk, CrCr, Crk, CrY, YY, Yk, kk; + var Y, Cb, Cr, k; var offset = 0; for (var i = 0, length = data.length; i < length; i += 4) { Y = data[i]; @@ -34980,43 +35096,35 @@ var JpegImage = (function jpegImage() { Cr = data[i + 2]; k = data[i + 3]; - CbCb = Cb * Cb; - CbCr = Cb * Cr; - CbY = Cb * Y; - Cbk = Cb * k; - CrCr = Cr * Cr; - Crk = Cr * k; - CrY = Cr * Y; - YY = Y * Y; - Yk = Y * k; - kk = k * k; - - var r = - 122.67195406894 - - 6.60635669420364e-5 * CbCb + 0.000437130475926232 * CbCr - - 5.4080610064599e-5* CbY + 0.00048449797120281* Cbk - - 0.154362151871126 * Cb - 0.000957964378445773 * CrCr + - 0.000817076911346625 * CrY - 0.00477271405408747 * Crk + - 1.53380253221734 * Cr + 0.000961250184130688 * YY - - 0.00266257332283933 * Yk + 0.48357088451265 * Y - - 0.000336197177618394 * kk + 0.484791561490776 * k; + var r = -122.67195406894 + + Cb * (-6.60635669420364e-5 * Cb + 0.000437130475926232 * Cr - + 5.4080610064599e-5 * Y + 0.00048449797120281 * k - + 0.154362151871126) + + Cr * (-0.000957964378445773 * Cr + 0.000817076911346625 * Y - + 0.00477271405408747 * k + 1.53380253221734) + + Y * (0.000961250184130688 * Y - 0.00266257332283933 * k + + 0.48357088451265) + + k * (-0.000336197177618394 * k + 0.484791561490776); var g = 107.268039397724 + - 2.19927104525741e-5 * CbCb - 0.000640992018297945 * CbCr + - 0.000659397001245577* CbY + 0.000426105652938837* Cbk - - 0.176491792462875 * Cb - 0.000778269941513683 * CrCr + - 0.00130872261408275 * CrY + 0.000770482631801132 * Crk - - 0.151051492775562 * Cr + 0.00126935368114843 * YY - - 0.00265090189010898 * Yk + 0.25802910206845 * Y - - 0.000318913117588328 * kk - 0.213742400323665 * k; - - var b = - 20.810012546947 - - 0.000570115196973677 * CbCb - 2.63409051004589e-5 * CbCr + - 0.0020741088115012* CbY - 0.00288260236853442* Cbk + - 0.814272968359295 * Cb - 1.53496057440975e-5 * CrCr - - 0.000132689043961446 * CrY + 0.000560833691242812 * Crk - - 0.195152027534049 * Cr + 0.00174418132927582 * YY - - 0.00255243321439347 * Yk + 0.116935020465145 * Y - - 0.000343531996510555 * kk + 0.24165260232407 * k; + Cb * (2.19927104525741e-5 * Cb - 0.000640992018297945 * Cr + + 0.000659397001245577 * Y + 0.000426105652938837 * k - + 0.176491792462875) + + Cr * (-0.000778269941513683 * Cr + 0.00130872261408275 * Y + + 0.000770482631801132 * k - 0.151051492775562) + + Y * (0.00126935368114843 * Y - 0.00265090189010898 * k + + 0.25802910206845) + + k * (-0.000318913117588328 * k - 0.213742400323665); + + var b = -20.810012546947 + + Cb * (-0.000570115196973677 * Cb - 2.63409051004589e-5 * Cr + + 0.0020741088115012 * Y - 0.00288260236853442 * k + + 0.814272968359295) + + Cr * (-1.53496057440975e-5 * Cr - 0.000132689043961446 * Y + + 0.000560833691242812 * k - 0.195152027534049) + + Y * (0.00174418132927582 * Y - 0.00255243321439347 * k + + 0.116935020465145) + + k * (-0.000343531996510555 * k + 0.24165260232407); data[offset++] = clamp0to255(r); data[offset++] = clamp0to255(g); @@ -35157,18 +35265,52 @@ var JpxImage = (function JpxImageClosure() { var dataLength = lbox - headerSize; var jumpDataLength = true; switch (tbox) { - case 0x6A501A1A: // 'jP\032\032' - // TODO - break; case 0x6A703268: // 'jp2h' jumpDataLength = false; // parsing child boxes break; case 0x636F6C72: // 'colr' - // TODO + // Colorspaces are not used, the CS from the PDF is used. + var method = data[position]; + var precedence = data[position + 1]; + var approximation = data[position + 2]; + if (method === 1) { + // enumerated colorspace + var colorspace = readUint32(data, position + 3); + switch (colorspace) { + case 16: // this indicates a sRGB colorspace + case 17: // this indicates a grayscale colorspace + case 18: // this indicates a YUV colorspace + break; + default: + warn('Unknown colorspace ' + colorspace); + break; + } + } else if (method === 2) { + info('ICC profile not supported'); + } break; case 0x6A703263: // 'jp2c' this.parseCodestream(data, position, position + dataLength); break; + case 0x6A502020: // 'jP\024\024' + if (0x0d0a870a !== readUint32(data, position)) { + warn('Invalid JP2 signature'); + } + break; + // The following header types are valid but currently not used: + case 0x6A501A1A: // 'jP\032\032' + case 0x66747970: // 'ftyp' + case 0x72726571: // 'rreq' + case 0x72657320: // 'res ' + case 0x69686472: // 'ihdr' + break; + default: + var headerType = String.fromCharCode((tbox >> 24) & 0xFF, + (tbox >> 16) & 0xFF, + (tbox >> 8) & 0xFF, + tbox & 0xFF); + warn('Unsupported header type ' + tbox + ' (' + headerType + ')'); + break; } if (jumpDataLength) { position += dataLength; @@ -35384,12 +35526,6 @@ var JpxImage = (function JpxImageClosure() { cod.precinctsSizes = precinctsSizes; } var unsupported = []; - if (cod.sopMarkerUsed) { - unsupported.push('sopMarkerUsed'); - } - if (cod.ephMarkerUsed) { - unsupported.push('ephMarkerUsed'); - } if (cod.selectiveArithmeticCodingBypass) { unsupported.push('selectiveArithmeticCodingBypass'); } @@ -35540,6 +35676,23 @@ var JpxImage = (function JpxImageClosure() { // Section B.6 Division resolution to precincts var precinctWidth = 1 << dimensions.PPx; var precinctHeight = 1 << dimensions.PPy; + // Jasper introduces codeblock groups for mapping each subband codeblocks + // to precincts. Precinct partition divides a resolution according to width + // and height parameters. The subband that belongs to the resolution level + // has a different size than the level, unless it is the zero resolution. + + // From Jasper documentation: jpeg2000.pdf, section K: Tier-2 coding: + // The precinct partitioning for a particular subband is derived from a + // partitioning of its parent LL band (i.e., the LL band at the next higher + // resolution level)... The LL band associated with each resolution level is + // divided into precincts... Each of the resulting precinct regions is then + // mapped into its child subbands (if any) at the next lower resolution + // level. This is accomplished by using the coordinate transformation + // (u, v) = (ceil(x/2), ceil(y/2)) where (x, y) and (u, v) are the + // coordinates of a point in the LL band and child subband, respectively. + var isZeroRes = resolution.resLevel === 0; + var precinctWidthInSubband = 1 << (dimensions.PPx + (isZeroRes ? 0 : -1)); + var precinctHeightInSubband = 1 << (dimensions.PPy + (isZeroRes ? 0 : -1)); var numprecinctswide = (resolution.trx1 > resolution.trx0 ? Math.ceil(resolution.trx1 / precinctWidth) - Math.floor(resolution.trx0 / precinctWidth) : 0); @@ -35547,18 +35700,15 @@ var JpxImage = (function JpxImageClosure() { Math.ceil(resolution.try1 / precinctHeight) - Math.floor(resolution.try0 / precinctHeight) : 0); var numprecincts = numprecinctswide * numprecinctshigh; - var precinctXOffset = Math.floor(resolution.trx0 / precinctWidth) * - precinctWidth; - var precinctYOffset = Math.floor(resolution.try0 / precinctHeight) * - precinctHeight; + resolution.precinctParameters = { - precinctXOffset: precinctXOffset, - precinctYOffset: precinctYOffset, precinctWidth: precinctWidth, precinctHeight: precinctHeight, numprecinctswide: numprecinctswide, numprecinctshigh: numprecinctshigh, - numprecincts: numprecincts + numprecincts: numprecincts, + precinctWidthInSubband: precinctWidthInSubband, + precinctHeightInSubband: precinctHeightInSubband }; } function buildCodeblocks(context, subband, dimensions) { @@ -35585,21 +35735,29 @@ var JpxImage = (function JpxImageClosure() { tbx1: codeblockWidth * (i + 1), tby1: codeblockHeight * (j + 1) }; - // calculate precinct number - var pi = Math.floor((codeblock.tbx0 - - precinctParameters.precinctXOffset) / - precinctParameters.precinctWidth); - var pj = Math.floor((codeblock.tby0 - - precinctParameters.precinctYOffset) / - precinctParameters.precinctHeight); - precinctNumber = pj + pi * precinctParameters.numprecinctswide; + codeblock.tbx0_ = Math.max(subband.tbx0, codeblock.tbx0); codeblock.tby0_ = Math.max(subband.tby0, codeblock.tby0); codeblock.tbx1_ = Math.min(subband.tbx1, codeblock.tbx1); codeblock.tby1_ = Math.min(subband.tby1, codeblock.tby1); + + // Calculate precinct number for this codeblock, codeblock position + // should be relative to its subband, use actual dimension and position + // See comment about codeblock group width and height + var pi = Math.floor((codeblock.tbx0_ - subband.tbx0) / + precinctParameters.precinctWidthInSubband); + var pj = Math.floor((codeblock.tby0_ - subband.tby0) / + precinctParameters.precinctHeightInSubband); + precinctNumber = pi + (pj * precinctParameters.numprecinctswide); + codeblock.precinctNumber = precinctNumber; codeblock.subbandType = subband.type; codeblock.Lblock = 3; + + if (codeblock.tbx1_ <= codeblock.tbx0_ || + codeblock.tby1_ <= codeblock.tby0_) { + continue; + } codeblocks.push(codeblock); // building precinct for the sub-band var precinct = precincts[precinctNumber]; @@ -35735,6 +35893,230 @@ var JpxImage = (function JpxImageClosure() { throw new Error('JPX Error: Out of packets'); }; } + function ResolutionPositionComponentLayerIterator(context) { + var siz = context.SIZ; + var tileIndex = context.currentTile.index; + var tile = context.tiles[tileIndex]; + var layersCount = tile.codingStyleDefaultParameters.layersCount; + var componentsCount = siz.Csiz; + var l, r, c, p; + var maxDecompositionLevelsCount = 0; + for (c = 0; c < componentsCount; c++) { + var component = tile.components[c]; + maxDecompositionLevelsCount = Math.max(maxDecompositionLevelsCount, + component.codingStyleParameters.decompositionLevelsCount); + } + var maxNumPrecinctsInLevel = new Int32Array( + maxDecompositionLevelsCount + 1); + for (r = 0; r <= maxDecompositionLevelsCount; ++r) { + var maxNumPrecincts = 0; + for (c = 0; c < componentsCount; ++c) { + var resolutions = tile.components[c].resolutions; + if (r < resolutions.length) { + maxNumPrecincts = Math.max(maxNumPrecincts, + resolutions[r].precinctParameters.numprecincts); + } + } + maxNumPrecinctsInLevel[r] = maxNumPrecincts; + } + l = 0; + r = 0; + c = 0; + p = 0; + + this.nextPacket = function JpxImage_nextPacket() { + // Section B.12.1.3 Resolution-position-component-layer + for (; r <= maxDecompositionLevelsCount; r++) { + for (; p < maxNumPrecinctsInLevel[r]; p++) { + for (; c < componentsCount; c++) { + var component = tile.components[c]; + if (r > component.codingStyleParameters.decompositionLevelsCount) { + continue; + } + var resolution = component.resolutions[r]; + var numprecincts = resolution.precinctParameters.numprecincts; + if (p >= numprecincts) { + continue; + } + for (; l < layersCount;) { + var packet = createPacket(resolution, p, l); + l++; + return packet; + } + l = 0; + } + c = 0; + } + p = 0; + } + throw new Error('JPX Error: Out of packets'); + }; + } + function PositionComponentResolutionLayerIterator(context) { + var siz = context.SIZ; + var tileIndex = context.currentTile.index; + var tile = context.tiles[tileIndex]; + var layersCount = tile.codingStyleDefaultParameters.layersCount; + var componentsCount = siz.Csiz; + var precinctsSizes = getPrecinctSizesInImageScale(tile); + var precinctsIterationSizes = precinctsSizes; + var l = 0, r = 0, c = 0, px = 0, py = 0; + + this.nextPacket = function JpxImage_nextPacket() { + // Section B.12.1.4 Position-component-resolution-layer + for (; py < precinctsIterationSizes.maxNumHigh; py++) { + for (; px < precinctsIterationSizes.maxNumWide; px++) { + for (; c < componentsCount; c++) { + var component = tile.components[c]; + var decompositionLevelsCount = + component.codingStyleParameters.decompositionLevelsCount; + for (; r <= decompositionLevelsCount; r++) { + var resolution = component.resolutions[r]; + var sizeInImageScale = + precinctsSizes.components[c].resolutions[r]; + var k = getPrecinctIndexIfExist( + px, + py, + sizeInImageScale, + precinctsIterationSizes, + resolution); + if (k === null) { + continue; + } + for (; l < layersCount;) { + var packet = createPacket(resolution, k, l); + l++; + return packet; + } + l = 0; + } + r = 0; + } + c = 0; + } + px = 0; + } + throw new Error('JPX Error: Out of packets'); + }; + } + function ComponentPositionResolutionLayerIterator(context) { + var siz = context.SIZ; + var tileIndex = context.currentTile.index; + var tile = context.tiles[tileIndex]; + var layersCount = tile.codingStyleDefaultParameters.layersCount; + var componentsCount = siz.Csiz; + var precinctsSizes = getPrecinctSizesInImageScale(tile); + var l = 0, r = 0, c = 0, px = 0, py = 0; + + this.nextPacket = function JpxImage_nextPacket() { + // Section B.12.1.5 Component-position-resolution-layer + for (; c < componentsCount; ++c) { + var component = tile.components[c]; + var precinctsIterationSizes = precinctsSizes.components[c]; + var decompositionLevelsCount = + component.codingStyleParameters.decompositionLevelsCount; + for (; py < precinctsIterationSizes.maxNumHigh; py++) { + for (; px < precinctsIterationSizes.maxNumWide; px++) { + for (; r <= decompositionLevelsCount; r++) { + var resolution = component.resolutions[r]; + var sizeInImageScale = precinctsIterationSizes.resolutions[r]; + var k = getPrecinctIndexIfExist( + px, + py, + sizeInImageScale, + precinctsIterationSizes, + resolution); + if (k === null) { + continue; + } + for (; l < layersCount;) { + var packet = createPacket(resolution, k, l); + l++; + return packet; + } + l = 0; + } + r = 0; + } + px = 0; + } + py = 0; + } + throw new Error('JPX Error: Out of packets'); + }; + } + function getPrecinctIndexIfExist( + pxIndex, pyIndex, sizeInImageScale, precinctIterationSizes, resolution) { + var posX = pxIndex * precinctIterationSizes.minWidth; + var posY = pyIndex * precinctIterationSizes.minHeight; + if (posX % sizeInImageScale.width !== 0 || + posY % sizeInImageScale.height !== 0) { + return null; + } + var startPrecinctRowIndex = + (posY / sizeInImageScale.width) * + resolution.precinctParameters.numprecinctswide; + return (posX / sizeInImageScale.height) + startPrecinctRowIndex; + } + function getPrecinctSizesInImageScale(tile) { + var componentsCount = tile.components.length; + var minWidth = Number.MAX_VALUE; + var minHeight = Number.MAX_VALUE; + var maxNumWide = 0; + var maxNumHigh = 0; + var sizePerComponent = new Array(componentsCount); + for (var c = 0; c < componentsCount; c++) { + var component = tile.components[c]; + var decompositionLevelsCount = + component.codingStyleParameters.decompositionLevelsCount; + var sizePerResolution = new Array(decompositionLevelsCount + 1); + var minWidthCurrentComponent = Number.MAX_VALUE; + var minHeightCurrentComponent = Number.MAX_VALUE; + var maxNumWideCurrentComponent = 0; + var maxNumHighCurrentComponent = 0; + var scale = 1; + for (var r = decompositionLevelsCount; r >= 0; --r) { + var resolution = component.resolutions[r]; + var widthCurrentResolution = + scale * resolution.precinctParameters.precinctWidth; + var heightCurrentResolution = + scale * resolution.precinctParameters.precinctHeight; + minWidthCurrentComponent = Math.min( + minWidthCurrentComponent, + widthCurrentResolution); + minHeightCurrentComponent = Math.min( + minHeightCurrentComponent, + heightCurrentResolution); + maxNumWideCurrentComponent = Math.max(maxNumWideCurrentComponent, + resolution.precinctParameters.numprecinctswide); + maxNumHighCurrentComponent = Math.max(maxNumHighCurrentComponent, + resolution.precinctParameters.numprecinctshigh); + sizePerResolution[r] = { + width: widthCurrentResolution, + height: heightCurrentResolution + }; + scale <<= 1; + } + minWidth = Math.min(minWidth, minWidthCurrentComponent); + minHeight = Math.min(minHeight, minHeightCurrentComponent); + maxNumWide = Math.max(maxNumWide, maxNumWideCurrentComponent); + maxNumHigh = Math.max(maxNumHigh, maxNumHighCurrentComponent); + sizePerComponent[c] = { + resolutions: sizePerResolution, + minWidth: minWidthCurrentComponent, + minHeight: minHeightCurrentComponent, + maxNumWide: maxNumWideCurrentComponent, + maxNumHigh: maxNumHighCurrentComponent + }; + } + return { + components: sizePerComponent, + minWidth: minWidth, + minHeight: minHeight, + maxNumWide: maxNumWide, + maxNumHigh: maxNumHigh + }; + } function buildPackets(context) { var siz = context.SIZ; var tileIndex = context.currentTile.index; @@ -35756,6 +36138,7 @@ var JpxImage = (function JpxImageClosure() { resolution.try0 = Math.ceil(component.tcy0 / scale); resolution.trx1 = Math.ceil(component.tcx1 / scale); resolution.try1 = Math.ceil(component.tcy1 / scale); + resolution.resLevel = r; buildPrecincts(context, resolution, blocksDimensions); resolutions.push(resolution); @@ -35826,6 +36209,18 @@ var JpxImage = (function JpxImageClosure() { tile.packetsIterator = new ResolutionLayerComponentPositionIterator(context); break; + case 2: + tile.packetsIterator = + new ResolutionPositionComponentLayerIterator(context); + break; + case 3: + tile.packetsIterator = + new PositionComponentResolutionLayerIterator(context); + break; + case 4: + tile.packetsIterator = + new ComponentPositionResolutionLayerIterator(context); + break; default: throw new Error('JPX Error: Unsupported progression order ' + progressionOrder); @@ -35853,6 +36248,21 @@ var JpxImage = (function JpxImageClosure() { bufferSize -= count; return (buffer >>> bufferSize) & ((1 << count) - 1); } + function skipMarkerIfEqual(value) { + if (data[offset + position - 1] === 0xFF && + data[offset + position] === value) { + skipBytes(1); + return true; + } else if (data[offset + position] === 0xFF && + data[offset + position + 1] === value) { + skipBytes(2); + return true; + } + return false; + } + function skipBytes(count) { + position += count; + } function alignToByte() { bufferSize = 0; if (skipNextBit) { @@ -35880,11 +36290,17 @@ var JpxImage = (function JpxImageClosure() { } var tileIndex = context.currentTile.index; var tile = context.tiles[tileIndex]; + var sopMarkerUsed = context.COD.sopMarkerUsed; + var ephMarkerUsed = context.COD.ephMarkerUsed; var packetsIterator = tile.packetsIterator; while (position < dataLength) { + alignToByte(); + if (sopMarkerUsed && skipMarkerIfEqual(0x91)) { + // Skip also marker segment length and packet sequence ID + skipBytes(4); + } var packet = packetsIterator.nextPacket(); if (!readBits(1)) { - alignToByte(); continue; } var layerNumber = packet.layerNumber; @@ -35897,13 +36313,13 @@ var JpxImage = (function JpxImageClosure() { var codeblockIncluded = false; var firstTimeInclusion = false; var valueReady; - if ('included' in codeblock) { + if (codeblock['included'] !== undefined) { codeblockIncluded = !!readBits(1); } else { // reading inclusion tree precinct = codeblock.precinct; var inclusionTree, zeroBitPlanesTree; - if ('inclusionTree' in precinct) { + if (precinct['inclusionTree'] !== undefined) { inclusionTree = precinct.inclusionTree; } else { // building inclusion and zero bit-planes trees @@ -35965,10 +36381,13 @@ var JpxImage = (function JpxImageClosure() { }); } alignToByte(); + if (ephMarkerUsed) { + skipMarkerIfEqual(0x92); + } while (queue.length > 0) { var packetItem = queue.shift(); codeblock = packetItem.codeblock; - if (!('data' in codeblock)) { + if (codeblock['data'] === undefined) { codeblock.data = []; } codeblock.data.push({ @@ -35998,7 +36417,7 @@ var JpxImage = (function JpxImageClosure() { if (blockWidth === 0 || blockHeight === 0) { continue; } - if (!('data' in codeblock)) { + if (codeblock['data'] === undefined) { continue; } @@ -36253,10 +36672,10 @@ var JpxImage = (function JpxImageClosure() { var tile = context.tiles[tileIndex]; for (var c = 0; c < componentsCount; c++) { var component = tile.components[c]; - var qcdOrQcc = (c in context.currentTile.QCC ? + var qcdOrQcc = (context.currentTile.QCC[c] !== undefined ? context.currentTile.QCC[c] : context.currentTile.QCD); component.quantizationParameters = qcdOrQcc; - var codOrCoc = (c in context.currentTile.COC ? + var codOrCoc = (context.currentTile.COC[c] !== undefined ? context.currentTile.COC[c] : context.currentTile.COD); component.codingStyleParameters = codOrCoc; } @@ -36285,7 +36704,7 @@ var JpxImage = (function JpxImageClosure() { while (currentLevel < this.levels.length) { level = this.levels[currentLevel]; var index = i + j * level.width; - if (index in level.items) { + if (level.items[index] !== undefined) { value = level.items[index]; break; }