Removes "too many inline images" limit
This commit is contained in:
parent
0910c5e68e
commit
d71c702dcf
117
src/canvas.js
117
src/canvas.js
@ -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) {
|
function rescaleImage(pixels, width, height, widthScale, heightScale) {
|
||||||
var scaledWidth = Math.ceil(width / widthScale);
|
var scaledWidth = Math.ceil(width / widthScale);
|
||||||
var scaledHeight = Math.ceil(height / heightScale);
|
var scaledHeight = Math.ceil(height / heightScale);
|
||||||
@ -1213,44 +1234,58 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
|
|||||||
|
|
||||||
paintImageMaskXObject: function CanvasGraphics_paintImageMaskXObject(
|
paintImageMaskXObject: function CanvasGraphics_paintImageMaskXObject(
|
||||||
imgArray, inverseDecode, width, height) {
|
imgArray, inverseDecode, width, height) {
|
||||||
function applyStencilMask(buffer, inverseDecode) {
|
var ctx = this.ctx;
|
||||||
var imgArrayPos = 0;
|
var tmpCanvas = createScratchCanvas(width, height);
|
||||||
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 tmpCtx = tmpCanvas.getContext('2d');
|
var tmpCtx = tmpCanvas.getContext('2d');
|
||||||
|
|
||||||
var fillColor = this.current.fillColor;
|
var fillColor = this.current.fillColor;
|
||||||
tmpCtx.fillStyle = (fillColor && fillColor.hasOwnProperty('type') &&
|
tmpCtx.fillStyle = (fillColor && fillColor.hasOwnProperty('type') &&
|
||||||
fillColor.type === 'Pattern') ?
|
fillColor.type === 'Pattern') ?
|
||||||
fillColor.getPattern(tmpCtx) : fillColor;
|
fillColor.getPattern(tmpCtx) : fillColor;
|
||||||
tmpCtx.fillRect(0, 0, w, h);
|
tmpCtx.fillRect(0, 0, width, height);
|
||||||
|
|
||||||
var imgData = tmpCtx.getImageData(0, 0, w, h);
|
var imgData = tmpCtx.getImageData(0, 0, width, height);
|
||||||
var pixels = imgData.data;
|
var pixels = imgData.data;
|
||||||
|
|
||||||
applyStencilMask(pixels, inverseDecode);
|
applyStencilMask(imgArray, width, height, inverseDecode, pixels);
|
||||||
|
|
||||||
this.paintImage(imgData);
|
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(image.data, w, h, image.inverseDecode, pixels);
|
||||||
|
|
||||||
|
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) {
|
paintImageXObject: function CanvasGraphics_paintImageXObject(objId) {
|
||||||
@ -1258,10 +1293,11 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
|
|||||||
if (!imgData)
|
if (!imgData)
|
||||||
error('Dependent image isn\'t ready yet');
|
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 width = imgData.width;
|
||||||
var height = imgData.height;
|
var height = imgData.height;
|
||||||
var ctx = this.ctx;
|
var ctx = this.ctx;
|
||||||
@ -1294,6 +1330,27 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
|
|||||||
this.restore();
|
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,
|
putBinaryImageData: function CanvasGraphics_putBinaryImageData(ctx,
|
||||||
imgData) {
|
imgData) {
|
||||||
var w = imgData.width, h = imgData.height;
|
var w = imgData.width, h = imgData.height;
|
||||||
|
141
src/evaluator.js
141
src/evaluator.js
@ -260,15 +260,28 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
|
|||||||
return;
|
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
|
// If there is no imageMask, create the PDFImage and a lot
|
||||||
// of image processing can be done here.
|
// of image processing can be done here.
|
||||||
var objId = 'img_' + uniquePrefix + (++self.objIdCounter);
|
var objId = 'img_' + uniquePrefix + (++self.objIdCounter);
|
||||||
insertDependency([objId]);
|
insertDependency([objId]);
|
||||||
args = [objId, w, h];
|
args = [objId, w, h];
|
||||||
|
|
||||||
var softMask = dict.get('SMask', 'SM') || false;
|
|
||||||
var mask = dict.get('Mask') || false;
|
|
||||||
|
|
||||||
if (!softMask && !mask && image instanceof JpegStream &&
|
if (!softMask && !mask && image instanceof JpegStream &&
|
||||||
image.isNativelySupported(xref, resources)) {
|
image.isNativelySupported(xref, resources)) {
|
||||||
// These JPEGs don't need any more processing so we can just send it.
|
// These JPEGs don't need any more processing so we can just send it.
|
||||||
@ -280,19 +293,121 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
|
|||||||
fn = 'paintImageXObject';
|
fn = 'paintImageXObject';
|
||||||
|
|
||||||
PDFImage.buildImage(function(imageObj) {
|
PDFImage.buildImage(function(imageObj) {
|
||||||
var drawWidth = imageObj.drawWidth;
|
var imgData = imageObj.getImageData();
|
||||||
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);
|
|
||||||
handler.send('obj', [objId, pageIndex, 'Image', imgData]);
|
handler.send('obj', [objId, pageIndex, 'Image', imgData]);
|
||||||
}, handler, xref, resources, image, inline);
|
}, 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)
|
if (!queue)
|
||||||
queue = {};
|
queue = {};
|
||||||
|
|
||||||
@ -509,6 +624,8 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
optimizeQueue();
|
||||||
|
|
||||||
return queue;
|
return queue;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
12
src/image.js
12
src/image.js
@ -433,6 +433,18 @@ var PDFImage = (function PDFImageClosure() {
|
|||||||
for (var i = 0; i < length; ++i)
|
for (var i = 0; i < length; ++i)
|
||||||
buffer[i] = (scale * comps[i]) | 0;
|
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) {
|
getImageBytes: function PDFImage_getImageBytes(length) {
|
||||||
this.image.reset();
|
this.image.reset();
|
||||||
return this.image.getBytes(length);
|
return this.image.getBytes(length);
|
||||||
|
@ -28,7 +28,6 @@ var Parser = (function ParserClosure() {
|
|||||||
this.lexer = lexer;
|
this.lexer = lexer;
|
||||||
this.allowStreams = allowStreams;
|
this.allowStreams = allowStreams;
|
||||||
this.xref = xref;
|
this.xref = xref;
|
||||||
this.inlineImg = 0;
|
|
||||||
this.refill();
|
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 length = (stream.pos - 4) - startPos;
|
||||||
var imageStream = stream.makeSubStream(startPos, length, dict);
|
var imageStream = stream.makeSubStream(startPos, length, dict);
|
||||||
if (cipherTransform)
|
if (cipherTransform)
|
||||||
|
@ -19,7 +19,8 @@
|
|||||||
|
|
||||||
var Stream = (function StreamClosure() {
|
var Stream = (function StreamClosure() {
|
||||||
function Stream(arrayBuffer, start, length, dict) {
|
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.start = start || 0;
|
||||||
this.pos = this.start;
|
this.pos = this.start;
|
||||||
this.end = (start + length) || this.bytes.length;
|
this.end = (start + length) || this.bytes.length;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user