Changeset 80718 in spip-zone for _plugins_/pdfjs


Ignore:
Timestamp:
Feb 15, 2014, 11:40:29 AM (5 years ago)
Author:
joseph@…
Message:

Mise à jour lib pdf.js

Location:
_plugins_/pdfjs
Files:
33 added
1 deleted
63 edited

Legend:

Unmodified
Added
Removed
  • _plugins_/pdfjs/lib/pdfjs/build/pdf.js

    r73616 r80718  
    1616 */
    1717
    18 var PDFJS = {};
    19 PDFJS.version = '0.8.243';
    20 PDFJS.build = 'a2678c5';
     18// Initializing PDFJS global object (if still undefined)
     19if (typeof PDFJS === 'undefined') {
     20  (typeof window !== 'undefined' ? window : this).PDFJS = {};
     21}
     22
     23PDFJS.version = '0.8.1017';
     24PDFJS.build = '35f5a1e';
    2125
    2226(function pdfjsWrapper() {
     
    4044 * limitations under the License.
    4145 */
    42 
    43 // NOTE: Be careful what goes in this file, as it is also used from the context
    44 // of the addon. So using warn/error in here will break the addon.
     46/* globals Cmd, ColorSpace, Dict, MozBlobBuilder, Name, PDFJS, Ref, URL,
     47           Promise */
    4548
    4649'use strict';
    4750
    48 
    49 
    50 var NetworkManager = (function NetworkManagerClosure() {
    51 
    52   var OK_RESPONSE = 200;
    53   var PARTIAL_CONTENT_RESPONSE = 206;
    54 
    55   function NetworkManager(url, args) {
    56     this.url = url;
    57     args = args || {};
    58     this.httpHeaders = args.httpHeaders || {};
    59     this.getXhr = args.getXhr ||
    60       function NetworkManager_getXhr() {
    61         return new XMLHttpRequest();
    62       };
    63 
    64     this.currXhrId = 0;
    65     this.pendingRequests = {};
    66     this.loadedRequests = {};
    67   }
    68 
    69   function getArrayBuffer(xhr) {
    70     var data = (xhr.mozResponseArrayBuffer || xhr.mozResponse ||
    71                 xhr.responseArrayBuffer || xhr.response);
    72     if (typeof data !== 'string') {
    73       return data;
    74     }
    75     var length = data.length;
    76     var buffer = new Uint8Array(length);
    77     for (var i = 0; i < length; i++) {
    78       buffer[i] = data.charCodeAt(i) & 0xFF;
    79     }
    80     return buffer;
    81   }
    82 
    83   NetworkManager.prototype = {
    84     requestRange: function NetworkManager_requestRange(begin, end, listeners) {
    85       var args = {
    86         begin: begin,
    87         end: end
    88       };
    89       for (var prop in listeners) {
    90         args[prop] = listeners[prop];
    91       }
    92       return this.request(args);
    93     },
    94 
    95     requestFull: function NetworkManager_requestRange(listeners) {
    96       return this.request(listeners);
    97     },
    98 
    99     request: function NetworkManager_requestRange(args) {
    100       var xhr = this.getXhr();
    101       var xhrId = this.currXhrId++;
    102       var pendingRequest = this.pendingRequests[xhrId] = {
    103         xhr: xhr
    104       };
    105 
    106       xhr.open('GET', this.url);
    107       for (var property in this.httpHeaders) {
    108         var value = this.httpHeaders[property];
    109         if (typeof value === 'undefined') {
    110           continue;
    111         }
    112         xhr.setRequestHeader(property, value);
    113       }
    114       if ('begin' in args && 'end' in args) {
    115         var rangeStr = args.begin + '-' + (args.end - 1);
    116         xhr.setRequestHeader('Range', 'bytes=' + rangeStr);
    117         pendingRequest.expectedStatus = 206;
    118       } else {
    119         pendingRequest.expectedStatus = 200;
    120       }
    121 
    122       xhr.mozResponseType = xhr.responseType = 'arraybuffer';
    123 
    124       if (args.onProgress) {
    125         xhr.onprogress = args.onProgress;
    126       }
    127       if (args.onError) {
    128         xhr.onerror = function(evt) {
    129           args.onError(xhr.status);
    130         };
    131       }
    132       xhr.onreadystatechange = this.onStateChange.bind(this, xhrId);
    133 
    134       pendingRequest.onHeadersReceived = args.onHeadersReceived;
    135       pendingRequest.onDone = args.onDone;
    136       pendingRequest.onError = args.onError;
    137 
    138       xhr.send(null);
    139 
    140       return xhrId;
    141     },
    142 
    143     onStateChange: function NetworkManager_onStateChange(xhrId, evt) {
    144       var pendingRequest = this.pendingRequests[xhrId];
    145       if (!pendingRequest) {
    146         // Maybe abortRequest was called...
    147         return;
    148       }
    149 
    150       var xhr = pendingRequest.xhr;
    151       if (xhr.readyState >= 2 && pendingRequest.onHeadersReceived) {
    152         pendingRequest.onHeadersReceived();
    153         delete pendingRequest.onHeadersReceived;
    154       }
    155 
    156       if (xhr.readyState !== 4) {
    157         return;
    158       }
    159 
    160       if (!(xhrId in this.pendingRequests)) {
    161         // The XHR request might have been aborted in onHeadersReceived()
    162         // callback, in which case we should abort request
    163         return;
    164       }
    165 
    166       delete this.pendingRequests[xhrId];
    167 
    168       // success status == 0 can be on ftp, file and other protocols
    169       if (xhr.status === 0 && /^https?:/i.test(this.url)) {
    170         if (pendingRequest.onError) {
    171           pendingRequest.onError(xhr.status);
    172         }
    173         return;
    174       }
    175       var xhrStatus = xhr.status || OK_RESPONSE;
    176 
    177       // From http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35.2:
    178       // "A server MAY ignore the Range header". This means it's possible to
    179       // get a 200 rather than a 206 response from a range request.
    180       var ok_response_on_range_request =
    181           xhrStatus === OK_RESPONSE &&
    182           pendingRequest.expectedStatus === PARTIAL_CONTENT_RESPONSE;
    183 
    184       if (!ok_response_on_range_request &&
    185           xhrStatus !== pendingRequest.expectedStatus) {
    186         if (pendingRequest.onError) {
    187           pendingRequest.onError(xhr.status);
    188         }
    189         return;
    190       }
    191 
    192       this.loadedRequests[xhrId] = true;
    193 
    194       var chunk = getArrayBuffer(xhr);
    195       if (xhrStatus === PARTIAL_CONTENT_RESPONSE) {
    196         var rangeHeader = xhr.getResponseHeader('Content-Range');
    197         var matches = /bytes (\d+)-(\d+)\/(\d+)/.exec(rangeHeader);
    198         var begin = parseInt(matches[1], 10);
    199         pendingRequest.onDone({
    200           begin: begin,
    201           chunk: chunk
    202         });
    203       } else {
    204         pendingRequest.onDone({
    205           begin: 0,
    206           chunk: chunk
    207         });
    208       }
    209     },
    210 
    211     hasPendingRequests: function NetworkManager_hasPendingRequests() {
    212       for (var xhrId in this.pendingRequests) {
    213         return true;
    214       }
    215       return false;
    216     },
    217 
    218     getRequestXhr: function NetworkManager_getXhr(xhrId) {
    219       return this.pendingRequests[xhrId].xhr;
    220     },
    221 
    222     isPendingRequest: function NetworkManager_isPendingRequest(xhrId) {
    223       return xhrId in this.pendingRequests;
    224     },
    225 
    226     isLoadedRequest: function NetworkManager_isLoadedRequest(xhrId) {
    227       return xhrId in this.loadedRequests;
    228     },
    229 
    230     abortAllRequests: function NetworkManager_abortAllRequests() {
    231       for (var xhrId in this.pendingRequests) {
    232         this.abortRequest(xhrId | 0);
    233       }
    234     },
    235 
    236     abortRequest: function NetworkManager_abortRequest(xhrId) {
    237       var xhr = this.pendingRequests[xhrId].xhr;
    238       delete this.pendingRequests[xhrId];
    239       xhr.abort();
    240     }
    241   };
    242 
    243   return NetworkManager;
    244 })();
    245 
    246 
    247 
    248 var ChunkedStream = (function ChunkedStreamClosure() {
    249   function ChunkedStream(length, chunkSize, manager) {
    250     this.bytes = new Uint8Array(length);
    251     this.start = 0;
    252     this.pos = 0;
    253     this.end = length;
    254     this.chunkSize = chunkSize;
    255     this.loadedChunks = [];
    256     this.numChunksLoaded = 0;
    257     this.numChunks = Math.ceil(length / chunkSize);
    258     this.manager = manager;
    259   }
    260 
    261   // required methods for a stream. if a particular stream does not
    262   // implement these, an error should be thrown
    263   ChunkedStream.prototype = {
    264 
    265     getMissingChunks: function ChunkedStream_getMissingChunks() {
    266       var chunks = [];
    267       for (var chunk = 0, n = this.numChunks; chunk < n; ++chunk) {
    268         if (!(chunk in this.loadedChunks)) {
    269           chunks.push(chunk);
    270         }
    271       }
    272       return chunks;
    273     },
    274 
    275     allChunksLoaded: function ChunkedStream_allChunksLoaded() {
    276       return this.numChunksLoaded === this.numChunks;
    277     },
    278 
    279     onReceiveData: function(begin, chunk) {
    280       var end = begin + chunk.byteLength;
    281 
    282       assert(begin % this.chunkSize === 0, 'Bad begin offset: ' + begin);
    283       // Using this.length is inaccurate here since this.start can be moved
    284       // See ChunkedStream.moveStart()
    285       var length = this.bytes.length;
    286       assert(end % this.chunkSize === 0 || end === length,
    287         'Bad end offset: ' + end);
    288 
    289       this.bytes.set(new Uint8Array(chunk), begin);
    290       var chunkSize = this.chunkSize;
    291       var beginChunk = Math.floor(begin / chunkSize);
    292       var endChunk = Math.floor((end - 1) / chunkSize) + 1;
    293 
    294       for (var chunk = beginChunk; chunk < endChunk; ++chunk) {
    295         if (!(chunk in this.loadedChunks)) {
    296           this.loadedChunks[chunk] = true;
    297           ++this.numChunksLoaded;
    298         }
    299       }
    300     },
    301 
    302     ensureRange: function ChunkedStream_ensureRange(begin, end) {
    303       if (begin >= end) {
    304         return;
    305       }
    306 
    307       var chunkSize = this.chunkSize;
    308       var beginChunk = Math.floor(begin / chunkSize);
    309       var endChunk = Math.floor((end - 1) / chunkSize) + 1;
    310       for (var chunk = beginChunk; chunk < endChunk; ++chunk) {
    311         if (!(chunk in this.loadedChunks)) {
    312           throw new MissingDataException(begin, end);
    313         }
    314       }
    315     },
    316 
    317     nextEmptyChunk: function ChunkedStream_nextEmptyChunk(beginChunk) {
    318       for (var chunk = beginChunk, n = this.numChunks; chunk < n; ++chunk) {
    319         if (!(chunk in this.loadedChunks)) {
    320           return chunk;
    321         }
    322       }
    323       // Wrap around to beginning
    324       for (var chunk = 0; chunk < beginChunk; ++chunk) {
    325         if (!(chunk in this.loadedChunks)) {
    326           return chunk;
    327         }
    328       }
    329       return null;
    330     },
    331 
    332     hasChunk: function ChunkedStream_hasChunk(chunk) {
    333       return chunk in this.loadedChunks;
    334     },
    335 
    336     get length() {
    337       return this.end - this.start;
    338     },
    339 
    340     getByte: function ChunkedStream_getByte() {
    341       var pos = this.pos;
    342       if (pos >= this.end) {
    343         return null;
    344       }
    345       this.ensureRange(pos, pos + 1);
    346       return this.bytes[this.pos++];
    347     },
    348 
    349     // returns subarray of original buffer
    350     // should only be read
    351     getBytes: function ChunkedStream_getBytes(length) {
    352       var bytes = this.bytes;
    353       var pos = this.pos;
    354       var strEnd = this.end;
    355 
    356       if (!length) {
    357         this.ensureRange(pos, strEnd);
    358         return bytes.subarray(pos, strEnd);
    359       }
    360 
    361       var end = pos + length;
    362       if (end > strEnd)
    363         end = strEnd;
    364       this.ensureRange(pos, end);
    365 
    366       this.pos = end;
    367       return bytes.subarray(pos, end);
    368     },
    369 
    370     getByteRange: function ChunkedStream_getBytes(begin, end) {
    371       this.ensureRange(begin, end);
    372       return this.bytes.subarray(begin, end);
    373     },
    374 
    375     lookChar: function ChunkedStream_lookChar() {
    376       var pos = this.pos;
    377       if (pos >= this.end)
    378         return null;
    379       this.ensureRange(pos, pos + 1);
    380       return String.fromCharCode(this.bytes[pos]);
    381     },
    382 
    383     getChar: function ChunkedStream_getChar() {
    384       var pos = this.pos;
    385       if (pos >= this.end)
    386         return null;
    387       this.ensureRange(pos, pos + 1);
    388       return String.fromCharCode(this.bytes[this.pos++]);
    389     },
    390 
    391     skip: function ChunkedStream_skip(n) {
    392       if (!n)
    393         n = 1;
    394       this.pos += n;
    395     },
    396 
    397     reset: function ChunkedStream_reset() {
    398       this.pos = this.start;
    399     },
    400 
    401     moveStart: function ChunkedStream_moveStart() {
    402       this.start = this.pos;
    403     },
    404 
    405     makeSubStream: function ChunkedStream_makeSubStream(start, length, dict) {
    406       function ChunkedStreamSubstream() {}
    407       ChunkedStreamSubstream.prototype = Object.create(this);
    408       ChunkedStreamSubstream.prototype.getMissingChunks = function() {
    409         var chunkSize = this.chunkSize;
    410         var beginChunk = Math.floor(this.start / chunkSize);
    411         var endChunk = Math.floor((this.end - 1) / chunkSize) + 1;
    412         var missingChunks = [];
    413         for (var chunk = beginChunk; chunk < endChunk; ++chunk) {
    414           if (!(chunk in this.loadedChunks)) {
    415             missingChunks.push(chunk);
    416           }
    417         }
    418         return missingChunks;
    419       };
    420       var subStream = new ChunkedStreamSubstream();
    421       subStream.pos = subStream.start = start;
    422       subStream.end = start + length || this.end;
    423       subStream.dict = dict;
    424       return subStream;
    425     },
    426 
    427     isStream: true
    428   };
    429 
    430   return ChunkedStream;
    431 })();
    432 
    433 var ChunkedStreamManager = (function ChunkedStreamManagerClosure() {
    434 
    435   function ChunkedStreamManager(length, chunkSize, url, args) {
    436     var self = this;
    437     this.stream = new ChunkedStream(length, chunkSize, this);
    438     this.length = length;
    439     this.chunkSize = chunkSize;
    440     this.url = url;
    441     this.disableAutoFetch = args.disableAutoFetch;
    442     var msgHandler = this.msgHandler = args.msgHandler;
    443 
    444     if (args.chunkedViewerLoading) {
    445       msgHandler.on('OnDataRange', this.onReceiveData.bind(this));
    446       msgHandler.on('OnDataProgress', this.onProgress.bind(this));
    447       this.sendRequest = function ChunkedStreamManager_sendRequest(begin, end) {
    448         msgHandler.send('RequestDataRange', { begin: begin, end: end });
    449       };
    450     } else {
    451 
    452       var getXhr = function getXhr() {
    453         return new XMLHttpRequest();
    454       };
    455       this.networkManager = new NetworkManager(this.url, {
    456         getXhr: getXhr,
    457         httpHeaders: args.httpHeaders
    458       });
    459       this.sendRequest = function ChunkedStreamManager_sendRequest(begin, end) {
    460         this.networkManager.requestRange(begin, end, {
    461           onDone: this.onReceiveData.bind(this),
    462           onProgress: this.onProgress.bind(this)
    463         });
    464       };
    465     }
    466 
    467     this.currRequestId = 0;
    468 
    469     this.chunksNeededByRequest = {};
    470     this.requestsByChunk = {};
    471     this.callbacksByRequest = {};
    472 
    473     this.loadedStream = new Promise();
    474   }
    475 
    476   ChunkedStreamManager.prototype = {
    477 
    478     onLoadedStream: function ChunkedStreamManager_getLoadedStream() {
    479       return this.loadedStream;
    480     },
    481 
    482     // Get all the chunks that are not yet loaded and groups them into
    483     // contiguous ranges to load in as few requests as possible
    484     requestAllChunks: function ChunkedStreamManager_requestAllChunks() {
    485       var missingChunks = this.stream.getMissingChunks();
    486       this.requestChunks(missingChunks);
    487       return this.loadedStream;
    488     },
    489 
    490     requestChunks: function ChunkedStreamManager_requestChunks(chunks,
    491                                                                callback) {
    492       var requestId = this.currRequestId++;
    493 
    494       var chunksNeeded;
    495       this.chunksNeededByRequest[requestId] = chunksNeeded = {};
    496       for (var i = 0, ii = chunks.length; i < ii; i++) {
    497         if (!this.stream.hasChunk(chunks[i])) {
    498           chunksNeeded[chunks[i]] = true;
    499         }
    500       }
    501 
    502       if (isEmptyObj(chunksNeeded)) {
    503         if (callback) {
    504           callback();
    505         }
    506         return;
    507       }
    508 
    509       this.callbacksByRequest[requestId] = callback;
    510 
    511       var chunksToRequest = [];
    512       for (var chunk in chunksNeeded) {
    513         chunk = chunk | 0;
    514         if (!(chunk in this.requestsByChunk)) {
    515           this.requestsByChunk[chunk] = [];
    516           chunksToRequest.push(chunk);
    517         }
    518         this.requestsByChunk[chunk].push(requestId);
    519       }
    520 
    521       if (!chunksToRequest.length) {
    522         return;
    523       }
    524 
    525       var groupedChunksToRequest = this.groupChunks(chunksToRequest);
    526 
    527       for (var i = 0; i < groupedChunksToRequest.length; ++i) {
    528         var groupedChunk = groupedChunksToRequest[i];
    529         var begin = groupedChunk.beginChunk * this.chunkSize;
    530         var end = Math.min(groupedChunk.endChunk * this.chunkSize, this.length);
    531         this.sendRequest(begin, end);
    532       }
    533     },
    534 
    535     getStream: function ChunkedStreamManager_getStream() {
    536       return this.stream;
    537     },
    538 
    539     // Loads any chunks in the requested range that are not yet loaded
    540     requestRange: function ChunkedStreamManager_requestRange(
    541                       begin, end, callback) {
    542 
    543       end = Math.min(end, this.length);
    544 
    545       var beginChunk = this.getBeginChunk(begin);
    546       var endChunk = this.getEndChunk(end);
    547 
    548       var chunks = [];
    549       for (var chunk = beginChunk; chunk < endChunk; ++chunk) {
    550         chunks.push(chunk);
    551       }
    552 
    553       this.requestChunks(chunks, callback);
    554     },
    555 
    556     requestRanges: function ChunkedStreamManager_requestRanges(ranges,
    557                                                                callback) {
    558       ranges = ranges || [];
    559       var chunksToRequest = [];
    560 
    561       for (var i = 0; i < ranges.length; i++) {
    562         var beginChunk = this.getBeginChunk(ranges[i].begin);
    563         var endChunk = this.getEndChunk(ranges[i].end);
    564         for (var chunk = beginChunk; chunk < endChunk; ++chunk) {
    565           if (chunksToRequest.indexOf(chunk) < 0) {
    566             chunksToRequest.push(chunk);
    567           }
    568         }
    569       }
    570 
    571       chunksToRequest.sort(function(a, b) { return a - b; });
    572       this.requestChunks(chunksToRequest, callback);
    573     },
    574 
    575     // Groups a sorted array of chunks into as few continguous larger
    576     // chunks as possible
    577     groupChunks: function ChunkedStreamManager_groupChunks(chunks) {
    578       var groupedChunks = [];
    579       var beginChunk;
    580       var prevChunk;
    581       for (var i = 0; i < chunks.length; ++i) {
    582         var chunk = chunks[i];
    583 
    584         if (!beginChunk) {
    585           beginChunk = chunk;
    586         }
    587 
    588         if (prevChunk && prevChunk + 1 !== chunk) {
    589           groupedChunks.push({
    590             beginChunk: beginChunk, endChunk: prevChunk + 1});
    591           beginChunk = chunk;
    592         }
    593         if (i + 1 === chunks.length) {
    594           groupedChunks.push({
    595             beginChunk: beginChunk, endChunk: chunk + 1});
    596         }
    597 
    598         prevChunk = chunk;
    599       }
    600       return groupedChunks;
    601     },
    602 
    603     onProgress: function ChunkedStreamManager_onProgress(args) {
    604       var bytesLoaded = this.stream.numChunksLoaded * this.chunkSize +
    605                         args.loaded;
    606       this.msgHandler.send('DocProgress', {
    607         loaded: bytesLoaded,
    608         total: this.length
    609       });
    610     },
    611 
    612     onReceiveData: function ChunkedStreamManager_onReceiveData(args) {
    613       var chunk = args.chunk;
    614       var begin = args.begin;
    615       var end = begin + chunk.byteLength;
    616 
    617       var beginChunk = this.getBeginChunk(begin);
    618       var endChunk = this.getEndChunk(end);
    619 
    620       this.stream.onReceiveData(begin, chunk);
    621       if (this.stream.allChunksLoaded()) {
    622         this.loadedStream.resolve(this.stream);
    623       }
    624 
    625       var loadedRequests = [];
    626       for (var chunk = beginChunk; chunk < endChunk; ++chunk) {
    627 
    628         // The server might return more chunks than requested
    629         var requestIds = this.requestsByChunk[chunk] || [];
    630         delete this.requestsByChunk[chunk];
    631 
    632         for (var i = 0; i < requestIds.length; ++i) {
    633           var requestId = requestIds[i];
    634           var chunksNeeded = this.chunksNeededByRequest[requestId];
    635           if (chunk in chunksNeeded) {
    636             delete chunksNeeded[chunk];
    637           }
    638 
    639           if (!isEmptyObj(chunksNeeded)) {
    640             continue;
    641           }
    642 
    643           loadedRequests.push(requestId);
    644         }
    645       }
    646 
    647       // If there are no pending requests, automatically fetch the next
    648       // unfetched chunk of the PDF
    649       if (!this.disableAutoFetch && isEmptyObj(this.requestsByChunk)) {
    650         var nextEmptyChunk;
    651         if (this.stream.numChunksLoaded === 1) {
    652           // This is a special optimization so that after fetching the first
    653           // chunk, rather than fetching the second chunk, we fetch the last
    654           // chunk.
    655           var lastChunk = this.stream.numChunks - 1;
    656           if (!this.stream.hasChunk(lastChunk)) {
    657             nextEmptyChunk = lastChunk;
    658           }
    659         } else {
    660           nextEmptyChunk = this.stream.nextEmptyChunk(endChunk);
    661         }
    662         if (isInt(nextEmptyChunk)) {
    663           this.requestChunks([nextEmptyChunk]);
    664         }
    665       }
    666 
    667       for (var i = 0; i < loadedRequests.length; ++i) {
    668         var requestId = loadedRequests[i];
    669         var callback = this.callbacksByRequest[requestId];
    670         delete this.callbacksByRequest[requestId];
    671         if (callback) {
    672           callback();
    673         }
    674       }
    675 
    676       this.msgHandler.send('DocProgress', {
    677         loaded: this.stream.numChunksLoaded * this.chunkSize,
    678         total: this.length
    679       });
    680     },
    681 
    682     getBeginChunk: function ChunkedStreamManager_getBeginChunk(begin) {
    683       var chunk = Math.floor(begin / this.chunkSize);
    684       return chunk;
    685     },
    686 
    687     getEndChunk: function ChunkedStreamManager_getEndChunk(end) {
    688       if (end % this.chunkSize === 0) {
    689         return end / this.chunkSize;
    690       }
    691 
    692       // 0 -> 0
    693       // 1 -> 1
    694       // 99 -> 1
    695       // 100 -> 1
    696       // 101 -> 2
    697       var chunk = Math.floor((end - 1) / this.chunkSize) + 1;
    698       return chunk;
    699     }
    700   };
    701 
    702   return ChunkedStreamManager;
    703 })();
    704 
    705 
    706 
    707 // TODO(mack): Make use of PDFJS.Util.inherit() when it becomes available
    708 var BasePdfManager = (function BasePdfManagerClosure() {
    709   function BasePdfManager() {
    710     throw new Error('Cannot initialize BaseManagerManager');
    711   }
    712 
    713   BasePdfManager.prototype = {
    714     onLoadedStream: function BasePdfManager_onLoadedStream() {
    715       throw new NotImplementedException();
    716     },
    717 
    718     ensureModel: function BasePdfManager_ensureModel(prop, args) {
    719       return this.ensure(this.pdfModel, prop, args);
    720     },
    721 
    722     ensureXRef: function BasePdfManager_ensureXRef(prop, args) {
    723       return this.ensure(this.pdfModel.xref, prop, args);
    724     },
    725 
    726     ensureCatalog: function BasePdfManager_ensureCatalog(prop, args) {
    727       return this.ensure(this.pdfModel.catalog, prop, args);
    728     },
    729 
    730     getPage: function BasePdfManager_pagePage(pageIndex) {
    731       return this.pdfModel.getPage(pageIndex);
    732     },
    733 
    734     ensure: function BasePdfManager_ensure(obj, prop, args) {
    735       return new NotImplementedException();
    736     },
    737 
    738     requestRange: function BasePdfManager_ensure(begin, end) {
    739       return new NotImplementedException();
    740     },
    741 
    742     requestLoadedStream: function BasePdfManager_requestLoadedStream() {
    743       return new NotImplementedException();
    744     },
    745 
    746     updatePassword: function BasePdfManager_updatePassword(password) {
    747       this.pdfModel.xref.password = this.password = password;
    748       if (this.passwordChangedPromise) {
    749         this.passwordChangedPromise.resolve();
    750       }
    751     }
    752   };
    753 
    754   return BasePdfManager;
    755 })();
    756 
    757 var LocalPdfManager = (function LocalPdfManagerClosure() {
    758   function LocalPdfManager(data, password) {
    759     var stream = new Stream(data);
    760     this.pdfModel = new PDFDocument(this, stream, password);
    761     this.loadedStream = new Promise();
    762     this.loadedStream.resolve(stream);
    763   }
    764 
    765   LocalPdfManager.prototype = Object.create(BasePdfManager.prototype);
    766   LocalPdfManager.prototype.constructor = LocalPdfManager;
    767 
    768   LocalPdfManager.prototype.ensure =
    769       function LocalPdfManager_ensure(obj, prop, args) {
    770     var promise = new Promise();
    771     try {
    772       var value = obj[prop];
    773       var result;
    774       if (typeof(value) === 'function') {
    775         result = value.apply(obj, args);
    776       } else {
    777         result = value;
    778       }
    779       promise.resolve(result);
    780     } catch (e) {
    781       console.log(e.stack);
    782       promise.reject(e);
    783     }
    784     return promise;
    785   };
    786 
    787   LocalPdfManager.prototype.requestRange =
    788       function LocalPdfManager_requestRange(begin, end) {
    789     var promise = new Promise();
    790     promise.resolve();
    791     return promise;
    792   };
    793 
    794   LocalPdfManager.prototype.requestLoadedStream =
    795       function LocalPdfManager_requestLoadedStream() {
    796   };
    797 
    798   LocalPdfManager.prototype.onLoadedStream =
    799       function LocalPdfManager_getLoadedStream() {
    800     return this.loadedStream;
    801   };
    802 
    803   return LocalPdfManager;
    804 })();
    805 
    806 var NetworkPdfManager = (function NetworkPdfManagerClosure() {
    807 
    808   var CHUNK_SIZE = 65536;
    809 
    810   function NetworkPdfManager(args, msgHandler) {
    811 
    812     this.msgHandler = msgHandler;
    813 
    814     var params = {
    815       msgHandler: msgHandler,
    816       httpHeaders: args.httpHeaders,
    817       chunkedViewerLoading: args.chunkedViewerLoading,
    818       disableAutoFetch: args.disableAutoFetch
    819     };
    820     this.streamManager = new ChunkedStreamManager(args.length, CHUNK_SIZE,
    821                                                   args.url, params);
    822 
    823     this.pdfModel = new PDFDocument(this, this.streamManager.getStream(),
    824                                     args.password);
    825   }
    826 
    827   NetworkPdfManager.prototype = Object.create(BasePdfManager.prototype);
    828   NetworkPdfManager.prototype.constructor = NetworkPdfManager;
    829 
    830   NetworkPdfManager.prototype.ensure =
    831       function NetworkPdfManager_ensure(obj, prop, args) {
    832     var promise = new Promise();
    833     this.ensureHelper(promise, obj, prop, args);
    834     return promise;
    835   };
    836 
    837   NetworkPdfManager.prototype.ensureHelper =
    838       function NetworkPdfManager_ensureHelper(promise, obj, prop, args) {
    839     try {
    840       var result;
    841       var value = obj[prop];
    842       if (typeof(value) === 'function') {
    843         result = value.apply(obj, args);
    844       } else {
    845         result = value;
    846       }
    847       promise.resolve(result);
    848     } catch(e) {
    849       if (!(e instanceof MissingDataException)) {
    850         console.log(e.stack);
    851         promise.reject(e);
    852         return;
    853       }
    854 
    855       this.streamManager.requestRange(e.begin, e.end, function() {
    856         this.ensureHelper(promise, obj, prop, args);
    857       }.bind(this));
    858     }
    859   };
    860 
    861   NetworkPdfManager.prototype.requestRange =
    862       function NetworkPdfManager_requestRange(begin, end) {
    863     var promise = new Promise();
    864     this.streamManager.requestRange(begin, end, function() {
    865       promise.resolve();
    866     });
    867     return promise;
    868   };
    869 
    870   NetworkPdfManager.prototype.requestLoadedStream =
    871       function NetworkPdfManager_requestLoadedStream() {
    872     this.streamManager.requestAllChunks();
    873   };
    874 
    875   NetworkPdfManager.prototype.onLoadedStream =
    876       function NetworkPdfManager_getLoadedStream() {
    877     return this.streamManager.onLoadedStream();
    878   };
    879 
    880   return NetworkPdfManager;
    881 })();
    882 
    883 
    884 
    88551var globalScope = (typeof window === 'undefined') ? this : window;
    88652
    88753var isWorker = (typeof window == 'undefined');
    88854
    889 var ERRORS = 0, WARNINGS = 1, INFOS = 5;
    890 var verbosity = WARNINGS;
     55var FONT_IDENTITY_MATRIX = [0.001, 0, 0, 0.001, 0, 0];
     56
     57var TextRenderingMode = {
     58  FILL: 0,
     59  STROKE: 1,
     60  FILL_STROKE: 2,
     61  INVISIBLE: 3,
     62  FILL_ADD_TO_PATH: 4,
     63  STROKE_ADD_TO_PATH: 5,
     64  FILL_STROKE_ADD_TO_PATH: 6,
     65  ADD_TO_PATH: 7,
     66  FILL_STROKE_MASK: 3,
     67  ADD_TO_PATH_FLAG: 4
     68};
    89169
    89270// The global PDFJS object exposes the API
     
    89977globalScope.PDFJS.pdfBug = false;
    90078
    901 
    902 var Page = (function PageClosure() {
    903 
    904   function Page(pdfManager, xref, pageIndex, pageDict, ref) {
    905     this.pdfManager = pdfManager;
    906     this.pageIndex = pageIndex;
    907     this.pageDict = pageDict;
    908     this.xref = xref;
    909     this.ref = ref;
    910     this.idCounters = {
    911       font: 0,
    912       obj: 0
    913     };
    914     this.resourcesPromise = null;
    915   }
    916 
    917   Page.prototype = {
    918     getPageProp: function Page_getPageProp(key) {
    919       return this.pageDict.get(key);
    920     },
    921     inheritPageProp: function Page_inheritPageProp(key) {
    922       var dict = this.pageDict;
    923       var obj = dict.get(key);
    924       while (obj === undefined) {
    925         dict = dict.get('Parent');
    926         if (!dict)
    927           break;
    928         obj = dict.get(key);
    929       }
    930       return obj;
    931     },
    932     get content() {
    933       return this.getPageProp('Contents');
    934     },
    935     get resources() {
    936       return shadow(this, 'resources', this.inheritPageProp('Resources'));
    937     },
    938     get mediaBox() {
    939       var obj = this.inheritPageProp('MediaBox');
    940       // Reset invalid media box to letter size.
    941       if (!isArray(obj) || obj.length !== 4)
    942         obj = [0, 0, 612, 792];
    943       return shadow(this, 'mediaBox', obj);
    944     },
    945     get view() {
    946       var mediaBox = this.mediaBox;
    947       var cropBox = this.inheritPageProp('CropBox');
    948       if (!isArray(cropBox) || cropBox.length !== 4)
    949         return shadow(this, 'view', mediaBox);
    950 
    951       // From the spec, 6th ed., p.963:
    952       // "The crop, bleed, trim, and art boxes should not ordinarily
    953       // extend beyond the boundaries of the media box. If they do, they are
    954       // effectively reduced to their intersection with the media box."
    955       cropBox = Util.intersect(cropBox, mediaBox);
    956       if (!cropBox)
    957         return shadow(this, 'view', mediaBox);
    958 
    959       return shadow(this, 'view', cropBox);
    960     },
    961     get annotationRefs() {
    962       return shadow(this, 'annotationRefs', this.inheritPageProp('Annots'));
    963     },
    964     get rotate() {
    965       var rotate = this.inheritPageProp('Rotate') || 0;
    966       // Normalize rotation so it's a multiple of 90 and between 0 and 270
    967       if (rotate % 90 !== 0) {
    968         rotate = 0;
    969       } else if (rotate >= 360) {
    970         rotate = rotate % 360;
    971       } else if (rotate < 0) {
    972         // The spec doesn't cover negatives, assume its counterclockwise
    973         // rotation. The following is the other implementation of modulo.
    974         rotate = ((rotate % 360) + 360) % 360;
    975       }
    976       return shadow(this, 'rotate', rotate);
    977     },
    978     getContentStream: function Page_getContentStream() {
    979       var content = this.content;
    980       var stream;
    981       if (isArray(content)) {
    982         // fetching items
    983         var xref = this.xref;
    984         var i, n = content.length;
    985         var streams = [];
    986         for (i = 0; i < n; ++i)
    987           streams.push(xref.fetchIfRef(content[i]));
    988         stream = new StreamsSequenceStream(streams);
    989       } else if (isStream(content)) {
    990         stream = content;
    991       } else {
    992         // replacing non-existent page content with empty one
    993         stream = new NullStream();
    994       }
    995       return stream;
    996     },
    997     loadResources: function(keys) {
    998       if (!this.resourcesPromise) {
    999         // TODO: add async inheritPageProp and remove this.
    1000         this.resourcesPromise = this.pdfManager.ensure(this, 'resources');
    1001       }
    1002       var promise = new Promise();
    1003       this.resourcesPromise.then(function resourceSuccess() {
    1004         var objectLoader = new ObjectLoader(this.resources.map,
    1005                                             keys,
    1006                                             this.xref);
    1007         objectLoader.load().then(function objectLoaderSuccess() {
    1008           promise.resolve();
    1009         });
    1010       }.bind(this));
    1011       return promise;
    1012     },
    1013     getOperatorList: function Page_getOperatorList(handler) {
    1014       var self = this;
    1015       var promise = new Promise();
    1016 
    1017       function reject(e) {
    1018         promise.reject(e);
    1019       }
    1020 
    1021       var pageListPromise = new Promise();
    1022 
    1023       var pdfManager = this.pdfManager;
    1024       var contentStreamPromise = pdfManager.ensure(this, 'getContentStream',
    1025                                                    []);
    1026       var resourcesPromise = this.loadResources([
    1027         'ExtGState',
    1028         'ColorSpace',
    1029         'Pattern',
    1030         'Shading',
    1031         'XObject',
    1032         'Font',
    1033         // ProcSet
    1034         // Properties
    1035       ]);
    1036 
    1037       var partialEvaluator = new PartialEvaluator(
    1038             pdfManager, this.xref, handler,
    1039             this.pageIndex, 'p' + this.pageIndex + '_',
    1040             this.idCounters);
    1041 
    1042       var dataPromises = Promise.all(
    1043           [contentStreamPromise, resourcesPromise], reject);
    1044       dataPromises.then(function(data) {
    1045         var contentStream = data[0];
    1046 
    1047         partialEvaluator.getOperatorList(contentStream, self.resources).then(
    1048           function(data) {
    1049             pageListPromise.resolve(data);
    1050           },
    1051           reject
    1052         );
    1053       });
    1054 
    1055       var annotationsPromise = pdfManager.ensure(this, 'annotations');
    1056       Promise.all([pageListPromise, annotationsPromise]).then(function(datas) {
    1057         var pageData = datas[0];
    1058         var pageQueue = pageData.queue;
    1059         var annotations = datas[1];
    1060 
    1061         if (annotations.length === 0) {
    1062           PartialEvaluator.optimizeQueue(pageQueue);
    1063           promise.resolve(pageData);
    1064           return;
    1065         }
    1066 
    1067         var dependencies = pageData.dependencies;
    1068         var annotationsReadyPromise = Annotation.appendToOperatorList(
    1069           annotations, pageQueue, pdfManager, dependencies, partialEvaluator);
    1070         annotationsReadyPromise.then(function () {
    1071           PartialEvaluator.optimizeQueue(pageQueue);
    1072 
    1073           promise.resolve(pageData);
    1074         }, reject);
    1075       }, reject);
    1076 
    1077       return promise;
    1078     },
    1079     extractTextContent: function Page_extractTextContent() {
    1080       var handler = {
    1081         on: function nullHandlerOn() {},
    1082         send: function nullHandlerSend() {}
    1083       };
    1084 
    1085       var self = this;
    1086 
    1087       var textContentPromise = new Promise();
    1088 
    1089       var pdfManager = this.pdfManager;
    1090       var contentStreamPromise = pdfManager.ensure(this, 'getContentStream',
    1091                                                    []);
    1092 
    1093       var resourcesPromise = this.loadResources([
    1094         'ExtGState',
    1095         'XObject',
    1096         'Font'
    1097       ]);
    1098 
    1099       var dataPromises = Promise.all([contentStreamPromise,
    1100                                       resourcesPromise]);
    1101       dataPromises.then(function(data) {
    1102         var contentStream = data[0];
    1103         var partialEvaluator = new PartialEvaluator(
    1104               pdfManager, self.xref, handler,
    1105               self.pageIndex, 'p' + self.pageIndex + '_',
    1106               self.idCounters);
    1107 
    1108         partialEvaluator.getTextContent(
    1109             contentStream, self.resources).then(function(bidiTexts) {
    1110           textContentPromise.resolve({
    1111             bidiTexts: bidiTexts
    1112           });
    1113         });
    1114       });
    1115 
    1116       return textContentPromise;
    1117     },
    1118 
    1119     getAnnotationsData: function Page_getAnnotationsData() {
    1120       var annotations = this.annotations;
    1121       var annotationsData = [];
    1122       for (var i = 0, n = annotations.length; i < n; ++i) {
    1123         annotationsData.push(annotations[i].getData());
    1124       }
    1125       return annotationsData;
    1126     },
    1127 
    1128     get annotations() {
    1129       var annotations = [];
    1130       var annotationRefs = this.annotationRefs || [];
    1131       for (var i = 0, n = annotationRefs.length; i < n; ++i) {
    1132         var annotationRef = annotationRefs[i];
    1133         var annotation = Annotation.fromRef(this.xref, annotationRef);
    1134         if (annotation) {
    1135           annotations.push(annotation);
    1136         }
    1137       }
    1138       return shadow(this, 'annotations', annotations);
    1139     }
    1140   };
    1141 
    1142   return Page;
    1143 })();
    1144 
    1145 /**
    1146  * The `PDFDocument` holds all the data of the PDF file. Compared to the
    1147  * `PDFDoc`, this one doesn't have any job management code.
    1148  * Right now there exists one PDFDocument on the main thread + one object
    1149  * for each worker. If there is no worker support enabled, there are two
    1150  * `PDFDocument` objects on the main thread created.
    1151  */
    1152 var PDFDocument = (function PDFDocumentClosure() {
    1153   function PDFDocument(pdfManager, arg, password) {
    1154     if (isStream(arg))
    1155       init.call(this, pdfManager, arg, password);
    1156     else if (isArrayBuffer(arg))
    1157       init.call(this, pdfManager, new Stream(arg), password);
    1158     else
    1159       error('PDFDocument: Unknown argument type');
    1160   }
    1161 
    1162   function init(pdfManager, stream, password) {
    1163     assertWellFormed(stream.length > 0, 'stream must have data');
    1164     this.pdfManager = pdfManager;
    1165     this.stream = stream;
    1166     var xref = new XRef(this.stream, password, pdfManager);
    1167     this.xref = xref;
    1168   }
    1169 
    1170   function find(stream, needle, limit, backwards) {
    1171     var pos = stream.pos;
    1172     var end = stream.end;
    1173     var str = '';
    1174     if (pos + limit > end)
    1175       limit = end - pos;
    1176     for (var n = 0; n < limit; ++n)
    1177       str += stream.getChar();
    1178     stream.pos = pos;
    1179     var index = backwards ? str.lastIndexOf(needle) : str.indexOf(needle);
    1180     if (index == -1)
    1181       return false; /* not found */
    1182     stream.pos += index;
    1183     return true; /* found */
    1184   }
    1185 
    1186   var DocumentInfoValidators = {
    1187     get entries() {
    1188       // Lazily build this since all the validation functions below are not
    1189       // defined until after this file loads.
    1190       return shadow(this, 'entries', {
    1191         Title: isString,
    1192         Author: isString,
    1193         Subject: isString,
    1194         Keywords: isString,
    1195         Creator: isString,
    1196         Producer: isString,
    1197         CreationDate: isString,
    1198         ModDate: isString,
    1199         Trapped: isName
    1200       });
    1201     }
    1202   };
    1203 
    1204   PDFDocument.prototype = {
    1205     parse: function PDFDocument_parse(recoveryMode) {
    1206       this.setup(recoveryMode);
    1207       this.acroForm = this.catalog.catDict.get('AcroForm');
    1208     },
    1209 
    1210     get linearization() {
    1211       var length = this.stream.length;
    1212       var linearization = false;
    1213       if (length) {
    1214         try {
    1215           linearization = new Linearization(this.stream);
    1216           if (linearization.length != length) {
    1217             linearization = false;
    1218           }
    1219         } catch (err) {
    1220           if (err instanceof MissingDataException) {
    1221             throw err;
    1222           }
    1223 
    1224           info('The linearization data is not available ' +
    1225                'or unreadable PDF data is found');
    1226           linearization = false;
    1227         }
    1228       }
    1229       // shadow the prototype getter with a data property
    1230       return shadow(this, 'linearization', linearization);
    1231     },
    1232     get startXRef() {
    1233       var stream = this.stream;
    1234       var startXRef = 0;
    1235       var linearization = this.linearization;
    1236       if (linearization) {
    1237         // Find end of first obj.
    1238         stream.reset();
    1239         if (find(stream, 'endobj', 1024))
    1240           startXRef = stream.pos + 6;
    1241       } else {
    1242         // Find startxref by jumping backward from the end of the file.
    1243         var step = 1024;
    1244         var found = false, pos = stream.end;
    1245         while (!found && pos > 0) {
    1246           pos -= step - 'startxref'.length;
    1247           if (pos < 0)
    1248             pos = 0;
    1249           stream.pos = pos;
    1250           found = find(stream, 'startxref', step, true);
    1251         }
    1252         if (found) {
    1253           stream.skip(9);
    1254           var ch;
    1255           do {
    1256             ch = stream.getChar();
    1257           } while (Lexer.isSpace(ch));
    1258           var str = '';
    1259           while ((ch - '0') <= 9) {
    1260             str += ch;
    1261             ch = stream.getChar();
    1262           }
    1263           startXRef = parseInt(str, 10);
    1264           if (isNaN(startXRef))
    1265             startXRef = 0;
    1266         }
    1267       }
    1268       // shadow the prototype getter with a data property
    1269       return shadow(this, 'startXRef', startXRef);
    1270     },
    1271     get mainXRefEntriesOffset() {
    1272       var mainXRefEntriesOffset = 0;
    1273       var linearization = this.linearization;
    1274       if (linearization)
    1275         mainXRefEntriesOffset = linearization.mainXRefEntriesOffset;
    1276       // shadow the prototype getter with a data property
    1277       return shadow(this, 'mainXRefEntriesOffset', mainXRefEntriesOffset);
    1278     },
    1279     // Find the header, remove leading garbage and setup the stream
    1280     // starting from the header.
    1281     checkHeader: function PDFDocument_checkHeader() {
    1282       var stream = this.stream;
    1283       stream.reset();
    1284       if (find(stream, '%PDF-', 1024)) {
    1285         // Found the header, trim off any garbage before it.
    1286         stream.moveStart();
    1287         // Reading file format version
    1288         var MAX_VERSION_LENGTH = 12;
    1289         var version = '', ch;
    1290         while ((ch = stream.getChar()) > ' ') {
    1291           if (version.length >= MAX_VERSION_LENGTH) {
    1292             break;
    1293           }
    1294           version += ch;
    1295         }
    1296         // removing "%PDF-"-prefix
    1297         this.pdfFormatVersion = version.substring(5);
    1298         return;
    1299       }
    1300       // May not be a PDF file, continue anyway.
    1301     },
    1302     parseStartXRef: function PDFDocument_parseStartXRef() {
    1303       var startXRef = this.startXRef;
    1304       this.xref.setStartXRef(startXRef);
    1305     },
    1306     setup: function PDFDocument_setup(recoveryMode) {
    1307       this.xref.parse(recoveryMode);
    1308       this.catalog = new Catalog(this.pdfManager, this.xref);
    1309     },
    1310     get numPages() {
    1311       var linearization = this.linearization;
    1312       var num = linearization ? linearization.numPages : this.catalog.numPages;
    1313       // shadow the prototype getter
    1314       return shadow(this, 'numPages', num);
    1315     },
    1316     get documentInfo() {
    1317       var docInfo = {
    1318         PDFFormatVersion: this.pdfFormatVersion,
    1319         IsAcroFormPresent: !!this.acroForm
    1320       };
    1321       if (this.xref.trailer.has('Info')) {
    1322         var infoDict = this.xref.trailer.get('Info');
    1323 
    1324         var validEntries = DocumentInfoValidators.entries;
    1325         // Only fill the document info with valid entries from the spec.
    1326         for (var key in validEntries) {
    1327           if (infoDict.has(key)) {
    1328             var value = infoDict.get(key);
    1329             // Make sure the value conforms to the spec.
    1330             if (validEntries[key](value)) {
    1331               docInfo[key] = typeof value !== 'string' ? value :
    1332                              stringToPDFString(value);
    1333             } else {
    1334               info('Bad value in document info for "' + key + '"');
    1335             }
    1336           }
    1337         }
    1338       }
    1339       return shadow(this, 'documentInfo', docInfo);
    1340     },
    1341     get fingerprint() {
    1342       var xref = this.xref, fileID;
    1343       if (xref.trailer.has('ID')) {
    1344         fileID = '';
    1345         var id = xref.trailer.get('ID')[0];
    1346         id.split('').forEach(function(el) {
    1347           fileID += Number(el.charCodeAt(0)).toString(16);
    1348         });
    1349       } else {
    1350         // If we got no fileID, then we generate one,
    1351         // from the first 100 bytes of PDF
    1352         var data = this.stream.bytes.subarray(0, 100);
    1353         var hash = calculateMD5(data, 0, data.length);
    1354         fileID = '';
    1355         for (var i = 0, length = hash.length; i < length; i++) {
    1356           fileID += Number(hash[i]).toString(16);
    1357         }
    1358       }
    1359 
    1360       return shadow(this, 'fingerprint', fileID);
    1361     },
    1362 
    1363     traversePages: function PDFDocument_traversePages() {
    1364       this.catalog.traversePages();
    1365     },
    1366 
    1367     getPage: function PDFDocument_getPage(pageIndex) {
    1368       return this.catalog.getPage(pageIndex);
    1369     }
    1370   };
    1371 
    1372   return PDFDocument;
    1373 })();
    1374 
    1375 
    1376 
    1377 // Use only for debugging purposes. This should not be used in any code that is
    1378 // in mozilla master.
    1379 var log = (function() {
    1380   if ('console' in globalScope && 'log' in globalScope['console']) {
    1381     return globalScope['console']['log'].bind(globalScope['console']);
    1382   } else {
    1383     return function nop() {
    1384     };
    1385   }
    1386 })();
    1387 
    1388 // A notice for devs that will not trigger the fallback UI.  These are good
    1389 // for things that are helpful to devs, such as warning that Workers were
    1390 // disabled, which is important to devs but not end users.
     79PDFJS.VERBOSITY_LEVELS = {
     80  errors: 0,
     81  warnings: 1,
     82  infos: 5
     83};
     84
     85// All the possible operations for an operator list.
     86var OPS = PDFJS.OPS = {
     87  // Intentionally start from 1 so it is easy to spot bad operators that will be
     88  // 0's.
     89  dependency: 1,
     90  setLineWidth: 2,
     91  setLineCap: 3,
     92  setLineJoin: 4,
     93  setMiterLimit: 5,
     94  setDash: 6,
     95  setRenderingIntent: 7,
     96  setFlatness: 8,
     97  setGState: 9,
     98  save: 10,
     99  restore: 11,
     100  transform: 12,
     101  moveTo: 13,
     102  lineTo: 14,
     103  curveTo: 15,
     104  curveTo2: 16,
     105  curveTo3: 17,
     106  closePath: 18,
     107  rectangle: 19,
     108  stroke: 20,
     109  closeStroke: 21,
     110  fill: 22,
     111  eoFill: 23,
     112  fillStroke: 24,
     113  eoFillStroke: 25,
     114  closeFillStroke: 26,
     115  closeEOFillStroke: 27,
     116  endPath: 28,
     117  clip: 29,
     118  eoClip: 30,
     119  beginText: 31,
     120  endText: 32,
     121  setCharSpacing: 33,
     122  setWordSpacing: 34,
     123  setHScale: 35,
     124  setLeading: 36,
     125  setFont: 37,
     126  setTextRenderingMode: 38,
     127  setTextRise: 39,
     128  moveText: 40,
     129  setLeadingMoveText: 41,
     130  setTextMatrix: 42,
     131  nextLine: 43,
     132  showText: 44,
     133  showSpacedText: 45,
     134  nextLineShowText: 46,
     135  nextLineSetSpacingShowText: 47,
     136  setCharWidth: 48,
     137  setCharWidthAndBounds: 49,
     138  setStrokeColorSpace: 50,
     139  setFillColorSpace: 51,
     140  setStrokeColor: 52,
     141  setStrokeColorN: 53,
     142  setFillColor: 54,
     143  setFillColorN: 55,
     144  setStrokeGray: 56,
     145  setFillGray: 57,
     146  setStrokeRGBColor: 58,
     147  setFillRGBColor: 59,
     148  setStrokeCMYKColor: 60,
     149  setFillCMYKColor: 61,
     150  shadingFill: 62,
     151  beginInlineImage: 63,
     152  beginImageData: 64,
     153  endInlineImage: 65,
     154  paintXObject: 66,
     155  markPoint: 67,
     156  markPointProps: 68,
     157  beginMarkedContent: 69,
     158  beginMarkedContentProps: 70,
     159  endMarkedContent: 71,
     160  beginCompat: 72,
     161  endCompat: 73,
     162  paintFormXObjectBegin: 74,
     163  paintFormXObjectEnd: 75,
     164  beginGroup: 76,
     165  endGroup: 77,
     166  beginAnnotations: 78,
     167  endAnnotations: 79,
     168  beginAnnotation: 80,
     169  endAnnotation: 81,
     170  paintJpegXObject: 82,
     171  paintImageMaskXObject: 83,
     172  paintImageMaskXObjectGroup: 84,
     173  paintImageXObject: 85,
     174  paintInlineImageXObject: 86,
     175  paintInlineImageXObjectGroup: 87
     176};
     177
     178// A notice for devs. These are good for things that are helpful to devs, such
     179// as warning that Workers were disabled, which is important to devs but not
     180// end users.
    1391181function info(msg) {
    1392   if (verbosity >= INFOS) {
    1393     log('Info: ' + msg);
    1394     PDFJS.LogManager.notify('info', msg);
     182  if (PDFJS.verbosity >= PDFJS.VERBOSITY_LEVELS.infos) {
     183    console.log('Info: ' + msg);
    1395184  }
    1396185}
    1397186
    1398 // Non-fatal warnings that should trigger the fallback UI.
     187// Non-fatal warnings.
    1399188function warn(msg) {
    1400   if (verbosity >= WARNINGS) {
    1401     log('Warning: ' + msg);
    1402     PDFJS.LogManager.notify('warn', msg);
     189  if (PDFJS.verbosity >= PDFJS.VERBOSITY_LEVELS.warnings) {
     190    console.log('Warning: ' + msg);
    1403191  }
    1404192}
     
    1411199    var logArguments = ['Error:'];
    1412200    logArguments.push.apply(logArguments, arguments);
    1413     log.apply(null, logArguments);
     201    console.log.apply(console, logArguments);
    1414202    // Join the arguments into a single string for the lines below.
    1415203    msg = [].join.call(arguments, ' ');
    1416204  } else {
    1417     log('Error: ' + msg);
    1418   }
    1419   log(backtrace());
    1420   PDFJS.LogManager.notify('error', msg);
     205    console.log('Error: ' + msg);
     206  }
     207  console.log(backtrace());
     208  UnsupportedManager.notify(UNSUPPORTED_FEATURES.unknown);
    1421209  throw new Error(msg);
    1422 }
    1423 
    1424 // Missing features that should trigger the fallback UI.
    1425 function TODO(what) {
    1426   warn('TODO: ' + what);
    1427210}
    1428211
     
    1440223}
    1441224
     225var UNSUPPORTED_FEATURES = PDFJS.UNSUPPORTED_FEATURES = {
     226  unknown: 'unknown',
     227  forms: 'forms',
     228  javaScript: 'javaScript',
     229  smask: 'smask',
     230  shadingPattern: 'shadingPattern',
     231  font: 'font'
     232};
     233
     234var UnsupportedManager = PDFJS.UnsupportedManager =
     235  (function UnsupportedManagerClosure() {
     236  var listeners = [];
     237  return {
     238    listen: function (cb) {
     239      listeners.push(cb);
     240    },
     241    notify: function (featureId) {
     242      warn('Unsupported feature "' + featureId + '"');
     243      for (var i = 0, ii = listeners.length; i < ii; i++) {
     244        listeners[i](featureId);
     245      }
     246    }
     247  };
     248})();
     249
    1442250// Combines two URLs. The baseUrl shall be absolute URL. If the url is an
    1443251// absolute URL, it will be returned as is.
     
    1445253  if (!url)
    1446254    return baseUrl;
    1447   if (url.indexOf(':') >= 0)
     255  if (/^[a-z][a-z0-9+\-.]*:/i.test(url))
    1448256    return url;
    1449257  if (url.charAt(0) == '/') {
     
    1464272}
    1465273
     274// Validates if URL is safe and allowed, e.g. to avoid XSS.
     275function isValidUrl(url, allowRelative) {
     276  if (!url) {
     277    return false;
     278  }
     279  // RFC 3986 (http://tools.ietf.org/html/rfc3986#section-3.1)
     280  // scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
     281  var protocol = /^[a-z][a-z0-9+\-.]*(?=:)/i.exec(url);
     282  if (!protocol) {
     283    return allowRelative;
     284  }
     285  protocol = protocol[0].toLowerCase();
     286  switch (protocol) {
     287    case 'http':
     288    case 'https':
     289    case 'ftp':
     290    case 'mailto':
     291      return true;
     292    default:
     293      return false;
     294  }
     295}
     296PDFJS.isValidUrl = isValidUrl;
     297
    1466298// In a well-formed PDF, |cond| holds.  If it doesn't, subsequent
    1467299// behavior is undefined.
     
    1470302    error(msg);
    1471303}
    1472 
    1473 var LogManager = PDFJS.LogManager = (function LogManagerClosure() {
    1474   var loggers = [];
    1475   return {
    1476     addLogger: function logManager_addLogger(logger) {
    1477       loggers.push(logger);
    1478     },
    1479     notify: function(type, message) {
    1480       for (var i = 0, ii = loggers.length; i < ii; i++) {
    1481         var logger = loggers[i];
    1482         if (logger[type])
    1483           logger[type](message);
    1484       }
    1485     }
    1486   };
    1487 })();
    1488304
    1489305function shadow(obj, prop, value) {
     
    1566382    this.begin = begin;
    1567383    this.end = end;
    1568     this.message = 'Missing data [begin, end)';
     384    this.message = 'Missing data [' + begin + ', ' + end + ')';
    1569385  }
    1570386
     
    1615431
    1616432  Util.makeCssCmyk = function Util_makeCssCmyk(cmyk) {
    1617     var cs = new DeviceCmykCS();
    1618     Util.makeCssCmyk = function makeCssCmyk(cmyk) {
    1619       var rgb = cs.getRgb(cmyk, 0);
    1620       return Util.makeCssRgb(rgb);
    1621     };
    1622     return Util.makeCssCmyk(cmyk);
     433    var rgb = ColorSpace.singletons.cmyk.getRgb(cmyk, 0);
     434    return Util.makeCssRgb(rgb);
    1623435  };
    1624436
     
    1802614      sub.prototype[prop] = prototype[prop];
    1803615    }
     616  };
     617
     618  Util.loadScript = function Util_loadScript(src, callback) {
     619    var script = document.createElement('script');
     620    var loaded = false;
     621    script.setAttribute('src', src);
     622    if (callback) {
     623      script.onload = function() {
     624        if (!loaded) {
     625          callback();
     626        }
     627        loaded = true;
     628      };
     629    }
     630    document.getElementsByTagName('head')[0].appendChild(script);
    1804631  };
    1805632
     
    1979806function isStream(v) {
    1980807  return typeof v == 'object' && v !== null && v !== undefined &&
    1981          ('getChar' in v);
     808         ('getBytes' in v);
    1982809}
    1983810
     
    2005832
    2006833/**
     834 * Legacy support for PDFJS Promise implementation.
     835 * TODO remove eventually
     836 */
     837var LegacyPromise = PDFJS.LegacyPromise = (function LegacyPromiseClosure() {
     838  return function LegacyPromise() {
     839    var resolve, reject;
     840    var promise = new Promise(function (resolve_, reject_) {
     841      resolve = resolve_;
     842      reject = reject_;
     843    });
     844    promise.resolve = resolve;
     845    promise.reject = reject;
     846    return promise;
     847  };
     848})();
     849
     850/**
     851 * Polyfill for Promises:
    2007852 * The following promise implementation tries to generally implment the
    2008853 * Promise/A+ spec. Some notable differences from other promise libaries are:
     
    2013858 * https://bugzilla.mozilla.org/show_bug.cgi?id=810490
    2014859 */
    2015 var Promise = PDFJS.Promise = (function PromiseClosure() {
     860(function PromiseClosure() {
     861  if (globalScope.Promise) {
     862    // Promises existing in the DOM/Worker, checking presence of all/resolve
     863    if (typeof globalScope.Promise.all !== 'function') {
     864      globalScope.Promise.all = function (iterable) {
     865        var count = 0, results = [], resolve, reject;
     866        var promise = new globalScope.Promise(function (resolve_, reject_) {
     867          resolve = resolve_;
     868          reject = reject_;
     869        });
     870        iterable.forEach(function (p, i) {
     871          count++;
     872          p.then(function (result) {
     873            results[i] = result;
     874            count--;
     875            if (count === 0) {
     876              resolve(results);
     877            }
     878          }, reject);
     879        });
     880        if (count === 0) {
     881          resolve(results);
     882        }
     883        return promise;
     884      };
     885    }
     886    if (typeof globalScope.Promise.resolve !== 'function') {
     887      globalScope.Promise.resolve = function (x) {
     888        return new globalScope.Promise(function (resolve) { resolve(x); });
     889      };
     890    }
     891    return;
     892  }
    2016893  var STATUS_PENDING = 0;
    2017894  var STATUS_RESOLVED = 1;
     
    2046923
    2047924    runHandlers: function runHandlers() {
     925      var RUN_TIMEOUT = 1; // ms
     926      var timeoutAt = Date.now() + RUN_TIMEOUT;
    2048927      while (this.handlers.length > 0) {
    2049928        var handler = this.handlers.shift();
     
    2071950
    2072951        handler.nextPromise._updateStatus(nextStatus, nextValue);
     952        if (Date.now() >= timeoutAt) {
     953          break;
     954        }
     955      }
     956
     957      if (this.handlers.length > 0) {
     958        setTimeout(this.runHandlers.bind(this), 0);
     959        return;
    2073960      }
    2074961
     
    2104991        for (var i = 0; i < this.unhandledRejections.length; i++) {
    2105992          if (now - this.unhandledRejections[i].time > REJECTION_TIMEOUT) {
    2106             console.error('Unhandled rejection: ' +
    2107                           this.unhandledRejections[i].promise._value);
     993            var unhandled = this.unhandledRejections[i].promise._value;
     994            var msg = 'Unhandled rejection: ' + unhandled;
     995            if (unhandled.stack) {
     996              msg += '\n' + unhandled.stack;
     997            }
     998            warn(msg);
    2108999            this.unhandledRejections.splice(i);
    21091000            i--;
     
    21171008  };
    21181009
    2119   function Promise() {
     1010  function Promise(resolver) {
    21201011    this._status = STATUS_PENDING;
    21211012    this._handlers = [];
     1013    resolver.call(this, this._resolve.bind(this), this._reject.bind(this));
    21221014  }
    21231015  /**
    21241016   * Builds a promise that is resolved when all the passed in promises are
    21251017   * resolved.
    2126    * @param {Promise[]} promises Array of promises to wait for.
     1018   * @param {array} array of data and/or promises to wait for.
    21271019   * @return {Promise} New dependant promise.
    21281020   */
    21291021  Promise.all = function Promise_all(promises) {
    2130     var deferred = new Promise();
     1022    var resolveAll, rejectAll;
     1023    var deferred = new Promise(function (resolve, reject) {
     1024      resolveAll = resolve;
     1025      rejectAll = reject;
     1026    });
    21311027    var unresolved = promises.length;
    21321028    var results = [];
    21331029    if (unresolved === 0) {
    2134       deferred.resolve(results);
     1030      resolveAll(results);
    21351031      return deferred;
    21361032    }
     
    21401036      }
    21411037      results = [];
    2142       deferred.reject(reason);
     1038      rejectAll(reason);
    21431039    }
    21441040    for (var i = 0, ii = promises.length; i < ii; ++i) {
    21451041      var promise = promises[i];
    2146       promise.then((function(i) {
     1042      var resolve = (function(i) {
    21471043        return function(value) {
    21481044          if (deferred._status === STATUS_REJECTED) {
     
    21521048          unresolved--;
    21531049          if (unresolved === 0)
    2154             deferred.resolve(results);
     1050            resolveAll(results);
    21551051        };
    2156       })(i), reject);
     1052      })(i);
     1053      if (Promise.isPromise(promise)) {
     1054        promise.then(resolve, reject);
     1055      } else {
     1056        resolve(promise);
     1057      }
    21571058    }
    21581059    return deferred;
     1060  };
     1061
     1062  /**
     1063   * Checks if the value is likely a promise (has a 'then' function).
     1064   * @return {boolean} true if x is thenable
     1065   */
     1066  Promise.isPromise = function Promise_isPromise(value) {
     1067    return value && typeof value.then === 'function';
     1068  };
     1069  /**
     1070   * Creates resolved promise
     1071   * @param x resolve value
     1072   * @returns {Promise}
     1073   */
     1074  Promise.resolve = function Promise_resolve(x) {
     1075    return new Promise(function (resolve) { resolve(x); });
    21591076  };
    21601077
     
    21721089
    21731090      if (status == STATUS_RESOLVED &&
    2174           value && typeof(value.then) === 'function') {
     1091          Promise.isPromise(value)) {
    21751092        value.then(this._updateStatus.bind(this, STATUS_RESOLVED),
    21761093                   this._updateStatus.bind(this, STATUS_REJECTED));
     
    21891106    },
    21901107
    2191     get isResolved() {
    2192       return this._status === STATUS_RESOLVED;
    2193     },
    2194 
    2195     get isRejected() {
    2196       return this._status === STATUS_REJECTED;
    2197     },
    2198 
    2199     resolve: function Promise_resolve(value) {
     1108    _resolve: function Promise_resolve(value) {
    22001109      this._updateStatus(STATUS_RESOLVED, value);
    22011110    },
    22021111
    2203     reject: function Promise_reject(reason) {
     1112    _reject: function Promise_reject(reason) {
    22041113      this._updateStatus(STATUS_REJECTED, reason);
    22051114    },
    22061115
    22071116    then: function Promise_then(onResolve, onReject) {
    2208       var nextPromise = new Promise();
     1117      var nextPromise = new Promise(function (resolve, reject) {
     1118        this.resolve = reject;
     1119        this.reject = reject;
     1120      });
    22091121      this._handlers.push({
    22101122        thisPromise: this,
     
    22181130  };
    22191131
    2220   return Promise;
     1132  globalScope.Promise = Promise;
    22211133})();
    22221134
     
    22371149        return;
    22381150      if (name in this.started)
    2239         throw 'Timer is already running for ' + name;
     1151        warn('Timer is already running for ' + name);
    22401152      this.started[name] = Date.now();
    22411153    },
     
    22441156        return;
    22451157      if (!(name in this.started))
    2246         throw 'Timer has not been started for ' + name;
     1158        warn('Timer has not been started for ' + name);
    22471159      this.times.push({
    22481160        'name': name,
     
    22751187
    22761188PDFJS.createBlob = function createBlob(data, contentType) {
    2277   if (typeof Blob === 'function')
     1189  if (typeof Blob !== 'undefined')
    22781190    return new Blob([data], { type: contentType });
    22791191  // Blob builder is deprecated in FF14 and removed in FF18.
     
    22831195};
    22841196
    2285 
    2286 /**
    2287  * This is the main entry point for loading a PDF and interacting with it.
    2288  * NOTE: If a URL is used to fetch the PDF data a standard XMLHttpRequest(XHR)
    2289  * is used, which means it must follow the same origin rules that any XHR does
    2290  * e.g. No cross domain requests without CORS.
    2291  *
    2292  * @param {string|TypedAray|object} source Can be an url to where a PDF is
    2293  * located, a typed array (Uint8Array) already populated with data or
    2294  * and parameter object with the following possible fields:
    2295  *  - url   - The URL of the PDF.
    2296  *  - data  - A typed array with PDF data.
    2297  *  - httpHeaders - Basic authentication headers.
    2298  *  - password - For decrypting password-protected PDFs.
    2299  *
    2300  * @param {object} pdfDataRangeTransport is optional. It is used if you want
    2301  * to manually serve range requests for data in the PDF. See viewer.js for
    2302  * an example of pdfDataRangeTransport's interface.
    2303  *
    2304  * @param {function} passwordCallback is optional. It is used to request a
    2305  * password if wrong or no password was provided. The callback receives two
    2306  * parameters: function that needs to be called with new password and reason
    2307  * (see {PasswordResponses}).
    2308  *
    2309  * @return {Promise} A promise that is resolved with {PDFDocumentProxy} object.
    2310  */
    2311 PDFJS.getDocument = function getDocument(source,
    2312                                          pdfDataRangeTransport,
    2313                                          passwordCallback,
    2314                                          progressCallback) {
    2315   var workerInitializedPromise, workerReadyPromise, transport;
    2316 
    2317   if (typeof source === 'string') {
    2318     source = { url: source };
    2319   } else if (isArrayBuffer(source)) {
    2320     source = { data: source };
    2321   } else if (typeof source !== 'object') {
    2322     error('Invalid parameter in getDocument, need either Uint8Array, ' +
    2323           'string or a parameter object');
    2324   }
    2325 
    2326   if (!source.url && !source.data)
    2327     error('Invalid parameter array, need either .data or .url');
    2328 
    2329   // copy/use all keys as is except 'url' -- full path is required
    2330   var params = {};
    2331   for (var key in source) {
    2332     if (key === 'url' && typeof window !== 'undefined') {
    2333       params[key] = combineUrl(window.location.href, source[key]);
    2334       continue;
    2335     }
    2336     params[key] = source[key];
    2337   }
    2338 
    2339   workerInitializedPromise = new PDFJS.Promise();
    2340   workerReadyPromise = new PDFJS.Promise();
    2341   transport = new WorkerTransport(workerInitializedPromise,
    2342       workerReadyPromise, pdfDataRangeTransport, progressCallback);
    2343   workerInitializedPromise.then(function transportInitialized() {
    2344     transport.passwordCallback = passwordCallback;
    2345     transport.fetchDocument(params);
    2346   });
    2347   return workerReadyPromise;
    2348 };
    2349 
    2350 /**
    2351  * Proxy to a PDFDocument in the worker thread. Also, contains commonly used
    2352  * properties that can be read synchronously.
    2353  */
    2354 var PDFDocumentProxy = (function PDFDocumentProxyClosure() {
    2355   function PDFDocumentProxy(pdfInfo, transport) {
    2356     this.pdfInfo = pdfInfo;
    2357     this.transport = transport;
    2358   }
    2359   PDFDocumentProxy.prototype = {
    2360     /**
    2361      * @return {number} Total number of pages the PDF contains.
    2362      */
    2363     get numPages() {
    2364       return this.pdfInfo.numPages;
    2365     },
    2366     /**
    2367      * @return {string} A unique ID to identify a PDF. Not guaranteed to be
    2368      * unique.
    2369      */
    2370     get fingerprint() {
    2371       return this.pdfInfo.fingerprint;
    2372     },
    2373     /**
    2374      * @return {boolean} true if embedded document fonts are in use. Will be
    2375      * set during rendering of the pages.
    2376      */
    2377     get embeddedFontsUsed() {
    2378       return this.transport.embeddedFontsUsed;
    2379     },
    2380     /**
    2381      * @param {number} The page number to get. The first page is 1.
    2382      * @return {Promise} A promise that is resolved with a {PDFPageProxy}
    2383      * object.
    2384      */
    2385     getPage: function PDFDocumentProxy_getPage(number) {
    2386       return this.transport.getPage(number);
    2387     },
    2388     /**
    2389      * @return {Promise} A promise that is resolved with a lookup table for
    2390      * mapping named destinations to reference numbers.
    2391      */
    2392     getDestinations: function PDFDocumentProxy_getDestinations() {
    2393       return this.transport.getDestinations();
    2394     },
    2395     /**
    2396      * @return {Promise} A promise that is resolved with an array of all the
    2397      * JavaScript strings in the name tree.
    2398      */
    2399     getJavaScript: function PDFDocumentProxy_getDestinations() {
    2400       var promise = new PDFJS.Promise();
    2401       var js = this.pdfInfo.javaScript;
    2402       promise.resolve(js);
    2403       return promise;
    2404     },
    2405     /**
    2406      * @return {Promise} A promise that is resolved with an {array} that is a
    2407      * tree outline (if it has one) of the PDF. The tree is in the format of:
    2408      * [
    2409      *  {
    2410      *   title: string,
    2411      *   bold: boolean,
    2412      *   italic: boolean,
    2413      *   color: rgb array,
    2414      *   dest: dest obj,
    2415      *   items: array of more items like this
    2416      *  },
    2417      *  ...
    2418      * ].
    2419      */
    2420     getOutline: function PDFDocumentProxy_getOutline() {
    2421       var promise = new PDFJS.Promise();
    2422       var outline = this.pdfInfo.outline;
    2423       promise.resolve(outline);
    2424       return promise;
    2425     },
    2426     /**
    2427      * @return {Promise} A promise that is resolved with an {object} that has
    2428      * info and metadata properties.  Info is an {object} filled with anything
    2429      * available in the information dictionary and similarly metadata is a
    2430      * {Metadata} object with information from the metadata section of the PDF.
    2431      */
    2432     getMetadata: function PDFDocumentProxy_getMetadata() {
    2433       var promise = new PDFJS.Promise();
    2434       var info = this.pdfInfo.info;
    2435       var metadata = this.pdfInfo.metadata;
    2436       promise.resolve({
    2437         info: info,
    2438         metadata: metadata ? new PDFJS.Metadata(metadata) : null
    2439       });
    2440       return promise;
    2441     },
    2442     isEncrypted: function PDFDocumentProxy_isEncrypted() {
    2443       var promise = new PDFJS.Promise();
    2444       promise.resolve(this.pdfInfo.encrypted);
    2445       return promise;
    2446     },
    2447     /**
    2448      * @return {Promise} A promise that is resolved with a TypedArray that has
    2449      * the raw data from the PDF.
    2450      */
    2451     getData: function PDFDocumentProxy_getData() {
    2452       var promise = new PDFJS.Promise();
    2453       this.transport.getData(promise);
    2454       return promise;
    2455     },
    2456     /**
    2457      * @return {Promise} A promise that is resolved when the document's data
    2458      * is loaded
    2459      */
    2460     dataLoaded: function PDFDocumentProxy_dataLoaded() {
    2461       return this.transport.dataLoaded();
    2462     },
    2463     destroy: function PDFDocumentProxy_destroy() {
    2464       this.transport.destroy();
    2465     }
     1197PDFJS.createObjectURL = (function createObjectURLClosure() {
     1198  // Blob/createObjectURL is not available, falling back to data schema.
     1199  var digits =
     1200    'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
     1201
     1202  return function createObjectURL(data, contentType) {
     1203    if (!PDFJS.disableCreateObjectURL &&
     1204        typeof URL !== 'undefined' && URL.createObjectURL) {
     1205      var blob = PDFJS.createBlob(data, contentType);
     1206      return URL.createObjectURL(blob);
     1207    }
     1208
     1209    var buffer = 'data:' + contentType + ';base64,';
     1210    for (var i = 0, ii = data.length; i < ii; i += 3) {
     1211      var b1 = data[i] & 0xFF;
     1212      var b2 = data[i + 1] & 0xFF;
     1213      var b3 = data[i + 2] & 0xFF;
     1214      var d1 = b1 >> 2, d2 = ((b1 & 3) << 4) | (b2 >> 4);
     1215      var d3 = i + 1 < ii ? ((b2 & 0xF) << 2) | (b3 >> 6) : 64;
     1216      var d4 = i + 2 < ii ? (b3 & 0x3F) : 64;
     1217      buffer += digits[d1] + digits[d2] + digits[d3] + digits[d4];
     1218    }
     1219    return buffer;
    24661220  };
    2467   return PDFDocumentProxy;
    24681221})();
    24691222
    2470 var PDFPageProxy = (function PDFPageProxyClosure() {
    2471   function PDFPageProxy(pageInfo, transport) {
    2472     this.pageInfo = pageInfo;
    2473     this.transport = transport;
    2474     this.stats = new StatTimer();
    2475     this.stats.enabled = !!globalScope.PDFJS.enableStats;
    2476     this.commonObjs = transport.commonObjs;
    2477     this.objs = new PDFObjects();
    2478     this.renderInProgress = false;
    2479     this.cleanupAfterRender = false;
    2480   }
    2481   PDFPageProxy.prototype = {
    2482     /**
    2483      * @return {number} Page number of the page. First page is 1.
    2484      */
    2485     get pageNumber() {
    2486       return this.pageInfo.pageIndex + 1;
    2487     },
    2488     /**
    2489      * @return {number} The number of degrees the page is rotated clockwise.
    2490      */
    2491     get rotate() {
    2492       return this.pageInfo.rotate;
    2493     },
    2494     /**
    2495      * @return {object} The reference that points to this page. It has 'num' and
    2496      * 'gen' properties.
    2497      */
    2498     get ref() {
    2499       return this.pageInfo.ref;
    2500     },
    2501     /**
    2502      * @return {array} An array of the visible portion of the PDF page in the
    2503      * user space units - [x1, y1, x2, y2].
    2504      */
    2505     get view() {
    2506       return this.pageInfo.view;
    2507     },
    2508     /**
    2509      * @param {number} scale The desired scale of the viewport.
    2510      * @param {number} rotate Degrees to rotate the viewport. If omitted this
    2511      * defaults to the page rotation.
    2512      * @return {PageViewport} Contains 'width' and 'height' properties along
    2513      * with transforms required for rendering.
    2514      */
    2515     getViewport: function PDFPageProxy_getViewport(scale, rotate) {
    2516       if (arguments.length < 2)
    2517         rotate = this.rotate;
    2518       return new PDFJS.PageViewport(this.view, scale, rotate, 0, 0);
    2519     },
    2520     /**
    2521      * @return {Promise} A promise that is resolved with an {array} of the
    2522      * annotation objects.
    2523      */
    2524     getAnnotations: function PDFPageProxy_getAnnotations() {
    2525       if (this.annotationsPromise)
    2526         return this.annotationsPromise;
    2527 
    2528       var promise = new PDFJS.Promise();
    2529       this.annotationsPromise = promise;
    2530       this.transport.getAnnotations(this.pageInfo.pageIndex);
    2531       return promise;
    2532     },
    2533     /**
    2534      * Begins the process of rendering a page to the desired context.
    2535      * @param {object} params A parameter object that supports:
    2536      * {
    2537      *   canvasContext(required): A 2D context of a DOM Canvas object.,
    2538      *   textLayer(optional): An object that has beginLayout, endLayout, and
    2539      *                        appendText functions.,
    2540      *   imageLayer(optional): An object that has beginLayout, endLayout and
    2541      *                         appendImage functions.,
    2542      *   continueCallback(optional): A function that will be called each time
    2543      *                               the rendering is paused.  To continue
    2544      *                               rendering call the function that is the
    2545      *                               first argument to the callback.
    2546      * }.
    2547      * @return {Promise} A promise that is resolved when the page finishes
    2548      * rendering.
    2549      */
    2550     render: function PDFPageProxy_render(params) {
    2551       this.renderInProgress = true;
    2552 
    2553       var promise = new Promise();
    2554       var stats = this.stats;
    2555       stats.time('Overall');
    2556       // If there is no displayReadyPromise yet, then the operatorList was never
    2557       // requested before. Make the request and create the promise.
    2558       if (!this.displayReadyPromise) {
    2559         this.displayReadyPromise = new Promise();
    2560         this.destroyed = false;
    2561 
    2562         this.stats.time('Page Request');
    2563         this.transport.messageHandler.send('RenderPageRequest', {
    2564           pageIndex: this.pageNumber - 1
     1223function MessageHandler(name, comObj) {
     1224  this.name = name;
     1225  this.comObj = comObj;
     1226  this.callbackIndex = 1;
     1227  this.postMessageTransfers = true;
     1228  var callbacks = this.callbacks = {};
     1229  var ah = this.actionHandler = {};
     1230
     1231  ah['console_log'] = [function ahConsoleLog(data) {
     1232    console.log.apply(console, data);
     1233  }];
     1234  ah['console_error'] = [function ahConsoleError(data) {
     1235    console.error.apply(console, data);
     1236  }];
     1237  ah['_unsupported_feature'] = [function ah_unsupportedFeature(data) {
     1238    UnsupportedManager.notify(data);
     1239  }];
     1240
     1241  comObj.onmessage = function messageHandlerComObjOnMessage(event) {
     1242    var data = event.data;
     1243    if (data.isReply) {
     1244      var callbackId = data.callbackId;
     1245      if (data.callbackId in callbacks) {
     1246        var callback = callbacks[callbackId];
     1247        delete callbacks[callbackId];
     1248        callback(data.data);
     1249      } else {
     1250        error('Cannot resolve callback ' + callbackId);
     1251      }
     1252    } else if (data.action in ah) {
     1253      var action = ah[data.action];
     1254      if (data.callbackId) {
     1255        var deferred = {};
     1256        var promise = new Promise(function (resolve, reject) {
     1257          deferred.resolve = resolve;
     1258          deferred.reject = reject;
    25651259        });
    2566       }
    2567 
    2568       var self = this;
    2569       function complete(error) {
    2570         self.renderInProgress = false;
    2571         if (self.destroyed || self.cleanupAfterRender) {
    2572           delete self.displayReadyPromise;
    2573           delete self.operatorList;
    2574           self.objs.clear();
    2575         }
    2576 
    2577         if (error)
    2578           promise.reject(error);
    2579         else
    2580           promise.resolve();
    2581       }
    2582       var continueCallback = params.continueCallback;
    2583 
    2584       // Once the operatorList and fonts are loaded, do the actual rendering.
    2585       this.displayReadyPromise.then(
    2586         function pageDisplayReadyPromise() {
    2587           if (self.destroyed) {
    2588             complete();
    2589             return;
    2590           }
    2591 
    2592           var gfx = new CanvasGraphics(params.canvasContext, this.commonObjs,
    2593             this.objs, params.textLayer, params.imageLayer);
    2594           try {
    2595             this.display(gfx, params.viewport, complete, continueCallback);
    2596           } catch (e) {
    2597             complete(e);
    2598           }
    2599         }.bind(this),
    2600         function pageDisplayReadPromiseError(reason) {
    2601           complete(reason);
    2602         }
    2603       );
    2604 
    2605       return promise;
    2606     },
    2607     /**
    2608      * For internal use only.
    2609      */
    2610     startRenderingFromOperatorList:
    2611       function PDFPageProxy_startRenderingFromOperatorList(operatorList,
    2612                                                            fonts) {
    2613       var self = this;
    2614       this.operatorList = operatorList;
    2615 
    2616       this.ensureFonts(fonts,
    2617         function pageStartRenderingFromOperatorListEnsureFonts() {
    2618           self.displayReadyPromise.resolve();
    2619         }
    2620       );
    2621     },
    2622     /**
    2623      * For internal use only.
    2624      */
    2625     ensureFonts: function PDFPageProxy_ensureFonts(fonts, callback) {
    2626       this.stats.time('Font Loading');
    2627       // Convert the font names to the corresponding font obj.
    2628       var fontObjs = [];
    2629       for (var i = 0, ii = fonts.length; i < ii; i++) {
    2630         var obj = this.commonObjs.getData(fonts[i]);
    2631         if (obj.error) {
    2632           warn('Error during font loading: ' + obj.error);
    2633           continue;
    2634         }
    2635         if (!obj.coded) {
    2636           this.transport.embeddedFontsUsed = true;
    2637         }
    2638         fontObjs.push(obj);
    2639       }
    2640 
    2641       // Load all the fonts
    2642       FontLoader.bind(
    2643         fontObjs,
    2644         function pageEnsureFontsFontObjs(fontObjs) {
    2645           this.stats.timeEnd('Font Loading');
    2646 
    2647           callback.call(this);
    2648         }.bind(this)
    2649       );
    2650     },
    2651     /**
    2652      * For internal use only.
    2653      */
    2654     display: function PDFPageProxy_display(gfx, viewport, callback,
    2655                                            continueCallback) {
    2656       var stats = this.stats;
    2657       stats.time('Rendering');
    2658 
    2659       var operatorList = this.operatorList;
    2660       gfx.beginDrawing(viewport, operatorList.transparency);
    2661 
    2662       var startIdx = 0;
    2663       var length = operatorList.fnArray.length;
    2664       var stepper = null;
    2665       if (PDFJS.pdfBug && 'StepperManager' in globalScope &&
    2666           globalScope['StepperManager'].enabled) {
    2667         stepper = globalScope['StepperManager'].create(this.pageNumber - 1);
    2668         stepper.init(operatorList);
    2669         stepper.nextBreakPoint = stepper.getNextBreakPoint();
    2670       }
    2671 
    2672       var continueWrapper;
    2673       if (continueCallback)
    2674         continueWrapper = function() { continueCallback(next); };
    2675       else
    2676         continueWrapper = next;
    2677 
    2678       var self = this;
    2679       function next() {
    2680         startIdx = gfx.executeOperatorList(operatorList, startIdx,
    2681                                            continueWrapper, stepper);
    2682         if (startIdx == length) {
    2683           gfx.endDrawing();
    2684           stats.timeEnd('Rendering');
    2685           stats.timeEnd('Overall');
    2686           if (callback) callback();
    2687         }
    2688       }
    2689       continueWrapper();
    2690     },
    2691     /**
    2692      * @return {Promise} That is resolved with the a {string} that is the text
    2693      * content from the page.
    2694      */
    2695     getTextContent: function PDFPageProxy_getTextContent() {
    2696       var promise = new PDFJS.Promise();
    2697       this.transport.messageHandler.send('GetTextContent', {
    2698           pageIndex: this.pageNumber - 1
    2699         },
    2700         function textContentCallback(textContent) {
    2701           promise.resolve(textContent);
    2702         }
    2703       );
    2704       return promise;
    2705     },
    2706     /**
    2707      * Stub for future feature.
    2708      */
    2709     getOperationList: function PDFPageProxy_getOperationList() {
    2710       var promise = new PDFJS.Promise();
    2711       var operationList = { // not implemented
    2712         dependencyFontsID: null,
    2713         operatorList: null
    2714       };
    2715       promise.resolve(operationList);
    2716       return promise;
    2717     },
    2718     /**
    2719      * Destroys resources allocated by the page.
    2720      */
    2721     destroy: function PDFPageProxy_destroy() {
    2722       this.destroyed = true;
    2723 
    2724       if (!this.renderInProgress) {
    2725         delete this.operatorList;
    2726         delete this.displayReadyPromise;
    2727         this.objs.clear();
    2728       }
    2729     }
    2730   };
    2731   return PDFPageProxy;
    2732 })();
    2733 /**
    2734  * For internal use only.
    2735  */
    2736 var WorkerTransport = (function WorkerTransportClosure() {
    2737   function WorkerTransport(workerInitializedPromise, workerReadyPromise,
    2738       pdfDataRangeTransport, progressCallback) {
    2739     this.pdfDataRangeTransport = pdfDataRangeTransport;
    2740 
    2741     this.workerReadyPromise = workerReadyPromise;
    2742     this.progressCallback = progressCallback;
    2743     this.commonObjs = new PDFObjects();
    2744 
    2745     this.pageCache = [];
    2746     this.pagePromises = [];
    2747     this.embeddedFontsUsed = false;
    2748 
    2749     this.passwordCallback = null;
    2750 
    2751     // If worker support isn't disabled explicit and the browser has worker
    2752     // support, create a new web worker and test if it/the browser fullfills
    2753     // all requirements to run parts of pdf.js in a web worker.
    2754     // Right now, the requirement is, that an Uint8Array is still an Uint8Array
    2755     // as it arrives on the worker. Chrome added this with version 15.
    2756     if (!globalScope.PDFJS.disableWorker && typeof Worker !== 'undefined') {
    2757       var workerSrc = PDFJS.workerSrc;
    2758       if (typeof workerSrc === 'undefined') {
    2759         error('No PDFJS.workerSrc specified');
    2760       }
    2761 
    2762       try {
    2763         // Some versions of FF can't create a worker on localhost, see:
    2764         // https://bugzilla.mozilla.org/show_bug.cgi?id=683280
    2765         var worker = new Worker(workerSrc);
    2766         var messageHandler = new MessageHandler('main', worker);
    2767         this.messageHandler = messageHandler;
    2768 
    2769         messageHandler.on('test', function transportTest(supportTypedArray) {
    2770           if (supportTypedArray) {
    2771             this.worker = worker;
    2772             this.setupMessageHandler(messageHandler);
    2773           } else {
    2774             globalScope.PDFJS.disableWorker = true;
    2775             this.setupFakeWorker();
    2776           }
    2777           workerInitializedPromise.resolve();
    2778         }.bind(this));
    2779 
    2780         var testObj = new Uint8Array(1);
    2781         // Some versions of Opera throw a DATA_CLONE_ERR on
    2782         // serializing the typed array.
    2783         messageHandler.send('test', testObj);
    2784         return;
    2785       } catch (e) {
    2786         info('The worker has been disabled.');
    2787       }
    2788     }
    2789     // Either workers are disabled, not supported or have thrown an exception.
    2790     // Thus, we fallback to a faked worker.
    2791     globalScope.PDFJS.disableWorker = true;
    2792     this.setupFakeWorker();
    2793     workerInitializedPromise.resolve();
    2794   }
    2795   WorkerTransport.prototype = {
    2796     destroy: function WorkerTransport_destroy() {
    2797       this.pageCache = [];
    2798       this.pagePromises = [];
    2799       var self = this;
    2800       this.messageHandler.send('Terminate', null, function () {
    2801         if (self.worker) {
    2802           self.worker.terminate();
    2803         }
    2804       });
    2805     },
    2806     setupFakeWorker: function WorkerTransport_setupFakeWorker() {
    2807       warn('Setting up fake worker.');
    2808       // If we don't use a worker, just post/sendMessage to the main thread.
    2809       var fakeWorker = {
    2810         postMessage: function WorkerTransport_postMessage(obj) {
    2811           fakeWorker.onmessage({data: obj});
    2812         },
    2813         terminate: function WorkerTransport_terminate() {}
    2814       };
    2815 
    2816       var messageHandler = new MessageHandler('main', fakeWorker);
    2817       this.setupMessageHandler(messageHandler);
    2818 
    2819       // If the main thread is our worker, setup the handling for the messages
    2820       // the main thread sends to it self.
    2821       WorkerMessageHandler.setup(messageHandler);
    2822     },
    2823 
    2824     setupMessageHandler:
    2825       function WorkerTransport_setupMessageHandler(messageHandler) {
    2826       this.messageHandler = messageHandler;
    2827 
    2828       function updatePassword(password) {
    2829         messageHandler.send('UpdatePassword', password);
    2830       }
    2831 
    2832       var pdfDataRangeTransport = this.pdfDataRangeTransport;
    2833       if (pdfDataRangeTransport) {
    2834         pdfDataRangeTransport.addRangeListener(function(begin, chunk) {
    2835           messageHandler.send('OnDataRange', {
    2836             begin: begin,
    2837             chunk: chunk
     1260        deferred.promise = promise;
     1261        promise.then(function(resolvedData) {
     1262          comObj.postMessage({
     1263            isReply: true,
     1264            callbackId: data.callbackId,
     1265            data: resolvedData
    28381266          });
    28391267        });
    2840 
    2841         pdfDataRangeTransport.addProgressListener(function(loaded) {
    2842           messageHandler.send('OnDataProgress', {
    2843             loaded: loaded
    2844           });
    2845         });
    2846 
    2847         messageHandler.on('RequestDataRange',
    2848           function transportDataRange(data) {
    2849             pdfDataRangeTransport.requestDataRange(data.begin, data.end);
    2850           }, this);
    2851       }
    2852 
    2853       messageHandler.on('GetDoc', function transportDoc(data) {
    2854         var pdfInfo = data.pdfInfo;
    2855         var pdfDocument = new PDFDocumentProxy(pdfInfo, this);
    2856         this.pdfDocument = pdfDocument;
    2857         this.workerReadyPromise.resolve(pdfDocument);
    2858       }, this);
    2859 
    2860       messageHandler.on('NeedPassword', function transportPassword(data) {
    2861         if (this.passwordCallback) {
    2862           return this.passwordCallback(updatePassword,
    2863                                        PasswordResponses.NEED_PASSWORD);
    2864         }
    2865         this.workerReadyPromise.reject(data.exception.message, data.exception);
    2866       }, this);
    2867 
    2868       messageHandler.on('IncorrectPassword', function transportBadPass(data) {
    2869         if (this.passwordCallback) {
    2870           return this.passwordCallback(updatePassword,
    2871                                        PasswordResponses.INCORRECT_PASSWORD);
    2872         }
    2873         this.workerReadyPromise.reject(data.exception.message, data.exception);
    2874       }, this);
    2875 
    2876       messageHandler.on('InvalidPDF', function transportInvalidPDF(data) {
    2877         this.workerReadyPromise.reject(data.exception.name, data.exception);
    2878       }, this);
    2879 
    2880       messageHandler.on('MissingPDF', function transportMissingPDF(data) {
    2881         this.workerReadyPromise.reject(data.exception.message, data.exception);
    2882       }, this);
    2883 
    2884       messageHandler.on('UnknownError', function transportUnknownError(data) {
    2885         this.workerReadyPromise.reject(data.exception.message, data.exception);
    2886       }, this);
    2887 
    2888       messageHandler.on('GetPage', function transportPage(data) {
    2889         var pageInfo = data.pageInfo;
    2890         var page = new PDFPageProxy(pageInfo, this);
    2891         this.pageCache[pageInfo.pageIndex] = page;
    2892         var promise = this.pagePromises[pageInfo.pageIndex];
    2893         promise.resolve(page);
    2894       }, this);
    2895 
    2896       messageHandler.on('GetAnnotations', function transportAnnotations(data) {
    2897         var annotations = data.annotations;
    2898         var promise = this.pageCache[data.pageIndex].annotationsPromise;
    2899         promise.resolve(annotations);
    2900       }, this);
    2901 
    2902       messageHandler.on('RenderPage', function transportRender(data) {
    2903         var page = this.pageCache[data.pageIndex];
    2904         var depFonts = data.depFonts;
    2905 
    2906         page.stats.timeEnd('Page Request');
    2907         page.startRenderingFromOperatorList(data.operatorList, depFonts);
    2908       }, this);
    2909 
    2910       messageHandler.on('commonobj', function transportObj(data) {
    2911         var id = data[0];
    2912         var type = data[1];
    2913         if (this.commonObjs.hasData(id))
    2914           return;
    2915 
    2916         switch (type) {
    2917           case 'Font':
    2918             var exportedData = data[2];
    2919 
    2920             // At this point, only the font object is created but the font is
    2921             // not yet attached to the DOM. This is done in `FontLoader.bind`.
    2922             var font;
    2923             if ('error' in exportedData)
    2924               font = new ErrorFont(exportedData.error);
    2925             else
    2926               font = new Font(exportedData);
    2927             this.commonObjs.resolve(id, font);
    2928             break;
    2929           default:
    2930             error('Got unknown common object type ' + type);
    2931         }
    2932       }, this);
    2933 
    2934       messageHandler.on('obj', function transportObj(data) {
    2935         var id = data[0];
    2936         var pageIndex = data[1];
    2937         var type = data[2];
    2938         var pageProxy = this.pageCache[pageIndex];
    2939         if (pageProxy.objs.hasData(id))
    2940           return;
    2941 
    2942         switch (type) {
    2943           case 'JpegStream':
    2944             var imageData = data[3];
    2945             loadJpegStream(id, imageData, pageProxy.objs);
    2946             break;
    2947           case 'Image':
    2948             var imageData = data[3];
    2949             pageProxy.objs.resolve(id, imageData);
    2950 
    2951             // heuristics that will allow not to store large data
    2952             var MAX_IMAGE_SIZE_TO_STORE = 8000000;
    2953             if ('data' in imageData &&
    2954                 imageData.data.length > MAX_IMAGE_SIZE_TO_STORE) {
    2955               pageProxy.cleanupAfterRender = true;
    2956             }
    2957             break;
    2958           default:
    2959             error('Got unknown object type ' + type);
    2960         }
    2961       }, this);
    2962 
    2963       messageHandler.on('DocProgress', function transportDocProgress(data) {
    2964         this.progressCallback({
    2965           loaded: data.loaded,
    2966           total: data.total
    2967         });
    2968       }, this);
    2969 
    2970       messageHandler.on('DocError', function transportDocError(data) {
    2971         this.workerReadyPromise.reject(data);
    2972       }, this);
    2973 
    2974       messageHandler.on('PageError', function transportError(data) {
    2975         var page = this.pageCache[data.pageNum - 1];
    2976         if (page.displayReadyPromise)
    2977           page.displayReadyPromise.reject(data.error);
    2978         else
    2979           error(data.error);
    2980       }, this);
    2981 
    2982       messageHandler.on('JpegDecode', function(data, promise) {
    2983         var imageData = data[0];
    2984         var components = data[1];
    2985         if (components != 3 && components != 1)
    2986           error('Only 3 component or 1 component can be returned');
    2987 
    2988         var img = new Image();
    2989         img.onload = (function messageHandler_onloadClosure() {
    2990           var width = img.width;
    2991           var height = img.height;
    2992           var size = width * height;
    2993           var rgbaLength = size * 4;
    2994           var buf = new Uint8Array(size * components);
    2995           var tmpCanvas = createScratchCanvas(width, height);
    2996           var tmpCtx = tmpCanvas.getContext('2d');
    2997           tmpCtx.drawImage(img, 0, 0);
    2998           var data = tmpCtx.getImageData(0, 0, width, height).data;
    2999 
    3000           if (components == 3) {
    3001             for (var i = 0, j = 0; i < rgbaLength; i += 4, j += 3) {
    3002               buf[j] = data[i];
    3003               buf[j + 1] = data[i + 1];
    3004               buf[j + 2] = data[i + 2];
    3005             }
    3006           } else if (components == 1) {
    3007             for (var i = 0, j = 0; i < rgbaLength; i += 4, j++) {
    3008               buf[j] = data[i];
    3009             }
    3010           }
    3011           promise.resolve({ data: buf, width: width, height: height});
    3012         }).bind(this);
    3013         var src = 'data:image/jpeg;base64,' + window.btoa(imageData);
    3014         img.src = src;
    3015       });
    3016     },
    3017 
    3018     fetchDocument: function WorkerTransport_fetchDocument(source) {
    3019       source.disableAutoFetch = PDFJS.disableAutoFetch;
    3020       source.chunkedViewerLoading = !!this.pdfDataRangeTransport;
    3021       this.messageHandler.send('GetDocRequest', {
    3022         source: source,
    3023         disableRange: PDFJS.disableRange
    3024       });
    3025     },
    3026 
    3027     getData: function WorkerTransport_getData(promise) {
    3028       this.messageHandler.send('GetData', null, function(data) {
    3029         promise.resolve(data);
    3030       });
    3031     },
    3032 
    3033     dataLoaded: function WorkerTransport_dataLoaded() {
    3034       var promise = new PDFJS.Promise();
    3035       this.messageHandler.send('DataLoaded', null, function(args) {
    3036         promise.resolve(args);
    3037       });
    3038       return promise;
    3039     },
    3040 
    3041     getPage: function WorkerTransport_getPage(pageNumber, promise) {
    3042       var pageIndex = pageNumber - 1;
    3043       if (pageIndex in this.pagePromises)
    3044         return this.pagePromises[pageIndex];
    3045       var promise = new PDFJS.Promise('Page ' + pageNumber);
    3046       this.pagePromises[pageIndex] = promise;
    3047       this.messageHandler.send('GetPageRequest', { pageIndex: pageIndex });
    3048       return promise;
    3049     },
    3050 
    3051     getAnnotations: function WorkerTransport_getAnnotations(pageIndex) {
    3052       this.messageHandler.send('GetAnnotationsRequest',
    3053         { pageIndex: pageIndex });
    3054     },
    3055 
    3056     getDestinations: function WorkerTransport_getDestinations() {
    3057       var promise = new PDFJS.Promise();
    3058       this.messageHandler.send('GetDestinations', null,
    3059         function transportDestinations(destinations) {
    3060           promise.resolve(destinations);
    3061         }
    3062       );
    3063       return promise;
     1268        action[0].call(action[1], data.data, deferred);
     1269      } else {
     1270        action[0].call(action[1], data.data);
     1271      }
     1272    } else {
     1273      error('Unkown action from worker: ' + data.action);
    30641274    }
    30651275  };
    3066   return WorkerTransport;
    3067 
    3068 })();
    3069 
    3070 
    3071 // <canvas> contexts store most of the state we need natively.
    3072 // However, PDF needs a bit more state, which we store here.
    3073 
    3074 var TextRenderingMode = {
    3075   FILL: 0,
    3076   STROKE: 1,
    3077   FILL_STROKE: 2,
    3078   INVISIBLE: 3,
    3079   FILL_ADD_TO_PATH: 4,
    3080   STROKE_ADD_TO_PATH: 5,
    3081   FILL_STROKE_ADD_TO_PATH: 6,
    3082   ADD_TO_PATH: 7,
    3083   FILL_STROKE_MASK: 3,
    3084   ADD_TO_PATH_FLAG: 4
     1276}
     1277
     1278MessageHandler.prototype = {
     1279  on: function messageHandlerOn(actionName, handler, scope) {
     1280    var ah = this.actionHandler;
     1281    if (ah[actionName]) {
     1282      error('There is already an actionName called "' + actionName + '"');
     1283    }
     1284    ah[actionName] = [handler, scope];
     1285  },
     1286  /**
     1287   * Sends a message to the comObj to invoke the action with the supplied data.
     1288   * @param {String} actionName Action to call.
     1289   * @param {JSON} data JSON data to send.
     1290   * @param {function} [callback] Optional callback that will handle a reply.
     1291   * @param {Array} [transfers] Optional list of transfers/ArrayBuffers
     1292   */
     1293  send: function messageHandlerSend(actionName, data, callback, transfers) {
     1294    var message = {
     1295      action: actionName,
     1296      data: data
     1297    };
     1298    if (callback) {
     1299      var callbackId = this.callbackIndex++;
     1300      this.callbacks[callbackId] = callback;
     1301      message.callbackId = callbackId;
     1302    }
     1303    if (transfers && this.postMessageTransfers) {
     1304      this.comObj.postMessage(message, transfers);
     1305    } else {
     1306      this.comObj.postMessage(message);
     1307    }
     1308  }
    30851309};
    30861310
    3087 // Minimal font size that would be used during canvas fillText operations.
    3088 var MIN_FONT_SIZE = 16;
    3089 
    3090 var COMPILE_TYPE3_GLYPHS = true;
    3091 
    3092 function createScratchCanvas(width, height) {
    3093   var canvas = document.createElement('canvas');
    3094   canvas.width = width;
    3095   canvas.height = height;
    3096   return canvas;
     1311function loadJpegStream(id, imageUrl, objs) {
     1312  var img = new Image();
     1313  img.onload = (function loadJpegStream_onloadClosure() {
     1314    objs.resolve(id, img);
     1315  });
     1316  img.src = imageUrl;
    30971317}
    30981318
    3099 function addContextCurrentTransform(ctx) {
    3100   // If the context doesn't expose a `mozCurrentTransform`, add a JS based on.
    3101   if (!ctx.mozCurrentTransform) {
    3102     // Store the original context
    3103     ctx._scaleX = ctx._scaleX || 1.0;
    3104     ctx._scaleY = ctx._scaleY || 1.0;
    3105     ctx._originalSave = ctx.save;
    3106     ctx._originalRestore = ctx.restore;
    3107     ctx._originalRotate = ctx.rotate;
    3108     ctx._originalScale = ctx.scale;
    3109     ctx._originalTranslate = ctx.translate;
    3110     ctx._originalTransform = ctx.transform;
    3111     ctx._originalSetTransform = ctx.setTransform;
    3112 
    3113     ctx._transformMatrix = [ctx._scaleX, 0, 0, ctx._scaleY, 0, 0];
    3114     ctx._transformStack = [];
    3115 
    3116     Object.defineProperty(ctx, 'mozCurrentTransform', {
    3117       get: function getCurrentTransform() {
    3118         return this._transformMatrix;
    3119       }
    3120     });
    3121 
    3122     Object.defineProperty(ctx, 'mozCurrentTransformInverse', {
    3123       get: function getCurrentTransformInverse() {
    3124         // Calculation done using WolframAlpha:
    3125         // http://www.wolframalpha.com/input/?
    3126         //   i=Inverse+{{a%2C+c%2C+e}%2C+{b%2C+d%2C+f}%2C+{0%2C+0%2C+1}}
    3127 
    3128         var m = this._transformMatrix;
    3129         var a = m[0], b = m[1], c = m[2], d = m[3], e = m[4], f = m[5];
    3130 
    3131         var ad_bc = a * d - b * c;
    3132         var bc_ad = b * c - a * d;
    3133 
    3134         return [
    3135           d / ad_bc,
    3136           b / bc_ad,
    3137           c / bc_ad,
    3138           a / ad_bc,
    3139           (d * e - c * f) / bc_ad,
    3140           (b * e - a * f) / ad_bc
    3141         ];
    3142       }
    3143     });
    3144 
    3145     ctx.save = function ctxSave() {
    3146       var old = this._transformMatrix;
    3147       this._transformStack.push(old);
    3148       this._transformMatrix = old.slice(0, 6);
    3149 
    3150       this._originalSave();
    3151     };
    3152 
    3153     ctx.restore = function ctxRestore() {
    3154       var prev = this._transformStack.pop();
    3155       if (prev) {
    3156         this._transformMatrix = prev;
    3157         this._originalRestore();
    3158       }
    3159     };
    3160 
    3161     ctx.translate = function ctxTranslate(x, y) {
    3162       var m = this._transformMatrix;
    3163       m[4] = m[0] * x + m[2] * y + m[4];
    3164       m[5] = m[1] * x + m[3] * y + m[5];
    3165 
    3166       this._originalTranslate(x, y);
    3167     };
    3168 
    3169     ctx.scale = function ctxScale(x, y) {
    3170       var m = this._transformMatrix;
    3171       m[0] = m[0] * x;
    3172       m[1] = m[1] * x;
    3173       m[2] = m[2] * y;
    3174       m[3] = m[3] * y;
    3175 
    3176       this._originalScale(x, y);
    3177     };
    3178 
    3179     ctx.transform = function ctxTransform(a, b, c, d, e, f) {
    3180       var m = this._transformMatrix;
    3181       this._transformMatrix = [
    3182         m[0] * a + m[2] * b,
    3183         m[1] * a + m[3] * b,
    3184         m[0] * c + m[2] * d,
    3185         m[1] * c + m[3] * d,
    3186         m[0] * e + m[2] * f + m[4],
    3187         m[1] * e + m[3] * f + m[5]
    3188       ];
    3189 
    3190       ctx._originalTransform(a, b, c, d, e, f);
    3191     };
    3192 
    3193     ctx.setTransform = function ctxSetTransform(a, b, c, d, e, f) {
    3194       this._transformMatrix = [a, b, c, d, e, f];
    3195 
    3196       ctx._originalSetTransform(a, b, c, d, e, f);
    3197     };
    3198 
    3199     ctx.rotate = function ctxRotate(angle) {
    3200       var cosValue = Math.cos(angle);
    3201       var sinValue = Math.sin(angle);
    3202 
    3203       var m = this._transformMatrix;
    3204       this._transformMatrix = [
    3205         m[0] * cosValue + m[2] * sinValue,
    3206         m[1] * cosValue + m[3] * sinValue,
    3207         m[0] * (-sinValue) + m[2] * cosValue,
    3208         m[1] * (-sinValue) + m[3] * cosValue,
    3209         m[4],
    3210         m[5]
    3211       ];
    3212 
    3213       this._originalRotate(angle);
    3214     };
    3215   }
    3216 }
    3217 
    3218 var CachedCanvases = (function CachedCanvasesClosure() {
    3219   var cache = {};
    3220   return {
    3221     getCanvas: function CachedCanvases_getCanvas(id, width, height) {
    3222       var canvas;
    3223       if (id in cache) {
    3224         canvas = cache[id];
    3225         canvas.width = width;
    3226         canvas.height = height;
    3227         // reset canvas transform for emulated mozCurrentTransform, if needed
    3228         canvas.getContext('2d').setTransform(1, 0, 0, 1, 0, 0);
    3229       } else {
    3230         canvas = createScratchCanvas(width, height);
    3231         cache[id] = canvas;
    3232       }
    3233       return canvas;
    3234     },
    3235     clear: function () {
    3236       cache = {};
    3237     }
    3238   };
    3239 })();
    3240 
    3241 function compileType3Glyph(imgData) {
    3242   var POINT_TO_PROCESS_LIMIT = 1000;
    3243 
    3244   var width = imgData.width, height = imgData.height;
    3245   var i, j, j0, width1 = width + 1;
    3246   var points = new Uint8Array(width1 * (height + 1));
    3247   var POINT_TYPES =
    3248       new Uint8Array([0, 2, 4, 0, 1, 0, 5, 4, 8, 10, 0, 8, 0, 2, 1, 0]);
    3249   // finding iteresting points: every point is located between mask pixels,
    3250   // so there will be points of the (width + 1)x(height + 1) grid. Every point
    3251   // will have flags assigned based on neighboring mask pixels:
    3252   //   4 | 8
    3253   //   --P--
    3254   //   2 | 1
    3255   // We are interested only in points with the flags:
    3256   //   - outside corners: 1, 2, 4, 8;
    3257   //   - inside corners: 7, 11, 13, 14;
    3258   //   - and, intersections: 5, 10.
    3259   var pos = 3, data = imgData.data, lineSize = width * 4, count = 0;
    3260   if (data[3] !== 0) {
    3261     points[0] = 1;
    3262     ++count;
    3263   }
    3264   for (j = 1; j < width; j++) {
    3265     if (data[pos] !== data[pos + 4]) {
    3266       points[j] = data[pos] ? 2 : 1;
    3267       ++count;
    3268     }
    3269     pos += 4;
    3270   }
    3271   if (data[pos] !== 0) {
    3272     points[j] = 2;
    3273     ++count;
    3274   }
    3275   pos += 4;
    3276   for (i = 1; i < height; i++) {
    3277     j0 = i * width1;
    3278     if (data[pos - lineSize] !== data[pos]) {
    3279       points[j0] = data[pos] ? 1 : 8;
    3280       ++count;
    3281     }
    3282     // 'sum' is the position of the current pixel configuration in the 'TYPES'
    3283     // array (in order 8-1-2-4, so we can use '>>2' to shift the column).
    3284     var sum = (data[pos] ? 4 : 0) + (data[pos - lineSize] ? 8 : 0);
    3285     for (j = 1; j < width; j++) {
    3286       sum = (sum >> 2) + (data[pos + 4] ? 4 : 0) +
    3287             (data[pos - lineSize + 4] ? 8 : 0);
    3288       if (POINT_TYPES[sum]) {
    3289         points[j0 + j] = POINT_TYPES[sum];
    3290         ++count;
    3291       }
    3292       pos += 4;
    3293     }
    3294     if (data[pos - lineSize] !== data[pos]) {
    3295       points[j0 + j] = data[pos] ? 2 : 4;
    3296       ++count;
    3297     }
    3298     pos += 4;
    3299 
    3300     if (count > POINT_TO_PROCESS_LIMIT) {
    3301       return null;
    3302     }
    3303   }
    3304 
    3305   pos -= lineSize;
    3306   j0 = i * width1;
    3307   if (data[pos] !== 0) {
    3308     points[j0] = 8;
    3309     ++count;
    3310   }
    3311   for (j = 1; j < width; j++) {
    3312     if (data[pos] !== data[pos + 4]) {
    3313       points[j0 + j] = data[pos] ? 4 : 8;
    3314       ++count;
    3315     }
    3316     pos += 4;
    3317   }
    3318   if (data[pos] !== 0) {
    3319     points[j0 + j] = 4;
    3320     ++count;
    3321   }
    3322   if (count > POINT_TO_PROCESS_LIMIT) {
    3323     return null;
    3324   }
    3325 
    3326   // building outlines
    3327   var steps = new Int32Array([0, width1, -1, 0, -width1, 0, 0, 0, 1]);
    3328   var outlines = [];
    3329   for (i = 0; count && i <= height; i++) {
    3330     var p = i * width1;
    3331     var end = p + width;
    3332     while (p < end && !points[p]) {
    3333       p++;
    3334     }
    3335     if (p === end) {
    3336       continue;
    3337     }
    3338     var coords = [p % width1, i];
    3339 
    3340     var type = points[p], p0 = p, pp;
    3341     do {
    3342       var step = steps[type];
    3343       do { p += step; } while (!points[p]);
    3344      
    3345       pp = points[p];
    3346       if (pp !== 5 && pp !== 10) {
    3347         // set new direction
    3348         type = pp;
    3349         // delete mark
    3350         points[p] = 0;
    3351       } else { // type is 5 or 10, ie, a crossing
    3352         // set new direction
    3353         type = pp & ((0x33 * type) >> 4);
    3354         // set new type for "future hit"
    3355         points[p] &= (type >> 2 | type << 2);
    3356       }
    3357 
    3358       coords.push(p % width1);
    3359       coords.push((p / width1) | 0);
    3360       --count;
    3361     } while (p0 !== p);
    3362     outlines.push(coords);
    3363     --i;
    3364   }
    3365 
    3366   var drawOutline = function(c) {
    3367     c.save();
    3368     // the path shall be painted in [0..1]x[0..1] space
    3369     c.scale(1 / width, -1 / height);
    3370     c.translate(0, -height);
    3371     c.beginPath();
    3372     for (var i = 0, ii = outlines.length; i < ii; i++) {
    3373       var o = outlines[i];
    3374       c.moveTo(o[0], o[1]);
    3375       for (var j = 2, jj = o.length; j < jj; j += 2) {
    3376         c.lineTo(o[j], o[j+1]);
    3377       }
    3378     }
    3379     c.fill();
    3380     c.beginPath();
    3381     c.restore();
    3382   };
    3383  
    3384   return drawOutline;
    3385 }
    3386 
    3387 var CanvasExtraState = (function CanvasExtraStateClosure() {
    3388   function CanvasExtraState(old) {
    3389     // Are soft masks and alpha values shapes or opacities?
    3390     this.alphaIsShape = false;
    3391     this.fontSize = 0;
    3392     this.fontSizeScale = 1;
    3393     this.textMatrix = IDENTITY_MATRIX;
    3394     this.fontMatrix = FONT_IDENTITY_MATRIX;
    3395     this.leading = 0;
    3396     // Current point (in user coordinates)
    3397     this.x = 0;
    3398     this.y = 0;
    3399     // Start of text line (in text coordinates)
    3400     this.lineX = 0;
    3401     this.lineY = 0;
    3402     // Character and word spacing
    3403     this.charSpacing = 0;
    3404     this.wordSpacing = 0;
    3405     this.textHScale = 1;
    3406     this.textRenderingMode = TextRenderingMode.FILL;
    3407     this.textRise = 0;
    3408     // Color spaces
    3409     this.fillColorSpace = new DeviceGrayCS();
    3410     this.fillColorSpaceObj = null;
    3411     this.strokeColorSpace = new DeviceGrayCS();
    3412     this.strokeColorSpaceObj = null;
    3413     this.fillColorObj = null;
    3414     this.strokeColorObj = null;
    3415     // Default fore and background colors
    3416     this.fillColor = '#000000';
    3417     this.strokeColor = '#000000';
    3418     // Note: fill alpha applies to all non-stroking operations
    3419     this.fillAlpha = 1;
    3420     this.strokeAlpha = 1;
    3421     this.lineWidth = 1;
    3422     this.paintFormXObjectDepth = 0;
    3423 
    3424     this.old = old;
    3425   }
    3426 
    3427   CanvasExtraState.prototype = {
    3428     clone: function CanvasExtraState_clone() {
    3429       return Object.create(this);
    3430     },
    3431     setCurrentPoint: function CanvasExtraState_setCurrentPoint(x, y) {
    3432       this.x = x;
    3433       this.y = y;
    3434     }
    3435   };
    3436   return CanvasExtraState;
    3437 })();
    3438 
    3439 var CanvasGraphics = (function CanvasGraphicsClosure() {
    3440   // Defines the time the executeOperatorList is going to be executing
    3441   // before it stops and shedules a continue of execution.
    3442   var EXECUTION_TIME = 15;
    3443 
    3444   function CanvasGraphics(canvasCtx, commonObjs, objs, textLayer, imageLayer) {
    3445     this.ctx = canvasCtx;
    3446     this.current = new CanvasExtraState();
    3447     this.stateStack = [];
    3448     this.pendingClip = null;
    3449     this.pendingEOFill = false;
    3450     this.res = null;
    3451     this.xobjs = null;
    3452     this.commonObjs = commonObjs;
    3453     this.objs = objs;
    3454     this.textLayer = textLayer;
    3455     this.imageLayer = imageLayer;
    3456     this.groupStack = [];
    3457     this.processingType3 = null;
    3458     if (canvasCtx) {
    3459       addContextCurrentTransform(canvasCtx);
    3460     }
    3461   }
    3462 
    3463   function putBinaryImageData(ctx, imgData) {
    3464     if (typeof ImageData !== 'undefined' && imgData instanceof ImageData) {
    3465       ctx.putImageData(imgData, 0, 0);
    3466       return;
    3467     }
    3468 
    3469     var tmpImgData = ctx.createImageData(imgData.width, imgData.height);
    3470 
    3471     var data = imgData.data;
    3472     var tmpImgDataPixels = tmpImgData.data;
    3473     if ('set' in tmpImgDataPixels)
    3474       tmpImgDataPixels.set(data);
    3475     else {
    3476       // Copy over the imageData pixel by pixel.
    3477       for (var i = 0, ii = tmpImgDataPixels.length; i < ii; i++)
    3478         tmpImgDataPixels[i] = data[i];
    3479     }
    3480 
    3481     ctx.putImageData(tmpImgData, 0, 0);
    3482   }
    3483 
    3484   function copyCtxState(sourceCtx, destCtx) {
    3485     var properties = ['strokeStyle', 'fillStyle', 'fillRule', 'globalAlpha',
    3486                       'lineWidth', 'lineCap', 'lineJoin', 'miterLimit',
    3487                       'globalCompositeOperation', 'font'];
    3488     for (var i = 0, ii = properties.length; i < ii; i++) {
    3489       var property = properties[i];
    3490       if (property in sourceCtx) {
    3491         destCtx[property] = sourceCtx[property];
    3492       }
    3493     }
    3494     if ('setLineDash' in sourceCtx) {
    3495       destCtx.setLineDash(sourceCtx.getLineDash());
    3496       destCtx.lineDashOffset =  sourceCtx.lineDashOffset;
    3497     } else if ('mozDash' in sourceCtx) {
    3498       destCtx.mozDash = sourceCtx.mozDash;
    3499       destCtx.mozDashOffset = sourceCtx.mozDashOffset;
    3500     }
    3501   }
    3502 
    3503   var LINE_CAP_STYLES = ['butt', 'round', 'square'];
    3504   var LINE_JOIN_STYLES = ['miter', 'round', 'bevel'];
    3505   var NORMAL_CLIP = {};
    3506   var EO_CLIP = {};
    3507 
    3508   CanvasGraphics.prototype = {
    3509     slowCommands: {
    3510       'stroke': true,
    3511       'closeStroke': true,
    3512       'fill': true,
    3513       'eoFill': true,
    3514       'fillStroke': true,
    3515       'eoFillStroke': true,
    3516       'closeFillStroke': true,
    3517       'closeEOFillStroke': true,
    3518       'showText': true,
    3519       'showSpacedText': true,
    3520       'setStrokeColorSpace': true,
    3521       'setFillColorSpace': true,
    3522       'setStrokeColor': true,
    3523       'setStrokeColorN': true,
    3524       'setFillColor': true,
    3525       'setFillColorN': true,
    3526       'setStrokeGray': true,
    3527       'setFillGray': true,
    3528       'setStrokeRGBColor': true,
    3529       'setFillRGBColor': true,
    3530       'setStrokeCMYKColor': true,
    3531       'setFillCMYKColor': true,
    3532       'paintJpegXObject': true,
    3533       'paintImageXObject': true,
    3534       'paintInlineImageXObject': true,
    3535       'paintInlineImageXObjectGroup': true,
    3536       'paintImageMaskXObject': true,
    3537       'paintImageMaskXObjectGroup': true,
    3538       'shadingFill': true
    3539     },
    3540 
    3541     beginDrawing: function CanvasGraphics_beginDrawing(viewport, transparency) {
    3542       // For pdfs that use blend modes we have to clear the canvas else certain
    3543       // blend modes can look wrong since we'd be blending with a white
    3544       // backdrop. The problem with a transparent backdrop though is we then
    3545       // don't get sub pixel anti aliasing on text, so we fill with white if
    3546       // we can.
    3547       var width = this.ctx.canvas.width;
    3548       var height = this.ctx.canvas.height;
    3549       if (transparency) {
    3550         this.ctx.clearRect(0, 0, width, height);
    3551       } else {
    3552         this.ctx.mozOpaque = true;
    3553         this.ctx.save();
    3554         this.ctx.fillStyle = 'rgb(255, 255, 255)';
    3555         this.ctx.fillRect(0, 0, width, height);
    3556         this.ctx.restore();
    3557       }
    3558 
    3559       var transform = viewport.transform;
    3560       this.ctx.save();
    3561       this.ctx.transform.apply(this.ctx, transform);
    3562 
    3563       if (this.textLayer) {
    3564         this.textLayer.beginLayout();
    3565       }
    3566       if (this.imageLayer) {
    3567         this.imageLayer.beginLayout();
    3568       }
    3569     },
    3570 
    3571     executeOperatorList: function CanvasGraphics_executeOperatorList(
    3572                                     operatorList,
    3573                                     executionStartIdx, continueCallback,
    3574                                     stepper) {
    3575       var argsArray = operatorList.argsArray;
    3576       var fnArray = operatorList.fnArray;
    3577       var i = executionStartIdx || 0;
    3578       var argsArrayLen = argsArray.length;
    3579 
    3580       // Sometimes the OperatorList to execute is empty.
    3581       if (argsArrayLen == i) {
    3582         return i;
    3583       }
    3584 
    3585       var executionEndIdx;
    3586       var endTime = Date.now() + EXECUTION_TIME;
    3587 
    3588       var commonObjs = this.commonObjs;
    3589       var objs = this.objs;
    3590       var fnName;
    3591       var slowCommands = this.slowCommands;
    3592 
    3593       while (true) {
    3594         if (stepper && i === stepper.nextBreakPoint) {
    3595           stepper.breakIt(i, continueCallback);
    3596           return i;
    3597         }
    3598 
    3599         fnName = fnArray[i];
    3600 
    3601         if (fnName !== 'dependency') {
    3602           this[fnName].apply(this, argsArray[i]);
    3603         } else {
    3604           var deps = argsArray[i];
    3605           for (var n = 0, nn = deps.length; n < nn; n++) {
    3606             var depObjId = deps[n];
    3607             var common = depObjId.substring(0, 2) == 'g_';
    3608 
    3609             // If the promise isn't resolved yet, add the continueCallback
    3610             // to the promise and bail out.
    3611             if (!common && !objs.isResolved(depObjId)) {
    3612               objs.get(depObjId, continueCallback);
    3613               return i;
    3614             }
    3615             if (common && !commonObjs.isResolved(depObjId)) {
    3616               commonObjs.get(depObjId, continueCallback);
    3617               return i;
    3618             }
    3619           }
    3620         }
    3621 
    3622         i++;
    3623 
    3624         // If the entire operatorList was executed, stop as were done.
    3625         if (i == argsArrayLen) {
    3626           return i;
    3627         }
    3628 
    3629         // If the execution took longer then a certain amount of time, shedule
    3630         // to continue exeution after a short delay.
    3631         // However, this is only possible if a 'continueCallback' is passed in.
    3632         if (continueCallback && slowCommands[fnName] && Date.now() > endTime) {
    3633           setTimeout(continueCallback, 0);
    3634           return i;
    3635         }
    3636 
    3637         // If the operatorList isn't executed completely yet OR the execution
    3638         // time was short enough, do another execution round.
    3639       }
    3640     },
    3641 
    3642     endDrawing: function CanvasGraphics_endDrawing() {
    3643       this.ctx.restore();
    3644       CachedCanvases.clear();
    3645 
    3646       if (this.textLayer) {
    3647         this.textLayer.endLayout();
    3648       }
    3649       if (this.imageLayer) {
    3650         this.imageLayer.endLayout();
    3651       }
    3652     },
    3653 
    3654     // Graphics state
    3655     setLineWidth: function CanvasGraphics_setLineWidth(width) {
    3656       this.current.lineWidth = width;
    3657       this.ctx.lineWidth = width;
    3658     },
    3659     setLineCap: function CanvasGraphics_setLineCap(style) {
    3660       this.ctx.lineCap = LINE_CAP_STYLES[style];
    3661     },
    3662     setLineJoin: function CanvasGraphics_setLineJoin(style) {
    3663       this.ctx.lineJoin = LINE_JOIN_STYLES[style];
    3664     },
    3665     setMiterLimit: function CanvasGraphics_setMiterLimit(limit) {
    3666       this.ctx.miterLimit = limit;
    3667     },
    3668     setDash: function CanvasGraphics_setDash(dashArray, dashPhase) {
    3669       var ctx = this.ctx;
    3670       if ('setLineDash' in ctx) {
    3671         ctx.setLineDash(dashArray);
    3672         ctx.lineDashOffset = dashPhase;
    3673       } else {
    3674         ctx.mozDash = dashArray;
    3675         ctx.mozDashOffset = dashPhase;
    3676       }
    3677     },
    3678     setRenderingIntent: function CanvasGraphics_setRenderingIntent(intent) {
    3679       // Maybe if we one day fully support color spaces this will be important
    3680       // for now we can ignore.
    3681       // TODO set rendering intent?
    3682     },
    3683     setFlatness: function CanvasGraphics_setFlatness(flatness) {
    3684       // There's no way to control this with canvas, but we can safely ignore.
    3685       // TODO set flatness?
    3686     },
    3687     setGState: function CanvasGraphics_setGState(states) {
    3688       for (var i = 0, ii = states.length; i < ii; i++) {
    3689         var state = states[i];
    3690         var key = state[0];
    3691         var value = state[1];
    3692 
    3693         switch (key) {
    3694           case 'LW':
    3695             this.setLineWidth(value);
    3696             break;
    3697           case 'LC':
    3698             this.setLineCap(value);
    3699             break;
    3700           case 'LJ':
    3701             this.setLineJoin(value);
    3702             break;
    3703           case 'ML':
    3704             this.setMiterLimit(value);
    3705             break;
    3706           case 'D':
    3707             this.setDash(value[0], value[1]);
    3708             break;
    3709           case 'RI':
    3710             this.setRenderingIntent(value);
    3711             break;
    3712           case 'FL':
    3713             this.setFlatness(value);
    3714             break;
    3715           case 'Font':
    3716             this.setFont(state[1], state[2]);
    3717             break;
    3718           case 'CA':
    3719             this.current.strokeAlpha = state[1];
    3720             break;
    3721           case 'ca':
    3722             this.current.fillAlpha = state[1];
    3723             this.ctx.globalAlpha = state[1];
    3724             break;
    3725           case 'BM':
    3726             if (value && value.name && (value.name !== 'Normal')) {
    3727               var mode = value.name.replace(/([A-Z])/g,
    3728                 function(c) {
    3729                   return '-' + c.toLowerCase();
    3730                 }
    3731               ).substring(1);
    3732               this.ctx.globalCompositeOperation = mode;
    3733               if (this.ctx.globalCompositeOperation !== mode) {
    3734                 warn('globalCompositeOperation "' + mode +
    3735                      '" is not supported');
    3736               }
    3737             } else {
    3738               this.ctx.globalCompositeOperation = 'source-over';
    3739             }
    3740             break;
    3741         }
    3742       }
    3743     },
    3744     save: function CanvasGraphics_save() {
    3745       this.ctx.save();
    3746       var old = this.current;
    3747       this.stateStack.push(old);
    3748       this.current = old.clone();
    3749     },
    3750     restore: function CanvasGraphics_restore() {
    3751       var prev = this.stateStack.pop();
    3752       if (prev) {
    3753         this.current = prev;
    3754         this.ctx.restore();
    3755       }
    3756     },
    3757     transform: function CanvasGraphics_transform(a, b, c, d, e, f) {
    3758       this.ctx.transform(a, b, c, d, e, f);
    3759     },
    3760 
    3761     // Path
    3762     moveTo: function CanvasGraphics_moveTo(x, y) {
    3763       this.ctx.moveTo(x, y);
    3764       this.current.setCurrentPoint(x, y);
    3765     },
    3766     lineTo: function CanvasGraphics_lineTo(x, y) {
    3767       this.ctx.lineTo(x, y);
    3768       this.current.setCurrentPoint(x, y);
    3769     },
    3770     curveTo: function CanvasGraphics_curveTo(x1, y1, x2, y2, x3, y3) {
    3771       this.ctx.bezierCurveTo(x1, y1, x2, y2, x3, y3);
    3772       this.current.setCurrentPoint(x3, y3);
    3773     },
    3774     curveTo2: function CanvasGraphics_curveTo2(x2, y2, x3, y3) {
    3775       var current = this.current;
    3776       this.ctx.bezierCurveTo(current.x, current.y, x2, y2, x3, y3);
    3777       current.setCurrentPoint(x3, y3);
    3778     },
    3779     curveTo3: function CanvasGraphics_curveTo3(x1, y1, x3, y3) {
    3780       this.curveTo(x1, y1, x3, y3, x3, y3);
    3781       this.current.setCurrentPoint(x3, y3);
    3782     },
    3783     closePath: function CanvasGraphics_closePath() {
    3784       this.ctx.closePath();
    3785     },
    3786     rectangle: function CanvasGraphics_rectangle(x, y, width, height) {
    3787       this.ctx.rect(x, y, width, height);
    3788     },
    3789     stroke: function CanvasGraphics_stroke(consumePath) {
    3790       consumePath = typeof consumePath !== 'undefined' ? consumePath : true;
    3791       var ctx = this.ctx;
    3792       var strokeColor = this.current.strokeColor;
    3793       if (this.current.lineWidth === 0)
    3794         ctx.lineWidth = this.getSinglePixelWidth();
    3795       // For stroke we want to temporarily change the global alpha to the
    3796       // stroking alpha.
    3797       ctx.globalAlpha = this.current.strokeAlpha;
    3798       if (strokeColor && strokeColor.hasOwnProperty('type') &&
    3799           strokeColor.type === 'Pattern') {
    3800         // for patterns, we transform to pattern space, calculate
    3801         // the pattern, call stroke, and restore to user space
    3802         ctx.save();
    3803         ctx.strokeStyle = strokeColor.getPattern(ctx);
    3804         ctx.stroke();
    3805         ctx.restore();
    3806       } else {
    3807         ctx.stroke();
    3808       }
    3809       if (consumePath)
    3810         this.consumePath();
    3811       // Restore the global alpha to the fill alpha
    3812       ctx.globalAlpha = this.current.fillAlpha;
    3813     },
    3814     closeStroke: function CanvasGraphics_closeStroke() {
    3815       this.closePath();
    3816       this.stroke();
    3817     },
    3818     fill: function CanvasGraphics_fill(consumePath) {
    3819       consumePath = typeof consumePath !== 'undefined' ? consumePath : true;
    3820       var ctx = this.ctx;
    3821       var fillColor = this.current.fillColor;
    3822       var needRestore = false;
    3823 
    3824       if (fillColor && fillColor.hasOwnProperty('type') &&
    3825           fillColor.type === 'Pattern') {
    3826         ctx.save();
    3827         ctx.fillStyle = fillColor.getPattern(ctx);
    3828         needRestore = true;
    3829       }
    3830 
    3831       if (this.pendingEOFill) {
    3832         if ('mozFillRule' in this.ctx) {
    3833           this.ctx.mozFillRule = 'evenodd';
    3834           this.ctx.fill();
    3835           this.ctx.mozFillRule = 'nonzero';
    3836         } else {
    3837           try {
    3838             this.ctx.fill('evenodd');
    3839           } catch (ex) {
    3840             // shouldn't really happen, but browsers might think differently
    3841             this.ctx.fill();
    3842           }
    3843         }
    3844         this.pendingEOFill = false;
    3845       } else {
    3846         this.ctx.fill();
    3847       }
    3848 
    3849       if (needRestore) {
    3850         ctx.restore();
    3851       }
    3852       if (consumePath) {
    3853         this.consumePath();
    3854       }
    3855     },
    3856     eoFill: function CanvasGraphics_eoFill() {
    3857       this.pendingEOFill = true;
    3858       this.fill();
    3859     },
    3860     fillStroke: function CanvasGraphics_fillStroke() {
    3861       this.fill(false);
    3862       this.stroke(false);
    3863 
    3864       this.consumePath();
    3865     },
    3866     eoFillStroke: function CanvasGraphics_eoFillStroke() {
    3867       this.pendingEOFill = true;
    3868       this.fillStroke();
    3869     },
    3870     closeFillStroke: function CanvasGraphics_closeFillStroke() {
    3871       this.closePath();
    3872       this.fillStroke();
    3873     },
    3874     closeEOFillStroke: function CanvasGraphics_closeEOFillStroke() {
    3875       this.pendingEOFill = true;
    3876       this.closePath();
    3877       this.fillStroke();
    3878     },
    3879     endPath: function CanvasGraphics_endPath() {
    3880       this.consumePath();
    3881     },
    3882 
    3883     // Clipping
    3884     clip: function CanvasGraphics_clip() {
    3885       this.pendingClip = NORMAL_CLIP;
    3886     },
    3887     eoClip: function CanvasGraphics_eoClip() {
    3888       this.pendingClip = EO_CLIP;
    3889     },
    3890 
    3891     // Text
    3892     beginText: function CanvasGraphics_beginText() {
    3893       this.current.textMatrix = IDENTITY_MATRIX;
    3894       this.current.x = this.current.lineX = 0;
    3895       this.current.y = this.current.lineY = 0;
    3896     },
    3897     endText: function CanvasGraphics_endText() {
    3898       if (!('pendingTextPaths' in this)) {
    3899         this.ctx.beginPath();
    3900         return;
    3901       }
    3902       var paths = this.pendingTextPaths;
    3903       var ctx = this.ctx;
    3904 
    3905       ctx.save();
    3906       ctx.beginPath();
    3907       for (var i = 0; i < paths.length; i++) {
    3908         var path = paths[i];
    3909         ctx.setTransform.apply(ctx, path.transform);
    3910         ctx.translate(path.x, path.y);
    3911         path.addToPath(ctx, path.fontSize);
    3912       }
    3913       ctx.restore();
    3914       ctx.clip();
    3915       ctx.beginPath();
    3916       delete this.pendingTextPaths;
    3917     },
    3918     setCharSpacing: function CanvasGraphics_setCharSpacing(spacing) {
    3919       this.current.charSpacing = spacing;
    3920     },
    3921     setWordSpacing: function CanvasGraphics_setWordSpacing(spacing) {
    3922       this.current.wordSpacing = spacing;
    3923     },
    3924     setHScale: function CanvasGraphics_setHScale(scale) {
    3925       this.current.textHScale = scale / 100;
    3926     },
    3927     setLeading: function CanvasGraphics_setLeading(leading) {
    3928       this.current.leading = -leading;
    3929     },
    3930     setFont: function CanvasGraphics_setFont(fontRefName, size) {
    3931       var fontObj = this.commonObjs.get(fontRefName);
    3932       var current = this.current;
    3933 
    3934       if (!fontObj)
    3935         error('Can\'t find font for ' + fontRefName);
    3936 
    3937       current.fontMatrix = fontObj.fontMatrix ? fontObj.fontMatrix :
    3938                                                 FONT_IDENTITY_MATRIX;
    3939 
    3940       // A valid matrix needs all main diagonal elements to be non-zero
    3941       // This also ensures we bypass FF bugzilla bug #719844.
    3942       if (current.fontMatrix[0] === 0 ||
    3943           current.fontMatrix[3] === 0) {
    3944         warn('Invalid font matrix for font ' + fontRefName);
    3945       }
    3946 
    3947       // The spec for Tf (setFont) says that 'size' specifies the font 'scale',
    3948       // and in some docs this can be negative (inverted x-y axes).
    3949       if (size < 0) {
    3950         size = -size;
    3951         current.fontDirection = -1;
    3952       } else {
    3953         current.fontDirection = 1;
    3954       }
    3955 
    3956       this.current.font = fontObj;
    3957       this.current.fontSize = size;
    3958 
    3959       if (fontObj.coded)
    3960         return; // we don't need ctx.font for Type3 fonts
    3961 
    3962       var name = fontObj.loadedName || 'sans-serif';
    3963       var bold = fontObj.black ? (fontObj.bold ? 'bolder' : 'bold') :
    3964                                  (fontObj.bold ? 'bold' : 'normal');
    3965 
    3966       var italic = fontObj.italic ? 'italic' : 'normal';
    3967       var typeface = '"' + name + '", ' + fontObj.fallbackName;
    3968 
    3969       // Some font backends cannot handle fonts below certain size.
    3970       // Keeping the font at minimal size and using the fontSizeScale to change
    3971       // the current transformation matrix before the fillText/strokeText.
    3972       // See https://bugzilla.mozilla.org/show_bug.cgi?id=726227
    3973       var browserFontSize = size >= MIN_FONT_SIZE ? size : MIN_FONT_SIZE;
    3974       this.current.fontSizeScale = browserFontSize != MIN_FONT_SIZE ? 1.0 :
    3975                                    size / MIN_FONT_SIZE;
    3976 
    3977       var rule = italic + ' ' + bold + ' ' + browserFontSize + 'px ' + typeface;
    3978       this.ctx.font = rule;
    3979     },
    3980     setTextRenderingMode: function CanvasGraphics_setTextRenderingMode(mode) {
    3981       this.current.textRenderingMode = mode;
    3982     },
    3983     setTextRise: function CanvasGraphics_setTextRise(rise) {
    3984       this.current.textRise = rise;
    3985     },
    3986     moveText: function CanvasGraphics_moveText(x, y) {
    3987       this.current.x = this.current.lineX += x;
    3988       this.current.y = this.current.lineY += y;
    3989     },
    3990     setLeadingMoveText: function CanvasGraphics_setLeadingMoveText(x, y) {
    3991       this.setLeading(-y);
    3992       this.moveText(x, y);
    3993     },
    3994     setTextMatrix: function CanvasGraphics_setTextMatrix(a, b, c, d, e, f) {
    3995       this.current.textMatrix = [a, b, c, d, e, f];
    3996 
    3997       this.current.x = this.current.lineX = 0;
    3998       this.current.y = this.current.lineY = 0;
    3999     },
    4000     nextLine: function CanvasGraphics_nextLine() {
    4001       this.moveText(0, this.current.leading);
    4002     },
    4003     applyTextTransforms: function CanvasGraphics_applyTextTransforms() {
    4004       var ctx = this.ctx;
    4005       var current = this.current;
    4006       ctx.transform.apply(ctx, current.textMatrix);
    4007       ctx.translate(current.x, current.y + current.textRise);
    4008       if (current.fontDirection > 0) {
    4009         ctx.scale(current.textHScale, -1);
    4010       } else {
    4011         ctx.scale(-current.textHScale, 1);
    4012       }
    4013     },
    4014     createTextGeometry: function CanvasGraphics_createTextGeometry() {
    4015       var geometry = {};
    4016       var ctx = this.ctx;
    4017       var font = this.current.font;
    4018       var ctxMatrix = ctx.mozCurrentTransform;
    4019       if (ctxMatrix) {
    4020         var bl = Util.applyTransform([0, 0], ctxMatrix);
    4021         var tr = Util.applyTransform([1, 1], ctxMatrix);
    4022         geometry.x = bl[0];
    4023         geometry.y = bl[1];
    4024         geometry.hScale = tr[0] - bl[0];
    4025         geometry.vScale = tr[1] - bl[1];
    4026       }
    4027       geometry.spaceWidth = font.spaceWidth;
    4028       geometry.fontName = font.loadedName;
    4029       geometry.fontFamily = font.fallbackName;
    4030       geometry.fontSize = this.current.fontSize;
    4031       return geometry;
    4032     },
    4033 
    4034     paintChar: function (character, x, y) {
    4035       var ctx = this.ctx;
    4036       var current = this.current;
    4037       var font = current.font;
    4038       var fontSize = current.fontSize / current.fontSizeScale;
    4039       var textRenderingMode = current.textRenderingMode;
    4040       var fillStrokeMode = textRenderingMode &
    4041         TextRenderingMode.FILL_STROKE_MASK;
    4042       var isAddToPathSet = !!(textRenderingMode &
    4043         TextRenderingMode.ADD_TO_PATH_FLAG);
    4044 
    4045       var addToPath;
    4046       if (font.disableFontFace || isAddToPathSet) {
    4047         addToPath = font.renderer.getPathGenerator(character);
    4048       }
    4049 
    4050       if (font.disableFontFace) {
    4051         ctx.save();
    4052         ctx.translate(x, y);
    4053         ctx.beginPath();
    4054         addToPath(ctx, fontSize);
    4055         if (fillStrokeMode === TextRenderingMode.FILL ||
    4056             fillStrokeMode === TextRenderingMode.FILL_STROKE) {
    4057           ctx.fill();
    4058         }
    4059         if (fillStrokeMode === TextRenderingMode.STROKE ||
    4060             fillStrokeMode === TextRenderingMode.FILL_STROKE) {
    4061           ctx.stroke();
    4062         }
    4063         ctx.restore();
    4064       } else {
    4065         if (fillStrokeMode === TextRenderingMode.FILL ||
    4066             fillStrokeMode === TextRenderingMode.FILL_STROKE) {
    4067           ctx.fillText(character, x, y);
    4068         }
    4069         if (fillStrokeMode === TextRenderingMode.STROKE ||
    4070             fillStrokeMode === TextRenderingMode.FILL_STROKE) {
    4071           ctx.strokeText(character, x, y);
    4072         }
    4073       }
    4074 
    4075       if (isAddToPathSet) {
    4076         var paths = this.pendingTextPaths || (this.pendingTextPaths = []);
    4077         paths.push({
    4078           transform: ctx.mozCurrentTransform,
    4079           x: x,
    4080           y: y,
    4081           fontSize: fontSize,
    4082           addToPath: addToPath
    4083         });
    4084       }
    4085     },
    4086 
    4087     showText: function CanvasGraphics_showText(str, skipTextSelection) {
    4088       var ctx = this.ctx;
    4089       var current = this.current;
    4090       var font = current.font;
    4091       var glyphs = font.charsToGlyphs(str);
    4092       var fontSize = current.fontSize;
    4093       var fontSizeScale = current.fontSizeScale;
    4094       var charSpacing = current.charSpacing;
    4095       var wordSpacing = current.wordSpacing;
    4096       var textHScale = current.textHScale * current.fontDirection;
    4097       var fontMatrix = current.fontMatrix || FONT_IDENTITY_MATRIX;
    4098       var glyphsLength = glyphs.length;
    4099       var textLayer = this.textLayer;
    4100       var geom;
    4101       var textSelection = textLayer && !skipTextSelection ? true : false;
    4102       var canvasWidth = 0.0;
    4103       var vertical = font.vertical;
    4104       var defaultVMetrics = font.defaultVMetrics;
    4105 
    4106       // Type3 fonts - each glyph is a "mini-PDF"
    4107       if (font.coded) {
    4108         ctx.save();
    4109         ctx.transform.apply(ctx, current.textMatrix);
    4110         ctx.translate(current.x, current.y);
    4111 
    4112         ctx.scale(textHScale, 1);
    4113 
    4114         if (textSelection) {
    4115           this.save();
    4116           ctx.scale(1, -1);
    4117           geom = this.createTextGeometry();
    4118           this.restore();
    4119         }
    4120         for (var i = 0; i < glyphsLength; ++i) {
    4121 
    4122           var glyph = glyphs[i];
    4123           if (glyph === null) {
    4124             // word break
    4125             this.ctx.translate(wordSpacing, 0);
    4126             current.x += wordSpacing * textHScale;
    4127             continue;
    4128           }
    4129 
    4130           this.processingType3 = glyph;
    4131           this.save();
    4132           ctx.scale(fontSize, fontSize);
    4133           ctx.transform.apply(ctx, fontMatrix);
    4134           this.executeOperatorList(glyph.operatorList);
    4135           this.restore();
    4136 
    4137           var transformed = Util.applyTransform([glyph.width, 0], fontMatrix);
    4138           var width = (transformed[0] * fontSize + charSpacing) *
    4139                       current.fontDirection;
    4140 
    4141           ctx.translate(width, 0);
    4142           current.x += width * textHScale;
    4143 
    4144           canvasWidth += width;
    4145         }
    4146         ctx.restore();
    4147         this.processingType3 = null;
    4148       } else {
    4149         ctx.save();
    4150         this.applyTextTransforms();
    4151 
    4152         var lineWidth = current.lineWidth;
    4153         var a1 = current.textMatrix[0], b1 = current.textMatrix[1];
    4154         var scale = Math.sqrt(a1 * a1 + b1 * b1);
    4155         if (scale === 0 || lineWidth === 0)
    4156           lineWidth = this.getSinglePixelWidth();
    4157         else
    4158           lineWidth /= scale;
    4159 
    4160         if (textSelection)
    4161           geom = this.createTextGeometry();
    4162 
    4163         if (fontSizeScale != 1.0) {
    4164           ctx.scale(fontSizeScale, fontSizeScale);
    4165           lineWidth /= fontSizeScale;
    4166         }
    4167 
    4168         ctx.lineWidth = lineWidth;
    4169 
    4170         var x = 0;
    4171         for (var i = 0; i < glyphsLength; ++i) {
    4172           var glyph = glyphs[i];
    4173           if (glyph === null) {
    4174             // word break
    4175             x += current.fontDirection * wordSpacing;
    4176             continue;
    4177           }
    4178 
    4179           var restoreNeeded = false;
    4180           var character = glyph.fontChar;
    4181           var vmetric = glyph.vmetric || defaultVMetrics;
    4182           if (vertical) {
    4183             var vx = glyph.vmetric ? vmetric[1] : glyph.width * 0.5;
    4184             vx = -vx * fontSize * current.fontMatrix[0];
    4185             var vy = vmetric[2] * fontSize * current.fontMatrix[0];
    4186           }
    4187           var width = vmetric ? -vmetric[0] : glyph.width;
    4188           var charWidth = width * fontSize * current.fontMatrix[0] +
    4189                           charSpacing * current.fontDirection;
    4190           var accent = glyph.accent;
    4191 
    4192           var scaledX, scaledY, scaledAccentX, scaledAccentY;
    4193           if (!glyph.disabled) {
    4194             if (vertical) {
    4195               scaledX = vx / fontSizeScale;
    4196               scaledY = (x + vy) / fontSizeScale;
    4197             } else {
    4198               scaledX = x / fontSizeScale;
    4199               scaledY = 0;
    4200             }
    4201 
    4202             if (font.remeasure && width > 0) {
    4203               // some standard fonts may not have the exact width, trying to
    4204               // rescale per character
    4205               var measuredWidth = ctx.measureText(character).width * 1000 /
    4206                 current.fontSize * current.fontSizeScale;
    4207               var characterScaleX = width / measuredWidth;
    4208               restoreNeeded = true;
    4209               ctx.save();
    4210               ctx.scale(characterScaleX, 1);
    4211               scaledX /= characterScaleX;
    4212               if (accent) {
    4213                 scaledAccentX /= characterScaleX;
    4214               }
    4215             }
    4216 
    4217             this.paintChar(character, scaledX, scaledY);
    4218             if (accent) {
    4219               scaledAccentX = scaledX + accent.offset.x / fontSizeScale;
    4220               scaledAccentY = scaledY - accent.offset.y / fontSizeScale;
    4221               this.paintChar(accent.fontChar, scaledAccentX, scaledAccentY);
    4222             }
    4223           }
    4224 
    4225           x += charWidth;
    4226 
    4227           canvasWidth += charWidth;
    4228 
    4229           if (restoreNeeded) {
    4230             ctx.restore();
    4231           }
    4232         }
    4233         if (vertical) {
    4234           current.y -= x * textHScale;
    4235         } else {
    4236           current.x += x * textHScale;
    4237         }
    4238         ctx.restore();
    4239       }
    4240 
    4241       if (textSelection) {
    4242         geom.canvasWidth = canvasWidth;
    4243         if (vertical) {
    4244           var vmetric = font.defaultVMetrics;
    4245           geom.x += vmetric[1] * fontSize * current.fontMatrix[0] /
    4246                     fontSizeScale * geom.hScale;
    4247           geom.y += vmetric[2] * fontSize * current.fontMatrix[0] /
    4248                     fontSizeScale * geom.vScale;
    4249         }
    4250         this.textLayer.appendText(geom);
    4251       }
    4252 
    4253       return canvasWidth;
    4254     },
    4255     showSpacedText: function CanvasGraphics_showSpacedText(arr) {
    4256       var ctx = this.ctx;
    4257       var current = this.current;
    4258       var font = current.font;
    4259       var fontSize = current.fontSize;
    4260       // TJ array's number is independent from fontMatrix
    4261       var textHScale = current.textHScale * 0.001 * current.fontDirection;
    4262       var arrLength = arr.length;
    4263       var textLayer = this.textLayer;
    4264       var geom;
    4265       var canvasWidth = 0.0;
    4266       var textSelection = textLayer ? true : false;
    4267       var vertical = font.vertical;
    4268       var spacingAccumulator = 0;
    4269 
    4270       if (textSelection) {
    4271         ctx.save();
    4272         this.applyTextTransforms();
    4273         geom = this.createTextGeometry();
    4274         ctx.restore();
    4275       }
    4276 
    4277       for (var i = 0; i < arrLength; ++i) {
    4278         var e = arr[i];
    4279         if (isNum(e)) {
    4280           var spacingLength = -e * fontSize * textHScale;
    4281           if (vertical) {
    4282             current.y += spacingLength;
    4283           } else {
    4284             current.x += spacingLength;
    4285           }
    4286 
    4287           if (textSelection)
    4288             spacingAccumulator += spacingLength;
    4289         } else if (isString(e)) {
    4290           var shownCanvasWidth = this.showText(e, true);
    4291 
    4292           if (textSelection) {
    4293             canvasWidth += spacingAccumulator + shownCanvasWidth;
    4294             spacingAccumulator = 0;
     1319
     1320var ColorSpace = (function ColorSpaceClosure() {
     1321  // Constructor should define this.numComps, this.defaultColor, this.name
     1322  function ColorSpace() {
     1323    error('should not call ColorSpace constructor');
     1324  }
     1325
     1326  ColorSpace.prototype = {
     1327    /**
     1328     * Converts the color value to the RGB color. The color components are
     1329     * located in the src array starting from the srcOffset. Returns the array
     1330     * of the rgb components, each value ranging from [0,255].
     1331     */
     1332    getRgb: function ColorSpace_getRgb(src, srcOffset) {
     1333      error('Should not call ColorSpace.getRgb');
     1334    },
     1335    /**
     1336     * Converts the color value to the RGB color, similar to the getRgb method.
     1337     * The result placed into the dest array starting from the destOffset.
     1338     */
     1339    getRgbItem: function ColorSpace_getRgb(src, srcOffset, dest, destOffset) {
     1340      error('Should not call ColorSpace.getRgbItem');
     1341    },
     1342    /**
     1343     * Converts the specified number of the color values to the RGB colors.
     1344     * The colors are located in the src array starting from the srcOffset.
     1345     * The result is placed into the dest array starting from the destOffset.
     1346     * The src array items shall be in [0,2^bits) range, the dest array items
     1347     * will be in [0,255] range. alpha01 indicates how many alpha components
     1348     * there are in the dest array; it will be either 0 (RGB array) or 1 (RGBA
     1349     * array).
     1350     */
     1351    getRgbBuffer: function ColorSpace_getRgbBuffer(src, srcOffset, count,
     1352                                                   dest, destOffset, bits,
     1353                                                   alpha01) {
     1354      error('Should not call ColorSpace.getRgbBuffer');
     1355    },
     1356    /**
     1357     * Determines the number of bytes required to store the result of the
     1358     * conversion done by the getRgbBuffer method. As in getRgbBuffer,
     1359     * |alpha01| is either 0 (RGB output) or 1 (RGBA output).
     1360     */
     1361    getOutputLength: function ColorSpace_getOutputLength(inputLength,
     1362                                                         alpha01) {
     1363      error('Should not call ColorSpace.getOutputLength');
     1364    },
     1365    /**
     1366     * Returns true if source data will be equal the result/output data.
     1367     */
     1368    isPassthrough: function ColorSpace_isPassthrough(bits) {
     1369      return false;
     1370    },
     1371    /**
     1372     * Fills in the RGB colors in an RGBA buffer.
     1373     */
     1374    fillRgb: function ColorSpace_fillRgb(rgbaBuf, originalWidth,
     1375                                         originalHeight, width, height,
     1376                                         actualHeight, bpc, comps) {
     1377      var count = originalWidth * originalHeight;
     1378      var rgbBuf = null;
     1379      var numComponentColors = 1 << bpc;
     1380      var needsResizing = originalHeight != height || originalWidth != width;
     1381
     1382      if (this.isPassthrough(bpc)) {
     1383        rgbBuf = comps;
     1384
     1385      } else if (this.numComps === 1 && count > numComponentColors &&
     1386          this.name !== 'DeviceGray' && this.name !== 'DeviceRGB') {
     1387        // Optimization: create a color map when there is just one component and
     1388        // we are converting more colors than the size of the color map. We
     1389        // don't build the map if the colorspace is gray or rgb since those
     1390        // methods are faster than building a map. This mainly offers big speed
     1391        // ups for indexed and alternate colorspaces.
     1392        //
     1393        // TODO it may be worth while to cache the color map. While running
     1394        // testing I never hit a cache so I will leave that out for now (perhaps
     1395        // we are reparsing colorspaces too much?).
     1396        var allColors = bpc <= 8 ? new Uint8Array(numComponentColors) :
     1397                                   new Uint16Array(numComponentColors);
     1398        for (var i = 0; i < numComponentColors; i++) {
     1399          allColors[i] = i;
     1400        }
     1401        var colorMap = new Uint8Array(numComponentColors * 3);
     1402        this.getRgbBuffer(allColors, 0, numComponentColors, colorMap, 0, bpc,
     1403                          /* alpha01 = */ 0);
     1404
     1405        if (!needsResizing) {
     1406          // Fill in the RGB values directly into |rgbaBuf|.
     1407          var rgbaPos = 0;
     1408          for (var i = 0; i < count; ++i) {
     1409            var key = comps[i] * 3;
     1410            rgbaBuf[rgbaPos++] = colorMap[key];
     1411            rgbaBuf[rgbaPos++] = colorMap[key + 1];
     1412            rgbaBuf[rgbaPos++] = colorMap[key + 2];
     1413            rgbaPos++;
    42951414          }
    42961415        } else {
    4297           error('TJ array element ' + e + ' is not string or num');
    4298         }
    4299       }
    4300 
    4301       if (textSelection) {
    4302         geom.canvasWidth = canvasWidth;
    4303         if (vertical) {
    4304           var fontSizeScale = current.fontSizeScale;
    4305           var vmetric = font.defaultVMetrics;
    4306           geom.x += vmetric[1] * fontSize * current.fontMatrix[0] /
    4307                     fontSizeScale * geom.hScale;
    4308           geom.y += vmetric[2] * fontSize * current.fontMatrix[0] /
    4309                     fontSizeScale * geom.vScale;
    4310         }
    4311         this.textLayer.appendText(geom);
    4312       }
    4313     },
    4314     nextLineShowText: function CanvasGraphics_nextLineShowText(text) {
    4315       this.nextLine();
    4316       this.showText(text);
    4317     },
    4318     nextLineSetSpacingShowText:
    4319       function CanvasGraphics_nextLineSetSpacingShowText(wordSpacing,
    4320                                                          charSpacing,
    4321                                                          text) {
    4322       this.setWordSpacing(wordSpacing);
    4323       this.setCharSpacing(charSpacing);
    4324       this.nextLineShowText(text);
    4325     },
    4326 
    4327     // Type3 fonts
    4328     setCharWidth: function CanvasGraphics_setCharWidth(xWidth, yWidth) {
    4329       // We can safely ignore this since the width should be the same
    4330       // as the width in the Widths array.
    4331     },
    4332     setCharWidthAndBounds: function CanvasGraphics_setCharWidthAndBounds(xWidth,
    4333                                                                         yWidth,
    4334                                                                         llx,
    4335                                                                         lly,
    4336                                                                         urx,
    4337                                                                         ury) {
    4338       // TODO According to the spec we're also suppose to ignore any operators
    4339       // that set color or include images while processing this type3 font.
    4340       this.rectangle(llx, lly, urx - llx, ury - lly);
    4341       this.clip();
    4342       this.endPath();
    4343     },
    4344 
    4345     // Color
    4346     setStrokeColorSpace: function CanvasGraphics_setStrokeColorSpace(raw) {
    4347       this.current.strokeColorSpace = ColorSpace.fromIR(raw);
    4348     },
    4349     setFillColorSpace: function CanvasGraphics_setFillColorSpace(raw) {
    4350       this.current.fillColorSpace = ColorSpace.fromIR(raw);
    4351     },
    4352     setStrokeColor: function CanvasGraphics_setStrokeColor(/*...*/) {
    4353       var cs = this.current.strokeColorSpace;
    4354       var rgbColor = cs.getRgb(arguments, 0);
    4355       var color = Util.makeCssRgb(rgbColor);
    4356       this.ctx.strokeStyle = color;
    4357       this.current.strokeColor = color;
    4358     },
    4359     getColorN_Pattern: function CanvasGraphics_getColorN_Pattern(IR, cs) {
    4360       if (IR[0] == 'TilingPattern') {
    4361         var args = IR[1];
    4362         var base = cs.base;
    4363         var color;
    4364         if (base) {
    4365           var baseComps = base.numComps;
    4366 
    4367           color = base.getRgb(args, 0);
    4368         }
    4369         var pattern = new TilingPattern(IR, color, this.ctx, this.objs,
    4370                                         this.commonObjs);
    4371       } else if (IR[0] == 'RadialAxial' || IR[0] == 'Dummy') {
    4372         var pattern = Pattern.shadingFromIR(IR);
     1416          rgbBuf = new Uint8Array(count * 3);
     1417          var rgbPos = 0;
     1418          for (var i = 0; i < count; ++i) {
     1419            var key = comps[i] * 3;
     1420            rgbBuf[rgbPos++] = colorMap[key];
     1421            rgbBuf[rgbPos++] = colorMap[key + 1];
     1422            rgbBuf[rgbPos++] = colorMap[key + 2];
     1423          }
     1424        }
    43731425      } else {
    4374         error('Unkown IR type ' + IR[0]);
    4375       }
    4376       return pattern;
    4377     },
    4378     setStrokeColorN: function CanvasGraphics_setStrokeColorN(/*...*/) {
    4379       var cs = this.current.strokeColorSpace;
    4380 
    4381       if (cs.name == 'Pattern') {
    4382         this.current.strokeColor = this.getColorN_Pattern(arguments, cs);
    4383       } else {
    4384         this.setStrokeColor.apply(this, arguments);
    4385       }
    4386     },
    4387     setFillColor: function CanvasGraphics_setFillColor(/*...*/) {
    4388       var cs = this.current.fillColorSpace;
    4389       var rgbColor = cs.getRgb(arguments, 0);
    4390       var color = Util.makeCssRgb(rgbColor);
    4391       this.ctx.fillStyle = color;
    4392       this.current.fillColor = color;
    4393     },
    4394     setFillColorN: function CanvasGraphics_setFillColorN(/*...*/) {
    4395       var cs = this.current.fillColorSpace;
    4396 
    4397       if (cs.name == 'Pattern') {
    4398         this.current.fillColor = this.getColorN_Pattern(arguments, cs);
    4399       } else {
    4400         this.setFillColor.apply(this, arguments);
    4401       }
    4402     },
    4403     setStrokeGray: function CanvasGraphics_setStrokeGray(gray) {
    4404       if (!(this.current.strokeColorSpace instanceof DeviceGrayCS))
    4405         this.current.strokeColorSpace = new DeviceGrayCS();
    4406 
    4407       var rgbColor = this.current.strokeColorSpace.getRgb(arguments, 0);
    4408       var color = Util.makeCssRgb(rgbColor);
    4409       this.ctx.strokeStyle = color;
    4410       this.current.strokeColor = color;
    4411     },
    4412     setFillGray: function CanvasGraphics_setFillGray(gray) {
    4413       if (!(this.current.fillColorSpace instanceof DeviceGrayCS))
    4414         this.current.fillColorSpace = new DeviceGrayCS();
    4415 
    4416       var rgbColor = this.current.fillColorSpace.getRgb(arguments, 0);
    4417       var color = Util.makeCssRgb(rgbColor);
    4418       this.ctx.fillStyle = color;
    4419       this.current.fillColor = color;
    4420     },
    4421     setStrokeRGBColor: function CanvasGraphics_setStrokeRGBColor(r, g, b) {
    4422       if (!(this.current.strokeColorSpace instanceof DeviceRgbCS))
    4423         this.current.strokeColorSpace = new DeviceRgbCS();
    4424 
    4425       var rgbColor = this.current.strokeColorSpace.getRgb(arguments, 0);
    4426       var color = Util.makeCssRgb(rgbColor);
    4427       this.ctx.strokeStyle = color;
    4428       this.current.strokeColor = color;
    4429     },
    4430     setFillRGBColor: function CanvasGraphics_setFillRGBColor(r, g, b) {
    4431       if (!(this.current.fillColorSpace instanceof DeviceRgbCS))
    4432         this.current.fillColorSpace = new DeviceRgbCS();
    4433 
    4434       var rgbColor = this.current.fillColorSpace.getRgb(arguments, 0);
    4435       var color = Util.makeCssRgb(rgbColor);
    4436       this.ctx.fillStyle = color;
    4437       this.current.fillColor = color;
    4438     },
    4439     setStrokeCMYKColor: function CanvasGraphics_setStrokeCMYKColor(c, m, y, k) {
    4440       if (!(this.current.strokeColorSpace instanceof DeviceCmykCS))
    4441         this.current.strokeColorSpace = new DeviceCmykCS();
    4442 
    4443       var color = Util.makeCssCmyk(arguments);
    4444       this.ctx.strokeStyle = color;
    4445       this.current.strokeColor = color;
    4446     },
    4447     setFillCMYKColor: function CanvasGraphics_setFillCMYKColor(c, m, y, k) {
    4448       if (!(this.current.fillColorSpace instanceof DeviceCmykCS))
    4449         this.current.fillColorSpace = new DeviceCmykCS();
    4450 
    4451       var color = Util.makeCssCmyk(arguments);
    4452       this.ctx.fillStyle = color;
    4453       this.current.fillColor = color;
    4454     },
    4455 
    4456     shadingFill: function CanvasGraphics_shadingFill(patternIR) {
    4457       var ctx = this.ctx;
    4458 
    4459       this.save();
    4460       var pattern = Pattern.shadingFromIR(patternIR);
    4461       ctx.fillStyle = pattern.getPattern(ctx);
    4462 
    4463       var inv = ctx.mozCurrentTransformInverse;
    4464       if (inv) {
    4465         var canvas = ctx.canvas;
    4466         var width = canvas.width;
    4467         var height = canvas.height;
    4468 
    4469         var bl = Util.applyTransform([0, 0], inv);
    4470         var br = Util.applyTransform([0, height], inv);
    4471         var ul = Util.applyTransform([width, 0], inv);
    4472         var ur = Util.applyTransform([width, height], inv);
    4473 
    4474         var x0 = Math.min(bl[0], br[0], ul[0], ur[0]);
    4475         var y0 = Math.min(bl[1], br[1], ul[1], ur[1]);
    4476         var x1 = Math.max(bl[0], br[0], ul[0], ur[0]);
    4477         var y1 = Math.max(bl[1], br[1], ul[1], ur[1]);
    4478 
    4479         this.ctx.fillRect(x0, y0, x1 - x0, y1 - y0);
    4480       } else {
    4481         // HACK to draw the gradient onto an infinite rectangle.
    4482         // PDF gradients are drawn across the entire image while
    4483         // Canvas only allows gradients to be drawn in a rectangle
    4484         // The following bug should allow us to remove this.
    4485         // https://bugzilla.mozilla.org/show_bug.cgi?id=664884
    4486 
    4487         this.ctx.fillRect(-1e10, -1e10, 2e10, 2e10);
    4488       }
    4489 
    4490       this.restore();
    4491     },
    4492 
    4493     // Images
    4494     beginInlineImage: function CanvasGraphics_beginInlineImage() {
    4495       error('Should not call beginInlineImage');
    4496     },
    4497     beginImageData: function CanvasGraphics_beginImageData() {
    4498       error('Should not call beginImageData');
    4499     },
    4500 
    4501     paintFormXObjectBegin: function CanvasGraphics_paintFormXObjectBegin(matrix,
    4502                                                                         bbox) {
    4503       this.save();
    4504       this.current.paintFormXObjectDepth++;
    4505 
    4506       if (matrix && isArray(matrix) && 6 == matrix.length)
    4507         this.transform.apply(this, matrix);
    4508 
    4509       if (bbox && isArray(bbox) && 4 == bbox.length) {
    4510         var width = bbox[2] - bbox[0];
    4511         var height = bbox[3] - bbox[1];
    4512         this.rectangle(bbox[0], bbox[1], width, height);
    4513         this.clip();
    4514         this.endPath();
    4515       }
    4516     },
    4517 
    4518     paintFormXObjectEnd: function CanvasGraphics_paintFormXObjectEnd() {
    4519       var depth = this.current.paintFormXObjectDepth;
    4520       do {
    4521         this.restore();
    4522         // some pdf don't close all restores inside object
    4523         // closing those for them
    4524       } while (this.current.paintFormXObjectDepth >= depth);
    4525     },
    4526 
    4527     beginGroup: function CanvasGraphics_beginGroup(group) {
    4528       this.save();
    4529       var currentCtx = this.ctx;
    4530       // TODO non-isolated groups - according to Rik at adobe non-isolated
    4531       // group results aren't usually that different and they even have tools
    4532       // that ignore this setting. Notes from Rik on implmenting:
    4533       // - When you encounter an transparency group, create a new canvas with
    4534       // the dimensions of the bbox
    4535       // - copy the content from the previous canvas to the new canvas
    4536       // - draw as usual
    4537       // - remove the backdrop alpha:
    4538       // alphaNew = 1 - (1 - alpha)/(1 - alphaBackdrop) with 'alpha' the alpha
    4539       // value of your transparency group and 'alphaBackdrop' the alpha of the
    4540       // backdrop
    4541       // - remove background color:
    4542       // colorNew = color - alphaNew *colorBackdrop /(1 - alphaNew)
    4543       if (!group.isolated) {
    4544         info('TODO: Support non-isolated groups.');
    4545       }
    4546 
    4547       // TODO knockout - supposedly possible with the clever use of compositing
    4548       // modes.
    4549       if (group.knockout) {
    4550         TODO('Support knockout groups.');
    4551       }
    4552 
    4553       var currentTransform = currentCtx.mozCurrentTransform;
    4554       if (group.matrix) {
    4555         currentCtx.transform.apply(currentCtx, group.matrix);
    4556       }
    4557       assert(group.bbox, 'Bounding box is required.');
    4558 
    4559       // Based on the current transform figure out how big the bounding box
    4560       // will actually be.
    4561       var bounds = Util.getAxialAlignedBoundingBox(
    4562                     group.bbox,
    4563                     currentCtx.mozCurrentTransform);
    4564       // Use ceil in case we're between sizes so we don't create canvas that is
    4565       // too small and make the canvas at least 1x1 pixels.
    4566       var drawnWidth = Math.max(Math.ceil(bounds[2] - bounds[0]), 1);
    4567       var drawnHeight = Math.max(Math.ceil(bounds[3] - bounds[1]), 1);
    4568 
    4569       var scratchCanvas = createScratchCanvas(drawnWidth, drawnHeight);
    4570       var groupCtx = scratchCanvas.getContext('2d');
    4571       addContextCurrentTransform(groupCtx);
    4572       // Since we created a new canvas that is just the size of the bounding box
    4573       // we have to translate the group ctx.
    4574       var offsetX = bounds[0];
    4575       var offsetY = bounds[1];
    4576       groupCtx.translate(-offsetX, -offsetY);
    4577       groupCtx.transform.apply(groupCtx, currentTransform);
    4578 
    4579       // Setup the current ctx so when the group is popped we draw it the right
    4580       // location.
    4581       currentCtx.setTransform(1, 0, 0, 1, 0, 0);
    4582       currentCtx.translate(offsetX, offsetY);
    4583 
    4584       // The transparency group inherits all off the current graphics state
    4585       // except the blend mode, soft mask, and alpha constants.
    4586       copyCtxState(currentCtx, groupCtx);
    4587       this.ctx = groupCtx;
    4588       this.setGState([
    4589         ['SMask', 'None'],
    4590         ['BM', 'Normal'],
    4591         ['ca', 1],
    4592         ['CA', 1]
    4593       ]);
    4594       this.groupStack.push(currentCtx);
    4595     },
    4596 
    4597     endGroup: function CanvasGraphics_endGroup(group) {
    4598       var groupCtx = this.ctx;
    4599       this.ctx = this.groupStack.pop();
    4600       // Turn off image smoothing to avoid sub pixel interpolation which can
    4601       // look kind of blurry for some pdfs.
    4602       if ('imageSmoothingEnabled' in this.ctx) {
    4603         this.ctx.imageSmoothingEnabled = false;
    4604       } else {
    4605         this.ctx.mozImageSmoothingEnabled = false;
    4606       }
    4607       this.ctx.drawImage(groupCtx.canvas, 0, 0);
    4608       this.restore();
    4609     },
    4610 
    4611     beginAnnotations: function CanvasGraphics_beginAnnotations() {
    4612       this.save();
    4613       this.current = new CanvasExtraState();
    4614     },
    4615 
    4616     endAnnotations: function CanvasGraphics_endAnnotations() {
    4617       this.restore();
    4618     },
    4619 
    4620     beginAnnotation: function CanvasGraphics_beginAnnotation(rect, transform,
    4621                                                              matrix) {
    4622       this.save();
    4623 
    4624       if (rect && isArray(rect) && 4 == rect.length) {
    4625         var width = rect[2] - rect[0];
    4626         var height = rect[3] - rect[1];
    4627         this.rectangle(rect[0], rect[1], width, height);
    4628         this.clip();
    4629         this.endPath();
    4630       }
    4631 
    4632       this.transform.apply(this, transform);
    4633       this.transform.apply(this, matrix);
    4634     },
    4635 
    4636     endAnnotation: function CanvasGraphics_endAnnotation() {
    4637       this.restore();
    4638     },
    4639 
    4640     paintJpegXObject: function CanvasGraphics_paintJpegXObject(objId, w, h) {
    4641       var domImage = this.objs.get(objId);
    4642       if (!domImage) {
    4643         error('Dependent image isn\'t ready yet');
    4644       }
    4645 
    4646       this.save();
    4647 
    4648       var ctx = this.ctx;
    4649       // scale the image to the unit square
    4650       ctx.scale(1 / w, -1 / h);
    4651 
    4652       ctx.drawImage(domImage, 0, 0, domImage.width, domImage.height,
    4653                     0, -h, w, h);
    4654       if (this.imageLayer) {
    4655         var currentTransform = ctx.mozCurrentTransformInverse;
    4656         var position = this.getCanvasPosition(0, 0);
    4657         this.imageLayer.appendImage({
    4658           objId: objId,
    4659           left: position[0],
    4660           top: position[1],
    4661           width: w / currentTransform[0],
    4662           height: h / currentTransform[3]
    4663         });
    4664       }
    4665       this.restore();
    4666     },
    4667 
    4668     paintImageMaskXObject: function CanvasGraphics_paintImageMaskXObject(img) {
    4669       var ctx = this.ctx;
    4670       var width = img.width, height = img.height;
    4671 
    4672       var glyph = this.processingType3;
    4673 
    4674       if (COMPILE_TYPE3_GLYPHS && glyph && !('compiled' in glyph)) {
    4675         var MAX_SIZE_TO_COMPILE = 1000;
    4676         if (width <= MAX_SIZE_TO_COMPILE && height <= MAX_SIZE_TO_COMPILE) {
    4677           glyph.compiled =
    4678             compileType3Glyph({data: img.data, width: width, height: height});
     1426        if (!needsResizing) {
     1427          // Fill in the RGB values directly into |rgbaBuf|.
     1428          this.getRgbBuffer(comps, 0, width * actualHeight, rgbaBuf, 0, bpc,
     1429                            /* alpha01 = */ 1);
    46791430        } else {
    4680           glyph.compiled = null;
    4681         }
    4682       }
    4683 
    4684       if (glyph && glyph.compiled) {
    4685         glyph.compiled(ctx);
    4686         return;
    4687       }
    4688 
    4689       var maskCanvas = CachedCanvases.getCanvas('maskCanvas', width, height);
    4690       var maskCtx = maskCanvas.getContext('2d');
    4691       maskCtx.save();
    4692 
    4693       putBinaryImageData(maskCtx, img);
    4694 
    4695       maskCtx.globalCompositeOperation = 'source-in';
    4696 
    4697       var fillColor = this.current.fillColor;
    4698       maskCtx.fillStyle = (fillColor && fillColor.hasOwnProperty('type') &&
    4699                           fillColor.type === 'Pattern') ?
    4700                           fillColor.getPattern(maskCtx) : fillColor;
    4701       maskCtx.fillRect(0, 0, width, height);
    4702 
    4703       maskCtx.restore();
    4704 
    4705       this.paintInlineImageXObject(maskCanvas);
    4706     },
    4707 
    4708     paintImageMaskXObjectGroup:
    4709       function CanvasGraphics_paintImageMaskXObjectGroup(images) {
    4710       var ctx = this.ctx;
    4711 
    4712       for (var i = 0, ii = images.length; i < ii; i++) {
    4713         var image = images[i];
    4714         var width = image.width, height = image.height;
    4715 
    4716         var maskCanvas = CachedCanvases.getCanvas('maskCanvas', width, height);
    4717         var maskCtx = maskCanvas.getContext('2d');
    4718         maskCtx.save();
    4719 
    4720         putBinaryImageData(maskCtx, image);
    4721 
    4722         maskCtx.globalCompositeOperation = 'source-in';
    4723 
    4724         var fillColor = this.current.fillColor;
    4725         maskCtx.fillStyle = (fillColor && fillColor.hasOwnProperty('type') &&
    4726                             fillColor.type === 'Pattern') ?
    4727                             fillColor.getPattern(maskCtx) : fillColor;
    4728         maskCtx.fillRect(0, 0, width, height);
    4729 
    4730         maskCtx.restore();
    4731 
    4732         ctx.save();
    4733         ctx.transform.apply(ctx, image.transform);
    4734         ctx.scale(1, -1);
    4735         ctx.drawImage(maskCanvas, 0, 0, width, height,
    4736                       0, -1, 1, 1);
    4737         ctx.restore();
    4738       }
    4739     },
    4740 
    4741     paintImageXObject: function CanvasGraphics_paintImageXObject(objId) {
    4742       var imgData = this.objs.get(objId);
    4743       if (!imgData)
    4744         error('Dependent image isn\'t ready yet');
    4745 
    4746       this.paintInlineImageXObject(imgData);
    4747     },
    4748 
    4749     paintInlineImageXObject:
    4750       function CanvasGraphics_paintInlineImageXObject(imgData) {
    4751       var width = imgData.width;
    4752       var height = imgData.height;
    4753       var ctx = this.ctx;
    4754 
    4755       this.save();
    4756       // scale the image to the unit square
    4757       ctx.scale(1 / width, -1 / height);
    4758 
    4759       var currentTransform = ctx.mozCurrentTransformInverse;
    4760       var a = currentTransform[0], b = currentTransform[1];
    4761       var widthScale = Math.max(Math.sqrt(a * a + b * b), 1);
    4762       var c = currentTransform[2], d = currentTransform[3];
    4763       var heightScale = Math.max(Math.sqrt(c * c + d * d), 1);
    4764 
    4765       var imgToPaint;
    4766       if (imgData instanceof HTMLElement) {
    4767         imgToPaint = imgData;
    4768       } else {
    4769         var tmpCanvas = CachedCanvases.getCanvas('inlineImage', width, height);
    4770         var tmpCtx = tmpCanvas.getContext('2d');
    4771         putBinaryImageData(tmpCtx, imgData);
    4772         imgToPaint = tmpCanvas;
    4773       }
    4774 
    4775       var paintWidth = width, paintHeight = height;
    4776       var tmpCanvasId = 'prescale1';
    4777       // Vertial or horizontal scaling shall not be more than 2 to not loose the
    4778       // pixels during drawImage operation, painting on the temporary canvas(es)
    4779       // that are twice smaller in size
    4780       while ((widthScale > 2 && paintWidth > 1) ||
    4781              (heightScale > 2 && paintHeight > 1)) {
    4782         var newWidth = paintWidth, newHeight = paintHeight;
    4783         if (widthScale > 2 && paintWidth > 1) {
    4784           newWidth = Math.ceil(paintWidth / 2);
    4785           widthScale /= paintWidth / newWidth;
    4786         }
    4787         if (heightScale > 2 && paintHeight > 1) {
    4788           newHeight = Math.ceil(paintHeight / 2);
    4789           heightScale /= paintHeight / newHeight;
    4790         }
    4791         var tmpCanvas = CachedCanvases.getCanvas(tmpCanvasId,
    4792                                                  newWidth, newHeight);
    4793         tmpCtx = tmpCanvas.getContext('2d');
    4794         tmpCtx.clearRect(0, 0, newWidth, newHeight);
    4795         tmpCtx.drawImage(imgToPaint, 0, 0, paintWidth, paintHeight,
    4796                                      0, 0, newWidth, newHeight);
    4797         imgToPaint = tmpCanvas;
    4798         paintWidth = newWidth;
    4799         paintHeight = newHeight;
    4800         tmpCanvasId = tmpCanvasId === 'prescale1' ? 'prescale2' : 'prescale1';
    4801       }
    4802       ctx.drawImage(imgToPaint, 0, 0, paintWidth, paintHeight,
    4803                                 0, -height, width, height);
    4804 
    4805       if (this.imageLayer) {
    4806         var position = this.getCanvasPosition(0, -height);
    4807         this.imageLayer.appendImage({
    4808           imgData: imgData,
    4809           left: position[0],
    4810           top: position[1],
    4811           width: width / currentTransform[0],
    4812           height: height / currentTransform[3]
    4813         });
    4814       }
    4815       this.restore();
    4816     },
    4817 
    4818     paintInlineImageXObjectGroup:
    4819       function CanvasGraphics_paintInlineImageXObjectGroup(imgData, map) {
    4820       var ctx = this.ctx;
    4821       var w = imgData.width;
    4822       var h = imgData.height;
    4823 
    4824       var tmpCanvas = CachedCanvases.getCanvas('inlineImage', w, h);
    4825       var tmpCtx = tmpCanvas.getContext('2d');
    4826       putBinaryImageData(tmpCtx, imgData);
    4827 
    4828       for (var i = 0, ii = map.length; i < ii; i++) {
    4829         var entry = map[i];
    4830         ctx.save();
    4831         ctx.transform.apply(ctx, entry.transform);
    4832         ctx.scale(1, -1);
    4833         ctx.drawImage(tmpCanvas, entry.x, entry.y, entry.w, entry.h,
    4834                       0, -1, 1, 1);
    4835         if (this.imageLayer) {
    4836           var position = this.getCanvasPosition(entry.x, entry.y);
    4837           this.imageLayer.appendImage({
    4838             imgData: imgData,
    4839             left: position[0],
    4840             top: position[1],
    4841             width: w,
    4842             height: h
    4843           });
    4844         }
    4845         ctx.restore();
    4846       }
    4847     },
    4848 
    4849     // Marked content
    4850 
    4851     markPoint: function CanvasGraphics_markPoint(tag) {
    4852       // TODO Marked content.
    4853     },
    4854     markPointProps: function CanvasGraphics_markPointProps(tag, properties) {
    4855       // TODO Marked content.
    4856     },
    4857     beginMarkedContent: function CanvasGraphics_beginMarkedContent(tag) {
    4858       // TODO Marked content.
    4859     },
    4860     beginMarkedContentProps: function CanvasGraphics_beginMarkedContentProps(
    4861                                         tag, properties) {
    4862       // TODO Marked content.
    4863     },
    4864     endMarkedContent: function CanvasGraphics_endMarkedContent() {
    4865       // TODO Marked content.
    4866     },
    4867 
    4868     // Compatibility
    4869 
    4870     beginCompat: function CanvasGraphics_beginCompat() {
    4871       // TODO ignore undefined operators (should we do that anyway?)
    4872     },
    4873     endCompat: function CanvasGraphics_endCompat() {
    4874       // TODO stop ignoring undefined operators
    4875     },
    4876 
    4877     // Helper functions
    4878 
    4879     consumePath: function CanvasGraphics_consumePath() {
    4880       if (this.pendingClip) {
    4881         if (this.pendingClip == EO_CLIP) {
    4882           if ('mozFillRule' in this.ctx) {
    4883             this.ctx.mozFillRule = 'evenodd';
    4884             this.ctx.clip();
    4885             this.ctx.mozFillRule = 'nonzero';
    4886           } else {
    4887             try {
    4888               this.ctx.clip('evenodd');
    4889             } catch (ex) {
    4890               // shouldn't really happen, but browsers might think differently
    4891               this.ctx.clip();
    4892             }
     1431          rgbBuf = new Uint8Array(count * 3);
     1432          this.getRgbBuffer(comps, 0, count, rgbBuf, 0, bpc,
     1433                            /* alpha01 = */ 0);
     1434        }
     1435      }
     1436
     1437      if (rgbBuf) {
     1438        if (needsResizing) {
     1439          rgbBuf = PDFImage.resize(rgbBuf, bpc, 3, originalWidth,
     1440                                   originalHeight, width, height);
     1441        }
     1442        var rgbPos = 0;
     1443        var actualLength = width * actualHeight * 4;
     1444        for (var i = 0; i < actualLength; i += 4) {
     1445          rgbaBuf[i] = rgbBuf[rgbPos++];
     1446          rgbaBuf[i + 1] = rgbBuf[rgbPos++];
     1447          rgbaBuf[i + 2] = rgbBuf[rgbPos++];
     1448        }
     1449      }
     1450    },
     1451    /**
     1452     * True if the colorspace has components in the default range of [0, 1].
     1453     * This should be true for all colorspaces except for lab color spaces
     1454     * which are [0,100], [-128, 127], [-128, 127].
     1455     */
     1456    usesZeroToOneRange: true
     1457  };
     1458
     1459  ColorSpace.parse = function ColorSpace_parse(cs, xref, res) {
     1460    var IR = ColorSpace.parseToIR(cs, xref, res);
     1461    if (IR instanceof AlternateCS)
     1462      return IR;
     1463
     1464    return ColorSpace.fromIR(IR);
     1465  };
     1466
     1467  ColorSpace.fromIR = function ColorSpace_fromIR(IR) {
     1468    var name = isArray(IR) ? IR[0] : IR;
     1469
     1470    switch (name) {
     1471      case 'DeviceGrayCS':
     1472        return this.singletons.gray;
     1473      case 'DeviceRgbCS':
     1474        return this.singletons.rgb;
     1475      case 'DeviceCmykCS':
     1476        return this.singletons.cmyk;
     1477      case 'CalGrayCS':
     1478        var whitePoint = IR[1].WhitePoint;
     1479        var blackPoint = IR[1].BlackPoint;
     1480        var gamma = IR[1].Gamma;
     1481        return new CalGrayCS(whitePoint, blackPoint, gamma);
     1482      case 'PatternCS':
     1483        var basePatternCS = IR[1];
     1484        if (basePatternCS)
     1485          basePatternCS = ColorSpace.fromIR(basePatternCS);
     1486        return new PatternCS(basePatternCS);
     1487      case 'IndexedCS':
     1488        var baseIndexedCS = IR[1];
     1489        var hiVal = IR[2];
     1490        var lookup = IR[3];
     1491        return new IndexedCS(ColorSpace.fromIR(baseIndexedCS), hiVal, lookup);
     1492      case 'AlternateCS':
     1493        var numComps = IR[1];
     1494        var alt = IR[2];
     1495        var tintFnIR = IR[3];
     1496
     1497        return new AlternateCS(numComps, ColorSpace.fromIR(alt),
     1498                                PDFFunction.fromIR(tintFnIR));
     1499      case 'LabCS':
     1500        var whitePoint = IR[1].WhitePoint;
     1501        var blackPoint = IR[1].BlackPoint;
     1502        var range = IR[1].Range;
     1503        return new LabCS(whitePoint, blackPoint, range);
     1504      default:
     1505        error('Unkown name ' + name);
     1506    }
     1507    return null;
     1508  };
     1509
     1510  ColorSpace.parseToIR = function ColorSpace_parseToIR(cs, xref, res) {
     1511    if (isName(cs)) {
     1512      var colorSpaces = res.get('ColorSpace');
     1513      if (isDict(colorSpaces)) {
     1514        var refcs = colorSpaces.get(cs.name);
     1515        if (refcs)
     1516          cs = refcs;
     1517      }
     1518    }
     1519
     1520    cs = xref.fetchIfRef(cs);
     1521    var mode;
     1522
     1523    if (isName(cs)) {
     1524      mode = cs.name;
     1525      this.mode = mode;
     1526
     1527      switch (mode) {
     1528        case 'DeviceGray':
     1529        case 'G':
     1530          return 'DeviceGrayCS';
     1531        case 'DeviceRGB':
     1532        case 'RGB':
     1533          return 'DeviceRgbCS';
     1534        case 'DeviceCMYK':
     1535        case 'CMYK':
     1536          return 'DeviceCmykCS';
     1537        case 'Pattern':
     1538          return ['PatternCS', null];
     1539        default:
     1540          error('unrecognized colorspace ' + mode);
     1541      }
     1542    } else if (isArray(cs)) {
     1543      mode = cs[0].name;
     1544      this.mode = mode;
     1545
     1546      switch (mode) {
     1547        case 'DeviceGray':
     1548        case 'G':
     1549          return 'DeviceGrayCS';
     1550        case 'DeviceRGB':
     1551        case 'RGB':
     1552          return 'DeviceRgbCS';
     1553        case 'DeviceCMYK':
     1554        case 'CMYK':
     1555          return 'DeviceCmykCS';
     1556        case 'CalGray':
     1557          var params = cs[1].getAll();
     1558          return ['CalGrayCS', params];
     1559        case 'CalRGB':
     1560          return 'DeviceRgbCS';
     1561        case 'ICCBased':
     1562          var stream = xref.fetchIfRef(cs[1]);
     1563          var dict = stream.dict;
     1564          var numComps = dict.get('N');
     1565          if (numComps == 1)
     1566            return 'DeviceGrayCS';
     1567          if (numComps == 3)
     1568            return 'DeviceRgbCS';
     1569          if (numComps == 4)
     1570            return 'DeviceCmykCS';
     1571          break;
     1572        case 'Pattern':
     1573          var basePatternCS = cs[1];
     1574          if (basePatternCS)
     1575            basePatternCS = ColorSpace.parseToIR(basePatternCS, xref, res);
     1576          return ['PatternCS', basePatternCS];
     1577        case 'Indexed':
     1578        case 'I':
     1579          var baseIndexedCS = ColorSpace.parseToIR(cs[1], xref, res);
     1580          var hiVal = cs[2] + 1;
     1581          var lookup = xref.fetchIfRef(cs[3]);
     1582          if (isStream(lookup)) {
     1583            lookup = lookup.getBytes();
     1584          }
     1585          return ['IndexedCS', baseIndexedCS, hiVal, lookup];
     1586        case 'Separation':
     1587        case 'DeviceN':
     1588          var name = cs[1];
     1589          var numComps = 1;
     1590          if (isName(name))
     1591            numComps = 1;
     1592          else if (isArray(name))
     1593            numComps = name.length;
     1594          var alt = ColorSpace.parseToIR(cs[2], xref, res);
     1595          var tintFnIR = PDFFunction.getIR(xref, xref.fetchIfRef(cs[3]));
     1596          return ['AlternateCS', numComps, alt, tintFnIR];
     1597        case 'Lab':
     1598          var params = cs[1].getAll();
     1599          return ['LabCS', params];
     1600        default:
     1601          error('unimplemented color space object "' + mode + '"');
     1602      }
     1603    } else {
     1604      error('unrecognized color space object: "' + cs + '"');
     1605    }
     1606    return null;
     1607  };
     1608  /**
     1609   * Checks if a decode map matches the default decode map for a color space.
     1610   * This handles the general decode maps where there are two values per
     1611   * component. e.g. [0, 1, 0, 1, 0, 1] for a RGB color.
     1612   * This does not handle Lab, Indexed, or Pattern decode maps since they are
     1613   * slightly different.
     1614   * @param {Array} decode Decode map (usually from an image).
     1615   * @param {Number} n Number of components the color space has.
     1616   */
     1617  ColorSpace.isDefaultDecode = function ColorSpace_isDefaultDecode(decode, n) {
     1618    if (!decode)
     1619      return true;
     1620
     1621    if (n * 2 !== decode.length) {
     1622      warn('The decode map is not the correct length');
     1623      return true;
     1624    }
     1625    for (var i = 0, ii = decode.length; i < ii; i += 2) {
     1626      if (decode[i] !== 0 || decode[i + 1] != 1)
     1627        return false;
     1628    }
     1629    return true;
     1630  };
     1631
     1632  ColorSpace.singletons = {
     1633    get gray() {
     1634      return shadow(this, 'gray', new DeviceGrayCS());
     1635    },
     1636    get rgb() {
     1637      return shadow(this, 'rgb', new DeviceRgbCS());
     1638    },
     1639    get cmyk() {
     1640      return shadow(this, 'cmyk', new DeviceCmykCS());
     1641    }
     1642  };
     1643
     1644  return ColorSpace;
     1645})();
     1646
     1647/**
     1648 * Alternate color space handles both Separation and DeviceN color spaces.  A
     1649 * Separation color space is actually just a DeviceN with one color component.
     1650 * Both color spaces use a tinting function to convert colors to a base color
     1651 * space.
     1652 */
     1653var AlternateCS = (function AlternateCSClosure() {
     1654  function AlternateCS(numComps, base, tintFn) {
     1655    this.name = 'Alternate';
     1656    this.numComps = numComps;
     1657    this.defaultColor = new Float32Array(numComps);
     1658    for (var i = 0; i < numComps; ++i) {
     1659      this.defaultColor[i] = 1;
     1660    }
     1661    this.base = base;
     1662    this.tintFn = tintFn;
     1663  }
     1664
     1665  AlternateCS.prototype = {
     1666    getRgb: function AlternateCS_getRgb(src, srcOffset) {
     1667      var rgb = new Uint8Array(3);
     1668      this.getRgbItem(src, srcOffset, rgb, 0);
     1669      return rgb;
     1670    },
     1671    getRgbItem: function AlternateCS_getRgbItem(src, srcOffset,
     1672                                                dest, destOffset) {
     1673      var baseNumComps = this.base.numComps;
     1674      var input = 'subarray' in src ?
     1675        src.subarray(srcOffset, srcOffset + this.numComps) :
     1676        Array.prototype.slice.call(src, srcOffset, srcOffset + this.numComps);
     1677      var tinted = this.tintFn(input);
     1678      this.base.getRgbItem(tinted, 0, dest, destOffset);
     1679    },
     1680    getRgbBuffer: function AlternateCS_getRgbBuffer(src, srcOffset, count,
     1681                                                    dest, destOffset, bits,
     1682                                                    alpha01) {
     1683      var tintFn = this.tintFn;
     1684      var base = this.base;
     1685      var scale = 1 / ((1 << bits) - 1);
     1686      var baseNumComps = base.numComps;
     1687      var usesZeroToOneRange = base.usesZeroToOneRange;
     1688      var isPassthrough = (base.isPassthrough(8) || !usesZeroToOneRange) &&
     1689                          alpha01 === 0;
     1690      var pos = isPassthrough ? destOffset : 0;
     1691      var baseBuf = isPassthrough ? dest : new Uint8Array(baseNumComps * count);
     1692      var numComps = this.numComps;
     1693
     1694      var scaled = new Float32Array(numComps);
     1695      for (var i = 0; i < count; i++) {
     1696        for (var j = 0; j < numComps; j++) {
     1697          scaled[j] = src[srcOffset++] * scale;
     1698        }
     1699        var tinted = tintFn(scaled);
     1700        if (usesZeroToOneRange) {
     1701          for (var j = 0; j < baseNumComps; j++) {
     1702            baseBuf[pos++] = tinted[j] * 255;
    48931703          }
    48941704        } else {
    4895           this.ctx.clip();
    4896         }
    4897         this.pendingClip = null;
    4898       }
    4899       this.ctx.beginPath();
    4900     },
    4901     getSinglePixelWidth: function CanvasGraphics_getSinglePixelWidth(scale) {
    4902       var inverse = this.ctx.mozCurrentTransformInverse;
    4903       // max of the current horizontal and vertical scale
    4904       return Math.sqrt(Math.max(
    4905         (inverse[0] * inverse[0] + inverse[1] * inverse[1]),
    4906         (inverse[2] * inverse[2] + inverse[3] * inverse[3])));
    4907     },
    4908     getCanvasPosition: function CanvasGraphics_getCanvasPosition(x, y) {
    4909         var transform = this.ctx.mozCurrentTransform;
    4910         return [
    4911           transform[0] * x + transform[2] * y + transform[4],
    4912           transform[1] * x + transform[3] * y + transform[5]
    4913         ];
    4914     }
     1705          base.getRgbItem(tinted, 0, baseBuf, pos);
     1706          pos += baseNumComps;
     1707        }
     1708      }
     1709      if (!isPassthrough) {
     1710        base.getRgbBuffer(baseBuf, 0, count, dest, destOffset, 8, alpha01);
     1711      }
     1712    },
     1713    getOutputLength: function AlternateCS_getOutputLength(inputLength,
     1714                                                          alpha01) {
     1715      return this.base.getOutputLength(inputLength *
     1716                                       this.base.numComps / this.numComps,
     1717                                       alpha01);
     1718    },
     1719    isPassthrough: ColorSpace.prototype.isPassthrough,
     1720    fillRgb: ColorSpace.prototype.fillRgb,
     1721    isDefaultDecode: function AlternateCS_isDefaultDecode(decodeMap) {
     1722      return ColorSpace.isDefaultDecode(decodeMap, this.numComps);
     1723    },
     1724    usesZeroToOneRange: true
    49151725  };
    49161726
    4917   return CanvasGraphics;
     1727  return AlternateCS;
    49181728})();
    49191729
    4920 
    4921 
    4922 var Name = (function NameClosure() {
    4923   function Name(name) {
    4924     this.name = name;
    4925   }
    4926 
    4927   Name.prototype = {};
    4928 
    4929   return Name;
     1730var PatternCS = (function PatternCSClosure() {
     1731  function PatternCS(baseCS) {
     1732    this.name = 'Pattern';
     1733    this.base = baseCS;
     1734  }
     1735  PatternCS.prototype = {};
     1736
     1737  return PatternCS;
    49301738})();
    49311739
    4932 var Cmd = (function CmdClosure() {
    4933   function Cmd(cmd) {
    4934     this.cmd = cmd;
    4935   }
    4936 
    4937   Cmd.prototype = {};
    4938 
    4939   var cmdCache = {};
    4940 
    4941   Cmd.get = function Cmd_get(cmd) {
    4942     var cmdValue = cmdCache[cmd];
    4943     if (cmdValue)
    4944       return cmdValue;
    4945 
    4946     return cmdCache[cmd] = new Cmd(cmd);
     1740var IndexedCS = (function IndexedCSClosure() {
     1741  function IndexedCS(base, highVal, lookup) {
     1742    this.name = 'Indexed';
     1743    this.numComps = 1;
     1744    this.defaultColor = new Uint8Array([0]);
     1745    this.base = base;
     1746    this.highVal = highVal;
     1747
     1748    var baseNumComps = base.numComps;
     1749    var length = baseNumComps * highVal;
     1750    var lookupArray;
     1751
     1752    if (isStream(lookup)) {
     1753      lookupArray = new Uint8Array(length);
     1754      var bytes = lookup.getBytes(length);
     1755      lookupArray.set(bytes);
     1756    } else if (isString(lookup)) {
     1757      lookupArray = new Uint8Array(length);
     1758      for (var i = 0; i < length; ++i)
     1759        lookupArray[i] = lookup.charCodeAt(i);
     1760    } else if (lookup instanceof Uint8Array || lookup instanceof Array) {
     1761      lookupArray = lookup;
     1762    } else {
     1763      error('Unrecognized lookup table: ' + lookup);
     1764    }
     1765    this.lookup = lookupArray;
     1766  }
     1767
     1768  IndexedCS.prototype = {
     1769    getRgb: function IndexedCS_getRgb(src, srcOffset) {
     1770      var numComps = this.base.numComps;
     1771      var start = src[srcOffset] * numComps;
     1772      return this.base.getRgb(this.lookup, start);
     1773    },
     1774    getRgbItem: function IndexedCS_getRgbItem(src, srcOffset,
     1775                                              dest, destOffset) {
     1776      var numComps = this.base.numComps;
     1777      var start = src[srcOffset] * numComps;
     1778      this.base.getRgbItem(this.lookup, start, dest, destOffset);
     1779    },
     1780    getRgbBuffer: function IndexedCS_getRgbBuffer(src, srcOffset, count,
     1781                                                  dest, destOffset, bits,
     1782                                                  alpha01) {
     1783      var base = this.base;
     1784      var numComps = base.numComps;
     1785      var outputDelta = base.getOutputLength(numComps, alpha01);
     1786      var lookup = this.lookup;
     1787
     1788      for (var i = 0; i < count; ++i) {
     1789        var lookupPos = src[srcOffset++] * numComps;
     1790        base.getRgbBuffer(lookup, lookupPos, 1, dest, destOffset, 8, alpha01);
     1791        destOffset += outputDelta;
     1792      }
     1793    },
     1794    getOutputLength: function IndexedCS_getOutputLength(inputLength, alpha01) {
     1795      return this.base.getOutputLength(inputLength * this.base.numComps,
     1796                                       alpha01);
     1797    },
     1798    isPassthrough: ColorSpace.prototype.isPassthrough,
     1799    fillRgb: ColorSpace.prototype.fillRgb,
     1800    isDefaultDecode: function IndexedCS_isDefaultDecode(decodeMap) {
     1801      // indexed color maps shouldn't be changed
     1802      return true;
     1803    },
     1804    usesZeroToOneRange: true
    49471805  };
    4948 
    4949   return Cmd;
     1806  return IndexedCS;
    49501807})();
    49511808
    4952 var Dict = (function DictClosure() {
    4953   var nonSerializable = function nonSerializableClosure() {
    4954     return nonSerializable; // creating closure on some variable
     1809var DeviceGrayCS = (function DeviceGrayCSClosure() {
     1810  function DeviceGrayCS() {
     1811    this.name = 'DeviceGray';
     1812    this.numComps = 1;
     1813    this.defaultColor = new Float32Array([0]);
     1814  }
     1815
     1816  DeviceGrayCS.prototype = {
     1817    getRgb: function DeviceGrayCS_getRgb(src, srcOffset) {
     1818      var rgb = new Uint8Array(3);
     1819      this.getRgbItem(src, srcOffset, rgb, 0);
     1820      return rgb;
     1821    },
     1822    getRgbItem: function DeviceGrayCS_getRgbItem(src, srcOffset,
     1823                                                 dest, destOffset) {
     1824      var c = (src[srcOffset] * 255) | 0;
     1825      c = c < 0 ? 0 : c > 255 ? 255 : c;
     1826      dest[destOffset] = dest[destOffset + 1] = dest[destOffset + 2] = c;
     1827    },
     1828    getRgbBuffer: function DeviceGrayCS_getRgbBuffer(src, srcOffset, count,
     1829                                                     dest, destOffset, bits,
     1830                                                     alpha01) {
     1831      var scale = 255 / ((1 << bits) - 1);
     1832      var j = srcOffset, q = destOffset;
     1833      for (var i = 0; i < count; ++i) {
     1834        var c = (scale * src[j++]) | 0;
     1835        dest[q++] = c;
     1836        dest[q++] = c;
     1837        dest[q++] = c;
     1838        q += alpha01;
     1839      }
     1840    },
     1841    getOutputLength: function DeviceGrayCS_getOutputLength(inputLength,
     1842                                                           alpha01) {
     1843      return inputLength * (3 + alpha01);
     1844    },
     1845    isPassthrough: ColorSpace.prototype.isPassthrough,
     1846    fillRgb: ColorSpace.prototype.fillRgb,
     1847    isDefaultDecode: function DeviceGrayCS_isDefaultDecode(decodeMap) {
     1848      return ColorSpace.isDefaultDecode(decodeMap, this.numComps);
     1849    },
     1850    usesZeroToOneRange: true
    49551851  };
    4956 
    4957   // xref is optional
    4958   function Dict(xref) {
    4959     // Map should only be used internally, use functions below to access.
    4960     this.map = Object.create(null);
    4961     this.xref = xref;
    4962     this.__nonSerializable__ = nonSerializable; // disable cloning of the Dict
    4963   }
    4964 
    4965   Dict.prototype = {
    4966     assignXref: function Dict_assignXref(newXref) {
    4967       this.xref = newXref;
    4968     },
    4969 
    4970     // automatically dereferences Ref objects
    4971     get: function Dict_get(key1, key2, key3) {
    4972       var value;
    4973       var xref = this.xref;
    4974       if (typeof (value = this.map[key1]) != 'undefined' || key1 in this.map ||
    4975           typeof key2 == 'undefined') {
    4976         return xref ? xref.fetchIfRef(value) : value;
    4977       }
    4978       if (typeof (value = this.map[key2]) != 'undefined' || key2 in this.map ||
    4979           typeof key3 == 'undefined') {
    4980         return xref ? xref.fetchIfRef(value) : value;
    4981       }
    4982       value = this.map[key3] || null;
    4983       return xref ? xref.fetchIfRef(value) : value;
    4984     },
    4985 
    4986     // Same as get(), but returns a promise and uses fetchIfRefAsync().
    4987     getAsync: function Dict_getAsync(key1, key2, key3) {
    4988       var value;
    4989       var promise;
    4990       var xref = this.xref;
    4991       if (typeof (value = this.map[key1]) !== undefined || key1 in this.map ||
    4992           typeof key2 === undefined) {
    4993         if (xref) {
    4994           return xref.fetchIfRefAsync(value);
    4995         }
    4996         promise = new Promise();
    4997         promise.resolve(value);
    4998         return promise;
    4999       }
    5000       if (typeof (value = this.map[key2]) !== undefined || key2 in this.map ||
    5001           typeof key3 === undefined) {
    5002         if (xref) {
    5003           return xref.fetchIfRefAsync(value);
    5004         }
    5005         promise = new Promise();
    5006         promise.resolve(value);
    5007         return promise;
    5008       }
    5009       value = this.map[key3] || null;
    5010       if (xref) {
    5011         return xref.fetchIfRefAsync(value);
    5012       }
    5013       promise = new Promise();
    5014       promise.resolve(value);
    5015       return promise;
    5016     },
    5017 
    5018     // no dereferencing
    5019     getRaw: function Dict_getRaw(key) {
    5020       return this.map[key];
    5021     },
    5022 
    5023     // creates new map and dereferences all Refs
    5024     getAll: function Dict_getAll() {
    5025       var all = {};
    5026       for (var key in this.map) {
    5027         var obj = this.get(key);
    5028         all[key] = obj instanceof Dict ? obj.getAll() : obj;
    5029       }
    5030       return all;
    5031     },
    5032 
    5033     set: function Dict_set(key, value) {
    5034       this.map[key] = value;
    5035     },
    5036 
    5037     has: function Dict_has(key) {
    5038       return key in this.map;
    5039     },
    5040 
    5041     forEach: function Dict_forEach(callback) {
    5042       for (var key in this.map) {
    5043         callback(key, this.get(key));
    5044       }
    5045     }
     1852  return DeviceGrayCS;
     1853})();
     1854
     1855var DeviceRgbCS = (function DeviceRgbCSClosure() {
     1856  function DeviceRgbCS() {
     1857    this.name = 'DeviceRGB';
     1858    this.numComps = 3;
     1859    this.defaultColor = new Float32Array([0, 0, 0]);
     1860  }
     1861  DeviceRgbCS.prototype = {
     1862    getRgb: function DeviceRgbCS_getRgb(src, srcOffset) {
     1863      var rgb = new Uint8Array(3);
     1864      this.getRgbItem(src, srcOffset, rgb, 0);
     1865      return rgb;
     1866    },
     1867    getRgbItem: function DeviceRgbCS_getRgbItem(src, srcOffset,
     1868                                                dest, destOffset) {
     1869      var r = (src[srcOffset] * 255) | 0;
     1870      var g = (src[srcOffset + 1] * 255) | 0;
     1871      var b = (src[srcOffset + 2] * 255) | 0;
     1872      dest[destOffset] = r < 0 ? 0 : r > 255 ? 255 : r;
     1873      dest[destOffset + 1] = g < 0 ? 0 : g > 255 ? 255 : g;
     1874      dest[destOffset + 2] = b < 0 ? 0 : b > 255 ? 255 : b;
     1875    },
     1876    getRgbBuffer: function DeviceRgbCS_getRgbBuffer(src, srcOffset, count,
     1877                                                    dest, destOffset, bits,
     1878                                                    alpha01) {
     1879      if (bits === 8 && alpha01 === 0) {
     1880        dest.set(src.subarray(srcOffset, srcOffset + count * 3), destOffset);
     1881        return;
     1882      }
     1883      var scale = 255 / ((1 << bits) - 1);
     1884      var j = srcOffset, q = destOffset;
     1885      for (var i = 0; i < count; ++i) {
     1886        dest[q++] = (scale * src[j++]) | 0;
     1887        dest[q++] = (scale * src[j++]) | 0;
     1888        dest[q++] = (scale * src[j++]) | 0;
     1889        q += alpha01;
     1890      }
     1891    },
     1892    getOutputLength: function DeviceRgbCS_getOutputLength(inputLength,
     1893                                                          alpha01) {
     1894      return (inputLength * (3 + alpha01) / 3) | 0;
     1895    },
     1896    isPassthrough: function DeviceRgbCS_isPassthrough(bits) {
     1897      return bits == 8;
     1898    },
     1899    fillRgb: ColorSpace.prototype.fillRgb,
     1900    isDefaultDecode: function DeviceRgbCS_isDefaultDecode(decodeMap) {
     1901      return ColorSpace.isDefaultDecode(decodeMap, this.numComps);
     1902    },
     1903    usesZeroToOneRange: true
    50461904  };
    5047 
    5048   return Dict;
     1905  return DeviceRgbCS;
    50491906})();
    50501907
    5051 var Ref = (function RefClosure() {
    5052   function Ref(num, gen) {
    5053     this.num = num;
    5054     this.gen = gen;
    5055   }
    5056 
    5057   Ref.prototype = {};
    5058 
    5059   return Ref;
     1908var DeviceCmykCS = (function DeviceCmykCSClosure() {
     1909  // The coefficients below was found using numerical analysis: the method of
     1910  // steepest descent for the sum((f_i - color_value_i)^2) for r/g/b colors,
     1911  // where color_value is the tabular value from the table of sampled RGB colors
     1912  // from CMYK US Web Coated (SWOP) colorspace, and f_i is the corresponding
     1913  // CMYK color conversion using the estimation below:
     1914  //   f(A, B,.. N) = Acc+Bcm+Ccy+Dck+c+Fmm+Gmy+Hmk+Im+Jyy+Kyk+Ly+Mkk+Nk+255
     1915  function convertToRgb(src, srcOffset, srcScale, dest, destOffset) {
     1916    var c = src[srcOffset + 0] * srcScale;
     1917    var m = src[srcOffset + 1] * srcScale;
     1918    var y = src[srcOffset + 2] * srcScale;
     1919    var k = src[srcOffset + 3] * srcScale;
     1920
     1921    var r =
     1922      c * (-4.387332384609988 * c + 54.48615194189176 * m +
     1923           18.82290502165302 * y + 212.25662451639585 * k +
     1924           -285.2331026137004) +
     1925      m * (1.7149763477362134 * m - 5.6096736904047315 * y +
     1926           -17.873870861415444 * k - 5.497006427196366) +
     1927      y * (-2.5217340131683033 * y - 21.248923337353073 * k +
     1928           17.5119270841813) +
     1929      k * (-21.86122147463605 * k - 189.48180835922747) + 255;
     1930    var g =
     1931      c * (8.841041422036149 * c + 60.118027045597366 * m +
     1932           6.871425592049007 * y + 31.159100130055922 * k +
     1933           -79.2970844816548) +
     1934      m * (-15.310361306967817 * m + 17.575251261109482 * y +
     1935           131.35250912493976 * k - 190.9453302588951) +
     1936      y * (4.444339102852739 * y + 9.8632861493405 * k - 24.86741582555878) +
     1937      k * (-20.737325471181034 * k - 187.80453709719578) + 255;
     1938    var b =
     1939      c * (0.8842522430003296 * c + 8.078677503112928 * m +
     1940           30.89978309703729 * y - 0.23883238689178934 * k +
     1941           -14.183576799673286) +
     1942      m * (10.49593273432072 * m + 63.02378494754052 * y +
     1943           50.606957656360734 * k - 112.23884253719248) +
     1944      y * (0.03296041114873217 * y + 115.60384449646641 * k +
     1945           -193.58209356861505) +
     1946      k * (-22.33816807309886 * k - 180.12613974708367) + 255;
     1947
     1948    dest[destOffset] = r > 255 ? 255 : r < 0 ? 0 : r;
     1949    dest[destOffset + 1] = g > 255 ? 255 : g < 0 ? 0 : g;
     1950    dest[destOffset + 2] = b > 255 ? 255 : b < 0 ? 0 : b;
     1951  }
     1952
     1953  function DeviceCmykCS() {
     1954    this.name = 'DeviceCMYK';
     1955    this.numComps = 4;
     1956    this.defaultColor = new Float32Array([0, 0, 0, 1]);
     1957  }
     1958  DeviceCmykCS.prototype = {
     1959    getRgb: function DeviceCmykCS_getRgb(src, srcOffset) {
     1960      var rgb = new Uint8Array(3);
     1961      convertToRgb(src, srcOffset, 1, rgb, 0);
     1962      return rgb;
     1963    },
     1964    getRgbItem: function DeviceCmykCS_getRgbItem(src, srcOffset,
     1965                                                 dest, destOffset) {
     1966      convertToRgb(src, srcOffset, 1, dest, destOffset);
     1967    },
     1968    getRgbBuffer: function DeviceCmykCS_getRgbBuffer(src, srcOffset, count,
     1969                                                     dest, destOffset, bits,
     1970                                                     alpha01) {
     1971      var scale = 1 / ((1 << bits) - 1);
     1972      for (var i = 0; i < count; i++) {
     1973        convertToRgb(src, srcOffset, scale, dest, destOffset);
     1974        srcOffset += 4;
     1975        destOffset += 3 + alpha01;
     1976      }
     1977    },
     1978    getOutputLength: function DeviceCmykCS_getOutputLength(inputLength,
     1979                                                           alpha01) {
     1980      return (inputLength / 4 * (3 + alpha01)) | 0;
     1981    },
     1982    isPassthrough: ColorSpace.prototype.isPassthrough,
     1983    fillRgb: ColorSpace.prototype.fillRgb,
     1984    isDefaultDecode: function DeviceCmykCS_isDefaultDecode(decodeMap) {
     1985      return ColorSpace.isDefaultDecode(decodeMap, this.numComps);
     1986    },
     1987    usesZeroToOneRange: true
     1988  };
     1989
     1990  return DeviceCmykCS;
    50601991})();
    50611992
    5062 // The reference is identified by number and generation,
    5063 // this structure stores only one instance of the reference.
    5064 var RefSet = (function RefSetClosure() {
    5065   function RefSet() {
    5066     this.dict = {};
    5067   }
    5068 
    5069   RefSet.prototype = {
    5070     has: function RefSet_has(ref) {
    5071       return ('R' + ref.num + '.' + ref.gen) in this.dict;
    5072     },
    5073 
    5074     put: function RefSet_put(ref) {
    5075       this.dict['R' + ref.num + '.' + ref.gen] = true;
    5076     },
    5077 
    5078     remove: function RefSet_remove(ref) {
    5079       delete this.dict['R' + ref.num + '.' + ref.gen];
    5080     }
     1993//
     1994// CalGrayCS: Based on "PDF Reference, Sixth Ed", p.245
     1995//
     1996var CalGrayCS = (function CalGrayCSClosure() {
     1997  function CalGrayCS(whitePoint, blackPoint, gamma) {
     1998    this.name = 'CalGray';
     1999    this.numComps = 1;
     2000    this.defaultColor = new Float32Array([0]);
     2001
     2002    if (!whitePoint) {
     2003      error('WhitePoint missing - required for color space CalGray');
     2004    }
     2005    blackPoint = blackPoint || [0, 0, 0];
     2006    gamma = gamma || 1;
     2007
     2008    // Translate arguments to spec variables.
     2009    this.XW = whitePoint[0];
     2010    this.YW = whitePoint[1];
     2011    this.ZW = whitePoint[2];
     2012
     2013    this.XB = blackPoint[0];
     2014    this.YB = blackPoint[1];
     2015    this.ZB = blackPoint[2];
     2016
     2017    this.G = gamma;
     2018
     2019    // Validate variables as per spec.
     2020    if (this.XW < 0 || this.ZW < 0 || this.YW !== 1) {
     2021      error('Invalid WhitePoint components for ' + this.name +
     2022            ', no fallback available');
     2023    }
     2024
     2025    if (this.XB < 0 || this.YB < 0 || this.ZB < 0) {
     2026      info('Invalid BlackPoint for ' + this.name + ', falling back to default');
     2027      this.XB = this.YB = this.ZB = 0;
     2028    }
     2029
     2030    if (this.XB !== 0 || this.YB !== 0 || this.ZB !== 0) {
     2031      warn(this.name + ', BlackPoint: XB: ' + this.XB + ', YB: ' + this.YB +
     2032           ', ZB: ' + this.ZB + ', only default values are supported.');
     2033    }
     2034
     2035    if (this.G < 1) {
     2036      info('Invalid Gamma: ' + this.G + ' for ' + this.name +
     2037           ', falling back to default');
     2038      this.G = 1;
     2039    }
     2040  }
     2041
     2042  function convertToRgb(cs, src, srcOffset, dest, destOffset, scale) {
     2043    // A represents a gray component of a calibrated gray space.
     2044    // A <---> AG in the spec
     2045    var A = src[srcOffset] * scale;
     2046    var AG = Math.pow(A, cs.G);
     2047
     2048    // Computes intermediate variables M, L, N as per spec.
     2049    // Except if other than default BlackPoint values are used.
     2050    var M = cs.XW * AG;
     2051    var L = cs.YW * AG;
     2052    var N = cs.ZW * AG;
     2053
     2054    // Decode XYZ, as per spec.
     2055    var X = M;
     2056    var Y = L;
     2057    var Z = N;
     2058
     2059    // http://www.poynton.com/notes/colour_and_gamma/ColorFAQ.html, Ch 4.
     2060    // This yields values in range [0, 100].
     2061    var Lstar = Math.max(116 * Math.pow(Y, 1 / 3) - 16, 0);
     2062
     2063    // Convert values to rgb range [0, 255].
     2064    dest[destOffset] = Lstar * 255 / 100;
     2065    dest[destOffset + 1] = Lstar * 255 / 100;
     2066    dest[destOffset + 2] = Lstar * 255 / 100;
     2067  }
     2068
     2069  CalGrayCS.prototype = {
     2070    getRgb: function CalGrayCS_getRgb(src, srcOffset) {
     2071      var rgb = new Uint8Array(3);
     2072      this.getRgbItem(src, srcOffset, rgb, 0);
     2073      return rgb;
     2074    },
     2075    getRgbItem: function CalGrayCS_getRgbItem(src, srcOffset,
     2076                                              dest, destOffset) {
     2077      convertToRgb(this, src, srcOffset, dest, destOffset, 1);
     2078    },
     2079    getRgbBuffer: function CalGrayCS_getRgbBuffer(src, srcOffset, count,
     2080                                                  dest, destOffset, bits,
     2081                                                  alpha01) {
     2082      var scale = 1 / ((1 << bits) - 1);
     2083
     2084      for (var i = 0; i < count; ++i) {
     2085        convertToRgb(this, src, srcOffset, dest, destOffset, scale);
     2086        srcOffset += 1;
     2087        destOffset += 3 + alpha01;
     2088      }
     2089    },
     2090    getOutputLength: function CalGrayCS_getOutputLength(inputLength, alpha01) {
     2091      return inputLength * (3 + alpha01);
     2092    },
     2093    isPassthrough: ColorSpace.prototype.isPassthrough,
     2094    fillRgb: ColorSpace.prototype.fillRgb,
     2095    isDefaultDecode: function CalGrayCS_isDefaultDecode(decodeMap) {
     2096      return ColorSpace.isDefaultDecode(decodeMap, this.numComps);
     2097    },
     2098    usesZeroToOneRange: true
    50812099  };
    5082 
    5083   return RefSet;
     2100  return CalGrayCS;
    50842101})();
    50852102
    5086 var Catalog = (function CatalogClosure() {
    5087   function Catalog(pdfManager, xref) {
    5088     this.pdfManager = pdfManager;
    5089     this.xref = xref;
    5090     this.catDict = xref.getCatalogObj();
    5091     assertWellFormed(isDict(this.catDict),
    5092       'catalog object is not a dictionary');
    5093 
    5094     // Stores state as we traverse the pages catalog so that we can resume
    5095     // parsing if an exception is thrown
    5096     this.traversePagesQueue = [{
    5097       pagesDict: this.toplevelPagesDict,
    5098       posInKids: 0
    5099     }];
    5100     this.pagePromises = [];
    5101     this.currPageIndex = 0;
    5102   }
    5103 
    5104   Catalog.prototype = {
    5105     get metadata() {
    5106       var streamRef = this.catDict.getRaw('Metadata');
    5107       if (!isRef(streamRef))
    5108         return shadow(this, 'metadata', null);
    5109 
    5110       var encryptMetadata = !this.xref.encrypt ? false :
    5111         this.xref.encrypt.encryptMetadata;
    5112 
    5113       var stream = this.xref.fetch(streamRef, !encryptMetadata);
    5114       var metadata;
    5115       if (stream && isDict(stream.dict)) {
    5116         var type = stream.dict.get('Type');
    5117         var subtype = stream.dict.get('Subtype');
    5118 
    5119         if (isName(type) && isName(subtype) &&
    5120             type.name === 'Metadata' && subtype.name === 'XML') {
    5121           // XXX: This should examine the charset the XML document defines,
    5122           // however since there are currently no real means to decode
    5123           // arbitrary charsets, let's just hope that the author of the PDF
    5124           // was reasonable enough to stick with the XML default charset,
    5125           // which is UTF-8.
    5126           try {
    5127             metadata = stringToUTF8String(bytesToString(stream.getBytes()));
    5128           } catch (e) {
    5129             info('Skipping invalid metadata.');
    5130           }
    5131         }
    5132       }
    5133 
    5134       return shadow(this, 'metadata', metadata);
    5135     },
    5136     get toplevelPagesDict() {
    5137       var pagesObj = this.catDict.get('Pages');
    5138       assertWellFormed(isDict(pagesObj), 'invalid top-level pages dictionary');
    5139       // shadow the prototype getter
    5140       return shadow(this, 'toplevelPagesDict', pagesObj);
    5141     },
    5142     get documentOutline() {
    5143       var xref = this.xref;
    5144       var obj = this.catDict.get('Outlines');
    5145       var root = { items: [] };
    5146       if (isDict(obj)) {
    5147         obj = obj.getRaw('First');
    5148         var processed = new RefSet();
    5149         if (isRef(obj)) {
    5150           var queue = [{obj: obj, parent: root}];
    5151           // to avoid recursion keeping track of the items
    5152           // in the processed dictionary
    5153           processed.put(obj);
    5154           while (queue.length > 0) {
    5155             var i = queue.shift();
    5156             var outlineDict = xref.fetchIfRef(i.obj);
    5157             if (outlineDict === null)
    5158               continue;
    5159             if (!outlineDict.has('Title'))
    5160               error('Invalid outline item');
    5161             var dest = outlineDict.get('A');
    5162             if (dest)
    5163               dest = dest.get('D');
    5164             else if (outlineDict.has('Dest')) {
    5165               dest = outlineDict.getRaw('Dest');
    5166               if (isName(dest))
    5167                 dest = dest.name;
    5168             }
    5169             var title = outlineDict.get('Title');
    5170             var outlineItem = {
    5171               dest: dest,
    5172               title: stringToPDFString(title),
    5173               color: outlineDict.get('C') || [0, 0, 0],
    5174               count: outlineDict.get('Count'),
    5175               bold: !!(outlineDict.get('F') & 2),
    5176               italic: !!(outlineDict.get('F') & 1),
    5177               items: []
    5178             };
    5179             i.parent.items.push(outlineItem);
    5180             obj = outlineDict.getRaw('First');
    5181             if (isRef(obj) && !processed.has(obj)) {
    5182               queue.push({obj: obj, parent: outlineItem});
    5183               processed.put(obj);
    5184             }
    5185             obj = outlineDict.getRaw('Next');
    5186             if (isRef(obj) && !processed.has(obj)) {
    5187               queue.push({obj: obj, parent: i.parent});
    5188               processed.put(obj);
    5189             }
    5190           }
    5191         }
    5192       }
    5193       obj = root.items.length > 0 ? root.items : null;
    5194       return shadow(this, 'documentOutline', obj);
    5195     },
    5196     get numPages() {
    5197       var obj = this.toplevelPagesDict.get('Count');
    5198       assertWellFormed(
    5199         isInt(obj),
    5200         'page count in top level pages object is not an integer'
    5201       );
    5202       // shadow the prototype getter
    5203       return shadow(this, 'num', obj);
    5204     },
    5205     get destinations() {
    5206       function fetchDestination(dest) {
    5207         return isDict(dest) ? dest.get('D') : dest;
    5208       }
    5209 
    5210       var xref = this.xref;
    5211       var dests = {}, nameTreeRef, nameDictionaryRef;
    5212       var obj = this.catDict.get('Names');
    5213       if (obj)
    5214         nameTreeRef = obj.getRaw('Dests');
    5215       else if (this.catDict.has('Dests'))
    5216         nameDictionaryRef = this.catDict.get('Dests');
    5217 
    5218       if (nameDictionaryRef) {
    5219         // reading simple destination dictionary
    5220         obj = nameDictionaryRef;
    5221         obj.forEach(function catalogForEach(key, value) {
    5222           if (!value) return;
    5223           dests[key] = fetchDestination(value);
    5224         });
    5225       }
    5226       if (nameTreeRef) {
    5227         var nameTree = new NameTree(nameTreeRef, xref);
    5228         var names = nameTree.getAll();
    5229         for (var name in names) {
    5230           if (!names.hasOwnProperty(name)) {
    5231             continue;
    5232           }
    5233           dests[name] = fetchDestination(names[name]);
    5234         }
    5235       }
    5236       return shadow(this, 'destinations', dests);
    5237     },
    5238     get javaScript() {
    5239       var xref = this.xref;
    5240       var obj = this.catDict.get('Names');
    5241 
    5242       var javaScript = [];
    5243       if (obj && obj.has('JavaScript')) {
    5244         var nameTree = new NameTree(obj.getRaw('JavaScript'), xref);
    5245         var names = nameTree.getAll();
    5246         for (var name in names) {
    5247           if (!names.hasOwnProperty(name)) {
    5248             continue;
    5249           }
    5250           // We don't really use the JavaScript right now so this code is
    5251           // defensive so we don't cause errors on document load.
    5252           var jsDict = names[name];
    5253           if (!isDict(jsDict)) {
    5254             continue;
    5255           }
    5256           var type = jsDict.get('S');
    5257           if (!isName(type) || type.name !== 'JavaScript') {
    5258             continue;
    5259           }
    5260           var js = jsDict.get('JS');
    5261           if (!isString(js) && !isStream(js)) {
    5262             continue;
    5263           }
    5264           if (isStream(js)) {
    5265             js = bytesToString(js.getBytes());
    5266           }
    5267           javaScript.push(stringToPDFString(js));
    5268         }
    5269       }
    5270       return shadow(this, 'javaScript', javaScript);
    5271     },
    5272 
    5273     getPage: function Catalog_getPage(pageIndex) {
    5274       if (!(pageIndex in this.pagePromises)) {
    5275         this.pagePromises[pageIndex] = new Promise();
    5276       }
    5277       return this.pagePromises[pageIndex];
    5278     },
    5279 
    5280     // Traverses pages in DFS order so that pages are processed in increasing
    5281     // order
    5282     traversePages: function Catalog_traversePages() {
    5283       var queue = this.traversePagesQueue;
    5284       while (queue.length) {
    5285         var queueItem = queue[queue.length - 1];
    5286         var pagesDict = queueItem.pagesDict;
    5287 
    5288         var kids = pagesDict.get('Kids');
    5289         assert(isArray(kids), 'page dictionary kids object is not an array');
    5290         if (queueItem.posInKids >= kids.length) {
    5291           queue.pop();
    5292           continue;
    5293         }
    5294         var kidRef = kids[queueItem.posInKids];
    5295         assert(isRef(kidRef), 'page dictionary kid is not a reference');
    5296 
    5297         var kid = this.xref.fetch(kidRef);
    5298         if (isDict(kid, 'Page') || (isDict(kid) && !kid.has('Kids'))) {
    5299           var pageIndex = this.currPageIndex++;
    5300           var page = new Page(this.pdfManager, this.xref, pageIndex, kid,
    5301                               kidRef);
    5302           if (!(pageIndex in this.pagePromises)) {
    5303             this.pagePromises[pageIndex] = new Promise();
    5304           }
    5305           this.pagePromises[pageIndex].resolve(page);
    5306 
    5307         } else { // must be a child page dictionary
    5308           assert(
    5309             isDict(kid),
    5310             'page dictionary kid reference points to wrong type of object'
    5311           );
    5312 
    5313           queue.push({
    5314             pagesDict: kid,
    5315             posInKids: 0
    5316           });
    5317         }
    5318         ++queueItem.posInKids;
    5319       }
    5320     }
     2103//
     2104// LabCS: Based on "PDF Reference, Sixth Ed", p.250
     2105//
     2106var LabCS = (function LabCSClosure() {
     2107  function LabCS(whitePoint, blackPoint, range) {
     2108    this.name = 'Lab';
     2109    this.numComps = 3;
     2110    this.defaultColor = new Float32Array([0, 0, 0]);
     2111
     2112    if (!whitePoint)
     2113      error('WhitePoint missing - required for color space Lab');
     2114    blackPoint = blackPoint || [0, 0, 0];
     2115    range = range || [-100, 100, -100, 100];
     2116
     2117    // Translate args to spec variables
     2118    this.XW = whitePoint[0];
     2119    this.YW = whitePoint[1];
     2120    this.ZW = whitePoint[2];
     2121    this.amin = range[0];
     2122    this.amax = range[1];
     2123    this.bmin = range[2];
     2124    this.bmax = range[3];
     2125
     2126    // These are here just for completeness - the spec doesn't offer any
     2127    // formulas that use BlackPoint in Lab
     2128    this.XB = blackPoint[0];
     2129    this.YB = blackPoint[1];
     2130    this.ZB = blackPoint[2];
     2131
     2132    // Validate vars as per spec
     2133    if (this.XW < 0 || this.ZW < 0 || this.YW !== 1)
     2134      error('Invalid WhitePoint components, no fallback available');
     2135
     2136    if (this.XB < 0 || this.YB < 0 || this.ZB < 0) {
     2137      info('Invalid BlackPoint, falling back to default');
     2138      this.XB = this.YB = this.ZB = 0;
     2139    }
     2140
     2141    if (this.amin > this.amax || this.bmin > this.bmax) {
     2142      info('Invalid Range, falling back to defaults');
     2143      this.amin = -100;
     2144      this.amax = 100;
     2145      this.bmin = -100;
     2146      this.bmax = 100;
     2147    }
     2148  }
     2149
     2150  // Function g(x) from spec
     2151  function fn_g(x) {
     2152    if (x >= 6 / 29)
     2153      return x * x * x;
     2154    else
     2155      return (108 / 841) * (x - 4 / 29);
     2156  }
     2157
     2158  function decode(value, high1, low2, high2) {
     2159    return low2 + (value) * (high2 - low2) / (high1);
     2160  }
     2161
     2162  // If decoding is needed maxVal should be 2^bits per component - 1.
     2163  function convertToRgb(cs, src, srcOffset, maxVal, dest, destOffset) {
     2164    // XXX: Lab input is in the range of [0, 100], [amin, amax], [bmin, bmax]
     2165    // not the usual [0, 1]. If a command like setFillColor is used the src
     2166    // values will already be within the correct range. However, if we are
     2167    // converting an image we have to map the values to the correct range given
     2168    // above.
     2169    // Ls,as,bs <---> L*,a*,b* in the spec
     2170    var Ls = src[srcOffset];
     2171    var as = src[srcOffset + 1];
     2172    var bs = src[srcOffset + 2];
     2173    if (maxVal !== false) {
     2174      Ls = decode(Ls, maxVal, 0, 100);
     2175      as = decode(as, maxVal, cs.amin, cs.amax);
     2176      bs = decode(bs, maxVal, cs.bmin, cs.bmax);
     2177    }
     2178
     2179    // Adjust limits of 'as' and 'bs'
     2180    as = as > cs.amax ? cs.amax : as < cs.amin ? cs.amin : as;
     2181    bs = bs > cs.bmax ? cs.bmax : bs < cs.bmin ? cs.bmin : bs;
     2182
     2183    // Computes intermediate variables X,Y,Z as per spec
     2184    var M = (Ls + 16) / 116;
     2185    var L = M + (as / 500);
     2186    var N = M - (bs / 200);
     2187
     2188    var X = cs.XW * fn_g(L);
     2189    var Y = cs.YW * fn_g(M);
     2190    var Z = cs.ZW * fn_g(N);
     2191
     2192    var r, g, b;
     2193    // Using different conversions for D50 and D65 white points,
     2194    // per http://www.color.org/srgb.pdf
     2195    if (cs.ZW < 1) {
     2196      // Assuming D50 (X=0.9642, Y=1.00, Z=0.8249)
     2197      r = X * 3.1339 + Y * -1.6170 + Z * -0.4906;
     2198      g = X * -0.9785 + Y * 1.9160 + Z * 0.0333;
     2199      b = X * 0.0720 + Y * -0.2290 + Z * 1.4057;
     2200    } else {
     2201      // Assuming D65 (X=0.9505, Y=1.00, Z=1.0888)
     2202      r = X * 3.2406 + Y * -1.5372 + Z * -0.4986;
     2203      g = X * -0.9689 + Y * 1.8758 + Z * 0.0415;
     2204      b = X * 0.0557 + Y * -0.2040 + Z * 1.0570;
     2205    }
     2206    // clamp color values to [0,1] range then convert to [0,255] range.
     2207    dest[destOffset] = Math.sqrt(r < 0 ? 0 : r > 1 ? 1 : r) * 255;
     2208    dest[destOffset + 1] = Math.sqrt(g < 0 ? 0 : g > 1 ? 1 : g) * 255;
     2209    dest[destOffset + 2] = Math.sqrt(b < 0 ? 0 : b > 1 ? 1 : b) * 255;
     2210  }
     2211
     2212  LabCS.prototype = {
     2213    getRgb: function LabCS_getRgb(src, srcOffset) {
     2214      var rgb = new Uint8Array(3);
     2215      convertToRgb(this, src, srcOffset, false, rgb, 0);
     2216      return rgb;
     2217    },
     2218    getRgbItem: function LabCS_getRgbItem(src, srcOffset, dest, destOffset) {
     2219      convertToRgb(this, src, srcOffset, false, dest, destOffset);
     2220    },
     2221    getRgbBuffer: function LabCS_getRgbBuffer(src, srcOffset, count,
     2222                                              dest, destOffset, bits,
     2223                                              alpha01) {
     2224      var maxVal = (1 << bits) - 1;
     2225      for (var i = 0; i < count; i++) {
     2226        convertToRgb(this, src, srcOffset, maxVal, dest, destOffset);
     2227        srcOffset += 3;
     2228        destOffset += 3 + alpha01;
     2229      }
     2230    },
     2231    getOutputLength: function LabCS_getOutputLength(inputLength, alpha01) {
     2232      return (inputLength * (3 + alpha01) / 3) | 0;
     2233    },
     2234    isPassthrough: ColorSpace.prototype.isPassthrough,
     2235    isDefaultDecode: function LabCS_isDefaultDecode(decodeMap) {
     2236      // XXX: Decoding is handled with the lab conversion because of the strange
     2237      // ranges that are used.
     2238      return true;
     2239    },
     2240    usesZeroToOneRange: false
    53212241  };
    5322 
    5323   return Catalog;
     2242  return LabCS;
    53242243})();
    53252244
    5326 var XRef = (function XRefClosure() {
    5327   function XRef(stream, password) {
    5328 
    5329     this.stream = stream;
    5330     this.entries = [];
    5331     this.xrefstms = {};
    5332     // prepare the XRef cache
    5333     this.cache = [];
    5334     this.password = password;
    5335   }
    5336 
    5337   XRef.prototype = {
    5338     setStartXRef: function XRef_setStartXRef(startXRef) {
    5339       // Store the starting positions of xref tables as we process them
    5340       // so we can recover from missing data errors
    5341       this.startXRefQueue = [startXRef];
    5342     },
    5343 
    5344     parse: function XRef_parse(recoveryMode) {
    5345       var trailerDict;
    5346       if (!recoveryMode) {
    5347         trailerDict = this.readXRef();
    5348       } else {
    5349         warn('Indexing all PDF objects');
    5350         trailerDict = this.indexObjects();
    5351       }
    5352       trailerDict.assignXref(this);
    5353       this.trailer = trailerDict;
    5354       var encrypt = trailerDict.get('Encrypt');
    5355       if (encrypt) {
    5356         var ids = trailerDict.get('ID');
    5357         var fileId = (ids && ids.length) ? ids[0] : '';
    5358         this.encrypt = new CipherTransformFactory(
    5359             encrypt, fileId, this.password);
    5360       }
    5361 
    5362       // get the root dictionary (catalog) object
    5363       if (!(this.root = trailerDict.get('Root'))) {
    5364         error('Invalid root reference');
    5365       }
    5366     },
    5367 
    5368     processXRefTable: function XRef_processXRefTable(parser) {
    5369       if (!('tableState' in this)) {
    5370         // Stores state of the table as we process it so we can resume
    5371         // from middle of table in case of missing data error
    5372         this.tableState = {
    5373           entryNum: 0,
    5374           streamPos: parser.lexer.stream.pos,
    5375           parserBuf1: parser.buf1,
    5376           parserBuf2: parser.buf2
    5377         };
    5378       }
    5379 
    5380       var obj = this.readXRefTable(parser);
    5381 
    5382       // Sanity check
    5383       if (!isCmd(obj, 'trailer'))
    5384         error('Invalid XRef table: could not find trailer dictionary');
    5385 
    5386       // Read trailer dictionary, e.g.
    5387       // trailer
    5388       //    << /Size 22
    5389       //      /Root 20R
    5390       //      /Info 10R
    5391       //      /ID [ <81b14aafa313db63dbd6f981e49f94f4> ]
    5392       //    >>
    5393       // The parser goes through the entire stream << ... >> and provides
    5394       // a getter interface for the key-value table
    5395       var dict = parser.getObj();
    5396       if (!isDict(dict))
    5397         error('Invalid XRef table: could not parse trailer dictionary');
    5398 
    5399       delete this.tableState;
    5400 
    5401       return dict;
    5402     },
    5403 
    5404     readXRefTable: function XRef_readXRefTable(parser) {
    5405       // Example of cross-reference table:
    5406       // xref
    5407       // 0 1                    <-- subsection header (first obj #, obj count)
    5408       // 0000000000 65535 f     <-- actual object (offset, generation #, f/n)
    5409       // 23 2                   <-- subsection header ... and so on ...
    5410       // 0000025518 00002 n
    5411       // 0000025635 00000 n
    5412       // trailer
    5413       // ...
    5414 
    5415       var stream = parser.lexer.stream;
    5416       var tableState = this.tableState;
    5417       stream.pos = tableState.streamPos;
    5418       parser.buf1 = tableState.parserBuf1;
    5419       parser.buf2 = tableState.parserBuf2;
    5420 
    5421       // Outer loop is over subsection headers
    5422       var obj;
    5423 
    5424       while (true) {
    5425         if (!('firstEntryNum' in tableState) || !('entryCount' in tableState)) {
    5426           if (isCmd(obj = parser.getObj(), 'trailer')) {
    5427             break;
    5428           }
    5429           tableState.firstEntryNum = obj;
    5430           tableState.entryCount = parser.getObj();
    5431         }
    5432 
    5433         var first = tableState.firstEntryNum;
    5434         var count = tableState.entryCount;
    5435         if (!isInt(first) || !isInt(count))
    5436           error('Invalid XRef table: wrong types in subsection header');
    5437 
    5438         // Inner loop is over objects themselves
    5439         for (var i = tableState.entryNum; i < count; i++) {
    5440           tableState.streamPos = stream.pos;
    5441           tableState.entryNum = i;
    5442           tableState.parserBuf1 = parser.buf1;
    5443           tableState.parserBuf2 = parser.buf2;
    5444 
    5445           var entry = {};
    5446           entry.offset = parser.getObj();
    5447           entry.gen = parser.getObj();
    5448           var type = parser.getObj();
    5449 
    5450           if (isCmd(type, 'f'))
    5451             entry.free = true;
    5452           else if (isCmd(type, 'n'))
    5453             entry.uncompressed = true;
    5454 
    5455           // Validate entry obj
    5456           if (!isInt(entry.offset) || !isInt(entry.gen) ||
    5457               !(entry.free || entry.uncompressed)) {
    5458             console.log(entry.offset, entry.gen, entry.free,
    5459                 entry.uncompressed);
    5460             error('Invalid entry in XRef subsection: ' + first + ', ' + count);
    5461           }
    5462 
    5463           if (!this.entries[i + first])
    5464             this.entries[i + first] = entry;
    5465         }
    5466 
    5467         tableState.entryNum = 0;
    5468         tableState.streamPos = stream.pos;
    5469         tableState.parserBuf1 = parser.buf1;
    5470         tableState.parserBuf2 = parser.buf2;
    5471         delete tableState.firstEntryNum;
    5472         delete tableState.entryCount;
    5473       }
    5474 
    5475       // Sanity check: as per spec, first object must be free
    5476       if (this.entries[0] && !this.entries[0].free)
    5477         error('Invalid XRef table: unexpected first object');
    5478 
    5479       return obj;
    5480     },
    5481 
    5482     processXRefStream: function XRef_processXRefStream(stream) {
    5483       if (!('streamState' in this)) {
    5484         // Stores state of the stream as we process it so we can resume
    5485         // from middle of stream in case of missing data error
    5486         var streamParameters = stream.dict;
    5487         var byteWidths = streamParameters.get('W');
    5488         var range = streamParameters.get('Index');
    5489         if (!range) {
    5490           range = [0, streamParameters.get('Size')];
    5491         }
    5492 
    5493         this.streamState = {
    5494           entryRanges: range,
    5495           byteWidths: byteWidths,
    5496           entryNum: 0,
    5497           streamPos: stream.pos
    5498         };
    5499       }
    5500       this.readXRefStream(stream);
    5501       delete this.streamState;
    5502 
    5503       return stream.dict;
    5504     },
    5505 
    5506     readXRefStream: function XRef_readXRefStream(stream) {
    5507       var i, j;
    5508       var streamState = this.streamState;
    5509       stream.pos = streamState.streamPos;
    5510 
    5511       var byteWidths = streamState.byteWidths;
    5512       var typeFieldWidth = byteWidths[0];
    5513       var offsetFieldWidth = byteWidths[1];
    5514       var generationFieldWidth = byteWidths[2];
    5515 
    5516       var entryRanges = streamState.entryRanges;
    5517       while (entryRanges.length > 0) {
    5518 
    5519         var first = entryRanges[0];
    5520         var n = entryRanges[1];
    5521 
    5522         if (!isInt(first) || !isInt(n))
    5523           error('Invalid XRef range fields: ' + first + ', ' + n);
    5524 
    5525         if (!isInt(typeFieldWidth) || !isInt(offsetFieldWidth) ||
    5526             !isInt(generationFieldWidth)) {
    5527           error('Invalid XRef entry fields length: ' + first + ', ' + n);
    5528         }
    5529         for (i = streamState.entryNum; i < n; ++i) {
    5530           streamState.entryNum = i;
    5531           streamState.streamPos = stream.pos;
    5532 
    5533           var type = 0, offset = 0, generation = 0;
    5534           for (j = 0; j < typeFieldWidth; ++j)
    5535             type = (type << 8) | stream.getByte();
    5536           // if type field is absent, its default value = 1
    5537           if (typeFieldWidth === 0)
    5538             type = 1;
    5539           for (j = 0; j < offsetFieldWidth; ++j)
    5540             offset = (offset << 8) | stream.getByte();
    5541           for (j = 0; j < generationFieldWidth; ++j)
    5542             generation = (generation << 8) | stream.getByte();
    5543           var entry = {};
    5544           entry.offset = offset;
    5545           entry.gen = generation;
    5546           switch (type) {
    5547             case 0:
    5548               entry.free = true;
    5549               break;
    5550             case 1:
    5551               entry.uncompressed = true;
    5552               break;
    5553             case 2:
    5554               break;
    5555             default:
    5556               error('Invalid XRef entry type: ' + type);
    5557           }
    5558           if (!this.entries[first + i])
    5559