diff --git a/src/evaluator.js b/src/evaluator.js
index 1c41f8409..3da4322c0 100644
--- a/src/evaluator.js
+++ b/src/evaluator.js
@@ -30,101 +30,104 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
this.fontIdCounter = 0;
}
+ // Specifies properties for each command
+ //
+ // If variableArgs === true: [0, `numArgs`] expected
+ // If variableArgs === false: exactly `numArgs` expected
var OP_MAP = {
- // Graphics state
- w: 'setLineWidth',
- J: 'setLineCap',
- j: 'setLineJoin',
- M: 'setMiterLimit',
- d: 'setDash',
- ri: 'setRenderingIntent',
- i: 'setFlatness',
- gs: 'setGState',
- q: 'save',
- Q: 'restore',
- cm: 'transform',
+ // Graphic state
+ w: { fnName: 'setLineWidth', numArgs: 1, variableArgs: false },
+ J: { fnName: 'setLineCap', numArgs: 1, variableArgs: false },
+ j: { fnName: 'setLineJoin', numArgs: 1, variableArgs: false },
+ M: { fnName: 'setMiterLimit', numArgs: 1, variableArgs: false },
+ d: { fnName: 'setDash', numArgs: 2, variableArgs: false },
+ ri: { fnName: 'setRenderingIntent', numArgs: 1, variableArgs: false },
+ i: { fnName: 'setFlatness', numArgs: 1, variableArgs: false },
+ gs: { fnName: 'setGState', numArgs: 1, variableArgs: false },
+ q: { fnName: 'save', numArgs: 0, variableArgs: false },
+ Q: { fnName: 'restore', numArgs: 0, variableArgs: false },
+ cm: { fnName: 'transform', numArgs: 6, variableArgs: false },
// Path
- m: 'moveTo',
- l: 'lineTo',
- c: 'curveTo',
- v: 'curveTo2',
- y: 'curveTo3',
- h: 'closePath',
- re: 'rectangle',
- S: 'stroke',
- s: 'closeStroke',
- f: 'fill',
- F: 'fill',
- 'f*': 'eoFill',
- B: 'fillStroke',
- 'B*': 'eoFillStroke',
- b: 'closeFillStroke',
- 'b*': 'closeEOFillStroke',
- n: 'endPath',
+ m: { fnName: 'moveTo', numArgs: 2, variableArgs: false },
+ l: { fnName: 'lineTo', numArgs: 2, variableArgs: false },
+ c: { fnName: 'curveTo', numArgs: 6, variableArgs: false },
+ v: { fnName: 'curveTo2', numArgs: 4, variableArgs: false },
+ y: { fnName: 'curveTo3', numArgs: 4, variableArgs: false },
+ h: { fnName: 'closePath', numArgs: 0, variableArgs: false },
+ re: { fnName: 'rectangle', numArgs: 4, variableArgs: false },
+ S: { fnName: 'stroke', numArgs: 0, variableArgs: false },
+ s: { fnName: 'closeStroke', numArgs: 0, variableArgs: false },
+ f: { fnName: 'fill', numArgs: 0, variableArgs: false },
+ F: { fnName: 'fill', numArgs: 0, variableArgs: false },
+ 'f*': { fnName: 'eoFill', numArgs: 0, variableArgs: false },
+ B: { fnName: 'fillStroke', numArgs: 0, variableArgs: false },
+ 'B*': { fnName: 'eoFillStroke', numArgs: 0, variableArgs: false },
+ b: { fnName: 'closeFillStroke', numArgs: 0, variableArgs: false },
+ 'b*': { fnName: 'closeEOFillStroke', numArgs: 0, variableArgs: false },
+ n: { fnName: 'endPath', numArgs: 0, variableArgs: false },
// Clipping
- W: 'clip',
- 'W*': 'eoClip',
+ W: { fnName: 'clip', numArgs: 0, variableArgs: false },
+ 'W*': { fnName: 'eoClip', numArgs: 0, variableArgs: false },
// Text
- BT: 'beginText',
- ET: 'endText',
- Tc: 'setCharSpacing',
- Tw: 'setWordSpacing',
- Tz: 'setHScale',
- TL: 'setLeading',
- Tf: 'setFont',
- Tr: 'setTextRenderingMode',
- Ts: 'setTextRise',
- Td: 'moveText',
- TD: 'setLeadingMoveText',
- Tm: 'setTextMatrix',
- 'T*': 'nextLine',
- Tj: 'showText',
- TJ: 'showSpacedText',
- "'": 'nextLineShowText',
- '"': 'nextLineSetSpacingShowText',
+ BT: { fnName: 'beginText', numArgs: 0, variableArgs: false },
+ ET: { fnName: 'endText', numArgs: 0, variableArgs: false },
+ Tc: { fnName: 'setCharSpacing', numArgs: 1, variableArgs: false },
+ Tw: { fnName: 'setWordSpacing', numArgs: 1, variableArgs: false },
+ Tz: { fnName: 'setHScale', numArgs: 1, variableArgs: false },
+ TL: { fnName: 'setLeading', numArgs: 1, variableArgs: false },
+ Tf: { fnName: 'setFont', numArgs: 2, variableArgs: false },
+ Tr: { fnName: 'setTextRenderingMode', numArgs: 1, variableArgs: false },
+ Ts: { fnName: 'setTextRise', numArgs: 1, variableArgs: false },
+ Td: { fnName: 'moveText', numArgs: 2, variableArgs: false },
+ TD: { fnName: 'setLeadingMoveText', numArgs: 2, variableArgs: false },
+ Tm: { fnName: 'setTextMatrix', numArgs: 6, variableArgs: false },
+ 'T*': { fnName: 'nextLine', numArgs: 0, variableArgs: false },
+ Tj: { fnName: 'showText', numArgs: 1, variableArgs: false },
+ TJ: { fnName: 'showSpacedText', numArgs: 1, variableArgs: false },
+ '\'': { fnName: 'nextLineShowText', numArgs: 1, variableArgs: false },
+ '"': { fnName: 'nextLineSetSpacingShowText', numArgs: 3,
+ variableArgs: false },
// Type3 fonts
- d0: 'setCharWidth',
- d1: 'setCharWidthAndBounds',
+ d0: { fnName: 'setCharWidth', numArgs: 2, variableArgs: false },
+ d1: { fnName: 'setCharWidthAndBounds', numArgs: 6, variableArgs: false },
// Color
- CS: 'setStrokeColorSpace',
- cs: 'setFillColorSpace',
- SC: 'setStrokeColor',
- SCN: 'setStrokeColorN',
- sc: 'setFillColor',
- scn: 'setFillColorN',
- G: 'setStrokeGray',
- g: 'setFillGray',
- RG: 'setStrokeRGBColor',
- rg: 'setFillRGBColor',
- K: 'setStrokeCMYKColor',
- k: 'setFillCMYKColor',
+ CS: { fnName: 'setStrokeColorSpace', numArgs: 1, variableArgs: false },
+ cs: { fnName: 'setFillColorSpace', numArgs: 1, variableArgs: false },
+ SC: { fnName: 'setStrokeColor', numArgs: 4, variableArgs: true },
+ SCN: { fnName: 'setStrokeColorN', numArgs: 33, variableArgs: true },
+ sc: { fnName: 'setFillColor', numArgs: 4, variableArgs: true },
+ scn: { fnName: 'setFillColorN', numArgs: 33, variableArgs: true },
+ G: { fnName: 'setStrokeGray', numArgs: 1, variableArgs: false },
+ g: { fnName: 'setFillGray', numArgs: 1, variableArgs: false },
+ RG: { fnName: 'setStrokeRGBColor', numArgs: 3, variableArgs: false },
+ rg: { fnName: 'setFillRGBColor', numArgs: 3, variableArgs: false },
+ K: { fnName: 'setStrokeCMYKColor', numArgs: 4, variableArgs: false },
+ k: { fnName: 'setFillCMYKColor', numArgs: 4, variableArgs: false },
// Shading
- sh: 'shadingFill',
+ sh: { fnName: 'shadingFill', numArgs: 1, variableArgs: false },
// Images
- BI: 'beginInlineImage',
- ID: 'beginImageData',
- EI: 'endInlineImage',
+ BI: { fnName: 'beginInlineImage', numArgs: 0, variableArgs: false },
+ ID: { fnName: 'beginImageData', numArgs: 0, variableArgs: false },
+ EI: { fnName: 'endInlineImage', numArgs: 0, variableArgs: false },
// XObjects
- Do: 'paintXObject',
-
- // Marked content
- MP: 'markPoint',
- DP: 'markPointProps',
- BMC: 'beginMarkedContent',
- BDC: 'beginMarkedContentProps',
- EMC: 'endMarkedContent',
+ Do: { fnName: 'paintXObject', numArgs: 1, variableArgs: false },
+ MP: { fnName: 'markPoint', numArgs: 1, variableArgs: false },
+ DP: { fnName: 'markPointProps', numArgs: 2, variableArgs: false },
+ BMC: { fnName: 'beginMarkedContent', numArgs: 1, variableArgs: false },
+ BDC: { fnName: 'beginMarkedContentProps', numArgs: 2, variableArgs: false },
+ EMC: { fnName: 'endMarkedContent', numArgs: 0, variableArgs: false },
// Compatibility
- BX: 'beginCompat',
- EX: 'endCompat',
+ BX: { fnName: 'beginCompat', numArgs: 0, variableArgs: false },
+ EX: { fnName: 'endCompat', numArgs: 0, variableArgs: false },
// (reserved partial commands for the lexer)
BM: null,
@@ -314,6 +317,8 @@ 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 res = resources;
var args = [], obj;
@@ -321,13 +326,42 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
while (true) {
obj = parser.getObj();
- if (isEOF(obj))
+ if (isEOF(obj)) {
break;
+ }
if (isCmd(obj)) {
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
if ((cmd == 'SCN' || cmd == 'scn') && !args[args.length - 1].code) {
@@ -509,8 +543,8 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
argsArray.push(args);
args = [];
} else if (obj != null) {
- assertWellFormed(args.length <= 33, 'Too many arguments');
args.push(obj instanceof Dict ? obj.getAll() : obj);
+ assertWellFormed(args.length <= 33, 'Too many arguments');
}
}
@@ -1146,10 +1180,26 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
// a variant.
var firstChar = dict.get('FirstChar') || 0;
var lastChar = dict.get('LastChar') || maxCharIndex;
+
var fontName = descriptor.get('FontName');
+ var baseFont = baseDict.get('BaseFont');
// Some bad pdf's have a string as the font name.
- if (isString(fontName))
+ if (isString(fontName)) {
fontName = new Name(fontName);
+ }
+ if (isString(baseFont)) {
+ baseFont = new Name(baseFont);
+ }
+
+ var fontNameStr = fontName && fontName.name;
+ var baseFontStr = baseFont && baseFont.name;
+ if (fontNameStr !== baseFontStr) {
+ warn('The FontDescriptor\'s FontName is "' + fontNameStr +
+ '" but should be the same as the Font\'s BaseFont "' +
+ baseFontStr + '"');
+ }
+ fontName = fontName || baseFont;
+
assertWellFormed(isName(fontName), 'invalid font name');
var fontFile = descriptor.get('FontFile', 'FontFile2', 'FontFile3');
diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore
index 32c97bad7..050b533fe 100644
--- a/test/pdfs/.gitignore
+++ b/test/pdfs/.gitignore
@@ -1,6 +1,7 @@
*.pdf
!tracemonkey.pdf
+!issue2391-1.pdf
!ArabicCIDTrueType.pdf
!ThuluthFeatures.pdf
!arial_unicode_ab_cidfont.pdf
@@ -38,3 +39,4 @@
!mixedfonts.pdf
!shading_extend.pdf
!noembed-identity.pdf
+!issue2099-1.pdf
diff --git a/test/pdfs/issue2099-1.pdf b/test/pdfs/issue2099-1.pdf
new file mode 100644
index 000000000..987006e7e
--- /dev/null
+++ b/test/pdfs/issue2099-1.pdf
@@ -0,0 +1,137 @@
+%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
+3 0 obj
+<<
+/Subtype /XML
+/Length 1421
+/Type /Metadata
+>>
+stream
+
+
+
+
+
+2013-01-11T10:57:59-08:00
+2013-01-11T10:57:59-08:00
+GNU Enscript 1.6.6
+
+Enscript OutputMack Duan
+
+
+
+
+
+endstream
+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
+5 0 obj
+<<
+/R7 8 0 R
+>>
+endobj
+6 0 obj
+<<
+/R8 9 0 R
+>>
+endobj
+7 0 obj
+<<
+/Length 138
+>>
+stream
+q 0.1 0 0 0.1 0 0 cm
+/R7 gs
+0 g
+q
+10 0 0 10 0 0 cm BT
+/R8 10 Tf
+1 0 0 1 29 805 Tm
+(test missing FontName in FontDescriptor dict)Tj
+ET
+Q
+Q
+
+endstream
+endobj
+8 0 obj
+<<
+/Type /ExtGState
+/OPM 1
+>>
+endobj
+9 0 obj
+<<
+/BaseFont /Courier
+/Subtype /Type1
+/FontDescriptor 10 0 R
+/Type /Font
+>>
+endobj
+10 0 obj
+<<
+/Type /FontDescriptor
+>>
+endobj
+11 0 obj
+<<
+/Creator (GNU Enscript 1.6.6)
+/Title (Enscript Output)
+/Producer (GPL Ghostscript 9.05)
+/Author (Mack Duan)
+/ModDate (D:20130111105759-08'00')
+/CreationDate (D:20130111105759-08'00')
+>>
+endobj xref
+0 12
+0000000000 65535 f
+0000000015 00000 n
+0000000082 00000 n
+0000000141 00000 n
+0000001647 00000 n
+0000001815 00000 n
+0000001848 00000 n
+0000001881 00000 n
+0000002073 00000 n
+0000002120 00000 n
+0000002213 00000 n
+0000002259 00000 n
+trailer
+
+<<
+/Info 11 0 R
+/Root 1 0 R
+/Size 12
+/ID [<2eee0bd94f14c7014ff602c9df19ad9a> <2eee0bd94f14c7014ff602c9df19ad9a>]
+>>
+startxref
+2465
+%%EOF
diff --git a/test/pdfs/issue2391-1.pdf b/test/pdfs/issue2391-1.pdf
new file mode 100644
index 000000000..bf0c4da75
--- /dev/null
+++ b/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
+
+
+
+
+
+2013-01-10T17:46:10-08:00
+2013-01-10T17:46:10-08:00
+GNU Enscript 1.6.6
+
+Enscript OutputMack Duan
+
+
+
+
+
+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
diff --git a/test/test_manifest.json b/test/test_manifest.json
index cf28c369d..14026c9e2 100644
--- a/test/test_manifest.json
+++ b/test/test_manifest.json
@@ -17,6 +17,13 @@
"rounds": 1,
"type": "text"
},
+ { "id": "issue2391-1",
+ "file": "pdfs/issue2391-1.pdf",
+ "md5": "25ae9cb959612e7b343b55da63af2716",
+ "rounds": 1,
+ "pageLimit": 1,
+ "type": "load"
+ },
{ "id": "html5-canvas-cheat-sheet-load",
"file": "pdfs/canvas.pdf",
"md5": "59510028561daf62e00bf9f6f066b033",
@@ -801,5 +808,11 @@
"md5": "05d3803b6c22451e18cb60d8d8c75c0c",
"rounds": 1,
"type": "eq"
+ },
+ { "id": "issue2099-1",
+ "file": "pdfs/issue2099-1.pdf",
+ "md5": "c7eca682d70a976dfc4b7e64d3e9f1ce",
+ "rounds": 1,
+ "type": "eq"
}
]
diff --git a/test/unit/evaluator_spec.js b/test/unit/evaluator_spec.js
index e31a525ac..f1f41cf37 100644
--- a/test/unit/evaluator_spec.js
+++ b/test/unit/evaluator_spec.js
@@ -32,13 +32,12 @@ describe('evaluator', function() {
var evaluator = new PartialEvaluator(new XrefMock(), new HandlerMock(),
'prefix');
var stream = new StringStream('qTT');
- var thrown = false;
- try {
- evaluator.getOperatorList(stream, new ResourcesMock(), []);
- } catch (e) {
- thrown = e;
- }
- expect(thrown).toNotEqual(false);
+ 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('save');
+ expect(result.argsArray[0].length).toEqual(0);
});
it('should handle one operations', function() {
@@ -84,14 +83,14 @@ describe('evaluator', function() {
'prefix');
var resources = new ResourcesMock();
resources.Res1 = {};
- var stream = new StringStream('B*BBMC');
+ var stream = new StringStream('B*Bf*');
var result = evaluator.getOperatorList(stream, resources, []);
expect(!!result.fnArray && !!result.argsArray).toEqual(true);
expect(result.fnArray.length).toEqual(3);
expect(result.fnArray[0]).toEqual('eoFillStroke');
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() {
@@ -112,19 +111,53 @@ describe('evaluator', function() {
it('should handle glued operations and literals', function() {
var evaluator = new PartialEvaluator(new XrefMock(), new HandlerMock(),
'prefix');
- var stream = new StringStream('trueifalserinulli');
+ var stream = new StringStream('trueifalserinullq');
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('setFlatness');
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[0].length).toEqual(1);
expect(result.argsArray[0][0]).toEqual(true);
expect(result.argsArray[1].length).toEqual(1);
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([]);
});
});
});