Merge pull request #692 from brendandahl/integration

Web worker integration (disabled by default)
This commit is contained in:
vingtetun 2011-10-21 16:49:25 -07:00
commit 65beea82a5
15 changed files with 1514 additions and 1481 deletions

View File

@ -7,6 +7,8 @@
<script type="text/javascript" src="../../metrics.js"></script>
<script type="text/javascript" src="../../fonts.js"></script>
<script type="text/javascript" src="../../glyphlist.js"></script>
<script type="text/javascript" src="../../worker/message_handler.js"></script>
<script type="text/javascript" src="../../worker/processor_handler.js"></script>
<script type="text/javascript" src="hello.js"></script>
</head>

View File

@ -146,7 +146,19 @@ var FontLoader = {
for (var i = 0; i < fonts.length; i++) {
var font = fonts[i];
// If there is already a fontObj on the font, then it was loaded/attached
// to the page already and we don't have to do anything for this font
// here future.
if (font.fontObj) {
continue;
}
var obj = new Font(font.name, font.file, font.properties);
// Store the fontObj on the font such that `setFont` in CanvasGraphics
// can reuse it later again.
font.fontObj = obj;
objs.push(obj);
var str = '';

1910
pdf.js

File diff suppressed because it is too large Load Diff

View File

@ -63,6 +63,11 @@ function cleanup() {
}
function nextTask() {
// If there is a pdfDoc on the last task executed, destroy it to free memory.
if (task && task.pdfDoc) {
task.pdfDoc.destroy();
delete task.pdfDoc;
}
cleanup();
if (currentTaskIdx == manifest.length) {
@ -81,7 +86,7 @@ function nextTask() {
} catch (e) {
failure = 'load PDF doc : ' + e.toString();
}
task.pageNum = 1;
task.pageNum = task.firstPage || 1;
nextPage(task, failure);
});
}

View File

@ -11,6 +11,8 @@
<script type="text/javascript" src="/charsets.js"></script>
<script type="text/javascript" src="/cidmaps.js"></script>
<script type="text/javascript" src="driver.js"></script>
<script type="text/javascript" src="../worker/message_handler.js"></script>
<script type="text/javascript" src="../worker/processor_handler.js"></script>
</head>
<body>

View File

@ -13,6 +13,8 @@
<script type="text/javascript" src="../metrics.js"></script>
<script type="text/javascript" src="../charsets.js"></script>
<script type="text/javascript" src="../cidmaps.js"></script>
<script type="text/javascript" src="../worker/message_handler.js"></script>
<script type="text/javascript" src="../worker/processor_handler.js"></script>
</head>
<body>

View File

@ -1,45 +0,0 @@
<html>
<head>
<title>Simple pdf.js page worker viewer</title>
<script type="text/javascript" src="../fonts.js"></script>
<script type="text/javascript" src="../glyphlist.js"></script>
<script type="text/javascript" src="../pdf.js"></script>
<script type="text/javascript" src="../worker/client.js"></script>
<script>
var pdfDoc;
window.onload = function webViewerWorkerOnload() {
window.canvas = document.getElementById("canvas");
window.ctx = canvas.getContext("2d");
pdfDoc = new WorkerPDFDoc(window.canvas);
pdfDoc.onChangePage = function webViewerWorkerOnChangePage(numPage) {
document.getElementById("pageNumber").value = numPage;
}
pdfDoc.open("compressed.tracemonkey-pldi-09.pdf", function webViewerWorkerOpen() {
document.getElementById("numPages").innerHTML = "/" + pdfDoc.numPages;
})
}
</script>
<link rel="stylesheet" href="viewer.css"></link>
</head>
<body>
<div id="controls">
<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="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>
</div>
<div id="viewer">
<canvas id="canvas"></canvas>
</div>
</body>
</html>

View File

@ -1,252 +0,0 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
'use strict';
var JpegStreamProxyCounter = 0;
// WebWorker Proxy for JpegStream.
var JpegStreamProxy = (function() {
function constructor(bytes, dict) {
this.id = JpegStreamProxyCounter++;
this.dict = dict;
// Tell the main thread to create an image.
postMessage({
action: 'jpeg_stream',
data: {
id: this.id,
raw: bytesToString(bytes)
}
});
}
constructor.prototype = {
getImage: function() {
return this;
},
getChar: function() {
error('internal error: getChar is not valid on JpegStream');
}
};
return constructor;
})();
// Really simple GradientProxy. There is currently only one active gradient at
// the time, meaning you can't create a gradient, create a second one and then
// use the first one again. As this isn't used in pdf.js right now, it's okay.
function GradientProxy(cmdQueue, x0, y0, x1, y1) {
cmdQueue.push(['$createLinearGradient', [x0, y0, x1, y1]]);
this.addColorStop = function(i, rgba) {
cmdQueue.push(['$addColorStop', [i, rgba]]);
};
}
// Really simple PatternProxy.
var patternProxyCounter = 0;
function PatternProxy(cmdQueue, object, kind) {
this.id = patternProxyCounter++;
if (!(object instanceof CanvasProxy)) {
throw 'unkown type to createPattern';
}
// Flush the object here to ensure it's available on the main thread.
// TODO: Make some kind of dependency management, such that the object
// gets flushed only if needed.
object.flush();
cmdQueue.push(['$createPatternFromCanvas', [this.id, object.id, kind]]);
}
var canvasProxyCounter = 0;
function CanvasProxy(width, height) {
this.id = canvasProxyCounter++;
// The `stack` holds the rendering calls and gets flushed to the main thead.
var cmdQueue = this.cmdQueue = [];
// Dummy context that gets exposed.
var ctx = {};
this.getContext = function(type) {
if (type != '2d') {
throw 'CanvasProxy can only provide a 2d context.';
}
return ctx;
};
// Expose only the minimum of the canvas object - there is no dom to do
// more here.
this.width = width;
this.height = height;
ctx.canvas = this;
// Setup function calls to `ctx`.
var ctxFunc = [
'createRadialGradient',
'arcTo',
'arc',
'fillText',
'strokeText',
'createImageData',
'drawWindow',
'save',
'restore',
'scale',
'rotate',
'translate',
'transform',
'setTransform',
'clearRect',
'fillRect',
'strokeRect',
'beginPath',
'closePath',
'moveTo',
'lineTo',
'quadraticCurveTo',
'bezierCurveTo',
'rect',
'fill',
'stroke',
'clip',
'measureText',
'isPointInPath',
// These functions are necessary to track the rendering currentX state.
// The exact values can be computed on the main thread only, as the
// worker has no idea about text width.
'$setCurrentX',
'$addCurrentX',
'$saveCurrentX',
'$restoreCurrentX',
'$showText',
'$setFont'
];
function buildFuncCall(name) {
return function() {
// console.log("funcCall", name)
cmdQueue.push([name, Array.prototype.slice.call(arguments)]);
};
}
var name;
for (var i = 0; i < ctxFunc.length; i++) {
name = ctxFunc[i];
ctx[name] = buildFuncCall(name);
}
// Some function calls that need more work.
ctx.createPattern = function(object, kind) {
return new PatternProxy(cmdQueue, object, kind);
};
ctx.createLinearGradient = function(x0, y0, x1, y1) {
return new GradientProxy(cmdQueue, x0, y0, x1, y1);
};
ctx.getImageData = function(x, y, w, h) {
return {
width: w,
height: h,
data: Uint8ClampedArray(w * h * 4)
};
};
ctx.putImageData = function(data, x, y, width, height) {
cmdQueue.push(['$putImageData', [data, x, y, width, height]]);
};
ctx.drawImage = function(image, x, y, width, height,
sx, sy, swidth, sheight) {
if (image instanceof CanvasProxy) {
// Send the image/CanvasProxy to the main thread.
image.flush();
cmdQueue.push(['$drawCanvas', [image.id, x, y, sx, sy, swidth, sheight]]);
} else if (image instanceof JpegStreamProxy) {
cmdQueue.push(['$drawImage', [image.id, x, y, sx, sy, swidth, sheight]]);
} else {
throw 'unkown type to drawImage';
}
};
// Setup property access to `ctx`.
var ctxProp = {
// "canvas"
'globalAlpha': '1',
'globalCompositeOperation': 'source-over',
'strokeStyle': '#000000',
'fillStyle': '#000000',
'lineWidth': '1',
'lineCap': 'butt',
'lineJoin': 'miter',
'miterLimit': '10',
'shadowOffsetX': '0',
'shadowOffsetY': '0',
'shadowBlur': '0',
'shadowColor': 'rgba(0, 0, 0, 0)',
'font': '10px sans-serif',
'textAlign': 'start',
'textBaseline': 'alphabetic',
'mozTextStyle': '10px sans-serif',
'mozImageSmoothingEnabled': 'true'
};
function buildGetter(name) {
return function() {
return ctx['$' + name];
};
}
function buildSetter(name) {
return function(value) {
cmdQueue.push(['$', name, value]);
return (ctx['$' + name] = value);
};
}
// Setting the value to `stroke|fillStyle` needs special handling, as it
// might gets an gradient/pattern.
function buildSetterStyle(name) {
return function(value) {
if (value instanceof GradientProxy) {
cmdQueue.push(['$' + name + 'Gradient']);
} else if (value instanceof PatternProxy) {
cmdQueue.push(['$' + name + 'Pattern', [value.id]]);
} else {
cmdQueue.push(['$', name, value]);
return (ctx['$' + name] = value);
}
};
}
for (var name in ctxProp) {
ctx['$' + name] = ctxProp[name];
ctx.__defineGetter__(name, buildGetter(name));
// Special treatment for `fillStyle` and `strokeStyle`: The passed style
// might be a gradient. Need to check for that.
if (name == 'fillStyle' || name == 'strokeStyle') {
ctx.__defineSetter__(name, buildSetterStyle(name));
} else {
ctx.__defineSetter__(name, buildSetter(name));
}
}
}
/**
* Sends the current cmdQueue of the CanvasProxy over to the main thread and
* resets the cmdQueue.
*/
CanvasProxy.prototype.flush = function() {
postMessage({
action: 'canvas_proxy_cmd_queue',
data: {
id: this.id,
cmdQueue: this.cmdQueue,
width: this.width,
height: this.height
}
});
this.cmdQueue.length = 0;
};

View File

@ -1,423 +0,0 @@
/* -*- 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 consoleUtils = (function() {
var consoleTimer = {};
var obj = {};
obj.time = function(name) {
consoleTimer[name] = Date.now();
};
obj.timeEnd = function(name) {
var time = consoleTimer[name];
if (time == null) {
throw 'Unkown timer name ' + name;
}
console.log('Timer:', name, Date.now() - time);
};
return obj;
})();
function FontWorker() {
this.worker = new Worker('../worker/font.js');
this.fontsWaiting = 0;
this.fontsWaitingCallbacks = [];
// Listen to the WebWorker for data and call actionHandler on it.
this.worker.onmessage = function(event) {
var data = event.data;
var actionHandler = this.actionHandler;
if (data.action in actionHandler) {
actionHandler[data.action].call(this, data.data);
} else {
throw 'Unkown action from worker: ' + data.action;
}
}.bind(this);
this.$handleFontLoadedCallback = this.handleFontLoadedCallback.bind(this);
}
FontWorker.prototype = {
handleFontLoadedCallback: function() {
// Decrease the number of fonts wainting to be loaded.
this.fontsWaiting--;
// If all fonts are available now, then call all the callbacks.
if (this.fontsWaiting == 0) {
var callbacks = this.fontsWaitingCallbacks;
for (var i = 0; i < callbacks.length; i++) {
callbacks[i]();
}
this.fontsWaitingCallbacks.length = 0;
}
},
actionHandler: {
'log': function(data) {
console.log.apply(console, data);
},
'fonts': function(data) {
// console.log("got processed fonts from worker", Object.keys(data));
for (var name in data) {
// Update the encoding property.
var font = Fonts.lookup(name);
font.properties = {
encoding: data[name].encoding
};
// Call `Font.prototype.bindDOM` to make the font get loaded
// on the page.
Font.prototype.bindDOM.call(
font,
data[name].str,
// IsLoadedCallback.
this.$handleFontLoadedCallback
);
}
}
},
ensureFonts: function(data, callback) {
var font;
var notLoaded = [];
for (var i = 0; i < data.length; i++) {
font = data[i];
if (Fonts[font.name]) {
continue;
}
// Register the font but don't pass in any real data. The idea is to
// store as less data as possible to reduce memory usage.
Fonts.registerFont(font.name, Object.create(null), Object.create(null));
// Mark this font to be handled later.
notLoaded.push(font);
// Increate the number of fonts to wait for.
this.fontsWaiting++;
}
consoleUtils.time('ensureFonts');
// If there are fonts, that need to get loaded, tell the FontWorker to get
// started and push the callback on the waiting-callback-stack.
if (notLoaded.length != 0) {
console.log('fonts -> FontWorker');
// Send the worker the fonts to work on.
this.worker.postMessage({
action: 'fonts',
data: notLoaded
});
if (callback) {
this.fontsWaitingCallbacks.push(callback);
}
}
// All fonts are present? Well, then just call the callback if there is one.
else {
if (callback) {
callback();
}
}
}
};
function WorkerPDFDoc(canvas) {
var timer = null;
this.ctx = canvas.getContext('2d');
this.canvas = canvas;
this.worker = new Worker('../worker/pdf.js');
this.fontWorker = new FontWorker();
this.waitingForFonts = false;
this.waitingForFontsCallback = [];
this.numPage = 1;
this.numPages = null;
var imagesList = {};
var canvasList = {
0: canvas
};
var patternList = {};
var gradient;
var currentX = 0;
var currentXStack = [];
var ctxSpecial = {
'$setCurrentX': function(value) {
currentX = value;
},
'$addCurrentX': function(value) {
currentX += value;
},
'$saveCurrentX': function() {
currentXStack.push(currentX);
},
'$restoreCurrentX': function() {
currentX = currentXStack.pop();
},
'$showText': function(y, text) {
text = Fonts.charsToUnicode(text);
this.translate(currentX, -1 * y);
this.fillText(text, 0, 0);
currentX += this.measureText(text).width;
},
'$putImageData': function(imageData, x, y) {
var imgData = this.getImageData(0, 0,
imageData.width, imageData.height);
// Store the .data property to avaid property lookups.
var imageRealData = imageData.data;
var imgRealData = imgData.data;
// Copy over the imageData.
var len = imageRealData.length;
while (len--)
imgRealData[len] = imageRealData[len];
this.putImageData(imgData, x, y);
},
'$drawImage': function(id, x, y, sx, sy, swidth, sheight) {
var image = imagesList[id];
if (!image) {
throw 'Image not found: ' + id;
}
this.drawImage(image, x, y, image.width, image.height,
sx, sy, swidth, sheight);
},
'$drawCanvas': function(id, x, y, sx, sy, swidth, sheight) {
var canvas = canvasList[id];
if (!canvas) {
throw 'Canvas not found';
}
if (sheight != null) {
this.drawImage(canvas, x, y, canvas.width, canvas.height,
sx, sy, swidth, sheight);
} else {
this.drawImage(canvas, x, y, canvas.width, canvas.height);
}
},
'$createLinearGradient': function(x0, y0, x1, y1) {
gradient = this.createLinearGradient(x0, y0, x1, y1);
},
'$createPatternFromCanvas': function(patternId, canvasId, kind) {
var canvas = canvasList[canvasId];
if (!canvas) {
throw 'Canvas not found';
}
patternList[patternId] = this.createPattern(canvas, kind);
},
'$addColorStop': function(i, rgba) {
gradient.addColorStop(i, rgba);
},
'$fillStyleGradient': function() {
this.fillStyle = gradient;
},
'$fillStylePattern': function(id) {
var pattern = patternList[id];
if (!pattern) {
throw 'Pattern not found';
}
this.fillStyle = pattern;
},
'$strokeStyleGradient': function() {
this.strokeStyle = gradient;
},
'$strokeStylePattern': function(id) {
var pattern = patternList[id];
if (!pattern) {
throw 'Pattern not found';
}
this.strokeStyle = pattern;
},
'$setFont': function(name, size) {
this.font = size + 'px "' + name + '"';
Fonts.setActive(name, size);
}
};
function renderProxyCanvas(canvas, cmdQueue) {
var ctx = canvas.getContext('2d');
var cmdQueueLength = cmdQueue.length;
for (var i = 0; i < cmdQueueLength; i++) {
var opp = cmdQueue[i];
if (opp[0] == '$') {
ctx[opp[1]] = opp[2];
} else if (opp[0] in ctxSpecial) {
ctxSpecial[opp[0]].apply(ctx, opp[1]);
} else {
ctx[opp[0]].apply(ctx, opp[1]);
}
}
}
/**
* Functions to handle data sent by the WebWorker.
*/
var actionHandler = {
'log': function(data) {
console.log.apply(console, data);
},
'pdf_num_pages': function(data) {
this.numPages = parseInt(data, 10);
if (this.loadCallback) {
this.loadCallback();
}
},
'font': function(data) {
var base64 = window.btoa(data.raw);
// Add the @font-face rule to the document
var url = 'url(data:' + data.mimetype + ';base64,' + base64 + ');';
var rule = ("@font-face { font-family:'" + data.fontName +
"';src:" + url + '}');
var styleSheet = document.styleSheets[0];
styleSheet.insertRule(rule, styleSheet.cssRules.length);
// Just adding the font-face to the DOM doesn't make it load. It
// seems it's loaded once Gecko notices it's used. Therefore,
// add a div on the page using the loaded font.
var div = document.createElement('div');
var style = 'font-family:"' + data.fontName +
'";position: absolute;top:-99999;left:-99999;z-index:-99999';
div.setAttribute('style', style);
document.body.appendChild(div);
},
'setup_page': function(data) {
var size = data.split(',');
var canvas = this.canvas, ctx = this.ctx;
canvas.width = parseInt(size[0], 10);
canvas.height = parseInt(size[1], 10);
},
'fonts': function(data) {
this.waitingForFonts = true;
this.fontWorker.ensureFonts(data, function() {
this.waitingForFonts = false;
var callbacks = this.waitingForFontsCallback;
for (var i = 0; i < callbacks.length; i++) {
callbacks[i]();
}
this.waitingForFontsCallback.length = 0;
}.bind(this));
},
'jpeg_stream': function(data) {
var img = new Image();
img.src = 'data:image/jpeg;base64,' + window.btoa(data.raw);
imagesList[data.id] = img;
},
'canvas_proxy_cmd_queue': function(data) {
var id = data.id;
var cmdQueue = data.cmdQueue;
// Check if there is already a canvas with the given id. If not,
// create a new canvas.
if (!canvasList[id]) {
var newCanvas = document.createElement('canvas');
newCanvas.width = data.width;
newCanvas.height = data.height;
canvasList[id] = newCanvas;
}
var renderData = function() {
if (id == 0) {
consoleUtils.time('main canvas rendering');
var ctx = this.ctx;
ctx.save();
ctx.fillStyle = 'rgb(255, 255, 255)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.restore();
}
renderProxyCanvas(canvasList[id], cmdQueue);
if (id == 0) {
consoleUtils.timeEnd('main canvas rendering');
consoleUtils.timeEnd('>>> total page display time:');
}
}.bind(this);
if (this.waitingForFonts) {
if (id == 0) {
console.log('want to render, but not all fonts are there', id);
this.waitingForFontsCallback.push(renderData);
} else {
// console.log("assume canvas doesn't have fonts", id);
renderData();
}
} else {
renderData();
}
}
};
// Listen to the WebWorker for data and call actionHandler on it.
this.worker.addEventListener('message', function(event) {
var data = event.data;
if (data.action in actionHandler) {
actionHandler[data.action].call(this, data.data);
} else {
throw 'Unkown action from worker: ' + data.action;
}
}.bind(this));
}
WorkerPDFDoc.prototype = {
open: function(url, callback) {
var req = new XMLHttpRequest();
req.open('GET', url);
req.mozResponseType = req.responseType = 'arraybuffer';
req.expected = (document.URL.indexOf('file:') == 0) ? 0 : 200;
req.onreadystatechange = function() {
if (req.readyState == 4 && req.status == req.expected) {
var data = req.mozResponseArrayBuffer || req.mozResponse ||
req.responseArrayBuffer || req.response;
this.loadCallback = callback;
this.worker.postMessage(data);
this.showPage(this.numPage);
}
}.bind(this);
req.send(null);
},
showPage: function(numPage) {
this.numPage = parseInt(numPage, 10);
console.log('=== start rendering page ' + numPage + ' ===');
consoleUtils.time('>>> total page display time:');
this.worker.postMessage(numPage);
if (this.onChangePage) {
this.onChangePage(numPage);
}
},
nextPage: function() {
if (this.numPage != this.numPages)
this.showPage(++this.numPage);
},
prevPage: function() {
if (this.numPage != 1)
this.showPage(--this.numPage);
}
};

View File

@ -8,7 +8,15 @@ var console = {
log: function log() {
var args = Array.prototype.slice.call(arguments);
postMessage({
action: 'log',
action: 'console_log',
data: args
});
},
error: function error() {
var args = Array.prototype.slice.call(arguments);
postMessage({
action: 'console_error',
data: args
});
},

View File

@ -1,67 +0,0 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
'use strict';
importScripts('console.js');
importScripts('../pdf.js');
importScripts('../fonts.js');
importScripts('../glyphlist.js');
function fontDataToString(font) {
// Doing postMessage on objects make them lose their "shape". This adds the
// "shape" for all required objects agains, such that the encoding works as
// expected.
var fontFileDict = new Dict();
fontFileDict.map = font.file.dict.map;
var fontFile = new Stream(font.file.bytes, font.file.start,
font.file.end - font.file.start, fontFileDict);
font.file = new FlateStream(fontFile);
// This will encode the font.
var fontObj = new Font(font.name, font.file, font.properties);
// Create string that is used for css later.
var str = '';
var data = fontObj.data;
var length = data.length;
for (var j = 0; j < length; j++)
str += String.fromCharCode(data[j]);
return {
str: str,
encoding: font.properties.encoding
};
}
/**
* Functions to handle data sent by the MainThread.
*/
var actionHandler = {
'fonts': function(data) {
var fontData;
var result = {};
for (var i = 0; i < data.length; i++) {
fontData = data[i];
result[fontData.name] = fontDataToString(fontData);
}
postMessage({
action: 'fonts',
data: result
});
}
};
// Listen to the MainThread for data and call actionHandler on it.
addEventListener('message', 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;
}
});

46
worker/message_handler.js Normal file
View File

@ -0,0 +1,46 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
'use strict';
function MessageHandler(name, comObj) {
this.name = name;
this.comObj = comObj;
var ah = this.actionHandler = {};
ah['console_log'] = [function(data) {
console.log.apply(console, data);
}];
ah['console_error'] = [function(data) {
console.error.apply(console, data);
}];
comObj.onmessage = function(event) {
var data = event.data;
if (data.action in ah) {
var action = ah[data.action];
action[0].call(action[1], data.data);
} else {
throw 'Unkown action from worker: ' + data.action;
}
};
}
MessageHandler.prototype = {
on: function(actionName, handler, scope) {
var ah = this.actionHandler;
if (ah[actionName]) {
throw "There is already an actionName called '" + actionName + "'";
}
ah[actionName] = [handler, scope];
},
send: function(actionName, data) {
this.comObj.postMessage({
action: actionName,
data: data
});
}
};

View File

@ -1,97 +0,0 @@
/* -*- 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
});
}
};
var consoleUtils = {
time: function(name) {
consoleTimer[name] = Date.now();
},
timeEnd: function(name) {
var time = consoleTimer[name];
if (time == null) {
throw 'Unkown timer name ' + name;
}
console.log('Timer:', name, Date.now() - time);
}
};
//
importScripts('console.js');
importScripts('canvas.js');
importScripts('../pdf.js');
importScripts('../fonts.js');
importScripts('../crypto.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;
addEventListener('message', function(event) {
var data = event.data;
// If there is no pdfDocument yet, then the sent data is the PDFDocument.
if (!pdfDocument) {
pdfDocument = new PDFDoc(data);
postMessage({
action: 'pdf_num_pages',
data: pdfDocument.numPages
});
return;
}
// User requested to render a certain page.
else {
consoleUtils.time('compile');
// Let's try to render the first page...
var page = pdfDocument.getPage(parseInt(data, 10));
var pdfToCssUnitsCoef = 96.0 / 72.0;
var pageWidth = (page.mediaBox[2] - page.mediaBox[0]) * pdfToCssUnitsCoef;
var pageHeight = (page.mediaBox[3] - page.mediaBox[1]) * pdfToCssUnitsCoef;
postMessage({
action: 'setup_page',
data: pageWidth + ',' + pageHeight
});
// Set canvas size.
canvas.width = pageWidth;
canvas.height = pageHeight;
// 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);
consoleUtils.timeEnd('compile');
// Send fonts to the main thread.
consoleUtils.time('fonts');
postMessage({
action: 'fonts',
data: fonts
});
consoleUtils.timeEnd('fonts');
consoleUtils.time('display');
page.display(gfx);
canvas.flush();
consoleUtils.timeEnd('display');
}
});

View File

@ -0,0 +1,19 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
'use strict';
importScripts('console.js');
importScripts('message_handler.js');
importScripts('../pdf.js');
importScripts('../fonts.js');
importScripts('../crypto.js');
importScripts('../glyphlist.js');
importScripts('../metrics.js');
importScripts('processor_handler.js');
// Listen for messages from the main thread.
var pdfDoc = null;
var handler = new MessageHandler('worker_processor', this);
WorkerProcessorHandler.setup(handler);

101
worker/processor_handler.js Normal file
View File

@ -0,0 +1,101 @@
/* -*- 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 WorkerProcessorHandler = {
setup: function(handler) {
var pdfDoc = null;
handler.on('doc', function(data) {
// Create only the model of the PDFDoc, which is enough for
// processing the content of the pdf.
pdfDoc = new PDFDocModel(new Stream(data));
});
handler.on('page_request', function(pageNum) {
pageNum = parseInt(pageNum);
var page = pdfDoc.getPage(pageNum);
// The following code does quite the same as
// Page.prototype.startRendering, but stops at one point and sends the
// result back to the main thread.
var gfx = new CanvasGraphics(null);
var start = Date.now();
var dependency = [];
// Pre compile the pdf page and fetch the fonts/images.
var IRQueue = page.getIRQueue(handler, dependency);
console.log('page=%d - getIRQueue: time=%dms, len=%d', pageNum,
Date.now() - start, IRQueue.fnArray.length);
// Filter the dependecies for fonts.
var fonts = {};
for (var i = 0; i < dependency.length; i++) {
var dep = dependency[i];
if (dep.indexOf('font_') == 0) {
fonts[dep] = true;
}
}
handler.send('page', {
pageNum: pageNum,
IRQueue: IRQueue,
depFonts: Object.keys(fonts)
});
}, this);
handler.on('font', function(data) {
var objId = data[0];
var name = data[1];
var file = data[2];
var properties = data[3];
var font = {
name: name,
file: file,
properties: properties
};
// Some fonts don't have a file, e.g. the build in ones like Arial.
if (file) {
var fontFileDict = new Dict();
fontFileDict.map = file.dict.map;
var fontFile = new Stream(file.bytes, file.start,
file.end - file.start, fontFileDict);
// Check if this is a FlateStream. Otherwise just use the created
// Stream one. This makes complex_ttf_font.pdf work.
var cmf = file.bytes[0];
if ((cmf & 0x0f) == 0x08) {
font.file = new FlateStream(fontFile);
} else {
font.file = fontFile;
}
}
var obj = new Font(font.name, font.file, font.properties);
var str = '';
var data = obj.data;
if (data) {
var length = data.length;
for (var j = 0; j < length; j++)
str += String.fromCharCode(data[j]);
}
obj.str = str;
// Remove the data array form the font object, as it's not needed
// anymore as we sent over the ready str.
delete obj.data;
handler.send('font_ready', [objId, obj]);
});
}
};