diff --git a/README b/README deleted file mode 100644 index ee537f0a5..000000000 --- a/README +++ /dev/null @@ -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 diff --git a/README.md b/README.md new file mode 100644 index 000000000..b6ff6c19f --- /dev/null +++ b/README.md @@ -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 diff --git a/crypto.js b/crypto.js index 14cc21902..e888d0212 100644 --- a/crypto.js +++ b/crypto.js @@ -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; diff --git a/fonts.js b/fonts.js index 728bc5c68..e25b2ae2d 100644 --- a/fonts.js +++ b/fonts.js @@ -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]); diff --git a/pdf.js b/pdf.js index c979b7684..a6681a687 100644 --- a/pdf.js +++ b/pdf.js @@ -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(); diff --git a/test/resources/browser_manifests/browser_manifest.json.linux b/test/resources/browser_manifests/browser_manifest.json.linux new file mode 100644 index 000000000..a576899b5 --- /dev/null +++ b/test/resources/browser_manifests/browser_manifest.json.linux @@ -0,0 +1,10 @@ +[ + { + "name":"firefox7", + "path":"/home/sayrer/firefoxen/nightly/firefox" + }, + { + "name":"chrome14", + "path":"/opt/google/chrome/chrome" + } +] diff --git a/test/resources/browser_manifests/browser_manifest.json.mac b/test/resources/browser_manifests/browser_manifest.json.mac index 7c9dda943..5b93ff196 100644 --- a/test/resources/browser_manifests/browser_manifest.json.mac +++ b/test/resources/browser_manifests/browser_manifest.json.mac @@ -6,5 +6,9 @@ { "name":"firefox6", "path":"/Users/sayrer/firefoxen/Aurora.app" + }, + { + "name":"chrome14", + "path":"/Applications/Google Chrome.app" } ] diff --git a/test/test.py b/test/test.py index 662f2d8e4..5f756877a 100644 --- a/test/test.py +++ b/test/test.py @@ -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< -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): diff --git a/test/test_slave.html b/test/test_slave.html index c64c6a390..d70e362af 100644 --- a/test/test_slave.html +++ b/test/test_slave.html @@ -6,7 +6,7 @@ + + + +