Merge pull request #5162 from yurydelendik/pramodhkp-fixupgstate2

[SVG] Reduces amount of used memory during PNG creation.
This commit is contained in:
Yury Delendik 2014-08-09 15:56:11 -05:00
commit 99b08ed223
2 changed files with 303 additions and 22 deletions

View File

@ -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: {

View File

@ -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;