Browse Source

Skip commands that have too few arguments

- Commands that have too few args will be skipped
- Commands that have too many args will generate an info, but still
execute
mduan 12 years ago
parent
commit
5ab3bb1e03
  1. 194
      src/evaluator.js
  2. 1
      test/pdfs/.gitignore
  3. 131
      test/pdfs/issue2391-1.pdf
  4. 7
      test/test_manifest.json
  5. 55
      test/unit/evaluator_spec.js

194
src/evaluator.js

@ -30,101 +30,104 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
this.fontIdCounter = 0; this.fontIdCounter = 0;
} }
// Specifies properties for each command
//
// If variableArgs === true: [0, `numArgs`] expected
// If variableArgs === false: exactly `numArgs` expected
var OP_MAP = { var OP_MAP = {
// Graphics state // Graphic state
w: 'setLineWidth', w: { fnName: 'setLineWidth', numArgs: 1, variableArgs: false },
J: 'setLineCap', J: { fnName: 'setLineCap', numArgs: 1, variableArgs: false },
j: 'setLineJoin', j: { fnName: 'setLineJoin', numArgs: 1, variableArgs: false },
M: 'setMiterLimit', M: { fnName: 'setMiterLimit', numArgs: 1, variableArgs: false },
d: 'setDash', d: { fnName: 'setDash', numArgs: 2, variableArgs: false },
ri: 'setRenderingIntent', ri: { fnName: 'setRenderingIntent', numArgs: 1, variableArgs: false },
i: 'setFlatness', i: { fnName: 'setFlatness', numArgs: 1, variableArgs: false },
gs: 'setGState', gs: { fnName: 'setGState', numArgs: 1, variableArgs: false },
q: 'save', q: { fnName: 'save', numArgs: 0, variableArgs: false },
Q: 'restore', Q: { fnName: 'restore', numArgs: 0, variableArgs: false },
cm: 'transform', cm: { fnName: 'transform', numArgs: 6, variableArgs: false },
// Path // Path
m: 'moveTo', m: { fnName: 'moveTo', numArgs: 2, variableArgs: false },
l: 'lineTo', l: { fnName: 'lineTo', numArgs: 2, variableArgs: false },
c: 'curveTo', c: { fnName: 'curveTo', numArgs: 6, variableArgs: false },
v: 'curveTo2', v: { fnName: 'curveTo2', numArgs: 4, variableArgs: false },
y: 'curveTo3', y: { fnName: 'curveTo3', numArgs: 4, variableArgs: false },
h: 'closePath', h: { fnName: 'closePath', numArgs: 0, variableArgs: false },
re: 'rectangle', re: { fnName: 'rectangle', numArgs: 4, variableArgs: false },
S: 'stroke', S: { fnName: 'stroke', numArgs: 0, variableArgs: false },
s: 'closeStroke', s: { fnName: 'closeStroke', numArgs: 0, variableArgs: false },
f: 'fill', f: { fnName: 'fill', numArgs: 0, variableArgs: false },
F: 'fill', F: { fnName: 'fill', numArgs: 0, variableArgs: false },
'f*': 'eoFill', 'f*': { fnName: 'eoFill', numArgs: 0, variableArgs: false },
B: 'fillStroke', B: { fnName: 'fillStroke', numArgs: 0, variableArgs: false },
'B*': 'eoFillStroke', 'B*': { fnName: 'eoFillStroke', numArgs: 0, variableArgs: false },
b: 'closeFillStroke', b: { fnName: 'closeFillStroke', numArgs: 0, variableArgs: false },
'b*': 'closeEOFillStroke', 'b*': { fnName: 'closeEOFillStroke', numArgs: 0, variableArgs: false },
n: 'endPath', n: { fnName: 'endPath', numArgs: 0, variableArgs: false },
// Clipping // Clipping
W: 'clip', W: { fnName: 'clip', numArgs: 0, variableArgs: false },
'W*': 'eoClip', 'W*': { fnName: 'eoClip', numArgs: 0, variableArgs: false },
// Text // Text
BT: 'beginText', BT: { fnName: 'beginText', numArgs: 0, variableArgs: false },
ET: 'endText', ET: { fnName: 'endText', numArgs: 0, variableArgs: false },
Tc: 'setCharSpacing', Tc: { fnName: 'setCharSpacing', numArgs: 1, variableArgs: false },
Tw: 'setWordSpacing', Tw: { fnName: 'setWordSpacing', numArgs: 1, variableArgs: false },
Tz: 'setHScale', Tz: { fnName: 'setHScale', numArgs: 1, variableArgs: false },
TL: 'setLeading', TL: { fnName: 'setLeading', numArgs: 1, variableArgs: false },
Tf: 'setFont', Tf: { fnName: 'setFont', numArgs: 2, variableArgs: false },
Tr: 'setTextRenderingMode', Tr: { fnName: 'setTextRenderingMode', numArgs: 1, variableArgs: false },
Ts: 'setTextRise', Ts: { fnName: 'setTextRise', numArgs: 1, variableArgs: false },
Td: 'moveText', Td: { fnName: 'moveText', numArgs: 2, variableArgs: false },
TD: 'setLeadingMoveText', TD: { fnName: 'setLeadingMoveText', numArgs: 2, variableArgs: false },
Tm: 'setTextMatrix', Tm: { fnName: 'setTextMatrix', numArgs: 6, variableArgs: false },
'T*': 'nextLine', 'T*': { fnName: 'nextLine', numArgs: 0, variableArgs: false },
Tj: 'showText', Tj: { fnName: 'showText', numArgs: 1, variableArgs: false },
TJ: 'showSpacedText', TJ: { fnName: 'showSpacedText', numArgs: 1, variableArgs: false },
"'": 'nextLineShowText', '\'': { fnName: 'nextLineShowText', numArgs: 1, variableArgs: false },
'"': 'nextLineSetSpacingShowText', '"': { fnName: 'nextLineSetSpacingShowText', numArgs: 3,
variableArgs: false },
// Type3 fonts // Type3 fonts
d0: 'setCharWidth', d0: { fnName: 'setCharWidth', numArgs: 2, variableArgs: false },
d1: 'setCharWidthAndBounds', d1: { fnName: 'setCharWidthAndBounds', numArgs: 6, variableArgs: false },
// Color // Color
CS: 'setStrokeColorSpace', CS: { fnName: 'setStrokeColorSpace', numArgs: 1, variableArgs: false },
cs: 'setFillColorSpace', cs: { fnName: 'setFillColorSpace', numArgs: 1, variableArgs: false },
SC: 'setStrokeColor', SC: { fnName: 'setStrokeColor', numArgs: 4, variableArgs: true },
SCN: 'setStrokeColorN', SCN: { fnName: 'setStrokeColorN', numArgs: 33, variableArgs: true },
sc: 'setFillColor', sc: { fnName: 'setFillColor', numArgs: 4, variableArgs: true },
scn: 'setFillColorN', scn: { fnName: 'setFillColorN', numArgs: 33, variableArgs: true },
G: 'setStrokeGray', G: { fnName: 'setStrokeGray', numArgs: 1, variableArgs: false },
g: 'setFillGray', g: { fnName: 'setFillGray', numArgs: 1, variableArgs: false },
RG: 'setStrokeRGBColor', RG: { fnName: 'setStrokeRGBColor', numArgs: 3, variableArgs: false },
rg: 'setFillRGBColor', rg: { fnName: 'setFillRGBColor', numArgs: 3, variableArgs: false },
K: 'setStrokeCMYKColor', K: { fnName: 'setStrokeCMYKColor', numArgs: 4, variableArgs: false },
k: 'setFillCMYKColor', k: { fnName: 'setFillCMYKColor', numArgs: 4, variableArgs: false },
// Shading // Shading
sh: 'shadingFill', sh: { fnName: 'shadingFill', numArgs: 1, variableArgs: false },
// Images // Images
BI: 'beginInlineImage', BI: { fnName: 'beginInlineImage', numArgs: 0, variableArgs: false },
ID: 'beginImageData', ID: { fnName: 'beginImageData', numArgs: 0, variableArgs: false },
EI: 'endInlineImage', EI: { fnName: 'endInlineImage', numArgs: 0, variableArgs: false },
// XObjects // XObjects
Do: 'paintXObject', Do: { fnName: 'paintXObject', numArgs: 1, variableArgs: false },
MP: { fnName: 'markPoint', numArgs: 1, variableArgs: false },
// Marked content DP: { fnName: 'markPointProps', numArgs: 2, variableArgs: false },
MP: 'markPoint', BMC: { fnName: 'beginMarkedContent', numArgs: 1, variableArgs: false },
DP: 'markPointProps', BDC: { fnName: 'beginMarkedContentProps', numArgs: 2, variableArgs: false },
BMC: 'beginMarkedContent', EMC: { fnName: 'endMarkedContent', numArgs: 0, variableArgs: false },
BDC: 'beginMarkedContentProps',
EMC: 'endMarkedContent',
// Compatibility // Compatibility
BX: 'beginCompat', BX: { fnName: 'beginCompat', numArgs: 0, variableArgs: false },
EX: 'endCompat', EX: { fnName: 'endCompat', numArgs: 0, variableArgs: false },
// (reserved partial commands for the lexer) // (reserved partial commands for the lexer)
BM: null, BM: null,
@ -314,6 +317,8 @@ 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
// dictionary
var parser = new Parser(new Lexer(stream, OP_MAP), false, xref); var parser = new Parser(new Lexer(stream, OP_MAP), false, xref);
var res = resources; var res = resources;
var args = [], obj; var args = [], obj;
@ -321,13 +326,42 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
while (true) { while (true) {
obj = parser.getObj(); obj = parser.getObj();
if (isEOF(obj)) if (isEOF(obj)) {
break; break;
}
if (isCmd(obj)) { if (isCmd(obj)) {
var cmd = obj.cmd; var cmd = obj.cmd;
var fn = OP_MAP[cmd];
assertWellFormed(fn, 'Unknown command "' + cmd + '"'); // Check that the command is valid
var opSpec = OP_MAP[cmd];
if (!opSpec) {
warn('Unknown command "' + cmd + '"');
continue;
}
var fn = opSpec.fnName;
// 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 // TODO figure out how to type-check vararg functions
if ((cmd == 'SCN' || cmd == 'scn') && !args[args.length - 1].code) { if ((cmd == 'SCN' || cmd == 'scn') && !args[args.length - 1].code) {
@ -509,8 +543,8 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
argsArray.push(args); argsArray.push(args);
args = []; args = [];
} else if (obj != null) { } else if (obj != null) {
assertWellFormed(args.length <= 33, 'Too many arguments');
args.push(obj instanceof Dict ? obj.getAll() : obj); args.push(obj instanceof Dict ? obj.getAll() : obj);
assertWellFormed(args.length <= 33, 'Too many arguments');
} }
} }

1
test/pdfs/.gitignore vendored

@ -1,6 +1,7 @@
*.pdf *.pdf
!tracemonkey.pdf !tracemonkey.pdf
!issue2391-1.pdf
!ArabicCIDTrueType.pdf !ArabicCIDTrueType.pdf
!ThuluthFeatures.pdf !ThuluthFeatures.pdf
!arial_unicode_ab_cidfont.pdf !arial_unicode_ab_cidfont.pdf

131
test/pdfs/issue2391-1.pdf

@ -0,0 +1,131 @@
%PDF-1.4
%âãÏÓ
1 0 obj
<<
/Pages 2 0 R
/Metadata 3 0 R
/Type /Catalog
>>
endobj
2 0 obj
<<
/Kids [4 0 R]
/Count 1
/Type /Pages
>>
endobj
4 0 obj
<<
/Rotate 0
/Parent 2 0 R
/Resources
<<
/ExtGState 5 0 R
/Font 6 0 R
/ProcSet [/PDF /Text]
>>
/MediaBox [0 0 595 842]
/Contents 7 0 R
/Type /Page
>>
endobj
7 0 obj
<<
/Length 148
>>
stream
q 0.1 0 0 0.1 0 0 cm
/R7 gs
0 g
q
10 0 0 10 0 0 cm BT
undefined 10 Tf
/R8 10 Tf
1 0 0 1 29 805 Tm
(test command with wrong number of args)Tj
ET
Q
Q
endstream
endobj
8 0 obj
<<
/Type /ExtGState
/OPM 1
>>
endobj
9 0 obj
<<
/BaseFont /Courier
/Subtype /Type1
/Type /Font
>>
endobj
5 0 obj
<<
/R7 8 0 R
>>
endobj
6 0 obj
<<
/R8 9 0 R
>>
endobj
3 0 obj
<<
/Subtype /XML
/Length 1421
/Type /Metadata
>>
stream
<?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d'?>
<?adobe-xap-filters esc="CRLF"?>
<x:xmpmeta xmlns:x='adobe:ns:meta/' x:xmptk='XMP toolkit 2.9.1-13, framework 1.6'>
<rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns:iX='http://ns.adobe.com/iX/1.0/'>
<rdf:Description rdf:about='uuid:39048ebf-93ad-11ed-0000-3d9120169d5e' xmlns:pdf='http://ns.adobe.com/pdf/1.3/' pdf:Producer='GPL Ghostscript 9.05'/>
<rdf:Description rdf:about='uuid:39048ebf-93ad-11ed-0000-3d9120169d5e' xmlns:xmp='http://ns.adobe.com/xap/1.0/'><xmp:ModifyDate>2013-01-10T17:46:10-08:00</xmp:ModifyDate>
<xmp:CreateDate>2013-01-10T17:46:10-08:00</xmp:CreateDate>
<xmp:CreatorTool>GNU Enscript 1.6.6</xmp:CreatorTool></rdf:Description>
<rdf:Description rdf:about='uuid:39048ebf-93ad-11ed-0000-3d9120169d5e' xmlns:xapMM='http://ns.adobe.com/xap/1.0/mm/' xapMM:DocumentID='uuid:39048ebf-93ad-11ed-0000-3d9120169d5e'/>
<rdf:Description rdf:about='uuid:39048ebf-93ad-11ed-0000-3d9120169d5e' xmlns:dc='http://purl.org/dc/elements/1.1/' dc:format='application/pdf'><dc:title><rdf:Alt><rdf:li xml:lang='x-default'>Enscript Output</rdf:li></rdf:Alt></dc:title><dc:creator><rdf:Seq><rdf:li>Mack Duan</rdf:li></rdf:Seq></dc:creator></rdf:Description>
</rdf:RDF>
</x:xmpmeta>
<?xpacket end='w'?>
endstream
endobj
10 0 obj
<<
/Creator (GNU Enscript 1.6.6)
/Title (Enscript Output)
/Producer (GPL Ghostscript 9.05)
/Author (Mack Duan)
/ModDate (D:20130110174610-08'00')
/CreationDate (D:20130110174610-08'00')
>>
endobj xref
0 11
0000000000 65535 f
0000000015 00000 n
0000000082 00000 n
0000000694 00000 n
0000000141 00000 n
0000000628 00000 n
0000000661 00000 n
0000000309 00000 n
0000000511 00000 n
0000000558 00000 n
0000002200 00000 n
trailer
<<
/Info 10 0 R
/Root 1 0 R
/Size 11
/ID [<5807376a23851d4cc6096009432892a3> <5807376a23851d4cc6096009432892a3>]
>>
startxref
2406
%%EOF

7
test/test_manifest.json

@ -17,6 +17,13 @@
"rounds": 1, "rounds": 1,
"type": "text" "type": "text"
}, },
{ "id": "issue2391-1",
"file": "pdfs/issue2391-1.pdf",
"md5": "25ae9cb959612e7b343b55da63af2716",
"rounds": 1,
"pageLimit": 1,
"type": "load"
},
{ "id": "html5-canvas-cheat-sheet-load", { "id": "html5-canvas-cheat-sheet-load",
"file": "pdfs/canvas.pdf", "file": "pdfs/canvas.pdf",
"md5": "59510028561daf62e00bf9f6f066b033", "md5": "59510028561daf62e00bf9f6f066b033",

55
test/unit/evaluator_spec.js

@ -32,13 +32,12 @@ describe('evaluator', function() {
var evaluator = new PartialEvaluator(new XrefMock(), new HandlerMock(), var evaluator = new PartialEvaluator(new XrefMock(), new HandlerMock(),
'prefix'); 'prefix');
var stream = new StringStream('qTT'); var stream = new StringStream('qTT');
var thrown = false; var result = evaluator.getOperatorList(stream, new ResourcesMock(), []);
try {
evaluator.getOperatorList(stream, new ResourcesMock(), []); expect(!!result.fnArray && !!result.argsArray).toEqual(true);
} catch (e) { expect(result.fnArray.length).toEqual(1);
thrown = e; expect(result.fnArray[0]).toEqual('save');
} expect(result.argsArray[0].length).toEqual(0);
expect(thrown).toNotEqual(false);
}); });
it('should handle one operations', function() { it('should handle one operations', function() {
@ -84,14 +83,14 @@ describe('evaluator', function() {
'prefix'); 'prefix');
var resources = new ResourcesMock(); var resources = new ResourcesMock();
resources.Res1 = {}; resources.Res1 = {};
var stream = new StringStream('B*BBMC'); var stream = new StringStream('B*Bf*');
var result = evaluator.getOperatorList(stream, resources, []); var result = evaluator.getOperatorList(stream, resources, []);
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('eoFillStroke'); expect(result.fnArray[0]).toEqual('eoFillStroke');
expect(result.fnArray[1]).toEqual('fillStroke'); expect(result.fnArray[1]).toEqual('fillStroke');
expect(result.fnArray[2]).toEqual('beginMarkedContent'); expect(result.fnArray[2]).toEqual('eoFill');
}); });
it('should handle glued operations and operands', function() { it('should handle glued operations and operands', function() {
@ -112,19 +111,53 @@ describe('evaluator', function() {
it('should handle glued operations and literals', function() { it('should handle glued operations and literals', function() {
var evaluator = new PartialEvaluator(new XrefMock(), new HandlerMock(), var evaluator = new PartialEvaluator(new XrefMock(), new HandlerMock(),
'prefix'); 'prefix');
var stream = new StringStream('trueifalserinulli'); var stream = new StringStream('trueifalserinullq');
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('setFlatness'); expect(result.fnArray[0]).toEqual('setFlatness');
expect(result.fnArray[1]).toEqual('setRenderingIntent'); expect(result.fnArray[1]).toEqual('setRenderingIntent');
expect(result.fnArray[2]).toEqual('setFlatness'); expect(result.fnArray[2]).toEqual('save');
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);
expect(result.argsArray[1].length).toEqual(1); expect(result.argsArray[1].length).toEqual(1);
expect(result.argsArray[1][0]).toEqual(false); expect(result.argsArray[1][0]).toEqual(false);
expect(result.argsArray[2].length).toEqual(0);
});
});
describe('validateNumberOfArgs', function() {
it('should execute if correct number of arguments', function() {
var evaluator = new PartialEvaluator(new XrefMock(), new HandlerMock(),
'prefix');
var stream = new StringStream('5 1 d0');
var result = evaluator.getOperatorList(stream, new ResourcesMock(), []);
expect(result.argsArray[0][0]).toEqual(5);
expect(result.argsArray[0][1]).toEqual(1);
expect(result.fnArray[0]).toEqual('setCharWidth');
});
it('should execute if too many arguments', function() {
var evaluator = new PartialEvaluator(new XrefMock(), new HandlerMock(),
'prefix');
var stream = new StringStream('5 1 4 d0');
var result = evaluator.getOperatorList(stream, new ResourcesMock(), []);
expect(result.argsArray[0][0]).toEqual(5);
expect(result.argsArray[0][1]).toEqual(1);
expect(result.argsArray[0][2]).toEqual(4);
expect(result.fnArray[0]).toEqual('setCharWidth');
});
it('should skip if too few arguments', function() {
var evaluator = new PartialEvaluator(new XrefMock(), new HandlerMock(),
'prefix');
var stream = new StringStream('5 d0');
var result = evaluator.getOperatorList(stream, new ResourcesMock(), []);
expect(result.argsArray).toEqual([]);
expect(result.fnArray).toEqual([]);
}); });
}); });
}); });

Loading…
Cancel
Save