Removes "too many inline images" limit

This commit is contained in:
Yury Delendik 2012-12-07 12:19:43 -06:00
parent 0910c5e68e
commit d71c702dcf
5 changed files with 230 additions and 53 deletions

View File

@ -224,6 +224,27 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
}
}
function applyStencilMask(imgArray, width, height, inverseDecode, buffer) {
var imgArrayPos = 0;
var i, j, mask, buf;
// removing making non-masked pixels transparent
var bufferPos = 3; // alpha component offset
for (i = 0; i < height; i++) {
mask = 0;
for (j = 0; j < width; j++) {
if (!mask) {
buf = imgArray[imgArrayPos++];
mask = 128;
}
if (!(buf & mask) == inverseDecode) {
buffer[bufferPos] = 0;
}
bufferPos += 4;
mask >>= 1;
}
}
}
function rescaleImage(pixels, width, height, widthScale, heightScale) {
var scaledWidth = Math.ceil(width / widthScale);
var scaledHeight = Math.ceil(height / heightScale);
@ -1213,44 +1234,58 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
paintImageMaskXObject: function CanvasGraphics_paintImageMaskXObject(
imgArray, inverseDecode, width, height) {
function applyStencilMask(buffer, inverseDecode) {
var imgArrayPos = 0;
var i, j, mask, buf;
// removing making non-masked pixels transparent
var bufferPos = 3; // alpha component offset
for (i = 0; i < height; i++) {
mask = 0;
for (j = 0; j < width; j++) {
if (!mask) {
buf = imgArray[imgArrayPos++];
mask = 128;
}
if (!(buf & mask) == inverseDecode) {
buffer[bufferPos] = 0;
}
bufferPos += 4;
mask >>= 1;
}
}
}
var w = width, h = height;
var tmpCanvas = createScratchCanvas(w, h);
var ctx = this.ctx;
var tmpCanvas = createScratchCanvas(width, height);
var tmpCtx = tmpCanvas.getContext('2d');
var fillColor = this.current.fillColor;
tmpCtx.fillStyle = (fillColor && fillColor.hasOwnProperty('type') &&
fillColor.type === 'Pattern') ?
fillColor.getPattern(tmpCtx) : fillColor;
tmpCtx.fillRect(0, 0, width, height);
var imgData = tmpCtx.getImageData(0, 0, width, height);
var pixels = imgData.data;
applyStencilMask(imgArray, width, height, inverseDecode, pixels);
this.paintInlineImageXObject(imgData);
},
paintImageMaskXObjectGroup:
function CanvasGraphics_paintImageMaskXObjectGroup(images) {
var ctx = this.ctx;
var tmpCanvasWidth = 0, tmpCanvasHeight = 0, tmpCanvas, tmpCtx;
for (var i = 0, ii = images.length; i < ii; i++) {
var image = images[i];
var w = image.width, h = image.height;
if (w > tmpCanvasWidth || h > tmpCanvasHeight) {
tmpCanvasWidth = Math.max(w, tmpCanvasWidth);
tmpCanvasHeight = Math.max(h, tmpCanvasHeight);
tmpCanvas = createScratchCanvas(tmpCanvasWidth, tmpCanvasHeight);
tmpCtx = tmpCanvas.getContext('2d');
var fillColor = this.current.fillColor;
tmpCtx.fillStyle = (fillColor && fillColor.hasOwnProperty('type') &&
fillColor.type === 'Pattern') ?
fillColor.getPattern(tmpCtx) : fillColor;
}
tmpCtx.fillRect(0, 0, w, h);
var imgData = tmpCtx.getImageData(0, 0, w, h);
var pixels = imgData.data;
applyStencilMask(pixels, inverseDecode);
applyStencilMask(image.data, w, h, image.inverseDecode, pixels);
this.paintImage(imgData);
tmpCtx.putImageData(imgData, 0, 0);
ctx.save();
ctx.transform.apply(ctx, image.transform);
ctx.scale(1, -1);
ctx.drawImage(tmpCanvas, 0, 0, w, h,
0, -1, 1, 1);
ctx.restore();
}
},
paintImageXObject: function CanvasGraphics_paintImageXObject(objId) {
@ -1258,10 +1293,11 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
if (!imgData)
error('Dependent image isn\'t ready yet');
this.paintImage(imgData);
this.paintInlineImageXObject(imgData);
},
paintImage: function CanvasGraphics_paintImage(imgData) {
paintInlineImageXObject:
function CanvasGraphics_paintInlineImageXObject(imgData) {
var width = imgData.width;
var height = imgData.height;
var ctx = this.ctx;
@ -1294,6 +1330,27 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
this.restore();
},
paintInlineImageXObjectGroup:
function CanvasGraphics_paintInlineImageXObjectGroup(imgData, map) {
var ctx = this.ctx;
var w = imgData.width;
var h = imgData.height;
var tmpCanvas = createScratchCanvas(w, h);
var tmpCtx = tmpCanvas.getContext('2d');
this.putBinaryImageData(tmpCtx, imgData);
for (var i = 0, ii = map.length; i < ii; i++) {
var entry = map[i];
ctx.save();
ctx.transform.apply(ctx, entry.transform);
ctx.scale(1, -1);
ctx.drawImage(tmpCanvas, entry.x, entry.y, entry.w, entry.h,
0, -1, 1, 1);
ctx.restore();
}
},
putBinaryImageData: function CanvasGraphics_putBinaryImageData(ctx,
imgData) {
var w = imgData.width, h = imgData.height;

View File

@ -260,15 +260,28 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
return;
}
var softMask = dict.get('SMask', 'SM') || false;
var mask = dict.get('Mask') || false;
var SMALL_IMAGE_DIMENSIONS = 200;
// Inlining small images into the queue as RGB data
if (inline && !softMask && !mask &&
!(image instanceof JpegStream) &&
(w + h) < SMALL_IMAGE_DIMENSIONS) {
var imageObj = new PDFImage(xref, resources, image,
inline, null, null);
var imgData = imageObj.getImageData();
fn = 'paintInlineImageXObject';
args = [imgData];
return;
}
// If there is no imageMask, create the PDFImage and a lot
// of image processing can be done here.
var objId = 'img_' + uniquePrefix + (++self.objIdCounter);
insertDependency([objId]);
args = [objId, w, h];
var softMask = dict.get('SMask', 'SM') || false;
var mask = dict.get('Mask') || false;
if (!softMask && !mask && image instanceof JpegStream &&
image.isNativelySupported(xref, resources)) {
// These JPEGs don't need any more processing so we can just send it.
@ -280,19 +293,121 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
fn = 'paintImageXObject';
PDFImage.buildImage(function(imageObj) {
var drawWidth = imageObj.drawWidth;
var drawHeight = imageObj.drawHeight;
var imgData = {
width: drawWidth,
height: drawHeight,
data: new Uint8Array(drawWidth * drawHeight * 4)
};
var pixels = imgData.data;
imageObj.fillRgbaBuffer(pixels, drawWidth, drawHeight);
var imgData = imageObj.getImageData();
handler.send('obj', [objId, pageIndex, 'Image', imgData]);
}, handler, xref, resources, image, inline);
}
function optimizeQueue() {
// grouping paintInlineImageXObject's into paintInlineImageXObjectGroup
// searching for (save, transform, paintInlineImageXObject, restore)+
var MIN_IMAGES_COUNT = 10;
var MAX_WIDTH = 1000;
var IMAGE_PADDING = 1;
for (var i = 0, ii = fnArray.length; i < ii; i++) {
if (fnArray[i] === 'paintInlineImageXObject' &&
fnArray[i - 2] === 'save' && fnArray[i - 1] === 'transform' &&
fnArray[i + 1] === 'restore') {
var j = i - 2;
for (i += 2; i < ii && fnArray[i - 4] === fnArray[i]; i++) {
}
var count = (i - j) >> 2;
if (count < MIN_IMAGES_COUNT) {
continue;
}
// assuming that heights of those image is too small (~1 pixel)
// packing as much as possible by lines
var maxX = 0;
var map = [], maxLineHeight = 0;
var currentX = IMAGE_PADDING, currentY = IMAGE_PADDING;
for (var q = 0; q < count; q++) {
var transform = argsArray[j + (q << 2) + 1];
var img = argsArray[j + (q << 2) + 2][0];
if (currentX + img.width > MAX_WIDTH) {
// starting new line
maxX = Math.max(maxX, currentX);
currentY += maxLineHeight + 2 * IMAGE_PADDING;
currentX = 0;
maxLineHeight = 0;
}
map.push({
transform: transform,
x: currentX, y: currentY,
w: img.width, h: img.height
});
currentX += img.width + 2 * IMAGE_PADDING;
maxLineHeight = Math.max(maxLineHeight, img.height);
}
var imgWidth = Math.max(maxX, currentX) + IMAGE_PADDING;
var imgHeight = currentY + maxLineHeight + IMAGE_PADDING;
var imgData = new Uint8Array(imgWidth * imgHeight * 4);
var imgRowSize = imgWidth << 2;
for (var q = 0; q < count; q++) {
var data = argsArray[j + (q << 2) + 2][0].data;
// copy image by lines and extends pixels into padding
var rowSize = map[q].w << 2;
var dataOffset = 0;
var offset = (map[q].x + map[q].y * imgWidth) << 2;
imgData.set(
data.subarray(0, rowSize), offset - imgRowSize);
for (var k = 0, kk = map[q].h; k < kk; k++) {
imgData.set(
data.subarray(dataOffset, dataOffset + rowSize), offset);
dataOffset += rowSize;
offset += imgRowSize;
}
imgData.set(
data.subarray(dataOffset - rowSize, dataOffset), offset);
while (offset >= 0) {
data[offset - 4] = data[offset];
data[offset - 3] = data[offset + 1];
data[offset - 2] = data[offset + 2];
data[offset - 1] = data[offset + 3];
data[offset + rowSize] = data[offset + rowSize - 4];
data[offset + rowSize + 1] = data[offset + rowSize - 3];
data[offset + rowSize + 2] = data[offset + rowSize - 2];
data[offset + rowSize + 3] = data[offset + rowSize - 1];
offset -= imgRowSize;
}
}
// replacing queue items
fnArray.splice(j, count * 4, ['paintInlineImageXObjectGroup']);
argsArray.splice(j, count * 4,
[{width: imgWidth, height: imgHeight, data: imgData}, map]);
i = j;
ii = fnArray.length;
}
}
// grouping paintImageMaskXObject's into paintImageMaskXObjectGroup
// searching for (save, transform, paintImageMaskXObject, restore)+
for (var i = 0, ii = fnArray.length; i < ii; i++) {
if (fnArray[i] === 'paintImageMaskXObject' &&
fnArray[i - 2] === 'save' && fnArray[i - 1] === 'transform' &&
fnArray[i + 1] === 'restore') {
var j = i - 2;
for (i += 2; i < ii && fnArray[i - 4] === fnArray[i]; i++) {
}
var count = (i - j) >> 2;
if (count < MIN_IMAGES_COUNT) {
continue;
}
var images = [];
for (var q = 0; q < count; q++) {
var transform = argsArray[j + (q << 2) + 1];
var maskParams = argsArray[j + (q << 2) + 2];
images.push({data: maskParams[0], width: maskParams[2],
height: maskParams[3], transform: transform,
inverseDecode: maskParams[1]});
}
// replacing queue items
fnArray.splice(j, count * 4, ['paintImageMaskXObjectGroup']);
argsArray.splice(j, count * 4, [images]);
i = j;
ii = fnArray.length;
}
}
}
if (!queue)
queue = {};
@ -509,6 +624,8 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
}
}
optimizeQueue();
return queue;
},

View File

@ -433,6 +433,18 @@ var PDFImage = (function PDFImageClosure() {
for (var i = 0; i < length; ++i)
buffer[i] = (scale * comps[i]) | 0;
},
getImageData: function PDFImage_getImageData() {
var drawWidth = this.drawWidth;
var drawHeight = this.drawHeight;
var imgData = {
width: drawWidth,
height: drawHeight,
data: new Uint8Array(drawWidth * drawHeight * 4)
};
var pixels = imgData.data;
this.fillRgbaBuffer(pixels, drawWidth, drawHeight);
return imgData;
},
getImageBytes: function PDFImage_getImageBytes(length) {
this.image.reset();
return this.image.getBytes(length);

View File

@ -28,7 +28,6 @@ var Parser = (function ParserClosure() {
this.lexer = lexer;
this.allowStreams = allowStreams;
this.xref = xref;
this.inlineImg = 0;
this.refill();
}
@ -153,15 +152,6 @@ var Parser = (function ParserClosure() {
}
}
// TODO improve the small images performance to remove the limit
var inlineImgLimit = 500;
if (++this.inlineImg >= inlineImgLimit) {
if (this.inlineImg === inlineImgLimit)
warn('Too many inline images');
this.shift();
return null;
}
var length = (stream.pos - 4) - startPos;
var imageStream = stream.makeSubStream(startPos, length, dict);
if (cipherTransform)

View File

@ -19,7 +19,8 @@
var Stream = (function StreamClosure() {
function Stream(arrayBuffer, start, length, dict) {
this.bytes = new Uint8Array(arrayBuffer);
this.bytes = arrayBuffer instanceof Uint8Array ? arrayBuffer :
new Uint8Array(arrayBuffer);
this.start = start || 0;
this.pos = this.start;
this.end = (start + length) || this.bytes.length;