+++ /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"
- ];
-
- 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";
-
-var consoleTimer = {};
-var console = {
- log: function log() {
- var args = Array.prototype.slice.call(arguments);
- postMessage({
- action: "log",
- data: args
- });
- },
-
- time: function(name) {
- consoleTimer[name] = Date.now();
- },
-
- timeEnd: function(name) {
- var time = consoleTimer[name];
- if (time == null) {
- throw "Unkown timer name " + name;
- }
- this.log("Timer:", name, Date.now() - time);
- }
-}
-
-//
-importScripts("canvas_proxy.js");
-importScripts("pdf.js");
-importScripts("fonts.js");
-importScripts("glyphlist.js")
-
-// Use the JpegStreamProxy proxy.
-JpegStream = JpegStreamProxy;
-
-// Create the WebWorkerProxyCanvas.
-var canvas = new CanvasProxy(1224, 1584);
-
-// Listen for messages from the main thread.
-var pdfDocument = null;
-onmessage = function(event) {
- 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({
- action: "pdf_num_pages",
- data: pdfDocument.numPages
- });
- return;
- }
- // User requested to render a certain page.
- else {
- console.time("compile");
-
- // 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);
- console.timeEnd("compile");
-
- console.time("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;
- }
-
- // This "builds" the font and sents it over to the main thread.
- new Font(font.name, font.file, font.properties);
- }
- console.timeEnd("fonts");
-
- console.time("display");
- page.display(gfx);
- canvas.flush();
- console.timeEnd("display");
- }
-}
<html>
<head>
<title>Simple pdf.js page worker viewer</title>
- <script type="text/javascript" src="worker_client.js"></script>
+ <script type="text/javascript" src="worker/client.js"></script>
<script>
--- /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"
+ ];
+
+ 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 WorkerPDFDoc(canvas) {
+ var timer = null
+
+ this.ctx = canvas.getContext("2d");
+ this.canvas = canvas;
+ this.worker = new Worker('worker/pdf.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) {
+ 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;
+ }
+ }
+
+ 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.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);
+ },
+
+ "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;
+ }
+
+ // 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) {
+ console.time("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("canvas rendering")
+ }, 0, this);
+ }
+ }
+
+ // List 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;
+ }
+ }
+}
+
+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);
+ 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);
+}
--- /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 consoleTimer = {};
+var console = {
+ log: function log() {
+ var args = Array.prototype.slice.call(arguments);
+ postMessage({
+ action: "log",
+ data: args
+ });
+ },
+
+ time: function(name) {
+ consoleTimer[name] = Date.now();
+ },
+
+ timeEnd: function(name) {
+ var time = consoleTimer[name];
+ if (time == null) {
+ throw "Unkown timer name " + name;
+ }
+ this.log("Timer:", name, Date.now() - time);
+ }
+}
+
+//
+importScripts("canvas.js");
+importScripts("../pdf.js");
+importScripts("../fonts.js");
+importScripts("../glyphlist.js")
+
+// Use the JpegStreamProxy proxy.
+JpegStream = JpegStreamProxy;
+
+// Create the WebWorkerProxyCanvas.
+var canvas = new CanvasProxy(1224, 1584);
+
+// Listen for messages from the main thread.
+var pdfDocument = null;
+onmessage = function(event) {
+ 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({
+ action: "pdf_num_pages",
+ data: pdfDocument.numPages
+ });
+ return;
+ }
+ // User requested to render a certain page.
+ else {
+ console.time("compile");
+
+ // 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);
+ console.timeEnd("compile");
+
+ console.time("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;
+ }
+
+ // This "builds" the font and sents it over to the main thread.
+ new Font(font.name, font.file, font.properties);
+ }
+ console.timeEnd("fonts");
+
+ console.time("display");
+ page.display(gfx);
+ canvas.flush();
+ console.timeEnd("display");
+ }
+}
+++ /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 WorkerPDFDoc(canvas) {
- var timer = null
-
- this.ctx = canvas.getContext("2d");
- this.canvas = canvas;
- this.worker = new Worker('pdf_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) {
- 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;
- }
- }
-
- 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.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);
- },
-
- "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;
- }
-
- // 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) {
- console.time("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("canvas rendering")
- }, 0, this);
- }
- }
-
- // List 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;
- }
- }
-}
-
-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);
- 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);
-}