Merge pull request #4119 from yurydelendik/preprocessor

Extracts evaluator preprocessor, refactor text extraction and paintFormXObject
This commit is contained in:
Tim van der Meij 2014-01-17 06:21:00 -08:00
commit 070d77a332
4 changed files with 289 additions and 264 deletions

View File

@ -40,119 +40,6 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
this.fontCache = fontCache;
}
// Specifies properties for each command
//
// If variableArgs === true: [0, `numArgs`] expected
// If variableArgs === false: exactly `numArgs` expected
var OP_MAP = {
// Graphic state
w: { id: OPS.setLineWidth, numArgs: 1, variableArgs: false },
J: { id: OPS.setLineCap, numArgs: 1, variableArgs: false },
j: { id: OPS.setLineJoin, numArgs: 1, variableArgs: false },
M: { id: OPS.setMiterLimit, numArgs: 1, variableArgs: false },
d: { id: OPS.setDash, numArgs: 2, variableArgs: false },
ri: { id: OPS.setRenderingIntent, numArgs: 1, variableArgs: false },
i: { id: OPS.setFlatness, numArgs: 1, variableArgs: false },
gs: { id: OPS.setGState, numArgs: 1, variableArgs: false },
q: { id: OPS.save, numArgs: 0, variableArgs: false },
Q: { id: OPS.restore, numArgs: 0, variableArgs: false },
cm: { id: OPS.transform, numArgs: 6, variableArgs: false },
// Path
m: { id: OPS.moveTo, numArgs: 2, variableArgs: false },
l: { id: OPS.lineTo, numArgs: 2, variableArgs: false },
c: { id: OPS.curveTo, numArgs: 6, variableArgs: false },
v: { id: OPS.curveTo2, numArgs: 4, variableArgs: false },
y: { id: OPS.curveTo3, numArgs: 4, variableArgs: false },
h: { id: OPS.closePath, numArgs: 0, variableArgs: false },
re: { id: OPS.rectangle, numArgs: 4, variableArgs: false },
S: { id: OPS.stroke, numArgs: 0, variableArgs: false },
s: { id: OPS.closeStroke, numArgs: 0, variableArgs: false },
f: { id: OPS.fill, numArgs: 0, variableArgs: false },
F: { id: OPS.fill, numArgs: 0, variableArgs: false },
'f*': { id: OPS.eoFill, numArgs: 0, variableArgs: false },
B: { id: OPS.fillStroke, numArgs: 0, variableArgs: false },
'B*': { id: OPS.eoFillStroke, numArgs: 0, variableArgs: false },
b: { id: OPS.closeFillStroke, numArgs: 0, variableArgs: false },
'b*': { id: OPS.closeEOFillStroke, numArgs: 0, variableArgs: false },
n: { id: OPS.endPath, numArgs: 0, variableArgs: false },
// Clipping
W: { id: OPS.clip, numArgs: 0, variableArgs: false },
'W*': { id: OPS.eoClip, numArgs: 0, variableArgs: false },
// Text
BT: { id: OPS.beginText, numArgs: 0, variableArgs: false },
ET: { id: OPS.endText, numArgs: 0, variableArgs: false },
Tc: { id: OPS.setCharSpacing, numArgs: 1, variableArgs: false },
Tw: { id: OPS.setWordSpacing, numArgs: 1, variableArgs: false },
Tz: { id: OPS.setHScale, numArgs: 1, variableArgs: false },
TL: { id: OPS.setLeading, numArgs: 1, variableArgs: false },
Tf: { id: OPS.setFont, numArgs: 2, variableArgs: false },
Tr: { id: OPS.setTextRenderingMode, numArgs: 1, variableArgs: false },
Ts: { id: OPS.setTextRise, numArgs: 1, variableArgs: false },
Td: { id: OPS.moveText, numArgs: 2, variableArgs: false },
TD: { id: OPS.setLeadingMoveText, numArgs: 2, variableArgs: false },
Tm: { id: OPS.setTextMatrix, numArgs: 6, variableArgs: false },
'T*': { id: OPS.nextLine, numArgs: 0, variableArgs: false },
Tj: { id: OPS.showText, numArgs: 1, variableArgs: false },
TJ: { id: OPS.showSpacedText, numArgs: 1, variableArgs: false },
'\'': { id: OPS.nextLineShowText, numArgs: 1, variableArgs: false },
'"': { id: OPS.nextLineSetSpacingShowText, numArgs: 3,
variableArgs: false },
// Type3 fonts
d0: { id: OPS.setCharWidth, numArgs: 2, variableArgs: false },
d1: { id: OPS.setCharWidthAndBounds, numArgs: 6, variableArgs: false },
// Color
CS: { id: OPS.setStrokeColorSpace, numArgs: 1, variableArgs: false },
cs: { id: OPS.setFillColorSpace, numArgs: 1, variableArgs: false },
SC: { id: OPS.setStrokeColor, numArgs: 4, variableArgs: true },
SCN: { id: OPS.setStrokeColorN, numArgs: 33, variableArgs: true },
sc: { id: OPS.setFillColor, numArgs: 4, variableArgs: true },
scn: { id: OPS.setFillColorN, numArgs: 33, variableArgs: true },
G: { id: OPS.setStrokeGray, numArgs: 1, variableArgs: false },
g: { id: OPS.setFillGray, numArgs: 1, variableArgs: false },
RG: { id: OPS.setStrokeRGBColor, numArgs: 3, variableArgs: false },
rg: { id: OPS.setFillRGBColor, numArgs: 3, variableArgs: false },
K: { id: OPS.setStrokeCMYKColor, numArgs: 4, variableArgs: false },
k: { id: OPS.setFillCMYKColor, numArgs: 4, variableArgs: false },
// Shading
sh: { id: OPS.shadingFill, numArgs: 1, variableArgs: false },
// Images
BI: { id: OPS.beginInlineImage, numArgs: 0, variableArgs: false },
ID: { id: OPS.beginImageData, numArgs: 0, variableArgs: false },
EI: { id: OPS.endInlineImage, numArgs: 1, variableArgs: false },
// XObjects
Do: { id: OPS.paintXObject, numArgs: 1, variableArgs: false },
MP: { id: OPS.markPoint, numArgs: 1, variableArgs: false },
DP: { id: OPS.markPointProps, numArgs: 2, variableArgs: false },
BMC: { id: OPS.beginMarkedContent, numArgs: 1, variableArgs: false },
BDC: { id: OPS.beginMarkedContentProps, numArgs: 2,
variableArgs: false },
EMC: { id: OPS.endMarkedContent, numArgs: 0, variableArgs: false },
// Compatibility
BX: { id: OPS.beginCompat, numArgs: 0, variableArgs: false },
EX: { id: OPS.endCompat, numArgs: 0, variableArgs: false },
// (reserved partial commands for the lexer)
BM: null,
BD: null,
'true': null,
fa: null,
fal: null,
fals: null,
'false': null,
nu: null,
nul: null,
'null': null
};
var TILING_PATTERN = 1, SHADING_PATTERN = 2;
PartialEvaluator.prototype = {
@ -198,7 +85,8 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
buildFormXObject: function PartialEvaluator_buildFormXObject(resources,
xobj, smask,
operatorList) {
operatorList,
state) {
var self = this;
var matrix = xobj.dict.get('Matrix');
@ -226,7 +114,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
operatorList.addOp(OPS.paintFormXObjectBegin, [matrix, bbox]);
this.getOperatorList(xobj, xobj.dict.get('Resources') || resources,
operatorList);
operatorList, state);
operatorList.addOp(OPS.paintFormXObjectEnd, []);
if (group) {
@ -532,7 +420,8 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
getOperatorList: function PartialEvaluator_getOperatorList(stream,
resources,
operatorList) {
operatorList,
evaluatorState) {
var self = this;
var xref = this.xref;
@ -543,54 +432,16 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
resources = resources || new Dict();
var xobjs = resources.get('XObject') || new Dict();
var patterns = resources.get('Pattern') || new Dict();
// TODO(mduan): pass array of knownCommands rather than OP_MAP
// dictionary
var parser = new Parser(new Lexer(stream, OP_MAP), false, xref);
var preprocessor = new EvaluatorPreprocessor(stream, xref);
if (evaluatorState) {
preprocessor.setState(evaluatorState);
}
var promise = new LegacyPromise();
var args = [];
while (true) {
var obj = parser.getObj();
if (isEOF(obj)) {
break;
}
if (isCmd(obj)) {
var cmd = obj.cmd;
// Check that the command is valid
var opSpec = OP_MAP[cmd];
if (!opSpec) {
warn('Unknown command "' + cmd + '"');
continue;
}
var fn = opSpec.id;
// Validate the number of arguments for the command
if (opSpec.variableArgs) {
if (args.length > opSpec.numArgs) {
info('Command ' + fn + ': expected [0,' + opSpec.numArgs +
'] args, but received ' + args.length + ' args');
}
} else {
if (args.length < opSpec.numArgs) {
// If we receive too few args, it's not possible to possible
// to execute the command, so skip the command
info('Command ' + fn + ': because expected ' +
opSpec.numArgs + ' args, but received ' + args.length +
' args; skipping');
args = [];
continue;
} else if (args.length > opSpec.numArgs) {
info('Command ' + fn + ': expected ' + opSpec.numArgs +
' args, but received ' + args.length + ' args');
}
}
// TODO figure out how to type-check vararg functions
var operation;
while ((operation = preprocessor.read())) {
var args = operation.args;
var fn = operation.fn;
switch (fn) {
case OPS.setStrokeColorN:
@ -642,7 +493,8 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
);
if ('Form' == type.name) {
self.buildFormXObject(resources, xobj, null, operatorList);
self.buildFormXObject(resources, xobj, null, operatorList,
preprocessor.getState());
args = [];
continue;
} else if ('Image' == type.name) {
@ -733,12 +585,12 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
} // switch
operatorList.addOp(fn, args);
args = [];
parser.saveState();
} else if (obj !== null && obj !== undefined) {
args.push(obj instanceof Dict ? obj.getAll() : obj);
assertWellFormed(args.length <= 33, 'Too many arguments');
}
}
// some pdf don't close all restores inside object/form
// closing those for them
for (var i = 0, ii = preprocessor.savedStatesDepth; i < ii; i++) {
operatorList.addOp(OPS.restore, []);
}
return operatorList;
@ -775,65 +627,55 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
// The xobj is parsed iff it's needed, e.g. if there is a `DO` cmd.
var xobjs = null;
var parser = new Parser(new Lexer(stream), false);
var preprocessor = new EvaluatorPreprocessor(stream, xref);
var res = resources;
var args = [], obj;
var chunk = '';
var font = null;
var charSpace = 0, wordSpace = 0;
while (!isEOF(obj = parser.getObj())) {
if (isCmd(obj)) {
var cmd = obj.cmd;
switch (cmd) {
var operation;
while ((operation = preprocessor.read())) {
var fn = operation.fn;
var args = operation.args;
switch (fn) {
// TODO: Add support for SAVE/RESTORE and XFORM here.
case 'Tf':
case OPS.setFont:
font = handleSetFont(args[0].name).translated;
textState.fontSize = args[1];
break;
case 'Ts':
case OPS.setTextRise:
textState.textRise = args[0];
break;
case 'Tz':
case OPS.setHScale:
textState.textHScale = args[0] / 100;
break;
case 'TL':
case OPS.setLeading:
textState.leading = args[0];
break;
case 'Td':
case OPS.moveText:
textState.translateTextMatrix(args[0], args[1]);
break;
case 'TD':
case OPS.setLeadingMoveText:
textState.leading = -args[1];
textState.translateTextMatrix(args[0], args[1]);
break;
case 'T*':
case OPS.nextLine:
textState.translateTextMatrix(0, -textState.leading);
break;
case 'Tm':
case OPS.setTextMatrix:
textState.setTextMatrix(args[0], args[1],
args[2], args[3], args[4], args[5]);
break;
case 'Tc':
case OPS.setCharSpacing:
charSpace = args[0];
break;
case 'Tw':
case OPS.setWordSpacing:
wordSpace = args[0];
break;
case 'q':
textState.push();
break;
case 'Q':
textState.pop();
break;
case 'BT':
case OPS.beginText:
textState.initialiseTextObj();
break;
case 'cm':
textState.transformCTM(args[0], args[1], args[2],
args[3], args[4], args[5]);
break;
case 'TJ':
case OPS.showSpacedText:
var items = args[0];
for (var j = 0, jj = items.length; j < jj; j++) {
if (typeof items[j] === 'string') {
@ -851,20 +693,20 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
}
}
break;
case 'Tj':
case OPS.showText:
chunk += fontCharsToUnicode(args[0], font);
break;
case '\'':
case OPS.nextLineShowText:
// For search, adding a extra white space for line breaks would be
// better here, but that causes too much spaces in the
// text-selection divs.
chunk += fontCharsToUnicode(args[0], font);
break;
case '"':
case OPS.nextLineSetSpacingShowText:
// Note comment in "'"
chunk += fontCharsToUnicode(args[2], font);
break;
case 'Do':
case OPS.paintXObject:
// Set the chunk such that the following if won't add something
// to the state.
chunk = '';
@ -898,7 +740,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
state
);
break;
case 'gs':
case OPS.setGState:
var dictName = args[0];
var extGState = resources.get('ExtGState');
@ -917,7 +759,11 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
if (chunk !== '') {
var bidiText = PDFJS.bidi(chunk, -1, font.vertical);
var renderParams = textState.calcRenderParams();
var renderParams = textState.calcRenderParams(preprocessor.ctm);
bidiText.x = renderParams.renderMatrix[4] - (textState.fontSize *
renderParams.vScale * Math.sin(renderParams.angle));
bidiText.y = renderParams.renderMatrix[5] + (textState.fontSize *
renderParams.vScale * Math.cos(renderParams.angle));
var fontHeight = textState.fontSize * renderParams.vScale;
var fontAscent = font.ascent ? font.ascent * fontHeight :
font.descent ? (1 + font.descent) * fontHeight : fontHeight;
@ -933,12 +779,6 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
chunk = '';
}
args = [];
} else if (obj !== null && obj !== undefined) {
assertWellFormed(args.length <= 33, 'Too many arguments');
args.push(obj);
}
} // while
return state;
@ -1597,7 +1437,6 @@ var OperatorList = (function OperatorListClosure() {
var TextState = (function TextStateClosure() {
function TextState() {
this.fontSize = 0;
this.ctm = [1, 0, 0, 1, 0, 0];
this.textMatrix = [1, 0, 0, 1, 0, 0];
this.stateStack = [];
//textState variables
@ -1606,15 +1445,6 @@ var TextState = (function TextStateClosure() {
this.textRise = 0;
}
TextState.prototype = {
push: function TextState_push() {
this.stateStack.push(this.ctm.slice());
},
pop: function TextState_pop() {
var prev = this.stateStack.pop();
if (prev) {
this.ctm = prev;
}
},
initialiseTextObj: function TextState_initialiseTextObj() {
var m = this.textMatrix;
m[0] = 1, m[1] = 0, m[2] = 0, m[3] = 1, m[4] = 0, m[5] = 0;
@ -1623,24 +1453,13 @@ var TextState = (function TextStateClosure() {
var m = this.textMatrix;
m[0] = a, m[1] = b, m[2] = c, m[3] = d, m[4] = e, m[5] = f;
},
transformCTM: function TextState_transformCTM(a, b, c, d, e, f) {
var m = this.ctm;
var m0 = m[0], m1 = m[1], m2 = m[2], m3 = m[3], m4 = m[4], m5 = m[5];
m[0] = m0 * a + m2 * b;
m[1] = m1 * a + m3 * b;
m[2] = m0 * c + m2 * d;
m[3] = m1 * c + m3 * d;
m[4] = m0 * e + m2 * f + m4;
m[5] = m1 * e + m3 * f + m5;
},
translateTextMatrix: function TextState_translateTextMatrix(x, y) {
var m = this.textMatrix;
m[4] = m[0] * x + m[2] * y + m[4];
m[5] = m[1] * x + m[3] * y + m[5];
},
calcRenderParams: function TextState_calcRenderingParams() {
calcRenderParams: function TextState_calcRenderingParams(cm) {
var tm = this.textMatrix;
var cm = this.ctm;
var a = this.fontSize;
var b = a * this.textHScale;
var c = this.textRise;
@ -1683,3 +1502,218 @@ var EvalState = (function EvalStateClosure() {
return EvalState;
})();
var EvaluatorPreprocessor = (function EvaluatorPreprocessor() {
// Specifies properties for each command
//
// If variableArgs === true: [0, `numArgs`] expected
// If variableArgs === false: exactly `numArgs` expected
var OP_MAP = {
// Graphic state
w: { id: OPS.setLineWidth, numArgs: 1, variableArgs: false },
J: { id: OPS.setLineCap, numArgs: 1, variableArgs: false },
j: { id: OPS.setLineJoin, numArgs: 1, variableArgs: false },
M: { id: OPS.setMiterLimit, numArgs: 1, variableArgs: false },
d: { id: OPS.setDash, numArgs: 2, variableArgs: false },
ri: { id: OPS.setRenderingIntent, numArgs: 1, variableArgs: false },
i: { id: OPS.setFlatness, numArgs: 1, variableArgs: false },
gs: { id: OPS.setGState, numArgs: 1, variableArgs: false },
q: { id: OPS.save, numArgs: 0, variableArgs: false },
Q: { id: OPS.restore, numArgs: 0, variableArgs: false },
cm: { id: OPS.transform, numArgs: 6, variableArgs: false },
// Path
m: { id: OPS.moveTo, numArgs: 2, variableArgs: false },
l: { id: OPS.lineTo, numArgs: 2, variableArgs: false },
c: { id: OPS.curveTo, numArgs: 6, variableArgs: false },
v: { id: OPS.curveTo2, numArgs: 4, variableArgs: false },
y: { id: OPS.curveTo3, numArgs: 4, variableArgs: false },
h: { id: OPS.closePath, numArgs: 0, variableArgs: false },
re: { id: OPS.rectangle, numArgs: 4, variableArgs: false },
S: { id: OPS.stroke, numArgs: 0, variableArgs: false },
s: { id: OPS.closeStroke, numArgs: 0, variableArgs: false },
f: { id: OPS.fill, numArgs: 0, variableArgs: false },
F: { id: OPS.fill, numArgs: 0, variableArgs: false },
'f*': { id: OPS.eoFill, numArgs: 0, variableArgs: false },
B: { id: OPS.fillStroke, numArgs: 0, variableArgs: false },
'B*': { id: OPS.eoFillStroke, numArgs: 0, variableArgs: false },
b: { id: OPS.closeFillStroke, numArgs: 0, variableArgs: false },
'b*': { id: OPS.closeEOFillStroke, numArgs: 0, variableArgs: false },
n: { id: OPS.endPath, numArgs: 0, variableArgs: false },
// Clipping
W: { id: OPS.clip, numArgs: 0, variableArgs: false },
'W*': { id: OPS.eoClip, numArgs: 0, variableArgs: false },
// Text
BT: { id: OPS.beginText, numArgs: 0, variableArgs: false },
ET: { id: OPS.endText, numArgs: 0, variableArgs: false },
Tc: { id: OPS.setCharSpacing, numArgs: 1, variableArgs: false },
Tw: { id: OPS.setWordSpacing, numArgs: 1, variableArgs: false },
Tz: { id: OPS.setHScale, numArgs: 1, variableArgs: false },
TL: { id: OPS.setLeading, numArgs: 1, variableArgs: false },
Tf: { id: OPS.setFont, numArgs: 2, variableArgs: false },
Tr: { id: OPS.setTextRenderingMode, numArgs: 1, variableArgs: false },
Ts: { id: OPS.setTextRise, numArgs: 1, variableArgs: false },
Td: { id: OPS.moveText, numArgs: 2, variableArgs: false },
TD: { id: OPS.setLeadingMoveText, numArgs: 2, variableArgs: false },
Tm: { id: OPS.setTextMatrix, numArgs: 6, variableArgs: false },
'T*': { id: OPS.nextLine, numArgs: 0, variableArgs: false },
Tj: { id: OPS.showText, numArgs: 1, variableArgs: false },
TJ: { id: OPS.showSpacedText, numArgs: 1, variableArgs: false },
'\'': { id: OPS.nextLineShowText, numArgs: 1, variableArgs: false },
'"': { id: OPS.nextLineSetSpacingShowText, numArgs: 3,
variableArgs: false },
// Type3 fonts
d0: { id: OPS.setCharWidth, numArgs: 2, variableArgs: false },
d1: { id: OPS.setCharWidthAndBounds, numArgs: 6, variableArgs: false },
// Color
CS: { id: OPS.setStrokeColorSpace, numArgs: 1, variableArgs: false },
cs: { id: OPS.setFillColorSpace, numArgs: 1, variableArgs: false },
SC: { id: OPS.setStrokeColor, numArgs: 4, variableArgs: true },
SCN: { id: OPS.setStrokeColorN, numArgs: 33, variableArgs: true },
sc: { id: OPS.setFillColor, numArgs: 4, variableArgs: true },
scn: { id: OPS.setFillColorN, numArgs: 33, variableArgs: true },
G: { id: OPS.setStrokeGray, numArgs: 1, variableArgs: false },
g: { id: OPS.setFillGray, numArgs: 1, variableArgs: false },
RG: { id: OPS.setStrokeRGBColor, numArgs: 3, variableArgs: false },
rg: { id: OPS.setFillRGBColor, numArgs: 3, variableArgs: false },
K: { id: OPS.setStrokeCMYKColor, numArgs: 4, variableArgs: false },
k: { id: OPS.setFillCMYKColor, numArgs: 4, variableArgs: false },
// Shading
sh: { id: OPS.shadingFill, numArgs: 1, variableArgs: false },
// Images
BI: { id: OPS.beginInlineImage, numArgs: 0, variableArgs: false },
ID: { id: OPS.beginImageData, numArgs: 0, variableArgs: false },
EI: { id: OPS.endInlineImage, numArgs: 1, variableArgs: false },
// XObjects
Do: { id: OPS.paintXObject, numArgs: 1, variableArgs: false },
MP: { id: OPS.markPoint, numArgs: 1, variableArgs: false },
DP: { id: OPS.markPointProps, numArgs: 2, variableArgs: false },
BMC: { id: OPS.beginMarkedContent, numArgs: 1, variableArgs: false },
BDC: { id: OPS.beginMarkedContentProps, numArgs: 2,
variableArgs: false },
EMC: { id: OPS.endMarkedContent, numArgs: 0, variableArgs: false },
// Compatibility
BX: { id: OPS.beginCompat, numArgs: 0, variableArgs: false },
EX: { id: OPS.endCompat, numArgs: 0, variableArgs: false },
// (reserved partial commands for the lexer)
BM: null,
BD: null,
'true': null,
fa: null,
fal: null,
fals: null,
'false': null,
nu: null,
nul: null,
'null': null
};
function EvaluatorPreprocessor(stream, xref) {
// TODO(mduan): pass array of knownCommands rather than OP_MAP
// dictionary
this.parser = new Parser(new Lexer(stream, OP_MAP), false, xref);
this.ctm = new Float32Array([1, 0, 0, 1, 0, 0]);
this.savedStates = [];
}
EvaluatorPreprocessor.prototype = {
get savedStatesDepth() {
return this.savedStates.length;
},
read: function EvaluatorPreprocessor_read() {
var args = [];
while (true) {
var obj = this.parser.getObj();
if (isEOF(obj)) {
return null; // no more commands
}
if (!isCmd(obj)) {
// argument
if (obj !== null && obj !== undefined) {
args.push(obj instanceof Dict ? obj.getAll() : obj);
assertWellFormed(args.length <= 33, 'Too many arguments');
}
continue;
}
var cmd = obj.cmd;
// Check that the command is valid
var opSpec = OP_MAP[cmd];
if (!opSpec) {
warn('Unknown command "' + cmd + '"');
continue;
}
var fn = opSpec.id;
// Validate the number of arguments for the command
if (opSpec.variableArgs) {
if (args.length > opSpec.numArgs) {
info('Command ' + fn + ': expected [0,' + opSpec.numArgs +
'] args, but received ' + args.length + ' args');
}
} else {
if (args.length < opSpec.numArgs) {
// If we receive too few args, it's not possible to possible
// to execute the command, so skip the command
info('Command ' + fn + ': because expected ' +
opSpec.numArgs + ' args, but received ' + args.length +
' args; skipping');
args = [];
continue;
} else if (args.length > opSpec.numArgs) {
info('Command ' + fn + ': expected ' + opSpec.numArgs +
' args, but received ' + args.length + ' args');
}
}
// TODO figure out how to type-check vararg functions
this.preprocessCommand(fn, args);
return {fn: fn, args: args};
}
},
getState: function EvaluatorPreprocessor_getState() {
return {
ctm: this.ctm
};
},
setState: function EvaluatorPreprocessor_setState(state) {
this.ctm = state.ctm;
},
preprocessCommand: function EvaluatorPreprocessor_preprocessCommand(fn,
args) {
switch (fn | 0) {
case OPS.save:
this.savedStates.push(this.getState());
break;
case OPS.restore:
var previousState = this.savedStates.pop();
if (previousState) {
this.setState(previousState);
}
break;
case OPS.transform:
var ctm = this.ctm;
var m = new Float32Array(6);
m[0] = ctm[0] * args[0] + ctm[2] * args[1];
m[1] = ctm[1] * args[0] + ctm[3] * args[1];
m[2] = ctm[0] * args[2] + ctm[2] * args[3];
m[3] = ctm[1] * args[2] + ctm[3] * args[3];
m[4] = ctm[0] * args[4] + ctm[2] * args[5] + ctm[4];
m[5] = ctm[1] * args[4] + ctm[3] * args[5] + ctm[5];
this.ctm = m;
break;
}
}
};
return EvaluatorPreprocessor;
})();

View File

@ -36,21 +36,6 @@ var Parser = (function ParserClosure() {
}
Parser.prototype = {
saveState: function Parser_saveState() {
this.state = {
buf1: this.buf1,
buf2: this.buf2,
streamPos: this.lexer.stream.pos
};
},
restoreState: function Parser_restoreState() {
var state = this.state;
this.buf1 = state.buf1;
this.buf2 = state.buf2;
this.lexer.stream.pos = state.streamPos;
},
refill: function Parser_refill() {
this.buf1 = this.lexer.getObj();
this.buf2 = this.lexer.getObj();

View File

@ -376,7 +376,6 @@ var CanvasExtraState = (function CanvasExtraStateClosure() {
this.fillAlpha = 1;
this.strokeAlpha = 1;
this.lineWidth = 1;
this.paintFormXObjectDepth = 0;
this.old = old;
}
@ -1453,7 +1452,6 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
paintFormXObjectBegin: function CanvasGraphics_paintFormXObjectBegin(matrix,
bbox) {
this.save();
this.current.paintFormXObjectDepth++;
this.baseTransformStack.push(this.baseTransform);
if (matrix && isArray(matrix) && 6 == matrix.length)
@ -1471,12 +1469,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
},
paintFormXObjectEnd: function CanvasGraphics_paintFormXObjectEnd() {
var depth = this.current.paintFormXObjectDepth;
do {
this.restore();
// some pdf don't close all restores inside object
// closing those for them
} while (this.current.paintFormXObjectDepth >= depth);
this.restore();
this.baseTransform = this.baseTransformStack.pop();
},

View File

@ -35,11 +35,11 @@ describe('evaluator', function() {
var evaluator = new PartialEvaluator(new PdfManagerMock(),
new XrefMock(), new HandlerMock(),
'prefix');
var stream = new StringStream('qTT');
var stream = new StringStream('fTT');
var result = evaluator.getOperatorList(stream, new ResourcesMock());
expect(!!result.fnArray && !!result.argsArray).toEqual(true);
expect(result.fnArray.length).toEqual(1);
expect(result.fnArray[0]).toEqual(OPS.save);
expect(result.fnArray[0]).toEqual(OPS.fill);
expect(result.argsArray[0].length).toEqual(0);
});
@ -72,13 +72,13 @@ describe('evaluator', function() {
var evaluator = new PartialEvaluator(new PdfManagerMock(),
new XrefMock(), new HandlerMock(),
'prefix');
var stream = new StringStream('qqq');
var stream = new StringStream('fff');
var result = evaluator.getOperatorList(stream, new ResourcesMock());
expect(!!result.fnArray && !!result.argsArray).toEqual(true);
expect(result.fnArray.length).toEqual(3);
expect(result.fnArray[0]).toEqual(OPS.save);
expect(result.fnArray[1]).toEqual(OPS.save);
expect(result.fnArray[2]).toEqual(OPS.save);
expect(result.fnArray[0]).toEqual(OPS.fill);
expect(result.fnArray[1]).toEqual(OPS.fill);
expect(result.fnArray[2]).toEqual(OPS.fill);
});
it('should handle three glued operations #2', function() {
@ -100,11 +100,11 @@ describe('evaluator', function() {
var evaluator = new PartialEvaluator(new PdfManagerMock(),
new XrefMock(), new HandlerMock(),
'prefix');
var stream = new StringStream('q5 Ts');
var stream = new StringStream('f5 Ts');
var result = evaluator.getOperatorList(stream, new ResourcesMock());
expect(!!result.fnArray && !!result.argsArray).toEqual(true);
expect(result.fnArray.length).toEqual(2);
expect(result.fnArray[0]).toEqual(OPS.save);
expect(result.fnArray[0]).toEqual(OPS.fill);
expect(result.fnArray[1]).toEqual(OPS.setTextRise);
expect(result.argsArray.length).toEqual(2);
expect(result.argsArray[1].length).toEqual(1);
@ -115,13 +115,13 @@ describe('evaluator', function() {
var evaluator = new PartialEvaluator(new PdfManagerMock(),
new XrefMock(), new HandlerMock(),
'prefix');
var stream = new StringStream('trueifalserinullq');
var stream = new StringStream('trueifalserinullh');
var result = evaluator.getOperatorList(stream, new ResourcesMock());
expect(!!result.fnArray && !!result.argsArray).toEqual(true);
expect(result.fnArray.length).toEqual(3);
expect(result.fnArray[0]).toEqual(OPS.setFlatness);
expect(result.fnArray[1]).toEqual(OPS.setRenderingIntent);
expect(result.fnArray[2]).toEqual(OPS.save);
expect(result.fnArray[2]).toEqual(OPS.closePath);
expect(result.argsArray.length).toEqual(3);
expect(result.argsArray[0].length).toEqual(1);
expect(result.argsArray[0][0]).toEqual(true);
@ -163,6 +163,19 @@ describe('evaluator', function() {
expect(result.argsArray).toEqual([]);
expect(result.fnArray).toEqual([]);
});
it('should close opened saves', function() {
var evaluator = new PartialEvaluator(new PdfManagerMock(),
new XrefMock(), new HandlerMock(),
'prefix');
var stream = new StringStream('qq');
var result = evaluator.getOperatorList(stream, new ResourcesMock());
expect(!!result.fnArray && !!result.argsArray).toEqual(true);
expect(result.fnArray.length).toEqual(4);
expect(result.fnArray[0]).toEqual(OPS.save);
expect(result.fnArray[1]).toEqual(OPS.save);
expect(result.fnArray[2]).toEqual(OPS.restore);
expect(result.fnArray[3]).toEqual(OPS.restore);
});
});
});