Merge branch 'master' into faxstream

Conflicts:
	pdf.js
This commit is contained in:
sbarman 2011-06-24 08:01:55 -07:00
commit 1ea51ed62d
26 changed files with 2332 additions and 811 deletions

250
canvas_proxy.js Normal file
View File

@ -0,0 +1,250 @@
/* -*- 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;
}

View File

@ -135,15 +135,28 @@ var Font = (function () {
break;
}
var data = this.font;
Fonts[name] = {
data: this.font,
data: data,
properties: properties,
loading: true,
cache: Object.create(null)
}
// Attach the font to the document
this.bind();
// Convert data to a string.
var dataStr = "";
var length = data.length;
for (var i = 0; i < length; ++i)
dataStr += String.fromCharCode(data[i]);
// Attach the font to the document. If this script is runnig in a worker,
// call `bindWorker`, which sends stuff over to the main thread.
if (typeof window != "undefined") {
this.bindDOM(dataStr);
} else {
this.bindWorker(dataStr);
}
};
function stringToArray(str) {
@ -755,12 +768,21 @@ var Font = (function () {
return fontData;
},
bind: function font_bind() {
var data = this.font;
bindWorker: function font_bind_worker(dataStr) {
postMessage({
action: "font",
data: {
raw: dataStr,
fontName: this.name,
mimetype: this.mimetype
}
});
},
bindDOM: function font_bind_dom(dataStr) {
var fontName = this.name;
/** Hack begin */
// Actually there is not event when a font has finished downloading so
// the following code are a dirty hack to 'guess' when a font is ready
var canvas = document.createElement("canvas");
@ -774,7 +796,7 @@ var Font = (function () {
// Get the font size canvas think it will be for 'spaces'
var ctx = canvas.getContext("2d");
ctx.font = "bold italic 20px " + fontName + ", Symbol, Arial";
var testString = " ";
var testString = " ";
// When debugging use the characters provided by the charsets to visually
// see what's happening instead of 'spaces'
@ -831,13 +853,8 @@ var Font = (function () {
/** Hack end */
// Get the base64 encoding of the binary font data
var str = "";
var length = data.length;
for (var i = 0; i < length; ++i)
str += String.fromCharCode(data[i]);
var base64 = window.btoa(str);
// Convert the data string and add it to the page.
var base64 = window.btoa(dataStr);
// Add the @font-face rule to the document
var url = "url(data:" + this.mimetype + ";base64," + base64 + ");";

View File

@ -1,197 +0,0 @@
/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- /
/* vim: set shiftwidth=4 tabstop=8 autoindent cindent expandtab: */
body {
background-color: #929292;
font-family: 'Lucida Grande', 'Lucida Sans Unicode', Helvetica, Arial, Verdana, sans-serif;
margin: 0px;
padding: 0px;
}
canvas {
box-shadow: 0px 4px 10px #000;
-moz-box-shadow: 0px 4px 10px #000;
-webkit-box-shadow: 0px 4px 10px #000;
}
span {
font-size: 0.8em;
}
.control {
display: inline-block;
float: left;
margin: 0px 20px 0px 0px;
padding: 0px 4px 0px 0px;
}
.control > input {
float: left;
border: 1px solid #4d4d4d;
height: 20px;
padding: 0px;
margin: 0px 2px 0px 0px;
border-radius: 4px;
-moz-border-radius: 4px;
-webkit-border-radius: 4px;
box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25);
-moz-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25);
-webkit-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25);
}
.control > select {
float: left;
border: 1px solid #4d4d4d;
height: 22px;
padding: 2px 0px 0px;
margin: 0px 0px 1px;
border-radius: 4px;
-moz-border-radius: 4px;
-webkit-border-radius: 4px;
box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25);
-moz-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25);
-webkit-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25);
}
.control > span {
cursor: default;
float: left;
height: 18px;
margin: 5px 2px 0px;
padding: 0px;
user-select: none;
-moz-user-select: none;
-webkit-user-select: none;
}
.control .label {
clear: both;
float: left;
font-size: 0.65em;
margin: 2px 0px 0px;
position: relative;
text-align: center;
width: 100%;
}
.page {
width: 816px;
height: 1056px;
margin: 10px auto;
}
#controls {
background-color: #eee;
border-bottom: 1px solid #666;
padding: 4px 0px 0px 8px;
position: fixed;
left: 0px;
top: 0px;
height: 40px;
width: 100%;
box-shadow: 0px 2px 8px #000;
-moz-box-shadow: 0px 2px 8px #000;
-webkit-box-shadow: 0px 2px 8px #000;
}
#controls input {
user-select: text;
-moz-user-select: text;
-webkit-user-select: text;
}
#previousPageButton {
background: url('images/buttons.png') no-repeat 0px -23px;
cursor: default;
display: inline-block;
float: left;
margin: 0px;
width: 28px;
height: 23px;
}
#previousPageButton.down {
background: url('images/buttons.png') no-repeat 0px -46px;
}
#previousPageButton.disabled {
background: url('images/buttons.png') no-repeat 0px 0px;
}
#nextPageButton {
background: url('images/buttons.png') no-repeat -28px -23px;
cursor: default;
display: inline-block;
float: left;
margin: 0px;
width: 28px;
height: 23px;
}
#nextPageButton.down {
background: url('images/buttons.png') no-repeat -28px -46px;
}
#nextPageButton.disabled {
background: url('images/buttons.png') no-repeat -28px 0px;
}
#openFileButton {
background: url('images/buttons.png') no-repeat -56px -23px;
cursor: default;
display: inline-block;
float: left;
margin: 0px 0px 0px 3px;
width: 29px;
height: 23px;
}
#openFileButton.down {
background: url('images/buttons.png') no-repeat -56px -46px;
}
#openFileButton.disabled {
background: url('images/buttons.png') no-repeat -56px 0px;
}
#fileInput {
display: none;
}
#pageNumber {
text-align: right;
}
#sidebar {
background-color: rgba(0, 0, 0, 0.8);
position: fixed;
width: 150px;
top: 62px;
bottom: 18px;
border-top-right-radius: 8px;
border-bottom-right-radius: 8px;
-moz-border-radius-topright: 8px;
-moz-border-radius-bottomright: 8px;
-webkit-border-top-right-radius: 8px;
-webkit-border-bottom-right-radius: 8px;
}
#sidebarScrollView {
position: absolute;
overflow: hidden;
overflow-y: auto;
top: 40px;
right: 10px;
bottom: 10px;
left: 10px;
}
#sidebarContentView {
height: auto;
width: 100px;
}
#viewer {
margin: 44px 0px 0px;
padding: 8px 0px;
}

View File

@ -1,51 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>pdf.js Multi-Page Viewer</title>
<meta http-equiv="Content-type" content="text/html;charset=UTF-8"/>
<link rel="stylesheet" href="multi-page-viewer.css" type="text/css" media="screen"/>
<script type="text/javascript" src="pdf.js"></script>
<script type="text/javascript" src="fonts.js"></script>
<script type="text/javascript" src="glyphlist.js"></script>
<script type="text/javascript" src="multi-page-viewer.js"></script>
</head>
<body>
<div id="controls">
<span class="control">
<span id="previousPageButton" class="disabled"></span>
<span id="nextPageButton" class="disabled"></span>
<span class="label">Previous/Next</span>
</span>
<span class="control">
<input type="text" id="pageNumber" value="1" size="2"/>
<span>/</span>
<span id="numPages">--</span>
<span class="label">Page Number</span>
</span>
<span class="control">
<select id="scaleSelect">
<option value="50">50%</option>
<option value="75">75%</option>
<option value="100" selected="selected">100%</option>
<option value="125">125%</option>
<option value="150">150%</option>
<option value="200">200%</option>
</select>
<span class="label">Zoom</span>
</span>
<span class="control">
<span id="openFileButton"></span>
<input type="file" id="fileInput"/>
<span class="label">Open File</span>
</span>
</div>
<!--<div id="sidebar">
<div id="sidebarScrollView">
<div id="sidebarContentView">
</div>
</div>
</div>-->
<div id="viewer"></div>
</body>
</html>

View File

@ -1,466 +0,0 @@
/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- /
/* vim: set shiftwidth=4 tabstop=8 autoindent cindent expandtab: */
"use strict";
var PDFViewer = {
queryParams: {},
element: null,
previousPageButton: null,
nextPageButton: null,
pageNumberInput: null,
scaleSelect: null,
fileInput: null,
willJumpToPage: false,
pdf: null,
url: 'compressed.tracemonkey-pldi-09.pdf',
pageNumber: 1,
numberOfPages: 1,
scale: 1.0,
pageWidth: function() {
return 816 * PDFViewer.scale;
},
pageHeight: function() {
return 1056 * PDFViewer.scale;
},
lastPagesDrawn: [],
visiblePages: function() {
var pageHeight = PDFViewer.pageHeight() + 20; // Add 20 for the margins.
var windowTop = window.pageYOffset;
var windowBottom = window.pageYOffset + window.innerHeight;
var pageStartIndex = Math.floor(windowTop / pageHeight);
var pageStopIndex = Math.ceil(windowBottom / pageHeight);
var pages = [];
for (var i = pageStartIndex; i <= pageStopIndex; i++) {
pages.push(i + 1);
}
return pages;
},
createPage: function(num) {
var anchor = document.createElement('a');
anchor.name = '' + num;
var div = document.createElement('div');
div.id = 'pageContainer' + num;
div.className = 'page';
div.style.width = PDFViewer.pageWidth() + 'px';
div.style.height = PDFViewer.pageHeight() + 'px';
PDFViewer.element.appendChild(anchor);
PDFViewer.element.appendChild(div);
},
removePage: function(num) {
var div = document.getElementById('pageContainer' + num);
if (div) {
while (div.hasChildNodes()) {
div.removeChild(div.firstChild);
}
}
},
drawPage: function(num) {
if (!PDFViewer.pdf) {
return;
}
var div = document.getElementById('pageContainer' + num);
var canvas = document.createElement('canvas');
if (div && !div.hasChildNodes()) {
div.appendChild(canvas);
var page = PDFViewer.pdf.getPage(num);
canvas.id = 'page' + num;
canvas.mozOpaque = true;
// Canvas dimensions must be specified in CSS pixels. CSS pixels
// are always 96 dpi. These dimensions are 8.5in x 11in at 96dpi.
canvas.width = PDFViewer.pageWidth();
canvas.height = PDFViewer.pageHeight();
var ctx = canvas.getContext('2d');
ctx.save();
ctx.fillStyle = 'rgb(255, 255, 255)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.restore();
var gfx = new CanvasGraphics(ctx);
var fonts = [];
// page.compile will collect all fonts for us, once we have loaded them
// we can trigger the actual page rendering with page.display
page.compile(gfx, fonts);
var areFontsReady = true;
// Inspect fonts and translate the missing one
var fontCount = fonts.length;
for (var i = 0; i < fontCount; i++) {
var font = fonts[i];
if (Fonts[font.name]) {
areFontsReady = areFontsReady && !Fonts[font.name].loading;
continue;
}
new Font(font.name, font.file, font.properties);
areFontsReady = false;
}
var pageInterval;
var delayLoadFont = function() {
for (var i = 0; i < fontCount; i++) {
if (Fonts[font.name].loading) {
return;
}
}
clearInterval(pageInterval);
while (div.hasChildNodes()) {
div.removeChild(div.firstChild);
}
PDFViewer.drawPage(num);
}
if (!areFontsReady) {
pageInterval = setInterval(delayLoadFont, 10);
return;
}
page.display(gfx);
}
},
changeScale: function(num) {
while (PDFViewer.element.hasChildNodes()) {
PDFViewer.element.removeChild(PDFViewer.element.firstChild);
}
PDFViewer.scale = num / 100;
var i;
if (PDFViewer.pdf) {
for (i = 1; i <= PDFViewer.numberOfPages; i++) {
PDFViewer.createPage(i);
}
if (PDFViewer.numberOfPages > 0) {
PDFViewer.drawPage(1);
}
}
for (i = 0; i < PDFViewer.scaleSelect.childNodes; i++) {
var option = PDFViewer.scaleSelect.childNodes[i];
if (option.value == num) {
if (!option.selected) {
option.selected = 'selected';
}
} else {
if (option.selected) {
option.removeAttribute('selected');
}
}
}
PDFViewer.scaleSelect.value = Math.floor(PDFViewer.scale * 100) + '%';
},
goToPage: function(num) {
if (1 <= num && num <= PDFViewer.numberOfPages) {
PDFViewer.pageNumber = num;
PDFViewer.pageNumberInput.value = PDFViewer.pageNumber;
PDFViewer.willJumpToPage = true;
document.location.hash = PDFViewer.pageNumber;
PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ?
'disabled' : '';
PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ?
'disabled' : '';
}
},
goToPreviousPage: function() {
if (PDFViewer.pageNumber > 1) {
PDFViewer.goToPage(--PDFViewer.pageNumber);
}
},
goToNextPage: function() {
if (PDFViewer.pageNumber < PDFViewer.numberOfPages) {
PDFViewer.goToPage(++PDFViewer.pageNumber);
}
},
openURL: function(url) {
PDFViewer.url = 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;
PDFViewer.readPDF(data);
}
};
req.send(null);
},
readPDF: function(data) {
while (PDFViewer.element.hasChildNodes()) {
PDFViewer.element.removeChild(PDFViewer.element.firstChild);
}
PDFViewer.pdf = new PDFDoc(new Stream(data));
PDFViewer.numberOfPages = PDFViewer.pdf.numPages;
document.getElementById('numPages').innerHTML = PDFViewer.numberOfPages.toString();
for (var i = 1; i <= PDFViewer.numberOfPages; i++) {
PDFViewer.createPage(i);
}
if (PDFViewer.numberOfPages > 0) {
PDFViewer.drawPage(1);
document.location.hash = 1;
}
PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ?
'disabled' : '';
PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ?
'disabled' : '';
}
};
window.onload = function() {
// Parse the URL query parameters into a cached object.
PDFViewer.queryParams = function() {
var qs = window.location.search.substring(1);
var kvs = qs.split('&');
var params = {};
for (var i = 0; i < kvs.length; ++i) {
var kv = kvs[i].split('=');
params[unescape(kv[0])] = unescape(kv[1]);
}
return params;
}();
PDFViewer.element = document.getElementById('viewer');
PDFViewer.pageNumberInput = document.getElementById('pageNumber');
PDFViewer.pageNumberInput.onkeydown = function(evt) {
var charCode = evt.charCode || evt.keyCode;
// Up arrow key.
if (charCode === 38) {
PDFViewer.goToNextPage();
this.select();
}
// Down arrow key.
else if (charCode === 40) {
PDFViewer.goToPreviousPage();
this.select();
}
// All other non-numeric keys (excluding Left arrow, Right arrow,
// Backspace, and Delete keys).
else if ((charCode < 48 || charCode > 57) &&
charCode !== 8 && // Backspace
charCode !== 46 && // Delete
charCode !== 37 && // Left arrow
charCode !== 39 // Right arrow
) {
return false;
}
return true;
};
PDFViewer.pageNumberInput.onkeyup = function(evt) {
var charCode = evt.charCode || evt.keyCode;
// All numeric keys, Backspace, and Delete.
if ((charCode >= 48 && charCode <= 57) ||
charCode === 8 || // Backspace
charCode === 46 // Delete
) {
PDFViewer.goToPage(this.value);
}
this.focus();
};
PDFViewer.previousPageButton = document.getElementById('previousPageButton');
PDFViewer.previousPageButton.onclick = function(evt) {
if (this.className.indexOf('disabled') === -1) {
PDFViewer.goToPreviousPage();
}
};
PDFViewer.previousPageButton.onmousedown = function(evt) {
if (this.className.indexOf('disabled') === -1) {
this.className = 'down';
}
};
PDFViewer.previousPageButton.onmouseup = function(evt) {
this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
};
PDFViewer.previousPageButton.onmouseout = function(evt) {
this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
};
PDFViewer.nextPageButton = document.getElementById('nextPageButton');
PDFViewer.nextPageButton.onclick = function(evt) {
if (this.className.indexOf('disabled') === -1) {
PDFViewer.goToNextPage();
}
};
PDFViewer.nextPageButton.onmousedown = function(evt) {
if (this.className.indexOf('disabled') === -1) {
this.className = 'down';
}
};
PDFViewer.nextPageButton.onmouseup = function(evt) {
this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
};
PDFViewer.nextPageButton.onmouseout = function(evt) {
this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
};
PDFViewer.scaleSelect = document.getElementById('scaleSelect');
PDFViewer.scaleSelect.onchange = function(evt) {
PDFViewer.changeScale(parseInt(this.value));
};
if (window.File && window.FileReader && window.FileList && window.Blob) {
var openFileButton = document.getElementById('openFileButton');
openFileButton.onclick = function(evt) {
if (this.className.indexOf('disabled') === -1) {
PDFViewer.fileInput.click();
}
};
openFileButton.onmousedown = function(evt) {
if (this.className.indexOf('disabled') === -1) {
this.className = 'down';
}
};
openFileButton.onmouseup = function(evt) {
this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
};
openFileButton.onmouseout = function(evt) {
this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
};
PDFViewer.fileInput = document.getElementById('fileInput');
PDFViewer.fileInput.onchange = function(evt) {
var files = evt.target.files;
if (files.length > 0) {
var file = files[0];
var fileReader = new FileReader();
document.title = file.name;
// Read the local file into a Uint8Array.
fileReader.onload = function(evt) {
var data = evt.target.result;
var buffer = new ArrayBuffer(data.length);
var uint8Array = new Uint8Array(buffer);
for (var i = 0; i < data.length; i++) {
uint8Array[i] = data.charCodeAt(i);
}
PDFViewer.readPDF(uint8Array);
};
// Read as a binary string since "readAsArrayBuffer" is not yet
// implemented in Firefox.
fileReader.readAsBinaryString(file);
}
};
PDFViewer.fileInput.value = null;
} else {
document.getElementById('fileWrapper').style.display = 'none';
}
PDFViewer.pageNumber = parseInt(PDFViewer.queryParams.page) || PDFViewer.pageNumber;
PDFViewer.scale = parseInt(PDFViewer.scaleSelect.value) / 100 || 1.0;
PDFViewer.openURL(PDFViewer.queryParams.file || PDFViewer.url);
window.onscroll = function(evt) {
var lastPagesDrawn = PDFViewer.lastPagesDrawn;
var visiblePages = PDFViewer.visiblePages();
var pagesToDraw = [];
var pagesToKeep = [];
var pagesToRemove = [];
var i;
// Determine which visible pages were not previously drawn.
for (i = 0; i < visiblePages.length; i++) {
if (lastPagesDrawn.indexOf(visiblePages[i]) === -1) {
pagesToDraw.push(visiblePages[i]);
PDFViewer.drawPage(visiblePages[i]);
} else {
pagesToKeep.push(visiblePages[i]);
}
}
// Determine which previously drawn pages are no longer visible.
for (i = 0; i < lastPagesDrawn.length; i++) {
if (visiblePages.indexOf(lastPagesDrawn[i]) === -1) {
pagesToRemove.push(lastPagesDrawn[i]);
PDFViewer.removePage(lastPagesDrawn[i]);
}
}
PDFViewer.lastPagesDrawn = pagesToDraw.concat(pagesToKeep);
// Update the page number input with the current page number.
if (!PDFViewer.willJumpToPage && visiblePages.length > 0) {
PDFViewer.pageNumber = PDFViewer.pageNumberInput.value = visiblePages[0];
PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ?
'disabled' : '';
PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ?
'disabled' : '';
} else {
PDFViewer.willJumpToPage = false;
}
};
};

197
multi_page_viewer.css Normal file
View File

@ -0,0 +1,197 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
body {
background-color: #929292;
font-family: 'Lucida Grande', 'Lucida Sans Unicode', Helvetica, Arial, Verdana, sans-serif;
margin: 0px;
padding: 0px;
}
canvas {
box-shadow: 0px 4px 10px #000;
-moz-box-shadow: 0px 4px 10px #000;
-webkit-box-shadow: 0px 4px 10px #000;
}
span {
font-size: 0.8em;
}
.control {
display: inline-block;
float: left;
margin: 0px 20px 0px 0px;
padding: 0px 4px 0px 0px;
}
.control > input {
float: left;
border: 1px solid #4d4d4d;
height: 20px;
padding: 0px;
margin: 0px 2px 0px 0px;
border-radius: 4px;
-moz-border-radius: 4px;
-webkit-border-radius: 4px;
box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25);
-moz-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25);
-webkit-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25);
}
.control > select {
float: left;
border: 1px solid #4d4d4d;
height: 22px;
padding: 2px 0px 0px;
margin: 0px 0px 1px;
border-radius: 4px;
-moz-border-radius: 4px;
-webkit-border-radius: 4px;
box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25);
-moz-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25);
-webkit-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25);
}
.control > span {
cursor: default;
float: left;
height: 18px;
margin: 5px 2px 0px;
padding: 0px;
user-select: none;
-moz-user-select: none;
-webkit-user-select: none;
}
.control .label {
clear: both;
float: left;
font-size: 0.65em;
margin: 2px 0px 0px;
position: relative;
text-align: center;
width: 100%;
}
.page {
width: 816px;
height: 1056px;
margin: 10px auto;
}
#controls {
background-color: #eee;
border-bottom: 1px solid #666;
padding: 4px 0px 0px 8px;
position: fixed;
left: 0px;
top: 0px;
height: 40px;
width: 100%;
box-shadow: 0px 2px 8px #000;
-moz-box-shadow: 0px 2px 8px #000;
-webkit-box-shadow: 0px 2px 8px #000;
}
#controls input {
user-select: text;
-moz-user-select: text;
-webkit-user-select: text;
}
#previousPageButton {
background: url('images/buttons.png') no-repeat 0px -23px;
cursor: default;
display: inline-block;
float: left;
margin: 0px;
width: 28px;
height: 23px;
}
#previousPageButton.down {
background: url('images/buttons.png') no-repeat 0px -46px;
}
#previousPageButton.disabled {
background: url('images/buttons.png') no-repeat 0px 0px;
}
#nextPageButton {
background: url('images/buttons.png') no-repeat -28px -23px;
cursor: default;
display: inline-block;
float: left;
margin: 0px;
width: 28px;
height: 23px;
}
#nextPageButton.down {
background: url('images/buttons.png') no-repeat -28px -46px;
}
#nextPageButton.disabled {
background: url('images/buttons.png') no-repeat -28px 0px;
}
#openFileButton {
background: url('images/buttons.png') no-repeat -56px -23px;
cursor: default;
display: inline-block;
float: left;
margin: 0px 0px 0px 3px;
width: 29px;
height: 23px;
}
#openFileButton.down {
background: url('images/buttons.png') no-repeat -56px -46px;
}
#openFileButton.disabled {
background: url('images/buttons.png') no-repeat -56px 0px;
}
#fileInput {
display: none;
}
#pageNumber {
text-align: right;
}
#sidebar {
background-color: rgba(0, 0, 0, 0.8);
position: fixed;
width: 150px;
top: 62px;
bottom: 18px;
border-top-right-radius: 8px;
border-bottom-right-radius: 8px;
-moz-border-radius-topright: 8px;
-moz-border-radius-bottomright: 8px;
-webkit-border-top-right-radius: 8px;
-webkit-border-bottom-right-radius: 8px;
}
#sidebarScrollView {
position: absolute;
overflow: hidden;
overflow-y: auto;
top: 40px;
right: 10px;
bottom: 10px;
left: 10px;
}
#sidebarContentView {
height: auto;
width: 100px;
}
#viewer {
margin: 44px 0px 0px;
padding: 8px 0px;
}

51
multi_page_viewer.html Normal file
View File

@ -0,0 +1,51 @@
<!DOCTYPE html>
<html>
<head>
<title>pdf.js Multi-Page Viewer</title>
<meta http-equiv="Content-type" content="text/html;charset=UTF-8"/>
<link rel="stylesheet" href="multi_page_viewer.css" type="text/css" media="screen"/>
<script type="text/javascript" src="pdf.js"></script>
<script type="text/javascript" src="fonts.js"></script>
<script type="text/javascript" src="glyphlist.js"></script>
<script type="text/javascript" src="multi_page_viewer.js"></script>
</head>
<body>
<div id="controls">
<span class="control">
<span id="previousPageButton" class="disabled"></span>
<span id="nextPageButton" class="disabled"></span>
<span class="label">Previous/Next</span>
</span>
<span class="control">
<input type="text" id="pageNumber" value="1" size="2"/>
<span>/</span>
<span id="numPages">--</span>
<span class="label">Page Number</span>
</span>
<span class="control">
<select id="scaleSelect">
<option value="50">50%</option>
<option value="75">75%</option>
<option value="100" selected="selected">100%</option>
<option value="125">125%</option>
<option value="150">150%</option>
<option value="200">200%</option>
</select>
<span class="label">Zoom</span>
</span>
<span class="control">
<span id="openFileButton"></span>
<input type="file" id="fileInput"/>
<span class="label">Open File</span>
</span>
</div>
<!--<div id="sidebar">
<div id="sidebarScrollView">
<div id="sidebarContentView">
</div>
</div>
</div>-->
<div id="viewer"></div>
</body>
</html>

458
multi_page_viewer.js Normal file
View File

@ -0,0 +1,458 @@
/* -*- 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 PDFViewer = {
queryParams: {},
element: null,
previousPageButton: null,
nextPageButton: null,
pageNumberInput: null,
scaleSelect: null,
fileInput: null,
willJumpToPage: false,
pdf: null,
url: 'compressed.tracemonkey-pldi-09.pdf',
pageNumber: 1,
numberOfPages: 1,
scale: 1.0,
pageWidth: function() {
return 816 * PDFViewer.scale;
},
pageHeight: function() {
return 1056 * PDFViewer.scale;
},
lastPagesDrawn: [],
visiblePages: function() {
var pageHeight = PDFViewer.pageHeight() + 20; // Add 20 for the margins.
var windowTop = window.pageYOffset;
var windowBottom = window.pageYOffset + window.innerHeight;
var pageStartIndex = Math.floor(windowTop / pageHeight);
var pageStopIndex = Math.ceil(windowBottom / pageHeight);
var pages = [];
for (var i = pageStartIndex; i <= pageStopIndex; i++) {
pages.push(i + 1);
}
return pages;
},
createPage: function(num) {
var anchor = document.createElement('a');
anchor.name = '' + num;
var div = document.createElement('div');
div.id = 'pageContainer' + num;
div.className = 'page';
div.style.width = PDFViewer.pageWidth() + 'px';
div.style.height = PDFViewer.pageHeight() + 'px';
PDFViewer.element.appendChild(anchor);
PDFViewer.element.appendChild(div);
},
removePage: function(num) {
var div = document.getElementById('pageContainer' + num);
if (div) {
while (div.hasChildNodes()) {
div.removeChild(div.firstChild);
}
}
},
drawPage: function(num) {
if (!PDFViewer.pdf) {
return;
}
var div = document.getElementById('pageContainer' + num);
var canvas = document.createElement('canvas');
if (div && !div.hasChildNodes()) {
div.appendChild(canvas);
var page = PDFViewer.pdf.getPage(num);
canvas.id = 'page' + num;
canvas.mozOpaque = true;
// Canvas dimensions must be specified in CSS pixels. CSS pixels
// are always 96 dpi. These dimensions are 8.5in x 11in at 96dpi.
canvas.width = PDFViewer.pageWidth();
canvas.height = PDFViewer.pageHeight();
var ctx = canvas.getContext('2d');
ctx.save();
ctx.fillStyle = 'rgb(255, 255, 255)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.restore();
var gfx = new CanvasGraphics(ctx);
var fonts = [];
// page.compile will collect all fonts for us, once we have loaded them
// we can trigger the actual page rendering with page.display
page.compile(gfx, fonts);
var areFontsReady = true;
// Inspect fonts and translate the missing one
var fontCount = fonts.length;
for (var i = 0; i < fontCount; i++) {
var font = fonts[i];
if (Fonts[font.name]) {
areFontsReady = areFontsReady && !Fonts[font.name].loading;
continue;
}
new Font(font.name, font.file, font.properties);
areFontsReady = false;
}
var pageInterval;
var delayLoadFont = function() {
for (var i = 0; i < fontCount; i++) {
if (Fonts[font.name].loading) {
return;
}
}
clearInterval(pageInterval);
while (div.hasChildNodes()) {
div.removeChild(div.firstChild);
}
PDFViewer.drawPage(num);
}
if (!areFontsReady) {
pageInterval = setInterval(delayLoadFont, 10);
return;
}
page.display(gfx);
}
},
changeScale: function(num) {
while (PDFViewer.element.hasChildNodes()) {
PDFViewer.element.removeChild(PDFViewer.element.firstChild);
}
PDFViewer.scale = num / 100;
var i;
if (PDFViewer.pdf) {
for (i = 1; i <= PDFViewer.numberOfPages; i++) {
PDFViewer.createPage(i);
}
if (PDFViewer.numberOfPages > 0) {
PDFViewer.drawPage(1);
}
}
for (i = 0; i < PDFViewer.scaleSelect.childNodes; i++) {
var option = PDFViewer.scaleSelect.childNodes[i];
if (option.value == num) {
if (!option.selected) {
option.selected = 'selected';
}
} else {
if (option.selected) {
option.removeAttribute('selected');
}
}
}
PDFViewer.scaleSelect.value = Math.floor(PDFViewer.scale * 100) + '%';
},
goToPage: function(num) {
if (1 <= num && num <= PDFViewer.numberOfPages) {
PDFViewer.pageNumber = num;
PDFViewer.pageNumberInput.value = PDFViewer.pageNumber;
PDFViewer.willJumpToPage = true;
document.location.hash = PDFViewer.pageNumber;
PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ? 'disabled' : '';
PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ? 'disabled' : '';
}
},
goToPreviousPage: function() {
if (PDFViewer.pageNumber > 1) {
PDFViewer.goToPage(--PDFViewer.pageNumber);
}
},
goToNextPage: function() {
if (PDFViewer.pageNumber < PDFViewer.numberOfPages) {
PDFViewer.goToPage(++PDFViewer.pageNumber);
}
},
openURL: function(url) {
PDFViewer.url = 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;
PDFViewer.readPDF(data);
}
};
req.send(null);
},
readPDF: function(data) {
while (PDFViewer.element.hasChildNodes()) {
PDFViewer.element.removeChild(PDFViewer.element.firstChild);
}
PDFViewer.pdf = new PDFDoc(new Stream(data));
PDFViewer.numberOfPages = PDFViewer.pdf.numPages;
document.getElementById('numPages').innerHTML = PDFViewer.numberOfPages.toString();
for (var i = 1; i <= PDFViewer.numberOfPages; i++) {
PDFViewer.createPage(i);
}
if (PDFViewer.numberOfPages > 0) {
PDFViewer.drawPage(1);
document.location.hash = 1;
}
PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ? 'disabled' : '';
PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ? 'disabled' : '';
}
};
window.onload = function() {
// Parse the URL query parameters into a cached object.
PDFViewer.queryParams = function() {
var qs = window.location.search.substring(1);
var kvs = qs.split('&');
var params = {};
for (var i = 0; i < kvs.length; ++i) {
var kv = kvs[i].split('=');
params[unescape(kv[0])] = unescape(kv[1]);
}
return params;
}();
PDFViewer.element = document.getElementById('viewer');
PDFViewer.pageNumberInput = document.getElementById('pageNumber');
PDFViewer.pageNumberInput.onkeydown = function(evt) {
var charCode = evt.charCode || evt.keyCode;
// Up arrow key.
if (charCode === 38) {
PDFViewer.goToNextPage();
this.select();
}
// Down arrow key.
else if (charCode === 40) {
PDFViewer.goToPreviousPage();
this.select();
}
// All other non-numeric keys (excluding Left arrow, Right arrow,
// Backspace, and Delete keys).
else if ((charCode < 48 || charCode > 57) &&
charCode !== 8 && // Backspace
charCode !== 46 && // Delete
charCode !== 37 && // Left arrow
charCode !== 39 // Right arrow
) {
return false;
}
return true;
};
PDFViewer.pageNumberInput.onkeyup = function(evt) {
var charCode = evt.charCode || evt.keyCode;
// All numeric keys, Backspace, and Delete.
if ((charCode >= 48 && charCode <= 57) ||
charCode === 8 || // Backspace
charCode === 46 // Delete
) {
PDFViewer.goToPage(this.value);
}
this.focus();
};
PDFViewer.previousPageButton = document.getElementById('previousPageButton');
PDFViewer.previousPageButton.onclick = function(evt) {
if (this.className.indexOf('disabled') === -1) {
PDFViewer.goToPreviousPage();
}
};
PDFViewer.previousPageButton.onmousedown = function(evt) {
if (this.className.indexOf('disabled') === -1) {
this.className = 'down';
}
};
PDFViewer.previousPageButton.onmouseup = function(evt) {
this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
};
PDFViewer.previousPageButton.onmouseout = function(evt) {
this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
};
PDFViewer.nextPageButton = document.getElementById('nextPageButton');
PDFViewer.nextPageButton.onclick = function(evt) {
if (this.className.indexOf('disabled') === -1) {
PDFViewer.goToNextPage();
}
};
PDFViewer.nextPageButton.onmousedown = function(evt) {
if (this.className.indexOf('disabled') === -1) {
this.className = 'down';
}
};
PDFViewer.nextPageButton.onmouseup = function(evt) {
this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
};
PDFViewer.nextPageButton.onmouseout = function(evt) {
this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
};
PDFViewer.scaleSelect = document.getElementById('scaleSelect');
PDFViewer.scaleSelect.onchange = function(evt) {
PDFViewer.changeScale(parseInt(this.value));
};
if (window.File && window.FileReader && window.FileList && window.Blob) {
var openFileButton = document.getElementById('openFileButton');
openFileButton.onclick = function(evt) {
if (this.className.indexOf('disabled') === -1) {
PDFViewer.fileInput.click();
}
};
openFileButton.onmousedown = function(evt) {
if (this.className.indexOf('disabled') === -1) {
this.className = 'down';
}
};
openFileButton.onmouseup = function(evt) {
this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
};
openFileButton.onmouseout = function(evt) {
this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
};
PDFViewer.fileInput = document.getElementById('fileInput');
PDFViewer.fileInput.onchange = function(evt) {
var files = evt.target.files;
if (files.length > 0) {
var file = files[0];
var fileReader = new FileReader();
document.title = file.name;
// Read the local file into a Uint8Array.
fileReader.onload = function(evt) {
var data = evt.target.result;
var buffer = new ArrayBuffer(data.length);
var uint8Array = new Uint8Array(buffer);
for (var i = 0; i < data.length; i++) {
uint8Array[i] = data.charCodeAt(i);
}
PDFViewer.readPDF(uint8Array);
};
// Read as a binary string since "readAsArrayBuffer" is not yet
// implemented in Firefox.
fileReader.readAsBinaryString(file);
}
};
PDFViewer.fileInput.value = null;
} else {
document.getElementById('fileWrapper').style.display = 'none';
}
PDFViewer.pageNumber = parseInt(PDFViewer.queryParams.page) || PDFViewer.pageNumber;
PDFViewer.scale = parseInt(PDFViewer.scaleSelect.value) / 100 || 1.0;
PDFViewer.openURL(PDFViewer.queryParams.file || PDFViewer.url);
window.onscroll = function(evt) {
var lastPagesDrawn = PDFViewer.lastPagesDrawn;
var visiblePages = PDFViewer.visiblePages();
var pagesToDraw = [];
var pagesToKeep = [];
var pagesToRemove = [];
var i;
// Determine which visible pages were not previously drawn.
for (i = 0; i < visiblePages.length; i++) {
if (lastPagesDrawn.indexOf(visiblePages[i]) === -1) {
pagesToDraw.push(visiblePages[i]);
PDFViewer.drawPage(visiblePages[i]);
} else {
pagesToKeep.push(visiblePages[i]);
}
}
// Determine which previously drawn pages are no longer visible.
for (i = 0; i < lastPagesDrawn.length; i++) {
if (visiblePages.indexOf(lastPagesDrawn[i]) === -1) {
pagesToRemove.push(lastPagesDrawn[i]);
PDFViewer.removePage(lastPagesDrawn[i]);
}
}
PDFViewer.lastPagesDrawn = pagesToDraw.concat(pagesToKeep);
// Update the page number input with the current page number.
if (!PDFViewer.willJumpToPage && visiblePages.length > 0) {
PDFViewer.pageNumber = PDFViewer.pageNumberInput.value = visiblePages[0];
PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ? 'disabled' : '';
PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ? 'disabled' : '';
} else {
PDFViewer.willJumpToPage = false;
}
};
};

104
pdf.js
View File

@ -479,17 +479,17 @@ var FlateStream = (function() {
array[i++] = what;
}
var bytes = this.bytes;
var bytesPos = this.bytesPos;
// read block header
var hdr = this.getBits(3);
if (hdr & 1)
this.eof = true;
hdr >>= 1;
var b;
if (hdr == 0) { // uncompressed block
var bytes = this.bytes;
var bytesPos = this.bytesPos;
var b;
if (typeof (b = bytes[bytesPos++]) == "undefined")
error("Bad block header in flate stream");
var blockLen = b;
@ -502,18 +502,24 @@ var FlateStream = (function() {
if (typeof (b = bytes[bytesPos++]) == "undefined")
error("Bad block header in flate stream");
check |= (b << 8);
if (check != (~this.blockLen & 0xffff))
if (check != (~blockLen & 0xffff))
error("Bad uncompressed block length in flate stream");
this.codeBuf = 0;
this.codeSize = 0;
var bufferLength = this.bufferLength;
var buffer = this.ensureBuffer(bufferLength + blockLen);
this.bufferLength = bufferLength + blockLen;
for (var n = bufferLength; n < blockLen; ++n) {
var end = bufferLength + blockLen;
this.bufferLength = end;
for (var n = bufferLength; n < end; ++n) {
if (typeof (b = bytes[bytesPos++]) == "undefined") {
this.eof = true;
break;
}
buffer[n] = b;
}
this.bytesPos = bytesPos;
return;
}
@ -3242,15 +3248,34 @@ var Encodings = {
}
};
function ScratchCanvas(width, height) {
var canvas = document.createElement("canvas");
canvas.width = width;
canvas.height = height;
return canvas;
}
var CanvasGraphics = (function() {
function constructor(canvasCtx) {
function constructor(canvasCtx, imageCanvas) {
this.ctx = canvasCtx;
this.current = new CanvasExtraState();
this.stateStack = [ ];
this.pendingClip = null;
this.res = null;
this.xobjs = null;
this.map = {
this.ScratchCanvas = imageCanvas || ScratchCanvas;
}
var LINE_CAP_STYLES = [ "butt", "round", "square" ];
var LINE_JOIN_STYLES = [ "miter", "round", "bevel" ];
var NORMAL_CLIP = {};
var EO_CLIP = {};
// Used for tiling patterns
var PAINT_TYPE_COLORED = 1, PAINT_TYPE_UNCOLORED = 2;
constructor.prototype = {
map: {
// Graphics state
w: "setLineWidth",
J: "setLineCap",
@ -3343,18 +3368,8 @@ var CanvasGraphics = (function() {
// Compatibility
BX: "beginCompat",
EX: "endCompat",
};
}
},
var LINE_CAP_STYLES = [ "butt", "round", "square" ];
var LINE_JOIN_STYLES = [ "miter", "round", "bevel" ];
var NORMAL_CLIP = {};
var EO_CLIP = {};
// Used for tiling patterns
var PAINT_TYPE_COLORED = 1, PAINT_TYPE_UNCOLORED = 2;
constructor.prototype = {
translateFont: function(fontDict, xref, resources) {
var fd = fontDict.get("FontDescriptor");
if (!fd)
@ -3642,12 +3657,18 @@ var CanvasGraphics = (function() {
},
save: function() {
this.ctx.save();
if (this.ctx.$saveCurrentX) {
this.ctx.$saveCurrentX();
}
this.stateStack.push(this.current);
this.current = new CanvasExtraState();
},
restore: function() {
var prev = this.stateStack.pop();
if (prev) {
if (this.ctx.$restoreCurrentX) {
this.ctx.$restoreCurrentX();
}
this.current = prev;
this.ctx.restore();
}
@ -3728,6 +3749,9 @@ var CanvasGraphics = (function() {
// Text
beginText: function() {
this.current.textMatrix = IDENTITY_MATRIX;
if (this.ctx.$setCurrentX) {
this.ctx.$setCurrentX(0)
}
this.current.x = this.current.lineX = 0;
this.current.y = this.current.lineY = 0;
},
@ -3782,6 +3806,9 @@ var CanvasGraphics = (function() {
moveText: function (x, y) {
this.current.x = this.current.lineX += x;
this.current.y = this.current.lineY += y;
if (this.ctx.$setCurrentX) {
this.ctx.$setCurrentX(this.current.x)
}
},
setLeadingMoveText: function(x, y) {
this.setLeading(-y);
@ -3789,6 +3816,10 @@ var CanvasGraphics = (function() {
},
setTextMatrix: function(a, b, c, d, e, f) {
this.current.textMatrix = [ a, b, c, d, e, f ];
if (this.ctx.$setCurrentX) {
this.ctx.$setCurrentX(0)
}
this.current.x = this.current.lineX = 0;
this.current.y = this.current.lineY = 0;
},
@ -3799,11 +3830,15 @@ var CanvasGraphics = (function() {
this.ctx.save();
this.ctx.transform.apply(this.ctx, this.current.textMatrix);
this.ctx.scale(1, -1);
this.ctx.translate(0, -2 * this.current.y);
text = Fonts.charsToUnicode(text);
this.ctx.fillText(text, this.current.x, this.current.y);
this.current.x += this.ctx.measureText(text).width;
if (this.ctx.$showText) {
this.ctx.$showText(this.current.y, Fonts.charsToUnicode(text));
} else {
text = Fonts.charsToUnicode(text);
this.ctx.translate(this.current.x, -1 * this.current.y);
this.ctx.fillText(text, 0, 0);
this.current.x += this.ctx.measureText(text).width;
}
this.ctx.restore();
},
@ -3811,7 +3846,11 @@ var CanvasGraphics = (function() {
for (var i = 0; i < arr.length; ++i) {
var e = arr[i];
if (IsNum(e)) {
this.current.x -= e * 0.001 * this.current.fontSize;
if (this.ctx.$addCurrentX) {
this.ctx.$addCurrentX(-e * 0.001 * this.current.fontSize)
} else {
this.current.x -= e * 0.001 * this.current.fontSize;
}
} else if (IsString(e)) {
this.showText(e);
} else {
@ -3950,9 +3989,10 @@ var CanvasGraphics = (function() {
// we want the canvas to be as large as the step size
var botRight = applyMatrix([x0 + xstep, y0 + ystep], matrix);
var tmpCanvas = document.createElement("canvas");
tmpCanvas.width = Math.ceil(botRight[0] - topLeft[0]);
tmpCanvas.height = Math.ceil(botRight[1] - topLeft[1]);
var tmpCanvas = new this.ScratchCanvas(
Math.ceil(botRight[0] - topLeft[0]), // width
Math.ceil(botRight[1] - topLeft[1]) // height
);
// set the new canvas element context as the graphics context
var tmpCtx = tmpCanvas.getContext("2d");
@ -4014,6 +4054,7 @@ var CanvasGraphics = (function() {
shadingFill: function(entryRef) {
var xref = this.xref;
var res = this.res;
var shadingRes = xref.fetchIfRef(res.get("Shading"));
if (!shadingRes)
error("No shading resource found");
@ -4310,9 +4351,7 @@ var CanvasGraphics = (function() {
// handle matte object
}
var tmpCanvas = document.createElement("canvas");
tmpCanvas.width = w;
tmpCanvas.height = h;
var tmpCanvas = new this.ScratchCanvas(w, h);
var tmpCtx = tmpCanvas.getContext("2d");
var imgData = tmpCtx.getImageData(0, 0, w, h);
var pixels = imgData.data;
@ -4480,6 +4519,7 @@ var ColorSpace = (function() {
break;
case "ICCBased":
var dict = stream.dict;
this.stream = stream;
this.dict = dict;
this.numComps = dict.get("N");
@ -4586,6 +4626,7 @@ var PDFFunction = (function() {
v = encode[i2] + ((v - domain[i2]) *
(encode[i2 + 1] - encode[i2]) /
(domain[i2 + 1] - domain[i2]));
// clip to the size
args[i] = clip(v, 0, size[i] - 1);
}
@ -4613,6 +4654,7 @@ var PDFFunction = (function() {
// decode
v = decode[i2] + (v * (decode[i2 + 1] - decode[i2]) /
((1 << bps) - 1));
// clip to the domain
output.push(clip(v, range[i2], range[i2 + 1]));
}

88
pdf_worker.js Normal file
View File

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

0
test/.gitignore vendored Normal file
View File

1
test/pdfs/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
pdf.pdf

View File

@ -0,0 +1,10 @@
[
{
"name":"firefox5",
"path":"/Applications/Firefox.app"
},
{
"name":"firefox6",
"path":"/Users/sayrer/firefoxen/Aurora.app"
}
]

View File

@ -0,0 +1,4 @@
content specialpowers chrome/specialpowers/content/
component {59a52458-13e0-4d93-9d85-a637344f29a1} components/SpecialPowersObserver.js
contract @mozilla.org/special-powers-observer;1 {59a52458-13e0-4d93-9d85-a637344f29a1}
category profile-after-change @mozilla.org/special-powers-observer;1 @mozilla.org/special-powers-observer;1

View File

@ -0,0 +1,372 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Special Powers code
*
* The Initial Developer of the Original Code is
* Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Clint Talbert cmtalbert@gmail.com
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK *****/
/* This code is loaded in every child process that is started by mochitest in
* order to be used as a replacement for UniversalXPConnect
*/
var Ci = Components.interfaces;
var Cc = Components.classes;
function SpecialPowers(window) {
this.window = window;
bindDOMWindowUtils(this, window);
this._encounteredCrashDumpFiles = [];
this._unexpectedCrashDumpFiles = { };
this._crashDumpDir = null;
this._pongHandlers = [];
this._messageListener = this._messageReceived.bind(this);
addMessageListener("SPPingService", this._messageListener);
}
function bindDOMWindowUtils(sp, window) {
var util = window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
// This bit of magic brought to you by the letters
// B Z, and E, S and the number 5.
//
// Take all of the properties on the nsIDOMWindowUtils-implementing
// object, and rebind them onto a new object with a stub that uses
// apply to call them from this privileged scope. This way we don't
// have to explicitly stub out new methods that appear on
// nsIDOMWindowUtils.
var proto = Object.getPrototypeOf(util);
var target = {};
function rebind(desc, prop) {
if (prop in desc && typeof(desc[prop]) == "function") {
var oldval = desc[prop];
desc[prop] = function() { return oldval.apply(util, arguments); };
}
}
for (var i in proto) {
var desc = Object.getOwnPropertyDescriptor(proto, i);
rebind(desc, "get");
rebind(desc, "set");
rebind(desc, "value");
Object.defineProperty(target, i, desc);
}
sp.DOMWindowUtils = target;
}
SpecialPowers.prototype = {
toString: function() { return "[SpecialPowers]"; },
sanityCheck: function() { return "foo"; },
// This gets filled in in the constructor.
DOMWindowUtils: undefined,
// Mimic the get*Pref API
getBoolPref: function(aPrefName) {
return (this._getPref(aPrefName, 'BOOL'));
},
getIntPref: function(aPrefName) {
return (this._getPref(aPrefName, 'INT'));
},
getCharPref: function(aPrefName) {
return (this._getPref(aPrefName, 'CHAR'));
},
getComplexValue: function(aPrefName, aIid) {
return (this._getPref(aPrefName, 'COMPLEX', aIid));
},
// Mimic the set*Pref API
setBoolPref: function(aPrefName, aValue) {
return (this._setPref(aPrefName, 'BOOL', aValue));
},
setIntPref: function(aPrefName, aValue) {
return (this._setPref(aPrefName, 'INT', aValue));
},
setCharPref: function(aPrefName, aValue) {
return (this._setPref(aPrefName, 'CHAR', aValue));
},
setComplexValue: function(aPrefName, aIid, aValue) {
return (this._setPref(aPrefName, 'COMPLEX', aValue, aIid));
},
// Mimic the clearUserPref API
clearUserPref: function(aPrefName) {
var msg = {'op':'clear', 'prefName': aPrefName, 'prefType': ""};
sendSyncMessage('SPPrefService', msg);
},
// Private pref functions to communicate to chrome
_getPref: function(aPrefName, aPrefType, aIid) {
var msg = {};
if (aIid) {
// Overloading prefValue to handle complex prefs
msg = {'op':'get', 'prefName': aPrefName, 'prefType':aPrefType, 'prefValue':[aIid]};
} else {
msg = {'op':'get', 'prefName': aPrefName,'prefType': aPrefType};
}
return(sendSyncMessage('SPPrefService', msg)[0]);
},
_setPref: function(aPrefName, aPrefType, aValue, aIid) {
var msg = {};
if (aIid) {
msg = {'op':'set','prefName':aPrefName, 'prefType': aPrefType, 'prefValue': [aIid,aValue]};
} else {
msg = {'op':'set', 'prefName': aPrefName, 'prefType': aPrefType, 'prefValue': aValue};
}
return(sendSyncMessage('SPPrefService', msg)[0]);
},
//XXX: these APIs really ought to be removed, they're not e10s-safe.
// (also they're pretty Firefox-specific)
_getTopChromeWindow: function(window) {
return window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShellTreeItem)
.rootTreeItem
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindow)
.QueryInterface(Ci.nsIDOMChromeWindow);
},
_getDocShell: function(window) {
return window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShell);
},
_getMUDV: function(window) {
return this._getDocShell(window).contentViewer
.QueryInterface(Ci.nsIMarkupDocumentViewer);
},
_getAutoCompletePopup: function(window) {
return this._getTopChromeWindow(window).document
.getElementById("PopupAutoComplete");
},
addAutoCompletePopupEventListener: function(window, listener) {
this._getAutoCompletePopup(window).addEventListener("popupshowing",
listener,
false);
},
removeAutoCompletePopupEventListener: function(window, listener) {
this._getAutoCompletePopup(window).removeEventListener("popupshowing",
listener,
false);
},
isBackButtonEnabled: function(window) {
return !this._getTopChromeWindow(window).document
.getElementById("Browser:Back")
.hasAttribute("disabled");
},
addChromeEventListener: function(type, listener, capture, allowUntrusted) {
addEventListener(type, listener, capture, allowUntrusted);
},
removeChromeEventListener: function(type, listener, capture) {
removeEventListener(type, listener, capture);
},
getFullZoom: function(window) {
return this._getMUDV(window).fullZoom;
},
setFullZoom: function(window, zoom) {
this._getMUDV(window).fullZoom = zoom;
},
getTextZoom: function(window) {
return this._getMUDV(window).textZoom;
},
setTextZoom: function(window, zoom) {
this._getMUDV(window).textZoom = zoom;
},
createSystemXHR: function() {
return Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
.createInstance(Ci.nsIXMLHttpRequest);
},
gc: function() {
this.DOMWindowUtils.garbageCollect();
},
hasContentProcesses: function() {
try {
var rt = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime);
return rt.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
} catch (e) {
return true;
}
},
registerProcessCrashObservers: function() {
addMessageListener("SPProcessCrashService", this._messageListener);
sendSyncMessage("SPProcessCrashService", { op: "register-observer" });
},
_messageReceived: function(aMessage) {
switch (aMessage.name) {
case "SPProcessCrashService":
if (aMessage.json.type == "crash-observed") {
var self = this;
aMessage.json.dumpIDs.forEach(function(id) {
self._encounteredCrashDumpFiles.push(id + ".dmp");
self._encounteredCrashDumpFiles.push(id + ".extra");
});
}
break;
case "SPPingService":
if (aMessage.json.op == "pong") {
var handler = this._pongHandlers.shift();
if (handler) {
handler();
}
}
break;
}
return true;
},
removeExpectedCrashDumpFiles: function(aExpectingProcessCrash) {
var success = true;
if (aExpectingProcessCrash) {
var message = {
op: "delete-crash-dump-files",
filenames: this._encounteredCrashDumpFiles
};
if (!sendSyncMessage("SPProcessCrashService", message)[0]) {
success = false;
}
}
this._encounteredCrashDumpFiles.length = 0;
return success;
},
findUnexpectedCrashDumpFiles: function() {
var self = this;
var message = {
op: "find-crash-dump-files",
crashDumpFilesToIgnore: this._unexpectedCrashDumpFiles
};
var crashDumpFiles = sendSyncMessage("SPProcessCrashService", message)[0];
crashDumpFiles.forEach(function(aFilename) {
self._unexpectedCrashDumpFiles[aFilename] = true;
});
return crashDumpFiles;
},
executeAfterFlushingMessageQueue: function(aCallback) {
this._pongHandlers.push(aCallback);
sendAsyncMessage("SPPingService", { op: "ping" });
},
executeSoon: function(aFunc) {
var tm = Cc["@mozilla.org/thread-manager;1"].getService(Ci.nsIThreadManager);
tm.mainThread.dispatch({
run: function() {
aFunc();
}
}, Ci.nsIThread.DISPATCH_NORMAL);
},
/* from http://mxr.mozilla.org/mozilla-central/source/testing/mochitest/tests/SimpleTest/quit.js
* by Bob Clary, Jeff Walden, and Robert Sayre.
*/
quitApplication: function() {
function canQuitApplication()
{
var os = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
if (!os)
return true;
try {
var cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].createInstance(Ci.nsISupportsPRBool);
os.notifyObservers(cancelQuit, "quit-application-requested", null);
// Something aborted the quit process.
if (cancelQuit.data)
return false;
} catch (ex) {}
return true;
}
if (!canQuitApplication())
return false;
var appService = Cc['@mozilla.org/toolkit/app-startup;1'].getService(Ci.nsIAppStartup);
appService.quit(Ci.nsIAppStartup.eForceQuit);
return true;
}
};
// Expose everything but internal APIs (starting with underscores) to
// web content.
SpecialPowers.prototype.__exposedProps__ = {};
for each (i in Object.keys(SpecialPowers.prototype).filter(function(v) {return v.charAt(0) != "_";})) {
SpecialPowers.prototype.__exposedProps__[i] = "r";
}
// Attach our API to the window.
function attachSpecialPowersToWindow(aWindow) {
try {
if ((aWindow !== null) &&
(aWindow !== undefined) &&
(aWindow.wrappedJSObject) &&
!(aWindow.wrappedJSObject.SpecialPowers)) {
aWindow.wrappedJSObject.SpecialPowers = new SpecialPowers(aWindow);
}
} catch(ex) {
dump("TEST-INFO | specialpowers.js | Failed to attach specialpowers to window exception: " + ex + "\n");
}
}
// This is a frame script, so it may be running in a content process.
// In any event, it is targeted at a specific "tab", so we listen for
// the DOMWindowCreated event to be notified about content windows
// being created in this context.
function SpecialPowersManager() {
addEventListener("DOMWindowCreated", this, false);
}
SpecialPowersManager.prototype = {
handleEvent: function handleEvent(aEvent) {
var window = aEvent.target.defaultView;
// Need to make sure we are called on what we care about -
// content windows. DOMWindowCreated is called on *all* HTMLDocuments,
// some of which belong to chrome windows or other special content.
//
var uri = window.document.documentURIObject;
if (uri.scheme === "chrome" || uri.spec.split(":")[0] == "about") {
return;
}
attachSpecialPowersToWindow(window);
}
};
var specialpowersmanager = new SpecialPowersManager();

View File

@ -0,0 +1,293 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Special Powers code
*
* The Initial Developer of the Original Code is
* Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Jesse Ruderman <jruderman@mozilla.com>
* Robert Sayre <sayrer@gmail.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK *****/
// Based on:
// https://bugzilla.mozilla.org/show_bug.cgi?id=549539
// https://bug549539.bugzilla.mozilla.org/attachment.cgi?id=429661
// https://developer.mozilla.org/en/XPCOM/XPCOM_changes_in_Gecko_1.9.3
// http://mxr.mozilla.org/mozilla-central/source/toolkit/components/console/hudservice/HUDService.jsm#3240
// https://developer.mozilla.org/en/how_to_build_an_xpcom_component_in_javascript
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
Components.utils.import("resource://gre/modules/Services.jsm");
const Cc = Components.classes;
const Ci = Components.interfaces;
const CHILD_SCRIPT = "chrome://specialpowers/content/specialpowers.js"
/**
* Special Powers Exception - used to throw exceptions nicely
**/
function SpecialPowersException(aMsg) {
this.message = aMsg;
this.name = "SpecialPowersException";
}
SpecialPowersException.prototype.toString = function() {
return this.name + ': "' + this.message + '"';
};
/* XPCOM gunk */
function SpecialPowersObserver() {
this._isFrameScriptLoaded = false;
this._messageManager = Cc["@mozilla.org/globalmessagemanager;1"].
getService(Ci.nsIChromeFrameMessageManager);
}
SpecialPowersObserver.prototype = {
classDescription: "Special powers Observer for use in testing.",
classID: Components.ID("{59a52458-13e0-4d93-9d85-a637344f29a1}"),
contractID: "@mozilla.org/special-powers-observer;1",
QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIObserver]),
_xpcom_categories: [{category: "profile-after-change", service: true }],
observe: function(aSubject, aTopic, aData)
{
switch (aTopic) {
case "profile-after-change":
this.init();
break;
case "chrome-document-global-created":
if (!this._isFrameScriptLoaded) {
// Register for any messages our API needs us to handle
this._messageManager.addMessageListener("SPPrefService", this);
this._messageManager.addMessageListener("SPProcessCrashService", this);
this._messageManager.addMessageListener("SPPingService", this);
this._messageManager.loadFrameScript(CHILD_SCRIPT, true);
this._isFrameScriptLoaded = true;
}
break;
case "xpcom-shutdown":
this.uninit();
break;
case "plugin-crashed":
case "ipc:content-shutdown":
function addDumpIDToMessage(propertyName) {
var id = aSubject.getPropertyAsAString(propertyName);
if (id) {
message.dumpIDs.push(id);
}
}
var message = { type: "crash-observed", dumpIDs: [] };
aSubject = aSubject.QueryInterface(Ci.nsIPropertyBag2);
if (aTopic == "plugin-crashed") {
addDumpIDToMessage("pluginDumpID");
addDumpIDToMessage("browserDumpID");
} else { // ipc:content-shutdown
addDumpIDToMessage("dumpID");
}
this._messageManager.sendAsyncMessage("SPProcessCrashService", message);
break;
}
},
init: function()
{
var obs = Services.obs;
obs.addObserver(this, "xpcom-shutdown", false);
obs.addObserver(this, "chrome-document-global-created", false);
},
uninit: function()
{
var obs = Services.obs;
obs.removeObserver(this, "chrome-document-global-created", false);
this.removeProcessCrashObservers();
},
addProcessCrashObservers: function() {
if (this._processCrashObserversRegistered) {
return;
}
Services.obs.addObserver(this, "plugin-crashed", false);
Services.obs.addObserver(this, "ipc:content-shutdown", false);
this._processCrashObserversRegistered = true;
},
removeProcessCrashObservers: function() {
if (!this._processCrashObserversRegistered) {
return;
}
Services.obs.removeObserver(this, "plugin-crashed");
Services.obs.removeObserver(this, "ipc:content-shutdown");
this._processCrashObserversRegistered = false;
},
getCrashDumpDir: function() {
if (!this._crashDumpDir) {
var directoryService = Cc["@mozilla.org/file/directory_service;1"]
.getService(Ci.nsIProperties);
this._crashDumpDir = directoryService.get("ProfD", Ci.nsIFile);
this._crashDumpDir.append("minidumps");
}
return this._crashDumpDir;
},
deleteCrashDumpFiles: function(aFilenames) {
var crashDumpDir = this.getCrashDumpDir();
if (!crashDumpDir.exists()) {
return false;
}
var success = aFilenames.length != 0;
aFilenames.forEach(function(crashFilename) {
var file = crashDumpDir.clone();
file.append(crashFilename);
if (file.exists()) {
file.remove(false);
} else {
success = false;
}
});
return success;
},
findCrashDumpFiles: function(aToIgnore) {
var crashDumpDir = this.getCrashDumpDir();
var entries = crashDumpDir.exists() && crashDumpDir.directoryEntries;
if (!entries) {
return [];
}
var crashDumpFiles = [];
while (entries.hasMoreElements()) {
var file = entries.getNext().QueryInterface(Ci.nsIFile);
var path = String(file.path);
if (path.match(/\.(dmp|extra)$/) && !aToIgnore[path]) {
crashDumpFiles.push(path);
}
}
return crashDumpFiles.concat();
},
/**
* messageManager callback function
* This will get requests from our API in the window and process them in chrome for it
**/
receiveMessage: function(aMessage) {
switch(aMessage.name) {
case "SPPrefService":
var prefs = Services.prefs;
var prefType = aMessage.json.prefType.toUpperCase();
var prefName = aMessage.json.prefName;
var prefValue = "prefValue" in aMessage.json ? aMessage.json.prefValue : null;
if (aMessage.json.op == "get") {
if (!prefName || !prefType)
throw new SpecialPowersException("Invalid parameters for get in SPPrefService");
} else if (aMessage.json.op == "set") {
if (!prefName || !prefType || prefValue === null)
throw new SpecialPowersException("Invalid parameters for set in SPPrefService");
} else if (aMessage.json.op == "clear") {
if (!prefName)
throw new SpecialPowersException("Invalid parameters for clear in SPPrefService");
} else {
throw new SpecialPowersException("Invalid operation for SPPrefService");
}
// Now we make the call
switch(prefType) {
case "BOOL":
if (aMessage.json.op == "get")
return(prefs.getBoolPref(prefName));
else
return(prefs.setBoolPref(prefName, prefValue));
case "INT":
if (aMessage.json.op == "get")
return(prefs.getIntPref(prefName));
else
return(prefs.setIntPref(prefName, prefValue));
case "CHAR":
if (aMessage.json.op == "get")
return(prefs.getCharPref(prefName));
else
return(prefs.setCharPref(prefName, prefValue));
case "COMPLEX":
if (aMessage.json.op == "get")
return(prefs.getComplexValue(prefName, prefValue[0]));
else
return(prefs.setComplexValue(prefName, prefValue[0], prefValue[1]));
case "":
if (aMessage.json.op == "clear") {
prefs.clearUserPref(prefName);
return;
}
}
break;
case "SPProcessCrashService":
switch (aMessage.json.op) {
case "register-observer":
this.addProcessCrashObservers();
break;
case "unregister-observer":
this.removeProcessCrashObservers();
break;
case "delete-crash-dump-files":
return this.deleteCrashDumpFiles(aMessage.json.filenames);
case "find-crash-dump-files":
return this.findCrashDumpFiles(aMessage.json.crashDumpFilesToIgnore);
default:
throw new SpecialPowersException("Invalid operation for SPProcessCrashService");
}
break;
case "SPPingService":
if (aMessage.json.op == "ping") {
aMessage.target
.QueryInterface(Ci.nsIFrameLoaderOwner)
.frameLoader
.messageManager
.sendAsyncMessage("SPPingService", { op: "pong" });
}
break;
default:
throw new SpecialPowersException("Unrecognized Special Powers API");
}
}
};
const NSGetFactory = XPCOMUtils.generateNSGetFactory([SpecialPowersObserver]);

View File

@ -0,0 +1,26 @@
<?xml version="1.0"?>
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
<Description about="urn:mozilla:install-manifest">
<em:id>special-powers@mozilla.org</em:id>
<em:version>2010.07.23</em:version>
<em:type>2</em:type>
<!-- Target Application this extension can install into,
with minimum and maximum supported versions. -->
<em:targetApplication>
<Description>
<em:id>toolkit@mozilla.org</em:id>
<em:minVersion>3.0</em:minVersion>
<em:maxVersion>7.0a1</em:maxVersion>
</Description>
</em:targetApplication>
<!-- Front End MetaData -->
<em:name>Special Powers</em:name>
<em:description>Special powers for use in testing.</em:description>
<em:creator>Mozilla</em:creator>
</Description>
</RDF>

View File

@ -0,0 +1,34 @@
user_pref("browser.console.showInPanel", true);
user_pref("browser.dom.window.dump.enabled", true);
user_pref("browser.firstrun.show.localepicker", false);
user_pref("browser.firstrun.show.uidiscovery", false);
user_pref("dom.allow_scripts_to_close_windows", true);
user_pref("dom.disable_open_during_load", false);
user_pref("dom.max_script_run_time", 0); // no slow script dialogs
user_pref("dom.max_chrome_script_run_time", 0);
user_pref("dom.popup_maximum", -1);
user_pref("dom.send_after_paint_to_content", true);
user_pref("dom.successive_dialog_time_limit", 0);
user_pref("security.warn_submit_insecure", false);
user_pref("browser.shell.checkDefaultBrowser", false);
user_pref("shell.checkDefaultClient", false);
user_pref("browser.warnOnQuit", false);
user_pref("accessibility.typeaheadfind.autostart", false);
user_pref("javascript.options.showInConsole", true);
user_pref("devtools.errorconsole.enabled", true);
user_pref("layout.debug.enable_data_xbl", true);
user_pref("browser.EULA.override", true);
user_pref("javascript.options.tracejit.content", true);
user_pref("javascript.options.methodjit.content", true);
user_pref("javascript.options.jitprofiling.content", true);
user_pref("javascript.options.methodjit_always", false);
user_pref("gfx.color_management.force_srgb", true);
user_pref("network.manage-offline-status", false);
user_pref("test.mousescroll", true);
user_pref("network.http.prompt-temp-redirect", false);
user_pref("media.cache_size", 100);
user_pref("security.warn_viewing_mixed", false);
user_pref("app.update.enabled", false);
user_pref("browser.panorama.experienced_first_run", true); // Assume experienced
user_pref("dom.w3c_touch_events.enabled", true);
user_pref("extensions.checkCompatibility", false);

View File

@ -1,11 +1,13 @@
import json, os, sys, subprocess, urllib2
import json, platform, os, shutil, sys, subprocess, tempfile, threading, urllib, urllib2
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
import SocketServer
from optparse import OptionParser
from urlparse import urlparse
def prompt(question):
'''Return True iff the user answered "yes" to |question|.'''
inp = raw_input(question +' [yes/no] > ')
return inp == 'yes'
USAGE_EXAMPLE = "%prog"
# The local web server uses the git repo as the document root.
DOC_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__),".."))
ANAL = True
DEFAULT_MANIFEST_FILE = 'test_manifest.json'
@ -14,6 +16,34 @@ REFDIR = 'ref'
TMPDIR = 'tmp'
VERBOSE = False
class TestOptions(OptionParser):
def __init__(self, **kwargs):
OptionParser.__init__(self, **kwargs)
self.add_option("-m", "--masterMode", action="store_true", dest="masterMode",
help="Run the script in master mode.", default=False)
self.add_option("--manifestFile", action="store", type="string", dest="manifestFile",
help="A JSON file in the form of test_manifest.json (the default).")
self.add_option("-b", "--browser", action="store", type="string", dest="browser",
help="The path to a single browser (right now, only Firefox is supported).")
self.add_option("--browserManifestFile", action="store", type="string",
dest="browserManifestFile",
help="A JSON file in the form of those found in resources/browser_manifests")
self.set_usage(USAGE_EXAMPLE)
def verifyOptions(self, options):
if options.masterMode and options.manifestFile:
self.error("--masterMode and --manifestFile must not be specified at the same time.")
if not options.manifestFile:
options.manifestFile = DEFAULT_MANIFEST_FILE
if options.browser and options.browserManifestFile:
print "Warning: ignoring browser argument since manifest file was also supplied"
return options
def prompt(question):
'''Return True iff the user answered "yes" to |question|.'''
inp = raw_input(question +' [yes/no] > ')
return inp == 'yes'
MIMEs = {
'.css': 'text/css',
'.html': 'text/html',
@ -43,8 +73,11 @@ class Result:
self.snapshot = snapshot
self.failure = failure
class TestServer(SocketServer.TCPServer):
allow_reuse_address = True
class PDFTestHandler(BaseHTTPRequestHandler):
# Disable annoying noise by default
def log_request(code=0, size=0):
if VERBOSE:
@ -54,13 +87,11 @@ class PDFTestHandler(BaseHTTPRequestHandler):
url = urlparse(self.path)
# Ignore query string
path, _ = url.path, url.query
cwd = os.getcwd()
path = os.path.abspath(os.path.realpath(cwd + os.sep + path))
cwd = os.path.abspath(cwd)
prefix = os.path.commonprefix(( path, cwd ))
path = os.path.abspath(os.path.realpath(DOC_ROOT + os.sep + path))
prefix = os.path.commonprefix(( path, DOC_ROOT ))
_, ext = os.path.splitext(path)
if not (prefix == cwd
if not (prefix == DOC_ROOT
and os.path.isfile(path)
and ext in MIMEs):
self.send_error(404)
@ -102,13 +133,49 @@ class PDFTestHandler(BaseHTTPRequestHandler):
State.done = (0 == State.remaining)
# this just does Firefox for now
class BrowserCommand():
def __init__(self, browserRecord):
self.name = browserRecord["name"]
self.path = browserRecord["path"]
def setUp(manifestFile, masterMode):
if platform.system() == "Darwin" and (self.path.endswith(".app") or self.path.endswith(".app/")):
self._fixupMacPath()
if not os.path.exists(self.path):
throw("Path to browser '%s' does not exist." % self.path)
def _fixupMacPath(self):
self.path = os.path.join(self.path, "Contents", "MacOS", "firefox-bin")
def setup(self):
self.tempDir = tempfile.mkdtemp()
self.profileDir = os.path.join(self.tempDir, "profile")
print self.profileDir
shutil.copytree(os.path.join(DOC_ROOT, "test", "resources", "firefox"),
self.profileDir)
def teardown(self):
shutil.rmtree(self.tempDir)
def start(self, url):
cmds = [self.path]
if platform.system() == "Darwin":
cmds.append("-foreground")
cmds.extend(["-no-remote", "-profile", self.profileDir, url])
subprocess.call(cmds)
def makeBrowserCommands(browserManifestFile):
with open(browserManifestFile) as bmf:
browsers = [BrowserCommand(browser) for browser in json.load(bmf)]
return browsers
def setUp(options):
# Only serve files from a pdf.js clone
assert not ANAL or os.path.isfile('pdf.js') and os.path.isdir('.git')
assert not ANAL or os.path.isfile('../pdf.js') and os.path.isdir('../.git')
State.masterMode = masterMode
if masterMode and os.path.isdir(TMPDIR):
State.masterMode = options.masterMode
if options.masterMode and os.path.isdir(TMPDIR):
print 'Temporary snapshot dir tmp/ is still around.'
print 'tmp/ can be removed if it has nothing you need.'
if prompt('SHOULD THIS SCRIPT REMOVE tmp/? THINK CAREFULLY'):
@ -116,14 +183,16 @@ def setUp(manifestFile, masterMode):
assert not os.path.isdir(TMPDIR)
testBrowsers = [ b for b in
( 'firefox5', 'firefox6', )
#'chrome12', 'chrome13', 'firefox4', 'opera11' ):
if os.access(b, os.R_OK | os.X_OK) ]
testBrowsers = []
if options.browserManifestFile:
testBrowsers = makeBrowserCommands(options.browserManifestFile)
elif options.browser:
testBrowsers = [BrowserCommand({"path":options.browser, "name":"firefox"})]
else:
print "No test browsers found. Use --browserManifest or --browser args."
mf = open(manifestFile)
manifestList = json.load(mf)
mf.close()
with open(options.manifestFile) as mf:
manifestList = json.load(mf)
for item in manifestList:
f, isLink = item['file'], item.get('link', False)
@ -143,23 +212,25 @@ def setUp(manifestFile, masterMode):
print 'done'
for b in testBrowsers:
State.taskResults[b] = { }
State.taskResults[b.name] = { }
for item in manifestList:
id, rounds = item['id'], int(item['rounds'])
State.manifest[id] = item
taskResults = [ ]
for r in xrange(rounds):
taskResults.append([ ])
State.taskResults[b][id] = taskResults
State.taskResults[b.name][id] = taskResults
State.remaining = len(testBrowsers) * len(manifestList)
for b in testBrowsers:
print 'Launching', b
qs = 'browser='+ b +'&manifestFile='+ manifestFile
subprocess.Popen(( os.path.abspath(os.path.realpath(b)),
'http://localhost:8080/test_slave.html?'+ qs))
try:
b.setup()
print 'Launching', b.name
qs = 'browser='+ urllib.quote(b.name) +'&manifestFile='+ urllib.quote(options.manifestFile)
b.start('http://localhost:8080/test/test_slave.html?'+ qs)
finally:
b.teardown()
def check(task, results, browser):
failed = False
@ -302,20 +373,20 @@ def processResults():
print 'done'
def main(args):
masterMode = False
manifestFile = DEFAULT_MANIFEST_FILE
if len(args) == 1:
masterMode = (args[0] == '-m')
manifestFile = args[0] if not masterMode else manifestFile
def main():
optionParser = TestOptions()
options, args = optionParser.parse_args()
options = optionParser.verifyOptions(options)
if options == None:
sys.exit(1)
setUp(manifestFile, masterMode)
server = HTTPServer(('127.0.0.1', 8080), PDFTestHandler)
while not State.done:
server.handle_request()
httpd = TestServer(('127.0.0.1', 8080), PDFTestHandler)
httpd_thread = threading.Thread(target=httpd.serve_forever)
httpd_thread.setDaemon(True)
httpd_thread.start()
setUp(options)
processResults()
if __name__ == '__main__':
main(sys.argv[1:])
main()

View File

@ -1,21 +1,21 @@
[
{ "id": "tracemonkey-eq",
"file": "tests/tracemonkey.pdf",
"file": "pdfs/tracemonkey.pdf",
"rounds": 1,
"type": "eq"
},
{ "id": "tracemonkey-fbf",
"file": "tests/tracemonkey.pdf",
"file": "pdfs/tracemonkey.pdf",
"rounds": 2,
"type": "fbf"
},
{ "id": "html5-canvas-cheat-sheet-load",
"file": "tests/canvas.pdf",
"file": "pdfs/canvas.pdf",
"rounds": 1,
"type": "load"
},
{ "id": "pdfspec-load",
"file": "tests/pdf.pdf",
"file": "pdfs/pdf.pdf",
"link": true,
"rounds": 1,
"type": "load"

View File

@ -2,9 +2,9 @@
<head>
<title>pdf.js test slave</title>
<style type="text/css"></style>
<script type="text/javascript" src="pdf.js"></script>
<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="/fonts.js"></script>
<script type="text/javascript" src="/glyphlist.js"></script>
<script type="application/javascript">
var browser, canvas, currentTask, currentTaskIdx, failure, manifest, pdfDoc, stdout;
@ -151,7 +151,10 @@ function done() {
log("Done!\n");
setTimeout(function() {
document.body.innerHTML = "Tests are finished. <h1>CLOSE ME!</h1>";
window.close();
if (window.SpecialPowers)
SpecialPowers.quitApplication();
else
window.close();
},
100
);
@ -169,7 +172,7 @@ function sendTaskResult(snapshot) {
var r = new XMLHttpRequest();
// (The POST URI is ignored atm.)
r.open("POST", "submit_task_results", false);
r.open("POST", "/submit_task_results", false);
r.setRequestHeader("Content-Type", "application/json");
// XXX async
r.send(JSON.stringify(result));

46
viewer_worker.html Normal file
View File

@ -0,0 +1,46 @@
<html>
<head>
<title>Simple pdf.js page worker viewer</title>
<script type="text/javascript" src="worker_client.js"></script>
<script>
var pdfDoc;
window.onload = function() {
window.canvas = document.getElementById("canvas");
window.ctx = canvas.getContext("2d");
pdfDoc = new WorkerPDFDoc(window.canvas);
pdfDoc.onChangePage = function(numPage) {
document.getElementById("pageNumber").value = numPage;
}
// pdfDoc.open("canvas.pdf", function() {
pdfDoc.open("compressed.tracemonkey-pldi-09.pdf", function() {
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 dimensions must be specified in CSS pixels. CSS pixels
are always 96 dpi. 816x1056 is 8.5x11in at 96dpi. -->
<!-- We're rendering here at 1.5x scale. -->
<canvas id="canvas" width="1224" height="1584"></canvas>
</div>
</body>
</html>

272
worker_client.js Normal file
View File

@ -0,0 +1,272 @@
/* -*- 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);
}