app/assets/javascripts/pdfjs_viewer/pdfjs/pdf.combined.js in pdfjs_viewer-rails-0.0.6 vs app/assets/javascripts/pdfjs_viewer/pdfjs/pdf.combined.js in pdfjs_viewer-rails-0.0.7
- old
+ new
@@ -1,7 +1,5 @@
-/* -*- 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
@@ -20,37 +18,18 @@
// Initializing PDFJS global object (if still undefined)
if (typeof PDFJS === 'undefined') {
(typeof window !== 'undefined' ? window : this).PDFJS = {};
}
-PDFJS.version = '1.0.907';
-PDFJS.build = 'e9072ac';
+PDFJS.version = '1.3.91';
+PDFJS.build = 'd1e83b5';
(function pdfjsWrapper() {
// Use strict in our context only - users might not want it
'use strict';
-/* -*- 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 Cmd, ColorSpace, Dict, MozBlobBuilder, Name, PDFJS, Ref, URL,
- Promise */
-'use strict';
var globalScope = (typeof window === 'undefined') ? this : window;
var isWorker = (typeof window === 'undefined');
@@ -74,15 +53,59 @@
RGB_24BPP: 2,
RGBA_32BPP: 3
};
var AnnotationType = {
- WIDGET: 1,
- TEXT: 2,
- LINK: 3
+ TEXT: 1,
+ LINK: 2,
+ FREETEXT: 3,
+ LINE: 4,
+ SQUARE: 5,
+ CIRCLE: 6,
+ POLYGON: 7,
+ POLYLINE: 8,
+ HIGHLIGHT: 9,
+ UNDERLINE: 10,
+ SQUIGGLY: 11,
+ STRIKEOUT: 12,
+ STAMP: 13,
+ CARET: 14,
+ INK: 15,
+ POPUP: 16,
+ FILEATTACHMENT: 17,
+ SOUND: 18,
+ MOVIE: 19,
+ WIDGET: 20,
+ SCREEN: 21,
+ PRINTERMARK: 22,
+ TRAPNET: 23,
+ WATERMARK: 24,
+ THREED: 25,
+ REDACT: 26
};
+var AnnotationFlag = {
+ INVISIBLE: 0x01,
+ HIDDEN: 0x02,
+ PRINT: 0x04,
+ NOZOOM: 0x08,
+ NOROTATE: 0x10,
+ NOVIEW: 0x20,
+ READONLY: 0x40,
+ LOCKED: 0x80,
+ TOGGLENOVIEW: 0x100,
+ LOCKEDCONTENTS: 0x200
+};
+
+var AnnotationBorderStyleType = {
+ SOLID: 1,
+ DASHED: 2,
+ BEVELED: 3,
+ INSET: 4,
+ UNDERLINE: 5
+};
+
var StreamType = {
UNKNOWN: 0,
FLATE: 1,
LZW: 2,
DCT: 3,
@@ -234,25 +257,22 @@
if (PDFJS.verbosity >= PDFJS.VERBOSITY_LEVELS.warnings) {
console.log('Warning: ' + msg);
}
}
+// Deprecated API function -- treated as warnings.
+function deprecated(details) {
+ warn('Deprecated API usage: ' + details);
+}
+
// Fatal errors that should trigger the fallback UI and halt execution by
// throwing an exception.
function error(msg) {
- // If multiple arguments were passed, pass them all to the log function.
- if (arguments.length > 1) {
- var logArguments = ['Error:'];
- logArguments.push.apply(logArguments, arguments);
- console.log.apply(console, logArguments);
- // Join the arguments into a single string for the lines below.
- msg = [].join.call(arguments, ' ');
- } else {
+ if (PDFJS.verbosity >= PDFJS.VERBOSITY_LEVELS.errors) {
console.log('Error: ' + msg);
+ console.log(backtrace());
}
- console.log(backtrace());
- UnsupportedManager.notify(UNSUPPORTED_FEATURES.unknown);
throw new Error(msg);
}
function backtrace() {
try {
@@ -275,55 +295,17 @@
smask: 'smask',
shadingPattern: 'shadingPattern',
font: 'font'
};
-var UnsupportedManager = PDFJS.UnsupportedManager =
- (function UnsupportedManagerClosure() {
- var listeners = [];
- return {
- listen: function (cb) {
- listeners.push(cb);
- },
- notify: function (featureId) {
- warn('Unsupported feature "' + featureId + '"');
- for (var i = 0, ii = listeners.length; i < ii; i++) {
- listeners[i](featureId);
- }
- }
- };
-})();
-
// Combines two URLs. The baseUrl shall be absolute URL. If the url is an
// absolute URL, it will be returned as is.
function combineUrl(baseUrl, url) {
if (!url) {
return baseUrl;
}
- if (/^[a-z][a-z0-9+\-.]*:/i.test(url)) {
- return url;
- }
- var i;
- if (url.charAt(0) === '/') {
- // absolute path
- i = baseUrl.indexOf('://');
- if (url.charAt(1) === '/') {
- ++i;
- } else {
- i = baseUrl.indexOf('/', i + 3);
- }
- return baseUrl.substring(0, i) + url;
- } else {
- // relative path
- var pathLength = baseUrl.length;
- i = baseUrl.lastIndexOf('#');
- pathLength = i >= 0 ? i : pathLength;
- i = baseUrl.lastIndexOf('?', pathLength);
- pathLength = i >= 0 ? i : pathLength;
- var prefixLength = baseUrl.lastIndexOf('/', pathLength);
- return baseUrl.substring(0, prefixLength + 1) + url;
- }
+ return new URL(url, baseUrl).href;
}
// Validates if URL is safe and allowed, e.g. to avoid XSS.
function isValidUrl(url, allowRelative) {
if (!url) {
@@ -339,10 +321,11 @@
switch (protocol) {
case 'http':
case 'https':
case 'ftp':
case 'mailto':
+ case 'tel':
return true;
default:
return false;
}
}
@@ -353,11 +336,53 @@
enumerable: true,
configurable: true,
writable: false });
return value;
}
+PDFJS.shadow = shadow;
+var LinkTarget = PDFJS.LinkTarget = {
+ NONE: 0, // Default value.
+ SELF: 1,
+ BLANK: 2,
+ PARENT: 3,
+ TOP: 4,
+};
+var LinkTargetStringMap = [
+ '',
+ '_self',
+ '_blank',
+ '_parent',
+ '_top'
+];
+
+function isExternalLinkTargetSet() {
+ if (PDFJS.openExternalLinksInNewWindow) {
+ deprecated('PDFJS.openExternalLinksInNewWindow, please use ' +
+ '"PDFJS.externalLinkTarget = PDFJS.LinkTarget.BLANK" instead.');
+ if (PDFJS.externalLinkTarget === LinkTarget.NONE) {
+ PDFJS.externalLinkTarget = LinkTarget.BLANK;
+ }
+ // Reset the deprecated parameter, to suppress further warnings.
+ PDFJS.openExternalLinksInNewWindow = false;
+ }
+ switch (PDFJS.externalLinkTarget) {
+ case LinkTarget.NONE:
+ return false;
+ case LinkTarget.SELF:
+ case LinkTarget.BLANK:
+ case LinkTarget.PARENT:
+ case LinkTarget.TOP:
+ return true;
+ }
+ warn('PDFJS.externalLinkTarget is invalid: ' + PDFJS.externalLinkTarget);
+ // Reset the external link target, to suppress further warnings.
+ PDFJS.externalLinkTarget = LinkTarget.NONE;
+ return false;
+}
+PDFJS.isExternalLinkTargetSet = isExternalLinkTargetSet;
+
var PasswordResponses = PDFJS.PasswordResponses = {
NEED_PASSWORD: 1,
INCORRECT_PASSWORD: 2
};
@@ -468,10 +493,12 @@
return XRefParseException;
})();
function bytesToString(bytes) {
+ assert(bytes !== null && typeof bytes === 'object' &&
+ bytes.length !== undefined, 'Invalid argument for bytesToString');
var length = bytes.length;
var MAX_ARGUMENT_COUNT = 8192;
if (length < MAX_ARGUMENT_COUNT) {
return String.fromCharCode.apply(null, bytes);
}
@@ -483,10 +510,11 @@
}
return strBuf.join('');
}
function stringToBytes(str) {
+ assert(typeof str === 'string', 'Invalid argument for stringToBytes');
var length = str.length;
var bytes = new Uint8Array(length);
for (var i = 0; i < length; ++i) {
bytes[i] = str.charCodeAt(i) & 0xFF;
}
@@ -534,11 +562,11 @@
get: function PDFJS_isLittleEndian() {
return shadow(PDFJS, 'isLittleEndian', isLittleEndian());
}
});
- // Lazy test if the userAgant support CanvasTypedArrays
+ // Lazy test if the userAgent support CanvasTypedArrays
function hasCanvasTypedArrays() {
var canvas = document.createElement('canvas');
canvas.width = canvas.height = 1;
var ctx = canvas.getContext('2d');
var imageData = ctx.createImageData(1, 1);
@@ -599,14 +627,14 @@
var rgbBuf = ['rgb(', 0, ',', 0, ',', 0, ')'];
// makeCssRgb() can be called thousands of times. Using |rgbBuf| avoids
// creating many intermediate strings.
- Util.makeCssRgb = function Util_makeCssRgb(rgb) {
- rgbBuf[1] = rgb[0];
- rgbBuf[3] = rgb[1];
- rgbBuf[5] = rgb[2];
+ Util.makeCssRgb = function Util_makeCssRgb(r, g, b) {
+ rgbBuf[1] = r;
+ rgbBuf[3] = g;
+ rgbBuf[5] = b;
return rgbBuf.join('');
};
// Concatenates two transformation matrices together and returns the result.
Util.transform = function Util_transform(m1, m2) {
@@ -973,10 +1001,14 @@
function stringToUTF8String(str) {
return decodeURIComponent(escape(str));
}
+function utf8StringToString(str) {
+ return unescape(encodeURIComponent(str));
+}
+
function isEmptyObj(obj) {
for (var key in obj) {
return false;
}
return true;
@@ -996,14 +1028,10 @@
function isString(v) {
return typeof v === 'string';
}
-function isNull(v) {
- return v === null;
-}
-
function isName(v) {
return v instanceof Name;
}
function isCmd(v, cmd) {
@@ -1474,30 +1502,24 @@
}
return buffer;
};
})();
-function MessageHandler(name, comObj) {
- this.name = name;
+function MessageHandler(sourceName, targetName, comObj) {
+ this.sourceName = sourceName;
+ this.targetName = targetName;
this.comObj = comObj;
this.callbackIndex = 1;
this.postMessageTransfers = true;
var callbacksCapabilities = this.callbacksCapabilities = {};
var ah = this.actionHandler = {};
- ah['console_log'] = [function ahConsoleLog(data) {
- console.log.apply(console, data);
- }];
- ah['console_error'] = [function ahConsoleError(data) {
- console.error.apply(console, data);
- }];
- ah['_unsupported_feature'] = [function ah_unsupportedFeature(data) {
- UnsupportedManager.notify(data);
- }];
-
- comObj.onmessage = function messageHandlerComObjOnMessage(event) {
+ this._onComObjOnMessage = function messageHandlerComObjOnMessage(event) {
var data = event.data;
+ if (data.targetName !== this.sourceName) {
+ return;
+ }
if (data.isReply) {
var callbackId = data.callbackId;
if (data.callbackId in callbacksCapabilities) {
var callback = callbacksCapabilities[callbackId];
delete callbacksCapabilities[callbackId];
@@ -1510,20 +1532,30 @@
error('Cannot resolve callback ' + callbackId);
}
} else if (data.action in ah) {
var action = ah[data.action];
if (data.callbackId) {
+ var sourceName = this.sourceName;
+ var targetName = data.sourceName;
Promise.resolve().then(function () {
return action[0].call(action[1], data.data);
}).then(function (result) {
comObj.postMessage({
+ sourceName: sourceName,
+ targetName: targetName,
isReply: true,
callbackId: data.callbackId,
data: result
});
}, function (reason) {
+ if (reason instanceof Error) {
+ // Serialize error to avoid "DataCloneError"
+ reason = reason + '';
+ }
comObj.postMessage({
+ sourceName: sourceName,
+ targetName: targetName,
isReply: true,
callbackId: data.callbackId,
error: reason
});
});
@@ -1531,11 +1563,12 @@
action[0].call(action[1], data.data);
}
} else {
error('Unknown action from worker: ' + data.action);
}
- };
+ }.bind(this);
+ comObj.addEventListener('message', this._onComObjOnMessage);
}
MessageHandler.prototype = {
on: function messageHandlerOn(actionName, handler, scope) {
var ah = this.actionHandler;
@@ -1550,10 +1583,12 @@
* @param {JSON} data JSON data to send.
* @param {Array} [transfers] Optional list of transfers/ArrayBuffers
*/
send: function messageHandlerSend(actionName, data, transfers) {
var message = {
+ sourceName: this.sourceName,
+ targetName: this.targetName,
action: actionName,
data: data
};
this.postMessage(message, transfers);
},
@@ -1567,10 +1602,12 @@
*/
sendWithPromise:
function messageHandlerSendWithPromise(actionName, data, transfers) {
var callbackId = this.callbackIndex++;
var message = {
+ sourceName: this.sourceName,
+ targetName: this.targetName,
action: actionName,
data: data,
callbackId: callbackId
};
var capability = createPromiseCapability();
@@ -1592,10 +1629,14 @@
if (transfers && this.postMessageTransfers) {
this.comObj.postMessage(message, transfers);
} else {
this.comObj.postMessage(message);
}
+ },
+
+ destroy: function () {
+ this.comObj.removeEventListener('message', this._onComObjOnMessage);
}
};
function loadJpegStream(id, imageUrl, objs) {
var img = new Image();
@@ -1607,11 +1648,630 @@
warn('Error during JPEG image loading');
});
img.src = imageUrl;
}
+ // Polyfill from https://github.com/Polymer/URL
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+(function checkURLConstructor(scope) {
+ /* jshint ignore:start */
+ // feature detect for URL constructor
+ var hasWorkingUrl = false;
+ if (typeof URL === 'function' && ('origin' in URL.prototype)) {
+ try {
+ var u = new URL('b', 'http://a');
+ u.pathname = 'c%20d';
+ hasWorkingUrl = u.href === 'http://a/c%20d';
+ } catch(e) {}
+ }
+
+ if (hasWorkingUrl)
+ return;
+
+ var relative = Object.create(null);
+ relative['ftp'] = 21;
+ relative['file'] = 0;
+ relative['gopher'] = 70;
+ relative['http'] = 80;
+ relative['https'] = 443;
+ relative['ws'] = 80;
+ relative['wss'] = 443;
+
+ var relativePathDotMapping = Object.create(null);
+ relativePathDotMapping['%2e'] = '.';
+ relativePathDotMapping['.%2e'] = '..';
+ relativePathDotMapping['%2e.'] = '..';
+ relativePathDotMapping['%2e%2e'] = '..';
+
+ function isRelativeScheme(scheme) {
+ return relative[scheme] !== undefined;
+ }
+
+ function invalid() {
+ clear.call(this);
+ this._isInvalid = true;
+ }
+
+ function IDNAToASCII(h) {
+ if ('' == h) {
+ invalid.call(this)
+ }
+ // XXX
+ return h.toLowerCase()
+ }
+
+ function percentEscape(c) {
+ var unicode = c.charCodeAt(0);
+ if (unicode > 0x20 &&
+ unicode < 0x7F &&
+ // " # < > ? `
+ [0x22, 0x23, 0x3C, 0x3E, 0x3F, 0x60].indexOf(unicode) == -1
+ ) {
+ return c;
+ }
+ return encodeURIComponent(c);
+ }
+
+ function percentEscapeQuery(c) {
+ // XXX This actually needs to encode c using encoding and then
+ // convert the bytes one-by-one.
+
+ var unicode = c.charCodeAt(0);
+ if (unicode > 0x20 &&
+ unicode < 0x7F &&
+ // " # < > ` (do not escape '?')
+ [0x22, 0x23, 0x3C, 0x3E, 0x60].indexOf(unicode) == -1
+ ) {
+ return c;
+ }
+ return encodeURIComponent(c);
+ }
+
+ var EOF = undefined,
+ ALPHA = /[a-zA-Z]/,
+ ALPHANUMERIC = /[a-zA-Z0-9\+\-\.]/;
+
+ function parse(input, stateOverride, base) {
+ function err(message) {
+ errors.push(message)
+ }
+
+ var state = stateOverride || 'scheme start',
+ cursor = 0,
+ buffer = '',
+ seenAt = false,
+ seenBracket = false,
+ errors = [];
+
+ loop: while ((input[cursor - 1] != EOF || cursor == 0) && !this._isInvalid) {
+ var c = input[cursor];
+ switch (state) {
+ case 'scheme start':
+ if (c && ALPHA.test(c)) {
+ buffer += c.toLowerCase(); // ASCII-safe
+ state = 'scheme';
+ } else if (!stateOverride) {
+ buffer = '';
+ state = 'no scheme';
+ continue;
+ } else {
+ err('Invalid scheme.');
+ break loop;
+ }
+ break;
+
+ case 'scheme':
+ if (c && ALPHANUMERIC.test(c)) {
+ buffer += c.toLowerCase(); // ASCII-safe
+ } else if (':' == c) {
+ this._scheme = buffer;
+ buffer = '';
+ if (stateOverride) {
+ break loop;
+ }
+ if (isRelativeScheme(this._scheme)) {
+ this._isRelative = true;
+ }
+ if ('file' == this._scheme) {
+ state = 'relative';
+ } else if (this._isRelative && base && base._scheme == this._scheme) {
+ state = 'relative or authority';
+ } else if (this._isRelative) {
+ state = 'authority first slash';
+ } else {
+ state = 'scheme data';
+ }
+ } else if (!stateOverride) {
+ buffer = '';
+ cursor = 0;
+ state = 'no scheme';
+ continue;
+ } else if (EOF == c) {
+ break loop;
+ } else {
+ err('Code point not allowed in scheme: ' + c)
+ break loop;
+ }
+ break;
+
+ case 'scheme data':
+ if ('?' == c) {
+ this._query = '?';
+ state = 'query';
+ } else if ('#' == c) {
+ this._fragment = '#';
+ state = 'fragment';
+ } else {
+ // XXX error handling
+ if (EOF != c && '\t' != c && '\n' != c && '\r' != c) {
+ this._schemeData += percentEscape(c);
+ }
+ }
+ break;
+
+ case 'no scheme':
+ if (!base || !(isRelativeScheme(base._scheme))) {
+ err('Missing scheme.');
+ invalid.call(this);
+ } else {
+ state = 'relative';
+ continue;
+ }
+ break;
+
+ case 'relative or authority':
+ if ('/' == c && '/' == input[cursor+1]) {
+ state = 'authority ignore slashes';
+ } else {
+ err('Expected /, got: ' + c);
+ state = 'relative';
+ continue
+ }
+ break;
+
+ case 'relative':
+ this._isRelative = true;
+ if ('file' != this._scheme)
+ this._scheme = base._scheme;
+ if (EOF == c) {
+ this._host = base._host;
+ this._port = base._port;
+ this._path = base._path.slice();
+ this._query = base._query;
+ this._username = base._username;
+ this._password = base._password;
+ break loop;
+ } else if ('/' == c || '\\' == c) {
+ if ('\\' == c)
+ err('\\ is an invalid code point.');
+ state = 'relative slash';
+ } else if ('?' == c) {
+ this._host = base._host;
+ this._port = base._port;
+ this._path = base._path.slice();
+ this._query = '?';
+ this._username = base._username;
+ this._password = base._password;
+ state = 'query';
+ } else if ('#' == c) {
+ this._host = base._host;
+ this._port = base._port;
+ this._path = base._path.slice();
+ this._query = base._query;
+ this._fragment = '#';
+ this._username = base._username;
+ this._password = base._password;
+ state = 'fragment';
+ } else {
+ var nextC = input[cursor+1]
+ var nextNextC = input[cursor+2]
+ if (
+ 'file' != this._scheme || !ALPHA.test(c) ||
+ (nextC != ':' && nextC != '|') ||
+ (EOF != nextNextC && '/' != nextNextC && '\\' != nextNextC && '?' != nextNextC && '#' != nextNextC)) {
+ this._host = base._host;
+ this._port = base._port;
+ this._username = base._username;
+ this._password = base._password;
+ this._path = base._path.slice();
+ this._path.pop();
+ }
+ state = 'relative path';
+ continue;
+ }
+ break;
+
+ case 'relative slash':
+ if ('/' == c || '\\' == c) {
+ if ('\\' == c) {
+ err('\\ is an invalid code point.');
+ }
+ if ('file' == this._scheme) {
+ state = 'file host';
+ } else {
+ state = 'authority ignore slashes';
+ }
+ } else {
+ if ('file' != this._scheme) {
+ this._host = base._host;
+ this._port = base._port;
+ this._username = base._username;
+ this._password = base._password;
+ }
+ state = 'relative path';
+ continue;
+ }
+ break;
+
+ case 'authority first slash':
+ if ('/' == c) {
+ state = 'authority second slash';
+ } else {
+ err("Expected '/', got: " + c);
+ state = 'authority ignore slashes';
+ continue;
+ }
+ break;
+
+ case 'authority second slash':
+ state = 'authority ignore slashes';
+ if ('/' != c) {
+ err("Expected '/', got: " + c);
+ continue;
+ }
+ break;
+
+ case 'authority ignore slashes':
+ if ('/' != c && '\\' != c) {
+ state = 'authority';
+ continue;
+ } else {
+ err('Expected authority, got: ' + c);
+ }
+ break;
+
+ case 'authority':
+ if ('@' == c) {
+ if (seenAt) {
+ err('@ already seen.');
+ buffer += '%40';
+ }
+ seenAt = true;
+ for (var i = 0; i < buffer.length; i++) {
+ var cp = buffer[i];
+ if ('\t' == cp || '\n' == cp || '\r' == cp) {
+ err('Invalid whitespace in authority.');
+ continue;
+ }
+ // XXX check URL code points
+ if (':' == cp && null === this._password) {
+ this._password = '';
+ continue;
+ }
+ var tempC = percentEscape(cp);
+ (null !== this._password) ? this._password += tempC : this._username += tempC;
+ }
+ buffer = '';
+ } else if (EOF == c || '/' == c || '\\' == c || '?' == c || '#' == c) {
+ cursor -= buffer.length;
+ buffer = '';
+ state = 'host';
+ continue;
+ } else {
+ buffer += c;
+ }
+ break;
+
+ case 'file host':
+ if (EOF == c || '/' == c || '\\' == c || '?' == c || '#' == c) {
+ if (buffer.length == 2 && ALPHA.test(buffer[0]) && (buffer[1] == ':' || buffer[1] == '|')) {
+ state = 'relative path';
+ } else if (buffer.length == 0) {
+ state = 'relative path start';
+ } else {
+ this._host = IDNAToASCII.call(this, buffer);
+ buffer = '';
+ state = 'relative path start';
+ }
+ continue;
+ } else if ('\t' == c || '\n' == c || '\r' == c) {
+ err('Invalid whitespace in file host.');
+ } else {
+ buffer += c;
+ }
+ break;
+
+ case 'host':
+ case 'hostname':
+ if (':' == c && !seenBracket) {
+ // XXX host parsing
+ this._host = IDNAToASCII.call(this, buffer);
+ buffer = '';
+ state = 'port';
+ if ('hostname' == stateOverride) {
+ break loop;
+ }
+ } else if (EOF == c || '/' == c || '\\' == c || '?' == c || '#' == c) {
+ this._host = IDNAToASCII.call(this, buffer);
+ buffer = '';
+ state = 'relative path start';
+ if (stateOverride) {
+ break loop;
+ }
+ continue;
+ } else if ('\t' != c && '\n' != c && '\r' != c) {
+ if ('[' == c) {
+ seenBracket = true;
+ } else if (']' == c) {
+ seenBracket = false;
+ }
+ buffer += c;
+ } else {
+ err('Invalid code point in host/hostname: ' + c);
+ }
+ break;
+
+ case 'port':
+ if (/[0-9]/.test(c)) {
+ buffer += c;
+ } else if (EOF == c || '/' == c || '\\' == c || '?' == c || '#' == c || stateOverride) {
+ if ('' != buffer) {
+ var temp = parseInt(buffer, 10);
+ if (temp != relative[this._scheme]) {
+ this._port = temp + '';
+ }
+ buffer = '';
+ }
+ if (stateOverride) {
+ break loop;
+ }
+ state = 'relative path start';
+ continue;
+ } else if ('\t' == c || '\n' == c || '\r' == c) {
+ err('Invalid code point in port: ' + c);
+ } else {
+ invalid.call(this);
+ }
+ break;
+
+ case 'relative path start':
+ if ('\\' == c)
+ err("'\\' not allowed in path.");
+ state = 'relative path';
+ if ('/' != c && '\\' != c) {
+ continue;
+ }
+ break;
+
+ case 'relative path':
+ if (EOF == c || '/' == c || '\\' == c || (!stateOverride && ('?' == c || '#' == c))) {
+ if ('\\' == c) {
+ err('\\ not allowed in relative path.');
+ }
+ var tmp;
+ if (tmp = relativePathDotMapping[buffer.toLowerCase()]) {
+ buffer = tmp;
+ }
+ if ('..' == buffer) {
+ this._path.pop();
+ if ('/' != c && '\\' != c) {
+ this._path.push('');
+ }
+ } else if ('.' == buffer && '/' != c && '\\' != c) {
+ this._path.push('');
+ } else if ('.' != buffer) {
+ if ('file' == this._scheme && this._path.length == 0 && buffer.length == 2 && ALPHA.test(buffer[0]) && buffer[1] == '|') {
+ buffer = buffer[0] + ':';
+ }
+ this._path.push(buffer);
+ }
+ buffer = '';
+ if ('?' == c) {
+ this._query = '?';
+ state = 'query';
+ } else if ('#' == c) {
+ this._fragment = '#';
+ state = 'fragment';
+ }
+ } else if ('\t' != c && '\n' != c && '\r' != c) {
+ buffer += percentEscape(c);
+ }
+ break;
+
+ case 'query':
+ if (!stateOverride && '#' == c) {
+ this._fragment = '#';
+ state = 'fragment';
+ } else if (EOF != c && '\t' != c && '\n' != c && '\r' != c) {
+ this._query += percentEscapeQuery(c);
+ }
+ break;
+
+ case 'fragment':
+ if (EOF != c && '\t' != c && '\n' != c && '\r' != c) {
+ this._fragment += c;
+ }
+ break;
+ }
+
+ cursor++;
+ }
+ }
+
+ function clear() {
+ this._scheme = '';
+ this._schemeData = '';
+ this._username = '';
+ this._password = null;
+ this._host = '';
+ this._port = '';
+ this._path = [];
+ this._query = '';
+ this._fragment = '';
+ this._isInvalid = false;
+ this._isRelative = false;
+ }
+
+ // Does not process domain names or IP addresses.
+ // Does not handle encoding for the query parameter.
+ function jURL(url, base /* , encoding */) {
+ if (base !== undefined && !(base instanceof jURL))
+ base = new jURL(String(base));
+
+ this._url = url;
+ clear.call(this);
+
+ var input = url.replace(/^[ \t\r\n\f]+|[ \t\r\n\f]+$/g, '');
+ // encoding = encoding || 'utf-8'
+
+ parse.call(this, input, null, base);
+ }
+
+ jURL.prototype = {
+ toString: function() {
+ return this.href;
+ },
+ get href() {
+ if (this._isInvalid)
+ return this._url;
+
+ var authority = '';
+ if ('' != this._username || null != this._password) {
+ authority = this._username +
+ (null != this._password ? ':' + this._password : '') + '@';
+ }
+
+ return this.protocol +
+ (this._isRelative ? '//' + authority + this.host : '') +
+ this.pathname + this._query + this._fragment;
+ },
+ set href(href) {
+ clear.call(this);
+ parse.call(this, href);
+ },
+
+ get protocol() {
+ return this._scheme + ':';
+ },
+ set protocol(protocol) {
+ if (this._isInvalid)
+ return;
+ parse.call(this, protocol + ':', 'scheme start');
+ },
+
+ get host() {
+ return this._isInvalid ? '' : this._port ?
+ this._host + ':' + this._port : this._host;
+ },
+ set host(host) {
+ if (this._isInvalid || !this._isRelative)
+ return;
+ parse.call(this, host, 'host');
+ },
+
+ get hostname() {
+ return this._host;
+ },
+ set hostname(hostname) {
+ if (this._isInvalid || !this._isRelative)
+ return;
+ parse.call(this, hostname, 'hostname');
+ },
+
+ get port() {
+ return this._port;
+ },
+ set port(port) {
+ if (this._isInvalid || !this._isRelative)
+ return;
+ parse.call(this, port, 'port');
+ },
+
+ get pathname() {
+ return this._isInvalid ? '' : this._isRelative ?
+ '/' + this._path.join('/') : this._schemeData;
+ },
+ set pathname(pathname) {
+ if (this._isInvalid || !this._isRelative)
+ return;
+ this._path = [];
+ parse.call(this, pathname, 'relative path start');
+ },
+
+ get search() {
+ return this._isInvalid || !this._query || '?' == this._query ?
+ '' : this._query;
+ },
+ set search(search) {
+ if (this._isInvalid || !this._isRelative)
+ return;
+ this._query = '?';
+ if ('?' == search[0])
+ search = search.slice(1);
+ parse.call(this, search, 'query');
+ },
+
+ get hash() {
+ return this._isInvalid || !this._fragment || '#' == this._fragment ?
+ '' : this._fragment;
+ },
+ set hash(hash) {
+ if (this._isInvalid)
+ return;
+ this._fragment = '#';
+ if ('#' == hash[0])
+ hash = hash.slice(1);
+ parse.call(this, hash, 'fragment');
+ },
+
+ get origin() {
+ var host;
+ if (this._isInvalid || !this._scheme) {
+ return '';
+ }
+ // javascript: Gecko returns String(""), WebKit/Blink String("null")
+ // Gecko throws error for "data://"
+ // data: Gecko returns "", Blink returns "data://", WebKit returns "null"
+ // Gecko returns String("") for file: mailto:
+ // WebKit/Blink returns String("SCHEME://") for file: mailto:
+ switch (this._scheme) {
+ case 'data':
+ case 'file':
+ case 'javascript':
+ case 'mailto':
+ return 'null';
+ }
+ host = this.host;
+ if (!host) {
+ return '';
+ }
+ return this._scheme + '://' + host;
+ }
+ };
+
+ // Copy over the static methods
+ var OriginalURL = scope.URL;
+ if (OriginalURL) {
+ jURL.createObjectURL = function(blob) {
+ // IE extension allows a second optional options argument.
+ // http://msdn.microsoft.com/en-us/library/ie/hh772302(v=vs.85).aspx
+ return OriginalURL.createObjectURL.apply(OriginalURL, arguments);
+ };
+ jURL.revokeObjectURL = function(url) {
+ OriginalURL.revokeObjectURL(url);
+ };
+ }
+
+ scope.URL = jURL;
+ /* jshint ignore:end */
+})(globalScope);
+
+
+var DEFAULT_RANGE_CHUNK_SIZE = 65536; // 2^16 = 65536
+
/**
* The maximum allowed image size in total pixels e.g. width * height. Images
* above this value will not be drawn. Use -1 for no limit.
* @var {number}
*/
@@ -1629,11 +2289,11 @@
* Specifies if CMaps are binary packed.
* @var {boolean}
*/
PDFJS.cMapPacked = PDFJS.cMapPacked === undefined ? false : PDFJS.cMapPacked;
-/*
+/**
* By default fonts are converted to OpenType fonts and loaded via font face
* rules. If disabled, the font will be rendered using a built in font renderer
* that constructs the glyphs with primitive path commands.
* @var {boolean}
*/
@@ -1658,11 +2318,13 @@
false : PDFJS.disableWorker);
/**
* Path and filename of the worker file. Required when the worker is enabled in
* development mode. If unspecified in the production build, the worker will be
- * loaded based on the location of the pdf.js file.
+ * loaded based on the location of the pdf.js file. It is recommended that
+ * the workerSrc is set in a custom application to prevent issues caused by
+ * third-party frameworks and libraries.
* @var {string}
*/
PDFJS.workerSrc = (PDFJS.workerSrc === undefined ? null : PDFJS.workerSrc);
/**
@@ -1684,10 +2346,13 @@
/**
* Disable pre-fetching of PDF file data. When range requests are enabled PDF.js
* will automatically keep fetching more data even if it isn't needed to display
* the current page. This default behavior can be disabled.
+ *
+ * NOTE: It is also necessary to disable streaming, see above,
+ * in order for disabling of pre-fetching to work correctly.
* @var {boolean}
*/
PDFJS.disableAutoFetch = (PDFJS.disableAutoFetch === undefined ?
false : PDFJS.disableAutoFetch);
@@ -1717,10 +2382,18 @@
*/
PDFJS.disableWebGL = (PDFJS.disableWebGL === undefined ?
true : PDFJS.disableWebGL);
/**
+ * Disables fullscreen support, and by extension Presentation Mode,
+ * in browsers which support the fullscreen API.
+ * @var {boolean}
+ */
+PDFJS.disableFullscreen = (PDFJS.disableFullscreen === undefined ?
+ false : PDFJS.disableFullscreen);
+
+/**
* Enables CSS only zooming.
* @var {boolean}
*/
PDFJS.useOnlyCssZoom = (PDFJS.useOnlyCssZoom === undefined ?
false : PDFJS.useOnlyCssZoom);
@@ -1735,31 +2408,74 @@
*/
PDFJS.verbosity = (PDFJS.verbosity === undefined ?
PDFJS.VERBOSITY_LEVELS.warnings : PDFJS.verbosity);
/**
- * The maximum supported canvas size in total pixels e.g. width * height.
+ * The maximum supported canvas size in total pixels e.g. width * height.
* The default value is 4096 * 4096. Use -1 for no limit.
* @var {number}
*/
PDFJS.maxCanvasPixels = (PDFJS.maxCanvasPixels === undefined ?
16777216 : PDFJS.maxCanvasPixels);
/**
+ * (Deprecated) Opens external links in a new window if enabled.
+ * The default behavior opens external links in the PDF.js window.
+ *
+ * NOTE: This property has been deprecated, please use
+ * `PDFJS.externalLinkTarget = PDFJS.LinkTarget.BLANK` instead.
+ * @var {boolean}
+ */
+PDFJS.openExternalLinksInNewWindow = (
+ PDFJS.openExternalLinksInNewWindow === undefined ?
+ false : PDFJS.openExternalLinksInNewWindow);
+
+/**
+ * Specifies the |target| attribute for external links.
+ * The constants from PDFJS.LinkTarget should be used:
+ * - NONE [default]
+ * - SELF
+ * - BLANK
+ * - PARENT
+ * - TOP
+ * @var {number}
+ */
+PDFJS.externalLinkTarget = (PDFJS.externalLinkTarget === undefined ?
+ PDFJS.LinkTarget.NONE : PDFJS.externalLinkTarget);
+
+/**
+ * Determines if we can eval strings as JS. Primarily used to improve
+ * performance for font rendering.
+ * @var {boolean}
+ */
+PDFJS.isEvalSupported = (PDFJS.isEvalSupported === undefined ?
+ true : PDFJS.isEvalSupported);
+
+/**
* Document initialization / loading parameters object.
*
* @typedef {Object} DocumentInitParameters
* @property {string} url - The URL of the PDF.
- * @property {TypedArray} data - A typed array with PDF data.
+ * @property {TypedArray|Array|string} data - Binary PDF data. Use typed arrays
+ * (Uint8Array) to improve the memory usage. If PDF data is BASE64-encoded,
+ * use atob() to convert it to a binary string first.
* @property {Object} httpHeaders - Basic authentication headers.
* @property {boolean} withCredentials - Indicates whether or not cross-site
* Access-Control requests should be made using credentials such as cookies
* or authorization headers. The default is false.
* @property {string} password - For decrypting password-protected PDFs.
* @property {TypedArray} initialData - A typed array with the first portion or
* all of the pdf data. Used by the extension since some data is already
* loaded before the switch to range requests.
+ * @property {number} length - The PDF file length. It's used for progress
+ * reports and range requests operations.
+ * @property {PDFDataRangeTransport} range
+ * @property {number} rangeChunkSize - Optional parameter to specify
+ * maximum number of bytes fetched per range request. The default value is
+ * 2^16 = 65536.
+ * @property {PDFWorker} worker - The worker that will be used for the loading
+ * and parsing of the PDF data.
*/
/**
* @typedef {Object} PDFDocumentStats
* @property {Array} streamTypes - Used stream types in the document (an item
@@ -1772,81 +2488,354 @@
* This is the main entry point for loading a PDF and interacting with it.
* NOTE: If a URL is used to fetch the PDF data a standard XMLHttpRequest(XHR)
* is used, which means it must follow the same origin rules that any XHR does
* e.g. No cross domain requests without CORS.
*
- * @param {string|TypedArray|DocumentInitParameters} source Can be a url to
- * where a PDF is located, a typed array (Uint8Array) already populated with
- * data or parameter object.
+ * @param {string|TypedArray|DocumentInitParameters|PDFDataRangeTransport} src
+ * Can be a url to where a PDF is located, a typed array (Uint8Array)
+ * already populated with data or parameter object.
*
- * @param {Object} pdfDataRangeTransport is optional. It is used if you want
- * to manually serve range requests for data in the PDF. See viewer.js for
- * an example of pdfDataRangeTransport's interface.
+ * @param {PDFDataRangeTransport} pdfDataRangeTransport (deprecated) It is used
+ * if you want to manually serve range requests for data in the PDF.
*
- * @param {function} passwordCallback is optional. It is used to request a
+ * @param {function} passwordCallback (deprecated) It is used to request a
* password if wrong or no password was provided. The callback receives two
* parameters: function that needs to be called with new password and reason
* (see {PasswordResponses}).
*
- * @param {function} progressCallback is optional. It is used to be able to
+ * @param {function} progressCallback (deprecated) It is used to be able to
* monitor the loading progress of the PDF file (necessary to implement e.g.
* a loading bar). The callback receives an {Object} with the properties:
* {number} loaded and {number} total.
*
- * @return {Promise} A promise that is resolved with {@link PDFDocumentProxy}
- * object.
+ * @return {PDFDocumentLoadingTask}
*/
-PDFJS.getDocument = function getDocument(source,
+PDFJS.getDocument = function getDocument(src,
pdfDataRangeTransport,
passwordCallback,
progressCallback) {
- var workerInitializedCapability, workerReadyCapability, transport;
+ var task = new PDFDocumentLoadingTask();
- if (typeof source === 'string') {
- source = { url: source };
- } else if (isArrayBuffer(source)) {
- source = { data: source };
- } else if (typeof source !== 'object') {
- error('Invalid parameter in getDocument, need either Uint8Array, ' +
- 'string or a parameter object');
+ // Support of the obsolete arguments (for compatibility with API v1.0)
+ if (arguments.length > 1) {
+ deprecated('getDocument is called with pdfDataRangeTransport, ' +
+ 'passwordCallback or progressCallback argument');
}
+ if (pdfDataRangeTransport) {
+ if (!(pdfDataRangeTransport instanceof PDFDataRangeTransport)) {
+ // Not a PDFDataRangeTransport instance, trying to add missing properties.
+ pdfDataRangeTransport = Object.create(pdfDataRangeTransport);
+ pdfDataRangeTransport.length = src.length;
+ pdfDataRangeTransport.initialData = src.initialData;
+ if (!pdfDataRangeTransport.abort) {
+ pdfDataRangeTransport.abort = function () {};
+ }
+ }
+ src = Object.create(src);
+ src.range = pdfDataRangeTransport;
+ }
+ task.onPassword = passwordCallback || null;
+ task.onProgress = progressCallback || null;
- if (!source.url && !source.data) {
- error('Invalid parameter array, need either .data or .url');
+ var source;
+ if (typeof src === 'string') {
+ source = { url: src };
+ } else if (isArrayBuffer(src)) {
+ source = { data: src };
+ } else if (src instanceof PDFDataRangeTransport) {
+ source = { range: src };
+ } else {
+ if (typeof src !== 'object') {
+ error('Invalid parameter in getDocument, need either Uint8Array, ' +
+ 'string or a parameter object');
+ }
+ if (!src.url && !src.data && !src.range) {
+ error('Invalid parameter object: need either .data, .range or .url');
+ }
+
+ source = src;
}
- // copy/use all keys as is except 'url' -- full path is required
var params = {};
+ var rangeTransport = null;
+ var worker = null;
for (var key in source) {
if (key === 'url' && typeof window !== 'undefined') {
+ // The full path is required in the 'url' field.
params[key] = combineUrl(window.location.href, source[key]);
continue;
+ } else if (key === 'range') {
+ rangeTransport = source[key];
+ continue;
+ } else if (key === 'worker') {
+ worker = source[key];
+ continue;
+ } else if (key === 'data' && !(source[key] instanceof Uint8Array)) {
+ // Converting string or array-like data to Uint8Array.
+ var pdfBytes = source[key];
+ if (typeof pdfBytes === 'string') {
+ params[key] = stringToBytes(pdfBytes);
+ } else if (typeof pdfBytes === 'object' && pdfBytes !== null &&
+ !isNaN(pdfBytes.length)) {
+ params[key] = new Uint8Array(pdfBytes);
+ } else if (isArrayBuffer(pdfBytes)) {
+ params[key] = new Uint8Array(pdfBytes);
+ } else {
+ error('Invalid PDF binary data: either typed array, string or ' +
+ 'array-like object is expected in the data property.');
+ }
+ continue;
}
params[key] = source[key];
}
- workerInitializedCapability = createPromiseCapability();
- workerReadyCapability = createPromiseCapability();
- transport = new WorkerTransport(workerInitializedCapability,
- workerReadyCapability, pdfDataRangeTransport,
- progressCallback);
- workerInitializedCapability.promise.then(function transportInitialized() {
- transport.passwordCallback = passwordCallback;
- transport.fetchDocument(params);
- });
- return workerReadyCapability.promise;
+ params.rangeChunkSize = params.rangeChunkSize || DEFAULT_RANGE_CHUNK_SIZE;
+
+ if (!worker) {
+ // Worker was not provided -- creating and owning our own.
+ worker = new PDFWorker();
+ task._worker = worker;
+ }
+ var docId = task.docId;
+ worker.promise.then(function () {
+ if (task.destroyed) {
+ throw new Error('Loading aborted');
+ }
+ return _fetchDocument(worker, params, rangeTransport, docId).then(
+ function (workerId) {
+ if (task.destroyed) {
+ throw new Error('Loading aborted');
+ }
+ var messageHandler = new MessageHandler(docId, workerId, worker.port);
+ messageHandler.send('Ready', null);
+ var transport = new WorkerTransport(messageHandler, task, rangeTransport);
+ task._transport = transport;
+ });
+ }, task._capability.reject);
+
+ return task;
};
/**
+ * Starts fetching of specified PDF document/data.
+ * @param {PDFWorker} worker
+ * @param {Object} source
+ * @param {PDFDataRangeTransport} pdfDataRangeTransport
+ * @param {string} docId Unique document id, used as MessageHandler id.
+ * @returns {Promise} The promise, which is resolved when worker id of
+ * MessageHandler is known.
+ * @private
+ */
+function _fetchDocument(worker, source, pdfDataRangeTransport, docId) {
+ if (worker.destroyed) {
+ return Promise.reject(new Error('Worker was destroyed'));
+ }
+
+ source.disableAutoFetch = PDFJS.disableAutoFetch;
+ source.disableStream = PDFJS.disableStream;
+ source.chunkedViewerLoading = !!pdfDataRangeTransport;
+ if (pdfDataRangeTransport) {
+ source.length = pdfDataRangeTransport.length;
+ source.initialData = pdfDataRangeTransport.initialData;
+ }
+ return worker.messageHandler.sendWithPromise('GetDocRequest', {
+ docId: docId,
+ source: source,
+ disableRange: PDFJS.disableRange,
+ maxImageSize: PDFJS.maxImageSize,
+ cMapUrl: PDFJS.cMapUrl,
+ cMapPacked: PDFJS.cMapPacked,
+ disableFontFace: PDFJS.disableFontFace,
+ disableCreateObjectURL: PDFJS.disableCreateObjectURL,
+ verbosity: PDFJS.verbosity
+ }).then(function (workerId) {
+ if (worker.destroyed) {
+ throw new Error('Worker was destroyed');
+ }
+ return workerId;
+ });
+}
+
+/**
+ * PDF document loading operation.
+ * @class
+ * @alias PDFDocumentLoadingTask
+ */
+var PDFDocumentLoadingTask = (function PDFDocumentLoadingTaskClosure() {
+ var nextDocumentId = 0;
+
+ /** @constructs PDFDocumentLoadingTask */
+ function PDFDocumentLoadingTask() {
+ this._capability = createPromiseCapability();
+ this._transport = null;
+ this._worker = null;
+
+ /**
+ * Unique document loading task id -- used in MessageHandlers.
+ * @type {string}
+ */
+ this.docId = 'd' + (nextDocumentId++);
+
+ /**
+ * Shows if loading task is destroyed.
+ * @type {boolean}
+ */
+ this.destroyed = false;
+
+ /**
+ * Callback to request a password if wrong or no password was provided.
+ * The callback receives two parameters: function that needs to be called
+ * with new password and reason (see {PasswordResponses}).
+ */
+ this.onPassword = null;
+
+ /**
+ * Callback to be able to monitor the loading progress of the PDF file
+ * (necessary to implement e.g. a loading bar). The callback receives
+ * an {Object} with the properties: {number} loaded and {number} total.
+ */
+ this.onProgress = null;
+
+ /**
+ * Callback to when unsupported feature is used. The callback receives
+ * an {PDFJS.UNSUPPORTED_FEATURES} argument.
+ */
+ this.onUnsupportedFeature = null;
+ }
+
+ PDFDocumentLoadingTask.prototype =
+ /** @lends PDFDocumentLoadingTask.prototype */ {
+ /**
+ * @return {Promise}
+ */
+ get promise() {
+ return this._capability.promise;
+ },
+
+ /**
+ * Aborts all network requests and destroys worker.
+ * @return {Promise} A promise that is resolved after destruction activity
+ * is completed.
+ */
+ destroy: function () {
+ this.destroyed = true;
+
+ var transportDestroyed = !this._transport ? Promise.resolve() :
+ this._transport.destroy();
+ return transportDestroyed.then(function () {
+ this._transport = null;
+ if (this._worker) {
+ this._worker.destroy();
+ this._worker = null;
+ }
+ }.bind(this));
+ },
+
+ /**
+ * Registers callbacks to indicate the document loading completion.
+ *
+ * @param {function} onFulfilled The callback for the loading completion.
+ * @param {function} onRejected The callback for the loading failure.
+ * @return {Promise} A promise that is resolved after the onFulfilled or
+ * onRejected callback.
+ */
+ then: function PDFDocumentLoadingTask_then(onFulfilled, onRejected) {
+ return this.promise.then.apply(this.promise, arguments);
+ }
+ };
+
+ return PDFDocumentLoadingTask;
+})();
+
+/**
+ * Abstract class to support range requests file loading.
+ * @class
+ * @alias PDFJS.PDFDataRangeTransport
+ * @param {number} length
+ * @param {Uint8Array} initialData
+ */
+var PDFDataRangeTransport = (function pdfDataRangeTransportClosure() {
+ function PDFDataRangeTransport(length, initialData) {
+ this.length = length;
+ this.initialData = initialData;
+
+ this._rangeListeners = [];
+ this._progressListeners = [];
+ this._progressiveReadListeners = [];
+ this._readyCapability = createPromiseCapability();
+ }
+ PDFDataRangeTransport.prototype =
+ /** @lends PDFDataRangeTransport.prototype */ {
+ addRangeListener:
+ function PDFDataRangeTransport_addRangeListener(listener) {
+ this._rangeListeners.push(listener);
+ },
+
+ addProgressListener:
+ function PDFDataRangeTransport_addProgressListener(listener) {
+ this._progressListeners.push(listener);
+ },
+
+ addProgressiveReadListener:
+ function PDFDataRangeTransport_addProgressiveReadListener(listener) {
+ this._progressiveReadListeners.push(listener);
+ },
+
+ onDataRange: function PDFDataRangeTransport_onDataRange(begin, chunk) {
+ var listeners = this._rangeListeners;
+ for (var i = 0, n = listeners.length; i < n; ++i) {
+ listeners[i](begin, chunk);
+ }
+ },
+
+ onDataProgress: function PDFDataRangeTransport_onDataProgress(loaded) {
+ this._readyCapability.promise.then(function () {
+ var listeners = this._progressListeners;
+ for (var i = 0, n = listeners.length; i < n; ++i) {
+ listeners[i](loaded);
+ }
+ }.bind(this));
+ },
+
+ onDataProgressiveRead:
+ function PDFDataRangeTransport_onDataProgress(chunk) {
+ this._readyCapability.promise.then(function () {
+ var listeners = this._progressiveReadListeners;
+ for (var i = 0, n = listeners.length; i < n; ++i) {
+ listeners[i](chunk);
+ }
+ }.bind(this));
+ },
+
+ transportReady: function PDFDataRangeTransport_transportReady() {
+ this._readyCapability.resolve();
+ },
+
+ requestDataRange:
+ function PDFDataRangeTransport_requestDataRange(begin, end) {
+ throw new Error('Abstract method PDFDataRangeTransport.requestDataRange');
+ },
+
+ abort: function PDFDataRangeTransport_abort() {
+ }
+ };
+ return PDFDataRangeTransport;
+})();
+
+PDFJS.PDFDataRangeTransport = PDFDataRangeTransport;
+
+/**
* Proxy to a PDFDocument in the worker thread. Also, contains commonly used
* properties that can be read synchronously.
* @class
+ * @alias PDFDocumentProxy
*/
var PDFDocumentProxy = (function PDFDocumentProxyClosure() {
- function PDFDocumentProxy(pdfInfo, transport) {
+ function PDFDocumentProxy(pdfInfo, transport, loadingTask) {
this.pdfInfo = pdfInfo;
this.transport = transport;
+ this.loadingTask = loadingTask;
}
PDFDocumentProxy.prototype = /** @lends PDFDocumentProxy.prototype */ {
/**
* @return {number} Total number of pages the PDF contains.
*/
@@ -1949,11 +2938,11 @@
*/
getDownloadInfo: function PDFDocumentProxy_getDownloadInfo() {
return this.transport.downloadInfoCapability.promise;
},
/**
- * @returns {Promise} A promise this is resolved with current stats about
+ * @return {Promise} A promise this is resolved with current stats about
* document structures (see {@link PDFDocumentStats}).
*/
getStats: function PDFDocumentProxy_getStats() {
return this.transport.getStats();
},
@@ -1965,17 +2954,25 @@
},
/**
* Destroys current document instance and terminates worker.
*/
destroy: function PDFDocumentProxy_destroy() {
- this.transport.destroy();
+ return this.loadingTask.destroy();
}
};
return PDFDocumentProxy;
})();
/**
+ * Page getTextContent parameters.
+ *
+ * @typedef {Object} getTextContentParameters
+ * @param {boolean} normalizeWhitespace - replaces all occurrences of
+ * whitespace with standard spaces (0x20). The default value is `false`.
+ */
+
+/**
* Page text content.
*
* @typedef {Object} TextContent
* @property {array} items - array of {@link TextItem}
* @property {Object} styles - {@link TextStyles} objects, indexed by font
@@ -2003,26 +3000,38 @@
* @property {boolean} vertical - text is in vertical mode.
* @property {string} fontFamily - possible font family
*/
/**
+ * Page annotation parameters.
+ *
+ * @typedef {Object} GetAnnotationsParameters
+ * @param {string} intent - Determines the annotations that will be fetched,
+ * can be either 'display' (viewable annotations) or 'print'
+ * (printable annotations).
+ * If the parameter is omitted, all annotations are fetched.
+ */
+
+/**
* Page render parameters.
*
* @typedef {Object} RenderParameters
* @property {Object} canvasContext - A 2D context of a DOM Canvas object.
* @property {PDFJS.PageViewport} viewport - Rendering viewport obtained by
* calling of PDFPage.getViewport method.
* @property {string} intent - Rendering intent, can be 'display' or 'print'
* (default value is 'display').
+ * @property {Array} transform - (optional) Additional transform, applied
+ * just before viewport transform.
* @property {Object} imageLayer - (optional) An object that has beginLayout,
* endLayout and appendImage functions.
- * @property {function} continueCallback - (optional) A function that will be
+ * @property {function} continueCallback - (deprecated) A function that will be
* called each time the rendering is paused. To continue
* rendering call the function that is the first argument
* to the callback.
*/
-
+
/**
* PDF page operator list.
*
* @typedef {Object} PDFOperatorList
* @property {Array} fnArray - Array containing the operator functions.
@@ -2031,10 +3040,11 @@
*/
/**
* Proxy to a PDFPage in the worker thread.
* @class
+ * @alias PDFPageProxy
*/
var PDFPageProxy = (function PDFPageProxyClosure() {
function PDFPageProxy(pageIndex, pageInfo, transport) {
this.pageIndex = pageIndex;
this.pageInfo = pageInfo;
@@ -2042,12 +3052,13 @@
this.stats = new StatTimer();
this.stats.enabled = !!globalScope.PDFJS.enableStats;
this.commonObjs = transport.commonObjs;
this.objs = new PDFObjects();
this.cleanupAfterRender = false;
- this.pendingDestroy = false;
+ this.pendingCleanup = false;
this.intentStates = {};
+ this.destroyed = false;
}
PDFPageProxy.prototype = /** @lends PDFPageProxy.prototype */ {
/**
* @return {number} Page number of the page. First page is 1.
*/
@@ -2086,21 +3097,23 @@
rotate = this.rotate;
}
return new PDFJS.PageViewport(this.view, scale, rotate, 0, 0);
},
/**
+ * @param {GetAnnotationsParameters} params - Annotation parameters.
* @return {Promise} A promise that is resolved with an {Array} of the
* annotation objects.
*/
- getAnnotations: function PDFPageProxy_getAnnotations() {
- if (this.annotationsPromise) {
- return this.annotationsPromise;
- }
+ getAnnotations: function PDFPageProxy_getAnnotations(params) {
+ var intent = (params && params.intent) || null;
- var promise = this.transport.getAnnotations(this.pageIndex);
- this.annotationsPromise = promise;
- return promise;
+ if (!this.annotationsPromise || this.annotationsIntent !== intent) {
+ this.annotationsPromise = this.transport.getAnnotations(this.pageIndex,
+ intent);
+ this.annotationsIntent = intent;
+ }
+ return this.annotationsPromise;
},
/**
* Begins the process of rendering a page to the desired context.
* @param {RenderParameters} params Page render parameters.
* @return {RenderTask} An object that contains the promise, which
@@ -2110,11 +3123,11 @@
var stats = this.stats;
stats.time('Overall');
// If there was a pending destroy cancel it so no cleanup happens during
// this call to render.
- this.pendingDestroy = false;
+ this.pendingCleanup = false;
var renderingIntent = (params.intent === 'print' ? 'print' : 'display');
if (!this.intentStates[renderingIntent]) {
this.intentStates[renderingIntent] = {};
@@ -2142,20 +3155,27 @@
var internalRenderTask = new InternalRenderTask(complete, params,
this.objs,
this.commonObjs,
intentState.operatorList,
this.pageNumber);
+ internalRenderTask.useRequestAnimationFrame = renderingIntent !== 'print';
if (!intentState.renderTasks) {
intentState.renderTasks = [];
}
intentState.renderTasks.push(internalRenderTask);
- var renderTask = new RenderTask(internalRenderTask);
+ var renderTask = internalRenderTask.task;
+ // Obsolete parameter support
+ if (params.continueCallback) {
+ deprecated('render is used with continueCallback parameter');
+ renderTask.onContinue = params.continueCallback;
+ }
+
var self = this;
intentState.displayReadyCapability.promise.then(
function pageDisplayReadyPromise(transparency) {
- if (self.pendingDestroy) {
+ if (self.pendingCleanup) {
complete();
return;
}
stats.time('Rendering');
internalRenderTask.initalizeGraphics(transparency);
@@ -2171,13 +3191,13 @@
if (i >= 0) {
intentState.renderTasks.splice(i, 1);
}
if (self.cleanupAfterRender) {
- self.pendingDestroy = true;
+ self.pendingCleanup = true;
}
- self._tryDestroy();
+ self._tryCleanup();
if (error) {
internalRenderTask.capability.reject(error);
} else {
internalRenderTask.capability.resolve();
@@ -2226,32 +3246,68 @@
}
return intentState.opListReadCapability.promise;
},
/**
+ * @param {getTextContentParameters} params - getTextContent parameters.
* @return {Promise} That is resolved a {@link TextContent}
* object that represent the page text content.
*/
- getTextContent: function PDFPageProxy_getTextContent() {
+ getTextContent: function PDFPageProxy_getTextContent(params) {
+ var normalizeWhitespace = (params && params.normalizeWhitespace) || false;
+
return this.transport.messageHandler.sendWithPromise('GetTextContent', {
- pageIndex: this.pageNumber - 1
+ pageIndex: this.pageNumber - 1,
+ normalizeWhitespace: normalizeWhitespace,
});
},
+
/**
- * Destroys resources allocated by the page.
+ * Destroys page object.
*/
- destroy: function PDFPageProxy_destroy() {
- this.pendingDestroy = true;
- this._tryDestroy();
+ _destroy: function PDFPageProxy_destroy() {
+ this.destroyed = true;
+ this.transport.pageCache[this.pageIndex] = null;
+
+ var waitOn = [];
+ Object.keys(this.intentStates).forEach(function(intent) {
+ var intentState = this.intentStates[intent];
+ intentState.renderTasks.forEach(function(renderTask) {
+ var renderCompleted = renderTask.capability.promise.
+ catch(function () {}); // ignoring failures
+ waitOn.push(renderCompleted);
+ renderTask.cancel();
+ });
+ }, this);
+ this.objs.clear();
+ this.annotationsPromise = null;
+ this.pendingCleanup = false;
+ return Promise.all(waitOn);
},
+
/**
+ * Cleans up resources allocated by the page. (deprecated)
+ */
+ destroy: function() {
+ deprecated('page destroy method, use cleanup() instead');
+ this.cleanup();
+ },
+
+ /**
+ * Cleans up resources allocated by the page.
+ */
+ cleanup: function PDFPageProxy_cleanup() {
+ this.pendingCleanup = true;
+ this._tryCleanup();
+ },
+ /**
* For internal use only. Attempts to clean up if rendering is in a state
* where that's possible.
* @ignore
*/
- _tryDestroy: function PDFPageProxy__destroy() {
- if (!this.pendingDestroy ||
+ _tryCleanup: function PDFPageProxy_tryCleanup() {
+ if (!this.pendingCleanup ||
Object.keys(this.intentStates).some(function(intent) {
var intentState = this.intentStates[intent];
return (intentState.renderTasks.length !== 0 ||
intentState.receivingOperatorList);
}, this)) {
@@ -2261,11 +3317,11 @@
Object.keys(this.intentStates).forEach(function(intent) {
delete this.intentStates[intent];
}, this);
this.objs.clear();
this.annotationsPromise = null;
- this.pendingDestroy = false;
+ this.pendingCleanup = false;
},
/**
* For internal use only.
* @ignore
*/
@@ -2299,92 +3355,201 @@
intentState.renderTasks[i].operatorListChanged();
}
if (operatorListChunk.lastChunk) {
intentState.receivingOperatorList = false;
- this._tryDestroy();
+ this._tryCleanup();
}
}
};
return PDFPageProxy;
})();
/**
+ * PDF.js web worker abstraction, it controls instantiation of PDF documents and
+ * WorkerTransport for them. If creation of a web worker is not possible,
+ * a "fake" worker will be used instead.
+ * @class
+ */
+var PDFWorker = (function PDFWorkerClosure() {
+ var nextFakeWorkerId = 0;
+
+ // Loads worker code into main thread.
+ function setupFakeWorkerGlobal() {
+ if (!PDFJS.fakeWorkerFilesLoadedCapability) {
+ PDFJS.fakeWorkerFilesLoadedCapability = createPromiseCapability();
+ // In the developer build load worker_loader which in turn loads all the
+ // other files and resolves the promise. In production only the
+ // pdf.worker.js file is needed.
+ PDFJS.fakeWorkerFilesLoadedCapability.resolve();
+ }
+ return PDFJS.fakeWorkerFilesLoadedCapability.promise;
+ }
+
+ function PDFWorker(name) {
+ this.name = name;
+ this.destroyed = false;
+
+ this._readyCapability = createPromiseCapability();
+ this._port = null;
+ this._webWorker = null;
+ this._messageHandler = null;
+ this._initialize();
+ }
+
+ PDFWorker.prototype = /** @lends PDFWorker.prototype */ {
+ get promise() {
+ return this._readyCapability.promise;
+ },
+
+ get port() {
+ return this._port;
+ },
+
+ get messageHandler() {
+ return this._messageHandler;
+ },
+
+ _initialize: function PDFWorker_initialize() {
+ // If worker support isn't disabled explicit and the browser has worker
+ // support, create a new web worker and test if it/the browser fullfills
+ // all requirements to run parts of pdf.js in a web worker.
+ // Right now, the requirement is, that an Uint8Array is still an
+ // Uint8Array as it arrives on the worker. (Chrome added this with v.15.)
+ // Either workers are disabled, not supported or have thrown an exception.
+ // Thus, we fallback to a faked worker.
+ this._setupFakeWorker();
+ },
+
+ _setupFakeWorker: function PDFWorker_setupFakeWorker() {
+ warn('Setting up fake worker.');
+ globalScope.PDFJS.disableWorker = true;
+
+ setupFakeWorkerGlobal().then(function () {
+ if (this.destroyed) {
+ this._readyCapability.reject(new Error('Worker was destroyed'));
+ return;
+ }
+
+ // If we don't use a worker, just post/sendMessage to the main thread.
+ var port = {
+ _listeners: [],
+ postMessage: function (obj) {
+ var e = {data: obj};
+ this._listeners.forEach(function (listener) {
+ listener.call(this, e);
+ }, this);
+ },
+ addEventListener: function (name, listener) {
+ this._listeners.push(listener);
+ },
+ removeEventListener: function (name, listener) {
+ var i = this._listeners.indexOf(listener);
+ this._listeners.splice(i, 1);
+ },
+ terminate: function () {}
+ };
+ this._port = port;
+
+ // All fake workers use the same port, making id unique.
+ var id = 'fake' + (nextFakeWorkerId++);
+
+ // If the main thread is our worker, setup the handling for the
+ // messages -- the main thread sends to it self.
+ var workerHandler = new MessageHandler(id + '_worker', id, port);
+ PDFJS.WorkerMessageHandler.setup(workerHandler, port);
+
+ var messageHandler = new MessageHandler(id, id + '_worker', port);
+ this._messageHandler = messageHandler;
+ this._readyCapability.resolve();
+ }.bind(this));
+ },
+
+ /**
+ * Destroys the worker instance.
+ */
+ destroy: function PDFWorker_destroy() {
+ this.destroyed = true;
+ if (this._webWorker) {
+ // We need to terminate only web worker created resource.
+ this._webWorker.terminate();
+ this._webWorker = null;
+ }
+ this._port = null;
+ if (this._messageHandler) {
+ this._messageHandler.destroy();
+ this._messageHandler = null;
+ }
+ }
+ };
+
+ return PDFWorker;
+})();
+PDFJS.PDFWorker = PDFWorker;
+
+/**
* For internal use only.
* @ignore
*/
var WorkerTransport = (function WorkerTransportClosure() {
- function WorkerTransport(workerInitializedCapability, workerReadyCapability,
- pdfDataRangeTransport, progressCallback) {
+ function WorkerTransport(messageHandler, loadingTask, pdfDataRangeTransport) {
+ this.messageHandler = messageHandler;
+ this.loadingTask = loadingTask;
this.pdfDataRangeTransport = pdfDataRangeTransport;
-
- this.workerInitializedCapability = workerInitializedCapability;
- this.workerReadyCapability = workerReadyCapability;
- this.progressCallback = progressCallback;
this.commonObjs = new PDFObjects();
+ this.fontLoader = new FontLoader(loadingTask.docId);
+ this.destroyed = false;
+ this.destroyCapability = null;
+
this.pageCache = [];
this.pagePromises = [];
this.downloadInfoCapability = createPromiseCapability();
- this.passwordCallback = null;
- // If worker support isn't disabled explicit and the browser has worker
- // support, create a new web worker and test if it/the browser fullfills
- // all requirements to run parts of pdf.js in a web worker.
- // Right now, the requirement is, that an Uint8Array is still an Uint8Array
- // as it arrives on the worker. Chrome added this with version 15.
- // Either workers are disabled, not supported or have thrown an exception.
- // Thus, we fallback to a faked worker.
- this.setupFakeWorker();
+ this.setupMessageHandler();
}
WorkerTransport.prototype = {
destroy: function WorkerTransport_destroy() {
+ if (this.destroyCapability) {
+ return this.destroyCapability.promise;
+ }
+
+ this.destroyed = true;
+ this.destroyCapability = createPromiseCapability();
+
+ var waitOn = [];
+ // We need to wait for all renderings to be completed, e.g.
+ // timeout/rAF can take a long time.
+ this.pageCache.forEach(function (page) {
+ if (page) {
+ waitOn.push(page._destroy());
+ }
+ });
this.pageCache = [];
this.pagePromises = [];
var self = this;
- this.messageHandler.sendWithPromise('Terminate', null).then(function () {
- FontLoader.clear();
- if (self.worker) {
- self.worker.terminate();
+ // We also need to wait for the worker to finish its long running tasks.
+ var terminated = this.messageHandler.sendWithPromise('Terminate', null);
+ waitOn.push(terminated);
+ Promise.all(waitOn).then(function () {
+ self.fontLoader.clear();
+ if (self.pdfDataRangeTransport) {
+ self.pdfDataRangeTransport.abort();
+ self.pdfDataRangeTransport = null;
}
- });
+ if (self.messageHandler) {
+ self.messageHandler.destroy();
+ self.messageHandler = null;
+ }
+ self.destroyCapability.resolve();
+ }, this.destroyCapability.reject);
+ return this.destroyCapability.promise;
},
- setupFakeWorker: function WorkerTransport_setupFakeWorker() {
- globalScope.PDFJS.disableWorker = true;
-
- if (!PDFJS.fakeWorkerFilesLoadedCapability) {
- PDFJS.fakeWorkerFilesLoadedCapability = createPromiseCapability();
- // In the developer build load worker_loader which in turn loads all the
- // other files and resolves the promise. In production only the
- // pdf.worker.js file is needed.
- PDFJS.fakeWorkerFilesLoadedCapability.resolve();
- }
- PDFJS.fakeWorkerFilesLoadedCapability.promise.then(function () {
- warn('Setting up fake worker.');
- // If we don't use a worker, just post/sendMessage to the main thread.
- var fakeWorker = {
- postMessage: function WorkerTransport_postMessage(obj) {
- fakeWorker.onmessage({data: obj});
- },
- terminate: function WorkerTransport_terminate() {}
- };
-
- var messageHandler = new MessageHandler('main', fakeWorker);
- this.setupMessageHandler(messageHandler);
-
- // If the main thread is our worker, setup the handling for the messages
- // the main thread sends to it self.
- PDFJS.WorkerMessageHandler.setup(messageHandler);
-
- this.workerInitializedCapability.resolve();
- }.bind(this));
- },
-
setupMessageHandler:
- function WorkerTransport_setupMessageHandler(messageHandler) {
- this.messageHandler = messageHandler;
+ function WorkerTransport_setupMessageHandler() {
+ var messageHandler = this.messageHandler;
function updatePassword(password) {
messageHandler.send('UpdatePassword', password);
}
@@ -2416,54 +3581,57 @@
}
messageHandler.on('GetDoc', function transportDoc(data) {
var pdfInfo = data.pdfInfo;
this.numPages = data.pdfInfo.numPages;
- var pdfDocument = new PDFDocumentProxy(pdfInfo, this);
+ var loadingTask = this.loadingTask;
+ var pdfDocument = new PDFDocumentProxy(pdfInfo, this, loadingTask);
this.pdfDocument = pdfDocument;
- this.workerReadyCapability.resolve(pdfDocument);
+ loadingTask._capability.resolve(pdfDocument);
}, this);
messageHandler.on('NeedPassword',
function transportNeedPassword(exception) {
- if (this.passwordCallback) {
- return this.passwordCallback(updatePassword,
- PasswordResponses.NEED_PASSWORD);
+ var loadingTask = this.loadingTask;
+ if (loadingTask.onPassword) {
+ return loadingTask.onPassword(updatePassword,
+ PasswordResponses.NEED_PASSWORD);
}
- this.workerReadyCapability.reject(
+ loadingTask._capability.reject(
new PasswordException(exception.message, exception.code));
}, this);
messageHandler.on('IncorrectPassword',
function transportIncorrectPassword(exception) {
- if (this.passwordCallback) {
- return this.passwordCallback(updatePassword,
- PasswordResponses.INCORRECT_PASSWORD);
+ var loadingTask = this.loadingTask;
+ if (loadingTask.onPassword) {
+ return loadingTask.onPassword(updatePassword,
+ PasswordResponses.INCORRECT_PASSWORD);
}
- this.workerReadyCapability.reject(
+ loadingTask._capability.reject(
new PasswordException(exception.message, exception.code));
}, this);
messageHandler.on('InvalidPDF', function transportInvalidPDF(exception) {
- this.workerReadyCapability.reject(
+ this.loadingTask._capability.reject(
new InvalidPDFException(exception.message));
}, this);
messageHandler.on('MissingPDF', function transportMissingPDF(exception) {
- this.workerReadyCapability.reject(
+ this.loadingTask._capability.reject(
new MissingPDFException(exception.message));
}, this);
messageHandler.on('UnexpectedResponse',
function transportUnexpectedResponse(exception) {
- this.workerReadyCapability.reject(
+ this.loadingTask._capability.reject(
new UnexpectedResponseException(exception.message, exception.status));
}, this);
messageHandler.on('UnknownError',
function transportUnknownError(exception) {
- this.workerReadyCapability.reject(
+ this.loadingTask._capability.reject(
new UnknownErrorException(exception.message, exception.details));
}, this);
messageHandler.on('DataLoaded', function transportPage(data) {
this.downloadInfoCapability.resolve(data);
@@ -2474,23 +3642,33 @@
this.pdfDataRangeTransport.transportReady();
}
}, this);
messageHandler.on('StartRenderPage', function transportRender(data) {
+ if (this.destroyed) {
+ return; // Ignore any pending requests if the worker was terminated.
+ }
var page = this.pageCache[data.pageIndex];
page.stats.timeEnd('Page Request');
page._startRenderPage(data.transparency, data.intent);
}, this);
messageHandler.on('RenderPageChunk', function transportRender(data) {
+ if (this.destroyed) {
+ return; // Ignore any pending requests if the worker was terminated.
+ }
var page = this.pageCache[data.pageIndex];
page._renderPageChunk(data.operatorList, data.intent);
}, this);
messageHandler.on('commonobj', function transportObj(data) {
+ if (this.destroyed) {
+ return; // Ignore any pending requests if the worker was terminated.
+ }
+
var id = data[0];
var type = data[1];
if (this.commonObjs.hasData(id)) {
return;
}
@@ -2507,11 +3685,11 @@
break;
} else {
font = new FontFaceObject(exportedData);
}
- FontLoader.bind(
+ this.fontLoader.bind(
[font],
function fontReady(fontObjs) {
this.commonObjs.resolve(id, font);
}.bind(this)
);
@@ -2523,10 +3701,14 @@
error('Got unknown common object type ' + type);
}
}, this);
messageHandler.on('obj', function transportObj(data) {
+ if (this.destroyed) {
+ return; // Ignore any pending requests if the worker was terminated.
+ }
+
var id = data[0];
var pageIndex = data[1];
var type = data[2];
var pageProxy = this.pageCache[pageIndex];
var imageData;
@@ -2554,29 +3736,55 @@
error('Got unknown object type ' + type);
}
}, this);
messageHandler.on('DocProgress', function transportDocProgress(data) {
- if (this.progressCallback) {
- this.progressCallback({
+ if (this.destroyed) {
+ return; // Ignore any pending requests if the worker was terminated.
+ }
+
+ var loadingTask = this.loadingTask;
+ if (loadingTask.onProgress) {
+ loadingTask.onProgress({
loaded: data.loaded,
total: data.total
});
}
}, this);
messageHandler.on('PageError', function transportError(data) {
+ if (this.destroyed) {
+ return; // Ignore any pending requests if the worker was terminated.
+ }
+
var page = this.pageCache[data.pageNum - 1];
var intentState = page.intentStates[data.intent];
if (intentState.displayReadyCapability) {
intentState.displayReadyCapability.reject(data.error);
} else {
error(data.error);
}
}, this);
+ messageHandler.on('UnsupportedFeature',
+ function transportUnsupportedFeature(data) {
+ if (this.destroyed) {
+ return; // Ignore any pending requests if the worker was terminated.
+ }
+ var featureId = data.featureId;
+ var loadingTask = this.loadingTask;
+ if (loadingTask.onUnsupportedFeature) {
+ loadingTask.onUnsupportedFeature(featureId);
+ }
+ PDFJS.UnsupportedManager.notify(featureId);
+ }, this);
+
messageHandler.on('JpegDecode', function(data) {
+ if (this.destroyed) {
+ return Promise.reject('Worker was terminated');
+ }
+
var imageUrl = data[0];
var components = data[1];
if (components !== 3 && components !== 1) {
return Promise.reject(
new Error('Only 3 components or 1 component can be returned'));
@@ -2612,29 +3820,13 @@
img.onerror = function () {
reject(new Error('JpegDecode failed to load image'));
};
img.src = imageUrl;
});
- });
+ }, this);
},
- fetchDocument: function WorkerTransport_fetchDocument(source) {
- source.disableAutoFetch = PDFJS.disableAutoFetch;
- source.disableStream = PDFJS.disableStream;
- source.chunkedViewerLoading = !!this.pdfDataRangeTransport;
- this.messageHandler.send('GetDocRequest', {
- source: source,
- disableRange: PDFJS.disableRange,
- maxImageSize: PDFJS.maxImageSize,
- cMapUrl: PDFJS.cMapUrl,
- cMapPacked: PDFJS.cMapPacked,
- disableFontFace: PDFJS.disableFontFace,
- disableCreateObjectURL: PDFJS.disableCreateObjectURL,
- verbosity: PDFJS.verbosity
- });
- },
-
getData: function WorkerTransport_getData() {
return this.messageHandler.sendWithPromise('GetData', null);
},
getPage: function WorkerTransport_getPage(pageNumber, capability) {
@@ -2648,10 +3840,13 @@
return this.pagePromises[pageIndex];
}
var promise = this.messageHandler.sendWithPromise('GetPage', {
pageIndex: pageIndex
}).then(function (pageInfo) {
+ if (this.destroyed) {
+ throw new Error('Transport destroyed');
+ }
var page = new PDFPageProxy(pageIndex, pageInfo, this);
this.pageCache[pageIndex] = page;
return page;
}.bind(this));
this.pagePromises[pageIndex] = promise;
@@ -2660,21 +3855,23 @@
getPageIndex: function WorkerTransport_getPageIndexByRef(ref) {
return this.messageHandler.sendWithPromise('GetPageIndex', { ref: ref });
},
- getAnnotations: function WorkerTransport_getAnnotations(pageIndex) {
- return this.messageHandler.sendWithPromise('GetAnnotations',
- { pageIndex: pageIndex });
+ getAnnotations: function WorkerTransport_getAnnotations(pageIndex, intent) {
+ return this.messageHandler.sendWithPromise('GetAnnotations', {
+ pageIndex: pageIndex,
+ intent: intent,
+ });
},
getDestinations: function WorkerTransport_getDestinations() {
return this.messageHandler.sendWithPromise('GetDestinations', null);
},
getDestination: function WorkerTransport_getDestination(id) {
- return this.messageHandler.sendWithPromise('GetDestination', { id: id } );
+ return this.messageHandler.sendWithPromise('GetDestination', { id: id });
},
getAttachments: function WorkerTransport_getAttachments() {
return this.messageHandler.sendWithPromise('GetAttachments', null);
},
@@ -2705,15 +3902,15 @@
this.messageHandler.sendWithPromise('Cleanup', null).
then(function endCleanup() {
for (var i = 0, ii = this.pageCache.length; i < ii; i++) {
var page = this.pageCache[i];
if (page) {
- page.destroy();
+ page.cleanup();
}
}
this.commonObjs.clear();
- FontLoader.clear();
+ this.fontLoader.clear();
}.bind(this));
}
};
return WorkerTransport;
@@ -2826,41 +4023,53 @@
})();
/**
* Allows controlling of the rendering tasks.
* @class
+ * @alias RenderTask
*/
var RenderTask = (function RenderTaskClosure() {
function RenderTask(internalRenderTask) {
- this.internalRenderTask = internalRenderTask;
+ this._internalRenderTask = internalRenderTask;
+
/**
- * Promise for rendering task completion.
- * @type {Promise}
+ * Callback for incremental rendering -- a function that will be called
+ * each time the rendering is paused. To continue rendering call the
+ * function that is the first argument to the callback.
+ * @type {function}
*/
- this.promise = this.internalRenderTask.capability.promise;
+ this.onContinue = null;
}
RenderTask.prototype = /** @lends RenderTask.prototype */ {
/**
+ * Promise for rendering task completion.
+ * @return {Promise}
+ */
+ get promise() {
+ return this._internalRenderTask.capability.promise;
+ },
+
+ /**
* Cancels the rendering task. If the task is currently rendering it will
* not be cancelled until graphics pauses with a timeout. The promise that
* this object extends will resolved when cancelled.
*/
cancel: function RenderTask_cancel() {
- this.internalRenderTask.cancel();
+ this._internalRenderTask.cancel();
},
/**
- * Registers callback to indicate the rendering task completion.
+ * Registers callbacks to indicate the rendering task completion.
*
* @param {function} onFulfilled The callback for the rendering completion.
* @param {function} onRejected The callback for the rendering failure.
* @return {Promise} A promise that is resolved after the onFulfilled or
* onRejected callback.
*/
then: function RenderTask_then(onFulfilled, onRejected) {
- return this.promise.then(onFulfilled, onRejected);
+ return this.promise.then.apply(this.promise, arguments);
}
};
return RenderTask;
})();
@@ -2881,12 +4090,14 @@
this.operatorList = operatorList;
this.pageNumber = pageNumber;
this.running = false;
this.graphicsReadyCallback = null;
this.graphicsReady = false;
+ this.useRequestAnimationFrame = false;
this.cancelled = false;
this.capability = createPromiseCapability();
+ this.task = new RenderTask(this);
// caching this-bound methods
this._continueBound = this._continue.bind(this);
this._scheduleNextBound = this._scheduleNext.bind(this);
this._nextBound = this._next.bind(this);
}
@@ -2908,11 +4119,11 @@
var params = this.params;
this.gfx = new CanvasGraphics(params.canvasContext, this.commonObjs,
this.objs, params.imageLayer);
- this.gfx.beginDrawing(params.viewport, transparency);
+ this.gfx.beginDrawing(params.transform, params.viewport, transparency);
this.operatorListIdx = 0;
this.graphicsReady = true;
if (this.graphicsReadyCallback) {
this.graphicsReadyCallback();
}
@@ -2945,19 +4156,23 @@
_continue: function InternalRenderTask__continue() {
this.running = true;
if (this.cancelled) {
return;
}
- if (this.params.continueCallback) {
- this.params.continueCallback(this._scheduleNextBound);
+ if (this.task.onContinue) {
+ this.task.onContinue.call(this.task, this._scheduleNextBound);
} else {
this._scheduleNext();
}
},
_scheduleNext: function InternalRenderTask__scheduleNext() {
- window.requestAnimationFrame(this._nextBound);
+ if (this.useRequestAnimationFrame) {
+ window.requestAnimationFrame(this._nextBound);
+ } else {
+ Promise.resolve(undefined).then(this._nextBound);
+ }
},
_next: function InternalRenderTask__next() {
if (this.cancelled) {
return;
@@ -2978,11 +4193,31 @@
};
return InternalRenderTask;
})();
+/**
+ * (Deprecated) Global observer of unsupported feature usages. Use
+ * onUnsupportedFeature callback of the {PDFDocumentLoadingTask} instance.
+ */
+PDFJS.UnsupportedManager = (function UnsupportedManagerClosure() {
+ var listeners = [];
+ return {
+ listen: function (cb) {
+ deprecated('Global UnsupportedManager.listen is used: ' +
+ ' use PDFDocumentLoadingTask.onUnsupportedFeature instead');
+ listeners.push(cb);
+ },
+ notify: function (featureId) {
+ for (var i = 0, ii = listeners.length; i < ii; i++) {
+ listeners[i](featureId);
+ }
+ }
+ };
+})();
+
var Metadata = PDFJS.Metadata = (function MetadataClosure() {
function fixMetadata(meta) {
return meta.replace(/>\\376\\377([^<]+)/g, function(all, codes) {
var bytes = codes.replace(/\\([0-3])([0-7])([0-7])/g,
function(code, d1, d2, d3) {
@@ -3065,36 +4300,41 @@
// <canvas> contexts store most of the state we need natively.
// However, PDF needs a bit more state, which we store here.
// Minimal font size that would be used during canvas fillText operations.
var MIN_FONT_SIZE = 16;
+// Maximum font size that would be used during canvas fillText operations.
+var MAX_FONT_SIZE = 100;
var MAX_GROUP_SIZE = 4096;
+// Heuristic value used when enforcing minimum line widths.
+var MIN_WIDTH_FACTOR = 0.65;
+
var COMPILE_TYPE3_GLYPHS = true;
+var MAX_SIZE_TO_COMPILE = 1000;
+var FULL_CHUNK_HEIGHT = 16;
+
function createScratchCanvas(width, height) {
var canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
return canvas;
}
function addContextCurrentTransform(ctx) {
- // If the context doesn't expose a `mozCurrentTransform`, add a JS based on.
+ // If the context doesn't expose a `mozCurrentTransform`, add a JS based one.
if (!ctx.mozCurrentTransform) {
- // Store the original context
- ctx._scaleX = ctx._scaleX || 1.0;
- ctx._scaleY = ctx._scaleY || 1.0;
ctx._originalSave = ctx.save;
ctx._originalRestore = ctx.restore;
ctx._originalRotate = ctx.rotate;
ctx._originalScale = ctx.scale;
ctx._originalTranslate = ctx.translate;
ctx._originalTransform = ctx.transform;
ctx._originalSetTransform = ctx.setTransform;
- ctx._transformMatrix = [ctx._scaleX, 0, 0, ctx._scaleY, 0, 0];
+ ctx._transformMatrix = ctx._transformMatrix || [1, 0, 0, 1, 0, 0];
ctx._transformStack = [];
Object.defineProperty(ctx, 'mozCurrentTransform', {
get: function getCurrentTransform() {
return this._transformMatrix;
@@ -3196,42 +4436,45 @@
};
}
}
var CachedCanvases = (function CachedCanvasesClosure() {
- var cache = {};
- return {
+ function CachedCanvases() {
+ this.cache = Object.create(null);
+ }
+ CachedCanvases.prototype = {
getCanvas: function CachedCanvases_getCanvas(id, width, height,
trackTransform) {
var canvasEntry;
- if (id in cache) {
- canvasEntry = cache[id];
+ if (this.cache[id] !== undefined) {
+ canvasEntry = this.cache[id];
canvasEntry.canvas.width = width;
canvasEntry.canvas.height = height;
// reset canvas transform for emulated mozCurrentTransform, if needed
canvasEntry.context.setTransform(1, 0, 0, 1, 0, 0);
} else {
var canvas = createScratchCanvas(width, height);
var ctx = canvas.getContext('2d');
if (trackTransform) {
addContextCurrentTransform(ctx);
}
- cache[id] = canvasEntry = {canvas: canvas, context: ctx};
+ this.cache[id] = canvasEntry = {canvas: canvas, context: ctx};
}
return canvasEntry;
},
clear: function () {
- for (var id in cache) {
- var canvasEntry = cache[id];
+ for (var id in this.cache) {
+ var canvasEntry = this.cache[id];
// Zeroing the width and height causes Firefox to release graphics
// resources immediately, which can greatly reduce memory consumption.
canvasEntry.canvas.width = 0;
canvasEntry.canvas.height = 0;
- delete cache[id];
+ delete this.cache[id];
}
}
};
+ return CachedCanvases;
})();
function compileType3Glyph(imgData) {
var POINT_TO_PROCESS_LIMIT = 1000;
@@ -3415,10 +4658,11 @@
this.textRenderingMode = TextRenderingMode.FILL;
this.textRise = 0;
// Default fore and background colors
this.fillColor = '#000000';
this.strokeColor = '#000000';
+ this.patternFill = false;
// Note: fill alpha applies to all non-stroking operations
this.fillAlpha = 1;
this.strokeAlpha = 1;
this.lineWidth = 1;
this.activeSMask = null; // nonclonable field (see the save method below)
@@ -3464,13 +4708,17 @@
this.baseTransformStack = [];
this.groupLevel = 0;
this.smaskStack = [];
this.smaskCounter = 0;
this.tempSMask = null;
+ this.cachedCanvases = new CachedCanvases();
if (canvasCtx) {
+ // NOTE: if mozCurrentTransform is polyfilled, then the current state of
+ // the transformation must already be set in canvasCtx._transformMatrix.
addContextCurrentTransform(canvasCtx);
}
+ this.cachedGetSinglePixelWidth = null;
}
function putBinaryImageData(ctx, imgData) {
if (typeof ImageData !== 'undefined' && imgData instanceof ImageData) {
ctx.putImageData(imgData, 0, 0);
@@ -3487,17 +4735,15 @@
// Note: as written, if the last chunk is partial, the putImageData() call
// will (conceptually) put pixels past the bounds of the canvas. But
// that's ok; any such pixels are ignored.
var height = imgData.height, width = imgData.width;
- var fullChunkHeight = 16;
- var fracChunks = height / fullChunkHeight;
- var fullChunks = Math.floor(fracChunks);
- var totalChunks = Math.ceil(fracChunks);
- var partialChunkHeight = height - fullChunks * fullChunkHeight;
+ var partialChunkHeight = height % FULL_CHUNK_HEIGHT;
+ var fullChunks = (height - partialChunkHeight) / FULL_CHUNK_HEIGHT;
+ var totalChunks = partialChunkHeight === 0 ? fullChunks : fullChunks + 1;
- var chunkImgData = ctx.createImageData(width, fullChunkHeight);
+ var chunkImgData = ctx.createImageData(width, FULL_CHUNK_HEIGHT);
var srcPos = 0, destPos;
var src = imgData.data;
var dest = chunkImgData.data;
var i, j, thisChunkHeight, elemsInThisChunk;
@@ -3513,11 +4759,11 @@
var white = 0xFFFFFFFF;
var black = (PDFJS.isLittleEndian || !PDFJS.hasCanvasTypedArrays) ?
0xFF000000 : 0x000000FF;
for (i = 0; i < totalChunks; i++) {
thisChunkHeight =
- (i < fullChunks) ? fullChunkHeight : partialChunkHeight;
+ (i < fullChunks) ? FULL_CHUNK_HEIGHT : partialChunkHeight;
destPos = 0;
for (j = 0; j < thisChunkHeight; j++) {
var srcDiff = srcLength - srcPos;
var k = 0;
var kEnd = (srcDiff > fullSrcDiff) ? width : srcDiff * 8 - 7;
@@ -3548,70 +4794,68 @@
// We ran out of input. Make all remaining pixels transparent.
while (destPos < dest32DataLength) {
dest32[destPos++] = 0;
}
- ctx.putImageData(chunkImgData, 0, i * fullChunkHeight);
+ ctx.putImageData(chunkImgData, 0, i * FULL_CHUNK_HEIGHT);
}
} else if (imgData.kind === ImageKind.RGBA_32BPP) {
// RGBA, 32-bits per pixel.
j = 0;
- elemsInThisChunk = width * fullChunkHeight * 4;
+ elemsInThisChunk = width * FULL_CHUNK_HEIGHT * 4;
for (i = 0; i < fullChunks; i++) {
dest.set(src.subarray(srcPos, srcPos + elemsInThisChunk));
srcPos += elemsInThisChunk;
ctx.putImageData(chunkImgData, 0, j);
- j += fullChunkHeight;
+ j += FULL_CHUNK_HEIGHT;
}
if (i < totalChunks) {
elemsInThisChunk = width * partialChunkHeight * 4;
dest.set(src.subarray(srcPos, srcPos + elemsInThisChunk));
ctx.putImageData(chunkImgData, 0, j);
}
} else if (imgData.kind === ImageKind.RGB_24BPP) {
// RGB, 24-bits per pixel.
- thisChunkHeight = fullChunkHeight;
+ thisChunkHeight = FULL_CHUNK_HEIGHT;
elemsInThisChunk = width * thisChunkHeight;
for (i = 0; i < totalChunks; i++) {
if (i >= fullChunks) {
- thisChunkHeight =partialChunkHeight;
+ thisChunkHeight = partialChunkHeight;
elemsInThisChunk = width * thisChunkHeight;
}
destPos = 0;
for (j = elemsInThisChunk; j--;) {
dest[destPos++] = src[srcPos++];
dest[destPos++] = src[srcPos++];
dest[destPos++] = src[srcPos++];
dest[destPos++] = 255;
}
- ctx.putImageData(chunkImgData, 0, i * fullChunkHeight);
+ ctx.putImageData(chunkImgData, 0, i * FULL_CHUNK_HEIGHT);
}
} else {
error('bad image kind: ' + imgData.kind);
}
}
function putBinaryImageMask(ctx, imgData) {
var height = imgData.height, width = imgData.width;
- var fullChunkHeight = 16;
- var fracChunks = height / fullChunkHeight;
- var fullChunks = Math.floor(fracChunks);
- var totalChunks = Math.ceil(fracChunks);
- var partialChunkHeight = height - fullChunks * fullChunkHeight;
+ var partialChunkHeight = height % FULL_CHUNK_HEIGHT;
+ var fullChunks = (height - partialChunkHeight) / FULL_CHUNK_HEIGHT;
+ var totalChunks = partialChunkHeight === 0 ? fullChunks : fullChunks + 1;
- var chunkImgData = ctx.createImageData(width, fullChunkHeight);
+ var chunkImgData = ctx.createImageData(width, FULL_CHUNK_HEIGHT);
var srcPos = 0;
var src = imgData.data;
var dest = chunkImgData.data;
for (var i = 0; i < totalChunks; i++) {
var thisChunkHeight =
- (i < fullChunks) ? fullChunkHeight : partialChunkHeight;
+ (i < fullChunks) ? FULL_CHUNK_HEIGHT : partialChunkHeight;
// Expand the mask so it can be used by the canvas. Any required
// inversion has already been handled.
var destPos = 3; // alpha component offset
for (var j = 0; j < thisChunkHeight; j++) {
@@ -3624,28 +4868,28 @@
dest[destPos] = (elem & mask) ? 0 : 255;
destPos += 4;
mask >>= 1;
}
}
- ctx.putImageData(chunkImgData, 0, i * fullChunkHeight);
+ ctx.putImageData(chunkImgData, 0, i * FULL_CHUNK_HEIGHT);
}
}
function copyCtxState(sourceCtx, destCtx) {
var properties = ['strokeStyle', 'fillStyle', 'fillRule', 'globalAlpha',
'lineWidth', 'lineCap', 'lineJoin', 'miterLimit',
'globalCompositeOperation', 'font'];
for (var i = 0, ii = properties.length; i < ii; i++) {
var property = properties[i];
- if (property in sourceCtx) {
+ if (sourceCtx[property] !== undefined) {
destCtx[property] = sourceCtx[property];
}
}
- if ('setLineDash' in sourceCtx) {
+ if (sourceCtx.setLineDash !== undefined) {
destCtx.setLineDash(sourceCtx.getLineDash());
destCtx.lineDashOffset = sourceCtx.lineDashOffset;
- } else if ('mozDash' in sourceCtx) {
+ } else if (sourceCtx.mozDashOffset !== undefined) {
destCtx.mozDash = sourceCtx.mozDash;
destCtx.mozDashOffset = sourceCtx.mozDashOffset;
}
}
@@ -3664,31 +4908,33 @@
bytes[i - 1] = (bytes[i - 1] * alpha + b0 * alpha_) >> 8;
}
}
}
- function composeSMaskAlpha(maskData, layerData) {
+ function composeSMaskAlpha(maskData, layerData, transferMap) {
var length = maskData.length;
var scale = 1 / 255;
for (var i = 3; i < length; i += 4) {
- var alpha = maskData[i];
+ var alpha = transferMap ? transferMap[maskData[i]] : maskData[i];
layerData[i] = (layerData[i] * alpha * scale) | 0;
}
}
- function composeSMaskLuminosity(maskData, layerData) {
+ function composeSMaskLuminosity(maskData, layerData, transferMap) {
var length = maskData.length;
for (var i = 3; i < length; i += 4) {
- var y = ((maskData[i - 3] * 77) + // * 0.3 / 255 * 0x10000
- (maskData[i - 2] * 152) + // * 0.59 ....
- (maskData[i - 1] * 28)) | 0; // * 0.11 ....
- layerData[i] = (layerData[i] * y) >> 16;
+ var y = (maskData[i - 3] * 77) + // * 0.3 / 255 * 0x10000
+ (maskData[i - 2] * 152) + // * 0.59 ....
+ (maskData[i - 1] * 28); // * 0.11 ....
+ layerData[i] = transferMap ?
+ (layerData[i] * transferMap[y >> 8]) >> 8 :
+ (layerData[i] * y) >> 16;
}
}
function genericComposeSMask(maskCtx, layerCtx, width, height,
- subtype, backdrop) {
+ subtype, backdrop, transferMap) {
var hasBackdrop = !!backdrop;
var r0 = hasBackdrop ? backdrop[0] : 0;
var g0 = hasBackdrop ? backdrop[1] : 0;
var b0 = hasBackdrop ? backdrop[2] : 0;
@@ -3698,21 +4944,21 @@
} else {
composeFn = composeSMaskAlpha;
}
// processing image in chunks to save memory
- var PIXELS_TO_PROCESS = 65536;
+ var PIXELS_TO_PROCESS = 1048576;
var chunkSize = Math.min(height, Math.ceil(PIXELS_TO_PROCESS / width));
for (var row = 0; row < height; row += chunkSize) {
var chunkHeight = Math.min(chunkSize, height - row);
var maskData = maskCtx.getImageData(0, row, width, chunkHeight);
var layerData = layerCtx.getImageData(0, row, width, chunkHeight);
if (hasBackdrop) {
composeSMaskBackdrop(maskData.data, r0, g0, b0);
}
- composeFn(maskData.data, layerData.data);
+ composeFn(maskData.data, layerData.data, transferMap);
maskCtx.putImageData(layerData, 0, row);
}
}
@@ -3722,51 +4968,62 @@
ctx.setTransform(smask.scaleX, 0, 0, smask.scaleY,
smask.offsetX, smask.offsetY);
var backdrop = smask.backdrop || null;
- if (WebGLUtils.isEnabled) {
+ if (!smask.transferMap && WebGLUtils.isEnabled) {
var composed = WebGLUtils.composeSMask(layerCtx.canvas, mask,
{subtype: smask.subtype, backdrop: backdrop});
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.drawImage(composed, smask.offsetX, smask.offsetY);
return;
}
genericComposeSMask(maskCtx, layerCtx, mask.width, mask.height,
- smask.subtype, backdrop);
+ smask.subtype, backdrop, smask.transferMap);
ctx.drawImage(mask, 0, 0);
}
var LINE_CAP_STYLES = ['butt', 'round', 'square'];
var LINE_JOIN_STYLES = ['miter', 'round', 'bevel'];
var NORMAL_CLIP = {};
var EO_CLIP = {};
CanvasGraphics.prototype = {
- beginDrawing: function CanvasGraphics_beginDrawing(viewport, transparency) {
+ beginDrawing: function CanvasGraphics_beginDrawing(transform, viewport,
+ transparency) {
// For pdfs that use blend modes we have to clear the canvas else certain
// blend modes can look wrong since we'd be blending with a white
// backdrop. The problem with a transparent backdrop though is we then
- // don't get sub pixel anti aliasing on text, so we fill with white if
- // we can.
+ // don't get sub pixel anti aliasing on text, creating temporary
+ // transparent canvas when we have blend modes.
var width = this.ctx.canvas.width;
var height = this.ctx.canvas.height;
+
+ this.ctx.save();
+ this.ctx.fillStyle = 'rgb(255, 255, 255)';
+ this.ctx.fillRect(0, 0, width, height);
+ this.ctx.restore();
+
if (transparency) {
- this.ctx.clearRect(0, 0, width, height);
- } else {
- this.ctx.mozOpaque = true;
+ var transparentCanvas = this.cachedCanvases.getCanvas(
+ 'transparent', width, height, true);
+ this.compositeCtx = this.ctx;
+ this.transparentCanvas = transparentCanvas.canvas;
+ this.ctx = transparentCanvas.context;
this.ctx.save();
- this.ctx.fillStyle = 'rgb(255, 255, 255)';
- this.ctx.fillRect(0, 0, width, height);
- this.ctx.restore();
+ // The transform can be applied before rendering, transferring it to
+ // the new canvas.
+ this.ctx.transform.apply(this.ctx,
+ this.compositeCtx.mozCurrentTransform);
}
- var transform = viewport.transform;
-
this.ctx.save();
- this.ctx.transform.apply(this.ctx, transform);
+ if (transform) {
+ this.ctx.transform.apply(this.ctx, transform);
+ }
+ this.ctx.transform.apply(this.ctx, viewport.transform);
this.baseTransform = this.ctx.mozCurrentTransform.slice();
if (this.imageLayer) {
this.imageLayer.beginLayout();
@@ -3844,11 +5101,18 @@
}
},
endDrawing: function CanvasGraphics_endDrawing() {
this.ctx.restore();
- CachedCanvases.clear();
+
+ if (this.transparentCanvas) {
+ this.ctx = this.compositeCtx;
+ this.ctx.drawImage(this.transparentCanvas, 0, 0);
+ this.transparentCanvas = null;
+ }
+
+ this.cachedCanvases.clear();
WebGLUtils.clear();
if (this.imageLayer) {
this.imageLayer.endLayout();
}
@@ -3868,11 +5132,11 @@
setMiterLimit: function CanvasGraphics_setMiterLimit(limit) {
this.ctx.miterLimit = limit;
},
setDash: function CanvasGraphics_setDash(dashArray, dashPhase) {
var ctx = this.ctx;
- if ('setLineDash' in ctx) {
+ if (ctx.setLineDash !== undefined) {
ctx.setLineDash(dashArray);
ctx.lineDashOffset = dashPhase;
} else {
ctx.mozDash = dashArray;
ctx.mozDashOffset = dashPhase;
@@ -3958,11 +5222,11 @@
var activeSMask = this.current.activeSMask;
var drawnWidth = activeSMask.canvas.width;
var drawnHeight = activeSMask.canvas.height;
var cacheId = 'smaskGroupAt' + this.groupLevel;
- var scratchCanvas = CachedCanvases.getCanvas(
+ var scratchCanvas = this.cachedCanvases.getCanvas(
cacheId, drawnWidth, drawnHeight, true);
var currentCtx = this.ctx;
var currentTransform = currentCtx.mozCurrentTransform;
this.ctx.save();
@@ -3987,10 +5251,11 @@
this.groupLevel--;
this.ctx = this.groupStack.pop();
composeSMask(this.ctx, this.current.activeSMask, groupCtx);
this.ctx.restore();
+ copyCtxState(groupCtx, this.ctx);
},
save: function CanvasGraphics_save() {
this.ctx.save();
var old = this.current;
this.stateStack.push(old);
@@ -4003,14 +5268,21 @@
this.endSMaskGroup();
}
this.current = this.stateStack.pop();
this.ctx.restore();
+
+ // Ensure that the clipping path is reset (fixes issue6413.pdf).
+ this.pendingClip = null;
+
+ this.cachedGetSinglePixelWidth = null;
}
},
transform: function CanvasGraphics_transform(a, b, c, d, e, f) {
this.ctx.transform(a, b, c, d, e, f);
+
+ this.cachedGetSinglePixelWidth = null;
},
// Path
constructPath: function CanvasGraphics_constructPath(ops, args) {
var ctx = this.ctx;
@@ -4080,13 +5352,13 @@
},
stroke: function CanvasGraphics_stroke(consumePath) {
consumePath = typeof consumePath !== 'undefined' ? consumePath : true;
var ctx = this.ctx;
var strokeColor = this.current.strokeColor;
- if (this.current.lineWidth === 0) {
- ctx.lineWidth = this.getSinglePixelWidth();
- }
+ // Prevent drawing too thin lines by enforcing a minimum line width.
+ ctx.lineWidth = Math.max(this.getSinglePixelWidth() * MIN_WIDTH_FACTOR,
+ this.current.lineWidth);
// For stroke we want to temporarily change the global alpha to the
// stroking alpha.
ctx.globalAlpha = this.current.strokeAlpha;
if (strokeColor && strokeColor.hasOwnProperty('type') &&
strokeColor.type === 'Pattern') {
@@ -4111,31 +5383,29 @@
},
fill: function CanvasGraphics_fill(consumePath) {
consumePath = typeof consumePath !== 'undefined' ? consumePath : true;
var ctx = this.ctx;
var fillColor = this.current.fillColor;
+ var isPatternFill = this.current.patternFill;
var needRestore = false;
- if (fillColor && fillColor.hasOwnProperty('type') &&
- fillColor.type === 'Pattern') {
+ if (isPatternFill) {
ctx.save();
+ if (this.baseTransform) {
+ ctx.setTransform.apply(ctx, this.baseTransform);
+ }
ctx.fillStyle = fillColor.getPattern(ctx, this);
needRestore = true;
}
if (this.pendingEOFill) {
if (ctx.mozFillRule !== undefined) {
ctx.mozFillRule = 'evenodd';
ctx.fill();
ctx.mozFillRule = 'nonzero';
} else {
- try {
- ctx.fill('evenodd');
- } catch (ex) {
- // shouldn't really happen, but browsers might think differently
- ctx.fill();
- }
+ ctx.fill('evenodd');
}
this.pendingEOFill = false;
} else {
ctx.fill();
}
@@ -4255,23 +5525,23 @@
if (fontObj.isType3Font) {
return; // we don't need ctx.font for Type3 fonts
}
var name = fontObj.loadedName || 'sans-serif';
- var bold = fontObj.black ? (fontObj.bold ? 'bolder' : 'bold') :
+ var bold = fontObj.black ? (fontObj.bold ? '900' : 'bold') :
(fontObj.bold ? 'bold' : 'normal');
var italic = fontObj.italic ? 'italic' : 'normal';
var typeface = '"' + name + '", ' + fontObj.fallbackName;
// Some font backends cannot handle fonts below certain size.
// Keeping the font at minimal size and using the fontSizeScale to change
// the current transformation matrix before the fillText/strokeText.
// See https://bugzilla.mozilla.org/show_bug.cgi?id=726227
- var browserFontSize = size >= MIN_FONT_SIZE ? size : MIN_FONT_SIZE;
- this.current.fontSizeScale = browserFontSize !== MIN_FONT_SIZE ? 1.0 :
- size / MIN_FONT_SIZE;
+ var browserFontSize = size < MIN_FONT_SIZE ? MIN_FONT_SIZE :
+ size > MAX_FONT_SIZE ? MAX_FONT_SIZE : size;
+ this.current.fontSizeScale = size / browserFontSize;
var rule = italic + ' ' + bold + ' ' + browserFontSize + 'px ' + typeface;
this.ctx.font = rule;
},
setTextRenderingMode: function CanvasGraphics_setTextRenderingMode(mode) {
@@ -4387,10 +5657,11 @@
var wordSpacing = current.wordSpacing;
var fontDirection = current.fontDirection;
var textHScale = current.textHScale * fontDirection;
var glyphsLength = glyphs.length;
var vertical = font.vertical;
+ var spacingDir = vertical ? 1 : -1;
var defaultVMetrics = font.defaultVMetrics;
var widthAdvanceScale = fontSize * current.fontMatrix[0];
var simpleFillText =
current.textRenderingMode === TextRenderingMode.FILL &&
@@ -4407,11 +5678,17 @@
}
var lineWidth = current.lineWidth;
var scale = current.textMatrixScale;
if (scale === 0 || lineWidth === 0) {
- lineWidth = this.getSinglePixelWidth();
+ var fillStrokeMode = current.textRenderingMode &
+ TextRenderingMode.FILL_STROKE_MASK;
+ if (fillStrokeMode === TextRenderingMode.STROKE ||
+ fillStrokeMode === TextRenderingMode.FILL_STROKE) {
+ this.cachedGetSinglePixelWidth = null;
+ lineWidth = this.getSinglePixelWidth() * MIN_WIDTH_FACTOR;
+ }
} else {
lineWidth /= scale;
}
if (fontSizeScale !== 1.0) {
@@ -4422,20 +5699,17 @@
ctx.lineWidth = lineWidth;
var x = 0, i;
for (i = 0; i < glyphsLength; ++i) {
var glyph = glyphs[i];
- if (glyph === null) {
- // word break
- x += fontDirection * wordSpacing;
+ if (isNum(glyph)) {
+ x += spacingDir * glyph * fontSize / 1000;
continue;
- } else if (isNum(glyph)) {
- x += -glyph * fontSize * 0.001;
- continue;
}
var restoreNeeded = false;
+ var spacing = (glyph.isSpace ? wordSpacing : 0) + charSpacing;
var character = glyph.fontChar;
var accent = glyph.accent;
var scaledX, scaledY, scaledAccentX, scaledAccentY;
var width = glyph.width;
if (vertical) {
@@ -4451,20 +5725,26 @@
} else {
scaledX = x / fontSizeScale;
scaledY = 0;
}
- if (font.remeasure && width > 0 && this.isFontSubpixelAAEnabled) {
- // some standard fonts may not have the exact width, trying to
- // rescale per character
+ if (font.remeasure && width > 0) {
+ // Some standard fonts may not have the exact width: rescale per
+ // character if measured width is greater than expected glyph width
+ // and subpixel-aa is enabled, otherwise just center the glyph.
var measuredWidth = ctx.measureText(character).width * 1000 /
fontSize * fontSizeScale;
- var characterScaleX = width / measuredWidth;
- restoreNeeded = true;
- ctx.save();
- ctx.scale(characterScaleX, 1);
- scaledX /= characterScaleX;
+ if (width < measuredWidth && this.isFontSubpixelAAEnabled) {
+ var characterScaleX = width / measuredWidth;
+ restoreNeeded = true;
+ ctx.save();
+ ctx.scale(characterScaleX, 1);
+ scaledX /= characterScaleX;
+ } else if (width !== measuredWidth) {
+ scaledX += (width - measuredWidth) / 2000 *
+ fontSize / fontSizeScale;
+ }
}
if (simpleFillText && !accent) {
// common case
ctx.fillText(character, scaledX, scaledY);
@@ -4475,11 +5755,11 @@
scaledAccentY = scaledY - accent.offset.y / fontSizeScale;
this.paintChar(accent.fontChar, scaledAccentX, scaledAccentY);
}
}
- var charWidth = width * widthAdvanceScale + charSpacing * fontDirection;
+ var charWidth = width * widthAdvanceScale + spacing * fontDirection;
x += charWidth;
if (restoreNeeded) {
ctx.restore();
}
@@ -4497,41 +5777,41 @@
var ctx = this.ctx;
var current = this.current;
var font = current.font;
var fontSize = current.fontSize;
var fontDirection = current.fontDirection;
+ var spacingDir = font.vertical ? 1 : -1;
var charSpacing = current.charSpacing;
var wordSpacing = current.wordSpacing;
var textHScale = current.textHScale * fontDirection;
var fontMatrix = current.fontMatrix || FONT_IDENTITY_MATRIX;
var glyphsLength = glyphs.length;
- var i, glyph, width;
+ var isTextInvisible =
+ current.textRenderingMode === TextRenderingMode.INVISIBLE;
+ var i, glyph, width, spacingLength;
- if (fontSize === 0) {
+ if (isTextInvisible || fontSize === 0) {
return;
}
+ this.cachedGetSinglePixelWidth = null;
ctx.save();
ctx.transform.apply(ctx, current.textMatrix);
ctx.translate(current.x, current.y);
ctx.scale(textHScale, fontDirection);
for (i = 0; i < glyphsLength; ++i) {
glyph = glyphs[i];
- if (glyph === null) {
- // word break
- this.ctx.translate(wordSpacing, 0);
- current.x += wordSpacing * textHScale;
- continue;
- } else if (isNum(glyph)) {
- var spacingLength = -glyph * 0.001 * fontSize;
+ if (isNum(glyph)) {
+ spacingLength = spacingDir * glyph * fontSize / 1000;
this.ctx.translate(spacingLength, 0);
current.x += spacingLength * textHScale;
continue;
}
+ var spacing = (glyph.isSpace ? wordSpacing : 0) + charSpacing;
var operatorList = font.charProcOperatorList[glyph.operatorListId];
if (!operatorList) {
warn('Type3 character \"' + glyph.operatorListId +
'\" is not available');
continue;
@@ -4542,11 +5822,11 @@
ctx.transform.apply(ctx, fontMatrix);
this.executeOperatorList(operatorList);
this.restore();
var transformed = Util.applyTransform([glyph.width, 0], fontMatrix);
- width = transformed[0] * fontSize + charSpacing;
+ width = transformed[0] * fontSize + spacing;
ctx.translate(width, 0);
current.x += width * textHScale;
}
ctx.restore();
@@ -4574,32 +5854,36 @@
// Color
getColorN_Pattern: function CanvasGraphics_getColorN_Pattern(IR) {
var pattern;
if (IR[0] === 'TilingPattern') {
var color = IR[1];
+ var baseTransform = this.baseTransform ||
+ this.ctx.mozCurrentTransform.slice();
pattern = new TilingPattern(IR, color, this.ctx, this.objs,
- this.commonObjs, this.baseTransform);
+ this.commonObjs, baseTransform);
} else {
pattern = getShadingPatternFromIR(IR);
}
return pattern;
},
setStrokeColorN: function CanvasGraphics_setStrokeColorN(/*...*/) {
this.current.strokeColor = this.getColorN_Pattern(arguments);
},
setFillColorN: function CanvasGraphics_setFillColorN(/*...*/) {
this.current.fillColor = this.getColorN_Pattern(arguments);
+ this.current.patternFill = true;
},
setStrokeRGBColor: function CanvasGraphics_setStrokeRGBColor(r, g, b) {
- var color = Util.makeCssRgb(arguments);
+ var color = Util.makeCssRgb(r, g, b);
this.ctx.strokeStyle = color;
this.current.strokeColor = color;
},
setFillRGBColor: function CanvasGraphics_setFillRGBColor(r, g, b) {
- var color = Util.makeCssRgb(arguments);
+ var color = Util.makeCssRgb(r, g, b);
this.ctx.fillStyle = color;
this.current.fillColor = color;
+ this.current.patternFill = false;
},
shadingFill: function CanvasGraphics_shadingFill(patternIR) {
var ctx = this.ctx;
@@ -4732,11 +6016,11 @@
var cacheId = 'groupAt' + this.groupLevel;
if (group.smask) {
// Using two cache entries is case if masks are used one after another.
cacheId += '_smask_' + ((this.smaskCounter++) % 2);
}
- var scratchCanvas = CachedCanvases.getCanvas(
+ var scratchCanvas = this.cachedCanvases.getCanvas(
cacheId, drawnWidth, drawnHeight, true);
var groupCtx = scratchCanvas.context;
// Since we created a new canvas that is just the size of the bounding box
// we have to translate the group ctx.
@@ -4752,11 +6036,12 @@
offsetX: offsetX,
offsetY: offsetY,
scaleX: scaleX,
scaleY: scaleY,
subtype: group.smask.subtype,
- backdrop: group.smask.backdrop
+ backdrop: group.smask.backdrop,
+ transferMap: group.smask.transferMap || null
});
} else {
// Setup the current ctx so when the group is popped we draw it at the
// right location.
currentCtx.setTransform(1, 0, 0, 1, 0, 0);
@@ -4854,15 +6139,16 @@
},
paintImageMaskXObject: function CanvasGraphics_paintImageMaskXObject(img) {
var ctx = this.ctx;
var width = img.width, height = img.height;
+ var fillColor = this.current.fillColor;
+ var isPatternFill = this.current.patternFill;
var glyph = this.processingType3;
- if (COMPILE_TYPE3_GLYPHS && glyph && !('compiled' in glyph)) {
- var MAX_SIZE_TO_COMPILE = 1000;
+ if (COMPILE_TYPE3_GLYPHS && glyph && glyph.compiled === undefined) {
if (width <= MAX_SIZE_TO_COMPILE && height <= MAX_SIZE_TO_COMPILE) {
glyph.compiled =
compileType3Glyph({data: img.data, width: width, height: height});
} else {
glyph.compiled = null;
@@ -4872,21 +6158,20 @@
if (glyph && glyph.compiled) {
glyph.compiled(ctx);
return;
}
- var maskCanvas = CachedCanvases.getCanvas('maskCanvas', width, height);
+ var maskCanvas = this.cachedCanvases.getCanvas('maskCanvas',
+ width, height);
var maskCtx = maskCanvas.context;
maskCtx.save();
putBinaryImageMask(maskCtx, img);
maskCtx.globalCompositeOperation = 'source-in';
- var fillColor = this.current.fillColor;
- maskCtx.fillStyle = (fillColor && fillColor.hasOwnProperty('type') &&
- fillColor.type === 'Pattern') ?
+ maskCtx.fillStyle = isPatternFill ?
fillColor.getPattern(maskCtx, this) : fillColor;
maskCtx.fillRect(0, 0, width, height);
maskCtx.restore();
@@ -4896,28 +6181,29 @@
paintImageMaskXObjectRepeat:
function CanvasGraphics_paintImageMaskXObjectRepeat(imgData, scaleX,
scaleY, positions) {
var width = imgData.width;
var height = imgData.height;
- var ctx = this.ctx;
+ var fillColor = this.current.fillColor;
+ var isPatternFill = this.current.patternFill;
- var maskCanvas = CachedCanvases.getCanvas('maskCanvas', width, height);
+ var maskCanvas = this.cachedCanvases.getCanvas('maskCanvas',
+ width, height);
var maskCtx = maskCanvas.context;
maskCtx.save();
putBinaryImageMask(maskCtx, imgData);
maskCtx.globalCompositeOperation = 'source-in';
- var fillColor = this.current.fillColor;
- maskCtx.fillStyle = (fillColor && fillColor.hasOwnProperty('type') &&
- fillColor.type === 'Pattern') ?
- fillColor.getPattern(maskCtx, this) : fillColor;
+ maskCtx.fillStyle = isPatternFill ?
+ fillColor.getPattern(maskCtx, this) : fillColor;
maskCtx.fillRect(0, 0, width, height);
maskCtx.restore();
+ var ctx = this.ctx;
for (var i = 0, ii = positions.length; i < ii; i += 2) {
ctx.save();
ctx.transform(scaleX, 0, 0, scaleY, positions[i], positions[i + 1]);
ctx.scale(1, -1);
ctx.drawImage(maskCanvas.canvas, 0, 0, width, height,
@@ -4928,25 +6214,26 @@
paintImageMaskXObjectGroup:
function CanvasGraphics_paintImageMaskXObjectGroup(images) {
var ctx = this.ctx;
+ var fillColor = this.current.fillColor;
+ var isPatternFill = this.current.patternFill;
for (var i = 0, ii = images.length; i < ii; i++) {
var image = images[i];
var width = image.width, height = image.height;
- var maskCanvas = CachedCanvases.getCanvas('maskCanvas', width, height);
+ var maskCanvas = this.cachedCanvases.getCanvas('maskCanvas',
+ width, height);
var maskCtx = maskCanvas.context;
maskCtx.save();
putBinaryImageMask(maskCtx, image);
maskCtx.globalCompositeOperation = 'source-in';
- var fillColor = this.current.fillColor;
- maskCtx.fillStyle = (fillColor && fillColor.hasOwnProperty('type') &&
- fillColor.type === 'Pattern') ?
+ maskCtx.fillStyle = isPatternFill ?
fillColor.getPattern(maskCtx, this) : fillColor;
maskCtx.fillRect(0, 0, width, height);
maskCtx.restore();
@@ -5007,11 +6294,12 @@
var imgToPaint, tmpCanvas;
// instanceof HTMLElement does not work in jsdom node.js module
if (imgData instanceof HTMLElement || !imgData.data) {
imgToPaint = imgData;
} else {
- tmpCanvas = CachedCanvases.getCanvas('inlineImage', width, height);
+ tmpCanvas = this.cachedCanvases.getCanvas('inlineImage',
+ width, height);
var tmpCtx = tmpCanvas.context;
putBinaryImageData(tmpCtx, imgData);
imgToPaint = tmpCanvas.canvas;
}
@@ -5029,11 +6317,12 @@
}
if (heightScale > 2 && paintHeight > 1) {
newHeight = Math.ceil(paintHeight / 2);
heightScale /= paintHeight / newHeight;
}
- tmpCanvas = CachedCanvases.getCanvas(tmpCanvasId, newWidth, newHeight);
+ tmpCanvas = this.cachedCanvases.getCanvas(tmpCanvasId,
+ newWidth, newHeight);
tmpCtx = tmpCanvas.context;
tmpCtx.clearRect(0, 0, newWidth, newHeight);
tmpCtx.drawImage(imgToPaint, 0, 0, paintWidth, paintHeight,
0, 0, newWidth, newHeight);
imgToPaint = tmpCanvas.canvas;
@@ -5061,11 +6350,11 @@
function CanvasGraphics_paintInlineImageXObjectGroup(imgData, map) {
var ctx = this.ctx;
var w = imgData.width;
var h = imgData.height;
- var tmpCanvas = CachedCanvases.getCanvas('inlineImage', w, h);
+ var tmpCanvas = this.cachedCanvases.getCanvas('inlineImage', w, h);
var tmpCtx = tmpCanvas.context;
putBinaryImageData(tmpCtx, imgData);
for (var i = 0, ii = map.length; i < ii; i++) {
var entry = map[i];
@@ -5091,10 +6380,14 @@
paintSolidColorImageMask:
function CanvasGraphics_paintSolidColorImageMask() {
this.ctx.fillRect(0, 0, 1, 1);
},
+ paintXObject: function CanvasGraphics_paintXObject() {
+ warn('Unsupported \'paintXObject\' command.');
+ },
+
// Marked content
markPoint: function CanvasGraphics_markPoint(tag) {
// TODO Marked content.
},
@@ -5130,30 +6423,28 @@
if (ctx.mozFillRule !== undefined) {
ctx.mozFillRule = 'evenodd';
ctx.clip();
ctx.mozFillRule = 'nonzero';
} else {
- try {
- ctx.clip('evenodd');
- } catch (ex) {
- // shouldn't really happen, but browsers might think differently
- ctx.clip();
- }
+ ctx.clip('evenodd');
}
} else {
ctx.clip();
}
this.pendingClip = null;
}
ctx.beginPath();
},
getSinglePixelWidth: function CanvasGraphics_getSinglePixelWidth(scale) {
- var inverse = this.ctx.mozCurrentTransformInverse;
- // max of the current horizontal and vertical scale
- return Math.sqrt(Math.max(
- (inverse[0] * inverse[0] + inverse[1] * inverse[1]),
- (inverse[2] * inverse[2] + inverse[3] * inverse[3])));
+ if (this.cachedGetSinglePixelWidth === null) {
+ var inverse = this.ctx.mozCurrentTransformInverse;
+ // max of the current horizontal and vertical scale
+ this.cachedGetSinglePixelWidth = Math.sqrt(Math.max(
+ (inverse[0] * inverse[0] + inverse[1] * inverse[1]),
+ (inverse[2] * inverse[2] + inverse[3] * inverse[3])));
+ }
+ return this.cachedGetSinglePixelWidth;
},
getCanvasPosition: function CanvasGraphics_getCanvasPosition(x, y) {
var transform = this.ctx.mozCurrentTransform;
return [
transform[0] * x + transform[2] * y + transform[4],
@@ -5168,11 +6459,10 @@
return CanvasGraphics;
})();
-
var WebGLUtils = (function WebGLUtilsClosure() {
function loadShader(gl, code, shaderType) {
var shader = gl.createShader(shaderType);
gl.shaderSource(shader, code);
gl.compileShader(shader);
@@ -5217,11 +6507,11 @@
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
return texture;
}
var currentGL, currentCanvas;
- function generageGL() {
+ function generateGL() {
if (currentGL) {
return;
}
currentCanvas = document.createElement('canvas');
currentGL = currentCanvas.getContext('webgl',
@@ -5275,11 +6565,11 @@
var smaskCache = null;
function initSmaskGL() {
var canvas, gl;
- generageGL();
+ generateGL();
canvas = currentCanvas;
currentCanvas = null;
gl = currentGL;
currentGL = null;
@@ -5407,11 +6697,11 @@
var figuresCache = null;
function initFiguresGL() {
var canvas, gl;
- generageGL();
+ generateGL();
canvas = currentCanvas;
currentCanvas = null;
gl = currentGL;
currentGL = null;
@@ -5510,11 +6800,11 @@
break;
case 'triangles':
for (var j = 0, jj = ps.length; j < jj; j++) {
coords[pIndex] = coordsMap[ps[j]];
coords[pIndex + 1] = coordsMap[ps[j] + 1];
- colors[cIndex] = colorsMap[cs[i]];
+ colors[cIndex] = colorsMap[cs[j]];
colors[cIndex + 1] = colorsMap[cs[j] + 1];
colors[cIndex + 2] = colorsMap[cs[j] + 2];
pIndex += 2;
cIndex += 3;
}
@@ -5575,11 +6865,11 @@
if (PDFJS.disableWebGL) {
return false;
}
var enabled = false;
try {
- generageGL();
+ generateGL();
enabled = !!currentGL;
} catch (e) { }
return shadow(this, 'isEnabled', enabled);
},
composeSMask: composeSMask,
@@ -5717,11 +7007,11 @@
break;
}
}
function createMeshCanvas(bounds, combinesScale, coords, colors, figures,
- backgroundColor) {
+ backgroundColor, cachedCanvases) {
// we will increase scale on some weird factor to let antialiasing take
// care of "rough" edges
var EXPECTED_SCALE = 1.1;
// MAX_PATTERN_SIZE is used to avoid OOM situation.
var MAX_PATTERN_SIZE = 3000; // 10in @ 300dpi shall be enough
@@ -5751,15 +7041,15 @@
if (WebGLUtils.isEnabled) {
canvas = WebGLUtils.drawFigures(width, height, backgroundColor,
figures, context);
// https://bugzilla.mozilla.org/show_bug.cgi?id=972126
- tmpCanvas = CachedCanvases.getCanvas('mesh', width, height, false);
+ tmpCanvas = cachedCanvases.getCanvas('mesh', width, height, false);
tmpCanvas.context.drawImage(canvas, 0, 0);
canvas = tmpCanvas.canvas;
} else {
- tmpCanvas = CachedCanvases.getCanvas('mesh', width, height, false);
+ tmpCanvas = cachedCanvases.getCanvas('mesh', width, height, false);
var tmpCtx = tmpCanvas.context;
var data = tmpCtx.createImageData(width, height);
if (backgroundColor) {
var bytes = data.data;
@@ -5811,11 +7101,12 @@
// Rasterizing on the main thread since sending/queue large canvases
// might cause OOM.
var temporaryPatternCanvas = createMeshCanvas(bounds, scale, coords,
- colors, figures, shadingFill ? null : background);
+ colors, figures, shadingFill ? null : background,
+ owner.cachedCanvases);
if (!shadingFill) {
ctx.setTransform.apply(ctx, owner.baseTransform);
if (matrix) {
ctx.transform.apply(ctx, matrix);
@@ -5914,11 +7205,12 @@
MAX_PATTERN_SIZE);
height = Math.min(Math.ceil(Math.abs(height * combinedScale[1])),
MAX_PATTERN_SIZE);
- var tmpCanvas = CachedCanvases.getCanvas('pattern', width, height, true);
+ var tmpCanvas = owner.cachedCanvases.getCanvas('pattern',
+ width, height, true);
var tmpCtx = tmpCanvas.context;
var graphics = new CanvasGraphics(tmpCtx, commonObjs, objs);
graphics.groupLevel = owner.groupLevel;
this.setFillAndStrokeStyleToContext(tmpCtx, paintType, color);
@@ -5968,11 +7260,11 @@
var ctx = this.ctx;
context.fillStyle = ctx.fillStyle;
context.strokeStyle = ctx.strokeStyle;
break;
case PaintType.UNCOLORED:
- var cssColor = Util.makeCssRgb(color);
+ var cssColor = Util.makeCssRgb(color[0], color[1], color[2]);
context.fillStyle = cssColor;
context.strokeStyle = cssColor;
break;
default:
error('Unsupported paint type: ' + paintType);
@@ -5993,30 +7285,39 @@
return TilingPattern;
})();
-PDFJS.disableFontFace = false;
-
-var FontLoader = {
+function FontLoader(docId) {
+ this.docId = docId;
+ this.styleElement = null;
+ this.nativeFontFaces = [];
+ this.loadTestFontId = 0;
+ this.loadingContext = {
+ requests: [],
+ nextRequestId: 0
+ };
+}
+FontLoader.prototype = {
insertRule: function fontLoaderInsertRule(rule) {
- var styleElement = document.getElementById('PDFJS_FONT_STYLE_TAG');
+ var styleElement = this.styleElement;
if (!styleElement) {
- styleElement = document.createElement('style');
- styleElement.id = 'PDFJS_FONT_STYLE_TAG';
+ styleElement = this.styleElement = document.createElement('style');
+ styleElement.id = 'PDFJS_FONT_STYLE_TAG_' + this.docId;
document.documentElement.getElementsByTagName('head')[0].appendChild(
styleElement);
}
var styleSheet = styleElement.sheet;
styleSheet.insertRule(rule, styleSheet.cssRules.length);
},
clear: function fontLoaderClear() {
- var styleElement = document.getElementById('PDFJS_FONT_STYLE_TAG');
+ var styleElement = this.styleElement;
if (styleElement) {
styleElement.parentNode.removeChild(styleElement);
+ styleElement = this.styleElement = null;
}
this.nativeFontFaces.forEach(function(nativeFontFace) {
document.fonts.delete(nativeFontFace);
});
this.nativeFontFaces.length = 0;
@@ -6048,40 +7349,10 @@
'AAAAAAABAAAAAMmJbzEAAAAAzgTjFQAAAADOBOQpAAEAAAAAAAAADAAUAAQAAAABAAAAAg' +
'ABAAAAAAAAAAAD6AAAAAAAAA=='
));
},
- loadTestFontId: 0,
-
- loadingContext: {
- requests: [],
- nextRequestId: 0
- },
-
- isSyncFontLoadingSupported: (function detectSyncFontLoadingSupport() {
- if (isWorker) {
- return false;
- }
-
- // User agent string sniffing is bad, but there is no reliable way to tell
- // if font is fully loaded and ready to be used with canvas.
- var userAgent = window.navigator.userAgent;
- var m = /Mozilla\/5.0.*?rv:(\d+).*? Gecko/.exec(userAgent);
- if (m && m[1] >= 14) {
- return true;
- }
- // TODO other browsers
- if (userAgent === 'node') {
- return true;
- }
- return false;
- })(),
-
- nativeFontFaces: [],
-
- isFontLoadingAPISupported: !isWorker && !!document.fonts,
-
addNativeFontFace: function fontLoader_addNativeFontFace(nativeFontFace) {
this.nativeFontFaces.push(nativeFontFace);
document.fonts.add(nativeFontFace);
},
@@ -6089,41 +7360,50 @@
assert(!isWorker, 'bind() shall be called from main thread');
var rules = [];
var fontsToLoad = [];
var fontLoadPromises = [];
+ var getNativeFontPromise = function(nativeFontFace) {
+ // Return a promise that is always fulfilled, even when the font fails to
+ // load.
+ return nativeFontFace.loaded.catch(function(e) {
+ warn('Failed to load font "' + nativeFontFace.family + '": ' + e);
+ });
+ };
for (var i = 0, ii = fonts.length; i < ii; i++) {
var font = fonts[i];
// Add the font to the DOM only once or skip if the font
// is already loaded.
if (font.attached || font.loading === false) {
continue;
}
font.attached = true;
- if (this.isFontLoadingAPISupported) {
+ if (FontLoader.isFontLoadingAPISupported) {
var nativeFontFace = font.createNativeFontFace();
if (nativeFontFace) {
- fontLoadPromises.push(nativeFontFace.loaded);
+ this.addNativeFontFace(nativeFontFace);
+ fontLoadPromises.push(getNativeFontPromise(nativeFontFace));
}
} else {
- var rule = font.bindDOM();
+ var rule = font.createFontFaceRule();
if (rule) {
+ this.insertRule(rule);
rules.push(rule);
fontsToLoad.push(font);
}
}
}
- var request = FontLoader.queueLoadingCallback(callback);
- if (this.isFontLoadingAPISupported) {
- Promise.all(fontsToLoad).then(function() {
+ var request = this.queueLoadingCallback(callback);
+ if (FontLoader.isFontLoadingAPISupported) {
+ Promise.all(fontLoadPromises).then(function() {
request.complete();
});
- } else if (rules.length > 0 && !this.isSyncFontLoadingSupported) {
- FontLoader.prepareFontLoadEvent(rules, fontsToLoad, request);
+ } else if (rules.length > 0 && !FontLoader.isSyncFontLoadingSupported) {
+ this.prepareFontLoadEvent(rules, fontsToLoad, request);
} else {
request.complete();
}
},
@@ -6137,11 +7417,11 @@
var otherRequest = context.requests.shift();
setTimeout(otherRequest.callback, 0);
}
}
- var context = FontLoader.loadingContext;
+ var context = this.loadingContext;
var requestId = 'pdfjs-font-loading-' + (context.nextRequestId++);
var request = {
id: requestId,
complete: LoadLoader_completeRequest,
callback: callback,
@@ -6224,11 +7504,11 @@
data = spliceString(data, CFF_CHECKSUM_OFFSET, 4, string32(checksum));
var url = 'url(data:font/opentype;base64,' + btoa(data) + ');';
var rule = '@font-face { font-family:"' + loadTestFontId + '";src:' +
url + '}';
- FontLoader.insertRule(rule);
+ this.insertRule(rule);
var names = [];
for (i = 0, ii = fonts.length; i < ii; i++) {
names.push(fonts[i].loadedName);
}
@@ -6252,23 +7532,56 @@
request.complete();
});
/** Hack end */
}
};
+FontLoader.isFontLoadingAPISupported = (!isWorker &&
+ typeof document !== 'undefined' && !!document.fonts);
+Object.defineProperty(FontLoader, 'isSyncFontLoadingSupported', {
+ get: function () {
+ var supported = false;
+ // User agent string sniffing is bad, but there is no reliable way to tell
+ // if font is fully loaded and ready to be used with canvas.
+ var userAgent = window.navigator.userAgent;
+ var m = /Mozilla\/5.0.*?rv:(\d+).*? Gecko/.exec(userAgent);
+ if (m && m[1] >= 14) {
+ supported = true;
+ }
+ // TODO other browsers
+ if (userAgent === 'node') {
+ supported = true;
+ }
+ return shadow(FontLoader, 'isSyncFontLoadingSupported', supported);
+ },
+ enumerable: true,
+ configurable: true
+});
+
var FontFaceObject = (function FontFaceObjectClosure() {
- function FontFaceObject(name, file, properties) {
+ function FontFaceObject(translatedData) {
this.compiledGlyphs = {};
- if (arguments.length === 1) {
- // importing translated data
- var data = arguments[0];
- for (var i in data) {
- this[i] = data[i];
- }
- return;
+ // importing translated data
+ for (var i in translatedData) {
+ this[i] = translatedData[i];
}
}
+ Object.defineProperty(FontFaceObject, 'isEvalSupported', {
+ get: function () {
+ var evalSupport = false;
+ if (PDFJS.isEvalSupported) {
+ try {
+ /* jshint evil: true */
+ new Function('');
+ evalSupport = true;
+ } catch (e) {}
+ }
+ return shadow(this, 'isEvalSupported', evalSupport);
+ },
+ enumerable: true,
+ configurable: true
+ });
FontFaceObject.prototype = {
createNativeFontFace: function FontFaceObject_createNativeFontFace() {
if (!this.data) {
return null;
}
@@ -6278,20 +7591,18 @@
return null;
}
var nativeFontFace = new FontFace(this.loadedName, this.data, {});
- FontLoader.addNativeFontFace(nativeFontFace);
-
if (PDFJS.pdfBug && 'FontInspector' in globalScope &&
globalScope['FontInspector'].enabled) {
globalScope['FontInspector'].fontAdded(this);
}
return nativeFontFace;
},
- bindDOM: function FontFaceObject_bindDOM() {
+ createFontFaceRule: function FontFaceObject_createFontFaceRule() {
if (!this.data) {
return null;
}
if (PDFJS.disableFontFace) {
@@ -6304,40 +7615,126 @@
// Add the font-face rule to the document
var url = ('url(data:' + this.mimetype + ';base64,' +
window.btoa(data) + ');');
var rule = '@font-face { font-family:"' + fontName + '";src:' + url + '}';
- FontLoader.insertRule(rule);
if (PDFJS.pdfBug && 'FontInspector' in globalScope &&
globalScope['FontInspector'].enabled) {
globalScope['FontInspector'].fontAdded(this, url);
}
return rule;
},
- getPathGenerator: function FontLoader_getPathGenerator(objs, character) {
+ getPathGenerator:
+ function FontFaceObject_getPathGenerator(objs, character) {
if (!(character in this.compiledGlyphs)) {
- var js = objs.get(this.loadedName + '_path_' + character);
- /*jshint -W054 */
- this.compiledGlyphs[character] = new Function('c', 'size', js);
+ var cmds = objs.get(this.loadedName + '_path_' + character);
+ var current, i, len;
+
+ // If we can, compile cmds into JS for MAXIMUM SPEED
+ if (FontFaceObject.isEvalSupported) {
+ var args, js = '';
+ for (i = 0, len = cmds.length; i < len; i++) {
+ current = cmds[i];
+
+ if (current.args !== undefined) {
+ args = current.args.join(',');
+ } else {
+ args = '';
+ }
+
+ js += 'c.' + current.cmd + '(' + args + ');\n';
+ }
+ /* jshint -W054 */
+ this.compiledGlyphs[character] = new Function('c', 'size', js);
+ } else {
+ // But fall back on using Function.prototype.apply() if we're
+ // blocked from using eval() for whatever reason (like CSP policies)
+ this.compiledGlyphs[character] = function(c, size) {
+ for (i = 0, len = cmds.length; i < len; i++) {
+ current = cmds[i];
+
+ if (current.cmd === 'scale') {
+ current.args = [size, -size];
+ }
+
+ c[current.cmd].apply(c, current.args);
+ }
+ };
+ }
}
return this.compiledGlyphs[character];
}
};
return FontFaceObject;
})();
-var HIGHLIGHT_OFFSET = 4; // px
+/**
+ * Optimised CSS custom property getter/setter.
+ * @class
+ */
+var CustomStyle = (function CustomStyleClosure() {
+
+ // As noted on: http://www.zachstronaut.com/posts/2009/02/17/
+ // animate-css-transforms-firefox-webkit.html
+ // in some versions of IE9 it is critical that ms appear in this list
+ // before Moz
+ var prefixes = ['ms', 'Moz', 'Webkit', 'O'];
+ var _cache = {};
+
+ function CustomStyle() {}
+
+ CustomStyle.getProp = function get(propName, element) {
+ // check cache only when no element is given
+ if (arguments.length === 1 && typeof _cache[propName] === 'string') {
+ return _cache[propName];
+ }
+
+ element = element || document.documentElement;
+ var style = element.style, prefixed, uPropName;
+
+ // test standard property first
+ if (typeof style[propName] === 'string') {
+ return (_cache[propName] = propName);
+ }
+
+ // capitalize
+ uPropName = propName.charAt(0).toUpperCase() + propName.slice(1);
+
+ // test vendor specific properties
+ for (var i = 0, l = prefixes.length; i < l; i++) {
+ prefixed = prefixes[i] + uPropName;
+ if (typeof style[prefixed] === 'string') {
+ return (_cache[propName] = prefixed);
+ }
+ }
+
+ //if all fails then set to undefined
+ return (_cache[propName] = 'undefined');
+ };
+
+ CustomStyle.setProp = function set(propName, element, str) {
+ var prop = this.getProp(propName);
+ if (prop !== 'undefined') {
+ element.style[prop] = str;
+ }
+ };
+
+ return CustomStyle;
+})();
+
+PDFJS.CustomStyle = CustomStyle;
+
+
var ANNOT_MIN_SIZE = 10; // px
-var AnnotationUtils = (function AnnotationUtilsClosure() {
+var AnnotationLayer = (function AnnotationLayerClosure() {
// TODO(mack): This dupes some of the logic in CanvasGraphics.setFont()
function setTextStyles(element, item, fontObj) {
-
var style = element.style;
style.fontSize = item.fontSize + 'px';
style.direction = item.fontDirection < 0 ? 'rtl': 'ltr';
if (!fontObj) {
@@ -6354,77 +7751,127 @@
// Use a reasonable default font if the font doesn't specify a fallback
var fallbackName = fontObj.fallbackName || 'Helvetica, sans-serif';
style.fontFamily = fontFamily + fallbackName;
}
- // TODO(mack): Remove this, it's not really that helpful.
- function getEmptyContainer(tagName, rect, borderWidth) {
- var bWidth = borderWidth || 0;
- var element = document.createElement(tagName);
- element.style.borderWidth = bWidth + 'px';
- var width = rect[2] - rect[0] - 2 * bWidth;
- var height = rect[3] - rect[1] - 2 * bWidth;
- element.style.width = width + 'px';
- element.style.height = height + 'px';
- return element;
- }
+ function getContainer(data, page, viewport) {
+ var container = document.createElement('section');
+ var width = data.rect[2] - data.rect[0];
+ var height = data.rect[3] - data.rect[1];
- function initContainer(item) {
- var container = getEmptyContainer('section', item.rect, item.borderWidth);
- container.style.backgroundColor = item.color;
+ container.setAttribute('data-annotation-id', data.id);
- var color = item.color;
- var rgb = [];
- for (var i = 0; i < 3; ++i) {
- rgb[i] = Math.round(color[i] * 255);
+ data.rect = Util.normalizeRect([
+ data.rect[0],
+ page.view[3] - data.rect[1] + page.view[1],
+ data.rect[2],
+ page.view[3] - data.rect[3] + page.view[1]
+ ]);
+
+ CustomStyle.setProp('transform', container,
+ 'matrix(' + viewport.transform.join(',') + ')');
+ CustomStyle.setProp('transformOrigin', container,
+ -data.rect[0] + 'px ' + -data.rect[1] + 'px');
+
+ if (data.borderStyle.width > 0) {
+ container.style.borderWidth = data.borderStyle.width + 'px';
+ if (data.borderStyle.style !== AnnotationBorderStyleType.UNDERLINE) {
+ // Underline styles only have a bottom border, so we do not need
+ // to adjust for all borders. This yields a similar result as
+ // Adobe Acrobat/Reader.
+ width = width - 2 * data.borderStyle.width;
+ height = height - 2 * data.borderStyle.width;
+ }
+
+ var horizontalRadius = data.borderStyle.horizontalCornerRadius;
+ var verticalRadius = data.borderStyle.verticalCornerRadius;
+ if (horizontalRadius > 0 || verticalRadius > 0) {
+ var radius = horizontalRadius + 'px / ' + verticalRadius + 'px';
+ CustomStyle.setProp('borderRadius', container, radius);
+ }
+
+ switch (data.borderStyle.style) {
+ case AnnotationBorderStyleType.SOLID:
+ container.style.borderStyle = 'solid';
+ break;
+
+ case AnnotationBorderStyleType.DASHED:
+ container.style.borderStyle = 'dashed';
+ break;
+
+ case AnnotationBorderStyleType.BEVELED:
+ warn('Unimplemented border style: beveled');
+ break;
+
+ case AnnotationBorderStyleType.INSET:
+ warn('Unimplemented border style: inset');
+ break;
+
+ case AnnotationBorderStyleType.UNDERLINE:
+ container.style.borderBottomStyle = 'solid';
+ break;
+
+ default:
+ break;
+ }
+
+ if (data.color) {
+ container.style.borderColor =
+ Util.makeCssRgb(data.color[0] | 0,
+ data.color[1] | 0,
+ data.color[2] | 0);
+ } else {
+ // Transparent (invisible) border, so do not draw it at all.
+ container.style.borderWidth = 0;
+ }
}
- item.colorCssRgb = Util.makeCssRgb(rgb);
- var highlight = document.createElement('div');
- highlight.className = 'annotationHighlight';
- highlight.style.left = highlight.style.top = -HIGHLIGHT_OFFSET + 'px';
- highlight.style.right = highlight.style.bottom = -HIGHLIGHT_OFFSET + 'px';
- highlight.setAttribute('hidden', true);
+ container.style.left = data.rect[0] + 'px';
+ container.style.top = data.rect[1] + 'px';
- item.highlightElement = highlight;
- container.appendChild(item.highlightElement);
+ container.style.width = width + 'px';
+ container.style.height = height + 'px';
return container;
}
- function getHtmlElementForTextWidgetAnnotation(item, commonObjs) {
- var element = getEmptyContainer('div', item.rect, 0);
+ function getHtmlElementForTextWidgetAnnotation(item, page) {
+ var element = document.createElement('div');
+ var width = item.rect[2] - item.rect[0];
+ var height = item.rect[3] - item.rect[1];
+ element.style.width = width + 'px';
+ element.style.height = height + 'px';
element.style.display = 'table';
var content = document.createElement('div');
content.textContent = item.fieldValue;
var textAlignment = item.textAlignment;
content.style.textAlign = ['left', 'center', 'right'][textAlignment];
content.style.verticalAlign = 'middle';
content.style.display = 'table-cell';
var fontObj = item.fontRefName ?
- commonObjs.getData(item.fontRefName) : null;
+ page.commonObjs.getData(item.fontRefName) : null;
setTextStyles(content, item, fontObj);
element.appendChild(content);
return element;
}
- function getHtmlElementForTextAnnotation(item) {
+ function getHtmlElementForTextAnnotation(item, page, viewport) {
var rect = item.rect;
// sanity check because of OOo-generated PDFs
if ((rect[3] - rect[1]) < ANNOT_MIN_SIZE) {
rect[3] = rect[1] + ANNOT_MIN_SIZE;
}
if ((rect[2] - rect[0]) < ANNOT_MIN_SIZE) {
rect[2] = rect[0] + (rect[3] - rect[1]); // make it square
}
- var container = initContainer(item);
+ var container = getContainer(item, page, viewport);
container.className = 'annotText';
var image = document.createElement('img');
image.style.height = container.style.height;
image.style.width = container.style.width;
@@ -6443,19 +7890,19 @@
var content = document.createElement('div');
content.className = 'annotTextContent';
content.setAttribute('hidden', true);
var i, ii;
- if (item.hasBgColor) {
+ if (item.hasBgColor && item.color) {
var color = item.color;
- var rgb = [];
- for (i = 0; i < 3; ++i) {
- // Enlighten the color (70%)
- var c = Math.round(color[i] * 255);
- rgb[i] = Math.round((255 - c) * 0.7) + c;
- }
- content.style.backgroundColor = Util.makeCssRgb(rgb);
+
+ // Enlighten the color (70%)
+ var BACKGROUND_ENLIGHT = 0.7;
+ var r = BACKGROUND_ENLIGHT * (255 - color[0]) + color[0];
+ var g = BACKGROUND_ENLIGHT * (255 - color[1]) + color[1];
+ var b = BACKGROUND_ENLIGHT * (255 - color[2]) + color[2];
+ content.style.backgroundColor = Util.makeCssRgb(r | 0, g | 0, b | 0);
}
var title = document.createElement('h1');
var text = document.createElement('p');
title.textContent = item.title;
@@ -6526,48 +7973,322 @@
container.appendChild(contentWrapper);
return container;
}
- function getHtmlElementForLinkAnnotation(item) {
- var container = initContainer(item);
- container.className = 'annotLink';
+ function getHtmlElementForLinkAnnotation(item, page, viewport, linkService) {
+ function bindLink(link, dest) {
+ link.href = linkService.getDestinationHash(dest);
+ link.onclick = function annotationsLayerBuilderLinksOnclick() {
+ if (dest) {
+ linkService.navigateTo(dest);
+ }
+ return false;
+ };
+ if (dest) {
+ link.className = 'internalLink';
+ }
+ }
- container.style.borderColor = item.colorCssRgb;
- container.style.borderStyle = 'solid';
+ function bindNamedAction(link, action) {
+ link.href = linkService.getAnchorUrl('');
+ link.onclick = function annotationsLayerBuilderNamedActionOnClick() {
+ linkService.executeNamedAction(action);
+ return false;
+ };
+ link.className = 'internalLink';
+ }
+ var container = getContainer(item, page, viewport);
+ container.className = 'annotLink';
+
var link = document.createElement('a');
link.href = link.title = item.url || '';
- if (link.href.indexOf("mailto:") !== 0) {
- link.target = "_blank";
+
+ if (item.url && isExternalLinkTargetSet()) {
+ link.target = LinkTargetStringMap[PDFJS.externalLinkTarget];
}
+ if (!item.url) {
+ if (item.action) {
+ bindNamedAction(link, item.action);
+ } else {
+ bindLink(link, ('dest' in item) ? item.dest : null);
+ }
+ }
+
container.appendChild(link);
return container;
}
- function getHtmlElement(data, objs) {
+ function getHtmlElement(data, page, viewport, linkService) {
switch (data.annotationType) {
case AnnotationType.WIDGET:
- return getHtmlElementForTextWidgetAnnotation(data, objs);
+ return getHtmlElementForTextWidgetAnnotation(data, page);
case AnnotationType.TEXT:
- return getHtmlElementForTextAnnotation(data);
+ return getHtmlElementForTextAnnotation(data, page, viewport);
case AnnotationType.LINK:
- return getHtmlElementForLinkAnnotation(data);
+ return getHtmlElementForLinkAnnotation(data, page, viewport,
+ linkService);
default:
throw new Error('Unsupported annotationType: ' + data.annotationType);
}
}
+ function render(viewport, div, annotations, page, linkService) {
+ for (var i = 0, ii = annotations.length; i < ii; i++) {
+ var data = annotations[i];
+ if (!data || !data.hasHtml) {
+ continue;
+ }
+
+ var element = getHtmlElement(data, page, viewport, linkService);
+ div.appendChild(element);
+ }
+ }
+
+ function update(viewport, div, annotations) {
+ for (var i = 0, ii = annotations.length; i < ii; i++) {
+ var data = annotations[i];
+ var element = div.querySelector(
+ '[data-annotation-id="' + data.id + '"]');
+ if (element) {
+ CustomStyle.setProp('transform', element,
+ 'matrix(' + viewport.transform.join(',') + ')');
+ }
+ }
+ div.removeAttribute('hidden');
+ }
+
return {
- getHtmlElement: getHtmlElement
+ render: render,
+ update: update
};
})();
-PDFJS.AnnotationUtils = AnnotationUtils;
+PDFJS.AnnotationLayer = AnnotationLayer;
+
+/**
+ * Text layer render parameters.
+ *
+ * @typedef {Object} TextLayerRenderParameters
+ * @property {TextContent} textContent - Text content to render (the object is
+ * returned by the page's getTextContent() method).
+ * @property {HTMLElement} container - HTML element that will contain text runs.
+ * @property {PDFJS.PageViewport} viewport - The target viewport to properly
+ * layout the text runs.
+ * @property {Array} textDivs - (optional) HTML elements that are correspond
+ * the text items of the textContent input. This is output and shall be
+ * initially be set to empty array.
+ * @property {number} timeout - (optional) Delay in milliseconds before
+ * rendering of the text runs occurs.
+ */
+var renderTextLayer = (function renderTextLayerClosure() {
+ var MAX_TEXT_DIVS_TO_RENDER = 100000;
+
+ var NonWhitespaceRegexp = /\S/;
+
+ function isAllWhitespace(str) {
+ return !NonWhitespaceRegexp.test(str);
+ }
+
+ function appendText(textDivs, viewport, geom, styles) {
+ var style = styles[geom.fontName];
+ var textDiv = document.createElement('div');
+ textDivs.push(textDiv);
+ if (isAllWhitespace(geom.str)) {
+ textDiv.dataset.isWhitespace = true;
+ return;
+ }
+ var tx = PDFJS.Util.transform(viewport.transform, geom.transform);
+ var angle = Math.atan2(tx[1], tx[0]);
+ if (style.vertical) {
+ angle += Math.PI / 2;
+ }
+ var fontHeight = Math.sqrt((tx[2] * tx[2]) + (tx[3] * tx[3]));
+ var fontAscent = fontHeight;
+ if (style.ascent) {
+ fontAscent = style.ascent * fontAscent;
+ } else if (style.descent) {
+ fontAscent = (1 + style.descent) * fontAscent;
+ }
+
+ var left;
+ var top;
+ if (angle === 0) {
+ left = tx[4];
+ top = tx[5] - fontAscent;
+ } else {
+ left = tx[4] + (fontAscent * Math.sin(angle));
+ top = tx[5] - (fontAscent * Math.cos(angle));
+ }
+ textDiv.style.left = left + 'px';
+ textDiv.style.top = top + 'px';
+ textDiv.style.fontSize = fontHeight + 'px';
+ textDiv.style.fontFamily = style.fontFamily;
+
+ textDiv.textContent = geom.str;
+ // |fontName| is only used by the Font Inspector. This test will succeed
+ // when e.g. the Font Inspector is off but the Stepper is on, but it's
+ // not worth the effort to do a more accurate test.
+ if (PDFJS.pdfBug) {
+ textDiv.dataset.fontName = geom.fontName;
+ }
+ // Storing into dataset will convert number into string.
+ if (angle !== 0) {
+ textDiv.dataset.angle = angle * (180 / Math.PI);
+ }
+ // We don't bother scaling single-char text divs, because it has very
+ // little effect on text highlighting. This makes scrolling on docs with
+ // lots of such divs a lot faster.
+ if (geom.str.length > 1) {
+ if (style.vertical) {
+ textDiv.dataset.canvasWidth = geom.height * viewport.scale;
+ } else {
+ textDiv.dataset.canvasWidth = geom.width * viewport.scale;
+ }
+ }
+ }
+
+ function render(task) {
+ if (task._canceled) {
+ return;
+ }
+ var textLayerFrag = task._container;
+ var textDivs = task._textDivs;
+ var capability = task._capability;
+ var textDivsLength = textDivs.length;
+
+ // No point in rendering many divs as it would make the browser
+ // unusable even after the divs are rendered.
+ if (textDivsLength > MAX_TEXT_DIVS_TO_RENDER) {
+ capability.resolve();
+ return;
+ }
+
+ var canvas = document.createElement('canvas');
+ var ctx = canvas.getContext('2d', {alpha: false});
+
+ var lastFontSize;
+ var lastFontFamily;
+ for (var i = 0; i < textDivsLength; i++) {
+ var textDiv = textDivs[i];
+ if (textDiv.dataset.isWhitespace !== undefined) {
+ continue;
+ }
+
+ var fontSize = textDiv.style.fontSize;
+ var fontFamily = textDiv.style.fontFamily;
+
+ // Only build font string and set to context if different from last.
+ if (fontSize !== lastFontSize || fontFamily !== lastFontFamily) {
+ ctx.font = fontSize + ' ' + fontFamily;
+ lastFontSize = fontSize;
+ lastFontFamily = fontFamily;
+ }
+
+ var width = ctx.measureText(textDiv.textContent).width;
+ if (width > 0) {
+ textLayerFrag.appendChild(textDiv);
+ var transform;
+ if (textDiv.dataset.canvasWidth !== undefined) {
+ // Dataset values come of type string.
+ var textScale = textDiv.dataset.canvasWidth / width;
+ transform = 'scaleX(' + textScale + ')';
+ } else {
+ transform = '';
+ }
+ var rotation = textDiv.dataset.angle;
+ if (rotation) {
+ transform = 'rotate(' + rotation + 'deg) ' + transform;
+ }
+ if (transform) {
+ PDFJS.CustomStyle.setProp('transform' , textDiv, transform);
+ }
+ }
+ }
+ capability.resolve();
+ }
+
+ /**
+ * Text layer rendering task.
+ *
+ * @param {TextContent} textContent
+ * @param {HTMLElement} container
+ * @param {PDFJS.PageViewport} viewport
+ * @param {Array} textDivs
+ * @private
+ */
+ function TextLayerRenderTask(textContent, container, viewport, textDivs) {
+ this._textContent = textContent;
+ this._container = container;
+ this._viewport = viewport;
+ textDivs = textDivs || [];
+ this._textDivs = textDivs;
+ this._canceled = false;
+ this._capability = createPromiseCapability();
+ this._renderTimer = null;
+ }
+ TextLayerRenderTask.prototype = {
+ get promise() {
+ return this._capability.promise;
+ },
+
+ cancel: function TextLayer_cancel() {
+ this._canceled = true;
+ if (this._renderTimer !== null) {
+ clearTimeout(this._renderTimer);
+ this._renderTimer = null;
+ }
+ this._capability.reject('canceled');
+ },
+
+ _render: function TextLayer_render(timeout) {
+ var textItems = this._textContent.items;
+ var styles = this._textContent.styles;
+ var textDivs = this._textDivs;
+ var viewport = this._viewport;
+ for (var i = 0, len = textItems.length; i < len; i++) {
+ appendText(textDivs, viewport, textItems[i], styles);
+ }
+
+ if (!timeout) { // Render right away
+ render(this);
+ } else { // Schedule
+ var self = this;
+ this._renderTimer = setTimeout(function() {
+ render(self);
+ self._renderTimer = null;
+ }, timeout);
+ }
+ }
+ };
+
+
+ /**
+ * Starts rendering of the text layer.
+ *
+ * @param {TextLayerRenderParameters} renderParameters
+ * @returns {TextLayerRenderTask}
+ */
+ function renderTextLayer(renderParameters) {
+ var task = new TextLayerRenderTask(renderParameters.textContent,
+ renderParameters.container,
+ renderParameters.viewport,
+ renderParameters.textDivs);
+ task._render(renderParameters.timeout);
+ return task;
+ }
+
+ return renderTextLayer;
+})();
+
+PDFJS.renderTextLayer = renderTextLayer;
+
+
var SVG_DEFAULTS = {
fontStyle: 'normal',
fontWeight: 'normal',
fillColor: '#000000'
};
@@ -6796,11 +8517,11 @@
this.strokeAlpha = 1;
this.lineWidth = 1;
this.lineJoin = '';
this.lineCap = '';
this.miterLimit = 0;
-
+
this.dashArray = [];
this.dashPhase = 0;
this.dependencies = [];
@@ -7023,11 +8744,11 @@
var fnId = fnArray[x];
opList.push({'fnId' : fnId, 'fn': REVOPS[fnId], 'args': argsArray[x]});
}
return opListToTree(opList);
},
-
+
executeOpTree: function SVGGraphics_executeOpTree(opTree) {
var opTreeLen = opTree.length;
for(var x = 0; x < opTreeLen; x++) {
var fn = opTree[x].fn;
var fnId = opTree[x].fnId;
@@ -7062,10 +8783,13 @@
this.setCharSpacing(args[0]);
break;
case OPS.setWordSpacing:
this.setWordSpacing(args[0]);
break;
+ case OPS.setHScale:
+ this.setHScale(args[0]);
+ break;
case OPS.setTextMatrix:
this.setTextMatrix(args[0], args[1], args[2],
args[3], args[4], args[5]);
break;
case OPS.setLineWidth:
@@ -7366,15 +9090,15 @@
},
setMiterLimit: function SVGGraphics_setMiterLimit(limit) {
this.current.miterLimit = limit;
},
setStrokeRGBColor: function SVGGraphics_setStrokeRGBColor(r, g, b) {
- var color = Util.makeCssRgb(arguments);
+ var color = Util.makeCssRgb(r, g, b);
this.current.strokeColor = color;
},
setFillRGBColor: function SVGGraphics_setFillRGBColor(r, g, b) {
- var color = Util.makeCssRgb(arguments);
+ var color = Util.makeCssRgb(r, g, b);
this.current.fillColor = color;
this.current.tspan = document.createElementNS(NS, 'svg:tspan');
this.current.xcoords = [];
},
setDash: function SVGGraphics_setDash(dashArray, dashPhase) {
@@ -7770,10 +9494,27 @@
array[i] = data.charCodeAt(i) & 0xFF;
}
return array.buffer;
}
+ var supportsMozChunked = (function supportsMozChunkedClosure() {
+ try {
+ var x = new XMLHttpRequest();
+ // Firefox 37- required .open() to be called before setting responseType.
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=707484
+ // Even though the URL is not visited, .open() could fail if the URL is
+ // blocked, e.g. via the connect-src CSP directive or the NoScript addon.
+ // When this error occurs, this feature detection method will mistakenly
+ // report that moz-chunked-arraybuffer is not supported in Firefox 37-.
+ x.open('GET', 'https://example.com');
+ x.responseType = 'moz-chunked-arraybuffer';
+ return x.responseType === 'moz-chunked-arraybuffer';
+ } catch (e) {
+ return false;
+ }
+ })();
+
NetworkManager.prototype = {
requestRange: function NetworkManager_requestRange(begin, end, listeners) {
var args = {
begin: begin,
end: end
@@ -7810,18 +9551,15 @@
pendingRequest.expectedStatus = 206;
} else {
pendingRequest.expectedStatus = 200;
}
- if (args.onProgressiveData) {
+ var useMozChunkedLoading = supportsMozChunked && !!args.onProgressiveData;
+ if (useMozChunkedLoading) {
xhr.responseType = 'moz-chunked-arraybuffer';
- if (xhr.responseType === 'moz-chunked-arraybuffer') {
- pendingRequest.onProgressiveData = args.onProgressiveData;
- pendingRequest.mozChunked = true;
- } else {
- xhr.responseType = 'arraybuffer';
- }
+ pendingRequest.onProgressiveData = args.onProgressiveData;
+ pendingRequest.mozChunked = true;
} else {
xhr.responseType = 'arraybuffer';
}
if (args.onError) {
@@ -7920,15 +9658,17 @@
begin: begin,
chunk: chunk
});
} else if (pendingRequest.onProgressiveData) {
pendingRequest.onDone(null);
- } else {
+ } else if (chunk) {
pendingRequest.onDone({
begin: 0,
chunk: chunk
});
+ } else if (pendingRequest.onError) {
+ pendingRequest.onError(xhr.status);
}
},
hasPendingRequests: function NetworkManager_hasPendingRequests() {
for (var xhrId in this.pendingRequests) {
@@ -7968,11 +9708,10 @@
return NetworkManager;
})();
-
var ChunkedStream = (function ChunkedStreamClosure() {
function ChunkedStream(length, chunkSize, manager) {
this.bytes = new Uint8Array(length);
this.start = 0;
this.pos = 0;
@@ -8081,22 +9820,17 @@
}
}
},
nextEmptyChunk: function ChunkedStream_nextEmptyChunk(beginChunk) {
- var chunk, n;
- for (chunk = beginChunk, n = this.numChunks; chunk < n; ++chunk) {
+ var chunk, numChunks = this.numChunks;
+ for (var i = 0; i < numChunks; ++i) {
+ chunk = (beginChunk + i) % numChunks; // Wrap around to beginning
if (!this.loadedChunks[chunk]) {
return chunk;
}
}
- // Wrap around to beginning
- for (chunk = 0; chunk < beginChunk; ++chunk) {
- if (!this.loadedChunks[chunk]) {
- return chunk;
- }
- }
return null;
},
hasChunk: function ChunkedStream_hasChunk(chunk) {
return !!this.loadedChunks[chunk];
@@ -8120,10 +9854,13 @@
},
getUint16: function ChunkedStream_getUint16() {
var b0 = this.getByte();
var b1 = this.getByte();
+ if (b0 === -1 || b1 === -1) {
+ return -1;
+ }
return (b0 << 8) + b1;
},
getInt32: function ChunkedStream_getInt32() {
var b0 = this.getByte();
@@ -8253,11 +9990,11 @@
this.currRequestId = 0;
this.chunksNeededByRequest = {};
this.requestsByChunk = {};
- this.callbacksByRequest = {};
+ this.promisesByRequest = {};
this.progressiveDataLength = 0;
this._loadedStreamCapability = createPromiseCapability();
if (args.initialData) {
@@ -8272,16 +10009,15 @@
// 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);
+ this._requestChunks(missingChunks);
return this._loadedStreamCapability.promise;
},
- requestChunks: function ChunkedStreamManager_requestChunks(chunks,
- callback) {
+ _requestChunks: function ChunkedStreamManager_requestChunks(chunks) {
var requestId = this.currRequestId++;
var chunksNeeded;
var i, ii;
this.chunksNeededByRequest[requestId] = chunksNeeded = {};
@@ -8290,17 +10026,15 @@
chunksNeeded[chunks[i]] = true;
}
}
if (isEmptyObj(chunksNeeded)) {
- if (callback) {
- callback();
- }
- return;
+ return Promise.resolve();
}
- this.callbacksByRequest[requestId] = callback;
+ var capability = createPromiseCapability();
+ this.promisesByRequest[requestId] = capability;
var chunksToRequest = [];
for (var chunk in chunksNeeded) {
chunk = chunk | 0;
if (!(chunk in this.requestsByChunk)) {
@@ -8309,30 +10043,31 @@
}
this.requestsByChunk[chunk].push(requestId);
}
if (!chunksToRequest.length) {
- return;
+ return capability.promise;
}
var groupedChunksToRequest = this.groupChunks(chunksToRequest);
for (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);
}
+
+ return capability.promise;
},
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) {
+ requestRange: function ChunkedStreamManager_requestRange(begin, end) {
end = Math.min(end, this.length);
var beginChunk = this.getBeginChunk(begin);
var endChunk = this.getEndChunk(end);
@@ -8340,15 +10075,14 @@
var chunks = [];
for (var chunk = beginChunk; chunk < endChunk; ++chunk) {
chunks.push(chunk);
}
- this.requestChunks(chunks, callback);
+ return this._requestChunks(chunks);
},
- requestRanges: function ChunkedStreamManager_requestRanges(ranges,
- callback) {
+ requestRanges: function ChunkedStreamManager_requestRanges(ranges) {
ranges = ranges || [];
var chunksToRequest = [];
for (var i = 0; i < ranges.length; i++) {
var beginChunk = this.getBeginChunk(ranges[i].begin);
@@ -8359,14 +10093,14 @@
}
}
}
chunksToRequest.sort(function(a, b) { return a - b; });
- this.requestChunks(chunksToRequest, callback);
+ return this._requestChunks(chunksToRequest);
},
- // Groups a sorted array of chunks into as few continguous larger
+ // Groups a sorted array of chunks into as few contiguous larger
// chunks as possible
groupChunks: function ChunkedStreamManager_groupChunks(chunks) {
var groupedChunks = [];
var beginChunk = -1;
var prevChunk = -1;
@@ -8458,21 +10192,19 @@
}
} else {
nextEmptyChunk = this.stream.nextEmptyChunk(endChunk);
}
if (isInt(nextEmptyChunk)) {
- this.requestChunks([nextEmptyChunk]);
+ this._requestChunks([nextEmptyChunk]);
}
}
for (i = 0; i < loadedRequests.length; ++i) {
requestId = loadedRequests[i];
- var callback = this.callbacksByRequest[requestId];
- delete this.callbacksByRequest[requestId];
- if (callback) {
- callback();
- }
+ var capability = this.promisesByRequest[requestId];
+ delete this.promisesByRequest[requestId];
+ capability.resolve();
}
this.msgHandler.send('DocProgress', {
loaded: this.stream.numChunksLoaded * this.chunkSize,
total: this.length
@@ -8487,39 +10219,39 @@
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;
+ },
+
+ abort: function ChunkedStreamManager_abort() {
+ if (this.networkManager) {
+ this.networkManager.abortAllRequests();
+ }
+ for(var requestId in this.promisesByRequest) {
+ var capability = this.promisesByRequest[requestId];
+ capability.reject(new Error('Request was aborted'));
+ }
}
};
return ChunkedStreamManager;
})();
-
-// The maximum number of bytes fetched per range request
-var RANGE_CHUNK_SIZE = 65536;
-
-// 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 = {
+ get docId() {
+ return this._docId;
+ },
+
onLoadedStream: function BasePdfManager_onLoadedStream() {
throw new NotImplementedException();
},
ensureDoc: function BasePdfManager_ensureDoc(prop, args) {
@@ -8532,11 +10264,11 @@
ensureCatalog: function BasePdfManager_ensureCatalog(prop, args) {
return this.ensure(this.pdfDocument.catalog, prop, args);
},
- getPage: function BasePdfManager_pagePage(pageIndex) {
+ getPage: function BasePdfManager_getPage(pageIndex) {
return this.pdfDocument.getPage(pageIndex);
},
cleanup: function BasePdfManager_cleanup() {
return this.pdfDocument.cleanup();
@@ -8544,11 +10276,11 @@
ensure: function BasePdfManager_ensure(obj, prop, args) {
return new NotImplementedException();
},
- requestRange: function BasePdfManager_ensure(begin, end) {
+ requestRange: function BasePdfManager_requestRange(begin, end) {
return new NotImplementedException();
},
requestLoadedStream: function BasePdfManager_requestLoadedStream() {
return new NotImplementedException();
@@ -8577,145 +10309,131 @@
return BasePdfManager;
})();
var LocalPdfManager = (function LocalPdfManagerClosure() {
- function LocalPdfManager(data, password) {
+ function LocalPdfManager(docId, data, password) {
+ this._docId = docId;
var stream = new Stream(data);
this.pdfDocument = new PDFDocument(this, stream, password);
this._loadedStreamCapability = createPromiseCapability();
this._loadedStreamCapability.resolve(stream);
}
- LocalPdfManager.prototype = Object.create(BasePdfManager.prototype);
- LocalPdfManager.prototype.constructor = LocalPdfManager;
-
- LocalPdfManager.prototype.ensure =
- function LocalPdfManager_ensure(obj, prop, args) {
- return new Promise(function (resolve, reject) {
- try {
- var value = obj[prop];
- var result;
- if (typeof value === 'function') {
- result = value.apply(obj, args);
- } else {
- result = value;
+ Util.inherit(LocalPdfManager, BasePdfManager, {
+ ensure: function LocalPdfManager_ensure(obj, prop, args) {
+ return new Promise(function (resolve, reject) {
+ try {
+ var value = obj[prop];
+ var result;
+ if (typeof value === 'function') {
+ result = value.apply(obj, args);
+ } else {
+ result = value;
+ }
+ resolve(result);
+ } catch (e) {
+ reject(e);
}
- resolve(result);
- } catch (e) {
- reject(e);
- }
- });
- };
+ });
+ },
- LocalPdfManager.prototype.requestRange =
- function LocalPdfManager_requestRange(begin, end) {
- return Promise.resolve();
- };
+ requestRange: function LocalPdfManager_requestRange(begin, end) {
+ return Promise.resolve();
+ },
- LocalPdfManager.prototype.requestLoadedStream =
- function LocalPdfManager_requestLoadedStream() {
- };
+ requestLoadedStream: function LocalPdfManager_requestLoadedStream() {
+ return;
+ },
- LocalPdfManager.prototype.onLoadedStream =
- function LocalPdfManager_getLoadedStream() {
- return this._loadedStreamCapability.promise;
- };
+ onLoadedStream: function LocalPdfManager_onLoadedStream() {
+ return this._loadedStreamCapability.promise;
+ },
- LocalPdfManager.prototype.terminate =
- function LocalPdfManager_terminate() {
- return;
- };
+ terminate: function LocalPdfManager_terminate() {
+ return;
+ }
+ });
return LocalPdfManager;
})();
var NetworkPdfManager = (function NetworkPdfManagerClosure() {
- function NetworkPdfManager(args, msgHandler) {
-
+ function NetworkPdfManager(docId, args, msgHandler) {
+ this._docId = docId;
this.msgHandler = msgHandler;
var params = {
msgHandler: msgHandler,
httpHeaders: args.httpHeaders,
withCredentials: args.withCredentials,
chunkedViewerLoading: args.chunkedViewerLoading,
disableAutoFetch: args.disableAutoFetch,
initialData: args.initialData
};
- this.streamManager = new ChunkedStreamManager(args.length, RANGE_CHUNK_SIZE,
+ this.streamManager = new ChunkedStreamManager(args.length,
+ args.rangeChunkSize,
args.url, params);
-
this.pdfDocument = new PDFDocument(this, this.streamManager.getStream(),
- args.password);
+ args.password);
}
- NetworkPdfManager.prototype = Object.create(BasePdfManager.prototype);
- NetworkPdfManager.prototype.constructor = NetworkPdfManager;
+ Util.inherit(NetworkPdfManager, BasePdfManager, {
+ ensure: function NetworkPdfManager_ensure(obj, prop, args) {
+ var pdfManager = this;
- NetworkPdfManager.prototype.ensure =
- function NetworkPdfManager_ensure(obj, prop, args) {
- var pdfManager = this;
-
- return new Promise(function (resolve, reject) {
- function ensureHelper() {
- try {
- var result;
- var value = obj[prop];
- if (typeof value === 'function') {
- result = value.apply(obj, args);
- } else {
- result = value;
+ return new Promise(function (resolve, reject) {
+ function ensureHelper() {
+ try {
+ var result;
+ var value = obj[prop];
+ if (typeof value === 'function') {
+ result = value.apply(obj, args);
+ } else {
+ result = value;
+ }
+ resolve(result);
+ } catch(e) {
+ if (!(e instanceof MissingDataException)) {
+ reject(e);
+ return;
+ }
+ pdfManager.streamManager.requestRange(e.begin, e.end).
+ then(ensureHelper, reject);
}
- resolve(result);
- } catch(e) {
- if (!(e instanceof MissingDataException)) {
- reject(e);
- return;
- }
- pdfManager.streamManager.requestRange(e.begin, e.end, ensureHelper);
}
- }
- ensureHelper();
- });
- };
-
- NetworkPdfManager.prototype.requestRange =
- function NetworkPdfManager_requestRange(begin, end) {
- return new Promise(function (resolve) {
- this.streamManager.requestRange(begin, end, function() {
- resolve();
+ ensureHelper();
});
- }.bind(this));
- };
+ },
- NetworkPdfManager.prototype.requestLoadedStream =
- function NetworkPdfManager_requestLoadedStream() {
- this.streamManager.requestAllChunks();
- };
+ requestRange: function NetworkPdfManager_requestRange(begin, end) {
+ return this.streamManager.requestRange(begin, end);
+ },
- NetworkPdfManager.prototype.sendProgressiveData =
- function NetworkPdfManager_sendProgressiveData(chunk) {
- this.streamManager.onReceiveData({ chunk: chunk });
- };
+ requestLoadedStream: function NetworkPdfManager_requestLoadedStream() {
+ this.streamManager.requestAllChunks();
+ },
- NetworkPdfManager.prototype.onLoadedStream =
- function NetworkPdfManager_getLoadedStream() {
- return this.streamManager.onLoadedStream();
- };
+ sendProgressiveData:
+ function NetworkPdfManager_sendProgressiveData(chunk) {
+ this.streamManager.onReceiveData({ chunk: chunk });
+ },
- NetworkPdfManager.prototype.terminate =
- function NetworkPdfManager_terminate() {
- this.streamManager.networkManager.abortAllRequests();
- };
+ onLoadedStream: function NetworkPdfManager_onLoadedStream() {
+ return this.streamManager.onLoadedStream();
+ },
+ terminate: function NetworkPdfManager_terminate() {
+ this.streamManager.abort();
+ }
+ });
+
return NetworkPdfManager;
})();
-
var Page = (function PageClosure() {
var LETTER_SIZE_MEDIABOX = [0, 0, 612, 792];
function Page(pdfManager, xref, pageIndex, pageDict, ref, fontCache) {
@@ -8734,36 +10452,48 @@
Page.prototype = {
getPageProp: function Page_getPageProp(key) {
return this.pageDict.get(key);
},
- getInheritedPageProp: function Page_inheritPageProp(key) {
- var dict = this.pageDict;
- var value = dict.get(key);
- while (value === undefined) {
- dict = dict.get('Parent');
- if (!dict) {
+ getInheritedPageProp: function Page_getInheritedPageProp(key) {
+ var dict = this.pageDict, valueArray = null, loopCount = 0;
+ var MAX_LOOP_COUNT = 100;
+ // Always walk up the entire parent chain, to be able to find
+ // e.g. \Resources placed on multiple levels of the tree.
+ while (dict) {
+ var value = dict.get(key);
+ if (value) {
+ if (!valueArray) {
+ valueArray = [];
+ }
+ valueArray.push(value);
+ }
+ if (++loopCount > MAX_LOOP_COUNT) {
+ warn('Page_getInheritedPageProp: maximum loop count exceeded.');
break;
}
- value = dict.get(key);
+ dict = dict.get('Parent');
}
- return value;
+ if (!valueArray) {
+ return Dict.empty;
+ }
+ if (valueArray.length === 1 || !isDict(valueArray[0]) ||
+ loopCount > MAX_LOOP_COUNT) {
+ return valueArray[0];
+ }
+ return Dict.merge(this.xref, valueArray);
},
get content() {
return this.getPageProp('Contents');
},
get resources() {
- var value = this.getInheritedPageProp('Resources');
// For robustness: The spec states that a \Resources entry has to be
- // present, but can be empty. Some document omit it still. In this case
- // return an empty dictionary:
- if (value === undefined) {
- value = Dict.empty;
- }
- return shadow(this, 'resources', value);
+ // present, but can be empty. Some document omit it still, in this case
+ // we return an empty dictionary.
+ return shadow(this, 'resources', this.getInheritedPageProp('Resources'));
},
get mediaBox() {
var obj = this.getInheritedPageProp('MediaBox');
// Reset invalid media box to letter size.
@@ -8789,15 +10519,10 @@
return shadow(this, 'view', mediaBox);
}
return shadow(this, 'view', cropBox);
},
- get annotationRefs() {
- return shadow(this, 'annotationRefs',
- this.getInheritedPageProp('Annots'));
- },
-
get rotate() {
var rotate = this.getInheritedPageProp('Rotate') || 0;
// Normalize rotation so it's a multiple of 90 and between 0 and 270
if (rotate % 90 !== 0) {
rotate = 0;
@@ -8843,11 +10568,11 @@
this.xref);
return objectLoader.load();
}.bind(this));
},
- getOperatorList: function Page_getOperatorList(handler, intent) {
+ getOperatorList: function Page_getOperatorList(handler, task, intent) {
var self = this;
var pdfManager = this.pdfManager;
var contentStreamPromise = pdfManager.ensure(this, 'getContentStream',
[]);
@@ -8876,12 +10601,12 @@
handler.send('StartRenderPage', {
transparency: partialEvaluator.hasBlendModes(self.resources),
pageIndex: self.pageIndex,
intent: intent
});
- return partialEvaluator.getOperatorList(contentStream, self.resources,
- opList).then(function () {
+ return partialEvaluator.getOperatorList(contentStream, task,
+ self.resources, opList).then(function () {
return opList;
});
});
var annotationsPromise = pdfManager.ensure(this, 'annotations');
@@ -8894,19 +10619,20 @@
pageOpList.flush(true);
return pageOpList;
}
var annotationsReadyPromise = Annotation.appendToOperatorList(
- annotations, pageOpList, pdfManager, partialEvaluator, intent);
+ annotations, pageOpList, partialEvaluator, task, intent);
return annotationsReadyPromise.then(function () {
pageOpList.flush(true);
return pageOpList;
});
});
},
- extractTextContent: function Page_extractTextContent() {
+ extractTextContent: function Page_extractTextContent(task,
+ normalizeWhitespace) {
var handler = {
on: function nullHandlerOn() {},
send: function nullHandlerSend() {}
};
@@ -8931,29 +10657,39 @@
'p' + self.pageIndex + '_',
self.idCounters,
self.fontCache);
return partialEvaluator.getTextContent(contentStream,
- self.resources);
+ task,
+ self.resources,
+ /* stateManager = */ null,
+ normalizeWhitespace);
});
},
- getAnnotationsData: function Page_getAnnotationsData() {
+ getAnnotationsData: function Page_getAnnotationsData(intent) {
var annotations = this.annotations;
var annotationsData = [];
for (var i = 0, n = annotations.length; i < n; ++i) {
- annotationsData.push(annotations[i].getData());
+ if (intent) {
+ if (!(intent === 'display' && annotations[i].viewable) &&
+ !(intent === 'print' && annotations[i].printable)) {
+ continue;
+ }
+ }
+ annotationsData.push(annotations[i].data);
}
return annotationsData;
},
get annotations() {
var annotations = [];
- var annotationRefs = (this.annotationRefs || []);
+ var annotationRefs = this.getInheritedPageProp('Annots') || [];
+ var annotationFactory = new AnnotationFactory();
for (var i = 0, n = annotationRefs.length; i < n; ++i) {
var annotationRef = annotationRefs[i];
- var annotation = Annotation.fromRef(this.xref, annotationRef);
+ var annotation = annotationFactory.create(this.xref, annotationRef);
if (annotation) {
annotations.push(annotation);
}
}
return shadow(this, 'annotations', annotations);
@@ -8969,10 +10705,14 @@
* 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() {
+ var FINGERPRINT_FIRST_BYTES = 1024;
+ var EMPTY_FINGERPRINT = '\x00\x00\x00\x00\x00\x00\x00' +
+ '\x00\x00\x00\x00\x00\x00\x00\x00\x00';
+
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);
@@ -9028,10 +10768,14 @@
};
PDFDocument.prototype = {
parse: function PDFDocument_parse(recoveryMode) {
this.setup(recoveryMode);
+ var version = this.catalog.catDict.get('Version');
+ if (isName(version)) {
+ this.pdfFormatVersion = version.name;
+ }
try {
// checking if AcroForm is present
this.acroForm = this.catalog.catDict.get('AcroForm');
if (this.acroForm) {
this.xfa = this.acroForm.get('XFA');
@@ -9129,12 +10873,14 @@
if (version.length >= MAX_VERSION_LENGTH) {
break;
}
version += String.fromCharCode(ch);
}
- // removing "%PDF-"-prefix
- this.pdfFormatVersion = version.substring(5);
+ if (!this.pdfFormatVersion) {
+ // removing "%PDF-"-prefix
+ this.pdfFormatVersion = version.substring(5);
+ }
return;
}
// May not be a PDF file, continue anyway.
},
parseStartXRef: function PDFDocument_parseStartXRef() {
@@ -9181,19 +10927,27 @@
}
return shadow(this, 'documentInfo', docInfo);
},
get fingerprint() {
var xref = this.xref, hash, fileID = '';
+ var idArray = xref.trailer.get('ID');
- if (xref.trailer.has('ID')) {
- hash = stringToBytes(xref.trailer.get('ID')[0]);
+ if (idArray && isArray(idArray) && idArray[0] && isString(idArray[0]) &&
+ idArray[0] !== EMPTY_FINGERPRINT) {
+ hash = stringToBytes(idArray[0]);
} else {
- hash = calculateMD5(this.stream.bytes.subarray(0, 100), 0, 100);
+ if (this.stream.ensureRange) {
+ this.stream.ensureRange(0,
+ Math.min(FINGERPRINT_FIRST_BYTES, this.stream.end));
+ }
+ hash = calculateMD5(this.stream.bytes.subarray(0,
+ FINGERPRINT_FIRST_BYTES), 0, FINGERPRINT_FIRST_BYTES);
}
for (var i = 0, n = hash.length; i < n; i++) {
- fileID += hash[i].toString(16);
+ var hex = hash[i].toString(16);
+ fileID += hex.length === 1 ? '0' + hex : hex;
}
return shadow(this, 'fingerprint', fileID);
},
@@ -9208,11 +10962,10 @@
return PDFDocument;
})();
-
var Name = (function NameClosure() {
function Name(name) {
this.name = name;
}
@@ -9321,10 +11074,27 @@
return xref.fetchIfRefAsync(value);
}
return Promise.resolve(value);
},
+ // Same as get(), but dereferences all elements if the result is an Array.
+ getArray: function Dict_getArray(key1, key2, key3) {
+ var value = this.get(key1, key2, key3);
+ var xref = this.xref;
+ if (!isArray(value) || !xref) {
+ return value;
+ }
+ value = value.slice(); // Ensure that we don't modify the Dict data.
+ for (var i = 0, ii = value.length; i < ii; i++) {
+ if (!isRef(value[i])) {
+ continue;
+ }
+ value[i] = xref.fetch(value[i]);
+ }
+ return value;
+ },
+
// no dereferencing
getRaw: function Dict_getRaw(key) {
return this.map[key];
},
@@ -9378,10 +11148,14 @@
item.target[item.key] = dereferenced;
}
return all;
},
+ getKeys: function Dict_getKeys() {
+ return Object.keys(this.map);
+ },
+
set: function Dict_set(key, value) {
this.map[key] = value;
},
has: function Dict_has(key) {
@@ -9395,10 +11169,28 @@
}
};
Dict.empty = new Dict(null);
+ Dict.merge = function Dict_merge(xref, dictArray) {
+ var mergedDict = new Dict(xref);
+
+ for (var i = 0, ii = dictArray.length; i < ii; i++) {
+ var dict = dictArray[i];
+ if (!isDict(dict)) {
+ continue;
+ }
+ for (var keyName in dict.map) {
+ if (mergedDict.map[keyName]) {
+ continue;
+ }
+ mergedDict.map[keyName] = dict.map[keyName];
+ }
+ }
+ return mergedDict;
+ };
+
return Dict;
})();
var Ref = (function RefClosure() {
function Ref(num, gen) {
@@ -9649,29 +11441,23 @@
function fetchDestination(dest) {
return isDict(dest) ? dest.get('D') : dest;
}
var xref = this.xref;
- var dest, nameTreeRef, nameDictionaryRef;
+ var dest = null, nameTreeRef, nameDictionaryRef;
var obj = this.catDict.get('Names');
if (obj && obj.has('Dests')) {
nameTreeRef = obj.getRaw('Dests');
} else if (this.catDict.has('Dests')) {
nameDictionaryRef = this.catDict.get('Dests');
}
- if (nameDictionaryRef) {
- // reading simple destination dictionary
- obj = nameDictionaryRef;
- obj.forEach(function catalogForEach(key, value) {
- if (!value) {
- return;
- }
- if (key === destinationId) {
- dest = fetchDestination(value);
- }
- });
+ if (nameDictionaryRef) { // Simple destination dictionary.
+ var value = nameDictionaryRef.get(destinationId);
+ if (value) {
+ dest = fetchDestination(value);
+ }
}
if (nameTreeRef) {
var nameTree = new NameTree(nameTreeRef, xref);
dest = fetchDestination(nameTree.get(destinationId));
}
@@ -9704,50 +11490,52 @@
get javaScript() {
var xref = this.xref;
var obj = this.catDict.get('Names');
var javaScript = [];
+ function appendIfJavaScriptDict(jsDict) {
+ var type = jsDict.get('S');
+ if (!isName(type) || type.name !== 'JavaScript') {
+ return;
+ }
+ var js = jsDict.get('JS');
+ if (isStream(js)) {
+ js = bytesToString(js.getBytes());
+ } else if (!isString(js)) {
+ return;
+ }
+ javaScript.push(stringToPDFString(js));
+ }
if (obj && obj.has('JavaScript')) {
var nameTree = new NameTree(obj.getRaw('JavaScript'), xref);
var names = nameTree.getAll();
for (var name in names) {
if (!names.hasOwnProperty(name)) {
continue;
}
// We don't really use the JavaScript right now. This code is
// defensive so we don't cause errors on document load.
var jsDict = names[name];
- if (!isDict(jsDict)) {
- continue;
+ if (isDict(jsDict)) {
+ appendIfJavaScriptDict(jsDict);
}
- var type = jsDict.get('S');
- if (!isName(type) || type.name !== 'JavaScript') {
- continue;
- }
- var js = jsDict.get('JS');
- if (!isString(js) && !isStream(js)) {
- continue;
- }
- if (isStream(js)) {
- js = bytesToString(js.getBytes());
- }
- javaScript.push(stringToPDFString(js));
}
}
// Append OpenAction actions to javaScript array
var openactionDict = this.catDict.get('OpenAction');
- if (isDict(openactionDict)) {
- var objType = openactionDict.get('Type');
+ if (isDict(openactionDict, 'Action')) {
var actionType = openactionDict.get('S');
- var action = openactionDict.get('N');
- var isPrintAction = (isName(objType) && objType.name === 'Action' &&
- isName(actionType) && actionType.name === 'Named' &&
- isName(action) && action.name === 'Print');
-
- if (isPrintAction) {
- javaScript.push('print(true);');
+ if (isName(actionType) && actionType.name === 'Named') {
+ // The named Print action is not a part of the PDF 1.7 specification,
+ // but is supported by many PDF readers/writers (including Adobe's).
+ var action = openactionDict.get('N');
+ if (isName(action) && action.name === 'Print') {
+ javaScript.push('print({});');
+ }
+ } else {
+ appendIfJavaScriptDict(openactionDict);
}
}
return shadow(this, 'javaScript', javaScript);
},
@@ -9783,18 +11571,19 @@
getPageDict: function Catalog_getPageDict(pageIndex) {
var capability = createPromiseCapability();
var nodesToVisit = [this.catDict.getRaw('Pages')];
var currentPageIndex = 0;
var xref = this.xref;
+ var checkAllKids = false;
function next() {
while (nodesToVisit.length) {
var currentNode = nodesToVisit.pop();
if (isRef(currentNode)) {
xref.fetchAsync(currentNode).then(function (obj) {
- if ((isDict(obj, 'Page') || (isDict(obj) && !obj.has('Kids')))) {
+ if (isDict(obj, 'Page') || (isDict(obj) && !obj.has('Kids'))) {
if (pageIndex === currentPageIndex) {
capability.resolve([obj, currentNode]);
} else {
currentPageIndex++;
next();
@@ -9805,25 +11594,30 @@
next();
}, capability.reject);
return;
}
- // must be a child page dictionary
+ // Must be a child page dictionary.
assert(
isDict(currentNode),
'page dictionary kid reference points to wrong type of object'
);
var count = currentNode.get('Count');
+ // If the current node doesn't have any children, avoid getting stuck
+ // in an empty node further down in the tree (see issue5644.pdf).
+ if (count === 0) {
+ checkAllKids = true;
+ }
// Skip nodes where the page can't be.
if (currentPageIndex + count <= pageIndex) {
currentPageIndex += count;
continue;
}
var kids = currentNode.get('Kids');
assert(isArray(kids), 'page dictionary kids object is not an array');
- if (count === kids.length) {
+ if (!checkAllKids && count === kids.length) {
// Nodes that don't have the page have been skipped and this is the
// bottom of the tree which means the page requested must be a
// descendant of this pages node. Ideally we would just resolve the
// promise with the page ref here, but there is the case where more
// pages nodes could link to single a page (see issue 3666 pdf). To
@@ -10175,13 +11969,16 @@
},
indexObjects: function XRef_indexObjects() {
// Simple scan through the PDF content to find objects,
// trailers and XRef streams.
+ var TAB = 0x9, LF = 0xA, CR = 0xD, SPACE = 0x20;
+ var PERCENT = 0x25, LT = 0x3C;
+
function readToken(data, offset) {
var token = '', ch = data[offset];
- while (ch !== 13 && ch !== 10) {
+ while (ch !== LF && ch !== CR && ch !== LT) {
if (++offset >= data.length) {
break;
}
token += String.fromCharCode(ch);
ch = data[offset];
@@ -10203,63 +12000,73 @@
offset++;
skipped++;
}
return skipped;
}
+ var objRegExp = /^(\d+)\s+(\d+)\s+obj\b/;
var trailerBytes = new Uint8Array([116, 114, 97, 105, 108, 101, 114]);
var startxrefBytes = new Uint8Array([115, 116, 97, 114, 116, 120, 114,
101, 102]);
var endobjBytes = new Uint8Array([101, 110, 100, 111, 98, 106]);
var xrefBytes = new Uint8Array([47, 88, 82, 101, 102]);
+ // Clear out any existing entries, since they may be bogus.
+ this.entries.length = 0;
+
var stream = this.stream;
stream.pos = 0;
var buffer = stream.getBytes();
var position = stream.start, length = buffer.length;
var trailers = [], xrefStms = [];
while (position < length) {
var ch = buffer[position];
- if (ch === 32 || ch === 9 || ch === 13 || ch === 10) {
+ if (ch === TAB || ch === LF || ch === CR || ch === SPACE) {
++position;
continue;
}
- if (ch === 37) { // %-comment
+ if (ch === PERCENT) { // %-comment
do {
++position;
if (position >= length) {
break;
}
ch = buffer[position];
- } while (ch !== 13 && ch !== 10);
+ } while (ch !== LF && ch !== CR);
continue;
}
var token = readToken(buffer, position);
var m;
- if (token === 'xref') {
+ if (token.indexOf('xref') === 0 &&
+ (token.length === 4 || /\s/.test(token[4]))) {
position += skipUntil(buffer, position, trailerBytes);
trailers.push(position);
position += skipUntil(buffer, position, startxrefBytes);
- } else if ((m = /^(\d+)\s+(\d+)\s+obj\b/.exec(token))) {
- this.entries[m[1]] = {
- offset: position,
- gen: m[2] | 0,
- uncompressed: true
- };
-
+ } else if ((m = objRegExp.exec(token))) {
+ if (typeof this.entries[m[1]] === 'undefined') {
+ this.entries[m[1]] = {
+ offset: position - stream.start,
+ gen: m[2] | 0,
+ uncompressed: true
+ };
+ }
var contentLength = skipUntil(buffer, position, endobjBytes) + 7;
var content = buffer.subarray(position, position + contentLength);
// checking XRef stream suspect
// (it shall have '/XRef' and next char is not a letter)
var xrefTagOffset = skipUntil(content, 0, xrefBytes);
if (xrefTagOffset < contentLength &&
content[xrefTagOffset + 5] < 64) {
- xrefStms.push(position);
- this.xrefstms[position] = 1; // don't read it recursively
+ xrefStms.push(position - stream.start);
+ this.xrefstms[position - stream.start] = 1; // Avoid recursion
}
position += contentLength;
+ } else if (token.indexOf('trailer') === 0 &&
+ (token.length === 7 || /\s/.test(token[7]))) {
+ trailers.push(position);
+ position += skipUntil(buffer, position, startxrefBytes);
} else {
position += token.length + 1;
}
}
// reading XRef streams
@@ -10510,13 +12317,13 @@
return new Promise(function tryFetch(resolve, reject) {
try {
resolve(xref.fetch(ref, suppressEncryption));
} catch (e) {
if (e instanceof MissingDataException) {
- streamManager.requestRange(e.begin, e.end, function () {
+ streamManager.requestRange(e.begin, e.end).then(function () {
tryFetch(resolve, reject);
- });
+ }, reject);
return;
}
reject(e);
}
});
@@ -10571,11 +12378,11 @@
continue;
}
var names = obj.get('Names');
if (names) {
for (i = 0, n = names.length; i < n; i += 2) {
- dict[names[i]] = xref.fetchIfRef(names[i + 1]);
+ dict[xref.fetchIfRef(names[i])] = xref.fetchIfRef(names[i + 1]);
}
}
}
return dict;
},
@@ -10597,11 +12404,11 @@
loopCount++;
if (loopCount > MAX_NAMES_LEVELS) {
warn('Search depth limit for named destionations has been reached.');
return null;
}
-
+
var kids = kidsOrNames.get('Kids');
if (!isArray(kids)) {
return null;
}
@@ -10610,13 +12417,13 @@
while (l <= r) {
m = (l + r) >> 1;
var kid = xref.fetchIfRef(kids[m]);
var limits = kid.get('Limits');
- if (destinationId < limits[0]) {
+ if (destinationId < xref.fetchIfRef(limits[0])) {
r = m - 1;
- } else if (destinationId > limits[1]) {
+ } else if (destinationId > xref.fetchIfRef(limits[1])) {
l = m + 1;
} else {
kidsOrNames = xref.fetchIfRef(kids[m]);
break;
}
@@ -10636,13 +12443,13 @@
r = names.length - 2;
while (l <= r) {
// Check only even indices (0, 2, 4, ...) because the
// odd indices contain the actual D array.
m = (l + r) & ~1;
- if (destinationId < names[m]) {
+ if (destinationId < xref.fetchIfRef(names[m])) {
r = m - 2;
- } else if (destinationId > names[m]) {
+ } else if (destinationId > xref.fetchIfRef(names[m])) {
l = m + 2;
} else {
return xref.fetchIfRef(names[m + 1]);
}
}
@@ -10652,14 +12459,14 @@
};
return NameTree;
})();
/**
- * "A PDF file can refer to the contents of another file by using a File
+ * "A PDF file can refer to the contents of another file by using a File
* Specification (PDF 1.1)", see the spec (7.11) for more details.
* NOTE: Only embedded files are supported (as part of the attachments support)
- * TODO: support the 'URL' file system (with caching if !/V), portable
+ * TODO: support the 'URL' file system (with caching if !/V), portable
* collections attributes and related files (/RF)
*/
var FileSpec = (function FileSpecClosure() {
function FileSpec(root, xref) {
if (!root || !isDict(root)) {
@@ -10788,10 +12595,11 @@
function ObjectLoader(obj, keys, xref) {
this.obj = obj;
this.keys = keys;
this.xref = xref;
this.refSet = null;
+ this.capability = null;
}
ObjectLoader.prototype = {
load: function ObjectLoader_load() {
var keys = this.keys;
@@ -10808,15 +12616,15 @@
var nodesToVisit = [];
for (var i = 0; i < keys.length; i++) {
nodesToVisit.push(this.obj[keys[i]]);
}
- this.walk(nodesToVisit);
+ this._walk(nodesToVisit);
return this.capability.promise;
},
- walk: function ObjectLoader_walk(nodesToVisit) {
+ _walk: function ObjectLoader_walk(nodesToVisit) {
var nodesToRevisit = [];
var pendingRequests = [];
// DFS walk of the object graph.
while (nodesToVisit.length) {
var currentNode = nodesToVisit.pop();
@@ -10859,23 +12667,23 @@
addChildren(currentNode, nodesToVisit);
}
if (pendingRequests.length) {
- this.xref.stream.manager.requestRanges(pendingRequests,
+ this.xref.stream.manager.requestRanges(pendingRequests).then(
function pendingRequestCallback() {
nodesToVisit = nodesToRevisit;
for (var i = 0; i < nodesToRevisit.length; i++) {
var node = nodesToRevisit[i];
// Remove any reference nodes from the currrent refset so they
// aren't skipped when we revist them.
if (isRef(node)) {
this.refSet.remove(node);
}
}
- this.walk(nodesToVisit);
- }.bind(this));
+ this._walk(nodesToVisit);
+ }.bind(this), this.capability.reject);
return;
}
// Everything is loaded.
this.refSet = null;
this.capability.resolve();
@@ -10985,14 +12793,61 @@
'eightinferior', 'nineinferior', 'centinferior', 'dollarinferior',
'periodinferior', 'commainferior'
];
-
var DEFAULT_ICON_SIZE = 22; // px
-var SUPPORTED_TYPES = ['Link', 'Text', 'Widget'];
+/**
+ * @class
+ * @alias AnnotationFactory
+ */
+function AnnotationFactory() {}
+AnnotationFactory.prototype = /** @lends AnnotationFactory.prototype */ {
+ /**
+ * @param {XRef} xref
+ * @param {Object} ref
+ * @returns {Annotation}
+ */
+ create: function AnnotationFactory_create(xref, ref) {
+ var dict = xref.fetchIfRef(ref);
+ if (!isDict(dict)) {
+ return;
+ }
+
+ // Determine the annotation's subtype.
+ var subtype = dict.get('Subtype');
+ subtype = isName(subtype) ? subtype.name : '';
+
+ // Return the right annotation object based on the subtype and field type.
+ var parameters = {
+ dict: dict,
+ ref: ref
+ };
+
+ switch (subtype) {
+ case 'Link':
+ return new LinkAnnotation(parameters);
+
+ case 'Text':
+ return new TextAnnotation(parameters);
+
+ case 'Widget':
+ var fieldType = Util.getInheritableProperty(dict, 'FT');
+ if (isName(fieldType) && fieldType.name === 'Tx') {
+ return new TextWidgetAnnotation(parameters);
+ }
+ return new WidgetAnnotation(parameters);
+
+ default:
+ warn('Unimplemented annotation type "' + subtype + '", ' +
+ 'falling back to base annotation');
+ return new Annotation(parameters);
+ }
+ }
+};
+
var Annotation = (function AnnotationClosure() {
// 12.5.5: Algorithm: Appearance streams
function getTransformMatrix(rect, bbox, matrix) {
var bounds = Util.getAxialAlignedBoundingBox(bbox, matrix);
var minX = bounds[0];
@@ -11037,105 +12892,185 @@
return appearance;
}
function Annotation(params) {
var dict = params.dict;
- var data = this.data = {};
- data.subtype = dict.get('Subtype').name;
- var rect = dict.get('Rect') || [0, 0, 0, 0];
- data.rect = Util.normalizeRect(rect);
- data.annotationFlags = dict.get('F');
+ this.setFlags(dict.get('F'));
+ this.setRectangle(dict.get('Rect'));
+ this.setColor(dict.get('C'));
+ this.setBorderStyle(dict);
+ this.appearance = getDefaultAppearance(dict);
- var color = dict.get('C');
- if (isArray(color) && color.length === 3) {
- // TODO(mack): currently only supporting rgb; need support different
- // colorspaces
- data.color = color;
- } else {
- data.color = [0, 0, 0];
- }
+ // Expose public properties using a data object.
+ this.data = {};
+ this.data.id = params.ref.num;
+ this.data.subtype = dict.get('Subtype').name;
+ this.data.annotationFlags = this.flags;
+ this.data.rect = this.rectangle;
+ this.data.color = this.color;
+ this.data.borderStyle = this.borderStyle;
+ this.data.hasAppearance = !!this.appearance;
+ }
- // Some types of annotations have border style dict which has more
- // info than the border array
- if (dict.has('BS')) {
- var borderStyle = dict.get('BS');
- data.borderWidth = borderStyle.has('W') ? borderStyle.get('W') : 1;
- } else {
- var borderArray = dict.get('Border') || [0, 0, 1];
- data.borderWidth = borderArray[2] || 0;
+ Annotation.prototype = {
+ /**
+ * @return {boolean}
+ */
+ get viewable() {
+ if (this.flags) {
+ return !this.hasFlag(AnnotationFlag.INVISIBLE) &&
+ !this.hasFlag(AnnotationFlag.HIDDEN) &&
+ !this.hasFlag(AnnotationFlag.NOVIEW);
+ }
+ return true;
+ },
- // TODO: implement proper support for annotations with line dash patterns.
- var dashArray = borderArray[3];
- if (data.borderWidth > 0 && dashArray) {
- if (!isArray(dashArray)) {
- // Ignore the border if dashArray is not actually an array,
- // this is consistent with the behaviour in Adobe Reader.
- data.borderWidth = 0;
- } else {
- var dashArrayLength = dashArray.length;
- if (dashArrayLength > 0) {
- // According to the PDF specification: the elements in a dashArray
- // shall be numbers that are nonnegative and not all equal to zero.
- var isInvalid = false;
- var numPositive = 0;
- for (var i = 0; i < dashArrayLength; i++) {
- var validNumber = (+dashArray[i] >= 0);
- if (!validNumber) {
- isInvalid = true;
- break;
- } else if (dashArray[i] > 0) {
- numPositive++;
- }
- }
- if (isInvalid || numPositive === 0) {
- data.borderWidth = 0;
- }
- }
- }
+ /**
+ * @return {boolean}
+ */
+ get printable() {
+ if (this.flags) {
+ return this.hasFlag(AnnotationFlag.PRINT) &&
+ !this.hasFlag(AnnotationFlag.INVISIBLE) &&
+ !this.hasFlag(AnnotationFlag.HIDDEN);
}
- }
+ return false;
+ },
- this.appearance = getDefaultAppearance(dict);
- data.hasAppearance = !!this.appearance;
- data.id = params.ref.num;
- }
+ /**
+ * Set the flags.
+ *
+ * @public
+ * @memberof Annotation
+ * @param {number} flags - Unsigned 32-bit integer specifying annotation
+ * characteristics
+ * @see {@link shared/util.js}
+ */
+ setFlags: function Annotation_setFlags(flags) {
+ if (isInt(flags)) {
+ this.flags = flags;
+ } else {
+ this.flags = 0;
+ }
+ },
- Annotation.prototype = {
-
- getData: function Annotation_getData() {
- return this.data;
+ /**
+ * Check if a provided flag is set.
+ *
+ * @public
+ * @memberof Annotation
+ * @param {number} flag - Hexadecimal representation for an annotation
+ * characteristic
+ * @return {boolean}
+ * @see {@link shared/util.js}
+ */
+ hasFlag: function Annotation_hasFlag(flag) {
+ if (this.flags) {
+ return (this.flags & flag) > 0;
+ }
+ return false;
},
- isInvisible: function Annotation_isInvisible() {
- var data = this.data;
- if (data && SUPPORTED_TYPES.indexOf(data.subtype) !== -1) {
- return false;
+ /**
+ * Set the rectangle.
+ *
+ * @public
+ * @memberof Annotation
+ * @param {Array} rectangle - The rectangle array with exactly four entries
+ */
+ setRectangle: function Annotation_setRectangle(rectangle) {
+ if (isArray(rectangle) && rectangle.length === 4) {
+ this.rectangle = Util.normalizeRect(rectangle);
} else {
- return !!(data &&
- data.annotationFlags && // Default: not invisible
- data.annotationFlags & 0x1); // Invisible
+ this.rectangle = [0, 0, 0, 0];
}
},
- isViewable: function Annotation_isViewable() {
- var data = this.data;
- return !!(!this.isInvisible() &&
- data &&
- (!data.annotationFlags ||
- !(data.annotationFlags & 0x22)) && // Hidden or NoView
- data.rect); // rectangle is necessary
+ /**
+ * Set the color and take care of color space conversion.
+ *
+ * @public
+ * @memberof Annotation
+ * @param {Array} color - The color array containing either 0
+ * (transparent), 1 (grayscale), 3 (RGB) or
+ * 4 (CMYK) elements
+ */
+ setColor: function Annotation_setColor(color) {
+ var rgbColor = new Uint8Array(3); // Black in RGB color space (default)
+ if (!isArray(color)) {
+ this.color = rgbColor;
+ return;
+ }
+
+ switch (color.length) {
+ case 0: // Transparent, which we indicate with a null value
+ this.color = null;
+ break;
+
+ case 1: // Convert grayscale to RGB
+ ColorSpace.singletons.gray.getRgbItem(color, 0, rgbColor, 0);
+ this.color = rgbColor;
+ break;
+
+ case 3: // Convert RGB percentages to RGB
+ ColorSpace.singletons.rgb.getRgbItem(color, 0, rgbColor, 0);
+ this.color = rgbColor;
+ break;
+
+ case 4: // Convert CMYK to RGB
+ ColorSpace.singletons.cmyk.getRgbItem(color, 0, rgbColor, 0);
+ this.color = rgbColor;
+ break;
+
+ default:
+ this.color = rgbColor;
+ break;
+ }
},
- isPrintable: function Annotation_isPrintable() {
- var data = this.data;
- return !!(!this.isInvisible() &&
- data &&
- data.annotationFlags && // Default: not printable
- data.annotationFlags & 0x4 && // Print
- !(data.annotationFlags & 0x2) && // Hidden
- data.rect); // rectangle is necessary
+ /**
+ * Set the border style (as AnnotationBorderStyle object).
+ *
+ * @public
+ * @memberof Annotation
+ * @param {Dict} borderStyle - The border style dictionary
+ */
+ setBorderStyle: function Annotation_setBorderStyle(borderStyle) {
+ this.borderStyle = new AnnotationBorderStyle();
+ if (!isDict(borderStyle)) {
+ return;
+ }
+ if (borderStyle.has('BS')) {
+ var dict = borderStyle.get('BS');
+ var dictType;
+
+ if (!dict.has('Type') || (isName(dictType = dict.get('Type')) &&
+ dictType.name === 'Border')) {
+ this.borderStyle.setWidth(dict.get('W'));
+ this.borderStyle.setStyle(dict.get('S'));
+ this.borderStyle.setDashArray(dict.get('D'));
+ }
+ } else if (borderStyle.has('Border')) {
+ var array = borderStyle.get('Border');
+ if (isArray(array) && array.length >= 3) {
+ this.borderStyle.setHorizontalCornerRadius(array[0]);
+ this.borderStyle.setVerticalCornerRadius(array[1]);
+ this.borderStyle.setWidth(array[2]);
+
+ if (array.length === 4) { // Dash array available
+ this.borderStyle.setDashArray(array[3]);
+ }
+ }
+ } else {
+ // There are no border entries in the dictionary. According to the
+ // specification, we should draw a solid border of width 1 in that
+ // case, but Adobe Reader did not implement that part of the
+ // specification and instead draws no border at all, so we do the same.
+ // See also https://github.com/mozilla/pdf.js/issues/6179.
+ this.borderStyle.setWidth(0);
+ }
},
loadResources: function Annotation_loadResources(keys) {
return new Promise(function (resolve, reject) {
this.appearance.dict.getAsync('Resources').then(function (resources) {
@@ -11151,18 +13086,16 @@
}, reject);
}, reject);
}.bind(this));
},
- getOperatorList: function Annotation_getOperatorList(evaluator) {
-
+ getOperatorList: function Annotation_getOperatorList(evaluator, task) {
if (!this.appearance) {
return Promise.resolve(new OperatorList());
}
var data = this.data;
-
var appearanceDict = this.appearance.dict;
var resourcesPromise = this.loadResources([
'ExtGState',
'ColorSpace',
'Pattern',
@@ -11178,134 +13111,204 @@
var self = this;
return resourcesPromise.then(function(resources) {
var opList = new OperatorList();
opList.addOp(OPS.beginAnnotation, [data.rect, transform, matrix]);
- return evaluator.getOperatorList(self.appearance, resources, opList).
+ return evaluator.getOperatorList(self.appearance, task,
+ resources, opList).
then(function () {
opList.addOp(OPS.endAnnotation, []);
self.appearance.reset();
return opList;
});
});
}
};
- Annotation.getConstructor =
- function Annotation_getConstructor(subtype, fieldType) {
-
- if (!subtype) {
- return;
- }
-
- // TODO(mack): Implement FreeText annotations
- if (subtype === 'Link') {
- return LinkAnnotation;
- } else if (subtype === 'Text') {
- return TextAnnotation;
- } else if (subtype === 'Widget') {
- if (!fieldType) {
- return;
+ Annotation.appendToOperatorList = function Annotation_appendToOperatorList(
+ annotations, opList, partialEvaluator, task, intent) {
+ var annotationPromises = [];
+ for (var i = 0, n = annotations.length; i < n; ++i) {
+ if ((intent === 'display' && annotations[i].viewable) ||
+ (intent === 'print' && annotations[i].printable)) {
+ annotationPromises.push(
+ annotations[i].getOperatorList(partialEvaluator, task));
}
-
- if (fieldType === 'Tx') {
- return TextWidgetAnnotation;
- } else {
- return WidgetAnnotation;
- }
- } else {
- return Annotation;
}
+ return Promise.all(annotationPromises).then(function(operatorLists) {
+ opList.addOp(OPS.beginAnnotations, []);
+ for (var i = 0, n = operatorLists.length; i < n; ++i) {
+ opList.addOpList(operatorLists[i]);
+ }
+ opList.addOp(OPS.endAnnotations, []);
+ });
};
- Annotation.fromRef = function Annotation_fromRef(xref, ref) {
+ return Annotation;
+})();
- var dict = xref.fetchIfRef(ref);
- if (!isDict(dict)) {
- return;
- }
+/**
+ * Contains all data regarding an annotation's border style.
+ *
+ * @class
+ */
+var AnnotationBorderStyle = (function AnnotationBorderStyleClosure() {
+ /**
+ * @constructor
+ * @private
+ */
+ function AnnotationBorderStyle() {
+ this.width = 1;
+ this.style = AnnotationBorderStyleType.SOLID;
+ this.dashArray = [3];
+ this.horizontalCornerRadius = 0;
+ this.verticalCornerRadius = 0;
+ }
- var subtype = dict.get('Subtype');
- subtype = isName(subtype) ? subtype.name : '';
- if (!subtype) {
- return;
- }
+ AnnotationBorderStyle.prototype = {
+ /**
+ * Set the width.
+ *
+ * @public
+ * @memberof AnnotationBorderStyle
+ * @param {integer} width - The width
+ */
+ setWidth: function AnnotationBorderStyle_setWidth(width) {
+ if (width === (width | 0)) {
+ this.width = width;
+ }
+ },
- var fieldType = Util.getInheritableProperty(dict, 'FT');
- fieldType = isName(fieldType) ? fieldType.name : '';
+ /**
+ * Set the style.
+ *
+ * @public
+ * @memberof AnnotationBorderStyle
+ * @param {Object} style - The style object
+ * @see {@link shared/util.js}
+ */
+ setStyle: function AnnotationBorderStyle_setStyle(style) {
+ if (!style) {
+ return;
+ }
+ switch (style.name) {
+ case 'S':
+ this.style = AnnotationBorderStyleType.SOLID;
+ break;
- var Constructor = Annotation.getConstructor(subtype, fieldType);
- if (!Constructor) {
- return;
- }
+ case 'D':
+ this.style = AnnotationBorderStyleType.DASHED;
+ break;
- var params = {
- dict: dict,
- ref: ref,
- };
+ case 'B':
+ this.style = AnnotationBorderStyleType.BEVELED;
+ break;
- var annotation = new Constructor(params);
+ case 'I':
+ this.style = AnnotationBorderStyleType.INSET;
+ break;
- if (annotation.isViewable() || annotation.isPrintable()) {
- return annotation;
- } else {
- if (SUPPORTED_TYPES.indexOf(subtype) === -1) {
- warn('unimplemented annotation type: ' + subtype);
+ case 'U':
+ this.style = AnnotationBorderStyleType.UNDERLINE;
+ break;
+
+ default:
+ break;
}
- }
- };
+ },
- Annotation.appendToOperatorList = function Annotation_appendToOperatorList(
- annotations, opList, pdfManager, partialEvaluator, intent) {
+ /**
+ * Set the dash array.
+ *
+ * @public
+ * @memberof AnnotationBorderStyle
+ * @param {Array} dashArray - The dash array with at least one element
+ */
+ setDashArray: function AnnotationBorderStyle_setDashArray(dashArray) {
+ // We validate the dash array, but we do not use it because CSS does not
+ // allow us to change spacing of dashes. For more information, visit
+ // http://www.w3.org/TR/css3-background/#the-border-style.
+ if (isArray(dashArray) && dashArray.length > 0) {
+ // According to the PDF specification: the elements in a dashArray
+ // shall be numbers that are nonnegative and not all equal to zero.
+ var isValid = true;
+ var allZeros = true;
+ for (var i = 0, len = dashArray.length; i < len; i++) {
+ var element = dashArray[i];
+ var validNumber = (+element >= 0);
+ if (!validNumber) {
+ isValid = false;
+ break;
+ } else if (element > 0) {
+ allZeros = false;
+ }
+ }
+ if (isValid && !allZeros) {
+ this.dashArray = dashArray;
+ } else {
+ this.width = 0; // Adobe behavior when the array is invalid.
+ }
+ } else if (dashArray) {
+ this.width = 0; // Adobe behavior when the array is invalid.
+ }
+ },
- function reject(e) {
- annotationsReadyCapability.reject(e);
- }
+ /**
+ * Set the horizontal corner radius (from a Border dictionary).
+ *
+ * @public
+ * @memberof AnnotationBorderStyle
+ * @param {integer} radius - The horizontal corner radius
+ */
+ setHorizontalCornerRadius:
+ function AnnotationBorderStyle_setHorizontalCornerRadius(radius) {
+ if (radius === (radius | 0)) {
+ this.horizontalCornerRadius = radius;
+ }
+ },
- var annotationsReadyCapability = createPromiseCapability();
-
- var annotationPromises = [];
- for (var i = 0, n = annotations.length; i < n; ++i) {
- if (intent === 'display' && annotations[i].isViewable() ||
- intent === 'print' && annotations[i].isPrintable()) {
- annotationPromises.push(
- annotations[i].getOperatorList(partialEvaluator));
+ /**
+ * Set the vertical corner radius (from a Border dictionary).
+ *
+ * @public
+ * @memberof AnnotationBorderStyle
+ * @param {integer} radius - The vertical corner radius
+ */
+ setVerticalCornerRadius:
+ function AnnotationBorderStyle_setVerticalCornerRadius(radius) {
+ if (radius === (radius | 0)) {
+ this.verticalCornerRadius = radius;
}
}
- Promise.all(annotationPromises).then(function(datas) {
- opList.addOp(OPS.beginAnnotations, []);
- for (var i = 0, n = datas.length; i < n; ++i) {
- var annotOpList = datas[i];
- opList.addOpList(annotOpList);
- }
- opList.addOp(OPS.endAnnotations, []);
- annotationsReadyCapability.resolve();
- }, reject);
-
- return annotationsReadyCapability.promise;
};
- return Annotation;
+ return AnnotationBorderStyle;
})();
var WidgetAnnotation = (function WidgetAnnotationClosure() {
-
function WidgetAnnotation(params) {
Annotation.call(this, params);
var dict = params.dict;
var data = this.data;
+ data.annotationType = AnnotationType.WIDGET;
data.fieldValue = stringToPDFString(
Util.getInheritableProperty(dict, 'V') || '');
data.alternativeText = stringToPDFString(dict.get('TU') || '');
data.defaultAppearance = Util.getInheritableProperty(dict, 'DA') || '';
var fieldType = Util.getInheritableProperty(dict, 'FT');
data.fieldType = isName(fieldType) ? fieldType.name : '';
data.fieldFlags = Util.getInheritableProperty(dict, 'Ff') || 0;
this.fieldResources = Util.getInheritableProperty(dict, 'DR') || Dict.empty;
+ // Hide unsupported Widget signatures.
+ if (data.fieldType === 'Sig') {
+ warn('unimplemented annotation type: Widget signature');
+ this.setFlags(AnnotationFlag.HIDDEN);
+ }
+
// Building the full field name by collecting the field and
// its ancestors 'T' data and joining them using '.'.
var fieldName = [];
var namedItem = dict;
var ref = params.ref;
@@ -11313,11 +13316,11 @@
var parent = namedItem.get('Parent');
var parentRef = namedItem.getRaw('Parent');
var name = namedItem.get('T');
if (name) {
fieldName.unshift(stringToPDFString(name));
- } else {
+ } else if (parent && ref) {
// The field name is absent, that means more than one field
// with the same name may exist. Replacing the empty name
// with the '`' plus index in the parent's 'Kids' array.
// This is not in the PDF spec but necessary to id the
// the input controls.
@@ -11335,38 +13338,28 @@
ref = parentRef;
}
data.fullName = fieldName.join('.');
}
- var parent = Annotation.prototype;
- Util.inherit(WidgetAnnotation, Annotation, {
- isViewable: function WidgetAnnotation_isViewable() {
- if (this.data.fieldType === 'Sig') {
- warn('unimplemented annotation type: Widget signature');
- return false;
- }
+ Util.inherit(WidgetAnnotation, Annotation, {});
- return parent.isViewable.call(this);
- }
- });
-
return WidgetAnnotation;
})();
var TextWidgetAnnotation = (function TextWidgetAnnotationClosure() {
function TextWidgetAnnotation(params) {
WidgetAnnotation.call(this, params);
this.data.textAlignment = Util.getInheritableProperty(params.dict, 'Q');
- this.data.annotationType = AnnotationType.WIDGET;
this.data.hasHtml = !this.data.hasAppearance && !!this.data.fieldValue;
}
Util.inherit(TextWidgetAnnotation, WidgetAnnotation, {
- getOperatorList: function TextWidgetAnnotation_getOperatorList(evaluator) {
+ getOperatorList: function TextWidgetAnnotation_getOperatorList(evaluator,
+ task) {
if (this.appearance) {
- return Annotation.prototype.getOperatorList.call(this, evaluator);
+ return Annotation.prototype.getOperatorList.call(this, evaluator, task);
}
var opList = new OperatorList();
var data = this.data;
@@ -11375,44 +13368,34 @@
if (!data.defaultAppearance) {
return Promise.resolve(opList);
}
var stream = new Stream(stringToBytes(data.defaultAppearance));
- return evaluator.getOperatorList(stream, this.fieldResources, opList).
+ return evaluator.getOperatorList(stream, task,
+ this.fieldResources, opList).
then(function () {
return opList;
});
}
});
return TextWidgetAnnotation;
})();
-var InteractiveAnnotation = (function InteractiveAnnotationClosure() {
- function InteractiveAnnotation(params) {
- Annotation.call(this, params);
-
- this.data.hasHtml = true;
- }
-
- Util.inherit(InteractiveAnnotation, Annotation, { });
-
- return InteractiveAnnotation;
-})();
-
var TextAnnotation = (function TextAnnotationClosure() {
function TextAnnotation(params) {
- InteractiveAnnotation.call(this, params);
+ Annotation.call(this, params);
var dict = params.dict;
var data = this.data;
var content = dict.get('Contents');
var title = dict.get('T');
data.annotationType = AnnotationType.TEXT;
data.content = stringToPDFString(content || '');
data.title = stringToPDFString(title || '');
+ data.hasHtml = true;
if (data.hasAppearance) {
data.name = 'NoIcon';
} else {
data.rect[1] = data.rect[3] - DEFAULT_ICON_SIZE;
@@ -11423,25 +13406,26 @@
if (dict.has('C')) {
data.hasBgColor = true;
}
}
- Util.inherit(TextAnnotation, InteractiveAnnotation, { });
+ Util.inherit(TextAnnotation, Annotation, {});
return TextAnnotation;
})();
var LinkAnnotation = (function LinkAnnotationClosure() {
function LinkAnnotation(params) {
- InteractiveAnnotation.call(this, params);
+ Annotation.call(this, params);
var dict = params.dict;
var data = this.data;
data.annotationType = AnnotationType.LINK;
+ data.hasHtml = true;
var action = dict.get('A');
- if (action) {
+ if (action && isDict(action)) {
var linkType = action.get('S').name;
if (linkType === 'URI') {
var url = action.get('URI');
if (isName(url)) {
// Some bad PDFs do not put parentheses around relative URLs.
@@ -11452,11 +13436,19 @@
// TODO: pdf spec mentions urls can be relative to a Base
// entry in the dictionary.
if (!isValidUrl(url, false)) {
url = '';
}
- data.url = url;
+ // According to ISO 32000-1:2008, section 12.6.4.7,
+ // URI should to be encoded in 7-bit ASCII.
+ // Some bad PDFs may have URIs in UTF-8 encoding, see Bugzilla 1122280.
+ try {
+ data.url = stringToUTF8String(url);
+ } catch (e) {
+ // Fall back to a simple copy.
+ data.url = url;
+ }
} else if (linkType === 'GoTo') {
data.dest = action.get('D');
} else if (linkType === 'GoToR') {
var urlDict = action.get('F');
if (isDict(urlDict)) {
@@ -11490,15 +13482,11 @@
return ('http://' + url);
}
return url;
}
- Util.inherit(LinkAnnotation, InteractiveAnnotation, {
- hasOperatorList: function LinkAnnotation_hasOperatorList() {
- return false;
- }
- });
+ Util.inherit(LinkAnnotation, Annotation, {});
return LinkAnnotation;
})();
@@ -11845,11 +13833,14 @@
}
var rmin = encode[2 * i];
var rmax = encode[2 * i + 1];
- tmpBuf[0] = rmin + (v - dmin) * (rmax - rmin) / (dmax - dmin);
+ // Prevent the value from becoming NaN as a result
+ // of division by zero (fixes issue6113.pdf).
+ tmpBuf[0] = dmin === dmax ? rmin :
+ rmin + (v - dmin) * (rmax - rmin) / (dmax - dmin);
// call the appropriate function
fns[i](tmpBuf, 0, dest, destOffset);
};
},
@@ -11914,11 +13905,11 @@
key += value + '_';
}
var cachedValue = cache[key];
if (cachedValue !== undefined) {
- cachedValue.set(dest, destOffset);
+ dest.set(cachedValue, destOffset);
return;
}
var output = new Float32Array(numOutputs);
var stack = evaluator.execute(input);
@@ -11938,11 +13929,11 @@
}
if (cache_available > 0) {
cache_available--;
cache[key] = output;
}
- output.set(dest, destOffset);
+ dest.set(output, destOffset);
};
}
};
})();
@@ -12854,13 +14845,13 @@
return ['PatternCS', null];
default:
error('unrecognized colorspace ' + mode);
}
} else if (isArray(cs)) {
- mode = cs[0].name;
+ mode = xref.fetchIfRef(cs[0]).name;
this.mode = mode;
- var numComps, params;
+ var numComps, params, alt;
switch (mode) {
case 'DeviceGray':
case 'G':
return 'DeviceGrayCS';
@@ -12869,56 +14860,67 @@
return 'DeviceRgbCS';
case 'DeviceCMYK':
case 'CMYK':
return 'DeviceCmykCS';
case 'CalGray':
- params = cs[1].getAll();
+ params = xref.fetchIfRef(cs[1]).getAll();
return ['CalGrayCS', params];
case 'CalRGB':
- params = cs[1].getAll();
+ params = xref.fetchIfRef(cs[1]).getAll();
return ['CalRGBCS', params];
case 'ICCBased':
var stream = xref.fetchIfRef(cs[1]);
var dict = stream.dict;
numComps = dict.get('N');
+ alt = dict.get('Alternate');
+ if (alt) {
+ var altIR = ColorSpace.parseToIR(alt, xref, res);
+ // Parse the /Alternate CS to ensure that the number of components
+ // are correct, and also (indirectly) that it is not a PatternCS.
+ var altCS = ColorSpace.fromIR(altIR);
+ if (altCS.numComps === numComps) {
+ return altIR;
+ }
+ warn('ICCBased color space: Ignoring incorrect /Alternate entry.');
+ }
if (numComps === 1) {
return 'DeviceGrayCS';
} else if (numComps === 3) {
return 'DeviceRgbCS';
} else if (numComps === 4) {
return 'DeviceCmykCS';
}
break;
case 'Pattern':
- var basePatternCS = cs[1];
+ var basePatternCS = cs[1] || null;
if (basePatternCS) {
basePatternCS = ColorSpace.parseToIR(basePatternCS, xref, res);
}
return ['PatternCS', basePatternCS];
case 'Indexed':
case 'I':
var baseIndexedCS = ColorSpace.parseToIR(cs[1], xref, res);
- var hiVal = cs[2] + 1;
+ var hiVal = xref.fetchIfRef(cs[2]) + 1;
var lookup = xref.fetchIfRef(cs[3]);
if (isStream(lookup)) {
lookup = lookup.getBytes();
}
return ['IndexedCS', baseIndexedCS, hiVal, lookup];
case 'Separation':
case 'DeviceN':
- var name = cs[1];
+ var name = xref.fetchIfRef(cs[1]);
numComps = 1;
if (isName(name)) {
numComps = 1;
} else if (isArray(name)) {
numComps = name.length;
}
- var alt = ColorSpace.parseToIR(cs[2], xref, res);
+ alt = ColorSpace.parseToIR(cs[2], xref, res);
var tintFnIR = PDFFunction.getIR(xref, xref.fetchIfRef(cs[3]));
return ['AlternateCS', numComps, alt, tintFnIR];
case 'Lab':
- params = cs[1].getAll();
+ params = xref.fetchIfRef(cs[1]).getAll();
return ['LabCS', params];
default:
error('unimplemented color space object "' + mode + '"');
}
} else {
@@ -12934,11 +14936,11 @@
* slightly different.
* @param {Array} decode Decode map (usually from an image).
* @param {Number} n Number of components the color space has.
*/
ColorSpace.isDefaultDecode = function ColorSpace_isDefaultDecode(decode, n) {
- if (!decode) {
+ if (!isArray(decode)) {
return true;
}
if (n * 2 !== decode.length) {
warn('The decode map is not the correct length');
@@ -15582,13 +17584,14 @@
if (pdfAlgorithm) {
if (pdfAlgorithm.checkUserPassword(password, userValidationSalt,
userPassword)) {
return pdfAlgorithm.getUserKey(password, userKeySalt, userEncryption);
- } else if (pdfAlgorithm.checkOwnerPassword(password, ownerValidationSalt,
- uBytes,
- ownerPassword)) {
+ } else if (password.length && pdfAlgorithm.checkOwnerPassword(password,
+ ownerValidationSalt,
+ uBytes,
+ ownerPassword)) {
return pdfAlgorithm.getOwnerKey(password, ownerKeySalt, uBytes,
ownerEncryption);
}
}
@@ -15739,10 +17742,18 @@
this.encryptMetadata = encryptMetadata;
var fileIdBytes = stringToBytes(fileId);
var passwordBytes;
if (password) {
+ if (revision === 6) {
+ try {
+ password = utf8StringToString(password);
+ } catch (ex) {
+ warn('CipherTransformFactory: ' +
+ 'Unable to convert UTF8 encoded password.');
+ }
+ }
passwordBytes = stringToBytes(password);
}
var encryptionKey;
if (algorithm !== 5) {
@@ -15788,11 +17799,11 @@
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;
+ this.eff = dict.get('EFF') || this.stmf;
}
}
function buildObjectKey(num, gen, encryptionKey, isAes) {
var key = new Uint8Array(encryptionKey.length + 9), i, n;
@@ -15843,11 +17854,11 @@
error('Unknown crypto method');
}
CipherTransformFactory.prototype = {
createCipherTransform:
- function CipherTransformFactory_createCipherTransform(num, gen) {
+ function CipherTransformFactory_createCipherTransform(num, gen) {
if (this.algorithm === 4 || this.algorithm === 5) {
return new CipherTransform(
buildCipherConstructor(this.cf, this.stmf,
num, gen, this.encryptionKey),
buildCipherConstructor(this.cf, this.strf,
@@ -15863,11 +17874,12 @@
};
return CipherTransformFactory;
})();
-var PatternType = {
+
+var ShadingType = {
FUNCTION_BASED: 1,
AXIAL: 2,
RADIAL: 3,
FREE_FORM_MESH: 4,
LATTICE_FORM_MESH: 5,
@@ -15888,28 +17900,37 @@
error('Should not call Pattern.getStyle: ' + ctx);
}
};
Pattern.parseShading = function Pattern_parseShading(shading, matrix, xref,
- res) {
+ res, handler) {
var dict = isStream(shading) ? shading.dict : shading;
var type = dict.get('ShadingType');
- switch (type) {
- case PatternType.AXIAL:
- case PatternType.RADIAL:
- // Both radial and axial shadings are handled by RadialAxial shading.
- return new Shadings.RadialAxial(dict, matrix, xref, res);
- case PatternType.FREE_FORM_MESH:
- case PatternType.LATTICE_FORM_MESH:
- case PatternType.COONS_PATCH_MESH:
- case PatternType.TENSOR_PATCH_MESH:
- return new Shadings.Mesh(shading, matrix, xref, res);
- default:
- UnsupportedManager.notify(UNSUPPORTED_FEATURES.shadingPattern);
- return new Shadings.Dummy();
+ try {
+ switch (type) {
+ case ShadingType.AXIAL:
+ case ShadingType.RADIAL:
+ // Both radial and axial shadings are handled by RadialAxial shading.
+ return new Shadings.RadialAxial(dict, matrix, xref, res);
+ case ShadingType.FREE_FORM_MESH:
+ case ShadingType.LATTICE_FORM_MESH:
+ case ShadingType.COONS_PATCH_MESH:
+ case ShadingType.TENSOR_PATCH_MESH:
+ return new Shadings.Mesh(shading, matrix, xref, res);
+ default:
+ throw new Error('Unsupported ShadingType: ' + type);
+ }
+ } catch (ex) {
+ if (ex instanceof MissingDataException) {
+ throw ex;
+ }
+ handler.send('UnsupportedFeature',
+ {featureId: UNSUPPORTED_FEATURES.shadingPattern});
+ warn(ex);
+ return new Shadings.Dummy();
}
};
return Pattern;
})();
@@ -15945,11 +17966,11 @@
var extendArr = dict.get('Extend');
extendStart = extendArr[0];
extendEnd = extendArr[1];
}
- if (this.shadingType === PatternType.RADIAL &&
+ if (this.shadingType === ShadingType.RADIAL &&
(!extendStart || !extendEnd)) {
// Radial gradient only currently works if either circle is fully within
// the other circle.
var x1 = this.coordsArr[0];
var y1 = this.coordsArr[1];
@@ -15990,18 +18011,18 @@
var rgbColor;
for (var i = t0; i <= t1; i += step) {
ratio[0] = i;
fn(ratio, 0, color, 0);
rgbColor = cs.getRgb(color, 0);
- var cssColor = Util.makeCssRgb(rgbColor);
+ var cssColor = Util.makeCssRgb(rgbColor[0], rgbColor[1], rgbColor[2]);
colorStops.push([(i - t0) / diff, cssColor]);
}
var background = 'transparent';
if (dict.has('Background')) {
rgbColor = cs.getRgb(dict.get('Background'), 0);
- background = Util.makeCssRgb(rgbColor);
+ background = Util.makeCssRgb(rgbColor[0], rgbColor[1], rgbColor[2]);
}
if (!extendStart) {
// Insert a color stop at the front and offset the first real color stop
// so it doesn't conflict with the one we insert.
@@ -16020,17 +18041,17 @@
RadialAxial.prototype = {
getIR: function RadialAxial_getIR() {
var coordsArr = this.coordsArr;
var shadingType = this.shadingType;
var type, p0, p1, r0, r1;
- if (shadingType === PatternType.AXIAL) {
+ if (shadingType === ShadingType.AXIAL) {
p0 = [coordsArr[0], coordsArr[1]];
p1 = [coordsArr[2], coordsArr[3]];
r0 = null;
r1 = null;
type = 'axial';
- } else if (shadingType === PatternType.RADIAL) {
+ } else if (shadingType === ShadingType.RADIAL) {
p0 = [coordsArr[0], coordsArr[1]];
p1 = [coordsArr[3], coordsArr[4]];
r0 = coordsArr[2];
r1 = coordsArr[5];
type = 'radial';
@@ -16040,10 +18061,15 @@
var matrix = this.matrix;
if (matrix) {
p0 = Util.applyTransform(p0, matrix);
p1 = Util.applyTransform(p1, matrix);
+ if (shadingType === ShadingType.RADIAL) {
+ var scale = Util.singularValueDecompose2dScale(matrix);
+ r0 *= scale[0];
+ r1 *= scale[1];
+ }
}
return ['RadialAxial', type, this.colorStops, p0, p1, r0, r1];
}
};
@@ -16060,11 +18086,11 @@
this.buffer = 0;
this.bufferLength = 0;
var numComps = context.numComps;
this.tmpCompsBuf = new Float32Array(numComps);
- var csNumComps = context.colorSpace;
+ var csNumComps = context.colorSpace.numComps;
this.tmpCsCompsBuf = context.colorFn ? new Float32Array(csNumComps) :
this.tmpCompsBuf;
}
MeshStreamReader.prototype = {
get hasData() {
@@ -16180,17 +18206,14 @@
colors.push(color);
verticesLeft--;
reader.align();
}
-
- var psPacked = new Int32Array(ps);
-
mesh.figures.push({
type: 'triangles',
- coords: psPacked,
- colors: psPacked
+ coords: new Int32Array(ps),
+ colors: new Int32Array(ps),
});
}
function decodeType5Shading(mesh, reader, verticesPerRow) {
var coords = mesh.coords;
@@ -16201,17 +18224,14 @@
var color = reader.readComponents();
ps.push(coords.length);
coords.push(coord);
colors.push(color);
}
-
- var psPacked = new Int32Array(ps);
-
mesh.figures.push({
type: 'lattice',
- coords: psPacked,
- colors: psPacked,
+ coords: new Int32Array(ps),
+ colors: new Int32Array(ps),
verticesPerRow: verticesPerRow
});
}
var MIN_SPLIT_PATCH_CHUNKS_AMOUNT = 3;
@@ -16349,33 +18369,36 @@
cs[2] = ci + 1; cs[3] = ci + 2;
cs[0] = ci; cs[1] = ci + 3;
break;
case 1:
tmp1 = ps[12]; tmp2 = ps[13]; tmp3 = ps[14]; tmp4 = ps[15];
- ps[12] = pi + 5; ps[13] = pi + 4; ps[14] = pi + 3; ps[15] = pi + 2;
- ps[ 8] = pi + 6; /* values for 5, 6, 9, 10 are */ ps[11] = pi + 1;
- ps[ 4] = pi + 7; /* calculated below */ ps[ 7] = pi;
- ps[ 0] = tmp1; ps[ 1] = tmp2; ps[ 2] = tmp3; ps[ 3] = tmp4;
+ ps[12] = tmp4; ps[13] = pi + 0; ps[14] = pi + 1; ps[15] = pi + 2;
+ ps[ 8] = tmp3; /* values for 5, 6, 9, 10 are */ ps[11] = pi + 3;
+ ps[ 4] = tmp2; /* calculated below */ ps[ 7] = pi + 4;
+ ps[ 0] = tmp1; ps[ 1] = pi + 7; ps[ 2] = pi + 6; ps[ 3] = pi + 5;
tmp1 = cs[2]; tmp2 = cs[3];
- cs[2] = ci + 1; cs[3] = ci;
- cs[0] = tmp1; cs[1] = tmp2;
+ cs[2] = tmp2; cs[3] = ci;
+ cs[0] = tmp1; cs[1] = ci + 1;
break;
case 2:
- ps[12] = ps[15]; ps[13] = pi + 7; ps[14] = pi + 6; ps[15] = pi + 5;
- ps[ 8] = ps[11]; /* values for 5, 6, 9, 10 are */ ps[11] = pi + 4;
- ps[ 4] = ps[7]; /* calculated below */ ps[ 7] = pi + 3;
- ps[ 0] = ps[3]; ps[ 1] = pi; ps[ 2] = pi + 1; ps[ 3] = pi + 2;
- cs[2] = cs[3]; cs[3] = ci + 1;
- cs[0] = cs[1]; cs[1] = ci;
+ tmp1 = ps[15];
+ tmp2 = ps[11];
+ ps[12] = ps[3]; ps[13] = pi + 0; ps[14] = pi + 1; ps[15] = pi + 2;
+ ps[ 8] = ps[7]; /* values for 5, 6, 9, 10 are */ ps[11] = pi + 3;
+ ps[ 4] = tmp2; /* calculated below */ ps[ 7] = pi + 4;
+ ps[ 0] = tmp1; ps[ 1] = pi + 7; ps[ 2] = pi + 6; ps[ 3] = pi + 5;
+ tmp1 = cs[3];
+ cs[2] = cs[1]; cs[3] = ci;
+ cs[0] = tmp1; cs[1] = ci + 1;
break;
case 3:
- ps[12] = ps[0]; ps[13] = ps[1]; ps[14] = ps[2]; ps[15] = ps[3];
- ps[ 8] = pi; /* values for 5, 6, 9, 10 are */ ps[11] = pi + 7;
- ps[ 4] = pi + 1; /* calculated below */ ps[ 7] = pi + 6;
- ps[ 0] = pi + 2; ps[ 1] = pi + 3; ps[ 2] = pi + 4; ps[ 3] = pi + 5;
- cs[2] = cs[0]; cs[3] = cs[1];
- cs[0] = ci; cs[1] = ci + 1;
+ ps[12] = ps[0]; ps[13] = pi + 0; ps[14] = pi + 1; ps[15] = pi + 2;
+ ps[ 8] = ps[1]; /* values for 5, 6, 9, 10 are */ ps[11] = pi + 3;
+ ps[ 4] = ps[2]; /* calculated below */ ps[ 7] = pi + 4;
+ ps[ 0] = ps[3]; ps[ 1] = pi + 7; ps[ 2] = pi + 6; ps[ 3] = pi + 5;
+ cs[2] = cs[0]; cs[3] = ci;
+ cs[0] = cs[1]; cs[1] = ci + 1;
break;
}
// set p11, p12, p21, p22
ps[5] = coords.length;
coords.push([
@@ -16456,33 +18479,36 @@
cs[2] = ci + 1; cs[3] = ci + 2;
cs[0] = ci; cs[1] = ci + 3;
break;
case 1:
tmp1 = ps[12]; tmp2 = ps[13]; tmp3 = ps[14]; tmp4 = ps[15];
- ps[12] = pi + 5; ps[13] = pi + 4; ps[14] = pi + 3; ps[15] = pi + 2;
- ps[ 8] = pi + 6; ps[ 9] = pi + 11; ps[10] = pi + 10; ps[11] = pi + 1;
- ps[ 4] = pi + 7; ps[ 5] = pi + 8; ps[ 6] = pi + 9; ps[ 7] = pi;
- ps[ 0] = tmp1; ps[ 1] = tmp2; ps[ 2] = tmp3; ps[ 3] = tmp4;
+ ps[12] = tmp4; ps[13] = pi + 0; ps[14] = pi + 1; ps[15] = pi + 2;
+ ps[ 8] = tmp3; ps[ 9] = pi + 9; ps[10] = pi + 10; ps[11] = pi + 3;
+ ps[ 4] = tmp2; ps[ 5] = pi + 8; ps[ 6] = pi + 11; ps[ 7] = pi + 4;
+ ps[ 0] = tmp1; ps[ 1] = pi + 7; ps[ 2] = pi + 6; ps[ 3] = pi + 5;
tmp1 = cs[2]; tmp2 = cs[3];
- cs[2] = ci + 1; cs[3] = ci;
- cs[0] = tmp1; cs[1] = tmp2;
+ cs[2] = tmp2; cs[3] = ci;
+ cs[0] = tmp1; cs[1] = ci + 1;
break;
case 2:
- ps[12] = ps[15]; ps[13] = pi + 7; ps[14] = pi + 6; ps[15] = pi + 5;
- ps[ 8] = ps[11]; ps[ 9] = pi + 8; ps[10] = pi + 11; ps[11] = pi + 4;
- ps[ 4] = ps[7]; ps[ 5] = pi + 9; ps[ 6] = pi + 10; ps[ 7] = pi + 3;
- ps[ 0] = ps[3]; ps[ 1] = pi; ps[ 2] = pi + 1; ps[ 3] = pi + 2;
- cs[2] = cs[3]; cs[3] = ci + 1;
- cs[0] = cs[1]; cs[1] = ci;
+ tmp1 = ps[15];
+ tmp2 = ps[11];
+ ps[12] = ps[3]; ps[13] = pi + 0; ps[14] = pi + 1; ps[15] = pi + 2;
+ ps[ 8] = ps[7]; ps[ 9] = pi + 9; ps[10] = pi + 10; ps[11] = pi + 3;
+ ps[ 4] = tmp2; ps[ 5] = pi + 8; ps[ 6] = pi + 11; ps[ 7] = pi + 4;
+ ps[ 0] = tmp1; ps[ 1] = pi + 7; ps[ 2] = pi + 6; ps[ 3] = pi + 5;
+ tmp1 = cs[3];
+ cs[2] = cs[1]; cs[3] = ci;
+ cs[0] = tmp1; cs[1] = ci + 1;
break;
case 3:
- ps[12] = ps[0]; ps[13] = ps[1]; ps[14] = ps[2]; ps[15] = ps[3];
- ps[ 8] = pi; ps[ 9] = pi + 9; ps[10] = pi + 8; ps[11] = pi + 7;
- ps[ 4] = pi + 1; ps[ 5] = pi + 10; ps[ 6] = pi + 11; ps[ 7] = pi + 6;
- ps[ 0] = pi + 2; ps[ 1] = pi + 3; ps[ 2] = pi + 4; ps[ 3] = pi + 5;
- cs[2] = cs[0]; cs[3] = cs[1];
- cs[0] = ci; cs[1] = ci + 1;
+ ps[12] = ps[0]; ps[13] = pi + 0; ps[14] = pi + 1; ps[15] = pi + 2;
+ ps[ 8] = ps[1]; ps[ 9] = pi + 9; ps[10] = pi + 10; ps[11] = pi + 3;
+ ps[ 4] = ps[2]; ps[ 5] = pi + 8; ps[ 6] = pi + 11; ps[ 7] = pi + 4;
+ ps[ 0] = ps[3]; ps[ 1] = pi + 7; ps[ 2] = pi + 6; ps[ 3] = pi + 5;
+ cs[2] = cs[0]; cs[3] = ci;
+ cs[0] = cs[1]; cs[1] = ci + 1;
break;
}
mesh.figures.push({
type: 'patch',
coords: new Int32Array(ps), // making copies of ps and cs
@@ -16567,23 +18593,23 @@
};
var reader = new MeshStreamReader(stream, decodeContext);
var patchMesh = false;
switch (this.shadingType) {
- case PatternType.FREE_FORM_MESH:
+ case ShadingType.FREE_FORM_MESH:
decodeType4Shading(this, reader);
break;
- case PatternType.LATTICE_FORM_MESH:
+ case ShadingType.LATTICE_FORM_MESH:
var verticesPerRow = dict.get('VerticesPerRow') | 0;
assert(verticesPerRow >= 2, 'Invalid VerticesPerRow');
decodeType5Shading(this, reader, verticesPerRow);
break;
- case PatternType.COONS_PATCH_MESH:
+ case ShadingType.COONS_PATCH_MESH:
decodeType6Shading(this, reader);
patchMesh = true;
break;
- case PatternType.TENSOR_PATCH_MESH:
+ case ShadingType.TENSOR_PATCH_MESH:
decodeType7Shading(this, reader);
patchMesh = true;
break;
default:
error('Unsupported mesh type.');
@@ -16737,13 +18763,14 @@
},
buildFormXObject: function PartialEvaluator_buildFormXObject(resources,
xobj, smask,
operatorList,
+ task,
initialState) {
- var matrix = xobj.dict.get('Matrix');
- var bbox = xobj.dict.get('BBox');
+ var matrix = xobj.dict.getArray('Matrix');
+ var bbox = xobj.dict.getArray('BBox');
var group = xobj.dict.get('Group');
if (group) {
var groupOptions = {
matrix: matrix,
bbox: bbox,
@@ -16769,11 +18796,11 @@
operatorList.addOp(OPS.beginGroup, [groupOptions]);
}
operatorList.addOp(OPS.paintFormXObjectBegin, [matrix, bbox]);
- return this.getOperatorList(xobj,
+ return this.getOperatorList(xobj, task,
(xobj.dict.get('Resources') || resources), operatorList, initialState).
then(function () {
operatorList.addOp(OPS.paintFormXObjectEnd, []);
if (group) {
@@ -16783,11 +18810,11 @@
},
buildPaintImageXObject:
function PartialEvaluator_buildPaintImageXObject(resources, image,
inline, operatorList,
- cacheKey, cache) {
+ cacheKey, imageCache) {
var self = this;
var dict = image.dict;
var w = dict.get('Width', 'W');
var h = dict.get('Height', 'H');
@@ -16821,13 +18848,14 @@
inverseDecode);
imgData.cached = true;
args = [imgData];
operatorList.addOp(OPS.paintImageMaskXObject, args);
if (cacheKey) {
- cache.key = cacheKey;
- cache.fn = OPS.paintImageMaskXObject;
- cache.args = args;
+ imageCache[cacheKey] = {
+ fn: OPS.paintImageMaskXObject,
+ args: args
+ };
}
return;
}
var softMask = (dict.get('SMask', 'SM') || false);
@@ -16865,44 +18893,65 @@
PDFImage.buildImage(self.handler, self.xref, resources, image, inline).
then(function(imageObj) {
var imgData = imageObj.createImageData(/* forceRGBA = */ false);
self.handler.send('obj', [objId, self.pageIndex, 'Image', imgData],
[imgData.data.buffer]);
- }).then(null, function (reason) {
+ }).then(undefined, function (reason) {
warn('Unable to decode image: ' + reason);
self.handler.send('obj', [objId, self.pageIndex, 'Image', null]);
});
operatorList.addOp(OPS.paintImageXObject, args);
if (cacheKey) {
- cache.key = cacheKey;
- cache.fn = OPS.paintImageXObject;
- cache.args = args;
+ imageCache[cacheKey] = {
+ fn: OPS.paintImageXObject,
+ args: args
+ };
}
},
handleSMask: function PartialEvaluator_handleSmask(smask, resources,
- operatorList,
+ operatorList, task,
stateManager) {
var smaskContent = smask.get('G');
var smaskOptions = {
subtype: smask.get('S').name,
backdrop: smask.get('BC')
};
+
+ // The SMask might have a alpha/luminosity value transfer function --
+ // we will build a map of integer values in range 0..255 to be fast.
+ var transferObj = smask.get('TR');
+ if (isPDFFunction(transferObj)) {
+ var transferFn = PDFFunction.parse(this.xref, transferObj);
+ var transferMap = new Uint8Array(256);
+ var tmp = new Float32Array(1);
+ for (var i = 0; i < 255; i++) {
+ tmp[0] = i / 255;
+ transferFn(tmp, 0, tmp, 0);
+ transferMap[i] = (tmp[0] * 255) | 0;
+ }
+ smaskOptions.transferMap = transferMap;
+ }
+
return this.buildFormXObject(resources, smaskContent, smaskOptions,
- operatorList, stateManager.state.clone());
+ operatorList, task, stateManager.state.clone());
},
handleTilingType:
function PartialEvaluator_handleTilingType(fn, args, resources,
pattern, patternDict,
- operatorList) {
+ operatorList, task) {
// Create an IR of the pattern code.
var tilingOpList = new OperatorList();
- return this.getOperatorList(pattern,
- (patternDict.get('Resources') || resources), tilingOpList).
- then(function () {
+ // Merge the available resources, to prevent issues when the patternDict
+ // is missing some /Resources entries (fixes issue6541.pdf).
+ var resourcesArray = [patternDict.get('Resources'), resources];
+ var patternResources = Dict.merge(this.xref, resourcesArray);
+
+ return this.getOperatorList(pattern, task, patternResources,
+ tilingOpList).then(function () {
// Add the dependencies to the parent operator list so they are
// resolved before sub operator list is executed synchronously.
operatorList.addDependencies(tilingOpList.dependencies);
operatorList.addOp(fn, getTilingPatternIR({
fnArray: tilingOpList.fnArray,
@@ -16911,11 +18960,11 @@
});
},
handleSetFont:
function PartialEvaluator_handleSetFont(resources, fontArgs, fontRef,
- operatorList, state) {
+ operatorList, task, state) {
// TODO(mack): Not needed?
var fontName;
if (fontArgs) {
fontArgs = fontArgs.slice();
fontName = fontArgs[0].name;
@@ -16925,13 +18974,19 @@
return this.loadFont(fontName, fontRef, this.xref, resources).then(
function (translated) {
if (!translated.font.isType3Font) {
return translated;
}
- return translated.loadType3Data(self, resources, operatorList).then(
- function () {
+ return translated.loadType3Data(self, resources, operatorList, task).
+ then(function () {
return translated;
+ }, function (reason) {
+ // Error in the font data -- sending unsupported feature notification.
+ self.handler.send('UnsupportedFeature',
+ {featureId: UNSUPPORTED_FEATURES.font});
+ return new TranslatedFont('g_font_error',
+ new ErrorFont('Type3 font load error: ' + reason), translated.font);
});
}).then(function (translated) {
state.font = translated.font;
translated.send(self.handler);
return translated.loadedName;
@@ -16955,13 +19010,10 @@
}
}.bind(this);
for (var i = 0, ii = glyphs.length; i < ii; i++) {
var glyph = glyphs[i];
- if (glyph === null) {
- continue;
- }
buildPath(glyph.fontChar);
// If the glyph has an accent we need to build a path for its
// fontChar too, otherwise CanvasGraphics_paintChar will fail.
var accent = glyph.accent;
@@ -16973,12 +19025,12 @@
return glyphs;
},
setGState: function PartialEvaluator_setGState(resources, gState,
- operatorList, xref,
- stateManager) {
+ operatorList, task,
+ xref, stateManager) {
// This array holds the converted/processed state data.
var gStateObj = [];
var gStateMap = gState.map;
var self = this;
var promise = Promise.resolve();
@@ -16998,12 +19050,12 @@
case 'ca':
gStateObj.push([key, value]);
break;
case 'Font':
promise = promise.then(function () {
- return self.handleSetFont(resources, null, value[0],
- operatorList, stateManager.state).
+ return self.handleSetFont(resources, null, value[0], operatorList,
+ task, stateManager.state).
then(function (loadedName) {
operatorList.addDependency(loadedName);
gStateObj.push([key, [loadedName, value[1]]]);
});
});
@@ -17018,11 +19070,11 @@
}
var dict = xref.fetchIfRef(value);
if (isDict(dict)) {
promise = promise.then(function () {
return self.handleSMask(dict, resources, operatorList,
- stateManager);
+ task, stateManager);
});
gStateObj.push([key, true]);
} else {
warn('Unsupported SMask type');
}
@@ -17139,11 +19191,11 @@
this.fontCache.put(fontRef, fontCapability.promise);
}
// Keep track of each font we translated so the caller can
// load them asynchronously before calling display on a page.
- font.loadedName = 'g_font_' + (fontRefIsDict ?
+ font.loadedName = 'g_' + this.pdfManager.docId + '_f' + (fontRefIsDict ?
fontName.replace(/\W/g, '') : fontID);
font.translated = fontCapability.promise;
// TODO move promises into translate font
@@ -17153,21 +19205,24 @@
this.translateFont(preEvaluatedFont, xref));
} catch (e) {
translatedPromise = Promise.reject(e);
}
+ var self = this;
translatedPromise.then(function (translatedFont) {
if (translatedFont.fontType !== undefined) {
var xrefFontStats = xref.stats.fontTypes;
xrefFontStats[translatedFont.fontType] = true;
}
fontCapability.resolve(new TranslatedFont(font.loadedName,
translatedFont, font));
}, function (reason) {
// TODO fontCapability.reject?
- UnsupportedManager.notify(UNSUPPORTED_FEATURES.font);
+ // Error in the font data -- sending unsupported feature notification.
+ self.handler.send('UnsupportedFeature',
+ {featureId: UNSUPPORTED_FEATURES.font});
try {
// error, but it's still nice to have font type reported
var descriptor = preEvaluatedFont.descriptor;
var fontFile3 = descriptor && descriptor.get('FontFile3');
@@ -17199,11 +19254,11 @@
Array.prototype.push.apply(opArgs[1], args);
}
},
handleColorN: function PartialEvaluator_handleColorN(operatorList, fn, args,
- cs, patterns, resources, xref) {
+ cs, patterns, resources, task, xref) {
// compile tiling patterns
var patternName = args[args.length - 1];
// SCN/scn applies patterns along with normal colors
var pattern;
if (isName(patternName) &&
@@ -17212,15 +19267,16 @@
var typeNum = dict.get('PatternType');
if (typeNum === TILING_PATTERN) {
var color = cs.base ? cs.base.getRgb(args, 0) : null;
return this.handleTilingType(fn, color, resources, pattern,
- dict, operatorList);
+ dict, operatorList, task);
} else if (typeNum === SHADING_PATTERN) {
var shading = dict.get('Shading');
var matrix = dict.get('Matrix');
- pattern = Pattern.parseShading(shading, matrix, xref, resources);
+ pattern = Pattern.parseShading(shading, matrix, xref, resources,
+ this.handler);
operatorList.addOp(fn, pattern.getIR());
return Promise.resolve();
} else {
return Promise.reject('Unknown PatternType: ' + typeNum);
}
@@ -17229,10 +19285,11 @@
operatorList.addOp(fn, args);
return Promise.resolve();
},
getOperatorList: function PartialEvaluator_getOperatorList(stream,
+ task,
resources,
operatorList,
initialState) {
var self = this;
@@ -17247,10 +19304,11 @@
var stateManager = new StateManager(initialState || new EvalState());
var preprocessor = new EvaluatorPreprocessor(stream, xref, stateManager);
var timeSlotManager = new TimeSlotManager();
return new Promise(function next(resolve, reject) {
+ task.ensureNotTerminated();
timeSlotManager.reset();
var stop, operation = {}, i, ii, cs;
while (!(stop = timeSlotManager.check())) {
// The arguments parsed by read() are used beyond this loop, so we
// cannot reuse the same array on each iteration. Therefore we pass
@@ -17268,12 +19326,16 @@
if (args[0].code) {
break;
}
// eagerly compile XForm objects
var name = args[0].name;
- if (imageCache.key === name) {
- operatorList.addOp(imageCache.fn, imageCache.args);
+ if (!name) {
+ warn('XObject must be referred to by name.');
+ continue;
+ }
+ if (imageCache[name] !== undefined) {
+ operatorList.addOp(imageCache[name].fn, imageCache[name].args);
args = null;
continue;
}
var xobj = xobjs.get(name);
@@ -17285,11 +19347,11 @@
'XObject should have a Name subtype');
if (type.name === 'Form') {
stateManager.save();
return self.buildFormXObject(resources, xobj, null,
- operatorList,
+ operatorList, task,
stateManager.state.clone()).
then(function () {
stateManager.restore();
next(resolve, reject);
}, reject);
@@ -17309,23 +19371,26 @@
}
break;
case OPS.setFont:
var fontSize = args[1];
// eagerly collect all fonts
- return self.handleSetFont(resources, args, null,
- operatorList, stateManager.state).
+ return self.handleSetFont(resources, args, null, operatorList,
+ task, stateManager.state).
then(function (loadedName) {
operatorList.addDependency(loadedName);
operatorList.addOp(OPS.setFont, [loadedName, fontSize]);
next(resolve, reject);
}, reject);
case OPS.endInlineImage:
var cacheKey = args[0].cacheKey;
- if (cacheKey && imageCache.key === cacheKey) {
- operatorList.addOp(imageCache.fn, imageCache.args);
- args = null;
- continue;
+ if (cacheKey) {
+ var cacheEntry = imageCache[cacheKey];
+ if (cacheEntry !== undefined) {
+ operatorList.addOp(cacheEntry.fn, cacheEntry.args);
+ args = null;
+ continue;
+ }
}
self.buildPaintImageXObject(resources, args[0], true,
operatorList, cacheKey, imageCache);
args = null;
continue;
@@ -17334,15 +19399,16 @@
break;
case OPS.showSpacedText:
var arr = args[0];
var combinedGlyphs = [];
var arrLength = arr.length;
+ var state = stateManager.state;
for (i = 0; i < arrLength; ++i) {
var arrItem = arr[i];
if (isString(arrItem)) {
Array.prototype.push.apply(combinedGlyphs,
- self.handleText(arrItem, stateManager.state));
+ self.handleText(arrItem, state));
} else if (isNum(arrItem)) {
combinedGlyphs.push(arrItem);
}
}
args[0] = combinedGlyphs;
@@ -17412,22 +19478,22 @@
break;
case OPS.setFillColorN:
cs = stateManager.state.fillColorSpace;
if (cs.name === 'Pattern') {
return self.handleColorN(operatorList, OPS.setFillColorN,
- args, cs, patterns, resources, xref).then(function() {
+ args, cs, patterns, resources, task, xref).then(function() {
next(resolve, reject);
}, reject);
}
args = cs.getRgb(args, 0);
fn = OPS.setFillRGBColor;
break;
case OPS.setStrokeColorN:
cs = stateManager.state.strokeColorSpace;
if (cs.name === 'Pattern') {
return self.handleColorN(operatorList, OPS.setStrokeColorN,
- args, cs, patterns, resources, xref).then(function() {
+ args, cs, patterns, resources, task, xref).then(function() {
next(resolve, reject);
}, reject);
}
args = cs.getRgb(args, 0);
fn = OPS.setStrokeRGBColor;
@@ -17443,11 +19509,11 @@
if (!shading) {
error('No shading object found');
}
var shadingFill = Pattern.parseShading(shading, null, xref,
- resources);
+ resources, self.handler);
var patternIR = shadingFill.getIR();
args = [patternIR];
fn = OPS.shadingFill;
break;
case OPS.setGState:
@@ -17457,12 +19523,12 @@
if (!isDict(extGState) || !extGState.has(dictName.name)) {
break;
}
var gState = extGState.get(dictName.name);
- return self.setGState(resources, gState, operatorList, xref,
- stateManager).then(function() {
+ return self.setGState(resources, gState, operatorList, task,
+ xref, stateManager).then(function() {
next(resolve, reject);
}, reject);
case OPS.moveTo:
case OPS.lineTo:
case OPS.curveTo:
@@ -17472,17 +19538,35 @@
self.buildPath(operatorList, fn, args);
continue;
case OPS.rectangle:
self.buildPath(operatorList, fn, args);
continue;
+ case OPS.markPoint:
+ case OPS.markPointProps:
+ case OPS.beginMarkedContent:
+ case OPS.beginMarkedContentProps:
+ case OPS.endMarkedContent:
+ case OPS.beginCompat:
+ case OPS.endCompat:
+ // Ignore operators where the corresponding handlers are known to
+ // be no-op in CanvasGraphics (display/canvas.js). This prevents
+ // serialization errors and is also a bit more efficient.
+ // We could also try to serialize all objects in a general way,
+ // e.g. as done in https://github.com/mozilla/pdf.js/pull/6266,
+ // but doing so is meaningless without knowing the semantics.
+ continue;
+ default:
+ // Note: Let's hope that the ignored operator does not have any
+ // non-serializable arguments, otherwise postMessage will throw
+ // "An object could not be cloned.".
}
operatorList.addOp(fn, args);
}
if (stop) {
deferred.then(function () {
next(resolve, reject);
- });
+ }, reject);
return;
}
// Some PDFs don't close all restores inside object/form.
// Closing those for them.
for (i = 0, ii = preprocessor.savedStatesDepth; i < ii; i++) {
@@ -17490,22 +19574,43 @@
}
resolve();
});
},
- getTextContent: function PartialEvaluator_getTextContent(stream, resources,
- stateManager) {
+ getTextContent:
+ function PartialEvaluator_getTextContent(stream, task, resources,
+ stateManager,
+ normalizeWhitespace) {
stateManager = (stateManager || new StateManager(new TextState()));
+ var WhitespaceRegexp = /\s/g;
+
var textContent = {
items: [],
styles: Object.create(null)
};
- var bidiTexts = textContent.items;
- var SPACE_FACTOR = 0.35;
+ var textContentItem = {
+ initialized: false,
+ str: [],
+ width: 0,
+ height: 0,
+ vertical: false,
+ lastAdvanceWidth: 0,
+ lastAdvanceHeight: 0,
+ textAdvanceScale: 0,
+ spaceWidth: 0,
+ fakeSpaceMin: Infinity,
+ fakeMultiSpaceMin: Infinity,
+ fakeMultiSpaceMax: -0,
+ textRunBreakAllowed: false,
+ transform: null,
+ fontName: null
+ };
+ var SPACE_FACTOR = 0.3;
var MULTI_SPACE_FACTOR = 1.5;
+ var MULTI_SPACE_FACTOR_MAX = 4;
var self = this;
var xref = this.xref;
resources = (xref.fetchIfRef(resources) || Dict.empty);
@@ -17516,38 +19621,108 @@
var preprocessor = new EvaluatorPreprocessor(stream, xref, stateManager);
var textState;
- function newTextChunk() {
+ function ensureTextContentItem() {
+ if (textContentItem.initialized) {
+ return textContentItem;
+ }
var font = textState.font;
if (!(font.loadedName in textContent.styles)) {
textContent.styles[font.loadedName] = {
fontFamily: font.fallbackName,
ascent: font.ascent,
descent: font.descent,
vertical: font.vertical
};
}
- return {
- // |str| is initially an array which we push individual chars to, and
- // then runBidi() overwrites it with the final string.
- str: [],
- dir: null,
- width: 0,
- height: 0,
- transform: null,
- fontName: font.loadedName
- };
+ textContentItem.fontName = font.loadedName;
+
+ // 9.4.4 Text Space Details
+ var tsm = [textState.fontSize * textState.textHScale, 0,
+ 0, textState.fontSize,
+ 0, textState.textRise];
+
+ if (font.isType3Font &&
+ textState.fontMatrix !== FONT_IDENTITY_MATRIX &&
+ textState.fontSize === 1) {
+ var glyphHeight = font.bbox[3] - font.bbox[1];
+ if (glyphHeight > 0) {
+ glyphHeight = glyphHeight * textState.fontMatrix[3];
+ tsm[3] *= glyphHeight;
+ }
+ }
+
+ var trm = Util.transform(textState.ctm,
+ Util.transform(textState.textMatrix, tsm));
+ textContentItem.transform = trm;
+ if (!font.vertical) {
+ textContentItem.width = 0;
+ textContentItem.height = Math.sqrt(trm[2] * trm[2] + trm[3] * trm[3]);
+ textContentItem.vertical = false;
+ } else {
+ textContentItem.width = Math.sqrt(trm[0] * trm[0] + trm[1] * trm[1]);
+ textContentItem.height = 0;
+ textContentItem.vertical = true;
+ }
+
+ var a = textState.textLineMatrix[0];
+ var b = textState.textLineMatrix[1];
+ var scaleLineX = Math.sqrt(a * a + b * b);
+ a = textState.ctm[0];
+ b = textState.ctm[1];
+ var scaleCtmX = Math.sqrt(a * a + b * b);
+ textContentItem.textAdvanceScale = scaleCtmX * scaleLineX;
+ textContentItem.lastAdvanceWidth = 0;
+ textContentItem.lastAdvanceHeight = 0;
+
+ var spaceWidth = font.spaceWidth / 1000 * textState.fontSize;
+ if (spaceWidth) {
+ textContentItem.spaceWidth = spaceWidth;
+ textContentItem.fakeSpaceMin = spaceWidth * SPACE_FACTOR;
+ textContentItem.fakeMultiSpaceMin = spaceWidth * MULTI_SPACE_FACTOR;
+ textContentItem.fakeMultiSpaceMax =
+ spaceWidth * MULTI_SPACE_FACTOR_MAX;
+ // It's okay for monospace fonts to fake as much space as needed.
+ textContentItem.textRunBreakAllowed = !font.isMonospace;
+ } else {
+ textContentItem.spaceWidth = 0;
+ textContentItem.fakeSpaceMin = Infinity;
+ textContentItem.fakeMultiSpaceMin = Infinity;
+ textContentItem.fakeMultiSpaceMax = 0;
+ textContentItem.textRunBreakAllowed = false;
+ }
+
+
+ textContentItem.initialized = true;
+ return textContentItem;
}
- function runBidi(textChunk) {
+ function replaceWhitespace(str) {
+ // Replaces all whitespaces with standard spaces (0x20), to avoid
+ // alignment issues between the textLayer and the canvas if the text
+ // contains e.g. tabs (fixes issue6612.pdf).
+ var i = 0, ii = str.length, code;
+ while (i < ii && (code = str.charCodeAt(i)) >= 0x20 && code <= 0x7F) {
+ i++;
+ }
+ return (i < ii ? str.replace(WhitespaceRegexp, ' ') : str);
+ }
+
+ function runBidiTransform(textChunk) {
var str = textChunk.str.join('');
- var bidiResult = PDFJS.bidi(str, -1, textState.font.vertical);
- textChunk.str = bidiResult.str;
- textChunk.dir = bidiResult.dir;
- return textChunk;
+ var bidiResult = PDFJS.bidi(str, -1, textChunk.vertical);
+ return {
+ str: (normalizeWhitespace ? replaceWhitespace(bidiResult.str) :
+ bidiResult.str),
+ dir: bidiResult.dir,
+ width: textChunk.width,
+ height: textChunk.height,
+ transform: textChunk.transform,
+ fontName: textChunk.fontName
+ };
}
function handleSetFont(fontName, fontRef) {
return self.loadFont(fontName, fontRef, xref, resources).
then(function (translated) {
@@ -17555,36 +19730,19 @@
textState.fontMatrix = translated.font.fontMatrix ||
FONT_IDENTITY_MATRIX;
});
}
- function buildTextGeometry(chars, textChunk) {
+ function buildTextContentItem(chars) {
var font = textState.font;
- textChunk = textChunk || newTextChunk();
- if (!textChunk.transform) {
- // 9.4.4 Text Space Details
- var tsm = [textState.fontSize * textState.textHScale, 0,
- 0, textState.fontSize,
- 0, textState.textRise];
- var trm = textChunk.transform = Util.transform(textState.ctm,
- Util.transform(textState.textMatrix, tsm));
- if (!font.vertical) {
- textChunk.height = Math.sqrt(trm[2] * trm[2] + trm[3] * trm[3]);
- } else {
- textChunk.width = Math.sqrt(trm[0] * trm[0] + trm[1] * trm[1]);
- }
- }
+ var textChunk = ensureTextContentItem();
var width = 0;
var height = 0;
var glyphs = font.charsToGlyphs(chars);
var defaultVMetrics = font.defaultVMetrics;
for (var i = 0; i < glyphs.length; i++) {
var glyph = glyphs[i];
- if (!glyph) { // Previous glyph was a space.
- width += textState.wordSpacing * textState.textHScale;
- continue;
- }
var vMetricX = null;
var vMetricY = null;
var glyphWidth = null;
if (font.vertical) {
if (glyph.vmetric) {
@@ -17616,44 +19774,75 @@
// var trm = Util.transform(textState.textMatrix, tsm);
// var pt = Util.applyTransform([trm[4], trm[5]], textState.ctm);
// var x = pt[0];
// var y = pt[1];
+ var charSpacing = textState.charSpacing;
+ if (glyph.isSpace) {
+ var wordSpacing = textState.wordSpacing;
+ charSpacing += wordSpacing;
+ if (wordSpacing > 0) {
+ addFakeSpaces(wordSpacing, textChunk.str);
+ }
+ }
+
var tx = 0;
var ty = 0;
if (!font.vertical) {
var w0 = glyphWidth * textState.fontMatrix[0];
- tx = (w0 * textState.fontSize + textState.charSpacing) *
+ tx = (w0 * textState.fontSize + charSpacing) *
textState.textHScale;
width += tx;
} else {
var w1 = glyphWidth * textState.fontMatrix[0];
- ty = w1 * textState.fontSize + textState.charSpacing;
+ ty = w1 * textState.fontSize + charSpacing;
height += ty;
}
textState.translateTextMatrix(tx, ty);
textChunk.str.push(glyphUnicode);
}
- var a = textState.textLineMatrix[0];
- var b = textState.textLineMatrix[1];
- var scaleLineX = Math.sqrt(a * a + b * b);
- a = textState.ctm[0];
- b = textState.ctm[1];
- var scaleCtmX = Math.sqrt(a * a + b * b);
if (!font.vertical) {
- textChunk.width += width * scaleCtmX * scaleLineX;
+ textChunk.lastAdvanceWidth = width;
+ textChunk.width += width * textChunk.textAdvanceScale;
} else {
- textChunk.height += Math.abs(height * scaleCtmX * scaleLineX);
+ textChunk.lastAdvanceHeight = height;
+ textChunk.height += Math.abs(height * textChunk.textAdvanceScale);
}
+
return textChunk;
}
+ function addFakeSpaces(width, strBuf) {
+ if (width < textContentItem.fakeSpaceMin) {
+ return;
+ }
+ if (width < textContentItem.fakeMultiSpaceMin) {
+ strBuf.push(' ');
+ return;
+ }
+ var fakeSpaces = Math.round(width / textContentItem.spaceWidth);
+ while (fakeSpaces-- > 0) {
+ strBuf.push(' ');
+ }
+ }
+
+ function flushTextContentItem() {
+ if (!textContentItem.initialized) {
+ return;
+ }
+ textContent.items.push(runBidiTransform(textContentItem));
+
+ textContentItem.initialized = false;
+ textContentItem.str.length = 0;
+ }
+
var timeSlotManager = new TimeSlotManager();
return new Promise(function next(resolve, reject) {
+ task.ensureNotTerminated();
timeSlotManager.reset();
var stop, operation = {}, args = [];
while (!(stop = timeSlotManager.check())) {
// The arguments parsed by read() are not used beyond this loop, so
// we can reuse the same array on every iteration, thus avoiding
@@ -17664,39 +19853,66 @@
break;
}
textState = stateManager.state;
var fn = operation.fn;
args = operation.args;
+ var advance;
switch (fn | 0) {
case OPS.setFont:
+ flushTextContentItem();
textState.fontSize = args[1];
return handleSetFont(args[0].name).then(function() {
next(resolve, reject);
}, reject);
case OPS.setTextRise:
+ flushTextContentItem();
textState.textRise = args[0];
break;
case OPS.setHScale:
+ flushTextContentItem();
textState.textHScale = args[0] / 100;
break;
case OPS.setLeading:
+ flushTextContentItem();
textState.leading = args[0];
break;
case OPS.moveText:
+ // Optimization to treat same line movement as advance
+ var isSameTextLine = !textState.font ? false :
+ ((textState.font.vertical ? args[0] : args[1]) === 0);
+ advance = args[0] - args[1];
+ if (isSameTextLine && textContentItem.initialized &&
+ advance > 0 &&
+ advance <= textContentItem.fakeMultiSpaceMax) {
+ textState.translateTextLineMatrix(args[0], args[1]);
+ textContentItem.width +=
+ (args[0] - textContentItem.lastAdvanceWidth);
+ textContentItem.height +=
+ (args[1] - textContentItem.lastAdvanceHeight);
+ var diff = (args[0] - textContentItem.lastAdvanceWidth) -
+ (args[1] - textContentItem.lastAdvanceHeight);
+ addFakeSpaces(diff, textContentItem.str);
+ break;
+ }
+
+ flushTextContentItem();
textState.translateTextLineMatrix(args[0], args[1]);
textState.textMatrix = textState.textLineMatrix.slice();
break;
case OPS.setLeadingMoveText:
+ flushTextContentItem();
textState.leading = -args[1];
textState.translateTextLineMatrix(args[0], args[1]);
textState.textMatrix = textState.textLineMatrix.slice();
break;
case OPS.nextLine:
+ flushTextContentItem();
textState.carriageReturn();
break;
case OPS.setTextMatrix:
+ flushTextContentItem();
textState.setTextMatrix(args[0], args[1], args[2], args[3],
args[4], args[5]);
textState.setTextLineMatrix(args[0], args[1], args[2], args[3],
args[4], args[5]);
break;
@@ -17705,62 +19921,82 @@
break;
case OPS.setWordSpacing:
textState.wordSpacing = args[0];
break;
case OPS.beginText:
+ flushTextContentItem();
textState.textMatrix = IDENTITY_MATRIX.slice();
textState.textLineMatrix = IDENTITY_MATRIX.slice();
break;
case OPS.showSpacedText:
var items = args[0];
- var textChunk = newTextChunk();
var offset;
for (var j = 0, jj = items.length; j < jj; j++) {
if (typeof items[j] === 'string') {
- buildTextGeometry(items[j], textChunk);
+ buildTextContentItem(items[j]);
} else {
- var val = items[j] / 1000;
- if (!textState.font.vertical) {
- offset = -val * textState.fontSize * textState.textHScale *
- textState.textMatrix[0];
- textState.translateTextMatrix(offset, 0);
- textChunk.width += offset;
+ ensureTextContentItem();
+
+ // PDF Specification 5.3.2 states:
+ // The number is expressed in thousandths of a unit of text
+ // space.
+ // This amount is subtracted from the current horizontal or
+ // vertical coordinate, depending on the writing mode.
+ // In the default coordinate system, a positive adjustment
+ // has the effect of moving the next glyph painted either to
+ // the left or down by the given amount.
+ advance = items[j] * textState.fontSize / 1000;
+ var breakTextRun = false;
+ if (textState.font.vertical) {
+ offset = advance *
+ (textState.textHScale * textState.textMatrix[2] +
+ textState.textMatrix[3]);
+ textState.translateTextMatrix(0, advance);
+ breakTextRun = textContentItem.textRunBreakAllowed &&
+ advance > textContentItem.fakeMultiSpaceMax;
+ if (!breakTextRun) {
+ // Value needs to be added to height to paint down.
+ textContentItem.height += offset;
+ }
} else {
- offset = -val * textState.fontSize *
- textState.textMatrix[3];
- textState.translateTextMatrix(0, offset);
- textChunk.height += offset;
- }
- if (items[j] < 0 && textState.font.spaceWidth > 0) {
- var fakeSpaces = -items[j] / textState.font.spaceWidth;
- if (fakeSpaces > MULTI_SPACE_FACTOR) {
- fakeSpaces = Math.round(fakeSpaces);
- while (fakeSpaces--) {
- textChunk.str.push(' ');
- }
- } else if (fakeSpaces > SPACE_FACTOR) {
- textChunk.str.push(' ');
+ advance = -advance;
+ offset = advance * (
+ textState.textHScale * textState.textMatrix[0] +
+ textState.textMatrix[1]);
+ textState.translateTextMatrix(advance, 0);
+ breakTextRun = textContentItem.textRunBreakAllowed &&
+ advance > textContentItem.fakeMultiSpaceMax;
+ if (!breakTextRun) {
+ // Value needs to be subtracted from width to paint left.
+ textContentItem.width += offset;
}
}
+ if (breakTextRun) {
+ flushTextContentItem();
+ } else if (advance > 0) {
+ addFakeSpaces(advance, textContentItem.str);
+ }
}
}
- bidiTexts.push(runBidi(textChunk));
break;
case OPS.showText:
- bidiTexts.push(runBidi(buildTextGeometry(args[0])));
+ buildTextContentItem(args[0]);
break;
case OPS.nextLineShowText:
+ flushTextContentItem();
textState.carriageReturn();
- bidiTexts.push(runBidi(buildTextGeometry(args[0])));
+ buildTextContentItem(args[0]);
break;
case OPS.nextLineSetSpacingShowText:
+ flushTextContentItem();
textState.wordSpacing = args[0];
textState.charSpacing = args[1];
textState.carriageReturn();
- bidiTexts.push(runBidi(buildTextGeometry(args[2])));
+ buildTextContentItem(args[2]);
break;
case OPS.paintXObject:
+ flushTextContentItem();
if (args[0].code) {
break;
}
if (!xobjs) {
@@ -17768,11 +20004,11 @@
}
var name = args[0].name;
if (xobjsCache.key === name) {
if (xobjsCache.texts) {
- Util.appendToArray(bidiTexts, xobjsCache.texts.items);
+ Util.appendToArray(textContent.items, xobjsCache.texts.items);
Util.extendObj(textContent.styles, xobjsCache.texts.styles);
}
break;
}
@@ -17796,23 +20032,24 @@
var matrix = xobj.dict.get('Matrix');
if (isArray(matrix) && matrix.length === 6) {
stateManager.transform(matrix);
}
- return self.getTextContent(xobj,
- xobj.dict.get('Resources') || resources, stateManager).
- then(function (formTextContent) {
- Util.appendToArray(bidiTexts, formTextContent.items);
+ return self.getTextContent(xobj, task,
+ xobj.dict.get('Resources') || resources, stateManager,
+ normalizeWhitespace).then(function (formTextContent) {
+ Util.appendToArray(textContent.items, formTextContent.items);
Util.extendObj(textContent.styles, formTextContent.styles);
stateManager.restore();
xobjsCache.key = name;
xobjsCache.texts = formTextContent;
next(resolve, reject);
}, reject);
case OPS.setGState:
+ flushTextContentItem();
var dictName = args[0];
var extGState = resources.get('ExtGState');
if (!isDict(extGState) || !extGState.has(dictName.name)) {
break;
@@ -17836,13 +20073,14 @@
} // switch
} // while
if (stop) {
deferred.then(function () {
next(resolve, reject);
- });
+ }, reject);
return;
}
+ flushTextContentItem();
resolve(textContent);
});
},
extractDataStructures: function
@@ -17891,12 +20129,17 @@
var index = 0;
for (var j = 0, jj = diffEncoding.length; j < jj; j++) {
var data = diffEncoding[j];
if (isNum(data)) {
index = data;
- } else {
+ } else if (isName(data)) {
differences[index++] = data.name;
+ } else if (isRef(data)) {
+ diffEncoding[j--] = xref.fetch(data);
+ continue;
+ } else {
+ error('Invalid entry in \'Differences\' array: ' + data);
}
}
}
} else if (isName(encoding)) {
baseEncodingName = encoding.name;
@@ -17939,19 +20182,26 @@
readToUnicode: function PartialEvaluator_readToUnicode(toUnicode) {
var cmap, cmapObj = toUnicode;
if (isName(cmapObj)) {
cmap = CMapFactory.create(cmapObj,
- { url: PDFJS.cMapUrl, packed: PDFJS.cMapPacked }, null).getMap();
- return new ToUnicodeMap(cmap);
+ { url: PDFJS.cMapUrl, packed: PDFJS.cMapPacked }, null);
+ if (cmap instanceof IdentityCMap) {
+ return new IdentityToUnicodeMap(0, 0xFFFF);
+ }
+ return new ToUnicodeMap(cmap.getMap());
} else if (isStream(cmapObj)) {
cmap = CMapFactory.create(cmapObj,
- { url: PDFJS.cMapUrl, packed: PDFJS.cMapPacked }, null).getMap();
+ { url: PDFJS.cMapUrl, packed: PDFJS.cMapPacked }, null);
+ if (cmap instanceof IdentityCMap) {
+ return new IdentityToUnicodeMap(0, 0xFFFF);
+ }
+ var map = new Array(cmap.length);
// Convert UTF-16BE
// NOTE: cmap can be a sparse array, so use forEach instead of for(;;)
// to iterate over all keys.
- cmap.forEach(function(token, i) {
+ cmap.forEach(function(charCode, token) {
var str = [];
for (var k = 0; k < token.length; k += 2) {
var w1 = (token.charCodeAt(k) << 8) | token.charCodeAt(k + 1);
if ((w1 & 0xF800) !== 0xD800) { // w1 < 0xD800 || w1 > 0xDFFF
str.push(w1);
@@ -17959,13 +20209,13 @@
}
k += 2;
var w2 = (token.charCodeAt(k) << 8) | token.charCodeAt(k + 1);
str.push(((w1 & 0x3ff) << 10) + (w2 & 0x3ff) + 0x10000);
}
- cmap[i] = String.fromCharCode.apply(String, str);
+ map[charCode] = String.fromCharCode.apply(String, str);
});
- return new ToUnicodeMap(cmap);
+ return new ToUnicodeMap(map);
}
return null;
},
readCidToGidMap: function PartialEvaluator_readCidToGidMap(cidToGidStream) {
@@ -18171,10 +20421,25 @@
var encoding = baseDict.getRaw('Encoding');
if (isName(encoding)) {
hash.update(encoding.name);
} else if (isRef(encoding)) {
hash.update(encoding.num + '_' + encoding.gen);
+ } else if (isDict(encoding)) {
+ var keys = encoding.getKeys();
+ for (var i = 0, ii = keys.length; i < ii; i++) {
+ var entry = encoding.getRaw(keys[i]);
+ if (isName(entry)) {
+ hash.update(entry.name);
+ } else if (isRef(entry)) {
+ hash.update(entry.num + '_' + entry.gen);
+ } else if (isArray(entry)) { // 'Differences' entry.
+ // Ideally we should check the contents of the array, but to avoid
+ // parsing it here and then again in |extractDataStructures|,
+ // we only use the array length for now (fixes bug1157493.pdf).
+ hash.update(entry.length.toString());
+ }
+ }
}
var toUnicode = dict.get('ToUnicode') || baseDict.get('ToUnicode');
if (isStream(toUnicode)) {
var stream = toUnicode.str || toUnicode;
@@ -18219,10 +20484,11 @@
if (type === 'Type3') {
// FontDescriptor is only required for Type3 fonts when the document
// is a tagged pdf. Create a barbebones one to get by.
descriptor = new Dict(null);
descriptor.set('FontName', Name.get(type));
+ descriptor.set('FontBBox', dict.get('FontBBox'));
} else {
// Before PDF 1.5 if the font was one of the base 14 fonts, having a
// FontDescriptor was not required.
// This case is here for compatibility.
var baseFontName = dict.get('BaseFont');
@@ -18373,11 +20639,11 @@
'Font',
fontData
]);
this.sent = true;
},
- loadType3Data: function (evaluator, resources, parentOperatorList) {
+ loadType3Data: function (evaluator, resources, parentOperatorList, task) {
assert(this.font.isType3Font);
if (this.type3Loaded) {
return this.type3Loaded;
}
@@ -18390,11 +20656,11 @@
var charProcOperatorList = {};
for (var i = 0, n = charProcKeys.length; i < n; ++i) {
loadCharProcsPromise = loadCharProcsPromise.then(function (key) {
var glyphStream = charProcs[key];
var operatorList = new OperatorList();
- return evaluator.getOperatorList(glyphStream, fontResources,
+ return evaluator.getOperatorList(glyphStream, task, fontResources,
operatorList).then(function () {
charProcOperatorList[key] = operatorList.getIR();
// Add the dependencies to the parent operator list so they are
// resolved before sub operator list is executed synchronously.
@@ -18440,19 +20706,28 @@
function OperatorList(intent, messageHandler, pageIndex) {
this.messageHandler = messageHandler;
this.fnArray = [];
this.argsArray = [];
this.dependencies = {};
+ this._totalLength = 0;
this.pageIndex = pageIndex;
this.intent = intent;
}
OperatorList.prototype = {
get length() {
return this.argsArray.length;
},
+ /**
+ * @returns {number} The total length of the entire operator list,
+ * since `this.length === 0` after flushing.
+ */
+ get totalLength() {
+ return (this._totalLength + this.length);
+ },
+
addOp: function(fn, args) {
this.fnArray.push(fn);
this.argsArray.push(args);
if (this.messageHandler) {
if (this.fnArray.length >= CHUNK_SIZE) {
@@ -18497,16 +20772,19 @@
flush: function(lastChunk) {
if (this.intent !== 'oplist') {
new QueueOptimizer().optimize(this);
}
var transfers = getTransfers(this);
+ var length = this.length;
+ this._totalLength += length;
+
this.messageHandler.send('RenderPageChunk', {
operatorList: {
fnArray: this.fnArray,
argsArray: this.argsArray,
lastChunk: lastChunk,
- length: this.length
+ length: length
},
pageIndex: this.pageIndex,
intent: this.intent
}, transfers);
this.dependencies = {};
@@ -19438,10 +21716,11 @@
// Map entries have one of two forms.
// - cid chars are 16-bit unsigned integers, stored as integers.
// - bf chars are variable-length byte sequences, stored as strings, with
// one byte per character.
this._map = [];
+ this.name = '';
this.vertical = false;
this.useCMap = null;
this.builtInCMap = builtInCMap;
}
CMap.prototype = {
@@ -19537,17 +21816,36 @@
}
}
}
out.charcode = 0;
out.length = 1;
+ },
+
+ get length() {
+ return this._map.length;
+ },
+
+ get isIdentityCMap() {
+ if (!(this.name === 'Identity-H' || this.name === 'Identity-V')) {
+ return false;
+ }
+ if (this._map.length !== 0x10000) {
+ return false;
+ }
+ for (var i = 0; i < 0x10000; i++) {
+ if (this._map[i] !== i) {
+ return false;
+ }
+ }
+ return true;
}
};
return CMap;
})();
// A special case of CMap, where the _map array implicitly has a length of
-// 65535 and each element is equal to its index.
+// 65536 and each element is equal to its index.
var IdentityCMap = (function IdentityCMapClosure() {
function IdentityCMap(vertical, n) {
CMap.call(this);
this.vertical = vertical;
this.addCodespaceRange(n, 0, 0xffff);
@@ -19598,11 +21896,19 @@
map[i] = i;
}
return map;
},
- readCharCode: CMap.prototype.readCharCode
+ readCharCode: CMap.prototype.readCharCode,
+
+ get length() {
+ return 0x10000;
+ },
+
+ get isIdentityCMap() {
+ error('should not access .isIdentityCMap');
+ }
};
return IdentityCMap;
})();
@@ -20063,20 +22369,29 @@
if (isInt(obj)) {
cMap.vertical = !!obj;
}
}
+ function parseCMapName(cMap, lexer) {
+ var obj = lexer.getObj();
+ if (isName(obj) && isString(obj.name)) {
+ cMap.name = obj.name;
+ }
+ }
+
function parseCMap(cMap, lexer, builtInCMapParams, useCMap) {
var previous;
var embededUseCMap;
objLoop: while (true) {
var obj = lexer.getObj();
if (isEOF(obj)) {
break;
} else if (isName(obj)) {
if (obj.name === 'WMode') {
parseWMode(cMap, lexer);
+ } else if (obj.name === 'CMapName') {
+ parseCMapName(cMap, lexer);
}
previous = obj;
} else if (isCmd(obj)) {
switch (obj.cmd) {
case 'endcmap':
@@ -20182,10 +22497,13 @@
try {
parseCMap(cMap, lexer, builtInCMapParams, useCMap);
} catch (e) {
warn('Invalid CMap data. ' + e);
}
+ if (cMap.isIdentityCMap) {
+ return createBuiltInCMap(cMap.name, builtInCMapParams);
+ }
return cMap;
}
error('Encoding required.');
}
};
@@ -20488,13 +22806,16 @@
'CourierNew-Italic': 'Courier-Oblique',
'CourierNewPS-BoldItalicMT': 'Courier-BoldOblique',
'CourierNewPS-BoldMT': 'Courier-Bold',
'CourierNewPS-ItalicMT': 'Courier-Oblique',
'CourierNewPSMT': 'Courier',
+ 'Helvetica': 'Helvetica',
'Helvetica-Bold': 'Helvetica-Bold',
'Helvetica-BoldItalic': 'Helvetica-BoldOblique',
+ 'Helvetica-BoldOblique': 'Helvetica-BoldOblique',
'Helvetica-Italic': 'Helvetica-Oblique',
+ 'Helvetica-Oblique':'Helvetica-Oblique',
'Symbol-Bold': 'Symbol',
'Symbol-BoldItalic': 'Symbol',
'Symbol-Italic': 'Symbol',
'TimesNewRoman': 'Times-Roman',
'TimesNewRoman-Bold': 'Times-Bold',
@@ -20516,10 +22837,14 @@
/**
* Holds the map of the non-standard fonts that might be included as a standard
* fonts without glyph data.
*/
var nonStdFontMap = {
+ 'CenturyGothic': 'Helvetica',
+ 'CenturyGothic-Bold': 'Helvetica-Bold',
+ 'CenturyGothic-BoldItalic': 'Helvetica-BoldOblique',
+ 'CenturyGothic-Italic': 'Helvetica-Oblique',
'ComicSansMS': 'Comic Sans MS',
'ComicSansMS-Bold': 'Comic Sans MS-Bold',
'ComicSansMS-BoldItalic': 'Comic Sans MS-BoldItalic',
'ComicSansMS-Italic': 'Comic Sans MS-Italic',
'LucidaConsole': 'Courier',
@@ -20539,11 +22864,12 @@
'MS-PGothic-BoldItalic': 'MS PGothic-BoldItalic',
'MS-PGothic-Italic': 'MS PGothic-Italic',
'MS-PMincho': 'MS PMincho',
'MS-PMincho-Bold': 'MS PMincho-Bold',
'MS-PMincho-BoldItalic': 'MS PMincho-BoldItalic',
- 'MS-PMincho-Italic': 'MS PMincho-Italic'
+ 'MS-PMincho-Italic': 'MS PMincho-Italic',
+ 'Wingdings': 'ZapfDingbats'
};
var serifFonts = {
'Adobe Jenson': true, 'Adobe Text': true, 'Albertus': true,
'Aldus': true, 'Alexandria': true, 'Algerian': true,
@@ -20668,10 +22994,17 @@
'3162': 589, '3165': 891, '3166': 892, '3169': 1274, '3170': 1275,
'3173': 1278, '3174': 1279, '3181': 7622, '3182': 7623, '3282': 11799,
'3316': 578, '3379': 42785, '3393': 1159, '3416': 8377
};
+// The glyph map for ArialBlack differs slightly from the glyph map used for
+// other well-known standard fonts. Hence we use this (incomplete) CID to GID
+// mapping to adjust the glyph map for non-embedded ArialBlack fonts.
+var SupplementalGlyphMapForArialBlack = {
+ '227': 322, '264': 261, '291': 346,
+};
+
// Some characters, e.g. copyrightserif, are mapped to the private use area and
// might not be displayed using standard fonts. Mapping/hacking well-known chars
// to the similar equivalents in the normal characters range.
var SpecialPUASymbols = {
'63721': 0x00A9, // copyrightsans (0xF8E9) => copyright
@@ -20683,11 +23016,11 @@
'63729': 0x23A7, // bracelefttp (0xF8F1)
'63730': 0x23A8, // braceleftmid (0xF8F2)
'63731': 0x23A9, // braceleftbt (0xF8F3)
'63740': 0x23AB, // bracerighttp (0xF8FC)
'63741': 0x23AC, // bracerightmid (0xF8FD)
- '63742': 0x23AD, // bracerightmid (0xF8FE)
+ '63742': 0x23AD, // bracerightbt (0xF8FE)
'63726': 0x23A1, // bracketlefttp (0xF8EE)
'63727': 0x23A2, // bracketleftex (0xF8EF)
'63728': 0x23A3, // bracketleftbt (0xF8F0)
'63737': 0x23A4, // bracketrighttp (0xF8F9)
'63738': 0x23A5, // bracketrightex (0xF8FA)
@@ -22288,10 +24621,13 @@
}
return s;
}
function adjustWidths(properties) {
+ if (!properties.fontMatrix) {
+ return;
+ }
if (properties.fontMatrix[0] === FONT_IDENTITY_MATRIX[0]) {
return;
}
// adjusting width to fontMatrix scale
var scale = 0.001 / properties.fontMatrix[0];
@@ -22323,27 +24659,30 @@
return FontType.UNKNOWN;
}
}
var Glyph = (function GlyphClosure() {
- function Glyph(fontChar, unicode, accent, width, vmetric, operatorListId) {
+ function Glyph(fontChar, unicode, accent, width, vmetric, operatorListId,
+ isSpace) {
this.fontChar = fontChar;
this.unicode = unicode;
this.accent = accent;
this.width = width;
this.vmetric = vmetric;
this.operatorListId = operatorListId;
+ this.isSpace = isSpace;
}
- Glyph.prototype.matchesForCache =
- function(fontChar, unicode, accent, width, vmetric, operatorListId) {
+ Glyph.prototype.matchesForCache = function(fontChar, unicode, accent, width,
+ vmetric, operatorListId, isSpace) {
return this.fontChar === fontChar &&
this.unicode === unicode &&
this.accent === accent &&
this.width === width &&
this.vmetric === vmetric &&
- this.operatorListId === operatorListId;
+ this.operatorListId === operatorListId &&
+ this.isSpace === isSpace;
};
return Glyph;
})();
@@ -22363,10 +24702,14 @@
for (var charCode in this._map) {
callback(charCode, this._map[charCode].charCodeAt(0));
}
},
+ has: function(i) {
+ return this._map[i] !== undefined;
+ },
+
get: function(i) {
return this._map[i];
},
charCodeOf: function(v) {
@@ -22383,28 +24726,32 @@
this.lastChar = lastChar;
}
IdentityToUnicodeMap.prototype = {
get length() {
- error('should not access .length');
+ return (this.lastChar + 1) - this.firstChar;
},
forEach: function (callback) {
for (var i = this.firstChar, ii = this.lastChar; i <= ii; i++) {
callback(i, i);
}
},
+ has: function (i) {
+ return this.firstChar <= i && i <= this.lastChar;
+ },
+
get: function (i) {
if (this.firstChar <= i && i <= this.lastChar) {
return String.fromCharCode(i);
}
return undefined;
},
charCodeOf: function (v) {
- error('should not call .charCodeOf');
+ return (isInt(v) && v >= this.firstChar && v <= this.lastChar) ? v : -1;
}
};
return IdentityToUnicodeMap;
})();
@@ -22548,10 +24895,36 @@
};
return OpenTypeFileBuilder;
})();
+// Problematic Unicode characters in the fonts that needs to be moved to avoid
+// issues when they are painted on the canvas, e.g. complex-script shaping or
+// control/whitespace characters. The ranges are listed in pairs: the first item
+// is a code of the first problematic code, the second one is the next
+// non-problematic code. The ranges must be in sorted order.
+var ProblematicCharRanges = new Int32Array([
+ // Control characters.
+ 0x0000, 0x0020,
+ 0x007F, 0x00A1,
+ 0x00AD, 0x00AE,
+ // Chars that is used in complex-script shaping.
+ 0x0600, 0x0780,
+ 0x08A0, 0x10A0,
+ 0x1780, 0x1800,
+ // General punctuation chars.
+ 0x2000, 0x2010,
+ 0x2011, 0x2012,
+ 0x2028, 0x2030,
+ 0x205F, 0x2070,
+ 0x25CC, 0x25CD,
+ // Chars that is used in complex-script shaping.
+ 0xAA60, 0xAA80,
+ // Specials Unicode block.
+ 0xFFF0, 0x10000
+]);
+
/**
* 'Font' is the class the outside world should use, it encapsulate all the font
* decoding logics whatever type it is (assuming the font type is supported).
*
* For example to read a Type1 font and to attach it to the document:
@@ -22590,10 +24963,11 @@
this.wideChars = properties.wideChars;
this.cMap = properties.cMap;
this.ascent = properties.ascent / PDF_GLYPH_SPACE_UNITS;
this.descent = properties.descent / PDF_GLYPH_SPACE_UNITS;
this.fontMatrix = properties.fontMatrix;
+ this.bbox = properties.bbox;
this.toUnicode = properties.toUnicode = this.buildToUnicode(properties);
this.toFontChar = [];
@@ -22622,11 +24996,12 @@
this.missingFile = true;
// The file data is not specified. Trying to fix the font name
// to be used with the canvas.font.
var fontName = name.replace(/[,_]/g, '-');
- var isStandardFont = fontName in stdFontMap;
+ var isStandardFont = !!stdFontMap[fontName] ||
+ !!(nonStdFontMap[fontName] && stdFontMap[nonStdFontMap[fontName]]);
fontName = stdFontMap[fontName] || nonStdFontMap[fontName] || fontName;
this.bold = (fontName.search(/bold/gi) !== -1);
this.italic = ((fontName.search(/oblique/gi) !== -1) ||
(fontName.search(/italic/gi) !== -1));
@@ -22640,13 +25015,18 @@
if (isStandardFont && type === 'CIDFontType2' &&
properties.cidEncoding.indexOf('Identity-') === 0) {
// Standard fonts might be embedded as CID font without glyph mapping.
// Building one based on GlyphMapForStandardFonts.
var map = [];
- for (var code in GlyphMapForStandardFonts) {
- map[+code] = GlyphMapForStandardFonts[code];
+ for (charCode in GlyphMapForStandardFonts) {
+ map[+charCode] = GlyphMapForStandardFonts[charCode];
}
+ if (/ArialBlack/i.test(name)) {
+ for (charCode in SupplementalGlyphMapForArialBlack) {
+ map[+charCode] = SupplementalGlyphMapForArialBlack[charCode];
+ }
+ }
var isIdentityUnicode = this.toUnicode instanceof IdentityToUnicodeMap;
if (!isIdentityUnicode) {
this.toUnicode.forEach(function(charCode, unicodeCharCode) {
map[+charCode] = unicodeCharCode;
});
@@ -22668,10 +25048,14 @@
continue;
}
this.toFontChar[charCode] = fontChar;
}
} else if (/Dingbats/i.test(fontName)) {
+ if (/Wingdings/i.test(name)) {
+ warn('Wingdings font without embedded font file, ' +
+ 'falling back to the ZapfDingbats encoding.');
+ }
var dingbats = Encodings.ZapfDingbatsEncoding;
for (charCode in dingbats) {
fontChar = DingbatsGlyphsUnicode[dingbats[charCode]];
if (!fontChar) {
continue;
@@ -22719,15 +25103,17 @@
}
}
if (subtype === 'CIDFontType0C' && type !== 'CIDFontType0') {
type = 'CIDFontType0';
}
- // XXX: Temporarily change the type for open type so we trigger a warning.
- // This should be removed when we add support for open type.
if (subtype === 'OpenType') {
type = 'OpenType';
}
+ // Some CIDFontType0C fonts by mistake claim CIDFontType0.
+ if (type === 'CIDFontType0') {
+ subtype = isType1File(file) ? 'CIDFontType0' : 'CIDFontType0C';
+ }
var data;
switch (type) {
case 'MMType1':
info('MMType1 font (' + name + '), falling back to Type1.');
@@ -22752,10 +25138,12 @@
// Repair the TrueType file. It is can be damaged in the point of
// view of the sanitizer
data = this.checkAndRepair(name, file, properties);
if (this.isOpenType) {
+ adjustWidths(properties);
+
type = 'OpenType';
}
break;
default:
@@ -22804,11 +25192,44 @@
function isTrueTypeFile(file) {
var header = file.peekBytes(4);
return readUint32(header, 0) === 0x00010000;
}
+ function isType1File(file) {
+ var header = file.peekBytes(2);
+ // All Type1 font programs must begin with the comment '%!' (0x25 + 0x21).
+ if (header[0] === 0x25 && header[1] === 0x21) {
+ return true;
+ }
+ // ... obviously some fonts violate that part of the specification,
+ // please refer to the comment in |Type1Font| below.
+ if (header[0] === 0x80 && header[1] === 0x01) { // pfb file header.
+ return true;
+ }
+ return false;
+ }
+
/**
+ * Helper function for |adjustMapping|.
+ * @return {boolean}
+ */
+ function isProblematicUnicodeLocation(code) {
+ // Using binary search to find a range start.
+ var i = 0, j = ProblematicCharRanges.length - 1;
+ while (i < j) {
+ var c = (i + j + 1) >> 1;
+ if (code < ProblematicCharRanges[c]) {
+ j = c - 1;
+ } else {
+ i = c;
+ }
+ }
+ // Even index means code in problematic range.
+ return !(i & 1);
+ }
+
+ /**
* Rebuilds the char code to glyph ID map by trying to replace the char codes
* with their unicode value. It also moves char codes that are in known
* problematic locations.
* @return {Object} Two properties:
* 'toFontChar' - maps original char codes(the value that will be read
@@ -22819,47 +25240,35 @@
function adjustMapping(charCodeToGlyphId, properties) {
var toUnicode = properties.toUnicode;
var isSymbolic = !!(properties.flags & FontFlags.Symbolic);
var isIdentityUnicode =
properties.toUnicode instanceof IdentityToUnicodeMap;
- var isCidFontType2 = (properties.type === 'CIDFontType2');
var newMap = Object.create(null);
var toFontChar = [];
var usedFontCharCodes = [];
var nextAvailableFontCharCode = PRIVATE_USE_OFFSET_START;
for (var originalCharCode in charCodeToGlyphId) {
originalCharCode |= 0;
var glyphId = charCodeToGlyphId[originalCharCode];
var fontCharCode = originalCharCode;
// First try to map the value to a unicode position if a non identity map
// was created.
- if (!isIdentityUnicode) {
- if (toUnicode.get(originalCharCode) !== undefined) {
- var unicode = toUnicode.get(fontCharCode);
- // TODO: Try to map ligatures to the correct spot.
- if (unicode.length === 1) {
- fontCharCode = unicode.charCodeAt(0);
- }
- } else if (isCidFontType2) {
- // For CIDFontType2, move characters not present in toUnicode
- // to the private use area (fixes bug 1028735 and issue 4881).
- fontCharCode = nextAvailableFontCharCode;
+ if (!isIdentityUnicode && toUnicode.has(originalCharCode)) {
+ var unicode = toUnicode.get(fontCharCode);
+ // TODO: Try to map ligatures to the correct spot.
+ if (unicode.length === 1) {
+ fontCharCode = unicode.charCodeAt(0);
}
}
// Try to move control characters, special characters and already mapped
// characters to the private use area since they will not be drawn by
// canvas if left in their current position. Also, move characters if the
// font was symbolic and there is only an identity unicode map since the
// characters probably aren't in the correct position (fixes an issue
// with firefox and thuluthfont).
if ((usedFontCharCodes[fontCharCode] !== undefined ||
- fontCharCode <= 0x1f || // Control chars
- fontCharCode === 0x7F || // Control char
- fontCharCode === 0xAD || // Soft hyphen
- (fontCharCode >= 0x80 && fontCharCode <= 0x9F) || // Control chars
- // Prevent drawing characters in the specials unicode block.
- (fontCharCode >= 0xFFF0 && fontCharCode <= 0xFFFF) ||
+ isProblematicUnicodeLocation(fontCharCode) ||
(isSymbolic && isIdentityUnicode)) &&
nextAvailableFontCharCode <= PRIVATE_USE_OFFSET_END) { // Room left.
// Loop to try and find a free spot in the private use area.
do {
fontCharCode = nextAvailableFontCharCode++;
@@ -22882,15 +25291,19 @@
charCodeToGlyphId: newMap,
nextAvailableFontCharCode: nextAvailableFontCharCode
};
}
- function getRanges(glyphs) {
+ function getRanges(glyphs, numGlyphs) {
// Array.sort() sorts by characters, not numerically, so convert to an
// array of characters.
var codes = [];
for (var charCode in glyphs) {
+ // Remove an invalid glyph ID mappings to make OTS happy.
+ if (glyphs[charCode] >= numGlyphs) {
+ continue;
+ }
codes.push({ fontCharCode: charCode | 0, glyphId: glyphs[charCode] });
}
codes.sort(function fontGetRangesSort(a, b) {
return a.fontCharCode - b.fontCharCode;
});
@@ -22915,12 +25328,12 @@
}
return ranges;
}
- function createCmapTable(glyphs) {
- var ranges = getRanges(glyphs);
+ function createCmapTable(glyphs, numGlyphs) {
+ var ranges = getRanges(glyphs, numGlyphs);
var numTables = ranges[ranges.length - 1][1] > 0xFFFF ? 2 : 1;
var cmap = '\x00\x00' + // version
string16(numTables) + // numTables
'\x00\x03' + // platformID
'\x00\x01' + // encodingID
@@ -23307,11 +25720,20 @@
/**
* Read the appropriate subtable from the cmap according to 9.6.6.4 from
* PDF spec
*/
- function readCmapTable(cmap, font, isSymbolicFont) {
+ function readCmapTable(cmap, font, isSymbolicFont, hasEncoding) {
+ if (!cmap) {
+ warn('No cmap table available.');
+ return {
+ platformId: -1,
+ encodingId: -1,
+ mappings: [],
+ hasShortCmap: false
+ };
+ }
var segment;
var start = (font.start ? font.start : 0) + cmap.offset;
font.pos = start;
var version = font.getUint16();
@@ -23329,17 +25751,24 @@
var platformId = font.getUint16();
var encodingId = font.getUint16();
var offset = font.getInt32() >>> 0;
var useTable = false;
- if (platformId === 1 && encodingId === 0) {
+ if (platformId === 0 && encodingId === 0) {
useTable = true;
// Continue the loop since there still may be a higher priority
// table.
- } else if (!isSymbolicFont && platformId === 3 && encodingId === 1) {
+ } else if (platformId === 1 && encodingId === 0) {
useTable = true;
- canBreak = true;
+ // Continue the loop since there still may be a higher priority
+ // table.
+ } else if (platformId === 3 && encodingId === 1 &&
+ ((!isSymbolicFont && hasEncoding) || !potentialTable)) {
+ useTable = true;
+ if (!isSymbolicFont) {
+ canBreak = true;
+ }
} else if (isSymbolicFont && platformId === 3 && encodingId === 0) {
useTable = true;
canBreak = true;
}
@@ -23353,21 +25782,23 @@
if (canBreak) {
break;
}
}
- if (!potentialTable) {
+ if (potentialTable) {
+ font.pos = start + potentialTable.offset;
+ }
+ if (!potentialTable || font.peekByte() === -1) {
warn('Could not find a preferred cmap table.');
return {
platformId: -1,
encodingId: -1,
mappings: [],
hasShortCmap: false
};
}
- font.pos = start + potentialTable.offset;
var format = font.getUint16();
var length = font.getUint16();
var language = font.getUint16();
var hasShortCmap = false;
@@ -23466,11 +25897,17 @@
charCode: charCode,
glyphId: glyphId
});
}
} else {
- error('cmap table has unsupported format: ' + format);
+ warn('cmap table has unsupported format: ' + format);
+ return {
+ platformId: -1,
+ encodingId: -1,
+ mappings: [],
+ hasShortCmap: false
+ };
}
// removing duplicate entries
mappings.sort(function (a, b) {
return a.charCode - b.charCode;
@@ -23680,10 +26117,11 @@
var oldGlyfData = glyf.data;
var oldGlyfDataLength = oldGlyfData.length;
var newGlyfData = new Uint8Array(oldGlyfDataLength);
var startOffset = itemDecode(locaData, 0);
var writeOffset = 0;
+ var missingGlyphData = {};
itemEncode(locaData, 0, writeOffset);
var i, j;
for (i = 0, j = itemSize; i < numGlyphs; i++, j += itemSize) {
var endOffset = itemDecode(locaData, j);
if (endOffset > oldGlyfDataLength &&
@@ -23697,10 +26135,14 @@
itemEncode(locaData, j, writeOffset);
startOffset = endOffset;
continue;
}
+ if (startOffset === endOffset) {
+ missingGlyphData[i] = true;
+ }
+
var newLength = sanitizeGlyph(oldGlyfData, startOffset, endOffset,
newGlyfData, writeOffset, hintsValid);
writeOffset += newLength;
itemEncode(locaData, j, writeOffset);
startOffset = endOffset;
@@ -23713,11 +26155,11 @@
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 49, 0]);
for (i = 0, j = itemSize; i < numGlyphs; i++, j += itemSize) {
itemEncode(locaData, j, simpleGlyph.length);
}
glyf.data = simpleGlyph;
- return;
+ return missingGlyphData;
}
if (dupFirstEntry) {
var firstEntryLength = itemDecode(locaData, itemSize);
if (newGlyfData.length > firstEntryLength + writeOffset) {
@@ -23730,10 +26172,11 @@
itemEncode(loca.data, locaData.length - itemSize,
writeOffset + firstEntryLength);
} else {
glyf.data = newGlyfData.subarray(0, writeOffset);
}
+ return missingGlyphData;
}
function readPostScriptTable(post, properties, maxpNumGlyphs) {
var start = (font.start ? font.start : 0) + post.offset;
font.pos = start;
@@ -23792,10 +26235,13 @@
case 0x00030000:
break;
default:
warn('Unknown/unsupported post table version ' + version);
valid = false;
+ if (properties.defaultEncoding) {
+ glyphNames = properties.defaultEncoding;
+ }
break;
}
properties.glyphNames = glyphNames;
return valid;
}
@@ -24120,15 +26566,18 @@
}
var isTrueType = !tables['CFF '];
if (!isTrueType) {
// OpenType font
- if (!tables.head || !tables.hhea || !tables.maxp || !tables.post) {
+ if ((header.version === 'OTTO' && properties.type !== 'CIDFontType2') ||
+ !tables.head || !tables.hhea || !tables.maxp || !tables.post) {
// no major tables: throwing everything at CFFFont
cffFile = new Stream(tables['CFF '].data);
cff = new CFFFont(cffFile, properties);
+ adjustWidths(properties);
+
return this.convert(name, cff, properties);
}
delete tables.glyf;
delete tables.loca;
@@ -24189,15 +26638,17 @@
error('Required "head" table is not found');
}
sanitizeHead(tables.head, numGlyphs, isTrueType ? tables.loca.length : 0);
+ var missingGlyphs = {};
if (isTrueType) {
var isGlyphLocationsLong = int16(tables.head.data[50],
tables.head.data[51]);
- sanitizeGlyphLocations(tables.loca, tables.glyf, numGlyphs,
- isGlyphLocationsLong, hintsValid, dupFirstEntry);
+ missingGlyphs = sanitizeGlyphLocations(tables.loca, tables.glyf,
+ numGlyphs, isGlyphLocationsLong,
+ hintsValid, dupFirstEntry);
}
if (!tables.hhea) {
error('Required "hhea" table is not found');
}
@@ -24207,47 +26658,85 @@
if (tables.hhea.data[10] === 0 && tables.hhea.data[11] === 0) {
tables.hhea.data[10] = 0xFF;
tables.hhea.data[11] = 0xFF;
}
+ // Extract some more font properties from the OpenType head and
+ // hhea tables; yMin and descent value are always negative.
+ var metricsOverride = {
+ unitsPerEm: int16(tables.head.data[18], tables.head.data[19]),
+ yMax: int16(tables.head.data[42], tables.head.data[43]),
+ yMin: int16(tables.head.data[38], tables.head.data[39]) - 0x10000,
+ ascent: int16(tables.hhea.data[4], tables.hhea.data[5]),
+ descent: int16(tables.hhea.data[6], tables.hhea.data[7]) - 0x10000
+ };
+
+ // PDF FontDescriptor metrics lie -- using data from actual font.
+ this.ascent = metricsOverride.ascent / metricsOverride.unitsPerEm;
+ this.descent = metricsOverride.descent / metricsOverride.unitsPerEm;
+
// The 'post' table has glyphs names.
if (tables.post) {
var valid = readPostScriptTable(tables.post, properties, numGlyphs);
if (!valid) {
tables.post = null;
}
}
var charCodeToGlyphId = [], charCode;
+ var toUnicode = properties.toUnicode, widths = properties.widths;
+ var skipToUnicode = (toUnicode instanceof IdentityToUnicodeMap ||
+ toUnicode.length === 0x10000);
+
+ // Helper function to try to skip mapping of empty glyphs.
+ // Note: In some cases, just relying on the glyph data doesn't work,
+ // hence we also use a few heuristics to fix various PDF files.
+ function hasGlyph(glyphId, charCode, widthCode) {
+ if (!missingGlyphs[glyphId]) {
+ return true;
+ }
+ if (!skipToUnicode && charCode >= 0 && toUnicode.has(charCode)) {
+ return true;
+ }
+ if (widths && widthCode >= 0 && isNum(widths[widthCode])) {
+ return true;
+ }
+ return false;
+ }
+
if (properties.type === 'CIDFontType2') {
var cidToGidMap = properties.cidToGidMap || [];
- var cidToGidMapLength = cidToGidMap.length;
+ var isCidToGidMapEmpty = cidToGidMap.length === 0;
+
properties.cMap.forEach(function(charCode, cid) {
assert(cid <= 0xffff, 'Max size of CID is 65,535');
var glyphId = -1;
- if (cidToGidMapLength === 0) {
+ if (isCidToGidMapEmpty) {
glyphId = charCode;
} else if (cidToGidMap[cid] !== undefined) {
glyphId = cidToGidMap[cid];
}
- if (glyphId >= 0 && glyphId < numGlyphs) {
+
+ if (glyphId >= 0 && glyphId < numGlyphs &&
+ hasGlyph(glyphId, charCode, cid)) {
charCodeToGlyphId[charCode] = glyphId;
}
});
if (dupFirstEntry) {
charCodeToGlyphId[0] = numGlyphs - 1;
}
} else {
// Most of the following logic in this code branch is based on the
// 9.6.6.4 of the PDF spec.
- var cmapTable = readCmapTable(tables.cmap, font, this.isSymbolicFont);
+ var hasEncoding =
+ properties.differences.length > 0 || !!properties.baseEncodingName;
+ var cmapTable =
+ readCmapTable(tables.cmap, font, this.isSymbolicFont, hasEncoding);
var cmapPlatformId = cmapTable.platformId;
var cmapEncodingId = cmapTable.encodingId;
var cmapMappings = cmapTable.mappings;
var cmapMappingsLength = cmapMappings.length;
- var hasEncoding = properties.differences.length ||
- !!properties.baseEncodingName;
// The spec seems to imply that if the font is symbolic the encoding
// should be ignored, this doesn't appear to work for 'preistabelle.pdf'
// where the the font is symbolic and it has an encoding.
if (hasEncoding &&
@@ -24277,35 +26766,47 @@
glyphName = Encodings.StandardEncoding[charCode];
}
if (!glyphName) {
continue;
}
- var unicodeOrCharCode;
+ var unicodeOrCharCode, isUnicode = false;
if (cmapPlatformId === 3 && cmapEncodingId === 1) {
unicodeOrCharCode = GlyphsUnicode[glyphName];
+ isUnicode = true;
} else if (cmapPlatformId === 1 && cmapEncodingId === 0) {
// TODO: the encoding needs to be updated with mac os table.
unicodeOrCharCode = Encodings.MacRomanEncoding.indexOf(glyphName);
}
var found = false;
for (i = 0; i < cmapMappingsLength; ++i) {
- if (cmapMappings[i].charCode === unicodeOrCharCode) {
+ if (cmapMappings[i].charCode !== unicodeOrCharCode) {
+ continue;
+ }
+ var code = isUnicode ? charCode : unicodeOrCharCode;
+ if (hasGlyph(cmapMappings[i].glyphId, code, -1)) {
charCodeToGlyphId[charCode] = cmapMappings[i].glyphId;
found = true;
break;
}
}
if (!found && properties.glyphNames) {
- // Try to map using the post table. There are currently no known
- // pdfs that this fixes.
+ // Try to map using the post table.
var glyphId = properties.glyphNames.indexOf(glyphName);
- if (glyphId > 0) {
+ if (glyphId > 0 && hasGlyph(glyphId, -1, -1)) {
charCodeToGlyphId[charCode] = glyphId;
+ } else {
+ charCodeToGlyphId[charCode] = 0; // notdef
}
}
}
+ } else if (cmapPlatformId === 0 && cmapEncodingId === 0) {
+ // Default Unicode semantics, use the charcodes as is.
+ for (i = 0; i < cmapMappingsLength; ++i) {
+ charCodeToGlyphId[cmapMappings[i].charCode] =
+ cmapMappings[i].glyphId;
+ }
} else {
// For (3, 0) cmap tables:
// The charcode key being stored in charCodeToGlyphId is the lower
// byte of the two-byte charcodes of the cmap table since according to
// the spec: 'each byte from the string shall be prepended with the
@@ -24333,28 +26834,18 @@
// Converting glyphs and ids into font's cmap table
var newMapping = adjustMapping(charCodeToGlyphId, properties);
this.toFontChar = newMapping.toFontChar;
tables.cmap = {
tag: 'cmap',
- data: createCmapTable(newMapping.charCodeToGlyphId)
+ data: createCmapTable(newMapping.charCodeToGlyphId, numGlyphs)
};
if (!tables['OS/2'] || !validateOS2Table(tables['OS/2'])) {
- // extract some more font properties from the OpenType head and
- // hhea tables; yMin and descent value are always negative
- var override = {
- unitsPerEm: int16(tables.head.data[18], tables.head.data[19]),
- yMax: int16(tables.head.data[42], tables.head.data[43]),
- yMin: int16(tables.head.data[38], tables.head.data[39]) - 0x10000,
- ascent: int16(tables.hhea.data[4], tables.hhea.data[5]),
- descent: int16(tables.hhea.data[6], tables.hhea.data[7]) - 0x10000
- };
-
tables['OS/2'] = {
tag: 'OS/2',
data: createOS2Table(properties, newMapping.charCodeToGlyphId,
- override)
+ metricsOverride)
};
}
// Rewrite the 'post' table if needed
if (!tables.post) {
@@ -24481,11 +26972,12 @@
builder.addTable('CFF ', font.data);
// OS/2 and Windows Specific metrics
builder.addTable('OS/2', createOS2Table(properties,
newMapping.charCodeToGlyphId));
// Character to glyphs mapping
- builder.addTable('cmap', createCmapTable(newMapping.charCodeToGlyphId));
+ builder.addTable('cmap', createCmapTable(newMapping.charCodeToGlyphId,
+ numGlyphs));
// Font header
builder.addTable('head',
'\x00\x01\x00\x00' + // Version number
'\x00\x00\x10\x00' + // fontRevision
'\x00\x00\x00\x00' + // checksumAdjustement
@@ -24703,11 +27195,11 @@
if (this.cMap.contains(glyphUnicode)) {
charcode = this.cMap.lookup(glyphUnicode);
}
}
// ... via toUnicode map
- if (!charcode && 'toUnicode' in this) {
+ if (!charcode && this.toUnicode) {
charcode = this.toUnicode.charCodeOf(glyphUnicode);
}
// setting it to unicode if negative or undefined
if (charcode <= 0) {
charcode = glyphUnicode;
@@ -24723,11 +27215,11 @@
// https://github.com/mozilla/pdf.js/pull/2127#discussion_r1662280
this._shadowWidth = width;
return width;
},
- charToGlyph: function Font_charToGlyph(charcode) {
+ charToGlyph: function Font_charToGlyph(charcode, isSpace) {
var fontCharCode, width, operatorListId;
var widthCode = charcode;
if (this.cMap && this.cMap.contains(charcode)) {
widthCode = this.cMap.lookup(charcode);
@@ -24766,13 +27258,13 @@
var fontChar = String.fromCharCode(fontCharCode);
var glyph = this.glyphCache[charcode];
if (!glyph ||
!glyph.matchesForCache(fontChar, unicode, accent, width, vmetric,
- operatorListId)) {
+ operatorListId, isSpace)) {
glyph = new Glyph(fontChar, unicode, accent, width, vmetric,
- operatorListId);
+ operatorListId, isSpace);
this.glyphCache[charcode] = glyph;
}
return glyph;
},
@@ -24804,26 +27296,20 @@
while (i < chars.length) {
this.cMap.readCharCode(chars, i, c);
charcode = c.charcode;
var length = c.length;
i += length;
- glyph = this.charToGlyph(charcode);
+ // Space is char with code 0x20 and length 1 in multiple-byte codes.
+ var isSpace = length === 1 && chars.charCodeAt(i - 1) === 0x20;
+ glyph = this.charToGlyph(charcode, isSpace);
glyphs.push(glyph);
- // placing null after each word break charcode (ASCII SPACE)
- // Ignore occurences of 0x20 in multiple-byte codes.
- if (length === 1 && chars.charCodeAt(i - 1) === 0x20) {
- glyphs.push(null);
- }
}
} else {
for (i = 0, ii = chars.length; i < ii; ++i) {
charcode = chars.charCodeAt(i);
- glyph = this.charToGlyph(charcode);
+ glyph = this.charToGlyph(charcode, charcode === 0x20);
glyphs.push(glyph);
- if (charcode === 0x20) {
- glyphs.push(null);
- }
}
}
// Enter the translated string into the cache
return (charsCache[charsCacheKey] = glyphs);
@@ -24871,10 +27357,12 @@
baseEncoding = Encodings[properties.baseEncodingName];
for (charCode = 0; charCode < baseEncoding.length; charCode++) {
glyphId = glyphNames.indexOf(baseEncoding[charCode]);
if (glyphId >= 0) {
charCodeToGlyphId[charCode] = glyphId;
+ } else {
+ charCodeToGlyphId[charCode] = 0; // notdef
}
}
} else if (!!(properties.flags & FontFlags.Symbolic)) {
// For a symbolic font the encoding should be the fonts built-in
// encoding.
@@ -24887,10 +27375,12 @@
baseEncoding = Encodings.StandardEncoding;
for (charCode = 0; charCode < baseEncoding.length; charCode++) {
glyphId = glyphNames.indexOf(baseEncoding[charCode]);
if (glyphId >= 0) {
charCodeToGlyphId[charCode] = glyphId;
+ } else {
+ charCodeToGlyphId[charCode] = 0; // notdef
}
}
}
// Lastly, merge in the differences.
@@ -24899,10 +27389,12 @@
for (charCode in differences) {
var glyphName = differences[charCode];
glyphId = glyphNames.indexOf(glyphName);
if (glyphId >= 0) {
charCodeToGlyphId[charCode] = glyphId;
+ } else {
+ charCodeToGlyphId[charCode] = 0; // notdef
}
}
}
return charCodeToGlyphId;
}
@@ -27482,20 +29974,19 @@
ranges[l].ids[code - ranges[l].start] : code)) & 0xFFFF;
}
return 0;
}
- function compileGlyf(code, js, font) {
+ function compileGlyf(code, cmds, font) {
function moveTo(x, y) {
- js.push('c.moveTo(' + x + ',' + y + ');');
+ cmds.push({cmd: 'moveTo', args: [x, y]});
}
function lineTo(x, y) {
- js.push('c.lineTo(' + x + ',' + y + ');');
+ cmds.push({cmd: 'lineTo', args: [x, y]});
}
function quadraticCurveTo(xa, ya, x, y) {
- js.push('c.quadraticCurveTo(' + xa + ',' + ya + ',' +
- x + ',' + y + ');');
+ cmds.push({cmd: 'quadraticCurveTo', args: [xa, ya, x, y]});
}
var i = 0;
var numberOfContours = ((code[i] << 24) | (code[i + 1] << 16)) >> 16;
var flags;
@@ -27537,15 +30028,15 @@
scaleY = ((code[i + 6] << 24) | (code[i + 7] << 16)) / 1073741824;
i += 8;
}
var subglyph = font.glyphs[glyphIndex];
if (subglyph) {
- js.push('c.save();');
- js.push('c.transform(' + scaleX + ',' + scale01 + ',' +
- scale10 + ',' + scaleY + ',' + x + ',' + y + ');');
- compileGlyf(subglyph, js, font);
- js.push('c.restore();');
+ cmds.push({cmd: 'save'});
+ cmds.push({cmd: 'transform',
+ args: [scaleX, scale01, scale10, scaleY, x, y]});
+ compileGlyf(subglyph, cmds, font);
+ cmds.push({cmd: 'restore'});
}
} while ((flags & 0x20));
} else {
// simple glyph
var endPtsOfContours = [];
@@ -27637,24 +30128,23 @@
startPoint = endPoint + 1;
}
}
}
- function compileCharString(code, js, font) {
+ function compileCharString(code, cmds, font) {
var stack = [];
var x = 0, y = 0;
var stems = 0;
function moveTo(x, y) {
- js.push('c.moveTo(' + x + ',' + y + ');');
+ cmds.push({cmd: 'moveTo', args: [x, y]});
}
function lineTo(x, y) {
- js.push('c.lineTo(' + x + ',' + y + ');');
+ cmds.push({cmd: 'lineTo', args: [x, y]});
}
function bezierCurveTo(x1, y1, x2, y2, x, y) {
- js.push('c.bezierCurveTo(' + x1 + ',' + y1 + ',' + x2 + ',' + y2 + ',' +
- x + ',' + y + ');');
+ cmds.push({cmd: 'bezierCurveTo', args: [x1, y1, x2, y2, x, y]});
}
function parse(code) {
var i = 0;
while (i < code.length) {
@@ -27779,20 +30269,20 @@
if (stack.length >= 4) {
var achar = stack.pop();
var bchar = stack.pop();
y = stack.pop();
x = stack.pop();
- js.push('c.save();');
- js.push('c.translate('+ x + ',' + y + ');');
+ cmds.push({cmd: 'save'});
+ cmds.push({cmd: 'translate', args: [x, y]});
var gid = lookupCmap(font.cmap, String.fromCharCode(
font.glyphNameMap[Encodings.StandardEncoding[achar]]));
- compileCharString(font.glyphs[gid], js, font);
- js.push('c.restore();');
+ compileCharString(font.glyphs[gid], cmds, font);
+ cmds.push({cmd: 'restore'});
gid = lookupCmap(font.cmap, String.fromCharCode(
font.glyphNameMap[Encodings.StandardEncoding[bchar]]));
- compileCharString(font.glyphs[gid], js, font);
+ compileCharString(font.glyphs[gid], cmds, font);
}
return;
case 18: // hstemhm
stems += stack.length >> 1;
stackClean = true;
@@ -27957,20 +30447,20 @@
compileGlyph: function (code) {
if (!code || code.length === 0 || code[0] === 14) {
return noop;
}
- var js = [];
- js.push('c.save();');
- js.push('c.transform(' + this.fontMatrix.join(',') + ');');
- js.push('c.scale(size, -size);');
+ var cmds = [];
+ cmds.push({cmd: 'save'});
+ cmds.push({cmd: 'transform', args: this.fontMatrix.slice()});
+ cmds.push({cmd: 'scale', args: ['size', '-size']});
- this.compileGlyphImpl(code, js);
+ this.compileGlyphImpl(code, cmds);
- js.push('c.restore();');
+ cmds.push({cmd: 'restore'});
- return js.join('\n');
+ return cmds;
},
compileGlyphImpl: function () {
error('Children classes should implement this.');
},
@@ -27990,12 +30480,12 @@
this.compiledGlyphs = [];
}
Util.inherit(TrueTypeCompiled, CompiledFont, {
- compileGlyphImpl: function (code, js) {
- compileGlyf(code, js, this);
+ compileGlyphImpl: function (code, cmds) {
+ compileGlyf(code, cmds, this);
}
});
function Type2Compiled(cffInfo, cmap, fontMatrix, glyphNameMap) {
fontMatrix = fontMatrix || [0.001, 0, 0, 0.001, 0, 0];
@@ -28012,12 +30502,12 @@
this.subrsBias = (this.subrs.length < 1240 ?
107 : (this.subrs.length < 33900 ? 1131 : 32768));
}
Util.inherit(Type2Compiled, CompiledFont, {
- compileGlyphImpl: function (code, js) {
- compileCharString(code, js, this);
+ compileGlyphImpl: function (code, cmds) {
+ compileCharString(code, cmds, this);
}
});
return {
@@ -28060,11 +30550,10 @@
}
};
})();
-
var GlyphsUnicode = {
A: 0x0041,
AE: 0x00C6,
AEacute: 0x01FC,
AEmacron: 0x01E2,
@@ -32596,11 +35085,16 @@
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);
+ var maskDict = mask.dict, imageMask = maskDict.get('ImageMask', 'IM');
+ if (!imageMask) {
+ warn('Ignoring /Mask in image without /ImageMask.');
+ } else {
+ this.mask = new PDFImage(xref, res, mask, false, null, null, true);
+ }
} else {
// Color key mask (just an array).
this.mask = mask;
}
}
@@ -33018,11 +35512,14 @@
buffer[i] ^= 0xff;
}
}
return imgData;
}
- if (this.image instanceof JpegStream && !this.smask && !this.mask) {
+ if (this.image instanceof JpegStream && !this.smask && !this.mask &&
+ (this.colorSpace.name === 'DeviceGray' ||
+ this.colorSpace.name === 'DeviceRGB' ||
+ this.colorSpace.name === 'DeviceCMYK')) {
imgData.kind = ImageKind.RGB_24BPP;
imgData.data = this.getImageBytes(originalHeight * rowBytes,
drawWidth, drawHeight, true);
return imgData;
}
@@ -36067,27 +38564,24 @@
'a191': 918
}
};
-
var EOF = {};
function isEOF(v) {
return (v === EOF);
}
+var MAX_LENGTH_TO_CACHE = 1000;
+
var Parser = (function ParserClosure() {
function Parser(lexer, allowStreams, xref) {
this.lexer = lexer;
this.allowStreams = allowStreams;
this.xref = xref;
- this.imageCache = {
- length: 0,
- adler32: 0,
- stream: null
- };
+ this.imageCache = {};
this.refill();
}
Parser.prototype = {
refill: function Parser_refill() {
@@ -36101,10 +38595,23 @@
} else {
this.buf1 = this.buf2;
this.buf2 = this.lexer.getObj();
}
},
+ tryShift: function Parser_tryShift() {
+ try {
+ this.shift();
+ return true;
+ } catch (e) {
+ if (e instanceof MissingDataException) {
+ throw e;
+ }
+ // Upon failure, the caller should reset this.lexer.pos to a known good
+ // state and call this.shift() twice to reset the buffers.
+ return false;
+ }
+ },
getObj: function Parser_getObj(cipherTransform) {
var buf1 = this.buf1;
this.shift();
if (buf1 instanceof Cmd) {
@@ -36174,140 +38681,310 @@
}
// simple object
return buf1;
},
- makeInlineImage: function Parser_makeInlineImage(cipherTransform) {
- var lexer = this.lexer;
- var stream = lexer.stream;
-
- // parse dictionary
- var dict = new Dict(null);
- 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;
- var E = 0x45, I = 0x49, SPACE = 0x20, NL = 0xA, CR = 0xD;
+ /**
+ * Find the end of the stream by searching for the /EI\s/.
+ * @returns {number} The inline stream length.
+ */
+ findDefaultInlineStreamEnd:
+ function Parser_findDefaultInlineStreamEnd(stream) {
+ var E = 0x45, I = 0x49, SPACE = 0x20, LF = 0xA, CR = 0xD;
+ var startPos = stream.pos, state = 0, ch, i, n, followingBytes;
while ((ch = stream.getByte()) !== -1) {
if (state === 0) {
state = (ch === E) ? 1 : 0;
} else if (state === 1) {
state = (ch === I) ? 2 : 0;
} else {
assert(state === 2);
- if (ch === SPACE || ch === NL || ch === CR) {
+ if (ch === SPACE || ch === LF || ch === CR) {
// Let's check the next five bytes are ASCII... just be sure.
- var n = 5;
- var followingBytes = stream.peekBytes(n);
+ n = 5;
+ followingBytes = stream.peekBytes(n);
for (i = 0; i < n; i++) {
ch = followingBytes[i];
- if (ch !== NL && ch !== CR && (ch < SPACE || ch > 0x7F)) {
+ if (ch !== LF && ch !== CR && (ch < SPACE || ch > 0x7F)) {
// Not a LF, CR, SPACE or any visible ASCII character, i.e.
// it's binary stuff. Resetting the state.
state = 0;
break;
}
}
if (state === 2) {
- break; // finished!
+ break; // Finished!
}
} else {
state = 0;
}
}
}
+ return ((stream.pos - 4) - startPos);
+ },
+ /**
+ * Find the EOI (end-of-image) marker 0xFFD9 of the stream.
+ * @returns {number} The inline stream length.
+ */
+ findDCTDecodeInlineStreamEnd:
+ function Parser_findDCTDecodeInlineStreamEnd(stream) {
+ var startPos = stream.pos, foundEOI = false, b, markerLength, length;
+ while ((b = stream.getByte()) !== -1) {
+ if (b !== 0xFF) { // Not a valid marker.
+ continue;
+ }
+ switch (stream.getByte()) {
+ case 0x00: // Byte stuffing.
+ // 0xFF00 appears to be a very common byte sequence in JPEG images.
+ break;
- var length = (stream.pos - 4) - startPos;
+ case 0xFF: // Fill byte.
+ // Avoid skipping a valid marker, resetting the stream position.
+ stream.skip(-1);
+ break;
+
+ case 0xD9: // EOI
+ foundEOI = true;
+ break;
+
+ case 0xC0: // SOF0
+ case 0xC1: // SOF1
+ case 0xC2: // SOF2
+ case 0xC3: // SOF3
+
+ case 0xC5: // SOF5
+ case 0xC6: // SOF6
+ case 0xC7: // SOF7
+
+ case 0xC9: // SOF9
+ case 0xCA: // SOF10
+ case 0xCB: // SOF11
+
+ case 0xCD: // SOF13
+ case 0xCE: // SOF14
+ case 0xCF: // SOF15
+
+ case 0xC4: // DHT
+ case 0xCC: // DAC
+
+ case 0xDA: // SOS
+ case 0xDB: // DQT
+ case 0xDC: // DNL
+ case 0xDD: // DRI
+ case 0xDE: // DHP
+ case 0xDF: // EXP
+
+ case 0xE0: // APP0
+ case 0xE1: // APP1
+ case 0xE2: // APP2
+ case 0xE3: // APP3
+ case 0xE4: // APP4
+ case 0xE5: // APP5
+ case 0xE6: // APP6
+ case 0xE7: // APP7
+ case 0xE8: // APP8
+ case 0xE9: // APP9
+ case 0xEA: // APP10
+ case 0xEB: // APP11
+ case 0xEC: // APP12
+ case 0xED: // APP13
+ case 0xEE: // APP14
+ case 0xEF: // APP15
+
+ case 0xFE: // COM
+ // The marker should be followed by the length of the segment.
+ markerLength = stream.getUint16();
+ if (markerLength > 2) {
+ // |markerLength| contains the byte length of the marker segment,
+ // including its own length (2 bytes) and excluding the marker.
+ stream.skip(markerLength - 2); // Jump to the next marker.
+ } else {
+ // The marker length is invalid, resetting the stream position.
+ stream.skip(-2);
+ }
+ break;
+ }
+ if (foundEOI) {
+ break;
+ }
+ }
+ length = stream.pos - startPos;
+ if (b === -1) {
+ warn('Inline DCTDecode image stream: ' +
+ 'EOI marker not found, searching for /EI/ instead.');
+ stream.skip(-length); // Reset the stream position.
+ return this.findDefaultInlineStreamEnd(stream);
+ }
+ this.inlineStreamSkipEI(stream);
+ return length;
+ },
+ /**
+ * Find the EOD (end-of-data) marker '~>' (i.e. TILDE + GT) of the stream.
+ * @returns {number} The inline stream length.
+ */
+ findASCII85DecodeInlineStreamEnd:
+ function Parser_findASCII85DecodeInlineStreamEnd(stream) {
+ var TILDE = 0x7E, GT = 0x3E;
+ var startPos = stream.pos, ch, length;
+ while ((ch = stream.getByte()) !== -1) {
+ if (ch === TILDE && stream.peekByte() === GT) {
+ stream.skip();
+ break;
+ }
+ }
+ length = stream.pos - startPos;
+ if (ch === -1) {
+ warn('Inline ASCII85Decode image stream: ' +
+ 'EOD marker not found, searching for /EI/ instead.');
+ stream.skip(-length); // Reset the stream position.
+ return this.findDefaultInlineStreamEnd(stream);
+ }
+ this.inlineStreamSkipEI(stream);
+ return length;
+ },
+ /**
+ * Find the EOD (end-of-data) marker '>' (i.e. GT) of the stream.
+ * @returns {number} The inline stream length.
+ */
+ findASCIIHexDecodeInlineStreamEnd:
+ function Parser_findASCIIHexDecodeInlineStreamEnd(stream) {
+ var GT = 0x3E;
+ var startPos = stream.pos, ch, length;
+ while ((ch = stream.getByte()) !== -1) {
+ if (ch === GT) {
+ break;
+ }
+ }
+ length = stream.pos - startPos;
+ if (ch === -1) {
+ warn('Inline ASCIIHexDecode image stream: ' +
+ 'EOD marker not found, searching for /EI/ instead.');
+ stream.skip(-length); // Reset the stream position.
+ return this.findDefaultInlineStreamEnd(stream);
+ }
+ this.inlineStreamSkipEI(stream);
+ return length;
+ },
+ /**
+ * Skip over the /EI/ for streams where we search for an EOD marker.
+ */
+ inlineStreamSkipEI: function Parser_inlineStreamSkipEI(stream) {
+ var E = 0x45, I = 0x49;
+ var state = 0, ch;
+ while ((ch = stream.getByte()) !== -1) {
+ if (state === 0) {
+ state = (ch === E) ? 1 : 0;
+ } else if (state === 1) {
+ state = (ch === I) ? 2 : 0;
+ } else if (state === 2) {
+ break;
+ }
+ }
+ },
+ makeInlineImage: function Parser_makeInlineImage(cipherTransform) {
+ var lexer = this.lexer;
+ var stream = lexer.stream;
+
+ // Parse dictionary.
+ var dict = new Dict(this.xref);
+ 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));
+ }
+
+ // Extract the name of the first (i.e. the current) image filter.
+ var filter = dict.get('Filter', 'F'), filterName;
+ if (isName(filter)) {
+ filterName = filter.name;
+ } else if (isArray(filter) && isName(filter[0])) {
+ filterName = filter[0].name;
+ }
+
+ // Parse image stream.
+ var startPos = stream.pos, length, i, ii;
+ if (filterName === 'DCTDecode' || filterName === 'DCT') {
+ length = this.findDCTDecodeInlineStreamEnd(stream);
+ } else if (filterName === 'ASCII85Decide' || filterName === 'A85') {
+ length = this.findASCII85DecodeInlineStreamEnd(stream);
+ } else if (filterName === 'ASCIIHexDecode' || filterName === 'AHx') {
+ length = this.findASCIIHexDecodeInlineStreamEnd(stream);
+ } else {
+ length = this.findDefaultInlineStreamEnd(stream);
+ }
var imageStream = stream.makeSubStream(startPos, length, dict);
- // trying to cache repeat images, first we are trying to "warm up" caching
- // using length, then comparing adler32
- var MAX_LENGTH_TO_CACHE = 1000;
- var cacheImage = false, adler32;
- if (length < MAX_LENGTH_TO_CACHE && this.imageCache.length === length) {
+ // Cache all images below the MAX_LENGTH_TO_CACHE threshold by their
+ // adler32 checksum.
+ var adler32;
+ if (length < MAX_LENGTH_TO_CACHE) {
var imageBytes = imageStream.getBytes();
imageStream.reset();
var a = 1;
var b = 0;
for (i = 0, ii = imageBytes.length; i < ii; ++i) {
- a = (a + (imageBytes[i] & 0xff)) % 65521;
- b = (b + a) % 65521;
+ // No modulo required in the loop if imageBytes.length < 5552.
+ a += imageBytes[i] & 0xff;
+ b += a;
}
- adler32 = (b << 16) | a;
+ adler32 = ((b % 65521) << 16) | (a % 65521);
- if (this.imageCache.stream && this.imageCache.adler32 === adler32) {
+ if (this.imageCache.adler32 === adler32) {
this.buf2 = Cmd.get('EI');
this.shift();
- this.imageCache.stream.reset();
- return this.imageCache.stream;
+ this.imageCache[adler32].reset();
+ return this.imageCache[adler32];
}
- cacheImage = true;
}
- if (!cacheImage && !this.imageCache.stream) {
- this.imageCache.length = length;
- this.imageCache.stream = null;
- }
if (cipherTransform) {
imageStream = cipherTransform.createStream(imageStream, length);
}
imageStream = this.filter(imageStream, dict, length);
imageStream.dict = dict;
- if (cacheImage) {
+ if (adler32 !== undefined) {
imageStream.cacheKey = 'inline_' + length + '_' + adler32;
- this.imageCache.adler32 = adler32;
- this.imageCache.stream = imageStream;
+ this.imageCache[adler32] = imageStream;
}
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'));
+ var length = dict.get('Length');
if (!isInt(length)) {
info('Bad ' + length + ' attribute in stream');
length = 0;
}
// skip over the stream data
stream.pos = pos + length;
lexer.nextChar();
- this.shift(); // '>>'
- this.shift(); // 'stream'
- if (!isCmd(this.buf1, 'endstream')) {
+ // Shift '>>' and check whether the new object marks the end of the stream
+ if (this.tryShift() && isCmd(this.buf2, 'endstream')) {
+ this.shift(); // 'stream'
+ } else {
// 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,
@@ -36360,12 +39037,12 @@
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'));
+ var filter = dict.get('Filter', 'F');
+ var params = dict.get('DecodeParms', 'DP');
if (isName(filter)) {
return this.makeFilter(stream, filter.name, length, params);
}
var maybeLength = length;
@@ -36388,16 +39065,17 @@
}
}
return stream;
},
makeFilter: function Parser_makeFilter(stream, name, maybeLength, params) {
- if (stream.dict.get('Length') === 0) {
+ if (stream.dict.get('Length') === 0 && !maybeLength) {
+ warn('Empty "' + name + '" stream.');
return new NullStream(stream);
}
try {
- if (params) {
- params = this.fetchIfRef(params);
+ if (params && this.xref) {
+ params = this.xref.fetchIfRef(params);
}
var xrefStreamStats = this.xref.stats.streamTypes;
if (name === 'FlateDecode' || name === 'Fl') {
xrefStreamStats[StreamType.FLATE] = true;
if (params) {
@@ -36418,26 +39096,10 @@
maybeLength, params);
}
return new LZWStream(stream, maybeLength, earlyChange);
}
if (name === 'DCTDecode' || name === 'DCT') {
- // According to the specification: for inline images, the ID operator
- // shall be followed by a single whitespace character (unless it uses
- // ASCII85Decode or ASCIIHexDecode filters).
- // In practice this only seems to be followed for inline JPEG images,
- // and generally ignoring the first byte of the stream if it is a
- // whitespace char can even *cause* issues (e.g. in the CCITTFaxDecode
- // filters used in issue2984.pdf).
- // Hence when the first byte of the stream of an inline JPEG image is
- // a whitespace character, we thus simply skip over it.
- if (isCmd(this.buf1, 'ID')) {
- var firstByte = stream.peekByte();
- if (firstByte === 0x0A /* LF */ || firstByte === 0x0D /* CR */ ||
- firstByte === 0x20 /* SPACE */) {
- stream.skip();
- }
- }
xrefStreamStats[StreamType.DCT] = true;
return new JpegStream(stream, maybeLength, stream.dict, this.xref);
}
if (name === 'JPXDecode' || name === 'JPX') {
xrefStreamStats[StreamType.JPX] = true;
@@ -36551,10 +39213,15 @@
var sign = 1;
if (ch === 0x2D) { // '-'
sign = -1;
ch = this.nextChar();
+
+ if (ch === 0x2D) { // '-'
+ // Ignore double negative (this is consistent with Adobe Reader).
+ ch = this.nextChar();
+ }
} else if (ch === 0x2B) { // '+'
ch = this.nextChar();
}
if (ch === 0x2E) { // '.'
divideBy = 10;
@@ -36709,33 +39376,47 @@
}
}
return strBuf.join('');
},
getName: function Lexer_getName() {
- var ch;
+ var ch, previousCh;
var strBuf = this.strBuf;
strBuf.length = 0;
while ((ch = this.nextChar()) >= 0 && !specialChars[ch]) {
if (ch === 0x23) { // '#'
ch = this.nextChar();
+ if (specialChars[ch]) {
+ warn('Lexer_getName: ' +
+ 'NUMBER SIGN (#) should be followed by a hexadecimal number.');
+ strBuf.push('#');
+ break;
+ }
var x = toHexDigit(ch);
if (x !== -1) {
- var x2 = toHexDigit(this.nextChar());
+ previousCh = ch;
+ ch = this.nextChar();
+ var x2 = toHexDigit(ch);
if (x2 === -1) {
- error('Illegal digit in hex char in name: ' + x2);
+ warn('Lexer_getName: Illegal digit (' +
+ String.fromCharCode(ch) +') in hexadecimal number.');
+ strBuf.push('#', String.fromCharCode(previousCh));
+ if (specialChars[ch]) {
+ break;
+ }
+ strBuf.push(String.fromCharCode(ch));
+ continue;
}
strBuf.push(String.fromCharCode((x << 4) | x2));
} else {
strBuf.push('#', String.fromCharCode(ch));
}
} else {
strBuf.push(String.fromCharCode(ch));
}
}
- if (strBuf.length > 128) {
- error('Warning: name token is longer than allowed by the spec: ' +
- strBuf.length);
+ if (strBuf.length > 127) {
+ warn('name token is longer than allowed by the spec: ' + strBuf.length);
}
return Name.get(strBuf.join(''));
},
getHexString: function Lexer_getHexString() {
var strBuf = this.strBuf;
@@ -37171,10 +39852,13 @@
return this.bytes[this.pos++];
},
getUint16: function Stream_getUint16() {
var b0 = this.getByte();
var b1 = this.getByte();
+ if (b0 === -1 || b1 === -1) {
+ return -1;
+ }
return (b0 << 8) + b1;
},
getInt32: function Stream_getInt32() {
var b0 = this.getByte();
var b1 = this.getByte();
@@ -37298,10 +39982,13 @@
return this.buffer[this.pos++];
},
getUint16: function DecodeStream_getUint16() {
var b0 = this.getByte();
var b1 = this.getByte();
+ if (b0 === -1 || b1 === -1) {
+ return -1;
+ }
return (b0 << 8) + b1;
},
getInt32: function DecodeStream_getInt32() {
var b0 = this.getByte();
var b1 = this.getByte();
@@ -37410,29 +40097,29 @@
return StreamsSequenceStream;
})();
var FlateStream = (function FlateStreamClosure() {
- var codeLenCodeMap = new Uint32Array([
+ var codeLenCodeMap = new Int32Array([
16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15
]);
- var lengthDecode = new Uint32Array([
+ var lengthDecode = new Int32Array([
0x00003, 0x00004, 0x00005, 0x00006, 0x00007, 0x00008, 0x00009, 0x0000a,
0x1000b, 0x1000d, 0x1000f, 0x10011, 0x20013, 0x20017, 0x2001b, 0x2001f,
0x30023, 0x3002b, 0x30033, 0x3003b, 0x40043, 0x40053, 0x40063, 0x40073,
0x50083, 0x500a3, 0x500c3, 0x500e3, 0x00102, 0x00102, 0x00102
]);
- var distDecode = new Uint32Array([
+ var distDecode = new Int32Array([
0x00001, 0x00002, 0x00003, 0x00004, 0x10005, 0x10007, 0x20009, 0x2000d,
0x30011, 0x30019, 0x40021, 0x40031, 0x50041, 0x50061, 0x60081, 0x600c1,
0x70101, 0x70181, 0x80201, 0x80301, 0x90401, 0x90601, 0xa0801, 0xa0c01,
0xb1001, 0xb1801, 0xc2001, 0xc3001, 0xd4001, 0xd6001
]);
- var fixedLitCodeTab = [new Uint32Array([
+ var fixedLitCodeTab = [new Int32Array([
0x70100, 0x80050, 0x80010, 0x80118, 0x70110, 0x80070, 0x80030, 0x900c0,
0x70108, 0x80060, 0x80020, 0x900a0, 0x80000, 0x80080, 0x80040, 0x900e0,
0x70104, 0x80058, 0x80018, 0x90090, 0x70114, 0x80078, 0x80038, 0x900d0,
0x7010c, 0x80068, 0x80028, 0x900b0, 0x80008, 0x80088, 0x80048, 0x900f0,
0x70102, 0x80054, 0x80014, 0x8011c, 0x70112, 0x80074, 0x80034, 0x900c8,
@@ -37495,11 +40182,11 @@
0x7010b, 0x80067, 0x80027, 0x900af, 0x80007, 0x80087, 0x80047, 0x900ef,
0x70107, 0x8005f, 0x8001f, 0x9009f, 0x70117, 0x8007f, 0x8003f, 0x900df,
0x7010f, 0x8006f, 0x8002f, 0x900bf, 0x8000f, 0x8008f, 0x8004f, 0x900ff
]), 9];
- var fixedDistCodeTab = [new Uint32Array([
+ var fixedDistCodeTab = [new Int32Array([
0x50000, 0x50010, 0x50008, 0x50018, 0x50004, 0x50014, 0x5000c, 0x5001c,
0x50002, 0x50012, 0x5000a, 0x5001a, 0x50006, 0x50016, 0x5000e, 0x00000,
0x50001, 0x50011, 0x50009, 0x50019, 0x50005, 0x50015, 0x5000d, 0x5001d,
0x50003, 0x50013, 0x5000b, 0x5001b, 0x50007, 0x50017, 0x5000f, 0x00000
]), 5];
@@ -37592,11 +40279,11 @@
}
}
// build the table
var size = 1 << maxLen;
- var codes = new Uint32Array(size);
+ var codes = new Int32Array(size);
for (var len = 1, code = 0, skip = 2;
len <= maxLen;
++len, code <<= 1, skip <<= 1) {
for (var val = 0; val < n; ++val) {
if (lengths[val] === len) {
@@ -37981,12 +40668,19 @@
* a library to decode these images and the stream behaves like all the other
* DecodeStreams.
*/
var JpegStream = (function JpegStreamClosure() {
function JpegStream(stream, maybeLength, dict, xref) {
- // TODO: per poppler, some images may have 'junk' before that
- // need to be removed
+ // Some images may contain 'junk' before the SOI (start-of-image) marker.
+ // Note: this seems to mainly affect inline images.
+ var ch;
+ while ((ch = stream.getByte()) !== -1) {
+ if (ch === 0xFF) { // Find the first byte of the SOI marker (0xFFD8).
+ stream.skip(-1); // Reset the stream position to the SOI.
+ break;
+ }
+ }
this.stream = stream;
this.maybeLength = maybeLength;
this.dict = dict;
DecodeStream.call(this, maybeLength);
@@ -38053,20 +40747,21 @@
* further processing such as color space conversions.
*/
JpegStream.prototype.isNativelySupported =
function JpegStream_isNativelySupported(xref, res) {
var cs = ColorSpace.parse(this.dict.get('ColorSpace', 'CS'), xref, res);
- return cs.name === 'DeviceGray' || cs.name === 'DeviceRGB';
+ return (cs.name === 'DeviceGray' || cs.name === 'DeviceRGB') &&
+ cs.isDefaultDecode(this.dict.get('Decode', 'D'));
};
/**
* Checks if the image can be decoded by the browser.
*/
JpegStream.prototype.isNativelyDecodable =
function JpegStream_isNativelyDecodable(xref, res) {
var cs = ColorSpace.parse(this.dict.get('ColorSpace', 'CS'), xref, res);
- var numComps = cs.numComps;
- return numComps === 1 || numComps === 3;
+ return (cs.numComps === 1 || cs.numComps === 3) &&
+ cs.isDefaultDecode(this.dict.get('Decode', 'D'));
};
return JpegStream;
})();
@@ -39155,13 +41850,17 @@
}
}
var gotEOL = false;
+ if (this.byteAlign) {
+ this.inputBits &= ~7;
+ }
+
if (!this.eoblock && this.row === this.rows - 1) {
this.eof = true;
- } else if (this.eoline || !this.byteAlign) {
+ } else {
code1 = this.lookBits(12);
if (this.eoline) {
while (code1 !== EOF && code1 !== 1) {
this.eatBits(1);
code1 = this.lookBits(12);
@@ -39178,14 +41877,10 @@
} else if (code1 === EOF) {
this.eof = true;
}
}
- if (this.byteAlign && !gotEOL) {
- this.inputBits &= ~7;
- }
-
if (!this.eof && this.encoding > 0) {
this.nextLine2D = !this.lookBits(1);
this.eatBits(1);
}
@@ -39578,14 +42273,102 @@
return NullStream;
})();
+var WorkerTask = (function WorkerTaskClosure() {
+ function WorkerTask(name) {
+ this.name = name;
+ this.terminated = false;
+ this._capability = createPromiseCapability();
+ }
+
+ WorkerTask.prototype = {
+ get finished() {
+ return this._capability.promise;
+ },
+
+ finish: function () {
+ this._capability.resolve();
+ },
+
+ terminate: function () {
+ this.terminated = true;
+ },
+
+ ensureNotTerminated: function () {
+ if (this.terminated) {
+ throw new Error('Worker task was terminated');
+ }
+ }
+ };
+
+ return WorkerTask;
+})();
+
var WorkerMessageHandler = PDFJS.WorkerMessageHandler = {
- setup: function wphSetup(handler) {
+ setup: function wphSetup(handler, port) {
+ handler.on('test', function wphSetupTest(data) {
+ // check if Uint8Array can be sent to worker
+ if (!(data instanceof Uint8Array)) {
+ handler.send('test', 'main', false);
+ return;
+ }
+ // making sure postMessage transfers are working
+ var supportTransfers = data[0] === 255;
+ handler.postMessageTransfers = supportTransfers;
+ // 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', {
+ supportTypedArray: true,
+ supportTransfers: supportTransfers
+ });
+ });
+
+ handler.on('GetDocRequest', function wphSetupDoc(data) {
+ return WorkerMessageHandler.createDocumentHandler(data, port);
+ });
+ },
+ createDocumentHandler: function wphCreateDocumentHandler(docParams, port) {
+ // This context is actually holds references on pdfManager and handler,
+ // until the latter is destroyed.
var pdfManager;
+ var terminated = false;
+ var cancelXHRs = null;
+ var WorkerTasks = [];
+ var docId = docParams.docId;
+ var workerHandlerName = docParams.docId + '_worker';
+ var handler = new MessageHandler(workerHandlerName, docId, port);
+
+ function ensureNotTerminated() {
+ if (terminated) {
+ throw new Error('Worker was terminated');
+ }
+ }
+
+ function startWorkerTask(task) {
+ WorkerTasks.push(task);
+ }
+
+ function finishWorkerTask(task) {
+ task.finish();
+ var i = WorkerTasks.indexOf(task);
+ WorkerTasks.splice(i, 1);
+ }
+
function loadDocument(recoveryMode) {
var loadDocumentCapability = createPromiseCapability();
var parseSuccess = function parseSuccess() {
var numPagesPromise = pdfManager.ensureDoc('numPages');
@@ -39617,25 +42400,26 @@
return loadDocumentCapability.promise;
}
function getPdfManager(data) {
var pdfManagerCapability = createPromiseCapability();
+ var pdfManager;
var source = data.source;
var disableRange = data.disableRange;
if (source.data) {
try {
- pdfManager = new LocalPdfManager(source.data, source.password);
- pdfManagerCapability.resolve();
+ pdfManager = new LocalPdfManager(docId, source.data, source.password);
+ pdfManagerCapability.resolve(pdfManager);
} catch (ex) {
pdfManagerCapability.reject(ex);
}
return pdfManagerCapability.promise;
} else if (source.chunkedViewerLoading) {
try {
- pdfManager = new NetworkPdfManager(source, handler);
- pdfManagerCapability.resolve();
+ pdfManager = new NetworkPdfManager(docId, source, handler);
+ pdfManagerCapability.resolve(pdfManager);
} catch (ex) {
pdfManagerCapability.reject(ex);
}
return pdfManagerCapability.promise;
}
@@ -39666,11 +42450,11 @@
length = parseInt(length, 10);
if (!isInt(length)) {
return;
}
source.length = length;
- if (length <= 2 * RANGE_CHUNK_SIZE) {
+ if (length <= 2 * source.rangeChunkSize) {
// The file size is smaller than the size of two chunks, so it does
// not make any sense to abort the request and retry with a range
// request.
return;
}
@@ -39687,15 +42471,16 @@
// requests.
networkManager.abortRequest(fullRequestXhrId);
}
try {
- pdfManager = new NetworkPdfManager(source, handler);
+ pdfManager = new NetworkPdfManager(docId, source, handler);
pdfManagerCapability.resolve(pdfManager);
} catch (ex) {
pdfManagerCapability.reject(ex);
}
+ cancelXHRs = null;
},
onProgressiveData: source.disableStream ? null :
function onProgressiveData(chunk) {
if (!pdfManager) {
@@ -39731,73 +42516,51 @@
pdfFile = args.chunk;
}
// the data is array, instantiating directly from it
try {
- pdfManager = new LocalPdfManager(pdfFile, source.password);
- pdfManagerCapability.resolve();
+ pdfManager = new LocalPdfManager(docId, pdfFile, source.password);
+ pdfManagerCapability.resolve(pdfManager);
} catch (ex) {
pdfManagerCapability.reject(ex);
}
+ cancelXHRs = null;
},
onError: function onError(status) {
var exception;
- if (status === 404) {
+ if (status === 404 || status === 0 && /^file:/.test(source.url)) {
exception = new MissingPDFException('Missing PDF "' +
source.url + '".');
handler.send('MissingPDF', exception);
} else {
exception = new UnexpectedResponseException(
'Unexpected server response (' + status +
') while retrieving PDF "' + source.url + '".', status);
handler.send('UnexpectedResponse', exception);
}
+ cancelXHRs = null;
},
onProgress: function onProgress(evt) {
handler.send('DocProgress', {
loaded: evt.loaded,
total: evt.lengthComputable ? evt.total : source.length
});
}
});
+ cancelXHRs = function () {
+ networkManager.abortRequest(fullRequestXhrId);
+ };
+
return pdfManagerCapability.promise;
}
- handler.on('test', function wphSetupTest(data) {
- // check if Uint8Array can be sent to worker
- if (!(data instanceof Uint8Array)) {
- handler.send('test', false);
- return;
- }
- // making sure postMessage transfers are working
- var supportTransfers = data[0] === 255;
- handler.postMessageTransfers = supportTransfers;
- // 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', {
- supportTypedArray: true,
- supportTransfers: supportTransfers
- });
- });
-
- handler.on('GetDocRequest', function wphSetupDoc(data) {
-
+ var setupDoc = function(data) {
var onSuccess = function(doc) {
+ ensureNotTerminated();
handler.send('GetDoc', { pdfInfo: doc });
};
var onFailure = function(e) {
if (e instanceof PasswordException) {
@@ -39816,26 +42579,40 @@
handler.send('UnknownError',
new UnknownErrorException(e.message, e.toString()));
}
};
+ ensureNotTerminated();
+
PDFJS.maxImageSize = data.maxImageSize === undefined ?
-1 : data.maxImageSize;
PDFJS.disableFontFace = data.disableFontFace;
PDFJS.disableCreateObjectURL = data.disableCreateObjectURL;
PDFJS.verbosity = data.verbosity;
PDFJS.cMapUrl = data.cMapUrl === undefined ?
null : data.cMapUrl;
PDFJS.cMapPacked = data.cMapPacked === true;
- getPdfManager(data).then(function () {
+ getPdfManager(data).then(function (newPdfManager) {
+ if (terminated) {
+ // We were in a process of setting up the manager, but it got
+ // terminated in the middle.
+ newPdfManager.terminate();
+ throw new Error('Worker was terminated');
+ }
+
+ pdfManager = newPdfManager;
handler.send('PDFManagerReady', null);
pdfManager.onLoadedStream().then(function(stream) {
handler.send('DataLoaded', { length: stream.bytes.byteLength });
});
}).then(function pdfManagerReady() {
+ ensureNotTerminated();
+
loadDocument(false).then(onSuccess, function loadFailure(ex) {
+ ensureNotTerminated();
+
// 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
@@ -39846,15 +42623,17 @@
return;
}
pdfManager.requestLoadedStream();
pdfManager.onLoadedStream().then(function() {
+ ensureNotTerminated();
+
loadDocument(true).then(onSuccess, onFailure);
});
}, onFailure);
}, onFailure);
- });
+ };
handler.on('GetPage', function wphSetupGetPage(data) {
return pdfManager.getPage(data.pageIndex).then(function(page) {
var rotatePromise = pdfManager.ensure(page, 'rotate');
var refPromise = pdfManager.ensure(page, 'ref');
@@ -39883,11 +42662,11 @@
}
);
handler.on('GetDestination',
function wphSetupGetDestination(data) {
- return pdfManager.ensureCatalog('getDestination', [ data.id ]);
+ return pdfManager.ensureCatalog('getDestination', [data.id]);
}
);
handler.on('GetAttachments',
function wphSetupGetAttachments(data) {
@@ -39931,27 +42710,40 @@
pdfManager.updatePassword(data);
});
handler.on('GetAnnotations', function wphSetupGetAnnotations(data) {
return pdfManager.getPage(data.pageIndex).then(function(page) {
- return pdfManager.ensure(page, 'getAnnotationsData', []);
+ return pdfManager.ensure(page, 'getAnnotationsData', [data.intent]);
});
});
handler.on('RenderPageRequest', function wphSetupRenderPage(data) {
- pdfManager.getPage(data.pageIndex).then(function(page) {
+ var pageIndex = data.pageIndex;
+ pdfManager.getPage(pageIndex).then(function(page) {
+ var task = new WorkerTask('RenderPageRequest: page ' + pageIndex);
+ startWorkerTask(task);
- var pageNum = data.pageIndex + 1;
+ var pageNum = pageIndex + 1;
var start = Date.now();
// Pre compile the pdf page and fetch the fonts/images.
- page.getOperatorList(handler, data.intent).then(function(operatorList) {
+ page.getOperatorList(handler, task, data.intent).then(
+ function(operatorList) {
+ finishWorkerTask(task);
info('page=' + pageNum + ' - getOperatorList: time=' +
- (Date.now() - start) + 'ms, len=' + operatorList.fnArray.length);
-
+ (Date.now() - start) + 'ms, len=' + operatorList.totalLength);
}, function(e) {
+ finishWorkerTask(task);
+ if (task.terminated) {
+ return; // ignoring errors from the terminated thread
+ }
+ // For compatibility with older behavior, generating unknown
+ // unsupported feature notification on errors.
+ handler.send('UnsupportedFeature',
+ {featureId: UNSUPPORTED_FEATURES.unknown});
+
var minimumStackMessage =
'worker.js: while trying to getPage() and getOperatorList()';
var wrappedException;
@@ -39981,45 +42773,85 @@
});
});
}, this);
handler.on('GetTextContent', function wphExtractText(data) {
- return pdfManager.getPage(data.pageIndex).then(function(page) {
- var pageNum = data.pageIndex + 1;
+ var pageIndex = data.pageIndex;
+ var normalizeWhitespace = data.normalizeWhitespace;
+ return pdfManager.getPage(pageIndex).then(function(page) {
+ var task = new WorkerTask('GetTextContent: page ' + pageIndex);
+ startWorkerTask(task);
+ var pageNum = pageIndex + 1;
var start = Date.now();
- return page.extractTextContent().then(function(textContent) {
+ return page.extractTextContent(task, normalizeWhitespace).then(
+ function(textContent) {
+ finishWorkerTask(task);
info('text indexing: page=' + pageNum + ' - time=' +
(Date.now() - start) + 'ms');
return textContent;
+ }, function (reason) {
+ finishWorkerTask(task);
+ if (task.terminated) {
+ return; // ignoring errors from the terminated thread
+ }
+ throw reason;
});
});
});
handler.on('Cleanup', function wphCleanup(data) {
return pdfManager.cleanup();
});
handler.on('Terminate', function wphTerminate(data) {
- pdfManager.terminate();
+ terminated = true;
+ if (pdfManager) {
+ pdfManager.terminate();
+ pdfManager = null;
+ }
+ if (cancelXHRs) {
+ cancelXHRs();
+ }
+
+ var waitOn = [];
+ WorkerTasks.forEach(function (task) {
+ waitOn.push(task.finished);
+ task.terminate();
+ });
+
+ return Promise.all(waitOn).then(function () {
+ // Notice that even if we destroying handler, resolved response promise
+ // must be sent back.
+ handler.destroy();
+ handler = null;
+ });
});
+
+ handler.on('Ready', function wphReady(data) {
+ setupDoc(docParams);
+ docParams = null; // we don't need docParams anymore -- saving memory.
+ });
+ return workerHandlerName;
}
};
var consoleTimer = {};
var workerConsole = {
log: function log() {
var args = Array.prototype.slice.call(arguments);
globalScope.postMessage({
+ targetName: 'main',
action: 'console_log',
data: args
});
},
error: function error() {
var args = Array.prototype.slice.call(arguments);
globalScope.postMessage({
+ targetName: 'main',
action: 'console_error',
data: args
});
throw 'pdf.js execution error';
},
@@ -40042,28 +42874,20 @@
if (typeof window === 'undefined') {
if (!('console' in globalScope)) {
globalScope.console = workerConsole;
}
- // Listen for unsupported features so we can pass them on to the main thread.
- PDFJS.UnsupportedManager.listen(function (msg) {
- globalScope.postMessage({
- action: '_unsupported_feature',
- data: msg
- });
- });
-
- var handler = new MessageHandler('worker_processor', this);
- WorkerMessageHandler.setup(handler);
+ var handler = new MessageHandler('worker', 'main', this);
+ WorkerMessageHandler.setup(handler, this);
}
/* This class implements the QM Coder decoding as defined in
* JPEG 2000 Part I Final Committee Draft Version 1.0
- * Annex C.3 Arithmetic decoding procedure
+ * Annex C.3 Arithmetic decoding procedure
* available at http://www.jpeg.org/public/fcd15444-1.pdf
- *
+ *
* The arithmetic decoder is used in conjunction with context models to decode
* JPEG2000 and JBIG2 streams.
*/
var ArithmeticDecoder = (function ArithmeticDecoderClosure() {
// Table C-2
@@ -40223,10 +43047,11 @@
return ArithmeticDecoder;
})();
+
var JpegImage = (function jpegImage() {
var dctZigZag = new Uint8Array([
0,
1, 8,
16, 9, 2,
@@ -40322,31 +43147,25 @@
return bitsData >>> 7;
}
function decodeHuffman(tree) {
var node = tree;
- var bit;
- while ((bit = readBit()) !== null) {
- node = node[bit];
+ while (true) {
+ node = node[readBit()];
if (typeof node === 'number') {
return node;
}
if (typeof node !== 'object') {
throw 'invalid huffman sequence';
}
}
- return null;
}
function receive(length) {
var n = 0;
while (length > 0) {
- var bit = readBit();
- if (bit === null) {
- return;
- }
- n = (n << 1) | bit;
+ n = (n << 1) | readBit();
length--;
}
return n;
}
@@ -40701,11 +43520,11 @@
v5 = p3;
v6 = p5;
// stage 3
// Shift v0 by 128.5 << 5 here, so we don't need to shift p0...p7 when
- // converting to UInt8 range later.
+ // converting to UInt8 range later.
v0 = ((v0 + v1 + 1) >> 1) + 4112;
v1 = v0 - v1;
t = (v2 * dctSin6 + v3 * dctCos6 + 2048) >> 12;
v2 = (v2 * dctCos6 - v3 * dctSin6 + 2048) >> 12;
v3 = t;
@@ -40867,13 +43686,13 @@
}
// TODO APP1 - Exif
if (fileMarker === 0xFFEE) {
if (appData[0] === 0x41 && appData[1] === 0x64 &&
appData[2] === 0x6F && appData[3] === 0x62 &&
- appData[4] === 0x65 && appData[5] === 0) { // 'Adobe\x00'
+ appData[4] === 0x65) { // 'Adobe'
adobe = {
- version: appData[6],
+ version: (appData[5] << 8) | appData[6],
flags0: (appData[7] << 8) | appData[8],
flags1: (appData[9] << 8) | appData[10],
transformCode: appData[11]
};
}
@@ -40990,10 +43809,17 @@
frame, components, resetInterval,
spectralStart, spectralEnd,
successiveApproximation >> 4, successiveApproximation & 15);
offset += processed;
break;
+
+ case 0xFFFF: // Fill bytes
+ if (data[offset] !== 0xFF) { // Avoid skipping a valid marker.
+ offset--;
+ }
+ break;
+
default:
if (data[offset - 3] === 0xFF &&
data[offset - 2] >= 0xC0 && data[offset - 2] <= 0xFE) {
// could be incorrect encoding -- last 0xFF byte of the previous
// block was eaten by the encoder
@@ -41058,12 +43884,11 @@
offset += numComponents;
}
}
}
- // decodeTransform will contains pairs of multiplier (-256..256) and
- // additive
+ // decodeTransform contains pairs of multiplier (-256..256) and additive
var transform = this.decodeTransform;
if (transform) {
for (i = 0; i < dataLength;) {
for (j = 0, k = 0; j < numComponents; j++, i++, k += 2) {
data[i] = ((data[i] * transform[k]) >> 8) + transform[k + 1];
@@ -41096,55 +43921,47 @@
}
return data;
},
_convertYcckToRgb: function convertYcckToRgb(data) {
- var Y, Cb, Cr, k, CbCb, CbCr, CbY, Cbk, CrCr, Crk, CrY, YY, Yk, kk;
+ var Y, Cb, Cr, k;
var offset = 0;
for (var i = 0, length = data.length; i < length; i += 4) {
Y = data[i];
Cb = data[i + 1];
Cr = data[i + 2];
k = data[i + 3];
- CbCb = Cb * Cb;
- CbCr = Cb * Cr;
- CbY = Cb * Y;
- Cbk = Cb * k;
- CrCr = Cr * Cr;
- Crk = Cr * k;
- CrY = Cr * Y;
- YY = Y * Y;
- Yk = Y * k;
- kk = k * k;
+ var r = -122.67195406894 +
+ Cb * (-6.60635669420364e-5 * Cb + 0.000437130475926232 * Cr -
+ 5.4080610064599e-5 * Y + 0.00048449797120281 * k -
+ 0.154362151871126) +
+ Cr * (-0.000957964378445773 * Cr + 0.000817076911346625 * Y -
+ 0.00477271405408747 * k + 1.53380253221734) +
+ Y * (0.000961250184130688 * Y - 0.00266257332283933 * k +
+ 0.48357088451265) +
+ k * (-0.000336197177618394 * k + 0.484791561490776);
- var r = - 122.67195406894 -
- 6.60635669420364e-5 * CbCb + 0.000437130475926232 * CbCr -
- 5.4080610064599e-5* CbY + 0.00048449797120281* Cbk -
- 0.154362151871126 * Cb - 0.000957964378445773 * CrCr +
- 0.000817076911346625 * CrY - 0.00477271405408747 * Crk +
- 1.53380253221734 * Cr + 0.000961250184130688 * YY -
- 0.00266257332283933 * Yk + 0.48357088451265 * Y -
- 0.000336197177618394 * kk + 0.484791561490776 * k;
-
var g = 107.268039397724 +
- 2.19927104525741e-5 * CbCb - 0.000640992018297945 * CbCr +
- 0.000659397001245577* CbY + 0.000426105652938837* Cbk -
- 0.176491792462875 * Cb - 0.000778269941513683 * CrCr +
- 0.00130872261408275 * CrY + 0.000770482631801132 * Crk -
- 0.151051492775562 * Cr + 0.00126935368114843 * YY -
- 0.00265090189010898 * Yk + 0.25802910206845 * Y -
- 0.000318913117588328 * kk - 0.213742400323665 * k;
+ Cb * (2.19927104525741e-5 * Cb - 0.000640992018297945 * Cr +
+ 0.000659397001245577 * Y + 0.000426105652938837 * k -
+ 0.176491792462875) +
+ Cr * (-0.000778269941513683 * Cr + 0.00130872261408275 * Y +
+ 0.000770482631801132 * k - 0.151051492775562) +
+ Y * (0.00126935368114843 * Y - 0.00265090189010898 * k +
+ 0.25802910206845) +
+ k * (-0.000318913117588328 * k - 0.213742400323665);
- var b = - 20.810012546947 -
- 0.000570115196973677 * CbCb - 2.63409051004589e-5 * CbCr +
- 0.0020741088115012* CbY - 0.00288260236853442* Cbk +
- 0.814272968359295 * Cb - 1.53496057440975e-5 * CrCr -
- 0.000132689043961446 * CrY + 0.000560833691242812 * Crk -
- 0.195152027534049 * Cr + 0.00174418132927582 * YY -
- 0.00255243321439347 * Yk + 0.116935020465145 * Y -
- 0.000343531996510555 * kk + 0.24165260232407 * k;
+ var b = -20.810012546947 +
+ Cb * (-0.000570115196973677 * Cb - 2.63409051004589e-5 * Cr +
+ 0.0020741088115012 * Y - 0.00288260236853442 * k +
+ 0.814272968359295) +
+ Cr * (-1.53496057440975e-5 * Cr - 0.000132689043961446 * Y +
+ 0.000560833691242812 * k - 0.195152027534049) +
+ Y * (0.00174418132927582 * Y - 0.00255243321439347 * k +
+ 0.116935020465145) +
+ k * (-0.000343531996510555 * k + 0.24165260232407);
data[offset++] = clamp0to255(r);
data[offset++] = clamp0to255(g);
data[offset++] = clamp0to255(b);
}
@@ -41281,22 +44098,56 @@
throw new Error('JPX Error: Invalid box field size');
}
var dataLength = lbox - headerSize;
var jumpDataLength = true;
switch (tbox) {
- case 0x6A501A1A: // 'jP\032\032'
- // TODO
- break;
case 0x6A703268: // 'jp2h'
jumpDataLength = false; // parsing child boxes
break;
case 0x636F6C72: // 'colr'
- // TODO
+ // Colorspaces are not used, the CS from the PDF is used.
+ var method = data[position];
+ var precedence = data[position + 1];
+ var approximation = data[position + 2];
+ if (method === 1) {
+ // enumerated colorspace
+ var colorspace = readUint32(data, position + 3);
+ switch (colorspace) {
+ case 16: // this indicates a sRGB colorspace
+ case 17: // this indicates a grayscale colorspace
+ case 18: // this indicates a YUV colorspace
+ break;
+ default:
+ warn('Unknown colorspace ' + colorspace);
+ break;
+ }
+ } else if (method === 2) {
+ info('ICC profile not supported');
+ }
break;
case 0x6A703263: // 'jp2c'
this.parseCodestream(data, position, position + dataLength);
break;
+ case 0x6A502020: // 'jP\024\024'
+ if (0x0d0a870a !== readUint32(data, position)) {
+ warn('Invalid JP2 signature');
+ }
+ break;
+ // The following header types are valid but currently not used:
+ case 0x6A501A1A: // 'jP\032\032'
+ case 0x66747970: // 'ftyp'
+ case 0x72726571: // 'rreq'
+ case 0x72657320: // 'res '
+ case 0x69686472: // 'ihdr'
+ break;
+ default:
+ var headerType = String.fromCharCode((tbox >> 24) & 0xFF,
+ (tbox >> 16) & 0xFF,
+ (tbox >> 8) & 0xFF,
+ tbox & 0xFF);
+ warn('Unsupported header type ' + tbox + ' (' + headerType + ')');
+ break;
}
if (jumpDataLength) {
position += dataLength;
}
}
@@ -41371,15 +44222,10 @@
context.components = components;
calculateTileGrids(context, components);
context.QCC = [];
context.COC = [];
break;
- case 0xFF55: // Tile-part lengths, main header (TLM)
- var Ltlm = readUint16(data, position); // Marker segment length
- // Skip tile length markers
- position += Ltlm;
- break;
case 0xFF5C: // Quantization default (QCD)
length = readUint16(data, position);
var qcd = {};
j = position + 2;
sqcd = data[j++];
@@ -41508,16 +44354,10 @@
});
}
cod.precinctsSizes = precinctsSizes;
}
var unsupported = [];
- if (cod.sopMarkerUsed) {
- unsupported.push('sopMarkerUsed');
- }
- if (cod.ephMarkerUsed) {
- unsupported.push('ephMarkerUsed');
- }
if (cod.selectiveArithmeticCodingBypass) {
unsupported.push('selectiveArithmeticCodingBypass');
}
if (cod.resetContextProbabilities) {
unsupported.push('resetContextProbabilities');
@@ -41571,10 +44411,13 @@
// moving to the end of the data
length = tile.dataEnd - position;
parseTilePackets(context, data, position, length);
break;
+ case 0xFF55: // Tile-part lengths, main header (TLM)
+ case 0xFF57: // Packet length, main header (PLM)
+ case 0xFF58: // Packet length, tile-part header (PLT)
case 0xFF64: // Comment (COM)
length = readUint16(data, position);
// skipping content
break;
case 0xFF53: // Coding style component (COC)
@@ -41664,29 +44507,43 @@
}
function buildPrecincts(context, resolution, dimensions) {
// Section B.6 Division resolution to precincts
var precinctWidth = 1 << dimensions.PPx;
var precinctHeight = 1 << dimensions.PPy;
+ // Jasper introduces codeblock groups for mapping each subband codeblocks
+ // to precincts. Precinct partition divides a resolution according to width
+ // and height parameters. The subband that belongs to the resolution level
+ // has a different size than the level, unless it is the zero resolution.
+
+ // From Jasper documentation: jpeg2000.pdf, section K: Tier-2 coding:
+ // The precinct partitioning for a particular subband is derived from a
+ // partitioning of its parent LL band (i.e., the LL band at the next higher
+ // resolution level)... The LL band associated with each resolution level is
+ // divided into precincts... Each of the resulting precinct regions is then
+ // mapped into its child subbands (if any) at the next lower resolution
+ // level. This is accomplished by using the coordinate transformation
+ // (u, v) = (ceil(x/2), ceil(y/2)) where (x, y) and (u, v) are the
+ // coordinates of a point in the LL band and child subband, respectively.
+ var isZeroRes = resolution.resLevel === 0;
+ var precinctWidthInSubband = 1 << (dimensions.PPx + (isZeroRes ? 0 : -1));
+ var precinctHeightInSubband = 1 << (dimensions.PPy + (isZeroRes ? 0 : -1));
var numprecinctswide = (resolution.trx1 > resolution.trx0 ?
Math.ceil(resolution.trx1 / precinctWidth) -
Math.floor(resolution.trx0 / precinctWidth) : 0);
var numprecinctshigh = (resolution.try1 > resolution.try0 ?
Math.ceil(resolution.try1 / precinctHeight) -
Math.floor(resolution.try0 / precinctHeight) : 0);
var numprecincts = numprecinctswide * numprecinctshigh;
- var precinctXOffset = Math.floor(resolution.trx0 / precinctWidth) *
- precinctWidth;
- var precinctYOffset = Math.floor(resolution.try0 / precinctHeight) *
- precinctHeight;
+
resolution.precinctParameters = {
- precinctXOffset: precinctXOffset,
- precinctYOffset: precinctYOffset,
precinctWidth: precinctWidth,
precinctHeight: precinctHeight,
numprecinctswide: numprecinctswide,
numprecinctshigh: numprecinctshigh,
- numprecincts: numprecincts
+ numprecincts: numprecincts,
+ precinctWidthInSubband: precinctWidthInSubband,
+ precinctHeightInSubband: precinctHeightInSubband
};
}
function buildCodeblocks(context, subband, dimensions) {
// Section B.7 Division sub-band into code-blocks
var xcb_ = dimensions.xcb_;
@@ -41709,25 +44566,33 @@
tbx0: codeblockWidth * i,
tby0: codeblockHeight * j,
tbx1: codeblockWidth * (i + 1),
tby1: codeblockHeight * (j + 1)
};
- // calculate precinct number
- var pi = Math.floor((codeblock.tbx0 -
- precinctParameters.precinctXOffset) /
- precinctParameters.precinctWidth);
- var pj = Math.floor((codeblock.tby0 -
- precinctParameters.precinctYOffset) /
- precinctParameters.precinctHeight);
- precinctNumber = pj + pi * precinctParameters.numprecinctswide;
+
codeblock.tbx0_ = Math.max(subband.tbx0, codeblock.tbx0);
codeblock.tby0_ = Math.max(subband.tby0, codeblock.tby0);
codeblock.tbx1_ = Math.min(subband.tbx1, codeblock.tbx1);
codeblock.tby1_ = Math.min(subband.tby1, codeblock.tby1);
+
+ // Calculate precinct number for this codeblock, codeblock position
+ // should be relative to its subband, use actual dimension and position
+ // See comment about codeblock group width and height
+ var pi = Math.floor((codeblock.tbx0_ - subband.tbx0) /
+ precinctParameters.precinctWidthInSubband);
+ var pj = Math.floor((codeblock.tby0_ - subband.tby0) /
+ precinctParameters.precinctHeightInSubband);
+ precinctNumber = pi + (pj * precinctParameters.numprecinctswide);
+
codeblock.precinctNumber = precinctNumber;
codeblock.subbandType = subband.type;
codeblock.Lblock = 3;
+
+ if (codeblock.tbx1_ <= codeblock.tbx0_ ||
+ codeblock.tby1_ <= codeblock.tby0_) {
+ continue;
+ }
codeblocks.push(codeblock);
// building precinct for the sub-band
var precinct = precincts[precinctNumber];
if (precinct !== undefined) {
if (i < precinct.cbxMin) {
@@ -41859,10 +44724,234 @@
l = 0;
}
throw new Error('JPX Error: Out of packets');
};
}
+ function ResolutionPositionComponentLayerIterator(context) {
+ var siz = context.SIZ;
+ var tileIndex = context.currentTile.index;
+ var tile = context.tiles[tileIndex];
+ var layersCount = tile.codingStyleDefaultParameters.layersCount;
+ var componentsCount = siz.Csiz;
+ var l, r, c, p;
+ var maxDecompositionLevelsCount = 0;
+ for (c = 0; c < componentsCount; c++) {
+ var component = tile.components[c];
+ maxDecompositionLevelsCount = Math.max(maxDecompositionLevelsCount,
+ component.codingStyleParameters.decompositionLevelsCount);
+ }
+ var maxNumPrecinctsInLevel = new Int32Array(
+ maxDecompositionLevelsCount + 1);
+ for (r = 0; r <= maxDecompositionLevelsCount; ++r) {
+ var maxNumPrecincts = 0;
+ for (c = 0; c < componentsCount; ++c) {
+ var resolutions = tile.components[c].resolutions;
+ if (r < resolutions.length) {
+ maxNumPrecincts = Math.max(maxNumPrecincts,
+ resolutions[r].precinctParameters.numprecincts);
+ }
+ }
+ maxNumPrecinctsInLevel[r] = maxNumPrecincts;
+ }
+ l = 0;
+ r = 0;
+ c = 0;
+ p = 0;
+
+ this.nextPacket = function JpxImage_nextPacket() {
+ // Section B.12.1.3 Resolution-position-component-layer
+ for (; r <= maxDecompositionLevelsCount; r++) {
+ for (; p < maxNumPrecinctsInLevel[r]; p++) {
+ for (; c < componentsCount; c++) {
+ var component = tile.components[c];
+ if (r > component.codingStyleParameters.decompositionLevelsCount) {
+ continue;
+ }
+ var resolution = component.resolutions[r];
+ var numprecincts = resolution.precinctParameters.numprecincts;
+ if (p >= numprecincts) {
+ continue;
+ }
+ for (; l < layersCount;) {
+ var packet = createPacket(resolution, p, l);
+ l++;
+ return packet;
+ }
+ l = 0;
+ }
+ c = 0;
+ }
+ p = 0;
+ }
+ throw new Error('JPX Error: Out of packets');
+ };
+ }
+ function PositionComponentResolutionLayerIterator(context) {
+ var siz = context.SIZ;
+ var tileIndex = context.currentTile.index;
+ var tile = context.tiles[tileIndex];
+ var layersCount = tile.codingStyleDefaultParameters.layersCount;
+ var componentsCount = siz.Csiz;
+ var precinctsSizes = getPrecinctSizesInImageScale(tile);
+ var precinctsIterationSizes = precinctsSizes;
+ var l = 0, r = 0, c = 0, px = 0, py = 0;
+
+ this.nextPacket = function JpxImage_nextPacket() {
+ // Section B.12.1.4 Position-component-resolution-layer
+ for (; py < precinctsIterationSizes.maxNumHigh; py++) {
+ for (; px < precinctsIterationSizes.maxNumWide; px++) {
+ for (; c < componentsCount; c++) {
+ var component = tile.components[c];
+ var decompositionLevelsCount =
+ component.codingStyleParameters.decompositionLevelsCount;
+ for (; r <= decompositionLevelsCount; r++) {
+ var resolution = component.resolutions[r];
+ var sizeInImageScale =
+ precinctsSizes.components[c].resolutions[r];
+ var k = getPrecinctIndexIfExist(
+ px,
+ py,
+ sizeInImageScale,
+ precinctsIterationSizes,
+ resolution);
+ if (k === null) {
+ continue;
+ }
+ for (; l < layersCount;) {
+ var packet = createPacket(resolution, k, l);
+ l++;
+ return packet;
+ }
+ l = 0;
+ }
+ r = 0;
+ }
+ c = 0;
+ }
+ px = 0;
+ }
+ throw new Error('JPX Error: Out of packets');
+ };
+ }
+ function ComponentPositionResolutionLayerIterator(context) {
+ var siz = context.SIZ;
+ var tileIndex = context.currentTile.index;
+ var tile = context.tiles[tileIndex];
+ var layersCount = tile.codingStyleDefaultParameters.layersCount;
+ var componentsCount = siz.Csiz;
+ var precinctsSizes = getPrecinctSizesInImageScale(tile);
+ var l = 0, r = 0, c = 0, px = 0, py = 0;
+
+ this.nextPacket = function JpxImage_nextPacket() {
+ // Section B.12.1.5 Component-position-resolution-layer
+ for (; c < componentsCount; ++c) {
+ var component = tile.components[c];
+ var precinctsIterationSizes = precinctsSizes.components[c];
+ var decompositionLevelsCount =
+ component.codingStyleParameters.decompositionLevelsCount;
+ for (; py < precinctsIterationSizes.maxNumHigh; py++) {
+ for (; px < precinctsIterationSizes.maxNumWide; px++) {
+ for (; r <= decompositionLevelsCount; r++) {
+ var resolution = component.resolutions[r];
+ var sizeInImageScale = precinctsIterationSizes.resolutions[r];
+ var k = getPrecinctIndexIfExist(
+ px,
+ py,
+ sizeInImageScale,
+ precinctsIterationSizes,
+ resolution);
+ if (k === null) {
+ continue;
+ }
+ for (; l < layersCount;) {
+ var packet = createPacket(resolution, k, l);
+ l++;
+ return packet;
+ }
+ l = 0;
+ }
+ r = 0;
+ }
+ px = 0;
+ }
+ py = 0;
+ }
+ throw new Error('JPX Error: Out of packets');
+ };
+ }
+ function getPrecinctIndexIfExist(
+ pxIndex, pyIndex, sizeInImageScale, precinctIterationSizes, resolution) {
+ var posX = pxIndex * precinctIterationSizes.minWidth;
+ var posY = pyIndex * precinctIterationSizes.minHeight;
+ if (posX % sizeInImageScale.width !== 0 ||
+ posY % sizeInImageScale.height !== 0) {
+ return null;
+ }
+ var startPrecinctRowIndex =
+ (posY / sizeInImageScale.width) *
+ resolution.precinctParameters.numprecinctswide;
+ return (posX / sizeInImageScale.height) + startPrecinctRowIndex;
+ }
+ function getPrecinctSizesInImageScale(tile) {
+ var componentsCount = tile.components.length;
+ var minWidth = Number.MAX_VALUE;
+ var minHeight = Number.MAX_VALUE;
+ var maxNumWide = 0;
+ var maxNumHigh = 0;
+ var sizePerComponent = new Array(componentsCount);
+ for (var c = 0; c < componentsCount; c++) {
+ var component = tile.components[c];
+ var decompositionLevelsCount =
+ component.codingStyleParameters.decompositionLevelsCount;
+ var sizePerResolution = new Array(decompositionLevelsCount + 1);
+ var minWidthCurrentComponent = Number.MAX_VALUE;
+ var minHeightCurrentComponent = Number.MAX_VALUE;
+ var maxNumWideCurrentComponent = 0;
+ var maxNumHighCurrentComponent = 0;
+ var scale = 1;
+ for (var r = decompositionLevelsCount; r >= 0; --r) {
+ var resolution = component.resolutions[r];
+ var widthCurrentResolution =
+ scale * resolution.precinctParameters.precinctWidth;
+ var heightCurrentResolution =
+ scale * resolution.precinctParameters.precinctHeight;
+ minWidthCurrentComponent = Math.min(
+ minWidthCurrentComponent,
+ widthCurrentResolution);
+ minHeightCurrentComponent = Math.min(
+ minHeightCurrentComponent,
+ heightCurrentResolution);
+ maxNumWideCurrentComponent = Math.max(maxNumWideCurrentComponent,
+ resolution.precinctParameters.numprecinctswide);
+ maxNumHighCurrentComponent = Math.max(maxNumHighCurrentComponent,
+ resolution.precinctParameters.numprecinctshigh);
+ sizePerResolution[r] = {
+ width: widthCurrentResolution,
+ height: heightCurrentResolution
+ };
+ scale <<= 1;
+ }
+ minWidth = Math.min(minWidth, minWidthCurrentComponent);
+ minHeight = Math.min(minHeight, minHeightCurrentComponent);
+ maxNumWide = Math.max(maxNumWide, maxNumWideCurrentComponent);
+ maxNumHigh = Math.max(maxNumHigh, maxNumHighCurrentComponent);
+ sizePerComponent[c] = {
+ resolutions: sizePerResolution,
+ minWidth: minWidthCurrentComponent,
+ minHeight: minHeightCurrentComponent,
+ maxNumWide: maxNumWideCurrentComponent,
+ maxNumHigh: maxNumHighCurrentComponent
+ };
+ }
+ return {
+ components: sizePerComponent,
+ minWidth: minWidth,
+ minHeight: minHeight,
+ maxNumWide: maxNumWide,
+ maxNumHigh: maxNumHigh
+ };
+ }
function buildPackets(context) {
var siz = context.SIZ;
var tileIndex = context.currentTile.index;
var tile = context.tiles[tileIndex];
var componentsCount = siz.Csiz;
@@ -41880,10 +44969,11 @@
var scale = 1 << (decompositionLevelsCount - r);
resolution.trx0 = Math.ceil(component.tcx0 / scale);
resolution.try0 = Math.ceil(component.tcy0 / scale);
resolution.trx1 = Math.ceil(component.tcx1 / scale);
resolution.try1 = Math.ceil(component.tcy1 / scale);
+ resolution.resLevel = r;
buildPrecincts(context, resolution, blocksDimensions);
resolutions.push(resolution);
var subband;
if (r === 0) {
@@ -41950,10 +45040,22 @@
break;
case 1:
tile.packetsIterator =
new ResolutionLayerComponentPositionIterator(context);
break;
+ case 2:
+ tile.packetsIterator =
+ new ResolutionPositionComponentLayerIterator(context);
+ break;
+ case 3:
+ tile.packetsIterator =
+ new PositionComponentResolutionLayerIterator(context);
+ break;
+ case 4:
+ tile.packetsIterator =
+ new ComponentPositionResolutionLayerIterator(context);
+ break;
default:
throw new Error('JPX Error: Unsupported progression order ' +
progressionOrder);
}
}
@@ -41977,10 +45079,25 @@
}
}
bufferSize -= count;
return (buffer >>> bufferSize) & ((1 << count) - 1);
}
+ function skipMarkerIfEqual(value) {
+ if (data[offset + position - 1] === 0xFF &&
+ data[offset + position] === value) {
+ skipBytes(1);
+ return true;
+ } else if (data[offset + position] === 0xFF &&
+ data[offset + position + 1] === value) {
+ skipBytes(2);
+ return true;
+ }
+ return false;
+ }
+ function skipBytes(count) {
+ position += count;
+ }
function alignToByte() {
bufferSize = 0;
if (skipNextBit) {
position++;
skipNextBit = false;
@@ -42004,15 +45121,21 @@
value = readBits(7);
return value + 37;
}
var tileIndex = context.currentTile.index;
var tile = context.tiles[tileIndex];
+ var sopMarkerUsed = context.COD.sopMarkerUsed;
+ var ephMarkerUsed = context.COD.ephMarkerUsed;
var packetsIterator = tile.packetsIterator;
while (position < dataLength) {
+ alignToByte();
+ if (sopMarkerUsed && skipMarkerIfEqual(0x91)) {
+ // Skip also marker segment length and packet sequence ID
+ skipBytes(4);
+ }
var packet = packetsIterator.nextPacket();
if (!readBits(1)) {
- alignToByte();
continue;
}
var layerNumber = packet.layerNumber;
var queue = [], codeblock;
for (var i = 0, ii = packet.codeblocks.length; i < ii; i++) {
@@ -42021,17 +45144,17 @@
var codeblockColumn = codeblock.cbx - precinct.cbxMin;
var codeblockRow = codeblock.cby - precinct.cbyMin;
var codeblockIncluded = false;
var firstTimeInclusion = false;
var valueReady;
- if ('included' in codeblock) {
+ if (codeblock['included'] !== undefined) {
codeblockIncluded = !!readBits(1);
} else {
// reading inclusion tree
precinct = codeblock.precinct;
var inclusionTree, zeroBitPlanesTree;
- if ('inclusionTree' in precinct) {
+ if (precinct['inclusionTree'] !== undefined) {
inclusionTree = precinct.inclusionTree;
} else {
// building inclusion and zero bit-planes trees
var width = precinct.cbxMax - precinct.cbxMin + 1;
var height = precinct.cbyMax - precinct.cbyMin + 1;
@@ -42089,14 +45212,17 @@
codingpasses: codingpasses,
dataLength: codedDataLength
});
}
alignToByte();
+ if (ephMarkerUsed) {
+ skipMarkerIfEqual(0x92);
+ }
while (queue.length > 0) {
var packetItem = queue.shift();
codeblock = packetItem.codeblock;
- if (!('data' in codeblock)) {
+ if (codeblock['data'] === undefined) {
codeblock.data = [];
}
codeblock.data.push({
data: data,
start: offset + position,
@@ -42122,11 +45248,11 @@
var blockWidth = codeblock.tbx1_ - codeblock.tbx0_;
var blockHeight = codeblock.tby1_ - codeblock.tby0_;
if (blockWidth === 0 || blockHeight === 0) {
continue;
}
- if (!('data' in codeblock)) {
+ if (codeblock['data'] === undefined) {
continue;
}
var bitModel, currentCodingpassType;
bitModel = new BitModel(blockWidth, blockHeight, codeblock.subbandType,
@@ -42377,14 +45503,14 @@
var siz = context.SIZ;
var componentsCount = siz.Csiz;
var tile = context.tiles[tileIndex];
for (var c = 0; c < componentsCount; c++) {
var component = tile.components[c];
- var qcdOrQcc = (c in context.currentTile.QCC ?
+ var qcdOrQcc = (context.currentTile.QCC[c] !== undefined ?
context.currentTile.QCC[c] : context.currentTile.QCD);
component.quantizationParameters = qcdOrQcc;
- var codOrCoc = (c in context.currentTile.COC ?
+ var codOrCoc = (context.currentTile.COC[c] !== undefined ?
context.currentTile.COC[c] : context.currentTile.COD);
component.codingStyleParameters = codOrCoc;
}
tile.codingStyleDefaultParameters = context.currentTile.COD;
}
@@ -42409,11 +45535,11 @@
reset: function TagTree_reset(i, j) {
var currentLevel = 0, value = 0, level;
while (currentLevel < this.levels.length) {
level = this.levels[currentLevel];
var index = i + j * level.width;
- if (index in level.items) {
+ if (level.items[index] !== undefined) {
value = level.items[index];
break;
}
level.index = index;
i >>= 1;
@@ -43124,11 +46250,10 @@
return JpxImage;
})();
-
var Jbig2Image = (function Jbig2ImageClosure() {
// Utility data structures
function ContextCache() {}
ContextCache.prototype = {
@@ -43285,14 +46410,13 @@
for (j = 0; j < width; j++) {
row[j] = pixel = decoder.readBit(contexts, contextLabel);
// At each pixel: Clear contextLabel pixels that are shifted
// out of the context, then add new ones.
- // If j + n is out of range at the right image border, then
- // the undefined value of bitmap[i - 2][j + n] is shifted to 0
contextLabel = ((contextLabel & OLD_PIXEL_MASK) << 1) |
- (row2[j + 3] << 11) | (row1[j + 4] << 4) | pixel;
+ (j + 3 < width ? row2[j + 3] << 11 : 0) |
+ (j + 4 < width ? row1[j + 4] << 4 : 0) | pixel;
}
}
return bitmap;
}
@@ -44589,49 +47713,24 @@
*/
// don't mirror as characters are already mirrored in the pdf
// Finally, return string
- var result = '';
for (i = 0, ii = chars.length; i < ii; ++i) {
var ch = chars[i];
- if (ch !== '<' && ch !== '>') {
- result += ch;
+ if (ch === '<' || ch === '>') {
+ chars[i] = '';
}
}
- return createBidiText(result, isLTR);
+ return createBidiText(chars.join(''), isLTR);
}
return bidi;
})();
-/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
-/* Copyright 2014 Opera Software ASA
- *
- * 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.
- *
- *
- * Based on https://code.google.com/p/smhasher/wiki/MurmurHash3.
- * Hashes roughly 100 KB per millisecond on i7 3.4 GHz.
- */
-/* globals Uint32ArrayView */
-
-'use strict';
-
var MurmurHash3_64 = (function MurmurHash3_64Closure (seed) {
// Workaround for missing math precison in JS.
var MASK_HIGH = 0xffff0000;
var MASK_LOW = 0xffff;
@@ -44775,13 +47874,11 @@
if (!PDFJS.workerSrc && typeof document !== 'undefined') {
// workerSrc is not set -- using last script url to define default location
PDFJS.workerSrc = (function () {
'use strict';
- var scriptTagContainer = document.body ||
- document.getElementsByTagName('head')[0];
- var pdfjsSrc = scriptTagContainer.lastChild.src;
- return pdfjsSrc && pdfjsSrc.replace(/\.js$/i, '.worker.js');
+ var pdfJsSrc = document.currentScript.src;
+ return pdfJsSrc && pdfJsSrc.replace(/\.js$/i, '.worker.js');
})();
}