-// var ImageCanvasProxyCounter = 0;
-// function ImageCanvasProxy(width, height) {
-// this.id = ImageCanvasProxyCounter++;
-// this.width = width;
-// this.height = height;
-//
-// // Using `Uint8ClampedArray` seems to be the type of ImageData - at least
-// // Firebug tells me so.
-// this.imgData = {
-// data: Uint8ClampedArray(width * height * 4)
-// };
-// }
-//
-// ImageCanvasProxy.prototype.putImageData = function(imgData) {
-// // this.ctx.putImageData(imgData, 0, 0);
-// }
-//
-// ImageCanvasProxy.prototype.getCanvas = function() {
-// return this;
-// }
var JpegStreamProxyCounter = 0;
// WebWorker Proxy for JpegStream.
var JpegStreamProxy = (function() {
- function constructor(bytes, dict) {
- this.id = JpegStreamProxyCounter++;
- this.dict = dict;
+ function constructor(bytes, dict) {
+ this.id = JpegStreamProxyCounter++;
+ this.dict = dict;
- // create DOM image.
- postMessage("jpeg_stream");
- postMessage({
- id: this.id,
- str: bytesToString(bytes)
- });
-
- // var img = new Image();
- // img.src = "data:image/jpeg;base64," + window.btoa(bytesToString(bytes));
- // this.domImage = img;
+ // Tell the main thread to create an image.
+ postMessage("jpeg_stream");
+ postMessage({
+ id: this.id,
+ str: bytesToString(bytes)
+ });
+ }
+
+ constructor.prototype = {
+ getImage: function() {
+ return this;
+ },
+ getChar: function() {
+ error("internal error: getChar is not valid on JpegStream");
}
+ };
- constructor.prototype = {
- getImage: function() {
- return this;
- // return this.domImage;
- },
- getChar: function() {
- error("internal error: getChar is not valid on JpegStream");
- }
- };
-
- return constructor;
+ 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(stack, x0, y0, x1, y1) {
- stack.push(["$createLinearGradient", [x0, y0, x1, y1]]);
- this.addColorStop = function(i, rgba) {
- stack.push(["$addColorStop", [i, rgba]]);
- }
+ stack.push(["$createLinearGradient", [x0, y0, x1, y1]]);
+ this.addColorStop = function(i, rgba) {
+ stack.push(["$addColorStop", [i, rgba]]);
+ }
}
+// Really simple PatternProxy.
var patternProxyCounter = 0;
function PatternProxy(stack, object, kind) {
- this.id = patternProxyCounter++;
+ 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();
- stack.push(["$createPatternFromCanvas", [this.id, object.id, kind]]);
+ 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();
+ stack.push(["$createPatternFromCanvas", [this.id, object.id, kind]]);
}
var canvasProxyCounter = 0;
function CanvasProxy(width, height) {
- this.id = canvasProxyCounter++;
-
- var stack = this.$stack = [];
-
- // Dummy context 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;
-
- var ctxFunc = [
- "createRadialGradient",
- "arcTo",
- "arc",
- "fillText",
- "strokeText",
- // "drawImage",
- // "getImageData",
- // "putImageData",
- "createImageData",
- "drawWindow",
- "save",
- "restore",
- "scale",
- "rotate",
- "translate",
- "transform",
- "setTransform",
- // "createLinearGradient",
- // "createPattern",
- "clearRect",
- "fillRect",
- "strokeRect",
- "beginPath",
- "closePath",
- "moveTo",
- "lineTo",
- "quadraticCurveTo",
- "bezierCurveTo",
- "rect",
- "fill",
- "stroke",
- "clip",
- "measureText",
- "isPointInPath",
-
- "$setCurrentX",
- "$addCurrentX",
- "$saveCurrentX",
- "$restoreCurrentX",
- "$showText"
- ];
-
- ctx.createPattern = function(object, kind) {
- return new PatternProxy(stack, object, kind);
- }
+ this.id = canvasProxyCounter++;
- ctx.createLinearGradient = function(x0, y0, x1, y1) {
- return new GradientProxy(stack, x0, y0, x1, y1);
- }
+ // The `stack` holds the rendering calls and gets flushed to the main thead.
+ var stack = this.$stack = [];
- ctx.getImageData = function(x, y, w, h) {
- return {
- width: w,
- height: h,
- data: Uint8ClampedArray(w * h * 4)
- };
+ // Dummy context that gets exposed.
+ var ctx = {};
+ this.getContext = function(type) {
+ if (type != "2d") {
+ throw "CanvasProxy can only provide a 2d context.";
}
-
- ctx.putImageData = function(data, x, y, width, height) {
- stack.push(["$putImageData", [data, x, y, width, height]]);
+ 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"
+ ];
+
+ function buildFuncCall(name) {
+ return function() {
+ // console.log("funcCall", name)
+ stack.push([name, Array.prototype.slice.call(arguments)]);
}
-
- 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();
- stack.push(["$drawCanvas", [image.id, x, y, sx, sy, swidth, sheight]]);
- } else if(image instanceof JpegStreamProxy) {
- stack.push(["$drawImage", [image.id, x, y, sx, sy, swidth, sheight]])
- } else {
- throw "unkown type to drawImage";
- }
- }
-
- function buildFuncCall(name) {
- return function() {
- // console.log("funcCall", name)
- stack.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(stack, object, kind);
+ }
+
+ ctx.createLinearGradient = function(x0, y0, x1, y1) {
+ return new GradientProxy(stack, 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) {
+ stack.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();
+ stack.push(["$drawCanvas", [image.id, x, y, sx, sy, swidth, sheight]]);
+ } else if(image instanceof JpegStreamProxy) {
+ stack.push(["$drawImage", [image.id, x, y, sx, sy, swidth, sheight]])
+ } else {
+ throw "unkown type to drawImage";
}
- var name;
- for (var i = 0; i < ctxFunc.length; i++) {
- name = ctxFunc[i];
- ctx[name] = buildFuncCall(name);
+ }
+
+ // 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];
}
+ }
- 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",
- "DRAWWINDOW_DRAW_CARET": "1",
- "DRAWWINDOW_DO_NOT_FLUSH": "2",
- "DRAWWINDOW_DRAW_VIEW": "4",
- "DRAWWINDOW_USE_WIDGET_LAYERS": "8",
- "DRAWWINDOW_ASYNC_DECODE_IMAGES": "16",
+ function buildSetter(name) {
+ return function(value) {
+ stack.push(["$", name, value]);
+ return ctx["$" + name] = value;
}
+ }
- function buildGetter(name) {
- return function() {
- return ctx["$" + name];
- }
- }
+ for (var name in ctxProp) {
+ ctx["$" + name] = ctxProp[name];
+ ctx.__defineGetter__(name, buildGetter(name));
- function buildSetter(name) {
+ // Special treatment for `fillStyle` and `strokeStyle`: The passed style
+ // might be a gradient. Need to check for that.
+ if (name == "fillStyle" || name == "strokeStyle") {
+ function buildSetterStyle(name) {
return function(value) {
+ if (value instanceof GradientProxy) {
+ stack.push(["$" + name + "Gradient"]);
+ } else if (value instanceof PatternProxy) {
+ stack.push(["$" + name + "Pattern", [value.id]]);
+ } else {
stack.push(["$", name, value]);
return ctx["$" + name] = value;
+ }
}
+ }
+ ctx.__defineSetter__(name, buildSetterStyle(name));
+ } else {
+ ctx.__defineSetter__(name, buildSetter(name));
}
-
- 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") {
- function buildSetterStyle(name) {
- return function(value) {
- if (value instanceof GradientProxy) {
- stack.push(["$" + name + "Gradient"]);
- } else if (value instanceof PatternProxy) {
- stack.push(["$" + name + "Pattern", [value.id]]);
- } else {
- stack.push(["$", name, value]);
- return ctx["$" + name] = value;
- }
- }
- }
- ctx.__defineSetter__(name, buildSetterStyle(name));
- } else {
- ctx.__defineSetter__(name, buildSetter(name));
- }
- }
+ }
}
+/**
+* Sends the current stack of the CanvasProxy over to the main thread and
+* resets the stack.
+*/
CanvasProxy.prototype.flush = function() {
- postMessage("canvas_proxy_stack");
- postMessage({
- id: this.id,
- stack: this.$stack,
- width: this.width,
- height: this.height
- });
- this.$stack.length = 0;
+ postMessage("canvas_proxy_stack");
+ postMessage({
+ id: this.id,
+ stack: this.$stack,
+ width: this.width,
+ height: this.height
+ });
+ this.$stack.length = 0;
}
<html>
<head>
<title>Simple pdf.js page viewer worker</title>
+ <script type="text/javascript" src="worker_client.js"></script>
<script>
-var timer = null
-function tic() {
- timer = Date.now();
-}
-
-function toc(msg) {
- console.log(msg + ": " + (Date.now() - timer) + "ms");
-}
-
-var myWorker = new Worker('worker.js');
-var imagesList = {};
-var canvasList = {};
-var patternList = {};
-var gradient;
-
-var currentX = 0;
-var currentXStack = [];
-var special = {
- "$setCurrentX": function(value) {
- currentX = value;
- },
-
- "$addCurrentX": function(value) {
- currentX += value;
- },
-
- "$saveCurrentX": function() {
- currentXStack.push(currentX);
- },
-
- "$restoreCurrentX": function() {
- currentX = currentXStack.pop();
- },
-
- "$showText": function(y, text, uniText) {
- this.translate(currentX, -1 * y);
- this.fillText(uniText, 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";
- }
- 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;
- }
-}
-
-var gStack;
-function renderProxyCanvas(canvas, stack) {
- var ctx = canvas.getContext("2d");
- for (var i = 0; i < stack.length; i++) {
- // for (var i = 0; i < 1000; i++) {
- var opp = stack[i];
- if (opp[0] == "$") {
- ctx[opp[1]] = opp[2];
- } else if (opp[0] in special) {
- special[opp[0]].apply(ctx, opp[1]);
- } else {
- ctx[opp[0]].apply(ctx, opp[1]);
- }
- }
-}
-
-const WAIT = 0;
-const CANVAS_PROXY_STACK = 1;
-const LOG = 2;
-const FONT = 3;
-const PDF_NUM_PAGE = 4;
-const JPEG_STREAM = 5;
-
-var onMessageState = WAIT;
-var fontStr = null;
-var first = true;
-var intervals = [];
-myWorker.onmessage = function(event) {
- var data = event.data;
- // console.log("onMessageRaw", data);
- switch (onMessageState) {
- case WAIT:
- if (typeof data != "string") {
- throw "expecting to get an string";
- }
- switch (data) {
- case "pdf_num_page":
- onMessageState = PDF_NUM_PAGE;
- return;
- case "log":
- onMessageState = LOG;
- return;
- case "canvas_proxy_stack":
- onMessageState = CANVAS_PROXY_STACK;
- return;
- case "font":
- onMessageState = FONT;
- return;
- case "jpeg_stream":
- onMessageState = JPEG_STREAM;
- return;
- default:
- throw "unkown state: " + data
- }
- break;
-
- case JPEG_STREAM:
- var img = new Image();
- img.src = "data:image/jpeg;base64," + window.btoa(data.str);
- imagesList[data.id] = img;
- console.log("got image", data.id)
- break;
-
- case PDF_NUM_PAGE:
- console.log(data);
- maxPages = parseInt(data);
- document.getElementById("numPages").innerHTML = "/" + data;
- onMessageState = WAIT;
- break;
-
- case FONT:
- data = JSON.parse(data);
- var base64 = window.btoa(data.str);
-
- // 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.
- document.getElementById("fonts").innerHTML += "<div style='font-family:" + data.fontName + "'>j</div>";
- console.log("setup font", data.fontName);
-
- onMessageState = WAIT;
- break;
-
- case LOG:
- console.log.apply(console, JSON.parse(data));
- onMessageState = WAIT;
- break;
-
- case CANVAS_PROXY_STACK:
- var id = data.id;
- var stack = data.stack;
- gStack = stack;
-
- // 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;
- }
-
- // There might be fonts that need to get loaded. Shedule the
- // rendering at the end of the event queue ensures this.
- setTimeout(function() {
- if (id == 0) tic();
- renderProxyCanvas(canvasList[id], stack);
- if (id == 0) toc("canvas rendering")
- }, 0);
- onMessageState = WAIT;
- break;
- }
-}
-//
-// myWorker.postMessage(array);
-
-var currentPage = 1;
-var maxPages = 1;
-function showPage(num) {
- ctx.save();
- ctx.fillStyle = "rgb(255, 255, 255)";
- ctx.fillRect(0, 0, canvas.width, canvas.height);
- ctx.restore();
- console.log("worker: page=" + num)
- document.getElementById('pageNumber').value = num;
- currentPage = parseInt(num);
- myWorker.postMessage(num);
-}
-
-function open(url) {
- document.title = url;
- 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;
- myWorker.postMessage(data);
- showPage("2");
- }
- };
- req.send(null);
-}
-
-function nextPage() {
- if (currentPage == maxPages) return;
- currentPage++;
- showPage(currentPage);
-}
-
-function prevPage() {
- if (currentPage == 1) return;
- currentPage--;
- showPage(currentPage);
-}
+var pdfDoc;
window.onload = function() {
window.canvas = document.getElementById("canvas");
window.ctx = canvas.getContext("2d");
- canvasList[0] = window.canvas;
- open("compressed.tracemonkey-pldi-09.pdf");
+
+ pdfDoc = new WorkerPDFDoc(window.canvas);
+ pdfDoc.onChangePage = function(numPage) {
+ document.getElementById("pageNumber").value = numPage;
+ }
+ pdfDoc.open("compressed.tracemonkey-pldi-09.pdf", function() {
+ document.getElementById("numPages").innerHTML = "/" + pdfDoc.numPages;
+ })
}
</script>
<link rel="stylesheet" href="viewer.css"></link>
<input type="file" style="float: right; margin: auto 32px;" onChange="load(this.value.toString());"></input>
<!-- This only opens supported PDFs from the source path...
-- Can we use JSONP to overcome the same-origin restrictions? -->
- <button onclick="prevPage();">Previous</button>
- <button onclick="nextPage();">Next</button>
- <input type="text" id="pageNumber" onchange="showPage(this.value);"
+ <button onclick="pdfDoc.prevPage();">Previous</button>
+ <button onclick="pdfDoc.nextPage();">Next</button>
+ <input type="text" id="pageNumber" onchange="pdfDoc.showPage(this.value);"
value="1" size="4"></input>
<span id="numPages">--</span>
<span id="info"></span>
"use strict";
+var timer = null;
+function tic() {
+ timer = Date.now();
+}
+
+function toc(msg) {
+ log(msg + ": " + (Date.now() - timer) + "ms");
+ timer = null;
+}
+
function log() {
- var args = Array.prototype.slice.call(arguments);
- postMessage("log");
- postMessage(JSON.stringify(args))
+ var args = Array.prototype.slice.call(arguments);
+ postMessage("log");
+ postMessage(JSON.stringify(args))
}
var console = {
- log: log
+ log: log
}
+//
importScripts("canvas_proxy.js");
importScripts("pdf.js");
importScripts("fonts.js");
// Use the JpegStreamProxy proxy.
JpegStream = JpegStreamProxy;
-var timer = null;
-function tic() {
- timer = Date.now();
-}
-
-function toc(msg) {
- log(msg + ": " + (Date.now() - timer) + "ms");
- timer = null;
-}
-
// Create the WebWorkerProxyCanvas.
var canvas = new CanvasProxy(1224, 1584);
-var pageInterval;
+// Listen for messages from the main thread.
var pdfDocument = null;
onmessage = function(event) {
- var data = event.data;
- if (!pdfDocument) {
- pdfDocument = new PDFDoc(new Stream(data));
- postMessage("pdf_num_page");
- postMessage(pdfDocument.numPages)
- return;
- } else {
- tic();
+ var data = event.data;
+ // If there is no pdfDocument yet, then the sent data is the PDFDocument.
+ if (!pdfDocument) {
+ pdfDocument = new PDFDoc(new Stream(data));
+ postMessage("pdf_num_page");
+ postMessage(pdfDocument.numPages)
+ return;
+ }
+ // User requested to render a certain page.
+ else {
+ tic();
- // Let's try to render the first page...
- var page = pdfDocument.getPage(parseInt(data));
+ // Let's try to render the first page...
+ var page = pdfDocument.getPage(parseInt(data));
- // page.compile will collect all fonts for us, once we have loaded them
- // we can trigger the actual page rendering with page.display
- var fonts = [];
- var gfx = new CanvasGraphics(canvas.getContext("2d"), CanvasProxy);
- page.compile(gfx, fonts);
+ // page.compile will collect all fonts for us, once we have loaded them
+ // we can trigger the actual page rendering with page.display
+ var fonts = [];
+ var gfx = new CanvasGraphics(canvas.getContext("2d"), CanvasProxy);
+ page.compile(gfx, fonts);
- // Inspect fonts and translate the missing one.
- var count = fonts.length;
- for (var i = 0; i < count; i++) {
- var font = fonts[i];
- if (Fonts[font.name]) {
- fontsReady = fontsReady && !Fonts[font.name].loading;
- continue;
- }
+ // Inspect fonts and translate the missing one.
+ var count = fonts.length;
+ for (var i = 0; i < count; i++) {
+ var font = fonts[i];
+ if (Fonts[font.name]) {
+ fontsReady = fontsReady && !Fonts[font.name].loading;
+ continue;
+ }
- // This "builds" the font and sents it over to the main thread.
- new Font(font.name, font.file, font.properties);
- }
- toc("compiled page");
-
- page.display(gfx);
- canvas.flush();
+ // This "builds" the font and sents it over to the main thread.
+ new Font(font.name, font.file, font.properties);
}
+ toc("compiled page");
+
+ tic()
+ page.display(gfx);
+ canvas.flush();
+ toc("displayed page");
+ }
}
--- /dev/null
+"use strict";
+
+function WorkerPDFDoc(canvas) {
+ var timer = null
+ function tic() {
+ timer = Date.now();
+ }
+
+ function toc(msg) {
+ console.log(msg + ": " + (Date.now() - timer) + "ms");
+ }
+
+ this.ctx = canvas.getContext("2d");
+ this.canvas = canvas;
+ this.worker = new Worker('worker.js');
+
+ 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, uniText) {
+ this.translate(currentX, -1 * y);
+ this.fillText(uniText, 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";
+ }
+ 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;
+ }
+ }
+
+ function renderProxyCanvas(canvas, stack) {
+ var ctx = canvas.getContext("2d");
+ for (var i = 0; i < stack.length; i++) {
+ var opp = stack[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]);
+ }
+ }
+ }
+
+ /**
+ * onMessage state machine.
+ */
+ const WAIT = 0;
+ const CANVAS_PROXY_STACK = 1;
+ const LOG = 2;
+ const FONT = 3;
+ const PDF_NUM_PAGE = 4;
+ const JPEG_STREAM = 5;
+
+ var onMessageState = WAIT;
+ this.worker.onmessage = function(event) {
+ var data = event.data;
+ // console.log("onMessageRaw", data);
+ switch (onMessageState) {
+ case WAIT:
+ if (typeof data != "string") {
+ throw "expecting to get an string";
+ }
+ switch (data) {
+ case "pdf_num_page":
+ onMessageState = PDF_NUM_PAGE;
+ return;
+
+ case "log":
+ onMessageState = LOG;
+ return;
+
+ case "canvas_proxy_stack":
+ onMessageState = CANVAS_PROXY_STACK;
+ return;
+
+ case "font":
+ onMessageState = FONT;
+ return;
+
+ case "jpeg_stream":
+ onMessageState = JPEG_STREAM;
+ return;
+
+ default:
+ throw "unkown state: " + data
+ }
+ break;
+
+ case JPEG_STREAM:
+ var img = new Image();
+ img.src = "data:image/jpeg;base64," + window.btoa(data.str);
+ imagesList[data.id] = img;
+ console.log("got image", data.id)
+ break;
+
+ case PDF_NUM_PAGE:
+ this.numPages = parseInt(data);
+ if (this.loadCallback) {
+ this.loadCallback();
+ }
+ onMessageState = WAIT;
+ break;
+
+ case FONT:
+ data = JSON.parse(data);
+ var base64 = window.btoa(data.str);
+
+ // 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.
+ document.getElementById("fonts").innerHTML += "<div style='font-family:" + data.fontName + "'>j</div>";
+
+ onMessageState = WAIT;
+ break;
+
+ case LOG:
+ console.log.apply(console, JSON.parse(data));
+ onMessageState = WAIT;
+ break;
+
+ case CANVAS_PROXY_STACK:
+ var id = data.id;
+ var stack = data.stack;
+
+ // 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;
+ }
+
+ // There might be fonts that need to get loaded. Shedule the
+ // rendering at the end of the event queue ensures this.
+ setTimeout(function() {
+ if (id == 0) tic();
+ renderProxyCanvas(canvasList[id], stack);
+ if (id == 0) toc("canvas rendering")
+ }, 0);
+ onMessageState = WAIT;
+ break;
+ }
+ }.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) {
+ var ctx = this.ctx;
+ ctx.save();
+ ctx.fillStyle = "rgb(255, 255, 255)";
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
+ ctx.restore();
+
+ this.numPage = parseInt(numPage);
+ 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);
+}