Browse Source

Make QueueOptimizer easier to read.

QueueOptimizer is really hard to read. Enough so that it's blocking my
efforts to streamline the representation used for operator lists.

This patch improves its readability in the following ways.

- More descriptive variable names make the sequence checking much clearer,
  as do additional comments.

- The addState() functions now return the index of the first op past the
  sequence, instead of setting context.currentOperation to the last op of
  the sequence.

- The loop in optimize() is clearer.

- The array modification in the fourth addState() function is much clearer
  -- we're just removing trios of ops.

- All four |addState| functions are now more consistent with each other.

I used some debug printfs to find documents where these optimizations are
used and then checked that the number of optimized ops was the same before
and after my changes.
Nicholas Nethercote 11 years ago
parent
commit
a483c80fc3
  1. 350
      src/core/evaluator.js

350
src/core/evaluator.js

@ -2201,18 +2201,20 @@ var QueueOptimizer = (function QueueOptimizerClosure() { @@ -2201,18 +2201,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;
@ -2222,26 +2224,43 @@ var QueueOptimizer = (function QueueOptimizerClosure() { @@ -2222,26 +2224,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 curr = context.iCurr;
var iFirstSave = curr - 3;
var iFirstTransform = curr - 2;
var iFirstPIIXO = curr - 1;
// 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;
}
for (; i < ii && fnArray[i - 4] === fnArray[i]; i++) {}
var count = Math.min((i - j) >> 2, MAX_IMAGES_IN_INLINE_IMAGES_BLOCK);
// 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) {
context.currentOperation = i - 1;
return;
return i;
}
// assuming that heights of those image is too small (~1 pixel)
// packing as much as possible by lines
var maxX = 0;
@ -2249,8 +2268,8 @@ var QueueOptimizer = (function QueueOptimizerClosure() { @@ -2249,8 +2268,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);
@ -2271,8 +2290,8 @@ var QueueOptimizer = (function QueueOptimizerClosure() { @@ -2271,8 +2290,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;
@ -2295,48 +2314,72 @@ var QueueOptimizer = (function QueueOptimizerClosure() { @@ -2295,48 +2314,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;
// 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;
}
for (; i < ii && fnArray[i - 4] === fnArray[i]; i++) {}
var count = (i - j) >> 2;
count = handlePaintSolidColorImageMask(j, count, fnArray, argsArray);
// 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) {
context.currentOperation = i - 1;
return;
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 {
@ -2350,39 +2393,39 @@ var QueueOptimizer = (function QueueOptimizerClosure() { @@ -2350,39 +2393,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) {
@ -2390,103 +2433,135 @@ var QueueOptimizer = (function QueueOptimizerClosure() { @@ -2390,103 +2433,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 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
}
// 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;
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
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 count = Math.min((i - j) >> 2, MAX_IMAGES_IN_BLOCK);
// 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) {
context.currentOperation = i - 1;
return;
return i;
}
// Extract the (x,y) positions from all of the matching transforms.
var positions = new Float32Array(count * 2);
i = j + 1;
for (var q = 0; q < count; q++) {
transformArgs = argsArray[i];
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];
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;
// 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);
// 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) {
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')
return i;
}
// If the preceding quintet is (<something>, setFont, setTextMatrix,
// showText, endText), include that as well. (E.g. <something> 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++;
j -= 5;
iFirst -= 5;
}
var k = j + 7;
i = j + 4;
// Remove (endText, beginText, setFont) trios.
var iEndText = iFirst + 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;
fnArray.splice(iEndText, 3);
argsArray.splice(iEndText, 3);
iEndText += 2;
}
return iEndText + 1;
});
function QueueOptimizer() {}
@ -2495,19 +2570,24 @@ var QueueOptimizer = (function QueueOptimizerClosure() { @@ -2495,19 +2570,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++;
}
}
}

Loading…
Cancel
Save