+++ /dev/null
-/* -*- 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;
-};
+++ /dev/null
-/* -*- 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);
-};