diff --git a/worker/boot_processor.js b/worker/boot_processor.js new file mode 100644 index 000000000..241870c5c --- /dev/null +++ b/worker/boot_processor.js @@ -0,0 +1,18 @@ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- / +/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ + +'use strict'; + +importScripts('console.js'); +importScripts('message_handler.js'); +importScripts('../pdf.js'); +importScripts('../fonts.js'); +importScripts('../crypto.js'); +importScripts('../glyphlist.js'); +importScripts('handler.js'); + +// Listen for messages from the main thread. +var pdfDoc = null; + +var handler = new MessageHandler("worker", this); +WorkerHandler.setup(handler); diff --git a/worker/canvas.js b/worker/canvas.js deleted file mode 100644 index 5a9237d9a..000000000 --- a/worker/canvas.js +++ /dev/null @@ -1,252 +0,0 @@ -/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ - -'use strict'; - -var JpegStreamProxyCounter = 0; -// WebWorker Proxy for JpegStream. -var JpegStreamProxy = (function() { - function constructor(bytes, dict) { - this.id = JpegStreamProxyCounter++; - this.dict = dict; - - // Tell the main thread to create an image. - postMessage({ - action: 'jpeg_stream', - data: { - id: this.id, - raw: bytesToString(bytes) - } - }); - } - - constructor.prototype = { - getImage: function() { - return this; - }, - getChar: function() { - error('internal error: getChar is not valid on JpegStream'); - } - }; - - return constructor; -})(); - -// Really simple GradientProxy. There is currently only one active gradient at -// the time, meaning you can't create a gradient, create a second one and then -// use the first one again. As this isn't used in pdf.js right now, it's okay. -function GradientProxy(cmdQueue, x0, y0, x1, y1) { - cmdQueue.push(['$createLinearGradient', [x0, y0, x1, y1]]); - this.addColorStop = function(i, rgba) { - cmdQueue.push(['$addColorStop', [i, rgba]]); - } -} - -// Really simple PatternProxy. -var patternProxyCounter = 0; -function PatternProxy(cmdQueue, object, kind) { - this.id = patternProxyCounter++; - - if (!(object instanceof CanvasProxy)) { - throw 'unkown type to createPattern'; - } - - // Flush the object here to ensure it's available on the main thread. - // TODO: Make some kind of dependency management, such that the object - // gets flushed only if needed. - object.flush(); - cmdQueue.push(['$createPatternFromCanvas', [this.id, object.id, kind]]); -} - -var canvasProxyCounter = 0; -function CanvasProxy(width, height) { - this.id = canvasProxyCounter++; - - // The `stack` holds the rendering calls and gets flushed to the main thead. - var cmdQueue = this.cmdQueue = []; - - // Dummy context that gets exposed. - var ctx = {}; - this.getContext = function(type) { - if (type != '2d') { - throw 'CanvasProxy can only provide a 2d context.'; - } - return ctx; - } - - // Expose only the minimum of the canvas object - there is no dom to do - // more here. - this.width = width; - this.height = height; - ctx.canvas = this; - - // Setup function calls to `ctx`. - var ctxFunc = [ - 'createRadialGradient', - 'arcTo', - 'arc', - 'fillText', - 'strokeText', - 'createImageData', - 'drawWindow', - 'save', - 'restore', - 'scale', - 'rotate', - 'translate', - 'transform', - 'setTransform', - 'clearRect', - 'fillRect', - 'strokeRect', - 'beginPath', - 'closePath', - 'moveTo', - 'lineTo', - 'quadraticCurveTo', - 'bezierCurveTo', - 'rect', - 'fill', - 'stroke', - 'clip', - 'measureText', - 'isPointInPath', - - // These functions are necessary to track the rendering currentX state. - // The exact values can be computed on the main thread only, as the - // worker has no idea about text width. - '$setCurrentX', - '$addCurrentX', - '$saveCurrentX', - '$restoreCurrentX', - '$showText', - '$setFont' - ]; - - function buildFuncCall(name) { - return function() { - // console.log("funcCall", name) - cmdQueue.push([name, Array.prototype.slice.call(arguments)]); - } - } - var name; - for (var i = 0; i < ctxFunc.length; i++) { - name = ctxFunc[i]; - ctx[name] = buildFuncCall(name); - } - - // Some function calls that need more work. - - ctx.createPattern = function(object, kind) { - return new PatternProxy(cmdQueue, object, kind); - } - - ctx.createLinearGradient = function(x0, y0, x1, y1) { - return new GradientProxy(cmdQueue, x0, y0, x1, y1); - } - - ctx.getImageData = function(x, y, w, h) { - return { - width: w, - height: h, - data: Uint8ClampedArray(w * h * 4) - }; - } - - ctx.putImageData = function(data, x, y, width, height) { - cmdQueue.push(['$putImageData', [data, x, y, width, height]]); - } - - ctx.drawImage = function(image, x, y, width, height, - sx, sy, swidth, sheight) { - if (image instanceof CanvasProxy) { - // Send the image/CanvasProxy to the main thread. - image.flush(); - cmdQueue.push(['$drawCanvas', [image.id, x, y, sx, sy, swidth, sheight]]); - } else if (image instanceof JpegStreamProxy) { - cmdQueue.push(['$drawImage', [image.id, x, y, sx, sy, swidth, sheight]]); - } else { - throw 'unkown type to drawImage'; - } - } - - // Setup property access to `ctx`. - var ctxProp = { - // "canvas" - 'globalAlpha': '1', - 'globalCompositeOperation': 'source-over', - 'strokeStyle': '#000000', - 'fillStyle': '#000000', - 'lineWidth': '1', - 'lineCap': 'butt', - 'lineJoin': 'miter', - 'miterLimit': '10', - 'shadowOffsetX': '0', - 'shadowOffsetY': '0', - 'shadowBlur': '0', - 'shadowColor': 'rgba(0, 0, 0, 0)', - 'font': '10px sans-serif', - 'textAlign': 'start', - 'textBaseline': 'alphabetic', - 'mozTextStyle': '10px sans-serif', - 'mozImageSmoothingEnabled': 'true' - }; - - function buildGetter(name) { - return function() { - return ctx['$' + name]; - } - } - - function buildSetter(name) { - return function(value) { - cmdQueue.push(['$', name, value]); - return ctx['$' + name] = value; - } - } - - // Setting the value to `stroke|fillStyle` needs special handling, as it - // might gets an gradient/pattern. - function buildSetterStyle(name) { - return function(value) { - if (value instanceof GradientProxy) { - cmdQueue.push(['$' + name + 'Gradient']); - } else if (value instanceof PatternProxy) { - cmdQueue.push(['$' + name + 'Pattern', [value.id]]); - } else { - cmdQueue.push(['$', name, value]); - return ctx['$' + name] = value; - } - } - } - - for (var name in ctxProp) { - ctx['$' + name] = ctxProp[name]; - ctx.__defineGetter__(name, buildGetter(name)); - - // Special treatment for `fillStyle` and `strokeStyle`: The passed style - // might be a gradient. Need to check for that. - if (name == 'fillStyle' || name == 'strokeStyle') { - ctx.__defineSetter__(name, buildSetterStyle(name)); - } else { - ctx.__defineSetter__(name, buildSetter(name)); - } - } -} - -/** -* Sends the current cmdQueue of the CanvasProxy over to the main thread and -* resets the cmdQueue. -*/ -CanvasProxy.prototype.flush = function() { - postMessage({ - action: 'canvas_proxy_cmd_queue', - data: { - id: this.id, - cmdQueue: this.cmdQueue, - width: this.width, - height: this.height - } - }); - this.cmdQueue.length = 0; -}; diff --git a/worker/client.js b/worker/client.js deleted file mode 100644 index a20a4179f..000000000 --- a/worker/client.js +++ /dev/null @@ -1,417 +0,0 @@ -/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ - -'use strict'; - -if (typeof console.time == 'undefined') { - var consoleTimer = {}; - console.time = function(name) { - consoleTimer[name] = Date.now(); - }; - - console.timeEnd = function(name) { - var time = consoleTimer[name]; - if (time == null) { - throw 'Unkown timer name ' + name; - } - this.log('Timer:', name, Date.now() - time); - }; -} - -function FontWorker() { - this.worker = new Worker('worker/font.js'); - this.fontsWaiting = 0; - this.fontsWaitingCallbacks = []; - - // Listen to the WebWorker for data and call actionHandler on it. - this.worker.onmessage = function(event) { - var data = event.data; - var actionHandler = this.actionHandler; - if (data.action in actionHandler) { - actionHandler[data.action].call(this, data.data); - } else { - throw 'Unkown action from worker: ' + data.action; - } - }.bind(this); - - this.$handleFontLoadedCallback = this.handleFontLoadedCallback.bind(this); -} - -FontWorker.prototype = { - handleFontLoadedCallback: function() { - // Decrease the number of fonts wainting to be loaded. - this.fontsWaiting--; - // If all fonts are available now, then call all the callbacks. - if (this.fontsWaiting == 0) { - var callbacks = this.fontsWaitingCallbacks; - for (var i = 0; i < callbacks.length; i++) { - callbacks[i](); - } - this.fontsWaitingCallbacks.length = 0; - } - }, - - actionHandler: { - 'log': function(data) { - console.log.apply(console, data); - }, - - 'fonts': function(data) { - // console.log("got processed fonts from worker", Object.keys(data)); - for (name in data) { - // Update the encoding property. - var font = Fonts.lookup(name); - font.properties = { - encoding: data[name].encoding - }; - - // Call `Font.prototype.bindDOM` to make the font get loaded - // on the page. - Font.prototype.bindDOM.call( - font, - data[name].str, - // IsLoadedCallback. - this.$handleFontLoadedCallback - ); - } - } - }, - - ensureFonts: function(data, callback) { - var font; - var notLoaded = []; - for (var i = 0; i < data.length; i++) { - font = data[i]; - if (Fonts[font.name]) { - continue; - } - - // Register the font but don't pass in any real data. The idea is to - // store as less data as possible to reduce memory usage. - Fonts.registerFont(font.name, Object.create(null), Object.create(null)); - - // Mark this font to be handled later. - notLoaded.push(font); - // Increate the number of fonts to wait for. - this.fontsWaiting++; - } - - console.time('ensureFonts'); - // If there are fonts, that need to get loaded, tell the FontWorker to get - // started and push the callback on the waiting-callback-stack. - if (notLoaded.length != 0) { - console.log('fonts -> FontWorker'); - // Send the worker the fonts to work on. - this.worker.postMessage({ - action: 'fonts', - data: notLoaded - }); - if (callback) { - this.fontsWaitingCallbacks.push(callback); - } - } - // All fonts are present? Well, then just call the callback if there is one. - else { - if (callback) { - callback(); - } - } - } -}; - -function WorkerPDFDoc(canvas) { - var timer = null; - - this.ctx = canvas.getContext('2d'); - this.canvas = canvas; - this.worker = new Worker('worker/pdf.js'); - this.fontWorker = new FontWorker(); - this.waitingForFonts = false; - this.waitingForFontsCallback = []; - - this.numPage = 1; - this.numPages = null; - - var imagesList = {}; - var canvasList = { - 0: canvas - }; - var patternList = {}; - var gradient; - - var currentX = 0; - var currentXStack = []; - - var ctxSpecial = { - '$setCurrentX': function(value) { - currentX = value; - }, - - '$addCurrentX': function(value) { - currentX += value; - }, - - '$saveCurrentX': function() { - currentXStack.push(currentX); - }, - - '$restoreCurrentX': function() { - currentX = currentXStack.pop(); - }, - - '$showText': function(y, text) { - text = Fonts.charsToUnicode(text); - this.translate(currentX, -1 * y); - this.fillText(text, 0, 0); - currentX += this.measureText(text).width; - }, - - '$putImageData': function(imageData, x, y) { - var imgData = this.getImageData(0, 0, imageData.width, imageData.height); - - // Store the .data property to avaid property lookups. - var imageRealData = imageData.data; - var imgRealData = imgData.data; - - // Copy over the imageData. - var len = imageRealData.length; - while (len--) - imgRealData[len] = imageRealData[len]; - - this.putImageData(imgData, x, y); - }, - - '$drawImage': function(id, x, y, sx, sy, swidth, sheight) { - var image = imagesList[id]; - if (!image) { - throw 'Image not found: ' + id; - } - this.drawImage(image, x, y, image.width, image.height, - sx, sy, swidth, sheight); - }, - - '$drawCanvas': function(id, x, y, sx, sy, swidth, sheight) { - var canvas = canvasList[id]; - if (!canvas) { - throw 'Canvas not found'; - } - if (sheight != null) { - this.drawImage(canvas, x, y, canvas.width, canvas.height, - sx, sy, swidth, sheight); - } else { - this.drawImage(canvas, x, y, canvas.width, canvas.height); - } - }, - - '$createLinearGradient': function(x0, y0, x1, y1) { - gradient = this.createLinearGradient(x0, y0, x1, y1); - }, - - '$createPatternFromCanvas': function(patternId, canvasId, kind) { - var canvas = canvasList[canvasId]; - if (!canvas) { - throw 'Canvas not found'; - } - patternList[patternId] = this.createPattern(canvas, kind); - }, - - '$addColorStop': function(i, rgba) { - gradient.addColorStop(i, rgba); - }, - - '$fillStyleGradient': function() { - this.fillStyle = gradient; - }, - - '$fillStylePattern': function(id) { - var pattern = patternList[id]; - if (!pattern) { - throw 'Pattern not found'; - } - this.fillStyle = pattern; - }, - - '$strokeStyleGradient': function() { - this.strokeStyle = gradient; - }, - - '$strokeStylePattern': function(id) { - var pattern = patternList[id]; - if (!pattern) { - throw 'Pattern not found'; - } - this.strokeStyle = pattern; - }, - - '$setFont': function(name, size) { - this.font = size + 'px "' + name + '"'; - Fonts.setActive(name, size); - } - }; - - function renderProxyCanvas(canvas, cmdQueue) { - var ctx = canvas.getContext('2d'); - var cmdQueueLength = cmdQueue.length; - for (var i = 0; i < cmdQueueLength; i++) { - var opp = cmdQueue[i]; - if (opp[0] == '$') { - ctx[opp[1]] = opp[2]; - } else if (opp[0] in ctxSpecial) { - ctxSpecial[opp[0]].apply(ctx, opp[1]); - } else { - ctx[opp[0]].apply(ctx, opp[1]); - } - } - } - - /** - * Functions to handle data sent by the WebWorker. - */ - var actionHandler = { - 'log': function(data) { - console.log.apply(console, data); - }, - - 'pdf_num_pages': function(data) { - this.numPages = parseInt(data); - if (this.loadCallback) { - this.loadCallback(); - } - }, - - 'font': function(data) { - var base64 = window.btoa(data.raw); - - // Add the @font-face rule to the document - var url = 'url(data:' + data.mimetype + ';base64,' + base64 + ');'; - var rule = ("@font-face { font-family:'" + data.fontName + - "';src:" + url + '}'); - var styleSheet = document.styleSheets[0]; - styleSheet.insertRule(rule, styleSheet.cssRules.length); - - // Just adding the font-face to the DOM doesn't make it load. It - // seems it's loaded once Gecko notices it's used. Therefore, - // add a div on the page using the loaded font. - var div = document.createElement('div'); - var style = 'font-family:"' + data.fontName + - '";position: absolute;top:-99999;left:-99999;z-index:-99999'; - div.setAttribute('style', style); - document.body.appendChild(div); - }, - - 'setup_page': function(data) { - var size = data.split(','); - var canvas = this.canvas, ctx = this.ctx; - canvas.width = parseInt(size[0]); - canvas.height = parseInt(size[1]); - }, - - 'fonts': function(data) { - this.waitingForFonts = true; - this.fontWorker.ensureFonts(data, function() { - this.waitingForFonts = false; - var callbacks = this.waitingForFontsCallback; - for (var i = 0; i < callbacks.length; i++) { - callbacks[i](); - } - this.waitingForFontsCallback.length = 0; - }.bind(this)); - }, - - 'jpeg_stream': function(data) { - var img = new Image(); - img.src = 'data:image/jpeg;base64,' + window.btoa(data.raw); - imagesList[data.id] = img; - }, - - 'canvas_proxy_cmd_queue': function(data) { - var id = data.id; - var cmdQueue = data.cmdQueue; - - // Check if there is already a canvas with the given id. If not, - // create a new canvas. - if (!canvasList[id]) { - var newCanvas = document.createElement('canvas'); - newCanvas.width = data.width; - newCanvas.height = data.height; - canvasList[id] = newCanvas; - } - - var renderData = function() { - if (id == 0) { - console.time('main canvas rendering'); - var ctx = this.ctx; - ctx.save(); - ctx.fillStyle = 'rgb(255, 255, 255)'; - ctx.fillRect(0, 0, canvas.width, canvas.height); - ctx.restore(); - } - renderProxyCanvas(canvasList[id], cmdQueue); - if (id == 0) { - console.timeEnd('main canvas rendering'); - console.timeEnd('>>> total page display time:'); - } - }.bind(this); - - if (this.waitingForFonts) { - if (id == 0) { - console.log('want to render, but not all fonts are there', id); - this.waitingForFontsCallback.push(renderData); - } else { - // console.log("assume canvas doesn't have fonts", id); - renderData(); - } - } else { - renderData(); - } - } - }; - - // Listen to the WebWorker for data and call actionHandler on it. - this.worker.onmessage = function(event) { - var data = event.data; - if (data.action in actionHandler) { - actionHandler[data.action].call(this, data.data); - } else { - throw 'Unkown action from worker: ' + data.action; - } - }.bind(this); -} - -WorkerPDFDoc.prototype.open = function(url, callback) { - var req = new XMLHttpRequest(); - req.open('GET', url); - req.mozResponseType = req.responseType = 'arraybuffer'; - req.expected = (document.URL.indexOf('file:') == 0) ? 0 : 200; - req.onreadystatechange = function() { - if (req.readyState == 4 && req.status == req.expected) { - var data = req.mozResponseArrayBuffer || req.mozResponse || - req.responseArrayBuffer || req.response; - - this.loadCallback = callback; - this.worker.postMessage(data); - this.showPage(this.numPage); - } - }.bind(this); - req.send(null); -}; - -WorkerPDFDoc.prototype.showPage = function(numPage) { - this.numPage = parseInt(numPage); - console.log('=== start rendering page ' + numPage + ' ==='); - console.time('>>> total page display time:'); - this.worker.postMessage(numPage); - if (this.onChangePage) { - this.onChangePage(numPage); - } -}; - -WorkerPDFDoc.prototype.nextPage = function() { - if (this.numPage == this.numPages) return; - this.showPage(++this.numPage); -}; - -WorkerPDFDoc.prototype.prevPage = function() { - if (this.numPage == 1) return; - this.showPage(--this.numPage); -}; diff --git a/worker/font.js b/worker/font.js deleted file mode 100644 index 549b73101..000000000 --- a/worker/font.js +++ /dev/null @@ -1,66 +0,0 @@ -/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ - -'use strict'; - -importScripts('console.js'); - -importScripts('../pdf.js'); -importScripts('../fonts.js'); -importScripts('../glyphlist.js'); - -function fontDataToString(font) { - // Doing postMessage on objects make them lose their "shape". This adds the - // "shape" for all required objects agains, such that the encoding works as - // expected. - var fontFileDict = new Dict(); - fontFileDict.map = font.file.dict.map; - - var fontFile = new Stream(font.file.bytes, font.file.start, - font.file.end - font.file.start, fontFileDict); - font.file = new FlateStream(fontFile); - - // This will encode the font. - var fontObj = new Font(font.name, font.file, font.properties); - - // Create string that is used for css later. - var str = ''; - var data = fontObj.data; - var length = data.length; - for (var j = 0; j < length; j++) - str += String.fromCharCode(data[j]); - - return { - str: str, - encoding: font.properties.encoding - }; -} - -/** -* Functions to handle data sent by the MainThread. -*/ -var actionHandler = { - 'fonts': function(data) { - var fontData; - var result = {}; - for (var i = 0; i < data.length; i++) { - fontData = data[i]; - result[fontData.name] = fontDataToString(fontData); - } - - postMessage({ - action: 'fonts', - data: result - }); - } -}; - -// Listen to the MainThread for data and call actionHandler on it. -this.onmessage = function(event) { - var data = event.data; - if (data.action in actionHandler) { - actionHandler[data.action].call(this, data.data); - } else { - throw 'Unkown action from worker: ' + data.action; - } -};