2011-10-26 10:18:22 +09:00
|
|
|
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
|
|
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
|
2012-09-01 07:48:21 +09:00
|
|
|
/* Copyright 2012 Mozilla Foundation
|
|
|
|
*
|
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
* You may obtain a copy of the License at
|
|
|
|
*
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
* limitations under the License.
|
|
|
|
*/
|
2013-02-03 07:49:19 +09:00
|
|
|
/* globals Ascii85Stream, AsciiHexStream, CCITTFaxStream, Cmd, Dict, error,
|
|
|
|
FlateStream, isArray, isCmd, isDict, isInt, isName, isNum, isRef,
|
|
|
|
isString, Jbig2Stream, JpegStream, JpxStream, LZWStream, Name,
|
2014-06-16 23:52:04 +09:00
|
|
|
NullStream, PredictorStream, Ref, RunLengthStream, warn, info,
|
|
|
|
StreamType */
|
2011-10-26 10:18:22 +09:00
|
|
|
|
|
|
|
'use strict';
|
|
|
|
|
2011-10-25 08:55:23 +09:00
|
|
|
var EOF = {};
|
|
|
|
|
|
|
|
function isEOF(v) {
|
2014-05-15 23:06:24 +09:00
|
|
|
return (v === EOF);
|
2011-10-25 08:55:23 +09:00
|
|
|
}
|
|
|
|
|
2011-12-09 07:18:43 +09:00
|
|
|
var Parser = (function ParserClosure() {
|
|
|
|
function Parser(lexer, allowStreams, xref) {
|
2011-10-25 08:55:23 +09:00
|
|
|
this.lexer = lexer;
|
|
|
|
this.allowStreams = allowStreams;
|
|
|
|
this.xref = xref;
|
2014-02-25 00:59:02 +09:00
|
|
|
this.imageCache = {
|
|
|
|
length: 0,
|
|
|
|
adler32: 0,
|
|
|
|
stream: null
|
|
|
|
};
|
2011-10-25 08:55:23 +09:00
|
|
|
this.refill();
|
|
|
|
}
|
|
|
|
|
2011-12-09 07:18:43 +09:00
|
|
|
Parser.prototype = {
|
2012-04-05 05:43:26 +09:00
|
|
|
refill: function Parser_refill() {
|
2011-10-25 08:55:23 +09:00
|
|
|
this.buf1 = this.lexer.getObj();
|
|
|
|
this.buf2 = this.lexer.getObj();
|
|
|
|
},
|
2012-04-05 05:43:26 +09:00
|
|
|
shift: function Parser_shift() {
|
2011-10-25 08:55:23 +09:00
|
|
|
if (isCmd(this.buf2, 'ID')) {
|
|
|
|
this.buf1 = this.buf2;
|
|
|
|
this.buf2 = null;
|
|
|
|
} else {
|
|
|
|
this.buf1 = this.buf2;
|
|
|
|
this.buf2 = this.lexer.getObj();
|
|
|
|
}
|
|
|
|
},
|
2012-04-05 05:43:26 +09:00
|
|
|
getObj: function Parser_getObj(cipherTransform) {
|
2014-05-23 16:25:36 +09:00
|
|
|
var buf1 = this.buf1;
|
|
|
|
this.shift();
|
|
|
|
|
|
|
|
if (buf1 instanceof Cmd) {
|
|
|
|
switch (buf1.cmd) {
|
|
|
|
case 'BI': // inline image
|
|
|
|
return this.makeInlineImage(cipherTransform);
|
|
|
|
case '[': // array
|
|
|
|
var array = [];
|
|
|
|
while (!isCmd(this.buf1, ']') && !isEOF(this.buf1)) {
|
|
|
|
array.push(this.getObj(cipherTransform));
|
|
|
|
}
|
|
|
|
if (isEOF(this.buf1)) {
|
|
|
|
error('End of file inside array');
|
|
|
|
}
|
2013-05-01 07:29:25 +09:00
|
|
|
this.shift();
|
2014-05-23 16:25:36 +09:00
|
|
|
return array;
|
|
|
|
case '<<': // dictionary or stream
|
|
|
|
var dict = new Dict(this.xref);
|
|
|
|
while (!isCmd(this.buf1, '>>') && !isEOF(this.buf1)) {
|
|
|
|
if (!isName(this.buf1)) {
|
|
|
|
info('Malformed dictionary: key must be a name object');
|
|
|
|
this.shift();
|
|
|
|
continue;
|
|
|
|
}
|
2012-03-20 21:16:48 +09:00
|
|
|
|
2014-05-23 16:25:36 +09:00
|
|
|
var key = this.buf1.name;
|
|
|
|
this.shift();
|
|
|
|
if (isEOF(this.buf1)) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
dict.set(key, this.getObj(cipherTransform));
|
|
|
|
}
|
|
|
|
if (isEOF(this.buf1)) {
|
|
|
|
error('End of file inside dictionary');
|
|
|
|
}
|
2011-10-25 08:55:23 +09:00
|
|
|
|
2014-05-23 16:25:36 +09:00
|
|
|
// Stream objects are not allowed inside content streams or
|
|
|
|
// object streams.
|
|
|
|
if (isCmd(this.buf2, 'stream')) {
|
|
|
|
return (this.allowStreams ?
|
|
|
|
this.makeStream(dict, cipherTransform) : dict);
|
|
|
|
}
|
|
|
|
this.shift();
|
|
|
|
return dict;
|
|
|
|
default: // simple object
|
|
|
|
return buf1;
|
2011-10-25 08:55:23 +09:00
|
|
|
}
|
|
|
|
}
|
2014-05-23 16:25:36 +09:00
|
|
|
|
|
|
|
if (isInt(buf1)) { // indirect reference or integer
|
|
|
|
var num = buf1;
|
2011-10-25 08:55:23 +09:00
|
|
|
if (isInt(this.buf1) && isCmd(this.buf2, 'R')) {
|
|
|
|
var ref = new Ref(num, this.buf1);
|
|
|
|
this.shift();
|
|
|
|
this.shift();
|
|
|
|
return ref;
|
|
|
|
}
|
|
|
|
return num;
|
|
|
|
}
|
2014-05-23 16:25:36 +09:00
|
|
|
|
|
|
|
if (isString(buf1)) { // string
|
|
|
|
var str = buf1;
|
2014-03-21 04:28:22 +09:00
|
|
|
if (cipherTransform) {
|
2011-10-25 08:55:23 +09:00
|
|
|
str = cipherTransform.decryptString(str);
|
2014-03-21 04:28:22 +09:00
|
|
|
}
|
2011-10-25 08:55:23 +09:00
|
|
|
return str;
|
|
|
|
}
|
|
|
|
|
|
|
|
// simple object
|
2014-05-23 16:25:36 +09:00
|
|
|
return buf1;
|
2011-10-25 08:55:23 +09:00
|
|
|
},
|
2012-04-05 05:43:26 +09:00
|
|
|
makeInlineImage: function Parser_makeInlineImage(cipherTransform) {
|
2011-10-25 08:55:23 +09:00
|
|
|
var lexer = this.lexer;
|
|
|
|
var stream = lexer.stream;
|
|
|
|
|
|
|
|
// parse dictionary
|
2014-03-26 23:07:38 +09:00
|
|
|
var dict = new Dict(null);
|
2011-10-25 08:55:23 +09:00
|
|
|
while (!isCmd(this.buf1, 'ID') && !isEOF(this.buf1)) {
|
2014-03-21 04:28:22 +09:00
|
|
|
if (!isName(this.buf1)) {
|
2011-10-25 08:55:23 +09:00
|
|
|
error('Dictionary key must be a name object');
|
2014-03-21 04:28:22 +09:00
|
|
|
}
|
2012-03-20 21:16:48 +09:00
|
|
|
|
|
|
|
var key = this.buf1.name;
|
|
|
|
this.shift();
|
2014-03-21 04:28:22 +09:00
|
|
|
if (isEOF(this.buf1)) {
|
2012-03-20 21:16:48 +09:00
|
|
|
break;
|
2014-03-21 04:28:22 +09:00
|
|
|
}
|
2012-03-20 21:16:48 +09:00
|
|
|
dict.set(key, this.getObj(cipherTransform));
|
2011-10-25 08:55:23 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
// parse image stream
|
|
|
|
var startPos = stream.pos;
|
|
|
|
|
2012-02-19 11:20:25 +09:00
|
|
|
// searching for the /EI\s/
|
2013-06-22 07:03:03 +09:00
|
|
|
var state = 0, ch, i, ii;
|
2013-07-01 05:45:15 +09:00
|
|
|
while (state != 4 && (ch = stream.getByte()) !== -1) {
|
|
|
|
switch (ch | 0) {
|
2011-10-25 08:55:23 +09:00
|
|
|
case 0x20:
|
|
|
|
case 0x0D:
|
|
|
|
case 0x0A:
|
2013-06-22 07:03:03 +09:00
|
|
|
// let's check next five bytes to be ASCII... just be sure
|
|
|
|
var followingBytes = stream.peekBytes(5);
|
|
|
|
for (i = 0, ii = followingBytes.length; i < ii; i++) {
|
|
|
|
ch = followingBytes[i];
|
2013-07-01 05:45:15 +09:00
|
|
|
if (ch !== 0x0A && ch !== 0x0D && (ch < 0x20 || ch > 0x7F)) {
|
|
|
|
// not a LF, CR, SPACE or any visible ASCII character
|
2013-06-22 07:03:03 +09:00
|
|
|
state = 0;
|
|
|
|
break; // some binary stuff found, resetting the state
|
|
|
|
}
|
|
|
|
}
|
2014-03-21 04:28:22 +09:00
|
|
|
state = (state === 3 ? 4 : 0);
|
2011-10-25 08:55:23 +09:00
|
|
|
break;
|
|
|
|
case 0x45:
|
2012-02-19 11:20:25 +09:00
|
|
|
state = 2;
|
2011-10-25 08:55:23 +09:00
|
|
|
break;
|
|
|
|
case 0x49:
|
2014-03-21 04:28:22 +09:00
|
|
|
state = (state === 2 ? 3 : 0);
|
2011-10-25 08:55:23 +09:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
state = 0;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var length = (stream.pos - 4) - startPos;
|
|
|
|
var imageStream = stream.makeSubStream(startPos, length, dict);
|
2014-02-25 00:59:02 +09:00
|
|
|
|
|
|
|
// trying to cache repeat images, first we are trying to "warm up" caching
|
|
|
|
// using length, then comparing adler32
|
|
|
|
var MAX_LENGTH_TO_CACHE = 1000;
|
|
|
|
var cacheImage = false, adler32;
|
|
|
|
if (length < MAX_LENGTH_TO_CACHE && this.imageCache.length === length) {
|
|
|
|
var imageBytes = imageStream.getBytes();
|
|
|
|
imageStream.reset();
|
|
|
|
|
|
|
|
var a = 1;
|
|
|
|
var b = 0;
|
2014-04-08 06:42:54 +09:00
|
|
|
for (i = 0, ii = imageBytes.length; i < ii; ++i) {
|
2014-02-25 00:59:02 +09:00
|
|
|
a = (a + (imageBytes[i] & 0xff)) % 65521;
|
|
|
|
b = (b + a) % 65521;
|
|
|
|
}
|
|
|
|
adler32 = (b << 16) | a;
|
|
|
|
|
|
|
|
if (this.imageCache.stream && this.imageCache.adler32 === adler32) {
|
|
|
|
this.buf2 = Cmd.get('EI');
|
|
|
|
this.shift();
|
|
|
|
|
|
|
|
this.imageCache.stream.reset();
|
|
|
|
return this.imageCache.stream;
|
|
|
|
}
|
|
|
|
cacheImage = true;
|
|
|
|
}
|
|
|
|
if (!cacheImage && !this.imageCache.stream) {
|
|
|
|
this.imageCache.length = length;
|
|
|
|
this.imageCache.stream = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cipherTransform) {
|
2014-03-11 14:18:30 +09:00
|
|
|
imageStream = cipherTransform.createStream(imageStream, length);
|
2014-02-25 00:59:02 +09:00
|
|
|
}
|
|
|
|
|
2011-10-25 08:55:23 +09:00
|
|
|
imageStream = this.filter(imageStream, dict, length);
|
2013-05-10 12:26:28 +09:00
|
|
|
imageStream.dict = dict;
|
2014-02-25 00:59:02 +09:00
|
|
|
if (cacheImage) {
|
|
|
|
imageStream.cacheKey = 'inline_' + length + '_' + adler32;
|
|
|
|
this.imageCache.adler32 = adler32;
|
|
|
|
this.imageCache.stream = imageStream;
|
|
|
|
}
|
2011-10-25 08:55:23 +09:00
|
|
|
|
2011-12-19 04:39:10 +09:00
|
|
|
this.buf2 = Cmd.get('EI');
|
2011-10-25 08:55:23 +09:00
|
|
|
this.shift();
|
|
|
|
|
|
|
|
return imageStream;
|
|
|
|
},
|
2012-04-05 05:43:26 +09:00
|
|
|
fetchIfRef: function Parser_fetchIfRef(obj) {
|
2012-02-15 11:19:43 +09:00
|
|
|
// not relying on the xref.fetchIfRef -- xref might not be set
|
2014-03-21 04:28:22 +09:00
|
|
|
return (isRef(obj) ? this.xref.fetch(obj) : obj);
|
2012-02-15 11:19:43 +09:00
|
|
|
},
|
2012-04-05 05:43:26 +09:00
|
|
|
makeStream: function Parser_makeStream(dict, cipherTransform) {
|
2011-10-25 08:55:23 +09:00
|
|
|
var lexer = this.lexer;
|
|
|
|
var stream = lexer.stream;
|
|
|
|
|
|
|
|
// get stream start position
|
|
|
|
lexer.skipToNextLine();
|
2013-07-01 05:45:15 +09:00
|
|
|
var pos = stream.pos - 1;
|
2011-10-25 08:55:23 +09:00
|
|
|
|
|
|
|
// get length
|
2012-02-15 11:19:43 +09:00
|
|
|
var length = this.fetchIfRef(dict.get('Length'));
|
2013-08-16 23:32:40 +09:00
|
|
|
if (!isInt(length)) {
|
|
|
|
info('Bad ' + length + ' attribute in stream');
|
|
|
|
length = 0;
|
|
|
|
}
|
2011-10-25 08:55:23 +09:00
|
|
|
|
|
|
|
// skip over the stream data
|
|
|
|
stream.pos = pos + length;
|
2013-07-01 05:45:15 +09:00
|
|
|
lexer.nextChar();
|
|
|
|
|
2011-10-25 08:55:23 +09:00
|
|
|
this.shift(); // '>>'
|
|
|
|
this.shift(); // 'stream'
|
2013-06-22 07:35:52 +09:00
|
|
|
if (!isCmd(this.buf1, 'endstream')) {
|
2013-06-23 03:21:19 +09:00
|
|
|
// bad stream length, scanning for endstream
|
|
|
|
stream.pos = pos;
|
|
|
|
var SCAN_BLOCK_SIZE = 2048;
|
|
|
|
var ENDSTREAM_SIGNATURE_LENGTH = 9;
|
|
|
|
var ENDSTREAM_SIGNATURE = [0x65, 0x6E, 0x64, 0x73, 0x74, 0x72, 0x65,
|
|
|
|
0x61, 0x6D];
|
2014-04-08 06:42:54 +09:00
|
|
|
var skipped = 0, found = false, i, j;
|
2013-06-23 03:21:19 +09:00
|
|
|
while (stream.pos < stream.end) {
|
|
|
|
var scanBytes = stream.peekBytes(SCAN_BLOCK_SIZE);
|
|
|
|
var scanLength = scanBytes.length - ENDSTREAM_SIGNATURE_LENGTH;
|
2014-06-07 05:06:29 +09:00
|
|
|
if (scanLength <= 0) {
|
|
|
|
break;
|
|
|
|
}
|
2014-04-08 06:42:54 +09:00
|
|
|
found = false;
|
2013-06-23 03:21:19 +09:00
|
|
|
for (i = 0, j = 0; i < scanLength; i++) {
|
|
|
|
var b = scanBytes[i];
|
|
|
|
if (b !== ENDSTREAM_SIGNATURE[j]) {
|
|
|
|
i -= j;
|
|
|
|
j = 0;
|
|
|
|
} else {
|
|
|
|
j++;
|
|
|
|
if (j >= ENDSTREAM_SIGNATURE_LENGTH) {
|
2014-03-07 07:57:27 +09:00
|
|
|
i++;
|
2013-06-23 03:21:19 +09:00
|
|
|
found = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (found) {
|
|
|
|
skipped += i - ENDSTREAM_SIGNATURE_LENGTH;
|
|
|
|
stream.pos += i - ENDSTREAM_SIGNATURE_LENGTH;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
skipped += scanLength;
|
|
|
|
stream.pos += scanLength;
|
|
|
|
}
|
|
|
|
if (!found) {
|
|
|
|
error('Missing endstream');
|
|
|
|
}
|
|
|
|
length = skipped;
|
2013-07-01 05:45:15 +09:00
|
|
|
|
|
|
|
lexer.nextChar();
|
2013-06-23 03:21:19 +09:00
|
|
|
this.shift();
|
|
|
|
this.shift();
|
2013-06-22 07:35:52 +09:00
|
|
|
}
|
2013-06-23 03:21:19 +09:00
|
|
|
this.shift(); // 'endstream'
|
2011-10-25 08:55:23 +09:00
|
|
|
|
|
|
|
stream = stream.makeSubStream(pos, length, dict);
|
2014-03-21 04:28:22 +09:00
|
|
|
if (cipherTransform) {
|
2014-03-11 14:18:30 +09:00
|
|
|
stream = cipherTransform.createStream(stream, length);
|
2014-03-21 04:28:22 +09:00
|
|
|
}
|
2011-10-25 08:55:23 +09:00
|
|
|
stream = this.filter(stream, dict, length);
|
2013-05-10 12:26:28 +09:00
|
|
|
stream.dict = dict;
|
2011-10-25 08:55:23 +09:00
|
|
|
return stream;
|
|
|
|
},
|
2012-04-05 05:43:26 +09:00
|
|
|
filter: function Parser_filter(stream, dict, length) {
|
2012-02-15 11:19:43 +09:00
|
|
|
var filter = this.fetchIfRef(dict.get('Filter', 'F'));
|
|
|
|
var params = this.fetchIfRef(dict.get('DecodeParms', 'DP'));
|
2014-03-21 04:28:22 +09:00
|
|
|
if (isName(filter)) {
|
2011-10-25 08:55:23 +09:00
|
|
|
return this.makeFilter(stream, filter.name, length, params);
|
2014-03-21 04:28:22 +09:00
|
|
|
}
|
2014-03-11 14:18:30 +09:00
|
|
|
|
|
|
|
var maybeLength = length;
|
2011-10-25 08:55:23 +09:00
|
|
|
if (isArray(filter)) {
|
|
|
|
var filterArray = filter;
|
|
|
|
var paramsArray = params;
|
|
|
|
for (var i = 0, ii = filterArray.length; i < ii; ++i) {
|
|
|
|
filter = filterArray[i];
|
2014-03-21 04:28:22 +09:00
|
|
|
if (!isName(filter)) {
|
2011-10-25 08:55:23 +09:00
|
|
|
error('Bad filter name: ' + filter);
|
2014-03-21 04:28:22 +09:00
|
|
|
}
|
2012-03-20 21:16:48 +09:00
|
|
|
|
|
|
|
params = null;
|
2014-03-21 04:28:22 +09:00
|
|
|
if (isArray(paramsArray) && (i in paramsArray)) {
|
2012-03-20 21:16:48 +09:00
|
|
|
params = paramsArray[i];
|
2014-03-21 04:28:22 +09:00
|
|
|
}
|
2014-03-11 14:18:30 +09:00
|
|
|
stream = this.makeFilter(stream, filter.name, maybeLength, params);
|
2012-03-20 21:16:48 +09:00
|
|
|
// after the first stream the length variable is invalid
|
2014-03-11 14:18:30 +09:00
|
|
|
maybeLength = null;
|
2011-10-25 08:55:23 +09:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return stream;
|
|
|
|
},
|
2014-03-11 14:18:30 +09:00
|
|
|
makeFilter: function Parser_makeFilter(stream, name, maybeLength, params) {
|
2012-10-23 00:53:15 +09:00
|
|
|
if (stream.dict.get('Length') === 0) {
|
|
|
|
return new NullStream(stream);
|
|
|
|
}
|
2014-06-16 23:52:04 +09:00
|
|
|
var xrefStreamStats = this.xref.stats.streamTypes;
|
2011-10-25 08:55:23 +09:00
|
|
|
if (name == 'FlateDecode' || name == 'Fl') {
|
2014-06-16 23:52:04 +09:00
|
|
|
xrefStreamStats[StreamType.FLATE] = true;
|
2011-10-25 08:55:23 +09:00
|
|
|
if (params) {
|
2014-03-11 14:18:30 +09:00
|
|
|
return new PredictorStream(new FlateStream(stream, maybeLength),
|
|
|
|
maybeLength, params);
|
2011-10-25 08:55:23 +09:00
|
|
|
}
|
2014-03-11 14:18:30 +09:00
|
|
|
return new FlateStream(stream, maybeLength);
|
2011-12-05 04:22:08 +09:00
|
|
|
}
|
|
|
|
if (name == 'LZWDecode' || name == 'LZW') {
|
2014-06-16 23:52:04 +09:00
|
|
|
xrefStreamStats[StreamType.LZW] = true;
|
2011-10-25 08:55:23 +09:00
|
|
|
var earlyChange = 1;
|
|
|
|
if (params) {
|
2014-03-21 04:28:22 +09:00
|
|
|
if (params.has('EarlyChange')) {
|
2011-10-25 08:55:23 +09:00
|
|
|
earlyChange = params.get('EarlyChange');
|
2014-03-21 04:28:22 +09:00
|
|
|
}
|
2011-10-25 08:55:23 +09:00
|
|
|
return new PredictorStream(
|
2014-03-11 14:18:30 +09:00
|
|
|
new LZWStream(stream, maybeLength, earlyChange),
|
2014-03-21 04:28:22 +09:00
|
|
|
maybeLength, params);
|
2011-10-25 08:55:23 +09:00
|
|
|
}
|
2014-03-11 14:18:30 +09:00
|
|
|
return new LZWStream(stream, maybeLength, earlyChange);
|
2011-12-05 04:22:08 +09:00
|
|
|
}
|
|
|
|
if (name == 'DCTDecode' || name == 'DCT') {
|
2014-06-16 23:52:04 +09:00
|
|
|
xrefStreamStats[StreamType.DCT] = true;
|
2014-03-11 14:18:30 +09:00
|
|
|
return new JpegStream(stream, maybeLength, stream.dict, this.xref);
|
2011-12-05 04:22:08 +09:00
|
|
|
}
|
2012-01-12 11:08:46 +09:00
|
|
|
if (name == 'JPXDecode' || name == 'JPX') {
|
2014-06-16 23:52:04 +09:00
|
|
|
xrefStreamStats[StreamType.JPX] = true;
|
2014-03-11 14:18:30 +09:00
|
|
|
return new JpxStream(stream, maybeLength, stream.dict);
|
2012-01-12 11:08:46 +09:00
|
|
|
}
|
2011-12-05 04:22:08 +09:00
|
|
|
if (name == 'ASCII85Decode' || name == 'A85') {
|
2014-06-16 23:52:04 +09:00
|
|
|
xrefStreamStats[StreamType.A85] = true;
|
2014-03-11 14:18:30 +09:00
|
|
|
return new Ascii85Stream(stream, maybeLength);
|
2011-12-05 04:22:08 +09:00
|
|
|
}
|
|
|
|
if (name == 'ASCIIHexDecode' || name == 'AHx') {
|
2014-06-16 23:52:04 +09:00
|
|
|
xrefStreamStats[StreamType.AHX] = true;
|
2014-03-11 14:18:30 +09:00
|
|
|
return new AsciiHexStream(stream, maybeLength);
|
2011-12-05 04:22:08 +09:00
|
|
|
}
|
|
|
|
if (name == 'CCITTFaxDecode' || name == 'CCF') {
|
2014-06-16 23:52:04 +09:00
|
|
|
xrefStreamStats[StreamType.CCF] = true;
|
2014-03-11 14:18:30 +09:00
|
|
|
return new CCITTFaxStream(stream, maybeLength, params);
|
2011-10-25 08:55:23 +09:00
|
|
|
}
|
2012-04-17 03:35:42 +09:00
|
|
|
if (name == 'RunLengthDecode' || name == 'RL') {
|
2014-06-16 23:52:04 +09:00
|
|
|
xrefStreamStats[StreamType.RL] = true;
|
2014-03-11 14:18:30 +09:00
|
|
|
return new RunLengthStream(stream, maybeLength);
|
2012-01-15 04:47:14 +09:00
|
|
|
}
|
2012-04-17 03:34:00 +09:00
|
|
|
if (name == 'JBIG2Decode') {
|
2014-06-16 23:52:04 +09:00
|
|
|
xrefStreamStats[StreamType.JBIG] = true;
|
2014-03-11 14:18:30 +09:00
|
|
|
return new Jbig2Stream(stream, maybeLength, stream.dict);
|
2012-04-17 03:34:00 +09:00
|
|
|
}
|
2011-12-13 04:33:26 +09:00
|
|
|
warn('filter "' + name + '" not supported yet');
|
2011-10-25 08:55:23 +09:00
|
|
|
return stream;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2011-12-09 07:18:43 +09:00
|
|
|
return Parser;
|
2011-10-25 08:55:23 +09:00
|
|
|
})();
|
|
|
|
|
2011-12-09 07:18:43 +09:00
|
|
|
var Lexer = (function LexerClosure() {
|
2012-05-21 03:44:03 +09:00
|
|
|
function Lexer(stream, knownCommands) {
|
2011-10-25 08:55:23 +09:00
|
|
|
this.stream = stream;
|
2013-07-01 05:45:15 +09:00
|
|
|
this.nextChar();
|
|
|
|
|
2014-01-29 13:21:16 +09:00
|
|
|
// While lexing, we build up many strings one char at a time. Using += for
|
|
|
|
// this can result in lots of garbage strings. It's better to build an
|
|
|
|
// array of single-char strings and then join() them together at the end.
|
|
|
|
// And reusing a single array (i.e. |this.strBuf|) over and over for this
|
|
|
|
// purpose uses less memory than using a new array for each string.
|
|
|
|
this.strBuf = [];
|
|
|
|
|
2012-05-22 05:23:49 +09:00
|
|
|
// The PDFs might have "glued" commands with other commands, operands or
|
|
|
|
// literals, e.g. "q1". The knownCommands is a dictionary of the valid
|
|
|
|
// commands and their prefixes. The prefixes are built the following way:
|
|
|
|
// if there a command that is a prefix of the other valid command or
|
|
|
|
// literal (e.g. 'f' and 'false') the following prefixes must be included,
|
|
|
|
// 'fa', 'fal', 'fals'. The prefixes are not needed, if the command has no
|
|
|
|
// other commands or literals as a prefix. The knowCommands is optional.
|
2012-05-21 03:44:03 +09:00
|
|
|
this.knownCommands = knownCommands;
|
2011-10-25 08:55:23 +09:00
|
|
|
}
|
|
|
|
|
2012-04-05 05:43:26 +09:00
|
|
|
Lexer.isSpace = function Lexer_isSpace(ch) {
|
2014-03-21 04:28:22 +09:00
|
|
|
// Space is one of the following characters: SPACE, TAB, CR or LF.
|
|
|
|
return (ch === 0x20 || ch === 0x09 || ch === 0x0D || ch === 0x0A);
|
2011-10-25 08:55:23 +09:00
|
|
|
};
|
|
|
|
|
2014-03-21 04:28:22 +09:00
|
|
|
// A '1' in this array means the character is white space. A '1' or
|
2011-10-25 08:55:23 +09:00
|
|
|
// '2' means the character ends a name or command.
|
|
|
|
var specialChars = [
|
2014-03-21 04:28:22 +09:00
|
|
|
1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, // 0x
|
|
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1x
|
|
|
|
1, 0, 0, 0, 0, 2, 0, 0, 2, 2, 0, 0, 0, 0, 0, 2, // 2x
|
|
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 0, // 3x
|
|
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 4x
|
|
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 0, 0, // 5x
|
|
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 6x
|
|
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 0, 0, // 7x
|
|
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 8x
|
|
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 9x
|
|
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // ax
|
|
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // bx
|
|
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // cx
|
|
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // dx
|
|
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // ex
|
|
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // fx
|
2011-10-25 08:55:23 +09:00
|
|
|
];
|
|
|
|
|
|
|
|
function toHexDigit(ch) {
|
2013-07-01 05:45:15 +09:00
|
|
|
if (ch >= 0x30 && ch <= 0x39) { // '0'-'9'
|
|
|
|
return ch & 0x0F;
|
|
|
|
}
|
|
|
|
if ((ch >= 0x41 && ch <= 0x46) || (ch >= 0x61 && ch <= 0x66)) {
|
|
|
|
// 'A'-'F', 'a'-'f'
|
|
|
|
return (ch & 0x0F) + 9;
|
|
|
|
}
|
2011-10-25 08:55:23 +09:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2011-12-09 07:18:43 +09:00
|
|
|
Lexer.prototype = {
|
2013-07-01 05:45:15 +09:00
|
|
|
nextChar: function Lexer_nextChar() {
|
|
|
|
return (this.currentChar = this.stream.getByte());
|
|
|
|
},
|
2014-02-02 05:46:09 +09:00
|
|
|
peekChar: function Lexer_peekChar() {
|
|
|
|
return this.stream.peekBytes(1)[0];
|
|
|
|
},
|
2013-07-01 05:45:15 +09:00
|
|
|
getNumber: function Lexer_getNumber() {
|
|
|
|
var ch = this.currentChar;
|
2014-02-02 05:46:09 +09:00
|
|
|
var eNotation = false;
|
|
|
|
var divideBy = 0; // different from 0 if it's a floating point value
|
|
|
|
var sign = 1;
|
|
|
|
|
|
|
|
if (ch === 0x2D) { // '-'
|
|
|
|
sign = -1;
|
|
|
|
ch = this.nextChar();
|
|
|
|
} else if (ch === 0x2B) { // '+'
|
|
|
|
ch = this.nextChar();
|
|
|
|
}
|
|
|
|
if (ch === 0x2E) { // '.'
|
|
|
|
divideBy = 10;
|
|
|
|
ch = this.nextChar();
|
|
|
|
}
|
|
|
|
if (ch < 0x30 || ch > 0x39) { // '0' - '9'
|
|
|
|
error('Invalid number: ' + String.fromCharCode(ch));
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
var baseValue = ch - 0x30; // '0'
|
|
|
|
var powerValue = 0;
|
|
|
|
var powerValueSign = 1;
|
|
|
|
|
2013-07-01 05:45:15 +09:00
|
|
|
while ((ch = this.nextChar()) >= 0) {
|
2014-02-02 05:46:09 +09:00
|
|
|
if (0x30 <= ch && ch <= 0x39) { // '0' - '9'
|
|
|
|
var currentDigit = ch - 0x30; // '0'
|
|
|
|
if (eNotation) { // We are after an 'e' or 'E'
|
|
|
|
powerValue = powerValue * 10 + currentDigit;
|
|
|
|
} else {
|
|
|
|
if (divideBy !== 0) { // We are after a point
|
|
|
|
divideBy *= 10;
|
|
|
|
}
|
|
|
|
baseValue = baseValue * 10 + currentDigit;
|
|
|
|
}
|
|
|
|
} else if (ch === 0x2E) { // '.'
|
|
|
|
if (divideBy === 0) {
|
|
|
|
divideBy = 1;
|
|
|
|
} else {
|
|
|
|
// A number can have only one '.'
|
|
|
|
break;
|
|
|
|
}
|
2013-07-01 05:45:15 +09:00
|
|
|
} else if (ch === 0x2D) { // '-'
|
2011-10-25 08:55:23 +09:00
|
|
|
// ignore minus signs in the middle of numbers to match
|
|
|
|
// Adobe's behavior
|
|
|
|
warn('Badly formated number');
|
2013-07-01 05:45:15 +09:00
|
|
|
} else if (ch === 0x45 || ch === 0x65) { // 'E', 'e'
|
2014-02-02 05:46:09 +09:00
|
|
|
// 'E' can be either a scientific notation or the beginning of a new
|
|
|
|
// operator
|
|
|
|
ch = this.peekChar();
|
|
|
|
if (ch === 0x2B || ch === 0x2D) { // '+', '-'
|
|
|
|
powerValueSign = (ch === 0x2D) ? -1 : 1;
|
|
|
|
this.nextChar(); // Consume the sign character
|
|
|
|
} else if (ch < 0x30 || ch > 0x39) { // '0' - '9'
|
|
|
|
// The 'E' must be the beginning of a new operator
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
eNotation = true;
|
2011-10-25 08:55:23 +09:00
|
|
|
} else {
|
|
|
|
// the last character doesn't belong to us
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2014-02-02 05:46:09 +09:00
|
|
|
|
|
|
|
if (divideBy !== 0) {
|
|
|
|
baseValue /= divideBy;
|
|
|
|
}
|
|
|
|
if (eNotation) {
|
|
|
|
baseValue *= Math.pow(10, powerValueSign * powerValue);
|
2014-01-29 13:45:23 +09:00
|
|
|
}
|
2014-02-02 05:46:09 +09:00
|
|
|
return sign * baseValue;
|
2011-10-25 08:55:23 +09:00
|
|
|
},
|
2012-04-05 05:43:26 +09:00
|
|
|
getString: function Lexer_getString() {
|
2011-10-25 08:55:23 +09:00
|
|
|
var numParen = 1;
|
|
|
|
var done = false;
|
2014-01-29 13:21:16 +09:00
|
|
|
var strBuf = this.strBuf;
|
|
|
|
strBuf.length = 0;
|
2013-07-01 05:45:15 +09:00
|
|
|
|
|
|
|
var ch = this.nextChar();
|
|
|
|
while (true) {
|
|
|
|
var charBuffered = false;
|
|
|
|
switch (ch | 0) {
|
|
|
|
case -1:
|
2011-10-25 08:55:23 +09:00
|
|
|
warn('Unterminated string');
|
|
|
|
done = true;
|
|
|
|
break;
|
2013-07-01 05:45:15 +09:00
|
|
|
case 0x28: // '('
|
2011-10-25 08:55:23 +09:00
|
|
|
++numParen;
|
2014-01-29 13:21:16 +09:00
|
|
|
strBuf.push('(');
|
2011-10-25 08:55:23 +09:00
|
|
|
break;
|
2013-07-01 05:45:15 +09:00
|
|
|
case 0x29: // ')'
|
2013-02-01 08:33:09 +09:00
|
|
|
if (--numParen === 0) {
|
2013-07-01 05:45:15 +09:00
|
|
|
this.nextChar(); // consume strings ')'
|
2011-10-25 08:55:23 +09:00
|
|
|
done = true;
|
|
|
|
} else {
|
2014-01-29 13:21:16 +09:00
|
|
|
strBuf.push(')');
|
2011-10-25 08:55:23 +09:00
|
|
|
}
|
|
|
|
break;
|
2013-07-01 05:45:15 +09:00
|
|
|
case 0x5C: // '\\'
|
|
|
|
ch = this.nextChar();
|
2011-10-25 08:55:23 +09:00
|
|
|
switch (ch) {
|
2013-07-01 05:45:15 +09:00
|
|
|
case -1:
|
2011-10-25 08:55:23 +09:00
|
|
|
warn('Unterminated string');
|
|
|
|
done = true;
|
|
|
|
break;
|
2013-07-01 05:45:15 +09:00
|
|
|
case 0x6E: // 'n'
|
2014-01-29 13:21:16 +09:00
|
|
|
strBuf.push('\n');
|
2011-10-25 08:55:23 +09:00
|
|
|
break;
|
2013-07-01 05:45:15 +09:00
|
|
|
case 0x72: // 'r'
|
2014-01-29 13:21:16 +09:00
|
|
|
strBuf.push('\r');
|
2011-10-25 08:55:23 +09:00
|
|
|
break;
|
2013-07-01 05:45:15 +09:00
|
|
|
case 0x74: // 't'
|
2014-01-29 13:21:16 +09:00
|
|
|
strBuf.push('\t');
|
2011-10-25 08:55:23 +09:00
|
|
|
break;
|
2013-07-01 05:45:15 +09:00
|
|
|
case 0x62: // 'b'
|
2014-01-29 13:21:16 +09:00
|
|
|
strBuf.push('\b');
|
2011-10-25 08:55:23 +09:00
|
|
|
break;
|
2013-07-01 05:45:15 +09:00
|
|
|
case 0x66: // 'f'
|
2014-01-29 13:21:16 +09:00
|
|
|
strBuf.push('\f');
|
2011-10-25 08:55:23 +09:00
|
|
|
break;
|
2013-07-01 05:45:15 +09:00
|
|
|
case 0x5C: // '\'
|
|
|
|
case 0x28: // '('
|
|
|
|
case 0x29: // ')'
|
2014-01-29 13:21:16 +09:00
|
|
|
strBuf.push(String.fromCharCode(ch));
|
2011-10-25 08:55:23 +09:00
|
|
|
break;
|
2013-07-01 05:45:15 +09:00
|
|
|
case 0x30: case 0x31: case 0x32: case 0x33: // '0'-'3'
|
|
|
|
case 0x34: case 0x35: case 0x36: case 0x37: // '4'-'7'
|
|
|
|
var x = ch & 0x0F;
|
|
|
|
ch = this.nextChar();
|
|
|
|
charBuffered = true;
|
|
|
|
if (ch >= 0x30 && ch <= 0x37) { // '0'-'7'
|
|
|
|
x = (x << 3) + (ch & 0x0F);
|
|
|
|
ch = this.nextChar();
|
|
|
|
if (ch >= 0x30 && ch <= 0x37) { // '0'-'7'
|
|
|
|
charBuffered = false;
|
|
|
|
x = (x << 3) + (ch & 0x0F);
|
2011-10-25 08:55:23 +09:00
|
|
|
}
|
|
|
|
}
|
2014-01-29 13:21:16 +09:00
|
|
|
strBuf.push(String.fromCharCode(x));
|
2011-10-25 08:55:23 +09:00
|
|
|
break;
|
2014-03-21 01:50:12 +09:00
|
|
|
case 0x0D: // CR
|
|
|
|
if (this.peekChar() === 0x0A) { // LF
|
|
|
|
this.nextChar();
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 0x0A: // LF
|
2011-10-25 08:55:23 +09:00
|
|
|
break;
|
|
|
|
default:
|
2014-01-29 13:21:16 +09:00
|
|
|
strBuf.push(String.fromCharCode(ch));
|
2013-02-24 02:35:18 +09:00
|
|
|
break;
|
2011-10-25 08:55:23 +09:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
2014-01-29 13:21:16 +09:00
|
|
|
strBuf.push(String.fromCharCode(ch));
|
2013-02-24 02:35:18 +09:00
|
|
|
break;
|
2011-10-25 08:55:23 +09:00
|
|
|
}
|
2013-07-01 05:45:15 +09:00
|
|
|
if (done) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (!charBuffered) {
|
|
|
|
ch = this.nextChar();
|
|
|
|
}
|
|
|
|
}
|
2014-01-29 13:21:16 +09:00
|
|
|
return strBuf.join('');
|
2011-10-25 08:55:23 +09:00
|
|
|
},
|
2013-07-01 05:45:15 +09:00
|
|
|
getName: function Lexer_getName() {
|
2014-01-29 13:21:16 +09:00
|
|
|
var ch;
|
|
|
|
var strBuf = this.strBuf;
|
|
|
|
strBuf.length = 0;
|
2013-07-01 05:45:15 +09:00
|
|
|
while ((ch = this.nextChar()) >= 0 && !specialChars[ch]) {
|
|
|
|
if (ch === 0x23) { // '#'
|
|
|
|
ch = this.nextChar();
|
2011-10-25 08:55:23 +09:00
|
|
|
var x = toHexDigit(ch);
|
|
|
|
if (x != -1) {
|
2013-07-01 05:45:15 +09:00
|
|
|
var x2 = toHexDigit(this.nextChar());
|
2014-05-23 16:25:36 +09:00
|
|
|
if (x2 === -1) {
|
2011-10-25 08:55:23 +09:00
|
|
|
error('Illegal digit in hex char in name: ' + x2);
|
2014-03-21 04:28:22 +09:00
|
|
|
}
|
2014-01-29 13:21:16 +09:00
|
|
|
strBuf.push(String.fromCharCode((x << 4) | x2));
|
2011-10-25 08:55:23 +09:00
|
|
|
} else {
|
2014-01-29 13:21:16 +09:00
|
|
|
strBuf.push('#', String.fromCharCode(ch));
|
2011-10-25 08:55:23 +09:00
|
|
|
}
|
|
|
|
} else {
|
2014-01-29 13:21:16 +09:00
|
|
|
strBuf.push(String.fromCharCode(ch));
|
2011-10-25 08:55:23 +09:00
|
|
|
}
|
|
|
|
}
|
2014-01-29 13:21:16 +09:00
|
|
|
if (strBuf.length > 128) {
|
2011-10-25 08:55:23 +09:00
|
|
|
error('Warning: name token is longer than allowed by the spec: ' +
|
2014-01-29 13:21:16 +09:00
|
|
|
strBuf.length);
|
2013-07-01 05:45:15 +09:00
|
|
|
}
|
2014-02-28 13:41:03 +09:00
|
|
|
return Name.get(strBuf.join(''));
|
2011-10-25 08:55:23 +09:00
|
|
|
},
|
2013-07-01 05:45:15 +09:00
|
|
|
getHexString: function Lexer_getHexString() {
|
2014-01-29 13:21:16 +09:00
|
|
|
var strBuf = this.strBuf;
|
|
|
|
strBuf.length = 0;
|
2013-07-01 05:45:15 +09:00
|
|
|
var ch = this.currentChar;
|
2013-01-09 08:28:08 +09:00
|
|
|
var isFirstHex = true;
|
|
|
|
var firstDigit;
|
|
|
|
var secondDigit;
|
|
|
|
while (true) {
|
2013-07-01 05:45:15 +09:00
|
|
|
if (ch < 0) {
|
2011-10-25 08:55:23 +09:00
|
|
|
warn('Unterminated hex string');
|
|
|
|
break;
|
2013-07-01 05:45:15 +09:00
|
|
|
} else if (ch === 0x3E) { // '>'
|
|
|
|
this.nextChar();
|
2013-01-09 08:28:08 +09:00
|
|
|
break;
|
2013-07-01 05:45:15 +09:00
|
|
|
} else if (specialChars[ch] === 1) {
|
|
|
|
ch = this.nextChar();
|
2013-01-09 08:28:08 +09:00
|
|
|
continue;
|
|
|
|
} else {
|
|
|
|
if (isFirstHex) {
|
|
|
|
firstDigit = toHexDigit(ch);
|
|
|
|
if (firstDigit === -1) {
|
2013-02-03 08:00:13 +09:00
|
|
|
warn('Ignoring invalid character "' + ch + '" in hex string');
|
2013-07-01 05:45:15 +09:00
|
|
|
ch = this.nextChar();
|
2013-01-09 08:28:08 +09:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
secondDigit = toHexDigit(ch);
|
|
|
|
if (secondDigit === -1) {
|
2013-02-03 08:00:13 +09:00
|
|
|
warn('Ignoring invalid character "' + ch + '" in hex string');
|
2013-07-01 05:45:15 +09:00
|
|
|
ch = this.nextChar();
|
2013-01-09 08:28:08 +09:00
|
|
|
continue;
|
|
|
|
}
|
2014-01-29 13:21:16 +09:00
|
|
|
strBuf.push(String.fromCharCode((firstDigit << 4) | secondDigit));
|
2013-01-09 08:28:08 +09:00
|
|
|
}
|
|
|
|
isFirstHex = !isFirstHex;
|
2013-07-01 05:45:15 +09:00
|
|
|
ch = this.nextChar();
|
2011-10-25 08:55:23 +09:00
|
|
|
}
|
|
|
|
}
|
2014-01-29 13:21:16 +09:00
|
|
|
return strBuf.join('');
|
2011-10-25 08:55:23 +09:00
|
|
|
},
|
2012-04-05 05:43:26 +09:00
|
|
|
getObj: function Lexer_getObj() {
|
2011-10-25 08:55:23 +09:00
|
|
|
// skip whitespace and comments
|
|
|
|
var comment = false;
|
2013-07-01 05:45:15 +09:00
|
|
|
var ch = this.currentChar;
|
2011-10-25 08:55:23 +09:00
|
|
|
while (true) {
|
2013-07-01 05:45:15 +09:00
|
|
|
if (ch < 0) {
|
2011-10-25 08:55:23 +09:00
|
|
|
return EOF;
|
2013-07-01 05:45:15 +09:00
|
|
|
}
|
2011-10-25 08:55:23 +09:00
|
|
|
if (comment) {
|
2014-05-23 16:25:36 +09:00
|
|
|
if (ch === 0x0A || ch === 0x0D) { // LF, CR
|
2011-10-25 08:55:23 +09:00
|
|
|
comment = false;
|
2014-03-21 04:28:22 +09:00
|
|
|
}
|
2013-07-01 05:45:15 +09:00
|
|
|
} else if (ch === 0x25) { // '%'
|
2011-10-25 08:55:23 +09:00
|
|
|
comment = true;
|
2013-07-01 05:45:15 +09:00
|
|
|
} else if (specialChars[ch] !== 1) {
|
2011-10-25 08:55:23 +09:00
|
|
|
break;
|
|
|
|
}
|
2013-07-01 05:45:15 +09:00
|
|
|
ch = this.nextChar();
|
2011-10-25 08:55:23 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
// start reading token
|
2013-07-01 05:45:15 +09:00
|
|
|
switch (ch | 0) {
|
|
|
|
case 0x30: case 0x31: case 0x32: case 0x33: case 0x34: // '0'-'4'
|
|
|
|
case 0x35: case 0x36: case 0x37: case 0x38: case 0x39: // '5'-'9'
|
|
|
|
case 0x2B: case 0x2D: case 0x2E: // '+', '-', '.'
|
|
|
|
return this.getNumber();
|
|
|
|
case 0x28: // '('
|
2011-10-25 08:55:23 +09:00
|
|
|
return this.getString();
|
2013-07-01 05:45:15 +09:00
|
|
|
case 0x2F: // '/'
|
|
|
|
return this.getName();
|
2011-10-25 08:55:23 +09:00
|
|
|
// array punctuation
|
2013-07-01 05:45:15 +09:00
|
|
|
case 0x5B: // '['
|
|
|
|
this.nextChar();
|
|
|
|
return Cmd.get('[');
|
|
|
|
case 0x5D: // ']'
|
|
|
|
this.nextChar();
|
|
|
|
return Cmd.get(']');
|
2011-10-25 08:55:23 +09:00
|
|
|
// hex string or dict punctuation
|
2013-07-01 05:45:15 +09:00
|
|
|
case 0x3C: // '<'
|
|
|
|
ch = this.nextChar();
|
|
|
|
if (ch === 0x3C) {
|
2011-10-25 08:55:23 +09:00
|
|
|
// dict punctuation
|
2013-07-01 05:45:15 +09:00
|
|
|
this.nextChar();
|
2011-12-19 04:39:10 +09:00
|
|
|
return Cmd.get('<<');
|
2011-10-25 08:55:23 +09:00
|
|
|
}
|
2013-07-01 05:45:15 +09:00
|
|
|
return this.getHexString();
|
2011-10-25 08:55:23 +09:00
|
|
|
// dict punctuation
|
2013-07-01 05:45:15 +09:00
|
|
|
case 0x3E: // '>'
|
|
|
|
ch = this.nextChar();
|
|
|
|
if (ch === 0x3E) {
|
|
|
|
this.nextChar();
|
2011-12-19 04:39:10 +09:00
|
|
|
return Cmd.get('>>');
|
2011-10-25 08:55:23 +09:00
|
|
|
}
|
2013-07-01 05:45:15 +09:00
|
|
|
return Cmd.get('>');
|
|
|
|
case 0x7B: // '{'
|
|
|
|
this.nextChar();
|
|
|
|
return Cmd.get('{');
|
|
|
|
case 0x7D: // '}'
|
|
|
|
this.nextChar();
|
|
|
|
return Cmd.get('}');
|
|
|
|
case 0x29: // ')'
|
2011-10-25 08:55:23 +09:00
|
|
|
error('Illegal character: ' + ch);
|
2013-07-01 05:45:15 +09:00
|
|
|
break;
|
2011-10-25 08:55:23 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
// command
|
2013-07-01 05:45:15 +09:00
|
|
|
var str = String.fromCharCode(ch);
|
2012-05-21 03:44:03 +09:00
|
|
|
var knownCommands = this.knownCommands;
|
2014-06-02 19:14:53 +09:00
|
|
|
var knownCommandFound = knownCommands && knownCommands[str] !== undefined;
|
2013-07-01 05:45:15 +09:00
|
|
|
while ((ch = this.nextChar()) >= 0 && !specialChars[ch]) {
|
2012-05-21 03:44:03 +09:00
|
|
|
// stop if known command is found and next character does not make
|
|
|
|
// the str a command
|
2013-07-01 05:45:15 +09:00
|
|
|
var possibleCommand = str + String.fromCharCode(ch);
|
2014-06-02 19:14:53 +09:00
|
|
|
if (knownCommandFound && knownCommands[possibleCommand] === undefined) {
|
2012-05-21 03:44:03 +09:00
|
|
|
break;
|
2013-07-01 05:45:15 +09:00
|
|
|
}
|
2014-05-23 16:25:36 +09:00
|
|
|
if (str.length === 128) {
|
2011-10-25 08:55:23 +09:00
|
|
|
error('Command token too long: ' + str.length);
|
2014-03-21 04:28:22 +09:00
|
|
|
}
|
2013-07-01 05:45:15 +09:00
|
|
|
str = possibleCommand;
|
2014-06-02 19:14:53 +09:00
|
|
|
knownCommandFound = knownCommands && knownCommands[str] !== undefined;
|
2011-10-25 08:55:23 +09:00
|
|
|
}
|
2014-05-23 16:25:36 +09:00
|
|
|
if (str === 'true') {
|
2011-10-25 08:55:23 +09:00
|
|
|
return true;
|
2014-03-21 04:28:22 +09:00
|
|
|
}
|
2014-05-23 16:25:36 +09:00
|
|
|
if (str === 'false') {
|
2011-10-25 08:55:23 +09:00
|
|
|
return false;
|
2014-03-21 04:28:22 +09:00
|
|
|
}
|
2014-05-23 16:25:36 +09:00
|
|
|
if (str === 'null') {
|
2011-10-25 08:55:23 +09:00
|
|
|
return null;
|
2014-03-21 04:28:22 +09:00
|
|
|
}
|
2011-12-19 04:39:10 +09:00
|
|
|
return Cmd.get(str);
|
2011-10-25 08:55:23 +09:00
|
|
|
},
|
2012-04-05 05:43:26 +09:00
|
|
|
skipToNextLine: function Lexer_skipToNextLine() {
|
2013-07-01 05:45:15 +09:00
|
|
|
var ch = this.currentChar;
|
|
|
|
while (ch >= 0) {
|
|
|
|
if (ch === 0x0D) { // CR
|
|
|
|
ch = this.nextChar();
|
|
|
|
if (ch === 0x0A) { // LF
|
|
|
|
this.nextChar();
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
} else if (ch === 0x0A) { // LF
|
|
|
|
this.nextChar();
|
|
|
|
break;
|
2011-10-25 08:55:23 +09:00
|
|
|
}
|
2013-07-01 05:45:15 +09:00
|
|
|
ch = this.nextChar();
|
2011-10-25 08:55:23 +09:00
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2011-12-09 07:18:43 +09:00
|
|
|
return Lexer;
|
2011-10-25 08:55:23 +09:00
|
|
|
})();
|
|
|
|
|
2011-12-09 07:18:43 +09:00
|
|
|
var Linearization = (function LinearizationClosure() {
|
|
|
|
function Linearization(stream) {
|
2012-04-05 03:43:04 +09:00
|
|
|
this.parser = new Parser(new Lexer(stream), false, null);
|
2011-10-25 08:55:23 +09:00
|
|
|
var obj1 = this.parser.getObj();
|
|
|
|
var obj2 = this.parser.getObj();
|
|
|
|
var obj3 = this.parser.getObj();
|
|
|
|
this.linDict = this.parser.getObj();
|
|
|
|
if (isInt(obj1) && isInt(obj2) && isCmd(obj3, 'obj') &&
|
|
|
|
isDict(this.linDict)) {
|
|
|
|
var obj = this.linDict.get('Linearized');
|
2014-03-21 04:28:22 +09:00
|
|
|
if (!(isNum(obj) && obj > 0)) {
|
2011-10-25 08:55:23 +09:00
|
|
|
this.linDict = null;
|
2014-03-21 04:28:22 +09:00
|
|
|
}
|
2011-10-25 08:55:23 +09:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-12-09 07:18:43 +09:00
|
|
|
Linearization.prototype = {
|
2012-04-05 05:43:26 +09:00
|
|
|
getInt: function Linearization_getInt(name) {
|
2011-10-25 08:55:23 +09:00
|
|
|
var linDict = this.linDict;
|
|
|
|
var obj;
|
2014-03-21 04:28:22 +09:00
|
|
|
if (isDict(linDict) && isInt(obj = linDict.get(name)) && obj > 0) {
|
2011-10-25 08:55:23 +09:00
|
|
|
return obj;
|
|
|
|
}
|
|
|
|
error('"' + name + '" field in linearization table is invalid');
|
|
|
|
},
|
2012-04-05 05:43:26 +09:00
|
|
|
getHint: function Linearization_getHint(index) {
|
2011-10-25 08:55:23 +09:00
|
|
|
var linDict = this.linDict;
|
|
|
|
var obj1, obj2;
|
2014-03-21 04:28:22 +09:00
|
|
|
if (isDict(linDict) && isArray(obj1 = linDict.get('H')) &&
|
|
|
|
obj1.length >= 2 && isInt(obj2 = obj1[index]) && obj2 > 0) {
|
2011-10-25 08:55:23 +09:00
|
|
|
return obj2;
|
|
|
|
}
|
|
|
|
error('Hints table in linearization table is invalid: ' + index);
|
|
|
|
},
|
|
|
|
get length() {
|
2014-03-21 04:28:22 +09:00
|
|
|
if (!isDict(this.linDict)) {
|
2011-10-25 08:55:23 +09:00
|
|
|
return 0;
|
2014-03-21 04:28:22 +09:00
|
|
|
}
|
2011-10-25 08:55:23 +09:00
|
|
|
return this.getInt('L');
|
|
|
|
},
|
|
|
|
get hintsOffset() {
|
|
|
|
return this.getHint(0);
|
|
|
|
},
|
|
|
|
get hintsLength() {
|
|
|
|
return this.getHint(1);
|
|
|
|
},
|
|
|
|
get hintsOffset2() {
|
|
|
|
return this.getHint(2);
|
|
|
|
},
|
|
|
|
get hintsLenth2() {
|
|
|
|
return this.getHint(3);
|
|
|
|
},
|
|
|
|
get objectNumberFirst() {
|
|
|
|
return this.getInt('O');
|
|
|
|
},
|
|
|
|
get endFirst() {
|
|
|
|
return this.getInt('E');
|
|
|
|
},
|
|
|
|
get numPages() {
|
|
|
|
return this.getInt('N');
|
|
|
|
},
|
|
|
|
get mainXRefEntriesOffset() {
|
|
|
|
return this.getInt('T');
|
|
|
|
},
|
|
|
|
get pageFirst() {
|
|
|
|
return this.getInt('P');
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2011-12-09 07:18:43 +09:00
|
|
|
return Linearization;
|
2011-10-25 08:55:23 +09:00
|
|
|
})();
|
2011-10-28 03:51:10 +09:00
|
|
|
|