2011-06-23 20:09:36 +09:00
|
|
|
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
|
|
|
|
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
|
|
|
|
|
2011-06-23 05:54:16 +09:00
|
|
|
"use strict";
|
|
|
|
|
2011-06-24 06:06:33 +09:00
|
|
|
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);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2011-06-27 03:55:27 +09:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
FontWorker.prototype = {
|
|
|
|
actionHandler: {
|
2011-06-27 03:58:22 +09:00
|
|
|
"log": function(data) {
|
|
|
|
console.log.apply(console, data);
|
|
|
|
},
|
|
|
|
|
2011-06-27 03:55:27 +09:00
|
|
|
"fonts": function(data) {
|
|
|
|
// console.log("got processed fonts from worker", Object.keys(data));
|
|
|
|
for (name in data) {
|
2011-06-27 03:58:22 +09:00
|
|
|
// Update the
|
|
|
|
Fonts[name].properties = {
|
|
|
|
encoding: data[name].encoding
|
|
|
|
}
|
|
|
|
var base64 = window.btoa(data[name].str);
|
2011-06-27 03:55:27 +09:00
|
|
|
|
|
|
|
// Add the @font-face rule to the document
|
|
|
|
var url = "url(data:font/opentype;base64," + base64 + ");";
|
|
|
|
var rule = "@font-face { font-family:'" + name + "';src:" + url + "}";
|
|
|
|
var styleSheet = document.styleSheets[0];
|
|
|
|
styleSheet.insertRule(rule, styleSheet.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:"' + name +
|
|
|
|
'";position: absolute;top:-99999;left:-99999;z-index:-99999';
|
|
|
|
div.setAttribute("style", style);
|
|
|
|
document.body.appendChild(div);
|
|
|
|
this.fontsWaiting --;
|
|
|
|
}
|
|
|
|
|
2011-06-26 08:35:17 +09:00
|
|
|
if (this.fontsWaiting == 0) {
|
|
|
|
console.timeEnd("ensureFonts");
|
|
|
|
}
|
|
|
|
|
2011-06-27 03:55:27 +09:00
|
|
|
// This timeout is necessary right now to make sure the fonts are really
|
|
|
|
// loaded at the point the callbacks are called.
|
|
|
|
setTimeout(function() {
|
|
|
|
// 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]();
|
|
|
|
}
|
2011-06-26 08:35:17 +09:00
|
|
|
this.fontsWaitingCallbacks.length = 0;
|
2011-06-27 03:55:27 +09:00
|
|
|
}
|
|
|
|
}.bind(this), 100);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
ensureFonts: function(data, callback) {
|
|
|
|
var font;
|
|
|
|
var notLoaded = [];
|
|
|
|
for (var i = 0; i < data.length; i++) {
|
|
|
|
font = data[i];
|
|
|
|
if (Fonts[font.name]) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Store only the data on Fonts that is needed later on, such that we
|
|
|
|
// hold track on as lease memory as possible.
|
|
|
|
Fonts[font.name] = {
|
2011-06-27 03:58:22 +09:00
|
|
|
// This is set later on the worker replay. For some fonts, the encoding
|
|
|
|
// is calculated during the conversion process happening on the worker
|
|
|
|
// and therefore is not available right now.
|
|
|
|
// properties: {
|
|
|
|
// encoding: font.properties.encoding
|
|
|
|
// },
|
2011-06-27 03:55:27 +09:00
|
|
|
cache: Object.create(null)
|
|
|
|
};
|
|
|
|
|
|
|
|
// Mark this font to be handled later.
|
|
|
|
notLoaded.push(font);
|
|
|
|
// Increate the number of fonts to wait for.
|
|
|
|
this.fontsWaiting++;
|
|
|
|
}
|
|
|
|
|
2011-06-26 08:35:17 +09:00
|
|
|
console.time("ensureFonts");
|
2011-06-27 03:55:27 +09:00
|
|
|
// 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) {
|
2011-06-26 08:35:17 +09:00
|
|
|
console.log("fonts -> FontWorker");
|
2011-06-27 03:55:27 +09:00
|
|
|
// 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();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2011-06-23 05:54:16 +09:00
|
|
|
function WorkerPDFDoc(canvas) {
|
|
|
|
var timer = null
|
|
|
|
|
|
|
|
this.ctx = canvas.getContext("2d");
|
|
|
|
this.canvas = canvas;
|
2011-06-27 03:44:13 +09:00
|
|
|
this.worker = new Worker('worker/pdf.js');
|
2011-06-27 03:55:27 +09:00
|
|
|
this.fontWorker = new FontWorker();
|
|
|
|
this.waitingForFonts = false;
|
|
|
|
this.waitingForFontsCallback = [];
|
2011-06-23 05:54:16 +09:00
|
|
|
|
|
|
|
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();
|
|
|
|
},
|
|
|
|
|
2011-06-24 06:55:14 +09:00
|
|
|
"$showText": function(y, text) {
|
2011-06-27 03:55:27 +09:00
|
|
|
text = Fonts.charsToUnicode(text);
|
2011-06-23 05:54:16 +09:00
|
|
|
this.translate(currentX, -1 * y);
|
2011-06-24 06:55:14 +09:00
|
|
|
this.fillText(text, 0, 0);
|
2011-06-23 05:54:16 +09:00
|
|
|
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) {
|
2011-06-23 21:36:45 +09:00
|
|
|
throw "Image not found: " + id;
|
2011-06-23 05:54:16 +09:00
|
|
|
}
|
|
|
|
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;
|
2011-06-27 03:55:27 +09:00
|
|
|
},
|
|
|
|
|
|
|
|
"$setFont": function(name) {
|
|
|
|
Fonts.active = name;
|
2011-06-23 05:54:16 +09:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-06-23 20:09:36 +09:00
|
|
|
function renderProxyCanvas(canvas, cmdQueue) {
|
2011-06-23 05:54:16 +09:00
|
|
|
var ctx = canvas.getContext("2d");
|
2011-06-23 20:09:36 +09:00
|
|
|
var cmdQueueLength = cmdQueue.length;
|
|
|
|
for (var i = 0; i < cmdQueueLength; i++) {
|
|
|
|
var opp = cmdQueue[i];
|
2011-06-23 05:54:16 +09:00
|
|
|
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]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2011-06-23 20:25:59 +09:00
|
|
|
* Functions to handle data sent by the WebWorker.
|
2011-06-23 05:54:16 +09:00
|
|
|
*/
|
2011-06-23 20:25:59 +09:00
|
|
|
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.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");
|
2011-06-24 02:43:01 +09:00
|
|
|
var style = 'font-family:"' + data.fontName +
|
|
|
|
'";position: absolute;top:-99999;left:-99999;z-index:-99999';
|
|
|
|
div.setAttribute("style", style);
|
|
|
|
document.body.appendChild(div);
|
2011-06-23 20:25:59 +09:00
|
|
|
},
|
2011-06-27 03:55:27 +09:00
|
|
|
|
|
|
|
"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]();
|
|
|
|
}
|
2011-06-26 08:35:17 +09:00
|
|
|
this.waitingForFontsCallback.length = 0;
|
2011-06-27 03:55:27 +09:00
|
|
|
}.bind(this));
|
|
|
|
},
|
2011-06-23 05:54:16 +09:00
|
|
|
|
2011-06-23 20:25:59 +09:00
|
|
|
"jpeg_stream": function(data) {
|
|
|
|
var img = new Image();
|
2011-06-23 21:36:45 +09:00
|
|
|
img.src = "data:image/jpeg;base64," + window.btoa(data.raw);
|
2011-06-23 20:25:59 +09:00
|
|
|
imagesList[data.id] = img;
|
|
|
|
},
|
|
|
|
|
|
|
|
"canvas_proxy_cmd_queue": function(data) {
|
2011-06-23 05:54:16 +09:00
|
|
|
var id = data.id;
|
2011-06-23 20:09:36 +09:00
|
|
|
var cmdQueue = data.cmdQueue;
|
2011-06-23 05:54:16 +09:00
|
|
|
|
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
|
2011-06-27 03:55:27 +09:00
|
|
|
var renderData = function() {
|
2011-06-24 05:24:41 +09:00
|
|
|
if (id == 0) {
|
2011-06-26 18:46:58 +09:00
|
|
|
console.time("main canvas rendering");
|
2011-06-24 05:24:41 +09:00
|
|
|
var ctx = this.ctx;
|
|
|
|
ctx.save();
|
|
|
|
ctx.fillStyle = "rgb(255, 255, 255)";
|
|
|
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
|
|
ctx.restore();
|
|
|
|
}
|
2011-06-23 20:09:36 +09:00
|
|
|
renderProxyCanvas(canvasList[id], cmdQueue);
|
2011-06-26 18:46:58 +09:00
|
|
|
if (id == 0) {
|
|
|
|
console.timeEnd("main canvas rendering");
|
|
|
|
console.timeEnd(">>> total page display time:");
|
|
|
|
}
|
2011-06-27 03:55:27 +09:00
|
|
|
}.bind(this);
|
|
|
|
|
|
|
|
if (this.waitingForFonts) {
|
2011-06-27 03:58:22 +09:00
|
|
|
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();
|
|
|
|
}
|
2011-06-27 03:55:27 +09:00
|
|
|
} else {
|
|
|
|
renderData();
|
|
|
|
}
|
2011-06-23 05:54:16 +09:00
|
|
|
}
|
2011-06-23 20:25:59 +09:00
|
|
|
}
|
|
|
|
|
2011-06-27 03:55:27 +09:00
|
|
|
// Listen to the WebWorker for data and call actionHandler on it.
|
2011-06-23 20:25:59 +09:00
|
|
|
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;
|
|
|
|
}
|
2011-06-27 03:55:27 +09:00
|
|
|
}.bind(this)
|
2011-06-23 05:54:16 +09:00
|
|
|
}
|
|
|
|
|
2011-06-23 20:25:59 +09:00
|
|
|
WorkerPDFDoc.prototype.open = function(url, callback) {
|
2011-06-23 05:54:16 +09:00
|
|
|
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);
|
2011-06-26 18:46:58 +09:00
|
|
|
console.log("=== start rendering page " + numPage + " ===");
|
|
|
|
console.time(">>> total page display time:");
|
2011-06-23 05:54:16 +09:00
|
|
|
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);
|
|
|
|
}
|