diff --git a/.gitignore b/.gitignore index 9e2d0f211..bb045de3e 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ pdf.pdf intelisa.pdf openweb_tm-PRINT.pdf local.mk +build/ + diff --git a/Makefile b/Makefile index 959681f3a..80003bdf6 100644 --- a/Makefile +++ b/Makefile @@ -1,36 +1,39 @@ REPO = git@github.com:andreasgal/pdf.js.git BUILD_DIR := build +BUILD_TARGET := $(BUILD_DIR)/pdf.js DEFAULT_BROWSERS := resources/browser_manifests/browser_manifest.json DEFAULT_TESTS := test_manifest.json EXTENSION_SRC := ./extensions/firefox EXTENSION_NAME := pdf.js.xpi +all: bundle + # 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 \ - metrics.js \ - charsets.js \ - glyphlist.js \ - cidmaps.js \ + core.js \ + util.js \ + canvas.js \ + obj.js \ + function.js \ + charsets.js \ + cidmaps.js \ + colorspace.js \ + crypto.js \ + evaluator.js \ + fonts.js \ + glyphlist.js \ + image.js \ + metrics.js \ + parser.js \ + pattern.js \ + stream.js \ + worker.js \ $(NULL) -PDF_WORKER_FILES = \ - worker/console.js \ - worker/message_handler.js \ - worker/pdf_worker_loader.js \ - worker/processor_handler.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 @@ -40,6 +43,28 @@ server: test: shell-test browser-test +# +# Create production output (pdf.js, and corresponding changes to web files) +# +production: | bundle + @echo "Preparing web/viewer-production.html"; \ + cd web; \ + sed '/PDFJSSCRIPT_REMOVE/d' viewer.html > viewer-1.tmp; \ + sed '/PDFJSSCRIPT_INCLUDE_BUILD/ r viewer-snippet.html' viewer-1.tmp > viewer-production.html; \ + rm -f *.tmp; \ + cd .. + +# +# Bundle pdf.js +# +bundle: | $(BUILD_DIR) + @echo "Bundling source files into $(BUILD_TARGET)" + @cd src; \ + cat $(PDF_JS_FILES) > all_files.tmp; \ + sed '/PDFJSSCRIPT_INCLUDE_ALL/ r all_files.tmp' pdf.js > ../$(BUILD_TARGET); \ + rm -f *.tmp; \ + cd .. + # make browser-test # # This target runs in-browser tests using two primary arguments: a @@ -99,11 +124,11 @@ browser-test: # To install gjslint, see: # # -SRC_DIRS := . utils worker web test examples/helloworld extensions/firefox \ +SRC_DIRS := . src utils web test examples/helloworld extensions/firefox \ extensions/firefox/components GJSLINT_FILES = $(foreach DIR,$(SRC_DIRS),$(wildcard $(DIR)/*.js)) lint: - gjslint $(GJSLINT_FILES) + gjslint --nojsdoc $(GJSLINT_FILES) # make web # @@ -114,14 +139,14 @@ lint: # TODO: Use the Closure compiler to optimize the pdf.js files. # GH_PAGES = $(BUILD_DIR)/gh-pages -web: | extension compiler pages-repo \ - $(addprefix $(GH_PAGES)/, $(PDF_JS_FILES)) \ - $(addprefix $(GH_PAGES)/, $(PDF_WORKER_FILES)) \ +web: | production extension compiler pages-repo \ + $(addprefix $(GH_PAGES)/, $(BUILD_TARGET)) \ $(addprefix $(GH_PAGES)/, $(wildcard web/*.*)) \ $(addprefix $(GH_PAGES)/, $(wildcard web/images/*.*)) \ $(addprefix $(GH_PAGES)/, $(wildcard $(EXTENSION_SRC)/*.xpi)) @cp $(GH_PAGES)/web/index.html.template $(GH_PAGES)/index.html; + @mv -f $(GH_PAGES)/web/viewer-production.html $(GH_PAGES)/web/viewer.html; @cd $(GH_PAGES); git add -A; @echo @echo "Website built in $(GH_PAGES)." @@ -139,15 +164,12 @@ pages-repo: | $(BUILD_DIR) git clone -b gh-pages $(REPO) $(GH_PAGES); \ rm -rf $(GH_PAGES)/*; \ fi; - @mkdir -p $(GH_PAGES)/worker; @mkdir -p $(GH_PAGES)/web; @mkdir -p $(GH_PAGES)/web/images; + @mkdir -p $(GH_PAGES)/build; @mkdir -p $(GH_PAGES)/$(EXTENSION_SRC); -$(GH_PAGES)/%.js: %.js - @cp $< $@ - -$(GH_PAGES)/worker/%: worker/% +$(GH_PAGES)/$(BUILD_DIR)/%.js: build/%.js @cp $< $@ $(GH_PAGES)/web/%: web/% @@ -182,16 +204,16 @@ PDF_WEB_FILES = \ web/compatibility.js \ web/viewer.css \ web/viewer.js \ - web/viewer.html \ + web/viewer-production.html \ $(NULL) -extension: +extension: | production # Copy a standalone version of pdf.js inside the content directory @rm -Rf $(EXTENSION_SRC)/$(CONTENT_DIR)/ + @mkdir -p $(EXTENSION_SRC)/$(CONTENT_DIR)/$(BUILD_DIR) @mkdir -p $(EXTENSION_SRC)/$(CONTENT_DIR)/web - @mkdir -p $(EXTENSION_SRC)/$(CONTENT_DIR)/worker - @cp $(PDF_JS_FILES) $(EXTENSION_SRC)/$(CONTENT_DIR)/ + @cp $(BUILD_TARGET) $(EXTENSION_SRC)/$(CONTENT_DIR)/$(BUILD_DIR) @cp -r $(PDF_WEB_FILES) $(EXTENSION_SRC)/$(CONTENT_DIR)/web/ - @cp -r $(PDF_WORKER_FILES) $(EXTENSION_SRC)/$(CONTENT_DIR)/worker/ + @mv -f $(EXTENSION_SRC)/$(CONTENT_DIR)/web/viewer-production.html $(EXTENSION_SRC)/$(CONTENT_DIR)/web/viewer.html # Create the xpi @cd $(EXTENSION_SRC); zip -r $(EXTENSION_NAME) * @@ -211,5 +233,5 @@ clean: help: @echo "Read the comments in the Makefile for guidance."; -.PHONY:: all test browser-test font-test shell-test \ +.PHONY:: production test browser-test font-test shell-test \ shell-msg lint clean web compiler help server diff --git a/README.md b/README.md index d2dd1f8d7..42669da28 100644 --- a/README.md +++ b/README.md @@ -55,18 +55,20 @@ You can also view all the test pdf files on the right side serving + http://localhost:8888/test/pdfs/?frame +### Building pdf.js + +In order to bundle all `src/` files into a final `pdf.js`, issue: + + $ make + +This will generate the file `build/pdf.js` that can be included in your final project. (WARNING: That's a large file! Consider minifying it). + + ## Learning Here are some initial pointers to help contributors get off the ground. Additional resources are available in a separate section below. -#### Introductory video - -Check out the presentation by our contributor Julian Viereck on the inner -workings of PDF and pdf.js: - -+ http://www.youtube.com/watch?v=Iv15UY-4Fg8 - #### Hello world For a "hello world" example, take a look at: @@ -76,6 +78,14 @@ For a "hello world" example, take a look at: This example illustrates the bare minimum ingredients for integrating pdf.js in a custom project. +#### Introductory video + +Check out the presentation by our contributor Julian Viereck on the inner +workings of PDF and pdf.js: + ++ http://www.youtube.com/watch?v=Iv15UY-4Fg8 + + ## Contributing diff --git a/examples/helloworld/hello.js b/examples/helloworld/hello.js index 9c653ca24..45e61eb6f 100644 --- a/examples/helloworld/hello.js +++ b/examples/helloworld/hello.js @@ -7,11 +7,11 @@ 'use strict'; -getPdf('helloworld.pdf', function getPdfHelloWorld(data) { +PDFJS.getPdf('helloworld.pdf', function getPdfHelloWorld(data) { // // Instantiate PDFDoc with PDF data // - var pdf = new PDFDoc(data); + var pdf = new PDFJS.PDFDoc(data); var page = pdf.getPage(1); var scale = 1.5; diff --git a/examples/helloworld/index.html b/examples/helloworld/index.html index 2aa30b426..b10e9eaeb 100644 --- a/examples/helloworld/index.html +++ b/examples/helloworld/index.html @@ -2,13 +2,25 @@ - - - - - - - + + + + + + + + + + + + + + + + + + + diff --git a/extensions/firefox/.gitignore b/extensions/firefox/.gitignore new file mode 100644 index 000000000..3eb92306c --- /dev/null +++ b/extensions/firefox/.gitignore @@ -0,0 +1 @@ +content/ diff --git a/pdf.js b/pdf.js deleted file mode 100644 index 4a8b9a091..000000000 --- a/pdf.js +++ /dev/null @@ -1,7589 +0,0 @@ -/* -*- 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 ERRORS = 0, WARNINGS = 1, TODOS = 5; -var verbosity = WARNINGS; - -// Set this to true if you want to use workers. -var useWorker = false; - -function log(msg) { - if (console && console.log) - console.log(msg); - else if (print) - print(msg); -} - -function warn(msg) { - if (verbosity >= WARNINGS) - log('Warning: ' + msg); -} - -function backtrace() { - try { - throw new Error(); - } catch (e) { - return e.stack ? e.stack.split('\n').slice(2).join('\n') : ""; - } -} - -function error(msg) { - log("Error: " + msg); - log(backtrace()); - throw new Error(msg); -} - -function TODO(what) { - if (verbosity >= TODOS) - log('TODO: ' + what); -} - -function malformed(msg) { - error('Malformed PDF: ' + msg); -} - -function assert(cond, 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); -} - -function shadow(obj, prop, value) { - 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; -} - -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 PDFStringTranslateTable = [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0x2D8, 0x2C7, 0x2C6, 0x2D9, 0x2DD, 0x2DB, 0x2DA, 0x2DC, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x2022, 0x2020, 0x2021, 0x2026, 0x2014, - 0x2013, 0x192, 0x2044, 0x2039, 0x203A, 0x2212, 0x2030, 0x201E, 0x201C, - 0x201D, 0x2018, 0x2019, 0x201A, 0x2122, 0xFB01, 0xFB02, 0x141, 0x152, 0x160, - 0x178, 0x17D, 0x131, 0x142, 0x153, 0x161, 0x17E, 0, 0x20AC -]; - -function stringToPDFString(str) { - var i, n = str.length, str2 = ''; - if (str[0] === '\xFE' && str[1] === '\xFF') { - // UTF16BE BOM - for (i = 2; i < n; i += 2) - str2 += String.fromCharCode( - (str.charCodeAt(i) << 8) | str.charCodeAt(i + 1)); - } else { - for (i = 0; i < n; ++i) { - var code = PDFStringTranslateTable[str.charCodeAt(i)]; - str2 += code ? String.fromCharCode(code) : str.charAt(i); - } - } - return str2; -} - -// -// getPdf() -// Convenience function to perform binary Ajax GET -// Usage: getPdf('http://...', callback) -// getPdf({ -// url:String , -// [,progress:Function, error:Function] -// }, -// callback) -// -function getPdf(arg, callback) { - var params = arg; - if (typeof arg === 'string') - params = { url: arg }; - - var xhr = new XMLHttpRequest(); - xhr.open('GET', params.url); - xhr.mozResponseType = xhr.responseType = 'arraybuffer'; - xhr.expected = (document.URL.indexOf('file:') === 0) ? 0 : 200; - - if ('progress' in params) - xhr.onprogress = params.progress || undefined; - - if ('error' in params) - xhr.onerror = params.error || undefined; - - xhr.onreadystatechange = function getPdfOnreadystatechange() { - if (xhr.readyState === 4 && xhr.status === xhr.expected) { - var data = (xhr.mozResponseArrayBuffer || xhr.mozResponse || - xhr.responseArrayBuffer || xhr.response); - callback(data); - } - }; - xhr.send(null); -} - -var Stream = (function streamStream() { - 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); - }, - isStream: true - }; - - return constructor; -})(); - -var StringStream = (function stringStream() { - 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; - - return constructor; -})(); - -// super class for the decoding streams -var DecodeStream = (function decodeStream() { - 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 end, pos = this.pos; - - if (length) { - this.ensureBuffer(pos + length); - 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(); - - end = this.bufferLength; - - // checking if bufferLength is still 0 then - // the buffer has to be initialized - if (!end) - this.buffer = new Uint8Array(0); - } - - 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++]); - }, - makeSubStream: function decodestream_makeSubstream(start, length, dict) { - var end = start + length; - while (this.bufferLength <= end && !this.eof) - this.readBlock(); - return new Stream(this.buffer, start, length, dict); - }, - skip: function decodestream_skip(n) { - if (!n) - n = 1; - this.pos += n; - }, - reset: function decodestream_reset() { - this.pos = 0; - } - }; - - return constructor; -})(); - -var FakeStream = (function fakeStream() { - function constructor(stream) { - this.dict = stream.dict; - DecodeStream.call(this); - } - - constructor.prototype = Object.create(DecodeStream.prototype); - constructor.prototype.readBlock = function fakeStreamReadBlock() { - var bufferLength = this.bufferLength; - bufferLength += 1024; - var buffer = this.ensureBuffer(bufferLength); - this.bufferLength = bufferLength; - }; - - constructor.prototype.getBytes = function fakeStreamGetBytes(length) { - var end, pos = this.pos; - - if (length) { - this.ensureBuffer(pos + length); - end = pos + length; - - while (!this.eof && this.bufferLength < end) - this.readBlock(); - - var bufEnd = this.bufferLength; - if (end > bufEnd) - end = bufEnd; - } else { - this.eof = true; - end = this.bufferLength; - } - - this.pos = end; - return this.buffer.subarray(pos, end); - }; - - return constructor; -})(); - -var StreamsSequenceStream = (function streamSequenceStream() { - function constructor(streams) { - this.streams = streams; - DecodeStream.call(this); - } - - constructor.prototype = Object.create(DecodeStream.prototype); - - constructor.prototype.readBlock = function streamSequenceStreamReadBlock() { - var streams = this.streams; - if (streams.length == 0) { - this.eof = true; - return; - } - var stream = streams.shift(); - var chunk = stream.getBytes(); - var bufferLength = this.bufferLength; - var newLength = bufferLength + chunk.length; - var buffer = this.ensureBuffer(newLength); - buffer.set(chunk, bufferLength); - this.bufferLength = newLength; - }; - - return constructor; -})(); - -var FlateStream = (function flateStream() { - 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 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 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; - - this.dict = stream.dict; - var cmf = bytes[bytesPos++]; - var flg = bytes[bytesPos++]; - if (cmf == -1 || flg == -1) - error('Invalid header in flate stream: ' + cmf + ', ' + flg); - if ((cmf & 0x0f) != 0x08) - error('Unknown compression method in flate stream: ' + cmf + ', ' + flg); - if ((((cmf << 8) + flg) % 31) != 0) - error('Bad FCHECK in flate stream: ' + cmf + ', ' + flg); - if (flg & 0x20) - error('FDICT bit set in flate stream: ' + cmf + ', ' + flg); - - this.bytes = bytes; - this.bytesPos = bytesPos; - - this.codeSize = 0; - this.codeBuf = 0; - - DecodeStream.call(this); - } - - constructor.prototype = Object.create(DecodeStream.prototype); - - constructor.prototype.getBits = function flateStreamGetBits(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 flateStreamGetCode(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 flateStreamGenerateHuffmanTable(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]; - } - - // 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; - } - - // 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 flateStreamReadBlock() { - // 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 = new Uint8Array(codeLenCodeMap.length); - - for (var i = 0; i < numCodeLenCodes; ++i) - 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 Uint8Array(codes); - while (i < codes) { - var code = this.getCode(codeLenCodeTab); - if (code == 16) { - var bitsLength = 2, bitsOffset = 3, what = len; - } else if (code == 17) { - var bitsLength = 3, bitsOffset = 3, what = (len = 0); - } else if (code == 18) { - var bitsLength = 7, bitsOffset = 11, what = (len = 0); - } else { - codeLengths[i++] = len = code; - continue; - } - - var repeatLength = this.getBits(bitsLength) + bitsOffset; - while (repeatLength-- > 0) - codeLengths[i++] = what; - } - - litCodeTable = - this.generateHuffmanTable(codeLengths.subarray(0, numLitCodes)); - distCodeTable = - this.generateHuffmanTable(codeLengths.subarray(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; -})(); - -var PredictorStream = (function predictorStream() { - 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: ' + predictor); - - if (predictor === 2) - this.readBlock = this.readBlockTiff; - else - this.readBlock = this.readBlockPng; - - this.stream = stream; - this.dict = stream.dict; - - var colors = this.colors = params.get('Colors') || 1; - var bits = this.bits = params.get('BitsPerComponent') || 8; - var columns = this.columns = params.get('Columns') || 1; - - this.pixBytes = (colors * bits + 7) >> 3; - this.rowBytes = (columns * colors * bits + 7) >> 3; - - DecodeStream.call(this); - return this; - } - - constructor.prototype = Object.create(DecodeStream.prototype); - - constructor.prototype.readBlockTiff = - function predictorStreamReadBlockTiff() { - var rowBytes = this.rowBytes; - - var bufferLength = this.bufferLength; - var buffer = this.ensureBuffer(bufferLength + rowBytes); - var currentRow = buffer.subarray(bufferLength, bufferLength + rowBytes); - - var bits = this.bits; - var colors = this.colors; - - var rawBytes = this.stream.getBytes(rowBytes); - - var inbuf = 0, outbuf = 0; - var inbits = 0, outbits = 0; - - if (bits === 1) { - 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 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 predictorStreamReadBlockPng() { - var rowBytes = this.rowBytes; - var pixBytes = this.pixBytes; - - 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 = new Uint8Array(rowBytes); - - switch (predictor) { - case 0: - for (var i = 0; i < rowBytes; ++i) - currentRow[i] = rawBytes[i]; - 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) { - var up = prevRow[i]; - var c = rawBytes[i]; - currentRow[i] = up + c; - } - for (; i < rowBytes; ++i) { - var up = prevRow[i]; - var upLeft = prevRow[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: ' + predictor); - } - this.bufferLength += rowBytes; - }; - - return constructor; -})(); - -var JpegImage = (function() { - function JpegImage(objId, imageData, objs) { - var src = 'data:image/jpeg;base64,' + window.btoa(imageData); - - var img = new Image(); - img.onload = (function() { - this.loaded = true; - - objs.resolve(objId, this); - - if (this.onLoad) - this.onLoad(); - }).bind(this); - img.src = src; - this.domImage = img; - } - - JpegImage.prototype = { - getImage: function() { - return this.domImage; - } - }; - - return JpegImage; -})(); - -// A JpegStream can't be read directly. We use the platform to render -// the underlying JPEG data for us. -var JpegStream = (function jpegStream() { - function isAdobeImage(bytes) { - var maxBytesScanned = Math.max(bytes.length - 16, 1024); - // Looking for APP14, 'Adobe' - for (var i = 0; i < maxBytesScanned; ++i) { - if (bytes[i] == 0xFF && bytes[i + 1] == 0xEE && - bytes[i + 2] == 0x00 && bytes[i + 3] == 0x0E && - bytes[i + 4] == 0x41 && bytes[i + 5] == 0x64 && - bytes[i + 6] == 0x6F && bytes[i + 7] == 0x62 && - bytes[i + 8] == 0x65 && bytes[i + 9] == 0x00) - return true; - // scanning until frame tag - if (bytes[i] == 0xFF && bytes[i + 1] == 0xC0) - break; - } - return false; - } - - function fixAdobeImage(bytes) { - // Inserting 'EMBED' marker after JPEG signature - var embedMarker = new Uint8Array([0xFF, 0xEC, 0, 8, 0x45, 0x4D, 0x42, 0x45, - 0x44, 0]); - var newBytes = new Uint8Array(bytes.length + embedMarker.length); - newBytes.set(bytes, embedMarker.length); - // copy JPEG header - newBytes[0] = bytes[0]; - newBytes[1] = bytes[1]; - newBytes.set(embedMarker, 2); - return newBytes; - } - - function constructor(bytes, dict) { - // TODO: per poppler, some images may have 'junk' before that - // need to be removed - this.dict = dict; - - if (isAdobeImage(bytes)) - bytes = fixAdobeImage(bytes); - - this.src = bytesToString(bytes); - } - - constructor.prototype = { - getIR: function() { - return this.src; - }, - getChar: function jpegStreamGetChar() { - error('internal error: getChar is not valid on JpegStream'); - } - }; - - return constructor; -})(); - -var DecryptStream = (function decryptStream() { - 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 decryptStreamReadBlock() { - 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; - }; - - return constructor; -})(); - -var Ascii85Stream = (function ascii85Stream() { - 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 ascii85StreamReadBlock() { - 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; - } - - var bufferLength = this.bufferLength, buffer; - - // special code for z - if (c == zCode) { - 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(); - - input[i] = c; - - if (!c || c == tildaCode) - break; - } - buffer = this.ensureBuffer(bufferLength + i - 1); - this.bufferLength += i - 1; - - // 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); - - for (var i = 3; i >= 0; --i) { - buffer[bufferLength + i] = t & 0xFF; - t >>= 8; - } - } - }; - - return constructor; -})(); - -var AsciiHexStream = (function asciiHexStream() { - 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 - }; - - constructor.prototype = Object.create(DecodeStream.prototype); - - constructor.prototype.readBlock = function asciiHexStreamReadBlock() { - 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 { - // EOD marker at an odd number, behave as if a 0 followed the last - // digit. - if (bytes[i] !== gtCode) { - buffer[bufferLength++] = c * 16; - } - } - } - - this.bufferLength = bufferLength; - this.eof = true; - }; - - return constructor; -})(); - -var CCITTFaxStream = (function ccittFaxStream() { - - 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 = [ - [-1, -1], [-1, -1], // 000000x - [7, twoDimVertL3], // 0000010 - [7, twoDimVertR3], // 0000011 - [6, twoDimVertL2], [6, twoDimVertL2], // 000010x - [6, twoDimVertR2], [6, twoDimVertR2], // 000011x - [4, twoDimPass], [4, twoDimPass], // 0001xxx - [4, twoDimPass], [4, twoDimPass], - [4, twoDimPass], [4, twoDimPass], - [4, twoDimPass], [4, twoDimPass], - [3, twoDimHoriz], [3, twoDimHoriz], // 001xxxx - [3, twoDimHoriz], [3, twoDimHoriz], - [3, twoDimHoriz], [3, twoDimHoriz], - [3, twoDimHoriz], [3, twoDimHoriz], - [3, twoDimHoriz], [3, twoDimHoriz], - [3, twoDimHoriz], [3, twoDimHoriz], - [3, twoDimHoriz], [3, twoDimHoriz], - [3, twoDimHoriz], [3, twoDimHoriz], - [3, twoDimVertL1], [3, twoDimVertL1], // 010xxxx - [3, twoDimVertL1], [3, twoDimVertL1], - [3, twoDimVertL1], [3, twoDimVertL1], - [3, twoDimVertL1], [3, twoDimVertL1], - [3, twoDimVertL1], [3, twoDimVertL1], - [3, twoDimVertL1], [3, twoDimVertL1], - [3, twoDimVertL1], [3, twoDimVertL1], - [3, twoDimVertL1], [3, twoDimVertL1], - [3, twoDimVertR1], [3, twoDimVertR1], // 011xxxx - [3, twoDimVertR1], [3, twoDimVertR1], - [3, twoDimVertR1], [3, twoDimVertR1], - [3, twoDimVertR1], [3, twoDimVertR1], - [3, twoDimVertR1], [3, twoDimVertR1], - [3, twoDimVertR1], [3, twoDimVertR1], - [3, twoDimVertR1], [3, twoDimVertR1], - [3, twoDimVertR1], [3, twoDimVertR1], - [1, twoDimVert0], [1, twoDimVert0], // 1xxxxxx - [1, twoDimVert0], [1, twoDimVert0], - [1, twoDimVert0], [1, twoDimVert0], - [1, twoDimVert0], [1, twoDimVert0], - [1, twoDimVert0], [1, twoDimVert0], - [1, twoDimVert0], [1, twoDimVert0], - [1, twoDimVert0], [1, twoDimVert0], - [1, twoDimVert0], [1, twoDimVert0], - [1, twoDimVert0], [1, twoDimVert0], - [1, twoDimVert0], [1, twoDimVert0], - [1, twoDimVert0], [1, twoDimVert0], - [1, twoDimVert0], [1, twoDimVert0], - [1, twoDimVert0], [1, twoDimVert0], - [1, twoDimVert0], [1, twoDimVert0], - [1, twoDimVert0], [1, twoDimVert0], - [1, twoDimVert0], [1, twoDimVert0], - [1, twoDimVert0], [1, twoDimVert0], - [1, twoDimVert0], [1, twoDimVert0], - [1, twoDimVert0], [1, twoDimVert0], - [1, twoDimVert0], [1, twoDimVert0], - [1, twoDimVert0], [1, twoDimVert0], - [1, twoDimVert0], [1, twoDimVert0], - [1, twoDimVert0], [1, twoDimVert0], - [1, twoDimVert0], [1, twoDimVert0], - [1, twoDimVert0], [1, twoDimVert0], - [1, twoDimVert0], [1, twoDimVert0], - [1, twoDimVert0], [1, twoDimVert0], - [1, twoDimVert0], [1, twoDimVert0], - [1, twoDimVert0], [1, twoDimVert0], - [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 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 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 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] - ]; - - function constructor(str, params) { - this.str = str; - this.dict = str.dict; - - params = params || new 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; - - 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 = 0; - 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 ccittFaxStreamReadBlock() { - while (!this.eof) { - var c = this.lookChar(); - this.buf = EOF; - this.ensureBuffer(this.bufferLength + 1); - this.buffer[this.bufferLength++] = c; - } - }; - - constructor.prototype.addPixels = - function ccittFaxStreamAddPixels(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 ccittFaxStreamAddPixelsNeg(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 ccittFaxStreamLookChar() { - if (this.buf != EOF) - return this.buf; - - var refLine = this.refLine; - var codingLine = this.codingLine; - var columns = this.columns; - - var refPos, blackPixels, bits; - - if (this.outputBits == 0) { - if (this.eof) - return null; - - this.err = false; - - var code1, code2, code3; - 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) { - code1 = this.getTwoDimCode(); - switch (code1) { - case twoDimPass: - this.addPixels(refLine[refPos + 1], blackPixels); - if (refLine[refPos + 1] < columns) - refPos += 2; - break; - case twoDimHoriz: - code1 = code2 = 0; - if (blackPixels) { - do { - code1 += (code3 = this.getBlackCode()); - } while (code3 >= 64); - do { - code2 += (code3 = this.getWhiteCode()); - } while (code3 >= 64); - } else { - 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; - } - } - } 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); - gotEOL = true; - } else if (code1 == EOF) { - this.eof = true; - } - } - - 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); - } - if (this.encoding >= 0) { - for (var i = 0; i < 4; ++i) { - code1 = this.lookBits(12); - if (code1 != 1) - warn('bad rtc code: ' + code1); - this.eatBits(12); - if (this.encoding > 0) { - this.lookBits(1); - this.eatBits(1); - } - } - } - this.eof = true; - } - } else if (this.err && this.eoline) { - while (true) { - code1 = this.lookBits(13); - if (code1 == EOF) { - this.eof = true; - return null; - } - 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++; - } - - 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; - }; - - // This functions returns the code found from the table. - // The start and end parameters set the boundaries for searching the table. - // The limit parameter is optional. Function returns an array with three - // values. The first array element indicates whether a valid code is being - // returned. The second array element is the actual code. The third array - // element indicates whether EOF was reached. - var findTableCode = function ccittFaxStreamFindTableCode(start, end, table, - limit) { - var limitValue = limit || 0; - - for (var i = start; i <= end; ++i) { - var code = this.lookBits(i); - if (code == EOF) - return [true, 1, false]; - if (i < end) - code <<= end - i; - if (!limitValue || code >= limitValue) { - var p = table[code - limitValue]; - if (p[0] == i) { - this.eatBits(i); - return [true, p[1], true]; - } - } - } - return [false, 0, false]; - }; - - constructor.prototype.getTwoDimCode = function ccittFaxStreamGetTwoDimCode() { - 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 { - var result = findTableCode(1, 7, twoDimTable); - if (result[0] && result[2]) - return result[1]; - } - warn('Bad two dim code'); - return EOF; - }; - - constructor.prototype.getWhiteCode = function ccittFaxStreamGetWhiteCode() { - 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 { - var result = findTableCode(1, 9, whiteTable2); - if (result[0]) - return result[1]; - - result = findTableCode(11, 12, whiteTable1); - if (result[0]) - return result[1]; - } - warn('bad white code'); - this.eatBits(1); - return 1; - }; - - constructor.prototype.getBlackCode = function ccittFaxStreamGetBlackCode() { - var code, p; - 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 { - var result = findTableCode(2, 6, blackTable3); - if (result[0]) - return result[1]; - - result = findTableCode(7, 12, blackTable2, 64); - if (result[0]) - return result[1]; - - result = findTableCode(10, 13, blackTable1); - if (result[0]) - return result[1]; - } - warn('bad black code'); - this.eatBits(1); - return 1; - }; - - constructor.prototype.lookBits = function ccittFaxStreamLookBits(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 ccittFaxStreamEatBits(n) { - if ((this.inputBits -= n) < 0) - this.inputBits = 0; - }; - - return constructor; -})(); - -var LZWStream = (function lzwStream() { - function constructor(str, earlyChange) { - this.str = str; - this.dict = str.dict; - this.cachedData = 0; - this.bitsCached = 0; - - var maxLzwDictionarySize = 4096; - var lzwState = { - earlyChange: earlyChange, - codeLength: 9, - nextCode: 258, - dictionaryValues: new Uint8Array(maxLzwDictionarySize), - dictionaryLengths: new Uint16Array(maxLzwDictionarySize), - dictionaryPrevCodes: new Uint16Array(maxLzwDictionarySize), - currentSequence: new Uint8Array(maxLzwDictionarySize), - currentSequenceLength: 0 - }; - for (var i = 0; i < 256; ++i) { - lzwState.dictionaryValues[i] = i; - lzwState.dictionaryLengths[i] = 1; - } - this.lzwState = lzwState; - - DecodeStream.call(this); - } - - constructor.prototype = Object.create(DecodeStream.prototype); - - constructor.prototype.readBits = function lzwStreamReadBits(n) { - var bitsCached = this.bitsCached; - var cachedData = this.cachedData; - while (bitsCached < n) { - var c = this.str.getByte(); - if (c == null) { - this.eof = true; - return null; - } - cachedData = (cachedData << 8) | c; - bitsCached += 8; - } - this.bitsCached = (bitsCached -= n); - this.cachedData = cachedData; - this.lastCode = null; - return (cachedData >>> bitsCached) & ((1 << n) - 1); - }; - - constructor.prototype.readBlock = function lzwStreamReadBlock() { - var blockSize = 512; - var estimatedDecodedSize = blockSize * 2, decodedSizeDelta = blockSize; - var i, j, q; - - var lzwState = this.lzwState; - if (!lzwState) - return; // eof was found - - var earlyChange = lzwState.earlyChange; - var nextCode = lzwState.nextCode; - var dictionaryValues = lzwState.dictionaryValues; - var dictionaryLengths = lzwState.dictionaryLengths; - var dictionaryPrevCodes = lzwState.dictionaryPrevCodes; - var codeLength = lzwState.codeLength; - var prevCode = lzwState.prevCode; - var currentSequence = lzwState.currentSequence; - var currentSequenceLength = lzwState.currentSequenceLength; - - var decodedLength = 0; - var currentBufferLength = this.bufferLength; - var buffer = this.ensureBuffer(this.bufferLength + estimatedDecodedSize); - - for (i = 0; i < blockSize; i++) { - var code = this.readBits(codeLength); - var hasPrev = currentSequenceLength > 0; - if (code < 256) { - currentSequence[0] = code; - currentSequenceLength = 1; - } else if (code >= 258) { - if (code < nextCode) { - currentSequenceLength = dictionaryLengths[code]; - for (j = currentSequenceLength - 1, q = code; j >= 0; j--) { - currentSequence[j] = dictionaryValues[q]; - q = dictionaryPrevCodes[q]; - } - } else { - currentSequence[currentSequenceLength++] = currentSequence[0]; - } - } else if (code == 256) { - codeLength = 9; - nextCode = 258; - currentSequenceLength = 0; - continue; - } else { - this.eof = true; - delete this.lzwState; - break; - } - - if (hasPrev) { - dictionaryPrevCodes[nextCode] = prevCode; - dictionaryLengths[nextCode] = dictionaryLengths[prevCode] + 1; - dictionaryValues[nextCode] = currentSequence[0]; - nextCode++; - codeLength = (nextCode + earlyChange) & (nextCode + earlyChange - 1) ? - codeLength : Math.min(Math.log(nextCode + earlyChange) / - 0.6931471805599453 + 1, 12) | 0; - } - prevCode = code; - - decodedLength += currentSequenceLength; - if (estimatedDecodedSize < decodedLength) { - do { - estimatedDecodedSize += decodedSizeDelta; - } while (estimatedDecodedSize < decodedLength); - buffer = this.ensureBuffer(this.bufferLength + estimatedDecodedSize); - } - for (j = 0; j < currentSequenceLength; j++) - buffer[currentBufferLength++] = currentSequence[j]; - } - lzwState.nextCode = nextCode; - lzwState.codeLength = codeLength; - lzwState.prevCode = prevCode; - lzwState.currentSequenceLength = currentSequenceLength; - - this.bufferLength = currentBufferLength; - }; - - return constructor; -})(); - - -var Name = (function nameName() { - function constructor(name) { - this.name = name; - } - - constructor.prototype = { - }; - - return constructor; -})(); - -var Cmd = (function cmdCmd() { - function constructor(cmd) { - this.cmd = cmd; - } - - constructor.prototype = { - }; - - return constructor; -})(); - -var Dict = (function dictDict() { - function constructor() { - this.map = Object.create(null); - } - - constructor.prototype = { - get: function dictGet(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 dictSet(key, value) { - this.map[key] = value; - }, - - has: function dictHas(key) { - return key in this.map; - }, - - forEach: function dictForEach(callback) { - for (var key in this.map) { - callback(key, this.map[key]); - } - } - }; - - return constructor; -})(); - -var Ref = (function refRef() { - function constructor(num, gen) { - this.num = num; - this.gen = gen; - } - - constructor.prototype = { - }; - - return constructor; -})(); - -// The reference is identified by number and generation, -// this structure stores only one instance of the reference. -var RefSet = (function refSet() { - function constructor() { - this.dict = {}; - } - - constructor.prototype = { - has: function refSetHas(ref) { - return !!this.dict['R' + ref.num + '.' + ref.gen]; - }, - - put: function refSetPut(ref) { - this.dict['R' + ref.num + '.' + ref.gen] = ref; - } - }; - - return constructor; -})(); - -function isBool(v) { - return typeof v == 'boolean'; -} - -function isInt(v) { - return typeof v == 'number' && ((v | 0) == v); -} - -function isNum(v) { - return typeof v == 'number'; -} - -function isString(v) { - return typeof v == 'string'; -} - -function isNull(v) { - return v === null; -} - -function isName(v) { - return v instanceof Name; -} - -function isCmd(v, cmd) { - return v instanceof Cmd && (!cmd || v.cmd == cmd); -} - -function isDict(v, type) { - return v instanceof Dict && (!type || v.get('Type').name == type); -} - -function isArray(v) { - return v instanceof Array; -} - -function isStream(v) { - return typeof v == 'object' && v != null && ('getChar' in v); -} - -function isArrayBuffer(v) { - return typeof v == 'object' && v != null && ('byteLength' in v); -} - -function isRef(v) { - 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 EOF = {}; - -function isEOF(v) { - return v == EOF; -} - -var None = {}; - -function isNone(v) { - return v == None; -} - -var Lexer = (function lexer() { - function constructor(stream) { - this.stream = stream; - } - - constructor.isSpace = function lexerIsSpace(ch) { - return ch == ' ' || ch == '\t' || ch == '\x0d' || ch == '\x0a'; - }; - - // 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 - ]; - - 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 lexerGetNumber(ch) { - var floating = false; - var str = ch; - var stream = this.stream; - for (;;) { - 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(); - } - var value = parseFloat(str); - if (isNaN(value)) - error('Invalid floating point number: ' + value); - return value; - }, - getString: function lexerGetString() { - var numParen = 1; - var done = false; - var str = ''; - var stream = this.stream; - var ch; - do { - ch = stream.getChar(); - switch (ch) { - case undefined: - warn('Unterminated string'); - done = true; - break; - case '(': - ++numParen; - str += ch; - break; - case ')': - if (--numParen == 0) { - done = true; - } else { - str += ch; - } - break; - case '\\': - ch = stream.getChar(); - switch (ch) { - 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; - default: - str += ch; - } - } while (!done); - return str; - }, - getName: function lexerGetName(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: ' + x2); - 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: ' + - str.length); - return new Name(str); - }, - getHexString: function lexerGetHexString(ch) { - var str = ''; - var stream = this.stream; - for (;;) { - 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) - error('Illegal character in hex string: ' + ch); - - ch = stream.getChar(); - while (specialChars[ch.charCodeAt(0)] == 1) - ch = stream.getChar(); - - if ((x2 = toHexDigit(ch)) == -1) - error('Illegal character in hex string: ' + ch); - - str += String.fromCharCode((x << 4) | x2); - } - } - return str; - }, - getObj: function lexerGetObj() { - // 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: ' + ch); - 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: ' + str.length); - break; - } - str += ch; - } - if (str == 'true') - return true; - if (str == 'false') - return false; - if (str == 'null') - return null; - return new Cmd(str); - }, - skipToNextLine: function lexerSkipToNextLine() { - 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; - } - } - }, - skip: function lexerSkip() { - this.stream.skip(); - } - }; - - return constructor; -})(); - -var Parser = (function parserParser() { - function constructor(lexer, allowStreams, xref) { - this.lexer = lexer; - this.allowStreams = allowStreams; - this.xref = xref; - this.inlineImg = 0; - this.refill(); - } - - constructor.prototype = { - refill: function parserRefill() { - this.buf1 = this.lexer.getObj(); - this.buf2 = this.lexer.getObj(); - }, - shift: function parserShift() { - if (isCmd(this.buf2, 'ID')) { - this.buf1 = this.buf2; - this.buf2 = null; - // skip byte after ID - this.lexer.skip(); - } else { - this.buf1 = this.buf2; - this.buf2 = this.lexer.getObj(); - } - }, - getObj: function parserGetObj(cipherTransform) { - if (isCmd(this.buf1, 'BI')) { // inline image - this.shift(); - return this.makeInlineImage(cipherTransform); - } - 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; - } - 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'); - } 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 (isCmd(this.buf2, 'stream')) { - return this.allowStreams ? - this.makeStream(dict, cipherTransform) : dict; - } - this.shift(); - return dict; - } - 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; - } - 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; - }, - makeInlineImage: function parserMakeInlineImage(cipherTransform) { - var lexer = this.lexer; - var stream = lexer.stream; - - // parse dictionary - var dict = new Dict(); - while (!isCmd(this.buf1, 'ID') && !isEOF(this.buf1)) { - if (!isName(this.buf1)) { - error('Dictionary key must be a name object'); - } else { - var key = this.buf1.name; - this.shift(); - if (isEOF(this.buf1)) - break; - dict.set(key, this.getObj(cipherTransform)); - } - } - - // parse image stream - var startPos = stream.pos; - - // searching for the /\sEI\s/ - var state = 0, ch; - while (state != 4 && (ch = stream.getByte()) != null) { - switch (ch) { - case 0x20: - case 0x0D: - case 0x0A: - state = state === 3 ? 4 : 1; - break; - case 0x45: - state = state === 1 ? 2 : 0; - break; - case 0x49: - state = state === 2 ? 3 : 0; - break; - default: - state = 0; - break; - } - } - - // TODO improve the small images performance to remove the limit - var inlineImgLimit = 500; - if (++this.inlineImg >= inlineImgLimit) { - if (this.inlineImg === inlineImgLimit) - warn('Too many inline images'); - this.shift(); - return null; - } - - var length = (stream.pos - 4) - startPos; - var imageStream = stream.makeSubStream(startPos, length, dict); - if (cipherTransform) - imageStream = cipherTransform.createStream(imageStream); - imageStream = this.filter(imageStream, dict, length); - imageStream.parameters = dict; - - this.buf2 = new Cmd('EI'); - this.shift(); - - return imageStream; - }, - makeStream: function parserMakeStream(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 parserFilter(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: ' + filter); - else { - params = null; - if (isArray(paramsArray) && (i in paramsArray)) - params = paramsArray[i]; - stream = this.makeFilter(stream, filter.name, length, params); - // after the first stream the length variable is invalid - length = null; - } - } - } - return stream; - }, - makeFilter: function parserMakeFilter(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 == 'LZWDecode' || name == 'LZW') { - var earlyChange = 1; - if (params) { - if (params.has('EarlyChange')) - earlyChange = params.get('EarlyChange'); - return new PredictorStream( - new LZWStream(stream, earlyChange), params); - } - return new LZWStream(stream, earlyChange); - } else if (name == 'DCTDecode' || name == 'DCT') { - var bytes = stream.getBytes(length); - return new JpegStream(bytes, stream.dict); - } else if (name == 'ASCII85Decode' || name == 'A85') { - return new Ascii85Stream(stream); - } else if (name == 'ASCIIHexDecode' || name == 'AHx') { - return new AsciiHexStream(stream); - } else if (name == 'CCITTFaxDecode' || name == 'CCF') { - return new CCITTFaxStream(stream, params); - } else { - TODO('filter "' + name + '" not supported yet'); - } - return stream; - } - }; - - return constructor; -})(); - -var Linearization = (function linearizationLinearization() { - 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 linearizationGetInt(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 linearizationGetHint(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: ' + index); - 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; -})(); - -var XRef = (function xRefXRef() { - function constructor(stream, startXRef, mainXRefEntriesOffset) { - this.stream = stream; - this.entries = []; - this.xrefstms = {}; - var trailerDict = this.readXRef(startXRef); - - // prepare the XRef cache - this.cache = []; - - var encrypt = trailerDict.get('Encrypt'); - if (encrypt) { - var fileId = trailerDict.get('ID'); - this.encrypt = new CipherTransformFactory(this.fetch(encrypt), - fileId[0] /*, password */); - } - - // get the root dictionary (catalog) object - if (!isRef(this.root = trailerDict.get('Root'))) - error('Invalid root reference'); - } - - 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: ' + first + ', ' + n); - for (var i = first; i < first + n; ++i) { - var entry = {}; - if (!isInt(obj = parser.getObj())) - error('Invalid XRef table: ' + first + ', ' + n); - entry.offset = obj; - if (!isInt(obj = parser.getObj())) - error('Invalid XRef table: ' + first + ', ' + n); - 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: ' + first + ', ' + n); - } - 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; - } - } - } - - // 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; - // ignore previously loaded xref streams (possible infinite recursion) - if (!(pos in this.xrefstms)) { - this.xrefstms[pos] = 1; - this.readXRef(pos); - } - } - - return dict; - }, - readXRefStream: function readXRefStream(stream) { - var streamParameters = stream.parameters; - 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: ' + first + ', ' + n); - 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: ' + first + ', ' + n); - } - 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: ' + type); - } - 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; - }, - indexObjects: function indexObjects() { - // Simple scan through the PDF content to find objects, - // trailers and XRef streams. - function readToken(data, offset) { - var token = '', ch = data[offset]; - while (ch !== 13 && ch !== 10) { - if (++offset >= data.length) - break; - token += String.fromCharCode(ch); - ch = data[offset]; - } - return token; - } - function skipUntil(data, offset, what) { - var length = what.length, dataLength = data.length; - var skipped = 0; - // finding byte sequence - while (offset < dataLength) { - var i = 0; - while (i < length && data[offset + i] == what[i]) - ++i; - if (i >= length) - break; // sequence found - - offset++; - skipped++; - } - return skipped; - } - var trailerBytes = new Uint8Array([116, 114, 97, 105, 108, 101, 114]); - var startxrefBytes = new Uint8Array([115, 116, 97, 114, 116, 120, 114, - 101, 102]); - var endobjBytes = new Uint8Array([101, 110, 100, 111, 98, 106]); - var xrefBytes = new Uint8Array([47, 88, 82, 101, 102]); - - var stream = this.stream; - stream.pos = 0; - var buffer = stream.getBytes(); - var position = stream.start, length = buffer.length; - var trailers = [], xrefStms = []; - var state = 0; - var currentToken; - while (position < length) { - var ch = buffer[position]; - if (ch === 32 || ch === 9 || ch === 13 || ch === 10) { - ++position; - continue; - } - if (ch === 37) { // %-comment - do { - ++position; - ch = buffer[position]; - } while (ch !== 13 && ch !== 10); - continue; - } - var token = readToken(buffer, position); - var m; - if (token === 'xref') { - position += skipUntil(buffer, position, trailerBytes); - trailers.push(position); - position += skipUntil(buffer, position, startxrefBytes); - } else if ((m = /^(\d+)\s+(\d+)\s+obj\b/.exec(token))) { - this.entries[m[1]] = { - offset: position, - gen: m[2] | 0, - uncompressed: true - }; - - var contentLength = skipUntil(buffer, position, endobjBytes) + 7; - var content = buffer.subarray(position, position + contentLength); - - // checking XRef stream suspect - // (it shall have '/XRef' and next char is not a letter) - var xrefTagOffset = skipUntil(content, 0, xrefBytes); - if (xrefTagOffset < contentLength && - content[xrefTagOffset + 5] < 64) { - xrefStms.push(position); - this.xrefstms[position] = 1; // don't read it recursively - } - - position += contentLength; - } else - position += token.length + 1; - } - // reading XRef streams - for (var i = 0; i < xrefStms.length; ++i) { - this.readXRef(xrefStms[i]); - } - // finding main trailer - var dict; - for (var i = 0; i < trailers.length; ++i) { - stream.pos = trailers[i]; - var parser = new Parser(new Lexer(stream), true); - var obj = parser.getObj(); - if (!isCmd(obj, 'trailer')) - continue; - // read the trailer dictionary - if (!isDict(dict = parser.getObj())) - continue; - // taking the first one with 'ID' - if (dict.has('ID')) - return dict; - } - // no tailer with 'ID', taking last one (if exists) - if (dict) - return dict; - // nothing helps - error('Invalid PDF structure'); - return null; - }, - 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); - } - return this.indexObjects(); - }, - getEntry: function xRefGetEntry(i) { - var e = this.entries[i]; - if (e.free) - error('reading an XRef stream not implemented yet'); - return e; - }, - fetchIfRef: function xRefFetchIfRef(obj) { - if (!isRef(obj)) - return obj; - return this.fetch(obj); - }, - fetch: function xRefFetch(ref, suppressEncryption) { - 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) { - num = parseInt(obj3.cmd.substring(3), 10); - if (!isNaN(num)) - return num; - } - error('bad XRef entry'); - } - if (this.encrypt && !suppressEncryption) { - try { - e = parser.getObj(this.encrypt.createCipherTransform(num, gen)); - } catch (ex) { - // almost all streams must be encrypted, but sometimes - // they are not probably due to some broken generators - // re-trying without encryption - return this.fetch(ref, true); - } - } else { - e = parser.getObj(); - } - // Don't cache streams since they are mutable (except images). - if (!isStream(e) || e.getImage) - 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) { - num = parser.getObj(); - if (!isInt(num)) { - error('invalid object number in the ObjStm stream: ' + num); - } - nums.push(num); - var offset = parser.getObj(); - if (!isInt(offset)) { - error('invalid object offset in the ObjStm stream: ' + offset); - } - } - // 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 xRefGetCatalogObj() { - return this.fetch(this.root); - } - }; - - return constructor; -})(); - -var Page = (function pagePage() { - function constructor(xref, pageNumber, pageDict, ref) { - this.pageNumber = pageNumber; - this.pageDict = pageDict; - this.stats = { - create: Date.now(), - compile: 0.0, - fonts: 0.0, - images: 0.0, - render: 0.0 - }; - this.xref = xref; - this.ref = ref; - } - - constructor.prototype = { - getPageProp: function pageGetPageProp(key) { - return this.xref.fetchIfRef(this.pageDict.get(key)); - }, - inheritPageProp: function pageInheritPageProp(key) { - var dict = this.pageDict; - var obj = dict.get(key); - while (obj === undefined) { - 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'); - // Reset invalid media box to letter size. - if (!isArray(obj) || obj.length !== 4) - obj = [0, 0, 612, 792]; - return shadow(this, 'mediaBox', obj); - }, - get view() { - var obj = this.inheritPageProp('CropBox'); - var view = { - x: 0, - y: 0, - width: this.width, - height: this.height - }; - if (isArray(obj) && obj.length == 4) { - var tl = this.rotatePoint(obj[0], obj[1]); - var br = this.rotatePoint(obj[2], obj[3]); - view.x = Math.min(tl.x, br.x); - view.y = Math.min(tl.y, br.y); - view.width = Math.abs(tl.x - br.x); - view.height = Math.abs(tl.y - br.y); - } - - return shadow(this, 'cropBox', view); - }, - get annotations() { - return shadow(this, 'annotations', this.inheritPageProp('Annots')); - }, - get width() { - var mediaBox = this.mediaBox; - var rotate = this.rotate; - var width; - if (rotate == 0 || rotate == 180) { - width = (mediaBox[2] - mediaBox[0]); - } else { - width = (mediaBox[3] - mediaBox[1]); - } - return shadow(this, 'width', width); - }, - get height() { - var mediaBox = this.mediaBox; - var rotate = this.rotate; - var height; - if (rotate == 0 || rotate == 180) { - height = (mediaBox[3] - mediaBox[1]); - } else { - height = (mediaBox[2] - mediaBox[0]); - } - return shadow(this, 'height', height); - }, - get rotate() { - var rotate = this.inheritPageProp('Rotate') || 0; - // Normalize rotation so it's a multiple of 90 and between 0 and 270 - if (rotate % 90 != 0) { - rotate = 0; - } else if (rotate >= 360) { - rotate = rotate % 360; - } else if (rotate < 0) { - // The spec doesn't cover negatives, assume its counterclockwise - // rotation. The following is the other implementation of modulo. - rotate = ((rotate % 360) + 360) % 360; - } - return shadow(this, 'rotate', rotate); - }, - - startRenderingFromIRQueue: function startRenderingFromIRQueue( - IRQueue, fonts) { - var self = this; - this.IRQueue = IRQueue; - var gfx = new CanvasGraphics(this.ctx, this.objs); - var startTime = Date.now(); - - var displayContinuation = function pageDisplayContinuation() { - // Always defer call to display() to work around bug in - // Firefox error reporting from XHR callbacks. - setTimeout(function pageSetTimeout() { - try { - self.display(gfx, self.callback); - } catch (e) { - if (self.callback) self.callback(e.toString()); - throw e; - } - }); - }; - - this.ensureFonts(fonts, function() { - displayContinuation(); - }); - }, - - getIRQueue: function(handler, dependency) { - if (this.IRQueue) { - // content was compiled - return this.IRQueue; - } - - var xref = this.xref; - var content = xref.fetchIfRef(this.content); - var resources = xref.fetchIfRef(this.resources); - if (isArray(content)) { - // fetching items - var i, n = content.length; - for (i = 0; i < n; ++i) - content[i] = xref.fetchIfRef(content[i]); - content = new StreamsSequenceStream(content); - } - - var pe = this.pe = new PartialEvaluator( - xref, handler, 'p' + this.pageNumber + '_'); - var IRQueue = {}; - return this.IRQueue = pe.getIRQueue( - content, resources, IRQueue, dependency); - }, - - ensureFonts: function(fonts, callback) { - // Convert the font names to the corresponding font obj. - for (var i = 0; i < fonts.length; i++) { - fonts[i] = this.objs.objs[fonts[i]].data; - } - - // Load all the fonts - var fontObjs = FontLoader.bind( - fonts, - function(fontObjs) { - this.stats.fonts = Date.now(); - - callback.call(this); - }.bind(this), - this.objs - ); - }, - - display: function(gfx, callback) { - var xref = this.xref; - var resources = xref.fetchIfRef(this.resources); - var mediaBox = xref.fetchIfRef(this.mediaBox); - assertWellFormed(isDict(resources), 'invalid page resources'); - - gfx.xref = xref; - gfx.res = resources; - gfx.beginDrawing({ x: mediaBox[0], y: mediaBox[1], - width: this.width, - height: this.height, - rotate: this.rotate }); - - var startIdx = 0; - var length = this.IRQueue.fnArray.length; - var IRQueue = this.IRQueue; - - var self = this; - var startTime = Date.now(); - function next() { - startIdx = gfx.executeIRQueue(IRQueue, startIdx, next); - if (startIdx == length) { - self.stats.render = Date.now(); - if (callback) callback(); - } - } - next(); - }, - rotatePoint: function pageRotatePoint(x, y, reverse) { - var rotate = reverse ? (360 - this.rotate) : this.rotate; - switch (rotate) { - case 180: - return {x: this.width - x, y: y}; - case 90: - return {x: this.width - y, y: this.height - x}; - case 270: - return {x: y, y: x}; - case 360: - case 0: - default: - return {x: x, y: this.height - y}; - } - }, - getLinks: function pageGetLinks() { - var xref = this.xref; - var annotations = xref.fetchIfRef(this.annotations) || []; - var i, n = annotations.length; - var links = []; - for (i = 0; i < n; ++i) { - var annotation = xref.fetch(annotations[i]); - if (!isDict(annotation)) - continue; - var subtype = annotation.get('Subtype'); - if (!isName(subtype) || subtype.name != 'Link') - continue; - var rect = annotation.get('Rect'); - var topLeftCorner = this.rotatePoint(rect[0], rect[1]); - var bottomRightCorner = this.rotatePoint(rect[2], rect[3]); - - var link = {}; - link.x = Math.min(topLeftCorner.x, bottomRightCorner.x); - link.y = Math.min(topLeftCorner.y, bottomRightCorner.y); - link.width = Math.abs(topLeftCorner.x - bottomRightCorner.x); - link.height = Math.abs(topLeftCorner.y - bottomRightCorner.y); - var a = this.xref.fetchIfRef(annotation.get('A')); - if (a) { - switch (a.get('S').name) { - case 'URI': - link.url = a.get('URI'); - break; - case 'GoTo': - link.dest = a.get('D'); - break; - default: - TODO('other link types'); - } - } else if (annotation.has('Dest')) { - // simple destination link - var dest = annotation.get('Dest'); - link.dest = isName(dest) ? dest.name : dest; - } - links.push(link); - } - return links; - }, - startRendering: function(ctx, callback) { - this.ctx = ctx; - this.callback = callback; - - this.startRenderingTime = Date.now(); - this.pdf.startRendering(this); - } - }; - - return constructor; -})(); - -var Catalog = (function catalogCatalog() { - 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 pagesObj = this.catDict.get('Pages'); - assertWellFormed(isRef(pagesObj), 'invalid top-level pages reference'); - var xrefObj = this.xref.fetch(pagesObj); - assertWellFormed(isDict(xrefObj), 'invalid top-level pages dictionary'); - // shadow the prototype getter - return shadow(this, 'toplevelPagesDict', xrefObj); - }, - get documentOutline() { - var obj = this.catDict.get('Outlines'); - var xref = this.xref; - var root = { items: [] }; - if (isRef(obj)) { - obj = xref.fetch(obj).get('First'); - var processed = new RefSet(); - if (isRef(obj)) { - var queue = [{obj: obj, parent: root}]; - // to avoid recursion keeping track of the items - // in the processed dictionary - processed.put(obj); - while (queue.length > 0) { - var i = queue.shift(); - var outlineDict = xref.fetch(i.obj); - if (!outlineDict.has('Title')) - error('Invalid outline item'); - var dest = outlineDict.get('A'); - if (dest) - dest = xref.fetchIfRef(dest).get('D'); - else if (outlineDict.has('Dest')) { - dest = outlineDict.get('Dest'); - if (isName(dest)) - dest = dest.name; - } - var title = xref.fetchIfRef(outlineDict.get('Title')); - var outlineItem = { - dest: dest, - title: stringToPDFString(title), - color: outlineDict.get('C') || [0, 0, 0], - count: outlineDict.get('Count'), - bold: !!(outlineDict.get('F') & 2), - italic: !!(outlineDict.get('F') & 1), - items: [] - }; - i.parent.items.push(outlineItem); - obj = outlineDict.get('First'); - if (isRef(obj) && !processed.has(obj)) { - queue.push({obj: obj, parent: outlineItem}); - processed.put(obj); - } - obj = outlineDict.get('Next'); - if (isRef(obj) && !processed.has(obj)) { - queue.push({obj: obj, parent: i.parent}); - processed.put(obj); - } - } - } - } - obj = root.items.length > 0 ? root.items : null; - return shadow(this, 'documentOutline', 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 catalogTraverseKids(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, kid)); - } else { // must be a child page dictionary - assertWellFormed( - isDict(obj), - 'page dictionary kid reference points to wrong type of object' - ); - this.traverseKids(obj); - } - } - }, - get destinations() { - function fetchDestination(xref, ref) { - var dest = xref.fetchIfRef(ref); - return isDict(dest) ? dest.get('D') : dest; - } - - var xref = this.xref; - var dests = {}, nameTreeRef, nameDictionaryRef; - var obj = this.catDict.get('Names'); - if (obj) - nameTreeRef = xref.fetchIfRef(obj).get('Dests'); - else if (this.catDict.has('Dests')) - nameDictionaryRef = this.catDict.get('Dests'); - - if (nameDictionaryRef) { - // reading simple destination dictionary - obj = xref.fetchIfRef(nameDictionaryRef); - obj.forEach(function catalogForEach(key, value) { - if (!value) return; - dests[key] = fetchDestination(xref, value); - }); - } - if (nameTreeRef) { - // reading name tree - var processed = new RefSet(); - processed.put(nameTreeRef); - var queue = [nameTreeRef]; - while (queue.length > 0) { - var i, n; - obj = xref.fetch(queue.shift()); - if (obj.has('Kids')) { - var kids = obj.get('Kids'); - for (i = 0, n = kids.length; i < n; i++) { - var kid = kids[i]; - if (processed.has(kid)) - error('invalid destinations'); - queue.push(kid); - processed.put(kid); - } - continue; - } - var names = obj.get('Names'); - for (i = 0, n = names.length; i < n; i += 2) { - dests[names[i]] = fetchDestination(xref, names[i + 1]); - } - } - } - return shadow(this, 'destinations', dests); - }, - getPage: function catalogGetPage(n) { - var pageCache = this.pageCache; - if (!pageCache) { - pageCache = this.pageCache = []; - this.traverseKids(this.toplevelPagesDict); - } - return this.pageCache[n - 1]; - } - }; - - return constructor; -})(); - -/** - * The `PDFDocModel` holds all the data of the PDF file. Compared to the - * `PDFDoc`, this one doesn't have any job management code. - * Right now there exists one PDFDocModel on the main thread + one object - * for each worker. If there is no worker support enabled, there are two - * `PDFDocModel` objects on the main thread created. - * TODO: Refactor the internal object structure, such that there is no - * need for the `PDFDocModel` anymore and there is only one object on the - * main thread and not one entire copy on each worker instance. - */ -var PDFDocModel = (function pdfDoc() { - function constructor(arg, callback) { - if (isStream(arg)) - init.call(this, arg); - else if (isArrayBuffer(arg)) - init.call(this, new Stream(arg)); - else - error('PDFDocModel: Unknown argument type'); - } - - function init(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 */ - } - - 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; - do { - ch = stream.getChar(); - } while (Lexer.isSpace(ch)); - var str = ''; - while ((ch - '0') <= 9) { - str += ch; - ch = stream.getChar(); - } - startXRef = parseInt(str, 10); - 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 pdfDocCheckHeader() { - 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 pdfDocSetup(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 pdfDocGetPage(n) { - return this.catalog.getPage(n); - } - }; - - return constructor; -})(); - -var PDFDoc = (function() { - function constructor(arg, callback) { - var stream = null; - var data = null; - - if (isStream(arg)) { - stream = arg; - data = arg.bytes; - } else if (isArrayBuffer(arg)) { - stream = new Stream(arg); - data = arg; - } else { - error('PDFDoc: Unknown argument type'); - } - - this.data = data; - this.stream = stream; - this.pdf = new PDFDocModel(stream); - - this.catalog = this.pdf.catalog; - this.objs = new PDFObjects(); - - this.pageCache = []; - - if (useWorker) { - var worker = new Worker('../worker/pdf_worker_loader.js'); - } else { - // If we don't use a worker, just post/sendMessage to the main thread. - var worker = { - postMessage: function(obj) { - worker.onmessage({data: obj}); - }, - terminate: function() {} - }; - } - this.worker = worker; - - this.fontsLoading = {}; - - var processorHandler = this.processorHandler = - new MessageHandler('main', worker); - - processorHandler.on('page', function(data) { - var pageNum = data.pageNum; - var page = this.pageCache[pageNum]; - var depFonts = data.depFonts; - - page.startRenderingFromIRQueue(data.IRQueue, depFonts); - }, this); - - processorHandler.on('obj', function(data) { - var id = data[0]; - var type = data[1]; - - switch (type) { - case 'JpegStream': - var IR = data[2]; - new JpegImage(id, IR, this.objs); - break; - case 'Font': - var name = data[2]; - var file = data[3]; - var properties = data[4]; - - if (file) { - var fontFileDict = new Dict(); - fontFileDict.map = file.dict.map; - - var fontFile = new Stream(file.bytes, file.start, - file.end - file.start, fontFileDict); - - // Check if this is a FlateStream. Otherwise just use the created - // Stream one. This makes complex_ttf_font.pdf work. - var cmf = file.bytes[0]; - if ((cmf & 0x0f) == 0x08) { - file = new FlateStream(fontFile); - } else { - file = fontFile; - } - } - - // For now, resolve the font object here direclty. The real font - // object is then created in FontLoader.bind(). - this.objs.resolve(id, { - name: name, - file: file, - properties: properties - }); - break; - default: - throw 'Got unkown object type ' + type; - } - }, this); - - processorHandler.on('font_ready', function(data) { - var id = data[0]; - var font = new FontShape(data[1]); - - // If there is no string, then there is nothing to attach to the DOM. - if (!font.str) { - this.objs.resolve(id, font); - } else { - this.objs.setData(id, font); - } - }.bind(this)); - - if (!useWorker) { - // If the main thread is our worker, setup the handling for the messages - // the main thread sends to it self. - WorkerProcessorHandler.setup(processorHandler); - } - - this.workerReadyPromise = new Promise('workerReady'); - setTimeout(function() { - processorHandler.send('doc', this.data); - this.workerReadyPromise.resolve(true); - }.bind(this)); - } - - constructor.prototype = { - get numPages() { - return this.pdf.numPages; - }, - - startRendering: function(page) { - // The worker might not be ready to receive the page request yet. - this.workerReadyPromise.then(function() { - this.processorHandler.send('page_request', page.pageNumber + 1); - }.bind(this)); - }, - - getPage: function(n) { - if (this.pageCache[n]) - return this.pageCache[n]; - - var page = this.pdf.getPage(n); - // Add a reference to the objects such that Page can forward the reference - // to the CanvasGraphics and so on. - page.objs = this.objs; - page.pdf = this; - return this.pageCache[n] = page; - }, - - destroy: function() { - if (this.worker) - this.worker.terminate(); - - if (this.fontWorker) - this.fontWorker.terminate(); - - for (var n in this.pageCache) - delete this.pageCache[n]; - - delete this.data; - delete this.stream; - delete this.pdf; - delete this.catalog; - } - }; - - 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' - ]); - }, - 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' - ]); - }, - 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' - ]); - }, - 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' - ]); - }, - 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' - ]); - }, - get symbolsEncoding() { - return shadow(this, 'symbolsEncoding', ['', '', '', '', '', '', '', '', '', - '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', - '', '', '', '', '', 'space', 'exclam', 'universal', 'numbersign', - 'existential', 'percent', 'ampersand', 'suchthat', 'parenleft', - 'parenright', 'asteriskmath', 'plus', 'comma', 'minus', 'period', - 'slash', 'zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', - 'eight', 'nine', 'colon', 'semicolon', 'less', 'equal', 'greater', - 'question', 'congruent', 'Alpha', 'Beta', 'Chi', 'Delta', 'Epsilon', - 'Phi', 'Gamma', 'Eta', 'Iota', 'theta1', 'Kappa', 'Lambda', 'Mu', 'Nu', - 'Omicron', 'Pi', 'Theta', 'Rho', 'Sigma', 'Tau', 'Upsilon', 'sigma1', - 'Omega', 'Xi', 'Psi', 'Zeta', 'bracketleft', 'therefore', 'bracketright', - 'perpendicular', 'underscore', 'radicalex', 'alpha', 'beta', 'chi', - 'delta', 'epsilon', 'phi', 'gamma', 'eta', 'iota', 'phi1', 'kappa', - 'lambda', 'mu', 'nu', 'omicron', 'pi', 'theta', 'rho', 'sigma', 'tau', - 'upsilon', 'omega1', 'omega', 'xi', 'psi', 'zeta', 'braceleft', 'bar', - 'braceright', 'similar', '', '', '', '', '', '', '', '', '', '', '', '', - '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', - '', '', '', 'Euro', 'Upsilon1', 'minute', 'lessequal', 'fraction', - 'infinity', 'florin', 'club', 'diamond', 'heart', 'spade', 'arrowboth', - 'arrowleft', 'arrowup', 'arrowright', 'arrowdown', 'degree', 'plusminus', - 'second', 'greaterequal', 'multiply', 'proportional', 'partialdiff', - 'bullet', 'divide', 'notequal', 'equivalence', 'approxequal', 'ellipsis', - 'arrowvertex', 'arrowhorizex', 'carriagereturn', 'aleph', 'Ifraktur', - 'Rfraktur', 'weierstrass', 'circlemultiply', 'circleplus', 'emptyset', - 'intersection', 'union', 'propersuperset', 'reflexsuperset', 'notsubset', - 'propersubset', 'reflexsubset', 'element', 'notelement', 'angle', - 'gradient', 'registerserif', 'copyrightserif', 'trademarkserif', - 'product', 'radical', 'dotmath', 'logicalnot', 'logicaland', 'logicalor', - 'arrowdblboth', 'arrowdblleft', 'arrowdblup', 'arrowdblright', - 'arrowdbldown', 'lozenge', 'angleleft', 'registersans', 'copyrightsans', - 'trademarksans', 'summation', 'parenlefttp', 'parenleftex', - 'parenleftbt', 'bracketlefttp', 'bracketleftex', 'bracketleftbt', - 'bracelefttp', 'braceleftmid', 'braceleftbt', 'braceex', '', - 'angleright', 'integral', 'integraltp', 'integralex', 'integralbt', - 'parenrighttp', 'parenrightex', 'parenrightbt', 'bracketrighttp', - 'bracketrightex', 'bracketrightbt', 'bracerighttp', 'bracerightmid', - 'bracerightbt' - ]); - }, - 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' - ]); - } -}; - -var IDENTITY_MATRIX = [1, 0, 0, 1, 0, 0]; - -var EvalState = (function evalState() { - 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.charSpacing = 0; - this.wordSpacing = 0; - this.textHScale = 1; - // Color spaces - this.fillColorSpace = null; - this.strokeColorSpace = null; - } - constructor.prototype = { - }; - return constructor; -})(); - -var PartialEvaluator = (function partialEvaluator() { - function constructor(xref, handler, uniquePrefix) { - this.state = new EvalState(); - this.stateStack = []; - - this.xref = xref; - this.handler = handler; - this.uniquePrefix = uniquePrefix; - this.objIdCounter = 0; - } - - 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', - ID: 'beginImageData', - EI: 'endInlineImage', - - // XObjects - Do: 'paintXObject', - - // Marked content - MP: 'markPoint', - DP: 'markPointProps', - BMC: 'beginMarkedContent', - BDC: 'beginMarkedContentProps', - EMC: 'endMarkedContent', - - // Compatibility - BX: 'beginCompat', - EX: 'endCompat' - }; - - constructor.prototype = { - getIRQueue: function partialEvaluatorGetIRQueue(stream, resources, - queue, dependency) { - - var self = this; - var xref = this.xref; - var handler = this.handler; - var uniquePrefix = this.uniquePrefix; - - function insertDependency(depList) { - fnArray.push('dependency'); - argsArray.push(depList); - for (var i = 0; i < depList.length; i++) { - var dep = depList[i]; - if (dependency.indexOf(dep) == -1) { - dependency.push(depList[i]); - } - } - } - - function handleSetFont(fontName, fontRef) { - var loadedName = null; - - var fontRes = resources.get('Font'); - - // TODO: TOASK: Is it possible to get here? If so, what does - // args[0].name should be like??? - assert(fontRes, 'fontRes not available'); - - fontRes = xref.fetchIfRef(fontRes); - fontRef = fontRef || fontRes.get(fontName); - var font = xref.fetchIfRef(fontRef); - assertWellFormed(isDict(font)); - if (!font.translated) { - font.translated = self.translateFont(font, xref, resources, handler, - uniquePrefix, dependency); - if (font.translated) { - // keep track of each font we translated so the caller can - // load them asynchronously before calling display on a page - loadedName = 'font_' + uniquePrefix + ++self.objIdCounter; - font.translated.properties.loadedName = loadedName; - font.loadedName = loadedName; - - var translated = font.translated; - handler.send('obj', [ - loadedName, - 'Font', - translated.name, - translated.file, - translated.properties - ]); - } - } - loadedName = loadedName || font.loadedName; - - // Ensure the font is ready before the font is set - // and later on used for drawing. - // TODO: This should get insert to the IRQueue only once per - // page. - insertDependency([loadedName]); - return loadedName; - } - - function buildPaintImageXObject(image, inline) { - var dict = image.dict; - var w = dict.get('Width', 'W'); - var h = dict.get('Height', 'H'); - - if (image instanceof JpegStream) { - var objId = 'img_' + uniquePrefix + ++self.objIdCounter; - handler.send('obj', [objId, 'JpegStream', image.getIR()]); - - // Add the dependency on the image object. - insertDependency([objId]); - - // The normal fn. - fn = 'paintJpegXObject'; - args = [objId, w, h]; - - return; - } - - // Needs to be rendered ourself. - - // Figure out if the image has an imageMask. - var imageMask = dict.get('ImageMask', 'IM') || false; - - // If there is no imageMask, create the PDFImage and a lot - // of image processing can be done here. - if (!imageMask) { - var imageObj = new PDFImage(xref, resources, image, inline); - - if (imageObj.imageMask) { - throw 'Can\'t handle this in the web worker :/'; - } - - var imgData = { - width: w, - height: h, - data: new Uint8Array(w * h * 4) - }; - var pixels = imgData.data; - imageObj.fillRgbaBuffer(pixels, imageObj.decode); - - fn = 'paintImageXObject'; - args = [imgData]; - return; - } - - // This depends on a tmpCanvas beeing filled with the - // current fillStyle, such that processing the pixel - // data can't be done here. Instead of creating a - // complete PDFImage, only read the information needed - // for later. - fn = 'paintImageMaskXObject'; - - var width = dict.get('Width', 'W'); - var height = dict.get('Height', 'H'); - var bitStrideLength = (width + 7) >> 3; - var imgArray = image.getBytes(bitStrideLength * height); - var decode = dict.get('Decode', 'D'); - var inverseDecode = !!decode && decode[0] > 0; - - args = [imgArray, inverseDecode, width, height]; - } - - uniquePrefix = uniquePrefix || ''; - if (!queue.argsArray) { - queue.argsArray = []; - } - if (!queue.fnArray) { - queue.fnArray = []; - } - - var fnArray = queue.fnArray, argsArray = queue.argsArray; - var dependencyArray = dependency || []; - - resources = xref.fetchIfRef(resources) || new Dict(); - var xobjs = xref.fetchIfRef(resources.get('XObject')) || new Dict(); - var patterns = xref.fetchIfRef(resources.get('Pattern')) || new Dict(); - var parser = new Parser(new Lexer(stream), false); - var res = resources; - var args = [], obj; - var getObjBt = function getObjBt() { - parser = this.oldParser; - return { name: 'BT' }; - }; - var TILING_PATTERN = 1, SHADING_PATTERN = 2; - - while (!isEOF(obj = parser.getObj())) { - if (isCmd(obj)) { - var cmd = obj.cmd; - var fn = OP_MAP[cmd]; - if (!fn) { - // invalid content command, trying to recover - if (cmd.substr(-2) == 'BT') { - fn = OP_MAP[cmd.substr(0, cmd.length - 2)]; - // feeding 'BT' on next interation - parser = { - getObj: getObjBt, - oldParser: parser - }; - } - } - assertWellFormed(fn, 'Unknown command "' + cmd + '"'); - // TODO figure out how to type-check vararg functions - - if ((cmd == 'SCN' || cmd == 'scn') && !args[args.length - 1].code) { - // Use the IR version for setStroke/FillColorN. - fn += '_IR'; - - // compile tiling patterns - var patternName = args[args.length - 1]; - // SCN/scn applies patterns along with normal colors - if (isName(patternName)) { - var pattern = xref.fetchIfRef(patterns.get(patternName.name)); - if (pattern) { - var dict = isStream(pattern) ? pattern.dict : pattern; - var typeNum = dict.get('PatternType'); - - if (typeNum == TILING_PATTERN) { - // Create an IR of the pattern code. - var depIdx = dependencyArray.length; - var queueObj = {}; - var codeIR = this.getIRQueue(pattern, dict.get('Resources'), - queueObj, dependencyArray); - - // Add the dependencies that are required to execute the - // codeIR. - insertDependency(dependencyArray.slice(depIdx)); - - args = TilingPattern.getIR(codeIR, dict, args); - } - else if (typeNum == SHADING_PATTERN) { - var shading = xref.fetchIfRef(dict.get('Shading')); - var matrix = dict.get('Matrix'); - var pattern = Pattern.parseShading(shading, matrix, xref, res, - null /*ctx*/); - args = pattern.getIR(); - } else { - error('Unkown PatternType ' + typeNum); - } - } - } - } else 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) { - var matrix = xobj.dict.get('Matrix'); - var bbox = xobj.dict.get('BBox'); - - fnArray.push('paintFormXObjectBegin'); - argsArray.push([matrix, bbox]); - - // This adds the IRQueue of the xObj to the current queue. - var depIdx = dependencyArray.length; - - this.getIRQueue(xobj, xobj.dict.get('Resources'), queue, - dependencyArray); - - // Add the dependencies that are required to execute the - // codeIR. - insertDependency(dependencyArray.slice(depIdx)); - - fn = 'paintFormXObjectEnd'; - args = []; - } else if ('Image' == type.name) { - buildPaintImageXObject(xobj, false); - } else { - error('Unhandled XObject subtype ' + type.name); - } - } - } else if (cmd == 'Tf') { // eagerly collect all fonts - args[0] = handleSetFont(args[0].name); - } else if (cmd == 'EI') { - buildPaintImageXObject(args[0], true); - } - - switch (fn) { - // Parse the ColorSpace data to a raw format. - case 'setFillColorSpace': - case 'setStrokeColorSpace': - args = [ColorSpace.parseToIR(args[0], xref, resources)]; - break; - case 'shadingFill': - var shadingRes = xref.fetchIfRef(res.get('Shading')); - if (!shadingRes) - error('No shading resource found'); - - var shading = xref.fetchIfRef(shadingRes.get(args[0].name)); - if (!shading) - error('No shading object found'); - - var shadingFill = Pattern.parseShading(shading, null, xref, res, - null); - var patternIR = shadingFill.getIR(); - args = [patternIR]; - fn = 'shadingFill'; - break; - case 'setGState': - var dictName = args[0]; - var extGState = xref.fetchIfRef(resources.get('ExtGState')); - - if (!isDict(extGState) || !extGState.has(dictName.name)) - break; - - var gsState = xref.fetchIfRef(extGState.get(dictName.name)); - - // This array holds the converted/processed state data. - var gsStateObj = []; - - gsState.forEach( - function canvasGraphicsSetGStateForEach(key, value) { - switch (key) { - case 'Type': - break; - case 'LW': - case 'LC': - case 'LJ': - case 'ML': - case 'D': - case 'RI': - case 'FL': - gsStateObj.push([key, value]); - break; - case 'Font': - gsStateObj.push([ - 'Font', - handleSetFont(null, value[0]), - value[1] - ]); - break; - case 'OP': - case 'op': - case 'OPM': - case 'BG': - case 'BG2': - case 'UCR': - case 'UCR2': - case 'TR': - case 'TR2': - case 'HT': - case 'SM': - case 'SA': - case 'BM': - case 'SMask': - case 'CA': - case 'ca': - case 'AIS': - case 'TK': - TODO('graphic state operator ' + key); - break; - default: - warn('Unknown graphic state operator ' + key); - break; - } - } - ); - args = [gsStateObj]; - break; - } // switch - - fnArray.push(fn); - argsArray.push(args); - args = []; - } else if (obj != null) { - assertWellFormed(args.length <= 33, 'Too many arguments'); - args.push(obj); - } - } - - return { - fnArray: fnArray, - argsArray: argsArray - }; - }, - - extractEncoding: function partialEvaluatorExtractEncoding(dict, - xref, - properties) { - var type = properties.type, encoding; - if (properties.composite) { - var defaultWidth = xref.fetchIfRef(dict.get('DW')) || 1000; - properties.defaultWidth = defaultWidth; - - var glyphsWidths = {}; - var widths = xref.fetchIfRef(dict.get('W')); - if (widths) { - var start = 0, end = 0; - for (var i = 0; i < widths.length; i++) { - var code = widths[i]; - if (isArray(code)) { - for (var j = 0; j < code.length; j++) - glyphsWidths[start++] = code[j]; - start = 0; - } else if (start) { - var width = widths[++i]; - for (var j = start; j <= code; j++) - glyphsWidths[j] = width; - start = 0; - } else { - start = code; - } - } - } - properties.widths = glyphsWidths; - - // Glyph ids are big-endian 2-byte values - encoding = properties.encoding; - - // CIDSystemInfo might help to match width and glyphs - var cidSystemInfo = dict.get('CIDSystemInfo'); - if (isDict(cidSystemInfo)) { - properties.cidSystemInfo = { - registry: cidSystemInfo.get('Registry'), - ordering: cidSystemInfo.get('Ordering'), - supplement: cidSystemInfo.get('Supplement') - }; - } - - var cidToGidMap = dict.get('CIDToGIDMap'); - if (!cidToGidMap || !isRef(cidToGidMap)) { - - - return Object.create(GlyphsUnicode); - } - - // Extract the encoding from the CIDToGIDMap - var glyphsStream = xref.fetchIfRef(cidToGidMap); - var glyphsData = glyphsStream.getBytes(0); - - // Set encoding 0 to later verify the font has an encoding - encoding[0] = { unicode: 0, width: 0 }; - for (var j = 0; j < glyphsData.length; j++) { - var glyphID = (glyphsData[j++] << 8) | glyphsData[j]; - if (glyphID == 0) - continue; - - var code = j >> 1; - var width = glyphsWidths[code]; - encoding[code] = { - unicode: glyphID, - width: isNum(width) ? width : defaultWidth - }; - } - - return Object.create(GlyphsUnicode); - } - - var differences = properties.differences; - var map = properties.encoding; - var baseEncoding = null; - if (dict.has('Encoding')) { - encoding = xref.fetchIfRef(dict.get('Encoding')); - if (isDict(encoding)) { - var baseName = encoding.get('BaseEncoding'); - if (baseName) - baseEncoding = Encodings[baseName.name].slice(); - - // Load the differences between the base and original - if (encoding.has('Differences')) { - var diffEncoding = encoding.get('Differences'); - var index = 0; - for (var j = 0; j < diffEncoding.length; j++) { - var data = diffEncoding[j]; - if (isNum(data)) - index = data; - else - differences[index++] = data.name; - } - } - } else if (isName(encoding)) { - baseEncoding = Encodings[encoding.name].slice(); - } else { - error('Encoding is not a Name nor a Dict'); - } - } - - if (!baseEncoding) { - switch (type) { - case 'TrueType': - baseEncoding = Encodings.WinAnsiEncoding.slice(); - break; - case 'Type1': - case 'Type3': - baseEncoding = Encodings.StandardEncoding.slice(); - break; - default: - warn('Unknown type of font: ' + type); - baseEncoding = []; - break; - } - } - - // merge in the differences - var firstChar = properties.firstChar; - var lastChar = properties.lastChar; - var widths = properties.widths || []; - var glyphs = {}; - for (var i = firstChar; i <= lastChar; i++) { - var glyph = differences[i]; - var replaceGlyph = true; - if (!glyph) { - glyph = baseEncoding[i] || i; - replaceGlyph = false; - } - var index = GlyphsUnicode[glyph] || i; - var width = widths[i] || widths[glyph]; - map[i] = { - unicode: index, - width: isNum(width) ? width : properties.defaultWidth - }; - - if (replaceGlyph || !glyphs[glyph]) - glyphs[glyph] = map[i]; - if (replaceGlyph || !glyphs[index]) - glyphs[index] = map[i]; - - // If there is no file, the character mapping can't be modified - // but this is unlikely that there is any standard encoding with - // chars below 0x1f, so that's fine. - if (!properties.file) - continue; - - if (index <= 0x1f || (index >= 127 && index <= 255)) - map[i].unicode += kCmapGlyphOffset; - } - - if (type == 'TrueType' && dict.has('ToUnicode') && differences) { - var cmapObj = dict.get('ToUnicode'); - if (isRef(cmapObj)) { - cmapObj = xref.fetch(cmapObj); - } - if (isName(cmapObj)) { - error('ToUnicode file cmap translation not implemented'); - } else if (isStream(cmapObj)) { - var tokens = []; - var token = ''; - var beginArrayToken = {}; - - var cmap = cmapObj.getBytes(cmapObj.length); - for (var i = 0; i < cmap.length; i++) { - var byte = cmap[i]; - if (byte == 0x20 || byte == 0x0D || byte == 0x0A || - byte == 0x3C || byte == 0x5B || byte == 0x5D) { - switch (token) { - case 'usecmap': - error('usecmap is not implemented'); - break; - - case 'beginbfchar': - case 'beginbfrange': - case 'begincidchar': - case 'begincidrange': - token = ''; - tokens = []; - break; - - case 'endcidrange': - case 'endbfrange': - for (var j = 0; j < tokens.length; j += 3) { - var startRange = tokens[j]; - var endRange = tokens[j + 1]; - var code = tokens[j + 2]; - while (startRange < endRange) { - var mapping = map[startRange] || {}; - mapping.unicode = code++; - map[startRange] = mapping; - ++startRange; - } - } - break; - - case 'endcidchar': - case 'endbfchar': - for (var j = 0; j < tokens.length; j += 2) { - var index = tokens[j]; - var code = tokens[j + 1]; - var mapping = map[index] || {}; - mapping.unicode = code; - map[index] = mapping; - } - break; - - case '': - break; - - default: - if (token[0] >= '0' && token[0] <= '9') - token = parseInt(token, 10); // a number - tokens.push(token); - token = ''; - } - switch (byte) { - case 0x5B: - // begin list parsing - tokens.push(beginArrayToken); - break; - case 0x5D: - // collect array items - var items = [], item; - while (tokens.length && - (item = tokens.pop()) != beginArrayToken) - items.unshift(item); - tokens.push(items); - break; - } - } else if (byte == 0x3E) { - if (token.length) { - // parsing hex number - tokens.push(parseInt(token, 16)); - token = ''; - } - } else { - token += String.fromCharCode(byte); - } - } - } - } - return glyphs; - }, - - getBaseFontMetricsAndMap: function getBaseFontMetricsAndMap(name) { - var map = {}; - if (/^Symbol(-?(Bold|Italic))*$/.test(name)) { - // special case for symbols - var encoding = Encodings.symbolsEncoding.slice(); - for (var i = 0, n = encoding.length, j; i < n; i++) { - if (!(j = encoding[i])) - continue; - map[i] = GlyphsUnicode[j] || 0; - } - } - - var defaultWidth = 0; - var widths = Metrics[stdFontMap[name] || name]; - if (isNum(widths)) { - defaultWidth = widths; - widths = null; - } - - return { - defaultWidth: defaultWidth, - widths: widths || [], - map: map - }; - }, - - translateFont: function partialEvaluatorTranslateFont(dict, xref, resources, - queue, handler, uniquePrefix, dependency) { - var baseDict = dict; - var type = dict.get('Subtype'); - assertWellFormed(isName(type), 'invalid font Subtype'); - - var composite = false; - if (type.name == 'Type0') { - // If font is a composite - // - get the descendant font - // - set the type according to the descendant font - // - get the FontDescriptor from the descendant font - var df = dict.get('DescendantFonts'); - if (!df) - return null; - - if (isRef(df)) - df = xref.fetch(df); - - dict = xref.fetchIfRef(isRef(df) ? df : df[0]); - - type = dict.get('Subtype'); - assertWellFormed(isName(type), 'invalid font Subtype'); - composite = true; - } - - var descriptor = xref.fetchIfRef(dict.get('FontDescriptor')); - if (!descriptor) { - if (type.name == 'Type3') { - // FontDescriptor is only required for Type3 fonts when the document - // is a tagged pdf. Create a barbebones one to get by. - descriptor = new Dict(); - descriptor.set('FontName', new Name(type.name)); - } else { - // Before PDF 1.5 if the font was one of the base 14 fonts, having a - // FontDescriptor was not required. - // This case is here for compatibility. - var baseFontName = dict.get('BaseFont'); - if (!isName(baseFontName)) - return null; - - // Using base font name as a font name. - baseFontName = baseFontName.name.replace(/[,_]/g, '-'); - var metricsAndMap = this.getBaseFontMetricsAndMap(baseFontName); - - var properties = { - type: type.name, - encoding: metricsAndMap.map, - differences: [], - widths: metricsAndMap.widths, - defaultWidth: metricsAndMap.defaultWidth, - firstChar: 0, - lastChar: 256 - }; - this.extractEncoding(dict, xref, properties); - - return { - name: baseFontName, - dict: baseDict, - properties: properties - }; - } - - } - - // According to the spec if 'FontDescriptor' is declared, 'FirstChar', - // 'LastChar' and 'Widths' should exists too, but some PDF encoders seems - // to ignore this rule when a variant of a standart font is used. - // TODO Fill the width array depending on which of the base font this is - // a variant. - var firstChar = xref.fetchIfRef(dict.get('FirstChar')) || 0; - var lastChar = xref.fetchIfRef(dict.get('LastChar')) || 256; - var defaultWidth = 0; - var glyphWidths = {}; - var encoding = {}; - var widths = xref.fetchIfRef(dict.get('Widths')); - if (widths) { - for (var i = 0, j = firstChar; i < widths.length; i++, j++) - glyphWidths[j] = widths[i]; - defaultWidth = parseFloat(descriptor.get('MissingWidth')) || 0; - } else { - // Trying get the BaseFont metrics (see comment above). - var baseFontName = dict.get('BaseFont'); - if (isName(baseFontName)) { - var metricsAndMap = this.getBaseFontMetricsAndMap(baseFontName.name); - - glyphWidths = metricsAndMap.widths; - defaultWidth = metricsAndMap.defaultWidth; - encoding = metricsAndMap.map; - } - } - - var fontName = xref.fetchIfRef(descriptor.get('FontName')); - assertWellFormed(isName(fontName), 'invalid font name'); - - var fontFile = descriptor.get('FontFile', 'FontFile2', 'FontFile3'); - if (fontFile) { - fontFile = xref.fetchIfRef(fontFile); - if (fontFile.dict) { - var subtype = fontFile.dict.get('Subtype'); - if (subtype) - subtype = subtype.name; - - var length1 = fontFile.dict.get('Length1'); - if (!isInt(length1)) - length1 = xref.fetchIfRef(length1); - - var length2 = fontFile.dict.get('Length2'); - if (!isInt(length2)) - length2 = xref.fetchIfRef(length2); - } - } - - var properties = { - type: type.name, - subtype: subtype, - file: fontFile, - length1: length1, - length2: length2, - composite: composite, - fixedPitch: false, - fontMatrix: dict.get('FontMatrix') || IDENTITY_MATRIX, - firstChar: firstChar || 0, - lastChar: lastChar || 256, - bbox: descriptor.get('FontBBox'), - ascent: descriptor.get('Ascent'), - descent: descriptor.get('Descent'), - xHeight: descriptor.get('XHeight'), - capHeight: descriptor.get('CapHeight'), - defaultWidth: defaultWidth, - flags: descriptor.get('Flags'), - italicAngle: descriptor.get('ItalicAngle'), - differences: [], - widths: glyphWidths, - encoding: encoding, - coded: false - }; - properties.glyphs = this.extractEncoding(dict, xref, properties); - - if (type.name === 'Type3') { - properties.coded = true; - var charProcs = xref.fetchIfRef(dict.get('CharProcs')); - var fontResources = xref.fetchIfRef(dict.get('Resources')) || resources; - properties.resources = fontResources; - for (var key in charProcs.map) { - var glyphStream = xref.fetchIfRef(charProcs.map[key]); - var queueObj = {}; - properties.glyphs[key].IRQueue = this.getIRQueue(glyphStream, - fontResources, - queueObj, - dependency); - } - } - - return { - name: fontName.name, - dict: baseDict, - 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 canvasExtraState() { - function constructor(old) { - // 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.charSpacing = 0; - this.wordSpacing = 0; - this.textHScale = 1; - // Color spaces - this.fillColorSpaceObj = null; - this.strokeColorSpaceObj = null; - this.fillColorObj = null; - this.strokeColorObj = null; - // Default fore and background colors - this.fillColor = '#000000'; - this.strokeColor = '#000000'; - - this.old = old; - - this.fillColorSpace = new DeviceGrayCS; - this.strokeColorSpace = new DeviceGrayCS; - } - - constructor.prototype = { - clone: function canvasextra_clone() { - return Object.create(this); - }, - setCurrentPoint: function canvasextra_setCurrentPoint(x, y) { - this.x = x; - this.y = y; - } - }; - return constructor; -})(); - -function ScratchCanvas(width, height) { - var canvas = document.createElement('canvas'); - canvas.width = width; - canvas.height = height; - return canvas; -} - -var CanvasGraphics = (function canvasGraphics() { - // Defines the time the executeIRQueue is going to be executing - // before it stops and shedules a continue of execution. - var kExecutionTime = 50; - - // Number of IR commands to execute before checking - // if we execute longer then `kExecutionTime`. - var kExecutionTimeCheck = 500; - - function constructor(canvasCtx, objs) { - this.ctx = canvasCtx; - this.current = new CanvasExtraState(); - this.stateStack = []; - this.pendingClip = null; - this.res = null; - this.xobjs = null; - this.ScratchCanvas = ScratchCanvas; - this.objs = objs; - } - - var LINE_CAP_STYLES = ['butt', 'round', 'square']; - var LINE_JOIN_STYLES = ['miter', 'round', 'bevel']; - var NORMAL_CLIP = {}; - var EO_CLIP = {}; - - constructor.prototype = { - beginDrawing: function canvasGraphicsBeginDrawing(mediaBox) { - var cw = this.ctx.canvas.width, ch = this.ctx.canvas.height; - this.ctx.save(); - switch (mediaBox.rotate) { - case 0: - this.ctx.transform(1, 0, 0, -1, 0, ch); - break; - case 90: - this.ctx.transform(0, 1, 1, 0, 0, 0); - break; - case 180: - this.ctx.transform(-1, 0, 0, 1, cw, 0); - break; - case 270: - this.ctx.transform(0, -1, -1, 0, cw, ch); - break; - } - this.ctx.scale(cw / mediaBox.width, ch / mediaBox.height); - }, - - executeIRQueue: function canvasGraphicsExecuteIRQueue(codeIR, - executionStartIdx, continueCallback) { - var argsArray = codeIR.argsArray; - var fnArray = codeIR.fnArray; - var i = executionStartIdx || 0; - var argsArrayLen = argsArray.length; - - var executionEndIdx; - var startTime = Date.now(); - - var objs = this.objs; - - do { - executionEndIdx = Math.min(argsArrayLen, i + kExecutionTimeCheck); - - for (i; i < executionEndIdx; i++) { - if (fnArray[i] !== 'dependency') { - this[fnArray[i]].apply(this, argsArray[i]); - } else { - var deps = argsArray[i]; - for (var n = 0; n < deps.length; n++) { - var depObjId = deps[n]; - - // If the promise isn't resolved yet, add the continueCallback - // to the promise and bail out. - if (!objs.isResolved(depObjId)) { - objs.get(depObjId, continueCallback); - return i; - } - } - } - } - - // If the entire IRQueue was executed, stop as were done. - if (i == argsArrayLen) { - return i; - } - - // If the execution took longer then a certain amount of time, shedule - // to continue exeution after a short delay. - // However, this is only possible if a 'continueCallback' is passed in. - if (continueCallback && (Date.now() - startTime) > kExecutionTime) { - setTimeout(continueCallback, 0); - return i; - } - - // If the IRQueue isn't executed completly yet OR the execution time - // was short enough, do another execution round. - } while (true); - }, - - endDrawing: function canvasGraphicsEndDrawing() { - this.ctx.restore(); - }, - - // Graphics state - setLineWidth: function canvasGraphicsSetLineWidth(width) { - this.ctx.lineWidth = width; - }, - setLineCap: function canvasGraphicsSetLineCap(style) { - this.ctx.lineCap = LINE_CAP_STYLES[style]; - }, - setLineJoin: function canvasGraphicsSetLineJoin(style) { - this.ctx.lineJoin = LINE_JOIN_STYLES[style]; - }, - setMiterLimit: function canvasGraphicsSetMiterLimit(limit) { - this.ctx.miterLimit = limit; - }, - setDash: function canvasGraphicsSetDash(dashArray, dashPhase) { - this.ctx.mozDash = dashArray; - this.ctx.mozDashOffset = dashPhase; - }, - setRenderingIntent: function canvasGraphicsSetRenderingIntent(intent) { - TODO('set rendering intent: ' + intent); - }, - setFlatness: function canvasGraphicsSetFlatness(flatness) { - TODO('set flatness: ' + flatness); - }, - setGState: function canvasGraphicsSetGState(states) { - for (var i = 0; i < states.length; i++) { - var state = states[i]; - var key = state[0]; - var value = state[1]; - - switch (key) { - case 'LW': - this.setLineWidth(value); - break; - case 'LC': - this.setLineCap(value); - break; - case 'LJ': - this.setLineJoin(value); - break; - case 'ML': - this.setMiterLimit(value); - break; - case 'D': - this.setDash(value[0], value[1]); - break; - case 'RI': - this.setRenderingIntent(value); - break; - case 'FL': - this.setFlatness(value); - break; - case 'Font': - this.setFont(state[1], state[2]); - break; - } - } - }, - save: function canvasGraphicsSave() { - this.ctx.save(); - var old = this.current; - this.stateStack.push(old); - this.current = old.clone(); - }, - restore: function canvasGraphicsRestore() { - var prev = this.stateStack.pop(); - if (prev) { - this.current = prev; - this.ctx.restore(); - } - }, - transform: function canvasGraphicsTransform(a, b, c, d, e, f) { - this.ctx.transform(a, b, c, d, e, f); - }, - - // Path - moveTo: function canvasGraphicsMoveTo(x, y) { - this.ctx.moveTo(x, y); - this.current.setCurrentPoint(x, y); - }, - lineTo: function canvasGraphicsLineTo(x, y) { - this.ctx.lineTo(x, y); - this.current.setCurrentPoint(x, y); - }, - curveTo: function canvasGraphicsCurveTo(x1, y1, x2, y2, x3, y3) { - this.ctx.bezierCurveTo(x1, y1, x2, y2, x3, y3); - this.current.setCurrentPoint(x3, y3); - }, - curveTo2: function canvasGraphicsCurveTo2(x2, y2, x3, y3) { - var current = this.current; - this.ctx.bezierCurveTo(current.x, current.y, x2, y2, x3, y3); - current.setCurrentPoint(x3, y3); - }, - curveTo3: function canvasGraphicsCurveTo3(x1, y1, x3, y3) { - this.curveTo(x1, y1, x3, y3, x3, y3); - this.current.setCurrentPoint(x3, y3); - }, - closePath: function canvasGraphicsClosePath() { - this.ctx.closePath(); - }, - rectangle: function canvasGraphicsRectangle(x, y, width, height) { - this.ctx.rect(x, y, width, height); - }, - stroke: function canvasGraphicsStroke() { - var ctx = this.ctx; - var strokeColor = this.current.strokeColor; - if (strokeColor && strokeColor.hasOwnProperty('type') && - strokeColor.type === 'Pattern') { - // for patterns, we transform to pattern space, calculate - // the pattern, call stroke, and restore to user space - ctx.save(); - ctx.strokeStyle = strokeColor.getPattern(ctx); - ctx.stroke(); - ctx.restore(); - } else { - ctx.stroke(); - } - - this.consumePath(); - }, - closeStroke: function canvasGraphicsCloseStroke() { - this.closePath(); - this.stroke(); - }, - fill: function canvasGraphicsFill() { - var ctx = this.ctx; - var fillColor = this.current.fillColor; - - if (fillColor && fillColor.hasOwnProperty('type') && - fillColor.type === 'Pattern') { - ctx.save(); - ctx.fillStyle = fillColor.getPattern(ctx); - ctx.fill(); - ctx.restore(); - } else { - ctx.fill(); - } - - this.consumePath(); - }, - eoFill: function canvasGraphicsEoFill() { - var savedFillRule = this.setEOFillRule(); - this.fill(); - this.restoreFillRule(savedFillRule); - }, - fillStroke: function canvasGraphicsFillStroke() { - var ctx = this.ctx; - - var fillColor = this.current.fillColor; - if (fillColor && fillColor.hasOwnProperty('type') && - fillColor.type === 'Pattern') { - ctx.save(); - ctx.fillStyle = fillColor.getPattern(ctx); - ctx.fill(); - ctx.restore(); - } else { - ctx.fill(); - } - - var strokeColor = this.current.strokeColor; - if (strokeColor && strokeColor.hasOwnProperty('type') && - strokeColor.type === 'Pattern') { - ctx.save(); - ctx.strokeStyle = strokeColor.getPattern(ctx); - ctx.stroke(); - ctx.restore(); - } else { - ctx.stroke(); - } - - this.consumePath(); - }, - eoFillStroke: function canvasGraphicsEoFillStroke() { - var savedFillRule = this.setEOFillRule(); - this.fillStroke(); - this.restoreFillRule(savedFillRule); - }, - closeFillStroke: function canvasGraphicsCloseFillStroke() { - this.closePath(); - this.fillStroke(); - }, - closeEOFillStroke: function canvasGraphicsCloseEOFillStroke() { - var savedFillRule = this.setEOFillRule(); - this.closePath(); - this.fillStroke(); - this.restoreFillRule(savedFillRule); - }, - endPath: function canvasGraphicsEndPath() { - this.consumePath(); - }, - - // Clipping - clip: function canvasGraphicsClip() { - this.pendingClip = NORMAL_CLIP; - }, - eoClip: function canvasGraphicsEoClip() { - this.pendingClip = EO_CLIP; - }, - - // Text - beginText: function canvasGraphicsBeginText() { - this.current.textMatrix = IDENTITY_MATRIX; - this.current.x = this.current.lineX = 0; - this.current.y = this.current.lineY = 0; - }, - endText: function canvasGraphicsEndText() { - }, - setCharSpacing: function canvasGraphicsSetCharSpacing(spacing) { - this.current.charSpacing = spacing; - }, - setWordSpacing: function canvasGraphicsSetWordSpacing(spacing) { - this.current.wordSpacing = spacing; - }, - setHScale: function canvasGraphicsSetHScale(scale) { - this.current.textHScale = scale / 100; - }, - setLeading: function canvasGraphicsSetLeading(leading) { - this.current.leading = -leading; - }, - setFont: function canvasGraphicsSetFont(fontRefName, size) { - var fontObj = this.objs.get(fontRefName).fontObj; - - if (!fontObj) { - throw 'Can\'t find font for ' + fontRefName; - } - - var name = fontObj.loadedName || 'sans-serif'; - - this.current.font = fontObj; - this.current.fontSize = size; - - var name = fontObj.loadedName || 'sans-serif'; - var bold = fontObj.black ? (fontObj.bold ? 'bolder' : 'bold') : - (fontObj.bold ? 'bold' : 'normal'); - - var italic = fontObj.italic ? 'italic' : 'normal'; - var serif = fontObj.serif ? 'serif' : 'sans-serif'; - var typeface = '"' + name + '", ' + serif; - var rule = italic + ' ' + bold + ' ' + size + 'px ' + typeface; - this.ctx.font = rule; - }, - setTextRenderingMode: function canvasGraphicsSetTextRenderingMode(mode) { - TODO('text rendering mode: ' + mode); - }, - setTextRise: function canvasGraphicsSetTextRise(rise) { - TODO('text rise: ' + rise); - }, - moveText: function canvasGraphicsMoveText(x, y) { - this.current.x = this.current.lineX += x; - this.current.y = this.current.lineY += y; - }, - setLeadingMoveText: function canvasGraphicsSetLeadingMoveText(x, y) { - this.setLeading(-y); - this.moveText(x, y); - }, - setTextMatrix: function canvasGraphicsSetTextMatrix(a, b, c, d, e, f) { - this.current.textMatrix = [a, b, c, d, e, f]; - - this.current.x = this.current.lineX = 0; - this.current.y = this.current.lineY = 0; - }, - nextLine: function canvasGraphicsNextLine() { - this.moveText(0, this.current.leading); - }, - showText: function canvasGraphicsShowText(text) { - var ctx = this.ctx; - var current = this.current; - var font = current.font; - var glyphs = font.charsToGlyphs(text); - var fontSize = current.fontSize; - var charSpacing = current.charSpacing; - var wordSpacing = current.wordSpacing; - var textHScale = current.textHScale; - var glyphsLength = glyphs.length; - if (font.coded) { - ctx.save(); - ctx.transform.apply(ctx, current.textMatrix); - ctx.translate(current.x, current.y); - - var fontMatrix = font.fontMatrix || IDENTITY_MATRIX; - ctx.scale(1 / textHScale, 1); - for (var i = 0; i < glyphsLength; ++i) { - - var glyph = glyphs[i]; - if (glyph === null) { - // word break - this.ctx.translate(wordSpacing, 0); - continue; - } - - this.save(); - ctx.scale(fontSize, fontSize); - ctx.transform.apply(ctx, fontMatrix); - this.executeIRQueue(glyph.IRQueue); - this.restore(); - - var transformed = Util.applyTransform([glyph.width, 0], fontMatrix); - var width = transformed[0] * fontSize + charSpacing; - - ctx.translate(width, 0); - current.x += width; - - } - ctx.restore(); - } else { - ctx.save(); - ctx.transform.apply(ctx, current.textMatrix); - ctx.scale(1, -1); - ctx.translate(current.x, -1 * current.y); - ctx.transform.apply(ctx, font.fontMatrix || IDENTITY_MATRIX); - - ctx.scale(1 / textHScale, 1); - - var width = 0; - for (var i = 0; i < glyphsLength; ++i) { - var glyph = glyphs[i]; - if (glyph === null) { - // word break - width += wordSpacing; - continue; - } - - var unicode = glyph.unicode; - var char = (unicode >= 0x10000) ? - String.fromCharCode(0xD800 | ((unicode - 0x10000) >> 10), - 0xDC00 | (unicode & 0x3FF)) : String.fromCharCode(unicode); - - ctx.fillText(char, width, 0); - width += glyph.width * fontSize * 0.001 + charSpacing; - } - current.x += width; - - ctx.restore(); - } - }, - - showSpacedText: function canvasGraphicsShowSpacedText(arr) { - var ctx = this.ctx; - var current = this.current; - var fontSize = current.fontSize; - var textHScale = current.textHScale; - var arrLength = arr.length; - for (var i = 0; i < arrLength; ++i) { - var e = arr[i]; - if (isNum(e)) { - current.x -= e * 0.001 * fontSize * textHScale; - } else if (isString(e)) { - this.showText(e); - } else { - malformed('TJ array element ' + e + ' is not string or num'); - } - } - }, - nextLineShowText: function canvasGraphicsNextLineShowText(text) { - this.nextLine(); - this.showText(text); - }, - nextLineSetSpacingShowText: - function canvasGraphicsNextLineSetSpacingShowText(wordSpacing, - charSpacing, - text) { - this.setWordSpacing(wordSpacing); - this.setCharSpacing(charSpacing); - this.nextLineShowText(text); - }, - - // Type3 fonts - setCharWidth: function canvasGraphicsSetCharWidth(xWidth, yWidth) { - // We can safely ignore this since the width should be the same - // as the width in the Widths array. - }, - setCharWidthAndBounds: function canvasGraphicsSetCharWidthAndBounds(xWidth, - yWidth, - llx, - lly, - urx, - ury) { - // TODO According to the spec we're also suppose to ignore any operators - // that set color or include images while processing this type3 font. - this.rectangle(llx, lly, urx - llx, ury - lly); - this.clip(); - this.endPath(); - }, - - // Color - setStrokeColorSpace: - function canvasGraphicsSetStrokeColorSpacefunction(raw) { - this.current.strokeColorSpace = ColorSpace.fromIR(raw); - }, - setFillColorSpace: function canvasGraphicsSetFillColorSpace(raw) { - this.current.fillColorSpace = ColorSpace.fromIR(raw); - }, - setStrokeColor: function canvasGraphicsSetStrokeColor(/*...*/) { - var cs = this.current.strokeColorSpace; - var color = cs.getRgb(arguments); - this.setStrokeRGBColor.apply(this, color); - }, - getColorN_IR_Pattern: function(IR, cs) { - if (IR[0] == 'TilingPattern') { - var args = IR[1]; - var base = cs.base; - var color; - if (base) { - var baseComps = base.numComps; - - color = []; - for (var i = 0; i < baseComps; ++i) - color.push(args[i]); - - color = base.getRgb(color); - } - var pattern = new TilingPattern(IR, color, this.ctx, this.objs); - } else if (IR[0] == 'RadialAxialShading' || IR[0] == 'DummyShading') { - var pattern = Pattern.shadingFromIR(this.ctx, IR); - } else { - throw 'Unkown IR type'; - } - return pattern; - }, - setStrokeColorN_IR: function canvasGraphicsSetStrokeColorN(/*...*/) { - var cs = this.current.strokeColorSpace; - - if (cs.name == 'Pattern') { - this.current.strokeColor = this.getColorN_IR_Pattern(arguments, cs); - } else { - this.setStrokeColor.apply(this, arguments); - } - }, - setFillColor: function canvasGraphicsSetFillColor(/*...*/) { - var cs = this.current.fillColorSpace; - var color = cs.getRgb(arguments); - this.setFillRGBColor.apply(this, color); - }, - setFillColorN_IR: function canvasGraphicsSetFillColorN(/*...*/) { - var cs = this.current.fillColorSpace; - - if (cs.name == 'Pattern') { - this.current.fillColor = this.getColorN_IR_Pattern(arguments, cs); - } else { - this.setFillColor.apply(this, arguments); - } - }, - setStrokeGray: function canvasGraphicsSetStrokeGray(gray) { - this.setStrokeRGBColor(gray, gray, gray); - }, - setFillGray: function canvasGraphicsSetFillGray(gray) { - this.setFillRGBColor(gray, gray, gray); - }, - setStrokeRGBColor: function canvasGraphicsSetStrokeRGBColor(r, g, b) { - var color = Util.makeCssRgb(r, g, b); - this.ctx.strokeStyle = color; - this.current.strokeColor = color; - }, - setFillRGBColor: function canvasGraphicsSetFillRGBColor(r, g, b) { - var color = Util.makeCssRgb(r, g, b); - this.ctx.fillStyle = color; - this.current.fillColor = color; - }, - setStrokeCMYKColor: function canvasGraphicsSetStrokeCMYKColor(c, m, y, k) { - var color = Util.makeCssCmyk(c, m, y, k); - this.ctx.strokeStyle = color; - this.current.strokeColor = color; - }, - setFillCMYKColor: function canvasGraphicsSetFillCMYKColor(c, m, y, k) { - var color = Util.makeCssCmyk(c, m, y, k); - this.ctx.fillStyle = color; - this.current.fillColor = color; - }, - - shadingFill: function canvasGraphicsShadingFill(patternIR) { - var ctx = this.ctx; - - this.save(); - ctx.fillStyle = Pattern.shadingFromIR(ctx, patternIR); - - var inv = ctx.mozCurrentTransformInverse; - if (inv) { - var canvas = ctx.canvas; - var width = canvas.width; - var height = canvas.height; - - var bl = Util.applyTransform([0, 0], inv); - var br = Util.applyTransform([0, width], inv); - var ul = Util.applyTransform([height, 0], inv); - var ur = Util.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(); - }, - - // Images - beginInlineImage: function canvasGraphicsBeginInlineImage() { - error('Should not call beginInlineImage'); - }, - beginImageData: function canvasGraphicsBeginImageData() { - error('Should not call beginImageData'); - }, - - paintFormXObjectBegin: - function canvasGraphicsPaintFormXObject(matrix, bbox) { - this.save(); - - if (matrix && isArray(matrix) && 6 == matrix.length) - this.transform.apply(this, matrix); - - if (bbox && isArray(bbox) && 4 == bbox.length) { - var width = bbox[2] - bbox[0]; - var height = bbox[3] - bbox[1]; - this.rectangle(bbox[0], bbox[1], width, height); - this.clip(); - this.endPath(); - } - }, - - paintFormXObjectEnd: function() { - this.restore(); - }, - - paintJpegXObject: function(objId, w, h) { - var image = this.objs.get(objId); - if (!image) { - error('Dependent image isn\'t ready yet'); - } - - this.save(); - - var ctx = this.ctx; - // scale the image to the unit square - ctx.scale(1 / w, -1 / h); - - var domImage = image.getImage(); - ctx.drawImage(domImage, 0, 0, domImage.width, domImage.height, - 0, -h, w, h); - - this.restore(); - }, - - paintImageMaskXObject: function(imgArray, inverseDecode, width, height) { - function applyStencilMask(buffer, inverseDecode) { - var imgArrayPos = 0; - var i, j, mask, buf; - // removing making non-masked pixels transparent - var bufferPos = 3; // alpha component offset - for (i = 0; i < height; i++) { - mask = 0; - for (j = 0; j < width; j++) { - if (!mask) { - buf = imgArray[imgArrayPos++]; - mask = 128; - } - if (!(buf & mask) == inverseDecode) { - buffer[bufferPos] = 0; - } - bufferPos += 4; - mask >>= 1; - } - } - } - - this.save(); - - var ctx = this.ctx; - var w = width, h = height; - // scale the image to the unit square - ctx.scale(1 / w, -1 / h); - - var tmpCanvas = new this.ScratchCanvas(w, h); - var tmpCtx = tmpCanvas.getContext('2d'); - - var fillColor = this.current.fillColor; - tmpCtx.fillStyle = (fillColor && fillColor.hasOwnProperty('type') && - fillColor.type === 'Pattern') ? - fillColor.getPattern(tmpCtx) : fillColor; - tmpCtx.fillRect(0, 0, w, h); - - var imgData = tmpCtx.getImageData(0, 0, w, h); - var pixels = imgData.data; - - applyStencilMask(pixels, inverseDecode); - - tmpCtx.putImageData(imgData, 0, 0); - ctx.drawImage(tmpCanvas, 0, -h); - this.restore(); - }, - - paintImageXObject: function(imgData) { - this.save(); - var ctx = this.ctx; - var w = imgData.width; - var h = imgData.height; - // scale the image to the unit square - ctx.scale(1 / w, -1 / h); - - var tmpCanvas = new this.ScratchCanvas(w, h); - var tmpCtx = tmpCanvas.getContext('2d'); - var tmpImgData; - - // Some browsers can set an UInt8Array directly as imageData, some - // can't. As long as we don't have proper feature detection, just - // copy over each pixel and set the imageData that way. - tmpImgData = tmpCtx.getImageData(0, 0, w, h); - - // Copy over the imageData. - var tmpImgDataPixels = tmpImgData.data; - var len = tmpImgDataPixels.length; - - while (len--) { - tmpImgDataPixels[len] = imgData.data[len]; - } - - tmpCtx.putImageData(tmpImgData, 0, 0); - ctx.drawImage(tmpCanvas, 0, -h); - this.restore(); - }, - - // Marked content - - markPoint: function canvasGraphicsMarkPoint(tag) { - TODO('Marked content'); - }, - markPointProps: function canvasGraphicsMarkPointProps(tag, properties) { - TODO('Marked content'); - }, - beginMarkedContent: function canvasGraphicsBeginMarkedContent(tag) { - TODO('Marked content'); - }, - beginMarkedContentProps: - function canvasGraphicsBeginMarkedContentProps(tag, properties) { - TODO('Marked content'); - }, - endMarkedContent: function canvasGraphicsEndMarkedContent() { - TODO('Marked content'); - }, - - // Compatibility - - beginCompat: function canvasGraphicsBeginCompat() { - TODO('ignore undefined operators (should we do that anyway?)'); - }, - endCompat: function canvasGraphicsEndCompat() { - TODO('stop ignoring undefined operators'); - }, - - // Helper functions - - consumePath: function canvasGraphicsConsumePath() { - 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(); - }, - // We generally keep the canvas context set for - // nonzero-winding, and just set evenodd for the operations - // that need them. - setEOFillRule: function canvasGraphicsSetEOFillRule() { - var savedFillRule = this.ctx.mozFillRule; - this.ctx.mozFillRule = 'evenodd'; - return savedFillRule; - }, - restoreFillRule: function canvasGraphicsRestoreFillRule(rule) { - this.ctx.mozFillRule = rule; - } - }; - - return constructor; -})(); - -var Util = (function utilUtil() { - function constructor() {} - constructor.makeCssRgb = function makergb(r, g, b) { - var ri = (255 * r) | 0, gi = (255 * g) | 0, bi = (255 * b) | 0; - return 'rgb(' + ri + ',' + gi + ',' + bi + ')'; - }; - constructor.makeCssCmyk = function makecmyk(c, m, y, k) { - c = (new DeviceCmykCS()).getRgb([c, m, y, k]); - var ri = (255 * c[0]) | 0, gi = (255 * c[1]) | 0, bi = (255 * c[2]) | 0; - return 'rgb(' + ri + ',' + gi + ',' + bi + ')'; - }; - constructor.applyTransform = function apply(p, m) { - var xt = p[0] * m[0] + p[1] * m[2] + m[4]; - var yt = p[0] * m[1] + p[1] * m[3] + m[5]; - return [xt, yt]; - }; - - return constructor; -})(); - -var ColorSpace = (function colorSpaceColorSpace() { - // Constructor should define this.numComps, this.defaultColor, this.name - function constructor() { - error('should not call ColorSpace constructor'); - } - - 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: ' + color); - }, - // 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: ' + input); - } - }; - - constructor.parse = function colorspace_parse(cs, xref, res) { - var IR = constructor.parseToIR(cs, xref, res, true); - if (IR instanceof SeparationCS) - return IR; - - return constructor.fromIR(IR); - }; - - constructor.fromIR = function(IR) { - var name; - if (isArray(IR)) { - name = IR[0]; - } else { - name = IR; - } - - switch (name) { - case 'DeviceGrayCS': - return new DeviceGrayCS(); - case 'DeviceRgbCS': - return new DeviceRgbCS(); - case 'DeviceCmykCS': - return new DeviceCmykCS(); - case 'PatternCS': - var baseCS = IR[1]; - if (baseCS == null) { - return new PatternCS(null); - } else { - return new PatternCS(ColorSpace.fromIR(baseCS)); - } - case 'IndexedCS': - var baseCS = IR[1]; - var hiVal = IR[2]; - var lookup = IR[3]; - return new IndexedCS(ColorSpace.fromIR(baseCS), hiVal, lookup); - case 'SeparationCS': - var alt = IR[1]; - var tintFnIR = IR[2]; - - return new SeparationCS( - ColorSpace.fromIR(alt), - PDFFunction.fromIR(tintFnIR) - ); - default: - error('Unkown name ' + name); - } - return null; - } - - constructor.parseToIR = function colorspace_parse(cs, xref, res, parseOnly) { - if (isName(cs)) { - var colorSpaces = xref.fetchIfRef(res.get('ColorSpace')); - if (isDict(colorSpaces)) { - var refcs = colorSpaces.get(cs.name); - if (refcs) - cs = refcs; - } - } - - cs = xref.fetchIfRef(cs); - - if (isName(cs)) { - var mode = cs.name; - this.mode = mode; - - switch (mode) { - case 'DeviceGray': - case 'G': - return 'DeviceGrayCS'; - case 'DeviceRGB': - case 'RGB': - return 'DeviceRgbCS'; - case 'DeviceCMYK': - case 'CMYK': - return 'DeviceCmykCS'; - case 'Pattern': - return ['PatternCS', null]; - default: - error('unrecognized colorspace ' + mode); - } - } else if (isArray(cs)) { - var mode = cs[0].name; - this.mode = mode; - - switch (mode) { - case 'DeviceGray': - case 'G': - return 'DeviceGrayCS'; - case 'DeviceRGB': - case 'RGB': - return 'DeviceRgbCS'; - case 'DeviceCMYK': - case 'CMYK': - return 'DeviceCmykCS'; - case 'CalGray': - return 'DeviceGrayCS'; - case 'CalRGB': - return 'DeviceRgbCS'; - case 'ICCBased': - var stream = xref.fetchIfRef(cs[1]); - var dict = stream.dict; - var numComps = dict.get('N'); - if (numComps == 1) - return 'DeviceGrayCS'; - if (numComps == 3) - return 'DeviceRgbCS'; - if (numComps == 4) - return 'DeviceCmykCS'; - break; - case 'Pattern': - var baseCS = cs[1]; - if (baseCS) - baseCS = ColorSpace.parseToIR(baseCS, xref, res); - return ['PatternCS', baseCS]; - case 'Indexed': - var baseCS = ColorSpace.parseToIR(cs[1], xref, res); - var hiVal = cs[2] + 1; - var lookup = xref.fetchIfRef(cs[3]); - return ['IndexedCS', baseCS, hiVal, lookup]; - case 'Separation': - var alt = ColorSpace.parseToIR(cs[2], xref, res); - var tintFnIR = PDFFunction.getIR(xref, xref.fetchIfRef(cs[3])); - return ['SeparationCS', alt, tintFnIR]; - case 'Lab': - case 'DeviceN': - default: - error('unimplemented color space object "' + mode + '"'); - } - } else { - error('unrecognized color space object: "' + cs + '"'); - } - return null; - }; - - return constructor; -})(); - -var SeparationCS = (function separationCS() { - 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(color); - return this.base.getRgb(tinted); - }, - getRgbBuffer: function sepcs_getRgbBuffer(input, bits) { - var tintFn = this.tintFn; - var base = this.base; - var scale = 1 / ((1 << bits) - 1); - - var length = input.length; - var pos = 0; - - var numComps = base.numComps; - var baseBuf = new Uint8Array(numComps * length); - for (var i = 0; i < length; ++i) { - var scaled = input[i] * scale; - var tinted = tintFn([scaled]); - for (var j = 0; j < numComps; ++j) - baseBuf[pos++] = 255 * tinted[j]; - } - return base.getRgbBuffer(baseBuf, 8); - } - }; - - return constructor; -})(); - -var PatternCS = (function patternCS() { - function constructor(baseCS) { - this.name = 'Pattern'; - this.base = baseCS; - } - constructor.prototype = {}; - - return constructor; -})(); - -var IndexedCS = (function indexedCS() { - function constructor(base, highVal, lookup) { - this.name = 'Indexed'; - this.numComps = 1; - this.defaultColor = [0]; - - 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: ' + lookup); - } - this.lookup = lookupArray; - } - - constructor.prototype = { - getRgb: function indexcs_getRgb(color) { - var numComps = this.base.numComps; - - 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 base.getRgbBuffer(baseBuf, 8); - } - }; - return constructor; -})(); - -var DeviceGrayCS = (function deviceGrayCS() { - 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, bits) { - var scale = 255 / ((1 << bits) - 1); - var length = input.length; - var rgbBuf = new Uint8Array(length * 3); - for (var i = 0, j = 0; i < length; ++i) { - var c = (scale * input[i]) | 0; - rgbBuf[j++] = c; - rgbBuf[j++] = c; - rgbBuf[j++] = c; - } - return rgbBuf; - } - }; - return constructor; -})(); - -var DeviceRgbCS = (function deviceRgbCS() { - function constructor(bits) { - 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, bits) { - if (bits == 8) - return input; - var scale = 255 / ((1 << bits) - 1); - var i, length = input.length; - var rgbBuf = new Uint8Array(length); - for (i = 0; i < length; ++i) - rgbBuf[i] = (scale * input[i]) | 0; - return rgbBuf; - } - }; - return constructor; -})(); - -var DeviceCmykCS = (function deviceCmykCS() { - 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, bits) { - var scale = 1 / ((1 << bits) - 1); - 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(scale * colorBuf[colorBufPos++]); - - var rgb = this.getRgb(cmyk); - for (var j = 0; j < 3; ++j) - rgbBuf[rgbBufPos++] = Math.round(rgb[j] * 255); - } - - return rgbBuf; - } - }; - return constructor; -})(); - -var Pattern = (function patternPattern() { - // Constructor should define this.getPattern - function constructor() { - error('should not call Pattern constructor'); - } - - constructor.prototype = { - // Input: current Canvas context - // Output: the appropriate fillStyle or strokeStyle - getPattern: function pattern_getStyle(ctx) { - error('Should not call Pattern.getStyle: ' + ctx); - } - }; - - constructor.shadingFromIR = function pattern_shadingFromIR(ctx, raw) { - var obj = window[raw[0]]; - return obj.fromIR(ctx, raw); - } - - constructor.parseShading = function pattern_shading(shading, matrix, - xref, res, ctx) { - - var dict = isStream(shading) ? shading.dict : shading; - var type = dict.get('ShadingType'); - - switch (type) { - case 2: - case 3: - // both radial and axial shadings are handled by RadialAxial shading - return new RadialAxialShading(dict, matrix, xref, res, ctx); - default: - return new DummyShading(); - } - }; - return constructor; -})(); - -var DummyShading = (function dummyShading() { - function constructor() { - this.type = 'Pattern'; - } - - constructor.fromIR = function() { - return 'hotpink'; - } - - constructor.prototype = { - getIR: function dummpy_getir() { - return ['DummyShading']; - } - }; - return constructor; -})(); - -// Radial and axial shading have very similar implementations -// If needed, the implementations can be broken into two classes -var RadialAxialShading = (function radialAxialShading() { - function constructor(dict, matrix, xref, res, ctx) { - this.matrix = matrix; - this.coordsArr = dict.get('Coords'); - this.shadingType = dict.get('ShadingType'); - this.type = 'Pattern'; - - this.ctx = ctx; - var cs = dict.get('ColorSpace', 'CS'); - cs = ColorSpace.parse(cs, xref, res); - this.cs = cs; - - var t0 = 0.0, t1 = 1.0; - if (dict.has('Domain')) { - var domainArr = dict.get('Domain'); - t0 = domainArr[0]; - t1 = domainArr[1]; - } - - var extendStart = false, extendEnd = false; - if (dict.has('Extend')) { - var extendArr = dict.get('Extend'); - extendStart = extendArr[0]; - extendEnd = extendArr[1]; - TODO('Support extend'); - } - - this.extendStart = extendStart; - this.extendEnd = extendEnd; - - var fnObj = dict.get('Function'); - fnObj = xref.fetchIfRef(fnObj); - if (isArray(fnObj)) - error('No support for array of functions'); - else if (!isPDFFunction(fnObj)) - error('Invalid function'); - var fn = PDFFunction.parse(xref, fnObj); - - // 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; - - var colorStops = []; - for (var i = t0; i <= t1; i += step) { - var color = fn([i]); - var rgbColor = Util.makeCssRgb.apply(this, cs.getRgb(color)); - colorStops.push([(i - t0) / diff, rgbColor]); - } - - this.colorStops = colorStops; - } - - constructor.fromIR = function(ctx, raw) { - var type = raw[1]; - var colorStops = raw[2]; - var p0 = raw[3]; - var p1 = raw[4]; - var r0 = raw[5]; - var r1 = raw[6]; - - var curMatrix = ctx.mozCurrentTransform; - if (curMatrix) { - var userMatrix = ctx.mozCurrentTransformInverse; - - p0 = Util.applyTransform(p0, curMatrix); - p0 = Util.applyTransform(p0, userMatrix); - - p1 = Util.applyTransform(p1, curMatrix); - p1 = Util.applyTransform(p1, userMatrix); - } - - if (type == 2) - var grad = ctx.createLinearGradient(p0[0], p0[1], p1[0], p1[1]); - else if (type == 3) - var grad = ctx.createRadialGradient(p0[0], p0[1], r0, p1[0], p1[1], r1); - - for (var i = 0, ii = colorStops.length; i < ii; ++i) { - var c = colorStops[i]; - grad.addColorStop(c[0], c[1]); - } - return grad; - } - - constructor.prototype = { - getIR: function RadialAxialShading_getIR() { - var coordsArr = this.coordsArr; - var type = this.shadingType; - if (type == 2) { - var p0 = [coordsArr[0], coordsArr[1]]; - var p1 = [coordsArr[2], coordsArr[3]]; - var r0 = null; - var r1 = null; - } else if (type == 3) { - var p0 = [coordsArr[0], coordsArr[1]]; - var p1 = [coordsArr[3], coordsArr[4]]; - var r0 = coordsArr[2]; - var r1 = coordsArr[5]; - } else { - error('getPattern type unknown: ' + type); - } - - var matrix = this.matrix; - if (matrix) { - p0 = Util.applyTransform(p0, matrix); - p1 = Util.applyTransform(p1, matrix); - } - - return ['RadialAxialShading', type, this.colorStops, p0, p1, r0, r1]; - } - }; - - return constructor; -})(); - -var TilingPattern = (function tilingPattern() { - var PAINT_TYPE_COLORED = 1, PAINT_TYPE_UNCOLORED = 2; - - function TilingPattern(IR, color, ctx, objs) { - var IRQueue = IR[2]; - this.matrix = IR[3]; - var bbox = IR[4]; - var xstep = IR[5]; - var ystep = IR[6]; - var paintType = IR[7]; - - TODO('TilingType'); - - this.curMatrix = ctx.mozCurrentTransform; - this.invMatrix = ctx.mozCurrentTransformInverse; - this.ctx = ctx; - this.type = 'Pattern'; - - var x0 = bbox[0], y0 = bbox[1], x1 = bbox[2], y1 = bbox[3]; - - var topLeft = [x0, y0]; - // we want the canvas to be as large as the step size - var botRight = [x0 + xstep, y0 + ystep]; - - var width = botRight[0] - topLeft[0]; - var height = botRight[1] - topLeft[1]; - - // TODO: hack to avoid OOM, we would idealy compute the tiling - // pattern to be only as large as the acual size in device space - // This could be computed with .mozCurrentTransform, but still - // needs to be implemented - while (Math.abs(width) > 512 || Math.abs(height) > 512) { - width = 512; - height = 512; - } - - var tmpCanvas = new ScratchCanvas(width, height); - - // set the new canvas element context as the graphics context - var tmpCtx = tmpCanvas.getContext('2d'); - var graphics = new CanvasGraphics(tmpCtx, objs); - - switch (paintType) { - case PAINT_TYPE_COLORED: - tmpCtx.fillStyle = ctx.fillStyle; - tmpCtx.strokeStyle = ctx.strokeStyle; - break; - case PAINT_TYPE_UNCOLORED: - color = Util.makeCssRgb.apply(this, color); - tmpCtx.fillStyle = color; - tmpCtx.strokeStyle = color; - break; - default: - error('Unsupported paint type: ' + paintType); - } - - var scale = [width / xstep, height / ystep]; - this.scale = scale; - - // transform coordinates to pattern space - var tmpTranslate = [1, 0, 0, 1, -topLeft[0], -topLeft[1]]; - var tmpScale = [scale[0], 0, 0, scale[1], 0, 0]; - graphics.transform.apply(graphics, tmpScale); - graphics.transform.apply(graphics, tmpTranslate); - - if (bbox && isArray(bbox) && 4 == bbox.length) { - var bboxWidth = bbox[2] - bbox[0]; - var bboxHeight = bbox[3] - bbox[1]; - graphics.rectangle(bbox[0], bbox[1], bboxWidth, bboxHeight); - graphics.clip(); - graphics.endPath(); - } - - graphics.executeIRQueue(IRQueue); - - this.canvas = tmpCanvas; - } - - TilingPattern.getIR = function tiling_getIR(codeIR, dict, args) { - var matrix = dict.get('Matrix'); - var bbox = dict.get('BBox'); - var xstep = dict.get('XStep'); - var ystep = dict.get('YStep'); - var paintType = dict.get('PaintType'); - - return [ - 'TilingPattern', args, codeIR, matrix, bbox, xstep, ystep, paintType - ]; - } - - TilingPattern.prototype = { - getPattern: function tiling_getPattern() { - var matrix = this.matrix; - var curMatrix = this.curMatrix; - var ctx = this.ctx; - - if (curMatrix) - ctx.setTransform.apply(ctx, curMatrix); - - if (matrix) - ctx.transform.apply(ctx, matrix); - - var scale = this.scale; - ctx.scale(1 / scale[0], 1 / scale[1]); - - return ctx.createPattern(this.canvas, 'repeat'); - } - }; - - return TilingPattern; -})(); - -var PDFImage = (function pdfImage() { - 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: ' + this.width + ' or height: ' + - this.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.imageMask); - } - } - this.bpc = bitsPerComponent; - - if (!this.imageMask) { - var colorSpace = dict.get('ColorSpace', 'CS'); - if (!colorSpace) { - TODO('JPX images (which don"t require color spaces'); - colorSpace = new Name('DeviceRGB'); - } - this.colorSpace = ColorSpace.parse(colorSpace, xref, res); - this.numComps = this.colorSpace.numComps; - } - - this.decode = dict.get('Decode', 'D'); - - var mask = xref.fetchIfRef(dict.get('Mask')); - var smask = xref.fetchIfRef(dict.get('SMask')); - - if (mask) { - TODO('masked images'); - } else if (smask) { - this.smask = new PDFImage(xref, res, smask); - } - } - - constructor.prototype = { - getComponents: function getComponents(buffer, decodeMap) { - 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 = bpc <= 8 ? new Uint8Array(length) : - bpc <= 16 ? new Uint16Array(length) : new Uint32Array(length); - var rowComps = width * numComps; - - if (bpc == 1) { - var valueZero = 0, valueOne = 1; - if (decodeMap) { - valueZero = decodeMap[0] ? 1 : 0; - valueOne = decodeMap[1] ? 1 : 0; - } - 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; - } - - output[i] = !(buf & mask) ? valueZero : valueOne; - } - } else { - if (decodeMap != null) - TODO('interpolate component values'); - var bits = 0, buf = 0; - for (var i = 0, ii = length; i < ii; ++i) { - if (i % rowComps == 0) { - buf = 0; - bits = 0; - } - - while (bits < bpc) { - buf = (buf << 8) | buffer[bufferPos++]; - bits += 8; - } - - var remainingBits = bits - bpc; - output[i] = buf >> remainingBits; - buf = buf & ((1 << remainingBits) - 1); - bits = remainingBits; - } - } - return output; - }, - getOpacity: function getOpacity() { - var smask = this.smask; - var width = this.width; - var height = this.height; - var buf = new Uint8Array(width * height); - - if (smask) { - if (smask.image.getImage) { - // smask is a DOM image - var tempCanvas = new ScratchCanvas(width, height); - var tempCtx = tempCanvas.getContext('2d'); - var domImage = smask.image.getImage(); - tempCtx.drawImage(domImage, 0, 0, domImage.width, domImage.height, - 0, 0, width, height); - var data = tempCtx.getImageData(0, 0, width, height).data; - for (var i = 0, j = 0, ii = width * height; i < ii; ++i, j += 4) - buf[i] = data[j]; // getting first component value - return buf; - } - var sw = smask.width; - var sh = smask.height; - if (sw != this.width || sh != this.height) - error('smask dimensions do not match image dimensions: ' + sw + - ' != ' + this.width + ', ' + sh + ' != ' + this.height); - - smask.fillGrayBuffer(buf); - return buf; - } else { - for (var i = 0, ii = width * height; i < ii; ++i) - buf[i] = 255; - } - return buf; - }, - applyStencilMask: function applyStencilMask(buffer, inverseDecode) { - var width = this.width, height = this.height; - var bitStrideLength = (width + 7) >> 3; - this.image.reset(); - var imgArray = this.image.getBytes(bitStrideLength * height); - var imgArrayPos = 0; - var i, j, mask, buf; - // removing making non-masked pixels transparent - var bufferPos = 3; // alpha component offset - for (i = 0; i < height; i++) { - mask = 0; - for (j = 0; j < width; j++) { - if (!mask) { - buf = imgArray[imgArrayPos++]; - mask = 128; - } - if (!(buf & mask) == inverseDecode) { - buffer[bufferPos] = 0; - } - bufferPos += 4; - mask >>= 1; - } - } - }, - fillRgbaBuffer: function fillRgbaBuffer(buffer, decodeMap) { - 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; - this.image.reset(); - var imgArray = this.image.getBytes(height * rowBytes); - - var comps = this.colorSpace.getRgbBuffer( - this.getComponents(imgArray, decodeMap), bpc); - var compsPos = 0; - var opacity = this.getOpacity(); - var opacityPos = 0; - var length = width * height * 4; - - 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: ' + 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; - this.image.reset(); - 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; -})(); - -var PDFFunction = (function() { - var CONSTRUCT_SAMPLED = 0; - var CONSTRUCT_INTERPOLATED = 2; - var CONSTRUCT_STICHED = 3; - var CONSTRUCT_POSTSCRIPT = 4; - - return { - 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++) { - while (codeSize < bps) { - codeBuf <<= 8; - codeBuf |= strBytes[strIdx++]; - codeSize += 8; - } - codeSize -= bps; - array.push(codeBuf >> codeSize); - codeBuf &= (1 << codeSize) - 1; - } - return array; - }, - - getIR: function(xref, fn) { - var dict = fn.dict; - if (!dict) - dict = fn; - - 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'); - - return typeFn.call(this, fn, dict, xref); - }, - - fromIR: function(IR) { - var type = IR[0]; - switch (type) { - case CONSTRUCT_SAMPLED: - return this.constructSampledFromIR(IR); - case CONSTRUCT_INTERPOLATED: - return this.constructInterpolatedFromIR(IR); - case CONSTRUCT_STICHED: - return this.constructStichedFromIR(IR); - case CONSTRUCT_POSTSCRIPT: - default: - return this.constructPostScriptFromIR(IR); - } - }, - - parse: function(xref, fn) { - var IR = this.getIR(xref, fn); - return this.fromIR(IR); - }, - - constructSampled: function(str, dict) { - var domain = dict.get('Domain'); - var range = dict.get('Range'); - - if (!domain || !range) - error('No domain or range'); - - var inputSize = domain.length / 2; - var outputSize = range.length / 2; - - if (inputSize != 1) - error('No support for multi-variable inputs to functions: ' + - inputSize); - - 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: ' + order); - - 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); - - return [ - CONSTRUCT_SAMPLED, inputSize, domain, encode, decode, samples, size, - outputSize, bps, range - ]; - }, - - constructSampledFromIR: function(IR) { - var inputSize = IR[1]; - var domain = IR[2]; - var encode = IR[3]; - var decode = IR[4]; - var samples = IR[5]; - var size = IR[6]; - var outputSize = IR[7]; - var bps = IR[8]; - var range = IR[9]; - - return 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: ' + inputSize + ' != ' + - args.length); - - 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 = [], v = 0; - for (var i = 0; i < outputSize; ++i) { - if (ceil == floor) { - v = samples[ceil + i]; - } else { - var low = samples[floor + i]; - var high = samples[ceil + i]; - 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; - } - }, - - constructInterpolated: - function pdfFunctionConstructInterpolated(str, dict) { - var c0 = dict.get('C0') || [0]; - var c1 = dict.get('C1') || [1]; - var n = dict.get('N'); - - if (!isArray(c0) || !isArray(c1)) - error('Illegal dictionary for interpolated function'); - - var length = c0.length; - var diff = []; - for (var i = 0; i < length; ++i) - diff.push(c1[i] - c0[i]); - - return [CONSTRUCT_INTERPOLATED, c0, diff, n]; - }, - - constructInterpolatedFromIR: - function pdfFunctionconstructInterpolatedFromIR(IR) { - var c0 = IR[1]; - var diff = IR[2]; - var n = IR[3]; - - var length = diff.length; - - return function(args) { - var x = n == 1 ? args[0] : Math.pow(args[0], n); - - var out = []; - for (var j = 0; j < length; ++j) - out.push(c0[j] + (x * diff[j])); - - return out; - - } - }, - - constructStiched: function pdfFunctionConstructStiched(fn, dict, xref) { - var domain = dict.get('Domain'); - var range = dict.get('Range'); - - if (!domain) - error('No domain'); - - var inputSize = domain.length / 2; - if (inputSize != 1) - error('Bad domain for stiched function'); - - var fnRefs = dict.get('Functions'); - var fns = []; - for (var i = 0, ii = fnRefs.length; i < ii; ++i) - fns.push(PDFFunction.getIR(xref, xref.fetchIfRef(fnRefs[i]))); - - var bounds = dict.get('Bounds'); - var encode = dict.get('Encode'); - - return [CONSTRUCT_STICHED, domain, bounds, encode, fns]; - }, - - constructStichedFromIR: function pdfFunctionConstructStichedFromIR(IR) { - var domain = IR[1]; - var bounds = IR[2]; - var encode = IR[3]; - var fnsIR = IR[4]; - var fns = []; - - for (var i = 0; i < fnsIR.length; i++) { - fns.push(PDFFunction.fromIR(fnsIR[i])); - } - - return function(args) { - var clip = function(v, min, max) { - if (v > max) - v = max; - else if (v < min) - v = min; - return v; - }; - - // clip to domain - var v = clip(args[0], domain[0], domain[1]); - // calulate which bound the value is in - for (var i = 0, ii = bounds.length; i < ii; ++i) { - if (v < bounds[i]) - break; - } - - // encode value into domain of function - var dmin = domain[0]; - if (i > 0) - dmin = bounds[i - 1]; - var dmax = domain[1]; - if (i < bounds.length) - dmax = bounds[i]; - - var rmin = encode[2 * i]; - var rmax = encode[2 * i + 1]; - - var v2 = rmin + (v - dmin) * (rmax - rmin) / (dmax - dmin); - - // call the appropropriate function - return fns[i]([v2]); - }; - }, - - constructPostScript: function pdfFunctionConstructPostScript() { - return [CONSTRUCT_POSTSCRIPT]; - }, - - constructPostScriptFromIR: function pdfFunctionConstructPostScriptFromIR() { - TODO('unhandled type of function'); - return function() { - return [255, 105, 180]; - }; - } - }; -})(); - -/** - * A PDF document and page is built of many objects. E.g. there are objects - * for fonts, images, rendering code and such. These objects might get processed - * inside of a worker. The `PDFObjects` implements some basic functions to - * manage these objects. - */ -var PDFObjects = (function() { - function PDFObjects() { - this.objs = {}; - } - - PDFObjects.prototype = { - objs: null, - - /** - * Internal function. - * Ensures there is an object defined for `objId`. Stores `data` on the - * object *if* it is created. - */ - ensureObj: function(objId, data) { - if (this.objs[objId]) - return this.objs[objId]; - return this.objs[objId] = new Promise(objId, data); - }, - - /** - * If called *without* callback, this returns the data of `objId` but the - * object needs to be resolved. If it isn't, this function throws. - * - * If called *with* a callback, the callback is called with the data of the - * object once the object is resolved. That means, if you call this - * function and the object is already resolved, the callback gets called - * right away. - */ - get: function(objId, callback) { - // If there is a callback, then the get can be async and the object is - // not required to be resolved right now - if (callback) { - this.ensureObj(objId).then(callback); - return null; - } - - // If there isn't a callback, the user expects to get the resolved data - // directly. - var obj = this.objs[objId]; - - // If there isn't an object yet or the object isn't resolved, then the - // data isn't ready yet! - if (!obj || !obj.isResolved) { - throw 'Requesting object that isn\'t resolved yet ' + objId; - return null; - } - else - return obj.data; - }, - - /** - * Resolves the object `objId` with optional `data`. - */ - resolve: function(objId, data) { - var objs = this.objs; - - // In case there is a promise already on this object, just resolve it. - if (objs[objId]) { - objs[objId].resolve(data); - } else { - this.ensureObj(objId, data); - } - }, - - onData: function(objId, callback) { - this.ensureObj(objId).onData(callback); - }, - - isResolved: function(objId) { - var objs = this.objs; - if (!objs[objId]) { - return false; - } else { - return objs[objId].isResolved; - } - }, - - hasData: function(objId) { - var objs = this.objs; - if (!objs[objId]) { - return false; - } else { - return objs[objId].hasData; - } - }, - - /** - * Sets the data of an object but *doesn't* resolve it. - */ - setData: function(objId, data) { - // Watchout! If you call `this.ensureObj(objId, data)` you're going to - // create a *resolved* promise which shouldn't be the case! - this.ensureObj(objId).data = data; - } - }; - return PDFObjects; -})(); - -/** - * 'Promise' object. - * Each object that is stored in PDFObjects is based on a Promise object that - * contains the status of the object and the data. There migth be situations, - * where a function want to use the value of an object, but it isn't ready at - * that time. To get a notification, once the object is ready to be used, s.o. - * can add a callback using the `then` method on the promise that then calls - * the callback once the object gets resolved. - * A promise can get resolved only once and only once the data of the promise - * can be set. If any of these happens twice or the data is required before - * it was set, an exception is throw. - */ -var Promise = (function() { - var EMPTY_PROMISE = {}; - - /** - * If `data` is passed in this constructor, the promise is created resolved. - * If there isn't data, it isn't resolved at the beginning. - */ - function Promise(name, data) { - this.name = name; - // If you build a promise and pass in some data it's already resolved. - if (data != null) { - this.isResolved = true; - this._data = data; - this.hasData = true; - } else { - this.isResolved = false; - this._data = EMPTY_PROMISE; - } - this.callbacks = []; - }; - - Promise.prototype = { - hasData: false, - - set data(value) { - if (value === undefined) { - return; - } - if (this._data !== EMPTY_PROMISE) { - throw 'Promise ' + this.name + - ': Cannot set the data of a promise twice'; - } - this._data = value; - this.hasData = true; - - if (this.onDataCallback) { - this.onDataCallback(value); - } - }, - - get data() { - if (this._data === EMPTY_PROMISE) { - throw 'Promise ' + this.name + ': Cannot get data that isn\'t set'; - } - return this._data; - }, - - onData: function(callback) { - if (this._data !== EMPTY_PROMISE) { - callback(this._data); - } else { - this.onDataCallback = callback; - } - }, - - resolve: function(data) { - if (this.isResolved) { - throw 'A Promise can be resolved only once ' + this.name; - } - - this.isResolved = true; - this.data = data; - var callbacks = this.callbacks; - - for (var i = 0; i < callbacks.length; i++) { - callbacks[i].call(null, data); - } - }, - - then: function(callback) { - if (!callback) { - throw 'Requiring callback' + this.name; - } - - // If the promise is already resolved, call the callback directly. - if (this.isResolved) { - var data = this.data; - callback.call(null, data); - } else { - this.callbacks.push(callback); - } - } - }; - return Promise; -})(); diff --git a/src/canvas.js b/src/canvas.js new file mode 100644 index 000000000..23258b71f --- /dev/null +++ b/src/canvas.js @@ -0,0 +1,848 @@ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ + +'use strict'; + +// contexts store most of the state we need natively. +// However, PDF needs a bit more state, which we store here. + +var CanvasExtraState = (function canvasExtraState() { + function constructor(old) { + // 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.charSpacing = 0; + this.wordSpacing = 0; + this.textHScale = 1; + // Color spaces + this.fillColorSpace = new DeviceGrayCS; + this.fillColorSpaceObj = null; + this.strokeColorSpace = new DeviceGrayCS; + this.strokeColorSpaceObj = null; + this.fillColorObj = null; + this.strokeColorObj = null; + // Default fore and background colors + this.fillColor = '#000000'; + this.strokeColor = '#000000'; + + this.old = old; + } + + constructor.prototype = { + clone: function canvasextra_clone() { + return Object.create(this); + }, + setCurrentPoint: function canvasextra_setCurrentPoint(x, y) { + this.x = x; + this.y = y; + } + }; + return constructor; +})(); + +function ScratchCanvas(width, height) { + var canvas = document.createElement('canvas'); + canvas.width = width; + canvas.height = height; + return canvas; +} + +var CanvasGraphics = (function canvasGraphics() { + // Defines the time the executeIRQueue is going to be executing + // before it stops and shedules a continue of execution. + var kExecutionTime = 50; + + // Number of IR commands to execute before checking + // if we execute longer then `kExecutionTime`. + var kExecutionTimeCheck = 500; + + function constructor(canvasCtx, objs) { + this.ctx = canvasCtx; + this.current = new CanvasExtraState(); + this.stateStack = []; + this.pendingClip = null; + this.res = null; + this.xobjs = null; + this.ScratchCanvas = ScratchCanvas; + this.objs = objs; + } + + var LINE_CAP_STYLES = ['butt', 'round', 'square']; + var LINE_JOIN_STYLES = ['miter', 'round', 'bevel']; + var NORMAL_CLIP = {}; + var EO_CLIP = {}; + + constructor.prototype = { + beginDrawing: function canvasGraphicsBeginDrawing(mediaBox) { + var cw = this.ctx.canvas.width, ch = this.ctx.canvas.height; + this.ctx.save(); + switch (mediaBox.rotate) { + case 0: + this.ctx.transform(1, 0, 0, -1, 0, ch); + break; + case 90: + this.ctx.transform(0, 1, 1, 0, 0, 0); + break; + case 180: + this.ctx.transform(-1, 0, 0, 1, cw, 0); + break; + case 270: + this.ctx.transform(0, -1, -1, 0, cw, ch); + break; + } + this.ctx.scale(cw / mediaBox.width, ch / mediaBox.height); + }, + + executeIRQueue: function canvasGraphicsExecuteIRQueue(codeIR, + executionStartIdx, continueCallback) { + var argsArray = codeIR.argsArray; + var fnArray = codeIR.fnArray; + var i = executionStartIdx || 0; + var argsArrayLen = argsArray.length; + + var executionEndIdx; + var startTime = Date.now(); + + var objs = this.objs; + + do { + executionEndIdx = Math.min(argsArrayLen, i + kExecutionTimeCheck); + + for (i; i < executionEndIdx; i++) { + if (fnArray[i] !== 'dependency') { + this[fnArray[i]].apply(this, argsArray[i]); + } else { + var deps = argsArray[i]; + for (var n = 0; n < deps.length; n++) { + var depObjId = deps[n]; + + // If the promise isn't resolved yet, add the continueCallback + // to the promise and bail out. + if (!objs.isResolved(depObjId)) { + objs.get(depObjId, continueCallback); + return i; + } + } + } + } + + // If the entire IRQueue was executed, stop as were done. + if (i == argsArrayLen) { + return i; + } + + // If the execution took longer then a certain amount of time, shedule + // to continue exeution after a short delay. + // However, this is only possible if a 'continueCallback' is passed in. + if (continueCallback && (Date.now() - startTime) > kExecutionTime) { + setTimeout(continueCallback, 0); + return i; + } + + // If the IRQueue isn't executed completly yet OR the execution time + // was short enough, do another execution round. + } while (true); + }, + + endDrawing: function canvasGraphicsEndDrawing() { + this.ctx.restore(); + }, + + // Graphics state + setLineWidth: function canvasGraphicsSetLineWidth(width) { + this.ctx.lineWidth = width; + }, + setLineCap: function canvasGraphicsSetLineCap(style) { + this.ctx.lineCap = LINE_CAP_STYLES[style]; + }, + setLineJoin: function canvasGraphicsSetLineJoin(style) { + this.ctx.lineJoin = LINE_JOIN_STYLES[style]; + }, + setMiterLimit: function canvasGraphicsSetMiterLimit(limit) { + this.ctx.miterLimit = limit; + }, + setDash: function canvasGraphicsSetDash(dashArray, dashPhase) { + this.ctx.mozDash = dashArray; + this.ctx.mozDashOffset = dashPhase; + }, + setRenderingIntent: function canvasGraphicsSetRenderingIntent(intent) { + TODO('set rendering intent: ' + intent); + }, + setFlatness: function canvasGraphicsSetFlatness(flatness) { + TODO('set flatness: ' + flatness); + }, + setGState: function canvasGraphicsSetGState(states) { + for (var i = 0; i < states.length; i++) { + var state = states[i]; + var key = state[0]; + var value = state[1]; + + switch (key) { + case 'LW': + this.setLineWidth(value); + break; + case 'LC': + this.setLineCap(value); + break; + case 'LJ': + this.setLineJoin(value); + break; + case 'ML': + this.setMiterLimit(value); + break; + case 'D': + this.setDash(value[0], value[1]); + break; + case 'RI': + this.setRenderingIntent(value); + break; + case 'FL': + this.setFlatness(value); + break; + case 'Font': + this.setFont(state[1], state[2]); + break; + } + } + }, + save: function canvasGraphicsSave() { + this.ctx.save(); + var old = this.current; + this.stateStack.push(old); + this.current = old.clone(); + }, + restore: function canvasGraphicsRestore() { + var prev = this.stateStack.pop(); + if (prev) { + this.current = prev; + this.ctx.restore(); + } + }, + transform: function canvasGraphicsTransform(a, b, c, d, e, f) { + this.ctx.transform(a, b, c, d, e, f); + }, + + // Path + moveTo: function canvasGraphicsMoveTo(x, y) { + this.ctx.moveTo(x, y); + this.current.setCurrentPoint(x, y); + }, + lineTo: function canvasGraphicsLineTo(x, y) { + this.ctx.lineTo(x, y); + this.current.setCurrentPoint(x, y); + }, + curveTo: function canvasGraphicsCurveTo(x1, y1, x2, y2, x3, y3) { + this.ctx.bezierCurveTo(x1, y1, x2, y2, x3, y3); + this.current.setCurrentPoint(x3, y3); + }, + curveTo2: function canvasGraphicsCurveTo2(x2, y2, x3, y3) { + var current = this.current; + this.ctx.bezierCurveTo(current.x, current.y, x2, y2, x3, y3); + current.setCurrentPoint(x3, y3); + }, + curveTo3: function canvasGraphicsCurveTo3(x1, y1, x3, y3) { + this.curveTo(x1, y1, x3, y3, x3, y3); + this.current.setCurrentPoint(x3, y3); + }, + closePath: function canvasGraphicsClosePath() { + this.ctx.closePath(); + }, + rectangle: function canvasGraphicsRectangle(x, y, width, height) { + this.ctx.rect(x, y, width, height); + }, + stroke: function canvasGraphicsStroke() { + var ctx = this.ctx; + var strokeColor = this.current.strokeColor; + if (strokeColor && strokeColor.hasOwnProperty('type') && + strokeColor.type === 'Pattern') { + // for patterns, we transform to pattern space, calculate + // the pattern, call stroke, and restore to user space + ctx.save(); + ctx.strokeStyle = strokeColor.getPattern(ctx); + ctx.stroke(); + ctx.restore(); + } else { + ctx.stroke(); + } + + this.consumePath(); + }, + closeStroke: function canvasGraphicsCloseStroke() { + this.closePath(); + this.stroke(); + }, + fill: function canvasGraphicsFill() { + var ctx = this.ctx; + var fillColor = this.current.fillColor; + + if (fillColor && fillColor.hasOwnProperty('type') && + fillColor.type === 'Pattern') { + ctx.save(); + ctx.fillStyle = fillColor.getPattern(ctx); + ctx.fill(); + ctx.restore(); + } else { + ctx.fill(); + } + + this.consumePath(); + }, + eoFill: function canvasGraphicsEoFill() { + var savedFillRule = this.setEOFillRule(); + this.fill(); + this.restoreFillRule(savedFillRule); + }, + fillStroke: function canvasGraphicsFillStroke() { + var ctx = this.ctx; + + var fillColor = this.current.fillColor; + if (fillColor && fillColor.hasOwnProperty('type') && + fillColor.type === 'Pattern') { + ctx.save(); + ctx.fillStyle = fillColor.getPattern(ctx); + ctx.fill(); + ctx.restore(); + } else { + ctx.fill(); + } + + var strokeColor = this.current.strokeColor; + if (strokeColor && strokeColor.hasOwnProperty('type') && + strokeColor.type === 'Pattern') { + ctx.save(); + ctx.strokeStyle = strokeColor.getPattern(ctx); + ctx.stroke(); + ctx.restore(); + } else { + ctx.stroke(); + } + + this.consumePath(); + }, + eoFillStroke: function canvasGraphicsEoFillStroke() { + var savedFillRule = this.setEOFillRule(); + this.fillStroke(); + this.restoreFillRule(savedFillRule); + }, + closeFillStroke: function canvasGraphicsCloseFillStroke() { + this.closePath(); + this.fillStroke(); + }, + closeEOFillStroke: function canvasGraphicsCloseEOFillStroke() { + var savedFillRule = this.setEOFillRule(); + this.closePath(); + this.fillStroke(); + this.restoreFillRule(savedFillRule); + }, + endPath: function canvasGraphicsEndPath() { + this.consumePath(); + }, + + // Clipping + clip: function canvasGraphicsClip() { + this.pendingClip = NORMAL_CLIP; + }, + eoClip: function canvasGraphicsEoClip() { + this.pendingClip = EO_CLIP; + }, + + // Text + beginText: function canvasGraphicsBeginText() { + this.current.textMatrix = IDENTITY_MATRIX; + this.current.x = this.current.lineX = 0; + this.current.y = this.current.lineY = 0; + }, + endText: function canvasGraphicsEndText() { + }, + setCharSpacing: function canvasGraphicsSetCharSpacing(spacing) { + this.current.charSpacing = spacing; + }, + setWordSpacing: function canvasGraphicsSetWordSpacing(spacing) { + this.current.wordSpacing = spacing; + }, + setHScale: function canvasGraphicsSetHScale(scale) { + this.current.textHScale = scale / 100; + }, + setLeading: function canvasGraphicsSetLeading(leading) { + this.current.leading = -leading; + }, + setFont: function canvasGraphicsSetFont(fontRefName, size) { + var fontObj = this.objs.get(fontRefName).fontObj; + + if (!fontObj) { + throw 'Can\'t find font for ' + fontRefName; + } + + var name = fontObj.loadedName || 'sans-serif'; + + this.current.font = fontObj; + this.current.fontSize = size; + + var name = fontObj.loadedName || 'sans-serif'; + var bold = fontObj.black ? (fontObj.bold ? 'bolder' : 'bold') : + (fontObj.bold ? 'bold' : 'normal'); + + var italic = fontObj.italic ? 'italic' : 'normal'; + var serif = fontObj.serif ? 'serif' : 'sans-serif'; + var typeface = '"' + name + '", ' + serif; + var rule = italic + ' ' + bold + ' ' + size + 'px ' + typeface; + this.ctx.font = rule; + }, + setTextRenderingMode: function canvasGraphicsSetTextRenderingMode(mode) { + TODO('text rendering mode: ' + mode); + }, + setTextRise: function canvasGraphicsSetTextRise(rise) { + TODO('text rise: ' + rise); + }, + moveText: function canvasGraphicsMoveText(x, y) { + this.current.x = this.current.lineX += x; + this.current.y = this.current.lineY += y; + }, + setLeadingMoveText: function canvasGraphicsSetLeadingMoveText(x, y) { + this.setLeading(-y); + this.moveText(x, y); + }, + setTextMatrix: function canvasGraphicsSetTextMatrix(a, b, c, d, e, f) { + this.current.textMatrix = [a, b, c, d, e, f]; + + this.current.x = this.current.lineX = 0; + this.current.y = this.current.lineY = 0; + }, + nextLine: function canvasGraphicsNextLine() { + this.moveText(0, this.current.leading); + }, + showText: function canvasGraphicsShowText(text) { + var ctx = this.ctx; + var current = this.current; + var font = current.font; + var glyphs = font.charsToGlyphs(text); + var fontSize = current.fontSize; + var charSpacing = current.charSpacing; + var wordSpacing = current.wordSpacing; + var textHScale = current.textHScale; + var glyphsLength = glyphs.length; + if (font.coded) { + ctx.save(); + ctx.transform.apply(ctx, current.textMatrix); + ctx.translate(current.x, current.y); + + var fontMatrix = font.fontMatrix || IDENTITY_MATRIX; + ctx.scale(1 / textHScale, 1); + for (var i = 0; i < glyphsLength; ++i) { + + var glyph = glyphs[i]; + if (glyph === null) { + // word break + this.ctx.translate(wordSpacing, 0); + continue; + } + + this.save(); + ctx.scale(fontSize, fontSize); + ctx.transform.apply(ctx, fontMatrix); + this.executeIRQueue(glyph.IRQueue); + this.restore(); + + var transformed = Util.applyTransform([glyph.width, 0], fontMatrix); + var width = transformed[0] * fontSize + charSpacing; + + ctx.translate(width, 0); + current.x += width; + + } + ctx.restore(); + } else { + ctx.save(); + ctx.transform.apply(ctx, current.textMatrix); + ctx.scale(1, -1); + ctx.translate(current.x, -1 * current.y); + ctx.transform.apply(ctx, font.fontMatrix || IDENTITY_MATRIX); + + ctx.scale(1 / textHScale, 1); + + var width = 0; + for (var i = 0; i < glyphsLength; ++i) { + var glyph = glyphs[i]; + if (glyph === null) { + // word break + width += wordSpacing; + continue; + } + + var unicode = glyph.unicode; + var char = (unicode >= 0x10000) ? + String.fromCharCode(0xD800 | ((unicode - 0x10000) >> 10), + 0xDC00 | (unicode & 0x3FF)) : String.fromCharCode(unicode); + + ctx.fillText(char, width, 0); + width += glyph.width * fontSize * 0.001 + charSpacing; + } + current.x += width; + + ctx.restore(); + } + }, + + showSpacedText: function canvasGraphicsShowSpacedText(arr) { + var ctx = this.ctx; + var current = this.current; + var fontSize = current.fontSize; + var textHScale = current.textHScale; + var arrLength = arr.length; + for (var i = 0; i < arrLength; ++i) { + var e = arr[i]; + if (isNum(e)) { + current.x -= e * 0.001 * fontSize * textHScale; + } else if (isString(e)) { + this.showText(e); + } else { + malformed('TJ array element ' + e + ' is not string or num'); + } + } + }, + nextLineShowText: function canvasGraphicsNextLineShowText(text) { + this.nextLine(); + this.showText(text); + }, + nextLineSetSpacingShowText: + function canvasGraphicsNextLineSetSpacingShowText(wordSpacing, + charSpacing, + text) { + this.setWordSpacing(wordSpacing); + this.setCharSpacing(charSpacing); + this.nextLineShowText(text); + }, + + // Type3 fonts + setCharWidth: function canvasGraphicsSetCharWidth(xWidth, yWidth) { + // We can safely ignore this since the width should be the same + // as the width in the Widths array. + }, + setCharWidthAndBounds: function canvasGraphicsSetCharWidthAndBounds(xWidth, + yWidth, + llx, + lly, + urx, + ury) { + // TODO According to the spec we're also suppose to ignore any operators + // that set color or include images while processing this type3 font. + this.rectangle(llx, lly, urx - llx, ury - lly); + this.clip(); + this.endPath(); + }, + + // Color + setStrokeColorSpace: + function canvasGraphicsSetStrokeColorSpacefunction(raw) { + this.current.strokeColorSpace = ColorSpace.fromIR(raw); + }, + setFillColorSpace: function canvasGraphicsSetFillColorSpace(raw) { + this.current.fillColorSpace = ColorSpace.fromIR(raw); + }, + setStrokeColor: function canvasGraphicsSetStrokeColor(/*...*/) { + var cs = this.current.strokeColorSpace; + var color = cs.getRgb(arguments); + this.setStrokeRGBColor.apply(this, color); + }, + getColorN_IR_Pattern: function(IR, cs) { + if (IR[0] == 'TilingPattern') { + var args = IR[1]; + var base = cs.base; + var color; + if (base) { + var baseComps = base.numComps; + + color = []; + for (var i = 0; i < baseComps; ++i) + color.push(args[i]); + + color = base.getRgb(color); + } + var pattern = new TilingPattern(IR, color, this.ctx, this.objs); + } else if (IR[0] == 'RadialAxial' || IR[0] == 'Dummy') { + var pattern = Pattern.shadingFromIR(this.ctx, IR); + } else { + throw 'Unkown IR type'; + } + return pattern; + }, + setStrokeColorN_IR: function canvasGraphicsSetStrokeColorN(/*...*/) { + var cs = this.current.strokeColorSpace; + + if (cs.name == 'Pattern') { + this.current.strokeColor = this.getColorN_IR_Pattern(arguments, cs); + } else { + this.setStrokeColor.apply(this, arguments); + } + }, + setFillColor: function canvasGraphicsSetFillColor(/*...*/) { + var cs = this.current.fillColorSpace; + var color = cs.getRgb(arguments); + this.setFillRGBColor.apply(this, color); + }, + setFillColorN_IR: function canvasGraphicsSetFillColorN(/*...*/) { + var cs = this.current.fillColorSpace; + + if (cs.name == 'Pattern') { + this.current.fillColor = this.getColorN_IR_Pattern(arguments, cs); + } else { + this.setFillColor.apply(this, arguments); + } + }, + setStrokeGray: function canvasGraphicsSetStrokeGray(gray) { + this.setStrokeRGBColor(gray, gray, gray); + }, + setFillGray: function canvasGraphicsSetFillGray(gray) { + this.setFillRGBColor(gray, gray, gray); + }, + setStrokeRGBColor: function canvasGraphicsSetStrokeRGBColor(r, g, b) { + var color = Util.makeCssRgb(r, g, b); + this.ctx.strokeStyle = color; + this.current.strokeColor = color; + }, + setFillRGBColor: function canvasGraphicsSetFillRGBColor(r, g, b) { + var color = Util.makeCssRgb(r, g, b); + this.ctx.fillStyle = color; + this.current.fillColor = color; + }, + setStrokeCMYKColor: function canvasGraphicsSetStrokeCMYKColor(c, m, y, k) { + var color = Util.makeCssCmyk(c, m, y, k); + this.ctx.strokeStyle = color; + this.current.strokeColor = color; + }, + setFillCMYKColor: function canvasGraphicsSetFillCMYKColor(c, m, y, k) { + var color = Util.makeCssCmyk(c, m, y, k); + this.ctx.fillStyle = color; + this.current.fillColor = color; + }, + + shadingFill: function canvasGraphicsShadingFill(patternIR) { + var ctx = this.ctx; + + this.save(); + ctx.fillStyle = Pattern.shadingFromIR(ctx, patternIR); + + var inv = ctx.mozCurrentTransformInverse; + if (inv) { + var canvas = ctx.canvas; + var width = canvas.width; + var height = canvas.height; + + var bl = Util.applyTransform([0, 0], inv); + var br = Util.applyTransform([0, width], inv); + var ul = Util.applyTransform([height, 0], inv); + var ur = Util.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(); + }, + + // Images + beginInlineImage: function canvasGraphicsBeginInlineImage() { + error('Should not call beginInlineImage'); + }, + beginImageData: function canvasGraphicsBeginImageData() { + error('Should not call beginImageData'); + }, + + paintFormXObjectBegin: + function canvasGraphicsPaintFormXObject(matrix, bbox) { + this.save(); + + if (matrix && isArray(matrix) && 6 == matrix.length) + this.transform.apply(this, matrix); + + if (bbox && isArray(bbox) && 4 == bbox.length) { + var width = bbox[2] - bbox[0]; + var height = bbox[3] - bbox[1]; + this.rectangle(bbox[0], bbox[1], width, height); + this.clip(); + this.endPath(); + } + }, + + paintFormXObjectEnd: function() { + this.restore(); + }, + + paintJpegXObject: function(objId, w, h) { + var image = this.objs.get(objId); + if (!image) { + error('Dependent image isn\'t ready yet'); + } + + this.save(); + + var ctx = this.ctx; + // scale the image to the unit square + ctx.scale(1 / w, -1 / h); + + var domImage = image.getImage(); + ctx.drawImage(domImage, 0, 0, domImage.width, domImage.height, + 0, -h, w, h); + + this.restore(); + }, + + paintImageMaskXObject: function(imgArray, inverseDecode, width, height) { + function applyStencilMask(buffer, inverseDecode) { + var imgArrayPos = 0; + var i, j, mask, buf; + // removing making non-masked pixels transparent + var bufferPos = 3; // alpha component offset + for (i = 0; i < height; i++) { + mask = 0; + for (j = 0; j < width; j++) { + if (!mask) { + buf = imgArray[imgArrayPos++]; + mask = 128; + } + if (!(buf & mask) == inverseDecode) { + buffer[bufferPos] = 0; + } + bufferPos += 4; + mask >>= 1; + } + } + } + + this.save(); + + var ctx = this.ctx; + var w = width, h = height; + // scale the image to the unit square + ctx.scale(1 / w, -1 / h); + + var tmpCanvas = new this.ScratchCanvas(w, h); + var tmpCtx = tmpCanvas.getContext('2d'); + + var fillColor = this.current.fillColor; + tmpCtx.fillStyle = (fillColor && fillColor.hasOwnProperty('type') && + fillColor.type === 'Pattern') ? + fillColor.getPattern(tmpCtx) : fillColor; + tmpCtx.fillRect(0, 0, w, h); + + var imgData = tmpCtx.getImageData(0, 0, w, h); + var pixels = imgData.data; + + applyStencilMask(pixels, inverseDecode); + + tmpCtx.putImageData(imgData, 0, 0); + ctx.drawImage(tmpCanvas, 0, -h); + this.restore(); + }, + + paintImageXObject: function(imgData) { + this.save(); + var ctx = this.ctx; + var w = imgData.width; + var h = imgData.height; + // scale the image to the unit square + ctx.scale(1 / w, -1 / h); + + var tmpCanvas = new this.ScratchCanvas(w, h); + var tmpCtx = tmpCanvas.getContext('2d'); + var tmpImgData; + + // Some browsers can set an UInt8Array directly as imageData, some + // can't. As long as we don't have proper feature detection, just + // copy over each pixel and set the imageData that way. + tmpImgData = tmpCtx.getImageData(0, 0, w, h); + + // Copy over the imageData. + var tmpImgDataPixels = tmpImgData.data; + var len = tmpImgDataPixels.length; + + while (len--) { + tmpImgDataPixels[len] = imgData.data[len]; + } + + tmpCtx.putImageData(tmpImgData, 0, 0); + ctx.drawImage(tmpCanvas, 0, -h); + this.restore(); + }, + + // Marked content + + markPoint: function canvasGraphicsMarkPoint(tag) { + TODO('Marked content'); + }, + markPointProps: function canvasGraphicsMarkPointProps(tag, properties) { + TODO('Marked content'); + }, + beginMarkedContent: function canvasGraphicsBeginMarkedContent(tag) { + TODO('Marked content'); + }, + beginMarkedContentProps: + function canvasGraphicsBeginMarkedContentProps(tag, properties) { + TODO('Marked content'); + }, + endMarkedContent: function canvasGraphicsEndMarkedContent() { + TODO('Marked content'); + }, + + // Compatibility + + beginCompat: function canvasGraphicsBeginCompat() { + TODO('ignore undefined operators (should we do that anyway?)'); + }, + endCompat: function canvasGraphicsEndCompat() { + TODO('stop ignoring undefined operators'); + }, + + // Helper functions + + consumePath: function canvasGraphicsConsumePath() { + 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(); + }, + // We generally keep the canvas context set for + // nonzero-winding, and just set evenodd for the operations + // that need them. + setEOFillRule: function canvasGraphicsSetEOFillRule() { + var savedFillRule = this.ctx.mozFillRule; + this.ctx.mozFillRule = 'evenodd'; + return savedFillRule; + }, + restoreFillRule: function canvasGraphicsRestoreFillRule(rule) { + this.ctx.mozFillRule = rule; + } + }; + + return constructor; +})(); + diff --git a/charsets.js b/src/charsets.js similarity index 100% rename from charsets.js rename to src/charsets.js diff --git a/cidmaps.js b/src/cidmaps.js similarity index 99% rename from cidmaps.js rename to src/cidmaps.js index 7de3d14f6..226843b71 100644 --- a/cidmaps.js +++ b/src/cidmaps.js @@ -6930,3 +6930,4 @@ var CIDToUnicodeMaps = { {f: 39, c: 19576}, {f: 111, c: 19620}, {f: 148, c: 19738}, {f: 7, c: 19887}] }; + diff --git a/src/colorspace.js b/src/colorspace.js new file mode 100644 index 000000000..1c5c291f4 --- /dev/null +++ b/src/colorspace.js @@ -0,0 +1,403 @@ +/* -*- 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 ColorSpace = (function colorSpaceColorSpace() { + // Constructor should define this.numComps, this.defaultColor, this.name + function constructor() { + error('should not call ColorSpace constructor'); + } + + 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: ' + color); + }, + // 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: ' + input); + } + }; + + constructor.parse = function colorspace_parse(cs, xref, res) { + var IR = constructor.parseToIR(cs, xref, res, true); + if (IR instanceof SeparationCS) + return IR; + + return constructor.fromIR(IR); + }; + + constructor.fromIR = function(IR) { + var name; + if (isArray(IR)) { + name = IR[0]; + } else { + name = IR; + } + + switch (name) { + case 'DeviceGrayCS': + return new DeviceGrayCS(); + case 'DeviceRgbCS': + return new DeviceRgbCS(); + case 'DeviceCmykCS': + return new DeviceCmykCS(); + case 'PatternCS': + var baseCS = IR[1]; + if (baseCS == null) { + return new PatternCS(null); + } else { + return new PatternCS(ColorSpace.fromIR(baseCS)); + } + case 'IndexedCS': + var baseCS = IR[1]; + var hiVal = IR[2]; + var lookup = IR[3]; + return new IndexedCS(ColorSpace.fromIR(baseCS), hiVal, lookup); + case 'SeparationCS': + var alt = IR[1]; + var tintFnIR = IR[2]; + + return new SeparationCS( + ColorSpace.fromIR(alt), + PDFFunction.fromIR(tintFnIR) + ); + default: + error('Unkown name ' + name); + } + return null; + } + + constructor.parseToIR = function colorspace_parse(cs, xref, res, parseOnly) { + if (isName(cs)) { + var colorSpaces = xref.fetchIfRef(res.get('ColorSpace')); + if (isDict(colorSpaces)) { + var refcs = colorSpaces.get(cs.name); + if (refcs) + cs = refcs; + } + } + + cs = xref.fetchIfRef(cs); + + if (isName(cs)) { + var mode = cs.name; + this.mode = mode; + + switch (mode) { + case 'DeviceGray': + case 'G': + return 'DeviceGrayCS'; + case 'DeviceRGB': + case 'RGB': + return 'DeviceRgbCS'; + case 'DeviceCMYK': + case 'CMYK': + return 'DeviceCmykCS'; + case 'Pattern': + return ['PatternCS', null]; + default: + error('unrecognized colorspace ' + mode); + } + } else if (isArray(cs)) { + var mode = cs[0].name; + this.mode = mode; + + switch (mode) { + case 'DeviceGray': + case 'G': + return 'DeviceGrayCS'; + case 'DeviceRGB': + case 'RGB': + return 'DeviceRgbCS'; + case 'DeviceCMYK': + case 'CMYK': + return 'DeviceCmykCS'; + case 'CalGray': + return 'DeviceGrayCS'; + case 'CalRGB': + return 'DeviceRgbCS'; + case 'ICCBased': + var stream = xref.fetchIfRef(cs[1]); + var dict = stream.dict; + var numComps = dict.get('N'); + if (numComps == 1) + return 'DeviceGrayCS'; + if (numComps == 3) + return 'DeviceRgbCS'; + if (numComps == 4) + return 'DeviceCmykCS'; + break; + case 'Pattern': + var baseCS = cs[1]; + if (baseCS) + baseCS = ColorSpace.parseToIR(baseCS, xref, res); + return ['PatternCS', baseCS]; + case 'Indexed': + var baseCS = ColorSpace.parseToIR(cs[1], xref, res); + var hiVal = cs[2] + 1; + var lookup = xref.fetchIfRef(cs[3]); + return ['IndexedCS', baseCS, hiVal, lookup]; + case 'Separation': + var alt = ColorSpace.parseToIR(cs[2], xref, res); + var tintFnIR = PDFFunction.getIR(xref, xref.fetchIfRef(cs[3])); + return ['SeparationCS', alt, tintFnIR]; + case 'Lab': + case 'DeviceN': + default: + error('unimplemented color space object "' + mode + '"'); + } + } else { + error('unrecognized color space object: "' + cs + '"'); + } + return null; + }; + + return constructor; +})(); + +var SeparationCS = (function separationCS() { + 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(color); + return this.base.getRgb(tinted); + }, + getRgbBuffer: function sepcs_getRgbBuffer(input, bits) { + var tintFn = this.tintFn; + var base = this.base; + var scale = 1 / ((1 << bits) - 1); + + var length = input.length; + var pos = 0; + + var numComps = base.numComps; + var baseBuf = new Uint8Array(numComps * length); + for (var i = 0; i < length; ++i) { + var scaled = input[i] * scale; + var tinted = tintFn([scaled]); + for (var j = 0; j < numComps; ++j) + baseBuf[pos++] = 255 * tinted[j]; + } + return base.getRgbBuffer(baseBuf, 8); + } + }; + + return constructor; +})(); + +var PatternCS = (function patternCS() { + function constructor(baseCS) { + this.name = 'Pattern'; + this.base = baseCS; + } + constructor.prototype = {}; + + return constructor; +})(); + +var IndexedCS = (function indexedCS() { + function constructor(base, highVal, lookup) { + this.name = 'Indexed'; + this.numComps = 1; + this.defaultColor = [0]; + + 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: ' + lookup); + } + this.lookup = lookupArray; + } + + constructor.prototype = { + getRgb: function indexcs_getRgb(color) { + var numComps = this.base.numComps; + + 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 base.getRgbBuffer(baseBuf, 8); + } + }; + return constructor; +})(); + +var DeviceGrayCS = (function deviceGrayCS() { + 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, bits) { + var scale = 255 / ((1 << bits) - 1); + var length = input.length; + var rgbBuf = new Uint8Array(length * 3); + for (var i = 0, j = 0; i < length; ++i) { + var c = (scale * input[i]) | 0; + rgbBuf[j++] = c; + rgbBuf[j++] = c; + rgbBuf[j++] = c; + } + return rgbBuf; + } + }; + return constructor; +})(); + +var DeviceRgbCS = (function deviceRgbCS() { + function constructor(bits) { + 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, bits) { + if (bits == 8) + return input; + var scale = 255 / ((1 << bits) - 1); + var i, length = input.length; + var rgbBuf = new Uint8Array(length); + for (i = 0; i < length; ++i) + rgbBuf[i] = (scale * input[i]) | 0; + return rgbBuf; + } + }; + return constructor; +})(); + +var DeviceCmykCS = (function deviceCmykCS() { + 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, bits) { + var scale = 1 / ((1 << bits) - 1); + 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(scale * colorBuf[colorBufPos++]); + + var rgb = this.getRgb(cmyk); + for (var j = 0; j < 3; ++j) + rgbBuf[rgbBufPos++] = Math.round(rgb[j] * 255); + } + + return rgbBuf; + } + }; + + return constructor; +})(); + diff --git a/src/core.js b/src/core.js new file mode 100644 index 000000000..e7241acfa --- /dev/null +++ b/src/core.js @@ -0,0 +1,613 @@ +/* -*- 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 globalScope = (typeof window === 'undefined') ? this : window; + +var ERRORS = 0, WARNINGS = 1, TODOS = 5; +var verbosity = WARNINGS; +var useWorker = false; + +// The global PDFJS object exposes the API +// In production, it will be declared outside a global wrapper +// In development, it will be declared here +if (!globalScope.PDFJS) { + globalScope.PDFJS = {}; +} + +// getPdf() +// Convenience function to perform binary Ajax GET +// Usage: getPdf('http://...', callback) +// getPdf({ +// url:String , +// [,progress:Function, error:Function] +// }, +// callback) +function getPdf(arg, callback) { + var params = arg; + if (typeof arg === 'string') + params = { url: arg }; + + var xhr = new XMLHttpRequest(); + xhr.open('GET', params.url); + xhr.mozResponseType = xhr.responseType = 'arraybuffer'; + xhr.expected = (document.URL.indexOf('file:') === 0) ? 0 : 200; + + if ('progress' in params) + xhr.onprogress = params.progress || undefined; + + if ('error' in params) + xhr.onerror = params.error || undefined; + + xhr.onreadystatechange = function getPdfOnreadystatechange() { + if (xhr.readyState === 4 && xhr.status === xhr.expected) { + var data = (xhr.mozResponseArrayBuffer || xhr.mozResponse || + xhr.responseArrayBuffer || xhr.response); + callback(data); + } + }; + xhr.send(null); +} +globalScope.PDFJS.getPdf = getPdf; + +var Page = (function pagePage() { + function constructor(xref, pageNumber, pageDict, ref) { + this.pageNumber = pageNumber; + this.pageDict = pageDict; + this.stats = { + create: Date.now(), + compile: 0.0, + fonts: 0.0, + images: 0.0, + render: 0.0 + }; + this.xref = xref; + this.ref = ref; + } + + constructor.prototype = { + getPageProp: function pageGetPageProp(key) { + return this.xref.fetchIfRef(this.pageDict.get(key)); + }, + inheritPageProp: function pageInheritPageProp(key) { + var dict = this.pageDict; + var obj = dict.get(key); + while (obj === undefined) { + 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'); + // Reset invalid media box to letter size. + if (!isArray(obj) || obj.length !== 4) + obj = [0, 0, 612, 792]; + return shadow(this, 'mediaBox', obj); + }, + get view() { + var obj = this.inheritPageProp('CropBox'); + var view = { + x: 0, + y: 0, + width: this.width, + height: this.height + }; + if (isArray(obj) && obj.length == 4) { + var tl = this.rotatePoint(obj[0], obj[1]); + var br = this.rotatePoint(obj[2], obj[3]); + view.x = Math.min(tl.x, br.x); + view.y = Math.min(tl.y, br.y); + view.width = Math.abs(tl.x - br.x); + view.height = Math.abs(tl.y - br.y); + } + + return shadow(this, 'cropBox', view); + }, + get annotations() { + return shadow(this, 'annotations', this.inheritPageProp('Annots')); + }, + get width() { + var mediaBox = this.mediaBox; + var rotate = this.rotate; + var width; + if (rotate == 0 || rotate == 180) { + width = (mediaBox[2] - mediaBox[0]); + } else { + width = (mediaBox[3] - mediaBox[1]); + } + return shadow(this, 'width', width); + }, + get height() { + var mediaBox = this.mediaBox; + var rotate = this.rotate; + var height; + if (rotate == 0 || rotate == 180) { + height = (mediaBox[3] - mediaBox[1]); + } else { + height = (mediaBox[2] - mediaBox[0]); + } + return shadow(this, 'height', height); + }, + get rotate() { + var rotate = this.inheritPageProp('Rotate') || 0; + // Normalize rotation so it's a multiple of 90 and between 0 and 270 + if (rotate % 90 != 0) { + rotate = 0; + } else if (rotate >= 360) { + rotate = rotate % 360; + } else if (rotate < 0) { + // The spec doesn't cover negatives, assume its counterclockwise + // rotation. The following is the other implementation of modulo. + rotate = ((rotate % 360) + 360) % 360; + } + return shadow(this, 'rotate', rotate); + }, + + startRenderingFromIRQueue: function startRenderingFromIRQueue( + IRQueue, fonts) { + var self = this; + this.IRQueue = IRQueue; + var gfx = new CanvasGraphics(this.ctx, this.objs); + var startTime = Date.now(); + + var displayContinuation = function pageDisplayContinuation() { + // Always defer call to display() to work around bug in + // Firefox error reporting from XHR callbacks. + setTimeout(function pageSetTimeout() { + try { + self.display(gfx, self.callback); + } catch (e) { + if (self.callback) self.callback(e.toString()); + throw e; + } + }); + }; + + this.ensureFonts(fonts, function() { + displayContinuation(); + }); + }, + + getIRQueue: function(handler, dependency) { + if (this.IRQueue) { + // content was compiled + return this.IRQueue; + } + + var xref = this.xref; + var content = xref.fetchIfRef(this.content); + var resources = xref.fetchIfRef(this.resources); + if (isArray(content)) { + // fetching items + var i, n = content.length; + for (i = 0; i < n; ++i) + content[i] = xref.fetchIfRef(content[i]); + content = new StreamsSequenceStream(content); + } + + var pe = this.pe = new PartialEvaluator( + xref, handler, 'p' + this.pageNumber + '_'); + var IRQueue = {}; + return this.IRQueue = pe.getIRQueue( + content, resources, IRQueue, dependency); + }, + + ensureFonts: function(fonts, callback) { + // Convert the font names to the corresponding font obj. + for (var i = 0; i < fonts.length; i++) { + fonts[i] = this.objs.objs[fonts[i]].data; + } + + // Load all the fonts + var fontObjs = FontLoader.bind( + fonts, + function(fontObjs) { + this.stats.fonts = Date.now(); + + callback.call(this); + }.bind(this), + this.objs + ); + }, + + display: function(gfx, callback) { + var xref = this.xref; + var resources = xref.fetchIfRef(this.resources); + var mediaBox = xref.fetchIfRef(this.mediaBox); + assertWellFormed(isDict(resources), 'invalid page resources'); + + gfx.xref = xref; + gfx.res = resources; + gfx.beginDrawing({ x: mediaBox[0], y: mediaBox[1], + width: this.width, + height: this.height, + rotate: this.rotate }); + + var startIdx = 0; + var length = this.IRQueue.fnArray.length; + var IRQueue = this.IRQueue; + + var self = this; + var startTime = Date.now(); + function next() { + startIdx = gfx.executeIRQueue(IRQueue, startIdx, next); + if (startIdx == length) { + self.stats.render = Date.now(); + if (callback) callback(); + } + } + next(); + }, + rotatePoint: function pageRotatePoint(x, y, reverse) { + var rotate = reverse ? (360 - this.rotate) : this.rotate; + switch (rotate) { + case 180: + return {x: this.width - x, y: y}; + case 90: + return {x: this.width - y, y: this.height - x}; + case 270: + return {x: y, y: x}; + case 360: + case 0: + default: + return {x: x, y: this.height - y}; + } + }, + getLinks: function pageGetLinks() { + var xref = this.xref; + var annotations = xref.fetchIfRef(this.annotations) || []; + var i, n = annotations.length; + var links = []; + for (i = 0; i < n; ++i) { + var annotation = xref.fetch(annotations[i]); + if (!isDict(annotation)) + continue; + var subtype = annotation.get('Subtype'); + if (!isName(subtype) || subtype.name != 'Link') + continue; + var rect = annotation.get('Rect'); + var topLeftCorner = this.rotatePoint(rect[0], rect[1]); + var bottomRightCorner = this.rotatePoint(rect[2], rect[3]); + + var link = {}; + link.x = Math.min(topLeftCorner.x, bottomRightCorner.x); + link.y = Math.min(topLeftCorner.y, bottomRightCorner.y); + link.width = Math.abs(topLeftCorner.x - bottomRightCorner.x); + link.height = Math.abs(topLeftCorner.y - bottomRightCorner.y); + var a = this.xref.fetchIfRef(annotation.get('A')); + if (a) { + switch (a.get('S').name) { + case 'URI': + link.url = a.get('URI'); + break; + case 'GoTo': + link.dest = a.get('D'); + break; + default: + TODO('other link types'); + } + } else if (annotation.has('Dest')) { + // simple destination link + var dest = annotation.get('Dest'); + link.dest = isName(dest) ? dest.name : dest; + } + links.push(link); + } + return links; + }, + startRendering: function(ctx, callback) { + this.ctx = ctx; + this.callback = callback; + + this.startRenderingTime = Date.now(); + this.pdf.startRendering(this); + } + }; + + return constructor; +})(); + +/** + * The `PDFDocModel` holds all the data of the PDF file. Compared to the + * `PDFDoc`, this one doesn't have any job management code. + * Right now there exists one PDFDocModel on the main thread + one object + * for each worker. If there is no worker support enabled, there are two + * `PDFDocModel` objects on the main thread created. + * TODO: Refactor the internal object structure, such that there is no + * need for the `PDFDocModel` anymore and there is only one object on the + * main thread and not one entire copy on each worker instance. + */ +var PDFDocModel = (function pdfDoc() { + function constructor(arg, callback) { + if (isStream(arg)) + init.call(this, arg); + else if (isArrayBuffer(arg)) + init.call(this, new Stream(arg)); + else + error('PDFDocModel: Unknown argument type'); + } + + function init(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 */ + } + + 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; + do { + ch = stream.getChar(); + } while (Lexer.isSpace(ch)); + var str = ''; + while ((ch - '0') <= 9) { + str += ch; + ch = stream.getChar(); + } + startXRef = parseInt(str, 10); + 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 pdfDocCheckHeader() { + 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 pdfDocSetup(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 pdfDocGetPage(n) { + return this.catalog.getPage(n); + } + }; + + return constructor; +})(); + +var PDFDoc = (function() { + function constructor(arg, callback) { + var stream = null; + var data = null; + + if (isStream(arg)) { + stream = arg; + data = arg.bytes; + } else if (isArrayBuffer(arg)) { + stream = new Stream(arg); + data = arg; + } else { + error('PDFDoc: Unknown argument type'); + } + + this.data = data; + this.stream = stream; + this.pdf = new PDFDocModel(stream); + + this.catalog = this.pdf.catalog; + this.objs = new PDFObjects(); + + this.pageCache = []; + + if (useWorker) { + var worker = new Worker('../src/worker_loader.js'); + } else { + // If we don't use a worker, just post/sendMessage to the main thread. + var worker = { + postMessage: function(obj) { + worker.onmessage({data: obj}); + }, + terminate: function() {} + }; + } + this.worker = worker; + + this.fontsLoading = {}; + + var processorHandler = this.processorHandler = + new MessageHandler('main', worker); + + processorHandler.on('page', function(data) { + var pageNum = data.pageNum; + var page = this.pageCache[pageNum]; + var depFonts = data.depFonts; + + page.startRenderingFromIRQueue(data.IRQueue, depFonts); + }, this); + + processorHandler.on('obj', function(data) { + var id = data[0]; + var type = data[1]; + + switch (type) { + case 'JpegStream': + var IR = data[2]; + new JpegImage(id, IR, this.objs); + break; + case 'Font': + var name = data[2]; + var file = data[3]; + var properties = data[4]; + + if (file) { + var fontFileDict = new Dict(); + fontFileDict.map = file.dict.map; + + var fontFile = new Stream(file.bytes, file.start, + file.end - file.start, fontFileDict); + + // Check if this is a FlateStream. Otherwise just use the created + // Stream one. This makes complex_ttf_font.pdf work. + var cmf = file.bytes[0]; + if ((cmf & 0x0f) == 0x08) { + file = new FlateStream(fontFile); + } else { + file = fontFile; + } + } + + // For now, resolve the font object here direclty. The real font + // object is then created in FontLoader.bind(). + this.objs.resolve(id, { + name: name, + file: file, + properties: properties + }); + break; + default: + throw 'Got unkown object type ' + type; + } + }, this); + + processorHandler.on('font_ready', function(data) { + var id = data[0]; + var font = new FontShape(data[1]); + + // If there is no string, then there is nothing to attach to the DOM. + if (!font.str) { + this.objs.resolve(id, font); + } else { + this.objs.setData(id, font); + } + }.bind(this)); + + if (!useWorker) { + // If the main thread is our worker, setup the handling for the messages + // the main thread sends to it self. + WorkerProcessorHandler.setup(processorHandler); + } + + this.workerReadyPromise = new Promise('workerReady'); + setTimeout(function() { + processorHandler.send('doc', this.data); + this.workerReadyPromise.resolve(true); + }.bind(this)); + } + + constructor.prototype = { + get numPages() { + return this.pdf.numPages; + }, + + startRendering: function(page) { + // The worker might not be ready to receive the page request yet. + this.workerReadyPromise.then(function() { + this.processorHandler.send('page_request', page.pageNumber + 1); + }.bind(this)); + }, + + getPage: function(n) { + if (this.pageCache[n]) + return this.pageCache[n]; + + var page = this.pdf.getPage(n); + // Add a reference to the objects such that Page can forward the reference + // to the CanvasGraphics and so on. + page.objs = this.objs; + page.pdf = this; + return this.pageCache[n] = page; + }, + + destroy: function() { + if (this.worker) + this.worker.terminate(); + + if (this.fontWorker) + this.fontWorker.terminate(); + + for (var n in this.pageCache) + delete this.pageCache[n]; + + delete this.data; + delete this.stream; + delete this.pdf; + delete this.catalog; + } + }; + + return constructor; +})(); + +globalScope.PDFJS.PDFDoc = PDFDoc; + diff --git a/crypto.js b/src/crypto.js similarity index 99% rename from crypto.js rename to src/crypto.js index 5699ea1df..2c86038f0 100644 --- a/crypto.js +++ b/src/crypto.js @@ -595,3 +595,4 @@ var CipherTransformFactory = (function cipherTransformFactory() { return constructor; })(); + diff --git a/src/evaluator.js b/src/evaluator.js new file mode 100644 index 000000000..48e12c83d --- /dev/null +++ b/src/evaluator.js @@ -0,0 +1,921 @@ +/* -*- 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 PartialEvaluator = (function partialEvaluator() { + function constructor(xref, handler, uniquePrefix) { + this.state = new EvalState(); + this.stateStack = []; + + this.xref = xref; + this.handler = handler; + this.uniquePrefix = uniquePrefix; + this.objIdCounter = 0; + } + + 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', + ID: 'beginImageData', + EI: 'endInlineImage', + + // XObjects + Do: 'paintXObject', + + // Marked content + MP: 'markPoint', + DP: 'markPointProps', + BMC: 'beginMarkedContent', + BDC: 'beginMarkedContentProps', + EMC: 'endMarkedContent', + + // Compatibility + BX: 'beginCompat', + EX: 'endCompat' + }; + + constructor.prototype = { + getIRQueue: function partialEvaluatorGetIRQueue(stream, resources, + queue, dependency) { + + var self = this; + var xref = this.xref; + var handler = this.handler; + var uniquePrefix = this.uniquePrefix; + + function insertDependency(depList) { + fnArray.push('dependency'); + argsArray.push(depList); + for (var i = 0; i < depList.length; i++) { + var dep = depList[i]; + if (dependency.indexOf(dep) == -1) { + dependency.push(depList[i]); + } + } + } + + function handleSetFont(fontName, fontRef) { + var loadedName = null; + + var fontRes = resources.get('Font'); + + // TODO: TOASK: Is it possible to get here? If so, what does + // args[0].name should be like??? + assert(fontRes, 'fontRes not available'); + + fontRes = xref.fetchIfRef(fontRes); + fontRef = fontRef || fontRes.get(fontName); + var font = xref.fetchIfRef(fontRef); + assertWellFormed(isDict(font)); + if (!font.translated) { + font.translated = self.translateFont(font, xref, resources, handler, + uniquePrefix, dependency); + if (font.translated) { + // keep track of each font we translated so the caller can + // load them asynchronously before calling display on a page + loadedName = 'font_' + uniquePrefix + ++self.objIdCounter; + font.translated.properties.loadedName = loadedName; + font.loadedName = loadedName; + + var translated = font.translated; + handler.send('obj', [ + loadedName, + 'Font', + translated.name, + translated.file, + translated.properties + ]); + } + } + loadedName = loadedName || font.loadedName; + + // Ensure the font is ready before the font is set + // and later on used for drawing. + // TODO: This should get insert to the IRQueue only once per + // page. + insertDependency([loadedName]); + return loadedName; + } + + function buildPaintImageXObject(image, inline) { + var dict = image.dict; + var w = dict.get('Width', 'W'); + var h = dict.get('Height', 'H'); + + if (image instanceof JpegStream) { + var objId = 'img_' + uniquePrefix + ++self.objIdCounter; + handler.send('obj', [objId, 'JpegStream', image.getIR()]); + + // Add the dependency on the image object. + insertDependency([objId]); + + // The normal fn. + fn = 'paintJpegXObject'; + args = [objId, w, h]; + + return; + } + + // Needs to be rendered ourself. + + // Figure out if the image has an imageMask. + var imageMask = dict.get('ImageMask', 'IM') || false; + + // If there is no imageMask, create the PDFImage and a lot + // of image processing can be done here. + if (!imageMask) { + var imageObj = new PDFImage(xref, resources, image, inline); + + if (imageObj.imageMask) { + throw 'Can\'t handle this in the web worker :/'; + } + + var imgData = { + width: w, + height: h, + data: new Uint8Array(w * h * 4) + }; + var pixels = imgData.data; + imageObj.fillRgbaBuffer(pixels, imageObj.decode); + + fn = 'paintImageXObject'; + args = [imgData]; + return; + } + + // This depends on a tmpCanvas beeing filled with the + // current fillStyle, such that processing the pixel + // data can't be done here. Instead of creating a + // complete PDFImage, only read the information needed + // for later. + fn = 'paintImageMaskXObject'; + + var width = dict.get('Width', 'W'); + var height = dict.get('Height', 'H'); + var bitStrideLength = (width + 7) >> 3; + var imgArray = image.getBytes(bitStrideLength * height); + var decode = dict.get('Decode', 'D'); + var inverseDecode = !!decode && decode[0] > 0; + + args = [imgArray, inverseDecode, width, height]; + } + + uniquePrefix = uniquePrefix || ''; + if (!queue.argsArray) { + queue.argsArray = []; + } + if (!queue.fnArray) { + queue.fnArray = []; + } + + var fnArray = queue.fnArray, argsArray = queue.argsArray; + var dependencyArray = dependency || []; + + resources = xref.fetchIfRef(resources) || new Dict(); + var xobjs = xref.fetchIfRef(resources.get('XObject')) || new Dict(); + var patterns = xref.fetchIfRef(resources.get('Pattern')) || new Dict(); + var parser = new Parser(new Lexer(stream), false); + var res = resources; + var args = [], obj; + var getObjBt = function getObjBt() { + parser = this.oldParser; + return { name: 'BT' }; + }; + var TILING_PATTERN = 1, SHADING_PATTERN = 2; + + while (!isEOF(obj = parser.getObj())) { + if (isCmd(obj)) { + var cmd = obj.cmd; + var fn = OP_MAP[cmd]; + if (!fn) { + // invalid content command, trying to recover + if (cmd.substr(-2) == 'BT') { + fn = OP_MAP[cmd.substr(0, cmd.length - 2)]; + // feeding 'BT' on next interation + parser = { + getObj: getObjBt, + oldParser: parser + }; + } + } + assertWellFormed(fn, 'Unknown command "' + cmd + '"'); + // TODO figure out how to type-check vararg functions + + if ((cmd == 'SCN' || cmd == 'scn') && !args[args.length - 1].code) { + // Use the IR version for setStroke/FillColorN. + fn += '_IR'; + + // compile tiling patterns + var patternName = args[args.length - 1]; + // SCN/scn applies patterns along with normal colors + if (isName(patternName)) { + var pattern = xref.fetchIfRef(patterns.get(patternName.name)); + if (pattern) { + var dict = isStream(pattern) ? pattern.dict : pattern; + var typeNum = dict.get('PatternType'); + + if (typeNum == TILING_PATTERN) { + // Create an IR of the pattern code. + var depIdx = dependencyArray.length; + var queueObj = {}; + var codeIR = this.getIRQueue(pattern, dict.get('Resources'), + queueObj, dependencyArray); + + // Add the dependencies that are required to execute the + // codeIR. + insertDependency(dependencyArray.slice(depIdx)); + + args = TilingPattern.getIR(codeIR, dict, args); + } + else if (typeNum == SHADING_PATTERN) { + var shading = xref.fetchIfRef(dict.get('Shading')); + var matrix = dict.get('Matrix'); + var pattern = Pattern.parseShading(shading, matrix, xref, res, + null /*ctx*/); + args = pattern.getIR(); + } else { + error('Unkown PatternType ' + typeNum); + } + } + } + } else 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) { + var matrix = xobj.dict.get('Matrix'); + var bbox = xobj.dict.get('BBox'); + + fnArray.push('paintFormXObjectBegin'); + argsArray.push([matrix, bbox]); + + // This adds the IRQueue of the xObj to the current queue. + var depIdx = dependencyArray.length; + + this.getIRQueue(xobj, xobj.dict.get('Resources'), queue, + dependencyArray); + + // Add the dependencies that are required to execute the + // codeIR. + insertDependency(dependencyArray.slice(depIdx)); + + fn = 'paintFormXObjectEnd'; + args = []; + } else if ('Image' == type.name) { + buildPaintImageXObject(xobj, false); + } else { + error('Unhandled XObject subtype ' + type.name); + } + } + } else if (cmd == 'Tf') { // eagerly collect all fonts + args[0] = handleSetFont(args[0].name); + } else if (cmd == 'EI') { + buildPaintImageXObject(args[0], true); + } + + switch (fn) { + // Parse the ColorSpace data to a raw format. + case 'setFillColorSpace': + case 'setStrokeColorSpace': + args = [ColorSpace.parseToIR(args[0], xref, resources)]; + break; + case 'shadingFill': + var shadingRes = xref.fetchIfRef(res.get('Shading')); + if (!shadingRes) + error('No shading resource found'); + + var shading = xref.fetchIfRef(shadingRes.get(args[0].name)); + if (!shading) + error('No shading object found'); + + var shadingFill = Pattern.parseShading(shading, null, xref, res, + null); + var patternIR = shadingFill.getIR(); + args = [patternIR]; + fn = 'shadingFill'; + break; + case 'setGState': + var dictName = args[0]; + var extGState = xref.fetchIfRef(resources.get('ExtGState')); + + if (!isDict(extGState) || !extGState.has(dictName.name)) + break; + + var gsState = xref.fetchIfRef(extGState.get(dictName.name)); + + // This array holds the converted/processed state data. + var gsStateObj = []; + + gsState.forEach( + function canvasGraphicsSetGStateForEach(key, value) { + switch (key) { + case 'Type': + break; + case 'LW': + case 'LC': + case 'LJ': + case 'ML': + case 'D': + case 'RI': + case 'FL': + gsStateObj.push([key, value]); + break; + case 'Font': + gsStateObj.push([ + 'Font', + handleSetFont(null, value[0]), + value[1] + ]); + break; + case 'OP': + case 'op': + case 'OPM': + case 'BG': + case 'BG2': + case 'UCR': + case 'UCR2': + case 'TR': + case 'TR2': + case 'HT': + case 'SM': + case 'SA': + case 'BM': + case 'SMask': + case 'CA': + case 'ca': + case 'AIS': + case 'TK': + TODO('graphic state operator ' + key); + break; + default: + warn('Unknown graphic state operator ' + key); + break; + } + } + ); + args = [gsStateObj]; + break; + } // switch + + fnArray.push(fn); + argsArray.push(args); + args = []; + } else if (obj != null) { + assertWellFormed(args.length <= 33, 'Too many arguments'); + args.push(obj); + } + } + + return { + fnArray: fnArray, + argsArray: argsArray + }; + }, + + extractEncoding: function partialEvaluatorExtractEncoding(dict, + xref, + properties) { + var type = properties.type, encoding; + if (properties.composite) { + var defaultWidth = xref.fetchIfRef(dict.get('DW')) || 1000; + properties.defaultWidth = defaultWidth; + + var glyphsWidths = {}; + var widths = xref.fetchIfRef(dict.get('W')); + if (widths) { + var start = 0, end = 0; + for (var i = 0; i < widths.length; i++) { + var code = widths[i]; + if (isArray(code)) { + for (var j = 0; j < code.length; j++) + glyphsWidths[start++] = code[j]; + start = 0; + } else if (start) { + var width = widths[++i]; + for (var j = start; j <= code; j++) + glyphsWidths[j] = width; + start = 0; + } else { + start = code; + } + } + } + properties.widths = glyphsWidths; + + // Glyph ids are big-endian 2-byte values + encoding = properties.encoding; + + // CIDSystemInfo might help to match width and glyphs + var cidSystemInfo = dict.get('CIDSystemInfo'); + if (isDict(cidSystemInfo)) { + properties.cidSystemInfo = { + registry: cidSystemInfo.get('Registry'), + ordering: cidSystemInfo.get('Ordering'), + supplement: cidSystemInfo.get('Supplement') + }; + } + + var cidToGidMap = dict.get('CIDToGIDMap'); + if (!cidToGidMap || !isRef(cidToGidMap)) { + + + return Object.create(GlyphsUnicode); + } + + // Extract the encoding from the CIDToGIDMap + var glyphsStream = xref.fetchIfRef(cidToGidMap); + var glyphsData = glyphsStream.getBytes(0); + + // Set encoding 0 to later verify the font has an encoding + encoding[0] = { unicode: 0, width: 0 }; + for (var j = 0; j < glyphsData.length; j++) { + var glyphID = (glyphsData[j++] << 8) | glyphsData[j]; + if (glyphID == 0) + continue; + + var code = j >> 1; + var width = glyphsWidths[code]; + encoding[code] = { + unicode: glyphID, + width: isNum(width) ? width : defaultWidth + }; + } + + return Object.create(GlyphsUnicode); + } + + var differences = properties.differences; + var map = properties.encoding; + var baseEncoding = null; + if (dict.has('Encoding')) { + encoding = xref.fetchIfRef(dict.get('Encoding')); + if (isDict(encoding)) { + var baseName = encoding.get('BaseEncoding'); + if (baseName) + baseEncoding = Encodings[baseName.name].slice(); + + // Load the differences between the base and original + if (encoding.has('Differences')) { + var diffEncoding = encoding.get('Differences'); + var index = 0; + for (var j = 0; j < diffEncoding.length; j++) { + var data = diffEncoding[j]; + if (isNum(data)) + index = data; + else + differences[index++] = data.name; + } + } + } else if (isName(encoding)) { + baseEncoding = Encodings[encoding.name].slice(); + } else { + error('Encoding is not a Name nor a Dict'); + } + } + + if (!baseEncoding) { + switch (type) { + case 'TrueType': + baseEncoding = Encodings.WinAnsiEncoding.slice(); + break; + case 'Type1': + case 'Type3': + baseEncoding = Encodings.StandardEncoding.slice(); + break; + default: + warn('Unknown type of font: ' + type); + baseEncoding = []; + break; + } + } + + // merge in the differences + var firstChar = properties.firstChar; + var lastChar = properties.lastChar; + var widths = properties.widths || []; + var glyphs = {}; + for (var i = firstChar; i <= lastChar; i++) { + var glyph = differences[i]; + var replaceGlyph = true; + if (!glyph) { + glyph = baseEncoding[i] || i; + replaceGlyph = false; + } + var index = GlyphsUnicode[glyph] || i; + var width = widths[i] || widths[glyph]; + map[i] = { + unicode: index, + width: isNum(width) ? width : properties.defaultWidth + }; + + if (replaceGlyph || !glyphs[glyph]) + glyphs[glyph] = map[i]; + if (replaceGlyph || !glyphs[index]) + glyphs[index] = map[i]; + + // If there is no file, the character mapping can't be modified + // but this is unlikely that there is any standard encoding with + // chars below 0x1f, so that's fine. + if (!properties.file) + continue; + + if (index <= 0x1f || (index >= 127 && index <= 255)) + map[i].unicode += kCmapGlyphOffset; + } + + if (type == 'TrueType' && dict.has('ToUnicode') && differences) { + var cmapObj = dict.get('ToUnicode'); + if (isRef(cmapObj)) { + cmapObj = xref.fetch(cmapObj); + } + if (isName(cmapObj)) { + error('ToUnicode file cmap translation not implemented'); + } else if (isStream(cmapObj)) { + var tokens = []; + var token = ''; + var beginArrayToken = {}; + + var cmap = cmapObj.getBytes(cmapObj.length); + for (var i = 0; i < cmap.length; i++) { + var byte = cmap[i]; + if (byte == 0x20 || byte == 0x0D || byte == 0x0A || + byte == 0x3C || byte == 0x5B || byte == 0x5D) { + switch (token) { + case 'usecmap': + error('usecmap is not implemented'); + break; + + case 'beginbfchar': + case 'beginbfrange': + case 'begincidchar': + case 'begincidrange': + token = ''; + tokens = []; + break; + + case 'endcidrange': + case 'endbfrange': + for (var j = 0; j < tokens.length; j += 3) { + var startRange = tokens[j]; + var endRange = tokens[j + 1]; + var code = tokens[j + 2]; + while (startRange < endRange) { + var mapping = map[startRange] || {}; + mapping.unicode = code++; + map[startRange] = mapping; + ++startRange; + } + } + break; + + case 'endcidchar': + case 'endbfchar': + for (var j = 0; j < tokens.length; j += 2) { + var index = tokens[j]; + var code = tokens[j + 1]; + var mapping = map[index] || {}; + mapping.unicode = code; + map[index] = mapping; + } + break; + + case '': + break; + + default: + if (token[0] >= '0' && token[0] <= '9') + token = parseInt(token, 10); // a number + tokens.push(token); + token = ''; + } + switch (byte) { + case 0x5B: + // begin list parsing + tokens.push(beginArrayToken); + break; + case 0x5D: + // collect array items + var items = [], item; + while (tokens.length && + (item = tokens.pop()) != beginArrayToken) + items.unshift(item); + tokens.push(items); + break; + } + } else if (byte == 0x3E) { + if (token.length) { + // parsing hex number + tokens.push(parseInt(token, 16)); + token = ''; + } + } else { + token += String.fromCharCode(byte); + } + } + } + } + return glyphs; + }, + + getBaseFontMetricsAndMap: function getBaseFontMetricsAndMap(name) { + var map = {}; + if (/^Symbol(-?(Bold|Italic))*$/.test(name)) { + // special case for symbols + var encoding = Encodings.symbolsEncoding.slice(); + for (var i = 0, n = encoding.length, j; i < n; i++) { + if (!(j = encoding[i])) + continue; + map[i] = GlyphsUnicode[j] || 0; + } + } + + var defaultWidth = 0; + var widths = Metrics[stdFontMap[name] || name]; + if (isNum(widths)) { + defaultWidth = widths; + widths = null; + } + + return { + defaultWidth: defaultWidth, + widths: widths || [], + map: map + }; + }, + + translateFont: function partialEvaluatorTranslateFont(dict, xref, resources, + queue, handler, uniquePrefix, dependency) { + var baseDict = dict; + var type = dict.get('Subtype'); + assertWellFormed(isName(type), 'invalid font Subtype'); + + var composite = false; + if (type.name == 'Type0') { + // If font is a composite + // - get the descendant font + // - set the type according to the descendant font + // - get the FontDescriptor from the descendant font + var df = dict.get('DescendantFonts'); + if (!df) + return null; + + if (isRef(df)) + df = xref.fetch(df); + + dict = xref.fetchIfRef(isRef(df) ? df : df[0]); + + type = dict.get('Subtype'); + assertWellFormed(isName(type), 'invalid font Subtype'); + composite = true; + } + + var descriptor = xref.fetchIfRef(dict.get('FontDescriptor')); + if (!descriptor) { + if (type.name == 'Type3') { + // FontDescriptor is only required for Type3 fonts when the document + // is a tagged pdf. Create a barbebones one to get by. + descriptor = new Dict(); + descriptor.set('FontName', new Name(type.name)); + } else { + // Before PDF 1.5 if the font was one of the base 14 fonts, having a + // FontDescriptor was not required. + // This case is here for compatibility. + var baseFontName = dict.get('BaseFont'); + if (!isName(baseFontName)) + return null; + + // Using base font name as a font name. + baseFontName = baseFontName.name.replace(/[,_]/g, '-'); + var metricsAndMap = this.getBaseFontMetricsAndMap(baseFontName); + + var properties = { + type: type.name, + encoding: metricsAndMap.map, + differences: [], + widths: metricsAndMap.widths, + defaultWidth: metricsAndMap.defaultWidth, + firstChar: 0, + lastChar: 256 + }; + this.extractEncoding(dict, xref, properties); + + return { + name: baseFontName, + dict: baseDict, + properties: properties + }; + } + + } + + // According to the spec if 'FontDescriptor' is declared, 'FirstChar', + // 'LastChar' and 'Widths' should exists too, but some PDF encoders seems + // to ignore this rule when a variant of a standart font is used. + // TODO Fill the width array depending on which of the base font this is + // a variant. + var firstChar = xref.fetchIfRef(dict.get('FirstChar')) || 0; + var lastChar = xref.fetchIfRef(dict.get('LastChar')) || 256; + var defaultWidth = 0; + var glyphWidths = {}; + var encoding = {}; + var widths = xref.fetchIfRef(dict.get('Widths')); + if (widths) { + for (var i = 0, j = firstChar; i < widths.length; i++, j++) + glyphWidths[j] = widths[i]; + defaultWidth = parseFloat(descriptor.get('MissingWidth')) || 0; + } else { + // Trying get the BaseFont metrics (see comment above). + var baseFontName = dict.get('BaseFont'); + if (isName(baseFontName)) { + var metricsAndMap = this.getBaseFontMetricsAndMap(baseFontName.name); + + glyphWidths = metricsAndMap.widths; + defaultWidth = metricsAndMap.defaultWidth; + encoding = metricsAndMap.map; + } + } + + var fontName = xref.fetchIfRef(descriptor.get('FontName')); + assertWellFormed(isName(fontName), 'invalid font name'); + + var fontFile = descriptor.get('FontFile', 'FontFile2', 'FontFile3'); + if (fontFile) { + fontFile = xref.fetchIfRef(fontFile); + if (fontFile.dict) { + var subtype = fontFile.dict.get('Subtype'); + if (subtype) + subtype = subtype.name; + + var length1 = fontFile.dict.get('Length1'); + if (!isInt(length1)) + length1 = xref.fetchIfRef(length1); + + var length2 = fontFile.dict.get('Length2'); + if (!isInt(length2)) + length2 = xref.fetchIfRef(length2); + } + } + + var properties = { + type: type.name, + subtype: subtype, + file: fontFile, + length1: length1, + length2: length2, + composite: composite, + fixedPitch: false, + fontMatrix: dict.get('FontMatrix') || IDENTITY_MATRIX, + firstChar: firstChar || 0, + lastChar: lastChar || 256, + bbox: descriptor.get('FontBBox'), + ascent: descriptor.get('Ascent'), + descent: descriptor.get('Descent'), + xHeight: descriptor.get('XHeight'), + capHeight: descriptor.get('CapHeight'), + defaultWidth: defaultWidth, + flags: descriptor.get('Flags'), + italicAngle: descriptor.get('ItalicAngle'), + differences: [], + widths: glyphWidths, + encoding: encoding, + coded: false + }; + properties.glyphs = this.extractEncoding(dict, xref, properties); + + if (type.name === 'Type3') { + properties.coded = true; + var charProcs = xref.fetchIfRef(dict.get('CharProcs')); + var fontResources = xref.fetchIfRef(dict.get('Resources')) || resources; + properties.resources = fontResources; + for (var key in charProcs.map) { + var glyphStream = xref.fetchIfRef(charProcs.map[key]); + var queueObj = {}; + properties.glyphs[key].IRQueue = this.getIRQueue(glyphStream, + fontResources, + queueObj, + dependency); + } + } + + return { + name: fontName.name, + dict: baseDict, + file: fontFile, + properties: properties + }; + } + }; + + return constructor; +})(); + +var EvalState = (function evalState() { + 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.charSpacing = 0; + this.wordSpacing = 0; + this.textHScale = 1; + // Color spaces + this.fillColorSpace = null; + this.strokeColorSpace = null; + } + constructor.prototype = { + }; + return constructor; +})(); + diff --git a/fonts.js b/src/fonts.js similarity index 85% rename from fonts.js rename to src/fonts.js index dfe1580c8..b027b766a 100644 --- a/fonts.js +++ b/src/fonts.js @@ -2,6 +2,7 @@ /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ 'use strict'; + var isWorker = (typeof window == 'undefined'); /** @@ -20,6 +21,271 @@ var kPDFGlyphSpaceUnits = 1000; // Until hinting is fully supported this constant can be used var kHintingEnabled = false; +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' + ]); + }, + 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' + ]); + }, + 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' + ]); + }, + 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' + ]); + }, + 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' + ]); + }, + get symbolsEncoding() { + return shadow(this, 'symbolsEncoding', ['', '', '', '', '', '', '', '', '', + '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', + '', '', '', '', '', 'space', 'exclam', 'universal', 'numbersign', + 'existential', 'percent', 'ampersand', 'suchthat', 'parenleft', + 'parenright', 'asteriskmath', 'plus', 'comma', 'minus', 'period', + 'slash', 'zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', + 'eight', 'nine', 'colon', 'semicolon', 'less', 'equal', 'greater', + 'question', 'congruent', 'Alpha', 'Beta', 'Chi', 'Delta', 'Epsilon', + 'Phi', 'Gamma', 'Eta', 'Iota', 'theta1', 'Kappa', 'Lambda', 'Mu', 'Nu', + 'Omicron', 'Pi', 'Theta', 'Rho', 'Sigma', 'Tau', 'Upsilon', 'sigma1', + 'Omega', 'Xi', 'Psi', 'Zeta', 'bracketleft', 'therefore', 'bracketright', + 'perpendicular', 'underscore', 'radicalex', 'alpha', 'beta', 'chi', + 'delta', 'epsilon', 'phi', 'gamma', 'eta', 'iota', 'phi1', 'kappa', + 'lambda', 'mu', 'nu', 'omicron', 'pi', 'theta', 'rho', 'sigma', 'tau', + 'upsilon', 'omega1', 'omega', 'xi', 'psi', 'zeta', 'braceleft', 'bar', + 'braceright', 'similar', '', '', '', '', '', '', '', '', '', '', '', '', + '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', + '', '', '', 'Euro', 'Upsilon1', 'minute', 'lessequal', 'fraction', + 'infinity', 'florin', 'club', 'diamond', 'heart', 'spade', 'arrowboth', + 'arrowleft', 'arrowup', 'arrowright', 'arrowdown', 'degree', 'plusminus', + 'second', 'greaterequal', 'multiply', 'proportional', 'partialdiff', + 'bullet', 'divide', 'notequal', 'equivalence', 'approxequal', 'ellipsis', + 'arrowvertex', 'arrowhorizex', 'carriagereturn', 'aleph', 'Ifraktur', + 'Rfraktur', 'weierstrass', 'circlemultiply', 'circleplus', 'emptyset', + 'intersection', 'union', 'propersuperset', 'reflexsuperset', 'notsubset', + 'propersubset', 'reflexsubset', 'element', 'notelement', 'angle', + 'gradient', 'registerserif', 'copyrightserif', 'trademarkserif', + 'product', 'radical', 'dotmath', 'logicalnot', 'logicaland', 'logicalor', + 'arrowdblboth', 'arrowdblleft', 'arrowdblup', 'arrowdblright', + 'arrowdbldown', 'lozenge', 'angleleft', 'registersans', 'copyrightsans', + 'trademarksans', 'summation', 'parenlefttp', 'parenleftex', + 'parenleftbt', 'bracketlefttp', 'bracketleftex', 'bracketleftbt', + 'bracelefttp', 'braceleftmid', 'braceleftbt', 'braceex', '', + 'angleright', 'integral', 'integraltp', 'integralex', 'integralbt', + 'parenrighttp', 'parenrightex', 'parenrightbt', 'bracketrighttp', + 'bracketrightex', 'bracketrightbt', 'bracerighttp', 'bracerightmid', + 'bracerightbt' + ]); + }, + 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' + ]); + } +}; + /** * Hold a map of decoded fonts and of the standard fourteen Type1 * fonts and their acronyms. @@ -3006,3 +3272,4 @@ var Type2CFF = (function type2CFF() { return constructor; })(); + diff --git a/src/function.js b/src/function.js new file mode 100644 index 000000000..e2b191274 --- /dev/null +++ b/src/function.js @@ -0,0 +1,307 @@ +/* -*- 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 PDFFunction = (function() { + var CONSTRUCT_SAMPLED = 0; + var CONSTRUCT_INTERPOLATED = 2; + var CONSTRUCT_STICHED = 3; + var CONSTRUCT_POSTSCRIPT = 4; + + return { + 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++) { + while (codeSize < bps) { + codeBuf <<= 8; + codeBuf |= strBytes[strIdx++]; + codeSize += 8; + } + codeSize -= bps; + array.push(codeBuf >> codeSize); + codeBuf &= (1 << codeSize) - 1; + } + return array; + }, + + getIR: function(xref, fn) { + var dict = fn.dict; + if (!dict) + dict = fn; + + 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'); + + return typeFn.call(this, fn, dict, xref); + }, + + fromIR: function(IR) { + var type = IR[0]; + switch (type) { + case CONSTRUCT_SAMPLED: + return this.constructSampledFromIR(IR); + case CONSTRUCT_INTERPOLATED: + return this.constructInterpolatedFromIR(IR); + case CONSTRUCT_STICHED: + return this.constructStichedFromIR(IR); + case CONSTRUCT_POSTSCRIPT: + default: + return this.constructPostScriptFromIR(IR); + } + }, + + parse: function(xref, fn) { + var IR = this.getIR(xref, fn); + return this.fromIR(IR); + }, + + constructSampled: function(str, dict) { + var domain = dict.get('Domain'); + var range = dict.get('Range'); + + if (!domain || !range) + error('No domain or range'); + + var inputSize = domain.length / 2; + var outputSize = range.length / 2; + + if (inputSize != 1) + error('No support for multi-variable inputs to functions: ' + + inputSize); + + 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: ' + order); + + 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); + + return [ + CONSTRUCT_SAMPLED, inputSize, domain, encode, decode, samples, size, + outputSize, bps, range + ]; + }, + + constructSampledFromIR: function(IR) { + var inputSize = IR[1]; + var domain = IR[2]; + var encode = IR[3]; + var decode = IR[4]; + var samples = IR[5]; + var size = IR[6]; + var outputSize = IR[7]; + var bps = IR[8]; + var range = IR[9]; + + return 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: ' + inputSize + ' != ' + + args.length); + + 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 = [], v = 0; + for (var i = 0; i < outputSize; ++i) { + if (ceil == floor) { + v = samples[ceil + i]; + } else { + var low = samples[floor + i]; + var high = samples[ceil + i]; + 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; + } + }, + + constructInterpolated: + function pdfFunctionConstructInterpolated(str, dict) { + var c0 = dict.get('C0') || [0]; + var c1 = dict.get('C1') || [1]; + var n = dict.get('N'); + + if (!isArray(c0) || !isArray(c1)) + error('Illegal dictionary for interpolated function'); + + var length = c0.length; + var diff = []; + for (var i = 0; i < length; ++i) + diff.push(c1[i] - c0[i]); + + return [CONSTRUCT_INTERPOLATED, c0, diff, n]; + }, + + constructInterpolatedFromIR: + function pdfFunctionconstructInterpolatedFromIR(IR) { + var c0 = IR[1]; + var diff = IR[2]; + var n = IR[3]; + + var length = diff.length; + + return function(args) { + var x = n == 1 ? args[0] : Math.pow(args[0], n); + + var out = []; + for (var j = 0; j < length; ++j) + out.push(c0[j] + (x * diff[j])); + + return out; + + } + }, + + constructStiched: function pdfFunctionConstructStiched(fn, dict, xref) { + var domain = dict.get('Domain'); + var range = dict.get('Range'); + + if (!domain) + error('No domain'); + + var inputSize = domain.length / 2; + if (inputSize != 1) + error('Bad domain for stiched function'); + + var fnRefs = dict.get('Functions'); + var fns = []; + for (var i = 0, ii = fnRefs.length; i < ii; ++i) + fns.push(PDFFunction.getIR(xref, xref.fetchIfRef(fnRefs[i]))); + + var bounds = dict.get('Bounds'); + var encode = dict.get('Encode'); + + return [CONSTRUCT_STICHED, domain, bounds, encode, fns]; + }, + + constructStichedFromIR: function pdfFunctionConstructStichedFromIR(IR) { + var domain = IR[1]; + var bounds = IR[2]; + var encode = IR[3]; + var fnsIR = IR[4]; + var fns = []; + + for (var i = 0; i < fnsIR.length; i++) { + fns.push(PDFFunction.fromIR(fnsIR[i])); + } + + return function(args) { + var clip = function(v, min, max) { + if (v > max) + v = max; + else if (v < min) + v = min; + return v; + }; + + // clip to domain + var v = clip(args[0], domain[0], domain[1]); + // calulate which bound the value is in + for (var i = 0, ii = bounds.length; i < ii; ++i) { + if (v < bounds[i]) + break; + } + + // encode value into domain of function + var dmin = domain[0]; + if (i > 0) + dmin = bounds[i - 1]; + var dmax = domain[1]; + if (i < bounds.length) + dmax = bounds[i]; + + var rmin = encode[2 * i]; + var rmax = encode[2 * i + 1]; + + var v2 = rmin + (v - dmin) * (rmax - rmin) / (dmax - dmin); + + // call the appropropriate function + return fns[i]([v2]); + }; + }, + + constructPostScript: function pdfFunctionConstructPostScript() { + return [CONSTRUCT_POSTSCRIPT]; + }, + + constructPostScriptFromIR: function pdfFunctionConstructPostScriptFromIR() { + TODO('unhandled type of function'); + return function() { + return [255, 105, 180]; + }; + } + }; +})(); + diff --git a/glyphlist.js b/src/glyphlist.js similarity index 100% rename from glyphlist.js rename to src/glyphlist.js diff --git a/src/image.js b/src/image.js new file mode 100644 index 000000000..b281e21c1 --- /dev/null +++ b/src/image.js @@ -0,0 +1,257 @@ +/* -*- 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 PDFImage = (function pdfImage() { + 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: ' + this.width + ' or height: ' + + this.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.imageMask); + } + } + this.bpc = bitsPerComponent; + + if (!this.imageMask) { + var colorSpace = dict.get('ColorSpace', 'CS'); + if (!colorSpace) { + TODO('JPX images (which don"t require color spaces'); + colorSpace = new Name('DeviceRGB'); + } + this.colorSpace = ColorSpace.parse(colorSpace, xref, res); + this.numComps = this.colorSpace.numComps; + } + + this.decode = dict.get('Decode', 'D'); + + var mask = xref.fetchIfRef(dict.get('Mask')); + var smask = xref.fetchIfRef(dict.get('SMask')); + + if (mask) { + TODO('masked images'); + } else if (smask) { + this.smask = new PDFImage(xref, res, smask); + } + } + + constructor.prototype = { + getComponents: function getComponents(buffer, decodeMap) { + 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 = bpc <= 8 ? new Uint8Array(length) : + bpc <= 16 ? new Uint16Array(length) : new Uint32Array(length); + var rowComps = width * numComps; + + if (bpc == 1) { + var valueZero = 0, valueOne = 1; + if (decodeMap) { + valueZero = decodeMap[0] ? 1 : 0; + valueOne = decodeMap[1] ? 1 : 0; + } + 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; + } + + output[i] = !(buf & mask) ? valueZero : valueOne; + } + } else { + if (decodeMap != null) + TODO('interpolate component values'); + var bits = 0, buf = 0; + for (var i = 0, ii = length; i < ii; ++i) { + if (i % rowComps == 0) { + buf = 0; + bits = 0; + } + + while (bits < bpc) { + buf = (buf << 8) | buffer[bufferPos++]; + bits += 8; + } + + var remainingBits = bits - bpc; + output[i] = buf >> remainingBits; + buf = buf & ((1 << remainingBits) - 1); + bits = remainingBits; + } + } + return output; + }, + getOpacity: function getOpacity() { + var smask = this.smask; + var width = this.width; + var height = this.height; + var buf = new Uint8Array(width * height); + + if (smask) { + if (smask.image.getImage) { + // smask is a DOM image + var tempCanvas = new ScratchCanvas(width, height); + var tempCtx = tempCanvas.getContext('2d'); + var domImage = smask.image.getImage(); + tempCtx.drawImage(domImage, 0, 0, domImage.width, domImage.height, + 0, 0, width, height); + var data = tempCtx.getImageData(0, 0, width, height).data; + for (var i = 0, j = 0, ii = width * height; i < ii; ++i, j += 4) + buf[i] = data[j]; // getting first component value + return buf; + } + var sw = smask.width; + var sh = smask.height; + if (sw != this.width || sh != this.height) + error('smask dimensions do not match image dimensions: ' + sw + + ' != ' + this.width + ', ' + sh + ' != ' + this.height); + + smask.fillGrayBuffer(buf); + return buf; + } else { + for (var i = 0, ii = width * height; i < ii; ++i) + buf[i] = 255; + } + return buf; + }, + applyStencilMask: function applyStencilMask(buffer, inverseDecode) { + var width = this.width, height = this.height; + var bitStrideLength = (width + 7) >> 3; + this.image.reset(); + var imgArray = this.image.getBytes(bitStrideLength * height); + var imgArrayPos = 0; + var i, j, mask, buf; + // removing making non-masked pixels transparent + var bufferPos = 3; // alpha component offset + for (i = 0; i < height; i++) { + mask = 0; + for (j = 0; j < width; j++) { + if (!mask) { + buf = imgArray[imgArrayPos++]; + mask = 128; + } + if (!(buf & mask) == inverseDecode) { + buffer[bufferPos] = 0; + } + bufferPos += 4; + mask >>= 1; + } + } + }, + fillRgbaBuffer: function fillRgbaBuffer(buffer, decodeMap) { + 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; + this.image.reset(); + var imgArray = this.image.getBytes(height * rowBytes); + + var comps = this.colorSpace.getRgbBuffer( + this.getComponents(imgArray, decodeMap), bpc); + var compsPos = 0; + var opacity = this.getOpacity(); + var opacityPos = 0; + var length = width * height * 4; + + 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: ' + 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; + this.image.reset(); + 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; +})(); + +var JpegImage = (function() { + function JpegImage(objId, imageData, objs) { + var src = 'data:image/jpeg;base64,' + window.btoa(imageData); + + var img = new Image(); + img.onload = (function() { + this.loaded = true; + + objs.resolve(objId, this); + + if (this.onLoad) + this.onLoad(); + }).bind(this); + img.src = src; + this.domImage = img; + } + + JpegImage.prototype = { + getImage: function() { + return this.domImage; + } + }; + + return JpegImage; +})(); + diff --git a/metrics.js b/src/metrics.js similarity index 99% rename from metrics.js rename to src/metrics.js index d4d07ec0d..c21b4aed1 100644 --- a/metrics.js +++ b/src/metrics.js @@ -2941,3 +2941,4 @@ var Metrics = { 'a191': 918 } }; + diff --git a/src/obj.js b/src/obj.js new file mode 100644 index 000000000..8d5684ec2 --- /dev/null +++ b/src/obj.js @@ -0,0 +1,742 @@ +/* -*- 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 Name = (function nameName() { + function constructor(name) { + this.name = name; + } + + constructor.prototype = { + }; + + return constructor; +})(); + +var Cmd = (function cmdCmd() { + function constructor(cmd) { + this.cmd = cmd; + } + + constructor.prototype = { + }; + + return constructor; +})(); + +var Dict = (function dictDict() { + function constructor() { + this.map = Object.create(null); + } + + constructor.prototype = { + get: function dictGet(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 dictSet(key, value) { + this.map[key] = value; + }, + + has: function dictHas(key) { + return key in this.map; + }, + + forEach: function dictForEach(callback) { + for (var key in this.map) { + callback(key, this.map[key]); + } + } + }; + + return constructor; +})(); + +var Ref = (function refRef() { + function constructor(num, gen) { + this.num = num; + this.gen = gen; + } + + constructor.prototype = { + }; + + return constructor; +})(); + +// The reference is identified by number and generation, +// this structure stores only one instance of the reference. +var RefSet = (function refSet() { + function constructor() { + this.dict = {}; + } + + constructor.prototype = { + has: function refSetHas(ref) { + return !!this.dict['R' + ref.num + '.' + ref.gen]; + }, + + put: function refSetPut(ref) { + this.dict['R' + ref.num + '.' + ref.gen] = ref; + } + }; + + return constructor; +})(); + +var Catalog = (function catalogCatalog() { + 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 pagesObj = this.catDict.get('Pages'); + assertWellFormed(isRef(pagesObj), 'invalid top-level pages reference'); + var xrefObj = this.xref.fetch(pagesObj); + assertWellFormed(isDict(xrefObj), 'invalid top-level pages dictionary'); + // shadow the prototype getter + return shadow(this, 'toplevelPagesDict', xrefObj); + }, + get documentOutline() { + var obj = this.catDict.get('Outlines'); + var xref = this.xref; + var root = { items: [] }; + if (isRef(obj)) { + obj = xref.fetch(obj).get('First'); + var processed = new RefSet(); + if (isRef(obj)) { + var queue = [{obj: obj, parent: root}]; + // to avoid recursion keeping track of the items + // in the processed dictionary + processed.put(obj); + while (queue.length > 0) { + var i = queue.shift(); + var outlineDict = xref.fetch(i.obj); + if (!outlineDict.has('Title')) + error('Invalid outline item'); + var dest = outlineDict.get('A'); + if (dest) + dest = xref.fetchIfRef(dest).get('D'); + else if (outlineDict.has('Dest')) { + dest = outlineDict.get('Dest'); + if (isName(dest)) + dest = dest.name; + } + var title = xref.fetchIfRef(outlineDict.get('Title')); + var outlineItem = { + dest: dest, + title: stringToPDFString(title), + color: outlineDict.get('C') || [0, 0, 0], + count: outlineDict.get('Count'), + bold: !!(outlineDict.get('F') & 2), + italic: !!(outlineDict.get('F') & 1), + items: [] + }; + i.parent.items.push(outlineItem); + obj = outlineDict.get('First'); + if (isRef(obj) && !processed.has(obj)) { + queue.push({obj: obj, parent: outlineItem}); + processed.put(obj); + } + obj = outlineDict.get('Next'); + if (isRef(obj) && !processed.has(obj)) { + queue.push({obj: obj, parent: i.parent}); + processed.put(obj); + } + } + } + } + obj = root.items.length > 0 ? root.items : null; + return shadow(this, 'documentOutline', 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 catalogTraverseKids(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, kid)); + } else { // must be a child page dictionary + assertWellFormed( + isDict(obj), + 'page dictionary kid reference points to wrong type of object' + ); + this.traverseKids(obj); + } + } + }, + get destinations() { + function fetchDestination(xref, ref) { + var dest = xref.fetchIfRef(ref); + return isDict(dest) ? dest.get('D') : dest; + } + + var xref = this.xref; + var dests = {}, nameTreeRef, nameDictionaryRef; + var obj = this.catDict.get('Names'); + if (obj) + nameTreeRef = xref.fetchIfRef(obj).get('Dests'); + else if (this.catDict.has('Dests')) + nameDictionaryRef = this.catDict.get('Dests'); + + if (nameDictionaryRef) { + // reading simple destination dictionary + obj = xref.fetchIfRef(nameDictionaryRef); + obj.forEach(function catalogForEach(key, value) { + if (!value) return; + dests[key] = fetchDestination(xref, value); + }); + } + if (nameTreeRef) { + // reading name tree + var processed = new RefSet(); + processed.put(nameTreeRef); + var queue = [nameTreeRef]; + while (queue.length > 0) { + var i, n; + obj = xref.fetch(queue.shift()); + if (obj.has('Kids')) { + var kids = obj.get('Kids'); + for (i = 0, n = kids.length; i < n; i++) { + var kid = kids[i]; + if (processed.has(kid)) + error('invalid destinations'); + queue.push(kid); + processed.put(kid); + } + continue; + } + var names = obj.get('Names'); + for (i = 0, n = names.length; i < n; i += 2) { + dests[names[i]] = fetchDestination(xref, names[i + 1]); + } + } + } + return shadow(this, 'destinations', dests); + }, + getPage: function catalogGetPage(n) { + var pageCache = this.pageCache; + if (!pageCache) { + pageCache = this.pageCache = []; + this.traverseKids(this.toplevelPagesDict); + } + return this.pageCache[n - 1]; + } + }; + + return constructor; +})(); + +var XRef = (function xRefXRef() { + function constructor(stream, startXRef, mainXRefEntriesOffset) { + this.stream = stream; + this.entries = []; + this.xrefstms = {}; + var trailerDict = this.readXRef(startXRef); + + // prepare the XRef cache + this.cache = []; + + var encrypt = trailerDict.get('Encrypt'); + if (encrypt) { + var fileId = trailerDict.get('ID'); + this.encrypt = new CipherTransformFactory(this.fetch(encrypt), + fileId[0] /*, password */); + } + + // get the root dictionary (catalog) object + if (!isRef(this.root = trailerDict.get('Root'))) + error('Invalid root reference'); + } + + 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: ' + first + ', ' + n); + for (var i = first; i < first + n; ++i) { + var entry = {}; + if (!isInt(obj = parser.getObj())) + error('Invalid XRef table: ' + first + ', ' + n); + entry.offset = obj; + if (!isInt(obj = parser.getObj())) + error('Invalid XRef table: ' + first + ', ' + n); + 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: ' + first + ', ' + n); + } + 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; + } + } + } + + // 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; + // ignore previously loaded xref streams (possible infinite recursion) + if (!(pos in this.xrefstms)) { + this.xrefstms[pos] = 1; + this.readXRef(pos); + } + } + + return dict; + }, + readXRefStream: function readXRefStream(stream) { + var streamParameters = stream.parameters; + 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: ' + first + ', ' + n); + 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: ' + first + ', ' + n); + } + 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: ' + type); + } + 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; + }, + indexObjects: function indexObjects() { + // Simple scan through the PDF content to find objects, + // trailers and XRef streams. + function readToken(data, offset) { + var token = '', ch = data[offset]; + while (ch !== 13 && ch !== 10) { + if (++offset >= data.length) + break; + token += String.fromCharCode(ch); + ch = data[offset]; + } + return token; + } + function skipUntil(data, offset, what) { + var length = what.length, dataLength = data.length; + var skipped = 0; + // finding byte sequence + while (offset < dataLength) { + var i = 0; + while (i < length && data[offset + i] == what[i]) + ++i; + if (i >= length) + break; // sequence found + + offset++; + skipped++; + } + return skipped; + } + var trailerBytes = new Uint8Array([116, 114, 97, 105, 108, 101, 114]); + var startxrefBytes = new Uint8Array([115, 116, 97, 114, 116, 120, 114, + 101, 102]); + var endobjBytes = new Uint8Array([101, 110, 100, 111, 98, 106]); + var xrefBytes = new Uint8Array([47, 88, 82, 101, 102]); + + var stream = this.stream; + stream.pos = 0; + var buffer = stream.getBytes(); + var position = stream.start, length = buffer.length; + var trailers = [], xrefStms = []; + var state = 0; + var currentToken; + while (position < length) { + var ch = buffer[position]; + if (ch === 32 || ch === 9 || ch === 13 || ch === 10) { + ++position; + continue; + } + if (ch === 37) { // %-comment + do { + ++position; + ch = buffer[position]; + } while (ch !== 13 && ch !== 10); + continue; + } + var token = readToken(buffer, position); + var m; + if (token === 'xref') { + position += skipUntil(buffer, position, trailerBytes); + trailers.push(position); + position += skipUntil(buffer, position, startxrefBytes); + } else if ((m = /^(\d+)\s+(\d+)\s+obj\b/.exec(token))) { + this.entries[m[1]] = { + offset: position, + gen: m[2] | 0, + uncompressed: true + }; + + var contentLength = skipUntil(buffer, position, endobjBytes) + 7; + var content = buffer.subarray(position, position + contentLength); + + // checking XRef stream suspect + // (it shall have '/XRef' and next char is not a letter) + var xrefTagOffset = skipUntil(content, 0, xrefBytes); + if (xrefTagOffset < contentLength && + content[xrefTagOffset + 5] < 64) { + xrefStms.push(position); + this.xrefstms[position] = 1; // don't read it recursively + } + + position += contentLength; + } else + position += token.length + 1; + } + // reading XRef streams + for (var i = 0; i < xrefStms.length; ++i) { + this.readXRef(xrefStms[i]); + } + // finding main trailer + var dict; + for (var i = 0; i < trailers.length; ++i) { + stream.pos = trailers[i]; + var parser = new Parser(new Lexer(stream), true); + var obj = parser.getObj(); + if (!isCmd(obj, 'trailer')) + continue; + // read the trailer dictionary + if (!isDict(dict = parser.getObj())) + continue; + // taking the first one with 'ID' + if (dict.has('ID')) + return dict; + } + // no tailer with 'ID', taking last one (if exists) + if (dict) + return dict; + // nothing helps + error('Invalid PDF structure'); + return null; + }, + 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); + } + return this.indexObjects(); + }, + getEntry: function xRefGetEntry(i) { + var e = this.entries[i]; + if (e.free) + error('reading an XRef stream not implemented yet'); + return e; + }, + fetchIfRef: function xRefFetchIfRef(obj) { + if (!isRef(obj)) + return obj; + return this.fetch(obj); + }, + fetch: function xRefFetch(ref, suppressEncryption) { + 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) { + num = parseInt(obj3.cmd.substring(3), 10); + if (!isNaN(num)) + return num; + } + error('bad XRef entry'); + } + if (this.encrypt && !suppressEncryption) { + try { + e = parser.getObj(this.encrypt.createCipherTransform(num, gen)); + } catch (ex) { + // almost all streams must be encrypted, but sometimes + // they are not probably due to some broken generators + // re-trying without encryption + return this.fetch(ref, true); + } + } else { + e = parser.getObj(); + } + // Don't cache streams since they are mutable (except images). + if (!isStream(e) || e.getImage) + 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) { + num = parser.getObj(); + if (!isInt(num)) { + error('invalid object number in the ObjStm stream: ' + num); + } + nums.push(num); + var offset = parser.getObj(); + if (!isInt(offset)) { + error('invalid object offset in the ObjStm stream: ' + offset); + } + } + // 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 xRefGetCatalogObj() { + return this.fetch(this.root); + } + }; + + return constructor; +})(); + +/** + * A PDF document and page is built of many objects. E.g. there are objects + * for fonts, images, rendering code and such. These objects might get processed + * inside of a worker. The `PDFObjects` implements some basic functions to + * manage these objects. + */ +var PDFObjects = (function() { + function PDFObjects() { + this.objs = {}; + } + + PDFObjects.prototype = { + objs: null, + + /** + * Internal function. + * Ensures there is an object defined for `objId`. Stores `data` on the + * object *if* it is created. + */ + ensureObj: function(objId, data) { + if (this.objs[objId]) + return this.objs[objId]; + return this.objs[objId] = new Promise(objId, data); + }, + + /** + * If called *without* callback, this returns the data of `objId` but the + * object needs to be resolved. If it isn't, this function throws. + * + * If called *with* a callback, the callback is called with the data of the + * object once the object is resolved. That means, if you call this + * function and the object is already resolved, the callback gets called + * right away. + */ + get: function(objId, callback) { + // If there is a callback, then the get can be async and the object is + // not required to be resolved right now + if (callback) { + this.ensureObj(objId).then(callback); + return null; + } + + // If there isn't a callback, the user expects to get the resolved data + // directly. + var obj = this.objs[objId]; + + // If there isn't an object yet or the object isn't resolved, then the + // data isn't ready yet! + if (!obj || !obj.isResolved) { + throw 'Requesting object that isn\'t resolved yet ' + objId; + return null; + } else { + return obj.data; + } + }, + + /** + * Resolves the object `objId` with optional `data`. + */ + resolve: function(objId, data) { + var objs = this.objs; + + // In case there is a promise already on this object, just resolve it. + if (objs[objId]) { + objs[objId].resolve(data); + } else { + this.ensureObj(objId, data); + } + }, + + onData: function(objId, callback) { + this.ensureObj(objId).onData(callback); + }, + + isResolved: function(objId) { + var objs = this.objs; + if (!objs[objId]) { + return false; + } else { + return objs[objId].isResolved; + } + }, + + hasData: function(objId) { + var objs = this.objs; + if (!objs[objId]) { + return false; + } else { + return objs[objId].hasData; + } + }, + + /** + * Sets the data of an object but *doesn't* resolve it. + */ + setData: function(objId, data) { + // Watchout! If you call `this.ensureObj(objId, data)` you're going to + // create a *resolved* promise which shouldn't be the case! + this.ensureObj(objId).data = data; + } + }; + return PDFObjects; +})(); + diff --git a/src/parser.js b/src/parser.js new file mode 100644 index 000000000..a740615ed --- /dev/null +++ b/src/parser.js @@ -0,0 +1,636 @@ +/* -*- 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 EOF = {}; + +function isEOF(v) { + return v == EOF; +} + +var Parser = (function parserParser() { + function constructor(lexer, allowStreams, xref) { + this.lexer = lexer; + this.allowStreams = allowStreams; + this.xref = xref; + this.inlineImg = 0; + this.refill(); + } + + constructor.prototype = { + refill: function parserRefill() { + this.buf1 = this.lexer.getObj(); + this.buf2 = this.lexer.getObj(); + }, + shift: function parserShift() { + if (isCmd(this.buf2, 'ID')) { + this.buf1 = this.buf2; + this.buf2 = null; + // skip byte after ID + this.lexer.skip(); + } else { + this.buf1 = this.buf2; + this.buf2 = this.lexer.getObj(); + } + }, + getObj: function parserGetObj(cipherTransform) { + if (isCmd(this.buf1, 'BI')) { // inline image + this.shift(); + return this.makeInlineImage(cipherTransform); + } + 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; + } + 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'); + } 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 (isCmd(this.buf2, 'stream')) { + return this.allowStreams ? + this.makeStream(dict, cipherTransform) : dict; + } + this.shift(); + return dict; + } + 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; + } + 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; + }, + makeInlineImage: function parserMakeInlineImage(cipherTransform) { + var lexer = this.lexer; + var stream = lexer.stream; + + // parse dictionary + var dict = new Dict(); + while (!isCmd(this.buf1, 'ID') && !isEOF(this.buf1)) { + if (!isName(this.buf1)) { + error('Dictionary key must be a name object'); + } else { + var key = this.buf1.name; + this.shift(); + if (isEOF(this.buf1)) + break; + dict.set(key, this.getObj(cipherTransform)); + } + } + + // parse image stream + var startPos = stream.pos; + + // searching for the /\sEI\s/ + var state = 0, ch; + while (state != 4 && (ch = stream.getByte()) != null) { + switch (ch) { + case 0x20: + case 0x0D: + case 0x0A: + state = state === 3 ? 4 : 1; + break; + case 0x45: + state = state === 1 ? 2 : 0; + break; + case 0x49: + state = state === 2 ? 3 : 0; + break; + default: + state = 0; + break; + } + } + + // TODO improve the small images performance to remove the limit + var inlineImgLimit = 500; + if (++this.inlineImg >= inlineImgLimit) { + if (this.inlineImg === inlineImgLimit) + warn('Too many inline images'); + this.shift(); + return null; + } + + var length = (stream.pos - 4) - startPos; + var imageStream = stream.makeSubStream(startPos, length, dict); + if (cipherTransform) + imageStream = cipherTransform.createStream(imageStream); + imageStream = this.filter(imageStream, dict, length); + imageStream.parameters = dict; + + this.buf2 = new Cmd('EI'); + this.shift(); + + return imageStream; + }, + makeStream: function parserMakeStream(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 parserFilter(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: ' + filter); + else { + params = null; + if (isArray(paramsArray) && (i in paramsArray)) + params = paramsArray[i]; + stream = this.makeFilter(stream, filter.name, length, params); + // after the first stream the length variable is invalid + length = null; + } + } + } + return stream; + }, + makeFilter: function parserMakeFilter(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 == 'LZWDecode' || name == 'LZW') { + var earlyChange = 1; + if (params) { + if (params.has('EarlyChange')) + earlyChange = params.get('EarlyChange'); + return new PredictorStream( + new LZWStream(stream, earlyChange), params); + } + return new LZWStream(stream, earlyChange); + } else if (name == 'DCTDecode' || name == 'DCT') { + var bytes = stream.getBytes(length); + return new JpegStream(bytes, stream.dict); + } else if (name == 'ASCII85Decode' || name == 'A85') { + return new Ascii85Stream(stream); + } else if (name == 'ASCIIHexDecode' || name == 'AHx') { + return new AsciiHexStream(stream); + } else if (name == 'CCITTFaxDecode' || name == 'CCF') { + return new CCITTFaxStream(stream, params); + } else { + TODO('filter "' + name + '" not supported yet'); + } + return stream; + } + }; + + return constructor; +})(); + +var Lexer = (function lexer() { + function constructor(stream) { + this.stream = stream; + } + + constructor.isSpace = function lexerIsSpace(ch) { + return ch == ' ' || ch == '\t' || ch == '\x0d' || ch == '\x0a'; + }; + + // 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 + ]; + + 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 lexerGetNumber(ch) { + var floating = false; + var str = ch; + var stream = this.stream; + for (;;) { + 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(); + } + var value = parseFloat(str); + if (isNaN(value)) + error('Invalid floating point number: ' + value); + return value; + }, + getString: function lexerGetString() { + var numParen = 1; + var done = false; + var str = ''; + var stream = this.stream; + var ch; + do { + ch = stream.getChar(); + switch (ch) { + case undefined: + warn('Unterminated string'); + done = true; + break; + case '(': + ++numParen; + str += ch; + break; + case ')': + if (--numParen == 0) { + done = true; + } else { + str += ch; + } + break; + case '\\': + ch = stream.getChar(); + switch (ch) { + 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; + default: + str += ch; + } + } while (!done); + return str; + }, + getName: function lexerGetName(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: ' + x2); + 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: ' + + str.length); + return new Name(str); + }, + getHexString: function lexerGetHexString(ch) { + var str = ''; + var stream = this.stream; + for (;;) { + 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) + error('Illegal character in hex string: ' + ch); + + ch = stream.getChar(); + while (specialChars[ch.charCodeAt(0)] == 1) + ch = stream.getChar(); + + if ((x2 = toHexDigit(ch)) == -1) + error('Illegal character in hex string: ' + ch); + + str += String.fromCharCode((x << 4) | x2); + } + } + return str; + }, + getObj: function lexerGetObj() { + // 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: ' + ch); + 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: ' + str.length); + break; + } + str += ch; + } + if (str == 'true') + return true; + if (str == 'false') + return false; + if (str == 'null') + return null; + return new Cmd(str); + }, + skipToNextLine: function lexerSkipToNextLine() { + 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; + } + } + }, + skip: function lexerSkip() { + this.stream.skip(); + } + }; + + return constructor; +})(); + +var Linearization = (function linearizationLinearization() { + 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 linearizationGetInt(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 linearizationGetHint(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: ' + index); + 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; +})(); + diff --git a/src/pattern.js b/src/pattern.js new file mode 100644 index 000000000..8e7760e51 --- /dev/null +++ b/src/pattern.js @@ -0,0 +1,290 @@ +/* -*- 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 Pattern = (function patternPattern() { + // Constructor should define this.getPattern + function constructor() { + error('should not call Pattern constructor'); + } + + constructor.prototype = { + // Input: current Canvas context + // Output: the appropriate fillStyle or strokeStyle + getPattern: function pattern_getStyle(ctx) { + error('Should not call Pattern.getStyle: ' + ctx); + } + }; + + constructor.shadingFromIR = function pattern_shadingFromIR(ctx, raw) { + return Shadings[raw[0]].fromIR(ctx, raw); + } + + constructor.parseShading = function pattern_shading(shading, matrix, + xref, res, ctx) { + + var dict = isStream(shading) ? shading.dict : shading; + var type = dict.get('ShadingType'); + + switch (type) { + case 2: + case 3: + // both radial and axial shadings are handled by RadialAxial shading + return new Shadings.RadialAxial(dict, matrix, xref, res, ctx); + default: + return new Shadings.Dummy(); + } + }; + return constructor; +})(); + +var Shadings = {}; + +// Radial and axial shading have very similar implementations +// If needed, the implementations can be broken into two classes +Shadings.RadialAxial = (function radialAxialShading() { + function constructor(dict, matrix, xref, res, ctx) { + this.matrix = matrix; + this.coordsArr = dict.get('Coords'); + this.shadingType = dict.get('ShadingType'); + this.type = 'Pattern'; + + this.ctx = ctx; + var cs = dict.get('ColorSpace', 'CS'); + cs = ColorSpace.parse(cs, xref, res); + this.cs = cs; + + var t0 = 0.0, t1 = 1.0; + if (dict.has('Domain')) { + var domainArr = dict.get('Domain'); + t0 = domainArr[0]; + t1 = domainArr[1]; + } + + var extendStart = false, extendEnd = false; + if (dict.has('Extend')) { + var extendArr = dict.get('Extend'); + extendStart = extendArr[0]; + extendEnd = extendArr[1]; + TODO('Support extend'); + } + + this.extendStart = extendStart; + this.extendEnd = extendEnd; + + var fnObj = dict.get('Function'); + fnObj = xref.fetchIfRef(fnObj); + if (isArray(fnObj)) + error('No support for array of functions'); + else if (!isPDFFunction(fnObj)) + error('Invalid function'); + var fn = PDFFunction.parse(xref, fnObj); + + // 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; + + var colorStops = []; + for (var i = t0; i <= t1; i += step) { + var color = fn([i]); + var rgbColor = Util.makeCssRgb.apply(this, cs.getRgb(color)); + colorStops.push([(i - t0) / diff, rgbColor]); + } + + this.colorStops = colorStops; + } + + constructor.fromIR = function(ctx, raw) { + var type = raw[1]; + var colorStops = raw[2]; + var p0 = raw[3]; + var p1 = raw[4]; + var r0 = raw[5]; + var r1 = raw[6]; + + var curMatrix = ctx.mozCurrentTransform; + if (curMatrix) { + var userMatrix = ctx.mozCurrentTransformInverse; + + p0 = Util.applyTransform(p0, curMatrix); + p0 = Util.applyTransform(p0, userMatrix); + + p1 = Util.applyTransform(p1, curMatrix); + p1 = Util.applyTransform(p1, userMatrix); + } + + if (type == 2) + var grad = ctx.createLinearGradient(p0[0], p0[1], p1[0], p1[1]); + else if (type == 3) + var grad = ctx.createRadialGradient(p0[0], p0[1], r0, p1[0], p1[1], r1); + + for (var i = 0, ii = colorStops.length; i < ii; ++i) { + var c = colorStops[i]; + grad.addColorStop(c[0], c[1]); + } + return grad; + } + + constructor.prototype = { + getIR: function RadialAxialShading_getIR() { + var coordsArr = this.coordsArr; + var type = this.shadingType; + if (type == 2) { + var p0 = [coordsArr[0], coordsArr[1]]; + var p1 = [coordsArr[2], coordsArr[3]]; + var r0 = null; + var r1 = null; + } else if (type == 3) { + var p0 = [coordsArr[0], coordsArr[1]]; + var p1 = [coordsArr[3], coordsArr[4]]; + var r0 = coordsArr[2]; + var r1 = coordsArr[5]; + } else { + error('getPattern type unknown: ' + type); + } + + var matrix = this.matrix; + if (matrix) { + p0 = Util.applyTransform(p0, matrix); + p1 = Util.applyTransform(p1, matrix); + } + + return ['RadialAxial', type, this.colorStops, p0, p1, r0, r1]; + } + }; + + return constructor; +})(); + +Shadings.Dummy = (function dummyShading() { + function constructor() { + this.type = 'Pattern'; + } + + constructor.fromIR = function() { + return 'hotpink'; + } + + constructor.prototype = { + getIR: function dummpy_getir() { + return ['Dummy']; + } + }; + return constructor; +})(); + +var TilingPattern = (function tilingPattern() { + var PAINT_TYPE_COLORED = 1, PAINT_TYPE_UNCOLORED = 2; + + function TilingPattern(IR, color, ctx, objs) { + var IRQueue = IR[2]; + this.matrix = IR[3]; + var bbox = IR[4]; + var xstep = IR[5]; + var ystep = IR[6]; + var paintType = IR[7]; + + TODO('TilingType'); + + this.curMatrix = ctx.mozCurrentTransform; + this.invMatrix = ctx.mozCurrentTransformInverse; + this.ctx = ctx; + this.type = 'Pattern'; + + var x0 = bbox[0], y0 = bbox[1], x1 = bbox[2], y1 = bbox[3]; + + var topLeft = [x0, y0]; + // we want the canvas to be as large as the step size + var botRight = [x0 + xstep, y0 + ystep]; + + var width = botRight[0] - topLeft[0]; + var height = botRight[1] - topLeft[1]; + + // TODO: hack to avoid OOM, we would idealy compute the tiling + // pattern to be only as large as the acual size in device space + // This could be computed with .mozCurrentTransform, but still + // needs to be implemented + while (Math.abs(width) > 512 || Math.abs(height) > 512) { + width = 512; + height = 512; + } + + var tmpCanvas = new ScratchCanvas(width, height); + + // set the new canvas element context as the graphics context + var tmpCtx = tmpCanvas.getContext('2d'); + var graphics = new CanvasGraphics(tmpCtx, objs); + + switch (paintType) { + case PAINT_TYPE_COLORED: + tmpCtx.fillStyle = ctx.fillStyle; + tmpCtx.strokeStyle = ctx.strokeStyle; + break; + case PAINT_TYPE_UNCOLORED: + color = Util.makeCssRgb.apply(this, color); + tmpCtx.fillStyle = color; + tmpCtx.strokeStyle = color; + break; + default: + error('Unsupported paint type: ' + paintType); + } + + var scale = [width / xstep, height / ystep]; + this.scale = scale; + + // transform coordinates to pattern space + var tmpTranslate = [1, 0, 0, 1, -topLeft[0], -topLeft[1]]; + var tmpScale = [scale[0], 0, 0, scale[1], 0, 0]; + graphics.transform.apply(graphics, tmpScale); + graphics.transform.apply(graphics, tmpTranslate); + + if (bbox && isArray(bbox) && 4 == bbox.length) { + var bboxWidth = bbox[2] - bbox[0]; + var bboxHeight = bbox[3] - bbox[1]; + graphics.rectangle(bbox[0], bbox[1], bboxWidth, bboxHeight); + graphics.clip(); + graphics.endPath(); + } + + graphics.executeIRQueue(IRQueue); + + this.canvas = tmpCanvas; + } + + TilingPattern.getIR = function tiling_getIR(codeIR, dict, args) { + var matrix = dict.get('Matrix'); + var bbox = dict.get('BBox'); + var xstep = dict.get('XStep'); + var ystep = dict.get('YStep'); + var paintType = dict.get('PaintType'); + + return [ + 'TilingPattern', args, codeIR, matrix, bbox, xstep, ystep, paintType + ]; + } + + TilingPattern.prototype = { + getPattern: function tiling_getPattern() { + var matrix = this.matrix; + var curMatrix = this.curMatrix; + var ctx = this.ctx; + + if (curMatrix) + ctx.setTransform.apply(ctx, curMatrix); + + if (matrix) + ctx.transform.apply(ctx, matrix); + + var scale = this.scale; + ctx.scale(1 / scale[0], 1 / scale[1]); + + return ctx.createPattern(this.canvas, 'repeat'); + } + }; + + return TilingPattern; +})(); + diff --git a/src/pdf.js b/src/pdf.js new file mode 100644 index 000000000..34e163967 --- /dev/null +++ b/src/pdf.js @@ -0,0 +1,15 @@ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ + +var PDFJS = {}; + +(function pdfjsWrapper() { + + // Use strict in our context only - users might not want it + 'use strict'; + + // Files are inserted below - see Makefile + /* PDFJSSCRIPT_INCLUDE_ALL */ + +})(); + diff --git a/src/stream.js b/src/stream.js new file mode 100644 index 000000000..2b10e2fbd --- /dev/null +++ b/src/stream.js @@ -0,0 +1,2076 @@ +/* -*- 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 Stream = (function streamStream() { + 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); + }, + isStream: true + }; + + return constructor; +})(); + +var StringStream = (function stringStream() { + 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; + + return constructor; +})(); + +// super class for the decoding streams +var DecodeStream = (function decodeStream() { + 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 end, pos = this.pos; + + if (length) { + this.ensureBuffer(pos + length); + 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(); + + end = this.bufferLength; + + // checking if bufferLength is still 0 then + // the buffer has to be initialized + if (!end) + this.buffer = new Uint8Array(0); + } + + 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++]); + }, + makeSubStream: function decodestream_makeSubstream(start, length, dict) { + var end = start + length; + while (this.bufferLength <= end && !this.eof) + this.readBlock(); + return new Stream(this.buffer, start, length, dict); + }, + skip: function decodestream_skip(n) { + if (!n) + n = 1; + this.pos += n; + }, + reset: function decodestream_reset() { + this.pos = 0; + } + }; + + return constructor; +})(); + +var FakeStream = (function fakeStream() { + function constructor(stream) { + this.dict = stream.dict; + DecodeStream.call(this); + } + + constructor.prototype = Object.create(DecodeStream.prototype); + constructor.prototype.readBlock = function fakeStreamReadBlock() { + var bufferLength = this.bufferLength; + bufferLength += 1024; + var buffer = this.ensureBuffer(bufferLength); + this.bufferLength = bufferLength; + }; + + constructor.prototype.getBytes = function fakeStreamGetBytes(length) { + var end, pos = this.pos; + + if (length) { + this.ensureBuffer(pos + length); + end = pos + length; + + while (!this.eof && this.bufferLength < end) + this.readBlock(); + + var bufEnd = this.bufferLength; + if (end > bufEnd) + end = bufEnd; + } else { + this.eof = true; + end = this.bufferLength; + } + + this.pos = end; + return this.buffer.subarray(pos, end); + }; + + return constructor; +})(); + +var StreamsSequenceStream = (function streamSequenceStream() { + function constructor(streams) { + this.streams = streams; + DecodeStream.call(this); + } + + constructor.prototype = Object.create(DecodeStream.prototype); + + constructor.prototype.readBlock = function streamSequenceStreamReadBlock() { + var streams = this.streams; + if (streams.length == 0) { + this.eof = true; + return; + } + var stream = streams.shift(); + var chunk = stream.getBytes(); + var bufferLength = this.bufferLength; + var newLength = bufferLength + chunk.length; + var buffer = this.ensureBuffer(newLength); + buffer.set(chunk, bufferLength); + this.bufferLength = newLength; + }; + + return constructor; +})(); + +var FlateStream = (function flateStream() { + 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 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 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; + + this.dict = stream.dict; + var cmf = bytes[bytesPos++]; + var flg = bytes[bytesPos++]; + if (cmf == -1 || flg == -1) + error('Invalid header in flate stream: ' + cmf + ', ' + flg); + if ((cmf & 0x0f) != 0x08) + error('Unknown compression method in flate stream: ' + cmf + ', ' + flg); + if ((((cmf << 8) + flg) % 31) != 0) + error('Bad FCHECK in flate stream: ' + cmf + ', ' + flg); + if (flg & 0x20) + error('FDICT bit set in flate stream: ' + cmf + ', ' + flg); + + this.bytes = bytes; + this.bytesPos = bytesPos; + + this.codeSize = 0; + this.codeBuf = 0; + + DecodeStream.call(this); + } + + constructor.prototype = Object.create(DecodeStream.prototype); + + constructor.prototype.getBits = function flateStreamGetBits(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 flateStreamGetCode(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 flateStreamGenerateHuffmanTable(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]; + } + + // 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; + } + + // 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 flateStreamReadBlock() { + // 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 = new Uint8Array(codeLenCodeMap.length); + + for (var i = 0; i < numCodeLenCodes; ++i) + 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 Uint8Array(codes); + while (i < codes) { + var code = this.getCode(codeLenCodeTab); + if (code == 16) { + var bitsLength = 2, bitsOffset = 3, what = len; + } else if (code == 17) { + var bitsLength = 3, bitsOffset = 3, what = (len = 0); + } else if (code == 18) { + var bitsLength = 7, bitsOffset = 11, what = (len = 0); + } else { + codeLengths[i++] = len = code; + continue; + } + + var repeatLength = this.getBits(bitsLength) + bitsOffset; + while (repeatLength-- > 0) + codeLengths[i++] = what; + } + + litCodeTable = + this.generateHuffmanTable(codeLengths.subarray(0, numLitCodes)); + distCodeTable = + this.generateHuffmanTable(codeLengths.subarray(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; +})(); + +var PredictorStream = (function predictorStream() { + 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: ' + predictor); + + if (predictor === 2) + this.readBlock = this.readBlockTiff; + else + this.readBlock = this.readBlockPng; + + this.stream = stream; + this.dict = stream.dict; + + var colors = this.colors = params.get('Colors') || 1; + var bits = this.bits = params.get('BitsPerComponent') || 8; + var columns = this.columns = params.get('Columns') || 1; + + this.pixBytes = (colors * bits + 7) >> 3; + this.rowBytes = (columns * colors * bits + 7) >> 3; + + DecodeStream.call(this); + return this; + } + + constructor.prototype = Object.create(DecodeStream.prototype); + + constructor.prototype.readBlockTiff = + function predictorStreamReadBlockTiff() { + var rowBytes = this.rowBytes; + + var bufferLength = this.bufferLength; + var buffer = this.ensureBuffer(bufferLength + rowBytes); + var currentRow = buffer.subarray(bufferLength, bufferLength + rowBytes); + + var bits = this.bits; + var colors = this.colors; + + var rawBytes = this.stream.getBytes(rowBytes); + + var inbuf = 0, outbuf = 0; + var inbits = 0, outbits = 0; + + if (bits === 1) { + 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 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 predictorStreamReadBlockPng() { + var rowBytes = this.rowBytes; + var pixBytes = this.pixBytes; + + 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 = new Uint8Array(rowBytes); + + switch (predictor) { + case 0: + for (var i = 0; i < rowBytes; ++i) + currentRow[i] = rawBytes[i]; + 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) { + var up = prevRow[i]; + var c = rawBytes[i]; + currentRow[i] = up + c; + } + for (; i < rowBytes; ++i) { + var up = prevRow[i]; + var upLeft = prevRow[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: ' + predictor); + } + 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 jpegStream() { + function isAdobeImage(bytes) { + var maxBytesScanned = Math.max(bytes.length - 16, 1024); + // Looking for APP14, 'Adobe' + for (var i = 0; i < maxBytesScanned; ++i) { + if (bytes[i] == 0xFF && bytes[i + 1] == 0xEE && + bytes[i + 2] == 0x00 && bytes[i + 3] == 0x0E && + bytes[i + 4] == 0x41 && bytes[i + 5] == 0x64 && + bytes[i + 6] == 0x6F && bytes[i + 7] == 0x62 && + bytes[i + 8] == 0x65 && bytes[i + 9] == 0x00) + return true; + // scanning until frame tag + if (bytes[i] == 0xFF && bytes[i + 1] == 0xC0) + break; + } + return false; + } + + function fixAdobeImage(bytes) { + // Inserting 'EMBED' marker after JPEG signature + var embedMarker = new Uint8Array([0xFF, 0xEC, 0, 8, 0x45, 0x4D, 0x42, 0x45, + 0x44, 0]); + var newBytes = new Uint8Array(bytes.length + embedMarker.length); + newBytes.set(bytes, embedMarker.length); + // copy JPEG header + newBytes[0] = bytes[0]; + newBytes[1] = bytes[1]; + newBytes.set(embedMarker, 2); + return newBytes; + } + + function constructor(bytes, dict) { + // TODO: per poppler, some images may have 'junk' before that + // need to be removed + this.dict = dict; + + if (isAdobeImage(bytes)) + bytes = fixAdobeImage(bytes); + + this.src = bytesToString(bytes); + } + + constructor.prototype = { + getIR: function() { + return this.src; + }, + getChar: function jpegStreamGetChar() { + error('internal error: getChar is not valid on JpegStream'); + } + }; + + return constructor; +})(); + +var DecryptStream = (function decryptStream() { + 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 decryptStreamReadBlock() { + 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; + }; + + return constructor; +})(); + +var Ascii85Stream = (function ascii85Stream() { + 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 ascii85StreamReadBlock() { + 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; + } + + var bufferLength = this.bufferLength, buffer; + + // special code for z + if (c == zCode) { + 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(); + + input[i] = c; + + if (!c || c == tildaCode) + break; + } + buffer = this.ensureBuffer(bufferLength + i - 1); + this.bufferLength += i - 1; + + // 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); + + for (var i = 3; i >= 0; --i) { + buffer[bufferLength + i] = t & 0xFF; + t >>= 8; + } + } + }; + + return constructor; +})(); + +var AsciiHexStream = (function asciiHexStream() { + 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 + }; + + constructor.prototype = Object.create(DecodeStream.prototype); + + constructor.prototype.readBlock = function asciiHexStreamReadBlock() { + 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 { + // EOD marker at an odd number, behave as if a 0 followed the last + // digit. + if (bytes[i] !== gtCode) { + buffer[bufferLength++] = c * 16; + } + } + } + + this.bufferLength = bufferLength; + this.eof = true; + }; + + return constructor; +})(); + +var CCITTFaxStream = (function ccittFaxStream() { + + 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 = [ + [-1, -1], [-1, -1], // 000000x + [7, twoDimVertL3], // 0000010 + [7, twoDimVertR3], // 0000011 + [6, twoDimVertL2], [6, twoDimVertL2], // 000010x + [6, twoDimVertR2], [6, twoDimVertR2], // 000011x + [4, twoDimPass], [4, twoDimPass], // 0001xxx + [4, twoDimPass], [4, twoDimPass], + [4, twoDimPass], [4, twoDimPass], + [4, twoDimPass], [4, twoDimPass], + [3, twoDimHoriz], [3, twoDimHoriz], // 001xxxx + [3, twoDimHoriz], [3, twoDimHoriz], + [3, twoDimHoriz], [3, twoDimHoriz], + [3, twoDimHoriz], [3, twoDimHoriz], + [3, twoDimHoriz], [3, twoDimHoriz], + [3, twoDimHoriz], [3, twoDimHoriz], + [3, twoDimHoriz], [3, twoDimHoriz], + [3, twoDimHoriz], [3, twoDimHoriz], + [3, twoDimVertL1], [3, twoDimVertL1], // 010xxxx + [3, twoDimVertL1], [3, twoDimVertL1], + [3, twoDimVertL1], [3, twoDimVertL1], + [3, twoDimVertL1], [3, twoDimVertL1], + [3, twoDimVertL1], [3, twoDimVertL1], + [3, twoDimVertL1], [3, twoDimVertL1], + [3, twoDimVertL1], [3, twoDimVertL1], + [3, twoDimVertL1], [3, twoDimVertL1], + [3, twoDimVertR1], [3, twoDimVertR1], // 011xxxx + [3, twoDimVertR1], [3, twoDimVertR1], + [3, twoDimVertR1], [3, twoDimVertR1], + [3, twoDimVertR1], [3, twoDimVertR1], + [3, twoDimVertR1], [3, twoDimVertR1], + [3, twoDimVertR1], [3, twoDimVertR1], + [3, twoDimVertR1], [3, twoDimVertR1], + [3, twoDimVertR1], [3, twoDimVertR1], + [1, twoDimVert0], [1, twoDimVert0], // 1xxxxxx + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [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 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 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 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] + ]; + + function constructor(str, params) { + this.str = str; + this.dict = str.dict; + + params = params || new 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; + + 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 = 0; + 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 ccittFaxStreamReadBlock() { + while (!this.eof) { + var c = this.lookChar(); + this.buf = EOF; + this.ensureBuffer(this.bufferLength + 1); + this.buffer[this.bufferLength++] = c; + } + }; + + constructor.prototype.addPixels = + function ccittFaxStreamAddPixels(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 ccittFaxStreamAddPixelsNeg(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 ccittFaxStreamLookChar() { + if (this.buf != EOF) + return this.buf; + + var refLine = this.refLine; + var codingLine = this.codingLine; + var columns = this.columns; + + var refPos, blackPixels, bits; + + if (this.outputBits == 0) { + if (this.eof) + return null; + + this.err = false; + + var code1, code2, code3; + 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) { + code1 = this.getTwoDimCode(); + switch (code1) { + case twoDimPass: + this.addPixels(refLine[refPos + 1], blackPixels); + if (refLine[refPos + 1] < columns) + refPos += 2; + break; + case twoDimHoriz: + code1 = code2 = 0; + if (blackPixels) { + do { + code1 += (code3 = this.getBlackCode()); + } while (code3 >= 64); + do { + code2 += (code3 = this.getWhiteCode()); + } while (code3 >= 64); + } else { + 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; + } + } + } 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); + gotEOL = true; + } else if (code1 == EOF) { + this.eof = true; + } + } + + 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); + } + if (this.encoding >= 0) { + for (var i = 0; i < 4; ++i) { + code1 = this.lookBits(12); + if (code1 != 1) + warn('bad rtc code: ' + code1); + this.eatBits(12); + if (this.encoding > 0) { + this.lookBits(1); + this.eatBits(1); + } + } + } + this.eof = true; + } + } else if (this.err && this.eoline) { + while (true) { + code1 = this.lookBits(13); + if (code1 == EOF) { + this.eof = true; + return null; + } + 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++; + } + + 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; + }; + + // This functions returns the code found from the table. + // The start and end parameters set the boundaries for searching the table. + // The limit parameter is optional. Function returns an array with three + // values. The first array element indicates whether a valid code is being + // returned. The second array element is the actual code. The third array + // element indicates whether EOF was reached. + var findTableCode = function ccittFaxStreamFindTableCode(start, end, table, + limit) { + var limitValue = limit || 0; + + for (var i = start; i <= end; ++i) { + var code = this.lookBits(i); + if (code == EOF) + return [true, 1, false]; + if (i < end) + code <<= end - i; + if (!limitValue || code >= limitValue) { + var p = table[code - limitValue]; + if (p[0] == i) { + this.eatBits(i); + return [true, p[1], true]; + } + } + } + return [false, 0, false]; + }; + + constructor.prototype.getTwoDimCode = function ccittFaxStreamGetTwoDimCode() { + 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 { + var result = findTableCode(1, 7, twoDimTable); + if (result[0] && result[2]) + return result[1]; + } + warn('Bad two dim code'); + return EOF; + }; + + constructor.prototype.getWhiteCode = function ccittFaxStreamGetWhiteCode() { + 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 { + var result = findTableCode(1, 9, whiteTable2); + if (result[0]) + return result[1]; + + result = findTableCode(11, 12, whiteTable1); + if (result[0]) + return result[1]; + } + warn('bad white code'); + this.eatBits(1); + return 1; + }; + + constructor.prototype.getBlackCode = function ccittFaxStreamGetBlackCode() { + var code, p; + 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 { + var result = findTableCode(2, 6, blackTable3); + if (result[0]) + return result[1]; + + result = findTableCode(7, 12, blackTable2, 64); + if (result[0]) + return result[1]; + + result = findTableCode(10, 13, blackTable1); + if (result[0]) + return result[1]; + } + warn('bad black code'); + this.eatBits(1); + return 1; + }; + + constructor.prototype.lookBits = function ccittFaxStreamLookBits(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 ccittFaxStreamEatBits(n) { + if ((this.inputBits -= n) < 0) + this.inputBits = 0; + }; + + return constructor; +})(); + +var LZWStream = (function lzwStream() { + function constructor(str, earlyChange) { + this.str = str; + this.dict = str.dict; + this.cachedData = 0; + this.bitsCached = 0; + + var maxLzwDictionarySize = 4096; + var lzwState = { + earlyChange: earlyChange, + codeLength: 9, + nextCode: 258, + dictionaryValues: new Uint8Array(maxLzwDictionarySize), + dictionaryLengths: new Uint16Array(maxLzwDictionarySize), + dictionaryPrevCodes: new Uint16Array(maxLzwDictionarySize), + currentSequence: new Uint8Array(maxLzwDictionarySize), + currentSequenceLength: 0 + }; + for (var i = 0; i < 256; ++i) { + lzwState.dictionaryValues[i] = i; + lzwState.dictionaryLengths[i] = 1; + } + this.lzwState = lzwState; + + DecodeStream.call(this); + } + + constructor.prototype = Object.create(DecodeStream.prototype); + + constructor.prototype.readBits = function lzwStreamReadBits(n) { + var bitsCached = this.bitsCached; + var cachedData = this.cachedData; + while (bitsCached < n) { + var c = this.str.getByte(); + if (c == null) { + this.eof = true; + return null; + } + cachedData = (cachedData << 8) | c; + bitsCached += 8; + } + this.bitsCached = (bitsCached -= n); + this.cachedData = cachedData; + this.lastCode = null; + return (cachedData >>> bitsCached) & ((1 << n) - 1); + }; + + constructor.prototype.readBlock = function lzwStreamReadBlock() { + var blockSize = 512; + var estimatedDecodedSize = blockSize * 2, decodedSizeDelta = blockSize; + var i, j, q; + + var lzwState = this.lzwState; + if (!lzwState) + return; // eof was found + + var earlyChange = lzwState.earlyChange; + var nextCode = lzwState.nextCode; + var dictionaryValues = lzwState.dictionaryValues; + var dictionaryLengths = lzwState.dictionaryLengths; + var dictionaryPrevCodes = lzwState.dictionaryPrevCodes; + var codeLength = lzwState.codeLength; + var prevCode = lzwState.prevCode; + var currentSequence = lzwState.currentSequence; + var currentSequenceLength = lzwState.currentSequenceLength; + + var decodedLength = 0; + var currentBufferLength = this.bufferLength; + var buffer = this.ensureBuffer(this.bufferLength + estimatedDecodedSize); + + for (i = 0; i < blockSize; i++) { + var code = this.readBits(codeLength); + var hasPrev = currentSequenceLength > 0; + if (code < 256) { + currentSequence[0] = code; + currentSequenceLength = 1; + } else if (code >= 258) { + if (code < nextCode) { + currentSequenceLength = dictionaryLengths[code]; + for (j = currentSequenceLength - 1, q = code; j >= 0; j--) { + currentSequence[j] = dictionaryValues[q]; + q = dictionaryPrevCodes[q]; + } + } else { + currentSequence[currentSequenceLength++] = currentSequence[0]; + } + } else if (code == 256) { + codeLength = 9; + nextCode = 258; + currentSequenceLength = 0; + continue; + } else { + this.eof = true; + delete this.lzwState; + break; + } + + if (hasPrev) { + dictionaryPrevCodes[nextCode] = prevCode; + dictionaryLengths[nextCode] = dictionaryLengths[prevCode] + 1; + dictionaryValues[nextCode] = currentSequence[0]; + nextCode++; + codeLength = (nextCode + earlyChange) & (nextCode + earlyChange - 1) ? + codeLength : Math.min(Math.log(nextCode + earlyChange) / + 0.6931471805599453 + 1, 12) | 0; + } + prevCode = code; + + decodedLength += currentSequenceLength; + if (estimatedDecodedSize < decodedLength) { + do { + estimatedDecodedSize += decodedSizeDelta; + } while (estimatedDecodedSize < decodedLength); + buffer = this.ensureBuffer(this.bufferLength + estimatedDecodedSize); + } + for (j = 0; j < currentSequenceLength; j++) + buffer[currentBufferLength++] = currentSequence[j]; + } + lzwState.nextCode = nextCode; + lzwState.codeLength = codeLength; + lzwState.prevCode = prevCode; + lzwState.currentSequenceLength = currentSequenceLength; + + this.bufferLength = currentBufferLength; + }; + + return constructor; +})(); + diff --git a/src/util.js b/src/util.js new file mode 100644 index 000000000..953732057 --- /dev/null +++ b/src/util.js @@ -0,0 +1,286 @@ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ + +'use strict'; + +function log(msg) { + if (console && console.log) + console.log(msg); + else if (print) + print(msg); +} + +function warn(msg) { + if (verbosity >= WARNINGS) + log('Warning: ' + msg); +} + +function backtrace() { + try { + throw new Error(); + } catch (e) { + return e.stack ? e.stack.split('\n').slice(2).join('\n') : ""; + } +} + +function error(msg) { + log("Error: " + msg); + log(backtrace()); + throw new Error(msg); +} + +function TODO(what) { + if (verbosity >= TODOS) + log('TODO: ' + what); +} + +function malformed(msg) { + error('Malformed PDF: ' + msg); +} + +function assert(cond, 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); +} + +function shadow(obj, prop, value) { + 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; +} + +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 IDENTITY_MATRIX = [1, 0, 0, 1, 0, 0]; + +var Util = (function utilUtil() { + function constructor() {} + constructor.makeCssRgb = function makergb(r, g, b) { + var ri = (255 * r) | 0, gi = (255 * g) | 0, bi = (255 * b) | 0; + return 'rgb(' + ri + ',' + gi + ',' + bi + ')'; + }; + constructor.makeCssCmyk = function makecmyk(c, m, y, k) { + c = (new DeviceCmykCS()).getRgb([c, m, y, k]); + var ri = (255 * c[0]) | 0, gi = (255 * c[1]) | 0, bi = (255 * c[2]) | 0; + return 'rgb(' + ri + ',' + gi + ',' + bi + ')'; + }; + constructor.applyTransform = function apply(p, m) { + var xt = p[0] * m[0] + p[1] * m[2] + m[4]; + var yt = p[0] * m[1] + p[1] * m[3] + m[5]; + return [xt, yt]; + }; + + return constructor; +})(); + +var PDFStringTranslateTable = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0x2D8, 0x2C7, 0x2C6, 0x2D9, 0x2DD, 0x2DB, 0x2DA, 0x2DC, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x2022, 0x2020, 0x2021, 0x2026, 0x2014, + 0x2013, 0x192, 0x2044, 0x2039, 0x203A, 0x2212, 0x2030, 0x201E, 0x201C, + 0x201D, 0x2018, 0x2019, 0x201A, 0x2122, 0xFB01, 0xFB02, 0x141, 0x152, 0x160, + 0x178, 0x17D, 0x131, 0x142, 0x153, 0x161, 0x17E, 0, 0x20AC +]; + +function stringToPDFString(str) { + var i, n = str.length, str2 = ''; + if (str[0] === '\xFE' && str[1] === '\xFF') { + // UTF16BE BOM + for (i = 2; i < n; i += 2) + str2 += String.fromCharCode( + (str.charCodeAt(i) << 8) | str.charCodeAt(i + 1)); + } else { + for (i = 0; i < n; ++i) { + var code = PDFStringTranslateTable[str.charCodeAt(i)]; + str2 += code ? String.fromCharCode(code) : str.charAt(i); + } + } + return str2; +} + +function isBool(v) { + return typeof v == 'boolean'; +} + +function isInt(v) { + return typeof v == 'number' && ((v | 0) == v); +} + +function isNum(v) { + return typeof v == 'number'; +} + +function isString(v) { + return typeof v == 'string'; +} + +function isNull(v) { + return v === null; +} + +function isName(v) { + return v instanceof Name; +} + +function isCmd(v, cmd) { + return v instanceof Cmd && (!cmd || v.cmd == cmd); +} + +function isDict(v, type) { + return v instanceof Dict && (!type || v.get('Type').name == type); +} + +function isArray(v) { + return v instanceof Array; +} + +function isStream(v) { + return typeof v == 'object' && v != null && ('getChar' in v); +} + +function isArrayBuffer(v) { + return typeof v == 'object' && v != null && ('byteLength' in v); +} + +function isRef(v) { + 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'); +} + +/** + * 'Promise' object. + * Each object that is stored in PDFObjects is based on a Promise object that + * contains the status of the object and the data. There migth be situations, + * where a function want to use the value of an object, but it isn't ready at + * that time. To get a notification, once the object is ready to be used, s.o. + * can add a callback using the `then` method on the promise that then calls + * the callback once the object gets resolved. + * A promise can get resolved only once and only once the data of the promise + * can be set. If any of these happens twice or the data is required before + * it was set, an exception is throw. + */ +var Promise = (function() { + var EMPTY_PROMISE = {}; + + /** + * If `data` is passed in this constructor, the promise is created resolved. + * If there isn't data, it isn't resolved at the beginning. + */ + function Promise(name, data) { + this.name = name; + // If you build a promise and pass in some data it's already resolved. + if (data != null) { + this.isResolved = true; + this._data = data; + this.hasData = true; + } else { + this.isResolved = false; + this._data = EMPTY_PROMISE; + } + this.callbacks = []; + }; + + Promise.prototype = { + hasData: false, + + set data(value) { + if (value === undefined) { + return; + } + if (this._data !== EMPTY_PROMISE) { + throw 'Promise ' + this.name + + ': Cannot set the data of a promise twice'; + } + this._data = value; + this.hasData = true; + + if (this.onDataCallback) { + this.onDataCallback(value); + } + }, + + get data() { + if (this._data === EMPTY_PROMISE) { + throw 'Promise ' + this.name + ': Cannot get data that isn\'t set'; + } + return this._data; + }, + + onData: function(callback) { + if (this._data !== EMPTY_PROMISE) { + callback(this._data); + } else { + this.onDataCallback = callback; + } + }, + + resolve: function(data) { + if (this.isResolved) { + throw 'A Promise can be resolved only once ' + this.name; + } + + this.isResolved = true; + this.data = data; + var callbacks = this.callbacks; + + for (var i = 0; i < callbacks.length; i++) { + callbacks[i].call(null, data); + } + }, + + then: function(callback) { + if (!callback) { + throw 'Requiring callback' + this.name; + } + + // If the promise is already resolved, call the callback directly. + if (this.isResolved) { + var data = this.data; + callback.call(null, data); + } else { + this.callbacks.push(callback); + } + } + }; + + return Promise; +})(); + diff --git a/utils/cffStandardStrings.js b/src/utils/cffStandardStrings.js similarity index 99% rename from utils/cffStandardStrings.js rename to src/utils/cffStandardStrings.js index 743c60b25..97d7e5a25 100644 --- a/utils/cffStandardStrings.js +++ b/src/utils/cffStandardStrings.js @@ -1,8 +1,6 @@ /* -*- 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 CFFEncodingMap = { '0': '-reserved-', '1': 'hstem', diff --git a/utils/fonts_utils.js b/src/utils/fonts_utils.js similarity index 99% rename from utils/fonts_utils.js rename to src/utils/fonts_utils.js index 654e98db1..2a1f0ea72 100644 --- a/utils/fonts_utils.js +++ b/src/utils/fonts_utils.js @@ -1,8 +1,6 @@ /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ -'use strict'; - /* * The Type2 reader code below is only used for debugging purpose since Type2 * is only a CharString format and is never used directly as a Font file. diff --git a/worker/processor_handler.js b/src/worker.js similarity index 57% rename from worker/processor_handler.js rename to src/worker.js index 6844168e6..a83f31668 100644 --- a/worker/processor_handler.js +++ b/src/worker.js @@ -1,8 +1,48 @@ -/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- / +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ 'use strict'; +function MessageHandler(name, comObj) { + this.name = name; + this.comObj = comObj; + var ah = this.actionHandler = {}; + + ah['console_log'] = [function(data) { + console.log.apply(console, data); + }]; + ah['console_error'] = [function(data) { + console.error.apply(console, data); + }]; + + comObj.onmessage = function(event) { + var data = event.data; + if (data.action in ah) { + var action = ah[data.action]; + action[0].call(action[1], data.data); + } else { + throw 'Unkown action from worker: ' + data.action; + } + }; +} + +MessageHandler.prototype = { + on: function(actionName, handler, scope) { + var ah = this.actionHandler; + if (ah[actionName]) { + throw "There is already an actionName called '" + actionName + "'"; + } + ah[actionName] = [handler, scope]; + }, + + send: function(actionName, data) { + this.comObj.postMessage({ + action: actionName, + data: data + }); + } +}; + var WorkerProcessorHandler = { setup: function(handler) { var pdfDoc = null; @@ -82,11 +122,11 @@ var WorkerProcessorHandler = { var obj = new Font(font.name, font.file, font.properties); var str = ''; - var data = obj.data; - if (data) { - var length = data.length; - for (var j = 0; j < length; j++) - str += String.fromCharCode(data[j]); + var objData = obj.data; + if (objData) { + var length = objData.length; + for (var j = 0; j < length; ++j) + str += String.fromCharCode(objData[j]); } obj.str = str; @@ -99,3 +139,45 @@ var WorkerProcessorHandler = { }); } }; + +var consoleTimer = {}; + +var workerConsole = { + log: function log() { + var args = Array.prototype.slice.call(arguments); + postMessage({ + action: 'console_log', + data: args + }); + }, + + error: function error() { + var args = Array.prototype.slice.call(arguments); + postMessage({ + action: 'console_error', + data: args + }); + }, + + time: function(name) { + consoleTimer[name] = Date.now(); + }, + + timeEnd: function(name) { + var time = consoleTimer[name]; + if (time == null) { + throw 'Unkown timer name ' + name; + } + this.log('Timer:', name, Date.now() - time); + } +}; + +// Worker thread? +if (typeof window === 'undefined') { + globalScope.console = workerConsole; + + // Listen for messages from the main thread. + var handler = new MessageHandler('worker_processor', globalScope); + WorkerProcessorHandler.setup(handler); +} + diff --git a/src/worker_loader.js b/src/worker_loader.js new file mode 100644 index 000000000..fb37ca9c4 --- /dev/null +++ b/src/worker_loader.js @@ -0,0 +1,24 @@ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ + +'use strict'; + +importScripts('../src/core.js'); +importScripts('../src/util.js'); +importScripts('../src/canvas.js'); +importScripts('../src/obj.js'); +importScripts('../src/function.js'); +importScripts('../src/charsets.js'); +importScripts('../src/cidmaps.js'); +importScripts('../src/colorspace.js'); +importScripts('../src/crypto.js'); +importScripts('../src/evaluator.js'); +importScripts('../src/fonts.js'); +importScripts('../src/glyphlist.js'); +importScripts('../src/image.js'); +importScripts('../src/metrics.js'); +importScripts('../src/parser.js'); +importScripts('../src/pattern.js'); +importScripts('../src/stream.js'); +importScripts('../src/worker.js'); + diff --git a/test/driver.js b/test/driver.js index 5418b3eb9..16375c30b 100644 --- a/test/driver.js +++ b/test/driver.js @@ -82,7 +82,7 @@ function nextTask() { getPdf(task.file, function nextTaskGetPdf(data) { var failure; try { - task.pdfDoc = new PDFDoc(data); + task.pdfDoc = new PDFJS.PDFDoc(data); } catch (e) { failure = 'load PDF doc : ' + e.toString(); } diff --git a/test/test.py b/test/test.py index 46106965b..66e1acd31 100644 --- a/test/test.py +++ b/test/test.py @@ -318,7 +318,7 @@ def downloadLinkedPDFs(manifestList): 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') + assert not ANAL or os.path.isfile('../src/pdf.js') and os.path.isdir('../.git') if options.masterMode and os.path.isdir(TMPDIR): print 'Temporary snapshot dir tmp/ is still around.' diff --git a/test/test_slave.html b/test/test_slave.html index 124537a88..7ac886769 100644 --- a/test/test_slave.html +++ b/test/test_slave.html @@ -3,16 +3,25 @@ pdf.js test slave - - - - - - - + + + + + + + + + + + + + + + + + + - - diff --git a/web/.gitignore b/web/.gitignore new file mode 100644 index 000000000..2c0f5df71 --- /dev/null +++ b/web/.gitignore @@ -0,0 +1 @@ +viewer-production.html diff --git a/web/viewer-snippet.html b/web/viewer-snippet.html new file mode 100644 index 000000000..c99897a44 --- /dev/null +++ b/web/viewer-snippet.html @@ -0,0 +1,2 @@ + + diff --git a/web/viewer.html b/web/viewer.html index f1f5efc78..f7a5378ed 100644 --- a/web/viewer.html +++ b/web/viewer.html @@ -3,18 +3,30 @@ Simple pdf.js page viewer + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - diff --git a/web/viewer.js b/web/viewer.js index 0b68f1137..1ab2c555c 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -122,7 +122,7 @@ var PDFView = { document.title = this.url = url; var self = this; - getPdf( + PDFJS.getPdf( { url: url, progress: function getPdfProgress(evt) { @@ -209,7 +209,7 @@ var PDFView = { while (container.hasChildNodes()) container.removeChild(container.lastChild); - var pdf = new PDFDoc(data); + var pdf = new PDFJS.PDFDoc(data); var pagesCount = pdf.numPages; document.getElementById('numPages').innerHTML = pagesCount; document.getElementById('pageNumber').max = pagesCount; diff --git a/worker/console.js b/worker/console.js deleted file mode 100644 index ffcaaf9a2..000000000 --- a/worker/console.js +++ /dev/null @@ -1,36 +0,0 @@ -/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ - -'use strict'; - -var consoleTimer = {}; -var console = { - log: function log() { - var args = Array.prototype.slice.call(arguments); - postMessage({ - action: 'console_log', - data: args - }); - }, - - error: function error() { - var args = Array.prototype.slice.call(arguments); - postMessage({ - action: 'console_error', - data: args - }); - }, - - time: function(name) { - consoleTimer[name] = Date.now(); - }, - - timeEnd: function(name) { - var time = consoleTimer[name]; - if (time == null) { - throw 'Unkown timer name ' + name; - } - this.log('Timer:', name, Date.now() - time); - } -}; - diff --git a/worker/message_handler.js b/worker/message_handler.js deleted file mode 100644 index 70a38e474..000000000 --- a/worker/message_handler.js +++ /dev/null @@ -1,46 +0,0 @@ -/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- / -/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ - -'use strict'; - - -function MessageHandler(name, comObj) { - this.name = name; - this.comObj = comObj; - var ah = this.actionHandler = {}; - - ah['console_log'] = [function(data) { - console.log.apply(console, data); - }]; - ah['console_error'] = [function(data) { - console.error.apply(console, data); - }]; - - comObj.onmessage = function(event) { - var data = event.data; - if (data.action in ah) { - var action = ah[data.action]; - action[0].call(action[1], data.data); - } else { - throw 'Unkown action from worker: ' + data.action; - } - }; -} - -MessageHandler.prototype = { - on: function(actionName, handler, scope) { - var ah = this.actionHandler; - if (ah[actionName]) { - throw "There is already an actionName called '" + actionName + "'"; - } - ah[actionName] = [handler, scope]; - }, - - send: function(actionName, data) { - this.comObj.postMessage({ - action: actionName, - data: data - }); - } -}; - diff --git a/worker/pdf_worker_loader.js b/worker/pdf_worker_loader.js deleted file mode 100644 index c67725317..000000000 --- a/worker/pdf_worker_loader.js +++ /dev/null @@ -1,19 +0,0 @@ -/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- / -/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ - -'use strict'; - -importScripts('console.js'); -importScripts('message_handler.js'); -importScripts('../pdf.js'); -importScripts('../fonts.js'); -importScripts('../crypto.js'); -importScripts('../glyphlist.js'); -importScripts('../metrics.js'); -importScripts('processor_handler.js'); - -// Listen for messages from the main thread. -var pdfDoc = null; - -var handler = new MessageHandler('worker_processor', this); -WorkerProcessorHandler.setup(handler);