15 changed files with 1513 additions and 1480 deletions
@ -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> |
|
||||||
|
|
@ -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; |
|
||||||
}; |
|
@ -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); |
|
||||||
} |
|
||||||
}; |
|
@ -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; |
|
||||||
} |
|
||||||
}); |
|
||||||
|
|
@ -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 |
||||||
|
}); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
@ -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'); |
|
||||||
} |
|
||||||
}); |
|
@ -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); |
@ -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]); |
||||||
|
}); |
||||||
|
} |
||||||
|
}; |
Loading…
Reference in new issue