Browse Source

Extracts evaluator preprocessor and refactor text extraction

Yury Delendik 11 years ago
parent
commit
09f8f951c8
  1. 494
      src/core/evaluator.js
  2. 15
      src/core/parser.js
  3. 7
      src/display/canvas.js
  4. 33
      test/unit/evaluator_spec.js

494
src/core/evaluator.js

@ -40,119 +40,6 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
this.fontCache = fontCache; 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; var TILING_PATTERN = 1, SHADING_PATTERN = 2;
PartialEvaluator.prototype = { PartialEvaluator.prototype = {
@ -198,7 +85,8 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
buildFormXObject: function PartialEvaluator_buildFormXObject(resources, buildFormXObject: function PartialEvaluator_buildFormXObject(resources,
xobj, smask, xobj, smask,
operatorList) { operatorList,
state) {
var self = this; var self = this;
var matrix = xobj.dict.get('Matrix'); var matrix = xobj.dict.get('Matrix');
@ -226,7 +114,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
operatorList.addOp(OPS.paintFormXObjectBegin, [matrix, bbox]); operatorList.addOp(OPS.paintFormXObjectBegin, [matrix, bbox]);
this.getOperatorList(xobj, xobj.dict.get('Resources') || resources, this.getOperatorList(xobj, xobj.dict.get('Resources') || resources,
operatorList); operatorList, state);
operatorList.addOp(OPS.paintFormXObjectEnd, []); operatorList.addOp(OPS.paintFormXObjectEnd, []);
if (group) { if (group) {
@ -532,7 +420,8 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
getOperatorList: function PartialEvaluator_getOperatorList(stream, getOperatorList: function PartialEvaluator_getOperatorList(stream,
resources, resources,
operatorList) { operatorList,
evaluatorState) {
var self = this; var self = this;
var xref = this.xref; var xref = this.xref;
@ -543,54 +432,16 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
resources = resources || new Dict(); resources = resources || new Dict();
var xobjs = resources.get('XObject') || new Dict(); var xobjs = resources.get('XObject') || new Dict();
var patterns = resources.get('Pattern') || new Dict(); var patterns = resources.get('Pattern') || new Dict();
// TODO(mduan): pass array of knownCommands rather than OP_MAP var preprocessor = new EvaluatorPreprocessor(stream, xref);
// dictionary if (evaluatorState) {
var parser = new Parser(new Lexer(stream, OP_MAP), false, xref); preprocessor.setState(evaluatorState);
var promise = new LegacyPromise();
var args = [];
while (true) {
var obj = parser.getObj();
if (isEOF(obj)) {
break;
} }
if (isCmd(obj)) { var promise = new LegacyPromise();
var cmd = obj.cmd; var operation;
while ((operation = preprocessor.read())) {
// Check that the command is valid var args = operation.args;
var opSpec = OP_MAP[cmd]; var fn = operation.fn;
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
switch (fn) { switch (fn) {
case OPS.setStrokeColorN: case OPS.setStrokeColorN:
@ -642,7 +493,8 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
); );
if ('Form' == type.name) { if ('Form' == type.name) {
self.buildFormXObject(resources, xobj, null, operatorList); self.buildFormXObject(resources, xobj, null, operatorList,
preprocessor.getState());
args = []; args = [];
continue; continue;
} else if ('Image' == type.name) { } else if ('Image' == type.name) {
@ -733,12 +585,12 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
} // switch } // switch
operatorList.addOp(fn, args); 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; 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. // The xobj is parsed iff it's needed, e.g. if there is a `DO` cmd.
var xobjs = null; var xobjs = null;
var parser = new Parser(new Lexer(stream), false); var preprocessor = new EvaluatorPreprocessor(stream, xref);
var res = resources; var res = resources;
var args = [], obj;
var chunk = ''; var chunk = '';
var font = null; var font = null;
var charSpace = 0, wordSpace = 0; var charSpace = 0, wordSpace = 0;
while (!isEOF(obj = parser.getObj())) { var operation;
if (isCmd(obj)) { while ((operation = preprocessor.read())) {
var cmd = obj.cmd; var fn = operation.fn;
switch (cmd) { var args = operation.args;
switch (fn) {
// TODO: Add support for SAVE/RESTORE and XFORM here. // TODO: Add support for SAVE/RESTORE and XFORM here.
case 'Tf': case OPS.setFont:
font = handleSetFont(args[0].name).translated; font = handleSetFont(args[0].name).translated;
textState.fontSize = args[1]; textState.fontSize = args[1];
break; break;
case 'Ts': case OPS.setTextRise:
textState.textRise = args[0]; textState.textRise = args[0];
break; break;
case 'Tz': case OPS.setHScale:
textState.textHScale = args[0] / 100; textState.textHScale = args[0] / 100;
break; break;
case 'TL': case OPS.setLeading:
textState.leading = args[0]; textState.leading = args[0];
break; break;
case 'Td': case OPS.moveText:
textState.translateTextMatrix(args[0], args[1]); textState.translateTextMatrix(args[0], args[1]);
break; break;
case 'TD': case OPS.setLeadingMoveText:
textState.leading = -args[1]; textState.leading = -args[1];
textState.translateTextMatrix(args[0], args[1]); textState.translateTextMatrix(args[0], args[1]);
break; break;
case 'T*': case OPS.nextLine:
textState.translateTextMatrix(0, -textState.leading); textState.translateTextMatrix(0, -textState.leading);
break; break;
case 'Tm': case OPS.setTextMatrix:
textState.setTextMatrix(args[0], args[1], textState.setTextMatrix(args[0], args[1],
args[2], args[3], args[4], args[5]); args[2], args[3], args[4], args[5]);
break; break;
case 'Tc': case OPS.setCharSpacing:
charSpace = args[0]; charSpace = args[0];
break; break;
case 'Tw': case OPS.setWordSpacing:
wordSpace = args[0]; wordSpace = args[0];
break; break;
case 'q': case OPS.beginText:
textState.push();
break;
case 'Q':
textState.pop();
break;
case 'BT':
textState.initialiseTextObj(); textState.initialiseTextObj();
break; break;
case 'cm': case OPS.showSpacedText:
textState.transformCTM(args[0], args[1], args[2],
args[3], args[4], args[5]);
break;
case 'TJ':
var items = args[0]; var items = args[0];
for (var j = 0, jj = items.length; j < jj; j++) { for (var j = 0, jj = items.length; j < jj; j++) {
if (typeof items[j] === 'string') { if (typeof items[j] === 'string') {
@ -851,20 +693,20 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
} }
} }
break; break;
case 'Tj': case OPS.showText:
chunk += fontCharsToUnicode(args[0], font); chunk += fontCharsToUnicode(args[0], font);
break; break;
case '\'': case OPS.nextLineShowText:
// For search, adding a extra white space for line breaks would be // For search, adding a extra white space for line breaks would be
// better here, but that causes too much spaces in the // better here, but that causes too much spaces in the
// text-selection divs. // text-selection divs.
chunk += fontCharsToUnicode(args[0], font); chunk += fontCharsToUnicode(args[0], font);
break; break;
case '"': case OPS.nextLineSetSpacingShowText:
// Note comment in "'" // Note comment in "'"
chunk += fontCharsToUnicode(args[2], font); chunk += fontCharsToUnicode(args[2], font);
break; break;
case 'Do': case OPS.paintXObject:
// Set the chunk such that the following if won't add something // Set the chunk such that the following if won't add something
// to the state. // to the state.
chunk = ''; chunk = '';
@ -898,7 +740,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
state state
); );
break; break;
case 'gs': case OPS.setGState:
var dictName = args[0]; var dictName = args[0];
var extGState = resources.get('ExtGState'); var extGState = resources.get('ExtGState');
@ -917,7 +759,11 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
if (chunk !== '') { if (chunk !== '') {
var bidiText = PDFJS.bidi(chunk, -1, font.vertical); 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 fontHeight = textState.fontSize * renderParams.vScale;
var fontAscent = font.ascent ? font.ascent * fontHeight : var fontAscent = font.ascent ? font.ascent * fontHeight :
font.descent ? (1 + font.descent) * fontHeight : fontHeight; font.descent ? (1 + font.descent) * fontHeight : fontHeight;
@ -933,12 +779,6 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
chunk = ''; chunk = '';
} }
args = [];
} else if (obj !== null && obj !== undefined) {
assertWellFormed(args.length <= 33, 'Too many arguments');
args.push(obj);
}
} // while } // while
return state; return state;
@ -1597,7 +1437,6 @@ var OperatorList = (function OperatorListClosure() {
var TextState = (function TextStateClosure() { var TextState = (function TextStateClosure() {
function TextState() { function TextState() {
this.fontSize = 0; this.fontSize = 0;
this.ctm = [1, 0, 0, 1, 0, 0];
this.textMatrix = [1, 0, 0, 1, 0, 0]; this.textMatrix = [1, 0, 0, 1, 0, 0];
this.stateStack = []; this.stateStack = [];
//textState variables //textState variables
@ -1606,15 +1445,6 @@ var TextState = (function TextStateClosure() {
this.textRise = 0; this.textRise = 0;
} }
TextState.prototype = { 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() { initialiseTextObj: function TextState_initialiseTextObj() {
var m = this.textMatrix; var m = this.textMatrix;
m[0] = 1, m[1] = 0, m[2] = 0, m[3] = 1, m[4] = 0, m[5] = 0; 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; var m = this.textMatrix;
m[0] = a, m[1] = b, m[2] = c, m[3] = d, m[4] = e, m[5] = f; 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) { translateTextMatrix: function TextState_translateTextMatrix(x, y) {
var m = this.textMatrix; var m = this.textMatrix;
m[4] = m[0] * x + m[2] * y + m[4]; m[4] = m[0] * x + m[2] * y + m[4];
m[5] = m[1] * x + m[3] * y + m[5]; m[5] = m[1] * x + m[3] * y + m[5];
}, },
calcRenderParams: function TextState_calcRenderingParams() { calcRenderParams: function TextState_calcRenderingParams(cm) {
var tm = this.textMatrix; var tm = this.textMatrix;
var cm = this.ctm;
var a = this.fontSize; var a = this.fontSize;
var b = a * this.textHScale; var b = a * this.textHScale;
var c = this.textRise; var c = this.textRise;
@ -1683,3 +1502,218 @@ var EvalState = (function EvalStateClosure() {
return EvalState; 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;
})();

15
src/core/parser.js

@ -36,21 +36,6 @@ var Parser = (function ParserClosure() {
} }
Parser.prototype = { 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() { refill: function Parser_refill() {
this.buf1 = this.lexer.getObj(); this.buf1 = this.lexer.getObj();
this.buf2 = this.lexer.getObj(); this.buf2 = this.lexer.getObj();

7
src/display/canvas.js

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

33
test/unit/evaluator_spec.js

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

Loading…
Cancel
Save