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 -*- /
/* vim: set shiftwidth=s tabstop=2 autoindent cindent expandtab: */
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
"use strict";
@ -45,12 +45,12 @@ var ARCFourCipher = (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,
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([
var k = new Int32Array([
-680876936, -389564586, 606105819, -1044525330, -176418897, 1200080426,
-1473231341, -45705983, 1770035416, -1958414417, -42063, -1990404162,
1804603682, -40341101, -1502002290, 1236535329, -165796510, -1069501632,
@ -149,7 +149,7 @@ var CipherTransform = (function() {
var CipherTransformFactory = (function() {
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,
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;

198
fonts.js
View File

@ -26,12 +26,15 @@ var fontName = "";
*/
var kDisableFonts = false;
/**
* Hold a map of decoded fonts and of the standard fourteen Type1 fonts and
* their acronyms.
* TODO Add the standard fourteen Type1 fonts list by default
* http://cgit.freedesktop.org/poppler/poppler/tree/poppler/GfxFont.cc#n65
*/
var kScalePrecision = 40;
var Fonts = {
_active: null,
@ -39,8 +42,9 @@ var Fonts = {
return this._active;
},
set active(name) {
setActive: function fonts_setActive(name, size) {
this._active = this[name];
this.ctx.font = (size * kScalePrecision) + 'px "' + name + '"';
},
charsToUnicode: function fonts_chars2Unicode(chars) {
@ -64,8 +68,8 @@ var Fonts = {
var unicode = encoding[charcode];
// Check if the glyph has already been converted
if (unicode instanceof Name)
unicode = encoding[unicode] = GlyphsUnicode[unicode.name];
if (!IsNum(unicode))
unicode = encoding[unicode] = GlyphsUnicode[unicode.name];
// Handle surrogate pairs
if (unicode > 0xFFFF) {
@ -77,6 +81,16 @@ var Fonts = {
// Enter the translated string into the cache
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");
break;
}
this.data = data;
Fonts[name] = {
data: data,
@ -720,7 +735,7 @@ var Font = (function () {
"\x00\x00" + // -reserved-
"\x00\x00" + // -reserved-
"\x00\x00" + // metricDataFormat
string16(charstrings.length)
string16(charstrings.length + 1) // Number of HMetrics
);
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
* 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;
for (var i = 0; i < charstrings.length; i++) {
width = charstrings[i].charstring[1];
hmtx += string16(width) + string16(lsb);
var charstring = charstrings[i];
hmtx += string16(charstring.width) + string16(0);
}
hmtx = stringToArray(hmtx);
createTableEntry(otf, offsets, "hmtx", hmtx);
/** MAXP */
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);
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;
// Just adding the font-face to the DOM doesn't make it load. It
// seems it's loaded once Gecko notices it's used. Therefore,
// add a div on the page using the loaded font.
var div = document.createElement("div");
var style = 'font-family:"' + name +
'";position: absolute;top:-99999;left:-99999;z-index:-99999';
div.setAttribute("style", style);
document.body.appendChild(div);
/** Hack begin */
// Actually there is not event when a font has finished downloading so
// the following code are a dirty hack to 'guess' when a font is ready
@ -800,15 +824,19 @@ var Font = (function () {
// For some reasons the font has not loaded, so mark it loaded for the
// page to proceed but cry
if ((Date.now() - this.start) >= kMaxWaitForFontFace) {
window.clearInterval(interval);
Fonts[fontName].loading = false;
warn("Is " + fontName + " loaded?");
this.start = 0;
} else if (textWidth != ctx.measureText(testString).width) {
window.clearInterval(interval);
Fonts[fontName].loading = false;
this.start = 0;
if (textWidth == ctx.measureText(testString).width) {
if ((Date.now() - this.start) < kMaxWaitForFontFace) {
return;
} else {
warn("Is " + fontName + " loaded?");
}
}
window.clearInterval(interval);
Fonts[fontName].loading = false;
this.start = 0;
if (callback) {
callback();
}
}, 30, this);
@ -839,7 +867,7 @@ var FontsUtils = {
bytes.set([value]);
return bytes[0];
} else if (bytesCount == 2) {
bytes.set([value >> 8, value]);
bytes.set([value >> 8, value & 0xff]);
return [bytes[0], bytes[1]];
} else if (bytesCount == 4) {
bytes.set([value >> 24, value >> 16, value >> 8, value]);
@ -980,16 +1008,8 @@ var Type1Parser = function() {
"12": "div",
// callothersubr is a mechanism to make calls on the postscript
// interpreter.
// TODO When decodeCharstring encounter such a command it should
// 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[]
// interpreter, this is not supported by Type2 charstring but hopefully
// most of the default commands can be ignored safely.
"16": "callothersubr",
"17": "pop",
@ -1009,8 +1029,13 @@ var Type1Parser = function() {
"31": "hvcurveto"
};
var kEscapeCommand = 12;
function decodeCharString(array) {
var charString = [];
var charstring = [];
var lsb = 0;
var width = 0;
var used = false;
var value = "";
var count = array.length;
@ -1019,10 +1044,48 @@ var Type1Parser = function() {
if (value < 32) {
var command = null;
if (value == 12) {
if (value == kEscapeCommand) {
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];
} 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];
}
@ -1044,16 +1107,14 @@ var Type1Parser = function() {
} else if (value <= 254) {
value = -((value - 251) * 256) - parseInt(array[++i]) - 108;
} else {
var byte = array[++i];
var high = (byte >> 1);
value = (byte - high) << 24 | array[++i] << 16 |
array[++i] << 8 | array[++i];
value = (array[++i] & 0xff) << 24 | (array[++i] & 0xff) << 16 |
(array[++i] & 0xff) << 8 | (array[++i] & 0xff) << 0;
}
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);
var data = eexecString.slice(i + 3, i + 3 + length);
var encodedSubr = decrypt(data, kCharStringsEncryptionKey, 4);
var subr = decodeCharString(encodedSubr);
var str = decodeCharString(encodedSubr);
subrs.push(subr);
subrs.push(str.charstring);
i += 3 + length;
} else if (inGlyphs && c == 0x52) {
length = parseInt(length);
var data = eexecString.slice(i + 3, i + 3 + length);
var encodedCharstring = decrypt(data, kCharStringsEncryptionKey, 4);
var subr = decodeCharString(encodedCharstring);
var str = decodeCharString(encodedCharstring);
glyphs.push({
glyph: glyph,
data: subr
data: str.charstring,
lsb: str.lsb,
width: str.width
});
i += 3 + length;
} else if (inGlyphs && c == 0x2F) {
@ -1254,16 +1317,18 @@ CFF.prototype = {
var charstrings = [];
for (var i = 0; i < glyphs.length; i++) {
var glyph = glyphs[i].glyph;
var unicode = GlyphsUnicode[glyph];
var glyph = glyphs[i];
var unicode = GlyphsUnicode[glyph.glyph];
if (!unicode) {
if (glyph != ".notdef")
if (glyph.glyph != ".notdef")
warn(glyph + " does not have an entry in the glyphs unicode dictionary");
} else {
charstrings.push({
glyph: glyph,
unicode: unicode,
charstring: glyphs[i].data
charstring: glyph.data,
width: glyph.width,
lsb: glyph.lsb
});
}
};
@ -1305,46 +1370,11 @@ CFF.prototype = {
var i = 0;
while (true) {
var obj = charstring[i];
if (obj == null)
return [];
if (obj == undefined) {
error("unknow charstring command for " + i + " in " + charstring);
}
if (obj.charAt) {
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 "return":
// CharString is ready to be re-encode to commands number at this point
@ -1356,7 +1386,7 @@ CFF.prototype = {
} else if (command.charAt) {
var cmd = this.commandsMap[command];
if (!cmd)
error(command);
error("Unknow command: " + command);
if (IsArray(cmd)) {
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;
// add an extra pixByte to represent the pixel left of column 0
var rowBytes = this.rowBytes = (columns * colors * bits + 7) >> 3;
DecodeStream.call(this);
return this;
}
@ -816,7 +816,7 @@ var DecryptStream = (function() {
DecodeStream.call(this);
}
const chunkSize = 512;
var chunkSize = 512;
constructor.prototype = Object.create(DecodeStream.prototype);
constructor.prototype.readBlock = function() {
@ -910,18 +910,18 @@ var Ascii85Stream = (function() {
var CCITTFaxStream = (function() {
const ccittEOL = -2;
const twoDimPass = 0;
const twoDimHoriz = 1;
const twoDimVert0 = 2;
const twoDimVertR1 = 3;
const twoDimVertL1 = 4;
const twoDimVertR2 = 5;
const twoDimVertL2 = 6;
const twoDimVertR3 = 7;
const twoDimVertL3 = 8;
var ccittEOL = -2;
var twoDimPass = 0;
var twoDimHoriz = 1;
var twoDimVert0 = 2;
var twoDimVertR1 = 3;
var twoDimVertL1 = 4;
var twoDimVertR2 = 5;
var twoDimVertL2 = 6;
var twoDimVertR3 = 7;
var twoDimVertL3 = 8;
const twoDimTable = [
var twoDimTable = [
[-1, -1], [-1, -1], // 000000x
[7, twoDimVertL3], // 0000010
[7, twoDimVertR3], // 0000011
@ -989,7 +989,7 @@ var CCITTFaxStream = (function() {
[1, twoDimVert0], [1, twoDimVert0]
];
const whiteTable1 = [
var whiteTable1 = [
[-1, -1], // 00000
[12, ccittEOL], // 00001
[-1, -1], [-1, -1], // 0001x
@ -1011,7 +1011,7 @@ var CCITTFaxStream = (function() {
[12, 2560] // 11111
];
const whiteTable2 = [
var whiteTable2 = [
[-1, -1], [-1, -1], [-1, -1], [-1, -1], // 0000000xx
[8, 29], [8, 29], // 00000010x
[8, 30], [8, 30], // 00000011x
@ -1175,7 +1175,7 @@ var CCITTFaxStream = (function() {
[4, 7], [4, 7], [4, 7], [4, 7]
];
const blackTable1 = [
var blackTable1 = [
[-1, -1], [-1, -1], // 000000000000x
[12, ccittEOL], [12, ccittEOL], // 000000000001x
[-1, -1], [-1, -1], [-1, -1], [-1, -1], // 00000000001xx
@ -1236,7 +1236,7 @@ var CCITTFaxStream = (function() {
[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],
[8, 13], [8, 13], [8, 13], [8, 13],
@ -1315,7 +1315,7 @@ var CCITTFaxStream = (function() {
[7, 12], [7, 12], [7, 12], [7, 12]
];
const blackTable3 = [
var blackTable3 = [
[-1, -1], [-1, -1], [-1, -1], [-1, -1], // 0000xx
[6, 9], // 000100
[6, 8], // 000101
@ -3809,18 +3809,24 @@ var CanvasGraphics = (function() {
if (fontDescriptor && fontDescriptor.num) {
var fontDescriptor = this.xref.fetchIfRef(fontDescriptor);
fontName = fontDescriptor.get("FontName").name.replace("+", "_");
Fonts.active = fontName;
Fonts.setActive(fontName, size);
}
if (!fontName) {
// TODO: fontDescriptor is not available, fallback to default font
this.current.fontSize = size;
this.ctx.font = this.current.fontSize + 'px sans-serif';
Fonts.setActive("sans-serif", this.current.fontSize);
return;
}
this.current.fontName = fontName;
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) {
TODO("text rendering mode");
@ -3864,7 +3870,7 @@ var CanvasGraphics = (function() {
text = Fonts.charsToUnicode(text);
this.ctx.translate(this.current.x, -1 * this.current.y);
this.ctx.fillText(text, 0, 0);
this.current.x += this.ctx.measureText(text).width;
this.current.x += Fonts.measureText(text);
}
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",
"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
import SocketServer
from optparse import OptionParser
from urlparse import urlparse
from urlparse import urlparse, parse_qs
USAGE_EXAMPLE = "%prog"
@ -132,6 +132,11 @@ class PDFTestHandler(BaseHTTPRequestHandler):
self.send_header('Content-Type', 'text/plain')
self.end_headers()
url = urlparse(self.path)
if url.path == "/tellMeToQuit":
tellAppToQuit(url.path, url.query)
return
result = json.loads(self.rfile.read(numBytes))
browser, id, failure, round, page, snapshot = result['browser'], result['id'], result['failure'], result['round'], result['page'], result['snapshot']
taskResults = State.taskResults[browser][id]
@ -156,8 +161,20 @@ class PDFTestHandler(BaseHTTPRequestHandler):
State.done = (0 == State.remaining)
# this just does Firefox for now
class BrowserCommand():
# Applescript hack to quit Chrome on Mac
def tellAppToQuit(path, query):
if platform.system() != "Darwin":
return
d = parse_qs(query)
path = d['path'][0]
cmd = """osascript<<END
tell application "%s"
quit
end tell
END""" % path
os.system(cmd)
class BaseBrowserCommand(object):
def __init__(self, browserRecord):
self.name = browserRecord["name"]
self.path = browserRecord["path"]
@ -170,14 +187,9 @@ class BrowserCommand():
if not os.path.exists(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):
self.tempDir = tempfile.mkdtemp()
self.profileDir = os.path.join(self.tempDir, "profile")
shutil.copytree(os.path.join(DOC_ROOT, "test", "resources", "firefox"),
self.profileDir)
def teardown(self):
# 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):
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):
cmds = [self.path]
if platform.system() == "Darwin":
@ -201,9 +225,29 @@ class BrowserCommand():
cmds.extend(["-no-remote", "-profile", self.profileDir, url])
self.process = subprocess.Popen(cmds)
class ChromeBrowserCommand(BaseBrowserCommand):
def _fixupMacPath(self):
self.path = os.path.join(self.path, "Contents", "MacOS", "Google Chrome")
def start(self, url):
cmds = [self.path]
cmds.extend(["--user-data-dir=%s" % self.profileDir,
"--no-first-run", "--disable-sync", url])
self.process = subprocess.Popen(cmds)
def makeBrowserCommand(browser):
path = browser["path"].lower()
name = browser["name"].lower()
if name.find("firefox") > -1 or path.find("firefox") > -1:
return FirefoxBrowserCommand(browser)
elif name.find("chrom") > -1 or path.find("chrom") > -1:
return ChromeBrowserCommand(browser)
else:
raise Exception("Unrecognized browser: %s" % browser)
def makeBrowserCommands(browserManifestFile):
with open(browserManifestFile) as bmf:
browsers = [BrowserCommand(browser) for browser in json.load(bmf)]
browsers = [makeBrowserCommand(browser) for browser in json.load(bmf)]
return browsers
def downloadLinkedPDFs(manifestList):
@ -267,6 +311,7 @@ def startBrowsers(browsers, options):
b.setup()
print 'Launching', b.name
qs = 'browser='+ urllib.quote(b.name) +'&manifestFile='+ urllib.quote(options.manifestFile)
qs += '&path=' + b.path
b.start('http://localhost:8080/test/test_slave.html?'+ qs)
def teardownBrowsers(browsers):

View File

@ -6,7 +6,7 @@
<script type="text/javascript" src="/fonts.js"></script>
<script type="text/javascript" src="/glyphlist.js"></script>
<script type="application/javascript">
var browser, canvas, currentTask, currentTaskIdx, failure, manifest, numPages, pdfDoc, stdout;
var appPath, browser, canvas, currentTask, currentTaskIdx, failure, manifest, numPages, pdfDoc, stdout;
function queryParams() {
var qs = window.location.search.substring(1);
@ -23,12 +23,13 @@ function load() {
var params = queryParams();
browser = params.browser;
manifestFile = params.manifestFile;
appPath = params.path;
canvas = document.createElement("canvas");
canvas.mozOpaque = true;
stdout = document.getElementById("stdout");
log("Harness thinks this browser is '"+ browser +"'\n");
log("Harness thinks this browser is '"+ browser + "' with path " + appPath + "\n");
log("Fetching manifest "+ manifestFile +"...");
var r = new XMLHttpRequest();
@ -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() {
log("Done!");
document.body.innerHTML = "Tests are finished. <h1>CLOSE ME!</h1>";
if (window.SpecialPowers)
if (window.SpecialPowers) {
SpecialPowers.quitApplication();
else
} else {
sendQuitRequest();
window.close();
}
}
function done() {

View File

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

View File

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

View File

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

27
worker/console.js Normal file
View File

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

65
worker/font.js Normal file
View File

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

View File

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