Move client code into worker_client.js. Cleanup + comments + 2-space-indention

This commit is contained in:
Julian Viereck 2011-06-22 22:54:16 +02:00
parent 5e02659eb7
commit 405c367ece
4 changed files with 547 additions and 541 deletions

View File

@ -1,23 +1,3 @@
// 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; var JpegStreamProxyCounter = 0;
// WebWorker Proxy for JpegStream. // WebWorker Proxy for JpegStream.
@ -26,22 +6,17 @@ var JpegStreamProxy = (function() {
this.id = JpegStreamProxyCounter++; this.id = JpegStreamProxyCounter++;
this.dict = dict; this.dict = dict;
// create DOM image. // Tell the main thread to create an image.
postMessage("jpeg_stream"); postMessage("jpeg_stream");
postMessage({ postMessage({
id: this.id, id: this.id,
str: bytesToString(bytes) str: bytesToString(bytes)
}); });
// var img = new Image();
// img.src = "data:image/jpeg;base64," + window.btoa(bytesToString(bytes));
// this.domImage = img;
} }
constructor.prototype = { constructor.prototype = {
getImage: function() { getImage: function() {
return this; return this;
// return this.domImage;
}, },
getChar: function() { getChar: function() {
error("internal error: getChar is not valid on JpegStream"); error("internal error: getChar is not valid on JpegStream");
@ -61,6 +36,7 @@ function GradientProxy(stack, x0, y0, x1, y1) {
} }
} }
// Really simple PatternProxy.
var patternProxyCounter = 0; var patternProxyCounter = 0;
function PatternProxy(stack, object, kind) { function PatternProxy(stack, object, kind) {
this.id = patternProxyCounter++; this.id = patternProxyCounter++;
@ -68,6 +44,7 @@ function PatternProxy(stack, object, kind) {
if (!(object instanceof CanvasProxy) ) { if (!(object instanceof CanvasProxy) ) {
throw "unkown type to createPattern"; throw "unkown type to createPattern";
} }
// Flush the object here to ensure it's available on the main thread. // Flush the object here to ensure it's available on the main thread.
// TODO: Make some kind of dependency management, such that the object // TODO: Make some kind of dependency management, such that the object
// gets flushed only if needed. // gets flushed only if needed.
@ -79,9 +56,10 @@ var canvasProxyCounter = 0;
function CanvasProxy(width, height) { function CanvasProxy(width, height) {
this.id = canvasProxyCounter++; this.id = canvasProxyCounter++;
// The `stack` holds the rendering calls and gets flushed to the main thead.
var stack = this.$stack = []; var stack = this.$stack = [];
// Dummy context exposed. // Dummy context that gets exposed.
var ctx = {}; var ctx = {};
this.getContext = function(type) { this.getContext = function(type) {
if (type != "2d") { if (type != "2d") {
@ -96,15 +74,13 @@ function CanvasProxy(width, height) {
this.height = height; this.height = height;
ctx.canvas = this; ctx.canvas = this;
// Setup function calls to `ctx`.
var ctxFunc = [ var ctxFunc = [
"createRadialGradient", "createRadialGradient",
"arcTo", "arcTo",
"arc", "arc",
"fillText", "fillText",
"strokeText", "strokeText",
// "drawImage",
// "getImageData",
// "putImageData",
"createImageData", "createImageData",
"drawWindow", "drawWindow",
"save", "save",
@ -114,8 +90,6 @@ function CanvasProxy(width, height) {
"translate", "translate",
"transform", "transform",
"setTransform", "setTransform",
// "createLinearGradient",
// "createPattern",
"clearRect", "clearRect",
"fillRect", "fillRect",
"strokeRect", "strokeRect",
@ -132,6 +106,9 @@ function CanvasProxy(width, height) {
"measureText", "measureText",
"isPointInPath", "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", "$setCurrentX",
"$addCurrentX", "$addCurrentX",
"$saveCurrentX", "$saveCurrentX",
@ -139,6 +116,20 @@ function CanvasProxy(width, height) {
"$showText" "$showText"
]; ];
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) { ctx.createPattern = function(object, kind) {
return new PatternProxy(stack, object, kind); return new PatternProxy(stack, object, kind);
} }
@ -171,18 +162,7 @@ function CanvasProxy(width, height) {
} }
} }
function buildFuncCall(name) { // Setup property access to `ctx`.
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);
}
var ctxProp = { var ctxProp = {
// "canvas" // "canvas"
"globalAlpha": "1", "globalAlpha": "1",
@ -201,12 +181,7 @@ function CanvasProxy(width, height) {
"textAlign": "start", "textAlign": "start",
"textBaseline": "alphabetic", "textBaseline": "alphabetic",
"mozTextStyle": "10px sans-serif", "mozTextStyle": "10px sans-serif",
"mozImageSmoothingEnabled": "true", "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 buildGetter(name) { function buildGetter(name) {
@ -248,6 +223,10 @@ function CanvasProxy(width, height) {
} }
} }
/**
* Sends the current stack of the CanvasProxy over to the main thread and
* resets the stack.
*/
CanvasProxy.prototype.flush = function() { CanvasProxy.prototype.flush = function() {
postMessage("canvas_proxy_stack"); postMessage("canvas_proxy_stack");
postMessage({ postMessage({

View File

@ -1,295 +1,22 @@
<html> <html>
<head> <head>
<title>Simple pdf.js page viewer worker</title> <title>Simple pdf.js page viewer worker</title>
<script type="text/javascript" src="worker_client.js"></script>
<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.onload = function() {
window.canvas = document.getElementById("canvas"); window.canvas = document.getElementById("canvas");
window.ctx = canvas.getContext("2d"); 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> </script>
<link rel="stylesheet" href="viewer.css"></link> <link rel="stylesheet" href="viewer.css"></link>
@ -301,9 +28,9 @@ window.onload = function() {
<input type="file" style="float: right; margin: auto 32px;" onChange="load(this.value.toString());"></input> <input type="file" style="float: right; margin: auto 32px;" onChange="load(this.value.toString());"></input>
<!-- This only opens supported PDFs from the source path... <!-- This only opens supported PDFs from the source path...
-- Can we use JSONP to overcome the same-origin restrictions? --> -- Can we use JSONP to overcome the same-origin restrictions? -->
<button onclick="prevPage();">Previous</button> <button onclick="pdfDoc.prevPage();">Previous</button>
<button onclick="nextPage();">Next</button> <button onclick="pdfDoc.nextPage();">Next</button>
<input type="text" id="pageNumber" onchange="showPage(this.value);" <input type="text" id="pageNumber" onchange="pdfDoc.showPage(this.value);"
value="1" size="4"></input> value="1" size="4"></input>
<span id="numPages">--</span> <span id="numPages">--</span>
<span id="info"></span> <span id="info"></span>

View File

@ -1,23 +1,5 @@
"use strict"; "use strict";
function log() {
var args = Array.prototype.slice.call(arguments);
postMessage("log");
postMessage(JSON.stringify(args))
}
var console = {
log: log
}
importScripts("canvas_proxy.js");
importScripts("pdf.js");
importScripts("fonts.js");
importScripts("glyphlist.js")
// Use the JpegStreamProxy proxy.
JpegStream = JpegStreamProxy;
var timer = null; var timer = null;
function tic() { function tic() {
timer = Date.now(); timer = Date.now();
@ -28,19 +10,41 @@ function toc(msg) {
timer = null; timer = null;
} }
function log() {
var args = Array.prototype.slice.call(arguments);
postMessage("log");
postMessage(JSON.stringify(args))
}
var console = {
log: log
}
//
importScripts("canvas_proxy.js");
importScripts("pdf.js");
importScripts("fonts.js");
importScripts("glyphlist.js")
// Use the JpegStreamProxy proxy.
JpegStream = JpegStreamProxy;
// Create the WebWorkerProxyCanvas. // Create the WebWorkerProxyCanvas.
var canvas = new CanvasProxy(1224, 1584); var canvas = new CanvasProxy(1224, 1584);
var pageInterval; // Listen for messages from the main thread.
var pdfDocument = null; var pdfDocument = null;
onmessage = function(event) { onmessage = function(event) {
var data = event.data; var data = event.data;
// If there is no pdfDocument yet, then the sent data is the PDFDocument.
if (!pdfDocument) { if (!pdfDocument) {
pdfDocument = new PDFDoc(new Stream(data)); pdfDocument = new PDFDoc(new Stream(data));
postMessage("pdf_num_page"); postMessage("pdf_num_page");
postMessage(pdfDocument.numPages) postMessage(pdfDocument.numPages)
return; return;
} else { }
// User requested to render a certain page.
else {
tic(); tic();
// Let's try to render the first page... // Let's try to render the first page...
@ -66,7 +70,9 @@ onmessage = function(event) {
} }
toc("compiled page"); toc("compiled page");
tic()
page.display(gfx); page.display(gfx);
canvas.flush(); canvas.flush();
toc("displayed page");
} }
} }

294
worker_client.js Normal file
View File

@ -0,0 +1,294 @@
"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);
}