diff --git a/src/core/evaluator.js b/src/core/evaluator.js index 82c669561..df1539dcf 100644 --- a/src/core/evaluator.js +++ b/src/core/evaluator.js @@ -2206,18 +2206,20 @@ var QueueOptimizer = (function QueueOptimizerClosure() { state[pattern[pattern.length - 1]] = fn; } - function handlePaintSolidColorImageMask(index, count, fnArray, argsArray) { - // Handles special case of mainly LaTeX documents which - // use image masks to draw lines with the current fill style. + function handlePaintSolidColorImageMask(iFirstSave, count, fnArray, + argsArray) { + // Handles special case of mainly LaTeX documents which use image masks to + // draw lines with the current fill style. // 'count' groups of (save, transform, paintImageMaskXObject, restore)+ - // have been found at index. + // have been found at iFirstSave. + var iFirstPIMXO = iFirstSave + 2; for (var i = 0; i < count; i++) { - var arg = argsArray[index + 4 * i + 2]; + var arg = argsArray[iFirstPIMXO + 4 * i]; var imageMask = arg.length === 1 && arg[0]; if (imageMask && imageMask.width === 1 && imageMask.height === 1 && - (!imageMask.data.length || (imageMask.data.length === 1 && - imageMask.data[0] === 0))) { - fnArray[index + 4 * i + 2] = OPS.paintSolidColorImageMask; + (!imageMask.data.length || + (imageMask.data.length === 1 && imageMask.data[0] === 0))) { + fnArray[iFirstPIMXO + 4 * i] = OPS.paintSolidColorImageMask; continue; } break; @@ -2227,26 +2229,43 @@ var QueueOptimizer = (function QueueOptimizerClosure() { var InitialState = []; + // This replaces (save, transform, paintInlineImageXObject, restore)+ + // sequences with one |paintInlineImageXObjectGroup| operation. addState(InitialState, [OPS.save, OPS.transform, OPS.paintInlineImageXObject, OPS.restore], function foundInlineImageGroup(context) { - // grouping paintInlineImageXObject's into paintInlineImageXObjectGroup - // searching for (save, transform, paintInlineImageXObject, restore)+ var MIN_IMAGES_IN_INLINE_IMAGES_BLOCK = 10; var MAX_IMAGES_IN_INLINE_IMAGES_BLOCK = 200; var MAX_WIDTH = 1000; var IMAGE_PADDING = 1; var fnArray = context.fnArray, argsArray = context.argsArray; - var j = context.currentOperation - 3, i = j + 4; - var ii = fnArray.length; + var curr = context.iCurr; + var iFirstSave = curr - 3; + var iFirstTransform = curr - 2; + var iFirstPIIXO = curr - 1; - for (; i < ii && fnArray[i - 4] === fnArray[i]; i++) {} - var count = Math.min((i - j) >> 2, MAX_IMAGES_IN_INLINE_IMAGES_BLOCK); - if (count < MIN_IMAGES_IN_INLINE_IMAGES_BLOCK) { - context.currentOperation = i - 1; - return; + // Look for the quartets. + var i = iFirstSave + 4; + var ii = fnArray.length; + while (i + 3 < ii) { + if (fnArray[i] !== OPS.save || + fnArray[i + 1] !== OPS.transform || + fnArray[i + 2] !== OPS.paintInlineImageXObject || + fnArray[i + 3] !== OPS.restore) { + break; // ops don't match + } + i += 4; } + + // At this point, i is the index of the first op past the last valid + // quartet. + var count = Math.min((i - iFirstSave) / 4, + MAX_IMAGES_IN_INLINE_IMAGES_BLOCK); + if (count < MIN_IMAGES_IN_INLINE_IMAGES_BLOCK) { + return i; + } + // assuming that heights of those image is too small (~1 pixel) // packing as much as possible by lines var maxX = 0; @@ -2254,8 +2273,8 @@ var QueueOptimizer = (function QueueOptimizerClosure() { var currentX = IMAGE_PADDING, currentY = IMAGE_PADDING; var q; for (q = 0; q < count; q++) { - var transform = argsArray[j + (q << 2) + 1]; - var img = argsArray[j + (q << 2) + 2][0]; + var transform = argsArray[iFirstTransform + (q << 2)]; + var img = argsArray[iFirstPIIXO + (q << 2)][0]; if (currentX + img.width > MAX_WIDTH) { // starting new line maxX = Math.max(maxX, currentX); @@ -2276,8 +2295,8 @@ var QueueOptimizer = (function QueueOptimizerClosure() { var imgData = new Uint8Array(imgWidth * imgHeight * 4); var imgRowSize = imgWidth << 2; for (q = 0; q < count; q++) { - var data = argsArray[j + (q << 2) + 2][0].data; - // copy image by lines and extends pixels into padding + var data = argsArray[iFirstPIIXO + (q << 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; @@ -2300,48 +2319,72 @@ var QueueOptimizer = (function QueueOptimizerClosure() { offset -= imgRowSize; } } - // replacing queue items - fnArray.splice(j, count * 4, OPS.paintInlineImageXObjectGroup); - argsArray.splice(j, count * 4, + + // Replace queue items. + fnArray.splice(iFirstSave, count * 4, OPS.paintInlineImageXObjectGroup); + argsArray.splice(iFirstSave, count * 4, [{ width: imgWidth, height: imgHeight, kind: ImageKind.RGBA_32BPP, data: imgData }, map]); - context.currentOperation = j; + + return iFirstSave + 1; }); + // This replaces (save, transform, paintImageMaskXObject, restore)+ + // sequences with one |paintImageMaskXObjectGroup| or one + // |paintImageMaskXObjectRepeat| operation. addState(InitialState, [OPS.save, OPS.transform, OPS.paintImageMaskXObject, OPS.restore], function foundImageMaskGroup(context) { - // grouping paintImageMaskXObject's into paintImageMaskXObjectGroup - // searching for (save, transform, paintImageMaskXObject, restore)+ var MIN_IMAGES_IN_MASKS_BLOCK = 10; var MAX_IMAGES_IN_MASKS_BLOCK = 100; var MAX_SAME_IMAGES_IN_MASKS_BLOCK = 1000; var fnArray = context.fnArray, argsArray = context.argsArray; - var j = context.currentOperation - 3, i = j + 4; - var ii = fnArray.length, q; + var curr = context.iCurr; + var iFirstSave = curr - 3; + var iFirstTransform = curr - 2; + var iFirstPIMXO = curr - 1; - for (; i < ii && fnArray[i - 4] === fnArray[i]; i++) {} - var count = (i - j) >> 2; - count = handlePaintSolidColorImageMask(j, count, fnArray, argsArray); - if (count < MIN_IMAGES_IN_MASKS_BLOCK) { - context.currentOperation = i - 1; - return; + // Look for the quartets. + var i = iFirstSave + 4; + var ii = fnArray.length; + while (i + 3 < ii) { + if (fnArray[i] !== OPS.save || + fnArray[i + 1] !== OPS.transform || + fnArray[i + 2] !== OPS.paintImageMaskXObject || + fnArray[i + 3] !== OPS.restore) { + break; // ops don't match + } + i += 4; } + // At this point, i is the index of the first op past the last valid + // quartet. + var count = (i - iFirstSave) / 4; + count = handlePaintSolidColorImageMask(iFirstSave, count, fnArray, + argsArray); + if (count < MIN_IMAGES_IN_MASKS_BLOCK) { + return i; + } + + var q; var isSameImage = false; - var transformArgs; - if (argsArray[j + 1][1] === 0 && argsArray[j + 1][2] === 0) { - i = j + 4; + var iTransform, transformArgs; + var firstPIMXOArg0 = argsArray[iFirstPIMXO][0]; + if (argsArray[iFirstTransform][1] === 0 && + argsArray[iFirstTransform][2] === 0) { isSameImage = true; - for (q = 1; q < count; q++, i += 4) { - var prevTransformArgs = argsArray[i - 3]; - transformArgs = argsArray[i + 1]; - if (argsArray[i - 2][0] !== argsArray[i + 2][0] || - prevTransformArgs[0] !== transformArgs[0] || - prevTransformArgs[1] !== transformArgs[1] || - prevTransformArgs[2] !== transformArgs[2] || - prevTransformArgs[3] !== transformArgs[3]) { + var firstTransformArg0 = argsArray[iFirstTransform][0]; + var firstTransformArg3 = argsArray[iFirstTransform][3]; + iTransform = iFirstTransform + 4; + var iPIMXO = iFirstPIMXO + 4; + for (q = 1; q < count; q++, iTransform += 4, iPIMXO += 4) { + transformArgs = argsArray[iTransform]; + if (argsArray[iPIMXO][0] !== firstPIMXOArg0 || + transformArgs[0] !== firstTransformArg0 || + transformArgs[1] !== 0 || + transformArgs[2] !== 0 || + transformArgs[3] !== firstTransformArg3) { if (q < MIN_IMAGES_IN_MASKS_BLOCK) { isSameImage = false; } else { @@ -2355,39 +2398,39 @@ var QueueOptimizer = (function QueueOptimizerClosure() { if (isSameImage) { count = Math.min(count, MAX_SAME_IMAGES_IN_MASKS_BLOCK); var positions = new Float32Array(count * 2); - i = j + 1; - for (q = 0; q < count; q++) { - transformArgs = argsArray[i]; + iTransform = iFirstTransform; + for (q = 0; q < count; q++, iTransform += 4) { + transformArgs = argsArray[iTransform]; positions[(q << 1)] = transformArgs[4]; positions[(q << 1) + 1] = transformArgs[5]; - i += 4; } - // replacing queue items - fnArray.splice(j, count * 4, OPS.paintImageMaskXObjectRepeat); - argsArray.splice(j, count * 4, [argsArray[j + 2][0], - argsArray[j + 1][0], argsArray[j + 1][3], positions]); - - context.currentOperation = j; + // Replace queue items. + fnArray.splice(iFirstSave, count * 4, OPS.paintImageMaskXObjectRepeat); + argsArray.splice(iFirstSave, count * 4, + [firstPIMXOArg0, firstTransformArg0, firstTransformArg3, positions]); } else { count = Math.min(count, MAX_IMAGES_IN_MASKS_BLOCK); var images = []; for (q = 0; q < count; q++) { - transformArgs = argsArray[j + (q << 2) + 1]; - var maskParams = argsArray[j + (q << 2) + 2][0]; + transformArgs = argsArray[iFirstTransform + (q << 2)]; + var maskParams = argsArray[iFirstPIMXO + (q << 2)][0]; images.push({ data: maskParams.data, width: maskParams.width, height: maskParams.height, transform: transformArgs }); } - // replacing queue items - fnArray.splice(j, count * 4, OPS.paintImageMaskXObjectGroup); - argsArray.splice(j, count * 4, [images]); - - context.currentOperation = j; + // Replace queue items. + fnArray.splice(iFirstSave, count * 4, OPS.paintImageMaskXObjectGroup); + argsArray.splice(iFirstSave, count * 4, [images]); } + + return iFirstSave + 1; }); + // This replaces (save, transform, paintImageXObject, restore)+ sequences + // with one paintImageXObjectRepeat operation, if the |transform| and + // |paintImageXObjectRepeat| ops are appropriate. addState(InitialState, [OPS.save, OPS.transform, OPS.paintImageXObject, OPS.restore], function (context) { @@ -2395,103 +2438,135 @@ var QueueOptimizer = (function QueueOptimizerClosure() { var MAX_IMAGES_IN_BLOCK = 1000; var fnArray = context.fnArray, argsArray = context.argsArray; - var j = context.currentOperation - 3, i = j + 4; - if (argsArray[j + 1][1] !== 0 || argsArray[j + 1][2] !== 0) { - return; - } - var ii = fnArray.length; - var transformArgs; - for (; i + 3 < ii && fnArray[i - 4] === fnArray[i]; i += 4) { - if (fnArray[i - 3] !== fnArray[i + 1] || - fnArray[i - 2] !== fnArray[i + 2] || - fnArray[i - 1] !== fnArray[i + 3]) { - break; - } - if (argsArray[i - 2][0] !== argsArray[i + 2][0]) { - break; // different image - } - var prevTransformArgs = argsArray[i - 3]; - transformArgs = argsArray[i + 1]; - if (prevTransformArgs[0] !== transformArgs[0] || - prevTransformArgs[1] !== transformArgs[1] || - prevTransformArgs[2] !== transformArgs[2] || - prevTransformArgs[3] !== transformArgs[3]) { - break; // different transform - } - } - var count = Math.min((i - j) >> 2, MAX_IMAGES_IN_BLOCK); - if (count < MIN_IMAGES_IN_BLOCK) { - context.currentOperation = i - 1; - return; + var curr = context.iCurr; + var iFirstSave = curr - 3; + var iFirstTransform = curr - 2; + var iFirstPIXO = curr - 1; + var iFirstRestore = curr; + + if (argsArray[iFirstTransform][1] !== 0 || + argsArray[iFirstTransform][2] !== 0) { + return iFirstRestore + 1; // transform has the wrong form } - var positions = new Float32Array(count * 2); - i = j + 1; - for (var q = 0; q < count; q++) { - transformArgs = argsArray[i]; - positions[(q << 1)] = transformArgs[4]; - positions[(q << 1) + 1] = transformArgs[5]; + // Look for the quartets. + var firstPIXOArg0 = argsArray[iFirstPIXO][0]; + var firstTransformArg0 = argsArray[iFirstTransform][0]; + var firstTransformArg3 = argsArray[iFirstTransform][3]; + var i = iFirstSave + 4; + var ii = fnArray.length; + while (i + 3 < ii) { + if (fnArray[i] !== OPS.save || + fnArray[i + 1] !== OPS.transform || + fnArray[i + 2] !== OPS.paintImageXObject || + fnArray[i + 3] !== OPS.restore) { + break; // ops don't match + } + if (argsArray[i + 1][0] !== firstTransformArg0 || + argsArray[i + 1][1] !== 0 || + argsArray[i + 1][2] !== 0 || + argsArray[i + 1][3] !== firstTransformArg3) { + break; // transforms don't match + } + if (argsArray[i + 2][0] !== firstPIXOArg0) { + break; // images don't match + } i += 4; } - var args = [argsArray[j + 2][0], argsArray[j + 1][0], - argsArray[j + 1][3], positions]; - // replacing queue items - fnArray.splice(j, count * 4, OPS.paintImageXObjectRepeat); - argsArray.splice(j, count * 4, args); - context.currentOperation = j; + // At this point, i is the index of the first op past the last valid + // quartet. + var count = Math.min((i - iFirstSave) / 4, MAX_IMAGES_IN_BLOCK); + if (count < MIN_IMAGES_IN_BLOCK) { + return i; + } + + // Extract the (x,y) positions from all of the matching transforms. + var positions = new Float32Array(count * 2); + var iTransform = iFirstTransform; + for (var q = 0; q < count; q++, iTransform += 4) { + var transformArgs = argsArray[iTransform]; + positions[(q << 1)] = transformArgs[4]; + positions[(q << 1) + 1] = transformArgs[5]; + } + + // Replace queue items. + var args = [firstPIXOArg0, firstTransformArg0, firstTransformArg3, + positions]; + fnArray.splice(iFirstSave, count * 4, OPS.paintImageXObjectRepeat); + argsArray.splice(iFirstSave, count * 4, args); + + return iFirstSave + 1; }); + // This replaces (beginText, setFont, setTextMatrix, showText, endText)+ + // sequences with (beginText, setFont, (setTextMatrix, showText)+, endText)+ + // sequences, if the font for each one is the same. addState(InitialState, [OPS.beginText, OPS.setFont, OPS.setTextMatrix, OPS.showText, OPS.endText], function (context) { - // moving single chars with same font into beginText/endText groups - // searching for (beginText, setFont, setTextMatrix, showText, endText)+ var MIN_CHARS_IN_BLOCK = 3; var MAX_CHARS_IN_BLOCK = 1000; var fnArray = context.fnArray, argsArray = context.argsArray; - var j = context.currentOperation - 4, i = j + 5; + var curr = context.iCurr; + var iFirstBeginText = curr - 4; + var iFirstSetFont = curr - 3; + var iFirstSetTextMatrix = curr - 2; + var iFirstShowText = curr - 1; + var iFirstEndText = curr; + + // Look for the quintets. + var firstSetFontArg0 = argsArray[iFirstSetFont][0]; + var firstSetFontArg1 = argsArray[iFirstSetFont][1]; + var i = iFirstBeginText + 5; var ii = fnArray.length; - - for (; i < ii && fnArray[i - 5] === fnArray[i]; i++) { - if (fnArray[i] === OPS.setFont) { - if (argsArray[i - 5][0] !== argsArray[i][0] || - argsArray[i - 5][1] !== argsArray[i][1]) { - break; - } + while (i + 4 < ii) { + if (fnArray[i] !== OPS.beginText || + fnArray[i + 1] !== OPS.setFont || + fnArray[i + 2] !== OPS.setTextMatrix || + fnArray[i + 3] !== OPS.showText || + fnArray[i + 4] !== OPS.endText) { + break; // ops don't match } + if (argsArray[i + 1][0] !== firstSetFontArg0 || + argsArray[i + 1][1] !== firstSetFontArg1) { + break; // fonts don't match + } + i += 5; } - var count = Math.min(((i - j) / 5) | 0, MAX_CHARS_IN_BLOCK); - if (count < MIN_CHARS_IN_BLOCK) { - context.currentOperation = i - 1; - return; - } - if (j >= 4 && fnArray[j - 4] === fnArray[j + 1] && - fnArray[j - 3] === fnArray[j + 2] && - fnArray[j - 2] === fnArray[j + 3] && - fnArray[j - 1] === fnArray[j + 4] && - argsArray[j - 4][0] === argsArray[j + 1][0] && - argsArray[j - 4][1] === argsArray[j + 1][1]) { - // extending one block ahead (very first block might have 'dependency') - count++; - j -= 5; - } - var k = j + 7; - i = j + 4; - for (var q = 1; q < count; q++) { - fnArray[i] = fnArray[k]; - argsArray[i] = argsArray[k]; - fnArray[i + 1] = fnArray[k + 1]; - argsArray[i + 1] = argsArray[k + 1]; - i += 2; - k += 5; - } - var removed = (count - 1) * 3; - fnArray.splice(i, removed); - argsArray.splice(i, removed); - context.currentOperation = i; + // At this point, i is the index of the first op past the last valid + // quintet. + var count = Math.min(((i - iFirstBeginText) / 5), MAX_CHARS_IN_BLOCK); + if (count < MIN_CHARS_IN_BLOCK) { + return i; + } + + // If the preceding quintet is (, setFont, setTextMatrix, + // showText, endText), include that as well. (E.g. might be + // |dependency|.) + var iFirst = iFirstBeginText; + if (iFirstBeginText >= 4 && + fnArray[iFirstBeginText - 4] === fnArray[iFirstSetFont] && + fnArray[iFirstBeginText - 3] === fnArray[iFirstSetTextMatrix] && + fnArray[iFirstBeginText - 2] === fnArray[iFirstShowText] && + fnArray[iFirstBeginText - 1] === fnArray[iFirstEndText] && + argsArray[iFirstBeginText - 4][0] === firstSetFontArg0 && + argsArray[iFirstBeginText - 4][1] === firstSetFontArg1) { + count++; + iFirst -= 5; + } + + // Remove (endText, beginText, setFont) trios. + var iEndText = iFirst + 4; + for (var q = 1; q < count; q++) { + fnArray.splice(iEndText, 3); + argsArray.splice(iEndText, 3); + iEndText += 2; + } + + return iEndText + 1; }); function QueueOptimizer() {} @@ -2500,19 +2575,24 @@ var QueueOptimizer = (function QueueOptimizerClosure() { optimize: function QueueOptimizer_optimize(queue) { var fnArray = queue.fnArray, argsArray = queue.argsArray; var context = { - currentOperation: 0, + iCurr: 0, fnArray: fnArray, argsArray: argsArray }; - var i, ii = argsArray.length; var state; - for (i = 0; i < ii; i++) { + var i = 0, ii = fnArray.length; + while (i < ii) { state = (state || InitialState)[fnArray[i]]; if (typeof state === 'function') { // we found some handler - context.currentOperation = i; - state = state(context); - i = context.currentOperation; + context.iCurr = i; + // state() returns the index of the first non-matching op (if we + // didn't match) or the first op past the modified ops (if we did + // match and replace). + i = state(context); + state = undefined; // reset the state machine ii = context.fnArray.length; + } else { + i++; } } }