diff --git a/src/core/evaluator.js b/src/core/evaluator.js index 500971257..bda043091 100644 --- a/src/core/evaluator.js +++ b/src/core/evaluator.js @@ -1865,7 +1865,9 @@ var OperatorList = (function OperatorListClosure() { }, flush: function(lastChunk) { - new QueueOptimizer().optimize(this); + if (this.intent !== 'oplist') { + new QueueOptimizer().optimize(this); + } var transfers = getTransfers(this); this.messageHandler.send('RenderPageChunk', { operatorList: { diff --git a/src/display/svg.js b/src/display/svg.js index 0522f1c11..47469154a 100644 --- a/src/display/svg.js +++ b/src/display/svg.js @@ -15,7 +15,7 @@ * limitations under the License. */ /* globals PDFJS, FONT_IDENTITY_MATRIX, IDENTITY_MATRIX, - isNum, OPS, Promise, Util, warn */ + isNum, OPS, Promise, Util, warn, ImageKind, PDFJS */ 'use strict'; @@ -29,6 +29,201 @@ function createScratchSVG(width, height) { return svg; } +var convertImgDataToPng = (function convertImgDataToPngClosure() { + var PNG_HEADER = + new Uint8Array([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]); + + var CHUNK_WRAPPER_SIZE = 12; + + var crcTable = new Int32Array(256); + for (var i = 0; i < 256; i++) { + var c = i; + for (var h = 0; h < 8; h++) { + if (c & 1) { + c = 0xedB88320 ^ ((c >> 1) & 0x7fffffff); + } else { + c = (c >> 1) & 0x7fffffff; + } + } + crcTable[i] = c; + } + + function crc32(data, start, end) { + var crc = -1; + for (var i = start; i < end; i++) { + var a = (crc ^ data[i]) & 0xff; + var b = crcTable[a]; + crc = (crc >>> 8) ^ b; + } + return crc ^ -1; + } + + function writePngChunk(type, body, data, offset) { + var p = offset; + + var len = body.length; + + data[p] = len >> 24 & 0xff; + data[p + 1] = len >> 16 & 0xff; + data[p + 2] = len >> 8 & 0xff; + data[p + 3] = len & 0xff; + p += 4; + + data[p] = type.charCodeAt(0) & 0xff; + data[p + 1] = type.charCodeAt(1) & 0xff; + data[p + 2] = type.charCodeAt(2) & 0xff; + data[p + 3] = type.charCodeAt(3) & 0xff; + p += 4; + + data.set(body, p); + p += body.length; + + var crc = crc32(data, offset + 4, p); + + data[p] = crc >> 24 & 0xff; + data[p + 1] = crc >> 16 & 0xff; + data[p + 2] = crc >> 8 & 0xff; + data[p + 3] = crc & 0xff; + } + + function adler32(data, start, end) { + var a = 1; + var b = 0; + for (var i = start; i < end; ++i) { + a = (a + (data[i] & 0xff)) % 65521; + b = (b + a) % 65521; + } + return (b << 16) | a; + } + + function encode(imgData, kind) { + var width = imgData.width; + var height = imgData.height; + var bitDepth; + var colorType; + + var bytes = imgData.data; + var lineSize; + switch (kind) { + case ImageKind.GRAYSCALE_1BPP: + colorType = 0; + bitDepth = 1; + lineSize = (width + 7) >> 3; + break; + case ImageKind.RGB_24BPP: + colorType = 2; + bitDepth = 8; + lineSize = width * 3; + break; + case ImageKind.RGBA_32BPP: + colorType = 6; + bitDepth = 8; + lineSize = width * 4; + break; + default: + throw new Error('invalid format'); + } + + // prefix every row with predictor 0 + var literals = new Uint8Array((1 + lineSize) * height); + var offsetLiterals = 0, offsetBytes = 0; + var y, i; + for (y = 0; y < height; ++y) { + literals[offsetLiterals++] = 0; // no prediction + literals.set(bytes.subarray(offsetBytes, offsetBytes + lineSize), + offsetLiterals); + offsetBytes += lineSize; + offsetLiterals += lineSize; + } + + if (kind === ImageKind.GRAYSCALE_1BPP) { + // inverting for B/W + offsetLiterals = 0; + for (y = 0; y < height; y++) { + offsetLiterals++; // skipping predictor + for (i = 0; i < lineSize; i++) { + literals[offsetLiterals++] ^= 0xFF; + } + } + } + + var ihdr = new Uint8Array([ + width >> 24 & 0xff, + width >> 16 & 0xff, + width >> 8 & 0xff, + width & 0xff, + height >> 24 & 0xff, + height >> 16 & 0xff, + height >> 8 & 0xff, + height & 0xff, + bitDepth, // bit depth + colorType, // color type + 0x00, // compression method + 0x00, // filter method + 0x00 // interlace method + ]); + + var len = literals.length; + var maxBlockLength = 0xFFFF; + + var deflateBlocks = Math.ceil(len / maxBlockLength); + var idat = new Uint8Array(2 + len + deflateBlocks * 5 + 4); + var pi = 0; + idat[pi++] = 0x78; // compression method and flags + idat[pi++] = 0x9c; // flags + + var pos = 0; + while (len > maxBlockLength) { + // writing non-final DEFLATE blocks type 0 and length of 65535 + idat[pi++] = 0x00; + idat[pi++] = 0xff; + idat[pi++] = 0xff; + idat[pi++] = 0x00; + idat[pi++] = 0x00; + idat.set(literals.subarray(pos, pos + maxBlockLength), pi); + pi += maxBlockLength; + pos += maxBlockLength; + len -= maxBlockLength; + } + + // writing non-final DEFLATE blocks type 0 + idat[pi++] = 0x01; + idat[pi++] = len & 0xff; + idat[pi++] = len >> 8 & 0xff; + idat[pi++] = (~len & 0xffff) & 0xff; + idat[pi++] = (~len & 0xffff) >> 8 & 0xff; + idat.set(literals.subarray(pos), pi); + pi += literals.length - pos; + + var adler = adler32(literals, 0, literals.length); // checksum + idat[pi++] = adler >> 24 & 0xff; + idat[pi++] = adler >> 16 & 0xff; + idat[pi++] = adler >> 8 & 0xff; + idat[pi++] = adler & 0xff; + + // PNG will consists: header, IHDR+data, IDAT+data, and IEND. + var pngLength = PNG_HEADER.length + CHUNK_WRAPPER_SIZE * 3 + + ihdr.length + idat.length; + var data = new Uint8Array(pngLength); + var offset = 0; + data.set(PNG_HEADER, offset); + offset += PNG_HEADER.length; + writePngChunk('IHDR', ihdr, data, offset); + offset += CHUNK_WRAPPER_SIZE + ihdr.length; + writePngChunk('IDATA', idat, data, offset); + offset += CHUNK_WRAPPER_SIZE + idat.length; + writePngChunk('IEND', new Uint8Array(0), data, offset); + + return PDFJS.createObjectURL(data, 'image/png'); + } + + return function convertImgDataToPng(imgData) { + var kind = (imgData.kind === undefined ? + ImageKind.GRAYSCALE_1BPP : imgData.kind); + return encode(imgData, kind); + }; +})(); + var SVGExtraState = (function SVGExtraStateClosure() { function SVGExtraState(old) { // Are soft masks and alpha values shapes or opacities? @@ -70,6 +265,8 @@ var SVGExtraState = (function SVGExtraStateClosure() { // Clipping this.clipId = ''; this.pendingClip = false; + + this.maskId = ''; } SVGExtraState.prototype = { @@ -109,7 +306,6 @@ function opListToTree(opList) { return opTree; } - var SVGGraphics = (function SVGGraphicsClosure(ctx) { function SVGGraphics(commonObjs, objs) { @@ -130,6 +326,7 @@ var SVGGraphics = (function SVGGraphicsClosure(ctx) { var NORMAL_CLIP = {}; var EO_CLIP = {}; var clipCount = 0; + var maskCount = 0; SVGGraphics.prototype = { save: function SVGGraphics_save() { @@ -211,7 +408,8 @@ var SVGGraphics = (function SVGGraphicsClosure(ctx) { this.pgrp.appendChild(this.tgrp); this.svg.appendChild(this.pgrp); this.container.appendChild(this.svg); - this.convertOpList(operatorList); + var opTree = this.convertOpList(operatorList); + this.executeOpTree(opTree); }, convertOpList: function SVGGraphics_convertOpList(operatorList) { @@ -233,8 +431,6 @@ var SVGGraphics = (function SVGGraphicsClosure(ctx) { opList.push({'fnId' : fnId, 'fn': REVOPS[fnId], 'args': argsArray[x]}); } opTree = opListToTree(opList); - - this.executeOpTree(opTree); return opTree; }, @@ -331,6 +527,15 @@ var SVGGraphics = (function SVGGraphicsClosure(ctx) { case OPS.paintJpegXObject: this.paintJpegXObject(args[0], args[1], args[2]); break; + case OPS.paintImageXObject: + this.paintImageXObject(args[0]); + break; + case OPS.paintInlineImageXObject: + this.paintInlineImageXObject(args[0]); + break; + case OPS.paintImageMaskXObject: + this.paintImageMaskXObject(args[0]); + break; case OPS.closePath: this.closePath(); break; @@ -635,6 +840,7 @@ var SVGGraphics = (function SVGGraphicsClosure(ctx) { endPath: function SVGGraphics_endPath() { var current = this.current; if (current.pendingClip) { + this.cgrp.appendChild(this.tgrp); this.pgrp.appendChild(this.cgrp); } else { this.pgrp.appendChild(this.tgrp); @@ -657,6 +863,8 @@ var SVGGraphics = (function SVGGraphicsClosure(ctx) { } else { clipElement.setAttributeNS(null, 'clip-rule', 'nonzero'); } + this.clippath.setAttributeNS(null, 'transform', + 'matrix(' + this.transformMatrix + ')'); this.clippath.appendChild(clipElement); this.defs.appendChild(this.clippath); @@ -779,26 +987,97 @@ var SVGGraphics = (function SVGGraphicsClosure(ctx) { this.tgrp.appendChild(rect); }, - paintJpegXObject: - function SVGGraphics_paintJpegXObject(objId, w, h) { - var current = this.current; - var imgObj = this.objs.get(objId); - var imgEl = document.createElementNS(NS, 'svg:image'); - imgEl.setAttributeNS(XLINK_NS, 'href', imgObj.src); - imgEl.setAttributeNS(null, 'width', imgObj.width + 'px'); - imgEl.setAttributeNS(null, 'height', imgObj.height + 'px'); - imgEl.setAttributeNS(null, 'x', 0); - imgEl.setAttributeNS(null, 'y', -h); - imgEl.setAttributeNS(null, 'transform', 'scale(' + 1 / w + - ' ' + -1 / h + ')'); + paintJpegXObject: function SVGGraphics_paintJpegXObject(objId, w, h) { + var current = this.current; + var imgObj = this.objs.get(objId); + var imgEl = document.createElementNS(NS, 'svg:image'); + imgEl.setAttributeNS(XLINK_NS, 'href', imgObj.src); + imgEl.setAttributeNS(null, 'width', imgObj.width + 'px'); + imgEl.setAttributeNS(null, 'height', imgObj.height + 'px'); + imgEl.setAttributeNS(null, 'x', 0); + imgEl.setAttributeNS(null, 'y', -h); + imgEl.setAttributeNS(null, 'transform', 'scale(' + 1 / w + + ' ' + -1 / h + ')'); - this.tgrp.appendChild(imgEl); - if (current.pendingClip) { + this.tgrp.appendChild(imgEl); + if (current.pendingClip) { + this.cgrp.appendChild(this.tgrp); + this.pgrp.appendChild(this.cgrp); + } else { + this.pgrp.appendChild(this.tgrp); + } + }, + + paintImageXObject: function SVGGraphics_paintImageXObject(objId) { + var imgData = this.objs.get(objId); + if (!imgData) { + warn('Dependent image isn\'t ready yet'); + return; + } + + this.paintInlineImageXObject(imgData); + }, + + paintInlineImageXObject: + function SVGGraphics_paintInlineImageXObject(imgData, mask) { + var current = this.current; + var width = imgData.width; + var height = imgData.height; + + var imgSrc = convertImgDataToPng(imgData); + var cliprect = document.createElementNS(NS, 'svg:rect'); + cliprect.setAttributeNS(null, 'x', 0); + cliprect.setAttributeNS(null, 'y', 0); + cliprect.setAttributeNS(null, 'width', width); + cliprect.setAttributeNS(null, 'height', height); + current.element = cliprect; + this.clip('nonzero'); + var imgEl = document.createElementNS(NS, 'svg:image'); + imgEl.setAttributeNS(XLINK_NS, 'href', imgSrc); + imgEl.setAttributeNS(null, 'x', 0); + imgEl.setAttributeNS(null, 'y', -height); + imgEl.setAttributeNS(null, 'width', width + 'px'); + imgEl.setAttributeNS(null, 'height', height + 'px'); + imgEl.setAttributeNS(null, 'transform', 'scale(' + (1 / width) + + ', ' + (-1 / height) + ')'); + if (mask) { + mask.appendChild(imgEl); + } else { + this.tgrp.appendChild(imgEl); + } + if (current.pendingClip) { this.cgrp.appendChild(this.tgrp); this.pgrp.appendChild(this.cgrp); - } else { + } else { this.pgrp.appendChild(this.tgrp); - } + } + }, + + paintImageMaskXObject: + function SVGGraphics_paintImageMaskXObject(imgData) { + var current = this.current; + + var width = imgData.width; + var height = imgData.height; + + var img = convertImgDataToPng(imgData); + var fillColor = current.fillColor; + + current.maskId = 'mask' + maskCount++; + var mask = document.createElementNS(NS, 'svg:mask'); + mask.setAttributeNS(null, 'id', current.maskId); + + var rect = document.createElementNS(NS, 'svg:rect'); + rect.setAttributeNS(null, 'x', 0); + rect.setAttributeNS(null, 'y', 0); + rect.setAttributeNS(null, 'width', width); + rect.setAttributeNS(null, 'height', height); + rect.setAttributeNS(null, 'fill', fillColor); + rect.setAttributeNS(null, 'mask', 'url(#' + current.maskId +')'); + this.defs.appendChild(mask); + this.tgrp.appendChild(rect); + + this.paintInlineImageXObject(imgData, mask); }, }; return SVGGraphics;