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;
})();

339
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 * '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). * decoding logics whatever type it is (assuming the font type is supported).
@ -113,13 +143,14 @@ var Font = (function () {
return; return;
} }
var data;
switch (properties.type) { switch (properties.type) {
case "Type1": case "Type1":
var cff = new CFF(name, file, properties); var cff = new CFF(name, file, properties);
this.mimetype = "font/opentype"; this.mimetype = "font/opentype";
// Wrap the CFF data inside an OTF font file // Wrap the CFF data inside an OTF font file
this.font = this.convert(name, cff, properties); data = this.convert(name, cff, properties);
break; break;
case "TrueType": case "TrueType":
@ -127,7 +158,7 @@ var Font = (function () {
// Repair the TrueType file if it is can be damaged in the point of // Repair the TrueType file if it is can be damaged in the point of
// view of the sanitizer // view of the sanitizer
this.font = this.checkAndRepair(name, file, properties); data = this.checkAndRepair(name, file, properties);
break; break;
default: default:
@ -135,28 +166,12 @@ var Font = (function () {
break; break;
} }
var data = this.font;
Fonts[name] = { Fonts[name] = {
data: data, data: data,
properties: properties, properties: properties,
loading: true, loading: true,
cache: Object.create(null) 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) { function stringToArray(str) {
@ -310,57 +325,60 @@ var Font = (function () {
idDeltas + idRangeOffsets + glyphsIds); idDeltas + idRangeOffsets + glyphsIds);
}; };
function createOS2Table() { function createOS2Table(properties) {
var OS2 = stringToArray( return "\x00\x03" + // version
"\x00\x03" + // version "\x02\x24" + // xAvgCharWidth
"\x02\x24" + // xAvgCharWidth "\x01\xF4" + // usWeightClass
"\x01\xF4" + // usWeightClass "\x00\x05" + // usWidthClass
"\x00\x05" + // usWidthClass "\x00\x00" + // fstype
"\x00\x00" + // fstype "\x02\x8A" + // ySubscriptXSize
"\x02\x8A" + // ySubscriptXSize "\x02\xBB" + // ySubscriptYSize
"\x02\xBB" + // ySubscriptYSize "\x00\x00" + // ySubscriptXOffset
"\x00\x00" + // ySubscriptXOffset "\x00\x8C" + // ySubscriptYOffset
"\x00\x8C" + // ySubscriptYOffset "\x02\x8A" + // ySuperScriptXSize
"\x02\x8A" + // ySuperScriptXSize "\x02\xBB" + // ySuperScriptYSize
"\x02\xBB" + // ySuperScriptYSize "\x00\x00" + // ySuperScriptXOffset
"\x00\x00" + // ySuperScriptXOffset "\x01\xDF" + // ySuperScriptYOffset
"\x01\xDF" + // ySuperScriptYOffset "\x00\x31" + // yStrikeOutSize
"\x00\x31" + // yStrikeOutSize "\x01\x02" + // yStrikeOutPosition
"\x01\x02" + // yStrikeOutPosition "\x00\x00" + // sFamilyClass
"\x00\x00" + // sFamilyClass "\x02\x00\x06\x03\x00\x00\x00\x00\x00\x00" + // Panose
"\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 0-31) "\xFF\xFF\xFF\xFF" + // ulUnicodeRange1 (Bits 32-63)
"\xFF\xFF\xFF\xFF" + // ulUnicodeRange1 (Bits 32-63) "\xFF\xFF\xFF\xFF" + // ulUnicodeRange1 (Bits 64-95)
"\xFF\xFF\xFF\xFF" + // ulUnicodeRange1 (Bits 64-95) "\xFF\xFF\xFF\xFF" + // ulUnicodeRange1 (Bits 96-127)
"\xFF\xFF\xFF\xFF" + // ulUnicodeRange1 (Bits 96-127) "\x2A\x32\x31\x2A" + // achVendID
"\x2A\x32\x31\x2A" + // achVendID "\x00\x20" + // fsSelection
"\x00\x20" + // fsSelection "\x00\x2D" + // usFirstCharIndex
"\x00\x2D" + // usFirstCharIndex "\x00\x7A" + // usLastCharIndex
"\x00\x7A" + // usLastCharIndex "\x00\x03" + // sTypoAscender
"\x00\x03" + // sTypoAscender "\x00\x20" + // sTypeDescender
"\x00\x20" + // sTypeDescender "\x00\x38" + // sTypoLineGap
"\x00\x38" + // sTypoLineGap string16(properties.ascent) + // usWinAscent
"\x00\x5A" + // usWinAscent string16(properties.descent) + // usWinDescent
"\x02\xB4" + // usWinDescent "\x00\xCE\x00\x00" + // ulCodePageRange1 (Bits 0-31)
"\x00\xCE\x00\x00" + // ulCodePageRange1 (Bits 0-31) "\x00\x01\x00\x00" + // ulCodePageRange2 (Bits 32-63)
"\x00\x01\x00\x00" + // ulCodePageRange2 (Bits 32-63) string16(properties.xHeight) + // sxHeight
"\x00\x00" + // sxHeight string16(properties.capHeight) + // sCapHeight
"\x00\x00" + // sCapHeight "\x00\x01" + // usDefaultChar
"\x00\x01" + // usDefaultChar "\x00\xCD" + // usBreakChar
"\x00\xCD" + // usBreakChar "\x00\x02"; // usMaxContext
"\x00\x02" // usMaxContext };
);
return OS2; 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 = { constructor.prototype = {
name: null, name: null,
font: null, font: null,
@ -422,6 +440,7 @@ var Font = (function () {
(format == 6 && numTables == 1 && !properties.encoding.empty)) { (format == 6 && numTables == 1 && !properties.encoding.empty)) {
// Format 0 alone is not allowed by the sanitizer so let's rewrite // Format 0 alone is not allowed by the sanitizer so let's rewrite
// that to a 3-1-4 Unicode BMP table // 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 charset = properties.charset;
var glyphs = []; var glyphs = [];
for (var j = 0; j < charset.length; j++) { for (var j = 0; j < charset.length; j++) {
@ -525,10 +544,9 @@ var Font = (function () {
createOpenTypeHeader("\x00\x01\x00\x00", ttf, offsets, numTables); createOpenTypeHeader("\x00\x01\x00\x00", ttf, offsets, numTables);
// Insert the missing table // Insert the missing table
var OS2 = createOS2Table();
tables.push({ tables.push({
tag: "OS/2", tag: "OS/2",
data: OS2 data: stringToArray(createOS2Table(properties))
}); });
// Replace the old CMAP table with a shiny new one // Replace the old CMAP table with a shiny new one
@ -536,20 +554,9 @@ var Font = (function () {
// Rewrite the 'post' table if needed // Rewrite the 'post' table if needed
if (!post) { if (!post) {
post = tables.push({
"\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({
tag: "post", tag: "post",
data: stringToArray(post) data: stringToArray(createPostTable(properties))
}); });
} }
@ -593,16 +600,16 @@ var Font = (function () {
return font.getBytes(); return font.getBytes();
}, },
convert: function font_convert(name, font, properties) { convert: function font_convert(fontName, font, properties) {
var otf = new Uint8Array(kMaxFontFileSize); var otf = new Uint8Array(kMaxFontFileSize);
function createNameTable(name) { function createNameTable(name) {
var names = [ var names = [
"See original licence", // Copyright "See original licence", // Copyright
name, // Font family fontName, // Font family
"undefined", // Font subfamily (font weight) "undefined", // Font subfamily (font weight)
"uniqueID", // Unique ID "uniqueID", // Unique ID
name, // Full font name fontName, // Full font name
"0.1", // Version "0.1", // Version
"undefined", // Postscript name "undefined", // Postscript name
"undefined", // Trademark "undefined", // Trademark
@ -610,7 +617,7 @@ var Font = (function () {
"undefined" // Designer "undefined" // Designer
]; ];
var name = var nameTable =
"\x00\x00" + // format "\x00\x00" + // format
"\x00\x0A" + // Number of names Record "\x00\x0A" + // Number of names Record
"\x00\x7E"; // Storage "\x00\x7E"; // Storage
@ -627,21 +634,21 @@ var Font = (function () {
"\x00\x00" + // name ID "\x00\x00" + // name ID
string16(str.length) + string16(str.length) +
string16(strOffset); string16(strOffset);
name += nameRecord; nameTable += nameRecord;
strOffset += str.length; strOffset += str.length;
} }
name += names.join(""); nameTable += names.join("");
return name; return nameTable;
} }
// Required Tables // Required Tables
var CFF = var CFF =
font.data, // PostScript Font Program font.data, // PostScript Font Program
OS2, // OS/2 and Windows Specific metrics OS2, // OS/2 and Windows Specific metrics
cmap, // Character to glyphs mapping cmap, // Character to glyphs mapping
head, // Font eader head, // Font header
hhea, // Horizontal header hhea, // Horizontal header
hmtx, // Horizontal metrics hmtx, // Horizontal metrics
maxp, // Maximum profile maxp, // Maximum profile
@ -665,13 +672,11 @@ var Font = (function () {
createTableEntry(otf, offsets, "CFF ", CFF); createTableEntry(otf, offsets, "CFF ", CFF);
/** OS/2 */ /** OS/2 */
OS2 = createOS2Table(); OS2 = stringToArray(createOS2Table(properties));
createTableEntry(otf, offsets, "OS/2", OS2); 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 */ /** CMAP */
var charstrings = font.charstrings;
cmap = createCMapTable(charstrings); cmap = createCMapTable(charstrings);
createTableEntry(otf, offsets, "cmap", cmap); createTableEntry(otf, offsets, "cmap", cmap);
@ -720,11 +725,15 @@ var Font = (function () {
createTableEntry(otf, offsets, "hhea", hhea); createTableEntry(otf, offsets, "hhea", hhea);
/** HMTX */ /** 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++) { for (var i = 0; i < charstrings.length; i++) {
var charstring = charstrings[i].charstring; width = charstrings[i].charstring[1];
var width = charstring[1];
var lsb = charstring[0];
hmtx += string16(width) + string16(lsb); hmtx += string16(width) + string16(lsb);
} }
hmtx = stringToArray(hmtx); hmtx = stringToArray(hmtx);
@ -741,17 +750,7 @@ var Font = (function () {
createTableEntry(otf, offsets, "name", name); createTableEntry(otf, offsets, "name", name);
/** POST */ /** POST */
// TODO: get those informations from the FontInfo structure post = stringToArray(createPostTable(properties));
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);
createTableEntry(otf, offsets, "post", post); createTableEntry(otf, offsets, "post", post);
// Once all the table entries header are written, dump the data! // Once all the table entries header are written, dump the data!
@ -768,96 +767,55 @@ var Font = (function () {
return fontData; return fontData;
}, },
bindWorker: function font_bind_worker(dataStr) { bindWorker: function font_bindWorker(data) {
postMessage({ postMessage({
action: "font", action: "font",
data: { data: {
raw: dataStr, raw: data,
fontName: this.name, fontName: this.name,
mimetype: this.mimetype mimetype: this.mimetype
} }
}); });
}, },
bindDOM: function font_bind_dom(dataStr) { bindDOM: function font_bindDom(data) {
var fontName = this.name; var fontName = this.name;
/** Hack begin */ /** Hack begin */
// Actually there is not event when a font has finished downloading so // 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 // 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 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"); var ctx = canvas.getContext("2d");
ctx.font = "bold italic 20px " + fontName + ", Symbol, Arial"; ctx.font = "bold italic 20px " + fontName + ", Symbol";
var testString = " "; 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 // Periodicaly check for the width of the testString, it will be
// different once the real font has loaded // different once the real font has loaded
var textWidth = ctx.measureText(testString).width; var textWidth = ctx.measureText(testString).width;
var interval = window.setInterval(function canvasInterval(self) { var interval = window.setInterval(function canvasInterval(self) {
this.start = this.start || Date.now(); 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 // For some reasons the font has not loaded, so mark it loaded for the
// page to proceed but cry // page to proceed but cry
if ((Date.now() - this.start) >= kMaxWaitForFontFace) { if ((Date.now() - this.start) >= kMaxWaitForFontFace) {
window.clearInterval(interval); window.clearInterval(interval);
Fonts[fontName].loading = false; Fonts[fontName].loading = false;
warn("Is " + fontName + " for charset: " + charset + " loaded?"); warn("Is " + fontName + " loaded?");
this.start = 0; this.start = 0;
} else if (textWidth != ctx.measureText(testString).width) { } else if (textWidth != ctx.measureText(testString).width) {
window.clearInterval(interval); window.clearInterval(interval);
Fonts[fontName].loading = false; Fonts[fontName].loading = false;
this.start = 0; this.start = 0;
} }
if (debug)
ctx.fillText(testString, 20, 50);
}, 30, this); }, 30, this);
/** Hack end */ /** 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 // 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 rule = "@font-face { font-family:'" + fontName + "';src:" + url + "}";
var styleSheet = document.styleSheets[0]; var styleSheet = document.styleSheets[0];
styleSheet.insertRule(rule, styleSheet.length); styleSheet.insertRule(rule, styleSheet.length);
@ -887,6 +845,9 @@ var FontsUtils = {
bytes.set([value >> 24, value >> 16, value >> 8, value]); bytes.set([value >> 24, value >> 16, value >> 8, value]);
return [bytes[0], bytes[1], bytes[2], bytes[3]]; 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) { bytesToInteger: function fu_bytesToInteger(bytesArray) {
@ -1223,6 +1184,8 @@ var CFFStrings = [
"001.003","Black","Bold","Book","Light","Medium","Regular","Roman","Semibold" "001.003","Black","Bold","Book","Light","Medium","Regular","Roman","Semibold"
]; ];
var type1Parser = new Type1Parser();
var CFF = function(name, file, properties) { var CFF = function(name, file, properties) {
// Get the data block containing glyphs and subrs informations // Get the data block containing glyphs and subrs informations
var length1 = file.dict.get("Length1"); var length1 = file.dict.get("Length1");
@ -1230,17 +1193,15 @@ var CFF = function(name, file, properties) {
file.skip(length1); file.skip(length1);
var eexecBlock = file.getBytes(length2); var eexecBlock = file.getBytes(length2);
// Decrypt the data blocks and retrieve the informations from it // Decrypt the data blocks and retrieve it's content
var parser = new Type1Parser(); var data = type1Parser.extractFontProgram(eexecBlock);
var fontInfo = parser.extractFontProgram(eexecBlock);
properties.subrs = fontInfo.subrs; this.charstrings = this.getOrderedCharStrings(data.charstrings);
properties.glyphs = fontInfo.charstrings; this.data = this.wrap(name, this.charstrings, data.subrs, properties);
this.data = this.wrap(name, properties);
}; };
CFF.prototype = { CFF.prototype = {
createCFFIndexHeader: function(objects, isByte) { createCFFIndexHeader: function cff_createCFFIndexHeader(objects, isByte) {
// First 2 bytes contains the number of objects contained into this index // First 2 bytes contains the number of objects contained into this index
var count = objects.length; var count = objects.length;
@ -1277,18 +1238,18 @@ CFF.prototype = {
return data; return data;
}, },
encodeNumber: function(value) { encodeNumber: function cff_encodeNumber(value) {
var x = 0; var x = 0;
if (value >= -32768 && value <= 32767) { if (value >= -32768 && value <= 32767) {
return [ 28, value >> 8, value & 0xFF ]; return [ 28, value >> 8, value & 0xFF ];
} else if (value >= (-2147483647-1) && value <= 2147483647) { } else if (value >= (-2147483647-1) && value <= 2147483647) {
return [ 0xFF, value >> 24, Value >> 16, value >> 8, value & 0xFF ]; 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 = []; var charstrings = [];
for (var i = 0; i < glyphs.length; i++) { for (var i = 0; i < glyphs.length; i++) {
@ -1301,7 +1262,7 @@ CFF.prototype = {
charstrings.push({ charstrings.push({
glyph: glyph, glyph: glyph,
unicode: unicode, unicode: unicode,
charstring: glyphs[i].data.slice() charstring: glyphs[i].data
}); });
} }
}; };
@ -1334,7 +1295,7 @@ CFF.prototype = {
"hvcurveto": 31, "hvcurveto": 31,
}, },
flattenCharstring: function flattenCharstring(glyph, charstring, subrs) { flattenCharstring: function flattenCharstring(charstring, subrs) {
var i = 0; var i = 0;
while (true) { while (true) {
var obj = charstring[i]; var obj = charstring[i];
@ -1344,9 +1305,9 @@ CFF.prototype = {
if (obj.charAt) { if (obj.charAt) {
switch (obj) { switch (obj) {
case "callsubr": case "callsubr":
var subr = subrs[charstring[i - 1]].slice(); var subr = subrs[charstring[i - 1]];
if (subr.length > 1) { if (subr.length > 1) {
subr = this.flattenCharstring(glyph, subr, subrs); subr = this.flattenCharstring(subr, subrs);
subr.pop(); subr.pop();
charstring.splice(i - 1, 2, subr); charstring.splice(i - 1, 2, subr);
} else { } else {
@ -1436,23 +1397,16 @@ CFF.prototype = {
i++; i++;
} }
error("failing with i = " + i + " in charstring:" + charstring + "(" + charstring.length + ")"); error("failing with i = " + i + " in charstring:" + charstring + "(" + charstring.length + ")");
return [];
}, },
wrap: function wrap(name, properties) { wrap: function wrap(name, charstrings, subrs, properties) {
var charstrings = this.getOrderedCharStrings(properties.glyphs);
// Starts the conversion of the Type1 charstrings to Type2 // Starts the conversion of the Type1 charstrings to Type2
var charstringsCount = 0;
var charstringsDataLength = 0;
var glyphs = []; var glyphs = [];
for (var i = 0; i < charstrings.length; i++) { var glyphsCount = charstrings.length;
var charstring = charstrings[i].charstring.slice(); for (var i = 0; i < glyphsCount; i++) {
var glyph = charstrings[i].glyph; var charstring = charstrings[i].charstring;
glyphs.push(this.flattenCharstring(charstring.slice(), subrs));
var flattened = this.flattenCharstring(glyph, charstring, properties.subrs);
glyphs.push(flattened);
charstringsCount++;
charstringsDataLength += flattened.length;
} }
// Create a CFF font data // Create a CFF font data
@ -1487,17 +1441,16 @@ CFF.prototype = {
// Fill the charset header (first byte is the encoding) // Fill the charset header (first byte is the encoding)
var charset = [0x00]; 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); var index = CFFStrings.indexOf(charstrings[i].glyph);
if (index == -1) if (index == -1)
index = CFFStrings.length + strings.indexOf(glyph); index = CFFStrings.length + strings.indexOf(charstrings[i].glyph);
var bytes = FontsUtils.integerToBytes(index, 2); var bytes = FontsUtils.integerToBytes(index, 2);
charset.push(bytes[0]); charset.push(bytes[0]);
charset.push(bytes[1]); charset.push(bytes[1]);
} }
var charstringsIndex = this.createCFFIndexHeader([[0x40, 0x0E]].concat(glyphs), true); var charstringsIndex = this.createCFFIndexHeader([[0x40, 0x0E]].concat(glyphs), true);
charstringsIndex = charstringsIndex.join(" ").split(" "); // XXX why?
//Top Dict Index //Top Dict Index
var topDictIndex = [ var topDictIndex = [
@ -1523,7 +1476,7 @@ CFF.prototype = {
topDictIndex = topDictIndex.concat([28, 0, 0, 16]) // Encoding 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 = topDictIndex.concat(this.encodeNumber(charstringsOffset));
topDictIndex.push(17); // charstrings topDictIndex.push(17); // charstrings
@ -1531,7 +1484,6 @@ CFF.prototype = {
var privateOffset = charstringsOffset + charstringsIndex.length; var privateOffset = charstringsOffset + charstringsIndex.length;
topDictIndex = topDictIndex.concat(this.encodeNumber(privateOffset)); topDictIndex = topDictIndex.concat(this.encodeNumber(privateOffset));
topDictIndex.push(18); // Private topDictIndex.push(18); // Private
topDictIndex = topDictIndex.join(" ").split(" ");
var indexes = [ var indexes = [
topDictIndex, stringsIndex, topDictIndex, stringsIndex,
@ -1561,7 +1513,6 @@ CFF.prototype = {
139, 12, 14, 139, 12, 14,
28, 0, 55, 19 28, 0, 55, 19
]); ]);
privateData = privateData.join(" ").split(" ");
cff.set(privateData, currentOffset); cff.set(privateData, currentOffset);
currentOffset += privateData.length; currentOffset += privateData.length;

View File

@ -6,6 +6,7 @@
<link rel="stylesheet" href="multi_page_viewer.css" type="text/css" media="screen"/> <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="pdf.js"></script>
<script type="text/javascript" src="fonts.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="glyphlist.js"></script>
<script type="text/javascript" src="multi_page_viewer.js"></script> <script type="text/javascript" src="multi_page_viewer.js"></script>
</head> </head>

View File

@ -3,6 +3,8 @@
"use strict"; "use strict";
var pageTimeout;
var PDFViewer = { var PDFViewer = {
queryParams: {}, queryParams: {},
@ -75,16 +77,13 @@ var PDFViewer = {
}, },
drawPage: function(num) { drawPage: function(num) {
if (!PDFViewer.pdf) { if (!PDFViewer.pdf)
return; return;
}
var div = document.getElementById('pageContainer' + num); var div = document.getElementById('pageContainer' + num);
var canvas = document.createElement('canvas'); var canvas = document.createElement('canvas');
if (div && !div.hasChildNodes()) { if (div && !div.hasChildNodes()) {
div.appendChild(canvas);
var page = PDFViewer.pdf.getPage(num); var page = PDFViewer.pdf.getPage(num);
canvas.id = 'page' + num; canvas.id = 'page' + num;
@ -94,6 +93,7 @@ var PDFViewer = {
// are always 96 dpi. These dimensions are 8.5in x 11in at 96dpi. // are always 96 dpi. These dimensions are 8.5in x 11in at 96dpi.
canvas.width = PDFViewer.pageWidth(); canvas.width = PDFViewer.pageWidth();
canvas.height = PDFViewer.pageHeight(); canvas.height = PDFViewer.pageHeight();
div.appendChild(canvas);
var ctx = canvas.getContext('2d'); var ctx = canvas.getContext('2d');
ctx.save(); ctx.save();
@ -102,54 +102,20 @@ var PDFViewer = {
ctx.restore(); ctx.restore();
var gfx = new CanvasGraphics(ctx); var gfx = new CanvasGraphics(ctx);
var fonts = [];
// page.compile will collect all fonts for us, once we have loaded them // page.compile will collect all fonts for us, once we have loaded them
// we can trigger the actual page rendering with page.display // we can trigger the actual page rendering with page.display
var fonts = [];
page.compile(gfx, fonts); page.compile(gfx, fonts);
var areFontsReady = true; var loadFont = function() {
if (!FontLoader.bind(fonts)) {
// Inspect fonts and translate the missing one pageTimeout = window.setTimeout(loadFont, 10);
var fontCount = fonts.length; return;
for (var i = 0; i < fontCount; i++) {
var font = fonts[i];
if (Fonts[font.name]) {
areFontsReady = areFontsReady && !Fonts[font.name].loading;
continue;
} }
page.display(gfx);
new Font(font.name, font.file, font.properties);
areFontsReady = false;
} }
loadFont();
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);
} }
}, },
@ -258,7 +224,6 @@ var PDFViewer = {
}; };
window.onload = function() { window.onload = function() {
// Parse the URL query parameters into a cached object. // Parse the URL query parameters into a cached object.
PDFViewer.queryParams = function() { PDFViewer.queryParams = function() {
var qs = window.location.search.substring(1); var qs = window.location.search.substring(1);

184
pdf.js
View File

@ -56,6 +56,14 @@ function bytesToString(bytes) {
return str; 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() { var Stream = (function() {
function constructor(arrayBuffer, start, length, dict) { function constructor(arrayBuffer, start, length, dict) {
this.bytes = new Uint8Array(arrayBuffer); this.bytes = new Uint8Array(arrayBuffer);
@ -71,14 +79,14 @@ var Stream = (function() {
get length() { get length() {
return this.end - this.start; return this.end - this.start;
}, },
getByte: function() { getByte: function stream_getByte() {
if (this.pos >= this.end) if (this.pos >= this.end)
return; return null;
return this.bytes[this.pos++]; return this.bytes[this.pos++];
}, },
// returns subarray of original buffer // returns subarray of original buffer
// should only be read // should only be read
getBytes: function(length) { getBytes: function stream_getBytes(length) {
var bytes = this.bytes; var bytes = this.bytes;
var pos = this.pos; var pos = this.pos;
var strEnd = this.end; var strEnd = this.end;
@ -93,28 +101,28 @@ var Stream = (function() {
this.pos = end; this.pos = end;
return bytes.subarray(pos, end); return bytes.subarray(pos, end);
}, },
lookChar: function() { lookChar: function stream_lookChar() {
if (this.pos >= this.end) if (this.pos >= this.end)
return; return null;
return String.fromCharCode(this.bytes[this.pos]); return String.fromCharCode(this.bytes[this.pos]);
}, },
getChar: function() { getChar: function stream_getChar() {
if (this.pos >= this.end) if (this.pos >= this.end)
return; return null;
return String.fromCharCode(this.bytes[this.pos++]); return String.fromCharCode(this.bytes[this.pos++]);
}, },
skip: function(n) { skip: function stream_skip(n) {
if (!n) if (!n)
n = 1; n = 1;
this.pos += n; this.pos += n;
}, },
reset: function() { reset: function stream_reset() {
this.pos = this.start; this.pos = this.start;
}, },
moveStart: function() { moveStart: function stream_moveStart() {
this.start = this.pos; 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); return new Stream(this.bytes.buffer, start, length, dict);
} }
}; };
@ -146,7 +154,7 @@ var DecodeStream = (function() {
} }
constructor.prototype = { constructor.prototype = {
ensureBuffer: function(requested) { ensureBuffer: function decodestream_ensureBuffer(requested) {
var buffer = this.buffer; var buffer = this.buffer;
var current = buffer ? buffer.byteLength : 0; var current = buffer ? buffer.byteLength : 0;
if (requested < current) if (requested < current)
@ -159,16 +167,16 @@ var DecodeStream = (function() {
buffer2[i] = buffer[i]; buffer2[i] = buffer[i];
return this.buffer = buffer2; return this.buffer = buffer2;
}, },
getByte: function() { getByte: function decodestream_getByte() {
var pos = this.pos; var pos = this.pos;
while (this.bufferLength <= pos) { while (this.bufferLength <= pos) {
if (this.eof) if (this.eof)
return; return null;
this.readBlock(); this.readBlock();
} }
return this.buffer[this.pos++]; return this.buffer[this.pos++];
}, },
getBytes: function(length) { getBytes: function decodestream_getBytes(length) {
var pos = this.pos; var pos = this.pos;
if (length) { if (length) {
@ -191,25 +199,25 @@ var DecodeStream = (function() {
this.pos = end; this.pos = end;
return this.buffer.subarray(pos, end) return this.buffer.subarray(pos, end)
}, },
lookChar: function() { lookChar: function decodestream_lookChar() {
var pos = this.pos; var pos = this.pos;
while (this.bufferLength <= pos) { while (this.bufferLength <= pos) {
if (this.eof) if (this.eof)
return; return null;
this.readBlock(); this.readBlock();
} }
return String.fromCharCode(this.buffer[this.pos]); return String.fromCharCode(this.buffer[this.pos]);
}, },
getChar: function() { getChar: function decodestream_getChar() {
var pos = this.pos; var pos = this.pos;
while (this.bufferLength <= pos) { while (this.bufferLength <= pos) {
if (this.eof) if (this.eof)
return; return null;
this.readBlock(); this.readBlock();
} }
return String.fromCharCode(this.buffer[this.pos++]); return String.fromCharCode(this.buffer[this.pos++]);
}, },
skip: function(n) { skip: function decodestream_skip(n) {
if (!n) if (!n)
n = 1; n = 1;
this.pos += n; this.pos += n;
@ -635,6 +643,7 @@ var PredictorStream = (function() {
var rowBytes = this.rowBytes = (columns * colors * bits + 7) >> 3; var rowBytes = this.rowBytes = (columns * colors * bits + 7) >> 3;
DecodeStream.call(this); DecodeStream.call(this);
return this;
} }
constructor.prototype = Object.create(DecodeStream.prototype); constructor.prototype = Object.create(DecodeStream.prototype);
@ -707,7 +716,7 @@ var PredictorStream = (function() {
var rawBytes = this.stream.getBytes(rowBytes); var rawBytes = this.stream.getBytes(rowBytes);
var bufferLength = this.bufferLength; var bufferLength = this.bufferLength;
var buffer = this.ensureBuffer(bufferLength + pixBytes); var buffer = this.ensureBuffer(bufferLength + rowBytes);
var currentRow = buffer.subarray(bufferLength, bufferLength + rowBytes); var currentRow = buffer.subarray(bufferLength, bufferLength + rowBytes);
var prevRow = buffer.subarray(bufferLength - rowBytes, bufferLength); var prevRow = buffer.subarray(bufferLength - rowBytes, bufferLength);
@ -799,11 +808,34 @@ var JpegStream = (function() {
return constructor; return constructor;
})(); })();
var DecryptStream = (function() { var DecryptStream = (function() {
function constructor(str, fileKey, encAlgorithm, keyLength) { function constructor(str, decrypt) {
TODO("decrypt stream is not implemented"); 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; return constructor;
})(); })();
@ -1883,7 +1915,9 @@ var Dict = (function() {
constructor.prototype = { constructor.prototype = {
get: function(key) { get: function(key) {
return this.map[key]; if (key in this.map)
return this.map[key];
return null;
}, },
get2: function(key1, key2) { get2: function(key1, key2) {
return this.get(key1) || this.get(key2); return this.get(key1) || this.get(key2);
@ -1954,7 +1988,7 @@ function IsArray(v) {
} }
function IsStream(v) { function IsStream(v) {
return typeof v == "object" && "getChar" in v; return typeof v == "object" && v != null && ("getChar" in v);
} }
function IsRef(v) { function IsRef(v) {
@ -2023,10 +2057,10 @@ var Lexer = (function() {
function ToHexDigit(ch) { function ToHexDigit(ch) {
if (ch >= "0" && ch <= "9") if (ch >= "0" && ch <= "9")
return ch - "0"; return ch.charCodeAt(0) - 48;
ch = ch.toLowerCase(); ch = ch.toUpperCase();
if (ch >= "a" && ch <= "f") if (ch >= "A" && ch <= "F")
return ch - "a"; return ch.charCodeAt(0) - 55;
return -1; return -1;
} }
@ -2320,7 +2354,7 @@ var Parser = (function() {
// don't buffer inline image data // don't buffer inline image data
this.buf2 = (this.inlineImg > 0) ? null : this.lexer.getObj(); this.buf2 = (this.inlineImg > 0) ? null : this.lexer.getObj();
}, },
getObj: function() { getObj: function(cipherTransform) {
// refill buffer after inline image data // refill buffer after inline image data
if (this.inlineImg == 2) if (this.inlineImg == 2)
this.refill(); this.refill();
@ -2346,7 +2380,7 @@ var Parser = (function() {
this.shift(); this.shift();
if (IsEOF(this.buf1)) if (IsEOF(this.buf1))
break; break;
dict.set(key, this.getObj()); dict.set(key, this.getObj(cipherTransform));
} }
} }
if (IsEOF(this.buf1)) if (IsEOF(this.buf1))
@ -2355,7 +2389,7 @@ var Parser = (function() {
// stream objects are not allowed inside content streams or // stream objects are not allowed inside content streams or
// object streams // object streams
if (this.allowStreams && IsCmd(this.buf2, "stream")) { if (this.allowStreams && IsCmd(this.buf2, "stream")) {
return this.makeStream(dict); return this.makeStream(dict, cipherTransform);
} else { } else {
this.shift(); this.shift();
} }
@ -2374,17 +2408,8 @@ var Parser = (function() {
} else if (IsString(this.buf1)) { // string } else if (IsString(this.buf1)) { // string
var str = this.buf1; var str = this.buf1;
this.shift(); this.shift();
if (this.fileKey) { if (cipherTransform)
var decrypt = new DecryptStream(new StringStream(str), str = cipherTransform.decryptString(str);
this.fileKey,
this.encAlgorithm,
this.keyLength);
var str = "";
var pos = decrypt.pos;
var length = decrypt.length;
while (pos++ > length)
str += decrypt.getChar();
}
return str; return str;
} }
@ -2393,7 +2418,7 @@ var Parser = (function() {
this.shift(); this.shift();
return obj; return obj;
}, },
makeStream: function(dict) { makeStream: function(dict, cipherTransform) {
var lexer = this.lexer; var lexer = this.lexer;
var stream = lexer.stream; var stream = lexer.stream;
@ -2420,12 +2445,8 @@ var Parser = (function() {
this.shift(); this.shift();
stream = stream.makeSubStream(pos, length, dict); stream = stream.makeSubStream(pos, length, dict);
if (this.fileKey) { if (cipherTransform)
stream = new DecryptStream(stream, stream = cipherTransform.createStream(stream);
this.fileKey,
this.encAlgorithm,
this.keyLength);
}
stream = this.filter(stream, dict, length); stream = this.filter(stream, dict, length);
stream.parameters = dict; stream.parameters = dict;
return stream; return stream;
@ -2559,16 +2580,22 @@ var XRef = (function() {
this.xrefstms = {}; this.xrefstms = {};
var trailerDict = this.readXRef(startXRef); 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 // get the root dictionary (catalog) object
if (!IsRef(this.root = trailerDict.get("Root"))) if (!IsRef(this.root = trailerDict.get("Root")))
error("Invalid root reference"); error("Invalid root reference");
// prepare the XRef cache
this.cache = [];
} }
constructor.prototype = { constructor.prototype = {
readXRefTable: function(parser) { readXRefTable: function readXRefTable(parser) {
var obj; var obj;
while (true) { while (true) {
if (IsCmd(obj = parser.getObj(), "trailer")) if (IsCmd(obj = parser.getObj(), "trailer"))
@ -2639,7 +2666,7 @@ var XRef = (function() {
return dict; return dict;
}, },
readXRefStream: function(stream) { readXRefStream: function readXRefStream(stream) {
var streamParameters = stream.parameters; var streamParameters = stream.parameters;
var length = streamParameters.get("Length"); var length = streamParameters.get("Length");
var byteWidths = streamParameters.get("W"); var byteWidths = streamParameters.get("W");
@ -2691,7 +2718,7 @@ var XRef = (function() {
this.readXRef(prev); this.readXRef(prev);
return streamParameters; return streamParameters;
}, },
readXRef: function(startXRef) { readXRef: function readXref(startXRef) {
var stream = this.stream; var stream = this.stream;
stream.pos = startXRef; stream.pos = startXRef;
var parser = new Parser(new Lexer(stream), true); var parser = new Parser(new Lexer(stream), true);
@ -2709,6 +2736,7 @@ var XRef = (function() {
return this.readXRefStream(obj); return this.readXRefStream(obj);
} }
error("Invalid XRef"); error("Invalid XRef");
return null;
}, },
getEntry: function(i) { getEntry: function(i) {
var e = this.entries[i]; var e = this.entries[i];
@ -2752,7 +2780,11 @@ var XRef = (function() {
} }
error("bad XRef entry"); 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. // Don't cache streams since they are mutable.
if (!IsStream(e)) if (!IsStream(e))
this.cache[num] = e; this.cache[num] = e;
@ -3374,7 +3406,7 @@ var CanvasGraphics = (function() {
if (!fd) if (!fd)
// XXX deprecated "special treatment" for standard // XXX deprecated "special treatment" for standard
// fonts? What do we need to do here? // fonts? What do we need to do here?
return; return null;
var descriptor = xref.fetch(fd); var descriptor = xref.fetch(fd);
var fontName = descriptor.get("FontName"); var fontName = descriptor.get("FontName");
@ -3386,16 +3418,9 @@ var CanvasGraphics = (function() {
error("FontFile not found for font: " + fontName); error("FontFile not found for font: " + fontName);
fontFile = xref.fetchIfRef(fontFile); 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 encodingMap = {};
var charset = []; var charset = [];
if (fontDict.has("Encoding")) { if (fontDict.has("Encoding")) {
ignoreFont = false;
var encoding = xref.fetchIfRef(fontDict.get("Encoding")); var encoding = xref.fetchIfRef(fontDict.get("Encoding"));
if (IsDict(encoding)) { if (IsDict(encoding)) {
// Build a map between codes and glyphs // Build a map between codes and glyphs
@ -3418,9 +3443,8 @@ var CanvasGraphics = (function() {
error("Unknown font encoding"); error("Unknown font encoding");
var index = 0; var index = 0;
for (var j = 0; j < encoding.length; j++) { for (var j = 0; j < encoding.length; j++)
encodingMap[index++] = GlyphsUnicode[encoding[j]]; encodingMap[index++] = GlyphsUnicode[encoding[j]];
}
var firstChar = xref.fetchIfRef(fontDict.get("FirstChar")); var firstChar = xref.fetchIfRef(fontDict.get("FirstChar"));
var widths = xref.fetchIfRef(fontDict.get("Widths")); var widths = xref.fetchIfRef(fontDict.get("Widths"));
@ -3444,13 +3468,7 @@ var CanvasGraphics = (function() {
var tokens = []; var tokens = [];
var token = ""; var token = "";
var length = cmapObj.length; var cmap = cmapObj.getBytes(cmapObj.length);
if (cmapObj instanceof FlateStream) {
cmapObj.readBlock();
length = cmapObj.bufferLength;
}
var cmap = cmapObj.getBytes(length);
for (var i =0; i < cmap.length; i++) { for (var i =0; i < cmap.length; i++) {
var byte = cmap[i]; var byte = cmap[i];
if (byte == 0x20 || byte == 0x0A || byte == 0x3C || byte == 0x3E) { if (byte == 0x20 || byte == 0x0A || byte == 0x3C || byte == 0x3E) {
@ -3460,7 +3478,6 @@ var CanvasGraphics = (function() {
break; break;
case "beginbfrange": case "beginbfrange":
ignoreFont = false;
case "begincodespacerange": case "begincodespacerange":
token = ""; token = "";
tokens = []; tokens = [];
@ -3507,16 +3524,19 @@ var CanvasGraphics = (function() {
} }
var subType = fontDict.get("Subtype"); var subType = fontDict.get("Subtype");
var bbox = descriptor.get("FontBBox"); assertWellFormed(IsName(subType), "invalid font Subtype");
assertWellFormed(IsName(subType) && IsArray(bbox),
"invalid font Subtype or FontBBox");
var properties = { var properties = {
type: subType.name, type: subType.name,
encoding: encodingMap, encoding: encodingMap,
charset: charset, charset: charset,
bbox: bbox, bbox: descriptor.get("FontBBox"),
ignore: ignoreFont 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 { return {
@ -4027,7 +4047,7 @@ var CanvasGraphics = (function() {
this.restore(); this.restore();
TODO("Inverse pattern is painted"); TODO("Inverse pattern is painted");
var pattern = this.ctx.createPattern(tmpCanvas, "repeat"); pattern = this.ctx.createPattern(tmpCanvas, "repeat");
this.ctx.fillStyle = pattern; this.ctx.fillStyle = pattern;
}, },
setStrokeGray: function(gray) { 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("browser.panorama.experienced_first_run", true); // Assume experienced
user_pref("dom.w3c_touch_events.enabled", true); user_pref("dom.w3c_touch_events.enabled", true);
user_pref("extensions.checkCompatibility", false); 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 from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
import SocketServer import SocketServer
from optparse import OptionParser from optparse import OptionParser
@ -51,6 +51,7 @@ MIMEs = {
'.json': 'application/json', '.json': 'application/json',
'.pdf': 'application/pdf', '.pdf': 'application/pdf',
'.xhtml': 'application/xhtml+xml', '.xhtml': 'application/xhtml+xml',
'.ico': 'image/x-icon'
} }
class State: class State:
@ -69,9 +70,10 @@ class State:
eqLog = None eqLog = None
class Result: class Result:
def __init__(self, snapshot, failure): def __init__(self, snapshot, failure, page):
self.snapshot = snapshot self.snapshot = snapshot
self.failure = failure self.failure = failure
self.page = page
class TestServer(SocketServer.TCPServer): class TestServer(SocketServer.TCPServer):
allow_reuse_address = True allow_reuse_address = True
@ -83,6 +85,14 @@ class PDFTestHandler(BaseHTTPRequestHandler):
if VERBOSE: if VERBOSE:
BaseHTTPRequestHandler.log_request(code, size) 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): def do_GET(self):
url = urlparse(self.path) url = urlparse(self.path)
# Ignore query string # Ignore query string
@ -91,9 +101,14 @@ class PDFTestHandler(BaseHTTPRequestHandler):
prefix = os.path.commonprefix(( path, DOC_ROOT )) prefix = os.path.commonprefix(( path, DOC_ROOT ))
_, ext = os.path.splitext(path) _, 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 if not (prefix == DOC_ROOT
and os.path.isfile(path) and os.path.isfile(path)
and ext in MIMEs): and ext in MIMEs):
print path
self.send_error(404) self.send_error(404)
return return
@ -102,14 +117,8 @@ class PDFTestHandler(BaseHTTPRequestHandler):
self.send_error(501) self.send_error(501)
return return
self.send_response(200) self.sendFile(path, ext)
self.send_header("Content-Type", MIMEs[ext])
self.end_headers()
# Sigh, os.sendfile() plz
f = open(path)
self.wfile.write(f.read())
f.close()
def do_POST(self): def do_POST(self):
@ -122,10 +131,20 @@ class PDFTestHandler(BaseHTTPRequestHandler):
result = json.loads(self.rfile.read(numBytes)) 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'] browser, id, failure, round, page, snapshot = result['browser'], result['id'], result['failure'], result['round'], result['page'], result['snapshot']
taskResults = State.taskResults[browser][id] taskResults = State.taskResults[browser][id]
taskResults[round].append(Result(snapshot, failure)) taskResults[round].append(Result(snapshot, failure, page))
assert len(taskResults[round]) == 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) check(State.manifest[id], taskResults, browser)
# Please oh please GC this ... # Please oh please GC this ...
del State.taskResults[browser][id] del State.taskResults[browser][id]
@ -138,6 +157,8 @@ class BrowserCommand():
def __init__(self, browserRecord): def __init__(self, browserRecord):
self.name = browserRecord["name"] self.name = browserRecord["name"]
self.path = browserRecord["path"] self.path = browserRecord["path"]
self.tempDir = None
self.process = None
if platform.system() == "Darwin" and (self.path.endswith(".app") or self.path.endswith(".app/")): if platform.system() == "Darwin" and (self.path.endswith(".app") or self.path.endswith(".app/")):
self._fixupMacPath() self._fixupMacPath()
@ -151,19 +172,30 @@ class BrowserCommand():
def setup(self): def setup(self):
self.tempDir = tempfile.mkdtemp() self.tempDir = tempfile.mkdtemp()
self.profileDir = os.path.join(self.tempDir, "profile") self.profileDir = os.path.join(self.tempDir, "profile")
print self.profileDir
shutil.copytree(os.path.join(DOC_ROOT, "test", "resources", "firefox"), shutil.copytree(os.path.join(DOC_ROOT, "test", "resources", "firefox"),
self.profileDir) self.profileDir)
def teardown(self): 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): def start(self, url):
cmds = [self.path] cmds = [self.path]
if platform.system() == "Darwin": if platform.system() == "Darwin":
cmds.append("-foreground") cmds.append("-foreground")
cmds.extend(["-no-remote", "-profile", self.profileDir, url]) cmds.extend(["-no-remote", "-profile", self.profileDir, url])
subprocess.call(cmds) self.process = subprocess.Popen(cmds)
def makeBrowserCommands(browserManifestFile): def makeBrowserCommands(browserManifestFile):
with open(browserManifestFile) as bmf: with open(browserManifestFile) as bmf:
@ -223,14 +255,23 @@ def setUp(options):
State.remaining = len(testBrowsers) * len(manifestList) 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: 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() 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): def check(task, results, browser):
failed = False failed = False
@ -385,8 +426,14 @@ def main():
httpd_thread.setDaemon(True) httpd_thread.setDaemon(True)
httpd_thread.start() httpd_thread.start()
setUp(options) browsers = setUp(options)
processResults() try:
startBrowsers(browsers, options)
while not State.done:
time.sleep(1)
processResults()
finally:
teardownBrowsers(browsers)
if __name__ == '__main__': if __name__ == '__main__':
main() main()

View File

@ -14,6 +14,12 @@
"rounds": 1, "rounds": 1,
"type": "load" "type": "load"
}, },
{ "id": "intelisa-load",
"file": "pdfs/intelisa.pdf",
"link": true,
"rounds": 1,
"type": "load"
},
{ "id": "pdfspec-load", { "id": "pdfspec-load",
"file": "pdfs/pdf.pdf", "file": "pdfs/pdf.pdf",
"link": true, "link": true,

View File

@ -6,7 +6,7 @@
<script type="text/javascript" src="/fonts.js"></script> <script type="text/javascript" src="/fonts.js"></script>
<script type="text/javascript" src="/glyphlist.js"></script> <script type="text/javascript" src="/glyphlist.js"></script>
<script type="application/javascript"> <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() { function queryParams() {
var qs = window.location.search.substring(1); var qs = window.location.search.substring(1);
@ -63,7 +63,15 @@ function nextTask() {
if (r.readyState == 4) { if (r.readyState == 4) {
var data = r.mozResponseArrayBuffer || r.mozResponse || var data = r.mozResponseArrayBuffer || r.mozResponse ||
r.responseArrayBuffer || r.response; 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(); currentTask.pageNum = 1, nextPage();
} }
}; };
@ -71,7 +79,7 @@ function nextTask() {
} }
function nextPage() { function nextPage() {
if (currentTask.pageNum > pdfDoc.numPages) { if (currentTask.pageNum > numPages) {
if (++currentTask.round < currentTask.rounds) { if (++currentTask.round < currentTask.rounds) {
log(" Round "+ (1 + currentTask.round) +"\n"); log(" Round "+ (1 + currentTask.round) +"\n");
currentTask.pageNum = 1; currentTask.pageNum = 1;
@ -88,43 +96,34 @@ function nextPage() {
clear(ctx); clear(ctx);
var fonts = []; var fonts = [];
var fontsReady = true; var gfx = null;
var gfx = new CanvasGraphics(ctx);
try { try {
gfx = new CanvasGraphics(ctx);
currentPage = pdfDoc.getPage(currentTask.pageNum); currentPage = pdfDoc.getPage(currentTask.pageNum);
currentPage.compile(gfx, fonts); 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) { } catch(e) {
failure = 'compile: '+ e.toString(); failure = 'compile: '+ e.toString();
} }
var checkFontsLoadedIntervalTimer = null; var fontLoaderTimer = null;
function checkFontsLoaded() { function checkFontsLoaded() {
for (var i = 0; i < count; i++) { try {
if (Fonts[font.name].loading) { if (!FontLoader.bind(fonts)) {
fontLoaderTimer = window.setTimeout(checkFontsLoaded, 10);
return; return;
} }
} catch(e) {
failure = 'fonts: '+ e.toString();
} }
window.clearInterval(checkFontsLoadedIntervalTimer);
snapshotCurrentPage(gfx); 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); snapshotCurrentPage(gfx);
} else { } 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")); sendTaskResult(canvas.toDataURL("image/png"));
log("done"+ (failure ? " (failed!)" : "") +"\n"); log("done"+ (failure ? " (failed!)" : "") +"\n");
++currentTask.pageNum, nextPage(); // Set up the next request
} backoff = (inFlightRequests > 0) ? inFlightRequests * 10 : 0;
function done() {
log("Done!\n");
setTimeout(function() { setTimeout(function() {
document.body.innerHTML = "Tests are finished. <h1>CLOSE ME!</h1>"; ++currentTask.pageNum, nextPage();
if (window.SpecialPowers)
SpecialPowers.quitApplication();
else
window.close();
}, },
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) { function sendTaskResult(snapshot) {
var result = { browser: browser, var result = { browser: browser,
id: currentTask.id, id: currentTask.id,
taskDone: currentTask.taskDone, numPages: numPages,
failure: failure, failure: failure,
file: currentTask.file, file: currentTask.file,
round: currentTask.round, round: currentTask.round,
@ -172,9 +181,14 @@ function sendTaskResult(snapshot) {
var r = new XMLHttpRequest(); var r = new XMLHttpRequest();
// (The POST URI is ignored atm.) // (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"); 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)); r.send(JSON.stringify(result));
} }
@ -194,7 +208,8 @@ function log(str) {
</head> </head>
<body onload="load();"> <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> </body>
</html> </html>

View File

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

View File

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