Merge branch 'master' into colorspace

This commit is contained in:
sbarman 2011-06-29 10:15:52 -07:00
commit 03a1a0369b
15 changed files with 516 additions and 163 deletions

12
README
View File

@ -1,12 +0,0 @@
pdf.js is a technology demonstrator prototype to explore whether the HTML5
platform is complete enough to faithfully and efficiently render the ISO
32000-1:2008 Portable Document Format (PDF) without native code assistance.
You can read more about pdf.js here:
http://andreasgal.com/2011/06/15/pdf-js/
http://blog.mozilla.com/cjones/2011/06/15/overview-of-pdf-js-guts/
Or follow us on twitter: @pdfjs
http://twitter.com/#!/pdfjs

27
README.md Normal file
View File

@ -0,0 +1,27 @@
# pdf.js
pdf.js is a technology demonstrator prototype to explore whether the HTML5
platform is complete enough to faithfully and efficiently render the ISO
32000-1:2008 Portable Document Format (PDF) without native code assistance.
pdf.js is not currently part of the Mozilla project, and there is no plan
yet to integrate it into Firefox. We will explore that possibility once
pdf.js is production ready. Until then we aim to publish a Firefox
PDF reader extension powered by pdf.js.
You can read more about pdf.js here:
http://andreasgal.com/2011/06/15/pdf-js/
http://blog.mozilla.com/cjones/2011/06/15/overview-of-pdf-js-guts/
follow us on twitter: @pdfjs
http://twitter.com/#!/pdfjs
join our mailing list:
dev-pdf-js@lists.mozilla.org
and talk to us on IRC:
#pdfjs on irc.mozilla.org

View File

@ -1,5 +1,5 @@
/* -*- Mode: Java; tab-width: s; indent-tabs-mode: nil; c-basic-offset: 2 -*- / /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
/* vim: set shiftwidth=s tabstop=2 autoindent cindent expandtab: */ /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
"use strict"; "use strict";
@ -45,12 +45,12 @@ var ARCFourCipher = (function() {
})(); })();
var md5 = (function() { var md5 = (function() {
const r = new Uint8Array([ var r = new Uint8Array([
7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 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, 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, 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]); 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21]);
const k = new Int32Array([ var k = new Int32Array([
-680876936, -389564586, 606105819, -1044525330, -176418897, 1200080426, -680876936, -389564586, 606105819, -1044525330, -176418897, 1200080426,
-1473231341, -45705983, 1770035416, -1958414417, -42063, -1990404162, -1473231341, -45705983, 1770035416, -1958414417, -42063, -1990404162,
1804603682, -40341101, -1502002290, 1236535329, -165796510, -1069501632, 1804603682, -40341101, -1502002290, 1236535329, -165796510, -1069501632,
@ -149,7 +149,7 @@ var CipherTransform = (function() {
var CipherTransformFactory = (function() { var CipherTransformFactory = (function() {
function prepareKeyData(fileId, password, ownerPassword, userPassword, flags, revision, keyLength) { function prepareKeyData(fileId, password, ownerPassword, userPassword, flags, revision, keyLength) {
const defaultPasswordBytes = new Uint8Array([ var defaultPasswordBytes = new Uint8Array([
0x28, 0xBF, 0x4E, 0x5E, 0x4E, 0x75, 0x8A, 0x41, 0x64, 0x00, 0x4E, 0x56, 0xFF, 0xFA, 0x01, 0x08, 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]); 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; var hashData = new Uint8Array(88), i = 0, j, n;

198
fonts.js
View File

@ -26,12 +26,15 @@ var fontName = "";
*/ */
var kDisableFonts = false; var kDisableFonts = false;
/** /**
* Hold a map of decoded fonts and of the standard fourteen Type1 fonts and * Hold a map of decoded fonts and of the standard fourteen Type1 fonts and
* their acronyms. * their acronyms.
* TODO Add the standard fourteen Type1 fonts list by default * TODO Add the standard fourteen Type1 fonts list by default
* http://cgit.freedesktop.org/poppler/poppler/tree/poppler/GfxFont.cc#n65 * http://cgit.freedesktop.org/poppler/poppler/tree/poppler/GfxFont.cc#n65
*/ */
var kScalePrecision = 40;
var Fonts = { var Fonts = {
_active: null, _active: null,
@ -39,8 +42,9 @@ var Fonts = {
return this._active; return this._active;
}, },
set active(name) { setActive: function fonts_setActive(name, size) {
this._active = this[name]; this._active = this[name];
this.ctx.font = (size * kScalePrecision) + 'px "' + name + '"';
}, },
charsToUnicode: function fonts_chars2Unicode(chars) { charsToUnicode: function fonts_chars2Unicode(chars) {
@ -64,8 +68,8 @@ var Fonts = {
var unicode = encoding[charcode]; var unicode = encoding[charcode];
// Check if the glyph has already been converted // Check if the glyph has already been converted
if (unicode instanceof Name) if (!IsNum(unicode))
unicode = encoding[unicode] = GlyphsUnicode[unicode.name]; unicode = encoding[unicode] = GlyphsUnicode[unicode.name];
// Handle surrogate pairs // Handle surrogate pairs
if (unicode > 0xFFFF) { if (unicode > 0xFFFF) {
@ -77,6 +81,16 @@ var Fonts = {
// Enter the translated string into the cache // Enter the translated string into the cache
return active.cache[chars] = str; return active.cache[chars] = str;
},
get ctx() {
var ctx = document.createElement("canvas").getContext("2d");
ctx.scale(1 / kScalePrecision, 1);
return shadow(this, "ctx", ctx);
},
measureText: function fonts_measureText(text) {
return this.ctx.measureText(text).width / kScalePrecision;
} }
}; };
@ -165,6 +179,7 @@ var Font = (function () {
warn("Font " + properties.type + " is not supported"); warn("Font " + properties.type + " is not supported");
break; break;
} }
this.data = data;
Fonts[name] = { Fonts[name] = {
data: data, data: data,
@ -720,7 +735,7 @@ var Font = (function () {
"\x00\x00" + // -reserved- "\x00\x00" + // -reserved-
"\x00\x00" + // -reserved- "\x00\x00" + // -reserved-
"\x00\x00" + // metricDataFormat "\x00\x00" + // metricDataFormat
string16(charstrings.length) string16(charstrings.length + 1) // Number of HMetrics
); );
createTableEntry(otf, offsets, "hhea", hhea); createTableEntry(otf, offsets, "hhea", hhea);
@ -730,18 +745,18 @@ var Font = (function () {
* while Windows use this data. So be careful if you hack on Linux and * while Windows use this data. So be careful if you hack on Linux and
* have to touch the 'hmtx' table * have to touch the 'hmtx' table
*/ */
hmtx = "\x01\xF4\x00\x00"; // Fake .notdef hmtx = "\x00\x00\x00\x00"; // Fake .notdef
var width = 0, lsb = 0; var width = 0, lsb = 0;
for (var i = 0; i < charstrings.length; i++) { for (var i = 0; i < charstrings.length; i++) {
width = charstrings[i].charstring[1]; var charstring = charstrings[i];
hmtx += string16(width) + string16(lsb); hmtx += string16(charstring.width) + string16(0);
} }
hmtx = stringToArray(hmtx); hmtx = stringToArray(hmtx);
createTableEntry(otf, offsets, "hmtx", hmtx); createTableEntry(otf, offsets, "hmtx", hmtx);
/** MAXP */ /** MAXP */
maxp = "\x00\x00\x50\x00" + // Version number maxp = "\x00\x00\x50\x00" + // Version number
string16(charstrings.length + 1); // Num of glyphs (+1 to pass the sanitizer...) string16(charstrings.length + 1); // Num of glyphs
maxp = stringToArray(maxp); maxp = stringToArray(maxp);
createTableEntry(otf, offsets, "maxp", maxp); createTableEntry(otf, offsets, "maxp", maxp);
@ -778,9 +793,18 @@ var Font = (function () {
}); });
}, },
bindDOM: function font_bindDom(data) { bindDOM: function font_bindDom(data, callback) {
var fontName = this.name; var fontName = this.name;
// Just adding the font-face to the DOM doesn't make it load. It
// seems it's loaded once Gecko notices it's used. Therefore,
// add a div on the page using the loaded font.
var div = document.createElement("div");
var style = 'font-family:"' + name +
'";position: absolute;top:-99999;left:-99999;z-index:-99999';
div.setAttribute("style", style);
document.body.appendChild(div);
/** Hack begin */ /** 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
@ -800,15 +824,19 @@ var Font = (function () {
// 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 (textWidth == ctx.measureText(testString).width) {
window.clearInterval(interval); if ((Date.now() - this.start) < kMaxWaitForFontFace) {
Fonts[fontName].loading = false; return;
warn("Is " + fontName + " loaded?"); } else {
this.start = 0; warn("Is " + fontName + " loaded?");
} else if (textWidth != ctx.measureText(testString).width) { }
window.clearInterval(interval); }
Fonts[fontName].loading = false;
this.start = 0; window.clearInterval(interval);
Fonts[fontName].loading = false;
this.start = 0;
if (callback) {
callback();
} }
}, 30, this); }, 30, this);
@ -839,7 +867,7 @@ var FontsUtils = {
bytes.set([value]); bytes.set([value]);
return bytes[0]; return bytes[0];
} else if (bytesCount == 2) { } else if (bytesCount == 2) {
bytes.set([value >> 8, value]); bytes.set([value >> 8, value & 0xff]);
return [bytes[0], bytes[1]]; return [bytes[0], bytes[1]];
} else if (bytesCount == 4) { } else if (bytesCount == 4) {
bytes.set([value >> 24, value >> 16, value >> 8, value]); bytes.set([value >> 24, value >> 16, value >> 8, value]);
@ -980,16 +1008,8 @@ var Type1Parser = function() {
"12": "div", "12": "div",
// callothersubr is a mechanism to make calls on the postscript // callothersubr is a mechanism to make calls on the postscript
// interpreter. // interpreter, this is not supported by Type2 charstring but hopefully
// TODO When decodeCharstring encounter such a command it should // most of the default commands can be ignored safely.
// directly do:
// - pop the previous charstring[] command into 'index'
// - pop the previous charstring[] command and ignore it, it is
// normally the number of element to push on the stack before
// the command but since everything will be pushed on the stack
// by the PS interpreter when it will read them that is safe to
// ignore this command
// - push the content of the OtherSubrs[index] inside charstring[]
"16": "callothersubr", "16": "callothersubr",
"17": "pop", "17": "pop",
@ -1009,8 +1029,13 @@ var Type1Parser = function() {
"31": "hvcurveto" "31": "hvcurveto"
}; };
var kEscapeCommand = 12;
function decodeCharString(array) { function decodeCharString(array) {
var charString = []; var charstring = [];
var lsb = 0;
var width = 0;
var used = false;
var value = ""; var value = "";
var count = array.length; var count = array.length;
@ -1019,10 +1044,48 @@ var Type1Parser = function() {
if (value < 32) { if (value < 32) {
var command = null; var command = null;
if (value == 12) { if (value == kEscapeCommand) {
var escape = array[++i]; var escape = array[++i];
// TODO Clean this code
if (escape == 16) {
var index = charstring.pop();
var argc = charstring.pop();
var data = charstring.pop();
// If the flex mechanishm is not used in a font program, Adobe
// state that that entries 0, 1 and 2 can simply be replace by
// {}, which means that we can simply ignore them.
if (index < 3) {
continue;
}
// This is the same things about hint replacement, if it is not used
// entry 3 can be replaced by {3}
if (index == 3) {
charstring.push(3);
i++;
continue;
}
}
command = charStringDictionary["12"][escape]; command = charStringDictionary["12"][escape];
} else { } else {
// TODO Clean this code
if (value == 13) {
if (charstring.length == 2) {
width = charstring[1];
} else if (charstring.length == 4 && charstring[3] == "div") {
width = charstring[1] / charstring[2];
} else {
error("Unsupported hsbw format: " + charstring);
}
lsb = charstring[0];
charstring.push(lsb, "hmoveto");
charstring.splice(0, 1);
continue;
}
command = charStringDictionary[value]; command = charStringDictionary[value];
} }
@ -1044,16 +1107,14 @@ var Type1Parser = function() {
} else if (value <= 254) { } else if (value <= 254) {
value = -((value - 251) * 256) - parseInt(array[++i]) - 108; value = -((value - 251) * 256) - parseInt(array[++i]) - 108;
} else { } else {
var byte = array[++i]; value = (array[++i] & 0xff) << 24 | (array[++i] & 0xff) << 16 |
var high = (byte >> 1); (array[++i] & 0xff) << 8 | (array[++i] & 0xff) << 0;
value = (byte - high) << 24 | array[++i] << 16 |
array[++i] << 8 | array[++i];
} }
charString.push(value); charstring.push(value);
} }
return charString; return { charstring: charstring, width: width, lsb: lsb };
}; };
/** /**
@ -1080,19 +1141,21 @@ var Type1Parser = function() {
length = parseInt(length); length = parseInt(length);
var data = eexecString.slice(i + 3, i + 3 + length); var data = eexecString.slice(i + 3, i + 3 + length);
var encodedSubr = decrypt(data, kCharStringsEncryptionKey, 4); var encodedSubr = decrypt(data, kCharStringsEncryptionKey, 4);
var subr = decodeCharString(encodedSubr); var str = decodeCharString(encodedSubr);
subrs.push(subr); subrs.push(str.charstring);
i += 3 + length; i += 3 + length;
} else if (inGlyphs && c == 0x52) { } else if (inGlyphs && c == 0x52) {
length = parseInt(length); length = parseInt(length);
var data = eexecString.slice(i + 3, i + 3 + length); var data = eexecString.slice(i + 3, i + 3 + length);
var encodedCharstring = decrypt(data, kCharStringsEncryptionKey, 4); var encodedCharstring = decrypt(data, kCharStringsEncryptionKey, 4);
var subr = decodeCharString(encodedCharstring); var str = decodeCharString(encodedCharstring);
glyphs.push({ glyphs.push({
glyph: glyph, glyph: glyph,
data: subr data: str.charstring,
lsb: str.lsb,
width: str.width
}); });
i += 3 + length; i += 3 + length;
} else if (inGlyphs && c == 0x2F) { } else if (inGlyphs && c == 0x2F) {
@ -1254,16 +1317,18 @@ CFF.prototype = {
var charstrings = []; var charstrings = [];
for (var i = 0; i < glyphs.length; i++) { for (var i = 0; i < glyphs.length; i++) {
var glyph = glyphs[i].glyph; var glyph = glyphs[i];
var unicode = GlyphsUnicode[glyph]; var unicode = GlyphsUnicode[glyph.glyph];
if (!unicode) { if (!unicode) {
if (glyph != ".notdef") if (glyph.glyph != ".notdef")
warn(glyph + " does not have an entry in the glyphs unicode dictionary"); warn(glyph + " does not have an entry in the glyphs unicode dictionary");
} else { } else {
charstrings.push({ charstrings.push({
glyph: glyph, glyph: glyph,
unicode: unicode, unicode: unicode,
charstring: glyphs[i].data charstring: glyph.data,
width: glyph.width,
lsb: glyph.lsb
}); });
} }
}; };
@ -1305,46 +1370,11 @@ CFF.prototype = {
var i = 0; var i = 0;
while (true) { while (true) {
var obj = charstring[i]; var obj = charstring[i];
if (obj == null) if (obj == undefined) {
return []; error("unknow charstring command for " + i + " in " + charstring);
}
if (obj.charAt) { if (obj.charAt) {
switch (obj) { switch (obj) {
case "callothersubr":
var index = charstring[i - 1];
var count = charstring[i - 2];
var data = charstring[i - 3];
// If the flex mechanishm is not used in a font program, Adobe
// state that that entries 0, 1 and 2 can simply be replace by
// {}, which means that we can simply ignore them.
if (index < 3) {
i -= 3;
continue;
}
// This is the same things about hint replacment, if it is not used
// entry 3 can be replaced by {}
if (index == 3) {
if (!data) {
charstring.splice(i - 2, 4, 3);
i -= 3;
} else {
// 5 to remove the arguments, the callothersubr call and the pop command
charstring.splice(i - 3, 5, 3);
i -= 3;
}
}
break;
case "hsbw":
var charWidthVector = charstring[1];
var leftSidebearing = charstring[0];
charstring.splice(i, 1, leftSidebearing, "hmoveto");
charstring.splice(0, 1);
break;
case "endchar": case "endchar":
case "return": case "return":
// CharString is ready to be re-encode to commands number at this point // CharString is ready to be re-encode to commands number at this point
@ -1356,7 +1386,7 @@ CFF.prototype = {
} else if (command.charAt) { } else if (command.charAt) {
var cmd = this.commandsMap[command]; var cmd = this.commandsMap[command];
if (!cmd) if (!cmd)
error(command); error("Unknow command: " + command);
if (IsArray(cmd)) { if (IsArray(cmd)) {
charstring.splice(j, 1, cmd[0], cmd[1]); charstring.splice(j, 1, cmd[0], cmd[1]);

48
pdf.js
View File

@ -641,7 +641,7 @@ var PredictorStream = (function() {
var pixBytes = this.pixBytes = (colors * bits + 7) >> 3; var pixBytes = this.pixBytes = (colors * bits + 7) >> 3;
// add an extra pixByte to represent the pixel left of column 0 // add an extra pixByte to represent the pixel left of column 0
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; return this;
} }
@ -816,7 +816,7 @@ var DecryptStream = (function() {
DecodeStream.call(this); DecodeStream.call(this);
} }
const chunkSize = 512; var chunkSize = 512;
constructor.prototype = Object.create(DecodeStream.prototype); constructor.prototype = Object.create(DecodeStream.prototype);
constructor.prototype.readBlock = function() { constructor.prototype.readBlock = function() {
@ -910,18 +910,18 @@ var Ascii85Stream = (function() {
var CCITTFaxStream = (function() { var CCITTFaxStream = (function() {
const ccittEOL = -2; var ccittEOL = -2;
const twoDimPass = 0; var twoDimPass = 0;
const twoDimHoriz = 1; var twoDimHoriz = 1;
const twoDimVert0 = 2; var twoDimVert0 = 2;
const twoDimVertR1 = 3; var twoDimVertR1 = 3;
const twoDimVertL1 = 4; var twoDimVertL1 = 4;
const twoDimVertR2 = 5; var twoDimVertR2 = 5;
const twoDimVertL2 = 6; var twoDimVertL2 = 6;
const twoDimVertR3 = 7; var twoDimVertR3 = 7;
const twoDimVertL3 = 8; var twoDimVertL3 = 8;
const twoDimTable = [ var twoDimTable = [
[-1, -1], [-1, -1], // 000000x [-1, -1], [-1, -1], // 000000x
[7, twoDimVertL3], // 0000010 [7, twoDimVertL3], // 0000010
[7, twoDimVertR3], // 0000011 [7, twoDimVertR3], // 0000011
@ -989,7 +989,7 @@ var CCITTFaxStream = (function() {
[1, twoDimVert0], [1, twoDimVert0] [1, twoDimVert0], [1, twoDimVert0]
]; ];
const whiteTable1 = [ var whiteTable1 = [
[-1, -1], // 00000 [-1, -1], // 00000
[12, ccittEOL], // 00001 [12, ccittEOL], // 00001
[-1, -1], [-1, -1], // 0001x [-1, -1], [-1, -1], // 0001x
@ -1011,7 +1011,7 @@ var CCITTFaxStream = (function() {
[12, 2560] // 11111 [12, 2560] // 11111
]; ];
const whiteTable2 = [ var whiteTable2 = [
[-1, -1], [-1, -1], [-1, -1], [-1, -1], // 0000000xx [-1, -1], [-1, -1], [-1, -1], [-1, -1], // 0000000xx
[8, 29], [8, 29], // 00000010x [8, 29], [8, 29], // 00000010x
[8, 30], [8, 30], // 00000011x [8, 30], [8, 30], // 00000011x
@ -1175,7 +1175,7 @@ var CCITTFaxStream = (function() {
[4, 7], [4, 7], [4, 7], [4, 7] [4, 7], [4, 7], [4, 7], [4, 7]
]; ];
const blackTable1 = [ var blackTable1 = [
[-1, -1], [-1, -1], // 000000000000x [-1, -1], [-1, -1], // 000000000000x
[12, ccittEOL], [12, ccittEOL], // 000000000001x [12, ccittEOL], [12, ccittEOL], // 000000000001x
[-1, -1], [-1, -1], [-1, -1], [-1, -1], // 00000000001xx [-1, -1], [-1, -1], [-1, -1], [-1, -1], // 00000000001xx
@ -1236,7 +1236,7 @@ var CCITTFaxStream = (function() {
[10, 64], [10, 64], [10, 64], [10, 64] [10, 64], [10, 64], [10, 64], [10, 64]
]; ];
const blackTable2 = [ var blackTable2 = [
[8, 13], [8, 13], [8, 13], [8, 13], // 00000100xxxx [8, 13], [8, 13], [8, 13], [8, 13], // 00000100xxxx
[8, 13], [8, 13], [8, 13], [8, 13], [8, 13], [8, 13], [8, 13], [8, 13],
[8, 13], [8, 13], [8, 13], [8, 13], [8, 13], [8, 13], [8, 13], [8, 13],
@ -1315,7 +1315,7 @@ var CCITTFaxStream = (function() {
[7, 12], [7, 12], [7, 12], [7, 12] [7, 12], [7, 12], [7, 12], [7, 12]
]; ];
const blackTable3 = [ var blackTable3 = [
[-1, -1], [-1, -1], [-1, -1], [-1, -1], // 0000xx [-1, -1], [-1, -1], [-1, -1], [-1, -1], // 0000xx
[6, 9], // 000100 [6, 9], // 000100
[6, 8], // 000101 [6, 8], // 000101
@ -3809,18 +3809,24 @@ var CanvasGraphics = (function() {
if (fontDescriptor && fontDescriptor.num) { if (fontDescriptor && fontDescriptor.num) {
var fontDescriptor = this.xref.fetchIfRef(fontDescriptor); var fontDescriptor = this.xref.fetchIfRef(fontDescriptor);
fontName = fontDescriptor.get("FontName").name.replace("+", "_"); fontName = fontDescriptor.get("FontName").name.replace("+", "_");
Fonts.active = fontName; Fonts.setActive(fontName, size);
} }
if (!fontName) { if (!fontName) {
// TODO: fontDescriptor is not available, fallback to default font // TODO: fontDescriptor is not available, fallback to default font
this.current.fontSize = size; this.current.fontSize = size;
this.ctx.font = this.current.fontSize + 'px sans-serif'; this.ctx.font = this.current.fontSize + 'px sans-serif';
Fonts.setActive("sans-serif", this.current.fontSize);
return; return;
} }
this.current.fontName = fontName;
this.current.fontSize = size; this.current.fontSize = size;
this.ctx.font = this.current.fontSize +'px "' + fontName + '", Symbol';
this.ctx.font = this.current.fontSize + 'px "' + fontName + '"';
if (this.ctx.$setFont) {
this.ctx.$setFont(fontName);
}
}, },
setTextRenderingMode: function(mode) { setTextRenderingMode: function(mode) {
TODO("text rendering mode"); TODO("text rendering mode");
@ -3864,7 +3870,7 @@ var CanvasGraphics = (function() {
text = Fonts.charsToUnicode(text); text = Fonts.charsToUnicode(text);
this.ctx.translate(this.current.x, -1 * this.current.y); this.ctx.translate(this.current.x, -1 * this.current.y);
this.ctx.fillText(text, 0, 0); this.ctx.fillText(text, 0, 0);
this.current.x += this.ctx.measureText(text).width; this.current.x += Fonts.measureText(text);
} }
this.ctx.restore(); this.ctx.restore();

View File

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

View File

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

View File

@ -2,7 +2,7 @@ import json, platform, os, shutil, sys, subprocess, tempfile, threading, time, u
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
import SocketServer import SocketServer
from optparse import OptionParser from optparse import OptionParser
from urlparse import urlparse from urlparse import urlparse, parse_qs
USAGE_EXAMPLE = "%prog" USAGE_EXAMPLE = "%prog"
@ -132,6 +132,11 @@ class PDFTestHandler(BaseHTTPRequestHandler):
self.send_header('Content-Type', 'text/plain') self.send_header('Content-Type', 'text/plain')
self.end_headers() self.end_headers()
url = urlparse(self.path)
if url.path == "/tellMeToQuit":
tellAppToQuit(url.path, url.query)
return
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]
@ -156,8 +161,20 @@ class PDFTestHandler(BaseHTTPRequestHandler):
State.done = (0 == State.remaining) State.done = (0 == State.remaining)
# this just does Firefox for now # Applescript hack to quit Chrome on Mac
class BrowserCommand(): def tellAppToQuit(path, query):
if platform.system() != "Darwin":
return
d = parse_qs(query)
path = d['path'][0]
cmd = """osascript<<END
tell application "%s"
quit
end tell
END""" % path
os.system(cmd)
class BaseBrowserCommand(object):
def __init__(self, browserRecord): def __init__(self, browserRecord):
self.name = browserRecord["name"] self.name = browserRecord["name"]
self.path = browserRecord["path"] self.path = browserRecord["path"]
@ -170,14 +187,9 @@ class BrowserCommand():
if not os.path.exists(self.path): if not os.path.exists(self.path):
throw("Path to browser '%s' does not exist." % self.path) throw("Path to browser '%s' does not exist." % self.path)
def _fixupMacPath(self):
self.path = os.path.join(self.path, "Contents", "MacOS", "firefox-bin")
def setup(self): 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")
shutil.copytree(os.path.join(DOC_ROOT, "test", "resources", "firefox"),
self.profileDir)
def teardown(self): def teardown(self):
# If the browser is still running, wait up to ten seconds for it to quit # If the browser is still running, wait up to ten seconds for it to quit
@ -194,6 +206,18 @@ class BrowserCommand():
if self.tempDir is not None and os.path.exists(self.tempDir): if self.tempDir is not None and os.path.exists(self.tempDir):
shutil.rmtree(self.tempDir) shutil.rmtree(self.tempDir)
def start(self, url):
raise Exception("Can't start BaseBrowserCommand")
class FirefoxBrowserCommand(BaseBrowserCommand):
def _fixupMacPath(self):
self.path = os.path.join(self.path, "Contents", "MacOS", "firefox-bin")
def setup(self):
super(FirefoxBrowserCommand, self).setup()
shutil.copytree(os.path.join(DOC_ROOT, "test", "resources", "firefox"),
self.profileDir)
def start(self, url): def start(self, url):
cmds = [self.path] cmds = [self.path]
if platform.system() == "Darwin": if platform.system() == "Darwin":
@ -201,9 +225,29 @@ class BrowserCommand():
cmds.extend(["-no-remote", "-profile", self.profileDir, url]) cmds.extend(["-no-remote", "-profile", self.profileDir, url])
self.process = subprocess.Popen(cmds) self.process = subprocess.Popen(cmds)
class ChromeBrowserCommand(BaseBrowserCommand):
def _fixupMacPath(self):
self.path = os.path.join(self.path, "Contents", "MacOS", "Google Chrome")
def start(self, url):
cmds = [self.path]
cmds.extend(["--user-data-dir=%s" % self.profileDir,
"--no-first-run", "--disable-sync", url])
self.process = subprocess.Popen(cmds)
def makeBrowserCommand(browser):
path = browser["path"].lower()
name = browser["name"].lower()
if name.find("firefox") > -1 or path.find("firefox") > -1:
return FirefoxBrowserCommand(browser)
elif name.find("chrom") > -1 or path.find("chrom") > -1:
return ChromeBrowserCommand(browser)
else:
raise Exception("Unrecognized browser: %s" % browser)
def makeBrowserCommands(browserManifestFile): def makeBrowserCommands(browserManifestFile):
with open(browserManifestFile) as bmf: with open(browserManifestFile) as bmf:
browsers = [BrowserCommand(browser) for browser in json.load(bmf)] browsers = [makeBrowserCommand(browser) for browser in json.load(bmf)]
return browsers return browsers
def downloadLinkedPDFs(manifestList): def downloadLinkedPDFs(manifestList):
@ -267,6 +311,7 @@ def startBrowsers(browsers, options):
b.setup() b.setup()
print 'Launching', b.name print 'Launching', b.name
qs = 'browser='+ urllib.quote(b.name) +'&manifestFile='+ urllib.quote(options.manifestFile) qs = 'browser='+ urllib.quote(b.name) +'&manifestFile='+ urllib.quote(options.manifestFile)
qs += '&path=' + b.path
b.start('http://localhost:8080/test/test_slave.html?'+ qs) b.start('http://localhost:8080/test/test_slave.html?'+ qs)
def teardownBrowsers(browsers): def teardownBrowsers(browsers):

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, numPages, pdfDoc, stdout; var appPath, 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);
@ -23,12 +23,13 @@ function load() {
var params = queryParams(); var params = queryParams();
browser = params.browser; browser = params.browser;
manifestFile = params.manifestFile; manifestFile = params.manifestFile;
appPath = params.path;
canvas = document.createElement("canvas"); canvas = document.createElement("canvas");
canvas.mozOpaque = true; canvas.mozOpaque = true;
stdout = document.getElementById("stdout"); stdout = document.getElementById("stdout");
log("Harness thinks this browser is '"+ browser +"'\n"); log("Harness thinks this browser is '"+ browser + "' with path " + appPath + "\n");
log("Fetching manifest "+ manifestFile +"..."); log("Fetching manifest "+ manifestFile +"...");
var r = new XMLHttpRequest(); var r = new XMLHttpRequest();
@ -157,13 +158,21 @@ function snapshotCurrentPage(gfx) {
); );
} }
function sendQuitRequest() {
var r = new XMLHttpRequest();
r.open("POST", "/tellMeToQuit?path=" + escape(appPath), false);
r.send("");
}
function quitApp() { function quitApp() {
log("Done!"); log("Done!");
document.body.innerHTML = "Tests are finished. <h1>CLOSE ME!</h1>"; document.body.innerHTML = "Tests are finished. <h1>CLOSE ME!</h1>";
if (window.SpecialPowers) if (window.SpecialPowers) {
SpecialPowers.quitApplication(); SpecialPowers.quitApplication();
else } else {
sendQuitRequest();
window.close(); window.close();
}
} }
function done() { function done() {

View File

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

View File

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

View File

@ -18,12 +18,124 @@ if (typeof console.time == "undefined") {
}; };
} }
function FontWorker() {
this.worker = new Worker("worker/font.js");
this.fontsWaiting = 0;
this.fontsWaitingCallbacks = [];
// Listen to the WebWorker for data and call actionHandler on it.
this.worker.onmessage = function(event) {
var data = event.data;
var actionHandler = this.actionHandler
if (data.action in actionHandler) {
actionHandler[data.action].call(this, data.data);
} else {
throw "Unkown action from worker: " + data.action;
}
}.bind(this);
this.$handleFontLoadedCallback = this.handleFontLoadedCallback.bind(this);
}
FontWorker.prototype = {
handleFontLoadedCallback: function() {
// Decrease the number of fonts wainting to be loaded.
this.fontsWaiting--;
// If all fonts are available now, then call all the callbacks.
if (this.fontsWaiting == 0) {
var callbacks = this.fontsWaitingCallbacks;
for (var i = 0; i < callbacks.length; i++) {
callbacks[i]();
}
this.fontsWaitingCallbacks.length = 0;
}
},
actionHandler: {
"log": function(data) {
console.log.apply(console, data);
},
"fonts": function(data) {
// console.log("got processed fonts from worker", Object.keys(data));
for (name in data) {
// Update the
Fonts[name].properties = {
encoding: data[name].encoding
}
// Call `Font.prototype.bindDOM` to make the font get loaded on the page.
Font.prototype.bindDOM.call(
Fonts[name],
data[name].str,
// IsLoadedCallback.
this.$handleFontLoadedCallback
);
}
}
},
ensureFonts: function(data, callback) {
var font;
var notLoaded = [];
for (var i = 0; i < data.length; i++) {
font = data[i];
if (Fonts[font.name]) {
continue;
}
// Store only the data on Fonts that is needed later on, such that we
// hold track on as lease memory as possible.
Fonts[font.name] = {
name: font.name,
mimetype: font.mimetype,
// This is set later on the worker replay. For some fonts, the encoding
// is calculated during the conversion process happening on the worker
// and therefore is not available right now.
// properties: {
// encoding: font.properties.encoding
// },
cache: Object.create(null)
};
// Mark this font to be handled later.
notLoaded.push(font);
// Increate the number of fonts to wait for.
this.fontsWaiting++;
}
console.time("ensureFonts");
// If there are fonts, that need to get loaded, tell the FontWorker to get
// started and push the callback on the waiting-callback-stack.
if (notLoaded.length != 0) {
console.log("fonts -> FontWorker");
// Send the worker the fonts to work on.
this.worker.postMessage({
action: "fonts",
data: notLoaded
});
if (callback) {
this.fontsWaitingCallbacks.push(callback);
}
}
// All fonts are present? Well, then just call the callback if there is one.
else {
if (callback) {
callback();
}
}
},
}
function WorkerPDFDoc(canvas) { function WorkerPDFDoc(canvas) {
var timer = null var timer = null
this.ctx = canvas.getContext("2d"); this.ctx = canvas.getContext("2d");
this.canvas = canvas; this.canvas = canvas;
this.worker = new Worker('pdf_worker.js'); this.worker = new Worker('worker/pdf.js');
this.fontWorker = new FontWorker();
this.waitingForFonts = false;
this.waitingForFontsCallback = [];
this.numPage = 1; this.numPage = 1;
this.numPages = null; this.numPages = null;
@ -56,6 +168,7 @@ function WorkerPDFDoc(canvas) {
}, },
"$showText": function(y, text) { "$showText": function(y, text) {
text = Fonts.charsToUnicode(text);
this.translate(currentX, -1 * y); this.translate(currentX, -1 * y);
this.fillText(text, 0, 0); this.fillText(text, 0, 0);
currentX += this.measureText(text).width; currentX += this.measureText(text).width;
@ -136,6 +249,10 @@ function WorkerPDFDoc(canvas) {
throw "Pattern not found"; throw "Pattern not found";
} }
this.strokeStyle = pattern; this.strokeStyle = pattern;
},
"$setFont": function(name) {
Fonts.active = name;
} }
} }
@ -187,6 +304,18 @@ function WorkerPDFDoc(canvas) {
div.setAttribute("style", style); div.setAttribute("style", style);
document.body.appendChild(div); document.body.appendChild(div);
}, },
"fonts": function(data) {
this.waitingForFonts = true;
this.fontWorker.ensureFonts(data, function() {
this.waitingForFonts = false;
var callbacks = this.waitingForFontsCallback;
for (var i = 0; i < callbacks.length; i++) {
callbacks[i]();
}
this.waitingForFontsCallback.length = 0;
}.bind(this));
},
"jpeg_stream": function(data) { "jpeg_stream": function(data) {
var img = new Image(); var img = new Image();
@ -207,11 +336,9 @@ function WorkerPDFDoc(canvas) {
canvasList[id] = newCanvas; canvasList[id] = newCanvas;
} }
// There might be fonts that need to get loaded. Shedule the var renderData = function() {
// rendering at the end of the event queue ensures this.
setTimeout(function() {
if (id == 0) { if (id == 0) {
console.time("canvas rendering"); console.time("main canvas rendering");
var ctx = this.ctx; var ctx = this.ctx;
ctx.save(); ctx.save();
ctx.fillStyle = "rgb(255, 255, 255)"; ctx.fillStyle = "rgb(255, 255, 255)";
@ -219,12 +346,27 @@ function WorkerPDFDoc(canvas) {
ctx.restore(); ctx.restore();
} }
renderProxyCanvas(canvasList[id], cmdQueue); renderProxyCanvas(canvasList[id], cmdQueue);
if (id == 0) console.timeEnd("canvas rendering") if (id == 0) {
}, 0, this); console.timeEnd("main canvas rendering");
console.timeEnd(">>> total page display time:");
}
}.bind(this);
if (this.waitingForFonts) {
if (id == 0) {
console.log("want to render, but not all fonts are there", id);
this.waitingForFontsCallback.push(renderData);
} else {
// console.log("assume canvas doesn't have fonts", id);
renderData();
}
} else {
renderData();
}
} }
} }
// List to the WebWorker for data and call actionHandler on it. // Listen to the WebWorker for data and call actionHandler on it.
this.worker.onmessage = function(event) { this.worker.onmessage = function(event) {
var data = event.data; var data = event.data;
if (data.action in actionHandler) { if (data.action in actionHandler) {
@ -232,7 +374,7 @@ function WorkerPDFDoc(canvas) {
} else { } else {
throw "Unkown action from worker: " + data.action; throw "Unkown action from worker: " + data.action;
} }
} }.bind(this)
} }
WorkerPDFDoc.prototype.open = function(url, callback) { WorkerPDFDoc.prototype.open = function(url, callback) {
@ -255,6 +397,8 @@ WorkerPDFDoc.prototype.open = function(url, callback) {
WorkerPDFDoc.prototype.showPage = function(numPage) { WorkerPDFDoc.prototype.showPage = function(numPage) {
this.numPage = parseInt(numPage); this.numPage = parseInt(numPage);
console.log("=== start rendering page " + numPage + " ===");
console.time(">>> total page display time:");
this.worker.postMessage(numPage); this.worker.postMessage(numPage);
if (this.onChangePage) { if (this.onChangePage) {
this.onChangePage(numPage); this.onChangePage(numPage);

27
worker/console.js Normal file
View File

@ -0,0 +1,27 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
"use strict";
var consoleTimer = {};
var console = {
log: function log() {
var args = Array.prototype.slice.call(arguments);
postMessage({
action: "log",
data: args
});
},
time: function(name) {
consoleTimer[name] = Date.now();
},
timeEnd: function(name) {
var time = consoleTimer[name];
if (time == null) {
throw "Unkown timer name " + name;
}
this.log("Timer:", name, Date.now() - time);
}
}

65
worker/font.js Normal file
View File

@ -0,0 +1,65 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
"use strict";
importScripts("console.js");
importScripts("../pdf.js");
importScripts("../fonts.js");
importScripts("../glyphlist.js")
function fontDataToString(font) {
// Doing postMessage on objects make them lose their "shape". This adds the
// "shape" for all required objects agains, such that the encoding works as
// expected.
var fontFileDict = new Dict();
fontFileDict.map = font.file.dict.map;
var fontFile = new Stream(font.file.bytes, font.file.start, font.file.end - font.file.start, fontFileDict);
font.file = new FlateStream(fontFile);
// This will encode the font.
var fontObj = new Font(font.name, font.file, font.properties);
// Create string that is used for css later.
var str = "";
var data = fontObj.data;
var length = data.length;
for (var j = 0; j < length; j++)
str += String.fromCharCode(data[j]);
return {
str: str,
encoding: font.properties.encoding
}
}
/**
* Functions to handle data sent by the MainThread.
*/
var actionHandler = {
"fonts": function(data) {
var fontData;
var result = {};
for (var i = 0; i < data.length; i++) {
fontData = data[i];
result[fontData.name] = fontDataToString(fontData);
}
postMessage({
action: "fonts",
data: result
})
},
}
// Listen to the MainThread for data and call actionHandler on it.
this.onmessage = function(event) {
var data = event.data;
if (data.action in actionHandler) {
actionHandler[data.action].call(this, data.data);
} else {
throw "Unkown action from worker: " + data.action;
}
}

View File

@ -27,10 +27,11 @@ var console = {
} }
// //
importScripts("canvas_proxy.js"); importScripts("console.js")
importScripts("pdf.js"); importScripts("canvas.js");
importScripts("fonts.js"); importScripts("../pdf.js");
importScripts("glyphlist.js") importScripts("../fonts.js");
importScripts("../glyphlist.js")
// Use the JpegStreamProxy proxy. // Use the JpegStreamProxy proxy.
JpegStream = JpegStreamProxy; JpegStream = JpegStreamProxy;
@ -65,21 +66,14 @@ onmessage = function(event) {
page.compile(gfx, fonts); page.compile(gfx, fonts);
console.timeEnd("compile"); console.timeEnd("compile");
// Send fonts to the main thread.
console.time("fonts"); console.time("fonts");
// Inspect fonts and translate the missing one. postMessage({
var count = fonts.length; action: "fonts",
for (var i = 0; i < count; i++) { data: fonts
var font = fonts[i]; });
if (Fonts[font.name]) {
fontsReady = fontsReady && !Fonts[font.name].loading;
continue;
}
// This "builds" the font and sents it over to the main thread.
new Font(font.name, font.file, font.properties);
}
console.timeEnd("fonts"); console.timeEnd("fonts");
console.time("display"); console.time("display");
page.display(gfx); page.display(gfx);
canvas.flush(); canvas.flush();