diff --git a/crypto.js b/crypto.js
new file mode 100644
index 000000000..14cc21902
--- /dev/null
+++ b/crypto.js
@@ -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;
+})();
diff --git a/fonts.js b/fonts.js
index f00f5c75f..ac06b76ae 100644
--- a/fonts.js
+++ b/fonts.js
@@ -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;
diff --git a/multi_page_viewer.html b/multi_page_viewer.html
index 47234686d..649e3a7cc 100644
--- a/multi_page_viewer.html
+++ b/multi_page_viewer.html
@@ -6,6 +6,7 @@
+
diff --git a/multi_page_viewer.js b/multi_page_viewer.js
index 3a02ea332..f262734d3 100644
--- a/multi_page_viewer.js
+++ b/multi_page_viewer.js
@@ -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);
diff --git a/pdf.js b/pdf.js
index 341d8fe06..180d96588 100644
--- a/pdf.js
+++ b/pdf.js
@@ -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) {
diff --git a/test/pdfs/intelisa.pdf.link b/test/pdfs/intelisa.pdf.link
new file mode 100644
index 000000000..371cdf947
--- /dev/null
+++ b/test/pdfs/intelisa.pdf.link
@@ -0,0 +1 @@
+http://www.intel.com/Assets/PDF/manual/253665.pdf
\ No newline at end of file
diff --git a/test/resources/favicon.ico b/test/resources/favicon.ico
new file mode 100644
index 000000000..d44438903
Binary files /dev/null and b/test/resources/favicon.ico differ
diff --git a/test/resources/firefox/user.js b/test/resources/firefox/user.js
index d4b9d4130..b01e2eb76 100644
--- a/test/resources/firefox/user.js
+++ b/test/resources/firefox/user.js
@@ -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
diff --git a/test/test.py b/test/test.py
index 53f65f78b..5aece2c24 100644
--- a/test/test.py
+++ b/test/test.py
@@ -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()
diff --git a/test/test_manifest.json b/test/test_manifest.json
index e4a7ada81..9b9d5e333 100644
--- a/test/test_manifest.json
+++ b/test/test_manifest.json
@@ -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,
diff --git a/test/test_slave.html b/test/test_slave.html
index 1053025e1..32076d075 100644
--- a/test/test_slave.html
+++ b/test/test_slave.html
@@ -6,7 +6,7 @@
+
diff --git a/viewer.js b/viewer.js
index 41aaf354c..c7be739bc 100644
--- a/viewer.js
+++ b/viewer.js
@@ -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() {