diff --git a/.gitignore b/.gitignore index 95de9fb8e..9e2d0f211 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +*~ pdf.pdf intelisa.pdf openweb_tm-PRINT.pdf +local.mk diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..a6f3ba3a4 --- /dev/null +++ b/Makefile @@ -0,0 +1,163 @@ +REPO = git@github.com:andreasgal/pdf.js.git +BUILD_DIR := build +DEFAULT_BROWSERS := resources/browser_manifests/browser_manifest.json +DEFAULT_TESTS := test_manifest.json + +# Let folks define custom rules for their clones. +-include local.mk + +# JS files needed for pdf.js. +# This list doesn't account for the 'worker' directory. +PDF_JS_FILES = \ + pdf.js \ + crypto.js \ + fonts.js \ + glyphlist.js \ + $(NULL) + +# not sure what to do for all yet +all: help + +# make server +# +# This target starts a local web server at localhost:8888. This can be +# used for testing all browsers. +server: + @cd test; python test.py --port=8888; + +test: shell-test browser-test + +# make browser-test +# +# This target runs in-browser tests using two primary arguments: a +# test manifest file, and a browser manifest file. Both are simple +# JSON formats, and examples can be found in the test/ directory. The +# target will inspect the environment for the PDF_TESTS and +# PDF_BROWSERS variables, and use those if found. Otherwise, the +# defaults at the top of this file are used. +ifeq ($(PDF_TESTS),) +PDF_TESTS := $(DEFAULT_TESTS) +endif +ifeq ($(PDF_BROWSERS),) +PDF_BROWSERS := $(DEFAULT_BROWSERS) +endif + +browser-test: + @if [ ! -f "test/$(PDF_BROWSERS)" ]; then \ + echo "Browser manifest file $(PDF_BROWSERS) does not exist."; \ + echo "Try copying one of the examples" \ + "in test/resources/browser_manifests/"; \ + exit 1; \ + fi; + + cd test; \ + python test.py --reftest \ + --browserManifestFile=$(PDF_BROWSERS) \ + --manifestFile=$(PDF_TESTS) + +# make shell-test +# +# This target runs all of the tests that can be run in a JS shell. +# The shell used is taken from the JS_SHELL environment variable. If +# that variable is not defined, the script will attempt to use the copy +# of Rhino that comes with the Closure compiler used for producing the +# website. +SHELL_TARGET = $(NULL) +ifeq ($(JS_SHELL),) +JS_SHELL := "java -cp $(BUILD_DIR)/compiler.jar" +JS_SHELL += "com.google.javascript.jscomp.mozilla.rhino.tools.shell.Main" +SHELL_TARGET = compiler +endif + +shell-test: shell-msg $(SHELL_TARGET) font-test +shell-msg: +ifeq ($(SHELL_TARGET), compiler) + @echo "No JS_SHELL env variable present." + @echo "The default is to find a copy of Rhino and try that." +endif + @echo "JS shell command is: $(JS_SHELL)" + +font-test: + @echo "font test stub." + +# make lint +# +# This target runs the Closure Linter on most of our JS files. +# To install gjslint, see: +# +# +SRC_DIRS := . utils worker web +GJSLINT_FILES = $(foreach DIR,$(SRC_DIRS),$(wildcard $(DIR)/*.js)) +lint: + gjslint $(GJSLINT_FILES) + +# make web +# +# This target produces the website for the project, by checking out +# the gh-pages branch underneath the build directory, and then move +# the various viewer files into place. +# +# TODO: Use the Closure compiler to optimize the pdf.js files. +# +GH_PAGES = $(BUILD_DIR)/gh-pages +web: | compiler pages-repo \ + $(addprefix $(GH_PAGES)/, $(PDF_JS_FILES)) \ + $(addprefix $(GH_PAGES)/, $(wildcard web/*.*)) \ + $(addprefix $(GH_PAGES)/, $(wildcard web/images/*.*)) + + @cp $(GH_PAGES)/web/index.html.template $(GH_PAGES)/index.html; + @cd $(GH_PAGES); git add -A; + @echo "Website built in $(GH_PAGES)." + +# make pages-repo +# +# This target clones the gh-pages repo into the build directory. It +# deletes the current contents of the repo, since we overwrite +# everything with data from the master repo. The 'make web' target +# then uses 'git add -A' to track additions, modifications, moves, +# and deletions. +pages-repo: | $(BUILD_DIR) + @if [ ! -d "$(GH_PAGES)" ]; then \ + git clone -b gh-pages $(REPO) $(GH_PAGES); \ + rm -rf $(GH_PAGES)/*; \ + fi; + @mkdir -p $(GH_PAGES)/web; + @mkdir -p $(GH_PAGES)/web/images; + +$(GH_PAGES)/%.js: %.js + @cp $< $@ + +$(GH_PAGES)/web/%: web/% + @cp $< $@ + +$(GH_PAGES)/web/images/%: web/images/% + @cp $< $@ + +# make compiler +# +# This target downloads the Closure compiler, and places it in the +# build directory. This target is also useful when the user doesn't +# have a JS shell available--we can have them use the Rhino shell that +# comes with Closure. +COMPILER_URL = http://closure-compiler.googlecode.com/files/compiler-latest.zip + +compiler: $(BUILD_DIR)/compiler.zip +$(BUILD_DIR)/compiler.zip: | $(BUILD_DIR) + curl $(COMPILER_URL) > $(BUILD_DIR)/compiler.zip; + cd $(BUILD_DIR); unzip compiler.zip compiler.jar; + +# Make sure there's a build directory. +$(BUILD_DIR): + mkdir -p $(BUILD_DIR) + +clean: + rm -rf $(BUILD_DIR) + +# make help +# +# This target just prints out a message to read these comments. :) +help: + @echo "Read the comments in the Makefile for guidance."; + +.PHONY:: all test browser-test font-test shell-test \ + shell-msg lint clean web compiler help server diff --git a/crypto.js b/crypto.js index e888d0212..c60590777 100644 --- a/crypto.js +++ b/crypto.js @@ -1,7 +1,7 @@ /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- / /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ -"use strict"; +'use strict'; var ARCFourCipher = (function() { function constructor(key) { @@ -30,7 +30,7 @@ var ARCFourCipher = (function() { a = (a + 1) & 0xFF; tmp = s[a]; b = (b + tmp) & 0xFF; - tmp2 = s[b] + tmp2 = s[b]; s[a] = tmp2; s[b] = tmp; output[i] = data[i] ^ s[(tmp + tmp2) & 0xFF]; @@ -47,22 +47,23 @@ var ARCFourCipher = (function() { var md5 = (function() { 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, + 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]); + var k = new Int32Array([ -680876936, -389564586, 606105819, -1044525330, -176418897, 1200080426, -1473231341, -45705983, 1770035416, -1958414417, -42063, -1990404162, 1804603682, -40341101, -1502002290, 1236535329, -165796510, -1069501632, - 643717713, -373897302, -701558691, 38016083, -660478335, -405537848, 568446438, - -1019803690, -187363961, 1163531501, -1444681467, -51403784, 1735328473, - -1926607734, -378558, -2022574463, 1839030562, -35309556, -1530992060, - 1272893353, -155497632, -1094730640, 681279174, -358537222, -722521979, - 76029189, -640364487, -421815835, 530742520, -995338651, -198630844, 1126891415, - -1416354905, -57434055, 1700485571, -1894986606, -1051523, -2054922799, - 1873313359, -30611744, -1560198380, 1309151649, -145523070, -1120210379, - 718787259, -343485551]); - + 643717713, -373897302, -701558691, 38016083, -660478335, -405537848, + 568446438, -1019803690, -187363961, 1163531501, -1444681467, -51403784, + 1735328473, -1926607734, -378558, -2022574463, 1839030562, -35309556, + -1530992060, 1272893353, -155497632, -1094730640, 681279174, -358537222, + -722521979, 76029189, -640364487, -421815835, 530742520, -995338651, + -198630844, 1126891415, -1416354905, -57434055, 1700485571, -1894986606, + -1051523, -2054922799, 1873313359, -30611744, -1560198380, 1309151649, + -145523070, -1120210379, 718787259, -343485551]); + function hash(data, offset, length) { var h0 = 1732584193, h1 = -271733879, h2 = -1732584194, h3 = 271733878; // pre-processing @@ -76,10 +77,10 @@ var md5 = (function() { for (; i < n; ++i) padded[i] = 0; padded[i++] = (length << 3) & 0xFF; - padded[i++] = (length >> 5) & 0xFF; - padded[i++] = (length >> 13) & 0xFF; - padded[i++] = (length >> 21) & 0xFF; - padded[i++] = (length >>> 29) & 0xFF; + padded[i++] = (length >> 5) & 0xFF; + padded[i++] = (length >> 13) & 0xFF; + padded[i++] = (length >> 21) & 0xFF; + padded[i++] = (length >>> 29) & 0xFF; padded[i++] = 0; padded[i++] = 0; padded[i++] = 0; @@ -87,8 +88,10 @@ var md5 = (function() { // TODO ArrayBuffer ? var w = new Int32Array(16); for (i = 0; i < paddedLength;) { - for (j = 0; j < 16; ++j, i += 4) - w[j] = padded[i] | (padded[i + 1] << 8) | (padded[i + 2] << 16) | (padded[i + 3] << 24); + for (j = 0; j < 16; ++j, i += 4) { + w[j] = (padded[i] | (padded[i + 1] << 8) | + (padded[i + 2] << 16) | (padded[i + 3] << 24)); + } var a = h0, b = h1, c = h2, d = h3, f, g; for (j = 0; j < 64; ++j) { if (j < 16) { @@ -131,7 +134,7 @@ var CipherTransform = (function() { this.streamCipherConstructor = streamCipherConstructor; } constructor.prototype = { - createStream: function (stream) { + createStream: function(stream) { var cipher = new this.streamCipherConstructor(); return new DecryptStream(stream, function(data) { return cipher.encryptBlock(data); @@ -139,19 +142,22 @@ var CipherTransform = (function() { }, decryptString: function(s) { var cipher = new this.stringCipherConstructor(); - var data = string2bytes(s); + var data = stringToBytes(s); data = cipher.encryptBlock(data); - return bytes2string(data); + return bytesToString(data); } }; return constructor; })(); var CipherTransformFactory = (function() { - function prepareKeyData(fileId, password, ownerPassword, userPassword, flags, revision, keyLength) { + function prepareKeyData(fileId, password, ownerPassword, userPassword, + flags, revision, keyLength) { 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]); + 0x28, 0xBF, 0x4E, 0x5E, 0x4E, 0x75, 0x8A, 0x41, + 0x64, 0x00, 0x4E, 0x56, 0xFF, 0xFA, 0x01, 0x08, + 0x2E, 0x2E, 0x00, 0xB6, 0xD0, 0x68, 0x3E, 0x80, + 0x2F, 0x0C, 0xA9, 0xFE, 0x64, 0x53, 0x69, 0x7A]); var hashData = new Uint8Array(88), i = 0, j, n; if (password) { n = Math.min(32, password.length); @@ -183,9 +189,10 @@ var CipherTransformFactory = (function() { var cipher, checkData; if (revision >= 3) { - // padded password in hashData, we can use this array for user password check + // padded password in hashData, we can use this array for user + // password check i = 32; - for(j = 0, n = fileId.length; j < n; ++j) + for (j = 0, n = fileId.length; j < n; ++j) hashData[i++] = fileId[j]; cipher = new ARCFourCipher(encryptionKey); var checkData = cipher.encryptBlock(md5(hashData, 0, i)); @@ -203,37 +210,38 @@ var CipherTransformFactory = (function() { } for (j = 0, n = checkData.length; j < n; ++j) { if (userPassword[j] != checkData[j]) - error("incorrect password"); + error('incorrect password'); } return encryptionKey; - } + } function constructor(dict, fileId, password) { - var filter = dict.get("Filter"); - if (!IsName(filter) || filter.name != "Standard") - error("unknown encryption method"); + var filter = dict.get('Filter'); + if (!IsName(filter) || filter.name != 'Standard') + error('unknown encryption method'); this.dict = dict; - var algorithm = dict.get("V"); + var algorithm = dict.get('V'); if (!IsInt(algorithm) || (algorithm != 1 && algorithm != 2)) - error("unsupported encryption algorithm"); + error('unsupported encryption algorithm'); // TODO support algorithm 4 - var keyLength = dict.get("Length") || 40; + var keyLength = dict.get('Length') || 40; if (!IsInt(keyLength) || keyLength < 40 || (keyLength % 8) != 0) - error("invalid key length"); + error('invalid key length'); // prepare keys - var ownerPassword = stringToBytes(dict.get("O")); - var userPassword = stringToBytes(dict.get("U")); - var flags = dict.get("P"); - var revision = dict.get("R"); + var ownerPassword = stringToBytes(dict.get('O')); + var userPassword = stringToBytes(dict.get('U')); + var flags = dict.get('P'); + var revision = dict.get('R'); var fileIdBytes = stringToBytes(fileId); var passwordBytes; if (password) passwordBytes = stringToBytes(password); - this.encryptionKey = prepareKeyData(fileIdBytes, passwordBytes, - ownerPassword, userPassword, flags, revision, keyLength); + this.encryptionKey = prepareKeyData(fileIdBytes, passwordBytes, + ownerPassword, userPassword, + flags, revision, keyLength); } constructor.prototype = { diff --git a/fonts.js b/fonts.js old mode 100644 new mode 100755 index 5abc13fd3..1f0a6a211 --- a/fonts.js +++ b/fonts.js @@ -1,9 +1,8 @@ /* -*- 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 isWorker = (typeof window == "undefined"); +'use strict'; +var isWorker = (typeof window == 'undefined'); /** * Maximum file size of the font. @@ -11,24 +10,10 @@ var isWorker = (typeof window == "undefined"); var kMaxFontFileSize = 40000; /** - * Maximum time to wait for a font to be loaded by @font-face + * Maximum time to wait for a font to be loaded by font-face rules. */ var kMaxWaitForFontFace = 1000; -/** - * Useful for debugging when you want to certains operations depending on how - * many fonts are loaded. - */ -var fontCount = 0; -var fontName = ""; - -/** - * If for some reason one want to debug without fonts activated, it just need - * to turn this pref to true/false. - */ -var kDisableFonts = false; - - /** * Hold a map of decoded fonts and of the standard fourteen Type1 fonts and * their acronyms. @@ -38,78 +23,53 @@ var kDisableFonts = false; var Fonts = (function Fonts() { var kScalePrecision = 40; - var fonts = Object.create(null); + var fonts = []; if (!isWorker) { - var ctx = document.createElement("canvas").getContext("2d"); + var ctx = document.createElement('canvas').getContext('2d'); ctx.scale(1 / kScalePrecision, 1); } - function Font(name, data, properties) { + var fontCount = 0; + + function FontInfo(name, data, properties) { this.name = name; this.data = data; this.properties = properties; + this.id = fontCount++; this.loading = true; - this.charsCache = Object.create(null); this.sizes = []; } var current; - var charsCache; var measureCache; return { registerFont: function fonts_registerFont(fontName, data, properties) { - fonts[fontName] = new Font(fontName, data, properties); + var font = new FontInfo(fontName, data, properties); + fonts.push(font); + return font.id; }, blacklistFont: function fonts_blacklistFont(fontName) { - registerFont(fontName, null, {}); + var id = registerFont(fontName, null, {}); markLoaded(fontName); + return id; }, - lookup: function fonts_lookup(fontName) { - return (fontName in fonts) ? fonts[fontName] : null; + lookupById: function fonts_lookupById(id) { + return fonts[id]; }, - setActive: function fonts_setActive(fontName, size) { - current = fonts[fontName]; - charsCache = current.charsCache; - var sizes = current.sizes; - if (!(measureCache = sizes[size])) - measureCache = sizes[size] = Object.create(null); - ctx.font = (size * kScalePrecision) + 'px "' + fontName + '"'; - }, - charsToUnicode: function fonts_chars2Unicode(chars) { - if (!charsCache) - return chars; - - // if we translated this string before, just grab it from the cache - var str = charsCache[chars]; - if (str) - return str; - - // translate the string using the font's encoding - var encoding = current.properties.encoding; - if (!encoding) - return chars; - - str = ""; - for (var i = 0; i < chars.length; ++i) { - var charcode = chars.charCodeAt(i); - var unicode = encoding[charcode]; - - // Check if the glyph has already been converted - if (!IsNum(unicode)) - unicode = encoding[unicode] = GlyphsUnicode[unicode.name]; - - // Handle surrogate pairs - if (unicode > 0xFFFF) { - str += String.fromCharCode(unicode & 0xFFFF); - unicode >>= 16; - } - str += String.fromCharCode(unicode); + setActive: function fonts_setActive(fontName, fontObj, size) { + // |current| can be null is fontName is a built-in font + // (e.g. "sans-serif") + if (fontObj && (current = fonts[fontObj.id])) { + var sizes = current.sizes; + if (!(measureCache = sizes[size])) + measureCache = sizes[size] = Object.create(null); + } else { + measureCache = null } - // Enter the translated string into the cache - return charsCache[chars] = str; + ctx.font = (size * kScalePrecision) + 'px "' + fontName + '"'; }, measureText: function fonts_measureText(text) { var width; @@ -120,55 +80,60 @@ var Fonts = (function Fonts() { measureCache[text] = width; return width; } - } + }; })(); var FontLoader = { listeningForFontLoad: false, - bind: function font_bind(fonts, callback) { + bind: function(fonts, callback) { function checkFontsLoaded() { - for (var i = 0; i < fonts.length; i++) { - var font = fonts[i]; - if (Fonts.lookup(font.name).loading) { + for (var i = 0; i < allIds.length; i++) { + var id = allIds[i]; + if (Fonts.lookupById(id).loading) { return false; } } document.documentElement.removeEventListener( - "pdfjsFontLoad", checkFontsLoaded, false); + 'pdfjsFontLoad', checkFontsLoaded, false); callback(); return true; } - var rules = [ ], names = [ ]; + var allIds = []; + var rules = [], names = [], ids = []; + for (var i = 0; i < fonts.length; i++) { var font = fonts[i]; - if (!Fonts.lookup(font.name)) { - var obj = new Font(font.name, font.file, font.properties); - var str = ""; - var data = Fonts.lookup(font.name).data; - var length = data.length; - for (var j = 0; j < length; j++) - str += String.fromCharCode(data[j]); + var obj = new Font(font.name, font.file, font.properties); + font.fontDict.fontObj = obj; + allIds.push(obj.id); - var rule = isWorker ? obj.bindWorker(str) : obj.bindDOM(str); - if (rule) { - rules.push(rule); - names.push(font.name); - } + var str = ''; + var data = Fonts.lookupById(obj.id).data; + var length = data.length; + for (var j = 0; j < length; j++) + str += String.fromCharCode(data[j]); + + var rule = isWorker ? obj.bindWorker(str) : obj.bindDOM(str); + if (rule) { + rules.push(rule); + names.push(obj.loadedName); + ids.push(obj.id); } } + this.listeningForFontLoad = false; if (!isWorker && rules.length) { - FontLoader.prepareFontLoadEvent(rules, names); + FontLoader.prepareFontLoadEvent(rules, names, ids); } - + if (!checkFontsLoaded()) { document.documentElement.addEventListener( - "pdfjsFontLoad", checkFontsLoaded, false); + 'pdfjsFontLoad', checkFontsLoaded, false); } return; @@ -178,7 +143,7 @@ var FontLoader = { // loaded in a subdocument. It's expected that the load of |rules| // has already started in this (outer) document, so that they should // be ordered before the load in the subdocument. - prepareFontLoadEvent: function prepareFontLoadEvent(rules, names) { + prepareFontLoadEvent: function(rules, names, ids) { /** Hack begin */ // There's no event when a font has finished downloading so the // following code is a dirty hack to 'guess' when a font is @@ -202,38 +167,33 @@ var FontLoader = { // The postMessage() hackery was added to work around chrome bug // 82402. - var div = document.createElement("div"); - div.setAttribute("style", - 'visibility: hidden;'+ - 'width: 10px; height: 10px;'+ + var div = document.createElement('div'); + div.setAttribute('style', + 'visibility: hidden;' + + 'width: 10px; height: 10px;' + 'position: absolute; top: 0px; left: 0px;'); var html = ''; for (var i = 0; i < names.length; ++i) { - html += 'Hi'; + html += 'Hi'; } div.innerHTML = html; document.body.appendChild(div); - if (!this.listeneningForFontLoad) { + if (!this.listeningForFontLoad) { window.addEventListener( - "message", + 'message', function(e) { - var fontNames = e.data; - // Firefox 5 doesn't parse the JSON here. Welcome to the - // Wonderful Web World. - if ("string" == typeof(fontNames)) { - fontNames = fontNames.split(","); - } + var fontNames = JSON.parse(e.data); for (var i = 0; i < fontNames.length; ++i) { - var font = Fonts.lookup(fontNames[i]); + var font = Fonts.lookupById(fontNames[i].substring(7) | 0); font.loading = false; } - var evt = document.createEvent("Events"); - evt.initEvent("pdfjsFontLoad", true, false); + var evt = document.createEvent('Events'); + evt.initEvent('pdfjsFontLoad', true, false); document.documentElement.dispatchEvent(evt); }, false); - this.listeneningForFontLoad = true; + this.listeningForFontLoad = true; } // XXX we should have a time-out here too, and maybe fire @@ -243,26 +203,26 @@ var FontLoader = { for (var i = 0; i < rules.length; ++i) { src += rules[i]; } - src += '' - src += ''; for (var i = 0; i < names.length; ++i) { - src += '

Hi

'; + src += '

Hi

'; } src += ''; - var frame = document.createElement("iframe"); - frame.src = 'data:text/html,'+ src; - frame.setAttribute("style", - 'visibility: hidden;'+ - 'width: 10px; height: 10px;'+ + var frame = document.createElement('iframe'); + frame.src = 'data:text/html,' + src; + frame.setAttribute('style', + 'visibility: hidden;' + + 'width: 10px; height: 10px;' + 'position: absolute; top: 0px; left: 0px;'); document.body.appendChild(frame); /** Hack end */ @@ -270,129 +230,129 @@ var FontLoader = { }; var UnicodeRanges = [ - { "begin": 0x0000, "end": 0x007F }, // Basic Latin - { "begin": 0x0080, "end": 0x00FF }, // Latin-1 Supplement - { "begin": 0x0100, "end": 0x017F }, // Latin Extended-A - { "begin": 0x0180, "end": 0x024F }, // Latin Extended-B - { "begin": 0x0250, "end": 0x02AF }, // IPA Extensions - { "begin": 0x02B0, "end": 0x02FF }, // Spacing Modifier Letters - { "begin": 0x0300, "end": 0x036F }, // Combining Diacritical Marks - { "begin": 0x0370, "end": 0x03FF }, // Greek and Coptic - { "begin": 0x2C80, "end": 0x2CFF }, // Coptic - { "begin": 0x0400, "end": 0x04FF }, // Cyrillic - { "begin": 0x0530, "end": 0x058F }, // Armenian - { "begin": 0x0590, "end": 0x05FF }, // Hebrew - { "begin": 0xA500, "end": 0xA63F }, // Vai - { "begin": 0x0600, "end": 0x06FF }, // Arabic - { "begin": 0x07C0, "end": 0x07FF }, // NKo - { "begin": 0x0900, "end": 0x097F }, // Devanagari - { "begin": 0x0980, "end": 0x09FF }, // Bengali - { "begin": 0x0A00, "end": 0x0A7F }, // Gurmukhi - { "begin": 0x0A80, "end": 0x0AFF }, // Gujarati - { "begin": 0x0B00, "end": 0x0B7F }, // Oriya - { "begin": 0x0B80, "end": 0x0BFF }, // Tamil - { "begin": 0x0C00, "end": 0x0C7F }, // Telugu - { "begin": 0x0C80, "end": 0x0CFF }, // Kannada - { "begin": 0x0D00, "end": 0x0D7F }, // Malayalam - { "begin": 0x0E00, "end": 0x0E7F }, // Thai - { "begin": 0x0E80, "end": 0x0EFF }, // Lao - { "begin": 0x10A0, "end": 0x10FF }, // Georgian - { "begin": 0x1B00, "end": 0x1B7F }, // Balinese - { "begin": 0x1100, "end": 0x11FF }, // Hangul Jamo - { "begin": 0x1E00, "end": 0x1EFF }, // Latin Extended Additional - { "begin": 0x1F00, "end": 0x1FFF }, // Greek Extended - { "begin": 0x2000, "end": 0x206F }, // General Punctuation - { "begin": 0x2070, "end": 0x209F }, // Superscripts And Subscripts - { "begin": 0x20A0, "end": 0x20CF }, // Currency Symbol - { "begin": 0x20D0, "end": 0x20FF }, // Combining Diacritical Marks For Symbols - { "begin": 0x2100, "end": 0x214F }, // Letterlike Symbols - { "begin": 0x2150, "end": 0x218F }, // Number Forms - { "begin": 0x2190, "end": 0x21FF }, // Arrows - { "begin": 0x2200, "end": 0x22FF }, // Mathematical Operators - { "begin": 0x2300, "end": 0x23FF }, // Miscellaneous Technical - { "begin": 0x2400, "end": 0x243F }, // Control Pictures - { "begin": 0x2440, "end": 0x245F }, // Optical Character Recognition - { "begin": 0x2460, "end": 0x24FF }, // Enclosed Alphanumerics - { "begin": 0x2500, "end": 0x257F }, // Box Drawing - { "begin": 0x2580, "end": 0x259F }, // Block Elements - { "begin": 0x25A0, "end": 0x25FF }, // Geometric Shapes - { "begin": 0x2600, "end": 0x26FF }, // Miscellaneous Symbols - { "begin": 0x2700, "end": 0x27BF }, // Dingbats - { "begin": 0x3000, "end": 0x303F }, // CJK Symbols And Punctuation - { "begin": 0x3040, "end": 0x309F }, // Hiragana - { "begin": 0x30A0, "end": 0x30FF }, // Katakana - { "begin": 0x3100, "end": 0x312F }, // Bopomofo - { "begin": 0x3130, "end": 0x318F }, // Hangul Compatibility Jamo - { "begin": 0xA840, "end": 0xA87F }, // Phags-pa - { "begin": 0x3200, "end": 0x32FF }, // Enclosed CJK Letters And Months - { "begin": 0x3300, "end": 0x33FF }, // CJK Compatibility - { "begin": 0xAC00, "end": 0xD7AF }, // Hangul Syllables - { "begin": 0xD800, "end": 0xDFFF }, // Non-Plane 0 * - { "begin": 0x10900, "end": 0x1091F }, // Phoenicia - { "begin": 0x4E00, "end": 0x9FFF }, // CJK Unified Ideographs - { "begin": 0xE000, "end": 0xF8FF }, // Private Use Area (plane 0) - { "begin": 0x31C0, "end": 0x31EF }, // CJK Strokes - { "begin": 0xFB00, "end": 0xFB4F }, // Alphabetic Presentation Forms - { "begin": 0xFB50, "end": 0xFDFF }, // Arabic Presentation Forms-A - { "begin": 0xFE20, "end": 0xFE2F }, // Combining Half Marks - { "begin": 0xFE10, "end": 0xFE1F }, // Vertical Forms - { "begin": 0xFE50, "end": 0xFE6F }, // Small Form Variants - { "begin": 0xFE70, "end": 0xFEFF }, // Arabic Presentation Forms-B - { "begin": 0xFF00, "end": 0xFFEF }, // Halfwidth And Fullwidth Forms - { "begin": 0xFFF0, "end": 0xFFFF }, // Specials - { "begin": 0x0F00, "end": 0x0FFF }, // Tibetan - { "begin": 0x0700, "end": 0x074F }, // Syriac - { "begin": 0x0780, "end": 0x07BF }, // Thaana - { "begin": 0x0D80, "end": 0x0DFF }, // Sinhala - { "begin": 0x1000, "end": 0x109F }, // Myanmar - { "begin": 0x1200, "end": 0x137F }, // Ethiopic - { "begin": 0x13A0, "end": 0x13FF }, // Cherokee - { "begin": 0x1400, "end": 0x167F }, // Unified Canadian Aboriginal Syllabics - { "begin": 0x1680, "end": 0x169F }, // Ogham - { "begin": 0x16A0, "end": 0x16FF }, // Runic - { "begin": 0x1780, "end": 0x17FF }, // Khmer - { "begin": 0x1800, "end": 0x18AF }, // Mongolian - { "begin": 0x2800, "end": 0x28FF }, // Braille Patterns - { "begin": 0xA000, "end": 0xA48F }, // Yi Syllables - { "begin": 0x1700, "end": 0x171F }, // Tagalog - { "begin": 0x10300, "end": 0x1032F }, // Old Italic - { "begin": 0x10330, "end": 0x1034F }, // Gothic - { "begin": 0x10400, "end": 0x1044F }, // Deseret - { "begin": 0x1D000, "end": 0x1D0FF }, // Byzantine Musical Symbols - { "begin": 0x1D400, "end": 0x1D7FF }, // Mathematical Alphanumeric Symbols - { "begin": 0xFF000, "end": 0xFFFFD }, // Private Use (plane 15) - { "begin": 0xFE00, "end": 0xFE0F }, // Variation Selectors - { "begin": 0xE0000, "end": 0xE007F }, // Tags - { "begin": 0x1900, "end": 0x194F }, // Limbu - { "begin": 0x1950, "end": 0x197F }, // Tai Le - { "begin": 0x1980, "end": 0x19DF }, // New Tai Lue - { "begin": 0x1A00, "end": 0x1A1F }, // Buginese - { "begin": 0x2C00, "end": 0x2C5F }, // Glagolitic - { "begin": 0x2D30, "end": 0x2D7F }, // Tifinagh - { "begin": 0x4DC0, "end": 0x4DFF }, // Yijing Hexagram Symbols - { "begin": 0xA800, "end": 0xA82F }, // Syloti Nagri - { "begin": 0x10000, "end": 0x1007F }, // Linear B Syllabary - { "begin": 0x10140, "end": 0x1018F }, // Ancient Greek Numbers - { "begin": 0x10380, "end": 0x1039F }, // Ugaritic - { "begin": 0x103A0, "end": 0x103DF }, // Old Persian - { "begin": 0x10450, "end": 0x1047F }, // Shavian - { "begin": 0x10480, "end": 0x104AF }, // Osmanya - { "begin": 0x10800, "end": 0x1083F }, // Cypriot Syllabary - { "begin": 0x10A00, "end": 0x10A5F }, // Kharoshthi - { "begin": 0x1D300, "end": 0x1D35F }, // Tai Xuan Jing Symbols - { "begin": 0x12000, "end": 0x123FF }, // Cuneiform - { "begin": 0x1D360, "end": 0x1D37F }, // Counting Rod Numerals - { "begin": 0x1B80, "end": 0x1BBF }, // Sundanese - { "begin": 0x1C00, "end": 0x1C4F }, // Lepcha - { "begin": 0x1C50, "end": 0x1C7F }, // Ol Chiki - { "begin": 0xA880, "end": 0xA8DF }, // Saurashtra - { "begin": 0xA900, "end": 0xA92F }, // Kayah Li - { "begin": 0xA930, "end": 0xA95F }, // Rejang - { "begin": 0xAA00, "end": 0xAA5F }, // Cham - { "begin": 0x10190, "end": 0x101CF }, // Ancient Symbols - { "begin": 0x101D0, "end": 0x101FF }, // Phaistos Disc - { "begin": 0x102A0, "end": 0x102DF }, // Carian - { "begin": 0x1F030, "end": 0x1F09F } // Domino Tiles + { 'begin': 0x0000, 'end': 0x007F }, // Basic Latin + { 'begin': 0x0080, 'end': 0x00FF }, // Latin-1 Supplement + { 'begin': 0x0100, 'end': 0x017F }, // Latin Extended-A + { 'begin': 0x0180, 'end': 0x024F }, // Latin Extended-B + { 'begin': 0x0250, 'end': 0x02AF }, // IPA Extensions + { 'begin': 0x02B0, 'end': 0x02FF }, // Spacing Modifier Letters + { 'begin': 0x0300, 'end': 0x036F }, // Combining Diacritical Marks + { 'begin': 0x0370, 'end': 0x03FF }, // Greek and Coptic + { 'begin': 0x2C80, 'end': 0x2CFF }, // Coptic + { 'begin': 0x0400, 'end': 0x04FF }, // Cyrillic + { 'begin': 0x0530, 'end': 0x058F }, // Armenian + { 'begin': 0x0590, 'end': 0x05FF }, // Hebrew + { 'begin': 0xA500, 'end': 0xA63F }, // Vai + { 'begin': 0x0600, 'end': 0x06FF }, // Arabic + { 'begin': 0x07C0, 'end': 0x07FF }, // NKo + { 'begin': 0x0900, 'end': 0x097F }, // Devanagari + { 'begin': 0x0980, 'end': 0x09FF }, // Bengali + { 'begin': 0x0A00, 'end': 0x0A7F }, // Gurmukhi + { 'begin': 0x0A80, 'end': 0x0AFF }, // Gujarati + { 'begin': 0x0B00, 'end': 0x0B7F }, // Oriya + { 'begin': 0x0B80, 'end': 0x0BFF }, // Tamil + { 'begin': 0x0C00, 'end': 0x0C7F }, // Telugu + { 'begin': 0x0C80, 'end': 0x0CFF }, // Kannada + { 'begin': 0x0D00, 'end': 0x0D7F }, // Malayalam + { 'begin': 0x0E00, 'end': 0x0E7F }, // Thai + { 'begin': 0x0E80, 'end': 0x0EFF }, // Lao + { 'begin': 0x10A0, 'end': 0x10FF }, // Georgian + { 'begin': 0x1B00, 'end': 0x1B7F }, // Balinese + { 'begin': 0x1100, 'end': 0x11FF }, // Hangul Jamo + { 'begin': 0x1E00, 'end': 0x1EFF }, // Latin Extended Additional + { 'begin': 0x1F00, 'end': 0x1FFF }, // Greek Extended + { 'begin': 0x2000, 'end': 0x206F }, // General Punctuation + { 'begin': 0x2070, 'end': 0x209F }, // Superscripts And Subscripts + { 'begin': 0x20A0, 'end': 0x20CF }, // Currency Symbol + { 'begin': 0x20D0, 'end': 0x20FF }, // Combining Diacritical Marks For Symbols + { 'begin': 0x2100, 'end': 0x214F }, // Letterlike Symbols + { 'begin': 0x2150, 'end': 0x218F }, // Number Forms + { 'begin': 0x2190, 'end': 0x21FF }, // Arrows + { 'begin': 0x2200, 'end': 0x22FF }, // Mathematical Operators + { 'begin': 0x2300, 'end': 0x23FF }, // Miscellaneous Technical + { 'begin': 0x2400, 'end': 0x243F }, // Control Pictures + { 'begin': 0x2440, 'end': 0x245F }, // Optical Character Recognition + { 'begin': 0x2460, 'end': 0x24FF }, // Enclosed Alphanumerics + { 'begin': 0x2500, 'end': 0x257F }, // Box Drawing + { 'begin': 0x2580, 'end': 0x259F }, // Block Elements + { 'begin': 0x25A0, 'end': 0x25FF }, // Geometric Shapes + { 'begin': 0x2600, 'end': 0x26FF }, // Miscellaneous Symbols + { 'begin': 0x2700, 'end': 0x27BF }, // Dingbats + { 'begin': 0x3000, 'end': 0x303F }, // CJK Symbols And Punctuation + { 'begin': 0x3040, 'end': 0x309F }, // Hiragana + { 'begin': 0x30A0, 'end': 0x30FF }, // Katakana + { 'begin': 0x3100, 'end': 0x312F }, // Bopomofo + { 'begin': 0x3130, 'end': 0x318F }, // Hangul Compatibility Jamo + { 'begin': 0xA840, 'end': 0xA87F }, // Phags-pa + { 'begin': 0x3200, 'end': 0x32FF }, // Enclosed CJK Letters And Months + { 'begin': 0x3300, 'end': 0x33FF }, // CJK Compatibility + { 'begin': 0xAC00, 'end': 0xD7AF }, // Hangul Syllables + { 'begin': 0xD800, 'end': 0xDFFF }, // Non-Plane 0 * + { 'begin': 0x10900, 'end': 0x1091F }, // Phoenicia + { 'begin': 0x4E00, 'end': 0x9FFF }, // CJK Unified Ideographs + { 'begin': 0xE000, 'end': 0xF8FF }, // Private Use Area (plane 0) + { 'begin': 0x31C0, 'end': 0x31EF }, // CJK Strokes + { 'begin': 0xFB00, 'end': 0xFB4F }, // Alphabetic Presentation Forms + { 'begin': 0xFB50, 'end': 0xFDFF }, // Arabic Presentation Forms-A + { 'begin': 0xFE20, 'end': 0xFE2F }, // Combining Half Marks + { 'begin': 0xFE10, 'end': 0xFE1F }, // Vertical Forms + { 'begin': 0xFE50, 'end': 0xFE6F }, // Small Form Variants + { 'begin': 0xFE70, 'end': 0xFEFF }, // Arabic Presentation Forms-B + { 'begin': 0xFF00, 'end': 0xFFEF }, // Halfwidth And Fullwidth Forms + { 'begin': 0xFFF0, 'end': 0xFFFF }, // Specials + { 'begin': 0x0F00, 'end': 0x0FFF }, // Tibetan + { 'begin': 0x0700, 'end': 0x074F }, // Syriac + { 'begin': 0x0780, 'end': 0x07BF }, // Thaana + { 'begin': 0x0D80, 'end': 0x0DFF }, // Sinhala + { 'begin': 0x1000, 'end': 0x109F }, // Myanmar + { 'begin': 0x1200, 'end': 0x137F }, // Ethiopic + { 'begin': 0x13A0, 'end': 0x13FF }, // Cherokee + { 'begin': 0x1400, 'end': 0x167F }, // Unified Canadian Aboriginal Syllabics + { 'begin': 0x1680, 'end': 0x169F }, // Ogham + { 'begin': 0x16A0, 'end': 0x16FF }, // Runic + { 'begin': 0x1780, 'end': 0x17FF }, // Khmer + { 'begin': 0x1800, 'end': 0x18AF }, // Mongolian + { 'begin': 0x2800, 'end': 0x28FF }, // Braille Patterns + { 'begin': 0xA000, 'end': 0xA48F }, // Yi Syllables + { 'begin': 0x1700, 'end': 0x171F }, // Tagalog + { 'begin': 0x10300, 'end': 0x1032F }, // Old Italic + { 'begin': 0x10330, 'end': 0x1034F }, // Gothic + { 'begin': 0x10400, 'end': 0x1044F }, // Deseret + { 'begin': 0x1D000, 'end': 0x1D0FF }, // Byzantine Musical Symbols + { 'begin': 0x1D400, 'end': 0x1D7FF }, // Mathematical Alphanumeric Symbols + { 'begin': 0xFF000, 'end': 0xFFFFD }, // Private Use (plane 15) + { 'begin': 0xFE00, 'end': 0xFE0F }, // Variation Selectors + { 'begin': 0xE0000, 'end': 0xE007F }, // Tags + { 'begin': 0x1900, 'end': 0x194F }, // Limbu + { 'begin': 0x1950, 'end': 0x197F }, // Tai Le + { 'begin': 0x1980, 'end': 0x19DF }, // New Tai Lue + { 'begin': 0x1A00, 'end': 0x1A1F }, // Buginese + { 'begin': 0x2C00, 'end': 0x2C5F }, // Glagolitic + { 'begin': 0x2D30, 'end': 0x2D7F }, // Tifinagh + { 'begin': 0x4DC0, 'end': 0x4DFF }, // Yijing Hexagram Symbols + { 'begin': 0xA800, 'end': 0xA82F }, // Syloti Nagri + { 'begin': 0x10000, 'end': 0x1007F }, // Linear B Syllabary + { 'begin': 0x10140, 'end': 0x1018F }, // Ancient Greek Numbers + { 'begin': 0x10380, 'end': 0x1039F }, // Ugaritic + { 'begin': 0x103A0, 'end': 0x103DF }, // Old Persian + { 'begin': 0x10450, 'end': 0x1047F }, // Shavian + { 'begin': 0x10480, 'end': 0x104AF }, // Osmanya + { 'begin': 0x10800, 'end': 0x1083F }, // Cypriot Syllabary + { 'begin': 0x10A00, 'end': 0x10A5F }, // Kharoshthi + { 'begin': 0x1D300, 'end': 0x1D35F }, // Tai Xuan Jing Symbols + { 'begin': 0x12000, 'end': 0x123FF }, // Cuneiform + { 'begin': 0x1D360, 'end': 0x1D37F }, // Counting Rod Numerals + { 'begin': 0x1B80, 'end': 0x1BBF }, // Sundanese + { 'begin': 0x1C00, 'end': 0x1C4F }, // Lepcha + { 'begin': 0x1C50, 'end': 0x1C7F }, // Ol Chiki + { 'begin': 0xA880, 'end': 0xA8DF }, // Saurashtra + { 'begin': 0xA900, 'end': 0xA92F }, // Kayah Li + { 'begin': 0xA930, 'end': 0xA95F }, // Rejang + { 'begin': 0xAA00, 'end': 0xAA5F }, // Cham + { 'begin': 0x10190, 'end': 0x101CF }, // Ancient Symbols + { 'begin': 0x101D0, 'end': 0x101FF }, // Phaistos Disc + { 'begin': 0x102A0, 'end': 0x102DF }, // Carian + { 'begin': 0x1F030, 'end': 0x1F09F } // Domino Tiles ]; function getUnicodeRangeFor(value) { @@ -402,7 +362,7 @@ function getUnicodeRangeFor(value) { return i; } return -1; -}; +} /** * 'Font' is the class the outside world should use, it encapsulate all the font @@ -412,38 +372,32 @@ function getUnicodeRangeFor(value) { * var type1Font = new Font("MyFontName", binaryFile, propertiesObject); * type1Font.bind(); */ -var Font = (function Font() { +var Font = (function() { var constructor = function font_constructor(name, file, properties) { this.name = name; + this.textMatrix = properties.textMatrix || IDENTITY_MATRIX; this.encoding = properties.encoding; - // If the font has already been decoded simply return it - if (Fonts.lookup(name)) { - this.font = Fonts.lookup(name).data; - return; - } - fontCount++; - fontName = name; - // If the font is to be ignored, register it like an already loaded font // to avoid the cost of waiting for it be be loaded by the platform. - if (properties.ignore || kDisableFonts) { - Fonts.blacklistFont(name); + if (properties.ignore) { + this.id = Fonts.blacklistFont(name); + this.loadedName = 'pdfFont' + this.id; return; } var data; switch (properties.type) { - case "Type1": + case 'Type1': var cff = new CFF(name, file, properties); - this.mimetype = "font/opentype"; + this.mimetype = 'font/opentype'; // Wrap the CFF data inside an OTF font file data = this.convert(name, cff, properties); break; - case "TrueType": - this.mimetype = "font/opentype"; + case 'TrueType': + this.mimetype = 'font/opentype'; // Repair the TrueType file if it is can be damaged in the point of // view of the sanitizer @@ -451,11 +405,13 @@ var Font = (function Font() { break; default: - warn("Font " + properties.type + " is not supported"); + warn('Font ' + properties.type + ' is not supported'); break; } this.data = data; - Fonts.registerFont(name, data, properties); + + this.id = Fonts.registerFont(name, data, properties); + this.loadedName = 'pdfFont' + this.id; }; function stringToArray(str) { @@ -539,15 +495,12 @@ var Font = (function Font() { offsets.virtualOffset++; // checksum - var checksum = 0; - for (var i = 0; i < length; i+=4) - checksum += int32([data[i], data[i+1], data[i+2], data[i+3]]); - - var tableEntry = tag + - string32(checksum) + - string32(offset) + - string32(length); + var checksum = 0, n = data.length; + for (var i = 0; i < n; i+=4) + checksum = (checksum + int32([data[i], data[i+1], data[i+2], data[i+3]])) | 0; + var tableEntry = (tag + string32(checksum) + + string32(offset) + string32(length)); tableEntry = stringToArray(tableEntry); file.set(tableEntry, offsets.currentOffset); @@ -561,7 +514,7 @@ var Font = (function Font() { var codes = []; var length = glyphs.length; for (var n = 0; n < length; ++n) - codes.push(String.fromCharCode(glyphs[n].unicode)) + codes.push(String.fromCharCode(glyphs[n].unicode)); codes.sort(); // Split the sorted codes into ranges. @@ -573,7 +526,7 @@ var Font = (function Font() { ++end; ++n; } - ranges.push({ start: start, end: end }); + ranges.push([start, end]); } return ranges; }; @@ -583,58 +536,59 @@ var Font = (function Font() { var ranges = getRanges(glyphs); var numTables = 1; - var cmap = "\x00\x00" + // version + var cmap = '\x00\x00' + // version string16(numTables) + // numTables - "\x00\x03" + // platformID - "\x00\x01" + // encodingID + '\x00\x03' + // platformID + '\x00\x01' + // encodingID string32(4 + numTables * 8); // start of the table record - var headerSize = ((7 * 2) + (ranges.length * 4 * 2) + (4 * 2) + (glyphs.length * 2)); var segCount = ranges.length + 1; - var segCount2 = 2 * segCount; - var searchRange = 2 * getMaxPower2(segCount); - var entrySelector = Math.log(segCount) / Math.log(2); + var segCount2 = segCount * 2; + var searchRange = getMaxPower2(segCount) * 2; + var searchEntry = Math.log(segCount) / Math.log(2); var rangeShift = 2 * segCount - searchRange; - var format314 = "\x00\x04" + // format - string16(headerSize) + // length - "\x00\x00" + // language - string16(segCount2) + - string16(searchRange) + - string16(entrySelector) + - string16(rangeShift); - // Fill up the 4 parallel arrays describing the segments. - var startCount = ""; - var endCount = ""; - var idDeltas = ""; - var idRangeOffsets = ""; - var glyphsIds = ""; + var startCount = ''; + var endCount = ''; + var idDeltas = ''; + var idRangeOffsets = ''; + var glyphsIds = ''; var bias = 0; for (var i = 0; i < segCount - 1; i++) { var range = ranges[i]; - var start = range.start; - var end = range.end; - var delta = (bias - start) % 0xffff; + var start = range[0]; + var end = range[1]; + var delta = (bias - start) & 0xffff; bias += (end - start + 1); startCount += string16(start); endCount += string16(end); idDeltas += string16(delta); - idRangeOffsets += string16(0); + idRangeOffsets += string16(0); + + for (var j = start; j <= end; j++) { + glyphsIds += string16(j); + } } - endCount += "\xFF\xFF"; - startCount += "\xFF\xFF"; - idDeltas += "\x00\x01"; - idRangeOffsets += "\x00\x00"; - for (var i = 0; i < glyphs.length; i++) - glyphsIds += string16(i); + endCount += '\xFF\xFF'; + startCount += '\xFF\xFF'; + idDeltas += '\x00\x01'; + idRangeOffsets += '\x00\x00'; - format314 += endCount + "\x00\x00" + startCount + - idDeltas + idRangeOffsets + glyphsIds; + var format314 = '\x00\x00' + // language + string16(segCount2) + + string16(searchRange) + + string16(searchEntry) + + string16(rangeShift) + + endCount + '\x00\x00' + startCount + + idDeltas + idRangeOffsets + glyphsIds; - return stringToArray(cmap + format314); + return stringToArray(cmap + + '\x00\x04' + // format + string16(format314.length + 4) + // length + format314); }; function createOS2Table(properties) { @@ -648,13 +602,14 @@ var Font = (function Font() { var firstCharIndex = null; var lastCharIndex = 0; - for (var i = 1; i < charset.length; i++) {var code = GlyphsUnicode[charset[i]]; + for (var i = 0; i < charset.length; i++) { + var code = GlyphsUnicode[charset[i]]; if (firstCharIndex > code || !firstCharIndex) - firstCharIndex = code; + firstCharIndex = code; if (lastCharIndex < code) lastCharIndex = code; - var position = getUnicodeRangeFor(code); + var position = getUnicodeRangeFor(code); if (position < 32) { ulUnicodeRange1 |= 1 << position; } else if (position < 64) { @@ -664,62 +619,64 @@ var Font = (function Font() { } else if (position < 123) { ulUnicodeRange4 |= 1 << position - 96; } else { - error("Unicode ranges Bits > 123 are reserved for internal usage"); + error('Unicode ranges Bits > 123 are reserved for internal usage'); } } } - return "\x00\x03" + // version - "\x02\x24" + // xAvgCharWidth - "\x01\xF4" + // usWeightClass - "\x00\x05" + // usWidthClass - "\x00\x00" + // fstype (0 to let the font loads via font-face on IE) - "\x02\x8A" + // ySubscriptXSize - "\x02\xBB" + // ySubscriptYSize - "\x00\x00" + // ySubscriptXOffset - "\x00\x8C" + // ySubscriptYOffset - "\x02\x8A" + // ySuperScriptXSize - "\x02\xBB" + // ySuperScriptYSize - "\x00\x00" + // ySuperScriptXOffset - "\x01\xDF" + // ySuperScriptYOffset - "\x00\x31" + // yStrikeOutSize - "\x01\x02" + // yStrikeOutPosition - "\x00\x00" + // sFamilyClass - "\x00\x00\x06" + String.fromCharCode(properties.fixedPitch ? 0x09 : 0x00) + - "\x00\x00\x00\x00\x00\x00" + // Panose + return '\x00\x03' + // version + '\x02\x24' + // xAvgCharWidth + '\x01\xF4' + // usWeightClass + '\x00\x05' + // usWidthClass + '\x00\x00' + // fstype (0 to let the font loads via font-face on IE) + '\x02\x8A' + // ySubscriptXSize + '\x02\xBB' + // ySubscriptYSize + '\x00\x00' + // ySubscriptXOffset + '\x00\x8C' + // ySubscriptYOffset + '\x02\x8A' + // ySuperScriptXSize + '\x02\xBB' + // ySuperScriptYSize + '\x00\x00' + // ySuperScriptXOffset + '\x01\xDF' + // ySuperScriptYOffset + '\x00\x31' + // yStrikeOutSize + '\x01\x02' + // yStrikeOutPosition + '\x00\x00' + // sFamilyClass + '\x00\x00\x06' + + String.fromCharCode(properties.fixedPitch ? 0x09 : 0x00) + + '\x00\x00\x00\x00\x00\x00' + // Panose string32(ulUnicodeRange1) + // ulUnicodeRange1 (Bits 0-31) string32(ulUnicodeRange2) + // ulUnicodeRange2 (Bits 32-63) string32(ulUnicodeRange3) + // ulUnicodeRange3 (Bits 64-95) string32(ulUnicodeRange4) + // ulUnicodeRange4 (Bits 96-127) - "\x2A\x32\x31\x2A" + // achVendID + '\x2A\x32\x31\x2A' + // achVendID string16(properties.italicAngle ? 1 : 0) + // fsSelection - string16(firstCharIndex || properties.firstChar) + // usFirstCharIndex + string16(firstCharIndex || + properties.firstChar) + // usFirstCharIndex string16(lastCharIndex || properties.lastChar) + // usLastCharIndex string16(properties.ascent) + // sTypoAscender string16(properties.descent) + // sTypoDescender - "\x00\x64" + // sTypoLineGap (7%-10% of the unitsPerEM value) - string16(properties.ascent) + // usWinAscent + '\x00\x64' + // sTypoLineGap (7%-10% of the unitsPerEM value) + string16(properties.ascent) + // usWinAscent string16(-properties.descent) + // usWinDescent - "\x00\x00\x00\x00" + // ulCodePageRange1 (Bits 0-31) - "\x00\x00\x00\x00" + // ulCodePageRange2 (Bits 32-63) - string16(properties.xHeight) + // sxHeight + '\x00\x00\x00\x00' + // ulCodePageRange1 (Bits 0-31) + '\x00\x00\x00\x00' + // ulCodePageRange2 (Bits 32-63) + string16(properties.xHeight) + // sxHeight string16(properties.capHeight) + // sCapHeight string16(0) + // usDefaultChar string16(firstCharIndex || properties.firstChar) + // usBreakChar - "\x00\x03"; // usMaxContext + '\x00\x03'; // usMaxContext }; function createPostTable(properties) { var angle = Math.floor(properties.italicAngle * (Math.pow(2, 16))); - return "\x00\x03\x00\x00" + // Version number - string32(angle) + // italicAngle - "\x00\x00" + // underlinePosition - "\x00\x00" + // underlineThickness + return '\x00\x03\x00\x00' + // Version number + string32(angle) + // italicAngle + '\x00\x00' + // underlinePosition + '\x00\x00' + // underlineThickness string32(properties.fixedPitch) + // isFixedPitch - "\x00\x00\x00\x00" + // minMemType42 - "\x00\x00\x00\x00" + // maxMemType42 - "\x00\x00\x00\x00" + // minMemType1 - "\x00\x00\x00\x00"; // maxMemType1 + '\x00\x00\x00\x00' + // minMemType42 + '\x00\x00\x00\x00' + // maxMemType42 + '\x00\x00\x00\x00' + // minMemType1 + '\x00\x00\x00\x00'; // maxMemType1 }; constructor.prototype = { @@ -748,13 +705,16 @@ var Font = (function Font() { var data = file.getBytes(length); file.pos = previousPosition; + if (tag == 'head') + data[8] = data[9] = data[10] = data[11] = 0; // clearing checksum adjustment + return { tag: tag, checksum: checksum, length: offset, offset: length, data: data - } + }; }; function readOpenTypeHeader(ttf) { @@ -764,7 +724,7 @@ var Font = (function Font() { searchRange: int16(ttf.getBytes(2)), entrySelector: int16(ttf.getBytes(2)), rangeShift: int16(ttf.getBytes(2)) - } + }; }; function replaceCMapTable(cmap, font, properties) { @@ -785,7 +745,8 @@ var Font = (function Font() { (format == 6 && numTables == 1 && !properties.encoding.empty)) { // Format 0 alone is not allowed by the sanitizer so let's rewrite // that to a 3-1-4 Unicode BMP table - TODO("Charset is not reliable, use somethin else"); + TODO('Use an other source of informations than ' + + 'charset here, it is not reliable'); var charset = properties.charset; var glyphs = []; for (var j = 0; j < charset.length; j++) { @@ -826,25 +787,24 @@ var Font = (function Font() { glyphs[j] = { unicode: glyphs[j] + firstCode }; var ranges = getRanges(glyphs); - assert(ranges.length == 1, - "Got " + ranges.length + " ranges in a dense array"); + assert(ranges.length == 1, 'Got ' + ranges.length + + ' ranges in a dense array'); var encoding = properties.encoding; var denseRange = ranges[0]; - var start = denseRange.start; - var end = denseRange.end; + var start = denseRange[0]; + var end = denseRange[1]; var index = firstCode; for (var j = start; j <= end; j++) encoding[index++] = glyphs[j - firstCode - 1].unicode; - cmap.data = createCMapTable(glyphs); } } }; // Check that required tables are present - var requiredTables = [ "OS/2", "cmap", "head", "hhea", - "hmtx", "maxp", "name", "post" ]; + var requiredTables = ['OS/2', 'cmap', 'head', 'hhea', + 'hmtx', 'maxp', 'name', 'post']; var header = readOpenTypeHeader(font); var numTables = header.numTables; @@ -858,9 +818,9 @@ var Font = (function Font() { var table = readTableEntry(font); var index = requiredTables.indexOf(table.tag); if (index != -1) { - if (table.tag == "cmap") + if (table.tag == 'cmap') cmap = table; - else if (table.tag == "post") + else if (table.tag == 'post') post = table; requiredTables.splice(index, 1); @@ -868,10 +828,10 @@ var Font = (function Font() { tables.push(table); } - // If any tables are still in the array this means some required tables are - // missing, which means that we need to rebuild the font in order to pass - // the sanitizer. - if (requiredTables.length && requiredTables[0] == "OS/2") { + // If any tables are still in the array this means some required + // tables are missing, which means that we need to rebuild the + // font in order to pass the sanitizer. + if (requiredTables.length && requiredTables[0] == 'OS/2') { // Create a new file to hold the new version of our truetype with a new // header and new offsets var ttf = new Uint8Array(kMaxFontFileSize); @@ -886,13 +846,13 @@ var Font = (function Font() { virtualOffset: numTables * (4 * 4) }; - // The new numbers of tables will be the last one plus the num of missing - // tables - createOpenTypeHeader("\x00\x01\x00\x00", ttf, offsets, numTables); + // The new numbers of tables will be the last one plus the num + // of missing tables + createOpenTypeHeader('\x00\x01\x00\x00', ttf, offsets, numTables); // Insert the missing table tables.push({ - tag: "OS/2", + tag: 'OS/2', data: stringToArray(createOS2Table(properties)) }); @@ -902,7 +862,7 @@ var Font = (function Font() { // Rewrite the 'post' table if needed if (!post) { tables.push({ - tag: "post", + tag: 'post', data: stringToArray(createPostTable(properties)) }); } @@ -941,7 +901,8 @@ var Font = (function Font() { return fontData; } else if (requiredTables.length) { - error("Table " + requiredTables[0] + " is missing from the TrueType font"); + error('Table ' + requiredTables[0] + + ' is missing from the TrueType font'); } return font.getBytes(); @@ -949,21 +910,17 @@ var Font = (function Font() { convert: function font_convert(fontName, font, properties) { function createNameTable(name) { - // All the strings of the name table should be an odd number of bytes - if (name.length % 2) - name = name.slice(0, name.length - 1); - var strings = [ - "Original licence", // 0.Copyright + 'Original licence', // 0.Copyright name, // 1.Font family - "Unknown", // 2.Font subfamily (font weight) - "uniqueID", // 3.Unique ID + 'Unknown', // 2.Font subfamily (font weight) + 'uniqueID', // 3.Unique ID name, // 4.Full font name - "Version 0.11", // 5.Version - "Unknown", // 6.Postscript name - "Unknown", // 7.Trademark - "Unknown", // 8.Manufacturer - "Unknown" // 9.Designer + 'Version 0.11', // 5.Version + '', // 6.Postscript name + 'Unknown', // 7.Trademark + 'Unknown', // 8.Manufacturer + 'Unknown' // 9.Designer ]; // Mac want 1-byte per character strings while Windows want @@ -972,22 +929,22 @@ var Font = (function Font() { for (var i = 0; i < strings.length; i++) { var str = strings[i]; - var strUnicode = ""; + var strUnicode = ''; for (var j = 0; j < str.length; j++) strUnicode += string16(str.charCodeAt(j)); stringsUnicode.push(strUnicode); } var names = [strings, stringsUnicode]; - var platforms = ["\x00\x01", "\x00\x03"]; - var encodings = ["\x00\x00", "\x00\x01"]; - var languages = ["\x00\x00", "\x04\x09"]; + var platforms = ['\x00\x01', '\x00\x03']; + var encodings = ['\x00\x00', '\x00\x01']; + var languages = ['\x00\x00', '\x04\x09']; var namesRecordCount = strings.length * platforms.length; var nameTable = - "\x00\x00" + // format + '\x00\x00' + // format string16(namesRecordCount) + // Number of names Record - string16(namesRecordCount * 12 + 6); // Offset to start of storage + string16(namesRecordCount * 12 + 6); // Storage // Build the name records field var strOffset = 0; @@ -1007,90 +964,91 @@ var Font = (function Font() { } } - nameTable += strings.join("") + stringsUnicode.join(""); + nameTable += strings.join('') + stringsUnicode.join(''); return nameTable; - }; + } - function isFixedPitch(glyphs) { - for (var i = 0; i < glyphs.length - 1; i++) { - if (glyphs[i] != glyphs[i+1]) - return false; - } - return true; + function isFixedPitch(glyphs) { + for (var i = 0; i < glyphs.length - 1; i++) { + if (glyphs[i] != glyphs[i + 1]) + return false; + } + return true; }; // The offsets object holds at the same time a representation of where // to write the table entry information about a table and another offset // representing the offset where to draw the actual data of a particular // table - var tablesCount = 9; + var kRequiredTablesCount = 9; var offsets = { currentOffset: 0, - virtualOffset: tablesCount * (4 * 4) + virtualOffset: 9 * (4 * 4) }; var otf = new Uint8Array(kMaxFontFileSize); - createOpenTypeHeader("\x4F\x54\x54\x4F", otf, offsets, tablesCount); + createOpenTypeHeader('\x4F\x54\x54\x4F', otf, offsets, 9); - var charstrings = font.charstrings; - properties.fixedPitch = isFixedPitch(charstrings); + var charstrings = font.charstrings; + properties.fixedPitch = isFixedPitch(charstrings); var fields = { // PostScript Font Program - "CFF ": font.data, + 'CFF ': font.data, // OS/2 and Windows Specific metrics - "OS/2": stringToArray(createOS2Table(properties)), + 'OS/2': stringToArray(createOS2Table(properties)), // Character to glyphs mapping - "cmap": createCMapTable(charstrings.slice()), + 'cmap': createCMapTable(charstrings.slice()), // Font header - "head": (function convert_fields_head() { + 'head': (function() { return stringToArray( - "\x00\x01\x00\x00" + // Version number - "\x00\x00\x10\x00" + // fontRevision - "\x00\x00\x00\x00" + // checksumAdjustement - "\x5F\x0F\x3C\xF5" + // magicNumber - "\x00\x00" + // Flags - "\x03\xE8" + // unitsPerEM (defaulting to 1000) - "\x00\x00\x00\x00\x9e\x0b\x7e\x27" + // creation date - "\x00\x00\x00\x00\x9e\x0b\x7e\x27" + // modifification date - "\x00\x00" + // xMin + '\x00\x01\x00\x00' + // Version number + '\x00\x00\x10\x00' + // fontRevision + '\x00\x00\x00\x00' + // checksumAdjustement + '\x5F\x0F\x3C\xF5' + // magicNumber + '\x00\x00' + // Flags + '\x03\xE8' + // unitsPerEM (defaulting to 1000) + '\x00\x00\x00\x00\x9e\x0b\x7e\x27' + // creation date + '\x00\x00\x00\x00\x9e\x0b\x7e\x27' + // modifification date + '\x00\x00' + // xMin string16(properties.descent) + // yMin - "\x0F\xFF" + // xMax + '\x0F\xFF' + // xMax string16(properties.ascent) + // yMax string16(properties.italicAngle ? 2 : 0) + // macStyle - "\x00\x11" + // lowestRecPPEM - "\x00\x00" + // fontDirectionHint - "\x00\x00" + // indexToLocFormat - "\x00\x00"); // glyphDataFormat + '\x00\x11' + // lowestRecPPEM + '\x00\x00' + // fontDirectionHint + '\x00\x00' + // indexToLocFormat + '\x00\x00'); // glyphDataFormat })(), // Horizontal header - "hhea": (function convert_fields_hhea() { + 'hhea': (function() { return stringToArray( - "\x00\x01\x00\x00" + // Version number + '\x00\x01\x00\x00' + // Version number string16(properties.ascent) + // Typographic Ascent string16(properties.descent) + // Typographic Descent - "\x00\x00" + // Line Gap - "\xFF\xFF" + // advanceWidthMax - "\x00\x00" + // minLeftSidebearing - "\x00\x00" + // minRightSidebearing - "\x00\x00" + // xMaxExtent + '\x00\x00' + // Line Gap + '\xFF\xFF' + // advanceWidthMax + '\x00\x00' + // minLeftSidebearing + '\x00\x00' + // minRightSidebearing + '\x00\x00' + // xMaxExtent string16(properties.capHeight) + // caretSlopeRise - string16(Math.tan(properties.italicAngle) * properties.xHeight) + // caretSlopeRun - "\x00\x00" + // caretOffset - "\x00\x00" + // -reserved- - "\x00\x00" + // -reserved- - "\x00\x00" + // -reserved- - "\x00\x00" + // -reserved- - "\x00\x00" + // metricDataFormat + string16(Math.tan(properties.italicAngle) * + properties.xHeight) + // caretSlopeRun + '\x00\x00' + // caretOffset + '\x00\x00' + // -reserved- + '\x00\x00' + // -reserved- + '\x00\x00' + // -reserved- + '\x00\x00' + // -reserved- + '\x00\x00' + // metricDataFormat string16(charstrings.length + 1)); // Number of HMetrics })(), // Horizontal metrics - "hmtx": (function convert_fields_hmtx() { - var hmtx = "\x00\x00\x00\x00"; // Fake .notdef + 'hmtx': (function() { + var hmtx = '\x00\x00\x00\x00'; // Fake .notdef for (var i = 0; i < charstrings.length; i++) { hmtx += string16(charstrings[i].width) + string16(0); } @@ -1098,42 +1056,28 @@ var Font = (function Font() { })(), // Maximum profile - "maxp": (function convert_fields_maxp() { + 'maxp': (function() { return stringToArray( - "\x00\x00\x50\x00" + // Version number + '\x00\x00\x50\x00' + // Version number string16(charstrings.length + 1)); // Num of glyphs })(), // Naming tables - "name": stringToArray(createNameTable(fontName)), + 'name': stringToArray(createNameTable(fontName)), // PostScript informations - "post": stringToArray(createPostTable(properties)) + 'post': stringToArray(createPostTable(properties)) }; for (var field in fields) createTableEntry(otf, offsets, field, fields[field]); - var headPosition = 0; for (var field in fields) { var table = fields[field]; - if (field == "head") - headPosition = offsets.currentOffset; - otf.set(table, offsets.currentOffset); offsets.currentOffset += table.length; } - // Now calculate the checksumAdjustement for all the file and put it into - // head. This will make the head checksum incorrect but per spec that's - // the way it works. - var checksumAdjustement = 0; - for (var i = 0; i < offsets.currentOffset; i+=4) - checksumAdjustement += int16([otf[i], otf[i+1], otf[i+2], otf[i+3]]); - checksumAdjustement = 0xB1B0AFBA - checksumAdjustement; - otf.set(stringToArray(string32(checksumAdjustement)), headPosition + (2 * 4)); - - var fontData = []; for (var i = 0; i < offsets.currentOffset; i++) fontData.push(otf[i]); @@ -1142,22 +1086,72 @@ var Font = (function Font() { bindWorker: function font_bindWorker(data) { postMessage({ - action: "font", + action: 'font', data: { - raw: data, - fontName: this.name, + raw: data, + fontName: this.loadedName, mimetype: this.mimetype } }); }, bindDOM: function font_bindDom(data) { - var url = "url(data:" + this.mimetype + ";base64," + window.btoa(data) + ");"; - var rule = "@font-face { font-family:'" + this.name + "';src:" + url + "}"; + var fontName = this.loadedName; + // Add the font-face rule to the document + var url = ('url(data:' + this.mimetype + ';base64,' + + window.btoa(data) + ');'); + var rule = "@font-face { font-family:'" + fontName + "';src:" + url + '}'; var styleSheet = document.styleSheets[0]; styleSheet.insertRule(rule, styleSheet.cssRules.length); + return rule; + }, + + charsToUnicode: function fonts_chars2Unicode(chars) { + var charsCache = this.charsCache; + + // if we translated this string before, just grab it from the cache + if (charsCache) { + var str = charsCache[chars]; + if (str) + return str; + } + + // translate the string using the font's encoding + var encoding = this.encoding; + if (!encoding) + return chars; + + // lazily create the translation cache + if (!charsCache) + charsCache = this.charsCache = Object.create(null); + + str = ''; + for (var i = 0; i < chars.length; ++i) { + var charcode = chars.charCodeAt(i); + var unicode = encoding[charcode]; + if ('undefined' == typeof(unicode)) { + // FIXME/issue 233: we're hitting this in test/pdf/sizes.pdf + // at the moment, for unknown reasons. + warn('Unencoded charcode '+ charcode); + unicode = charcode; + } + + // Check if the glyph has already been converted + if (!IsNum(unicode)) + unicode = encoding[unicode] = GlyphsUnicode[unicode.name]; + + // Handle surrogate pairs + if (unicode > 0xFFFF) { + str += String.fromCharCode(unicode & 0xFFFF); + unicode >>= 16; + } + str += String.fromCharCode(unicode); + } + + // Enter the translated string into the cache + return charsCache[chars] = str; } }; @@ -1166,10 +1160,10 @@ var Font = (function Font() { /** * Type1Parser encapsulate the needed code for parsing a Type1 font - * program. - * Some of its logic depends on the Type2 charstrings structure. + * program. Some of its logic depends on the Type2 charstrings + * structure. */ -var Type1Parser = function Type1Parser() { +var Type1Parser = function() { /* * Decrypt a Sequence of Ciphertext Bytes to Produce the Original Sequence * of Plaintext Bytes. The function took a key as a parameter which can be @@ -1182,7 +1176,7 @@ var Type1Parser = function Type1Parser() { var r = key, c1 = 52845, c2 = 22719; var decryptedString = []; - var value = ""; + var value = ''; var count = stream.length; for (var i = 0; i < count; i++) { value = stream[i]; @@ -1231,65 +1225,65 @@ var Type1Parser = function Type1Parser() { * the charStrings. */ var charStringDictionary = { - "1": "hstem", - "3": "vstem", - "4": "vmoveto", - "5": "rlineto", - "6": "hlineto", - "7": "vlineto", - "8": "rrcurveto", + '1': 'hstem', + '3': 'vstem', + '4': 'vmoveto', + '5': 'rlineto', + '6': 'hlineto', + '7': 'vlineto', + '8': 'rrcurveto', // closepath is a Type1 command that do not take argument and is useless // in Type2 and it can simply be ignored. - "9": null, // closepath + '9': null, // closepath - "10": "callsubr", + '10': 'callsubr', // return is normally used inside sub-routines to tells to the execution // flow that it can be back to normal. // During the translation process Type1 charstrings will be flattened and // sub-routines will be embedded directly into the charstring directly, so // this can be ignored safely. - "11": "return", + '11': 'return', - "12": { + '12': { // dotsection is a Type1 command to specify some hinting feature for dots // that do not take a parameter and it can safely be ignored for Type2. - "0": null, // dotsection + '0': null, // dotsection // [vh]stem3 are Type1 only and Type2 supports [vh]stem with multiple // parameters, so instead of returning [vh]stem3 take a shortcut and // return [vhstem] instead. - "1": "vstem", - "2": "hstem", + '1': 'vstem', + '2': 'hstem', // Type1 only command with command not (yet) built-in ,throw an error - "6": -1, // seac - "7": -1, //sbw + '6': -1, // seac + '7': -1, //sbw - "11": "sub", - "12": "div", + '11': 'sub', + '12': 'div', // callothersubr is a mechanism to make calls on the postscript // interpreter, this is not supported by Type2 charstring but hopefully // most of the default commands can be ignored safely. - "16": "callothersubr", + '16': 'callothersubr', - "17": "pop", + '17': 'pop', // setcurrentpoint sets the current point to x, y without performing a // moveto (this is a one shot positionning command). This is used only // with the return of an OtherSubrs call. // TODO Implement the OtherSubrs charstring embedding and replace this // call by a no-op, like 2 'pop' commands for example. - "33": null, //setcurrentpoint + '33': null //setcurrentpoint }, - "13": "hsbw", - "14": "endchar", - "21": "rmoveto", - "22": "hmoveto", - "30": "vhcurveto", - "31": "hvcurveto" + '13': 'hsbw', + '14': 'endchar', + '21': 'rmoveto', + '22': 'hmoveto', + '30': 'vhcurveto', + '31': 'hvcurveto' }; var kEscapeCommand = 12; @@ -1300,7 +1294,7 @@ var Type1Parser = function Type1Parser() { var width = 0; var used = false; - var value = ""; + var value = ''; var count = array.length; for (var i = 0; i < count; i++) { value = parseInt(array[i]); @@ -1332,20 +1326,20 @@ var Type1Parser = function Type1Parser() { } } - command = charStringDictionary["12"][escape]; + 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") { + } else if (charstring.length == 4 && charstring[3] == 'div') { width = charstring[1] / charstring[2]; } else { - error("Unsupported hsbw format: " + charstring); + error('Unsupported hsbw format: ' + charstring); } lsb = charstring[0]; - charstring.push(lsb, "hmoveto"); + charstring.push(lsb, 'hmoveto'); charstring.splice(0, 1); continue; } @@ -1359,9 +1353,9 @@ var Type1Parser = function Type1Parser() { } else if (!command) { break; } else if (command == -1) { - error("Support for Type1 command " + - value + " (" + escape + ")" + - " is not implemented in charstring: " + charString); + error('Support for Type1 command ' + value + + ' (' + escape + ') is not implemented in charstring: ' + + charString); } value = command; @@ -1383,101 +1377,119 @@ var Type1Parser = function Type1Parser() { }; /** - * Returns an object containing a Subrs array and a CharStrings array - * extracted from and eexec encrypted block of data + * Returns an object containing a Subrs array and a CharStrings + * array extracted from and eexec encrypted block of data */ - function readNumbers(str, index) { + function readNumberArray(str, index) { var start = ++index; var count = 0; - while (str[index++] != "]") + while (str[index++] != ']') count++; - var array = str.substr(start, count).split(" "); + var array = str.substr(start, count).split(' '); for (var i = 0; i < array.length; i++) array[i] = parseFloat(array[i] || 0); return array; }; + function readNumber(str, index) { + while (str[index++] == ' ') + ; + var start = index; + + var count = 0; + while (str[index++] != ' ') + count++; + + return parseFloat(str.substr(start, count) || 0); + }; + this.extractFontProgram = function t1_extractFontProgram(stream) { var eexec = decrypt(stream, kEexecEncryptionKey, 4); - var eexecString = ""; + var eexecStr = ''; for (var i = 0; i < eexec.length; i++) - eexecString += String.fromCharCode(eexec[i]); + eexecStr += String.fromCharCode(eexec[i]); var glyphsSection = false, subrsSection = false; - var extracted = { + var program = { subrs: [], charstrings: [], properties: { - stemSnapH: [0, 0], - stemSnapV: [0, 0] + 'private': {} } }; - var glyph = ""; - var token = ""; + var glyph = ''; + var token = ''; var length = 0; - var c = ""; - var count = eexecString.length; + var c = ''; + var count = eexecStr.length; for (var i = 0; i < count; i++) { - var c = eexecString[i]; + var c = eexecStr[i]; - if ((glyphsSection || subrsSection) && c == "R") { + if ((glyphsSection || subrsSection) && c == 'R') { var data = eexec.slice(i + 3, i + 3 + length); var encoded = decrypt(data, kCharStringsEncryptionKey, 4); var str = decodeCharString(encoded); if (glyphsSection) { - extracted.charstrings.push({ + program.charstrings.push({ glyph: glyph, data: str.charstring, lsb: str.lsb, width: str.width }); } else { - extracted.subrs.push(str.charstring); + program.subrs.push(str.charstring); } i += length + 3; - } else if (c == " " || c == "\n") { + } else if (c == ' ' || c == '\n') { length = parseInt(token); - token = ""; + token = ''; } else { token += c; if (!glyphsSection) { switch (token) { - case "/CharString": + case '/CharString': glyphsSection = true; break; - case "/Subrs": + case '/Subrs': subrsSection = true; break; - case "/StdHW": - extracted.properties.stdHW = readNumbers(eexecString, i + 2)[0]; + case '/BlueValues': + case '/OtherBlues': + case '/FamilyBlues': + case '/FamilyOtherBlues': + case '/StemSnapH': + case '/StemSnapV': + program.properties.private[token.substring(1)] = readNumberArray(eexecStr, i + 2); break; - case "/StdVW": - extracted.properties.stdVW = readNumbers(eexecString, i + 2)[0]; + case '/StdHW': + case '/StdVW': + program.properties.private[token.substring(1)] = readNumberArray(eexecStr, i + 2)[0]; break; - case "/StemSnapH": - extracted.properties.stemSnapH = readNumbers(eexecString, i + 2); - break; - case "/StemSnapV": - extracted.properties.stemSnapV = readNumbers(eexecString, i + 2); + case '/BlueShift': + case '/BlueFuzz': + case '/BlueScale': + case '/LanguageGroup': + case '/ExpansionFactor': + program.properties.private[token.substring(1)] = readNumber(eexecStr, i + 1); break; } - } else if (c == "/") { - token = glyph = ""; - while ((c = eexecString[++i]) != " ") + } else if (c == '/') { + token = glyph = ''; + while ((c = eexecStr[++i]) != ' ') glyph += c; } } } - return extracted; + return program; }, this.extractFontHeader = function t1_extractFontProgram(stream) { - var headerString = ""; + var headerString = ''; for (var i = 0; i < stream.length; i++) headerString += String.fromCharCode(stream[i]); @@ -1485,14 +1497,14 @@ var Type1Parser = function Type1Parser() { textMatrix: null }; - var token = ""; + var token = ''; var count = headerString.length; for (var i = 0; i < count; i++) { var c = headerString[i]; - if (c == " " || c == "\n") { + if (c == ' ' || c == '\n') { switch (token) { - case "/FontMatrix": - var matrix = readNumbers(headerString, i + 1); + case '/FontMatrix': + var matrix = readNumberArray(headerString, i + 1); // The FontMatrix is in unitPerEm, so make it pixels for (var j = 0; j < matrix.length; j++) @@ -1504,7 +1516,7 @@ var Type1Parser = function Type1Parser() { info.textMatrix = matrix; break; } - token = ""; + token = ''; } else { token += c; } @@ -1519,73 +1531,79 @@ var Type1Parser = function Type1Parser() { * which itself embed Type2 charstrings. */ var CFFStrings = [ - ".notdef","space","exclam","quotedbl","numbersign","dollar","percent", - "ampersand","quoteright","parenleft","parenright","asterisk","plus","comma", - "hyphen","period","slash","zero","one","two","three","four","five","six", - "seven","eight","nine","colon","semicolon","less","equal","greater", - "question","at","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O", - "P","Q","R","S","T","U","V","W","X","Y","Z","bracketleft","backslash", - "bracketright","asciicircum","underscore","quoteleft","a","b","c","d","e", - "f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x", - "y","z","braceleft","bar","braceright","asciitilde","exclamdown","cent", - "sterling","fraction","yen","florin","section","currency","quotesingle", - "quotedblleft","guillemotleft","guilsinglleft","guilsinglright","fi","fl", - "endash","dagger","daggerdbl","periodcentered","paragraph","bullet", - "quotesinglbase","quotedblbase","quotedblright","guillemotright","ellipsis", - "perthousand","questiondown","grave","acute","circumflex","tilde","macron", - "breve","dotaccent","dieresis","ring","cedilla","hungarumlaut","ogonek", - "caron","emdash","AE","ordfeminine","Lslash","Oslash","OE","ordmasculine", - "ae","dotlessi","lslash","oslash","oe","germandbls","onesuperior", - "logicalnot","mu","trademark","Eth","onehalf","plusminus","Thorn", - "onequarter","divide","brokenbar","degree","thorn","threequarters", - "twosuperior","registered","minus","eth","multiply","threesuperior", - "copyright","Aacute","Acircumflex","Adieresis","Agrave","Aring", - "Atilde","Ccedilla","Eacute","Ecircumflex","Edieresis","Egrave","Iacute", - "Icircumflex","Idieresis","Igrave","Ntilde","Oacute","Ocircumflex", - "Odieresis","Ograve","Otilde","Scaron","Uacute","Ucircumflex","Udieresis", - "Ugrave","Yacute","Ydieresis","Zcaron","aacute","acircumflex","adieresis", - "agrave","aring","atilde","ccedilla","eacute","ecircumflex","edieresis", - "egrave","iacute","icircumflex","idieresis","igrave","ntilde","oacute", - "ocircumflex","odieresis","ograve","otilde","scaron","uacute","ucircumflex", - "udieresis","ugrave","yacute","ydieresis","zcaron","exclamsmall", - "Hungarumlautsmall","dollaroldstyle","dollarsuperior","ampersandsmall", - "Acutesmall","parenleftsuperior","parenrightsuperior","206 ff", - "onedotenleader","zerooldstyle","oneoldstyle","twooldstyle","threeoldstyle", - "fouroldstyle","fiveoldstyle","sixoldstyle","sevenoldstyle","eightoldstyle", - "nineoldstyle","commasuperior","threequartersemdash","periodsuperior", - "questionsmall","asuperior","bsuperior","centsuperior","dsuperior", - "esuperior","isuperior","lsuperior","msuperior", "nsuperior","osuperior", - "rsuperior","ssuperior","tsuperior","ff","ffi","ffl","parenleftinferior", - "parenrightinferior","Circumflexsmall","hyphensuperior","Gravesmall", - "Asmall","Bsmall","Csmall","Dsmall","Esmall","Fsmall","Gsmall","Hsmall", - "Ismall","Jsmall","Ksmall","Lsmall","Msmall","Nsmall","Osmall","Psmall", - "Qsmall","Rsmall","Ssmall","Tsmall","Usmall","Vsmall","Wsmall","Xsmall", - "Ysmall","Zsmall","colonmonetary","onefitted","rupiah","Tildesmall", - "exclamdownsmall","centoldstyle","Lslashsmall","Scaronsmall","Zcaronsmall", - "Dieresissmall","Brevesmall","Caronsmall","Dotaccentsmall","Macronsmall", - "figuredash","hypheninferior","Ogoneksmall","Ringsmall","Cedillasmall", - "questiondownsmall","oneeighth","threeeighths","fiveeighths","seveneighths", - "onethird","twothirds","zerosuperior","foursuperior","fivesuperior", - "sixsuperior","sevensuperior","eightsuperior","ninesuperior","zeroinferior", - "oneinferior","twoinferior","threeinferior","fourinferior","fiveinferior", - "sixinferior","seveninferior","eightinferior","nineinferior","centinferior", - "dollarinferior","periodinferior","commainferior","Agravesmall", - "Aacutesmall","Acircumflexsmall","Atildesmall","Adieresissmall","Aringsmall", - "AEsmall","Ccedillasmall","Egravesmall","Eacutesmall","Ecircumflexsmall", - "Edieresissmall","Igravesmall","Iacutesmall","Icircumflexsmall", - "Idieresissmall","Ethsmall","Ntildesmall","Ogravesmall","Oacutesmall", - "Ocircumflexsmall","Otildesmall","Odieresissmall","OEsmall","Oslashsmall", - "Ugravesmall","Uacutesmall","Ucircumflexsmall","Udieresissmall", - "Yacutesmall","Thornsmall","Ydieresissmall","001.000","001.001","001.002", - "001.003","Black","Bold","Book","Light","Medium","Regular","Roman","Semibold" + '.notdef', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent', + 'ampersand', 'quoteright', 'parenleft', 'parenright', 'asterisk', 'plus', + 'comma', 'hyphen', 'period', 'slash', 'zero', 'one', 'two', 'three', 'four', + 'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon', 'less', + 'equal', 'greater', 'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', + 'X', 'Y', 'Z', 'bracketleft', 'backslash', 'bracketright', 'asciicircum', + 'underscore', 'quoteleft', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', + 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', + 'z', 'braceleft', 'bar', 'braceright', 'asciitilde', 'exclamdown', 'cent', + 'sterling', 'fraction', 'yen', 'florin', 'section', 'currency', + 'quotesingle', 'quotedblleft', 'guillemotleft', 'guilsinglleft', + 'guilsinglright', 'fi', 'fl', 'endash', 'dagger', 'daggerdbl', + 'periodcentered', 'paragraph', 'bullet', 'quotesinglbase', 'quotedblbase', + 'quotedblright', 'guillemotright', 'ellipsis', 'perthousand', 'questiondown', + 'grave', 'acute', 'circumflex', 'tilde', 'macron', 'breve', 'dotaccent', + 'dieresis', 'ring', 'cedilla', 'hungarumlaut', 'ogonek', 'caron', 'emdash', + 'AE', 'ordfeminine', 'Lslash', 'Oslash', 'OE', 'ordmasculine', 'ae', + 'dotlessi', 'lslash', 'oslash', 'oe', 'germandbls', 'onesuperior', + 'logicalnot', 'mu', 'trademark', 'Eth', 'onehalf', 'plusminus', 'Thorn', + 'onequarter', 'divide', 'brokenbar', 'degree', 'thorn', 'threequarters', + 'twosuperior', 'registered', 'minus', 'eth', 'multiply', 'threesuperior', + 'copyright', 'Aacute', 'Acircumflex', 'Adieresis', 'Agrave', 'Aring', + 'Atilde', 'Ccedilla', 'Eacute', 'Ecircumflex', 'Edieresis', 'Egrave', + 'Iacute', 'Icircumflex', 'Idieresis', 'Igrave', 'Ntilde', 'Oacute', + 'Ocircumflex', 'Odieresis', 'Ograve', 'Otilde', 'Scaron', 'Uacute', + 'Ucircumflex', 'Udieresis', 'Ugrave', 'Yacute', 'Ydieresis', 'Zcaron', + 'aacute', 'acircumflex', 'adieresis', 'agrave', 'aring', 'atilde', + 'ccedilla', 'eacute', 'ecircumflex', 'edieresis', 'egrave', 'iacute', + 'icircumflex', 'idieresis', 'igrave', 'ntilde', 'oacute', 'ocircumflex', + 'odieresis', 'ograve', 'otilde', 'scaron', 'uacute', 'ucircumflex', + 'udieresis', 'ugrave', 'yacute', 'ydieresis', 'zcaron', 'exclamsmall', + 'Hungarumlautsmall', 'dollaroldstyle', 'dollarsuperior', 'ampersandsmall', + 'Acutesmall', 'parenleftsuperior', 'parenrightsuperior', '266 ff', + 'onedotenleader', 'zerooldstyle', 'oneoldstyle', 'twooldstyle', + 'threeoldstyle', 'fouroldstyle', 'fiveoldstyle', 'sixoldstyle', + 'sevenoldstyle', 'eightoldstyle', 'nineoldstyle', 'commasuperior', + 'threequartersemdash', 'periodsuperior', 'questionsmall', 'asuperior', + 'bsuperior', 'centsuperior', 'dsuperior', 'esuperior', 'isuperior', + 'lsuperior', 'msuperior', 'nsuperior', 'osuperior', 'rsuperior', 'ssuperior', + 'tsuperior', 'ff', 'ffi', 'ffl', 'parenleftinferior', 'parenrightinferior', + 'Circumflexsmall', 'hyphensuperior', 'Gravesmall', 'Asmall', 'Bsmall', + 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall', 'Ismall', + 'Jsmall', 'Ksmall', 'Lsmall', 'Msmall', 'Nsmall', 'Osmall', 'Psmall', + 'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall', + 'Xsmall', 'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', + 'Tildesmall', 'exclamdownsmall', 'centoldstyle', 'Lslashsmall', + 'Scaronsmall', 'Zcaronsmall', 'Dieresissmall', 'Brevesmall', 'Caronsmall', + 'Dotaccentsmall', 'Macronsmall', 'figuredash', 'hypheninferior', + 'Ogoneksmall', 'Ringsmall', 'Cedillasmall', 'questiondownsmall', 'oneeighth', + 'threeeighths', 'fiveeighths', 'seveneighths', 'onethird', 'twothirds', + 'zerosuperior', 'foursuperior', 'fivesuperior', 'sixsuperior', + 'sevensuperior', 'eightsuperior', 'ninesuperior', 'zeroinferior', + 'oneinferior', 'twoinferior', 'threeinferior', 'fourinferior', + 'fiveinferior', 'sixinferior', 'seveninferior', 'eightinferior', + 'nineinferior', 'centinferior', 'dollarinferior', 'periodinferior', + 'commainferior', 'Agravesmall', 'Aacutesmall', 'Acircumflexsmall', + 'Atildesmall', 'Adieresissmall', 'Aringsmall', 'AEsmall', 'Ccedillasmall', + 'Egravesmall', 'Eacutesmall', 'Ecircumflexsmall', 'Edieresissmall', + 'Igravesmall', 'Iacutesmall', 'Icircumflexsmall', 'Idieresissmall', + 'Ethsmall', 'Ntildesmall', 'Ogravesmall', 'Oacutesmall', 'Ocircumflexsmall', + 'Otildesmall', 'Odieresissmall', 'OEsmall', 'Oslashsmall', 'Ugravesmall', + 'Uacutesmall', 'Ucircumflexsmall', 'Udieresissmall', 'Yacutesmall', + 'Thornsmall', 'Ydieresissmall', '001.000', '001.001', '001.002', '001.003', + 'Black', 'Bold', 'Book', 'Light', 'Medium', 'Regular', 'Roman', 'Semibold' ]; var type1Parser = new Type1Parser(); -var CFF = function CFF(name, file, properties) { +var CFF = function(name, file, properties) { // Get the data block containing glyphs and subrs informations - var length1 = file.dict.get("Length1"); - var length2 = file.dict.get("Length2"); + var length1 = file.dict.get('Length1'); + var length2 = file.dict.get('Length2'); var headerBlock = file.getBytes(length1); var header = type1Parser.extractFontHeader(headerBlock); @@ -1603,8 +1621,8 @@ var CFF = function CFF(name, file, properties) { var subrs = this.getType2Subrs(data.subrs); this.charstrings = charstrings; - this.data = this.wrap(name, type2Charstrings, - this.charstrings, subrs, properties); + this.data = this.wrap(name, type2Charstrings, this.charstrings, + subrs, properties); }; CFF.prototype = { @@ -1615,45 +1633,45 @@ CFF.prototype = { // If there is no object, just create an array saying that with another // offset byte. if (count == 0) - return "\x00\x00\x00"; + return '\x00\x00\x00'; var data = String.fromCharCode(count >> 8, count & 0xff); // Next byte contains the offset size use to reference object in the file // Actually we're using 0x04 to be sure to be able to store everything // without thinking of it while coding. - data += "\x04"; + data += '\x04'; // Add another offset after this one because we need a new offset var relativeOffset = 1; for (var i = 0; i < count + 1; i++) { - data += String.fromCharCode(relativeOffset >> 24, relativeOffset >> 16, - relativeOffset >> 8, relativeOffset & 0xff); + data += String.fromCharCode((relativeOffset >>> 24) & 0xFF, (relativeOffset >> 16) & 0xFF, + (relativeOffset >> 8) & 0xFF, relativeOffset & 0xFF); if (objects[i]) relativeOffset += objects[i].length; } - for (var i =0; i < count; i++) { + for (var i = 0; i < count; i++) { for (var j = 0; j < objects[i].length; j++) - data += isByte ? String.fromCharCode(objects[i][j]) : objects[i][j]; + data += isByte ? String.fromCharCode(objects[i][j] & 0xFF) : objects[i][j]; } return data; }, encodeNumber: function cff_encodeNumber(value) { if (value >= -32768 && value <= 32767) { - return "\x1c" + - String.fromCharCode(value >> 8) + + return '\x1c' + + String.fromCharCode((value >> 8) & 0xFF) + String.fromCharCode(value & 0xFF); - } else if (value >= (-2147483647-1) && value <= 2147483647) { - return "\xff" + - String.fromCharCode(value >> 24) + - String.fromCharCode(value >> 16) + - String.fromCharCode(value >> 8) + + } else if (value >= (-2147483648) && value <= 2147483647) { + return '\xff' + + String.fromCharCode((value >>> 24) & 0xFF) + + String.fromCharCode((value >> 16) & 0xFF) + + String.fromCharCode((value >> 8) & 0xFF) + String.fromCharCode(value & 0xFF); } - error("Value: " + value + " is not allowed"); + error('Value: ' + value + ' is not allowed'); return null; }, @@ -1664,8 +1682,10 @@ CFF.prototype = { var glyph = glyphs[i]; var unicode = GlyphsUnicode[glyph.glyph]; if (!unicode) { - if (glyph.glyph != ".notdef") - warn(glyph + " has no entry in the glyphs unicode dictionary"); + if (glyph.glyph != '.notdef') { + warn(glyph.glyph + + ' does not have an entry in the glyphs unicode dictionary'); + } } else { charstrings.push({ glyph: glyph, @@ -1675,7 +1695,7 @@ CFF.prototype = { lsb: glyph.lsb }); } - }; + } charstrings.sort(function charstrings_sort(a, b) { return a.unicode - b.unicode; @@ -1685,11 +1705,11 @@ CFF.prototype = { getType2Charstrings: function cff_getType2Charstrings(type1Charstrings) { var type2Charstrings = []; - var count = type1Charstrings.length; + var count = type1Charstrings.length; for (var i = 0; i < count; i++) { var charstring = type1Charstrings[i].charstring; - var flate = this.flattenCharstring(charstring.slice(), this.commandsMap); - type2Charstrings.push(flate); + type2Charstrings.push(this.flattenCharstring(charstring.slice(), + this.commandsMap)); } return type2Charstrings; }, @@ -1720,23 +1740,23 @@ CFF.prototype = { * every 'callsubr', 'callothersubr' by the real commands. */ commandsMap: { - "hstem": 1, - "vstem": 3, - "vmoveto": 4, - "rlineto": 5, - "hlineto": 6, - "vlineto": 7, - "rrcurveto": 8, - "callsubr": 10, - "return": 11, - "sub": [12, 11], - "div": [12, 12], - "pop": [1, 12, 18], - "endchar": 14, - "rmoveto": 21, - "hmoveto": 22, - "vhcurveto": 30, - "hvcurveto": 31, + 'hstem': 1, + 'vstem': 3, + 'vmoveto': 4, + 'rlineto': 5, + 'hlineto': 6, + 'vlineto': 7, + 'rrcurveto': 8, + 'callsubr': 10, + 'return': 11, + 'sub': [12, 11], + 'div': [12, 12], + 'pop': [1, 12, 18], + 'endchar': 14, + 'rmoveto': 21, + 'hmoveto': 22, + 'vhcurveto': 30, + 'hvcurveto': 31 }, flattenCharstring: function flattenCharstring(charstring, map) { @@ -1744,7 +1764,7 @@ CFF.prototype = { var command = charstring[i]; if (command.charAt) { var cmd = map[command]; - assert(cmd, "Unknow command: " + command); + assert(cmd, 'Unknow command: ' + command); if (IsArray(cmd)) { charstring.splice(i++, 1, cmd[0], cmd[1]); @@ -1753,7 +1773,7 @@ CFF.prototype = { } } else { charstring.splice(i, 1, 28, command >> 8, command & 0xff); - i+= 2; + i += 2; } } return charstring; @@ -1762,66 +1782,67 @@ CFF.prototype = { wrap: function wrap(name, glyphs, charstrings, subrs, properties) { var fields = { // major version, minor version, header size, offset size - "header": "\x01\x00\x04\x04", + 'header': '\x01\x00\x04\x04', - "names": this.createCFFIndexHeader([name]), + 'names': this.createCFFIndexHeader([name]), - "topDict": (function topDict(self) { - return function wrap_inner_topDict() { + 'topDict': (function topDict(self) { + return function() { var dict = - "\x00\x01\x01\x01\x30" + - "\xf8\x1b\x00" + // version - "\xf8\x1b\x01" + // Notice - "\xf8\x1b\x02" + // FullName - "\xf8\x1b\x03" + // FamilyName - "\xf8\x1b\x04" + // Weight - "\x1c\x00\x00\x10"; // Encoding + '\x00\x01\x01\x01\x30' + + '\xf8\x1b\x00' + // version + '\xf8\x1c\x01' + // Notice + '\xf8\x1d\x02' + // FullName + '\xf8\x1e\x03' + // FamilyName + '\xf8\x1f\x04' + // Weight + '\x1c\x00\x00\x10'; // Encoding var boundingBox = properties.bbox; for (var i = 0; i < boundingBox.length; i++) dict += self.encodeNumber(boundingBox[i]); - dict += "\x05"; // FontBBox; + dict += '\x05'; // FontBBox; var offset = fields.header.length + fields.names.length + (dict.length + (4 + 4 + 7)) + fields.strings.length + fields.globalSubrs.length; - dict += self.encodeNumber(offset) + "\x0f"; // Charset + dict += self.encodeNumber(offset) + '\x0f'; // Charset offset = offset + (glyphs.length * 2) + 1; - dict += self.encodeNumber(offset) + "\x11"; // Charstrings + dict += self.encodeNumber(offset) + '\x11'; // Charstrings dict += self.encodeNumber(fields.private.length); - var offset = offset + fields.charstrings.length; - dict += self.encodeNumber(offset) + "\x12"; // Private + offset = offset + fields.charstrings.length; + dict += self.encodeNumber(offset) + '\x12'; // Private return dict; }; })(this), - "strings": (function strings(self) { + 'strings': (function strings(self) { var strings = [ - "Version 0.11", // Version - "See original notice", // Notice + 'Version 0.11', // Version + 'See original notice', // Notice name, // FullName name, // FamilyName - "Medium" // Weight + 'Medium' // Weight ]; return self.createCFFIndexHeader(strings); })(this), - "globalSubrs": this.createCFFIndexHeader([]), + 'globalSubrs': this.createCFFIndexHeader([]), - "charset": (function charset(self) { - var charset = "\x00"; // Encoding + 'charset': (function charset(self) { + var charset = '\x00'; // Encoding - var count = glyphs.length; + var count = glyphs.length; for (var i = 0; i < count; i++) { var index = CFFStrings.indexOf(charstrings[i].glyph.glyph); - // Some characters like asterikmath && circlecopyrt are missing from - // the original strings, for the moment let's map them to .notdef and - // see later if it cause any problems + // Some characters like asterikmath && circlecopyrt are + // missing from the original strings, for the moment let's + // map them to .notdef and see later if it cause any + // problems if (index == -1) index = 0; @@ -1830,31 +1851,46 @@ CFF.prototype = { return charset; })(this), - "charstrings": this.createCFFIndexHeader([[0x8B, 0x0E]].concat(glyphs), true), + 'charstrings': this.createCFFIndexHeader([[0x8B, 0x0E]].concat(glyphs), + true), - "private": (function wrap_private(self) { + 'private': (function(self) { var data = - "\x8b\x14" + // defaultWidth - "\x8b\x15" + // nominalWidth - self.encodeNumber(properties.stdHW) + "\x0a" + // StdHW - self.encodeNumber(properties.stdVW) + "\x0b"; // StdVW + '\x8b\x14' + // defaultWidth + '\x8b\x15'; // nominalWidth + var fieldMap = { + BlueValues: '\x06', + OtherBlues: '\x07', + FamilyBlues: '\x08', + FamilyOtherBlues: '\x09', + StemSnapH: '\x0c\x0c', + StemSnapV: '\x0c\x0d', + BlueShift: '\x0c\x0a', + BlueFuzz: '\x0c\x0b', + BlueScale: '\x0c\x09', + LanguageGroup: '\x0c\x11', + ExpansionFactor: '\x0c\x18' + }; + for (var field in fieldMap) { + if (!properties.private.hasOwnProperty(field)) continue; + var value = properties.private[field]; - var stemH = properties.stemSnapH; - for (var i = 0; i < stemH.length; i++) - data += self.encodeNumber(stemH[i]); - data += "\x0c\x0c"; // StemSnapH + if (IsArray(value)) { + data += self.encodeNumber(value[0]); + for (var i = 1; i < value.length; i++) + data += self.encodeNumber(value[i] - value[i - 1]); + } else { + data += self.encodeNumber(value); + } + data += fieldMap[field]; + } - var stemV = properties.stemSnapV; - for (var i = 0; i < stemV.length; i++) - data += self.encodeNumber(stemV[i]); - data += "\x0c\x0d"; // StemSnapV - - data += self.encodeNumber(data.length + 4) + "\x13"; // Subrs offset + data += self.encodeNumber(data.length + 4) + '\x13'; // Subrs offset return data; })(this), - "localSubrs": this.createCFFIndexHeader(subrs, true) + 'localSubrs': this.createCFFIndexHeader(subrs, true) }; fields.topDict = fields.topDict(); diff --git a/glyphlist.js b/glyphlist.js index 85ab876f9..3551e6173 100644 --- a/glyphlist.js +++ b/glyphlist.js @@ -1,7 +1,7 @@ /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- / /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ -"use strict"; +'use strict'; var GlyphsUnicode = { A: 0x0041, @@ -2474,8 +2474,8 @@ var GlyphsUnicode = { lameddageshhebrew: 0xFB3C, lamedhebrew: 0x05DC, lamedholam: 0x05DC05B9, - lamedholamdagesh: "05DC 05B9 05BC", - lamedholamdageshhebrew: "05DC 05B9 05BC", + lamedholamdagesh: '05DC 05B9 05BC', + lamedholamdageshhebrew: '05DC 05B9 05BC', lamedholamhebrew: 0x05DC05B9, lamfinalarabic: 0xFEDE, lamhahinitialarabic: 0xFCCA, @@ -2486,8 +2486,8 @@ var GlyphsUnicode = { lammedialarabic: 0xFEE0, lammeemhahinitialarabic: 0xFD88, lammeeminitialarabic: 0xFCCC, - lammeemjeeminitialarabic: "FEDF FEE4 FEA0", - lammeemkhahinitialarabic: "FEDF FEE4 FEA8", + lammeemjeeminitialarabic: 'FEDF FEE4 FEA0', + lammeemkhahinitialarabic: 'FEDF FEE4 FEA8', largecircle: 0x25EF, lbar: 0x019A, lbelt: 0x026C, @@ -3250,7 +3250,7 @@ var GlyphsUnicode = { reharmenian: 0x0580, rehfinalarabic: 0xFEAE, rehiragana: 0x308C, - rehyehaleflamarabic: "0631 FEF3 FE8E 0644", + rehyehaleflamarabic: '0631 FEF3 FE8E 0644', rekatakana: 0x30EC, rekatakanahalfwidth: 0xFF9A, resh: 0x05E8, diff --git a/images/buttons.png b/images/buttons.png deleted file mode 100644 index 3357b47d6..000000000 Binary files a/images/buttons.png and /dev/null differ diff --git a/images/source/Buttons.psd.zip b/images/source/Buttons.psd.zip deleted file mode 100644 index 528e6ee3c..000000000 Binary files a/images/source/Buttons.psd.zip and /dev/null differ diff --git a/images/source/FileButton.psd.zip b/images/source/FileButton.psd.zip deleted file mode 100644 index 1f2b51cee..000000000 Binary files a/images/source/FileButton.psd.zip and /dev/null differ diff --git a/multi_page_viewer.css b/multi_page_viewer.css deleted file mode 100644 index 17b2537be..000000000 --- a/multi_page_viewer.css +++ /dev/null @@ -1,230 +0,0 @@ -/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- / -/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ - -body { - background-color: #929292; - font-family: 'Lucida Grande', 'Lucida Sans Unicode', Helvetica, Arial, Verdana, sans-serif; - margin: 0px; - padding: 0px; -} - -canvas { - box-shadow: 0px 4px 10px #000; - -moz-box-shadow: 0px 4px 10px #000; - -webkit-box-shadow: 0px 4px 10px #000; -} - -span { - font-size: 0.8em; -} - -.control { - display: inline-block; - float: left; - margin: 0px 20px 0px 0px; - padding: 0px 4px 0px 0px; -} - -.control > input { - float: left; - border: 1px solid #4d4d4d; - height: 20px; - padding: 0px; - margin: 0px 2px 0px 0px; - border-radius: 4px; - -moz-border-radius: 4px; - -webkit-border-radius: 4px; - box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); - -moz-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); - -webkit-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); -} - -.control > select { - float: left; - border: 1px solid #4d4d4d; - height: 22px; - padding: 2px 0px 0px; - margin: 0px 0px 1px; - border-radius: 4px; - -moz-border-radius: 4px; - -webkit-border-radius: 4px; - box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); - -moz-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); - -webkit-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); -} - -.control > span { - cursor: default; - float: left; - height: 18px; - margin: 5px 2px 0px; - padding: 0px; - user-select: none; - -moz-user-select: none; - -webkit-user-select: none; -} - -.control .label { - clear: both; - float: left; - font-size: 0.65em; - margin: 2px 0px 0px; - position: relative; - text-align: center; - width: 100%; -} - -.thumbnailPageNumber { - color: #fff; - font-size: 0.55em; - text-align: right; - margin: -6px 2px 6px 0px; - width: 102px; -} - -.thumbnail { - width: 104px; - height: 134px; - margin: 0px auto 10px; -} - -.page { - width: 816px; - height: 1056px; - margin: 10px auto; -} - -#controls { - background-color: #eee; - border-bottom: 1px solid #666; - padding: 4px 0px 0px 8px; - position: fixed; - left: 0px; - top: 0px; - height: 40px; - width: 100%; - box-shadow: 0px 2px 8px #000; - -moz-box-shadow: 0px 2px 8px #000; - -webkit-box-shadow: 0px 2px 8px #000; -} - -#controls input { - user-select: text; - -moz-user-select: text; - -webkit-user-select: text; -} - -#previousPageButton { - background: url('images/buttons.png') no-repeat 0px -23px; - cursor: default; - display: inline-block; - float: left; - margin: 0px; - width: 28px; - height: 23px; -} - -#previousPageButton.down { - background: url('images/buttons.png') no-repeat 0px -46px; -} - -#previousPageButton.disabled { - background: url('images/buttons.png') no-repeat 0px 0px; -} - -#nextPageButton { - background: url('images/buttons.png') no-repeat -28px -23px; - cursor: default; - display: inline-block; - float: left; - margin: 0px; - width: 28px; - height: 23px; -} - -#nextPageButton.down { - background: url('images/buttons.png') no-repeat -28px -46px; -} - -#nextPageButton.disabled { - background: url('images/buttons.png') no-repeat -28px 0px; -} - -#openFileButton { - background: url('images/buttons.png') no-repeat -56px -23px; - cursor: default; - display: inline-block; - float: left; - margin: 0px 0px 0px 3px; - width: 29px; - height: 23px; -} - -#openFileButton.down { - background: url('images/buttons.png') no-repeat -56px -46px; -} - -#openFileButton.disabled { - background: url('images/buttons.png') no-repeat -56px 0px; -} - -#fileInput { - display: none; -} - -#pageNumber { - text-align: right; -} - -#sidebar { - position: fixed; - width: 200px; - top: 62px; - bottom: 18px; - left: -140px; - transition: left 0.25s ease-in-out 1s; - -moz-transition: left 0.25s ease-in-out 1s; - -webkit-transition: left 0.25s ease-in-out 1s; -} - -#sidebar:hover { - left: 0px; - transition: left 0.25s ease-in-out 0s; - -moz-transition: left 0.25s ease-in-out 0s; - -webkit-transition: left 0.25s ease-in-out 0s; -} - -#sidebarBox { - background-color: rgba(0, 0, 0, 0.7); - width: 150px; - height: 100%; - border-top-right-radius: 8px; - border-bottom-right-radius: 8px; - -moz-border-radius-topright: 8px; - -moz-border-radius-bottomright: 8px; - -webkit-border-top-right-radius: 8px; - -webkit-border-bottom-right-radius: 8px; - box-shadow: 0px 2px 8px #000; - -moz-box-shadow: 0px 2px 8px #000; - -webkit-box-shadow: 0px 2px 8px #000; -} - -#sidebarScrollView { - position: absolute; - overflow: hidden; - overflow-y: auto; - top: 10px; - bottom: 10px; - left: 10px; - width: 130px; -} - -#sidebarContentView { - height: auto; - width: 100px; -} - -#viewer { - margin: 44px 0px 0px; - padding: 8px 0px; -} diff --git a/pdf.js b/pdf.js index 7e47a3c57..c450b6ddd 100644 --- a/pdf.js +++ b/pdf.js @@ -1,927 +1,991 @@ -/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- / -/* vim: set shiftwidth=4 tabstop=8 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"; +'use strict'; var ERRORS = 0, WARNINGS = 1, TODOS = 5; var verbosity = WARNINGS; function log(msg) { - if (console && console.log) - console.log(msg); - else if (print) - print(msg); + if (console && console.log) + console.log(msg); + else if (print) + print(msg); } function warn(msg) { - if (verbosity >= WARNINGS) - log("Warning: "+ msg); + if (verbosity >= WARNINGS) + log('Warning: ' + msg); } function error(msg) { - throw new Error(msg); + throw new Error(msg); } function TODO(what) { - if (verbosity >= TODOS) - log("TODO: "+ what); + if (verbosity >= TODOS) + log('TODO: ' + what); } function malformed(msg) { - error("Malformed PDF: "+ msg); + error('Malformed PDF: ' + msg); } function assert(cond, msg) { - if (!cond) - error(msg); + if (!cond) + error(msg); } // In a well-formed PDF, |cond| holds. If it doesn't, subsequent // behavior is undefined. function assertWellFormed(cond, msg) { - if (!cond) - malformed(msg); + if (!cond) + malformed(msg); } function shadow(obj, prop, value) { - Object.defineProperty(obj, prop, { value: value, enumerable: true }); + Object.defineProperty(obj, prop, { value: value, enumerable: true, configurable: true, writable: false }); return value; } function bytesToString(bytes) { - var str = ""; - var length = bytes.length; - for (var n = 0; n < length; ++n) - str += String.fromCharCode(bytes[n]); - return str; + var str = ''; + var length = bytes.length; + for (var n = 0; n < length; ++n) + str += String.fromCharCode(bytes[n]); + return str; } function stringToBytes(str) { - var length = str.length; - var bytes = new Uint8Array(length); - for (var n = 0; n < length; ++n) - bytes[n] = str.charCodeAt(n) & 0xFF; - return bytes; + var length = str.length; + var bytes = new Uint8Array(length); + for (var n = 0; n < length; ++n) + bytes[n] = str.charCodeAt(n) & 0xFF; + return bytes; } var Stream = (function() { - function constructor(arrayBuffer, start, length, dict) { - this.bytes = new Uint8Array(arrayBuffer); - this.start = start || 0; - this.pos = this.start; - this.end = (start + length) || this.bytes.length; - this.dict = dict; + function constructor(arrayBuffer, start, length, dict) { + this.bytes = new Uint8Array(arrayBuffer); + this.start = start || 0; + this.pos = this.start; + this.end = (start + length) || this.bytes.length; + this.dict = dict; + } + + // required methods for a stream. if a particular stream does not + // implement these, an error should be thrown + constructor.prototype = { + get length() { + return this.end - this.start; + }, + getByte: function stream_getByte() { + if (this.pos >= this.end) + return null; + return this.bytes[this.pos++]; + }, + // returns subarray of original buffer + // should only be read + getBytes: function stream_getBytes(length) { + var bytes = this.bytes; + var pos = this.pos; + var strEnd = this.end; + + if (!length) + return bytes.subarray(pos, strEnd); + + var end = pos + length; + if (end > strEnd) + end = strEnd; + + this.pos = end; + return bytes.subarray(pos, end); + }, + lookChar: function stream_lookChar() { + if (this.pos >= this.end) + return null; + return String.fromCharCode(this.bytes[this.pos]); + }, + getChar: function stream_getChar() { + if (this.pos >= this.end) + return null; + return String.fromCharCode(this.bytes[this.pos++]); + }, + skip: function stream_skip(n) { + if (!n) + n = 1; + this.pos += n; + }, + reset: function stream_reset() { + this.pos = this.start; + }, + moveStart: function stream_moveStart() { + this.start = this.pos; + }, + makeSubStream: function stream_makeSubstream(start, length, dict) { + return new Stream(this.bytes.buffer, start, length, dict); } + }; - // required methods for a stream. if a particular stream does not - // implement these, an error should be thrown - constructor.prototype = { - get length() { - return this.end - this.start; - }, - getByte: function stream_getByte() { - if (this.pos >= this.end) - return null; - return this.bytes[this.pos++]; - }, - // returns subarray of original buffer - // should only be read - getBytes: function stream_getBytes(length) { - var bytes = this.bytes; - var pos = this.pos; - var strEnd = this.end; - - if (!length) - return bytes.subarray(pos, strEnd); - - var end = pos + length; - if (end > strEnd) - end = strEnd; - - this.pos = end; - return bytes.subarray(pos, end); - }, - lookChar: function stream_lookChar() { - if (this.pos >= this.end) - return null; - return String.fromCharCode(this.bytes[this.pos]); - }, - getChar: function stream_getChar() { - if (this.pos >= this.end) - return null; - return String.fromCharCode(this.bytes[this.pos++]); - }, - skip: function stream_skip(n) { - if (!n) - n = 1; - this.pos += n; - }, - reset: function stream_reset() { - this.pos = this.start; - }, - moveStart: function stream_moveStart() { - this.start = this.pos; - }, - makeSubStream: function stream_makeSubstream(start, length, dict) { - return new Stream(this.bytes.buffer, start, length, dict); - } - }; - - return constructor; + return constructor; })(); var StringStream = (function() { - function constructor(str) { - var length = str.length; - var bytes = new Uint8Array(length); - for (var n = 0; n < length; ++n) - bytes[n] = str.charCodeAt(n); - Stream.call(this, bytes); - } + function constructor(str) { + var length = str.length; + var bytes = new Uint8Array(length); + for (var n = 0; n < length; ++n) + bytes[n] = str.charCodeAt(n); + Stream.call(this, bytes); + } - constructor.prototype = Stream.prototype; + constructor.prototype = Stream.prototype; - return constructor; + return constructor; })(); // super class for the decoding streams var DecodeStream = (function() { - function constructor() { - this.pos = 0; - this.bufferLength = 0; - this.eof = false; - this.buffer = null; + function constructor() { + this.pos = 0; + this.bufferLength = 0; + this.eof = false; + this.buffer = null; + } + + constructor.prototype = { + ensureBuffer: function decodestream_ensureBuffer(requested) { + var buffer = this.buffer; + var current = buffer ? buffer.byteLength : 0; + if (requested < current) + return buffer; + var size = 512; + while (size < requested) + size <<= 1; + var buffer2 = new Uint8Array(size); + for (var i = 0; i < current; ++i) + buffer2[i] = buffer[i]; + return this.buffer = buffer2; + }, + getByte: function decodestream_getByte() { + var pos = this.pos; + while (this.bufferLength <= pos) { + if (this.eof) + return null; + this.readBlock(); + } + return this.buffer[this.pos++]; + }, + getBytes: function decodestream_getBytes(length) { + var pos = this.pos; + + if (length) { + this.ensureBuffer(pos + length); + var end = pos + length; + + while (!this.eof && this.bufferLength < end) + this.readBlock(); + + var bufEnd = this.bufferLength; + if (end > bufEnd) + end = bufEnd; + } else { + while (!this.eof) + this.readBlock(); + + var end = this.bufferLength; + } + + this.pos = end; + return this.buffer.subarray(pos, end); + }, + lookChar: function decodestream_lookChar() { + var pos = this.pos; + while (this.bufferLength <= pos) { + if (this.eof) + return null; + this.readBlock(); + } + return String.fromCharCode(this.buffer[this.pos]); + }, + getChar: function decodestream_getChar() { + var pos = this.pos; + while (this.bufferLength <= pos) { + if (this.eof) + return null; + this.readBlock(); + } + return String.fromCharCode(this.buffer[this.pos++]); + }, + skip: function decodestream_skip(n) { + if (!n) + n = 1; + this.pos += n; } - - constructor.prototype = { - ensureBuffer: function decodestream_ensureBuffer(requested) { - var buffer = this.buffer; - var current = buffer ? buffer.byteLength : 0; - if (requested < current) - return buffer; - var size = 512; - while (size < requested) - size <<= 1; - var buffer2 = new Uint8Array(size); - for (var i = 0; i < current; ++i) - buffer2[i] = buffer[i]; - return this.buffer = buffer2; - }, - getByte: function decodestream_getByte() { - var pos = this.pos; - while (this.bufferLength <= pos) { - if (this.eof) - return null; - this.readBlock(); - } - return this.buffer[this.pos++]; - }, - getBytes: function decodestream_getBytes(length) { - var pos = this.pos; + }; - if (length) { - this.ensureBuffer(pos + length); - var end = pos + length; - - while (!this.eof && this.bufferLength < end) - this.readBlock(); - - var bufEnd = this.bufferLength; - if (end > bufEnd) - end = bufEnd; - } else { - while (!this.eof) - this.readBlock(); - - var end = this.bufferLength; - } - - this.pos = end; - return this.buffer.subarray(pos, end) - }, - lookChar: function decodestream_lookChar() { - var pos = this.pos; - while (this.bufferLength <= pos) { - if (this.eof) - return null; - this.readBlock(); - } - return String.fromCharCode(this.buffer[this.pos]); - }, - getChar: function decodestream_getChar() { - var pos = this.pos; - while (this.bufferLength <= pos) { - if (this.eof) - return null; - this.readBlock(); - } - return String.fromCharCode(this.buffer[this.pos++]); - }, - skip: function decodestream_skip(n) { - if (!n) - n = 1; - this.pos += n; - } - }; - - return constructor; + return constructor; })(); - var FakeStream = (function() { - function constructor(stream) { - this.dict = stream.dict; - DecodeStream.call(this); - }; + function constructor(stream) { + this.dict = stream.dict; + DecodeStream.call(this); + }; - constructor.prototype = Object.create(DecodeStream.prototype); - constructor.prototype.readBlock = function() { - var bufferLength = this.bufferLength; - bufferLength += 1024; - var buffer = this.ensureBuffer(bufferLength); - this.bufferLength = bufferLength; - }; - constructor.prototype.getBytes = function(length) { - var pos = this.pos; + constructor.prototype = Object.create(DecodeStream.prototype); + constructor.prototype.readBlock = function() { + var bufferLength = this.bufferLength; + bufferLength += 1024; + var buffer = this.ensureBuffer(bufferLength); + this.bufferLength = bufferLength; + }; - if (length) { - this.ensureBuffer(pos + length); - var end = pos + length; + constructor.prototype.getBytes = function(length) { + var pos = this.pos; - while (!this.eof && this.bufferLength < end) - this.readBlock(); + if (length) { + this.ensureBuffer(pos + length); + var end = pos + length; - var bufEnd = this.bufferLength; - if (end > bufEnd) - end = bufEnd; - } else { - this.eof = true; - var end = this.bufferLength; - } + while (!this.eof && this.bufferLength < end) + this.readBlock(); - this.pos = end; - return this.buffer.subarray(pos, end) - }; + var bufEnd = this.bufferLength; + if (end > bufEnd) + end = bufEnd; + } else { + this.eof = true; + var end = this.bufferLength; + } - return constructor; + this.pos = end; + return this.buffer.subarray(pos, end); + }; + + return constructor; })(); var FlateStream = (function() { - var codeLenCodeMap = new Uint32Array([ - 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 - ]); + var codeLenCodeMap = new Uint32Array([ + 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 + ]); - var lengthDecode = new Uint32Array([ - 0x00003, 0x00004, 0x00005, 0x00006, 0x00007, 0x00008, 0x00009, - 0x0000a, 0x1000b, 0x1000d, 0x1000f, 0x10011, 0x20013, 0x20017, - 0x2001b, 0x2001f, 0x30023, 0x3002b, 0x30033, 0x3003b, 0x40043, - 0x40053, 0x40063, 0x40073, 0x50083, 0x500a3, 0x500c3, 0x500e3, - 0x00102, 0x00102, 0x00102 - ]); + var lengthDecode = new Uint32Array([ + 0x00003, 0x00004, 0x00005, 0x00006, 0x00007, 0x00008, 0x00009, 0x0000a, + 0x1000b, 0x1000d, 0x1000f, 0x10011, 0x20013, 0x20017, 0x2001b, 0x2001f, + 0x30023, 0x3002b, 0x30033, 0x3003b, 0x40043, 0x40053, 0x40063, 0x40073, + 0x50083, 0x500a3, 0x500c3, 0x500e3, 0x00102, 0x00102, 0x00102 + ]); - var distDecode = new Uint32Array([ - 0x00001, 0x00002, 0x00003, 0x00004, 0x10005, 0x10007, 0x20009, - 0x2000d, 0x30011, 0x30019, 0x40021, 0x40031, 0x50041, 0x50061, - 0x60081, 0x600c1, 0x70101, 0x70181, 0x80201, 0x80301, 0x90401, - 0x90601, 0xa0801, 0xa0c01, 0xb1001, 0xb1801, 0xc2001, 0xc3001, - 0xd4001, 0xd6001 - ]); + var distDecode = new Uint32Array([ + 0x00001, 0x00002, 0x00003, 0x00004, 0x10005, 0x10007, 0x20009, 0x2000d, + 0x30011, 0x30019, 0x40021, 0x40031, 0x50041, 0x50061, 0x60081, 0x600c1, + 0x70101, 0x70181, 0x80201, 0x80301, 0x90401, 0x90601, 0xa0801, 0xa0c01, + 0xb1001, 0xb1801, 0xc2001, 0xc3001, 0xd4001, 0xd6001 + ]); - var fixedLitCodeTab = [new Uint32Array([ - 0x70100, 0x80050, 0x80010, 0x80118, 0x70110, 0x80070, 0x80030, - 0x900c0, 0x70108, 0x80060, 0x80020, 0x900a0, 0x80000, 0x80080, - 0x80040, 0x900e0, 0x70104, 0x80058, 0x80018, 0x90090, 0x70114, - 0x80078, 0x80038, 0x900d0, 0x7010c, 0x80068, 0x80028, 0x900b0, - 0x80008, 0x80088, 0x80048, 0x900f0, 0x70102, 0x80054, 0x80014, - 0x8011c, 0x70112, 0x80074, 0x80034, 0x900c8, 0x7010a, 0x80064, - 0x80024, 0x900a8, 0x80004, 0x80084, 0x80044, 0x900e8, 0x70106, - 0x8005c, 0x8001c, 0x90098, 0x70116, 0x8007c, 0x8003c, 0x900d8, - 0x7010e, 0x8006c, 0x8002c, 0x900b8, 0x8000c, 0x8008c, 0x8004c, - 0x900f8, 0x70101, 0x80052, 0x80012, 0x8011a, 0x70111, 0x80072, - 0x80032, 0x900c4, 0x70109, 0x80062, 0x80022, 0x900a4, 0x80002, - 0x80082, 0x80042, 0x900e4, 0x70105, 0x8005a, 0x8001a, 0x90094, - 0x70115, 0x8007a, 0x8003a, 0x900d4, 0x7010d, 0x8006a, 0x8002a, - 0x900b4, 0x8000a, 0x8008a, 0x8004a, 0x900f4, 0x70103, 0x80056, - 0x80016, 0x8011e, 0x70113, 0x80076, 0x80036, 0x900cc, 0x7010b, - 0x80066, 0x80026, 0x900ac, 0x80006, 0x80086, 0x80046, 0x900ec, - 0x70107, 0x8005e, 0x8001e, 0x9009c, 0x70117, 0x8007e, 0x8003e, - 0x900dc, 0x7010f, 0x8006e, 0x8002e, 0x900bc, 0x8000e, 0x8008e, - 0x8004e, 0x900fc, 0x70100, 0x80051, 0x80011, 0x80119, 0x70110, - 0x80071, 0x80031, 0x900c2, 0x70108, 0x80061, 0x80021, 0x900a2, - 0x80001, 0x80081, 0x80041, 0x900e2, 0x70104, 0x80059, 0x80019, - 0x90092, 0x70114, 0x80079, 0x80039, 0x900d2, 0x7010c, 0x80069, - 0x80029, 0x900b2, 0x80009, 0x80089, 0x80049, 0x900f2, 0x70102, - 0x80055, 0x80015, 0x8011d, 0x70112, 0x80075, 0x80035, 0x900ca, - 0x7010a, 0x80065, 0x80025, 0x900aa, 0x80005, 0x80085, 0x80045, - 0x900ea, 0x70106, 0x8005d, 0x8001d, 0x9009a, 0x70116, 0x8007d, - 0x8003d, 0x900da, 0x7010e, 0x8006d, 0x8002d, 0x900ba, 0x8000d, - 0x8008d, 0x8004d, 0x900fa, 0x70101, 0x80053, 0x80013, 0x8011b, - 0x70111, 0x80073, 0x80033, 0x900c6, 0x70109, 0x80063, 0x80023, - 0x900a6, 0x80003, 0x80083, 0x80043, 0x900e6, 0x70105, 0x8005b, - 0x8001b, 0x90096, 0x70115, 0x8007b, 0x8003b, 0x900d6, 0x7010d, - 0x8006b, 0x8002b, 0x900b6, 0x8000b, 0x8008b, 0x8004b, 0x900f6, - 0x70103, 0x80057, 0x80017, 0x8011f, 0x70113, 0x80077, 0x80037, - 0x900ce, 0x7010b, 0x80067, 0x80027, 0x900ae, 0x80007, 0x80087, - 0x80047, 0x900ee, 0x70107, 0x8005f, 0x8001f, 0x9009e, 0x70117, - 0x8007f, 0x8003f, 0x900de, 0x7010f, 0x8006f, 0x8002f, 0x900be, - 0x8000f, 0x8008f, 0x8004f, 0x900fe, 0x70100, 0x80050, 0x80010, - 0x80118, 0x70110, 0x80070, 0x80030, 0x900c1, 0x70108, 0x80060, - 0x80020, 0x900a1, 0x80000, 0x80080, 0x80040, 0x900e1, 0x70104, - 0x80058, 0x80018, 0x90091, 0x70114, 0x80078, 0x80038, 0x900d1, - 0x7010c, 0x80068, 0x80028, 0x900b1, 0x80008, 0x80088, 0x80048, - 0x900f1, 0x70102, 0x80054, 0x80014, 0x8011c, 0x70112, 0x80074, - 0x80034, 0x900c9, 0x7010a, 0x80064, 0x80024, 0x900a9, 0x80004, - 0x80084, 0x80044, 0x900e9, 0x70106, 0x8005c, 0x8001c, 0x90099, - 0x70116, 0x8007c, 0x8003c, 0x900d9, 0x7010e, 0x8006c, 0x8002c, - 0x900b9, 0x8000c, 0x8008c, 0x8004c, 0x900f9, 0x70101, 0x80052, - 0x80012, 0x8011a, 0x70111, 0x80072, 0x80032, 0x900c5, 0x70109, - 0x80062, 0x80022, 0x900a5, 0x80002, 0x80082, 0x80042, 0x900e5, - 0x70105, 0x8005a, 0x8001a, 0x90095, 0x70115, 0x8007a, 0x8003a, - 0x900d5, 0x7010d, 0x8006a, 0x8002a, 0x900b5, 0x8000a, 0x8008a, - 0x8004a, 0x900f5, 0x70103, 0x80056, 0x80016, 0x8011e, 0x70113, - 0x80076, 0x80036, 0x900cd, 0x7010b, 0x80066, 0x80026, 0x900ad, - 0x80006, 0x80086, 0x80046, 0x900ed, 0x70107, 0x8005e, 0x8001e, - 0x9009d, 0x70117, 0x8007e, 0x8003e, 0x900dd, 0x7010f, 0x8006e, - 0x8002e, 0x900bd, 0x8000e, 0x8008e, 0x8004e, 0x900fd, 0x70100, - 0x80051, 0x80011, 0x80119, 0x70110, 0x80071, 0x80031, 0x900c3, - 0x70108, 0x80061, 0x80021, 0x900a3, 0x80001, 0x80081, 0x80041, - 0x900e3, 0x70104, 0x80059, 0x80019, 0x90093, 0x70114, 0x80079, - 0x80039, 0x900d3, 0x7010c, 0x80069, 0x80029, 0x900b3, 0x80009, - 0x80089, 0x80049, 0x900f3, 0x70102, 0x80055, 0x80015, 0x8011d, - 0x70112, 0x80075, 0x80035, 0x900cb, 0x7010a, 0x80065, 0x80025, - 0x900ab, 0x80005, 0x80085, 0x80045, 0x900eb, 0x70106, 0x8005d, - 0x8001d, 0x9009b, 0x70116, 0x8007d, 0x8003d, 0x900db, 0x7010e, - 0x8006d, 0x8002d, 0x900bb, 0x8000d, 0x8008d, 0x8004d, 0x900fb, - 0x70101, 0x80053, 0x80013, 0x8011b, 0x70111, 0x80073, 0x80033, - 0x900c7, 0x70109, 0x80063, 0x80023, 0x900a7, 0x80003, 0x80083, - 0x80043, 0x900e7, 0x70105, 0x8005b, 0x8001b, 0x90097, 0x70115, - 0x8007b, 0x8003b, 0x900d7, 0x7010d, 0x8006b, 0x8002b, 0x900b7, - 0x8000b, 0x8008b, 0x8004b, 0x900f7, 0x70103, 0x80057, 0x80017, - 0x8011f, 0x70113, 0x80077, 0x80037, 0x900cf, 0x7010b, 0x80067, - 0x80027, 0x900af, 0x80007, 0x80087, 0x80047, 0x900ef, 0x70107, - 0x8005f, 0x8001f, 0x9009f, 0x70117, 0x8007f, 0x8003f, 0x900df, - 0x7010f, 0x8006f, 0x8002f, 0x900bf, 0x8000f, 0x8008f, 0x8004f, - 0x900ff - ]), 9]; + var fixedLitCodeTab = [new Uint32Array([ + 0x70100, 0x80050, 0x80010, 0x80118, 0x70110, 0x80070, 0x80030, 0x900c0, + 0x70108, 0x80060, 0x80020, 0x900a0, 0x80000, 0x80080, 0x80040, 0x900e0, + 0x70104, 0x80058, 0x80018, 0x90090, 0x70114, 0x80078, 0x80038, 0x900d0, + 0x7010c, 0x80068, 0x80028, 0x900b0, 0x80008, 0x80088, 0x80048, 0x900f0, + 0x70102, 0x80054, 0x80014, 0x8011c, 0x70112, 0x80074, 0x80034, 0x900c8, + 0x7010a, 0x80064, 0x80024, 0x900a8, 0x80004, 0x80084, 0x80044, 0x900e8, + 0x70106, 0x8005c, 0x8001c, 0x90098, 0x70116, 0x8007c, 0x8003c, 0x900d8, + 0x7010e, 0x8006c, 0x8002c, 0x900b8, 0x8000c, 0x8008c, 0x8004c, 0x900f8, + 0x70101, 0x80052, 0x80012, 0x8011a, 0x70111, 0x80072, 0x80032, 0x900c4, + 0x70109, 0x80062, 0x80022, 0x900a4, 0x80002, 0x80082, 0x80042, 0x900e4, + 0x70105, 0x8005a, 0x8001a, 0x90094, 0x70115, 0x8007a, 0x8003a, 0x900d4, + 0x7010d, 0x8006a, 0x8002a, 0x900b4, 0x8000a, 0x8008a, 0x8004a, 0x900f4, + 0x70103, 0x80056, 0x80016, 0x8011e, 0x70113, 0x80076, 0x80036, 0x900cc, + 0x7010b, 0x80066, 0x80026, 0x900ac, 0x80006, 0x80086, 0x80046, 0x900ec, + 0x70107, 0x8005e, 0x8001e, 0x9009c, 0x70117, 0x8007e, 0x8003e, 0x900dc, + 0x7010f, 0x8006e, 0x8002e, 0x900bc, 0x8000e, 0x8008e, 0x8004e, 0x900fc, + 0x70100, 0x80051, 0x80011, 0x80119, 0x70110, 0x80071, 0x80031, 0x900c2, + 0x70108, 0x80061, 0x80021, 0x900a2, 0x80001, 0x80081, 0x80041, 0x900e2, + 0x70104, 0x80059, 0x80019, 0x90092, 0x70114, 0x80079, 0x80039, 0x900d2, + 0x7010c, 0x80069, 0x80029, 0x900b2, 0x80009, 0x80089, 0x80049, 0x900f2, + 0x70102, 0x80055, 0x80015, 0x8011d, 0x70112, 0x80075, 0x80035, 0x900ca, + 0x7010a, 0x80065, 0x80025, 0x900aa, 0x80005, 0x80085, 0x80045, 0x900ea, + 0x70106, 0x8005d, 0x8001d, 0x9009a, 0x70116, 0x8007d, 0x8003d, 0x900da, + 0x7010e, 0x8006d, 0x8002d, 0x900ba, 0x8000d, 0x8008d, 0x8004d, 0x900fa, + 0x70101, 0x80053, 0x80013, 0x8011b, 0x70111, 0x80073, 0x80033, 0x900c6, + 0x70109, 0x80063, 0x80023, 0x900a6, 0x80003, 0x80083, 0x80043, 0x900e6, + 0x70105, 0x8005b, 0x8001b, 0x90096, 0x70115, 0x8007b, 0x8003b, 0x900d6, + 0x7010d, 0x8006b, 0x8002b, 0x900b6, 0x8000b, 0x8008b, 0x8004b, 0x900f6, + 0x70103, 0x80057, 0x80017, 0x8011f, 0x70113, 0x80077, 0x80037, 0x900ce, + 0x7010b, 0x80067, 0x80027, 0x900ae, 0x80007, 0x80087, 0x80047, 0x900ee, + 0x70107, 0x8005f, 0x8001f, 0x9009e, 0x70117, 0x8007f, 0x8003f, 0x900de, + 0x7010f, 0x8006f, 0x8002f, 0x900be, 0x8000f, 0x8008f, 0x8004f, 0x900fe, + 0x70100, 0x80050, 0x80010, 0x80118, 0x70110, 0x80070, 0x80030, 0x900c1, + 0x70108, 0x80060, 0x80020, 0x900a1, 0x80000, 0x80080, 0x80040, 0x900e1, + 0x70104, 0x80058, 0x80018, 0x90091, 0x70114, 0x80078, 0x80038, 0x900d1, + 0x7010c, 0x80068, 0x80028, 0x900b1, 0x80008, 0x80088, 0x80048, 0x900f1, + 0x70102, 0x80054, 0x80014, 0x8011c, 0x70112, 0x80074, 0x80034, 0x900c9, + 0x7010a, 0x80064, 0x80024, 0x900a9, 0x80004, 0x80084, 0x80044, 0x900e9, + 0x70106, 0x8005c, 0x8001c, 0x90099, 0x70116, 0x8007c, 0x8003c, 0x900d9, + 0x7010e, 0x8006c, 0x8002c, 0x900b9, 0x8000c, 0x8008c, 0x8004c, 0x900f9, + 0x70101, 0x80052, 0x80012, 0x8011a, 0x70111, 0x80072, 0x80032, 0x900c5, + 0x70109, 0x80062, 0x80022, 0x900a5, 0x80002, 0x80082, 0x80042, 0x900e5, + 0x70105, 0x8005a, 0x8001a, 0x90095, 0x70115, 0x8007a, 0x8003a, 0x900d5, + 0x7010d, 0x8006a, 0x8002a, 0x900b5, 0x8000a, 0x8008a, 0x8004a, 0x900f5, + 0x70103, 0x80056, 0x80016, 0x8011e, 0x70113, 0x80076, 0x80036, 0x900cd, + 0x7010b, 0x80066, 0x80026, 0x900ad, 0x80006, 0x80086, 0x80046, 0x900ed, + 0x70107, 0x8005e, 0x8001e, 0x9009d, 0x70117, 0x8007e, 0x8003e, 0x900dd, + 0x7010f, 0x8006e, 0x8002e, 0x900bd, 0x8000e, 0x8008e, 0x8004e, 0x900fd, + 0x70100, 0x80051, 0x80011, 0x80119, 0x70110, 0x80071, 0x80031, 0x900c3, + 0x70108, 0x80061, 0x80021, 0x900a3, 0x80001, 0x80081, 0x80041, 0x900e3, + 0x70104, 0x80059, 0x80019, 0x90093, 0x70114, 0x80079, 0x80039, 0x900d3, + 0x7010c, 0x80069, 0x80029, 0x900b3, 0x80009, 0x80089, 0x80049, 0x900f3, + 0x70102, 0x80055, 0x80015, 0x8011d, 0x70112, 0x80075, 0x80035, 0x900cb, + 0x7010a, 0x80065, 0x80025, 0x900ab, 0x80005, 0x80085, 0x80045, 0x900eb, + 0x70106, 0x8005d, 0x8001d, 0x9009b, 0x70116, 0x8007d, 0x8003d, 0x900db, + 0x7010e, 0x8006d, 0x8002d, 0x900bb, 0x8000d, 0x8008d, 0x8004d, 0x900fb, + 0x70101, 0x80053, 0x80013, 0x8011b, 0x70111, 0x80073, 0x80033, 0x900c7, + 0x70109, 0x80063, 0x80023, 0x900a7, 0x80003, 0x80083, 0x80043, 0x900e7, + 0x70105, 0x8005b, 0x8001b, 0x90097, 0x70115, 0x8007b, 0x8003b, 0x900d7, + 0x7010d, 0x8006b, 0x8002b, 0x900b7, 0x8000b, 0x8008b, 0x8004b, 0x900f7, + 0x70103, 0x80057, 0x80017, 0x8011f, 0x70113, 0x80077, 0x80037, 0x900cf, + 0x7010b, 0x80067, 0x80027, 0x900af, 0x80007, 0x80087, 0x80047, 0x900ef, + 0x70107, 0x8005f, 0x8001f, 0x9009f, 0x70117, 0x8007f, 0x8003f, 0x900df, + 0x7010f, 0x8006f, 0x8002f, 0x900bf, 0x8000f, 0x8008f, 0x8004f, 0x900ff + ]), 9]; - var fixedDistCodeTab = [new Uint32Array([ - 0x50000, 0x50010, 0x50008, 0x50018, 0x50004, 0x50014, 0x5000c, - 0x5001c, 0x50002, 0x50012, 0x5000a, 0x5001a, 0x50006, 0x50016, - 0x5000e, 0x00000, 0x50001, 0x50011, 0x50009, 0x50019, 0x50005, - 0x50015, 0x5000d, 0x5001d, 0x50003, 0x50013, 0x5000b, 0x5001b, - 0x50007, 0x50017, 0x5000f, 0x00000 - ]), 5]; + var fixedDistCodeTab = [new Uint32Array([ + 0x50000, 0x50010, 0x50008, 0x50018, 0x50004, 0x50014, 0x5000c, 0x5001c, + 0x50002, 0x50012, 0x5000a, 0x5001a, 0x50006, 0x50016, 0x5000e, 0x00000, + 0x50001, 0x50011, 0x50009, 0x50019, 0x50005, 0x50015, 0x5000d, 0x5001d, + 0x50003, 0x50013, 0x5000b, 0x5001b, 0x50007, 0x50017, 0x5000f, 0x00000 + ]), 5]; - function constructor(stream) { - var bytes = stream.getBytes(); - var bytesPos = 0; + function constructor(stream) { + var bytes = stream.getBytes(); + var bytesPos = 0; - this.dict = stream.dict; - var cmf = bytes[bytesPos++]; - var flg = bytes[bytesPos++]; - if (cmf == -1 || flg == -1) - error("Invalid header in flate stream"); - if ((cmf & 0x0f) != 0x08) - error("Unknown compression method in flate stream"); - if ((((cmf << 8) + flg) % 31) != 0) - error("Bad FCHECK in flate stream"); - if (flg & 0x20) - error("FDICT bit set in flate stream"); + this.dict = stream.dict; + var cmf = bytes[bytesPos++]; + var flg = bytes[bytesPos++]; + if (cmf == -1 || flg == -1) + error('Invalid header in flate stream'); + if ((cmf & 0x0f) != 0x08) + error('Unknown compression method in flate stream'); + if ((((cmf << 8) + flg) % 31) != 0) + error('Bad FCHECK in flate stream'); + if (flg & 0x20) + error('FDICT bit set in flate stream'); - this.bytes = bytes; - this.bytesPos = bytesPos; + this.bytes = bytes; + this.bytesPos = bytesPos; - this.codeSize = 0; - this.codeBuf = 0; - - DecodeStream.call(this); + this.codeSize = 0; + this.codeBuf = 0; + + DecodeStream.call(this); + } + + constructor.prototype = Object.create(DecodeStream.prototype); + + constructor.prototype.getBits = function(bits) { + var codeSize = this.codeSize; + var codeBuf = this.codeBuf; + var bytes = this.bytes; + var bytesPos = this.bytesPos; + + var b; + while (codeSize < bits) { + if (typeof (b = bytes[bytesPos++]) == 'undefined') + error('Bad encoding in flate stream'); + codeBuf |= b << codeSize; + codeSize += 8; + } + b = codeBuf & ((1 << bits) - 1); + this.codeBuf = codeBuf >> bits; + this.codeSize = codeSize -= bits; + this.bytesPos = bytesPos; + return b; + }; + + constructor.prototype.getCode = function(table) { + var codes = table[0]; + var maxLen = table[1]; + var codeSize = this.codeSize; + var codeBuf = this.codeBuf; + var bytes = this.bytes; + var bytesPos = this.bytesPos; + + while (codeSize < maxLen) { + var b; + if (typeof (b = bytes[bytesPos++]) == 'undefined') + error('Bad encoding in flate stream'); + codeBuf |= (b << codeSize); + codeSize += 8; + } + var code = codes[codeBuf & ((1 << maxLen) - 1)]; + var codeLen = code >> 16; + var codeVal = code & 0xffff; + if (codeSize == 0 || codeSize < codeLen || codeLen == 0) + error('Bad encoding in flate stream'); + this.codeBuf = (codeBuf >> codeLen); + this.codeSize = (codeSize - codeLen); + this.bytesPos = bytesPos; + return codeVal; + }; + + constructor.prototype.generateHuffmanTable = function(lengths) { + var n = lengths.length; + + // find max code length + var maxLen = 0; + for (var i = 0; i < n; ++i) { + if (lengths[i] > maxLen) + maxLen = lengths[i]; } - constructor.prototype = Object.create(DecodeStream.prototype); + // build the table + var size = 1 << maxLen; + var codes = new Uint32Array(size); + for (var len = 1, code = 0, skip = 2; + len <= maxLen; + ++len, code <<= 1, skip <<= 1) { + for (var val = 0; val < n; ++val) { + if (lengths[val] == len) { + // bit-reverse the code + var code2 = 0; + var t = code; + for (var i = 0; i < len; ++i) { + code2 = (code2 << 1) | (t & 1); + t >>= 1; + } - constructor.prototype.getBits = function(bits) { - var codeSize = this.codeSize; - var codeBuf = this.codeBuf; - var bytes = this.bytes; - var bytesPos = this.bytesPos; + // fill the table entries + for (var i = code2; i < size; i += skip) + codes[i] = (len << 16) | val; - var b; - while (codeSize < bits) { - if (typeof (b = bytes[bytesPos++]) == "undefined") - error("Bad encoding in flate stream"); - codeBuf |= b << codeSize; - codeSize += 8; + ++code; } - b = codeBuf & ((1 << bits) - 1); - this.codeBuf = codeBuf >> bits; - this.codeSize = codeSize -= bits; - this.bytesPos = bytesPos; - return b; - }; - constructor.prototype.getCode = function(table) { - var codes = table[0]; - var maxLen = table[1]; - var codeSize = this.codeSize; - var codeBuf = this.codeBuf; - var bytes = this.bytes; - var bytesPos = this.bytesPos; + } + } - while (codeSize < maxLen) { - var b; - if (typeof (b = bytes[bytesPos++]) == "undefined") - error("Bad encoding in flate stream"); - codeBuf |= (b << codeSize); - codeSize += 8; + return [codes, maxLen]; + }; + + constructor.prototype.readBlock = function() { + function repeat(stream, array, len, offset, what) { + var repeat = stream.getBits(len) + offset; + while (repeat-- > 0) + array[i++] = what; + } + + // read block header + var hdr = this.getBits(3); + if (hdr & 1) + this.eof = true; + hdr >>= 1; + + if (hdr == 0) { // uncompressed block + var bytes = this.bytes; + var bytesPos = this.bytesPos; + var b; + + if (typeof (b = bytes[bytesPos++]) == 'undefined') + error('Bad block header in flate stream'); + var blockLen = b; + if (typeof (b = bytes[bytesPos++]) == 'undefined') + error('Bad block header in flate stream'); + blockLen |= (b << 8); + if (typeof (b = bytes[bytesPos++]) == 'undefined') + error('Bad block header in flate stream'); + var check = b; + if (typeof (b = bytes[bytesPos++]) == 'undefined') + error('Bad block header in flate stream'); + check |= (b << 8); + if (check != (~blockLen & 0xffff)) + error('Bad uncompressed block length in flate stream'); + + this.codeBuf = 0; + this.codeSize = 0; + + var bufferLength = this.bufferLength; + var buffer = this.ensureBuffer(bufferLength + blockLen); + var end = bufferLength + blockLen; + this.bufferLength = end; + for (var n = bufferLength; n < end; ++n) { + if (typeof (b = bytes[bytesPos++]) == 'undefined') { + this.eof = true; + break; } - var code = codes[codeBuf & ((1 << maxLen) - 1)]; - var codeLen = code >> 16; - var codeVal = code & 0xffff; - if (codeSize == 0|| codeSize < codeLen || codeLen == 0) - error("Bad encoding in flate stream"); - this.codeBuf = (codeBuf >> codeLen); - this.codeSize = (codeSize - codeLen); - this.bytesPos = bytesPos; - return codeVal; - }; - constructor.prototype.generateHuffmanTable = function(lengths) { - var n = lengths.length; + buffer[n] = b; + } + this.bytesPos = bytesPos; + return; + } - // find max code length - var maxLen = 0; - for (var i = 0; i < n; ++i) { - if (lengths[i] > maxLen) - maxLen = lengths[i]; - } + var litCodeTable; + var distCodeTable; + if (hdr == 1) { // compressed block, fixed codes + litCodeTable = fixedLitCodeTab; + distCodeTable = fixedDistCodeTab; + } else if (hdr == 2) { // compressed block, dynamic codes + var numLitCodes = this.getBits(5) + 257; + var numDistCodes = this.getBits(5) + 1; + var numCodeLenCodes = this.getBits(4) + 4; - // build the table - var size = 1 << maxLen; - var codes = new Uint32Array(size); - for (var len = 1, code = 0, skip = 2; - len <= maxLen; - ++len, code <<= 1, skip <<= 1) { - for (var val = 0; val < n; ++val) { - if (lengths[val] == len) { - // bit-reverse the code - var code2 = 0; - var t = code; - for (var i = 0; i < len; ++i) { - code2 = (code2 << 1) | (t & 1); - t >>= 1; - } + // build the code lengths code table + var codeLenCodeLengths = Array(codeLenCodeMap.length); + var i = 0; + while (i < numCodeLenCodes) + codeLenCodeLengths[codeLenCodeMap[i++]] = this.getBits(3); + var codeLenCodeTab = this.generateHuffmanTable(codeLenCodeLengths); - // fill the table entries - for (var i = code2; i < size; i += skip) - codes[i] = (len << 16) | val; - - ++code; - } - } - } - - return [codes, maxLen]; - }; - constructor.prototype.readBlock = function() { - function repeat(stream, array, len, offset, what) { - var repeat = stream.getBits(len) + offset; - while (repeat-- > 0) - array[i++] = what; - } - - // read block header - var hdr = this.getBits(3); - if (hdr & 1) - this.eof = true; - hdr >>= 1; - - if (hdr == 0) { // uncompressed block - var bytes = this.bytes; - var bytesPos = this.bytesPos; - var b; - - if (typeof (b = bytes[bytesPos++]) == "undefined") - error("Bad block header in flate stream"); - var blockLen = b; - if (typeof (b = bytes[bytesPos++]) == "undefined") - error("Bad block header in flate stream"); - blockLen |= (b << 8); - if (typeof (b = bytes[bytesPos++]) == "undefined") - error("Bad block header in flate stream"); - var check = b; - if (typeof (b = bytes[bytesPos++]) == "undefined") - error("Bad block header in flate stream"); - check |= (b << 8); - if (check != (~blockLen & 0xffff)) - error("Bad uncompressed block length in flate stream"); - - this.codeBuf = 0; - this.codeSize = 0; - - var bufferLength = this.bufferLength; - var buffer = this.ensureBuffer(bufferLength + blockLen); - var end = bufferLength + blockLen; - this.bufferLength = end; - for (var n = bufferLength; n < end; ++n) { - if (typeof (b = bytes[bytesPos++]) == "undefined") { - this.eof = true; - break; - } - buffer[n] = b; - } - this.bytesPos = bytesPos; - return; - } - - var litCodeTable; - var distCodeTable; - if (hdr == 1) { // compressed block, fixed codes - litCodeTable = fixedLitCodeTab; - distCodeTable = fixedDistCodeTab; - } else if (hdr == 2) { // compressed block, dynamic codes - var numLitCodes = this.getBits(5) + 257; - var numDistCodes = this.getBits(5) + 1; - var numCodeLenCodes = this.getBits(4) + 4; - - // build the code lengths code table - var codeLenCodeLengths = Array(codeLenCodeMap.length); - var i = 0; - while (i < numCodeLenCodes) - codeLenCodeLengths[codeLenCodeMap[i++]] = this.getBits(3); - var codeLenCodeTab = this.generateHuffmanTable(codeLenCodeLengths); - - // build the literal and distance code tables - var len = 0; - var i = 0; - var codes = numLitCodes + numDistCodes; - var codeLengths = new Array(codes); - while (i < codes) { - var code = this.getCode(codeLenCodeTab); - if (code == 16) { - repeat(this, codeLengths, 2, 3, len); - } else if (code == 17) { - repeat(this, codeLengths, 3, 3, len = 0); - } else if (code == 18) { - repeat(this, codeLengths, 7, 11, len = 0); - } else { - codeLengths[i++] = len = code; - } - } - - litCodeTable = - this.generateHuffmanTable(codeLengths.slice(0, numLitCodes)); - distCodeTable = - this.generateHuffmanTable(codeLengths.slice(numLitCodes, codes)); + // build the literal and distance code tables + var len = 0; + var i = 0; + var codes = numLitCodes + numDistCodes; + var codeLengths = new Array(codes); + while (i < codes) { + var code = this.getCode(codeLenCodeTab); + if (code == 16) { + repeat(this, codeLengths, 2, 3, len); + } else if (code == 17) { + repeat(this, codeLengths, 3, 3, len = 0); + } else if (code == 18) { + repeat(this, codeLengths, 7, 11, len = 0); } else { - error("Unknown block type in flate stream"); + codeLengths[i++] = len = code; } + } - var buffer = this.buffer; - var limit = buffer ? buffer.length : 0; - var pos = this.bufferLength; - while (true) { - var code1 = this.getCode(litCodeTable); - if (code1 < 256) { - if (pos + 1 >= limit) { - buffer = this.ensureBuffer(pos + 1); - limit = buffer.length; - } - buffer[pos++] = code1; - continue; - } - if (code1 == 256) { - this.bufferLength = pos; - return; - } - code1 -= 257; - code1 = lengthDecode[code1]; - var code2 = code1 >> 16; - if (code2 > 0) - code2 = this.getBits(code2); - var len = (code1 & 0xffff) + code2; - code1 = this.getCode(distCodeTable); - code1 = distDecode[code1]; - code2 = code1 >> 16; - if (code2 > 0) - code2 = this.getBits(code2); - var dist = (code1 & 0xffff) + code2; - if (pos + len >= limit) { - buffer = this.ensureBuffer(pos + len); - limit = buffer.length; - } - for (var k = 0; k < len; ++k, ++pos) - buffer[pos] = buffer[pos - dist]; + litCodeTable = + this.generateHuffmanTable(codeLengths.slice(0, numLitCodes)); + distCodeTable = + this.generateHuffmanTable(codeLengths.slice(numLitCodes, codes)); + } else { + error('Unknown block type in flate stream'); + } + + var buffer = this.buffer; + var limit = buffer ? buffer.length : 0; + var pos = this.bufferLength; + while (true) { + var code1 = this.getCode(litCodeTable); + if (code1 < 256) { + if (pos + 1 >= limit) { + buffer = this.ensureBuffer(pos + 1); + limit = buffer.length; } - }; + buffer[pos++] = code1; + continue; + } + if (code1 == 256) { + this.bufferLength = pos; + return; + } + code1 -= 257; + code1 = lengthDecode[code1]; + var code2 = code1 >> 16; + if (code2 > 0) + code2 = this.getBits(code2); + var len = (code1 & 0xffff) + code2; + code1 = this.getCode(distCodeTable); + code1 = distDecode[code1]; + code2 = code1 >> 16; + if (code2 > 0) + code2 = this.getBits(code2); + var dist = (code1 & 0xffff) + code2; + if (pos + len >= limit) { + buffer = this.ensureBuffer(pos + len); + limit = buffer.length; + } + for (var k = 0; k < len; ++k, ++pos) + buffer[pos] = buffer[pos - dist]; + } + }; - return constructor; + return constructor; })(); var PredictorStream = (function() { - function constructor(stream, params) { - var predictor = this.predictor = params.get("Predictor") || 1; + function constructor(stream, params) { + var predictor = this.predictor = params.get('Predictor') || 1; - if (predictor <= 1) - return stream; // no prediction - if (predictor !== 2 && (predictor < 10 || predictor > 15)) - error("Unsupported predictor"); + if (predictor <= 1) + return stream; // no prediction + if (predictor !== 2 && (predictor < 10 || predictor > 15)) + error('Unsupported predictor'); - if (predictor === 2) - this.readBlock = this.readBlockTiff; - else - this.readBlock = this.readBlockPng; + if (predictor === 2) + this.readBlock = this.readBlockTiff; + else + this.readBlock = this.readBlockPng; - this.stream = stream; - this.dict = stream.dict; - if (params.has("EarlyChange")) { - error("EarlyChange predictor parameter is not supported"); - } - var colors = this.colors = params.get("Colors") || 1; - var bits = this.bits = params.get("BitsPerComponent") || 8; - var columns = this.columns = params.get("Columns") || 1; - - 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; + this.stream = stream; + this.dict = stream.dict; + if (params.has('EarlyChange')) { + error('EarlyChange predictor parameter is not supported'); } + var colors = this.colors = params.get('Colors') || 1; + var bits = this.bits = params.get('BitsPerComponent') || 8; + var columns = this.columns = params.get('Columns') || 1; - constructor.prototype = Object.create(DecodeStream.prototype); + 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; - constructor.prototype.readBlockTiff = function() { - var rowBytes = this.rowBytes; - var pixBytes = this.pixBytes; + DecodeStream.call(this); + return this; + } - var bufferLength = this.bufferLength; - var buffer = this.ensureBuffer(bufferLength + rowBytes); - var currentRow = buffer.subarray(bufferLength, bufferLength + rowBytes); + constructor.prototype = Object.create(DecodeStream.prototype); - var bits = this.bits; - var colors = this.colors; + constructor.prototype.readBlockTiff = function() { + var rowBytes = this.rowBytes; + var pixBytes = this.pixBytes; - var rawBytes = this.stream.getBytes(rowBytes); + var bufferLength = this.bufferLength; + var buffer = this.ensureBuffer(bufferLength + rowBytes); + var currentRow = buffer.subarray(bufferLength, bufferLength + rowBytes); - if (bits === 1) { - var inbuf = 0; - for (var i = 0; i < rowBytes; ++i) { - var c = rawBytes[i]; - inBuf = (inBuf << 8) | c; - // bitwise addition is exclusive or - // first shift inBuf and then add - currentRow[i] = (c ^ (inBuf >> colors)) & 0xFF; - // truncate inBuf (assumes colors < 16) - inBuf &= 0xFFFF; - } - } else if (bits === 8) { - for (var i = 0; i < colors; ++i) - currentRow[i] = rawBytes[i]; - for (; i < rowBytes; ++i) - currentRow[i] = currentRow[i - colors] + rawBytes[i]; - } else { - var compArray = new Uint8Array(colors + 1); - var bitMask = (1 << bits) - 1; - var inbuf = 0, outbut = 0; - var inbits = 0, outbits = 0; - var j = 0, k = 0; - var columns = this.columns; - for (var i = 0; i < columns; ++i) { - for (var kk = 0; kk < colors; ++kk) { - if (inbits < bits) { - inbuf = (inbuf << 8) | (rawBytes[j++] & 0xFF); - inbits += 8; - } - compArray[kk] = (compArray[kk] + - (inbuf >> (inbits - bits))) & bitMask; - inbits -= bits; - outbuf = (outbuf << bits) | compArray[kk]; - outbits += bits; - if (outbits >= 8) { - currentRow[k++] = (outbuf >> (outbits - 8)) & 0xFF; - outbits -= 8; - } - } - } - if (outbits > 0) { - currentRow[k++] = (outbuf << (8 - outbits)) + - (inbuf & ((1 << (8 - outbits)) - 1)) - } - } - this.bufferLength += rowBytes; - }; - constructor.prototype.readBlockPng = function() { - var rowBytes = this.rowBytes; - var pixBytes = this.pixBytes; + var bits = this.bits; + var colors = this.colors; - var predictor = this.stream.getByte(); - var rawBytes = this.stream.getBytes(rowBytes); + var rawBytes = this.stream.getBytes(rowBytes); - var bufferLength = this.bufferLength; - var buffer = this.ensureBuffer(bufferLength + rowBytes); - - var currentRow = buffer.subarray(bufferLength, bufferLength + rowBytes); - var prevRow = buffer.subarray(bufferLength - rowBytes, bufferLength); - if (prevRow.length == 0) - prevRow = currentRow; - - switch (predictor) { - case 0: - break; - case 1: - for (var i = 0; i < pixBytes; ++i) - currentRow[i] = rawBytes[i]; - for (; i < rowBytes; ++i) - currentRow[i] = (currentRow[i - pixBytes] + rawBytes[i]) & 0xFF; - break; - case 2: - for (var i = 0; i < rowBytes; ++i) - currentRow[i] = (prevRow[i] + rawBytes[i]) & 0xFF; - break; - case 3: - for (var i = 0; i < pixBytes; ++i) - currentRow[i] = (prevRow[i] >> 1) + rawBytes[i]; - for (; i < rowBytes; ++i) - currentRow[i] = (((prevRow[i] + currentRow[i - pixBytes]) - >> 1) + rawBytes[i]) & 0xFF; - break; - case 4: - // we need to save the up left pixels values. the simplest way - // is to create a new buffer - for (var i = 0; i < pixBytes; ++i) - currentRow[i] = rawBytes[i]; - for (; i < rowBytes; ++i) { - var up = prevRow[i]; - var upLeft = lastRow[i - pixBytes]; - var left = currentRow[i - pixBytes]; - var p = left + up - upLeft; - - var pa = p - left; - if (pa < 0) - pa = -pa; - var pb = p - up; - if (pb < 0) - pb = -pb; - var pc = p - upLeft; - if (pc < 0) - pc = -pc; - - var c = rawBytes[i]; - if (pa <= pb && pa <= pc) - currentRow[i] = left + c; - else if (pb <= pc) - currentRow[i] = up + c; - else - currentRow[i] = upLeft + c; - break; - } - default: - error("Unsupported predictor"); - break; - } - this.bufferLength += rowBytes; - }; - - return constructor; -})(); - -// A JpegStream can't be read directly. We use the platform to render the underlying -// JPEG data for us. -var JpegStream = (function() { - function constructor(bytes, dict) { - // TODO: per poppler, some images may have "junk" before that need to be removed - this.dict = dict; - - // create DOM image - var img = new Image(); - img.src = "data:image/jpeg;base64," + window.btoa(bytesToString(bytes)); - this.domImage = img; - } - - constructor.prototype = { - getImage: function() { - return this.domImage; - }, - getChar: function() { - error("internal error: getChar is not valid on JpegStream"); - } - }; - - return constructor; -})(); -var DecryptStream = (function() { - function constructor(str, decrypt) { - this.str = str; - this.dict = str.dict; - this.decrypt = decrypt; - - DecodeStream.call(this); - } - - var chunkSize = 512; - - constructor.prototype = Object.create(DecodeStream.prototype); - constructor.prototype.readBlock = function() { - var chunk = this.str.getBytes(chunkSize); - if (!chunk || chunk.length == 0) { - this.eof = true; - return; + if (bits === 1) { + var inbuf = 0; + for (var i = 0; i < rowBytes; ++i) { + var c = rawBytes[i]; + inBuf = (inBuf << 8) | c; + // bitwise addition is exclusive or + // first shift inBuf and then add + currentRow[i] = (c ^ (inBuf >> colors)) & 0xFF; + // truncate inBuf (assumes colors < 16) + inBuf &= 0xFFFF; } - var decrypt = this.decrypt; - chunk = decrypt(chunk); + } else if (bits === 8) { + for (var i = 0; i < colors; ++i) + currentRow[i] = rawBytes[i]; + for (; i < rowBytes; ++i) + currentRow[i] = currentRow[i - colors] + rawBytes[i]; + } else { + var compArray = new Uint8Array(colors + 1); + var bitMask = (1 << bits) - 1; + var inbuf = 0, outbut = 0; + var inbits = 0, outbits = 0; + var j = 0, k = 0; + var columns = this.columns; + for (var i = 0; i < columns; ++i) { + for (var kk = 0; kk < colors; ++kk) { + if (inbits < bits) { + inbuf = (inbuf << 8) | (rawBytes[j++] & 0xFF); + inbits += 8; + } + compArray[kk] = (compArray[kk] + + (inbuf >> (inbits - bits))) & bitMask; + inbits -= bits; + outbuf = (outbuf << bits) | compArray[kk]; + outbits += bits; + if (outbits >= 8) { + currentRow[k++] = (outbuf >> (outbits - 8)) & 0xFF; + outbits -= 8; + } + } + } + if (outbits > 0) { + currentRow[k++] = (outbuf << (8 - outbits)) + + (inbuf & ((1 << (8 - outbits)) - 1)); + } + } + this.bufferLength += rowBytes; + }; - var bufferLength = this.bufferLength; - var i, n = chunk.length; - var buffer = this.ensureBuffer(bufferLength + n); - for (i = 0; i < n; i++) - buffer[bufferLength++] = chunk[i]; - this.bufferLength = bufferLength; - this.eof = n < chunkSize; - }; + constructor.prototype.readBlockPng = function() { + var rowBytes = this.rowBytes; + var pixBytes = this.pixBytes; - return constructor; + var predictor = this.stream.getByte(); + var rawBytes = this.stream.getBytes(rowBytes); + + var bufferLength = this.bufferLength; + var buffer = this.ensureBuffer(bufferLength + rowBytes); + + var currentRow = buffer.subarray(bufferLength, bufferLength + rowBytes); + var prevRow = buffer.subarray(bufferLength - rowBytes, bufferLength); + if (prevRow.length == 0) + prevRow = currentRow; + + switch (predictor) { + case 0: + break; + case 1: + for (var i = 0; i < pixBytes; ++i) + currentRow[i] = rawBytes[i]; + for (; i < rowBytes; ++i) + currentRow[i] = (currentRow[i - pixBytes] + rawBytes[i]) & 0xFF; + break; + case 2: + for (var i = 0; i < rowBytes; ++i) + currentRow[i] = (prevRow[i] + rawBytes[i]) & 0xFF; + break; + case 3: + for (var i = 0; i < pixBytes; ++i) + currentRow[i] = (prevRow[i] >> 1) + rawBytes[i]; + for (; i < rowBytes; ++i) { + currentRow[i] = (((prevRow[i] + currentRow[i - pixBytes]) >> 1) + + rawBytes[i]) & 0xFF; + } + break; + case 4: + // we need to save the up left pixels values. the simplest way + // is to create a new buffer + for (var i = 0; i < pixBytes; ++i) + currentRow[i] = rawBytes[i]; + for (; i < rowBytes; ++i) { + var up = prevRow[i]; + var upLeft = lastRow[i - pixBytes]; + var left = currentRow[i - pixBytes]; + var p = left + up - upLeft; + + var pa = p - left; + if (pa < 0) + pa = -pa; + var pb = p - up; + if (pb < 0) + pb = -pb; + var pc = p - upLeft; + if (pc < 0) + pc = -pc; + + var c = rawBytes[i]; + if (pa <= pb && pa <= pc) + currentRow[i] = left + c; + else if (pb <= pc) + currentRow[i] = up + c; + else + currentRow[i] = upLeft + c; + break; + } + default: + error('Unsupported predictor'); + break; + } + this.bufferLength += rowBytes; + }; + + return constructor; +})(); + +// A JpegStream can't be read directly. We use the platform to render +// the underlying JPEG data for us. +var JpegStream = (function() { + function constructor(bytes, dict) { + // TODO: per poppler, some images may have "junk" before that + // need to be removed + this.dict = dict; + + // create DOM image + var img = new Image(); + img.src = 'data:image/jpeg;base64,' + window.btoa(bytesToString(bytes)); + this.domImage = img; + } + + constructor.prototype = { + getImage: function() { + return this.domImage; + }, + getChar: function() { + error('internal error: getChar is not valid on JpegStream'); + } + }; + + return constructor; +})(); + +var DecryptStream = (function() { + function constructor(str, decrypt) { + this.str = str; + this.dict = str.dict; + this.decrypt = decrypt; + + DecodeStream.call(this); + } + + var chunkSize = 512; + + constructor.prototype = Object.create(DecodeStream.prototype); + + constructor.prototype.readBlock = function() { + var chunk = this.str.getBytes(chunkSize); + if (!chunk || chunk.length == 0) { + this.eof = true; + return; + } + var decrypt = this.decrypt; + chunk = decrypt(chunk); + + var bufferLength = this.bufferLength; + var i, n = chunk.length; + var buffer = this.ensureBuffer(bufferLength + n); + for (i = 0; i < n; i++) + buffer[bufferLength++] = chunk[i]; + this.bufferLength = bufferLength; + this.eof = n < chunkSize; + }; + + return constructor; })(); var Ascii85Stream = (function() { - function constructor(str) { - this.str = str; - this.dict = str.dict; - this.input = new Uint8Array(5); - - DecodeStream.call(this); + function constructor(str) { + this.str = str; + this.dict = str.dict; + this.input = new Uint8Array(5); + + DecodeStream.call(this); + } + + constructor.prototype = Object.create(DecodeStream.prototype); + + constructor.prototype.readBlock = function() { + var tildaCode = '~'.charCodeAt(0); + var zCode = 'z'.charCodeAt(0); + var str = this.str; + + var c = str.getByte(); + while (Lexer.isSpace(String.fromCharCode(c))) + c = str.getByte(); + + if (!c || c === tildaCode) { + this.eof = true; + return; } - constructor.prototype = Object.create(DecodeStream.prototype); - constructor.prototype.readBlock = function() { - var tildaCode = "~".charCodeAt(0); - var zCode = "z".charCodeAt(0); - var str = this.str; + var bufferLength = this.bufferLength; - var c = str.getByte(); + // special code for z + if (c == zCode) { + var buffer = this.ensureBuffer(bufferLength + 4); + for (var i = 0; i < 4; ++i) + buffer[bufferLength + i] = 0; + this.bufferLength += 4; + } else { + var input = this.input; + input[0] = c; + for (var i = 1; i < 5; ++i) { + c = str.getByte(); while (Lexer.isSpace(String.fromCharCode(c))) - c = str.getByte(); + c = str.getByte(); - if (!c || c === tildaCode) { - this.eof = true; - return; - } + input[i] = c; - var bufferLength = this.bufferLength; + if (!c || c == tildaCode) + break; + } + var buffer = this.ensureBuffer(bufferLength + i - 1); + this.bufferLength += i - 1; - // special code for z - if (c == zCode) { - var buffer = this.ensureBuffer(bufferLength + 4); - for (var i = 0; i < 4; ++i) - buffer[bufferLength + i] = 0; - this.bufferLength += 4; - } else { - var input = this.input; - input[0] = c; - for (var i = 1; i < 5; ++i){ - c = str.getByte(); - while (Lexer.isSpace(String.fromCharCode(c))) - c = str.getByte(); + // partial ending; + if (i < 5) { + for (; i < 5; ++i) + input[i] = 0x21 + 84; + this.eof = true; + } + var t = 0; + for (var i = 0; i < 5; ++i) + t = t * 85 + (input[i] - 0x21); - input[i] = c; + for (var i = 3; i >= 0; --i) { + buffer[bufferLength + i] = t & 0xFF; + t >>= 8; + } + } + }; - if (!c || c == tildaCode) - break; - } - var buffer = this.ensureBuffer(bufferLength + i - 1); - this.bufferLength += i - 1; + return constructor; +})(); - // partial ending; - if (i < 5) { - for (; i < 5; ++i) - input[i] = 0x21 + 84; - this.eof = true; - } - var t = 0; - for (var i = 0; i < 5; ++i) - t = t * 85 + (input[i] - 0x21); +var AsciiHexStream = (function() { + function constructor(str) { + this.str = str; + this.dict = str.dict; + + DecodeStream.call(this); + } + + var hexvalueMap = { + 9: -1, // \t + 32: -1, // space + 48: 0, + 49: 1, + 50: 2, + 51: 3, + 52: 4, + 53: 5, + 54: 6, + 55: 7, + 56: 8, + 57: 9, + 65: 10, + 66: 11, + 67: 12, + 68: 13, + 69: 14, + 70: 15, + 97: 10, + 98: 11, + 99: 12, + 100: 13, + 101: 14, + 102: 15 + }; - for (var i = 3; i >= 0; --i){ - buffer[bufferLength + i] = t & 0xFF; - t >>= 8; - } + constructor.prototype = Object.create(DecodeStream.prototype); + + constructor.prototype.readBlock = function() { + var gtCode = '>'.charCodeAt(0), bytes = this.str.getBytes(), c, n, + decodeLength, buffer, bufferLength, i, length; + + decodeLength = (bytes.length + 1) >> 1; + buffer = this.ensureBuffer(this.bufferLength + decodeLength); + bufferLength = this.bufferLength; + + for(i = 0, length = bytes.length; i < length; i++) { + c = hexvalueMap[bytes[i]]; + while (c == -1 && (i+1) < length) { + c = hexvalueMap[bytes[++i]]; + } + + if((i+1) < length && (bytes[i+1] !== gtCode)) { + n = hexvalueMap[bytes[++i]]; + buffer[bufferLength++] = c*16+n; + } else { + if(bytes[i] !== gtCode) { // EOD marker at an odd number, behave as if a 0 followed the last digit. + buffer[bufferLength++] = c*16; } - }; - - return constructor; + } + } + + this.bufferLength = bufferLength; + this.eof = true; + }; + + return constructor; })(); var CCITTFaxStream = (function() { - 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; + 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; - var twoDimTable = [ + var twoDimTable = [ [-1, -1], [-1, -1], // 000000x [7, twoDimVertL3], // 0000010 [7, twoDimVertR3], // 0000011 @@ -987,3961 +1051,4339 @@ var CCITTFaxStream = (function() { [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0] - ]; + ]; - var whiteTable1 = [ - [-1, -1], // 00000 - [12, ccittEOL], // 00001 - [-1, -1], [-1, -1], // 0001x - [-1, -1], [-1, -1], [-1, -1], [-1, -1], // 001xx - [-1, -1], [-1, -1], [-1, -1], [-1, -1], // 010xx - [-1, -1], [-1, -1], [-1, -1], [-1, -1], // 011xx - [11, 1792], [11, 1792], // 1000x - [12, 1984], // 10010 - [12, 2048], // 10011 - [12, 2112], // 10100 - [12, 2176], // 10101 - [12, 2240], // 10110 - [12, 2304], // 10111 - [11, 1856], [11, 1856], // 1100x - [11, 1920], [11, 1920], // 1101x - [12, 2368], // 11100 - [12, 2432], // 11101 - [12, 2496], // 11110 - [12, 2560] // 11111 - ]; + var whiteTable1 = [ + [-1, -1], // 00000 + [12, ccittEOL], // 00001 + [-1, -1], [-1, -1], // 0001x + [-1, -1], [-1, -1], [-1, -1], [-1, -1], // 001xx + [-1, -1], [-1, -1], [-1, -1], [-1, -1], // 010xx + [-1, -1], [-1, -1], [-1, -1], [-1, -1], // 011xx + [11, 1792], [11, 1792], // 1000x + [12, 1984], // 10010 + [12, 2048], // 10011 + [12, 2112], // 10100 + [12, 2176], // 10101 + [12, 2240], // 10110 + [12, 2304], // 10111 + [11, 1856], [11, 1856], // 1100x + [11, 1920], [11, 1920], // 1101x + [12, 2368], // 11100 + [12, 2432], // 11101 + [12, 2496], // 11110 + [12, 2560] // 11111 + ]; - var whiteTable2 = [ - [-1, -1], [-1, -1], [-1, -1], [-1, -1], // 0000000xx - [8, 29], [8, 29], // 00000010x - [8, 30], [8, 30], // 00000011x - [8, 45], [8, 45], // 00000100x - [8, 46], [8, 46], // 00000101x - [7, 22], [7, 22], [7, 22], [7, 22], // 0000011xx - [7, 23], [7, 23], [7, 23], [7, 23], // 0000100xx - [8, 47], [8, 47], // 00001010x - [8, 48], [8, 48], // 00001011x - [6, 13], [6, 13], [6, 13], [6, 13], // 000011xxx - [6, 13], [6, 13], [6, 13], [6, 13], - [7, 20], [7, 20], [7, 20], [7, 20], // 0001000xx - [8, 33], [8, 33], // 00010010x - [8, 34], [8, 34], // 00010011x - [8, 35], [8, 35], // 00010100x - [8, 36], [8, 36], // 00010101x - [8, 37], [8, 37], // 00010110x - [8, 38], [8, 38], // 00010111x - [7, 19], [7, 19], [7, 19], [7, 19], // 0001100xx - [8, 31], [8, 31], // 00011010x - [8, 32], [8, 32], // 00011011x - [6, 1], [6, 1], [6, 1], [6, 1], // 000111xxx - [6, 1], [6, 1], [6, 1], [6, 1], - [6, 12], [6, 12], [6, 12], [6, 12], // 001000xxx - [6, 12], [6, 12], [6, 12], [6, 12], - [8, 53], [8, 53], // 00100100x - [8, 54], [8, 54], // 00100101x - [7, 26], [7, 26], [7, 26], [7, 26], // 0010011xx - [8, 39], [8, 39], // 00101000x - [8, 40], [8, 40], // 00101001x - [8, 41], [8, 41], // 00101010x - [8, 42], [8, 42], // 00101011x - [8, 43], [8, 43], // 00101100x - [8, 44], [8, 44], // 00101101x - [7, 21], [7, 21], [7, 21], [7, 21], // 0010111xx - [7, 28], [7, 28], [7, 28], [7, 28], // 0011000xx - [8, 61], [8, 61], // 00110010x - [8, 62], [8, 62], // 00110011x - [8, 63], [8, 63], // 00110100x - [8, 0], [8, 0], // 00110101x - [8, 320], [8, 320], // 00110110x - [8, 384], [8, 384], // 00110111x - [5, 10], [5, 10], [5, 10], [5, 10], // 00111xxxx - [5, 10], [5, 10], [5, 10], [5, 10], - [5, 10], [5, 10], [5, 10], [5, 10], - [5, 10], [5, 10], [5, 10], [5, 10], - [5, 11], [5, 11], [5, 11], [5, 11], // 01000xxxx - [5, 11], [5, 11], [5, 11], [5, 11], - [5, 11], [5, 11], [5, 11], [5, 11], - [5, 11], [5, 11], [5, 11], [5, 11], - [7, 27], [7, 27], [7, 27], [7, 27], // 0100100xx - [8, 59], [8, 59], // 01001010x - [8, 60], [8, 60], // 01001011x - [9, 1472], // 010011000 - [9, 1536], // 010011001 - [9, 1600], // 010011010 - [9, 1728], // 010011011 - [7, 18], [7, 18], [7, 18], [7, 18], // 0100111xx - [7, 24], [7, 24], [7, 24], [7, 24], // 0101000xx - [8, 49], [8, 49], // 01010010x - [8, 50], [8, 50], // 01010011x - [8, 51], [8, 51], // 01010100x - [8, 52], [8, 52], // 01010101x - [7, 25], [7, 25], [7, 25], [7, 25], // 0101011xx - [8, 55], [8, 55], // 01011000x - [8, 56], [8, 56], // 01011001x - [8, 57], [8, 57], // 01011010x - [8, 58], [8, 58], // 01011011x - [6, 192], [6, 192], [6, 192], [6, 192], // 010111xxx - [6, 192], [6, 192], [6, 192], [6, 192], - [6, 1664], [6, 1664], [6, 1664], [6, 1664], // 011000xxx - [6, 1664], [6, 1664], [6, 1664], [6, 1664], - [8, 448], [8, 448], // 01100100x - [8, 512], [8, 512], // 01100101x - [9, 704], // 011001100 - [9, 768], // 011001101 - [8, 640], [8, 640], // 01100111x - [8, 576], [8, 576], // 01101000x - [9, 832], // 011010010 - [9, 896], // 011010011 - [9, 960], // 011010100 - [9, 1024], // 011010101 - [9, 1088], // 011010110 - [9, 1152], // 011010111 - [9, 1216], // 011011000 - [9, 1280], // 011011001 - [9, 1344], // 011011010 - [9, 1408], // 011011011 - [7, 256], [7, 256], [7, 256], [7, 256], // 0110111xx - [4, 2], [4, 2], [4, 2], [4, 2], // 0111xxxxx - [4, 2], [4, 2], [4, 2], [4, 2], - [4, 2], [4, 2], [4, 2], [4, 2], - [4, 2], [4, 2], [4, 2], [4, 2], - [4, 2], [4, 2], [4, 2], [4, 2], - [4, 2], [4, 2], [4, 2], [4, 2], - [4, 2], [4, 2], [4, 2], [4, 2], - [4, 2], [4, 2], [4, 2], [4, 2], - [4, 3], [4, 3], [4, 3], [4, 3], // 1000xxxxx - [4, 3], [4, 3], [4, 3], [4, 3], - [4, 3], [4, 3], [4, 3], [4, 3], - [4, 3], [4, 3], [4, 3], [4, 3], - [4, 3], [4, 3], [4, 3], [4, 3], - [4, 3], [4, 3], [4, 3], [4, 3], - [4, 3], [4, 3], [4, 3], [4, 3], - [4, 3], [4, 3], [4, 3], [4, 3], - [5, 128], [5, 128], [5, 128], [5, 128], // 10010xxxx - [5, 128], [5, 128], [5, 128], [5, 128], - [5, 128], [5, 128], [5, 128], [5, 128], - [5, 128], [5, 128], [5, 128], [5, 128], - [5, 8], [5, 8], [5, 8], [5, 8], // 10011xxxx - [5, 8], [5, 8], [5, 8], [5, 8], - [5, 8], [5, 8], [5, 8], [5, 8], - [5, 8], [5, 8], [5, 8], [5, 8], - [5, 9], [5, 9], [5, 9], [5, 9], // 10100xxxx - [5, 9], [5, 9], [5, 9], [5, 9], - [5, 9], [5, 9], [5, 9], [5, 9], - [5, 9], [5, 9], [5, 9], [5, 9], - [6, 16], [6, 16], [6, 16], [6, 16], // 101010xxx - [6, 16], [6, 16], [6, 16], [6, 16], - [6, 17], [6, 17], [6, 17], [6, 17], // 101011xxx - [6, 17], [6, 17], [6, 17], [6, 17], - [4, 4], [4, 4], [4, 4], [4, 4], // 1011xxxxx - [4, 4], [4, 4], [4, 4], [4, 4], - [4, 4], [4, 4], [4, 4], [4, 4], - [4, 4], [4, 4], [4, 4], [4, 4], - [4, 4], [4, 4], [4, 4], [4, 4], - [4, 4], [4, 4], [4, 4], [4, 4], - [4, 4], [4, 4], [4, 4], [4, 4], - [4, 4], [4, 4], [4, 4], [4, 4], - [4, 5], [4, 5], [4, 5], [4, 5], // 1100xxxxx - [4, 5], [4, 5], [4, 5], [4, 5], - [4, 5], [4, 5], [4, 5], [4, 5], - [4, 5], [4, 5], [4, 5], [4, 5], - [4, 5], [4, 5], [4, 5], [4, 5], - [4, 5], [4, 5], [4, 5], [4, 5], - [4, 5], [4, 5], [4, 5], [4, 5], - [4, 5], [4, 5], [4, 5], [4, 5], - [6, 14], [6, 14], [6, 14], [6, 14], // 110100xxx - [6, 14], [6, 14], [6, 14], [6, 14], - [6, 15], [6, 15], [6, 15], [6, 15], // 110101xxx - [6, 15], [6, 15], [6, 15], [6, 15], - [5, 64], [5, 64], [5, 64], [5, 64], // 11011xxxx - [5, 64], [5, 64], [5, 64], [5, 64], - [5, 64], [5, 64], [5, 64], [5, 64], - [5, 64], [5, 64], [5, 64], [5, 64], - [4, 6], [4, 6], [4, 6], [4, 6], // 1110xxxxx - [4, 6], [4, 6], [4, 6], [4, 6], - [4, 6], [4, 6], [4, 6], [4, 6], - [4, 6], [4, 6], [4, 6], [4, 6], - [4, 6], [4, 6], [4, 6], [4, 6], - [4, 6], [4, 6], [4, 6], [4, 6], - [4, 6], [4, 6], [4, 6], [4, 6], - [4, 6], [4, 6], [4, 6], [4, 6], - [4, 7], [4, 7], [4, 7], [4, 7], // 1111xxxxx - [4, 7], [4, 7], [4, 7], [4, 7], - [4, 7], [4, 7], [4, 7], [4, 7], - [4, 7], [4, 7], [4, 7], [4, 7], - [4, 7], [4, 7], [4, 7], [4, 7], - [4, 7], [4, 7], [4, 7], [4, 7], - [4, 7], [4, 7], [4, 7], [4, 7], - [4, 7], [4, 7], [4, 7], [4, 7] - ]; - - var blackTable1 = [ - [-1, -1], [-1, -1], // 000000000000x - [12, ccittEOL], [12, ccittEOL], // 000000000001x - [-1, -1], [-1, -1], [-1, -1], [-1, -1], // 00000000001xx - [-1, -1], [-1, -1], [-1, -1], [-1, -1], // 00000000010xx - [-1, -1], [-1, -1], [-1, -1], [-1, -1], // 00000000011xx - [-1, -1], [-1, -1], [-1, -1], [-1, -1], // 00000000100xx - [-1, -1], [-1, -1], [-1, -1], [-1, -1], // 00000000101xx - [-1, -1], [-1, -1], [-1, -1], [-1, -1], // 00000000110xx - [-1, -1], [-1, -1], [-1, -1], [-1, -1], // 00000000111xx - [11, 1792], [11, 1792], [11, 1792], [11, 1792], // 00000001000xx - [12, 1984], [12, 1984], // 000000010010x - [12, 2048], [12, 2048], // 000000010011x - [12, 2112], [12, 2112], // 000000010100x - [12, 2176], [12, 2176], // 000000010101x - [12, 2240], [12, 2240], // 000000010110x - [12, 2304], [12, 2304], // 000000010111x - [11, 1856], [11, 1856], [11, 1856], [11, 1856], // 00000001100xx - [11, 1920], [11, 1920], [11, 1920], [11, 1920], // 00000001101xx - [12, 2368], [12, 2368], // 000000011100x - [12, 2432], [12, 2432], // 000000011101x - [12, 2496], [12, 2496], // 000000011110x - [12, 2560], [12, 2560], // 000000011111x - [10, 18], [10, 18], [10, 18], [10, 18], // 0000001000xxx - [10, 18], [10, 18], [10, 18], [10, 18], - [12, 52], [12, 52], // 000000100100x - [13, 640], // 0000001001010 - [13, 704], // 0000001001011 - [13, 768], // 0000001001100 - [13, 832], // 0000001001101 - [12, 55], [12, 55], // 000000100111x - [12, 56], [12, 56], // 000000101000x - [13, 1280], // 0000001010010 - [13, 1344], // 0000001010011 - [13, 1408], // 0000001010100 - [13, 1472], // 0000001010101 - [12, 59], [12, 59], // 000000101011x - [12, 60], [12, 60], // 000000101100x - [13, 1536], // 0000001011010 - [13, 1600], // 0000001011011 - [11, 24], [11, 24], [11, 24], [11, 24], // 00000010111xx - [11, 25], [11, 25], [11, 25], [11, 25], // 00000011000xx - [13, 1664], // 0000001100100 - [13, 1728], // 0000001100101 - [12, 320], [12, 320], // 000000110011x - [12, 384], [12, 384], // 000000110100x - [12, 448], [12, 448], // 000000110101x - [13, 512], // 0000001101100 - [13, 576], // 0000001101101 - [12, 53], [12, 53], // 000000110111x - [12, 54], [12, 54], // 000000111000x - [13, 896], // 0000001110010 - [13, 960], // 0000001110011 - [13, 1024], // 0000001110100 - [13, 1088], // 0000001110101 - [13, 1152], // 0000001110110 - [13, 1216], // 0000001110111 - [10, 64], [10, 64], [10, 64], [10, 64], // 0000001111xxx - [10, 64], [10, 64], [10, 64], [10, 64] - ]; + var whiteTable2 = [ + [-1, -1], [-1, -1], [-1, -1], [-1, -1], // 0000000xx + [8, 29], [8, 29], // 00000010x + [8, 30], [8, 30], // 00000011x + [8, 45], [8, 45], // 00000100x + [8, 46], [8, 46], // 00000101x + [7, 22], [7, 22], [7, 22], [7, 22], // 0000011xx + [7, 23], [7, 23], [7, 23], [7, 23], // 0000100xx + [8, 47], [8, 47], // 00001010x + [8, 48], [8, 48], // 00001011x + [6, 13], [6, 13], [6, 13], [6, 13], // 000011xxx + [6, 13], [6, 13], [6, 13], [6, 13], + [7, 20], [7, 20], [7, 20], [7, 20], // 0001000xx + [8, 33], [8, 33], // 00010010x + [8, 34], [8, 34], // 00010011x + [8, 35], [8, 35], // 00010100x + [8, 36], [8, 36], // 00010101x + [8, 37], [8, 37], // 00010110x + [8, 38], [8, 38], // 00010111x + [7, 19], [7, 19], [7, 19], [7, 19], // 0001100xx + [8, 31], [8, 31], // 00011010x + [8, 32], [8, 32], // 00011011x + [6, 1], [6, 1], [6, 1], [6, 1], // 000111xxx + [6, 1], [6, 1], [6, 1], [6, 1], + [6, 12], [6, 12], [6, 12], [6, 12], // 001000xxx + [6, 12], [6, 12], [6, 12], [6, 12], + [8, 53], [8, 53], // 00100100x + [8, 54], [8, 54], // 00100101x + [7, 26], [7, 26], [7, 26], [7, 26], // 0010011xx + [8, 39], [8, 39], // 00101000x + [8, 40], [8, 40], // 00101001x + [8, 41], [8, 41], // 00101010x + [8, 42], [8, 42], // 00101011x + [8, 43], [8, 43], // 00101100x + [8, 44], [8, 44], // 00101101x + [7, 21], [7, 21], [7, 21], [7, 21], // 0010111xx + [7, 28], [7, 28], [7, 28], [7, 28], // 0011000xx + [8, 61], [8, 61], // 00110010x + [8, 62], [8, 62], // 00110011x + [8, 63], [8, 63], // 00110100x + [8, 0], [8, 0], // 00110101x + [8, 320], [8, 320], // 00110110x + [8, 384], [8, 384], // 00110111x + [5, 10], [5, 10], [5, 10], [5, 10], // 00111xxxx + [5, 10], [5, 10], [5, 10], [5, 10], + [5, 10], [5, 10], [5, 10], [5, 10], + [5, 10], [5, 10], [5, 10], [5, 10], + [5, 11], [5, 11], [5, 11], [5, 11], // 01000xxxx + [5, 11], [5, 11], [5, 11], [5, 11], + [5, 11], [5, 11], [5, 11], [5, 11], + [5, 11], [5, 11], [5, 11], [5, 11], + [7, 27], [7, 27], [7, 27], [7, 27], // 0100100xx + [8, 59], [8, 59], // 01001010x + [8, 60], [8, 60], // 01001011x + [9, 1472], // 010011000 + [9, 1536], // 010011001 + [9, 1600], // 010011010 + [9, 1728], // 010011011 + [7, 18], [7, 18], [7, 18], [7, 18], // 0100111xx + [7, 24], [7, 24], [7, 24], [7, 24], // 0101000xx + [8, 49], [8, 49], // 01010010x + [8, 50], [8, 50], // 01010011x + [8, 51], [8, 51], // 01010100x + [8, 52], [8, 52], // 01010101x + [7, 25], [7, 25], [7, 25], [7, 25], // 0101011xx + [8, 55], [8, 55], // 01011000x + [8, 56], [8, 56], // 01011001x + [8, 57], [8, 57], // 01011010x + [8, 58], [8, 58], // 01011011x + [6, 192], [6, 192], [6, 192], [6, 192], // 010111xxx + [6, 192], [6, 192], [6, 192], [6, 192], + [6, 1664], [6, 1664], [6, 1664], [6, 1664], // 011000xxx + [6, 1664], [6, 1664], [6, 1664], [6, 1664], + [8, 448], [8, 448], // 01100100x + [8, 512], [8, 512], // 01100101x + [9, 704], // 011001100 + [9, 768], // 011001101 + [8, 640], [8, 640], // 01100111x + [8, 576], [8, 576], // 01101000x + [9, 832], // 011010010 + [9, 896], // 011010011 + [9, 960], // 011010100 + [9, 1024], // 011010101 + [9, 1088], // 011010110 + [9, 1152], // 011010111 + [9, 1216], // 011011000 + [9, 1280], // 011011001 + [9, 1344], // 011011010 + [9, 1408], // 011011011 + [7, 256], [7, 256], [7, 256], [7, 256], // 0110111xx + [4, 2], [4, 2], [4, 2], [4, 2], // 0111xxxxx + [4, 2], [4, 2], [4, 2], [4, 2], + [4, 2], [4, 2], [4, 2], [4, 2], + [4, 2], [4, 2], [4, 2], [4, 2], + [4, 2], [4, 2], [4, 2], [4, 2], + [4, 2], [4, 2], [4, 2], [4, 2], + [4, 2], [4, 2], [4, 2], [4, 2], + [4, 2], [4, 2], [4, 2], [4, 2], + [4, 3], [4, 3], [4, 3], [4, 3], // 1000xxxxx + [4, 3], [4, 3], [4, 3], [4, 3], + [4, 3], [4, 3], [4, 3], [4, 3], + [4, 3], [4, 3], [4, 3], [4, 3], + [4, 3], [4, 3], [4, 3], [4, 3], + [4, 3], [4, 3], [4, 3], [4, 3], + [4, 3], [4, 3], [4, 3], [4, 3], + [4, 3], [4, 3], [4, 3], [4, 3], + [5, 128], [5, 128], [5, 128], [5, 128], // 10010xxxx + [5, 128], [5, 128], [5, 128], [5, 128], + [5, 128], [5, 128], [5, 128], [5, 128], + [5, 128], [5, 128], [5, 128], [5, 128], + [5, 8], [5, 8], [5, 8], [5, 8], // 10011xxxx + [5, 8], [5, 8], [5, 8], [5, 8], + [5, 8], [5, 8], [5, 8], [5, 8], + [5, 8], [5, 8], [5, 8], [5, 8], + [5, 9], [5, 9], [5, 9], [5, 9], // 10100xxxx + [5, 9], [5, 9], [5, 9], [5, 9], + [5, 9], [5, 9], [5, 9], [5, 9], + [5, 9], [5, 9], [5, 9], [5, 9], + [6, 16], [6, 16], [6, 16], [6, 16], // 101010xxx + [6, 16], [6, 16], [6, 16], [6, 16], + [6, 17], [6, 17], [6, 17], [6, 17], // 101011xxx + [6, 17], [6, 17], [6, 17], [6, 17], + [4, 4], [4, 4], [4, 4], [4, 4], // 1011xxxxx + [4, 4], [4, 4], [4, 4], [4, 4], + [4, 4], [4, 4], [4, 4], [4, 4], + [4, 4], [4, 4], [4, 4], [4, 4], + [4, 4], [4, 4], [4, 4], [4, 4], + [4, 4], [4, 4], [4, 4], [4, 4], + [4, 4], [4, 4], [4, 4], [4, 4], + [4, 4], [4, 4], [4, 4], [4, 4], + [4, 5], [4, 5], [4, 5], [4, 5], // 1100xxxxx + [4, 5], [4, 5], [4, 5], [4, 5], + [4, 5], [4, 5], [4, 5], [4, 5], + [4, 5], [4, 5], [4, 5], [4, 5], + [4, 5], [4, 5], [4, 5], [4, 5], + [4, 5], [4, 5], [4, 5], [4, 5], + [4, 5], [4, 5], [4, 5], [4, 5], + [4, 5], [4, 5], [4, 5], [4, 5], + [6, 14], [6, 14], [6, 14], [6, 14], // 110100xxx + [6, 14], [6, 14], [6, 14], [6, 14], + [6, 15], [6, 15], [6, 15], [6, 15], // 110101xxx + [6, 15], [6, 15], [6, 15], [6, 15], + [5, 64], [5, 64], [5, 64], [5, 64], // 11011xxxx + [5, 64], [5, 64], [5, 64], [5, 64], + [5, 64], [5, 64], [5, 64], [5, 64], + [5, 64], [5, 64], [5, 64], [5, 64], + [4, 6], [4, 6], [4, 6], [4, 6], // 1110xxxxx + [4, 6], [4, 6], [4, 6], [4, 6], + [4, 6], [4, 6], [4, 6], [4, 6], + [4, 6], [4, 6], [4, 6], [4, 6], + [4, 6], [4, 6], [4, 6], [4, 6], + [4, 6], [4, 6], [4, 6], [4, 6], + [4, 6], [4, 6], [4, 6], [4, 6], + [4, 6], [4, 6], [4, 6], [4, 6], + [4, 7], [4, 7], [4, 7], [4, 7], // 1111xxxxx + [4, 7], [4, 7], [4, 7], [4, 7], + [4, 7], [4, 7], [4, 7], [4, 7], + [4, 7], [4, 7], [4, 7], [4, 7], + [4, 7], [4, 7], [4, 7], [4, 7], + [4, 7], [4, 7], [4, 7], [4, 7], + [4, 7], [4, 7], [4, 7], [4, 7], + [4, 7], [4, 7], [4, 7], [4, 7] + ]; - 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], - [8, 13], [8, 13], [8, 13], [8, 13], - [11, 23], [11, 23], // 00000101000x - [12, 50], // 000001010010 - [12, 51], // 000001010011 - [12, 44], // 000001010100 - [12, 45], // 000001010101 - [12, 46], // 000001010110 - [12, 47], // 000001010111 - [12, 57], // 000001011000 - [12, 58], // 000001011001 - [12, 61], // 000001011010 - [12, 256], // 000001011011 - [10, 16], [10, 16], [10, 16], [10, 16], // 0000010111xx - [10, 17], [10, 17], [10, 17], [10, 17], // 0000011000xx - [12, 48], // 000001100100 - [12, 49], // 000001100101 - [12, 62], // 000001100110 - [12, 63], // 000001100111 - [12, 30], // 000001101000 - [12, 31], // 000001101001 - [12, 32], // 000001101010 - [12, 33], // 000001101011 - [12, 40], // 000001101100 - [12, 41], // 000001101101 - [11, 22], [11, 22], // 00000110111x - [8, 14], [8, 14], [8, 14], [8, 14], // 00000111xxxx - [8, 14], [8, 14], [8, 14], [8, 14], - [8, 14], [8, 14], [8, 14], [8, 14], - [8, 14], [8, 14], [8, 14], [8, 14], - [7, 10], [7, 10], [7, 10], [7, 10], // 0000100xxxxx - [7, 10], [7, 10], [7, 10], [7, 10], - [7, 10], [7, 10], [7, 10], [7, 10], - [7, 10], [7, 10], [7, 10], [7, 10], - [7, 10], [7, 10], [7, 10], [7, 10], - [7, 10], [7, 10], [7, 10], [7, 10], - [7, 10], [7, 10], [7, 10], [7, 10], - [7, 10], [7, 10], [7, 10], [7, 10], - [7, 11], [7, 11], [7, 11], [7, 11], // 0000101xxxxx - [7, 11], [7, 11], [7, 11], [7, 11], - [7, 11], [7, 11], [7, 11], [7, 11], - [7, 11], [7, 11], [7, 11], [7, 11], - [7, 11], [7, 11], [7, 11], [7, 11], - [7, 11], [7, 11], [7, 11], [7, 11], - [7, 11], [7, 11], [7, 11], [7, 11], - [7, 11], [7, 11], [7, 11], [7, 11], - [9, 15], [9, 15], [9, 15], [9, 15], // 000011000xxx - [9, 15], [9, 15], [9, 15], [9, 15], - [12, 128], // 000011001000 - [12, 192], // 000011001001 - [12, 26], // 000011001010 - [12, 27], // 000011001011 - [12, 28], // 000011001100 - [12, 29], // 000011001101 - [11, 19], [11, 19], // 00001100111x - [11, 20], [11, 20], // 00001101000x - [12, 34], // 000011010010 - [12, 35], // 000011010011 - [12, 36], // 000011010100 - [12, 37], // 000011010101 - [12, 38], // 000011010110 - [12, 39], // 000011010111 - [11, 21], [11, 21], // 00001101100x - [12, 42], // 000011011010 - [12, 43], // 000011011011 - [10, 0], [10, 0], [10, 0], [10, 0], // 0000110111xx - [7, 12], [7, 12], [7, 12], [7, 12], // 0000111xxxxx - [7, 12], [7, 12], [7, 12], [7, 12], - [7, 12], [7, 12], [7, 12], [7, 12], - [7, 12], [7, 12], [7, 12], [7, 12], - [7, 12], [7, 12], [7, 12], [7, 12], - [7, 12], [7, 12], [7, 12], [7, 12], - [7, 12], [7, 12], [7, 12], [7, 12], - [7, 12], [7, 12], [7, 12], [7, 12] - ]; + var blackTable1 = [ + [-1, -1], [-1, -1], // 000000000000x + [12, ccittEOL], [12, ccittEOL], // 000000000001x + [-1, -1], [-1, -1], [-1, -1], [-1, -1], // 00000000001xx + [-1, -1], [-1, -1], [-1, -1], [-1, -1], // 00000000010xx + [-1, -1], [-1, -1], [-1, -1], [-1, -1], // 00000000011xx + [-1, -1], [-1, -1], [-1, -1], [-1, -1], // 00000000100xx + [-1, -1], [-1, -1], [-1, -1], [-1, -1], // 00000000101xx + [-1, -1], [-1, -1], [-1, -1], [-1, -1], // 00000000110xx + [-1, -1], [-1, -1], [-1, -1], [-1, -1], // 00000000111xx + [11, 1792], [11, 1792], [11, 1792], [11, 1792], // 00000001000xx + [12, 1984], [12, 1984], // 000000010010x + [12, 2048], [12, 2048], // 000000010011x + [12, 2112], [12, 2112], // 000000010100x + [12, 2176], [12, 2176], // 000000010101x + [12, 2240], [12, 2240], // 000000010110x + [12, 2304], [12, 2304], // 000000010111x + [11, 1856], [11, 1856], [11, 1856], [11, 1856], // 00000001100xx + [11, 1920], [11, 1920], [11, 1920], [11, 1920], // 00000001101xx + [12, 2368], [12, 2368], // 000000011100x + [12, 2432], [12, 2432], // 000000011101x + [12, 2496], [12, 2496], // 000000011110x + [12, 2560], [12, 2560], // 000000011111x + [10, 18], [10, 18], [10, 18], [10, 18], // 0000001000xxx + [10, 18], [10, 18], [10, 18], [10, 18], + [12, 52], [12, 52], // 000000100100x + [13, 640], // 0000001001010 + [13, 704], // 0000001001011 + [13, 768], // 0000001001100 + [13, 832], // 0000001001101 + [12, 55], [12, 55], // 000000100111x + [12, 56], [12, 56], // 000000101000x + [13, 1280], // 0000001010010 + [13, 1344], // 0000001010011 + [13, 1408], // 0000001010100 + [13, 1472], // 0000001010101 + [12, 59], [12, 59], // 000000101011x + [12, 60], [12, 60], // 000000101100x + [13, 1536], // 0000001011010 + [13, 1600], // 0000001011011 + [11, 24], [11, 24], [11, 24], [11, 24], // 00000010111xx + [11, 25], [11, 25], [11, 25], [11, 25], // 00000011000xx + [13, 1664], // 0000001100100 + [13, 1728], // 0000001100101 + [12, 320], [12, 320], // 000000110011x + [12, 384], [12, 384], // 000000110100x + [12, 448], [12, 448], // 000000110101x + [13, 512], // 0000001101100 + [13, 576], // 0000001101101 + [12, 53], [12, 53], // 000000110111x + [12, 54], [12, 54], // 000000111000x + [13, 896], // 0000001110010 + [13, 960], // 0000001110011 + [13, 1024], // 0000001110100 + [13, 1088], // 0000001110101 + [13, 1152], // 0000001110110 + [13, 1216], // 0000001110111 + [10, 64], [10, 64], [10, 64], [10, 64], // 0000001111xxx + [10, 64], [10, 64], [10, 64], [10, 64] + ]; - var blackTable3 = [ - [-1, -1], [-1, -1], [-1, -1], [-1, -1], // 0000xx - [6, 9], // 000100 - [6, 8], // 000101 - [5, 7], [5, 7], // 00011x - [4, 6], [4, 6], [4, 6], [4, 6], // 0010xx - [4, 5], [4, 5], [4, 5], [4, 5], // 0011xx - [3, 1], [3, 1], [3, 1], [3, 1], // 010xxx - [3, 1], [3, 1], [3, 1], [3, 1], - [3, 4], [3, 4], [3, 4], [3, 4], // 011xxx - [3, 4], [3, 4], [3, 4], [3, 4], - [2, 3], [2, 3], [2, 3], [2, 3], // 10xxxx - [2, 3], [2, 3], [2, 3], [2, 3], - [2, 3], [2, 3], [2, 3], [2, 3], - [2, 3], [2, 3], [2, 3], [2, 3], - [2, 2], [2, 2], [2, 2], [2, 2], // 11xxxx - [2, 2], [2, 2], [2, 2], [2, 2], - [2, 2], [2, 2], [2, 2], [2, 2], - [2, 2], [2, 2], [2, 2], [2, 2] - ]; + 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], + [8, 13], [8, 13], [8, 13], [8, 13], + [11, 23], [11, 23], // 00000101000x + [12, 50], // 000001010010 + [12, 51], // 000001010011 + [12, 44], // 000001010100 + [12, 45], // 000001010101 + [12, 46], // 000001010110 + [12, 47], // 000001010111 + [12, 57], // 000001011000 + [12, 58], // 000001011001 + [12, 61], // 000001011010 + [12, 256], // 000001011011 + [10, 16], [10, 16], [10, 16], [10, 16], // 0000010111xx + [10, 17], [10, 17], [10, 17], [10, 17], // 0000011000xx + [12, 48], // 000001100100 + [12, 49], // 000001100101 + [12, 62], // 000001100110 + [12, 63], // 000001100111 + [12, 30], // 000001101000 + [12, 31], // 000001101001 + [12, 32], // 000001101010 + [12, 33], // 000001101011 + [12, 40], // 000001101100 + [12, 41], // 000001101101 + [11, 22], [11, 22], // 00000110111x + [8, 14], [8, 14], [8, 14], [8, 14], // 00000111xxxx + [8, 14], [8, 14], [8, 14], [8, 14], + [8, 14], [8, 14], [8, 14], [8, 14], + [8, 14], [8, 14], [8, 14], [8, 14], + [7, 10], [7, 10], [7, 10], [7, 10], // 0000100xxxxx + [7, 10], [7, 10], [7, 10], [7, 10], + [7, 10], [7, 10], [7, 10], [7, 10], + [7, 10], [7, 10], [7, 10], [7, 10], + [7, 10], [7, 10], [7, 10], [7, 10], + [7, 10], [7, 10], [7, 10], [7, 10], + [7, 10], [7, 10], [7, 10], [7, 10], + [7, 10], [7, 10], [7, 10], [7, 10], + [7, 11], [7, 11], [7, 11], [7, 11], // 0000101xxxxx + [7, 11], [7, 11], [7, 11], [7, 11], + [7, 11], [7, 11], [7, 11], [7, 11], + [7, 11], [7, 11], [7, 11], [7, 11], + [7, 11], [7, 11], [7, 11], [7, 11], + [7, 11], [7, 11], [7, 11], [7, 11], + [7, 11], [7, 11], [7, 11], [7, 11], + [7, 11], [7, 11], [7, 11], [7, 11], + [9, 15], [9, 15], [9, 15], [9, 15], // 000011000xxx + [9, 15], [9, 15], [9, 15], [9, 15], + [12, 128], // 000011001000 + [12, 192], // 000011001001 + [12, 26], // 000011001010 + [12, 27], // 000011001011 + [12, 28], // 000011001100 + [12, 29], // 000011001101 + [11, 19], [11, 19], // 00001100111x + [11, 20], [11, 20], // 00001101000x + [12, 34], // 000011010010 + [12, 35], // 000011010011 + [12, 36], // 000011010100 + [12, 37], // 000011010101 + [12, 38], // 000011010110 + [12, 39], // 000011010111 + [11, 21], [11, 21], // 00001101100x + [12, 42], // 000011011010 + [12, 43], // 000011011011 + [10, 0], [10, 0], [10, 0], [10, 0], // 0000110111xx + [7, 12], [7, 12], [7, 12], [7, 12], // 0000111xxxxx + [7, 12], [7, 12], [7, 12], [7, 12], + [7, 12], [7, 12], [7, 12], [7, 12], + [7, 12], [7, 12], [7, 12], [7, 12], + [7, 12], [7, 12], [7, 12], [7, 12], + [7, 12], [7, 12], [7, 12], [7, 12], + [7, 12], [7, 12], [7, 12], [7, 12], + [7, 12], [7, 12], [7, 12], [7, 12] + ]; - function constructor(str, params) { - this.str = str; - this.dict = str.dict; + var blackTable3 = [ + [-1, -1], [-1, -1], [-1, -1], [-1, -1], // 0000xx + [6, 9], // 000100 + [6, 8], // 000101 + [5, 7], [5, 7], // 00011x + [4, 6], [4, 6], [4, 6], [4, 6], // 0010xx + [4, 5], [4, 5], [4, 5], [4, 5], // 0011xx + [3, 1], [3, 1], [3, 1], [3, 1], // 010xxx + [3, 1], [3, 1], [3, 1], [3, 1], + [3, 4], [3, 4], [3, 4], [3, 4], // 011xxx + [3, 4], [3, 4], [3, 4], [3, 4], + [2, 3], [2, 3], [2, 3], [2, 3], // 10xxxx + [2, 3], [2, 3], [2, 3], [2, 3], + [2, 3], [2, 3], [2, 3], [2, 3], + [2, 3], [2, 3], [2, 3], [2, 3], + [2, 2], [2, 2], [2, 2], [2, 2], // 11xxxx + [2, 2], [2, 2], [2, 2], [2, 2], + [2, 2], [2, 2], [2, 2], [2, 2], + [2, 2], [2, 2], [2, 2], [2, 2] + ]; - params = params || new Dict(); + function constructor(str, params) { + this.str = str; + this.dict = str.dict; - this.encoding = params.get("K") || 0; - this.eoline = params.get("EndOfLine") || false; - this.byteAlign = params.get("EncodedByteAlign") || false; - this.columns = params.get("Columns") || 1728; - this.rows = params.get("Rows") || 0; - var eoblock = params.get("EndOfBlock"); - if (eoblock == null) - eoblock = true; - this.eoblock = eoblock; - this.black = params.get("BlackIs1") || false; + params = params || new Dict(); - this.codingLine = new Uint32Array(this.columns + 1); - this.refLine = new Uint32Array(this.columns + 2); - - this.codingLine[0] = this.columns; + this.encoding = params.get('K') || 0; + this.eoline = params.get('EndOfLine') || false; + this.byteAlign = params.get('EncodedByteAlign') || false; + this.columns = params.get('Columns') || 1728; + this.rows = params.get('Rows') || 0; + var eoblock = params.get('EndOfBlock'); + if (eoblock == null) + eoblock = true; + this.eoblock = eoblock; + this.black = params.get('BlackIs1') || false; + + this.codingLine = new Uint32Array(this.columns + 1); + this.refLine = new Uint32Array(this.columns + 2); + + this.codingLine[0] = this.columns; + this.codingPos = 0; + + this.row = 0; + this.nextLine2D = this.encoding < 0; + this.inputBits = 0; + this.inputBuf; + this.outputBits = 0; + this.buf = EOF; + + var code1; + while ((code1 = this.lookBits(12)) == 0) { + this.eatBits(1); + } + if (code1 == 1) { + this.eatBits(12); + } + if (this.encoding > 0) { + this.nextLine2D = !this.lookBits(1); + this.eatBits(1); + } + + DecodeStream.call(this); + } + + constructor.prototype = Object.create(DecodeStream.prototype); + + constructor.prototype.readBlock = function() { + while (!this.eof) { + var c = this.lookChar(); + this.buf = EOF; + this.ensureBuffer(this.bufferLength + 1); + this.buffer[this.bufferLength++] = c; + } + }; + + constructor.prototype.addPixels = function(a1, blackPixels) { + var codingLine = this.codingLine; + var codingPos = this.codingPos; + + if (a1 > codingLine[codingPos]) { + if (a1 > this.columns) { + warn('row is wrong length'); + this.err = true; + a1 = this.columns; + } + if ((codingPos & 1) ^ blackPixels) { + ++codingPos; + } + + codingLine[codingPos] = a1; + } + this.codingPos = codingPos; + }; + + constructor.prototype.addPixelsNeg = function(a1, blackPixels) { + var codingLine = this.codingLine; + var codingPos = this.codingPos; + + if (a1 > codingLine[codingPos]) { + if (a1 > this.columns) { + warn('row is wrong length'); + this.err = true; + a1 = this.columns; + } + if ((codingPos & 1) ^ blackPixels) + ++codingPos; + + codingLine[codingPos] = a1; + } else if (a1 < codingLine[codingPos]) { + if (a1 < 0) { + warn('invalid code'); + this.err = true; + a1 = 0; + } + while (codingPos > 0 && a1 < codingLine[codingPos - 1]) + --codingPos; + codingLine[codingPos] = a1; + } + + this.codingPos = codingPos; + }; + + constructor.prototype.lookChar = function() { + var refLine = this.refLine; + var codingLine = this.codingLine; + var columns = this.columns; + + var refPos, blackPixels, bits; + + if (this.buf != EOF) + return buf; + + if (this.outputBits == 0) { + if (this.eof) + return; + + this.err = false; + + if (this.nextLine2D) { + for (var i = 0; codingLine[i] < columns; ++i) + refLine[i] = codingLine[i]; + + refLine[i++] = columns; + refLine[i] = columns; + codingLine[0] = 0; this.codingPos = 0; + refPos = 0; + blackPixels = 0; - this.row = 0; - this.nextLine2D = this.encoding < 0; - this.inputBits = 0; - this.inputBuf; - this.outputBits = 0; - this.buf = EOF; + while (codingLine[this.codingPos] < columns) { + var code1 = this.getTwoDimCode(); + switch (code1) { + case twoDimPass: + this.addPixels(refLine[refPos + 1], blackPixels); + if (refLine[refPos + 1] < columns) + refPos += 2; + break; + case twoDimHoriz: + var code1 = 0, code2 = 0; + if (blackPixels) { + var code3; + do { + code1 += (code3 = this.getBlackCode()); + } while (code3 >= 64); + do { + code2 += (code3 = this.getWhiteCode()); + } while (code3 >= 64); + } else { + var code3; + do { + code1 += (code3 = this.getWhiteCode()); + } while (code3 >= 64); + do { + code2 += (code3 = this.getBlackCode()); + } while (code3 >= 64); + } + this.addPixels(codingLine[this.codingPos] + + code1, blackPixels); + if (codingLine[this.codingPos] < columns) { + this.addPixels(codingLine[this.codingPos] + code2, + blackPixels ^ 1); + } + while (refLine[refPos] <= codingLine[this.codingPos] && + refLine[refPos] < columns) { + refPos += 2; + } + break; + case twoDimVertR3: + this.addPixels(refLine[refPos] + 3, blackPixels); + blackPixels ^= 1; + if (codingLine[this.codingPos] < columns) { + ++refPos; + while (refLine[refPos] <= codingLine[this.codingPos] && + refLine[refPos] < columns) + refPos += 2; + } + break; + case twoDimVertR2: + this.addPixels(refLine[refPos] + 2, blackPixels); + blackPixels ^= 1; + if (codingLine[this.codingPos] < columns) { + ++refPos; + while (refLine[refPos] <= codingLine[this.codingPos] && + refLine[refPos] < columns) { + refPos += 2; + } + } + break; + case twoDimVertR1: + this.addPixels(refLine[refPos] + 1, blackPixels); + blackPixels ^= 1; + if (codingLine[this.codingPos] < columns) { + ++refPos; + while (refLine[refPos] <= codingLine[this.codingPos] && + refLine[refPos] < columns) + refPos += 2; + } + break; + case twoDimVert0: + this.addPixels(refLine[refPos], blackPixels); + blackPixels ^= 1; + if (codingLine[this.codingPos] < columns) { + ++refPos; + while (refLine[refPos] <= codingLine[this.codingPos] && + refLine[refPos] < columns) + refPos += 2; + } + break; + case twoDimVertL3: + this.addPixelsNeg(refLine[refPos] - 3, blackPixels); + blackPixels ^= 1; + if (codingLine[this.codingPos] < columns) { + if (refPos > 0) + --refPos; + else + ++refPos; + while (refLine[refPos] <= codingLine[this.codingPos] && + refLine[refPos] < columns) + refPos += 2; + } + break; + case twoDimVertL2: + this.addPixelsNeg(refLine[refPos] - 2, blackPixels); + blackPixels ^= 1; + if (codingLine[this.codingPos] < columns) { + if (refPos > 0) + --refPos; + else + ++refPos; + while (refLine[refPos] <= codingLine[this.codingPos] && + refLine[refPos] < columns) + refPos += 2; + } + break; + case twoDimVertL1: + this.addPixelsNeg(refLine[refPos] - 1, blackPixels); + blackPixels ^= 1; + if (codingLine[this.codingPos] < columns) { + if (refPos > 0) + --refPos; + else + ++refPos; - var code1; - while ((code1 = this.lookBits(12)) == 0) { - this.eatBits(1); + while (refLine[refPos] <= codingLine[this.codingPos] && + refLine[refPos] < columns) + refPos += 2; + } + break; + case EOF: + this.addPixels(columns, 0); + this.eof = true; + break; + default: + warn('bad 2d code'); + this.addPixels(columns, 0); + this.err = true; + break; + } + } + } else { + codingLine[0] = 0; + this.codingPos = 0; + blackPixels = 0; + while (codingLine[this.codingPos] < columns) { + code1 = 0; + if (blackPixels) { + do { + code1 += (code3 = this.getBlackCode()); + } while (code3 >= 64); + } else { + do { + code1 += (code3 = this.getWhiteCode()); + } while (code3 >= 64); + } + this.addPixels(codingLine[this.codingPos] + code1, blackPixels); + blackPixels ^= 1; + } + } + + if (this.byteAlign) + this.inputBits &= ~7; + + var gotEOL = false; + + if (!this.eoblock && this.row == this.rows - 1) { + this.eof = true; + } else { + code1 = this.lookBits(12); + while (code1 == 0) { + this.eatBits(1); + code1 = this.lookBits(12); } if (code1 == 1) { - this.eatBits(12); + this.eatBits(12); + gotEOL = true; + } else if (code1 == EOF) { + this.eof = true; } - if (this.encoding > 0) { - this.nextLine2D = !this.lookBits(1); + } + + if (!this.eof && this.encoding > 0) { + this.nextLine2D = !this.lookBits(1); + this.eatBits(1); + } + + if (this.eoblock && gotEOL) { + code1 = this.lookBits(12); + if (code1 == 1) { + this.eatBits(12); + if (this.encoding > 0) { + this.lookBits(1); this.eatBits(1); - } - - DecodeStream.call(this); - } - - constructor.prototype = Object.create(DecodeStream.prototype); - constructor.prototype.readBlock = function() { - while (!this.eof) { - var c = this.lookChar(); - this.buf = EOF; - this.ensureBuffer(this.bufferLength + 1); - this.buffer[this.bufferLength++] = c; - } - }; - constructor.prototype.addPixels = function(a1, blackPixels) { - var codingLine = this.codingLine; - var codingPos = this.codingPos; - - if (a1 > codingLine[codingPos]) { - if (a1 > this.columns) { - warn("row is wrong length"); - this.err = true; - a1 = this.columns; - } - if ((codingPos & 1) ^ blackPixels) { - ++codingPos; - } - - codingLine[codingPos] = a1; - } - this.codingPos = codingPos; - }; - constructor.prototype.addPixelsNeg = function(a1, blackPixels) { - var codingLine = this.codingLine; - var codingPos = this.codingPos; - - if (a1 > codingLine[codingPos]) { - if (a1 > this.columns) { - warn("row is wrong length"); - this.err = true; - a1 = this.columns; - } - if ((codingPos & 1) ^ blackPixels) - ++codingPos; - - codingLine[codingPos] = a1; - } else if (a1 < codingLine[codingPos]) { - if (a1 < 0) { - warn("invalid code"); - this.err = true; - a1 = 0; - } - while (codingPos > 0 && a1 < codingLine[codingPos - 1]) - --codingPos; - codingLine[codingPos] = a1; - } - - this.codingPos = codingPos; - }; - constructor.prototype.lookChar = function() { - var refLine = this.refLine; - var codingLine = this.codingLine; - var columns = this.columns; - - var refPos, blackPixels, bits; - - if (this.buf != EOF) - return buf; - - if (this.outputBits == 0) { - if (this.eof) - return; - - this.err = false; - - if (this.nextLine2D) { - for (var i = 0; codingLine[i] < columns; ++i) - refLine[i] = codingLine[i]; - - refLine[i++] = columns; - refLine[i] = columns; - codingLine[0] = 0; - this.codingPos = 0; - refPos = 0; - blackPixels = 0; - - while (codingLine[this.codingPos] < columns) { - var code1 = this.getTwoDimCode(); - switch (code1) { - case twoDimPass: - this.addPixels(refLine[refPos + 1], blackPixels); - if (refLine[refPos + 1] < columns) - refPos += 2; - break; - case twoDimHoriz: - var code1 = 0, code2 = 0; - if (blackPixels) { - var code3; - do { - code1 += (code3 = this.getBlackCode()); - } while (code3 >= 64); - do { - code2 += (code3 = this.getWhiteCode()); - } while (code3 >= 64); - } else { - var code3; - do { - code1 += (code3 = this.getWhiteCode()); - } while (code3 >= 64); - do { - code2 += (code3 = this.getBlackCode()); - } while (code3 >= 64); - } - this.addPixels(codingLine[this.codingPos] + code1, blackPixels); - if (codingLine[this.codingPos] < columns) { - this.addPixels(codingLine[this.codingPos] + code2, - blackPixels ^ 1); - } - while (refLine[refPos] <= codingLine[this.codingPos] - && refLine[refPos] < columns) { - refPos += 2; - } - break; - case twoDimVertR3: - this.addPixels(refLine[refPos] + 3, blackPixels); - blackPixels ^= 1; - if (codingLine[this.codingPos] < columns) { - ++refPos; - while (refLine[refPos] <= codingLine[this.codingPos] && - refLine[refPos] < columns) - refPos += 2; - } - break; - case twoDimVertR2: - this.addPixels(refLine[refPos] + 2, blackPixels); - blackPixels ^= 1; - if (codingLine[this.codingPos] < columns) { - ++refPos; - while (refLine[refPos] <= codingLine[this.codingPos] && - refLine[refPos] < columns) { - refPos += 2; - } - } - break; - case twoDimVertR1: - this.addPixels(refLine[refPos] + 1, blackPixels); - blackPixels ^= 1; - if (codingLine[this.codingPos] < columns) { - ++refPos; - while (refLine[refPos] <= codingLine[this.codingPos] && - refLine[refPos] < columns) - refPos += 2; - } - break; - case twoDimVert0: - this.addPixels(refLine[refPos], blackPixels); - blackPixels ^= 1; - if (codingLine[this.codingPos] < columns) { - ++refPos; - while (refLine[refPos] <= codingLine[this.codingPos] && - refLine[refPos] < columns) - refPos += 2; - } - break; - case twoDimVertL3: - this.addPixelsNeg(refLine[refPos] - 3, blackPixels); - blackPixels ^= 1; - if (codingLine[this.codingPos] < columns) { - if (refPos > 0) - --refPos; - else - ++refPos; - while (refLine[refPos] <= codingLine[this.codingPos] && - refLine[refPos] < columns) - refPos += 2; - } - break; - case twoDimVertL2: - this.addPixelsNeg(refLine[refPos] - 2, blackPixels); - blackPixels ^= 1; - if (codingLine[this.codingPos] < columns) { - if (refPos > 0) - --refPos; - else - ++refPos; - while (refLine[refPos] <= codingLine[this.codingPos] && - refLine[refPos] < columns) - refPos += 2; - } - break; - case twoDimVertL1: - this.addPixelsNeg(refLine[refPos] - 1, blackPixels); - blackPixels ^= 1; - if (codingLine[this.codingPos] < columns) { - if (refPos > 0) - --refPos; - else - ++refPos; - - while (refLine[refPos] <= codingLine[this.codingPos] && - refLine[refPos] < columns) - refPos += 2; - } - break; - case EOF: - this.addPixels(columns, 0); - this.eof = true; - break; - default: - warn("bad 2d code"); - this.addPixels(columns, 0); - this.err = true; - break; - } - } - } else { - codingLine[0] = 0; - this.codingPos = 0; - blackPixels = 0; - while(codingLine[this.codingPos] < columns) { - code1 = 0; - if (blackPixels) { - do { - code1 += (code3 = this.getBlackCode()); - } while (code3 >= 64); - } else { - do { - code1 += (code3 = this.getWhiteCode()); - } while (code3 >= 64); - } - this.addPixels(codingLine[this.codingPos] + code1, blackPixels); - blackPixels ^= 1; - } - } - - if (this.byteAlign) - inputBits &= ~7; - - var gotEOL = false; - - if (!this.eoblock && this.row == this.rows - 1) { - this.eof = true; - } else { - code1 = this.lookBits(12); - while (code1 == 0) { - this.eatBits(1); - code1 = this.lookBits(12); - } - if (code1 == 1) { - this.eatBits(12); - gotEOL = true; - } else if (code1 == EOF) { - this.eof = true; - } - } - - if (!this.eof && this.encoding > 0) { - this.nextLine2D = !this.lookBits(1); + } + if (this.encoding >= 0) { + for (var i = 0; i < 4; ++i) { + code1 = this.lookBits(12); + if (code1 != 1) + warning('bad rtc code'); + this.eatBits(12); + if (this.encoding > 0) { + this.lookBits(1); this.eatBits(1); + } } - - if (this.eoblock && gotEOL) { - code1 = this.lookBits(12); - if (code1 == 1) { - this.eatBits(12); - if (this.encoding > 0) { - this.lookBits(1); - this.eatBits(1); - } - if (this.encoding >= 0) { - for (var i = 0; i < 4; ++i) { - code1 = this.lookBits(12); - if (code1 != 1) - warning("bad rtc code"); - this.eatBits(12); - if (this.encoding > 0) { - this.lookBits(1); - this.eatBits(1); - } - } - } - this.eof = true; - } - } else if (this.err && this.eoline) { - var code1; - while (true) { - code1 = this.lookBits(13); - if (code1 == EOF) { - this.eof = true; - return; - } - if ((code1 >> 1) == 1) { - break; - } - this.eatBits(1); - } - this.eatBits(12); - if (this.encoding > 0) { - this.eatBits(1); - this.nextLine2D = !(code1 & 1); - } - } - - if (codingLine[0] > 0) - this.outputBits = codingLine[this.codingPos = 0]; - else - this.outputBits = codingLine[this.codingPos = 1]; - this.row++; + } + this.eof = true; } - - if (this.outputBits >= 8) { - this.buf = (this.codingPos & 1) ? 0 : 0xFF; - this.outputBits -= 8; - if (this.outputBits == 0 && codingLine[this.codingPos] < columns) { - this.codingPos++; - this.outputBits = codingLine[this.codingPos] - codingLine[this.codingPos - 1]; - } - } else { - var bits = 8; - this.buf = 0; - do { - if (this.outputBits > bits) { - this.buf <<= bits; - if (!(this.codingPos & 1)) { - this.buf |= 0xFF >> (8 - bits); - } - this.outputBits -= bits; - bits = 0; - } else { - this.buf <<= this.outputBits; - if (!(this.codingPos & 1)) { - this.buf |= 0xFF >> (8 - this.outputBits); - } - bits -= this.outputBits; - this.outputBits = 0; - if (codingLine[this.codingPos] < columns) { - this.codingPos++; - this.outputBits = codingLine[this.codingPos] - codingLine[this.codingPos - 1]; - } else if (bits > 0) { - this.buf <<= bits; - bits = 0; - } - } - } while (bits); + } else if (this.err && this.eoline) { + var code1; + while (true) { + code1 = this.lookBits(13); + if (code1 == EOF) { + this.eof = true; + return; + } + if ((code1 >> 1) == 1) { + break; + } + this.eatBits(1); } - if (this.black) { - this.buf ^= 0xFF; + this.eatBits(12); + if (this.encoding > 0) { + this.eatBits(1); + this.nextLine2D = !(code1 & 1); } - return this.buf; - }; - constructor.prototype.getTwoDimCode = function() { - var code = 0; - var p; - if (this.eoblock) { - code = this.lookBits(7); - p = twoDimTable[code]; - if (p[0] > 0) { - this.eatBits(p[0]); - return p[1]; - } - } else { - for (var n = 1; n <= 7; ++n) { - code = this.lookBits(n); - if (n < 7) { - code <<= 7 - n; - } - p = twoDimTable[code]; - if (p[0] == n) { - this.eatBits(n); - return p[1]; - } - } - } - warn("Bad two dim code"); - return EOF; - }; + } - constructor.prototype.getWhiteCode = function() { - var code = 0; - var p; - var n; - if (this.eoblock) { - code = this.lookBits(12); - if (code == EOF) - return 1; - - if ((code >> 5) == 0) - p = whiteTable1[code]; - else - p = whiteTable2[code >> 3]; - - if (p[0] > 0) { - this.eatBits(p[0]); - return p[1]; - } - } else { - for (var n = 1; n <= 9; ++n) { - code = this.lookBits(n); - if (code == EOF) - return 1; - - if (n < 9) - code <<= 9 - n; - p = whiteTable2[code]; - if (p[0] == n) { - this.eatBits(n); - return p[0]; - } - } - for (var n = 11; n <= 12; ++n) { - code == this.lookBits(n); - if (code == EOF) - return 1; - if (n < 12) - code <<= 12 - n; - p = whiteTable1[code]; - if (p[0] == n) { - this.eatBits(n); - return p[1]; - } - } - } - warn("bad white code"); - this.eatBits(1); - return 1; - }; - constructor.prototype.getBlackCode = function() { - var code, p, n; - if (this.eoblock) { - code = this.lookBits(13); - if (code == EOF) - return 1; - if ((code >> 7) == 0) - p = blackTable1[code]; - else if ((code >> 9) == 0 && (code >> 7) != 0) - p = blackTable2[(code >> 1) - 64]; - else - p = blackTable3[code >> 7]; - - if (p[0] > 0) { - this.eatBits(p[0]); - return p[1]; - } - } else { - for (var n = 2; n <= 6; ++n) { - code = this.lookBits(n); - if (code == EOF) - return 1; - if (n < 6) - code <<= 6 - n; - - p = blackTable3[code]; - if (p[0] == n) { - this.eatBits(n); - return p[1]; - } - } - for (var n = 7; n <= 12; ++n) { - code = this.lookBits(n); - if (code == EOF) - return 1; - if (n < 12) - code <<= 12 - n; - if (code >= 64) { - p = blackTable2[code - 64]; - if (p[0] == n) { - this.eatBits(n); - return p[1]; - } - } - } - for (n = 10; n <= 13; ++n) { - code = this.lookBits(n); - if (code == EOF) - return 1; - if (n < 13) - code << 13 - n; - p = blackTable1[code]; - if (p[0] == n) { - this.eatBits(n); - return p[1]; - } - } - } - warn("bad black code"); - this.eatBits(1); - return 1; - }; - constructor.prototype.lookBits = function(n) { - var c; - while (this.inputBits < n) { - if ((c = this.str.getByte()) == null) { - if (this.inputBits == 0) - return EOF; - return (this.inputBuf << (n - this.inputBits)) - & (0xFFFF >> (16 - n)); - } - this.inputBuf = (this.inputBuf << 8) + c; - this.inputBits += 8; - } - return (this.inputBuf >> (this.inputBits - n)) & (0xFFFF >> (16 - n)); - }; - constructor.prototype.eatBits = function(n) { - if ((this.inputBits -= n) < 0) - this.inputBits = 0; + if (codingLine[0] > 0) + this.outputBits = codingLine[this.codingPos = 0]; + else + this.outputBits = codingLine[this.codingPos = 1]; + this.row++; } - return constructor; + if (this.outputBits >= 8) { + this.buf = (this.codingPos & 1) ? 0 : 0xFF; + this.outputBits -= 8; + if (this.outputBits == 0 && codingLine[this.codingPos] < columns) { + this.codingPos++; + this.outputBits = (codingLine[this.codingPos] - + codingLine[this.codingPos - 1]); + } + } else { + var bits = 8; + this.buf = 0; + do { + if (this.outputBits > bits) { + this.buf <<= bits; + if (!(this.codingPos & 1)) { + this.buf |= 0xFF >> (8 - bits); + } + this.outputBits -= bits; + bits = 0; + } else { + this.buf <<= this.outputBits; + if (!(this.codingPos & 1)) { + this.buf |= 0xFF >> (8 - this.outputBits); + } + bits -= this.outputBits; + this.outputBits = 0; + if (codingLine[this.codingPos] < columns) { + this.codingPos++; + this.outputBits = (codingLine[this.codingPos] - + codingLine[this.codingPos - 1]); + } else if (bits > 0) { + this.buf <<= bits; + bits = 0; + } + } + } while (bits); + } + if (this.black) { + this.buf ^= 0xFF; + } + return this.buf; + }; + + constructor.prototype.getTwoDimCode = function() { + var code = 0; + var p; + if (this.eoblock) { + code = this.lookBits(7); + p = twoDimTable[code]; + if (p[0] > 0) { + this.eatBits(p[0]); + return p[1]; + } + } else { + for (var n = 1; n <= 7; ++n) { + code = this.lookBits(n); + if (n < 7) { + code <<= 7 - n; + } + p = twoDimTable[code]; + if (p[0] == n) { + this.eatBits(n); + return p[1]; + } + } + } + warn('Bad two dim code'); + return EOF; + }; + + constructor.prototype.getWhiteCode = function() { + var code = 0; + var p; + var n; + if (this.eoblock) { + code = this.lookBits(12); + if (code == EOF) + return 1; + + if ((code >> 5) == 0) + p = whiteTable1[code]; + else + p = whiteTable2[code >> 3]; + + if (p[0] > 0) { + this.eatBits(p[0]); + return p[1]; + } + } else { + for (var n = 1; n <= 9; ++n) { + code = this.lookBits(n); + if (code == EOF) + return 1; + + if (n < 9) + code <<= 9 - n; + p = whiteTable2[code]; + if (p[0] == n) { + this.eatBits(n); + return p[0]; + } + } + for (var n = 11; n <= 12; ++n) { + code == this.lookBits(n); + if (code == EOF) + return 1; + if (n < 12) + code <<= 12 - n; + p = whiteTable1[code]; + if (p[0] == n) { + this.eatBits(n); + return p[1]; + } + } + } + warn('bad white code'); + this.eatBits(1); + return 1; + }; + + constructor.prototype.getBlackCode = function() { + var code, p, n; + if (this.eoblock) { + code = this.lookBits(13); + if (code == EOF) + return 1; + if ((code >> 7) == 0) + p = blackTable1[code]; + else if ((code >> 9) == 0 && (code >> 7) != 0) + p = blackTable2[(code >> 1) - 64]; + else + p = blackTable3[code >> 7]; + + if (p[0] > 0) { + this.eatBits(p[0]); + return p[1]; + } + } else { + for (var n = 2; n <= 6; ++n) { + code = this.lookBits(n); + if (code == EOF) + return 1; + if (n < 6) + code <<= 6 - n; + + p = blackTable3[code]; + if (p[0] == n) { + this.eatBits(n); + return p[1]; + } + } + for (var n = 7; n <= 12; ++n) { + code = this.lookBits(n); + if (code == EOF) + return 1; + if (n < 12) + code <<= 12 - n; + if (code >= 64) { + p = blackTable2[code - 64]; + if (p[0] == n) { + this.eatBits(n); + return p[1]; + } + } + } + for (n = 10; n <= 13; ++n) { + code = this.lookBits(n); + if (code == EOF) + return 1; + if (n < 13) + code << 13 - n; + p = blackTable1[code]; + if (p[0] == n) { + this.eatBits(n); + return p[1]; + } + } + } + warn('bad black code'); + this.eatBits(1); + return 1; + }; + + constructor.prototype.lookBits = function(n) { + var c; + while (this.inputBits < n) { + if ((c = this.str.getByte()) == null) { + if (this.inputBits == 0) + return EOF; + return ((this.inputBuf << (n - this.inputBits)) & + (0xFFFF >> (16 - n))); + } + this.inputBuf = (this.inputBuf << 8) + c; + this.inputBits += 8; + } + return (this.inputBuf >> (this.inputBits - n)) & (0xFFFF >> (16 - n)); + }; + + constructor.prototype.eatBits = function(n) { + if ((this.inputBits -= n) < 0) + this.inputBits = 0; + } + + return constructor; })(); var Name = (function() { - function constructor(name) { - this.name = name; - } + function constructor(name) { + this.name = name; + } - constructor.prototype = { - }; + constructor.prototype = { + }; - return constructor; + return constructor; })(); var Cmd = (function() { - function constructor(cmd) { - this.cmd = cmd; - } + function constructor(cmd) { + this.cmd = cmd; + } - constructor.prototype = { - }; + constructor.prototype = { + }; - return constructor; + return constructor; })(); var Dict = (function() { - function constructor() { - this.map = Object.create(null); - } + function constructor() { + this.map = Object.create(null); + } - constructor.prototype = { - get: function(key) { - if (key in this.map) - return this.map[key]; - return null; - }, - get2: function(key1, key2) { - return this.get(key1) || this.get(key2); - }, - get3: function(key1, key2, key3) { - return this.get(key1) || this.get(key2) || this.get(key3); - }, - has: function(key) { - return key in this.map; - }, - set: function(key, value) { - this.map[key] = value; - }, - forEach: function(aCallback) { - for (var key in this.map) - aCallback(key, this.map[key]); - } - }; - return constructor; + constructor.prototype = { + get: function(key1, key2, key3) { + var value; + if (typeof (value = this.map[key1]) != 'undefined' || key1 in this.map || typeof key2 == 'undefined') { + return value; + } + if (typeof (value = this.map[key2]) != 'undefined' || key2 in this.map || typeof key3 == 'undefined') { + return value; + } + + return this.map[key3] || null; + }, + + set: function(key, value) { + this.map[key] = value; + }, + + has: function(key) { + return key in this.map; + }, + + forEach: function(callback) { + for (var key in this.map) { + callback(key, this.map[key]); + } + } + }; + + return constructor; })(); var Ref = (function() { - function constructor(num, gen) { - this.num = num; - this.gen = gen; - } + function constructor(num, gen) { + this.num = num; + this.gen = gen; + } - constructor.prototype = { - }; + constructor.prototype = { + }; - return constructor; + return constructor; })(); function IsBool(v) { - return typeof v == "boolean"; + return typeof v == 'boolean'; } function IsInt(v) { - return typeof v == "number" && ((v|0) == v); + return typeof v == 'number' && ((v | 0) == v); } function IsNum(v) { - return typeof v == "number"; + return typeof v == 'number'; } function IsString(v) { - return typeof v == "string"; + return typeof v == 'string'; } function IsNull(v) { - return v === null; + return v === null; } function IsName(v) { - return v instanceof Name; + return v instanceof Name; } function IsCmd(v, cmd) { - return v instanceof Cmd && (!cmd || v.cmd == cmd); + return v instanceof Cmd && (!cmd || v.cmd == cmd); } function IsDict(v, type) { - return v instanceof Dict && (!type || v.get("Type").name == type); + return v instanceof Dict && (!type || v.get('Type').name == type); } function IsArray(v) { - return v instanceof Array; + return v instanceof Array; } function IsStream(v) { - return typeof v == "object" && v != null && ("getChar" in v); + return typeof v == 'object' && v != null && ('getChar' in v); } function IsRef(v) { - return v instanceof Ref; + return v instanceof Ref; } function IsPDFFunction(v) { - var fnDict; - if (typeof v != "object") - return false; - else if (IsDict(v)) - fnDict = v; - else if (IsStream(v)) - fnDict = v.dict; - else - return false; - return fnDict.has("FunctionType"); + var fnDict; + if (typeof v != 'object') + return false; + else if (IsDict(v)) + fnDict = v; + else if (IsStream(v)) + fnDict = v.dict; + else + return false; + return fnDict.has('FunctionType'); } var EOF = {}; function IsEOF(v) { - return v == EOF; + return v == EOF; } var None = {}; function IsNone(v) { - return v == None; + return v == None; } var Lexer = (function() { - function constructor(stream) { - this.stream = stream; - } + function constructor(stream) { + this.stream = stream; + } - constructor.isSpace = function(ch) { - return ch == " " || ch == "\t"; - } + constructor.isSpace = function(ch) { + return ch == ' ' || ch == '\t'; + } - // A '1' in this array means the character is white space. A '1' or - // '2' means the character ends a name or command. - var specialChars = [ - 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, // 0x - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1x - 1, 0, 0, 0, 0, 2, 0, 0, 2, 2, 0, 0, 0, 0, 0, 2, // 2x - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 0, // 3x - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 4x - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 0, 0, // 5x - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 6x - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 0, 0, // 7x - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 8x - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 9x - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // ax - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // bx - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // cx - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // dx - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // ex - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // fx - ]; + // A '1' in this array means the character is white space. A '1' or + // '2' means the character ends a name or command. + var specialChars = [ + 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, // 0x + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1x + 1, 0, 0, 0, 0, 2, 0, 0, 2, 2, 0, 0, 0, 0, 0, 2, // 2x + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 0, // 3x + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 4x + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 0, 0, // 5x + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 6x + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 0, 0, // 7x + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 8x + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 9x + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // ax + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // bx + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // cx + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // dx + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // ex + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // fx + ]; - var MIN_INT = (1<<31) | 0; - var MAX_INT = (MIN_INT - 1) | 0; - var MIN_UINT = 0; - var MAX_UINT = ((1<<30) * 4) - 1; + var MIN_INT = (1 << 31) | 0; + var MAX_INT = (MIN_INT - 1) | 0; + var MIN_UINT = 0; + var MAX_UINT = ((1 << 30) * 4) - 1; - function ToHexDigit(ch) { - if (ch >= "0" && ch <= "9") - return ch.charCodeAt(0) - 48; - ch = ch.toUpperCase(); - if (ch >= "A" && ch <= "F") - return ch.charCodeAt(0) - 55; - return -1; - } + function ToHexDigit(ch) { + if (ch >= '0' && ch <= '9') + return ch.charCodeAt(0) - 48; + ch = ch.toUpperCase(); + if (ch >= 'A' && ch <= 'F') + return ch.charCodeAt(0) - 55; + return -1; + } - constructor.prototype = { - getNumber: function(ch) { - var floating = false; - var str = ch; - var stream = this.stream; - do { - ch = stream.lookChar(); - if (ch == "." && !floating) { - str += ch; - floating = true; - } else if (ch == "-") { - // ignore minus signs in the middle of numbers to match - // Adobe's behavior - warn("Badly formated number"); - } else if (ch >= "0" && ch <= "9") { - str += ch; - } else if (ch == "e" || ch == "E") { - floating = true; - } else { - // the last character doesn't belong to us - break; - } - stream.skip(); - } while (true); - var value = parseFloat(str); - if (isNaN(value)) - error("Invalid floating point number"); - return value; - }, - getString: function() { - var n = 0; - var numParen = 1; - var done = false; - var str = ""; - var stream = this.stream; - var ch; - do { - switch (ch = stream.getChar()) { - case undefined: - warn("Unterminated string"); - done = true; - break; - case '(': - ++numParen; - str += ch; - break; - case ')': - if (--numParen == 0) { - done = true; - } else { - str += ch; - } - break; - case '\\': - switch (ch = stream.getChar()) { - case undefined: - warn("Unterminated string"); - done = true; - break; - case 'n': - str += '\n'; - break; - case 'r': - str += '\r'; - break; - case 't': - str += '\t'; - break; - case 'b': - str += '\b'; - break; - case 'f': - str += '\f'; - break; - case '\\': - case '(': - case ')': - str += ch; - break; - case '0': case '1': case '2': case '3': - case '4': case '5': case '6': case '7': - var x = ch - '0'; - ch = stream.lookChar(); - if (ch >= '0' && ch <= '7') { - stream.skip(); - x = (x << 3) + (ch - '0'); - ch = stream.lookChar(); - if (ch >= '0' && ch <= '7') { - stream.skip(); - x = (x << 3) + (ch - '0'); - } - } - - str += String.fromCharCode(x); - break; - case '\r': - ch = stream.lookChar(); - if (ch == '\n') - stream.skip(); - break; - case '\n': - break; - default: - str += ch; - break; - } - break; - default: - str += ch; - break; - } - } while (!done); - if (!str.length) - return EOF; - return str; - }, - getName: function(ch) { - var str = ""; - var stream = this.stream; - while (!!(ch = stream.lookChar()) && !specialChars[ch.charCodeAt(0)]) { - stream.skip(); - if (ch == "#") { - ch = stream.lookChar(); - var x = ToHexDigit(ch); - if (x != -1) { - stream.skip(); - var x2 = ToHexDigit(stream.getChar()); - if (x2 == -1) - error("Illegal digit in hex char in name"); - str += String.fromCharCode((x << 4) | x2); - } else { - str += "#"; - str += ch; - } - } else { - str += ch; - } - } - if (str.length > 128) - error("Warning: name token is longer than allowed by the specification"); - return new Name(str); - }, - getHexString: function(ch) { - var str = ""; - var stream = this.stream; - while (1) { - ch = stream.getChar(); - if (ch == '>') { - break; - } - if (!ch) { - warn("Unterminated hex string"); - break; - } - if (specialChars[ch.charCodeAt(0)] != 1) { - var x, x2; - if (((x = ToHexDigit(ch)) == -1) || - ((x2 = ToHexDigit(stream.getChar())) == -1)) { - error("Illegal character in hex string"); - break; - } - str += String.fromCharCode((x << 4) | x2); - } - } - return str; - }, - getObj: function() { - // skip whitespace and comments - var comment = false; - var stream = this.stream; - var ch; - while (true) { - if (!(ch = stream.getChar())) - return EOF; - if (comment) { - if (ch == '\r' || ch == '\n') - comment = false; - } else if (ch == '%') { - comment = true; - } else if (specialChars[ch.charCodeAt(0)] != 1) { - break; - } - } - - // start reading token - switch (ch) { - case '0': case '1': case '2': case '3': case '4': - case '5': case '6': case '7': case '8': case '9': - case '+': case '-': case '.': - return this.getNumber(ch); - case '(': - return this.getString(); - case '/': - return this.getName(ch); - // array punctuation - case '[': - case ']': - return new Cmd(ch); - // hex string or dict punctuation - case '<': - ch = stream.lookChar(); - if (ch == '<') { - // dict punctuation - stream.skip(); - return new Cmd("<<"); - } - return this.getHexString(ch); - // dict punctuation - case '>': - ch = stream.lookChar(); - if (ch == '>') { - stream.skip(); - return new Cmd(">>"); - } - case "{": - case "}": - return new Cmd(ch); - // fall through - case ')': - error("Illegal character"); - return Error; - } - - // command - var str = ch; - while (!!(ch = stream.lookChar()) && !specialChars[ch.charCodeAt(0)]) { - stream.skip(); - if (str.length == 128) { - error("Command token too long"); - break; - } - str += ch; - } - if (str == "true") - return true; - if (str == "false") - return false; - if (str == "null") - return null; - return new Cmd(str); - }, - skipToNextLine: function() { - var stream = this.stream; - while (true) { - var ch = stream.getChar(); - if (!ch || ch == "\n") - return; - if (ch == "\r") { - if ((ch = stream.lookChar()) == "\n") - stream.skip(); - return; - } - } + constructor.prototype = { + getNumber: function(ch) { + var floating = false; + var str = ch; + var stream = this.stream; + do { + ch = stream.lookChar(); + if (ch == '.' && !floating) { + str += ch; + floating = true; + } else if (ch == '-') { + // ignore minus signs in the middle of numbers to match + // Adobe's behavior + warn('Badly formated number'); + } else if (ch >= '0' && ch <= '9') { + str += ch; + } else if (ch == 'e' || ch == 'E') { + floating = true; + } else { + // the last character doesn't belong to us + break; } - }; + stream.skip(); + } while (true); + var value = parseFloat(str); + if (isNaN(value)) + error('Invalid floating point number'); + return value; + }, + getString: function() { + var n = 0; + var numParen = 1; + var done = false; + var str = ''; + var stream = this.stream; + var ch; + do { + switch (ch = stream.getChar()) { + case undefined: + warn('Unterminated string'); + done = true; + break; + case '(': + ++numParen; + str += ch; + break; + case ')': + if (--numParen == 0) { + done = true; + } else { + str += ch; + } + break; + case '\\': + switch (ch = stream.getChar()) { + case undefined: + warn('Unterminated string'); + done = true; + break; + case 'n': + str += '\n'; + break; + case 'r': + str += '\r'; + break; + case 't': + str += '\t'; + break; + case 'b': + str += '\b'; + break; + case 'f': + str += '\f'; + break; + case '\\': + case '(': + case ')': + str += ch; + break; + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + var x = ch - '0'; + ch = stream.lookChar(); + if (ch >= '0' && ch <= '7') { + stream.skip(); + x = (x << 3) + (ch - '0'); + ch = stream.lookChar(); + if (ch >= '0' && ch <= '7') { + stream.skip(); + x = (x << 3) + (ch - '0'); + } + } - return constructor; + str += String.fromCharCode(x); + break; + case '\r': + ch = stream.lookChar(); + if (ch == '\n') + stream.skip(); + break; + case '\n': + break; + default: + str += ch; + break; + } + break; + default: + str += ch; + break; + } + } while (!done); + if (!str.length) + return EOF; + return str; + }, + getName: function(ch) { + var str = ''; + var stream = this.stream; + while (!!(ch = stream.lookChar()) && !specialChars[ch.charCodeAt(0)]) { + stream.skip(); + if (ch == '#') { + ch = stream.lookChar(); + var x = ToHexDigit(ch); + if (x != -1) { + stream.skip(); + var x2 = ToHexDigit(stream.getChar()); + if (x2 == -1) + error('Illegal digit in hex char in name'); + str += String.fromCharCode((x << 4) | x2); + } else { + str += '#'; + str += ch; + } + } else { + str += ch; + } + } + if (str.length > 128) + error('Warning: name token is longer than allowed by the spec.'); + return new Name(str); + }, + getHexString: function(ch) { + var str = ''; + var stream = this.stream; + while (1) { + ch = stream.getChar(); + if (ch == '>') { + break; + } + if (!ch) { + warn('Unterminated hex string'); + break; + } + if (specialChars[ch.charCodeAt(0)] != 1) { + var x, x2; + if (((x = ToHexDigit(ch)) == -1) || + ((x2 = ToHexDigit(stream.getChar())) == -1)) { + error('Illegal character in hex string'); + break; + } + str += String.fromCharCode((x << 4) | x2); + } + } + return str; + }, + getObj: function() { + // skip whitespace and comments + var comment = false; + var stream = this.stream; + var ch; + while (true) { + if (!(ch = stream.getChar())) + return EOF; + if (comment) { + if (ch == '\r' || ch == '\n') + comment = false; + } else if (ch == '%') { + comment = true; + } else if (specialChars[ch.charCodeAt(0)] != 1) { + break; + } + } + + // start reading token + switch (ch) { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + case '+': case '-': case '.': + return this.getNumber(ch); + case '(': + return this.getString(); + case '/': + return this.getName(ch); + // array punctuation + case '[': + case ']': + return new Cmd(ch); + // hex string or dict punctuation + case '<': + ch = stream.lookChar(); + if (ch == '<') { + // dict punctuation + stream.skip(); + return new Cmd('<<'); + } + return this.getHexString(ch); + // dict punctuation + case '>': + ch = stream.lookChar(); + if (ch == '>') { + stream.skip(); + return new Cmd('>>'); + } + case '{': + case '}': + return new Cmd(ch); + // fall through + case ')': + error('Illegal character'); + return Error; + } + + // command + var str = ch; + while (!!(ch = stream.lookChar()) && !specialChars[ch.charCodeAt(0)]) { + stream.skip(); + if (str.length == 128) { + error('Command token too long'); + break; + } + str += ch; + } + if (str == 'true') + return true; + if (str == 'false') + return false; + if (str == 'null') + return null; + return new Cmd(str); + }, + skipToNextLine: function() { + var stream = this.stream; + while (true) { + var ch = stream.getChar(); + if (!ch || ch == '\n') + return; + if (ch == '\r') { + if ((ch = stream.lookChar()) == '\n') + stream.skip(); + return; + } + } + } + }; + + return constructor; })(); var Parser = (function() { - function constructor(lexer, allowStreams, xref) { - this.lexer = lexer; - this.allowStreams = allowStreams; - this.xref = xref; - this.inlineImg = 0; - this.refill(); - } + function constructor(lexer, allowStreams, xref) { + this.lexer = lexer; + this.allowStreams = allowStreams; + this.xref = xref; + this.inlineImg = 0; + this.refill(); + } - constructor.prototype = { - refill: function() { - this.buf1 = this.lexer.getObj(); - this.buf2 = this.lexer.getObj(); - }, - shift: function() { - if (this.inlineImg > 0) { - if (this.inlineImg < 2) { - this.inlineImg++; - } else { - // in a damaged content stream, if 'ID' shows up in the middle - // of a dictionary, we need to reset - this.inlineImg = 0; - } - } else if (IsCmd(this.buf2, "ID")) { - this.lexer.skip(); // skip char after 'ID' command - this.inlineImg = 1; - } - this.buf1 = this.buf2; - // don't buffer inline image data - this.buf2 = (this.inlineImg > 0) ? null : this.lexer.getObj(); - }, - getObj: function(cipherTransform) { - // refill buffer after inline image data - if (this.inlineImg == 2) - this.refill(); - - if (IsCmd(this.buf1, "[")) { // array - this.shift(); - var array = []; - while (!IsCmd(this.buf1, "]") && !IsEOF(this.buf1)) - array.push(this.getObj()); - if (IsEOF(this.buf1)) - error("End of file inside array"); - this.shift(); - return array; - } else if (IsCmd(this.buf1, "<<")) { // dictionary or stream - this.shift(); - var dict = new Dict(); - while (!IsCmd(this.buf1, ">>") && !IsEOF(this.buf1)) { - if (!IsName(this.buf1)) { - error("Dictionary key must be a name object"); - shift(); - } else { - var key = this.buf1.name; - this.shift(); - if (IsEOF(this.buf1)) - break; - dict.set(key, this.getObj(cipherTransform)); - } - } - if (IsEOF(this.buf1)) - error("End of file inside dictionary"); - - // stream objects are not allowed inside content streams or - // object streams - if (this.allowStreams && IsCmd(this.buf2, "stream")) { - return this.makeStream(dict, cipherTransform); - } else { - this.shift(); - } - return dict; - - } else if (IsInt(this.buf1)) { // indirect reference or integer - var num = this.buf1; - this.shift(); - if (IsInt(this.buf1) && IsCmd(this.buf2, "R")) { - var ref = new Ref(num, this.buf1); - this.shift(); - this.shift(); - return ref; - } - return num; - } else if (IsString(this.buf1)) { // string - var str = this.buf1; - this.shift(); - if (cipherTransform) - str = cipherTransform.decryptString(str); - return str; - } - - // simple object - var obj = this.buf1; - this.shift(); - return obj; - }, - makeStream: function(dict, cipherTransform) { - var lexer = this.lexer; - var stream = lexer.stream; - - // get stream start position - lexer.skipToNextLine(); - var pos = stream.pos; - - // get length - var length = dict.get("Length"); - var xref = this.xref; - if (xref) - length = xref.fetchIfRef(length); - if (!IsInt(length)) { - error("Bad 'Length' attribute in stream"); - length = 0; - } - - // skip over the stream data - stream.pos = pos + length; - this.shift(); // '>>' - this.shift(); // 'stream' - if (!IsCmd(this.buf1, "endstream")) - error("Missing 'endstream'"); - this.shift(); - - stream = stream.makeSubStream(pos, length, dict); - if (cipherTransform) - stream = cipherTransform.createStream(stream); - stream = this.filter(stream, dict, length); - stream.parameters = dict; - return stream; - }, - filter: function(stream, dict, length) { - var filter = dict.get2("Filter", "F"); - var params = dict.get2("DecodeParms", "DP"); - if (IsName(filter)) - return this.makeFilter(stream, filter.name, length, params); - if (IsArray(filter)) { - var filterArray = filter; - var paramsArray = params; - for (var i = 0, ii = filterArray.length; i < ii; ++i) { - filter = filterArray[i]; - if (!IsName(filter)) - error("Bad filter name"); - else { - params = null; - if (IsArray(paramsArray) && (i in paramsArray)) - params = paramsArray[i]; - stream = this.makeFilter(stream, filter.name, length, params); - } - } - } - return stream; - }, - makeFilter: function(stream, name, length, params) { - if (name == "FlateDecode" || name == "Fl") { - if (params) { - return new PredictorStream(new FlateStream(stream), params); - } - return new FlateStream(stream); - } else if (name == "DCTDecode") { - var bytes = stream.getBytes(length); - return new JpegStream(bytes, stream.dict); - } else if (name == "ASCII85Decode") { - return new Ascii85Stream(stream); - } else if (name == "CCITTFaxDecode") { - TODO("implement fax stream"); - return new CCITTFaxStream(stream, params); - } else { - error("filter '" + name + "' not supported yet"); - } - return stream; + constructor.prototype = { + refill: function() { + this.buf1 = this.lexer.getObj(); + this.buf2 = this.lexer.getObj(); + }, + shift: function() { + if (this.inlineImg > 0) { + if (this.inlineImg < 2) { + this.inlineImg++; + } else { + // in a damaged content stream, if 'ID' shows up in the middle + // of a dictionary, we need to reset + this.inlineImg = 0; } - }; + } else if (IsCmd(this.buf2, 'ID')) { + this.lexer.skip(); // skip char after 'ID' command + this.inlineImg = 1; + } + this.buf1 = this.buf2; + // don't buffer inline image data + this.buf2 = (this.inlineImg > 0) ? null : this.lexer.getObj(); + }, + getObj: function(cipherTransform) { + // refill buffer after inline image data + if (this.inlineImg == 2) + this.refill(); - return constructor; + if (IsCmd(this.buf1, '[')) { // array + this.shift(); + var array = []; + while (!IsCmd(this.buf1, ']') && !IsEOF(this.buf1)) + array.push(this.getObj()); + if (IsEOF(this.buf1)) + error('End of file inside array'); + this.shift(); + return array; + } else if (IsCmd(this.buf1, '<<')) { // dictionary or stream + this.shift(); + var dict = new Dict(); + while (!IsCmd(this.buf1, '>>') && !IsEOF(this.buf1)) { + if (!IsName(this.buf1)) { + error('Dictionary key must be a name object'); + shift(); + } else { + var key = this.buf1.name; + this.shift(); + if (IsEOF(this.buf1)) + break; + dict.set(key, this.getObj(cipherTransform)); + } + } + if (IsEOF(this.buf1)) + error('End of file inside dictionary'); + + // stream objects are not allowed inside content streams or + // object streams + if (this.allowStreams && IsCmd(this.buf2, 'stream')) { + return this.makeStream(dict, cipherTransform); + } else { + this.shift(); + } + return dict; + + } else if (IsInt(this.buf1)) { // indirect reference or integer + var num = this.buf1; + this.shift(); + if (IsInt(this.buf1) && IsCmd(this.buf2, 'R')) { + var ref = new Ref(num, this.buf1); + this.shift(); + this.shift(); + return ref; + } + return num; + } else if (IsString(this.buf1)) { // string + var str = this.buf1; + this.shift(); + if (cipherTransform) + str = cipherTransform.decryptString(str); + return str; + } + + // simple object + var obj = this.buf1; + this.shift(); + return obj; + }, + makeStream: function(dict, cipherTransform) { + var lexer = this.lexer; + var stream = lexer.stream; + + // get stream start position + lexer.skipToNextLine(); + var pos = stream.pos; + + // get length + var length = dict.get('Length'); + var xref = this.xref; + if (xref) + length = xref.fetchIfRef(length); + if (!IsInt(length)) { + error("Bad 'Length' attribute in stream"); + length = 0; + } + + // skip over the stream data + stream.pos = pos + length; + this.shift(); // '>>' + this.shift(); // 'stream' + if (!IsCmd(this.buf1, 'endstream')) + error("Missing 'endstream'"); + this.shift(); + + stream = stream.makeSubStream(pos, length, dict); + if (cipherTransform) + stream = cipherTransform.createStream(stream); + stream = this.filter(stream, dict, length); + stream.parameters = dict; + return stream; + }, + filter: function(stream, dict, length) { + var filter = dict.get('Filter', 'F'); + var params = dict.get('DecodeParms', 'DP'); + if (IsName(filter)) + return this.makeFilter(stream, filter.name, length, params); + if (IsArray(filter)) { + var filterArray = filter; + var paramsArray = params; + for (var i = 0, ii = filterArray.length; i < ii; ++i) { + filter = filterArray[i]; + if (!IsName(filter)) + error('Bad filter name'); + else { + params = null; + if (IsArray(paramsArray) && (i in paramsArray)) + params = paramsArray[i]; + stream = this.makeFilter(stream, filter.name, length, params); + } + } + } + return stream; + }, + makeFilter: function(stream, name, length, params) { + if (name == 'FlateDecode' || name == 'Fl') { + if (params) { + return new PredictorStream(new FlateStream(stream), params); + } + return new FlateStream(stream); + } else if (name == 'DCTDecode') { + var bytes = stream.getBytes(length); + return new JpegStream(bytes, stream.dict); + } else if (name == 'ASCII85Decode') { + return new Ascii85Stream(stream); + } else if (name == 'ASCIIHexDecode') { + return new AsciiHexStream(stream); + } else if (name == 'CCITTFaxDecode') { + TODO('implement fax stream'); + return new CCITTFaxStream(stream, params); + } else { + error("filter '" + name + "' not supported yet"); + } + return stream; + } + }; + + return constructor; })(); var Linearization = (function() { - function constructor(stream) { - this.parser = new Parser(new Lexer(stream), false); - var obj1 = this.parser.getObj(); - var obj2 = this.parser.getObj(); - var obj3 = this.parser.getObj(); - this.linDict = this.parser.getObj(); - if (IsInt(obj1) && IsInt(obj2) && IsCmd(obj3, "obj") && IsDict(this.linDict)) { - var obj = this.linDict.get("Linearized"); - if (!(IsNum(obj) && obj > 0)) - this.linDict = null; - } + function constructor(stream) { + this.parser = new Parser(new Lexer(stream), false); + var obj1 = this.parser.getObj(); + var obj2 = this.parser.getObj(); + var obj3 = this.parser.getObj(); + this.linDict = this.parser.getObj(); + if (IsInt(obj1) && IsInt(obj2) && IsCmd(obj3, 'obj') && + IsDict(this.linDict)) { + var obj = this.linDict.get('Linearized'); + if (!(IsNum(obj) && obj > 0)) + this.linDict = null; } + } - constructor.prototype = { - getInt: function(name) { - var linDict = this.linDict; - var obj; - if (IsDict(linDict) && - IsInt(obj = linDict.get(name)) && - obj > 0) { - return obj; - } - error("'" + name + "' field in linearization table is invalid"); - return 0; - }, - getHint: function(index) { - var linDict = this.linDict; - var obj1, obj2; - if (IsDict(linDict) && - IsArray(obj1 = linDict.get("H")) && - obj1.length >= 2 && - IsInt(obj2 = obj1[index]) && - obj2 > 0) { - return obj2; - } - error("Hints table in linearization table is invalid"); - return 0; - }, - get length() { - if (!IsDict(this.linDict)) - return 0; - return this.getInt("L"); - }, - get hintsOffset() { - return this.getHint(0); - }, - get hintsLength() { - return this.getHint(1); - }, - get hintsOffset2() { - return this.getHint(2); - }, - get hintsLenth2() { - return this.getHint(3); - }, - get objectNumberFirst() { - return this.getInt("O"); - }, - get endFirst() { - return this.getInt("E"); - }, - get numPages() { - return this.getInt("N"); - }, - get mainXRefEntriesOffset() { - return this.getInt("T"); - }, - get pageFirst() { - return this.getInt("P"); - } - }; + constructor.prototype = { + getInt: function(name) { + var linDict = this.linDict; + var obj; + if (IsDict(linDict) && + IsInt(obj = linDict.get(name)) && + obj > 0) { + return obj; + } + error("'" + name + "' field in linearization table is invalid"); + return 0; + }, + getHint: function(index) { + var linDict = this.linDict; + var obj1, obj2; + if (IsDict(linDict) && + IsArray(obj1 = linDict.get('H')) && + obj1.length >= 2 && + IsInt(obj2 = obj1[index]) && + obj2 > 0) { + return obj2; + } + error('Hints table in linearization table is invalid'); + return 0; + }, + get length() { + if (!IsDict(this.linDict)) + return 0; + return this.getInt('L'); + }, + get hintsOffset() { + return this.getHint(0); + }, + get hintsLength() { + return this.getHint(1); + }, + get hintsOffset2() { + return this.getHint(2); + }, + get hintsLenth2() { + return this.getHint(3); + }, + get objectNumberFirst() { + return this.getInt('O'); + }, + get endFirst() { + return this.getInt('E'); + }, + get numPages() { + return this.getInt('N'); + }, + get mainXRefEntriesOffset() { + return this.getInt('T'); + }, + get pageFirst() { + return this.getInt('P'); + } + }; - return constructor; + return constructor; })(); var XRef = (function() { - function constructor(stream, startXRef, mainXRefEntriesOffset) { - this.stream = stream; - this.entries = []; - this.xrefstms = {}; - var trailerDict = this.readXRef(startXRef); + function constructor(stream, startXRef, mainXRefEntriesOffset) { + this.stream = stream; + this.entries = []; + this.xrefstms = {}; + var trailerDict = this.readXRef(startXRef); - // prepare the XRef cache - this.cache = []; + // prepare the XRef cache + this.cache = []; - var encrypt = trailerDict.get("Encrypt"); - if (encrypt) { - var fileId = trailerDict.get("ID"); - this.encrypt = new CipherTransformFactory(this.fetch(encrypt), fileId[0] /*, password */); - } - - // get the root dictionary (catalog) object - if (!IsRef(this.root = trailerDict.get("Root"))) - error("Invalid root reference"); + var encrypt = trailerDict.get('Encrypt'); + if (encrypt) { + var fileId = trailerDict.get('ID'); + this.encrypt = new CipherTransformFactory(this.fetch(encrypt), + fileId[0] /*, password */); } - constructor.prototype = { - readXRefTable: function readXRefTable(parser) { - var obj; - while (true) { - if (IsCmd(obj = parser.getObj(), "trailer")) - break; - if (!IsInt(obj)) - error("Invalid XRef table"); - var first = obj; - if (!IsInt(obj = parser.getObj())) - error("Invalid XRef table"); - var n = obj; - if (first < 0 || n < 0 || (first + n) != ((first + n) | 0)) - error("Invalid XRef table"); - for (var i = first; i < first + n; ++i) { - var entry = {}; - if (!IsInt(obj = parser.getObj())) - error("Invalid XRef table"); - entry.offset = obj; - if (!IsInt(obj = parser.getObj())) - error("Invalid XRef table"); - entry.gen = obj; - obj = parser.getObj(); - if (IsCmd(obj, "n")) { - entry.uncompressed = true; - } else if (IsCmd(obj, "f")) { - entry.free = true; - } else { - error("Invalid XRef table"); - } - if (!this.entries[i]) { - // In some buggy PDF files the xref table claims to start at 1 - // instead of 0. - if (i == 1 && first == 1 && - entry.offset == 0 && entry.gen == 65535 && entry.free) { - i = first = 0; - } - this.entries[i] = entry; - } - } - } + // get the root dictionary (catalog) object + if (!IsRef(this.root = trailerDict.get('Root'))) + error('Invalid root reference'); + } - // read the trailer dictionary - var dict; - if (!IsDict(dict = parser.getObj())) - error("Invalid XRef table"); - - // get the 'Prev' pointer - var prev; - obj = dict.get("Prev"); - if (IsInt(obj)) { - prev = obj; - } else if (IsRef(obj)) { - // certain buggy PDF generators generate "/Prev NNN 0 R" instead - // of "/Prev NNN" - prev = obj.num; + constructor.prototype = { + readXRefTable: function readXRefTable(parser) { + var obj; + while (true) { + if (IsCmd(obj = parser.getObj(), 'trailer')) + break; + if (!IsInt(obj)) + error('Invalid XRef table'); + var first = obj; + if (!IsInt(obj = parser.getObj())) + error('Invalid XRef table'); + var n = obj; + if (first < 0 || n < 0 || (first + n) != ((first + n) | 0)) + error('Invalid XRef table'); + for (var i = first; i < first + n; ++i) { + var entry = {}; + if (!IsInt(obj = parser.getObj())) + error('Invalid XRef table'); + entry.offset = obj; + if (!IsInt(obj = parser.getObj())) + error('Invalid XRef table'); + entry.gen = obj; + obj = parser.getObj(); + if (IsCmd(obj, 'n')) { + entry.uncompressed = true; + } else if (IsCmd(obj, 'f')) { + entry.free = true; + } else { + error('Invalid XRef table'); + } + if (!this.entries[i]) { + // In some buggy PDF files the xref table claims to start at 1 + // instead of 0. + if (i == 1 && first == 1 && + entry.offset == 0 && entry.gen == 65535 && entry.free) { + i = first = 0; } - if (prev) { - this.readXRef(prev); - } - - // check for 'XRefStm' key - if (IsInt(obj = dict.get("XRefStm"))) { - var pos = obj; - if (pos in this.xrefstms) - error("Invalid XRef table"); - this.xrefstms[pos] = 1; // avoid infinite recursion - this.readXRef(pos); - } - - return dict; - }, - readXRefStream: function readXRefStream(stream) { - var streamParameters = stream.parameters; - var length = streamParameters.get("Length"); - var byteWidths = streamParameters.get("W"); - var range = streamParameters.get("Index"); - if (!range) - range = [0, streamParameters.get("Size")]; - var i, j; - while (range.length > 0) { - var first = range[0], n = range[1]; - if (!IsInt(first) || !IsInt(n)) - error("Invalid XRef range fields"); - var typeFieldWidth = byteWidths[0], offsetFieldWidth = byteWidths[1], generationFieldWidth = byteWidths[2]; - if (!IsInt(typeFieldWidth) || !IsInt(offsetFieldWidth) || !IsInt(generationFieldWidth)) - error("Invalid XRef entry fields length"); - for (i = 0; i < n; ++i) { - var type = 0, offset = 0, generation = 0; - for (j = 0; j < typeFieldWidth; ++j) - type = (type << 8) | stream.getByte(); - // if type field is absent, its default value = 1 - if (typeFieldWidth == 0) - type = 1; - for (j = 0; j < offsetFieldWidth; ++j) - offset = (offset << 8) | stream.getByte(); - for (j = 0; j < generationFieldWidth; ++j) - generation = (generation << 8) | stream.getByte(); - var entry = {} - entry.offset = offset; - entry.gen = generation; - switch (type) { - case 0: - entry.free = true; - break; - case 1: - entry.uncompressed = true; - break; - case 2: - break; - default: - error("Invalid XRef entry type"); - break; - } - if (!this.entries[first + i]) - this.entries[first + i] = entry; - } - range.splice(0, 2); - } - var prev = streamParameters.get("Prev"); - if (IsInt(prev)) - this.readXRef(prev); - return streamParameters; - }, - readXRef: function readXref(startXRef) { - var stream = this.stream; - stream.pos = startXRef; - var parser = new Parser(new Lexer(stream), true); - var obj = parser.getObj(); - // parse an old-style xref table - if (IsCmd(obj, "xref")) - return this.readXRefTable(parser); - // parse an xref stream - if (IsInt(obj)) { - if (!IsInt(parser.getObj()) || - !IsCmd(parser.getObj(), "obj") || - !IsStream(obj = parser.getObj())) { - error("Invalid XRef stream"); - } - return this.readXRefStream(obj); - } - error("Invalid XRef"); - return null; - }, - getEntry: function(i) { - var e = this.entries[i]; - if (e.free) - error("reading an XRef stream not implemented yet"); - return e; - }, - fetchIfRef: function(obj) { - if (!IsRef(obj)) - return obj; - return this.fetch(obj); - }, - fetch: function(ref) { - var num = ref.num; - var e = this.cache[num]; - if (e) - return e; - - e = this.getEntry(num); - var gen = ref.gen; - var stream, parser; - if (e.uncompressed) { - if (e.gen != gen) - throw("inconsistent generation in XRef"); - stream = this.stream.makeSubStream(e.offset); - parser = new Parser(new Lexer(stream), true, this); - var obj1 = parser.getObj(); - var obj2 = parser.getObj(); - var obj3 = parser.getObj(); - if (!IsInt(obj1) || obj1 != num || - !IsInt(obj2) || obj2 != gen || - !IsCmd(obj3)) { - error("bad XRef entry"); - } - if (!IsCmd(obj3, "obj")) { - // some bad pdfs use "obj1234" and really mean 1234 - if (obj3.cmd.indexOf("obj") == 0) { - var num = parseInt(obj3.cmd.substring(3)); - if (!isNaN(num)) - return num; - } - error("bad XRef entry"); - } - if (this.encrypt) { - e = parser.getObj(this.encrypt.createCipherTransform(num, gen)); - } else { - e = parser.getObj(); - } - // Don't cache streams since they are mutable. - if (!IsStream(e)) - this.cache[num] = e; - return e; - } - - // compressed entry - stream = this.fetch(new Ref(e.offset, 0)); - if (!IsStream(stream)) - error("bad ObjStm stream"); - var first = stream.parameters.get("First"); - var n = stream.parameters.get("N"); - if (!IsInt(first) || !IsInt(n)) { - error("invalid first and n parameters for ObjStm stream"); - } - parser = new Parser(new Lexer(stream), false); - var i, entries = [], nums = []; - // read the object numbers to populate cache - for (i = 0; i < n; ++i) { - var num = parser.getObj(); - if (!IsInt(num)) { - error("invalid object number in the ObjStm stream"); - } - nums.push(num); - var offset = parser.getObj(); - if (!IsInt(offset)) { - error("invalid object offset in the ObjStm stream"); - } - } - // read stream objects for cache - for (i = 0; i < n; ++i) { - entries.push(parser.getObj()); - this.cache[nums[i]] = entries[i]; - } - e = entries[e.gen]; - if (!e) { - error("bad XRef entry for compressed object"); - } - return e; - }, - getCatalogObj: function() { - return this.fetch(this.root); + this.entries[i] = entry; + } } - }; + } - return constructor; + // read the trailer dictionary + var dict; + if (!IsDict(dict = parser.getObj())) + error('Invalid XRef table'); + + // get the 'Prev' pointer + var prev; + obj = dict.get('Prev'); + if (IsInt(obj)) { + prev = obj; + } else if (IsRef(obj)) { + // certain buggy PDF generators generate "/Prev NNN 0 R" instead + // of "/Prev NNN" + prev = obj.num; + } + if (prev) { + this.readXRef(prev); + } + + // check for 'XRefStm' key + if (IsInt(obj = dict.get('XRefStm'))) { + var pos = obj; + if (pos in this.xrefstms) + error('Invalid XRef table'); + this.xrefstms[pos] = 1; // avoid infinite recursion + this.readXRef(pos); + } + + return dict; + }, + readXRefStream: function readXRefStream(stream) { + var streamParameters = stream.parameters; + var length = streamParameters.get('Length'); + var byteWidths = streamParameters.get('W'); + var range = streamParameters.get('Index'); + if (!range) + range = [0, streamParameters.get('Size')]; + var i, j; + while (range.length > 0) { + var first = range[0], n = range[1]; + if (!IsInt(first) || !IsInt(n)) + error('Invalid XRef range fields'); + var typeFieldWidth = byteWidths[0]; + var offsetFieldWidth = byteWidths[1]; + var generationFieldWidth = byteWidths[2]; + if (!IsInt(typeFieldWidth) || !IsInt(offsetFieldWidth) || + !IsInt(generationFieldWidth)) { + error('Invalid XRef entry fields length'); + } + for (i = 0; i < n; ++i) { + var type = 0, offset = 0, generation = 0; + for (j = 0; j < typeFieldWidth; ++j) + type = (type << 8) | stream.getByte(); + // if type field is absent, its default value = 1 + if (typeFieldWidth == 0) + type = 1; + for (j = 0; j < offsetFieldWidth; ++j) + offset = (offset << 8) | stream.getByte(); + for (j = 0; j < generationFieldWidth; ++j) + generation = (generation << 8) | stream.getByte(); + var entry = {}; + entry.offset = offset; + entry.gen = generation; + switch (type) { + case 0: + entry.free = true; + break; + case 1: + entry.uncompressed = true; + break; + case 2: + break; + default: + error('Invalid XRef entry type'); + break; + } + if (!this.entries[first + i]) + this.entries[first + i] = entry; + } + range.splice(0, 2); + } + var prev = streamParameters.get('Prev'); + if (IsInt(prev)) + this.readXRef(prev); + return streamParameters; + }, + readXRef: function readXref(startXRef) { + var stream = this.stream; + stream.pos = startXRef; + var parser = new Parser(new Lexer(stream), true); + var obj = parser.getObj(); + // parse an old-style xref table + if (IsCmd(obj, 'xref')) + return this.readXRefTable(parser); + // parse an xref stream + if (IsInt(obj)) { + if (!IsInt(parser.getObj()) || + !IsCmd(parser.getObj(), 'obj') || + !IsStream(obj = parser.getObj())) { + error('Invalid XRef stream'); + } + return this.readXRefStream(obj); + } + error('Invalid XRef'); + return null; + }, + getEntry: function(i) { + var e = this.entries[i]; + if (e.free) + error('reading an XRef stream not implemented yet'); + return e; + }, + fetchIfRef: function(obj) { + if (!IsRef(obj)) + return obj; + return this.fetch(obj); + }, + fetch: function(ref) { + var num = ref.num; + var e = this.cache[num]; + if (e) + return e; + + e = this.getEntry(num); + var gen = ref.gen; + var stream, parser; + if (e.uncompressed) { + if (e.gen != gen) + throw ('inconsistent generation in XRef'); + stream = this.stream.makeSubStream(e.offset); + parser = new Parser(new Lexer(stream), true, this); + var obj1 = parser.getObj(); + var obj2 = parser.getObj(); + var obj3 = parser.getObj(); + if (!IsInt(obj1) || obj1 != num || + !IsInt(obj2) || obj2 != gen || + !IsCmd(obj3)) { + error('bad XRef entry'); + } + if (!IsCmd(obj3, 'obj')) { + // some bad pdfs use "obj1234" and really mean 1234 + if (obj3.cmd.indexOf('obj') == 0) { + var num = parseInt(obj3.cmd.substring(3)); + if (!isNaN(num)) + return num; + } + error('bad XRef entry'); + } + if (this.encrypt) { + e = parser.getObj(this.encrypt.createCipherTransform(num, gen)); + } else { + e = parser.getObj(); + } + // Don't cache streams since they are mutable. + if (!IsStream(e)) + this.cache[num] = e; + return e; + } + + // compressed entry + stream = this.fetch(new Ref(e.offset, 0)); + if (!IsStream(stream)) + error('bad ObjStm stream'); + var first = stream.parameters.get('First'); + var n = stream.parameters.get('N'); + if (!IsInt(first) || !IsInt(n)) { + error('invalid first and n parameters for ObjStm stream'); + } + parser = new Parser(new Lexer(stream), false); + var i, entries = [], nums = []; + // read the object numbers to populate cache + for (i = 0; i < n; ++i) { + var num = parser.getObj(); + if (!IsInt(num)) { + error('invalid object number in the ObjStm stream'); + } + nums.push(num); + var offset = parser.getObj(); + if (!IsInt(offset)) { + error('invalid object offset in the ObjStm stream'); + } + } + // read stream objects for cache + for (i = 0; i < n; ++i) { + entries.push(parser.getObj()); + this.cache[nums[i]] = entries[i]; + } + e = entries[e.gen]; + if (!e) { + error('bad XRef entry for compressed object'); + } + return e; + }, + getCatalogObj: function() { + return this.fetch(this.root); + } + }; + + return constructor; })(); var Page = (function() { - function constructor(xref, pageNumber, pageDict) { - this.xref = xref; - this.pageNumber = pageNumber; - this.pageDict = pageDict; - } - - constructor.prototype = { - getPageProp: function(key) { - return this.pageDict.get(key); - }, - inheritPageProp: function(key) { - var dict = this.pageDict; - var obj = dict.get(key); - while (!obj) { - dict = this.xref.fetchIfRef(dict.get("Parent")); - if (!dict) - break; - obj = dict.get(key); - } - return obj; - }, - get content() { - return shadow(this, "content", this.getPageProp("Contents")); - }, - get resources() { - return shadow(this, "resources", this.inheritPageProp("Resources")); - }, - get mediaBox() { - var obj = this.inheritPageProp("MediaBox"); - return shadow(this, "mediaBox", ((IsArray(obj) && obj.length == 4) - ? obj - : null)); - }, - compile: function(gfx, fonts) { - if (this.code) { - // content was compiled - return; - } - - var xref = this.xref; - var content; - var resources = xref.fetchIfRef(this.resources); - if (!IsArray(this.content)) { - // content is not an array, shortcut - content = xref.fetchIfRef(this.content); - this.code = gfx.compile(content, xref, resources, fonts); - return; - } - // the content is an array, compiling all items - var i, n = this.content.length, compiledItems = []; - for (i = 0; i < n; ++i) { - content = xref.fetchIfRef(this.content[i]); - compiledItems.push(gfx.compile(content, xref, resources, fonts)); - } - // creating the function that executes all compiled items - this.code = function(gfx) { - var i, n = compiledItems.length; - for (i = 0; i < n; ++i) { - compiledItems[i](gfx); - } - }; - }, - display: function(gfx) { - assert(this.code instanceof Function, "page content must be compiled first"); - var xref = this.xref; - var resources = xref.fetchIfRef(this.resources); - var mediaBox = xref.fetchIfRef(this.mediaBox); - assertWellFormed(IsDict(resources), "invalid page resources"); - gfx.beginDrawing({ x: mediaBox[0], y: mediaBox[1], - width: mediaBox[2] - mediaBox[0], - height: mediaBox[3] - mediaBox[1] }); - gfx.execute(this.code, xref, resources); - gfx.endDrawing(); - } + function constructor(xref, pageNumber, pageDict) { + this.pageNumber = pageNumber; + this.pageDict = pageDict; + this.stats = { + create: Date.now(), + compile: 0.0, + fonts: 0.0, + render: 0.0, }; + this.xref = xref; + } - return constructor; + constructor.prototype = { + getPageProp: function(key) { + return this.xref.fetchIfRef(this.pageDict.get(key)); + }, + inheritPageProp: function(key) { + var dict = this.pageDict; + var obj = dict.get(key); + while (!obj) { + dict = this.xref.fetchIfRef(dict.get('Parent')); + if (!dict) + break; + obj = dict.get(key); + } + return obj; + }, + get content() { + return shadow(this, 'content', this.getPageProp('Contents')); + }, + get resources() { + return shadow(this, 'resources', this.inheritPageProp('Resources')); + }, + get mediaBox() { + var obj = this.inheritPageProp('MediaBox'); + return shadow(this, 'mediaBox', + ((IsArray(obj) && obj.length == 4) ? obj : null)); + }, + startRendering: function(canvasCtx, continuation) { + var self = this; + var stats = self.stats; + stats.compile = stats.fonts = stats.render = 0; + + var gfx = new CanvasGraphics(canvasCtx); + var fonts = [ ]; + + this.compile(gfx, fonts); + stats.compile = Date.now(); + + FontLoader.bind( + fonts, + function() { + stats.fonts = Date.now(); + // Always defer call to display() to work around bug in + // Firefox error reporting from XHR callbacks. + setTimeout(function () { + self.display(gfx); + stats.render = Date.now(); + continuation(); + }); + }); + }, + + + compile: function(gfx, fonts) { + if (this.code) { + // content was compiled + return; + } + + var xref = this.xref; + var content; + var resources = xref.fetchIfRef(this.resources); + if (!IsArray(this.content)) { + // content is not an array, shortcut + content = xref.fetchIfRef(this.content); + this.code = gfx.compile(content, xref, resources, fonts); + return; + } + // the content is an array, compiling all items + var i, n = this.content.length, compiledItems = []; + for (i = 0; i < n; ++i) { + content = xref.fetchIfRef(this.content[i]); + compiledItems.push(gfx.compile(content, xref, resources, fonts)); + } + // creating the function that executes all compiled items + this.code = function(gfx) { + var i, n = compiledItems.length; + for (i = 0; i < n; ++i) { + compiledItems[i](gfx); + } + }; + }, + display: function(gfx) { + assert(this.code instanceof Function, + 'page content must be compiled first'); + var xref = this.xref; + var resources = xref.fetchIfRef(this.resources); + var mediaBox = xref.fetchIfRef(this.mediaBox); + assertWellFormed(IsDict(resources), 'invalid page resources'); + gfx.beginDrawing({ x: mediaBox[0], y: mediaBox[1], + width: mediaBox[2] - mediaBox[0], + height: mediaBox[3] - mediaBox[1] }); + gfx.execute(this.code, xref, resources); + gfx.endDrawing(); + } + }; + + return constructor; })(); var Catalog = (function() { - function constructor(xref) { - this.xref = xref; - var obj = xref.getCatalogObj(); - assertWellFormed(IsDict(obj), "catalog object is not a dictionary"); - this.catDict = obj; - } + function constructor(xref) { + this.xref = xref; + var obj = xref.getCatalogObj(); + assertWellFormed(IsDict(obj), 'catalog object is not a dictionary'); + this.catDict = obj; + } - constructor.prototype = { - get toplevelPagesDict() { - var obj = this.catDict.get("Pages"); - assertWellFormed(IsRef(obj), "invalid top-level pages reference"); - var obj = this.xref.fetch(obj); - assertWellFormed(IsDict(obj), "invalid top-level pages dictionary"); - // shadow the prototype getter - return shadow(this, "toplevelPagesDict", obj); - }, - get numPages() { - var obj = this.toplevelPagesDict.get("Count"); - assertWellFormed(IsInt(obj), - "page count in top level pages object is not an integer"); - // shadow the prototype getter - return shadow(this, "num", obj); - }, - traverseKids: function(pagesDict) { - var pageCache = this.pageCache; - var kids = pagesDict.get("Kids"); - assertWellFormed(IsArray(kids), - "page dictionary kids object is not an array"); - for (var i = 0; i < kids.length; ++i) { - var kid = kids[i]; - assertWellFormed(IsRef(kid), - "page dictionary kid is not a reference"); - var obj = this.xref.fetch(kid); - if (IsDict(obj, "Page") || (IsDict(obj) && !obj.has("Kids"))) { - pageCache.push(new Page(this.xref, pageCache.length, obj)); - } else { // must be a child page dictionary - assertWellFormed(IsDict(obj), - "page dictionary kid reference points to wrong type of object"); - this.traverseKids(obj); - } - } - }, - getPage: function(n) { - var pageCache = this.pageCache; - if (!pageCache) { - pageCache = this.pageCache = []; - this.traverseKids(this.toplevelPagesDict); - } - return this.pageCache[n-1]; + constructor.prototype = { + get toplevelPagesDict() { + var obj = this.catDict.get('Pages'); + assertWellFormed(IsRef(obj), 'invalid top-level pages reference'); + var obj = this.xref.fetch(obj); + assertWellFormed(IsDict(obj), 'invalid top-level pages dictionary'); + // shadow the prototype getter + return shadow(this, 'toplevelPagesDict', obj); + }, + get numPages() { + var obj = this.toplevelPagesDict.get('Count'); + assertWellFormed( + IsInt(obj), + 'page count in top level pages object is not an integer' + ); + // shadow the prototype getter + return shadow(this, 'num', obj); + }, + traverseKids: function(pagesDict) { + var pageCache = this.pageCache; + var kids = pagesDict.get('Kids'); + assertWellFormed(IsArray(kids), + 'page dictionary kids object is not an array'); + for (var i = 0; i < kids.length; ++i) { + var kid = kids[i]; + assertWellFormed(IsRef(kid), + 'page dictionary kid is not a reference'); + var obj = this.xref.fetch(kid); + if (IsDict(obj, 'Page') || (IsDict(obj) && !obj.has('Kids'))) { + pageCache.push(new Page(this.xref, pageCache.length, obj)); + } else { // must be a child page dictionary + assertWellFormed( + IsDict(obj), + 'page dictionary kid reference points to wrong type of object' + ); + this.traverseKids(obj); } - }; + } + }, + getPage: function(n) { + var pageCache = this.pageCache; + if (!pageCache) { + pageCache = this.pageCache = []; + this.traverseKids(this.toplevelPagesDict); + } + return this.pageCache[n - 1]; + } + }; - return constructor; + return constructor; })(); var PDFDoc = (function() { - function constructor(stream) { - this.stream = stream; - this.setup(); - } + function constructor(stream) { + assertWellFormed(stream.length > 0, 'stream must have data'); + this.stream = stream; + this.setup(); + } - function find(stream, needle, limit, backwards) { - var pos = stream.pos; - var end = stream.end; - var str = ""; - if (pos + limit > end) - limit = end - pos; - for (var n = 0; n < limit; ++n) - str += stream.getChar(); - stream.pos = pos; - var index = backwards ? str.lastIndexOf(needle) : str.indexOf(needle); - if (index == -1) - return false; /* not found */ - stream.pos += index; - return true; /* found */ - } + function find(stream, needle, limit, backwards) { + var pos = stream.pos; + var end = stream.end; + var str = ''; + if (pos + limit > end) + limit = end - pos; + for (var n = 0; n < limit; ++n) + str += stream.getChar(); + stream.pos = pos; + var index = backwards ? str.lastIndexOf(needle) : str.indexOf(needle); + if (index == -1) + return false; /* not found */ + stream.pos += index; + return true; /* found */ + } - constructor.prototype = { - get linearization() { - var length = this.stream.length; - var linearization = false; - if (length) { - linearization = new Linearization(this.stream); - if (linearization.length != length) - linearization = false; - } - // shadow the prototype getter with a data property - return shadow(this, "linearization", linearization); - }, - get startXRef() { - var stream = this.stream; - var startXRef = 0; - var linearization = this.linearization; - if (linearization) { - // Find end of first obj. - stream.reset(); - if (find(stream, "endobj", 1024)) - startXRef = stream.pos + 6; - } else { - // Find startxref at the end of the file. - var start = stream.end - 1024; - if (start < 0) - start = 0; - stream.pos = start; - if (find(stream, "startxref", 1024, true)) { - stream.skip(9); - var ch; - while (Lexer.isSpace(ch = stream.getChar())) - ; - var str = ""; - while ((ch - "0") <= 9) { - str += ch; - ch = stream.getChar(); - } - startXRef = parseInt(str); - if (isNaN(startXRef)) - startXRef = 0; - } - } - // shadow the prototype getter with a data property - return shadow(this, "startXRef", startXRef); - }, - get mainXRefEntriesOffset() { - var mainXRefEntriesOffset = 0; - var linearization = this.linearization; - if (linearization) - mainXRefEntriesOffset = linearization.mainXRefEntriesOffset; - // shadow the prototype getter with a data property - return shadow(this, "mainXRefEntriesOffset", mainXRefEntriesOffset); - }, - // Find the header, remove leading garbage and setup the stream - // starting from the header. - checkHeader: function() { - var stream = this.stream; - stream.reset(); - if (find(stream, "%PDF-", 1024)) { - // Found the header, trim off any garbage before it. - stream.moveStart(); - return; - } - // May not be a PDF file, continue anyway. - }, - setup: function(ownerPassword, userPassword) { - this.checkHeader(); - this.xref = new XRef(this.stream, - this.startXRef, - this.mainXRefEntriesOffset); - this.catalog = new Catalog(this.xref); - }, - get numPages() { - var linearization = this.linearization; - var num = linearization - ? linearization.numPages - : this.catalog.numPages; - // shadow the prototype getter - return shadow(this, "numPages", num); - }, - getPage: function(n) { - var linearization = this.linearization; - // assert(!linearization, "linearized page access not implemented"); - return this.catalog.getPage(n); + constructor.prototype = { + get linearization() { + var length = this.stream.length; + var linearization = false; + if (length) { + linearization = new Linearization(this.stream); + if (linearization.length != length) + linearization = false; + } + // shadow the prototype getter with a data property + return shadow(this, 'linearization', linearization); + }, + get startXRef() { + var stream = this.stream; + var startXRef = 0; + var linearization = this.linearization; + if (linearization) { + // Find end of first obj. + stream.reset(); + if (find(stream, 'endobj', 1024)) + startXRef = stream.pos + 6; + } else { + // Find startxref at the end of the file. + var start = stream.end - 1024; + if (start < 0) + start = 0; + stream.pos = start; + if (find(stream, 'startxref', 1024, true)) { + stream.skip(9); + var ch; + while (Lexer.isSpace(ch = stream.getChar())) +; + var str = ''; + while ((ch - '0') <= 9) { + str += ch; + ch = stream.getChar(); + } + startXRef = parseInt(str); + if (isNaN(startXRef)) + startXRef = 0; } - }; + } + // shadow the prototype getter with a data property + return shadow(this, 'startXRef', startXRef); + }, + get mainXRefEntriesOffset() { + var mainXRefEntriesOffset = 0; + var linearization = this.linearization; + if (linearization) + mainXRefEntriesOffset = linearization.mainXRefEntriesOffset; + // shadow the prototype getter with a data property + return shadow(this, 'mainXRefEntriesOffset', mainXRefEntriesOffset); + }, + // Find the header, remove leading garbage and setup the stream + // starting from the header. + checkHeader: function() { + var stream = this.stream; + stream.reset(); + if (find(stream, '%PDF-', 1024)) { + // Found the header, trim off any garbage before it. + stream.moveStart(); + return; + } + // May not be a PDF file, continue anyway. + }, + setup: function(ownerPassword, userPassword) { + this.checkHeader(); + this.xref = new XRef(this.stream, + this.startXRef, + this.mainXRefEntriesOffset); + this.catalog = new Catalog(this.xref); + }, + get numPages() { + var linearization = this.linearization; + var num = linearization ? linearization.numPages : this.catalog.numPages; + // shadow the prototype getter + return shadow(this, 'numPages', num); + }, + getPage: function(n) { + var linearization = this.linearization; + // assert(!linearization, "linearized page access not implemented"); + return this.catalog.getPage(n); + } + }; - return constructor; + return constructor; })(); var Encodings = { get ExpertEncoding() { - return shadow(this, "ExpertEncoding", [ ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, - "space","exclamsmall","Hungarumlautsmall",,"dollaroldstyle","dollarsuperior", - "ampersandsmall","Acutesmall","parenleftsuperior","parenrightsuperior", - "twodotenleader","onedotenleader","comma","hyphen","period","fraction", - "zerooldstyle","oneoldstyle","twooldstyle","threeoldstyle","fouroldstyle", - "fiveoldstyle","sixoldstyle","sevenoldstyle","eightoldstyle","nineoldstyle", - "colon","semicolon","commasuperior","threequartersemdash","periodsuperior", - "questionsmall",,"asuperior","bsuperior","centsuperior","dsuperior","esuperior",,, - "isuperior",,,"lsuperior","msuperior","nsuperior","osuperior",,,"rsuperior", - "ssuperior","tsuperior",,"ff","fi","fl","ffi","ffl","parenleftinferior",, - "parenrightinferior","Circumflexsmall","hyphensuperior","Gravesmall","Asmall", - "Bsmall","Csmall","Dsmall","Esmall","Fsmall","Gsmall","Hsmall","Ismall","Jsmall", - "Ksmall","Lsmall","Msmall","Nsmall","Osmall","Psmall","Qsmall","Rsmall","Ssmall", - "Tsmall","Usmall","Vsmall","Wsmall","Xsmall","Ysmall","Zsmall","colonmonetary", - "onefitted","rupiah","Tildesmall",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, - "exclamdownsmall","centoldstyle","Lslashsmall",,,"Scaronsmall","Zcaronsmall", - "Dieresissmall","Brevesmall","Caronsmall",,"Dotaccentsmall",,,"Macronsmall",,, - "figuredash","hypheninferior",,,"Ogoneksmall","Ringsmall","Cedillasmall",,,, - "onequarter","onehalf","threequarters","questiondownsmall","oneeighth", - "threeeighths","fiveeighths","seveneighths","onethird","twothirds",,, - "zerosuperior","onesuperior","twosuperior","threesuperior","foursuperior", - "fivesuperior","sixsuperior","sevensuperior","eightsuperior","ninesuperior", - "zeroinferior","oneinferior","twoinferior","threeinferior","fourinferior", - "fiveinferior","sixinferior","seveninferior","eightinferior","nineinferior", - "centinferior","dollarinferior","periodinferior","commainferior","Agravesmall", - "Aacutesmall","Acircumflexsmall","Atildesmall","Adieresissmall","Aringsmall", - "AEsmall","Ccedillasmall","Egravesmall","Eacutesmall","Ecircumflexsmall", - "Edieresissmall","Igravesmall","Iacutesmall","Icircumflexsmall","Idieresissmall", - "Ethsmall","Ntildesmall","Ogravesmall","Oacutesmall","Ocircumflexsmall", - "Otildesmall","Odieresissmall","OEsmall","Oslashsmall","Ugravesmall", - "Uacutesmall","Ucircumflexsmall","Udieresissmall","Yacutesmall","Thornsmall", - "Ydieresissmall" + return shadow(this, 'ExpertEncoding', [,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, + 'space', 'exclamsmall', 'Hungarumlautsmall',, 'dollaroldstyle', + 'dollarsuperior', 'ampersandsmall', 'Acutesmall', 'parenleftsuperior', + 'parenrightsuperior', 'twodotenleader', 'onedotenleader', 'comma', + 'hyphen', 'period', 'fraction', 'zerooldstyle', 'oneoldstyle', + 'twooldstyle', 'threeoldstyle', 'fouroldstyle', 'fiveoldstyle', + 'sixoldstyle', 'sevenoldstyle', 'eightoldstyle', 'nineoldstyle', 'colon', + 'semicolon', 'commasuperior', 'threequartersemdash', 'periodsuperior', + 'questionsmall',, 'asuperior', 'bsuperior', 'centsuperior', 'dsuperior', + 'esuperior',,, 'isuperior',,, 'lsuperior', 'msuperior', 'nsuperior', + 'osuperior',,, 'rsuperior', 'ssuperior', 'tsuperior',, 'ff', 'fi', 'fl', + 'ffi', 'ffl', 'parenleftinferior',, 'parenrightinferior', + 'Circumflexsmall', 'hyphensuperior', 'Gravesmall', 'Asmall', 'Bsmall', + 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall', 'Ismall', + 'Jsmall', 'Ksmall', 'Lsmall', 'Msmall', 'Nsmall', 'Osmall', 'Psmall', + 'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall', + 'Xsmall', 'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', + 'Tildesmall',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, 'exclamdownsmall', + 'centoldstyle', 'Lslashsmall',,, 'Scaronsmall', 'Zcaronsmall', + 'Dieresissmall', 'Brevesmall', 'Caronsmall',, 'Dotaccentsmall',,, + 'Macronsmall',,, 'figuredash', 'hypheninferior',,, 'Ogoneksmall', + 'Ringsmall', 'Cedillasmall',,,, 'onequarter', 'onehalf', 'threequarters', + 'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths', + 'seveneighths', 'onethird', 'twothirds',,, 'zerosuperior', 'onesuperior', + 'twosuperior', 'threesuperior', 'foursuperior', 'fivesuperior', + 'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior', + 'zeroinferior', 'oneinferior', 'twoinferior', 'threeinferior', + 'fourinferior', 'fiveinferior', 'sixinferior', 'seveninferior', + 'eightinferior', 'nineinferior', 'centinferior', 'dollarinferior', + 'periodinferior', 'commainferior', 'Agravesmall', 'Aacutesmall', + 'Acircumflexsmall', 'Atildesmall', 'Adieresissmall', 'Aringsmall', + 'AEsmall', 'Ccedillasmall', 'Egravesmall', 'Eacutesmall', + 'Ecircumflexsmall', 'Edieresissmall', 'Igravesmall', 'Iacutesmall', + 'Icircumflexsmall', 'Idieresissmall', 'Ethsmall', 'Ntildesmall', + 'Ogravesmall', 'Oacutesmall', 'Ocircumflexsmall', 'Otildesmall', + 'Odieresissmall', 'OEsmall', 'Oslashsmall', 'Ugravesmall', 'Uacutesmall', + 'Ucircumflexsmall', 'Udieresissmall', 'Yacutesmall', 'Thornsmall', + 'Ydieresissmall' ]); }, get MacExpertEncoding() { - return shadow(this, "MacExpertEncoding", [ ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, - "space","exclamsmall","Hungarumlautsmall","centoldstyle","dollaroldstyle", - "dollarsuperior","ampersandsmall","Acutesmall","parenleftsuperior", - "parenrightsuperior","twodotenleader","onedotenleader","comma","hyphen","period", - "fraction","zerooldstyle","oneoldstyle","twooldstyle","threeoldstyle", - "fouroldstyle","fiveoldstyle","sixoldstyle","sevenoldstyle","eightoldstyle", - "nineoldstyle","colon","semicolon",,"threequartersemdash",,"questionsmall",,,,, - "Ethsmall",,,"onequarter","onehalf","threequarters","oneeighth","threeeighths", - "fiveeighths","seveneighths","onethird","twothirds",,,,,,,"ff","fi","fl","ffi", - "ffl","parenleftinferior",,"parenrightinferior","Circumflexsmall", - "hypheninferior","Gravesmall","Asmall","Bsmall","Csmall","Dsmall","Esmall", - "Fsmall","Gsmall","Hsmall","Ismall","Jsmall","Ksmall","Lsmall","Msmall","Nsmall", - "Osmall","Psmall","Qsmall","Rsmall","Ssmall","Tsmall","Usmall","Vsmall","Wsmall", - "Xsmall","Ysmall","Zsmall","colonmonetary","onefitted","rupiah","Tildesmall",,, - "asuperior","centsuperior",,,,,"Aacutesmall","Agravesmall","Acircumflexsmall", - "Adieresissmall","Atildesmall","Aringsmall","Ccedillasmall","Eacutesmall", - "Egravesmall","Ecircumflexsmall","Edieresissmall","Iacutesmall","Igravesmall", - "Icircumflexsmall","Idieresissmall","Ntildesmall","Oacutesmall","Ogravesmall", - "Ocircumflexsmall","Odieresissmall","Otildesmall","Uacutesmall","Ugravesmall", - "Ucircumflexsmall","Udieresissmall",,"eightsuperior","fourinferior", - "threeinferior","sixinferior","eightinferior","seveninferior","Scaronsmall",, - "centinferior","twoinferior",,"Dieresissmall",,"Caronsmall","osuperior", - "fiveinferior",,"commainferior","periodinferior","Yacutesmall",,"dollarinferior",, - "Thornsmall",,"nineinferior","zeroinferior","Zcaronsmall","AEsmall","Oslashsmall", - "questiondownsmall","oneinferior","Lslashsmall",,,,,,,"Cedillasmall",,,,,, - "OEsmall","figuredash","hyphensuperior",,,,,"exclamdownsmall",,"Ydieresissmall",, - "onesuperior","twosuperior","threesuperior","foursuperior","fivesuperior", - "sixsuperior","sevensuperior","ninesuperior","zerosuperior",,"esuperior", - "rsuperior","tsuperior",,,"isuperior","ssuperior","dsuperior",,,,,,"lsuperior", - "Ogoneksmall","Brevesmall","Macronsmall","bsuperior","nsuperior","msuperior", - "commasuperior","periodsuperior","Dotaccentsmall","Ringsmall",,, + return shadow(this, 'MacExpertEncoding', [,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, + 'space', 'exclamsmall', 'Hungarumlautsmall', 'centoldstyle', + 'dollaroldstyle', 'dollarsuperior', 'ampersandsmall', 'Acutesmall', + 'parenleftsuperior', 'parenrightsuperior', 'twodotenleader', + 'onedotenleader', 'comma', 'hyphen', 'period', 'fraction', + 'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle', + 'fouroldstyle', 'fiveoldstyle', 'sixoldstyle', 'sevenoldstyle', + 'eightoldstyle', 'nineoldstyle', 'colon', 'semicolon',, + 'threequartersemdash',, 'questionsmall',,,,, 'Ethsmall',,, 'onequarter', + 'onehalf', 'threequarters', 'oneeighth', 'threeeighths', 'fiveeighths', + 'seveneighths', 'onethird', 'twothirds',,,,,,, 'ff', 'fi', 'fl', 'ffi', + 'ffl', 'parenleftinferior',, 'parenrightinferior', 'Circumflexsmall', + 'hypheninferior', 'Gravesmall', 'Asmall', 'Bsmall', 'Csmall', 'Dsmall', + 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall', 'Ismall', 'Jsmall', 'Ksmall', + 'Lsmall', 'Msmall', 'Nsmall', 'Osmall', 'Psmall', 'Qsmall', 'Rsmall', + 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall', 'Xsmall', 'Ysmall', + 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall',,, + 'asuperior', 'centsuperior',,,,, 'Aacutesmall', 'Agravesmall', + 'Acircumflexsmall', 'Adieresissmall', 'Atildesmall', 'Aringsmall', + 'Ccedillasmall', 'Eacutesmall', 'Egravesmall', 'Ecircumflexsmall', + 'Edieresissmall', 'Iacutesmall', 'Igravesmall', 'Icircumflexsmall', + 'Idieresissmall', 'Ntildesmall', 'Oacutesmall', 'Ogravesmall', + 'Ocircumflexsmall', 'Odieresissmall', 'Otildesmall', 'Uacutesmall', + 'Ugravesmall', 'Ucircumflexsmall', 'Udieresissmall',, 'eightsuperior', + 'fourinferior', 'threeinferior', 'sixinferior', 'eightinferior', + 'seveninferior', 'Scaronsmall',, 'centinferior', 'twoinferior',, + 'Dieresissmall',, 'Caronsmall', 'osuperior', 'fiveinferior',, + 'commainferior', 'periodinferior', 'Yacutesmall',, 'dollarinferior',, + 'Thornsmall',, 'nineinferior', 'zeroinferior', 'Zcaronsmall', 'AEsmall', + 'Oslashsmall', 'questiondownsmall', 'oneinferior', 'Lslashsmall',,,,,,, + 'Cedillasmall',,,,,, 'OEsmall', 'figuredash', 'hyphensuperior',,,,, + 'exclamdownsmall',, 'Ydieresissmall',, 'onesuperior', 'twosuperior', + 'threesuperior', 'foursuperior', 'fivesuperior', 'sixsuperior', + 'sevensuperior', 'ninesuperior', 'zerosuperior',, 'esuperior', + 'rsuperior', 'tsuperior',,, 'isuperior', 'ssuperior', 'dsuperior',,,,,, + 'lsuperior', 'Ogoneksmall', 'Brevesmall', 'Macronsmall', 'bsuperior', + 'nsuperior', 'msuperior', 'commasuperior', 'periodsuperior', + 'Dotaccentsmall', 'Ringsmall' ]); }, get MacRomanEncoding() { - return shadow(this, "MacRomanEncoding", [ ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, - "space","exclam","quotedbl","numbersign","dollar","percent","ampersand", - "quotesingle","parenleft","parenright","asterisk","plus","comma","hyphen", - "period","slash","zero","one","two","three","four","five","six","seven","eight", - "nine","colon","semicolon","less","equal","greater","question","at","A","B","C", - "D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W", - "X","Y","Z","bracketleft","backslash","bracketright","asciicircum","underscore", - "grave","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r", - "s","t","u","v","w","x","y","z","braceleft","bar","braceright","asciitilde",, - "Adieresis","Aring","Ccedilla","Eacute","Ntilde","Odieresis","Udieresis","aacute", - "agrave","acircumflex","adieresis","atilde","aring","ccedilla","eacute","egrave", - "ecircumflex","edieresis","iacute","igrave","icircumflex","idieresis","ntilde", - "oacute","ograve","ocircumflex","odieresis","otilde","uacute","ugrave", - "ucircumflex","udieresis","dagger","degree","cent","sterling","section","bullet", - "paragraph","germandbls","registered","copyright","trademark","acute","dieresis", - "notequal","AE","Oslash","infinity","plusminus","lessequal","greaterequal","yen", - "mu","partialdiff","summation","product","pi","integral","ordfeminine", - "ordmasculine","Omega","ae","oslash","questiondown","exclamdown","logicalnot", - "radical","florin","approxequal","Delta","guillemotleft","guillemotright", - "ellipsis","space","Agrave","Atilde","Otilde","OE","oe","endash","emdash", - "quotedblleft","quotedblright","quoteleft","quoteright","divide","lozenge", - "ydieresis","Ydieresis","fraction","currency","guilsinglleft","guilsinglright", - "fi","fl","daggerdbl","periodcentered","quotesinglbase","quotedblbase", - "perthousand","Acircumflex","Ecircumflex","Aacute","Edieresis","Egrave","Iacute", - "Icircumflex","Idieresis","Igrave","Oacute","Ocircumflex","apple","Ograve", - "Uacute","Ucircumflex","Ugrave","dotlessi","circumflex","tilde","macron","breve", - "dotaccent","ring","cedilla","hungarumlaut","ogonek","caron" + return shadow(this, 'MacRomanEncoding', [,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, + 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent', + 'ampersand', 'quotesingle', 'parenleft', 'parenright', 'asterisk', + 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one', 'two', + 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', + 'semicolon', 'less', 'equal', 'greater', 'question', 'at', 'A', 'B', 'C', + 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', + 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', + 'bracketright', 'asciicircum', 'underscore', 'grave', 'a', 'b', 'c', 'd', + 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', + 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', + 'asciitilde',, 'Adieresis', 'Aring', 'Ccedilla', 'Eacute', 'Ntilde', + 'Odieresis', 'Udieresis', 'aacute', 'agrave', 'acircumflex', 'adieresis', + 'atilde', 'aring', 'ccedilla', 'eacute', 'egrave', 'ecircumflex', + 'edieresis', 'iacute', 'igrave', 'icircumflex', 'idieresis', 'ntilde', + 'oacute', 'ograve', 'ocircumflex', 'odieresis', 'otilde', 'uacute', + 'ugrave', 'ucircumflex', 'udieresis', 'dagger', 'degree', 'cent', + 'sterling', 'section', 'bullet', 'paragraph', 'germandbls', 'registered', + 'copyright', 'trademark', 'acute', 'dieresis', 'notequal', 'AE', + 'Oslash', 'infinity', 'plusminus', 'lessequal', 'greaterequal', 'yen', + 'mu', 'partialdiff', 'summation', 'product', 'pi', 'integral', + 'ordfeminine', 'ordmasculine', 'Omega', 'ae', 'oslash', 'questiondown', + 'exclamdown', 'logicalnot', 'radical', 'florin', 'approxequal', 'Delta', + 'guillemotleft', 'guillemotright', 'ellipsis', 'space', 'Agrave', + 'Atilde', 'Otilde', 'OE', 'oe', 'endash', 'emdash', 'quotedblleft', + 'quotedblright', 'quoteleft', 'quoteright', 'divide', 'lozenge', + 'ydieresis', 'Ydieresis', 'fraction', 'currency', 'guilsinglleft', + 'guilsinglright', 'fi', 'fl', 'daggerdbl', 'periodcentered', + 'quotesinglbase', 'quotedblbase', 'perthousand', 'Acircumflex', + 'Ecircumflex', 'Aacute', 'Edieresis', 'Egrave', 'Iacute', 'Icircumflex', + 'Idieresis', 'Igrave', 'Oacute', 'Ocircumflex', 'apple', 'Ograve', + 'Uacute', 'Ucircumflex', 'Ugrave', 'dotlessi', 'circumflex', 'tilde', + 'macron', 'breve', 'dotaccent', 'ring', 'cedilla', 'hungarumlaut', + 'ogonek', 'caron' ]); }, get StandardEncoding() { - return shadow(this, "StandardEncoding", [ ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, - "space","exclam","quotedbl","numbersign","dollar","percent","ampersand", - "quoteright","parenleft","parenright","asterisk","plus","comma","hyphen","period", - "slash","zero","one","two","three","four","five","six","seven","eight","nine", - "colon","semicolon","less","equal","greater","question","at","A","B","C","D","E", - "F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y", - "Z","bracketleft","backslash","bracketright","asciicircum","underscore", - "quoteleft","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q", - "r","s","t","u","v","w","x","y","z","braceleft","bar","braceright","asciitilde",,, - "exclamdown","cent","sterling","fraction","yen","florin","section","currency", - "quotesingle","quotedblleft","guillemotleft","guilsinglleft","guilsinglright", - "fi","fl",,"endash","dagger","daggerdbl","periodcentered",,"paragraph","bullet", - "quotesinglbase","quotedblbase","quotedblright","guillemotright","ellipsis", - "perthousand",,"questiondown",,"grave","acute","circumflex","tilde","macron", - "breve","dotaccent","dieresis",,"ring","cedilla",,"hungarumlaut","ogonek","caron", - "emdash",,,,,,,,,,,,,,,,,"AE",,"ordfeminine",,,,,"Lslash","Oslash","OE", - "ordmasculine",,,,,,"ae",,,,"dotlessi",,,"lslash","oslash","oe","germandbls",,, + return shadow(this, 'StandardEncoding', [,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, + 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent', + 'ampersand', 'quoteright', 'parenleft', 'parenright', 'asterisk', + 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one', 'two', + 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', + 'semicolon', 'less', 'equal', 'greater', 'question', 'at', 'A', 'B', + 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', + 'backslash', 'bracketright', 'asciicircum', 'underscore', 'quoteleft', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', + 'bar', 'braceright', 'asciitilde',,, 'exclamdown', 'cent', 'sterling', + 'fraction', 'yen', 'florin', 'section', 'currency', 'quotesingle', + 'quotedblleft', 'guillemotleft', 'guilsinglleft', 'guilsinglright', 'fi', + 'fl',, 'endash', 'dagger', 'daggerdbl', 'periodcentered',, 'paragraph', + 'bullet', 'quotesinglbase', 'quotedblbase', 'quotedblright', + 'guillemotright', 'ellipsis', 'perthousand',, 'questiondown',, 'grave', + 'acute', 'circumflex', 'tilde', 'macron', 'breve', 'dotaccent', + 'dieresis',, 'ring', 'cedilla',, 'hungarumlaut', 'ogonek', 'caron', + 'emdash',,,,,,,,,,,,,,,,, 'AE',, 'ordfeminine',,,,, 'Lslash', 'Oslash', + 'OE', 'ordmasculine',,,,,, 'ae',,,, 'dotlessi',,, 'lslash', 'oslash', + 'oe', 'germandbls' ]); }, get WinAnsiEncoding() { - return shadow(this, "WinAnsiEncoding", [ ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, - "space","exclam","quotedbl","numbersign","dollar","percent","ampersand", - "quotesingle","parenleft","parenright","asterisk","plus","comma","hyphen", - "period","slash","zero","one","two","three","four","five","six","seven","eight", - "nine","colon","semicolon","less","equal","greater","question","at","A","B","C", - "D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W", - "X","Y","Z","bracketleft","backslash","bracketright","asciicircum","underscore", - "grave","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r", - "s","t","u","v","w","x","y","z","braceleft","bar","braceright","asciitilde", - "bullet","Euro","bullet","quotesinglbase","florin","quotedblbase","ellipsis", - "dagger","daggerdbl","circumflex","perthousand","Scaron","guilsinglleft","OE", - "bullet","Zcaron","bullet","bullet","quoteleft","quoteright","quotedblleft", - "quotedblright","bullet","endash","emdash","tilde","trademark","scaron", - "guilsinglright","oe","bullet","zcaron","Ydieresis","space","exclamdown","cent", - "sterling","currency","yen","brokenbar","section","dieresis","copyright", - "ordfeminine","guillemotleft","logicalnot","hyphen","registered","macron", - "degree","plusminus","twosuperior","threesuperior","acute","mu","paragraph", - "periodcentered","cedilla","onesuperior","ordmasculine","guillemotright", - "onequarter","onehalf","threequarters","questiondown","Agrave","Aacute", - "Acircumflex","Atilde","Adieresis","Aring","AE","Ccedilla","Egrave","Eacute", - "Ecircumflex","Edieresis","Igrave","Iacute","Icircumflex","Idieresis","Eth", - "Ntilde","Ograve","Oacute","Ocircumflex","Otilde","Odieresis","multiply","Oslash", - "Ugrave","Uacute","Ucircumflex","Udieresis","Yacute","Thorn","germandbls", - "agrave","aacute","acircumflex","atilde","adieresis","aring","ae","ccedilla", - "egrave","eacute","ecircumflex","edieresis","igrave","iacute","icircumflex", - "idieresis","eth","ntilde","ograve","oacute","ocircumflex","otilde","odieresis", - "divide","oslash","ugrave","uacute","ucircumflex","udieresis","yacute","thorn", - "ydieresis" + return shadow(this, 'WinAnsiEncoding', + [,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, + 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent', + 'ampersand', 'quotesingle', 'parenleft', 'parenright', 'asterisk', + 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one', 'two', + 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', + 'semicolon', 'less', 'equal', 'greater', 'question', 'at', 'A', 'B', 'C', + 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', + 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', + 'bracketright', 'asciicircum', 'underscore', 'grave', 'a', 'b', 'c', 'd', + 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', + 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', + 'asciitilde', 'bullet', 'Euro', 'bullet', 'quotesinglbase', 'florin', + 'quotedblbase', 'ellipsis', 'dagger', 'daggerdbl', 'circumflex', + 'perthousand', 'Scaron', 'guilsinglleft', 'OE', 'bullet', 'Zcaron', + 'bullet', 'bullet', 'quoteleft', 'quoteright', 'quotedblleft', + 'quotedblright', 'bullet', 'endash', 'emdash', 'tilde', 'trademark', + 'scaron', 'guilsinglright', 'oe', 'bullet', 'zcaron', 'Ydieresis', + 'space', 'exclamdown', 'cent', 'sterling', 'currency', 'yen', 'brokenbar', + 'section', 'dieresis', 'copyright', 'ordfeminine', 'guillemotleft', + 'logicalnot', 'hyphen', 'registered', 'macron', 'degree', 'plusminus', + 'twosuperior', 'threesuperior', 'acute', 'mu', 'paragraph', + 'periodcentered', 'cedilla', 'onesuperior', 'ordmasculine', + 'guillemotright', 'onequarter', 'onehalf', 'threequarters', + 'questiondown', 'Agrave', 'Aacute', 'Acircumflex', 'Atilde', 'Adieresis', + 'Aring', 'AE', 'Ccedilla', 'Egrave', 'Eacute', 'Ecircumflex', + 'Edieresis', 'Igrave', 'Iacute', 'Icircumflex', 'Idieresis', 'Eth', + 'Ntilde', 'Ograve', 'Oacute', 'Ocircumflex', 'Otilde', 'Odieresis', + 'multiply', 'Oslash', 'Ugrave', 'Uacute', 'Ucircumflex', 'Udieresis', + 'Yacute', 'Thorn', 'germandbls', 'agrave', 'aacute', 'acircumflex', + 'atilde', 'adieresis', 'aring', 'ae', 'ccedilla', 'egrave', 'eacute', + 'ecircumflex', 'edieresis', 'igrave', 'iacute', 'icircumflex', + 'idieresis', 'eth', 'ntilde', 'ograve', 'oacute', 'ocircumflex', + 'otilde', 'odieresis', 'divide', 'oslash', 'ugrave', 'uacute', + 'ucircumflex', 'udieresis', 'yacute', 'thorn', 'ydieresis' ]); }, get zapfDingbatsEncoding() { - return shadow(this, "zapfDingbatsEncoding", [ ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, - "space","a1","a2","a202","a3","a4","a5","a119","a118","a117","a11","a12","a13", - "a14","a15","a16","a105","a17","a18","a19","a20","a21","a22","a23","a24","a25", - "a26","a27","a28","a6","a7","a8","a9","a10","a29","a30","a31","a32","a33","a34", - "a35","a36","a37","a38","a39","a40","a41","a42","a43","a44","a45","a46","a47", - "a48","a49","a50","a51","a52","a53","a54","a55","a56","a57","a58","a59","a60", - "a61","a62","a63","a64","a65","a66","a67","a68","a69","a70","a71","a72","a73", - "a74","a203","a75","a204","a76","a77","a78","a79","a81","a82","a83","a84","a97", - "a98","a99","a100",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,"a101","a102","a103","a104", - "a106","a107","a108","a112","a111","a110","a109","a120","a121","a122","a123", - "a124","a125","a126","a127","a128","a129","a130","a131","a132","a133","a134", - "a135","a136","a137","a138","a139","a140","a141","a142","a143","a144","a145", - "a146","a147","a148","a149","a150","a151","a152","a153","a154","a155","a156", - "a157","a158","a159","a160","a161","a163","a164","a196","a165","a192","a166", - "a167","a168","a169","a170","a171","a172","a173","a162","a174","a175","a176", - "a177","a178","a179","a193","a180","a199","a181","a200","a182",,"a201","a183", - "a184","a197","a185","a194","a198","a186","a195","a187","a188","a189","a190", - "a191" + return shadow(this, 'zapfDingbatsEncoding', + [,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, + 'space', 'a1', 'a2', 'a202', 'a3', 'a4', 'a5', 'a119', 'a118', 'a117', + 'a11', 'a12', 'a13', 'a14', 'a15', 'a16', 'a105', 'a17', 'a18', 'a19', + 'a20', 'a21', 'a22', 'a23', 'a24', 'a25', 'a26', 'a27', 'a28', 'a6', + 'a7', 'a8', 'a9', 'a10', 'a29', 'a30', 'a31', 'a32', 'a33', 'a34', 'a35', + 'a36', 'a37', 'a38', 'a39', 'a40', 'a41', 'a42', 'a43', 'a44', 'a45', + 'a46', 'a47', 'a48', 'a49', 'a50', 'a51', 'a52', 'a53', 'a54', 'a55', + 'a56', 'a57', 'a58', 'a59', 'a60', 'a61', 'a62', 'a63', 'a64', 'a65', + 'a66', 'a67', 'a68', 'a69', 'a70', 'a71', 'a72', 'a73', 'a74', 'a203', + 'a75', 'a204', 'a76', 'a77', 'a78', 'a79', 'a81', 'a82', 'a83', 'a84', + 'a97', 'a98', 'a99', 'a100',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, 'a101', + 'a102', 'a103', 'a104', 'a106', 'a107', 'a108', 'a112', 'a111', 'a110', + 'a109', 'a120', 'a121', 'a122', 'a123', 'a124', 'a125', 'a126', 'a127', + 'a128', 'a129', 'a130', 'a131', 'a132', 'a133', 'a134', 'a135', 'a136', + 'a137', 'a138', 'a139', 'a140', 'a141', 'a142', 'a143', 'a144', 'a145', + 'a146', 'a147', 'a148', 'a149', 'a150', 'a151', 'a152', 'a153', 'a154', + 'a155', 'a156', 'a157', 'a158', 'a159', 'a160', 'a161', 'a163', 'a164', + 'a196', 'a165', 'a192', 'a166', 'a167', 'a168', 'a169', 'a170', 'a171', + 'a172', 'a173', 'a162', 'a174', 'a175', 'a176', 'a177', 'a178', 'a179', + 'a193', 'a180', 'a199', 'a181', 'a200', 'a182',, 'a201', 'a183', 'a184', + 'a197', 'a185', 'a194', 'a198', 'a186', 'a195', 'a187', 'a188', 'a189', + 'a190', 'a191' ]); } }; -var IDENTITY_MATRIX = [ 1, 0, 0, 1, 0, 0 ]; +var IDENTITY_MATRIX = [1, 0, 0, 1, 0, 0]; + +var EvalState = (function() { + function constructor() { + // Are soft masks and alpha values shapes or opacities? + this.alphaIsShape = false; + this.fontSize = 0; + this.textMatrix = IDENTITY_MATRIX; + this.leading = 0; + // Start of text line (in text coordinates) + this.lineX = 0; + this.lineY = 0; + // Character and word spacing + this.charSpace = 0; + this.wordSpace = 0; + this.textHScale = 100; + // Color spaces + this.fillColorSpace = null; + this.strokeColorSpace = null; + } + constructor.prototype = { + }; + return constructor; +})(); + +var PartialEvaluator = (function() { + function constructor() { + this.state = new EvalState(); + this.stateStack = [ ]; + } + + var OP_MAP = { + // Graphics state + w: 'setLineWidth', + J: 'setLineCap', + j: 'setLineJoin', + M: 'setMiterLimit', + d: 'setDash', + ri: 'setRenderingIntent', + i: 'setFlatness', + gs: 'setGState', + q: 'save', + Q: 'restore', + cm: 'transform', + + // Path + m: 'moveTo', + l: 'lineTo', + c: 'curveTo', + v: 'curveTo2', + y: 'curveTo3', + h: 'closePath', + re: 'rectangle', + S: 'stroke', + s: 'closeStroke', + f: 'fill', + F: 'fill', + 'f*': 'eoFill', + B: 'fillStroke', + 'B*': 'eoFillStroke', + b: 'closeFillStroke', + 'b*': 'closeEOFillStroke', + n: 'endPath', + + // Clipping + W: 'clip', + 'W*': 'eoClip', + + // Text + BT: 'beginText', + ET: 'endText', + Tc: 'setCharSpacing', + Tw: 'setWordSpacing', + Tz: 'setHScale', + TL: 'setLeading', + Tf: 'setFont', + Tr: 'setTextRenderingMode', + Ts: 'setTextRise', + Td: 'moveText', + TD: 'setLeadingMoveText', + Tm: 'setTextMatrix', + 'T*': 'nextLine', + Tj: 'showText', + TJ: 'showSpacedText', + "'": 'nextLineShowText', + '"': 'nextLineSetSpacingShowText', + + // Type3 fonts + d0: 'setCharWidth', + d1: 'setCharWidthAndBounds', + + // Color + CS: 'setStrokeColorSpace', + cs: 'setFillColorSpace', + SC: 'setStrokeColor', + SCN: 'setStrokeColorN', + sc: 'setFillColor', + scn: 'setFillColorN', + G: 'setStrokeGray', + g: 'setFillGray', + RG: 'setStrokeRGBColor', + rg: 'setFillRGBColor', + K: 'setStrokeCMYKColor', + k: 'setFillCMYKColor', + + // Shading + sh: 'shadingFill', + + // Images + BI: 'beginInlineImage', + + // XObjects + Do: 'paintXObject', + + // Marked content + MP: 'markPoint', + DP: 'markPointProps', + BMC: 'beginMarkedContent', + BDC: 'beginMarkedContentProps', + EMC: 'endMarkedContent', + + // Compatibility + BX: 'beginCompat', + EX: 'endCompat' + }; + + constructor.prototype = { + eval: function(stream, xref, resources, fonts) { + resources = xref.fetchIfRef(resources) || new Dict(); + var xobjs = xref.fetchIfRef(resources.get('XObject')) || new Dict(); + + var parser = new Parser(new Lexer(stream), false); + var objpool = []; + + function emitArg(arg) { + if (typeof arg == 'object' || typeof arg == 'string') { + var index = objpool.length; + objpool[index] = arg; + return 'objpool[' + index + ']'; + } + return arg; + } + + var src = ''; + + var args = []; + var obj; + while (!IsEOF(obj = parser.getObj())) { + if (IsCmd(obj)) { + var cmd = obj.cmd; + var fn = OP_MAP[cmd]; + assertWellFormed(fn, "Unknown command '" + cmd + "'"); + // TODO figure out how to type-check vararg functions + + if (cmd == 'Do' && !args[0].code) { // eagerly compile XForm objects + var name = args[0].name; + var xobj = xobjs.get(name); + if (xobj) { + xobj = xref.fetchIfRef(xobj); + assertWellFormed(IsStream(xobj), 'XObject should be a stream'); + + var type = xobj.dict.get('Subtype'); + assertWellFormed( + IsName(type), + 'XObject should have a Name subtype' + ); + + if ('Form' == type.name) { + args[0].code = this.eval(xobj, + xref, + xobj.dict.get('Resources'), + fonts); + } + } + } else if (cmd == 'Tf') { // eagerly collect all fonts + var fontRes = resources.get('Font'); + if (fontRes) { + fontRes = xref.fetchIfRef(fontRes); + var font = xref.fetchIfRef(fontRes.get(args[0].name)); + assertWellFormed(IsDict(font)); + if (!font.translated) { + font.translated = this.translateFont(font, xref, resources); + if (fonts && font.translated) { + // keep track of each font we translated so the caller can + // load them asynchronously before calling display on a page + fonts.push(font.translated); + } + } + } + } + + src += 'this.'; + src += fn; + src += '('; + src += args.map(emitArg).join(','); + src += ');\n'; + + args.length = 0; + } else { + assertWellFormed(args.length <= 33, 'Too many arguments'); + args.push(obj); + } + } + + var fn = Function('objpool', src); + return function(gfx) { fn.call(gfx, objpool); }; + }, + + translateFont: function(fontDict, xref, resources) { + var fd = fontDict.get('FontDescriptor'); + if (!fd) + // XXX deprecated "special treatment" for standard + // fonts? What do we need to do here? + return null; + var descriptor = xref.fetch(fd); + + var fontName = descriptor.get('FontName'); + assertWellFormed(IsName(fontName), 'invalid font name'); + fontName = fontName.name.replace('+', '_'); + + var fontFile = descriptor.get('FontFile', 'FontFile2', 'FontFile3'); + if (!fontFile) + error('FontFile not found for font: ' + fontName); + fontFile = xref.fetchIfRef(fontFile); + + var encodingMap = {}; + var charset = []; + if (fontDict.has('Encoding')) { + var encoding = xref.fetchIfRef(fontDict.get('Encoding')); + if (IsDict(encoding)) { + // Build a map of between codes and glyphs + // Load the base encoding + var baseName = encoding.get('BaseEncoding'); + if (baseName) { + var base = Encodings[baseName.name]; + var index = 0; + for (var j = 0, end = base.length; j < end; j++) + encodingMap[index++] = GlyphsUnicode[base[j]]; + } else { + TODO('need to load default encoding'); + } + + // Load the differences between the base and original + var differences = encoding.get('Differences'); + var index = 0; + for (var j = 0; j < differences.length; j++) { + var data = differences[j]; + IsNum(data) ? index = data : encodingMap[index++] = data; + } + + // Get the font charset if any + var charset = descriptor.get('CharSet'); + if (charset) { + assertWellFormed(IsString(charset), 'invalid charset'); + charset = charset.split('/'); + charset.shift(); + } + } else if (IsName(encoding)) { + var encoding = Encodings[encoding.name]; + if (!encoding) + error('Unknown font encoding'); + + var index = 0; + for (var j = 0; j < encoding.length; j++) + encodingMap[index++] = GlyphsUnicode[encoding[j]]; + + var firstChar = xref.fetchIfRef(fontDict.get('FirstChar')); + var widths = xref.fetchIfRef(fontDict.get('Widths')); + assertWellFormed(IsArray(widths) && IsInt(firstChar), + 'invalid font Widths or FirstChar'); + + for (var j = 0; j < widths.length; j++) { + if (widths[j]) + charset.push(encoding[j + firstChar]); + } + } + } else if (fontDict.has('ToUnicode')) { + encodingMap = {empty: true}; + var cmapObj = xref.fetchIfRef(fontDict.get('ToUnicode')); + if (IsName(cmapObj)) { + error('ToUnicode file cmap translation not implemented'); + } else if (IsStream(cmapObj)) { + var encoding = Encodings['WinAnsiEncoding']; + var firstChar = xref.fetchIfRef(fontDict.get('FirstChar')); + + var tokens = []; + var token = ''; + + var cmap = cmapObj.getBytes(cmapObj.length); + for (var i = 0; i < cmap.length; i++) { + var byte = cmap[i]; + if (byte == 0x20 || byte == 0x0A || byte == 0x3C || byte == 0x3E) { + switch (token) { + case 'useCMap': + error('useCMap is not implemented'); + break; + + case 'beginbfrange': + case 'begincodespacerange': + token = ''; + tokens = []; + break; + + case 'endcodespacerange': + TODO('Support CMap ranges'); + break; + + case 'endbfrange': + for (var j = 0; j < tokens.length; j += 3) { + var startRange = parseInt('0x' + tokens[j]); + var endRange = parseInt('0x' + tokens[j + 1]); + var code = parseInt('0x' + tokens[j + 2]); + + for (var k = startRange; k <= endRange; k++) { + // The encoding mapping table will be filled + // later during the building phase + //encodingMap[k] = GlyphsUnicode[encoding[code]]; + charset.push(encoding[code++] || '.notdef'); + } + } + break; + + case 'beginfbchar': + case 'endfbchar': + error('fbchar parsing is not implemented'); + break; + + default: + if (token.length) { + tokens.push(token); + token = ''; + } + break; + } + } else if (byte == 0x5B || byte == 0x5D) { + error('CMAP list parsing is not implemented'); + } else { + token += String.fromCharCode(byte); + } + } + } + } + + var subType = fontDict.get('Subtype'); + assertWellFormed(IsName(subType), 'invalid font Subtype'); + + var properties = { + type: subType.name, + encoding: encodingMap, + charset: charset, + firstChar: fontDict.get('FirstChar'), + lastChar: fontDict.get('LastChar'), + bbox: descriptor.get('FontBBox'), + ascent: descriptor.get('Ascent'), + descent: descriptor.get('Descent'), + xHeight: descriptor.get('XHeight'), + capHeight: descriptor.get('CapHeight'), + flags: descriptor.get('Flags'), + italicAngle: descriptor.get('ItalicAngle'), + fixedPitch: false, + textMatrix: IDENTITY_MATRIX + }; + + return { + name: fontName, + fontDict: fontDict, + file: fontFile, + properties: properties + }; + }, + }; + + return constructor; +})(); // contexts store most of the state we need natively. // However, PDF needs a bit more state, which we store here. var CanvasExtraState = (function() { - function constructor() { - // Are soft masks and alpha values shapes or opacities? - this.alphaIsShape = false; - this.fontSize = 0; - this.textMatrix = IDENTITY_MATRIX; - this.leading = 0; - // Current point (in user coordinates) - this.x = 0; - this.y = 0; - // Start of text line (in text coordinates) - this.lineX = 0; - this.lineY = 0; - // Character and word spacing - this.charSpace = 0; - this.wordSpace = 0; - this.textHScale = 100; - // Color spaces - this.fillColorSpace = null; - this.strokeColorSpace = null; - } - constructor.prototype = { - }; - return constructor; + function constructor() { + // Are soft masks and alpha values shapes or opacities? + this.alphaIsShape = false; + this.fontSize = 0; + this.textMatrix = IDENTITY_MATRIX; + this.leading = 0; + // Current point (in user coordinates) + this.x = 0; + this.y = 0; + // Start of text line (in text coordinates) + this.lineX = 0; + this.lineY = 0; + // Character and word spacing + this.charSpace = 0; + this.wordSpace = 0; + this.textHScale = 100; + // Color spaces + this.fillColorSpace = null; + this.strokeColorSpace = null; + } + constructor.prototype = { + }; + return constructor; })(); function ScratchCanvas(width, height) { - var canvas = document.createElement("canvas"); - canvas.width = width; - canvas.height = height; - return canvas; + var canvas = document.createElement('canvas'); + canvas.width = width; + canvas.height = height; + return canvas; } var CanvasGraphics = (function() { - function constructor(canvasCtx, imageCanvas) { - this.ctx = canvasCtx; - this.current = new CanvasExtraState(); - this.stateStack = [ ]; - this.pendingClip = null; - this.res = null; - this.xobjs = null; - this.ScratchCanvas = imageCanvas || ScratchCanvas; - } - - var LINE_CAP_STYLES = [ "butt", "round", "square" ]; - var LINE_JOIN_STYLES = [ "miter", "round", "bevel" ]; - var NORMAL_CLIP = {}; - var EO_CLIP = {}; - - // Used for tiling patterns - var PAINT_TYPE_COLORED = 1, PAINT_TYPE_UNCOLORED = 2; - - constructor.prototype = { - map: { - // Graphics state - w: "setLineWidth", - J: "setLineCap", - j: "setLineJoin", - M: "setMiterLimit", - d: "setDash", - ri: "setRenderingIntent", - i: "setFlatness", - gs: "setGState", - q: "save", - Q: "restore", - cm: "transform", - - // Path - m: "moveTo", - l: "lineTo", - c: "curveTo", - v: "curveTo2", - y: "curveTo3", - h: "closePath", - re: "rectangle", - S: "stroke", - s: "closeStroke", - f: "fill", - F: "fill", - "f*": "eoFill", - B: "fillStroke", - "B*": "eoFillStroke", - b: "closeFillStroke", - "b*": "closeEOFillStroke", - n: "endPath", - - // Clipping - W: "clip", - "W*": "eoClip", - - // Text - BT: "beginText", - ET: "endText", - Tc: "setCharSpacing", - Tw: "setWordSpacing", - Tz: "setHScale", - TL: "setLeading", - Tf: "setFont", - Tr: "setTextRenderingMode", - Ts: "setTextRise", - Td: "moveText", - TD: "setLeadingMoveText", - Tm: "setTextMatrix", - "T*": "nextLine", - Tj: "showText", - TJ: "showSpacedText", - "'": "nextLineShowText", - '"': "nextLineSetSpacingShowText", - - // Type3 fonts - d0: "setCharWidth", - d1: "setCharWidthAndBounds", - - // Color - CS: "setStrokeColorSpace", - cs: "setFillColorSpace", - SC: "setStrokeColor", - SCN: "setStrokeColorN", - sc: "setFillColor", - scn: "setFillColorN", - G: "setStrokeGray", - g: "setFillGray", - RG: "setStrokeRGBColor", - rg: "setFillRGBColor", - K: "setStrokeCMYKColor", - k: "setFillCMYKColor", - - // Shading - sh: "shadingFill", - - // Images - BI: "beginInlineImage", - - // XObjects - Do: "paintXObject", - - // Marked content - MP: "markPoint", - DP: "markPointProps", - BMC: "beginMarkedContent", - BDC: "beginMarkedContentProps", - EMC: "endMarkedContent", - - // Compatibility - BX: "beginCompat", - EX: "endCompat", - }, - - translateFont: function(fontDict, xref, resources) { - var fd = fontDict.get("FontDescriptor"); - if (!fd) - // XXX deprecated "special treatment" for standard - // fonts? What do we need to do here? - return null; - var descriptor = xref.fetch(fd); - - var fontName = descriptor.get("FontName"); - assertWellFormed(IsName(fontName), "invalid font name"); - fontName = fontName.name.replace("+", "_"); - - var fontFile = descriptor.get3("FontFile", "FontFile2", "FontFile3"); - if (!fontFile) - error("FontFile not found for font: " + fontName); - fontFile = xref.fetchIfRef(fontFile); - - var encodingMap = {}; - var charset = []; - if (fontDict.has("Encoding")) { - var encoding = xref.fetchIfRef(fontDict.get("Encoding")); - if (IsDict(encoding)) { - // Build a map between codes and glyphs - var differences = encoding.get("Differences"); - var index = 0; - for (var j = 0; j < differences.length; j++) { - var data = differences[j]; - IsNum(data) ? index = data : encodingMap[index++] = data; - } - - // Get the font charset if any - var charset = descriptor.get("CharSet"); - if (charset) { - assertWellFormed(IsString(charset), "invalid charset"); - charset = charset.split("/"); - charset.shift(); - } - } else if (IsName(encoding)) { - var encoding = Encodings[encoding.name]; - if (!encoding) - error("Unknown font encoding"); - - var index = 0; - for (var j = 0; j < encoding.length; j++) - encodingMap[index++] = GlyphsUnicode[encoding[j]]; - - var firstChar = xref.fetchIfRef(fontDict.get("FirstChar")); - var widths = xref.fetchIfRef(fontDict.get("Widths")); - assertWellFormed(IsArray(widths) && IsInt(firstChar), - "invalid font Widths or FirstChar"); - - for (var j = 0; j < widths.length; j++) { - if (widths[j]) - charset.push(encoding[j + firstChar]); - } - } - } else if (fontDict.has("ToUnicode")) { - encodingMap = {empty: true}; - var cmapObj = xref.fetchIfRef(fontDict.get("ToUnicode")); - if (IsName(cmapObj)) { - error("ToUnicode file cmap translation not implemented"); - } else if (IsStream(cmapObj)) { - var encoding = Encodings["WinAnsiEncoding"]; - var firstChar = xref.fetchIfRef(fontDict.get("FirstChar")); - - var tokens = []; - var token = ""; - - var cmap = cmapObj.getBytes(cmapObj.length); - for (var i =0; i < cmap.length; i++) { - var byte = cmap[i]; - if (byte == 0x20 || byte == 0x0A || byte == 0x3C || byte == 0x3E) { - switch (token) { - case "useCMap": - error("useCMap is not implemented"); - break; - - case "beginbfrange": - case "begincodespacerange": - token = ""; - tokens = []; - break; - - case "endcodespacerange": - TODO("Support CMap ranges"); - break; - - case "endbfrange": - for (var j = 0; j < tokens.length; j+=3) { - var startRange = parseInt("0x" + tokens[j]); - var endRange = parseInt("0x" + tokens[j+1]); - var code = parseInt("0x" + tokens[j+2]); - - for (var k = startRange; k <= endRange; k++) { - // The encoding mapping table will be filled - // later during the building phase - //encodingMap[k] = GlyphsUnicode[encoding[code]]; - charset.push(encoding[code++] || ".notdef"); - } - } - break; - - case "beginfbchar": - case "endfbchar": - error("fbchar parsing is not implemented"); - break; - - default: - if (token.length) { - tokens.push(token); - token = ""; - } - break; - } - } else if (byte == 0x5B || byte == 0x5D) { - error("CMAP list parsing is not implemented"); - } else { - token += String.fromCharCode(byte); - } - } - } - } - - var subType = fontDict.get("Subtype"); - assertWellFormed(IsName(subType), "invalid font Subtype"); - - var properties = { - type: subType.name, - encoding: encodingMap, - charset: charset, - firstChar: fontDict.get("FirstChar"), - lastChar: fontDict.get("LastChar"), - bbox: descriptor.get("FontBBox"), - ascent: descriptor.get("Ascent"), - descent: descriptor.get("Descent"), - xHeight: descriptor.get("XHeight"), - capHeight: descriptor.get("CapHeight"), - flags: descriptor.get("Flags"), - italicAngle: descriptor.get("ItalicAngle"), - fixedPitch: false, - textMatrix: IDENTITY_MATRIX - }; - - return { - name: fontName, - file: fontFile, - properties: properties - } - }, - - beginDrawing: function(mediaBox) { - var cw = this.ctx.canvas.width, ch = this.ctx.canvas.height; - this.ctx.save(); - this.ctx.scale(cw / mediaBox.width, -ch / mediaBox.height); - this.ctx.translate(0, -mediaBox.height); - }, - - execute: function(code, xref, resources) { - var savedXref = this.xref, savedRes = this.res, savedXobjs = this.xobjs; - this.xref = xref; - this.res = resources || new Dict(); - this.xobjs = xref.fetchIfRef(this.res.get("XObject")) || new Dict(); - - code(this); - - this.xobjs = savedXobjs; - this.res = savedRes; - this.xref = savedXref; - }, - - compile: function(stream, xref, resources, fonts) { - var xobjs = xref.fetchIfRef(resources.get("XObject")) || new Dict(); - - var parser = new Parser(new Lexer(stream), false); - var objpool = []; - - function emitArg(arg) { - if (typeof arg == "object" || typeof arg == "string") { - var index = objpool.length; - objpool[index] = arg; - return "objpool[" + index + "]"; - } - return arg; - } - - var src = ""; - - var args = []; - var map = this.map; - var obj; - while (!IsEOF(obj = parser.getObj())) { - if (IsCmd(obj)) { - var cmd = obj.cmd; - var fn = map[cmd]; - assertWellFormed(fn, "Unknown command '" + cmd + "'"); - // TODO figure out how to type-check vararg functions - - if (cmd == "Do" && !args[0].code) { // eagerly compile XForm objects - var name = args[0].name; - var xobj = xobjs.get(name); - if (xobj) { - xobj = xref.fetchIfRef(xobj); - assertWellFormed(IsStream(xobj), "XObject should be a stream"); - - var type = xobj.dict.get("Subtype"); - assertWellFormed(IsName(type), "XObject should have a Name subtype"); - - if ("Form" == type.name) { - args[0].code = this.compile(xobj, - xref, - xobj.dict.get("Resources"), - fonts); - } - } - } else if (cmd == "Tf") { // eagerly collect all fonts - var fontRes = resources.get("Font"); - if (fontRes) { - fontRes = xref.fetchIfRef(fontRes); - var font = xref.fetchIfRef(fontRes.get(args[0].name)); - assertWellFormed(IsDict(font)); - if (!font.translated) { - font.translated = this.translateFont(font, xref, resources); - if (fonts && font.translated) { - // keep track of each font we translated so the caller can - // load them asynchronously before calling display on a page - fonts.push(font.translated); - } - } - } - } - - src += "this."; - src += fn; - src += "("; - src += args.map(emitArg).join(","); - src += ");\n"; - - args.length = 0; - } else { - assertWellFormed(args.length <= 33, "Too many arguments"); - args.push(obj); - } - } - - var fn = Function("objpool", src); - return function (gfx) { fn.call(gfx, objpool); }; - }, - - endDrawing: function() { - this.ctx.restore(); - }, - - // Graphics state - setLineWidth: function(width) { - this.ctx.lineWidth = width; - }, - setLineCap: function(style) { - this.ctx.lineCap = LINE_CAP_STYLES[style]; - }, - setLineJoin: function(style) { - this.ctx.lineJoin = LINE_JOIN_STYLES[style]; - }, - setMiterLimit: function(limit) { - this.ctx.miterLimit = limit; - }, - setDash: function(dashArray, dashPhase) { - this.ctx.mozDash = dashArray; - this.ctx.mozDashOffset = dashPhase; - }, - setRenderingIntent: function(intent) { - TODO("set rendering intent"); - }, - setFlatness: function(flatness) { - TODO("set flatness"); - }, - setGState: function(dictName) { - TODO("set graphics state from dict"); - }, - save: function() { - this.ctx.save(); - if (this.ctx.$saveCurrentX) { - this.ctx.$saveCurrentX(); - } - this.stateStack.push(this.current); - this.current = new CanvasExtraState(); - }, - restore: function() { - var prev = this.stateStack.pop(); - if (prev) { - if (this.ctx.$restoreCurrentX) { - this.ctx.$restoreCurrentX(); - } - this.current = prev; - this.ctx.restore(); - } - }, - transform: function(a, b, c, d, e, f) { - this.ctx.transform(a, b, c, d, e, f); - }, - - // Path - moveTo: function(x, y) { - this.ctx.moveTo(x, y); - }, - lineTo: function(x, y) { - this.ctx.lineTo(x, y); - }, - curveTo: function(x1, y1, x2, y2, x3, y3) { - this.ctx.bezierCurveTo(x1, y1, x2, y2, x3, y3); - }, - curveTo2: function(x2, y2, x3, y3) { - TODO("'v' operator: need current point in gfx context"); - }, - curveTo3: function(x1, y1, x3, y3) { - this.curveTo(x1, y1, x3, y3, x3, y3); - }, - closePath: function() { - this.ctx.closePath(); - }, - rectangle: function(x, y, width, height) { - this.ctx.rect(x, y, width, height); - }, - stroke: function() { - this.ctx.stroke(); - this.consumePath(); - }, - closeStroke: function() { - this.closePath(); - this.stroke(); - }, - fill: function() { - this.ctx.fill(); - this.consumePath(); - }, - eoFill: function() { - var savedFillRule = this.setEOFillRule(); - this.fill(); - this.restoreFillRule(savedFillRule); - }, - fillStroke: function() { - this.ctx.fill(); - this.ctx.stroke(); - this.consumePath(); - }, - eoFillStroke: function() { - var savedFillRule = this.setEOFillRule(); - this.fillStroke(); - this.restoreFillRule(savedFillRule); - }, - closeFillStroke: function() { - return this.fillStroke(); - }, - closeEOFillStroke: function() { - var savedFillRule = this.setEOFillRule(); - this.fillStroke(); - this.restoreFillRule(savedFillRule); - }, - endPath: function() { - this.consumePath(); - }, - - // Clipping - clip: function() { - this.pendingClip = NORMAL_CLIP; - }, - eoClip: function() { - this.pendingClip = EO_CLIP; - }, - - // Text - beginText: function() { - this.current.textMatrix = IDENTITY_MATRIX; - if (this.ctx.$setCurrentX) { - this.ctx.$setCurrentX(0) - } - this.current.x = this.current.lineX = 0; - this.current.y = this.current.lineY = 0; - }, - endText: function() { - }, - setCharSpacing: function(spacing) { - this.ctx.charSpacing = spacing; - }, - setWordSpacing: function(spacing) { - this.ctx.wordSpacing = spacing; - }, - setHScale: function(scale) { - this.ctx.textHScale = (scale % 100) * 0.01; - }, - setLeading: function(leading) { - this.current.leading = leading; - }, - setFont: function(fontRef, size) { - var font = this.res.get("Font"); - if (!IsDict(font)) - return; - - font = font.get(fontRef.name); - font = this.xref.fetchIfRef(font); - if (!font) - return; - - var fontName = ""; - var fontDescriptor = font.get("FontDescriptor"); - if (fontDescriptor && fontDescriptor.num) { - var fontDescriptor = this.xref.fetchIfRef(fontDescriptor); - fontName = fontDescriptor.get("FontName").name.replace("+", "_"); - } - - if (!fontName) { - // TODO: fontDescriptor is not available, fallback to default font - fontName = "sans-serif"; - } - - this.current.fontName = fontName; - this.current.fontSize = size; - - if (this.ctx.$setFont) { - this.ctx.$setFont(fontName, size); - } else { - this.ctx.font = size + 'px "' + fontName + '"'; - Fonts.setActive(fontName, size); - } - }, - setTextRenderingMode: function(mode) { - TODO("text rendering mode"); - }, - setTextRise: function(rise) { - TODO("text rise"); - }, - moveText: function (x, y) { - this.current.x = this.current.lineX += x; - this.current.y = this.current.lineY += y; - if (this.ctx.$setCurrentX) { - this.ctx.$setCurrentX(this.current.x) - } - }, - setLeadingMoveText: function(x, y) { - this.setLeading(-y); - this.moveText(x, y); - }, - setTextMatrix: function(a, b, c, d, e, f) { - this.current.textMatrix = [ a, b, c, d, e, f ]; - - if (this.ctx.$setCurrentX) { - this.ctx.$setCurrentX(0) - } - this.current.x = this.current.lineX = 0; - this.current.y = this.current.lineY = 0; - }, - nextLine: function() { - this.moveText(0, this.current.leading); - }, - showText: function(text) { - // TODO: apply charSpacing, wordSpacing, textHScale - - this.ctx.save(); - this.ctx.transform.apply(this.ctx, this.current.textMatrix); - this.ctx.scale(1, -1); - - if (this.ctx.$showText) { - this.ctx.$showText(this.current.y, Fonts.charsToUnicode(text)); - } else { - text = Fonts.charsToUnicode(text); - this.ctx.translate(this.current.x, -1 * this.current.y); - - var font = Fonts.lookup(this.current.fontName); - if (font) - this.ctx.transform.apply(this.ctx, font.properties.textMatrix); - - this.ctx.fillText(text, 0, 0); - this.current.x += Fonts.measureText(text); - } - - this.ctx.restore(); - }, - showSpacedText: function(arr) { - for (var i = 0; i < arr.length; ++i) { - var e = arr[i]; - if (IsNum(e)) { - if (this.ctx.$addCurrentX) { - this.ctx.$addCurrentX(-e * 0.001 * this.current.fontSize) - } else { - this.current.x -= e * 0.001 * this.current.fontSize; - } - } else if (IsString(e)) { - this.showText(e); - } else { - malformed("TJ array element "+ e +" isn't string or num"); - } - } - }, - nextLineShowText: function(text) { - this.nextLine(); - this.showText(text); - }, - nextLineSetSpacingShowText: function(wordSpacing, charSpacing, text) { - this.setWordSpacing(wordSpacing); - this.setCharSpacing(charSpacing); - this.nextLineShowText(text); - }, - - // Type3 fonts - setCharWidth: function(xWidth, yWidth) { - TODO("type 3 fonts ('d0' operator)"); - }, - setCharWidthAndBounds: function(xWidth, yWidth, llx, lly, urx, ury) { - TODO("type 3 fonts ('d1' operator)"); - }, - - // Color - setStrokeColorSpace: function(space) { - this.current.strokeColorSpace = - ColorSpace.parse(space, this.xref, this.res); - }, - setFillColorSpace: function(space) { - this.current.fillColorSpace = - ColorSpace.parse(space, this.xref, this.res); - }, - setStrokeColor: function(/*...*/) { - var cs = this.getStrokeColorSpace(); - var color = cs.getRgb(arguments); - this.setStrokeRGBColor.apply(this, color); - }, - setStrokeColorN: function(/*...*/) { - // TODO real impl - TODO("check for special color spaces"); - this.setStrokeColor.apply(this, arguments); - }, - setFillColor: function(/*...*/) { - var cs = this.getFillColorSpace(); - if (cs.name == "Pattern") { - TODO("implement Pattern fill"); - return; - } - var color = cs.getRgb(arguments); - this.setFillRGBColor.apply(this, color); - }, - setFillColorN: function(/*...*/) { - var cs = this.getStrokeColorSpace(); - - if (cs.name == "Pattern") { - var patternName = arguments[0]; - if (IsName(patternName)) { - var xref = this.xref; - var patternRes = xref.fetchIfRef(this.res.get("Pattern")); - if (!patternRes) - error("Unable to find pattern resource"); - - var pattern = xref.fetchIfRef(patternRes.get(patternName.name)); - var patternDict = IsStream(pattern) ? pattern.dict : pattern; - var types = [null, this.tilingFill, - function() { TODO("Shading Patterns"); }]; - var typeNum = patternDict.get("PatternType"); - var patternFn = types[typeNum]; - if (!patternFn) - error("Unhandled pattern type"); - patternFn.call(this, pattern, patternDict); - } - } else { - // TODO real impl - this.setFillColor.apply(this, arguments); - } - }, - tilingFill: function(pattern) { - function applyMatrix(point, m) { - var x = point[0] * m[0] + point[1] * m[2] + m[4]; - var y = point[0] * m[1] + point[1] * m[3] + m[5]; - return [x,y]; - }; - - function multiply(m, tm) { - var a = m[0] * tm[0] + m[1] * tm[2]; - var b = m[0] * tm[1] + m[1] * tm[3]; - var c = m[2] * tm[0] + m[3] * tm[2]; - var d = m[2] * tm[1] + m[3] * tm[3]; - var e = m[4] * tm[0] + m[5] * tm[2] + tm[4]; - var f = m[4] * tm[1] + m[5] * tm[3] + tm[5]; - return [a, b, c, d, e, f] - }; - - this.save(); - var dict = pattern.dict; - var ctx = this.ctx; - - var paintType = dict.get("PaintType"); - switch (paintType) { - case PAINT_TYPE_COLORED: - // should go to default for color space - ctx.fillStyle = this.makeCssRgb(1, 1, 1); - ctx.strokeStyle = this.makeCssRgb(0, 0, 0); - break; - case PAINT_TYPE_UNCOLORED: - default: - error("Unsupported paint type"); - } - - TODO("TilingType"); - - var matrix = dict.get("Matrix") || IDENTITY_MATRIX; - - var bbox = dict.get("BBox"); - var x0 = bbox[0], y0 = bbox[1], x1 = bbox[2], y1 = bbox[3]; - - var xstep = dict.get("XStep"); - var ystep = dict.get("YStep"); - - // top left corner should correspond to the top left of the bbox - var topLeft = applyMatrix([x0,y0], matrix); - // we want the canvas to be as large as the step size - var botRight = applyMatrix([x0 + xstep, y0 + ystep], matrix); - - var width = botRight[0] - topLeft[0]; - var height = botRight[1] - topLeft[1]; - - // TODO: hack to avoid OOM, remove then pattern code is fixed - if (Math.abs(width) > 8192 || Math.abs(height) > 8192) - return false; - - var tmpCanvas = new this.ScratchCanvas(width, height); - - // set the new canvas element context as the graphics context - var tmpCtx = tmpCanvas.getContext("2d"); - var savedCtx = ctx; - this.ctx = tmpCtx; - - // normalize transform matrix so each step - // takes up the entire tmpCanvas (need to remove white borders) - if (matrix[1] === 0 && matrix[2] === 0) { - matrix[0] = tmpCanvas.width / xstep; - matrix[3] = tmpCanvas.height / ystep; - topLeft = applyMatrix([x0,y0], matrix); - } - - // move the top left corner of bounding box to [0,0] - matrix = multiply(matrix, [1, 0, 0, 1, -topLeft[0], -topLeft[1]]); - - this.transform.apply(this, matrix); - - if (bbox && IsArray(bbox) && 4 == bbox.length) { - this.rectangle.apply(this, bbox); - this.clip(); - this.endPath(); - } - - var xref = this.xref; - var res = xref.fetchIfRef(dict.get("Resources")); - if (!pattern.code) - pattern.code = this.compile(pattern, xref, res, []); - this.execute(pattern.code, xref, res); - - this.ctx = savedCtx; - this.restore(); - - TODO("Inverse pattern is painted"); - pattern = this.ctx.createPattern(tmpCanvas, "repeat"); - this.ctx.fillStyle = pattern; - }, - setStrokeGray: function(gray) { - this.setStrokeRGBColor(gray, gray, gray); - }, - setFillGray: function(gray) { - this.setFillRGBColor(gray, gray, gray); - }, - setStrokeRGBColor: function(r, g, b) { - this.ctx.strokeStyle = this.makeCssRgb(r, g, b); - }, - setFillRGBColor: function(r, g, b) { - this.ctx.fillStyle = this.makeCssRgb(r, g, b); - }, - setStrokeCMYKColor: function(c, m, y, k) { - this.ctx.strokeStyle = this.makeCssCmyk(c, m, y, k); - }, - setFillCMYKColor: function(c, m, y, k) { - this.ctx.fillStyle = this.makeCssCmyk(c, m, y, k); - }, - - // Shading - shadingFill: function(entryRef) { - var xref = this.xref; - var res = this.res; - - var shadingRes = xref.fetchIfRef(res.get("Shading")); - if (!shadingRes) - error("No shading resource found"); - - var shading = xref.fetchIfRef(shadingRes.get(entryRef.name)); - if (!shading) - error("No shading object found"); - - this.save(); - - var bbox = shading.get("BBox"); - if (bbox && IsArray(bbox) && 4 == bbox.length) { - this.rectangle.apply(this, bbox); - this.clip(); - this.endPath(); - } - - var cs = shading.get2("ColorSpace", "CS"); - TODO("shading-fill color space"); - - var background = shading.get("Background"); - if (background) - TODO("handle background colors"); - - var types = [null, - this.fillFunctionShading, - this.fillAxialShading, - this.fillRadialShading]; - - var typeNum = shading.get("ShadingType"); - var fillFn = types[typeNum]; - if (!fillFn) - error("Unknown or NYI type of shading '"+ typeNum +"'"); - fillFn.apply(this, [shading]); - - this.restore(); - }, - - fillAxialShading: function(sh) { - var coordsArr = sh.get("Coords"); - var x0 = coordsArr[0], y0 = coordsArr[1], - x1 = coordsArr[2], y1 = coordsArr[3]; - - var t0 = 0.0, t1 = 1.0; - if (sh.has("Domain")) { - var domainArr = sh.get("Domain"); - t0 = domainArr[0], t1 = domainArr[1]; - } - - var extendStart = false, extendEnd = false; - if (sh.has("Extend")) { - var extendArr = sh.get("Extend"); - extendStart = extendArr[0], extendEnd = extendArr[1]; - TODO("Support extend"); - } - var fnObj = sh.get("Function"); - fnObj = this.xref.fetchIfRef(fnObj); - if (IsArray(fnObj)) - error("No support for array of functions"); - else if (!IsPDFFunction(fnObj)) - error("Invalid function"); - var fn = new PDFFunction(this.xref, fnObj); - - var gradient = this.ctx.createLinearGradient(x0, y0, x1, y1); - - // 10 samples seems good enough for now, but probably won't work - // if there are sharp color changes. Ideally, we would implement - // the spec faithfully and add lossless optimizations. - var step = (t1 - t0) / 10; - - for (var i = t0; i <= t1; i += step) { - var c = fn.func([i]); - gradient.addColorStop(i, this.makeCssRgb.apply(this, c)); - } - - this.ctx.fillStyle = gradient; - - // HACK to draw the gradient onto an infinite rectangle. - // PDF gradients are drawn across the entire image while - // Canvas only allows gradients to be drawn in a rectangle - // The following bug should allow us to remove this. - // https://bugzilla.mozilla.org/show_bug.cgi?id=664884 - this.ctx.fillRect(-1e10, -1e10, 2e10, 2e10); - }, - - fillRadialShading: function(sh) { - TODO("radial shading"); - }, - - // Images - beginInlineImage: function() { - TODO("inline images"); - error("(Stream will not be parsed properly, bailing now)"); - // Like an inline stream: - // - key/value pairs up to Cmd(ID) - // - then image data up to Cmd(EI) - }, - - // XObjects - paintXObject: function(obj) { - var xobj = this.xobjs.get(obj.name); - if (!xobj) - return; - xobj = this.xref.fetchIfRef(xobj); - assertWellFormed(IsStream(xobj), "XObject should be a stream"); - - var oc = xobj.dict.get("OC"); - if (oc) { - TODO("oc for xobject"); - } - - var opi = xobj.dict.get("OPI"); - if (opi) { - TODO("opi for xobject"); - } - - var type = xobj.dict.get("Subtype"); - assertWellFormed(IsName(type), "XObject should have a Name subtype"); - if ("Image" == type.name) { - this.paintImageXObject(obj, xobj, false); - } else if ("Form" == type.name) { - this.paintFormXObject(obj, xobj); - } else if ("PS" == type.name) { - warn("(deprecated) PostScript XObjects are not supported"); - } else { - malformed("Unknown XObject subtype "+ type.name); - } - }, - - paintFormXObject: function(ref, stream) { - this.save(); - - var matrix = stream.dict.get("Matrix"); - if (matrix && IsArray(matrix) && 6 == matrix.length) - this.transform.apply(this, matrix); - - var bbox = stream.dict.get("BBox"); - if (bbox && IsArray(bbox) && 4 == bbox.length) { - this.rectangle.apply(this, bbox); - this.clip(); - this.endPath(); - } - - this.execute(ref.code, this.xref, stream.dict.get("Resources")); - - this.restore(); - }, - - paintImageXObject: function(ref, image, inline) { - this.save(); - - var ctx = this.ctx; - var dict = image.dict; - var w = dict.get2("Width", "W"); - var h = dict.get2("Height", "H"); - // scale the image to the unit square - ctx.scale(1/w, -1/h); - - // If the platform can render the image format directly, the - // stream has a getImage property which directly returns a - // suitable DOM Image object. - if (image.getImage) { - var domImage = image.getImage(); - ctx.drawImage(domImage, 0, 0, domImage.width, domImage.height, - 0, -h, w, h); - this.restore(); - return; - } - - var imageObj = new PDFImage(this.xref, this.res, image, inline); - - var tmpCanvas = new this.ScratchCanvas(w, h); - var tmpCtx = tmpCanvas.getContext("2d"); - var imgData = tmpCtx.getImageData(0, 0, w, h); - var pixels = imgData.data; - - imageObj.fillRgbaBuffer(pixels); - - tmpCtx.putImageData(imgData, 0, 0); - ctx.drawImage(tmpCanvas, 0, -h); - this.restore(); - }, - - // Marked content - - markPoint: function(tag) { - TODO("Marked content"); - }, - markPointProps: function(tag, properties) { - TODO("Marked content"); - }, - beginMarkedContent: function(tag) { - TODO("Marked content"); - }, - beginMarkedContentProps: function(tag, properties) { - TODO("Marked content"); - }, - endMarkedContent: function() { - TODO("Marked content"); - }, - - // Compatibility - - beginCompat: function() { - TODO("ignore undefined operators (should we do that anyway?)"); - }, - endCompat: function() { - TODO("stop ignoring undefined operators"); - }, - - // Helper functions - - consumePath: function() { - if (this.pendingClip) { - var savedFillRule = null; - if (this.pendingClip == EO_CLIP) - savedFillRule = this.setEOFillRule(); - - this.ctx.clip(); - - this.pendingClip = null; - if (savedFillRule !== null) - this.restoreFillRule(savedFillRule); - } - this.ctx.beginPath(); - }, - makeCssRgb: function(r, g, b) { - var ri = (255 * r) | 0, gi = (255 * g) | 0, bi = (255 * b) | 0; - return "rgb("+ ri +","+ gi +","+ bi +")"; - }, - makeCssCmyk: function(c, m, y, k) { - // while waiting on CSS's cmyk()... http://www.ilkeratalay.com/colorspacesfaq.php#rgb - var ri = (255 * (1 - Math.min(1, c * (1 - k) + k))) | 0; - var gi = (255 * (1 - Math.min(1, m * (1 - k) + k))) | 0; - var bi = (255 * (1 - Math.min(1, y * (1 - k) + k))) | 0; - return "rgb("+ ri +","+ gi +","+ bi +")"; - }, - getFillColorSpace: function() { - var cs = this.current.fillColorSpace; - if (cs) - return cs; - - var states = this.stateStack; - var i = states.length - 1; - while (i >= 0 && !(cs = states[i].fillColorSpace)) - --i; - - if (cs) - return cs; - else - return new DeviceRgbCS(); - }, - getStrokeColorSpace: function() { - var cs = this.current.strokeColorSpace; - if (cs) - return cs; - - var states = this.stateStack; - var i = states.length - 1; - while (i >= 0 && !(cs = states[i].strokeColorSpace)) - --i; - - if (cs) - return cs; - else - return new DeviceRgbCS(); - }, - // We generally keep the canvas context set for - // nonzero-winding, and just set evenodd for the operations - // that need them. - setEOFillRule: function() { - var savedFillRule = this.ctx.mozFillRule; - this.ctx.mozFillRule = "evenodd"; - return savedFillRule; - }, - restoreFillRule: function(rule) { - this.ctx.mozFillRule = rule; + function constructor(canvasCtx, imageCanvas) { + this.ctx = canvasCtx; + this.current = new CanvasExtraState(); + this.stateStack = []; + this.pendingClip = null; + this.res = null; + this.xobjs = null; + this.ScratchCanvas = imageCanvas || ScratchCanvas; + } + + var LINE_CAP_STYLES = ['butt', 'round', 'square']; + var LINE_JOIN_STYLES = ['miter', 'round', 'bevel']; + var NORMAL_CLIP = {}; + var EO_CLIP = {}; + + // Used for tiling patterns + var PAINT_TYPE_COLORED = 1, PAINT_TYPE_UNCOLORED = 2; + + constructor.prototype = { + beginDrawing: function(mediaBox) { + var cw = this.ctx.canvas.width, ch = this.ctx.canvas.height; + this.ctx.save(); + this.ctx.scale(cw / mediaBox.width, -ch / mediaBox.height); + this.ctx.translate(0, -mediaBox.height); + }, + + compile: function(stream, xref, resources, fonts) { + var pe = new PartialEvaluator(); + return pe.eval(stream, xref, resources, fonts); + }, + + execute: function(code, xref, resources) { + resources = xref.fetchIfRef(resources) || new Dict(); + var savedXref = this.xref, savedRes = this.res, savedXobjs = this.xobjs; + this.xref = xref; + this.res = resources || new Dict(); + this.xobjs = xref.fetchIfRef(this.res.get('XObject')) || new Dict(); + + code(this); + + this.xobjs = savedXobjs; + this.res = savedRes; + this.xref = savedXref; + }, + + endDrawing: function() { + this.ctx.restore(); + }, + + // Graphics state + setLineWidth: function(width) { + this.ctx.lineWidth = width; + }, + setLineCap: function(style) { + this.ctx.lineCap = LINE_CAP_STYLES[style]; + }, + setLineJoin: function(style) { + this.ctx.lineJoin = LINE_JOIN_STYLES[style]; + }, + setMiterLimit: function(limit) { + this.ctx.miterLimit = limit; + }, + setDash: function(dashArray, dashPhase) { + this.ctx.mozDash = dashArray; + this.ctx.mozDashOffset = dashPhase; + }, + setRenderingIntent: function(intent) { + TODO('set rendering intent'); + }, + setFlatness: function(flatness) { + TODO('set flatness'); + }, + setGState: function(dictName) { + TODO('set graphics state from dict'); + }, + save: function() { + this.ctx.save(); + if (this.ctx.$saveCurrentX) { + this.ctx.$saveCurrentX(); + } + this.stateStack.push(this.current); + this.current = new CanvasExtraState(); + }, + restore: function() { + var prev = this.stateStack.pop(); + if (prev) { + if (this.ctx.$restoreCurrentX) { + this.ctx.$restoreCurrentX(); } - }; + this.current = prev; + this.ctx.restore(); + } + }, + transform: function(a, b, c, d, e, f) { + this.ctx.transform(a, b, c, d, e, f); + }, - return constructor; + // Path + moveTo: function(x, y) { + this.ctx.moveTo(x, y); + }, + lineTo: function(x, y) { + this.ctx.lineTo(x, y); + }, + curveTo: function(x1, y1, x2, y2, x3, y3) { + this.ctx.bezierCurveTo(x1, y1, x2, y2, x3, y3); + }, + curveTo2: function(x2, y2, x3, y3) { + TODO("'v' operator: need current point in gfx context"); + }, + curveTo3: function(x1, y1, x3, y3) { + this.curveTo(x1, y1, x3, y3, x3, y3); + }, + closePath: function() { + this.ctx.closePath(); + }, + rectangle: function(x, y, width, height) { + this.ctx.rect(x, y, width, height); + }, + stroke: function() { + this.ctx.stroke(); + this.consumePath(); + }, + closeStroke: function() { + this.closePath(); + this.stroke(); + }, + fill: function() { + this.ctx.fill(); + this.consumePath(); + }, + eoFill: function() { + var savedFillRule = this.setEOFillRule(); + this.fill(); + this.restoreFillRule(savedFillRule); + }, + fillStroke: function() { + this.ctx.fill(); + this.ctx.stroke(); + this.consumePath(); + }, + eoFillStroke: function() { + var savedFillRule = this.setEOFillRule(); + this.fillStroke(); + this.restoreFillRule(savedFillRule); + }, + closeFillStroke: function() { + return this.fillStroke(); + }, + closeEOFillStroke: function() { + var savedFillRule = this.setEOFillRule(); + this.fillStroke(); + this.restoreFillRule(savedFillRule); + }, + endPath: function() { + this.consumePath(); + }, + + // Clipping + clip: function() { + this.pendingClip = NORMAL_CLIP; + }, + eoClip: function() { + this.pendingClip = EO_CLIP; + }, + + // Text + beginText: function() { + this.current.textMatrix = IDENTITY_MATRIX; + if (this.ctx.$setCurrentX) { + this.ctx.$setCurrentX(0); + } + this.current.x = this.current.lineX = 0; + this.current.y = this.current.lineY = 0; + }, + endText: function() { + }, + setCharSpacing: function(spacing) { + this.ctx.charSpacing = spacing; + }, + setWordSpacing: function(spacing) { + this.ctx.wordSpacing = spacing; + }, + setHScale: function(scale) { + this.ctx.textHScale = (scale % 100) * 0.01; + }, + setLeading: function(leading) { + this.current.leading = leading; + }, + setFont: function(fontRef, size) { + var font = this.xref.fetchIfRef(this.res.get('Font')); + if (!IsDict(font)) + return; + + font = font.get(fontRef.name); + font = this.xref.fetchIfRef(font); + if (!font) + return; + + var fontName = ''; + var fontObj = font.fontObj; + if (fontObj) + fontName = fontObj.loadedName; + + if (!fontName) { + // TODO: fontDescriptor is not available, fallback to default font + fontName = 'sans-serif'; + } + + this.current.font = fontObj; + this.current.fontSize = size; + + if (this.ctx.$setFont) { + this.ctx.$setFont(fontName, size); + } else { + this.ctx.font = size + 'px "' + fontName + '"'; + Fonts.setActive(fontName, fontObj, size); + } + }, + setTextRenderingMode: function(mode) { + TODO('text rendering mode'); + }, + setTextRise: function(rise) { + TODO('text rise'); + }, + moveText: function(x, y) { + this.current.x = this.current.lineX += x; + this.current.y = this.current.lineY += y; + if (this.ctx.$setCurrentX) { + this.ctx.$setCurrentX(this.current.x); + } + }, + setLeadingMoveText: function(x, y) { + this.setLeading(-y); + this.moveText(x, y); + }, + setTextMatrix: function(a, b, c, d, e, f) { + this.current.textMatrix = [a, b, c, d, e, f]; + + if (this.ctx.$setCurrentX) { + this.ctx.$setCurrentX(0); + } + this.current.x = this.current.lineX = 0; + this.current.y = this.current.lineY = 0; + }, + nextLine: function() { + this.moveText(0, this.current.leading); + }, + showText: function(text) { + // TODO: apply charSpacing, wordSpacing, textHScale + + var ctx = this.ctx; + var current = this.current; + + ctx.save(); + ctx.transform.apply(ctx, current.textMatrix); + ctx.scale(1, -1); + + if (this.ctx.$showText) { + ctx.$showText(current.y, text); + } else { + ctx.translate(current.x, -1 * current.y); + var font = this.current.font; + if (font) { + ctx.transform.apply(ctx, font.textMatrix); + text = font.charsToUnicode(text); + } + ctx.fillText(text, 0, 0); + current.x += Fonts.measureText(text); + } + + this.ctx.restore(); + }, + showSpacedText: function(arr) { + for (var i = 0; i < arr.length; ++i) { + var e = arr[i]; + if (IsNum(e)) { + if (this.ctx.$addCurrentX) { + this.ctx.$addCurrentX(-e * 0.001 * this.current.fontSize); + } else { + this.current.x -= e * 0.001 * this.current.fontSize; + } + } else if (IsString(e)) { + this.showText(e); + } else { + malformed('TJ array element ' + e + " isn't string or num"); + } + } + }, + nextLineShowText: function(text) { + this.nextLine(); + this.showText(text); + }, + nextLineSetSpacingShowText: function(wordSpacing, charSpacing, text) { + this.setWordSpacing(wordSpacing); + this.setCharSpacing(charSpacing); + this.nextLineShowText(text); + }, + + // Type3 fonts + setCharWidth: function(xWidth, yWidth) { + TODO("type 3 fonts ('d0' operator)"); + }, + setCharWidthAndBounds: function(xWidth, yWidth, llx, lly, urx, ury) { + TODO("type 3 fonts ('d1' operator)"); + }, + + // Color + setStrokeColorSpace: function(space) { + this.current.strokeColorSpace = + ColorSpace.parse(space, this.xref, this.res); + }, + setFillColorSpace: function(space) { + this.current.fillColorSpace = + ColorSpace.parse(space, this.xref, this.res); + }, + setStrokeColor: function(/*...*/) { + var cs = this.getStrokeColorSpace(); + var color = cs.getRgb(arguments); + this.setStrokeRGBColor.apply(this, color); + }, + setStrokeColorN: function(/*...*/) { + // TODO real impl + TODO('check for special color spaces'); + this.setStrokeColor.apply(this, arguments); + }, + setFillColor: function(/*...*/) { + var cs = this.getFillColorSpace(); + var color = cs.getRgb(arguments); + this.setFillRGBColor.apply(this, color); + }, + setFillColorN: function(/*...*/) { + var cs = this.getFillColorSpace(); + + if (cs.name == 'Pattern') { + var length = arguments.length; + var base = cs.base; + if (base) { + var baseComps = base.numComps; + + if (baseComps != length - 1) + error("invalid base color for pattern colorspace"); + + var color = []; + for (var i = 0; i < baseComps; ++i) + color.push(arguments[i]); + + color = base.getRgb(color); + } + var patternName = arguments[length - 1]; + this.setFillPattern(patternName, base, color); + } else { + // TODO real impl + this.setFillColor.apply(this, arguments); + } + }, + setFillPattern: function(patternName, baseCS, color) { + if (!IsName(patternName)) + error("Bad args to getPattern"); + + var xref = this.xref; + var patternRes = xref.fetchIfRef(this.res.get("Pattern")); + if (!patternRes) + error("Unable to find pattern resource"); + + var pattern = xref.fetchIfRef(patternRes.get(patternName.name)); + var dict = IsStream(pattern) ? pattern.dict : pattern; + + var types = [null, this.setTilingPattern, this.setShadingPattern]; + + var typeNum = dict.get("PatternType"); + var patternFn = types[typeNum]; + if (!patternFn) + error("Unhandled pattern type"); + patternFn.call(this, pattern, dict, baseCS, color); + }, + setShadingPattern: function(pattern, dict) { + var matrix = dict.get("Matrix"); + + var inv = [0,0,0,0,0,0]; + var det = 1 / (matrix[0] * matrix[3] - matrix[1] * matrix[2]); + inv[0] = matrix[3] * det; + inv[1] = -matrix[1] * det; + inv[2] = -matrix[2] * det; + inv[3] = matrix[0] * det; + inv[4] = det * (matrix[2] * matrix[5] - matrix[3] * matrix[4]); + inv[5] = det * (matrix[1] * matrix[4] - matrix[0] * matrix[5]); + + this.transform.apply(this, matrix); + var shading = this.getShading(pattern.get("Shading")); + this.ctx.fillStyle = shading; + + // HACK to get the gradient to show at the right location. If + // removed, the gradient will show at the pre-transform coordinates. + this.ctx.fillRect(0,0,0,0); + this.transform.apply(this, inv); + }, + setTilingPattern: function(pattern, dict, baseCS, color) { + function multiply(m, tm) { + var a = m[0] * tm[0] + m[1] * tm[2]; + var b = m[0] * tm[1] + m[1] * tm[3]; + var c = m[2] * tm[0] + m[3] * tm[2]; + var d = m[2] * tm[1] + m[3] * tm[3]; + var e = m[4] * tm[0] + m[5] * tm[2] + tm[4]; + var f = m[4] * tm[1] + m[5] * tm[3] + tm[5]; + return [a, b, c, d, e, f]; + }; + + this.save(); + var ctx = this.ctx; + + + TODO('TilingType'); + + var matrix = dict.get('Matrix') || IDENTITY_MATRIX; + + var bbox = dict.get('BBox'); + var x0 = bbox[0], y0 = bbox[1], x1 = bbox[2], y1 = bbox[3]; + + var xstep = dict.get('XStep'); + var ystep = dict.get('YStep'); + + // top left corner should correspond to the top left of the bbox + var topLeft = this.applyTransform(x0, y0, matrix); + // we want the canvas to be as large as the step size + var botRight = this.applyTransform(x0 + xstep, y0 + ystep, matrix); + + var width = botRight[0] - topLeft[0]; + var height = botRight[1] - topLeft[1]; + + // TODO: hack to avoid OOM, remove then pattern code is fixed + if (Math.abs(width) > 8192 || Math.abs(height) > 8192) + return false; + + var tmpCanvas = new this.ScratchCanvas(width, height); + + // set the new canvas element context as the graphics context + var tmpCtx = tmpCanvas.getContext('2d'); + var savedCtx = ctx; + this.ctx = tmpCtx; + + var paintType = dict.get('PaintType'); + switch (paintType) { + case PAINT_TYPE_COLORED: + // should go to default for color space + tmpCtx.fillStyle = this.makeCssRgb(1, 1, 1); + tmpCtx.strokeStyle = this.makeCssRgb(0, 0, 0); + break; + case PAINT_TYPE_UNCOLORED: + tmpCtx.fillStyle = this.makeCssRgb.apply(this, baseCS.getRgb(color)); + tmpCtx.strokeStyle = this.makeCssRgb.apply(this, baseCS.getRgb(color)); + break; + default: + error('Unsupported paint type'); + } + + // normalize transform matrix so each step + // takes up the entire tmpCanvas (need to remove white borders) + if (matrix[1] === 0 && matrix[2] === 0) { + matrix[0] = tmpCanvas.width / xstep; + matrix[3] = tmpCanvas.height / ystep; + topLeft = this.applyTransform(x0, y0, matrix); + } + + // move the top left corner of bounding box to [0,0] + matrix = multiply(matrix, [1, 0, 0, 1, -topLeft[0], -topLeft[1]]); + + this.transform.apply(this, matrix); + + if (bbox && IsArray(bbox) && 4 == bbox.length) { + this.rectangle.apply(this, bbox); + this.clip(); + this.endPath(); + } + + var xref = this.xref; + var res = xref.fetchIfRef(dict.get('Resources')); + if (!pattern.code) + pattern.code = this.compile(pattern, xref, res, []); + this.execute(pattern.code, xref, res); + + this.ctx = savedCtx; + this.restore(); + + TODO('Inverse pattern is painted'); + pattern = this.ctx.createPattern(tmpCanvas, 'repeat'); + this.ctx.fillStyle = pattern; + }, + setStrokeGray: function(gray) { + this.setStrokeRGBColor(gray, gray, gray); + }, + setFillGray: function(gray) { + this.setFillRGBColor(gray, gray, gray); + }, + setStrokeRGBColor: function(r, g, b) { + this.ctx.strokeStyle = this.makeCssRgb(r, g, b); + }, + setFillRGBColor: function(r, g, b) { + this.ctx.fillStyle = this.makeCssRgb(r, g, b); + }, + setStrokeCMYKColor: function(c, m, y, k) { + this.ctx.strokeStyle = this.makeCssCmyk(c, m, y, k); + }, + setFillCMYKColor: function(c, m, y, k) { + this.ctx.fillStyle = this.makeCssCmyk(c, m, y, k); + }, + + // Shading + shadingFill: function(shadingName) { + var xref = this.xref; + var res = this.res; + var ctx = this.ctx; + + var shadingRes = xref.fetchIfRef(res.get('Shading')); + if (!shadingRes) + error('No shading resource found'); + + var shading = xref.fetchIfRef(shadingRes.get(shadingName.name)); + if (!shading) + error('No shading object found'); + + var shadingFill = this.getShading(shading); + + this.save(); + ctx.fillStyle = shadingFill; + + var inv = ctx.mozCurrentTransformInverse; + if (inv) { + var canvas = ctx.canvas; + var width = canvas.width; + var height = canvas.height; + + var bl = this.applyTransform(0, 0, inv); + var br = this.applyTransform(0, width, inv); + var ul = this.applyTransform(height, 0, inv); + var ur = this.applyTransform(height, width, inv); + + var x0 = Math.min(bl[0], br[0], ul[0], ur[0]); + var y0 = Math.min(bl[1], br[1], ul[1], ur[1]); + var x1 = Math.max(bl[0], br[0], ul[0], ur[0]); + var y1 = Math.max(bl[1], br[1], ul[1], ur[1]); + + this.ctx.fillRect(x0, y0, x1 - x0, y1 - y0); + } else { + // HACK to draw the gradient onto an infinite rectangle. + // PDF gradients are drawn across the entire image while + // Canvas only allows gradients to be drawn in a rectangle + // The following bug should allow us to remove this. + // https://bugzilla.mozilla.org/show_bug.cgi?id=664884 + + this.ctx.fillRect(-1e10, -1e10, 2e10, 2e10); + } + + this.restore(); + }, + getShading: function(shading) { + shading = this.xref.fetchIfRef(shading); + var dict = IsStream(shading) ? shading.dict : shading; + + var bbox = dict.get('BBox'); + if (bbox && IsArray(bbox) && 4 == bbox.length) { + this.rectangle.apply(this, bbox); + this.clip(); + this.endPath(); + } + + var background = dict.get('Background'); + if (background) + TODO('handle background colors'); + + var cs = dict.get('ColorSpace', 'CS'); + cs = ColorSpace.parse(cs, this.xref, this.res); + + var types = [null, + null, + this.getAxialShading, + this.getRadialShading]; + + var typeNum = dict.get('ShadingType'); + var shadingFn = types[typeNum]; + + // Most likely we will not implement other types of shading + // unless the browser supports them + if (!shadingFn) { + warn("Unknown or NYI type of shading '"+ typeNum +"'"); + return 'hotpink'; + } + + return shadingFn.call(this, shading, cs); + }, + getAxialShading: function(sh, cs) { + var coordsArr = sh.get('Coords'); + var x0 = coordsArr[0], y0 = coordsArr[1], + x1 = coordsArr[2], y1 = coordsArr[3]; + + var t0 = 0.0, t1 = 1.0; + if (sh.has('Domain')) { + var domainArr = sh.get('Domain'); + t0 = domainArr[0], t1 = domainArr[1]; + } + + var extendStart = false, extendEnd = false; + if (sh.has('Extend')) { + var extendArr = sh.get('Extend'); + extendStart = extendArr[0], extendEnd = extendArr[1]; + TODO('Support extend'); + } + var fnObj = sh.get('Function'); + fnObj = this.xref.fetchIfRef(fnObj); + if (IsArray(fnObj)) + error('No support for array of functions'); + else if (!IsPDFFunction(fnObj)) + error('Invalid function'); + var fn = new PDFFunction(this.xref, fnObj); + + var gradient = this.ctx.createLinearGradient(x0, y0, x1, y1); + + // 10 samples seems good enough for now, but probably won't work + // if there are sharp color changes. Ideally, we would implement + // the spec faithfully and add lossless optimizations. + var step = (t1 - t0) / 10; + var diff = t1 - t0; + + for (var i = t0; i <= t1; i += step) { + var color = fn.func([i]); + var rgbColor = cs.getRgb(color); + gradient.addColorStop((i - t0) / diff, + this.makeCssRgb.apply(this, rgbColor)); + } + + return gradient; + }, + getRadialShading: function(sh, cs) { + var coordsArr = sh.get('Coords'); + var x0 = coordsArr[0], y0 = coordsArr[1], r0 = coordsArr[2]; + var x1 = coordsArr[3], y1 = coordsArr[4], r1 = coordsArr[5]; + + var t0 = 0.0, t1 = 1.0; + if (sh.has('Domain')) { + var domainArr = sh.get('Domain'); + t0 = domainArr[0], t1 = domainArr[1]; + } + + var extendStart = false, extendEnd = false; + if (sh.has('Extend')) { + var extendArr = sh.get('Extend'); + extendStart = extendArr[0], extendEnd = extendArr[1]; + TODO('Support extend'); + } + var fnObj = sh.get('Function'); + fnObj = this.xref.fetchIfRef(fnObj); + if (IsArray(fnObj)) + error('No support for array of functions'); + else if (!IsPDFFunction(fnObj)) + error('Invalid function'); + var fn = new PDFFunction(this.xref, fnObj); + + var gradient = + this.ctx.createRadialGradient(x0, y0, r0, x1, y1, r1); + + // 10 samples seems good enough for now, but probably won't work + // if there are sharp color changes. Ideally, we would implement + // the spec faithfully and add lossless optimizations. + var step = (t1 - t0) / 10; + var diff = t1 - t0; + + for (var i = t0; i <= t1; i += step) { + var color = fn.func([i]); + var rgbColor = cs.getRgb(color); + gradient.addColorStop((i - t0) / diff, + this.makeCssRgb.apply(this, rgbColor)); + } + + return gradient; + }, + + // Images + beginInlineImage: function() { + TODO('inline images'); + error('(Stream will not be parsed properly, bailing now)'); + // Like an inline stream: + // - key/value pairs up to Cmd(ID) + // - then image data up to Cmd(EI) + }, + + // XObjects + paintXObject: function(obj) { + var xobj = this.xobjs.get(obj.name); + if (!xobj) + return; + xobj = this.xref.fetchIfRef(xobj); + assertWellFormed(IsStream(xobj), 'XObject should be a stream'); + + var oc = xobj.dict.get('OC'); + if (oc) { + TODO('oc for xobject'); + } + + var opi = xobj.dict.get('OPI'); + if (opi) { + TODO('opi for xobject'); + } + + var type = xobj.dict.get('Subtype'); + assertWellFormed(IsName(type), 'XObject should have a Name subtype'); + if ('Image' == type.name) { + this.paintImageXObject(obj, xobj, false); + } else if ('Form' == type.name) { + this.paintFormXObject(obj, xobj); + } else if ('PS' == type.name) { + warn('(deprecated) PostScript XObjects are not supported'); + } else { + malformed('Unknown XObject subtype ' + type.name); + } + }, + + paintFormXObject: function(ref, stream) { + this.save(); + + var matrix = stream.dict.get('Matrix'); + if (matrix && IsArray(matrix) && 6 == matrix.length) + this.transform.apply(this, matrix); + + var bbox = stream.dict.get('BBox'); + if (bbox && IsArray(bbox) && 4 == bbox.length) { + this.rectangle.apply(this, bbox); + this.clip(); + this.endPath(); + } + + this.execute(ref.code, this.xref, stream.dict.get('Resources')); + + this.restore(); + }, + + paintImageXObject: function(ref, image, inline) { + this.save(); + + var ctx = this.ctx; + var dict = image.dict; + var w = dict.get('Width', 'W'); + var h = dict.get('Height', 'H'); + // scale the image to the unit square + ctx.scale(1 / w, -1 / h); + + // If the platform can render the image format directly, the + // stream has a getImage property which directly returns a + // suitable DOM Image object. + if (image.getImage) { + var domImage = image.getImage(); + ctx.drawImage(domImage, 0, 0, domImage.width, domImage.height, + 0, -h, w, h); + this.restore(); + return; + } + + var imageObj = new PDFImage(this.xref, this.res, image, inline); + + var tmpCanvas = new this.ScratchCanvas(w, h); + var tmpCtx = tmpCanvas.getContext('2d'); + var imgData = tmpCtx.getImageData(0, 0, w, h); + var pixels = imgData.data; + + imageObj.fillRgbaBuffer(pixels); + + tmpCtx.putImageData(imgData, 0, 0); + ctx.drawImage(tmpCanvas, 0, -h); + this.restore(); + }, + + // Marked content + + markPoint: function(tag) { + TODO('Marked content'); + }, + markPointProps: function(tag, properties) { + TODO('Marked content'); + }, + beginMarkedContent: function(tag) { + TODO('Marked content'); + }, + beginMarkedContentProps: function(tag, properties) { + TODO('Marked content'); + }, + endMarkedContent: function() { + TODO('Marked content'); + }, + + // Compatibility + + beginCompat: function() { + TODO('ignore undefined operators (should we do that anyway?)'); + }, + endCompat: function() { + TODO('stop ignoring undefined operators'); + }, + + // Helper functions + + consumePath: function() { + if (this.pendingClip) { + var savedFillRule = null; + if (this.pendingClip == EO_CLIP) + savedFillRule = this.setEOFillRule(); + + this.ctx.clip(); + + this.pendingClip = null; + if (savedFillRule !== null) + this.restoreFillRule(savedFillRule); + } + this.ctx.beginPath(); + }, + makeCssRgb: function(r, g, b) { + var ri = (255 * r) | 0, gi = (255 * g) | 0, bi = (255 * b) | 0; + return 'rgb(' + ri + ',' + gi + ',' + bi + ')'; + }, + makeCssCmyk: function(c, m, y, k) { + // while waiting on CSS's cmyk()... + // http://www.ilkeratalay.com/colorspacesfaq.php#rgb + var ri = (255 * (1 - Math.min(1, c * (1 - k) + k))) | 0; + var gi = (255 * (1 - Math.min(1, m * (1 - k) + k))) | 0; + var bi = (255 * (1 - Math.min(1, y * (1 - k) + k))) | 0; + return 'rgb(' + ri + ',' + gi + ',' + bi + ')'; + }, + getFillColorSpace: function() { + var cs = this.current.fillColorSpace; + if (cs) + return cs; + + var states = this.stateStack; + var i = states.length - 1; + while (i >= 0 && !(cs = states[i].fillColorSpace)) + --i; + + if (cs) + return cs; + else + return new DeviceRgbCS(); + }, + getStrokeColorSpace: function() { + var cs = this.current.strokeColorSpace; + if (cs) + return cs; + + var states = this.stateStack; + var i = states.length - 1; + while (i >= 0 && !(cs = states[i].strokeColorSpace)) + --i; + + if (cs) + return cs; + else + return new DeviceRgbCS(); + }, + // We generally keep the canvas context set for + // nonzero-winding, and just set evenodd for the operations + // that need them. + setEOFillRule: function() { + var savedFillRule = this.ctx.mozFillRule; + this.ctx.mozFillRule = 'evenodd'; + return savedFillRule; + }, + restoreFillRule: function(rule) { + this.ctx.mozFillRule = rule; + }, + applyTransform: function(x0, y0, m) { + var xt = x0 * m[0] + y0 * m[2] + m[4]; + var yt = x0 * m[1] + y0 * m[3] + m[5]; + return [xt, yt]; + } + }; + + return constructor; })(); var ColorSpace = (function() { - function constructor() { - error("should not call ColorSpace constructor"); - }; + // Constructor should define this.numComps, this.defaultColor, this.name + function constructor() { + error('should not call ColorSpace constructor'); + }; - constructor.parse = function colorspace_parse(cs, xref, res) { - if (IsName(cs)) { - var colorSpaces = res.get("ColorSpace"); - if (colorSpaces) { - var refcs = colorSpaces.get(cs.name); - if (refcs) - cs = refcs; - } - } - - cs = xref.fetchIfRef(cs); + constructor.prototype = { + // Input: array of size numComps representing color component values + // Output: array of rgb values, each value ranging from [0.1] + getRgb: function cs_getRgb(color) { + error('Should not call ColorSpace.getRgb'); + }, + // Input: Uint8Array of component values, each value scaled to [0,255] + // Output: Uint8Array of rgb values, each value scaled to [0,255] + getRgbBuffer: function cs_getRgbBuffer(input) { + error('Should not call ColorSpace.getRgbBuffer'); + } + }; - if (IsName(cs)) { - var mode = cs.name; - this.mode = mode; + constructor.parse = function colorspace_parse(cs, xref, res) { + if (IsName(cs)) { + var colorSpaces = res.get('ColorSpace'); + if (colorSpaces) { + var refcs = colorSpaces.get(cs.name); + if (refcs) + cs = refcs; + } + } - switch(mode) { - case "DeviceGray": - case "G": - return new DeviceGrayCS(); - break; - case "DeviceRGB": - case "RGB": - return new DeviceRgbCS(); - break; - case "DeviceCMYK": - case "CMYK": - return new DeviceCmykCS(); - break; - case "Pattern": - return new PatternCS(null); - break; - default: - error("unrecognized colorspace " + mode); - } - } else if (IsArray(cs)) { - var mode = cs[0].name; - this.mode = mode; + cs = xref.fetchIfRef(cs); - switch (mode) { - case "DeviceGray": - case "G": - return new DeviceGrayCS(); - break; - case "DeviceRGB": - case "RGB": - return new DeviceRgbCS(); - break; - case "DeviceCMYK": - case "CMYK": - return new DeviceCmykCS(); - break; - case "CalGray": - return new DeviceGrayCS(); - break; - case "CalRGB": - return new DeviceRgbCS(); - break; - case "ICCBased": - var stream = xref.fetchIfRef(cs[1]); - var dict = stream.dict; - var numComps = dict.get("N"); - if (numComps == 1) - return new DeviceGrayCS(); - else if (numComps == 3) - return new DeviceRgbCS(); - else if (numComps == 4) - return new DeviceCmykCS(); - break; - case "Pattern": - return new PatternCS(); - break; - case "Indexed": - var base = ColorSpace.parse(cs[1], xref, res); - var hiVal = cs[2]; - var lookup = xref.fetchIfRef(cs[3]); - return new IndexedCS(base, hiVal, lookup); - case "Lab": - case "Seperation": - case "DeviceN": - default: - error("unrecognized color space object '"+ mode +"'"); - } - } else { - error("unrecognized color space object"); - } - }; + if (IsName(cs)) { + var mode = cs.name; + this.mode = mode; - return constructor; + switch (mode) { + case 'DeviceGray': + case 'G': + return new DeviceGrayCS(); + break; + case 'DeviceRGB': + case 'RGB': + return new DeviceRgbCS(); + break; + case 'DeviceCMYK': + case 'CMYK': + return new DeviceCmykCS(); + break; + case 'Pattern': + return new PatternCS(null); + break; + default: + error('unrecognized colorspace ' + mode); + } + } else if (IsArray(cs)) { + var mode = cs[0].name; + this.mode = mode; + + switch (mode) { + case 'DeviceGray': + case 'G': + return new DeviceGrayCS(); + break; + case 'DeviceRGB': + case 'RGB': + return new DeviceRgbCS(); + break; + case 'DeviceCMYK': + case 'CMYK': + return new DeviceCmykCS(); + break; + case 'CalGray': + return new DeviceGrayCS(); + break; + case 'CalRGB': + return new DeviceRgbCS(); + break; + case 'ICCBased': + var stream = xref.fetchIfRef(cs[1]); + var dict = stream.dict; + var numComps = dict.get('N'); + if (numComps == 1) + return new DeviceGrayCS(); + else if (numComps == 3) + return new DeviceRgbCS(); + else if (numComps == 4) + return new DeviceCmykCS(); + break; + case 'Pattern': + var baseCS = cs[1]; + if (baseCS) + baseCS = ColorSpace.parse(baseCS, xref, res); + return new PatternCS(baseCS); + break; + case 'Indexed': + var base = ColorSpace.parse(cs[1], xref, res); + var hiVal = cs[2] + 1; + var lookup = xref.fetchIfRef(cs[3]); + return new IndexedCS(base, hiVal, lookup); + break; + case 'Separation': + var name = cs[1]; + var alt = ColorSpace.parse(cs[2], xref, res); + var tintFn = new PDFFunction(xref, xref.fetchIfRef(cs[3])); + return new SeparationCS(alt, tintFn); + break; + case 'Lab': + case 'DeviceN': + default: + error("unrecognized color space object '" + mode + "'"); + } + } else { + error('unrecognized color space object'); + } + }; + + return constructor; +})(); + +var SeparationCS = (function() { + function constructor(base, tintFn) { + this.name = "Separation"; + this.numComps = 1; + this.defaultColor = [1]; + + this.base = base; + this.tintFn = tintFn; + } + + constructor.prototype = { + getRgb: function sepcs_getRgb(color) { + var tinted = this.tintFn.func(color); + return this.base.getRgb(tinted); + }, + getRgbBuffer: function sepcs_getRgbBuffer(input) { + var tintFn = this.tintFn; + var base = this.base; + + var length = 3 * input.length; + var pos = 0; + + var numComps = base.numComps; + var baseBuf = new Uint8Array(numComps * input.length); + for (var i = 0, ii = input.length; i < ii; ++i) { + var scaled = input[i] / 255; + var tinted = tintFn.func([scaled]); + for (var j = 0; j < numComps; ++j) + baseBuf[pos++] = 255 * tinted[j]; + } + return base.getRgbBuffer(baseBuf); + + } + }; + + return constructor; })(); var PatternCS = (function() { - function constructor() { - this.name = "Pattern"; - } - constructor.prototype = {}; - - return constructor; + function constructor(baseCS) { + this.name = 'Pattern'; + this.base = baseCS; + } + constructor.prototype = {}; + + return constructor; })(); var IndexedCS = (function() { - function constructor(base, highVal, lookup) { - this.name = "Indexed"; - this.numComps = 1; - this.defaultColor = [0]; + function constructor(base, highVal, lookup) { + this.name = 'Indexed'; + this.numComps = 1; + this.defaultColor = [0]; - this.base = base; - var baseNumComps = base.numComps; - this.highVal = highVal; + this.base = base; + var baseNumComps = base.numComps; + this.highVal = highVal; - var length = baseNumComps * highVal; - var lookupArray = new Uint8Array(length); - if (IsStream(lookup)) { - var bytes = lookup.getBytes(length); - lookupArray.set(bytes); - } else if (IsString(lookup)) { - for (var i = 0; i < length; ++i) - lookupArray[i] = lookup.charCodeAt(i); - } else { - error("Unrecognized lookup table"); - } - this.lookup = lookupArray; + var length = baseNumComps * highVal; + var lookupArray = new Uint8Array(length); + if (IsStream(lookup)) { + var bytes = lookup.getBytes(length); + lookupArray.set(bytes); + } else if (IsString(lookup)) { + for (var i = 0; i < length; ++i) + lookupArray[i] = lookup.charCodeAt(i); + } else { + error('Unrecognized lookup table'); } + this.lookup = lookupArray; + } - constructor.prototype = { - getRgb: function graycs_getRgb(color) { - var lookup = this.lookup; - var base = this.base; - var numComps = base.numComps; - - var c = []; - for (var i = 0; i < numComps; ++i) - c.push(lookup[i]) - return this.base.getRgb(c); - }, - getRgbBuffer: function graycs_getRgbBuffer(input) { - var base = this.base; - var numComps = base.numComps; - var lookup = this.lookup; - var length = input.length; + constructor.prototype = { + getRgb: function indexcs_getRgb(color) { + var numComps = base.numComps; - var baseBuf = new Uint8Array(length * numComps); - var baseBufPos = 0; - for (var i = 0; i < length; ++i) { - var lookupPos = input[i]; - for (var j = 0; j < numComps ; ++j) { - baseBuf[baseBufPos++] = lookup[lookupPos + j]; - } - } - - return base.getRgbBuffer(baseBuf); + var start = color[0] * numComps; + var c = []; + + for (var i = start, ii = start + numComps; i < ii; ++i) + c.push(this.lookup[i]); + + return this.base.getRgb(c); + }, + getRgbBuffer: function indexcs_getRgbBuffer(input) { + var base = this.base; + var numComps = base.numComps; + var lookup = this.lookup; + var length = input.length; + + var baseBuf = new Uint8Array(length * numComps); + var baseBufPos = 0; + for (var i = 0; i < length; ++i) { + var lookupPos = input[i] * numComps; + for (var j = 0; j < numComps; ++j) { + baseBuf[baseBufPos++] = lookup[lookupPos + j]; } - }; - return constructor; + } + + return base.getRgbBuffer(baseBuf); + } + }; + return constructor; })(); var DeviceGrayCS = (function() { - function constructor() { - this.name = "DeviceGray"; - this.numComps = 1; - this.defaultColor = [0]; - }; + function constructor() { + this.name = 'DeviceGray'; + this.numComps = 1; + this.defaultColor = [0]; + }; - constructor.prototype = { - getRgb: function graycs_getRgb(color) { - var c = color[0]; - return [c, c, c]; - }, - getRgbBuffer: function graycs_getRgbBuffer(input) { - var length = input.length; - var rgbBuf = new Uint8Array(length); - for (var i = 0, j = 0; i < length; ++i) { - var c = input[i]; - rgbBuf[j++] = c; - rgbBuf[j++] = c; - rgbBuf[j++] = c; - } - return rgbBuf; - } - }; - return constructor; + constructor.prototype = { + getRgb: function graycs_getRgb(color) { + var c = color[0]; + return [c, c, c]; + }, + getRgbBuffer: function graycs_getRgbBuffer(input) { + var length = input.length * 3; + var rgbBuf = new Uint8Array(length); + for (var i = 0, j = 0; i < length; ++i) { + var c = input[i]; + rgbBuf[j++] = c; + rgbBuf[j++] = c; + rgbBuf[j++] = c; + } + return rgbBuf; + } + }; + return constructor; })(); var DeviceRgbCS = (function() { - function constructor() { - this.name = "DeviceRGB"; - this.numComps = 3; - this.defaultColor = [0, 0, 0]; + function constructor() { + this.name = 'DeviceRGB'; + this.numComps = 3; + this.defaultColor = [0, 0, 0]; + } + constructor.prototype = { + getRgb: function rgbcs_getRgb(color) { + return color; + }, + getRgbBuffer: function rgbcs_getRgbBuffer(input) { + return input; } - constructor.prototype = { - getRgb: function graycs_getRgb(color) { - return color; - }, - getRgbBuffer: function graycs_getRgbBuffer(input) { - return input; - } - }; - return constructor; + }; + return constructor; })(); var DeviceCmykCS = (function() { - function constructor() { - this.name = "DeviceCMYK"; - this.numComps = 4; - this.defaultColor = [0, 0, 0, 1]; + function constructor() { + this.name = 'DeviceCMYK'; + this.numComps = 4; + this.defaultColor = [0, 0, 0, 1]; + } + constructor.prototype = { + getRgb: function cmykcs_getRgb(color) { + var c = color[0], m = color[1], y = color[2], k = color[3]; + var c1 = 1 - c, m1 = 1 - m, y1 = 1 - y, k1 = 1 - k; + + var x, r, g, b; + // this is a matrix multiplication, unrolled for performance + // code is taken from the poppler implementation + x = c1 * m1 * y1 * k1; // 0 0 0 0 + r = g = b = x; + x = c1 * m1 * y1 * k; // 0 0 0 1 + r += 0.1373 * x; + g += 0.1216 * x; + b += 0.1255 * x; + x = c1 * m1 * y * k1; // 0 0 1 0 + r += x; + g += 0.9490 * x; + x = c1 * m1 * y * k; // 0 0 1 1 + r += 0.1098 * x; + g += 0.1020 * x; + x = c1 * m * y1 * k1; // 0 1 0 0 + r += 0.9255 * x; + b += 0.5490 * x; + x = c1 * m * y1 * k; // 0 1 0 1 + r += 0.1412 * x; + x = c1 * m * y * k1; // 0 1 1 0 + r += 0.9294 * x; + g += 0.1098 * x; + b += 0.1412 * x; + x = c1 * m * y * k; // 0 1 1 1 + r += 0.1333 * x; + x = c * m1 * y1 * k1; // 1 0 0 0 + g += 0.6784 * x; + b += 0.9373 * x; + x = c * m1 * y1 * k; // 1 0 0 1 + g += 0.0588 * x; + b += 0.1412 * x; + x = c * m1 * y * k1; // 1 0 1 0 + g += 0.6510 * x; + b += 0.3137 * x; + x = c * m1 * y * k; // 1 0 1 1 + g += 0.0745 * x; + x = c * m * y1 * k1; // 1 1 0 0 + r += 0.1804 * x; + g += 0.1922 * x; + b += 0.5725 * x; + x = c * m * y1 * k; // 1 1 0 1 + b += 0.0078 * x; + x = c * m * y * k1; // 1 1 1 0 + r += 0.2118 * x; + g += 0.2119 * x; + b += 0.2235 * x; + + return [r, g, b]; + }, + getRgbBuffer: function cmykcs_getRgbBuffer(colorBuf) { + var length = colorBuf.length / 4; + var rgbBuf = new Uint8Array(length * 3); + var rgbBufPos = 0; + var colorBufPos = 0; + + for (var i = 0; i < length; i++) { + var cmyk = []; + for (var j = 0; j < 4; ++j) + cmyk.push(colorBuf[colorBufPos++]/255); + + var rgb = this.getRgb(cmyk); + for (var j = 0; j < 3; ++j) + rgbBuf[rgbBufPos++] = Math.round(rgb[j] * 255); + } + + return rgbBuf; } - constructor.prototype = { - getRgb: function graycs_getRgb(color) { - var c = color[0], y = color[1], m = color[2], k = color[3]; - var ri = (1 - Math.min(1, c * (1 - k) + k)) | 0; - var gi = (1 - Math.min(1, m * (1 - k) + k)) | 0; - var bi = (1 - Math.min(1, y * (1 - k) + k)) | 0; - return [ri, gi, bi]; - }, - getRgbBuffer: function graycs_getRgbBuffer(colorBuf) { - error("conversion from rgb to cmyk not implemented for images"); - return colorBuf; - } - }; - return constructor; + }; + return constructor; })(); var PDFImage = (function() { - function constructor(xref, res, image, inline) { - this.image = image; - if (image.getParams) { - // JPX/JPEG2000 streams directly contain bits per component - // and color space mode information. - TODO("get params from actual stream"); - // var bits = ... - // var colorspace = ... + function constructor(xref, res, image, inline) { + this.image = image; + if (image.getParams) { + // JPX/JPEG2000 streams directly contain bits per component + // and color space mode information. + TODO('get params from actual stream'); + // var bits = ... + // var colorspace = ... + } + // TODO cache rendered images? + + var dict = image.dict; + this.width = dict.get('Width', 'W'); + this.height = dict.get('Height', 'H'); + + if (this.width < 1 || this.height < 1) + error('Invalid image width or height'); + + this.interpolate = dict.get('Interpolate', 'I') || false; + this.imageMask = dict.get('ImageMask', 'IM') || false; + + var bitsPerComponent = image.bitsPerComponent; + if (!bitsPerComponent) { + bitsPerComponent = dict.get('BitsPerComponent', 'BPC'); + if (!bitsPerComponent) { + if (this.imageMask) + bitsPerComponent = 1; + else + error('Bits per component missing in image'); + } + } + this.bpc = bitsPerComponent; + + var colorSpace = dict.get('ColorSpace', 'CS'); + this.colorSpace = ColorSpace.parse(colorSpace, xref, res); + + this.numComps = this.colorSpace.numComps; + this.decode = dict.get('Decode', 'D'); + + var mask = xref.fetchIfRef(image.dict.get('Mask')); + var smask = xref.fetchIfRef(image.dict.get('SMask')); + + if (mask) { + TODO('masked images'); + } else if (smask) { + this.smask = new PDFImage(xref, res, smask); + } + }; + + constructor.prototype = { + getComponents: function getComponents(buffer) { + var bpc = this.bpc; + if (bpc == 8) + return buffer; + + var width = this.width; + var height = this.height; + var numComps = this.numComps; + + var length = width * height; + var bufferPos = 0; + var output = new Uint8Array(length); + + if (bpc == 1) { + var rowComps = width * numComps; + var mask = 0; + var buf = 0; + + for (var i = 0, ii = length; i < ii; ++i) { + if (i % rowComps == 0) { + mask = 0; + buf = 0; + } else { + mask >>= 1; + } + + if (mask <= 0) { + buf = buffer[bufferPos++]; + mask = 128; + } + + var t = buf & mask; + if (t == 0) + output[i] = 0; + else + output[i] = 255; } - // TODO cache rendered images? + } else { + var rowComps = width * numComps; + var bits = 0; + var buf = 0; - var dict = image.dict; - this.width = dict.get2("Width", "W"); - this.height = dict.get2("Height", "H"); + for (var i = 0, ii = length; i < ii; ++i) { + while (bits < bpc) { + buf = (buf << 8) | buffer[bufferPos++]; + bits += 8; + } + var remainingBits = bits - bpc; + var ret = buf >> remainingBits; - if (this.width < 1 || this.height < 1) - error("Invalid image width or height"); - - this.interpolate = dict.get2("Interpolate", "I") || false; - this.imageMask = dict.get2("ImageMask", "IM") || false; - - var bitsPerComponent = image.bitsPerComponent; - if (!bitsPerComponent) { - bitsPerComponent = dict.get2("BitsPerComponent", "BPC"); - if (!bitsPerComponent) { - if (this.imageMask) - bitsPerComponent = 1; - else - error("Bits per component missing in image"); - } + if (i % rowComps == 0) { + buf = 0; + bits = 0; + } else { + buf = buf & ((1 << remainingBits) - 1); + bits = remainingBits; + } + output[i] = Math.round(255 * ret / ((1 << bpc) - 1)); } - this.bpc = bitsPerComponent; + } + return output; + }, + getOpacity: function getOpacity() { + var smask = this.smask; + var width = this.width; + var height = this.height; + var buf = new Uint8Array(width * height); - var colorSpace = dict.get2("ColorSpace", "CS"); - this.colorSpace = ColorSpace.parse(colorSpace, xref, res); + if (smask) { + var sw = smask.width; + var sh = smask.height; + if (sw != this.width || sh != this.height) + error('smask dimensions do not match image dimensions'); - this.numComps = this.colorSpace.numComps; - this.decode = dict.get2("Decode", "D"); + smask.fillGrayBuffer(buf); + return buf; + } else { + for (var i = 0, ii = width * height; i < ii; ++i) + buf[i] = 255; + } + return buf; + }, + fillRgbaBuffer: function fillRgbaBuffer(buffer) { + var numComps = this.numComps; + var width = this.width; + var height = this.height; + var bpc = this.bpc; - var mask = xref.fetchIfRef(image.dict.get("Mask")); - var smask = xref.fetchIfRef(image.dict.get("SMask")); + // rows start at byte boundary; + var rowBytes = (width * numComps * bpc + 7) >> 3; + var imgArray = this.image.getBytes(height * rowBytes); - if (mask) { - TODO("masked images"); - } else if (smask) { - this.smask = new PDFImage(xref, res, smask); - } - }; - - constructor.prototype = { - getComponents: function getComponents(buffer) { - var bpc = this.bpc; - if (bpc == 8) - return buffer; + var comps = this.colorSpace.getRgbBuffer(this.getComponents(imgArray)); + var compsPos = 0; + var opacity = this.getOpacity(); + var opacityPos = 0; + var length = width * height * 4; - var width = this.width; - var height = this.height; - var numComps = this.numComps; - - var length = width * height; - var bufferPos = 0; - var output = new Uint8Array(length); + for (var i = 0; i < length; i += 4) { + buffer[i] = comps[compsPos++]; + buffer[i + 1] = comps[compsPos++]; + buffer[i + 2] = comps[compsPos++]; + buffer[i + 3] = opacity[opacityPos++]; + } + }, + fillGrayBuffer: function fillGrayBuffer(buffer) { + var numComps = this.numComps; + if (numComps != 1) + error('Reading gray scale from a color image'); - if (bpc == 1) { - var rowComps = width * numComps; - var mask = 0; - var buf = 0; - - for (var i = 0, ii = length; i < ii; ++i) { - if (i % rowComps == 0) { - mask = 0; - buf = 0; - } else { - mask >>= 1; - } + var width = this.width; + var height = this.height; + var bpc = this.bpc; - if (mask <= 0) { - buf = buffer[bufferPos++]; - mask = 128; - } + // rows start at byte boundary; + var rowBytes = (width * numComps * bpc + 7) >> 3; + var imgArray = this.image.getBytes(height * rowBytes); - var t = buf & mask; - if (t == 0) - output[i] = 0; - else - output[i] = 255; - } - } else { - var rowComps = width * numComps; - var bits = 0; - var buf = 0; + var comps = this.getComponents(imgArray); + var length = width * height; - for (var i = 0, ii = length; i < ii; ++i) { - while (bits < bpc) { - buf = (buf << 8) | buffer[bufferPos++]; - bits += 8; - } - var remainingBits = bits - bpc; - var ret = buf >> remainingBits; - - if (i % rowComps == 0) { - buf = 0; - bits = 0; - } else { - buf = buf & ((1 << remainingBits) - 1); - bits = remainingBits; - } - output[i] = Math.round(255 * ret / ((1 << bpc) - 1)); - } - } - return this.colorSpace.getRbaBuffer(output); - }, - getOpacity: function getOpacity() { - var smask = this.smask; - var width = this.width; - var height = this.height; - var buf = new Uint8Array(width * height); - - if (smask) { - var sw = smask.width; - var sh = smask.height; - if (sw != this.width || sh != this.height) - error("smask dimensions do not match image dimensions"); - - smask.fillGrayBuffer(buf); - return buf; - } else { - for (var i = 0, ii = width * height; i < ii; ++i) - buf[i] = 255; - } - return buf; - }, - fillRgbaBuffer: function fillRgbaBuffer(buffer) { - var numComps = this.numComps; - var width = this.width; - var height = this.height; - var bpc = this.bpc; - - // rows start at byte boundary; - var rowBytes = (width * numComps * bpc + 7) >> 3; - var imgArray = this.image.getBytes(height * rowBytes); - - var comps = this.getComponents(imgArray); - var compsPos = 0; - var opacity = this.getOpacity(); - var opacityPos = 0; - var length = width * height * 4; - - switch (numComps) { - case 1: - for (var i = 0; i < length; i += 4) { - var p = comps[compsPos++]; - buffer[i] = p; - buffer[i+1] = p; - buffer[i+2] = p; - buffer[i+3] = opacity[opacityPos++]; - } - break; - case 3: - for (var i = 0; i < length; i += 4) { - buffer[i] = comps[compsPos++]; - buffer[i+1] = comps[compsPos++]; - buffer[i+2] = comps[compsPos++]; - buffer[i+3] = opacity[opacityPos++]; - } - break; - default: - TODO("Images with "+ numComps + " components per pixel"); - } - }, - fillGrayBuffer: function fillGrayBuffer(buffer) { - var numComps = this.numComps; - if (numComps != 1) - error("Reading gray scale from a color image"); - - var width = this.width; - var height = this.height; - var bpc = this.bpc; - - // rows start at byte boundary; - var rowBytes = (width * numComps * bpc + 7) >> 3; - var imgArray = this.image.getBytes(height * rowBytes); - - var comps = this.getComponents(imgArray); - var length = width * height; - - for (var i = 0; i < length; ++i) - buffer[i] = comps[i]; - }, - }; - return constructor; + for (var i = 0; i < length; ++i) + buffer[i] = comps[i]; + } + }; + return constructor; })(); var PDFFunction = (function() { - function constructor(xref, fn) { - var dict = fn.dict; - if (!dict) - dict = fn; + function constructor(xref, fn) { + var dict = fn.dict; + if (!dict) + dict = fn; - var types = [this.constructSampled, - null, - this.constructInterpolated, - this.constructStiched, - this.constructPostScript]; + var types = [this.constructSampled, + null, + this.constructInterpolated, + this.constructStiched, + this.constructPostScript]; - var typeNum = dict.get("FunctionType"); - var typeFn = types[typeNum]; - if (!typeFn) - error("Unknown type of function"); + var typeNum = dict.get('FunctionType'); + var typeFn = types[typeNum]; + if (!typeFn) + error('Unknown type of function'); - typeFn.apply(this, [fn, dict]); - }; + typeFn.call(this, fn, dict); + }; - constructor.prototype = { - constructSampled: function(str, dict) { - var domain = dict.get("Domain"); - var range = dict.get("Range"); + constructor.prototype = { + constructSampled: function(str, dict) { + var domain = dict.get('Domain'); + var range = dict.get('Range'); - if (!domain || !range) - error("No domain or range"); + if (!domain || !range) + error('No domain or range'); - var inputSize = domain.length / 2; - var outputSize = range.length / 2; + var inputSize = domain.length / 2; + var outputSize = range.length / 2; - if (inputSize != 1) - error("No support for multi-variable inputs to functions"); + if (inputSize != 1) + error('No support for multi-variable inputs to functions'); - var size = dict.get("Size"); - var bps = dict.get("BitsPerSample"); - var order = dict.get("Order"); - if (!order) - order = 1; - if (order !== 1) - error ("No support for cubic spline interpolation"); + var size = dict.get('Size'); + var bps = dict.get('BitsPerSample'); + var order = dict.get('Order'); + if (!order) + order = 1; + if (order !== 1) + error('No support for cubic spline interpolation'); - var encode = dict.get("Encode"); - if (!encode) { - encode = []; - for (var i = 0; i < inputSize; ++i) { - encode.push(0); - encode.push(size[i] - 1); - } - } - var decode = dict.get("Decode"); - if (!decode) - decode = range; - - var samples = this.getSampleArray(size, outputSize, bps, str); - - this.func = function(args) { - var clip = function(v, min, max) { - if (v > max) - v = max; - else if (v < min) - v = min - return v; - } - - if (inputSize != args.length) - error("Incorrect number of arguments"); - - for (var i = 0; i < inputSize; i++) { - var i2 = i * 2; - - // clip to the domain - var v = clip(args[i], domain[i2], domain[i2 + 1]); - - // encode - v = encode[i2] + ((v - domain[i2]) * - (encode[i2 + 1] - encode[i2]) / - (domain[i2 + 1] - domain[i2])); - - // clip to the size - args[i] = clip(v, 0, size[i] - 1); - } - - // interpolate to table - TODO("Multi-dimensional interpolation"); - var floor = Math.floor(args[0]); - var ceil = Math.ceil(args[0]); - var scale = args[0] - floor; - - floor *= outputSize; - ceil *= outputSize; - - var output = []; - for (var i = 0; i < outputSize; ++i) { - if (ceil == floor) { - var v = samples[ceil + i]; - } else { - var low = samples[floor + i]; - var high = samples[ceil + i]; - var v = low * scale + high * (1 - scale); - } - - var i2 = i * 2; - // decode - v = decode[i2] + (v * (decode[i2 + 1] - decode[i2]) / - ((1 << bps) - 1)); - - // clip to the domain - output.push(clip(v, range[i2], range[i2 + 1])); - } - - return output; - } - }, - getSampleArray: function(size, outputSize, bps, str) { - var length = 1; - for (var i = 0; i < size.length; i++) - length *= size[i]; - length *= outputSize; - - var array = []; - var codeSize = 0; - var codeBuf = 0; - - var strBytes = str.getBytes((length * bps + 7) / 8); - var strIdx = 0; - for (var i = 0; i < length; i++) { - var b; - while (codeSize < bps) { - codeBuf <<= 8; - codeBuf |= strBytes[strIdx++]; - codeSize += 8; - } - codeSize -= bps - array.push(codeBuf >> codeSize); - codeBuf &= (1 << codeSize) - 1; - } - return array; - }, - constructInterpolated: function() { - error("unhandled type of function"); - }, - constructStiched: function() { - error("unhandled type of function"); - }, - constructPostScript: function() { - error("unhandled type of function"); + var encode = dict.get('Encode'); + if (!encode) { + encode = []; + for (var i = 0; i < inputSize; ++i) { + encode.push(0); + encode.push(size[i] - 1); } - }; + } + var decode = dict.get('Decode'); + if (!decode) + decode = range; - return constructor; + var samples = this.getSampleArray(size, outputSize, bps, str); + + this.func = function(args) { + var clip = function(v, min, max) { + if (v > max) + v = max; + else if (v < min) + v = min; + return v; + } + + if (inputSize != args.length) + error('Incorrect number of arguments'); + + for (var i = 0; i < inputSize; i++) { + var i2 = i * 2; + + // clip to the domain + var v = clip(args[i], domain[i2], domain[i2 + 1]); + + // encode + v = encode[i2] + ((v - domain[i2]) * + (encode[i2 + 1] - encode[i2]) / + (domain[i2 + 1] - domain[i2])); + + // clip to the size + args[i] = clip(v, 0, size[i] - 1); + } + + // interpolate to table + TODO('Multi-dimensional interpolation'); + var floor = Math.floor(args[0]); + var ceil = Math.ceil(args[0]); + var scale = args[0] - floor; + + floor *= outputSize; + ceil *= outputSize; + + var output = []; + for (var i = 0; i < outputSize; ++i) { + if (ceil == floor) { + var v = samples[ceil + i]; + } else { + var low = samples[floor + i]; + var high = samples[ceil + i]; + var v = low * scale + high * (1 - scale); + } + + var i2 = i * 2; + // decode + v = decode[i2] + (v * (decode[i2 + 1] - decode[i2]) / + ((1 << bps) - 1)); + + // clip to the domain + output.push(clip(v, range[i2], range[i2 + 1])); + } + + return output; + } + }, + getSampleArray: function(size, outputSize, bps, str) { + var length = 1; + for (var i = 0; i < size.length; i++) + length *= size[i]; + length *= outputSize; + + var array = []; + var codeSize = 0; + var codeBuf = 0; + + var strBytes = str.getBytes((length * bps + 7) / 8); + var strIdx = 0; + for (var i = 0; i < length; i++) { + var b; + while (codeSize < bps) { + codeBuf <<= 8; + codeBuf |= strBytes[strIdx++]; + codeSize += 8; + } + codeSize -= bps; + array.push(codeBuf >> codeSize); + codeBuf &= (1 << codeSize) - 1; + } + return array; + }, + constructInterpolated: function() { + TODO('unhandled type of function'); + this.func = function () { return [ 255, 105, 180 ]; } + }, + constructStiched: function() { + TODO('unhandled type of function'); + this.func = function () { return [ 255, 105, 180 ]; } + }, + constructPostScript: function() { + TODO('unhandled type of function'); + this.func = function () { return [ 255, 105, 180 ]; } + } + }; + + return constructor; })(); diff --git a/test/driver.js b/test/driver.js new file mode 100644 index 000000000..e397f108b --- /dev/null +++ b/test/driver.js @@ -0,0 +1,209 @@ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- / +/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ + +/* + * A Test Driver for PDF.js + */ + +var appPath, browser, canvas, currentTaskIdx, manifest, stdout; + +function queryParams() { + var qs = window.location.search.substring(1); + var kvs = qs.split("&"); + var params = { }; + for (var i = 0; i < kvs.length; ++i) { + var kv = kvs[i].split("="); + params[unescape(kv[0])] = unescape(kv[1]); + } + return params; +} + +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("load...\n"); + + log("Harness thinks this browser is '"+ browser + "' with path " + appPath + "\n"); + log("Fetching manifest "+ manifestFile +"..."); + + var r = new XMLHttpRequest(); + r.open("GET", manifestFile, false); + r.onreadystatechange = function(e) { + if (r.readyState == 4) { + log("done\n"); + manifest = JSON.parse(r.responseText); + currentTaskIdx = 0, nextTask(); + } + }; + r.send(null); +} +window.onload = load; + +function nextTask() { + if (currentTaskIdx == manifest.length) { + return done(); + } + var task = manifest[currentTaskIdx]; + task.round = 0; + + log("Loading file "+ task.file +"\n"); + + var r = new XMLHttpRequest(); + r.open("GET", task.file); + r.mozResponseType = r.responseType = "arraybuffer"; + r.onreadystatechange = function() { + var failure; + if (r.readyState == 4) { + var data = r.mozResponseArrayBuffer || r.mozResponse || + r.responseArrayBuffer || r.response; + + try { + task.pdfDoc = new PDFDoc(new Stream(data)); + } catch(e) { + failure = 'load PDF doc: '+ e.toString(); + } + + task.pageNum = 1, nextPage(task, failure); + } + }; + r.send(null); +} + +function isLastPage(task) { + return (task.pdfDoc && (task.pageNum > task.pdfDoc.numPages)); +} + +function nextPage(task, loadError) { + if (isLastPage(task)) { + if (++task.round < task.rounds) { + log(" Round "+ (1 + task.round) +"\n"); + task.pageNum = 1; + } else { + ++currentTaskIdx, nextTask(); + return; + } + } + + var failure = loadError || ''; + + var ctx = null; + var page = null; + if (!failure) { + try { + log(" loading page "+ task.pageNum +"... "); + ctx = canvas.getContext("2d"); + page = task.pdfDoc.getPage(task.pageNum); + + var pdfToCssUnitsCoef = 96.0 / 72.0; + // using mediaBox for the canvas size + var pageWidth = (page.mediaBox[2] - page.mediaBox[0]); + var pageHeight = (page.mediaBox[3] - page.mediaBox[1]); + canvas.width = pageWidth * pdfToCssUnitsCoef; + canvas.height = pageHeight * pdfToCssUnitsCoef; + clear(ctx); + + page.startRendering( + ctx, + function() { snapshotCurrentPage(page, task, failure); }); + } catch(e) { + failure = 'page setup: '+ e.toString(); + } + } + + if (failure) { + // Skip right to snapshotting if there was a failure, since the + // fonts might be in an inconsistent state. + snapshotCurrentPage(page, task, failure); + } +} + +function snapshotCurrentPage(page, task, failure) { + log("done, snapshotting... "); + + sendTaskResult(canvas.toDataURL("image/png"), task, failure); + log("done"+ (failure ? " (failed!: "+ failure +")" : "") +"\n"); + + // Set up the next request + backoff = (inFlightRequests > 0) ? inFlightRequests * 10 : 0; + setTimeout(function() { + ++task.pageNum, nextPage(task); + }, + backoff + ); +} + +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.

CLOSE ME!

"; + if (window.SpecialPowers) { + SpecialPowers.quitApplication(); + } else { + sendQuitRequest(); + window.close(); + } +} + +function done() { + if (inFlightRequests > 0) { + document.getElementById("inFlightCount").innerHTML = inFlightRequests; + setTimeout(done, 100); + } else { + setTimeout(quitApp, 100); + } +} + +var inFlightRequests = 0; +function sendTaskResult(snapshot, task, failure) { + var result = { browser: browser, + id: task.id, + numPages: task.pdfDoc.numPages, + failure: failure, + file: task.file, + round: task.round, + page: task.pageNum, + snapshot: snapshot }; + + var r = new XMLHttpRequest(); + // (The POST URI is ignored atm.) + r.open("POST", "/submit_task_results", true); + r.setRequestHeader("Content-Type", "application/json"); + r.onreadystatechange = function(e) { + if (r.readyState == 4) { + inFlightRequests--; + } + } + document.getElementById("inFlightCount").innerHTML = inFlightRequests++; + r.send(JSON.stringify(result)); +} + +function clear(ctx) { + ctx.save(); + ctx.fillStyle = "rgb(255, 255, 255)"; + ctx.fillRect(0, 0, canvas.width, canvas.height); + ctx.restore(); +} + +/* Auto-scroll if the scrollbar is near the bottom, otherwise do nothing. */ +function checkScrolling() { + if ((stdout.scrollHeight - stdout.scrollTop) <= stdout.offsetHeight) { + stdout.scrollTop = stdout.scrollHeight; + } +} + +function log(str) { + stdout.innerHTML += str; + checkScrolling(); +} \ No newline at end of file diff --git a/test/pdfs/DiwanProfile.pdf.link b/test/pdfs/DiwanProfile.pdf.link new file mode 100644 index 000000000..12694632f --- /dev/null +++ b/test/pdfs/DiwanProfile.pdf.link @@ -0,0 +1 @@ +http://oannis.com/DiwanProfile.pdf \ No newline at end of file diff --git a/test/pdfs/asciihexdecode.pdf b/test/pdfs/asciihexdecode.pdf new file mode 100644 index 000000000..f3bd457ec --- /dev/null +++ b/test/pdfs/asciihexdecode.pdf @@ -0,0 +1,55 @@ +%PDF-1.0 +1 0 obj +<< + /Pages 2 0 R + /Type /Catalog +>> +endobj +2 0 obj +<< + /Count 1 + /Kids [ 3 0 R ] + /Type /Pages +>> +endobj +3 0 obj +<< + /MediaBox [ 0 0 795 842 ] + /Parent 2 0 R + /Contents 4 0 R + /Resources << + /Font << + /F1 << + /Name /F1 + /BaseFont /Helvetica + /Subtype /Type1 + /Type /Font + >> + >> + >> + /Type /Page +>> +endobj +4 0 obj +<< + /Filter /ASCIIHexDecode + /Length 111 +>>stream +42540A2F46312033302054660A333530203735302054640A323020544C0A312054720A2848656C6C6F20776F726C642920546A0A45540A> +endstream +endobj +xref +0 5 +0000000000 65535 f +0000000010 00000 n +0000000067 00000 n +0000000136 00000 n +0000000373 00000 n +trailer +<< + /Root 1 0 R + /Size 5 +>> +startxref +568 +%%EOF diff --git a/test/pdfs/shavian.pdf.link b/test/pdfs/shavian.pdf.link new file mode 100644 index 000000000..42c438644 --- /dev/null +++ b/test/pdfs/shavian.pdf.link @@ -0,0 +1 @@ +http://www.unicode.org/charts/PDF/U10450.pdf \ No newline at end of file diff --git a/test/test.py b/test/test.py index 52e91476a..b61ba816b 100644 --- a/test/test.py +++ b/test/test.py @@ -17,7 +17,6 @@ TMPDIR = 'tmp' VERBOSE = False SERVER_HOST = "localhost" -SERVER_PORT = 8080 class TestOptions(OptionParser): def __init__(self, **kwargs): @@ -34,6 +33,8 @@ class TestOptions(OptionParser): self.add_option("--reftest", action="store_true", dest="reftest", help="Automatically start reftest showing comparison test failures, if there are any.", default=False) + self.add_option("--port", action="store", dest="port", type="int", + help="The port the HTTP server should listen on.", default=8080) self.set_usage(USAGE_EXAMPLE) def verifyOptions(self, options): @@ -44,7 +45,7 @@ class TestOptions(OptionParser): if options.browser and options.browserManifestFile: print "Warning: ignoring browser argument since manifest file was also supplied" if not options.browser and not options.browserManifestFile: - print "No browser arguments supplied, so just starting server on port %s." % SERVER_PORT + print "Starting server on port %s." % options.port return options def prompt(question): @@ -70,7 +71,6 @@ class State: remaining = 0 results = { } done = False - masterMode = False numErrors = 0 numEqFailures = 0 numEqNoSnapshot = 0 @@ -99,7 +99,7 @@ class PDFTestHandler(BaseHTTPRequestHandler): self.send_header("Content-Type", MIMEs[ext]) self.send_header("Content-Length", os.path.getsize(path)) self.end_headers() - with open(path) as f: + with open(path, "rb") as f: self.wfile.write(f.read()) def do_GET(self): @@ -157,7 +157,8 @@ class PDFTestHandler(BaseHTTPRequestHandler): # sort the results since they sometimes come in out of order for results in taskResults: results.sort(key=lambda result: result.page) - check(State.manifest[id], taskResults, browser) + check(State.manifest[id], taskResults, browser, + self.server.masterMode) # Please oh please GC this ... del State.taskResults[browser][id] State.remaining -= 1 @@ -188,7 +189,7 @@ class BaseBrowserCommand(object): self._fixupMacPath() if not os.path.exists(self.path): - throw("Path to browser '%s' does not exist." % self.path) + raise Exception("Path to browser '%s' does not exist." % self.path) def setup(self): self.tempDir = tempfile.mkdtemp() @@ -275,7 +276,7 @@ def downloadLinkedPDFs(manifestList): sys.stdout.flush() response = urllib2.urlopen(link) - with open(f, 'w') as out: + with open(f, 'wb') as out: out.write(response.read()) print 'done' @@ -284,7 +285,6 @@ def setUp(options): # Only serve files from a pdf.js clone assert not ANAL or os.path.isfile('../pdf.js') and os.path.isdir('../.git') - State.masterMode = options.masterMode if options.masterMode and os.path.isdir(TMPDIR): print 'Temporary snapshot dir tmp/ is still around.' print 'tmp/ can be removed if it has nothing you need.' @@ -325,7 +325,7 @@ def startBrowsers(browsers, options): for b in browsers: b.setup() print 'Launching', b.name - host = 'http://%s:%s' % (SERVER_HOST, SERVER_PORT) + host = 'http://%s:%s' % (SERVER_HOST, options.port) path = '/test/test_slave.html?' qs = 'browser='+ urllib.quote(b.name) +'&manifestFile='+ urllib.quote(options.manifestFile) qs += '&path=' + b.path @@ -340,7 +340,7 @@ def teardownBrowsers(browsers): print "Temp dir was ", b.tempDir print "Error:", sys.exc_info()[0] -def check(task, results, browser): +def check(task, results, browser, masterMode): failed = False for r in xrange(len(results)): pageResults = results[r] @@ -359,7 +359,7 @@ def check(task, results, browser): kind = task['type'] if 'eq' == kind: - checkEq(task, results, browser) + checkEq(task, results, browser, masterMode) elif 'fbf' == kind: checkFBF(task, results, browser) elif 'load' == kind: @@ -368,7 +368,7 @@ def check(task, results, browser): assert 0 and 'Unknown test type' -def checkEq(task, results, browser): +def checkEq(task, results, browser, masterMode): pfx = os.path.join(REFDIR, sys.platform, browser, task['id']) results = results[0] taskId = task['id'] @@ -406,12 +406,12 @@ def checkEq(task, results, browser): passed = False State.numEqFailures += 1 - if State.masterMode and (ref is None or not eq): + if masterMode and (ref is None or not eq): tmpTaskDir = os.path.join(TMPDIR, sys.platform, browser, task['id']) try: os.makedirs(tmpTaskDir) except OSError, e: - pass + print >>sys.stderr, 'Creating', tmpTaskDir, 'failed!' of = open(os.path.join(tmpTaskDir, str(page + 1)), 'w') of.write(snapshot) @@ -482,8 +482,8 @@ def maybeUpdateRefImages(options, browser): print 'done' -def startReftest(browser): - url = "http://%s:%s" % (SERVER_HOST, SERVER_PORT) +def startReftest(browser, options): + url = "http://%s:%s" % (SERVER_HOST, options.port) url += "/test/resources/reftest-analyzer.xhtml" url += "#web=/test/eq.log" try: @@ -511,7 +511,7 @@ def runTests(options, browsers): maybeUpdateRefImages(options, browsers[0]) elif options.reftest and State.numEqFailures > 0: print "\nStarting reftest harness to examine %d eq test failures." % State.numEqFailures - startReftest(browsers[0]) + startReftest(browsers[0], options) def main(): optionParser = TestOptions() @@ -520,7 +520,8 @@ def main(): if options == None: sys.exit(1) - httpd = TestServer((SERVER_HOST, SERVER_PORT), PDFTestHandler) + httpd = TestServer((SERVER_HOST, options.port), PDFTestHandler) + httpd.masterMode = options.masterMode httpd_thread = threading.Thread(target=httpd.serve_forever) httpd_thread.setDaemon(True) httpd_thread.start() @@ -531,8 +532,11 @@ def main(): else: # just run the server print "Running HTTP server. Press Ctrl-C to quit." - while True: - time.sleep(1) + try: + while True: + time.sleep(1) + except (KeyboardInterrupt): + print "\nExiting." if __name__ == '__main__': main() diff --git a/test/test_manifest.json b/test/test_manifest.json index 06787925f..d8a8e8f7d 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -26,6 +26,12 @@ "rounds": 1, "type": "load" }, + { "id": "shavian-load", + "file": "pdfs/shavian.pdf", + "link": true, + "rounds": 1, + "type": "load" + }, { "id": "sizes", "file": "pdfs/sizes.pdf", "rounds": 1, @@ -36,5 +42,11 @@ "link": true, "rounds": 1, "type": "eq" + }, + { "id": "openoffice-pdf", + "file": "pdfs/DiwanProfile.pdf", + "link": true, + "rounds": 1, + "type": "load" } ] diff --git a/test/test_slave.html b/test/test_slave.html index 3180418fa..f6d1f7f48 100644 --- a/test/test_slave.html +++ b/test/test_slave.html @@ -6,222 +6,7 @@ - + diff --git a/utils/cffStandardStrings.js b/utils/cffStandardStrings.js index 1b328a2da..09c408ee7 100644 --- a/utils/cffStandardStrings.js +++ b/utils/cffStandardStrings.js @@ -1,693 +1,693 @@ /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- / /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ -"use strict"; +'use strict'; var CFFStrings = [ - ".notdef", - "space", - "exclam", - "quotedbl", - "numbersign", - "dollar", - "percent", - "ampersand", - "quoteright", - "parenleft", - "parenright", - "asterisk", - "plus", - "comma", - "hyphen", - "period", - "slash", - "zero", - "one", - "two", - "three", - "four", - "five", - "six", - "seven", - "eight", - "nine", - "colon", - "semicolon", - "less", - "equal", - "greater", - "question", - "at", - "A", - "B", - "C", - "D", - "E", - "F", - "G", - "H", - "I", - "J", - "K", - "L", - "M", - "N", - "O", - "P", - "Q", - "R", - "S", - "T", - "U", - "V", - "W", - "X", - "Y", - "Z", - "bracketleft", - "backslash", - "bracketright", - "asciicircum", - "underscore", - "quoteleft", - "a", - "b", - "c", - "d", - "e", - "f", - "g", - "h", - "i", - "j", - "k", - "l", - "m", - "n", - "o", - "p", - "q", - "r", - "s", - "t", - "u", - "v", - "w", - "x", - "y", - "z", - "braceleft", - "bar", - "braceright", - "asciitilde", - "exclamdown", - "cent", - "sterling", - "fraction", - "yen", - "florin", - "section", - "currency", - "quotesingle", - "quotedblleft", - "guillemotleft", - "guilsinglleft", - "guilsinglright", - "fi", - "fl", - "endash", - "dagger", - "daggerdbl", - "periodcentered", - "paragraph", - "bullet", - "quotesinglbase", - "quotedblbase", - "quotedblright", - "guillemotright", - "ellipsis", - "perthousand", - "questiondown", - "grave", - "acute", - "circumflex", - "tilde", - "macron", - "breve", - "dotaccent", - "dieresis", - "ring", - "cedilla", - "hungarumlaut", - "ogonek", - "caron", - "emdash", - "AE", - "ordfeminine", - "Lslash", - "Oslash", - "OE", - "ordmasculine", - "ae", - "dotlessi", - "lslash", - "oslash", - "oe", - "germandbls", - "onesuperior", - "logicalnot", - "mu", - "trademark", - "Eth", - "onehalf", - "plusminus", - "Thorn", - "onequarter", - "divide", - "brokenbar", - "degree", - "thorn", - "threequarters", - "twosuperior", - "registered", - "minus", - "eth", - "multiply", - "threesuperior", - "copyright", - "Aacute", - "Acircumflex", - "Adieresis", - "Agrave", - "Aring", - "Atilde", - "Ccedilla", - "Eacute", - "Ecircumflex", - "Edieresis", - "Egrave", - "Iacute", - "Icircumflex", - "Idieresis", - "Igrave", - "Ntilde", - "Oacute", - "Ocircumflex", - "Odieresis", - "Ograve", - "Otilde", - "Scaron", - "Uacute", - "Ucircumflex", - "Udieresis", - "Ugrave", - "Yacute", - "Ydieresis", - "Zcaron", - "aacute", - "acircumflex", - "adieresis", - "agrave", - "aring", - "atilde", - "ccedilla", - "eacute", - "ecircumflex", - "edieresis", - "egrave", - "iacute", - "icircumflex", - "idieresis", - "igrave", - "ntilde", - "oacute", - "ocircumflex", - "odieresis", - "ograve", - "otilde", - "scaron", - "uacute", - "ucircumflex", - "udieresis", - "ugrave", - "yacute", - "ydieresis", - "zcaron", - "exclamsmall", - "Hungarumlautsmall", - "dollaroldstyle", - "dollarsuperior", - "ampersandsmall", - "Acutesmall", - "parenleftsuperior", - "parenrightsuperior", - "266 ff", - "onedotenleader", - "zerooldstyle", - "oneoldstyle", - "twooldstyle", - "threeoldstyle", - "fouroldstyle", - "fiveoldstyle", - "sixoldstyle", - "sevenoldstyle", - "eightoldstyle", - "nineoldstyle", - "commasuperior", - "threequartersemdash", - "periodsuperior", - "questionsmall", - "asuperior", - "bsuperior", - "centsuperior", - "dsuperior", - "esuperior", - "isuperior", - "lsuperior", - "msuperior", - "nsuperior", - "osuperior", - "rsuperior", - "ssuperior", - "tsuperior", - "ff", - "ffi", - "ffl", - "parenleftinferior", - "parenrightinferior", - "Circumflexsmall", - "hyphensuperior", - "Gravesmall", - "Asmall", - "Bsmall", - "Csmall", - "Dsmall", - "Esmall", - "Fsmall", - "Gsmall", - "Hsmall", - "Ismall", - "Jsmall", - "Ksmall", - "Lsmall", - "Msmall", - "Nsmall", - "Osmall", - "Psmall", - "Qsmall", - "Rsmall", - "Ssmall", - "Tsmall", - "Usmall", - "Vsmall", - "Wsmall", - "Xsmall", - "Ysmall", - "Zsmall", - "colonmonetary", - "onefitted", - "rupiah", - "Tildesmall", - "exclamdownsmall", - "centoldstyle", - "Lslashsmall", - "Scaronsmall", - "Zcaronsmall", - "Dieresissmall", - "Brevesmall", - "Caronsmall", - "Dotaccentsmall", - "Macronsmall", - "figuredash", - "hypheninferior", - "Ogoneksmall", - "Ringsmall", - "Cedillasmall", - "questiondownsmall", - "oneeighth", - "threeeighths", - "fiveeighths", - "seveneighths", - "onethird", - "twothirds", - "zerosuperior", - "foursuperior", - "fivesuperior", - "sixsuperior", - "sevensuperior", - "eightsuperior", - "ninesuperior", - "zeroinferior", - "oneinferior", - "twoinferior", - "threeinferior", - "fourinferior", - "fiveinferior", - "sixinferior", - "seveninferior", - "eightinferior", - "nineinferior", - "centinferior", - "dollarinferior", - "periodinferior", - "commainferior", - "Agravesmall", - "Aacutesmall", - "Acircumflexsmall", - "Atildesmall", - "Adieresissmall", - "Aringsmall", - "AEsmall", - "Ccedillasmall", - "Egravesmall", - "Eacutesmall", - "Ecircumflexsmall", - "Edieresissmall", - "Igravesmall", - "Iacutesmall", - "Icircumflexsmall", - "Idieresissmall", - "Ethsmall", - "Ntildesmall", - "Ogravesmall", - "Oacutesmall", - "Ocircumflexsmall", - "Otildesmall", - "Odieresissmall", - "OEsmall", - "Oslashsmall", - "Ugravesmall", - "Uacutesmall", - "Ucircumflexsmall", - "Udieresissmall", - "Yacutesmall", - "Thornsmall", - "Ydieresissmall", - "001.000", - "001.001", - "001.002", - "001.003", - "Black", - "Bold", - "Book", - "Light", - "Medium", - "Regular", - "Roman", - "Semibold" + '.notdef', + 'space', + 'exclam', + 'quotedbl', + 'numbersign', + 'dollar', + 'percent', + 'ampersand', + 'quoteright', + 'parenleft', + 'parenright', + 'asterisk', + 'plus', + 'comma', + 'hyphen', + 'period', + 'slash', + 'zero', + 'one', + 'two', + 'three', + 'four', + 'five', + 'six', + 'seven', + 'eight', + 'nine', + 'colon', + 'semicolon', + 'less', + 'equal', + 'greater', + 'question', + 'at', + 'A', + 'B', + 'C', + 'D', + 'E', + 'F', + 'G', + 'H', + 'I', + 'J', + 'K', + 'L', + 'M', + 'N', + 'O', + 'P', + 'Q', + 'R', + 'S', + 'T', + 'U', + 'V', + 'W', + 'X', + 'Y', + 'Z', + 'bracketleft', + 'backslash', + 'bracketright', + 'asciicircum', + 'underscore', + 'quoteleft', + 'a', + 'b', + 'c', + 'd', + 'e', + 'f', + 'g', + 'h', + 'i', + 'j', + 'k', + 'l', + 'm', + 'n', + 'o', + 'p', + 'q', + 'r', + 's', + 't', + 'u', + 'v', + 'w', + 'x', + 'y', + 'z', + 'braceleft', + 'bar', + 'braceright', + 'asciitilde', + 'exclamdown', + 'cent', + 'sterling', + 'fraction', + 'yen', + 'florin', + 'section', + 'currency', + 'quotesingle', + 'quotedblleft', + 'guillemotleft', + 'guilsinglleft', + 'guilsinglright', + 'fi', + 'fl', + 'endash', + 'dagger', + 'daggerdbl', + 'periodcentered', + 'paragraph', + 'bullet', + 'quotesinglbase', + 'quotedblbase', + 'quotedblright', + 'guillemotright', + 'ellipsis', + 'perthousand', + 'questiondown', + 'grave', + 'acute', + 'circumflex', + 'tilde', + 'macron', + 'breve', + 'dotaccent', + 'dieresis', + 'ring', + 'cedilla', + 'hungarumlaut', + 'ogonek', + 'caron', + 'emdash', + 'AE', + 'ordfeminine', + 'Lslash', + 'Oslash', + 'OE', + 'ordmasculine', + 'ae', + 'dotlessi', + 'lslash', + 'oslash', + 'oe', + 'germandbls', + 'onesuperior', + 'logicalnot', + 'mu', + 'trademark', + 'Eth', + 'onehalf', + 'plusminus', + 'Thorn', + 'onequarter', + 'divide', + 'brokenbar', + 'degree', + 'thorn', + 'threequarters', + 'twosuperior', + 'registered', + 'minus', + 'eth', + 'multiply', + 'threesuperior', + 'copyright', + 'Aacute', + 'Acircumflex', + 'Adieresis', + 'Agrave', + 'Aring', + 'Atilde', + 'Ccedilla', + 'Eacute', + 'Ecircumflex', + 'Edieresis', + 'Egrave', + 'Iacute', + 'Icircumflex', + 'Idieresis', + 'Igrave', + 'Ntilde', + 'Oacute', + 'Ocircumflex', + 'Odieresis', + 'Ograve', + 'Otilde', + 'Scaron', + 'Uacute', + 'Ucircumflex', + 'Udieresis', + 'Ugrave', + 'Yacute', + 'Ydieresis', + 'Zcaron', + 'aacute', + 'acircumflex', + 'adieresis', + 'agrave', + 'aring', + 'atilde', + 'ccedilla', + 'eacute', + 'ecircumflex', + 'edieresis', + 'egrave', + 'iacute', + 'icircumflex', + 'idieresis', + 'igrave', + 'ntilde', + 'oacute', + 'ocircumflex', + 'odieresis', + 'ograve', + 'otilde', + 'scaron', + 'uacute', + 'ucircumflex', + 'udieresis', + 'ugrave', + 'yacute', + 'ydieresis', + 'zcaron', + 'exclamsmall', + 'Hungarumlautsmall', + 'dollaroldstyle', + 'dollarsuperior', + 'ampersandsmall', + 'Acutesmall', + 'parenleftsuperior', + 'parenrightsuperior', + '266 ff', + 'onedotenleader', + 'zerooldstyle', + 'oneoldstyle', + 'twooldstyle', + 'threeoldstyle', + 'fouroldstyle', + 'fiveoldstyle', + 'sixoldstyle', + 'sevenoldstyle', + 'eightoldstyle', + 'nineoldstyle', + 'commasuperior', + 'threequartersemdash', + 'periodsuperior', + 'questionsmall', + 'asuperior', + 'bsuperior', + 'centsuperior', + 'dsuperior', + 'esuperior', + 'isuperior', + 'lsuperior', + 'msuperior', + 'nsuperior', + 'osuperior', + 'rsuperior', + 'ssuperior', + 'tsuperior', + 'ff', + 'ffi', + 'ffl', + 'parenleftinferior', + 'parenrightinferior', + 'Circumflexsmall', + 'hyphensuperior', + 'Gravesmall', + 'Asmall', + 'Bsmall', + 'Csmall', + 'Dsmall', + 'Esmall', + 'Fsmall', + 'Gsmall', + 'Hsmall', + 'Ismall', + 'Jsmall', + 'Ksmall', + 'Lsmall', + 'Msmall', + 'Nsmall', + 'Osmall', + 'Psmall', + 'Qsmall', + 'Rsmall', + 'Ssmall', + 'Tsmall', + 'Usmall', + 'Vsmall', + 'Wsmall', + 'Xsmall', + 'Ysmall', + 'Zsmall', + 'colonmonetary', + 'onefitted', + 'rupiah', + 'Tildesmall', + 'exclamdownsmall', + 'centoldstyle', + 'Lslashsmall', + 'Scaronsmall', + 'Zcaronsmall', + 'Dieresissmall', + 'Brevesmall', + 'Caronsmall', + 'Dotaccentsmall', + 'Macronsmall', + 'figuredash', + 'hypheninferior', + 'Ogoneksmall', + 'Ringsmall', + 'Cedillasmall', + 'questiondownsmall', + 'oneeighth', + 'threeeighths', + 'fiveeighths', + 'seveneighths', + 'onethird', + 'twothirds', + 'zerosuperior', + 'foursuperior', + 'fivesuperior', + 'sixsuperior', + 'sevensuperior', + 'eightsuperior', + 'ninesuperior', + 'zeroinferior', + 'oneinferior', + 'twoinferior', + 'threeinferior', + 'fourinferior', + 'fiveinferior', + 'sixinferior', + 'seveninferior', + 'eightinferior', + 'nineinferior', + 'centinferior', + 'dollarinferior', + 'periodinferior', + 'commainferior', + 'Agravesmall', + 'Aacutesmall', + 'Acircumflexsmall', + 'Atildesmall', + 'Adieresissmall', + 'Aringsmall', + 'AEsmall', + 'Ccedillasmall', + 'Egravesmall', + 'Eacutesmall', + 'Ecircumflexsmall', + 'Edieresissmall', + 'Igravesmall', + 'Iacutesmall', + 'Icircumflexsmall', + 'Idieresissmall', + 'Ethsmall', + 'Ntildesmall', + 'Ogravesmall', + 'Oacutesmall', + 'Ocircumflexsmall', + 'Otildesmall', + 'Odieresissmall', + 'OEsmall', + 'Oslashsmall', + 'Ugravesmall', + 'Uacutesmall', + 'Ucircumflexsmall', + 'Udieresissmall', + 'Yacutesmall', + 'Thornsmall', + 'Ydieresissmall', + '001.000', + '001.001', + '001.002', + '001.003', + 'Black', + 'Bold', + 'Book', + 'Light', + 'Medium', + 'Regular', + 'Roman', + 'Semibold' ]; var CFFEncodingMap = { - "0": "-reserved-", - "1": "hstem", - "2": "-reserved-", - "3": "vstem", - "4": "vmoveto", - "5": "rlineto", - "6": "hlineto", - "7": "vlineto", - "8": "rrcurveto", - "9": "-reserved-", - "10": "callsubr", - "11": "return", - "12": { - "3": "and", - "4": "or", - "5": "not", - "9": "abs", - "10": "add", - "11": "div", - "12": "sub", - "14": "neg", - "15": "eq", - "18": "drop", - "20": "put", - "21": "get", - "22": "ifelse", - "23": "random", - "24": "mul", - "26": "sqrt", - "27": "dup", - "28": "exch", - "29": "index", - "30": "roll", - "34": "hflex", - "35": "flex", - "36": "hflex1", - "37": "flex1" + '0': '-reserved-', + '1': 'hstem', + '2': '-reserved-', + '3': 'vstem', + '4': 'vmoveto', + '5': 'rlineto', + '6': 'hlineto', + '7': 'vlineto', + '8': 'rrcurveto', + '9': '-reserved-', + '10': 'callsubr', + '11': 'return', + '12': { + '3': 'and', + '4': 'or', + '5': 'not', + '9': 'abs', + '10': 'add', + '11': 'div', + '12': 'sub', + '14': 'neg', + '15': 'eq', + '18': 'drop', + '20': 'put', + '21': 'get', + '22': 'ifelse', + '23': 'random', + '24': 'mul', + '26': 'sqrt', + '27': 'dup', + '28': 'exch', + '29': 'index', + '30': 'roll', + '34': 'hflex', + '35': 'flex', + '36': 'hflex1', + '37': 'flex1' }, - "13": "-reserved-", - "14": "endchar", - "15": "-reserved-", - "16": "-reserved-", - "17": "-reserved-", - "18": "hstemhm", - "19": "hintmask", - "20": "cntrmask", - "21": "rmoveto", - "22": "hmoveto", - "23": "vstemhm", - "24": "rcurveline", - "25": "rlivecurve", - "26": "vvcurveto", - "27": "hhcurveto", - "29": "callgsubr", - "30": "vhcurveto", - "31": "hvcurveto" + '13': '-reserved-', + '14': 'endchar', + '15': '-reserved-', + '16': '-reserved-', + '17': '-reserved-', + '18': 'hstemhm', + '19': 'hintmask', + '20': 'cntrmask', + '21': 'rmoveto', + '22': 'hmoveto', + '23': 'vstemhm', + '24': 'rcurveline', + '25': 'rlivecurve', + '26': 'vvcurveto', + '27': 'hhcurveto', + '29': 'callgsubr', + '30': 'vhcurveto', + '31': 'hvcurveto' }; var CFFDictDataMap = { - "0": { - name: "version", - operand: "SID" + '0': { + name: 'version', + operand: 'SID' }, - "1": { - name: "Notice", - operand: "SID" + '1': { + name: 'Notice', + operand: 'SID' }, - "2": { - name: "FullName", - operand: "SID" + '2': { + name: 'FullName', + operand: 'SID' }, - "3": { - name: "FamilyName", - operand: "SID" + '3': { + name: 'FamilyName', + operand: 'SID' }, - "4": { - name: "Weight", - operand: "SID" + '4': { + name: 'Weight', + operand: 'SID' }, - "5": { - name: "FontBBox", + '5': { + name: 'FontBBox', operand: [0, 0, 0, 0] }, - "6": { - name: "BlueValues" + '6': { + name: 'BlueValues' }, - "7": { - name: "OtherBlues" + '7': { + name: 'OtherBlues' }, - "8": { - name: "FamilyBlues" + '8': { + name: 'FamilyBlues' }, - "9": { - name: "FamilyOtherBlues" + '9': { + name: 'FamilyOtherBlues' }, - "10": { - name: "StdHW" + '10': { + name: 'StdHW' }, - "11": { - name: "StdVW" + '11': { + name: 'StdVW' }, - "12": { - "0": { - name: "Copyright", - operand: "SID" + '12': { + '0': { + name: 'Copyright', + operand: 'SID' }, - "1": { - name: "IsFixedPitch", + '1': { + name: 'IsFixedPitch', operand: false }, - "2": { - name: "ItalicAngle", + '2': { + name: 'ItalicAngle', operand: 0 }, - "3": { - name: "UnderlinePosition", + '3': { + name: 'UnderlinePosition', operand: -100 }, - "4": { - name: "UnderlineThickness", + '4': { + name: 'UnderlineThickness', operand: 50 }, - "5": { - name: "PaintType", + '5': { + name: 'PaintType', operand: 0 }, - "6": { - name: "CharstringType", + '6': { + name: 'CharstringType', operand: 2 }, - "7": { - name: "FontMatrix", - operand: [0.001, 0, 0, 0.001, 0 ,0] + '7': { + name: 'FontMatrix', + operand: [0.001, 0, 0, 0.001, 0 , 0] }, - "8": { - name: "StrokeWidth", + '8': { + name: 'StrokeWidth', operand: 0 }, - "9": { - name: "BlueScale" + '9': { + name: 'BlueScale' }, - "10": { - name: "BlueShift" + '10': { + name: 'BlueShift' }, - "11": { - name: "BlueFuzz" + '11': { + name: 'BlueFuzz' }, - "12": { - name: "StemSnapH" + '12': { + name: 'StemSnapH' }, - "13": { - name: "StemSnapV" + '13': { + name: 'StemSnapV' }, - "14": { - name: "ForceBold" + '14': { + name: 'ForceBold' }, - "17": { - name: "LanguageGroup" + '17': { + name: 'LanguageGroup' }, - "18": { - name: "ExpansionFactor" + '18': { + name: 'ExpansionFactor' }, - "9": { - name: "initialRandomSeed" + '9': { + name: 'initialRandomSeed' }, - "20": { - name: "SyntheticBase", + '20': { + name: 'SyntheticBase', operand: null }, - "21": { - name: "PostScript", - operand: "SID" + '21': { + name: 'PostScript', + operand: 'SID' }, - "22": { - name: "BaseFontName", - operand: "SID" + '22': { + name: 'BaseFontName', + operand: 'SID' }, - "23": { - name: "BaseFontBlend", - operand: "delta" + '23': { + name: 'BaseFontBlend', + operand: 'delta' } }, - "13": { - name: "UniqueID", + '13': { + name: 'UniqueID', operand: null }, - "14": { - name: "XUID", + '14': { + name: 'XUID', operand: [] }, - "15": { - name: "charset", + '15': { + name: 'charset', operand: 0 }, - "16": { - name: "Encoding", + '16': { + name: 'Encoding', operand: 0 }, - "17": { - name: "CharStrings", + '17': { + name: 'CharStrings', operand: null }, - "18": { - name: "Private", - operand: "number number" + '18': { + name: 'Private', + operand: 'number number' }, - "19": { - name: "Subrs" + '19': { + name: 'Subrs' }, - "20": { - name: "defaultWidthX" + '20': { + name: 'defaultWidthX' }, - "21": { - name: "nominalWidthX" + '21': { + name: 'nominalWidthX' } }; var CFFDictPrivateDataMap = { - "6": { - name: "BluesValues", - operand: "delta" + '6': { + name: 'BluesValues', + operand: 'delta' }, - "7": { - name: "OtherBlues", - operand: "delta" + '7': { + name: 'OtherBlues', + operand: 'delta' }, - "8": { - name: "FamilyBlues", - operand: "delta" + '8': { + name: 'FamilyBlues', + operand: 'delta' }, - "9": { - name: "FamilyOtherBlues", - operand: "delta" + '9': { + name: 'FamilyOtherBlues', + operand: 'delta' }, - "10": { - name: "StdHW", + '10': { + name: 'StdHW', operand: null }, - "11": { - name: "StdVW", + '11': { + name: 'StdVW', operand: null }, - "12": { - "9": { - name: "BlueScale", + '12': { + '9': { + name: 'BlueScale', operand: 0.039625 }, - "10": { - name: "BlueShift", + '10': { + name: 'BlueShift', operand: 7 }, - "11": { - name: "BlueFuzz", + '11': { + name: 'BlueFuzz', operand: 1 }, - "12": { - name: "StemSnapH", - operand: "delta" + '12': { + name: 'StemSnapH', + operand: 'delta' }, - "13": { - name: "StemSnapV", - operand: "delta" + '13': { + name: 'StemSnapV', + operand: 'delta' }, - "14": { - name: "ForceBold", - operand: "boolean" + '14': { + name: 'ForceBold', + operand: 'boolean' }, - "17": { - name: "LanguageGroup", + '17': { + name: 'LanguageGroup', operand: 0 }, - "18": { - name: "ExpansionFactor", + '18': { + name: 'ExpansionFactor', operand: 0.06 }, - "19": { - name: "initialRandomSeed", + '19': { + name: 'initialRandomSeed', operand: 0 } }, - "19": { - name: "Subrs", + '19': { + name: 'Subrs', operand: null }, - "20": { - name: "defaultWidthX", + '20': { + name: 'defaultWidthX', operand: 0 }, - "21": { - name: "nominalWidthX", + '21': { + name: 'nominalWidthX', operand: 0 } }; diff --git a/utils/fonts_utils.js b/utils/fonts_utils.js index bc0a8544c..edfc22186 100644 --- a/utils/fonts_utils.js +++ b/utils/fonts_utils.js @@ -1,7 +1,7 @@ /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- / /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ -"use strict"; +'use strict'; /** * The Type2 reader code below is only used for debugging purpose since Type2 @@ -21,7 +21,7 @@ function readCharset(aStream, aCharstrings) { var format = aStream.getByte(); if (format == 0) { - charset[".notdef"] = readCharstringEncoding(aCharstrings[0]); + charset['.notdef'] = readCharstringEncoding(aCharstrings[0]); var count = aCharstrings.length - 1; for (var i = 1; i < count + 1; i++) { @@ -30,13 +30,13 @@ function readCharset(aStream, aCharstrings) { //log(CFFStrings[sid] + "::" + charset[CFFStrings[sid]]); } } else if (format == 1) { - error("Charset Range are not supported"); + error('Charset Range are not supported'); } else { - error("Invalid charset format"); + error('Invalid charset format'); } return charset; -}; +} /** * Take a Type2 binary charstring as input and transform it to a human @@ -83,7 +83,7 @@ function readCharstringEncoding(aString) { } return charstringTokens; -}; +} /** @@ -105,10 +105,10 @@ function readFontDictData(aString, aMap) { } else if (value == 29) { token = aString[i++] << 24 | aString[i++] << 16 | - aString[i++] << 8 | + aString[i++] << 8 | aString[i++]; } else if (value == 30) { - token = ""; + token = ''; var parsed = false; while (!parsed) { var byte = aString[i++]; @@ -118,18 +118,18 @@ function readFontDictData(aString, aMap) { var nibble = nibbles[j]; switch (nibble) { case 0xA: - token += "."; + token += '.'; break; case 0xB: - token += "E"; + token += 'E'; break; case 0xC: - token += "E-"; + token += 'E-'; break; case 0xD: break; case 0xE: - token += "-"; + token += '-'; break; case 0xF: parsed = true; @@ -139,7 +139,7 @@ function readFontDictData(aString, aMap) { break; } } - }; + } token = parseFloat(token); } else if (value <= 31) { token = aMap[value]; @@ -150,14 +150,14 @@ function readFontDictData(aString, aMap) { } else if (value <= 254) { token = -((value - 251) * 256) - aString[i++] - 108; } else if (value == 255) { - error("255 is not a valid DICT command"); + error('255 is not a valid DICT command'); } fontDictDataTokens.push(token); } return fontDictDataTokens; -}; +} /** @@ -192,7 +192,7 @@ function readFontIndexData(aStream, aIsByte) { return aStream.getByte() << 24 | aStream.getByte() << 16 | aStream.getByte() << 8 | aStream.getByte(); } - error(offsize + " is not a valid offset size"); + error(offsize + ' is not a valid offset size'); return null; }; @@ -200,7 +200,8 @@ function readFontIndexData(aStream, aIsByte) { for (var i = 0; i < count + 1; i++) offsets.push(getNextOffset()); - log("Found " + count + " objects at offsets :" + offsets + " (offsize: " + offsize + ")"); + log('Found ' + count + ' objects at offsets :' + + offsets + ' (offsize: ' + offsize + ')'); // Now extract the objects var relativeOffset = aStream.pos; @@ -217,15 +218,15 @@ function readFontIndexData(aStream, aIsByte) { } return objects; -}; +} var Type2Parser = function(aFilePath) { var font = new Dict(); var xhr = new XMLHttpRequest(); - xhr.open("GET", aFilePath, false); - xhr.mozResponseType = xhr.responseType = "arraybuffer"; - xhr.expected = (document.URL.indexOf("file:") == 0) ? 0 : 200; + xhr.open('GET', aFilePath, false); + xhr.mozResponseType = xhr.responseType = 'arraybuffer'; + xhr.expected = (document.URL.indexOf('file:') == 0) ? 0 : 200; xhr.send(null); this.data = new Stream(xhr.mozResponseArrayBuffer || xhr.mozResponse || xhr.responseArrayBuffer || xhr.response); @@ -249,19 +250,19 @@ var Type2Parser = function(aFilePath) { stack.push(token); } else { switch (token.operand) { - case "SID": + case 'SID': font.set(token.name, CFFStrings[stack.pop()]); break; - case "number number": + case 'number number': font.set(token.name, { offset: stack.pop(), size: stack.pop() }); break; - case "boolean": + case 'boolean': font.set(token.name, stack.pop()); break; - case "delta": + case 'delta': font.set(token.name, stack.pop()); break; default: @@ -280,32 +281,32 @@ var Type2Parser = function(aFilePath) { }; this.parse = function(aStream) { - font.set("major", aStream.getByte()); - font.set("minor", aStream.getByte()); - font.set("hdrSize", aStream.getByte()); - font.set("offsize", aStream.getByte()); + font.set('major', aStream.getByte()); + font.set('minor', aStream.getByte()); + font.set('hdrSize', aStream.getByte()); + font.set('offsize', aStream.getByte()); // Move the cursor after the header - aStream.skip(font.get("hdrSize") - aStream.pos); + aStream.skip(font.get('hdrSize') - aStream.pos); // Read the NAME Index - dump("Reading Index: Names"); - font.set("Names", readFontIndexData(aStream)); - log("Names: " + font.get("Names")); + dump('Reading Index: Names'); + font.set('Names', readFontIndexData(aStream)); + log('Names: ' + font.get('Names')); // Read the Top Dict Index - dump("Reading Index: TopDict"); + dump('Reading Index: TopDict'); var topDict = readFontIndexData(aStream, true); - log("TopDict: " + topDict); + log('TopDict: ' + topDict); // Read the String Index - dump("Reading Index: Strings"); + dump('Reading Index: Strings'); var strings = readFontIndexData(aStream); - log("strings: " + strings); + log('strings: ' + strings); // Fill up the Strings dictionary with the new unique strings for (var i = 0; i < strings.length; i++) - CFFStrings.push(strings[i].join("")); + CFFStrings.push(strings[i].join('')); // Parse the TopDict operator var objects = []; @@ -315,39 +316,40 @@ var Type2Parser = function(aFilePath) { // Read the Global Subr Index that comes just after the Strings Index // (cf. "The Compact Font Format Specification" Chapter 16) - dump("Reading Global Subr Index"); + dump('Reading Global Subr Index'); var subrs = readFontIndexData(aStream, true); dump(subrs); // Reading Private Dict - var priv = font.get("Private"); - log("Reading Private Dict (offset: " + priv.offset + " size: " + priv.size + ")"); + var priv = font.get('Private'); + log('Reading Private Dict (offset: ' + priv.offset + + ' size: ' + priv.size + ')'); aStream.pos = priv.offset; var privateDict = []; for (var i = 0; i < priv.size; i++) privateDict.push(aStream.getByte()); - dump("private:" + privateDict); + dump('private:' + privateDict); parseAsToken(privateDict, CFFDictPrivateDataMap); for (var p in font.map) - dump(p + "::" + font.get(p)); + dump(p + '::' + font.get(p)); // Read CharStrings Index - var charStringsOffset = font.get("CharStrings"); - dump("Read CharStrings Index (offset: " + charStringsOffset + ")"); + var charStringsOffset = font.get('CharStrings'); + dump('Read CharStrings Index (offset: ' + charStringsOffset + ')'); aStream.pos = charStringsOffset; var charStrings = readFontIndexData(aStream, true); // Read Charset - dump("Read Charset for " + charStrings.length + " glyphs"); - var charsetEntry = font.get("charset"); + dump('Read Charset for ' + charStrings.length + ' glyphs'); + var charsetEntry = font.get('charset'); if (charsetEntry == 0) { - error("Need to support CFFISOAdobeCharset"); + error('Need to support CFFISOAdobeCharset'); } else if (charsetEntry == 1) { - error("Need to support CFFExpert"); + error('Need to support CFFExpert'); } else if (charsetEntry == 2) { - error("Need to support CFFExpertSubsetCharset"); + error('Need to support CFFExpertSubsetCharset'); } else { aStream.pos = charsetEntry; var charset = readCharset(aStream, charStrings); @@ -378,23 +380,23 @@ var Type2Parser = function(aFilePath) { * writeToFile(fontData, "/tmp/pdf.js." + fontCount + ".cff"); */ function writeToFile(aBytes, aFilePath) { - if (!("netscape" in window)) + if (!('netscape' in window)) return; - netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect"); + netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect'); var Cc = Components.classes, Ci = Components.interfaces; - var file = Cc['@mozilla.org/file/local;1'].createInstance(Ci.nsILocalFile); + var file = Cc['@mozilla.org/file/local;1'].createInstance(Ci.nsILocalFile); file.initWithPath(aFilePath); - var stream = Cc["@mozilla.org/network/file-output-stream;1"] + var stream = Cc['@mozilla.org/network/file-output-stream;1'] .createInstance(Ci.nsIFileOutputStream); stream.init(file, 0x04 | 0x08 | 0x20, 0x180, 0); - var bos = Cc["@mozilla.org/binaryoutputstream;1"] + var bos = Cc['@mozilla.org/binaryoutputstream;1'] .createInstance(Ci.nsIBinaryOutputStream); bos.setOutputStream(stream); bos.writeByteArray(aBytes, aBytes.length); stream.close(); -}; +} diff --git a/viewer.js b/viewer.js deleted file mode 100644 index 4071151aa..000000000 --- a/viewer.js +++ /dev/null @@ -1,111 +0,0 @@ -/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- / -/* vim: set shiftwidth=4 tabstop=8 autoindent cindent expandtab: */ - -"use strict"; - -var pdfDocument, canvas, pageScale, pageDisplay, pageNum, numPages; -function load(userInput) { - canvas = document.getElementById("canvas"); - canvas.mozOpaque = true; - pageNum = ("page" in queryParams()) ? parseInt(queryParams().page) : 1; - pageScale = ("scale" in queryParams()) ? parseInt(queryParams().scale) : 1.5; - var fileName = userInput; - if (!userInput) { - fileName = queryParams().file || "compressed.tracemonkey-pldi-09.pdf"; - } - open(fileName); -} - -function queryParams() { - var qs = window.location.search.substring(1); - var kvs = qs.split("&"); - var params = { }; - for (var i = 0; i < kvs.length; ++i) { - var kv = kvs[i].split("="); - params[unescape(kv[0])] = unescape(kv[1]); - } - return params; -} - -function open(url) { - document.title = url; - var req = new XMLHttpRequest(); - req.open("GET", url); - req.mozResponseType = req.responseType = "arraybuffer"; - req.expected = (document.URL.indexOf("file:") == 0) ? 0 : 200; - req.onreadystatechange = function() { - if (req.readyState == 4 && req.status == req.expected) { - var data = req.mozResponseArrayBuffer || req.mozResponse || - req.responseArrayBuffer || req.response; - pdfDocument = new PDFDoc(new Stream(data)); - numPages = pdfDocument.numPages; - document.getElementById("numPages").innerHTML = numPages.toString(); - goToPage(pageNum); - } - }; - req.send(null); -} - -function gotoPage(num) { - if (0 <= num && num <= numPages) - pageNum = num; - displayPage(pageNum); -} - -function displayPage(num) { - document.getElementById("pageNumber").value = num; - - var t0 = Date.now(); - - var page = pdfDocument.getPage(pageNum = num); - - var pdfToCssUnitsCoef = 96.0 / 72.0; - var pageWidth = (page.mediaBox[2] - page.mediaBox[0]); - var pageHeight = (page.mediaBox[3] - page.mediaBox[1]); - canvas.width = pageScale * pageWidth * pdfToCssUnitsCoef; - canvas.height = pageScale * pageHeight * pdfToCssUnitsCoef; - - var t1 = Date.now(); - var ctx = canvas.getContext("2d"); - ctx.save(); - ctx.fillStyle = "rgb(255, 255, 255)"; - ctx.fillRect(0, 0, canvas.width, canvas.height); - ctx.restore(); - - var gfx = new CanvasGraphics(ctx); - - // page.compile will collect all fonts for us, once we have loaded them - // we can trigger the actual page rendering with page.display - var fonts = []; - page.compile(gfx, fonts); - var t2 = Date.now(); - - function displayPage() { - var t3 = Date.now(); - - page.display(gfx); - - var t4 = Date.now(); - - var infoDisplay = document.getElementById("info"); - infoDisplay.innerHTML = "Time to load/compile/fonts/render: "+ (t1 - t0) + "/" + (t2 - t1) + "/" + (t3 - t2) + "/" + (t4 - t3) + " ms"; - } - - FontLoader.bind(fonts, displayPage); -} - -function nextPage() { - if (pageNum < pdfDocument.numPages) - displayPage(++pageNum); -} - -function prevPage() { - if (pageNum > 1) - displayPage(--pageNum); -} - -function goToPage(num) { - if (0 <= num && num <= numPages) - displayPage(pageNum = num); -} - diff --git a/web/compatibility.js b/web/compatibility.js new file mode 100644 index 000000000..5687d30ce --- /dev/null +++ b/web/compatibility.js @@ -0,0 +1,156 @@ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- / +/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ + +// Checking if the typed arrays are supported +(function() { + if (typeof Uint8Array !== 'undefined') + return; + + function subarray(start, end) { + return this.slice(start, end); + } + + function set_(array, offset) { + if (arguments.length < 2) offset = 0; + for (var i = 0, n = array.length; i < n; ++i, ++offset) + this[offset] = array[i] & 0xFF; + } + + function TypedArray(arg1) { + var result; + if (typeof arg1 === 'number') { + result = new Array(arg1); + for (var i = 0; i < arg1; ++i) + result[i] = 0; + } else + result = arg1.slice(0); + result.subarray = subarray; + result.buffer = result; + result.byteLength = result.length; + result.set = set_; + if (typeof arg1 === 'object' && arg1.buffer) + result.buffer = arg1.buffer; + + return result; + } + + window.Uint8Array = TypedArray; + + // we don't need support for set, byteLength for 32-bit array + // so we can use the TypedArray as well + window.Uint32Array = TypedArray; + window.Int32Array = TypedArray; +})(); + +// Object.create() ? +(function() { + if (typeof Object.create !== 'undefined') + return; + + Object.create = function(proto) { + var constructor = function() {}; + constructor.prototype = proto; + return new constructor(); + }; +})(); + +// Object.defineProperty() ? +(function() { + if (typeof Object.defineProperty !== 'undefined') + return; + + Object.defineProperty = function(obj, name, def) { + delete obj[name]; + if ('get' in def) + obj.__defineGetter__(name, def['get']); + if ('set' in def) + obj.__defineSetter__(name, def['set']); + if ('value' in def) { + obj.__defineSetter__(name, function(value) { + this.__defineGetter__(name, function() { + return value; + }); + return value; + }); + obj[name] = def.value; + } + }; +})(); + +// No XMLHttpRequest.response ? +(function() { + var xhrPrototype = XMLHttpRequest.prototype; + if ('response' in xhrPrototype || + 'mozResponseArrayBuffer' in xhrPrototype || + 'mozResponse' in xhrPrototype || + 'responseArrayBuffer' in xhrPrototype) + return; + // IE ? + if (typeof VBArray !== 'undefined') { + Object.defineProperty(xhrPrototype, 'response', { + get: function() { + return new Uint8Array(new VBArray(this.responseBody).toArray()); + } + }); + return; + } + + // other browsers + function responseTypeSetter() { + // will be only called to set "arraybuffer" + this.overrideMimeType('text/plain; charset=x-user-defined'); + } + if (typeof xhrPrototype.overrideMimeType === 'function') { + Object.defineProperty(xhrPrototype, 'responseType', + { set: responseTypeSetter }); + } + function responseGetter() { + var text = this.responseText; + var i, n = text.length; + var result = new Uint8Array(n); + for (i = 0; i < n; ++i) + result[i] = text.charCodeAt(i) & 0xFF; + return result; + } + Object.defineProperty(xhrPrototype, 'response', { get: responseGetter }); +})(); + +// window.btoa (base64 encode function) ? +(function() { + if ('btoa' in window) + return; + + var digits = + 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; + + window.btoa = function(chars) { + var buffer = ''; + var i, n; + for (i = 0, n = chars.length; i < n; i += 3) { + var b1 = chars.charCodeAt(i) & 0xFF; + var b2 = chars.charCodeAt(i + 1) & 0xFF; + var b3 = chars.charCodeAt(i + 2) & 0xFF; + var d1 = b1 >> 2, d2 = ((b1 & 3) << 4) | (b2 >> 4); + var d3 = i + 1 < n ? ((b2 & 0xF) << 2) | (b3 >> 6) : 64; + var d4 = i + 2 < n ? (b3 & 0x3F) : 64; + buffer += (digits.charAt(d1) + digits.charAt(d2) + + digits.charAt(d3) + digits.charAt(d4)); + } + return buffer; + }; +})(); + +// Function.prototype.bind ? +(function() { + if (typeof Function.prototype.bind !== 'undefined') + return; + + Function.prototype.bind = function(obj) { + var fn = this, headArgs = Array.prototype.slice.call(arguments, 1); + var binded = function(tailArgs) { + var args = headArgs.concat(tailArgs); + return fn.apply(obj, args); + }; + return binded; + }; +})(); diff --git a/compressed.tracemonkey-pldi-09.pdf b/web/compressed.tracemonkey-pldi-09.pdf similarity index 100% rename from compressed.tracemonkey-pldi-09.pdf rename to web/compressed.tracemonkey-pldi-09.pdf diff --git a/web/images/buttons.png b/web/images/buttons.png new file mode 100644 index 000000000..b96b9e141 Binary files /dev/null and b/web/images/buttons.png differ diff --git a/web/images/source/Buttons.psd.zip b/web/images/source/Buttons.psd.zip new file mode 100644 index 000000000..39745f540 Binary files /dev/null and b/web/images/source/Buttons.psd.zip differ diff --git a/web/index.html.template b/web/index.html.template new file mode 100644 index 000000000..c3086f078 --- /dev/null +++ b/web/index.html.template @@ -0,0 +1,88 @@ + + + + + + andreasgal/pdf.js @ GitHub + + + + + + Fork me on GitHub + +
+ +
+ + + + +
+ +

pdf.js + by andreasgal

+ +
+ PDF Reader in JavaScript +
+ +

Try it out!

+

Live demo lives here.

+ +

Authors

+

Vivien Nicolas (21@vingtetun.org) +
Andreas Gal (andreas.gal@gmail.com) +
Soumya Deb (debloper@gmail.com) +
Chris Jones (jones.chris.g@gmail.com) +
Justin D'Arcangelo (justindarc@gmail.com) +
sbarman (sbarman@eecs.berkeley.edu) +
+

+

Contact

+

(andreas.gal@gmail.com) +

+ + +

Download

+

+ You can download this project in either + zip or + tar formats. +

+

You can also clone the project with Git + by running: +

$ git clone git://github.com/andreasgal/pdf.js
+

+ + + +
+ + + + diff --git a/web/multi_page_viewer.css b/web/multi_page_viewer.css new file mode 100644 index 000000000..76bf8cd1e --- /dev/null +++ b/web/multi_page_viewer.css @@ -0,0 +1,296 @@ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- / +/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ + +body { + background-color: #929292; + font-family: 'Lucida Grande', 'Lucida Sans Unicode', Helvetica, Arial, Verdana, sans-serif; + margin: 0px; + padding: 0px; +} + +canvas { + box-shadow: 0px 4px 10px #000; + -moz-box-shadow: 0px 4px 10px #000; + -webkit-box-shadow: 0px 4px 10px #000; +} + +span { + font-size: 0.8em; + text-shadow: 0px 1px 0px #fff; +} + +.control { + display: inline-block; + float: left; + margin: 0px 20px 0px 0px; + padding: 0px 4px 0px 0px; +} + +.control > input { + float: left; + border: 1px solid #4d4d4d; + height: 20px; + padding: 0px; + margin: 0px 2px 0px 0px; + border-radius: 3px; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + box-shadow: inset 0px 1px 1px rgba(0, 0, 0, 0.3); + -moz-box-shadow: inset 0px 1px 1px rgba(0, 0, 0, 0.3); + -webkit-box-shadow: inset 0px 1px 1px rgba(0, 0, 0, 0.3); +} + +.control > select { + float: left; + border: 1px solid #4d4d4d; + height: 22px; + padding: 2px 0px 0px; + margin: 0px 0px 1px; + border-radius: 3px; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + box-shadow: inset 0px 1px 1px rgba(0, 0, 0, 0.3); + -moz-box-shadow: inset 0px 1px 1px rgba(0, 0, 0, 0.3); + -webkit-box-shadow: inset 0px 1px 1px rgba(0, 0, 0, 0.3); +} + +.control > span { + cursor: default; + float: left; + height: 18px; + margin: 5px 2px 0px; + padding: 0px; + user-select: none; + -moz-user-select: none; + -webkit-user-select: none; +} + +.control .label { + clear: both; + float: left; + font-size: 0.65em; + margin: 2px 0px 0px; + position: relative; + text-align: center; + width: 100%; +} + +.thumbnailPageNumber { + color: #fff; + font-size: 0.55em; + text-align: right; + margin: -6px 2px 6px 0px; + width: 102px; +} + +.thumbnail { + width: 104px; + height: 134px; + margin: 0px auto 10px; +} + +.page { + width: 816px; + height: 1056px; + margin: 10px auto; +} + +#controls { + background-color: #eee; + background: -moz-linear-gradient(center bottom, #ddd 0%, #fff 100%); + background: -webkit-gradient(linear, left bottom, left top, color-stop(0.0, #ddd), color-stop(1.0, #fff)); + border-bottom: 1px solid #666; + padding: 4px 0px 0px 8px; + position: fixed; + left: 0px; + top: 0px; + height: 40px; + width: 100%; + box-shadow: 0px 2px 8px #000; + -moz-box-shadow: 0px 2px 8px #000; + -webkit-box-shadow: 0px 2px 8px #000; +} + +#controls input { + user-select: text; + -moz-user-select: text; + -webkit-user-select: text; +} + +button { + background-color: #ddd; + background: -moz-linear-gradient(center bottom, #c3c3c3 0%, #f3f3f3 100%); + background: -webkit-gradient(linear, left bottom, left top, color-stop(0.0, #c3c3c3), color-stop(1.0, #f3f3f3)); + border: 1px solid #4d4d4d; + cursor: default; + float: left; + margin: 0px 0px 1px; + width: 29px; + height: 22px; + border-radius: 3px; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; +} + +button:disabled { + background-color: #eee; + background: -moz-linear-gradient(center bottom, #ddd 0%, #fff 100%); + background: -webkit-gradient(linear, left bottom, left top, color-stop(0.0, #ddd), color-stop(1.0, #fff)); +} + +button:disabled > span { + opacity: 0.3; + -moz-opacity: 0.3; + -webkit-opacity: 0.3; +} + +button.down { + background-color: #777; + background: -moz-linear-gradient(center bottom, #888 0%, #555 100%); + background: -webkit-gradient(linear, left bottom, left top, color-stop(0.0, #888), color-stop(1.0, #555)); + box-shadow: inset 0px 0px 2px rgba(0, 0, 0, 0.8); + -moz-box-shadow: inset 0px 0px 2px rgba(0, 0, 0, 0.8); + -webkit-box-shadow: inset 0px 0px 2px rgba(0, 0, 0, 0.8); +} + +#previousPageButton { + width: 28px; + border-right: 0px; + border-top-right-radius: 0px; + border-bottom-right-radius: 0px; + -moz-border-radius-topright: 0px; + -moz-border-radius-bottomright: 0px; + -webkit-border-top-right-radius: 0px; + -webkit-border-bottom-right-radius: 0px; +} + +#previousPageButton > span { + background: url('images/buttons.png') no-repeat 0px 0px; + display: inline-block; + width: 19px; + height: 19px; +} + +#nextPageButton { + width: 28px; + border-top-left-radius: 0px; + border-bottom-left-radius: 0px; + -moz-border-radius-topleft: 0px; + -moz-border-radius-bottomleft: 0px; + -webkit-border-top-left-radius: 0px; + -webkit-border-bottom-left-radius: 0px; +} + +#nextPageButton > span { + background: url('images/buttons.png') no-repeat -19px 0px; + display: inline-block; + width: 19px; + height: 19px; +} + +#singleLayoutButton { + width: 28px; + border-right: 0px; + border-top-right-radius: 0px; + border-bottom-right-radius: 0px; + -moz-border-radius-topright: 0px; + -moz-border-radius-bottomright: 0px; + -webkit-border-top-right-radius: 0px; + -webkit-border-bottom-right-radius: 0px; +} + +#singleLayoutButton > span { + background: url('images/buttons.png') no-repeat -57px 0px; + display: inline-block; + width: 19px; + height: 19px; +} + +#splitLayoutButton { + width: 28px; + border-top-left-radius: 0px; + border-bottom-left-radius: 0px; + -moz-border-radius-topleft: 0px; + -moz-border-radius-bottomleft: 0px; + -webkit-border-top-left-radius: 0px; + -webkit-border-bottom-left-radius: 0px; +} + +#splitLayoutButton > span { + background: url('images/buttons.png') no-repeat -76px 0px; + display: inline-block; + width: 19px; + height: 19px; +} + +#openFileButton { + margin-left: 3px; +} + +#openFileButton > span { + background: url('images/buttons.png') no-repeat -38px 0px; + display: inline-block; + width: 19px; + height: 19px; +} + +#fileInput { + display: none; +} + +#pageNumber { + text-align: right; +} + +#sidebar { + position: fixed; + width: 200px; + top: 62px; + bottom: 18px; + left: -140px; + transition: left 0.25s ease-in-out 1s; + -moz-transition: left 0.25s ease-in-out 1s; + -webkit-transition: left 0.25s ease-in-out 1s; +} + +#sidebar:hover { + left: 0px; + transition: left 0.25s ease-in-out 0s; + -moz-transition: left 0.25s ease-in-out 0s; + -webkit-transition: left 0.25s ease-in-out 0s; +} + +#sidebarBox { + background-color: rgba(0, 0, 0, 0.7); + width: 150px; + height: 100%; + border-top-right-radius: 8px; + border-bottom-right-radius: 8px; + -moz-border-radius-topright: 8px; + -moz-border-radius-bottomright: 8px; + -webkit-border-top-right-radius: 8px; + -webkit-border-bottom-right-radius: 8px; + box-shadow: 0px 2px 8px #000; + -moz-box-shadow: 0px 2px 8px #000; + -webkit-box-shadow: 0px 2px 8px #000; +} + +#sidebarScrollView { + position: absolute; + overflow: hidden; + overflow-y: auto; + top: 10px; + bottom: 10px; + left: 10px; + width: 130px; +} + +#sidebarContentView { + height: auto; + width: 100px; +} + +#viewer { + margin: 44px 0px 0px; + padding: 8px 0px; +} diff --git a/multi_page_viewer.html b/web/multi_page_viewer.html similarity index 63% rename from multi_page_viewer.html rename to web/multi_page_viewer.html index df71d6690..00dac6484 100644 --- a/multi_page_viewer.html +++ b/web/multi_page_viewer.html @@ -4,17 +4,18 @@ pdf.js Multi-Page Viewer - - - - + + + + +
- - + + Previous/Next @@ -34,8 +35,18 @@ Zoom + + + + + + Open File diff --git a/multi_page_viewer.js b/web/multi_page_viewer.js similarity index 80% rename from multi_page_viewer.js rename to web/multi_page_viewer.js index 01d7b4f6e..3e7e122e0 100644 --- a/multi_page_viewer.js +++ b/web/multi_page_viewer.js @@ -1,49 +1,49 @@ /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- / /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ -"use strict"; +'use strict'; var pageTimeout; var PDFViewer = { queryParams: {}, - + element: null, - + sidebarContentView: null, - + previousPageButton: null, nextPageButton: null, pageNumberInput: null, scaleSelect: null, fileInput: null, - + willJumpToPage: false, - + pdf: null, - + url: 'compressed.tracemonkey-pldi-09.pdf', pageNumber: 1, numberOfPages: 1, - + scale: 1.0, - + pageWidth: function(page) { var pdfToCssUnitsCoef = 96.0 / 72.0; var width = (page.mediaBox[2] - page.mediaBox[0]); return width * PDFViewer.scale * pdfToCssUnitsCoef; }, - + pageHeight: function(page) { var pdfToCssUnitsCoef = 96.0 / 72.0; var height = (page.mediaBox[3] - page.mediaBox[1]); return height * PDFViewer.scale * pdfToCssUnitsCoef; }, - + lastPagesDrawn: [], - + visiblePages: function() { - const pageBottomMargin = 10; + var pageBottomMargin = 10; var windowTop = window.pageYOffset; var windowBottom = window.pageYOffset + window.innerHeight; @@ -56,57 +56,57 @@ var PDFViewer = { break; currentHeight += pageHeight; } - - var pages = []; + + var pages = []; for (; i <= n && currentHeight < windowBottom; i++) { var page = PDFViewer.pdf.getPage(i); pageHeight = PDFViewer.pageHeight(page) + pageBottomMargin; currentHeight += pageHeight; pages.push(i); } - + return pages; }, - + createThumbnail: function(num) { if (PDFViewer.sidebarContentView) { var anchor = document.createElement('a'); anchor.href = '#' + num; - + var containerDiv = document.createElement('div'); containerDiv.id = 'thumbnailContainer' + num; containerDiv.className = 'thumbnail'; - + var pageNumberDiv = document.createElement('div'); pageNumberDiv.className = 'thumbnailPageNumber'; pageNumberDiv.innerHTML = '' + num; - + anchor.appendChild(containerDiv); PDFViewer.sidebarContentView.appendChild(anchor); PDFViewer.sidebarContentView.appendChild(pageNumberDiv); } }, - + removeThumbnail: function(num) { var div = document.getElementById('thumbnailContainer' + num); - + if (div) { while (div.hasChildNodes()) { div.removeChild(div.firstChild); } } }, - + drawThumbnail: function(num) { if (!PDFViewer.pdf) return; var div = document.getElementById('thumbnailContainer' + num); - + if (div && !div.hasChildNodes()) { var page = PDFViewer.pdf.getPage(num); var canvas = document.createElement('canvas'); - + canvas.id = 'thumbnail' + num; canvas.mozOpaque = true; @@ -123,53 +123,46 @@ var PDFViewer = { ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.restore(); - var gfx = new CanvasGraphics(ctx); - - // page.compile will collect all fonts for us, once we have loaded them - // we can trigger the actual page rendering with page.display - var fonts = []; - page.compile(gfx, fonts); - - FontLoader.bind(fonts, function() { page.display(gfx); }); + page.startRendering(ctx, function() { }); } }, - + createPage: function(num) { var page = PDFViewer.pdf.getPage(num); var anchor = document.createElement('a'); anchor.name = '' + num; - + var div = document.createElement('div'); div.id = 'pageContainer' + num; div.className = 'page'; div.style.width = PDFViewer.pageWidth(page) + 'px'; div.style.height = PDFViewer.pageHeight(page) + 'px'; - + PDFViewer.element.appendChild(anchor); PDFViewer.element.appendChild(div); }, - + removePage: function(num) { var div = document.getElementById('pageContainer' + num); - + if (div) { while (div.hasChildNodes()) { div.removeChild(div.firstChild); } } }, - + drawPage: function(num) { if (!PDFViewer.pdf) return; var div = document.getElementById('pageContainer' + num); - + if (div && !div.hasChildNodes()) { var page = PDFViewer.pdf.getPage(num); var canvas = document.createElement('canvas'); - + canvas.id = 'page' + num; canvas.mozOpaque = true; @@ -183,14 +176,7 @@ var PDFViewer = { ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.restore(); - var gfx = new CanvasGraphics(ctx); - - // page.compile will collect all fonts for us, once we have loaded them - // we can trigger the actual page rendering with page.display - var fonts = []; - page.compile(gfx, fonts); - - FontLoader.bind(fonts, function() { page.display(gfx); }); + page.startRendering(ctx, function() { }); } }, @@ -198,20 +184,20 @@ var PDFViewer = { while (PDFViewer.element.hasChildNodes()) { PDFViewer.element.removeChild(PDFViewer.element.firstChild); } - + PDFViewer.scale = num / 100; - + var i; - + if (PDFViewer.pdf) { for (i = 1; i <= PDFViewer.numberOfPages; i++) { PDFViewer.createPage(i); } } - + for (i = 0; i < PDFViewer.scaleSelect.childNodes; i++) { var option = PDFViewer.scaleSelect.childNodes[i]; - + if (option.value == num) { if (!option.selected) { option.selected = 'selected'; @@ -222,16 +208,16 @@ var PDFViewer = { } } } - + PDFViewer.scaleSelect.value = Math.floor(PDFViewer.scale * 100) + '%'; - + // Clear the array of the last pages drawn to force a redraw. PDFViewer.lastPagesDrawn = []; - + // Jump the scroll position to the correct page. PDFViewer.goToPage(PDFViewer.pageNumber); }, - + goToPage: function(num) { if (1 <= num && num <= PDFViewer.numberOfPages) { PDFViewer.pageNumber = num; @@ -242,24 +228,24 @@ var PDFViewer = { // Force a "scroll event" to redraw setTimeout(window.onscroll, 0); document.location.hash = PDFViewer.pageNumber; - - PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ? 'disabled' : ''; - PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ? 'disabled' : ''; + + PDFViewer.previousPageButton.disabled = (PDFViewer.pageNumber === 1); + PDFViewer.nextPageButton.disabled = (PDFViewer.pageNumber === PDFViewer.numberOfPages); } }, - + goToPreviousPage: function() { if (PDFViewer.pageNumber > 1) { PDFViewer.goToPage(--PDFViewer.pageNumber); } }, - + goToNextPage: function() { if (PDFViewer.pageNumber < PDFViewer.numberOfPages) { PDFViewer.goToPage(++PDFViewer.pageNumber); } }, - + openURL: function(url) { PDFViewer.url = url; document.title = url; @@ -269,20 +255,21 @@ var PDFViewer = { clearInterval(this.thumbsLoadingInterval); this.thumbsLoadingInterval = null; } - + var req = new XMLHttpRequest(); req.open('GET', url); req.mozResponseType = req.responseType = 'arraybuffer'; req.expected = (document.URL.indexOf('file:') === 0) ? 0 : 200; - + req.onreadystatechange = function() { if (req.readyState === 4 && req.status === req.expected) { - var data = req.mozResponseArrayBuffer || req.mozResponse || req.responseArrayBuffer || req.response; - + var data = (req.mozResponseArrayBuffer || req.mozResponse || + req.responseArrayBuffer || req.response); + PDFViewer.readPDF(data); } }; - + req.send(null); }, @@ -292,23 +279,26 @@ var PDFViewer = { while (PDFViewer.element.hasChildNodes()) { PDFViewer.element.removeChild(PDFViewer.element.firstChild); } - + while (PDFViewer.sidebarContentView.hasChildNodes()) { - PDFViewer.sidebarContentView.removeChild(PDFViewer.sidebarContentView.firstChild); + PDFViewer.sidebarContentView.removeChild( + PDFViewer.sidebarContentView.firstChild + ); } - + PDFViewer.pdf = new PDFDoc(new Stream(data)); PDFViewer.numberOfPages = PDFViewer.pdf.numPages; - document.getElementById('numPages').innerHTML = PDFViewer.numberOfPages.toString(); - + document.getElementById('numPages').innerHTML = + PDFViewer.numberOfPages.toString(); + for (var i = 1; i <= PDFViewer.numberOfPages; i++) { PDFViewer.createPage(i); } - + if (PDFViewer.numberOfPages > 0) { PDFViewer.drawPage(1); document.location.hash = 1; - + // slowly loading the thumbs (few per second) // first time we are loading more images than subsequent var currentPageIndex = 1, imagesToLoad = 15; @@ -326,9 +316,9 @@ var PDFViewer = { imagesToLoad = 3; // next time loading less images }).bind(this), 500); } - - PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ? 'disabled' : ''; - PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ? 'disabled' : ''; + + PDFViewer.previousPageButton.disabled = (PDFViewer.pageNumber === 1); + PDFViewer.nextPageButton.disabled = (PDFViewer.pageNumber === PDFViewer.numberOfPages); } }; @@ -338,35 +328,35 @@ window.onload = function() { var qs = window.location.search.substring(1); var kvs = qs.split('&'); var params = {}; - + for (var i = 0; i < kvs.length; ++i) { var kv = kvs[i].split('='); params[unescape(kv[0])] = unescape(kv[1]); } - + return params; }(); PDFViewer.element = document.getElementById('viewer'); - + PDFViewer.sidebarContentView = document.getElementById('sidebarContentView'); - + PDFViewer.pageNumberInput = document.getElementById('pageNumber'); PDFViewer.pageNumberInput.onkeydown = function(evt) { var charCode = evt.charCode || evt.keyCode; - + // Up arrow key. if (charCode === 38) { PDFViewer.goToNextPage(); this.select(); } - + // Down arrow key. else if (charCode === 40) { PDFViewer.goToPreviousPage(); this.select(); } - + // All other non-numeric keys (excluding Left arrow, Right arrow, // Backspace, and Delete keys). else if ((charCode < 48 || charCode > 57) && @@ -377,12 +367,12 @@ window.onload = function() { ) { return false; } - + return true; }; PDFViewer.pageNumberInput.onkeyup = function(evt) { var charCode = evt.charCode || evt.keyCode; - + // All numeric keys, Backspace, and Delete. if ((charCode >= 48 && charCode <= 57) || charCode === 8 || // Backspace @@ -390,93 +380,81 @@ window.onload = function() { ) { PDFViewer.goToPage(this.value); } - + this.focus(); }; - + PDFViewer.previousPageButton = document.getElementById('previousPageButton'); PDFViewer.previousPageButton.onclick = function(evt) { - if (this.className.indexOf('disabled') === -1) { - PDFViewer.goToPreviousPage(); - } + PDFViewer.goToPreviousPage(); }; PDFViewer.previousPageButton.onmousedown = function(evt) { - if (this.className.indexOf('disabled') === -1) { - this.className = 'down'; - } + this.className = 'down'; }; PDFViewer.previousPageButton.onmouseup = function(evt) { - this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; + this.className = ''; }; PDFViewer.previousPageButton.onmouseout = function(evt) { - this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; + this.className = ''; }; - + PDFViewer.nextPageButton = document.getElementById('nextPageButton'); PDFViewer.nextPageButton.onclick = function(evt) { - if (this.className.indexOf('disabled') === -1) { - PDFViewer.goToNextPage(); - } + PDFViewer.goToNextPage(); }; PDFViewer.nextPageButton.onmousedown = function(evt) { - if (this.className.indexOf('disabled') === -1) { - this.className = 'down'; - } + this.className = 'down'; }; PDFViewer.nextPageButton.onmouseup = function(evt) { - this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; + this.className = ''; }; PDFViewer.nextPageButton.onmouseout = function(evt) { - this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; + this.className = ''; }; - + PDFViewer.scaleSelect = document.getElementById('scaleSelect'); PDFViewer.scaleSelect.onchange = function(evt) { PDFViewer.changeScale(parseInt(this.value)); }; - + if (window.File && window.FileReader && window.FileList && window.Blob) { var openFileButton = document.getElementById('openFileButton'); openFileButton.onclick = function(evt) { - if (this.className.indexOf('disabled') === -1) { - PDFViewer.fileInput.click(); - } + PDFViewer.fileInput.click(); }; openFileButton.onmousedown = function(evt) { - if (this.className.indexOf('disabled') === -1) { - this.className = 'down'; - } + this.className = 'down'; }; openFileButton.onmouseup = function(evt) { - this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; + this.className = ''; }; openFileButton.onmouseout = function(evt) { - this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; + this.className = ''; }; - + PDFViewer.fileInput = document.getElementById('fileInput'); PDFViewer.fileInput.onchange = function(evt) { var files = evt.target.files; - + if (files.length > 0) { var file = files[0]; var fileReader = new FileReader(); - + document.title = file.name; - + // Read the local file into a Uint8Array. fileReader.onload = function(evt) { var data = evt.target.result; var buffer = new ArrayBuffer(data.length); var uint8Array = new Uint8Array(buffer); - + for (var i = 0; i < data.length; i++) { uint8Array[i] = data.charCodeAt(i); } - + PDFViewer.readPDF(uint8Array); }; - + // Read as a binary string since "readAsArrayBuffer" is not yet // implemented in Firefox. fileReader.readAsBinaryString(file); @@ -486,22 +464,23 @@ window.onload = function() { } else { document.getElementById('fileWrapper').style.display = 'none'; } - - PDFViewer.pageNumber = parseInt(PDFViewer.queryParams.page) || PDFViewer.pageNumber; + + PDFViewer.pageNumber = + parseInt(PDFViewer.queryParams.page) || PDFViewer.pageNumber; PDFViewer.scale = parseInt(PDFViewer.scaleSelect.value) / 100 || 1.0; - + PDFViewer.openURL(PDFViewer.queryParams.file || PDFViewer.url); - - window.onscroll = function(evt) { + + window.onscroll = function(evt) { var lastPagesDrawn = PDFViewer.lastPagesDrawn; var visiblePages = PDFViewer.visiblePages(); - + var pagesToDraw = []; var pagesToKeep = []; var pagesToRemove = []; - + var i; - + // Determine which visible pages were not previously drawn. for (i = 0; i < visiblePages.length; i++) { if (lastPagesDrawn.indexOf(visiblePages[i]) === -1) { @@ -511,7 +490,7 @@ window.onload = function() { pagesToKeep.push(visiblePages[i]); } } - + // Determine which previously drawn pages are no longer visible. for (i = 0; i < lastPagesDrawn.length; i++) { if (visiblePages.indexOf(lastPagesDrawn[i]) === -1) { @@ -519,14 +498,14 @@ window.onload = function() { PDFViewer.removePage(lastPagesDrawn[i]); } } - + PDFViewer.lastPagesDrawn = pagesToDraw.concat(pagesToKeep); - + // Update the page number input with the current page number. if (!PDFViewer.willJumpToPage && visiblePages.length > 0) { PDFViewer.pageNumber = PDFViewer.pageNumberInput.value = visiblePages[0]; - PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ? 'disabled' : ''; - PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ? 'disabled' : ''; + PDFViewer.previousPageButton.disabled = (PDFViewer.pageNumber === 1); + PDFViewer.nextPageButton.disabled = (PDFViewer.pageNumber === PDFViewer.numberOfPages); } else { PDFViewer.willJumpToPage = false; } diff --git a/viewer.css b/web/viewer.css similarity index 100% rename from viewer.css rename to web/viewer.css diff --git a/viewer.html b/web/viewer.html similarity index 71% rename from viewer.html rename to web/viewer.html index c600547f0..df9604db4 100644 --- a/viewer.html +++ b/web/viewer.html @@ -1,14 +1,15 @@ + Simple pdf.js page viewer + - - - - - + + + + diff --git a/web/viewer.js b/web/viewer.js new file mode 100644 index 000000000..0e7cd59db --- /dev/null +++ b/web/viewer.js @@ -0,0 +1,99 @@ +/* -*- 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 pdfDocument, canvas, pageScale, pageDisplay, pageNum, numPages; +function load(userInput) { + canvas = document.getElementById('canvas'); + canvas.mozOpaque = true; + pageNum = ('page' in queryParams()) ? parseInt(queryParams().page) : 1; + pageScale = ('scale' in queryParams()) ? parseInt(queryParams().scale) : 1.5; + var fileName = userInput; + if (!userInput) { + fileName = queryParams().file || 'compressed.tracemonkey-pldi-09.pdf'; + } + open(fileName); +} + +function queryParams() { + var qs = window.location.search.substring(1); + var kvs = qs.split('&'); + var params = { }; + for (var i = 0; i < kvs.length; ++i) { + var kv = kvs[i].split('='); + params[unescape(kv[0])] = unescape(kv[1]); + } + return params; +} + +function open(url) { + document.title = url; + var req = new XMLHttpRequest(); + req.open('GET', url); + req.mozResponseType = req.responseType = 'arraybuffer'; + req.expected = (document.URL.indexOf('file:') == 0) ? 0 : 200; + req.onreadystatechange = function() { + if (req.readyState == 4 && req.status == req.expected) { + var data = (req.mozResponseArrayBuffer || req.mozResponse || + req.responseArrayBuffer || req.response); + pdfDocument = new PDFDoc(new Stream(data)); + numPages = pdfDocument.numPages; + document.getElementById('numPages').innerHTML = numPages.toString(); + goToPage(pageNum); + } + }; + req.send(null); +} + +function gotoPage(num) { + if (0 <= num && num <= numPages) + pageNum = num; + displayPage(pageNum); +} + +function displayPage(num) { + document.getElementById('pageNumber').value = num; + + var t0 = Date.now(); + + var page = pdfDocument.getPage(pageNum = num); + + var pdfToCssUnitsCoef = 96.0 / 72.0; + var pageWidth = (page.mediaBox[2] - page.mediaBox[0]); + var pageHeight = (page.mediaBox[3] - page.mediaBox[1]); + canvas.width = pageScale * pageWidth * pdfToCssUnitsCoef; + canvas.height = pageScale * pageHeight * pdfToCssUnitsCoef; + + var t1 = Date.now(); + var ctx = canvas.getContext('2d'); + ctx.save(); + ctx.fillStyle = 'rgb(255, 255, 255)'; + ctx.fillRect(0, 0, canvas.width, canvas.height); + ctx.restore(); + + page.startRendering( + ctx, + function() { + var infoDisplay = document.getElementById('info'); + var stats = page.stats; + var t2 = stats.compile, t3 = stats.fonts, t4 = stats.render; + infoDisplay.innerHTML = 'Time to load/compile/fonts/render: ' + + (t1 - t0) + '/' + (t2 - t1) + '/' + (t3 - t2) + '/' + (t4 - t3) + ' ms'; + }); +} + +function nextPage() { + if (pageNum < pdfDocument.numPages) + displayPage(++pageNum); +} + +function prevPage() { + if (pageNum > 1) + displayPage(--pageNum); +} + +function goToPage(num) { + if (0 <= num && num <= numPages) + displayPage(pageNum = num); +} diff --git a/viewer_worker.html b/web/viewer_worker.html similarity index 82% rename from viewer_worker.html rename to web/viewer_worker.html index 89fb8a087..21a5be3ca 100644 --- a/viewer_worker.html +++ b/web/viewer_worker.html @@ -1,10 +1,10 @@ Simple pdf.js page worker viewer - - - - + + + +