1
0
Fork 0
mirror of https://github.com/mozilla/pdf.js.git synced 2025-04-25 09:38:06 +02:00

Split files into worker and main thread pieces.

This commit is contained in:
Brendan Dahl 2013-08-12 10:48:06 -07:00
parent e5cd027dce
commit 5ecce4996b
41 changed files with 817 additions and 786 deletions

453
src/core/bidi.js Normal file
View file

@ -0,0 +1,453 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
/* globals PDFJS */
/* 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.
*/
'use strict';
var bidi = PDFJS.bidi = (function bidiClosure() {
// Character types for symbols from 0000 to 00FF.
var baseTypes = [
'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'S', 'B', 'S', 'WS',
'B', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN',
'BN', 'BN', 'B', 'B', 'B', 'S', 'WS', 'ON', 'ON', 'ET', 'ET', 'ET', 'ON',
'ON', 'ON', 'ON', 'ON', 'ON', 'CS', 'ON', 'CS', 'ON', 'EN', 'EN', 'EN',
'EN', 'EN', 'EN', 'EN', 'EN', 'EN', 'EN', 'ON', 'ON', 'ON', 'ON', 'ON',
'ON', 'ON', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L',
'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'ON', 'ON',
'ON', 'ON', 'ON', 'ON', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L',
'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L',
'L', 'ON', 'ON', 'ON', 'ON', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'B', 'BN',
'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN',
'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN',
'BN', 'CS', 'ON', 'ET', 'ET', 'ET', 'ET', 'ON', 'ON', 'ON', 'ON', 'L', 'ON',
'ON', 'ON', 'ON', 'ON', 'ET', 'ET', 'EN', 'EN', 'ON', 'L', 'ON', 'ON', 'ON',
'EN', 'L', 'ON', 'ON', 'ON', 'ON', 'ON', 'L', 'L', 'L', 'L', 'L', 'L', 'L',
'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L',
'L', 'ON', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L',
'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L',
'L', 'L', 'L', 'ON', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L'
];
// Character types for symbols from 0600 to 06FF
var arabicTypes = [
'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL',
'CS', 'AL', 'ON', 'ON', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'AL',
'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL',
'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL',
'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL',
'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL',
'AL', 'AL', 'AL', 'AL', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM',
'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'AL', 'AL', 'AL', 'AL',
'AL', 'AL', 'AL', 'AN', 'AN', 'AN', 'AN', 'AN', 'AN', 'AN', 'AN', 'AN',
'AN', 'ET', 'AN', 'AN', 'AL', 'AL', 'AL', 'NSM', 'AL', 'AL', 'AL', 'AL',
'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL',
'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL',
'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL',
'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL',
'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL',
'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL',
'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL',
'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL',
'AL', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM',
'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'ON', 'NSM',
'NSM', 'NSM', 'NSM', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL',
'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL'
];
function isOdd(i) {
return (i & 1) !== 0;
}
function isEven(i) {
return (i & 1) === 0;
}
function findUnequal(arr, start, value) {
var j;
for (var j = start, jj = arr.length; j < jj; ++j) {
if (arr[j] != value)
return j;
}
return j;
}
function setValues(arr, start, end, value) {
for (var j = start; j < end; ++j) {
arr[j] = value;
}
}
function reverseValues(arr, start, end) {
for (var i = start, j = end - 1; i < j; ++i, --j) {
var temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
function mirrorGlyphs(c) {
/*
# BidiMirroring-1.txt
0028; 0029 # LEFT PARENTHESIS
0029; 0028 # RIGHT PARENTHESIS
003C; 003E # LESS-THAN SIGN
003E; 003C # GREATER-THAN SIGN
005B; 005D # LEFT SQUARE BRACKET
005D; 005B # RIGHT SQUARE BRACKET
007B; 007D # LEFT CURLY BRACKET
007D; 007B # RIGHT CURLY BRACKET
00AB; 00BB # LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
00BB; 00AB # RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
*/
switch (c) {
case '(':
return ')';
case ')':
return '(';
case '<':
return '>';
case '>':
return '<';
case ']':
return '[';
case '[':
return ']';
case '}':
return '{';
case '{':
return '}';
case '\u00AB':
return '\u00BB';
case '\u00BB':
return '\u00AB';
default:
return c;
}
}
function BidiResult(str, isLTR, vertical) {
this.str = str;
this.dir = vertical ? 'ttb' : isLTR ? 'ltr' : 'rtl';
}
function bidi(str, startLevel, vertical) {
var isLTR = true;
var strLength = str.length;
if (strLength === 0 || vertical)
return new BidiResult(str, isLTR, vertical);
// get types, fill arrays
var chars = [];
var types = [];
var oldtypes = [];
var numBidi = 0;
for (var i = 0; i < strLength; ++i) {
chars[i] = str.charAt(i);
var charCode = str.charCodeAt(i);
var charType = 'L';
if (charCode <= 0x00ff)
charType = baseTypes[charCode];
else if (0x0590 <= charCode && charCode <= 0x05f4)
charType = 'R';
else if (0x0600 <= charCode && charCode <= 0x06ff)
charType = arabicTypes[charCode & 0xff];
else if (0x0700 <= charCode && charCode <= 0x08AC)
charType = 'AL';
if (charType == 'R' || charType == 'AL' || charType == 'AN')
numBidi++;
oldtypes[i] = types[i] = charType;
}
// detect the bidi method
// if there are no rtl characters then no bidi needed
// if less than 30% chars are rtl then string is primarily ltr
// if more than 30% chars are rtl then string is primarily rtl
if (numBidi === 0) {
isLTR = true;
return new BidiResult(str, isLTR);
}
if (startLevel == -1) {
if ((strLength / numBidi) < 0.3) {
isLTR = true;
startLevel = 0;
} else {
isLTR = false;
startLevel = 1;
}
}
var levels = [];
for (var i = 0; i < strLength; ++i) {
levels[i] = startLevel;
}
/*
X1-X10: skip most of this, since we are NOT doing the embeddings.
*/
var e = isOdd(startLevel) ? 'R' : 'L';
var sor = e;
var eor = sor;
/*
W1. Examine each non-spacing mark (NSM) in the level run, and change the
type of the NSM to the type of the previous character. If the NSM is at the
start of the level run, it will get the type of sor.
*/
var lastType = sor;
for (var i = 0; i < strLength; ++i) {
if (types[i] == 'NSM')
types[i] = lastType;
else
lastType = types[i];
}
/*
W2. Search backwards from each instance of a European number until the
first strong type (R, L, AL, or sor) is found. If an AL is found, change
the type of the European number to Arabic number.
*/
var lastType = sor;
for (var i = 0; i < strLength; ++i) {
var t = types[i];
if (t == 'EN')
types[i] = (lastType == 'AL') ? 'AN' : 'EN';
else if (t == 'R' || t == 'L' || t == 'AL')
lastType = t;
}
/*
W3. Change all ALs to R.
*/
for (var i = 0; i < strLength; ++i) {
var t = types[i];
if (t == 'AL')
types[i] = 'R';
}
/*
W4. A single European separator between two European numbers changes to a
European number. A single common separator between two numbers of the same
type changes to that type:
*/
for (var i = 1; i < strLength - 1; ++i) {
if (types[i] == 'ES' && types[i - 1] == 'EN' && types[i + 1] == 'EN')
types[i] = 'EN';
if (types[i] == 'CS' && (types[i - 1] == 'EN' || types[i - 1] == 'AN') &&
types[i + 1] == types[i - 1])
types[i] = types[i - 1];
}
/*
W5. A sequence of European terminators adjacent to European numbers changes
to all European numbers:
*/
for (var i = 0; i < strLength; ++i) {
if (types[i] == 'EN') {
// do before
for (var j = i - 1; j >= 0; --j) {
if (types[j] != 'ET')
break;
types[j] = 'EN';
}
// do after
for (var j = i + 1; j < strLength; --j) {
if (types[j] != 'ET')
break;
types[j] = 'EN';
}
}
}
/*
W6. Otherwise, separators and terminators change to Other Neutral:
*/
for (var i = 0; i < strLength; ++i) {
var t = types[i];
if (t == 'WS' || t == 'ES' || t == 'ET' || t == 'CS')
types[i] = 'ON';
}
/*
W7. Search backwards from each instance of a European number until the
first strong type (R, L, or sor) is found. If an L is found, then change
the type of the European number to L.
*/
var lastType = sor;
for (var i = 0; i < strLength; ++i) {
var t = types[i];
if (t == 'EN')
types[i] = (lastType == 'L') ? 'L' : 'EN';
else if (t == 'R' || t == 'L')
lastType = t;
}
/*
N1. A sequence of neutrals takes the direction of the surrounding strong
text if the text on both sides has the same direction. European and Arabic
numbers are treated as though they were R. Start-of-level-run (sor) and
end-of-level-run (eor) are used at level run boundaries.
*/
for (var i = 0; i < strLength; ++i) {
if (types[i] == 'ON') {
var end = findUnequal(types, i + 1, 'ON');
var before = sor;
if (i > 0)
before = types[i - 1];
var after = eor;
if (end + 1 < strLength)
after = types[end + 1];
if (before != 'L')
before = 'R';
if (after != 'L')
after = 'R';
if (before == after)
setValues(types, i, end, before);
i = end - 1; // reset to end (-1 so next iteration is ok)
}
}
/*
N2. Any remaining neutrals take the embedding direction.
*/
for (var i = 0; i < strLength; ++i) {
if (types[i] == 'ON')
types[i] = e;
}
/*
I1. For all characters with an even (left-to-right) embedding direction,
those of type R go up one level and those of type AN or EN go up two
levels.
I2. For all characters with an odd (right-to-left) embedding direction,
those of type L, EN or AN go up one level.
*/
for (var i = 0; i < strLength; ++i) {
var t = types[i];
if (isEven(levels[i])) {
if (t == 'R') {
levels[i] += 1;
} else if (t == 'AN' || t == 'EN') {
levels[i] += 2;
}
} else { // isOdd, so
if (t == 'L' || t == 'AN' || t == 'EN') {
levels[i] += 1;
}
}
}
/*
L1. On each line, reset the embedding level of the following characters to
the paragraph embedding level:
segment separators,
paragraph separators,
any sequence of whitespace characters preceding a segment separator or
paragraph separator, and any sequence of white space characters at the end
of the line.
*/
// don't bother as text is only single line
/*
L2. From the highest level found in the text to the lowest odd level on
each line, reverse any contiguous sequence of characters that are at that
level or higher.
*/
// find highest level & lowest odd level
var highestLevel = -1;
var lowestOddLevel = 99;
for (var i = 0, ii = levels.length; i < ii; ++i) {
var level = levels[i];
if (highestLevel < level)
highestLevel = level;
if (lowestOddLevel > level && isOdd(level))
lowestOddLevel = level;
}
// now reverse between those limits
for (var level = highestLevel; level >= lowestOddLevel; --level) {
// find segments to reverse
var start = -1;
for (var i = 0, ii = levels.length; i < ii; ++i) {
if (levels[i] < level) {
if (start >= 0) {
reverseValues(chars, start, i);
start = -1;
}
} else if (start < 0) {
start = i;
}
}
if (start >= 0) {
reverseValues(chars, start, levels.length);
}
}
/*
L3. Combining marks applied to a right-to-left base character will at this
point precede their base character. If the rendering engine expects them to
follow the base characters in the final display process, then the ordering
of the marks and the base character must be reversed.
*/
// don't bother for now
/*
L4. A character that possesses the mirrored property as specified by
Section 4.7, Mirrored, must be depicted by a mirrored glyph if the resolved
directionality of that character is R.
*/
// don't mirror as characters are already mirrored in the pdf
// Finally, return string
var result = '';
for (var i = 0, ii = chars.length; i < ii; ++i) {
var ch = chars[i];
if (ch != '<' && ch != '>')
result += ch;
}
return new BidiResult(result, isLTR);
}
return bidi;
})();

119
src/core/charsets.js Normal file
View file

@ -0,0 +1,119 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
/* 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.
*/
'use strict';
var ISOAdobeCharset = [
'.notdef', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar',
'percent', 'ampersand', 'quoteright', 'parenleft', 'parenright',
'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero',
'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight',
'nine', 'colon', 'semicolon', 'less', 'equal', 'greater', 'question',
'at', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
'bracketleft', 'backslash', 'bracketright', 'asciicircum', 'underscore',
'quoteleft', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
'braceleft', 'bar', 'braceright', 'asciitilde', 'exclamdown', 'cent',
'sterling', 'fraction', 'yen', 'florin', 'section', 'currency',
'quotesingle', 'quotedblleft', 'guillemotleft', 'guilsinglleft',
'guilsinglright', 'fi', 'fl', 'endash', 'dagger', 'daggerdbl',
'periodcentered', 'paragraph', 'bullet', 'quotesinglbase',
'quotedblbase', 'quotedblright', 'guillemotright', 'ellipsis',
'perthousand', 'questiondown', 'grave', 'acute', 'circumflex', 'tilde',
'macron', 'breve', 'dotaccent', 'dieresis', 'ring', 'cedilla',
'hungarumlaut', 'ogonek', 'caron', 'emdash', 'AE', 'ordfeminine',
'Lslash', 'Oslash', 'OE', 'ordmasculine', 'ae', 'dotlessi', 'lslash',
'oslash', 'oe', 'germandbls', 'onesuperior', 'logicalnot', 'mu',
'trademark', 'Eth', 'onehalf', 'plusminus', 'Thorn', 'onequarter',
'divide', 'brokenbar', 'degree', 'thorn', 'threequarters', 'twosuperior',
'registered', 'minus', 'eth', 'multiply', 'threesuperior', 'copyright',
'Aacute', 'Acircumflex', 'Adieresis', 'Agrave', 'Aring', 'Atilde',
'Ccedilla', 'Eacute', 'Ecircumflex', 'Edieresis', 'Egrave', 'Iacute',
'Icircumflex', 'Idieresis', 'Igrave', 'Ntilde', 'Oacute', 'Ocircumflex',
'Odieresis', 'Ograve', 'Otilde', 'Scaron', 'Uacute', 'Ucircumflex',
'Udieresis', 'Ugrave', 'Yacute', 'Ydieresis', 'Zcaron', 'aacute',
'acircumflex', 'adieresis', 'agrave', 'aring', 'atilde', 'ccedilla',
'eacute', 'ecircumflex', 'edieresis', 'egrave', 'iacute', 'icircumflex',
'idieresis', 'igrave', 'ntilde', 'oacute', 'ocircumflex', 'odieresis',
'ograve', 'otilde', 'scaron', 'uacute', 'ucircumflex', 'udieresis',
'ugrave', 'yacute', 'ydieresis', 'zcaron'
];
var ExpertCharset = [
'.notdef', 'space', 'exclamsmall', 'Hungarumlautsmall', 'dollaroldstyle',
'dollarsuperior', 'ampersandsmall', 'Acutesmall', 'parenleftsuperior',
'parenrightsuperior', 'twodotenleader', 'onedotenleader', 'comma',
'hyphen', 'period', 'fraction', 'zerooldstyle', 'oneoldstyle',
'twooldstyle', 'threeoldstyle', 'fouroldstyle', 'fiveoldstyle',
'sixoldstyle', 'sevenoldstyle', 'eightoldstyle', 'nineoldstyle',
'colon', 'semicolon', 'commasuperior', 'threequartersemdash',
'periodsuperior', 'questionsmall', 'asuperior', 'bsuperior',
'centsuperior', 'dsuperior', 'esuperior', 'isuperior', 'lsuperior',
'msuperior', 'nsuperior', 'osuperior', 'rsuperior', 'ssuperior',
'tsuperior', 'ff', 'fi', 'fl', 'ffi', 'ffl', 'parenleftinferior',
'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', 'Gravesmall',
'Asmall', 'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall',
'Hsmall', 'Ismall', 'Jsmall', 'Ksmall', 'Lsmall', 'Msmall', 'Nsmall',
'Osmall', 'Psmall', 'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall',
'Vsmall', 'Wsmall', 'Xsmall', 'Ysmall', 'Zsmall', 'colonmonetary',
'onefitted', 'rupiah', 'Tildesmall', 'exclamdownsmall', 'centoldstyle',
'Lslashsmall', 'Scaronsmall', 'Zcaronsmall', 'Dieresissmall',
'Brevesmall', 'Caronsmall', 'Dotaccentsmall', 'Macronsmall',
'figuredash', 'hypheninferior', 'Ogoneksmall', 'Ringsmall',
'Cedillasmall', 'onequarter', 'onehalf', 'threequarters',
'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths',
'seveneighths', 'onethird', 'twothirds', 'zerosuperior', 'onesuperior',
'twosuperior', 'threesuperior', 'foursuperior', 'fivesuperior',
'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior',
'zeroinferior', 'oneinferior', 'twoinferior', 'threeinferior',
'fourinferior', 'fiveinferior', 'sixinferior', 'seveninferior',
'eightinferior', 'nineinferior', 'centinferior', 'dollarinferior',
'periodinferior', 'commainferior', 'Agravesmall', 'Aacutesmall',
'Acircumflexsmall', 'Atildesmall', 'Adieresissmall', 'Aringsmall',
'AEsmall', 'Ccedillasmall', 'Egravesmall', 'Eacutesmall',
'Ecircumflexsmall', 'Edieresissmall', 'Igravesmall', 'Iacutesmall',
'Icircumflexsmall', 'Idieresissmall', 'Ethsmall', 'Ntildesmall',
'Ogravesmall', 'Oacutesmall', 'Ocircumflexsmall', 'Otildesmall',
'Odieresissmall', 'OEsmall', 'Oslashsmall', 'Ugravesmall', 'Uacutesmall',
'Ucircumflexsmall', 'Udieresissmall', 'Yacutesmall', 'Thornsmall',
'Ydieresissmall'
];
var ExpertSubsetCharset = [
'.notdef', 'space', 'dollaroldstyle', 'dollarsuperior',
'parenleftsuperior', 'parenrightsuperior', 'twodotenleader',
'onedotenleader', 'comma', 'hyphen', 'period', 'fraction',
'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle',
'fouroldstyle', 'fiveoldstyle', 'sixoldstyle', 'sevenoldstyle',
'eightoldstyle', 'nineoldstyle', 'colon', 'semicolon', 'commasuperior',
'threequartersemdash', 'periodsuperior', 'asuperior', 'bsuperior',
'centsuperior', 'dsuperior', 'esuperior', 'isuperior', 'lsuperior',
'msuperior', 'nsuperior', 'osuperior', 'rsuperior', 'ssuperior',
'tsuperior', 'ff', 'fi', 'fl', 'ffi', 'ffl', 'parenleftinferior',
'parenrightinferior', 'hyphensuperior', 'colonmonetary', 'onefitted',
'rupiah', 'centoldstyle', 'figuredash', 'hypheninferior', 'onequarter',
'onehalf', 'threequarters', 'oneeighth', 'threeeighths', 'fiveeighths',
'seveneighths', 'onethird', 'twothirds', 'zerosuperior', 'onesuperior',
'twosuperior', 'threesuperior', 'foursuperior', 'fivesuperior',
'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior',
'zeroinferior', 'oneinferior', 'twoinferior', 'threeinferior',
'fourinferior', 'fiveinferior', 'sixinferior', 'seveninferior',
'eightinferior', 'nineinferior', 'centinferior', 'dollarinferior',
'periodinferior', 'commainferior'
];

476
src/core/chunked_stream.js Normal file
View file

@ -0,0 +1,476 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
/* 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.
*/
/* globals assert, MissingDataException, isInt, NetworkManager, Promise,
isEmptyObj */
'use strict';
var ChunkedStream = (function ChunkedStreamClosure() {
function ChunkedStream(length, chunkSize, manager) {
this.bytes = new Uint8Array(length);
this.start = 0;
this.pos = 0;
this.end = length;
this.chunkSize = chunkSize;
this.loadedChunks = [];
this.numChunksLoaded = 0;
this.numChunks = Math.ceil(length / chunkSize);
this.manager = manager;
}
// required methods for a stream. if a particular stream does not
// implement these, an error should be thrown
ChunkedStream.prototype = {
getMissingChunks: function ChunkedStream_getMissingChunks() {
var chunks = [];
for (var chunk = 0, n = this.numChunks; chunk < n; ++chunk) {
if (!(chunk in this.loadedChunks)) {
chunks.push(chunk);
}
}
return chunks;
},
getBaseStreams: function ChunkedStream_getBaseStreams() {
return [this];
},
allChunksLoaded: function ChunkedStream_allChunksLoaded() {
return this.numChunksLoaded === this.numChunks;
},
onReceiveData: function(begin, chunk) {
var end = begin + chunk.byteLength;
assert(begin % this.chunkSize === 0, 'Bad begin offset: ' + begin);
// Using this.length is inaccurate here since this.start can be moved
// See ChunkedStream.moveStart()
var length = this.bytes.length;
assert(end % this.chunkSize === 0 || end === length,
'Bad end offset: ' + end);
this.bytes.set(new Uint8Array(chunk), begin);
var chunkSize = this.chunkSize;
var beginChunk = Math.floor(begin / chunkSize);
var endChunk = Math.floor((end - 1) / chunkSize) + 1;
for (var chunk = beginChunk; chunk < endChunk; ++chunk) {
if (!(chunk in this.loadedChunks)) {
this.loadedChunks[chunk] = true;
++this.numChunksLoaded;
}
}
},
ensureRange: function ChunkedStream_ensureRange(begin, end) {
if (begin >= end) {
return;
}
var chunkSize = this.chunkSize;
var beginChunk = Math.floor(begin / chunkSize);
var endChunk = Math.floor((end - 1) / chunkSize) + 1;
for (var chunk = beginChunk; chunk < endChunk; ++chunk) {
if (!(chunk in this.loadedChunks)) {
throw new MissingDataException(begin, end);
}
}
},
nextEmptyChunk: function ChunkedStream_nextEmptyChunk(beginChunk) {
for (var chunk = beginChunk, n = this.numChunks; chunk < n; ++chunk) {
if (!(chunk in this.loadedChunks)) {
return chunk;
}
}
// Wrap around to beginning
for (var chunk = 0; chunk < beginChunk; ++chunk) {
if (!(chunk in this.loadedChunks)) {
return chunk;
}
}
return null;
},
hasChunk: function ChunkedStream_hasChunk(chunk) {
return chunk in this.loadedChunks;
},
get length() {
return this.end - this.start;
},
getByte: function ChunkedStream_getByte() {
var pos = this.pos;
if (pos >= this.end) {
return -1;
}
this.ensureRange(pos, pos + 1);
return this.bytes[this.pos++];
},
// returns subarray of original buffer
// should only be read
getBytes: function ChunkedStream_getBytes(length) {
var bytes = this.bytes;
var pos = this.pos;
var strEnd = this.end;
if (!length) {
this.ensureRange(pos, strEnd);
return bytes.subarray(pos, strEnd);
}
var end = pos + length;
if (end > strEnd)
end = strEnd;
this.ensureRange(pos, end);
this.pos = end;
return bytes.subarray(pos, end);
},
peekBytes: function ChunkedStream_peekBytes(length) {
var bytes = this.getBytes(length);
this.pos -= bytes.length;
return bytes;
},
getByteRange: function ChunkedStream_getBytes(begin, end) {
this.ensureRange(begin, end);
return this.bytes.subarray(begin, end);
},
skip: function ChunkedStream_skip(n) {
if (!n)
n = 1;
this.pos += n;
},
reset: function ChunkedStream_reset() {
this.pos = this.start;
},
moveStart: function ChunkedStream_moveStart() {
this.start = this.pos;
},
makeSubStream: function ChunkedStream_makeSubStream(start, length, dict) {
function ChunkedStreamSubstream() {}
ChunkedStreamSubstream.prototype = Object.create(this);
ChunkedStreamSubstream.prototype.getMissingChunks = function() {
var chunkSize = this.chunkSize;
var beginChunk = Math.floor(this.start / chunkSize);
var endChunk = Math.floor((this.end - 1) / chunkSize) + 1;
var missingChunks = [];
for (var chunk = beginChunk; chunk < endChunk; ++chunk) {
if (!(chunk in this.loadedChunks)) {
missingChunks.push(chunk);
}
}
return missingChunks;
};
var subStream = new ChunkedStreamSubstream();
subStream.pos = subStream.start = start;
subStream.end = start + length || this.end;
subStream.dict = dict;
return subStream;
},
isStream: true
};
return ChunkedStream;
})();
var ChunkedStreamManager = (function ChunkedStreamManagerClosure() {
function ChunkedStreamManager(length, chunkSize, url, args) {
var self = this;
this.stream = new ChunkedStream(length, chunkSize, this);
this.length = length;
this.chunkSize = chunkSize;
this.url = url;
this.disableAutoFetch = args.disableAutoFetch;
var msgHandler = this.msgHandler = args.msgHandler;
if (args.chunkedViewerLoading) {
msgHandler.on('OnDataRange', this.onReceiveData.bind(this));
msgHandler.on('OnDataProgress', this.onProgress.bind(this));
this.sendRequest = function ChunkedStreamManager_sendRequest(begin, end) {
msgHandler.send('RequestDataRange', { begin: begin, end: end });
};
} else {
var getXhr = function getXhr() {
//#if B2G
// return new XMLHttpRequest({ mozSystem: true });
//#else
return new XMLHttpRequest();
//#endif
};
this.networkManager = new NetworkManager(this.url, {
getXhr: getXhr,
httpHeaders: args.httpHeaders
});
this.sendRequest = function ChunkedStreamManager_sendRequest(begin, end) {
this.networkManager.requestRange(begin, end, {
onDone: this.onReceiveData.bind(this),
onProgress: this.onProgress.bind(this)
});
};
}
this.currRequestId = 0;
this.chunksNeededByRequest = {};
this.requestsByChunk = {};
this.callbacksByRequest = {};
this.loadedStream = new Promise();
}
ChunkedStreamManager.prototype = {
onLoadedStream: function ChunkedStreamManager_getLoadedStream() {
return this.loadedStream;
},
// Get all the chunks that are not yet loaded and groups them into
// contiguous ranges to load in as few requests as possible
requestAllChunks: function ChunkedStreamManager_requestAllChunks() {
var missingChunks = this.stream.getMissingChunks();
this.requestChunks(missingChunks);
return this.loadedStream;
},
requestChunks: function ChunkedStreamManager_requestChunks(chunks,
callback) {
var requestId = this.currRequestId++;
var chunksNeeded;
this.chunksNeededByRequest[requestId] = chunksNeeded = {};
for (var i = 0, ii = chunks.length; i < ii; i++) {
if (!this.stream.hasChunk(chunks[i])) {
chunksNeeded[chunks[i]] = true;
}
}
if (isEmptyObj(chunksNeeded)) {
if (callback) {
callback();
}
return;
}
this.callbacksByRequest[requestId] = callback;
var chunksToRequest = [];
for (var chunk in chunksNeeded) {
chunk = chunk | 0;
if (!(chunk in this.requestsByChunk)) {
this.requestsByChunk[chunk] = [];
chunksToRequest.push(chunk);
}
this.requestsByChunk[chunk].push(requestId);
}
if (!chunksToRequest.length) {
return;
}
var groupedChunksToRequest = this.groupChunks(chunksToRequest);
for (var i = 0; i < groupedChunksToRequest.length; ++i) {
var groupedChunk = groupedChunksToRequest[i];
var begin = groupedChunk.beginChunk * this.chunkSize;
var end = Math.min(groupedChunk.endChunk * this.chunkSize, this.length);
this.sendRequest(begin, end);
}
},
getStream: function ChunkedStreamManager_getStream() {
return this.stream;
},
// Loads any chunks in the requested range that are not yet loaded
requestRange: function ChunkedStreamManager_requestRange(
begin, end, callback) {
end = Math.min(end, this.length);
var beginChunk = this.getBeginChunk(begin);
var endChunk = this.getEndChunk(end);
var chunks = [];
for (var chunk = beginChunk; chunk < endChunk; ++chunk) {
chunks.push(chunk);
}
this.requestChunks(chunks, callback);
},
requestRanges: function ChunkedStreamManager_requestRanges(ranges,
callback) {
ranges = ranges || [];
var chunksToRequest = [];
for (var i = 0; i < ranges.length; i++) {
var beginChunk = this.getBeginChunk(ranges[i].begin);
var endChunk = this.getEndChunk(ranges[i].end);
for (var chunk = beginChunk; chunk < endChunk; ++chunk) {
if (chunksToRequest.indexOf(chunk) < 0) {
chunksToRequest.push(chunk);
}
}
}
chunksToRequest.sort(function(a, b) { return a - b; });
this.requestChunks(chunksToRequest, callback);
},
// Groups a sorted array of chunks into as few continguous larger
// chunks as possible
groupChunks: function ChunkedStreamManager_groupChunks(chunks) {
var groupedChunks = [];
var beginChunk;
var prevChunk;
for (var i = 0; i < chunks.length; ++i) {
var chunk = chunks[i];
if (!beginChunk) {
beginChunk = chunk;
}
if (prevChunk && prevChunk + 1 !== chunk) {
groupedChunks.push({
beginChunk: beginChunk, endChunk: prevChunk + 1});
beginChunk = chunk;
}
if (i + 1 === chunks.length) {
groupedChunks.push({
beginChunk: beginChunk, endChunk: chunk + 1});
}
prevChunk = chunk;
}
return groupedChunks;
},
onProgress: function ChunkedStreamManager_onProgress(args) {
var bytesLoaded = this.stream.numChunksLoaded * this.chunkSize +
args.loaded;
this.msgHandler.send('DocProgress', {
loaded: bytesLoaded,
total: this.length
});
},
onReceiveData: function ChunkedStreamManager_onReceiveData(args) {
var chunk = args.chunk;
var begin = args.begin;
var end = begin + chunk.byteLength;
var beginChunk = this.getBeginChunk(begin);
var endChunk = this.getEndChunk(end);
this.stream.onReceiveData(begin, chunk);
if (this.stream.allChunksLoaded()) {
this.loadedStream.resolve(this.stream);
}
var loadedRequests = [];
for (var chunk = beginChunk; chunk < endChunk; ++chunk) {
// The server might return more chunks than requested
var requestIds = this.requestsByChunk[chunk] || [];
delete this.requestsByChunk[chunk];
for (var i = 0; i < requestIds.length; ++i) {
var requestId = requestIds[i];
var chunksNeeded = this.chunksNeededByRequest[requestId];
if (chunk in chunksNeeded) {
delete chunksNeeded[chunk];
}
if (!isEmptyObj(chunksNeeded)) {
continue;
}
loadedRequests.push(requestId);
}
}
// If there are no pending requests, automatically fetch the next
// unfetched chunk of the PDF
if (!this.disableAutoFetch && isEmptyObj(this.requestsByChunk)) {
var nextEmptyChunk;
if (this.stream.numChunksLoaded === 1) {
// This is a special optimization so that after fetching the first
// chunk, rather than fetching the second chunk, we fetch the last
// chunk.
var lastChunk = this.stream.numChunks - 1;
if (!this.stream.hasChunk(lastChunk)) {
nextEmptyChunk = lastChunk;
}
} else {
nextEmptyChunk = this.stream.nextEmptyChunk(endChunk);
}
if (isInt(nextEmptyChunk)) {
this.requestChunks([nextEmptyChunk]);
}
}
for (var i = 0; i < loadedRequests.length; ++i) {
var requestId = loadedRequests[i];
var callback = this.callbacksByRequest[requestId];
delete this.callbacksByRequest[requestId];
if (callback) {
callback();
}
}
this.msgHandler.send('DocProgress', {
loaded: this.stream.numChunksLoaded * this.chunkSize,
total: this.length
});
},
getBeginChunk: function ChunkedStreamManager_getBeginChunk(begin) {
var chunk = Math.floor(begin / this.chunkSize);
return chunk;
},
getEndChunk: function ChunkedStreamManager_getEndChunk(end) {
if (end % this.chunkSize === 0) {
return end / this.chunkSize;
}
// 0 -> 0
// 1 -> 1
// 99 -> 1
// 100 -> 1
// 101 -> 2
var chunk = Math.floor((end - 1) / this.chunkSize) + 1;
return chunk;
}
};
return ChunkedStreamManager;
})();

6948
src/core/cidmaps.js Normal file

File diff suppressed because it is too large Load diff

500
src/core/core.js Normal file
View file

@ -0,0 +1,500 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
/* 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.
*/
/* globals assertWellFormed, calculateMD5, Catalog, error, info, isArray,
isArrayBuffer, isName, isStream, isString, Lexer,
Linearization, NullStream, PartialEvaluator, shadow, Stream,
StreamsSequenceStream, stringToPDFString, Util, XRef,
MissingDataException, Promise, Annotation, ObjectLoader, OperatorList
*/
'use strict';
var Page = (function PageClosure() {
function Page(pdfManager, xref, pageIndex, pageDict, ref) {
this.pdfManager = pdfManager;
this.pageIndex = pageIndex;
this.pageDict = pageDict;
this.xref = xref;
this.ref = ref;
this.idCounters = {
obj: 0
};
this.resourcesPromise = null;
}
Page.prototype = {
getPageProp: function Page_getPageProp(key) {
return this.pageDict.get(key);
},
inheritPageProp: function Page_inheritPageProp(key) {
var dict = this.pageDict;
var obj = dict.get(key);
while (obj === undefined) {
dict = dict.get('Parent');
if (!dict)
break;
obj = dict.get(key);
}
return obj;
},
get content() {
return this.getPageProp('Contents');
},
get resources() {
return shadow(this, 'resources', this.inheritPageProp('Resources'));
},
get mediaBox() {
var obj = this.inheritPageProp('MediaBox');
// Reset invalid media box to letter size.
if (!isArray(obj) || obj.length !== 4)
obj = [0, 0, 612, 792];
return shadow(this, 'mediaBox', obj);
},
get view() {
var mediaBox = this.mediaBox;
var cropBox = this.inheritPageProp('CropBox');
if (!isArray(cropBox) || cropBox.length !== 4)
return shadow(this, 'view', mediaBox);
// From the spec, 6th ed., p.963:
// "The crop, bleed, trim, and art boxes should not ordinarily
// extend beyond the boundaries of the media box. If they do, they are
// effectively reduced to their intersection with the media box."
cropBox = Util.intersect(cropBox, mediaBox);
if (!cropBox)
return shadow(this, 'view', mediaBox);
return shadow(this, 'view', cropBox);
},
get annotationRefs() {
return shadow(this, 'annotationRefs', this.inheritPageProp('Annots'));
},
get rotate() {
var rotate = this.inheritPageProp('Rotate') || 0;
// Normalize rotation so it's a multiple of 90 and between 0 and 270
if (rotate % 90 !== 0) {
rotate = 0;
} else if (rotate >= 360) {
rotate = rotate % 360;
} else if (rotate < 0) {
// The spec doesn't cover negatives, assume its counterclockwise
// rotation. The following is the other implementation of modulo.
rotate = ((rotate % 360) + 360) % 360;
}
return shadow(this, 'rotate', rotate);
},
getContentStream: function Page_getContentStream() {
var content = this.content;
var stream;
if (isArray(content)) {
// fetching items
var xref = this.xref;
var i, n = content.length;
var streams = [];
for (i = 0; i < n; ++i)
streams.push(xref.fetchIfRef(content[i]));
stream = new StreamsSequenceStream(streams);
} else if (isStream(content)) {
stream = content;
} else {
// replacing non-existent page content with empty one
stream = new NullStream();
}
return stream;
},
loadResources: function(keys) {
if (!this.resourcesPromise) {
// TODO: add async inheritPageProp and remove this.
this.resourcesPromise = this.pdfManager.ensure(this, 'resources');
}
var promise = new Promise();
this.resourcesPromise.then(function resourceSuccess() {
var objectLoader = new ObjectLoader(this.resources.map,
keys,
this.xref);
objectLoader.load().then(function objectLoaderSuccess() {
promise.resolve();
});
}.bind(this));
return promise;
},
getOperatorList: function Page_getOperatorList(handler) {
var self = this;
var promise = new Promise();
function reject(e) {
promise.reject(e);
}
var pageListPromise = new Promise();
var pdfManager = this.pdfManager;
var contentStreamPromise = pdfManager.ensure(this, 'getContentStream',
[]);
var resourcesPromise = this.loadResources([
'ExtGState',
'ColorSpace',
'Pattern',
'Shading',
'XObject',
'Font',
// ProcSet
// Properties
]);
var partialEvaluator = new PartialEvaluator(
pdfManager, this.xref, handler,
this.pageIndex, 'p' + this.pageIndex + '_',
this.idCounters);
var dataPromises = Promise.all(
[contentStreamPromise, resourcesPromise], reject);
dataPromises.then(function(data) {
var contentStream = data[0];
var opList = new OperatorList(handler, self.pageIndex);
handler.send('StartRenderPage', {
transparency: partialEvaluator.hasBlendModes(self.resources),
pageIndex: self.pageIndex
});
partialEvaluator.getOperatorList(contentStream, self.resources, opList);
pageListPromise.resolve(opList);
});
var annotationsPromise = pdfManager.ensure(this, 'annotations');
Promise.all([pageListPromise, annotationsPromise]).then(function(datas) {
var pageOpList = datas[0];
var annotations = datas[1];
if (annotations.length === 0) {
PartialEvaluator.optimizeQueue(pageOpList);
pageOpList.flush(true);
promise.resolve(pageOpList);
return;
}
var annotationsReadyPromise = Annotation.appendToOperatorList(
annotations, pageOpList, pdfManager, partialEvaluator);
annotationsReadyPromise.then(function () {
PartialEvaluator.optimizeQueue(pageOpList);
pageOpList.flush(true);
promise.resolve(pageOpList);
}, reject);
}, reject);
return promise;
},
extractTextContent: function Page_extractTextContent() {
var handler = {
on: function nullHandlerOn() {},
send: function nullHandlerSend() {}
};
var self = this;
var textContentPromise = new Promise();
var pdfManager = this.pdfManager;
var contentStreamPromise = pdfManager.ensure(this, 'getContentStream',
[]);
var resourcesPromise = this.loadResources([
'ExtGState',
'XObject',
'Font'
]);
var dataPromises = Promise.all([contentStreamPromise,
resourcesPromise]);
dataPromises.then(function(data) {
var contentStream = data[0];
var partialEvaluator = new PartialEvaluator(
pdfManager, self.xref, handler,
self.pageIndex, 'p' + self.pageIndex + '_',
self.idCounters);
var bidiTexts = partialEvaluator.getTextContent(contentStream,
self.resources);
textContentPromise.resolve(bidiTexts);
});
return textContentPromise;
},
getAnnotationsData: function Page_getAnnotationsData() {
var annotations = this.annotations;
var annotationsData = [];
for (var i = 0, n = annotations.length; i < n; ++i) {
annotationsData.push(annotations[i].getData());
}
return annotationsData;
},
get annotations() {
var annotations = [];
var annotationRefs = this.annotationRefs || [];
for (var i = 0, n = annotationRefs.length; i < n; ++i) {
var annotationRef = annotationRefs[i];
var annotation = Annotation.fromRef(this.xref, annotationRef);
if (annotation) {
annotations.push(annotation);
}
}
return shadow(this, 'annotations', annotations);
}
};
return Page;
})();
/**
* The `PDFDocument` holds all the data of the PDF file. Compared to the
* `PDFDoc`, this one doesn't have any job management code.
* Right now there exists one PDFDocument on the main thread + one object
* for each worker. If there is no worker support enabled, there are two
* `PDFDocument` objects on the main thread created.
*/
var PDFDocument = (function PDFDocumentClosure() {
function PDFDocument(pdfManager, arg, password) {
if (isStream(arg))
init.call(this, pdfManager, arg, password);
else if (isArrayBuffer(arg))
init.call(this, pdfManager, new Stream(arg), password);
else
error('PDFDocument: Unknown argument type');
}
function init(pdfManager, stream, password) {
assertWellFormed(stream.length > 0, 'stream must have data');
this.pdfManager = pdfManager;
this.stream = stream;
var xref = new XRef(this.stream, password, pdfManager);
this.xref = xref;
}
function find(stream, needle, limit, backwards) {
var pos = stream.pos;
var end = stream.end;
var str = '';
if (pos + limit > end)
limit = end - pos;
for (var n = 0; n < limit; ++n)
str += String.fromCharCode(stream.getByte());
stream.pos = pos;
var index = backwards ? str.lastIndexOf(needle) : str.indexOf(needle);
if (index == -1)
return false; /* not found */
stream.pos += index;
return true; /* found */
}
var DocumentInfoValidators = {
get entries() {
// Lazily build this since all the validation functions below are not
// defined until after this file loads.
return shadow(this, 'entries', {
Title: isString,
Author: isString,
Subject: isString,
Keywords: isString,
Creator: isString,
Producer: isString,
CreationDate: isString,
ModDate: isString,
Trapped: isName
});
}
};
PDFDocument.prototype = {
parse: function PDFDocument_parse(recoveryMode) {
this.setup(recoveryMode);
this.acroForm = this.catalog.catDict.get('AcroForm');
},
get linearization() {
var length = this.stream.length;
var linearization = false;
if (length) {
try {
linearization = new Linearization(this.stream);
if (linearization.length != length) {
linearization = false;
}
} catch (err) {
if (err instanceof MissingDataException) {
throw err;
}
info('The linearization data is not available ' +
'or unreadable PDF data is found');
linearization = false;
}
}
// shadow the prototype getter with a data property
return shadow(this, 'linearization', linearization);
},
get startXRef() {
var stream = this.stream;
var startXRef = 0;
var linearization = this.linearization;
if (linearization) {
// Find end of first obj.
stream.reset();
if (find(stream, 'endobj', 1024))
startXRef = stream.pos + 6;
} else {
// Find startxref by jumping backward from the end of the file.
var step = 1024;
var found = false, pos = stream.end;
while (!found && pos > 0) {
pos -= step - 'startxref'.length;
if (pos < 0)
pos = 0;
stream.pos = pos;
found = find(stream, 'startxref', step, true);
}
if (found) {
stream.skip(9);
var ch;
do {
ch = stream.getByte();
} while (Lexer.isSpace(ch));
var str = '';
while (ch >= 0x20 && ch <= 0x39) { // < '9'
str += String.fromCharCode(ch);
ch = stream.getByte();
}
startXRef = parseInt(str, 10);
if (isNaN(startXRef))
startXRef = 0;
}
}
// shadow the prototype getter with a data property
return shadow(this, 'startXRef', startXRef);
},
get mainXRefEntriesOffset() {
var mainXRefEntriesOffset = 0;
var linearization = this.linearization;
if (linearization)
mainXRefEntriesOffset = linearization.mainXRefEntriesOffset;
// shadow the prototype getter with a data property
return shadow(this, 'mainXRefEntriesOffset', mainXRefEntriesOffset);
},
// Find the header, remove leading garbage and setup the stream
// starting from the header.
checkHeader: function PDFDocument_checkHeader() {
var stream = this.stream;
stream.reset();
if (find(stream, '%PDF-', 1024)) {
// Found the header, trim off any garbage before it.
stream.moveStart();
// Reading file format version
var MAX_VERSION_LENGTH = 12;
var version = '', ch;
while ((ch = stream.getByte()) > 0x20) { // SPACE
if (version.length >= MAX_VERSION_LENGTH) {
break;
}
version += String.fromCharCode(ch);
}
// removing "%PDF-"-prefix
this.pdfFormatVersion = version.substring(5);
return;
}
// May not be a PDF file, continue anyway.
},
parseStartXRef: function PDFDocument_parseStartXRef() {
var startXRef = this.startXRef;
this.xref.setStartXRef(startXRef);
},
setup: function PDFDocument_setup(recoveryMode) {
this.xref.parse(recoveryMode);
this.catalog = new Catalog(this.pdfManager, this.xref);
},
get numPages() {
var linearization = this.linearization;
var num = linearization ? linearization.numPages : this.catalog.numPages;
// shadow the prototype getter
return shadow(this, 'numPages', num);
},
get documentInfo() {
var docInfo = {
PDFFormatVersion: this.pdfFormatVersion,
IsAcroFormPresent: !!this.acroForm
};
var infoDict;
try {
infoDict = this.xref.trailer.get('Info');
} catch (err) {
info('The document information dictionary is invalid.');
}
if (infoDict) {
var validEntries = DocumentInfoValidators.entries;
// Only fill the document info with valid entries from the spec.
for (var key in validEntries) {
if (infoDict.has(key)) {
var value = infoDict.get(key);
// Make sure the value conforms to the spec.
if (validEntries[key](value)) {
docInfo[key] = typeof value !== 'string' ? value :
stringToPDFString(value);
} else {
info('Bad value in document info for "' + key + '"');
}
}
}
}
return shadow(this, 'documentInfo', docInfo);
},
get fingerprint() {
var xref = this.xref, fileID;
if (xref.trailer.has('ID')) {
fileID = '';
var id = xref.trailer.get('ID')[0];
id.split('').forEach(function(el) {
fileID += Number(el.charCodeAt(0)).toString(16);
});
} else {
// If we got no fileID, then we generate one,
// from the first 100 bytes of PDF
var data = this.stream.bytes.subarray(0, 100);
var hash = calculateMD5(data, 0, data.length);
fileID = '';
for (var i = 0, length = hash.length; i < length; i++) {
fileID += Number(hash[i]).toString(16);
}
}
return shadow(this, 'fingerprint', fileID);
},
traversePages: function PDFDocument_traversePages() {
this.catalog.traversePages();
},
getPage: function PDFDocument_getPage(pageIndex) {
return this.catalog.getPage(pageIndex);
}
};
return PDFDocument;
})();

674
src/core/crypto.js Normal file
View file

@ -0,0 +1,674 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
/* 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.
*/
/* globals bytesToString, DecryptStream, error, isInt, isName, Name,
PasswordException, PasswordResponses, stringToBytes */
'use strict';
var ARCFourCipher = (function ARCFourCipherClosure() {
function ARCFourCipher(key) {
this.a = 0;
this.b = 0;
var s = new Uint8Array(256);
var i, j = 0, tmp, keyLength = key.length;
for (i = 0; i < 256; ++i)
s[i] = i;
for (i = 0; i < 256; ++i) {
tmp = s[i];
j = (j + tmp + key[i % keyLength]) & 0xFF;
s[i] = s[j];
s[j] = tmp;
}
this.s = s;
}
ARCFourCipher.prototype = {
encryptBlock: function ARCFourCipher_encryptBlock(data) {
var i, n = data.length, tmp, tmp2;
var a = this.a, b = this.b, s = this.s;
var output = new Uint8Array(n);
for (i = 0; i < n; ++i) {
a = (a + 1) & 0xFF;
tmp = s[a];
b = (b + tmp) & 0xFF;
tmp2 = s[b];
s[a] = tmp2;
s[b] = tmp;
output[i] = data[i] ^ s[(tmp + tmp2) & 0xFF];
}
this.a = a;
this.b = b;
return output;
}
};
ARCFourCipher.prototype.decryptBlock = ARCFourCipher.prototype.encryptBlock;
return ARCFourCipher;
})();
var calculateMD5 = (function calculateMD5Closure() {
var r = new Uint8Array([
7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21]);
var k = new Int32Array([
-680876936, -389564586, 606105819, -1044525330, -176418897, 1200080426,
-1473231341, -45705983, 1770035416, -1958414417, -42063, -1990404162,
1804603682, -40341101, -1502002290, 1236535329, -165796510, -1069501632,
643717713, -373897302, -701558691, 38016083, -660478335, -405537848,
568446438, -1019803690, -187363961, 1163531501, -1444681467, -51403784,
1735328473, -1926607734, -378558, -2022574463, 1839030562, -35309556,
-1530992060, 1272893353, -155497632, -1094730640, 681279174, -358537222,
-722521979, 76029189, -640364487, -421815835, 530742520, -995338651,
-198630844, 1126891415, -1416354905, -57434055, 1700485571, -1894986606,
-1051523, -2054922799, 1873313359, -30611744, -1560198380, 1309151649,
-145523070, -1120210379, 718787259, -343485551]);
function hash(data, offset, length) {
var h0 = 1732584193, h1 = -271733879, h2 = -1732584194, h3 = 271733878;
// pre-processing
var paddedLength = (length + 72) & ~63; // data + 9 extra bytes
var padded = new Uint8Array(paddedLength);
var i, j, n;
for (i = 0; i < length; ++i)
padded[i] = data[offset++];
padded[i++] = 0x80;
n = paddedLength - 8;
while (i < n)
padded[i++] = 0;
padded[i++] = (length << 3) & 0xFF;
padded[i++] = (length >> 5) & 0xFF;
padded[i++] = (length >> 13) & 0xFF;
padded[i++] = (length >> 21) & 0xFF;
padded[i++] = (length >>> 29) & 0xFF;
padded[i++] = 0;
padded[i++] = 0;
padded[i++] = 0;
// chunking
// TODO ArrayBuffer ?
var w = new Int32Array(16);
for (i = 0; i < paddedLength;) {
for (j = 0; j < 16; ++j, i += 4) {
w[j] = (padded[i] | (padded[i + 1] << 8) |
(padded[i + 2] << 16) | (padded[i + 3] << 24));
}
var a = h0, b = h1, c = h2, d = h3, f, g;
for (j = 0; j < 64; ++j) {
if (j < 16) {
f = (b & c) | ((~b) & d);
g = j;
} else if (j < 32) {
f = (d & b) | ((~d) & c);
g = (5 * j + 1) & 15;
} else if (j < 48) {
f = b ^ c ^ d;
g = (3 * j + 5) & 15;
} else {
f = c ^ (b | (~d));
g = (7 * j) & 15;
}
var tmp = d, rotateArg = (a + f + k[j] + w[g]) | 0, rotate = r[j];
d = c;
c = b;
b = (b + ((rotateArg << rotate) | (rotateArg >>> (32 - rotate)))) | 0;
a = tmp;
}
h0 = (h0 + a) | 0;
h1 = (h1 + b) | 0;
h2 = (h2 + c) | 0;
h3 = (h3 + d) | 0;
}
return new Uint8Array([
h0 & 0xFF, (h0 >> 8) & 0xFF, (h0 >> 16) & 0xFF, (h0 >>> 24) & 0xFF,
h1 & 0xFF, (h1 >> 8) & 0xFF, (h1 >> 16) & 0xFF, (h1 >>> 24) & 0xFF,
h2 & 0xFF, (h2 >> 8) & 0xFF, (h2 >> 16) & 0xFF, (h2 >>> 24) & 0xFF,
h3 & 0xFF, (h3 >> 8) & 0xFF, (h3 >> 16) & 0xFF, (h3 >>> 24) & 0xFF
]);
}
return hash;
})();
var NullCipher = (function NullCipherClosure() {
function NullCipher() {
}
NullCipher.prototype = {
decryptBlock: function NullCipher_decryptBlock(data) {
return data;
}
};
return NullCipher;
})();
var AES128Cipher = (function AES128CipherClosure() {
var rcon = new Uint8Array([
0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c,
0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a,
0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd,
0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a,
0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80,
0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6,
0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72,
0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc,
0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10,
0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e,
0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5,
0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94,
0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02,
0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d,
0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d,
0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f,
0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb,
0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c,
0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a,
0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd,
0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a,
0x74, 0xe8, 0xcb, 0x8d]);
var s = new Uint8Array([
0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b,
0xfe, 0xd7, 0xab, 0x76, 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0,
0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, 0xb7, 0xfd, 0x93, 0x26,
0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2,
0xeb, 0x27, 0xb2, 0x75, 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0,
0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, 0x53, 0xd1, 0x00, 0xed,
0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f,
0x50, 0x3c, 0x9f, 0xa8, 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5,
0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 0xcd, 0x0c, 0x13, 0xec,
0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14,
0xde, 0x5e, 0x0b, 0xdb, 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c,
0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, 0xe7, 0xc8, 0x37, 0x6d,
0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f,
0x4b, 0xbd, 0x8b, 0x8a, 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e,
0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, 0xe1, 0xf8, 0x98, 0x11,
0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f,
0xb0, 0x54, 0xbb, 0x16]);
var inv_s = new Uint8Array([
0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e,
0x81, 0xf3, 0xd7, 0xfb, 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87,
0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, 0x54, 0x7b, 0x94, 0x32,
0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e,
0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49,
0x6d, 0x8b, 0xd1, 0x25, 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16,
0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, 0x6c, 0x70, 0x48, 0x50,
0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84,
0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05,
0xb8, 0xb3, 0x45, 0x06, 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02,
0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, 0x3a, 0x91, 0x11, 0x41,
0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73,
0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8,
0x1c, 0x75, 0xdf, 0x6e, 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89,
0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, 0xfc, 0x56, 0x3e, 0x4b,
0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4,
0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59,
0x27, 0x80, 0xec, 0x5f, 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d,
0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, 0xa0, 0xe0, 0x3b, 0x4d,
0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61,
0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63,
0x55, 0x21, 0x0c, 0x7d]);
var mix = new Uint32Array([
0x00000000, 0x0e090d0b, 0x1c121a16, 0x121b171d, 0x3824342c, 0x362d3927,
0x24362e3a, 0x2a3f2331, 0x70486858, 0x7e416553, 0x6c5a724e, 0x62537f45,
0x486c5c74, 0x4665517f, 0x547e4662, 0x5a774b69, 0xe090d0b0, 0xee99ddbb,
0xfc82caa6, 0xf28bc7ad, 0xd8b4e49c, 0xd6bde997, 0xc4a6fe8a, 0xcaaff381,
0x90d8b8e8, 0x9ed1b5e3, 0x8ccaa2fe, 0x82c3aff5, 0xa8fc8cc4, 0xa6f581cf,
0xb4ee96d2, 0xbae79bd9, 0xdb3bbb7b, 0xd532b670, 0xc729a16d, 0xc920ac66,
0xe31f8f57, 0xed16825c, 0xff0d9541, 0xf104984a, 0xab73d323, 0xa57ade28,
0xb761c935, 0xb968c43e, 0x9357e70f, 0x9d5eea04, 0x8f45fd19, 0x814cf012,
0x3bab6bcb, 0x35a266c0, 0x27b971dd, 0x29b07cd6, 0x038f5fe7, 0x0d8652ec,
0x1f9d45f1, 0x119448fa, 0x4be30393, 0x45ea0e98, 0x57f11985, 0x59f8148e,
0x73c737bf, 0x7dce3ab4, 0x6fd52da9, 0x61dc20a2, 0xad766df6, 0xa37f60fd,
0xb16477e0, 0xbf6d7aeb, 0x955259da, 0x9b5b54d1, 0x894043cc, 0x87494ec7,
0xdd3e05ae, 0xd33708a5, 0xc12c1fb8, 0xcf2512b3, 0xe51a3182, 0xeb133c89,
0xf9082b94, 0xf701269f, 0x4de6bd46, 0x43efb04d, 0x51f4a750, 0x5ffdaa5b,
0x75c2896a, 0x7bcb8461, 0x69d0937c, 0x67d99e77, 0x3daed51e, 0x33a7d815,
0x21bccf08, 0x2fb5c203, 0x058ae132, 0x0b83ec39, 0x1998fb24, 0x1791f62f,
0x764dd68d, 0x7844db86, 0x6a5fcc9b, 0x6456c190, 0x4e69e2a1, 0x4060efaa,
0x527bf8b7, 0x5c72f5bc, 0x0605bed5, 0x080cb3de, 0x1a17a4c3, 0x141ea9c8,
0x3e218af9, 0x302887f2, 0x223390ef, 0x2c3a9de4, 0x96dd063d, 0x98d40b36,
0x8acf1c2b, 0x84c61120, 0xaef93211, 0xa0f03f1a, 0xb2eb2807, 0xbce2250c,
0xe6956e65, 0xe89c636e, 0xfa877473, 0xf48e7978, 0xdeb15a49, 0xd0b85742,
0xc2a3405f, 0xccaa4d54, 0x41ecdaf7, 0x4fe5d7fc, 0x5dfec0e1, 0x53f7cdea,
0x79c8eedb, 0x77c1e3d0, 0x65daf4cd, 0x6bd3f9c6, 0x31a4b2af, 0x3fadbfa4,
0x2db6a8b9, 0x23bfa5b2, 0x09808683, 0x07898b88, 0x15929c95, 0x1b9b919e,
0xa17c0a47, 0xaf75074c, 0xbd6e1051, 0xb3671d5a, 0x99583e6b, 0x97513360,
0x854a247d, 0x8b432976, 0xd134621f, 0xdf3d6f14, 0xcd267809, 0xc32f7502,
0xe9105633, 0xe7195b38, 0xf5024c25, 0xfb0b412e, 0x9ad7618c, 0x94de6c87,
0x86c57b9a, 0x88cc7691, 0xa2f355a0, 0xacfa58ab, 0xbee14fb6, 0xb0e842bd,
0xea9f09d4, 0xe49604df, 0xf68d13c2, 0xf8841ec9, 0xd2bb3df8, 0xdcb230f3,
0xcea927ee, 0xc0a02ae5, 0x7a47b13c, 0x744ebc37, 0x6655ab2a, 0x685ca621,
0x42638510, 0x4c6a881b, 0x5e719f06, 0x5078920d, 0x0a0fd964, 0x0406d46f,
0x161dc372, 0x1814ce79, 0x322bed48, 0x3c22e043, 0x2e39f75e, 0x2030fa55,
0xec9ab701, 0xe293ba0a, 0xf088ad17, 0xfe81a01c, 0xd4be832d, 0xdab78e26,
0xc8ac993b, 0xc6a59430, 0x9cd2df59, 0x92dbd252, 0x80c0c54f, 0x8ec9c844,
0xa4f6eb75, 0xaaffe67e, 0xb8e4f163, 0xb6edfc68, 0x0c0a67b1, 0x02036aba,
0x10187da7, 0x1e1170ac, 0x342e539d, 0x3a275e96, 0x283c498b, 0x26354480,
0x7c420fe9, 0x724b02e2, 0x605015ff, 0x6e5918f4, 0x44663bc5, 0x4a6f36ce,
0x587421d3, 0x567d2cd8, 0x37a10c7a, 0x39a80171, 0x2bb3166c, 0x25ba1b67,
0x0f853856, 0x018c355d, 0x13972240, 0x1d9e2f4b, 0x47e96422, 0x49e06929,
0x5bfb7e34, 0x55f2733f, 0x7fcd500e, 0x71c45d05, 0x63df4a18, 0x6dd64713,
0xd731dcca, 0xd938d1c1, 0xcb23c6dc, 0xc52acbd7, 0xef15e8e6, 0xe11ce5ed,
0xf307f2f0, 0xfd0efffb, 0xa779b492, 0xa970b999, 0xbb6bae84, 0xb562a38f,
0x9f5d80be, 0x91548db5, 0x834f9aa8, 0x8d4697a3]);
function expandKey128(cipherKey) {
var b = 176, result = new Uint8Array(b);
result.set(cipherKey);
for (var j = 16, i = 1; j < b; ++i) {
// RotWord
var t1 = result[j - 3], t2 = result[j - 2],
t3 = result[j - 1], t4 = result[j - 4];
// SubWord
t1 = s[t1]; t2 = s[t2]; t3 = s[t3]; t4 = s[t4];
// Rcon
t1 = t1 ^ rcon[i];
for (var n = 0; n < 4; ++n) {
result[j] = (t1 ^= result[j - 16]); j++;
result[j] = (t2 ^= result[j - 16]); j++;
result[j] = (t3 ^= result[j - 16]); j++;
result[j] = (t4 ^= result[j - 16]); j++;
}
}
return result;
}
function decrypt128(input, key) {
var state = new Uint8Array(16);
state.set(input);
var i, j, k;
var t, u, v;
// AddRoundKey
for (j = 0, k = 160; j < 16; ++j, ++k)
state[j] ^= key[k];
for (i = 9; i >= 1; --i) {
// InvShiftRows
t = state[13]; state[13] = state[9]; state[9] = state[5];
state[5] = state[1]; state[1] = t;
t = state[14]; u = state[10]; state[14] = state[6];
state[10] = state[2]; state[6] = t; state[2] = u;
t = state[15]; u = state[11]; v = state[7]; state[15] = state[3];
state[11] = t; state[7] = u; state[3] = v;
// InvSubBytes
for (j = 0; j < 16; ++j)
state[j] = inv_s[state[j]];
// AddRoundKey
for (j = 0, k = i * 16; j < 16; ++j, ++k)
state[j] ^= key[k];
// InvMixColumns
for (j = 0; j < 16; j += 4) {
var s0 = mix[state[j]], s1 = mix[state[j + 1]],
s2 = mix[state[j + 2]], s3 = mix[state[j + 3]];
t = (s0 ^ (s1 >>> 8) ^ (s1 << 24) ^ (s2 >>> 16) ^ (s2 << 16) ^
(s3 >>> 24) ^ (s3 << 8));
state[j] = (t >>> 24) & 0xFF;
state[j + 1] = (t >> 16) & 0xFF;
state[j + 2] = (t >> 8) & 0xFF;
state[j + 3] = t & 0xFF;
}
}
// InvShiftRows
t = state[13]; state[13] = state[9]; state[9] = state[5];
state[5] = state[1]; state[1] = t;
t = state[14]; u = state[10]; state[14] = state[6];
state[10] = state[2]; state[6] = t; state[2] = u;
t = state[15]; u = state[11]; v = state[7]; state[15] = state[3];
state[11] = t; state[7] = u; state[3] = v;
for (j = 0; j < 16; ++j) {
// InvSubBytes
state[j] = inv_s[state[j]];
// AddRoundKey
state[j] ^= key[j];
}
return state;
}
function AES128Cipher(key) {
this.key = expandKey128(key);
this.buffer = new Uint8Array(16);
this.bufferPosition = 0;
}
function decryptBlock2(data, finalize) {
var i, j, ii, sourceLength = data.length,
buffer = this.buffer, bufferLength = this.bufferPosition,
result = [], iv = this.iv;
for (i = 0; i < sourceLength; ++i) {
buffer[bufferLength] = data[i];
++bufferLength;
if (bufferLength < 16)
continue;
// buffer is full, decrypting
var plain = decrypt128(buffer, this.key);
// xor-ing the IV vector to get plain text
for (j = 0; j < 16; ++j)
plain[j] ^= iv[j];
iv = buffer;
result.push(plain);
buffer = new Uint8Array(16);
bufferLength = 0;
}
// saving incomplete buffer
this.buffer = buffer;
this.bufferLength = bufferLength;
this.iv = iv;
if (result.length === 0) {
return new Uint8Array([]);
}
// combining plain text blocks into one
var outputLength = 16 * result.length;
if (finalize) {
// undo a padding that is described in RFC 2898
var lastBlock = result[result.length - 1];
outputLength -= lastBlock[15];
result[result.length - 1] = lastBlock.subarray(0, 16 - lastBlock[15]);
}
var output = new Uint8Array(outputLength);
for (i = 0, j = 0, ii = result.length; i < ii; ++i, j += 16)
output.set(result[i], j);
return output;
}
AES128Cipher.prototype = {
decryptBlock: function AES128Cipher_decryptBlock(data, finalize) {
var i, sourceLength = data.length;
var buffer = this.buffer, bufferLength = this.bufferPosition;
// waiting for IV values -- they are at the start of the stream
for (i = 0; bufferLength < 16 && i < sourceLength; ++i, ++bufferLength)
buffer[bufferLength] = data[i];
if (bufferLength < 16) {
// need more data
this.bufferLength = bufferLength;
return new Uint8Array([]);
}
this.iv = buffer;
this.buffer = new Uint8Array(16);
this.bufferLength = 0;
// starting decryption
this.decryptBlock = decryptBlock2;
return this.decryptBlock(data.subarray(16), finalize);
}
};
return AES128Cipher;
})();
var CipherTransform = (function CipherTransformClosure() {
function CipherTransform(stringCipherConstructor, streamCipherConstructor) {
this.stringCipherConstructor = stringCipherConstructor;
this.streamCipherConstructor = streamCipherConstructor;
}
CipherTransform.prototype = {
createStream: function CipherTransform_createStream(stream) {
var cipher = new this.streamCipherConstructor();
return new DecryptStream(stream,
function cipherTransformDecryptStream(data, finalize) {
return cipher.decryptBlock(data, finalize);
}
);
},
decryptString: function CipherTransform_decryptString(s) {
var cipher = new this.stringCipherConstructor();
var data = stringToBytes(s);
data = cipher.decryptBlock(data, true);
return bytesToString(data);
}
};
return CipherTransform;
})();
var CipherTransformFactory = (function CipherTransformFactoryClosure() {
var defaultPasswordBytes = new Uint8Array([
0x28, 0xBF, 0x4E, 0x5E, 0x4E, 0x75, 0x8A, 0x41,
0x64, 0x00, 0x4E, 0x56, 0xFF, 0xFA, 0x01, 0x08,
0x2E, 0x2E, 0x00, 0xB6, 0xD0, 0x68, 0x3E, 0x80,
0x2F, 0x0C, 0xA9, 0xFE, 0x64, 0x53, 0x69, 0x7A]);
function prepareKeyData(fileId, password, ownerPassword, userPassword,
flags, revision, keyLength, encryptMetadata) {
var hashData = new Uint8Array(100), i = 0, j, n;
if (password) {
n = Math.min(32, password.length);
for (; i < n; ++i)
hashData[i] = password[i];
}
j = 0;
while (i < 32) {
hashData[i++] = defaultPasswordBytes[j++];
}
// as now the padded password in the hashData[0..i]
for (j = 0, n = ownerPassword.length; j < n; ++j)
hashData[i++] = ownerPassword[j];
hashData[i++] = flags & 0xFF;
hashData[i++] = (flags >> 8) & 0xFF;
hashData[i++] = (flags >> 16) & 0xFF;
hashData[i++] = (flags >>> 24) & 0xFF;
for (j = 0, n = fileId.length; j < n; ++j)
hashData[i++] = fileId[j];
if (revision >= 4 && !encryptMetadata) {
hashData[i++] = 0xFF;
hashData[i++] = 0xFF;
hashData[i++] = 0xFF;
hashData[i++] = 0xFF;
}
var hash = calculateMD5(hashData, 0, i);
var keyLengthInBytes = keyLength >> 3;
if (revision >= 3) {
for (j = 0; j < 50; ++j) {
hash = calculateMD5(hash, 0, keyLengthInBytes);
}
}
var encryptionKey = hash.subarray(0, keyLengthInBytes);
var cipher, checkData;
if (revision >= 3) {
for (i = 0; i < 32; ++i)
hashData[i] = defaultPasswordBytes[i];
for (j = 0, n = fileId.length; j < n; ++j)
hashData[i++] = fileId[j];
cipher = new ARCFourCipher(encryptionKey);
var checkData = cipher.encryptBlock(calculateMD5(hashData, 0, i));
n = encryptionKey.length;
var derivedKey = new Uint8Array(n), k;
for (j = 1; j <= 19; ++j) {
for (k = 0; k < n; ++k)
derivedKey[k] = encryptionKey[k] ^ j;
cipher = new ARCFourCipher(derivedKey);
checkData = cipher.encryptBlock(checkData);
}
for (j = 0, n = checkData.length; j < n; ++j) {
if (userPassword[j] != checkData[j])
return null;
}
} else {
cipher = new ARCFourCipher(encryptionKey);
checkData = cipher.encryptBlock(defaultPasswordBytes);
for (j = 0, n = checkData.length; j < n; ++j) {
if (userPassword[j] != checkData[j])
return null;
}
}
return encryptionKey;
}
function decodeUserPassword(password, ownerPassword, revision, keyLength) {
var hashData = new Uint8Array(32), i = 0, j, n;
n = Math.min(32, password.length);
for (; i < n; ++i)
hashData[i] = password[i];
j = 0;
while (i < 32) {
hashData[i++] = defaultPasswordBytes[j++];
}
var hash = calculateMD5(hashData, 0, i);
var keyLengthInBytes = keyLength >> 3;
if (revision >= 3) {
for (j = 0; j < 50; ++j) {
hash = calculateMD5(hash, 0, hash.length);
}
}
var cipher, userPassword;
if (revision >= 3) {
userPassword = ownerPassword;
var derivedKey = new Uint8Array(keyLengthInBytes), k;
for (j = 19; j >= 0; j--) {
for (k = 0; k < keyLengthInBytes; ++k)
derivedKey[k] = hash[k] ^ j;
cipher = new ARCFourCipher(derivedKey);
userPassword = cipher.encryptBlock(userPassword);
}
} else {
cipher = new ARCFourCipher(hash.subarray(0, keyLengthInBytes));
userPassword = cipher.encryptBlock(ownerPassword);
}
return userPassword;
}
var identityName = new Name('Identity');
function CipherTransformFactory(dict, fileId, password) {
var filter = dict.get('Filter');
if (!isName(filter) || filter.name != 'Standard')
error('unknown encryption method');
this.dict = dict;
var algorithm = dict.get('V');
if (!isInt(algorithm) ||
(algorithm != 1 && algorithm != 2 && algorithm != 4))
error('unsupported encryption algorithm');
this.algorithm = algorithm;
var keyLength = dict.get('Length') || 40;
if (!isInt(keyLength) ||
keyLength < 40 || (keyLength % 8) !== 0)
error('invalid key length');
// prepare keys
var ownerPassword = stringToBytes(dict.get('O'));
var userPassword = stringToBytes(dict.get('U'));
var flags = dict.get('P');
var revision = dict.get('R');
var encryptMetadata = algorithm == 4 && // meaningful when V is 4
dict.get('EncryptMetadata') !== false; // makes true as default value
this.encryptMetadata = encryptMetadata;
var fileIdBytes = stringToBytes(fileId);
var passwordBytes;
if (password)
passwordBytes = stringToBytes(password);
var encryptionKey = prepareKeyData(fileIdBytes, passwordBytes,
ownerPassword, userPassword, flags,
revision, keyLength, encryptMetadata);
if (!encryptionKey && !password) {
throw new PasswordException('No password given',
PasswordResponses.NEED_PASSWORD);
} else if (!encryptionKey && password) {
// Attempting use the password as an owner password
var decodedPassword = decodeUserPassword(passwordBytes, ownerPassword,
revision, keyLength);
encryptionKey = prepareKeyData(fileIdBytes, decodedPassword,
ownerPassword, userPassword, flags,
revision, keyLength, encryptMetadata);
}
if (!encryptionKey)
throw new PasswordException('Incorrect Password',
PasswordResponses.INCORRECT_PASSWORD);
this.encryptionKey = encryptionKey;
if (algorithm == 4) {
this.cf = dict.get('CF');
this.stmf = dict.get('StmF') || identityName;
this.strf = dict.get('StrF') || identityName;
this.eff = dict.get('EFF') || this.strf;
}
}
function buildObjectKey(num, gen, encryptionKey, isAes) {
var key = new Uint8Array(encryptionKey.length + 9), i, n;
for (i = 0, n = encryptionKey.length; i < n; ++i)
key[i] = encryptionKey[i];
key[i++] = num & 0xFF;
key[i++] = (num >> 8) & 0xFF;
key[i++] = (num >> 16) & 0xFF;
key[i++] = gen & 0xFF;
key[i++] = (gen >> 8) & 0xFF;
if (isAes) {
key[i++] = 0x73;
key[i++] = 0x41;
key[i++] = 0x6C;
key[i++] = 0x54;
}
var hash = calculateMD5(key, 0, i);
return hash.subarray(0, Math.min(encryptionKey.length + 5, 16));
}
function buildCipherConstructor(cf, name, num, gen, key) {
var cryptFilter = cf.get(name.name);
var cfm;
if (cryptFilter !== null && cryptFilter !== undefined)
cfm = cryptFilter.get('CFM');
if (!cfm || cfm.name == 'None') {
return function cipherTransformFactoryBuildCipherConstructorNone() {
return new NullCipher();
};
}
if ('V2' == cfm.name) {
return function cipherTransformFactoryBuildCipherConstructorV2() {
return new ARCFourCipher(
buildObjectKey(num, gen, key, false));
};
}
if ('AESV2' == cfm.name) {
return function cipherTransformFactoryBuildCipherConstructorAESV2() {
return new AES128Cipher(
buildObjectKey(num, gen, key, true));
};
}
error('Unknown crypto method');
}
CipherTransformFactory.prototype = {
createCipherTransform:
function CipherTransformFactory_createCipherTransform(num, gen) {
if (this.algorithm == 4) {
return new CipherTransform(
buildCipherConstructor(this.cf, this.stmf,
num, gen, this.encryptionKey),
buildCipherConstructor(this.cf, this.strf,
num, gen, this.encryptionKey));
}
// algorithms 1 and 2
var key = buildObjectKey(num, gen, this.encryptionKey, false);
var cipherConstructor = function buildCipherCipherConstructor() {
return new ARCFourCipher(key);
};
return new CipherTransform(cipherConstructor, cipherConstructor);
}
};
return CipherTransformFactory;
})();

1540
src/core/evaluator.js Normal file

File diff suppressed because it is too large Load diff

6969
src/core/fonts.js Normal file

File diff suppressed because it is too large Load diff

4226
src/core/glyphlist.js Normal file

File diff suppressed because it is too large Load diff

498
src/core/image.js Normal file
View file

@ -0,0 +1,498 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
/* 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.
*/
/* globals ColorSpace, error, isArray, isStream, JpegStream, Name, Promise,
Stream, TODO, warn */
'use strict';
var PDFImage = (function PDFImageClosure() {
/**
* Decode the image in the main thread if it supported. Resovles the promise
* when the image data is ready.
*/
function handleImageData(handler, xref, res, image, promise) {
if (image instanceof JpegStream && image.isNativelyDecodable(xref, res)) {
// For natively supported jpegs send them to the main thread for decoding.
var dict = image.dict;
var colorSpace = dict.get('ColorSpace', 'CS');
colorSpace = ColorSpace.parse(colorSpace, xref, res);
var numComps = colorSpace.numComps;
handler.send('JpegDecode', [image.getIR(), numComps], function(message) {
var data = message.data;
var stream = new Stream(data, 0, data.length, image.dict);
promise.resolve(stream);
});
} else {
promise.resolve(image);
}
}
/**
* Decode and clamp a value. The formula is different from the spec because we
* don't decode to float range [0,1], we decode it in the [0,max] range.
*/
function decodeAndClamp(value, addend, coefficient, max) {
value = addend + value * coefficient;
// Clamp the value to the range
return value < 0 ? 0 : value > max ? max : value;
}
function PDFImage(xref, res, image, inline, smask, mask, isMask) {
this.image = image;
if (image.getParams) {
// JPX/JPEG2000 streams directly contain bits per component
// and color space mode information.
TODO('get params from actual stream');
// var bits = ...
// var colorspace = ...
}
// TODO cache rendered images?
var dict = image.dict;
this.width = dict.get('Width', 'W');
this.height = dict.get('Height', 'H');
if (this.width < 1 || this.height < 1)
error('Invalid image width: ' + this.width + ' or height: ' +
this.height);
this.interpolate = dict.get('Interpolate', 'I') || false;
this.imageMask = dict.get('ImageMask', 'IM') || false;
this.matte = dict.get('Matte') || false;
var bitsPerComponent = image.bitsPerComponent;
if (!bitsPerComponent) {
bitsPerComponent = dict.get('BitsPerComponent', 'BPC');
if (!bitsPerComponent) {
if (this.imageMask)
bitsPerComponent = 1;
else
error('Bits per component missing in image: ' + this.imageMask);
}
}
this.bpc = bitsPerComponent;
if (!this.imageMask) {
var colorSpace = dict.get('ColorSpace', 'CS');
if (!colorSpace) {
TODO('JPX images (which don"t require color spaces');
colorSpace = new Name('DeviceRGB');
}
this.colorSpace = ColorSpace.parse(colorSpace, xref, res);
this.numComps = this.colorSpace.numComps;
}
this.decode = dict.get('Decode', 'D');
this.needsDecode = false;
if (this.decode &&
((this.colorSpace && !this.colorSpace.isDefaultDecode(this.decode)) ||
(isMask && !ColorSpace.isDefaultDecode(this.decode, 1)))) {
this.needsDecode = true;
// Do some preprocessing to avoid more math.
var max = (1 << bitsPerComponent) - 1;
this.decodeCoefficients = [];
this.decodeAddends = [];
for (var i = 0, j = 0; i < this.decode.length; i += 2, ++j) {
var dmin = this.decode[i];
var dmax = this.decode[i + 1];
this.decodeCoefficients[j] = dmax - dmin;
this.decodeAddends[j] = max * dmin;
}
}
if (smask) {
this.smask = new PDFImage(xref, res, smask, false);
} else if (mask) {
if (isStream(mask)) {
this.mask = new PDFImage(xref, res, mask, false, null, null, true);
} else {
// Color key mask (just an array).
this.mask = mask;
}
}
}
/**
* Handles processing of image data and calls the callback with an argument
* of a PDFImage when the image is ready to be used.
*/
PDFImage.buildImage = function PDFImage_buildImage(callback, handler, xref,
res, image, inline) {
var imageDataPromise = new Promise();
var smaskPromise = new Promise();
var maskPromise = new Promise();
// The image data and smask data may not be ready yet, wait till both are
// resolved.
Promise.all([imageDataPromise, smaskPromise, maskPromise]).then(
function(results) {
var imageData = results[0], smaskData = results[1], maskData = results[2];
var image = new PDFImage(xref, res, imageData, inline, smaskData,
maskData);
callback(image);
});
handleImageData(handler, xref, res, image, imageDataPromise);
var smask = image.dict.get('SMask');
var mask = image.dict.get('Mask');
if (smask) {
handleImageData(handler, xref, res, smask, smaskPromise);
maskPromise.resolve(null);
} else {
smaskPromise.resolve(null);
if (mask) {
if (isStream(mask)) {
handleImageData(handler, xref, res, mask, maskPromise);
} else if (isArray(mask)) {
maskPromise.resolve(mask);
} else {
warn('Unsupported mask format.');
maskPromise.resolve(null);
}
} else {
maskPromise.resolve(null);
}
}
};
/**
* Resize an image using the nearest neighbor algorithm. Currently only
* supports one and three component images.
* @param {TypedArray} pixels The original image with one component.
* @param {Number} bpc Number of bits per component.
* @param {Number} components Number of color components, 1 or 3 is supported.
* @param {Number} w1 Original width.
* @param {Number} h1 Original height.
* @param {Number} w2 New width.
* @param {Number} h2 New height.
* @return {TypedArray} Resized image data.
*/
PDFImage.resize = function PDFImage_resize(pixels, bpc, components,
w1, h1, w2, h2) {
var length = w2 * h2 * components;
var temp = bpc <= 8 ? new Uint8Array(length) :
bpc <= 16 ? new Uint16Array(length) : new Uint32Array(length);
var xRatio = w1 / w2;
var yRatio = h1 / h2;
var px, py, newIndex, oldIndex;
for (var i = 0; i < h2; i++) {
for (var j = 0; j < w2; j++) {
px = Math.floor(j * xRatio);
py = Math.floor(i * yRatio);
newIndex = (i * w2) + j;
oldIndex = ((py * w1) + px);
if (components === 1) {
temp[newIndex] = pixels[oldIndex];
} else if (components === 3) {
newIndex *= 3;
oldIndex *= 3;
temp[newIndex] = pixels[oldIndex];
temp[newIndex + 1] = pixels[oldIndex + 1];
temp[newIndex + 2] = pixels[oldIndex + 2];
}
}
}
return temp;
};
PDFImage.createMask = function PDFImage_createMask(imgArray, width, height,
inverseDecode) {
var buffer = new Uint8Array(width * height * 4);
var imgArrayPos = 0;
var i, j, mask, buf;
// removing making non-masked pixels transparent
var bufferPos = 3; // alpha component offset
for (i = 0; i < height; i++) {
mask = 0;
for (j = 0; j < width; j++) {
if (!mask) {
buf = imgArray[imgArrayPos++];
mask = 128;
}
if (!(buf & mask) !== inverseDecode) {
buffer[bufferPos] = 255;
}
bufferPos += 4;
mask >>= 1;
}
}
return {data: buffer, width: width, height: height};
};
PDFImage.prototype = {
get drawWidth() {
if (!this.smask)
return this.width;
return Math.max(this.width, this.smask.width);
},
get drawHeight() {
if (!this.smask)
return this.height;
return Math.max(this.height, this.smask.height);
},
decodeBuffer: function PDFImage_decodeBuffer(buffer) {
var bpc = this.bpc;
var decodeMap = this.decode;
var numComps = this.numComps;
var decodeAddends, decodeCoefficients;
var decodeAddends = this.decodeAddends;
var decodeCoefficients = this.decodeCoefficients;
var max = (1 << bpc) - 1;
if (bpc === 1) {
// If the buffer needed decode that means it just needs to be inverted.
for (var i = 0, ii = buffer.length; i < ii; i++) {
buffer[i] = +!(buffer[i]);
}
return;
}
var index = 0;
for (var i = 0, ii = this.width * this.height; i < ii; i++) {
for (var j = 0; j < numComps; j++) {
buffer[index] = decodeAndClamp(buffer[index], decodeAddends[j],
decodeCoefficients[j], max);
index++;
}
}
},
getComponents: function PDFImage_getComponents(buffer) {
var bpc = this.bpc;
// This image doesn't require any extra work.
if (bpc === 8)
return buffer;
var bufferLength = buffer.length;
var width = this.width;
var height = this.height;
var numComps = this.numComps;
var length = width * height * numComps;
var bufferPos = 0;
var output = bpc <= 8 ? new Uint8Array(length) :
bpc <= 16 ? new Uint16Array(length) : new Uint32Array(length);
var rowComps = width * numComps;
var max = (1 << bpc) - 1;
if (bpc === 1) {
// Optimization for reading 1 bpc images.
var mask = 0;
var buf = 0;
for (var i = 0, ii = length; i < ii; ++i) {
if (i % rowComps === 0) {
mask = 0;
buf = 0;
} else {
mask >>= 1;
}
if (mask <= 0) {
buf = buffer[bufferPos++];
mask = 128;
}
output[i] = +!!(buf & mask);
}
} else {
// The general case that handles all other bpc values.
var bits = 0, buf = 0;
for (var i = 0, ii = length; i < ii; ++i) {
if (i % rowComps === 0) {
buf = 0;
bits = 0;
}
while (bits < bpc) {
buf = (buf << 8) | buffer[bufferPos++];
bits += 8;
}
var remainingBits = bits - bpc;
var value = buf >> remainingBits;
output[i] = value < 0 ? 0 : value > max ? max : value;
buf = buf & ((1 << remainingBits) - 1);
bits = remainingBits;
}
}
return output;
},
getOpacity: function PDFImage_getOpacity(width, height, image) {
var smask = this.smask;
var mask = this.mask;
var originalWidth = this.width;
var originalHeight = this.height;
var buf;
if (smask) {
var sw = smask.width;
var sh = smask.height;
buf = new Uint8Array(sw * sh);
smask.fillGrayBuffer(buf);
if (sw != width || sh != height)
buf = PDFImage.resize(buf, smask.bpc, 1, sw, sh, width, height);
} else if (mask) {
if (mask instanceof PDFImage) {
var sw = mask.width;
var sh = mask.height;
buf = new Uint8Array(sw * sh);
mask.numComps = 1;
mask.fillGrayBuffer(buf);
// Need to invert values in buffer
for (var i = 0, ii = sw * sh; i < ii; ++i)
buf[i] = 255 - buf[i];
if (sw != width || sh != height)
buf = PDFImage.resize(buf, mask.bpc, 1, sw, sh, width, height);
} else if (isArray(mask)) {
// Color key mask: if any of the compontents are outside the range
// then they should be painted.
buf = new Uint8Array(width * height);
var numComps = this.numComps;
for (var i = 0, ii = width * height; i < ii; ++i) {
var opacity = 0;
var imageOffset = i * numComps;
for (var j = 0; j < numComps; ++j) {
var color = image[imageOffset + j];
var maskOffset = j * 2;
if (color < mask[maskOffset] || color > mask[maskOffset + 1]) {
opacity = 255;
break;
}
}
buf[i] = opacity;
}
} else {
error('Unknown mask format.');
}
} else {
buf = new Uint8Array(width * height);
for (var i = 0, ii = width * height; i < ii; ++i)
buf[i] = 255;
}
return buf;
},
undoPreblend: function PDFImage_undoPreblend(buffer, width, height) {
var matte = this.smask && this.smask.matte;
if (!matte) {
return;
}
function clamp(value) {
return (value < 0 ? 0 : value > 255 ? 255 : value) | 0;
}
var matteRgb = this.colorSpace.getRgb(matte, 0);
var length = width * height * 4;
for (var i = 0; i < length; i += 4) {
var alpha = buffer[i + 3];
if (alpha === 0) {
// according formula we have to get Infinity in all components
// making it as white (tipical paper color) should be okay
buffer[i] = 255;
buffer[i + 1] = 255;
buffer[i + 2] = 255;
continue;
}
var k = 255 / alpha;
buffer[i] = clamp((buffer[i] - matteRgb[0]) * k + matteRgb[0]);
buffer[i + 1] = clamp((buffer[i + 1] - matteRgb[1]) * k + matteRgb[1]);
buffer[i + 2] = clamp((buffer[i + 2] - matteRgb[2]) * k + matteRgb[2]);
}
},
fillRgbaBuffer: function PDFImage_fillRgbaBuffer(buffer, width, height) {
var numComps = this.numComps;
var originalWidth = this.width;
var originalHeight = this.height;
var bpc = this.bpc;
// rows start at byte boundary;
var rowBytes = (originalWidth * numComps * bpc + 7) >> 3;
var imgArray = this.getImageBytes(originalHeight * rowBytes);
// imgArray can be incomplete (e.g. after CCITT fax encoding)
var actualHeight = 0 | (imgArray.length / rowBytes *
height / originalHeight);
var comps = this.getComponents(imgArray);
// Build opacity here since color key masking needs to be perormed on
// undecoded values.
var opacity = this.getOpacity(width, height, comps);
if (this.needsDecode) {
this.decodeBuffer(comps);
}
var rgbBuf = this.colorSpace.createRgbBuffer(comps, 0,
originalWidth * originalHeight, bpc);
if (originalWidth != width || originalHeight != height)
rgbBuf = PDFImage.resize(rgbBuf, this.bpc, 3, originalWidth,
originalHeight, width, height);
var compsPos = 0;
var opacityPos = 0;
var length = width * actualHeight * 4;
for (var i = 0; i < length; i += 4) {
buffer[i] = rgbBuf[compsPos++];
buffer[i + 1] = rgbBuf[compsPos++];
buffer[i + 2] = rgbBuf[compsPos++];
buffer[i + 3] = opacity[opacityPos++];
}
this.undoPreblend(buffer, width, actualHeight);
},
fillGrayBuffer: function PDFImage_fillGrayBuffer(buffer) {
var numComps = this.numComps;
if (numComps != 1)
error('Reading gray scale from a color image: ' + numComps);
var width = this.width;
var height = this.height;
var bpc = this.bpc;
// rows start at byte boundary;
var rowBytes = (width * numComps * bpc + 7) >> 3;
var imgArray = this.getImageBytes(height * rowBytes);
var comps = this.getComponents(imgArray);
if (this.needsDecode) {
this.decodeBuffer(comps);
}
var length = width * height;
// we aren't using a colorspace so we need to scale the value
var scale = 255 / ((1 << bpc) - 1);
for (var i = 0; i < length; ++i)
buffer[i] = (scale * comps[i]) | 0;
},
getImageData: function PDFImage_getImageData() {
var drawWidth = this.drawWidth;
var drawHeight = this.drawHeight;
var imgData = {
width: drawWidth,
height: drawHeight,
data: new Uint8Array(drawWidth * drawHeight * 4)
};
var pixels = imgData.data;
this.fillRgbaBuffer(pixels, drawWidth, drawHeight);
return imgData;
},
getImageBytes: function PDFImage_getImageBytes(length) {
this.image.reset();
return this.image.getBytes(length);
}
};
return PDFImage;
})();

1086
src/core/jbig2.js Executable file

File diff suppressed because it is too large Load diff

1890
src/core/jpx.js Normal file

File diff suppressed because it is too large Load diff

2961
src/core/metrics.js Normal file

File diff suppressed because it is too large Load diff

237
src/core/network.js Normal file
View file

@ -0,0 +1,237 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
/* 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.
*/
// NOTE: Be careful what goes in this file, as it is also used from the context
// of the addon. So using warn/error in here will break the addon.
'use strict';
//#if (FIREFOX || MOZCENTRAL)
//
//Components.utils.import('resource://gre/modules/Services.jsm');
//
//var EXPORTED_SYMBOLS = ['NetworkManager'];
//
//function log(aMsg) {
// var msg = 'network.js: ' + (aMsg.join ? aMsg.join('') : aMsg);
// Services.console.logStringMessage(msg);
// // TODO(mack): dump() doesn't seem to work here...
// dump(msg + '\n');
//}
//#endif
var NetworkManager = (function NetworkManagerClosure() {
var OK_RESPONSE = 200;
var PARTIAL_CONTENT_RESPONSE = 206;
function NetworkManager(url, args) {
this.url = url;
args = args || {};
this.httpHeaders = args.httpHeaders || {};
this.getXhr = args.getXhr ||
function NetworkManager_getXhr() {
//#if B2G
// return new XMLHttpRequest({ mozSystem: true });
//#else
return new XMLHttpRequest();
//#endif
};
this.currXhrId = 0;
this.pendingRequests = {};
this.loadedRequests = {};
}
function getArrayBuffer(xhr) {
var data = (xhr.mozResponseArrayBuffer || xhr.mozResponse ||
xhr.responseArrayBuffer || xhr.response);
if (typeof data !== 'string') {
return data;
}
var length = data.length;
var buffer = new Uint8Array(length);
for (var i = 0; i < length; i++) {
buffer[i] = data.charCodeAt(i) & 0xFF;
}
return buffer;
}
NetworkManager.prototype = {
requestRange: function NetworkManager_requestRange(begin, end, listeners) {
var args = {
begin: begin,
end: end
};
for (var prop in listeners) {
args[prop] = listeners[prop];
}
return this.request(args);
},
requestFull: function NetworkManager_requestRange(listeners) {
return this.request(listeners);
},
request: function NetworkManager_requestRange(args) {
var xhr = this.getXhr();
var xhrId = this.currXhrId++;
var pendingRequest = this.pendingRequests[xhrId] = {
xhr: xhr
};
xhr.open('GET', this.url);
for (var property in this.httpHeaders) {
var value = this.httpHeaders[property];
if (typeof value === 'undefined') {
continue;
}
xhr.setRequestHeader(property, value);
}
if ('begin' in args && 'end' in args) {
var rangeStr = args.begin + '-' + (args.end - 1);
xhr.setRequestHeader('Range', 'bytes=' + rangeStr);
pendingRequest.expectedStatus = 206;
} else {
pendingRequest.expectedStatus = 200;
}
xhr.mozResponseType = xhr.responseType = 'arraybuffer';
if (args.onProgress) {
xhr.onprogress = args.onProgress;
}
if (args.onError) {
xhr.onerror = function(evt) {
args.onError(xhr.status);
};
}
xhr.onreadystatechange = this.onStateChange.bind(this, xhrId);
pendingRequest.onHeadersReceived = args.onHeadersReceived;
pendingRequest.onDone = args.onDone;
pendingRequest.onError = args.onError;
xhr.send(null);
return xhrId;
},
onStateChange: function NetworkManager_onStateChange(xhrId, evt) {
var pendingRequest = this.pendingRequests[xhrId];
if (!pendingRequest) {
// Maybe abortRequest was called...
return;
}
var xhr = pendingRequest.xhr;
if (xhr.readyState >= 2 && pendingRequest.onHeadersReceived) {
pendingRequest.onHeadersReceived();
delete pendingRequest.onHeadersReceived;
}
if (xhr.readyState !== 4) {
return;
}
if (!(xhrId in this.pendingRequests)) {
// The XHR request might have been aborted in onHeadersReceived()
// callback, in which case we should abort request
return;
}
delete this.pendingRequests[xhrId];
// success status == 0 can be on ftp, file and other protocols
if (xhr.status === 0 && /^https?:/i.test(this.url)) {
if (pendingRequest.onError) {
pendingRequest.onError(xhr.status);
}
return;
}
var xhrStatus = xhr.status || OK_RESPONSE;
// From http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35.2:
// "A server MAY ignore the Range header". This means it's possible to
// get a 200 rather than a 206 response from a range request.
var ok_response_on_range_request =
xhrStatus === OK_RESPONSE &&
pendingRequest.expectedStatus === PARTIAL_CONTENT_RESPONSE;
if (!ok_response_on_range_request &&
xhrStatus !== pendingRequest.expectedStatus) {
if (pendingRequest.onError) {
pendingRequest.onError(xhr.status);
}
return;
}
this.loadedRequests[xhrId] = true;
var chunk = getArrayBuffer(xhr);
if (xhrStatus === PARTIAL_CONTENT_RESPONSE) {
var rangeHeader = xhr.getResponseHeader('Content-Range');
var matches = /bytes (\d+)-(\d+)\/(\d+)/.exec(rangeHeader);
var begin = parseInt(matches[1], 10);
pendingRequest.onDone({
begin: begin,
chunk: chunk
});
} else {
pendingRequest.onDone({
begin: 0,
chunk: chunk
});
}
},
hasPendingRequests: function NetworkManager_hasPendingRequests() {
for (var xhrId in this.pendingRequests) {
return true;
}
return false;
},
getRequestXhr: function NetworkManager_getXhr(xhrId) {
return this.pendingRequests[xhrId].xhr;
},
isPendingRequest: function NetworkManager_isPendingRequest(xhrId) {
return xhrId in this.pendingRequests;
},
isLoadedRequest: function NetworkManager_isLoadedRequest(xhrId) {
return xhrId in this.loadedRequests;
},
abortAllRequests: function NetworkManager_abortAllRequests() {
for (var xhrId in this.pendingRequests) {
this.abortRequest(xhrId | 0);
}
},
abortRequest: function NetworkManager_abortRequest(xhrId) {
var xhr = this.pendingRequests[xhrId].xhr;
delete this.pendingRequests[xhrId];
xhr.abort();
}
};
return NetworkManager;
})();

1236
src/core/obj.js Normal file

File diff suppressed because it is too large Load diff

764
src/core/parser.js Normal file
View file

@ -0,0 +1,764 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
/* 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.
*/
/* globals Ascii85Stream, AsciiHexStream, CCITTFaxStream, Cmd, Dict, error,
FlateStream, isArray, isCmd, isDict, isInt, isName, isNum, isRef,
isString, Jbig2Stream, JpegStream, JpxStream, LZWStream, Name,
NullStream, PredictorStream, Ref, RunLengthStream, warn, info */
'use strict';
var EOF = {};
function isEOF(v) {
return v == EOF;
}
var Parser = (function ParserClosure() {
function Parser(lexer, allowStreams, xref) {
this.lexer = lexer;
this.allowStreams = allowStreams;
this.xref = xref;
this.refill();
}
Parser.prototype = {
saveState: function Parser_saveState() {
this.state = {
buf1: this.buf1,
buf2: this.buf2,
streamPos: this.lexer.stream.pos
};
},
restoreState: function Parser_restoreState() {
var state = this.state;
this.buf1 = state.buf1;
this.buf2 = state.buf2;
this.lexer.stream.pos = state.streamPos;
},
refill: function Parser_refill() {
this.buf1 = this.lexer.getObj();
this.buf2 = this.lexer.getObj();
},
shift: function Parser_shift() {
if (isCmd(this.buf2, 'ID')) {
this.buf1 = this.buf2;
this.buf2 = null;
} else {
this.buf1 = this.buf2;
this.buf2 = this.lexer.getObj();
}
},
getObj: function Parser_getObj(cipherTransform) {
if (isCmd(this.buf1, 'BI')) { // inline image
this.shift();
return this.makeInlineImage(cipherTransform);
}
if (isCmd(this.buf1, '[')) { // array
this.shift();
var array = [];
while (!isCmd(this.buf1, ']') && !isEOF(this.buf1))
array.push(this.getObj(cipherTransform));
if (isEOF(this.buf1))
error('End of file inside array');
this.shift();
return array;
}
if (isCmd(this.buf1, '<<')) { // dictionary or stream
this.shift();
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;
}
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');
// 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;
}
if (isInt(this.buf1)) { // indirect reference or integer
var num = this.buf1;
this.shift();
if (isInt(this.buf1) && isCmd(this.buf2, 'R')) {
var ref = new Ref(num, this.buf1);
this.shift();
this.shift();
return ref;
}
return num;
}
if (isString(this.buf1)) { // string
var str = this.buf1;
this.shift();
if (cipherTransform)
str = cipherTransform.decryptString(str);
return str;
}
// simple object
var obj = this.buf1;
this.shift();
return obj;
},
makeInlineImage: function Parser_makeInlineImage(cipherTransform) {
var lexer = this.lexer;
var stream = lexer.stream;
// parse dictionary
var dict = new Dict();
while (!isCmd(this.buf1, 'ID') && !isEOF(this.buf1)) {
if (!isName(this.buf1))
error('Dictionary key must be a name object');
var key = this.buf1.name;
this.shift();
if (isEOF(this.buf1))
break;
dict.set(key, this.getObj(cipherTransform));
}
// parse image stream
var startPos = stream.pos;
// searching for the /EI\s/
var state = 0, ch, i, ii;
while (state != 4 && (ch = stream.getByte()) !== -1) {
switch (ch | 0) {
case 0x20:
case 0x0D:
case 0x0A:
// 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];
if (ch !== 0x0A && ch !== 0x0D && (ch < 0x20 || ch > 0x7F)) {
// not a LF, CR, SPACE or any visible ASCII character
state = 0;
break; // some binary stuff found, resetting the state
}
}
state = state === 3 ? 4 : 0;
break;
case 0x45:
state = 2;
break;
case 0x49:
state = state === 2 ? 3 : 0;
break;
default:
state = 0;
break;
}
}
var length = (stream.pos - 4) - startPos;
var imageStream = stream.makeSubStream(startPos, length, dict);
if (cipherTransform)
imageStream = cipherTransform.createStream(imageStream);
imageStream = this.filter(imageStream, dict, length);
imageStream.dict = dict;
this.buf2 = Cmd.get('EI');
this.shift();
return imageStream;
},
fetchIfRef: function Parser_fetchIfRef(obj) {
// not relying on the xref.fetchIfRef -- xref might not be set
return isRef(obj) ? this.xref.fetch(obj) : obj;
},
makeStream: function Parser_makeStream(dict, cipherTransform) {
var lexer = this.lexer;
var stream = lexer.stream;
// get stream start position
lexer.skipToNextLine();
var pos = stream.pos - 1;
// get length
var length = this.fetchIfRef(dict.get('Length'));
if (!isInt(length))
error('Bad ' + length + ' attribute in stream');
// skip over the stream data
stream.pos = pos + length;
lexer.nextChar();
this.shift(); // '>>'
this.shift(); // 'stream'
if (!isCmd(this.buf1, 'endstream')) {
// 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];
var skipped = 0, found = false;
while (stream.pos < stream.end) {
var scanBytes = stream.peekBytes(SCAN_BLOCK_SIZE);
var scanLength = scanBytes.length - ENDSTREAM_SIGNATURE_LENGTH;
var found = false, i, ii, j;
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) {
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;
lexer.nextChar();
this.shift();
this.shift();
}
this.shift(); // 'endstream'
stream = stream.makeSubStream(pos, length, dict);
if (cipherTransform)
stream = cipherTransform.createStream(stream);
stream = this.filter(stream, dict, length);
stream.dict = dict;
return stream;
},
filter: function Parser_filter(stream, dict, length) {
var filter = this.fetchIfRef(dict.get('Filter', 'F'));
var params = this.fetchIfRef(dict.get('DecodeParms', 'DP'));
if (isName(filter))
return this.makeFilter(stream, filter.name, length, params);
if (isArray(filter)) {
var filterArray = filter;
var paramsArray = params;
for (var i = 0, ii = filterArray.length; i < ii; ++i) {
filter = filterArray[i];
if (!isName(filter))
error('Bad filter name: ' + filter);
params = null;
if (isArray(paramsArray) && (i in paramsArray))
params = paramsArray[i];
stream = this.makeFilter(stream, filter.name, length, params);
// after the first stream the length variable is invalid
length = null;
}
}
return stream;
},
makeFilter: function Parser_makeFilter(stream, name, length, params) {
if (stream.dict.get('Length') === 0) {
return new NullStream(stream);
}
if (name == 'FlateDecode' || name == 'Fl') {
if (params) {
return new PredictorStream(new FlateStream(stream), params);
}
return new FlateStream(stream);
}
if (name == 'LZWDecode' || name == 'LZW') {
var earlyChange = 1;
if (params) {
if (params.has('EarlyChange'))
earlyChange = params.get('EarlyChange');
return new PredictorStream(
new LZWStream(stream, earlyChange), params);
}
return new LZWStream(stream, earlyChange);
}
if (name == 'DCTDecode' || name == 'DCT') {
var bytes = stream.getBytes(length);
return new JpegStream(bytes, stream.dict, this.xref);
}
if (name == 'JPXDecode' || name == 'JPX') {
var bytes = stream.getBytes(length);
return new JpxStream(bytes, stream.dict);
}
if (name == 'ASCII85Decode' || name == 'A85') {
return new Ascii85Stream(stream);
}
if (name == 'ASCIIHexDecode' || name == 'AHx') {
return new AsciiHexStream(stream);
}
if (name == 'CCITTFaxDecode' || name == 'CCF') {
return new CCITTFaxStream(stream, params);
}
if (name == 'RunLengthDecode' || name == 'RL') {
return new RunLengthStream(stream);
}
if (name == 'JBIG2Decode') {
var bytes = stream.getBytes(length);
return new Jbig2Stream(bytes, stream.dict);
}
warn('filter "' + name + '" not supported yet');
return stream;
}
};
return Parser;
})();
var Lexer = (function LexerClosure() {
function Lexer(stream, knownCommands) {
this.stream = stream;
this.nextChar();
// 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.
this.knownCommands = knownCommands;
}
Lexer.isSpace = function Lexer_isSpace(ch) {
// space is one of the following characters: SPACE, TAB, CR, or LF
return ch === 0x20 || ch === 0x09 || ch === 0x0D || ch === 0x0A;
};
// A '1' in this array means the character is white space. A '1' or
// '2' means the character ends a name or command.
var specialChars = [
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
];
function toHexDigit(ch) {
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;
}
return -1;
}
Lexer.prototype = {
nextChar: function Lexer_nextChar() {
return (this.currentChar = this.stream.getByte());
},
getNumber: function Lexer_getNumber() {
var floating = false;
var ch = this.currentChar;
var str = String.fromCharCode(ch);
while ((ch = this.nextChar()) >= 0) {
if (ch === 0x2E && !floating) { // '.'
str += '.';
floating = true;
} else if (ch === 0x2D) { // '-'
// ignore minus signs in the middle of numbers to match
// Adobe's behavior
warn('Badly formated number');
} else if (ch >= 0x30 && ch <= 0x39) { // '0'-'9'
str += String.fromCharCode(ch);
} else if (ch === 0x45 || ch === 0x65) { // 'E', 'e'
floating = true;
} else {
// the last character doesn't belong to us
break;
}
}
var value = parseFloat(str);
if (isNaN(value))
error('Invalid floating point number: ' + value);
return value;
},
getString: function Lexer_getString() {
var numParen = 1;
var done = false;
var str = '';
var ch = this.nextChar();
while (true) {
var charBuffered = false;
switch (ch | 0) {
case -1:
warn('Unterminated string');
done = true;
break;
case 0x28: // '('
++numParen;
str += '(';
break;
case 0x29: // ')'
if (--numParen === 0) {
this.nextChar(); // consume strings ')'
done = true;
} else {
str += ')';
}
break;
case 0x5C: // '\\'
ch = this.nextChar();
switch (ch) {
case -1:
warn('Unterminated string');
done = true;
break;
case 0x6E: // 'n'
str += '\n';
break;
case 0x72: // 'r'
str += '\r';
break;
case 0x74: // 't'
str += '\t';
break;
case 0x62: // 'b'
str += '\b';
break;
case 0x66: // 'f'
str += '\f';
break;
case 0x5C: // '\'
case 0x28: // '('
case 0x29: // ')'
str += String.fromCharCode(ch);
break;
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);
}
}
str += String.fromCharCode(x);
break;
case 0x0A: case 0x0D: // LF, CR
break;
default:
str += String.fromCharCode(ch);
break;
}
break;
default:
str += String.fromCharCode(ch);
break;
}
if (done) {
break;
}
if (!charBuffered) {
ch = this.nextChar();
}
}
return str;
},
getName: function Lexer_getName() {
var str = '', ch;
while ((ch = this.nextChar()) >= 0 && !specialChars[ch]) {
if (ch === 0x23) { // '#'
ch = this.nextChar();
var x = toHexDigit(ch);
if (x != -1) {
var x2 = toHexDigit(this.nextChar());
if (x2 == -1)
error('Illegal digit in hex char in name: ' + x2);
str += String.fromCharCode((x << 4) | x2);
} else {
str += '#';
str += String.fromCharCode(ch);
}
} else {
str += String.fromCharCode(ch);
}
}
if (str.length > 128) {
error('Warning: name token is longer than allowed by the spec: ' +
str.length);
}
return new Name(str);
},
getHexString: function Lexer_getHexString() {
var str = '';
var ch = this.currentChar;
var isFirstHex = true;
var firstDigit;
var secondDigit;
while (true) {
if (ch < 0) {
warn('Unterminated hex string');
break;
} else if (ch === 0x3E) { // '>'
this.nextChar();
break;
} else if (specialChars[ch] === 1) {
ch = this.nextChar();
continue;
} else {
if (isFirstHex) {
firstDigit = toHexDigit(ch);
if (firstDigit === -1) {
warn('Ignoring invalid character "' + ch + '" in hex string');
ch = this.nextChar();
continue;
}
} else {
secondDigit = toHexDigit(ch);
if (secondDigit === -1) {
warn('Ignoring invalid character "' + ch + '" in hex string');
ch = this.nextChar();
continue;
}
str += String.fromCharCode((firstDigit << 4) | secondDigit);
}
isFirstHex = !isFirstHex;
ch = this.nextChar();
}
}
return str;
},
getObj: function Lexer_getObj() {
// skip whitespace and comments
var comment = false;
var ch = this.currentChar;
while (true) {
if (ch < 0) {
return EOF;
}
if (comment) {
if (ch === 0x0A || ch == 0x0D) // LF, CR
comment = false;
} else if (ch === 0x25) { // '%'
comment = true;
} else if (specialChars[ch] !== 1) {
break;
}
ch = this.nextChar();
}
// start reading token
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: // '('
return this.getString();
case 0x2F: // '/'
return this.getName();
// array punctuation
case 0x5B: // '['
this.nextChar();
return Cmd.get('[');
case 0x5D: // ']'
this.nextChar();
return Cmd.get(']');
// hex string or dict punctuation
case 0x3C: // '<'
ch = this.nextChar();
if (ch === 0x3C) {
// dict punctuation
this.nextChar();
return Cmd.get('<<');
}
return this.getHexString();
// dict punctuation
case 0x3E: // '>'
ch = this.nextChar();
if (ch === 0x3E) {
this.nextChar();
return Cmd.get('>>');
}
return Cmd.get('>');
case 0x7B: // '{'
this.nextChar();
return Cmd.get('{');
case 0x7D: // '}'
this.nextChar();
return Cmd.get('}');
case 0x29: // ')'
error('Illegal character: ' + ch);
break;
}
// command
var str = String.fromCharCode(ch);
var knownCommands = this.knownCommands;
var knownCommandFound = knownCommands && (str in knownCommands);
while ((ch = this.nextChar()) >= 0 && !specialChars[ch]) {
// stop if known command is found and next character does not make
// the str a command
var possibleCommand = str + String.fromCharCode(ch);
if (knownCommandFound && !(possibleCommand in knownCommands)) {
break;
}
if (str.length == 128)
error('Command token too long: ' + str.length);
str = possibleCommand;
knownCommandFound = knownCommands && (str in knownCommands);
}
if (str == 'true')
return true;
if (str == 'false')
return false;
if (str == 'null')
return null;
return Cmd.get(str);
},
skipToNextLine: function Lexer_skipToNextLine() {
var stream = this.stream;
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;
}
ch = this.nextChar();
}
}
};
return Lexer;
})();
var Linearization = (function LinearizationClosure() {
function Linearization(stream) {
this.parser = new Parser(new Lexer(stream), false, null);
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');
if (!(isNum(obj) && obj > 0))
this.linDict = null;
}
}
Linearization.prototype = {
getInt: function Linearization_getInt(name) {
var linDict = this.linDict;
var obj;
if (isDict(linDict) &&
isInt(obj = linDict.get(name)) &&
obj > 0) {
return obj;
}
error('"' + name + '" field in linearization table is invalid');
},
getHint: function Linearization_getHint(index) {
var linDict = this.linDict;
var obj1, obj2;
if (isDict(linDict) &&
isArray(obj1 = linDict.get('H')) &&
obj1.length >= 2 &&
isInt(obj2 = obj1[index]) &&
obj2 > 0) {
return obj2;
}
error('Hints table in linearization table is invalid: ' + index);
},
get length() {
if (!isDict(this.linDict))
return 0;
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');
}
};
return Linearization;
})();

197
src/core/pdf_manager.js Normal file
View file

@ -0,0 +1,197 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
/* 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.
*/
/* globals NotImplementedException, MissingDataException, Promise, Stream,
PDFDocument, ChunkedStreamManager */
'use strict';
// TODO(mack): Make use of PDFJS.Util.inherit() when it becomes available
var BasePdfManager = (function BasePdfManagerClosure() {
function BasePdfManager() {
throw new Error('Cannot initialize BaseManagerManager');
}
BasePdfManager.prototype = {
onLoadedStream: function BasePdfManager_onLoadedStream() {
throw new NotImplementedException();
},
ensureModel: function BasePdfManager_ensureModel(prop, args) {
return this.ensure(this.pdfModel, prop, args);
},
ensureXRef: function BasePdfManager_ensureXRef(prop, args) {
return this.ensure(this.pdfModel.xref, prop, args);
},
ensureCatalog: function BasePdfManager_ensureCatalog(prop, args) {
return this.ensure(this.pdfModel.catalog, prop, args);
},
getPage: function BasePdfManager_pagePage(pageIndex) {
return this.pdfModel.getPage(pageIndex);
},
ensure: function BasePdfManager_ensure(obj, prop, args) {
return new NotImplementedException();
},
requestRange: function BasePdfManager_ensure(begin, end) {
return new NotImplementedException();
},
requestLoadedStream: function BasePdfManager_requestLoadedStream() {
return new NotImplementedException();
},
updatePassword: function BasePdfManager_updatePassword(password) {
this.pdfModel.xref.password = this.password = password;
if (this.passwordChangedPromise) {
this.passwordChangedPromise.resolve();
}
}
};
return BasePdfManager;
})();
var LocalPdfManager = (function LocalPdfManagerClosure() {
function LocalPdfManager(data, password) {
var stream = new Stream(data);
this.pdfModel = new PDFDocument(this, stream, password);
this.loadedStream = new Promise();
this.loadedStream.resolve(stream);
}
LocalPdfManager.prototype = Object.create(BasePdfManager.prototype);
LocalPdfManager.prototype.constructor = LocalPdfManager;
LocalPdfManager.prototype.ensure =
function LocalPdfManager_ensure(obj, prop, args) {
var promise = new Promise();
try {
var value = obj[prop];
var result;
if (typeof(value) === 'function') {
result = value.apply(obj, args);
} else {
result = value;
}
promise.resolve(result);
} catch (e) {
console.log(e.stack);
promise.reject(e);
}
return promise;
};
LocalPdfManager.prototype.requestRange =
function LocalPdfManager_requestRange(begin, end) {
var promise = new Promise();
promise.resolve();
return promise;
};
LocalPdfManager.prototype.requestLoadedStream =
function LocalPdfManager_requestLoadedStream() {
};
LocalPdfManager.prototype.onLoadedStream =
function LocalPdfManager_getLoadedStream() {
return this.loadedStream;
};
return LocalPdfManager;
})();
var NetworkPdfManager = (function NetworkPdfManagerClosure() {
var CHUNK_SIZE = 65536;
function NetworkPdfManager(args, msgHandler) {
this.msgHandler = msgHandler;
var params = {
msgHandler: msgHandler,
httpHeaders: args.httpHeaders,
chunkedViewerLoading: args.chunkedViewerLoading,
disableAutoFetch: args.disableAutoFetch
};
this.streamManager = new ChunkedStreamManager(args.length, CHUNK_SIZE,
args.url, params);
this.pdfModel = new PDFDocument(this, this.streamManager.getStream(),
args.password);
}
NetworkPdfManager.prototype = Object.create(BasePdfManager.prototype);
NetworkPdfManager.prototype.constructor = NetworkPdfManager;
NetworkPdfManager.prototype.ensure =
function NetworkPdfManager_ensure(obj, prop, args) {
var promise = new Promise();
this.ensureHelper(promise, obj, prop, args);
return promise;
};
NetworkPdfManager.prototype.ensureHelper =
function NetworkPdfManager_ensureHelper(promise, obj, prop, args) {
try {
var result;
var value = obj[prop];
if (typeof(value) === 'function') {
result = value.apply(obj, args);
} else {
result = value;
}
promise.resolve(result);
} catch(e) {
if (!(e instanceof MissingDataException)) {
console.log(e.stack);
promise.reject(e);
return;
}
this.streamManager.requestRange(e.begin, e.end, function() {
this.ensureHelper(promise, obj, prop, args);
}.bind(this));
}
};
NetworkPdfManager.prototype.requestRange =
function NetworkPdfManager_requestRange(begin, end) {
var promise = new Promise();
this.streamManager.requestRange(begin, end, function() {
promise.resolve();
});
return promise;
};
NetworkPdfManager.prototype.requestLoadedStream =
function NetworkPdfManager_requestLoadedStream() {
this.streamManager.requestAllChunks();
};
NetworkPdfManager.prototype.onLoadedStream =
function NetworkPdfManager_getLoadedStream() {
return this.streamManager.onLoadedStream();
};
return NetworkPdfManager;
})();

2323
src/core/stream.js Normal file

File diff suppressed because it is too large Load diff

426
src/core/worker.js Normal file
View file

@ -0,0 +1,426 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
/* 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.
*/
/* globals error, globalScope, InvalidPDFException, log,
MissingPDFException, PasswordException, PDFJS, Promise,
UnknownErrorException, NetworkManager, LocalPdfManager,
NetworkPdfManager, XRefParseException,
isInt, PasswordResponses, MessageHandler */
'use strict';
var WorkerMessageHandler = PDFJS.WorkerMessageHandler = {
setup: function wphSetup(handler) {
var pdfManager;
function loadDocument(recoveryMode) {
var loadDocumentPromise = new Promise();
var parseSuccess = function parseSuccess() {
var numPagesPromise = pdfManager.ensureModel('numPages');
var fingerprintPromise = pdfManager.ensureModel('fingerprint');
var outlinePromise = pdfManager.ensureCatalog('documentOutline');
var infoPromise = pdfManager.ensureModel('documentInfo');
var metadataPromise = pdfManager.ensureCatalog('metadata');
var encryptedPromise = pdfManager.ensureXRef('encrypt');
var javaScriptPromise = pdfManager.ensureCatalog('javaScript');
Promise.all([numPagesPromise, fingerprintPromise, outlinePromise,
infoPromise, metadataPromise, encryptedPromise,
javaScriptPromise]).then(
function onDocReady(results) {
var doc = {
numPages: results[0],
fingerprint: results[1],
outline: results[2],
info: results[3],
metadata: results[4],
encrypted: !!results[5],
javaScript: results[6]
};
loadDocumentPromise.resolve(doc);
},
parseFailure);
};
var parseFailure = function parseFailure(e) {
loadDocumentPromise.reject(e);
};
pdfManager.ensureModel('checkHeader', []).then(function() {
pdfManager.ensureModel('parseStartXRef', []).then(function() {
pdfManager.ensureModel('parse', [recoveryMode]).then(
parseSuccess, parseFailure);
}, parseFailure);
}, parseFailure);
return loadDocumentPromise;
}
function getPdfManager(data) {
var pdfManagerPromise = new Promise();
var source = data.source;
var disableRange = data.disableRange;
if (source.data) {
try {
pdfManager = new LocalPdfManager(source.data, source.password);
pdfManagerPromise.resolve();
} catch (ex) {
pdfManagerPromise.reject(ex);
}
return pdfManagerPromise;
} else if (source.chunkedViewerLoading) {
try {
pdfManager = new NetworkPdfManager(source, handler);
pdfManagerPromise.resolve();
} catch (ex) {
pdfManagerPromise.reject(ex);
}
return pdfManagerPromise;
}
var networkManager = new NetworkManager(source.url, {
httpHeaders: source.httpHeaders
});
var fullRequestXhrId = networkManager.requestFull({
onHeadersReceived: function onHeadersReceived() {
if (disableRange) {
return;
}
var fullRequestXhr = networkManager.getRequestXhr(fullRequestXhrId);
if (fullRequestXhr.getResponseHeader('Accept-Ranges') !== 'bytes') {
return;
}
var contentEncoding =
fullRequestXhr.getResponseHeader('Content-Encoding') || 'identity';
if (contentEncoding !== 'identity') {
return;
}
var length = fullRequestXhr.getResponseHeader('Content-Length');
length = parseInt(length, 10);
if (!isInt(length)) {
return;
}
// NOTE: by cancelling the full request, and then issuing range
// requests, there will be an issue for sites where you can only
// request the pdf once. However, if this is the case, then the
// server should not be returning that it can support range requests.
networkManager.abortRequest(fullRequestXhrId);
source.length = length;
try {
pdfManager = new NetworkPdfManager(source, handler);
pdfManagerPromise.resolve(pdfManager);
} catch (ex) {
pdfManagerPromise.reject(ex);
}
},
onDone: function onDone(args) {
// the data is array, instantiating directly from it
try {
pdfManager = new LocalPdfManager(args.chunk, source.password);
pdfManagerPromise.resolve();
} catch (ex) {
pdfManagerPromise.reject(ex);
}
},
onError: function onError(status) {
if (status == 404) {
var exception = new MissingPDFException( 'Missing PDF "' +
source.url + '".');
handler.send('MissingPDF', { exception: exception });
} else {
handler.send('DocError', 'Unexpected server response (' +
status + ') while retrieving PDF "' +
source.url + '".');
}
},
onProgress: function onProgress(evt) {
handler.send('DocProgress', {
loaded: evt.loaded,
total: evt.lengthComputable ? evt.total : void(0)
});
}
});
return pdfManagerPromise;
}
handler.on('test', function wphSetupTest(data) {
// check if Uint8Array can be sent to worker
if (!(data instanceof Uint8Array)) {
handler.send('test', false);
return;
}
// check if the response property is supported by xhr
var xhr = new XMLHttpRequest();
var responseExists = 'response' in xhr;
// check if the property is actually implemented
try {
var dummy = xhr.responseType;
} catch (e) {
responseExists = false;
}
if (!responseExists) {
handler.send('test', false);
return;
}
handler.send('test', true);
});
handler.on('GetDocRequest', function wphSetupDoc(data) {
var onSuccess = function(doc) {
handler.send('GetDoc', { pdfInfo: doc });
pdfManager.ensureModel('traversePages', []).then(null, onFailure);
};
var onFailure = function(e) {
if (e instanceof PasswordException) {
if (e.code === PasswordResponses.NEED_PASSWORD) {
handler.send('NeedPassword', {
exception: e
});
} else if (e.code === PasswordResponses.INCORRECT_PASSWORD) {
handler.send('IncorrectPassword', {
exception: e
});
}
} else if (e instanceof InvalidPDFException) {
handler.send('InvalidPDF', {
exception: e
});
} else if (e instanceof MissingPDFException) {
handler.send('MissingPDF', {
exception: e
});
} else {
handler.send('UnknownError', {
exception: new UnknownErrorException(e.message, e.toString())
});
}
};
PDFJS.maxImageSize = data.maxImageSize === undefined ?
-1 : data.maxImageSize;
getPdfManager(data).then(function pdfManagerReady() {
loadDocument(false).then(onSuccess, function loadFailure(ex) {
// Try again with recoveryMode == true
if (!(ex instanceof XRefParseException)) {
if (ex instanceof PasswordException) {
// after password exception prepare to receive a new password
// to repeat loading
pdfManager.passwordChangedPromise = new Promise();
pdfManager.passwordChangedPromise.then(pdfManagerReady);
}
onFailure(ex);
return;
}
pdfManager.requestLoadedStream();
pdfManager.onLoadedStream().then(function() {
loadDocument(true).then(onSuccess, onFailure);
});
}, onFailure);
}, onFailure);
});
handler.on('GetPageRequest', function wphSetupGetPage(data) {
var pageIndex = data.pageIndex;
pdfManager.getPage(pageIndex).then(function(page) {
var rotatePromise = pdfManager.ensure(page, 'rotate');
var refPromise = pdfManager.ensure(page, 'ref');
var viewPromise = pdfManager.ensure(page, 'view');
Promise.all([rotatePromise, refPromise, viewPromise]).then(
function(results) {
var page = {
pageIndex: data.pageIndex,
rotate: results[0],
ref: results[1],
view: results[2]
};
handler.send('GetPage', { pageInfo: page });
});
});
});
handler.on('GetDestinations',
function wphSetupGetDestinations(data, promise) {
pdfManager.ensureCatalog('destinations').then(function(destinations) {
promise.resolve(destinations);
});
}
);
handler.on('GetData', function wphSetupGetData(data, promise) {
pdfManager.requestLoadedStream();
pdfManager.onLoadedStream().then(function(stream) {
promise.resolve(stream.bytes);
});
});
handler.on('DataLoaded', function wphSetupDataLoaded(data, promise) {
pdfManager.onLoadedStream().then(function(stream) {
promise.resolve({ length: stream.bytes.byteLength });
});
});
handler.on('UpdatePassword', function wphSetupUpdatePassword(data) {
pdfManager.updatePassword(data);
});
handler.on('GetAnnotationsRequest', function wphSetupGetAnnotations(data) {
pdfManager.getPage(data.pageIndex).then(function(page) {
pdfManager.ensure(page, 'getAnnotationsData', []).then(
function(annotationsData) {
handler.send('GetAnnotations', {
pageIndex: data.pageIndex,
annotations: annotationsData
});
}
);
});
});
handler.on('RenderPageRequest', function wphSetupRenderPage(data) {
pdfManager.getPage(data.pageIndex).then(function(page) {
var pageNum = data.pageIndex + 1;
var start = Date.now();
// Pre compile the pdf page and fetch the fonts/images.
page.getOperatorList(handler).then(function(operatorList) {
log('page=%d - getOperatorList: time=%dms, len=%d', pageNum,
Date.now() - start, operatorList.fnArray.length);
}, function(e) {
var minimumStackMessage =
'worker.js: while trying to getPage() and getOperatorList()';
var wrappedException;
// Turn the error into an obj that can be serialized
if (typeof e === 'string') {
wrappedException = {
message: e,
stack: minimumStackMessage
};
} else if (typeof e === 'object') {
wrappedException = {
message: e.message || e.toString(),
stack: e.stack || minimumStackMessage
};
} else {
wrappedException = {
message: 'Unknown exception type: ' + (typeof e),
stack: minimumStackMessage
};
}
handler.send('PageError', {
pageNum: pageNum,
error: wrappedException
});
});
});
}, this);
handler.on('GetTextContent', function wphExtractText(data, promise) {
pdfManager.getPage(data.pageIndex).then(function(page) {
var pageNum = data.pageIndex + 1;
var start = Date.now();
page.extractTextContent().then(function(textContent) {
promise.resolve(textContent);
log('text indexing: page=%d - time=%dms', pageNum,
Date.now() - start);
}, function (e) {
// Skip errored pages
promise.reject(e);
});
});
});
handler.on('Terminate', function wphTerminate(data, promise) {
pdfManager.streamManager.networkManager.abortAllRequests();
promise.resolve();
});
}
};
var consoleTimer = {};
var workerConsole = {
log: function log() {
var args = Array.prototype.slice.call(arguments);
globalScope.postMessage({
action: 'console_log',
data: args
});
},
error: function error() {
var args = Array.prototype.slice.call(arguments);
globalScope.postMessage({
action: 'console_error',
data: args
});
throw 'pdf.js execution error';
},
time: function time(name) {
consoleTimer[name] = Date.now();
},
timeEnd: function timeEnd(name) {
var time = consoleTimer[name];
if (!time) {
error('Unkown timer name ' + name);
}
this.log('Timer:', name, Date.now() - time);
}
};
// Worker thread?
if (typeof window === 'undefined') {
globalScope.console = workerConsole;
// Add a logger so we can pass warnings on to the main thread, errors will
// throw an exception which will be forwarded on automatically.
PDFJS.LogManager.addLogger({
warn: function(msg) {
globalScope.postMessage({
action: '_warn',
data: msg
});
}
});
var handler = new MessageHandler('worker_processor', this);
WorkerMessageHandler.setup(handler);
}