Merge with upstream, reverse changeset 4e24288 since it brokes TTF on linux

This commit is contained in:
Vivien Nicolas 2011-06-29 02:58:51 +02:00
commit dd923d5aea
28 changed files with 2946 additions and 416 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
pdf.pdf
intelisa.pdf
openweb_tm-PRINT.pdf

10
README
View File

@ -7,6 +7,14 @@ You can read more about pdf.js here:
http://andreasgal.com/2011/06/15/pdf-js/
http://blog.mozilla.com/cjones/2011/06/15/overview-of-pdf-js-guts/
Or follow us on twitter: @pdfjs
follow us on twitter: @pdfjs
http://twitter.com/#!/pdfjs
join our mailing list:
dev-pdf-js@lists.mozilla.org
and talk to us on IRC:
#pdfjs on irc.mozilla.org

260
crypto.js Normal file
View File

@ -0,0 +1,260 @@
/* -*- 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 ARCFourCipher = (function() {
function constructor(key) {
this.a = 0;
this.b = 0;
var s = new Uint8Array(256);
var i, j = 0, tmp, keyLength = key.length;
for (i = 0; i < 256; ++i)
s[i] = i;
for (i = 0; i < 256; ++i) {
tmp = s[i];
j = (j + tmp + key[i % keyLength]) & 0xFF;
s[i] = s[j];
s[j] = tmp;
}
this.s = s;
}
constructor.prototype = {
encryptBlock: function(data) {
var i, n = data.length, tmp, tmp2;
var a = this.a, b = this.b, s = this.s;
var output = new Uint8Array(n);
for (i = 0; i < n; ++i) {
var tmp;
a = (a + 1) & 0xFF;
tmp = s[a];
b = (b + tmp) & 0xFF;
tmp2 = s[b]
s[a] = tmp2;
s[b] = tmp;
output[i] = data[i] ^ s[(tmp + tmp2) & 0xFF];
}
this.a = a;
this.b = b;
return output;
}
};
return constructor;
})();
var md5 = (function() {
var r = new Uint8Array([
7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21]);
var k = new Int32Array([
-680876936, -389564586, 606105819, -1044525330, -176418897, 1200080426,
-1473231341, -45705983, 1770035416, -1958414417, -42063, -1990404162,
1804603682, -40341101, -1502002290, 1236535329, -165796510, -1069501632,
643717713, -373897302, -701558691, 38016083, -660478335, -405537848, 568446438,
-1019803690, -187363961, 1163531501, -1444681467, -51403784, 1735328473,
-1926607734, -378558, -2022574463, 1839030562, -35309556, -1530992060,
1272893353, -155497632, -1094730640, 681279174, -358537222, -722521979,
76029189, -640364487, -421815835, 530742520, -995338651, -198630844, 1126891415,
-1416354905, -57434055, 1700485571, -1894986606, -1051523, -2054922799,
1873313359, -30611744, -1560198380, 1309151649, -145523070, -1120210379,
718787259, -343485551]);
function hash(data, offset, length) {
var h0 = 1732584193, h1 = -271733879, h2 = -1732584194, h3 = 271733878;
// pre-processing
var paddedLength = (length + 72) & ~63; // data + 9 extra bytes
var padded = new Uint8Array(paddedLength);
var i, j, n;
for (i = 0; i < length; ++i)
padded[i] = data[offset++];
padded[i++] = 0x80;
n = paddedLength - 8;
for (; i < n; ++i)
padded[i] = 0;
padded[i++] = (length << 3) & 0xFF;
padded[i++] = (length >> 5) & 0xFF;
padded[i++] = (length >> 13) & 0xFF;
padded[i++] = (length >> 21) & 0xFF;
padded[i++] = (length >>> 29) & 0xFF;
padded[i++] = 0;
padded[i++] = 0;
padded[i++] = 0;
// chunking
// TODO ArrayBuffer ?
var w = new Int32Array(16);
for (i = 0; i < paddedLength;) {
for (j = 0; j < 16; ++j, i += 4)
w[j] = padded[i] | (padded[i + 1] << 8) | (padded[i + 2] << 16) | (padded[i + 3] << 24);
var a = h0, b = h1, c = h2, d = h3, f, g;
for (j = 0; j < 64; ++j) {
if (j < 16) {
f = (b & c) | ((~b) & d);
g = j;
} else if (j < 32) {
f = (d & b) | ((~d) & c);
g = (5 * j + 1) & 15;
} else if (j < 48) {
f = b ^ c ^ d;
g = (3 * j + 5) & 15;
} else {
f = c ^ (b | (~d));
g = (7 * j) & 15;
}
var tmp = d, rotateArg = (a + f + k[j] + w[g]) | 0, rotate = r[j];
d = c;
c = b;
b = (b + ((rotateArg << rotate) | (rotateArg >>> (32 - rotate)))) | 0;
a = tmp;
}
h0 = (h0 + a) | 0;
h1 = (h1 + b) | 0;
h2 = (h2 + c) | 0;
h3 = (h3 + d) | 0;
}
return new Uint8Array([
h0 & 0xFF, (h0 >> 8) & 0xFF, (h0 >> 16) & 0xFF, (h0 >>> 24) & 0xFF,
h1 & 0xFF, (h1 >> 8) & 0xFF, (h1 >> 16) & 0xFF, (h1 >>> 24) & 0xFF,
h2 & 0xFF, (h2 >> 8) & 0xFF, (h2 >> 16) & 0xFF, (h2 >>> 24) & 0xFF,
h3 & 0xFF, (h3 >> 8) & 0xFF, (h3 >> 16) & 0xFF, (h3 >>> 24) & 0xFF
]);
}
return hash;
})();
var CipherTransform = (function() {
function constructor(stringCipherConstructor, streamCipherConstructor) {
this.stringCipherConstructor = stringCipherConstructor;
this.streamCipherConstructor = streamCipherConstructor;
}
constructor.prototype = {
createStream: function (stream) {
var cipher = new this.streamCipherConstructor();
return new DecryptStream(stream, function(data) {
return cipher.encryptBlock(data);
});
},
decryptString: function(s) {
var cipher = new this.stringCipherConstructor();
var data = string2bytes(s);
data = cipher.encryptBlock(data);
return bytes2string(data);
}
};
return constructor;
})();
var CipherTransformFactory = (function() {
function prepareKeyData(fileId, password, ownerPassword, userPassword, flags, revision, keyLength) {
var defaultPasswordBytes = new Uint8Array([
0x28, 0xBF, 0x4E, 0x5E, 0x4E, 0x75, 0x8A, 0x41, 0x64, 0x00, 0x4E, 0x56, 0xFF, 0xFA, 0x01, 0x08,
0x2E, 0x2E, 0x00, 0xB6, 0xD0, 0x68, 0x3E, 0x80, 0x2F, 0x0C, 0xA9, 0xFE, 0x64, 0x53, 0x69, 0x7A]);
var hashData = new Uint8Array(88), i = 0, j, n;
if (password) {
n = Math.min(32, password.length);
for (; i < n; ++i)
hashData[i] = password[i];
}
j = 0;
while (i < 32) {
hashData[i++] = defaultPasswordBytes[j++];
}
// as now the padded password in the hashData[0..i]
for (j = 0, n = ownerPassword.length; j < n; ++j)
hashData[i++] = ownerPassword[j];
hashData[i++] = flags & 0xFF;
hashData[i++] = (flags >> 8) & 0xFF;
hashData[i++] = (flags >> 16) & 0xFF;
hashData[i++] = (flags >>> 24) & 0xFF;
for (j = 0, n = fileId.length; j < n; ++j)
hashData[i++] = fileId[j];
// TODO rev 4, if metadata is not encrypted pass 0xFFFFFF also
var hash = md5(hashData, 0, i);
var keyLengthInBytes = keyLength >> 3;
if (revision >= 3) {
for (j = 0; j < 50; ++j) {
hash = md5(hash, 0, keyLengthInBytes);
}
}
var encryptionKey = hash.subarray(0, keyLengthInBytes);
var cipher, checkData;
if (revision >= 3) {
// padded password in hashData, we can use this array for user password check
i = 32;
for(j = 0, n = fileId.length; j < n; ++j)
hashData[i++] = fileId[j];
cipher = new ARCFourCipher(encryptionKey);
var checkData = cipher.encryptBlock(md5(hashData, 0, i));
n = encryptionKey.length;
var derrivedKey = new Uint8Array(n), k;
for (j = 1; j <= 19; ++j) {
for (k = 0; k < n; ++k)
derrivedKey[k] = encryptionKey[k] ^ j;
cipher = new ARCFourCipher(derrivedKey);
checkData = cipher.encryptBlock(checkData);
}
} else {
cipher = new ARCFourCipher(encryptionKey);
checkData = cipher.encryptBlock(hashData.subarray(0, 32));
}
for (j = 0, n = checkData.length; j < n; ++j) {
if (userPassword[j] != checkData[j])
error("incorrect password");
}
return encryptionKey;
}
function constructor(dict, fileId, password) {
var filter = dict.get("Filter");
if (!IsName(filter) || filter.name != "Standard")
error("unknown encryption method");
this.dict = dict;
var algorithm = dict.get("V");
if (!IsInt(algorithm) ||
(algorithm != 1 && algorithm != 2))
error("unsupported encryption algorithm");
// TODO support algorithm 4
var keyLength = dict.get("Length") || 40;
if (!IsInt(keyLength) ||
keyLength < 40 || (keyLength % 8) != 0)
error("invalid key length");
// prepare keys
var ownerPassword = stringToBytes(dict.get("O"));
var userPassword = stringToBytes(dict.get("U"));
var flags = dict.get("P");
var revision = dict.get("R");
var fileIdBytes = stringToBytes(fileId);
var passwordBytes;
if (password)
passwordBytes = stringToBytes(password);
this.encryptionKey = prepareKeyData(fileIdBytes, passwordBytes,
ownerPassword, userPassword, flags, revision, keyLength);
}
constructor.prototype = {
createCipherTransform: function(num, gen) {
var encryptionKey = this.encryptionKey;
var key = new Uint8Array(encryptionKey.length + 5), i, n;
for (i = 0, n = encryptionKey.length; i < n; ++i)
key[i] = encryptionKey[i];
key[i++] = num & 0xFF;
key[i++] = (num >> 8) & 0xFF;
key[i++] = (num >> 16) & 0xFF;
key[i++] = gen & 0xFF;
key[i++] = (gen >> 8) & 0xFF;
var hash = md5(key, 0, i);
key = hash.subarray(0, Math.min(key.length, 16));
var cipherConstructor = function() {
return new ARCFourCipher(key);
};
return new CipherTransform(cipherConstructor, cipherConstructor);
}
};
return constructor;
})();

View File

@ -34,7 +34,7 @@ var kDisableFonts = false;
* http://cgit.freedesktop.org/poppler/poppler/tree/poppler/GfxFont.cc#n65
*/
var kScalePrecision = 100;
var kScalePrecision = 40;
var Fonts = {
_active: null,
@ -68,8 +68,8 @@ var Fonts = {
var unicode = encoding[charcode];
// Check if the glyph has already been converted
if (unicode instanceof Name)
unicode = encoding[unicode] = GlyphsUnicode[unicode.name];
if (!IsNum(unicode))
unicode = encoding[unicode] = GlyphsUnicode[unicode.name];
// Handle surrogate pairs
if (unicode > 0xFFFF) {
@ -95,7 +95,7 @@ var Fonts = {
}
};
var FontsLoader = {
var FontLoader = {
bind: function(fonts) {
var worker = (typeof window == "undefined");
var ready = true;
@ -105,10 +105,10 @@ var FontsLoader = {
if (Fonts[font.name]) {
ready = ready && !Fonts[font.name].loading;
continue;
} else {
ready = false;
}
ready = false;
var obj = new Font(font.name, font.file, font.properties);
var str = "";
@ -180,6 +180,7 @@ var Font = (function () {
warn("Font " + properties.type + " is not supported");
break;
}
this.data = data;
Fonts[name] = {
data: data,
@ -793,9 +794,18 @@ var Font = (function () {
});
},
bindDOM: function font_bindDom(data) {
bindDOM: function font_bindDom(data, callback) {
var fontName = this.name;
// 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:"' + name +
'";position: absolute;top:-99999;left:-99999;z-index:-99999';
div.setAttribute("style", style);
document.body.appendChild(div);
/** 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
@ -815,15 +825,19 @@ var Font = (function () {
// For some reasons the font has not loaded, so mark it loaded for the
// page to proceed but cry
if ((Date.now() - this.start) >= kMaxWaitForFontFace) {
window.clearInterval(interval);
Fonts[fontName].loading = false;
warn("Is " + fontName + " loaded?");
this.start = 0;
} else if (textWidth != ctx.measureText(testString).width) {
window.clearInterval(interval);
Fonts[fontName].loading = false;
this.start = 0;
if (textWidth == ctx.measureText(testString).width) {
if ((Date.now() - this.start) < kMaxWaitForFontFace) {
return;
} else {
warn("Is " + fontName + " loaded?");
}
}
window.clearInterval(interval);
Fonts[fontName].loading = false;
this.start = 0;
if (callback) {
callback();
}
}, 30, this);

View File

@ -74,6 +74,20 @@ span {
width: 100%;
}
.thumbnailPageNumber {
color: #fff;
font-size: 0.55em;
text-align: right;
margin: -6px 2px 6px 0px;
width: 102px;
}
.thumbnail {
width: 104px;
height: 134px;
margin: 0px auto 10px;
}
.page {
width: 816px;
height: 1056px;
@ -163,27 +177,46 @@ span {
}
#sidebar {
background-color: rgba(0, 0, 0, 0.8);
position: fixed;
width: 150px;
width: 200px;
top: 62px;
bottom: 18px;
left: -170px;
transition: left 0.25s ease-in-out 1s;
-moz-transition: left 0.25s ease-in-out 1s;
-webkit-transition: left 0.25s ease-in-out 1s;
}
#sidebar:hover {
left: 0px;
transition: left 0.25s ease-in-out 0s;
-moz-transition: left 0.25s ease-in-out 0s;
-webkit-transition: left 0.25s ease-in-out 0s;
}
#sidebarBox {
background-color: rgba(0, 0, 0, 0.7);
width: 150px;
height: 100%;
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;
box-shadow: 0px 2px 8px #000;
-moz-box-shadow: 0px 2px 8px #000;
-webkit-box-shadow: 0px 2px 8px #000;
}
#sidebarScrollView {
position: absolute;
overflow: hidden;
overflow-y: auto;
top: 40px;
right: 10px;
top: 10px;
bottom: 10px;
left: 10px;
width: 130px;
}
#sidebarContentView {

View File

@ -6,6 +6,7 @@
<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="crypto.js"></script>
<script type="text/javascript" src="glyphlist.js"></script>
<script type="text/javascript" src="multi_page_viewer.js"></script>
</head>
@ -39,13 +40,16 @@
<span class="label">Open File</span>
</span>
</div>
<!--<div id="sidebar">
<div id="sidebarScrollView">
<div id="sidebarContentView">
<!-- EXPERIMENTAL: Slide-out sidebar with page thumbnails (comment-out to disable) -->
<div id="sidebar">
<div id="sidebarBox">
<div id="sidebarScrollView">
<div id="sidebarContentView"></div>
</div>
</div>
</div>-->
</div>
<div id="viewer"></div>
</body>
</html>

View File

@ -10,6 +10,8 @@ var PDFViewer = {
element: null,
sidebarContentView: null,
previousPageButton: null,
nextPageButton: null,
pageNumberInput: null,
@ -26,41 +28,125 @@ var PDFViewer = {
scale: 1.0,
pageWidth: function() {
return 816 * PDFViewer.scale;
pageWidth: function(page) {
return page.mediaBox[2] * PDFViewer.scale;
},
pageHeight: function() {
return 1056 * PDFViewer.scale;
pageHeight: function(page) {
return page.mediaBox[3] * PDFViewer.scale;
},
lastPagesDrawn: [],
visiblePages: function() {
var pageHeight = PDFViewer.pageHeight() + 20; // Add 20 for the margins.
visiblePages: function() {
const pageBottomMargin = 20;
var windowTop = window.pageYOffset;
var windowBottom = window.pageYOffset + window.innerHeight;
var pageStartIndex = Math.floor(windowTop / pageHeight);
var pageStopIndex = Math.ceil(windowBottom / pageHeight);
var pageHeight, page;
var i, n = PDFViewer.numberOfPages, currentHeight = 0;
for (i = 1; i <= n; i++) {
var page = PDFViewer.pdf.getPage(i);
pageHeight = PDFViewer.pageHeight(page) + pageBottomMargin;
if (currentHeight + pageHeight > windowTop)
break;
currentHeight += pageHeight;
}
var pages = [];
for (var i = pageStartIndex; i <= pageStopIndex; i++) {
pages.push(i + 1);
for (; i <= n && currentHeight < windowBottom; i++) {
var page = PDFViewer.pdf.getPage(i);
pageHeight = PDFViewer.pageHeight(page) + pageBottomMargin;
currentHeight += pageHeight;
pages.push(i);
}
return pages;
},
createThumbnail: function(num) {
if (PDFViewer.sidebarContentView) {
var anchor = document.createElement('a');
anchor.href = '#' + num;
var containerDiv = document.createElement('div');
containerDiv.id = 'thumbnailContainer' + num;
containerDiv.className = 'thumbnail';
var pageNumberDiv = document.createElement('div');
pageNumberDiv.className = 'thumbnailPageNumber';
pageNumberDiv.innerHTML = '' + num;
anchor.appendChild(containerDiv);
PDFViewer.sidebarContentView.appendChild(anchor);
PDFViewer.sidebarContentView.appendChild(pageNumberDiv);
}
},
removeThumbnail: function(num) {
var div = document.getElementById('thumbnailContainer' + num);
if (div) {
while (div.hasChildNodes()) {
div.removeChild(div.firstChild);
}
}
},
drawThumbnail: function(num) {
if (!PDFViewer.pdf)
return;
var div = document.getElementById('thumbnailContainer' + num);
if (div && !div.hasChildNodes()) {
var page = PDFViewer.pdf.getPage(num);
var canvas = document.createElement('canvas');
canvas.id = 'thumbnail' + 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 = 104;
canvas.height = 134;
div.appendChild(canvas);
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);
// 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 = [];
page.compile(gfx, fonts);
var loadFont = function() {
if (!FontLoader.bind(fonts)) {
pageTimeout = window.setTimeout(loadFont, 10);
return;
}
page.display(gfx);
}
loadFont();
}
},
createPage: function(num) {
var page = PDFViewer.pdf.getPage(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';
div.style.width = PDFViewer.pageWidth(page) + 'px';
div.style.height = PDFViewer.pageHeight(page) + 'px';
PDFViewer.element.appendChild(anchor);
PDFViewer.element.appendChild(div);
@ -81,18 +167,18 @@ var PDFViewer = {
return;
var div = document.getElementById('pageContainer' + num);
var canvas = document.createElement('canvas');
if (div && !div.hasChildNodes()) {
var page = PDFViewer.pdf.getPage(num);
var canvas = document.createElement('canvas');
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();
canvas.width = PDFViewer.pageWidth(page);
canvas.height = PDFViewer.pageHeight(page);
div.appendChild(canvas);
var ctx = canvas.getContext('2d');
@ -109,7 +195,7 @@ var PDFViewer = {
page.compile(gfx, fonts);
var loadFont = function() {
if (!FontsLoader.bind(fonts)) {
if (!FontLoader.bind(fonts)) {
pageTimeout = window.setTimeout(loadFont, 10);
return;
}
@ -130,12 +216,9 @@ var PDFViewer = {
if (PDFViewer.pdf) {
for (i = 1; i <= PDFViewer.numberOfPages; i++) {
PDFViewer.createThumbnail(i);
PDFViewer.createPage(i);
}
if (PDFViewer.numberOfPages > 0) {
PDFViewer.drawPage(1);
}
}
for (i = 0; i < PDFViewer.scaleSelect.childNodes; i++) {
@ -153,6 +236,12 @@ var PDFViewer = {
}
PDFViewer.scaleSelect.value = Math.floor(PDFViewer.scale * 100) + '%';
// Clear the array of the last pages drawn to force a redraw.
PDFViewer.lastPagesDrawn = [];
// Jump the scroll position to the correct page.
PDFViewer.goToPage(PDFViewer.pageNumber);
},
goToPage: function(num) {
@ -205,6 +294,10 @@ var PDFViewer = {
PDFViewer.element.removeChild(PDFViewer.element.firstChild);
}
while (PDFViewer.sidebarContentView.hasChildNodes()) {
PDFViewer.sidebarContentView.removeChild(PDFViewer.sidebarContentView.firstChild);
}
PDFViewer.pdf = new PDFDoc(new Stream(data));
PDFViewer.numberOfPages = PDFViewer.pdf.numPages;
document.getElementById('numPages').innerHTML = PDFViewer.numberOfPages.toString();
@ -216,6 +309,13 @@ var PDFViewer = {
if (PDFViewer.numberOfPages > 0) {
PDFViewer.drawPage(1);
document.location.hash = 1;
setTimeout(function() {
for (var i = 1; i <= PDFViewer.numberOfPages; i++) {
PDFViewer.createThumbnail(i);
PDFViewer.drawThumbnail(i);
}
}, 500);
}
PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ? 'disabled' : '';
@ -240,6 +340,8 @@ window.onload = function() {
PDFViewer.element = document.getElementById('viewer');
PDFViewer.sidebarContentView = document.getElementById('sidebarContentView');
PDFViewer.pageNumberInput = document.getElementById('pageNumber');
PDFViewer.pageNumberInput.onkeydown = function(evt) {
var charCode = evt.charCode || evt.keyCode;

1527
pdf.js

File diff suppressed because it is too large Load Diff

View File

@ -1 +1,3 @@
pdf.pdf
intelisa.pdf
openweb_tm-PRINT.pdf

View File

@ -0,0 +1 @@
http://www.intel.com/Assets/PDF/manual/253665.pdf

View File

@ -0,0 +1 @@
http://openweb.flossmanuals.net/materials/openweb_tm-PRINT.pdf

BIN
test/pdfs/sizes.pdf Normal file

Binary file not shown.

View File

@ -0,0 +1,10 @@
[
{
"name":"firefox7",
"path":"/home/sayrer/firefoxen/nightly/firefox"
},
{
"name":"chrome14",
"path":"/opt/google/chrome/chrome"
}
]

View File

@ -6,5 +6,9 @@
{
"name":"firefox6",
"path":"/Users/sayrer/firefoxen/Aurora.app"
},
{
"name":"chrome14",
"path":"/Applications/Google Chrome.app"
}
]

BIN
test/resources/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -32,3 +32,7 @@ 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);
user_pref("extensions.installDistroAddons", false); // prevent testpilot etc
user_pref("browser.safebrowsing.enable", false); // prevent traffic to google servers
user_pref("toolkit.telemetry.prompted", true); // prevent telemetry banner
user_pref("toolkit.telemetry.enabled", false);

View File

@ -0,0 +1,601 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- vim: set shiftwidth=4 tabstop=4 autoindent noexpandtab: -->
<!-- ***** 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 reftest-analyzer.html.
-
- The Initial Developer of the Original Code is the Mozilla Foundation.
- Portions created by the Initial Developer are Copyright (C) 2008
- the Initial Developer. All Rights Reserved.
-
- Contributor(s):
- L. David Baron <dbaron@dbaron.org>, Mozilla Corporation (original author)
-
- 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 LGPL or the GPL. 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 ***** -->
<!--
Features to add:
* make the left and right parts of the viewer independently scrollable
* make the test list filterable
** default to only showing unexpecteds
* add other ways to highlight differences other than circling?
* add zoom/pan to images
* Add ability to load log via XMLHttpRequest (also triggered via URL param)
* color the test list based on pass/fail and expected/unexpected/random/skip
* ability to load multiple logs ?
** rename them by clicking on the name and editing
** turn the test list into a collapsing tree view
** move log loading into popup from viewer UI
-->
<html lang="en-US" xml:lang="en-US" xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Reftest analyzer</title>
<style type="text/css"><![CDATA[
html, body { margin: 0; }
html { padding: 0; }
body { padding: 4px; }
#pixelarea, #itemlist, #images { position: absolute; }
#itemlist, #images { overflow: auto; }
#pixelarea { top: 0; left: 0; width: 320px; height: 84px; overflow: visible }
#itemlist { top: 84px; left: 0; width: 320px; bottom: 0; }
#images { top: 0; bottom: 0; left: 320px; right: 0; }
#leftpane { width: 320px; }
#images { position: fixed; top: 10px; left: 340px; }
form#imgcontrols { margin: 0; display: block; }
#itemlist > table { border-collapse: collapse; }
#itemlist > table > tbody > tr > td { border: 1px solid; padding: 1px; }
/*
#itemlist > table > tbody > tr.pass > td.url { background: lime; }
#itemlist > table > tbody > tr.fail > td.url { background: red; }
*/
#magnification > svg { display: block; width: 84px; height: 84px; }
#pixelinfo { font: small sans-serif; position: absolute; width: 200px; left: 84px; }
#pixelinfo table { border-collapse: collapse; }
#pixelinfo table th { white-space: nowrap; text-align: left; padding: 0; }
#pixelinfo table td { font-family: monospace; padding: 0 0 0 0.25em; }
#pixelhint { display: inline; color: #88f; cursor: help; }
#pixelhint > * { display: none; position: absolute; margin: 8px 0 0 8px; padding: 4px; width: 400px; background: #ffa; color: black; box-shadow: 3px 3px 2px #888; z-index: 1; }
#pixelhint:hover { color: #000; }
#pixelhint:hover > * { display: block; }
#pixelhint p { margin: 0; }
#pixelhint p + p { margin-top: 1em; }
]]></style>
<script type="text/javascript"><![CDATA[
var XLINK_NS = "http://www.w3.org/1999/xlink";
var SVG_NS = "http://www.w3.org/2000/svg";
var gPhases = null;
var gIDCache = {};
var gMagPixPaths = []; // 2D array of array-of-two <path> objects used in the pixel magnifier
var gMagWidth = 5; // number of zoomed in pixels to show horizontally
var gMagHeight = 5; // number of zoomed in pixels to show vertically
var gMagZoom = 16; // size of the zoomed in pixels
var gImage1Data; // ImageData object for the reference image
var gImage2Data; // ImageData object for the test output image
var gFlashingPixels = []; // array of <path> objects that should be flashed due to pixel color mismatch
function ID(id) {
if (!(id in gIDCache))
gIDCache[id] = document.getElementById(id);
return gIDCache[id];
}
function hash_parameters() {
var result = { };
var params = window.location.hash.substr(1).split(/[&;]/);
for (var i = 0; i < params.length; i++) {
var parts = params[i].split("=");
result[parts[0]] = unescape(unescape(parts[1]));
}
return result;
}
function load() {
gPhases = [ ID("entry"), ID("loading"), ID("viewer") ];
build_mag();
var params = hash_parameters();
if (params.log) {
ID("logentry").value = params.log;
log_pasted();
} else if (params.web) {
loadFromWeb(params.web);
}
}
function build_mag() {
var mag = ID("mag");
var r = document.createElementNS(SVG_NS, "rect");
r.setAttribute("x", gMagZoom * -gMagWidth / 2);
r.setAttribute("y", gMagZoom * -gMagHeight / 2);
r.setAttribute("width", gMagZoom * gMagWidth);
r.setAttribute("height", gMagZoom * gMagHeight);
mag.appendChild(r);
mag.setAttribute("transform", "translate(" + (gMagZoom * (gMagWidth / 2) + 1) + "," + (gMagZoom * (gMagHeight / 2) + 1) + ")");
for (var x = 0; x < gMagWidth; x++) {
gMagPixPaths[x] = [];
for (var y = 0; y < gMagHeight; y++) {
var p1 = document.createElementNS(SVG_NS, "path");
p1.setAttribute("d", "M" + ((x - gMagWidth / 2) + 1) * gMagZoom + "," + (y - gMagHeight / 2) * gMagZoom + "h" + -gMagZoom + "v" + gMagZoom);
p1.setAttribute("stroke", "black");
p1.setAttribute("stroke-width", "1px");
p1.setAttribute("fill", "#aaa");
var p2 = document.createElementNS(SVG_NS, "path");
p2.setAttribute("d", "M" + ((x - gMagWidth / 2) + 1) * gMagZoom + "," + (y - gMagHeight / 2) * gMagZoom + "v" + gMagZoom + "h" + -gMagZoom);
p2.setAttribute("stroke", "black");
p2.setAttribute("stroke-width", "1px");
p2.setAttribute("fill", "#888");
mag.appendChild(p1);
mag.appendChild(p2);
gMagPixPaths[x][y] = [p1, p2];
}
}
var flashedOn = false;
setInterval(function() {
flashedOn = !flashedOn;
flash_pixels(flashedOn);
}, 500);
}
function show_phase(phaseid) {
for (var i in gPhases) {
var phase = gPhases[i];
phase.style.display = (phase.id == phaseid) ? "" : "none";
}
if (phase == "viewer")
ID("images").style.display = "none";
}
function loadFromWeb(url) {
var r = new XMLHttpRequest();
r.open("GET", url);
r.onreadystatechange = function() {
if (r.readyState == 4) {
process_log(r.response);
}
}
r.send(null);
}
function fileentry_changed() {
show_phase("loading");
var input = ID("fileentry");
var files = input.files;
if (files.length > 0) {
// Only handle the first file; don't handle multiple selection.
// The parts of the log we care about are ASCII-only. Since we
// can ignore lines we don't care about, best to read in as
// iso-8859-1, which guarantees we don't get decoding errors.
var fileReader = new FileReader();
fileReader.onload = function(e) {
var log = null;
log = e.target.result;
if (log)
process_log(log);
else
show_phase("entry");
}
fileReader.readAsText(files[0], "iso-8859-1");
}
// So the user can process the same filename again (after
// overwriting the log), clear the value on the form input so we
// will always get an onchange event.
input.value = "";
}
function log_pasted() {
show_phase("loading");
var entry = ID("logentry");
var log = entry.value;
entry.value = "";
process_log(log);
}
var gTestItems;
function process_log(contents) {
var lines = contents.split(/[\r\n]+/);
gTestItems = [];
for (var j in lines) {
var line = lines[j];
var match = line.match(/^(?:NEXT ERROR )?REFTEST (.*)$/);
if (!match)
continue;
line = match[1];
match = line.match(/^(TEST-PASS|TEST-UNEXPECTED-PASS|TEST-KNOWN-FAIL|TEST-UNEXPECTED-FAIL)(\(EXPECTED RANDOM\)|) \| ([^\|]+) \|(.*)/);
if (match) {
var state = match[1];
var random = match[2];
var url = match[3];
var extra = match[4];
gTestItems.push(
{
pass: !state.match(/FAIL$/),
// only one of the following three should ever be true
unexpected: !!state.match(/^TEST-UNEXPECTED/),
random: (random == "(EXPECTED RANDOM)"),
skip: (extra == " (SKIP)"),
url: url,
images: []
});
continue;
}
match = line.match(/^ IMAGE[^:]*: (.*)$/);
if (match) {
var item = gTestItems[gTestItems.length - 1];
item.images.push(match[1]);
}
}
build_viewer();
}
function build_viewer() {
if (gTestItems.length == 0) {
show_phase("entry");
return;
}
var cell = ID("itemlist");
while (cell.childNodes.length > 0)
cell.removeChild(cell.childNodes[cell.childNodes.length - 1]);
var table = document.createElement("table");
var tbody = document.createElement("tbody");
table.appendChild(tbody);
for (var i in gTestItems) {
var item = gTestItems[i];
// XXX skip expected pass items until we have filtering UI
if (item.pass && !item.unexpected)
continue;
var tr = document.createElement("tr");
var rowclass = item.pass ? "pass" : "fail";
var td;
var text;
td = document.createElement("td");
text = "";
if (item.unexpected) { text += "!"; rowclass += " unexpected"; }
if (item.random) { text += "R"; rowclass += " random"; }
if (item.skip) { text += "S"; rowclass += " skip"; }
td.appendChild(document.createTextNode(text));
tr.appendChild(td);
td = document.createElement("td");
td.className = "url";
// Only display part of URL after "/mozilla/".
var match = item.url.match(/\/mozilla\/(.*)/);
text = document.createTextNode(match ? match[1] : item.url);
if (item.images.length > 0) {
var a = document.createElement("a");
a.href = "javascript:show_images(" + i + ")";
a.appendChild(text);
td.appendChild(a);
} else {
td.appendChild(text);
}
tr.appendChild(td);
tbody.appendChild(tr);
}
cell.appendChild(table);
show_phase("viewer");
}
function get_image_data(src, whenReady) {
var img = new Image();
img.onload = function() {
var canvas = document.createElement("canvas");
canvas.width = 800;
canvas.height = 1000;
var ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0);
whenReady(ctx.getImageData(0, 0, 800, 1000));
};
img.src = src;
}
function show_images(i) {
var item = gTestItems[i];
var cell = ID("images");
ID("image1").style.display = "";
ID("image2").style.display = "none";
ID("diffrect").style.display = "none";
ID("imgcontrols").reset();
ID("image1").setAttributeNS(XLINK_NS, "xlink:href", item.images[0]);
// Making the href be #image1 doesn't seem to work
ID("feimage1").setAttributeNS(XLINK_NS, "xlink:href", item.images[0]);
if (item.images.length == 1) {
ID("imgcontrols").style.display = "none";
} else {
ID("imgcontrols").style.display = "";
ID("image2").setAttributeNS(XLINK_NS, "xlink:href", item.images[1]);
// Making the href be #image2 doesn't seem to work
ID("feimage2").setAttributeNS(XLINK_NS, "xlink:href", item.images[1]);
}
cell.style.display = "";
get_image_data(item.images[0], function(data) { gImage1Data = data });
get_image_data(item.images[1], function(data) { gImage2Data = data });
}
function show_image(i) {
if (i == 1) {
ID("image1").style.display = "";
ID("image2").style.display = "none";
} else {
ID("image1").style.display = "none";
ID("image2").style.display = "";
}
}
function show_differences(cb) {
ID("diffrect").style.display = cb.checked ? "" : "none";
}
function flash_pixels(on) {
var stroke = on ? "red" : "black";
var strokeWidth = on ? "2px" : "1px";
for (var i = 0; i < gFlashingPixels.length; i++) {
gFlashingPixels[i].setAttribute("stroke", stroke);
gFlashingPixels[i].setAttribute("stroke-width", strokeWidth);
}
}
function cursor_point(evt) {
var m = evt.target.getScreenCTM().inverse();
var p = ID("svg").createSVGPoint();
p.x = evt.clientX;
p.y = evt.clientY;
p = p.matrixTransform(m);
return { x: Math.floor(p.x), y: Math.floor(p.y) };
}
function hex2(i) {
return (i < 16 ? "0" : "") + i.toString(16);
}
function canvas_pixel_as_hex(data, x, y) {
var offset = (y * data.width + x) * 4;
var r = data.data[offset];
var g = data.data[offset + 1];
var b = data.data[offset + 2];
return "#" + hex2(r) + hex2(g) + hex2(b);
}
function hex_as_rgb(hex) {
return "rgb(" + [parseInt(hex.substring(1, 3), 16), parseInt(hex.substring(3, 5), 16), parseInt(hex.substring(5, 7), 16)] + ")";
}
function magnify(evt) {
var { x: x, y: y } = cursor_point(evt);
var centerPixelColor1, centerPixelColor2;
var dx_lo = -Math.floor(gMagWidth / 2);
var dx_hi = Math.floor(gMagWidth / 2);
var dy_lo = -Math.floor(gMagHeight / 2);
var dy_hi = Math.floor(gMagHeight / 2);
flash_pixels(false);
gFlashingPixels = [];
for (var j = dy_lo; j <= dy_hi; j++) {
for (var i = dx_lo; i <= dx_hi; i++) {
var px = x + i;
var py = y + j;
var p1 = gMagPixPaths[i + dx_hi][j + dy_hi][0];
var p2 = gMagPixPaths[i + dx_hi][j + dy_hi][1];
if (px < 0 || py < 0 || px >= 800 || py >= 1000) {
p1.setAttribute("fill", "#aaa");
p2.setAttribute("fill", "#888");
} else {
var color1 = canvas_pixel_as_hex(gImage1Data, x + i, y + j);
var color2 = canvas_pixel_as_hex(gImage2Data, x + i, y + j);
p1.setAttribute("fill", color1);
p2.setAttribute("fill", color2);
if (color1 != color2) {
gFlashingPixels.push(p1, p2);
p1.parentNode.appendChild(p1);
p2.parentNode.appendChild(p2);
}
if (i == 0 && j == 0) {
centerPixelColor1 = color1;
centerPixelColor2 = color2;
}
}
}
}
flash_pixels(true);
show_pixelinfo(x, y, centerPixelColor1, hex_as_rgb(centerPixelColor1), centerPixelColor2, hex_as_rgb(centerPixelColor2));
}
function show_pixelinfo(x, y, pix1rgb, pix1hex, pix2rgb, pix2hex) {
var pixelinfo = ID("pixelinfo");
ID("coords").textContent = [x, y];
ID("pix1hex").textContent = pix1hex;
ID("pix1rgb").textContent = pix1rgb;
ID("pix2hex").textContent = pix2hex;
ID("pix2rgb").textContent = pix2rgb;
}
]]></script>
</head>
<body onload="load()">
<div id="entry">
<h1>Reftest analyzer: load reftest log</h1>
<p>Either paste your log into this textarea:<br />
<textarea cols="80" rows="10" id="logentry"/><br/>
<input type="button" value="Process pasted log" onclick="log_pasted()" /></p>
<p>... or load it from a file:<br/>
<input type="file" id="fileentry" onchange="fileentry_changed()" />
</p>
</div>
<div id="loading" style="display:none">Loading log...</div>
<div id="viewer" style="display:none">
<div id="pixelarea">
<div id="pixelinfo">
<table>
<tbody>
<tr><th>Pixel at:</th><td colspan="2" id="coords"/></tr>
<tr><th>Image 1:</th><td id="pix1rgb"></td><td id="pix1hex"></td></tr>
<tr><th>Image 2:</th><td id="pix2rgb"></td><td id="pix2hex"></td></tr>
</tbody>
</table>
<div>
<div id="pixelhint">
<div>
<p>Move the mouse over the reftest image on the right to show
magnified pixels on the left. The color information above is for
the pixel centered in the magnified view.</p>
<p>Image 1 is shown in the upper triangle of each pixel and Image 2
is shown in the lower triangle.</p>
</div>
</div>
</div>
</div>
<div id="magnification">
<svg xmlns="http://www.w3.org/2000/svg" width="84" height="84" shape-rendering="optimizeSpeed">
<g id="mag"/>
</svg>
</div>
</div>
<div id="itemlist"></div>
<div id="images" style="display:none">
<form id="imgcontrols">
<label><input type="radio" name="which" value="0" onchange="show_image(1)" checked="checked" />Image 1</label>
<label><input type="radio" name="which" value="1" onchange="show_image(2)" />Image 2</label>
<label><input type="checkbox" onchange="show_differences(this)" />Circle differences</label>
</form>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="800px" height="1000px" viewbox="0 0 800 1000" id="svg">
<defs>
<!-- use sRGB to avoid loss of data -->
<filter id="showDifferences" x="0%" y="0%" width="100%" height="100%"
style="color-interpolation-filters: sRGB">
<feImage id="feimage1" result="img1" xlink:href="#image1" />
<feImage id="feimage2" result="img2" xlink:href="#image2" />
<!-- inv1 and inv2 are the images with RGB inverted -->
<feComponentTransfer result="inv1" in="img1">
<feFuncR type="linear" slope="-1" intercept="1" />
<feFuncG type="linear" slope="-1" intercept="1" />
<feFuncB type="linear" slope="-1" intercept="1" />
</feComponentTransfer>
<feComponentTransfer result="inv2" in="img2">
<feFuncR type="linear" slope="-1" intercept="1" />
<feFuncG type="linear" slope="-1" intercept="1" />
<feFuncB type="linear" slope="-1" intercept="1" />
</feComponentTransfer>
<!-- w1 will have non-white pixels anywhere that img2
is brighter than img1, and w2 for the reverse.
It would be nice not to have to go through these
intermediate states, but feComposite
type="arithmetic" can't transform the RGB channels
and leave the alpha channel untouched. -->
<feComposite result="w1" in="img1" in2="inv2" operator="arithmetic" k2="1" k3="1" />
<feComposite result="w2" in="img2" in2="inv1" operator="arithmetic" k2="1" k3="1" />
<!-- c1 will have non-black pixels anywhere that img2
is brighter than img1, and c2 for the reverse -->
<feComponentTransfer result="c1" in="w1">
<feFuncR type="linear" slope="-1" intercept="1" />
<feFuncG type="linear" slope="-1" intercept="1" />
<feFuncB type="linear" slope="-1" intercept="1" />
</feComponentTransfer>
<feComponentTransfer result="c2" in="w2">
<feFuncR type="linear" slope="-1" intercept="1" />
<feFuncG type="linear" slope="-1" intercept="1" />
<feFuncB type="linear" slope="-1" intercept="1" />
</feComponentTransfer>
<!-- c will be nonblack (and fully on) for every pixel+component where there are differences -->
<feComposite result="c" in="c1" in2="c2" operator="arithmetic" k2="255" k3="255" />
<!-- a will be opaque for every pixel with differences and transparent for all others -->
<feColorMatrix result="a" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 0" />
<!-- a, dilated by 4 pixels -->
<feMorphology result="dila4" in="a" operator="dilate" radius="4" />
<!-- a, dilated by 1 pixel -->
<feMorphology result="dila1" in="a" operator="dilate" radius="1" />
<!-- all the pixels in the 3-pixel dilation of a but not in the 1-pixel dilation of a, to highlight the diffs -->
<feComposite result="highlight" in="dila4" in2="dila1" operator="out" />
<feFlood result="red" flood-color="red" />
<feComposite result="redhighlight" in="red" in2="highlight" operator="in" />
<feFlood result="black" flood-color="black" flood-opacity="0.5" />
<feMerge>
<feMergeNode in="black" />
<feMergeNode in="redhighlight" />
</feMerge>
</filter>
</defs>
<g onmousemove="magnify(evt)">
<image x="0" y="0" width="100%" height="100%" id="image1" />
<image x="0" y="0" width="100%" height="100%" id="image2" />
</g>
<rect id="diffrect" filter="url(#showDifferences)" pointer-events="none" x="0" y="0" width="100%" height="100%" />
</svg>
</div>
</div>
</body>
</html>

View File

@ -1,8 +1,8 @@
import json, platform, os, shutil, sys, subprocess, tempfile, threading, urllib, urllib2
import json, platform, os, shutil, sys, subprocess, tempfile, threading, time, urllib, urllib2
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
import SocketServer
from optparse import OptionParser
from urlparse import urlparse
from urlparse import urlparse, parse_qs
USAGE_EXAMPLE = "%prog"
@ -28,6 +28,9 @@ class TestOptions(OptionParser):
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.add_option("--reftest", action="store_true", dest="reftest",
help="Automatically start reftest showing comparison test failures, if there are any.",
default=False)
self.set_usage(USAGE_EXAMPLE)
def verifyOptions(self, options):
@ -37,6 +40,8 @@ class TestOptions(OptionParser):
options.manifestFile = DEFAULT_MANIFEST_FILE
if options.browser and options.browserManifestFile:
print "Warning: ignoring browser argument since manifest file was also supplied"
if not options.browser and not options.browserManifestFile:
self.error("No test browsers found. Use --browserManifest or --browser args.")
return options
def prompt(question):
@ -51,6 +56,8 @@ MIMEs = {
'.json': 'application/json',
'.pdf': 'application/pdf',
'.xhtml': 'application/xhtml+xml',
'.ico': 'image/x-icon',
'.log': 'text/plain'
}
class State:
@ -69,9 +76,10 @@ class State:
eqLog = None
class Result:
def __init__(self, snapshot, failure):
def __init__(self, snapshot, failure, page):
self.snapshot = snapshot
self.failure = failure
self.page = page
class TestServer(SocketServer.TCPServer):
allow_reuse_address = True
@ -83,6 +91,14 @@ class PDFTestHandler(BaseHTTPRequestHandler):
if VERBOSE:
BaseHTTPRequestHandler.log_request(code, size)
def sendFile(self, path, ext):
self.send_response(200)
self.send_header("Content-Type", MIMEs[ext])
self.send_header("Content-Length", os.path.getsize(path))
self.end_headers()
with open(path) as f:
self.wfile.write(f.read())
def do_GET(self):
url = urlparse(self.path)
# Ignore query string
@ -91,9 +107,14 @@ class PDFTestHandler(BaseHTTPRequestHandler):
prefix = os.path.commonprefix(( path, DOC_ROOT ))
_, ext = os.path.splitext(path)
if url.path == "/favicon.ico":
self.sendFile(os.path.join(DOC_ROOT, "test", "resources", "favicon.ico"), ext)
return
if not (prefix == DOC_ROOT
and os.path.isfile(path)
and ext in MIMEs):
print path
self.send_error(404)
return
@ -102,15 +123,7 @@ class PDFTestHandler(BaseHTTPRequestHandler):
self.send_error(501)
return
self.send_response(200)
self.send_header("Content-Type", MIMEs[ext])
self.end_headers()
# Sigh, os.sendfile() plz
f = open(path)
self.wfile.write(f.read())
f.close()
self.sendFile(path, ext)
def do_POST(self):
numBytes = int(self.headers['Content-Length'])
@ -119,13 +132,28 @@ class PDFTestHandler(BaseHTTPRequestHandler):
self.send_header('Content-Type', 'text/plain')
self.end_headers()
url = urlparse(self.path)
if url.path == "/tellMeToQuit":
tellAppToQuit(url.path, url.query)
return
result = json.loads(self.rfile.read(numBytes))
browser, id, failure, round, page, snapshot = result['browser'], result['id'], result['failure'], result['round'], result['page'], result['snapshot']
taskResults = State.taskResults[browser][id]
taskResults[round].append(Result(snapshot, failure))
assert len(taskResults[round]) == page
taskResults[round].append(Result(snapshot, failure, page))
if result['taskDone']:
def isTaskDone():
numPages = result["numPages"]
rounds = State.manifest[id]["rounds"]
for round in range(0,rounds):
if len(taskResults[round]) < numPages:
return False
return True
if isTaskDone():
# sort the results since they sometimes come in out of order
for results in taskResults:
results.sort(key=lambda result: result.page)
check(State.manifest[id], taskResults, browser)
# Please oh please GC this ...
del State.taskResults[browser][id]
@ -133,11 +161,25 @@ class PDFTestHandler(BaseHTTPRequestHandler):
State.done = (0 == State.remaining)
# this just does Firefox for now
class BrowserCommand():
# Applescript hack to quit Chrome on Mac
def tellAppToQuit(path, query):
if platform.system() != "Darwin":
return
d = parse_qs(query)
path = d['path'][0]
cmd = """osascript<<END
tell application "%s"
quit
end tell
END""" % path
os.system(cmd)
class BaseBrowserCommand(object):
def __init__(self, browserRecord):
self.name = browserRecord["name"]
self.path = browserRecord["path"]
self.tempDir = None
self.process = None
if platform.system() == "Darwin" and (self.path.endswith(".app") or self.path.endswith(".app/")):
self._fixupMacPath()
@ -145,31 +187,86 @@ class BrowserCommand():
if not os.path.exists(self.path):
throw("Path to browser '%s' does not exist." % self.path)
def setup(self):
self.tempDir = tempfile.mkdtemp()
self.profileDir = os.path.join(self.tempDir, "profile")
def teardown(self):
# If the browser is still running, wait up to ten seconds for it to quit
if self.process and self.process.poll() is None:
checks = 0
while self.process.poll() is None and checks < 20:
checks += 1
time.sleep(.5)
# If it's still not dead, try to kill it
if self.process.poll() is None:
print "Process %s is still running. Killing." % self.name
self.process.kill()
if self.tempDir is not None and os.path.exists(self.tempDir):
shutil.rmtree(self.tempDir)
def start(self, url):
raise Exception("Can't start BaseBrowserCommand")
class FirefoxBrowserCommand(BaseBrowserCommand):
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
super(FirefoxBrowserCommand, self).setup()
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)
self.process = subprocess.Popen(cmds)
class ChromeBrowserCommand(BaseBrowserCommand):
def _fixupMacPath(self):
self.path = os.path.join(self.path, "Contents", "MacOS", "Google Chrome")
def start(self, url):
cmds = [self.path]
cmds.extend(["--user-data-dir=%s" % self.profileDir,
"--no-first-run", "--disable-sync", url])
self.process = subprocess.Popen(cmds)
def makeBrowserCommand(browser):
path = browser["path"].lower()
name = browser["name"].lower()
if name.find("firefox") > -1 or path.find("firefox") > -1:
return FirefoxBrowserCommand(browser)
elif name.find("chrom") > -1 or path.find("chrom") > -1:
return ChromeBrowserCommand(browser)
else:
raise Exception("Unrecognized browser: %s" % browser)
def makeBrowserCommands(browserManifestFile):
with open(browserManifestFile) as bmf:
browsers = [BrowserCommand(browser) for browser in json.load(bmf)]
browsers = [makeBrowserCommand(browser) for browser in json.load(bmf)]
return browsers
def downloadLinkedPDFs(manifestList):
for item in manifestList:
f, isLink = item['file'], item.get('link', False)
if isLink and not os.access(f, os.R_OK):
linkFile = open(f +'.link')
link = linkFile.read()
linkFile.close()
sys.stdout.write('Downloading '+ link +' to '+ f +' ...')
sys.stdout.flush()
response = urllib2.urlopen(link)
with open(f, 'w') as out:
out.write(response.read())
print 'done'
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')
@ -187,29 +284,13 @@ def setUp(options):
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."
testBrowsers = [BrowserCommand({"path":options.browser, "name":"firefox"})]
assert len(testBrowsers) > 0
with open(options.manifestFile) as mf:
manifestList = json.load(mf)
for item in manifestList:
f, isLink = item['file'], item.get('link', False)
if isLink and not os.access(f, os.R_OK):
linkFile = open(f +'.link')
link = linkFile.read()
linkFile.close()
sys.stdout.write('Downloading '+ link +' to '+ f +' ...')
sys.stdout.flush()
response = urllib2.urlopen(link)
out = open(f, 'w')
out.write(response.read())
out.close()
print 'done'
downloadLinkedPDFs(manifestList)
for b in testBrowsers:
State.taskResults[b.name] = { }
@ -223,14 +304,24 @@ def setUp(options):
State.remaining = len(testBrowsers) * len(manifestList)
for b in testBrowsers:
return testBrowsers
def startBrowsers(browsers, options):
for b in browsers:
b.setup()
print 'Launching', b.name
qs = 'browser='+ urllib.quote(b.name) +'&manifestFile='+ urllib.quote(options.manifestFile)
qs += '&path=' + b.path
b.start('http://localhost:8080/test/test_slave.html?'+ qs)
def teardownBrowsers(browsers):
for b in browsers:
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()
except:
print "Error cleaning up after browser at ", b.path
print "Temp dir was ", b.tempDir
print "Error:", sys.exc_info()[0]
def check(task, results, browser):
failed = False
@ -283,19 +374,17 @@ def checkEq(task, results, browser):
eq = (ref == snapshot)
if not eq:
print 'TEST-UNEXPECTED-FAIL | eq', taskId, '| in', browser, '| rendering of page', page + 1, '!= reference rendering'
# XXX need to dump this always, somehow, when we have
# the reference repository
if State.masterMode:
if not State.eqLog:
State.eqLog = open(EQLOG_FILE, 'w')
eqLog = State.eqLog
# NB: this follows the format of Mozilla reftest
# output so that we can reuse its reftest-analyzer
# script
print >>eqLog, 'REFTEST TEST-UNEXPECTED-FAIL |', browser +'-'+ taskId +'-page'+ str(page + 1), '| image comparison (==)'
print >>eqLog, 'REFTEST IMAGE 1 (TEST):', snapshot
print >>eqLog, 'REFTEST IMAGE 2 (REFERENCE):', ref
if not State.eqLog:
State.eqLog = open(EQLOG_FILE, 'w')
eqLog = State.eqLog
# NB: this follows the format of Mozilla reftest
# output so that we can reuse its reftest-analyzer
# script
print >>eqLog, 'REFTEST TEST-UNEXPECTED-FAIL |', browser +'-'+ taskId +'-page'+ str(page + 1), '| image comparison (==)'
print >>eqLog, 'REFTEST IMAGE 1 (TEST):', snapshot
print >>eqLog, 'REFTEST IMAGE 2 (REFERENCE):', ref
passed = False
State.numEqFailures += 1
@ -372,8 +461,20 @@ def processResults():
print 'done'
def startReftest(browser):
url = "http://127.0.0.1:8080/test/resources/reftest-analyzer.xhtml"
url += "#web=/test/eq.log"
try:
browser.setup()
browser.start(url)
print "Waiting for browser..."
browser.process.wait()
finally:
teardownBrowsers([browser])
print "Completed reftest usage."
def main():
t1 = time.time()
optionParser = TestOptions()
options, args = optionParser.parse_args()
options = optionParser.verifyOptions(options)
@ -385,8 +486,20 @@ def main():
httpd_thread.setDaemon(True)
httpd_thread.start()
setUp(options)
processResults()
browsers = setUp(options)
try:
startBrowsers(browsers, options)
while not State.done:
time.sleep(1)
processResults()
finally:
teardownBrowsers(browsers)
t2 = time.time()
print "Runtime was", int(t2 - t1), "seconds"
if options.reftest and State.numEqFailures > 0:
print "\nStarting reftest harness to examine %d eq test failures." % State.numEqFailures
startReftest(browsers[0])
if __name__ == '__main__':
main()

View File

@ -14,10 +14,27 @@
"rounds": 1,
"type": "load"
},
{ "id": "intelisa-load",
"file": "pdfs/intelisa.pdf",
"link": true,
"rounds": 1,
"type": "load"
},
{ "id": "pdfspec-load",
"file": "pdfs/pdf.pdf",
"link": true,
"rounds": 1,
"type": "load"
},
{ "id": "sizes",
"file": "pdfs/sizes.pdf",
"rounds": 1,
"type": "eq"
},
{ "id": "openweb-cover",
"file": "pdfs/openweb_tm-PRINT.pdf",
"link": true,
"rounds": 1,
"type": "eq"
}
]

View File

@ -6,7 +6,7 @@
<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;
var appPath, browser, canvas, currentTask, currentTaskIdx, failure, manifest, numPages, pdfDoc, stdout;
function queryParams() {
var qs = window.location.search.substring(1);
@ -23,15 +23,13 @@ function load() {
var params = queryParams();
browser = params.browser;
manifestFile = params.manifestFile;
appPath = params.path;
canvas = document.createElement("canvas");
// 8.5x11in @ 100% ... XXX need something better here
canvas.width = 816;
canvas.height = 1056;
canvas.mozOpaque = true;
stdout = document.getElementById("stdout");
log("Harness thinks this browser is '"+ browser +"'\n");
log("Harness thinks this browser is '"+ browser + "' with path " + appPath + "\n");
log("Fetching manifest "+ manifestFile +"...");
var r = new XMLHttpRequest();
@ -63,7 +61,15 @@ function nextTask() {
if (r.readyState == 4) {
var data = r.mozResponseArrayBuffer || r.mozResponse ||
r.responseArrayBuffer || r.response;
pdfDoc = new PDFDoc(new Stream(data));
try {
pdfDoc = new PDFDoc(new Stream(data));
numPages = pdfDoc.numPages;
} catch(e) {
numPages = 1;
failure = 'load PDF doc: '+ e.toString();
}
currentTask.pageNum = 1, nextPage();
}
};
@ -71,7 +77,7 @@ function nextTask() {
}
function nextPage() {
if (currentTask.pageNum > pdfDoc.numPages) {
if (currentTask.pageNum > numPages) {
if (++currentTask.round < currentTask.rounds) {
log(" Round "+ (1 + currentTask.round) +"\n");
currentTask.pageNum = 1;
@ -85,46 +91,47 @@ function nextPage() {
log(" loading page "+ currentTask.pageNum +"... ");
var ctx = canvas.getContext("2d");
clear(ctx);
var fonts = [];
var fontsReady = true;
var gfx = new CanvasGraphics(ctx);
var gfx = null;
try {
gfx = new CanvasGraphics(ctx);
currentPage = pdfDoc.getPage(currentTask.pageNum);
currentPage.compile(gfx, fonts);
// Inspect fonts and translate the missing ones
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;
}
new Font(font.name, font.file, font.properties);
fontsReady = false;
}
} catch(e) {
failure = 'compile: '+ e.toString();
}
var checkFontsLoadedIntervalTimer = null;
try {
// using mediaBox for the canvas size
var wTwips = (currentPage.mediaBox[2] - currentPage.mediaBox[0]);
var hTwips = (currentPage.mediaBox[3] - currentPage.mediaBox[1]);
canvas.width = wTwips * 96.0 / 72.0;
canvas.height = hTwips * 96.0 / 72.0;
clear(ctx);
} catch(e) {
failure = 'page setup: '+ e.toString();
}
var fontLoaderTimer = null;
function checkFontsLoaded() {
for (var i = 0; i < count; i++) {
if (Fonts[font.name].loading) {
try {
if (!FontLoader.bind(fonts)) {
fontLoaderTimer = window.setTimeout(checkFontsLoaded, 10);
return;
}
} catch(e) {
failure = 'fonts: '+ e.toString();
}
window.clearInterval(checkFontsLoadedIntervalTimer);
snapshotCurrentPage(gfx);
}
if (failure || fontsReady) {
if (failure) {
// Skip font loading if there was a failure, since the fonts might
// be in an inconsistent state.
snapshotCurrentPage(gfx);
} else {
checkFontsLoadedIntervalTimer = setInterval(checkFontsLoaded, 10);
checkFontsLoaded();
}
}
@ -139,31 +146,49 @@ function snapshotCurrentPage(gfx) {
}
}
currentTask.taskDone = (currentTask.pageNum == pdfDoc.numPages
&& (1 + currentTask.round) == currentTask.rounds);
sendTaskResult(canvas.toDataURL("image/png"));
log("done"+ (failure ? " (failed!)" : "") +"\n");
++currentTask.pageNum, nextPage();
}
function done() {
log("Done!\n");
// Set up the next request
backoff = (inFlightRequests > 0) ? inFlightRequests * 10 : 0;
setTimeout(function() {
document.body.innerHTML = "Tests are finished. <h1>CLOSE ME!</h1>";
if (window.SpecialPowers)
SpecialPowers.quitApplication();
else
window.close();
++currentTask.pageNum, nextPage();
},
100
backoff
);
}
function sendQuitRequest() {
var r = new XMLHttpRequest();
r.open("POST", "/tellMeToQuit?path=" + escape(appPath), false);
r.send("");
}
function quitApp() {
log("Done!");
document.body.innerHTML = "Tests are finished. <h1>CLOSE ME!</h1>";
if (window.SpecialPowers) {
SpecialPowers.quitApplication();
} else {
sendQuitRequest();
window.close();
}
}
function done() {
if (inFlightRequests > 0) {
document.getElementById("inFlightCount").innerHTML = inFlightRequests;
setTimeout(done, 100);
} else {
setTimeout(quitApp, 100);
}
}
var inFlightRequests = 0;
function sendTaskResult(snapshot) {
var result = { browser: browser,
id: currentTask.id,
taskDone: currentTask.taskDone,
numPages: numPages,
failure: failure,
file: currentTask.file,
round: currentTask.round,
@ -172,29 +197,41 @@ 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", true);
r.setRequestHeader("Content-Type", "application/json");
// XXX async
r.onreadystatechange = function(e) {
if (r.readyState == 4) {
inFlightRequests--;
}
}
document.getElementById("inFlightCount").innerHTML = inFlightRequests++;
r.send(JSON.stringify(result));
}
function clear(ctx) {
var ctx = canvas.getContext("2d");
ctx.save();
ctx.fillStyle = "rgb(255, 255, 255)";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.restore();
}
/* Auto-scroll if the scrollbar is near the bottom, otherwise do nothing. */
function checkScrolling() {
if ((stdout.scrollHeight - stdout.scrollTop) <= stdout.offsetHeight) {
stdout.scrollTop = stdout.scrollHeight;
}
}
function log(str) {
stdout.innerHTML += str;
window.scrollTo(0, stdout.getBoundingClientRect().bottom);
checkScrolling();
}
</script>
</head>
<body onload="load();">
<pre id="stdout"></pre>
<pre style="width:800; height:800; overflow: scroll;"id="stdout"></pre>
<p>Inflight requests: <span id="inFlightCount"></span></p>
</body>
</html>

View File

@ -7,6 +7,7 @@
<script type="text/javascript" src="pdf.js"></script>
<script type="text/javascript" src="utils/fonts_utils.js"></script>
<script type="text/javascript" src="fonts.js"></script>
<script type="text/javascript" src="crypto.js"></script>
<script type="text/javascript" src="glyphlist.js"></script>
</head>

View File

@ -63,6 +63,10 @@ function displayPage(num) {
canvas.width = parseInt(canvas.getAttribute("defaultwidth")) * pageScale;
canvas.height = parseInt(canvas.getAttribute("defaultheight")) * pageScale;
// scale canvas by 2
canvas.width = 2 * page.mediaBox[2];
canvas.hieght = 2 * page.mediaBox[3];
var t1 = Date.now();
var ctx = canvas.getContext("2d");
ctx.save();
@ -79,7 +83,7 @@ function displayPage(num) {
var t2 = Date.now();
function loadFont() {
if (!FontsLoader.bind(fonts)) {
if (!FontLoader.bind(fonts)) {
pageTimeout = window.setTimeout(loadFont, 10);
return;
}

View File

@ -1,7 +1,10 @@
<html>
<head>
<title>Simple pdf.js page worker viewer</title>
<script type="text/javascript" src="worker_client.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="worker/client.js"></script>
<script>

View File

@ -119,7 +119,8 @@ function CanvasProxy(width, height) {
"$addCurrentX",
"$saveCurrentX",
"$restoreCurrentX",
"$showText"
"$showText",
"$setFont"
];
function buildFuncCall(name) {

View File

@ -18,12 +18,124 @@ if (typeof console.time == "undefined") {
};
}
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 (name in data) {
// Update the
Fonts[name].properties = {
encoding: data[name].encoding
}
// Call `Font.prototype.bindDOM` to make the font get loaded on the page.
Font.prototype.bindDOM.call(
Fonts[name],
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;
}
// Store only the data on Fonts that is needed later on, such that we
// hold track on as lease memory as possible.
Fonts[font.name] = {
name: font.name,
mimetype: font.mimetype,
// This is set later on the worker replay. For some fonts, the encoding
// is calculated during the conversion process happening on the worker
// and therefore is not available right now.
// properties: {
// encoding: font.properties.encoding
// },
cache: Object.create(null)
};
// Mark this font to be handled later.
notLoaded.push(font);
// Increate the number of fonts to wait for.
this.fontsWaiting++;
}
console.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('pdf_worker.js');
this.worker = new Worker('worker/pdf.js');
this.fontWorker = new FontWorker();
this.waitingForFonts = false;
this.waitingForFontsCallback = [];
this.numPage = 1;
this.numPages = null;
@ -56,6 +168,7 @@ function WorkerPDFDoc(canvas) {
},
"$showText": function(y, text) {
text = Fonts.charsToUnicode(text);
this.translate(currentX, -1 * y);
this.fillText(text, 0, 0);
currentX += this.measureText(text).width;
@ -136,6 +249,10 @@ function WorkerPDFDoc(canvas) {
throw "Pattern not found";
}
this.strokeStyle = pattern;
},
"$setFont": function(name) {
Fonts.active = name;
}
}
@ -187,6 +304,18 @@ function WorkerPDFDoc(canvas) {
div.setAttribute("style", style);
document.body.appendChild(div);
},
"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();
@ -207,11 +336,9 @@ function WorkerPDFDoc(canvas) {
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() {
var renderData = function() {
if (id == 0) {
console.time("canvas rendering");
console.time("main canvas rendering");
var ctx = this.ctx;
ctx.save();
ctx.fillStyle = "rgb(255, 255, 255)";
@ -219,12 +346,27 @@ function WorkerPDFDoc(canvas) {
ctx.restore();
}
renderProxyCanvas(canvasList[id], cmdQueue);
if (id == 0) console.timeEnd("canvas rendering")
}, 0, this);
if (id == 0) {
console.timeEnd("main canvas rendering");
console.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();
}
}
}
// List to the WebWorker for data and call actionHandler on it.
// Listen to the WebWorker for data and call actionHandler on it.
this.worker.onmessage = function(event) {
var data = event.data;
if (data.action in actionHandler) {
@ -232,7 +374,7 @@ function WorkerPDFDoc(canvas) {
} else {
throw "Unkown action from worker: " + data.action;
}
}
}.bind(this)
}
WorkerPDFDoc.prototype.open = function(url, callback) {
@ -255,6 +397,8 @@ WorkerPDFDoc.prototype.open = function(url, callback) {
WorkerPDFDoc.prototype.showPage = function(numPage) {
this.numPage = parseInt(numPage);
console.log("=== start rendering page " + numPage + " ===");
console.time(">>> total page display time:");
this.worker.postMessage(numPage);
if (this.onChangePage) {
this.onChangePage(numPage);

27
worker/console.js Normal file
View File

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

65
worker/font.js Normal file
View File

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

View File

@ -27,10 +27,11 @@ var console = {
}
//
importScripts("canvas_proxy.js");
importScripts("pdf.js");
importScripts("fonts.js");
importScripts("glyphlist.js")
importScripts("console.js")
importScripts("canvas.js");
importScripts("../pdf.js");
importScripts("../fonts.js");
importScripts("../glyphlist.js")
// Use the JpegStreamProxy proxy.
JpegStream = JpegStreamProxy;
@ -65,21 +66,14 @@ onmessage = function(event) {
page.compile(gfx, fonts);
console.timeEnd("compile");
// Send fonts to the main thread.
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);
}
postMessage({
action: "fonts",
data: fonts
});
console.timeEnd("fonts");
console.time("display");
page.display(gfx);
canvas.flush();