You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
461 lines
15 KiB
461 lines
15 KiB
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ |
|
/* Copyright 2012 Mozilla Foundation |
|
* |
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
* you may not use this file except in compliance with the License. |
|
* You may obtain a copy of the License at |
|
* |
|
* http://www.apache.org/licenses/LICENSE-2.0 |
|
* |
|
* Unless required by applicable law or agreed to in writing, software |
|
* distributed under the License is distributed on an "AS IS" BASIS, |
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
* See the License for the specific language governing permissions and |
|
* limitations under the License. |
|
*/ |
|
/* globals error, globalScope, InvalidPDFException, info, |
|
MissingPDFException, PasswordException, PDFJS, Promise, |
|
UnknownErrorException, NetworkManager, LocalPdfManager, |
|
NetworkPdfManager, XRefParseException, createPromiseCapability, |
|
isInt, PasswordResponses, MessageHandler, Ref, RANGE_CHUNK_SIZE */ |
|
|
|
'use strict'; |
|
|
|
var WorkerMessageHandler = PDFJS.WorkerMessageHandler = { |
|
setup: function wphSetup(handler) { |
|
var pdfManager; |
|
|
|
function loadDocument(recoveryMode) { |
|
var loadDocumentCapability = createPromiseCapability(); |
|
|
|
var parseSuccess = function parseSuccess() { |
|
var numPagesPromise = pdfManager.ensureDoc('numPages'); |
|
var fingerprintPromise = pdfManager.ensureDoc('fingerprint'); |
|
var encryptedPromise = pdfManager.ensureXRef('encrypt'); |
|
Promise.all([numPagesPromise, fingerprintPromise, |
|
encryptedPromise]).then(function onDocReady(results) { |
|
var doc = { |
|
numPages: results[0], |
|
fingerprint: results[1], |
|
encrypted: !!results[2], |
|
}; |
|
loadDocumentCapability.resolve(doc); |
|
}, |
|
parseFailure); |
|
}; |
|
|
|
var parseFailure = function parseFailure(e) { |
|
loadDocumentCapability.reject(e); |
|
}; |
|
|
|
pdfManager.ensureDoc('checkHeader', []).then(function() { |
|
pdfManager.ensureDoc('parseStartXRef', []).then(function() { |
|
pdfManager.ensureDoc('parse', [recoveryMode]).then( |
|
parseSuccess, parseFailure); |
|
}, parseFailure); |
|
}, parseFailure); |
|
|
|
return loadDocumentCapability.promise; |
|
} |
|
|
|
function getPdfManager(data) { |
|
var pdfManagerCapability = createPromiseCapability(); |
|
|
|
var source = data.source; |
|
var disableRange = data.disableRange; |
|
if (source.data) { |
|
try { |
|
pdfManager = new LocalPdfManager(source.data, source.password); |
|
pdfManagerCapability.resolve(); |
|
} catch (ex) { |
|
pdfManagerCapability.reject(ex); |
|
} |
|
return pdfManagerCapability.promise; |
|
} else if (source.chunkedViewerLoading) { |
|
try { |
|
pdfManager = new NetworkPdfManager(source, handler); |
|
pdfManagerCapability.resolve(); |
|
} catch (ex) { |
|
pdfManagerCapability.reject(ex); |
|
} |
|
return pdfManagerCapability.promise; |
|
} |
|
|
|
var networkManager = new NetworkManager(source.url, { |
|
httpHeaders: source.httpHeaders, |
|
withCredentials: source.withCredentials |
|
}); |
|
var fullRequestXhrId = networkManager.requestFull({ |
|
onHeadersReceived: function onHeadersReceived() { |
|
if (disableRange) { |
|
return; |
|
} |
|
|
|
var fullRequestXhr = networkManager.getRequestXhr(fullRequestXhrId); |
|
if (fullRequestXhr.getResponseHeader('Accept-Ranges') !== 'bytes') { |
|
return; |
|
} |
|
|
|
var contentEncoding = |
|
fullRequestXhr.getResponseHeader('Content-Encoding') || 'identity'; |
|
if (contentEncoding !== 'identity') { |
|
return; |
|
} |
|
|
|
var length = fullRequestXhr.getResponseHeader('Content-Length'); |
|
length = parseInt(length, 10); |
|
if (!isInt(length)) { |
|
return; |
|
} |
|
source.length = length; |
|
if (length <= 2 * RANGE_CHUNK_SIZE) { |
|
// 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; |
|
} |
|
|
|
// NOTE: by cancelling the full request, and then issuing range |
|
// requests, there will be an issue for sites where you can only |
|
// request the pdf once. However, if this is the case, then the |
|
// server should not be returning that it can support range requests. |
|
networkManager.abortRequest(fullRequestXhrId); |
|
|
|
try { |
|
pdfManager = new NetworkPdfManager(source, handler); |
|
pdfManagerCapability.resolve(pdfManager); |
|
} catch (ex) { |
|
pdfManagerCapability.reject(ex); |
|
} |
|
}, |
|
|
|
onDone: function onDone(args) { |
|
// the data is array, instantiating directly from it |
|
try { |
|
pdfManager = new LocalPdfManager(args.chunk, source.password); |
|
pdfManagerCapability.resolve(); |
|
} catch (ex) { |
|
pdfManagerCapability.reject(ex); |
|
} |
|
}, |
|
|
|
onError: function onError(status) { |
|
if (status == 404) { |
|
var exception = new MissingPDFException('Missing PDF "' + |
|
source.url + '".'); |
|
handler.send('MissingPDF', { exception: exception }); |
|
} else { |
|
handler.send('DocError', 'Unexpected server response (' + |
|
status + ') while retrieving PDF "' + |
|
source.url + '".'); |
|
} |
|
}, |
|
|
|
onProgress: function onProgress(evt) { |
|
handler.send('DocProgress', { |
|
loaded: evt.loaded, |
|
total: evt.lengthComputable ? evt.total : source.length |
|
}); |
|
} |
|
}); |
|
|
|
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 onSuccess = function(doc) { |
|
handler.send('GetDoc', { pdfInfo: doc }); |
|
}; |
|
|
|
var onFailure = function(e) { |
|
if (e instanceof PasswordException) { |
|
if (e.code === PasswordResponses.NEED_PASSWORD) { |
|
handler.send('NeedPassword', { |
|
exception: e |
|
}); |
|
} else if (e.code === PasswordResponses.INCORRECT_PASSWORD) { |
|
handler.send('IncorrectPassword', { |
|
exception: e |
|
}); |
|
} |
|
} else if (e instanceof InvalidPDFException) { |
|
handler.send('InvalidPDF', { |
|
exception: e |
|
}); |
|
} else if (e instanceof MissingPDFException) { |
|
handler.send('MissingPDF', { |
|
exception: e |
|
}); |
|
} else { |
|
handler.send('UnknownError', { |
|
exception: new UnknownErrorException(e.message, e.toString()) |
|
}); |
|
} |
|
}; |
|
|
|
PDFJS.maxImageSize = data.maxImageSize === undefined ? |
|
-1 : data.maxImageSize; |
|
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 () { |
|
pdfManager.onLoadedStream().then(function(stream) { |
|
handler.send('DataLoaded', { length: stream.bytes.byteLength }); |
|
}); |
|
}).then(function pdfManagerReady() { |
|
loadDocument(false).then(onSuccess, function loadFailure(ex) { |
|
// Try again with recoveryMode == true |
|
if (!(ex instanceof XRefParseException)) { |
|
if (ex instanceof PasswordException) { |
|
// after password exception prepare to receive a new password |
|
// to repeat loading |
|
pdfManager.passwordChanged().then(pdfManagerReady); |
|
} |
|
|
|
onFailure(ex); |
|
return; |
|
} |
|
|
|
pdfManager.requestLoadedStream(); |
|
pdfManager.onLoadedStream().then(function() { |
|
loadDocument(true).then(onSuccess, onFailure); |
|
}); |
|
}, onFailure); |
|
}, onFailure); |
|
}); |
|
|
|
handler.on('GetPageRequest', function wphSetupGetPage(data) { |
|
var pageIndex = data.pageIndex; |
|
pdfManager.getPage(pageIndex).then(function(page) { |
|
var rotatePromise = pdfManager.ensure(page, 'rotate'); |
|
var refPromise = pdfManager.ensure(page, 'ref'); |
|
var viewPromise = pdfManager.ensure(page, 'view'); |
|
|
|
Promise.all([rotatePromise, refPromise, viewPromise]).then( |
|
function(results) { |
|
var page = { |
|
pageIndex: data.pageIndex, |
|
rotate: results[0], |
|
ref: results[1], |
|
view: results[2] |
|
}; |
|
|
|
handler.send('GetPage', { pageInfo: page }); |
|
}); |
|
}); |
|
}); |
|
|
|
handler.on('GetPageIndex', function wphSetupGetPageIndex(data) { |
|
var ref = new Ref(data.ref.num, data.ref.gen); |
|
var catalog = pdfManager.pdfDocument.catalog; |
|
return catalog.getPageIndex(ref); |
|
}); |
|
|
|
handler.on('GetDestinations', |
|
function wphSetupGetDestinations(data) { |
|
return pdfManager.ensureCatalog('destinations'); |
|
} |
|
); |
|
|
|
handler.on('GetAttachments', |
|
function wphSetupGetAttachments(data) { |
|
return pdfManager.ensureCatalog('attachments'); |
|
} |
|
); |
|
|
|
handler.on('GetJavaScript', |
|
function wphSetupGetJavaScript(data) { |
|
return pdfManager.ensureCatalog('javaScript'); |
|
} |
|
); |
|
|
|
handler.on('GetOutline', |
|
function wphSetupGetOutline(data) { |
|
return pdfManager.ensureCatalog('documentOutline'); |
|
} |
|
); |
|
|
|
handler.on('GetMetadata', |
|
function wphSetupGetMetadata(data) { |
|
return Promise.all([pdfManager.ensureDoc('documentInfo'), |
|
pdfManager.ensureCatalog('metadata')]); |
|
} |
|
); |
|
|
|
handler.on('GetData', function wphSetupGetData(data) { |
|
pdfManager.requestLoadedStream(); |
|
return pdfManager.onLoadedStream().then(function(stream) { |
|
return stream.bytes; |
|
}); |
|
}); |
|
|
|
handler.on('UpdatePassword', function wphSetupUpdatePassword(data) { |
|
pdfManager.updatePassword(data); |
|
}); |
|
|
|
handler.on('GetAnnotationsRequest', function wphSetupGetAnnotations(data) { |
|
pdfManager.getPage(data.pageIndex).then(function(page) { |
|
pdfManager.ensure(page, 'getAnnotationsData', []).then( |
|
function(annotationsData) { |
|
handler.send('GetAnnotations', { |
|
pageIndex: data.pageIndex, |
|
annotations: annotationsData |
|
}); |
|
} |
|
); |
|
}); |
|
}); |
|
|
|
handler.on('RenderPageRequest', function wphSetupRenderPage(data) { |
|
pdfManager.getPage(data.pageIndex).then(function(page) { |
|
|
|
var pageNum = data.pageIndex + 1; |
|
var start = Date.now(); |
|
// Pre compile the pdf page and fetch the fonts/images. |
|
page.getOperatorList(handler, data.intent).then(function(operatorList) { |
|
|
|
info('page=' + pageNum + ' - getOperatorList: time=' + |
|
(Date.now() - start) + 'ms, len=' + operatorList.fnArray.length); |
|
|
|
}, function(e) { |
|
|
|
var minimumStackMessage = |
|
'worker.js: while trying to getPage() and getOperatorList()'; |
|
|
|
var wrappedException; |
|
|
|
// Turn the error into an obj that can be serialized |
|
if (typeof e === 'string') { |
|
wrappedException = { |
|
message: e, |
|
stack: minimumStackMessage |
|
}; |
|
} else if (typeof e === 'object') { |
|
wrappedException = { |
|
message: e.message || e.toString(), |
|
stack: e.stack || minimumStackMessage |
|
}; |
|
} else { |
|
wrappedException = { |
|
message: 'Unknown exception type: ' + (typeof e), |
|
stack: minimumStackMessage |
|
}; |
|
} |
|
|
|
handler.send('PageError', { |
|
pageNum: pageNum, |
|
error: wrappedException, |
|
intent: data.intent |
|
}); |
|
}); |
|
}); |
|
}, this); |
|
|
|
handler.on('GetTextContent', function wphExtractText(data) { |
|
return pdfManager.getPage(data.pageIndex).then(function(page) { |
|
var pageNum = data.pageIndex + 1; |
|
var start = Date.now(); |
|
return page.extractTextContent().then(function(textContent) { |
|
info('text indexing: page=' + pageNum + ' - time=' + |
|
(Date.now() - start) + 'ms'); |
|
return textContent; |
|
}); |
|
}); |
|
}); |
|
|
|
handler.on('Cleanup', function wphCleanup(data) { |
|
pdfManager.cleanup(); |
|
return true; |
|
}); |
|
|
|
handler.on('Terminate', function wphTerminate(data) { |
|
pdfManager.terminate(); |
|
}); |
|
} |
|
}; |
|
|
|
var consoleTimer = {}; |
|
|
|
var workerConsole = { |
|
log: function log() { |
|
var args = Array.prototype.slice.call(arguments); |
|
globalScope.postMessage({ |
|
action: 'console_log', |
|
data: args |
|
}); |
|
}, |
|
|
|
error: function error() { |
|
var args = Array.prototype.slice.call(arguments); |
|
globalScope.postMessage({ |
|
action: 'console_error', |
|
data: args |
|
}); |
|
throw 'pdf.js execution error'; |
|
}, |
|
|
|
time: function time(name) { |
|
consoleTimer[name] = Date.now(); |
|
}, |
|
|
|
timeEnd: function timeEnd(name) { |
|
var time = consoleTimer[name]; |
|
if (!time) { |
|
error('Unknown timer name ' + name); |
|
} |
|
this.log('Timer:', name, Date.now() - time); |
|
} |
|
}; |
|
|
|
|
|
// Worker thread? |
|
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); |
|
}
|
|
|