Merge branch 'master' into faxstream

This commit is contained in:
sbarman 2011-06-24 21:05:59 -07:00
commit 548f481062
13 changed files with 672 additions and 424 deletions

260
crypto.js Normal file
View File

@ -0,0 +1,260 @@
/* -*- Mode: Java; tab-width: s; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
/* vim: set shiftwidth=s 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() {
const 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]);
const 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) {
const 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;
})();

341
fonts.js
View File

@ -80,6 +80,36 @@ var Fonts = {
}
};
var FontLoader = {
bind: function(fonts) {
var worker = (typeof window == "undefined");
var ready = true;
for (var i = 0; i < fonts.length; i++) {
var font = fonts[i];
if (Fonts[font.name]) {
ready = ready && !Fonts[font.name].loading;
continue;
}
ready = false;
var obj = new Font(font.name, font.file, font.properties);
var str = "";
var data = Fonts[font.name].data;
var length = data.length;
for (var j = 0; j < length; j++)
str += String.fromCharCode(data[j]);
worker ? obj.bindWorker(str) : obj.bindDOM(str);
}
return ready;
}
};
/**
* 'Font' is the class the outside world should use, it encapsulate all the font
* decoding logics whatever type it is (assuming the font type is supported).
@ -113,13 +143,14 @@ var Font = (function () {
return;
}
var data;
switch (properties.type) {
case "Type1":
var cff = new CFF(name, file, properties);
this.mimetype = "font/opentype";
// Wrap the CFF data inside an OTF font file
this.font = this.convert(name, cff, properties);
data = this.convert(name, cff, properties);
break;
case "TrueType":
@ -127,7 +158,7 @@ var Font = (function () {
// Repair the TrueType file if it is can be damaged in the point of
// view of the sanitizer
this.font = this.checkAndRepair(name, file, properties);
data = this.checkAndRepair(name, file, properties);
break;
default:
@ -135,28 +166,12 @@ var Font = (function () {
break;
}
var data = this.font;
Fonts[name] = {
data: data,
properties: properties,
loading: true,
cache: Object.create(null)
}
// 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) {
@ -310,57 +325,60 @@ var Font = (function () {
idDeltas + idRangeOffsets + glyphsIds);
};
function createOS2Table() {
var OS2 = stringToArray(
"\x00\x03" + // version
"\x02\x24" + // xAvgCharWidth
"\x01\xF4" + // usWeightClass
"\x00\x05" + // usWidthClass
"\x00\x00" + // fstype
"\x02\x8A" + // ySubscriptXSize
"\x02\xBB" + // ySubscriptYSize
"\x00\x00" + // ySubscriptXOffset
"\x00\x8C" + // ySubscriptYOffset
"\x02\x8A" + // ySuperScriptXSize
"\x02\xBB" + // ySuperScriptYSize
"\x00\x00" + // ySuperScriptXOffset
"\x01\xDF" + // ySuperScriptYOffset
"\x00\x31" + // yStrikeOutSize
"\x01\x02" + // yStrikeOutPosition
"\x00\x00" + // sFamilyClass
"\x02\x00\x06\x03\x00\x00\x00\x00\x00\x00" + // Panose
"\xFF\xFF\xFF\xFF" + // ulUnicodeRange1 (Bits 0-31)
"\xFF\xFF\xFF\xFF" + // ulUnicodeRange1 (Bits 32-63)
"\xFF\xFF\xFF\xFF" + // ulUnicodeRange1 (Bits 64-95)
"\xFF\xFF\xFF\xFF" + // ulUnicodeRange1 (Bits 96-127)
"\x2A\x32\x31\x2A" + // achVendID
"\x00\x20" + // fsSelection
"\x00\x2D" + // usFirstCharIndex
"\x00\x7A" + // usLastCharIndex
"\x00\x03" + // sTypoAscender
"\x00\x20" + // sTypeDescender
"\x00\x38" + // sTypoLineGap
"\x00\x5A" + // usWinAscent
"\x02\xB4" + // usWinDescent
"\x00\xCE\x00\x00" + // ulCodePageRange1 (Bits 0-31)
"\x00\x01\x00\x00" + // ulCodePageRange2 (Bits 32-63)
"\x00\x00" + // sxHeight
"\x00\x00" + // sCapHeight
"\x00\x01" + // usDefaultChar
"\x00\xCD" + // usBreakChar
"\x00\x02" // usMaxContext
);
return OS2;
function createOS2Table(properties) {
return "\x00\x03" + // version
"\x02\x24" + // xAvgCharWidth
"\x01\xF4" + // usWeightClass
"\x00\x05" + // usWidthClass
"\x00\x00" + // fstype
"\x02\x8A" + // ySubscriptXSize
"\x02\xBB" + // ySubscriptYSize
"\x00\x00" + // ySubscriptXOffset
"\x00\x8C" + // ySubscriptYOffset
"\x02\x8A" + // ySuperScriptXSize
"\x02\xBB" + // ySuperScriptYSize
"\x00\x00" + // ySuperScriptXOffset
"\x01\xDF" + // ySuperScriptYOffset
"\x00\x31" + // yStrikeOutSize
"\x01\x02" + // yStrikeOutPosition
"\x00\x00" + // sFamilyClass
"\x02\x00\x06\x03\x00\x00\x00\x00\x00\x00" + // Panose
"\xFF\xFF\xFF\xFF" + // ulUnicodeRange1 (Bits 0-31)
"\xFF\xFF\xFF\xFF" + // ulUnicodeRange1 (Bits 32-63)
"\xFF\xFF\xFF\xFF" + // ulUnicodeRange1 (Bits 64-95)
"\xFF\xFF\xFF\xFF" + // ulUnicodeRange1 (Bits 96-127)
"\x2A\x32\x31\x2A" + // achVendID
"\x00\x20" + // fsSelection
"\x00\x2D" + // usFirstCharIndex
"\x00\x7A" + // usLastCharIndex
"\x00\x03" + // sTypoAscender
"\x00\x20" + // sTypeDescender
"\x00\x38" + // sTypoLineGap
string16(properties.ascent) + // usWinAscent
string16(properties.descent) + // usWinDescent
"\x00\xCE\x00\x00" + // ulCodePageRange1 (Bits 0-31)
"\x00\x01\x00\x00" + // ulCodePageRange2 (Bits 32-63)
string16(properties.xHeight) + // sxHeight
string16(properties.capHeight) + // sCapHeight
"\x00\x01" + // usDefaultChar
"\x00\xCD" + // usBreakChar
"\x00\x02"; // usMaxContext
};
function createPostTable(properties) {
TODO("Fill with real values from the font dict");
return "\x00\x03\x00\x00" + // Version number
string32(properties.italicAngle) + // italicAngle
"\x00\x00" + // underlinePosition
"\x00\x00" + // underlineThickness
"\x00\x00\x00\x00" + // isFixedPitch
"\x00\x00\x00\x00" + // minMemType42
"\x00\x00\x00\x00" + // maxMemType42
"\x00\x00\x00\x00" + // minMemType1
"\x00\x00\x00\x00"; // maxMemType1
};
/**
* A bunch of the OpenType code is duplicate between this class and the
* TrueType code, this is intentional and will merge in a future version
* where all the code relative to OpenType will probably have its own
* class and will take decision without the Fonts consent.
* But at the moment it allows to develop around the TrueType rewriting
* on the fly without messing up with the 'regular' Type1 to OTF conversion.
*/
constructor.prototype = {
name: null,
font: null,
@ -418,10 +436,11 @@ var Font = (function () {
var length = FontsUtils.bytesToInteger(font.getBytes(2));
var language = FontsUtils.bytesToInteger(font.getBytes(2));
if ((format == 0 && numTables == 1) ||
if ((format == 0 && numTables == 1) ||
(format == 6 && numTables == 1 && !properties.encoding.empty)) {
// Format 0 alone is not allowed by the sanitizer so let's rewrite
// that to a 3-1-4 Unicode BMP table
TODO("Use an other source of informations than charset here, it is not reliable");
var charset = properties.charset;
var glyphs = [];
for (var j = 0; j < charset.length; j++) {
@ -525,10 +544,9 @@ var Font = (function () {
createOpenTypeHeader("\x00\x01\x00\x00", ttf, offsets, numTables);
// Insert the missing table
var OS2 = createOS2Table();
tables.push({
tag: "OS/2",
data: OS2
data: stringToArray(createOS2Table(properties))
});
// Replace the old CMAP table with a shiny new one
@ -536,20 +554,9 @@ var Font = (function () {
// Rewrite the 'post' table if needed
if (!post) {
post =
"\x00\x03\x00\x00" + // Version number
"\x00\x00\x01\x00" + // italicAngle
"\x00\x00" + // underlinePosition
"\x00\x00" + // underlineThickness
"\x00\x00\x00\x00" + // isFixedPitch
"\x00\x00\x00\x00" + // minMemType42
"\x00\x00\x00\x00" + // maxMemType42
"\x00\x00\x00\x00" + // minMemType1
"\x00\x00\x00\x00"; // maxMemType1
tables.unshift({
tables.push({
tag: "post",
data: stringToArray(post)
data: stringToArray(createPostTable(properties))
});
}
@ -593,16 +600,16 @@ var Font = (function () {
return font.getBytes();
},
convert: function font_convert(name, font, properties) {
convert: function font_convert(fontName, font, properties) {
var otf = new Uint8Array(kMaxFontFileSize);
function createNameTable(name) {
var names = [
"See original licence", // Copyright
name, // Font family
fontName, // Font family
"undefined", // Font subfamily (font weight)
"uniqueID", // Unique ID
name, // Full font name
fontName, // Full font name
"0.1", // Version
"undefined", // Postscript name
"undefined", // Trademark
@ -610,7 +617,7 @@ var Font = (function () {
"undefined" // Designer
];
var name =
var nameTable =
"\x00\x00" + // format
"\x00\x0A" + // Number of names Record
"\x00\x7E"; // Storage
@ -627,21 +634,21 @@ var Font = (function () {
"\x00\x00" + // name ID
string16(str.length) +
string16(strOffset);
name += nameRecord;
nameTable += nameRecord;
strOffset += str.length;
}
name += names.join("");
return name;
nameTable += names.join("");
return nameTable;
}
// Required Tables
var CFF =
font.data, // PostScript Font Program
font.data, // PostScript Font Program
OS2, // OS/2 and Windows Specific metrics
cmap, // Character to glyphs mapping
head, // Font eader
head, // Font header
hhea, // Horizontal header
hmtx, // Horizontal metrics
maxp, // Maximum profile
@ -665,13 +672,11 @@ var Font = (function () {
createTableEntry(otf, offsets, "CFF ", CFF);
/** OS/2 */
OS2 = createOS2Table();
OS2 = stringToArray(createOS2Table(properties));
createTableEntry(otf, offsets, "OS/2", OS2);
//XXX Getting charstrings here seems wrong since this is another CFF glue
var charstrings = font.getOrderedCharStrings(properties.glyphs);
/** CMAP */
var charstrings = font.charstrings;
cmap = createCMapTable(charstrings);
createTableEntry(otf, offsets, "cmap", cmap);
@ -720,11 +725,15 @@ var Font = (function () {
createTableEntry(otf, offsets, "hhea", hhea);
/** HMTX */
hmtx = "\x01\xF4\x00\x00";
/* For some reasons, probably related to how the backend handle fonts,
* Linux seems to ignore this file and prefer the data from the CFF itself
* while Windows use this data. So be careful if you hack on Linux and
* have to touch the 'hmtx' table
*/
hmtx = "\x01\xF4\x00\x00"; // Fake .notdef
var width = 0, lsb = 0;
for (var i = 0; i < charstrings.length; i++) {
var charstring = charstrings[i].charstring;
var width = charstring[1];
var lsb = charstring[0];
width = charstrings[i].charstring[1];
hmtx += string16(width) + string16(lsb);
}
hmtx = stringToArray(hmtx);
@ -741,17 +750,7 @@ var Font = (function () {
createTableEntry(otf, offsets, "name", name);
/** POST */
// TODO: get those informations from the FontInfo structure
post = "\x00\x03\x00\x00" + // Version number
"\x00\x00\x01\x00" + // italicAngle
"\x00\x00" + // underlinePosition
"\x00\x00" + // underlineThickness
"\x00\x00\x00\x00" + // isFixedPitch
"\x00\x00\x00\x00" + // minMemType42
"\x00\x00\x00\x00" + // maxMemType42
"\x00\x00\x00\x00" + // minMemType1
"\x00\x00\x00\x00"; // maxMemType1
post = stringToArray(post);
post = stringToArray(createPostTable(properties));
createTableEntry(otf, offsets, "post", post);
// Once all the table entries header are written, dump the data!
@ -768,96 +767,55 @@ var Font = (function () {
return fontData;
},
bindWorker: function font_bind_worker(dataStr) {
bindWorker: function font_bindWorker(data) {
postMessage({
action: "font",
data: {
raw: dataStr,
raw: data,
fontName: this.name,
mimetype: this.mimetype
}
});
},
bindDOM: function font_bind_dom(dataStr) {
bindDOM: function font_bindDom(data) {
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
// This code could go away when bug 471915 has landed
var canvas = document.createElement("canvas");
var style = "border: 1px solid black; position:absolute; top: " +
(debug ? (100 * fontCount) : "-200") + "px; left: 2px; width: 340px; height: 100px";
canvas.setAttribute("style", style);
canvas.setAttribute("width", 340);
canvas.setAttribute("heigth", 100);
document.body.appendChild(canvas);
// Get the font size canvas think it will be for 'spaces'
var ctx = canvas.getContext("2d");
ctx.font = "bold italic 20px " + fontName + ", Symbol, Arial";
ctx.font = "bold italic 20px " + fontName + ", Symbol";
var testString = " ";
// When debugging use the characters provided by the charsets to visually
// see what's happening instead of 'spaces'
var debug = false;
if (debug) {
var name = document.createElement("font");
name.setAttribute("style", "position: absolute; left: 20px; top: " +
(100 * fontCount + 60) + "px");
name.innerHTML = fontName;
document.body.appendChild(name);
// Retrieve font charset
var charset = Fonts[fontName].properties.charset || [];
// if the charset is too small make it repeat a few times
var count = 30;
while (count-- && charset.length <= 30)
charset = charset.concat(charset.slice());
for (var i = 0; i < charset.length; i++) {
var unicode = GlyphsUnicode[charset[i]];
if (!unicode)
continue;
testString += String.fromCharCode(unicode);
}
ctx.fillText(testString, 20, 20);
}
// Periodicaly check for the width of the testString, it will be
// different once the real font has loaded
var textWidth = ctx.measureText(testString).width;
var interval = window.setInterval(function canvasInterval(self) {
this.start = this.start || Date.now();
ctx.font = "bold italic 20px " + fontName + ", Symbol, Arial";
ctx.font = "bold italic 20px " + fontName + ", Symbol";
// 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 + " for charset: " + charset + " loaded?");
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 (debug)
ctx.fillText(testString, 20, 50);
}, 30, this);
/** Hack end */
// 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 + ");";
var url = "url(data:" + this.mimetype + ";base64," + window.btoa(data) + ");";
var rule = "@font-face { font-family:'" + fontName + "';src:" + url + "}";
var styleSheet = document.styleSheets[0];
styleSheet.insertRule(rule, styleSheet.length);
@ -887,6 +845,9 @@ var FontsUtils = {
bytes.set([value >> 24, value >> 16, value >> 8, value]);
return [bytes[0], bytes[1], bytes[2], bytes[3]];
}
error("This number of bytes " + bytesCount + " is not supported");
return null;
},
bytesToInteger: function fu_bytesToInteger(bytesArray) {
@ -1223,6 +1184,8 @@ var CFFStrings = [
"001.003","Black","Bold","Book","Light","Medium","Regular","Roman","Semibold"
];
var type1Parser = new Type1Parser();
var CFF = function(name, file, properties) {
// Get the data block containing glyphs and subrs informations
var length1 = file.dict.get("Length1");
@ -1230,17 +1193,15 @@ var CFF = function(name, file, properties) {
file.skip(length1);
var eexecBlock = file.getBytes(length2);
// Decrypt the data blocks and retrieve the informations from it
var parser = new Type1Parser();
var fontInfo = parser.extractFontProgram(eexecBlock);
// Decrypt the data blocks and retrieve it's content
var data = type1Parser.extractFontProgram(eexecBlock);
properties.subrs = fontInfo.subrs;
properties.glyphs = fontInfo.charstrings;
this.data = this.wrap(name, properties);
this.charstrings = this.getOrderedCharStrings(data.charstrings);
this.data = this.wrap(name, this.charstrings, data.subrs, properties);
};
CFF.prototype = {
createCFFIndexHeader: function(objects, isByte) {
createCFFIndexHeader: function cff_createCFFIndexHeader(objects, isByte) {
// First 2 bytes contains the number of objects contained into this index
var count = objects.length;
@ -1277,18 +1238,18 @@ CFF.prototype = {
return data;
},
encodeNumber: function(value) {
encodeNumber: function cff_encodeNumber(value) {
var x = 0;
if (value >= -32768 && value <= 32767) {
return [ 28, value >> 8, value & 0xFF ];
} else if (value >= (-2147483647-1) && value <= 2147483647) {
return [ 0xFF, value >> 24, Value >> 16, value >> 8, value & 0xFF ];
} else {
error("Value: " + value + " is not allowed");
}
error("Value: " + value + " is not allowed");
return null;
},
getOrderedCharStrings: function(glyphs) {
getOrderedCharStrings: function cff_getOrderedCharStrings(glyphs) {
var charstrings = [];
for (var i = 0; i < glyphs.length; i++) {
@ -1301,7 +1262,7 @@ CFF.prototype = {
charstrings.push({
glyph: glyph,
unicode: unicode,
charstring: glyphs[i].data.slice()
charstring: glyphs[i].data
});
}
};
@ -1334,7 +1295,7 @@ CFF.prototype = {
"hvcurveto": 31,
},
flattenCharstring: function flattenCharstring(glyph, charstring, subrs) {
flattenCharstring: function flattenCharstring(charstring, subrs) {
var i = 0;
while (true) {
var obj = charstring[i];
@ -1344,9 +1305,9 @@ CFF.prototype = {
if (obj.charAt) {
switch (obj) {
case "callsubr":
var subr = subrs[charstring[i - 1]].slice();
var subr = subrs[charstring[i - 1]];
if (subr.length > 1) {
subr = this.flattenCharstring(glyph, subr, subrs);
subr = this.flattenCharstring(subr, subrs);
subr.pop();
charstring.splice(i - 1, 2, subr);
} else {
@ -1436,23 +1397,16 @@ CFF.prototype = {
i++;
}
error("failing with i = " + i + " in charstring:" + charstring + "(" + charstring.length + ")");
return [];
},
wrap: function wrap(name, properties) {
var charstrings = this.getOrderedCharStrings(properties.glyphs);
wrap: function wrap(name, charstrings, subrs, properties) {
// Starts the conversion of the Type1 charstrings to Type2
var charstringsCount = 0;
var charstringsDataLength = 0;
var glyphs = [];
for (var i = 0; i < charstrings.length; i++) {
var charstring = charstrings[i].charstring.slice();
var glyph = charstrings[i].glyph;
var flattened = this.flattenCharstring(glyph, charstring, properties.subrs);
glyphs.push(flattened);
charstringsCount++;
charstringsDataLength += flattened.length;
var glyphsCount = charstrings.length;
for (var i = 0; i < glyphsCount; i++) {
var charstring = charstrings[i].charstring;
glyphs.push(this.flattenCharstring(charstring.slice(), subrs));
}
// Create a CFF font data
@ -1487,17 +1441,16 @@ CFF.prototype = {
// Fill the charset header (first byte is the encoding)
var charset = [0x00];
for (var i = 0; i < glyphs.length; i++) {
for (var i = 0; i < glyphsCount; i++) {
var index = CFFStrings.indexOf(charstrings[i].glyph);
if (index == -1)
index = CFFStrings.length + strings.indexOf(glyph);
index = CFFStrings.length + strings.indexOf(charstrings[i].glyph);
var bytes = FontsUtils.integerToBytes(index, 2);
charset.push(bytes[0]);
charset.push(bytes[1]);
}
var charstringsIndex = this.createCFFIndexHeader([[0x40, 0x0E]].concat(glyphs), true);
charstringsIndex = charstringsIndex.join(" ").split(" "); // XXX why?
//Top Dict Index
var topDictIndex = [
@ -1523,7 +1476,7 @@ CFF.prototype = {
topDictIndex = topDictIndex.concat([28, 0, 0, 16]) // Encoding
var charstringsOffset = charsetOffset + (charstringsCount * 2) + 1;
var charstringsOffset = charsetOffset + (glyphsCount * 2) + 1;
topDictIndex = topDictIndex.concat(this.encodeNumber(charstringsOffset));
topDictIndex.push(17); // charstrings
@ -1531,7 +1484,6 @@ CFF.prototype = {
var privateOffset = charstringsOffset + charstringsIndex.length;
topDictIndex = topDictIndex.concat(this.encodeNumber(privateOffset));
topDictIndex.push(18); // Private
topDictIndex = topDictIndex.join(" ").split(" ");
var indexes = [
topDictIndex, stringsIndex,
@ -1561,7 +1513,6 @@ CFF.prototype = {
139, 12, 14,
28, 0, 55, 19
]);
privateData = privateData.join(" ").split(" ");
cff.set(privateData, currentOffset);
currentOffset += privateData.length;

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>

View File

@ -3,6 +3,8 @@
"use strict";
var pageTimeout;
var PDFViewer = {
queryParams: {},
@ -75,81 +77,45 @@ var PDFViewer = {
},
drawPage: function(num) {
if (!PDFViewer.pdf) {
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();
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);
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
var fonts = [];
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;
var loadFont = function() {
if (!FontLoader.bind(fonts)) {
pageTimeout = window.setTimeout(loadFont, 10);
return;
}
new Font(font.name, font.file, font.properties);
areFontsReady = false;
page.display(gfx);
}
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);
loadFont();
}
},
@ -258,7 +224,6 @@ var PDFViewer = {
};
window.onload = function() {
// Parse the URL query parameters into a cached object.
PDFViewer.queryParams = function() {
var qs = window.location.search.substring(1);

184
pdf.js
View File

@ -56,6 +56,14 @@ function bytesToString(bytes) {
return str;
}
function stringToBytes(str) {
var length = str.length;
var bytes = new Uint8Array(length);
for (var n = 0; n < length; ++n)
bytes[n] = str.charCodeAt(n) & 0xFF;
return bytes;
}
var Stream = (function() {
function constructor(arrayBuffer, start, length, dict) {
this.bytes = new Uint8Array(arrayBuffer);
@ -71,14 +79,14 @@ var Stream = (function() {
get length() {
return this.end - this.start;
},
getByte: function() {
getByte: function stream_getByte() {
if (this.pos >= this.end)
return;
return null;
return this.bytes[this.pos++];
},
// returns subarray of original buffer
// should only be read
getBytes: function(length) {
getBytes: function stream_getBytes(length) {
var bytes = this.bytes;
var pos = this.pos;
var strEnd = this.end;
@ -93,28 +101,28 @@ var Stream = (function() {
this.pos = end;
return bytes.subarray(pos, end);
},
lookChar: function() {
lookChar: function stream_lookChar() {
if (this.pos >= this.end)
return;
return null;
return String.fromCharCode(this.bytes[this.pos]);
},
getChar: function() {
getChar: function stream_getChar() {
if (this.pos >= this.end)
return;
return null;
return String.fromCharCode(this.bytes[this.pos++]);
},
skip: function(n) {
skip: function stream_skip(n) {
if (!n)
n = 1;
this.pos += n;
},
reset: function() {
reset: function stream_reset() {
this.pos = this.start;
},
moveStart: function() {
moveStart: function stream_moveStart() {
this.start = this.pos;
},
makeSubStream: function(start, length, dict) {
makeSubStream: function stream_makeSubstream(start, length, dict) {
return new Stream(this.bytes.buffer, start, length, dict);
}
};
@ -146,7 +154,7 @@ var DecodeStream = (function() {
}
constructor.prototype = {
ensureBuffer: function(requested) {
ensureBuffer: function decodestream_ensureBuffer(requested) {
var buffer = this.buffer;
var current = buffer ? buffer.byteLength : 0;
if (requested < current)
@ -159,16 +167,16 @@ var DecodeStream = (function() {
buffer2[i] = buffer[i];
return this.buffer = buffer2;
},
getByte: function() {
getByte: function decodestream_getByte() {
var pos = this.pos;
while (this.bufferLength <= pos) {
if (this.eof)
return;
return null;
this.readBlock();
}
return this.buffer[this.pos++];
},
getBytes: function(length) {
getBytes: function decodestream_getBytes(length) {
var pos = this.pos;
if (length) {
@ -191,25 +199,25 @@ var DecodeStream = (function() {
this.pos = end;
return this.buffer.subarray(pos, end)
},
lookChar: function() {
lookChar: function decodestream_lookChar() {
var pos = this.pos;
while (this.bufferLength <= pos) {
if (this.eof)
return;
return null;
this.readBlock();
}
return String.fromCharCode(this.buffer[this.pos]);
},
getChar: function() {
getChar: function decodestream_getChar() {
var pos = this.pos;
while (this.bufferLength <= pos) {
if (this.eof)
return;
return null;
this.readBlock();
}
return String.fromCharCode(this.buffer[this.pos++]);
},
skip: function(n) {
skip: function decodestream_skip(n) {
if (!n)
n = 1;
this.pos += n;
@ -635,6 +643,7 @@ var PredictorStream = (function() {
var rowBytes = this.rowBytes = (columns * colors * bits + 7) >> 3;
DecodeStream.call(this);
return this;
}
constructor.prototype = Object.create(DecodeStream.prototype);
@ -707,7 +716,7 @@ var PredictorStream = (function() {
var rawBytes = this.stream.getBytes(rowBytes);
var bufferLength = this.bufferLength;
var buffer = this.ensureBuffer(bufferLength + pixBytes);
var buffer = this.ensureBuffer(bufferLength + rowBytes);
var currentRow = buffer.subarray(bufferLength, bufferLength + rowBytes);
var prevRow = buffer.subarray(bufferLength - rowBytes, bufferLength);
@ -799,11 +808,34 @@ var JpegStream = (function() {
return constructor;
})();
var DecryptStream = (function() {
function constructor(str, fileKey, encAlgorithm, keyLength) {
TODO("decrypt stream is not implemented");
function constructor(str, decrypt) {
this.str = str;
this.dict = str.dict;
this.decrypt = decrypt;
DecodeStream.call(this);
}
constructor.prototype = Stream.prototype;
const chunkSize = 512;
constructor.prototype = Object.create(DecodeStream.prototype);
constructor.prototype.readBlock = function() {
var chunk = this.str.getBytes(chunkSize);
if (!chunk || chunk.length == 0) {
this.eof = true;
return;
}
var decrypt = this.decrypt;
chunk = decrypt(chunk);
var bufferLength = this.bufferLength;
var i, n = chunk.length;
var buffer = this.ensureBuffer(bufferLength + n);
for (i = 0; i < n; i++)
buffer[bufferLength++] = chunk[i];
this.bufferLength = bufferLength;
this.eof = n < chunkSize;
};
return constructor;
})();
@ -1883,7 +1915,9 @@ var Dict = (function() {
constructor.prototype = {
get: function(key) {
return this.map[key];
if (key in this.map)
return this.map[key];
return null;
},
get2: function(key1, key2) {
return this.get(key1) || this.get(key2);
@ -1954,7 +1988,7 @@ function IsArray(v) {
}
function IsStream(v) {
return typeof v == "object" && "getChar" in v;
return typeof v == "object" && v != null && ("getChar" in v);
}
function IsRef(v) {
@ -2023,10 +2057,10 @@ var Lexer = (function() {
function ToHexDigit(ch) {
if (ch >= "0" && ch <= "9")
return ch - "0";
ch = ch.toLowerCase();
if (ch >= "a" && ch <= "f")
return ch - "a";
return ch.charCodeAt(0) - 48;
ch = ch.toUpperCase();
if (ch >= "A" && ch <= "F")
return ch.charCodeAt(0) - 55;
return -1;
}
@ -2320,7 +2354,7 @@ var Parser = (function() {
// don't buffer inline image data
this.buf2 = (this.inlineImg > 0) ? null : this.lexer.getObj();
},
getObj: function() {
getObj: function(cipherTransform) {
// refill buffer after inline image data
if (this.inlineImg == 2)
this.refill();
@ -2346,7 +2380,7 @@ var Parser = (function() {
this.shift();
if (IsEOF(this.buf1))
break;
dict.set(key, this.getObj());
dict.set(key, this.getObj(cipherTransform));
}
}
if (IsEOF(this.buf1))
@ -2355,7 +2389,7 @@ var Parser = (function() {
// stream objects are not allowed inside content streams or
// object streams
if (this.allowStreams && IsCmd(this.buf2, "stream")) {
return this.makeStream(dict);
return this.makeStream(dict, cipherTransform);
} else {
this.shift();
}
@ -2374,17 +2408,8 @@ var Parser = (function() {
} else if (IsString(this.buf1)) { // string
var str = this.buf1;
this.shift();
if (this.fileKey) {
var decrypt = new DecryptStream(new StringStream(str),
this.fileKey,
this.encAlgorithm,
this.keyLength);
var str = "";
var pos = decrypt.pos;
var length = decrypt.length;
while (pos++ > length)
str += decrypt.getChar();
}
if (cipherTransform)
str = cipherTransform.decryptString(str);
return str;
}
@ -2393,7 +2418,7 @@ var Parser = (function() {
this.shift();
return obj;
},
makeStream: function(dict) {
makeStream: function(dict, cipherTransform) {
var lexer = this.lexer;
var stream = lexer.stream;
@ -2420,12 +2445,8 @@ var Parser = (function() {
this.shift();
stream = stream.makeSubStream(pos, length, dict);
if (this.fileKey) {
stream = new DecryptStream(stream,
this.fileKey,
this.encAlgorithm,
this.keyLength);
}
if (cipherTransform)
stream = cipherTransform.createStream(stream);
stream = this.filter(stream, dict, length);
stream.parameters = dict;
return stream;
@ -2559,16 +2580,22 @@ var XRef = (function() {
this.xrefstms = {};
var trailerDict = this.readXRef(startXRef);
// prepare the XRef cache
this.cache = [];
var encrypt = trailerDict.get("Encrypt");
if (encrypt) {
var fileId = trailerDict.get("ID");
this.encrypt = new CipherTransformFactory(this.fetch(encrypt), fileId[0] /*, password */);
}
// get the root dictionary (catalog) object
if (!IsRef(this.root = trailerDict.get("Root")))
error("Invalid root reference");
// prepare the XRef cache
this.cache = [];
}
constructor.prototype = {
readXRefTable: function(parser) {
readXRefTable: function readXRefTable(parser) {
var obj;
while (true) {
if (IsCmd(obj = parser.getObj(), "trailer"))
@ -2639,7 +2666,7 @@ var XRef = (function() {
return dict;
},
readXRefStream: function(stream) {
readXRefStream: function readXRefStream(stream) {
var streamParameters = stream.parameters;
var length = streamParameters.get("Length");
var byteWidths = streamParameters.get("W");
@ -2691,7 +2718,7 @@ var XRef = (function() {
this.readXRef(prev);
return streamParameters;
},
readXRef: function(startXRef) {
readXRef: function readXref(startXRef) {
var stream = this.stream;
stream.pos = startXRef;
var parser = new Parser(new Lexer(stream), true);
@ -2709,6 +2736,7 @@ var XRef = (function() {
return this.readXRefStream(obj);
}
error("Invalid XRef");
return null;
},
getEntry: function(i) {
var e = this.entries[i];
@ -2752,7 +2780,11 @@ var XRef = (function() {
}
error("bad XRef entry");
}
e = parser.getObj();
if (this.encrypt) {
e = parser.getObj(this.encrypt.createCipherTransform(num, gen));
} else {
e = parser.getObj();
}
// Don't cache streams since they are mutable.
if (!IsStream(e))
this.cache[num] = e;
@ -3374,7 +3406,7 @@ var CanvasGraphics = (function() {
if (!fd)
// XXX deprecated "special treatment" for standard
// fonts? What do we need to do here?
return;
return null;
var descriptor = xref.fetch(fd);
var fontName = descriptor.get("FontName");
@ -3386,16 +3418,9 @@ var CanvasGraphics = (function() {
error("FontFile not found for font: " + fontName);
fontFile = xref.fetchIfRef(fontFile);
// Fonts with an embedded cmap but without any assignment in
// it are not yet supported, so ask the fonts loader to ignore
// them to not pay a stupid one sec latence.
var ignoreFont = false;
var encodingMap = {};
var charset = [];
if (fontDict.has("Encoding")) {
ignoreFont = false;
var encoding = xref.fetchIfRef(fontDict.get("Encoding"));
if (IsDict(encoding)) {
// Build a map between codes and glyphs
@ -3418,9 +3443,8 @@ var CanvasGraphics = (function() {
error("Unknown font encoding");
var index = 0;
for (var j = 0; j < encoding.length; j++) {
for (var j = 0; j < encoding.length; j++)
encodingMap[index++] = GlyphsUnicode[encoding[j]];
}
var firstChar = xref.fetchIfRef(fontDict.get("FirstChar"));
var widths = xref.fetchIfRef(fontDict.get("Widths"));
@ -3444,13 +3468,7 @@ var CanvasGraphics = (function() {
var tokens = [];
var token = "";
var length = cmapObj.length;
if (cmapObj instanceof FlateStream) {
cmapObj.readBlock();
length = cmapObj.bufferLength;
}
var cmap = cmapObj.getBytes(length);
var cmap = cmapObj.getBytes(cmapObj.length);
for (var i =0; i < cmap.length; i++) {
var byte = cmap[i];
if (byte == 0x20 || byte == 0x0A || byte == 0x3C || byte == 0x3E) {
@ -3460,7 +3478,6 @@ var CanvasGraphics = (function() {
break;
case "beginbfrange":
ignoreFont = false;
case "begincodespacerange":
token = "";
tokens = [];
@ -3507,16 +3524,19 @@ var CanvasGraphics = (function() {
}
var subType = fontDict.get("Subtype");
var bbox = descriptor.get("FontBBox");
assertWellFormed(IsName(subType) && IsArray(bbox),
"invalid font Subtype or FontBBox");
assertWellFormed(IsName(subType), "invalid font Subtype");
var properties = {
type: subType.name,
encoding: encodingMap,
charset: charset,
bbox: bbox,
ignore: ignoreFont
bbox: descriptor.get("FontBBox"),
ascent: descriptor.get("Ascent"),
descent: descriptor.get("Descent"),
xHeight: descriptor.get("XHeight"),
capHeight: descriptor.get("CapHeight"),
flags: descriptor.get("Flags"),
italicAngle: descriptor.get("ItalicAngle")
};
return {
@ -4027,7 +4047,7 @@ var CanvasGraphics = (function() {
this.restore();
TODO("Inverse pattern is painted");
var pattern = this.ctx.createPattern(tmpCanvas, "repeat");
pattern = this.ctx.createPattern(tmpCanvas, "repeat");
this.ctx.fillStyle = pattern;
},
setStrokeGray: function(gray) {

View File

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

BIN
test/resources/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -32,3 +32,5 @@ 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

View File

@ -1,4 +1,4 @@
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
@ -51,6 +51,7 @@ MIMEs = {
'.json': 'application/json',
'.pdf': 'application/pdf',
'.xhtml': 'application/xhtml+xml',
'.ico': 'image/x-icon'
}
class State:
@ -69,9 +70,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 +85,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 +101,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,14 +117,8 @@ 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):
@ -122,10 +131,20 @@ class PDFTestHandler(BaseHTTPRequestHandler):
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]
@ -138,6 +157,8 @@ class BrowserCommand():
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()
@ -151,19 +172,30 @@ class BrowserCommand():
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)
# 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):
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)
def makeBrowserCommands(browserManifestFile):
with open(browserManifestFile) as bmf:
@ -223,14 +255,23 @@ 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)
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
@ -385,8 +426,14 @@ 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)
if __name__ == '__main__':
main()

View File

@ -14,6 +14,12 @@
"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,

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 browser, canvas, currentTask, currentTaskIdx, failure, manifest, numPages, pdfDoc, stdout;
function queryParams() {
var qs = window.location.search.substring(1);
@ -63,7 +63,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 +79,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;
@ -88,43 +96,34 @@ function nextPage() {
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;
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 +138,41 @@ 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 quitApp() {
log("Done!");
document.body.innerHTML = "Tests are finished. <h1>CLOSE ME!</h1>";
if (window.SpecialPowers)
SpecialPowers.quitApplication();
else
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,9 +181,14 @@ 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));
}
@ -194,7 +208,8 @@ function log(str) {
</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

@ -6,6 +6,7 @@
<script type="text/javascript" src="viewer.js"></script>
<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>
</head>

View File

@ -3,7 +3,7 @@
"use strict";
var pdfDocument, canvas, pageDisplay, pageNum, numPages, pageInterval;
var pdfDocument, canvas, pageDisplay, pageNum, numPages, pageTimeout;
function load(userInput) {
canvas = document.getElementById("canvas");
canvas.mozOpaque = true;
@ -52,7 +52,7 @@ function gotoPage(num) {
}
function displayPage(num) {
window.clearInterval(pageInterval);
window.clearTimeout(pageTimeout);
document.getElementById("pageNumber").value = num;
@ -75,28 +75,12 @@ function displayPage(num) {
page.compile(gfx, fonts);
var t2 = Date.now();
var fontsReady = true;
// 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;
function loadFont() {
if (!FontLoader.bind(fonts)) {
pageTimeout = window.setTimeout(loadFont, 10);
return;
}
new Font(font.name, font.file, font.properties);
fontsReady = false;
}
function delayLoadFont() {
for (var i = 0; i < count; i++) {
if (Fonts[font.name].loading)
return;
}
window.clearInterval(pageInterval);
var t3 = Date.now();
page.display(gfx);
@ -106,12 +90,7 @@ function displayPage(num) {
var infoDisplay = document.getElementById("info");
infoDisplay.innerHTML = "Time to load/compile/fonts/render: "+ (t1 - t0) + "/" + (t2 - t1) + "/" + (t3 - t2) + "/" + (t4 - t3) + " ms";
};
if (fontsReady) {
delayLoadFont();
} else {
pageInterval = setInterval(delayLoadFont, 10);
}
loadFont();
}
function nextPage() {